diff --git a/packages/acp-bridge/README.md b/packages/acp-bridge/README.md index ad03ac52282..8dc16ce4174 100644 --- a/packages/acp-bridge/README.md +++ b/packages/acp-bridge/README.md @@ -5,13 +5,13 @@ 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 | +| 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** (#4490) | Lift `defaultSpawnChannelFactory` + `BridgeClient` + `createHttpAcpBridge` factory closure + new `BridgeFileSystem` injection seam (22b' scope) | ✅ merged | +| **F3 PR 24** | Implement the four `PermissionMediator` policies (`first-responder`, `designated`, `consensus`, `local-only`) plus audit/emit fan-out; pair-token binding and revocation stay future scope | ✅ implemented | ## What's here today @@ -25,13 +25,12 @@ Lift history (#4175 Mode B daemon roadmap): type contract that `createHttpAcpBridge` (now in this package) plus the channels / VSCode IDE companion's own-spawn paths consume via `BridgeOptions.channelFactory`. -- `permission` — type-only `PermissionMediator` interface, +- `permission` — `PermissionMediator` interface, `PermissionPolicy` literal union (4 strategies), and - `PermissionResolution` discriminated union. **No implementation - yet** — first-responder voting still lives in - `BridgeClient.requestPermission` (in `bridgeClient.ts` after F1). - F3 PR 24 will move that and add the other three policies behind - this interface. + `PermissionResolution` discriminated union. `MultiClientPermissionMediator` + implements the four policies, owns pending/resolved permission state, and + handles strategy dispatch plus audit/emit fan-out; `BridgeClient` only + plumbs `requestPermission` into `mediator.request`. - `status` (PR 22b/1) — wire-contract status types for `/workspace/{mcp,skills,providers,env,preflight}` and `/session/:id/{context,supported-commands,tasks}` routes, the @@ -75,20 +74,20 @@ Lift history (#4175 Mode B daemon roadmap): companion consume this directly instead of each reimplementing the child lifecycle. - `bridgeClient` (F1) — `BridgeClient` class implementing the ACP - `Client` surface: first-responder permission flow, session-update - fan-out into `EventBus`, child-side `extNotification` routing, - early-event buffer + tombstone bookkeeping, inline fs proxy for - `writeTextFile` / `readTextFile`. Exports the supporting - `PendingPermission` / `PermissionResolutionRecord` / - `BridgeClientSessionEntry` types + `MAX_RESOLVED_PERMISSION_RECORDS` - cap that the factory's bookkeeping maps consume. + `Client` surface: permission requests delegated to + `PermissionMediator`, session-update fan-out into `EventBus`, + child-side `extNotification` routing, early-event buffer + tombstone + bookkeeping, inline fs proxy for `writeTextFile` / `readTextFile`. + Exports the supporting `BridgeClientSessionEntry` type consumed by + the session-entry lookup passed in by the factory. - `bridge` (F1) — `createHttpAcpBridge` factory closure (~3000 LOC) - `ChannelInfo` / `SessionEntry` interfaces + factory-only helpers (`withTimeout`, `canonicalizeExistingAncestor`, `verifyParentWithinWorkspace`, debug log helpers, - `hasControlCharacter`) + factory constants. Builds the - bookkeeping closures (`resolveEntry`, `registerPending`, etc.) - and wires them into `BridgeClient`. + `hasControlCharacter`) + factory constants. Owns session + bookkeeping, constructs the `MultiClientPermissionMediator` that + owns permission state, and passes the session-entry lookup into + `BridgeClient`. - `bridgeFileSystem` (F1) — `BridgeFileSystem` interface for the ACP fs proxy. When wired through `BridgeOptions.fileSystem`, `BridgeClient.readTextFile` / `BridgeClient.writeTextFile` @@ -135,6 +134,6 @@ exported symbols (`createHttpAcpBridge`, `defaultSpawnChannelFactory`, - #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.requestPermission` with the four `PermissionMediator` - strategies declared in `permission.ts`. +- `permissionMediator.ts` implements the four `PermissionMediator` + strategies declared in `permission.ts`; `BridgeClient.requestPermission` + delegates to that mediator. diff --git a/packages/acp-bridge/src/permission.ts b/packages/acp-bridge/src/permission.ts index 496ad9a89a6..28c4e2b04f2 100644 --- a/packages/acp-bridge/src/permission.ts +++ b/packages/acp-bridge/src/permission.ts @@ -5,36 +5,32 @@ */ /** - * `PermissionMediator` — type-only interface contract for daemon - * permission flow. **No implementation lives here.** Permission voting - * still runs inside `BridgeClient.requestPermission` - * (`@qwen-code/acp-bridge/bridgeClient`) and - * `respondToPermission` (inside `createHttpAcpBridge` factory closure - * at `@qwen-code/acp-bridge/bridge` after F1 step 3), hard-coded to - * `first-responder`. A future change will move that code behind this - * interface and add the other three policies. + * `PermissionMediator` — interface contract for daemon permission flow. + * `MultiClientPermissionMediator` in `permissionMediator.ts` owns the + * policy dispatch and pending/resolved permission state used by + * `BridgeClient.requestPermission` plus the `respondToPermission` route + * in `createHttpAcpBridge`. * - * The four policies are ordered from cheapest to strongest: + * The four policy contracts are ordered from cheapest to strongest: * * - `first-responder` — first valid `POST /permission/:requestId` * wins; later voters get `permission_already_resolved`. Today's * default; preserves the live-collaboration UX. * - `designated` — only the `originatorClientId` that started the * prompt may answer; other clients see `permission_forbidden`. - * Use case: per-tenant SaaS where a UI surface must own its own - * approvals. - * - `consensus` — N-of-M quorum across pair-token-authenticated - * clients before resolving; intermediate `permission_partial_vote` - * events let UIs render progress. Use case: enterprise change - * review where two operators must agree. + * Prompts with no originator fall back to first-responder. Use case: + * per-tenant SaaS where a UI surface must own its own approvals. + * - `consensus` — N-of-M quorum across the session client IDs + * captured when the permission request is issued. Client identity is + * self-declared until pair-token authentication lands; intermediate + * `permission_partial_vote` events let UIs render progress. Use case: + * enterprise change review where two operators must agree. * - `local-only` — refuses any HTTP voter; the prompt blocks until * a loopback client (the local TUI super-client) resolves it. * Use case: workstations where remote control should never grant * privilege escalation. * - * See `bridgeClient.ts BridgeClient.requestPermission` for the - * current first-responder implementation; the `FIXME(stage-1.5)` - * block above that method scoped this contract. + * See `permissionMediator.ts` for the implementation details. */ export type PermissionPolicy = | 'first-responder' @@ -43,10 +39,7 @@ export type PermissionPolicy = | 'local-only'; /** - * One pending permission tracked by a `PermissionMediator`. The - * shape mirrors the current `PendingPermission` record in - * `@qwen-code/acp-bridge/bridgeClient` - * so the mediation implementation's lift is a structural rename rather than a redesign. + * One pending permission tracked by a `PermissionMediator`. */ export interface PermissionRequestRecord { /** ACP `RequestPermission` request id, unique per session. */ @@ -148,9 +141,7 @@ export type PermissionResolution = /** * The contract `qwen serve`'s permission route layer talks to. - * Today there is one implementation (first-responder) wired - * inline in `BridgeClient`; The implementation will provide all four behind - * this surface plus pair-token authentication and an audit log. + * `MultiClientPermissionMediator` provides the implementation. */ export interface PermissionMediator { /** Active policy. May be reconfigured per session in future