stitchkit 0.88.0 → 0.90.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 (184) hide show
  1. package/CHANGELOG.md +182 -0
  2. package/dist/agent-runtime/child-blocking.d.ts +17 -0
  3. package/dist/agent-runtime/child-blocking.d.ts.map +1 -0
  4. package/dist/agent-runtime/child-budget-policy.d.ts +23 -0
  5. package/dist/agent-runtime/child-budget-policy.d.ts.map +1 -0
  6. package/dist/agent-runtime/children-contract.d.ts +155 -0
  7. package/dist/agent-runtime/children-contract.d.ts.map +1 -0
  8. package/dist/agent-runtime/children.d.ts +4 -99
  9. package/dist/agent-runtime/children.d.ts.map +1 -1
  10. package/dist/agent-runtime/coding-shell-process.d.ts +33 -0
  11. package/dist/agent-runtime/coding-shell-process.d.ts.map +1 -0
  12. package/dist/agent-runtime/coding-tool-contract.d.ts +18 -0
  13. package/dist/agent-runtime/coding-tool-contract.d.ts.map +1 -1
  14. package/dist/agent-runtime/coding-tool-files.d.ts.map +1 -1
  15. package/dist/agent-runtime/coding-tool-listing.d.ts.map +1 -1
  16. package/dist/agent-runtime/coding-tool-search-patch.d.ts.map +1 -1
  17. package/dist/agent-runtime/coding-tool-shell.d.ts.map +1 -1
  18. package/dist/agent-runtime/coding-tool-utf8.d.ts +28 -0
  19. package/dist/agent-runtime/coding-tool-utf8.d.ts.map +1 -0
  20. package/dist/agent-runtime/contained-files.d.ts +20 -0
  21. package/dist/agent-runtime/contained-files.d.ts.map +1 -1
  22. package/dist/agent-runtime/durability-contract.d.ts +176 -0
  23. package/dist/agent-runtime/durability-contract.d.ts.map +1 -0
  24. package/dist/agent-runtime/durability-json.d.ts +3 -0
  25. package/dist/agent-runtime/durability-json.d.ts.map +1 -0
  26. package/dist/agent-runtime/durability-ledger.d.ts +16 -0
  27. package/dist/agent-runtime/durability-ledger.d.ts.map +1 -0
  28. package/dist/agent-runtime/durability-scheduler.d.ts +29 -0
  29. package/dist/agent-runtime/durability-scheduler.d.ts.map +1 -0
  30. package/dist/agent-runtime/durability.d.ts +14 -0
  31. package/dist/agent-runtime/durability.d.ts.map +1 -0
  32. package/dist/agent-runtime/event-search.d.ts.map +1 -1
  33. package/dist/agent-runtime/harness-contract.d.ts +37 -0
  34. package/dist/agent-runtime/harness-contract.d.ts.map +1 -1
  35. package/dist/agent-runtime/harness.d.ts +8 -0
  36. package/dist/agent-runtime/harness.d.ts.map +1 -1
  37. package/dist/agent-runtime/prompt.d.ts +23 -0
  38. package/dist/agent-runtime/prompt.d.ts.map +1 -1
  39. package/dist/agent-runtime/run-execution.d.ts.map +1 -1
  40. package/dist/agent-runtime/runtime-internals.d.ts +4 -0
  41. package/dist/agent-runtime/runtime-internals.d.ts.map +1 -1
  42. package/dist/agent-runtime/runtime.d.ts +13 -0
  43. package/dist/agent-runtime/runtime.d.ts.map +1 -1
  44. package/dist/agent-runtime/sandbox-broker.d.ts +7 -0
  45. package/dist/agent-runtime/sandbox-broker.d.ts.map +1 -0
  46. package/dist/agent-runtime/sandbox-bubblewrap-args.d.ts +9 -0
  47. package/dist/agent-runtime/sandbox-bubblewrap-args.d.ts.map +1 -0
  48. package/dist/agent-runtime/sandbox-bubblewrap-storage.d.ts +22 -0
  49. package/dist/agent-runtime/sandbox-bubblewrap-storage.d.ts.map +1 -0
  50. package/dist/agent-runtime/sandbox-bubblewrap.d.ts +14 -0
  51. package/dist/agent-runtime/sandbox-bubblewrap.d.ts.map +1 -0
  52. package/dist/agent-runtime/sandbox-coding-adapter.d.ts +15 -0
  53. package/dist/agent-runtime/sandbox-coding-adapter.d.ts.map +1 -0
  54. package/dist/agent-runtime/sandbox-coding.d.ts +15 -0
  55. package/dist/agent-runtime/sandbox-coding.d.ts.map +1 -0
  56. package/dist/agent-runtime/sandbox-contract.d.ts +93 -0
  57. package/dist/agent-runtime/sandbox-contract.d.ts.map +1 -0
  58. package/dist/agent-runtime/sandbox-process-owner.d.ts +11 -0
  59. package/dist/agent-runtime/sandbox-process-owner.d.ts.map +1 -0
  60. package/dist/agent-runtime/sandbox-process.d.ts +8 -0
  61. package/dist/agent-runtime/sandbox-process.d.ts.map +1 -0
  62. package/dist/agent-runtime/sandbox-session.d.ts +5 -0
  63. package/dist/agent-runtime/sandbox-session.d.ts.map +1 -0
  64. package/dist/agent-runtime/sandbox.d.ts +19 -0
  65. package/dist/agent-runtime/sandbox.d.ts.map +1 -1
  66. package/dist/agent-runtime/sqlite-purge.d.ts.map +1 -1
  67. package/dist/agent-runtime/sqlite.d.ts.map +1 -1
  68. package/dist/agent-runtime/store-driver.d.ts +127 -0
  69. package/dist/agent-runtime/store-driver.d.ts.map +1 -1
  70. package/dist/agent-runtime/store-events.d.ts +104 -0
  71. package/dist/agent-runtime/store-events.d.ts.map +1 -1
  72. package/dist/agent-runtime/store-migrations/v2-to-v3.d.ts +14 -0
  73. package/dist/agent-runtime/store-migrations/v2-to-v3.d.ts.map +1 -0
  74. package/dist/agent-runtime/store.d.ts +115 -0
  75. package/dist/agent-runtime/store.d.ts.map +1 -1
  76. package/dist/agent-runtime-coding-tools.js +11 -1247
  77. package/dist/agent-runtime-harness.d.ts +1 -1
  78. package/dist/agent-runtime-harness.d.ts.map +1 -1
  79. package/dist/agent-runtime-harness.js +65 -15
  80. package/dist/agent-runtime-openrouter.d.ts +3 -12
  81. package/dist/agent-runtime-openrouter.d.ts.map +1 -1
  82. package/dist/agent-runtime-openrouter.js +10 -7
  83. package/dist/agent-runtime-sandbox.d.ts +5 -0
  84. package/dist/agent-runtime-sandbox.d.ts.map +1 -0
  85. package/dist/agent-runtime-sandbox.js +797 -0
  86. package/dist/agent-runtime-sqlite-bun.js +3 -3
  87. package/dist/agent-runtime-sqlite-node.js +3 -3
  88. package/dist/agent-runtime.d.ts +5 -4
  89. package/dist/agent-runtime.d.ts.map +1 -1
  90. package/dist/agent-runtime.js +259 -65
  91. package/dist/application/watch-hub.d.ts +24 -2
  92. package/dist/application/watch-hub.d.ts.map +1 -1
  93. package/dist/application.js +127 -37
  94. package/dist/cli.js +8 -8
  95. package/dist/index-2na0qrfn.js +214 -0
  96. package/dist/{index-rcgsnrr6.js → index-31jw8fjq.js} +3 -3
  97. package/dist/{index-9hw9eq48.js → index-7601qfg1.js} +14 -14
  98. package/dist/{index-3xnq72rz.js → index-7rkhw9ec.js} +14 -1
  99. package/dist/{index-0vfdh685.js → index-8c3a7m4g.js} +1 -1
  100. package/dist/{index-t7gh5w3v.js → index-bn2cjajt.js} +27 -6
  101. package/dist/index-cp20z0n6.js +1351 -0
  102. package/dist/{index-qp3vshvc.js → index-fadnxwna.js} +87 -5
  103. package/dist/{index-v1qk37y9.js → index-hrbajay7.js} +106 -4
  104. package/dist/{index-p86mxmpp.js → index-hw2chdvy.js} +646 -43
  105. package/dist/{index-0ydchz76.js → index-mnm4qp0p.js} +5 -5
  106. package/dist/{index-1szetq6f.js → index-q3ve2d1k.js} +30 -8
  107. package/dist/{index-6krwcdcf.js → index-zwkgfq1s.js} +95 -85
  108. package/dist/{index-wn9cb82y.js → index-zyvby92n.js} +12 -9
  109. package/dist/index.js +13 -13
  110. package/dist/live/watch-client.d.ts +2 -1
  111. package/dist/live/watch-client.d.ts.map +1 -1
  112. package/dist/live/watch-contract.d.ts +70 -4
  113. package/dist/live/watch-contract.d.ts.map +1 -1
  114. package/dist/live/watch-delta.d.ts +110 -0
  115. package/dist/live/watch-delta.d.ts.map +1 -0
  116. package/dist/live.d.ts +2 -1
  117. package/dist/live.d.ts.map +1 -1
  118. package/dist/live.js +56 -4
  119. package/dist/observability/changes.d.ts +55 -0
  120. package/dist/observability/changes.d.ts.map +1 -0
  121. package/dist/observability/index.d.ts +2 -0
  122. package/dist/observability/index.d.ts.map +1 -1
  123. package/dist/observability/index.js +127 -0
  124. package/dist/observability/spool.d.ts +53 -0
  125. package/dist/observability/spool.d.ts.map +1 -0
  126. package/dist/server/index.js +9 -9
  127. package/dist/testing/agent-store-conformance.d.ts.map +1 -1
  128. package/dist/testing.js +57 -23
  129. package/dist/tool-invoker.js +6 -6
  130. package/dist/tools/agent.d.ts +6 -0
  131. package/dist/tools/agent.d.ts.map +1 -1
  132. package/dist/tools/connections/define.d.ts +6 -0
  133. package/dist/tools/connections/define.d.ts.map +1 -0
  134. package/dist/tools/connections/errors.d.ts +50 -0
  135. package/dist/tools/connections/errors.d.ts.map +1 -0
  136. package/dist/tools/connections/http.d.ts +3 -0
  137. package/dist/tools/connections/http.d.ts.map +1 -0
  138. package/dist/tools/connections/index.d.ts +19 -0
  139. package/dist/tools/connections/index.d.ts.map +1 -0
  140. package/dist/tools/connections/index.js +1047 -0
  141. package/dist/tools/connections/instance.d.ts +3 -0
  142. package/dist/tools/connections/instance.d.ts.map +1 -0
  143. package/dist/tools/connections/json-rpc.d.ts +8 -0
  144. package/dist/tools/connections/json-rpc.d.ts.map +1 -0
  145. package/dist/tools/connections/limits.d.ts +36 -0
  146. package/dist/tools/connections/limits.d.ts.map +1 -0
  147. package/dist/tools/connections/mcp-client.d.ts +44 -0
  148. package/dist/tools/connections/mcp-client.d.ts.map +1 -0
  149. package/dist/tools/connections/mcp.d.ts +5 -0
  150. package/dist/tools/connections/mcp.d.ts.map +1 -0
  151. package/dist/tools/connections/mount.d.ts +12 -0
  152. package/dist/tools/connections/mount.d.ts.map +1 -0
  153. package/dist/tools/connections/openapi-document.d.ts +4 -0
  154. package/dist/tools/connections/openapi-document.d.ts.map +1 -0
  155. package/dist/tools/connections/openapi.d.ts +5 -0
  156. package/dist/tools/connections/openapi.d.ts.map +1 -0
  157. package/dist/tools/connections/runtime.d.ts +29 -0
  158. package/dist/tools/connections/runtime.d.ts.map +1 -0
  159. package/dist/tools/connections/schema-budget.d.ts +8 -0
  160. package/dist/tools/connections/schema-budget.d.ts.map +1 -0
  161. package/dist/tools/connections/sse-frames.d.ts +7 -0
  162. package/dist/tools/connections/sse-frames.d.ts.map +1 -0
  163. package/dist/tools/connections/sse-session.d.ts +26 -0
  164. package/dist/tools/connections/sse-session.d.ts.map +1 -0
  165. package/dist/tools/connections/ssrf.d.ts +15 -0
  166. package/dist/tools/connections/ssrf.d.ts.map +1 -0
  167. package/dist/tools/connections/types.d.ts +60 -0
  168. package/dist/tools/connections/types.d.ts.map +1 -0
  169. package/dist/tools/durability-context.d.ts +7 -0
  170. package/dist/tools/durability-context.d.ts.map +1 -0
  171. package/dist/tools/execute.d.ts +2 -0
  172. package/dist/tools/execute.d.ts.map +1 -1
  173. package/dist/tools/registry.d.ts +38 -0
  174. package/dist/tools/registry.d.ts.map +1 -0
  175. package/dist/tools/runtime-tool.d.ts +4 -0
  176. package/dist/tools/runtime-tool.d.ts.map +1 -1
  177. package/dist/tools.d.ts +1 -0
  178. package/dist/tools.d.ts.map +1 -1
  179. package/dist/tools.js +83 -26
  180. package/llms-full.txt +386 -16
  181. package/llms.txt +1 -0
  182. package/package.json +9 -1
  183. package/dist/index-dre2ywck.js +0 -48
  184. package/dist/{index-evcnmsc3.js → index-t7fkyxcr.js} +3 -3
@@ -1,1255 +1,19 @@
1
1
  import {
2
- missingSandboxRestrictions,
3
- probeAgentProcessSandbox
4
- } from "./index-gskm1k5a.js";
5
- import {
6
- assertContainedFileCurrent,
7
- assertContainedParentCurrent,
8
- containedEntryMetadata,
9
- listContainedDirectory,
10
- missingContainedDirectories,
11
- openContainedFile,
12
- openContainedParent,
13
- openContainedParentFile,
14
- readContainedUtf8Handle,
15
- scanContainedFiles,
16
- writeContainedFile
17
- } from "./index-t7gh5w3v.js";
18
- import {
19
- defineRuntimeTool
20
- } from "./index-kc6h6hg0.js";
21
- import"./index-r159gjwy.js";
22
- import"./index-cby4ar3v.js";
2
+ AgentCodingToolAuthorizationSchema,
3
+ AgentCodingToolLimitsSchema,
4
+ AgentCodingToolPathAuthorizationSchema,
5
+ createAgentCodingTools
6
+ } from "./index-cp20z0n6.js";
7
+ import"./index-bn2cjajt.js";
8
+ import"./index-gskm1k5a.js";
23
9
  import"./index-2bxcmqkb.js";
24
10
  import"./index-6k1937bx.js";
25
- import {
26
- AppError,
27
- conflict,
28
- forbidden
29
- } from "./index-0w9abg87.js";
11
+ import"./index-0w9abg87.js";
30
12
  import"./index-kzfs85xp.js";
13
+ import"./index-kc6h6hg0.js";
14
+ import"./index-r159gjwy.js";
15
+ import"./index-cby4ar3v.js";
31
16
  import"./index-77fekveh.js";
32
-
33
- // src/agent-runtime/coding-tools.ts
34
- import path4 from "node:path";
35
- import { z as z4 } from "zod";
36
-
37
- // src/agent-runtime/coding-tool-contract.ts
38
- import { z } from "zod";
39
- var AgentCodingToolLimitsSchema = z.object({
40
- maxPathBytes: z.int().positive(),
41
- maxReadBytes: z.int().positive(),
42
- maxWriteBytes: z.int().positive(),
43
- maxShellArguments: z.int().positive(),
44
- maxShellArgumentBytes: z.int().positive(),
45
- maxShellOutputBytes: z.int().positive(),
46
- maxArtifactBytes: z.int().positive(),
47
- maxListEntries: z.int().positive(),
48
- maxSearchResults: z.int().positive(),
49
- maxSearchFiles: z.int().positive(),
50
- maxSearchDepth: z.int().nonnegative(),
51
- shellTimeoutMs: z.int().positive(),
52
- shellTerminationGraceMs: z.int().positive()
53
- }).strict();
54
- var AgentCodingToolAuthorizationSchema = z.discriminatedUnion("operation", [
55
- z.object({ operation: z.literal("read"), path: z.string().min(1) }).strict(),
56
- z.object({
57
- operation: z.literal("write"),
58
- path: z.string().min(1),
59
- bytes: z.int().nonnegative(),
60
- overwrite: z.boolean(),
61
- createsDirectories: z.array(z.string().min(1))
62
- }).strict(),
63
- z.object({
64
- operation: z.literal("search"),
65
- query: z.string().min(1),
66
- mode: z.enum(["path", "content"])
67
- }).strict(),
68
- z.object({
69
- operation: z.literal("edit"),
70
- path: z.string().min(1),
71
- baseSha256: z.string().length(64),
72
- resultSha256: z.string().length(64),
73
- resultBytes: z.int().nonnegative(),
74
- replacements: z.int().positive(),
75
- dryRun: z.boolean()
76
- }).strict(),
77
- z.object({ operation: z.literal("list"), path: z.string().min(1) }).strict(),
78
- z.object({
79
- operation: z.literal("glob"),
80
- pattern: z.string().min(1),
81
- path: z.string().min(1)
82
- }).strict(),
83
- z.object({
84
- operation: z.literal("artifact-read"),
85
- reference: z.string().min(1),
86
- offset: z.int().nonnegative(),
87
- maxBytes: z.int().positive()
88
- }).strict(),
89
- z.object({
90
- operation: z.literal("artifact-search"),
91
- reference: z.string().min(1),
92
- query: z.string().min(1),
93
- maxMatches: z.int().positive()
94
- }).strict(),
95
- z.object({
96
- operation: z.literal("shell"),
97
- executable: z.string().min(1),
98
- args: z.array(z.string()),
99
- cwd: z.string().min(1)
100
- }).strict()
101
- ]);
102
- var AgentCodingToolPathAuthorizationSchema = z.object({ path: z.string().min(1) }).strict();
103
- var FileReadInputSchema = z.object({
104
- path: z.string().min(1),
105
- offset: z.int().nonnegative().default(0),
106
- maxBytes: z.int().positive().optional()
107
- }).strict();
108
- var FileReadOutputSchema = z.object({
109
- path: z.string().min(1),
110
- text: z.string(),
111
- bytes: z.int().nonnegative(),
112
- sha256: z.string().length(64),
113
- truncated: z.boolean(),
114
- nextOffset: z.int().nonnegative().optional()
115
- }).strict();
116
- var FileWriteInputSchema = z.object({
117
- path: z.string().min(1),
118
- content: z.string(),
119
- overwrite: z.boolean().default(false)
120
- }).strict();
121
- var FileWriteOutputSchema = z.object({
122
- path: z.string().min(1),
123
- bytes: z.int().nonnegative(),
124
- createdDirectories: z.array(z.string().min(1))
125
- }).strict();
126
- function createShellInputSchema(executableNames) {
127
- if (executableNames.length === 0) {
128
- throw new Error("A command tool requires at least one executable alias");
129
- }
130
- return z.object({
131
- executable: z.enum(executableNames),
132
- args: z.array(z.string()).default([]),
133
- cwd: z.string().min(1).default(".")
134
- }).strict();
135
- }
136
- var ShellInputSchema = z.object({
137
- executable: z.string().min(1),
138
- args: z.array(z.string()).default([]),
139
- cwd: z.string().min(1).default(".")
140
- }).strict();
141
- var ShellOutputSchema = z.object({
142
- executable: z.string().min(1),
143
- exitCode: z.int().nullable(),
144
- signal: z.string().nullable(),
145
- stdout: z.string(),
146
- stderr: z.string(),
147
- outcome: z.enum(["exited", "timeout", "output-limit", "cancelled"]),
148
- artifact: z.object({
149
- reference: z.string().min(1),
150
- bytes: z.int().nonnegative(),
151
- truncated: z.boolean(),
152
- headBytes: z.int().nonnegative(),
153
- tailBytes: z.int().nonnegative(),
154
- omittedBytes: z.int().nonnegative()
155
- }).strict().optional()
156
- }).strict();
157
- var DEFAULT_LIMITS = {
158
- maxPathBytes: 4096,
159
- maxReadBytes: 262144,
160
- maxWriteBytes: 262144,
161
- maxShellArguments: 128,
162
- maxShellArgumentBytes: 65536,
163
- maxShellOutputBytes: 262144,
164
- maxArtifactBytes: 4194304,
165
- maxListEntries: 500,
166
- maxSearchResults: 100,
167
- maxSearchFiles: 1e4,
168
- maxSearchDepth: 32,
169
- shellTimeoutMs: 30000,
170
- shellTerminationGraceMs: 250
171
- };
172
- function resolveCodingToolLimits(input) {
173
- return AgentCodingToolLimitsSchema.parse({ ...DEFAULT_LIMITS, ...input });
174
- }
175
-
176
- // src/agent-runtime/coding-tool-files.ts
177
- import { createHash } from "node:crypto";
178
- import { realpath as realpath2 } from "node:fs/promises";
179
-
180
- // src/agent-runtime/coding-tool-paths.ts
181
- import { lstat, realpath } from "node:fs/promises";
182
- import path from "node:path";
183
-
184
- // src/agent-runtime/coding-tool-refusals.ts
185
- var REFUSAL_STATUS = {
186
- BAD_REQUEST: 400,
187
- FORBIDDEN: 403,
188
- NOT_FOUND: 404,
189
- CONFLICT: 409,
190
- SANDBOX_UNAVAILABLE: 503,
191
- SANDBOX_INSUFFICIENT: 503
192
- };
193
- function codingRefusal(code, message, options = {}) {
194
- throw new AppError(code, message, REFUSAL_STATUS[code], { message, ...options.details }, options.hint);
195
- }
196
- function codingPathRefusal(code, message, path, options = {}) {
197
- codingRefusal(code, message, {
198
- details: { path, ...options.details },
199
- ...options.hint && { hint: options.hint }
200
- });
201
- }
202
- function refuseMissingCodingPath(error, relative) {
203
- const code = error?.code;
204
- if (error instanceof Error && error.message.includes("is not a regular file")) {
205
- codingPathRefusal("BAD_REQUEST", "This path is not a regular file", relative, {
206
- hint: "Use list_directory to see what is here."
207
- });
208
- }
209
- if (code === "ENOENT") {
210
- codingPathRefusal("NOT_FOUND", "This path does not exist", relative, {
211
- hint: "Use list_directory or glob to find the right path."
212
- });
213
- }
214
- if (code === "ENOTDIR") {
215
- codingPathRefusal("BAD_REQUEST", "A segment of this path is not a directory", relative, {
216
- hint: "Check the path with list_directory."
217
- });
218
- }
219
- if (code === "ELOOP") {
220
- codingPathRefusal("FORBIDDEN", "This path is a symlink, which is not followed", relative, {
221
- hint: "Read or write the target directly, by its own workspace-relative path."
222
- });
223
- }
224
- if (code === "EISDIR") {
225
- codingPathRefusal("BAD_REQUEST", "This path is not a regular file", relative, {
226
- hint: "Use list_directory to see what is here."
227
- });
228
- }
229
- throw error;
230
- }
231
-
232
- // src/agent-runtime/coding-tool-paths.ts
233
- function within(root, target) {
234
- const relative = path.relative(root, target);
235
- return relative === "" || !relative.startsWith(`..${path.sep}`) && relative !== ".." && !path.isAbsolute(relative);
236
- }
237
- function inputPath(root, requested, maxPathBytes) {
238
- if (new TextEncoder().encode(requested).byteLength > maxPathBytes) {
239
- codingRefusal("BAD_REQUEST", `Path exceeds the ${maxPathBytes}-byte limit`, {
240
- details: { maxPathBytes },
241
- hint: "Use a shorter workspace-relative path."
242
- });
243
- }
244
- if (path.isAbsolute(requested)) {
245
- const relative = path.relative(root, requested);
246
- const recoveryPath = within(root, requested) ? relative.split(path.sep).join("/") || "." : undefined;
247
- codingRefusal("BAD_REQUEST", "Paths must be relative to the workspace root", {
248
- details: { path: requested, ...recoveryPath && { recoveryPath } },
249
- hint: recoveryPath ? `Use the workspace-relative path \`${recoveryPath}\`.` : "Pass a path relative to the workspace root; absolute paths outside it have no workspace-relative equivalent."
250
- });
251
- }
252
- const resolved = path.resolve(root, requested);
253
- if (!within(root, resolved)) {
254
- codingRefusal("FORBIDDEN", "Path escapes the workspace root", {
255
- details: { path: requested },
256
- hint: "Stay inside the workspace; `..` segments that leave the root are refused."
257
- });
258
- }
259
- return resolved;
260
- }
261
- function boundedCodingRelativePath(root, requested, maxPathBytes) {
262
- if (path.isAbsolute(requested))
263
- inputPath(root, requested, maxPathBytes);
264
- if (requested.includes("\\")) {
265
- codingRefusal("FORBIDDEN", "Path separators must be `/`", {
266
- details: { path: requested },
267
- hint: "Use `/` between segments; a backslash is refused because the workspace walk reads it as a separator."
268
- });
269
- }
270
- const segments = requested.split("/");
271
- if (segments.some((segment) => segment === "" || segment === "." || segment === "..")) {
272
- codingRefusal("FORBIDDEN", "Path segments must name real entries under the root", {
273
- details: { path: requested },
274
- hint: "Pass a plain workspace-relative path — `.`, `..` and empty segments are refused."
275
- });
276
- }
277
- inputPath(root, requested, maxPathBytes);
278
- return requested;
279
- }
280
- async function existingCodingPath(root, requested, maxPathBytes) {
281
- const resolved = await realpath(inputPath(root, requested, maxPathBytes));
282
- if (!within(root, resolved))
283
- throw new Error("Coding tool path resolves outside its root");
284
- return { absolute: resolved, relative: path.relative(root, resolved) || "." };
285
- }
286
- async function authorizeCodingTool(config, request) {
287
- const parsed = AgentCodingToolAuthorizationSchema.parse(request);
288
- if (!await config.authorize(parsed))
289
- forbidden("Coding tool permission denied");
290
- }
291
- async function isCodingPathAuthorized(config, path2) {
292
- if (!config.authorizePath)
293
- return true;
294
- const parsed = AgentCodingToolPathAuthorizationSchema.parse({ path: path2 });
295
- return await config.authorizePath(parsed);
296
- }
297
- async function authorizeCodingPath(config, path2) {
298
- if (!await isCodingPathAuthorized(config, path2)) {
299
- codingRefusal("FORBIDDEN", "Coding tool path permission denied", {
300
- details: { path: path2 },
301
- hint: "The host policy refused this path or a directory above it."
302
- });
303
- }
304
- }
305
- async function authorizeCodingPathChain(config, relative) {
306
- if (!config.authorizePath)
307
- return;
308
- const segments = relative === "." ? ["."] : relative.split("/");
309
- for (let depth = 1;depth <= segments.length; depth += 1) {
310
- await authorizeCodingPath(config, segments.slice(0, depth).join("/"));
311
- }
312
- }
313
- var codingPathLocks = new Map;
314
- async function withCodingPathLock(target, work) {
315
- const previous = codingPathLocks.get(target) ?? Promise.resolve();
316
- let release = () => {
317
- return;
318
- };
319
- const current = new Promise((resolve) => {
320
- release = resolve;
321
- });
322
- const queued = previous.then(() => current);
323
- codingPathLocks.set(target, queued);
324
- await previous;
325
- try {
326
- return await work();
327
- } finally {
328
- release();
329
- if (codingPathLocks.get(target) === queued)
330
- codingPathLocks.delete(target);
331
- }
332
- }
333
- function textOccurrences(source, search) {
334
- let count = 0;
335
- let offset = 0;
336
- while (offset <= source.length - search.length) {
337
- const found = source.indexOf(search, offset);
338
- if (found < 0)
339
- break;
340
- count += 1;
341
- offset = found + search.length;
342
- }
343
- return count;
344
- }
345
-
346
- // src/agent-runtime/coding-tool-files.ts
347
- async function digestFile(handle, size) {
348
- const hash = createHash("sha256");
349
- const chunk = Buffer.alloc(Math.min(64 * 1024, Math.max(1, size)));
350
- for (let position = 0;position < size; ) {
351
- const length = Math.min(chunk.byteLength, size - position);
352
- const { bytesRead } = await handle.read(chunk, 0, length, position);
353
- if (bytesRead === 0)
354
- break;
355
- hash.update(chunk.subarray(0, bytesRead));
356
- position += bytesRead;
357
- }
358
- return hash.digest("hex");
359
- }
360
- function createFileCodingTools(config, limits) {
361
- const read = defineRuntimeTool({
362
- name: "read_file",
363
- description: "Read a bounded UTF-8 slice from a host-authorized workspace file.",
364
- identity: { serviceName: "coding", action: "read-file", method: "POST" },
365
- input: FileReadInputSchema,
366
- output: FileReadOutputSchema,
367
- transports: ["AGENT"],
368
- handler: async ({ input }) => {
369
- const root = await realpath2(config.root);
370
- const relative = boundedCodingRelativePath(root, input.path, limits.maxPathBytes);
371
- await authorizeCodingPathChain(config, relative);
372
- const handle = await openContainedFile(root, relative).catch((error) => refuseMissingCodingPath(error, relative));
373
- const maximum = Math.min(input.maxBytes ?? limits.maxReadBytes, limits.maxReadBytes);
374
- let selected;
375
- let size;
376
- let sha256;
377
- try {
378
- await authorizeCodingTool(config, { operation: "read", path: relative });
379
- await assertContainedFileCurrent(root, relative, handle);
380
- const metadata = await handle.stat();
381
- if (!metadata.isFile()) {
382
- codingPathRefusal("BAD_REQUEST", "This path is not a regular file", relative, {
383
- hint: "Use list_directory to see what is here."
384
- });
385
- }
386
- size = metadata.size;
387
- const length = Math.min(maximum, Math.max(0, size - input.offset));
388
- selected = Buffer.alloc(length);
389
- const { bytesRead } = await handle.read(selected, 0, length, input.offset);
390
- selected = selected.subarray(0, bytesRead);
391
- sha256 = await digestFile(handle, size);
392
- } finally {
393
- await handle.close();
394
- }
395
- const text = new TextDecoder("utf-8", { fatal: true }).decode(selected);
396
- const end = input.offset + selected.byteLength;
397
- const truncated = end < size;
398
- return {
399
- path: relative,
400
- text,
401
- bytes: selected.byteLength,
402
- sha256,
403
- truncated,
404
- ...truncated && { nextOffset: end }
405
- };
406
- }
407
- });
408
- const write = defineRuntimeTool({
409
- name: "write_file",
410
- description: "Write bounded UTF-8 content to a host-authorized workspace file.",
411
- identity: { serviceName: "coding", action: "write-file", method: "POST" },
412
- input: FileWriteInputSchema,
413
- output: FileWriteOutputSchema,
414
- transports: ["AGENT"],
415
- handler: async ({ input }) => {
416
- const root = await realpath2(config.root);
417
- const relative = boundedCodingRelativePath(root, input.path, limits.maxPathBytes);
418
- await authorizeCodingPathChain(config, relative);
419
- const bytes = Buffer.byteLength(input.content);
420
- if (bytes > limits.maxWriteBytes) {
421
- codingPathRefusal("BAD_REQUEST", `The content is ${bytes} bytes, over the ${limits.maxWriteBytes}-byte limit`, relative, {
422
- details: { bytes, maxWriteBytes: limits.maxWriteBytes },
423
- hint: "Write a smaller file, or split it."
424
- });
425
- }
426
- const createsDirectories = await missingContainedDirectories(root, relative).catch((error) => {
427
- if (error.code === "ENOTDIR") {
428
- codingPathRefusal("CONFLICT", "A segment of this path is not a directory", relative, {
429
- hint: "Write under a different path, or remove the file blocking it."
430
- });
431
- }
432
- throw error;
433
- });
434
- await authorizeCodingTool(config, {
435
- operation: "write",
436
- path: relative,
437
- bytes,
438
- overwrite: input.overwrite,
439
- createsDirectories
440
- });
441
- const parent = await openContainedParent(root, relative, { create: true }).catch((error) => {
442
- const code = error.code;
443
- if (code === "ENOTDIR" || error instanceof Error && error.message.includes("ancestor is not a directory")) {
444
- codingPathRefusal("CONFLICT", "A segment of this path is not a directory", relative, {
445
- details: { createsDirectories },
446
- hint: "Write under a different path, or remove the file blocking it."
447
- });
448
- }
449
- throw error;
450
- });
451
- try {
452
- await assertContainedParentCurrent(root, relative, parent.handle);
453
- const current = await containedEntryMetadata(parent);
454
- if (input.overwrite) {
455
- if (current?.isSymbolicLink()) {
456
- codingPathRefusal("FORBIDDEN", "This path is a symlink", relative, {
457
- hint: "Coding tools never write through symlinks; write to the target directly."
458
- });
459
- }
460
- await writeContainedFile({
461
- parent,
462
- content: input.content,
463
- replace: true,
464
- ...current && { mode: current.mode }
465
- });
466
- } else {
467
- if (current) {
468
- codingPathRefusal("CONFLICT", "This file already exists", relative, {
469
- hint: "Pass overwrite: true to replace it, or edit_file to change part of it."
470
- });
471
- }
472
- await writeContainedFile({ parent, content: input.content, replace: false });
473
- }
474
- } finally {
475
- await parent.handle.close();
476
- }
477
- return { path: relative, bytes, createdDirectories: createsDirectories };
478
- }
479
- });
480
- return [read, write];
481
- }
482
-
483
- // src/agent-runtime/coding-tool-listing.ts
484
- import { realpath as realpath3 } from "node:fs/promises";
485
- import path2 from "node:path";
486
- import { z as z2 } from "zod";
487
- var DEFAULT_EXCLUDED_DIRECTORIES = [
488
- ".git",
489
- ".next",
490
- ".stitchkit",
491
- "build",
492
- "coverage",
493
- "dist",
494
- "node_modules"
495
- ];
496
- var ListInputSchema = z2.object({ path: z2.string().min(1).default(".") }).strict();
497
- var ListOutputSchema = z2.object({
498
- path: z2.string().min(1),
499
- entries: z2.array(z2.object({
500
- name: z2.string().min(1),
501
- kind: z2.enum(["file", "directory", "symlink", "other"]),
502
- bytes: z2.int().nonnegative().optional(),
503
- excluded: z2.boolean().optional()
504
- }).strict()),
505
- truncated: z2.boolean()
506
- }).strict();
507
- var GlobInputSchema = z2.object({ pattern: z2.string().min(1), path: z2.string().min(1).default(".") }).strict();
508
- var GlobOutputSchema = z2.object({
509
- paths: z2.array(z2.string().min(1)),
510
- truncated: z2.boolean(),
511
- resultTruncated: z2.boolean(),
512
- skippedDirectories: z2.int().nonnegative(),
513
- scannedFiles: z2.int().nonnegative()
514
- }).strict();
515
- function compileWorkspaceGlob(pattern) {
516
- let source = "";
517
- for (let index = 0;index < pattern.length; index += 1) {
518
- const character = pattern[index];
519
- if (character === "*") {
520
- if (pattern[index + 1] === "*") {
521
- if (pattern[index + 2] === "/") {
522
- source += "(?:[^/]+/)*";
523
- index += 2;
524
- continue;
525
- }
526
- source += ".*";
527
- index += 1;
528
- continue;
529
- }
530
- source += "[^/]*";
531
- continue;
532
- }
533
- if (character === "?") {
534
- source += "[^/]";
535
- continue;
536
- }
537
- source += character?.replace(/[.*+?^${}()|[\]\\]/g, "\\$&") ?? "";
538
- }
539
- return new RegExp(`^${source}$`);
540
- }
541
- function createListingCodingTools(config, limits) {
542
- const excluded = new Set(config.search?.excludeDirectories ?? DEFAULT_EXCLUDED_DIRECTORIES);
543
- const list = defineRuntimeTool({
544
- name: "list_directory",
545
- description: "List the direct entries of one workspace directory.",
546
- identity: { serviceName: "coding", action: "list-directory", method: "POST" },
547
- input: ListInputSchema,
548
- output: ListOutputSchema,
549
- transports: ["AGENT"],
550
- handler: async ({ input }) => {
551
- const root = await realpath3(config.root);
552
- const relative = input.path === "." ? "." : boundedCodingRelativePath(root, input.path, limits.maxPathBytes);
553
- await authorizeCodingPathChain(config, relative);
554
- await authorizeCodingTool(config, { operation: "list", path: relative });
555
- const listing = await listContainedDirectory(root, relative, limits.maxListEntries, (candidate) => isCodingPathAuthorized(config, candidate)).catch((error) => {
556
- if (error instanceof Error && error.message.includes("ancestor is not a directory")) {
557
- codingPathRefusal("BAD_REQUEST", "This path is not a directory", relative, {
558
- hint: "Pass a directory; use read_file for a file."
559
- });
560
- }
561
- if (error.code === "ENOENT") {
562
- codingPathRefusal("NOT_FOUND", "This directory does not exist", relative, {
563
- hint: "List a parent directory to see what is there."
564
- });
565
- }
566
- return refuseMissingCodingPath(error, relative);
567
- });
568
- return {
569
- path: relative,
570
- entries: listing.entries.map((entry) => ({
571
- ...entry,
572
- ...entry.kind === "directory" && excluded.has(entry.name) && { excluded: true }
573
- })),
574
- truncated: listing.truncated
575
- };
576
- }
577
- });
578
- const glob = defineRuntimeTool({
579
- name: "glob",
580
- description: "List workspace files matching a `**`/`*`/`?` pattern.",
581
- identity: { serviceName: "coding", action: "glob", method: "POST" },
582
- input: GlobInputSchema,
583
- output: GlobOutputSchema,
584
- transports: ["AGENT"],
585
- handler: async ({ input }) => {
586
- const root = await realpath3(config.root);
587
- const relative = input.path === "." ? "." : boundedCodingRelativePath(root, input.path, limits.maxPathBytes);
588
- await authorizeCodingPathChain(config, relative);
589
- await authorizeCodingTool(config, {
590
- operation: "glob",
591
- pattern: input.pattern,
592
- path: relative
593
- });
594
- let matcher;
595
- try {
596
- matcher = compileWorkspaceGlob(input.pattern);
597
- } catch {
598
- codingRefusal("BAD_REQUEST", "This glob pattern could not be compiled", {
599
- details: { pattern: input.pattern },
600
- hint: "Use only `**`, `*` and `?`."
601
- });
602
- }
603
- const scan = await scanContainedFiles({
604
- root,
605
- maxDepth: limits.maxSearchDepth,
606
- maxFiles: limits.maxSearchFiles,
607
- symlinks: "skip",
608
- excludeDirectory: (directory) => directory.split(path2.sep).some((segment) => excluded.has(segment)),
609
- authorizePath: (candidate) => isCodingPathAuthorized(config, candidate)
610
- });
611
- const prefix = relative === "." ? "" : `${relative}${path2.sep}`;
612
- const matched = scan.files.filter((file) => file.relative.startsWith(prefix)).map((file) => file.relative).filter((candidate) => matcher.test(candidate.slice(prefix.length))).sort();
613
- const paths = matched.slice(0, limits.maxSearchResults);
614
- return {
615
- paths,
616
- truncated: scan.truncated,
617
- resultTruncated: matched.length > paths.length,
618
- skippedDirectories: scan.skippedDirectories,
619
- scannedFiles: scan.files.length
620
- };
621
- }
622
- });
623
- return [list, glob];
624
- }
625
-
626
- // src/agent-runtime/coding-tool-search-patch.ts
627
- import { createHash as createHash2 } from "node:crypto";
628
- import { realpath as realpath4 } from "node:fs/promises";
629
- import path3 from "node:path";
630
- import { z as z3 } from "zod";
631
- var SearchInputSchema = z3.object({
632
- query: z3.string().min(1),
633
- mode: z3.enum(["path", "content"]).default("content"),
634
- regex: z3.boolean().default(false),
635
- context: z3.int().nonnegative().max(10).default(0),
636
- include: z3.string().min(1).describe("Anchored `**`/`*`/`?` pattern matched against the whole workspace-relative path. `*` does not cross `/`; use `**/` to include matching files in subdirectories.").optional()
637
- }).strict();
638
- var SearchOutputSchema = z3.object({
639
- matches: z3.array(z3.object({
640
- path: z3.string().min(1),
641
- line: z3.int().positive().optional(),
642
- text: z3.string().optional(),
643
- before: z3.array(z3.string()).optional(),
644
- after: z3.array(z3.string()).optional()
645
- }).strict()),
646
- truncated: z3.boolean(),
647
- scannedFiles: z3.int().nonnegative().describe("Files admitted by include and host authorization that reached path or content comparison."),
648
- skippedDirectories: z3.int().nonnegative(),
649
- skippedSymlinks: z3.int().nonnegative(),
650
- hint: z3.string().min(1).optional()
651
- }).strict();
652
- function compileSearchRegex(pattern) {
653
- if (/\\[1-9]/.test(pattern) || /\(\?<?[=!]/.test(pattern)) {
654
- codingRefusal("BAD_REQUEST", "This pattern uses a construct search does not allow", {
655
- details: { pattern },
656
- hint: "Backreferences and lookaround are refused because they cannot be bounded. Rewrite without them."
657
- });
658
- }
659
- try {
660
- return new RegExp(pattern);
661
- } catch (error) {
662
- codingRefusal("BAD_REQUEST", "This pattern is not a valid regular expression", {
663
- details: { pattern, reason: error instanceof Error ? error.message : "unknown" },
664
- hint: "Fix the expression, or pass regex: false to search for it literally."
665
- });
666
- }
667
- }
668
- var MAX_SEARCHED_LINE_BYTES = 8192;
669
- var EditInputSchema = z3.object({
670
- path: z3.string().min(1),
671
- oldText: z3.string().min(1),
672
- newText: z3.string(),
673
- replaceAll: z3.boolean().default(false),
674
- expectedSha256: z3.string().length(64).optional(),
675
- dryRun: z3.boolean().default(false)
676
- }).strict();
677
- var EditOutputSchema = z3.object({
678
- path: z3.string().min(1),
679
- baseSha256: z3.string().length(64),
680
- sha256: z3.string().length(64),
681
- replacements: z3.int().positive(),
682
- bytes: z3.int().nonnegative(),
683
- applied: z3.boolean()
684
- }).strict();
685
- function sha256(value) {
686
- return createHash2("sha256").update(value).digest("hex");
687
- }
688
- function createSearchAndPatchCodingTools(config, limits) {
689
- const search = defineRuntimeTool({
690
- name: "search_files",
691
- description: "Search bounded relative paths or UTF-8 file content under the workspace root.",
692
- identity: { serviceName: "coding", action: "search", method: "POST" },
693
- input: SearchInputSchema,
694
- output: SearchOutputSchema,
695
- transports: ["AGENT"],
696
- handler: async ({ input }) => {
697
- await authorizeCodingTool(config, {
698
- operation: "search",
699
- query: input.query,
700
- mode: input.mode
701
- });
702
- const root = await realpath4(config.root);
703
- await authorizeCodingPath(config, ".");
704
- const excluded = new Set(config.search?.excludeDirectories ?? DEFAULT_EXCLUDED_DIRECTORIES);
705
- const included = input.include ? compileWorkspaceGlob(input.include) : null;
706
- let rejectedByInclude = 0;
707
- const scan = await scanContainedFiles({
708
- root,
709
- maxDepth: limits.maxSearchDepth,
710
- maxFiles: limits.maxSearchFiles,
711
- symlinks: "skip",
712
- ...input.mode === "content" && {
713
- readMaxBytes: limits.maxReadBytes,
714
- skipUnreadable: true
715
- },
716
- excludeDirectory: (relative) => relative.split(path3.sep).some((segment) => excluded.has(segment)),
717
- authorizePath: async (candidate, kind) => {
718
- if (kind === "file" && included && !included.test(candidate)) {
719
- rejectedByInclude += 1;
720
- return false;
721
- }
722
- return await isCodingPathAuthorized(config, candidate);
723
- }
724
- });
725
- const matcher = input.regex ? compileSearchRegex(input.query) : null;
726
- const hit = (line) => {
727
- if (line.length > MAX_SEARCHED_LINE_BYTES)
728
- return false;
729
- if (matcher) {
730
- matcher.lastIndex = 0;
731
- return matcher.test(line);
732
- }
733
- return line.includes(input.query);
734
- };
735
- const matches = [];
736
- let truncated = false;
737
- for (const file of scan.files) {
738
- if (input.mode === "path") {
739
- if (hit(file.relative))
740
- matches.push({ path: file.relative });
741
- } else {
742
- const text = file.content?.text;
743
- if (text === undefined)
744
- continue;
745
- const lines = text.split(`
746
- `);
747
- for (let index = 0;index < lines.length; index += 1) {
748
- const line = lines[index];
749
- if (line === undefined || !hit(line))
750
- continue;
751
- matches.push({
752
- path: file.relative,
753
- line: index + 1,
754
- text: line,
755
- ...input.context > 0 && {
756
- before: lines.slice(Math.max(0, index - input.context), index),
757
- after: lines.slice(index + 1, index + 1 + input.context)
758
- }
759
- });
760
- if (matches.length >= limits.maxSearchResults)
761
- break;
762
- }
763
- }
764
- if (matches.length >= limits.maxSearchResults) {
765
- truncated = true;
766
- break;
767
- }
768
- }
769
- return {
770
- matches,
771
- truncated: truncated || scan.truncated,
772
- scannedFiles: scan.files.length,
773
- skippedDirectories: scan.skippedDirectories,
774
- skippedSymlinks: scan.skippedSymlinks,
775
- ...input.include && scan.files.length === 0 && rejectedByInclude > 0 && {
776
- hint: `The anchored include pattern \`${input.include}\` rejected all ${rejectedByInclude} ${rejectedByInclude === 1 ? "file" : "files"} the scan reached. \`*\` does not cross \`/\`; use a pattern such as \`**/*.ts\` for matching files in subdirectories.`
777
- }
778
- };
779
- }
780
- });
781
- const edit = defineRuntimeTool({
782
- name: "edit_file",
783
- description: "Replace an exact snippet in one workspace file.",
784
- identity: { serviceName: "coding", action: "edit-file", method: "POST" },
785
- input: EditInputSchema,
786
- output: EditOutputSchema,
787
- transports: ["AGENT"],
788
- handler: async ({ input }) => {
789
- const root = await realpath4(config.root);
790
- const relative = boundedCodingRelativePath(root, input.path, limits.maxPathBytes);
791
- await authorizeCodingPathChain(config, relative);
792
- const parent = await openContainedParent(root, relative).catch((error) => refuseMissingCodingPath(error, relative));
793
- try {
794
- return await withCodingPathLock(`${root}:${relative}`, async () => {
795
- const handle = await openContainedParentFile(parent).catch((error) => refuseMissingCodingPath(error, relative));
796
- const read = await readContainedUtf8Handle(handle, limits.maxWriteBytes).finally(() => handle.close());
797
- const source = read.text;
798
- const baseSha256 = sha256(source);
799
- if (input.expectedSha256 !== undefined && baseSha256 !== input.expectedSha256) {
800
- conflict("The file changed since it was read", {
801
- message: "The file changed since it was read",
802
- path: relative,
803
- expectedSha256: input.expectedSha256,
804
- actualSha256: baseSha256
805
- });
806
- }
807
- const count = textOccurrences(source, input.oldText);
808
- if (count === 0) {
809
- codingPathRefusal("NOT_FOUND", "oldText does not appear in this file", relative, {
810
- details: { occurrences: 0 },
811
- hint: "Read the file and copy the snippet exactly, including indentation."
812
- });
813
- }
814
- if (!input.replaceAll && count !== 1) {
815
- codingPathRefusal("CONFLICT", `oldText appears ${count} times, so the edit is ambiguous`, relative, {
816
- details: { occurrences: count },
817
- hint: "Lengthen the snippet until it is unique, or pass replaceAll: true."
818
- });
819
- }
820
- const replacements = input.replaceAll ? count : 1;
821
- const changed = input.replaceAll ? source.replaceAll(input.oldText, input.newText) : source.replace(input.oldText, input.newText);
822
- const bytes = Buffer.byteLength(changed);
823
- const resultSha256 = sha256(changed);
824
- if (bytes > limits.maxWriteBytes) {
825
- codingPathRefusal("BAD_REQUEST", `The result would be ${bytes} bytes, over the ${limits.maxWriteBytes}-byte limit`, relative, {
826
- details: { bytes, maxWriteBytes: limits.maxWriteBytes },
827
- hint: "Make a smaller edit, or split the file."
828
- });
829
- }
830
- await authorizeCodingTool(config, {
831
- operation: "edit",
832
- path: relative,
833
- baseSha256,
834
- resultSha256,
835
- resultBytes: bytes,
836
- replacements,
837
- dryRun: input.dryRun
838
- });
839
- await assertContainedParentCurrent(root, relative, parent.handle);
840
- if (!input.dryRun) {
841
- await writeContainedFile({
842
- parent,
843
- content: changed,
844
- replace: true,
845
- mode: read.mode
846
- });
847
- }
848
- return {
849
- path: relative,
850
- baseSha256,
851
- sha256: resultSha256,
852
- replacements,
853
- bytes,
854
- applied: !input.dryRun
855
- };
856
- });
857
- } finally {
858
- await parent.handle.close();
859
- }
860
- }
861
- });
862
- return [search, edit];
863
- }
864
-
865
- // src/agent-runtime/coding-tool-shell.ts
866
- import { spawn } from "node:child_process";
867
- import { realpath as realpath5, stat } from "node:fs/promises";
868
- async function runShell(input) {
869
- const preview = (data, budget) => {
870
- if (data.byteLength <= budget) {
871
- return { data, headBytes: data.byteLength, tailBytes: 0, omittedBytes: 0 };
872
- }
873
- const headBytes = Math.ceil(budget / 2);
874
- const tailBytes = Math.floor(budget / 2);
875
- return {
876
- data: Buffer.concat([
877
- data.subarray(0, headBytes),
878
- data.subarray(data.byteLength - tailBytes)
879
- ]),
880
- headBytes,
881
- tailBytes,
882
- omittedBytes: data.byteLength - budget
883
- };
884
- };
885
- const stdoutHeader = Buffer.from(`--- stdout ---
886
- `);
887
- const stderrHeader = Buffer.from(`
888
- --- stderr ---
889
- `);
890
- const artifactPayloadLimit = input.maxArtifactBytes - stdoutHeader.byteLength - stderrHeader.byteLength;
891
- if (input.artifacts && artifactPayloadLimit < 0)
892
- throw new Error("maxArtifactBytes is smaller than the shell artifact envelope");
893
- if (input.signal?.aborted) {
894
- return ShellOutputSchema.parse({
895
- executable: input.executableName,
896
- exitCode: null,
897
- signal: null,
898
- stdout: "",
899
- stderr: "",
900
- outcome: "cancelled"
901
- });
902
- }
903
- return await new Promise((resolve, reject) => {
904
- const ownsProcessGroup = process.platform !== "win32";
905
- const child = spawn(input.executable, input.args, {
906
- cwd: input.cwd,
907
- env: input.environment,
908
- stdio: ["ignore", "pipe", "pipe"],
909
- detached: ownsProcessGroup
910
- });
911
- const stdout = [];
912
- const stderr = [];
913
- const artifactStdout = [];
914
- const artifactStderr = [];
915
- let retained = 0;
916
- let artifactBytes = 0;
917
- let outcome = "exited";
918
- let settled = false;
919
- let settlementTimer;
920
- const cancel = () => terminate("cancelled");
921
- const cleanup = () => {
922
- clearTimeout(timer);
923
- if (settlementTimer)
924
- clearTimeout(settlementTimer);
925
- input.signal?.removeEventListener("abort", cancel);
926
- };
927
- const persistAndResolve = async (exitCode, signal) => {
928
- if (settled)
929
- return;
930
- settled = true;
931
- cleanup();
932
- try {
933
- const hasArtifact = input.artifacts && artifactBytes > input.maxOutputBytes;
934
- const completeStdout = Buffer.concat(artifactStdout);
935
- const completeStderr = Buffer.concat(artifactStderr);
936
- const artifactData = hasArtifact ? Buffer.concat([stdoutHeader, completeStdout, stderrHeader, completeStderr]) : undefined;
937
- const stdoutBudget = completeStderr.byteLength === 0 ? input.maxOutputBytes : Math.ceil(input.maxOutputBytes / 2);
938
- const stderrBudget = completeStdout.byteLength === 0 ? input.maxOutputBytes : Math.floor(input.maxOutputBytes / 2);
939
- const stdoutPreview = hasArtifact ? preview(completeStdout, stdoutBudget) : {
940
- data: Buffer.concat(stdout),
941
- headBytes: retained,
942
- tailBytes: 0,
943
- omittedBytes: 0
944
- };
945
- const stderrPreview = hasArtifact ? preview(completeStderr, stderrBudget) : { data: Buffer.concat(stderr), headBytes: 0, tailBytes: 0, omittedBytes: 0 };
946
- const persisted = hasArtifact ? await input.artifacts?.write({
947
- mediaType: "text/plain; charset=utf-8",
948
- data: artifactData ?? new Uint8Array,
949
- ...input.authorization && { authorization: input.authorization }
950
- }) : undefined;
951
- resolve(ShellOutputSchema.parse({
952
- executable: input.executableName,
953
- exitCode,
954
- signal,
955
- stdout: stdoutPreview.data.toString("utf8"),
956
- stderr: stderrPreview.data.toString("utf8"),
957
- outcome,
958
- ...persisted && {
959
- artifact: {
960
- reference: persisted.reference,
961
- bytes: artifactData?.byteLength ?? 0,
962
- truncated: outcome === "output-limit",
963
- headBytes: stdoutPreview.headBytes + stderrPreview.headBytes,
964
- tailBytes: stdoutPreview.tailBytes + stderrPreview.tailBytes,
965
- omittedBytes: stdoutPreview.omittedBytes + stderrPreview.omittedBytes
966
- }
967
- }
968
- }));
969
- } catch (error) {
970
- reject(error);
971
- }
972
- };
973
- const killOwnedProcessGroup = () => {
974
- if (!ownsProcessGroup || child.pid === undefined)
975
- return false;
976
- try {
977
- process.kill(-child.pid, "SIGKILL");
978
- return true;
979
- } catch {
980
- return false;
981
- }
982
- };
983
- const boundStreamSettlement = () => {
984
- if (settlementTimer)
985
- return;
986
- settlementTimer = setTimeout(() => {
987
- child.stdout.destroy();
988
- child.stderr.destroy();
989
- persistAndResolve(child.exitCode, child.signalCode);
990
- }, input.terminationGraceMs);
991
- };
992
- const terminate = (reason) => {
993
- if (settled || outcome !== "exited")
994
- return;
995
- outcome = reason;
996
- if (!killOwnedProcessGroup())
997
- child.kill("SIGKILL");
998
- boundStreamSettlement();
999
- };
1000
- const timer = setTimeout(() => terminate("timeout"), input.timeoutMs);
1001
- timer.unref();
1002
- const retain = (target, artifactTarget, chunk) => {
1003
- const remaining = Math.max(0, input.maxOutputBytes - retained);
1004
- if (remaining > 0) {
1005
- const kept = chunk.subarray(0, remaining);
1006
- target.push(kept);
1007
- retained += kept.byteLength;
1008
- }
1009
- if (input.artifacts) {
1010
- const artifactRemaining = Math.max(0, artifactPayloadLimit - artifactBytes);
1011
- if (artifactRemaining > 0) {
1012
- const kept = chunk.subarray(0, artifactRemaining);
1013
- artifactTarget.push(kept);
1014
- artifactBytes += kept.byteLength;
1015
- }
1016
- if (chunk.byteLength > artifactRemaining)
1017
- terminate("output-limit");
1018
- } else if (chunk.byteLength > remaining)
1019
- terminate("output-limit");
1020
- };
1021
- child.stdout.on("data", (chunk) => retain(stdout, artifactStdout, chunk));
1022
- child.stderr.on("data", (chunk) => retain(stderr, artifactStderr, chunk));
1023
- child.on("error", (error) => {
1024
- if (settled)
1025
- return;
1026
- settled = true;
1027
- cleanup();
1028
- reject(error);
1029
- });
1030
- child.on("exit", (exitCode, signal) => {
1031
- if (outcome === "exited")
1032
- killOwnedProcessGroup();
1033
- boundStreamSettlement();
1034
- if (child.stdout.destroyed && child.stderr.destroyed) {
1035
- persistAndResolve(exitCode, signal);
1036
- }
1037
- });
1038
- child.on("close", (exitCode, signal) => void persistAndResolve(exitCode, signal));
1039
- input.signal?.addEventListener("abort", cancel, { once: true });
1040
- if (input.signal?.aborted)
1041
- cancel();
1042
- });
1043
- }
1044
- function createShellCodingTool(config, limits) {
1045
- const executables = config.executables ?? {};
1046
- const executableNames = Object.keys(executables).sort();
1047
- return defineRuntimeTool({
1048
- name: "run_command",
1049
- description: "Run a finite host-declared executable with bounded arguments, time and output.",
1050
- identity: { serviceName: "coding", action: "shell", method: "POST" },
1051
- input: createShellInputSchema(executableNames),
1052
- output: ShellOutputSchema,
1053
- transports: ["AGENT"],
1054
- handler: async ({ input, signal }) => {
1055
- if (input.args.length > limits.maxShellArguments) {
1056
- throw new Error("Coding tool shell arguments exceed maxShellArguments");
1057
- }
1058
- const argumentBytes = input.args.reduce((total, argument) => total + Buffer.byteLength(argument), 0);
1059
- if (argumentBytes > limits.maxShellArgumentBytes) {
1060
- throw new Error("Coding tool shell arguments exceed maxShellArgumentBytes");
1061
- }
1062
- const executable = executables[input.executable];
1063
- if (!executable)
1064
- throw new Error("Coding tool executable is not declared");
1065
- const root = await realpath5(config.root);
1066
- const cwd = await existingCodingPath(root, input.cwd === "." ? "." : boundedCodingRelativePath(root, input.cwd, limits.maxPathBytes), limits.maxPathBytes);
1067
- if (!(await stat(cwd.absolute)).isDirectory()) {
1068
- throw new Error("Coding tool cwd is not a directory");
1069
- }
1070
- const shellAuthorization = {
1071
- operation: "shell",
1072
- executable: input.executable,
1073
- args: input.args,
1074
- cwd: cwd.relative
1075
- };
1076
- await authorizeCodingTool(config, shellAuthorization);
1077
- let executablePath = executable;
1078
- let executableArgs = input.args;
1079
- let environment = config.environment ?? {};
1080
- if (config.sandbox) {
1081
- const grade = await probeAgentProcessSandbox(config.sandbox.adapter);
1082
- if (grade.grade === "unavailable") {
1083
- codingRefusal("SANDBOX_UNAVAILABLE", "The configured process sandbox is unavailable", {
1084
- details: { reason: grade.reason }
1085
- });
1086
- }
1087
- const missing = missingSandboxRestrictions(grade, config.sandbox.required);
1088
- if (missing.length > 0) {
1089
- codingRefusal("SANDBOX_INSUFFICIENT", "The process sandbox lacks required restrictions", {
1090
- details: { missing, grade: grade.grade }
1091
- });
1092
- }
1093
- try {
1094
- const prepared = await config.sandbox.adapter.prepare({
1095
- executable,
1096
- args: input.args,
1097
- cwd: cwd.absolute,
1098
- environment
1099
- });
1100
- executablePath = prepared.executable;
1101
- executableArgs = [...prepared.args];
1102
- environment = prepared.environment ?? environment;
1103
- } catch (error) {
1104
- const refreshed = await probeAgentProcessSandbox(config.sandbox.adapter, {
1105
- refresh: true
1106
- });
1107
- codingRefusal("SANDBOX_UNAVAILABLE", "The process sandbox failed while preparing a command", {
1108
- details: {
1109
- grade: refreshed.grade,
1110
- reason: error instanceof Error ? error.message : "unknown sandbox error"
1111
- }
1112
- });
1113
- }
1114
- }
1115
- return runShell({
1116
- executableName: input.executable,
1117
- executable: executablePath,
1118
- args: executableArgs,
1119
- cwd: cwd.absolute,
1120
- environment,
1121
- ...signal && { signal },
1122
- timeoutMs: limits.shellTimeoutMs,
1123
- terminationGraceMs: limits.shellTerminationGraceMs,
1124
- maxOutputBytes: limits.maxShellOutputBytes,
1125
- maxArtifactBytes: limits.maxArtifactBytes,
1126
- ...config.artifacts && { artifacts: config.artifacts },
1127
- authorization: shellAuthorization
1128
- });
1129
- }
1130
- });
1131
- }
1132
-
1133
- // src/agent-runtime/coding-tools.ts
1134
- var AGENT_CODING_TOOL_NAMES = {
1135
- editFile: "edit_file",
1136
- glob: "glob",
1137
- listDirectory: "list_directory",
1138
- readFile: "read_file",
1139
- readOutput: "read_output",
1140
- searchOutput: "search_output",
1141
- runCommand: "run_command",
1142
- searchFiles: "search_files",
1143
- writeFile: "write_file"
1144
- };
1145
- function createAgentCodingTools(config) {
1146
- if (!path4.isAbsolute(config.root))
1147
- throw new Error("Coding tool root must be absolute");
1148
- for (const executable of Object.values(config.executables ?? {})) {
1149
- if (!path4.isAbsolute(executable)) {
1150
- throw new Error("Coding tool executables must be absolute paths");
1151
- }
1152
- }
1153
- const limits = resolveCodingToolLimits(config.limits);
1154
- const artifactTools = [];
1155
- if (config.artifacts) {
1156
- const artifacts = config.artifacts;
1157
- artifactTools.push(defineRuntimeTool({
1158
- name: AGENT_CODING_TOOL_NAMES.readOutput,
1159
- description: "Read a bounded byte slice from one opaque coding output artifact.",
1160
- identity: { serviceName: "coding", action: "read-artifact", method: "POST" },
1161
- input: z4.object({
1162
- reference: z4.string().min(1),
1163
- offset: z4.int().nonnegative().default(0),
1164
- maxBytes: z4.int().positive().optional()
1165
- }).strict(),
1166
- output: z4.object({
1167
- reference: z4.string().min(1),
1168
- offset: z4.int().nonnegative(),
1169
- text: z4.string(),
1170
- bytes: z4.int().nonnegative(),
1171
- totalBytes: z4.int().nonnegative().optional()
1172
- }).strict(),
1173
- transports: ["AGENT"],
1174
- handler: async ({ input }) => {
1175
- const maxBytes = Math.min(input.maxBytes ?? limits.maxReadBytes, limits.maxReadBytes);
1176
- await authorizeCodingTool(config, {
1177
- operation: "artifact-read",
1178
- reference: input.reference,
1179
- offset: input.offset,
1180
- maxBytes
1181
- });
1182
- const originAuthorization = await artifacts.authorization?.(input.reference);
1183
- if (originAuthorization)
1184
- await authorizeCodingTool(config, originAuthorization);
1185
- const result = await artifacts.read({
1186
- reference: input.reference,
1187
- offset: input.offset,
1188
- maxBytes
1189
- });
1190
- if (result.data.byteLength > maxBytes)
1191
- throw new Error("Coding artifact store exceeded the requested byte budget");
1192
- if (result.totalBytes !== undefined && result.totalBytes < input.offset + result.data.byteLength) {
1193
- throw new Error("Coding artifact store returned inconsistent totalBytes");
1194
- }
1195
- return {
1196
- reference: input.reference,
1197
- offset: input.offset,
1198
- text: new TextDecoder("utf-8", { fatal: true }).decode(result.data),
1199
- bytes: result.data.byteLength,
1200
- ...result.totalBytes !== undefined && { totalBytes: result.totalBytes }
1201
- };
1202
- }
1203
- }));
1204
- if (artifacts.search) {
1205
- const search = artifacts.search;
1206
- artifactTools.push(defineRuntimeTool({
1207
- name: AGENT_CODING_TOOL_NAMES.searchOutput,
1208
- description: "Search a durable spilled output without loading it into context.",
1209
- identity: { serviceName: "coding", action: "search-artifact", method: "POST" },
1210
- input: z4.object({
1211
- reference: z4.string().min(1),
1212
- query: z4.string().min(1),
1213
- maxMatches: z4.int().positive().max(limits.maxSearchResults).default(20)
1214
- }).strict(),
1215
- output: z4.object({
1216
- reference: z4.string(),
1217
- matches: z4.array(z4.object({ line: z4.int().positive(), text: z4.string() }))
1218
- }).strict(),
1219
- transports: ["AGENT"],
1220
- handler: async ({ input }) => {
1221
- await authorizeCodingTool(config, {
1222
- operation: "artifact-search",
1223
- reference: input.reference,
1224
- query: input.query,
1225
- maxMatches: input.maxMatches
1226
- });
1227
- const originAuthorization = await artifacts.authorization?.(input.reference);
1228
- if (originAuthorization)
1229
- await authorizeCodingTool(config, originAuthorization);
1230
- return {
1231
- reference: input.reference,
1232
- matches: [
1233
- ...await search({
1234
- reference: input.reference,
1235
- query: input.query,
1236
- maxMatches: input.maxMatches
1237
- })
1238
- ]
1239
- };
1240
- }
1241
- }));
1242
- }
1243
- }
1244
- const commandTools = Object.keys(config.executables ?? {}).length ? [createShellCodingTool(config, limits)] : [];
1245
- return [
1246
- ...createFileCodingTools(config, limits),
1247
- ...createListingCodingTools(config, limits),
1248
- ...createSearchAndPatchCodingTools(config, limits),
1249
- ...commandTools,
1250
- ...artifactTools
1251
- ];
1252
- }
1253
17
  export {
1254
18
  createAgentCodingTools,
1255
19
  AgentCodingToolPathAuthorizationSchema,