@gaunt-sloth/agent 2.0.0-alpha.8 → 2.0.0-beta.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (126) hide show
  1. package/README.md +73 -23
  2. package/cli-acp.js +21 -24
  3. package/dist/builtInToolsConfig.d.ts +17 -3
  4. package/dist/builtInToolsConfig.js +43 -41
  5. package/dist/builtInToolsConfig.js.map +1 -1
  6. package/dist/core/debugCapture.d.ts +7 -49
  7. package/dist/core/debugCapture.js +1 -1
  8. package/dist/core/debugCapture.js.map +1 -1
  9. package/dist/core/resolveAgentFactory.d.ts +10 -9
  10. package/dist/core/resolveAgentFactory.js +11 -12
  11. package/dist/core/resolveAgentFactory.js.map +1 -1
  12. package/dist/index.d.ts +4 -4
  13. package/dist/index.js +7 -5
  14. package/dist/index.js.map +1 -1
  15. package/dist/mcp/OAuthClientProviderImpl.js.map +1 -1
  16. package/dist/mcp/tlsTrust.d.ts +61 -0
  17. package/dist/mcp/tlsTrust.js +109 -0
  18. package/dist/mcp/tlsTrust.js.map +1 -0
  19. package/dist/middleware/binaryContentInjectionMiddleware.d.ts +8 -1
  20. package/dist/middleware/binaryContentInjectionMiddleware.js +11 -2
  21. package/dist/middleware/binaryContentInjectionMiddleware.js.map +1 -1
  22. package/dist/middleware/frontendImageInjectionMiddleware.d.ts +107 -0
  23. package/dist/middleware/frontendImageInjectionMiddleware.js +167 -0
  24. package/dist/middleware/frontendImageInjectionMiddleware.js.map +1 -0
  25. package/dist/middleware/registry.js +49 -1
  26. package/dist/middleware/registry.js.map +1 -1
  27. package/dist/middleware/types.d.ts +16 -2
  28. package/dist/modules/a2a/A2AClientWrapper.d.ts +56 -1
  29. package/dist/modules/a2a/A2AClientWrapper.js +131 -31
  30. package/dist/modules/a2a/A2AClientWrapper.js.map +1 -1
  31. package/dist/modules/acp/acpAgentApp.d.ts +57 -0
  32. package/dist/modules/acp/acpAgentApp.js +378 -0
  33. package/dist/modules/acp/acpAgentApp.js.map +1 -0
  34. package/dist/modules/acp/acpAgentAppV1.d.ts +52 -0
  35. package/dist/modules/acp/acpAgentAppV1.js +307 -0
  36. package/dist/modules/acp/acpAgentAppV1.js.map +1 -0
  37. package/dist/modules/acp/acpCommon.d.ts +167 -0
  38. package/dist/modules/acp/acpCommon.js +282 -0
  39. package/dist/modules/acp/acpCommon.js.map +1 -0
  40. package/dist/modules/acp/acpPermissions.d.ts +65 -0
  41. package/dist/modules/acp/acpPermissions.js +162 -0
  42. package/dist/modules/acp/acpPermissions.js.map +1 -0
  43. package/dist/modules/acp/acpPermissionsV1.d.ts +45 -0
  44. package/dist/modules/acp/acpPermissionsV1.js +110 -0
  45. package/dist/modules/acp/acpPermissionsV1.js.map +1 -0
  46. package/dist/modules/acp/acpRouter.d.ts +41 -0
  47. package/dist/modules/acp/acpRouter.js +48 -0
  48. package/dist/modules/acp/acpRouter.js.map +1 -0
  49. package/dist/modules/acp/acpStdio.d.ts +47 -0
  50. package/dist/modules/acp/acpStdio.js +71 -0
  51. package/dist/modules/acp/acpStdio.js.map +1 -0
  52. package/dist/modules/acp/acpToolCalls.d.ts +93 -0
  53. package/dist/modules/acp/acpToolCalls.js +193 -0
  54. package/dist/modules/acp/acpToolCalls.js.map +1 -0
  55. package/dist/modules/acp/acpUpdates.d.ts +48 -0
  56. package/dist/modules/acp/acpUpdates.js +142 -0
  57. package/dist/modules/acp/acpUpdates.js.map +1 -0
  58. package/dist/modules/acp/acpUpdatesV1.d.ts +61 -0
  59. package/dist/modules/acp/acpUpdatesV1.js +162 -0
  60. package/dist/modules/acp/acpUpdatesV1.js.map +1 -0
  61. package/dist/modules/apiAgUiModule.d.ts +87 -0
  62. package/dist/modules/apiAgUiModule.js +193 -28
  63. package/dist/modules/apiAgUiModule.js.map +1 -1
  64. package/dist/modules/interactiveSessionModule.js +497 -51
  65. package/dist/modules/interactiveSessionModule.js.map +1 -1
  66. package/dist/modules/slashCommands.d.ts +582 -0
  67. package/dist/modules/slashCommands.js +1001 -0
  68. package/dist/modules/slashCommands.js.map +1 -0
  69. package/dist/resolvers.js +119 -9
  70. package/dist/resolvers.js.map +1 -1
  71. package/dist/tools/GthCustomToolkit.js +95 -11
  72. package/dist/tools/GthCustomToolkit.js.map +1 -1
  73. package/dist/tools/GthDevToolkit.d.ts +8 -5
  74. package/dist/tools/GthDevToolkit.js +89 -43
  75. package/dist/tools/GthDevToolkit.js.map +1 -1
  76. package/dist/tools/GthFileSystemToolkit.d.ts +63 -0
  77. package/dist/tools/GthFileSystemToolkit.js +506 -105
  78. package/dist/tools/GthFileSystemToolkit.js.map +1 -1
  79. package/dist/tools/McpResourceTool.d.ts +31 -0
  80. package/dist/tools/McpResourceTool.js +106 -0
  81. package/dist/tools/McpResourceTool.js.map +1 -0
  82. package/dist/tools/gthChecklistTool.d.ts +30 -0
  83. package/dist/tools/gthChecklistTool.js +80 -0
  84. package/dist/tools/gthChecklistTool.js.map +1 -0
  85. package/dist/tools/gthGrepTool.d.ts +54 -0
  86. package/dist/tools/gthGrepTool.js +482 -0
  87. package/dist/tools/gthGrepTool.js.map +1 -0
  88. package/dist/tools/shell/env.js +1 -1
  89. package/dist/tools/shell/env.js.map +1 -1
  90. package/dist/tools/shell/outputBuffer.js.map +1 -1
  91. package/dist/tools/shell/workDir.d.ts +1 -10
  92. package/dist/tools/shell/workDir.js +10 -36
  93. package/dist/tools/shell/workDir.js.map +1 -1
  94. package/dist/utils/mcpAuthError.d.ts +36 -0
  95. package/dist/utils/mcpAuthError.js +89 -0
  96. package/dist/utils/mcpAuthError.js.map +1 -0
  97. package/dist/utils/mcpUtils.js +16 -0
  98. package/dist/utils/mcpUtils.js.map +1 -1
  99. package/package.json +13 -13
  100. package/dist/core/GthDeepAgent.d.ts +0 -181
  101. package/dist/core/GthDeepAgent.js +0 -682
  102. package/dist/core/GthDeepAgent.js.map +0 -1
  103. package/dist/core/deepAgentPermissions.d.ts +0 -115
  104. package/dist/core/deepAgentPermissions.js +0 -282
  105. package/dist/core/deepAgentPermissions.js.map +0 -1
  106. package/dist/core/gthAcpServer.d.ts +0 -27
  107. package/dist/core/gthAcpServer.js +0 -82
  108. package/dist/core/gthAcpServer.js.map +0 -1
  109. package/dist/core/gthDeepAgentFactory.d.ts +0 -8
  110. package/dist/core/gthDeepAgentFactory.js +0 -9
  111. package/dist/core/gthDeepAgentFactory.js.map +0 -1
  112. package/dist/modules/acpModule.d.ts +0 -40
  113. package/dist/modules/acpModule.js +0 -78
  114. package/dist/modules/acpModule.js.map +0 -1
  115. package/dist/tools/shell/allowlist.d.ts +0 -11
  116. package/dist/tools/shell/allowlist.js +0 -12
  117. package/dist/tools/shell/allowlist.js.map +0 -1
  118. package/dist/tools/shell/arity.d.ts +0 -11
  119. package/dist/tools/shell/arity.js +0 -12
  120. package/dist/tools/shell/arity.js.map +0 -1
  121. package/dist/tools/shell/hardline.d.ts +0 -15
  122. package/dist/tools/shell/hardline.js +0 -88
  123. package/dist/tools/shell/hardline.js.map +0 -1
  124. package/dist/tools/shell/normalize.d.ts +0 -10
  125. package/dist/tools/shell/normalize.js +0 -11
  126. package/dist/tools/shell/normalize.js.map +0 -1
@@ -0,0 +1,378 @@
1
+ /**
2
+ * @packageDocumentation
3
+ * Gaunt Sloth's ACP (Agent Client Protocol) **v2** agent, built on the official SDK's
4
+ * `experimental/v2` surface.
5
+ *
6
+ * ## One of two dialects
7
+ *
8
+ * v1 is ACP's stable protocol and v2 is a draft, and the agent serves **both**: `acpRouter.ts`
9
+ * reads the `protocolVersion` on the first `initialize` and hands the connection to the matching
10
+ * app, so a host gets the dialect it asked for. This file is the v2 half; `acpAgentAppV1.ts` is
11
+ * the v1 half. Serving only v2 would cut the surface off from every shipping editor that speaks
12
+ * ACP today — measured against Zed 1.15.0 stable, which sends `protocolVersion: 1` as a literal
13
+ * with no setting to change it.
14
+ *
15
+ * ## Shape
16
+ *
17
+ * v2's agent side is a fluent app builder: `acp.agent()` returns an {@link AgentApp} on which
18
+ * typed handlers are registered by ACP method name, and `connect()` serves a client — either over
19
+ * a transport stream or, for tests, directly against a `ClientApp`. The SDK's v1 entry point offers
20
+ * the same builder, which is why the two dialects read alike even though their handlers differ.
21
+ *
22
+ * ## The prompt lifecycle is the part that looks wrong until you read the spec
23
+ *
24
+ * This is also the sharpest difference from v1, which answers the prompt request with the stop
25
+ * reason itself. In v2 `session/prompt` returns `{}` **as soon as the prompt is accepted** — not
26
+ * when the turn finishes.
27
+ * Progress, tool calls and the final stop reason all arrive as `session/update` notifications, and
28
+ * the turn is over when an idle `state_update` carrying a stop reason lands. That is what makes
29
+ * cancellation expressible at all: a cancelled turn reports `cancelled` on a `state_update`, while
30
+ * the prompt request it belongs to was answered long before. A client that waited on the prompt
31
+ * response for the result would hang through every tool call.
32
+ *
33
+ * ## Why a runner and not an agent
34
+ *
35
+ * Each session drives a {@link GthAgentRunner}, not a bare agent. The runner is where the
36
+ * tool-approval gate lives, so this is also the surface where `session/request_permission` is
37
+ * wired — see `acpPermissions.ts` for why that is not optional.
38
+ *
39
+ * ## One workspace per process
40
+ *
41
+ * Config discovery, the filesystem tools' allowed root, grep and the shell tool ALL resolve
42
+ * through `getCurrentWorkDir()`, a process-global. So the workspace is bound by the first
43
+ * `session/new` and a later `session/new` naming a different `cwd` is refused rather than served
44
+ * from the wrong root. An editor spawns one agent process per project, which is exactly the shape
45
+ * this serves; the alternative — letting the second session silently re-root the first session's
46
+ * tools — is the kind of quiet wrongness that is worse than an error a host can report.
47
+ */
48
+ import * as acp from '@agentclientprotocol/sdk/experimental/v2';
49
+ import { randomUUID } from 'node:crypto';
50
+ import { resolve as resolvePath } from 'node:path';
51
+ import { HumanMessage } from '@langchain/core/messages';
52
+ import { MemorySaver } from '@langchain/langgraph';
53
+ import { GthAgentRunner } from '@gaunt-sloth/core/core/GthAgentRunner.js';
54
+ import { displayWarning } from '@gaunt-sloth/core/utils/consoleUtils.js';
55
+ import { createResolvers } from '#src/resolvers.js';
56
+ import { resolveAgentFactory } from '#src/core/resolveAgentFactory.js';
57
+ import { AcpUpdateMapper } from '#src/modules/acp/acpUpdates.js';
58
+ import { decisionForOutcome, permissionRequestFor } from '#src/modules/acp/acpPermissions.js';
59
+ import { ACP_AGENT_NAME, ACP_AGENT_TITLE, CLOSE_TURN_DRAIN_MS, acpStatusCallback, agentVersion, announceAcpStart, drainWithDeadline, isSameWorkspace, loadConfigForCwd, promptText, resolveAcpSessionCommand, } from '#src/modules/acp/acpCommon.js';
60
+ export { ACP_AGENT_NAME, ACP_AGENT_TITLE, announceAcpStart, isSameWorkspace };
61
+ /**
62
+ * Builds the ACP v2 agent app. Register-and-return only: nothing is connected and no config is
63
+ * read until a client connects and creates a session.
64
+ */
65
+ export function createAcpAgentApp(options = {}) {
66
+ const loadConfig = options.loadConfig ?? loadConfigForCwd;
67
+ const agentFactoryFor = options.agentFactory ?? ((config) => resolveAgentFactory(config, 'lean'));
68
+ const sessions = new Map();
69
+ /** The workspace this process is serving, bound by the first `session/new`. */
70
+ let workspaceRoot;
71
+ /**
72
+ * `session/new` requests that have claimed the workspace and are still building their session.
73
+ *
74
+ * Counted so a release can tell "nothing depends on this binding" from "the session that depends
75
+ * on it does not exist yet". Without it, one `session/new` failing while a concurrent one is still
76
+ * loading would clear the root out from under the survivor, and a third request could then bind a
77
+ * different directory — the same race the claim closes, reopened by the cleanup.
78
+ */
79
+ let pendingSessions = 0;
80
+ /**
81
+ * Release the workspace binding when nothing — live or in flight — still depends on it.
82
+ *
83
+ * The binding exists to stop a second workspace re-rooting a LIVE session's tools; with no session
84
+ * and nothing building one there is nothing to re-root, and holding it would refuse a workspace
85
+ * for no reason left.
86
+ */
87
+ const releaseWorkspaceIfIdle = () => {
88
+ if (sessions.size === 0 && pendingSessions === 0)
89
+ workspaceRoot = undefined;
90
+ };
91
+ const sessionOrThrow = (sessionId) => {
92
+ const session = sessions.get(sessionId);
93
+ if (!session) {
94
+ throw acp.RequestError.invalidParams({ sessionId }, `No such session: ${sessionId}. Create one with session/new.`);
95
+ }
96
+ return session;
97
+ };
98
+ /** Send one `session/update`. Notifications, so nothing here waits on the client. */
99
+ const sendUpdate = async (session, update) => {
100
+ await session.client.notify(acp.CLIENT_METHODS.session_update, {
101
+ sessionId: session.sessionId,
102
+ update,
103
+ });
104
+ };
105
+ const sendState = async (session, state) => {
106
+ await sendUpdate(session, { sessionUpdate: 'state_update', ...state });
107
+ };
108
+ /**
109
+ * Runs one prompt turn to completion and reports it entirely through notifications.
110
+ *
111
+ * Never rejects: this is started detached from the `session/prompt` request (which has already
112
+ * been answered), so a throw here would be an unhandled rejection rather than an error anyone
113
+ * sees. Every exit — success, cancellation, failure — ends on an idle `state_update` with a stop
114
+ * reason, because that notification is the ONLY thing that tells a client the turn is over.
115
+ */
116
+ const runTurn = async (session, prompt) => {
117
+ const mapper = new AcpUpdateMapper();
118
+ const abort = new AbortController();
119
+ session.abort = abort;
120
+ session.cancelled = false;
121
+ const text = promptText(prompt);
122
+ const userMessageId = randomUUID();
123
+ /** Assistant text accumulated per message id, so the replay log can carry whole messages. */
124
+ const assistantText = new Map();
125
+ let stopReason = 'end_turn';
126
+ try {
127
+ // The spec's step 2: after accepting the prompt, report where the user message landed.
128
+ const userMessage = {
129
+ sessionUpdate: 'user_message',
130
+ messageId: userMessageId,
131
+ content: prompt,
132
+ };
133
+ session.replayLog.push(userMessage);
134
+ await sendUpdate(session, userMessage);
135
+ await sendState(session, { state: 'running' });
136
+ // The gate's last hop. Registered per turn so it closes over THIS turn's mapper, which is
137
+ // what knows the id of the tool call a permission request is about.
138
+ session.runner.setToolApprovalCallback(async (pending) => {
139
+ await sendState(session, { state: 'requires_action' }).catch(() => undefined);
140
+ try {
141
+ const response = await session.client.request(acp.CLIENT_METHODS.session_request_permission, permissionRequestFor({
142
+ sessionId: session.sessionId,
143
+ pending,
144
+ toolCallId: mapper.claimToolCallId(pending.name, pending.args),
145
+ cwd: session.cwd,
146
+ }),
147
+ // The spec's cancellation cascade: when the turn is cancelled, outstanding permission
148
+ // requests are cancelled too, so the client can take its prompt off the screen instead
149
+ // of asking about work that has already stopped. Cooperative by design — the promise
150
+ // still settles on the peer's eventual answer — which is why `session/close` bounds its
151
+ // wait rather than trusting this to end it.
152
+ { cancellationSignal: abort.signal });
153
+ return decisionForOutcome(response.outcome);
154
+ }
155
+ finally {
156
+ // Never allowed to become the callback's result. A notification that fails — the usual
157
+ // reason being the connection going away mid-prompt — must not turn a decision the human
158
+ // actually made into an error the runner records as a failed gate.
159
+ await sendState(session, { state: 'running' }).catch(() => undefined);
160
+ }
161
+ });
162
+ for await (const event of session.runner.processMessagesWithEvents([new HumanMessage(text)], abort.signal)) {
163
+ for (const update of mapper.map(event)) {
164
+ if (update.sessionUpdate === 'agent_message_chunk') {
165
+ const chunk = update;
166
+ assistantText.set(chunk.messageId, (assistantText.get(chunk.messageId) ?? '') + (chunk.content.text ?? ''));
167
+ }
168
+ if (update.sessionUpdate === 'tool_call_update') {
169
+ session.replayLog.push(update);
170
+ }
171
+ await sendUpdate(session, update);
172
+ }
173
+ }
174
+ if (session.cancelled || abort.signal.aborted)
175
+ stopReason = 'cancelled';
176
+ }
177
+ catch (error) {
178
+ // A cancelled run surfaces as a thrown abort from whatever library noticed the signal first.
179
+ // The spec is explicit that this must NOT reach the client as a generic failure, or a client
180
+ // shows an error for something the user asked for.
181
+ if (session.cancelled || abort.signal.aborted) {
182
+ stopReason = 'cancelled';
183
+ }
184
+ else {
185
+ // NOT `refusal`, which the spec defines as the agent declining to continue — a crash is
186
+ // not a refusal, and a client would render it as one. v2 reserves custom stop reasons to
187
+ // names beginning with an underscore, so this is the sanctioned way to say "stopped, and
188
+ // none of the defined reasons is what happened".
189
+ stopReason = '_error';
190
+ const message = error instanceof Error ? error.message : String(error);
191
+ displayWarning(`ACP session ${session.sessionId}: turn failed — ${message}`);
192
+ await sendUpdate(session, {
193
+ sessionUpdate: 'agent_message',
194
+ messageId: randomUUID(),
195
+ content: [{ type: 'text', text: `The agent could not complete this turn: ${message}` }],
196
+ }).catch(() => {
197
+ /* the connection is already gone; the idle state below is still attempted */
198
+ });
199
+ }
200
+ }
201
+ finally {
202
+ session.runner.setToolApprovalCallback(null);
203
+ session.abort = null;
204
+ // `turn` is deliberately NOT cleared here: this runs inside the promise it holds, so clearing
205
+ // it would let a `session/close` that reads the field in that instant skip the wait entirely.
206
+ // A settled promise is harmless to await and the next prompt replaces it.
207
+ for (const [messageId, content] of assistantText) {
208
+ if (content.length > 0) {
209
+ session.replayLog.push({
210
+ sessionUpdate: 'agent_message',
211
+ messageId,
212
+ content: [{ type: 'text', text: content }],
213
+ });
214
+ }
215
+ }
216
+ // The turn is over only once this lands, whatever happened above.
217
+ await sendState(session, { state: 'idle', stopReason }).catch(() => {
218
+ /* connection closed mid-turn; nothing left to report to */
219
+ });
220
+ }
221
+ };
222
+ const closeSession = async (session) => {
223
+ session.closed = true;
224
+ session.cancelled = true;
225
+ session.abort?.abort();
226
+ sessions.delete(session.sessionId);
227
+ // Wait for the aborted turn to finish unwinding before anything else. `cleanup()` does not do
228
+ // this — it drops the agent and returns — so releasing the workspace below without the wait
229
+ // would let a `session/new` for another directory reassign INIT_CWD out from under a turn still
230
+ // running its last tool call. That is precisely the "re-rooting a live session's tools" hazard
231
+ // the binding exists to prevent, so the release must not be the thing that opens it.
232
+ //
233
+ // BOUNDED, for the reason {@link CLOSE_TURN_DRAIN_MS} gives: a turn parked on a permission
234
+ // request is waiting on the client, and no abort of ours can force that to settle.
235
+ await drainWithDeadline(session.turn, CLOSE_TURN_DRAIN_MS);
236
+ try {
237
+ await session.runner.cleanup();
238
+ }
239
+ catch (error) {
240
+ displayWarning(`ACP session ${session.sessionId}: cleanup failed — ${error instanceof Error ? error.message : String(error)}`);
241
+ }
242
+ releaseWorkspaceIfIdle();
243
+ };
244
+ return acp
245
+ .agent({ name: ACP_AGENT_NAME })
246
+ .onRequest(acp.AGENT_METHODS.initialize, () => ({
247
+ protocolVersion: acp.PROTOCOL_VERSION,
248
+ info: { name: ACP_AGENT_NAME, title: ACP_AGENT_TITLE, version: agentVersion() },
249
+ // `session: {}` claims the baseline session surface and nothing more. Per the v2
250
+ // initialization spec the baseline is `session/new`, `session/list`, `session/resume`,
251
+ // `session/close`, `session/prompt`, `session/cancel` and `session/update` — which is
252
+ // exactly the set of handlers registered below. (The SDK's own type doc for this field
253
+ // enumerates a shorter baseline; the protocol doc is the authority, and the handlers here
254
+ // match it.)
255
+ //
256
+ // Nothing beyond it is advertised, because every capability is a promise a client will hold
257
+ // us to: image and audio prompt content, MCP server transports, `session/delete`,
258
+ // `session/fork` and `additionalDirectories` are the things this agent does not serve, so it
259
+ // does not claim them.
260
+ capabilities: { session: {} },
261
+ }))
262
+ .onRequest(acp.AGENT_METHODS.session_new, async ({ params, client }) => {
263
+ const cwd = resolvePath(params.cwd);
264
+ if (workspaceRoot !== undefined && !isSameWorkspace(workspaceRoot, cwd)) {
265
+ throw acp.RequestError.invalidParams({ cwd: params.cwd, workspaceRoot }, `This agent process is serving ${workspaceRoot}. Start a separate agent process for ${cwd}.`);
266
+ }
267
+ // **Claim the workspace BEFORE the first await, or the check above decides nothing.** Nothing
268
+ // serialises inbound requests on a stdio connection, so two `session/new` calls naming
269
+ // different directories both saw an unset root, both suspended on the config load, and both
270
+ // assigned — leaving two live sessions in different workspaces, which is the exact state the
271
+ // refusal exists to prevent. Testing and assigning either side of an await is not a check.
272
+ workspaceRoot = cwd;
273
+ pendingSessions += 1;
274
+ try {
275
+ const config = await loadConfig(cwd);
276
+ const runner = new GthAgentRunner(acpStatusCallback, options.resolvers ?? createResolvers(), agentFactoryFor(config));
277
+ // The command a run is resolved under decides its toolset, its mode prompt and its
278
+ // approvals posture. For an editor session that is `acp.mode`, defaulting to `code`.
279
+ //
280
+ // **The checkpoint saver is not optional here, even though `init` accepts none.** A gated
281
+ // tool call suspends the graph on a LangGraph interrupt, and an interrupt with nowhere to
282
+ // checkpoint throws `MISSING_CHECKPOINTER` instead of asking the client for permission — so
283
+ // without one every shell command in an editor session fails while the ungated built-ins
284
+ // keep working. `MemorySaver` is the right lifetime as well as the right shape: these
285
+ // sessions live in one process and die with it.
286
+ //
287
+ // **Per session, not per process.** Each session owns its runner, its workspace and the
288
+ // graphs suspended in it, and `sessions` holds several at once; one shared saver would pool
289
+ // every session's checkpoint threads, so a `session/close` could clear state another
290
+ // session is still parked on.
291
+ await runner.init(resolveAcpSessionCommand(config), config, new MemorySaver());
292
+ const sessionId = randomUUID();
293
+ sessions.set(sessionId, {
294
+ sessionId,
295
+ cwd,
296
+ runner,
297
+ client,
298
+ abort: null,
299
+ turn: null,
300
+ closed: false,
301
+ cancelled: false,
302
+ replayLog: [],
303
+ });
304
+ return { sessionId };
305
+ }
306
+ finally {
307
+ pendingSessions -= 1;
308
+ // A `session/new` that never produced a session must not leave the process bound to its
309
+ // directory — one bad config would otherwise refuse every later session for the life of the
310
+ // agent. Released on the same condition `closeSession` uses, and counting the in-flight
311
+ // requests as well as the live ones: a concurrent `session/new` that has claimed the root
312
+ // and is still loading is exactly as much a reason to hold it as a session already running.
313
+ releaseWorkspaceIfIdle();
314
+ }
315
+ })
316
+ .onRequest(acp.AGENT_METHODS.session_list, ({ params }) => {
317
+ const filter = params.cwd === undefined || params.cwd === null ? undefined : params.cwd;
318
+ return {
319
+ sessions: [...sessions.values()]
320
+ .filter((session) => filter === undefined || session.cwd === resolvePath(filter))
321
+ .map((session) => ({ sessionId: session.sessionId, cwd: session.cwd })),
322
+ };
323
+ })
324
+ .onRequest(acp.AGENT_METHODS.session_resume, async ({ params }) => {
325
+ const session = sessionOrThrow(params.sessionId);
326
+ // Same comparison as the workspace binding, for the same reason: on win32 a client re-sending
327
+ // its own cwd with different casing names the same directory.
328
+ if (!isSameWorkspace(resolvePath(params.cwd), session.cwd)) {
329
+ throw acp.RequestError.invalidParams({ cwd: params.cwd, sessionCwd: session.cwd }, `Session ${session.sessionId} belongs to ${session.cwd}.`);
330
+ }
331
+ // `replayFrom` is an INCLUSIVE cursor and the only cursor type in v2 is `start`, so a request
332
+ // that carries one asks for the whole conversation and anything else asks for none of it.
333
+ // Replayed as message-level updates rather than as the original chunk stream: a client
334
+ // rebuilding a transcript wants the messages, and re-emitting thousands of one-token chunks
335
+ // to say the same thing would be a worse answer to the same question.
336
+ if (params.replayFrom?.type === 'start') {
337
+ for (const update of session.replayLog)
338
+ await sendUpdate(session, update);
339
+ }
340
+ return {};
341
+ })
342
+ .onRequest(acp.AGENT_METHODS.session_close, async ({ params }) => {
343
+ // Closing MUST cancel whatever is running, exactly as session/cancel would, before the
344
+ // session's resources go away.
345
+ await closeSession(sessionOrThrow(params.sessionId));
346
+ return {};
347
+ })
348
+ .onRequest(acp.AGENT_METHODS.session_prompt, ({ params }) => {
349
+ const session = sessionOrThrow(params.sessionId);
350
+ if (session.abort) {
351
+ throw acp.RequestError.invalidRequest({ sessionId: params.sessionId }, 'A prompt is already running in this session. Wait for the idle state update.');
352
+ }
353
+ // Detached ON PURPOSE, and after this handler's response: the response only acknowledges
354
+ // acceptance, and the turn reports itself through notifications. `setImmediate` (rather than
355
+ // starting it inline) is what keeps the acknowledgement first on the wire — a client that
356
+ // saw a `session/update` for a prompt it had not yet been told was accepted would be right
357
+ // to treat it as a protocol error.
358
+ setImmediate(() => {
359
+ // A close that landed in the gap between accepting the prompt and this tick wins: starting
360
+ // the turn now would run it against a session already torn down, and nothing would be
361
+ // waiting for it.
362
+ if (session.closed)
363
+ return;
364
+ session.turn = runTurn(session, params.prompt);
365
+ });
366
+ return {};
367
+ })
368
+ .onNotification(acp.AGENT_METHODS.session_cancel, ({ params }) => {
369
+ const session = sessions.get(params.sessionId);
370
+ if (!session)
371
+ return;
372
+ // Recorded as well as aborted: the abort makes the run stop, and the flag is what makes the
373
+ // turn report `cancelled` instead of reading its own truncated stream as a normal finish.
374
+ session.cancelled = true;
375
+ session.abort?.abort();
376
+ });
377
+ }
378
+ //# sourceMappingURL=acpAgentApp.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"acpAgentApp.js","sourceRoot":"","sources":["../../../src/modules/acp/acpAgentApp.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,52 @@
1
+ /**
2
+ * @packageDocumentation
3
+ * Gaunt Sloth's ACP (Agent Client Protocol) **v1** agent, built on the official SDK's stable root
4
+ * export. (`acpAgentApp.ts` is the v2 half; `acpRouter.ts` picks between them.)
5
+ *
6
+ * ## Why v1 is served at all
7
+ *
8
+ * v1 is ACP's stable protocol; v2 is a draft. Every shipping editor that speaks ACP today speaks
9
+ * v1 — measured against Zed 1.15.0 stable, whose `initialize` carries `protocolVersion: 1` as a
10
+ * literal with no setting to change it, and whose build does not compile the draft-v2 API at all.
11
+ * An agent that serves only v2 cannot open a session with any of them.
12
+ *
13
+ * ## The shape is the same; the semantics are not
14
+ *
15
+ * The SDK's v1 entry point offers the same fluent app builder as v2 — `acp.agent()` returns an
16
+ * `AgentApp`, handlers are registered by ACP method name, and `connect()` serves either a transport
17
+ * or, in tests, a `ClientApp` directly. That similarity is a trap if it is mistaken for sameness.
18
+ * Four differences decide what this file does, and none of them can be reached by renaming a v2
19
+ * handler:
20
+ *
21
+ * 1. **`session/prompt` answers with the stop reason.** The turn runs INSIDE the request and the
22
+ * response is what tells the client it is over. v2 acknowledges immediately and reports the stop
23
+ * reason later on an idle `state_update`, which does not exist here.
24
+ * 2. **A cancelled turn is a `cancelled` stop reason, never an error.** The v1 spec is explicit
25
+ * that agents MUST catch the abort their libraries throw and answer with the stop reason,
26
+ * because clients show unrecognized errors to the user and a cancellation is not one.
27
+ * 3. **A FAILED turn is a JSON-RPC error**, because v1's `StopReason` union is closed —
28
+ * `end_turn | max_tokens | max_turn_requests | refusal | cancelled` — and none of them means
29
+ * "crashed". (This is exactly why v2 needed a custom `_error` reason: there, the response was
30
+ * already sent and the notification stream was the only channel left. Here the request is still
31
+ * open, so the error belongs in its response, and answering `end_turn` would be a lie.)
32
+ * 4. **No `session/load`, and no `loadSession` capability.** In v1 that capability means sessions
33
+ * survive restarts and can be picked up by another client instance; these sessions live in one
34
+ * process and die with it. Clients MUST NOT call `session/load` unless it is advertised, so
35
+ * declining to advertise it is the conforming way to say so — and the same goes for
36
+ * `sessionCapabilities.resume`. Claiming either would be a promise this agent cannot keep.
37
+ *
38
+ * ## What carries over unchanged, because it must
39
+ *
40
+ * `session/request_permission` is wired to the approval gate, so [[EXT-54]]'s hole does not reopen
41
+ * on a second surface; and the process serves ONE workspace, refusing a `session/new` that names a
42
+ * different directory rather than silently re-rooting a live session's file tools. Both are
43
+ * properties of this agent rather than of a protocol version, so both hold here exactly as they do
44
+ * on v2.
45
+ */
46
+ import * as acp from '@agentclientprotocol/sdk';
47
+ import type { AcpAgentAppOptions } from '#src/modules/acp/acpCommon.js';
48
+ /**
49
+ * Builds the ACP v1 agent app. Register-and-return only: nothing is connected and no config is
50
+ * read until a client connects and creates a session.
51
+ */
52
+ export declare function createAcpV1AgentApp(options?: AcpAgentAppOptions): acp.AgentApp;