@robota-sdk/agent-transport-acp 3.0.0-beta.95 → 3.0.0-beta.99
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 +75 -0
- package/README.md +7 -3
- package/dist/node/index.cjs +3 -3
- package/dist/node/index.d.cts +38 -7
- package/dist/node/index.d.cts.map +1 -1
- package/dist/node/index.d.ts +38 -7
- package/dist/node/index.d.ts.map +1 -1
- package/dist/node/index.js +3 -3
- package/dist/node/index.js.map +1 -1
- package/package.json +5 -5
- package/src/__tests__/acp-editor-files.test.ts +236 -0
- package/src/__tests__/acp-session-load.test.ts +649 -0
- package/src/__tests__/acp-test-harness.ts +9 -1
- package/src/acp-agent.ts +180 -42
- package/src/acp-editor-files.ts +86 -0
- package/src/acp-session-catalog.ts +65 -0
- package/src/acp-tool-calls.ts +21 -11
- package/src/acp-transport.ts +5 -0
- package/src/acp-turn.ts +2 -2
- package/src/acp-types.ts +38 -5
- package/src/index.ts +2 -0
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,80 @@
|
|
|
1
1
|
# @robota-sdk/agent-transport-acp
|
|
2
2
|
|
|
3
|
+
## 3.0.0-beta.99
|
|
4
|
+
### Patch Changes
|
|
5
|
+
|
|
6
|
+
- @robota-sdk/agent-core@3.0.0-beta.99
|
|
7
|
+
- @robota-sdk/agent-interface-session@3.0.0-beta.99
|
|
8
|
+
- @robota-sdk/agent-interface-transport@3.0.0-beta.99
|
|
9
|
+
- @robota-sdk/agent-transport@3.0.0-beta.99
|
|
10
|
+
|
|
11
|
+
## 3.0.0-beta.98
|
|
12
|
+
### Patch Changes
|
|
13
|
+
|
|
14
|
+
- @robota-sdk/agent-core@3.0.0-beta.98
|
|
15
|
+
- @robota-sdk/agent-interface-session@3.0.0-beta.98
|
|
16
|
+
- @robota-sdk/agent-interface-transport@3.0.0-beta.98
|
|
17
|
+
- @robota-sdk/agent-transport@3.0.0-beta.98
|
|
18
|
+
|
|
19
|
+
## 3.0.0-beta.97
|
|
20
|
+
### Minor Changes
|
|
21
|
+
|
|
22
|
+
- e5a4254: **An editor that offers its file system carries the file tools' reads and writes.**
|
|
23
|
+
|
|
24
|
+
`Read`, `Write` and `Edit` take an optional `editorFiles` port (`IEditorFileAccess`), asked at each
|
|
25
|
+
call. On the host, after the tool's containment check (the permission gate has already decided),
|
|
26
|
+
`Read` returns the editor's text, unsaved changes included, and `Write` hands the content to the
|
|
27
|
+
editor; `Edit` reads where `Read` reads and writes where `Write` writes. The editor's text keeps
|
|
28
|
+
`Read`'s byte budget, binary refusal, formatting and cancellation, and an editor failure becomes the
|
|
29
|
+
tool's error result. `Glob` and `Grep` still search the disk and end their result with a note saying
|
|
30
|
+
so while an editor is attached. A sandbox with its own filesystem still carries every file tool, and
|
|
31
|
+
commands never use the editor. `createDefaultTools` and `createCodingPack` pass `editorFiles` through
|
|
32
|
+
to those tools only.
|
|
33
|
+
|
|
34
|
+
The ACP agent transport records the editor's `clientCapabilities.fs` and, once `session/new` binds the
|
|
35
|
+
session, hands the host an access through the new `onEditorFileAccess` option that sends
|
|
36
|
+
`fs/read_text_file` and `fs/write_text_file` for that session, for what the editor offered; stopping
|
|
37
|
+
the transport withdraws it. `robota acp` connects the two, so Read, Write and Edit go through the
|
|
38
|
+
editor in an editor that offers `fs`; `terminal/*` is not used.
|
|
39
|
+
- 0d43f37: **An editor can reopen an earlier conversation over ACP and offer the agent's slash commands.**
|
|
40
|
+
|
|
41
|
+
`@robota-sdk/agent-transport-acp` sends `available_commands_update` once the editor holds the session
|
|
42
|
+
id — the runtime's commands and the user-invocable skills, each with its argument hint — and again
|
|
43
|
+
after a prompt that changed the list (a plugin reload, a preset). A host can supply `loadSession`;
|
|
44
|
+
the agent then offers `session/load`, checks it exactly like `session/new` (the served directory, no
|
|
45
|
+
editor MCP servers or extra roots, workspace trust), refuses it while a prompt runs, and replays the
|
|
46
|
+
loaded conversation — what was typed, what the agent answered, and each finished tool call, a reused
|
|
47
|
+
call id getting a row of its own — before it answers. The port makes the stored session current in
|
|
48
|
+
the object the transport was attached to, so a host attaches a forwarder such as a session slot.
|
|
49
|
+
The published narrow `IAcpTransportSession` contract remains supported: command listing and history
|
|
50
|
+
display reads are optional capabilities. Loading is offered only with both the host port and the
|
|
51
|
+
history read, so a session cannot be replaced without replaying its conversation.
|
|
52
|
+
|
|
53
|
+
`robota acp` supplies the load: the editor may load a stored session of the folder it serves that
|
|
54
|
+
this version can read, and the agent replaces its session with it and shuts the old one down. A load
|
|
55
|
+
is refused while the current session has work in progress, and for a stored session with an active
|
|
56
|
+
goal, a live loop or a schedule that restore would re-arm to fire, which would otherwise run unseen
|
|
57
|
+
by the editor. With `--no-session-persistence` nothing is stored, so loading is not offered.
|
|
58
|
+
|
|
59
|
+
### Patch Changes
|
|
60
|
+
|
|
61
|
+
- Updated dependencies [c207a31]
|
|
62
|
+
- Updated dependencies [9b4f63b]
|
|
63
|
+
- Updated dependencies [7911a7d]
|
|
64
|
+
- Updated dependencies [23a30cd]
|
|
65
|
+
- @robota-sdk/agent-core@3.0.0-beta.97
|
|
66
|
+
- @robota-sdk/agent-transport@3.0.0-beta.97
|
|
67
|
+
- @robota-sdk/agent-interface-session@3.0.0-beta.97
|
|
68
|
+
- @robota-sdk/agent-interface-transport@3.0.0-beta.97
|
|
69
|
+
|
|
70
|
+
## 3.0.0-beta.96
|
|
71
|
+
### Patch Changes
|
|
72
|
+
|
|
73
|
+
- @robota-sdk/agent-core@3.0.0-beta.96
|
|
74
|
+
- @robota-sdk/agent-interface-session@3.0.0-beta.96
|
|
75
|
+
- @robota-sdk/agent-interface-transport@3.0.0-beta.96
|
|
76
|
+
- @robota-sdk/agent-transport@3.0.0-beta.96
|
|
77
|
+
|
|
3
78
|
## 3.0.0-beta.95
|
|
4
79
|
### Minor Changes
|
|
5
80
|
|
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 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. |
|
|
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
|
|
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. |
|
package/dist/node/index.cjs
CHANGED
|
@@ -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
|
|
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
|
|
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
|
|
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;
|
package/dist/node/index.d.cts
CHANGED
|
@@ -1,19 +1,28 @@
|
|
|
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.
|
|
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
|
-
*
|
|
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 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.
|
|
21
|
+
*/
|
|
22
|
+
interface IAcpEditorFileAccess {
|
|
23
|
+
readTextFile?(path: string, signal?: AbortSignal): Promise<string>;
|
|
24
|
+
writeTextFile?(path: string, content: string, signal?: AbortSignal): Promise<void>;
|
|
25
|
+
}
|
|
17
26
|
/** The host's answer to whether the workspace may serve an editor; a refusal carries what to tell the user. */
|
|
18
27
|
type TAcpWorkspaceAdmission = {
|
|
19
28
|
readonly admitted: true;
|
|
@@ -21,6 +30,13 @@ type TAcpWorkspaceAdmission = {
|
|
|
21
30
|
readonly admitted: false;
|
|
22
31
|
readonly message: string;
|
|
23
32
|
};
|
|
33
|
+
/** The host's answer to a `session/load`; a refusal carries what to tell the editor. */
|
|
34
|
+
type TAcpSessionLoad = {
|
|
35
|
+
readonly loaded: true;
|
|
36
|
+
} | {
|
|
37
|
+
readonly loaded: false;
|
|
38
|
+
readonly message: string;
|
|
39
|
+
};
|
|
24
40
|
interface IAcpTransportOptions {
|
|
25
41
|
/** Process-owned byte streams carrying newline-delimited JSON-RPC: the editor writes `input`, reads `output`. */
|
|
26
42
|
readonly input: Readable;
|
|
@@ -30,9 +46,9 @@ interface IAcpTransportOptions {
|
|
|
30
46
|
readonly name: string;
|
|
31
47
|
readonly version: string;
|
|
32
48
|
};
|
|
33
|
-
/** The directory the attached session is rooted at; `session/new` must name
|
|
49
|
+
/** The directory the attached session is rooted at; `session/new` and `session/load` must name it. */
|
|
34
50
|
readonly cwd: string;
|
|
35
|
-
/** The host's trust check, asked when the editor creates the session. */
|
|
51
|
+
/** The host's trust check, asked when the editor creates or loads the session. */
|
|
36
52
|
readonly admitWorkspace: () => TAcpWorkspaceAdmission;
|
|
37
53
|
/**
|
|
38
54
|
* Runs a `/command` prompt and returns the text to show. Supplied by the host because command
|
|
@@ -41,6 +57,21 @@ interface IAcpTransportOptions {
|
|
|
41
57
|
* cancels the prompt or the connection closes; pass it on so what the command starts stops with it.
|
|
42
58
|
*/
|
|
43
59
|
readonly executeCommand?: (text: string, signal: AbortSignal) => Promise<string>;
|
|
60
|
+
/**
|
|
61
|
+
* Makes the stored session `sessionId` current, so that the object this transport was attached to
|
|
62
|
+
* answers for it from then on (the host attaches a forwarder, such as a session slot, whose current
|
|
63
|
+
* session the port replaces). It must admit only a session rooted in the served directory, and may
|
|
64
|
+
* refuse with a message for the editor. Supplied by the host because building a session from
|
|
65
|
+
* storage is not this package's; without it or the session's history read, `session/load` is not offered.
|
|
66
|
+
*/
|
|
67
|
+
readonly loadSession?: (sessionId: string) => Promise<TAcpSessionLoad>;
|
|
68
|
+
/**
|
|
69
|
+
* Receives the editor's file system after a new or loaded session binds, when the editor offers reads or
|
|
70
|
+
* writes, and `undefined` when the transport stops. The host decides which of its tools use it;
|
|
71
|
+
* they must apply their own path and permission checks first, because the editor checks nothing
|
|
72
|
+
* for the agent. Never called for an editor that offers no file system.
|
|
73
|
+
*/
|
|
74
|
+
readonly onEditorFileAccess?: (access: IAcpEditorFileAccess | undefined) => void;
|
|
44
75
|
/** Attribution recorded on turns and prompt answers submitted through this transport. Default `'owner'`. */
|
|
45
76
|
readonly driverId?: TDriverId;
|
|
46
77
|
/** Usage surface recorded on turns. Default `'unknown'` (no editor value exists). */
|
|
@@ -59,5 +90,5 @@ interface IAcpTransport extends ITransportAdapter<IAcpTransportSession> {
|
|
|
59
90
|
//#region src/acp-transport.d.ts
|
|
60
91
|
declare function createAcpTransport(options: IAcpTransportOptions): IAcpTransport;
|
|
61
92
|
//#endregion
|
|
62
|
-
export { type IAcpTransport, type IAcpTransportOptions, type IAcpTransportSession, type TAcpWorkspaceAdmission, createAcpTransport };
|
|
93
|
+
export { type IAcpEditorFileAccess, type IAcpTransport, type IAcpTransportOptions, type IAcpTransportSession, type TAcpSessionLoad, type TAcpWorkspaceAdmission, createAcpTransport };
|
|
63
94
|
//# 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":"
|
|
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"}
|
package/dist/node/index.d.ts
CHANGED
|
@@ -1,19 +1,28 @@
|
|
|
1
1
|
import { Readable, Writable } from "node:stream";
|
|
2
|
-
import { ISessionEvents, ISessionPromptResolution, ISessionStatusRead, ISessionTurnControl, ISessionTurnSubmission, ISubmitOptions, TDriverId } from "@robota-sdk/agent-interface-session";
|
|
2
|
+
import { ISessionCommands, ISessionConversationRead, ISessionEvents, ISessionPromptResolution, ISessionStatusRead, ISessionTurnControl, ISessionTurnSubmission, ISubmitOptions, TDriverId } from "@robota-sdk/agent-interface-session";
|
|
3
3
|
import { ITransportAdapter } from "@robota-sdk/agent-interface-transport";
|
|
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.
|
|
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
|
-
*
|
|
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 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.
|
|
21
|
+
*/
|
|
22
|
+
interface IAcpEditorFileAccess {
|
|
23
|
+
readTextFile?(path: string, signal?: AbortSignal): Promise<string>;
|
|
24
|
+
writeTextFile?(path: string, content: string, signal?: AbortSignal): Promise<void>;
|
|
25
|
+
}
|
|
17
26
|
/** The host's answer to whether the workspace may serve an editor; a refusal carries what to tell the user. */
|
|
18
27
|
type TAcpWorkspaceAdmission = {
|
|
19
28
|
readonly admitted: true;
|
|
@@ -21,6 +30,13 @@ type TAcpWorkspaceAdmission = {
|
|
|
21
30
|
readonly admitted: false;
|
|
22
31
|
readonly message: string;
|
|
23
32
|
};
|
|
33
|
+
/** The host's answer to a `session/load`; a refusal carries what to tell the editor. */
|
|
34
|
+
type TAcpSessionLoad = {
|
|
35
|
+
readonly loaded: true;
|
|
36
|
+
} | {
|
|
37
|
+
readonly loaded: false;
|
|
38
|
+
readonly message: string;
|
|
39
|
+
};
|
|
24
40
|
interface IAcpTransportOptions {
|
|
25
41
|
/** Process-owned byte streams carrying newline-delimited JSON-RPC: the editor writes `input`, reads `output`. */
|
|
26
42
|
readonly input: Readable;
|
|
@@ -30,9 +46,9 @@ interface IAcpTransportOptions {
|
|
|
30
46
|
readonly name: string;
|
|
31
47
|
readonly version: string;
|
|
32
48
|
};
|
|
33
|
-
/** The directory the attached session is rooted at; `session/new` must name
|
|
49
|
+
/** The directory the attached session is rooted at; `session/new` and `session/load` must name it. */
|
|
34
50
|
readonly cwd: string;
|
|
35
|
-
/** The host's trust check, asked when the editor creates the session. */
|
|
51
|
+
/** The host's trust check, asked when the editor creates or loads the session. */
|
|
36
52
|
readonly admitWorkspace: () => TAcpWorkspaceAdmission;
|
|
37
53
|
/**
|
|
38
54
|
* Runs a `/command` prompt and returns the text to show. Supplied by the host because command
|
|
@@ -41,6 +57,21 @@ interface IAcpTransportOptions {
|
|
|
41
57
|
* cancels the prompt or the connection closes; pass it on so what the command starts stops with it.
|
|
42
58
|
*/
|
|
43
59
|
readonly executeCommand?: (text: string, signal: AbortSignal) => Promise<string>;
|
|
60
|
+
/**
|
|
61
|
+
* Makes the stored session `sessionId` current, so that the object this transport was attached to
|
|
62
|
+
* answers for it from then on (the host attaches a forwarder, such as a session slot, whose current
|
|
63
|
+
* session the port replaces). It must admit only a session rooted in the served directory, and may
|
|
64
|
+
* refuse with a message for the editor. Supplied by the host because building a session from
|
|
65
|
+
* storage is not this package's; without it or the session's history read, `session/load` is not offered.
|
|
66
|
+
*/
|
|
67
|
+
readonly loadSession?: (sessionId: string) => Promise<TAcpSessionLoad>;
|
|
68
|
+
/**
|
|
69
|
+
* Receives the editor's file system after a new or loaded session binds, when the editor offers reads or
|
|
70
|
+
* writes, and `undefined` when the transport stops. The host decides which of its tools use it;
|
|
71
|
+
* they must apply their own path and permission checks first, because the editor checks nothing
|
|
72
|
+
* for the agent. Never called for an editor that offers no file system.
|
|
73
|
+
*/
|
|
74
|
+
readonly onEditorFileAccess?: (access: IAcpEditorFileAccess | undefined) => void;
|
|
44
75
|
/** Attribution recorded on turns and prompt answers submitted through this transport. Default `'owner'`. */
|
|
45
76
|
readonly driverId?: TDriverId;
|
|
46
77
|
/** Usage surface recorded on turns. Default `'unknown'` (no editor value exists). */
|
|
@@ -59,5 +90,5 @@ interface IAcpTransport extends ITransportAdapter<IAcpTransportSession> {
|
|
|
59
90
|
//#region src/acp-transport.d.ts
|
|
60
91
|
declare function createAcpTransport(options: IAcpTransportOptions): IAcpTransport;
|
|
61
92
|
//#endregion
|
|
62
|
-
export { type IAcpTransport, type IAcpTransportOptions, type IAcpTransportSession, type TAcpWorkspaceAdmission, createAcpTransport };
|
|
93
|
+
export { type IAcpEditorFileAccess, type IAcpTransport, type IAcpTransportOptions, type IAcpTransportSession, type TAcpSessionLoad, type TAcpWorkspaceAdmission, createAcpTransport };
|
|
63
94
|
//# sourceMappingURL=index.d.ts.map
|
package/dist/node/index.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","names":[],"sources":["../../src/acp-types.ts","../../src/acp-transport.ts"],"mappings":"
|
|
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"}
|
package/dist/node/index.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
|
-
import{Readable as e}from"node:stream";import{PROTOCOL_VERSION as t,RequestError as n,agent as r,ndJsonStream as i}from"@agentclientprotocol/sdk";import{attachStdioCarrier as a}from"@robota-sdk/agent-transport/node";import{realpathSync as o}from"node:fs";import{isAbsolute as s,resolve as c}from"node:path";import{OWNER_DRIVER_ID as l,isTurnNotRunError as u}from"@robota-sdk/agent-interface-session";import{permissionSummaryArgument as d}from"@robota-sdk/agent-transport";function f(e){let t=Math.max(0,...[...e.matchAll(/`+/g)].map(e=>e[0].length));return"`".repeat(Math.max(3,t+1))}function
|
|
1
|
+
import{Readable as e}from"node:stream";import{PROTOCOL_VERSION as t,RequestError as n,agent as r,ndJsonStream as i}from"@agentclientprotocol/sdk";import{attachStdioCarrier as a}from"@robota-sdk/agent-transport/node";import{realpathSync as o}from"node:fs";import{isAbsolute as s,resolve as c}from"node:path";import{OWNER_DRIVER_ID as l,isTurnNotRunError as u}from"@robota-sdk/agent-interface-session";import{permissionSummaryArgument as d}from"@robota-sdk/agent-transport";function f(e){return e===void 0?void 0:{cancellationSignal:e}}function p(e){if(!(e instanceof n))return e instanceof Error?e:Error(String(e));let t=e.data,r=typeof t==`object`&&t&&`details`in t&&typeof t.details==`string`?t.details:void 0;return Error(r===void 0?e.message:`${e.message}: ${r}`)}function m(e){if(!s(e))throw Error(`the editor is given absolute paths only: ${e}`)}function h(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)=>{m(e);try{return(await t.request(`fs/read_text_file`,{sessionId:a(),path:e},f(n))).content}catch(e){throw p(e)}}}:{},...i?{writeTextFile:async(e,n,r)=>{m(e);try{await t.request(`fs/write_text_file`,{sessionId:a(),path:e,content:n},f(r))}catch(e){throw p(e)}}}:{}}}function g(e){let t=Math.max(0,...[...e.matchAll(/`+/g)].map(e=>e[0].length));return"`".repeat(Math.max(3,t+1))}function _(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=g(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
|
|
4
|
+
`)}`}}const v={Read:`read`,Glob:`search`,Grep:`search`,Edit:`edit`,Write:`edit`,Shell:`execute`,Bash:`execute`,WebFetch:`fetch`,WebSearch:`fetch`};function y(e){return v[e]??`other`}function b(e,t){let n=t?.replace(/\s+/g,` `).trim();return n?`${e}: ${n.length>120?`${n.slice(0,119)}…`:n}`:e}function x(e,t){let n=t?.trim();return n?`${e}: ${n}`:e}function S(e){return b(e.toolName,e.displayPath??e.firstArg)}function C(e){return e.result===`success`?`completed`:`failed`}function w(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 T(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:S(e),kind:y(e.toolName),status:C(e)})}}const E={allowOnce:`allow-once`,allowSession:`allow-session`,allowProject:`allow-project`,reject:`reject`};function D(e){return[{optionId:E.allowOnce,name:`Allow once`,kind:`allow_once`},{optionId:E.allowSession,name:`Allow for this session`,kind:`allow_always`},...e?[{optionId:E.allowProject,name:`Allow for this project`,kind:`allow_always`}]:[],{optionId:E.reject,name:`Reject`,kind:`reject_once`}]}function O(e){let t=x(e.toolName,e.diffFile??d(e.toolArgs));return e.requester?`${e.requester.label}: ${t}`:t}function k(e){return e.cwd===void 0||`cwd`in e.toolArgs?e.toolArgs:{...e.toolArgs,cwd:e.cwd}}const A=/^@@ /;function j(e){let t=[],n=[],r=`context`;for(let i of e){if(i.type===`hunk`){if(A.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
|
|
6
|
+
`)}}function M(e,t,n){let r=e.diffFile===void 0?void 0:c(n,e.diffFile);return{toolCallId:t,title:O(e),kind:y(e.toolName),status:`pending`,rawInput:k(e),...r!==void 0&&e.diffLines!==void 0?{content:[{type:`diff`,path:r,...j(e.diffLines)}]}:{},...r===void 0?{}:{locations:[{path:r}]}}}function N(e,t){if(e.outcome!==`selected`||!t.some(t=>t.optionId===e.optionId))return!1;switch(e.optionId){case E.allowOnce:return!0;case E.allowSession:return`allow-session`;case E.allowProject:return`allow-project`;default:return!1}}function P(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 F(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:S(r),kind:y(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=C(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:S(r),kind:y(r.toolName),status:i})},requested(t){let{executionId:a}=t;if(a===void 0){let n=M(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=M(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 I={stopReason:`cancelled`},L={stopReason:`end_turn`};function R(e){return e instanceof Error?e:Error(String(e))}async function z(e,t){let{session:n,sessionId:r,client:i,updates:a}=e,o=F({updates:a,nextToolCallId:e.nextToolCallId,cwd:e.cwd}),s=!1,c=P(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=D(t.canPersistProjectPermission===!0),s=o.requested(t);(async()=>{let c=!1;try{c=N((await i.request(`session/request_permission`,{sessionId:r,toolCall:s,options:a})).outcome,a)}catch(t){e.report(R(t),`session/request_permission`)}o.answered(t,c!==!1);try{n.resolvePermission(t.id,c,e.driverId)}catch(t){e.report(R(t),`resolvePermission`)}})()}});try{let n=await t.run();return e.wasCancelled()||e.signal.aborted||n.interrupted?I:(!s&&n.reply!==``&&a.text(n.reply),L)}catch(n){return e.wasCancelled()||e.signal.aborted||t.isCancellation?.(n)===!0?I:t.fail(n)}finally{c(),o.finish(),await a.drained()}}function B(e,t){return z(e,{async run(){let n=await(await e.session.submit(t,void 0,void 0,{signal:e.signal,driverId:e.driverId,...e.surface===void 0?{}:{surface:e.surface}})).completed;return{interrupted:n.interrupted===!0,reply:n.response}},isCancellation:e=>u(e)&&e.reason===`cancelled`,fail(t){throw e.report(R(t),`turn`),new n(-32603,u(t)?`Turn was not run`:`Turn failed`)}})}function V(e,t,r){return z(e,{async run(){return{interrupted:!1,reply:await r(t,e.signal)}},fail(t){throw e.report(R(t),`command`),new n(-32603,`Command failed`)}})}function H(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 U=-32603,W=()=>Promise.resolve(`Slash commands are not available in this host.`);function G(e,t){if(c(e)===c(t))return!0;try{return o(e)===o(t)}catch{return!1}}function K(e){return e instanceof Error?e:Error(String(e))}function q(e,i){let a=i.driverId??l,o=(e,t)=>i.onDeliveryError?.(e,t),c,u=!1,d,f=0,p=new Set,m=e=>{let t=e;for(;t===void 0||p.has(t);)t=`tool-${f+=1}`;return p.add(t),t},g,v=e=>{if(!s(e.cwd)||!G(e.cwd,i.cwd))throw n.invalidParams(void 0,`cwd must be the directory this agent was started in: ${i.cwd}`);if(e.mcpServers.length>0)throw n.invalidParams(void 0,`mcpServers are not accepted by this agent`);if((e.additionalDirectories??[]).length>0)throw n.invalidParams(void 0,`additionalDirectories are not accepted by this agent`);let t=i.admitWorkspace();if(!t.admitted)throw new n(-32001,t.message,{reason:`workspace-not-trusted`})},y=async t=>{try{return await e.whenInitialized?.(),e.getStatusSnapshot().sessionId}catch(e){throw o(K(e),t),new n(U,`The session could not be started`)}},b=(t,n)=>{if(e.listCommands===void 0&&e.listSkills===void 0)return;let r;try{r=w(e)}catch(e){o(K(e),`available_commands_update`);return}let i=JSON.stringify(r);i!==g&&(g=i,t.notify(`session/update`,{sessionId:n,update:{sessionUpdate:`available_commands_update`,availableCommands:r}}).catch(e=>o(K(e),`available_commands_update`)))},x=(e,t)=>{setImmediate(()=>b(e,t))},S,C=!1,E=e=>{let t=h(S,e,()=>c);t!==void 0&&(C=!0,i.onEditorFileAccess?.(t))};return{app:r({name:i.agentInfo.name}).onRequest(`initialize`,({params:n})=>(S=n.clientCapabilities?.fs,{protocolVersion:t,agentCapabilities:{loadSession:i.loadSession!==void 0&&e.getMessagesDisplay!==void 0,promptCapabilities:{image:!1,audio:!1,embeddedContext:!0}},agentInfo:{name:i.agentInfo.name,version:i.agentInfo.version}})).onRequest(`session/new`,async({params:e,client:t})=>{if(c!==void 0||u)throw n.invalidParams(void 0,`one session per connection`);v(e),u=!0;try{c=await y(`session/new`)}finally{u=!1}return E(t),x(t,c),{sessionId:c}}).onRequest(`session/load`,async({params:t,client:r})=>{let a=i.loadSession;if(a===void 0||e.getMessagesDisplay===void 0)throw n.methodNotFound(`session/load`);if(u)throw n.invalidParams(void 0,`a session is already being opened`);if(d!==void 0)throw n.invalidParams(void 0,`a prompt is running`);v(t),u=!0;try{let i;try{i=await a(t.sessionId)}catch(e){throw o(K(e),`session/load`),new n(U,`The session could not be loaded`)}if(!i.loaded)throw n.invalidParams(void 0,i.message);c=await y(`session/load`),E(r);let s=H(r,c,o);try{T(e.getMessagesDisplay(),s,m)}catch(e){throw o(K(e),`session/load`),new n(U,`The session history could not be shown`)}await s.drained()}finally{u=!1}return g=void 0,x(r,c),{}}).onRequest(`session/prompt`,async({params:t,signal:r,client:s})=>{if(t.sessionId!==c||u)throw n.invalidParams(void 0,`unknown session`);if(d!==void 0)throw n.invalidParams(void 0,`a prompt is already running`);let l=_(t.prompt);if(l.full.trim()===``)throw n.invalidParams(void 0,`the prompt has no text`);let f=new AbortController,p=!1;d={cancel(){p=!0,f.abort(),e.abort()}};let h={session:e,sessionId:t.sessionId,client:s,updates:H(s,t.sessionId,o),signal:AbortSignal.any([r,f.signal]),driverId:a,surface:i.surface??`unknown`,cwd:i.cwd,nextToolCallId:m,wasCancelled:()=>p,report:o};try{return l.typed.trimStart().startsWith(`/`)?await V(h,l.typed,i.executeCommand??W):await B(h,l.full)}finally{d=void 0,b(s,t.sessionId)}}).onNotification(`session/cancel`,({params:e})=>{e.sessionId===c&&d?.cancel()}),cancelActive:()=>d?.cancel(),withdrawEditorFiles:()=>{C&&(C=!1,i.onEditorFileAccess?.(void 0))}}}function J(e){return new WritableStream({write:t=>new Promise((n,r)=>{e.write(t,e=>e?r(e):n())})})}function Y(t){let n=null,r=!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(r||o)throw g(`already-started`);n=e,m()},async start(){if(!n)throw g(`not-attached`);if(r||o)throw g(`already-started`);r=!0;try{let r=q(n,t);l=r.cancelActive,u=r.withdrawEditorFiles;let o=a(t.input,t.output,{onInputClose:()=>d?.(),onOutputClose:()=>d?.(),onOutputError:e=>f?.(e)});s=o;let p=r.app.connect(i(J(t.output),e.toWeb(o.bridge)));c=p,p.closed.then(()=>d?.())}catch(e){throw h(),r=!1,e}},stop(){return o||(o=(async()=>{try{h()}finally{n=null,r=!1,d?.()}})().finally(()=>{o=null}),o)},waitForClose(){return p}}}export{Y as createAcpTransport};
|
|
7
7
|
//# sourceMappingURL=index.js.map
|