flavor-code 1.2.7 → 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 +405 -369
- package/README.zh-CN.md +387 -356
- package/dist/agent/loop.d.ts +4 -0
- package/dist/agent/types.d.ts +10 -0
- package/dist/{app-R3SDG2OH.js → app-USAFQVSX.js} +379 -12
- package/dist/{chunk-NVLYFU6P.js → chunk-F2HMO5Q2.js} +28 -11
- package/dist/{chunk-WN2EZV3Y.js → chunk-G32MCAZA.js} +1 -1
- package/dist/{chunk-N2S7USST.js → chunk-RQTYHUMK.js} +2 -1
- package/dist/{chunk-5RHJ3PQN.js → chunk-WGYNTR4Q.js} +1834 -495
- package/dist/{chunk-HFR2WS6T.js → chunk-XFCJXRJ2.js} +18 -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/config/schema.d.ts +2 -0
- package/dist/context/manager.d.ts +2 -0
- package/dist/context/workspace-instructions.d.ts +10 -0
- package/dist/desktop/main.js +12736 -5178
- 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-XC5JYYBQ.js → load-6ZMEBHSG.js} +1 -1
- package/dist/models/anthropic.d.ts +4 -0
- 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-CLrVaR9H.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) {
|
|
@@ -7335,6 +7895,10 @@ function normalizeToolCallInput(input, maxDepth = 3) {
|
|
|
7335
7895
|
}
|
|
7336
7896
|
|
|
7337
7897
|
// src/models/anthropic.ts
|
|
7898
|
+
var CLAUDE_CLIENT_HEADERS = {
|
|
7899
|
+
"User-Agent": "claude-cli/2.1.0 (external, cli)",
|
|
7900
|
+
"x-app": "cli"
|
|
7901
|
+
};
|
|
7338
7902
|
var DEFAULT_MAX_OUTPUT_TOKENS = 32768;
|
|
7339
7903
|
var MAX_CACHE_MARKERS = 4;
|
|
7340
7904
|
function collectCacheMarkers(system, messages) {
|
|
@@ -7368,27 +7932,27 @@ function markLastBlock(message2) {
|
|
|
7368
7932
|
};
|
|
7369
7933
|
message2.content = blocks;
|
|
7370
7934
|
}
|
|
7371
|
-
function updateInputUsage(
|
|
7372
|
-
if (usage?.input_tokens != null)
|
|
7935
|
+
function updateInputUsage(snapshot4, usage) {
|
|
7936
|
+
if (usage?.input_tokens != null) snapshot4.base = usage.input_tokens;
|
|
7373
7937
|
if (usage?.cache_creation_input_tokens != null) {
|
|
7374
|
-
|
|
7938
|
+
snapshot4.cacheCreation = usage.cache_creation_input_tokens;
|
|
7375
7939
|
}
|
|
7376
7940
|
if (usage?.cache_read_input_tokens != null) {
|
|
7377
|
-
|
|
7941
|
+
snapshot4.cacheRead = usage.cache_read_input_tokens;
|
|
7378
7942
|
}
|
|
7379
|
-
return
|
|
7943
|
+
return snapshot4.base + snapshot4.cacheCreation + snapshot4.cacheRead;
|
|
7380
7944
|
}
|
|
7381
|
-
function formatCacheUsage(model,
|
|
7382
|
-
const total =
|
|
7383
|
-
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;
|
|
7384
7948
|
return JSON.stringify({
|
|
7385
7949
|
event: "flavor-usage",
|
|
7386
7950
|
sessionId: currentUsageSession(),
|
|
7387
7951
|
provider: "anthropic",
|
|
7388
7952
|
model,
|
|
7389
|
-
inputTokens:
|
|
7390
|
-
cacheReadTokens:
|
|
7391
|
-
cacheCreationTokens:
|
|
7953
|
+
inputTokens: snapshot4.base,
|
|
7954
|
+
cacheReadTokens: snapshot4.cacheRead,
|
|
7955
|
+
cacheCreationTokens: snapshot4.cacheCreation,
|
|
7392
7956
|
totalInputTokens: total,
|
|
7393
7957
|
cacheHitRatio: Number(hitRatio.toFixed(4)),
|
|
7394
7958
|
...shape === void 0 ? {} : { requestMessages: shape.messages, requestMarkers: shape.markers }
|
|
@@ -7407,11 +7971,12 @@ var AnthropicModelAdapter = class {
|
|
|
7407
7971
|
this.debugUsage = options.debugUsage ?? isEnvTruthy(process.env.FLAVOR_DEBUG_USAGE);
|
|
7408
7972
|
this.client = options.client ?? new Anthropic({
|
|
7409
7973
|
...options.apiKey === void 0 ? {} : { apiKey: options.apiKey },
|
|
7410
|
-
...options.baseURL === void 0 ? {} : { baseURL: options.baseURL }
|
|
7974
|
+
...options.baseURL === void 0 ? {} : { baseURL: options.baseURL },
|
|
7975
|
+
...options.headers === void 0 ? {} : { defaultHeaders: options.headers }
|
|
7411
7976
|
});
|
|
7412
7977
|
}
|
|
7413
|
-
#logUsage(request,
|
|
7414
|
-
const line = formatCacheUsage(request.model,
|
|
7978
|
+
#logUsage(request, snapshot4, shape) {
|
|
7979
|
+
const line = formatCacheUsage(request.model, snapshot4, shape);
|
|
7415
7980
|
if (this.debugUsage) {
|
|
7416
7981
|
try {
|
|
7417
7982
|
process.stderr.write(`${line}
|
|
@@ -7490,7 +8055,7 @@ var AnthropicModelAdapter = class {
|
|
|
7490
8055
|
source: {
|
|
7491
8056
|
type: "base64",
|
|
7492
8057
|
media_type: block.mediaType,
|
|
7493
|
-
data: (await
|
|
8058
|
+
data: (await readFile9(block.source.path)).toString("base64")
|
|
7494
8059
|
}
|
|
7495
8060
|
}));
|
|
7496
8061
|
if (message2.cacheBreakpoint && Array.isArray(content) && content.length > 0) {
|
|
@@ -7672,7 +8237,7 @@ function resolveCacheProfile(input) {
|
|
|
7672
8237
|
|
|
7673
8238
|
// src/models/openai.ts
|
|
7674
8239
|
import OpenAI from "openai";
|
|
7675
|
-
import { readFile as
|
|
8240
|
+
import { readFile as readFile10 } from "fs/promises";
|
|
7676
8241
|
async function toInput(message2) {
|
|
7677
8242
|
if (message2.role === "tool") {
|
|
7678
8243
|
if (!message2.toolCallId) throw new Error("Tool messages require toolCallId");
|
|
@@ -7684,7 +8249,7 @@ async function toInput(message2) {
|
|
|
7684
8249
|
}
|
|
7685
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 } : {
|
|
7686
8251
|
type: "input_image",
|
|
7687
|
-
image_url: `data:${block.mediaType};base64,${(await
|
|
8252
|
+
image_url: `data:${block.mediaType};base64,${(await readFile10(block.source.path)).toString("base64")}`,
|
|
7688
8253
|
detail: "auto"
|
|
7689
8254
|
}));
|
|
7690
8255
|
return [
|
|
@@ -7903,8 +8468,8 @@ var ModelRegistry = class {
|
|
|
7903
8468
|
};
|
|
7904
8469
|
|
|
7905
8470
|
// src/mcp/client.ts
|
|
7906
|
-
import { createHash as
|
|
7907
|
-
import { z as
|
|
8471
|
+
import { createHash as createHash4 } from "crypto";
|
|
8472
|
+
import { z as z14 } from "zod";
|
|
7908
8473
|
var McpManager = class {
|
|
7909
8474
|
#workspace;
|
|
7910
8475
|
#clientFactory;
|
|
@@ -8040,7 +8605,7 @@ async function listAllTools(client) {
|
|
|
8040
8605
|
function createMcpToolDefinition(serverName, timeoutMs, client, remote) {
|
|
8041
8606
|
if (remote.name.length === 0) throw new Error("MCP tool name must not be empty");
|
|
8042
8607
|
if (!isRecord3(remote.inputSchema)) throw new Error(`MCP tool "${remote.name}" has no JSON object input schema`);
|
|
8043
|
-
const inputSchema =
|
|
8608
|
+
const inputSchema = z14.fromJSONSchema(
|
|
8044
8609
|
remote.inputSchema
|
|
8045
8610
|
);
|
|
8046
8611
|
return {
|
|
@@ -8070,7 +8635,7 @@ function mcpToolName(serverName, remoteName) {
|
|
|
8070
8635
|
const safe = remoteName.replace(/[^A-Za-z0-9_-]/g, "_") || "tool";
|
|
8071
8636
|
const direct = `${prefix}${safe}`;
|
|
8072
8637
|
if (direct.length <= 64 && safe === remoteName) return direct;
|
|
8073
|
-
const suffix = `__${
|
|
8638
|
+
const suffix = `__${createHash4("sha256").update(remoteName).digest("hex").slice(0, 8)}`;
|
|
8074
8639
|
return `${prefix}${safe.slice(0, Math.max(1, 64 - prefix.length - suffix.length))}${suffix}`;
|
|
8075
8640
|
}
|
|
8076
8641
|
function mcpErrorText(content) {
|
|
@@ -8089,7 +8654,7 @@ function isRecord3(value) {
|
|
|
8089
8654
|
}
|
|
8090
8655
|
|
|
8091
8656
|
// src/mcp/sdk.ts
|
|
8092
|
-
import { resolve as
|
|
8657
|
+
import { resolve as resolve12 } from "path";
|
|
8093
8658
|
import { Client } from "@modelcontextprotocol/sdk/client/index.js";
|
|
8094
8659
|
import {
|
|
8095
8660
|
getDefaultEnvironment,
|
|
@@ -8111,7 +8676,7 @@ async function connectSdkMcpClient(input, dependencies = DEFAULT_DEPENDENCIES) {
|
|
|
8111
8676
|
command: config.command,
|
|
8112
8677
|
args: [...config.args],
|
|
8113
8678
|
env: { ...dependencies.defaultEnvironment(), ...config.env },
|
|
8114
|
-
cwd:
|
|
8679
|
+
cwd: resolve12(input.workspace, config.cwd ?? "."),
|
|
8115
8680
|
// MCP servers frequently log startup banners to stderr. Inheriting that
|
|
8116
8681
|
// stream corrupts Ink's input area, so connection errors are surfaced
|
|
8117
8682
|
// through McpManager diagnostics instead.
|
|
@@ -8136,14 +8701,14 @@ async function connectSdkMcpClient(input, dependencies = DEFAULT_DEPENDENCIES) {
|
|
|
8136
8701
|
}
|
|
8137
8702
|
|
|
8138
8703
|
// src/auth/oauth.ts
|
|
8139
|
-
import { randomBytes, createHash as
|
|
8704
|
+
import { randomBytes, createHash as createHash6 } from "crypto";
|
|
8140
8705
|
import { createServer } from "http";
|
|
8141
8706
|
import { URL as URL2, URLSearchParams } from "url";
|
|
8142
8707
|
|
|
8143
8708
|
// src/auth/oauth-config.ts
|
|
8144
|
-
import { createHash as
|
|
8709
|
+
import { createHash as createHash5 } from "crypto";
|
|
8145
8710
|
function oauthCredentialId(tokenUrl, clientId) {
|
|
8146
|
-
const digest =
|
|
8711
|
+
const digest = createHash5("sha256").update(`${tokenUrl}\0${clientId}`, "utf8").digest("base64url");
|
|
8147
8712
|
return `oauth:${digest}`;
|
|
8148
8713
|
}
|
|
8149
8714
|
function parseOAuthLlmConfig(value) {
|
|
@@ -8316,8 +8881,8 @@ var OAuthCallbackAuthProvider = class {
|
|
|
8316
8881
|
async #startCallbackServer(expectedState, signal) {
|
|
8317
8882
|
let serverResolve;
|
|
8318
8883
|
let serverReject;
|
|
8319
|
-
const codePromise = new Promise((
|
|
8320
|
-
serverResolve =
|
|
8884
|
+
const codePromise = new Promise((resolve25, reject) => {
|
|
8885
|
+
serverResolve = resolve25;
|
|
8321
8886
|
serverReject = reject;
|
|
8322
8887
|
});
|
|
8323
8888
|
const server = createServer((req, res) => {
|
|
@@ -8351,7 +8916,7 @@ var OAuthCallbackAuthProvider = class {
|
|
|
8351
8916
|
);
|
|
8352
8917
|
serverResolve(code);
|
|
8353
8918
|
});
|
|
8354
|
-
return new Promise((
|
|
8919
|
+
return new Promise((resolve25, reject) => {
|
|
8355
8920
|
server.once("error", (error) => {
|
|
8356
8921
|
reject(new Error(`OAuth callback server failed: ${error.message}`));
|
|
8357
8922
|
});
|
|
@@ -8368,7 +8933,7 @@ var OAuthCallbackAuthProvider = class {
|
|
|
8368
8933
|
signal.addEventListener("abort", () => {
|
|
8369
8934
|
serverReject(new Error("OAuth flow aborted"));
|
|
8370
8935
|
}, { once: true });
|
|
8371
|
-
|
|
8936
|
+
resolve25({ redirectUri, promise: codePromise });
|
|
8372
8937
|
});
|
|
8373
8938
|
});
|
|
8374
8939
|
}
|
|
@@ -8400,7 +8965,7 @@ var OAuthCallbackAuthProvider = class {
|
|
|
8400
8965
|
return randomBytes(32).toString("base64url");
|
|
8401
8966
|
}
|
|
8402
8967
|
#computeCodeChallenge(verifier) {
|
|
8403
|
-
return
|
|
8968
|
+
return createHash6("sha256").update(verifier).digest("base64url");
|
|
8404
8969
|
}
|
|
8405
8970
|
#generateState() {
|
|
8406
8971
|
return randomBytes(32).toString("base64url");
|
|
@@ -8450,13 +9015,13 @@ function escapeHtml(text) {
|
|
|
8450
9015
|
}
|
|
8451
9016
|
|
|
8452
9017
|
// src/auth/store.ts
|
|
8453
|
-
import { dirname as
|
|
9018
|
+
import { dirname as dirname10 } from "path";
|
|
8454
9019
|
function createFileTokenStore(filePath) {
|
|
8455
9020
|
let baseline = {};
|
|
8456
9021
|
const store = {
|
|
8457
9022
|
tokens: {},
|
|
8458
9023
|
async load() {
|
|
8459
|
-
const key = await loadOrCreateConfigKey(
|
|
9024
|
+
const key = await loadOrCreateConfigKey(dirname10(filePath));
|
|
8460
9025
|
const result = await readRecoverableFile(filePath, (raw) => parseTokens(decryptDocument(raw, key)));
|
|
8461
9026
|
const tokens = result?.value ?? {};
|
|
8462
9027
|
baseline = { ...tokens };
|
|
@@ -8464,7 +9029,7 @@ function createFileTokenStore(filePath) {
|
|
|
8464
9029
|
return tokens;
|
|
8465
9030
|
},
|
|
8466
9031
|
async save(tokens) {
|
|
8467
|
-
const key = await loadOrCreateConfigKey(
|
|
9032
|
+
const key = await loadOrCreateConfigKey(dirname10(filePath));
|
|
8468
9033
|
const removals = Object.keys(baseline).filter((providerId) => !(providerId in tokens));
|
|
8469
9034
|
const upserts = Object.fromEntries(Object.entries(tokens).filter(
|
|
8470
9035
|
([providerId, token]) => !sameToken(baseline[providerId], token)
|
|
@@ -8630,16 +9195,16 @@ function data(value) {
|
|
|
8630
9195
|
|
|
8631
9196
|
// src/plugins/host.ts
|
|
8632
9197
|
import { lstatSync, realpathSync as realpathSync2 } from "fs";
|
|
8633
|
-
import { lstat as
|
|
8634
|
-
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";
|
|
8635
9200
|
import { pathToFileURL } from "url";
|
|
8636
9201
|
|
|
8637
9202
|
// src/plugins/types.ts
|
|
8638
|
-
import { z as
|
|
9203
|
+
import { z as z15 } from "zod";
|
|
8639
9204
|
var PLUGIN_NAME = /^(?:@[a-z0-9][a-z0-9._-]{0,63}\/)?[a-z0-9][a-z0-9._-]{0,63}$/;
|
|
8640
9205
|
var CONTRIBUTION_NAME = /^[a-zA-Z0-9][a-zA-Z0-9._:-]{0,127}$/;
|
|
8641
|
-
var entry =
|
|
8642
|
-
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) => {
|
|
8643
9208
|
const seen = /* @__PURE__ */ new Set();
|
|
8644
9209
|
for (const [index, item] of items.entries()) {
|
|
8645
9210
|
const key = identify(item);
|
|
@@ -8647,17 +9212,17 @@ var uniqueArray = (schema, label, identify = (item) => String(item)) => z14.arra
|
|
|
8647
9212
|
seen.add(key);
|
|
8648
9213
|
}
|
|
8649
9214
|
});
|
|
8650
|
-
var PluginManifestSchema =
|
|
8651
|
-
name:
|
|
8652
|
-
version:
|
|
8653
|
-
apiVersion:
|
|
8654
|
-
main:
|
|
8655
|
-
permissions: uniqueArray(
|
|
8656
|
-
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({
|
|
8657
9222
|
commands: uniqueArray(entry, "command contribution", (item) => item.name),
|
|
8658
9223
|
tools: uniqueArray(entry, "tool contribution", (item) => item.name),
|
|
8659
|
-
hooks: uniqueArray(
|
|
8660
|
-
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),
|
|
8661
9226
|
modelAdapters: uniqueArray(entry, "model-adapter contribution", (item) => item.name)
|
|
8662
9227
|
}).strict()
|
|
8663
9228
|
}).strict();
|
|
@@ -8749,7 +9314,7 @@ var PluginHost = class {
|
|
|
8749
9314
|
const roots = [];
|
|
8750
9315
|
for (const directory of directories) {
|
|
8751
9316
|
try {
|
|
8752
|
-
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));
|
|
8753
9318
|
} catch (error) {
|
|
8754
9319
|
this.#diagnose(directory, error, directory);
|
|
8755
9320
|
}
|
|
@@ -8779,10 +9344,10 @@ var PluginHost = class {
|
|
|
8779
9344
|
async #readCandidate(inputRoot, source, label = inputRoot) {
|
|
8780
9345
|
let diagnosticLabel = label;
|
|
8781
9346
|
try {
|
|
8782
|
-
const rootInfo = await
|
|
9347
|
+
const rootInfo = await lstat6(inputRoot);
|
|
8783
9348
|
if (!rootInfo.isDirectory() || rootInfo.isSymbolicLink()) throw new Error("Plugin root must be a static directory");
|
|
8784
|
-
const root = await
|
|
8785
|
-
const rawManifest = await readManifest(
|
|
9349
|
+
const root = await realpath4(inputRoot);
|
|
9350
|
+
const rawManifest = await readManifest(resolve13(root, MANIFEST));
|
|
8786
9351
|
if (typeof rawManifest === "object" && rawManifest !== null && "name" in rawManifest && typeof rawManifest.name === "string") {
|
|
8787
9352
|
diagnosticLabel = rawManifest.name;
|
|
8788
9353
|
}
|
|
@@ -8881,7 +9446,7 @@ var PluginHost = class {
|
|
|
8881
9446
|
assertActive();
|
|
8882
9447
|
const permission = `filesystem:${operation}`;
|
|
8883
9448
|
if (!manifest.permissions.includes(permission)) throw new Error(`Plugin ${manifest.name} lacks ${permission} permission`);
|
|
8884
|
-
const path =
|
|
9449
|
+
const path = resolve13(rawPath);
|
|
8885
9450
|
const authorized = await this.#options.authorizeFilesystem?.({ plugin: manifest.name, operation, path });
|
|
8886
9451
|
assertActive();
|
|
8887
9452
|
if (authorized !== true) throw new Error(`Filesystem ${operation} denied for plugin ${manifest.name}`);
|
|
@@ -8922,8 +9487,8 @@ var PluginHost = class {
|
|
|
8922
9487
|
if (!info.isDirectory() || info.isSymbolicLink()) throw new Error(`Skill root "${name}" must be a static directory`);
|
|
8923
9488
|
const physical = realpathSync2.native(registered);
|
|
8924
9489
|
assertContained(candidate.root, physical, "Skill root");
|
|
8925
|
-
const
|
|
8926
|
-
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)) });
|
|
8927
9492
|
return register("skillRoot", name, [capability]);
|
|
8928
9493
|
},
|
|
8929
9494
|
registerModelAdapter: (name, value) => register("modelAdapter", name, [value])
|
|
@@ -8949,7 +9514,7 @@ var PluginHost = class {
|
|
|
8949
9514
|
}
|
|
8950
9515
|
};
|
|
8951
9516
|
async function readManifest(path) {
|
|
8952
|
-
const lexical = await
|
|
9517
|
+
const lexical = await lstat6(path, { bigint: true });
|
|
8953
9518
|
if (!lexical.isFile() || lexical.isSymbolicLink()) throw new Error("Plugin manifest must be a static regular file");
|
|
8954
9519
|
const handle = await open5(path, "r");
|
|
8955
9520
|
try {
|
|
@@ -8966,7 +9531,7 @@ async function readManifest(path) {
|
|
|
8966
9531
|
if (bytesRead > MAX_MANIFEST_BYTES2) throw new Error(`Plugin manifest exceeds ${MAX_MANIFEST_BYTES2} bytes`);
|
|
8967
9532
|
const after = fromStats(await handle.stat({ bigint: true }));
|
|
8968
9533
|
assertSnapshot(before, after, "Plugin manifest");
|
|
8969
|
-
const current = fromStats(await
|
|
9534
|
+
const current = fromStats(await lstat6(path, { bigint: true }));
|
|
8970
9535
|
assertSnapshot(before, current, "Plugin manifest path");
|
|
8971
9536
|
return JSON.parse(new TextDecoder("utf-8", { fatal: true }).decode(buffer.subarray(0, bytesRead)));
|
|
8972
9537
|
} finally {
|
|
@@ -8974,9 +9539,9 @@ async function readManifest(path) {
|
|
|
8974
9539
|
}
|
|
8975
9540
|
}
|
|
8976
9541
|
async function snapshotEntry(root, entry2) {
|
|
8977
|
-
const lexicalStats = await
|
|
9542
|
+
const lexicalStats = await lstat6(entry2, { bigint: true });
|
|
8978
9543
|
if (!lexicalStats.isFile() && !lexicalStats.isSymbolicLink()) throw new Error("Plugin entry must be a file");
|
|
8979
|
-
const physicalPath = await
|
|
9544
|
+
const physicalPath = await realpath4(entry2);
|
|
8980
9545
|
assertContained(root, physicalPath, "Plugin entry symlink");
|
|
8981
9546
|
const physicalStats = await stat3(physicalPath, { bigint: true });
|
|
8982
9547
|
if (!physicalStats.isFile()) throw new Error("Plugin entry must resolve to a regular file");
|
|
@@ -9005,21 +9570,21 @@ function contributionNames(manifest, kind) {
|
|
|
9005
9570
|
function fromStats(stats) {
|
|
9006
9571
|
return { dev: stats.dev, ino: stats.ino, mode: stats.mode, size: stats.size, mtimeNs: stats.mtimeNs, ctimeNs: stats.ctimeNs };
|
|
9007
9572
|
}
|
|
9008
|
-
function pickIdentity(
|
|
9009
|
-
return { dev:
|
|
9573
|
+
function pickIdentity(snapshot4) {
|
|
9574
|
+
return { dev: snapshot4.dev, ino: snapshot4.ino, mtimeNs: snapshot4.mtimeNs, ctimeNs: snapshot4.ctimeNs };
|
|
9010
9575
|
}
|
|
9011
9576
|
function assertSnapshot(expected, actual, label) {
|
|
9012
9577
|
for (const key of ["dev", "ino", "mode", "size", "mtimeNs", "ctimeNs"]) if (expected[key] !== actual[key]) throw new Error(`${label} changed during validation`);
|
|
9013
9578
|
}
|
|
9014
9579
|
function resolveContained(root, value, label) {
|
|
9015
|
-
if (
|
|
9016
|
-
const candidate =
|
|
9580
|
+
if (isAbsolute6(value)) throw new Error(`${label} must be relative to the plugin root`);
|
|
9581
|
+
const candidate = resolve13(root, value);
|
|
9017
9582
|
assertContained(root, candidate, label);
|
|
9018
9583
|
return candidate;
|
|
9019
9584
|
}
|
|
9020
9585
|
function assertContained(root, candidate, label) {
|
|
9021
|
-
const delta =
|
|
9022
|
-
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`);
|
|
9023
9588
|
}
|
|
9024
9589
|
function scopedLogger(logger, plugin) {
|
|
9025
9590
|
const prefix = `[plugin:${plugin}] `;
|
|
@@ -9063,8 +9628,8 @@ function compare(left, right) {
|
|
|
9063
9628
|
|
|
9064
9629
|
// src/skills/registry.ts
|
|
9065
9630
|
import { constants as constants3 } from "fs";
|
|
9066
|
-
import { lstat as
|
|
9067
|
-
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";
|
|
9068
9633
|
import { marked } from "marked";
|
|
9069
9634
|
import { parseDocument } from "yaml";
|
|
9070
9635
|
var SKILL_NAME = /^[a-z0-9]+(?:-[a-z0-9]+)*$/;
|
|
@@ -9189,13 +9754,13 @@ var SkillRegistry = class {
|
|
|
9189
9754
|
const record = await this.#recordFor(skill);
|
|
9190
9755
|
const normalized = normalizeReference(reference);
|
|
9191
9756
|
const parts = normalized.split("/");
|
|
9192
|
-
if (
|
|
9757
|
+
if (isAbsolute7(reference) || parts.includes("..") || parts.length !== 2 || !RESOURCE_DIRECTORIES.has(parts[0] ?? "") || !RESOURCE_NAME.test(parts[1] ?? "")) {
|
|
9193
9758
|
throw new Error(`Resource reference escapes the skill root or is not a direct resource: ${reference}`);
|
|
9194
9759
|
}
|
|
9195
9760
|
const body = await this.loadBody(record.metadata);
|
|
9196
9761
|
const references = extractResourceReferences(body);
|
|
9197
9762
|
if (!references.has(normalized)) throw new Error(`Skill resource is not directly referenced: ${reference}`);
|
|
9198
|
-
const candidate =
|
|
9763
|
+
const candidate = resolve14(record.metadata.root, ...parts);
|
|
9199
9764
|
const file = await openVerifiedFile(candidate, record.metadata.root, "Resource", this.#options.openFile);
|
|
9200
9765
|
try {
|
|
9201
9766
|
if (file.snapshot.size > BigInt(this.#options.maxResourceBytes)) throw new Error(`Skill resource is too large: ${reference}`);
|
|
@@ -9224,13 +9789,13 @@ var SkillRegistry = class {
|
|
|
9224
9789
|
async #discoverSource(roots, source) {
|
|
9225
9790
|
const found = [];
|
|
9226
9791
|
for (const configuredRoot of roots) {
|
|
9227
|
-
const root =
|
|
9792
|
+
const root = resolve14(configuredRoot);
|
|
9228
9793
|
let entries;
|
|
9229
9794
|
let physicalRoot = root;
|
|
9230
9795
|
try {
|
|
9231
|
-
const rootInfo = await
|
|
9796
|
+
const rootInfo = await lstat7(root);
|
|
9232
9797
|
if (rootInfo.isSymbolicLink() || !rootInfo.isDirectory()) throw new Error("Skill root must be a real directory");
|
|
9233
|
-
physicalRoot = await
|
|
9798
|
+
physicalRoot = await realpath5(root);
|
|
9234
9799
|
entries = await readdir5(root, { withFileTypes: true });
|
|
9235
9800
|
} catch (error) {
|
|
9236
9801
|
if (isMissing2(error)) continue;
|
|
@@ -9239,13 +9804,13 @@ var SkillRegistry = class {
|
|
|
9239
9804
|
}
|
|
9240
9805
|
entries.sort((left, right) => compareCodePoints(left.name, right.name));
|
|
9241
9806
|
for (const entry2 of entries) {
|
|
9242
|
-
const skillRoot =
|
|
9807
|
+
const skillRoot = resolve14(root, entry2.name);
|
|
9243
9808
|
try {
|
|
9244
9809
|
if (entry2.isSymbolicLink()) throw new Error("Symlinked skill directories are not allowed");
|
|
9245
9810
|
if (!entry2.isDirectory()) continue;
|
|
9246
|
-
const physicalSkillRoot = await
|
|
9811
|
+
const physicalSkillRoot = await realpath5(skillRoot);
|
|
9247
9812
|
if (!isWithin3(physicalRoot, physicalSkillRoot)) throw new Error("Skill directory escapes its registry root");
|
|
9248
|
-
const skillFile =
|
|
9813
|
+
const skillFile = resolve14(skillRoot, "SKILL.md");
|
|
9249
9814
|
const parsed = await readFrontmatterFile(
|
|
9250
9815
|
skillFile,
|
|
9251
9816
|
physicalSkillRoot,
|
|
@@ -9382,10 +9947,10 @@ async function openFile(path, flags) {
|
|
|
9382
9947
|
return defaultOpen(path, flags);
|
|
9383
9948
|
}
|
|
9384
9949
|
async function openVerifiedFile(path, root, label, opener, expected) {
|
|
9385
|
-
const initial = await
|
|
9950
|
+
const initial = await lstat7(path, { bigint: true });
|
|
9386
9951
|
if (initial.isSymbolicLink()) throw new Error(`${label} symlinks are not allowed`);
|
|
9387
9952
|
assertRegular(initial, label);
|
|
9388
|
-
const canonical3 = await
|
|
9953
|
+
const canonical3 = await realpath5(path);
|
|
9389
9954
|
if (!isWithin3(root, canonical3)) throw new Error(`${label} escapes the skill root`);
|
|
9390
9955
|
const noFollow = typeof constants3.O_NOFOLLOW === "number" ? constants3.O_NOFOLLOW : 0;
|
|
9391
9956
|
const handle = await opener(canonical3, constants3.O_RDONLY | noFollow);
|
|
@@ -9446,8 +10011,8 @@ function decodeUtf82(content, label) {
|
|
|
9446
10011
|
}
|
|
9447
10012
|
}
|
|
9448
10013
|
function isWithin3(root, candidate) {
|
|
9449
|
-
const delta =
|
|
9450
|
-
return delta === "" || delta !== ".." && !delta.startsWith(`..${
|
|
10014
|
+
const delta = relative12(root, candidate);
|
|
10015
|
+
return delta === "" || delta !== ".." && !delta.startsWith(`..${sep11}`) && !isAbsolute7(delta);
|
|
9451
10016
|
}
|
|
9452
10017
|
function isMissing2(error) {
|
|
9453
10018
|
return typeof error === "object" && error !== null && "code" in error && error.code === "ENOENT";
|
|
@@ -9494,10 +10059,10 @@ function score(metadata, query) {
|
|
|
9494
10059
|
}
|
|
9495
10060
|
|
|
9496
10061
|
// src/skills/tool.ts
|
|
9497
|
-
import { z as
|
|
9498
|
-
var SkillResourceInput =
|
|
9499
|
-
skill:
|
|
9500
|
-
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)
|
|
9501
10066
|
}).strict();
|
|
9502
10067
|
function createSkillResourceTool(registry) {
|
|
9503
10068
|
return {
|
|
@@ -9524,97 +10089,97 @@ function createSkillResourceTool(registry) {
|
|
|
9524
10089
|
|
|
9525
10090
|
// src/session/store.ts
|
|
9526
10091
|
import { constants as constants4 } from "fs";
|
|
9527
|
-
import { lstat as
|
|
9528
|
-
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";
|
|
9529
10094
|
import { randomUUID as randomUUID6 } from "crypto";
|
|
9530
|
-
import { z as
|
|
10095
|
+
import { z as z17 } from "zod";
|
|
9531
10096
|
var SESSION_VERSION = 3;
|
|
9532
10097
|
var DEFAULT_MAX_SESSION_BYTES = 5 * 1024 * 1024;
|
|
9533
|
-
var SessionIdSchema =
|
|
9534
|
-
var IsoDateSchema =
|
|
9535
|
-
var ToolCallSchema =
|
|
9536
|
-
id:
|
|
9537
|
-
name:
|
|
9538
|
-
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()
|
|
9539
10104
|
}).strict();
|
|
9540
|
-
var TextContentBlockSchema =
|
|
9541
|
-
type:
|
|
9542
|
-
text:
|
|
10105
|
+
var TextContentBlockSchema = z17.object({
|
|
10106
|
+
type: z17.literal("text"),
|
|
10107
|
+
text: z17.string().max(DEFAULT_MAX_SESSION_BYTES)
|
|
9543
10108
|
}).strict();
|
|
9544
|
-
var ImageContentBlockSchema =
|
|
9545
|
-
type:
|
|
9546
|
-
source:
|
|
9547
|
-
mediaType:
|
|
9548
|
-
sha256:
|
|
9549
|
-
bytes:
|
|
9550
|
-
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()
|
|
9551
10116
|
}).strict();
|
|
9552
|
-
var UserContentSchema =
|
|
9553
|
-
|
|
9554
|
-
|
|
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)
|
|
9555
10120
|
]);
|
|
9556
10121
|
var MessageMetadataShape = {
|
|
9557
|
-
toolCallId:
|
|
9558
|
-
toolCalls:
|
|
10122
|
+
toolCallId: z17.string().min(1).max(256).optional(),
|
|
10123
|
+
toolCalls: z17.array(ToolCallSchema).max(1e3).optional()
|
|
9559
10124
|
};
|
|
9560
|
-
var MessageSchema =
|
|
9561
|
-
|
|
9562
|
-
|
|
9563
|
-
|
|
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()
|
|
9564
10129
|
]);
|
|
9565
|
-
var LegacySummarySchema =
|
|
9566
|
-
var CompactBoundarySchema =
|
|
9567
|
-
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),
|
|
9568
10133
|
compactedAt: IsoDateSchema
|
|
9569
10134
|
}).strict();
|
|
9570
|
-
var StateSchema =
|
|
9571
|
-
var MemoryLifecycleSchema =
|
|
9572
|
-
status:
|
|
9573
|
-
taskId:
|
|
9574
|
-
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(),
|
|
9575
10140
|
finalizedAt: IsoDateSchema.optional(),
|
|
9576
|
-
transcriptHash:
|
|
10141
|
+
transcriptHash: z17.string().regex(/^[a-f0-9]{64}$/).optional()
|
|
9577
10142
|
}).strict();
|
|
9578
|
-
var SessionBaseSchema =
|
|
10143
|
+
var SessionBaseSchema = z17.object({
|
|
9579
10144
|
sessionId: SessionIdSchema,
|
|
9580
10145
|
createdAt: IsoDateSchema,
|
|
9581
10146
|
updatedAt: IsoDateSchema,
|
|
9582
|
-
workspace:
|
|
9583
|
-
tasks:
|
|
10147
|
+
workspace: z17.object({ path: z17.string().min(1).max(32768) }).strict(),
|
|
10148
|
+
tasks: z17.object({
|
|
9584
10149
|
plan: TaskPlanSchema.optional(),
|
|
9585
10150
|
graph: TaskGraphSchema.optional(),
|
|
9586
|
-
states:
|
|
9587
|
-
results:
|
|
10151
|
+
states: z17.record(z17.string(), StateSchema),
|
|
10152
|
+
results: z17.record(z17.string(), SubagentResultSchema)
|
|
9588
10153
|
}).strict(),
|
|
9589
|
-
models:
|
|
10154
|
+
models: z17.object({ main: z17.string().min(1).max(1024), subagent: z17.string().min(1).max(1024) }).strict(),
|
|
9590
10155
|
permissionMode: PermissionModeSchema,
|
|
9591
10156
|
memory: MemoryLifecycleSchema.optional()
|
|
9592
10157
|
}).strict();
|
|
9593
10158
|
var SessionDocumentV1Schema = SessionBaseSchema.extend({
|
|
9594
|
-
version:
|
|
9595
|
-
conversation:
|
|
10159
|
+
version: z17.literal(1),
|
|
10160
|
+
conversation: z17.object({
|
|
9596
10161
|
summary: LegacySummarySchema.optional(),
|
|
9597
|
-
messages:
|
|
10162
|
+
messages: z17.array(MessageSchema).max(5e4)
|
|
9598
10163
|
}).strict()
|
|
9599
10164
|
}).strict();
|
|
9600
|
-
var ConversationSchema =
|
|
10165
|
+
var ConversationSchema = z17.object({
|
|
9601
10166
|
compact: CompactBoundarySchema.optional(),
|
|
9602
|
-
messages:
|
|
10167
|
+
messages: z17.array(MessageSchema).max(5e4)
|
|
9603
10168
|
}).strict();
|
|
9604
10169
|
var SessionDocumentV2Schema = SessionBaseSchema.extend({
|
|
9605
|
-
version:
|
|
10170
|
+
version: z17.literal(2),
|
|
9606
10171
|
conversation: ConversationSchema
|
|
9607
10172
|
}).strict();
|
|
9608
|
-
var TranscriptStateSchema =
|
|
9609
|
-
var TimelineSchema =
|
|
9610
|
-
version:
|
|
10173
|
+
var TranscriptStateSchema = z17.custom(isTranscriptState, "Invalid transcript state");
|
|
10174
|
+
var TimelineSchema = z17.object({
|
|
10175
|
+
version: z17.literal(1),
|
|
9611
10176
|
state: TranscriptStateSchema
|
|
9612
10177
|
}).strict();
|
|
9613
10178
|
var SessionDocumentSchema = SessionBaseSchema.extend({
|
|
9614
|
-
version:
|
|
9615
|
-
conversation:
|
|
10179
|
+
version: z17.literal(SESSION_VERSION),
|
|
10180
|
+
conversation: z17.object({
|
|
9616
10181
|
compact: CompactBoundarySchema.optional(),
|
|
9617
|
-
messages:
|
|
10182
|
+
messages: z17.array(MessageSchema).max(5e4)
|
|
9618
10183
|
}).strict(),
|
|
9619
10184
|
timeline: TimelineSchema
|
|
9620
10185
|
}).strict();
|
|
@@ -9624,7 +10189,7 @@ var SessionStore = class {
|
|
|
9624
10189
|
#maxBytes;
|
|
9625
10190
|
#maxSessions;
|
|
9626
10191
|
constructor(options) {
|
|
9627
|
-
this.#workspace =
|
|
10192
|
+
this.#workspace = resolve15(options.workspace);
|
|
9628
10193
|
this.#sessions = join12(this.#workspace, ".flavor", "sessions");
|
|
9629
10194
|
this.#maxBytes = options.maxBytes ?? DEFAULT_MAX_SESSION_BYTES;
|
|
9630
10195
|
if (!Number.isSafeInteger(this.#maxBytes) || this.#maxBytes < 256) throw new Error("maxBytes must be an integer of at least 256");
|
|
@@ -9761,7 +10326,7 @@ var SessionStore = class {
|
|
|
9761
10326
|
}
|
|
9762
10327
|
const path = this.#path(sessionId);
|
|
9763
10328
|
try {
|
|
9764
|
-
const metadata = await
|
|
10329
|
+
const metadata = await lstat8(path);
|
|
9765
10330
|
if (!metadata.isFile()) throw new Error("Session path is not a regular file");
|
|
9766
10331
|
await rm4(path);
|
|
9767
10332
|
} catch (error) {
|
|
@@ -9770,7 +10335,7 @@ var SessionStore = class {
|
|
|
9770
10335
|
}
|
|
9771
10336
|
#path(sessionId) {
|
|
9772
10337
|
SessionIdSchema.parse(sessionId);
|
|
9773
|
-
const path =
|
|
10338
|
+
const path = resolve15(this.#sessions, `${sessionId}.jsonl`);
|
|
9774
10339
|
if (!isWithin4(this.#sessions, path)) throw new Error("Invalid session id");
|
|
9775
10340
|
return path;
|
|
9776
10341
|
}
|
|
@@ -9790,14 +10355,14 @@ var SessionStore = class {
|
|
|
9790
10355
|
}
|
|
9791
10356
|
}
|
|
9792
10357
|
async #prepareDirectory() {
|
|
9793
|
-
await assertNoSymlink3(this.#workspace,
|
|
10358
|
+
await assertNoSymlink3(this.#workspace, dirname11(this.#sessions));
|
|
9794
10359
|
await mkdir9(this.#sessions, { recursive: true, mode: 448 });
|
|
9795
10360
|
await this.#assertSafeExistingDirectory();
|
|
9796
10361
|
}
|
|
9797
10362
|
async #assertSafeExistingDirectory() {
|
|
9798
10363
|
await assertNoSymlink3(this.#workspace, this.#sessions);
|
|
9799
|
-
const canonicalRoot = await
|
|
9800
|
-
const canonicalSessions = await
|
|
10364
|
+
const canonicalRoot = await realpath6(this.#workspace);
|
|
10365
|
+
const canonicalSessions = await realpath6(this.#sessions);
|
|
9801
10366
|
if (!isWithin4(canonicalRoot, canonicalSessions)) throw new Error("Session directory escapes the workspace");
|
|
9802
10367
|
}
|
|
9803
10368
|
async #quarantine(path) {
|
|
@@ -9810,7 +10375,7 @@ async function boundedRead(path, maxBytes) {
|
|
|
9810
10375
|
const metadata = await stat4(path);
|
|
9811
10376
|
if (!metadata.isFile()) throw new Error("Session path is not a regular file");
|
|
9812
10377
|
if (metadata.size > maxBytes) throw new Error(`Session exceeds maximum read size of ${maxBytes} bytes`);
|
|
9813
|
-
const value = await
|
|
10378
|
+
const value = await readFile11(path);
|
|
9814
10379
|
if (value.byteLength > maxBytes) throw new Error(`Session exceeds maximum read size of ${maxBytes} bytes`);
|
|
9815
10380
|
return value.toString("utf8");
|
|
9816
10381
|
}
|
|
@@ -9948,31 +10513,31 @@ function isRecord5(value) {
|
|
|
9948
10513
|
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
9949
10514
|
}
|
|
9950
10515
|
async function assertNoSymlink3(root, target) {
|
|
9951
|
-
let cursor =
|
|
9952
|
-
const stop =
|
|
10516
|
+
let cursor = resolve15(target);
|
|
10517
|
+
const stop = resolve15(root);
|
|
9953
10518
|
if (!isWithin4(stop, cursor)) throw new Error("Session path escapes the workspace");
|
|
9954
10519
|
const chain = [];
|
|
9955
10520
|
while (cursor !== stop) {
|
|
9956
10521
|
chain.push(cursor);
|
|
9957
|
-
cursor =
|
|
10522
|
+
cursor = dirname11(cursor);
|
|
9958
10523
|
}
|
|
9959
10524
|
for (const path of chain.reverse()) {
|
|
9960
10525
|
try {
|
|
9961
|
-
if ((await
|
|
10526
|
+
if ((await lstat8(path)).isSymbolicLink()) throw new Error(`Session path contains a symbolic link: ${path}`);
|
|
9962
10527
|
} catch (error) {
|
|
9963
10528
|
if (!isCode6(error, "ENOENT")) throw error;
|
|
9964
10529
|
}
|
|
9965
10530
|
}
|
|
9966
10531
|
}
|
|
9967
10532
|
function isWithin4(root, path) {
|
|
9968
|
-
const value =
|
|
9969
|
-
return value === "" || !value.startsWith(`..${
|
|
10533
|
+
const value = relative13(resolve15(root), resolve15(path));
|
|
10534
|
+
return value === "" || !value.startsWith(`..${sep12}`) && value !== ".." && !value.includes(":");
|
|
9970
10535
|
}
|
|
9971
10536
|
async function canonical2(path) {
|
|
9972
10537
|
try {
|
|
9973
|
-
return await
|
|
10538
|
+
return await realpath6(resolve15(path));
|
|
9974
10539
|
} catch {
|
|
9975
|
-
return
|
|
10540
|
+
return resolve15(path);
|
|
9976
10541
|
}
|
|
9977
10542
|
}
|
|
9978
10543
|
function isCode6(error, code) {
|
|
@@ -9999,45 +10564,45 @@ function sanitize2(input, seen = /* @__PURE__ */ new WeakSet()) {
|
|
|
9999
10564
|
// src/sleep/organizer.ts
|
|
10000
10565
|
import { constants as constants5 } from "fs";
|
|
10001
10566
|
import { randomUUID as randomUUID7 } from "crypto";
|
|
10002
|
-
import { lstat as
|
|
10003
|
-
import { isAbsolute as
|
|
10004
|
-
import { z as
|
|
10005
|
-
var DateKeySchema =
|
|
10006
|
-
var ReviewSchema =
|
|
10007
|
-
title:
|
|
10008
|
-
taskSummary:
|
|
10009
|
-
executionReflection:
|
|
10010
|
-
decisionsAndLearnings:
|
|
10011
|
-
openQuestionsAndRisks:
|
|
10012
|
-
tomorrowPlan:
|
|
10013
|
-
toolUsage:
|
|
10014
|
-
totalCalls:
|
|
10015
|
-
shell:
|
|
10016
|
-
fileRead:
|
|
10017
|
-
fileWrite:
|
|
10018
|
-
search:
|
|
10019
|
-
lsp:
|
|
10020
|
-
subagent:
|
|
10021
|
-
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)
|
|
10022
10587
|
}).strict(),
|
|
10023
|
-
tokenEstimate:
|
|
10024
|
-
estimatedInput:
|
|
10025
|
-
estimatedOutput:
|
|
10026
|
-
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)
|
|
10027
10592
|
}).strict(),
|
|
10028
|
-
humanIntervention:
|
|
10029
|
-
approvalRequests:
|
|
10030
|
-
questionsAsked:
|
|
10031
|
-
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)
|
|
10032
10597
|
}).strict(),
|
|
10033
|
-
qualityIndicators:
|
|
10034
|
-
hallucinationAlerts:
|
|
10035
|
-
failuresAndRetries:
|
|
10036
|
-
codeChangeSummary:
|
|
10037
|
-
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)
|
|
10038
10603
|
}).strict(),
|
|
10039
|
-
knowledgeDeposits:
|
|
10040
|
-
worthRemembering:
|
|
10604
|
+
knowledgeDeposits: z18.object({
|
|
10605
|
+
worthRemembering: z18.array(z18.string().min(1)).max(10)
|
|
10041
10606
|
}).strict()
|
|
10042
10607
|
}).strict();
|
|
10043
10608
|
function localDateKey(date) {
|
|
@@ -10054,7 +10619,7 @@ var ProjectSleepOrganizer = class {
|
|
|
10054
10619
|
#sessions;
|
|
10055
10620
|
#generate;
|
|
10056
10621
|
constructor(options) {
|
|
10057
|
-
this.#workspace =
|
|
10622
|
+
this.#workspace = resolve16(options.workspace);
|
|
10058
10623
|
this.#sleepDirectory = join13(this.#workspace, ".flavor", "sleep");
|
|
10059
10624
|
this.#sessions = options.sessions ?? new SessionStore({ workspace: this.#workspace });
|
|
10060
10625
|
this.#generate = options.generate;
|
|
@@ -10146,8 +10711,8 @@ Previous output was invalid. Repair it and return strict JSON only.`;
|
|
|
10146
10711
|
await mkdir10(flavorDirectory, { recursive: true, mode: 448 });
|
|
10147
10712
|
await assertNotSymlink(this.#sleepDirectory);
|
|
10148
10713
|
await mkdir10(this.#sleepDirectory, { recursive: true, mode: 448 });
|
|
10149
|
-
const root = await
|
|
10150
|
-
const target = await
|
|
10714
|
+
const root = await realpath7(this.#workspace);
|
|
10715
|
+
const target = await realpath7(this.#sleepDirectory);
|
|
10151
10716
|
if (!isWithin5(root, target)) throw new Error("Sleep directory escapes the workspace");
|
|
10152
10717
|
}
|
|
10153
10718
|
async #hasReport(date) {
|
|
@@ -10471,16 +11036,16 @@ function assertDateKey(value) {
|
|
|
10471
11036
|
}
|
|
10472
11037
|
async function assertNotSymlink(path) {
|
|
10473
11038
|
try {
|
|
10474
|
-
if ((await
|
|
11039
|
+
if ((await lstat9(path)).isSymbolicLink()) throw new Error(`Refusing to use symbolic link: ${path}`);
|
|
10475
11040
|
} catch (error) {
|
|
10476
11041
|
if (!hasCode(error, "ENOENT")) throw error;
|
|
10477
11042
|
}
|
|
10478
11043
|
}
|
|
10479
11044
|
function isWithin5(root, candidate) {
|
|
10480
|
-
const path =
|
|
11045
|
+
const path = relative14(root, candidate);
|
|
10481
11046
|
if (path === "") return true;
|
|
10482
|
-
if (path === ".." || path.startsWith(`..${
|
|
10483
|
-
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}`);
|
|
10484
11049
|
}
|
|
10485
11050
|
function hasCode(error, code) {
|
|
10486
11051
|
return typeof error === "object" && error !== null && "code" in error && error.code === code;
|
|
@@ -10529,10 +11094,10 @@ function computeSessionStats(documents) {
|
|
|
10529
11094
|
|
|
10530
11095
|
// src/tools/files.ts
|
|
10531
11096
|
import { constants as constants6 } from "fs";
|
|
10532
|
-
import { mkdir as mkdir11, open as open8, readFile as
|
|
10533
|
-
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";
|
|
10534
11099
|
import { randomUUID as randomUUID8 } from "crypto";
|
|
10535
|
-
import { z as
|
|
11100
|
+
import { z as z19 } from "zod";
|
|
10536
11101
|
|
|
10537
11102
|
// src/tools/file-diff.ts
|
|
10538
11103
|
var MAX_DIFF_PREVIEW_LINES = 120;
|
|
@@ -10638,18 +11203,34 @@ function truncateLines(lines) {
|
|
|
10638
11203
|
|
|
10639
11204
|
// src/tools/files.ts
|
|
10640
11205
|
var MAX_READ_BYTES = 1048576;
|
|
10641
|
-
var
|
|
10642
|
-
path:
|
|
10643
|
-
maxBytes:
|
|
10644
|
-
startLine:
|
|
10645
|
-
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(),
|
|
10646
11211
|
// Accepts booleans and their string forms (weak-typed models emit "true");
|
|
10647
11212
|
// kept transform-free so the schema converts to JSON Schema for providers.
|
|
10648
|
-
force:
|
|
11213
|
+
force: z19.union([z19.boolean(), z19.string().refine((value) => value === "true" || value === "false")]).nullable().optional()
|
|
10649
11214
|
});
|
|
10650
|
-
var WriteInput =
|
|
10651
|
-
var EditInput =
|
|
10652
|
-
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
|
+
};
|
|
10653
11234
|
function createReadTool(workspace, options = {}) {
|
|
10654
11235
|
const guard = createPathGuard(workspace);
|
|
10655
11236
|
const openFile2 = options.openFile ?? ((path) => open8(path, constants6.O_RDONLY));
|
|
@@ -10657,7 +11238,7 @@ function createReadTool(workspace, options = {}) {
|
|
|
10657
11238
|
return {
|
|
10658
11239
|
name: "Read",
|
|
10659
11240
|
description: "Read a UTF-8 text file, optionally restricted to a line range with startLine/endLine",
|
|
10660
|
-
inputSchema:
|
|
11241
|
+
inputSchema: ReadInput2,
|
|
10661
11242
|
paths: (input) => [guard.lexical(input.path)],
|
|
10662
11243
|
execute: async (input, signal) => {
|
|
10663
11244
|
abortIfNeeded(signal);
|
|
@@ -10679,6 +11260,7 @@ function createReadTool(workspace, options = {}) {
|
|
|
10679
11260
|
const textEnd = utf8SafeEnd(contents, windowBytes);
|
|
10680
11261
|
const truncated = contents.length > windowBytes;
|
|
10681
11262
|
const text = contents.subarray(0, textEnd).toString("utf8").replaceAll("\r\n", "\n");
|
|
11263
|
+
options.observations?.set(path, versionOfStat(info));
|
|
10682
11264
|
const lines = text.split("\n");
|
|
10683
11265
|
if (lines.at(-1) === "") lines.pop();
|
|
10684
11266
|
const availableLines = lines.length;
|
|
@@ -10725,6 +11307,7 @@ function createWriteTool(workspace, options = {}) {
|
|
|
10725
11307
|
execute: async (input, signal) => {
|
|
10726
11308
|
abortIfNeeded(signal);
|
|
10727
11309
|
const path = await guard.destination(input.path);
|
|
11310
|
+
const expected = await expectedMutationVersion(path, options.observations);
|
|
10728
11311
|
const previous = await readOptionalPresentationText(path);
|
|
10729
11312
|
if (!previous.exists || previous.text !== void 0) {
|
|
10730
11313
|
await options.beforeCommit?.({
|
|
@@ -10734,7 +11317,9 @@ function createWriteTool(workspace, options = {}) {
|
|
|
10734
11317
|
kind: previous.exists ? "update" : "create"
|
|
10735
11318
|
}, signal);
|
|
10736
11319
|
}
|
|
10737
|
-
await
|
|
11320
|
+
await assertMutationVersion(path, expected, options.observations);
|
|
11321
|
+
await atomicWrite(path, input.content, signal, expected);
|
|
11322
|
+
await options.observations?.refresh(path);
|
|
10738
11323
|
const output = { path, bytes: Buffer.byteLength(input.content) };
|
|
10739
11324
|
if (previous.exists && previous.text === void 0) return output;
|
|
10740
11325
|
return withToolPresentation(output, buildFileChangePresentation(
|
|
@@ -10757,6 +11342,7 @@ function createEditTool(workspace, options = {}) {
|
|
|
10757
11342
|
abortIfNeeded(signal);
|
|
10758
11343
|
const displayPath = guard.lexical(input.path);
|
|
10759
11344
|
const path = await guard.existing(input.path);
|
|
11345
|
+
const expected = await expectedMutationVersion(path, options.observations);
|
|
10760
11346
|
const contents = await readText(path);
|
|
10761
11347
|
const hasCRLF = contents.includes("\r\n");
|
|
10762
11348
|
const norm = (s) => hasCRLF ? s.replace(/\r\n/g, "\n") : s;
|
|
@@ -10772,7 +11358,9 @@ function createEditTool(workspace, options = {}) {
|
|
|
10772
11358
|
const updatedLF = contentsLF.slice(0, first) + newTextLF + contentsLF.slice(first + oldTextLF.length);
|
|
10773
11359
|
const updated = hasCRLF ? updatedLF.replace(/\n/g, "\r\n") : updatedLF;
|
|
10774
11360
|
await options.beforeCommit?.({ path: displayPath, before: contents, after: updated, kind: "update" }, signal);
|
|
10775
|
-
await
|
|
11361
|
+
await assertMutationVersion(path, expected, options.observations);
|
|
11362
|
+
await atomicWrite(path, updated, signal, expected);
|
|
11363
|
+
await options.observations?.refresh(path);
|
|
10776
11364
|
return withToolPresentation(
|
|
10777
11365
|
{ path: displayPath, replacements: 1 },
|
|
10778
11366
|
buildFileChangePresentation(displayPath, contents, updated, "update")
|
|
@@ -10793,10 +11381,12 @@ function createApplyPatchTool(workspace, options = {}) {
|
|
|
10793
11381
|
const prepared = [];
|
|
10794
11382
|
for (const change of changes) {
|
|
10795
11383
|
const path = await guard.destination(change.path);
|
|
11384
|
+
const expected = await expectedMutationVersion(path, options.observations);
|
|
10796
11385
|
const original = change.created ? await requireAbsent(guard, change.path) : await readText(await guard.existing(change.path));
|
|
10797
11386
|
const applied = applyHunks(original, change.hunks);
|
|
10798
|
-
prepared.push({ path, content: applied.content, change, hunks: applied.hunks });
|
|
11387
|
+
prepared.push({ path, content: applied.content, change, hunks: applied.hunks, expected });
|
|
10799
11388
|
}
|
|
11389
|
+
for (const change of prepared) await assertMutationVersion(change.path, change.expected, options.observations);
|
|
10800
11390
|
for (const change of prepared) {
|
|
10801
11391
|
const before = change.change.created ? "" : await readText(change.path);
|
|
10802
11392
|
await options.beforeCommit?.({
|
|
@@ -10805,43 +11395,49 @@ function createApplyPatchTool(workspace, options = {}) {
|
|
|
10805
11395
|
after: change.content,
|
|
10806
11396
|
kind: change.change.created ? "create" : "update"
|
|
10807
11397
|
}, signal);
|
|
10808
|
-
|
|
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);
|
|
10809
11403
|
}
|
|
10810
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));
|
|
10811
11407
|
return withToolPresentation(
|
|
10812
11408
|
{ files: prepared.map((change) => change.path) },
|
|
10813
|
-
|
|
11409
|
+
relatedChanges.length === 0 ? primaryPresentation : { ...primaryPresentation, relatedChanges }
|
|
10814
11410
|
);
|
|
10815
11411
|
}
|
|
10816
11412
|
};
|
|
10817
11413
|
}
|
|
10818
11414
|
function createPathGuard(workspace) {
|
|
10819
|
-
const root =
|
|
11415
|
+
const root = resolve17(workspace);
|
|
10820
11416
|
const lexical = (input) => {
|
|
10821
|
-
const candidate =
|
|
10822
|
-
if (!
|
|
11417
|
+
const candidate = resolve17(root, input);
|
|
11418
|
+
if (!within2(root, candidate)) throw new Error("Path is outside the workspace");
|
|
10823
11419
|
return candidate;
|
|
10824
11420
|
};
|
|
10825
11421
|
return {
|
|
10826
11422
|
lexical,
|
|
10827
11423
|
existing: async (input) => {
|
|
10828
11424
|
const candidate = lexical(input);
|
|
10829
|
-
const physical = await
|
|
10830
|
-
if (!
|
|
11425
|
+
const physical = await realpath8(candidate);
|
|
11426
|
+
if (!within2(await realpath8(root), physical)) throw new Error("Path escapes the workspace through a symlink");
|
|
10831
11427
|
return physical;
|
|
10832
11428
|
},
|
|
10833
11429
|
destination: async (input) => {
|
|
10834
11430
|
const candidate = lexical(input);
|
|
10835
|
-
const physicalRoot = await
|
|
11431
|
+
const physicalRoot = await realpath8(root);
|
|
10836
11432
|
let ancestor = candidate;
|
|
10837
11433
|
while (true) {
|
|
10838
11434
|
try {
|
|
10839
|
-
const physical = await
|
|
10840
|
-
if (!
|
|
11435
|
+
const physical = await realpath8(ancestor);
|
|
11436
|
+
if (!within2(physicalRoot, physical)) throw new Error("Path escapes the workspace through a symlink");
|
|
10841
11437
|
break;
|
|
10842
11438
|
} catch (error) {
|
|
10843
11439
|
if (!isMissing3(error)) throw error;
|
|
10844
|
-
const parent =
|
|
11440
|
+
const parent = dirname12(ancestor);
|
|
10845
11441
|
if (parent === ancestor) throw error;
|
|
10846
11442
|
ancestor = parent;
|
|
10847
11443
|
}
|
|
@@ -10850,9 +11446,9 @@ function createPathGuard(workspace) {
|
|
|
10850
11446
|
}
|
|
10851
11447
|
};
|
|
10852
11448
|
}
|
|
10853
|
-
async function atomicWrite(path, content, signal) {
|
|
11449
|
+
async function atomicWrite(path, content, signal, expectedVersion) {
|
|
10854
11450
|
abortIfNeeded(signal);
|
|
10855
|
-
await mkdir11(
|
|
11451
|
+
await mkdir11(dirname12(path), { recursive: true });
|
|
10856
11452
|
const temporary = `${path}.${randomUUID8()}.tmp`;
|
|
10857
11453
|
try {
|
|
10858
11454
|
const handle = await open8(temporary, constants6.O_CREAT | constants6.O_EXCL | constants6.O_WRONLY, 384);
|
|
@@ -10863,19 +11459,46 @@ async function atomicWrite(path, content, signal) {
|
|
|
10863
11459
|
await handle.close();
|
|
10864
11460
|
}
|
|
10865
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
|
+
}
|
|
10866
11465
|
await rename7(temporary, path);
|
|
10867
11466
|
} finally {
|
|
10868
11467
|
await rm6(temporary, { force: true });
|
|
10869
11468
|
}
|
|
10870
11469
|
}
|
|
10871
11470
|
async function readText(path) {
|
|
10872
|
-
const contents = await
|
|
11471
|
+
const contents = await readFile12(path);
|
|
10873
11472
|
if (isBinary(contents)) throw new Error("Cannot edit binary file as text");
|
|
10874
11473
|
return contents.toString("utf8");
|
|
10875
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
|
+
}
|
|
10876
11499
|
async function readOptionalPresentationText(path) {
|
|
10877
11500
|
try {
|
|
10878
|
-
const contents = await
|
|
11501
|
+
const contents = await readFile12(path);
|
|
10879
11502
|
return isBinary(contents) ? { exists: true } : { exists: true, text: contents.toString("utf8") };
|
|
10880
11503
|
} catch (error) {
|
|
10881
11504
|
return isMissing3(error) ? { exists: false } : { exists: true };
|
|
@@ -10959,9 +11582,9 @@ async function readBounded2(path, maxBytes, signal, openFile2) {
|
|
|
10959
11582
|
await handle.close();
|
|
10960
11583
|
}
|
|
10961
11584
|
}
|
|
10962
|
-
function
|
|
10963
|
-
const delta =
|
|
10964
|
-
return delta === "" || !delta.startsWith(`..${
|
|
11585
|
+
function within2(root, candidate) {
|
|
11586
|
+
const delta = relative15(root, candidate);
|
|
11587
|
+
return delta === "" || !delta.startsWith(`..${sep14}`) && delta !== ".." && !isAbsolute9(delta);
|
|
10965
11588
|
}
|
|
10966
11589
|
function forceFlag(value) {
|
|
10967
11590
|
return value === true || value === "true";
|
|
@@ -11182,13 +11805,321 @@ function matchesLines(source, start, expected) {
|
|
|
11182
11805
|
return start >= 0 && start + expected.length <= source.length && expected.every((line, index) => source[start + index] === line);
|
|
11183
11806
|
}
|
|
11184
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
|
+
|
|
11185
12116
|
// src/tools/lsp.ts
|
|
11186
12117
|
import { spawn as spawn5 } from "child_process";
|
|
11187
12118
|
import { existsSync as existsSync2 } from "fs";
|
|
11188
|
-
import { access, constants as constants7, readFile as
|
|
11189
|
-
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";
|
|
11190
12121
|
import { fileURLToPath as fileURLToPath3, pathToFileURL as pathToFileURL2 } from "url";
|
|
11191
|
-
import { z as
|
|
12122
|
+
import { z as z21 } from "zod";
|
|
11192
12123
|
var LSP_REQUEST_TIMEOUT_MS = 15e3;
|
|
11193
12124
|
var LspConnection = class {
|
|
11194
12125
|
#process;
|
|
@@ -11285,8 +12216,8 @@ var LspConnection = class {
|
|
|
11285
12216
|
const id = this.#nextId++;
|
|
11286
12217
|
const request = { jsonrpc: "2.0", id, method, params };
|
|
11287
12218
|
const timeout = AbortSignal.timeout(LSP_REQUEST_TIMEOUT_MS);
|
|
11288
|
-
const promise = new Promise((
|
|
11289
|
-
this.#pending.set(id, { resolve:
|
|
12219
|
+
const promise = new Promise((resolve25, reject) => {
|
|
12220
|
+
this.#pending.set(id, { resolve: resolve25, reject });
|
|
11290
12221
|
timeout.addEventListener("abort", () => {
|
|
11291
12222
|
if (this.#pending.has(id)) {
|
|
11292
12223
|
this.#pending.delete(id);
|
|
@@ -11351,7 +12282,7 @@ var LspConnection = class {
|
|
|
11351
12282
|
const url = new URL(uri);
|
|
11352
12283
|
if (url.protocol !== "file:") return;
|
|
11353
12284
|
const filePath = fileURLToPath3(url);
|
|
11354
|
-
const text = await
|
|
12285
|
+
const text = await readFile13(filePath, "utf8");
|
|
11355
12286
|
const current = this.#documents.get(uri);
|
|
11356
12287
|
if (current === void 0) {
|
|
11357
12288
|
this.#documents.set(uri, { text, version: 1 });
|
|
@@ -11402,8 +12333,8 @@ var LspBuffer = class {
|
|
|
11402
12333
|
}
|
|
11403
12334
|
}
|
|
11404
12335
|
};
|
|
11405
|
-
var TYPESCRIPT_CLI =
|
|
11406
|
-
|
|
12336
|
+
var TYPESCRIPT_CLI = resolve18(
|
|
12337
|
+
dirname13(fileURLToPath3(import.meta.resolve("typescript/package.json"))),
|
|
11407
12338
|
"lib",
|
|
11408
12339
|
"tsc.js"
|
|
11409
12340
|
);
|
|
@@ -11444,7 +12375,7 @@ var RealLspManager = class {
|
|
|
11444
12375
|
#serverConfigs;
|
|
11445
12376
|
#onStatus;
|
|
11446
12377
|
constructor(options) {
|
|
11447
|
-
this.#workspace =
|
|
12378
|
+
this.#workspace = resolve18(options.workspace);
|
|
11448
12379
|
this.#serverConfigs = options.serverConfigs ?? KNOWN_SERVERS;
|
|
11449
12380
|
this.#onStatus = options.onStatus;
|
|
11450
12381
|
}
|
|
@@ -11493,7 +12424,7 @@ var RealLspManager = class {
|
|
|
11493
12424
|
if (pending !== void 0) return pending;
|
|
11494
12425
|
const config = this.#serverConfigs.find((c) => c.language === language);
|
|
11495
12426
|
if (config === void 0) return void 0;
|
|
11496
|
-
const hasRoot = config.rootFiles.some((file) => existsSync2(
|
|
12427
|
+
const hasRoot = config.rootFiles.some((file) => existsSync2(resolve18(this.#workspace, file)));
|
|
11497
12428
|
if (!hasRoot) return void 0;
|
|
11498
12429
|
const startPromise = this.#startServer(config);
|
|
11499
12430
|
this.#pendingStarts.set(language, startPromise);
|
|
@@ -11522,24 +12453,24 @@ var RealLspManager = class {
|
|
|
11522
12453
|
return connection;
|
|
11523
12454
|
}
|
|
11524
12455
|
};
|
|
11525
|
-
var LspFindRefsInput =
|
|
11526
|
-
file:
|
|
11527
|
-
line:
|
|
11528
|
-
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()
|
|
11529
12460
|
});
|
|
11530
|
-
var LspHoverInput =
|
|
11531
|
-
file:
|
|
11532
|
-
line:
|
|
11533
|
-
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()
|
|
11534
12465
|
});
|
|
11535
|
-
var LspDiagnosticsInput =
|
|
11536
|
-
file:
|
|
12466
|
+
var LspDiagnosticsInput = z21.object({
|
|
12467
|
+
file: z21.string().min(1)
|
|
11537
12468
|
});
|
|
11538
12469
|
function createLspTools(workspace, options = {}) {
|
|
11539
|
-
const root =
|
|
12470
|
+
const root = resolve18(workspace);
|
|
11540
12471
|
const manager = options.manager ?? new RealLspManager({ workspace: root, onStatus: options.onStatus });
|
|
11541
12472
|
const guard = (input) => {
|
|
11542
|
-
const candidate =
|
|
12473
|
+
const candidate = resolve18(root, input);
|
|
11543
12474
|
if (!isWithin6(root, candidate)) throw new Error("Path is outside the workspace");
|
|
11544
12475
|
return candidate;
|
|
11545
12476
|
};
|
|
@@ -11615,8 +12546,8 @@ function languageIdForPath(filePath) {
|
|
|
11615
12546
|
}
|
|
11616
12547
|
}
|
|
11617
12548
|
function isWithin6(root, candidate) {
|
|
11618
|
-
const delta =
|
|
11619
|
-
return delta === "" || !delta.startsWith(`..${
|
|
12549
|
+
const delta = relative16(root, candidate);
|
|
12550
|
+
return delta === "" || !delta.startsWith(`..${sep15}`) && delta !== ".." && !isAbsolute10(delta);
|
|
11620
12551
|
}
|
|
11621
12552
|
function uriToPath(uri) {
|
|
11622
12553
|
try {
|
|
@@ -11648,18 +12579,18 @@ function severityLabel(severity) {
|
|
|
11648
12579
|
}
|
|
11649
12580
|
|
|
11650
12581
|
// src/tools/ask-user-question.ts
|
|
11651
|
-
import { z as
|
|
11652
|
-
var QuestionOptionSchema =
|
|
11653
|
-
label:
|
|
11654
|
-
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)
|
|
11655
12586
|
});
|
|
11656
|
-
var QuestionSchema =
|
|
11657
|
-
question:
|
|
11658
|
-
header:
|
|
11659
|
-
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)
|
|
11660
12591
|
});
|
|
11661
|
-
var AskUserQuestionInput =
|
|
11662
|
-
questions:
|
|
12592
|
+
var AskUserQuestionInput = z22.object({
|
|
12593
|
+
questions: z22.array(QuestionSchema).min(1).max(4)
|
|
11663
12594
|
});
|
|
11664
12595
|
function hookAnswersFromUpdatedInput(updatedInput, questions) {
|
|
11665
12596
|
if (typeof updatedInput !== "object" || updatedInput === null) return void 0;
|
|
@@ -11688,7 +12619,7 @@ var QuestionBridge = class {
|
|
|
11688
12619
|
ask(questions, signal) {
|
|
11689
12620
|
if (this.#pending !== void 0) return Promise.reject(new Error("A question is already pending"));
|
|
11690
12621
|
if (signal.aborted) return Promise.reject(signal.reason);
|
|
11691
|
-
return new Promise((
|
|
12622
|
+
return new Promise((resolve25, reject) => {
|
|
11692
12623
|
const onAbort = () => {
|
|
11693
12624
|
this.#pending = void 0;
|
|
11694
12625
|
reject(signal.reason instanceof Error ? signal.reason : new Error(String(signal.reason)));
|
|
@@ -11700,7 +12631,7 @@ var QuestionBridge = class {
|
|
|
11700
12631
|
signal.removeEventListener("abort", onAbort);
|
|
11701
12632
|
this.#pending = void 0;
|
|
11702
12633
|
this.#onChange?.();
|
|
11703
|
-
|
|
12634
|
+
resolve25(answers);
|
|
11704
12635
|
},
|
|
11705
12636
|
reject: (error) => {
|
|
11706
12637
|
signal.removeEventListener("abort", onAbort);
|
|
@@ -11743,20 +12674,20 @@ function createAskUserQuestionTool(handler) {
|
|
|
11743
12674
|
}
|
|
11744
12675
|
|
|
11745
12676
|
// src/tools/todo-write.ts
|
|
11746
|
-
import { z as
|
|
11747
|
-
var TodoStatusSchema =
|
|
12677
|
+
import { z as z23 } from "zod";
|
|
12678
|
+
var TodoStatusSchema = z23.enum([
|
|
11748
12679
|
"pending",
|
|
11749
12680
|
"in_progress",
|
|
11750
12681
|
"completed",
|
|
11751
12682
|
"cancelled"
|
|
11752
12683
|
]);
|
|
11753
|
-
var TodoItemSchema =
|
|
11754
|
-
content:
|
|
12684
|
+
var TodoItemSchema = z23.object({
|
|
12685
|
+
content: z23.string().trim().min(1),
|
|
11755
12686
|
status: TodoStatusSchema,
|
|
11756
|
-
activeForm:
|
|
12687
|
+
activeForm: z23.string().trim().min(1)
|
|
11757
12688
|
});
|
|
11758
|
-
var TodoWriteInput =
|
|
11759
|
-
todos:
|
|
12689
|
+
var TodoWriteInput = z23.object({
|
|
12690
|
+
todos: z23.array(TodoItemSchema).min(1).max(50)
|
|
11760
12691
|
});
|
|
11761
12692
|
function createTodoWriteTool() {
|
|
11762
12693
|
return {
|
|
@@ -11791,38 +12722,38 @@ function createTodoWriteTool() {
|
|
|
11791
12722
|
|
|
11792
12723
|
// src/tools/managed.ts
|
|
11793
12724
|
import { randomUUID as randomUUID9 } from "crypto";
|
|
11794
|
-
import { link, lstat as
|
|
11795
|
-
import { dirname as
|
|
11796
|
-
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";
|
|
11797
12728
|
var MANAGED_TOOL_VERSION = 1;
|
|
11798
12729
|
var MAX_RECORD_BYTES = 256 * 1024;
|
|
11799
12730
|
var TOOL_NAME = /^[A-Za-z][A-Za-z0-9_]{0,63}$/;
|
|
11800
|
-
var JsonSchemaObject =
|
|
12731
|
+
var JsonSchemaObject = z24.record(z24.string(), z24.unknown()).superRefine((schema, context) => {
|
|
11801
12732
|
if (schema.type !== "object") {
|
|
11802
12733
|
context.addIssue({ code: "custom", message: "Tool input JSON Schema must have type object" });
|
|
11803
12734
|
}
|
|
11804
12735
|
});
|
|
11805
|
-
var RegisterManagedToolInputSchema =
|
|
11806
|
-
name:
|
|
11807
|
-
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"),
|
|
11808
12739
|
inputSchema: JsonSchemaObject.describe("Provider-facing JSON Schema with an object at the root"),
|
|
11809
|
-
implementation:
|
|
12740
|
+
implementation: z24.string().min(1).max(64 * 1024).describe(
|
|
11810
12741
|
"Async JavaScript function body, or complete function/arrow expression, receiving input, signal, and context; return the tool result"
|
|
11811
12742
|
),
|
|
11812
|
-
scope:
|
|
12743
|
+
scope: z24.enum(["project", "global"]).default("project").describe(
|
|
11813
12744
|
"Project persists in the current workspace; global persists for every workspace"
|
|
11814
12745
|
),
|
|
11815
|
-
agents:
|
|
12746
|
+
agents: z24.array(z24.enum(["main", "subagent"])).min(1).max(2).optional().describe(
|
|
11816
12747
|
"Agent roles allowed to call the generated tool; omit to allow both"
|
|
11817
12748
|
)
|
|
11818
12749
|
}).strict();
|
|
11819
|
-
var RemoveManagedToolInputSchema =
|
|
11820
|
-
name:
|
|
11821
|
-
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")
|
|
11822
12753
|
}).strict();
|
|
11823
12754
|
var ManagedToolRecordSchema = RegisterManagedToolInputSchema.extend({
|
|
11824
|
-
version:
|
|
11825
|
-
createdAt:
|
|
12755
|
+
version: z24.literal(MANAGED_TOOL_VERSION),
|
|
12756
|
+
createdAt: z24.string().datetime()
|
|
11826
12757
|
}).strict();
|
|
11827
12758
|
var AsyncFunction = Object.getPrototypeOf(async function() {
|
|
11828
12759
|
}).constructor;
|
|
@@ -11834,9 +12765,9 @@ var ManagedToolStore = class {
|
|
|
11834
12765
|
#entries = /* @__PURE__ */ new Map();
|
|
11835
12766
|
#diagnostics = [];
|
|
11836
12767
|
constructor(options) {
|
|
11837
|
-
this.#workspace =
|
|
12768
|
+
this.#workspace = resolve19(options.workspace);
|
|
11838
12769
|
this.#projectRoot = join14(this.#workspace, ".flavor", "tools");
|
|
11839
|
-
this.#globalRoot = join14(
|
|
12770
|
+
this.#globalRoot = join14(resolve19(options.home), ".flavor-code", "tools");
|
|
11840
12771
|
this.#now = options.now ?? (() => (/* @__PURE__ */ new Date()).toISOString());
|
|
11841
12772
|
}
|
|
11842
12773
|
get diagnostics() {
|
|
@@ -11897,7 +12828,7 @@ var ManagedToolStore = class {
|
|
|
11897
12828
|
if (matches.length === 0) throw new Error(`Managed tool "${input.name}" was not found`);
|
|
11898
12829
|
if (matches.length > 1) throw new Error(`Managed tool "${input.name}" exists in multiple scopes; specify scope`);
|
|
11899
12830
|
const entry2 = matches[0];
|
|
11900
|
-
const info = await
|
|
12831
|
+
const info = await lstat10(entry2.path);
|
|
11901
12832
|
if (!info.isFile() || info.isSymbolicLink()) throw new Error(`Managed tool path is no longer a regular file: ${entry2.path}`);
|
|
11902
12833
|
await unlink(entry2.path);
|
|
11903
12834
|
this.#entries.delete(scopedKey(entry2.scope, entry2.record.name));
|
|
@@ -11923,10 +12854,10 @@ var ManagedToolStore = class {
|
|
|
11923
12854
|
for (const name of names) {
|
|
11924
12855
|
const path = join14(root, name);
|
|
11925
12856
|
try {
|
|
11926
|
-
const info = await
|
|
12857
|
+
const info = await lstat10(path);
|
|
11927
12858
|
if (!info.isFile() || info.isSymbolicLink()) throw new Error("Managed tool record must be a regular file");
|
|
11928
12859
|
if (info.size > MAX_RECORD_BYTES) throw new Error(`Managed tool record exceeds ${MAX_RECORD_BYTES} bytes`);
|
|
11929
|
-
const record = ManagedToolRecordSchema.parse(JSON.parse(await
|
|
12860
|
+
const record = ManagedToolRecordSchema.parse(JSON.parse(await readFile14(path, "utf8")));
|
|
11930
12861
|
if (record.scope !== scope2) throw new Error(`Managed tool record scope must match its ${scope2} directory`);
|
|
11931
12862
|
const key = scopedKey(scope2, record.name);
|
|
11932
12863
|
if (this.#entries.has(key)) throw new Error(`Duplicate managed tool name in ${scope2} scope: ${record.name}`);
|
|
@@ -12009,7 +12940,7 @@ function createManagedToolManagementTools(options) {
|
|
|
12009
12940
|
const listTools = {
|
|
12010
12941
|
name: "ListRegisteredTools",
|
|
12011
12942
|
description: "List durable tools created through RegisterTool, including scope, path, and active precedence",
|
|
12012
|
-
inputSchema:
|
|
12943
|
+
inputSchema: z24.object({}).strict(),
|
|
12013
12944
|
agents: mainOnly,
|
|
12014
12945
|
paths: () => [],
|
|
12015
12946
|
execute: async (_input, signal) => {
|
|
@@ -12022,7 +12953,7 @@ function createManagedToolManagementTools(options) {
|
|
|
12022
12953
|
function compileDefinition(record, scope2, workspace) {
|
|
12023
12954
|
let inputSchema;
|
|
12024
12955
|
try {
|
|
12025
|
-
inputSchema =
|
|
12956
|
+
inputSchema = z24.fromJSONSchema(record.inputSchema);
|
|
12026
12957
|
} catch (error) {
|
|
12027
12958
|
throw new Error(`Invalid input schema for managed tool "${record.name}": ${message(error)}`);
|
|
12028
12959
|
}
|
|
@@ -12076,7 +13007,7 @@ ${sourceUrl}`
|
|
|
12076
13007
|
}
|
|
12077
13008
|
}
|
|
12078
13009
|
async function writeExclusive(path, content) {
|
|
12079
|
-
await mkdir12(
|
|
13010
|
+
await mkdir12(dirname14(path), { recursive: true });
|
|
12080
13011
|
const temporary = `${path}.${randomUUID9()}.tmp`;
|
|
12081
13012
|
try {
|
|
12082
13013
|
await writeFile7(temporary, content, { encoding: "utf8", flag: "wx", mode: 384 });
|
|
@@ -12122,7 +13053,7 @@ function isMissing4(error) {
|
|
|
12122
13053
|
}
|
|
12123
13054
|
|
|
12124
13055
|
// src/hallucination/confidence.ts
|
|
12125
|
-
import { z as
|
|
13056
|
+
import { z as z25 } from "zod";
|
|
12126
13057
|
|
|
12127
13058
|
// src/hallucination/types.ts
|
|
12128
13059
|
var DEFAULT_SLIDING_WINDOW_SIZE = 20;
|
|
@@ -12135,12 +13066,12 @@ var DEFAULT_EVALUATION_TIMEOUT_MS = 2e3;
|
|
|
12135
13066
|
var MAX_QUERY_CHARS = 5e3;
|
|
12136
13067
|
var MAX_OUTPUT_CHARS = 1e4;
|
|
12137
13068
|
var MAX_EVIDENCE_CHARS = 6e3;
|
|
12138
|
-
var ConfidenceSchema =
|
|
12139
|
-
taskAlignment:
|
|
12140
|
-
evidenceGrounding:
|
|
12141
|
-
processReliability:
|
|
12142
|
-
reason:
|
|
12143
|
-
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)
|
|
12144
13075
|
});
|
|
12145
13076
|
var PROMPT = `You are a fast, evidence-aware task-completion verifier.
|
|
12146
13077
|
|
|
@@ -12467,7 +13398,7 @@ function buildWarningMessages(report, languageTag) {
|
|
|
12467
13398
|
}
|
|
12468
13399
|
|
|
12469
13400
|
// src/hallucination/sliding-window.ts
|
|
12470
|
-
import { createHash as
|
|
13401
|
+
import { createHash as createHash7 } from "crypto";
|
|
12471
13402
|
var SlidingWindow = class {
|
|
12472
13403
|
#windowSize;
|
|
12473
13404
|
#threshold;
|
|
@@ -12483,7 +13414,7 @@ var SlidingWindow = class {
|
|
|
12483
13414
|
}
|
|
12484
13415
|
hash(toolName, params) {
|
|
12485
13416
|
const sorted = JSON.stringify(params, sortedKeys(params));
|
|
12486
|
-
return
|
|
13417
|
+
return createHash7("sha256").update(`${toolName}:${sorted}`).digest("hex");
|
|
12487
13418
|
}
|
|
12488
13419
|
push(toolName, params) {
|
|
12489
13420
|
const hash = this.hash(toolName, params);
|
|
@@ -12838,9 +13769,9 @@ ${total}`;
|
|
|
12838
13769
|
}
|
|
12839
13770
|
|
|
12840
13771
|
// src/memory/store.ts
|
|
12841
|
-
import { createHash as
|
|
12842
|
-
import { readFile as
|
|
12843
|
-
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";
|
|
12844
13775
|
|
|
12845
13776
|
// src/memory/similarity.ts
|
|
12846
13777
|
function normalizeForSimilarity(value) {
|
|
@@ -12942,7 +13873,7 @@ var MemoryStore = class {
|
|
|
12942
13873
|
constructor(options) {
|
|
12943
13874
|
if (!Number.isSafeInteger(options.maxEntries) || options.maxEntries < 1) throw new Error("maxEntries must be an integer of at least 1");
|
|
12944
13875
|
if (!Number.isSafeInteger(options.maxEntryChars) || options.maxEntryChars < 1) throw new Error("maxEntryChars must be an integer of at least 1");
|
|
12945
|
-
this.workspace =
|
|
13876
|
+
this.workspace = resolve20(options.workspace);
|
|
12946
13877
|
this.#memoryRoot = join15(this.workspace, ".flavor", "memory");
|
|
12947
13878
|
this.path = join15(this.#memoryRoot, "MEMORY.md");
|
|
12948
13879
|
this.#behaviorPath = join15(this.#memoryRoot, "behavior.json");
|
|
@@ -13163,14 +14094,14 @@ var MemoryStore = class {
|
|
|
13163
14094
|
});
|
|
13164
14095
|
}
|
|
13165
14096
|
async #readTaskItem(reference) {
|
|
13166
|
-
const document = decodeTask(await
|
|
14097
|
+
const document = decodeTask(await readFile15(this.#resolveContentPath(reference.contentPath), "utf8"));
|
|
13167
14098
|
return document.items.find((item) => item.id === reference.id)?.content ?? reference.summary;
|
|
13168
14099
|
}
|
|
13169
14100
|
#resolveContentPath(contentPath) {
|
|
13170
14101
|
if (!/^tasks\/[A-Za-z0-9][A-Za-z0-9._-]{0,127}\.md$/.test(contentPath)) throw new Error("Invalid memory content path");
|
|
13171
|
-
const path =
|
|
13172
|
-
const rel =
|
|
13173
|
-
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");
|
|
13174
14105
|
return path;
|
|
13175
14106
|
}
|
|
13176
14107
|
async #updateIndex(update) {
|
|
@@ -13324,7 +14255,7 @@ function sanitizeTopicKey(value) {
|
|
|
13324
14255
|
return value.normalize("NFKC").toLocaleLowerCase().replace(/[^a-z0-9._-]+/g, "-").replace(/^-+|-+$/g, "").slice(0, 128);
|
|
13325
14256
|
}
|
|
13326
14257
|
function memoryId(type, content) {
|
|
13327
|
-
return
|
|
14258
|
+
return createHash8("sha256").update(`${type}\0${normalizeMemoryContent(content).toLocaleLowerCase()}`, "utf8").digest("hex").slice(0, 12);
|
|
13328
14259
|
}
|
|
13329
14260
|
function assertTaskId(taskId) {
|
|
13330
14261
|
if (!TASK_ID.test(taskId)) throw new Error(`Invalid memory task id: ${taskId}`);
|
|
@@ -13639,8 +14570,8 @@ function createExecutionEnvironment(workspace, config) {
|
|
|
13639
14570
|
}
|
|
13640
14571
|
|
|
13641
14572
|
// src/ide/client.ts
|
|
13642
|
-
import { readFile as
|
|
13643
|
-
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";
|
|
13644
14575
|
var FlavorIdeClient = class {
|
|
13645
14576
|
#workspace;
|
|
13646
14577
|
#home;
|
|
@@ -13651,8 +14582,8 @@ var FlavorIdeClient = class {
|
|
|
13651
14582
|
#sessionId;
|
|
13652
14583
|
#eventTail = Promise.resolve();
|
|
13653
14584
|
constructor(options) {
|
|
13654
|
-
this.#workspace =
|
|
13655
|
-
this.#home =
|
|
14585
|
+
this.#workspace = resolve21(options.workspace);
|
|
14586
|
+
this.#home = resolve21(options.home);
|
|
13656
14587
|
this.#environment = options.environment ?? process.env;
|
|
13657
14588
|
this.#fetch = options.fetch ?? fetch;
|
|
13658
14589
|
}
|
|
@@ -13746,7 +14677,7 @@ var FlavorIdeClient = class {
|
|
|
13746
14677
|
const candidates = (await Promise.all(entries.map(async (name) => {
|
|
13747
14678
|
const path = join16(directory, name);
|
|
13748
14679
|
try {
|
|
13749
|
-
const [contents, info] = await Promise.all([
|
|
14680
|
+
const [contents, info] = await Promise.all([readFile16(path, "utf8"), stat6(path)]);
|
|
13750
14681
|
const lock = parseLockfile(contents);
|
|
13751
14682
|
return lock === void 0 ? void 0 : { lock, modified: info.mtimeMs };
|
|
13752
14683
|
} catch {
|
|
@@ -13850,19 +14781,385 @@ function parsePort(value) {
|
|
|
13850
14781
|
return Number.isInteger(port) && port > 0 && port <= 65535 ? port : void 0;
|
|
13851
14782
|
}
|
|
13852
14783
|
function isWithin7(parent, child) {
|
|
13853
|
-
const base = normalize(
|
|
13854
|
-
const target = normalize(
|
|
13855
|
-
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}`);
|
|
13856
14787
|
}
|
|
13857
14788
|
function normalize(path) {
|
|
13858
14789
|
const normalized = path.normalize("NFC");
|
|
13859
14790
|
return process.platform === "win32" ? normalized.toLowerCase() : normalized;
|
|
13860
14791
|
}
|
|
13861
14792
|
function relativeDisplay(workspace, filePath) {
|
|
13862
|
-
const path =
|
|
14793
|
+
const path = relative18(workspace, filePath).replaceAll("\\", "/");
|
|
13863
14794
|
return path && !path.startsWith("../") ? path : basename5(filePath);
|
|
13864
14795
|
}
|
|
13865
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
|
+
|
|
13866
15163
|
// src/production.ts
|
|
13867
15164
|
var INTERRUPTED_TASK_PLAN_CONTEXT = [
|
|
13868
15165
|
"The previous turn's task plan was cancelled and archived, so it is no longer active.",
|
|
@@ -13893,7 +15190,7 @@ var ApprovalBridge = class {
|
|
|
13893
15190
|
request(request, signal = new AbortController().signal) {
|
|
13894
15191
|
if (this.#settle !== void 0) return Promise.resolve("deny");
|
|
13895
15192
|
if (signal.aborted) return Promise.resolve("deny");
|
|
13896
|
-
this.#pending = { id:
|
|
15193
|
+
this.#pending = { id: randomUUID12(), ...request };
|
|
13897
15194
|
this.#onChange?.();
|
|
13898
15195
|
return new Promise((resolvePromise) => {
|
|
13899
15196
|
this.#settle = resolvePromise;
|
|
@@ -13914,8 +15211,8 @@ var ApprovalBridge = class {
|
|
|
13914
15211
|
}
|
|
13915
15212
|
};
|
|
13916
15213
|
async function createProductionRuntime(options) {
|
|
13917
|
-
const workspace =
|
|
13918
|
-
const home =
|
|
15214
|
+
const workspace = resolve24(options.workspace ?? process.cwd());
|
|
15215
|
+
const home = resolve24(options.home ?? homedir());
|
|
13919
15216
|
const environment = options.environment ?? process.env;
|
|
13920
15217
|
const ide = new FlavorIdeClient({ workspace, home, environment });
|
|
13921
15218
|
const ideReady = ide.initialize();
|
|
@@ -14048,17 +15345,29 @@ async function createProductionRuntime(options) {
|
|
|
14048
15345
|
return questions.ask(qs, signal);
|
|
14049
15346
|
};
|
|
14050
15347
|
const executionEnvironment = createExecutionEnvironment(workspace, config.execution);
|
|
14051
|
-
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
|
+
};
|
|
14052
15356
|
const tools = [
|
|
14053
|
-
createReadTool(workspace),
|
|
15357
|
+
createReadTool(workspace, { observations }),
|
|
14054
15358
|
createWriteTool(workspace, fileMutationOptions),
|
|
14055
15359
|
createEditTool(workspace, fileMutationOptions),
|
|
14056
15360
|
createApplyPatchTool(workspace, fileMutationOptions),
|
|
14057
15361
|
createGlobTool(workspace),
|
|
14058
15362
|
createGrepTool(workspace),
|
|
14059
15363
|
createShellTool(workspace, {
|
|
15364
|
+
jobs,
|
|
14060
15365
|
...executionEnvironment === void 0 ? {} : { executionEnvironment }
|
|
14061
15366
|
}),
|
|
15367
|
+
createWebFetchTool(),
|
|
15368
|
+
createWebSearchTool(),
|
|
15369
|
+
...createJobTools(jobs),
|
|
15370
|
+
...createTerminalTools(terminals, workspace),
|
|
14062
15371
|
...createLspTools(workspace, {
|
|
14063
15372
|
onStatus: (message2) => emitOutput({ type: "notice", message: message2 })
|
|
14064
15373
|
}),
|
|
@@ -14190,7 +15499,9 @@ async function createProductionRuntime(options) {
|
|
|
14190
15499
|
const skillsReady = skills.discover();
|
|
14191
15500
|
skillsReady.catch(() => void 0);
|
|
14192
15501
|
tools.push(createSkillResourceTool(skills));
|
|
15502
|
+
if (options.extraTools !== void 0) tools.push(...options.extraTools);
|
|
14193
15503
|
const flavor = await optionalText2(join17(workspace, "FLAVOR.md"));
|
|
15504
|
+
const instructionBaseline = await workspaceInstructions.baseline();
|
|
14194
15505
|
let memoryContext;
|
|
14195
15506
|
if (memoryStore !== void 0) {
|
|
14196
15507
|
try {
|
|
@@ -14213,7 +15524,7 @@ async function createProductionRuntime(options) {
|
|
|
14213
15524
|
let taskResults = { ...recovered?.tasks.results ?? {} };
|
|
14214
15525
|
const subagentStartedAt = {};
|
|
14215
15526
|
const subagentElapsedMs = {};
|
|
14216
|
-
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)}`;
|
|
14217
15528
|
setUsageSession(sessionId);
|
|
14218
15529
|
ideSessionId = sessionId;
|
|
14219
15530
|
await ideReady;
|
|
@@ -14383,6 +15694,7 @@ async function createProductionRuntime(options) {
|
|
|
14383
15694
|
})
|
|
14384
15695
|
],
|
|
14385
15696
|
...flavor === void 0 ? {} : { flavor },
|
|
15697
|
+
...instructionBaseline === "" ? {} : { workspaceInstructions: instructionBaseline },
|
|
14386
15698
|
...memoryContext === void 0 ? {} : { memory: memoryContext },
|
|
14387
15699
|
...taskState === void 0 ? {} : { taskState },
|
|
14388
15700
|
userMemory: () => userMemoryContext ?? "",
|
|
@@ -14420,6 +15732,7 @@ async function createProductionRuntime(options) {
|
|
|
14420
15732
|
maxIterationsMain: config.maxIterations.main,
|
|
14421
15733
|
maxIterationsSubagent: config.maxIterations.subagent,
|
|
14422
15734
|
hasActiveProgress,
|
|
15735
|
+
afterToolSuccess: async (_tool, paths, _input, _output, context) => workspaceInstructions.discover(paths, context.ownerId ?? context.agent),
|
|
14423
15736
|
approve: resolveToolApproval
|
|
14424
15737
|
});
|
|
14425
15738
|
harnessCreated = true;
|
|
@@ -14520,7 +15833,7 @@ async function createProductionRuntime(options) {
|
|
|
14520
15833
|
const prompt = String(event.payload.prompt);
|
|
14521
15834
|
const reassessInterruptedPlan = interruptedTaskPlanNeedsReassessment && !prompt.startsWith("/");
|
|
14522
15835
|
timelineState = transcriptReducer(timelineState, { type: "submit", prompt });
|
|
14523
|
-
automaticMemoryTask = !prompt.startsWith("/");
|
|
15836
|
+
automaticMemoryTask = !prompt.startsWith("/") && harness.permissionProfile !== "d2c";
|
|
14524
15837
|
if (automaticMemoryTask) memoryReviews.dismissAll();
|
|
14525
15838
|
if (!prompt.startsWith("/") && memoryLifecycle.status === "completed") {
|
|
14526
15839
|
memoryLifecycle = {
|
|
@@ -14645,16 +15958,23 @@ async function createProductionRuntime(options) {
|
|
|
14645
15958
|
return;
|
|
14646
15959
|
}
|
|
14647
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 };
|
|
14648
15964
|
const loopTools = [
|
|
14649
|
-
createReadTool(input.workspace),
|
|
14650
|
-
createWriteTool(input.workspace),
|
|
14651
|
-
createEditTool(input.workspace),
|
|
14652
|
-
createApplyPatchTool(input.workspace),
|
|
15965
|
+
createReadTool(input.workspace, { observations: loopObservations }),
|
|
15966
|
+
createWriteTool(input.workspace, loopMutationOptions),
|
|
15967
|
+
createEditTool(input.workspace, loopMutationOptions),
|
|
15968
|
+
createApplyPatchTool(input.workspace, loopMutationOptions),
|
|
14653
15969
|
createGlobTool(input.workspace),
|
|
14654
15970
|
createGrepTool(input.workspace),
|
|
14655
15971
|
createShellTool(input.workspace, {
|
|
15972
|
+
jobs,
|
|
14656
15973
|
...loopExecutionEnvironment === void 0 ? {} : { executionEnvironment: loopExecutionEnvironment }
|
|
14657
15974
|
}),
|
|
15975
|
+
createWebFetchTool(),
|
|
15976
|
+
createWebSearchTool(),
|
|
15977
|
+
...createJobTools(jobs),
|
|
14658
15978
|
...createLspTools(input.workspace, {
|
|
14659
15979
|
onStatus: (status) => emitOutput({ type: "notice", message: status })
|
|
14660
15980
|
}),
|
|
@@ -14662,6 +15982,7 @@ async function createProductionRuntime(options) {
|
|
|
14662
15982
|
...mcpTools
|
|
14663
15983
|
];
|
|
14664
15984
|
const loopFlavor = await optionalText2(join17(input.workspace, "FLAVOR.md"));
|
|
15985
|
+
const loopInstructionBaseline = await loopInstructions.baseline();
|
|
14665
15986
|
const loopEnvironment = createPromptEnvironment({
|
|
14666
15987
|
now: /* @__PURE__ */ new Date(),
|
|
14667
15988
|
platform: process.platform,
|
|
@@ -14691,6 +16012,7 @@ async function createProductionRuntime(options) {
|
|
|
14691
16012
|
})
|
|
14692
16013
|
],
|
|
14693
16014
|
...loopFlavor === void 0 ? {} : { flavor: loopFlavor },
|
|
16015
|
+
...loopInstructionBaseline === "" ? {} : { workspaceInstructions: loopInstructionBaseline },
|
|
14694
16016
|
...memoryContext === void 0 ? {} : { memory: memoryContext },
|
|
14695
16017
|
userMemory: () => userMemoryContext ?? "",
|
|
14696
16018
|
...compactAtChars === void 0 ? {} : { compactAtChars },
|
|
@@ -14723,6 +16045,7 @@ async function createProductionRuntime(options) {
|
|
|
14723
16045
|
maxIterationsMain: config.maxIterations.main,
|
|
14724
16046
|
maxIterationsSubagent: config.maxIterations.subagent,
|
|
14725
16047
|
loopMode: true,
|
|
16048
|
+
afterToolSuccess: async (_tool, paths, _input, _output, context) => loopInstructions.discover(paths, context.ownerId ?? context.agent),
|
|
14726
16049
|
approve: resolveToolApproval
|
|
14727
16050
|
});
|
|
14728
16051
|
try {
|
|
@@ -14774,7 +16097,7 @@ async function createProductionRuntime(options) {
|
|
|
14774
16097
|
return answers[0] === "Continue" ? "approved" : "rejected";
|
|
14775
16098
|
},
|
|
14776
16099
|
fingerprint: workspaceFingerprint,
|
|
14777
|
-
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)}`
|
|
14778
16101
|
});
|
|
14779
16102
|
const goalStore = new GoalStore({ workspace });
|
|
14780
16103
|
const goalOrchestrator = new GoalOrchestrator({
|
|
@@ -14787,13 +16110,13 @@ async function createProductionRuntime(options) {
|
|
|
14787
16110
|
maxStallStreak: 2,
|
|
14788
16111
|
persistence: goalStore,
|
|
14789
16112
|
now: () => (/* @__PURE__ */ new Date()).toISOString(),
|
|
14790
|
-
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)}`,
|
|
14791
16114
|
runWorker: ({ workspace: goalWorkspace, prompt, signal }) => runLoopWorker({ workspace: goalWorkspace, prompt, signal })
|
|
14792
16115
|
});
|
|
14793
16116
|
let sessionHistory = await SessionHistory.open({
|
|
14794
16117
|
workspace,
|
|
14795
16118
|
sessionId,
|
|
14796
|
-
restoreContext: (
|
|
16119
|
+
restoreContext: (snapshot4) => harness.main.context.restore(snapshot4)
|
|
14797
16120
|
});
|
|
14798
16121
|
const services = {
|
|
14799
16122
|
hooks,
|
|
@@ -14905,7 +16228,7 @@ async function createProductionRuntime(options) {
|
|
|
14905
16228
|
tasks: () => ({ plan: taskPlan, graph: taskGraph, states: taskStates, results: taskResults }),
|
|
14906
16229
|
audit: async (toolFilter) => {
|
|
14907
16230
|
try {
|
|
14908
|
-
const raw = await
|
|
16231
|
+
const raw = await readFile17(auditLogger.path, "utf8");
|
|
14909
16232
|
const lines = raw.trim().split("\n").filter((line) => line.length > 0);
|
|
14910
16233
|
const entries = lines.map((line) => {
|
|
14911
16234
|
try {
|
|
@@ -14944,7 +16267,7 @@ ${summary2}`;
|
|
|
14944
16267
|
},
|
|
14945
16268
|
usage: async () => {
|
|
14946
16269
|
try {
|
|
14947
|
-
const raw = await
|
|
16270
|
+
const raw = await readFile17(usageLogPath(), "utf8");
|
|
14948
16271
|
return formatUsageSummary(summarizeUsage(parseUsageEntries(raw)));
|
|
14949
16272
|
} catch (error) {
|
|
14950
16273
|
if (typeof error === "object" && error !== null && "code" in error && error.code === "ENOENT") {
|
|
@@ -14972,7 +16295,7 @@ ${summary2}`;
|
|
|
14972
16295
|
for (const key of Object.keys(subagentStartedAt)) delete subagentStartedAt[key];
|
|
14973
16296
|
for (const key of Object.keys(subagentElapsedMs)) delete subagentElapsedMs[key];
|
|
14974
16297
|
const previousIdeSessionId = ideSessionId;
|
|
14975
|
-
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)}`;
|
|
14976
16299
|
setUsageSession(sessionId);
|
|
14977
16300
|
ideSessionId = sessionId;
|
|
14978
16301
|
if (previousIdeSessionId !== void 0) await ide.endSession(previousIdeSessionId);
|
|
@@ -14981,7 +16304,7 @@ ${summary2}`;
|
|
|
14981
16304
|
sessionHistory = await SessionHistory.open({
|
|
14982
16305
|
workspace,
|
|
14983
16306
|
sessionId,
|
|
14984
|
-
restoreContext: (
|
|
16307
|
+
restoreContext: (snapshot4) => harness.main.context.restore(snapshot4)
|
|
14985
16308
|
});
|
|
14986
16309
|
memoryLifecycle = { status: "active", taskId: createMemoryTaskId(), messageStart: 0 };
|
|
14987
16310
|
timelineState = createTranscriptState();
|
|
@@ -15087,7 +16410,10 @@ ${renderMemoryDocument(entries)}`;
|
|
|
15087
16410
|
baseURL: next.baseURL,
|
|
15088
16411
|
...next.maxOutputTokens === void 0 ? {} : { maxOutputTokens: next.maxOutputTokens }
|
|
15089
16412
|
};
|
|
15090
|
-
registry.register(next.providerId, next.apiType === "anthropic" ? new AnthropicModelAdapter(
|
|
16413
|
+
registry.register(next.providerId, next.apiType === "anthropic" ? new AnthropicModelAdapter({
|
|
16414
|
+
...adapterOptions,
|
|
16415
|
+
...providerConfig?.claudeClient === true ? { headers: CLAUDE_CLIENT_HEADERS } : {}
|
|
16416
|
+
}) : new OpenAIModelAdapter(adapterOptions));
|
|
15091
16417
|
effectiveLlm = next;
|
|
15092
16418
|
mainModel = `${next.providerId}:${next.defaultModel}`;
|
|
15093
16419
|
childModel = `${next.providerId}:${next.cheapModel}`;
|
|
@@ -15127,13 +16453,19 @@ ${renderMemoryDocument(entries)}`;
|
|
|
15127
16453
|
sleepScheduler.start();
|
|
15128
16454
|
}
|
|
15129
16455
|
const session = new FlavorSession(services);
|
|
16456
|
+
const authorization = {
|
|
16457
|
+
permissionProfile: () => harness.permissionProfile,
|
|
16458
|
+
setPermissionProfile: (profile) => harness.setPermissionProfile(profile)
|
|
16459
|
+
};
|
|
15130
16460
|
let disposed = false;
|
|
15131
16461
|
return {
|
|
15132
16462
|
session,
|
|
15133
16463
|
services,
|
|
16464
|
+
authorization,
|
|
15134
16465
|
approvals,
|
|
15135
16466
|
memoryReviews,
|
|
15136
16467
|
restoredTranscript,
|
|
16468
|
+
jobs: { list: () => jobs.list(), subscribe: (listener) => jobs.subscribe(listener) },
|
|
15137
16469
|
get sessionId() {
|
|
15138
16470
|
return sessionId;
|
|
15139
16471
|
},
|
|
@@ -15150,6 +16482,8 @@ ${renderMemoryDocument(entries)}`;
|
|
|
15150
16482
|
await persistTail;
|
|
15151
16483
|
if (ideSessionId !== void 0) await ide.endSession(ideSessionId);
|
|
15152
16484
|
await executionEnvironment?.dispose();
|
|
16485
|
+
terminals.dispose();
|
|
16486
|
+
await jobs.dispose();
|
|
15153
16487
|
auditLogger.close();
|
|
15154
16488
|
memoryReviews.dispose();
|
|
15155
16489
|
await cleanupProduction(approvals, questions, pluginHost, mcpManager, harness);
|
|
@@ -15162,6 +16496,8 @@ ${renderMemoryDocument(entries)}`;
|
|
|
15162
16496
|
if (ideSessionId !== void 0) await ide.endSession(ideSessionId);
|
|
15163
16497
|
await sleepScheduler?.dispose();
|
|
15164
16498
|
await executionEnvironment?.dispose();
|
|
16499
|
+
terminals.dispose();
|
|
16500
|
+
await jobs.dispose();
|
|
15165
16501
|
await cleanupProduction(approvals, questions, pluginHost, mcpManager, harnessCreated ? harness : void 0);
|
|
15166
16502
|
} catch (cleanupError) {
|
|
15167
16503
|
attachCleanupError(primaryError, cleanupError);
|
|
@@ -15276,7 +16612,7 @@ function terminalProgressState(status) {
|
|
|
15276
16612
|
return "failed";
|
|
15277
16613
|
}
|
|
15278
16614
|
async function workspaceFingerprint(workspace) {
|
|
15279
|
-
const hash =
|
|
16615
|
+
const hash = createHash9("sha256");
|
|
15280
16616
|
const diff = await execFileNoThrow(
|
|
15281
16617
|
"git",
|
|
15282
16618
|
["-C", workspace, "diff", "--no-ext-diff", "--binary", "HEAD"],
|
|
@@ -15292,13 +16628,13 @@ async function workspaceFingerprint(workspace) {
|
|
|
15292
16628
|
let remaining = 5 * 1024 * 1024;
|
|
15293
16629
|
for (const name of untracked.stdout.split("\0").filter(Boolean).sort()) {
|
|
15294
16630
|
if (name === ".flavor/loops" || name.startsWith(".flavor/loops/")) continue;
|
|
15295
|
-
const path =
|
|
15296
|
-
const relativePath =
|
|
15297
|
-
if (relativePath === ".." || relativePath.startsWith(`..${
|
|
16631
|
+
const path = resolve24(workspace, name);
|
|
16632
|
+
const relativePath = relative20(resolve24(workspace), path);
|
|
16633
|
+
if (relativePath === ".." || relativePath.startsWith(`..${sep19}`)) continue;
|
|
15298
16634
|
hash.update(name);
|
|
15299
16635
|
if (remaining <= 0) continue;
|
|
15300
16636
|
try {
|
|
15301
|
-
const content = await
|
|
16637
|
+
const content = await readFile17(path);
|
|
15302
16638
|
const slice = content.subarray(0, remaining);
|
|
15303
16639
|
hash.update(slice);
|
|
15304
16640
|
remaining -= slice.length;
|
|
@@ -15495,7 +16831,10 @@ async function registerConfiguredAdapters(providers, registry, environment, diag
|
|
|
15495
16831
|
...runtimeProvider.baseURL === void 0 ? {} : { baseURL: runtimeProvider.baseURL },
|
|
15496
16832
|
...runtimeProvider.maxOutputTokens === void 0 ? {} : { maxOutputTokens: runtimeProvider.maxOutputTokens }
|
|
15497
16833
|
};
|
|
15498
|
-
const adapter = apiProtocol === "anthropic" ? new AnthropicModelAdapter(
|
|
16834
|
+
const adapter = apiProtocol === "anthropic" ? new AnthropicModelAdapter({
|
|
16835
|
+
...adapterOptions,
|
|
16836
|
+
...runtimeProvider.claudeClient === true ? { headers: CLAUDE_CLIENT_HEADERS } : {}
|
|
16837
|
+
}) : new OpenAIModelAdapter(adapterOptions);
|
|
15499
16838
|
const cacheProfile = resolveCacheProfile({ apiType: apiProtocol, baseURL: runtimeProvider.baseURL });
|
|
15500
16839
|
if (apiProtocol === "openai" && isDashScopeBaseURL(runtimeProvider.baseURL)) {
|
|
15501
16840
|
diagnostics.push(
|
|
@@ -15687,7 +17026,7 @@ async function generateSleepReview(registry, modelId, prompt, signal) {
|
|
|
15687
17026
|
}
|
|
15688
17027
|
async function optionalText2(path) {
|
|
15689
17028
|
try {
|
|
15690
|
-
return await
|
|
17029
|
+
return await readFile17(path, "utf8");
|
|
15691
17030
|
} catch (error) {
|
|
15692
17031
|
if (typeof error === "object" && error !== null && "code" in error && error.code === "ENOENT") return void 0;
|
|
15693
17032
|
throw error;
|
|
@@ -15700,10 +17039,10 @@ function remove(items, item) {
|
|
|
15700
17039
|
function sameToolName(left, right) {
|
|
15701
17040
|
return left.toLowerCase() === right.toLowerCase();
|
|
15702
17041
|
}
|
|
15703
|
-
function storedConversation(
|
|
17042
|
+
function storedConversation(snapshot4) {
|
|
15704
17043
|
return {
|
|
15705
|
-
...
|
|
15706
|
-
messages:
|
|
17044
|
+
...snapshot4.compact === void 0 ? {} : { compact: snapshot4.compact },
|
|
17045
|
+
messages: snapshot4.messages.flatMap((message2) => {
|
|
15707
17046
|
if (message2.role === "system") return [];
|
|
15708
17047
|
const metadata = {
|
|
15709
17048
|
...message2.toolCallId === void 0 ? {} : { toolCallId: message2.toolCallId },
|
|
@@ -15826,10 +17165,10 @@ async function* runGoalSession(orchestrator, goal, signal) {
|
|
|
15826
17165
|
}
|
|
15827
17166
|
function memoryTranscriptHash(messages) {
|
|
15828
17167
|
const visible = messages.filter((item) => item.role === "user" || item.role === "assistant").map((item) => `${item.role}\0${modelContentText(item.content).trim()}`).join("\n");
|
|
15829
|
-
return
|
|
17168
|
+
return createHash9("sha256").update(visible, "utf8").digest("hex");
|
|
15830
17169
|
}
|
|
15831
17170
|
function createMemoryTaskId() {
|
|
15832
|
-
return `memory-${
|
|
17171
|
+
return `memory-${randomUUID12()}`;
|
|
15833
17172
|
}
|
|
15834
17173
|
|
|
15835
17174
|
export {
|