@gaunt-sloth/agent 2.0.0-alpha.37 → 2.0.0-alpha.39

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 (43) hide show
  1. package/README.md +6 -5
  2. package/cli-acp.js +5 -4
  3. package/dist/builtInToolsConfig.js +7 -4
  4. package/dist/builtInToolsConfig.js.map +1 -1
  5. package/dist/index.d.ts +2 -0
  6. package/dist/index.js +4 -1
  7. package/dist/index.js.map +1 -1
  8. package/dist/modules/acp/acpAgentApp.d.ts +16 -52
  9. package/dist/modules/acp/acpAgentApp.js +31 -246
  10. package/dist/modules/acp/acpAgentApp.js.map +1 -1
  11. package/dist/modules/acp/acpAgentAppV1.d.ts +52 -0
  12. package/dist/modules/acp/acpAgentAppV1.js +307 -0
  13. package/dist/modules/acp/acpAgentAppV1.js.map +1 -0
  14. package/dist/modules/acp/acpCommon.d.ts +167 -0
  15. package/dist/modules/acp/acpCommon.js +282 -0
  16. package/dist/modules/acp/acpCommon.js.map +1 -0
  17. package/dist/modules/acp/acpPermissions.d.ts +6 -1
  18. package/dist/modules/acp/acpPermissions.js +5 -1
  19. package/dist/modules/acp/acpPermissions.js.map +1 -1
  20. package/dist/modules/acp/acpPermissionsV1.d.ts +45 -0
  21. package/dist/modules/acp/acpPermissionsV1.js +110 -0
  22. package/dist/modules/acp/acpPermissionsV1.js.map +1 -0
  23. package/dist/modules/acp/acpRouter.d.ts +41 -0
  24. package/dist/modules/acp/acpRouter.js +48 -0
  25. package/dist/modules/acp/acpRouter.js.map +1 -0
  26. package/dist/modules/acp/acpStdio.d.ts +9 -3
  27. package/dist/modules/acp/acpStdio.js +11 -5
  28. package/dist/modules/acp/acpStdio.js.map +1 -1
  29. package/dist/modules/acp/acpToolCalls.d.ts +93 -0
  30. package/dist/modules/acp/acpToolCalls.js +193 -0
  31. package/dist/modules/acp/acpToolCalls.js.map +1 -0
  32. package/dist/modules/acp/acpUpdates.d.ts +7 -60
  33. package/dist/modules/acp/acpUpdates.js +10 -155
  34. package/dist/modules/acp/acpUpdates.js.map +1 -1
  35. package/dist/modules/acp/acpUpdatesV1.d.ts +61 -0
  36. package/dist/modules/acp/acpUpdatesV1.js +162 -0
  37. package/dist/modules/acp/acpUpdatesV1.js.map +1 -0
  38. package/dist/modules/interactiveSessionModule.js +101 -97
  39. package/dist/modules/interactiveSessionModule.js.map +1 -1
  40. package/dist/modules/slashCommands.d.ts +8 -2
  41. package/dist/modules/slashCommands.js +8 -2
  42. package/dist/modules/slashCommands.js.map +1 -1
  43. package/package.json +2 -2
@@ -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;
@@ -0,0 +1,307 @@
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 { randomUUID } from 'node:crypto';
48
+ import { resolve as resolvePath } from 'node:path';
49
+ import { HumanMessage } from '@langchain/core/messages';
50
+ import { MemorySaver } from '@langchain/langgraph';
51
+ import { GthAgentRunner } from '@gaunt-sloth/core/core/GthAgentRunner.js';
52
+ import { displayWarning } from '@gaunt-sloth/core/utils/consoleUtils.js';
53
+ import { createResolvers } from '#src/resolvers.js';
54
+ import { resolveAgentFactory } from '#src/core/resolveAgentFactory.js';
55
+ import { AcpV1UpdateMapper } from '#src/modules/acp/acpUpdatesV1.js';
56
+ import { decisionForOutcome } from '#src/modules/acp/acpPermissions.js';
57
+ import { permissionRequestForV1 } from '#src/modules/acp/acpPermissionsV1.js';
58
+ import { ACP_AGENT_NAME, ACP_AGENT_TITLE, CLOSE_TURN_DRAIN_MS, acpStatusCallback, agentVersion, drainWithDeadline, isSameWorkspace, loadConfigForCwd, promptText, resolveAcpSessionCommand, } from '#src/modules/acp/acpCommon.js';
59
+ /**
60
+ * Resolves with the `cancelled` permission outcome once `signal` aborts, and never otherwise.
61
+ *
62
+ * Used to race a permission request that the client may never answer. Resolving rather than
63
+ * rejecting is deliberate: the caller turns this into a gate DECISION, and a rejection there would
64
+ * be recorded as the gate failing rather than as the user's turn being stopped.
65
+ */
66
+ function cancelledWhenAborted(signal) {
67
+ const cancelled = { outcome: 'cancelled' };
68
+ if (signal.aborted)
69
+ return Promise.resolve(cancelled);
70
+ return new Promise((resolveCancelled) => {
71
+ signal.addEventListener('abort', () => resolveCancelled(cancelled), { once: true });
72
+ });
73
+ }
74
+ /**
75
+ * Builds the ACP v1 agent app. Register-and-return only: nothing is connected and no config is
76
+ * read until a client connects and creates a session.
77
+ */
78
+ export function createAcpV1AgentApp(options = {}) {
79
+ const loadConfig = options.loadConfig ?? loadConfigForCwd;
80
+ const agentFactoryFor = options.agentFactory ?? ((config) => resolveAgentFactory(config, 'lean'));
81
+ const sessions = new Map();
82
+ /** The workspace this process is serving, bound by the first `session/new`. */
83
+ let workspaceRoot;
84
+ /**
85
+ * `session/new` requests that have claimed the workspace and are still building their session.
86
+ *
87
+ * Counted so a release can tell "nothing depends on this binding" from "the session that depends
88
+ * on it does not exist yet". Without it, one `session/new` failing while a concurrent one is still
89
+ * loading would clear the root out from under the survivor, and a third request could then bind a
90
+ * different directory — the same race the claim closes, reopened by the cleanup.
91
+ */
92
+ let pendingSessions = 0;
93
+ const releaseWorkspaceIfIdle = () => {
94
+ if (sessions.size === 0 && pendingSessions === 0)
95
+ workspaceRoot = undefined;
96
+ };
97
+ const sessionOrThrow = (sessionId) => {
98
+ const session = sessions.get(sessionId);
99
+ if (!session) {
100
+ throw acp.RequestError.invalidParams({ sessionId }, `No such session: ${sessionId}. Create one with session/new.`);
101
+ }
102
+ return session;
103
+ };
104
+ /** Send one `session/update`. Notifications, so nothing here waits on the client. */
105
+ const sendUpdate = async (session, update) => {
106
+ await session.client.notify(acp.CLIENT_METHODS.session_update, {
107
+ sessionId: session.sessionId,
108
+ update,
109
+ });
110
+ };
111
+ /**
112
+ * Runs one prompt turn to completion and reports how it ended.
113
+ *
114
+ * Never rejects: the caller needs to answer the still-open `session/prompt` request, and it has
115
+ * to be able to tell a cancellation from a crash to do that. Progress goes out as
116
+ * `session/update` notifications; the outcome comes back here.
117
+ */
118
+ const runTurn = async (session, prompt) => {
119
+ const mapper = new AcpV1UpdateMapper();
120
+ const abort = new AbortController();
121
+ session.abort = abort;
122
+ session.cancelled = false;
123
+ try {
124
+ // Inside the try, so a throw here is reported as a failed turn rather than leaving
125
+ // `session.abort` set by the prologue above and every later prompt refused as concurrent.
126
+ const text = promptText(prompt);
127
+ // No `user_message` echo. v1 replays a conversation only on `session/load`; during a turn the
128
+ // client already has the message it just sent, and echoing it would draw it twice.
129
+ //
130
+ // The gate's last hop. Registered per turn so it closes over THIS turn's mapper, which is
131
+ // what knows the id of the tool call a permission request is about.
132
+ session.runner.setToolApprovalCallback(async (pending) => {
133
+ const asked = session.client.request(acp.CLIENT_METHODS.session_request_permission, permissionRequestForV1({
134
+ sessionId: session.sessionId,
135
+ pending,
136
+ toolCallId: mapper.claimToolCallId(pending.name, pending.args),
137
+ }),
138
+ // The spec's cancellation cascade: when the turn is cancelled, outstanding permission
139
+ // requests are cancelled too, so the client can take its prompt off the screen instead of
140
+ // asking about work that has already stopped.
141
+ { cancellationSignal: abort.signal });
142
+ // **The abort has to be able to end this wait on its own.** `cancellationSignal` sends the
143
+ // peer a `$/cancel_request` and then keeps waiting for its answer — cooperative by design.
144
+ // The spec says a client MUST answer an outstanding permission request with the `cancelled`
145
+ // outcome, but on v1 a client that does not would wedge the whole connection rather than
146
+ // just this turn: the `session/prompt` request is still open, so it would never be answered
147
+ // and the client would hold a pending request forever. (On v2 the prompt was acknowledged
148
+ // long before, which is why bounding the CLOSE was enough there and is not here.)
149
+ //
150
+ // Racing the signal ends the wait locally. `cancelled` is what the spec says the answer
151
+ // would have been, and {@link decisionForOutcome} turns it into a rejection — the same
152
+ // fail-closed polarity every other unrecognised answer gets.
153
+ const outcome = await Promise.race([
154
+ asked.then((response) => response.outcome),
155
+ cancelledWhenAborted(abort.signal),
156
+ ]);
157
+ return decisionForOutcome(outcome);
158
+ });
159
+ for await (const event of session.runner.processMessagesWithEvents([new HumanMessage(text)], abort.signal)) {
160
+ for (const update of mapper.map(event))
161
+ await sendUpdate(session, update);
162
+ }
163
+ if (session.cancelled || abort.signal.aborted)
164
+ return { ok: true, stopReason: 'cancelled' };
165
+ return { ok: true, stopReason: 'end_turn' };
166
+ }
167
+ catch (error) {
168
+ // A cancelled run surfaces as a thrown abort from whatever library noticed the signal first.
169
+ // The spec is explicit that this MUST NOT reach the client as an error: clients render
170
+ // unrecognized errors, and a user who pressed stop would be shown a failure for it.
171
+ if (session.cancelled || abort.signal.aborted)
172
+ return { ok: true, stopReason: 'cancelled' };
173
+ const message = error instanceof Error ? error.message : String(error);
174
+ displayWarning(`ACP session ${session.sessionId}: turn failed — ${message}`);
175
+ return { ok: false, message };
176
+ }
177
+ finally {
178
+ session.runner.setToolApprovalCallback(null);
179
+ session.abort = null;
180
+ }
181
+ };
182
+ const closeSession = async (session) => {
183
+ session.cancelled = true;
184
+ session.abort?.abort();
185
+ sessions.delete(session.sessionId);
186
+ // Wait for the aborted turn to finish unwinding before anything else. `cleanup()` does not do
187
+ // this — it drops the agent and returns — so releasing the workspace below without the wait
188
+ // would let a `session/new` for another directory reassign INIT_CWD out from under a turn still
189
+ // running its last tool call.
190
+ //
191
+ // BOUNDED, for the reason CLOSE_TURN_DRAIN_MS gives: a turn parked on a permission request is
192
+ // waiting on the client, and no abort of ours can force that to settle.
193
+ await drainWithDeadline(session.turn, CLOSE_TURN_DRAIN_MS);
194
+ try {
195
+ await session.runner.cleanup();
196
+ }
197
+ catch (error) {
198
+ displayWarning(`ACP session ${session.sessionId}: cleanup failed — ${error instanceof Error ? error.message : String(error)}`);
199
+ }
200
+ releaseWorkspaceIfIdle();
201
+ };
202
+ return acp
203
+ .agent({ name: ACP_AGENT_NAME })
204
+ .onRequest(acp.AGENT_METHODS.initialize, () => ({
205
+ // The version the CLIENT asked for, which the router has already checked this app can serve.
206
+ protocolVersion: acp.PROTOCOL_VERSION,
207
+ agentInfo: { name: ACP_AGENT_NAME, title: ACP_AGENT_TITLE, version: agentVersion() },
208
+ agentCapabilities: {
209
+ // `loadSession` and `sessionCapabilities.resume` are deliberately absent: both promise a
210
+ // session that outlives the process, and these do not. `promptCapabilities` is absent for
211
+ // the same reason — omitted means the v1 baseline of text and resource links, which is
212
+ // exactly what this agent reads; claiming `image`, `audio` or `embeddedContext` would
213
+ // invite content it can only describe back.
214
+ sessionCapabilities: { list: {}, close: {} },
215
+ },
216
+ }))
217
+ .onRequest(acp.AGENT_METHODS.session_new, async ({ params, client }) => {
218
+ const cwd = resolvePath(params.cwd);
219
+ if (workspaceRoot !== undefined && !isSameWorkspace(workspaceRoot, cwd)) {
220
+ throw acp.RequestError.invalidParams({ cwd: params.cwd, workspaceRoot }, `This agent process is serving ${workspaceRoot}. Start a separate agent process for ${cwd}.`);
221
+ }
222
+ // **Claim the workspace BEFORE the first await, or the check above decides nothing.** Nothing
223
+ // serialises inbound requests on a stdio connection, so two `session/new` calls naming
224
+ // different directories would both see an unset root, both suspend on the config load, and
225
+ // both assign. Testing and assigning either side of an await is not a check.
226
+ workspaceRoot = cwd;
227
+ pendingSessions += 1;
228
+ try {
229
+ const config = await loadConfig(cwd);
230
+ const runner = new GthAgentRunner(acpStatusCallback, options.resolvers ?? createResolvers(), agentFactoryFor(config));
231
+ // The command a run is resolved under decides its toolset, its mode prompt and its
232
+ // approvals posture. For an editor session that is `acp.mode`, defaulting to `code`.
233
+ //
234
+ // **The checkpoint saver is not optional here, even though `init` accepts none.** A gated
235
+ // tool call suspends the graph on a LangGraph interrupt, and an interrupt with nowhere to
236
+ // checkpoint throws `MISSING_CHECKPOINTER` instead of asking the client for permission — so
237
+ // without one every shell command in an editor session fails while the ungated built-ins
238
+ // keep working. `MemorySaver` is the right lifetime as well as the right shape: these
239
+ // sessions live in one process and die with it, which is also why `loadSession` is not
240
+ // advertised.
241
+ //
242
+ // **Per session, not per process.** Each session owns its runner, its workspace and the
243
+ // graphs suspended in it, and `sessions` holds several at once; one shared saver would pool
244
+ // every session's checkpoint threads, so a `session/close` could clear state another
245
+ // session is still parked on.
246
+ await runner.init(resolveAcpSessionCommand(config), config, new MemorySaver());
247
+ const sessionId = randomUUID();
248
+ sessions.set(sessionId, {
249
+ sessionId,
250
+ cwd,
251
+ runner,
252
+ client,
253
+ abort: null,
254
+ turn: null,
255
+ cancelled: false,
256
+ });
257
+ return { sessionId };
258
+ }
259
+ finally {
260
+ pendingSessions -= 1;
261
+ // A `session/new` that never produced a session must not leave the process bound to its
262
+ // directory — one bad config would otherwise refuse every later session for the life of the
263
+ // agent.
264
+ releaseWorkspaceIfIdle();
265
+ }
266
+ })
267
+ .onRequest(acp.AGENT_METHODS.session_list, ({ params }) => {
268
+ const filter = params.cwd === undefined || params.cwd === null ? undefined : params.cwd;
269
+ return {
270
+ sessions: [...sessions.values()]
271
+ .filter((session) => filter === undefined || session.cwd === resolvePath(filter))
272
+ .map((session) => ({ sessionId: session.sessionId, cwd: session.cwd })),
273
+ };
274
+ })
275
+ .onRequest(acp.AGENT_METHODS.session_close, async ({ params }) => {
276
+ // Closing MUST cancel whatever is running, exactly as session/cancel would, before the
277
+ // session's resources go away.
278
+ await closeSession(sessionOrThrow(params.sessionId));
279
+ return {};
280
+ })
281
+ .onRequest(acp.AGENT_METHODS.session_prompt, async ({ params }) => {
282
+ const session = sessionOrThrow(params.sessionId);
283
+ if (session.abort) {
284
+ throw acp.RequestError.invalidRequest({ sessionId: params.sessionId }, 'A prompt is already running in this session. Wait for it to answer.');
285
+ }
286
+ // Started synchronously and recorded before anything can suspend: `runTurn` assigns
287
+ // `session.abort` before its first await, and `session.turn` is what a concurrent
288
+ // `session/close` waits on. A gap here is a close that skips the drain.
289
+ const turn = runTurn(session, params.prompt);
290
+ session.turn = turn.then(() => undefined);
291
+ const outcome = await turn;
292
+ if (!outcome.ok) {
293
+ throw acp.RequestError.internalError({ sessionId: session.sessionId }, `The agent could not complete this turn: ${outcome.message}`);
294
+ }
295
+ return { stopReason: outcome.stopReason };
296
+ })
297
+ .onNotification(acp.AGENT_METHODS.session_cancel, ({ params }) => {
298
+ const session = sessions.get(params.sessionId);
299
+ if (!session)
300
+ return;
301
+ // Recorded as well as aborted: the abort makes the run stop, and the flag is what makes the
302
+ // turn answer `cancelled` instead of reading its own truncated stream as a normal finish.
303
+ session.cancelled = true;
304
+ session.abort?.abort();
305
+ });
306
+ }
307
+ //# sourceMappingURL=acpAgentAppV1.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"acpAgentAppV1.js","sourceRoot":"","sources":["../../../src/modules/acp/acpAgentAppV1.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,167 @@
1
+ /**
2
+ * @packageDocumentation
3
+ * The parts of Gaunt Sloth's ACP surface that are the same in **both** protocol dialects.
4
+ *
5
+ * The agent serves ACP v1 and ACP v2 from one process (`acpRouter.ts` picks between them on the
6
+ * first message). Two dialects mean two sets of handlers, and everything that is genuinely about
7
+ * the protocol version belongs in those. Everything else lives here — the workspace binding's
8
+ * comparison rule, the config load that roots a session, the version reported in a handshake, the
9
+ * bounded drain a close waits on, and, most importantly, the **untrusted-attachment fencing** a
10
+ * prompt goes through.
11
+ *
12
+ * **The fencing is here rather than duplicated because a second copy is how one of them ends up
13
+ * missing an arm.** It is the one piece on this surface that is a security boundary rather than a
14
+ * convenience: client-supplied names, URIs, descriptions and block types are interpolated into the
15
+ * same message as the user's own words, and the defang → cap → collapse → fence pipeline is what
16
+ * keeps them from impersonating the agent's instructions. A dialect that grew its own copy would
17
+ * be one review away from diverging.
18
+ */
19
+ import type { GthConfig } from '@gaunt-sloth/core/config.js';
20
+ import type { AgentResolvers, GthAgentFactory, GthCommand, StatusUpdateCallback } from '@gaunt-sloth/core/core/types.js';
21
+ /** How this agent identifies itself in the `initialize` response. */
22
+ export declare const ACP_AGENT_NAME = "gaunt-sloth";
23
+ /** Human-facing name for the same, used where a client shows a title rather than an id. */
24
+ export declare const ACP_AGENT_TITLE = "Gaunt Sloth";
25
+ /**
26
+ * How long `session/close` waits for an aborted turn to finish unwinding before proceeding anyway.
27
+ *
28
+ * **The wait is bounded because it cannot be trusted to end.** A turn parked on
29
+ * `session/request_permission` is waiting on the CLIENT, and ACP cancellation is cooperative: the
30
+ * `$/cancel_request` we send settles the promise only when the peer answers it. A client that closes
31
+ * a session while its own permission prompt is on screen and then never answers would otherwise
32
+ * leave this handler suspended forever — and `session/close` would never get a response, which is a
33
+ * worse failure than the narrow re-rooting window the wait exists to close.
34
+ *
35
+ * On expiry the close proceeds exactly as it did before the wait existed. So the bound degrades to
36
+ * the previously accepted behaviour rather than to a hang, and an aborted turn — which unwinds in
37
+ * milliseconds — is unaffected.
38
+ */
39
+ export declare const CLOSE_TURN_DRAIN_MS = 2000;
40
+ /** Seams the tests replace; production leaves every one of them at its default. */
41
+ export interface AcpAgentAppOptions {
42
+ /**
43
+ * Loads the effective gth config for a session rooted at `cwd`. Defaults to pointing config
44
+ * discovery at the session workspace and running the normal loader.
45
+ */
46
+ loadConfig?: (cwd: string) => Promise<GthConfig>;
47
+ /**
48
+ * The agent backend for a session. Defaults to `resolveAgentFactory`, i.e. the lean agent
49
+ * — the same backend every other command resolves, reached through the same seam rather than
50
+ * named here, so a future backend choice stays a single edit.
51
+ */
52
+ agentFactory?: (config: GthConfig) => GthAgentFactory;
53
+ /** Tool/content resolvers handed to the runner. Defaults to `createResolvers`. */
54
+ resolvers?: AgentResolvers;
55
+ }
56
+ /**
57
+ * Whether two already-resolved absolute paths name the same workspace.
58
+ *
59
+ * **Case-insensitive on win32 and nowhere else**, because that is where the answer differs: NTFS is
60
+ * case-insensitive and case-preserving, so `C:\Foo` and `c:\foo` are one directory and an exact
61
+ * string compare calls them two — a client that re-sends its own `cwd` with different casing would
62
+ * be told to start a second agent process for the project it is already in. On POSIX the two really
63
+ * are different directories and must keep comparing unequal, so the platform is the whole
64
+ * distinction rather than a workaround for one.
65
+ *
66
+ * `platform` is a parameter with the live value as its default so both arms are testable on any
67
+ * host. Every other bug of this shape in this repo (OPS-27, EXT-38, GS2-42, EXT-16) was a POSIX-only
68
+ * assertion that passed everywhere except the Windows cell, and a test that can only run on win32
69
+ * would have the same blind spot pointed the other way.
70
+ */
71
+ export declare function isSameWorkspace(a: string, b: string, platform?: NodeJS.Platform): boolean;
72
+ /**
73
+ * Points config discovery — and with it the filesystem toolkit's allowed root, grep's boundary and
74
+ * the shell tool's spawn directory — at the ACP session's workspace.
75
+ *
76
+ * All of them read `getCurrentWorkDir()`, which prefers `INIT_CWD`. In a normal CLI run that is
77
+ * npm's, and correct. In an editor-spawned agent it is whatever the install shell left behind, and
78
+ * pointing an agent's file tools at a stale directory is worse than any error: it reads and writes
79
+ * real files in the wrong project. Setting it to the workspace the CLIENT named replaces a guess
80
+ * with the one authoritative value, and does it without `process.chdir()`, which in a process
81
+ * serving several sessions would be a race rather than a fix.
82
+ */
83
+ export declare function loadConfigForCwd(cwd: string): Promise<GthConfig>;
84
+ /**
85
+ * The command an ACP session is resolved under — `acp.mode`, defaulting to **`code`**.
86
+ *
87
+ * An editor connects to get an agent that can do the job. Resolving its sessions under `chat`
88
+ * hands it `filesystem: 'read'` and, because `filterDevTools` only builds the toolkit for `code`
89
+ * and `exec`, no shell and no dev tools at all: a read-only agent in a place nobody asked for one.
90
+ * `code` is the same default the bare `gth` CLI already resolves to.
91
+ *
92
+ * **Why `acp.mode` naming an existing command, and not a `commands.acp` block.** `runner.init`
93
+ * takes a {@link GthCommand}, and two pieces of tool gating branch on that union — `filterDevTools`
94
+ * (`command !== 'code' && command !== 'exec'`) and `GthDevToolkit`, which resolves the shell
95
+ * default for the active mode. An `acp` command would have to join the union and every one of
96
+ * those branches would have to learn the new member, with a silently wrong default wherever one was
97
+ * missed. A `mode` whose value is an existing command passes something those branches already
98
+ * handle. `ask --write` is mapped the same way, to `'code'` at the call boundary, rather than
99
+ * becoming a command of its own. The return type is what enforces it: a mode that is not a
100
+ * `GthCommand` does not compile.
101
+ *
102
+ * **Both dialects call this**, so the v1 and v2 apps cannot drift on the answer.
103
+ */
104
+ export declare function resolveAcpSessionCommand(config: GthConfig): GthCommand;
105
+ /**
106
+ * Status output from a session goes to the console utilities, which this surface has already
107
+ * routed away from stdout (see `acpStdio.ts`) — stdout belongs to the JSON-RPC framing.
108
+ *
109
+ * Only warnings and errors are forwarded. The rest of a run's status chatter is already reported
110
+ * to the client as `session/update` notifications, where the editor can render it; duplicating it
111
+ * into the agent's stderr would make the log a second, worse copy of the transcript.
112
+ */
113
+ export declare const acpStatusCallback: StatusUpdateCallback;
114
+ /**
115
+ * This build's version, for the `initialize` handshake, or `unknown` when it cannot be read.
116
+ *
117
+ * `getSlothVersion()` reads the package manifest under the install dir, which an entry point has
118
+ * to have registered. Both ACP doors do. It is caught anyway because failing the HANDSHAKE over a
119
+ * display string would take the whole agent down for a piece of metadata — a host that cannot
120
+ * connect is a far worse outcome than one that shows an unknown version. The bins' own spec pins
121
+ * that the real path reports a real version, so this fallback cannot become the normal answer
122
+ * without something going red.
123
+ */
124
+ export declare function agentVersion(): string;
125
+ /**
126
+ * Waits for `work` to settle, giving up after `ms`. Never rejects — the caller is tearing a session
127
+ * down, and a failure in what it is waiting on changes nothing about that.
128
+ *
129
+ * The timer is cleared on the winning path and unreferenced regardless, so a close cannot leave a
130
+ * pending timer holding the process open.
131
+ */
132
+ export declare function drainWithDeadline(work: Promise<void> | null, ms: number): Promise<void>;
133
+ /**
134
+ * The least a prompt content block is guaranteed to be in either dialect.
135
+ *
136
+ * v1 and v2 declare their own `ContentBlock` unions and they are not the same type, but every
137
+ * member of both carries a `type` discriminator and nothing below reads a field without checking
138
+ * it first. Typing the seam this way is what lets one implementation of the fencing serve both,
139
+ * which is the point of keeping it here.
140
+ */
141
+ export interface AcpContentBlockLike {
142
+ readonly type: string;
143
+ }
144
+ /**
145
+ * The prompt as the text handed to the model: the user's own words, then — when the client attached
146
+ * anything else — ONE fenced section describing what arrived.
147
+ *
148
+ * **The user's `text` blocks stay unfenced and everything else is fenced.** That split is the whole
149
+ * design: the text IS the user's instruction and always was, while a resource link's metadata comes
150
+ * from the editor, the filesystem or an MCP server, and a block's `type` is whatever the client put
151
+ * on the wire. Interpolating those into the same prose would put attacker-influenceable bytes in
152
+ * the model's context with no marker of provenance and a structural marker they could forge — the
153
+ * thing `acpPermissions.ts` says this surface must not do.
154
+ *
155
+ * The framing line and the closing reassertion are first-party and sit OUTSIDE the fence, so the
156
+ * last thing the model reads about the attachments is this agent's own authority rather than the
157
+ * client's text. That is the same shape `appendMcpServerInstructionsNote` uses, deliberately: a
158
+ * second shape for the same problem is how one of them ends up missing an arm.
159
+ */
160
+ export declare function promptText(prompt: readonly AcpContentBlockLike[]): string;
161
+ /**
162
+ * Kept for the entry points, which announce themselves on stderr before serving.
163
+ *
164
+ * Both protocol versions are named because both are served, and the version a host gets is the
165
+ * one it asks for. A notice claiming a single dialect is how the wrong one ends up believed.
166
+ */
167
+ export declare function announceAcpStart(): void;