zcode-acp-server 0.1.0 → 0.2.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 (188) hide show
  1. package/README.md +98 -1
  2. package/README.zh-CN.md +81 -1
  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/quota.d.ts +54 -0
  17. package/dist/bin/quota.d.ts.map +1 -0
  18. package/dist/bin/quota.js +333 -0
  19. package/dist/bin/quota.js.map +1 -0
  20. package/dist/config/auto-compact.d.ts +23 -0
  21. package/dist/config/auto-compact.d.ts.map +1 -0
  22. package/dist/config/auto-compact.js +67 -0
  23. package/dist/config/auto-compact.js.map +1 -0
  24. package/dist/config/mcp-discovery.d.ts +34 -0
  25. package/dist/config/mcp-discovery.d.ts.map +1 -0
  26. package/dist/config/mcp-discovery.js +153 -0
  27. package/dist/config/mcp-discovery.js.map +1 -0
  28. package/dist/config/model-cache.d.ts +12 -1
  29. package/dist/config/model-cache.d.ts.map +1 -1
  30. package/dist/config/model-cache.js +30 -8
  31. package/dist/config/model-cache.js.map +1 -1
  32. package/dist/config/options.d.ts +87 -9
  33. package/dist/config/options.d.ts.map +1 -1
  34. package/dist/config/options.js +248 -45
  35. package/dist/config/options.js.map +1 -1
  36. package/dist/config/plugin-commands.d.ts +24 -0
  37. package/dist/config/plugin-commands.d.ts.map +1 -0
  38. package/dist/config/plugin-commands.js +107 -0
  39. package/dist/config/plugin-commands.js.map +1 -0
  40. package/dist/config/provider-registry.d.ts +60 -0
  41. package/dist/config/provider-registry.d.ts.map +1 -0
  42. package/dist/config/provider-registry.js +128 -0
  43. package/dist/config/provider-registry.js.map +1 -0
  44. package/dist/config/runtime-model.d.ts +54 -18
  45. package/dist/config/runtime-model.d.ts.map +1 -1
  46. package/dist/config/runtime-model.js +105 -61
  47. package/dist/config/runtime-model.js.map +1 -1
  48. package/dist/config/skill-discovery.d.ts +35 -0
  49. package/dist/config/skill-discovery.d.ts.map +1 -0
  50. package/dist/config/skill-discovery.js +188 -0
  51. package/dist/config/skill-discovery.js.map +1 -0
  52. package/dist/handlers/background-tasks.d.ts +72 -0
  53. package/dist/handlers/background-tasks.d.ts.map +1 -0
  54. package/dist/handlers/background-tasks.js +330 -0
  55. package/dist/handlers/background-tasks.js.map +1 -0
  56. package/dist/handlers/dispatch.d.ts.map +1 -1
  57. package/dist/handlers/dispatch.js +134 -22
  58. package/dist/handlers/dispatch.js.map +1 -1
  59. package/dist/handlers/extensions.d.ts +15 -0
  60. package/dist/handlers/extensions.d.ts.map +1 -1
  61. package/dist/handlers/extensions.js +43 -27
  62. package/dist/handlers/extensions.js.map +1 -1
  63. package/dist/handlers/io.d.ts +11 -2
  64. package/dist/handlers/io.d.ts.map +1 -1
  65. package/dist/handlers/io.js +51 -7
  66. package/dist/handlers/io.js.map +1 -1
  67. package/dist/handlers/server-requests.d.ts +20 -4
  68. package/dist/handlers/server-requests.d.ts.map +1 -1
  69. package/dist/handlers/server-requests.js +293 -63
  70. package/dist/handlers/server-requests.js.map +1 -1
  71. package/dist/handlers/session.d.ts +113 -10
  72. package/dist/handlers/session.d.ts.map +1 -1
  73. package/dist/handlers/session.js +876 -220
  74. package/dist/handlers/session.js.map +1 -1
  75. package/dist/handlers/slash.d.ts +14 -0
  76. package/dist/handlers/slash.d.ts.map +1 -1
  77. package/dist/handlers/slash.js +80 -6
  78. package/dist/handlers/slash.js.map +1 -1
  79. package/dist/index.js +38 -4
  80. package/dist/index.js.map +1 -1
  81. package/dist/interaction/adapter.d.ts +19 -33
  82. package/dist/interaction/adapter.d.ts.map +1 -1
  83. package/dist/interaction/adapter.js +80 -79
  84. package/dist/interaction/adapter.js.map +1 -1
  85. package/dist/lazy-sessions.d.ts +35 -0
  86. package/dist/lazy-sessions.d.ts.map +1 -0
  87. package/dist/lazy-sessions.js +98 -0
  88. package/dist/lazy-sessions.js.map +1 -0
  89. package/dist/quota/cache.d.ts +18 -0
  90. package/dist/quota/cache.d.ts.map +1 -0
  91. package/dist/quota/cache.js +32 -0
  92. package/dist/quota/cache.js.map +1 -0
  93. package/dist/quota/client.d.ts +30 -0
  94. package/dist/quota/client.d.ts.map +1 -0
  95. package/dist/quota/client.js +57 -0
  96. package/dist/quota/client.js.map +1 -0
  97. package/dist/quota/color.d.ts +58 -0
  98. package/dist/quota/color.d.ts.map +1 -0
  99. package/dist/quota/color.js +95 -0
  100. package/dist/quota/color.js.map +1 -0
  101. package/dist/quota/combined.d.ts +66 -0
  102. package/dist/quota/combined.d.ts.map +1 -0
  103. package/dist/quota/combined.js +179 -0
  104. package/dist/quota/combined.js.map +1 -0
  105. package/dist/quota/format.d.ts +105 -0
  106. package/dist/quota/format.d.ts.map +1 -0
  107. package/dist/quota/format.js +219 -0
  108. package/dist/quota/format.js.map +1 -0
  109. package/dist/quota/index.d.ts +23 -0
  110. package/dist/quota/index.d.ts.map +1 -0
  111. package/dist/quota/index.js +42 -0
  112. package/dist/quota/index.js.map +1 -0
  113. package/dist/quota/opencode-go/cache.d.ts +17 -0
  114. package/dist/quota/opencode-go/cache.d.ts.map +1 -0
  115. package/dist/quota/opencode-go/cache.js +31 -0
  116. package/dist/quota/opencode-go/cache.js.map +1 -0
  117. package/dist/quota/opencode-go/client.d.ts +28 -0
  118. package/dist/quota/opencode-go/client.d.ts.map +1 -0
  119. package/dist/quota/opencode-go/client.js +48 -0
  120. package/dist/quota/opencode-go/client.js.map +1 -0
  121. package/dist/quota/opencode-go/config.d.ts +37 -0
  122. package/dist/quota/opencode-go/config.d.ts.map +1 -0
  123. package/dist/quota/opencode-go/config.js +58 -0
  124. package/dist/quota/opencode-go/config.js.map +1 -0
  125. package/dist/quota/opencode-go/format.d.ts +36 -0
  126. package/dist/quota/opencode-go/format.d.ts.map +1 -0
  127. package/dist/quota/opencode-go/format.js +87 -0
  128. package/dist/quota/opencode-go/format.js.map +1 -0
  129. package/dist/quota/opencode-go/index.d.ts +30 -0
  130. package/dist/quota/opencode-go/index.d.ts.map +1 -0
  131. package/dist/quota/opencode-go/index.js +108 -0
  132. package/dist/quota/opencode-go/index.js.map +1 -0
  133. package/dist/quota/opencode-go/parse.d.ts +41 -0
  134. package/dist/quota/opencode-go/parse.d.ts.map +1 -0
  135. package/dist/quota/opencode-go/parse.js +75 -0
  136. package/dist/quota/opencode-go/parse.js.map +1 -0
  137. package/dist/quota/opencode-go/types.d.ts +48 -0
  138. package/dist/quota/opencode-go/types.d.ts.map +1 -0
  139. package/dist/quota/opencode-go/types.js +11 -0
  140. package/dist/quota/opencode-go/types.js.map +1 -0
  141. package/dist/quota/parse.d.ts +33 -0
  142. package/dist/quota/parse.d.ts.map +1 -0
  143. package/dist/quota/parse.js +200 -0
  144. package/dist/quota/parse.js.map +1 -0
  145. package/dist/quota/types.d.ts +72 -0
  146. package/dist/quota/types.d.ts.map +1 -0
  147. package/dist/quota/types.js +10 -0
  148. package/dist/quota/types.js.map +1 -0
  149. package/dist/server.d.ts +104 -0
  150. package/dist/server.d.ts.map +1 -1
  151. package/dist/server.js +119 -2
  152. package/dist/server.js.map +1 -1
  153. package/dist/tasks-index.d.ts +14 -4
  154. package/dist/tasks-index.d.ts.map +1 -1
  155. package/dist/tasks-index.js +145 -46
  156. package/dist/tasks-index.js.map +1 -1
  157. package/dist/translators/event-translator.d.ts +33 -0
  158. package/dist/translators/event-translator.d.ts.map +1 -1
  159. package/dist/translators/event-translator.js +101 -0
  160. package/dist/translators/event-translator.js.map +1 -1
  161. package/dist/translators/index.d.ts +1 -1
  162. package/dist/translators/index.d.ts.map +1 -1
  163. package/dist/translators/index.js +1 -1
  164. package/dist/translators/index.js.map +1 -1
  165. package/dist/translators/projection-differ.d.ts +8 -0
  166. package/dist/translators/projection-differ.d.ts.map +1 -1
  167. package/dist/translators/projection-differ.js +19 -9
  168. package/dist/translators/projection-differ.js.map +1 -1
  169. package/dist/translators/tool-helpers.d.ts +28 -0
  170. package/dist/translators/tool-helpers.d.ts.map +1 -1
  171. package/dist/translators/tool-helpers.js +104 -0
  172. package/dist/translators/tool-helpers.js.map +1 -1
  173. package/dist/translators/types.d.ts +38 -1
  174. package/dist/translators/types.d.ts.map +1 -1
  175. package/dist/translators/types.js.map +1 -1
  176. package/dist/utils.d.ts +35 -6
  177. package/dist/utils.d.ts.map +1 -1
  178. package/dist/utils.js +52 -4
  179. package/dist/utils.js.map +1 -1
  180. package/docs/ARCHITECTURE.md +96 -38
  181. package/docs/BACKLOG.md +57 -0
  182. package/docs/DEVELOPMENT.md +5 -0
  183. package/docs/PROTOCOL.md +270 -5
  184. package/docs/TROUBLESHOOTING.md +98 -14
  185. package/docs/agents/domain.md +51 -0
  186. package/docs/agents/issue-tracker.md +22 -0
  187. package/docs/agents/triage-labels.md +15 -0
  188. package/package.json +6 -5
@@ -25,18 +25,58 @@ zcode-acp-server (stdio JSON-RPC ACP)
25
25
  zcode app-server --stdio (line-delimited JSON)
26
26
  ```
27
27
 
28
+ ## ACP Handshake
29
+
30
+ The `initialize` request (`server.ts`) negotiates the protocol version and
31
+ declares the agent's shape to the editor:
32
+
33
+ - **`protocolVersion`** — pinned to `PROTOCOL_VERSION` (currently 1).
34
+ - **`agentInfo`** — name/title/version from `AGENT_INFO` in `utils.ts`.
35
+ - **`agentCapabilities`** — `loadSession`, plus `sessionCapabilities.list /
36
+ resume / fork`. The prompt capabilities (image/audio/embeddedContext) and
37
+ MCP capabilities are all off.
38
+ - **`authMethods`** — a single agent-type entry (`zcode-credentials`). The
39
+ bridge reads the GLM API key itself from `~/.zcode/v2/config.json` and
40
+ forwards it to the ZCode subprocess via `ANTHROPIC_API_KEY`; the editor
41
+ never supplies credentials. Omitting the `type` field defaults to `"agent"`,
42
+ which the ACP registry's auth-check accepts as "agent self-handles auth".
43
+
44
+ `initialize` does **not** spawn the backend, and neither does `session/new`:
45
+ the backend is lazily created on the first backend RPC — for a fresh session
46
+ that is the first `session/create` at its first use (prompt / config change /
47
+ extension method). This keeps the handshake succeeding even in an environment
48
+ without `~/.zcode/v2/config.json` (e.g. the registry CI runs `initialize` with
49
+ an isolated `HOME`).
50
+
51
+ Client capabilities advertised at `initialize` are recorded on the server
52
+ (`clientCapabilities`) and drive later behaviour: `supportsElicitationForm()`
53
+ gates form-based elicitation, and `supportsTerminalOutput()` gates Zed's Bash
54
+ terminal UI.
55
+
28
56
  ## Core Data Flow
29
57
 
30
58
  ### 1. Session lifecycle
31
59
 
32
60
  ```
33
- session/new → session/create register EventListener
61
+ session/new → placeholder id (backend session NOT created yet)
62
+ |
63
+ first use: prompt / set_config_option / extension method
64
+ |
65
+ session/create → register EventListener
34
66
  |
35
67
  prompt request → session/send → EventTranslator translates → dispatchEvent
36
68
  | |
37
69
  end_turn / cancelled session/update notification
38
70
  ```
39
71
 
72
+ Sessions are materialized lazily (`ensureRealSession`): an editor startup that
73
+ never sends a message leaves no empty session in the backend or the App's task
74
+ index. The placeholder → backend-session mapping is persisted to
75
+ `~/.zcode/v2/acp-lazy-sessions.json` (`src/lazy-sessions.ts`), so a `session/
76
+ resume` / `session/load` of a placeholder from a previous bridge lifetime still
77
+ resolves: with a recorded backend id the real session is resumed, without one a
78
+ fresh (empty) session is materialized — never "Session not found".
79
+
40
80
  ### 2. Event stream subscription
41
81
 
42
82
  ```
@@ -52,11 +92,13 @@ pollEvent() consumes → EventTranslator.translate()
52
92
  ### 3. Dual-path event handling
53
93
 
54
94
  #### Real-time path (EventTranslator)
95
+
55
96
  - Listens to zcode `session/event` pushes
56
97
  - Translates each event to an ACP `session/update` in real time
57
98
  - Maintains `seenToolIds` to avoid duplicates
58
99
 
59
100
  #### Snapshot path (ProjectionDiffer)
101
+
60
102
  - On turn completion, builds a snapshot from `session/messages` + `session/read`
61
103
  - Diffs two snapshots to produce new events (PlanUpdate / TextDelta / ToolCallNew, etc.)
62
104
  - Used for turn-completion triage and stall recovery
@@ -82,6 +124,7 @@ Key: **`seenToolIds` synchronization**
82
124
 
83
125
  In `session.ts:629`, after the event path finishes processing, the state is
84
126
  synced to the differ:
127
+
85
128
  ```typescript
86
129
  for (const seenId of translator.seenToolIds) {
87
130
  differ.markToolSeen(seenId);
@@ -92,49 +135,64 @@ This ensures the snapshot diff does not re-emit tools already handled by the
92
135
  event path, preventing Bash terminal output from being overwritten by a
93
136
  content-less ToolCallNew.
94
137
 
138
+ ## Data & Privacy
139
+
140
+ **No telemetry, no analytics, no third-party network calls.** The server is a
141
+ local relay: prompts, code, and tool outputs pass through process memory on
142
+ their way between the editor and the ZCode subprocess, but reach the GLM cloud
143
+ API only because the ZCode backend itself sends them for inference.
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 |
151
+
95
152
  ## Module Responsibilities
96
153
 
97
154
  ### `backend/` — ZCode process communication
98
155
 
99
- | File | Responsibility |
100
- |------|------|
101
- | `client.ts` | Spawn/manage the zcode subprocess, reader-loop, request/response multiplexing, process watchdog |
102
- | `listener.ts` | EventStreamListener (subscribe/consume the event stream) and TurnMonitor (snapshot polling) |
103
- | `types.ts` | ZCode JSON-RPC message type definitions |
156
+ | File | Responsibility |
157
+ | ------------- | ----------------------------------------------------------------------------------------------- |
158
+ | `client.ts` | Spawn/manage the zcode subprocess, reader-loop, request/response multiplexing, process watchdog |
159
+ | `listener.ts` | EventStreamListener (subscribe/consume the event stream) and TurnMonitor (snapshot polling) |
160
+ | `types.ts` | ZCode JSON-RPC message type definitions |
104
161
 
105
162
  ### `translators/` — Event translation
106
163
 
107
- | File | Responsibility |
108
- |------|------|
109
- | `event-translator.ts` | Translate zcode events to InternalEvent (real-time path) |
110
- | `projection-differ.ts` | Diff two snapshots to produce InternalEvent (snapshot path) |
111
- | `tool-helpers.ts` | Tool-related pure functions: title generation, output rendering, diff parsing, location extraction |
112
- | `types.ts` | InternalEvent union type and plan entry builders |
164
+ | File | Responsibility |
165
+ | ---------------------- | -------------------------------------------------------------------------------------------------- |
166
+ | `event-translator.ts` | Translate zcode events to InternalEvent (real-time path) |
167
+ | `projection-differ.ts` | Diff two snapshots to produce InternalEvent (snapshot path) |
168
+ | `tool-helpers.ts` | Tool-related pure functions: title generation, output rendering, diff parsing, location extraction |
169
+ | `types.ts` | InternalEvent union type and plan entry builders |
113
170
 
114
171
  ### `handlers/` — ACP method handling
115
172
 
116
- | File | Responsibility |
117
- |------|------|
118
- | `session.ts` | session/new/list/resume/load/prompt/set_config_option/cancel |
119
- | `extensions.ts` | fork/rewind/rewindCascade/goal/compact/steer/cancelBackgroundTask/setModel/setMode/setThoughtLevel |
120
- | `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
+ | `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) |
121
179
  | `server-requests.ts` | Handle zcode interaction/* requests (tool auth, ExitPlanMode, AskUserQuestion), protocol negotiation routing |
122
- | `io.ts` | ACP notification helpers (including `sendAvailableCommandsDeferred` deferred notification) |
123
- | `slash.ts` | Interception of `/`-prefixed commands (/compact /goal /fork /rewind /steer /model /mode /thought) |
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) |
124
182
 
125
183
  ### `interaction/` — Interaction bridging
126
184
 
127
- | File | Responsibility |
128
- |------|------|
185
+ | File | Responsibility |
186
+ | ------------ | ------------------------------------------------------------------------------------------------ |
129
187
  | `adapter.ts` | Conversion adapter from zcode interaction requests to ACP (requestPermission + elicitation form) |
130
188
 
131
189
  ### `config/` — Configuration management
132
190
 
133
- | File | Responsibility |
134
- |------|------|
135
- | `options.ts` | configOptions / modes construction, set_config_option dispatch |
136
- | `runtime-model.ts` | runtimeModel overlay construction and application |
137
- | `model-cache.ts` | Model ID cache and usage initialization |
191
+ | File | Responsibility |
192
+ | ------------------ | -------------------------------------------------------------- |
193
+ | `options.ts` | configOptions / modes construction, set_config_option dispatch |
194
+ | `runtime-model.ts` | runtimeModel overlay construction and application |
195
+ | `model-cache.ts` | Model ID cache and usage initialization |
138
196
 
139
197
  ## Key State Machines
140
198
 
@@ -222,12 +280,12 @@ mirroring the Python bridge's `_pending_post_notifs` queue +
222
280
  The session mode can change through four entry points, all of which must
223
281
  notify the editor UI:
224
282
 
225
- | Trigger | Path | Notifies UI |
226
- |------|------|:---:|
227
- | `session/setMode` request | `extensions.ts:setMode` | yes |
228
- | `session/set_config_option` (mode) | `session.ts:setConfigOptionHandler` → `emitConfigOptionUpdate` | yes |
229
- | `/mode` slash command | `slash.ts` → `emitConfigOptionUpdate` | yes |
230
- | In-turn `EnterPlanMode`/`ExitPlanMode` | reconciled at turn completion | yes |
283
+ | Trigger | Path | Notifies UI |
284
+ | -------------------------------------- | -------------------------------------------------------------- | :---------: |
285
+ | `session/setMode` request | `extensions.ts:setMode` | yes |
286
+ | `session/set_config_option` (mode) | `session.ts:setConfigOptionHandler` → `emitConfigOptionUpdate` | yes |
287
+ | `/mode` slash command | `slash.ts` → `emitConfigOptionUpdate` | yes |
288
+ | In-turn `EnterPlanMode`/`ExitPlanMode` | reconciled at turn completion | yes |
231
289
 
232
290
  The in-turn path bypasses the bridge entirely, so `prompt()` runs
233
291
  `emitModeIfChanged` (`session.ts`) at turn completion: it re-reads the
@@ -277,12 +335,12 @@ zcode process exits, so a normal shutdown leaves no lingering watchdog.
277
335
 
278
336
  ### Why a dual path?
279
337
 
280
- | Scenario | Real-time path | Snapshot path |
281
- |------|---------|----------|
282
- | Normal streaming | Low latency | Must wait for turn end |
283
- | Lost events | Loses data | Recovers from snapshot |
284
- | Deduplication | seenToolIds | seenMessageIds + markToolSeen() |
285
- | Turn-completion triage | Not triggered | PlanUpdate / usage_update |
338
+ | Scenario | Real-time path | Snapshot path |
339
+ | ---------------------- | -------------- | ------------------------------- |
340
+ | Normal streaming | Low latency | Must wait for turn end |
341
+ | Lost events | Loses data | Recovers from snapshot |
342
+ | Deduplication | seenToolIds | seenMessageIds + markToolSeen() |
343
+ | Turn-completion triage | Not triggered | PlanUpdate / usage_update |
286
344
 
287
345
  ### Why no polling fallback?
288
346
 
@@ -0,0 +1,57 @@
1
+ # Protocol Backlog
2
+
3
+ Backend RPC methods and event types exposed by the ZCode CLI (`zcode app-server`)
4
+ that are **not yet wired into the bridge**, tracked for potential future support.
5
+
6
+ Last audited against **app-server 0.15.2** (bundled in ZCode desktop 3.4.2,
7
+ 2026-07-22). Method names were extracted from the bundled `zcode.cjs`.
8
+
9
+ ## Candidate methods (optional enhancements)
10
+
11
+ These are available in the backend but have no ACP-side counterpart yet. Pick
12
+ them up when a concrete ACP/editor need appears.
13
+
14
+ | Method | Purpose | Current bridge behavior |
15
+ |--------|---------|-------------------------|
16
+ | `session/subagents` | Query the list of sub-agents for a session | Sub-agent info is parsed from the `Agent` tool result (`_meta.subagent`); sufficient for now |
17
+ | `session/events` | Pull-mode event history (complement to `session/subscribe`) | Not used; could support event replay/gap-fill |
18
+ | `session/usage` | Per-session token usage | Context bar uses `session.updated` usage payload instead |
19
+ | `session/close` | Explicitly close a session (vs `session/stop` which ends a turn) | Not used; sessions close on process exit |
20
+
21
+ ### New event types (undocumented in PROTOCOL.md)
22
+
23
+ | Event | Status field | Notes |
24
+ |-------|--------------|-------|
25
+ | `turn.steerQueued` | — | Emitted when a `session/steer` is queued behind a running turn. Enhances steer UI feedback |
26
+ | `turn.steerDrained` | — | Emitted when queued steer instructions are drained into the turn |
27
+ | `turn.terminal` | `status: success \| interrupted \| failed`, `resultType?`, `durationMs`, `...usage` | Terminal turn lifecycle event; currently the bridge relies on `turn.completed`/`turn.failed` |
28
+
29
+ ## Not planned (client/config layer)
30
+
31
+ These methods belong to the desktop client or workspace configuration layer and
32
+ have no ACP equivalent. Listed for completeness only — the bridge does not
33
+ intend to surface them.
34
+
35
+ `automation/create`, `automation/list`, `automation/delete` (scheduled tasks),
36
+ `usage/stats`, `workspace/readState`, `workspace/upsertModelProvider`,
37
+ `workspace/removeModelProvider`, `workspace/updateProviderRegistry`,
38
+ `workspace/setDefaultModel`, `workspace/setDefaultThoughtLevel`,
39
+ `workspace/setDefaultMode`, `workspace/generateText`, `mcp/list`,
40
+ `plugins/list`, `plugins/setEnabled`, `plugins/overview`, `plugins/describe`,
41
+ `plugins/marketplace/*`.
42
+
43
+ ## Verification method
44
+
45
+ The bundled CLI is minified, so a literal `grep "session/rewind"` returns 0
46
+ hits even when the method is fully supported — method names are split across
47
+ variable references. To audit reliably:
48
+
49
+ ```sh
50
+ cd /Applications/ZCode.app/Contents/Resources/glm
51
+ # Full RPC enum: search for the method-dispatch table (Key:"ns/name" pairs)
52
+ python3 -c "import re; ..." # see commit that added this file
53
+ # Word-level presence is more reliable for verifying a method still exists:
54
+ grep -oiF "rewind" zcode.cjs | wc -l # 357 → definitely present
55
+ ```
56
+
57
+ Never conclude a method was removed from a single string-literal search.
@@ -117,6 +117,11 @@ cat ~/.zcode/v2/config.json
117
117
  # confirm a provider is enabled and has models
118
118
  ```
119
119
 
120
+ The bridge reads the GLM API key from this file and forwards it to the ZCode
121
+ subprocess — no editor-side API key or environment variable is required. If
122
+ `config.json` is missing or no provider is enabled, install and log into the
123
+ ZCode desktop app so it writes a valid file.
124
+
120
125
  ### Test session/subscribe
121
126
 
122
127
  ```bash
package/docs/PROTOCOL.md CHANGED
@@ -78,7 +78,10 @@ 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:**
84
87
  ```json
@@ -131,6 +134,13 @@ List all sessions.
131
134
 
132
135
  Resume an existing session.
133
136
 
137
+ The sessionId may be a lazy `session/new` placeholder (the editor persists it
138
+ and resumes it after a bridge restart). The bridge resolves it before the
139
+ backend call: an in-memory or persisted (`acp-lazy-sessions.json`) mapping is
140
+ followed to the real backend session — resuming it, or materializing a fresh
141
+ empty one if the placeholder was never used. Real ids from `session/list` pass
142
+ through unchanged.
143
+
134
144
  **Request:**
135
145
  ```json
136
146
  {
@@ -408,6 +418,53 @@ Session state update (usage, etc.).
408
418
  }
409
419
  ```
410
420
 
421
+ ### Steer lifecycle events
422
+
423
+ When `session/steer` appends instructions to a running turn, the backend emits
424
+ a pair of lifecycle events (available in app-server 0.15.2+). The bridge does
425
+ not currently translate these — they are tracked as a future enhancement (see
426
+ [`BACKLOG.md`](./BACKLOG.md)).
427
+
428
+ ```json
429
+ {
430
+ "type": "turn.steerQueued",
431
+ "payload": {}
432
+ }
433
+ ```
434
+
435
+ ```json
436
+ {
437
+ "type": "turn.steerDrained",
438
+ "payload": {}
439
+ }
440
+ ```
441
+
442
+ ### `turn.terminal`
443
+
444
+ Terminal turn lifecycle event (app-server 0.15.2+). Carries a status plus
445
+ usage. The bridge currently relies on `turn.completed`/`turn.failed` instead;
446
+ documented here for completeness.
447
+
448
+ ```json
449
+ {
450
+ "method": "session/event",
451
+ "params": {
452
+ "sessionId": "sess_abc123",
453
+ "seq": 49,
454
+ "type": "turn.terminal",
455
+ "payload": {
456
+ "kind": "turn.terminal",
457
+ "status": "success",
458
+ "resultType": "end_turn",
459
+ "durationMs": 12345,
460
+ "inputTokens": 1234,
461
+ "outputTokens": 567,
462
+ "totalTokens": 1801
463
+ }
464
+ }
465
+ }
466
+ ```
467
+
411
468
  ## Interaction Protocol (Server -> Client)
412
469
 
413
470
  Requests that zcode actively sends to the bridge.
@@ -505,16 +562,34 @@ mechanisms based on client capabilities:
505
562
  "properties": {
506
563
  "q_0": {
507
564
  "type": "string",
508
- "enum": ["auth.test.ts", "user.test.ts"],
509
- "title": "Select the files to test"
565
+ "title": "Select the files to test",
566
+ "oneOf": [
567
+ { "const": "auth.test.ts", "title": "auth.test.ts" },
568
+ { "const": "user.test.ts", "title": "user.test.ts" },
569
+ { "const": "__skip__", "title": "Skip this question" }
570
+ ]
571
+ },
572
+ "q_0_other": {
573
+ "type": "string",
574
+ "title": "↳ or type a custom value (overrides the selection)"
510
575
  }
511
576
  },
512
- "required": ["q_0"]
577
+ "required": []
513
578
  }
514
579
  }
515
580
  }
516
581
  ```
517
582
 
583
+ ACP/MCP elicitation string fields are EITHER an enum (restricted dropdown) OR
584
+ free text — the spec forbids a single field that is both. So each question is
585
+ rendered as TWO fields: `q_<i>` (a `oneOf`/`anyOf` enum dropdown of the model's
586
+ suggested answers, with a trailing "Skip this question" option whose `const` is
587
+ the `__skip__` sentinel and whose `title` is the readable label) and
588
+ `q_<i>_other` (a free-text companion). On submit, a non-empty `q_<i>_other`
589
+ overrides the dropdown (single-select) or is appended to the picked values
590
+ (multi-select); selecting "Skip this question" or leaving both blank skips just
591
+ that question without cancelling the form.
592
+
518
593
  **elicitation response** (accept/decline/cancel):
519
594
  ```json
520
595
  {
@@ -523,6 +598,34 @@ mechanisms based on client capabilities:
523
598
  }
524
599
  ```
525
600
 
601
+ **ExitPlanMode elicitation form** — single `feedback` text field; no
602
+ approve/reject dropdown. The client's own submit button is the approve action;
603
+ typing into the field is the reject action. Submitting with the field empty
604
+ approves the plan; submitting with text rejects it and returns the text to
605
+ zcode as the decline `reason` (so the agent sees the redirection when it
606
+ re-plans). The cancel/decline button is a plain reject with no reason.
607
+ ```json
608
+ {
609
+ "method": "elicitation/create",
610
+ "params": {
611
+ "mode": "form",
612
+ "sessionId": "sess_abc123",
613
+ "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.",
614
+ "requestedSchema": {
615
+ "type": "object",
616
+ "properties": {
617
+ "feedback": {
618
+ "type": "string",
619
+ "title": "Feedback",
620
+ "description": "Empty = approve the plan. Anything typed = reject and use this text as the redirection."
621
+ }
622
+ },
623
+ "required": []
624
+ }
625
+ }
626
+ }
627
+ ```
628
+
526
629
  ## Extension Methods (0.14.8+)
527
630
 
528
631
  ### `session/fork`
@@ -640,10 +743,172 @@ Set the thought level.
640
743
  }
641
744
  ```
642
745
 
643
- ## Version Compatibility
746
+ ## Background Tasks & Sub-Agents
747
+
748
+ When the model dispatches a sub-agent via the `Agent` (or `Task`) tool, the
749
+ backend keeps producing events on the **same session stream** — both while the
750
+ sub-agent runs and after the main turn ends. The bridge forwards a curated
751
+ subset to the ACP client:
752
+
753
+ ### Synchronous sub-agent (blocking)
754
+
755
+ The `Agent` tool blocks until the sub-agent finishes. Its internal tool calls
756
+ (`Read`, `Bash`, …) arrive as ordinary `tool.updated` events on the main stream
757
+ and are forwarded as regular `tool_call` cards. The `Agent` card itself carries
758
+ structured metadata in `_meta.subagent` (parsed from the result content):
759
+
760
+ ```json
761
+ {
762
+ "sessionUpdate": "tool_call_update",
763
+ "toolCallId": "call_xxx",
764
+ "status": "completed",
765
+ "_meta": {
766
+ "claudeCode": { "toolName": "Agent" },
767
+ "subagent": {
768
+ "agentId": "agent_73c7c63d-...",
769
+ "tokens": 40904,
770
+ "toolUses": 1,
771
+ "durationMs": 10559
772
+ }
773
+ }
774
+ }
775
+ ```
776
+
777
+ ### Background sub-agent (`run_in_background: true`)
778
+
779
+ The `Agent` tool returns immediately with a launch acknowledgement (result
780
+ content contains `agentId` + `output_file` + "working in the background"). The
781
+ main turn then completes, but the backend continues to push the task's
782
+ lifecycle on the same stream:
783
+
784
+ **1. Status changes** — `session.updated` carries a `taskId` and `status`:
785
+
786
+ ```json
787
+ {
788
+ "method": "session/event",
789
+ "params": {
790
+ "sessionId": "sess_abc123",
791
+ "seq": 16,
792
+ "type": "session.updated",
793
+ "payload": {
794
+ "taskId": "agent_88a44529-...",
795
+ "toolCallId": "call_orig",
796
+ "toolName": "Agent",
797
+ "status": "running",
798
+ "description": "Read README first heading",
799
+ "outputPath": "/.../output.txt",
800
+ "terminalId": "agent_88a44529-...",
801
+ "startedAt": "2026-07-18T09:06:45.929Z"
802
+ }
803
+ }
804
+ }
805
+ ```
806
+
807
+ The bridge's session-scoped `BackgroundTaskListener` turns these into a
808
+ dedicated ACP tool card (`[background] <description>`) plus status updates:
809
+
810
+ | Backend event | ACP notification |
811
+ |---|---|
812
+ | first `session.updated` (status `running`) | `tool_call` (new card, `kind:"other"`, `status:"in_progress"`) |
813
+ | `session.updated` (status `completed`) | `tool_call_update` (`status:"completed"`) |
814
+
815
+ `session.updated` events WITHOUT a `taskId` (e.g. usage updates) are ignored by
816
+ the background listener — they remain owned by the turn loop.
817
+
818
+ **2. Completion notification turn** — when the background task finishes, the
819
+ backend auto-triggers a new turn whose `turn.started` carries
820
+ `inputSource:"background_task"`:
821
+
822
+ ```json
823
+ {
824
+ "type": "turn.started",
825
+ "payload": {
826
+ "inputSource": "background_task",
827
+ "inputVisibility": "model-only",
828
+ "input": "<task-notification>\n <task-id>agent_...</task-id>\n <status>completed</status-status>\n ...\n</task-notification>",
829
+ "turnId": "turn_95197b25-..."
830
+ }
831
+ }
832
+ ```
833
+
834
+ The background listener forwards that turn's `model.streaming text_delta` as
835
+ `agent_message_chunk` so the user sees the background result. The per-prompt
836
+ turn loop **defers** this entire turn (drops its events) to avoid double-
837
+ forwarding and to keep it from prematurely ending the user's real turn.
838
+
839
+ ### Background Bash (`run_in_background: true`)
840
+
841
+ The `Bash` tool launched with `run_in_background: true` returns immediately
842
+ with a launch acknowledgement (result content: "Command running in background
843
+ with ID: exec_…"). Like the Agent sub-agent, the backend keeps pushing the
844
+ task's lifecycle on the same stream via `session.updated` events that carry
845
+ the originating `toolCallId`:
846
+
847
+ ```json
848
+ {
849
+ "type": "session.updated",
850
+ "payload": {
851
+ "taskId": "exec_ac3a5053-...",
852
+ "toolCallId": "call_e282b4ec...",
853
+ "toolName": "Bash",
854
+ "status": "running",
855
+ "pid": 22410,
856
+ "outputPath": "/.../call_...-stdout.log",
857
+ "outputTail": "done\n"
858
+ }
859
+ }
860
+ ```
861
+
862
+ **Card reuse, not duplication.** Unlike an Agent sub-agent (which mints a fresh
863
+ `bg_*` card), a background Bash task **reuses the launch card** — the very
864
+ terminal card the dispatcher created when `Bash` was scheduled. This keeps the
865
+ lifecycle on a single card instead of producing a duplicate `[background]` card
866
+ that the editor would show alongside the closed launch card.
867
+
868
+ The mechanism:
869
+
870
+ 1. **Launch turn** — the dispatcher tags the `ToolCallNew`/`ToolCallUpdate`
871
+ with `background: true` (threaded from the cached `input.run_in_background`
872
+ flag) and, on the launch `result`, **skips `terminal_exit`** so the launch
873
+ card stays `in_progress`. It seeds an empty marker in `terminalSentData`
874
+ for the `toolCallId` — this is the signal the background listener uses to
875
+ recognise "this is a tracked launch card".
876
+ 2. **Lifecycle (`session.updated`)** — the `BackgroundTaskListener` resolves
877
+ the `toolCallId`, sees it in `terminalSentData`, and routes status updates
878
+ back to the launch card. On `status:"completed"`, it emits the final
879
+ `outputTail` via `terminal_output` (iff launch text wasn't already streamed)
880
+ and closes the terminal UI with `terminal_exit` (exit code 0, or 1 on
881
+ `failed`), then clears the `terminalSentData` entry.
882
+ 3. **Fallback** — if the `session.updated` lacks a `toolCallId`, or the
883
+ `toolCallId` is unknown to `terminalSentData` (sub-agent case), the listener
884
+ falls back to minting a fresh `bg_*` card — the Agent sub-agent path above.
885
+
886
+ | Backend event | ACP notification (background Bash) |
887
+ |---|---|
888
+ | first `session.updated` (status `running`) | `tool_call_update` on the launch card (`status:"in_progress"`) |
889
+ | `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) |
891
+
892
+ `session/cancelBackgroundTask` for a background Bash task additionally emits
893
+ `terminal_exit` with `_meta.backgroundTask.cancelled = true` so the terminal
894
+ UI closes on cancellation.
895
+
896
+ ### `session/cancelBackgroundTask`
897
+
898
+ Cancels a background task. The bridge additionally marks the corresponding ACP
899
+ tool card as `failed` with `_meta.backgroundTask.cancelled = true`.
900
+
901
+
644
902
 
645
903
  | ZCode CLI version | session/subscribe | Extension methods | Notes |
646
904
  |---------------|-------------------|----------|------|
647
905
  | >= 0.15.0 | Supported | All supported | Full functionality |
648
906
  | >= 0.14.8 | Supported | Partially supported | workspace/* unavailable |
649
907
  | 0.14.5 ~ 0.14.7 | Not supported | Not supported | Incompatible with this project |
908
+
909
+ ## Additional backend methods (not wired into the bridge)
910
+
911
+ The backend exposes more RPC methods than the bridge uses (sub-agent listing,
912
+ event pull, session usage/close, automation, workspace config, MCP/plugins).
913
+ These have no ACP-side counterpart yet. See [`BACKLOG.md`](./BACKLOG.md) for
914
+ the full list and which are candidates for future support.