Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
51 changes: 25 additions & 26 deletions packages/acp-bridge/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand All @@ -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
Expand Down Expand Up @@ -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`
Expand Down Expand Up @@ -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.
41 changes: 16 additions & 25 deletions packages/acp-bridge/src/permission.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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'
Expand All @@ -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. */
Expand Down Expand Up @@ -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
Expand Down
Loading