@gaunt-sloth/agent 2.0.0-alpha.9 → 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 (124) 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/package.json +13 -13
  98. package/dist/core/GthDeepAgent.d.ts +0 -181
  99. package/dist/core/GthDeepAgent.js +0 -682
  100. package/dist/core/GthDeepAgent.js.map +0 -1
  101. package/dist/core/deepAgentPermissions.d.ts +0 -115
  102. package/dist/core/deepAgentPermissions.js +0 -282
  103. package/dist/core/deepAgentPermissions.js.map +0 -1
  104. package/dist/core/gthAcpServer.d.ts +0 -27
  105. package/dist/core/gthAcpServer.js +0 -82
  106. package/dist/core/gthAcpServer.js.map +0 -1
  107. package/dist/core/gthDeepAgentFactory.d.ts +0 -8
  108. package/dist/core/gthDeepAgentFactory.js +0 -9
  109. package/dist/core/gthDeepAgentFactory.js.map +0 -1
  110. package/dist/modules/acpModule.d.ts +0 -40
  111. package/dist/modules/acpModule.js +0 -78
  112. package/dist/modules/acpModule.js.map +0 -1
  113. package/dist/tools/shell/allowlist.d.ts +0 -11
  114. package/dist/tools/shell/allowlist.js +0 -12
  115. package/dist/tools/shell/allowlist.js.map +0 -1
  116. package/dist/tools/shell/arity.d.ts +0 -11
  117. package/dist/tools/shell/arity.js +0 -12
  118. package/dist/tools/shell/arity.js.map +0 -1
  119. package/dist/tools/shell/hardline.d.ts +0 -15
  120. package/dist/tools/shell/hardline.js +0 -88
  121. package/dist/tools/shell/hardline.js.map +0 -1
  122. package/dist/tools/shell/normalize.d.ts +0 -10
  123. package/dist/tools/shell/normalize.js +0 -11
  124. package/dist/tools/shell/normalize.js.map +0 -1
@@ -0,0 +1,110 @@
1
+ /**
2
+ * @packageDocumentation
3
+ * Bridges the agent's tool-approval gate to ACP **v1**'s `session/request_permission`.
4
+ * (`acpPermissions.ts` is the v2 half and carries the reasoning both share.)
5
+ *
6
+ * **The gate must reach a human on this surface too.** [[EXT-54]] records what happens otherwise:
7
+ * a server that wires no approval callback does not get a quieter gate, it gets a gated tool that
8
+ * silently does nothing. A new dialect is a new surface, and the hole reopens per surface unless
9
+ * each one answers it.
10
+ *
11
+ * ## What v1 has instead of v2's `subject`
12
+ *
13
+ * v2 carries a request-level `title`, an optional `description`, and a structured `subject` that
14
+ * can say "this is a shell command, here it is, and here is its cwd". **v1 has none of those**: a
15
+ * `RequestPermissionRequest` is a session id, a `ToolCallUpdate`, and the options. So:
16
+ *
17
+ * - the command travels as `rawInput`, which is where a v1 client reads a tool call's arguments;
18
+ * - the gate's explanation — the rater's verdict, the escalation entry that fired, what a
19
+ * remembered answer would store — travels as the tool call's `content`, the only free-text field
20
+ * in the request. Dropping it is not an option: a user asked to rule on a call their own
21
+ * configuration should have approved reads an unexplained prompt as the gate malfunctioning.
22
+ *
23
+ * The `toolCall` field is an UPSERT against the call the client is already rendering, so every
24
+ * descriptive field is sent with the same values the creating `tool_call` update carried. Re-sending
25
+ * them is a no-op for a call the client knows, and it is what lets the request stand on its own for
26
+ * a call the client has never seen — which is what a minted id means.
27
+ *
28
+ * ## Untrusted text crosses this boundary as DATA, deliberately
29
+ *
30
+ * The command, the rater's reason and the escalation provenance are all model- or third-party-
31
+ * authored, and they are not painted through `core/shell/framing` here. That is not an oversight:
32
+ * these values leave as JSON fields of a structured request and the client draws them in its own
33
+ * UI, where terminal control codes are inert, and framing them would ship a line-number gutter into
34
+ * a GUI. What matters is that they stay in their own fields rather than being interpolated into the
35
+ * prompt's own prose, so nothing the model writes can impersonate the request itself.
36
+ */
37
+ import { ACP_PERMISSION_OPTIONS } from '#src/modules/acp/acpPermissions.js';
38
+ import { toolKindFor } from '#src/modules/acp/acpToolCalls.js';
39
+ /** The gated tool whose argument is a shell command. */
40
+ const SHELL_TOOL = 'run_shell_command';
41
+ /**
42
+ * The human-readable explanation for the prompt: everything the gate knows about WHY this call
43
+ * reached a human.
44
+ *
45
+ * Assembled from the same three sources the terminal surfaces show, for the same reason. Empty when
46
+ * the gate has nothing to add, in which case no content is attached rather than an empty block.
47
+ */
48
+ function descriptionFor(pending) {
49
+ const parts = [];
50
+ if (pending.safetyVerdict) {
51
+ parts.push(`AI rater: ${pending.safetyVerdict.outcome} — ${pending.safetyVerdict.reason}`.trimEnd());
52
+ }
53
+ if (pending.escalatedBy) {
54
+ parts.push(`Your approvals.escalate list matched this call: ${pending.escalatedBy}`);
55
+ }
56
+ if (pending.grantPreview) {
57
+ parts.push(`"Allow and remember" will store: ${pending.grantPreview}`);
58
+ }
59
+ return parts.length > 0 ? parts.join('\n\n') : undefined;
60
+ }
61
+ /** The shell command a pending call would run, when it is one and it is a plain string. */
62
+ function shellCommandOf(pending) {
63
+ if (pending.name !== SHELL_TOOL)
64
+ return undefined;
65
+ const command = pending.args.command;
66
+ return typeof command === 'string' ? command : undefined;
67
+ }
68
+ /** One tool-call content entry wrapping a line of text. */
69
+ function toolText(text) {
70
+ return { type: 'content', content: { type: 'text', text } };
71
+ }
72
+ /**
73
+ * What the request puts in front of the human, beyond the tool's name and arguments.
74
+ *
75
+ * The command comes first when there is one: v2 can say "this is a command" structurally and v1
76
+ * cannot, so on this dialect the only way to put it where a person ruling on it will read it is to
77
+ * spell it out. The gate's own explanation follows.
78
+ */
79
+ function explanationFor(pending) {
80
+ const command = shellCommandOf(pending);
81
+ const description = descriptionFor(pending);
82
+ return [
83
+ ...(command === undefined ? [] : [toolText(`Shell command: ${command}`)]),
84
+ ...(description === undefined ? [] : [toolText(description)]),
85
+ ];
86
+ }
87
+ /** The ACP v1 permission request for one pending tool call the gate escalated to a human. */
88
+ export function permissionRequestForV1(options) {
89
+ const { sessionId, pending, toolCallId } = options;
90
+ const content = explanationFor(pending);
91
+ return {
92
+ sessionId,
93
+ toolCall: {
94
+ // A request about a call the update stream never announced still needs an id. It is minted
95
+ // rather than omitted — the field is required — and carries the same descriptive fields a
96
+ // creating update would, so the client renders the tool by name instead of an empty row.
97
+ toolCallId: toolCallId ?? `permission-${pending.name}`,
98
+ name: pending.name,
99
+ // The SAME title the creating update sent. This request is an upsert against a call the
100
+ // client is already drawing, so a different title here would rename that row mid-flight.
101
+ title: pending.name,
102
+ kind: toolKindFor(pending.name),
103
+ status: 'pending',
104
+ rawInput: pending.args,
105
+ ...(content.length === 0 ? {} : { content }),
106
+ },
107
+ options: [...ACP_PERMISSION_OPTIONS],
108
+ };
109
+ }
110
+ //# sourceMappingURL=acpPermissionsV1.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"acpPermissionsV1.js","sourceRoot":"","sources":["../../../src/modules/acp/acpPermissionsV1.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,41 @@
1
+ /**
2
+ * @packageDocumentation
3
+ * Picks the ACP dialect a connection gets, from the connection itself.
4
+ *
5
+ * ## The problem, and why the SDK solves it rather than us
6
+ *
7
+ * There is one stdio connection and the protocol version is knowable only from the first
8
+ * `initialize` — which is also the first thing the chosen app has to handle. So a dispatcher has to
9
+ * read a message it is not allowed to consume, and pushing it back onto a `ReadableStream` is the
10
+ * part that is awkward to do correctly.
11
+ *
12
+ * `@agentclientprotocol/sdk` already does exactly this. {@link acp.agentProtocolRouter} takes the
13
+ * first wire item, requires it to be an `initialize`, selects the highest configured version that
14
+ * does not exceed the client's requested one, and re-enqueues the request into a fresh readable in
15
+ * front of the rest of the stream. Nothing later is touched. Using it means the sniff, the pushback
16
+ * and the not-configured / not-an-initialize error paths are the SDK's problem, on the same pinned
17
+ * version as the two apps it routes between.
18
+ *
19
+ * It is exported from the `experimental/v2` entry point rather than from `experimental/server` —
20
+ * `AcpServer` there is an HTTP/WebSocket transport and does not route by version.
21
+ *
22
+ * ## What each side gets
23
+ *
24
+ * A client asking for **1** reaches the v1 app; a client asking for **2 or higher** reaches the v2
25
+ * app. Neither app sees the other's traffic and neither is a translation layer over the other:
26
+ * `acpAgentAppV1.ts` and `acpAgentApp.ts` implement their own dialects, which differ in more than
27
+ * naming (see either module's doc).
28
+ *
29
+ * A version below 1 gets the SDK's `unsupported ACP protocol version` error, which is the honest
30
+ * answer — there is no ACP dialect this agent could serve such a client in.
31
+ */
32
+ import * as acp from '@agentclientprotocol/sdk/experimental/v2';
33
+ import type { AcpAgentAppOptions } from '#src/modules/acp/acpCommon.js';
34
+ /**
35
+ * Builds the version-dispatching front door: both apps, wired behind one connector.
36
+ *
37
+ * `options` reaches both apps unchanged, so a caller cannot end up with a configured v2 surface and
38
+ * a default v1 one — a divergence that would show up only against whichever client the tests do not
39
+ * use.
40
+ */
41
+ export declare function createAcpAgentRouter(options?: AcpAgentAppOptions): acp.AgentProtocolRouter;
@@ -0,0 +1,48 @@
1
+ /**
2
+ * @packageDocumentation
3
+ * Picks the ACP dialect a connection gets, from the connection itself.
4
+ *
5
+ * ## The problem, and why the SDK solves it rather than us
6
+ *
7
+ * There is one stdio connection and the protocol version is knowable only from the first
8
+ * `initialize` — which is also the first thing the chosen app has to handle. So a dispatcher has to
9
+ * read a message it is not allowed to consume, and pushing it back onto a `ReadableStream` is the
10
+ * part that is awkward to do correctly.
11
+ *
12
+ * `@agentclientprotocol/sdk` already does exactly this. {@link acp.agentProtocolRouter} takes the
13
+ * first wire item, requires it to be an `initialize`, selects the highest configured version that
14
+ * does not exceed the client's requested one, and re-enqueues the request into a fresh readable in
15
+ * front of the rest of the stream. Nothing later is touched. Using it means the sniff, the pushback
16
+ * and the not-configured / not-an-initialize error paths are the SDK's problem, on the same pinned
17
+ * version as the two apps it routes between.
18
+ *
19
+ * It is exported from the `experimental/v2` entry point rather than from `experimental/server` —
20
+ * `AcpServer` there is an HTTP/WebSocket transport and does not route by version.
21
+ *
22
+ * ## What each side gets
23
+ *
24
+ * A client asking for **1** reaches the v1 app; a client asking for **2 or higher** reaches the v2
25
+ * app. Neither app sees the other's traffic and neither is a translation layer over the other:
26
+ * `acpAgentAppV1.ts` and `acpAgentApp.ts` implement their own dialects, which differ in more than
27
+ * naming (see either module's doc).
28
+ *
29
+ * A version below 1 gets the SDK's `unsupported ACP protocol version` error, which is the honest
30
+ * answer — there is no ACP dialect this agent could serve such a client in.
31
+ */
32
+ import * as acp from '@agentclientprotocol/sdk/experimental/v2';
33
+ import { createAcpAgentApp } from '#src/modules/acp/acpAgentApp.js';
34
+ import { createAcpV1AgentApp } from '#src/modules/acp/acpAgentAppV1.js';
35
+ /**
36
+ * Builds the version-dispatching front door: both apps, wired behind one connector.
37
+ *
38
+ * `options` reaches both apps unchanged, so a caller cannot end up with a configured v2 surface and
39
+ * a default v1 one — a divergence that would show up only against whichever client the tests do not
40
+ * use.
41
+ */
42
+ export function createAcpAgentRouter(options = {}) {
43
+ return acp
44
+ .agentProtocolRouter()
45
+ .withV1(createAcpV1AgentApp(options))
46
+ .withV2(createAcpAgentApp(options));
47
+ }
48
+ //# sourceMappingURL=acpRouter.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"acpRouter.js","sourceRoot":"","sources":["../../../src/modules/acp/acpRouter.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA8BG;AAEH,OAAO,KAAK,GAAG,MAAM,0CAA0C,CAAC;AAChE,OAAO,EAAE,iBAAiB,EAAE,MAAM,iCAAiC,CAAC;AACpE,OAAO,EAAE,mBAAmB,EAAE,MAAM,mCAAmC,CAAC;AAGxE;;;;;;GAMG;AACH,MAAM,UAAU,oBAAoB,CAAC,OAAO,GAAuB,EAAE;IACnE,OAAO,GAAG;SACP,mBAAmB,EAAE;SACrB,MAAM,CAAC,mBAAmB,CAAC,OAAO,CAAC,CAAC;SACpC,MAAM,CAAC,iBAAiB,CAAC,OAAO,CAAC,CAAC,CAAC;AACxC,CAAC"}
@@ -0,0 +1,47 @@
1
+ /**
2
+ * @packageDocumentation
3
+ * The stdio entry point both ACP doors go through — the standalone `gaunt-sloth-acp` bin and
4
+ * `gaunt-sloth --acp-agent` — serving whichever protocol version the client speaks.
5
+ *
6
+ * **It is one function on purpose.** Two entry points spelling the same startup twice is how the
7
+ * doors drift, and the thing that would drift here is not cosmetic: it is the stdout guarantee
8
+ * below, which is invisible until an editor cannot parse a frame.
9
+ *
10
+ * ## stdout belongs to the protocol, and nothing else may touch it
11
+ *
12
+ * An ACP host reads JSON-RPC off the agent's stdout. Any other byte there is a framing error, not
13
+ * a message someone reads — and this codebase writes to stdout constantly: `displayInfo` and
14
+ * friends go through `console.log`/`console.info`, the streaming path writes to
15
+ * `process.stdout` directly, and the dev/custom toolkits pipe a child process's stdout through.
16
+ * A single status line would corrupt the stream.
17
+ *
18
+ * So the transport captures the REAL stdout writer first, and then `process.stdout.write` is
19
+ * redirected to stderr for the life of the process. Everything that thought it was printing to the
20
+ * user keeps working and lands on stderr, where a host shows it as agent output; the protocol
21
+ * keeps the channel it needs. Redirecting rather than silencing is deliberate — a warning nobody
22
+ * can see is how a misconfigured agent looks identical to a working one.
23
+ */
24
+ import type { AcpAgentAppOptions } from '#src/modules/acp/acpCommon.js';
25
+ /** Seams for the tests; production passes nothing. */
26
+ export interface AcpStdioOptions extends AcpAgentAppOptions {
27
+ /** Byte stream the client writes to. Defaults to the process's stdin. */
28
+ input?: ReadableStream<Uint8Array>;
29
+ /**
30
+ * Byte sink the protocol writes to. Defaults to the process's real stdout, captured before the
31
+ * redirect below so the redirect cannot swallow the protocol along with everything else.
32
+ */
33
+ output?: WritableStream<Uint8Array>;
34
+ }
35
+ /**
36
+ * Serves ACP over stdio until the client disconnects, in whichever protocol version the client
37
+ * asks for on its first message.
38
+ *
39
+ * The dialect is not chosen here and is not configurable: `acpRouter.ts` reads it off the
40
+ * `initialize` and hands the connection to the matching app. A flag would be the wrong shape —
41
+ * an editor spawns this command with no arguments, so a version it had to be told would be a
42
+ * version it never gets.
43
+ *
44
+ * Resolves when the connection closes, so a bin can `await` it and exit cleanly rather than
45
+ * holding the event loop open on a socket nobody is reading.
46
+ */
47
+ export declare function startAcpServer(options?: AcpStdioOptions): Promise<void>;
@@ -0,0 +1,71 @@
1
+ /**
2
+ * @packageDocumentation
3
+ * The stdio entry point both ACP doors go through — the standalone `gaunt-sloth-acp` bin and
4
+ * `gaunt-sloth --acp-agent` — serving whichever protocol version the client speaks.
5
+ *
6
+ * **It is one function on purpose.** Two entry points spelling the same startup twice is how the
7
+ * doors drift, and the thing that would drift here is not cosmetic: it is the stdout guarantee
8
+ * below, which is invisible until an editor cannot parse a frame.
9
+ *
10
+ * ## stdout belongs to the protocol, and nothing else may touch it
11
+ *
12
+ * An ACP host reads JSON-RPC off the agent's stdout. Any other byte there is a framing error, not
13
+ * a message someone reads — and this codebase writes to stdout constantly: `displayInfo` and
14
+ * friends go through `console.log`/`console.info`, the streaming path writes to
15
+ * `process.stdout` directly, and the dev/custom toolkits pipe a child process's stdout through.
16
+ * A single status line would corrupt the stream.
17
+ *
18
+ * So the transport captures the REAL stdout writer first, and then `process.stdout.write` is
19
+ * redirected to stderr for the life of the process. Everything that thought it was printing to the
20
+ * user keeps working and lands on stderr, where a host shows it as agent output; the protocol
21
+ * keeps the channel it needs. Redirecting rather than silencing is deliberate — a warning nobody
22
+ * can see is how a misconfigured agent looks identical to a working one.
23
+ */
24
+ import { Readable } from 'node:stream';
25
+ import * as acp from '@agentclientprotocol/sdk/experimental/v2';
26
+ import { announceAcpStart } from '#src/modules/acp/acpCommon.js';
27
+ import { createAcpAgentRouter } from '#src/modules/acp/acpRouter.js';
28
+ /**
29
+ * Sends everything written to `process.stdout` to stderr instead, and returns a sink holding the
30
+ * ORIGINAL writer.
31
+ *
32
+ * The capture must happen before the redirect, and the sink must hold the captured function rather
33
+ * than reach for `process.stdout.write` at write time — otherwise the transport's own frames go
34
+ * through the redirect too and the agent talks to itself.
35
+ */
36
+ function captureStdoutForProtocol() {
37
+ const writeToRealStdout = process.stdout.write.bind(process.stdout);
38
+ const writeToStderr = process.stderr.write.bind(process.stderr);
39
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
40
+ process.stdout.write = ((chunk, ...rest) =>
41
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
42
+ writeToStderr(chunk, ...rest));
43
+ return new WritableStream({
44
+ write(chunk) {
45
+ writeToRealStdout(chunk);
46
+ },
47
+ });
48
+ }
49
+ /**
50
+ * Serves ACP over stdio until the client disconnects, in whichever protocol version the client
51
+ * asks for on its first message.
52
+ *
53
+ * The dialect is not chosen here and is not configurable: `acpRouter.ts` reads it off the
54
+ * `initialize` and hands the connection to the matching app. A flag would be the wrong shape —
55
+ * an editor spawns this command with no arguments, so a version it had to be told would be a
56
+ * version it never gets.
57
+ *
58
+ * Resolves when the connection closes, so a bin can `await` it and exit cleanly rather than
59
+ * holding the event loop open on a socket nobody is reading.
60
+ */
61
+ export async function startAcpServer(options = {}) {
62
+ const { input, output, ...appOptions } = options;
63
+ // Before anything can print. `announceAcpStart` below is the first thing that tries, and it is
64
+ // there so the guarantee is exercised on every real start rather than only under test.
65
+ const protocolOut = output ?? captureStdoutForProtocol();
66
+ const protocolIn = input ?? Readable.toWeb(process.stdin);
67
+ const connection = createAcpAgentRouter(appOptions).connect(acp.ndJsonStream(protocolOut, protocolIn));
68
+ announceAcpStart();
69
+ await connection.closed;
70
+ }
71
+ //# sourceMappingURL=acpStdio.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"acpStdio.js","sourceRoot":"","sources":["../../../src/modules/acp/acpStdio.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;GAsBG;AAEH,OAAO,EAAE,QAAQ,EAAE,MAAM,aAAa,CAAC;AACvC,OAAO,KAAK,GAAG,MAAM,0CAA0C,CAAC;AAChE,OAAO,EAAE,gBAAgB,EAAE,MAAM,+BAA+B,CAAC;AAEjE,OAAO,EAAE,oBAAoB,EAAE,MAAM,+BAA+B,CAAC;AAarE;;;;;;;GAOG;AACH,SAAS,wBAAwB;IAC/B,MAAM,iBAAiB,GAAG,OAAO,CAAC,MAAM,CAAC,KAAK,CAAC,IAAI,CAAC,OAAO,CAAC,MAAM,CAAC,CAAC;IACpE,MAAM,aAAa,GAAG,OAAO,CAAC,MAAM,CAAC,KAAK,CAAC,IAAI,CAAC,OAAO,CAAC,MAAM,CAAC,CAAC;IAChE,8DAA8D;IAC9D,OAAO,CAAC,MAAM,CAAC,KAAK,GAAG,CAAC,CAAC,KAAU,EAAE,GAAG,IAAW,EAAE,EAAE;IACrD,8DAA8D;IAC7D,aAAqB,CAAC,KAAK,EAAE,GAAG,IAAI,CAAC,CAAgC,CAAC;IACzE,OAAO,IAAI,cAAc,CAAa;QACpC,KAAK,CAAC,KAAK;YACT,iBAAiB,CAAC,KAAK,CAAC,CAAC;QAC3B,CAAC;KACF,CAAC,CAAC;AACL,CAAC;AAED;;;;;;;;;;;GAWG;AACH,MAAM,CAAC,KAAK,UAAU,cAAc,CAAC,OAAO,GAAoB,EAAE;IAChE,MAAM,EAAE,KAAK,EAAE,MAAM,EAAE,GAAG,UAAU,EAAE,GAAG,OAAO,CAAC;IACjD,+FAA+F;IAC/F,uFAAuF;IACvF,MAAM,WAAW,GAAG,MAAM,IAAI,wBAAwB,EAAE,CAAC;IACzD,MAAM,UAAU,GAAG,KAAK,IAAK,QAAQ,CAAC,KAAK,CAAC,OAAO,CAAC,KAAK,CAAgC,CAAC;IAE1F,MAAM,UAAU,GAAG,oBAAoB,CAAC,UAAU,CAAC,CAAC,OAAO,CACzD,GAAG,CAAC,YAAY,CAAC,WAAW,EAAE,UAAU,CAAC,CAC1C,CAAC;IACF,gBAAgB,EAAE,CAAC;IACnB,MAAM,UAAU,CAAC,MAAM,CAAC;AAC1B,CAAC"}
@@ -0,0 +1,93 @@
1
+ /**
2
+ * @packageDocumentation
3
+ * Tool-call identity, shared by both ACP dialects.
4
+ *
5
+ * The two dialects disagree about how a tool call is REPORTED — v1 has a distinct `tool_call`
6
+ * update that creates one and a `tool_call_update` that patches it, v2 folds creation into the
7
+ * first `tool_call_update` — but they agree completely about what a tool call IS. The kind hint, the
8
+ * reassembly of streamed argument deltas, and the pairing of a permission request with the call it
9
+ * is about are the same problem on both surfaces, so they are solved once here.
10
+ *
11
+ * **The pairing especially.** {@link AcpToolCallTracker.claimToolCallId} is the piece that took
12
+ * three attempts to get right, and a second copy of it living in the other dialect's mapper is how
13
+ * one of them silently regresses to attaching a permission prompt to the wrong row.
14
+ */
15
+ import type { ToolKind } from '@agentclientprotocol/sdk';
16
+ /**
17
+ * The ACP tool kind for a gth tool name; `other` for anything not built in.
18
+ *
19
+ * Typed against **v1's** `ToolKind`, which is the closed union of the ten defined kinds; v2 widens
20
+ * the same union with a catch-all for future values. Typing against the stricter one is what lets a
21
+ * single function serve both — a v1 kind is always a valid v2 kind, and the reverse is not true.
22
+ */
23
+ export declare function toolKindFor(name: string): ToolKind;
24
+ /**
25
+ * Tracks the tool calls a turn has opened, so a permission request can name the one it is about.
26
+ *
27
+ * A dialect's update mapper extends this and keeps the fields fed: {@link trackToolStart} when the
28
+ * model announces a call, {@link appendToolArgs} for each argument delta, {@link trackToolSettled}
29
+ * when a result lands.
30
+ */
31
+ export declare abstract class AcpToolCallTracker {
32
+ /**
33
+ * Streamed argument text per tool call id, reassembled from `tool_args` deltas. Held rather than
34
+ * forwarded per delta because ACP has no argument-delta update: `rawInput` is a whole value, so
35
+ * it can only be sent once the deltas stop arriving.
36
+ */
37
+ protected readonly toolArgs: Map<string, string>;
38
+ /**
39
+ * Tool calls the model has requested but which have neither produced a result nor been claimed by
40
+ * a permission request, in the order they were announced, as `[toolCallId, toolName]`.
41
+ *
42
+ * Kept so the approval bridge can name the tool call a permission request is ABOUT. The gate's
43
+ * `PendingToolInterrupt` carries the tool's name and arguments but no call id — the graph
44
+ * suspends inside the middleware wrapping the call, which is downstream of where the id lives —
45
+ * while the client has already drawn that call from this stream. This queue reconnects the two.
46
+ */
47
+ protected readonly openToolCalls: Array<[string, string]>;
48
+ /** Records a newly announced call and opens its argument buffer. */
49
+ protected trackToolStart(id: string, name: string): void;
50
+ /** Accumulates one streamed argument delta. */
51
+ protected appendToolArgs(id: string, delta: string): void;
52
+ /** The reassembled arguments for a call, as a value for `rawInput`. */
53
+ protected rawInputFor(id: string): unknown;
54
+ /** Forgets a call that has produced its result. */
55
+ protected trackToolSettled(id: string): void;
56
+ /**
57
+ * Takes the id of the unclaimed tool call that matches `name` and `args`, removing it from the
58
+ * queue, or `undefined` when there is none.
59
+ *
60
+ * **Matched on the ARGUMENTS, because position is not a reliable discriminator here.** The
61
+ * arguments are the one value both sides hold: the gate's `PendingToolInterrupt` carries them, and
62
+ * this tracker still holds the streamed argument text at claim time (it is only discarded on
63
+ * `tool_result`, which exists solely on the resumed run).
64
+ *
65
+ * **Where the exact match degrades, named rather than implied:** this side is JSON reassembled
66
+ * from the model's streamed argument deltas, and a local model that ignores
67
+ * `disable_parallel_tool_use` can merge sibling calls' buffers into invalid JSON (`{}{}`,
68
+ * `{"steps":3}{}` — see the AG-UI server's `parseToolArguments` note). {@link parseToolArgs}
69
+ * deliberately does not carry that path's recovery, so such a payload stays a raw string, matches
70
+ * nothing, and falls back to position — on exactly the model class most likely to emit sloppy
71
+ * parallel calls. The fallback is still the best remaining answer and the cost is which row a
72
+ * client attaches the prompt to, never which command the human rules on.
73
+ *
74
+ * **Position was tried and is wrong in both directions**, which is why it is only the fallback.
75
+ * The runner drains suspended calls as a BATCH — every pending call decided in turn before
76
+ * anything resumes — so a model emitting two parallel calls of one tool has both open and neither
77
+ * running. Returning "the most recent" handed BOTH requests the second call's id. But plain
78
+ * oldest-first is no better in the case that is *normal* at the rated rungs: this method is
79
+ * reached only from the human-approval callback, which sits behind the gate's earlier exits
80
+ * (not-gated, deny list, bypass, the hardline floor, the allow list, and the rater's own arms), so
81
+ * a batch where one call is settled without a human and its sibling escalates leaves the settled
82
+ * call's id unclaimed at the head of the queue — and oldest-first then hands the request the
83
+ * wrong one, with every upstream ordering assumption perfectly intact.
84
+ *
85
+ * **Consuming is right either way**, and the queue order still decides between two calls whose
86
+ * arguments are genuinely identical, where either answer is equally true.
87
+ *
88
+ * Absent rather than guessed when nothing matches — a permission request pointing at the WRONG
89
+ * call is worse than one pointing at no call, because a client would then attach the answer to a
90
+ * call the user never saw.
91
+ */
92
+ claimToolCallId(name: string, args?: Record<string, unknown>): string | undefined;
93
+ }
@@ -0,0 +1,193 @@
1
+ /**
2
+ * @packageDocumentation
3
+ * Tool-call identity, shared by both ACP dialects.
4
+ *
5
+ * The two dialects disagree about how a tool call is REPORTED — v1 has a distinct `tool_call`
6
+ * update that creates one and a `tool_call_update` that patches it, v2 folds creation into the
7
+ * first `tool_call_update` — but they agree completely about what a tool call IS. The kind hint, the
8
+ * reassembly of streamed argument deltas, and the pairing of a permission request with the call it
9
+ * is about are the same problem on both surfaces, so they are solved once here.
10
+ *
11
+ * **The pairing especially.** {@link AcpToolCallTracker.claimToolCallId} is the piece that took
12
+ * three attempts to get right, and a second copy of it living in the other dialect's mapper is how
13
+ * one of them silently regresses to attaching a permission prompt to the wrong row.
14
+ */
15
+ /**
16
+ * ACP tool KIND for a gth tool name — the hint a client uses to pick an icon and a UI treatment.
17
+ *
18
+ * Matched by exact name, with `other` as the fallback, because a wrong kind is worse than a
19
+ * generic one: a client that shows a delete glyph for a read is actively misleading about what the
20
+ * agent is doing. Unknown names (custom tools, MCP tools) therefore land on `other` rather than
21
+ * being guessed at from substrings.
22
+ */
23
+ const TOOL_KINDS = {
24
+ create_directory: 'edit',
25
+ delete_directory: 'delete',
26
+ delete_file: 'delete',
27
+ directory_tree: 'read',
28
+ edit_file: 'edit',
29
+ get_file_info: 'read',
30
+ gth_read_binary: 'read',
31
+ gth_status_update: 'think',
32
+ gth_web_fetch: 'fetch',
33
+ list_allowed_directories: 'read',
34
+ list_directory: 'read',
35
+ list_directory_with_sizes: 'read',
36
+ move_file: 'move',
37
+ read_file: 'read',
38
+ read_multiple_files: 'read',
39
+ run_build: 'execute',
40
+ run_lint: 'execute',
41
+ run_shell_command: 'execute',
42
+ run_single_test: 'execute',
43
+ run_tests: 'execute',
44
+ search_files: 'search',
45
+ write_file: 'edit',
46
+ };
47
+ /**
48
+ * The ACP tool kind for a gth tool name; `other` for anything not built in.
49
+ *
50
+ * Typed against **v1's** `ToolKind`, which is the closed union of the ten defined kinds; v2 widens
51
+ * the same union with a catch-all for future values. Typing against the stricter one is what lets a
52
+ * single function serve both — a v1 kind is always a valid v2 kind, and the reverse is not true.
53
+ */
54
+ export function toolKindFor(name) {
55
+ return TOOL_KINDS[name] ?? 'other';
56
+ }
57
+ /**
58
+ * Tracks the tool calls a turn has opened, so a permission request can name the one it is about.
59
+ *
60
+ * A dialect's update mapper extends this and keeps the fields fed: {@link trackToolStart} when the
61
+ * model announces a call, {@link appendToolArgs} for each argument delta, {@link trackToolSettled}
62
+ * when a result lands.
63
+ */
64
+ export class AcpToolCallTracker {
65
+ /**
66
+ * Streamed argument text per tool call id, reassembled from `tool_args` deltas. Held rather than
67
+ * forwarded per delta because ACP has no argument-delta update: `rawInput` is a whole value, so
68
+ * it can only be sent once the deltas stop arriving.
69
+ */
70
+ toolArgs = new Map();
71
+ /**
72
+ * Tool calls the model has requested but which have neither produced a result nor been claimed by
73
+ * a permission request, in the order they were announced, as `[toolCallId, toolName]`.
74
+ *
75
+ * Kept so the approval bridge can name the tool call a permission request is ABOUT. The gate's
76
+ * `PendingToolInterrupt` carries the tool's name and arguments but no call id — the graph
77
+ * suspends inside the middleware wrapping the call, which is downstream of where the id lives —
78
+ * while the client has already drawn that call from this stream. This queue reconnects the two.
79
+ */
80
+ openToolCalls = [];
81
+ /** Records a newly announced call and opens its argument buffer. */
82
+ trackToolStart(id, name) {
83
+ this.toolArgs.set(id, '');
84
+ this.openToolCalls.push([id, name]);
85
+ }
86
+ /** Accumulates one streamed argument delta. */
87
+ appendToolArgs(id, delta) {
88
+ this.toolArgs.set(id, (this.toolArgs.get(id) ?? '') + delta);
89
+ }
90
+ /** The reassembled arguments for a call, as a value for `rawInput`. */
91
+ rawInputFor(id) {
92
+ return parseToolArgs(this.toolArgs.get(id));
93
+ }
94
+ /** Forgets a call that has produced its result. */
95
+ trackToolSettled(id) {
96
+ this.toolArgs.delete(id);
97
+ // A call a permission request already claimed is no longer in the queue; that is expected,
98
+ // not a miss.
99
+ const open = this.openToolCalls.findIndex(([openId]) => openId === id);
100
+ if (open >= 0)
101
+ this.openToolCalls.splice(open, 1);
102
+ }
103
+ /**
104
+ * Takes the id of the unclaimed tool call that matches `name` and `args`, removing it from the
105
+ * queue, or `undefined` when there is none.
106
+ *
107
+ * **Matched on the ARGUMENTS, because position is not a reliable discriminator here.** The
108
+ * arguments are the one value both sides hold: the gate's `PendingToolInterrupt` carries them, and
109
+ * this tracker still holds the streamed argument text at claim time (it is only discarded on
110
+ * `tool_result`, which exists solely on the resumed run).
111
+ *
112
+ * **Where the exact match degrades, named rather than implied:** this side is JSON reassembled
113
+ * from the model's streamed argument deltas, and a local model that ignores
114
+ * `disable_parallel_tool_use` can merge sibling calls' buffers into invalid JSON (`{}{}`,
115
+ * `{"steps":3}{}` — see the AG-UI server's `parseToolArguments` note). {@link parseToolArgs}
116
+ * deliberately does not carry that path's recovery, so such a payload stays a raw string, matches
117
+ * nothing, and falls back to position — on exactly the model class most likely to emit sloppy
118
+ * parallel calls. The fallback is still the best remaining answer and the cost is which row a
119
+ * client attaches the prompt to, never which command the human rules on.
120
+ *
121
+ * **Position was tried and is wrong in both directions**, which is why it is only the fallback.
122
+ * The runner drains suspended calls as a BATCH — every pending call decided in turn before
123
+ * anything resumes — so a model emitting two parallel calls of one tool has both open and neither
124
+ * running. Returning "the most recent" handed BOTH requests the second call's id. But plain
125
+ * oldest-first is no better in the case that is *normal* at the rated rungs: this method is
126
+ * reached only from the human-approval callback, which sits behind the gate's earlier exits
127
+ * (not-gated, deny list, bypass, the hardline floor, the allow list, and the rater's own arms), so
128
+ * a batch where one call is settled without a human and its sibling escalates leaves the settled
129
+ * call's id unclaimed at the head of the queue — and oldest-first then hands the request the
130
+ * wrong one, with every upstream ordering assumption perfectly intact.
131
+ *
132
+ * **Consuming is right either way**, and the queue order still decides between two calls whose
133
+ * arguments are genuinely identical, where either answer is equally true.
134
+ *
135
+ * Absent rather than guessed when nothing matches — a permission request pointing at the WRONG
136
+ * call is worse than one pointing at no call, because a client would then attach the answer to a
137
+ * call the user never saw.
138
+ */
139
+ claimToolCallId(name, args) {
140
+ const candidates = this.openToolCalls
141
+ .map(([id, toolName], index) => ({ id, toolName, index }))
142
+ .filter((candidate) => candidate.toolName === name);
143
+ if (candidates.length === 0)
144
+ return undefined;
145
+ const wanted = args === undefined ? undefined : canonicalJson(args);
146
+ const matched = wanted === undefined
147
+ ? undefined
148
+ : candidates.find((candidate) => canonicalJson(parseToolArgs(this.toolArgs.get(candidate.id))) === wanted);
149
+ const chosen = matched ?? candidates[0];
150
+ this.openToolCalls.splice(chosen.index, 1);
151
+ return chosen.id;
152
+ }
153
+ }
154
+ /**
155
+ * A value serialized with object keys in sorted order, so two arguments objects that differ only in
156
+ * key order compare equal.
157
+ *
158
+ * They routinely do: one side is JSON reassembled from a model's streamed argument deltas and the
159
+ * other is the object the graph handed the gate. Comparing raw `JSON.stringify` output would make
160
+ * the exact match in {@link AcpToolCallTracker.claimToolCallId} fail for a reason that has nothing
161
+ * to do with whether the two describe the same call.
162
+ */
163
+ function canonicalJson(value) {
164
+ if (value === null || typeof value !== 'object')
165
+ return JSON.stringify(value) ?? 'undefined';
166
+ if (Array.isArray(value))
167
+ return `[${value.map(canonicalJson).join(',')}]`;
168
+ const entries = Object.keys(value)
169
+ .sort()
170
+ .map((key) => `${JSON.stringify(key)}:${canonicalJson(value[key])}`);
171
+ return `{${entries.join(',')}}`;
172
+ }
173
+ /**
174
+ * The reassembled tool arguments as a value for `rawInput`, or `undefined` when there is nothing
175
+ * worth sending.
176
+ *
177
+ * Deliberately fail-soft: a model that emits malformed argument JSON is a real and recurring
178
+ * condition (see the AG-UI server's `parseToolArguments` note), and it must not take down the turn
179
+ * that reports it. An unparseable payload is passed through as the raw string so the client can
180
+ * still show what the model asked for.
181
+ */
182
+ function parseToolArgs(raw) {
183
+ const text = (raw ?? '').trim();
184
+ if (text.length === 0)
185
+ return undefined;
186
+ try {
187
+ return JSON.parse(text);
188
+ }
189
+ catch {
190
+ return text;
191
+ }
192
+ }
193
+ //# sourceMappingURL=acpToolCalls.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"acpToolCalls.js","sourceRoot":"","sources":["../../../src/modules/acp/acpToolCalls.ts"],"names":[],"mappings":"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"}