zcode-acp-server 0.1.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 (223) hide show
  1. package/README.md +192 -9
  2. package/README.zh-CN.md +143 -8
  3. package/dist/backend/client.d.ts +12 -4
  4. package/dist/backend/client.d.ts.map +1 -1
  5. package/dist/backend/client.js +72 -7
  6. package/dist/backend/client.js.map +1 -1
  7. package/dist/backend/credentials.d.ts.map +1 -1
  8. package/dist/backend/credentials.js +2 -1
  9. package/dist/backend/credentials.js.map +1 -1
  10. package/dist/backend/listener.d.ts +12 -4
  11. package/dist/backend/listener.d.ts.map +1 -1
  12. package/dist/backend/listener.js +68 -12
  13. package/dist/backend/listener.js.map +1 -1
  14. package/dist/backend/types.d.ts +1 -1
  15. package/dist/backend/types.d.ts.map +1 -1
  16. package/dist/bin/hub.d.ts +16 -0
  17. package/dist/bin/hub.d.ts.map +1 -0
  18. package/dist/bin/hub.js +41 -0
  19. package/dist/bin/hub.js.map +1 -0
  20. package/dist/bin/quota.d.ts +54 -0
  21. package/dist/bin/quota.d.ts.map +1 -0
  22. package/dist/bin/quota.js +333 -0
  23. package/dist/bin/quota.js.map +1 -0
  24. package/dist/config/auto-compact.d.ts +23 -0
  25. package/dist/config/auto-compact.d.ts.map +1 -0
  26. package/dist/config/auto-compact.js +67 -0
  27. package/dist/config/auto-compact.js.map +1 -0
  28. package/dist/config/mcp-discovery.d.ts +34 -0
  29. package/dist/config/mcp-discovery.d.ts.map +1 -0
  30. package/dist/config/mcp-discovery.js +153 -0
  31. package/dist/config/mcp-discovery.js.map +1 -0
  32. package/dist/config/model-cache.d.ts +12 -1
  33. package/dist/config/model-cache.d.ts.map +1 -1
  34. package/dist/config/model-cache.js +30 -8
  35. package/dist/config/model-cache.js.map +1 -1
  36. package/dist/config/options.d.ts +87 -9
  37. package/dist/config/options.d.ts.map +1 -1
  38. package/dist/config/options.js +248 -45
  39. package/dist/config/options.js.map +1 -1
  40. package/dist/config/plugin-commands.d.ts +24 -0
  41. package/dist/config/plugin-commands.d.ts.map +1 -0
  42. package/dist/config/plugin-commands.js +107 -0
  43. package/dist/config/plugin-commands.js.map +1 -0
  44. package/dist/config/provider-registry.d.ts +60 -0
  45. package/dist/config/provider-registry.d.ts.map +1 -0
  46. package/dist/config/provider-registry.js +128 -0
  47. package/dist/config/provider-registry.js.map +1 -0
  48. package/dist/config/runtime-model.d.ts +54 -18
  49. package/dist/config/runtime-model.d.ts.map +1 -1
  50. package/dist/config/runtime-model.js +105 -61
  51. package/dist/config/runtime-model.js.map +1 -1
  52. package/dist/config/skill-discovery.d.ts +35 -0
  53. package/dist/config/skill-discovery.d.ts.map +1 -0
  54. package/dist/config/skill-discovery.js +188 -0
  55. package/dist/config/skill-discovery.js.map +1 -0
  56. package/dist/handlers/account.d.ts +43 -0
  57. package/dist/handlers/account.d.ts.map +1 -0
  58. package/dist/handlers/account.js +59 -0
  59. package/dist/handlers/account.js.map +1 -0
  60. package/dist/handlers/background-tasks.d.ts +72 -0
  61. package/dist/handlers/background-tasks.d.ts.map +1 -0
  62. package/dist/handlers/background-tasks.js +330 -0
  63. package/dist/handlers/background-tasks.js.map +1 -0
  64. package/dist/handlers/dispatch.d.ts.map +1 -1
  65. package/dist/handlers/dispatch.js +134 -22
  66. package/dist/handlers/dispatch.js.map +1 -1
  67. package/dist/handlers/extensions.d.ts +15 -0
  68. package/dist/handlers/extensions.d.ts.map +1 -1
  69. package/dist/handlers/extensions.js +43 -27
  70. package/dist/handlers/extensions.js.map +1 -1
  71. package/dist/handlers/io.d.ts +31 -3
  72. package/dist/handlers/io.d.ts.map +1 -1
  73. package/dist/handlers/io.js +108 -9
  74. package/dist/handlers/io.js.map +1 -1
  75. package/dist/handlers/replay.d.ts +79 -0
  76. package/dist/handlers/replay.d.ts.map +1 -0
  77. package/dist/handlers/replay.js +252 -0
  78. package/dist/handlers/replay.js.map +1 -0
  79. package/dist/handlers/server-requests.d.ts +20 -4
  80. package/dist/handlers/server-requests.d.ts.map +1 -1
  81. package/dist/handlers/server-requests.js +293 -63
  82. package/dist/handlers/server-requests.js.map +1 -1
  83. package/dist/handlers/session.d.ts +113 -10
  84. package/dist/handlers/session.d.ts.map +1 -1
  85. package/dist/handlers/session.js +932 -277
  86. package/dist/handlers/session.js.map +1 -1
  87. package/dist/handlers/slash.d.ts +37 -1
  88. package/dist/handlers/slash.d.ts.map +1 -1
  89. package/dist/handlers/slash.js +143 -8
  90. package/dist/handlers/slash.js.map +1 -1
  91. package/dist/index.js +76 -12
  92. package/dist/index.js.map +1 -1
  93. package/dist/interaction/adapter.d.ts +19 -33
  94. package/dist/interaction/adapter.d.ts.map +1 -1
  95. package/dist/interaction/adapter.js +80 -79
  96. package/dist/interaction/adapter.js.map +1 -1
  97. package/dist/lazy-sessions.d.ts +35 -0
  98. package/dist/lazy-sessions.d.ts.map +1 -0
  99. package/dist/lazy-sessions.js +98 -0
  100. package/dist/lazy-sessions.js.map +1 -0
  101. package/dist/quota/cache.d.ts +18 -0
  102. package/dist/quota/cache.d.ts.map +1 -0
  103. package/dist/quota/cache.js +32 -0
  104. package/dist/quota/cache.js.map +1 -0
  105. package/dist/quota/client.d.ts +30 -0
  106. package/dist/quota/client.d.ts.map +1 -0
  107. package/dist/quota/client.js +57 -0
  108. package/dist/quota/client.js.map +1 -0
  109. package/dist/quota/color.d.ts +58 -0
  110. package/dist/quota/color.d.ts.map +1 -0
  111. package/dist/quota/color.js +95 -0
  112. package/dist/quota/color.js.map +1 -0
  113. package/dist/quota/combined.d.ts +66 -0
  114. package/dist/quota/combined.d.ts.map +1 -0
  115. package/dist/quota/combined.js +179 -0
  116. package/dist/quota/combined.js.map +1 -0
  117. package/dist/quota/format.d.ts +105 -0
  118. package/dist/quota/format.d.ts.map +1 -0
  119. package/dist/quota/format.js +219 -0
  120. package/dist/quota/format.js.map +1 -0
  121. package/dist/quota/index.d.ts +23 -0
  122. package/dist/quota/index.d.ts.map +1 -0
  123. package/dist/quota/index.js +42 -0
  124. package/dist/quota/index.js.map +1 -0
  125. package/dist/quota/opencode-go/cache.d.ts +17 -0
  126. package/dist/quota/opencode-go/cache.d.ts.map +1 -0
  127. package/dist/quota/opencode-go/cache.js +31 -0
  128. package/dist/quota/opencode-go/cache.js.map +1 -0
  129. package/dist/quota/opencode-go/client.d.ts +28 -0
  130. package/dist/quota/opencode-go/client.d.ts.map +1 -0
  131. package/dist/quota/opencode-go/client.js +48 -0
  132. package/dist/quota/opencode-go/client.js.map +1 -0
  133. package/dist/quota/opencode-go/config.d.ts +37 -0
  134. package/dist/quota/opencode-go/config.d.ts.map +1 -0
  135. package/dist/quota/opencode-go/config.js +58 -0
  136. package/dist/quota/opencode-go/config.js.map +1 -0
  137. package/dist/quota/opencode-go/format.d.ts +36 -0
  138. package/dist/quota/opencode-go/format.d.ts.map +1 -0
  139. package/dist/quota/opencode-go/format.js +87 -0
  140. package/dist/quota/opencode-go/format.js.map +1 -0
  141. package/dist/quota/opencode-go/index.d.ts +30 -0
  142. package/dist/quota/opencode-go/index.d.ts.map +1 -0
  143. package/dist/quota/opencode-go/index.js +108 -0
  144. package/dist/quota/opencode-go/index.js.map +1 -0
  145. package/dist/quota/opencode-go/parse.d.ts +41 -0
  146. package/dist/quota/opencode-go/parse.d.ts.map +1 -0
  147. package/dist/quota/opencode-go/parse.js +75 -0
  148. package/dist/quota/opencode-go/parse.js.map +1 -0
  149. package/dist/quota/opencode-go/types.d.ts +48 -0
  150. package/dist/quota/opencode-go/types.d.ts.map +1 -0
  151. package/dist/quota/opencode-go/types.js +11 -0
  152. package/dist/quota/opencode-go/types.js.map +1 -0
  153. package/dist/quota/parse.d.ts +33 -0
  154. package/dist/quota/parse.d.ts.map +1 -0
  155. package/dist/quota/parse.js +200 -0
  156. package/dist/quota/parse.js.map +1 -0
  157. package/dist/quota/types.d.ts +72 -0
  158. package/dist/quota/types.d.ts.map +1 -0
  159. package/dist/quota/types.js +10 -0
  160. package/dist/quota/types.js.map +1 -0
  161. package/dist/remote/broadcast.d.ts +47 -0
  162. package/dist/remote/broadcast.d.ts.map +1 -0
  163. package/dist/remote/broadcast.js +121 -0
  164. package/dist/remote/broadcast.js.map +1 -0
  165. package/dist/remote/config.d.ts +32 -0
  166. package/dist/remote/config.d.ts.map +1 -0
  167. package/dist/remote/config.js +65 -0
  168. package/dist/remote/config.js.map +1 -0
  169. package/dist/remote/endpoint.d.ts +30 -0
  170. package/dist/remote/endpoint.d.ts.map +1 -0
  171. package/dist/remote/endpoint.js +213 -0
  172. package/dist/remote/endpoint.js.map +1 -0
  173. package/dist/remote/hub-server.d.ts +41 -0
  174. package/dist/remote/hub-server.d.ts.map +1 -0
  175. package/dist/remote/hub-server.js +346 -0
  176. package/dist/remote/hub-server.js.map +1 -0
  177. package/dist/server.d.ts +139 -1
  178. package/dist/server.d.ts.map +1 -1
  179. package/dist/server.js +179 -4
  180. package/dist/server.js.map +1 -1
  181. package/dist/tasks-index.d.ts +14 -4
  182. package/dist/tasks-index.d.ts.map +1 -1
  183. package/dist/tasks-index.js +145 -46
  184. package/dist/tasks-index.js.map +1 -1
  185. package/dist/translators/event-translator.d.ts +33 -0
  186. package/dist/translators/event-translator.d.ts.map +1 -1
  187. package/dist/translators/event-translator.js +101 -0
  188. package/dist/translators/event-translator.js.map +1 -1
  189. package/dist/translators/index.d.ts +1 -1
  190. package/dist/translators/index.d.ts.map +1 -1
  191. package/dist/translators/index.js +1 -1
  192. package/dist/translators/index.js.map +1 -1
  193. package/dist/translators/projection-differ.d.ts +8 -0
  194. package/dist/translators/projection-differ.d.ts.map +1 -1
  195. package/dist/translators/projection-differ.js +19 -9
  196. package/dist/translators/projection-differ.js.map +1 -1
  197. package/dist/translators/tool-helpers.d.ts +28 -0
  198. package/dist/translators/tool-helpers.d.ts.map +1 -1
  199. package/dist/translators/tool-helpers.js +104 -0
  200. package/dist/translators/tool-helpers.js.map +1 -1
  201. package/dist/translators/types.d.ts +38 -1
  202. package/dist/translators/types.d.ts.map +1 -1
  203. package/dist/translators/types.js.map +1 -1
  204. package/dist/utils.d.ts +36 -7
  205. package/dist/utils.d.ts.map +1 -1
  206. package/dist/utils.js +69 -5
  207. package/dist/utils.js.map +1 -1
  208. package/docs/ARCHITECTURE.md +130 -40
  209. package/docs/BACKLOG.md +59 -0
  210. package/docs/DEVELOPMENT.md +31 -0
  211. package/docs/PROTOCOL.md +328 -23
  212. package/docs/REMOTE-CLIENTS.md +260 -0
  213. package/docs/REPLAY-GUIDE.md +131 -0
  214. package/docs/TROUBLESHOOTING.md +131 -14
  215. package/docs/adr/0001-bridge-lifetime-follows-primary-client.md +14 -0
  216. package/docs/adr/0002-stateless-hub-over-per-bridge-acp-endpoints.md +23 -0
  217. package/docs/adr/0003-tail-replay-meta-and-cursor-pagination.md +40 -0
  218. package/docs/agents/domain.md +51 -0
  219. package/docs/agents/issue-tracker.md +22 -0
  220. package/docs/agents/triage-labels.md +15 -0
  221. package/docs/proposals/0001-tail-session-replay.md +136 -0
  222. package/docs/proposals/0002-plan-quota-usage.md +81 -0
  223. package/package.json +10 -6
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
 
@@ -78,9 +78,13 @@ Messages are classified by the presence of `id` and `method`:
78
78
 
79
79
  ### `session/create`
80
80
 
81
- Create a new session.
81
+ Create a new session. Note: the bridge defers this call until a session's
82
+ first use — ACP `session/new` returns a local placeholder id and materializes
83
+ the backend session (this RPC) on the first prompt / config change / extension
84
+ method, so an editor startup that never sends a message leaves no session.
82
85
 
83
86
  **Request:**
87
+
84
88
  ```json
85
89
  {
86
90
  "id": 1,
@@ -96,6 +100,7 @@ Create a new session.
96
100
  ```
97
101
 
98
102
  **Response:**
103
+
99
104
  ```json
100
105
  {
101
106
  "id": 1,
@@ -114,6 +119,7 @@ Create a new session.
114
119
  List all sessions.
115
120
 
116
121
  **Request:**
122
+
117
123
  ```json
118
124
  {
119
125
  "id": 2,
@@ -131,7 +137,15 @@ List all sessions.
131
137
 
132
138
  Resume an existing session.
133
139
 
140
+ The sessionId may be a lazy `session/new` placeholder (the editor persists it
141
+ and resumes it after a bridge restart). The bridge resolves it before the
142
+ backend call: an in-memory or persisted (`acp-lazy-sessions.json`) mapping is
143
+ followed to the real backend session — resuming it, or materializing a fresh
144
+ empty one if the placeholder was never used. Real ids from `session/list` pass
145
+ through unchanged.
146
+
134
147
  **Request:**
148
+
135
149
  ```json
136
150
  {
137
151
  "id": 3,
@@ -151,6 +165,7 @@ Resume an existing session.
151
165
  Send a prompt.
152
166
 
153
167
  **Request:**
168
+
154
169
  ```json
155
170
  {
156
171
  "id": 4,
@@ -163,6 +178,7 @@ Send a prompt.
163
178
  ```
164
179
 
165
180
  **Response:**
181
+
166
182
  ```json
167
183
  {
168
184
  "id": 4,
@@ -190,6 +206,7 @@ Stop the current turn (fire-and-forget).
190
206
  Read the session state and projection.
191
207
 
192
208
  **Request:**
209
+
193
210
  ```json
194
211
  {
195
212
  "id": 5,
@@ -201,6 +218,7 @@ Read the session state and projection.
201
218
  ```
202
219
 
203
220
  **Response:**
221
+
204
222
  ```json
205
223
  {
206
224
  "id": 5,
@@ -216,9 +234,7 @@ Read the session state and projection.
216
234
  "model": { "current": { "modelId": "GLM-5.2" } },
217
235
  "thoughtLevel": { "current": "high" }
218
236
  },
219
- "todos": [
220
- { "content": "Implement login", "status": "pending", "priority": "high" }
221
- ]
237
+ "todos": [{ "content": "Implement login", "status": "pending", "priority": "high" }]
222
238
  }
223
239
  }
224
240
  ```
@@ -228,6 +244,7 @@ Read the session state and projection.
228
244
  Fetch the session's historical messages.
229
245
 
230
246
  **Request:**
247
+
231
248
  ```json
232
249
  {
233
250
  "id": 6,
@@ -245,6 +262,7 @@ Fetch the session's historical messages.
245
262
  Subscribe to a session's event push.
246
263
 
247
264
  **Request:**
265
+
248
266
  ```json
249
267
  {
250
268
  "id": 7,
@@ -259,6 +277,7 @@ Subscribe to a session's event push.
259
277
  ```
260
278
 
261
279
  **Response:**
280
+
262
281
  ```json
263
282
  {
264
283
  "id": 7,
@@ -312,6 +331,7 @@ Model streaming output.
312
331
  ```
313
332
 
314
333
  `kind` can be:
334
+
315
335
  - `text_delta`: text delta
316
336
  - `reasoning_delta`: reasoning text delta
317
337
  - `tool_call`: tool call declaration (caches toolName and input)
@@ -338,6 +358,7 @@ Tool status update.
338
358
  ```
339
359
 
340
360
  `kind` can be:
361
+
341
362
  - `scheduled`: tool scheduled
342
363
  - `started`: tool started executing
343
364
  - `progress`: progress update (stdoutTail / stderrTail)
@@ -408,6 +429,53 @@ Session state update (usage, etc.).
408
429
  }
409
430
  ```
410
431
 
432
+ ### Steer lifecycle events
433
+
434
+ When `session/steer` appends instructions to a running turn, the backend emits
435
+ a pair of lifecycle events (available in app-server 0.15.2+). The bridge does
436
+ not currently translate these — they are tracked as a future enhancement (see
437
+ [`BACKLOG.md`](./BACKLOG.md)).
438
+
439
+ ```json
440
+ {
441
+ "type": "turn.steerQueued",
442
+ "payload": {}
443
+ }
444
+ ```
445
+
446
+ ```json
447
+ {
448
+ "type": "turn.steerDrained",
449
+ "payload": {}
450
+ }
451
+ ```
452
+
453
+ ### `turn.terminal`
454
+
455
+ Terminal turn lifecycle event (app-server 0.15.2+). Carries a status plus
456
+ usage. The bridge currently relies on `turn.completed`/`turn.failed` instead;
457
+ documented here for completeness.
458
+
459
+ ```json
460
+ {
461
+ "method": "session/event",
462
+ "params": {
463
+ "sessionId": "sess_abc123",
464
+ "seq": 49,
465
+ "type": "turn.terminal",
466
+ "payload": {
467
+ "kind": "turn.terminal",
468
+ "status": "success",
469
+ "resultType": "end_turn",
470
+ "durationMs": 12345,
471
+ "inputTokens": 1234,
472
+ "outputTokens": 567,
473
+ "totalTokens": 1801
474
+ }
475
+ }
476
+ }
477
+ ```
478
+
411
479
  ## Interaction Protocol (Server -> Client)
412
480
 
413
481
  Requests that zcode actively sends to the bridge.
@@ -440,6 +508,7 @@ Tool permission request.
440
508
  User input request (ExitPlanMode / AskUserQuestion).
441
509
 
442
510
  **ExitPlanMode:**
511
+
443
512
  ```json
444
513
  {
445
514
  "id": 101,
@@ -455,6 +524,7 @@ User input request (ExitPlanMode / AskUserQuestion).
455
524
  ```
456
525
 
457
526
  **AskUserQuestion:**
527
+
458
528
  ```json
459
529
  {
460
530
  "id": 102,
@@ -482,17 +552,18 @@ User input request (ExitPlanMode / AskUserQuestion).
482
552
  ZCode `interaction/*` requests are routed to different ACP interaction
483
553
  mechanisms based on client capabilities:
484
554
 
485
- | Request type | Client supports elicitation.form | Client does not |
486
- |---------|:------------------------:|:----------:|
487
- | Tool auth (`interaction/requestPermission`) | `session/request_permission` | `session/request_permission` |
488
- | ExitPlanMode (`interaction/requestUserInput` + plan_approval) | `elicitation/create` (approve/reject form) | `session/request_permission` |
489
- | 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` |
490
560
 
491
561
  **Capability detection**: at `initialize` time the client declares support via
492
562
  `clientCapabilities.elicitation.form`. The server detects it with
493
563
  `server.supportsElicitationForm()`.
494
564
 
495
565
  **elicitation form example** (AskUserQuestion):
566
+
496
567
  ```json
497
568
  {
498
569
  "method": "elicitation/create",
@@ -505,17 +576,36 @@ mechanisms based on client capabilities:
505
576
  "properties": {
506
577
  "q_0": {
507
578
  "type": "string",
508
- "enum": ["auth.test.ts", "user.test.ts"],
509
- "title": "Select the files to test"
579
+ "title": "Select the files to test",
580
+ "oneOf": [
581
+ { "const": "auth.test.ts", "title": "auth.test.ts" },
582
+ { "const": "user.test.ts", "title": "user.test.ts" },
583
+ { "const": "__skip__", "title": "Skip this question" }
584
+ ]
585
+ },
586
+ "q_0_other": {
587
+ "type": "string",
588
+ "title": "↳ or type a custom value (overrides the selection)"
510
589
  }
511
590
  },
512
- "required": ["q_0"]
591
+ "required": []
513
592
  }
514
593
  }
515
594
  }
516
595
  ```
517
596
 
597
+ ACP/MCP elicitation string fields are EITHER an enum (restricted dropdown) OR
598
+ free text — the spec forbids a single field that is both. So each question is
599
+ rendered as TWO fields: `q_<i>` (a `oneOf`/`anyOf` enum dropdown of the model's
600
+ suggested answers, with a trailing "Skip this question" option whose `const` is
601
+ the `__skip__` sentinel and whose `title` is the readable label) and
602
+ `q_<i>_other` (a free-text companion). On submit, a non-empty `q_<i>_other`
603
+ overrides the dropdown (single-select) or is appended to the picked values
604
+ (multi-select); selecting "Skip this question" or leaving both blank skips just
605
+ that question without cancelling the form.
606
+
518
607
  **elicitation response** (accept/decline/cancel):
608
+
519
609
  ```json
520
610
  {
521
611
  "action": "accept",
@@ -523,6 +613,35 @@ mechanisms based on client capabilities:
523
613
  }
524
614
  ```
525
615
 
616
+ **ExitPlanMode elicitation form** — single `feedback` text field; no
617
+ approve/reject dropdown. The client's own submit button is the approve action;
618
+ typing into the field is the reject action. Submitting with the field empty
619
+ approves the plan; submitting with text rejects it and returns the text to
620
+ zcode as the decline `reason` (so the agent sees the redirection when it
621
+ re-plans). The cancel/decline button is a plain reject with no reason.
622
+
623
+ ```json
624
+ {
625
+ "method": "elicitation/create",
626
+ "params": {
627
+ "mode": "form",
628
+ "sessionId": "sess_abc123",
629
+ "message": "Ready to code?\n\n1. Implement login\n2. Implement signup\n\nLeave the box empty and submit to approve; type feedback to reject and redirect.",
630
+ "requestedSchema": {
631
+ "type": "object",
632
+ "properties": {
633
+ "feedback": {
634
+ "type": "string",
635
+ "title": "Feedback",
636
+ "description": "Empty = approve the plan. Anything typed = reject and use this text as the redirection."
637
+ }
638
+ },
639
+ "required": []
640
+ }
641
+ }
642
+ }
643
+ ```
644
+
526
645
  ## Extension Methods (0.14.8+)
527
646
 
528
647
  ### `session/fork`
@@ -530,6 +649,7 @@ mechanisms based on client capabilities:
530
649
  Fork a new session from a checkpoint.
531
650
 
532
651
  **Request:**
652
+
533
653
  ```json
534
654
  {
535
655
  "id": 8,
@@ -546,6 +666,7 @@ Fork a new session from a checkpoint.
546
666
  Rewind to a checkpoint.
547
667
 
548
668
  **Request:**
669
+
549
670
  ```json
550
671
  {
551
672
  "id": 9,
@@ -563,6 +684,7 @@ Rewind to a checkpoint.
563
684
  Read / set / replace / clear the goal.
564
685
 
565
686
  **Request:**
687
+
566
688
  ```json
567
689
  {
568
690
  "id": 10,
@@ -582,6 +704,7 @@ Read / set / replace / clear the goal.
582
704
  Compact the conversation history.
583
705
 
584
706
  **Request:**
707
+
585
708
  ```json
586
709
  {
587
710
  "id": 11,
@@ -597,6 +720,7 @@ Compact the conversation history.
597
720
  Append instructions to a running turn.
598
721
 
599
722
  **Request:**
723
+
600
724
  ```json
601
725
  {
602
726
  "id": 12,
@@ -613,6 +737,7 @@ Append instructions to a running turn.
613
737
  Switch the session mode.
614
738
 
615
739
  **Request:**
740
+
616
741
  ```json
617
742
  {
618
743
  "id": 13,
@@ -629,6 +754,7 @@ Switch the session mode.
629
754
  Set the thought level.
630
755
 
631
756
  **Request:**
757
+
632
758
  ```json
633
759
  {
634
760
  "id": 14,
@@ -640,10 +766,189 @@ Set the thought level.
640
766
  }
641
767
  ```
642
768
 
643
- ## Version Compatibility
769
+ ## Background Tasks & Sub-Agents
770
+
771
+ When the model dispatches a sub-agent via the `Agent` (or `Task`) tool, the
772
+ backend keeps producing events on the **same session stream** — both while the
773
+ sub-agent runs and after the main turn ends. The bridge forwards a curated
774
+ subset to the ACP client:
775
+
776
+ ### Synchronous sub-agent (blocking)
777
+
778
+ The `Agent` tool blocks until the sub-agent finishes. Its internal tool calls
779
+ (`Read`, `Bash`, …) arrive as ordinary `tool.updated` events on the main stream
780
+ and are forwarded as regular `tool_call` cards. The `Agent` card itself carries
781
+ structured metadata in `_meta.subagent` (parsed from the result content):
782
+
783
+ ```json
784
+ {
785
+ "sessionUpdate": "tool_call_update",
786
+ "toolCallId": "call_xxx",
787
+ "status": "completed",
788
+ "_meta": {
789
+ "claudeCode": { "toolName": "Agent" },
790
+ "subagent": {
791
+ "agentId": "agent_73c7c63d-...",
792
+ "tokens": 40904,
793
+ "toolUses": 1,
794
+ "durationMs": 10559
795
+ }
796
+ }
797
+ }
798
+ ```
799
+
800
+ ### Background sub-agent (`run_in_background: true`)
801
+
802
+ The `Agent` tool returns immediately with a launch acknowledgement (result
803
+ content contains `agentId` + `output_file` + "working in the background"). The
804
+ main turn then completes, but the backend continues to push the task's
805
+ lifecycle on the same stream:
806
+
807
+ **1. Status changes** — `session.updated` carries a `taskId` and `status`:
808
+
809
+ ```json
810
+ {
811
+ "method": "session/event",
812
+ "params": {
813
+ "sessionId": "sess_abc123",
814
+ "seq": 16,
815
+ "type": "session.updated",
816
+ "payload": {
817
+ "taskId": "agent_88a44529-...",
818
+ "toolCallId": "call_orig",
819
+ "toolName": "Agent",
820
+ "status": "running",
821
+ "description": "Read README first heading",
822
+ "outputPath": "/.../output.txt",
823
+ "terminalId": "agent_88a44529-...",
824
+ "startedAt": "2026-07-18T09:06:45.929Z"
825
+ }
826
+ }
827
+ }
828
+ ```
829
+
830
+ The bridge's session-scoped `BackgroundTaskListener` turns these into a
831
+ dedicated ACP tool card (`[background] <description>`) plus status updates:
832
+
833
+ | Backend event | ACP notification |
834
+ | ------------------------------------------ | -------------------------------------------------------------- |
835
+ | first `session.updated` (status `running`) | `tool_call` (new card, `kind:"other"`, `status:"in_progress"`) |
836
+ | `session.updated` (status `completed`) | `tool_call_update` (`status:"completed"`) |
837
+
838
+ `session.updated` events WITHOUT a `taskId` (e.g. usage updates) are ignored by
839
+ the background listener — they remain owned by the turn loop.
840
+
841
+ **2. Completion notification turn** — when the background task finishes, the
842
+ backend auto-triggers a new turn whose `turn.started` carries
843
+ `inputSource:"background_task"`:
844
+
845
+ ```json
846
+ {
847
+ "type": "turn.started",
848
+ "payload": {
849
+ "inputSource": "background_task",
850
+ "inputVisibility": "model-only",
851
+ "input": "<task-notification>\n <task-id>agent_...</task-id>\n <status>completed</status-status>\n ...\n</task-notification>",
852
+ "turnId": "turn_95197b25-..."
853
+ }
854
+ }
855
+ ```
856
+
857
+ The background listener forwards that turn's `model.streaming text_delta` as
858
+ `agent_message_chunk` so the user sees the background result. The per-prompt
859
+ turn loop **defers** this entire turn (drops its events) to avoid double-
860
+ forwarding and to keep it from prematurely ending the user's real turn.
861
+
862
+ ### Background Bash (`run_in_background: true`)
863
+
864
+ The `Bash` tool launched with `run_in_background: true` returns immediately
865
+ with a launch acknowledgement (result content: "Command running in background
866
+ with ID: exec_…"). Like the Agent sub-agent, the backend keeps pushing the
867
+ task's lifecycle on the same stream via `session.updated` events that carry
868
+ the originating `toolCallId`:
644
869
 
645
- | ZCode CLI version | session/subscribe | Extension methods | Notes |
646
- |---------------|-------------------|----------|------|
647
- | >= 0.15.0 | Supported | All supported | Full functionality |
648
- | >= 0.14.8 | Supported | Partially supported | workspace/* unavailable |
649
- | 0.14.5 ~ 0.14.7 | Not supported | Not supported | Incompatible with this project |
870
+ ```json
871
+ {
872
+ "type": "session.updated",
873
+ "payload": {
874
+ "taskId": "exec_ac3a5053-...",
875
+ "toolCallId": "call_e282b4ec...",
876
+ "toolName": "Bash",
877
+ "status": "running",
878
+ "pid": 22410,
879
+ "outputPath": "/.../call_...-stdout.log",
880
+ "outputTail": "done\n"
881
+ }
882
+ }
883
+ ```
884
+
885
+ **Card reuse, not duplication.** Unlike an Agent sub-agent (which mints a fresh
886
+ `bg_*` card), a background Bash task **reuses the launch card** — the very
887
+ terminal card the dispatcher created when `Bash` was scheduled. This keeps the
888
+ lifecycle on a single card instead of producing a duplicate `[background]` card
889
+ that the editor would show alongside the closed launch card.
890
+
891
+ The mechanism:
892
+
893
+ 1. **Launch turn** — the dispatcher tags the `ToolCallNew`/`ToolCallUpdate`
894
+ with `background: true` (threaded from the cached `input.run_in_background`
895
+ flag) and, on the launch `result`, **skips `terminal_exit`** so the launch
896
+ card stays `in_progress`. It seeds an empty marker in `terminalSentData`
897
+ for the `toolCallId` — this is the signal the background listener uses to
898
+ recognise "this is a tracked launch card".
899
+ 2. **Lifecycle (`session.updated`)** — the `BackgroundTaskListener` resolves
900
+ the `toolCallId`, sees it in `terminalSentData`, and routes status updates
901
+ back to the launch card. On `status:"completed"`, it emits the final
902
+ `outputTail` via `terminal_output` (iff launch text wasn't already streamed)
903
+ and closes the terminal UI with `terminal_exit` (exit code 0, or 1 on
904
+ `failed`), then clears the `terminalSentData` entry.
905
+ 3. **Fallback** — if the `session.updated` lacks a `toolCallId`, or the
906
+ `toolCallId` is unknown to `terminalSentData` (sub-agent case), the listener
907
+ falls back to minting a fresh `bg_*` card — the Agent sub-agent path above.
908
+
909
+ | Backend event | ACP notification (background Bash) |
910
+ | --------------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------- |
911
+ | first `session.updated` (status `running`) | `tool_call_update` on the launch card (`status:"in_progress"`) |
912
+ | `session.updated` (status `completed`, with `outputTail`) | `terminal_output` (final output, if not already streamed) + `tool_call_update` with `terminal_exit` (`status:"completed"`) |
913
+ | `session.updated` (status `failed`) | `tool_call_update` with `terminal_exit` (`status:"failed"`, exit_code 1) |
914
+
915
+ `session/cancelBackgroundTask` for a background Bash task additionally emits
916
+ `terminal_exit` with `_meta.backgroundTask.cancelled = true` so the terminal
917
+ UI closes on cancellation.
918
+
919
+ ### `session/cancelBackgroundTask`
920
+
921
+ Cancels a background task. The bridge additionally marks the corresponding ACP
922
+ tool card as `failed` with `_meta.backgroundTask.cancelled = true`.
923
+
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 |
929
+
930
+ ## Additional backend methods (not wired into the bridge)
931
+
932
+ The backend exposes more RPC methods than the bridge uses (sub-agent listing,
933
+ event pull, session usage/close, automation, workspace config, MCP/plugins).
934
+ These have no ACP-side counterpart yet. See [`BACKLOG.md`](./BACKLOG.md) for
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.