flavor-code 1.2.8 → 1.2.9
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.
- package/README.md +35 -0
- package/README.zh-CN.md +36 -6
- package/dist/agent/loop.d.ts +4 -0
- package/dist/agent/types.d.ts +10 -0
- package/dist/{app-NL2O75LQ.js → app-USAFQVSX.js} +379 -12
- package/dist/{chunk-NVLYFU6P.js → chunk-F2HMO5Q2.js} +28 -11
- package/dist/{chunk-6X5PHLZI.js → chunk-G32MCAZA.js} +1 -1
- package/dist/{chunk-N2S7USST.js → chunk-RQTYHUMK.js} +2 -1
- package/dist/{chunk-ED4R7ZCP.js → chunk-WGYNTR4Q.js} +1820 -492
- package/dist/{chunk-MBLAXTUP.js → chunk-XFCJXRJ2.js} +15 -5
- package/dist/{claude-ink-WPWPEL5A.js → claude-ink-ORUA7GWG.js} +2 -2
- package/dist/cli.js +9 -8
- package/dist/config/protected-file.d.ts +6 -0
- package/dist/context/manager.d.ts +2 -0
- package/dist/context/workspace-instructions.d.ts +10 -0
- package/dist/desktop/main.js +12725 -5181
- package/dist/desktop/pixel-worker.js +73 -0
- package/dist/desktop/preload.cjs +80 -2
- package/dist/desktop-renderer/assets/index-D-s27J39.js +151 -0
- package/dist/desktop-renderer/assets/index-DMZBSGrp.css +1 -0
- package/dist/desktop-renderer/index.html +3 -4
- package/dist/harness/local.d.ts +4 -1
- package/dist/jobs/registry.d.ts +48 -0
- package/dist/{load-RN3WBX4C.js → load-6ZMEBHSG.js} +1 -1
- package/dist/permissions/engine.d.ts +7 -0
- package/dist/production.d.ts +13 -1
- package/dist/sdk/index.js +4 -4
- package/dist/terminal/service.d.ts +57 -0
- package/dist/tools/files.d.ts +10 -0
- package/dist/tools/jobs.d.ts +4 -0
- package/dist/tools/runtime.d.ts +2 -0
- package/dist/tools/shell.d.ts +13 -3
- package/dist/tools/terminal.d.ts +3 -0
- package/dist/tools/types.d.ts +76 -4
- package/dist/tools/web.d.ts +53 -0
- package/package.json +13 -2
- package//346/212/200/346/234/257/346/226/271/346/241/210/346/212/245/345/221/212.md +752 -1
- package/dist/desktop-renderer/assets/index-57tXtXhI.js +0 -143
- package/dist/desktop-renderer/assets/index-Cm_ktmXm.css +0 -1
|
@@ -12,7 +12,7 @@ import {
|
|
|
12
12
|
redactConfig,
|
|
13
13
|
setProjectMcpServerDisabled,
|
|
14
14
|
updateProtectedFile
|
|
15
|
-
} from "./chunk-
|
|
15
|
+
} from "./chunk-XFCJXRJ2.js";
|
|
16
16
|
import {
|
|
17
17
|
AuditLogger,
|
|
18
18
|
appendUsageLog,
|
|
@@ -23,7 +23,7 @@ import {
|
|
|
23
23
|
resolveLanguage,
|
|
24
24
|
setUsageSession,
|
|
25
25
|
usageLogPath
|
|
26
|
-
} from "./chunk-
|
|
26
|
+
} from "./chunk-RQTYHUMK.js";
|
|
27
27
|
|
|
28
28
|
// src/models/types.ts
|
|
29
29
|
function modelContentText(content) {
|
|
@@ -70,11 +70,11 @@ function message(error) {
|
|
|
70
70
|
// src/permissions/engine.ts
|
|
71
71
|
import { existsSync, realpathSync } from "fs";
|
|
72
72
|
import { dirname, isAbsolute, parse, relative, resolve, sep } from "path";
|
|
73
|
-
var CONTROL_TOOLS = /* @__PURE__ */ new Set(["TaskPlan", "TaskUpdate", "AskUserQuestion", "TodoWrite", "TaskOutput"]);
|
|
73
|
+
var CONTROL_TOOLS = /* @__PURE__ */ new Set(["TaskPlan", "TaskUpdate", "AskUserQuestion", "TodoWrite", "TaskOutput", "JobList", "JobRead", "JobWait", "JobKill", "TerminalRead", "TerminalList"]);
|
|
74
74
|
var READ_TOOLS = /* @__PURE__ */ new Set(["Read", "Glob", "Grep", "Search", "List", "SkillResource", "LspFindRefs", "LspHover", "LspDiagnostics", "ListRegisteredTools"]);
|
|
75
75
|
var WRITE_TOOLS = /* @__PURE__ */ new Set(["Write", "Edit", "ApplyPatch", "Copy", "Mkdir", "RegisterTool"]);
|
|
76
76
|
var DESTRUCTIVE_TOOLS = /* @__PURE__ */ new Set(["Delete", "Move", "RemoveTool"]);
|
|
77
|
-
var SHELL_TOOLS = /* @__PURE__ */ new Set(["Shell", "Bash", "Command", "Exec"]);
|
|
77
|
+
var SHELL_TOOLS = /* @__PURE__ */ new Set(["Shell", "Bash", "Command", "Exec", "TerminalOpen", "TerminalWrite", "TerminalResize", "TerminalClose"]);
|
|
78
78
|
var NETWORK_TOOLS = /* @__PURE__ */ new Set(["WebFetch", "WebSearch", "Fetch", "Network"]);
|
|
79
79
|
var PATH_REQUIRED_TOOLS = /* @__PURE__ */ new Set([
|
|
80
80
|
"Read",
|
|
@@ -107,18 +107,26 @@ var PermissionEngine = class {
|
|
|
107
107
|
#lexicalWorkspace;
|
|
108
108
|
#workspace;
|
|
109
109
|
#mode;
|
|
110
|
+
#profile;
|
|
110
111
|
constructor(options) {
|
|
111
112
|
const root = resolve(options.workspace);
|
|
112
113
|
this.#lexicalWorkspace = root;
|
|
113
114
|
this.#workspace = existsSync(root) ? realpathSync.native(root) : root;
|
|
114
115
|
this.#mode = canonicalMode(options.mode ?? "default");
|
|
116
|
+
this.#profile = options.profile ?? "standard";
|
|
115
117
|
}
|
|
116
118
|
get mode() {
|
|
117
119
|
return this.#mode;
|
|
118
120
|
}
|
|
121
|
+
get profile() {
|
|
122
|
+
return this.#profile;
|
|
123
|
+
}
|
|
119
124
|
setMode(mode) {
|
|
120
125
|
this.#mode = canonicalMode(mode);
|
|
121
126
|
}
|
|
127
|
+
setProfile(profile) {
|
|
128
|
+
this.#profile = profile;
|
|
129
|
+
}
|
|
122
130
|
decide(request) {
|
|
123
131
|
if (request.tool === "Task") return request.agent === "main" ? { decision: "allow" } : { decision: "deny", reason: "Task delegation is restricted to the main agent" };
|
|
124
132
|
if (CONTROL_TOOLS.has(request.tool)) return { decision: "allow" };
|
|
@@ -137,6 +145,7 @@ var PermissionEngine = class {
|
|
|
137
145
|
}
|
|
138
146
|
}
|
|
139
147
|
const inside = paths.every((path) => classifyPath(this.#lexicalWorkspace, this.#workspace, path).inside);
|
|
148
|
+
if (this.#profile === "d2c") return this.#d2cDecision(request, inside);
|
|
140
149
|
if (this.#mode === "plan") {
|
|
141
150
|
return READ_TOOLS.has(request.tool) ? { decision: "allow" } : { decision: "deny", reason: "Plan mode is read-only" };
|
|
142
151
|
}
|
|
@@ -159,6 +168,44 @@ var PermissionEngine = class {
|
|
|
159
168
|
}
|
|
160
169
|
return ask(this.#mode, `Unknown tool: ${request.tool}`);
|
|
161
170
|
}
|
|
171
|
+
#d2cDecision(request, inside) {
|
|
172
|
+
if ((request.paths?.length ?? 0) > 0 && !inside) {
|
|
173
|
+
return { decision: "deny", reason: "D2C tools must remain in the workspace" };
|
|
174
|
+
}
|
|
175
|
+
if (READ_TOOLS.has(request.tool)) return { decision: "allow" };
|
|
176
|
+
if (isDeletionTool(request.tool)) {
|
|
177
|
+
return { decision: "ask", reason: "Deletion requires approval", allowAlways: false };
|
|
178
|
+
}
|
|
179
|
+
if (DESTRUCTIVE_TOOLS.has(request.tool)) {
|
|
180
|
+
return request.tool === "Move" ? { decision: "allow" } : { decision: "ask", reason: "Deletion requires approval", allowAlways: false };
|
|
181
|
+
}
|
|
182
|
+
if (WRITE_TOOLS.has(request.tool)) return { decision: "allow" };
|
|
183
|
+
if (SHELL_TOOLS.has(request.tool)) return this.#d2cShellDecision(request);
|
|
184
|
+
if (isNetworkTool(request.tool)) return { decision: "allow" };
|
|
185
|
+
return { decision: "allow" };
|
|
186
|
+
}
|
|
187
|
+
#d2cShellDecision(request) {
|
|
188
|
+
const analysis = request.args === void 0 ? analyzeCommand(request.command ?? "") : analyzeArgumentCommand(request.command ?? "", request.args);
|
|
189
|
+
const cwd = request.cwd ?? this.#workspace;
|
|
190
|
+
const cwdClassification = classifyPath(this.#lexicalWorkspace, this.#workspace, cwd);
|
|
191
|
+
if (cwdClassification.escape || !cwdClassification.inside) {
|
|
192
|
+
return { decision: "deny", reason: "D2C shell cwd must remain in the workspace" };
|
|
193
|
+
}
|
|
194
|
+
if (analysis.catastrophic) {
|
|
195
|
+
return { decision: "deny", reason: "Explicitly forbidden system-level command" };
|
|
196
|
+
}
|
|
197
|
+
if (isD2cManagedPreviewCommand(analysis.command)) {
|
|
198
|
+
return {
|
|
199
|
+
decision: "deny",
|
|
200
|
+
reason: "D2C preview lifecycle is managed by D2cCompare; pass the project directory directly instead of starting npm/Vite manually"
|
|
201
|
+
};
|
|
202
|
+
}
|
|
203
|
+
if (assessD2cCommandPaths(analysis.command, cwd, this.#workspace, this.#lexicalWorkspace) === "deny") {
|
|
204
|
+
return { decision: "deny", reason: "D2C command arguments escape the workspace" };
|
|
205
|
+
}
|
|
206
|
+
if (analysis.deletion) return { decision: "ask", reason: "Deletion requires approval", allowAlways: false };
|
|
207
|
+
return { decision: "allow" };
|
|
208
|
+
}
|
|
162
209
|
#shellDecision(request) {
|
|
163
210
|
const analysis = request.args === void 0 ? analyzeCommand(request.command ?? "") : analyzeArgumentCommand(request.command ?? "", request.args);
|
|
164
211
|
if (request.agent === "subagent") {
|
|
@@ -201,25 +248,29 @@ function ask(mode, reason) {
|
|
|
201
248
|
function isNetworkTool(name) {
|
|
202
249
|
return NETWORK_TOOLS.has(name) || name.startsWith("mcp__");
|
|
203
250
|
}
|
|
251
|
+
function isDeletionTool(name) {
|
|
252
|
+
if (name === "Delete" || name === "RemoveTool") return true;
|
|
253
|
+
return /(?:^|[_-])(delete|remove|unlink|destroy)(?:[_-]|$)/i.test(name);
|
|
254
|
+
}
|
|
204
255
|
function analyzeArgumentCommand(executable, args) {
|
|
205
256
|
const name = parse(executable.replace(/\.exe$/i, "")).name.toLowerCase();
|
|
206
257
|
const command = { executable: name, args: [...args], raw: [executable, ...args].join(" ").toLowerCase() };
|
|
207
258
|
if (["sh", "bash", "zsh"].includes(name)) {
|
|
208
259
|
const flag = args.findIndex((arg) => arg.toLowerCase() === "-c");
|
|
209
|
-
if (flag < 0 || args[flag + 1] === void 0) return
|
|
260
|
+
if (flag < 0 || args[flag + 1] === void 0) return commandAnalysis(command, true, true);
|
|
210
261
|
return { ...analyzeCommand(args[flag + 1]), wrapped: true };
|
|
211
262
|
}
|
|
212
263
|
if (name === "cmd") {
|
|
213
264
|
const flag = args.findIndex((arg) => arg.toLowerCase() === "/c");
|
|
214
|
-
if (flag < 0 || args[flag + 1] === void 0) return
|
|
265
|
+
if (flag < 0 || args[flag + 1] === void 0) return commandAnalysis(command, true, true);
|
|
215
266
|
return { ...analyzeCommand(args.slice(flag + 1).join(" ")), wrapped: true };
|
|
216
267
|
}
|
|
217
268
|
if (["powershell", "pwsh"].includes(name)) {
|
|
218
269
|
const flag = args.findIndex((arg) => ["-command", "-c"].includes(arg.toLowerCase()));
|
|
219
|
-
if (flag < 0 || args[flag + 1] === void 0) return
|
|
270
|
+
if (flag < 0 || args[flag + 1] === void 0) return commandAnalysis(command, true, true);
|
|
220
271
|
return { ...analyzeCommand(args.slice(flag + 1).join(" ")), wrapped: true };
|
|
221
272
|
}
|
|
222
|
-
return
|
|
273
|
+
return commandAnalysis(command, false, false);
|
|
223
274
|
}
|
|
224
275
|
function classifyPath(lexicalWorkspace, physicalWorkspace, input) {
|
|
225
276
|
const candidate = resolve(lexicalWorkspace, input);
|
|
@@ -256,33 +307,60 @@ function isWithin(root, candidate) {
|
|
|
256
307
|
const delta = relative(root, candidate);
|
|
257
308
|
return delta === "" || !delta.startsWith(`..${sep}`) && delta !== ".." && !isAbsolute(delta);
|
|
258
309
|
}
|
|
310
|
+
function isD2cManagedPreviewCommand(command) {
|
|
311
|
+
const executable = command.executable.replace(/\.cmd$/i, "");
|
|
312
|
+
const args = command.args.map((arg) => arg.toLowerCase());
|
|
313
|
+
if (executable === "npm") {
|
|
314
|
+
if (args[0] === "start") return true;
|
|
315
|
+
if (args[0] === "run" && ["dev", "start", "serve", "preview"].includes(args[1] ?? "")) return true;
|
|
316
|
+
if (args[0] === "exec" && args.some((arg) => arg === "vite")) return true;
|
|
317
|
+
}
|
|
318
|
+
if (["pnpm", "yarn", "bun"].includes(executable)) {
|
|
319
|
+
const script = args[0] === "run" ? args[1] : args[0];
|
|
320
|
+
if (["dev", "start", "serve", "preview"].includes(script ?? "")) return true;
|
|
321
|
+
}
|
|
322
|
+
if (executable === "npx" && args.some((arg) => arg === "vite")) return true;
|
|
323
|
+
if (executable === "vite") return true;
|
|
324
|
+
if (executable === "node" && args.some((arg) => /(?:^|[\\/])vite[\\/]bin[\\/]vite(?:\.js)?$/i.test(arg))) return true;
|
|
325
|
+
return /(?:^|[\s;&|])(?:npm(?:\.cmd)?\s+(?:run\s+)?(?:dev|start|serve|preview)|(?:pnpm|yarn|bun)(?:\.cmd)?\s+(?:run\s+)?(?:dev|start|serve|preview)|npx(?:\.cmd)?\s+(?:[^\s]+\s+)*vite|npm(?:\.cmd)?\s+exec\s+(?:--\s+)?vite|vite(?:\.cmd)?|node(?:\.exe)?\s+[^\s]*vite[\\/]bin[\\/]vite(?:\.js)?)(?:\s|$)/i.test(command.raw);
|
|
326
|
+
}
|
|
327
|
+
function commandAnalysis(command, opaque, wrapped) {
|
|
328
|
+
return {
|
|
329
|
+
command,
|
|
330
|
+
destructive: isForbiddenCommand(command),
|
|
331
|
+
deletion: isDeletionCommand(command),
|
|
332
|
+
catastrophic: isCatastrophicCommand(command),
|
|
333
|
+
opaque,
|
|
334
|
+
wrapped
|
|
335
|
+
};
|
|
336
|
+
}
|
|
259
337
|
function analyzeCommand(raw, depth = 0) {
|
|
260
338
|
const command = parseCommand(raw);
|
|
261
|
-
if (depth > 4) return
|
|
339
|
+
if (depth > 4) return commandAnalysis(command, true, depth > 0);
|
|
262
340
|
const wrapper = command.executable;
|
|
263
341
|
if (["sh", "bash", "zsh"].includes(wrapper)) {
|
|
264
342
|
const flag = command.args.findIndex((arg) => arg.toLowerCase() === "-c");
|
|
265
|
-
if (flag < 0 || command.args[flag + 1] === void 0) return
|
|
343
|
+
if (flag < 0 || command.args[flag + 1] === void 0) return commandAnalysis(command, true, true);
|
|
266
344
|
return { ...analyzeCommand(command.args.slice(flag + 1).join(" "), depth + 1), wrapped: true };
|
|
267
345
|
}
|
|
268
346
|
if (wrapper === "cmd") {
|
|
269
347
|
const flag = command.args.findIndex((arg) => arg.toLowerCase() === "/c");
|
|
270
|
-
if (flag < 0 || command.args[flag + 1] === void 0) return
|
|
348
|
+
if (flag < 0 || command.args[flag + 1] === void 0) return commandAnalysis(command, true, true);
|
|
271
349
|
return { ...analyzeCommand(command.args.slice(flag + 1).join(" "), depth + 1), wrapped: true };
|
|
272
350
|
}
|
|
273
351
|
if (["powershell", "pwsh"].includes(wrapper)) {
|
|
274
352
|
const fileFlag = command.args.some((arg) => ["-file", "-f"].includes(arg.toLowerCase()));
|
|
275
|
-
if (fileFlag) return
|
|
353
|
+
if (fileFlag) return commandAnalysis(command, true, true);
|
|
276
354
|
const flag = command.args.findIndex((arg) => ["-command", "-c"].includes(arg.toLowerCase()));
|
|
277
|
-
if (flag < 0 || command.args[flag + 1] === void 0) return
|
|
355
|
+
if (flag < 0 || command.args[flag + 1] === void 0) return commandAnalysis(command, true, true);
|
|
278
356
|
return { ...analyzeCommand(command.args.slice(flag + 1).join(" "), depth + 1), wrapped: true };
|
|
279
357
|
}
|
|
280
358
|
if (wrapper === "call") {
|
|
281
|
-
if (command.args.length === 0) return
|
|
359
|
+
if (command.args.length === 0) return commandAnalysis(command, true, true);
|
|
282
360
|
return { ...analyzeCommand(command.args.join(" "), depth + 1), wrapped: true };
|
|
283
361
|
}
|
|
284
362
|
const opaque = ["start", "for", "if"].includes(wrapper) || /[%!()]/.test(command.raw);
|
|
285
|
-
return
|
|
363
|
+
return commandAnalysis(command, opaque, depth > 0);
|
|
286
364
|
}
|
|
287
365
|
function parseCommand(raw) {
|
|
288
366
|
const tokens = raw.match(/(?:[^\s"']+|"[^"]*"|'[^']*')+/g)?.map((token) => token.replace(/^(?:"(.*)"|'(.*)')$/, "$1$2")) ?? [];
|
|
@@ -290,15 +368,26 @@ function parseCommand(raw) {
|
|
|
290
368
|
return { executable: parse(executable).name.toLowerCase(), args: tokens, raw: raw.toLowerCase() };
|
|
291
369
|
}
|
|
292
370
|
function isForbiddenCommand(command) {
|
|
371
|
+
return isCatastrophicCommand(command) || /(^|\s)(git\s+reset\s+--hard|git\s+clean\s+-[^\s]*f)/.test(command.raw);
|
|
372
|
+
}
|
|
373
|
+
function isCatastrophicCommand(command) {
|
|
293
374
|
if (["shutdown", "reboot", "halt", "mkfs", "diskpart", "format"].includes(command.executable)) return true;
|
|
375
|
+
if (/(^|[;&|]\s*)(shutdown|reboot|halt|mkfs|diskpart|format)(?:\.exe)?(?:\s|$)/i.test(command.raw)) return true;
|
|
294
376
|
if (command.executable === "rm" && isDestructiveRm(command.args)) return true;
|
|
295
377
|
for (const match of command.raw.matchAll(/\brm(?:\.exe)?\s+([^;&|]+)/gi)) {
|
|
296
378
|
const nested = parseCommand(`rm ${match[1] ?? ""}`);
|
|
297
379
|
if (isDestructiveRm(nested.args)) return true;
|
|
298
380
|
}
|
|
299
|
-
if (
|
|
300
|
-
|
|
301
|
-
|
|
381
|
+
if (/\b(remove-item|ri)\b/i.test(command.raw)) {
|
|
382
|
+
const recursive = /\s-(recurse|r)(?:\s|$)/i.test(command.raw);
|
|
383
|
+
const root = command.args.some(isFilesystemRoot) || /\s(?:[a-z]:[\\/]?|\/)(?:\s|$)/i.test(command.raw);
|
|
384
|
+
if (recursive && root) return true;
|
|
385
|
+
}
|
|
386
|
+
return command.executable === "dd" && command.args.some((arg) => /^of=\/dev\//i.test(arg));
|
|
387
|
+
}
|
|
388
|
+
function isDeletionCommand(command) {
|
|
389
|
+
if (["rm", "rmdir", "unlink", "del", "erase", "remove-item", "ri"].includes(command.executable)) return true;
|
|
390
|
+
return /(^|[;&|]\s*)(rm|rmdir|unlink|del|erase|remove-item|ri)(?:\.exe)?(?:\s|$)/i.test(command.raw) || /(^|\s)git\s+(rm|clean)(?:\s|$)/i.test(command.raw) || /(^|\s)git\s+reset\s+--hard(?:\s|$)/i.test(command.raw) || /(^|\s)find\s+[^;&|]*(?:-delete|-exec\s+rm)(?:\s|$)/i.test(command.raw);
|
|
302
391
|
}
|
|
303
392
|
function isDestructiveRm(args) {
|
|
304
393
|
const shortFlags = args.filter((arg) => /^-[^-]/.test(arg)).join("").toLowerCase();
|
|
@@ -387,6 +476,16 @@ function assessArgumentPath(value, cwd, lexicalWorkspace, physicalWorkspace) {
|
|
|
387
476
|
function looksLikePath(value) {
|
|
388
477
|
return isAbsolute(value) || value === "." || value === ".." || value.startsWith("./") || value.startsWith(".\\") || value.startsWith("../") || value.startsWith("..\\") || value.includes("/") || value.includes("\\");
|
|
389
478
|
}
|
|
479
|
+
function assessD2cCommandPaths(command, cwd, workspace, lexicalWorkspace) {
|
|
480
|
+
for (const raw of command.args) {
|
|
481
|
+
const value = raw.includes("=") ? raw.slice(raw.indexOf("=") + 1) : raw;
|
|
482
|
+
if (process.platform === "win32" && /^\/[a-z?]+$/i.test(value)) continue;
|
|
483
|
+
if (!(isAbsolute(value) || value === ".." || value.startsWith("../") || value.startsWith("..\\"))) continue;
|
|
484
|
+
const classification = classifyPath(lexicalWorkspace, workspace, resolve(cwd, value));
|
|
485
|
+
if (classification.escape || !classification.inside) return "deny";
|
|
486
|
+
}
|
|
487
|
+
return "allow";
|
|
488
|
+
}
|
|
390
489
|
|
|
391
490
|
// src/utils/redact.ts
|
|
392
491
|
function redactErrorText(text) {
|
|
@@ -996,22 +1095,32 @@ function transcriptReducer(state, action) {
|
|
|
996
1095
|
state: "running",
|
|
997
1096
|
text: `${event.name}${event.label ? ` ${event.label}` : ""}`,
|
|
998
1097
|
...event.hint === void 0 ? {} : { hint: event.hint },
|
|
1098
|
+
...event.presentation === void 0 ? {} : { presentation: event.presentation },
|
|
999
1099
|
tool: { name: event.name, input: event.input }
|
|
1000
1100
|
});
|
|
1001
1101
|
if (event.type === "tool-end") {
|
|
1002
1102
|
const cancelled = !event.result.ok && event.result.error?.code === "cancelled";
|
|
1003
1103
|
const previous = state.active.blocks.find((block) => block.kind === "status" && block.id === `tool:${event.id}`);
|
|
1004
1104
|
const input = previous?.kind === "status" && previous.tool !== void 0 ? previous.tool.input : null;
|
|
1105
|
+
const presentation = event.result.ok ? event.result.presentation ?? (previous?.kind === "status" ? previous.presentation : void 0) : void 0;
|
|
1005
1106
|
return upsertStatus(state, {
|
|
1006
1107
|
kind: "status",
|
|
1007
1108
|
id: `tool:${event.id}`,
|
|
1008
1109
|
state: event.result.ok ? "completed" : cancelled ? "cancelled" : "failed",
|
|
1009
1110
|
text: `${event.result.ok ? "\u2713" : "\xD7"} ${event.name}${event.label ? ` ${event.label}` : ""}`,
|
|
1010
1111
|
...event.hint === void 0 ? {} : { hint: event.hint },
|
|
1011
|
-
...
|
|
1112
|
+
...presentation === void 0 ? {} : { presentation },
|
|
1012
1113
|
tool: { name: event.name, input, result: event.result }
|
|
1013
1114
|
});
|
|
1014
1115
|
}
|
|
1116
|
+
if (event.type === "deliverables") return upsertStatus(state, {
|
|
1117
|
+
kind: "status",
|
|
1118
|
+
id: `deliverables:${state.active.id}`,
|
|
1119
|
+
state: "completed",
|
|
1120
|
+
text: `Changed ${event.files.length} file${event.files.length === 1 ? "" : "s"}`,
|
|
1121
|
+
details: event.files.map((file) => `${file.operation} ${file.path} (+${file.added} -${file.removed})`).join("\n"),
|
|
1122
|
+
presentation: { kind: "changeset", files: event.files }
|
|
1123
|
+
});
|
|
1015
1124
|
if (event.type === "notice") return upsertStatus(state, {
|
|
1016
1125
|
kind: "status",
|
|
1017
1126
|
id: `notice:${state.active.blocks.length}`,
|
|
@@ -1114,12 +1223,12 @@ function hydrateHistory(messages, compact) {
|
|
|
1114
1223
|
function transcriptFromLegacyConversation(messages, compact) {
|
|
1115
1224
|
return hydrateHistory(messages, compact);
|
|
1116
1225
|
}
|
|
1117
|
-
function applyTaskSnapshot(turn,
|
|
1226
|
+
function applyTaskSnapshot(turn, snapshot4, includeTerminal = true) {
|
|
1118
1227
|
const taskBlocks = [];
|
|
1119
1228
|
const previous = new Map(turn.blocks.filter((block) => block.kind === "status").map((block) => [block.id, block]));
|
|
1120
1229
|
const now = Date.now();
|
|
1121
1230
|
const suppressed = new Set(turn.suppressedTaskIds ?? []);
|
|
1122
|
-
for (const task of
|
|
1231
|
+
for (const task of snapshot4.plan?.tasks ?? []) {
|
|
1123
1232
|
const id = `task:${task.id}`;
|
|
1124
1233
|
const terminal = ["completed", "failed", "blocked", "cancelled"].includes(task.status);
|
|
1125
1234
|
if (!terminal) suppressed.delete(id);
|
|
@@ -1139,16 +1248,16 @@ function applyTaskSnapshot(turn, snapshot2, includeTerminal = true) {
|
|
|
1139
1248
|
...elapsedMs === void 0 ? {} : { elapsedMs }
|
|
1140
1249
|
});
|
|
1141
1250
|
}
|
|
1142
|
-
for (const node of
|
|
1143
|
-
const status =
|
|
1251
|
+
for (const node of snapshot4.subagents.graph?.nodes ?? []) {
|
|
1252
|
+
const status = snapshot4.subagents.states[node.id] ?? "pending";
|
|
1144
1253
|
const id = `subagent:${node.id}`;
|
|
1145
1254
|
const terminal = ["completed", "failed", "blocked", "cancelled"].includes(status);
|
|
1146
1255
|
if (!terminal) suppressed.delete(id);
|
|
1147
1256
|
if ((!includeTerminal || suppressed.has(id)) && terminal) continue;
|
|
1148
1257
|
const state = status === "running" ? "running" : status === "completed" ? "completed" : status === "cancelled" ? "cancelled" : status === "failed" || status === "blocked" ? "failed" : "info";
|
|
1149
1258
|
const prior = previous.get(id);
|
|
1150
|
-
const snapshotStartedAt =
|
|
1151
|
-
const snapshotElapsedMs =
|
|
1259
|
+
const snapshotStartedAt = snapshot4.subagents.startedAt?.[node.id];
|
|
1260
|
+
const snapshotElapsedMs = snapshot4.subagents.elapsedMs?.[node.id];
|
|
1152
1261
|
const startedAt = state === "running" ? snapshotStartedAt ?? prior?.startedAt ?? now : prior?.startedAt ?? snapshotStartedAt;
|
|
1153
1262
|
const elapsedMs = state !== "running" && startedAt !== void 0 ? prior?.elapsedMs ?? snapshotElapsedMs ?? Math.max(0, now - startedAt) : prior?.elapsedMs;
|
|
1154
1263
|
taskBlocks.push({
|
|
@@ -1174,15 +1283,15 @@ function applyTaskSnapshot(turn, snapshot2, includeTerminal = true) {
|
|
|
1174
1283
|
}
|
|
1175
1284
|
return {
|
|
1176
1285
|
...turn,
|
|
1177
|
-
taskSnapshot:
|
|
1286
|
+
taskSnapshot: snapshot4,
|
|
1178
1287
|
suppressedTaskIds: [...suppressed],
|
|
1179
1288
|
blocks,
|
|
1180
1289
|
statusLines: blocks.filter((block) => block.kind === "status").map((block) => block.text)
|
|
1181
1290
|
};
|
|
1182
1291
|
}
|
|
1183
|
-
function terminalTaskIds(
|
|
1184
|
-
const ids = (
|
|
1185
|
-
for (const [id, status] of Object.entries(
|
|
1292
|
+
function terminalTaskIds(snapshot4) {
|
|
1293
|
+
const ids = (snapshot4.plan?.tasks ?? []).filter((task) => ["completed", "failed", "blocked", "cancelled"].includes(task.status)).map((task) => `task:${task.id}`);
|
|
1294
|
+
for (const [id, status] of Object.entries(snapshot4.subagents.states)) {
|
|
1186
1295
|
if (["completed", "failed", "blocked", "cancelled"].includes(status)) ids.push(`subagent:${id}`);
|
|
1187
1296
|
}
|
|
1188
1297
|
return ids;
|
|
@@ -1466,7 +1575,8 @@ async function gitFiles(workspace) {
|
|
|
1466
1575
|
execFile("git", ["-C", workspace, "ls-files", "-z", "--cached", "--others", "--exclude-standard"], {
|
|
1467
1576
|
encoding: "buffer",
|
|
1468
1577
|
windowsHide: true,
|
|
1469
|
-
maxBuffer: 16 * 1024 * 1024
|
|
1578
|
+
maxBuffer: 16 * 1024 * 1024,
|
|
1579
|
+
timeout: 3e4
|
|
1470
1580
|
}, (error, stdout, stderr) => {
|
|
1471
1581
|
if (error !== null) {
|
|
1472
1582
|
reject(new Error(`Cannot enumerate checkpoint files with git: ${stderr.toString("utf8").trim() || error.message}`));
|
|
@@ -2490,10 +2600,10 @@ var AgentMessageQueue = class {
|
|
|
2490
2600
|
return { steering: [...this.#steering], followUp: [...this.#followUp] };
|
|
2491
2601
|
}
|
|
2492
2602
|
clear() {
|
|
2493
|
-
const
|
|
2603
|
+
const snapshot4 = this.snapshot();
|
|
2494
2604
|
this.#steering.length = 0;
|
|
2495
2605
|
this.#followUp.length = 0;
|
|
2496
|
-
return
|
|
2606
|
+
return snapshot4;
|
|
2497
2607
|
}
|
|
2498
2608
|
#items(kind) {
|
|
2499
2609
|
return kind === "steer" ? this.#steering : this.#followUp;
|
|
@@ -2862,10 +2972,10 @@ function runInvocation(invocation, signal, timeoutMs) {
|
|
|
2862
2972
|
}
|
|
2863
2973
|
|
|
2864
2974
|
// src/production.ts
|
|
2865
|
-
import { readFile as
|
|
2866
|
-
import { createHash as
|
|
2975
|
+
import { readFile as readFile17 } from "fs/promises";
|
|
2976
|
+
import { createHash as createHash9, randomUUID as randomUUID12 } from "crypto";
|
|
2867
2977
|
import { homedir, release as osRelease, version as osVersion } from "os";
|
|
2868
|
-
import { join as join17, relative as
|
|
2978
|
+
import { join as join17, relative as relative20, resolve as resolve24, sep as sep19 } from "path";
|
|
2869
2979
|
|
|
2870
2980
|
// src/agent/subagents.ts
|
|
2871
2981
|
import { z as z4 } from "zod";
|
|
@@ -3037,13 +3147,13 @@ function hasCycle(nodes) {
|
|
|
3037
3147
|
// src/utils/async.ts
|
|
3038
3148
|
function awaitWithSignal(promise, signal) {
|
|
3039
3149
|
signal.throwIfAborted();
|
|
3040
|
-
return new Promise((
|
|
3150
|
+
return new Promise((resolve25, reject) => {
|
|
3041
3151
|
const onAbort = () => reject(signal.reason);
|
|
3042
3152
|
signal.addEventListener("abort", onAbort, { once: true });
|
|
3043
3153
|
promise.then(
|
|
3044
3154
|
(value) => {
|
|
3045
3155
|
signal.removeEventListener("abort", onAbort);
|
|
3046
|
-
|
|
3156
|
+
resolve25(value);
|
|
3047
3157
|
},
|
|
3048
3158
|
(error) => {
|
|
3049
3159
|
signal.removeEventListener("abort", onAbort);
|
|
@@ -3591,6 +3701,7 @@ var ContextManager = class _ContextManager {
|
|
|
3591
3701
|
#system;
|
|
3592
3702
|
#volatileSystem;
|
|
3593
3703
|
#flavor;
|
|
3704
|
+
#workspaceInstructions;
|
|
3594
3705
|
#memory;
|
|
3595
3706
|
#userMemory;
|
|
3596
3707
|
#compactAtChars;
|
|
@@ -3624,6 +3735,7 @@ var ContextManager = class _ContextManager {
|
|
|
3624
3735
|
this.#system = options.system;
|
|
3625
3736
|
this.#volatileSystem = options.volatileSystem;
|
|
3626
3737
|
this.#flavor = options.flavor;
|
|
3738
|
+
this.#workspaceInstructions = options.workspaceInstructions;
|
|
3627
3739
|
this.#memory = options.memory;
|
|
3628
3740
|
this.#userMemory = options.userMemory;
|
|
3629
3741
|
this.#taskState = options.taskState;
|
|
@@ -3657,6 +3769,7 @@ var ContextManager = class _ContextManager {
|
|
|
3657
3769
|
system: resolveSystemSections(this.#system),
|
|
3658
3770
|
...this.#volatileSystem === void 0 ? {} : { volatileSystem: resolveSystemSections(this.#volatileSystem) },
|
|
3659
3771
|
...this.#flavor === void 0 ? {} : { flavor: this.#flavor },
|
|
3772
|
+
...this.#workspaceInstructions === void 0 ? {} : { workspaceInstructions: this.#workspaceInstructions },
|
|
3660
3773
|
...this.#memory === void 0 ? {} : { memory: this.#memory },
|
|
3661
3774
|
...this.#taskState === void 0 ? {} : { taskState: this.#taskState },
|
|
3662
3775
|
...userMemory === void 0 ? {} : { userMemory },
|
|
@@ -3698,10 +3811,10 @@ var ContextManager = class _ContextManager {
|
|
|
3698
3811
|
messages: providerValidMessages(this.#messages)
|
|
3699
3812
|
};
|
|
3700
3813
|
}
|
|
3701
|
-
restore(
|
|
3702
|
-
const messages = providerValidMessages(
|
|
3703
|
-
const legacySummary =
|
|
3704
|
-
this.#compact =
|
|
3814
|
+
restore(snapshot4) {
|
|
3815
|
+
const messages = providerValidMessages(snapshot4.messages);
|
|
3816
|
+
const legacySummary = snapshot4.summary?.role === "system" && snapshot4.summary.content.startsWith("Conversation summary\n") ? snapshot4.summary.content.slice("Conversation summary\n".length) : void 0;
|
|
3817
|
+
this.#compact = snapshot4.compact === void 0 ? legacySummary === void 0 ? void 0 : { summary: legacySummary, compactedAt: (/* @__PURE__ */ new Date(0)).toISOString() } : { ...snapshot4.compact };
|
|
3705
3818
|
this.#messages = messages.map((message2) => message2.role === "tool" ? { ...message2, content: truncateToolOutput(message2.content, this.#toolOutputChars) } : cloneMessage2(message2));
|
|
3706
3819
|
this.#lastRecordedInputTokens = void 0;
|
|
3707
3820
|
this.#estimatedTokensAtLastRecordedUsage = void 0;
|
|
@@ -3867,6 +3980,12 @@ var ContextManager = class _ContextManager {
|
|
|
3867
3980
|
${this.#flavor}`,
|
|
3868
3981
|
cacheBreakpoint: true
|
|
3869
3982
|
}],
|
|
3983
|
+
...this.#workspaceInstructions === void 0 ? [] : [{
|
|
3984
|
+
role: "system",
|
|
3985
|
+
content: `Workspace instructions
|
|
3986
|
+
${this.#workspaceInstructions}`,
|
|
3987
|
+
cacheBreakpoint: true
|
|
3988
|
+
}],
|
|
3870
3989
|
// Stable user preferences close the cacheable prefix.
|
|
3871
3990
|
...userMemory === void 0 ? [] : [{ role: "system", content: `User memory
|
|
3872
3991
|
${userMemory}` }]
|
|
@@ -3969,6 +4088,125 @@ function providerValidMessages(input) {
|
|
|
3969
4088
|
return output;
|
|
3970
4089
|
}
|
|
3971
4090
|
|
|
4091
|
+
// src/context/workspace-instructions.ts
|
|
4092
|
+
import { createHash as createHash2 } from "crypto";
|
|
4093
|
+
import { lstat as lstat3, readFile as readFile4, realpath as realpath2 } from "fs/promises";
|
|
4094
|
+
import { dirname as dirname7, isAbsolute as isAbsolute4, relative as relative6, resolve as resolve6, sep as sep6 } from "path";
|
|
4095
|
+
var NAMES = ["AGENTS.md", "CLAUDE.md", "AGENTS.local.md", "CLAUDE.local.md"];
|
|
4096
|
+
var ROOT_NAMES = ["AGENTS.md", "CLAUDE.md", "AGENTS.local.md", "CLAUDE.local.md"];
|
|
4097
|
+
var WorkspaceInstructions = class {
|
|
4098
|
+
#root;
|
|
4099
|
+
#maxFileBytes;
|
|
4100
|
+
#maxTotalBytes;
|
|
4101
|
+
#seen = /* @__PURE__ */ new Map();
|
|
4102
|
+
constructor(workspace, options = {}) {
|
|
4103
|
+
this.#root = resolve6(workspace);
|
|
4104
|
+
this.#maxFileBytes = options.maxFileBytes ?? 64 * 1024;
|
|
4105
|
+
this.#maxTotalBytes = options.maxTotalBytes ?? 256 * 1024;
|
|
4106
|
+
}
|
|
4107
|
+
async baseline(scope2 = "main") {
|
|
4108
|
+
const documents = await this.#readDirectories([{ path: this.#root, depth: 0 }], ROOT_NAMES);
|
|
4109
|
+
return this.#format(this.#newOrChanged(documents, scope2));
|
|
4110
|
+
}
|
|
4111
|
+
async discover(paths, scope2 = "main") {
|
|
4112
|
+
const directories = /* @__PURE__ */ new Map();
|
|
4113
|
+
for (const path of paths) {
|
|
4114
|
+
const absolute = resolve6(this.#root, path);
|
|
4115
|
+
if (!within(this.#root, absolute)) continue;
|
|
4116
|
+
const targetDirectory = await directoryForPath(absolute);
|
|
4117
|
+
let current = targetDirectory;
|
|
4118
|
+
while (within(this.#root, current)) {
|
|
4119
|
+
const depth = depthOf(this.#root, current);
|
|
4120
|
+
directories.set(current, depth);
|
|
4121
|
+
if (current === this.#root) break;
|
|
4122
|
+
current = dirname7(current);
|
|
4123
|
+
}
|
|
4124
|
+
}
|
|
4125
|
+
const ordered = [...directories].map(([path, depth]) => ({ path, depth })).sort((a, b) => a.depth - b.depth || a.path.localeCompare(b.path));
|
|
4126
|
+
const changed = this.#newOrChanged(await this.#readDirectories(ordered, NAMES), scope2);
|
|
4127
|
+
const formatted = this.#format(changed);
|
|
4128
|
+
return formatted === "" ? [] : [formatted];
|
|
4129
|
+
}
|
|
4130
|
+
#newOrChanged(documents, scope2) {
|
|
4131
|
+
const seen = this.#seen.get(scope2) ?? /* @__PURE__ */ new Map();
|
|
4132
|
+
this.#seen.set(scope2, seen);
|
|
4133
|
+
const changed = [];
|
|
4134
|
+
for (const document of documents) {
|
|
4135
|
+
if (seen.get(document.path) === document.digest) continue;
|
|
4136
|
+
seen.set(document.path, document.digest);
|
|
4137
|
+
changed.push(document);
|
|
4138
|
+
}
|
|
4139
|
+
return changed;
|
|
4140
|
+
}
|
|
4141
|
+
async #readDirectories(directories, names) {
|
|
4142
|
+
const result = [];
|
|
4143
|
+
let physicalRoot;
|
|
4144
|
+
try {
|
|
4145
|
+
physicalRoot = await realpath2(this.#root);
|
|
4146
|
+
} catch {
|
|
4147
|
+
return result;
|
|
4148
|
+
}
|
|
4149
|
+
for (const directory of directories) {
|
|
4150
|
+
for (const name of names) {
|
|
4151
|
+
const path = resolve6(directory.path, name);
|
|
4152
|
+
try {
|
|
4153
|
+
const info = await lstat3(path);
|
|
4154
|
+
if (!info.isFile() && !info.isSymbolicLink()) continue;
|
|
4155
|
+
const physical = await realpath2(path);
|
|
4156
|
+
if (!within(physicalRoot, physical)) continue;
|
|
4157
|
+
const bytes = await readFile4(path);
|
|
4158
|
+
const clipped = bytes.subarray(0, this.#maxFileBytes);
|
|
4159
|
+
const suffix = bytes.length > clipped.length ? `
|
|
4160
|
+
|
|
4161
|
+
[Instruction truncated at ${this.#maxFileBytes} bytes]` : "";
|
|
4162
|
+
const content = clipped.toString("utf8") + suffix;
|
|
4163
|
+
result.push({
|
|
4164
|
+
path,
|
|
4165
|
+
depth: directory.depth,
|
|
4166
|
+
content,
|
|
4167
|
+
digest: createHash2("sha256").update(bytes).digest("hex")
|
|
4168
|
+
});
|
|
4169
|
+
} catch {
|
|
4170
|
+
}
|
|
4171
|
+
}
|
|
4172
|
+
}
|
|
4173
|
+
return result;
|
|
4174
|
+
}
|
|
4175
|
+
#format(documents) {
|
|
4176
|
+
if (documents.length === 0) return "";
|
|
4177
|
+
const selected = [];
|
|
4178
|
+
let bytes = 0;
|
|
4179
|
+
for (const document of [...documents].sort((a, b) => b.depth - a.depth)) {
|
|
4180
|
+
const size = Buffer.byteLength(document.content);
|
|
4181
|
+
if (bytes + size > this.#maxTotalBytes) continue;
|
|
4182
|
+
bytes += size;
|
|
4183
|
+
selected.push(document);
|
|
4184
|
+
}
|
|
4185
|
+
selected.sort((a, b) => a.depth - b.depth || a.path.localeCompare(b.path));
|
|
4186
|
+
return selected.map((document) => {
|
|
4187
|
+
const display = relative6(this.#root, document.path) || document.path;
|
|
4188
|
+
return `<workspace-instructions path="${display}">
|
|
4189
|
+
${document.content}
|
|
4190
|
+
</workspace-instructions>`;
|
|
4191
|
+
}).join("\n\n");
|
|
4192
|
+
}
|
|
4193
|
+
};
|
|
4194
|
+
async function directoryForPath(path) {
|
|
4195
|
+
try {
|
|
4196
|
+
return (await lstat3(path)).isDirectory() ? path : dirname7(path);
|
|
4197
|
+
} catch {
|
|
4198
|
+
return dirname7(path);
|
|
4199
|
+
}
|
|
4200
|
+
}
|
|
4201
|
+
function depthOf(root, path) {
|
|
4202
|
+
const delta = relative6(root, path);
|
|
4203
|
+
return delta === "" ? 0 : delta.split(sep6).length;
|
|
4204
|
+
}
|
|
4205
|
+
function within(root, candidate) {
|
|
4206
|
+
const delta = relative6(root, candidate);
|
|
4207
|
+
return delta === "" || !delta.startsWith(`..${sep6}`) && delta !== ".." && !isAbsolute4(delta);
|
|
4208
|
+
}
|
|
4209
|
+
|
|
3972
4210
|
// src/context/summarizer.ts
|
|
3973
4211
|
async function summarizeWithModel(options) {
|
|
3974
4212
|
const maxAttempts = options.maxPromptTooLongAttempts ?? 3;
|
|
@@ -4244,10 +4482,10 @@ function sanitize(message2, secrets) {
|
|
|
4244
4482
|
}
|
|
4245
4483
|
function waitForRetry(delayMs, signal) {
|
|
4246
4484
|
if (signal?.aborted) return Promise.reject(signal.reason);
|
|
4247
|
-
return new Promise((
|
|
4485
|
+
return new Promise((resolve25, reject) => {
|
|
4248
4486
|
const timer = setTimeout(() => {
|
|
4249
4487
|
signal?.removeEventListener("abort", onAbort);
|
|
4250
|
-
|
|
4488
|
+
resolve25();
|
|
4251
4489
|
}, delayMs);
|
|
4252
4490
|
const onAbort = () => {
|
|
4253
4491
|
clearTimeout(timer);
|
|
@@ -4260,6 +4498,7 @@ function waitForRetry(delayMs, signal) {
|
|
|
4260
4498
|
|
|
4261
4499
|
// src/agent/loop.ts
|
|
4262
4500
|
var DEFAULT_MAX_ITERATIONS = 40;
|
|
4501
|
+
var D2C_MAX_ITERATION_EXTENSIONS = 10;
|
|
4263
4502
|
var DEFAULT_MODEL_ATTEMPTS = 3;
|
|
4264
4503
|
var FALLBACK_MODEL_ATTEMPTS = 2;
|
|
4265
4504
|
var RETRY_BASE_DELAY_MS = 1e3;
|
|
@@ -4278,6 +4517,7 @@ function envMaxIterations() {
|
|
|
4278
4517
|
}
|
|
4279
4518
|
var AgentLoop = class {
|
|
4280
4519
|
#options;
|
|
4520
|
+
#iterationLimitMode = "standard";
|
|
4281
4521
|
constructor(options) {
|
|
4282
4522
|
const envOverride = envMaxIterations();
|
|
4283
4523
|
const maxIterations = envOverride ?? options.maxIterations ?? DEFAULT_MAX_ITERATIONS;
|
|
@@ -4300,6 +4540,9 @@ var AgentLoop = class {
|
|
|
4300
4540
|
get modelId() {
|
|
4301
4541
|
return this.#options.modelId;
|
|
4302
4542
|
}
|
|
4543
|
+
get iterationLimitMode() {
|
|
4544
|
+
return this.#iterationLimitMode;
|
|
4545
|
+
}
|
|
4303
4546
|
setModel(modelId) {
|
|
4304
4547
|
this.#options.registry.get(modelId);
|
|
4305
4548
|
this.#options.modelId = modelId;
|
|
@@ -4308,6 +4551,10 @@ var AgentLoop = class {
|
|
|
4308
4551
|
this.#options.registry.get(modelId);
|
|
4309
4552
|
this.#options.fallbackModelId = modelId;
|
|
4310
4553
|
}
|
|
4554
|
+
/** D2C keeps a hard cap, but may automatically expand it up to ten times. */
|
|
4555
|
+
setIterationLimitMode(mode) {
|
|
4556
|
+
this.#iterationLimitMode = mode;
|
|
4557
|
+
}
|
|
4311
4558
|
async *run(request) {
|
|
4312
4559
|
this.#options.context.append(request.initialUserMessage ?? { role: "user", content: request.prompt });
|
|
4313
4560
|
let totalInputTokens = 0;
|
|
@@ -4317,7 +4564,10 @@ var AgentLoop = class {
|
|
|
4317
4564
|
let hasCacheData = false;
|
|
4318
4565
|
let accumulatedText = "";
|
|
4319
4566
|
let modelInvocation = 0;
|
|
4567
|
+
const deliverables = /* @__PURE__ */ new Map();
|
|
4320
4568
|
let maxIterations = this.#options.maxIterations;
|
|
4569
|
+
const iterationLimitMode = this.#iterationLimitMode;
|
|
4570
|
+
const maxExtensions = iterationLimitMode === "d2c" ? D2C_MAX_ITERATION_EXTENSIONS : this.#options.maxExtensions;
|
|
4321
4571
|
let extensions = 0;
|
|
4322
4572
|
let warned = false;
|
|
4323
4573
|
let iteration = 0;
|
|
@@ -4332,11 +4582,12 @@ var AgentLoop = class {
|
|
|
4332
4582
|
const remaining = maxIterations - iteration;
|
|
4333
4583
|
yield {
|
|
4334
4584
|
type: "warning",
|
|
4335
|
-
message: `Approaching iteration limit: ${iteration}/${maxIterations} rounds used. ${remaining} rounds remaining before automatic circuit breaker.`
|
|
4585
|
+
message: iterationLimitMode === "d2c" ? `Approaching D2C base iteration limit: ${iteration}/${maxIterations} rounds used. ${remaining} rounds remain before automatic expansion (up to ${maxExtensions} expansions).` : `Approaching iteration limit: ${iteration}/${maxIterations} rounds used. ${remaining} rounds remaining before automatic circuit breaker.`
|
|
4336
4586
|
};
|
|
4337
4587
|
}
|
|
4338
4588
|
if (iteration >= maxIterations) {
|
|
4339
|
-
|
|
4589
|
+
const canAutoExtend = extensions < maxExtensions && (iterationLimitMode === "d2c" || this.#options.hasActiveProgress?.() === true);
|
|
4590
|
+
if (canAutoExtend) {
|
|
4340
4591
|
const previous = maxIterations;
|
|
4341
4592
|
maxIterations += this.#options.extendIterations;
|
|
4342
4593
|
extensions += 1;
|
|
@@ -4348,7 +4599,7 @@ var AgentLoop = class {
|
|
|
4348
4599
|
};
|
|
4349
4600
|
yield {
|
|
4350
4601
|
type: "warning",
|
|
4351
|
-
message: `Iteration limit ${previous} reached but task progress is active. Auto-extending by ${this.#options.extendIterations} rounds (extension ${extensions}/${
|
|
4602
|
+
message: iterationLimitMode === "d2c" ? `D2C iteration limit ${previous} reached. Auto-expanding by ${this.#options.extendIterations} rounds (expansion ${extensions}/${maxExtensions}).` : `Iteration limit ${previous} reached but task progress is active. Auto-extending by ${this.#options.extendIterations} rounds (extension ${extensions}/${maxExtensions}).`
|
|
4352
4603
|
};
|
|
4353
4604
|
continue;
|
|
4354
4605
|
}
|
|
@@ -4680,6 +4931,7 @@ var AgentLoop = class {
|
|
|
4680
4931
|
} catch {
|
|
4681
4932
|
}
|
|
4682
4933
|
}
|
|
4934
|
+
if (deliverables.size > 0) yield { type: "deliverables", files: [...deliverables.values()] };
|
|
4683
4935
|
yield {
|
|
4684
4936
|
type: "done",
|
|
4685
4937
|
usage: {
|
|
@@ -4708,13 +4960,15 @@ var AgentLoop = class {
|
|
|
4708
4960
|
for (const call of batch) {
|
|
4709
4961
|
const label = this.#options.runtime.label(call);
|
|
4710
4962
|
const hint = this.#options.runtime.hint(call);
|
|
4963
|
+
const presentation = this.#options.runtime.callPresentation(call);
|
|
4711
4964
|
this.#options.hallucinationGuard?.recordToolCall(call.name, call.input, call.id);
|
|
4712
|
-
yield { type: "tool-start", id: call.id, name: call.name, input: call.input, ...label === void 0 ? {} : { label }, ...hint === void 0 ? {} : { hint } };
|
|
4965
|
+
yield { type: "tool-start", id: call.id, name: call.name, input: call.input, ...label === void 0 ? {} : { label }, ...hint === void 0 ? {} : { hint }, ...presentation === void 0 ? {} : { presentation } };
|
|
4713
4966
|
}
|
|
4714
4967
|
const results = await Promise.all(batch.map(async (call) => ({
|
|
4715
4968
|
call,
|
|
4716
4969
|
result: await this.#options.runtime.execute(call, {
|
|
4717
4970
|
agent: this.#options.agent,
|
|
4971
|
+
...this.#options.ownerId === void 0 ? {} : { ownerId: this.#options.ownerId },
|
|
4718
4972
|
...request.signal === void 0 ? {} : { signal: request.signal }
|
|
4719
4973
|
})
|
|
4720
4974
|
})));
|
|
@@ -4751,7 +5005,14 @@ var AgentLoop = class {
|
|
|
4751
5005
|
const endLabel = this.#options.runtime.label(call);
|
|
4752
5006
|
const endHint = this.#options.runtime.hint(call);
|
|
4753
5007
|
this.#options.hallucinationGuard?.recordToolResult(call.name, result, call.id);
|
|
5008
|
+
if (result.ok && result.presentation?.kind === "file-change") {
|
|
5009
|
+
for (const change of [result.presentation, ...result.presentation.relatedChanges ?? []]) {
|
|
5010
|
+
const current = deliverables.get(change.path);
|
|
5011
|
+
deliverables.set(change.path, current === void 0 ? { path: change.path, operation: change.operation, added: change.added, removed: change.removed } : { ...current, operation: change.operation, added: current.added + change.added, removed: current.removed + change.removed });
|
|
5012
|
+
}
|
|
5013
|
+
}
|
|
4754
5014
|
yield { type: "tool-end", id: call.id, name: call.name, result, ...endLabel === void 0 ? {} : { label: endLabel }, ...endHint === void 0 ? {} : { hint: endHint } };
|
|
5015
|
+
for (const context of result.additionalContext ?? []) this.#options.context.append({ role: "system", content: context });
|
|
4755
5016
|
}
|
|
4756
5017
|
if (turnError !== void 0) {
|
|
4757
5018
|
yield { type: "error", error: turnError };
|
|
@@ -4781,7 +5042,7 @@ function parallelReadBatchEnd(calls, start) {
|
|
|
4781
5042
|
return end;
|
|
4782
5043
|
}
|
|
4783
5044
|
function toolResultMessage(toolCallId, result) {
|
|
4784
|
-
return { role: "tool", toolCallId, content: JSON.stringify(result.ok ? result.output : { error: result.error }) ?? "null" };
|
|
5045
|
+
return { role: "tool", toolCallId, content: result.ok && result.content !== void 0 ? result.content : JSON.stringify(result.ok ? result.output : { error: result.error }) ?? "null" };
|
|
4785
5046
|
}
|
|
4786
5047
|
function abortMessage(signal) {
|
|
4787
5048
|
return signal.reason instanceof Error ? signal.reason.message : "Agent run cancelled";
|
|
@@ -4795,10 +5056,10 @@ function serializeToolInput(input) {
|
|
|
4795
5056
|
}
|
|
4796
5057
|
function waitForRetry2(delayMs, signal) {
|
|
4797
5058
|
if (signal?.aborted) return Promise.reject(signal.reason);
|
|
4798
|
-
return new Promise((
|
|
5059
|
+
return new Promise((resolve25, reject) => {
|
|
4799
5060
|
const timer = setTimeout(() => {
|
|
4800
5061
|
signal?.removeEventListener("abort", onAbort);
|
|
4801
|
-
|
|
5062
|
+
resolve25();
|
|
4802
5063
|
}, delayMs);
|
|
4803
5064
|
const onAbort = () => {
|
|
4804
5065
|
clearTimeout(timer);
|
|
@@ -4876,7 +5137,7 @@ function redactClassifierText(value) {
|
|
|
4876
5137
|
// src/tools/runtime.ts
|
|
4877
5138
|
import { randomUUID as randomUUID3 } from "crypto";
|
|
4878
5139
|
import { mkdir as mkdir4, writeFile as writeFile4 } from "fs/promises";
|
|
4879
|
-
import { basename as basename2, join as join5, resolve as
|
|
5140
|
+
import { basename as basename2, join as join5, resolve as resolve7 } from "path";
|
|
4880
5141
|
import { z as z8 } from "zod";
|
|
4881
5142
|
|
|
4882
5143
|
// src/tools/types.ts
|
|
@@ -4914,6 +5175,7 @@ var ToolRuntime = class {
|
|
|
4914
5175
|
#disposeSchemas;
|
|
4915
5176
|
#workspace;
|
|
4916
5177
|
#outputLimits;
|
|
5178
|
+
#afterSuccess;
|
|
4917
5179
|
#turnOutputChars = 0;
|
|
4918
5180
|
#disposed = false;
|
|
4919
5181
|
constructor(options) {
|
|
@@ -4923,8 +5185,9 @@ var ToolRuntime = class {
|
|
|
4923
5185
|
this.#approve = options.approve;
|
|
4924
5186
|
this.#classify = options.classify;
|
|
4925
5187
|
this.#alwaysAllowed = new Set(options.alwaysAllowed ?? []);
|
|
4926
|
-
this.#workspace =
|
|
5188
|
+
this.#workspace = resolve7(options.workspace ?? process.cwd());
|
|
4927
5189
|
this.#outputLimits = { ...DEFAULT_TOOL_OUTPUT_LIMITS, ...options.outputLimits };
|
|
5190
|
+
this.#afterSuccess = options.afterSuccess;
|
|
4928
5191
|
validateOutputLimit("perToolChars", this.#outputLimits.perToolChars);
|
|
4929
5192
|
validateOutputLimit("perTurnChars", this.#outputLimits.perTurnChars);
|
|
4930
5193
|
this.#disposeSchemas = [
|
|
@@ -5034,7 +5297,7 @@ var ToolRuntime = class {
|
|
|
5034
5297
|
if (permission.decision === "deny") {
|
|
5035
5298
|
return this.#fail(tool.name, input, context.agent, "permission_denied", permission.reason ?? "Tool use denied");
|
|
5036
5299
|
}
|
|
5037
|
-
if (permission.decision === "ask" && context.agent === "main" && this.#permissions.mode === "auto" && this.#classify !== void 0) {
|
|
5300
|
+
if (permission.decision === "ask" && context.agent === "main" && this.#permissions.mode === "auto" && this.#permissions.profile === "standard" && this.#classify !== void 0) {
|
|
5038
5301
|
try {
|
|
5039
5302
|
const classified = await this.#classify(request, signal);
|
|
5040
5303
|
if (classified.decision === "deny") {
|
|
@@ -5051,6 +5314,7 @@ var ToolRuntime = class {
|
|
|
5051
5314
|
const sessionAllowed = this.#alwaysAllowed.has(getToolCategory(tool.name));
|
|
5052
5315
|
if (!sessionAllowed && (pre.decision === "ask" || permission.decision === "ask")) {
|
|
5053
5316
|
const reason = [pre.reason, permission.reason].filter((value) => value !== void 0).join("\n") || "Approval required";
|
|
5317
|
+
const allowAlways = permission.allowAlways !== false;
|
|
5054
5318
|
const requestDecision = await this.#hooks.emit({
|
|
5055
5319
|
version: 1,
|
|
5056
5320
|
type: "PermissionRequest",
|
|
@@ -5066,7 +5330,7 @@ var ToolRuntime = class {
|
|
|
5066
5330
|
const ctx = requestDecision.additionalContext ?? "";
|
|
5067
5331
|
if (ctx.includes("codeisland:allow-all")) {
|
|
5068
5332
|
const category = getToolCategory(tool.name);
|
|
5069
|
-
if (category !== "destructive") this.#alwaysAllowed.add(category);
|
|
5333
|
+
if (allowAlways && category !== "destructive") this.#alwaysAllowed.add(category);
|
|
5070
5334
|
}
|
|
5071
5335
|
} else if (context.agent !== "main" && this.#permissions.mode !== "bubble") {
|
|
5072
5336
|
return this.#fail(tool.name, input, context.agent, "approval_required", reason);
|
|
@@ -5074,29 +5338,46 @@ var ToolRuntime = class {
|
|
|
5074
5338
|
throw signal.reason;
|
|
5075
5339
|
} else {
|
|
5076
5340
|
const category = getToolCategory(tool.name);
|
|
5077
|
-
if (this.#
|
|
5341
|
+
if (allowAlways && this.#alwaysAllowed.has(category)) {
|
|
5342
|
+
} else if (this.#approve === void 0) {
|
|
5078
5343
|
return this.#fail(tool.name, input, context.agent, "permission_denied", reason);
|
|
5079
5344
|
} else {
|
|
5080
|
-
const decision = await this.#approve({ ...request, reason }, signal);
|
|
5345
|
+
const decision = await this.#approve({ ...request, reason, ...allowAlways ? {} : { allowAlways: false } }, signal);
|
|
5081
5346
|
if (decision === "deny") {
|
|
5082
5347
|
return this.#fail(tool.name, input, context.agent, "user_denied", reason);
|
|
5083
5348
|
}
|
|
5084
|
-
if (decision === "always" && category !== "destructive") {
|
|
5349
|
+
if (decision === "always" && allowAlways && category !== "destructive") {
|
|
5085
5350
|
this.#alwaysAllowed.add(category);
|
|
5086
5351
|
}
|
|
5087
5352
|
}
|
|
5088
5353
|
}
|
|
5089
5354
|
}
|
|
5090
5355
|
if (signal.aborted) throw signal.reason;
|
|
5091
|
-
const
|
|
5092
|
-
|
|
5093
|
-
|
|
5356
|
+
const executed = await tool.execute(input, signal, context);
|
|
5357
|
+
let rawOutput;
|
|
5358
|
+
try {
|
|
5359
|
+
rawOutput = tool.outputSchema === void 0 ? executed : tool.outputSchema.parse(executed);
|
|
5360
|
+
} catch (error) {
|
|
5361
|
+
return this.#fail(tool.name, input, context.agent, "invalid_output", message(error));
|
|
5362
|
+
}
|
|
5363
|
+
const declaredPresentation = tool.presentResult?.(rawOutput, input);
|
|
5364
|
+
const presentation = declaredPresentation ?? getToolPresentation(rawOutput);
|
|
5365
|
+
const rendered = tool.renderForModel?.(rawOutput, input);
|
|
5366
|
+
const output = rendered === void 0 ? await this.#limitOutput(tool.name, rawOutput) : rawOutput;
|
|
5367
|
+
const content = rendered === void 0 ? void 0 : await this.#limitText(tool.name, rendered);
|
|
5368
|
+
const additionalContext = await this.#afterSuccess?.(tool.name, tool.paths(input), input, rawOutput, context);
|
|
5094
5369
|
await this.#hooks.emit({
|
|
5095
5370
|
version: 1,
|
|
5096
5371
|
type: "PostToolUse",
|
|
5097
5372
|
payload: { tool: tool.name, input, agent: context.agent, output }
|
|
5098
5373
|
});
|
|
5099
|
-
return {
|
|
5374
|
+
return {
|
|
5375
|
+
ok: true,
|
|
5376
|
+
output,
|
|
5377
|
+
...content === void 0 ? {} : { content },
|
|
5378
|
+
...presentation === void 0 ? {} : { presentation },
|
|
5379
|
+
...additionalContext === void 0 || additionalContext.length === 0 ? {} : { additionalContext }
|
|
5380
|
+
};
|
|
5100
5381
|
} catch (error) {
|
|
5101
5382
|
return this.#fail(tool.name, input, context.agent, "tool_error", message(error));
|
|
5102
5383
|
}
|
|
@@ -5133,6 +5414,19 @@ var ToolRuntime = class {
|
|
|
5133
5414
|
savedTo
|
|
5134
5415
|
};
|
|
5135
5416
|
}
|
|
5417
|
+
callPresentation(call) {
|
|
5418
|
+
const tool = this.#tools.get(call.name);
|
|
5419
|
+
if (tool?.presentCall === void 0) return void 0;
|
|
5420
|
+
try {
|
|
5421
|
+
return tool.presentCall(tool.inputSchema.parse(call.input));
|
|
5422
|
+
} catch {
|
|
5423
|
+
return void 0;
|
|
5424
|
+
}
|
|
5425
|
+
}
|
|
5426
|
+
async #limitText(tool, content) {
|
|
5427
|
+
const limited = await this.#limitOutput(tool, content);
|
|
5428
|
+
return typeof limited === "string" ? limited : serializeToolOutput(limited);
|
|
5429
|
+
}
|
|
5136
5430
|
async #fail(tool, input, agent, code, errorMessage) {
|
|
5137
5431
|
const result = { ok: false, error: { code, message: errorMessage } };
|
|
5138
5432
|
try {
|
|
@@ -5169,6 +5463,7 @@ var LocalHarness = class {
|
|
|
5169
5463
|
#options;
|
|
5170
5464
|
#subagentModelId;
|
|
5171
5465
|
#mainPermissions;
|
|
5466
|
+
#permissionProfile = "standard";
|
|
5172
5467
|
#contexts = /* @__PURE__ */ new WeakSet();
|
|
5173
5468
|
#children = /* @__PURE__ */ new Set();
|
|
5174
5469
|
#mainDefinitions;
|
|
@@ -5199,6 +5494,9 @@ var LocalHarness = class {
|
|
|
5199
5494
|
get permissionMode() {
|
|
5200
5495
|
return this.#mainPermissions.mode;
|
|
5201
5496
|
}
|
|
5497
|
+
get permissionProfile() {
|
|
5498
|
+
return this.#permissionProfile;
|
|
5499
|
+
}
|
|
5202
5500
|
setModel(role, modelId) {
|
|
5203
5501
|
this.#options.registry.get(modelId);
|
|
5204
5502
|
if (role === "main") this.main.loop.setModel(modelId);
|
|
@@ -5210,6 +5508,12 @@ var LocalHarness = class {
|
|
|
5210
5508
|
setPermissionMode(mode) {
|
|
5211
5509
|
this.#mainPermissions.setMode(mode);
|
|
5212
5510
|
}
|
|
5511
|
+
setPermissionProfile(profile) {
|
|
5512
|
+
this.#permissionProfile = profile;
|
|
5513
|
+
this.#mainPermissions.setProfile(profile);
|
|
5514
|
+
this.main.loop.setIterationLimitMode(profile === "d2c" ? "d2c" : "standard");
|
|
5515
|
+
for (const child of this.#children) child.loop.setIterationLimitMode(profile === "d2c" ? "d2c" : "standard");
|
|
5516
|
+
}
|
|
5213
5517
|
replaceMainTools(definitions) {
|
|
5214
5518
|
if (this.#disposed) throw new Error("LocalHarness is disposed");
|
|
5215
5519
|
const nextDefinitions = definitions.filter((tool) => tool.agents === void 0 || tool.agents.includes("main"));
|
|
@@ -5224,7 +5528,7 @@ var LocalHarness = class {
|
|
|
5224
5528
|
const tools = this.#toolsForAgent("subagent").filter((tool) => !MAIN_TASK_TOOL_NAMES.has(tool.name));
|
|
5225
5529
|
const context = this.#options.createContext("subagent", tools, this.#subagentModelId, parentContext);
|
|
5226
5530
|
this.#claimContext(context);
|
|
5227
|
-
const profile = this.#createProfile(this.#subagentModelId, tools, "subagent", context, this.#options.approve);
|
|
5531
|
+
const profile = this.#createProfile(this.#subagentModelId, tools, "subagent", context, this.#options.approve, void 0, `subagent:${task.id}`);
|
|
5228
5532
|
let disposed = false;
|
|
5229
5533
|
const child = {
|
|
5230
5534
|
...profile,
|
|
@@ -5270,9 +5574,11 @@ var LocalHarness = class {
|
|
|
5270
5574
|
#toolsForAgent(agent) {
|
|
5271
5575
|
return this.#options.tools.filter((tool) => tool.agents === void 0 || tool.agents.includes(agent));
|
|
5272
5576
|
}
|
|
5273
|
-
#createProfile(modelId, definitions, agent, context, approve, fallbackModelId) {
|
|
5577
|
+
#createProfile(modelId, definitions, agent, context, approve, fallbackModelId, ownerId = agent) {
|
|
5274
5578
|
const permissions = new PermissionEngine({
|
|
5275
5579
|
workspace: this.#options.workspace,
|
|
5580
|
+
...this.#options.afterToolSuccess === void 0 ? {} : { afterSuccess: this.#options.afterToolSuccess },
|
|
5581
|
+
profile: this.#permissionProfile,
|
|
5276
5582
|
mode: this.#options.loopMode ? "bypassPermissions" : agent === "subagent" ? this.#mainPermissions.mode === "plan" ? "plan" : "bubble" : this.#options.permissionMode ?? "default"
|
|
5277
5583
|
});
|
|
5278
5584
|
if (agent === "main") this.#mainPermissions = permissions;
|
|
@@ -5302,10 +5608,12 @@ var LocalHarness = class {
|
|
|
5302
5608
|
hooks: this.#options.hooks,
|
|
5303
5609
|
tools,
|
|
5304
5610
|
agent,
|
|
5611
|
+
ownerId,
|
|
5305
5612
|
...maxIterations === void 0 ? {} : { maxIterations },
|
|
5306
5613
|
...isMain && this.#options.hasActiveProgress !== void 0 ? { hasActiveProgress: this.#options.hasActiveProgress } : {},
|
|
5307
5614
|
...isMain && this.#options.hallucinationGuard !== void 0 ? { hallucinationGuard: this.#options.hallucinationGuard } : {}
|
|
5308
5615
|
});
|
|
5616
|
+
loop.setIterationLimitMode(this.#permissionProfile === "d2c" ? "d2c" : "standard");
|
|
5309
5617
|
return { get modelId() {
|
|
5310
5618
|
return loop.modelId;
|
|
5311
5619
|
}, context, runtime, tools, loop };
|
|
@@ -5475,7 +5783,7 @@ var HookBus = class {
|
|
|
5475
5783
|
}
|
|
5476
5784
|
};
|
|
5477
5785
|
function runShellHandler(descriptor, event, signal) {
|
|
5478
|
-
return new Promise((
|
|
5786
|
+
return new Promise((resolve25, reject) => {
|
|
5479
5787
|
const child = spawn3(descriptor.command, [...descriptor.args ?? []], {
|
|
5480
5788
|
env: { ...process.env, ...descriptor.env },
|
|
5481
5789
|
shell: false,
|
|
@@ -5518,7 +5826,7 @@ function runShellHandler(descriptor, event, signal) {
|
|
|
5518
5826
|
return;
|
|
5519
5827
|
}
|
|
5520
5828
|
try {
|
|
5521
|
-
|
|
5829
|
+
resolve25(HookDecisionSchema.parse(JSON.parse(stdout)));
|
|
5522
5830
|
} catch (error) {
|
|
5523
5831
|
reject(error);
|
|
5524
5832
|
}
|
|
@@ -6211,9 +6519,9 @@ function extractJson(text) {
|
|
|
6211
6519
|
}
|
|
6212
6520
|
|
|
6213
6521
|
// src/goal/classifier.ts
|
|
6214
|
-
import { createHash as
|
|
6215
|
-
import { readFile as
|
|
6216
|
-
import { join as join6, relative as
|
|
6522
|
+
import { createHash as createHash3 } from "crypto";
|
|
6523
|
+
import { readFile as readFile5, readdir as readdir3 } from "fs/promises";
|
|
6524
|
+
import { join as join6, relative as relative7 } from "path";
|
|
6217
6525
|
function buildSkepticPrompt(evidence, plan, index) {
|
|
6218
6526
|
const criteriaList = plan.criteria.map((c) => ` ${c.id}. [${c.type}] ${c.description}`).join("\n");
|
|
6219
6527
|
return [
|
|
@@ -6377,19 +6685,19 @@ function deduplicateGaps(gaps) {
|
|
|
6377
6685
|
}
|
|
6378
6686
|
function computeFingerprint(gaps) {
|
|
6379
6687
|
const content = gaps.map((g) => `${g.criterion}:${g.description}`).sort().join("\n");
|
|
6380
|
-
return
|
|
6688
|
+
return createHash3("sha256").update(content).digest("hex").slice(0, 16);
|
|
6381
6689
|
}
|
|
6382
6690
|
async function collectEvidence(workspace, objective, finalResponse, priorGaps) {
|
|
6383
6691
|
const changedFiles = await collectChangedFiles(workspace);
|
|
6384
6692
|
const planPath = join6(workspace, ".flavor", "goal-plan.md");
|
|
6385
6693
|
let planFile = null;
|
|
6386
6694
|
try {
|
|
6387
|
-
planFile = await
|
|
6695
|
+
planFile = await readFile5(planPath, "utf8");
|
|
6388
6696
|
} catch {
|
|
6389
6697
|
}
|
|
6390
6698
|
return {
|
|
6391
6699
|
objective,
|
|
6392
|
-
changedFiles: changedFiles.map((f) =>
|
|
6700
|
+
changedFiles: changedFiles.map((f) => relative7(workspace, f)),
|
|
6393
6701
|
planFile,
|
|
6394
6702
|
finalResponse,
|
|
6395
6703
|
priorGaps
|
|
@@ -6407,7 +6715,7 @@ async function collectChangedFiles(workspace) {
|
|
|
6407
6715
|
}
|
|
6408
6716
|
for (const entry2 of entries) {
|
|
6409
6717
|
const full = join6(dir, entry2.name);
|
|
6410
|
-
const rel =
|
|
6718
|
+
const rel = relative7(workspace, full);
|
|
6411
6719
|
if (entry2.name === ".git" || entry2.name === "node_modules" || entry2.name === ".flavor" || entry2.name.startsWith(".")) continue;
|
|
6412
6720
|
if (entry2.isDirectory()) {
|
|
6413
6721
|
await walk(full);
|
|
@@ -6681,21 +6989,21 @@ function formatPriorGaps(gaps) {
|
|
|
6681
6989
|
// src/goal/store.ts
|
|
6682
6990
|
import { constants } from "fs";
|
|
6683
6991
|
import { randomUUID as randomUUID4 } from "crypto";
|
|
6684
|
-
import { lstat as
|
|
6685
|
-
import { dirname as
|
|
6992
|
+
import { lstat as lstat4, mkdir as mkdir6, open as open3, rename as rename3, rm as rm2 } from "fs/promises";
|
|
6993
|
+
import { dirname as dirname8, join as join8, relative as relative8, resolve as resolve8, sep as sep7 } from "path";
|
|
6686
6994
|
var DEFAULT_MAX_BYTES = 1024 * 1024;
|
|
6687
6995
|
var GoalStore = class {
|
|
6688
6996
|
#workspace;
|
|
6689
6997
|
#root;
|
|
6690
6998
|
#maxBytes;
|
|
6691
6999
|
constructor(options) {
|
|
6692
|
-
this.#workspace =
|
|
7000
|
+
this.#workspace = resolve8(options.workspace);
|
|
6693
7001
|
this.#root = join8(this.#workspace, ".flavor", "goals");
|
|
6694
7002
|
this.#maxBytes = options.maxBytes ?? DEFAULT_MAX_BYTES;
|
|
6695
7003
|
}
|
|
6696
7004
|
async save(input) {
|
|
6697
7005
|
const state = GoalStateSchema.parse(input);
|
|
6698
|
-
await assertNoSymlink(this.#workspace,
|
|
7006
|
+
await assertNoSymlink(this.#workspace, dirname8(this.#root));
|
|
6699
7007
|
await mkdir6(this.#root, { recursive: true, mode: 448 });
|
|
6700
7008
|
await assertNoSymlink(this.#workspace, this.#root);
|
|
6701
7009
|
const target = join8(this.#root, `${state.id}.json`);
|
|
@@ -6720,20 +7028,20 @@ var GoalStore = class {
|
|
|
6720
7028
|
}
|
|
6721
7029
|
};
|
|
6722
7030
|
async function assertNoSymlink(root, target) {
|
|
6723
|
-
let cursor =
|
|
6724
|
-
const stop =
|
|
6725
|
-
const value =
|
|
6726
|
-
if (value === ".." || value.startsWith(`..${
|
|
7031
|
+
let cursor = resolve8(target);
|
|
7032
|
+
const stop = resolve8(root);
|
|
7033
|
+
const value = relative8(stop, cursor);
|
|
7034
|
+
if (value === ".." || value.startsWith(`..${sep7}`) || value.includes(":")) {
|
|
6727
7035
|
throw new Error("Goal path escapes workspace");
|
|
6728
7036
|
}
|
|
6729
7037
|
const chain = [];
|
|
6730
7038
|
while (cursor !== stop) {
|
|
6731
7039
|
chain.push(cursor);
|
|
6732
|
-
cursor =
|
|
7040
|
+
cursor = dirname8(cursor);
|
|
6733
7041
|
}
|
|
6734
7042
|
for (const path of chain.reverse()) {
|
|
6735
7043
|
try {
|
|
6736
|
-
if ((await
|
|
7044
|
+
if ((await lstat4(path)).isSymbolicLink()) throw new Error(`Goal path contains a symbolic link: ${path}`);
|
|
6737
7045
|
} catch (error) {
|
|
6738
7046
|
if (!isCode3(error, "ENOENT")) throw error;
|
|
6739
7047
|
}
|
|
@@ -6744,8 +7052,8 @@ function isCode3(error, code) {
|
|
|
6744
7052
|
}
|
|
6745
7053
|
|
|
6746
7054
|
// src/loop/isolation.ts
|
|
6747
|
-
import { appendFile, mkdir as mkdir7, readFile as
|
|
6748
|
-
import { join as join9, resolve as
|
|
7055
|
+
import { appendFile, mkdir as mkdir7, readFile as readFile6 } from "fs/promises";
|
|
7056
|
+
import { join as join9, resolve as resolve9 } from "path";
|
|
6749
7057
|
var CODE_CHANGE = /\b(?:fix|implement|add|update|change|modify|refactor|build|create|remove|delete|write|migrate)\b|修复|实现|添加|更新|修改|重构|创建|删除|迁移/i;
|
|
6750
7058
|
var READ_ONLY = /\b(?:review|analy[sz]e|inspect|explain|research|investigate|audit|summari[sz]e|report)\b|调研|分析|审查|检查|解释|研究|总结|报告/i;
|
|
6751
7059
|
function inferGoalIntent(goal) {
|
|
@@ -6755,7 +7063,7 @@ function inferGoalIntent(goal) {
|
|
|
6755
7063
|
}
|
|
6756
7064
|
async function prepareLoopWorkspace(options) {
|
|
6757
7065
|
options.signal?.throwIfAborted();
|
|
6758
|
-
const root =
|
|
7066
|
+
const root = resolve9(options.root);
|
|
6759
7067
|
const intent = inferGoalIntent(options.goal);
|
|
6760
7068
|
if (intent === "read_only") return { kind: "ready", workspace: { root, mode: "current" } };
|
|
6761
7069
|
const inside = await git(root, ["rev-parse", "--is-inside-work-tree"]);
|
|
@@ -6798,13 +7106,13 @@ async function ensureWorktreeDirectoryIgnored(root) {
|
|
|
6798
7106
|
if (ignored2.code === 0) return;
|
|
6799
7107
|
const commonDirectory = await git(root, ["rev-parse", "--git-common-dir"]);
|
|
6800
7108
|
if (commonDirectory.code !== 0) throw new Error(commonDirectory.stderr.trim() || "Git common directory is unavailable");
|
|
6801
|
-
const gitDirectory =
|
|
7109
|
+
const gitDirectory = resolve9(root, commonDirectory.stdout.trim());
|
|
6802
7110
|
const infoDirectory = join9(gitDirectory, "info");
|
|
6803
7111
|
const excludePath = join9(infoDirectory, "exclude");
|
|
6804
7112
|
await mkdir7(infoDirectory, { recursive: true });
|
|
6805
7113
|
let existing = "";
|
|
6806
7114
|
try {
|
|
6807
|
-
existing = await
|
|
7115
|
+
existing = await readFile6(excludePath, "utf8");
|
|
6808
7116
|
} catch (error) {
|
|
6809
7117
|
if (!(typeof error === "object" && error !== null && "code" in error && error.code === "ENOENT")) throw error;
|
|
6810
7118
|
}
|
|
@@ -6815,8 +7123,8 @@ async function ensureWorktreeDirectoryIgnored(root) {
|
|
|
6815
7123
|
|
|
6816
7124
|
// src/loop/store.ts
|
|
6817
7125
|
import { constants as constants2 } from "fs";
|
|
6818
|
-
import { lstat as
|
|
6819
|
-
import { dirname as
|
|
7126
|
+
import { lstat as lstat5, mkdir as mkdir8, open as open4, readFile as readFile7, realpath as realpath3, rename as rename4, rm as rm3, stat as stat2 } from "fs/promises";
|
|
7127
|
+
import { dirname as dirname9, join as join10, relative as relative9, resolve as resolve10, sep as sep8 } from "path";
|
|
6820
7128
|
import { randomUUID as randomUUID5 } from "crypto";
|
|
6821
7129
|
var LOOP_ID = /^[A-Za-z0-9][A-Za-z0-9._-]{0,127}$/;
|
|
6822
7130
|
var DEFAULT_MAX_BYTES2 = 5 * 1024 * 1024;
|
|
@@ -6826,7 +7134,7 @@ var LoopStore = class {
|
|
|
6826
7134
|
#maxBytes;
|
|
6827
7135
|
#appendTails = /* @__PURE__ */ new Map();
|
|
6828
7136
|
constructor(options) {
|
|
6829
|
-
this.#workspace =
|
|
7137
|
+
this.#workspace = resolve10(options.workspace);
|
|
6830
7138
|
this.#root = join10(this.#workspace, ".flavor", "loops");
|
|
6831
7139
|
this.#maxBytes = options.maxBytes ?? DEFAULT_MAX_BYTES2;
|
|
6832
7140
|
}
|
|
@@ -6868,7 +7176,7 @@ var LoopStore = class {
|
|
|
6868
7176
|
try {
|
|
6869
7177
|
const metadata = await stat2(target);
|
|
6870
7178
|
if (!metadata.isFile() || metadata.size > this.#maxBytes) throw new Error("Loop state is not a bounded regular file");
|
|
6871
|
-
const raw = await
|
|
7179
|
+
const raw = await readFile7(target, "utf8");
|
|
6872
7180
|
const state = LoopStateSchema.parse(JSON.parse(raw));
|
|
6873
7181
|
await this.#assertWorkspace(state.workspace);
|
|
6874
7182
|
return state;
|
|
@@ -6898,7 +7206,7 @@ var LoopStore = class {
|
|
|
6898
7206
|
}
|
|
6899
7207
|
async #prepare(loopId) {
|
|
6900
7208
|
validateId(loopId);
|
|
6901
|
-
await assertNoSymlink2(this.#workspace,
|
|
7209
|
+
await assertNoSymlink2(this.#workspace, dirname9(this.#root));
|
|
6902
7210
|
await mkdir8(this.#root, { recursive: true, mode: 448 });
|
|
6903
7211
|
await assertNoSymlink2(this.#workspace, this.#root);
|
|
6904
7212
|
const directory = join10(this.#root, loopId);
|
|
@@ -6925,31 +7233,31 @@ function validateId(loopId) {
|
|
|
6925
7233
|
if (!LOOP_ID.test(loopId)) throw new Error("Invalid loop id");
|
|
6926
7234
|
}
|
|
6927
7235
|
async function assertNoSymlink2(root, target) {
|
|
6928
|
-
let cursor =
|
|
6929
|
-
const stop =
|
|
7236
|
+
let cursor = resolve10(target);
|
|
7237
|
+
const stop = resolve10(root);
|
|
6930
7238
|
if (!isWithin2(stop, cursor)) throw new Error("Loop path escapes workspace");
|
|
6931
7239
|
const chain = [];
|
|
6932
7240
|
while (cursor !== stop) {
|
|
6933
7241
|
chain.push(cursor);
|
|
6934
|
-
cursor =
|
|
7242
|
+
cursor = dirname9(cursor);
|
|
6935
7243
|
}
|
|
6936
7244
|
for (const path of chain.reverse()) {
|
|
6937
7245
|
try {
|
|
6938
|
-
if ((await
|
|
7246
|
+
if ((await lstat5(path)).isSymbolicLink()) throw new Error(`Loop path contains a symbolic link: ${path}`);
|
|
6939
7247
|
} catch (error) {
|
|
6940
7248
|
if (!isCode4(error, "ENOENT")) throw error;
|
|
6941
7249
|
}
|
|
6942
7250
|
}
|
|
6943
7251
|
}
|
|
6944
7252
|
function isWithin2(root, path) {
|
|
6945
|
-
const value =
|
|
6946
|
-
return value === "" || !value.startsWith(`..${
|
|
7253
|
+
const value = relative9(resolve10(root), resolve10(path));
|
|
7254
|
+
return value === "" || !value.startsWith(`..${sep8}`) && value !== ".." && !value.includes(":");
|
|
6947
7255
|
}
|
|
6948
7256
|
async function canonical(path) {
|
|
6949
7257
|
try {
|
|
6950
|
-
return await
|
|
7258
|
+
return await realpath3(resolve10(path));
|
|
6951
7259
|
} catch {
|
|
6952
|
-
return
|
|
7260
|
+
return resolve10(path);
|
|
6953
7261
|
}
|
|
6954
7262
|
}
|
|
6955
7263
|
function isCode4(error, code) {
|
|
@@ -6957,46 +7265,191 @@ function isCode4(error, code) {
|
|
|
6957
7265
|
}
|
|
6958
7266
|
|
|
6959
7267
|
// src/loop/verifier.ts
|
|
6960
|
-
import { readFile as
|
|
7268
|
+
import { readFile as readFile8 } from "fs/promises";
|
|
6961
7269
|
import { join as join11 } from "path";
|
|
6962
7270
|
|
|
6963
7271
|
// src/tools/shell.ts
|
|
6964
7272
|
import { spawn as spawn4 } from "child_process";
|
|
6965
|
-
import { isAbsolute as
|
|
7273
|
+
import { isAbsolute as isAbsolute5, relative as relative10, resolve as resolve11, sep as sep9 } from "path";
|
|
7274
|
+
import { z as z13 } from "zod";
|
|
7275
|
+
|
|
7276
|
+
// src/tools/jobs.ts
|
|
6966
7277
|
import { z as z12 } from "zod";
|
|
7278
|
+
var JobId = z12.string().regex(/^job-[a-f0-9-]+$/);
|
|
7279
|
+
var ReadInput = z12.object({ id: JobId, cursor: z12.coerce.number().int().min(0).optional() });
|
|
7280
|
+
var IdInput = z12.object({ id: JobId });
|
|
7281
|
+
function createJobTools(registry) {
|
|
7282
|
+
const tools = [
|
|
7283
|
+
{
|
|
7284
|
+
name: "JobList",
|
|
7285
|
+
description: "List background jobs owned by this agent",
|
|
7286
|
+
inputSchema: z12.object({}),
|
|
7287
|
+
paths: () => [],
|
|
7288
|
+
execute: async (_input, _signal, context) => registry.list(owner(context)),
|
|
7289
|
+
presentResult: (output) => jobListPresentation(output)
|
|
7290
|
+
},
|
|
7291
|
+
{
|
|
7292
|
+
name: "JobRead",
|
|
7293
|
+
description: "Read incremental output from an owned background job",
|
|
7294
|
+
inputSchema: ReadInput,
|
|
7295
|
+
paths: () => [],
|
|
7296
|
+
execute: async (input, _signal, context) => registry.read(input.id, owner(context), input.cursor ?? 0),
|
|
7297
|
+
renderForModel: (output) => JSON.stringify(output),
|
|
7298
|
+
presentResult: (output) => jobPresentation("read", output)
|
|
7299
|
+
},
|
|
7300
|
+
{
|
|
7301
|
+
name: "JobWait",
|
|
7302
|
+
description: "Wait for an owned background job to finish",
|
|
7303
|
+
inputSchema: IdInput,
|
|
7304
|
+
paths: () => [],
|
|
7305
|
+
execute: async (input, signal, context) => registry.wait(input.id, owner(context), signal),
|
|
7306
|
+
presentResult: (output) => jobPresentation("wait", output)
|
|
7307
|
+
},
|
|
7308
|
+
{
|
|
7309
|
+
name: "JobKill",
|
|
7310
|
+
description: "Cancel an owned background job and its process tree",
|
|
7311
|
+
inputSchema: IdInput,
|
|
7312
|
+
paths: () => [],
|
|
7313
|
+
execute: async (input, _signal, context) => {
|
|
7314
|
+
registry.kill(input.id, owner(context));
|
|
7315
|
+
return registry.read(input.id, owner(context));
|
|
7316
|
+
},
|
|
7317
|
+
presentResult: (output) => jobPresentation("kill", output)
|
|
7318
|
+
}
|
|
7319
|
+
];
|
|
7320
|
+
return tools;
|
|
7321
|
+
}
|
|
7322
|
+
function jobPresentation(action, job) {
|
|
7323
|
+
const read = "output" in job ? job : void 0;
|
|
7324
|
+
return {
|
|
7325
|
+
kind: "job",
|
|
7326
|
+
action,
|
|
7327
|
+
id: job.id,
|
|
7328
|
+
jobKind: job.kind,
|
|
7329
|
+
label: job.label,
|
|
7330
|
+
state: job.state,
|
|
7331
|
+
...job.exitCode === void 0 ? {} : { exitCode: job.exitCode },
|
|
7332
|
+
...job.error === void 0 ? {} : { error: job.error },
|
|
7333
|
+
...read === void 0 ? {} : { output: read.output, cursor: read.cursor },
|
|
7334
|
+
...job.truncated ? { truncated: true } : {}
|
|
7335
|
+
};
|
|
7336
|
+
}
|
|
7337
|
+
function jobListPresentation(jobs) {
|
|
7338
|
+
return {
|
|
7339
|
+
kind: "job",
|
|
7340
|
+
action: "list",
|
|
7341
|
+
jobs: jobs.map((job) => ({
|
|
7342
|
+
id: job.id,
|
|
7343
|
+
kind: job.kind,
|
|
7344
|
+
label: job.label,
|
|
7345
|
+
state: job.state,
|
|
7346
|
+
...job.exitCode === void 0 ? {} : { exitCode: job.exitCode }
|
|
7347
|
+
}))
|
|
7348
|
+
};
|
|
7349
|
+
}
|
|
7350
|
+
function owner(context) {
|
|
7351
|
+
return context?.ownerId ?? context?.agent ?? "main";
|
|
7352
|
+
}
|
|
7353
|
+
|
|
7354
|
+
// src/tools/shell.ts
|
|
6967
7355
|
var DEFAULT_MAX_OUTPUT_BYTES2 = 1048576;
|
|
6968
|
-
var
|
|
7356
|
+
var DEFAULT_SHELL_TIMEOUT_MS = 10 * 6e4;
|
|
7357
|
+
var ELLIPSIS = "\u2026";
|
|
6969
7358
|
var TERMINATION_GRACE_MS = 250;
|
|
6970
7359
|
var TERMINATION_FAILURE_MS = 5e3;
|
|
6971
|
-
var
|
|
6972
|
-
|
|
6973
|
-
|
|
6974
|
-
|
|
6975
|
-
|
|
7360
|
+
var STREAM_CLOSE_GRACE_MS = 500;
|
|
7361
|
+
var ShellInput = z13.object({
|
|
7362
|
+
command: z13.string().min(1),
|
|
7363
|
+
args: z13.array(z13.string()),
|
|
7364
|
+
cwd: z13.string().min(1).nullable().optional(),
|
|
7365
|
+
timeoutMs: z13.coerce.number().int().positive().max(864e5).nullable().optional(),
|
|
7366
|
+
background: z13.boolean().optional()
|
|
6976
7367
|
});
|
|
6977
7368
|
function createShellTool(workspace, options = {}) {
|
|
6978
|
-
const root =
|
|
7369
|
+
const root = resolve11(workspace);
|
|
6979
7370
|
const maxBytes = options.maxOutputBytes ?? DEFAULT_MAX_OUTPUT_BYTES2;
|
|
7371
|
+
const defaultTimeoutMs = options.defaultTimeoutMs ?? DEFAULT_SHELL_TIMEOUT_MS;
|
|
6980
7372
|
if (!Number.isSafeInteger(maxBytes) || maxBytes <= 0) throw new Error("maxOutputBytes must be a positive integer");
|
|
7373
|
+
if (!Number.isSafeInteger(defaultTimeoutMs) || defaultTimeoutMs <= 0 || defaultTimeoutMs > 864e5) {
|
|
7374
|
+
throw new Error("defaultTimeoutMs must be a positive integer no greater than 86400000");
|
|
7375
|
+
}
|
|
6981
7376
|
return {
|
|
6982
7377
|
name: "Shell",
|
|
6983
|
-
description: "Run a command with an argument array inside the workspace",
|
|
7378
|
+
description: "Run a bounded command with an argument array inside the workspace; background=true returns a managed job id",
|
|
6984
7379
|
inputSchema: ShellInput,
|
|
6985
7380
|
paths: (input) => [workingDirectory(root, input.cwd ?? void 0)],
|
|
6986
7381
|
summarize: (input) => [input.command, ...input.args].join(" "),
|
|
7382
|
+
presentCall: (input) => ({ kind: "terminal", variant: "command", title: input.background ? "Starting background command" : "Running command", command: [input.command, ...input.args].join(" "), state: "running" }),
|
|
6987
7383
|
permissions: (input) => ({
|
|
6988
7384
|
paths: [workingDirectory(root, input.cwd ?? void 0)],
|
|
6989
7385
|
command: input.command,
|
|
6990
7386
|
args: input.args,
|
|
6991
7387
|
cwd: workingDirectory(root, input.cwd ?? void 0)
|
|
6992
7388
|
}),
|
|
6993
|
-
|
|
6994
|
-
|
|
7389
|
+
presentResult: (output, input) => output && "jobId" in output ? {
|
|
7390
|
+
kind: "job",
|
|
7391
|
+
action: "start",
|
|
7392
|
+
id: output.jobId,
|
|
7393
|
+
jobKind: "shell",
|
|
7394
|
+
label: [input.command, ...input.args].join(" "),
|
|
7395
|
+
state: "running"
|
|
7396
|
+
} : { kind: "terminal", variant: "command", title: [input.command, ...input.args].join(" "), command: [input.command, ...input.args].join(" "), stdout: output.stdout, stderr: output.stderr, exitCode: output.exitCode, state: output.terminationReason === "cancelled" ? "cancelled" : output.exitCode === 0 ? "completed" : "failed", truncated: output.truncated },
|
|
7397
|
+
execute: async (input, signal, context) => {
|
|
7398
|
+
const timeoutMs = input.timeoutMs ?? defaultTimeoutMs;
|
|
7399
|
+
if (input.background === true) {
|
|
7400
|
+
if (options.jobs === void 0) throw new Error("Background shell requires a JobRegistry");
|
|
7401
|
+
const cancellation = new AbortController();
|
|
7402
|
+
const job = options.jobs.create({
|
|
7403
|
+
kind: "shell",
|
|
7404
|
+
owner: owner(context),
|
|
7405
|
+
label: [input.command, ...input.args].join(" "),
|
|
7406
|
+
cancel: () => cancellation.abort(new Error("Background job cancelled"))
|
|
7407
|
+
});
|
|
7408
|
+
void (async () => {
|
|
7409
|
+
try {
|
|
7410
|
+
let result2;
|
|
7411
|
+
if (options.executionEnvironment === void 0) {
|
|
7412
|
+
result2 = await executeShell(root, { ...input, timeoutMs }, cancellation.signal, maxBytes, (stream, text) => job.append(stream === "stderr" ? `[stderr] ${text}` : text));
|
|
7413
|
+
} else {
|
|
7414
|
+
const raw = await options.executionEnvironment.exec({
|
|
7415
|
+
command: input.command,
|
|
7416
|
+
args: input.args,
|
|
7417
|
+
cwd: workingDirectory(root, input.cwd ?? void 0),
|
|
7418
|
+
timeoutMs
|
|
7419
|
+
}, cancellation.signal);
|
|
7420
|
+
job.append(raw.stdout);
|
|
7421
|
+
if (raw.stderr !== "") job.append(`[stderr] ${raw.stderr}`);
|
|
7422
|
+
result2 = { ...raw, truncated: false, truncation: { stdout: { truncated: false, originalBytes: Buffer.byteLength(raw.stdout), limitBytes: maxBytes }, stderr: { truncated: false, originalBytes: Buffer.byteLength(raw.stderr), limitBytes: maxBytes } } };
|
|
7423
|
+
}
|
|
7424
|
+
job.complete({ exitCode: result2.exitCode });
|
|
7425
|
+
} catch (error) {
|
|
7426
|
+
job.fail(error);
|
|
7427
|
+
}
|
|
7428
|
+
})();
|
|
7429
|
+
return {
|
|
7430
|
+
jobId: job.id,
|
|
7431
|
+
state: "running",
|
|
7432
|
+
command: [input.command, ...input.args].join(" "),
|
|
7433
|
+
exitCode: null,
|
|
7434
|
+
signal: null,
|
|
7435
|
+
stdout: "",
|
|
7436
|
+
stderr: "",
|
|
7437
|
+
truncated: false,
|
|
7438
|
+
truncation: {
|
|
7439
|
+
stdout: { truncated: false, originalBytes: 0, limitBytes: maxBytes },
|
|
7440
|
+
stderr: { truncated: false, originalBytes: 0, limitBytes: maxBytes }
|
|
7441
|
+
},
|
|
7442
|
+
terminationReason: null
|
|
7443
|
+
};
|
|
7444
|
+
}
|
|
7445
|
+
if (options.executionEnvironment === void 0) {
|
|
7446
|
+
return executeShell(root, { ...input, timeoutMs }, signal, maxBytes);
|
|
7447
|
+
}
|
|
6995
7448
|
const result = await options.executionEnvironment.exec({
|
|
6996
7449
|
command: input.command,
|
|
6997
7450
|
args: input.args,
|
|
6998
7451
|
cwd: workingDirectory(root, input.cwd ?? void 0),
|
|
6999
|
-
|
|
7452
|
+
timeoutMs
|
|
7000
7453
|
}, signal);
|
|
7001
7454
|
const stdout = new BoundedOutput(maxBytes);
|
|
7002
7455
|
const stderr = new BoundedOutput(maxBytes);
|
|
@@ -7015,7 +7468,7 @@ function createShellTool(workspace, options = {}) {
|
|
|
7015
7468
|
}
|
|
7016
7469
|
};
|
|
7017
7470
|
}
|
|
7018
|
-
async function executeShell(root, input, cancellation, maxBytes) {
|
|
7471
|
+
async function executeShell(root, input, cancellation, maxBytes, onOutput) {
|
|
7019
7472
|
const cwd = workingDirectory(root, input.cwd ?? void 0);
|
|
7020
7473
|
const stdout = new BoundedOutput(maxBytes);
|
|
7021
7474
|
const stderr = new BoundedOutput(maxBytes);
|
|
@@ -7027,27 +7480,37 @@ async function executeShell(root, input, cancellation, maxBytes) {
|
|
|
7027
7480
|
const quoteArg = (s) => s.includes(" ") ? `"${s.replace(/"/g, '""')}"` : s;
|
|
7028
7481
|
const quoted = input.args.map(quoteArg).join(" ");
|
|
7029
7482
|
const commandLine = quoted.length > 0 ? `${quoteArg(input.command)} ${quoted}` : quoteArg(input.command);
|
|
7030
|
-
const
|
|
7483
|
+
const shellCommandLine = process.platform === "win32" ? `chcp 65001>nul & ${commandLine}` : commandLine;
|
|
7484
|
+
const child = spawn4(shellCommandLine, {
|
|
7031
7485
|
cwd,
|
|
7032
7486
|
shell: true,
|
|
7033
7487
|
windowsHide: true,
|
|
7034
7488
|
detached: process.platform !== "win32"
|
|
7035
7489
|
});
|
|
7036
7490
|
let exitObserved = false;
|
|
7491
|
+
let observedExitCode = null;
|
|
7492
|
+
let observedSignal = null;
|
|
7037
7493
|
let settled = false;
|
|
7038
7494
|
let terminationReason = null;
|
|
7039
7495
|
let termination;
|
|
7040
7496
|
let terminalTimer;
|
|
7497
|
+
let streamCloseTimer;
|
|
7498
|
+
const stdoutDecoder = onOutput === void 0 ? void 0 : new AdaptiveOutputDecoder();
|
|
7499
|
+
const stderrDecoder = onOutput === void 0 ? void 0 : new AdaptiveOutputDecoder();
|
|
7041
7500
|
const terminate = (reason) => {
|
|
7042
|
-
if (termination !== void 0 ||
|
|
7501
|
+
if (termination !== void 0 || settled) return;
|
|
7502
|
+
terminationReason = reason;
|
|
7503
|
+
if (exitObserved || child.exitCode !== null || child.signalCode !== null) {
|
|
7504
|
+
child.stdout.destroy();
|
|
7505
|
+
child.stderr.destroy();
|
|
7506
|
+
void finishResolve(observedExitCode ?? child.exitCode, observedSignal ?? child.signalCode);
|
|
7043
7507
|
return;
|
|
7044
7508
|
}
|
|
7045
|
-
terminationReason = reason;
|
|
7046
7509
|
termination = terminateTree(child.pid);
|
|
7047
7510
|
terminalTimer = setTimeout(() => finishReject(new Error(`Process did not close after ${reason} termination`)), TERMINATION_FAILURE_MS);
|
|
7048
7511
|
terminalTimer.unref();
|
|
7049
7512
|
};
|
|
7050
|
-
const timeoutMs = input.timeoutMs ??
|
|
7513
|
+
const timeoutMs = input.timeoutMs ?? DEFAULT_SHELL_TIMEOUT_MS;
|
|
7051
7514
|
let timer;
|
|
7052
7515
|
child.once("spawn", () => {
|
|
7053
7516
|
if (timeoutMs === void 0 || exitObserved || settled) return;
|
|
@@ -7056,30 +7519,57 @@ async function executeShell(root, input, cancellation, maxBytes) {
|
|
|
7056
7519
|
});
|
|
7057
7520
|
const onCancel = () => terminate("cancelled");
|
|
7058
7521
|
cancellation.addEventListener("abort", onCancel, { once: true });
|
|
7059
|
-
child.stdout.on("data", (chunk) =>
|
|
7060
|
-
|
|
7061
|
-
|
|
7522
|
+
child.stdout.on("data", (chunk) => {
|
|
7523
|
+
stdout.add(chunk);
|
|
7524
|
+
if (stdoutDecoder !== void 0) {
|
|
7525
|
+
const text = stdoutDecoder.write(chunk);
|
|
7526
|
+
if (text !== "") onOutput?.("stdout", text);
|
|
7527
|
+
}
|
|
7528
|
+
});
|
|
7529
|
+
child.stderr.on("data", (chunk) => {
|
|
7530
|
+
stderr.add(chunk);
|
|
7531
|
+
if (stderrDecoder !== void 0) {
|
|
7532
|
+
const text = stderrDecoder.write(chunk);
|
|
7533
|
+
if (text !== "") onOutput?.("stderr", text);
|
|
7534
|
+
}
|
|
7535
|
+
});
|
|
7536
|
+
child.once("exit", (exitCode, exitSignal) => {
|
|
7062
7537
|
exitObserved = true;
|
|
7538
|
+
observedExitCode = exitCode;
|
|
7539
|
+
observedSignal = exitSignal;
|
|
7063
7540
|
if (timer !== void 0) clearTimeout(timer);
|
|
7541
|
+
streamCloseTimer = setTimeout(() => {
|
|
7542
|
+
child.stdout.destroy();
|
|
7543
|
+
child.stderr.destroy();
|
|
7544
|
+
void finishResolve(observedExitCode, observedSignal);
|
|
7545
|
+
}, STREAM_CLOSE_GRACE_MS);
|
|
7546
|
+
streamCloseTimer.unref();
|
|
7064
7547
|
});
|
|
7065
7548
|
child.once("error", (error) => {
|
|
7066
7549
|
finishReject(error);
|
|
7067
7550
|
});
|
|
7068
|
-
child.once("close",
|
|
7069
|
-
|
|
7551
|
+
child.once("close", (exitCode, signal) => {
|
|
7552
|
+
void finishResolve(exitCode, signal);
|
|
7553
|
+
});
|
|
7554
|
+
async function finishResolve(exitCode, exitSignal) {
|
|
7070
7555
|
if (settled) return;
|
|
7071
7556
|
settled = true;
|
|
7072
7557
|
cleanup();
|
|
7558
|
+
if (termination !== void 0) await termination;
|
|
7559
|
+
const stdoutTail = stdoutDecoder?.end() ?? "";
|
|
7560
|
+
const stderrTail = stderrDecoder?.end() ?? "";
|
|
7561
|
+
if (stdoutTail !== "") onOutput?.("stdout", stdoutTail);
|
|
7562
|
+
if (stderrTail !== "") onOutput?.("stderr", stderrTail);
|
|
7073
7563
|
resolvePromise({
|
|
7074
7564
|
exitCode,
|
|
7075
|
-
signal,
|
|
7565
|
+
signal: exitSignal,
|
|
7076
7566
|
stdout: stdout.text(),
|
|
7077
7567
|
stderr: stderr.text(),
|
|
7078
7568
|
truncated: stdout.truncated || stderr.truncated,
|
|
7079
7569
|
truncation: { stdout: stdout.metadata(), stderr: stderr.metadata() },
|
|
7080
7570
|
terminationReason
|
|
7081
7571
|
});
|
|
7082
|
-
}
|
|
7572
|
+
}
|
|
7083
7573
|
function finishReject(error) {
|
|
7084
7574
|
if (settled) return;
|
|
7085
7575
|
settled = true;
|
|
@@ -7089,6 +7579,7 @@ async function executeShell(root, input, cancellation, maxBytes) {
|
|
|
7089
7579
|
function cleanup() {
|
|
7090
7580
|
if (timer !== void 0) clearTimeout(timer);
|
|
7091
7581
|
if (terminalTimer !== void 0) clearTimeout(terminalTimer);
|
|
7582
|
+
if (streamCloseTimer !== void 0) clearTimeout(streamCloseTimer);
|
|
7092
7583
|
cancellation.removeEventListener("abort", onCancel);
|
|
7093
7584
|
}
|
|
7094
7585
|
});
|
|
@@ -7124,14 +7615,41 @@ var BoundedOutput = class {
|
|
|
7124
7615
|
return { truncated: this.truncated, originalBytes: this.#bytes, limitBytes: this.#limit };
|
|
7125
7616
|
}
|
|
7126
7617
|
text() {
|
|
7127
|
-
if (!this.truncated) return this.#complete
|
|
7128
|
-
|
|
7618
|
+
if (!this.truncated) return decodeOutput(this.#complete);
|
|
7619
|
+
const encoding = classifyUtf8(this.#complete) === "invalid" && process.platform === "win32" ? "gb18030" : "utf-8";
|
|
7620
|
+
return `${decodePrefix(this.#head, encoding)}${ELLIPSIS}${decodeSuffix(this.#tail, encoding)}`;
|
|
7621
|
+
}
|
|
7622
|
+
};
|
|
7623
|
+
var AdaptiveOutputDecoder = class {
|
|
7624
|
+
#pending = Buffer.alloc(0);
|
|
7625
|
+
#decoder;
|
|
7626
|
+
write(chunk) {
|
|
7627
|
+
if (this.#decoder !== void 0) return this.#decoder.decode(chunk, { stream: true });
|
|
7628
|
+
this.#pending = Buffer.concat([this.#pending, chunk]);
|
|
7629
|
+
if (this.#pending.every((byte) => byte < 128)) {
|
|
7630
|
+
const ascii = this.#pending.toString("ascii");
|
|
7631
|
+
this.#pending = Buffer.alloc(0);
|
|
7632
|
+
return ascii;
|
|
7633
|
+
}
|
|
7634
|
+
const classification = classifyUtf8(this.#pending);
|
|
7635
|
+
if (classification === "incomplete") return "";
|
|
7636
|
+
const encoding = classification === "valid" || process.platform !== "win32" ? "utf-8" : "gb18030";
|
|
7637
|
+
this.#decoder = new TextDecoder(encoding);
|
|
7638
|
+
const text = this.#decoder.decode(this.#pending, { stream: true });
|
|
7639
|
+
this.#pending = Buffer.alloc(0);
|
|
7640
|
+
return text;
|
|
7641
|
+
}
|
|
7642
|
+
end() {
|
|
7643
|
+
if (this.#decoder !== void 0) return this.#decoder.decode();
|
|
7644
|
+
const text = decodeOutput(this.#pending);
|
|
7645
|
+
this.#pending = Buffer.alloc(0);
|
|
7646
|
+
return text;
|
|
7129
7647
|
}
|
|
7130
7648
|
};
|
|
7131
7649
|
function workingDirectory(root, cwd = ".") {
|
|
7132
|
-
const candidate =
|
|
7133
|
-
const delta =
|
|
7134
|
-
if (delta === ".." || delta.startsWith(`..${
|
|
7650
|
+
const candidate = resolve11(root, cwd);
|
|
7651
|
+
const delta = relative10(root, candidate);
|
|
7652
|
+
if (delta === ".." || delta.startsWith(`..${sep9}`) || isAbsolute5(delta)) throw new Error("Working directory is outside the workspace");
|
|
7135
7653
|
return candidate;
|
|
7136
7654
|
}
|
|
7137
7655
|
async function terminateTree(pid) {
|
|
@@ -7164,34 +7682,76 @@ async function terminateTree(pid) {
|
|
|
7164
7682
|
}
|
|
7165
7683
|
function waitForProcess(child) {
|
|
7166
7684
|
return new Promise((resolvePromise) => {
|
|
7167
|
-
|
|
7168
|
-
|
|
7685
|
+
let settled = false;
|
|
7686
|
+
const finish = () => {
|
|
7687
|
+
if (settled) return;
|
|
7688
|
+
settled = true;
|
|
7689
|
+
clearTimeout(timer);
|
|
7690
|
+
resolvePromise();
|
|
7691
|
+
};
|
|
7692
|
+
const timer = setTimeout(finish, TERMINATION_FAILURE_MS);
|
|
7693
|
+
timer.unref();
|
|
7694
|
+
child.once("error", finish);
|
|
7695
|
+
child.once("close", finish);
|
|
7169
7696
|
});
|
|
7170
7697
|
}
|
|
7171
7698
|
function delay(milliseconds) {
|
|
7172
7699
|
return new Promise((resolvePromise) => setTimeout(resolvePromise, milliseconds));
|
|
7173
7700
|
}
|
|
7174
|
-
function
|
|
7701
|
+
function decodeOutput(buffer) {
|
|
7702
|
+
const encoding = classifyUtf8(buffer) === "invalid" && process.platform === "win32" ? "gb18030" : "utf-8";
|
|
7703
|
+
return new TextDecoder(encoding).decode(buffer);
|
|
7704
|
+
}
|
|
7705
|
+
function decodePrefix(buffer, encoding) {
|
|
7175
7706
|
for (let end = buffer.length; end >= Math.max(0, buffer.length - 3); end -= 1) {
|
|
7176
7707
|
const candidate = buffer.subarray(0, end);
|
|
7177
7708
|
try {
|
|
7178
|
-
new TextDecoder(
|
|
7179
|
-
return candidate;
|
|
7709
|
+
return new TextDecoder(encoding, { fatal: true }).decode(candidate);
|
|
7180
7710
|
} catch {
|
|
7181
7711
|
}
|
|
7182
7712
|
}
|
|
7183
|
-
return
|
|
7713
|
+
return new TextDecoder(encoding).decode(buffer);
|
|
7184
7714
|
}
|
|
7185
|
-
function
|
|
7715
|
+
function decodeSuffix(buffer, encoding) {
|
|
7186
7716
|
for (let start = 0; start <= Math.min(3, buffer.length); start += 1) {
|
|
7187
7717
|
const candidate = buffer.subarray(start);
|
|
7188
7718
|
try {
|
|
7189
|
-
new TextDecoder(
|
|
7190
|
-
return candidate;
|
|
7719
|
+
return new TextDecoder(encoding, { fatal: true }).decode(candidate);
|
|
7191
7720
|
} catch {
|
|
7192
7721
|
}
|
|
7193
7722
|
}
|
|
7194
|
-
return
|
|
7723
|
+
return new TextDecoder(encoding).decode(buffer);
|
|
7724
|
+
}
|
|
7725
|
+
function classifyUtf8(buffer) {
|
|
7726
|
+
try {
|
|
7727
|
+
new TextDecoder("utf-8", { fatal: true }).decode(buffer);
|
|
7728
|
+
return "valid";
|
|
7729
|
+
} catch {
|
|
7730
|
+
const suffixLength = incompleteUtf8SuffixLength(buffer);
|
|
7731
|
+
if (suffixLength === 0) return "invalid";
|
|
7732
|
+
try {
|
|
7733
|
+
new TextDecoder("utf-8", { fatal: true }).decode(buffer.subarray(0, buffer.length - suffixLength));
|
|
7734
|
+
return "incomplete";
|
|
7735
|
+
} catch {
|
|
7736
|
+
return "invalid";
|
|
7737
|
+
}
|
|
7738
|
+
}
|
|
7739
|
+
}
|
|
7740
|
+
function incompleteUtf8SuffixLength(buffer) {
|
|
7741
|
+
for (let start = Math.max(0, buffer.length - 3); start < buffer.length; start += 1) {
|
|
7742
|
+
const lead = buffer[start];
|
|
7743
|
+
const expected = lead >= 194 && lead <= 223 ? 2 : lead >= 224 && lead <= 239 ? 3 : lead >= 240 && lead <= 244 ? 4 : 0;
|
|
7744
|
+
if (expected === 0 || buffer.length - start >= expected) continue;
|
|
7745
|
+
let continuation = true;
|
|
7746
|
+
for (let index = start + 1; index < buffer.length; index += 1) {
|
|
7747
|
+
if ((buffer[index] & 192) !== 128) {
|
|
7748
|
+
continuation = false;
|
|
7749
|
+
break;
|
|
7750
|
+
}
|
|
7751
|
+
}
|
|
7752
|
+
if (continuation) return buffer.length - start;
|
|
7753
|
+
}
|
|
7754
|
+
return 0;
|
|
7195
7755
|
}
|
|
7196
7756
|
|
|
7197
7757
|
// src/loop/verifier.ts
|
|
@@ -7272,7 +7832,7 @@ function isUnconditionalPassThrough(script) {
|
|
|
7272
7832
|
}
|
|
7273
7833
|
async function optionalText(path) {
|
|
7274
7834
|
try {
|
|
7275
|
-
return await
|
|
7835
|
+
return await readFile8(path, "utf8");
|
|
7276
7836
|
} catch (error) {
|
|
7277
7837
|
if (isCode5(error, "ENOENT")) return void 0;
|
|
7278
7838
|
throw error;
|
|
@@ -7293,7 +7853,7 @@ function isCode5(error, code) {
|
|
|
7293
7853
|
|
|
7294
7854
|
// src/models/anthropic.ts
|
|
7295
7855
|
import Anthropic from "@anthropic-ai/sdk";
|
|
7296
|
-
import { readFile as
|
|
7856
|
+
import { readFile as readFile9 } from "fs/promises";
|
|
7297
7857
|
|
|
7298
7858
|
// src/utils/json.ts
|
|
7299
7859
|
function safeParseJson(input) {
|
|
@@ -7372,27 +7932,27 @@ function markLastBlock(message2) {
|
|
|
7372
7932
|
};
|
|
7373
7933
|
message2.content = blocks;
|
|
7374
7934
|
}
|
|
7375
|
-
function updateInputUsage(
|
|
7376
|
-
if (usage?.input_tokens != null)
|
|
7935
|
+
function updateInputUsage(snapshot4, usage) {
|
|
7936
|
+
if (usage?.input_tokens != null) snapshot4.base = usage.input_tokens;
|
|
7377
7937
|
if (usage?.cache_creation_input_tokens != null) {
|
|
7378
|
-
|
|
7938
|
+
snapshot4.cacheCreation = usage.cache_creation_input_tokens;
|
|
7379
7939
|
}
|
|
7380
7940
|
if (usage?.cache_read_input_tokens != null) {
|
|
7381
|
-
|
|
7941
|
+
snapshot4.cacheRead = usage.cache_read_input_tokens;
|
|
7382
7942
|
}
|
|
7383
|
-
return
|
|
7943
|
+
return snapshot4.base + snapshot4.cacheCreation + snapshot4.cacheRead;
|
|
7384
7944
|
}
|
|
7385
|
-
function formatCacheUsage(model,
|
|
7386
|
-
const total =
|
|
7387
|
-
const hitRatio = total > 0 ?
|
|
7945
|
+
function formatCacheUsage(model, snapshot4, shape) {
|
|
7946
|
+
const total = snapshot4.base + snapshot4.cacheCreation + snapshot4.cacheRead;
|
|
7947
|
+
const hitRatio = total > 0 ? snapshot4.cacheRead / total : 0;
|
|
7388
7948
|
return JSON.stringify({
|
|
7389
7949
|
event: "flavor-usage",
|
|
7390
7950
|
sessionId: currentUsageSession(),
|
|
7391
7951
|
provider: "anthropic",
|
|
7392
7952
|
model,
|
|
7393
|
-
inputTokens:
|
|
7394
|
-
cacheReadTokens:
|
|
7395
|
-
cacheCreationTokens:
|
|
7953
|
+
inputTokens: snapshot4.base,
|
|
7954
|
+
cacheReadTokens: snapshot4.cacheRead,
|
|
7955
|
+
cacheCreationTokens: snapshot4.cacheCreation,
|
|
7396
7956
|
totalInputTokens: total,
|
|
7397
7957
|
cacheHitRatio: Number(hitRatio.toFixed(4)),
|
|
7398
7958
|
...shape === void 0 ? {} : { requestMessages: shape.messages, requestMarkers: shape.markers }
|
|
@@ -7415,8 +7975,8 @@ var AnthropicModelAdapter = class {
|
|
|
7415
7975
|
...options.headers === void 0 ? {} : { defaultHeaders: options.headers }
|
|
7416
7976
|
});
|
|
7417
7977
|
}
|
|
7418
|
-
#logUsage(request,
|
|
7419
|
-
const line = formatCacheUsage(request.model,
|
|
7978
|
+
#logUsage(request, snapshot4, shape) {
|
|
7979
|
+
const line = formatCacheUsage(request.model, snapshot4, shape);
|
|
7420
7980
|
if (this.debugUsage) {
|
|
7421
7981
|
try {
|
|
7422
7982
|
process.stderr.write(`${line}
|
|
@@ -7495,7 +8055,7 @@ var AnthropicModelAdapter = class {
|
|
|
7495
8055
|
source: {
|
|
7496
8056
|
type: "base64",
|
|
7497
8057
|
media_type: block.mediaType,
|
|
7498
|
-
data: (await
|
|
8058
|
+
data: (await readFile9(block.source.path)).toString("base64")
|
|
7499
8059
|
}
|
|
7500
8060
|
}));
|
|
7501
8061
|
if (message2.cacheBreakpoint && Array.isArray(content) && content.length > 0) {
|
|
@@ -7677,7 +8237,7 @@ function resolveCacheProfile(input) {
|
|
|
7677
8237
|
|
|
7678
8238
|
// src/models/openai.ts
|
|
7679
8239
|
import OpenAI from "openai";
|
|
7680
|
-
import { readFile as
|
|
8240
|
+
import { readFile as readFile10 } from "fs/promises";
|
|
7681
8241
|
async function toInput(message2) {
|
|
7682
8242
|
if (message2.role === "tool") {
|
|
7683
8243
|
if (!message2.toolCallId) throw new Error("Tool messages require toolCallId");
|
|
@@ -7689,7 +8249,7 @@ async function toInput(message2) {
|
|
|
7689
8249
|
}
|
|
7690
8250
|
const content = typeof message2.content === "string" ? message2.content : await Promise.all(message2.content.map(async (block) => block.type === "text" ? { type: "input_text", text: block.text } : {
|
|
7691
8251
|
type: "input_image",
|
|
7692
|
-
image_url: `data:${block.mediaType};base64,${(await
|
|
8252
|
+
image_url: `data:${block.mediaType};base64,${(await readFile10(block.source.path)).toString("base64")}`,
|
|
7693
8253
|
detail: "auto"
|
|
7694
8254
|
}));
|
|
7695
8255
|
return [
|
|
@@ -7908,8 +8468,8 @@ var ModelRegistry = class {
|
|
|
7908
8468
|
};
|
|
7909
8469
|
|
|
7910
8470
|
// src/mcp/client.ts
|
|
7911
|
-
import { createHash as
|
|
7912
|
-
import { z as
|
|
8471
|
+
import { createHash as createHash4 } from "crypto";
|
|
8472
|
+
import { z as z14 } from "zod";
|
|
7913
8473
|
var McpManager = class {
|
|
7914
8474
|
#workspace;
|
|
7915
8475
|
#clientFactory;
|
|
@@ -8045,7 +8605,7 @@ async function listAllTools(client) {
|
|
|
8045
8605
|
function createMcpToolDefinition(serverName, timeoutMs, client, remote) {
|
|
8046
8606
|
if (remote.name.length === 0) throw new Error("MCP tool name must not be empty");
|
|
8047
8607
|
if (!isRecord3(remote.inputSchema)) throw new Error(`MCP tool "${remote.name}" has no JSON object input schema`);
|
|
8048
|
-
const inputSchema =
|
|
8608
|
+
const inputSchema = z14.fromJSONSchema(
|
|
8049
8609
|
remote.inputSchema
|
|
8050
8610
|
);
|
|
8051
8611
|
return {
|
|
@@ -8075,7 +8635,7 @@ function mcpToolName(serverName, remoteName) {
|
|
|
8075
8635
|
const safe = remoteName.replace(/[^A-Za-z0-9_-]/g, "_") || "tool";
|
|
8076
8636
|
const direct = `${prefix}${safe}`;
|
|
8077
8637
|
if (direct.length <= 64 && safe === remoteName) return direct;
|
|
8078
|
-
const suffix = `__${
|
|
8638
|
+
const suffix = `__${createHash4("sha256").update(remoteName).digest("hex").slice(0, 8)}`;
|
|
8079
8639
|
return `${prefix}${safe.slice(0, Math.max(1, 64 - prefix.length - suffix.length))}${suffix}`;
|
|
8080
8640
|
}
|
|
8081
8641
|
function mcpErrorText(content) {
|
|
@@ -8094,7 +8654,7 @@ function isRecord3(value) {
|
|
|
8094
8654
|
}
|
|
8095
8655
|
|
|
8096
8656
|
// src/mcp/sdk.ts
|
|
8097
|
-
import { resolve as
|
|
8657
|
+
import { resolve as resolve12 } from "path";
|
|
8098
8658
|
import { Client } from "@modelcontextprotocol/sdk/client/index.js";
|
|
8099
8659
|
import {
|
|
8100
8660
|
getDefaultEnvironment,
|
|
@@ -8116,7 +8676,7 @@ async function connectSdkMcpClient(input, dependencies = DEFAULT_DEPENDENCIES) {
|
|
|
8116
8676
|
command: config.command,
|
|
8117
8677
|
args: [...config.args],
|
|
8118
8678
|
env: { ...dependencies.defaultEnvironment(), ...config.env },
|
|
8119
|
-
cwd:
|
|
8679
|
+
cwd: resolve12(input.workspace, config.cwd ?? "."),
|
|
8120
8680
|
// MCP servers frequently log startup banners to stderr. Inheriting that
|
|
8121
8681
|
// stream corrupts Ink's input area, so connection errors are surfaced
|
|
8122
8682
|
// through McpManager diagnostics instead.
|
|
@@ -8141,14 +8701,14 @@ async function connectSdkMcpClient(input, dependencies = DEFAULT_DEPENDENCIES) {
|
|
|
8141
8701
|
}
|
|
8142
8702
|
|
|
8143
8703
|
// src/auth/oauth.ts
|
|
8144
|
-
import { randomBytes, createHash as
|
|
8704
|
+
import { randomBytes, createHash as createHash6 } from "crypto";
|
|
8145
8705
|
import { createServer } from "http";
|
|
8146
8706
|
import { URL as URL2, URLSearchParams } from "url";
|
|
8147
8707
|
|
|
8148
8708
|
// src/auth/oauth-config.ts
|
|
8149
|
-
import { createHash as
|
|
8709
|
+
import { createHash as createHash5 } from "crypto";
|
|
8150
8710
|
function oauthCredentialId(tokenUrl, clientId) {
|
|
8151
|
-
const digest =
|
|
8711
|
+
const digest = createHash5("sha256").update(`${tokenUrl}\0${clientId}`, "utf8").digest("base64url");
|
|
8152
8712
|
return `oauth:${digest}`;
|
|
8153
8713
|
}
|
|
8154
8714
|
function parseOAuthLlmConfig(value) {
|
|
@@ -8321,8 +8881,8 @@ var OAuthCallbackAuthProvider = class {
|
|
|
8321
8881
|
async #startCallbackServer(expectedState, signal) {
|
|
8322
8882
|
let serverResolve;
|
|
8323
8883
|
let serverReject;
|
|
8324
|
-
const codePromise = new Promise((
|
|
8325
|
-
serverResolve =
|
|
8884
|
+
const codePromise = new Promise((resolve25, reject) => {
|
|
8885
|
+
serverResolve = resolve25;
|
|
8326
8886
|
serverReject = reject;
|
|
8327
8887
|
});
|
|
8328
8888
|
const server = createServer((req, res) => {
|
|
@@ -8356,7 +8916,7 @@ var OAuthCallbackAuthProvider = class {
|
|
|
8356
8916
|
);
|
|
8357
8917
|
serverResolve(code);
|
|
8358
8918
|
});
|
|
8359
|
-
return new Promise((
|
|
8919
|
+
return new Promise((resolve25, reject) => {
|
|
8360
8920
|
server.once("error", (error) => {
|
|
8361
8921
|
reject(new Error(`OAuth callback server failed: ${error.message}`));
|
|
8362
8922
|
});
|
|
@@ -8373,7 +8933,7 @@ var OAuthCallbackAuthProvider = class {
|
|
|
8373
8933
|
signal.addEventListener("abort", () => {
|
|
8374
8934
|
serverReject(new Error("OAuth flow aborted"));
|
|
8375
8935
|
}, { once: true });
|
|
8376
|
-
|
|
8936
|
+
resolve25({ redirectUri, promise: codePromise });
|
|
8377
8937
|
});
|
|
8378
8938
|
});
|
|
8379
8939
|
}
|
|
@@ -8405,7 +8965,7 @@ var OAuthCallbackAuthProvider = class {
|
|
|
8405
8965
|
return randomBytes(32).toString("base64url");
|
|
8406
8966
|
}
|
|
8407
8967
|
#computeCodeChallenge(verifier) {
|
|
8408
|
-
return
|
|
8968
|
+
return createHash6("sha256").update(verifier).digest("base64url");
|
|
8409
8969
|
}
|
|
8410
8970
|
#generateState() {
|
|
8411
8971
|
return randomBytes(32).toString("base64url");
|
|
@@ -8455,13 +9015,13 @@ function escapeHtml(text) {
|
|
|
8455
9015
|
}
|
|
8456
9016
|
|
|
8457
9017
|
// src/auth/store.ts
|
|
8458
|
-
import { dirname as
|
|
9018
|
+
import { dirname as dirname10 } from "path";
|
|
8459
9019
|
function createFileTokenStore(filePath) {
|
|
8460
9020
|
let baseline = {};
|
|
8461
9021
|
const store = {
|
|
8462
9022
|
tokens: {},
|
|
8463
9023
|
async load() {
|
|
8464
|
-
const key = await loadOrCreateConfigKey(
|
|
9024
|
+
const key = await loadOrCreateConfigKey(dirname10(filePath));
|
|
8465
9025
|
const result = await readRecoverableFile(filePath, (raw) => parseTokens(decryptDocument(raw, key)));
|
|
8466
9026
|
const tokens = result?.value ?? {};
|
|
8467
9027
|
baseline = { ...tokens };
|
|
@@ -8469,7 +9029,7 @@ function createFileTokenStore(filePath) {
|
|
|
8469
9029
|
return tokens;
|
|
8470
9030
|
},
|
|
8471
9031
|
async save(tokens) {
|
|
8472
|
-
const key = await loadOrCreateConfigKey(
|
|
9032
|
+
const key = await loadOrCreateConfigKey(dirname10(filePath));
|
|
8473
9033
|
const removals = Object.keys(baseline).filter((providerId) => !(providerId in tokens));
|
|
8474
9034
|
const upserts = Object.fromEntries(Object.entries(tokens).filter(
|
|
8475
9035
|
([providerId, token]) => !sameToken(baseline[providerId], token)
|
|
@@ -8635,16 +9195,16 @@ function data(value) {
|
|
|
8635
9195
|
|
|
8636
9196
|
// src/plugins/host.ts
|
|
8637
9197
|
import { lstatSync, realpathSync as realpathSync2 } from "fs";
|
|
8638
|
-
import { lstat as
|
|
8639
|
-
import { isAbsolute as
|
|
9198
|
+
import { lstat as lstat6, open as open5, readdir as readdir4, realpath as realpath4, stat as stat3, writeFile as writeFile6 } from "fs/promises";
|
|
9199
|
+
import { isAbsolute as isAbsolute6, relative as relative11, resolve as resolve13, sep as sep10 } from "path";
|
|
8640
9200
|
import { pathToFileURL } from "url";
|
|
8641
9201
|
|
|
8642
9202
|
// src/plugins/types.ts
|
|
8643
|
-
import { z as
|
|
9203
|
+
import { z as z15 } from "zod";
|
|
8644
9204
|
var PLUGIN_NAME = /^(?:@[a-z0-9][a-z0-9._-]{0,63}\/)?[a-z0-9][a-z0-9._-]{0,63}$/;
|
|
8645
9205
|
var CONTRIBUTION_NAME = /^[a-zA-Z0-9][a-zA-Z0-9._:-]{0,127}$/;
|
|
8646
|
-
var entry =
|
|
8647
|
-
var uniqueArray = (schema, label, identify = (item) => String(item)) =>
|
|
9206
|
+
var entry = z15.object({ name: z15.string().regex(CONTRIBUTION_NAME) }).strict();
|
|
9207
|
+
var uniqueArray = (schema, label, identify = (item) => String(item)) => z15.array(schema).max(256).superRefine((items, context) => {
|
|
8648
9208
|
const seen = /* @__PURE__ */ new Set();
|
|
8649
9209
|
for (const [index, item] of items.entries()) {
|
|
8650
9210
|
const key = identify(item);
|
|
@@ -8652,17 +9212,17 @@ var uniqueArray = (schema, label, identify = (item) => String(item)) => z14.arra
|
|
|
8652
9212
|
seen.add(key);
|
|
8653
9213
|
}
|
|
8654
9214
|
});
|
|
8655
|
-
var PluginManifestSchema =
|
|
8656
|
-
name:
|
|
8657
|
-
version:
|
|
8658
|
-
apiVersion:
|
|
8659
|
-
main:
|
|
8660
|
-
permissions: uniqueArray(
|
|
8661
|
-
contributes:
|
|
9215
|
+
var PluginManifestSchema = z15.object({
|
|
9216
|
+
name: z15.string().regex(PLUGIN_NAME),
|
|
9217
|
+
version: z15.string().min(1).max(64),
|
|
9218
|
+
apiVersion: z15.literal("1"),
|
|
9219
|
+
main: z15.string().min(1).max(512),
|
|
9220
|
+
permissions: uniqueArray(z15.enum(["filesystem:read", "filesystem:write"]), "permission").max(16),
|
|
9221
|
+
contributes: z15.object({
|
|
8662
9222
|
commands: uniqueArray(entry, "command contribution", (item) => item.name),
|
|
8663
9223
|
tools: uniqueArray(entry, "tool contribution", (item) => item.name),
|
|
8664
|
-
hooks: uniqueArray(
|
|
8665
|
-
skillRoots: uniqueArray(entry.extend({ path:
|
|
9224
|
+
hooks: uniqueArray(z15.object({ name: z15.enum(HOOK_EVENT_NAMES) }).strict(), "hook contribution", (item) => item.name),
|
|
9225
|
+
skillRoots: uniqueArray(entry.extend({ path: z15.string().min(1).max(512) }).strict(), "skill-root contribution", (item) => item.name),
|
|
8666
9226
|
modelAdapters: uniqueArray(entry, "model-adapter contribution", (item) => item.name)
|
|
8667
9227
|
}).strict()
|
|
8668
9228
|
}).strict();
|
|
@@ -8754,7 +9314,7 @@ var PluginHost = class {
|
|
|
8754
9314
|
const roots = [];
|
|
8755
9315
|
for (const directory of directories) {
|
|
8756
9316
|
try {
|
|
8757
|
-
for (const entry2 of await readdir4(directory, { withFileTypes: true })) if (entry2.isDirectory()) roots.push(
|
|
9317
|
+
for (const entry2 of await readdir4(directory, { withFileTypes: true })) if (entry2.isDirectory()) roots.push(resolve13(directory, entry2.name));
|
|
8758
9318
|
} catch (error) {
|
|
8759
9319
|
this.#diagnose(directory, error, directory);
|
|
8760
9320
|
}
|
|
@@ -8784,10 +9344,10 @@ var PluginHost = class {
|
|
|
8784
9344
|
async #readCandidate(inputRoot, source, label = inputRoot) {
|
|
8785
9345
|
let diagnosticLabel = label;
|
|
8786
9346
|
try {
|
|
8787
|
-
const rootInfo = await
|
|
9347
|
+
const rootInfo = await lstat6(inputRoot);
|
|
8788
9348
|
if (!rootInfo.isDirectory() || rootInfo.isSymbolicLink()) throw new Error("Plugin root must be a static directory");
|
|
8789
|
-
const root = await
|
|
8790
|
-
const rawManifest = await readManifest(
|
|
9349
|
+
const root = await realpath4(inputRoot);
|
|
9350
|
+
const rawManifest = await readManifest(resolve13(root, MANIFEST));
|
|
8791
9351
|
if (typeof rawManifest === "object" && rawManifest !== null && "name" in rawManifest && typeof rawManifest.name === "string") {
|
|
8792
9352
|
diagnosticLabel = rawManifest.name;
|
|
8793
9353
|
}
|
|
@@ -8886,7 +9446,7 @@ var PluginHost = class {
|
|
|
8886
9446
|
assertActive();
|
|
8887
9447
|
const permission = `filesystem:${operation}`;
|
|
8888
9448
|
if (!manifest.permissions.includes(permission)) throw new Error(`Plugin ${manifest.name} lacks ${permission} permission`);
|
|
8889
|
-
const path =
|
|
9449
|
+
const path = resolve13(rawPath);
|
|
8890
9450
|
const authorized = await this.#options.authorizeFilesystem?.({ plugin: manifest.name, operation, path });
|
|
8891
9451
|
assertActive();
|
|
8892
9452
|
if (authorized !== true) throw new Error(`Filesystem ${operation} denied for plugin ${manifest.name}`);
|
|
@@ -8927,8 +9487,8 @@ var PluginHost = class {
|
|
|
8927
9487
|
if (!info.isDirectory() || info.isSymbolicLink()) throw new Error(`Skill root "${name}" must be a static directory`);
|
|
8928
9488
|
const physical = realpathSync2.native(registered);
|
|
8929
9489
|
assertContained(candidate.root, physical, "Skill root");
|
|
8930
|
-
const
|
|
8931
|
-
const capability = Object.freeze({ path: physical, identity: Object.freeze(pickIdentity(
|
|
9490
|
+
const snapshot4 = fromStats(info);
|
|
9491
|
+
const capability = Object.freeze({ path: physical, identity: Object.freeze(pickIdentity(snapshot4)) });
|
|
8932
9492
|
return register("skillRoot", name, [capability]);
|
|
8933
9493
|
},
|
|
8934
9494
|
registerModelAdapter: (name, value) => register("modelAdapter", name, [value])
|
|
@@ -8954,7 +9514,7 @@ var PluginHost = class {
|
|
|
8954
9514
|
}
|
|
8955
9515
|
};
|
|
8956
9516
|
async function readManifest(path) {
|
|
8957
|
-
const lexical = await
|
|
9517
|
+
const lexical = await lstat6(path, { bigint: true });
|
|
8958
9518
|
if (!lexical.isFile() || lexical.isSymbolicLink()) throw new Error("Plugin manifest must be a static regular file");
|
|
8959
9519
|
const handle = await open5(path, "r");
|
|
8960
9520
|
try {
|
|
@@ -8971,7 +9531,7 @@ async function readManifest(path) {
|
|
|
8971
9531
|
if (bytesRead > MAX_MANIFEST_BYTES2) throw new Error(`Plugin manifest exceeds ${MAX_MANIFEST_BYTES2} bytes`);
|
|
8972
9532
|
const after = fromStats(await handle.stat({ bigint: true }));
|
|
8973
9533
|
assertSnapshot(before, after, "Plugin manifest");
|
|
8974
|
-
const current = fromStats(await
|
|
9534
|
+
const current = fromStats(await lstat6(path, { bigint: true }));
|
|
8975
9535
|
assertSnapshot(before, current, "Plugin manifest path");
|
|
8976
9536
|
return JSON.parse(new TextDecoder("utf-8", { fatal: true }).decode(buffer.subarray(0, bytesRead)));
|
|
8977
9537
|
} finally {
|
|
@@ -8979,9 +9539,9 @@ async function readManifest(path) {
|
|
|
8979
9539
|
}
|
|
8980
9540
|
}
|
|
8981
9541
|
async function snapshotEntry(root, entry2) {
|
|
8982
|
-
const lexicalStats = await
|
|
9542
|
+
const lexicalStats = await lstat6(entry2, { bigint: true });
|
|
8983
9543
|
if (!lexicalStats.isFile() && !lexicalStats.isSymbolicLink()) throw new Error("Plugin entry must be a file");
|
|
8984
|
-
const physicalPath = await
|
|
9544
|
+
const physicalPath = await realpath4(entry2);
|
|
8985
9545
|
assertContained(root, physicalPath, "Plugin entry symlink");
|
|
8986
9546
|
const physicalStats = await stat3(physicalPath, { bigint: true });
|
|
8987
9547
|
if (!physicalStats.isFile()) throw new Error("Plugin entry must resolve to a regular file");
|
|
@@ -9010,21 +9570,21 @@ function contributionNames(manifest, kind) {
|
|
|
9010
9570
|
function fromStats(stats) {
|
|
9011
9571
|
return { dev: stats.dev, ino: stats.ino, mode: stats.mode, size: stats.size, mtimeNs: stats.mtimeNs, ctimeNs: stats.ctimeNs };
|
|
9012
9572
|
}
|
|
9013
|
-
function pickIdentity(
|
|
9014
|
-
return { dev:
|
|
9573
|
+
function pickIdentity(snapshot4) {
|
|
9574
|
+
return { dev: snapshot4.dev, ino: snapshot4.ino, mtimeNs: snapshot4.mtimeNs, ctimeNs: snapshot4.ctimeNs };
|
|
9015
9575
|
}
|
|
9016
9576
|
function assertSnapshot(expected, actual, label) {
|
|
9017
9577
|
for (const key of ["dev", "ino", "mode", "size", "mtimeNs", "ctimeNs"]) if (expected[key] !== actual[key]) throw new Error(`${label} changed during validation`);
|
|
9018
9578
|
}
|
|
9019
9579
|
function resolveContained(root, value, label) {
|
|
9020
|
-
if (
|
|
9021
|
-
const candidate =
|
|
9580
|
+
if (isAbsolute6(value)) throw new Error(`${label} must be relative to the plugin root`);
|
|
9581
|
+
const candidate = resolve13(root, value);
|
|
9022
9582
|
assertContained(root, candidate, label);
|
|
9023
9583
|
return candidate;
|
|
9024
9584
|
}
|
|
9025
9585
|
function assertContained(root, candidate, label) {
|
|
9026
|
-
const delta =
|
|
9027
|
-
if (delta === ".." || delta.startsWith(`..${
|
|
9586
|
+
const delta = relative11(root, candidate);
|
|
9587
|
+
if (delta === ".." || delta.startsWith(`..${sep10}`) || isAbsolute6(delta)) throw new Error(`${label} escapes the plugin root`);
|
|
9028
9588
|
}
|
|
9029
9589
|
function scopedLogger(logger, plugin) {
|
|
9030
9590
|
const prefix = `[plugin:${plugin}] `;
|
|
@@ -9068,8 +9628,8 @@ function compare(left, right) {
|
|
|
9068
9628
|
|
|
9069
9629
|
// src/skills/registry.ts
|
|
9070
9630
|
import { constants as constants3 } from "fs";
|
|
9071
|
-
import { lstat as
|
|
9072
|
-
import { isAbsolute as
|
|
9631
|
+
import { lstat as lstat7, open as defaultOpen, readdir as readdir5, realpath as realpath5 } from "fs/promises";
|
|
9632
|
+
import { isAbsolute as isAbsolute7, relative as relative12, resolve as resolve14, sep as sep11 } from "path";
|
|
9073
9633
|
import { marked } from "marked";
|
|
9074
9634
|
import { parseDocument } from "yaml";
|
|
9075
9635
|
var SKILL_NAME = /^[a-z0-9]+(?:-[a-z0-9]+)*$/;
|
|
@@ -9194,13 +9754,13 @@ var SkillRegistry = class {
|
|
|
9194
9754
|
const record = await this.#recordFor(skill);
|
|
9195
9755
|
const normalized = normalizeReference(reference);
|
|
9196
9756
|
const parts = normalized.split("/");
|
|
9197
|
-
if (
|
|
9757
|
+
if (isAbsolute7(reference) || parts.includes("..") || parts.length !== 2 || !RESOURCE_DIRECTORIES.has(parts[0] ?? "") || !RESOURCE_NAME.test(parts[1] ?? "")) {
|
|
9198
9758
|
throw new Error(`Resource reference escapes the skill root or is not a direct resource: ${reference}`);
|
|
9199
9759
|
}
|
|
9200
9760
|
const body = await this.loadBody(record.metadata);
|
|
9201
9761
|
const references = extractResourceReferences(body);
|
|
9202
9762
|
if (!references.has(normalized)) throw new Error(`Skill resource is not directly referenced: ${reference}`);
|
|
9203
|
-
const candidate =
|
|
9763
|
+
const candidate = resolve14(record.metadata.root, ...parts);
|
|
9204
9764
|
const file = await openVerifiedFile(candidate, record.metadata.root, "Resource", this.#options.openFile);
|
|
9205
9765
|
try {
|
|
9206
9766
|
if (file.snapshot.size > BigInt(this.#options.maxResourceBytes)) throw new Error(`Skill resource is too large: ${reference}`);
|
|
@@ -9229,13 +9789,13 @@ var SkillRegistry = class {
|
|
|
9229
9789
|
async #discoverSource(roots, source) {
|
|
9230
9790
|
const found = [];
|
|
9231
9791
|
for (const configuredRoot of roots) {
|
|
9232
|
-
const root =
|
|
9792
|
+
const root = resolve14(configuredRoot);
|
|
9233
9793
|
let entries;
|
|
9234
9794
|
let physicalRoot = root;
|
|
9235
9795
|
try {
|
|
9236
|
-
const rootInfo = await
|
|
9796
|
+
const rootInfo = await lstat7(root);
|
|
9237
9797
|
if (rootInfo.isSymbolicLink() || !rootInfo.isDirectory()) throw new Error("Skill root must be a real directory");
|
|
9238
|
-
physicalRoot = await
|
|
9798
|
+
physicalRoot = await realpath5(root);
|
|
9239
9799
|
entries = await readdir5(root, { withFileTypes: true });
|
|
9240
9800
|
} catch (error) {
|
|
9241
9801
|
if (isMissing2(error)) continue;
|
|
@@ -9244,13 +9804,13 @@ var SkillRegistry = class {
|
|
|
9244
9804
|
}
|
|
9245
9805
|
entries.sort((left, right) => compareCodePoints(left.name, right.name));
|
|
9246
9806
|
for (const entry2 of entries) {
|
|
9247
|
-
const skillRoot =
|
|
9807
|
+
const skillRoot = resolve14(root, entry2.name);
|
|
9248
9808
|
try {
|
|
9249
9809
|
if (entry2.isSymbolicLink()) throw new Error("Symlinked skill directories are not allowed");
|
|
9250
9810
|
if (!entry2.isDirectory()) continue;
|
|
9251
|
-
const physicalSkillRoot = await
|
|
9811
|
+
const physicalSkillRoot = await realpath5(skillRoot);
|
|
9252
9812
|
if (!isWithin3(physicalRoot, physicalSkillRoot)) throw new Error("Skill directory escapes its registry root");
|
|
9253
|
-
const skillFile =
|
|
9813
|
+
const skillFile = resolve14(skillRoot, "SKILL.md");
|
|
9254
9814
|
const parsed = await readFrontmatterFile(
|
|
9255
9815
|
skillFile,
|
|
9256
9816
|
physicalSkillRoot,
|
|
@@ -9387,10 +9947,10 @@ async function openFile(path, flags) {
|
|
|
9387
9947
|
return defaultOpen(path, flags);
|
|
9388
9948
|
}
|
|
9389
9949
|
async function openVerifiedFile(path, root, label, opener, expected) {
|
|
9390
|
-
const initial = await
|
|
9950
|
+
const initial = await lstat7(path, { bigint: true });
|
|
9391
9951
|
if (initial.isSymbolicLink()) throw new Error(`${label} symlinks are not allowed`);
|
|
9392
9952
|
assertRegular(initial, label);
|
|
9393
|
-
const canonical3 = await
|
|
9953
|
+
const canonical3 = await realpath5(path);
|
|
9394
9954
|
if (!isWithin3(root, canonical3)) throw new Error(`${label} escapes the skill root`);
|
|
9395
9955
|
const noFollow = typeof constants3.O_NOFOLLOW === "number" ? constants3.O_NOFOLLOW : 0;
|
|
9396
9956
|
const handle = await opener(canonical3, constants3.O_RDONLY | noFollow);
|
|
@@ -9451,8 +10011,8 @@ function decodeUtf82(content, label) {
|
|
|
9451
10011
|
}
|
|
9452
10012
|
}
|
|
9453
10013
|
function isWithin3(root, candidate) {
|
|
9454
|
-
const delta =
|
|
9455
|
-
return delta === "" || delta !== ".." && !delta.startsWith(`..${
|
|
10014
|
+
const delta = relative12(root, candidate);
|
|
10015
|
+
return delta === "" || delta !== ".." && !delta.startsWith(`..${sep11}`) && !isAbsolute7(delta);
|
|
9456
10016
|
}
|
|
9457
10017
|
function isMissing2(error) {
|
|
9458
10018
|
return typeof error === "object" && error !== null && "code" in error && error.code === "ENOENT";
|
|
@@ -9499,10 +10059,10 @@ function score(metadata, query) {
|
|
|
9499
10059
|
}
|
|
9500
10060
|
|
|
9501
10061
|
// src/skills/tool.ts
|
|
9502
|
-
import { z as
|
|
9503
|
-
var SkillResourceInput =
|
|
9504
|
-
skill:
|
|
9505
|
-
reference:
|
|
10062
|
+
import { z as z16 } from "zod";
|
|
10063
|
+
var SkillResourceInput = z16.object({
|
|
10064
|
+
skill: z16.string().min(1),
|
|
10065
|
+
reference: z16.string().min(1)
|
|
9506
10066
|
}).strict();
|
|
9507
10067
|
function createSkillResourceTool(registry) {
|
|
9508
10068
|
return {
|
|
@@ -9529,97 +10089,97 @@ function createSkillResourceTool(registry) {
|
|
|
9529
10089
|
|
|
9530
10090
|
// src/session/store.ts
|
|
9531
10091
|
import { constants as constants4 } from "fs";
|
|
9532
|
-
import { lstat as
|
|
9533
|
-
import { basename as basename3, dirname as
|
|
10092
|
+
import { lstat as lstat8, mkdir as mkdir9, open as open6, readFile as readFile11, readdir as readdir6, realpath as realpath6, rename as rename5, rm as rm4, stat as stat4 } from "fs/promises";
|
|
10093
|
+
import { basename as basename3, dirname as dirname11, join as join12, relative as relative13, resolve as resolve15, sep as sep12 } from "path";
|
|
9534
10094
|
import { randomUUID as randomUUID6 } from "crypto";
|
|
9535
|
-
import { z as
|
|
10095
|
+
import { z as z17 } from "zod";
|
|
9536
10096
|
var SESSION_VERSION = 3;
|
|
9537
10097
|
var DEFAULT_MAX_SESSION_BYTES = 5 * 1024 * 1024;
|
|
9538
|
-
var SessionIdSchema =
|
|
9539
|
-
var IsoDateSchema =
|
|
9540
|
-
var ToolCallSchema =
|
|
9541
|
-
id:
|
|
9542
|
-
name:
|
|
9543
|
-
input:
|
|
10098
|
+
var SessionIdSchema = z17.string().regex(/^[A-Za-z0-9][A-Za-z0-9._-]{0,127}$/, "Invalid session id");
|
|
10099
|
+
var IsoDateSchema = z17.string().datetime({ offset: true });
|
|
10100
|
+
var ToolCallSchema = z17.object({
|
|
10101
|
+
id: z17.string().min(1).max(256),
|
|
10102
|
+
name: z17.string().min(1).max(256),
|
|
10103
|
+
input: z17.unknown()
|
|
9544
10104
|
}).strict();
|
|
9545
|
-
var TextContentBlockSchema =
|
|
9546
|
-
type:
|
|
9547
|
-
text:
|
|
10105
|
+
var TextContentBlockSchema = z17.object({
|
|
10106
|
+
type: z17.literal("text"),
|
|
10107
|
+
text: z17.string().max(DEFAULT_MAX_SESSION_BYTES)
|
|
9548
10108
|
}).strict();
|
|
9549
|
-
var ImageContentBlockSchema =
|
|
9550
|
-
type:
|
|
9551
|
-
source:
|
|
9552
|
-
mediaType:
|
|
9553
|
-
sha256:
|
|
9554
|
-
bytes:
|
|
9555
|
-
name:
|
|
10109
|
+
var ImageContentBlockSchema = z17.object({
|
|
10110
|
+
type: z17.literal("image"),
|
|
10111
|
+
source: z17.object({ type: z17.literal("file"), path: z17.string().min(1).max(32768) }).strict(),
|
|
10112
|
+
mediaType: z17.enum(["image/png", "image/jpeg", "image/webp"]),
|
|
10113
|
+
sha256: z17.string().regex(/^[a-f0-9]{64}$/),
|
|
10114
|
+
bytes: z17.number().int().positive().max(20 * 1024 * 1024),
|
|
10115
|
+
name: z17.string().min(1).max(255).optional()
|
|
9556
10116
|
}).strict();
|
|
9557
|
-
var UserContentSchema =
|
|
9558
|
-
|
|
9559
|
-
|
|
10117
|
+
var UserContentSchema = z17.union([
|
|
10118
|
+
z17.string().max(DEFAULT_MAX_SESSION_BYTES),
|
|
10119
|
+
z17.array(z17.discriminatedUnion("type", [TextContentBlockSchema, ImageContentBlockSchema])).min(1).max(100)
|
|
9560
10120
|
]);
|
|
9561
10121
|
var MessageMetadataShape = {
|
|
9562
|
-
toolCallId:
|
|
9563
|
-
toolCalls:
|
|
10122
|
+
toolCallId: z17.string().min(1).max(256).optional(),
|
|
10123
|
+
toolCalls: z17.array(ToolCallSchema).max(1e3).optional()
|
|
9564
10124
|
};
|
|
9565
|
-
var MessageSchema =
|
|
9566
|
-
|
|
9567
|
-
|
|
9568
|
-
|
|
10125
|
+
var MessageSchema = z17.discriminatedUnion("role", [
|
|
10126
|
+
z17.object({ role: z17.literal("user"), content: UserContentSchema, ...MessageMetadataShape }).strict(),
|
|
10127
|
+
z17.object({ role: z17.literal("assistant"), content: z17.string().max(DEFAULT_MAX_SESSION_BYTES), ...MessageMetadataShape }).strict(),
|
|
10128
|
+
z17.object({ role: z17.literal("tool"), content: z17.string().max(DEFAULT_MAX_SESSION_BYTES), ...MessageMetadataShape }).strict()
|
|
9569
10129
|
]);
|
|
9570
|
-
var LegacySummarySchema =
|
|
9571
|
-
var CompactBoundarySchema =
|
|
9572
|
-
summary:
|
|
10130
|
+
var LegacySummarySchema = z17.object({ role: z17.literal("system"), content: z17.string().max(DEFAULT_MAX_SESSION_BYTES) }).strict();
|
|
10131
|
+
var CompactBoundarySchema = z17.object({
|
|
10132
|
+
summary: z17.string().min(1).max(DEFAULT_MAX_SESSION_BYTES),
|
|
9573
10133
|
compactedAt: IsoDateSchema
|
|
9574
10134
|
}).strict();
|
|
9575
|
-
var StateSchema =
|
|
9576
|
-
var MemoryLifecycleSchema =
|
|
9577
|
-
status:
|
|
9578
|
-
taskId:
|
|
9579
|
-
messageStart:
|
|
10135
|
+
var StateSchema = z17.enum(["pending", "running", "completed", "failed", "blocked", "cancelled"]);
|
|
10136
|
+
var MemoryLifecycleSchema = z17.object({
|
|
10137
|
+
status: z17.enum(["active", "completed", "abandoned"]),
|
|
10138
|
+
taskId: z17.string().regex(/^memory-[A-Za-z0-9._-]{1,120}$/).optional(),
|
|
10139
|
+
messageStart: z17.number().int().min(0).max(5e4).optional(),
|
|
9580
10140
|
finalizedAt: IsoDateSchema.optional(),
|
|
9581
|
-
transcriptHash:
|
|
10141
|
+
transcriptHash: z17.string().regex(/^[a-f0-9]{64}$/).optional()
|
|
9582
10142
|
}).strict();
|
|
9583
|
-
var SessionBaseSchema =
|
|
10143
|
+
var SessionBaseSchema = z17.object({
|
|
9584
10144
|
sessionId: SessionIdSchema,
|
|
9585
10145
|
createdAt: IsoDateSchema,
|
|
9586
10146
|
updatedAt: IsoDateSchema,
|
|
9587
|
-
workspace:
|
|
9588
|
-
tasks:
|
|
10147
|
+
workspace: z17.object({ path: z17.string().min(1).max(32768) }).strict(),
|
|
10148
|
+
tasks: z17.object({
|
|
9589
10149
|
plan: TaskPlanSchema.optional(),
|
|
9590
10150
|
graph: TaskGraphSchema.optional(),
|
|
9591
|
-
states:
|
|
9592
|
-
results:
|
|
10151
|
+
states: z17.record(z17.string(), StateSchema),
|
|
10152
|
+
results: z17.record(z17.string(), SubagentResultSchema)
|
|
9593
10153
|
}).strict(),
|
|
9594
|
-
models:
|
|
10154
|
+
models: z17.object({ main: z17.string().min(1).max(1024), subagent: z17.string().min(1).max(1024) }).strict(),
|
|
9595
10155
|
permissionMode: PermissionModeSchema,
|
|
9596
10156
|
memory: MemoryLifecycleSchema.optional()
|
|
9597
10157
|
}).strict();
|
|
9598
10158
|
var SessionDocumentV1Schema = SessionBaseSchema.extend({
|
|
9599
|
-
version:
|
|
9600
|
-
conversation:
|
|
10159
|
+
version: z17.literal(1),
|
|
10160
|
+
conversation: z17.object({
|
|
9601
10161
|
summary: LegacySummarySchema.optional(),
|
|
9602
|
-
messages:
|
|
10162
|
+
messages: z17.array(MessageSchema).max(5e4)
|
|
9603
10163
|
}).strict()
|
|
9604
10164
|
}).strict();
|
|
9605
|
-
var ConversationSchema =
|
|
10165
|
+
var ConversationSchema = z17.object({
|
|
9606
10166
|
compact: CompactBoundarySchema.optional(),
|
|
9607
|
-
messages:
|
|
10167
|
+
messages: z17.array(MessageSchema).max(5e4)
|
|
9608
10168
|
}).strict();
|
|
9609
10169
|
var SessionDocumentV2Schema = SessionBaseSchema.extend({
|
|
9610
|
-
version:
|
|
10170
|
+
version: z17.literal(2),
|
|
9611
10171
|
conversation: ConversationSchema
|
|
9612
10172
|
}).strict();
|
|
9613
|
-
var TranscriptStateSchema =
|
|
9614
|
-
var TimelineSchema =
|
|
9615
|
-
version:
|
|
10173
|
+
var TranscriptStateSchema = z17.custom(isTranscriptState, "Invalid transcript state");
|
|
10174
|
+
var TimelineSchema = z17.object({
|
|
10175
|
+
version: z17.literal(1),
|
|
9616
10176
|
state: TranscriptStateSchema
|
|
9617
10177
|
}).strict();
|
|
9618
10178
|
var SessionDocumentSchema = SessionBaseSchema.extend({
|
|
9619
|
-
version:
|
|
9620
|
-
conversation:
|
|
10179
|
+
version: z17.literal(SESSION_VERSION),
|
|
10180
|
+
conversation: z17.object({
|
|
9621
10181
|
compact: CompactBoundarySchema.optional(),
|
|
9622
|
-
messages:
|
|
10182
|
+
messages: z17.array(MessageSchema).max(5e4)
|
|
9623
10183
|
}).strict(),
|
|
9624
10184
|
timeline: TimelineSchema
|
|
9625
10185
|
}).strict();
|
|
@@ -9629,7 +10189,7 @@ var SessionStore = class {
|
|
|
9629
10189
|
#maxBytes;
|
|
9630
10190
|
#maxSessions;
|
|
9631
10191
|
constructor(options) {
|
|
9632
|
-
this.#workspace =
|
|
10192
|
+
this.#workspace = resolve15(options.workspace);
|
|
9633
10193
|
this.#sessions = join12(this.#workspace, ".flavor", "sessions");
|
|
9634
10194
|
this.#maxBytes = options.maxBytes ?? DEFAULT_MAX_SESSION_BYTES;
|
|
9635
10195
|
if (!Number.isSafeInteger(this.#maxBytes) || this.#maxBytes < 256) throw new Error("maxBytes must be an integer of at least 256");
|
|
@@ -9766,7 +10326,7 @@ var SessionStore = class {
|
|
|
9766
10326
|
}
|
|
9767
10327
|
const path = this.#path(sessionId);
|
|
9768
10328
|
try {
|
|
9769
|
-
const metadata = await
|
|
10329
|
+
const metadata = await lstat8(path);
|
|
9770
10330
|
if (!metadata.isFile()) throw new Error("Session path is not a regular file");
|
|
9771
10331
|
await rm4(path);
|
|
9772
10332
|
} catch (error) {
|
|
@@ -9775,7 +10335,7 @@ var SessionStore = class {
|
|
|
9775
10335
|
}
|
|
9776
10336
|
#path(sessionId) {
|
|
9777
10337
|
SessionIdSchema.parse(sessionId);
|
|
9778
|
-
const path =
|
|
10338
|
+
const path = resolve15(this.#sessions, `${sessionId}.jsonl`);
|
|
9779
10339
|
if (!isWithin4(this.#sessions, path)) throw new Error("Invalid session id");
|
|
9780
10340
|
return path;
|
|
9781
10341
|
}
|
|
@@ -9795,14 +10355,14 @@ var SessionStore = class {
|
|
|
9795
10355
|
}
|
|
9796
10356
|
}
|
|
9797
10357
|
async #prepareDirectory() {
|
|
9798
|
-
await assertNoSymlink3(this.#workspace,
|
|
10358
|
+
await assertNoSymlink3(this.#workspace, dirname11(this.#sessions));
|
|
9799
10359
|
await mkdir9(this.#sessions, { recursive: true, mode: 448 });
|
|
9800
10360
|
await this.#assertSafeExistingDirectory();
|
|
9801
10361
|
}
|
|
9802
10362
|
async #assertSafeExistingDirectory() {
|
|
9803
10363
|
await assertNoSymlink3(this.#workspace, this.#sessions);
|
|
9804
|
-
const canonicalRoot = await
|
|
9805
|
-
const canonicalSessions = await
|
|
10364
|
+
const canonicalRoot = await realpath6(this.#workspace);
|
|
10365
|
+
const canonicalSessions = await realpath6(this.#sessions);
|
|
9806
10366
|
if (!isWithin4(canonicalRoot, canonicalSessions)) throw new Error("Session directory escapes the workspace");
|
|
9807
10367
|
}
|
|
9808
10368
|
async #quarantine(path) {
|
|
@@ -9815,7 +10375,7 @@ async function boundedRead(path, maxBytes) {
|
|
|
9815
10375
|
const metadata = await stat4(path);
|
|
9816
10376
|
if (!metadata.isFile()) throw new Error("Session path is not a regular file");
|
|
9817
10377
|
if (metadata.size > maxBytes) throw new Error(`Session exceeds maximum read size of ${maxBytes} bytes`);
|
|
9818
|
-
const value = await
|
|
10378
|
+
const value = await readFile11(path);
|
|
9819
10379
|
if (value.byteLength > maxBytes) throw new Error(`Session exceeds maximum read size of ${maxBytes} bytes`);
|
|
9820
10380
|
return value.toString("utf8");
|
|
9821
10381
|
}
|
|
@@ -9953,31 +10513,31 @@ function isRecord5(value) {
|
|
|
9953
10513
|
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
9954
10514
|
}
|
|
9955
10515
|
async function assertNoSymlink3(root, target) {
|
|
9956
|
-
let cursor =
|
|
9957
|
-
const stop =
|
|
10516
|
+
let cursor = resolve15(target);
|
|
10517
|
+
const stop = resolve15(root);
|
|
9958
10518
|
if (!isWithin4(stop, cursor)) throw new Error("Session path escapes the workspace");
|
|
9959
10519
|
const chain = [];
|
|
9960
10520
|
while (cursor !== stop) {
|
|
9961
10521
|
chain.push(cursor);
|
|
9962
|
-
cursor =
|
|
10522
|
+
cursor = dirname11(cursor);
|
|
9963
10523
|
}
|
|
9964
10524
|
for (const path of chain.reverse()) {
|
|
9965
10525
|
try {
|
|
9966
|
-
if ((await
|
|
10526
|
+
if ((await lstat8(path)).isSymbolicLink()) throw new Error(`Session path contains a symbolic link: ${path}`);
|
|
9967
10527
|
} catch (error) {
|
|
9968
10528
|
if (!isCode6(error, "ENOENT")) throw error;
|
|
9969
10529
|
}
|
|
9970
10530
|
}
|
|
9971
10531
|
}
|
|
9972
10532
|
function isWithin4(root, path) {
|
|
9973
|
-
const value =
|
|
9974
|
-
return value === "" || !value.startsWith(`..${
|
|
10533
|
+
const value = relative13(resolve15(root), resolve15(path));
|
|
10534
|
+
return value === "" || !value.startsWith(`..${sep12}`) && value !== ".." && !value.includes(":");
|
|
9975
10535
|
}
|
|
9976
10536
|
async function canonical2(path) {
|
|
9977
10537
|
try {
|
|
9978
|
-
return await
|
|
10538
|
+
return await realpath6(resolve15(path));
|
|
9979
10539
|
} catch {
|
|
9980
|
-
return
|
|
10540
|
+
return resolve15(path);
|
|
9981
10541
|
}
|
|
9982
10542
|
}
|
|
9983
10543
|
function isCode6(error, code) {
|
|
@@ -10004,45 +10564,45 @@ function sanitize2(input, seen = /* @__PURE__ */ new WeakSet()) {
|
|
|
10004
10564
|
// src/sleep/organizer.ts
|
|
10005
10565
|
import { constants as constants5 } from "fs";
|
|
10006
10566
|
import { randomUUID as randomUUID7 } from "crypto";
|
|
10007
|
-
import { lstat as
|
|
10008
|
-
import { isAbsolute as
|
|
10009
|
-
import { z as
|
|
10010
|
-
var DateKeySchema =
|
|
10011
|
-
var ReviewSchema =
|
|
10012
|
-
title:
|
|
10013
|
-
taskSummary:
|
|
10014
|
-
executionReflection:
|
|
10015
|
-
decisionsAndLearnings:
|
|
10016
|
-
openQuestionsAndRisks:
|
|
10017
|
-
tomorrowPlan:
|
|
10018
|
-
toolUsage:
|
|
10019
|
-
totalCalls:
|
|
10020
|
-
shell:
|
|
10021
|
-
fileRead:
|
|
10022
|
-
fileWrite:
|
|
10023
|
-
search:
|
|
10024
|
-
lsp:
|
|
10025
|
-
subagent:
|
|
10026
|
-
other:
|
|
10567
|
+
import { lstat as lstat9, mkdir as mkdir10, open as open7, readdir as readdir7, realpath as realpath7, rename as rename6, rm as rm5 } from "fs/promises";
|
|
10568
|
+
import { isAbsolute as isAbsolute8, join as join13, relative as relative14, resolve as resolve16, sep as sep13 } from "path";
|
|
10569
|
+
import { z as z18 } from "zod";
|
|
10570
|
+
var DateKeySchema = z18.string().regex(/^\d{4}-\d{2}-\d{2}$/);
|
|
10571
|
+
var ReviewSchema = z18.object({
|
|
10572
|
+
title: z18.string().min(1).max(120),
|
|
10573
|
+
taskSummary: z18.array(z18.string().min(1)).min(1).max(20),
|
|
10574
|
+
executionReflection: z18.array(z18.string().min(1)).min(1).max(20),
|
|
10575
|
+
decisionsAndLearnings: z18.array(z18.string().min(1)).max(20),
|
|
10576
|
+
openQuestionsAndRisks: z18.array(z18.string().min(1)).max(20),
|
|
10577
|
+
tomorrowPlan: z18.array(z18.string().min(1)).min(1).max(20),
|
|
10578
|
+
toolUsage: z18.object({
|
|
10579
|
+
totalCalls: z18.number().int().min(0),
|
|
10580
|
+
shell: z18.number().int().min(0),
|
|
10581
|
+
fileRead: z18.number().int().min(0),
|
|
10582
|
+
fileWrite: z18.number().int().min(0),
|
|
10583
|
+
search: z18.number().int().min(0),
|
|
10584
|
+
lsp: z18.number().int().min(0),
|
|
10585
|
+
subagent: z18.number().int().min(0),
|
|
10586
|
+
other: z18.number().int().min(0)
|
|
10027
10587
|
}).strict(),
|
|
10028
|
-
tokenEstimate:
|
|
10029
|
-
estimatedInput:
|
|
10030
|
-
estimatedOutput:
|
|
10031
|
-
notes:
|
|
10588
|
+
tokenEstimate: z18.object({
|
|
10589
|
+
estimatedInput: z18.number().int().min(0),
|
|
10590
|
+
estimatedOutput: z18.number().int().min(0),
|
|
10591
|
+
notes: z18.string().max(200)
|
|
10032
10592
|
}).strict(),
|
|
10033
|
-
humanIntervention:
|
|
10034
|
-
approvalRequests:
|
|
10035
|
-
questionsAsked:
|
|
10036
|
-
summary:
|
|
10593
|
+
humanIntervention: z18.object({
|
|
10594
|
+
approvalRequests: z18.number().int().min(0),
|
|
10595
|
+
questionsAsked: z18.number().int().min(0),
|
|
10596
|
+
summary: z18.string().max(300)
|
|
10037
10597
|
}).strict(),
|
|
10038
|
-
qualityIndicators:
|
|
10039
|
-
hallucinationAlerts:
|
|
10040
|
-
failuresAndRetries:
|
|
10041
|
-
codeChangeSummary:
|
|
10042
|
-
overallAssessment:
|
|
10598
|
+
qualityIndicators: z18.object({
|
|
10599
|
+
hallucinationAlerts: z18.array(z18.string().min(1)).max(10),
|
|
10600
|
+
failuresAndRetries: z18.array(z18.string().min(1)).max(10),
|
|
10601
|
+
codeChangeSummary: z18.string().max(500),
|
|
10602
|
+
overallAssessment: z18.string().max(200)
|
|
10043
10603
|
}).strict(),
|
|
10044
|
-
knowledgeDeposits:
|
|
10045
|
-
worthRemembering:
|
|
10604
|
+
knowledgeDeposits: z18.object({
|
|
10605
|
+
worthRemembering: z18.array(z18.string().min(1)).max(10)
|
|
10046
10606
|
}).strict()
|
|
10047
10607
|
}).strict();
|
|
10048
10608
|
function localDateKey(date) {
|
|
@@ -10059,7 +10619,7 @@ var ProjectSleepOrganizer = class {
|
|
|
10059
10619
|
#sessions;
|
|
10060
10620
|
#generate;
|
|
10061
10621
|
constructor(options) {
|
|
10062
|
-
this.#workspace =
|
|
10622
|
+
this.#workspace = resolve16(options.workspace);
|
|
10063
10623
|
this.#sleepDirectory = join13(this.#workspace, ".flavor", "sleep");
|
|
10064
10624
|
this.#sessions = options.sessions ?? new SessionStore({ workspace: this.#workspace });
|
|
10065
10625
|
this.#generate = options.generate;
|
|
@@ -10151,8 +10711,8 @@ Previous output was invalid. Repair it and return strict JSON only.`;
|
|
|
10151
10711
|
await mkdir10(flavorDirectory, { recursive: true, mode: 448 });
|
|
10152
10712
|
await assertNotSymlink(this.#sleepDirectory);
|
|
10153
10713
|
await mkdir10(this.#sleepDirectory, { recursive: true, mode: 448 });
|
|
10154
|
-
const root = await
|
|
10155
|
-
const target = await
|
|
10714
|
+
const root = await realpath7(this.#workspace);
|
|
10715
|
+
const target = await realpath7(this.#sleepDirectory);
|
|
10156
10716
|
if (!isWithin5(root, target)) throw new Error("Sleep directory escapes the workspace");
|
|
10157
10717
|
}
|
|
10158
10718
|
async #hasReport(date) {
|
|
@@ -10476,16 +11036,16 @@ function assertDateKey(value) {
|
|
|
10476
11036
|
}
|
|
10477
11037
|
async function assertNotSymlink(path) {
|
|
10478
11038
|
try {
|
|
10479
|
-
if ((await
|
|
11039
|
+
if ((await lstat9(path)).isSymbolicLink()) throw new Error(`Refusing to use symbolic link: ${path}`);
|
|
10480
11040
|
} catch (error) {
|
|
10481
11041
|
if (!hasCode(error, "ENOENT")) throw error;
|
|
10482
11042
|
}
|
|
10483
11043
|
}
|
|
10484
11044
|
function isWithin5(root, candidate) {
|
|
10485
|
-
const path =
|
|
11045
|
+
const path = relative14(root, candidate);
|
|
10486
11046
|
if (path === "") return true;
|
|
10487
|
-
if (path === ".." || path.startsWith(`..${
|
|
10488
|
-
return process.platform !== "win32" ||
|
|
11047
|
+
if (path === ".." || path.startsWith(`..${sep13}`) || isAbsolute8(path)) return false;
|
|
11048
|
+
return process.platform !== "win32" || resolve16(root, path).toLowerCase().startsWith(`${resolve16(root).toLowerCase()}${sep13}`);
|
|
10489
11049
|
}
|
|
10490
11050
|
function hasCode(error, code) {
|
|
10491
11051
|
return typeof error === "object" && error !== null && "code" in error && error.code === code;
|
|
@@ -10534,10 +11094,10 @@ function computeSessionStats(documents) {
|
|
|
10534
11094
|
|
|
10535
11095
|
// src/tools/files.ts
|
|
10536
11096
|
import { constants as constants6 } from "fs";
|
|
10537
|
-
import { mkdir as mkdir11, open as open8, readFile as
|
|
10538
|
-
import { dirname as
|
|
11097
|
+
import { mkdir as mkdir11, open as open8, readFile as readFile12, realpath as realpath8, rename as rename7, rm as rm6, stat as stat5 } from "fs/promises";
|
|
11098
|
+
import { dirname as dirname12, isAbsolute as isAbsolute9, relative as relative15, resolve as resolve17, sep as sep14 } from "path";
|
|
10539
11099
|
import { randomUUID as randomUUID8 } from "crypto";
|
|
10540
|
-
import { z as
|
|
11100
|
+
import { z as z19 } from "zod";
|
|
10541
11101
|
|
|
10542
11102
|
// src/tools/file-diff.ts
|
|
10543
11103
|
var MAX_DIFF_PREVIEW_LINES = 120;
|
|
@@ -10643,18 +11203,34 @@ function truncateLines(lines) {
|
|
|
10643
11203
|
|
|
10644
11204
|
// src/tools/files.ts
|
|
10645
11205
|
var MAX_READ_BYTES = 1048576;
|
|
10646
|
-
var
|
|
10647
|
-
path:
|
|
10648
|
-
maxBytes:
|
|
10649
|
-
startLine:
|
|
10650
|
-
endLine:
|
|
11206
|
+
var ReadInput2 = z19.object({
|
|
11207
|
+
path: z19.string().min(1),
|
|
11208
|
+
maxBytes: z19.coerce.number().int().positive().max(MAX_READ_BYTES).nullable().optional(),
|
|
11209
|
+
startLine: z19.coerce.number().int().positive().nullable().optional(),
|
|
11210
|
+
endLine: z19.coerce.number().int().positive().nullable().optional(),
|
|
10651
11211
|
// Accepts booleans and their string forms (weak-typed models emit "true");
|
|
10652
11212
|
// kept transform-free so the schema converts to JSON Schema for providers.
|
|
10653
|
-
force:
|
|
11213
|
+
force: z19.union([z19.boolean(), z19.string().refine((value) => value === "true" || value === "false")]).nullable().optional()
|
|
10654
11214
|
});
|
|
10655
|
-
var WriteInput =
|
|
10656
|
-
var EditInput =
|
|
10657
|
-
var ApplyPatchInput =
|
|
11215
|
+
var WriteInput = z19.object({ path: z19.string().min(1), content: z19.string() });
|
|
11216
|
+
var EditInput = z19.object({ path: z19.string().min(1), oldText: z19.string().min(1), newText: z19.string() });
|
|
11217
|
+
var ApplyPatchInput = z19.object({ patch: z19.string().min(1) });
|
|
11218
|
+
var FileObservationStore = class {
|
|
11219
|
+
#versions = /* @__PURE__ */ new Map();
|
|
11220
|
+
get(path) {
|
|
11221
|
+
return this.#versions.get(path);
|
|
11222
|
+
}
|
|
11223
|
+
set(path, version) {
|
|
11224
|
+
this.#versions.set(path, version);
|
|
11225
|
+
}
|
|
11226
|
+
async refresh(path) {
|
|
11227
|
+
this.set(path, await fileVersion(path));
|
|
11228
|
+
}
|
|
11229
|
+
async assertCurrent(path, expected) {
|
|
11230
|
+
const current = await fileVersion(path);
|
|
11231
|
+
if (current !== expected) throw new Error(`Stale file: ${path} changed since it was read; read it again before writing`);
|
|
11232
|
+
}
|
|
11233
|
+
};
|
|
10658
11234
|
function createReadTool(workspace, options = {}) {
|
|
10659
11235
|
const guard = createPathGuard(workspace);
|
|
10660
11236
|
const openFile2 = options.openFile ?? ((path) => open8(path, constants6.O_RDONLY));
|
|
@@ -10662,7 +11238,7 @@ function createReadTool(workspace, options = {}) {
|
|
|
10662
11238
|
return {
|
|
10663
11239
|
name: "Read",
|
|
10664
11240
|
description: "Read a UTF-8 text file, optionally restricted to a line range with startLine/endLine",
|
|
10665
|
-
inputSchema:
|
|
11241
|
+
inputSchema: ReadInput2,
|
|
10666
11242
|
paths: (input) => [guard.lexical(input.path)],
|
|
10667
11243
|
execute: async (input, signal) => {
|
|
10668
11244
|
abortIfNeeded(signal);
|
|
@@ -10684,6 +11260,7 @@ function createReadTool(workspace, options = {}) {
|
|
|
10684
11260
|
const textEnd = utf8SafeEnd(contents, windowBytes);
|
|
10685
11261
|
const truncated = contents.length > windowBytes;
|
|
10686
11262
|
const text = contents.subarray(0, textEnd).toString("utf8").replaceAll("\r\n", "\n");
|
|
11263
|
+
options.observations?.set(path, versionOfStat(info));
|
|
10687
11264
|
const lines = text.split("\n");
|
|
10688
11265
|
if (lines.at(-1) === "") lines.pop();
|
|
10689
11266
|
const availableLines = lines.length;
|
|
@@ -10730,6 +11307,7 @@ function createWriteTool(workspace, options = {}) {
|
|
|
10730
11307
|
execute: async (input, signal) => {
|
|
10731
11308
|
abortIfNeeded(signal);
|
|
10732
11309
|
const path = await guard.destination(input.path);
|
|
11310
|
+
const expected = await expectedMutationVersion(path, options.observations);
|
|
10733
11311
|
const previous = await readOptionalPresentationText(path);
|
|
10734
11312
|
if (!previous.exists || previous.text !== void 0) {
|
|
10735
11313
|
await options.beforeCommit?.({
|
|
@@ -10739,7 +11317,9 @@ function createWriteTool(workspace, options = {}) {
|
|
|
10739
11317
|
kind: previous.exists ? "update" : "create"
|
|
10740
11318
|
}, signal);
|
|
10741
11319
|
}
|
|
10742
|
-
await
|
|
11320
|
+
await assertMutationVersion(path, expected, options.observations);
|
|
11321
|
+
await atomicWrite(path, input.content, signal, expected);
|
|
11322
|
+
await options.observations?.refresh(path);
|
|
10743
11323
|
const output = { path, bytes: Buffer.byteLength(input.content) };
|
|
10744
11324
|
if (previous.exists && previous.text === void 0) return output;
|
|
10745
11325
|
return withToolPresentation(output, buildFileChangePresentation(
|
|
@@ -10762,6 +11342,7 @@ function createEditTool(workspace, options = {}) {
|
|
|
10762
11342
|
abortIfNeeded(signal);
|
|
10763
11343
|
const displayPath = guard.lexical(input.path);
|
|
10764
11344
|
const path = await guard.existing(input.path);
|
|
11345
|
+
const expected = await expectedMutationVersion(path, options.observations);
|
|
10765
11346
|
const contents = await readText(path);
|
|
10766
11347
|
const hasCRLF = contents.includes("\r\n");
|
|
10767
11348
|
const norm = (s) => hasCRLF ? s.replace(/\r\n/g, "\n") : s;
|
|
@@ -10777,7 +11358,9 @@ function createEditTool(workspace, options = {}) {
|
|
|
10777
11358
|
const updatedLF = contentsLF.slice(0, first) + newTextLF + contentsLF.slice(first + oldTextLF.length);
|
|
10778
11359
|
const updated = hasCRLF ? updatedLF.replace(/\n/g, "\r\n") : updatedLF;
|
|
10779
11360
|
await options.beforeCommit?.({ path: displayPath, before: contents, after: updated, kind: "update" }, signal);
|
|
10780
|
-
await
|
|
11361
|
+
await assertMutationVersion(path, expected, options.observations);
|
|
11362
|
+
await atomicWrite(path, updated, signal, expected);
|
|
11363
|
+
await options.observations?.refresh(path);
|
|
10781
11364
|
return withToolPresentation(
|
|
10782
11365
|
{ path: displayPath, replacements: 1 },
|
|
10783
11366
|
buildFileChangePresentation(displayPath, contents, updated, "update")
|
|
@@ -10798,10 +11381,12 @@ function createApplyPatchTool(workspace, options = {}) {
|
|
|
10798
11381
|
const prepared = [];
|
|
10799
11382
|
for (const change of changes) {
|
|
10800
11383
|
const path = await guard.destination(change.path);
|
|
11384
|
+
const expected = await expectedMutationVersion(path, options.observations);
|
|
10801
11385
|
const original = change.created ? await requireAbsent(guard, change.path) : await readText(await guard.existing(change.path));
|
|
10802
11386
|
const applied = applyHunks(original, change.hunks);
|
|
10803
|
-
prepared.push({ path, content: applied.content, change, hunks: applied.hunks });
|
|
11387
|
+
prepared.push({ path, content: applied.content, change, hunks: applied.hunks, expected });
|
|
10804
11388
|
}
|
|
11389
|
+
for (const change of prepared) await assertMutationVersion(change.path, change.expected, options.observations);
|
|
10805
11390
|
for (const change of prepared) {
|
|
10806
11391
|
const before = change.change.created ? "" : await readText(change.path);
|
|
10807
11392
|
await options.beforeCommit?.({
|
|
@@ -10810,43 +11395,49 @@ function createApplyPatchTool(workspace, options = {}) {
|
|
|
10810
11395
|
after: change.content,
|
|
10811
11396
|
kind: change.change.created ? "create" : "update"
|
|
10812
11397
|
}, signal);
|
|
10813
|
-
|
|
11398
|
+
}
|
|
11399
|
+
for (const change of prepared) await assertMutationVersion(change.path, change.expected, options.observations);
|
|
11400
|
+
for (const change of prepared) {
|
|
11401
|
+
await atomicWrite(change.path, change.content, signal, change.expected);
|
|
11402
|
+
await options.observations?.refresh(change.path);
|
|
10814
11403
|
}
|
|
10815
11404
|
const first = prepared[0];
|
|
11405
|
+
const primaryPresentation = buildPatchPresentation(first.path, first.change.created, first.hunks);
|
|
11406
|
+
const relatedChanges = prepared.slice(1).map((change) => buildPatchPresentation(change.path, change.change.created, change.hunks));
|
|
10816
11407
|
return withToolPresentation(
|
|
10817
11408
|
{ files: prepared.map((change) => change.path) },
|
|
10818
|
-
|
|
11409
|
+
relatedChanges.length === 0 ? primaryPresentation : { ...primaryPresentation, relatedChanges }
|
|
10819
11410
|
);
|
|
10820
11411
|
}
|
|
10821
11412
|
};
|
|
10822
11413
|
}
|
|
10823
11414
|
function createPathGuard(workspace) {
|
|
10824
|
-
const root =
|
|
11415
|
+
const root = resolve17(workspace);
|
|
10825
11416
|
const lexical = (input) => {
|
|
10826
|
-
const candidate =
|
|
10827
|
-
if (!
|
|
11417
|
+
const candidate = resolve17(root, input);
|
|
11418
|
+
if (!within2(root, candidate)) throw new Error("Path is outside the workspace");
|
|
10828
11419
|
return candidate;
|
|
10829
11420
|
};
|
|
10830
11421
|
return {
|
|
10831
11422
|
lexical,
|
|
10832
11423
|
existing: async (input) => {
|
|
10833
11424
|
const candidate = lexical(input);
|
|
10834
|
-
const physical = await
|
|
10835
|
-
if (!
|
|
11425
|
+
const physical = await realpath8(candidate);
|
|
11426
|
+
if (!within2(await realpath8(root), physical)) throw new Error("Path escapes the workspace through a symlink");
|
|
10836
11427
|
return physical;
|
|
10837
11428
|
},
|
|
10838
11429
|
destination: async (input) => {
|
|
10839
11430
|
const candidate = lexical(input);
|
|
10840
|
-
const physicalRoot = await
|
|
11431
|
+
const physicalRoot = await realpath8(root);
|
|
10841
11432
|
let ancestor = candidate;
|
|
10842
11433
|
while (true) {
|
|
10843
11434
|
try {
|
|
10844
|
-
const physical = await
|
|
10845
|
-
if (!
|
|
11435
|
+
const physical = await realpath8(ancestor);
|
|
11436
|
+
if (!within2(physicalRoot, physical)) throw new Error("Path escapes the workspace through a symlink");
|
|
10846
11437
|
break;
|
|
10847
11438
|
} catch (error) {
|
|
10848
11439
|
if (!isMissing3(error)) throw error;
|
|
10849
|
-
const parent =
|
|
11440
|
+
const parent = dirname12(ancestor);
|
|
10850
11441
|
if (parent === ancestor) throw error;
|
|
10851
11442
|
ancestor = parent;
|
|
10852
11443
|
}
|
|
@@ -10855,9 +11446,9 @@ function createPathGuard(workspace) {
|
|
|
10855
11446
|
}
|
|
10856
11447
|
};
|
|
10857
11448
|
}
|
|
10858
|
-
async function atomicWrite(path, content, signal) {
|
|
11449
|
+
async function atomicWrite(path, content, signal, expectedVersion) {
|
|
10859
11450
|
abortIfNeeded(signal);
|
|
10860
|
-
await mkdir11(
|
|
11451
|
+
await mkdir11(dirname12(path), { recursive: true });
|
|
10861
11452
|
const temporary = `${path}.${randomUUID8()}.tmp`;
|
|
10862
11453
|
try {
|
|
10863
11454
|
const handle = await open8(temporary, constants6.O_CREAT | constants6.O_EXCL | constants6.O_WRONLY, 384);
|
|
@@ -10868,19 +11459,46 @@ async function atomicWrite(path, content, signal) {
|
|
|
10868
11459
|
await handle.close();
|
|
10869
11460
|
}
|
|
10870
11461
|
abortIfNeeded(signal);
|
|
11462
|
+
if (expectedVersion !== void 0 && await fileVersion(path) !== expectedVersion) {
|
|
11463
|
+
throw new Error(`Stale file: ${path} changed while the replacement was being prepared`);
|
|
11464
|
+
}
|
|
10871
11465
|
await rename7(temporary, path);
|
|
10872
11466
|
} finally {
|
|
10873
11467
|
await rm6(temporary, { force: true });
|
|
10874
11468
|
}
|
|
10875
11469
|
}
|
|
10876
11470
|
async function readText(path) {
|
|
10877
|
-
const contents = await
|
|
11471
|
+
const contents = await readFile12(path);
|
|
10878
11472
|
if (isBinary(contents)) throw new Error("Cannot edit binary file as text");
|
|
10879
11473
|
return contents.toString("utf8");
|
|
10880
11474
|
}
|
|
11475
|
+
var ABSENT_VERSION = "absent";
|
|
11476
|
+
async function expectedMutationVersion(path, observations) {
|
|
11477
|
+
const current = await fileVersion(path);
|
|
11478
|
+
const observed = observations?.get(path);
|
|
11479
|
+
if (observed !== void 0 && observed !== current) {
|
|
11480
|
+
throw new Error(`Stale file: ${path} changed since it was read; read it again before writing`);
|
|
11481
|
+
}
|
|
11482
|
+
return current;
|
|
11483
|
+
}
|
|
11484
|
+
async function assertMutationVersion(path, expected, observations) {
|
|
11485
|
+
if (observations !== void 0) await observations.assertCurrent(path, expected);
|
|
11486
|
+
else if (await fileVersion(path) !== expected) throw new Error(`Stale file: ${path} changed since mutation preparation`);
|
|
11487
|
+
}
|
|
11488
|
+
async function fileVersion(path) {
|
|
11489
|
+
try {
|
|
11490
|
+
return versionOfStat(await stat5(path));
|
|
11491
|
+
} catch (error) {
|
|
11492
|
+
if (isMissing3(error)) return ABSENT_VERSION;
|
|
11493
|
+
throw error;
|
|
11494
|
+
}
|
|
11495
|
+
}
|
|
11496
|
+
function versionOfStat(info) {
|
|
11497
|
+
return `${info.dev}:${info.ino}:${info.size}:${info.mtimeMs}:${info.ctimeMs}`;
|
|
11498
|
+
}
|
|
10881
11499
|
async function readOptionalPresentationText(path) {
|
|
10882
11500
|
try {
|
|
10883
|
-
const contents = await
|
|
11501
|
+
const contents = await readFile12(path);
|
|
10884
11502
|
return isBinary(contents) ? { exists: true } : { exists: true, text: contents.toString("utf8") };
|
|
10885
11503
|
} catch (error) {
|
|
10886
11504
|
return isMissing3(error) ? { exists: false } : { exists: true };
|
|
@@ -10964,9 +11582,9 @@ async function readBounded2(path, maxBytes, signal, openFile2) {
|
|
|
10964
11582
|
await handle.close();
|
|
10965
11583
|
}
|
|
10966
11584
|
}
|
|
10967
|
-
function
|
|
10968
|
-
const delta =
|
|
10969
|
-
return delta === "" || !delta.startsWith(`..${
|
|
11585
|
+
function within2(root, candidate) {
|
|
11586
|
+
const delta = relative15(root, candidate);
|
|
11587
|
+
return delta === "" || !delta.startsWith(`..${sep14}`) && delta !== ".." && !isAbsolute9(delta);
|
|
10970
11588
|
}
|
|
10971
11589
|
function forceFlag(value) {
|
|
10972
11590
|
return value === true || value === "true";
|
|
@@ -11187,13 +11805,321 @@ function matchesLines(source, start, expected) {
|
|
|
11187
11805
|
return start >= 0 && start + expected.length <= source.length && expected.every((line, index) => source[start + index] === line);
|
|
11188
11806
|
}
|
|
11189
11807
|
|
|
11808
|
+
// src/tools/web.ts
|
|
11809
|
+
import { lookup } from "dns/promises";
|
|
11810
|
+
import { isIP } from "net";
|
|
11811
|
+
import { request as httpRequest } from "http";
|
|
11812
|
+
import { request as httpsRequest } from "https";
|
|
11813
|
+
import { z as z20 } from "zod";
|
|
11814
|
+
var WebFetchInput = z20.object({
|
|
11815
|
+
url: z20.string().url().max(8192),
|
|
11816
|
+
timeoutMs: z20.coerce.number().int().positive().max(12e4).optional(),
|
|
11817
|
+
maxBytes: z20.coerce.number().int().positive().max(10 * 1024 * 1024).optional()
|
|
11818
|
+
});
|
|
11819
|
+
var WebSearchInput = z20.object({
|
|
11820
|
+
query: z20.string().trim().min(1).max(1e3),
|
|
11821
|
+
maxResults: z20.coerce.number().int().min(1).max(20).optional()
|
|
11822
|
+
});
|
|
11823
|
+
function createWebFetchTool() {
|
|
11824
|
+
return {
|
|
11825
|
+
name: "WebFetch",
|
|
11826
|
+
description: "Fetch a public HTTP(S) page with SSRF protection and return readable text",
|
|
11827
|
+
inputSchema: WebFetchInput,
|
|
11828
|
+
outputSchema: z20.object({ url: z20.string(), status: z20.number(), contentType: z20.string(), content: z20.string(), truncated: z20.boolean() }),
|
|
11829
|
+
paths: () => [],
|
|
11830
|
+
summarize: (input) => input.url,
|
|
11831
|
+
presentCall: (input) => ({ kind: "web", title: `Fetching ${new URL(input.url).hostname}`, url: input.url }),
|
|
11832
|
+
permissions: () => ({ paths: [] }),
|
|
11833
|
+
execute: async (input, signal) => fetchPublic(input.url, {
|
|
11834
|
+
signal,
|
|
11835
|
+
timeoutMs: input.timeoutMs ?? 3e4,
|
|
11836
|
+
maxBytes: input.maxBytes ?? 2 * 1024 * 1024
|
|
11837
|
+
}),
|
|
11838
|
+
renderForModel: (output) => [
|
|
11839
|
+
`URL: ${output.url}`,
|
|
11840
|
+
`Status: ${output.status}`,
|
|
11841
|
+
`Content-Type: ${output.contentType}`,
|
|
11842
|
+
output.truncated ? "[Response truncated]" : "",
|
|
11843
|
+
"",
|
|
11844
|
+
output.content
|
|
11845
|
+
].filter((line, index) => line !== "" || index >= 4).join("\n"),
|
|
11846
|
+
presentResult: (output) => ({ kind: "web", title: `Fetched ${new URL(output.url).hostname}`, url: output.url, summary: `${output.status} \xB7 ${output.contentType}` })
|
|
11847
|
+
};
|
|
11848
|
+
}
|
|
11849
|
+
function createWebSearchTool(provider = createDefaultWebSearchProvider()) {
|
|
11850
|
+
return {
|
|
11851
|
+
name: "WebSearch",
|
|
11852
|
+
description: "Search the public web and return titles, URLs, and snippets",
|
|
11853
|
+
inputSchema: WebSearchInput,
|
|
11854
|
+
outputSchema: z20.object({ query: z20.string(), results: z20.array(z20.object({ title: z20.string(), url: z20.string(), snippet: z20.string() })) }),
|
|
11855
|
+
paths: () => [],
|
|
11856
|
+
summarize: (input) => input.query,
|
|
11857
|
+
presentCall: (input) => ({ kind: "web", title: `Searching: ${input.query}` }),
|
|
11858
|
+
permissions: () => ({ paths: [] }),
|
|
11859
|
+
execute: async (input, signal) => ({ query: input.query, results: [...await provider.search(input.query, input.maxResults ?? 8, signal)] }),
|
|
11860
|
+
renderForModel: (output) => output.results.length === 0 ? `No web results found for: ${output.query}` : output.results.map((item, index) => `${index + 1}. ${item.title}
|
|
11861
|
+
${item.url}
|
|
11862
|
+
${item.snippet}`).join("\n\n"),
|
|
11863
|
+
presentResult: (output) => ({ kind: "web", title: `Search: ${output.query}`, summary: `${output.results.length} results`, items: output.results })
|
|
11864
|
+
};
|
|
11865
|
+
}
|
|
11866
|
+
function createDefaultWebSearchProvider(fetcher = fetchPublic) {
|
|
11867
|
+
return new MultiSourceWebSearchProvider(fetcher);
|
|
11868
|
+
}
|
|
11869
|
+
var MultiSourceWebSearchProvider = class {
|
|
11870
|
+
constructor(fetcher) {
|
|
11871
|
+
this.fetcher = fetcher;
|
|
11872
|
+
}
|
|
11873
|
+
fetcher;
|
|
11874
|
+
async search(query, maxResults, signal) {
|
|
11875
|
+
const sources = [
|
|
11876
|
+
{ name: "DuckDuckGo Lite", url: `https://lite.duckduckgo.com/lite/?q=${encodeURIComponent(query)}`, parse: parseDuckDuckGo },
|
|
11877
|
+
{ name: "Bing", url: `https://www.bing.com/search?q=${encodeURIComponent(query)}`, parse: parseBing }
|
|
11878
|
+
];
|
|
11879
|
+
const failures = [];
|
|
11880
|
+
for (const source of sources) {
|
|
11881
|
+
try {
|
|
11882
|
+
const response = await this.fetcher(source.url, {
|
|
11883
|
+
signal,
|
|
11884
|
+
timeoutMs: 3e4,
|
|
11885
|
+
maxBytes: 2 * 1024 * 1024,
|
|
11886
|
+
rawHtml: true
|
|
11887
|
+
});
|
|
11888
|
+
if (response.status < 200 || response.status >= 300) {
|
|
11889
|
+
failures.push(`${source.name}: HTTP ${response.status}`);
|
|
11890
|
+
continue;
|
|
11891
|
+
}
|
|
11892
|
+
const results = source.parse(response.content);
|
|
11893
|
+
if (results.length > 0) return results.slice(0, maxResults);
|
|
11894
|
+
failures.push(`${source.name}: no results parsed`);
|
|
11895
|
+
} catch (error) {
|
|
11896
|
+
if (signal.aborted) throw error;
|
|
11897
|
+
failures.push(`${source.name}: ${error instanceof Error ? error.message : String(error)}`);
|
|
11898
|
+
}
|
|
11899
|
+
}
|
|
11900
|
+
throw new Error(`Web search providers failed (${failures.join("; ")})`);
|
|
11901
|
+
}
|
|
11902
|
+
};
|
|
11903
|
+
async function fetchPublic(initialUrl, options) {
|
|
11904
|
+
let current = new URL(initialUrl);
|
|
11905
|
+
for (let redirects = 0; redirects <= 5; redirects += 1) {
|
|
11906
|
+
validateUrl(current);
|
|
11907
|
+
const addresses = await lookup(normalizedHostname(current.hostname), { all: true, verbatim: true });
|
|
11908
|
+
if (addresses.length === 0) throw new Error("Host did not resolve");
|
|
11909
|
+
if (addresses.some((entry2) => isBlockedResolvedAddress(entry2.address, current.hostname))) {
|
|
11910
|
+
throw new Error("Blocked private or special network destination");
|
|
11911
|
+
}
|
|
11912
|
+
const response = await requestAddress(current, addresses[0].address, options);
|
|
11913
|
+
if (response.redirect !== void 0) {
|
|
11914
|
+
if (redirects === 5) throw new Error("Too many redirects");
|
|
11915
|
+
current = new URL(response.redirect, current);
|
|
11916
|
+
continue;
|
|
11917
|
+
}
|
|
11918
|
+
const contentType = response.contentType;
|
|
11919
|
+
const decoded = new TextDecoder(charset(contentType), { fatal: false }).decode(response.body);
|
|
11920
|
+
const content = !options.rawHtml && /text\/html|application\/xhtml\+xml/i.test(contentType) ? htmlToText(decoded) : decoded;
|
|
11921
|
+
return { url: current.href, status: response.status, contentType, content, truncated: response.truncated };
|
|
11922
|
+
}
|
|
11923
|
+
throw new Error("Too many redirects");
|
|
11924
|
+
}
|
|
11925
|
+
async function requestAddress(url, address, options) {
|
|
11926
|
+
return new Promise((resolvePromise, reject) => {
|
|
11927
|
+
let settled = false;
|
|
11928
|
+
const finish = (value) => {
|
|
11929
|
+
if (settled) return;
|
|
11930
|
+
settled = true;
|
|
11931
|
+
clearTimeout(totalTimer);
|
|
11932
|
+
resolvePromise(value);
|
|
11933
|
+
};
|
|
11934
|
+
const fail = (error) => {
|
|
11935
|
+
if (settled) return;
|
|
11936
|
+
settled = true;
|
|
11937
|
+
clearTimeout(totalTimer);
|
|
11938
|
+
reject(error);
|
|
11939
|
+
};
|
|
11940
|
+
const secure = url.protocol === "https:";
|
|
11941
|
+
const request = (secure ? httpsRequest : httpRequest)({
|
|
11942
|
+
protocol: url.protocol,
|
|
11943
|
+
hostname: address,
|
|
11944
|
+
port: url.port || (secure ? 443 : 80),
|
|
11945
|
+
method: "GET",
|
|
11946
|
+
path: `${url.pathname}${url.search}`,
|
|
11947
|
+
headers: { host: url.host, accept: "text/html,application/json,text/plain;q=0.9,*/*;q=0.5", "accept-encoding": "identity", "user-agent": "Flavor-Code/1" },
|
|
11948
|
+
...secure ? { servername: normalizedHostname(url.hostname) } : {},
|
|
11949
|
+
signal: options.signal
|
|
11950
|
+
}, (response) => {
|
|
11951
|
+
const status = response.statusCode ?? 0;
|
|
11952
|
+
const location = response.headers.location;
|
|
11953
|
+
if (location !== void 0 && [301, 302, 303, 307, 308].includes(status)) {
|
|
11954
|
+
response.resume();
|
|
11955
|
+
finish({ status, contentType: "", body: new Uint8Array(), truncated: false, redirect: location });
|
|
11956
|
+
return;
|
|
11957
|
+
}
|
|
11958
|
+
const chunks = [];
|
|
11959
|
+
let bytes = 0;
|
|
11960
|
+
let truncated = false;
|
|
11961
|
+
response.on("data", (chunk) => {
|
|
11962
|
+
if (bytes >= options.maxBytes) {
|
|
11963
|
+
truncated = true;
|
|
11964
|
+
finish({ status, contentType: String(response.headers["content-type"] ?? "application/octet-stream"), body: Buffer.concat(chunks), truncated });
|
|
11965
|
+
response.destroy();
|
|
11966
|
+
return;
|
|
11967
|
+
}
|
|
11968
|
+
const accepted = chunk.subarray(0, options.maxBytes - bytes);
|
|
11969
|
+
chunks.push(accepted);
|
|
11970
|
+
bytes += accepted.length;
|
|
11971
|
+
if (accepted.length < chunk.length) truncated = true;
|
|
11972
|
+
});
|
|
11973
|
+
response.once("end", () => finish({
|
|
11974
|
+
status,
|
|
11975
|
+
contentType: String(response.headers["content-type"] ?? "application/octet-stream"),
|
|
11976
|
+
body: Buffer.concat(chunks),
|
|
11977
|
+
truncated
|
|
11978
|
+
}));
|
|
11979
|
+
response.once("error", fail);
|
|
11980
|
+
});
|
|
11981
|
+
const totalTimer = setTimeout(() => request.destroy(new Error(`Web request timed out after ${options.timeoutMs}ms`)), options.timeoutMs);
|
|
11982
|
+
totalTimer.unref();
|
|
11983
|
+
request.once("error", fail);
|
|
11984
|
+
request.end();
|
|
11985
|
+
});
|
|
11986
|
+
}
|
|
11987
|
+
function validateUrl(url) {
|
|
11988
|
+
if (url.protocol !== "http:" && url.protocol !== "https:") throw new Error("Only HTTP(S) URLs are supported");
|
|
11989
|
+
if (url.username !== "" || url.password !== "") throw new Error("URL credentials are not allowed");
|
|
11990
|
+
const hostname = normalizedHostname(url.hostname).toLowerCase();
|
|
11991
|
+
if (hostname === "localhost" || hostname.endsWith(".localhost")) throw new Error("Blocked private destination");
|
|
11992
|
+
const port = url.port === "" ? url.protocol === "https:" ? 443 : 80 : Number(url.port);
|
|
11993
|
+
if (![80, 443].includes(port)) throw new Error(`Blocked network port: ${port}`);
|
|
11994
|
+
if (isIP(hostname) && isBlockedAddress(hostname)) throw new Error("Blocked private or special network destination");
|
|
11995
|
+
}
|
|
11996
|
+
function isBlockedResolvedAddress(input, hostname) {
|
|
11997
|
+
return isBlockedAddress(input, { allowBenchmarkCarrier: isIP(normalizedHostname(hostname)) === 0 });
|
|
11998
|
+
}
|
|
11999
|
+
function isBlockedAddress(input, options = {}) {
|
|
12000
|
+
const address = input.toLowerCase().replace(/^\[|\]$/g, "").split("%")[0];
|
|
12001
|
+
if (address === "::" || address === "::1") return true;
|
|
12002
|
+
const mappedIpv4 = extractMappedIpv4(address);
|
|
12003
|
+
if (mappedIpv4 !== void 0) return isBlockedAddress(mappedIpv4, options);
|
|
12004
|
+
if (address.includes(":")) {
|
|
12005
|
+
return address.startsWith("fc") || address.startsWith("fd") || /^fe[89ab]/.test(address) || address.startsWith("ff") || address.startsWith("2001:db8:");
|
|
12006
|
+
}
|
|
12007
|
+
const parts = address.split(".").map(Number);
|
|
12008
|
+
if (parts.length !== 4 || parts.some((part) => !Number.isInteger(part) || part < 0 || part > 255)) return true;
|
|
12009
|
+
const [a, b, c] = parts;
|
|
12010
|
+
return a === 0 || a === 10 || a === 127 || a >= 224 || a === 100 && b >= 64 && b <= 127 || a === 169 && b === 254 || a === 172 && b >= 16 && b <= 31 || a === 192 && b === 168 || a === 192 && b === 0 && (c === 0 || c === 2) || a === 192 && b === 88 && c === 99 || !options.allowBenchmarkCarrier && a === 198 && (b === 18 || b === 19) || a === 198 && b === 51 && c === 100 || a === 203 && b === 0 && c === 113;
|
|
12011
|
+
}
|
|
12012
|
+
function extractMappedIpv4(address) {
|
|
12013
|
+
if (isIP(address) !== 6) return void 0;
|
|
12014
|
+
let canonical3;
|
|
12015
|
+
try {
|
|
12016
|
+
canonical3 = new URL(`http://[${address}]/`).hostname.replace(/^\[|\]$/g, "");
|
|
12017
|
+
} catch {
|
|
12018
|
+
return void 0;
|
|
12019
|
+
}
|
|
12020
|
+
const match = /^::ffff:([0-9a-f]{1,4}):([0-9a-f]{1,4})$/i.exec(canonical3);
|
|
12021
|
+
if (match === null) return void 0;
|
|
12022
|
+
const high = Number.parseInt(match[1], 16);
|
|
12023
|
+
const low = Number.parseInt(match[2], 16);
|
|
12024
|
+
return `${high >>> 8}.${high & 255}.${low >>> 8}.${low & 255}`;
|
|
12025
|
+
}
|
|
12026
|
+
function normalizedHostname(hostname) {
|
|
12027
|
+
return hostname.replace(/^\[|\]$/g, "");
|
|
12028
|
+
}
|
|
12029
|
+
function htmlToText(html) {
|
|
12030
|
+
let text = html.replace(/<!--[\s\S]*?-->/g, "").replace(/<(script|style|noscript|svg)\b[^>]*>[\s\S]*?<\/\1>/gi, "").replace(/<h1\b[^>]*>([\s\S]*?)<\/h1>/gi, "\n# $1\n").replace(/<h2\b[^>]*>([\s\S]*?)<\/h2>/gi, "\n## $1\n").replace(/<h3\b[^>]*>([\s\S]*?)<\/h3>/gi, "\n### $1\n").replace(/<li\b[^>]*>/gi, "\n- ").replace(/<(p|div|section|article|main|header|footer|ul|ol|table|tr)\b[^>]*>/gi, "\n").replace(/<\/(p|div|section|article|main|header|footer|li|ul|ol|table|tr)>/gi, "\n").replace(/<br\s*\/?>/gi, "\n").replace(/<[^>]+>/g, "");
|
|
12031
|
+
text = decodeEntities(text).replace(/\r/g, "").replace(/[ \t]+\n/g, "\n").replace(/\n[ \t]+/g, "\n").replace(/\n{3,}/g, "\n\n");
|
|
12032
|
+
return text.trim();
|
|
12033
|
+
}
|
|
12034
|
+
function parseDuckDuckGo(html) {
|
|
12035
|
+
const results = [];
|
|
12036
|
+
const pattern = /<a[^>]*class="[^"]*result__a[^"]*"[^>]*href="([^"]+)"[^>]*>([\s\S]*?)<\/a>[\s\S]*?<a[^>]*class="[^"]*result__snippet[^"]*"[^>]*>([\s\S]*?)<\/a>/gi;
|
|
12037
|
+
for (const match of html.matchAll(pattern)) {
|
|
12038
|
+
const url = unwrapDuckDuckGoUrl(match[1]);
|
|
12039
|
+
if (url !== void 0) results.push({ title: htmlToText(match[2]), url, snippet: htmlToText(match[3]) });
|
|
12040
|
+
}
|
|
12041
|
+
const litePattern = /<a\b[^>]*href=(["'])(.*?)\1[^>]*class=(["'])[^"']*\bresult-link\b[^"']*\3[^>]*>([\s\S]*?)<\/a>[\s\S]*?<td\b[^>]*class=(["'])[^"']*\bresult-snippet\b[^"']*\5[^>]*>([\s\S]*?)<\/td>/gi;
|
|
12042
|
+
for (const match of html.matchAll(litePattern)) {
|
|
12043
|
+
const url = unwrapDuckDuckGoUrl(match[2]);
|
|
12044
|
+
if (url !== void 0) results.push({ title: htmlToText(match[4]), url, snippet: htmlToText(match[6]) });
|
|
12045
|
+
}
|
|
12046
|
+
return uniqueResults(results);
|
|
12047
|
+
}
|
|
12048
|
+
function parseBing(html) {
|
|
12049
|
+
const results = [];
|
|
12050
|
+
const itemPattern = /<li\b[^>]*class=(["'])[^"']*\bb_algo\b[^"']*\1[^>]*>([\s\S]*?)<\/li>/gi;
|
|
12051
|
+
for (const itemMatch of html.matchAll(itemPattern)) {
|
|
12052
|
+
const item = itemMatch[2];
|
|
12053
|
+
const titleMatch = /<h2\b[^>]*>[\s\S]*?<a\b([^>]*)>([\s\S]*?)<\/a>[\s\S]*?<\/h2>/i.exec(item);
|
|
12054
|
+
const snippetMatch = /<div\b[^>]*class=(["'])[^"']*\bb_caption\b[^"']*\1[^>]*>[\s\S]*?<p\b[^>]*>([\s\S]*?)<\/p>/i.exec(item);
|
|
12055
|
+
if (titleMatch === null) continue;
|
|
12056
|
+
const href = readHtmlAttribute(titleMatch[1], "href");
|
|
12057
|
+
const url = href === void 0 ? void 0 : unwrapBingUrl(href);
|
|
12058
|
+
if (url === void 0) continue;
|
|
12059
|
+
results.push({ title: htmlToText(titleMatch[2]), url, snippet: snippetMatch === null ? "" : htmlToText(snippetMatch[2]) });
|
|
12060
|
+
}
|
|
12061
|
+
return uniqueResults(results);
|
|
12062
|
+
}
|
|
12063
|
+
function unwrapDuckDuckGoUrl(input) {
|
|
12064
|
+
try {
|
|
12065
|
+
const parsed = new URL(decodeEntities(input), "https://lite.duckduckgo.com");
|
|
12066
|
+
return publicResultUrl(parsed.searchParams.get("uddg") ?? parsed.href);
|
|
12067
|
+
} catch {
|
|
12068
|
+
return void 0;
|
|
12069
|
+
}
|
|
12070
|
+
}
|
|
12071
|
+
function unwrapBingUrl(input) {
|
|
12072
|
+
try {
|
|
12073
|
+
const parsed = new URL(decodeEntities(input), "https://www.bing.com");
|
|
12074
|
+
const encoded = parsed.hostname.endsWith("bing.com") && parsed.pathname === "/ck/a" ? parsed.searchParams.get("u") : null;
|
|
12075
|
+
if (encoded?.startsWith("a1")) {
|
|
12076
|
+
const target = Buffer.from(encoded.slice(2), "base64url").toString("utf8");
|
|
12077
|
+
return publicResultUrl(target);
|
|
12078
|
+
}
|
|
12079
|
+
return publicResultUrl(parsed.href);
|
|
12080
|
+
} catch {
|
|
12081
|
+
return void 0;
|
|
12082
|
+
}
|
|
12083
|
+
}
|
|
12084
|
+
function publicResultUrl(input) {
|
|
12085
|
+
try {
|
|
12086
|
+
const parsed = new URL(input);
|
|
12087
|
+
return parsed.protocol === "http:" || parsed.protocol === "https:" ? parsed.href : void 0;
|
|
12088
|
+
} catch {
|
|
12089
|
+
return void 0;
|
|
12090
|
+
}
|
|
12091
|
+
}
|
|
12092
|
+
function readHtmlAttribute(attributes, name) {
|
|
12093
|
+
const match = new RegExp(`\\b${name}\\s*=\\s*(?:"([^"]*)"|'([^']*)')`, "i").exec(attributes);
|
|
12094
|
+
return match?.[1] ?? match?.[2];
|
|
12095
|
+
}
|
|
12096
|
+
function uniqueResults(results) {
|
|
12097
|
+
const seen = /* @__PURE__ */ new Set();
|
|
12098
|
+
return results.filter((result) => {
|
|
12099
|
+
if (seen.has(result.url)) return false;
|
|
12100
|
+
seen.add(result.url);
|
|
12101
|
+
return true;
|
|
12102
|
+
});
|
|
12103
|
+
}
|
|
12104
|
+
function decodeEntities(text) {
|
|
12105
|
+
const named = { amp: "&", lt: "<", gt: ">", quot: '"', apos: "'", nbsp: " " };
|
|
12106
|
+
return text.replace(/&(#x[0-9a-f]+|#\d+|[a-z]+);/gi, (_all, entity) => {
|
|
12107
|
+
if (entity.startsWith("#x")) return String.fromCodePoint(Number.parseInt(entity.slice(2), 16));
|
|
12108
|
+
if (entity.startsWith("#")) return String.fromCodePoint(Number.parseInt(entity.slice(1), 10));
|
|
12109
|
+
return named[entity.toLowerCase()] ?? `&${entity};`;
|
|
12110
|
+
});
|
|
12111
|
+
}
|
|
12112
|
+
function charset(contentType) {
|
|
12113
|
+
return /charset\s*=\s*(?:utf-16|utf-16le)/i.test(contentType) ? "utf-16le" : "utf-8";
|
|
12114
|
+
}
|
|
12115
|
+
|
|
11190
12116
|
// src/tools/lsp.ts
|
|
11191
12117
|
import { spawn as spawn5 } from "child_process";
|
|
11192
12118
|
import { existsSync as existsSync2 } from "fs";
|
|
11193
|
-
import { access, constants as constants7, readFile as
|
|
11194
|
-
import { basename as basename4, dirname as
|
|
12119
|
+
import { access, constants as constants7, readFile as readFile13 } from "fs/promises";
|
|
12120
|
+
import { basename as basename4, dirname as dirname13, extname, isAbsolute as isAbsolute10, relative as relative16, resolve as resolve18, sep as sep15 } from "path";
|
|
11195
12121
|
import { fileURLToPath as fileURLToPath3, pathToFileURL as pathToFileURL2 } from "url";
|
|
11196
|
-
import { z as
|
|
12122
|
+
import { z as z21 } from "zod";
|
|
11197
12123
|
var LSP_REQUEST_TIMEOUT_MS = 15e3;
|
|
11198
12124
|
var LspConnection = class {
|
|
11199
12125
|
#process;
|
|
@@ -11290,8 +12216,8 @@ var LspConnection = class {
|
|
|
11290
12216
|
const id = this.#nextId++;
|
|
11291
12217
|
const request = { jsonrpc: "2.0", id, method, params };
|
|
11292
12218
|
const timeout = AbortSignal.timeout(LSP_REQUEST_TIMEOUT_MS);
|
|
11293
|
-
const promise = new Promise((
|
|
11294
|
-
this.#pending.set(id, { resolve:
|
|
12219
|
+
const promise = new Promise((resolve25, reject) => {
|
|
12220
|
+
this.#pending.set(id, { resolve: resolve25, reject });
|
|
11295
12221
|
timeout.addEventListener("abort", () => {
|
|
11296
12222
|
if (this.#pending.has(id)) {
|
|
11297
12223
|
this.#pending.delete(id);
|
|
@@ -11356,7 +12282,7 @@ var LspConnection = class {
|
|
|
11356
12282
|
const url = new URL(uri);
|
|
11357
12283
|
if (url.protocol !== "file:") return;
|
|
11358
12284
|
const filePath = fileURLToPath3(url);
|
|
11359
|
-
const text = await
|
|
12285
|
+
const text = await readFile13(filePath, "utf8");
|
|
11360
12286
|
const current = this.#documents.get(uri);
|
|
11361
12287
|
if (current === void 0) {
|
|
11362
12288
|
this.#documents.set(uri, { text, version: 1 });
|
|
@@ -11407,8 +12333,8 @@ var LspBuffer = class {
|
|
|
11407
12333
|
}
|
|
11408
12334
|
}
|
|
11409
12335
|
};
|
|
11410
|
-
var TYPESCRIPT_CLI =
|
|
11411
|
-
|
|
12336
|
+
var TYPESCRIPT_CLI = resolve18(
|
|
12337
|
+
dirname13(fileURLToPath3(import.meta.resolve("typescript/package.json"))),
|
|
11412
12338
|
"lib",
|
|
11413
12339
|
"tsc.js"
|
|
11414
12340
|
);
|
|
@@ -11449,7 +12375,7 @@ var RealLspManager = class {
|
|
|
11449
12375
|
#serverConfigs;
|
|
11450
12376
|
#onStatus;
|
|
11451
12377
|
constructor(options) {
|
|
11452
|
-
this.#workspace =
|
|
12378
|
+
this.#workspace = resolve18(options.workspace);
|
|
11453
12379
|
this.#serverConfigs = options.serverConfigs ?? KNOWN_SERVERS;
|
|
11454
12380
|
this.#onStatus = options.onStatus;
|
|
11455
12381
|
}
|
|
@@ -11498,7 +12424,7 @@ var RealLspManager = class {
|
|
|
11498
12424
|
if (pending !== void 0) return pending;
|
|
11499
12425
|
const config = this.#serverConfigs.find((c) => c.language === language);
|
|
11500
12426
|
if (config === void 0) return void 0;
|
|
11501
|
-
const hasRoot = config.rootFiles.some((file) => existsSync2(
|
|
12427
|
+
const hasRoot = config.rootFiles.some((file) => existsSync2(resolve18(this.#workspace, file)));
|
|
11502
12428
|
if (!hasRoot) return void 0;
|
|
11503
12429
|
const startPromise = this.#startServer(config);
|
|
11504
12430
|
this.#pendingStarts.set(language, startPromise);
|
|
@@ -11527,24 +12453,24 @@ var RealLspManager = class {
|
|
|
11527
12453
|
return connection;
|
|
11528
12454
|
}
|
|
11529
12455
|
};
|
|
11530
|
-
var LspFindRefsInput =
|
|
11531
|
-
file:
|
|
11532
|
-
line:
|
|
11533
|
-
character:
|
|
12456
|
+
var LspFindRefsInput = z21.object({
|
|
12457
|
+
file: z21.string().min(1),
|
|
12458
|
+
line: z21.coerce.number().int().nonnegative(),
|
|
12459
|
+
character: z21.coerce.number().int().nonnegative()
|
|
11534
12460
|
});
|
|
11535
|
-
var LspHoverInput =
|
|
11536
|
-
file:
|
|
11537
|
-
line:
|
|
11538
|
-
character:
|
|
12461
|
+
var LspHoverInput = z21.object({
|
|
12462
|
+
file: z21.string().min(1),
|
|
12463
|
+
line: z21.coerce.number().int().nonnegative(),
|
|
12464
|
+
character: z21.coerce.number().int().nonnegative()
|
|
11539
12465
|
});
|
|
11540
|
-
var LspDiagnosticsInput =
|
|
11541
|
-
file:
|
|
12466
|
+
var LspDiagnosticsInput = z21.object({
|
|
12467
|
+
file: z21.string().min(1)
|
|
11542
12468
|
});
|
|
11543
12469
|
function createLspTools(workspace, options = {}) {
|
|
11544
|
-
const root =
|
|
12470
|
+
const root = resolve18(workspace);
|
|
11545
12471
|
const manager = options.manager ?? new RealLspManager({ workspace: root, onStatus: options.onStatus });
|
|
11546
12472
|
const guard = (input) => {
|
|
11547
|
-
const candidate =
|
|
12473
|
+
const candidate = resolve18(root, input);
|
|
11548
12474
|
if (!isWithin6(root, candidate)) throw new Error("Path is outside the workspace");
|
|
11549
12475
|
return candidate;
|
|
11550
12476
|
};
|
|
@@ -11620,8 +12546,8 @@ function languageIdForPath(filePath) {
|
|
|
11620
12546
|
}
|
|
11621
12547
|
}
|
|
11622
12548
|
function isWithin6(root, candidate) {
|
|
11623
|
-
const delta =
|
|
11624
|
-
return delta === "" || !delta.startsWith(`..${
|
|
12549
|
+
const delta = relative16(root, candidate);
|
|
12550
|
+
return delta === "" || !delta.startsWith(`..${sep15}`) && delta !== ".." && !isAbsolute10(delta);
|
|
11625
12551
|
}
|
|
11626
12552
|
function uriToPath(uri) {
|
|
11627
12553
|
try {
|
|
@@ -11653,18 +12579,18 @@ function severityLabel(severity) {
|
|
|
11653
12579
|
}
|
|
11654
12580
|
|
|
11655
12581
|
// src/tools/ask-user-question.ts
|
|
11656
|
-
import { z as
|
|
11657
|
-
var QuestionOptionSchema =
|
|
11658
|
-
label:
|
|
11659
|
-
description:
|
|
12582
|
+
import { z as z22 } from "zod";
|
|
12583
|
+
var QuestionOptionSchema = z22.object({
|
|
12584
|
+
label: z22.string().trim().min(1),
|
|
12585
|
+
description: z22.string().trim().min(1)
|
|
11660
12586
|
});
|
|
11661
|
-
var QuestionSchema =
|
|
11662
|
-
question:
|
|
11663
|
-
header:
|
|
11664
|
-
options:
|
|
12587
|
+
var QuestionSchema = z22.object({
|
|
12588
|
+
question: z22.string().trim().min(1),
|
|
12589
|
+
header: z22.string().trim().min(1),
|
|
12590
|
+
options: z22.array(QuestionOptionSchema).min(1).max(4)
|
|
11665
12591
|
});
|
|
11666
|
-
var AskUserQuestionInput =
|
|
11667
|
-
questions:
|
|
12592
|
+
var AskUserQuestionInput = z22.object({
|
|
12593
|
+
questions: z22.array(QuestionSchema).min(1).max(4)
|
|
11668
12594
|
});
|
|
11669
12595
|
function hookAnswersFromUpdatedInput(updatedInput, questions) {
|
|
11670
12596
|
if (typeof updatedInput !== "object" || updatedInput === null) return void 0;
|
|
@@ -11693,7 +12619,7 @@ var QuestionBridge = class {
|
|
|
11693
12619
|
ask(questions, signal) {
|
|
11694
12620
|
if (this.#pending !== void 0) return Promise.reject(new Error("A question is already pending"));
|
|
11695
12621
|
if (signal.aborted) return Promise.reject(signal.reason);
|
|
11696
|
-
return new Promise((
|
|
12622
|
+
return new Promise((resolve25, reject) => {
|
|
11697
12623
|
const onAbort = () => {
|
|
11698
12624
|
this.#pending = void 0;
|
|
11699
12625
|
reject(signal.reason instanceof Error ? signal.reason : new Error(String(signal.reason)));
|
|
@@ -11705,7 +12631,7 @@ var QuestionBridge = class {
|
|
|
11705
12631
|
signal.removeEventListener("abort", onAbort);
|
|
11706
12632
|
this.#pending = void 0;
|
|
11707
12633
|
this.#onChange?.();
|
|
11708
|
-
|
|
12634
|
+
resolve25(answers);
|
|
11709
12635
|
},
|
|
11710
12636
|
reject: (error) => {
|
|
11711
12637
|
signal.removeEventListener("abort", onAbort);
|
|
@@ -11748,20 +12674,20 @@ function createAskUserQuestionTool(handler) {
|
|
|
11748
12674
|
}
|
|
11749
12675
|
|
|
11750
12676
|
// src/tools/todo-write.ts
|
|
11751
|
-
import { z as
|
|
11752
|
-
var TodoStatusSchema =
|
|
12677
|
+
import { z as z23 } from "zod";
|
|
12678
|
+
var TodoStatusSchema = z23.enum([
|
|
11753
12679
|
"pending",
|
|
11754
12680
|
"in_progress",
|
|
11755
12681
|
"completed",
|
|
11756
12682
|
"cancelled"
|
|
11757
12683
|
]);
|
|
11758
|
-
var TodoItemSchema =
|
|
11759
|
-
content:
|
|
12684
|
+
var TodoItemSchema = z23.object({
|
|
12685
|
+
content: z23.string().trim().min(1),
|
|
11760
12686
|
status: TodoStatusSchema,
|
|
11761
|
-
activeForm:
|
|
12687
|
+
activeForm: z23.string().trim().min(1)
|
|
11762
12688
|
});
|
|
11763
|
-
var TodoWriteInput =
|
|
11764
|
-
todos:
|
|
12689
|
+
var TodoWriteInput = z23.object({
|
|
12690
|
+
todos: z23.array(TodoItemSchema).min(1).max(50)
|
|
11765
12691
|
});
|
|
11766
12692
|
function createTodoWriteTool() {
|
|
11767
12693
|
return {
|
|
@@ -11796,38 +12722,38 @@ function createTodoWriteTool() {
|
|
|
11796
12722
|
|
|
11797
12723
|
// src/tools/managed.ts
|
|
11798
12724
|
import { randomUUID as randomUUID9 } from "crypto";
|
|
11799
|
-
import { link, lstat as
|
|
11800
|
-
import { dirname as
|
|
11801
|
-
import { z as
|
|
12725
|
+
import { link, lstat as lstat10, mkdir as mkdir12, readFile as readFile14, readdir as readdir8, unlink, writeFile as writeFile7 } from "fs/promises";
|
|
12726
|
+
import { dirname as dirname14, join as join14, resolve as resolve19 } from "path";
|
|
12727
|
+
import { z as z24 } from "zod";
|
|
11802
12728
|
var MANAGED_TOOL_VERSION = 1;
|
|
11803
12729
|
var MAX_RECORD_BYTES = 256 * 1024;
|
|
11804
12730
|
var TOOL_NAME = /^[A-Za-z][A-Za-z0-9_]{0,63}$/;
|
|
11805
|
-
var JsonSchemaObject =
|
|
12731
|
+
var JsonSchemaObject = z24.record(z24.string(), z24.unknown()).superRefine((schema, context) => {
|
|
11806
12732
|
if (schema.type !== "object") {
|
|
11807
12733
|
context.addIssue({ code: "custom", message: "Tool input JSON Schema must have type object" });
|
|
11808
12734
|
}
|
|
11809
12735
|
});
|
|
11810
|
-
var RegisterManagedToolInputSchema =
|
|
11811
|
-
name:
|
|
11812
|
-
description:
|
|
12736
|
+
var RegisterManagedToolInputSchema = z24.object({
|
|
12737
|
+
name: z24.string().regex(TOOL_NAME).describe("Unique callable tool name using letters, digits, and underscores"),
|
|
12738
|
+
description: z24.string().min(1).max(4096).describe("Purpose and appropriate use of the generated tool"),
|
|
11813
12739
|
inputSchema: JsonSchemaObject.describe("Provider-facing JSON Schema with an object at the root"),
|
|
11814
|
-
implementation:
|
|
12740
|
+
implementation: z24.string().min(1).max(64 * 1024).describe(
|
|
11815
12741
|
"Async JavaScript function body, or complete function/arrow expression, receiving input, signal, and context; return the tool result"
|
|
11816
12742
|
),
|
|
11817
|
-
scope:
|
|
12743
|
+
scope: z24.enum(["project", "global"]).default("project").describe(
|
|
11818
12744
|
"Project persists in the current workspace; global persists for every workspace"
|
|
11819
12745
|
),
|
|
11820
|
-
agents:
|
|
12746
|
+
agents: z24.array(z24.enum(["main", "subagent"])).min(1).max(2).optional().describe(
|
|
11821
12747
|
"Agent roles allowed to call the generated tool; omit to allow both"
|
|
11822
12748
|
)
|
|
11823
12749
|
}).strict();
|
|
11824
|
-
var RemoveManagedToolInputSchema =
|
|
11825
|
-
name:
|
|
11826
|
-
scope:
|
|
12750
|
+
var RemoveManagedToolInputSchema = z24.object({
|
|
12751
|
+
name: z24.string().regex(TOOL_NAME).describe("Managed tool name to remove"),
|
|
12752
|
+
scope: z24.enum(["project", "global"]).optional().describe("Required only when the same name exists in both scopes")
|
|
11827
12753
|
}).strict();
|
|
11828
12754
|
var ManagedToolRecordSchema = RegisterManagedToolInputSchema.extend({
|
|
11829
|
-
version:
|
|
11830
|
-
createdAt:
|
|
12755
|
+
version: z24.literal(MANAGED_TOOL_VERSION),
|
|
12756
|
+
createdAt: z24.string().datetime()
|
|
11831
12757
|
}).strict();
|
|
11832
12758
|
var AsyncFunction = Object.getPrototypeOf(async function() {
|
|
11833
12759
|
}).constructor;
|
|
@@ -11839,9 +12765,9 @@ var ManagedToolStore = class {
|
|
|
11839
12765
|
#entries = /* @__PURE__ */ new Map();
|
|
11840
12766
|
#diagnostics = [];
|
|
11841
12767
|
constructor(options) {
|
|
11842
|
-
this.#workspace =
|
|
12768
|
+
this.#workspace = resolve19(options.workspace);
|
|
11843
12769
|
this.#projectRoot = join14(this.#workspace, ".flavor", "tools");
|
|
11844
|
-
this.#globalRoot = join14(
|
|
12770
|
+
this.#globalRoot = join14(resolve19(options.home), ".flavor-code", "tools");
|
|
11845
12771
|
this.#now = options.now ?? (() => (/* @__PURE__ */ new Date()).toISOString());
|
|
11846
12772
|
}
|
|
11847
12773
|
get diagnostics() {
|
|
@@ -11902,7 +12828,7 @@ var ManagedToolStore = class {
|
|
|
11902
12828
|
if (matches.length === 0) throw new Error(`Managed tool "${input.name}" was not found`);
|
|
11903
12829
|
if (matches.length > 1) throw new Error(`Managed tool "${input.name}" exists in multiple scopes; specify scope`);
|
|
11904
12830
|
const entry2 = matches[0];
|
|
11905
|
-
const info = await
|
|
12831
|
+
const info = await lstat10(entry2.path);
|
|
11906
12832
|
if (!info.isFile() || info.isSymbolicLink()) throw new Error(`Managed tool path is no longer a regular file: ${entry2.path}`);
|
|
11907
12833
|
await unlink(entry2.path);
|
|
11908
12834
|
this.#entries.delete(scopedKey(entry2.scope, entry2.record.name));
|
|
@@ -11928,10 +12854,10 @@ var ManagedToolStore = class {
|
|
|
11928
12854
|
for (const name of names) {
|
|
11929
12855
|
const path = join14(root, name);
|
|
11930
12856
|
try {
|
|
11931
|
-
const info = await
|
|
12857
|
+
const info = await lstat10(path);
|
|
11932
12858
|
if (!info.isFile() || info.isSymbolicLink()) throw new Error("Managed tool record must be a regular file");
|
|
11933
12859
|
if (info.size > MAX_RECORD_BYTES) throw new Error(`Managed tool record exceeds ${MAX_RECORD_BYTES} bytes`);
|
|
11934
|
-
const record = ManagedToolRecordSchema.parse(JSON.parse(await
|
|
12860
|
+
const record = ManagedToolRecordSchema.parse(JSON.parse(await readFile14(path, "utf8")));
|
|
11935
12861
|
if (record.scope !== scope2) throw new Error(`Managed tool record scope must match its ${scope2} directory`);
|
|
11936
12862
|
const key = scopedKey(scope2, record.name);
|
|
11937
12863
|
if (this.#entries.has(key)) throw new Error(`Duplicate managed tool name in ${scope2} scope: ${record.name}`);
|
|
@@ -12014,7 +12940,7 @@ function createManagedToolManagementTools(options) {
|
|
|
12014
12940
|
const listTools = {
|
|
12015
12941
|
name: "ListRegisteredTools",
|
|
12016
12942
|
description: "List durable tools created through RegisterTool, including scope, path, and active precedence",
|
|
12017
|
-
inputSchema:
|
|
12943
|
+
inputSchema: z24.object({}).strict(),
|
|
12018
12944
|
agents: mainOnly,
|
|
12019
12945
|
paths: () => [],
|
|
12020
12946
|
execute: async (_input, signal) => {
|
|
@@ -12027,7 +12953,7 @@ function createManagedToolManagementTools(options) {
|
|
|
12027
12953
|
function compileDefinition(record, scope2, workspace) {
|
|
12028
12954
|
let inputSchema;
|
|
12029
12955
|
try {
|
|
12030
|
-
inputSchema =
|
|
12956
|
+
inputSchema = z24.fromJSONSchema(record.inputSchema);
|
|
12031
12957
|
} catch (error) {
|
|
12032
12958
|
throw new Error(`Invalid input schema for managed tool "${record.name}": ${message(error)}`);
|
|
12033
12959
|
}
|
|
@@ -12081,7 +13007,7 @@ ${sourceUrl}`
|
|
|
12081
13007
|
}
|
|
12082
13008
|
}
|
|
12083
13009
|
async function writeExclusive(path, content) {
|
|
12084
|
-
await mkdir12(
|
|
13010
|
+
await mkdir12(dirname14(path), { recursive: true });
|
|
12085
13011
|
const temporary = `${path}.${randomUUID9()}.tmp`;
|
|
12086
13012
|
try {
|
|
12087
13013
|
await writeFile7(temporary, content, { encoding: "utf8", flag: "wx", mode: 384 });
|
|
@@ -12127,7 +13053,7 @@ function isMissing4(error) {
|
|
|
12127
13053
|
}
|
|
12128
13054
|
|
|
12129
13055
|
// src/hallucination/confidence.ts
|
|
12130
|
-
import { z as
|
|
13056
|
+
import { z as z25 } from "zod";
|
|
12131
13057
|
|
|
12132
13058
|
// src/hallucination/types.ts
|
|
12133
13059
|
var DEFAULT_SLIDING_WINDOW_SIZE = 20;
|
|
@@ -12140,12 +13066,12 @@ var DEFAULT_EVALUATION_TIMEOUT_MS = 2e3;
|
|
|
12140
13066
|
var MAX_QUERY_CHARS = 5e3;
|
|
12141
13067
|
var MAX_OUTPUT_CHARS = 1e4;
|
|
12142
13068
|
var MAX_EVIDENCE_CHARS = 6e3;
|
|
12143
|
-
var ConfidenceSchema =
|
|
12144
|
-
taskAlignment:
|
|
12145
|
-
evidenceGrounding:
|
|
12146
|
-
processReliability:
|
|
12147
|
-
reason:
|
|
12148
|
-
unsupportedClaims:
|
|
13069
|
+
var ConfidenceSchema = z25.object({
|
|
13070
|
+
taskAlignment: z25.number(),
|
|
13071
|
+
evidenceGrounding: z25.number(),
|
|
13072
|
+
processReliability: z25.number(),
|
|
13073
|
+
reason: z25.string(),
|
|
13074
|
+
unsupportedClaims: z25.array(z25.string()).max(3)
|
|
12149
13075
|
});
|
|
12150
13076
|
var PROMPT = `You are a fast, evidence-aware task-completion verifier.
|
|
12151
13077
|
|
|
@@ -12472,7 +13398,7 @@ function buildWarningMessages(report, languageTag) {
|
|
|
12472
13398
|
}
|
|
12473
13399
|
|
|
12474
13400
|
// src/hallucination/sliding-window.ts
|
|
12475
|
-
import { createHash as
|
|
13401
|
+
import { createHash as createHash7 } from "crypto";
|
|
12476
13402
|
var SlidingWindow = class {
|
|
12477
13403
|
#windowSize;
|
|
12478
13404
|
#threshold;
|
|
@@ -12488,7 +13414,7 @@ var SlidingWindow = class {
|
|
|
12488
13414
|
}
|
|
12489
13415
|
hash(toolName, params) {
|
|
12490
13416
|
const sorted = JSON.stringify(params, sortedKeys(params));
|
|
12491
|
-
return
|
|
13417
|
+
return createHash7("sha256").update(`${toolName}:${sorted}`).digest("hex");
|
|
12492
13418
|
}
|
|
12493
13419
|
push(toolName, params) {
|
|
12494
13420
|
const hash = this.hash(toolName, params);
|
|
@@ -12843,9 +13769,9 @@ ${total}`;
|
|
|
12843
13769
|
}
|
|
12844
13770
|
|
|
12845
13771
|
// src/memory/store.ts
|
|
12846
|
-
import { createHash as
|
|
12847
|
-
import { readFile as
|
|
12848
|
-
import { resolve as
|
|
13772
|
+
import { createHash as createHash8 } from "crypto";
|
|
13773
|
+
import { readFile as readFile15, rm as rm7 } from "fs/promises";
|
|
13774
|
+
import { resolve as resolve20, join as join15, relative as relative17, sep as sep16 } from "path";
|
|
12849
13775
|
|
|
12850
13776
|
// src/memory/similarity.ts
|
|
12851
13777
|
function normalizeForSimilarity(value) {
|
|
@@ -12947,7 +13873,7 @@ var MemoryStore = class {
|
|
|
12947
13873
|
constructor(options) {
|
|
12948
13874
|
if (!Number.isSafeInteger(options.maxEntries) || options.maxEntries < 1) throw new Error("maxEntries must be an integer of at least 1");
|
|
12949
13875
|
if (!Number.isSafeInteger(options.maxEntryChars) || options.maxEntryChars < 1) throw new Error("maxEntryChars must be an integer of at least 1");
|
|
12950
|
-
this.workspace =
|
|
13876
|
+
this.workspace = resolve20(options.workspace);
|
|
12951
13877
|
this.#memoryRoot = join15(this.workspace, ".flavor", "memory");
|
|
12952
13878
|
this.path = join15(this.#memoryRoot, "MEMORY.md");
|
|
12953
13879
|
this.#behaviorPath = join15(this.#memoryRoot, "behavior.json");
|
|
@@ -13168,14 +14094,14 @@ var MemoryStore = class {
|
|
|
13168
14094
|
});
|
|
13169
14095
|
}
|
|
13170
14096
|
async #readTaskItem(reference) {
|
|
13171
|
-
const document = decodeTask(await
|
|
14097
|
+
const document = decodeTask(await readFile15(this.#resolveContentPath(reference.contentPath), "utf8"));
|
|
13172
14098
|
return document.items.find((item) => item.id === reference.id)?.content ?? reference.summary;
|
|
13173
14099
|
}
|
|
13174
14100
|
#resolveContentPath(contentPath) {
|
|
13175
14101
|
if (!/^tasks\/[A-Za-z0-9][A-Za-z0-9._-]{0,127}\.md$/.test(contentPath)) throw new Error("Invalid memory content path");
|
|
13176
|
-
const path =
|
|
13177
|
-
const rel =
|
|
13178
|
-
if (rel === ".." || rel.startsWith(`..${
|
|
14102
|
+
const path = resolve20(this.#memoryRoot, ...contentPath.split("/"));
|
|
14103
|
+
const rel = relative17(this.#memoryRoot, path);
|
|
14104
|
+
if (rel === ".." || rel.startsWith(`..${sep16}`)) throw new Error("Memory content path escapes the memory directory");
|
|
13179
14105
|
return path;
|
|
13180
14106
|
}
|
|
13181
14107
|
async #updateIndex(update) {
|
|
@@ -13329,7 +14255,7 @@ function sanitizeTopicKey(value) {
|
|
|
13329
14255
|
return value.normalize("NFKC").toLocaleLowerCase().replace(/[^a-z0-9._-]+/g, "-").replace(/^-+|-+$/g, "").slice(0, 128);
|
|
13330
14256
|
}
|
|
13331
14257
|
function memoryId(type, content) {
|
|
13332
|
-
return
|
|
14258
|
+
return createHash8("sha256").update(`${type}\0${normalizeMemoryContent(content).toLocaleLowerCase()}`, "utf8").digest("hex").slice(0, 12);
|
|
13333
14259
|
}
|
|
13334
14260
|
function assertTaskId(taskId) {
|
|
13335
14261
|
if (!TASK_ID.test(taskId)) throw new Error(`Invalid memory task id: ${taskId}`);
|
|
@@ -13644,8 +14570,8 @@ function createExecutionEnvironment(workspace, config) {
|
|
|
13644
14570
|
}
|
|
13645
14571
|
|
|
13646
14572
|
// src/ide/client.ts
|
|
13647
|
-
import { readFile as
|
|
13648
|
-
import { basename as basename5, join as join16, relative as
|
|
14573
|
+
import { readFile as readFile16, readdir as readdir9, stat as stat6 } from "fs/promises";
|
|
14574
|
+
import { basename as basename5, join as join16, relative as relative18, resolve as resolve21, sep as sep17 } from "path";
|
|
13649
14575
|
var FlavorIdeClient = class {
|
|
13650
14576
|
#workspace;
|
|
13651
14577
|
#home;
|
|
@@ -13656,8 +14582,8 @@ var FlavorIdeClient = class {
|
|
|
13656
14582
|
#sessionId;
|
|
13657
14583
|
#eventTail = Promise.resolve();
|
|
13658
14584
|
constructor(options) {
|
|
13659
|
-
this.#workspace =
|
|
13660
|
-
this.#home =
|
|
14585
|
+
this.#workspace = resolve21(options.workspace);
|
|
14586
|
+
this.#home = resolve21(options.home);
|
|
13661
14587
|
this.#environment = options.environment ?? process.env;
|
|
13662
14588
|
this.#fetch = options.fetch ?? fetch;
|
|
13663
14589
|
}
|
|
@@ -13751,7 +14677,7 @@ var FlavorIdeClient = class {
|
|
|
13751
14677
|
const candidates = (await Promise.all(entries.map(async (name) => {
|
|
13752
14678
|
const path = join16(directory, name);
|
|
13753
14679
|
try {
|
|
13754
|
-
const [contents, info] = await Promise.all([
|
|
14680
|
+
const [contents, info] = await Promise.all([readFile16(path, "utf8"), stat6(path)]);
|
|
13755
14681
|
const lock = parseLockfile(contents);
|
|
13756
14682
|
return lock === void 0 ? void 0 : { lock, modified: info.mtimeMs };
|
|
13757
14683
|
} catch {
|
|
@@ -13855,19 +14781,385 @@ function parsePort(value) {
|
|
|
13855
14781
|
return Number.isInteger(port) && port > 0 && port <= 65535 ? port : void 0;
|
|
13856
14782
|
}
|
|
13857
14783
|
function isWithin7(parent, child) {
|
|
13858
|
-
const base = normalize(
|
|
13859
|
-
const target = normalize(
|
|
13860
|
-
return target === base || target.startsWith(`${base}${
|
|
14784
|
+
const base = normalize(resolve21(parent));
|
|
14785
|
+
const target = normalize(resolve21(child));
|
|
14786
|
+
return target === base || target.startsWith(`${base}${sep17}`);
|
|
13861
14787
|
}
|
|
13862
14788
|
function normalize(path) {
|
|
13863
14789
|
const normalized = path.normalize("NFC");
|
|
13864
14790
|
return process.platform === "win32" ? normalized.toLowerCase() : normalized;
|
|
13865
14791
|
}
|
|
13866
14792
|
function relativeDisplay(workspace, filePath) {
|
|
13867
|
-
const path =
|
|
14793
|
+
const path = relative18(workspace, filePath).replaceAll("\\", "/");
|
|
13868
14794
|
return path && !path.startsWith("../") ? path : basename5(filePath);
|
|
13869
14795
|
}
|
|
13870
14796
|
|
|
14797
|
+
// src/jobs/registry.ts
|
|
14798
|
+
import { randomUUID as randomUUID10 } from "crypto";
|
|
14799
|
+
var JobRegistry = class {
|
|
14800
|
+
#jobs = /* @__PURE__ */ new Map();
|
|
14801
|
+
#maxOutputChars;
|
|
14802
|
+
#maxJobs;
|
|
14803
|
+
#listeners = /* @__PURE__ */ new Set();
|
|
14804
|
+
constructor(options = {}) {
|
|
14805
|
+
this.#maxOutputChars = options.maxOutputChars ?? 2e5;
|
|
14806
|
+
this.#maxJobs = options.maxJobs ?? 100;
|
|
14807
|
+
if (!Number.isSafeInteger(this.#maxOutputChars) || this.#maxOutputChars <= 0) throw new Error("maxOutputChars must be positive");
|
|
14808
|
+
if (!Number.isSafeInteger(this.#maxJobs) || this.#maxJobs <= 0) throw new Error("maxJobs must be positive");
|
|
14809
|
+
}
|
|
14810
|
+
create(input) {
|
|
14811
|
+
this.#pruneCompleted();
|
|
14812
|
+
const id = `job-${randomUUID10().slice(0, 12)}`;
|
|
14813
|
+
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
14814
|
+
let settle;
|
|
14815
|
+
const done = new Promise((resolve25) => {
|
|
14816
|
+
settle = resolve25;
|
|
14817
|
+
});
|
|
14818
|
+
const record = {
|
|
14819
|
+
id,
|
|
14820
|
+
...input,
|
|
14821
|
+
state: "running",
|
|
14822
|
+
createdAt: now,
|
|
14823
|
+
updatedAt: now,
|
|
14824
|
+
outputChars: 0,
|
|
14825
|
+
truncated: false,
|
|
14826
|
+
output: "",
|
|
14827
|
+
outputStart: 0,
|
|
14828
|
+
settle,
|
|
14829
|
+
done
|
|
14830
|
+
};
|
|
14831
|
+
this.#jobs.set(id, record);
|
|
14832
|
+
this.#publish();
|
|
14833
|
+
return {
|
|
14834
|
+
id,
|
|
14835
|
+
append: (text) => {
|
|
14836
|
+
if (record.state !== "running" || text === "") return;
|
|
14837
|
+
record.output += text;
|
|
14838
|
+
record.outputChars += text.length;
|
|
14839
|
+
if (record.output.length > this.#maxOutputChars) {
|
|
14840
|
+
const removed = record.output.length - this.#maxOutputChars;
|
|
14841
|
+
record.output = record.output.slice(removed);
|
|
14842
|
+
record.outputStart += removed;
|
|
14843
|
+
record.truncated = true;
|
|
14844
|
+
}
|
|
14845
|
+
record.updatedAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
14846
|
+
this.#publish();
|
|
14847
|
+
},
|
|
14848
|
+
complete: (result = {}) => this.#finish(
|
|
14849
|
+
record,
|
|
14850
|
+
result.exitCode !== void 0 && result.exitCode !== null && result.exitCode !== 0 ? "failed" : "completed",
|
|
14851
|
+
result
|
|
14852
|
+
),
|
|
14853
|
+
fail: (error) => this.#finish(record, "failed", { error: error instanceof Error ? error.message : String(error) }),
|
|
14854
|
+
cancel: () => this.#finish(record, "cancelled", {})
|
|
14855
|
+
};
|
|
14856
|
+
}
|
|
14857
|
+
list(owner2) {
|
|
14858
|
+
return [...this.#jobs.values()].filter((job) => owner2 === void 0 || job.owner === owner2).sort((a, b) => b.createdAt.localeCompare(a.createdAt)).map(snapshot2);
|
|
14859
|
+
}
|
|
14860
|
+
read(id, owner2, cursor = 0) {
|
|
14861
|
+
const job = this.#owned(id, owner2);
|
|
14862
|
+
const effective = Math.max(cursor, job.outputStart);
|
|
14863
|
+
return { ...snapshot2(job), output: job.output.slice(effective - job.outputStart), cursor: job.outputStart + job.output.length };
|
|
14864
|
+
}
|
|
14865
|
+
async wait(id, owner2, signal) {
|
|
14866
|
+
const job = this.#owned(id, owner2);
|
|
14867
|
+
if (job.state !== "running") return snapshot2(job);
|
|
14868
|
+
if (signal === void 0) return job.done;
|
|
14869
|
+
return Promise.race([job.done, new Promise((_resolve, reject) => {
|
|
14870
|
+
if (signal.aborted) {
|
|
14871
|
+
reject(signal.reason);
|
|
14872
|
+
return;
|
|
14873
|
+
}
|
|
14874
|
+
signal.addEventListener("abort", () => reject(signal.reason), { once: true });
|
|
14875
|
+
})]);
|
|
14876
|
+
}
|
|
14877
|
+
kill(id, owner2) {
|
|
14878
|
+
const job = this.#owned(id, owner2);
|
|
14879
|
+
if (job.state !== "running") return;
|
|
14880
|
+
void job.cancel?.();
|
|
14881
|
+
this.#finish(job, "cancelled", {});
|
|
14882
|
+
}
|
|
14883
|
+
subscribe(listener) {
|
|
14884
|
+
this.#listeners.add(listener);
|
|
14885
|
+
return () => this.#listeners.delete(listener);
|
|
14886
|
+
}
|
|
14887
|
+
async dispose() {
|
|
14888
|
+
const running = [...this.#jobs.values()].filter((job) => job.state === "running");
|
|
14889
|
+
await Promise.allSettled(running.map(async (job) => job.cancel?.()));
|
|
14890
|
+
for (const job of running) this.#finish(job, "cancelled", {});
|
|
14891
|
+
}
|
|
14892
|
+
#owned(id, owner2) {
|
|
14893
|
+
const job = this.#jobs.get(id);
|
|
14894
|
+
if (job === void 0) throw new Error(`Unknown job: ${id}`);
|
|
14895
|
+
if (job.owner !== owner2) throw new Error("Job owner mismatch");
|
|
14896
|
+
return job;
|
|
14897
|
+
}
|
|
14898
|
+
#finish(record, state, detail) {
|
|
14899
|
+
if (record.state !== "running") return;
|
|
14900
|
+
record.state = state;
|
|
14901
|
+
record.updatedAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
14902
|
+
if (detail.exitCode !== void 0) record.exitCode = detail.exitCode;
|
|
14903
|
+
if (detail.error !== void 0) record.error = detail.error;
|
|
14904
|
+
const value = snapshot2(record);
|
|
14905
|
+
record.settle(value);
|
|
14906
|
+
this.#publish();
|
|
14907
|
+
}
|
|
14908
|
+
#publish() {
|
|
14909
|
+
const value = this.list();
|
|
14910
|
+
for (const listener of this.#listeners) listener(value);
|
|
14911
|
+
}
|
|
14912
|
+
#pruneCompleted() {
|
|
14913
|
+
if (this.#jobs.size < this.#maxJobs) return;
|
|
14914
|
+
const removable = [...this.#jobs.values()].filter((job) => job.state !== "running").sort((a, b) => a.updatedAt.localeCompare(b.updatedAt));
|
|
14915
|
+
for (const job of removable) {
|
|
14916
|
+
if (this.#jobs.size < this.#maxJobs) break;
|
|
14917
|
+
this.#jobs.delete(job.id);
|
|
14918
|
+
}
|
|
14919
|
+
}
|
|
14920
|
+
};
|
|
14921
|
+
function snapshot2(job) {
|
|
14922
|
+
return {
|
|
14923
|
+
id: job.id,
|
|
14924
|
+
kind: job.kind,
|
|
14925
|
+
owner: job.owner,
|
|
14926
|
+
label: job.label,
|
|
14927
|
+
state: job.state,
|
|
14928
|
+
createdAt: job.createdAt,
|
|
14929
|
+
updatedAt: job.updatedAt,
|
|
14930
|
+
outputChars: job.outputChars,
|
|
14931
|
+
truncated: job.truncated,
|
|
14932
|
+
...job.exitCode === void 0 ? {} : { exitCode: job.exitCode },
|
|
14933
|
+
...job.error === void 0 ? {} : { error: job.error }
|
|
14934
|
+
};
|
|
14935
|
+
}
|
|
14936
|
+
|
|
14937
|
+
// src/terminal/service.ts
|
|
14938
|
+
import { randomUUID as randomUUID11 } from "crypto";
|
|
14939
|
+
import { createRequire as nodeCreateRequire } from "module";
|
|
14940
|
+
import { isAbsolute as isAbsolute11, relative as relative19, resolve as resolve22, sep as sep18 } from "path";
|
|
14941
|
+
var TerminalService = class {
|
|
14942
|
+
#root;
|
|
14943
|
+
#factory;
|
|
14944
|
+
#jobs;
|
|
14945
|
+
#maxOutputChars;
|
|
14946
|
+
#sessions = /* @__PURE__ */ new Map();
|
|
14947
|
+
constructor(workspace, options = {}) {
|
|
14948
|
+
this.#root = resolve22(workspace);
|
|
14949
|
+
this.#factory = options.factory ?? defaultFactory;
|
|
14950
|
+
this.#jobs = options.jobs;
|
|
14951
|
+
this.#maxOutputChars = options.maxOutputChars ?? 2e5;
|
|
14952
|
+
}
|
|
14953
|
+
open(input) {
|
|
14954
|
+
const cwd = workspacePath(this.#root, input.cwd ?? ".");
|
|
14955
|
+
const shell = input.shell ?? defaultShell();
|
|
14956
|
+
const id = `term-${randomUUID11().slice(0, 12)}`;
|
|
14957
|
+
const pty = this.#factory(shell, input.args ?? [], {
|
|
14958
|
+
cwd,
|
|
14959
|
+
cols: input.columns ?? 100,
|
|
14960
|
+
rows: input.rows ?? 30,
|
|
14961
|
+
env: cleanEnvironment(process.env)
|
|
14962
|
+
});
|
|
14963
|
+
let jobId;
|
|
14964
|
+
const record = {
|
|
14965
|
+
id,
|
|
14966
|
+
owner: input.owner,
|
|
14967
|
+
shell,
|
|
14968
|
+
cwd,
|
|
14969
|
+
state: "running",
|
|
14970
|
+
createdAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
14971
|
+
pty,
|
|
14972
|
+
output: "",
|
|
14973
|
+
outputStart: 0,
|
|
14974
|
+
outputChars: 0,
|
|
14975
|
+
truncated: false,
|
|
14976
|
+
disposables: []
|
|
14977
|
+
};
|
|
14978
|
+
const job = this.#jobs?.create({ kind: "terminal", owner: input.owner, label: shell, cancel: () => this.close(id, input.owner) });
|
|
14979
|
+
if (job !== void 0) {
|
|
14980
|
+
jobId = job.id;
|
|
14981
|
+
record.jobId = job.id;
|
|
14982
|
+
record.job = job;
|
|
14983
|
+
}
|
|
14984
|
+
record.disposables.push(pty.onData((data2) => {
|
|
14985
|
+
record.output += data2;
|
|
14986
|
+
record.outputChars += data2.length;
|
|
14987
|
+
if (record.output.length > this.#maxOutputChars) {
|
|
14988
|
+
const removed = record.output.length - this.#maxOutputChars;
|
|
14989
|
+
record.output = record.output.slice(removed);
|
|
14990
|
+
record.outputStart += removed;
|
|
14991
|
+
record.truncated = true;
|
|
14992
|
+
}
|
|
14993
|
+
job?.append(data2);
|
|
14994
|
+
}));
|
|
14995
|
+
record.disposables.push(pty.onExit(({ exitCode }) => {
|
|
14996
|
+
if (record.state === "running") record.state = "exited";
|
|
14997
|
+
record.exitCode = exitCode;
|
|
14998
|
+
job?.complete({ exitCode });
|
|
14999
|
+
}));
|
|
15000
|
+
this.#sessions.set(id, record);
|
|
15001
|
+
return { ...snapshot3(record), ...jobId === void 0 ? {} : { jobId } };
|
|
15002
|
+
}
|
|
15003
|
+
write(id, owner2, data2) {
|
|
15004
|
+
this.#owned(id, owner2).pty.write(data2);
|
|
15005
|
+
}
|
|
15006
|
+
resize(id, owner2, columns, rows) {
|
|
15007
|
+
this.#owned(id, owner2).pty.resize(columns, rows);
|
|
15008
|
+
}
|
|
15009
|
+
read(id, owner2, cursor = 0) {
|
|
15010
|
+
const record = this.#owned(id, owner2);
|
|
15011
|
+
const effective = Math.max(cursor, record.outputStart);
|
|
15012
|
+
return { ...snapshot3(record), output: record.output.slice(effective - record.outputStart), cursor: record.outputStart + record.output.length, truncated: record.truncated };
|
|
15013
|
+
}
|
|
15014
|
+
list(owner2) {
|
|
15015
|
+
return [...this.#sessions.values()].filter((item) => item.owner === owner2).map(snapshot3);
|
|
15016
|
+
}
|
|
15017
|
+
close(id, owner2) {
|
|
15018
|
+
const record = this.#owned(id, owner2);
|
|
15019
|
+
if (record.state === "running") record.pty.kill();
|
|
15020
|
+
record.state = "closed";
|
|
15021
|
+
record.job?.cancel();
|
|
15022
|
+
for (const disposable of record.disposables) disposable.dispose();
|
|
15023
|
+
}
|
|
15024
|
+
dispose() {
|
|
15025
|
+
for (const record of this.#sessions.values()) {
|
|
15026
|
+
if (record.state === "running") record.pty.kill();
|
|
15027
|
+
for (const disposable of record.disposables) disposable.dispose();
|
|
15028
|
+
record.state = "closed";
|
|
15029
|
+
record.job?.cancel();
|
|
15030
|
+
}
|
|
15031
|
+
}
|
|
15032
|
+
#owned(id, owner2) {
|
|
15033
|
+
const record = this.#sessions.get(id);
|
|
15034
|
+
if (record === void 0) throw new Error(`Unknown terminal: ${id}`);
|
|
15035
|
+
if (record.owner !== owner2) throw new Error("Terminal owner mismatch");
|
|
15036
|
+
return record;
|
|
15037
|
+
}
|
|
15038
|
+
};
|
|
15039
|
+
function snapshot3(record) {
|
|
15040
|
+
return {
|
|
15041
|
+
id: record.id,
|
|
15042
|
+
owner: record.owner,
|
|
15043
|
+
shell: record.shell,
|
|
15044
|
+
cwd: record.cwd,
|
|
15045
|
+
state: record.state,
|
|
15046
|
+
createdAt: record.createdAt,
|
|
15047
|
+
...record.exitCode === void 0 ? {} : { exitCode: record.exitCode },
|
|
15048
|
+
...record.jobId === void 0 ? {} : { jobId: record.jobId }
|
|
15049
|
+
};
|
|
15050
|
+
}
|
|
15051
|
+
function defaultFactory(shell, args, options) {
|
|
15052
|
+
try {
|
|
15053
|
+
const pty = nodeCreateRequire(import.meta.url)("node-pty");
|
|
15054
|
+
return pty.spawn(shell, args, { ...options, name: "xterm-256color" });
|
|
15055
|
+
} catch (error) {
|
|
15056
|
+
throw new Error(`Pseudo-terminal backend is unavailable: ${error instanceof Error ? error.message : String(error)}`);
|
|
15057
|
+
}
|
|
15058
|
+
}
|
|
15059
|
+
function defaultShell() {
|
|
15060
|
+
return process.platform === "win32" ? process.env.ComSpec ?? "cmd.exe" : process.env.SHELL ?? "/bin/sh";
|
|
15061
|
+
}
|
|
15062
|
+
function cleanEnvironment(environment) {
|
|
15063
|
+
return Object.fromEntries(Object.entries(environment).filter((entry2) => entry2[1] !== void 0));
|
|
15064
|
+
}
|
|
15065
|
+
function workspacePath(root, input) {
|
|
15066
|
+
const path = resolve22(root, input);
|
|
15067
|
+
const delta = relative19(root, path);
|
|
15068
|
+
if (delta === ".." || delta.startsWith(`..${sep18}`) || isAbsolute11(delta)) throw new Error("Terminal cwd is outside the workspace");
|
|
15069
|
+
return path;
|
|
15070
|
+
}
|
|
15071
|
+
|
|
15072
|
+
// src/tools/terminal.ts
|
|
15073
|
+
import { z as z26 } from "zod";
|
|
15074
|
+
import { resolve as resolve23 } from "path";
|
|
15075
|
+
var Id = z26.string().regex(/^term-[a-f0-9-]+$/);
|
|
15076
|
+
var OpenInput = z26.object({ cwd: z26.string().optional(), shell: z26.string().optional(), args: z26.array(z26.string()).optional(), columns: z26.coerce.number().int().min(10).max(500).optional(), rows: z26.coerce.number().int().min(2).max(300).optional() });
|
|
15077
|
+
var WriteInput2 = z26.object({ id: Id, data: z26.string().max(1e5), enter: z26.boolean().optional() });
|
|
15078
|
+
var ReadInput3 = z26.object({ id: Id, cursor: z26.coerce.number().int().min(0).optional() });
|
|
15079
|
+
var ResizeInput = z26.object({ id: Id, columns: z26.coerce.number().int().min(10).max(500), rows: z26.coerce.number().int().min(2).max(300) });
|
|
15080
|
+
var CloseInput = z26.object({ id: Id });
|
|
15081
|
+
function createTerminalTools(terminals, workspace = process.cwd()) {
|
|
15082
|
+
const tools = [
|
|
15083
|
+
{
|
|
15084
|
+
name: "TerminalOpen",
|
|
15085
|
+
description: "Open a persistent interactive pseudo-terminal in the workspace",
|
|
15086
|
+
inputSchema: OpenInput,
|
|
15087
|
+
paths: (input) => [resolve23(workspace, input.cwd ?? ".")],
|
|
15088
|
+
permissions: (input) => ({ paths: [resolve23(workspace, input.cwd ?? ".")], command: input.shell ?? "system-shell", args: input.args ?? [], cwd: resolve23(workspace, input.cwd ?? ".") }),
|
|
15089
|
+
execute: async (input, _signal, context) => terminals.open({
|
|
15090
|
+
owner: owner(context),
|
|
15091
|
+
...input.cwd === void 0 ? {} : { cwd: input.cwd },
|
|
15092
|
+
...input.shell === void 0 ? {} : { shell: input.shell },
|
|
15093
|
+
...input.args === void 0 ? {} : { args: input.args },
|
|
15094
|
+
...input.columns === void 0 ? {} : { columns: input.columns },
|
|
15095
|
+
...input.rows === void 0 ? {} : { rows: input.rows }
|
|
15096
|
+
}),
|
|
15097
|
+
presentResult: (output) => ({ kind: "terminal", variant: "terminal", title: `Terminal ${output.id}`, state: "running" })
|
|
15098
|
+
},
|
|
15099
|
+
{
|
|
15100
|
+
name: "TerminalWrite",
|
|
15101
|
+
description: "Write text or a command to a persistent terminal",
|
|
15102
|
+
inputSchema: WriteInput2,
|
|
15103
|
+
paths: () => [],
|
|
15104
|
+
permissions: (input) => ({ paths: [], command: input.data, args: [] }),
|
|
15105
|
+
execute: async (input, _signal, context) => {
|
|
15106
|
+
terminals.write(input.id, owner(context), input.data + (input.enter ? "\r" : ""));
|
|
15107
|
+
return { id: input.id, written: input.data.length + (input.enter ? 1 : 0) };
|
|
15108
|
+
}
|
|
15109
|
+
},
|
|
15110
|
+
{
|
|
15111
|
+
name: "TerminalRead",
|
|
15112
|
+
description: "Read output produced by a persistent terminal since a cursor",
|
|
15113
|
+
inputSchema: ReadInput3,
|
|
15114
|
+
paths: () => [],
|
|
15115
|
+
execute: async (input, _signal, context) => terminals.read(input.id, owner(context), input.cursor ?? 0),
|
|
15116
|
+
renderForModel: (output) => output.output,
|
|
15117
|
+
presentResult: (output) => {
|
|
15118
|
+
const result = output;
|
|
15119
|
+
return {
|
|
15120
|
+
kind: "terminal",
|
|
15121
|
+
variant: "terminal",
|
|
15122
|
+
title: `Terminal ${result.id}`,
|
|
15123
|
+
stdout: result.output,
|
|
15124
|
+
truncated: result.truncated,
|
|
15125
|
+
...result.exitCode === void 0 ? { state: result.state === "closed" ? "cancelled" : "running" } : {
|
|
15126
|
+
exitCode: result.exitCode,
|
|
15127
|
+
state: result.exitCode === 0 ? "completed" : "failed"
|
|
15128
|
+
}
|
|
15129
|
+
};
|
|
15130
|
+
}
|
|
15131
|
+
},
|
|
15132
|
+
{
|
|
15133
|
+
name: "TerminalResize",
|
|
15134
|
+
description: "Resize a persistent terminal",
|
|
15135
|
+
inputSchema: ResizeInput,
|
|
15136
|
+
paths: () => [],
|
|
15137
|
+
execute: async (input, _signal, context) => {
|
|
15138
|
+
terminals.resize(input.id, owner(context), input.columns, input.rows);
|
|
15139
|
+
return { id: input.id, columns: input.columns, rows: input.rows };
|
|
15140
|
+
}
|
|
15141
|
+
},
|
|
15142
|
+
{
|
|
15143
|
+
name: "TerminalClose",
|
|
15144
|
+
description: "Close a persistent terminal",
|
|
15145
|
+
inputSchema: CloseInput,
|
|
15146
|
+
paths: () => [],
|
|
15147
|
+
execute: async (input, _signal, context) => {
|
|
15148
|
+
terminals.close(input.id, owner(context));
|
|
15149
|
+
return { id: input.id, closed: true };
|
|
15150
|
+
}
|
|
15151
|
+
},
|
|
15152
|
+
{
|
|
15153
|
+
name: "TerminalList",
|
|
15154
|
+
description: "List persistent terminals owned by this agent",
|
|
15155
|
+
inputSchema: z26.object({}),
|
|
15156
|
+
paths: () => [],
|
|
15157
|
+
execute: async (_input, _signal, context) => terminals.list(owner(context))
|
|
15158
|
+
}
|
|
15159
|
+
];
|
|
15160
|
+
return tools;
|
|
15161
|
+
}
|
|
15162
|
+
|
|
13871
15163
|
// src/production.ts
|
|
13872
15164
|
var INTERRUPTED_TASK_PLAN_CONTEXT = [
|
|
13873
15165
|
"The previous turn's task plan was cancelled and archived, so it is no longer active.",
|
|
@@ -13898,7 +15190,7 @@ var ApprovalBridge = class {
|
|
|
13898
15190
|
request(request, signal = new AbortController().signal) {
|
|
13899
15191
|
if (this.#settle !== void 0) return Promise.resolve("deny");
|
|
13900
15192
|
if (signal.aborted) return Promise.resolve("deny");
|
|
13901
|
-
this.#pending = { id:
|
|
15193
|
+
this.#pending = { id: randomUUID12(), ...request };
|
|
13902
15194
|
this.#onChange?.();
|
|
13903
15195
|
return new Promise((resolvePromise) => {
|
|
13904
15196
|
this.#settle = resolvePromise;
|
|
@@ -13919,8 +15211,8 @@ var ApprovalBridge = class {
|
|
|
13919
15211
|
}
|
|
13920
15212
|
};
|
|
13921
15213
|
async function createProductionRuntime(options) {
|
|
13922
|
-
const workspace =
|
|
13923
|
-
const home =
|
|
15214
|
+
const workspace = resolve24(options.workspace ?? process.cwd());
|
|
15215
|
+
const home = resolve24(options.home ?? homedir());
|
|
13924
15216
|
const environment = options.environment ?? process.env;
|
|
13925
15217
|
const ide = new FlavorIdeClient({ workspace, home, environment });
|
|
13926
15218
|
const ideReady = ide.initialize();
|
|
@@ -14053,17 +15345,29 @@ async function createProductionRuntime(options) {
|
|
|
14053
15345
|
return questions.ask(qs, signal);
|
|
14054
15346
|
};
|
|
14055
15347
|
const executionEnvironment = createExecutionEnvironment(workspace, config.execution);
|
|
14056
|
-
const
|
|
15348
|
+
const jobs = new JobRegistry();
|
|
15349
|
+
const terminals = new TerminalService(workspace, { jobs });
|
|
15350
|
+
const observations = new FileObservationStore();
|
|
15351
|
+
const workspaceInstructions = new WorkspaceInstructions(workspace);
|
|
15352
|
+
const fileMutationOptions = {
|
|
15353
|
+
observations,
|
|
15354
|
+
...options.beforeFileCommit === void 0 ? {} : { beforeCommit: options.beforeFileCommit }
|
|
15355
|
+
};
|
|
14057
15356
|
const tools = [
|
|
14058
|
-
createReadTool(workspace),
|
|
15357
|
+
createReadTool(workspace, { observations }),
|
|
14059
15358
|
createWriteTool(workspace, fileMutationOptions),
|
|
14060
15359
|
createEditTool(workspace, fileMutationOptions),
|
|
14061
15360
|
createApplyPatchTool(workspace, fileMutationOptions),
|
|
14062
15361
|
createGlobTool(workspace),
|
|
14063
15362
|
createGrepTool(workspace),
|
|
14064
15363
|
createShellTool(workspace, {
|
|
15364
|
+
jobs,
|
|
14065
15365
|
...executionEnvironment === void 0 ? {} : { executionEnvironment }
|
|
14066
15366
|
}),
|
|
15367
|
+
createWebFetchTool(),
|
|
15368
|
+
createWebSearchTool(),
|
|
15369
|
+
...createJobTools(jobs),
|
|
15370
|
+
...createTerminalTools(terminals, workspace),
|
|
14067
15371
|
...createLspTools(workspace, {
|
|
14068
15372
|
onStatus: (message2) => emitOutput({ type: "notice", message: message2 })
|
|
14069
15373
|
}),
|
|
@@ -14195,7 +15499,9 @@ async function createProductionRuntime(options) {
|
|
|
14195
15499
|
const skillsReady = skills.discover();
|
|
14196
15500
|
skillsReady.catch(() => void 0);
|
|
14197
15501
|
tools.push(createSkillResourceTool(skills));
|
|
15502
|
+
if (options.extraTools !== void 0) tools.push(...options.extraTools);
|
|
14198
15503
|
const flavor = await optionalText2(join17(workspace, "FLAVOR.md"));
|
|
15504
|
+
const instructionBaseline = await workspaceInstructions.baseline();
|
|
14199
15505
|
let memoryContext;
|
|
14200
15506
|
if (memoryStore !== void 0) {
|
|
14201
15507
|
try {
|
|
@@ -14218,7 +15524,7 @@ async function createProductionRuntime(options) {
|
|
|
14218
15524
|
let taskResults = { ...recovered?.tasks.results ?? {} };
|
|
14219
15525
|
const subagentStartedAt = {};
|
|
14220
15526
|
const subagentElapsedMs = {};
|
|
14221
|
-
let sessionId = recovered?.sessionId ?? `session-${(/* @__PURE__ */ new Date()).toISOString().replace(/[^0-9]/g, "").slice(0, 17)}-${
|
|
15527
|
+
let sessionId = recovered?.sessionId ?? `session-${(/* @__PURE__ */ new Date()).toISOString().replace(/[^0-9]/g, "").slice(0, 17)}-${randomUUID12().slice(0, 8)}`;
|
|
14222
15528
|
setUsageSession(sessionId);
|
|
14223
15529
|
ideSessionId = sessionId;
|
|
14224
15530
|
await ideReady;
|
|
@@ -14388,6 +15694,7 @@ async function createProductionRuntime(options) {
|
|
|
14388
15694
|
})
|
|
14389
15695
|
],
|
|
14390
15696
|
...flavor === void 0 ? {} : { flavor },
|
|
15697
|
+
...instructionBaseline === "" ? {} : { workspaceInstructions: instructionBaseline },
|
|
14391
15698
|
...memoryContext === void 0 ? {} : { memory: memoryContext },
|
|
14392
15699
|
...taskState === void 0 ? {} : { taskState },
|
|
14393
15700
|
userMemory: () => userMemoryContext ?? "",
|
|
@@ -14425,6 +15732,7 @@ async function createProductionRuntime(options) {
|
|
|
14425
15732
|
maxIterationsMain: config.maxIterations.main,
|
|
14426
15733
|
maxIterationsSubagent: config.maxIterations.subagent,
|
|
14427
15734
|
hasActiveProgress,
|
|
15735
|
+
afterToolSuccess: async (_tool, paths, _input, _output, context) => workspaceInstructions.discover(paths, context.ownerId ?? context.agent),
|
|
14428
15736
|
approve: resolveToolApproval
|
|
14429
15737
|
});
|
|
14430
15738
|
harnessCreated = true;
|
|
@@ -14525,7 +15833,7 @@ async function createProductionRuntime(options) {
|
|
|
14525
15833
|
const prompt = String(event.payload.prompt);
|
|
14526
15834
|
const reassessInterruptedPlan = interruptedTaskPlanNeedsReassessment && !prompt.startsWith("/");
|
|
14527
15835
|
timelineState = transcriptReducer(timelineState, { type: "submit", prompt });
|
|
14528
|
-
automaticMemoryTask = !prompt.startsWith("/");
|
|
15836
|
+
automaticMemoryTask = !prompt.startsWith("/") && harness.permissionProfile !== "d2c";
|
|
14529
15837
|
if (automaticMemoryTask) memoryReviews.dismissAll();
|
|
14530
15838
|
if (!prompt.startsWith("/") && memoryLifecycle.status === "completed") {
|
|
14531
15839
|
memoryLifecycle = {
|
|
@@ -14650,16 +15958,23 @@ async function createProductionRuntime(options) {
|
|
|
14650
15958
|
return;
|
|
14651
15959
|
}
|
|
14652
15960
|
const loopExecutionEnvironment = createExecutionEnvironment(input.workspace, config.execution);
|
|
15961
|
+
const loopObservations = new FileObservationStore();
|
|
15962
|
+
const loopInstructions = new WorkspaceInstructions(input.workspace);
|
|
15963
|
+
const loopMutationOptions = { observations: loopObservations };
|
|
14653
15964
|
const loopTools = [
|
|
14654
|
-
createReadTool(input.workspace),
|
|
14655
|
-
createWriteTool(input.workspace),
|
|
14656
|
-
createEditTool(input.workspace),
|
|
14657
|
-
createApplyPatchTool(input.workspace),
|
|
15965
|
+
createReadTool(input.workspace, { observations: loopObservations }),
|
|
15966
|
+
createWriteTool(input.workspace, loopMutationOptions),
|
|
15967
|
+
createEditTool(input.workspace, loopMutationOptions),
|
|
15968
|
+
createApplyPatchTool(input.workspace, loopMutationOptions),
|
|
14658
15969
|
createGlobTool(input.workspace),
|
|
14659
15970
|
createGrepTool(input.workspace),
|
|
14660
15971
|
createShellTool(input.workspace, {
|
|
15972
|
+
jobs,
|
|
14661
15973
|
...loopExecutionEnvironment === void 0 ? {} : { executionEnvironment: loopExecutionEnvironment }
|
|
14662
15974
|
}),
|
|
15975
|
+
createWebFetchTool(),
|
|
15976
|
+
createWebSearchTool(),
|
|
15977
|
+
...createJobTools(jobs),
|
|
14663
15978
|
...createLspTools(input.workspace, {
|
|
14664
15979
|
onStatus: (status) => emitOutput({ type: "notice", message: status })
|
|
14665
15980
|
}),
|
|
@@ -14667,6 +15982,7 @@ async function createProductionRuntime(options) {
|
|
|
14667
15982
|
...mcpTools
|
|
14668
15983
|
];
|
|
14669
15984
|
const loopFlavor = await optionalText2(join17(input.workspace, "FLAVOR.md"));
|
|
15985
|
+
const loopInstructionBaseline = await loopInstructions.baseline();
|
|
14670
15986
|
const loopEnvironment = createPromptEnvironment({
|
|
14671
15987
|
now: /* @__PURE__ */ new Date(),
|
|
14672
15988
|
platform: process.platform,
|
|
@@ -14696,6 +16012,7 @@ async function createProductionRuntime(options) {
|
|
|
14696
16012
|
})
|
|
14697
16013
|
],
|
|
14698
16014
|
...loopFlavor === void 0 ? {} : { flavor: loopFlavor },
|
|
16015
|
+
...loopInstructionBaseline === "" ? {} : { workspaceInstructions: loopInstructionBaseline },
|
|
14699
16016
|
...memoryContext === void 0 ? {} : { memory: memoryContext },
|
|
14700
16017
|
userMemory: () => userMemoryContext ?? "",
|
|
14701
16018
|
...compactAtChars === void 0 ? {} : { compactAtChars },
|
|
@@ -14728,6 +16045,7 @@ async function createProductionRuntime(options) {
|
|
|
14728
16045
|
maxIterationsMain: config.maxIterations.main,
|
|
14729
16046
|
maxIterationsSubagent: config.maxIterations.subagent,
|
|
14730
16047
|
loopMode: true,
|
|
16048
|
+
afterToolSuccess: async (_tool, paths, _input, _output, context) => loopInstructions.discover(paths, context.ownerId ?? context.agent),
|
|
14731
16049
|
approve: resolveToolApproval
|
|
14732
16050
|
});
|
|
14733
16051
|
try {
|
|
@@ -14779,7 +16097,7 @@ async function createProductionRuntime(options) {
|
|
|
14779
16097
|
return answers[0] === "Continue" ? "approved" : "rejected";
|
|
14780
16098
|
},
|
|
14781
16099
|
fingerprint: workspaceFingerprint,
|
|
14782
|
-
idFactory: () => `loop-${(/* @__PURE__ */ new Date()).toISOString().replace(/[^0-9]/g, "").slice(0, 17)}-${
|
|
16100
|
+
idFactory: () => `loop-${(/* @__PURE__ */ new Date()).toISOString().replace(/[^0-9]/g, "").slice(0, 17)}-${randomUUID12().slice(0, 8)}`
|
|
14783
16101
|
});
|
|
14784
16102
|
const goalStore = new GoalStore({ workspace });
|
|
14785
16103
|
const goalOrchestrator = new GoalOrchestrator({
|
|
@@ -14792,13 +16110,13 @@ async function createProductionRuntime(options) {
|
|
|
14792
16110
|
maxStallStreak: 2,
|
|
14793
16111
|
persistence: goalStore,
|
|
14794
16112
|
now: () => (/* @__PURE__ */ new Date()).toISOString(),
|
|
14795
|
-
idFactory: () => `goal-${(/* @__PURE__ */ new Date()).toISOString().replace(/[^0-9]/g, "").slice(0, 17)}-${
|
|
16113
|
+
idFactory: () => `goal-${(/* @__PURE__ */ new Date()).toISOString().replace(/[^0-9]/g, "").slice(0, 17)}-${randomUUID12().slice(0, 8)}`,
|
|
14796
16114
|
runWorker: ({ workspace: goalWorkspace, prompt, signal }) => runLoopWorker({ workspace: goalWorkspace, prompt, signal })
|
|
14797
16115
|
});
|
|
14798
16116
|
let sessionHistory = await SessionHistory.open({
|
|
14799
16117
|
workspace,
|
|
14800
16118
|
sessionId,
|
|
14801
|
-
restoreContext: (
|
|
16119
|
+
restoreContext: (snapshot4) => harness.main.context.restore(snapshot4)
|
|
14802
16120
|
});
|
|
14803
16121
|
const services = {
|
|
14804
16122
|
hooks,
|
|
@@ -14910,7 +16228,7 @@ async function createProductionRuntime(options) {
|
|
|
14910
16228
|
tasks: () => ({ plan: taskPlan, graph: taskGraph, states: taskStates, results: taskResults }),
|
|
14911
16229
|
audit: async (toolFilter) => {
|
|
14912
16230
|
try {
|
|
14913
|
-
const raw = await
|
|
16231
|
+
const raw = await readFile17(auditLogger.path, "utf8");
|
|
14914
16232
|
const lines = raw.trim().split("\n").filter((line) => line.length > 0);
|
|
14915
16233
|
const entries = lines.map((line) => {
|
|
14916
16234
|
try {
|
|
@@ -14949,7 +16267,7 @@ ${summary2}`;
|
|
|
14949
16267
|
},
|
|
14950
16268
|
usage: async () => {
|
|
14951
16269
|
try {
|
|
14952
|
-
const raw = await
|
|
16270
|
+
const raw = await readFile17(usageLogPath(), "utf8");
|
|
14953
16271
|
return formatUsageSummary(summarizeUsage(parseUsageEntries(raw)));
|
|
14954
16272
|
} catch (error) {
|
|
14955
16273
|
if (typeof error === "object" && error !== null && "code" in error && error.code === "ENOENT") {
|
|
@@ -14977,7 +16295,7 @@ ${summary2}`;
|
|
|
14977
16295
|
for (const key of Object.keys(subagentStartedAt)) delete subagentStartedAt[key];
|
|
14978
16296
|
for (const key of Object.keys(subagentElapsedMs)) delete subagentElapsedMs[key];
|
|
14979
16297
|
const previousIdeSessionId = ideSessionId;
|
|
14980
|
-
sessionId = `session-${(/* @__PURE__ */ new Date()).toISOString().replace(/[^0-9]/g, "").slice(0, 17)}-${
|
|
16298
|
+
sessionId = `session-${(/* @__PURE__ */ new Date()).toISOString().replace(/[^0-9]/g, "").slice(0, 17)}-${randomUUID12().slice(0, 8)}`;
|
|
14981
16299
|
setUsageSession(sessionId);
|
|
14982
16300
|
ideSessionId = sessionId;
|
|
14983
16301
|
if (previousIdeSessionId !== void 0) await ide.endSession(previousIdeSessionId);
|
|
@@ -14986,7 +16304,7 @@ ${summary2}`;
|
|
|
14986
16304
|
sessionHistory = await SessionHistory.open({
|
|
14987
16305
|
workspace,
|
|
14988
16306
|
sessionId,
|
|
14989
|
-
restoreContext: (
|
|
16307
|
+
restoreContext: (snapshot4) => harness.main.context.restore(snapshot4)
|
|
14990
16308
|
});
|
|
14991
16309
|
memoryLifecycle = { status: "active", taskId: createMemoryTaskId(), messageStart: 0 };
|
|
14992
16310
|
timelineState = createTranscriptState();
|
|
@@ -15135,13 +16453,19 @@ ${renderMemoryDocument(entries)}`;
|
|
|
15135
16453
|
sleepScheduler.start();
|
|
15136
16454
|
}
|
|
15137
16455
|
const session = new FlavorSession(services);
|
|
16456
|
+
const authorization = {
|
|
16457
|
+
permissionProfile: () => harness.permissionProfile,
|
|
16458
|
+
setPermissionProfile: (profile) => harness.setPermissionProfile(profile)
|
|
16459
|
+
};
|
|
15138
16460
|
let disposed = false;
|
|
15139
16461
|
return {
|
|
15140
16462
|
session,
|
|
15141
16463
|
services,
|
|
16464
|
+
authorization,
|
|
15142
16465
|
approvals,
|
|
15143
16466
|
memoryReviews,
|
|
15144
16467
|
restoredTranscript,
|
|
16468
|
+
jobs: { list: () => jobs.list(), subscribe: (listener) => jobs.subscribe(listener) },
|
|
15145
16469
|
get sessionId() {
|
|
15146
16470
|
return sessionId;
|
|
15147
16471
|
},
|
|
@@ -15158,6 +16482,8 @@ ${renderMemoryDocument(entries)}`;
|
|
|
15158
16482
|
await persistTail;
|
|
15159
16483
|
if (ideSessionId !== void 0) await ide.endSession(ideSessionId);
|
|
15160
16484
|
await executionEnvironment?.dispose();
|
|
16485
|
+
terminals.dispose();
|
|
16486
|
+
await jobs.dispose();
|
|
15161
16487
|
auditLogger.close();
|
|
15162
16488
|
memoryReviews.dispose();
|
|
15163
16489
|
await cleanupProduction(approvals, questions, pluginHost, mcpManager, harness);
|
|
@@ -15170,6 +16496,8 @@ ${renderMemoryDocument(entries)}`;
|
|
|
15170
16496
|
if (ideSessionId !== void 0) await ide.endSession(ideSessionId);
|
|
15171
16497
|
await sleepScheduler?.dispose();
|
|
15172
16498
|
await executionEnvironment?.dispose();
|
|
16499
|
+
terminals.dispose();
|
|
16500
|
+
await jobs.dispose();
|
|
15173
16501
|
await cleanupProduction(approvals, questions, pluginHost, mcpManager, harnessCreated ? harness : void 0);
|
|
15174
16502
|
} catch (cleanupError) {
|
|
15175
16503
|
attachCleanupError(primaryError, cleanupError);
|
|
@@ -15284,7 +16612,7 @@ function terminalProgressState(status) {
|
|
|
15284
16612
|
return "failed";
|
|
15285
16613
|
}
|
|
15286
16614
|
async function workspaceFingerprint(workspace) {
|
|
15287
|
-
const hash =
|
|
16615
|
+
const hash = createHash9("sha256");
|
|
15288
16616
|
const diff = await execFileNoThrow(
|
|
15289
16617
|
"git",
|
|
15290
16618
|
["-C", workspace, "diff", "--no-ext-diff", "--binary", "HEAD"],
|
|
@@ -15300,13 +16628,13 @@ async function workspaceFingerprint(workspace) {
|
|
|
15300
16628
|
let remaining = 5 * 1024 * 1024;
|
|
15301
16629
|
for (const name of untracked.stdout.split("\0").filter(Boolean).sort()) {
|
|
15302
16630
|
if (name === ".flavor/loops" || name.startsWith(".flavor/loops/")) continue;
|
|
15303
|
-
const path =
|
|
15304
|
-
const relativePath =
|
|
15305
|
-
if (relativePath === ".." || relativePath.startsWith(`..${
|
|
16631
|
+
const path = resolve24(workspace, name);
|
|
16632
|
+
const relativePath = relative20(resolve24(workspace), path);
|
|
16633
|
+
if (relativePath === ".." || relativePath.startsWith(`..${sep19}`)) continue;
|
|
15306
16634
|
hash.update(name);
|
|
15307
16635
|
if (remaining <= 0) continue;
|
|
15308
16636
|
try {
|
|
15309
|
-
const content = await
|
|
16637
|
+
const content = await readFile17(path);
|
|
15310
16638
|
const slice = content.subarray(0, remaining);
|
|
15311
16639
|
hash.update(slice);
|
|
15312
16640
|
remaining -= slice.length;
|
|
@@ -15698,7 +17026,7 @@ async function generateSleepReview(registry, modelId, prompt, signal) {
|
|
|
15698
17026
|
}
|
|
15699
17027
|
async function optionalText2(path) {
|
|
15700
17028
|
try {
|
|
15701
|
-
return await
|
|
17029
|
+
return await readFile17(path, "utf8");
|
|
15702
17030
|
} catch (error) {
|
|
15703
17031
|
if (typeof error === "object" && error !== null && "code" in error && error.code === "ENOENT") return void 0;
|
|
15704
17032
|
throw error;
|
|
@@ -15711,10 +17039,10 @@ function remove(items, item) {
|
|
|
15711
17039
|
function sameToolName(left, right) {
|
|
15712
17040
|
return left.toLowerCase() === right.toLowerCase();
|
|
15713
17041
|
}
|
|
15714
|
-
function storedConversation(
|
|
17042
|
+
function storedConversation(snapshot4) {
|
|
15715
17043
|
return {
|
|
15716
|
-
...
|
|
15717
|
-
messages:
|
|
17044
|
+
...snapshot4.compact === void 0 ? {} : { compact: snapshot4.compact },
|
|
17045
|
+
messages: snapshot4.messages.flatMap((message2) => {
|
|
15718
17046
|
if (message2.role === "system") return [];
|
|
15719
17047
|
const metadata = {
|
|
15720
17048
|
...message2.toolCallId === void 0 ? {} : { toolCallId: message2.toolCallId },
|
|
@@ -15837,10 +17165,10 @@ async function* runGoalSession(orchestrator, goal, signal) {
|
|
|
15837
17165
|
}
|
|
15838
17166
|
function memoryTranscriptHash(messages) {
|
|
15839
17167
|
const visible = messages.filter((item) => item.role === "user" || item.role === "assistant").map((item) => `${item.role}\0${modelContentText(item.content).trim()}`).join("\n");
|
|
15840
|
-
return
|
|
17168
|
+
return createHash9("sha256").update(visible, "utf8").digest("hex");
|
|
15841
17169
|
}
|
|
15842
17170
|
function createMemoryTaskId() {
|
|
15843
|
-
return `memory-${
|
|
17171
|
+
return `memory-${randomUUID12()}`;
|
|
15844
17172
|
}
|
|
15845
17173
|
|
|
15846
17174
|
export {
|