stitchkit 0.119.0 → 0.121.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 (163) hide show
  1. package/CHANGELOG.md +68 -0
  2. package/README.md +10 -7
  3. package/dist/agent-runtime/coding-discovery.d.ts +25 -0
  4. package/dist/agent-runtime/coding-discovery.d.ts.map +1 -0
  5. package/dist/agent-runtime/coding-search-automaton.d.ts +37 -0
  6. package/dist/agent-runtime/coding-search-automaton.d.ts.map +1 -0
  7. package/dist/agent-runtime/coding-search-case-fold.d.ts +4 -0
  8. package/dist/agent-runtime/coding-search-case-fold.d.ts.map +1 -0
  9. package/dist/agent-runtime/coding-search-class-ranges.d.ts +10 -0
  10. package/dist/agent-runtime/coding-search-class-ranges.d.ts.map +1 -0
  11. package/dist/agent-runtime/coding-search-contract.d.ts +86 -0
  12. package/dist/agent-runtime/coding-search-contract.d.ts.map +1 -0
  13. package/dist/agent-runtime/coding-search-limits.d.ts +13 -0
  14. package/dist/agent-runtime/coding-search-limits.d.ts.map +1 -0
  15. package/dist/agent-runtime/coding-search-matcher.d.ts +33 -0
  16. package/dist/agent-runtime/coding-search-matcher.d.ts.map +1 -0
  17. package/dist/agent-runtime/coding-search-output.d.ts +5 -0
  18. package/dist/agent-runtime/coding-search-output.d.ts.map +1 -0
  19. package/dist/agent-runtime/coding-search-regex-syntax.d.ts +39 -0
  20. package/dist/agent-runtime/coding-search-regex-syntax.d.ts.map +1 -0
  21. package/dist/agent-runtime/coding-search-unit-classes.d.ts +31 -0
  22. package/dist/agent-runtime/coding-search-unit-classes.d.ts.map +1 -0
  23. package/dist/agent-runtime/coding-shell-process.d.ts +1 -0
  24. package/dist/agent-runtime/coding-shell-process.d.ts.map +1 -1
  25. package/dist/agent-runtime/coding-tool-contract.d.ts +8 -3
  26. package/dist/agent-runtime/coding-tool-contract.d.ts.map +1 -1
  27. package/dist/agent-runtime/coding-tool-files.d.ts.map +1 -1
  28. package/dist/agent-runtime/coding-tool-listing.d.ts +1 -11
  29. package/dist/agent-runtime/coding-tool-listing.d.ts.map +1 -1
  30. package/dist/agent-runtime/coding-tool-paths.d.ts +6 -2
  31. package/dist/agent-runtime/coding-tool-paths.d.ts.map +1 -1
  32. package/dist/agent-runtime/coding-tool-refusals.d.ts +6 -0
  33. package/dist/agent-runtime/coding-tool-refusals.d.ts.map +1 -1
  34. package/dist/agent-runtime/coding-tool-search-patch.d.ts.map +1 -1
  35. package/dist/agent-runtime/coding-tool-search.d.ts +113 -0
  36. package/dist/agent-runtime/coding-tool-search.d.ts.map +1 -0
  37. package/dist/agent-runtime/coding-tool-shell.d.ts.map +1 -1
  38. package/dist/agent-runtime/contained-scan-budget.d.ts +32 -0
  39. package/dist/agent-runtime/contained-scan-budget.d.ts.map +1 -0
  40. package/dist/agent-runtime/contained-scan-contract.d.ts +52 -0
  41. package/dist/agent-runtime/contained-scan-contract.d.ts.map +1 -0
  42. package/dist/agent-runtime/contained-scan.d.ts +48 -13
  43. package/dist/agent-runtime/contained-scan.d.ts.map +1 -1
  44. package/dist/agent-runtime/sandbox.d.ts +5 -1
  45. package/dist/agent-runtime/sandbox.d.ts.map +1 -1
  46. package/dist/agent-runtime/workspace-glob.d.ts +6 -0
  47. package/dist/agent-runtime/workspace-glob.d.ts.map +1 -0
  48. package/dist/entrypoints/agent-runtime/coding-tools.js +7 -7
  49. package/dist/entrypoints/agent-runtime/harness.js +5 -5
  50. package/dist/entrypoints/agent-runtime/realtime.js +3 -3
  51. package/dist/entrypoints/agent-runtime/sandbox.js +8 -8
  52. package/dist/entrypoints/agent-runtime/sqlite/bun.js +9 -9
  53. package/dist/entrypoints/agent-runtime/sqlite/node.js +9 -9
  54. package/dist/entrypoints/agent-runtime/testing.js +9 -9
  55. package/dist/entrypoints/agent-runtime.js +16 -14
  56. package/dist/entrypoints/application/diagnostic-journal.js +3 -3
  57. package/dist/entrypoints/application/directory-inbox.js +5 -5
  58. package/dist/entrypoints/application.js +6 -6
  59. package/dist/entrypoints/cli/publish.js +5 -5
  60. package/dist/entrypoints/cli.js +8 -8
  61. package/dist/entrypoints/contract.js +4 -4
  62. package/dist/entrypoints/files.js +4 -4
  63. package/dist/entrypoints/http.js +2 -2
  64. package/dist/entrypoints/index.js +2 -2
  65. package/dist/entrypoints/node.js +1 -1
  66. package/dist/entrypoints/process/owner-loss.js +2 -1
  67. package/dist/entrypoints/process.js +4 -4
  68. package/dist/entrypoints/remote.js +1 -1
  69. package/dist/entrypoints/server.js +12 -12
  70. package/dist/entrypoints/telegram/screens.js +5 -5
  71. package/dist/entrypoints/telegram.js +8 -8
  72. package/dist/entrypoints/testing.js +2 -2
  73. package/dist/entrypoints/tools/agent.d.ts +9 -0
  74. package/dist/entrypoints/tools/agent.d.ts.map +1 -0
  75. package/dist/entrypoints/tools/agent.js +16 -0
  76. package/dist/entrypoints/tools/connections.js +4 -4
  77. package/dist/entrypoints/tools/mcp.js +104 -4
  78. package/dist/entrypoints/tools/toolkit.d.ts +9 -0
  79. package/dist/entrypoints/tools/toolkit.d.ts.map +1 -0
  80. package/dist/entrypoints/tools/toolkit.js +40 -0
  81. package/dist/entrypoints/tools.d.ts +0 -10
  82. package/dist/entrypoints/tools.d.ts.map +1 -1
  83. package/dist/entrypoints/tools.js +25 -69
  84. package/dist/{index-s0qjs6ee.js → index-0fra8xvy.js} +9 -81
  85. package/dist/{index-rxcgp4b9.js → index-10rg78vf.js} +1 -1
  86. package/dist/index-1psbyyhj.js +21 -0
  87. package/dist/index-3prvb42w.js +30 -0
  88. package/dist/{index-ba7are04.js → index-4tyt1qya.js} +1 -1
  89. package/dist/index-568w258r.js +76 -0
  90. package/dist/{index-fqd4yqwd.js → index-6j15ktag.js} +1 -1
  91. package/dist/{index-zrdcmg6j.js → index-72xtvcvr.js} +40 -6
  92. package/dist/{index-6a69xzrx.js → index-7zwtbak8.js} +5 -46
  93. package/dist/{index-fcqj44mb.js → index-8ryydawp.js} +4 -4
  94. package/dist/{index-q968hy6y.js → index-aqmadanx.js} +2 -2
  95. package/dist/{index-s20s2mad.js → index-bgcwxhxh.js} +16 -14
  96. package/dist/index-bph35vpy.js +77 -0
  97. package/dist/{index-arjmb6de.js → index-eeahxwvg.js} +60 -76
  98. package/dist/{index-57gqaq8b.js → index-f6pw75e1.js} +1 -1
  99. package/dist/{index-f4njz9j0.js → index-gq3fnq3f.js} +1 -1
  100. package/dist/{index-ynrf908v.js → index-gtx3eknx.js} +1 -1
  101. package/dist/index-h1j71dr1.js +48 -0
  102. package/dist/{index-nn9qvkww.js → index-j95jp7ee.js} +45 -9
  103. package/dist/{index-mgb7r8b8.js → index-jswst0h3.js} +1 -1
  104. package/dist/{index-bbvc59gw.js → index-knmnj11j.js} +9 -180
  105. package/dist/{index-2kbysk9s.js → index-m5m9rmvt.js} +2 -0
  106. package/dist/{index-g444vfz2.js → index-nwm3ww60.js} +1 -1
  107. package/dist/{index-yk4g8805.js → index-pq7rkwkk.js} +1 -30
  108. package/dist/index-qaqxdbxf.js +2857 -0
  109. package/dist/{index-x8d9fdeg.js → index-qctbzmg9.js} +8 -4
  110. package/dist/index-s9a0fjh3.js +305 -0
  111. package/dist/index-sb9dr3tq.js +14 -0
  112. package/dist/{index-tvtacc9a.js → index-zyc64nbh.js} +1 -34
  113. package/dist/internal/contained-directory.d.ts +1 -1
  114. package/dist/internal/contained-directory.d.ts.map +1 -1
  115. package/dist/internal/contained-files.d.ts +18 -51
  116. package/dist/internal/contained-files.d.ts.map +1 -1
  117. package/dist/internal/contained-read.d.ts +17 -0
  118. package/dist/internal/contained-read.d.ts.map +1 -0
  119. package/dist/internal/darwin-binding.d.ts +3 -1
  120. package/dist/internal/darwin-binding.d.ts.map +1 -1
  121. package/dist/process/bun-child.d.ts.map +1 -1
  122. package/dist/process/command-owner.d.ts.map +1 -1
  123. package/dist/process/group.d.ts +2 -0
  124. package/dist/process/group.d.ts.map +1 -1
  125. package/dist/process/launch.d.ts +1 -0
  126. package/dist/process/launch.d.ts.map +1 -1
  127. package/dist/process/owned-child.d.ts.map +1 -1
  128. package/dist/process/owner-loss-guard.d.ts.map +1 -1
  129. package/dist/process/owner-loss-protocol.d.ts +1 -0
  130. package/dist/process/owner-loss-protocol.d.ts.map +1 -1
  131. package/llms/agent-runtime-coding-tools.txt +9 -9
  132. package/llms/agent-runtime-harness.txt +4 -3
  133. package/llms/agent-runtime-sandbox.txt +1 -1
  134. package/llms/agent-runtime.2.txt +20 -2
  135. package/llms/agent-runtime.3.txt +4 -46
  136. package/llms/agent-runtime.4.txt +113 -0
  137. package/llms/agent-runtime.txt +70 -37
  138. package/llms/application.3.txt +5 -2
  139. package/llms/cli.txt +1 -1
  140. package/llms/contract.txt +1 -1
  141. package/llms/process.txt +3 -1
  142. package/llms/server.5.txt +2 -0
  143. package/llms/stitchkit.txt +15 -9
  144. package/llms/testing.txt +4 -6
  145. package/llms/tools-agent.txt +24 -0
  146. package/llms/tools-mcp.txt +71 -6
  147. package/llms/tools-toolkit.txt +23 -0
  148. package/llms/tools.2.txt +6 -3
  149. package/llms/tools.3.txt +8 -77
  150. package/llms/tools.txt +8 -8
  151. package/llms/{upgrading-0.106.0-0.119.0.txt → upgrading-0.106.0-0.121.0.txt} +84 -3
  152. package/llms/upgrading.txt +1 -1
  153. package/llms-full.txt +360 -139
  154. package/llms.txt +11 -9
  155. package/native/darwin-arm64.node +0 -0
  156. package/native/darwin-x64.node +0 -0
  157. package/native-assets.json +4 -4
  158. package/package.json +10 -2
  159. package/dist/index-1k18qzqa.js +0 -1478
  160. package/dist/index-6f49krb5.js +0 -107
  161. package/dist/{index-2dfvv1jg.js → index-2fc1k9k2.js} +0 -0
  162. package/dist/{index-qndsr7f0.js → index-5efrpfmj.js} +3 -3
  163. /package/dist/{index-cyxp8rbf.js → index-mtwh9hyp.js} +0 -0
@@ -0,0 +1,2857 @@
1
+ import {
2
+ NativeCommandError2
3
+ } from "./index-72xtvcvr.js";
4
+ import {
5
+ CommandStopped,
6
+ startNativeCommand
7
+ } from "./index-j95jp7ee.js";
8
+ import {
9
+ ScanStopped,
10
+ ContainedScanBudget,
11
+ readContainedUtf8Handle,
12
+ scanContainedFiles
13
+ } from "./index-s9a0fjh3.js";
14
+ import {
15
+ writeContainedFile
16
+ } from "./index-aqmadanx.js";
17
+ import {
18
+ openPinnedDirectory,
19
+ openContainedDirectory,
20
+ missingContainedDirectories,
21
+ misspelledContainedSegment,
22
+ UnapprovedContainedDirectoryError,
23
+ openContainedParent,
24
+ openContainedFile,
25
+ ContainedPathChangedError,
26
+ assertContainedFileCurrent,
27
+ assertContainedParentCurrent,
28
+ containedEntryMetadata,
29
+ openContainedParentFile
30
+ } from "./index-eeahxwvg.js";
31
+ import {
32
+ withSignalDeadline
33
+ } from "./index-9tqbxv5p.js";
34
+ import {
35
+ defineRuntimeTool2
36
+ } from "./index-aqwg2dm5.js";
37
+ import {
38
+ errnoOf
39
+ } from "./index-jj9228tt.js";
40
+ import {
41
+ MAX_TIMER_MS
42
+ } from "./index-eemvahkc.js";
43
+ import {
44
+ AppError2,
45
+ forbidden2,
46
+ conflict2,
47
+ STITCH_ERROR_STATUS2
48
+ } from "./index-qcn84byh.js";
49
+
50
+ // src/agent-runtime/coding-tool-contract.ts
51
+ import { z as z2 } from "zod";
52
+
53
+ // src/agent-runtime/coding-search-limits.ts
54
+ import { z } from "zod";
55
+ var MAX_DIRECTORY_ENTRIES = 2147483647;
56
+ var AgentCodingSearchLimitsSchema = z.object({
57
+ maxSearchResults: z.int().positive(),
58
+ maxSearchFiles: z.int().positive(),
59
+ maxSearchDepth: z.int().nonnegative(),
60
+ maxSearchDirectories: z.int().positive(),
61
+ maxSearchEntries: z.int().positive().max(MAX_DIRECTORY_ENTRIES),
62
+ maxSearchBytes: z.int().positive(),
63
+ maxSearchOutputBytes: z.int().min(1024),
64
+ searchTimeoutMs: z.int().positive().max(MAX_TIMER_MS)
65
+ }).strict();
66
+
67
+ // src/agent-runtime/coding-tool-contract.ts
68
+ var AgentCodingToolLimitsSchema2 = z2.object({
69
+ maxPathBytes: z2.int().positive(),
70
+ maxReadBytes: z2.int().positive(),
71
+ maxWriteBytes: z2.int().positive(),
72
+ maxShellArguments: z2.int().positive(),
73
+ maxShellArgumentBytes: z2.int().positive(),
74
+ maxShellOutputBytes: z2.int().positive(),
75
+ maxArtifactBytes: z2.int().positive(),
76
+ maxListEntries: z2.int().positive(),
77
+ ...AgentCodingSearchLimitsSchema.shape,
78
+ shellTimeoutMs: z2.int().positive(),
79
+ shellTerminationGraceMs: z2.int().positive()
80
+ }).strict();
81
+ var AgentCodingToolAuthorizationSchema2 = z2.discriminatedUnion("operation", [
82
+ z2.object({ operation: z2.literal("read"), path: z2.string().min(1) }).strict(),
83
+ z2.object({
84
+ operation: z2.literal("write"),
85
+ path: z2.string().min(1),
86
+ bytes: z2.int().nonnegative(),
87
+ overwrite: z2.boolean(),
88
+ createsDirectories: z2.array(z2.string().min(1))
89
+ }).strict(),
90
+ z2.object({
91
+ operation: z2.literal("search"),
92
+ query: z2.string().min(1),
93
+ mode: z2.enum(["path", "content"])
94
+ }).strict(),
95
+ z2.object({
96
+ operation: z2.literal("edit"),
97
+ path: z2.string().min(1),
98
+ baseSha256: z2.string().length(64),
99
+ resultSha256: z2.string().length(64),
100
+ resultBytes: z2.int().nonnegative(),
101
+ replacements: z2.int().positive(),
102
+ dryRun: z2.boolean()
103
+ }).strict(),
104
+ z2.object({ operation: z2.literal("list"), path: z2.string().min(1) }).strict(),
105
+ z2.object({
106
+ operation: z2.literal("glob"),
107
+ pattern: z2.string().min(1),
108
+ path: z2.string().min(1)
109
+ }).strict(),
110
+ z2.object({
111
+ operation: z2.literal("artifact-read"),
112
+ reference: z2.string().min(1),
113
+ offset: z2.int().nonnegative(),
114
+ maxBytes: z2.int().positive()
115
+ }).strict(),
116
+ z2.object({
117
+ operation: z2.literal("artifact-search"),
118
+ reference: z2.string().min(1),
119
+ query: z2.string().min(1),
120
+ maxMatches: z2.int().positive()
121
+ }).strict(),
122
+ z2.object({
123
+ operation: z2.literal("shell"),
124
+ executable: z2.string().min(1),
125
+ args: z2.array(z2.string()),
126
+ cwd: z2.string().min(1)
127
+ }).strict()
128
+ ]);
129
+ var AgentCodingToolPathAuthorizationSchema2 = z2.object({ path: z2.string().min(1) }).strict();
130
+ var CodingDeniedEntrySchema = z2.object({
131
+ path: z2.string().min(1),
132
+ kind: z2.enum(["file", "directory", "symlink", "other"])
133
+ }).strict();
134
+ var FileReadInputSchema = z2.object({
135
+ path: z2.string().min(1),
136
+ offset: z2.int().nonnegative().default(0),
137
+ maxBytes: z2.int().positive().optional()
138
+ }).strict();
139
+ var FileReadOutputSchema = z2.object({
140
+ path: z2.string().min(1),
141
+ text: z2.string(),
142
+ bytes: z2.int().nonnegative(),
143
+ sha256: z2.string().length(64),
144
+ truncated: z2.boolean(),
145
+ nextOffset: z2.int().nonnegative().optional()
146
+ }).strict();
147
+ var FileWriteInputSchema = z2.object({
148
+ path: z2.string().min(1),
149
+ content: z2.string(),
150
+ overwrite: z2.boolean().default(false)
151
+ }).strict();
152
+ var FileWriteOutputSchema = z2.object({
153
+ path: z2.string().min(1),
154
+ bytes: z2.int().nonnegative(),
155
+ createdDirectories: z2.array(z2.string().min(1))
156
+ }).strict();
157
+ function createShellInputSchema(executableNames) {
158
+ if (executableNames.length === 0) {
159
+ throw new Error("A command tool requires at least one executable alias");
160
+ }
161
+ return z2.object({
162
+ executable: z2.enum(executableNames),
163
+ args: z2.array(z2.string()).default([]),
164
+ cwd: z2.string().min(1).default(".")
165
+ }).strict();
166
+ }
167
+ var ShellInputSchema = z2.object({
168
+ executable: z2.string().min(1),
169
+ args: z2.array(z2.string()).default([]),
170
+ cwd: z2.string().min(1).default(".")
171
+ }).strict();
172
+ var ShellOutputSchema = z2.object({
173
+ executable: z2.string().min(1),
174
+ exitCode: z2.int().nullable(),
175
+ signal: z2.string().nullable(),
176
+ stdout: z2.string(),
177
+ stderr: z2.string(),
178
+ outcome: z2.enum(["exited", "timeout", "output-limit", "cancelled"]),
179
+ artifact: z2.object({
180
+ reference: z2.string().min(1),
181
+ bytes: z2.int().nonnegative(),
182
+ truncated: z2.boolean(),
183
+ headBytes: z2.int().nonnegative(),
184
+ tailBytes: z2.int().nonnegative(),
185
+ omittedBytes: z2.int().nonnegative()
186
+ }).strict().optional()
187
+ }).strict();
188
+ var DEFAULT_LIMITS = {
189
+ maxPathBytes: 4096,
190
+ maxReadBytes: 262144,
191
+ maxWriteBytes: 262144,
192
+ maxShellArguments: 128,
193
+ maxShellArgumentBytes: 65536,
194
+ maxShellOutputBytes: 262144,
195
+ maxArtifactBytes: 4194304,
196
+ maxListEntries: 500,
197
+ maxSearchResults: 100,
198
+ maxSearchFiles: 1e4,
199
+ maxSearchDepth: 32,
200
+ maxSearchDirectories: 1000,
201
+ maxSearchEntries: 20000,
202
+ maxSearchBytes: 16777216,
203
+ maxSearchOutputBytes: 65536,
204
+ searchTimeoutMs: 5000,
205
+ shellTimeoutMs: 30000,
206
+ shellTerminationGraceMs: 250
207
+ };
208
+ function resolveCodingToolLimits(input) {
209
+ return AgentCodingToolLimitsSchema2.parse({ ...DEFAULT_LIMITS, ...input });
210
+ }
211
+
212
+ // src/agent-runtime/sandbox.ts
213
+ import { z as z3 } from "zod";
214
+ var AgentSandboxRestrictionSchema2 = z3.enum([
215
+ "network-denied",
216
+ "write-contained",
217
+ "secrets-hidden",
218
+ "process-contained"
219
+ ]);
220
+ var AgentSandboxGradeSchema2 = z3.discriminatedUnion("grade", [
221
+ z3.object({ grade: z3.literal("full"), restrictions: z3.array(AgentSandboxRestrictionSchema2) }).strict(),
222
+ z3.object({
223
+ grade: z3.literal("partial"),
224
+ restrictions: z3.array(AgentSandboxRestrictionSchema2),
225
+ gaps: z3.array(AgentSandboxRestrictionSchema2)
226
+ }).strict(),
227
+ z3.object({ grade: z3.literal("unavailable"), reason: z3.string().min(1) }).strict()
228
+ ]);
229
+ var probes = new WeakMap;
230
+ var PROBE_FAILED = "sandbox probe failed";
231
+ var PROBE_INVALID = "sandbox probe returned no valid grade";
232
+ async function runProbe(sandbox) {
233
+ let value;
234
+ try {
235
+ value = await sandbox.probe();
236
+ } catch (cause) {
237
+ return { grade: { grade: "unavailable", reason: PROBE_FAILED }, cause };
238
+ }
239
+ const parsed = AgentSandboxGradeSchema2.safeParse(value);
240
+ if (parsed.success)
241
+ return { grade: parsed.data };
242
+ return { grade: { grade: "unavailable", reason: PROBE_INVALID }, cause: parsed.error };
243
+ }
244
+ function probeAgentProcessSandboxOutcome(sandbox, options = {}) {
245
+ if (options.refresh)
246
+ probes.delete(sandbox);
247
+ const present = probes.get(sandbox);
248
+ if (present)
249
+ return present;
250
+ const pending = runProbe(sandbox);
251
+ probes.set(sandbox, pending);
252
+ return pending;
253
+ }
254
+ async function probeAgentProcessSandbox2(sandbox, options = {}) {
255
+ const fresh = options.refresh === true || !probes.has(sandbox);
256
+ const outcome = await probeAgentProcessSandboxOutcome(sandbox, options);
257
+ if (fresh && outcome.cause !== undefined) {
258
+ process.emitWarning(new Error("Sandbox probe failed", { cause: outcome.cause }));
259
+ }
260
+ return outcome.grade;
261
+ }
262
+ function missingSandboxRestrictions2(grade, required) {
263
+ if (grade.grade === "unavailable")
264
+ return required;
265
+ const present = new Set(grade.restrictions);
266
+ return required.filter((restriction) => !present.has(restriction));
267
+ }
268
+ async function recordAgentSandboxProbe2(input) {
269
+ const grade = await probeAgentProcessSandbox2(input.sandbox);
270
+ const missing = missingSandboxRestrictions2(grade, input.required);
271
+ await input.store.appendEvent({
272
+ conversationId: input.conversationId,
273
+ kind: "sandbox/probed",
274
+ payload: {
275
+ result: grade,
276
+ required: [...input.required],
277
+ missing: [...missing]
278
+ }
279
+ });
280
+ return grade;
281
+ }
282
+
283
+ // src/agent-runtime/coding-tool-refusals.ts
284
+ function codingRefusal(code, message, options = {}) {
285
+ throw new AppError2(code, {
286
+ message,
287
+ status: STITCH_ERROR_STATUS2[code],
288
+ details: { message, ...options.details },
289
+ hint: options.hint,
290
+ ..."cause" in options && { cause: options.cause }
291
+ });
292
+ }
293
+ function codingPathRefusal(code, message, path, options = {}) {
294
+ codingRefusal(code, message, {
295
+ details: { path, ...options.details },
296
+ ...options.hint && { hint: options.hint }
297
+ });
298
+ }
299
+ function refuseMissingCodingPath(error, relative) {
300
+ refuseChangedPath(error, relative);
301
+ const code = errnoOf(error);
302
+ if (error instanceof Error && error.message.includes("is not a regular file")) {
303
+ codingPathRefusal("BAD_REQUEST", "This path is not a regular file", relative, {
304
+ hint: "Use list_directory to see what is here."
305
+ });
306
+ }
307
+ if (code === "ENOENT") {
308
+ codingPathRefusal("NOT_FOUND", "This path does not exist", relative, {
309
+ hint: "Use list_directory or glob to find the right path."
310
+ });
311
+ }
312
+ if (code === "ENOTDIR") {
313
+ codingPathRefusal("BAD_REQUEST", "A segment of this path is not a directory", relative, {
314
+ hint: "Check the path with list_directory."
315
+ });
316
+ }
317
+ if (code === "ELOOP") {
318
+ codingPathRefusal("FORBIDDEN", "This path is a symlink, which is not followed", relative, {
319
+ hint: "Read or write the target directly, by its own workspace-relative path."
320
+ });
321
+ }
322
+ if (code === "EISDIR" || code === "ENXIO" || code === "EOPNOTSUPP") {
323
+ codingPathRefusal("BAD_REQUEST", "This path is not a regular file", relative, {
324
+ hint: "Use list_directory to see what is here."
325
+ });
326
+ }
327
+ throw error;
328
+ }
329
+ function refuseUnopenableCodingDirectory(error, relative) {
330
+ const code = errnoOf(error);
331
+ if (code === "ENOENT") {
332
+ codingPathRefusal("NOT_FOUND", "This directory does not exist", relative, {
333
+ hint: "List a parent directory to see what is there."
334
+ });
335
+ }
336
+ if (code === "ENOTDIR" || code === "ELOOP") {
337
+ codingPathRefusal("BAD_REQUEST", "This path is not a directory; symlinks are not followed", relative, {
338
+ hint: "Name a real directory by its workspace-relative path; list the parent to see what is there."
339
+ });
340
+ }
341
+ return refuseMissingCodingPath(error, relative);
342
+ }
343
+ function refuseChangedPath(error, relative) {
344
+ if (error instanceof ContainedPathChangedError) {
345
+ codingPathRefusal("CONFLICT", "This path changed while it was being authorized", relative, {
346
+ hint: "Read it again and retry."
347
+ });
348
+ }
349
+ }
350
+ function refuseWriteConflict(error, relative, details = {}) {
351
+ refuseChangedPath(error, relative);
352
+ const code = errnoOf(error);
353
+ if (code === "ENOENT") {
354
+ codingPathRefusal("CONFLICT", "This path changed while it was being written", relative, {
355
+ details,
356
+ hint: "Read it again and retry."
357
+ });
358
+ }
359
+ if (code === "ENOTDIR") {
360
+ codingPathRefusal("CONFLICT", "A segment of this path is not a directory", relative, {
361
+ details,
362
+ hint: "Write under a different path, or remove the file blocking it."
363
+ });
364
+ }
365
+ if (code === "ELOOP") {
366
+ codingPathRefusal("FORBIDDEN", "This path is a symlink, which is not followed", relative, {
367
+ details,
368
+ hint: "Write to the target directly, by its own workspace-relative path."
369
+ });
370
+ }
371
+ if (code === "EISDIR") {
372
+ codingPathRefusal("CONFLICT", "This path is a directory", relative, {
373
+ details,
374
+ hint: "Write to a file path inside it, or choose another name."
375
+ });
376
+ }
377
+ if (code === "EEXIST") {
378
+ codingPathRefusal("CONFLICT", "This file already exists", relative, {
379
+ details,
380
+ hint: "Pass overwrite: true to replace it, or edit_file to change part of it."
381
+ });
382
+ }
383
+ throw error;
384
+ }
385
+ function refuseUnknownSpillReference(reference) {
386
+ codingRefusal("SPILL_REFERENCE_UNKNOWN", `No spilled output is held under \`${reference}\``, {
387
+ details: { reference },
388
+ hint: "Use the reference returned by the command that spilled its output, not a session or call id."
389
+ });
390
+ }
391
+
392
+ // src/agent-runtime/coding-tools.ts
393
+ import path5 from "node:path";
394
+ import { z as z8 } from "zod";
395
+
396
+ // src/agent-runtime/coding-tool-files.ts
397
+ import { createHash } from "node:crypto";
398
+
399
+ // src/agent-runtime/coding-tool-paths.ts
400
+ import { lstat, realpath } from "node:fs/promises";
401
+ import path from "node:path";
402
+ function within(root, target) {
403
+ const relative = path.relative(root, target);
404
+ return relative === "" || !relative.startsWith(`..${path.sep}`) && relative !== ".." && !path.isAbsolute(relative);
405
+ }
406
+ function inputPath(root, requested, maxPathBytes) {
407
+ if (new TextEncoder().encode(requested).byteLength > maxPathBytes) {
408
+ codingRefusal("BAD_REQUEST", `Path exceeds the ${maxPathBytes}-byte limit`, {
409
+ details: { maxPathBytes },
410
+ hint: "Use a shorter workspace-relative path."
411
+ });
412
+ }
413
+ if (path.isAbsolute(requested)) {
414
+ const relative = path.relative(root, requested);
415
+ const recoveryPath = within(root, requested) ? relative.split(path.sep).join("/") || "." : undefined;
416
+ codingRefusal("BAD_REQUEST", "Paths must be relative to the workspace root", {
417
+ details: { path: requested, ...recoveryPath && { recoveryPath } },
418
+ 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."
419
+ });
420
+ }
421
+ const resolved = path.resolve(root, requested);
422
+ if (!within(root, resolved)) {
423
+ codingRefusal("FORBIDDEN", "Path escapes the workspace root", {
424
+ details: { path: requested },
425
+ hint: "Stay inside the workspace; `..` segments that leave the root are refused."
426
+ });
427
+ }
428
+ return resolved;
429
+ }
430
+ function boundedCodingRelativePath(root, requested, maxPathBytes) {
431
+ if (requested.includes("\x00")) {
432
+ codingRefusal("BAD_REQUEST", "Path segments cannot contain a NUL byte", {
433
+ details: { path: requested },
434
+ hint: "Pass the exact workspace-relative entry name without NUL bytes."
435
+ });
436
+ }
437
+ if (path.isAbsolute(requested))
438
+ inputPath(root, requested, maxPathBytes);
439
+ if (requested.includes("\\")) {
440
+ codingRefusal("FORBIDDEN", "Path separators must be `/`", {
441
+ details: { path: requested },
442
+ hint: "Use `/` between segments; a backslash is refused because the workspace walk reads it as a separator."
443
+ });
444
+ }
445
+ const segments = requested.split("/");
446
+ if (segments.some((segment) => segment === "" || segment === "." || segment === "..")) {
447
+ codingRefusal("FORBIDDEN", "Path segments must name real entries under the root", {
448
+ details: { path: requested },
449
+ hint: "Pass a plain workspace-relative path — `.`, `..` and empty segments are refused."
450
+ });
451
+ }
452
+ inputPath(root, requested, maxPathBytes);
453
+ return requested;
454
+ }
455
+ async function authorizeCodingTool(config, request) {
456
+ const parsed = AgentCodingToolAuthorizationSchema2.parse(request);
457
+ if (!await config.authorize(parsed))
458
+ forbidden2("Coding tool permission denied");
459
+ }
460
+ async function isCodingPathAuthorized(config, path2) {
461
+ if (!config.authorizePath)
462
+ return true;
463
+ const parsed = AgentCodingToolPathAuthorizationSchema2.parse({ path: path2 });
464
+ return await config.authorizePath(parsed);
465
+ }
466
+ async function authorizeCodingPath(config, path2) {
467
+ if (!await isCodingPathAuthorized(config, path2)) {
468
+ codingRefusal("FORBIDDEN", "Coding tool path permission denied", {
469
+ details: { path: path2 },
470
+ hint: "The host policy refused this path or a directory above it."
471
+ });
472
+ }
473
+ }
474
+ async function authorizeCodingPathChain(config, relative, root, budget) {
475
+ if (relative !== ".") {
476
+ const misspelled = await misspelledContainedSegment(root, relative, budget && {
477
+ check: () => budget.check(),
478
+ directory: () => budget.directory(),
479
+ entry: () => budget.entry(),
480
+ remainingEntries: () => budget.remainingEntries(),
481
+ wait: (operation, dispose) => budget.wait(operation, dispose)
482
+ });
483
+ if (misspelled !== undefined) {
484
+ codingPathRefusal("BAD_REQUEST", "This path differs from the name stored on disk", relative, {
485
+ hint: "Use list_directory to see the stored spelling."
486
+ });
487
+ }
488
+ }
489
+ if (!config.authorizePath)
490
+ return;
491
+ const segments = relative === "." ? ["."] : relative.split("/");
492
+ for (let depth = 1;depth <= segments.length; depth += 1) {
493
+ const authorize = () => authorizeCodingPath(config, segments.slice(0, depth).join("/"));
494
+ if (budget)
495
+ await budget.wait(authorize);
496
+ else
497
+ await authorize();
498
+ }
499
+ }
500
+ var codingPathLocks = new Map;
501
+ async function withCodingPathLock(root, relative, work) {
502
+ const identity = await root.stat();
503
+ const target = `${identity.dev}:${identity.ino}:${relative}`;
504
+ const previous = codingPathLocks.get(target) ?? Promise.resolve();
505
+ let release = () => {
506
+ return;
507
+ };
508
+ const current = new Promise((resolve) => {
509
+ release = resolve;
510
+ });
511
+ const queued = previous.then(() => current);
512
+ codingPathLocks.set(target, queued);
513
+ await previous;
514
+ try {
515
+ return await work();
516
+ } finally {
517
+ release();
518
+ if (codingPathLocks.get(target) === queued)
519
+ codingPathLocks.delete(target);
520
+ }
521
+ }
522
+ function textOccurrences(source, search) {
523
+ let count = 0;
524
+ let offset = 0;
525
+ while (offset <= source.length - search.length) {
526
+ const found = source.indexOf(search, offset);
527
+ if (found < 0)
528
+ break;
529
+ count += 1;
530
+ offset = found + search.length;
531
+ }
532
+ return count;
533
+ }
534
+ async function withCodingRoot(configured, work) {
535
+ const canonical = await realpath(configured);
536
+ const root = await openPinnedDirectory(canonical);
537
+ try {
538
+ return await work(root, canonical);
539
+ } finally {
540
+ await root.close();
541
+ }
542
+ }
543
+
544
+ // src/agent-runtime/coding-tool-utf8.ts
545
+ function sequenceLength(lead) {
546
+ if (lead < 128)
547
+ return 1;
548
+ if ((lead & 224) === 192)
549
+ return 2;
550
+ if ((lead & 240) === 224)
551
+ return 3;
552
+ if ((lead & 248) === 240)
553
+ return 4;
554
+ return;
555
+ }
556
+ function utf8AlignedEnd(data, start, limit) {
557
+ const safe = Math.max(start, Math.min(start + limit, data.byteLength));
558
+ for (let back = 1;back <= 4 && safe - back >= start; back += 1) {
559
+ const byte = data[safe - back];
560
+ if ((byte & 192) === 128)
561
+ continue;
562
+ const length = sequenceLength(byte);
563
+ if (length === undefined)
564
+ return safe;
565
+ if (back >= length)
566
+ return safe;
567
+ const sequenceEnd = safe - back + length;
568
+ return sequenceEnd <= data.byteLength ? sequenceEnd : safe - back;
569
+ }
570
+ return safe;
571
+ }
572
+ function utf8AlignedStart(data, start, end) {
573
+ let safe = Math.max(0, Math.min(start, end));
574
+ while (safe < end && (data[safe] & 192) === 128)
575
+ safe += 1;
576
+ return safe;
577
+ }
578
+
579
+ // src/agent-runtime/coding-tool-files.ts
580
+ async function digestFile(handle, size) {
581
+ const hash = createHash("sha256");
582
+ const chunk = Buffer.alloc(Math.min(64 * 1024, Math.max(1, size)));
583
+ for (let position = 0;position < size; ) {
584
+ const length = Math.min(chunk.byteLength, size - position);
585
+ const { bytesRead } = await handle.read(chunk, 0, length, position);
586
+ if (bytesRead === 0)
587
+ break;
588
+ hash.update(chunk.subarray(0, bytesRead));
589
+ position += bytesRead;
590
+ }
591
+ return hash.digest("hex");
592
+ }
593
+ async function openAuthorizedWriteParent(root, relative, createsDirectories) {
594
+ return openContainedParent(root, relative, { createDirectories: createsDirectories }).catch((error) => {
595
+ if (error instanceof UnapprovedContainedDirectoryError) {
596
+ codingPathRefusal("CONFLICT", "The directory creation plan changed during authorization", relative, {
597
+ details: { createsDirectories },
598
+ hint: "Retry the write so the host can authorize the current directories."
599
+ });
600
+ }
601
+ refuseWriteConflict(error, relative, { createsDirectories });
602
+ });
603
+ }
604
+ function createFileCodingTools(config, limits) {
605
+ const read = defineRuntimeTool2({
606
+ name: "read_file",
607
+ description: "Read a bounded UTF-8 slice from a host-authorized workspace file.",
608
+ identity: { serviceName: "coding", action: "read-file", method: "POST" },
609
+ input: FileReadInputSchema,
610
+ output: FileReadOutputSchema,
611
+ transports: ["AGENT"],
612
+ handler: async ({ input }) => withCodingRoot(config.root, async (root, rootPath) => {
613
+ const relative = boundedCodingRelativePath(rootPath, input.path, limits.maxPathBytes);
614
+ await authorizeCodingPathChain(config, relative, root);
615
+ const handle = await openContainedFile(root, relative).catch((error) => refuseMissingCodingPath(error, relative));
616
+ const maximum = Math.min(input.maxBytes ?? limits.maxReadBytes, limits.maxReadBytes);
617
+ let selected;
618
+ let start = input.offset;
619
+ let size;
620
+ let sha256;
621
+ try {
622
+ await authorizeCodingTool(config, { operation: "read", path: relative });
623
+ await assertContainedFileCurrent(root, relative, handle).catch((error) => refuseMissingCodingPath(error, relative));
624
+ const metadata = await handle.stat();
625
+ if (!metadata.isFile()) {
626
+ codingPathRefusal("BAD_REQUEST", "This path is not a regular file", relative, {
627
+ hint: "Use list_directory to see what is here."
628
+ });
629
+ }
630
+ size = metadata.size;
631
+ const length = Math.min(maximum + 4, Math.max(0, size - input.offset));
632
+ selected = Buffer.alloc(length);
633
+ const { bytesRead } = await handle.read(selected, 0, length, input.offset);
634
+ selected = selected.subarray(0, bytesRead);
635
+ if (input.offset > 0) {
636
+ const aligned = utf8AlignedStart(selected, 0, selected.byteLength);
637
+ start = input.offset + aligned;
638
+ selected = selected.subarray(aligned);
639
+ }
640
+ if (selected.byteLength > maximum) {
641
+ selected = selected.subarray(0, utf8AlignedEnd(selected, 0, maximum));
642
+ }
643
+ sha256 = await digestFile(handle, size);
644
+ } finally {
645
+ await handle.close();
646
+ }
647
+ let text;
648
+ try {
649
+ text = new TextDecoder("utf-8", { fatal: true }).decode(selected);
650
+ } catch {
651
+ codingPathRefusal("BAD_REQUEST", "The selected file bytes are not valid UTF-8", relative, {
652
+ details: { reason: "invalid_utf8" },
653
+ hint: "Use a binary-capable reader or convert the file to UTF-8."
654
+ });
655
+ }
656
+ const end = start + selected.byteLength;
657
+ const truncated = end < size;
658
+ return {
659
+ path: relative,
660
+ text,
661
+ bytes: selected.byteLength,
662
+ sha256,
663
+ truncated,
664
+ ...truncated && { nextOffset: end }
665
+ };
666
+ })
667
+ });
668
+ const write = defineRuntimeTool2({
669
+ name: "write_file",
670
+ description: "Write bounded UTF-8 content to a host-authorized workspace file.",
671
+ identity: { serviceName: "coding", action: "write-file", method: "POST" },
672
+ input: FileWriteInputSchema,
673
+ output: FileWriteOutputSchema,
674
+ transports: ["AGENT"],
675
+ handler: async ({ input }) => withCodingRoot(config.root, async (root, rootPath) => {
676
+ const relative = boundedCodingRelativePath(rootPath, input.path, limits.maxPathBytes);
677
+ await authorizeCodingPathChain(config, relative, root);
678
+ const bytes = Buffer.byteLength(input.content);
679
+ if (bytes > limits.maxWriteBytes) {
680
+ codingPathRefusal("BAD_REQUEST", `The content is ${bytes} bytes, over the ${limits.maxWriteBytes}-byte limit`, relative, {
681
+ details: { bytes, maxWriteBytes: limits.maxWriteBytes },
682
+ hint: "Write a smaller file, or split it."
683
+ });
684
+ }
685
+ const createsDirectories = await missingContainedDirectories(root, relative).catch((error) => refuseWriteConflict(error, relative));
686
+ await authorizeCodingTool(config, {
687
+ operation: "write",
688
+ path: relative,
689
+ bytes,
690
+ overwrite: input.overwrite,
691
+ createsDirectories
692
+ });
693
+ await withCodingPathLock(root, relative, async () => {
694
+ const parent = await openAuthorizedWriteParent(root, relative, createsDirectories);
695
+ try {
696
+ await assertContainedParentCurrent(root, relative, parent.handle).catch((error) => refuseWriteConflict(error, relative));
697
+ const current = await containedEntryMetadata(parent);
698
+ if (input.overwrite) {
699
+ if (current?.isSymbolicLink()) {
700
+ codingPathRefusal("FORBIDDEN", "This path is a symlink", relative, {
701
+ hint: "Coding tools never write through symlinks; write to the target directly."
702
+ });
703
+ }
704
+ await writeContainedFile({
705
+ parent,
706
+ content: input.content,
707
+ replace: true,
708
+ ...current && { mode: current.mode }
709
+ }).catch((error) => refuseWriteConflict(error, relative));
710
+ } else {
711
+ if (current) {
712
+ codingPathRefusal("CONFLICT", "This file already exists", relative, {
713
+ hint: "Pass overwrite: true to replace it, or edit_file to change part of it."
714
+ });
715
+ }
716
+ await writeContainedFile({
717
+ parent,
718
+ content: input.content,
719
+ replace: false
720
+ }).catch((error) => refuseWriteConflict(error, relative));
721
+ }
722
+ } finally {
723
+ await parent.handle.close();
724
+ }
725
+ });
726
+ return { path: relative, bytes, createdDirectories: createsDirectories };
727
+ })
728
+ });
729
+ return [read, write];
730
+ }
731
+
732
+ // src/agent-runtime/coding-tool-listing.ts
733
+ import path2 from "node:path";
734
+ import { z as z5 } from "zod";
735
+
736
+ // src/agent-runtime/coding-discovery.ts
737
+ function discoveryRun(config, limits, path, include, symlinks) {
738
+ const started = performance.now();
739
+ const budget = new ContainedScanBudget({
740
+ maxDirectories: limits.maxSearchDirectories,
741
+ maxEntries: limits.maxSearchEntries,
742
+ maxBytes: limits.maxSearchBytes,
743
+ deadline: started + limits.searchTimeoutMs
744
+ });
745
+ const coverage = {
746
+ complete: true,
747
+ reasons: [],
748
+ scope: {
749
+ path,
750
+ include,
751
+ excludedDirectories: config.search?.excludeDirectories ? "configured" : "default",
752
+ symlinks
753
+ },
754
+ visitedDirectories: 0,
755
+ visitedEntries: 0,
756
+ readBytes: 0,
757
+ elapsedMs: 0
758
+ };
759
+ return {
760
+ budget,
761
+ coverage,
762
+ async run(caller, work) {
763
+ if (caller?.aborted) {
764
+ budget.reasons.add("cancelled");
765
+ return;
766
+ }
767
+ try {
768
+ await withSignalDeadline(limits.searchTimeoutMs, caller, () => "discovery-timeout", async (signal) => {
769
+ budget.limits.signal = signal;
770
+ budget.directory();
771
+ const root = await budget.wait(() => openPinnedDirectory(config.root), (handle) => {
772
+ handle.close().catch(() => {
773
+ return;
774
+ });
775
+ });
776
+ try {
777
+ budget.check();
778
+ await work(root);
779
+ } finally {
780
+ await root.close();
781
+ }
782
+ });
783
+ } catch (error) {
784
+ if (!(error instanceof ScanStopped))
785
+ throw error;
786
+ }
787
+ },
788
+ refresh() {
789
+ coverage.complete = budget.reasons.size === 0;
790
+ coverage.reasons = [...budget.reasons];
791
+ coverage.visitedDirectories = budget.visitedDirectories;
792
+ coverage.visitedEntries = budget.visitedEntries;
793
+ coverage.readBytes = budget.readBytes;
794
+ coverage.elapsedMs = Math.round((performance.now() - started) * 100) / 100;
795
+ }
796
+ };
797
+ }
798
+ function discoveryCollector(output, budget, maximum) {
799
+ const reserve = Buffer.byteLength(JSON.stringify(output)) + 512;
800
+ if (reserve > maximum)
801
+ codingRefusal("BAD_REQUEST", "Discovery output budget cannot hold its coverage envelope", {
802
+ hint: "Increase maxSearchOutputBytes or shorten the path/pattern."
803
+ });
804
+ let bytes = reserve;
805
+ return (item) => {
806
+ budget.check();
807
+ const next = Buffer.byteLength(JSON.stringify(item)) + 1;
808
+ if (bytes + next > maximum)
809
+ budget.stop("max-output-bytes");
810
+ bytes += next;
811
+ };
812
+ }
813
+
814
+ // src/agent-runtime/coding-search-output.ts
815
+ function boundSearchDiagnostics(output, budget, maxBytes) {
816
+ const canContinue = () => {
817
+ try {
818
+ budget.check();
819
+ return true;
820
+ } catch (error) {
821
+ if (!(error instanceof ScanStopped))
822
+ throw error;
823
+ return false;
824
+ }
825
+ };
826
+ canContinue();
827
+ const denied = output.denied;
828
+ const errors = output.errors;
829
+ if (denied)
830
+ output.denied = [];
831
+ if (errors)
832
+ output.errors = [];
833
+ const available = maxBytes - Buffer.byteLength(JSON.stringify(output)) - 256;
834
+ let used = 0;
835
+ const take = (items, truncated) => {
836
+ const admitted = [];
837
+ for (const item of items) {
838
+ if (!canContinue()) {
839
+ truncated();
840
+ break;
841
+ }
842
+ const bytes = Buffer.byteLength(JSON.stringify(item)) + 1;
843
+ if (used + bytes > available) {
844
+ budget.reasons.add("max-output-bytes");
845
+ truncated();
846
+ break;
847
+ }
848
+ used += bytes;
849
+ admitted.push(item);
850
+ }
851
+ return admitted;
852
+ };
853
+ if (denied)
854
+ output.denied = take(denied, () => {
855
+ output.deniedTruncated = true;
856
+ });
857
+ if (errors)
858
+ output.errors = take(errors, () => {
859
+ output.errorsTruncated = true;
860
+ });
861
+ }
862
+
863
+ // src/agent-runtime/contained-scan-contract.ts
864
+ import { z as z4 } from "zod";
865
+ var ScanStopReasonSchema = z4.enum([
866
+ "max-files",
867
+ "max-depth",
868
+ "max-directories",
869
+ "max-entries",
870
+ "max-bytes",
871
+ "max-results",
872
+ "max-output-bytes",
873
+ "cancelled",
874
+ "timeout",
875
+ "denied",
876
+ "unreadable",
877
+ "max-file-bytes",
878
+ "max-line-bytes"
879
+ ]);
880
+ var DiscoveryCoverageSchema = z4.object({
881
+ complete: z4.boolean(),
882
+ reasons: z4.array(ScanStopReasonSchema),
883
+ scope: z4.object({
884
+ path: z4.string(),
885
+ include: z4.string(),
886
+ excludedDirectories: z4.enum(["default", "configured"]),
887
+ symlinks: z4.enum(["skip", "list"])
888
+ }).strict(),
889
+ visitedDirectories: z4.int().nonnegative(),
890
+ visitedEntries: z4.int().nonnegative(),
891
+ readBytes: z4.int().nonnegative(),
892
+ elapsedMs: z4.number().nonnegative()
893
+ }).strict();
894
+
895
+ // src/agent-runtime/workspace-glob.ts
896
+ function compileWorkspaceGlob(pattern, check) {
897
+ let operations = 0;
898
+ const checkpoint = () => {
899
+ if (++operations % 128 === 0)
900
+ check?.();
901
+ };
902
+ const states = [{ epsilon: [], edges: [] }];
903
+ const add = () => {
904
+ states.push({ epsilon: [], edges: [] });
905
+ return states.length - 1;
906
+ };
907
+ let current = 0;
908
+ for (let index = 0;index < pattern.length; index += 1) {
909
+ checkpoint();
910
+ const state = states[current];
911
+ if (!state)
912
+ throw new Error("Missing glob state");
913
+ const character = pattern[index];
914
+ const next = add();
915
+ if (character === "*" && pattern[index + 1] === "*") {
916
+ state.epsilon.push(next);
917
+ if (pattern[index + 2] === "/") {
918
+ const segment = add();
919
+ state.edges.push({ kind: "segment", next: segment });
920
+ states[segment]?.edges.push({ kind: "segment", next: segment }, { kind: "literal", value: "/", next: current });
921
+ index += 2;
922
+ } else {
923
+ state.edges.push({ kind: "dot", next: current });
924
+ index += 1;
925
+ }
926
+ } else if (character === "*") {
927
+ state.epsilon.push(next);
928
+ state.edges.push({ kind: "segment", next: current });
929
+ } else {
930
+ state.edges.push(character === "?" ? { kind: "segment", next } : { kind: "literal", value: character, next });
931
+ }
932
+ current = next;
933
+ }
934
+ const accepting = current;
935
+ const closure = (initial) => {
936
+ const reached = new Set(initial);
937
+ for (const index of reached) {
938
+ checkpoint();
939
+ for (const next of states[index]?.epsilon ?? [])
940
+ reached.add(next);
941
+ }
942
+ return reached;
943
+ };
944
+ const advance = (active, character) => {
945
+ const next = new Set;
946
+ for (const index of active) {
947
+ checkpoint();
948
+ for (const edge of states[index]?.edges ?? []) {
949
+ if (edge.kind === "literal" ? character === edge.value : edge.kind === "segment" ? character !== "/" : !`
950
+ \r\u2028\u2029`.includes(character)) {
951
+ next.add(edge.next);
952
+ }
953
+ }
954
+ }
955
+ return closure(next);
956
+ };
957
+ const consume = (value) => {
958
+ let active = closure([0]);
959
+ for (let index = 0;index < value.length && active.size > 0; index += 1) {
960
+ active = advance(active, value.charAt(index));
961
+ }
962
+ return active;
963
+ };
964
+ return {
965
+ test: (value) => consume(value).has(accepting),
966
+ mayContain: (directory) => {
967
+ const active = consume(`${directory}/`);
968
+ return [...active].some((index) => states[index]?.edges.some((edge) => edge.kind !== "literal" || edge.value !== "/"));
969
+ }
970
+ };
971
+ }
972
+
973
+ // src/agent-runtime/coding-tool-listing.ts
974
+ var DEFAULT_EXCLUDED_DIRECTORIES = [
975
+ ".git",
976
+ ".next",
977
+ ".stitchkit",
978
+ "build",
979
+ "coverage",
980
+ "dist",
981
+ "node_modules"
982
+ ];
983
+ var ListInputSchema = z5.object({ path: z5.string().min(1).default(".") }).strict();
984
+ var diagnostics = {
985
+ denied: z5.array(CodingDeniedEntrySchema).optional(),
986
+ deniedTruncated: z5.boolean().optional(),
987
+ errors: z5.array(z5.object({ path: z5.string(), operation: z5.string(), code: z5.string() }).strict()).optional(),
988
+ errorsTruncated: z5.boolean().optional(),
989
+ coverage: DiscoveryCoverageSchema
990
+ };
991
+ var ListOutputSchema = z5.object({
992
+ path: z5.string().min(1),
993
+ entries: z5.array(z5.object({
994
+ name: z5.string().min(1),
995
+ kind: z5.enum(["file", "directory", "symlink", "other"]),
996
+ bytes: z5.int().nonnegative().optional(),
997
+ excluded: z5.boolean().optional()
998
+ }).strict()),
999
+ truncated: z5.boolean(),
1000
+ ...diagnostics
1001
+ }).strict();
1002
+ var GlobInputSchema = z5.object({ pattern: z5.string().min(1).max(4096), path: z5.string().min(1).default(".") }).strict();
1003
+ var GlobOutputSchema = z5.object({
1004
+ paths: z5.array(z5.string().min(1)),
1005
+ truncated: z5.boolean(),
1006
+ resultTruncated: z5.boolean(),
1007
+ skippedDirectories: z5.int().nonnegative(),
1008
+ scannedFiles: z5.int().nonnegative(),
1009
+ ...diagnostics
1010
+ }).strict();
1011
+ function createListingCodingTools(config, limits) {
1012
+ const excluded = new Set(config.search?.excludeDirectories ?? DEFAULT_EXCLUDED_DIRECTORIES);
1013
+ const relativePath = (requested) => requested === "." ? "." : boundedCodingRelativePath(path2.resolve(config.root), requested, limits.maxPathBytes);
1014
+ const dispose = (handle) => {
1015
+ handle.close().catch(() => {
1016
+ return;
1017
+ });
1018
+ };
1019
+ const list = defineRuntimeTool2({
1020
+ name: "list_directory",
1021
+ description: "List bounded direct workspace entries with explicit coverage.",
1022
+ identity: { serviceName: "coding", action: "list-directory", method: "POST" },
1023
+ input: ListInputSchema,
1024
+ output: ListOutputSchema,
1025
+ transports: ["AGENT"],
1026
+ handler: async ({ input, signal }) => {
1027
+ const relative = relativePath(input.path);
1028
+ const run = discoveryRun(config, limits, relative, "*", "list");
1029
+ const { budget } = run;
1030
+ budget.limits.maxEntries = Math.min(limits.maxListEntries, limits.maxSearchEntries);
1031
+ const output = {
1032
+ path: relative,
1033
+ entries: [],
1034
+ truncated: false,
1035
+ coverage: run.coverage
1036
+ };
1037
+ const retain = discoveryCollector(output, budget, limits.maxSearchOutputBytes);
1038
+ await run.run(signal, async (root) => {
1039
+ await budget.wait(() => authorizeCodingPathChain(config, relative, root, budget));
1040
+ await budget.wait(() => authorizeCodingTool(config, { operation: "list", path: relative }));
1041
+ const directory = await budget.wait(() => openContainedDirectory(root, relative, () => budget.directory()), dispose).catch((error) => refuseUnopenableCodingDirectory(error, relative));
1042
+ try {
1043
+ const scan = await scanContainedFiles({
1044
+ root: directory,
1045
+ basePath: relative,
1046
+ budget,
1047
+ maxDepth: 0,
1048
+ maxFiles: limits.maxSearchFiles,
1049
+ symlinks: "skip",
1050
+ skipUnreadable: true,
1051
+ maxDiagnostics: limits.maxListEntries,
1052
+ authorizePath: (candidate) => isCodingPathAuthorized(config, candidate),
1053
+ visitEntry: async (parent, entry) => {
1054
+ if (output.entries.length >= limits.maxListEntries)
1055
+ budget.stop("max-results");
1056
+ const metadata = entry.kind === "file" ? await budget.wait(() => containedEntryMetadata({ handle: parent, basename: entry.name })) : null;
1057
+ const item = {
1058
+ ...entry,
1059
+ ...metadata?.size !== undefined && { bytes: metadata.size },
1060
+ ...entry.kind === "directory" && excluded.has(entry.name) && { excluded: true }
1061
+ };
1062
+ retain(item);
1063
+ output.entries.push(item);
1064
+ return false;
1065
+ }
1066
+ });
1067
+ if (scan.denied.length) {
1068
+ output.denied = scan.denied.map(({ relative, kind }) => ({
1069
+ path: relative,
1070
+ kind
1071
+ }));
1072
+ output.deniedTruncated = scan.deniedTruncated;
1073
+ }
1074
+ if (scan.errors.length) {
1075
+ output.errors = scan.errors;
1076
+ output.errorsTruncated = scan.errorsTruncated;
1077
+ }
1078
+ } finally {
1079
+ await directory.close();
1080
+ }
1081
+ });
1082
+ output.entries.sort((a, b) => a.kind === b.kind ? a.name.localeCompare(b.name) : a.kind === "directory" ? -1 : b.kind === "directory" ? 1 : 0);
1083
+ run.refresh();
1084
+ boundSearchDiagnostics(output, budget, limits.maxSearchOutputBytes);
1085
+ run.refresh();
1086
+ output.truncated = !output.coverage.complete;
1087
+ return output;
1088
+ }
1089
+ });
1090
+ const glob = defineRuntimeTool2({
1091
+ name: "glob",
1092
+ description: "List bounded workspace files matching **/*/? with explicit coverage.",
1093
+ identity: { serviceName: "coding", action: "glob", method: "POST" },
1094
+ input: GlobInputSchema,
1095
+ output: GlobOutputSchema,
1096
+ transports: ["AGENT"],
1097
+ handler: async ({ input, signal }) => {
1098
+ const relative = relativePath(input.path);
1099
+ const run = discoveryRun(config, limits, relative, input.pattern, "skip");
1100
+ const { budget } = run;
1101
+ const output = {
1102
+ paths: [],
1103
+ truncated: false,
1104
+ resultTruncated: false,
1105
+ skippedDirectories: 0,
1106
+ scannedFiles: 0,
1107
+ coverage: run.coverage
1108
+ };
1109
+ const retain = discoveryCollector(output, budget, limits.maxSearchOutputBytes);
1110
+ await run.run(signal, async (root) => {
1111
+ const matcher = compileWorkspaceGlob(input.pattern, () => budget.check());
1112
+ await budget.wait(() => authorizeCodingPathChain(config, relative, root, budget));
1113
+ await budget.wait(() => authorizeCodingTool(config, {
1114
+ operation: "glob",
1115
+ pattern: input.pattern,
1116
+ path: relative
1117
+ }));
1118
+ const directory = await budget.wait(() => openContainedDirectory(root, relative, () => budget.directory()), dispose).catch((error) => refuseUnopenableCodingDirectory(error, relative));
1119
+ const prefix = relative === "." ? "" : `${relative}/`;
1120
+ try {
1121
+ const scan = await scanContainedFiles({
1122
+ root: directory,
1123
+ basePath: relative,
1124
+ budget,
1125
+ maxDepth: limits.maxSearchDepth,
1126
+ maxFiles: limits.maxSearchFiles,
1127
+ symlinks: "skip",
1128
+ skipUnreadable: true,
1129
+ maxDiagnostics: limits.maxSearchResults,
1130
+ excludeDirectory: (directory) => directory.split("/").some((segment) => excluded.has(segment)) || !matcher.mayContain(directory.slice(prefix.length)),
1131
+ authorizePath: (candidate) => isCodingPathAuthorized(config, candidate),
1132
+ visitFile: (file) => {
1133
+ if (!matcher.test(file.relative.slice(prefix.length)))
1134
+ return true;
1135
+ if (output.paths.length >= limits.maxSearchResults) {
1136
+ output.resultTruncated = true;
1137
+ budget.stop("max-results");
1138
+ }
1139
+ retain(file.relative);
1140
+ output.paths.push(file.relative);
1141
+ if (output.paths.length >= limits.maxSearchResults) {
1142
+ output.resultTruncated = true;
1143
+ budget.stop("max-results");
1144
+ }
1145
+ return true;
1146
+ }
1147
+ });
1148
+ output.scannedFiles = scan.scannedFiles;
1149
+ output.skippedDirectories = scan.skippedDirectories;
1150
+ if (scan.denied.length) {
1151
+ output.denied = scan.denied.map(({ relative, kind }) => ({
1152
+ path: relative,
1153
+ kind
1154
+ }));
1155
+ output.deniedTruncated = scan.deniedTruncated;
1156
+ }
1157
+ if (scan.errors.length) {
1158
+ output.errors = scan.errors;
1159
+ output.errorsTruncated = scan.errorsTruncated;
1160
+ }
1161
+ } finally {
1162
+ await directory.close();
1163
+ }
1164
+ });
1165
+ output.paths.sort();
1166
+ run.refresh();
1167
+ boundSearchDiagnostics(output, budget, limits.maxSearchOutputBytes);
1168
+ run.refresh();
1169
+ output.truncated = !output.coverage.complete;
1170
+ return output;
1171
+ }
1172
+ });
1173
+ return [list, glob];
1174
+ }
1175
+
1176
+ // src/agent-runtime/coding-tool-search-patch.ts
1177
+ import { createHash as createHash2 } from "node:crypto";
1178
+ import { z as z7 } from "zod";
1179
+
1180
+ // src/agent-runtime/coding-tool-search.ts
1181
+ import path3 from "node:path";
1182
+
1183
+ // src/agent-runtime/coding-search-contract.ts
1184
+ import { z as z6 } from "zod";
1185
+ var SearchInputSchema = z6.object({
1186
+ query: z6.string().min(1).max(8192),
1187
+ mode: z6.enum(["path", "content"]).default("content"),
1188
+ regex: z6.boolean().default(false).describe("Treat `query` as a JavaScript regular expression without flags, run in linear time: groups, alternation, classes, anchors, quantifiers and (?ims:) modifiers; no backreferences or lookaround."),
1189
+ context: z6.int().nonnegative().max(10).default(0),
1190
+ include: z6.string().min(1).max(4096).describe("Anchored `**`/`*`/`?` pattern matched against the whole workspace-relative path. `*` does not cross `/`; use `**/` to include matching files in subdirectories.").optional()
1191
+ }).strict();
1192
+ var SearchMatchSchema = z6.object({
1193
+ path: z6.string().min(1),
1194
+ line: z6.int().positive().optional(),
1195
+ text: z6.string().optional(),
1196
+ before: z6.array(z6.string()).optional(),
1197
+ after: z6.array(z6.string()).optional()
1198
+ }).strict();
1199
+ var SearchOutputSchema = z6.object({
1200
+ matches: z6.array(SearchMatchSchema),
1201
+ truncated: z6.boolean(),
1202
+ scannedFiles: z6.int().nonnegative().describe("Files admitted by include and host authorization that reached path or content comparison."),
1203
+ skippedDirectories: z6.int().nonnegative(),
1204
+ skippedSymlinks: z6.int().nonnegative(),
1205
+ denied: z6.array(CodingDeniedEntrySchema).optional(),
1206
+ deniedTruncated: z6.boolean().optional(),
1207
+ errors: z6.array(z6.object({ path: z6.string(), operation: z6.string(), code: z6.string() }).strict()).optional(),
1208
+ errorsTruncated: z6.boolean().optional(),
1209
+ hint: z6.string().min(1).optional(),
1210
+ coverage: DiscoveryCoverageSchema
1211
+ }).strict();
1212
+
1213
+ // src/agent-runtime/coding-search-case-fold.ts
1214
+ var MAX_UNIT = 65535;
1215
+ var foldedUnits;
1216
+ var hex = (unit) => `\\u${unit.toString(16).padStart(4, "0")}`;
1217
+ function folded() {
1218
+ if (!foldedUnits) {
1219
+ const canonical = new Int32Array(MAX_UNIT + 1);
1220
+ const variants = new Map;
1221
+ for (let unit = 0;unit <= MAX_UNIT; unit += 1) {
1222
+ const upper = String.fromCharCode(unit).toUpperCase();
1223
+ let key = upper.length !== 1 || unit >= 128 && upper.charCodeAt(0) < 128 ? unit : upper.charCodeAt(0);
1224
+ if (key !== unit && !new RegExp(hex(unit), "i").test(String.fromCharCode(key)))
1225
+ key = unit;
1226
+ canonical[unit] = key;
1227
+ const group = variants.get(key) ?? [];
1228
+ group.push(unit);
1229
+ variants.set(key, group);
1230
+ }
1231
+ foldedUnits = { canonical, variants };
1232
+ }
1233
+ return foldedUnits;
1234
+ }
1235
+ function caseVariants(code) {
1236
+ const { canonical, variants } = folded();
1237
+ return variants.get(canonical[code] ?? code) ?? [code];
1238
+ }
1239
+ var cased;
1240
+ function casedUnits() {
1241
+ cased ??= [...folded().variants.values()].filter((group) => group.length > 1).flat();
1242
+ return cased;
1243
+ }
1244
+
1245
+ // src/agent-runtime/coding-search-class-ranges.ts
1246
+ var MAX_UNIT2 = 65535;
1247
+ var CONTROL_ESCAPES = { b: 8, t: 9, n: 10, v: 11, f: 12, r: 13 };
1248
+ var classEscapes;
1249
+ function classEscape(name) {
1250
+ if (!classEscapes) {
1251
+ classEscapes = new Map;
1252
+ for (const letter of ["d", "D", "w", "W", "s", "S"]) {
1253
+ const expression = new RegExp(`\\${letter}`);
1254
+ const ranges = [];
1255
+ for (let unit = 0;unit <= MAX_UNIT2; unit += 1) {
1256
+ if (!expression.test(String.fromCharCode(unit)))
1257
+ continue;
1258
+ const last = ranges.at(-1);
1259
+ if (last && last[1] === unit - 1)
1260
+ last[1] = unit;
1261
+ else
1262
+ ranges.push([unit, unit]);
1263
+ }
1264
+ classEscapes.set(letter, ranges);
1265
+ }
1266
+ }
1267
+ return classEscapes.get(name) ?? [];
1268
+ }
1269
+ function merge(ranges) {
1270
+ const sorted = [...ranges].sort((left, right) => left[0] - right[0]);
1271
+ const merged = [];
1272
+ for (const [low, high] of sorted) {
1273
+ const last = merged.at(-1);
1274
+ if (last && low <= last[1] + 1)
1275
+ last[1] = Math.max(last[1], high);
1276
+ else
1277
+ merged.push([low, high]);
1278
+ }
1279
+ return merged;
1280
+ }
1281
+ function complement(ranges) {
1282
+ const result = [];
1283
+ let next = 0;
1284
+ for (const [low, high] of ranges) {
1285
+ if (low > next)
1286
+ result.push([next, low - 1]);
1287
+ next = high + 1;
1288
+ }
1289
+ if (next <= MAX_UNIT2)
1290
+ result.push([next, MAX_UNIT2]);
1291
+ return result;
1292
+ }
1293
+ function classItem(body, start) {
1294
+ if (body[start] !== "\\")
1295
+ return { unit: body.charCodeAt(start), length: 1 };
1296
+ const name = body.charAt(start + 1);
1297
+ if ("dDwWsS".includes(name))
1298
+ return { set: classEscape(name), length: 2 };
1299
+ if (name in CONTROL_ESCAPES)
1300
+ return { unit: CONTROL_ESCAPES[name], length: 2 };
1301
+ const hex = (digits) => body.slice(start + 2, start + 2 + digits);
1302
+ if (name === "x" && /^[0-9A-Fa-f]{2}$/.test(hex(2)))
1303
+ return { unit: Number.parseInt(hex(2), 16), length: 4 };
1304
+ if (name === "u" && /^[0-9A-Fa-f]{4}$/.test(hex(4)))
1305
+ return { unit: Number.parseInt(hex(4), 16), length: 6 };
1306
+ if (name === "c") {
1307
+ const letter = body.charAt(start + 2);
1308
+ return /[A-Za-z0-9_]/.test(letter) ? { unit: letter.charCodeAt(0) % 32, length: 3 } : { unit: 92, length: 1 };
1309
+ }
1310
+ if (/[0-7]/.test(name)) {
1311
+ const digits = /^[0-7]+/.exec(body.slice(start + 1, start + (name <= "3" ? 4 : 3)))?.[0] ?? name;
1312
+ return { unit: Number.parseInt(digits, 8), length: 1 + digits.length };
1313
+ }
1314
+ if (name === "")
1315
+ return;
1316
+ return { unit: body.charCodeAt(start + 1), length: 2 };
1317
+ }
1318
+ function parsed(source) {
1319
+ let body = source.slice(1, -1);
1320
+ const negated = body.startsWith("^");
1321
+ if (negated)
1322
+ body = body.slice(1);
1323
+ const ranges = [];
1324
+ const add = (item) => {
1325
+ if (item.set)
1326
+ ranges.push(...item.set);
1327
+ else if (item.unit !== undefined)
1328
+ ranges.push([item.unit, item.unit]);
1329
+ };
1330
+ for (let index = 0;index < body.length; ) {
1331
+ const first = classItem(body, index);
1332
+ if (!first)
1333
+ return;
1334
+ index += first.length;
1335
+ if (body[index] !== "-" || index + 1 >= body.length) {
1336
+ add(first);
1337
+ continue;
1338
+ }
1339
+ const second = classItem(body, index + 1);
1340
+ if (!second)
1341
+ return;
1342
+ index += 1 + second.length;
1343
+ if (first.unit !== undefined && second.unit !== undefined) {
1344
+ ranges.push([first.unit, second.unit]);
1345
+ continue;
1346
+ }
1347
+ add(first);
1348
+ add({ unit: 45 });
1349
+ add(second);
1350
+ }
1351
+ return { positive: merge(ranges), negated };
1352
+ }
1353
+ function caseFolded(positive) {
1354
+ const inside = (unit) => contains(positive, unit);
1355
+ const added = [];
1356
+ for (const unit of casedUnits()) {
1357
+ if (!inside(unit) && caseVariants(unit).some(inside))
1358
+ added.push([unit, unit]);
1359
+ }
1360
+ return added.length > 0 ? merge([...positive, ...added]) : positive;
1361
+ }
1362
+ function contains(ranges, unit) {
1363
+ let low = 0;
1364
+ let high = ranges.length - 1;
1365
+ while (low <= high) {
1366
+ const middle = low + high >> 1;
1367
+ const range = ranges[middle];
1368
+ if (!range)
1369
+ return false;
1370
+ if (unit < range[0])
1371
+ high = middle - 1;
1372
+ else if (unit > range[1])
1373
+ low = middle + 1;
1374
+ else
1375
+ return true;
1376
+ }
1377
+ return false;
1378
+ }
1379
+ function classRanges(source, ignoreCase = false) {
1380
+ const found = parsed(source);
1381
+ if (!found)
1382
+ return;
1383
+ const positive = ignoreCase ? caseFolded(found.positive) : found.positive;
1384
+ const ranges = found.negated ? complement(positive) : positive;
1385
+ const expression = new RegExp(source, ignoreCase ? "i" : "");
1386
+ const inside = (unit) => contains(ranges, unit);
1387
+ for (const [low, high] of ranges.length > 0 ? ranges : [[0, MAX_UNIT2]]) {
1388
+ for (const unit of [low - 1, low, low + high >> 1, high, high + 1]) {
1389
+ if (unit < 0 || unit > MAX_UNIT2)
1390
+ continue;
1391
+ if (expression.test(String.fromCharCode(unit)) !== inside(unit))
1392
+ return;
1393
+ }
1394
+ }
1395
+ return ranges;
1396
+ }
1397
+
1398
+ // src/agent-runtime/coding-search-regex-syntax.ts
1399
+ class UnsupportedRegexSyntax extends Error {
1400
+ }
1401
+ var MAX_REGEX_REPEAT = 1000;
1402
+ var QUANTIFIER = /^\{(\d+)(?:(,)(\d*))?\}/;
1403
+ var MODIFIERS = /^\(\?([ims]*)(?:-([ims]*))?:/;
1404
+ var CONTROL_ESCAPES2 = { t: 9, n: 10, v: 11, f: 12, r: 13 };
1405
+ function escapedUnit(source) {
1406
+ const name = source.charAt(1);
1407
+ if ("dDwWsS".includes(name))
1408
+ return;
1409
+ if (name in CONTROL_ESCAPES2)
1410
+ return CONTROL_ESCAPES2[name];
1411
+ if (source.length > 2 && (name === "x" || name === "u"))
1412
+ return Number.parseInt(source.slice(2), 16);
1413
+ if (name === "0")
1414
+ return Number.parseInt(source.slice(1), 8);
1415
+ if (name === "c")
1416
+ return source.charCodeAt(2) % 32;
1417
+ return source.charCodeAt(1);
1418
+ }
1419
+ function parseSearchRegex(pattern, check) {
1420
+ let index = 0;
1421
+ let modifiers = { ignoreCase: false, multiline: false, dotAll: false };
1422
+ const atoms = new Map;
1423
+ const unit = (key, create) => {
1424
+ let atom = atoms.get(key);
1425
+ if (!atom) {
1426
+ atom = { key, ...create() };
1427
+ atoms.set(key, atom);
1428
+ }
1429
+ return { kind: "unit", atom };
1430
+ };
1431
+ const native = (source, ranges) => {
1432
+ const flags = `${modifiers.ignoreCase ? "i" : ""}${modifiers.dotAll ? "s" : ""}`;
1433
+ return unit(`${flags}/${source}`, () => {
1434
+ const expression = new RegExp(source, flags);
1435
+ return {
1436
+ ...ranges && { ranges },
1437
+ matches: (code) => expression.test(String.fromCharCode(code))
1438
+ };
1439
+ });
1440
+ };
1441
+ const literal = (code) => {
1442
+ const units = modifiers.ignoreCase ? caseVariants(code) : [code];
1443
+ return unit(`=${units.join(",")}`, () => ({
1444
+ ranges: units.map((value) => [value, value]),
1445
+ matches: (other) => units.includes(other)
1446
+ }));
1447
+ };
1448
+ const namedGroups = /\(\?<(?![=!])/.test(pattern);
1449
+ const classEnd = (start) => {
1450
+ let cursor = start + 1;
1451
+ if (pattern[cursor] === "^")
1452
+ cursor += 1;
1453
+ while (cursor < pattern.length && pattern[cursor] !== "]") {
1454
+ cursor += pattern[cursor] === "\\" ? 2 : 1;
1455
+ }
1456
+ return cursor + 1;
1457
+ };
1458
+ const escapeLength = (start) => {
1459
+ const next = pattern[start + 1] ?? "";
1460
+ if (/[1-9]/.test(next) || next === "k" && namedGroups) {
1461
+ throw new UnsupportedRegexSyntax("backreference");
1462
+ }
1463
+ if (next === "0") {
1464
+ const digits = /^[0-7]{0,2}/.exec(pattern.slice(start + 2))?.[0] ?? "";
1465
+ return 2 + digits.length;
1466
+ }
1467
+ if (next === "c")
1468
+ return /[A-Za-z]/.test(pattern[start + 2] ?? "") ? 3 : 0;
1469
+ if (next === "x" && /^[0-9A-Fa-f]{2}$/.test(pattern.slice(start + 2, start + 4)))
1470
+ return 4;
1471
+ if (next === "u" && /^[0-9A-Fa-f]{4}$/.test(pattern.slice(start + 2, start + 6)))
1472
+ return 6;
1473
+ return 2;
1474
+ };
1475
+ const group = () => {
1476
+ if (/^\(\?(?:=|!|<=|<!)/.test(pattern.slice(index))) {
1477
+ throw new UnsupportedRegexSyntax("lookaround");
1478
+ }
1479
+ const outer = modifiers;
1480
+ const modified = MODIFIERS.exec(pattern.slice(index));
1481
+ if (modified) {
1482
+ const on = modified[1] ?? "";
1483
+ const off = modified[2] ?? "";
1484
+ const flag = (name, current) => on.includes(name) ? true : off.includes(name) ? false : current;
1485
+ modifiers = {
1486
+ ignoreCase: flag("i", outer.ignoreCase),
1487
+ multiline: flag("m", outer.multiline),
1488
+ dotAll: flag("s", outer.dotAll)
1489
+ };
1490
+ index += modified[0].length;
1491
+ } else if (pattern.startsWith("(?<", index))
1492
+ index = pattern.indexOf(">", index) + 1;
1493
+ else
1494
+ index += 1;
1495
+ const body = alternation();
1496
+ modifiers = outer;
1497
+ index += 1;
1498
+ return body;
1499
+ };
1500
+ const assertion = (character) => {
1501
+ index += 1;
1502
+ if (character === "^")
1503
+ return { kind: "assert", at: modifiers.multiline ? "line-start" : "start" };
1504
+ return { kind: "assert", at: modifiers.multiline ? "line-end" : "end" };
1505
+ };
1506
+ let atomsRead = 0;
1507
+ const atom = () => {
1508
+ if (++atomsRead % 16 === 0)
1509
+ check();
1510
+ const character = pattern[index] ?? "";
1511
+ if (character === "(")
1512
+ return group();
1513
+ if (character === "^" || character === "$")
1514
+ return assertion(character);
1515
+ if (character === "\\") {
1516
+ const next = pattern[index + 1];
1517
+ if (next === "b" || next === "B") {
1518
+ index += 2;
1519
+ return { kind: "assert", at: next === "b" ? "word" : "not-word" };
1520
+ }
1521
+ const length = escapeLength(index);
1522
+ if (length === 0) {
1523
+ index += 1;
1524
+ return literal(92);
1525
+ }
1526
+ const source = pattern.slice(index, index + length);
1527
+ index += length;
1528
+ const code = escapedUnit(source);
1529
+ if (code !== undefined && new RegExp(`^${source}$`).test(String.fromCharCode(code))) {
1530
+ return literal(code);
1531
+ }
1532
+ return native(source, classRanges(`[${source}]`, modifiers.ignoreCase));
1533
+ }
1534
+ if (character === "[") {
1535
+ const end = classEnd(index);
1536
+ const source = pattern.slice(index, end);
1537
+ index = end;
1538
+ return native(source, classRanges(source, modifiers.ignoreCase));
1539
+ }
1540
+ index += 1;
1541
+ if (character === ".") {
1542
+ return native(".", classRanges(modifiers.dotAll ? "[\\s\\S]" : "[^\\n\\r\\u2028\\u2029]"));
1543
+ }
1544
+ return literal(character.charCodeAt(0));
1545
+ };
1546
+ const quantified = (body) => {
1547
+ const character = pattern[index];
1548
+ let min;
1549
+ let max;
1550
+ if (character === "*" || character === "+" || character === "?") {
1551
+ min = character === "+" ? 1 : 0;
1552
+ max = character === "?" ? 1 : Number.POSITIVE_INFINITY;
1553
+ index += 1;
1554
+ } else {
1555
+ const counted = character === "{" ? QUANTIFIER.exec(pattern.slice(index)) : null;
1556
+ if (!counted)
1557
+ return body;
1558
+ min = Number(counted[1]);
1559
+ max = counted[2] === undefined ? min : counted[3] ? Number(counted[3]) : Number.POSITIVE_INFINITY;
1560
+ if (min > MAX_REGEX_REPEAT || Number.isFinite(max) && max > MAX_REGEX_REPEAT) {
1561
+ throw new UnsupportedRegexSyntax(`repetition beyond ${MAX_REGEX_REPEAT}`);
1562
+ }
1563
+ index += counted[0].length;
1564
+ }
1565
+ if (pattern[index] === "?")
1566
+ index += 1;
1567
+ return { kind: "repeat", body, min, max };
1568
+ };
1569
+ const sequence = () => {
1570
+ const items = [];
1571
+ while (index < pattern.length && pattern[index] !== "|" && pattern[index] !== ")") {
1572
+ items.push(quantified(atom()));
1573
+ }
1574
+ if (items.length === 0)
1575
+ return { kind: "empty" };
1576
+ return items.length === 1 && items[0] ? items[0] : { kind: "sequence", items };
1577
+ };
1578
+ function alternation() {
1579
+ const options = [sequence()];
1580
+ while (pattern[index] === "|") {
1581
+ index += 1;
1582
+ options.push(sequence());
1583
+ }
1584
+ return options.length === 1 && options[0] ? options[0] : { kind: "alternation", options };
1585
+ }
1586
+ return alternation();
1587
+ }
1588
+
1589
+ // src/agent-runtime/coding-search-unit-classes.ts
1590
+ var Ahead = { End: 0, Word: 1, LineTerminator: 2, Other: 3 };
1591
+ var isWord = (unit) => unit >= 48 && unit <= 57 || unit >= 65 && unit <= 90 || unit >= 97 && unit <= 122 || unit === 95;
1592
+ var isLineTerminator = (unit) => unit === 10 || unit === 13 || unit === 8232 || unit === 8233;
1593
+ var classify = (unit) => isWord(unit) ? Ahead.Word : isLineTerminator(unit) ? Ahead.LineTerminator : Ahead.Other;
1594
+ var MAX_RANGE_MEMBERSHIPS = 1 << 22;
1595
+ var MAX_NATIVE_ATOMS = 64;
1596
+ var UNITS = 65536;
1597
+ function contains2(sorted, value) {
1598
+ let low = 0;
1599
+ let high = sorted.length - 1;
1600
+ while (low <= high) {
1601
+ const middle = low + high >> 1;
1602
+ const found = sorted[middle] ?? 0;
1603
+ if (found === value)
1604
+ return true;
1605
+ if (found < value)
1606
+ low = middle + 1;
1607
+ else
1608
+ high = middle - 1;
1609
+ }
1610
+ return false;
1611
+ }
1612
+ function partition(ranged, check) {
1613
+ const boundaries = new Set([0, UNITS]);
1614
+ for (const atom of ranged) {
1615
+ for (const [low, high] of atom.ranges ?? []) {
1616
+ boundaries.add(low);
1617
+ boundaries.add(high + 1);
1618
+ }
1619
+ }
1620
+ const points = [...boundaries].sort((left, right) => left - right);
1621
+ const segmentOf = new Int32Array(UNITS);
1622
+ const starts = new Map;
1623
+ const ends = new Map;
1624
+ for (const [index, atom] of ranged.entries()) {
1625
+ for (const [low, high] of atom.ranges ?? []) {
1626
+ const opening = starts.get(low) ?? [];
1627
+ opening.push(index);
1628
+ starts.set(low, opening);
1629
+ const closing = ends.get(high + 1) ?? [];
1630
+ closing.push(index);
1631
+ ends.set(high + 1, closing);
1632
+ }
1633
+ }
1634
+ const active = new Set;
1635
+ const members = [];
1636
+ let memberships = 0;
1637
+ for (let segment = 0;segment + 1 < points.length; segment += 1) {
1638
+ const from = points[segment] ?? 0;
1639
+ const to = points[segment + 1] ?? UNITS;
1640
+ for (const index of ends.get(from) ?? [])
1641
+ active.delete(index);
1642
+ for (const index of starts.get(from) ?? [])
1643
+ active.add(index);
1644
+ memberships += active.size;
1645
+ if (memberships > MAX_RANGE_MEMBERSHIPS) {
1646
+ throw new UnsupportedRegexSyntax("character sets that overlap too much");
1647
+ }
1648
+ if (segment % 128 === 0)
1649
+ check();
1650
+ members.push(Int32Array.from(active).sort());
1651
+ segmentOf.fill(segment, from, to);
1652
+ }
1653
+ return { segmentOf, members };
1654
+ }
1655
+ function unitClasses(atoms, check) {
1656
+ const ranged = [];
1657
+ const natives = [];
1658
+ const indexOf = new Map;
1659
+ for (const atom of atoms) {
1660
+ if (indexOf.has(atom))
1661
+ continue;
1662
+ const list = atom.ranges ? ranged : natives;
1663
+ indexOf.set(atom, { ranged: list === ranged, index: list.push(atom) - 1 });
1664
+ }
1665
+ if (natives.length > MAX_NATIVE_ATOMS) {
1666
+ throw new UnsupportedRegexSyntax(`more than ${MAX_NATIVE_ATOMS} character classes the engine must evaluate unit by unit`);
1667
+ }
1668
+ const { segmentOf, members } = partition(ranged, check);
1669
+ const words = Math.ceil(natives.length / 32);
1670
+ const classOfUnit = new Int32Array(UNITS).fill(-1);
1671
+ const known = new Map;
1672
+ const classes = [];
1673
+ let evaluations = 0;
1674
+ const classOf = (unit) => {
1675
+ const cached = classOfUnit[unit] ?? -1;
1676
+ if (cached >= 0)
1677
+ return cached;
1678
+ const kind = classify(unit);
1679
+ const segment = segmentOf[unit] ?? 0;
1680
+ const native = new Uint32Array(words);
1681
+ for (const [index, atom] of natives.entries()) {
1682
+ if (++evaluations % 128 === 0)
1683
+ check();
1684
+ if (atom.matches(unit))
1685
+ native[index >> 5] = (native[index >> 5] ?? 0) | 1 << (index & 31);
1686
+ }
1687
+ const key = `${kind}:${segment}:${native.join(",")}`;
1688
+ let found = known.get(key);
1689
+ if (found === undefined) {
1690
+ found = classes.push({ kind, ranged: members[segment] ?? new Int32Array, native }) - 1;
1691
+ known.set(key, found);
1692
+ }
1693
+ classOfUnit[unit] = found;
1694
+ return found;
1695
+ };
1696
+ const nativeBit = (found, index) => ((found.native[index >> 5] ?? 0) & 1 << (index & 31)) !== 0;
1697
+ return {
1698
+ classOf,
1699
+ kindOf: (unitClass) => classes[unitClass]?.kind ?? Ahead.Other,
1700
+ atomsOf: (unitClass) => {
1701
+ const found = classes[unitClass];
1702
+ if (!found)
1703
+ return [];
1704
+ found.atoms ??= [
1705
+ ...Array.from(found.ranged, (index) => ranged[index]),
1706
+ ...natives.filter((_, index) => nativeBit(found, index))
1707
+ ].filter((atom) => atom !== undefined);
1708
+ return found.atoms;
1709
+ },
1710
+ matches: (atom, unitClass) => {
1711
+ const place = indexOf.get(atom);
1712
+ const found = classes[unitClass];
1713
+ if (!place || !found)
1714
+ return false;
1715
+ return place.ranged ? contains2(found.ranged, place.index) : nativeBit(found, place.index);
1716
+ }
1717
+ };
1718
+ }
1719
+
1720
+ // src/agent-runtime/coding-search-automaton.ts
1721
+ var MAX_REGEX_STATES = 16384;
1722
+ var MAX_BUILD_STEPS = 4 * MAX_REGEX_STATES;
1723
+ var MAX_CACHED_STATES = 4096;
1724
+ var MAX_CACHED_TRANSITIONS = 65536;
1725
+ function buildAutomaton(tree, check) {
1726
+ const states = [{ kind: "accept" }];
1727
+ let steps = 0;
1728
+ const add = (state) => {
1729
+ if (states.length >= MAX_REGEX_STATES) {
1730
+ throw new UnsupportedRegexSyntax(`more than ${MAX_REGEX_STATES} matcher states`);
1731
+ }
1732
+ states.push(state);
1733
+ return states.length - 1;
1734
+ };
1735
+ const build = (node, next) => {
1736
+ if (++steps > MAX_BUILD_STEPS) {
1737
+ throw new UnsupportedRegexSyntax(`more than ${MAX_BUILD_STEPS} construction steps`);
1738
+ }
1739
+ if (steps % 128 === 0)
1740
+ check();
1741
+ switch (node.kind) {
1742
+ case "empty":
1743
+ return next;
1744
+ case "unit":
1745
+ return add({ kind: "unit", atom: node.atom, next });
1746
+ case "assert":
1747
+ return add({ kind: "assert", at: node.at, next });
1748
+ case "sequence":
1749
+ return node.items.reduceRight((after, item) => build(item, after), next);
1750
+ case "alternation":
1751
+ return add({ kind: "split", next: node.options.map((option) => build(option, next)) });
1752
+ case "repeat": {
1753
+ let tail = next;
1754
+ if (node.max === Number.POSITIVE_INFINITY) {
1755
+ const loop = { kind: "split", next: [] };
1756
+ tail = add(loop);
1757
+ loop.next.push(build(node.body, tail), next);
1758
+ } else {
1759
+ for (let optional = node.min;optional < node.max; optional += 1) {
1760
+ tail = add({ kind: "split", next: [build(node.body, tail), next] });
1761
+ }
1762
+ }
1763
+ for (let required = 0;required < node.min; required += 1)
1764
+ tail = build(node.body, tail);
1765
+ return tail;
1766
+ }
1767
+ }
1768
+ };
1769
+ return { states, start: build(tree, 0) };
1770
+ }
1771
+ var holds = (at, atStart, behind, ahead) => {
1772
+ switch (at) {
1773
+ case "start":
1774
+ return atStart;
1775
+ case "end":
1776
+ return ahead === Ahead.End;
1777
+ case "line-start":
1778
+ return atStart || behind === Ahead.LineTerminator;
1779
+ case "line-end":
1780
+ return ahead === Ahead.End || ahead === Ahead.LineTerminator;
1781
+ case "word":
1782
+ return behind === Ahead.Word !== (ahead === Ahead.Word);
1783
+ case "not-word":
1784
+ return behind === Ahead.Word === (ahead === Ahead.Word);
1785
+ }
1786
+ };
1787
+ var KINDS = [Ahead.End, Ahead.Word, Ahead.LineTerminator, Ahead.Other];
1788
+ var contextOf = (atStart, behind, ahead) => (atStart ? 16 : 0) + behind * 4 + ahead;
1789
+ function lazyMatcher({ states, start }, check) {
1790
+ let interned = new Map;
1791
+ const initial = new Map;
1792
+ const starts = new Map;
1793
+ const startsByUnits = new Map;
1794
+ let startKernels = new Map;
1795
+ let transitions = 0;
1796
+ let operations = 0;
1797
+ let flushes = 0;
1798
+ let simulating = false;
1799
+ const flush = () => {
1800
+ interned = new Map;
1801
+ initial.clear();
1802
+ startKernels = new Map;
1803
+ transitions = 0;
1804
+ if (++flushes >= 2)
1805
+ simulating = true;
1806
+ };
1807
+ const classes = unitClasses(states.flatMap((state) => state.kind === "unit" ? [state.atom] : []), check);
1808
+ const seen = new Int32Array(states.length);
1809
+ let generation = 0;
1810
+ const closure = (kernel, context) => {
1811
+ const atStart = context >= 16;
1812
+ const behind = KINDS[context >> 2 & 3] ?? Ahead.Other;
1813
+ const ahead = KINDS[context & 3] ?? Ahead.Other;
1814
+ generation += 1;
1815
+ const units = [];
1816
+ let accepts = false;
1817
+ const pending = [...kernel];
1818
+ for (let index = pending.pop();index !== undefined; index = pending.pop()) {
1819
+ if (seen[index] === generation)
1820
+ continue;
1821
+ seen[index] = generation;
1822
+ if (++operations % 128 === 0)
1823
+ check();
1824
+ const state = states[index];
1825
+ if (state?.kind === "unit")
1826
+ units.push(index);
1827
+ else if (state?.kind === "accept")
1828
+ accepts = true;
1829
+ else if (state?.kind === "split")
1830
+ pending.push(...state.next);
1831
+ else if (state && holds(state.at, atStart, behind, ahead))
1832
+ pending.push(state.next);
1833
+ }
1834
+ return { units, accepts };
1835
+ };
1836
+ const sorted = (units) => units.sort((left, right) => left - right);
1837
+ const startOf = (context) => {
1838
+ let found = starts.get(context);
1839
+ if (!found) {
1840
+ const { units, accepts } = closure([start], context);
1841
+ sorted(units);
1842
+ const key = `${accepts ? 1 : 0}:${units.join(",")}`;
1843
+ found = startsByUnits.get(key);
1844
+ if (!found) {
1845
+ const byAtom = new Map;
1846
+ for (const index of units) {
1847
+ const state = states[index];
1848
+ if (state?.kind !== "unit")
1849
+ continue;
1850
+ const next = byAtom.get(state.atom) ?? [];
1851
+ next.push(state.next);
1852
+ byAtom.set(state.atom, next);
1853
+ }
1854
+ found = { id: startsByUnits.size, units, accepts, byAtom };
1855
+ startsByUnits.set(key, found);
1856
+ }
1857
+ starts.set(context, found);
1858
+ }
1859
+ return found;
1860
+ };
1861
+ const enter = (kernel, context) => {
1862
+ const own = closure(kernel, context);
1863
+ sorted(own.units);
1864
+ const opening = startOf(context);
1865
+ const accepts = own.accepts || opening.accepts;
1866
+ const key = `${opening.id}:${accepts ? 1 : 0}:${own.units.join(",")}`;
1867
+ let found = interned.get(key);
1868
+ if (!found) {
1869
+ if (interned.size >= MAX_CACHED_STATES)
1870
+ flush();
1871
+ found = { start: opening, own: own.units, accepts, next: new Map };
1872
+ interned.set(key, found);
1873
+ }
1874
+ return found;
1875
+ };
1876
+ const begin = (ahead) => {
1877
+ let found = initial.get(ahead);
1878
+ if (!found) {
1879
+ found = enter([], contextOf(true, Ahead.End, ahead));
1880
+ initial.set(ahead, found);
1881
+ }
1882
+ return found;
1883
+ };
1884
+ const startKernel = (opening, unitClass) => {
1885
+ let byClass = startKernels.get(opening.id);
1886
+ if (!byClass) {
1887
+ byClass = new Map;
1888
+ startKernels.set(opening.id, byClass);
1889
+ }
1890
+ let found = byClass.get(unitClass);
1891
+ if (!found) {
1892
+ const matching = classes.atomsOf(unitClass);
1893
+ found = [];
1894
+ if (matching.length < opening.byAtom.size) {
1895
+ for (const atom of matching)
1896
+ found.push(...opening.byAtom.get(atom) ?? []);
1897
+ } else {
1898
+ for (const [atom, next] of opening.byAtom) {
1899
+ if (++operations % 128 === 0)
1900
+ check();
1901
+ if (classes.matches(atom, unitClass))
1902
+ found.push(...next);
1903
+ }
1904
+ }
1905
+ byClass.set(unitClass, found);
1906
+ transitions += 1;
1907
+ }
1908
+ return found;
1909
+ };
1910
+ const advance = (opening, own, unitClass) => {
1911
+ const kernel = [...startKernel(opening, unitClass)];
1912
+ for (const index of own) {
1913
+ const state = states[index];
1914
+ if (state?.kind === "unit" && classes.matches(state.atom, unitClass))
1915
+ kernel.push(state.next);
1916
+ }
1917
+ return kernel;
1918
+ };
1919
+ const step = (from, unitClass, ahead) => {
1920
+ const key = unitClass * 4 + ahead;
1921
+ const kernel = advance(from.start, from.own, unitClass);
1922
+ const reached = enter(kernel, contextOf(false, classes.kindOf(unitClass), ahead));
1923
+ if (++transitions > MAX_CACHED_TRANSITIONS)
1924
+ flush();
1925
+ from.next.set(key, reached);
1926
+ return reached;
1927
+ };
1928
+ const { classOf } = classes;
1929
+ const simulate = (text) => {
1930
+ const length = text.length;
1931
+ let context = contextOf(true, Ahead.End, length === 0 ? Ahead.End : classify(text.charCodeAt(0)));
1932
+ let own = [];
1933
+ let accepts = startOf(context).accepts;
1934
+ for (let position = 0;; position += 1) {
1935
+ if (accepts)
1936
+ return true;
1937
+ if (position === length)
1938
+ return false;
1939
+ if ((position & 4095) === 4095)
1940
+ check();
1941
+ const after = position + 1;
1942
+ const ahead = after === length ? Ahead.End : classify(text.charCodeAt(after));
1943
+ const unitClass = classOf(text.charCodeAt(position));
1944
+ const kernel = advance(startOf(context), own, unitClass);
1945
+ context = contextOf(false, classes.kindOf(unitClass), ahead);
1946
+ const reached = closure(kernel, context);
1947
+ own = reached.units;
1948
+ accepts = reached.accepts || startOf(context).accepts;
1949
+ }
1950
+ };
1951
+ return {
1952
+ test(text) {
1953
+ if (simulating)
1954
+ return simulate(text);
1955
+ const length = text.length;
1956
+ let current = begin(length === 0 ? Ahead.End : classify(text.charCodeAt(0)));
1957
+ for (let position = 0;; position += 1) {
1958
+ if (current.accepts)
1959
+ return true;
1960
+ if (position === length)
1961
+ return false;
1962
+ if ((position & 4095) === 4095)
1963
+ check();
1964
+ const after = position + 1;
1965
+ const ahead = after === length ? Ahead.End : classify(text.charCodeAt(after));
1966
+ const unitClass = classOf(text.charCodeAt(position));
1967
+ current = current.next.get(unitClass * 4 + ahead) ?? step(current, unitClass, ahead);
1968
+ }
1969
+ }
1970
+ };
1971
+ }
1972
+
1973
+ // src/agent-runtime/coding-search-matcher.ts
1974
+ var MAX_NATIVE_LINE_WORK = 2 ** 24;
1975
+ var LONGEST_LINE = 8192;
1976
+ function disjointUnits(options) {
1977
+ const ranges = [];
1978
+ for (const option of options) {
1979
+ if (option.kind !== "unit" || !option.atom.ranges)
1980
+ return false;
1981
+ ranges.push(...option.atom.ranges);
1982
+ }
1983
+ ranges.sort((left, right) => left[0] - right[0]);
1984
+ return ranges.every((range, index) => index === 0 || range[0] > (ranges[index - 1]?.[1] ?? -1));
1985
+ }
1986
+ function matchesEmpty(node) {
1987
+ switch (node.kind) {
1988
+ case "empty":
1989
+ case "assert":
1990
+ return true;
1991
+ case "unit":
1992
+ return false;
1993
+ case "repeat":
1994
+ return node.min === 0 || matchesEmpty(node.body);
1995
+ case "sequence":
1996
+ return node.items.every(matchesEmpty);
1997
+ case "alternation":
1998
+ return node.options.some(matchesEmpty);
1999
+ }
2000
+ }
2001
+ function backtrackingWork(node, units) {
2002
+ switch (node.kind) {
2003
+ case "empty":
2004
+ return { paths: 1, length: 0 };
2005
+ case "unit":
2006
+ case "assert":
2007
+ return { paths: 1, length: 1 };
2008
+ case "repeat": {
2009
+ const body = backtrackingWork(node.body, units);
2010
+ const empty = matchesEmpty(node.body);
2011
+ const min = empty ? node.min : Math.min(node.min, units);
2012
+ const max = Math.min(node.max, empty ? node.min + units : units);
2013
+ let paths = 1;
2014
+ let optional = 1;
2015
+ for (let count = 0;count < max && paths * optional <= MAX_NATIVE_LINE_WORK; count += 1) {
2016
+ if (count < min)
2017
+ paths *= body.paths;
2018
+ else
2019
+ optional = 1 + body.paths * optional;
2020
+ }
2021
+ return { paths: paths * optional, length: max * Math.max(body.length, 1) };
2022
+ }
2023
+ case "sequence":
2024
+ return node.items.reduce((total, item) => {
2025
+ const part = backtrackingWork(item, units);
2026
+ return { paths: total.paths * part.paths, length: total.length + part.length };
2027
+ }, { paths: 1, length: 0 });
2028
+ case "alternation":
2029
+ if (disjointUnits(node.options))
2030
+ return { paths: 1, length: node.options.length };
2031
+ return node.options.reduce((total, option) => {
2032
+ const part = backtrackingWork(option, units);
2033
+ return {
2034
+ paths: total.paths + part.paths,
2035
+ length: Math.max(total.length, part.length)
2036
+ };
2037
+ }, { paths: 0, length: 0 });
2038
+ }
2039
+ }
2040
+ function groupCount(pattern) {
2041
+ let groups = 0;
2042
+ let inClass = false;
2043
+ for (let index = 0;index < pattern.length; index += 1) {
2044
+ const character = pattern[index];
2045
+ if (character === "\\")
2046
+ index += 1;
2047
+ else if (inClass)
2048
+ inClass = character !== "]";
2049
+ else if (character === "[")
2050
+ inClass = true;
2051
+ else if (character === "(")
2052
+ groups += 1;
2053
+ }
2054
+ return groups;
2055
+ }
2056
+ function validated(pattern) {
2057
+ try {
2058
+ return new RegExp(pattern);
2059
+ } catch (error) {
2060
+ codingRefusal("BAD_REQUEST", "This pattern is not a valid regular expression", {
2061
+ details: { pattern, reason: error instanceof Error ? error.message : String(error) },
2062
+ hint: "Fix the expression, or use regex: false.",
2063
+ cause: error
2064
+ });
2065
+ }
2066
+ }
2067
+ function refuseUnsupported(error) {
2068
+ if (!(error instanceof UnsupportedRegexSyntax))
2069
+ throw error;
2070
+ codingRefusal("BAD_REQUEST", `Search regex cannot use ${error.message}`, {
2071
+ hint: "Groups, alternation, classes, modifiers and quantifiers are supported; backreferences and lookaround are not. Rewrite the pattern or use regex: false.",
2072
+ cause: error
2073
+ });
2074
+ }
2075
+ function compileSearchRegex(pattern, check) {
2076
+ const expression = validated(pattern);
2077
+ try {
2078
+ const tree = parseSearchRegex(pattern, check);
2079
+ const nativeUpTo = new Map;
2080
+ const groups = groupCount(pattern);
2081
+ const native = (length) => {
2082
+ let bucket = 16;
2083
+ while (bucket < length)
2084
+ bucket *= 2;
2085
+ let found = nativeUpTo.get(bucket);
2086
+ if (found === undefined) {
2087
+ const work = backtrackingWork(tree, bucket);
2088
+ found = work.paths * Math.max(work.length, 1) * bucket * (groups + 1) <= MAX_NATIVE_LINE_WORK;
2089
+ nativeUpTo.set(bucket, found);
2090
+ }
2091
+ return found;
2092
+ };
2093
+ let automaton = native(LONGEST_LINE) ? undefined : lazyMatcher(buildAutomaton(tree, check), check);
2094
+ return {
2095
+ test(text) {
2096
+ if (native(text.length)) {
2097
+ check();
2098
+ return expression.test(text);
2099
+ }
2100
+ automaton ??= lazyMatcher(buildAutomaton(tree, check), check);
2101
+ return automaton.test(text);
2102
+ }
2103
+ };
2104
+ } catch (error) {
2105
+ refuseUnsupported(error);
2106
+ }
2107
+ }
2108
+
2109
+ // src/agent-runtime/coding-tool-search.ts
2110
+ var jsonBytes = (value) => Buffer.byteLength(JSON.stringify(value));
2111
+ var YIELD_EVERY_MS = 16;
2112
+ function collectMatches(input, limits, budget, output, availableBytes) {
2113
+ const matcher = input.regex ? compileSearchRegex(input.query, () => budget.check()) : null;
2114
+ let usedBytes = 0;
2115
+ const hit = (text) => {
2116
+ budget.check();
2117
+ if (Buffer.byteLength(text) > 8192) {
2118
+ budget.reasons.add("max-line-bytes");
2119
+ return false;
2120
+ }
2121
+ return matcher ? matcher.test(text) : text.includes(input.query);
2122
+ };
2123
+ const append = (match) => {
2124
+ const bytes = jsonBytes(match) + 1;
2125
+ if (usedBytes + bytes > availableBytes)
2126
+ budget.stop("max-output-bytes");
2127
+ output.matches.push(match);
2128
+ usedBytes += bytes;
2129
+ if (output.matches.length >= limits.maxSearchResults)
2130
+ budget.stop("max-results");
2131
+ };
2132
+ let since = performance.now();
2133
+ const yieldBetweenLines = async () => {
2134
+ if (performance.now() - since < YIELD_EVERY_MS)
2135
+ return;
2136
+ await new Promise((resolve) => setImmediate(resolve));
2137
+ since = performance.now();
2138
+ budget.check();
2139
+ };
2140
+ return async (file) => {
2141
+ if (input.mode === "path") {
2142
+ if (hit(file.relative))
2143
+ append({ path: file.relative });
2144
+ return true;
2145
+ }
2146
+ if (file.content === undefined)
2147
+ return true;
2148
+ const lines = file.content.text.split(`
2149
+ `);
2150
+ for (let index = 0;index < lines.length; index += 1) {
2151
+ await yieldBetweenLines();
2152
+ const line = lines[index];
2153
+ if (line === undefined || !hit(line))
2154
+ continue;
2155
+ append({
2156
+ path: file.relative,
2157
+ line: index + 1,
2158
+ text: line,
2159
+ ...input.context > 0 && {
2160
+ before: lines.slice(Math.max(0, index - input.context), index),
2161
+ after: lines.slice(index + 1, index + 1 + input.context)
2162
+ }
2163
+ });
2164
+ }
2165
+ return true;
2166
+ };
2167
+ }
2168
+ async function searchFiles(config, limits, input, caller) {
2169
+ const started = performance.now();
2170
+ const budget = new ContainedScanBudget({
2171
+ maxDirectories: limits.maxSearchDirectories,
2172
+ maxEntries: limits.maxSearchEntries,
2173
+ maxBytes: limits.maxSearchBytes,
2174
+ deadline: started + limits.searchTimeoutMs
2175
+ });
2176
+ const output = {
2177
+ matches: [],
2178
+ truncated: false,
2179
+ scannedFiles: 0,
2180
+ skippedDirectories: 0,
2181
+ skippedSymlinks: 0,
2182
+ coverage: {
2183
+ complete: true,
2184
+ reasons: [],
2185
+ scope: {
2186
+ path: ".",
2187
+ include: input.include ?? "**",
2188
+ excludedDirectories: config.search?.excludeDirectories ? "configured" : "default",
2189
+ symlinks: "skip"
2190
+ },
2191
+ visitedDirectories: 0,
2192
+ visitedEntries: 0,
2193
+ readBytes: 0,
2194
+ elapsedMs: 0
2195
+ }
2196
+ };
2197
+ const minimumBytes = jsonBytes(output);
2198
+ if (minimumBytes + 512 > limits.maxSearchOutputBytes) {
2199
+ codingRefusal("BAD_REQUEST", "The search output budget cannot hold its coverage envelope", {
2200
+ hint: "Increase maxSearchOutputBytes or shorten include."
2201
+ });
2202
+ }
2203
+ const visitFile = collectMatches(input, limits, budget, output, limits.maxSearchOutputBytes - minimumBytes - 512);
2204
+ const excluded = new Set(config.search?.excludeDirectories ?? DEFAULT_EXCLUDED_DIRECTORIES);
2205
+ let rejectedByInclude = 0;
2206
+ let prunedByInclude = 0;
2207
+ if (caller?.aborted)
2208
+ budget.reasons.add("cancelled");
2209
+ else {
2210
+ try {
2211
+ await withSignalDeadline(limits.searchTimeoutMs, caller, () => "search-timeout", async (signal) => {
2212
+ budget.limits.signal = signal;
2213
+ const included = input.include ? compileWorkspaceGlob(input.include, () => budget.check()) : undefined;
2214
+ budget.directory();
2215
+ const root = await budget.wait(() => openPinnedDirectory(config.root), (handle) => {
2216
+ handle.close().catch(() => {
2217
+ return;
2218
+ });
2219
+ });
2220
+ try {
2221
+ await budget.wait(() => authorizeCodingTool(config, {
2222
+ operation: "search",
2223
+ query: input.query,
2224
+ mode: input.mode
2225
+ }));
2226
+ await budget.wait(() => authorizeCodingPath(config, "."));
2227
+ const scan = await scanContainedFiles({
2228
+ root,
2229
+ maxDepth: limits.maxSearchDepth,
2230
+ maxFiles: limits.maxSearchFiles,
2231
+ symlinks: "skip",
2232
+ skipUnreadable: true,
2233
+ budget,
2234
+ visitFile,
2235
+ maxDiagnostics: limits.maxSearchResults,
2236
+ ...input.mode === "content" && { readMaxBytes: limits.maxReadBytes },
2237
+ excludeDirectory: (relative) => {
2238
+ if (relative.split(path3.sep).some((segment) => excluded.has(segment)))
2239
+ return true;
2240
+ if (included && !included.mayContain(relative)) {
2241
+ prunedByInclude += 1;
2242
+ return true;
2243
+ }
2244
+ return false;
2245
+ },
2246
+ includeFile: (candidate) => {
2247
+ if (included && !included.test(candidate)) {
2248
+ rejectedByInclude += 1;
2249
+ return false;
2250
+ }
2251
+ return true;
2252
+ },
2253
+ authorizePath: (candidate) => isCodingPathAuthorized(config, candidate)
2254
+ });
2255
+ output.scannedFiles = scan.scannedFiles;
2256
+ output.skippedDirectories = scan.skippedDirectories;
2257
+ output.skippedSymlinks = scan.skippedSymlinks;
2258
+ if (scan.denied.length > 0) {
2259
+ output.denied = scan.denied.slice(0, limits.maxSearchResults).map(({ relative, kind }) => ({ path: relative, kind }));
2260
+ output.deniedTruncated = scan.deniedTruncated || scan.denied.length > output.denied.length;
2261
+ }
2262
+ if (scan.errors.length > 0) {
2263
+ output.errors = scan.errors.slice(0, limits.maxSearchResults);
2264
+ output.errorsTruncated = scan.errorsTruncated || scan.errors.length > output.errors.length;
2265
+ }
2266
+ } finally {
2267
+ await root.close();
2268
+ }
2269
+ });
2270
+ } catch (error) {
2271
+ if (!(error instanceof ScanStopped))
2272
+ throw error;
2273
+ }
2274
+ }
2275
+ if (input.include && output.scannedFiles === 0 && (rejectedByInclude > 0 || prunedByInclude > 0) && budget.reasons.size === 0) {
2276
+ output.hint = "The anchored include rejected the reached files or pruned incompatible directories. `*` does not cross `/`; use `**/` for subdirectories.";
2277
+ }
2278
+ const refresh = () => {
2279
+ output.coverage.reasons = [...budget.reasons];
2280
+ output.coverage.complete = budget.reasons.size === 0;
2281
+ output.truncated = !output.coverage.complete;
2282
+ output.coverage.visitedDirectories = budget.visitedDirectories;
2283
+ output.coverage.visitedEntries = budget.visitedEntries;
2284
+ output.coverage.readBytes = budget.readBytes;
2285
+ output.coverage.elapsedMs = Math.round((performance.now() - started) * 100) / 100;
2286
+ };
2287
+ refresh();
2288
+ boundSearchDiagnostics(output, budget, limits.maxSearchOutputBytes);
2289
+ refresh();
2290
+ if (jsonBytes(output) > limits.maxSearchOutputBytes)
2291
+ throw new Error("Search coverage envelope exceeded its reserved budget");
2292
+ return output;
2293
+ }
2294
+ function createSearchCodingTool(config, limits) {
2295
+ return defineRuntimeTool2({
2296
+ name: "search_files",
2297
+ description: "Search bounded relative paths or UTF-8 file content under the workspace root; coverage explains incomplete results.",
2298
+ identity: { serviceName: "coding", action: "search", method: "POST" },
2299
+ input: SearchInputSchema,
2300
+ output: SearchOutputSchema,
2301
+ transports: ["AGENT"],
2302
+ handler: ({ input, signal }) => searchFiles(config, limits, input, signal)
2303
+ });
2304
+ }
2305
+
2306
+ // src/agent-runtime/coding-tool-search-patch.ts
2307
+ var EditInputSchema = z7.object({
2308
+ path: z7.string().min(1),
2309
+ oldText: z7.string().min(1),
2310
+ newText: z7.string(),
2311
+ replaceAll: z7.boolean().default(false),
2312
+ expectedSha256: z7.string().length(64).optional(),
2313
+ dryRun: z7.boolean().default(false)
2314
+ }).strict();
2315
+ var EditOutputSchema = z7.object({
2316
+ path: z7.string().min(1),
2317
+ baseSha256: z7.string().length(64),
2318
+ sha256: z7.string().length(64),
2319
+ replacements: z7.int().positive(),
2320
+ bytes: z7.int().nonnegative(),
2321
+ applied: z7.boolean()
2322
+ }).strict();
2323
+ function sha256(value) {
2324
+ return createHash2("sha256").update(value).digest("hex");
2325
+ }
2326
+ async function editFile(config, limits, input) {
2327
+ return withCodingRoot(config.root, async (root, rootPath) => {
2328
+ const relative = boundedCodingRelativePath(rootPath, input.path, limits.maxPathBytes);
2329
+ await authorizeCodingPathChain(config, relative, root);
2330
+ const parent = await openContainedParent(root, relative).catch((error) => refuseMissingCodingPath(error, relative));
2331
+ try {
2332
+ return await withCodingPathLock(root, relative, async () => {
2333
+ const readCurrent = async () => {
2334
+ const handle = await openContainedParentFile(parent).catch((error) => refuseMissingCodingPath(error, relative));
2335
+ return readContainedUtf8Handle(handle, limits.maxWriteBytes).finally(() => handle.close());
2336
+ };
2337
+ await authorizeCodingTool(config, { operation: "read", path: relative });
2338
+ const read = await readCurrent();
2339
+ const source = read.text;
2340
+ const baseSha256 = sha256(source);
2341
+ if (input.expectedSha256 !== undefined && baseSha256 !== input.expectedSha256) {
2342
+ conflict2("The file changed since it was read", {
2343
+ message: "The file changed since it was read",
2344
+ path: relative,
2345
+ expectedSha256: input.expectedSha256,
2346
+ actualSha256: baseSha256
2347
+ });
2348
+ }
2349
+ const count = textOccurrences(source, input.oldText);
2350
+ if (count === 0) {
2351
+ codingPathRefusal("NOT_FOUND", "oldText does not appear in this file", relative, {
2352
+ details: { occurrences: 0 },
2353
+ hint: "Read the file and copy the snippet exactly, including indentation."
2354
+ });
2355
+ }
2356
+ if (!input.replaceAll && count !== 1) {
2357
+ codingPathRefusal("CONFLICT", `oldText appears ${count} times, so the edit is ambiguous`, relative, {
2358
+ details: { occurrences: count },
2359
+ hint: "Lengthen the snippet until it is unique, or pass replaceAll: true."
2360
+ });
2361
+ }
2362
+ const replacements = input.replaceAll ? count : 1;
2363
+ const literalReplacement = () => input.newText;
2364
+ const changed = input.replaceAll ? source.replaceAll(input.oldText, literalReplacement) : source.replace(input.oldText, literalReplacement);
2365
+ const bytes = Buffer.byteLength(changed);
2366
+ const resultSha256 = sha256(changed);
2367
+ if (bytes > limits.maxWriteBytes) {
2368
+ codingPathRefusal("BAD_REQUEST", `The result would be ${bytes} bytes, over the ${limits.maxWriteBytes}-byte limit`, relative, {
2369
+ details: { bytes, maxWriteBytes: limits.maxWriteBytes },
2370
+ hint: "Make a smaller edit, or split the file."
2371
+ });
2372
+ }
2373
+ await authorizeCodingTool(config, {
2374
+ operation: "edit",
2375
+ path: relative,
2376
+ baseSha256,
2377
+ resultSha256,
2378
+ resultBytes: bytes,
2379
+ replacements,
2380
+ dryRun: input.dryRun
2381
+ });
2382
+ await assertContainedParentCurrent(root, relative, parent.handle).catch((error) => refuseWriteConflict(error, relative));
2383
+ if (!input.dryRun) {
2384
+ if (sha256((await readCurrent()).text) !== baseSha256) {
2385
+ conflict2("The file changed since it was read", {
2386
+ message: "The file changed since it was read",
2387
+ path: relative
2388
+ });
2389
+ }
2390
+ await writeContainedFile({
2391
+ parent,
2392
+ content: changed,
2393
+ replace: true,
2394
+ mode: read.mode
2395
+ }).catch((error) => refuseWriteConflict(error, relative));
2396
+ }
2397
+ return {
2398
+ path: relative,
2399
+ baseSha256,
2400
+ sha256: resultSha256,
2401
+ replacements,
2402
+ bytes,
2403
+ applied: !input.dryRun
2404
+ };
2405
+ });
2406
+ } finally {
2407
+ await parent.handle.close();
2408
+ }
2409
+ });
2410
+ }
2411
+ function createSearchAndPatchCodingTools(config, limits) {
2412
+ const edit = defineRuntimeTool2({
2413
+ name: "edit_file",
2414
+ description: "Replace an exact snippet in one workspace file.",
2415
+ identity: { serviceName: "coding", action: "edit-file", method: "POST" },
2416
+ input: EditInputSchema,
2417
+ output: EditOutputSchema,
2418
+ transports: ["AGENT"],
2419
+ handler: ({ input }) => editFile(config, limits, input)
2420
+ });
2421
+ return [createSearchCodingTool(config, limits), edit];
2422
+ }
2423
+
2424
+ // src/agent-runtime/coding-tool-shell.ts
2425
+ import { fstatSync, statSync } from "node:fs";
2426
+ import { realpath as realpath2, stat } from "node:fs/promises";
2427
+ import path4 from "node:path";
2428
+
2429
+ // src/agent-runtime/coding-shell-process.ts
2430
+ function preview(data, budget) {
2431
+ if (data.byteLength <= budget) {
2432
+ return { data, headBytes: data.byteLength, tailBytes: 0, omittedBytes: 0 };
2433
+ }
2434
+ const headEnd = utf8AlignedEnd(data, 0, Math.ceil(budget / 2));
2435
+ const tailStart = Math.max(headEnd, utf8AlignedStart(data, data.byteLength - Math.floor(budget / 2), data.byteLength));
2436
+ const tailBytes = data.byteLength - tailStart;
2437
+ return {
2438
+ data: Buffer.concat([data.subarray(0, headEnd), data.subarray(tailStart)]),
2439
+ headBytes: headEnd,
2440
+ tailBytes,
2441
+ omittedBytes: data.byteLength - headEnd - tailBytes
2442
+ };
2443
+ }
2444
+ function retainedPreview(data) {
2445
+ const end = utf8AlignedEnd(data, 0, data.byteLength);
2446
+ return {
2447
+ data: data.subarray(0, end),
2448
+ headBytes: end,
2449
+ tailBytes: 0,
2450
+ omittedBytes: data.byteLength - end
2451
+ };
2452
+ }
2453
+ async function runCodingShell(input) {
2454
+ const stdoutHeader = Buffer.from(`--- stdout ---
2455
+ `);
2456
+ const stderrHeader = Buffer.from(`
2457
+ --- stderr ---
2458
+ `);
2459
+ const artifactPayloadLimit = input.maxArtifactBytes - stdoutHeader.byteLength - stderrHeader.byteLength;
2460
+ if (input.artifacts && artifactPayloadLimit < 0)
2461
+ throw new Error("maxArtifactBytes is smaller than the shell artifact envelope");
2462
+ if (input.signal?.aborted) {
2463
+ return ShellOutputSchema.parse({
2464
+ executable: input.executableName,
2465
+ exitCode: null,
2466
+ signal: null,
2467
+ stdout: "",
2468
+ stderr: "",
2469
+ outcome: "cancelled"
2470
+ });
2471
+ }
2472
+ const stdout = [];
2473
+ const stderr = [];
2474
+ const artifactStdout = [];
2475
+ const artifactStderr = [];
2476
+ let retained = 0;
2477
+ let artifactBytes = 0;
2478
+ let outcome = "exited";
2479
+ let child;
2480
+ let exitCode = null;
2481
+ let signal = null;
2482
+ const outputLimit = new Error("Coding command output retention budget exceeded");
2483
+ const launch = input.spawn;
2484
+ const retain = (target, artifactTarget, chunk) => {
2485
+ const remaining = Math.max(0, input.maxOutputBytes - retained);
2486
+ if (remaining > 0) {
2487
+ const kept = Buffer.from(chunk.subarray(0, remaining));
2488
+ target.push(kept);
2489
+ retained += kept.byteLength;
2490
+ }
2491
+ if (input.artifacts) {
2492
+ const artifactRemaining = Math.max(0, artifactPayloadLimit - artifactBytes);
2493
+ if (artifactRemaining > 0) {
2494
+ const kept = Buffer.from(chunk.subarray(0, artifactRemaining));
2495
+ artifactTarget.push(kept);
2496
+ artifactBytes += kept.byteLength;
2497
+ }
2498
+ if (chunk.byteLength > artifactRemaining)
2499
+ throw outputLimit;
2500
+ } else if (chunk.byteLength > remaining)
2501
+ throw outputLimit;
2502
+ };
2503
+ const command = startNativeCommand({
2504
+ executable: input.executable,
2505
+ args: [...input.args],
2506
+ cwd: input.cwd,
2507
+ env: { ...input.environment },
2508
+ envPolicy: "declared-only",
2509
+ signal: input.signal ?? new AbortController().signal,
2510
+ stop: { target: "group", graceMs: 0 },
2511
+ descendants: "terminate-after-leader",
2512
+ onOutput: (bytes, channel) => retain(channel === "stdout" ? stdout : stderr, channel === "stdout" ? artifactStdout : artifactStderr, bytes),
2513
+ onLeaderSettled: (event) => {
2514
+ if (event.kind === "error")
2515
+ return;
2516
+ exitCode = event.exitCode;
2517
+ signal = event.signal;
2518
+ }
2519
+ }, (started) => {
2520
+ child = started;
2521
+ }, {
2522
+ group: process.platform !== "win32",
2523
+ force: true,
2524
+ timeoutMs: input.timeoutMs,
2525
+ cleanupTimeoutMs: input.terminationGraceMs,
2526
+ closeTimeoutMs: input.terminationGraceMs,
2527
+ ...launch ? {
2528
+ launch: () => {
2529
+ child = launch();
2530
+ return child;
2531
+ }
2532
+ } : {},
2533
+ maxBufferedBytes: Math.max(64 * 1024, input.maxOutputBytes, input.artifacts ? artifactPayloadLimit : 0),
2534
+ ...input.cwdDescriptor !== undefined && { cwdDescriptor: input.cwdDescriptor }
2535
+ });
2536
+ try {
2537
+ const result = await command.result;
2538
+ exitCode = result.exitCode;
2539
+ signal = result.signal;
2540
+ } catch (error) {
2541
+ if (error === outputLimit)
2542
+ outcome = "output-limit";
2543
+ else if (input.signal?.aborted && error === input.signal.reason)
2544
+ outcome = "cancelled";
2545
+ else if (error instanceof NativeCommandError2 && error.code === "COMMAND_LIMIT" && (error.reason === "deadline" || error.reason === "output-budget"))
2546
+ outcome = error.reason === "deadline" ? "timeout" : "output-limit";
2547
+ else if (error instanceof CommandStopped)
2548
+ outcome = "cancelled";
2549
+ else
2550
+ throw error;
2551
+ }
2552
+ const hasArtifact = input.artifacts && artifactBytes > input.maxOutputBytes;
2553
+ const completeStdout = Buffer.concat(artifactStdout);
2554
+ const completeStderr = Buffer.concat(artifactStderr);
2555
+ const artifactData = hasArtifact ? Buffer.concat([stdoutHeader, completeStdout, stderrHeader, completeStderr]) : undefined;
2556
+ const stdoutBudget = completeStderr.byteLength === 0 ? input.maxOutputBytes : Math.ceil(input.maxOutputBytes / 2);
2557
+ const stderrBudget = completeStdout.byteLength === 0 ? input.maxOutputBytes : Math.floor(input.maxOutputBytes / 2);
2558
+ const stdoutPreview = hasArtifact ? preview(completeStdout, stdoutBudget) : retainedPreview(Buffer.concat(stdout));
2559
+ const stderrPreview = hasArtifact ? preview(completeStderr, stderrBudget) : retainedPreview(Buffer.concat(stderr));
2560
+ const persisted = hasArtifact ? await input.artifacts?.write({
2561
+ mediaType: "text/plain; charset=utf-8",
2562
+ data: artifactData ?? new Uint8Array,
2563
+ ...input.authorization && { authorization: input.authorization }
2564
+ }) : undefined;
2565
+ return ShellOutputSchema.parse({
2566
+ executable: input.executableName,
2567
+ exitCode,
2568
+ signal,
2569
+ stdout: stdoutPreview.data.toString("utf8"),
2570
+ stderr: stderrPreview.data.toString("utf8"),
2571
+ outcome,
2572
+ ...persisted && {
2573
+ artifact: {
2574
+ reference: persisted.reference,
2575
+ bytes: artifactData?.byteLength ?? 0,
2576
+ truncated: outcome === "output-limit",
2577
+ headBytes: stdoutPreview.headBytes + stderrPreview.headBytes,
2578
+ tailBytes: stdoutPreview.tailBytes + stderrPreview.tailBytes,
2579
+ omittedBytes: stdoutPreview.omittedBytes + stderrPreview.omittedBytes
2580
+ }
2581
+ }
2582
+ });
2583
+ }
2584
+
2585
+ // src/agent-runtime/coding-tool-shell.ts
2586
+ function createShellCodingTool(config, limits) {
2587
+ const executables = config.executables ?? {};
2588
+ const executableNames = Object.keys(executables).sort();
2589
+ return defineRuntimeTool2({
2590
+ name: "run_command",
2591
+ description: "Run a finite host-declared executable with bounded arguments, time and output.",
2592
+ identity: { serviceName: "coding", action: "shell", method: "POST" },
2593
+ input: createShellInputSchema(executableNames),
2594
+ output: ShellOutputSchema,
2595
+ transports: ["AGENT"],
2596
+ handler: async ({ input, signal }) => {
2597
+ if (input.args.length > limits.maxShellArguments) {
2598
+ throw new Error("Coding tool shell arguments exceed maxShellArguments");
2599
+ }
2600
+ const argumentBytes = input.args.reduce((total, argument) => total + Buffer.byteLength(argument), 0);
2601
+ if (argumentBytes > limits.maxShellArgumentBytes) {
2602
+ throw new Error("Coding tool shell arguments exceed maxShellArgumentBytes");
2603
+ }
2604
+ const executable = executables[input.executable];
2605
+ if (!executable)
2606
+ throw new Error("Coding tool executable is not declared");
2607
+ return withCommandDirectory(config.root, input.cwd, limits.maxPathBytes, async (cwd) => {
2608
+ const assertSandboxCwd = cwd.assertCurrent;
2609
+ const shellAuthorization = {
2610
+ operation: "shell",
2611
+ executable: input.executable,
2612
+ args: input.args,
2613
+ cwd: cwd.relative
2614
+ };
2615
+ await authorizeCodingTool(config, shellAuthorization);
2616
+ let executablePath = executable;
2617
+ let executableArgs = input.args;
2618
+ let environment = config.environment ?? {};
2619
+ if (config.sandbox) {
2620
+ assertSandboxCwd();
2621
+ const probed = await probeAgentProcessSandboxOutcome(config.sandbox.adapter);
2622
+ const grade = probed.grade;
2623
+ if (grade.grade === "unavailable") {
2624
+ codingRefusal("SANDBOX_UNAVAILABLE", "The configured process sandbox is unavailable", {
2625
+ details: { reason: grade.reason },
2626
+ ...probed.cause !== undefined && { cause: probed.cause }
2627
+ });
2628
+ }
2629
+ const missing = missingSandboxRestrictions2(grade, config.sandbox.required);
2630
+ if (missing.length > 0) {
2631
+ codingRefusal("SANDBOX_INSUFFICIENT", "The process sandbox lacks required restrictions", {
2632
+ details: { missing, grade: grade.grade }
2633
+ });
2634
+ }
2635
+ try {
2636
+ const prepared = await config.sandbox.adapter.prepare({
2637
+ required: config.sandbox.required,
2638
+ executable,
2639
+ args: input.args,
2640
+ cwd: cwd.absolute,
2641
+ environment
2642
+ });
2643
+ executablePath = prepared.executable;
2644
+ executableArgs = [...prepared.args];
2645
+ environment = prepared.environment ?? environment;
2646
+ } catch (error) {
2647
+ const refreshed = await probeAgentProcessSandboxOutcome(config.sandbox.adapter, {
2648
+ refresh: true
2649
+ });
2650
+ codingRefusal("SANDBOX_UNAVAILABLE", "The process sandbox failed while preparing a command", {
2651
+ details: { grade: refreshed.grade.grade, reason: "sandbox prepare failed" },
2652
+ cause: refreshed.cause === undefined ? error : new AggregateError([error, refreshed.cause], "Sandbox prepare failed and probing it again failed")
2653
+ });
2654
+ }
2655
+ }
2656
+ if (config.sandbox || cwd.descriptor === undefined)
2657
+ assertSandboxCwd();
2658
+ const ordinary = !config.sandbox?.adapter.spawn && cwd.descriptor !== undefined;
2659
+ const linkedCwd = ordinary && process.platform === "linux" ? `/proc/${process.pid}/fd/${cwd.descriptor}` : undefined;
2660
+ return await runCodingShell({
2661
+ ...ordinary && linkedCwd === undefined && cwd.descriptor !== undefined && { cwdDescriptor: cwd.descriptor },
2662
+ ...config.sandbox?.adapter.spawn && {
2663
+ spawn: () => {
2664
+ const adapter = config.sandbox?.adapter;
2665
+ if (!adapter?.spawn)
2666
+ throw new Error("Sandbox launcher is unavailable");
2667
+ assertSandboxCwd();
2668
+ return adapter.spawn({
2669
+ executable: executablePath,
2670
+ args: executableArgs,
2671
+ cwd: cwd.absolute,
2672
+ environment
2673
+ });
2674
+ }
2675
+ },
2676
+ executableName: input.executable,
2677
+ executable: executablePath,
2678
+ args: executableArgs,
2679
+ cwd: linkedCwd ?? cwd.absolute,
2680
+ environment,
2681
+ ...signal && { signal },
2682
+ timeoutMs: limits.shellTimeoutMs,
2683
+ terminationGraceMs: limits.shellTerminationGraceMs,
2684
+ maxOutputBytes: limits.maxShellOutputBytes,
2685
+ maxArtifactBytes: limits.maxArtifactBytes,
2686
+ ...config.artifacts && { artifacts: config.artifacts },
2687
+ authorization: shellAuthorization
2688
+ });
2689
+ });
2690
+ }
2691
+ });
2692
+ }
2693
+ async function withCommandDirectory(configuredRoot, requested, maxPathBytes, work) {
2694
+ const shaped = (root) => requested === "." ? "." : boundedCodingRelativePath(root, requested, maxPathBytes);
2695
+ if (process.platform !== "linux" && process.platform !== "darwin") {
2696
+ const root = await realpath2(configuredRoot);
2697
+ const relative = shaped(root);
2698
+ const absolute = path4.resolve(root, relative);
2699
+ const resolved = await realpath2(absolute).catch((error) => refuseUnopenableCodingDirectory(error, relative));
2700
+ const identity = await stat(resolved);
2701
+ if (!identity.isDirectory() || resolved !== absolute) {
2702
+ refuseUnopenableCodingDirectory(Object.assign(new Error("not a directory"), { code: "ENOTDIR" }), relative);
2703
+ }
2704
+ const found = { relative, absolute };
2705
+ return work({
2706
+ ...found,
2707
+ assertCurrent: () => {
2708
+ const current = statSync(found.absolute);
2709
+ if (identity.dev !== current.dev || identity.ino !== current.ino)
2710
+ codingRefusal("CONFLICT", "Command directory changed during authorization");
2711
+ }
2712
+ });
2713
+ }
2714
+ return withCodingRoot(configuredRoot, async (root, rootPath) => {
2715
+ const relative = shaped(rootPath);
2716
+ const directory = await openContainedDirectory(root, relative).catch((error) => refuseUnopenableCodingDirectory(error, relative));
2717
+ try {
2718
+ const absolute = path4.resolve(rootPath, relative);
2719
+ return await work({
2720
+ relative,
2721
+ absolute,
2722
+ descriptor: directory.fd,
2723
+ assertCurrent: () => {
2724
+ const expected = fstatSync(directory.fd);
2725
+ const current = statSync(absolute);
2726
+ if (expected.dev !== current.dev || expected.ino !== current.ino)
2727
+ codingRefusal("CONFLICT", "Command directory changed during authorization");
2728
+ }
2729
+ });
2730
+ } finally {
2731
+ await directory.close();
2732
+ }
2733
+ });
2734
+ }
2735
+
2736
+ // src/agent-runtime/coding-tools.ts
2737
+ var AGENT_CODING_TOOL_NAMES = {
2738
+ editFile: "edit_file",
2739
+ glob: "glob",
2740
+ listDirectory: "list_directory",
2741
+ readFile: "read_file",
2742
+ readOutput: "read_output",
2743
+ searchOutput: "search_output",
2744
+ runCommand: "run_command",
2745
+ searchFiles: "search_files",
2746
+ writeFile: "write_file"
2747
+ };
2748
+ function createAgentCodingTools2(config) {
2749
+ if (!path5.isAbsolute(config.root))
2750
+ throw new Error("Coding tool root must be absolute");
2751
+ for (const executable of Object.values(config.executables ?? {})) {
2752
+ if (!path5.isAbsolute(executable)) {
2753
+ throw new Error("Coding tool executables must be absolute paths");
2754
+ }
2755
+ }
2756
+ const limits = resolveCodingToolLimits(config.limits);
2757
+ const artifactTools = [];
2758
+ if (config.artifacts) {
2759
+ const artifacts = config.artifacts;
2760
+ artifactTools.push(defineRuntimeTool2({
2761
+ name: AGENT_CODING_TOOL_NAMES.readOutput,
2762
+ description: "Read a bounded byte slice from one opaque coding output artifact.",
2763
+ identity: { serviceName: "coding", action: "read-artifact", method: "POST" },
2764
+ input: z8.object({
2765
+ reference: z8.string().min(1),
2766
+ offset: z8.int().nonnegative().default(0),
2767
+ maxBytes: z8.int().positive().optional()
2768
+ }).strict(),
2769
+ output: z8.object({
2770
+ reference: z8.string().min(1),
2771
+ offset: z8.int().nonnegative(),
2772
+ text: z8.string(),
2773
+ bytes: z8.int().nonnegative(),
2774
+ totalBytes: z8.int().nonnegative().optional()
2775
+ }).strict(),
2776
+ transports: ["AGENT"],
2777
+ handler: async ({ input }) => {
2778
+ const maxBytes = Math.min(input.maxBytes ?? limits.maxReadBytes, limits.maxReadBytes);
2779
+ await authorizeCodingTool(config, {
2780
+ operation: "artifact-read",
2781
+ reference: input.reference,
2782
+ offset: input.offset,
2783
+ maxBytes
2784
+ });
2785
+ const originAuthorization = await artifacts.authorization?.(input.reference);
2786
+ if (originAuthorization)
2787
+ await authorizeCodingTool(config, originAuthorization);
2788
+ const result = await artifacts.read({
2789
+ reference: input.reference,
2790
+ offset: input.offset,
2791
+ maxBytes
2792
+ });
2793
+ if (result.data.byteLength > maxBytes)
2794
+ throw new Error("Coding artifact store exceeded the requested byte budget");
2795
+ if (result.totalBytes !== undefined && result.totalBytes < input.offset + result.data.byteLength) {
2796
+ throw new Error("Coding artifact store returned inconsistent totalBytes");
2797
+ }
2798
+ return {
2799
+ reference: input.reference,
2800
+ offset: input.offset,
2801
+ text: new TextDecoder("utf-8", { fatal: true }).decode(result.data),
2802
+ bytes: result.data.byteLength,
2803
+ ...result.totalBytes !== undefined && { totalBytes: result.totalBytes }
2804
+ };
2805
+ }
2806
+ }));
2807
+ if (artifacts.search) {
2808
+ const search = artifacts.search;
2809
+ artifactTools.push(defineRuntimeTool2({
2810
+ name: AGENT_CODING_TOOL_NAMES.searchOutput,
2811
+ description: "Search a durable spilled output without loading it into context.",
2812
+ identity: { serviceName: "coding", action: "search-artifact", method: "POST" },
2813
+ input: z8.object({
2814
+ reference: z8.string().min(1),
2815
+ query: z8.string().min(1),
2816
+ maxMatches: z8.int().positive().max(limits.maxSearchResults).default(20)
2817
+ }).strict(),
2818
+ output: z8.object({
2819
+ reference: z8.string(),
2820
+ matches: z8.array(z8.object({ line: z8.int().positive(), text: z8.string() }))
2821
+ }).strict(),
2822
+ transports: ["AGENT"],
2823
+ handler: async ({ input }) => {
2824
+ await authorizeCodingTool(config, {
2825
+ operation: "artifact-search",
2826
+ reference: input.reference,
2827
+ query: input.query,
2828
+ maxMatches: input.maxMatches
2829
+ });
2830
+ const originAuthorization = await artifacts.authorization?.(input.reference);
2831
+ if (originAuthorization)
2832
+ await authorizeCodingTool(config, originAuthorization);
2833
+ return {
2834
+ reference: input.reference,
2835
+ matches: [
2836
+ ...await search({
2837
+ reference: input.reference,
2838
+ query: input.query,
2839
+ maxMatches: input.maxMatches
2840
+ })
2841
+ ]
2842
+ };
2843
+ }
2844
+ }));
2845
+ }
2846
+ }
2847
+ const commandTools = Object.keys(config.executables ?? {}).length ? [createShellCodingTool(config, limits)] : [];
2848
+ return [
2849
+ ...createFileCodingTools(config, limits),
2850
+ ...createListingCodingTools(config, limits),
2851
+ ...createSearchAndPatchCodingTools(config, limits),
2852
+ ...commandTools,
2853
+ ...artifactTools
2854
+ ];
2855
+ }
2856
+
2857
+ export { AgentCodingToolLimitsSchema2, AgentCodingToolAuthorizationSchema2, AgentCodingToolPathAuthorizationSchema2, ShellOutputSchema, AgentSandboxRestrictionSchema2, AgentSandboxGradeSchema2, probeAgentProcessSandbox2, missingSandboxRestrictions2, recordAgentSandboxProbe2, codingRefusal, refuseUnknownSpillReference, AGENT_CODING_TOOL_NAMES, createAgentCodingTools2 };