@robota-sdk/agent-transport-acp 3.0.0-beta.99 → 3.0.2

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.
package/CHANGELOG.md CHANGED
@@ -1,5 +1,308 @@
1
1
  # @robota-sdk/agent-transport-acp
2
2
 
3
+ ## 3.0.2
4
+ ### Patch Changes
5
+
6
+ - @robota-sdk/agent-core@3.0.2
7
+ - @robota-sdk/agent-interface-session@3.0.2
8
+ - @robota-sdk/agent-interface-transport@3.0.2
9
+ - @robota-sdk/agent-transport@3.0.2
10
+
11
+ ## 3.0.1
12
+ ### Patch Changes
13
+
14
+ - @robota-sdk/agent-core@3.0.1
15
+ - @robota-sdk/agent-interface-session@3.0.1
16
+ - @robota-sdk/agent-interface-transport@3.0.1
17
+ - @robota-sdk/agent-transport@3.0.1
18
+
19
+ ## 3.0.0
20
+ ### Minor Changes
21
+
22
+ - c967c57: `robota acp` runs the agent as an Agent Client Protocol (v1) agent over stdio, so an editor that launches it (Zed, JetBrains and other ACP clients) can create a session, send prompts, see streamed replies and tool calls, approve or reject permission requests, and cancel a turn. Workspace trust, permission rules and the sandbox behave as in the terminal: the editor answers prompts and widens nothing. A folder that is not trusted yet is refused at `session/new` with the `trust` command to run, a working directory other than the one the agent was started in and any editor-supplied MCP servers or extra roots are refused, and a prompt that starts with `/` runs that command.
23
+
24
+ New package `@robota-sdk/agent-transport-acp` provides `createAcpTransport`, the ACP `ITransportAdapter` the command uses.
25
+
26
+ `@robota-sdk/agent-transport/node` gains `attachStdioCarrier`, the process-input bridge and close observation a stdio protocol carrier shares. `agent-transport-mcp` now uses it for its stdio carrier; its behavior is unchanged.
27
+ - e5a4254: **An editor that offers its file system carries the file tools' reads and writes.**
28
+
29
+ `Read`, `Write` and `Edit` take an optional `editorFiles` port (`IEditorFileAccess`), asked at each
30
+ call. On the host, after the tool's containment check (the permission gate has already decided),
31
+ `Read` returns the editor's text, unsaved changes included, and `Write` hands the content to the
32
+ editor; `Edit` reads where `Read` reads and writes where `Write` writes. The editor's text keeps
33
+ `Read`'s byte budget, binary refusal, formatting and cancellation, and an editor failure becomes the
34
+ tool's error result. `Glob` and `Grep` still search the disk and end their result with a note saying
35
+ so while an editor is attached. A sandbox with its own filesystem still carries every file tool, and
36
+ commands never use the editor. `createDefaultTools` and `createCodingPack` pass `editorFiles` through
37
+ to those tools only.
38
+
39
+ The ACP agent transport records the editor's `clientCapabilities.fs` and, once `session/new` binds the
40
+ session, hands the host an access through the new `onEditorFileAccess` option that sends
41
+ `fs/read_text_file` and `fs/write_text_file` for that session, for what the editor offered; stopping
42
+ the transport withdraws it. `robota acp` connects the two, so Read, Write and Edit go through the
43
+ editor in an editor that offers `fs`; `terminal/*` is not used.
44
+ - 04719a9: **A permission request names the tool call it is about.**
45
+
46
+ `IPermissionRequestEvent` gains an optional `executionId`: the same id the call's `tool_start` and
47
+ `tool_end` carry, so a surface can hold one row from the request to the call's end. The session's
48
+ permission path carries the call's id from the tool wrapper to the prompt (`IPermissionAskContext.executionId`)
49
+ and the framework's prompt registry stamps it on the event, so it reaches every client that receives
50
+ the event, the WebSocket and HTTP `permission_request` frames included. It is present whenever the
51
+ session reports that call (its own, or an in-process background agent's) and absent for a
52
+ child-process agent's forwarded request.
53
+
54
+ The ACP agent transport keys its permission rows by that id: the request's `toolCallId` is the
55
+ call's execution id, its start and end address the same row, a denial the session decides on its
56
+ own while the editor's dialog is open closes that one row, and two same-named calls each follow
57
+ their own row whatever order the editor answers in. A request that names no call gets a row that
58
+ closes with the answer, and is closed at the end of the turn if no answer came. The name-ordered
59
+ matching is gone.
60
+ - 0d43f37: **An editor can reopen an earlier conversation over ACP and offer the agent's slash commands.**
61
+
62
+ `@robota-sdk/agent-transport-acp` sends `available_commands_update` once the editor holds the session
63
+ id — the runtime's commands and the user-invocable skills, each with its argument hint — and again
64
+ after a prompt that changed the list (a plugin reload, a preset). A host can supply `loadSession`;
65
+ the agent then offers `session/load`, checks it exactly like `session/new` (the served directory, no
66
+ editor MCP servers or extra roots, workspace trust), refuses it while a prompt runs, and replays the
67
+ loaded conversation — what was typed, what the agent answered, and each finished tool call, a reused
68
+ call id getting a row of its own — before it answers. The port makes the stored session current in
69
+ the object the transport was attached to, so a host attaches a forwarder such as a session slot.
70
+ The published narrow `IAcpTransportSession` contract remains supported: command listing and history
71
+ display reads are optional capabilities. Loading is offered only with both the host port and the
72
+ history read, so a session cannot be replaced without replaying its conversation.
73
+
74
+ `robota acp` supplies the load: the editor may load a stored session of the folder it serves that
75
+ this version can read, and the agent replaces its session with it and shuts the old one down. A load
76
+ is refused while the current session has work in progress, and for a stored session with an active
77
+ goal, a live loop or a schedule that restore would re-arm to fire, which would otherwise run unseen
78
+ by the editor. With `--no-session-persistence` nothing is stored, so loading is not offered.
79
+ - 04a6de4: **Reasoning text a provider streams now reaches the editor as an ACP `agent_thought_chunk`, through a new `thinking_delta` session event.**
80
+
81
+ `IChatOptions.onThinkingDelta`, `IRunOptions.onThinkingDelta` and `IResumeExecutionOptions.onThinkingDelta`
82
+ (typed `TThinkingDeltaCallback`) carry reasoning text from a provider through the run, and the session
83
+ options (`ISessionOptions.onThinkingDelta`, `ICreateSessionOptions.onThinkingDelta`) hand it to the
84
+ session's owner. `InteractiveSession` emits it as `thinking_delta(delta)`, beside `text_delta`. The
85
+ text is never part of the reply: it is not appended to the conversation, not streamed through
86
+ `onTextDelta`, and the core adds no record of its own for it (no text-delta log entry, no
87
+ `provider_stream_raw_delta`); a provider's own native payload capture and response metadata keep
88
+ what the provider sent, as before. A reasoning delta counts as provider activity for the idle
89
+ timeout whether or not anyone listens to it, so a model that is only thinking is not cut off. The
90
+ existing `thinking(isThinking)` event is unchanged and remains the busy indicator. Where a provider
91
+ sends no reasoning text, nothing is emitted and nothing new is switched on.
92
+
93
+ `@robota-sdk/agent-interface-session` is a major change because a new session event name makes an
94
+ exhaustive map over the event names stop compiling until it classifies the event.
95
+
96
+ `@robota-sdk/agent-transport-acp` sends each non-empty `thinking_delta` to the editor as an
97
+ `agent_thought_chunk`, in order with the message chunks, so `robota acp` shows the model's reasoning
98
+ in the editor. A turn that streamed only thoughts still shows its result text, because only reply
99
+ text marks a turn as streamed. The WebSocket wire, the HTTP stream, headless stream-json, `query()`
100
+ and the terminal UI do not carry the new event in this change (`@robota-sdk/agent-transport` and
101
+ `@robota-sdk/agent-ui-terminal` classify it as non-surface). A forked skill and an in-process subagent
102
+ pass their reply text up but not their reasoning.
103
+
104
+ Two providers already receive reasoning text and now report it. `@robota-sdk/agent-provider-openai`
105
+ passes `response.reasoning_summary_text.delta` events of a streamed Responses call to
106
+ `onThinkingDelta`; the API sends them only when the provider profile asks for a reasoning summary, and
107
+ no request option changed; a summary streamed in several parts, and the summary of a later reasoning
108
+ item, each start on a new paragraph (`\n\n`) so the parts do not run together.
109
+ `@robota-sdk/agent-provider-openai-compatible` reports the text of Gemma's thought channel, which the
110
+ provider already removed from the reply, as it streams across chunk boundaries, with each later
111
+ channel of one reply on a new paragraph; the reply text is unchanged. No other provider reports
112
+ reasoning, and Anthropic thinking is not enabled. A model fallback chain still moves to the next model when only reasoning had streamed.
113
+
114
+ The scripted test provider (`@robota-sdk/agent-core/testing`) accepts an optional `thinking` string on
115
+ a turn and delivers it through `onThinkingDelta` before the turn's text; the scripted session harness
116
+ collects `thinking_delta` and now keeps a scripted tool-call turn's `usage`, which it used to drop.
117
+
118
+ ### Patch Changes
119
+
120
+ - 0f7d214: Name a built-in file tool's path in the permission request an editor is asked about. The title of a Read request now reads `Read: <path>` instead of the tool name alone, because the adapter takes the call's identifying argument from the shared `permissionSummaryArgument` that the MCP transport also uses, which includes `filePath`.
121
+ - 5783499: Harden file, HTTP, relay, hosted-runtime, editor, and native path boundaries against resource exhaustion, short writes, path races, and integer narrowing.
122
+
123
+ Editor integrations now carry `Read`, `Write`, and `Edit` only when their `IEditorFileAccess` adapter declares `pathAuthority: 'stable-root'`; pathname-only adapters continue to receive disk-search notices, while file operations use the tools' stable disk authority. Existing adapters that can prove stable rooted access should add the marker to retain editor-buffer reads and writes.
124
+
125
+ `HttpDagRuntimeProvider` accepts a `token` option and applies it to every orchestration and event-stream request so it interoperates with the authenticated native DAG runtime server.
126
+ - Updated dependencies [7b6234c]
127
+ - Updated dependencies [37b4bd7]
128
+ - Updated dependencies [4eea54b]
129
+ - Updated dependencies [50d2c9f]
130
+ - Updated dependencies [724fabb]
131
+ - Updated dependencies [d877de2]
132
+ - Updated dependencies [d61e159]
133
+ - Updated dependencies [bfe8ed5]
134
+ - Updated dependencies [1698be4]
135
+ - Updated dependencies [a5961c9]
136
+ - Updated dependencies [d4189b9]
137
+ - Updated dependencies [9edae52]
138
+ - Updated dependencies [4078a72]
139
+ - Updated dependencies [4c73a0b]
140
+ - Updated dependencies [196a900]
141
+ - Updated dependencies [f336838]
142
+ - Updated dependencies [34e50f0]
143
+ - Updated dependencies [722e88a]
144
+ - Updated dependencies [d23c848]
145
+ - Updated dependencies [3038eb7]
146
+ - Updated dependencies [02b7452]
147
+ - Updated dependencies [e689c8e]
148
+ - Updated dependencies [ec5e477]
149
+ - Updated dependencies [c7f9203]
150
+ - Updated dependencies [7b72344]
151
+ - Updated dependencies [6ae3f28]
152
+ - Updated dependencies [be0e53c]
153
+ - Updated dependencies [57f57f5]
154
+ - Updated dependencies [ba822c1]
155
+ - Updated dependencies [6e6b06b]
156
+ - Updated dependencies [8bd5fac]
157
+ - Updated dependencies [57280bf]
158
+ - Updated dependencies [5033dd9]
159
+ - Updated dependencies [18c0d5c]
160
+ - Updated dependencies [dbd888d]
161
+ - Updated dependencies [1887e54]
162
+ - Updated dependencies [9f46375]
163
+ - Updated dependencies [41cca13]
164
+ - Updated dependencies [9a01e78]
165
+ - Updated dependencies [ada3841]
166
+ - Updated dependencies [5093a30]
167
+ - Updated dependencies [94b2c87]
168
+ - Updated dependencies [3ab2eca]
169
+ - Updated dependencies [e8d70ac]
170
+ - Updated dependencies [2c612ba]
171
+ - Updated dependencies [66af868]
172
+ - Updated dependencies [3427887]
173
+ - Updated dependencies [c967c57]
174
+ - Updated dependencies [60f3965]
175
+ - Updated dependencies [115e104]
176
+ - Updated dependencies [04719a9]
177
+ - Updated dependencies [04a6de4]
178
+ - Updated dependencies [0ba247b]
179
+ - Updated dependencies [6f41c53]
180
+ - Updated dependencies [4c5148e]
181
+ - Updated dependencies [0116a29]
182
+ - Updated dependencies [fec722f]
183
+ - Updated dependencies [e82215f]
184
+ - Updated dependencies [52b7346]
185
+ - Updated dependencies [9db63ee]
186
+ - Updated dependencies [b078afa]
187
+ - Updated dependencies [2ebff01]
188
+ - Updated dependencies [9db63ee]
189
+ - Updated dependencies [2ebff01]
190
+ - Updated dependencies [2d3b2c0]
191
+ - Updated dependencies [4772067]
192
+ - Updated dependencies [0f98419]
193
+ - Updated dependencies [64ba748]
194
+ - Updated dependencies [9fbab1b]
195
+ - Updated dependencies [d312755]
196
+ - Updated dependencies [a009f5b]
197
+ - Updated dependencies [c207a31]
198
+ - Updated dependencies [9b4f63b]
199
+ - Updated dependencies [3244fb8]
200
+ - Updated dependencies [1e3f91a]
201
+ - Updated dependencies [4f3c075]
202
+ - Updated dependencies [475e085]
203
+ - Updated dependencies [e477440]
204
+ - Updated dependencies [9dcb5da]
205
+ - Updated dependencies [a95ca85]
206
+ - Updated dependencies [b6d14ce]
207
+ - Updated dependencies [0382a51]
208
+ - Updated dependencies [93d061d]
209
+ - Updated dependencies [39554a1]
210
+ - Updated dependencies [d28430a]
211
+ - Updated dependencies [7669851]
212
+ - Updated dependencies [7d9cc66]
213
+ - Updated dependencies [7d9cc66]
214
+ - Updated dependencies [7d9cc66]
215
+ - Updated dependencies [df02568]
216
+ - Updated dependencies [4b76cfa]
217
+ - Updated dependencies [07b627f]
218
+ - Updated dependencies [d0de5b2]
219
+ - Updated dependencies [9c6a8db]
220
+ - Updated dependencies [dfb9b15]
221
+ - Updated dependencies [9665c6e]
222
+ - Updated dependencies [44393be]
223
+ - Updated dependencies [c7fa299]
224
+ - Updated dependencies [7d9cc66]
225
+ - Updated dependencies [7d9cc66]
226
+ - Updated dependencies [5134b3b]
227
+ - Updated dependencies [7911a7d]
228
+ - Updated dependencies [d6b9404]
229
+ - Updated dependencies [62663ae]
230
+ - Updated dependencies [23a30cd]
231
+ - Updated dependencies [7d9cc66]
232
+ - Updated dependencies [7d9cc66]
233
+ - Updated dependencies [5c5ff23]
234
+ - Updated dependencies [9814afc]
235
+ - Updated dependencies [7d9cc66]
236
+ - Updated dependencies [02053ec]
237
+ - Updated dependencies [9c6a8db]
238
+ - @robota-sdk/agent-core@3.0.0
239
+ - @robota-sdk/agent-interface-session@3.0.0
240
+ - @robota-sdk/agent-interface-transport@3.0.0
241
+ - @robota-sdk/agent-transport@3.0.0
242
+
243
+ ## 3.0.0-beta.106
244
+ ### Patch Changes
245
+
246
+ - @robota-sdk/agent-core@3.0.0-beta.106
247
+ - @robota-sdk/agent-interface-session@3.0.0-beta.106
248
+ - @robota-sdk/agent-interface-transport@3.0.0-beta.106
249
+ - @robota-sdk/agent-transport@3.0.0-beta.106
250
+
251
+ ## 3.0.0-beta.105
252
+ ### Patch Changes
253
+
254
+ - @robota-sdk/agent-core@3.0.0-beta.105
255
+ - @robota-sdk/agent-interface-session@3.0.0-beta.105
256
+ - @robota-sdk/agent-interface-transport@3.0.0-beta.105
257
+ - @robota-sdk/agent-transport@3.0.0-beta.105
258
+
259
+ ## 3.0.0-beta.104
260
+ ### Patch Changes
261
+
262
+ - @robota-sdk/agent-core@3.0.0-beta.104
263
+ - @robota-sdk/agent-interface-session@3.0.0-beta.104
264
+ - @robota-sdk/agent-interface-transport@3.0.0-beta.104
265
+ - @robota-sdk/agent-transport@3.0.0-beta.104
266
+
267
+ ## 3.0.0-beta.103
268
+ ### Patch Changes
269
+
270
+ - Updated dependencies [6f41c53]
271
+ - @robota-sdk/agent-core@3.0.0-beta.103
272
+ - @robota-sdk/agent-interface-session@3.0.0-beta.103
273
+ - @robota-sdk/agent-interface-transport@3.0.0-beta.103
274
+ - @robota-sdk/agent-transport@3.0.0-beta.103
275
+
276
+ ## 3.0.0-beta.102
277
+ ### Patch Changes
278
+
279
+ - @robota-sdk/agent-core@3.0.0-beta.102
280
+ - @robota-sdk/agent-interface-session@3.0.0-beta.102
281
+ - @robota-sdk/agent-interface-transport@3.0.0-beta.102
282
+ - @robota-sdk/agent-transport@3.0.0-beta.102
283
+
284
+ ## 3.0.0-beta.101
285
+ ### Patch Changes
286
+
287
+ - 5783499: Harden file, HTTP, relay, hosted-runtime, editor, and native path boundaries against resource exhaustion, short writes, path races, and integer narrowing.
288
+
289
+ Editor integrations now carry `Read`, `Write`, and `Edit` only when their `IEditorFileAccess` adapter declares `pathAuthority: 'stable-root'`; pathname-only adapters continue to receive disk-search notices, while file operations use the tools' stable disk authority. Existing adapters that can prove stable rooted access should add the marker to retain editor-buffer reads and writes.
290
+
291
+ `HttpDagRuntimeProvider` accepts a `token` option and applies it to every orchestration and event-stream request so it interoperates with the authenticated native DAG runtime server.
292
+ - Updated dependencies [62663ae]
293
+ - @robota-sdk/agent-core@3.0.0-beta.101
294
+ - @robota-sdk/agent-interface-session@3.0.0-beta.101
295
+ - @robota-sdk/agent-interface-transport@3.0.0-beta.101
296
+ - @robota-sdk/agent-transport@3.0.0-beta.101
297
+
298
+ ## 3.0.0-beta.100
299
+ ### Patch Changes
300
+
301
+ - @robota-sdk/agent-core@3.0.0-beta.100
302
+ - @robota-sdk/agent-interface-session@3.0.0-beta.100
303
+ - @robota-sdk/agent-interface-transport@3.0.0-beta.100
304
+ - @robota-sdk/agent-transport@3.0.0-beta.100
305
+
3
306
  ## 3.0.0-beta.99
4
307
  ### Patch Changes
5
308
 
package/README.md CHANGED
@@ -49,7 +49,7 @@ anything else in the process can print to it, and send logs to stderr.
49
49
  | `admitWorkspace` | `() => { admitted: true } \| { admitted: false, message }` | The host's trust check, asked on `session/new` and `session/load`. A refusal's `message` is returned to the editor as the error. |
50
50
  | `executeCommand` | `(text, signal) => Promise<string>` | Runs a `/command` prompt and returns the text to show; `signal` aborts on `session/cancel` or when the editor goes away, so pass it on to the command. Without it, a `/command` prompt is answered with a notice. |
51
51
  | `loadSession` | `(sessionId) => Promise<{ loaded: true } \| { loaded: false, message }>` | Makes a stored session of the served directory current in the object the transport was attached to (attach a forwarder such as a session slot). Requires the attached session's `getMessagesDisplay`; without either capability the agent does not offer `session/load`. |
52
- | `onEditorFileAccess` | `(access \| undefined) => void` | Receives the editor's file system (`readTextFile` / `writeTextFile`, only what the editor offered) once the session is bound, and `undefined` when the transport stops. Hand it to file tools that check paths and permissions first; the editor checks nothing for the agent. |
52
+ | `onEditorFileAccess` | `(access \| undefined) => void` | Receives ACP v1 pathname file operations once the session is bound, and `undefined` when the transport stops. They do not carry stable rooted path authority, so contained `Read`, `Write` and `Edit` tools keep their disk capability. |
53
53
  | `driverId` | `TDriverId` | Attribution on turns and prompt answers. Default `'owner'`. |
54
54
  | `surface` | `TUsageSurface` | Usage surface recorded on turns. Default `'unknown'`. |
55
55
  | `onDeliveryError` | `(error, context) => void` | Receives errors the editor does not: a failed notification write, or the cause of a failed turn. |
@@ -16,8 +16,9 @@ interface IAcpTransportSession extends ISessionTurnSubmission, ISessionTurnContr
16
16
  whenInitialized?(): Promise<void>;
17
17
  }
18
18
  /**
19
- * The editor's file system, as the editor offered it (`clientCapabilities.fs`): a method it did not
20
- * offer is absent. Paths are absolute. A failure rejects with the editor's reason.
19
+ * The editor's pathname file system, as the editor offered it (`clientCapabilities.fs`): a method
20
+ * it did not offer is absent. Paths are absolute. ACP v1 does not bind the path to stable rooted
21
+ * authority, so contained host file tools must not treat this as `IEditorFileAccess.pathAuthority`.
21
22
  */
22
23
  interface IAcpEditorFileAccess {
23
24
  readTextFile?(path: string, signal?: AbortSignal): Promise<string>;
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.cts","names":[],"sources":["../../src/acp-types.ts","../../src/acp-transport.ts"],"mappings":";;;;;;;;;UAqBiB,6BAEb,wBACA,qBACA,gBACA,0BACA,oBACA,QAAQ,KAAK,mDACb,QAAQ,KAAK;;;;;;EAMf,oBAAoB;;;;;;UAOL;EACf,cAAc,cAAc,SAAS,cAAc;EACnD,eAAe,cAAc,iBAAiB,SAAS,cAAc;;;KAI3D;WACC;;WAA8B;WAA0B;;;KAGzD;WACC;;WAA4B;WAAwB;;UAEhD;;WAEN,OAAO;WACP,QAAQ;;WAER;aAAsB;aAAuB;;;WAE7C;;WAEA,sBAAsB;;;;;;;WAOtB,kBAAkB,cAAc,QAAQ,gBAAgB;;;;;;;;WAQxD,eAAe,sBAAsB,QAAQ;;;;;;;WAO7C,sBAAsB,QAAQ;;WAE9B,WAAW;;WAEX,UAAU;;;;;WAKV,mBAAmB,OAAO,OAAO;;UAG3B,sBAAsB,kBAAkB;;EAEvD,gBAAgB;;;;iBCjEF,mBAAmB,SAAS,uBAAuB"}
1
+ {"version":3,"file":"index.d.cts","names":[],"sources":["../../src/acp-types.ts","../../src/acp-transport.ts"],"mappings":";;;;;;;;;UAqBiB,6BAEb,wBACA,qBACA,gBACA,0BACA,oBACA,QAAQ,KAAK,mDACb,QAAQ,KAAK;;;;;;EAMf,oBAAoB;;;;;;;UAQL;EACf,cAAc,cAAc,SAAS,cAAc;EACnD,eAAe,cAAc,iBAAiB,SAAS,cAAc;;;KAI3D;WACC;;WAA8B;WAA0B;;;KAGzD;WACC;;WAA4B;WAAwB;;UAEhD;;WAEN,OAAO;WACP,QAAQ;;WAER;aAAsB;aAAuB;;;WAE7C;;WAEA,sBAAsB;;;;;;;WAOtB,kBAAkB,cAAc,QAAQ,gBAAgB;;;;;;;;WAQxD,eAAe,sBAAsB,QAAQ;;;;;;;WAO7C,sBAAsB,QAAQ;;WAE9B,WAAW;;WAEX,UAAU;;;;;WAKV,mBAAmB,OAAO,OAAO;;UAG3B,sBAAsB,kBAAkB;;EAEvD,gBAAgB;;;;iBClEF,mBAAmB,SAAS,uBAAuB"}
@@ -16,8 +16,9 @@ interface IAcpTransportSession extends ISessionTurnSubmission, ISessionTurnContr
16
16
  whenInitialized?(): Promise<void>;
17
17
  }
18
18
  /**
19
- * The editor's file system, as the editor offered it (`clientCapabilities.fs`): a method it did not
20
- * offer is absent. Paths are absolute. A failure rejects with the editor's reason.
19
+ * The editor's pathname file system, as the editor offered it (`clientCapabilities.fs`): a method
20
+ * it did not offer is absent. Paths are absolute. ACP v1 does not bind the path to stable rooted
21
+ * authority, so contained host file tools must not treat this as `IEditorFileAccess.pathAuthority`.
21
22
  */
22
23
  interface IAcpEditorFileAccess {
23
24
  readTextFile?(path: string, signal?: AbortSignal): Promise<string>;
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","names":[],"sources":["../../src/acp-types.ts","../../src/acp-transport.ts"],"mappings":";;;;;;;;;UAqBiB,6BAEb,wBACA,qBACA,gBACA,0BACA,oBACA,QAAQ,KAAK,mDACb,QAAQ,KAAK;;;;;;EAMf,oBAAoB;;;;;;UAOL;EACf,cAAc,cAAc,SAAS,cAAc;EACnD,eAAe,cAAc,iBAAiB,SAAS,cAAc;;;KAI3D;WACC;;WAA8B;WAA0B;;;KAGzD;WACC;;WAA4B;WAAwB;;UAEhD;;WAEN,OAAO;WACP,QAAQ;;WAER;aAAsB;aAAuB;;;WAE7C;;WAEA,sBAAsB;;;;;;;WAOtB,kBAAkB,cAAc,QAAQ,gBAAgB;;;;;;;;WAQxD,eAAe,sBAAsB,QAAQ;;;;;;;WAO7C,sBAAsB,QAAQ;;WAE9B,WAAW;;WAEX,UAAU;;;;;WAKV,mBAAmB,OAAO,OAAO;;UAG3B,sBAAsB,kBAAkB;;EAEvD,gBAAgB;;;;iBCjEF,mBAAmB,SAAS,uBAAuB"}
1
+ {"version":3,"file":"index.d.ts","names":[],"sources":["../../src/acp-types.ts","../../src/acp-transport.ts"],"mappings":";;;;;;;;;UAqBiB,6BAEb,wBACA,qBACA,gBACA,0BACA,oBACA,QAAQ,KAAK,mDACb,QAAQ,KAAK;;;;;;EAMf,oBAAoB;;;;;;;UAQL;EACf,cAAc,cAAc,SAAS,cAAc;EACnD,eAAe,cAAc,iBAAiB,SAAS,cAAc;;;KAI3D;WACC;;WAA8B;WAA0B;;;KAGzD;WACC;;WAA4B;WAAwB;;UAEhD;;WAEN,OAAO;WACP,QAAQ;;WAER;aAAsB;aAAuB;;;WAE7C;;WAEA,sBAAsB;;;;;;;WAOtB,kBAAkB,cAAc,QAAQ,gBAAgB;;;;;;;;WAQxD,eAAe,sBAAsB,QAAQ;;;;;;;WAO7C,sBAAsB,QAAQ;;WAE9B,WAAW;;WAEX,UAAU;;;;;WAKV,mBAAmB,OAAO,OAAO;;UAG3B,sBAAsB,kBAAkB;;EAEvD,gBAAgB;;;;iBClEF,mBAAmB,SAAS,uBAAuB"}
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","names":["asError"],"sources":["../../src/acp-editor-files.ts","../../src/acp-prompt-text.ts","../../src/acp-tool-mapping.ts","../../src/acp-session-catalog.ts","../../src/acp-permission.ts","../../src/acp-session-events.ts","../../src/acp-tool-calls.ts","../../src/acp-turn.ts","../../src/acp-updates.ts","../../src/acp-agent.ts","../../src/acp-transport.ts"],"sourcesContent":["/**\n * The editor's file system, as the editor offered it in `initialize`: reads and writes the host's\n * file tools may hand to it once the session is bound. The tools decide what they touch; this only\n * carries the access, so it never widens what a tool may reach.\n */\n\nimport { isAbsolute } from 'node:path';\n\nimport { RequestError } from '@agentclientprotocol/sdk';\n\nimport type { IAcpEditorFileAccess } from './acp-types.js';\nimport type { AgentContext, ClientCapabilities, SendRequestOptions } from '@agentclientprotocol/sdk';\n\nfunction cancellable(signal: AbortSignal | undefined): SendRequestOptions | undefined {\n return signal === undefined ? undefined : { cancellationSignal: signal };\n}\n\n/** A JSON-RPC error names its kind in `message` and, from an SDK client, the cause in `data.details`. */\nfunction editorError(error: unknown): Error {\n if (!(error instanceof RequestError)) return error instanceof Error ? error : new Error(String(error));\n const data: unknown = error.data;\n const details =\n typeof data === 'object' && data !== null && 'details' in data && typeof data.details === 'string'\n ? data.details\n : undefined;\n return new Error(details === undefined ? error.message : `${error.message}: ${details}`);\n}\n\nfunction absolute(path: string): void {\n // ACP names files by absolute path; a relative one would be resolved by the editor, not the tool.\n if (!isAbsolute(path)) throw new Error(`the editor is given absolute paths only: ${path}`);\n}\n\n/**\n * The access the editor offered, for the bound session; `undefined` when it offered neither read nor\n * write. `sessionId` is read at each request, so the access follows the session the connection serves.\n */\nexport function editorFileAccess(\n fs: ClientCapabilities['fs'],\n client: AgentContext,\n sessionId: () => string | undefined,\n): IAcpEditorFileAccess | undefined {\n const reads = fs?.readTextFile === true;\n const writes = fs?.writeTextFile === true;\n if (!reads && !writes) return undefined;\n const bound = (): string => {\n const id = sessionId();\n if (id === undefined) throw new Error('the editor has no session to read or write for');\n return id;\n };\n return {\n ...(reads\n ? {\n readTextFile: async (path: string, signal?: AbortSignal): Promise<string> => {\n absolute(path);\n try {\n const reply = await client.request(\n 'fs/read_text_file',\n { sessionId: bound(), path },\n cancellable(signal),\n );\n return reply.content;\n } catch (error) {\n throw editorError(error);\n }\n },\n }\n : {}),\n ...(writes\n ? {\n writeTextFile: async (path: string, content: string, signal?: AbortSignal): Promise<void> => {\n absolute(path);\n try {\n await client.request(\n 'fs/write_text_file',\n { sessionId: bound(), path, content },\n cancellable(signal),\n );\n } catch (error) {\n throw editorError(error);\n }\n },\n }\n : {}),\n };\n}\n","/** A prompt's content blocks as the single text the session receives. */\n\nimport type { ContentBlock } from '@agentclientprotocol/sdk';\n\nexport interface IAcpPromptText {\n /** What the person typed: the text blocks alone. A `/command` is recognised on this. */\n readonly typed: string;\n /** What the session receives: the typed text followed by the context the editor attached. */\n readonly full: string;\n}\n\nconst MIN_FENCE = 3;\n\n/** A backtick fence longer than any run inside the text, so attached content cannot close it early. */\nfunction fence(text: string): string {\n const longest = Math.max(0, ...[...text.matchAll(/`+/g)].map((run) => run[0].length));\n return '`'.repeat(Math.max(MIN_FENCE, longest + 1));\n}\n\n/**\n * Text blocks are the typed message. An embedded text resource is appended as fenced context; a\n * resource link is listed by URI so the model can read it under the session's own permissions. Image\n * and audio blocks are not advertised as supported, so they are ignored.\n */\nexport function promptText(blocks: readonly ContentBlock[]): IAcpPromptText {\n let typed = '';\n const context: string[] = [];\n const links: string[] = [];\n for (const block of blocks) {\n if (block.type === 'text') typed += block.text;\n else if (block.type === 'resource' && 'text' in block.resource) {\n const marks = fence(block.resource.text);\n context.push(\n `Context from ${block.resource.uri}:\\n${marks}\\n${block.resource.text}\\n${marks}`,\n );\n } else if (block.type === 'resource_link') links.push(`- ${block.uri}`);\n }\n if (links.length > 0) context.push(`Referenced resources:\\n${links.join('\\n')}`);\n return { typed, full: context.length === 0 ? typed : `${typed}\\n\\n${context.join('\\n\\n')}` };\n}\n","/** How a session tool call is described to an editor. */\n\nimport type { ToolCallStatus, ToolKind } from '@agentclientprotocol/sdk';\nimport type { IToolState } from '@robota-sdk/agent-interface-session';\n\nconst TOOL_KINDS: Readonly<Record<string, ToolKind>> = {\n Read: 'read',\n Glob: 'search',\n Grep: 'search',\n Edit: 'edit',\n Write: 'edit',\n Shell: 'execute',\n Bash: 'execute',\n WebFetch: 'fetch',\n WebSearch: 'fetch',\n};\n\nconst MAX_TITLE_DETAIL = 120;\n\nexport function acpToolKind(toolName: string): ToolKind {\n return TOOL_KINDS[toolName] ?? 'other';\n}\n\n/** The tool's name plus the one argument the session summarises it by, shortened for a title line. */\nexport function acpToolTitle(toolName: string, detail: string | undefined): string {\n const text = detail?.replace(/\\s+/g, ' ').trim();\n if (!text) return toolName;\n const shown = text.length > MAX_TITLE_DETAIL ? `${text.slice(0, MAX_TITLE_DETAIL - 1)}…` : text;\n return `${toolName}: ${shown}`;\n}\n\n/**\n * The tool's name plus its argument as given. A person deciding whether a call may run sees all of\n * what it would do, so nothing is shortened and line breaks stay.\n */\nexport function acpFullToolTitle(toolName: string, detail: string | undefined): string {\n const text = detail?.trim();\n return text ? `${toolName}: ${text}` : toolName;\n}\n\nexport function acpToolStartTitle(tool: IToolState): string {\n return acpToolTitle(tool.toolName, tool.displayPath ?? tool.firstArg);\n}\n\n/** A call that ended in error or was denied is `failed`; only a success is `completed`. */\nexport function acpToolEndStatus(tool: IToolState): ToolCallStatus {\n return tool.result === 'success' ? 'completed' : 'failed';\n}\n","/** What an editor learns about the session besides a turn: the commands it can offer, and the history of a loaded session. */\n\nimport { acpToolEndStatus, acpToolKind, acpToolStartTitle } from './acp-tool-mapping.js';\n\nimport type { IAcpUpdateStream } from './acp-updates.js';\nimport type { AvailableCommand } from '@agentclientprotocol/sdk';\nimport type { IHistoryDisplaySegment, ISessionCommands } from '@robota-sdk/agent-interface-session';\n\n/**\n * The slash commands an editor may offer: the runtime's commands, then the user-invocable skills\n * whose names no command already uses. A command a client implements itself (its runner is the\n * client) is not offered, because an editor cannot run it and the runtime would not.\n */\nexport function availableCommands(\n session: Partial<Pick<ISessionCommands, 'listCommands' | 'listSkills'>>,\n): AvailableCommand[] {\n const offered: AvailableCommand[] = [];\n const names = new Set<string>();\n const offer = (name: string, description: string, hint: string | undefined): void => {\n if (names.has(name)) return;\n names.add(name);\n offered.push({ name, description, ...(hint ? { input: { hint } } : {}) });\n };\n for (const command of session.listCommands?.() ?? []) {\n if (command.runner !== 'runtime') continue;\n offer(command.name, command.description, command.argumentHint);\n }\n for (const skill of session.listSkills?.() ?? []) {\n if (skill.userInvocable) offer(skill.name, skill.description, skill.argumentHint);\n }\n return offered;\n}\n\n/**\n * A loaded session's history as the editor would have seen it live: what the person typed, what the\n * agent answered, and each finished tool call with how it ended. A call keeps its own id unless that\n * id already named a row of this replay (a provider can reuse ids across turns), because the editor\n * would merge the two rows into one. The connection's allocator also keeps later live calls distinct.\n */\nexport function replayHistory(\n segments: readonly IHistoryDisplaySegment[],\n updates: IAcpUpdateStream,\n nextToolCallId: (preferredId?: string) => string,\n): void {\n for (const segment of segments) {\n if (segment.type === 'text') {\n if (segment.content === '') continue;\n updates.send({\n sessionUpdate: segment.role === 'user' ? 'user_message_chunk' : 'agent_message_chunk',\n content: { type: 'text', text: segment.content },\n });\n continue;\n }\n const { tool } = segment;\n if (tool.internal) continue;\n const toolCallId = nextToolCallId(tool.executionId);\n updates.send({\n sessionUpdate: 'tool_call',\n toolCallId,\n title: acpToolStartTitle(tool),\n kind: acpToolKind(tool.toolName),\n status: acpToolEndStatus(tool),\n });\n }\n}\n","/** How a session permission prompt becomes an ACP permission request and its answer becomes a decision. */\n\nimport { resolve } from 'node:path';\n\nimport { permissionSummaryArgument } from '@robota-sdk/agent-transport';\n\nimport { acpFullToolTitle, acpToolKind } from './acp-tool-mapping.js';\n\nimport type {\n PermissionOption,\n RequestPermissionOutcome,\n ToolCallUpdate,\n} from '@agentclientprotocol/sdk';\nimport type {\n IDiffLine,\n IPermissionRequestEvent,\n TPermissionResultValue,\n} from '@robota-sdk/agent-interface-session';\n\nconst OPTION = {\n allowOnce: 'allow-once',\n allowSession: 'allow-session',\n allowProject: 'allow-project',\n reject: 'reject',\n} as const;\n\nexport function permissionOptions(canPersistProject: boolean): PermissionOption[] {\n return [\n { optionId: OPTION.allowOnce, name: 'Allow once', kind: 'allow_once' },\n { optionId: OPTION.allowSession, name: 'Allow for this session', kind: 'allow_always' },\n ...(canPersistProject\n ? [\n {\n optionId: OPTION.allowProject,\n name: 'Allow for this project',\n kind: 'allow_always' as const,\n },\n ]\n : []),\n { optionId: OPTION.reject, name: 'Reject', kind: 'reject_once' },\n ];\n}\n\nexport function permissionTitle(event: IPermissionRequestEvent): string {\n const title = acpFullToolTitle(\n event.toolName,\n event.diffFile ?? permissionSummaryArgument(event.toolArgs),\n );\n return event.requester ? `${event.requester.label}: ${title}` : title;\n}\n\n/** What the call was asked with, including a shell call's working directory when the session resolved one. */\nfunction permissionRawInput(event: IPermissionRequestEvent): unknown {\n if (event.cwd === undefined || 'cwd' in event.toolArgs) return event.toolArgs;\n return { ...event.toolArgs, cwd: event.cwd };\n}\n\nconst HUNK_HEADER = /^@@ /;\n\n/**\n * The file as it is before and after, rebuilt from the session's diff lines: context lines belong\n * to both sides. A diff with nothing on the old side is a new file, which ACP spells as no old text.\n * The session caps each side and says so in a hunk line placed after the lines it kept; that line\n * stays with its side so a cut-off file is not presented as whole. A hunk header carries no text.\n */\nfunction diffTexts(lines: readonly IDiffLine[]): { oldText?: string; newText: string } {\n const before: string[] = [];\n const after: string[] = [];\n let previous: IDiffLine['type'] = 'context';\n for (const line of lines) {\n if (line.type === 'hunk') {\n if (HUNK_HEADER.test(line.text)) continue;\n if (previous !== 'add') before.push(line.text);\n if (previous !== 'remove') after.push(line.text);\n continue;\n }\n previous = line.type;\n if (line.type !== 'add') before.push(line.text);\n if (line.type !== 'remove') after.push(line.text);\n }\n return {\n ...(before.length > 0 ? { oldText: before.join('\\n') } : {}),\n newText: after.join('\\n'),\n };\n}\n\n/**\n * The tool call a permission request describes. The editor gets what the terminal shows: the\n * arguments whole, and for an edit the diff and the file it concerns. `cwd` roots a path the\n * session reported relative to the workspace, because ACP paths are absolute.\n */\nexport function permissionToolCall(\n event: IPermissionRequestEvent,\n toolCallId: string,\n cwd: string,\n): ToolCallUpdate {\n const path = event.diffFile === undefined ? undefined : resolve(cwd, event.diffFile);\n return {\n toolCallId,\n title: permissionTitle(event),\n kind: acpToolKind(event.toolName),\n status: 'pending',\n rawInput: permissionRawInput(event),\n ...(path !== undefined && event.diffLines !== undefined\n ? { content: [{ type: 'diff' as const, path, ...diffTexts(event.diffLines) }] }\n : {}),\n ...(path !== undefined ? { locations: [{ path }] } : {}),\n };\n}\n\n/**\n * The editor's answer as a session decision. Anything the editor was not offered, or that is not\n * recognised, is a denial: an answer widens permission only by naming an option this request listed.\n */\nexport function permissionDecision(\n outcome: RequestPermissionOutcome,\n offered: readonly PermissionOption[],\n): TPermissionResultValue {\n if (outcome.outcome !== 'selected') return false;\n if (!offered.some((option) => option.optionId === outcome.optionId)) return false;\n switch (outcome.optionId) {\n case OPTION.allowOnce:\n return true;\n case OPTION.allowSession:\n return 'allow-session';\n case OPTION.allowProject:\n return 'allow-project';\n default:\n return false;\n }\n}\n","/**\n * Which session events this transport acts on.\n *\n * This is the ACP owner's own exhaustive policy over the shared event vocabulary, not the WebSocket\n * fan-out's: a new session event fails compilation here until someone decides what an editor sees.\n * The subscription is derived from it, so promoting an event to `stream` or `permission` without a\n * handler fails compilation too.\n */\n\nimport type {\n IInteractiveSessionEvents,\n ISessionEvents,\n TInteractiveEventName,\n} from '@robota-sdk/agent-interface-session';\n\n/**\n * - `stream`: forwarded to the editor as a session update while a prompt runs.\n * - `permission`: becomes a permission request to the editor.\n * - `settle`: ends the turn; the outcome is read from the submission's turn handle, which names its\n * own turn, rather than from the session-wide event, which cannot say which turn ended.\n * - `ignore`: nothing an ACP editor shows. `ask_request` is deliberately here: a session with no\n * listener answers an ask as cancelled, which fails closed because ACP v1 has no question prompt.\n */\nexport type TAcpEventPolicy = 'stream' | 'permission' | 'settle' | 'ignore';\n\nexport const ACP_SESSION_EVENT_POLICY = {\n text_delta: 'stream',\n tool_start: 'stream',\n tool_end: 'stream',\n thinking: 'ignore', // a busy flag with no text; the reasoning text is `thinking_delta`\n thinking_delta: 'stream',\n complete: 'settle',\n error: 'settle',\n interrupted: 'settle',\n permission_request: 'permission',\n ask_request: 'ignore',\n context_update: 'ignore',\n compact: 'ignore',\n skill_activation: 'ignore',\n background_task_event: 'ignore',\n background_job_group_event: 'ignore',\n execution_workspace_event: 'ignore',\n user_message: 'ignore',\n turn_source: 'ignore',\n context_file_refreshed: 'ignore',\n memory_event: 'ignore',\n goal_event: 'ignore',\n plan_event: 'ignore',\n branch_event: 'ignore',\n prompt_resolved: 'ignore',\n ui_intent: 'ignore',\n session_renamed: 'ignore',\n history_cleared: 'ignore',\n session_switched: 'ignore',\n status_changed: 'ignore',\n} as const satisfies Record<TInteractiveEventName, TAcpEventPolicy>;\n\ntype TPolicyMap = typeof ACP_SESSION_EVENT_POLICY;\ntype TEventsWith<P extends TAcpEventPolicy> = {\n [K in keyof TPolicyMap]: TPolicyMap[K] extends P ? K : never;\n}[keyof TPolicyMap];\n\nexport type TAcpSubscribedEvent = TEventsWith<'stream' | 'permission'>;\n\n/** Exactly one handler for every event the policy subscribes, and none for any other. */\nexport type TAcpEventHandlers = {\n readonly [K in TAcpSubscribedEvent]: IInteractiveSessionEvents[K];\n};\n\n/** Subscribe the handlers; returns the unsubscribe. */\nexport function subscribeAcpEvents(\n session: ISessionEvents,\n handlers: TAcpEventHandlers,\n): () => void {\n const names = Object.keys(handlers) as TAcpSubscribedEvent[];\n for (const name of names) session.on(name, handlers[name]);\n return () => {\n for (const name of names) session.off(name, handlers[name]);\n };\n}\n","/** The tool-call rows one turn shows an editor: which session call each row stands for, and when it is done. */\n\nimport { permissionToolCall } from './acp-permission.js';\nimport { acpToolEndStatus, acpToolKind, acpToolStartTitle } from './acp-tool-mapping.js';\n\nimport type { IAcpUpdateStream } from './acp-updates.js';\nimport type { ToolCallUpdate } from '@agentclientprotocol/sdk';\nimport type { IPermissionRequestEvent, IToolState } from '@robota-sdk/agent-interface-session';\n\nexport interface IAcpToolCallTracker {\n start(tool: IToolState): void;\n end(tool: IToolState): void;\n /** The row a permission request asks about; when the request names its call, the row is that call's. */\n requested(event: IPermissionRequestEvent): ToolCallUpdate;\n /** The editor answered; the session decides what happens next, and its tool events say so. */\n answered(event: IPermissionRequestEvent, allowed: boolean): void;\n /** The turn is over: close every row still waiting for a call that never came. */\n finish(): void;\n}\n\nexport interface IAcpToolCallDeps {\n readonly updates: IAcpUpdateStream;\n /** Allocate a new row id, preserving a preferred execution id only when no earlier row used it. */\n readonly nextToolCallId: (preferredId?: string) => string;\n /** The served directory, for paths the session reports relative to it. */\n readonly cwd: string;\n}\n\ninterface IOpenCall {\n readonly toolName: string;\n readonly executionId: string | undefined;\n readonly id: string;\n}\n\n/** A permission request's row, held until the call it names starts or is refused. */\ninterface IAwaitedCall {\n readonly id: string;\n readonly rawInput: unknown;\n /** A background agent's call can run after this prompt, so its row closes with the answer. */\n readonly background: boolean;\n allowed?: boolean;\n}\n\nexport function createToolCallTracker(deps: IAcpToolCallDeps): IAcpToolCallTracker {\n const { updates } = deps;\n const open: IOpenCall[] = [];\n /**\n * The session asks permission before it reports a call, and a refused call is reported ending\n * without ever having started. A request names its call by the execution id the call's own\n * events carry, so the request's row is keyed by it and those events address the same row.\n */\n const awaited = new Map<string, IAwaitedCall>();\n /** Rows of requests that name no call, by request id: nothing else will close them. */\n const unnamed = new Map<string, string>();\n let finished = false;\n\n const take = (executionId: string | undefined): IAwaitedCall | undefined => {\n if (executionId === undefined) return undefined;\n const call = awaited.get(executionId);\n awaited.delete(executionId);\n return call;\n };\n const close = (toolCallId: string, allowed: boolean): void =>\n updates.send({\n sessionUpdate: 'tool_call_update',\n toolCallId,\n status: allowed ? 'completed' : 'failed',\n });\n\n return {\n start(tool) {\n // An internal signal tool is not a call the person made or should see.\n if (tool.internal) return;\n const asked = take(tool.executionId);\n const id = asked?.id ?? deps.nextToolCallId(tool.executionId);\n open.push({ id, toolName: tool.toolName, executionId: tool.executionId });\n const call = {\n toolCallId: id,\n title: acpToolStartTitle(tool),\n kind: acpToolKind(tool.toolName),\n status: 'in_progress',\n } as const;\n if (asked === undefined) updates.send({ sessionUpdate: 'tool_call', ...call });\n else {\n updates.send({ sessionUpdate: 'tool_call_update', ...call, rawInput: asked.rawInput });\n }\n },\n\n end(tool) {\n if (tool.internal) return;\n const status = acpToolEndStatus(tool);\n const index = open.findIndex((call) =>\n tool.executionId !== undefined\n ? call.executionId === tool.executionId\n : call.executionId === undefined && call.toolName === tool.toolName,\n );\n if (index >= 0) {\n const [call] = open.splice(index, 1);\n updates.send({ sessionUpdate: 'tool_call_update', toolCallId: call!.id, status });\n return;\n }\n // A refused call ends with no start; when its permission was asked, that row is the call.\n const asked = take(tool.executionId);\n if (asked !== undefined) {\n updates.send({ sessionUpdate: 'tool_call_update', toolCallId: asked.id, status });\n return;\n }\n // Otherwise give the editor the call, finished, rather than an update for nothing.\n updates.send({\n sessionUpdate: 'tool_call',\n toolCallId: deps.nextToolCallId(tool.executionId),\n title: acpToolStartTitle(tool),\n kind: acpToolKind(tool.toolName),\n status,\n });\n },\n\n requested(event) {\n const { executionId } = event;\n if (executionId === undefined) {\n const call = permissionToolCall(\n event,\n deps.nextToolCallId(`permission-${event.id}`),\n deps.cwd,\n );\n unnamed.set(event.id, call.toolCallId);\n return call;\n }\n // A repeated request and the call's own start/end keep the row allocated for this call.\n const running = open.find((call) => call.executionId === executionId);\n const waiting = awaited.get(executionId);\n const id = running?.id ?? waiting?.id ?? deps.nextToolCallId(executionId);\n const call = permissionToolCall(event, id, deps.cwd);\n if (running === undefined && waiting === undefined) {\n awaited.set(executionId, {\n id,\n rawInput: call.rawInput,\n background: event.requester !== undefined,\n });\n }\n return call;\n },\n\n answered(event, allowed) {\n if (event.executionId !== undefined) {\n // The call's own events say what the answer led to; the answer is kept for a call that\n // may run after this turn.\n const asked = awaited.get(event.executionId);\n if (asked !== undefined) asked.allowed = allowed;\n return;\n }\n const row = unnamed.get(event.id);\n if (row === undefined || finished) return;\n unnamed.delete(event.id);\n close(row, allowed);\n },\n\n finish() {\n finished = true;\n for (const call of awaited.values()) close(call.id, call.background && call.allowed === true);\n for (const row of unnamed.values()) close(row, false);\n awaited.clear();\n unnamed.clear();\n },\n };\n}\n","/**\n * One `session/prompt` that runs a turn: stream the session's events to the editor, ask it about\n * permissions, and settle with a stop reason. A typed message and a `/command` that runs a model\n * turn (a skill) both go through here, so the editor sees the same thing from either.\n */\n\nimport { RequestError } from '@agentclientprotocol/sdk';\nimport { isTurnNotRunError } from '@robota-sdk/agent-interface-session';\n\nimport { permissionDecision, permissionOptions } from './acp-permission.js';\nimport { subscribeAcpEvents } from './acp-session-events.js';\nimport { createToolCallTracker } from './acp-tool-calls.js';\n\nimport type { IAcpTransportSession } from './acp-types.js';\nimport type { IAcpUpdateStream } from './acp-updates.js';\nimport type { AgentContext, PromptResponse } from '@agentclientprotocol/sdk';\nimport type {\n IPermissionRequestEvent,\n ISubmitOptions,\n TDriverId,\n} from '@robota-sdk/agent-interface-session';\n\nexport interface IAcpTurnContext {\n readonly session: IAcpTransportSession;\n readonly sessionId: string;\n readonly client: AgentContext;\n readonly updates: IAcpUpdateStream;\n /** Aborts when the editor cancels the request or the connection closes. */\n readonly signal: AbortSignal;\n readonly driverId: TDriverId;\n readonly surface: ISubmitOptions['surface'];\n /** The served directory; paths the session reports relative to it are made absolute for the editor. */\n readonly cwd: string;\n /** Allocate a new row id, preserving a preferred execution id only when no earlier row used it. */\n readonly nextToolCallId: (preferredId?: string) => string;\n /** Whether the editor sent `session/cancel` for this prompt. */\n readonly wasCancelled: () => boolean;\n readonly report: (error: Error, context: string) => void;\n}\n\nconst CANCELLED: PromptResponse = { stopReason: 'cancelled' };\nconst END_TURN: PromptResponse = { stopReason: 'end_turn' };\n\nfunction asError(error: unknown): Error {\n return error instanceof Error ? error : new Error(String(error));\n}\n\n/** What a turn's work left behind: whether it was cut short, and the text to show if nothing streamed. */\ninterface IWorkOutcome {\n readonly interrupted: boolean;\n readonly reply: string;\n}\n\ninterface IStreamedWork {\n run(): Promise<IWorkOutcome>;\n /** A failure that was not a cancellation, as the editor's answer: it reports the cause and throws a fixed error. */\n fail(error: unknown): never;\n /** Whether `error` is the work saying it was cancelled. */\n isCancellation?(error: unknown): boolean;\n}\n\nasync function runStreamedTurn(\n turn: IAcpTurnContext,\n work: IStreamedWork,\n): Promise<PromptResponse> {\n const { session, sessionId, client, updates } = turn;\n const tools = createToolCallTracker({\n updates,\n nextToolCallId: turn.nextToolCallId,\n cwd: turn.cwd,\n });\n let streamed = false;\n\n const onPermissionRequest = (event: IPermissionRequestEvent): void => {\n const options = permissionOptions(event.canPersistProjectPermission === true);\n const toolCall = tools.requested(event);\n void (async () => {\n let decision: ReturnType<typeof permissionDecision> = false;\n try {\n const response = await client.request('session/request_permission', {\n sessionId,\n toolCall,\n options,\n });\n decision = permissionDecision(response.outcome, options);\n } catch (error) {\n // A closed connection or a refused request means nobody approved: deny.\n turn.report(asError(error), 'session/request_permission');\n }\n tools.answered(event, decision !== false);\n try {\n session.resolvePermission(event.id, decision, turn.driverId);\n } catch (error) {\n turn.report(asError(error), 'resolvePermission');\n }\n })();\n };\n\n const unsubscribe = subscribeAcpEvents(session, {\n text_delta: (delta) => {\n if (delta === '') return;\n streamed = true;\n updates.text(delta);\n },\n // Reasoning is not the reply: it never marks the turn streamed, so a turn that showed only\n // thoughts still gets its result text.\n thinking_delta: (delta) => {\n if (delta === '') return;\n updates.thought(delta);\n },\n tool_start: tools.start,\n tool_end: tools.end,\n permission_request: onPermissionRequest,\n });\n\n try {\n const outcome = await work.run();\n if (turn.wasCancelled() || turn.signal.aborted || outcome.interrupted) return CANCELLED;\n // Nothing streamed (a provider without deltas, or a command that is not a turn): the text arrives whole.\n if (!streamed && outcome.reply !== '') updates.text(outcome.reply);\n return END_TURN;\n } catch (error) {\n if (turn.wasCancelled() || turn.signal.aborted || work.isCancellation?.(error) === true) {\n return CANCELLED;\n }\n return work.fail(error);\n } finally {\n unsubscribe();\n tools.finish();\n await updates.drained();\n }\n}\n\n/** A typed message: submit it to the session and take the turn's result. */\nexport function runModelTurn(turn: IAcpTurnContext, text: string): Promise<PromptResponse> {\n return runStreamedTurn(turn, {\n async run() {\n const handle = await turn.session.submit(text, undefined, undefined, {\n signal: turn.signal,\n driverId: turn.driverId,\n ...(turn.surface !== undefined ? { surface: turn.surface } : {}),\n });\n const result = await handle.completed;\n return { interrupted: result.interrupted === true, reply: result.response };\n },\n isCancellation: (error) => isTurnNotRunError(error) && error.reason === 'cancelled',\n fail(error) {\n turn.report(asError(error), 'turn');\n // The provider's own text can carry request details; the editor gets a fixed message.\n throw new RequestError(-32603, isTurnNotRunError(error) ? 'Turn was not run' : 'Turn failed');\n },\n });\n}\n\n/**\n * A `/command` prompt: the host runs it and its output is the reply. A command that runs a model\n * turn (a skill) streams that turn and asks about permissions while it runs; its output then\n * repeats the streamed reply, so it is shown only when nothing streamed (`/help`, a message).\n */\nexport function runCommandTurn(\n turn: IAcpTurnContext,\n text: string,\n executeCommand: (text: string, signal: AbortSignal) => Promise<string>,\n): Promise<PromptResponse> {\n return runStreamedTurn(turn, {\n async run() {\n return { interrupted: false, reply: await executeCommand(text, turn.signal) };\n },\n fail(error) {\n turn.report(asError(error), 'command');\n throw new RequestError(-32603, 'Command failed');\n },\n });\n}\n","/** Ordered `session/update` delivery for one prompt. */\n\nimport type { AgentContext, SessionUpdate } from '@agentclientprotocol/sdk';\n\nexport interface IAcpUpdateStream {\n send(update: SessionUpdate): void;\n /** Queue message text for the editor. */\n text(text: string): void;\n /** Queue reasoning text for the editor, shown apart from the reply. */\n thought(text: string): void;\n /** Settles once everything queued has been written or abandoned. Never rejects. */\n drained(): Promise<void>;\n}\n\n/**\n * Updates are written in the order they were queued, and a prompt's reply waits on `drained()`, so\n * the editor has every update before it sees `stopReason`. A failed write is reported once and the\n * rest are dropped: the editor is gone, and the session turn is not the carrier's to fail.\n */\nexport function createUpdateStream(\n client: AgentContext,\n sessionId: string,\n report: (error: Error, context: string) => void,\n): IAcpUpdateStream {\n let tail: Promise<void> = Promise.resolve();\n let failed = false;\n const send = (update: SessionUpdate): void => {\n tail = tail.then(async () => {\n if (failed) return;\n try {\n await client.notify('session/update', { sessionId, update });\n } catch (error) {\n failed = true;\n report(error instanceof Error ? error : new Error(String(error)), 'session/update');\n }\n });\n };\n return {\n send,\n text: (text) => send({ sessionUpdate: 'agent_message_chunk', content: { type: 'text', text } }),\n thought: (text) =>\n send({ sessionUpdate: 'agent_thought_chunk', content: { type: 'text', text } }),\n drained: () => tail,\n };\n}\n","/** The ACP agent handlers for one connection: one attached session, one prompt at a time. */\n\nimport { realpathSync } from 'node:fs';\nimport { isAbsolute, resolve } from 'node:path';\n\nimport { agent, PROTOCOL_VERSION, RequestError } from '@agentclientprotocol/sdk';\nimport { OWNER_DRIVER_ID } from '@robota-sdk/agent-interface-session';\n\nimport { editorFileAccess } from './acp-editor-files.js';\nimport { promptText } from './acp-prompt-text.js';\nimport { availableCommands, replayHistory } from './acp-session-catalog.js';\nimport { runCommandTurn, runModelTurn } from './acp-turn.js';\nimport { createUpdateStream } from './acp-updates.js';\n\nimport type { IAcpTurnContext } from './acp-turn.js';\nimport type { IAcpTransportOptions, IAcpTransportSession } from './acp-types.js';\nimport type {\n AgentApp,\n AgentContext,\n ClientCapabilities,\n McpServer,\n PromptResponse,\n} from '@agentclientprotocol/sdk';\n\n/**\n * JSON-RPC server-error code for a workspace the host has not admitted. Not -32000: ACP assigns\n * that to \"authentication required\", which an editor answers with a sign-in state this agent\n * does not have.\n */\nconst WORKSPACE_NOT_ADMITTED = -32001;\nconst INTERNAL_ERROR = -32603;\nconst COMMANDS_UNAVAILABLE = 'Slash commands are not available in this host.';\n\nconst commandsUnavailable = (): Promise<string> => Promise.resolve(COMMANDS_UNAVAILABLE);\n\nexport interface IAcpAgent {\n readonly app: AgentApp;\n /** Cancel the running prompt, if there is one. */\n cancelActive(): void;\n /** Tell the host the editor's file system is gone, if it was handed one. */\n withdrawEditorFiles(): void;\n}\n\n/** What `session/new` and `session/load` both carry that decides whether the editor may have the session. */\ninterface ISessionRequest {\n readonly cwd: string;\n readonly mcpServers: readonly McpServer[];\n readonly additionalDirectories?: readonly string[] | null;\n}\n\nfunction sameDirectory(requested: string, served: string): boolean {\n if (resolve(requested) === resolve(served)) return true;\n try {\n return realpathSync(requested) === realpathSync(served);\n } catch {\n // allow-fallback: a path that cannot be resolved is not the served directory, and the caller refuses it.\n return false;\n }\n}\n\nfunction asError(error: unknown): Error {\n return error instanceof Error ? error : new Error(String(error));\n}\n\nexport function createAcpAgent(\n session: IAcpTransportSession,\n options: IAcpTransportOptions,\n): IAcpAgent {\n const driverId = options.driverId ?? OWNER_DRIVER_ID;\n const report = (error: Error, context: string): void => options.onDeliveryError?.(error, context);\n let boundSessionId: string | undefined;\n /** A `session/new` or `session/load` is still settling which session this connection serves. */\n let binding = false;\n let active: { cancel(): void } | undefined;\n let toolCalls = 0;\n const usedToolCallIds = new Set<string>();\n const nextToolCallId = (preferredId?: string): string => {\n let id = preferredId;\n while (id === undefined || usedToolCallIds.has(id)) id = `tool-${(toolCalls += 1)}`;\n usedToolCallIds.add(id);\n return id;\n };\n /** The command list the editor last received, so an unchanged list is not sent again. */\n let advertised: string | undefined;\n\n /** Refuse what the editor may not ask for; the same rules hold for a new and a loaded session. */\n const admit = (params: ISessionRequest): void => {\n if (!isAbsolute(params.cwd) || !sameDirectory(params.cwd, options.cwd)) {\n throw RequestError.invalidParams(\n undefined,\n `cwd must be the directory this agent was started in: ${options.cwd}`,\n );\n }\n // Servers or roots named by the editor would widen what the session can reach.\n if (params.mcpServers.length > 0) {\n throw RequestError.invalidParams(undefined, 'mcpServers are not accepted by this agent');\n }\n if ((params.additionalDirectories ?? []).length > 0) {\n throw RequestError.invalidParams(\n undefined,\n 'additionalDirectories are not accepted by this agent',\n );\n }\n const admission = options.admitWorkspace();\n if (!admission.admitted) {\n throw new RequestError(WORKSPACE_NOT_ADMITTED, admission.message, {\n reason: 'workspace-not-trusted',\n });\n }\n };\n\n /** Wait for the attached session to name itself; a session that failed to start is a fixed error. */\n const sessionId = async (context: string): Promise<string> => {\n try {\n // A session that builds itself in the background cannot name itself until it is ready.\n await session.whenInitialized?.();\n return session.getStatusSnapshot().sessionId;\n } catch (error) {\n report(asError(error), context);\n // The cause can name host paths or settings; the editor only needs to know it failed.\n throw new RequestError(INTERNAL_ERROR, 'The session could not be started');\n }\n };\n\n /**\n * Tell the editor which commands it may offer, when they differ from what it last received. The\n * update names the session, so it goes out only once the editor holds that id.\n */\n const advertiseCommands = (client: AgentContext, id: string): void => {\n if (session.listCommands === undefined && session.listSkills === undefined) return;\n let commands: ReturnType<typeof availableCommands>;\n try {\n commands = availableCommands(session);\n } catch (error) {\n report(asError(error), 'available_commands_update');\n return;\n }\n const serialized = JSON.stringify(commands);\n if (serialized === advertised) return;\n advertised = serialized;\n client\n .notify('session/update', {\n sessionId: id,\n update: { sessionUpdate: 'available_commands_update', availableCommands: commands },\n })\n .catch((error: unknown) => report(asError(error), 'available_commands_update'));\n };\n /** After the response that gives the editor the session id has been written. */\n const advertiseAfterReply = (client: AgentContext, id: string): void => {\n setImmediate(() => advertiseCommands(client, id));\n };\n\n /** The file system the editor offered in `initialize`. */\n let editorFs: ClientCapabilities['fs'];\n let editorFilesOffered = false;\n /** Hand the host the editor's file system for the bound session, if the editor offered one. */\n const offerEditorFiles = (client: AgentContext): void => {\n const access = editorFileAccess(editorFs, client, () => boundSessionId);\n if (access === undefined) return;\n editorFilesOffered = true;\n options.onEditorFileAccess?.(access);\n };\n\n const app = agent({ name: options.agentInfo.name })\n .onRequest('initialize', ({ params }) => {\n editorFs = params.clientCapabilities?.fs;\n return {\n // Always the version this agent speaks: the client disconnects if it cannot use it.\n protocolVersion: PROTOCOL_VERSION,\n agentCapabilities: {\n loadSession:\n options.loadSession !== undefined && session.getMessagesDisplay !== undefined,\n promptCapabilities: { image: false, audio: false, embeddedContext: true },\n },\n agentInfo: { name: options.agentInfo.name, version: options.agentInfo.version },\n };\n })\n .onRequest('session/new', async ({ params, client }) => {\n if (boundSessionId !== undefined || binding) {\n throw RequestError.invalidParams(undefined, 'one session per connection');\n }\n admit(params);\n binding = true;\n try {\n boundSessionId = await sessionId('session/new');\n } finally {\n binding = false;\n }\n offerEditorFiles(client);\n advertiseAfterReply(client, boundSessionId);\n return { sessionId: boundSessionId };\n })\n .onRequest('session/load', async ({ params, client }) => {\n const load = options.loadSession;\n if (load === undefined || session.getMessagesDisplay === undefined) {\n throw RequestError.methodNotFound('session/load');\n }\n if (binding) throw RequestError.invalidParams(undefined, 'a session is already being opened');\n // A load replaces this connection's session, so it may not pull the session out from under a turn.\n if (active !== undefined) {\n throw RequestError.invalidParams(undefined, 'a prompt is running');\n }\n admit(params);\n binding = true;\n try {\n let outcome: Awaited<ReturnType<typeof load>>;\n try {\n outcome = await load(params.sessionId);\n } catch (error) {\n report(asError(error), 'session/load');\n throw new RequestError(INTERNAL_ERROR, 'The session could not be loaded');\n }\n if (!outcome.loaded) throw RequestError.invalidParams(undefined, outcome.message);\n boundSessionId = await sessionId('session/load');\n offerEditorFiles(client);\n // The editor sees the conversation it reopened before the load answers.\n const updates = createUpdateStream(client, boundSessionId, report);\n try {\n replayHistory(session.getMessagesDisplay(), updates, nextToolCallId);\n } catch (error) {\n report(asError(error), 'session/load');\n // The session is loaded; only its history could not be shown, and the cause stays local.\n throw new RequestError(INTERNAL_ERROR, 'The session history could not be shown');\n }\n await updates.drained();\n } finally {\n binding = false;\n }\n advertised = undefined;\n advertiseAfterReply(client, boundSessionId);\n return {};\n })\n .onRequest('session/prompt', async ({ params, signal, client }): Promise<PromptResponse> => {\n if (params.sessionId !== boundSessionId || binding) {\n throw RequestError.invalidParams(undefined, 'unknown session');\n }\n if (active !== undefined) {\n throw RequestError.invalidParams(undefined, 'a prompt is already running');\n }\n const text = promptText(params.prompt);\n if (text.full.trim() === '') {\n throw RequestError.invalidParams(undefined, 'the prompt has no text');\n }\n const controller = new AbortController();\n let cancelled = false;\n active = {\n cancel() {\n cancelled = true;\n controller.abort();\n session.abort();\n },\n };\n const turn: IAcpTurnContext = {\n session,\n sessionId: params.sessionId,\n client,\n updates: createUpdateStream(client, params.sessionId, report),\n signal: AbortSignal.any([signal, controller.signal]),\n driverId,\n surface: options.surface ?? 'unknown',\n cwd: options.cwd,\n nextToolCallId,\n wasCancelled: () => cancelled,\n report,\n };\n try {\n return text.typed.trimStart().startsWith('/')\n ? await runCommandTurn(turn, text.typed, options.executeCommand ?? commandsUnavailable)\n : await runModelTurn(turn, text.full);\n } finally {\n active = undefined;\n // A command or a model turn can change the catalog (a plugin reload, a preset): the editor\n // gets the new list, and an unchanged one is not sent again.\n advertiseCommands(client, params.sessionId);\n }\n })\n .onNotification('session/cancel', ({ params }) => {\n if (params.sessionId === boundSessionId) active?.cancel();\n });\n\n return {\n app,\n cancelActive: () => active?.cancel(),\n withdrawEditorFiles: () => {\n if (!editorFilesOffered) return;\n editorFilesOffered = false;\n options.onEditorFileAccess?.(undefined);\n },\n };\n}\n","/**\n * ITransportAdapter that runs the attached session as an Agent Client Protocol (v1) agent, so an\n * editor that launches the process can create a session, prompt it, see streamed output and tool\n * calls, approve or reject permissions, and cancel a turn.\n *\n * The adapter owns the carrier's readiness and teardown; the product shell owns process policy\n * (stdout reservation, workspace trust, signals).\n */\n\nimport { Readable } from 'node:stream';\n\nimport { ndJsonStream } from '@agentclientprotocol/sdk';\nimport { attachStdioCarrier } from '@robota-sdk/agent-transport/node';\n\nimport { createAcpAgent } from './acp-agent.js';\n\nimport type { IAcpTransport, IAcpTransportOptions, IAcpTransportSession } from './acp-types.js';\nimport type { AgentConnection } from '@agentclientprotocol/sdk';\nimport type { Writable } from 'node:stream';\nimport type { ITransportLifecycleError } from '@robota-sdk/agent-interface-transport';\nimport type { IStdioCarrier } from '@robota-sdk/agent-transport/node';\n\n/**\n * A Web sink over the process output. `Writable.toWeb` would leave its own listeners on a stream\n * the transport does not own, and destroy it if the SDK ever aborts the sink; this adds none.\n */\nfunction outputSink(output: Writable): WritableStream<Uint8Array> {\n return new WritableStream<Uint8Array>({\n write: (chunk) =>\n new Promise<void>((resolve, reject) => {\n output.write(chunk, (error) => (error ? reject(error) : resolve()));\n }),\n });\n}\n\nexport function createAcpTransport(options: IAcpTransportOptions): IAcpTransport {\n let session: IAcpTransportSession | null = null;\n let running = false;\n let stopping: Promise<void> | null = null;\n let carrier: IStdioCarrier | null = null;\n let connection: AgentConnection | null = null;\n let cancelActive: (() => void) | null = null;\n let withdrawEditorFiles: (() => void) | null = null;\n let resolveClose: (() => void) | null = null;\n let rejectClose: ((error: Error) => void) | null = null;\n let closePromise: Promise<void> = Promise.resolve();\n const resetClose = (): void => {\n closePromise = new Promise<void>((resolve, reject) => {\n resolveClose = resolve;\n rejectClose = reject;\n });\n // allow-fallback: waitForClose retains the rejection for the process owner; this observer only prevents an early unhandled-rejection warning.\n void closePromise.catch(() => undefined);\n };\n const release = (): void => {\n try {\n // The host's tools go back to the disk before the connection that carried their access closes.\n withdrawEditorFiles?.();\n cancelActive?.();\n connection?.close();\n } finally {\n carrier?.release();\n carrier = null;\n connection = null;\n cancelActive = null;\n withdrawEditorFiles = null;\n }\n };\n const lifecycleError = (code: ITransportLifecycleError['code']): ITransportLifecycleError =>\n Object.assign(new Error(`ACP transport ${code}.`), {\n name: 'TransportLifecycleError' as const,\n code,\n transportName: 'acp',\n });\n\n return {\n name: 'acp',\n lifecycle: Object.freeze({ kind: 'service' }),\n attach(s: IAcpTransportSession) {\n if (running || stopping) throw lifecycleError('already-started');\n session = s;\n resetClose();\n },\n async start() {\n if (!session) throw lifecycleError('not-attached');\n if (running || stopping) throw lifecycleError('already-started');\n running = true;\n try {\n const acpAgent = createAcpAgent(session, options);\n cancelActive = acpAgent.cancelActive;\n withdrawEditorFiles = acpAgent.withdrawEditorFiles;\n // The SDK destroys what it reads when its connection closes; it reads the bridge, never the\n // process input, and a closed or failed end ends the transport instead of leaving it idle.\n const attached = attachStdioCarrier(options.input, options.output, {\n onInputClose: () => resolveClose?.(),\n onOutputClose: () => resolveClose?.(),\n onOutputError: (error) => rejectClose?.(error),\n });\n carrier = attached;\n const opened = acpAgent.app.connect(\n ndJsonStream(\n outputSink(options.output),\n Readable.toWeb(attached.bridge) as ReadableStream<Uint8Array>,\n ),\n );\n connection = opened;\n void opened.closed.then(() => resolveClose?.());\n } catch (error) {\n // A start that failed serves nothing, so a retry must not be told it is already running.\n release();\n running = false;\n throw error;\n }\n },\n stop() {\n if (stopping) return stopping;\n stopping = (async () => {\n try {\n release();\n } finally {\n session = null;\n running = false;\n resolveClose?.();\n }\n })().finally(() => {\n stopping = null;\n });\n return stopping;\n },\n waitForClose() {\n return closePromise;\n },\n };\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"index.js","names":["asError"],"sources":["../../src/acp-editor-files.ts","../../src/acp-prompt-text.ts","../../src/acp-tool-mapping.ts","../../src/acp-session-catalog.ts","../../src/acp-permission.ts","../../src/acp-session-events.ts","../../src/acp-tool-calls.ts","../../src/acp-turn.ts","../../src/acp-updates.ts","../../src/acp-agent.ts","../../src/acp-transport.ts"],"sourcesContent":["/**\n * The editor's file system, as the editor offered it in `initialize`: reads and writes the host's\n * file tools may inspect once the session is bound. ACP v1 carries only a pathname, not a stable\n * rooted file capability, so this adapter deliberately cannot claim the authority required to\n * carry contained Read, Write or Edit operations.\n */\n\nimport { isAbsolute } from 'node:path';\n\nimport { RequestError } from '@agentclientprotocol/sdk';\n\nimport type { IAcpEditorFileAccess } from './acp-types.js';\nimport type { AgentContext, ClientCapabilities, SendRequestOptions } from '@agentclientprotocol/sdk';\n\nfunction cancellable(signal: AbortSignal | undefined): SendRequestOptions | undefined {\n return signal === undefined ? undefined : { cancellationSignal: signal };\n}\n\n/** A JSON-RPC error names its kind in `message` and, from an SDK client, the cause in `data.details`. */\nfunction editorError(error: unknown): Error {\n if (!(error instanceof RequestError)) return error instanceof Error ? error : new Error(String(error));\n const data: unknown = error.data;\n const details =\n typeof data === 'object' && data !== null && 'details' in data && typeof data.details === 'string'\n ? data.details\n : undefined;\n return new Error(details === undefined ? error.message : `${error.message}: ${details}`);\n}\n\nfunction absolute(path: string): void {\n // ACP names files by absolute path; a relative one would be resolved by the editor, not the tool.\n if (!isAbsolute(path)) throw new Error(`the editor is given absolute paths only: ${path}`);\n}\n\n/**\n * The access the editor offered, for the bound session; `undefined` when it offered neither read nor\n * write. `sessionId` is read at each request, so the access follows the session the connection serves.\n */\nexport function editorFileAccess(\n fs: ClientCapabilities['fs'],\n client: AgentContext,\n sessionId: () => string | undefined,\n): IAcpEditorFileAccess | undefined {\n const reads = fs?.readTextFile === true;\n const writes = fs?.writeTextFile === true;\n if (!reads && !writes) return undefined;\n const bound = (): string => {\n const id = sessionId();\n if (id === undefined) throw new Error('the editor has no session to read or write for');\n return id;\n };\n return {\n ...(reads\n ? {\n readTextFile: async (path: string, signal?: AbortSignal): Promise<string> => {\n absolute(path);\n try {\n const reply = await client.request(\n 'fs/read_text_file',\n { sessionId: bound(), path },\n cancellable(signal),\n );\n return reply.content;\n } catch (error) {\n throw editorError(error);\n }\n },\n }\n : {}),\n ...(writes\n ? {\n writeTextFile: async (path: string, content: string, signal?: AbortSignal): Promise<void> => {\n absolute(path);\n try {\n await client.request(\n 'fs/write_text_file',\n { sessionId: bound(), path, content },\n cancellable(signal),\n );\n } catch (error) {\n throw editorError(error);\n }\n },\n }\n : {}),\n };\n}\n","/** A prompt's content blocks as the single text the session receives. */\n\nimport type { ContentBlock } from '@agentclientprotocol/sdk';\n\nexport interface IAcpPromptText {\n /** What the person typed: the text blocks alone. A `/command` is recognised on this. */\n readonly typed: string;\n /** What the session receives: the typed text followed by the context the editor attached. */\n readonly full: string;\n}\n\nconst MIN_FENCE = 3;\n\n/** A backtick fence longer than any run inside the text, so attached content cannot close it early. */\nfunction fence(text: string): string {\n const longest = Math.max(0, ...[...text.matchAll(/`+/g)].map((run) => run[0].length));\n return '`'.repeat(Math.max(MIN_FENCE, longest + 1));\n}\n\n/**\n * Text blocks are the typed message. An embedded text resource is appended as fenced context; a\n * resource link is listed by URI so the model can read it under the session's own permissions. Image\n * and audio blocks are not advertised as supported, so they are ignored.\n */\nexport function promptText(blocks: readonly ContentBlock[]): IAcpPromptText {\n let typed = '';\n const context: string[] = [];\n const links: string[] = [];\n for (const block of blocks) {\n if (block.type === 'text') typed += block.text;\n else if (block.type === 'resource' && 'text' in block.resource) {\n const marks = fence(block.resource.text);\n context.push(\n `Context from ${block.resource.uri}:\\n${marks}\\n${block.resource.text}\\n${marks}`,\n );\n } else if (block.type === 'resource_link') links.push(`- ${block.uri}`);\n }\n if (links.length > 0) context.push(`Referenced resources:\\n${links.join('\\n')}`);\n return { typed, full: context.length === 0 ? typed : `${typed}\\n\\n${context.join('\\n\\n')}` };\n}\n","/** How a session tool call is described to an editor. */\n\nimport type { ToolCallStatus, ToolKind } from '@agentclientprotocol/sdk';\nimport type { IToolState } from '@robota-sdk/agent-interface-session';\n\nconst TOOL_KINDS: Readonly<Record<string, ToolKind>> = {\n Read: 'read',\n Glob: 'search',\n Grep: 'search',\n Edit: 'edit',\n Write: 'edit',\n Shell: 'execute',\n Bash: 'execute',\n WebFetch: 'fetch',\n WebSearch: 'fetch',\n};\n\nconst MAX_TITLE_DETAIL = 120;\n\nexport function acpToolKind(toolName: string): ToolKind {\n return TOOL_KINDS[toolName] ?? 'other';\n}\n\n/** The tool's name plus the one argument the session summarises it by, shortened for a title line. */\nexport function acpToolTitle(toolName: string, detail: string | undefined): string {\n const text = detail?.replace(/\\s+/g, ' ').trim();\n if (!text) return toolName;\n const shown = text.length > MAX_TITLE_DETAIL ? `${text.slice(0, MAX_TITLE_DETAIL - 1)}…` : text;\n return `${toolName}: ${shown}`;\n}\n\n/**\n * The tool's name plus its argument as given. A person deciding whether a call may run sees all of\n * what it would do, so nothing is shortened and line breaks stay.\n */\nexport function acpFullToolTitle(toolName: string, detail: string | undefined): string {\n const text = detail?.trim();\n return text ? `${toolName}: ${text}` : toolName;\n}\n\nexport function acpToolStartTitle(tool: IToolState): string {\n return acpToolTitle(tool.toolName, tool.displayPath ?? tool.firstArg);\n}\n\n/** A call that ended in error or was denied is `failed`; only a success is `completed`. */\nexport function acpToolEndStatus(tool: IToolState): ToolCallStatus {\n return tool.result === 'success' ? 'completed' : 'failed';\n}\n","/** What an editor learns about the session besides a turn: the commands it can offer, and the history of a loaded session. */\n\nimport { acpToolEndStatus, acpToolKind, acpToolStartTitle } from './acp-tool-mapping.js';\n\nimport type { IAcpUpdateStream } from './acp-updates.js';\nimport type { AvailableCommand } from '@agentclientprotocol/sdk';\nimport type { IHistoryDisplaySegment, ISessionCommands } from '@robota-sdk/agent-interface-session';\n\n/**\n * The slash commands an editor may offer: the runtime's commands, then the user-invocable skills\n * whose names no command already uses. A command a client implements itself (its runner is the\n * client) is not offered, because an editor cannot run it and the runtime would not.\n */\nexport function availableCommands(\n session: Partial<Pick<ISessionCommands, 'listCommands' | 'listSkills'>>,\n): AvailableCommand[] {\n const offered: AvailableCommand[] = [];\n const names = new Set<string>();\n const offer = (name: string, description: string, hint: string | undefined): void => {\n if (names.has(name)) return;\n names.add(name);\n offered.push({ name, description, ...(hint ? { input: { hint } } : {}) });\n };\n for (const command of session.listCommands?.() ?? []) {\n if (command.runner !== 'runtime') continue;\n offer(command.name, command.description, command.argumentHint);\n }\n for (const skill of session.listSkills?.() ?? []) {\n if (skill.userInvocable) offer(skill.name, skill.description, skill.argumentHint);\n }\n return offered;\n}\n\n/**\n * A loaded session's history as the editor would have seen it live: what the person typed, what the\n * agent answered, and each finished tool call with how it ended. A call keeps its own id unless that\n * id already named a row of this replay (a provider can reuse ids across turns), because the editor\n * would merge the two rows into one. The connection's allocator also keeps later live calls distinct.\n */\nexport function replayHistory(\n segments: readonly IHistoryDisplaySegment[],\n updates: IAcpUpdateStream,\n nextToolCallId: (preferredId?: string) => string,\n): void {\n for (const segment of segments) {\n if (segment.type === 'text') {\n if (segment.content === '') continue;\n updates.send({\n sessionUpdate: segment.role === 'user' ? 'user_message_chunk' : 'agent_message_chunk',\n content: { type: 'text', text: segment.content },\n });\n continue;\n }\n const { tool } = segment;\n if (tool.internal) continue;\n const toolCallId = nextToolCallId(tool.executionId);\n updates.send({\n sessionUpdate: 'tool_call',\n toolCallId,\n title: acpToolStartTitle(tool),\n kind: acpToolKind(tool.toolName),\n status: acpToolEndStatus(tool),\n });\n }\n}\n","/** How a session permission prompt becomes an ACP permission request and its answer becomes a decision. */\n\nimport { resolve } from 'node:path';\n\nimport { permissionSummaryArgument } from '@robota-sdk/agent-transport';\n\nimport { acpFullToolTitle, acpToolKind } from './acp-tool-mapping.js';\n\nimport type {\n PermissionOption,\n RequestPermissionOutcome,\n ToolCallUpdate,\n} from '@agentclientprotocol/sdk';\nimport type {\n IDiffLine,\n IPermissionRequestEvent,\n TPermissionResultValue,\n} from '@robota-sdk/agent-interface-session';\n\nconst OPTION = {\n allowOnce: 'allow-once',\n allowSession: 'allow-session',\n allowProject: 'allow-project',\n reject: 'reject',\n} as const;\n\nexport function permissionOptions(canPersistProject: boolean): PermissionOption[] {\n return [\n { optionId: OPTION.allowOnce, name: 'Allow once', kind: 'allow_once' },\n { optionId: OPTION.allowSession, name: 'Allow for this session', kind: 'allow_always' },\n ...(canPersistProject\n ? [\n {\n optionId: OPTION.allowProject,\n name: 'Allow for this project',\n kind: 'allow_always' as const,\n },\n ]\n : []),\n { optionId: OPTION.reject, name: 'Reject', kind: 'reject_once' },\n ];\n}\n\nexport function permissionTitle(event: IPermissionRequestEvent): string {\n const title = acpFullToolTitle(\n event.toolName,\n event.diffFile ?? permissionSummaryArgument(event.toolArgs),\n );\n return event.requester ? `${event.requester.label}: ${title}` : title;\n}\n\n/** What the call was asked with, including a shell call's working directory when the session resolved one. */\nfunction permissionRawInput(event: IPermissionRequestEvent): unknown {\n if (event.cwd === undefined || 'cwd' in event.toolArgs) return event.toolArgs;\n return { ...event.toolArgs, cwd: event.cwd };\n}\n\nconst HUNK_HEADER = /^@@ /;\n\n/**\n * The file as it is before and after, rebuilt from the session's diff lines: context lines belong\n * to both sides. A diff with nothing on the old side is a new file, which ACP spells as no old text.\n * The session caps each side and says so in a hunk line placed after the lines it kept; that line\n * stays with its side so a cut-off file is not presented as whole. A hunk header carries no text.\n */\nfunction diffTexts(lines: readonly IDiffLine[]): { oldText?: string; newText: string } {\n const before: string[] = [];\n const after: string[] = [];\n let previous: IDiffLine['type'] = 'context';\n for (const line of lines) {\n if (line.type === 'hunk') {\n if (HUNK_HEADER.test(line.text)) continue;\n if (previous !== 'add') before.push(line.text);\n if (previous !== 'remove') after.push(line.text);\n continue;\n }\n previous = line.type;\n if (line.type !== 'add') before.push(line.text);\n if (line.type !== 'remove') after.push(line.text);\n }\n return {\n ...(before.length > 0 ? { oldText: before.join('\\n') } : {}),\n newText: after.join('\\n'),\n };\n}\n\n/**\n * The tool call a permission request describes. The editor gets what the terminal shows: the\n * arguments whole, and for an edit the diff and the file it concerns. `cwd` roots a path the\n * session reported relative to the workspace, because ACP paths are absolute.\n */\nexport function permissionToolCall(\n event: IPermissionRequestEvent,\n toolCallId: string,\n cwd: string,\n): ToolCallUpdate {\n const path = event.diffFile === undefined ? undefined : resolve(cwd, event.diffFile);\n return {\n toolCallId,\n title: permissionTitle(event),\n kind: acpToolKind(event.toolName),\n status: 'pending',\n rawInput: permissionRawInput(event),\n ...(path !== undefined && event.diffLines !== undefined\n ? { content: [{ type: 'diff' as const, path, ...diffTexts(event.diffLines) }] }\n : {}),\n ...(path !== undefined ? { locations: [{ path }] } : {}),\n };\n}\n\n/**\n * The editor's answer as a session decision. Anything the editor was not offered, or that is not\n * recognised, is a denial: an answer widens permission only by naming an option this request listed.\n */\nexport function permissionDecision(\n outcome: RequestPermissionOutcome,\n offered: readonly PermissionOption[],\n): TPermissionResultValue {\n if (outcome.outcome !== 'selected') return false;\n if (!offered.some((option) => option.optionId === outcome.optionId)) return false;\n switch (outcome.optionId) {\n case OPTION.allowOnce:\n return true;\n case OPTION.allowSession:\n return 'allow-session';\n case OPTION.allowProject:\n return 'allow-project';\n default:\n return false;\n }\n}\n","/**\n * Which session events this transport acts on.\n *\n * This is the ACP owner's own exhaustive policy over the shared event vocabulary, not the WebSocket\n * fan-out's: a new session event fails compilation here until someone decides what an editor sees.\n * The subscription is derived from it, so promoting an event to `stream` or `permission` without a\n * handler fails compilation too.\n */\n\nimport type {\n IInteractiveSessionEvents,\n ISessionEvents,\n TInteractiveEventName,\n} from '@robota-sdk/agent-interface-session';\n\n/**\n * - `stream`: forwarded to the editor as a session update while a prompt runs.\n * - `permission`: becomes a permission request to the editor.\n * - `settle`: ends the turn; the outcome is read from the submission's turn handle, which names its\n * own turn, rather than from the session-wide event, which cannot say which turn ended.\n * - `ignore`: nothing an ACP editor shows. `ask_request` is deliberately here: a session with no\n * listener answers an ask as cancelled, which fails closed because ACP v1 has no question prompt.\n */\nexport type TAcpEventPolicy = 'stream' | 'permission' | 'settle' | 'ignore';\n\nexport const ACP_SESSION_EVENT_POLICY = {\n text_delta: 'stream',\n tool_start: 'stream',\n tool_end: 'stream',\n thinking: 'ignore', // a busy flag with no text; the reasoning text is `thinking_delta`\n thinking_delta: 'stream',\n complete: 'settle',\n error: 'settle',\n interrupted: 'settle',\n permission_request: 'permission',\n ask_request: 'ignore',\n context_update: 'ignore',\n compact: 'ignore',\n skill_activation: 'ignore',\n background_task_event: 'ignore',\n background_job_group_event: 'ignore',\n execution_workspace_event: 'ignore',\n user_message: 'ignore',\n turn_source: 'ignore',\n context_file_refreshed: 'ignore',\n memory_event: 'ignore',\n goal_event: 'ignore',\n plan_event: 'ignore',\n branch_event: 'ignore',\n prompt_resolved: 'ignore',\n ui_intent: 'ignore',\n session_renamed: 'ignore',\n history_cleared: 'ignore',\n session_switched: 'ignore',\n status_changed: 'ignore',\n} as const satisfies Record<TInteractiveEventName, TAcpEventPolicy>;\n\ntype TPolicyMap = typeof ACP_SESSION_EVENT_POLICY;\ntype TEventsWith<P extends TAcpEventPolicy> = {\n [K in keyof TPolicyMap]: TPolicyMap[K] extends P ? K : never;\n}[keyof TPolicyMap];\n\nexport type TAcpSubscribedEvent = TEventsWith<'stream' | 'permission'>;\n\n/** Exactly one handler for every event the policy subscribes, and none for any other. */\nexport type TAcpEventHandlers = {\n readonly [K in TAcpSubscribedEvent]: IInteractiveSessionEvents[K];\n};\n\n/** Subscribe the handlers; returns the unsubscribe. */\nexport function subscribeAcpEvents(\n session: ISessionEvents,\n handlers: TAcpEventHandlers,\n): () => void {\n const names = Object.keys(handlers) as TAcpSubscribedEvent[];\n for (const name of names) session.on(name, handlers[name]);\n return () => {\n for (const name of names) session.off(name, handlers[name]);\n };\n}\n","/** The tool-call rows one turn shows an editor: which session call each row stands for, and when it is done. */\n\nimport { permissionToolCall } from './acp-permission.js';\nimport { acpToolEndStatus, acpToolKind, acpToolStartTitle } from './acp-tool-mapping.js';\n\nimport type { IAcpUpdateStream } from './acp-updates.js';\nimport type { ToolCallUpdate } from '@agentclientprotocol/sdk';\nimport type { IPermissionRequestEvent, IToolState } from '@robota-sdk/agent-interface-session';\n\nexport interface IAcpToolCallTracker {\n start(tool: IToolState): void;\n end(tool: IToolState): void;\n /** The row a permission request asks about; when the request names its call, the row is that call's. */\n requested(event: IPermissionRequestEvent): ToolCallUpdate;\n /** The editor answered; the session decides what happens next, and its tool events say so. */\n answered(event: IPermissionRequestEvent, allowed: boolean): void;\n /** The turn is over: close every row still waiting for a call that never came. */\n finish(): void;\n}\n\nexport interface IAcpToolCallDeps {\n readonly updates: IAcpUpdateStream;\n /** Allocate a new row id, preserving a preferred execution id only when no earlier row used it. */\n readonly nextToolCallId: (preferredId?: string) => string;\n /** The served directory, for paths the session reports relative to it. */\n readonly cwd: string;\n}\n\ninterface IOpenCall {\n readonly toolName: string;\n readonly executionId: string | undefined;\n readonly id: string;\n}\n\n/** A permission request's row, held until the call it names starts or is refused. */\ninterface IAwaitedCall {\n readonly id: string;\n readonly rawInput: unknown;\n /** A background agent's call can run after this prompt, so its row closes with the answer. */\n readonly background: boolean;\n allowed?: boolean;\n}\n\nexport function createToolCallTracker(deps: IAcpToolCallDeps): IAcpToolCallTracker {\n const { updates } = deps;\n const open: IOpenCall[] = [];\n /**\n * The session asks permission before it reports a call, and a refused call is reported ending\n * without ever having started. A request names its call by the execution id the call's own\n * events carry, so the request's row is keyed by it and those events address the same row.\n */\n const awaited = new Map<string, IAwaitedCall>();\n /** Rows of requests that name no call, by request id: nothing else will close them. */\n const unnamed = new Map<string, string>();\n let finished = false;\n\n const take = (executionId: string | undefined): IAwaitedCall | undefined => {\n if (executionId === undefined) return undefined;\n const call = awaited.get(executionId);\n awaited.delete(executionId);\n return call;\n };\n const close = (toolCallId: string, allowed: boolean): void =>\n updates.send({\n sessionUpdate: 'tool_call_update',\n toolCallId,\n status: allowed ? 'completed' : 'failed',\n });\n\n return {\n start(tool) {\n // An internal signal tool is not a call the person made or should see.\n if (tool.internal) return;\n const asked = take(tool.executionId);\n const id = asked?.id ?? deps.nextToolCallId(tool.executionId);\n open.push({ id, toolName: tool.toolName, executionId: tool.executionId });\n const call = {\n toolCallId: id,\n title: acpToolStartTitle(tool),\n kind: acpToolKind(tool.toolName),\n status: 'in_progress',\n } as const;\n if (asked === undefined) updates.send({ sessionUpdate: 'tool_call', ...call });\n else {\n updates.send({ sessionUpdate: 'tool_call_update', ...call, rawInput: asked.rawInput });\n }\n },\n\n end(tool) {\n if (tool.internal) return;\n const status = acpToolEndStatus(tool);\n const index = open.findIndex((call) =>\n tool.executionId !== undefined\n ? call.executionId === tool.executionId\n : call.executionId === undefined && call.toolName === tool.toolName,\n );\n if (index >= 0) {\n const [call] = open.splice(index, 1);\n updates.send({ sessionUpdate: 'tool_call_update', toolCallId: call!.id, status });\n return;\n }\n // A refused call ends with no start; when its permission was asked, that row is the call.\n const asked = take(tool.executionId);\n if (asked !== undefined) {\n updates.send({ sessionUpdate: 'tool_call_update', toolCallId: asked.id, status });\n return;\n }\n // Otherwise give the editor the call, finished, rather than an update for nothing.\n updates.send({\n sessionUpdate: 'tool_call',\n toolCallId: deps.nextToolCallId(tool.executionId),\n title: acpToolStartTitle(tool),\n kind: acpToolKind(tool.toolName),\n status,\n });\n },\n\n requested(event) {\n const { executionId } = event;\n if (executionId === undefined) {\n const call = permissionToolCall(\n event,\n deps.nextToolCallId(`permission-${event.id}`),\n deps.cwd,\n );\n unnamed.set(event.id, call.toolCallId);\n return call;\n }\n // A repeated request and the call's own start/end keep the row allocated for this call.\n const running = open.find((call) => call.executionId === executionId);\n const waiting = awaited.get(executionId);\n const id = running?.id ?? waiting?.id ?? deps.nextToolCallId(executionId);\n const call = permissionToolCall(event, id, deps.cwd);\n if (running === undefined && waiting === undefined) {\n awaited.set(executionId, {\n id,\n rawInput: call.rawInput,\n background: event.requester !== undefined,\n });\n }\n return call;\n },\n\n answered(event, allowed) {\n if (event.executionId !== undefined) {\n // The call's own events say what the answer led to; the answer is kept for a call that\n // may run after this turn.\n const asked = awaited.get(event.executionId);\n if (asked !== undefined) asked.allowed = allowed;\n return;\n }\n const row = unnamed.get(event.id);\n if (row === undefined || finished) return;\n unnamed.delete(event.id);\n close(row, allowed);\n },\n\n finish() {\n finished = true;\n for (const call of awaited.values()) close(call.id, call.background && call.allowed === true);\n for (const row of unnamed.values()) close(row, false);\n awaited.clear();\n unnamed.clear();\n },\n };\n}\n","/**\n * One `session/prompt` that runs a turn: stream the session's events to the editor, ask it about\n * permissions, and settle with a stop reason. A typed message and a `/command` that runs a model\n * turn (a skill) both go through here, so the editor sees the same thing from either.\n */\n\nimport { RequestError } from '@agentclientprotocol/sdk';\nimport { isTurnNotRunError } from '@robota-sdk/agent-interface-session';\n\nimport { permissionDecision, permissionOptions } from './acp-permission.js';\nimport { subscribeAcpEvents } from './acp-session-events.js';\nimport { createToolCallTracker } from './acp-tool-calls.js';\n\nimport type { IAcpTransportSession } from './acp-types.js';\nimport type { IAcpUpdateStream } from './acp-updates.js';\nimport type { AgentContext, PromptResponse } from '@agentclientprotocol/sdk';\nimport type {\n IPermissionRequestEvent,\n ISubmitOptions,\n TDriverId,\n} from '@robota-sdk/agent-interface-session';\n\nexport interface IAcpTurnContext {\n readonly session: IAcpTransportSession;\n readonly sessionId: string;\n readonly client: AgentContext;\n readonly updates: IAcpUpdateStream;\n /** Aborts when the editor cancels the request or the connection closes. */\n readonly signal: AbortSignal;\n readonly driverId: TDriverId;\n readonly surface: ISubmitOptions['surface'];\n /** The served directory; paths the session reports relative to it are made absolute for the editor. */\n readonly cwd: string;\n /** Allocate a new row id, preserving a preferred execution id only when no earlier row used it. */\n readonly nextToolCallId: (preferredId?: string) => string;\n /** Whether the editor sent `session/cancel` for this prompt. */\n readonly wasCancelled: () => boolean;\n readonly report: (error: Error, context: string) => void;\n}\n\nconst CANCELLED: PromptResponse = { stopReason: 'cancelled' };\nconst END_TURN: PromptResponse = { stopReason: 'end_turn' };\n\nfunction asError(error: unknown): Error {\n return error instanceof Error ? error : new Error(String(error));\n}\n\n/** What a turn's work left behind: whether it was cut short, and the text to show if nothing streamed. */\ninterface IWorkOutcome {\n readonly interrupted: boolean;\n readonly reply: string;\n}\n\ninterface IStreamedWork {\n run(): Promise<IWorkOutcome>;\n /** A failure that was not a cancellation, as the editor's answer: it reports the cause and throws a fixed error. */\n fail(error: unknown): never;\n /** Whether `error` is the work saying it was cancelled. */\n isCancellation?(error: unknown): boolean;\n}\n\nasync function runStreamedTurn(\n turn: IAcpTurnContext,\n work: IStreamedWork,\n): Promise<PromptResponse> {\n const { session, sessionId, client, updates } = turn;\n const tools = createToolCallTracker({\n updates,\n nextToolCallId: turn.nextToolCallId,\n cwd: turn.cwd,\n });\n let streamed = false;\n\n const onPermissionRequest = (event: IPermissionRequestEvent): void => {\n const options = permissionOptions(event.canPersistProjectPermission === true);\n const toolCall = tools.requested(event);\n void (async () => {\n let decision: ReturnType<typeof permissionDecision> = false;\n try {\n const response = await client.request('session/request_permission', {\n sessionId,\n toolCall,\n options,\n });\n decision = permissionDecision(response.outcome, options);\n } catch (error) {\n // A closed connection or a refused request means nobody approved: deny.\n turn.report(asError(error), 'session/request_permission');\n }\n tools.answered(event, decision !== false);\n try {\n session.resolvePermission(event.id, decision, turn.driverId);\n } catch (error) {\n turn.report(asError(error), 'resolvePermission');\n }\n })();\n };\n\n const unsubscribe = subscribeAcpEvents(session, {\n text_delta: (delta) => {\n if (delta === '') return;\n streamed = true;\n updates.text(delta);\n },\n // Reasoning is not the reply: it never marks the turn streamed, so a turn that showed only\n // thoughts still gets its result text.\n thinking_delta: (delta) => {\n if (delta === '') return;\n updates.thought(delta);\n },\n tool_start: tools.start,\n tool_end: tools.end,\n permission_request: onPermissionRequest,\n });\n\n try {\n const outcome = await work.run();\n if (turn.wasCancelled() || turn.signal.aborted || outcome.interrupted) return CANCELLED;\n // Nothing streamed (a provider without deltas, or a command that is not a turn): the text arrives whole.\n if (!streamed && outcome.reply !== '') updates.text(outcome.reply);\n return END_TURN;\n } catch (error) {\n if (turn.wasCancelled() || turn.signal.aborted || work.isCancellation?.(error) === true) {\n return CANCELLED;\n }\n return work.fail(error);\n } finally {\n unsubscribe();\n tools.finish();\n await updates.drained();\n }\n}\n\n/** A typed message: submit it to the session and take the turn's result. */\nexport function runModelTurn(turn: IAcpTurnContext, text: string): Promise<PromptResponse> {\n return runStreamedTurn(turn, {\n async run() {\n const handle = await turn.session.submit(text, undefined, undefined, {\n signal: turn.signal,\n driverId: turn.driverId,\n ...(turn.surface !== undefined ? { surface: turn.surface } : {}),\n });\n const result = await handle.completed;\n return { interrupted: result.interrupted === true, reply: result.response };\n },\n isCancellation: (error) => isTurnNotRunError(error) && error.reason === 'cancelled',\n fail(error) {\n turn.report(asError(error), 'turn');\n // The provider's own text can carry request details; the editor gets a fixed message.\n throw new RequestError(-32603, isTurnNotRunError(error) ? 'Turn was not run' : 'Turn failed');\n },\n });\n}\n\n/**\n * A `/command` prompt: the host runs it and its output is the reply. A command that runs a model\n * turn (a skill) streams that turn and asks about permissions while it runs; its output then\n * repeats the streamed reply, so it is shown only when nothing streamed (`/help`, a message).\n */\nexport function runCommandTurn(\n turn: IAcpTurnContext,\n text: string,\n executeCommand: (text: string, signal: AbortSignal) => Promise<string>,\n): Promise<PromptResponse> {\n return runStreamedTurn(turn, {\n async run() {\n return { interrupted: false, reply: await executeCommand(text, turn.signal) };\n },\n fail(error) {\n turn.report(asError(error), 'command');\n throw new RequestError(-32603, 'Command failed');\n },\n });\n}\n","/** Ordered `session/update` delivery for one prompt. */\n\nimport type { AgentContext, SessionUpdate } from '@agentclientprotocol/sdk';\n\nexport interface IAcpUpdateStream {\n send(update: SessionUpdate): void;\n /** Queue message text for the editor. */\n text(text: string): void;\n /** Queue reasoning text for the editor, shown apart from the reply. */\n thought(text: string): void;\n /** Settles once everything queued has been written or abandoned. Never rejects. */\n drained(): Promise<void>;\n}\n\n/**\n * Updates are written in the order they were queued, and a prompt's reply waits on `drained()`, so\n * the editor has every update before it sees `stopReason`. A failed write is reported once and the\n * rest are dropped: the editor is gone, and the session turn is not the carrier's to fail.\n */\nexport function createUpdateStream(\n client: AgentContext,\n sessionId: string,\n report: (error: Error, context: string) => void,\n): IAcpUpdateStream {\n let tail: Promise<void> = Promise.resolve();\n let failed = false;\n const send = (update: SessionUpdate): void => {\n tail = tail.then(async () => {\n if (failed) return;\n try {\n await client.notify('session/update', { sessionId, update });\n } catch (error) {\n failed = true;\n report(error instanceof Error ? error : new Error(String(error)), 'session/update');\n }\n });\n };\n return {\n send,\n text: (text) => send({ sessionUpdate: 'agent_message_chunk', content: { type: 'text', text } }),\n thought: (text) =>\n send({ sessionUpdate: 'agent_thought_chunk', content: { type: 'text', text } }),\n drained: () => tail,\n };\n}\n","/** The ACP agent handlers for one connection: one attached session, one prompt at a time. */\n\nimport { realpathSync } from 'node:fs';\nimport { isAbsolute, resolve } from 'node:path';\n\nimport { agent, PROTOCOL_VERSION, RequestError } from '@agentclientprotocol/sdk';\nimport { OWNER_DRIVER_ID } from '@robota-sdk/agent-interface-session';\n\nimport { editorFileAccess } from './acp-editor-files.js';\nimport { promptText } from './acp-prompt-text.js';\nimport { availableCommands, replayHistory } from './acp-session-catalog.js';\nimport { runCommandTurn, runModelTurn } from './acp-turn.js';\nimport { createUpdateStream } from './acp-updates.js';\n\nimport type { IAcpTurnContext } from './acp-turn.js';\nimport type { IAcpTransportOptions, IAcpTransportSession } from './acp-types.js';\nimport type {\n AgentApp,\n AgentContext,\n ClientCapabilities,\n McpServer,\n PromptResponse,\n} from '@agentclientprotocol/sdk';\n\n/**\n * JSON-RPC server-error code for a workspace the host has not admitted. Not -32000: ACP assigns\n * that to \"authentication required\", which an editor answers with a sign-in state this agent\n * does not have.\n */\nconst WORKSPACE_NOT_ADMITTED = -32001;\nconst INTERNAL_ERROR = -32603;\nconst COMMANDS_UNAVAILABLE = 'Slash commands are not available in this host.';\n\nconst commandsUnavailable = (): Promise<string> => Promise.resolve(COMMANDS_UNAVAILABLE);\n\nexport interface IAcpAgent {\n readonly app: AgentApp;\n /** Cancel the running prompt, if there is one. */\n cancelActive(): void;\n /** Tell the host the editor's file system is gone, if it was handed one. */\n withdrawEditorFiles(): void;\n}\n\n/** What `session/new` and `session/load` both carry that decides whether the editor may have the session. */\ninterface ISessionRequest {\n readonly cwd: string;\n readonly mcpServers: readonly McpServer[];\n readonly additionalDirectories?: readonly string[] | null;\n}\n\nfunction sameDirectory(requested: string, served: string): boolean {\n if (resolve(requested) === resolve(served)) return true;\n try {\n return realpathSync(requested) === realpathSync(served);\n } catch {\n // allow-fallback: a path that cannot be resolved is not the served directory, and the caller refuses it.\n return false;\n }\n}\n\nfunction asError(error: unknown): Error {\n return error instanceof Error ? error : new Error(String(error));\n}\n\nexport function createAcpAgent(\n session: IAcpTransportSession,\n options: IAcpTransportOptions,\n): IAcpAgent {\n const driverId = options.driverId ?? OWNER_DRIVER_ID;\n const report = (error: Error, context: string): void => options.onDeliveryError?.(error, context);\n let boundSessionId: string | undefined;\n /** A `session/new` or `session/load` is still settling which session this connection serves. */\n let binding = false;\n let active: { cancel(): void } | undefined;\n let toolCalls = 0;\n const usedToolCallIds = new Set<string>();\n const nextToolCallId = (preferredId?: string): string => {\n let id = preferredId;\n while (id === undefined || usedToolCallIds.has(id)) id = `tool-${(toolCalls += 1)}`;\n usedToolCallIds.add(id);\n return id;\n };\n /** The command list the editor last received, so an unchanged list is not sent again. */\n let advertised: string | undefined;\n\n /** Refuse what the editor may not ask for; the same rules hold for a new and a loaded session. */\n const admit = (params: ISessionRequest): void => {\n if (!isAbsolute(params.cwd) || !sameDirectory(params.cwd, options.cwd)) {\n throw RequestError.invalidParams(\n undefined,\n `cwd must be the directory this agent was started in: ${options.cwd}`,\n );\n }\n // Servers or roots named by the editor would widen what the session can reach.\n if (params.mcpServers.length > 0) {\n throw RequestError.invalidParams(undefined, 'mcpServers are not accepted by this agent');\n }\n if ((params.additionalDirectories ?? []).length > 0) {\n throw RequestError.invalidParams(\n undefined,\n 'additionalDirectories are not accepted by this agent',\n );\n }\n const admission = options.admitWorkspace();\n if (!admission.admitted) {\n throw new RequestError(WORKSPACE_NOT_ADMITTED, admission.message, {\n reason: 'workspace-not-trusted',\n });\n }\n };\n\n /** Wait for the attached session to name itself; a session that failed to start is a fixed error. */\n const sessionId = async (context: string): Promise<string> => {\n try {\n // A session that builds itself in the background cannot name itself until it is ready.\n await session.whenInitialized?.();\n return session.getStatusSnapshot().sessionId;\n } catch (error) {\n report(asError(error), context);\n // The cause can name host paths or settings; the editor only needs to know it failed.\n throw new RequestError(INTERNAL_ERROR, 'The session could not be started');\n }\n };\n\n /**\n * Tell the editor which commands it may offer, when they differ from what it last received. The\n * update names the session, so it goes out only once the editor holds that id.\n */\n const advertiseCommands = (client: AgentContext, id: string): void => {\n if (session.listCommands === undefined && session.listSkills === undefined) return;\n let commands: ReturnType<typeof availableCommands>;\n try {\n commands = availableCommands(session);\n } catch (error) {\n report(asError(error), 'available_commands_update');\n return;\n }\n const serialized = JSON.stringify(commands);\n if (serialized === advertised) return;\n advertised = serialized;\n client\n .notify('session/update', {\n sessionId: id,\n update: { sessionUpdate: 'available_commands_update', availableCommands: commands },\n })\n .catch((error: unknown) => report(asError(error), 'available_commands_update'));\n };\n /** After the response that gives the editor the session id has been written. */\n const advertiseAfterReply = (client: AgentContext, id: string): void => {\n setImmediate(() => advertiseCommands(client, id));\n };\n\n /** The file system the editor offered in `initialize`. */\n let editorFs: ClientCapabilities['fs'];\n let editorFilesOffered = false;\n /** Hand the host the editor's file system for the bound session, if the editor offered one. */\n const offerEditorFiles = (client: AgentContext): void => {\n const access = editorFileAccess(editorFs, client, () => boundSessionId);\n if (access === undefined) return;\n editorFilesOffered = true;\n options.onEditorFileAccess?.(access);\n };\n\n const app = agent({ name: options.agentInfo.name })\n .onRequest('initialize', ({ params }) => {\n editorFs = params.clientCapabilities?.fs;\n return {\n // Always the version this agent speaks: the client disconnects if it cannot use it.\n protocolVersion: PROTOCOL_VERSION,\n agentCapabilities: {\n loadSession:\n options.loadSession !== undefined && session.getMessagesDisplay !== undefined,\n promptCapabilities: { image: false, audio: false, embeddedContext: true },\n },\n agentInfo: { name: options.agentInfo.name, version: options.agentInfo.version },\n };\n })\n .onRequest('session/new', async ({ params, client }) => {\n if (boundSessionId !== undefined || binding) {\n throw RequestError.invalidParams(undefined, 'one session per connection');\n }\n admit(params);\n binding = true;\n try {\n boundSessionId = await sessionId('session/new');\n } finally {\n binding = false;\n }\n offerEditorFiles(client);\n advertiseAfterReply(client, boundSessionId);\n return { sessionId: boundSessionId };\n })\n .onRequest('session/load', async ({ params, client }) => {\n const load = options.loadSession;\n if (load === undefined || session.getMessagesDisplay === undefined) {\n throw RequestError.methodNotFound('session/load');\n }\n if (binding) throw RequestError.invalidParams(undefined, 'a session is already being opened');\n // A load replaces this connection's session, so it may not pull the session out from under a turn.\n if (active !== undefined) {\n throw RequestError.invalidParams(undefined, 'a prompt is running');\n }\n admit(params);\n binding = true;\n try {\n let outcome: Awaited<ReturnType<typeof load>>;\n try {\n outcome = await load(params.sessionId);\n } catch (error) {\n report(asError(error), 'session/load');\n throw new RequestError(INTERNAL_ERROR, 'The session could not be loaded');\n }\n if (!outcome.loaded) throw RequestError.invalidParams(undefined, outcome.message);\n boundSessionId = await sessionId('session/load');\n offerEditorFiles(client);\n // The editor sees the conversation it reopened before the load answers.\n const updates = createUpdateStream(client, boundSessionId, report);\n try {\n replayHistory(session.getMessagesDisplay(), updates, nextToolCallId);\n } catch (error) {\n report(asError(error), 'session/load');\n // The session is loaded; only its history could not be shown, and the cause stays local.\n throw new RequestError(INTERNAL_ERROR, 'The session history could not be shown');\n }\n await updates.drained();\n } finally {\n binding = false;\n }\n advertised = undefined;\n advertiseAfterReply(client, boundSessionId);\n return {};\n })\n .onRequest('session/prompt', async ({ params, signal, client }): Promise<PromptResponse> => {\n if (params.sessionId !== boundSessionId || binding) {\n throw RequestError.invalidParams(undefined, 'unknown session');\n }\n if (active !== undefined) {\n throw RequestError.invalidParams(undefined, 'a prompt is already running');\n }\n const text = promptText(params.prompt);\n if (text.full.trim() === '') {\n throw RequestError.invalidParams(undefined, 'the prompt has no text');\n }\n const controller = new AbortController();\n let cancelled = false;\n active = {\n cancel() {\n cancelled = true;\n controller.abort();\n session.abort();\n },\n };\n const turn: IAcpTurnContext = {\n session,\n sessionId: params.sessionId,\n client,\n updates: createUpdateStream(client, params.sessionId, report),\n signal: AbortSignal.any([signal, controller.signal]),\n driverId,\n surface: options.surface ?? 'unknown',\n cwd: options.cwd,\n nextToolCallId,\n wasCancelled: () => cancelled,\n report,\n };\n try {\n return text.typed.trimStart().startsWith('/')\n ? await runCommandTurn(turn, text.typed, options.executeCommand ?? commandsUnavailable)\n : await runModelTurn(turn, text.full);\n } finally {\n active = undefined;\n // A command or a model turn can change the catalog (a plugin reload, a preset): the editor\n // gets the new list, and an unchanged one is not sent again.\n advertiseCommands(client, params.sessionId);\n }\n })\n .onNotification('session/cancel', ({ params }) => {\n if (params.sessionId === boundSessionId) active?.cancel();\n });\n\n return {\n app,\n cancelActive: () => active?.cancel(),\n withdrawEditorFiles: () => {\n if (!editorFilesOffered) return;\n editorFilesOffered = false;\n options.onEditorFileAccess?.(undefined);\n },\n };\n}\n","/**\n * ITransportAdapter that runs the attached session as an Agent Client Protocol (v1) agent, so an\n * editor that launches the process can create a session, prompt it, see streamed output and tool\n * calls, approve or reject permissions, and cancel a turn.\n *\n * The adapter owns the carrier's readiness and teardown; the product shell owns process policy\n * (stdout reservation, workspace trust, signals).\n */\n\nimport { Readable } from 'node:stream';\n\nimport { ndJsonStream } from '@agentclientprotocol/sdk';\nimport { attachStdioCarrier } from '@robota-sdk/agent-transport/node';\n\nimport { createAcpAgent } from './acp-agent.js';\n\nimport type { IAcpTransport, IAcpTransportOptions, IAcpTransportSession } from './acp-types.js';\nimport type { AgentConnection } from '@agentclientprotocol/sdk';\nimport type { Writable } from 'node:stream';\nimport type { ITransportLifecycleError } from '@robota-sdk/agent-interface-transport';\nimport type { IStdioCarrier } from '@robota-sdk/agent-transport/node';\n\n/**\n * A Web sink over the process output. `Writable.toWeb` would leave its own listeners on a stream\n * the transport does not own, and destroy it if the SDK ever aborts the sink; this adds none.\n */\nfunction outputSink(output: Writable): WritableStream<Uint8Array> {\n return new WritableStream<Uint8Array>({\n write: (chunk) =>\n new Promise<void>((resolve, reject) => {\n output.write(chunk, (error) => (error ? reject(error) : resolve()));\n }),\n });\n}\n\nexport function createAcpTransport(options: IAcpTransportOptions): IAcpTransport {\n let session: IAcpTransportSession | null = null;\n let running = false;\n let stopping: Promise<void> | null = null;\n let carrier: IStdioCarrier | null = null;\n let connection: AgentConnection | null = null;\n let cancelActive: (() => void) | null = null;\n let withdrawEditorFiles: (() => void) | null = null;\n let resolveClose: (() => void) | null = null;\n let rejectClose: ((error: Error) => void) | null = null;\n let closePromise: Promise<void> = Promise.resolve();\n const resetClose = (): void => {\n closePromise = new Promise<void>((resolve, reject) => {\n resolveClose = resolve;\n rejectClose = reject;\n });\n // allow-fallback: waitForClose retains the rejection for the process owner; this observer only prevents an early unhandled-rejection warning.\n void closePromise.catch(() => undefined);\n };\n const release = (): void => {\n try {\n // The host's tools go back to the disk before the connection that carried their access closes.\n withdrawEditorFiles?.();\n cancelActive?.();\n connection?.close();\n } finally {\n carrier?.release();\n carrier = null;\n connection = null;\n cancelActive = null;\n withdrawEditorFiles = null;\n }\n };\n const lifecycleError = (code: ITransportLifecycleError['code']): ITransportLifecycleError =>\n Object.assign(new Error(`ACP transport ${code}.`), {\n name: 'TransportLifecycleError' as const,\n code,\n transportName: 'acp',\n });\n\n return {\n name: 'acp',\n lifecycle: Object.freeze({ kind: 'service' }),\n attach(s: IAcpTransportSession) {\n if (running || stopping) throw lifecycleError('already-started');\n session = s;\n resetClose();\n },\n async start() {\n if (!session) throw lifecycleError('not-attached');\n if (running || stopping) throw lifecycleError('already-started');\n running = true;\n try {\n const acpAgent = createAcpAgent(session, options);\n cancelActive = acpAgent.cancelActive;\n withdrawEditorFiles = acpAgent.withdrawEditorFiles;\n // The SDK destroys what it reads when its connection closes; it reads the bridge, never the\n // process input, and a closed or failed end ends the transport instead of leaving it idle.\n const attached = attachStdioCarrier(options.input, options.output, {\n onInputClose: () => resolveClose?.(),\n onOutputClose: () => resolveClose?.(),\n onOutputError: (error) => rejectClose?.(error),\n });\n carrier = attached;\n const opened = acpAgent.app.connect(\n ndJsonStream(\n outputSink(options.output),\n Readable.toWeb(attached.bridge) as ReadableStream<Uint8Array>,\n ),\n );\n connection = opened;\n void opened.closed.then(() => resolveClose?.());\n } catch (error) {\n // A start that failed serves nothing, so a retry must not be told it is already running.\n release();\n running = false;\n throw error;\n }\n },\n stop() {\n if (stopping) return stopping;\n stopping = (async () => {\n try {\n release();\n } finally {\n session = null;\n running = false;\n resolveClose?.();\n }\n })().finally(() => {\n stopping = null;\n });\n return stopping;\n },\n waitForClose() {\n return closePromise;\n },\n };\n}\n"],"mappings":"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"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@robota-sdk/agent-transport-acp",
3
- "version": "3.0.0-beta.99",
3
+ "version": "3.0.2",
4
4
  "description": "Agent Client Protocol (ACP) agent transport for the Robota SDK",
5
5
  "type": "module",
6
6
  "main": "dist/node/index.js",
@@ -41,11 +41,11 @@
41
41
  ],
42
42
  "dependencies": {
43
43
  "@agentclientprotocol/sdk": "1.9.0",
44
- "zod": "^3.25.76",
45
- "@robota-sdk/agent-core": "3.0.0-beta.99",
46
- "@robota-sdk/agent-interface-session": "3.0.0-beta.99",
47
- "@robota-sdk/agent-interface-transport": "3.0.0-beta.99",
48
- "@robota-sdk/agent-transport": "3.0.0-beta.99"
44
+ "zod": "^4.6.7",
45
+ "@robota-sdk/agent-core": "3.0.2",
46
+ "@robota-sdk/agent-interface-session": "3.0.2",
47
+ "@robota-sdk/agent-interface-transport": "3.0.2",
48
+ "@robota-sdk/agent-transport": "3.0.2"
49
49
  },
50
50
  "devDependencies": {
51
51
  "@types/node": "^20.19.43",
@@ -1,7 +1,8 @@
1
1
  /**
2
2
  * The editor's file system, as the editor offered it in `initialize`: reads and writes the host's
3
- * file tools may hand to it once the session is bound. The tools decide what they touch; this only
4
- * carries the access, so it never widens what a tool may reach.
3
+ * file tools may inspect once the session is bound. ACP v1 carries only a pathname, not a stable
4
+ * rooted file capability, so this adapter deliberately cannot claim the authority required to
5
+ * carry contained Read, Write or Edit operations.
5
6
  */
6
7
 
7
8
  import { isAbsolute } from 'node:path';
package/src/acp-types.ts CHANGED
@@ -37,8 +37,9 @@ export interface IAcpTransportSession
37
37
  }
38
38
 
39
39
  /**
40
- * The editor's file system, as the editor offered it (`clientCapabilities.fs`): a method it did not
41
- * offer is absent. Paths are absolute. A failure rejects with the editor's reason.
40
+ * The editor's pathname file system, as the editor offered it (`clientCapabilities.fs`): a method
41
+ * it did not offer is absent. Paths are absolute. ACP v1 does not bind the path to stable rooted
42
+ * authority, so contained host file tools must not treat this as `IEditorFileAccess.pathAuthority`.
42
43
  */
43
44
  export interface IAcpEditorFileAccess {
44
45
  readTextFile?(path: string, signal?: AbortSignal): Promise<string>;