stitchkit 0.68.11 → 0.70.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 (125) hide show
  1. package/README.md +30 -1
  2. package/dist/agent-runtime/coding-tool-contract.d.ts +156 -0
  3. package/dist/agent-runtime/coding-tool-contract.d.ts.map +1 -0
  4. package/dist/agent-runtime/coding-tool-files.d.ts +3 -0
  5. package/dist/agent-runtime/coding-tool-files.d.ts.map +1 -0
  6. package/dist/agent-runtime/coding-tool-paths.d.ts +20 -0
  7. package/dist/agent-runtime/coding-tool-paths.d.ts.map +1 -0
  8. package/dist/agent-runtime/coding-tool-search-patch.d.ts +3 -0
  9. package/dist/agent-runtime/coding-tool-search-patch.d.ts.map +1 -0
  10. package/dist/agent-runtime/coding-tool-shell.d.ts +3 -0
  11. package/dist/agent-runtime/coding-tool-shell.d.ts.map +1 -0
  12. package/dist/agent-runtime/coding-tools.d.ts +17 -0
  13. package/dist/agent-runtime/coding-tools.d.ts.map +1 -0
  14. package/dist/agent-runtime/compaction.d.ts +2 -0
  15. package/dist/agent-runtime/compaction.d.ts.map +1 -1
  16. package/dist/agent-runtime/contained-files.d.ts +56 -0
  17. package/dist/agent-runtime/contained-files.d.ts.map +1 -0
  18. package/dist/agent-runtime/control-schema.d.ts +1074 -0
  19. package/dist/agent-runtime/control-schema.d.ts.map +1 -0
  20. package/dist/agent-runtime/conversations.d.ts +128 -0
  21. package/dist/agent-runtime/conversations.d.ts.map +1 -0
  22. package/dist/agent-runtime/event-schema.d.ts +96 -0
  23. package/dist/agent-runtime/event-schema.d.ts.map +1 -1
  24. package/dist/agent-runtime/harness-contract.d.ts +169 -0
  25. package/dist/agent-runtime/harness-contract.d.ts.map +1 -0
  26. package/dist/agent-runtime/harness-control.d.ts +25 -0
  27. package/dist/agent-runtime/harness-control.d.ts.map +1 -0
  28. package/dist/agent-runtime/harness-file-resources.d.ts +33 -0
  29. package/dist/agent-runtime/harness-file-resources.d.ts.map +1 -0
  30. package/dist/agent-runtime/harness-resources.d.ts +5 -0
  31. package/dist/agent-runtime/harness-resources.d.ts.map +1 -0
  32. package/dist/agent-runtime/harness.d.ts +12 -0
  33. package/dist/agent-runtime/harness.d.ts.map +1 -0
  34. package/dist/agent-runtime/history.d.ts +2 -0
  35. package/dist/agent-runtime/history.d.ts.map +1 -1
  36. package/dist/agent-runtime/models.d.ts +199 -0
  37. package/dist/agent-runtime/models.d.ts.map +1 -1
  38. package/dist/agent-runtime/prompt.d.ts +2 -0
  39. package/dist/agent-runtime/prompt.d.ts.map +1 -1
  40. package/dist/agent-runtime/run-execution.d.ts.map +1 -1
  41. package/dist/agent-runtime/runtime.d.ts +11 -3
  42. package/dist/agent-runtime/runtime.d.ts.map +1 -1
  43. package/dist/agent-runtime/schemas.d.ts +49 -0
  44. package/dist/agent-runtime/schemas.d.ts.map +1 -1
  45. package/dist/agent-runtime/sqlite.d.ts +2 -0
  46. package/dist/agent-runtime/sqlite.d.ts.map +1 -1
  47. package/dist/agent-runtime/store-driver.d.ts +61 -212
  48. package/dist/agent-runtime/store-driver.d.ts.map +1 -1
  49. package/dist/agent-runtime/store.d.ts +368 -1
  50. package/dist/agent-runtime/store.d.ts.map +1 -1
  51. package/dist/agent-runtime/terminal-commit.d.ts +12 -1
  52. package/dist/agent-runtime/terminal-commit.d.ts.map +1 -1
  53. package/dist/agent-runtime/terminal-status.d.ts +8 -0
  54. package/dist/agent-runtime/terminal-status.d.ts.map +1 -1
  55. package/dist/agent-runtime-browser.d.ts +1 -0
  56. package/dist/agent-runtime-browser.d.ts.map +1 -1
  57. package/dist/agent-runtime-browser.js +217 -1
  58. package/dist/agent-runtime-coding-tools.d.ts +2 -0
  59. package/dist/agent-runtime-coding-tools.d.ts.map +1 -0
  60. package/dist/agent-runtime-coding-tools.js +766 -0
  61. package/dist/agent-runtime-harness.d.ts +2 -0
  62. package/dist/agent-runtime-harness.d.ts.map +1 -0
  63. package/dist/agent-runtime-harness.js +704 -0
  64. package/dist/agent-runtime-openrouter.d.ts +14 -1
  65. package/dist/agent-runtime-openrouter.d.ts.map +1 -1
  66. package/dist/agent-runtime-openrouter.js +211 -0
  67. package/dist/agent-runtime-sqlite-bun.js +4 -3
  68. package/dist/agent-runtime-sqlite-node.js +4 -3
  69. package/dist/agent-runtime.d.ts +5 -3
  70. package/dist/agent-runtime.d.ts.map +1 -1
  71. package/dist/agent-runtime.js +108 -2580
  72. package/dist/application/diagnostic-journal-contract.d.ts +164 -0
  73. package/dist/application/diagnostic-journal-contract.d.ts.map +1 -0
  74. package/dist/application/diagnostic-journal-manager.d.ts +12 -0
  75. package/dist/application/diagnostic-journal-manager.d.ts.map +1 -0
  76. package/dist/application/diagnostic-journal-storage.d.ts +29 -0
  77. package/dist/application/diagnostic-journal-storage.d.ts.map +1 -0
  78. package/dist/application/diagnostic-journal.d.ts +11 -0
  79. package/dist/application/diagnostic-journal.d.ts.map +1 -0
  80. package/dist/application.d.ts +2 -0
  81. package/dist/application.d.ts.map +1 -1
  82. package/dist/application.js +561 -17
  83. package/dist/browser/socket-io.d.ts.map +1 -1
  84. package/dist/cli.js +9 -6
  85. package/dist/{index-t23p2b68.js → index-0yphgata.js} +1 -1
  86. package/dist/{index-8vpzxg55.js → index-10gbbbaa.js} +103 -2
  87. package/dist/{index-hcx9yypn.js → index-38hs3a58.js} +13 -11
  88. package/dist/index-3xnq72rz.js +21 -0
  89. package/dist/index-7rey2b8s.js +73 -0
  90. package/dist/{index-22tjt2rk.js → index-aczggrty.js} +1 -1
  91. package/dist/index-bd17ytae.js +192 -0
  92. package/dist/{index-xyvxez9r.js → index-da1aqnhb.js} +5 -5
  93. package/dist/index-es0h4w26.js +63 -0
  94. package/dist/{index-hqza5nde.js → index-hsabxjz0.js} +78 -77
  95. package/dist/{index-f24xg2cw.js → index-ktsps64f.js} +4 -2
  96. package/dist/{index-2t6zt5cg.js → index-mjx0wt4c.js} +11 -7
  97. package/dist/{index-y2ctppmg.js → index-qzbg46b6.js} +10 -74
  98. package/dist/{index-bt5179zb.js → index-s8nt8c22.js} +16 -11
  99. package/dist/index-tg3m2ec5.js +157 -0
  100. package/dist/{index-hxh98zbm.js → index-vbf2p6me.js} +1 -1
  101. package/dist/index-vc498pst.js +51 -0
  102. package/dist/{index-pgsyp3xh.js → index-vj3vvpaa.js} +1 -192
  103. package/dist/index-vy5bjy07.js +2569 -0
  104. package/dist/{index-hb0mncmj.js → index-wc80at9n.js} +2 -2
  105. package/dist/{index-eqqyd9v8.js → index-wwst0td5.js} +2 -46
  106. package/dist/{index-ysphyxax.js → index-x1th9s8c.js} +23 -2
  107. package/dist/index-xbtcszs7.js +202 -0
  108. package/dist/{index-grgvpbch.js → index-y3w12g8d.js} +4 -2
  109. package/dist/index.js +51 -37
  110. package/dist/internal/ai-sdk-typed.d.ts +14 -0
  111. package/dist/internal/ai-sdk-typed.d.ts.map +1 -0
  112. package/dist/node.js +6 -5
  113. package/dist/observability/index.js +5 -4
  114. package/dist/realtime/request.d.ts +5 -0
  115. package/dist/realtime/request.d.ts.map +1 -1
  116. package/dist/remote.js +7 -6
  117. package/dist/server/index.js +13 -11
  118. package/dist/testing.js +6 -5
  119. package/dist/tool-invoker.js +7 -5
  120. package/dist/tools/agent-tool-error.d.ts +14 -0
  121. package/dist/tools/agent-tool-error.d.ts.map +1 -0
  122. package/dist/tools/agent.d.ts.map +1 -1
  123. package/dist/tools.js +24 -17
  124. package/llms-full.txt +386 -29
  125. package/package.json +10 -2
@@ -0,0 +1,766 @@
1
+ import {
2
+ assertContainedFileCurrent,
3
+ assertContainedParentCurrent,
4
+ openContainedFile,
5
+ openContainedParent,
6
+ readContainedUtf8Handle,
7
+ scanContainedFiles
8
+ } from "./index-xbtcszs7.js";
9
+ import {
10
+ defineRuntimeTool
11
+ } from "./index-wwst0td5.js";
12
+ import"./index-22by16v6.js";
13
+ import"./index-cby4ar3v.js";
14
+ import"./index-vj3vvpaa.js";
15
+ import {
16
+ conflict,
17
+ forbidden
18
+ } from "./index-0w9abg87.js";
19
+ import"./index-qyrqwr4c.js";
20
+ import"./index-6k1937bx.js";
21
+
22
+ // src/agent-runtime/coding-tools.ts
23
+ import path4 from "node:path";
24
+ import { z as z3 } from "zod";
25
+
26
+ // src/agent-runtime/coding-tool-contract.ts
27
+ import { z } from "zod";
28
+ var AgentCodingToolLimitsSchema = z.object({
29
+ maxPathBytes: z.int().positive(),
30
+ maxReadBytes: z.int().positive(),
31
+ maxWriteBytes: z.int().positive(),
32
+ maxShellArguments: z.int().positive(),
33
+ maxShellArgumentBytes: z.int().positive(),
34
+ maxShellOutputBytes: z.int().positive(),
35
+ maxArtifactBytes: z.int().positive(),
36
+ maxSearchResults: z.int().positive(),
37
+ maxSearchFiles: z.int().positive(),
38
+ maxSearchDepth: z.int().nonnegative(),
39
+ shellTimeoutMs: z.int().positive(),
40
+ shellTerminationGraceMs: z.int().positive()
41
+ }).strict();
42
+ var AgentCodingToolAuthorizationSchema = z.discriminatedUnion("operation", [
43
+ z.object({ operation: z.literal("read"), path: z.string().min(1) }).strict(),
44
+ z.object({
45
+ operation: z.literal("write"),
46
+ path: z.string().min(1),
47
+ bytes: z.int().nonnegative(),
48
+ overwrite: z.boolean()
49
+ }).strict(),
50
+ z.object({
51
+ operation: z.literal("search"),
52
+ query: z.string().min(1),
53
+ mode: z.enum(["path", "content"])
54
+ }).strict(),
55
+ z.object({
56
+ operation: z.literal("patch"),
57
+ path: z.string().min(1),
58
+ baseSha256: z.string().length(64),
59
+ resultSha256: z.string().length(64),
60
+ resultBytes: z.int().nonnegative(),
61
+ replacements: z.int().positive(),
62
+ dryRun: z.boolean()
63
+ }).strict(),
64
+ z.object({
65
+ operation: z.literal("artifact-read"),
66
+ reference: z.string().min(1),
67
+ offset: z.int().nonnegative(),
68
+ maxBytes: z.int().positive()
69
+ }).strict(),
70
+ z.object({
71
+ operation: z.literal("shell"),
72
+ executable: z.string().min(1),
73
+ args: z.array(z.string()),
74
+ cwd: z.string().min(1)
75
+ }).strict()
76
+ ]);
77
+ var FileReadInputSchema = z.object({
78
+ path: z.string().min(1),
79
+ offset: z.int().nonnegative().default(0),
80
+ maxBytes: z.int().positive().optional()
81
+ }).strict();
82
+ var FileReadOutputSchema = z.object({
83
+ path: z.string().min(1),
84
+ text: z.string(),
85
+ bytes: z.int().nonnegative(),
86
+ sha256: z.string().length(64),
87
+ truncated: z.boolean(),
88
+ nextOffset: z.int().nonnegative().optional()
89
+ }).strict();
90
+ var FileWriteInputSchema = z.object({
91
+ path: z.string().min(1),
92
+ content: z.string(),
93
+ overwrite: z.boolean().default(false)
94
+ }).strict();
95
+ var FileWriteOutputSchema = z.object({ path: z.string().min(1), bytes: z.int().nonnegative() }).strict();
96
+ function createShellInputSchema(executableNames) {
97
+ if (executableNames.length === 0) {
98
+ throw new Error("A command tool requires at least one executable alias");
99
+ }
100
+ return z.object({
101
+ executable: z.enum(executableNames),
102
+ args: z.array(z.string()).default([]),
103
+ cwd: z.string().min(1).default(".")
104
+ }).strict();
105
+ }
106
+ var ShellInputSchema = z.object({
107
+ executable: z.string().min(1),
108
+ args: z.array(z.string()).default([]),
109
+ cwd: z.string().min(1).default(".")
110
+ }).strict();
111
+ var ShellOutputSchema = z.object({
112
+ executable: z.string().min(1),
113
+ exitCode: z.int().nullable(),
114
+ signal: z.string().nullable(),
115
+ stdout: z.string(),
116
+ stderr: z.string(),
117
+ outcome: z.enum(["exited", "timeout", "output-limit", "cancelled"]),
118
+ artifact: z.object({
119
+ reference: z.string().min(1),
120
+ bytes: z.int().nonnegative(),
121
+ truncated: z.boolean()
122
+ }).strict().optional()
123
+ }).strict();
124
+ var DEFAULT_LIMITS = {
125
+ maxPathBytes: 4096,
126
+ maxReadBytes: 262144,
127
+ maxWriteBytes: 262144,
128
+ maxShellArguments: 128,
129
+ maxShellArgumentBytes: 65536,
130
+ maxShellOutputBytes: 262144,
131
+ maxArtifactBytes: 4194304,
132
+ maxSearchResults: 100,
133
+ maxSearchFiles: 1e4,
134
+ maxSearchDepth: 32,
135
+ shellTimeoutMs: 30000,
136
+ shellTerminationGraceMs: 250
137
+ };
138
+ function resolveCodingToolLimits(input) {
139
+ return AgentCodingToolLimitsSchema.parse({ ...DEFAULT_LIMITS, ...input });
140
+ }
141
+
142
+ // src/agent-runtime/coding-tool-files.ts
143
+ import { createHash } from "node:crypto";
144
+ import { lstat as lstat2, realpath as realpath2, writeFile as writeFile2 } from "node:fs/promises";
145
+ import path2 from "node:path";
146
+
147
+ // src/agent-runtime/coding-tool-paths.ts
148
+ import { lstat, realpath, rename, unlink, writeFile } from "node:fs/promises";
149
+ import path from "node:path";
150
+ function within(root, target) {
151
+ const relative = path.relative(root, target);
152
+ return relative === "" || !relative.startsWith(`..${path.sep}`) && relative !== ".." && !path.isAbsolute(relative);
153
+ }
154
+ function inputPath(root, requested, maxPathBytes) {
155
+ if (new TextEncoder().encode(requested).byteLength > maxPathBytes) {
156
+ throw new Error("Coding tool path exceeds maxPathBytes");
157
+ }
158
+ if (path.isAbsolute(requested))
159
+ throw new Error("Coding tool paths must be relative");
160
+ const resolved = path.resolve(root, requested);
161
+ if (!within(root, resolved))
162
+ throw new Error("Coding tool path escapes its root");
163
+ return resolved;
164
+ }
165
+ function boundedCodingRelativePath(requested, maxPathBytes) {
166
+ inputPath(path.parse(process.cwd()).root, requested, maxPathBytes);
167
+ return requested;
168
+ }
169
+ async function existingCodingPath(root, requested, maxPathBytes) {
170
+ const resolved = await realpath(inputPath(root, requested, maxPathBytes));
171
+ if (!within(root, resolved))
172
+ throw new Error("Coding tool path resolves outside its root");
173
+ return { absolute: resolved, relative: path.relative(root, resolved) || "." };
174
+ }
175
+ async function authorizeCodingTool(config, request) {
176
+ const parsed = AgentCodingToolAuthorizationSchema.parse(request);
177
+ if (!await config.authorize(parsed))
178
+ forbidden("Coding tool permission denied");
179
+ }
180
+ async function atomicCodingReplace(target, content, mode) {
181
+ const temporary = path.join(path.dirname(target), `.${path.basename(target)}.${process.pid}.${crypto.randomUUID()}.tmp`);
182
+ try {
183
+ await writeFile(temporary, content, { flag: "wx", ...mode !== undefined && { mode } });
184
+ await rename(temporary, target);
185
+ } catch (error) {
186
+ await unlink(temporary).catch(() => {
187
+ return;
188
+ });
189
+ throw error;
190
+ }
191
+ }
192
+ var codingPathLocks = new Map;
193
+ async function withCodingPathLock(target, work) {
194
+ const previous = codingPathLocks.get(target) ?? Promise.resolve();
195
+ let release = () => {
196
+ return;
197
+ };
198
+ const current = new Promise((resolve) => {
199
+ release = resolve;
200
+ });
201
+ const queued = previous.then(() => current);
202
+ codingPathLocks.set(target, queued);
203
+ await previous;
204
+ try {
205
+ return await work();
206
+ } finally {
207
+ release();
208
+ if (codingPathLocks.get(target) === queued)
209
+ codingPathLocks.delete(target);
210
+ }
211
+ }
212
+ function textOccurrences(source, search) {
213
+ let count = 0;
214
+ let offset = 0;
215
+ while (offset <= source.length - search.length) {
216
+ const found = source.indexOf(search, offset);
217
+ if (found < 0)
218
+ break;
219
+ count += 1;
220
+ offset = found + search.length;
221
+ }
222
+ return count;
223
+ }
224
+
225
+ // src/agent-runtime/coding-tool-files.ts
226
+ async function digestFile(handle, size) {
227
+ const hash = createHash("sha256");
228
+ const chunk = Buffer.alloc(Math.min(64 * 1024, Math.max(1, size)));
229
+ for (let position = 0;position < size; ) {
230
+ const length = Math.min(chunk.byteLength, size - position);
231
+ const { bytesRead } = await handle.read(chunk, 0, length, position);
232
+ if (bytesRead === 0)
233
+ break;
234
+ hash.update(chunk.subarray(0, bytesRead));
235
+ position += bytesRead;
236
+ }
237
+ return hash.digest("hex");
238
+ }
239
+ function createFileCodingTools(config, limits) {
240
+ const read = defineRuntimeTool({
241
+ name: "read_file",
242
+ description: "Read a bounded UTF-8 slice from a host-authorized workspace file.",
243
+ identity: { serviceName: "coding", action: "read-file", method: "POST" },
244
+ input: FileReadInputSchema,
245
+ output: FileReadOutputSchema,
246
+ transports: ["AGENT"],
247
+ handler: async ({ input }) => {
248
+ const root = await realpath2(config.root);
249
+ const relative = boundedCodingRelativePath(input.path, limits.maxPathBytes);
250
+ const handle = await openContainedFile(root, relative);
251
+ const maximum = Math.min(input.maxBytes ?? limits.maxReadBytes, limits.maxReadBytes);
252
+ let selected;
253
+ let size;
254
+ let sha256;
255
+ try {
256
+ await authorizeCodingTool(config, { operation: "read", path: relative });
257
+ await assertContainedFileCurrent(root, relative, handle);
258
+ const metadata = await handle.stat();
259
+ if (!metadata.isFile())
260
+ throw new Error("Coding read path is not a regular file");
261
+ size = metadata.size;
262
+ const length = Math.min(maximum, Math.max(0, size - input.offset));
263
+ selected = Buffer.alloc(length);
264
+ const { bytesRead } = await handle.read(selected, 0, length, input.offset);
265
+ selected = selected.subarray(0, bytesRead);
266
+ sha256 = await digestFile(handle, size);
267
+ } finally {
268
+ await handle.close();
269
+ }
270
+ const text = new TextDecoder("utf-8", { fatal: true }).decode(selected);
271
+ const end = input.offset + selected.byteLength;
272
+ const truncated = end < size;
273
+ return {
274
+ path: relative,
275
+ text,
276
+ bytes: selected.byteLength,
277
+ sha256,
278
+ truncated,
279
+ ...truncated && { nextOffset: end }
280
+ };
281
+ }
282
+ });
283
+ const write = defineRuntimeTool({
284
+ name: "write_file",
285
+ description: "Write bounded UTF-8 content to a host-authorized workspace file.",
286
+ identity: { serviceName: "coding", action: "write-file", method: "POST" },
287
+ input: FileWriteInputSchema,
288
+ output: FileWriteOutputSchema,
289
+ transports: ["AGENT"],
290
+ handler: async ({ input }) => {
291
+ const root = await realpath2(config.root);
292
+ const relative = boundedCodingRelativePath(input.path, limits.maxPathBytes);
293
+ const bytes = Buffer.byteLength(input.content);
294
+ if (bytes > limits.maxWriteBytes)
295
+ throw new Error("Coding tool write exceeds maxWriteBytes");
296
+ const parent = await openContainedParent(root, relative);
297
+ const target = path2.join(parent.path, parent.basename);
298
+ try {
299
+ await authorizeCodingTool(config, {
300
+ operation: "write",
301
+ path: relative,
302
+ bytes,
303
+ overwrite: input.overwrite
304
+ });
305
+ await assertContainedParentCurrent(root, relative, parent.handle);
306
+ if (input.overwrite) {
307
+ const current = await lstat2(target).catch(() => null);
308
+ if (current?.isSymbolicLink())
309
+ throw new Error("Coding tools do not overwrite symlinks");
310
+ await atomicCodingReplace(target, input.content, current?.mode);
311
+ } else {
312
+ await writeFile2(target, input.content, { flag: "wx" });
313
+ }
314
+ } finally {
315
+ await parent.handle.close();
316
+ }
317
+ return { path: relative, bytes };
318
+ }
319
+ });
320
+ return [read, write];
321
+ }
322
+
323
+ // src/agent-runtime/coding-tool-search-patch.ts
324
+ import { createHash as createHash2 } from "node:crypto";
325
+ import { constants } from "node:fs";
326
+ import { open, realpath as realpath3 } from "node:fs/promises";
327
+ import path3 from "node:path";
328
+ import { z as z2 } from "zod";
329
+ var DEFAULT_EXCLUDED_DIRECTORIES = [
330
+ ".git",
331
+ ".next",
332
+ ".stitchkit",
333
+ "build",
334
+ "coverage",
335
+ "dist",
336
+ "node_modules"
337
+ ];
338
+ var SearchInputSchema = z2.object({ query: z2.string().min(1), mode: z2.enum(["path", "content"]).default("content") }).strict();
339
+ var SearchOutputSchema = z2.object({
340
+ matches: z2.array(z2.object({ path: z2.string().min(1), line: z2.int().positive().optional() }).strict()),
341
+ truncated: z2.boolean(),
342
+ scannedFiles: z2.int().nonnegative(),
343
+ skippedDirectories: z2.int().nonnegative(),
344
+ skippedSymlinks: z2.int().nonnegative()
345
+ }).strict();
346
+ var PatchInputSchema = z2.object({
347
+ path: z2.string().min(1),
348
+ baseSha256: z2.string().length(64),
349
+ oldText: z2.string().min(1),
350
+ newText: z2.string(),
351
+ replaceAll: z2.boolean().default(false),
352
+ dryRun: z2.boolean().default(true)
353
+ }).strict();
354
+ var PatchOutputSchema = z2.object({
355
+ path: z2.string().min(1),
356
+ baseSha256: z2.string().length(64),
357
+ resultSha256: z2.string().length(64),
358
+ replacements: z2.int().positive(),
359
+ bytes: z2.int().nonnegative(),
360
+ applied: z2.boolean()
361
+ }).strict();
362
+ function sha256(value) {
363
+ return createHash2("sha256").update(value).digest("hex");
364
+ }
365
+ function createSearchAndPatchCodingTools(config, limits) {
366
+ const search = defineRuntimeTool({
367
+ name: "search_files",
368
+ description: "Search bounded relative paths or UTF-8 file content under the workspace root.",
369
+ identity: { serviceName: "coding", action: "search", method: "POST" },
370
+ input: SearchInputSchema,
371
+ output: SearchOutputSchema,
372
+ transports: ["AGENT"],
373
+ handler: async ({ input }) => {
374
+ await authorizeCodingTool(config, {
375
+ operation: "search",
376
+ query: input.query,
377
+ mode: input.mode
378
+ });
379
+ const root = await realpath3(config.root);
380
+ const excluded = new Set(config.search?.excludeDirectories ?? DEFAULT_EXCLUDED_DIRECTORIES);
381
+ const scan = await scanContainedFiles({
382
+ root,
383
+ maxDepth: limits.maxSearchDepth,
384
+ maxFiles: limits.maxSearchFiles,
385
+ symlinks: "skip",
386
+ ...input.mode === "content" && {
387
+ readMaxBytes: limits.maxReadBytes,
388
+ skipUnreadable: true
389
+ },
390
+ excludeDirectory: (relative) => relative.split(path3.sep).some((segment) => excluded.has(segment))
391
+ });
392
+ const matches = [];
393
+ let truncated = false;
394
+ for (const file of scan.files) {
395
+ if (input.mode === "path") {
396
+ if (file.relative.includes(input.query))
397
+ matches.push({ path: file.relative });
398
+ } else {
399
+ const text = file.content?.text;
400
+ if (text === undefined)
401
+ continue;
402
+ const lines = text.split(`
403
+ `);
404
+ for (let index = 0;index < lines.length; index += 1) {
405
+ if (lines[index]?.includes(input.query)) {
406
+ matches.push({ path: file.relative, line: index + 1 });
407
+ if (matches.length >= limits.maxSearchResults)
408
+ break;
409
+ }
410
+ }
411
+ }
412
+ if (matches.length >= limits.maxSearchResults) {
413
+ truncated = true;
414
+ break;
415
+ }
416
+ }
417
+ return {
418
+ matches,
419
+ truncated: truncated || scan.truncated,
420
+ scannedFiles: scan.files.length,
421
+ skippedDirectories: scan.skippedDirectories,
422
+ skippedSymlinks: scan.skippedSymlinks
423
+ };
424
+ }
425
+ });
426
+ const patch = defineRuntimeTool({
427
+ name: "apply_patch",
428
+ description: "Dry-run or atomically apply one exact guarded UTF-8 file patch.",
429
+ identity: { serviceName: "coding", action: "patch-file", method: "POST" },
430
+ input: PatchInputSchema,
431
+ output: PatchOutputSchema,
432
+ transports: ["AGENT"],
433
+ handler: async ({ input }) => {
434
+ const root = await realpath3(config.root);
435
+ const relative = boundedCodingRelativePath(input.path, limits.maxPathBytes);
436
+ const parent = await openContainedParent(root, relative);
437
+ try {
438
+ const target = path3.join(parent.path, parent.basename);
439
+ const initial = await open(target, constants.O_RDONLY | constants.O_NOFOLLOW);
440
+ const read = await readContainedUtf8Handle(initial, limits.maxWriteBytes).finally(() => initial.close());
441
+ const source = read.text;
442
+ const baseSha256 = sha256(source);
443
+ if (baseSha256 !== input.baseSha256)
444
+ conflict("Coding patch base digest is stale");
445
+ const count = textOccurrences(source, input.oldText);
446
+ if (count === 0)
447
+ throw new Error("Coding patch text was not found");
448
+ if (!input.replaceAll && count !== 1)
449
+ throw new Error("Coding patch text is not unique");
450
+ const replacements = input.replaceAll ? count : 1;
451
+ const changed = input.replaceAll ? source.replaceAll(input.oldText, input.newText) : source.replace(input.oldText, input.newText);
452
+ const bytes = Buffer.byteLength(changed);
453
+ const resultSha256 = sha256(changed);
454
+ if (bytes > limits.maxWriteBytes)
455
+ throw new Error("Coding patch exceeds maxWriteBytes");
456
+ await authorizeCodingTool(config, {
457
+ operation: "patch",
458
+ path: relative,
459
+ baseSha256,
460
+ resultSha256,
461
+ resultBytes: bytes,
462
+ replacements,
463
+ dryRun: input.dryRun
464
+ });
465
+ await assertContainedParentCurrent(root, relative, parent.handle);
466
+ if (!input.dryRun) {
467
+ await withCodingPathLock(`${root}:${relative}`, async () => {
468
+ const currentHandle = await open(target, constants.O_RDONLY | constants.O_NOFOLLOW);
469
+ const current = await readContainedUtf8Handle(currentHandle, limits.maxWriteBytes).finally(() => currentHandle.close());
470
+ if (sha256(current.text) !== baseSha256)
471
+ conflict("Coding patch base digest became stale before apply");
472
+ await atomicCodingReplace(target, changed, current.mode);
473
+ });
474
+ }
475
+ return {
476
+ path: relative,
477
+ baseSha256,
478
+ resultSha256,
479
+ replacements,
480
+ bytes,
481
+ applied: !input.dryRun
482
+ };
483
+ } finally {
484
+ await parent.handle.close();
485
+ }
486
+ }
487
+ });
488
+ return [search, patch];
489
+ }
490
+
491
+ // src/agent-runtime/coding-tool-shell.ts
492
+ import { spawn } from "node:child_process";
493
+ import { realpath as realpath4, stat } from "node:fs/promises";
494
+ async function runShell(input) {
495
+ const stdoutHeader = Buffer.from(`--- stdout ---
496
+ `);
497
+ const stderrHeader = Buffer.from(`
498
+ --- stderr ---
499
+ `);
500
+ const artifactPayloadLimit = input.maxArtifactBytes - stdoutHeader.byteLength - stderrHeader.byteLength;
501
+ if (input.artifacts && artifactPayloadLimit < 0)
502
+ throw new Error("maxArtifactBytes is smaller than the shell artifact envelope");
503
+ if (input.signal?.aborted) {
504
+ return ShellOutputSchema.parse({
505
+ executable: input.executableName,
506
+ exitCode: null,
507
+ signal: null,
508
+ stdout: "",
509
+ stderr: "",
510
+ outcome: "cancelled"
511
+ });
512
+ }
513
+ return await new Promise((resolve, reject) => {
514
+ const ownsProcessGroup = process.platform !== "win32";
515
+ const child = spawn(input.executable, input.args, {
516
+ cwd: input.cwd,
517
+ env: input.environment,
518
+ stdio: ["ignore", "pipe", "pipe"],
519
+ detached: ownsProcessGroup
520
+ });
521
+ const stdout = [];
522
+ const stderr = [];
523
+ const artifactStdout = [];
524
+ const artifactStderr = [];
525
+ let retained = 0;
526
+ let artifactBytes = 0;
527
+ let outcome = "exited";
528
+ let settled = false;
529
+ let settlementTimer;
530
+ const cancel = () => terminate("cancelled");
531
+ const cleanup = () => {
532
+ clearTimeout(timer);
533
+ if (settlementTimer)
534
+ clearTimeout(settlementTimer);
535
+ input.signal?.removeEventListener("abort", cancel);
536
+ };
537
+ const persistAndResolve = async (exitCode, signal) => {
538
+ if (settled)
539
+ return;
540
+ settled = true;
541
+ cleanup();
542
+ try {
543
+ const hasArtifact = input.artifacts && artifactBytes > input.maxOutputBytes;
544
+ const artifactData = hasArtifact ? Buffer.concat([stdoutHeader, ...artifactStdout, stderrHeader, ...artifactStderr]) : undefined;
545
+ const persisted = hasArtifact ? await input.artifacts?.write({
546
+ mediaType: "text/plain; charset=utf-8",
547
+ data: artifactData ?? new Uint8Array
548
+ }) : undefined;
549
+ resolve(ShellOutputSchema.parse({
550
+ executable: input.executableName,
551
+ exitCode,
552
+ signal,
553
+ stdout: Buffer.concat(stdout).toString("utf8"),
554
+ stderr: Buffer.concat(stderr).toString("utf8"),
555
+ outcome,
556
+ ...persisted && {
557
+ artifact: {
558
+ reference: persisted.reference,
559
+ bytes: artifactData?.byteLength ?? 0,
560
+ truncated: outcome === "output-limit"
561
+ }
562
+ }
563
+ }));
564
+ } catch (error) {
565
+ reject(error);
566
+ }
567
+ };
568
+ const killOwnedProcessGroup = () => {
569
+ if (!ownsProcessGroup || child.pid === undefined)
570
+ return false;
571
+ try {
572
+ process.kill(-child.pid, "SIGKILL");
573
+ return true;
574
+ } catch {
575
+ return false;
576
+ }
577
+ };
578
+ const boundStreamSettlement = () => {
579
+ if (settlementTimer)
580
+ return;
581
+ settlementTimer = setTimeout(() => {
582
+ child.stdout.destroy();
583
+ child.stderr.destroy();
584
+ persistAndResolve(child.exitCode, child.signalCode);
585
+ }, input.terminationGraceMs);
586
+ };
587
+ const terminate = (reason) => {
588
+ if (settled || outcome !== "exited")
589
+ return;
590
+ outcome = reason;
591
+ if (!killOwnedProcessGroup())
592
+ child.kill("SIGKILL");
593
+ boundStreamSettlement();
594
+ };
595
+ const timer = setTimeout(() => terminate("timeout"), input.timeoutMs);
596
+ timer.unref();
597
+ const retain = (target, artifactTarget, chunk) => {
598
+ const remaining = Math.max(0, input.maxOutputBytes - retained);
599
+ if (remaining > 0) {
600
+ const kept = chunk.subarray(0, remaining);
601
+ target.push(kept);
602
+ retained += kept.byteLength;
603
+ }
604
+ if (input.artifacts) {
605
+ const artifactRemaining = Math.max(0, artifactPayloadLimit - artifactBytes);
606
+ if (artifactRemaining > 0) {
607
+ const kept = chunk.subarray(0, artifactRemaining);
608
+ artifactTarget.push(kept);
609
+ artifactBytes += kept.byteLength;
610
+ }
611
+ if (chunk.byteLength > artifactRemaining)
612
+ terminate("output-limit");
613
+ } else if (chunk.byteLength > remaining)
614
+ terminate("output-limit");
615
+ };
616
+ child.stdout.on("data", (chunk) => retain(stdout, artifactStdout, chunk));
617
+ child.stderr.on("data", (chunk) => retain(stderr, artifactStderr, chunk));
618
+ child.on("error", (error) => {
619
+ if (settled)
620
+ return;
621
+ settled = true;
622
+ cleanup();
623
+ reject(error);
624
+ });
625
+ child.on("exit", (exitCode, signal) => {
626
+ if (outcome === "exited")
627
+ killOwnedProcessGroup();
628
+ boundStreamSettlement();
629
+ if (child.stdout.destroyed && child.stderr.destroyed) {
630
+ persistAndResolve(exitCode, signal);
631
+ }
632
+ });
633
+ child.on("close", (exitCode, signal) => void persistAndResolve(exitCode, signal));
634
+ input.signal?.addEventListener("abort", cancel, { once: true });
635
+ if (input.signal?.aborted)
636
+ cancel();
637
+ });
638
+ }
639
+ function createShellCodingTool(config, limits) {
640
+ const executables = config.executables ?? {};
641
+ const executableNames = Object.keys(executables).sort();
642
+ return defineRuntimeTool({
643
+ name: "run_command",
644
+ description: "Run a finite host-declared executable with bounded arguments, time and output.",
645
+ identity: { serviceName: "coding", action: "shell", method: "POST" },
646
+ input: createShellInputSchema(executableNames),
647
+ output: ShellOutputSchema,
648
+ transports: ["AGENT"],
649
+ handler: async ({ input, signal }) => {
650
+ if (input.args.length > limits.maxShellArguments) {
651
+ throw new Error("Coding tool shell arguments exceed maxShellArguments");
652
+ }
653
+ const argumentBytes = input.args.reduce((total, argument) => total + Buffer.byteLength(argument), 0);
654
+ if (argumentBytes > limits.maxShellArgumentBytes) {
655
+ throw new Error("Coding tool shell arguments exceed maxShellArgumentBytes");
656
+ }
657
+ const executable = executables[input.executable];
658
+ if (!executable)
659
+ throw new Error("Coding tool executable is not declared");
660
+ const root = await realpath4(config.root);
661
+ const cwd = await existingCodingPath(root, input.cwd, limits.maxPathBytes);
662
+ if (!(await stat(cwd.absolute)).isDirectory()) {
663
+ throw new Error("Coding tool cwd is not a directory");
664
+ }
665
+ await authorizeCodingTool(config, {
666
+ operation: "shell",
667
+ executable: input.executable,
668
+ args: input.args,
669
+ cwd: cwd.relative
670
+ });
671
+ return runShell({
672
+ executableName: input.executable,
673
+ executable,
674
+ args: input.args,
675
+ cwd: cwd.absolute,
676
+ environment: config.environment ?? {},
677
+ ...signal && { signal },
678
+ timeoutMs: limits.shellTimeoutMs,
679
+ terminationGraceMs: limits.shellTerminationGraceMs,
680
+ maxOutputBytes: limits.maxShellOutputBytes,
681
+ maxArtifactBytes: limits.maxArtifactBytes,
682
+ ...config.artifacts && { artifacts: config.artifacts }
683
+ });
684
+ }
685
+ });
686
+ }
687
+
688
+ // src/agent-runtime/coding-tools.ts
689
+ var AGENT_CODING_TOOL_NAMES = {
690
+ applyPatch: "apply_patch",
691
+ readFile: "read_file",
692
+ readOutput: "read_output",
693
+ runCommand: "run_command",
694
+ searchFiles: "search_files",
695
+ writeFile: "write_file"
696
+ };
697
+ function createAgentCodingTools(config) {
698
+ if (!path4.isAbsolute(config.root))
699
+ throw new Error("Coding tool root must be absolute");
700
+ for (const executable of Object.values(config.executables ?? {})) {
701
+ if (!path4.isAbsolute(executable)) {
702
+ throw new Error("Coding tool executables must be absolute paths");
703
+ }
704
+ }
705
+ const limits = resolveCodingToolLimits(config.limits);
706
+ const artifactTools = [];
707
+ if (config.artifacts) {
708
+ const artifacts = config.artifacts;
709
+ artifactTools.push(defineRuntimeTool({
710
+ name: AGENT_CODING_TOOL_NAMES.readOutput,
711
+ description: "Read a bounded byte slice from one opaque coding output artifact.",
712
+ identity: { serviceName: "coding", action: "read-artifact", method: "POST" },
713
+ input: z3.object({
714
+ reference: z3.string().min(1),
715
+ offset: z3.int().nonnegative().default(0),
716
+ maxBytes: z3.int().positive().optional()
717
+ }).strict(),
718
+ output: z3.object({
719
+ reference: z3.string().min(1),
720
+ offset: z3.int().nonnegative(),
721
+ text: z3.string(),
722
+ bytes: z3.int().nonnegative(),
723
+ totalBytes: z3.int().nonnegative().optional()
724
+ }).strict(),
725
+ transports: ["AGENT"],
726
+ handler: async ({ input }) => {
727
+ const maxBytes = Math.min(input.maxBytes ?? limits.maxReadBytes, limits.maxReadBytes);
728
+ await authorizeCodingTool(config, {
729
+ operation: "artifact-read",
730
+ reference: input.reference,
731
+ offset: input.offset,
732
+ maxBytes
733
+ });
734
+ const result = await artifacts.read({
735
+ reference: input.reference,
736
+ offset: input.offset,
737
+ maxBytes
738
+ });
739
+ if (result.data.byteLength > maxBytes)
740
+ throw new Error("Coding artifact store exceeded the requested byte budget");
741
+ if (result.totalBytes !== undefined && result.totalBytes < input.offset + result.data.byteLength) {
742
+ throw new Error("Coding artifact store returned inconsistent totalBytes");
743
+ }
744
+ return {
745
+ reference: input.reference,
746
+ offset: input.offset,
747
+ text: new TextDecoder("utf-8", { fatal: true }).decode(result.data),
748
+ bytes: result.data.byteLength,
749
+ ...result.totalBytes !== undefined && { totalBytes: result.totalBytes }
750
+ };
751
+ }
752
+ }));
753
+ }
754
+ const commandTools = Object.keys(config.executables ?? {}).length ? [createShellCodingTool(config, limits)] : [];
755
+ return [
756
+ ...createFileCodingTools(config, limits),
757
+ ...createSearchAndPatchCodingTools(config, limits),
758
+ ...commandTools,
759
+ ...artifactTools
760
+ ];
761
+ }
762
+ export {
763
+ AgentCodingToolAuthorizationSchema,
764
+ AgentCodingToolLimitsSchema,
765
+ createAgentCodingTools
766
+ };