zcode-acp-server 0.2.0 → 0.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (64) hide show
  1. package/README.md +101 -15
  2. package/README.zh-CN.md +68 -13
  3. package/dist/bin/hub.d.ts +16 -0
  4. package/dist/bin/hub.d.ts.map +1 -0
  5. package/dist/bin/hub.js +41 -0
  6. package/dist/bin/hub.js.map +1 -0
  7. package/dist/handlers/account.d.ts +43 -0
  8. package/dist/handlers/account.d.ts.map +1 -0
  9. package/dist/handlers/account.js +59 -0
  10. package/dist/handlers/account.js.map +1 -0
  11. package/dist/handlers/io.d.ts +20 -1
  12. package/dist/handlers/io.d.ts.map +1 -1
  13. package/dist/handlers/io.js +57 -2
  14. package/dist/handlers/io.js.map +1 -1
  15. package/dist/handlers/replay.d.ts +79 -0
  16. package/dist/handlers/replay.d.ts.map +1 -0
  17. package/dist/handlers/replay.js +252 -0
  18. package/dist/handlers/replay.js.map +1 -0
  19. package/dist/handlers/session.d.ts.map +1 -1
  20. package/dist/handlers/session.js +64 -65
  21. package/dist/handlers/session.js.map +1 -1
  22. package/dist/handlers/slash.d.ts +23 -1
  23. package/dist/handlers/slash.d.ts.map +1 -1
  24. package/dist/handlers/slash.js +67 -6
  25. package/dist/handlers/slash.js.map +1 -1
  26. package/dist/index.js +47 -17
  27. package/dist/index.js.map +1 -1
  28. package/dist/remote/broadcast.d.ts +47 -0
  29. package/dist/remote/broadcast.d.ts.map +1 -0
  30. package/dist/remote/broadcast.js +121 -0
  31. package/dist/remote/broadcast.js.map +1 -0
  32. package/dist/remote/config.d.ts +32 -0
  33. package/dist/remote/config.d.ts.map +1 -0
  34. package/dist/remote/config.js +65 -0
  35. package/dist/remote/config.js.map +1 -0
  36. package/dist/remote/endpoint.d.ts +30 -0
  37. package/dist/remote/endpoint.d.ts.map +1 -0
  38. package/dist/remote/endpoint.js +213 -0
  39. package/dist/remote/endpoint.js.map +1 -0
  40. package/dist/remote/hub-server.d.ts +41 -0
  41. package/dist/remote/hub-server.d.ts.map +1 -0
  42. package/dist/remote/hub-server.js +346 -0
  43. package/dist/remote/hub-server.js.map +1 -0
  44. package/dist/server.d.ts +41 -7
  45. package/dist/server.d.ts.map +1 -1
  46. package/dist/server.js +69 -11
  47. package/dist/server.js.map +1 -1
  48. package/dist/utils.d.ts +1 -1
  49. package/dist/utils.d.ts.map +1 -1
  50. package/dist/utils.js +17 -1
  51. package/dist/utils.js.map +1 -1
  52. package/docs/ARCHITECTURE.md +47 -15
  53. package/docs/BACKLOG.md +3 -1
  54. package/docs/DEVELOPMENT.md +26 -0
  55. package/docs/PROTOCOL.md +67 -27
  56. package/docs/REMOTE-CLIENTS.md +260 -0
  57. package/docs/REPLAY-GUIDE.md +131 -0
  58. package/docs/TROUBLESHOOTING.md +39 -6
  59. package/docs/adr/0001-bridge-lifetime-follows-primary-client.md +14 -0
  60. package/docs/adr/0002-stateless-hub-over-per-bridge-acp-endpoints.md +23 -0
  61. package/docs/adr/0003-tail-replay-meta-and-cursor-pagination.md +40 -0
  62. package/docs/proposals/0001-tail-session-replay.md +136 -0
  63. package/docs/proposals/0002-plan-quota-usage.md +81 -0
  64. package/package.json +5 -2
package/dist/utils.js CHANGED
@@ -3,15 +3,31 @@
3
3
  *
4
4
  * Logging goes to stderr so it never corrupts the stdout ACP protocol stream.
5
5
  */
6
+ import { readFileSync } from "node:fs";
6
7
  import path from "node:path";
7
8
  import process from "node:process";
8
9
  /** ACP protocol version this server speaks. */
9
10
  export const PROTOCOL_VERSION = 1;
11
+ /**
12
+ * Package version, read once from package.json. Kept in sync with releases by
13
+ * construction (the hardcoded constant used to drift from package.json); the
14
+ * hub-vs-bridge version handshake in remote/ relies on it changing per
15
+ * release.
16
+ */
17
+ const PACKAGE_VERSION = (() => {
18
+ try {
19
+ const pkg = JSON.parse(readFileSync(new URL("../package.json", import.meta.url), "utf8"));
20
+ return pkg.version ?? "0.0.0";
21
+ }
22
+ catch {
23
+ return "0.0.0";
24
+ }
25
+ })();
10
26
  /** Agent identity advertised in the initialize response. */
11
27
  export const AGENT_INFO = {
12
28
  name: "zcode-acp-server",
13
29
  title: "ZCode",
14
- version: "0.1.0",
30
+ version: PACKAGE_VERSION,
15
31
  };
16
32
  /** Path to the ZCode v2 config (credentials + provider/model metadata). */
17
33
  export const ZCODE_CREDS_PATH = path.join(process.env.HOME || process.env.USERPROFILE || "~", ".zcode", "v2", "config.json");
package/dist/utils.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"utils.js","sourceRoot":"","sources":["../src/utils.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"utils.js","sourceRoot":"","sources":["../src/utils.ts"],"names":[],"mappings":"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"}
@@ -14,7 +14,7 @@ application-client (Zed / JetBrains)
14
14
  |
15
15
  v
16
16
  zcode-acp-server (stdio JSON-RPC ACP)
17
- |-- handlers/ session, extensions, dispatch, server-requests, io, slash
17
+ |-- handlers/ session, extensions, dispatch, server-requests, io, slash, account
18
18
  |-- translators/ event-translator, projection-differ, tool-helpers
19
19
  |-- interaction/ adapter
20
20
  |-- config/ options, runtime-model, model-cache
@@ -142,12 +142,12 @@ local relay: prompts, code, and tool outputs pass through process memory on
142
142
  their way between the editor and the ZCode subprocess, but reach the GLM cloud
143
143
  API only because the ZCode backend itself sends them for inference.
144
144
 
145
- | Concern | Detail |
146
- | ------- | ------ |
147
- | Network | One outbound request in the whole codebase — `src/quota/client.ts` GET to the quota API, Bearer token only, no body |
148
- | Credentials | API key from `~/.zcode/v2/config.json` (authenticates the subprocess + quota request), never logged. OAuth handled by the ZCode subprocess, not this server |
149
- | Disk | No new files. Writes only to the existing `~/.zcode/v2/tasks-index.sqlite` — syncs sessions into the ZCode app's history & search (session title + first prompt) |
150
- | Logging | `log()`/`warn()` → stderr only for troubleshooting; even with `ZCODE_ACP_DEBUG=1`, no prompts/code/keys are logged |
145
+ | Concern | Detail |
146
+ | ----------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------- |
147
+ | Network | One outbound request in the whole codebase — `src/quota/client.ts` GET to the quota API, Bearer token only, no body |
148
+ | Credentials | API key from `~/.zcode/v2/config.json` (authenticates the subprocess + quota request), never logged. OAuth handled by the ZCode subprocess, not this server |
149
+ | Disk | No new files. Writes only to the existing `~/.zcode/v2/tasks-index.sqlite` — syncs sessions into the ZCode app's history & search (session title + first prompt) |
150
+ | Logging | `log()`/`warn()` → stderr only for troubleshooting; even with `ZCODE_ACP_DEBUG=1`, no prompts/code/keys are logged |
151
151
 
152
152
  ## Module Responsibilities
153
153
 
@@ -170,15 +170,16 @@ API only because the ZCode backend itself sends them for inference.
170
170
 
171
171
  ### `handlers/` — ACP method handling
172
172
 
173
- | File | Responsibility |
174
- | -------------------- | ------------------------------------------------------------------------------------------------------------ |
175
- | `session.ts` | session/new/list/resume/load/prompt/set_config_option/cancel |
176
- | `extensions.ts` | fork/rewind/rewindCascade/goal/compact/steer/cancelBackgroundTask/setModel/setMode/setThoughtLevel |
177
- | `dispatch.ts` | dispatchEvent single exit point: InternalEvent → ACP session/update |
173
+ | File | Responsibility |
174
+ | --------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
175
+ | `session.ts` | session/new/list/resume/load/prompt/set_config_option/cancel |
176
+ | `extensions.ts` | fork/rewind/rewindCascade/goal/compact/steer/cancelBackgroundTask/setModel/setMode/setThoughtLevel |
177
+ | `dispatch.ts` | dispatchEvent single exit point: InternalEvent → ACP session/update |
178
178
  | `background-tasks.ts` | Session-scoped `BackgroundTaskListener` — forwards background sub-agent status (`session.updated` taskId) + completion-notification turns to the client OUTSIDE request handlers (lives across prompts) |
179
- | `server-requests.ts` | Handle zcode interaction/* requests (tool auth, ExitPlanMode, AskUserQuestion), protocol negotiation routing |
180
- | `io.ts` | ACP notification helpers (including `sendAvailableCommandsDeferred` deferred notification) |
181
- | `slash.ts` | Interception of `/`-prefixed commands (/compact /goal /fork /rewind /steer /model /mode /thought) |
179
+ | `server-requests.ts` | Handle zcode interaction/* requests (tool auth, ExitPlanMode, AskUserQuestion), protocol negotiation routing |
180
+ | `io.ts` | ACP notification helpers (including `sendAvailableCommandsDeferred` deferred notification) |
181
+ | `slash.ts` | Interception of `/`-prefixed commands (/compact /goal /fork /rewind /steer /model /mode /thought); non-advertised `/x` prompts are neutralized into plain text (`neutralizeSlashText`) |
182
+ | `account.ts` | `account/usage_stats` — account-level plan quota for remote clients (Proposal 0002; quota pipeline + graceful error) |
182
183
 
183
184
  ### `interaction/` — Interaction bridging
184
185
 
@@ -194,6 +195,37 @@ API only because the ZCode backend itself sends them for inference.
194
195
  | `runtime-model.ts` | runtimeModel overlay construction and application |
195
196
  | `model-cache.ts` | Model ID cache and usage initialization |
196
197
 
198
+ ### `remote/` — Remote access (opt-in via `ZCODE_ACP_REMOTE=1`)
199
+
200
+ | File | Responsibility |
201
+ | --------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------ |
202
+ | `broadcast.ts` | ClientRegistry + broadcast proxy: notify fans out to all clients; request is first-response-wins with loser `$/cancel_request` |
203
+ | `config.ts` | ENV parsing (gate, mandatory token, hub/bridge ports) |
204
+ | `endpoint.ts` | Loopback ACP endpoint (SDK AcpServer transport, port auto-increment) + hub registration/heartbeat |
205
+ | `hub-server.ts` | The hub singleton: token auth, instance discovery, byte-level WS proxying, heartbeat pruning, on-demand `?probe=1` liveness, idle exit, version self-upgrade |
206
+
207
+ When enabled, the same `AgentApp` serves the stdio editor and a loopback
208
+ WebSocket endpoint. Every connection (editor or remote) joins the broadcast
209
+ registry via `trackConnections`, so one turn's notifications reach all clients
210
+ regardless of who prompted. The bridge registers itself with the machine-level
211
+ `zcode-acp-hub` (`bin/hub.ts`), which is the only public entry point and holds
212
+ no session state (see `docs/adr/0002`). The bridge's lifetime still follows the
213
+ stdio client (ADR-0001); the listener is `unref()`'d so remote clients alone
214
+ never keep the process alive.
215
+
216
+ Hub upgrades are self-managing: each heartbeat carries the bridge's package
217
+ version, and a hub that sees a NEWER bridge replies `{ok, restarting}`, exits,
218
+ and is re-spawned by that bridge from its own (upgraded) `dist/` within a few
219
+ seconds. Equal, older, or absent versions never trigger a restart — downgrades
220
+ and mixed-version fleets are fine. Without this handshake a long-lived hub
221
+ would keep running pre-upgrade code until its 10-minute idle exit.
222
+
223
+ Discovery liveness has two layers: the heartbeat TTL (30s, pruned every 5s)
224
+ drops bridges that stopped registering — the fallback for hard kills — and
225
+ `GET /api/instances?probe=1` actively TCP-probes each registered loopback port
226
+ on demand, so a client refresh gets an immediately-honest list with no
227
+ background probing cost.
228
+
197
229
  ## Key State Machines
198
230
 
199
231
  ### Turn state
package/docs/BACKLOG.md CHANGED
@@ -33,7 +33,9 @@ have no ACP equivalent. Listed for completeness only — the bridge does not
33
33
  intend to surface them.
34
34
 
35
35
  `automation/create`, `automation/list`, `automation/delete` (scheduled tasks),
36
- `usage/stats`, `workspace/readState`, `workspace/upsertModelProvider`,
36
+ `usage/stats` (token analytics; the account-level plan quota it does NOT cover
37
+ is exposed via the bridge's own `account/usage_stats` — see Proposal 0002),
38
+ `workspace/readState`, `workspace/upsertModelProvider`,
37
39
  `workspace/removeModelProvider`, `workspace/updateProviderRegistry`,
38
40
  `workspace/setDefaultModel`, `workspace/setDefaultThoughtLevel`,
39
41
  `workspace/setDefaultMode`, `workspace/generateText`, `mcp/list`,
@@ -90,6 +90,32 @@ describe("backend", () => {
90
90
  });
91
91
  ```
92
92
 
93
+ ### Method 4: Remote access (hub + endpoint)
94
+
95
+ Run the bridge with the remote env vars on scratch ports (keep stdin open —
96
+ the bridge exits when its stdio client disconnects):
97
+
98
+ ```bash
99
+ ZCODE_ACP_REMOTE=1 \
100
+ ZCODE_ACP_REMOTE_TOKEN=dev-token \
101
+ ZCODE_ACP_HUB_PORT=18377 \
102
+ ZCODE_ACP_REMOTE_PORT=18378 \
103
+ ZCODE_ACP_DEBUG=1 \
104
+ tail -f /dev/null | node dist/index.js
105
+ ```
106
+
107
+ The first bridge spawns `dist/bin/hub.js` on its own. Verify discovery:
108
+
109
+ ```bash
110
+ curl -H "Authorization: Bearer dev-token" http://127.0.0.1:18377/api/instances
111
+ curl -H "Authorization: Bearer dev-token" "http://127.0.0.1:18377/api/instances?probe=1"
112
+ ```
113
+
114
+ Integration tests live in `tests/hub.test.ts` (hub API, probe, proxy, idle
115
+ exit) and `tests/remote-endpoint.test.ts` (registration + end-to-end proxied
116
+ initialize). The remote features are opt-in and self-contained — remote
117
+ failures warn and disable remote only, so stdio testing works without them.
118
+
93
119
  ## Debugging Tips
94
120
 
95
121
  ### Enable verbose logging
package/docs/PROTOCOL.md CHANGED
@@ -12,11 +12,11 @@ resembles JSON-RPC, but **does not include the `jsonrpc` field**.
12
12
 
13
13
  Messages are classified by the presence of `id` and `method`:
14
14
 
15
- | Combination | Type | Direction |
16
- |------|------|------|
17
- | `id` + no `method` | Response | zcode -> bridge |
18
- | `id` + `method` | Request | bridge -> zcode or zcode -> bridge |
19
- | `method` + no `id` | Notification | bidirectional |
15
+ | Combination | Type | Direction |
16
+ | ------------------ | ------------ | ---------------------------------- |
17
+ | `id` + no `method` | Response | zcode -> bridge |
18
+ | `id` + `method` | Request | bridge -> zcode or zcode -> bridge |
19
+ | `method` + no `id` | Notification | bidirectional |
20
20
 
21
21
  ### Request format
22
22
 
@@ -84,6 +84,7 @@ the backend session (this RPC) on the first prompt / config change / extension
84
84
  method, so an editor startup that never sends a message leaves no session.
85
85
 
86
86
  **Request:**
87
+
87
88
  ```json
88
89
  {
89
90
  "id": 1,
@@ -99,6 +100,7 @@ method, so an editor startup that never sends a message leaves no session.
99
100
  ```
100
101
 
101
102
  **Response:**
103
+
102
104
  ```json
103
105
  {
104
106
  "id": 1,
@@ -117,6 +119,7 @@ method, so an editor startup that never sends a message leaves no session.
117
119
  List all sessions.
118
120
 
119
121
  **Request:**
122
+
120
123
  ```json
121
124
  {
122
125
  "id": 2,
@@ -142,6 +145,7 @@ empty one if the placeholder was never used. Real ids from `session/list` pass
142
145
  through unchanged.
143
146
 
144
147
  **Request:**
148
+
145
149
  ```json
146
150
  {
147
151
  "id": 3,
@@ -161,6 +165,7 @@ through unchanged.
161
165
  Send a prompt.
162
166
 
163
167
  **Request:**
168
+
164
169
  ```json
165
170
  {
166
171
  "id": 4,
@@ -173,6 +178,7 @@ Send a prompt.
173
178
  ```
174
179
 
175
180
  **Response:**
181
+
176
182
  ```json
177
183
  {
178
184
  "id": 4,
@@ -200,6 +206,7 @@ Stop the current turn (fire-and-forget).
200
206
  Read the session state and projection.
201
207
 
202
208
  **Request:**
209
+
203
210
  ```json
204
211
  {
205
212
  "id": 5,
@@ -211,6 +218,7 @@ Read the session state and projection.
211
218
  ```
212
219
 
213
220
  **Response:**
221
+
214
222
  ```json
215
223
  {
216
224
  "id": 5,
@@ -226,9 +234,7 @@ Read the session state and projection.
226
234
  "model": { "current": { "modelId": "GLM-5.2" } },
227
235
  "thoughtLevel": { "current": "high" }
228
236
  },
229
- "todos": [
230
- { "content": "Implement login", "status": "pending", "priority": "high" }
231
- ]
237
+ "todos": [{ "content": "Implement login", "status": "pending", "priority": "high" }]
232
238
  }
233
239
  }
234
240
  ```
@@ -238,6 +244,7 @@ Read the session state and projection.
238
244
  Fetch the session's historical messages.
239
245
 
240
246
  **Request:**
247
+
241
248
  ```json
242
249
  {
243
250
  "id": 6,
@@ -255,6 +262,7 @@ Fetch the session's historical messages.
255
262
  Subscribe to a session's event push.
256
263
 
257
264
  **Request:**
265
+
258
266
  ```json
259
267
  {
260
268
  "id": 7,
@@ -269,6 +277,7 @@ Subscribe to a session's event push.
269
277
  ```
270
278
 
271
279
  **Response:**
280
+
272
281
  ```json
273
282
  {
274
283
  "id": 7,
@@ -322,6 +331,7 @@ Model streaming output.
322
331
  ```
323
332
 
324
333
  `kind` can be:
334
+
325
335
  - `text_delta`: text delta
326
336
  - `reasoning_delta`: reasoning text delta
327
337
  - `tool_call`: tool call declaration (caches toolName and input)
@@ -348,6 +358,7 @@ Tool status update.
348
358
  ```
349
359
 
350
360
  `kind` can be:
361
+
351
362
  - `scheduled`: tool scheduled
352
363
  - `started`: tool started executing
353
364
  - `progress`: progress update (stdoutTail / stderrTail)
@@ -497,6 +508,7 @@ Tool permission request.
497
508
  User input request (ExitPlanMode / AskUserQuestion).
498
509
 
499
510
  **ExitPlanMode:**
511
+
500
512
  ```json
501
513
  {
502
514
  "id": 101,
@@ -512,6 +524,7 @@ User input request (ExitPlanMode / AskUserQuestion).
512
524
  ```
513
525
 
514
526
  **AskUserQuestion:**
527
+
515
528
  ```json
516
529
  {
517
530
  "id": 102,
@@ -539,17 +552,18 @@ User input request (ExitPlanMode / AskUserQuestion).
539
552
  ZCode `interaction/*` requests are routed to different ACP interaction
540
553
  mechanisms based on client capabilities:
541
554
 
542
- | Request type | Client supports elicitation.form | Client does not |
543
- |---------|:------------------------:|:----------:|
544
- | Tool auth (`interaction/requestPermission`) | `session/request_permission` | `session/request_permission` |
545
- | ExitPlanMode (`interaction/requestUserInput` + plan_approval) | `elicitation/create` (approve/reject form) | `session/request_permission` |
546
- | AskUserQuestion (`interaction/requestUserInput`) | `elicitation/create` (single form) | per-question `session/request_permission` |
555
+ | Request type | Client supports elicitation.form | Client does not |
556
+ | ------------------------------------------------------------- | :----------------------------------------: | :---------------------------------------: |
557
+ | Tool auth (`interaction/requestPermission`) | `session/request_permission` | `session/request_permission` |
558
+ | ExitPlanMode (`interaction/requestUserInput` + plan_approval) | `elicitation/create` (approve/reject form) | `session/request_permission` |
559
+ | AskUserQuestion (`interaction/requestUserInput`) | `elicitation/create` (single form) | per-question `session/request_permission` |
547
560
 
548
561
  **Capability detection**: at `initialize` time the client declares support via
549
562
  `clientCapabilities.elicitation.form`. The server detects it with
550
563
  `server.supportsElicitationForm()`.
551
564
 
552
565
  **elicitation form example** (AskUserQuestion):
566
+
553
567
  ```json
554
568
  {
555
569
  "method": "elicitation/create",
@@ -591,6 +605,7 @@ overrides the dropdown (single-select) or is appended to the picked values
591
605
  that question without cancelling the form.
592
606
 
593
607
  **elicitation response** (accept/decline/cancel):
608
+
594
609
  ```json
595
610
  {
596
611
  "action": "accept",
@@ -604,6 +619,7 @@ typing into the field is the reject action. Submitting with the field empty
604
619
  approves the plan; submitting with text rejects it and returns the text to
605
620
  zcode as the decline `reason` (so the agent sees the redirection when it
606
621
  re-plans). The cancel/decline button is a plain reject with no reason.
622
+
607
623
  ```json
608
624
  {
609
625
  "method": "elicitation/create",
@@ -633,6 +649,7 @@ re-plans). The cancel/decline button is a plain reject with no reason.
633
649
  Fork a new session from a checkpoint.
634
650
 
635
651
  **Request:**
652
+
636
653
  ```json
637
654
  {
638
655
  "id": 8,
@@ -649,6 +666,7 @@ Fork a new session from a checkpoint.
649
666
  Rewind to a checkpoint.
650
667
 
651
668
  **Request:**
669
+
652
670
  ```json
653
671
  {
654
672
  "id": 9,
@@ -666,6 +684,7 @@ Rewind to a checkpoint.
666
684
  Read / set / replace / clear the goal.
667
685
 
668
686
  **Request:**
687
+
669
688
  ```json
670
689
  {
671
690
  "id": 10,
@@ -685,6 +704,7 @@ Read / set / replace / clear the goal.
685
704
  Compact the conversation history.
686
705
 
687
706
  **Request:**
707
+
688
708
  ```json
689
709
  {
690
710
  "id": 11,
@@ -700,6 +720,7 @@ Compact the conversation history.
700
720
  Append instructions to a running turn.
701
721
 
702
722
  **Request:**
723
+
703
724
  ```json
704
725
  {
705
726
  "id": 12,
@@ -716,6 +737,7 @@ Append instructions to a running turn.
716
737
  Switch the session mode.
717
738
 
718
739
  **Request:**
740
+
719
741
  ```json
720
742
  {
721
743
  "id": 13,
@@ -732,6 +754,7 @@ Switch the session mode.
732
754
  Set the thought level.
733
755
 
734
756
  **Request:**
757
+
735
758
  ```json
736
759
  {
737
760
  "id": 14,
@@ -807,10 +830,10 @@ lifecycle on the same stream:
807
830
  The bridge's session-scoped `BackgroundTaskListener` turns these into a
808
831
  dedicated ACP tool card (`[background] <description>`) plus status updates:
809
832
 
810
- | Backend event | ACP notification |
811
- |---|---|
833
+ | Backend event | ACP notification |
834
+ | ------------------------------------------ | -------------------------------------------------------------- |
812
835
  | first `session.updated` (status `running`) | `tool_call` (new card, `kind:"other"`, `status:"in_progress"`) |
813
- | `session.updated` (status `completed`) | `tool_call_update` (`status:"completed"`) |
836
+ | `session.updated` (status `completed`) | `tool_call_update` (`status:"completed"`) |
814
837
 
815
838
  `session.updated` events WITHOUT a `taskId` (e.g. usage updates) are ignored by
816
839
  the background listener — they remain owned by the turn loop.
@@ -883,11 +906,11 @@ The mechanism:
883
906
  `toolCallId` is unknown to `terminalSentData` (sub-agent case), the listener
884
907
  falls back to minting a fresh `bg_*` card — the Agent sub-agent path above.
885
908
 
886
- | Backend event | ACP notification (background Bash) |
887
- |---|---|
888
- | first `session.updated` (status `running`) | `tool_call_update` on the launch card (`status:"in_progress"`) |
909
+ | Backend event | ACP notification (background Bash) |
910
+ | --------------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------- |
911
+ | first `session.updated` (status `running`) | `tool_call_update` on the launch card (`status:"in_progress"`) |
889
912
  | `session.updated` (status `completed`, with `outputTail`) | `terminal_output` (final output, if not already streamed) + `tool_call_update` with `terminal_exit` (`status:"completed"`) |
890
- | `session.updated` (status `failed`) | `tool_call_update` with `terminal_exit` (`status:"failed"`, exit_code 1) |
913
+ | `session.updated` (status `failed`) | `tool_call_update` with `terminal_exit` (`status:"failed"`, exit_code 1) |
891
914
 
892
915
  `session/cancelBackgroundTask` for a background Bash task additionally emits
893
916
  `terminal_exit` with `_meta.backgroundTask.cancelled = true` so the terminal
@@ -898,13 +921,11 @@ UI closes on cancellation.
898
921
  Cancels a background task. The bridge additionally marks the corresponding ACP
899
922
  tool card as `failed` with `_meta.backgroundTask.cancelled = true`.
900
923
 
901
-
902
-
903
- | ZCode CLI version | session/subscribe | Extension methods | Notes |
904
- |---------------|-------------------|----------|------|
905
- | >= 0.15.0 | Supported | All supported | Full functionality |
906
- | >= 0.14.8 | Supported | Partially supported | workspace/* unavailable |
907
- | 0.14.5 ~ 0.14.7 | Not supported | Not supported | Incompatible with this project |
924
+ | ZCode CLI version | session/subscribe | Extension methods | Notes |
925
+ | ----------------- | ----------------- | ------------------- | ------------------------------ |
926
+ | >= 0.15.0 | Supported | All supported | Full functionality |
927
+ | >= 0.14.8 | Supported | Partially supported | workspace/* unavailable |
928
+ | 0.14.5 ~ 0.14.7 | Not supported | Not supported | Incompatible with this project |
908
929
 
909
930
  ## Additional backend methods (not wired into the bridge)
910
931
 
@@ -912,3 +933,22 @@ The backend exposes more RPC methods than the bridge uses (sub-agent listing,
912
933
  event pull, session usage/close, automation, workspace config, MCP/plugins).
913
934
  These have no ACP-side counterpart yet. See [`BACKLOG.md`](./BACKLOG.md) for
914
935
  the full list and which are candidates for future support.
936
+
937
+ ## Multi-client semantics (remote access)
938
+
939
+ When `ZCODE_ACP_REMOTE=1` is enabled, the bridge accepts additional ACP clients
940
+ over WebSocket (via the machine-level hub) alongside the stdio editor. All
941
+ clients share the same backend sessions; the rules below define how one agent
942
+ serves many clients.
943
+
944
+ | Aspect | Behaviour |
945
+ | -------------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
946
+ | `session/update` notifications | Broadcast to every connected client. A client that never saw a session (e.g. an editor receiving a phone-created session) simply ignores the update. |
947
+ | `session/request_permission`, `elicitation/create` | Sent to every client; the **first response wins**. Losing requests are aborted, which emits `$/cancel_request` so the losing client dismisses its dialog and replies `RequestCancelled`. |
948
+ | Capabilities | OR-merged across clients at each `initialize` (booleans union, `_meta` shallow-merged). A capability any client declares is enabled for interaction routing. |
949
+ | Concurrent `session/prompt` on one session | Serialized by the per-session preempt lock — identical to the single-client case; a second client's prompt preempts or queues the same way. |
950
+ | `session/cancel` | Affects the shared turn regardless of which client sent it. |
951
+ | Process lifetime | Follows the stdio client: when the editor disconnects, the bridge (and every remote attachment) exits. Remote clients never extend the lifetime. |
952
+
953
+ Transport details (hub discovery API, token auth, tunnel notes) live in the
954
+ [Remote Access](../README.md#remote-access) section of the README.