@hoptrendy/acp-bridge
v0.30.3
Published
Shared ACP bridge core (createHttpAcpBridge factory, BridgeClient, defaultSpawnChannelFactory, BridgeFileSystem injection seam) + primitives (EventBus, AcpChannel, in-memory channel, PermissionMediator interface) used by hopcode serve, channels, IDE, TUI,
Readme
@hoptrendy/hopcode-acp-bridge
Shared ACP bridge primitives consumed by hopcode serve, channels, IDE, TUI,
and remote-control adapters. Lives in the monorepo, not published to npm.
Lift history (#4175 Mode B daemon roadmap):
| Slice | Scope | Status |
| -------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------- |
| PR 22a (#4295) | Skeleton + EventBus + inMemoryChannel + AcpChannel types + PermissionMediator type-only stub | ✅ merged |
| PR 22b/1 (#4298) | Lift status + workspacePaths + bridgeErrors + bridgeTypes | ✅ merged |
| PR 22b/2 (#4304) | Lift BridgeOptions + new DaemonStatusProvider injection seam | ✅ merged |
| F1 (this PR) | Lift defaultSpawnChannelFactory + BridgeClient + createHttpAcpBridge factory closure + new BridgeFileSystem injection seam (22b' scope) | ✅ in this PR |
| F3 PR 24 | Implement the four PermissionMediator strategies (first-responder, designated, consensus, local-only) + pair-token revocation + audit log | F3 in the feature-cohesive plan |
What's here today
eventBus— per-session NDJSON pub/sub with bounded ring replay,Last-Event-IDreconnect, and slow-client backpressure (slow_client_warning→client_evicted).inMemoryChannel— paired NDJSON streams without spawning a child; used for in-process bridge tests and the parked Mode A (hopcode --serve) path.channel—AcpChannel/AcpChannelExitInfo/ChannelFactorytype contract thatcreateHttpAcpBridge(now in this package) plus the channels / VSCode IDE companion's own-spawn paths consume viaBridgeOptions.channelFactory.permission— type-onlyPermissionMediatorinterface,PermissionPolicyliteral union (4 strategies), andPermissionResolutiondiscriminated union. No implementation yet — first-responder voting still lives inBridgeClient.requestPermission(inbridgeClient.tsafter F1). F3 PR 24 will move that and add the other three policies behind this interface.status(PR 22b/1) — wire-contract status types for/workspace/{mcp,skills,providers,env,preflight}and/session/:id/{context,supported-commands,tasks}routes, theSTATUS_SCHEMA_VERSION/SERVE_*_EXT_METHODSconstants,BridgeTimeoutError/MissingCliEntryError/BridgeChannelClosedErrortyped exceptions, and themapDomainErrorToErrorKindclassifier (regex →instanceofafter #4299 / #4300). The 27-symbol contractacp-integration/acpAgent.tsconsumes lives here.workspacePaths(PR 22b/1) —canonicalizeWorkspace(the cross-module BX9_q contract used byconfig.ts/settings.ts/sandbox.ts/ bridge to collapse boot-time + per-request workspace paths to one canonical key) plusMAX_WORKSPACE_PATH_LENGTH.bridgeErrors(PR 22b/1) — 11 typedErrorsubclasses the bridge throws (SessionNotFoundError,WorkspaceMismatchError,RestoreInProgressError, etc.); HTTP route layerinstanceof-branches on these to map to specific status codes.bridgeTypes(PR 22b/1) — public bridge contract types:BridgeSpawnRequest,BridgeSession,BridgeRestoreSessionRequest,BridgeSessionState,BridgeRestoredSession,BridgeSessionSummary,SessionMetadataUpdate,BridgeClientRequestContext,BridgeHeartbeatResult,BridgeHeartbeatState, plus theHttpAcpBridgeinterface itself (~30-method facade).bridgeOptions(PR 22b/2) —BridgeOptionsinterface (factory construction contract:boundWorkspace,channelFactory,maxSessions,eventRingSize,permissionResponseTimeoutMs, persistence callbacks, etc.) plus theDaemonStatusProviderinjection seam for daemon-host env / preflight cells (production impl incli/src/serve/daemonStatusProvider.ts) and the F1BridgeFileSysteminjection seam for the ACP fs proxy.spawnChannel(F1) —defaultSpawnChannelFactory+killChild+SCRUBBED_CHILD_ENV_KEYSdenylist +scrubChildEnvpure env-policy helper (exported for adapter reuse + unit-test access; isolates the scrub + override + defense-in-depth ordering invariant the security argument relies on). Production spawn of thehopcode --acpchild with stderr prefix-and-forward, kill cascade, and env passthrough. Channels (packages/channels/base/AcpBridge.ts) and the VSCode IDE companion consume this directly instead of each reimplementing the child lifecycle.bridgeClient(F1) —BridgeClientclass implementing the ACPClientsurface: first-responder permission flow, session-update fan-out intoEventBus, child-sideextNotificationrouting, early-event buffer + tombstone bookkeeping, inline fs proxy forwriteTextFile/readTextFile. Exports the supportingPendingPermission/PermissionResolutionRecord/BridgeClientSessionEntrytypes +MAX_RESOLVED_PERMISSION_RECORDScap that the factory's bookkeeping maps consume.bridge(F1) —createHttpAcpBridgefactory closure (~3000 LOC)ChannelInfo/SessionEntryinterfaces + factory-only helpers (withTimeout,canonicalizeExistingAncestor,verifyParentWithinWorkspace, debug log helpers,hasControlCharacter) + factory constants. Builds the bookkeeping closures (resolveEntry,registerPending, etc.) and wires them intoBridgeClient.
bridgeFileSystem(F1) —BridgeFileSysteminterface for the ACP fs proxy. When wired throughBridgeOptions.fileSystem,BridgeClient.readTextFile/BridgeClient.writeTextFiledelegate to it instead of the inlinefs.realpath/fs.writeFile/fs.readFileproxy. Productionhopcode servefollow-up wraps PR 18'sWorkspaceFileSystemhere so writes get TOCTOU + symlink + trust-gate + audit guarantees.
Imports — root vs subpaths
The package exposes both a barrel root (@hoptrendy/hopcode-acp-bridge) and
per-module subpaths (/eventBus, /inMemoryChannel, /channel,
/permission, /status, /workspacePaths, /bridgeErrors,
/bridgeTypes, /bridgeOptions, /spawnChannel, /bridgeClient,
/bridge, /bridgeFileSystem). They re-export the same symbols, so
either form resolves to the same module at runtime. Pick by intent:
- Root for application/test code that uses several primitives at
once — concise and matches how
serve/imports landed today. - Subpaths for client adapters (TUI / channels / IDE / future
remoteControl) that only consume one slice — keeps the dependency surface explicit and lets bundlers tree-shake the rest.
Both variants are stable across the F1 lift.
Backward compatibility
packages/cli/src/serve/eventBus.ts and
packages/cli/src/serve/inMemoryChannel.ts remain as one-line
re-export wrappers, so every existing relative import inside
serve/ and the one external import in cli/src/commands/serve.ts
keeps resolving without churn.
After F1, packages/cli/src/serve/httpAcpBridge.ts shrinks to a
~97-line re-export shim that forwards every previously-exported
symbol (createHttpAcpBridge, defaultSpawnChannelFactory,
BridgeClient, all the typed errors, all the type aliases) from
the lifted subpaths. Every relative ./httpAcpBridge.js import in
server.ts / runHopCodeServe.ts / workspaceAgents.ts /
workspaceMemory.ts / index.ts / the bridge test suite keeps
resolving without any call-site changes.
See also
- #4175 Mode B daemon roadmap (feature-cohesive F1-F5 plan targeting
daemon_mode_b_main) - #3803
Stage 1.5-prereq AcpChannel lift(chiga0's original framing) - F3 PR 24 will replace the inline first-responder logic in
BridgeClient.requestPermissionwith the fourPermissionMediatorstrategies declared inpermission.ts.
