@robota-sdk/agent-transport-acp 3.0.0-beta.96 → 3.0.1

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,367 @@
1
1
  # @robota-sdk/agent-transport-acp
2
2
 
3
+ ## 3.0.1
4
+ ### Patch Changes
5
+
6
+ - @robota-sdk/agent-core@3.0.1
7
+ - @robota-sdk/agent-interface-session@3.0.1
8
+ - @robota-sdk/agent-interface-transport@3.0.1
9
+ - @robota-sdk/agent-transport@3.0.1
10
+
11
+ ## 3.0.0
12
+ ### Minor Changes
13
+
14
+ - 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.
15
+
16
+ New package `@robota-sdk/agent-transport-acp` provides `createAcpTransport`, the ACP `ITransportAdapter` the command uses.
17
+
18
+ `@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.
19
+ - e5a4254: **An editor that offers its file system carries the file tools' reads and writes.**
20
+
21
+ `Read`, `Write` and `Edit` take an optional `editorFiles` port (`IEditorFileAccess`), asked at each
22
+ call. On the host, after the tool's containment check (the permission gate has already decided),
23
+ `Read` returns the editor's text, unsaved changes included, and `Write` hands the content to the
24
+ editor; `Edit` reads where `Read` reads and writes where `Write` writes. The editor's text keeps
25
+ `Read`'s byte budget, binary refusal, formatting and cancellation, and an editor failure becomes the
26
+ tool's error result. `Glob` and `Grep` still search the disk and end their result with a note saying
27
+ so while an editor is attached. A sandbox with its own filesystem still carries every file tool, and
28
+ commands never use the editor. `createDefaultTools` and `createCodingPack` pass `editorFiles` through
29
+ to those tools only.
30
+
31
+ The ACP agent transport records the editor's `clientCapabilities.fs` and, once `session/new` binds the
32
+ session, hands the host an access through the new `onEditorFileAccess` option that sends
33
+ `fs/read_text_file` and `fs/write_text_file` for that session, for what the editor offered; stopping
34
+ the transport withdraws it. `robota acp` connects the two, so Read, Write and Edit go through the
35
+ editor in an editor that offers `fs`; `terminal/*` is not used.
36
+ - 04719a9: **A permission request names the tool call it is about.**
37
+
38
+ `IPermissionRequestEvent` gains an optional `executionId`: the same id the call's `tool_start` and
39
+ `tool_end` carry, so a surface can hold one row from the request to the call's end. The session's
40
+ permission path carries the call's id from the tool wrapper to the prompt (`IPermissionAskContext.executionId`)
41
+ and the framework's prompt registry stamps it on the event, so it reaches every client that receives
42
+ the event, the WebSocket and HTTP `permission_request` frames included. It is present whenever the
43
+ session reports that call (its own, or an in-process background agent's) and absent for a
44
+ child-process agent's forwarded request.
45
+
46
+ The ACP agent transport keys its permission rows by that id: the request's `toolCallId` is the
47
+ call's execution id, its start and end address the same row, a denial the session decides on its
48
+ own while the editor's dialog is open closes that one row, and two same-named calls each follow
49
+ their own row whatever order the editor answers in. A request that names no call gets a row that
50
+ closes with the answer, and is closed at the end of the turn if no answer came. The name-ordered
51
+ matching is gone.
52
+ - 0d43f37: **An editor can reopen an earlier conversation over ACP and offer the agent's slash commands.**
53
+
54
+ `@robota-sdk/agent-transport-acp` sends `available_commands_update` once the editor holds the session
55
+ id — the runtime's commands and the user-invocable skills, each with its argument hint — and again
56
+ after a prompt that changed the list (a plugin reload, a preset). A host can supply `loadSession`;
57
+ the agent then offers `session/load`, checks it exactly like `session/new` (the served directory, no
58
+ editor MCP servers or extra roots, workspace trust), refuses it while a prompt runs, and replays the
59
+ loaded conversation — what was typed, what the agent answered, and each finished tool call, a reused
60
+ call id getting a row of its own — before it answers. The port makes the stored session current in
61
+ the object the transport was attached to, so a host attaches a forwarder such as a session slot.
62
+ The published narrow `IAcpTransportSession` contract remains supported: command listing and history
63
+ display reads are optional capabilities. Loading is offered only with both the host port and the
64
+ history read, so a session cannot be replaced without replaying its conversation.
65
+
66
+ `robota acp` supplies the load: the editor may load a stored session of the folder it serves that
67
+ this version can read, and the agent replaces its session with it and shuts the old one down. A load
68
+ is refused while the current session has work in progress, and for a stored session with an active
69
+ goal, a live loop or a schedule that restore would re-arm to fire, which would otherwise run unseen
70
+ by the editor. With `--no-session-persistence` nothing is stored, so loading is not offered.
71
+ - 04a6de4: **Reasoning text a provider streams now reaches the editor as an ACP `agent_thought_chunk`, through a new `thinking_delta` session event.**
72
+
73
+ `IChatOptions.onThinkingDelta`, `IRunOptions.onThinkingDelta` and `IResumeExecutionOptions.onThinkingDelta`
74
+ (typed `TThinkingDeltaCallback`) carry reasoning text from a provider through the run, and the session
75
+ options (`ISessionOptions.onThinkingDelta`, `ICreateSessionOptions.onThinkingDelta`) hand it to the
76
+ session's owner. `InteractiveSession` emits it as `thinking_delta(delta)`, beside `text_delta`. The
77
+ text is never part of the reply: it is not appended to the conversation, not streamed through
78
+ `onTextDelta`, and the core adds no record of its own for it (no text-delta log entry, no
79
+ `provider_stream_raw_delta`); a provider's own native payload capture and response metadata keep
80
+ what the provider sent, as before. A reasoning delta counts as provider activity for the idle
81
+ timeout whether or not anyone listens to it, so a model that is only thinking is not cut off. The
82
+ existing `thinking(isThinking)` event is unchanged and remains the busy indicator. Where a provider
83
+ sends no reasoning text, nothing is emitted and nothing new is switched on.
84
+
85
+ `@robota-sdk/agent-interface-session` is a major change because a new session event name makes an
86
+ exhaustive map over the event names stop compiling until it classifies the event.
87
+
88
+ `@robota-sdk/agent-transport-acp` sends each non-empty `thinking_delta` to the editor as an
89
+ `agent_thought_chunk`, in order with the message chunks, so `robota acp` shows the model's reasoning
90
+ in the editor. A turn that streamed only thoughts still shows its result text, because only reply
91
+ text marks a turn as streamed. The WebSocket wire, the HTTP stream, headless stream-json, `query()`
92
+ and the terminal UI do not carry the new event in this change (`@robota-sdk/agent-transport` and
93
+ `@robota-sdk/agent-ui-terminal` classify it as non-surface). A forked skill and an in-process subagent
94
+ pass their reply text up but not their reasoning.
95
+
96
+ Two providers already receive reasoning text and now report it. `@robota-sdk/agent-provider-openai`
97
+ passes `response.reasoning_summary_text.delta` events of a streamed Responses call to
98
+ `onThinkingDelta`; the API sends them only when the provider profile asks for a reasoning summary, and
99
+ no request option changed; a summary streamed in several parts, and the summary of a later reasoning
100
+ item, each start on a new paragraph (`\n\n`) so the parts do not run together.
101
+ `@robota-sdk/agent-provider-openai-compatible` reports the text of Gemma's thought channel, which the
102
+ provider already removed from the reply, as it streams across chunk boundaries, with each later
103
+ channel of one reply on a new paragraph; the reply text is unchanged. No other provider reports
104
+ reasoning, and Anthropic thinking is not enabled. A model fallback chain still moves to the next model when only reasoning had streamed.
105
+
106
+ The scripted test provider (`@robota-sdk/agent-core/testing`) accepts an optional `thinking` string on
107
+ a turn and delivers it through `onThinkingDelta` before the turn's text; the scripted session harness
108
+ collects `thinking_delta` and now keeps a scripted tool-call turn's `usage`, which it used to drop.
109
+
110
+ ### Patch Changes
111
+
112
+ - 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`.
113
+ - 5783499: Harden file, HTTP, relay, hosted-runtime, editor, and native path boundaries against resource exhaustion, short writes, path races, and integer narrowing.
114
+
115
+ 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.
116
+
117
+ `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.
118
+ - Updated dependencies [7b6234c]
119
+ - Updated dependencies [37b4bd7]
120
+ - Updated dependencies [4eea54b]
121
+ - Updated dependencies [50d2c9f]
122
+ - Updated dependencies [724fabb]
123
+ - Updated dependencies [d877de2]
124
+ - Updated dependencies [d61e159]
125
+ - Updated dependencies [bfe8ed5]
126
+ - Updated dependencies [1698be4]
127
+ - Updated dependencies [a5961c9]
128
+ - Updated dependencies [d4189b9]
129
+ - Updated dependencies [9edae52]
130
+ - Updated dependencies [4078a72]
131
+ - Updated dependencies [4c73a0b]
132
+ - Updated dependencies [196a900]
133
+ - Updated dependencies [f336838]
134
+ - Updated dependencies [34e50f0]
135
+ - Updated dependencies [722e88a]
136
+ - Updated dependencies [d23c848]
137
+ - Updated dependencies [3038eb7]
138
+ - Updated dependencies [02b7452]
139
+ - Updated dependencies [e689c8e]
140
+ - Updated dependencies [ec5e477]
141
+ - Updated dependencies [c7f9203]
142
+ - Updated dependencies [7b72344]
143
+ - Updated dependencies [6ae3f28]
144
+ - Updated dependencies [be0e53c]
145
+ - Updated dependencies [57f57f5]
146
+ - Updated dependencies [ba822c1]
147
+ - Updated dependencies [6e6b06b]
148
+ - Updated dependencies [8bd5fac]
149
+ - Updated dependencies [57280bf]
150
+ - Updated dependencies [5033dd9]
151
+ - Updated dependencies [18c0d5c]
152
+ - Updated dependencies [dbd888d]
153
+ - Updated dependencies [1887e54]
154
+ - Updated dependencies [9f46375]
155
+ - Updated dependencies [41cca13]
156
+ - Updated dependencies [9a01e78]
157
+ - Updated dependencies [ada3841]
158
+ - Updated dependencies [5093a30]
159
+ - Updated dependencies [94b2c87]
160
+ - Updated dependencies [3ab2eca]
161
+ - Updated dependencies [e8d70ac]
162
+ - Updated dependencies [2c612ba]
163
+ - Updated dependencies [66af868]
164
+ - Updated dependencies [3427887]
165
+ - Updated dependencies [c967c57]
166
+ - Updated dependencies [60f3965]
167
+ - Updated dependencies [115e104]
168
+ - Updated dependencies [04719a9]
169
+ - Updated dependencies [04a6de4]
170
+ - Updated dependencies [0ba247b]
171
+ - Updated dependencies [6f41c53]
172
+ - Updated dependencies [4c5148e]
173
+ - Updated dependencies [0116a29]
174
+ - Updated dependencies [fec722f]
175
+ - Updated dependencies [e82215f]
176
+ - Updated dependencies [52b7346]
177
+ - Updated dependencies [9db63ee]
178
+ - Updated dependencies [b078afa]
179
+ - Updated dependencies [2ebff01]
180
+ - Updated dependencies [9db63ee]
181
+ - Updated dependencies [2ebff01]
182
+ - Updated dependencies [2d3b2c0]
183
+ - Updated dependencies [4772067]
184
+ - Updated dependencies [0f98419]
185
+ - Updated dependencies [64ba748]
186
+ - Updated dependencies [9fbab1b]
187
+ - Updated dependencies [d312755]
188
+ - Updated dependencies [a009f5b]
189
+ - Updated dependencies [c207a31]
190
+ - Updated dependencies [9b4f63b]
191
+ - Updated dependencies [3244fb8]
192
+ - Updated dependencies [1e3f91a]
193
+ - Updated dependencies [4f3c075]
194
+ - Updated dependencies [475e085]
195
+ - Updated dependencies [e477440]
196
+ - Updated dependencies [9dcb5da]
197
+ - Updated dependencies [a95ca85]
198
+ - Updated dependencies [b6d14ce]
199
+ - Updated dependencies [0382a51]
200
+ - Updated dependencies [93d061d]
201
+ - Updated dependencies [39554a1]
202
+ - Updated dependencies [d28430a]
203
+ - Updated dependencies [7669851]
204
+ - Updated dependencies [7d9cc66]
205
+ - Updated dependencies [7d9cc66]
206
+ - Updated dependencies [7d9cc66]
207
+ - Updated dependencies [df02568]
208
+ - Updated dependencies [4b76cfa]
209
+ - Updated dependencies [07b627f]
210
+ - Updated dependencies [d0de5b2]
211
+ - Updated dependencies [9c6a8db]
212
+ - Updated dependencies [dfb9b15]
213
+ - Updated dependencies [9665c6e]
214
+ - Updated dependencies [44393be]
215
+ - Updated dependencies [c7fa299]
216
+ - Updated dependencies [7d9cc66]
217
+ - Updated dependencies [7d9cc66]
218
+ - Updated dependencies [5134b3b]
219
+ - Updated dependencies [7911a7d]
220
+ - Updated dependencies [d6b9404]
221
+ - Updated dependencies [62663ae]
222
+ - Updated dependencies [23a30cd]
223
+ - Updated dependencies [7d9cc66]
224
+ - Updated dependencies [7d9cc66]
225
+ - Updated dependencies [5c5ff23]
226
+ - Updated dependencies [9814afc]
227
+ - Updated dependencies [7d9cc66]
228
+ - Updated dependencies [02053ec]
229
+ - Updated dependencies [9c6a8db]
230
+ - @robota-sdk/agent-core@3.0.0
231
+ - @robota-sdk/agent-interface-session@3.0.0
232
+ - @robota-sdk/agent-interface-transport@3.0.0
233
+ - @robota-sdk/agent-transport@3.0.0
234
+
235
+ ## 3.0.0-beta.106
236
+ ### Patch Changes
237
+
238
+ - @robota-sdk/agent-core@3.0.0-beta.106
239
+ - @robota-sdk/agent-interface-session@3.0.0-beta.106
240
+ - @robota-sdk/agent-interface-transport@3.0.0-beta.106
241
+ - @robota-sdk/agent-transport@3.0.0-beta.106
242
+
243
+ ## 3.0.0-beta.105
244
+ ### Patch Changes
245
+
246
+ - @robota-sdk/agent-core@3.0.0-beta.105
247
+ - @robota-sdk/agent-interface-session@3.0.0-beta.105
248
+ - @robota-sdk/agent-interface-transport@3.0.0-beta.105
249
+ - @robota-sdk/agent-transport@3.0.0-beta.105
250
+
251
+ ## 3.0.0-beta.104
252
+ ### Patch Changes
253
+
254
+ - @robota-sdk/agent-core@3.0.0-beta.104
255
+ - @robota-sdk/agent-interface-session@3.0.0-beta.104
256
+ - @robota-sdk/agent-interface-transport@3.0.0-beta.104
257
+ - @robota-sdk/agent-transport@3.0.0-beta.104
258
+
259
+ ## 3.0.0-beta.103
260
+ ### Patch Changes
261
+
262
+ - Updated dependencies [6f41c53]
263
+ - @robota-sdk/agent-core@3.0.0-beta.103
264
+ - @robota-sdk/agent-interface-session@3.0.0-beta.103
265
+ - @robota-sdk/agent-interface-transport@3.0.0-beta.103
266
+ - @robota-sdk/agent-transport@3.0.0-beta.103
267
+
268
+ ## 3.0.0-beta.102
269
+ ### Patch Changes
270
+
271
+ - @robota-sdk/agent-core@3.0.0-beta.102
272
+ - @robota-sdk/agent-interface-session@3.0.0-beta.102
273
+ - @robota-sdk/agent-interface-transport@3.0.0-beta.102
274
+ - @robota-sdk/agent-transport@3.0.0-beta.102
275
+
276
+ ## 3.0.0-beta.101
277
+ ### Patch Changes
278
+
279
+ - 5783499: Harden file, HTTP, relay, hosted-runtime, editor, and native path boundaries against resource exhaustion, short writes, path races, and integer narrowing.
280
+
281
+ 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.
282
+
283
+ `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.
284
+ - Updated dependencies [62663ae]
285
+ - @robota-sdk/agent-core@3.0.0-beta.101
286
+ - @robota-sdk/agent-interface-session@3.0.0-beta.101
287
+ - @robota-sdk/agent-interface-transport@3.0.0-beta.101
288
+ - @robota-sdk/agent-transport@3.0.0-beta.101
289
+
290
+ ## 3.0.0-beta.100
291
+ ### Patch Changes
292
+
293
+ - @robota-sdk/agent-core@3.0.0-beta.100
294
+ - @robota-sdk/agent-interface-session@3.0.0-beta.100
295
+ - @robota-sdk/agent-interface-transport@3.0.0-beta.100
296
+ - @robota-sdk/agent-transport@3.0.0-beta.100
297
+
298
+ ## 3.0.0-beta.99
299
+ ### Patch Changes
300
+
301
+ - @robota-sdk/agent-core@3.0.0-beta.99
302
+ - @robota-sdk/agent-interface-session@3.0.0-beta.99
303
+ - @robota-sdk/agent-interface-transport@3.0.0-beta.99
304
+ - @robota-sdk/agent-transport@3.0.0-beta.99
305
+
306
+ ## 3.0.0-beta.98
307
+ ### Patch Changes
308
+
309
+ - @robota-sdk/agent-core@3.0.0-beta.98
310
+ - @robota-sdk/agent-interface-session@3.0.0-beta.98
311
+ - @robota-sdk/agent-interface-transport@3.0.0-beta.98
312
+ - @robota-sdk/agent-transport@3.0.0-beta.98
313
+
314
+ ## 3.0.0-beta.97
315
+ ### Minor Changes
316
+
317
+ - e5a4254: **An editor that offers its file system carries the file tools' reads and writes.**
318
+
319
+ `Read`, `Write` and `Edit` take an optional `editorFiles` port (`IEditorFileAccess`), asked at each
320
+ call. On the host, after the tool's containment check (the permission gate has already decided),
321
+ `Read` returns the editor's text, unsaved changes included, and `Write` hands the content to the
322
+ editor; `Edit` reads where `Read` reads and writes where `Write` writes. The editor's text keeps
323
+ `Read`'s byte budget, binary refusal, formatting and cancellation, and an editor failure becomes the
324
+ tool's error result. `Glob` and `Grep` still search the disk and end their result with a note saying
325
+ so while an editor is attached. A sandbox with its own filesystem still carries every file tool, and
326
+ commands never use the editor. `createDefaultTools` and `createCodingPack` pass `editorFiles` through
327
+ to those tools only.
328
+
329
+ The ACP agent transport records the editor's `clientCapabilities.fs` and, once `session/new` binds the
330
+ session, hands the host an access through the new `onEditorFileAccess` option that sends
331
+ `fs/read_text_file` and `fs/write_text_file` for that session, for what the editor offered; stopping
332
+ the transport withdraws it. `robota acp` connects the two, so Read, Write and Edit go through the
333
+ editor in an editor that offers `fs`; `terminal/*` is not used.
334
+ - 0d43f37: **An editor can reopen an earlier conversation over ACP and offer the agent's slash commands.**
335
+
336
+ `@robota-sdk/agent-transport-acp` sends `available_commands_update` once the editor holds the session
337
+ id — the runtime's commands and the user-invocable skills, each with its argument hint — and again
338
+ after a prompt that changed the list (a plugin reload, a preset). A host can supply `loadSession`;
339
+ the agent then offers `session/load`, checks it exactly like `session/new` (the served directory, no
340
+ editor MCP servers or extra roots, workspace trust), refuses it while a prompt runs, and replays the
341
+ loaded conversation — what was typed, what the agent answered, and each finished tool call, a reused
342
+ call id getting a row of its own — before it answers. The port makes the stored session current in
343
+ the object the transport was attached to, so a host attaches a forwarder such as a session slot.
344
+ The published narrow `IAcpTransportSession` contract remains supported: command listing and history
345
+ display reads are optional capabilities. Loading is offered only with both the host port and the
346
+ history read, so a session cannot be replaced without replaying its conversation.
347
+
348
+ `robota acp` supplies the load: the editor may load a stored session of the folder it serves that
349
+ this version can read, and the agent replaces its session with it and shuts the old one down. A load
350
+ is refused while the current session has work in progress, and for a stored session with an active
351
+ goal, a live loop or a schedule that restore would re-arm to fire, which would otherwise run unseen
352
+ by the editor. With `--no-session-persistence` nothing is stored, so loading is not offered.
353
+
354
+ ### Patch Changes
355
+
356
+ - Updated dependencies [c207a31]
357
+ - Updated dependencies [9b4f63b]
358
+ - Updated dependencies [7911a7d]
359
+ - Updated dependencies [23a30cd]
360
+ - @robota-sdk/agent-core@3.0.0-beta.97
361
+ - @robota-sdk/agent-transport@3.0.0-beta.97
362
+ - @robota-sdk/agent-interface-session@3.0.0-beta.97
363
+ - @robota-sdk/agent-interface-transport@3.0.0-beta.97
364
+
3
365
  ## 3.0.0-beta.96
4
366
  ### Patch Changes
5
367
 
package/README.md CHANGED
@@ -45,9 +45,11 @@ anything else in the process can print to it, and send logs to stderr.
45
45
  | ------------------ | ---------------------------------------------------------- | ----------------------------------------------------------------------------------------------------------------- |
46
46
  | `input` / `output` | `Readable` / `Writable` | The editor writes `input` and reads `output`. Stopping never destroys either. |
47
47
  | `agentInfo` | `{ name, version }` | Reported to the editor on `initialize`. |
48
- | `cwd` | `string` | The directory the session is rooted at. `session/new` must name it. |
49
- | `admitWorkspace` | `() => { admitted: true } \| { admitted: false, message }` | The host's trust check, asked on `session/new`. A refusal's `message` is returned to the editor as the error. |
48
+ | `cwd` | `string` | The directory the session is rooted at. `session/new` and `session/load` must name it. |
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
+ | `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 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. |
51
53
  | `driverId` | `TDriverId` | Attribution on turns and prompt answers. Default `'owner'`. |
52
54
  | `surface` | `TUsageSurface` | Usage surface recorded on turns. Default `'unknown'`. |
53
55
  | `onDeliveryError` | `(error, context) => void` | Receives errors the editor does not: a failed notification write, or the cause of a failed turn. |
@@ -56,9 +58,11 @@ anything else in the process can print to it, and send logs to stderr.
56
58
 
57
59
  | Editor action | Behavior |
58
60
  | ----------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
59
- | `initialize` | Always answers protocol version 1; text and embedded-context prompts, no image or audio, no `session/load`. |
61
+ | `initialize` | Always answers protocol version 1; text and embedded-context prompts, no image or audio; `session/load` only when the host supplies `loadSession` and the session supplies `getMessagesDisplay`. |
60
62
  | `session/new` | Returns the attached session's id. Refused if `cwd` is not the served directory, if `mcpServers` or `additionalDirectories` are not empty, or if the workspace is not admitted (error `-32001`, `data.reason` `workspace-not-trusted`); one session per connection. |
61
63
  | `session/prompt` | Streams `agent_message_chunk` and `tool_call` / `tool_call_update` updates, and `agent_thought_chunk` when the provider streams reasoning text, then answers `end_turn`, or `cancelled` after a cancel. A prompt starting with `/` runs as a command. |
64
+ | `session/load` | With `loadSession` and history reads: makes the stored session current, replays its conversation (messages and finished tool calls) as updates, then answers. Checked like `session/new`; refused while a prompt runs. |
65
+ | available commands | When the session supplies command or skill listing, after `session/new` or `session/load`, and after a prompt that changed them: `available_commands_update` with the runtime's commands and the user-invocable skills, each with its argument hint. |
62
66
  | `session/cancel` | Aborts the running turn; the pending prompt answers `cancelled`. |
63
67
  | permission request from the session | `session/request_permission` with allow once, allow for this session, allow for this project (only if the session can persist it) and reject. Cancelled, failed or unrecognised answers deny. |
64
68
  | question from the session | Not forwarded; the session answers it as cancelled. |
@@ -1,6 +1,6 @@
1
- Object.defineProperty(exports,Symbol.toStringTag,{value:`Module`});let e=require("node:stream"),t=require("@agentclientprotocol/sdk"),n=require("@robota-sdk/agent-transport/node"),r=require("node:fs"),i=require("node:path"),a=require("@robota-sdk/agent-interface-session"),o=require("@robota-sdk/agent-transport");function s(e){let t=Math.max(0,...[...e.matchAll(/`+/g)].map(e=>e[0].length));return"`".repeat(Math.max(3,t+1))}function c(e){let t=``,n=[],r=[];for(let i of e)if(i.type===`text`)t+=i.text;else if(i.type===`resource`&&`text`in i.resource){let e=s(i.resource.text);n.push(`Context from ${i.resource.uri}:\n${e}\n${i.resource.text}\n${e}`)}else i.type===`resource_link`&&r.push(`- ${i.uri}`);return r.length>0&&n.push(`Referenced resources:\n${r.join(`
1
+ Object.defineProperty(exports,Symbol.toStringTag,{value:`Module`});let e=require("node:stream"),t=require("@agentclientprotocol/sdk"),n=require("@robota-sdk/agent-transport/node"),r=require("node:fs"),i=require("node:path"),a=require("@robota-sdk/agent-interface-session"),o=require("@robota-sdk/agent-transport");function s(e){return e===void 0?void 0:{cancellationSignal:e}}function c(e){if(!(e instanceof t.RequestError))return e instanceof Error?e:Error(String(e));let n=e.data,r=typeof n==`object`&&n&&`details`in n&&typeof n.details==`string`?n.details:void 0;return Error(r===void 0?e.message:`${e.message}: ${r}`)}function l(e){if(!(0,i.isAbsolute)(e))throw Error(`the editor is given absolute paths only: ${e}`)}function u(e,t,n){let r=e?.readTextFile===!0,i=e?.writeTextFile===!0;if(!r&&!i)return;let a=()=>{let e=n();if(e===void 0)throw Error(`the editor has no session to read or write for`);return e};return{...r?{readTextFile:async(e,n)=>{l(e);try{return(await t.request(`fs/read_text_file`,{sessionId:a(),path:e},s(n))).content}catch(e){throw c(e)}}}:{},...i?{writeTextFile:async(e,n,r)=>{l(e);try{await t.request(`fs/write_text_file`,{sessionId:a(),path:e,content:n},s(r))}catch(e){throw c(e)}}}:{}}}function d(e){let t=Math.max(0,...[...e.matchAll(/`+/g)].map(e=>e[0].length));return"`".repeat(Math.max(3,t+1))}function f(e){let t=``,n=[],r=[];for(let i of e)if(i.type===`text`)t+=i.text;else if(i.type===`resource`&&`text`in i.resource){let e=d(i.resource.text);n.push(`Context from ${i.resource.uri}:\n${e}\n${i.resource.text}\n${e}`)}else i.type===`resource_link`&&r.push(`- ${i.uri}`);return r.length>0&&n.push(`Referenced resources:\n${r.join(`
2
2
  `)}`),{typed:t,full:n.length===0?t:`${t}\n\n${n.join(`
3
3
 
4
- `)}`}}const l={Read:`read`,Glob:`search`,Grep:`search`,Edit:`edit`,Write:`edit`,Shell:`execute`,Bash:`execute`,WebFetch:`fetch`,WebSearch:`fetch`};function u(e){return l[e]??`other`}function d(e,t){let n=t?.replace(/\s+/g,` `).trim();return n?`${e}: ${n.length>120?`${n.slice(0,119)}…`:n}`:e}function f(e,t){let n=t?.trim();return n?`${e}: ${n}`:e}function p(e){return d(e.toolName,e.displayPath??e.firstArg)}function m(e){return e.result===`success`?`completed`:`failed`}const h={allowOnce:`allow-once`,allowSession:`allow-session`,allowProject:`allow-project`,reject:`reject`};function g(e){return[{optionId:h.allowOnce,name:`Allow once`,kind:`allow_once`},{optionId:h.allowSession,name:`Allow for this session`,kind:`allow_always`},...e?[{optionId:h.allowProject,name:`Allow for this project`,kind:`allow_always`}]:[],{optionId:h.reject,name:`Reject`,kind:`reject_once`}]}function _(e){let t=f(e.toolName,e.diffFile??(0,o.permissionSummaryArgument)(e.toolArgs));return e.requester?`${e.requester.label}: ${t}`:t}function v(e){return e.cwd===void 0||`cwd`in e.toolArgs?e.toolArgs:{...e.toolArgs,cwd:e.cwd}}const y=/^@@ /;function b(e){let t=[],n=[],r=`context`;for(let i of e){if(i.type===`hunk`){if(y.test(i.text))continue;r!==`add`&&t.push(i.text),r!==`remove`&&n.push(i.text);continue}r=i.type,i.type!==`add`&&t.push(i.text),i.type!==`remove`&&n.push(i.text)}return{...t.length>0?{oldText:t.join(`
4
+ `)}`}}const p={Read:`read`,Glob:`search`,Grep:`search`,Edit:`edit`,Write:`edit`,Shell:`execute`,Bash:`execute`,WebFetch:`fetch`,WebSearch:`fetch`};function m(e){return p[e]??`other`}function h(e,t){let n=t?.replace(/\s+/g,` `).trim();return n?`${e}: ${n.length>120?`${n.slice(0,119)}…`:n}`:e}function g(e,t){let n=t?.trim();return n?`${e}: ${n}`:e}function _(e){return h(e.toolName,e.displayPath??e.firstArg)}function v(e){return e.result===`success`?`completed`:`failed`}function y(e){let t=[],n=new Set,r=(e,r,i)=>{n.has(e)||(n.add(e),t.push({name:e,description:r,...i?{input:{hint:i}}:{}}))};for(let t of e.listCommands?.()??[])t.runner===`runtime`&&r(t.name,t.description,t.argumentHint);for(let t of e.listSkills?.()??[])t.userInvocable&&r(t.name,t.description,t.argumentHint);return t}function b(e,t,n){for(let r of e){if(r.type===`text`){if(r.content===``)continue;t.send({sessionUpdate:r.role===`user`?`user_message_chunk`:`agent_message_chunk`,content:{type:`text`,text:r.content}});continue}let{tool:e}=r;if(e.internal)continue;let i=n(e.executionId);t.send({sessionUpdate:`tool_call`,toolCallId:i,title:_(e),kind:m(e.toolName),status:v(e)})}}const x={allowOnce:`allow-once`,allowSession:`allow-session`,allowProject:`allow-project`,reject:`reject`};function S(e){return[{optionId:x.allowOnce,name:`Allow once`,kind:`allow_once`},{optionId:x.allowSession,name:`Allow for this session`,kind:`allow_always`},...e?[{optionId:x.allowProject,name:`Allow for this project`,kind:`allow_always`}]:[],{optionId:x.reject,name:`Reject`,kind:`reject_once`}]}function C(e){let t=g(e.toolName,e.diffFile??(0,o.permissionSummaryArgument)(e.toolArgs));return e.requester?`${e.requester.label}: ${t}`:t}function w(e){return e.cwd===void 0||`cwd`in e.toolArgs?e.toolArgs:{...e.toolArgs,cwd:e.cwd}}const T=/^@@ /;function E(e){let t=[],n=[],r=`context`;for(let i of e){if(i.type===`hunk`){if(T.test(i.text))continue;r!==`add`&&t.push(i.text),r!==`remove`&&n.push(i.text);continue}r=i.type,i.type!==`add`&&t.push(i.text),i.type!==`remove`&&n.push(i.text)}return{...t.length>0?{oldText:t.join(`
5
5
  `)}:{},newText:n.join(`
6
- `)}}function x(e,t,n){let r=e.diffFile===void 0?void 0:(0,i.resolve)(n,e.diffFile);return{toolCallId:t,title:_(e),kind:u(e.toolName),status:`pending`,rawInput:v(e),...r!==void 0&&e.diffLines!==void 0?{content:[{type:`diff`,path:r,...b(e.diffLines)}]}:{},...r===void 0?{}:{locations:[{path:r}]}}}function S(e,t){if(e.outcome!==`selected`||!t.some(t=>t.optionId===e.optionId))return!1;switch(e.optionId){case h.allowOnce:return!0;case h.allowSession:return`allow-session`;case h.allowProject:return`allow-project`;default:return!1}}function C(e,t){let n=Object.keys(t);for(let r of n)e.on(r,t[r]);return()=>{for(let r of n)e.off(r,t[r])}}function w(e){let{updates:t}=e,n=[],r=new Map,i=new Map,a=!1,o=e=>{if(e===void 0)return;let t=r.get(e);return r.delete(e),t},s=(e,n)=>t.send({sessionUpdate:`tool_call_update`,toolCallId:e,status:n?`completed`:`failed`});return{start(r){if(r.internal)return;let i=o(r.executionId),a=r.executionId??e.nextToolCallId();n.push({id:a,toolName:r.toolName,executionId:r.executionId});let s={toolCallId:a,title:p(r),kind:u(r.toolName),status:`in_progress`};i===void 0?t.send({sessionUpdate:`tool_call`,...s}):t.send({sessionUpdate:`tool_call_update`,...s,rawInput:i.rawInput})},end(r){if(r.internal)return;let i=m(r),a=n.findIndex(e=>r.executionId===void 0?e.executionId===void 0&&e.toolName===r.toolName:e.executionId===r.executionId);if(a>=0){let[e]=n.splice(a,1);t.send({sessionUpdate:`tool_call_update`,toolCallId:e.id,status:i});return}if(o(r.executionId)!==void 0){t.send({sessionUpdate:`tool_call_update`,toolCallId:r.executionId,status:i});return}t.send({sessionUpdate:`tool_call`,toolCallId:r.executionId??e.nextToolCallId(),title:p(r),kind:u(r.toolName),status:i})},requested(t){let{executionId:a}=t;if(a===void 0){let n=x(t,`permission-${t.id}`,e.cwd);return i.set(t.id,n.toolCallId),n}let o=x(t,a,e.cwd);return n.some(e=>e.executionId===a)||r.set(a,{rawInput:o.rawInput,background:t.requester!==void 0}),o},answered(e,t){if(e.executionId!==void 0){let n=r.get(e.executionId);n!==void 0&&(n.allowed=t);return}let n=i.get(e.id);n===void 0||a||(i.delete(e.id),s(n,t))},finish(){a=!0;for(let[e,t]of r)s(e,t.background&&t.allowed===!0);for(let e of i.values())s(e,!1);r.clear(),i.clear()}}}const T={stopReason:`cancelled`},E={stopReason:`end_turn`};function D(e){return e instanceof Error?e:Error(String(e))}async function O(e,t){let{session:n,sessionId:r,client:i,updates:a}=e,o=w({updates:a,nextToolCallId:e.nextToolCallId,cwd:e.cwd}),s=!1,c=C(n,{text_delta:e=>{e!==``&&(s=!0,a.text(e))},thinking_delta:e=>{e!==``&&a.thought(e)},tool_start:o.start,tool_end:o.end,permission_request:t=>{let a=g(t.canPersistProjectPermission===!0),s=o.requested(t);(async()=>{let c=!1;try{c=S((await i.request(`session/request_permission`,{sessionId:r,toolCall:s,options:a})).outcome,a)}catch(t){e.report(D(t),`session/request_permission`)}o.answered(t,c!==!1);try{n.resolvePermission(t.id,c,e.driverId)}catch(t){e.report(D(t),`resolvePermission`)}})()}});try{let n=await t.run();return e.wasCancelled()||e.signal.aborted||n.interrupted?T:(!s&&n.reply!==``&&a.text(n.reply),E)}catch(n){return e.wasCancelled()||e.signal.aborted||t.isCancellation?.(n)===!0?T:t.fail(n)}finally{c(),o.finish(),await a.drained()}}function k(e,n){return O(e,{async run(){let t=await(await e.session.submit(n,void 0,void 0,{signal:e.signal,driverId:e.driverId,...e.surface===void 0?{}:{surface:e.surface}})).completed;return{interrupted:t.interrupted===!0,reply:t.response}},isCancellation:e=>(0,a.isTurnNotRunError)(e)&&e.reason===`cancelled`,fail(n){throw e.report(D(n),`turn`),new t.RequestError(-32603,(0,a.isTurnNotRunError)(n)?`Turn was not run`:`Turn failed`)}})}function A(e,n,r){return O(e,{async run(){return{interrupted:!1,reply:await r(n,e.signal)}},fail(n){throw e.report(D(n),`command`),new t.RequestError(-32603,`Command failed`)}})}function j(e,t,n){let r=Promise.resolve(),i=!1,a=a=>{r=r.then(async()=>{if(!i)try{await e.notify(`session/update`,{sessionId:t,update:a})}catch(e){i=!0,n(e instanceof Error?e:Error(String(e)),`session/update`)}})};return{send:a,text:e=>a({sessionUpdate:`agent_message_chunk`,content:{type:`text`,text:e}}),thought:e=>a({sessionUpdate:`agent_thought_chunk`,content:{type:`text`,text:e}}),drained:()=>r}}const M=()=>Promise.resolve(`Slash commands are not available in this host.`);function N(e,t){if((0,i.resolve)(e)===(0,i.resolve)(t))return!0;try{return(0,r.realpathSync)(e)===(0,r.realpathSync)(t)}catch{return!1}}function P(e,n){let r=n.driverId??a.OWNER_DRIVER_ID,o=(e,t)=>n.onDeliveryError?.(e,t),s,l=!1,u,d=0;return{app:(0,t.agent)({name:n.agentInfo.name}).onRequest(`initialize`,()=>({protocolVersion:t.PROTOCOL_VERSION,agentCapabilities:{loadSession:!1,promptCapabilities:{image:!1,audio:!1,embeddedContext:!0}},agentInfo:{name:n.agentInfo.name,version:n.agentInfo.version}})).onRequest(`session/new`,async({params:r})=>{if(s!==void 0||l)throw t.RequestError.invalidParams(void 0,`one session per connection`);if(!(0,i.isAbsolute)(r.cwd)||!N(r.cwd,n.cwd))throw t.RequestError.invalidParams(void 0,`cwd must be the directory this agent was started in: ${n.cwd}`);if(r.mcpServers.length>0)throw t.RequestError.invalidParams(void 0,`mcpServers are not accepted by this agent`);if((r.additionalDirectories??[]).length>0)throw t.RequestError.invalidParams(void 0,`additionalDirectories are not accepted by this agent`);let a=n.admitWorkspace();if(!a.admitted)throw new t.RequestError(-32001,a.message,{reason:`workspace-not-trusted`});l=!0;try{await e.whenInitialized?.(),s=e.getStatusSnapshot().sessionId}catch(e){throw o(e instanceof Error?e:Error(String(e)),`session/new`),new t.RequestError(-32603,`The session could not be started`)}finally{l=!1}return{sessionId:s}}).onRequest(`session/prompt`,async({params:i,signal:a,client:l})=>{if(i.sessionId!==s)throw t.RequestError.invalidParams(void 0,`unknown session`);if(u!==void 0)throw t.RequestError.invalidParams(void 0,`a prompt is already running`);let f=c(i.prompt);if(f.full.trim()===``)throw t.RequestError.invalidParams(void 0,`the prompt has no text`);let p=new AbortController,m=!1;u={cancel(){m=!0,p.abort(),e.abort()}};let h={session:e,sessionId:i.sessionId,client:l,updates:j(l,i.sessionId,o),signal:AbortSignal.any([a,p.signal]),driverId:r,surface:n.surface??`unknown`,cwd:n.cwd,nextToolCallId:()=>`tool-${d+=1}`,wasCancelled:()=>m,report:o};try{return f.typed.trimStart().startsWith(`/`)?await A(h,f.typed,n.executeCommand??M):await k(h,f.full)}finally{u=void 0}}).onNotification(`session/cancel`,({params:e})=>{e.sessionId===s&&u?.cancel()}),cancelActive:()=>u?.cancel()}}function F(e){return new WritableStream({write:t=>new Promise((n,r)=>{e.write(t,e=>e?r(e):n())})})}function I(r){let i=null,a=!1,o=null,s=null,c=null,l=null,u=null,d=null,f=Promise.resolve(),p=()=>{f=new Promise((e,t)=>{u=e,d=t}),f.catch(()=>void 0)},m=()=>{try{l?.(),c?.close()}finally{s?.release(),s=null,c=null,l=null}},h=e=>Object.assign(Error(`ACP transport ${e}.`),{name:`TransportLifecycleError`,code:e,transportName:`acp`});return{name:`acp`,lifecycle:Object.freeze({kind:`service`}),attach(e){if(a||o)throw h(`already-started`);i=e,p()},async start(){if(!i)throw h(`not-attached`);if(a||o)throw h(`already-started`);a=!0;try{let a=P(i,r);l=a.cancelActive;let o=(0,n.attachStdioCarrier)(r.input,r.output,{onInputClose:()=>u?.(),onOutputClose:()=>u?.(),onOutputError:e=>d?.(e)});s=o;let f=a.app.connect((0,t.ndJsonStream)(F(r.output),e.Readable.toWeb(o.bridge)));c=f,f.closed.then(()=>u?.())}catch(e){throw m(),a=!1,e}},stop(){return o||(o=(async()=>{try{m()}finally{i=null,a=!1,u?.()}})().finally(()=>{o=null}),o)},waitForClose(){return f}}}exports.createAcpTransport=I;
6
+ `)}}function D(e,t,n){let r=e.diffFile===void 0?void 0:(0,i.resolve)(n,e.diffFile);return{toolCallId:t,title:C(e),kind:m(e.toolName),status:`pending`,rawInput:w(e),...r!==void 0&&e.diffLines!==void 0?{content:[{type:`diff`,path:r,...E(e.diffLines)}]}:{},...r===void 0?{}:{locations:[{path:r}]}}}function O(e,t){if(e.outcome!==`selected`||!t.some(t=>t.optionId===e.optionId))return!1;switch(e.optionId){case x.allowOnce:return!0;case x.allowSession:return`allow-session`;case x.allowProject:return`allow-project`;default:return!1}}function k(e,t){let n=Object.keys(t);for(let r of n)e.on(r,t[r]);return()=>{for(let r of n)e.off(r,t[r])}}function A(e){let{updates:t}=e,n=[],r=new Map,i=new Map,a=!1,o=e=>{if(e===void 0)return;let t=r.get(e);return r.delete(e),t},s=(e,n)=>t.send({sessionUpdate:`tool_call_update`,toolCallId:e,status:n?`completed`:`failed`});return{start(r){if(r.internal)return;let i=o(r.executionId),a=i?.id??e.nextToolCallId(r.executionId);n.push({id:a,toolName:r.toolName,executionId:r.executionId});let s={toolCallId:a,title:_(r),kind:m(r.toolName),status:`in_progress`};i===void 0?t.send({sessionUpdate:`tool_call`,...s}):t.send({sessionUpdate:`tool_call_update`,...s,rawInput:i.rawInput})},end(r){if(r.internal)return;let i=v(r),a=n.findIndex(e=>r.executionId===void 0?e.executionId===void 0&&e.toolName===r.toolName:e.executionId===r.executionId);if(a>=0){let[e]=n.splice(a,1);t.send({sessionUpdate:`tool_call_update`,toolCallId:e.id,status:i});return}let s=o(r.executionId);if(s!==void 0){t.send({sessionUpdate:`tool_call_update`,toolCallId:s.id,status:i});return}t.send({sessionUpdate:`tool_call`,toolCallId:e.nextToolCallId(r.executionId),title:_(r),kind:m(r.toolName),status:i})},requested(t){let{executionId:a}=t;if(a===void 0){let n=D(t,e.nextToolCallId(`permission-${t.id}`),e.cwd);return i.set(t.id,n.toolCallId),n}let o=n.find(e=>e.executionId===a),s=r.get(a),c=o?.id??s?.id??e.nextToolCallId(a),l=D(t,c,e.cwd);return o===void 0&&s===void 0&&r.set(a,{id:c,rawInput:l.rawInput,background:t.requester!==void 0}),l},answered(e,t){if(e.executionId!==void 0){let n=r.get(e.executionId);n!==void 0&&(n.allowed=t);return}let n=i.get(e.id);n===void 0||a||(i.delete(e.id),s(n,t))},finish(){a=!0;for(let e of r.values())s(e.id,e.background&&e.allowed===!0);for(let e of i.values())s(e,!1);r.clear(),i.clear()}}}const j={stopReason:`cancelled`},M={stopReason:`end_turn`};function N(e){return e instanceof Error?e:Error(String(e))}async function P(e,t){let{session:n,sessionId:r,client:i,updates:a}=e,o=A({updates:a,nextToolCallId:e.nextToolCallId,cwd:e.cwd}),s=!1,c=k(n,{text_delta:e=>{e!==``&&(s=!0,a.text(e))},thinking_delta:e=>{e!==``&&a.thought(e)},tool_start:o.start,tool_end:o.end,permission_request:t=>{let a=S(t.canPersistProjectPermission===!0),s=o.requested(t);(async()=>{let c=!1;try{c=O((await i.request(`session/request_permission`,{sessionId:r,toolCall:s,options:a})).outcome,a)}catch(t){e.report(N(t),`session/request_permission`)}o.answered(t,c!==!1);try{n.resolvePermission(t.id,c,e.driverId)}catch(t){e.report(N(t),`resolvePermission`)}})()}});try{let n=await t.run();return e.wasCancelled()||e.signal.aborted||n.interrupted?j:(!s&&n.reply!==``&&a.text(n.reply),M)}catch(n){return e.wasCancelled()||e.signal.aborted||t.isCancellation?.(n)===!0?j:t.fail(n)}finally{c(),o.finish(),await a.drained()}}function F(e,n){return P(e,{async run(){let t=await(await e.session.submit(n,void 0,void 0,{signal:e.signal,driverId:e.driverId,...e.surface===void 0?{}:{surface:e.surface}})).completed;return{interrupted:t.interrupted===!0,reply:t.response}},isCancellation:e=>(0,a.isTurnNotRunError)(e)&&e.reason===`cancelled`,fail(n){throw e.report(N(n),`turn`),new t.RequestError(-32603,(0,a.isTurnNotRunError)(n)?`Turn was not run`:`Turn failed`)}})}function I(e,n,r){return P(e,{async run(){return{interrupted:!1,reply:await r(n,e.signal)}},fail(n){throw e.report(N(n),`command`),new t.RequestError(-32603,`Command failed`)}})}function L(e,t,n){let r=Promise.resolve(),i=!1,a=a=>{r=r.then(async()=>{if(!i)try{await e.notify(`session/update`,{sessionId:t,update:a})}catch(e){i=!0,n(e instanceof Error?e:Error(String(e)),`session/update`)}})};return{send:a,text:e=>a({sessionUpdate:`agent_message_chunk`,content:{type:`text`,text:e}}),thought:e=>a({sessionUpdate:`agent_thought_chunk`,content:{type:`text`,text:e}}),drained:()=>r}}const R=-32603,z=()=>Promise.resolve(`Slash commands are not available in this host.`);function B(e,t){if((0,i.resolve)(e)===(0,i.resolve)(t))return!0;try{return(0,r.realpathSync)(e)===(0,r.realpathSync)(t)}catch{return!1}}function V(e){return e instanceof Error?e:Error(String(e))}function H(e,n){let r=n.driverId??a.OWNER_DRIVER_ID,o=(e,t)=>n.onDeliveryError?.(e,t),s,c=!1,l,d=0,p=new Set,m=e=>{let t=e;for(;t===void 0||p.has(t);)t=`tool-${d+=1}`;return p.add(t),t},h,g=e=>{if(!(0,i.isAbsolute)(e.cwd)||!B(e.cwd,n.cwd))throw t.RequestError.invalidParams(void 0,`cwd must be the directory this agent was started in: ${n.cwd}`);if(e.mcpServers.length>0)throw t.RequestError.invalidParams(void 0,`mcpServers are not accepted by this agent`);if((e.additionalDirectories??[]).length>0)throw t.RequestError.invalidParams(void 0,`additionalDirectories are not accepted by this agent`);let r=n.admitWorkspace();if(!r.admitted)throw new t.RequestError(-32001,r.message,{reason:`workspace-not-trusted`})},_=async n=>{try{return await e.whenInitialized?.(),e.getStatusSnapshot().sessionId}catch(e){throw o(V(e),n),new t.RequestError(R,`The session could not be started`)}},v=(t,n)=>{if(e.listCommands===void 0&&e.listSkills===void 0)return;let r;try{r=y(e)}catch(e){o(V(e),`available_commands_update`);return}let i=JSON.stringify(r);i!==h&&(h=i,t.notify(`session/update`,{sessionId:n,update:{sessionUpdate:`available_commands_update`,availableCommands:r}}).catch(e=>o(V(e),`available_commands_update`)))},x=(e,t)=>{setImmediate(()=>v(e,t))},S,C=!1,w=e=>{let t=u(S,e,()=>s);t!==void 0&&(C=!0,n.onEditorFileAccess?.(t))};return{app:(0,t.agent)({name:n.agentInfo.name}).onRequest(`initialize`,({params:r})=>(S=r.clientCapabilities?.fs,{protocolVersion:t.PROTOCOL_VERSION,agentCapabilities:{loadSession:n.loadSession!==void 0&&e.getMessagesDisplay!==void 0,promptCapabilities:{image:!1,audio:!1,embeddedContext:!0}},agentInfo:{name:n.agentInfo.name,version:n.agentInfo.version}})).onRequest(`session/new`,async({params:e,client:n})=>{if(s!==void 0||c)throw t.RequestError.invalidParams(void 0,`one session per connection`);g(e),c=!0;try{s=await _(`session/new`)}finally{c=!1}return w(n),x(n,s),{sessionId:s}}).onRequest(`session/load`,async({params:r,client:i})=>{let a=n.loadSession;if(a===void 0||e.getMessagesDisplay===void 0)throw t.RequestError.methodNotFound(`session/load`);if(c)throw t.RequestError.invalidParams(void 0,`a session is already being opened`);if(l!==void 0)throw t.RequestError.invalidParams(void 0,`a prompt is running`);g(r),c=!0;try{let n;try{n=await a(r.sessionId)}catch(e){throw o(V(e),`session/load`),new t.RequestError(R,`The session could not be loaded`)}if(!n.loaded)throw t.RequestError.invalidParams(void 0,n.message);s=await _(`session/load`),w(i);let c=L(i,s,o);try{b(e.getMessagesDisplay(),c,m)}catch(e){throw o(V(e),`session/load`),new t.RequestError(R,`The session history could not be shown`)}await c.drained()}finally{c=!1}return h=void 0,x(i,s),{}}).onRequest(`session/prompt`,async({params:i,signal:a,client:u})=>{if(i.sessionId!==s||c)throw t.RequestError.invalidParams(void 0,`unknown session`);if(l!==void 0)throw t.RequestError.invalidParams(void 0,`a prompt is already running`);let d=f(i.prompt);if(d.full.trim()===``)throw t.RequestError.invalidParams(void 0,`the prompt has no text`);let p=new AbortController,h=!1;l={cancel(){h=!0,p.abort(),e.abort()}};let g={session:e,sessionId:i.sessionId,client:u,updates:L(u,i.sessionId,o),signal:AbortSignal.any([a,p.signal]),driverId:r,surface:n.surface??`unknown`,cwd:n.cwd,nextToolCallId:m,wasCancelled:()=>h,report:o};try{return d.typed.trimStart().startsWith(`/`)?await I(g,d.typed,n.executeCommand??z):await F(g,d.full)}finally{l=void 0,v(u,i.sessionId)}}).onNotification(`session/cancel`,({params:e})=>{e.sessionId===s&&l?.cancel()}),cancelActive:()=>l?.cancel(),withdrawEditorFiles:()=>{C&&(C=!1,n.onEditorFileAccess?.(void 0))}}}function U(e){return new WritableStream({write:t=>new Promise((n,r)=>{e.write(t,e=>e?r(e):n())})})}function W(r){let i=null,a=!1,o=null,s=null,c=null,l=null,u=null,d=null,f=null,p=Promise.resolve(),m=()=>{p=new Promise((e,t)=>{d=e,f=t}),p.catch(()=>void 0)},h=()=>{try{u?.(),l?.(),c?.close()}finally{s?.release(),s=null,c=null,l=null,u=null}},g=e=>Object.assign(Error(`ACP transport ${e}.`),{name:`TransportLifecycleError`,code:e,transportName:`acp`});return{name:`acp`,lifecycle:Object.freeze({kind:`service`}),attach(e){if(a||o)throw g(`already-started`);i=e,m()},async start(){if(!i)throw g(`not-attached`);if(a||o)throw g(`already-started`);a=!0;try{let a=H(i,r);l=a.cancelActive,u=a.withdrawEditorFiles;let o=(0,n.attachStdioCarrier)(r.input,r.output,{onInputClose:()=>d?.(),onOutputClose:()=>d?.(),onOutputError:e=>f?.(e)});s=o;let p=a.app.connect((0,t.ndJsonStream)(U(r.output),e.Readable.toWeb(o.bridge)));c=p,p.closed.then(()=>d?.())}catch(e){throw h(),a=!1,e}},stop(){return o||(o=(async()=>{try{h()}finally{i=null,a=!1,d?.()}})().finally(()=>{o=null}),o)},waitForClose(){return p}}}exports.createAcpTransport=W;
@@ -1,19 +1,29 @@
1
- import { ISessionEvents, ISessionPromptResolution, ISessionStatusRead, ISessionTurnControl, ISessionTurnSubmission, ISubmitOptions, TDriverId } from "@robota-sdk/agent-interface-session";
1
+ import { ISessionCommands, ISessionConversationRead, ISessionEvents, ISessionPromptResolution, ISessionStatusRead, ISessionTurnControl, ISessionTurnSubmission, ISubmitOptions, TDriverId } from "@robota-sdk/agent-interface-session";
2
2
  import { ITransportAdapter } from "@robota-sdk/agent-interface-transport";
3
3
  import { Readable, Writable } from "node:stream";
4
4
  //#region src/acp-types.d.ts
5
5
  /**
6
6
  * The session roles ACP uses: submit and abort a turn, stream its events, answer its permission
7
- * prompts, and name the session. A full interactive session is assignable; nothing else is required.
7
+ * prompts, and name the session. Optional catalog reads let the editor offer commands; optional
8
+ * history reads enable a host load port to replay a stored session. A full interactive session is assignable.
8
9
  */
9
- interface IAcpTransportSession extends ISessionTurnSubmission, ISessionTurnControl, ISessionEvents, ISessionPromptResolution, ISessionStatusRead {
10
+ interface IAcpTransportSession extends ISessionTurnSubmission, ISessionTurnControl, ISessionEvents, ISessionPromptResolution, ISessionStatusRead, Partial<Pick<ISessionCommands, 'listCommands' | 'listSkills'>>, Partial<Pick<ISessionConversationRead, 'getMessagesDisplay'>> {
10
11
  /**
11
12
  * Resolves once the session can name itself, and rejects if it failed to start. A session that
12
13
  * builds itself in the background (`InteractiveSession`) has no id before then, so `session/new`
13
- * waits on this. A session that is ready at construction can omit it.
14
+ * and `session/load` wait on this. A session that is ready at construction can omit it.
14
15
  */
15
16
  whenInitialized?(): Promise<void>;
16
17
  }
18
+ /**
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`.
22
+ */
23
+ interface IAcpEditorFileAccess {
24
+ readTextFile?(path: string, signal?: AbortSignal): Promise<string>;
25
+ writeTextFile?(path: string, content: string, signal?: AbortSignal): Promise<void>;
26
+ }
17
27
  /** The host's answer to whether the workspace may serve an editor; a refusal carries what to tell the user. */
18
28
  type TAcpWorkspaceAdmission = {
19
29
  readonly admitted: true;
@@ -21,6 +31,13 @@ type TAcpWorkspaceAdmission = {
21
31
  readonly admitted: false;
22
32
  readonly message: string;
23
33
  };
34
+ /** The host's answer to a `session/load`; a refusal carries what to tell the editor. */
35
+ type TAcpSessionLoad = {
36
+ readonly loaded: true;
37
+ } | {
38
+ readonly loaded: false;
39
+ readonly message: string;
40
+ };
24
41
  interface IAcpTransportOptions {
25
42
  /** Process-owned byte streams carrying newline-delimited JSON-RPC: the editor writes `input`, reads `output`. */
26
43
  readonly input: Readable;
@@ -30,9 +47,9 @@ interface IAcpTransportOptions {
30
47
  readonly name: string;
31
48
  readonly version: string;
32
49
  };
33
- /** The directory the attached session is rooted at; `session/new` must name this directory. */
50
+ /** The directory the attached session is rooted at; `session/new` and `session/load` must name it. */
34
51
  readonly cwd: string;
35
- /** The host's trust check, asked when the editor creates the session. */
52
+ /** The host's trust check, asked when the editor creates or loads the session. */
36
53
  readonly admitWorkspace: () => TAcpWorkspaceAdmission;
37
54
  /**
38
55
  * Runs a `/command` prompt and returns the text to show. Supplied by the host because command
@@ -41,6 +58,21 @@ interface IAcpTransportOptions {
41
58
  * cancels the prompt or the connection closes; pass it on so what the command starts stops with it.
42
59
  */
43
60
  readonly executeCommand?: (text: string, signal: AbortSignal) => Promise<string>;
61
+ /**
62
+ * Makes the stored session `sessionId` current, so that the object this transport was attached to
63
+ * answers for it from then on (the host attaches a forwarder, such as a session slot, whose current
64
+ * session the port replaces). It must admit only a session rooted in the served directory, and may
65
+ * refuse with a message for the editor. Supplied by the host because building a session from
66
+ * storage is not this package's; without it or the session's history read, `session/load` is not offered.
67
+ */
68
+ readonly loadSession?: (sessionId: string) => Promise<TAcpSessionLoad>;
69
+ /**
70
+ * Receives the editor's file system after a new or loaded session binds, when the editor offers reads or
71
+ * writes, and `undefined` when the transport stops. The host decides which of its tools use it;
72
+ * they must apply their own path and permission checks first, because the editor checks nothing
73
+ * for the agent. Never called for an editor that offers no file system.
74
+ */
75
+ readonly onEditorFileAccess?: (access: IAcpEditorFileAccess | undefined) => void;
44
76
  /** Attribution recorded on turns and prompt answers submitted through this transport. Default `'owner'`. */
45
77
  readonly driverId?: TDriverId;
46
78
  /** Usage surface recorded on turns. Default `'unknown'` (no editor value exists). */
@@ -59,5 +91,5 @@ interface IAcpTransport extends ITransportAdapter<IAcpTransportSession> {
59
91
  //#region src/acp-transport.d.ts
60
92
  declare function createAcpTransport(options: IAcpTransportOptions): IAcpTransport;
61
93
  //#endregion
62
- export { type IAcpTransport, type IAcpTransportOptions, type IAcpTransportSession, type TAcpWorkspaceAdmission, createAcpTransport };
94
+ export { type IAcpEditorFileAccess, type IAcpTransport, type IAcpTransportOptions, type IAcpTransportSession, type TAcpSessionLoad, type TAcpWorkspaceAdmission, createAcpTransport };
63
95
  //# sourceMappingURL=index.d.cts.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.cts","names":[],"sources":["../../src/acp-types.ts","../../src/acp-transport.ts"],"mappings":";;;;;;;;UAkBiB,6BAEb,wBACA,qBACA,gBACA,0BACA;;;;;;EAMF,oBAAoB;;;KAIV;WACC;;WAA8B;WAA0B;;UAEpD;;WAEN,OAAO;WACP,QAAQ;;WAER;aAAsB;aAAuB;;;WAE7C;;WAEA,sBAAsB;;;;;;;WAOtB,kBAAkB,cAAc,QAAQ,gBAAgB;;WAExD,WAAW;;WAEX,UAAU;;;;;WAKV,mBAAmB,OAAO,OAAO;;UAG3B,sBAAsB,kBAAkB;;EAEvD,gBAAgB;;;;iBChCF,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"}