@wrongstack/core 0.260.0 → 0.265.1
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/dist/{agent-bridge-BbskZ7HH.d.ts → agent-bridge-DrkBxszZ.d.ts} +1 -1
- package/dist/{agent-subagent-runner-BNIGZx18.d.ts → agent-subagent-runner-DM2pP-B6.d.ts} +116 -12
- package/dist/{brain-C2yDd7Lw.d.ts → brain-BXd_61kQ.d.ts} +32 -3
- package/dist/{compactor-t0R_AIt_.d.ts → compactor-B8pOf45Y.d.ts} +1 -1
- package/dist/{config-FG6As4H5.d.ts → config-BMCj_XDs.d.ts} +86 -12
- package/dist/{context-JFOVvu6z.d.ts → context-MRk5PhNv.d.ts} +26 -1
- package/dist/coordination/index.d.ts +1737 -15
- package/dist/coordination/index.js +3152 -494
- package/dist/coordination/index.js.map +1 -1
- package/dist/{default-config-CXsDvOmP.d.ts → default-config-B0cj-Hry.d.ts} +11 -1
- package/dist/defaults/index.d.ts +28 -28
- package/dist/defaults/index.js +1804 -1363
- package/dist/defaults/index.js.map +1 -1
- package/dist/dispatcher-types.d-BBeXBQgS.d.ts +66 -0
- package/dist/execution/index.d.ts +16 -16
- package/dist/execution/index.js +933 -672
- package/dist/execution/index.js.map +1 -1
- package/dist/execution/prompt-enhancer.d.ts +1 -1
- package/dist/execution/prompt-enhancer.js +7 -1
- package/dist/execution/prompt-enhancer.js.map +1 -1
- package/dist/extension/index.d.ts +6 -6
- package/dist/extension/index.js.map +1 -1
- package/dist/{goal-preamble-B1IXJtLX.d.ts → goal-preamble-DvHDSKSe.d.ts} +26 -10
- package/dist/{goal-store-CPXz6Mml.d.ts → goal-store-DtLMySNb.d.ts} +1 -1
- package/dist/{index-CebbJB94.d.ts → index-B-ch8K9C.d.ts} +8 -8
- package/dist/{index-BPcg4N3M.d.ts → index-CEDeNodM.d.ts} +5 -5
- package/dist/index.d.ts +189 -104
- package/dist/index.js +24693 -21162
- package/dist/index.js.map +1 -1
- package/dist/infrastructure/index.d.ts +6 -6
- package/dist/infrastructure/index.js +12 -8
- package/dist/infrastructure/index.js.map +1 -1
- package/dist/kernel/index.d.ts +9 -9
- package/dist/kernel/index.js +7 -2
- package/dist/kernel/index.js.map +1 -1
- package/dist/{llm-selector-DXxI2tlu.d.ts → llm-selector-C0tfTCUe.d.ts} +14 -2
- package/dist/{mcp-servers-OwNHo43-.d.ts → mcp-servers-2x4w6Jn9.d.ts} +3 -3
- package/dist/models/index.d.ts +5 -5
- package/dist/models/index.js +80 -31
- package/dist/models/index.js.map +1 -1
- package/dist/{models-registry-Djlmq4uB.d.ts → models-registry-DmJlKuNp.d.ts} +1 -1
- package/dist/{multi-agent-coordinator-CEmrSCMJ.d.ts → multi-agent-coordinator-DyCkCZnU.d.ts} +2 -2
- package/dist/{null-fleet-bus-DT92xqgJ.d.ts → null-fleet-bus-CG9QY2aP.d.ts} +6 -6
- package/dist/observability/index.d.ts +2 -2
- package/dist/observability/index.js +8 -3
- package/dist/observability/index.js.map +1 -1
- package/dist/{parallel-eternal-engine-0SItuq5r.d.ts → parallel-eternal-engine-Jw9uhEoT.d.ts} +9 -9
- package/dist/{path-resolver-DKBh6Jlo.d.ts → path-resolver-Dy2ej-gE.d.ts} +3 -3
- package/dist/{permission-BJ7eO9Vl.d.ts → permission-B9SB45lp.d.ts} +1 -1
- package/dist/{permission-policy-DEXOfnpm.d.ts → permission-policy-CkjSXabK.d.ts} +2 -2
- package/dist/{pipeline-zflkI2dp.d.ts → pipeline-DPDxH_7m.d.ts} +59 -4
- package/dist/{plan-templates-BFXyRkEK.d.ts → plan-templates-CzD9GnAU.d.ts} +32 -8
- package/dist/{provider-runner-BC-uywtT.d.ts → provider-runner-DMa70ODu.d.ts} +3 -3
- package/dist/{retry-policy-Cavrzmtk.d.ts → retry-policy-CN0khdlj.d.ts} +1 -1
- package/dist/sdd/index.d.ts +8 -8
- package/dist/sdd/index.js +313 -122
- package/dist/sdd/index.js.map +1 -1
- package/dist/{secret-vault-CDvDYXWX.d.ts → secret-vault-B2yw84VT.d.ts} +43 -4
- package/dist/secret-vault-BAKpgFw_.d.ts +57 -0
- package/dist/security/index.d.ts +5 -5
- package/dist/security/index.js +411 -225
- package/dist/security/index.js.map +1 -1
- package/dist/{selector-B7AivHsu.d.ts → selector-CzHh_igB.d.ts} +1 -1
- package/dist/{session-event-bridge-BmIDxdJd.d.ts → session-event-bridge-BUI6Jf-4.d.ts} +8 -2
- package/dist/{session-reader-DtofsB-2.d.ts → session-reader-CMgdMSRP.d.ts} +1 -1
- package/dist/skills/index.js +67 -64
- package/dist/skills/index.js.map +1 -1
- package/dist/storage/index.d.ts +132 -16
- package/dist/storage/index.js +851 -432
- package/dist/storage/index.js.map +1 -1
- package/dist/tools/index.d.ts +57 -0
- package/dist/tools/index.js +411 -0
- package/dist/tools/index.js.map +1 -0
- package/dist/types/index.d.ts +21 -21
- package/dist/types/index.js +928 -711
- package/dist/types/index.js.map +1 -1
- package/dist/utils/error.d.ts +7 -0
- package/dist/utils/error.js +8 -0
- package/dist/utils/error.js.map +1 -0
- package/dist/utils/index.d.ts +8 -68
- package/dist/utils/index.js +20 -10
- package/dist/utils/index.js.map +1 -1
- package/dist/{wstack-paths-CJjEwPXn.d.ts → wstack-paths-hOpNLmvf.d.ts} +2 -0
- package/package.json +5 -1
- package/skills/api-design/SKILL.md +1 -1
- package/skills/audit-log/SKILL.md +6 -6
- package/skills/bug-hunter/SKILL.md +5 -5
- package/skills/chimera/SKILL.md +4 -4
- package/skills/docker-deploy/SKILL.md +1 -1
- package/skills/git-flow/SKILL.md +3 -3
- package/skills/multi-agent/SKILL.md +3 -3
- package/skills/node-modern/SKILL.md +1 -0
- package/skills/observability/SKILL.md +2 -2
- package/skills/output-standards/SKILL.md +51 -28
- package/skills/refactor-planner/SKILL.md +3 -3
- package/skills/security-scanner/SKILL.md +4 -3
- package/skills/tech-stack/SKILL.md +1 -2
- package/dist/package-outdated-watcher-C70ag2G9.d.ts +0 -581
- package/dist/secret-vault-BJDY28ev.d.ts +0 -25
package/dist/defaults/index.js
CHANGED
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
import * as crypto2 from 'crypto';
|
|
2
2
|
import { randomBytes, createCipheriv, createDecipheriv, randomUUID, createHash } from 'crypto';
|
|
3
|
-
import * as
|
|
4
|
-
import * as
|
|
3
|
+
import * as fsp2 from 'fs/promises';
|
|
4
|
+
import * as path3 from 'path';
|
|
5
5
|
import { isAbsolute, resolve } from 'path';
|
|
6
|
-
import * as
|
|
6
|
+
import * as fs4 from 'fs';
|
|
7
7
|
import * as os from 'os';
|
|
8
8
|
import { hostname } from 'os';
|
|
9
9
|
import { execFile } from 'child_process';
|
|
@@ -33,17 +33,17 @@ __export(atomic_write_exports, {
|
|
|
33
33
|
withFileLock: () => withFileLock
|
|
34
34
|
});
|
|
35
35
|
async function atomicWrite(targetPath, content, opts = {}) {
|
|
36
|
-
const dir =
|
|
37
|
-
await
|
|
38
|
-
const tmp =
|
|
36
|
+
const dir = path3.dirname(targetPath);
|
|
37
|
+
await fsp2.mkdir(dir, { recursive: true });
|
|
38
|
+
const tmp = path3.join(dir, `.${path3.basename(targetPath)}.${randomBytes(6).toString("hex")}.tmp`);
|
|
39
39
|
try {
|
|
40
40
|
if (typeof content === "string") {
|
|
41
|
-
await
|
|
41
|
+
await fsp2.writeFile(tmp, content, { flag: "wx", encoding: opts.encoding ?? "utf8" });
|
|
42
42
|
} else {
|
|
43
|
-
await
|
|
43
|
+
await fsp2.writeFile(tmp, content, { flag: "wx" });
|
|
44
44
|
}
|
|
45
45
|
try {
|
|
46
|
-
const fh = await
|
|
46
|
+
const fh = await fsp2.open(tmp, "r+");
|
|
47
47
|
try {
|
|
48
48
|
await fh.sync();
|
|
49
49
|
} finally {
|
|
@@ -53,45 +53,50 @@ async function atomicWrite(targetPath, content, opts = {}) {
|
|
|
53
53
|
}
|
|
54
54
|
let mode;
|
|
55
55
|
try {
|
|
56
|
-
const stat6 = await
|
|
56
|
+
const stat6 = await fsp2.stat(targetPath);
|
|
57
57
|
mode = stat6.mode & 511;
|
|
58
58
|
} catch {
|
|
59
59
|
mode = opts.mode;
|
|
60
60
|
}
|
|
61
61
|
if (mode !== void 0) {
|
|
62
|
-
await
|
|
62
|
+
await fsp2.chmod(tmp, mode);
|
|
63
63
|
}
|
|
64
64
|
await renameWithRetry(tmp, targetPath);
|
|
65
65
|
} catch (err) {
|
|
66
66
|
try {
|
|
67
|
-
await
|
|
67
|
+
await fsp2.unlink(tmp);
|
|
68
68
|
} catch {
|
|
69
69
|
}
|
|
70
70
|
throw err;
|
|
71
71
|
}
|
|
72
72
|
}
|
|
73
73
|
async function ensureDir(dir) {
|
|
74
|
-
await
|
|
74
|
+
await fsp2.mkdir(dir, { recursive: true });
|
|
75
75
|
}
|
|
76
76
|
async function withFileLock(targetPath, fn, opts = {}) {
|
|
77
|
-
const dir =
|
|
78
|
-
await
|
|
79
|
-
const lockPath =
|
|
77
|
+
const dir = path3.dirname(targetPath);
|
|
78
|
+
await fsp2.mkdir(dir, { recursive: true });
|
|
79
|
+
const lockPath = path3.join(dir, `.${path3.basename(targetPath)}.lock`);
|
|
80
80
|
const timeoutMs = opts.timeoutMs ?? 5e3;
|
|
81
81
|
const staleMs = opts.staleMs ?? 3e4;
|
|
82
82
|
const started = Date.now();
|
|
83
83
|
let handle;
|
|
84
84
|
for (; ; ) {
|
|
85
85
|
try {
|
|
86
|
-
handle = await
|
|
86
|
+
handle = await fsp2.open(lockPath, "wx");
|
|
87
87
|
await handle.writeFile(`${process.pid}:${Date.now()}`);
|
|
88
88
|
break;
|
|
89
89
|
} catch (err) {
|
|
90
|
-
|
|
90
|
+
const code = err.code;
|
|
91
|
+
if (code === "ENOENT") {
|
|
92
|
+
await fsp2.mkdir(dir, { recursive: true });
|
|
93
|
+
continue;
|
|
94
|
+
}
|
|
95
|
+
if (code !== "EEXIST") throw err;
|
|
91
96
|
try {
|
|
92
|
-
const stat6 = await
|
|
97
|
+
const stat6 = await fsp2.stat(lockPath);
|
|
93
98
|
if (Date.now() - stat6.mtimeMs > staleMs) {
|
|
94
|
-
await
|
|
99
|
+
await fsp2.unlink(lockPath);
|
|
95
100
|
continue;
|
|
96
101
|
}
|
|
97
102
|
} catch {
|
|
@@ -111,21 +116,21 @@ async function withFileLock(targetPath, fn, opts = {}) {
|
|
|
111
116
|
} catch {
|
|
112
117
|
}
|
|
113
118
|
try {
|
|
114
|
-
await
|
|
119
|
+
await fsp2.unlink(lockPath);
|
|
115
120
|
} catch {
|
|
116
121
|
}
|
|
117
122
|
}
|
|
118
123
|
}
|
|
119
124
|
async function renameWithRetry(from, to) {
|
|
120
125
|
if (process.platform !== "win32") {
|
|
121
|
-
await
|
|
126
|
+
await fsp2.rename(from, to);
|
|
122
127
|
return;
|
|
123
128
|
}
|
|
124
129
|
const delays = [10, 25, 60, 120, 250];
|
|
125
130
|
let lastErr;
|
|
126
131
|
for (let i = 0; i <= delays.length; i++) {
|
|
127
132
|
try {
|
|
128
|
-
await
|
|
133
|
+
await fsp2.rename(from, to);
|
|
129
134
|
return;
|
|
130
135
|
} catch (err) {
|
|
131
136
|
lastErr = err;
|
|
@@ -229,7 +234,7 @@ var DefaultLogger = class _DefaultLogger {
|
|
|
229
234
|
this.maxFileBytes = opts.maxFileBytes ?? 10 * 1024 * 1024;
|
|
230
235
|
if (this.file) {
|
|
231
236
|
try {
|
|
232
|
-
|
|
237
|
+
fs4.mkdirSync(path3.dirname(this.file), { recursive: true });
|
|
233
238
|
} catch {
|
|
234
239
|
}
|
|
235
240
|
}
|
|
@@ -270,10 +275,10 @@ var DefaultLogger = class _DefaultLogger {
|
|
|
270
275
|
maybeRotate(file) {
|
|
271
276
|
if (this.writesSinceRotateCheck++ % _DefaultLogger.ROTATE_CHECK_EVERY !== 0) return;
|
|
272
277
|
try {
|
|
273
|
-
const st =
|
|
278
|
+
const st = fs4.statSync(file);
|
|
274
279
|
if (st.size < this.maxFileBytes) return;
|
|
275
|
-
|
|
276
|
-
|
|
280
|
+
fs4.rmSync(`${file}.1`, { force: true });
|
|
281
|
+
fs4.renameSync(file, `${file}.1`);
|
|
277
282
|
} catch {
|
|
278
283
|
}
|
|
279
284
|
}
|
|
@@ -289,7 +294,7 @@ var DefaultLogger = class _DefaultLogger {
|
|
|
289
294
|
if (this.file) {
|
|
290
295
|
try {
|
|
291
296
|
this.maybeRotate(this.file);
|
|
292
|
-
|
|
297
|
+
fs4.appendFileSync(this.file, `${JSON.stringify(entry)}
|
|
293
298
|
`);
|
|
294
299
|
} catch {
|
|
295
300
|
}
|
|
@@ -386,49 +391,873 @@ function repairToolUseAdjacency(messages) {
|
|
|
386
391
|
changed = true;
|
|
387
392
|
continue;
|
|
388
393
|
}
|
|
389
|
-
out.push(msg);
|
|
394
|
+
out.push(msg);
|
|
395
|
+
}
|
|
396
|
+
return {
|
|
397
|
+
messages: changed ? out : messages,
|
|
398
|
+
report: { changed, removedToolUses, removedToolResults, removedMessages }
|
|
399
|
+
};
|
|
400
|
+
}
|
|
401
|
+
function hasToolUse(msg) {
|
|
402
|
+
return contentBlocks(msg).some((b) => b.type === "tool_use");
|
|
403
|
+
}
|
|
404
|
+
function hasToolResult(msg) {
|
|
405
|
+
return contentBlocks(msg).some((b) => b.type === "tool_result");
|
|
406
|
+
}
|
|
407
|
+
function toolUseIds(msg) {
|
|
408
|
+
const ids = /* @__PURE__ */ new Set();
|
|
409
|
+
if (!msg || msg.role !== "assistant") return ids;
|
|
410
|
+
for (const block of contentBlocks(msg)) {
|
|
411
|
+
if (block.type === "tool_use") ids.add(block.id);
|
|
412
|
+
}
|
|
413
|
+
return ids;
|
|
414
|
+
}
|
|
415
|
+
function toolResultIds(msg) {
|
|
416
|
+
const ids = /* @__PURE__ */ new Set();
|
|
417
|
+
if (!msg || msg.role !== "user") return ids;
|
|
418
|
+
for (const block of contentBlocks(msg)) {
|
|
419
|
+
if (block.type === "tool_result") ids.add(block.tool_use_id);
|
|
420
|
+
}
|
|
421
|
+
return ids;
|
|
422
|
+
}
|
|
423
|
+
function contentBlocks(msg) {
|
|
424
|
+
return msg && Array.isArray(msg.content) ? msg.content : [];
|
|
425
|
+
}
|
|
426
|
+
function mapContent(msg, fn) {
|
|
427
|
+
if (!Array.isArray(msg.content)) return msg;
|
|
428
|
+
const next = fn(msg.content);
|
|
429
|
+
if (next.length === msg.content.length && next.every((b, idx) => b === msg.content[idx])) {
|
|
430
|
+
return msg;
|
|
431
|
+
}
|
|
432
|
+
return { ...msg, content: next };
|
|
433
|
+
}
|
|
434
|
+
function isEmptyMessage(msg) {
|
|
435
|
+
if (typeof msg.content === "string") return msg.content.trim().length === 0;
|
|
436
|
+
return msg.content.length === 0;
|
|
437
|
+
}
|
|
438
|
+
|
|
439
|
+
// src/utils/index.ts
|
|
440
|
+
init_atomic_write();
|
|
441
|
+
|
|
442
|
+
// src/utils/error.ts
|
|
443
|
+
function toErrorMessage(err) {
|
|
444
|
+
return err instanceof Error ? err.message : String(err);
|
|
445
|
+
}
|
|
446
|
+
|
|
447
|
+
// src/utils/safe-json.ts
|
|
448
|
+
function safeParse(input, maxBytes = 5e6) {
|
|
449
|
+
if (input.length > maxBytes) {
|
|
450
|
+
return { ok: false, error: `Input exceeds limit (${maxBytes} bytes)` };
|
|
451
|
+
}
|
|
452
|
+
try {
|
|
453
|
+
return { ok: true, value: JSON.parse(input) };
|
|
454
|
+
} catch (err) {
|
|
455
|
+
return {
|
|
456
|
+
ok: false,
|
|
457
|
+
error: toErrorMessage(err)
|
|
458
|
+
};
|
|
459
|
+
}
|
|
460
|
+
}
|
|
461
|
+
|
|
462
|
+
// src/utils/string.ts
|
|
463
|
+
function truncate(s, max) {
|
|
464
|
+
return s.length <= max ? s : `${s.slice(0, max - 1)}\u2026`;
|
|
465
|
+
}
|
|
466
|
+
|
|
467
|
+
// src/utils/glob-match.ts
|
|
468
|
+
function escapeRegex(s) {
|
|
469
|
+
return s.replace(/[.+^${}()|\\]/g, "\\$&");
|
|
470
|
+
}
|
|
471
|
+
var COMPILED_GLOB_CACHE = /* @__PURE__ */ new Map();
|
|
472
|
+
var CACHE_MAX_SIZE = 2e3;
|
|
473
|
+
function getCachedGlob(pattern) {
|
|
474
|
+
const cached = COMPILED_GLOB_CACHE.get(pattern);
|
|
475
|
+
if (cached) return cached;
|
|
476
|
+
if (COMPILED_GLOB_CACHE.size >= CACHE_MAX_SIZE) {
|
|
477
|
+
const keys = [...COMPILED_GLOB_CACHE.keys()];
|
|
478
|
+
for (let i = 0; i < Math.floor(CACHE_MAX_SIZE / 4); i++) {
|
|
479
|
+
COMPILED_GLOB_CACHE.delete(expectDefined(keys[i]));
|
|
480
|
+
}
|
|
481
|
+
}
|
|
482
|
+
const re = compileGlob(pattern);
|
|
483
|
+
COMPILED_GLOB_CACHE.set(pattern, re);
|
|
484
|
+
return re;
|
|
485
|
+
}
|
|
486
|
+
var MAX_GLOB_PATTERN_LEN = 1024;
|
|
487
|
+
function compileGlob(pattern) {
|
|
488
|
+
if (pattern.length > MAX_GLOB_PATTERN_LEN) {
|
|
489
|
+
throw new Error(`Glob pattern exceeds ${MAX_GLOB_PATTERN_LEN} characters`);
|
|
490
|
+
}
|
|
491
|
+
let i = 0;
|
|
492
|
+
let re = "^";
|
|
493
|
+
while (i < pattern.length) {
|
|
494
|
+
const c = pattern[i];
|
|
495
|
+
if (c === "*") {
|
|
496
|
+
if (pattern[i + 1] === "*") {
|
|
497
|
+
re += ".*";
|
|
498
|
+
i += 2;
|
|
499
|
+
if (pattern[i] === "/") i++;
|
|
500
|
+
} else {
|
|
501
|
+
re += "[^/]*";
|
|
502
|
+
i++;
|
|
503
|
+
}
|
|
504
|
+
} else if (c === "?") {
|
|
505
|
+
re += "[^/]";
|
|
506
|
+
i++;
|
|
507
|
+
} else if (c === "[") {
|
|
508
|
+
let cls = "[";
|
|
509
|
+
i++;
|
|
510
|
+
if (pattern[i] === "!" || pattern[i] === "^") {
|
|
511
|
+
cls += "^";
|
|
512
|
+
i++;
|
|
513
|
+
}
|
|
514
|
+
while (i < pattern.length && pattern[i] !== "]") {
|
|
515
|
+
const ch = pattern[i] ?? "";
|
|
516
|
+
if (ch === "\\") {
|
|
517
|
+
cls += "\\\\";
|
|
518
|
+
} else if (ch === "]" || ch === "^") {
|
|
519
|
+
cls += `\\${ch}`;
|
|
520
|
+
} else {
|
|
521
|
+
cls += ch;
|
|
522
|
+
}
|
|
523
|
+
i++;
|
|
524
|
+
}
|
|
525
|
+
cls += "]";
|
|
526
|
+
re += cls;
|
|
527
|
+
i++;
|
|
528
|
+
} else {
|
|
529
|
+
re += escapeRegex(c ?? "");
|
|
530
|
+
i++;
|
|
531
|
+
}
|
|
532
|
+
}
|
|
533
|
+
re += "$";
|
|
534
|
+
return new RegExp(re);
|
|
535
|
+
}
|
|
536
|
+
function matchGlob(pattern, input) {
|
|
537
|
+
return getCachedGlob(pattern).test(input);
|
|
538
|
+
}
|
|
539
|
+
function matchAny(patterns, input) {
|
|
540
|
+
return patterns.some((p) => matchGlob(p, input));
|
|
541
|
+
}
|
|
542
|
+
function projectHash(absRoot) {
|
|
543
|
+
return createHash("sha256").update(path3.resolve(absRoot)).digest("hex").slice(0, 12);
|
|
544
|
+
}
|
|
545
|
+
function projectSlug(absRoot) {
|
|
546
|
+
const base = slugify(path3.basename(absRoot));
|
|
547
|
+
const hash = createHash("sha256").update(path3.resolve(absRoot)).digest("hex").slice(0, 6);
|
|
548
|
+
return `${base}-${hash}`;
|
|
549
|
+
}
|
|
550
|
+
function slugify(name) {
|
|
551
|
+
return name.toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-+|-+$/g, "").slice(0, 40) || "project";
|
|
552
|
+
}
|
|
553
|
+
function wstackGlobalRoot() {
|
|
554
|
+
const fromEnv = process.env["WRONGSTACK_HOME"];
|
|
555
|
+
if (fromEnv && fromEnv.trim().length > 0) return path3.resolve(fromEnv);
|
|
556
|
+
return path3.join(os.homedir(), ".wrongstack");
|
|
557
|
+
}
|
|
558
|
+
function resolveWstackPaths(opts) {
|
|
559
|
+
const globalRoot = opts.globalRoot ?? (opts.userHome ? path3.join(opts.userHome, ".wrongstack") : wstackGlobalRoot());
|
|
560
|
+
const hash = projectHash(opts.projectRoot);
|
|
561
|
+
const slug = projectSlug(opts.projectRoot);
|
|
562
|
+
const projectDir = path3.join(globalRoot, "projects", slug);
|
|
563
|
+
return {
|
|
564
|
+
globalRoot,
|
|
565
|
+
configDir: globalRoot,
|
|
566
|
+
globalConfig: path3.join(globalRoot, "config.json"),
|
|
567
|
+
secretsKey: path3.join(globalRoot, ".key"),
|
|
568
|
+
globalMemory: path3.join(globalRoot, "memory.md"),
|
|
569
|
+
globalSkills: path3.join(globalRoot, "skills"),
|
|
570
|
+
globalPrompts: path3.join(globalRoot, "prompts"),
|
|
571
|
+
cacheDir: path3.join(globalRoot, "cache"),
|
|
572
|
+
modelsCache: path3.join(globalRoot, "cache", "models.dev.json"),
|
|
573
|
+
modelsOverlayCache: path3.join(globalRoot, "cache", "models-overlay.json"),
|
|
574
|
+
historyFile: path3.join(globalRoot, "history"),
|
|
575
|
+
logFile: path3.join(globalRoot, "logs", "wrongstack.log"),
|
|
576
|
+
projectDir,
|
|
577
|
+
projectCodebaseIndex: path3.join(projectDir, "codebase-index"),
|
|
578
|
+
projectMemory: path3.join(projectDir, "memory.md"),
|
|
579
|
+
projectSessions: path3.join(projectDir, "sessions"),
|
|
580
|
+
projectTrust: path3.join(projectDir, "trust.json"),
|
|
581
|
+
projectMeta: path3.join(projectDir, "meta.json"),
|
|
582
|
+
projectLocalConfig: path3.join(projectDir, "config.local.json"),
|
|
583
|
+
inProjectConfig: path3.join(opts.projectRoot, ".wrongstack", "config.json"),
|
|
584
|
+
inProjectAgentsFile: path3.join(opts.projectRoot, ".wrongstack", "AGENTS.md"),
|
|
585
|
+
inProjectSkills: path3.join(opts.projectRoot, ".wrongstack", "skills"),
|
|
586
|
+
inProjectWorktrees: path3.join(opts.projectRoot, ".wrongstack", "worktrees"),
|
|
587
|
+
projectHash: hash,
|
|
588
|
+
projectSlug: slug,
|
|
589
|
+
projectGoal: path3.join(projectDir, "goal.json"),
|
|
590
|
+
projectSpecs: path3.join(projectDir, "specs"),
|
|
591
|
+
projectTaskGraphs: path3.join(projectDir, "task-graphs"),
|
|
592
|
+
projectSddSession: path3.join(projectDir, "sdd-session.json"),
|
|
593
|
+
projectPlan: path3.join(projectDir, "plan.json"),
|
|
594
|
+
projectAutophase: path3.join(projectDir, "autophase"),
|
|
595
|
+
syncConfig: path3.join(globalRoot, "sync.json"),
|
|
596
|
+
projectStatus: (projectHash2) => path3.join(globalRoot, "projects", projectHash2, "status.json")
|
|
597
|
+
};
|
|
598
|
+
}
|
|
599
|
+
|
|
600
|
+
// src/utils/sleep.ts
|
|
601
|
+
function sleep(ms) {
|
|
602
|
+
return new Promise((resolve5) => setTimeout(resolve5, ms));
|
|
603
|
+
}
|
|
604
|
+
|
|
605
|
+
// src/utils/assert-never.ts
|
|
606
|
+
function assertNever(x, message) {
|
|
607
|
+
const err = new Error(
|
|
608
|
+
`Unhandled case: ${JSON.stringify(x)}`
|
|
609
|
+
);
|
|
610
|
+
err.name = "AssertNeverError";
|
|
611
|
+
throw err;
|
|
612
|
+
}
|
|
613
|
+
|
|
614
|
+
// src/utils/deep-merge.ts
|
|
615
|
+
var FORBIDDEN_PROTO_KEYS = /* @__PURE__ */ new Set([
|
|
616
|
+
"__proto__",
|
|
617
|
+
"constructor",
|
|
618
|
+
"prototype",
|
|
619
|
+
"__defineGetter__",
|
|
620
|
+
"__defineSetter__",
|
|
621
|
+
"__lookupGetter__",
|
|
622
|
+
"__lookupSetter__"
|
|
623
|
+
]);
|
|
624
|
+
function isPrimitiveArray(a) {
|
|
625
|
+
return a.every((v) => v === null || typeof v !== "object" && typeof v !== "function");
|
|
626
|
+
}
|
|
627
|
+
function deepMerge(base, patch, options = {}) {
|
|
628
|
+
const {
|
|
629
|
+
conflictResolution = "prefer-patch",
|
|
630
|
+
arrayMode = "replace",
|
|
631
|
+
protectProto = true,
|
|
632
|
+
onNonPrimitiveArrayReplace
|
|
633
|
+
} = options;
|
|
634
|
+
if (typeof base !== "object" || base === null) {
|
|
635
|
+
return conflictResolution === "prefer-patch" ? patch : base;
|
|
636
|
+
}
|
|
637
|
+
if (typeof patch !== "object" || patch === null) {
|
|
638
|
+
return conflictResolution === "prefer-patch" ? patch : base;
|
|
639
|
+
}
|
|
640
|
+
if (Array.isArray(base) && Array.isArray(patch)) {
|
|
641
|
+
if (arrayMode === "concat-primitives" && isPrimitiveArray(base) && isPrimitiveArray(patch)) {
|
|
642
|
+
return [.../* @__PURE__ */ new Set([...base, ...patch])];
|
|
643
|
+
}
|
|
644
|
+
return conflictResolution === "prefer-patch" ? patch : base;
|
|
645
|
+
}
|
|
646
|
+
if (Array.isArray(base) || Array.isArray(patch)) {
|
|
647
|
+
return conflictResolution === "prefer-patch" ? patch : base;
|
|
648
|
+
}
|
|
649
|
+
const baseObj = base;
|
|
650
|
+
const patchObj = patch;
|
|
651
|
+
const out = { ...baseObj };
|
|
652
|
+
for (const [k, v] of Object.entries(patchObj)) {
|
|
653
|
+
if (protectProto && FORBIDDEN_PROTO_KEYS.has(k)) continue;
|
|
654
|
+
const existing = out[k];
|
|
655
|
+
if (v !== null && typeof v === "object" && !Array.isArray(v) && existing !== null && typeof existing === "object" && !Array.isArray(existing)) {
|
|
656
|
+
out[k] = deepMerge(existing, v, options);
|
|
657
|
+
} else if (Array.isArray(v) && Array.isArray(existing)) {
|
|
658
|
+
if (onNonPrimitiveArrayReplace && !isPrimitiveArray(v)) {
|
|
659
|
+
onNonPrimitiveArrayReplace(k, existing.length, v.length);
|
|
660
|
+
}
|
|
661
|
+
out[k] = deepMerge(existing, v, options);
|
|
662
|
+
} else if (v !== void 0) {
|
|
663
|
+
if (onNonPrimitiveArrayReplace && Array.isArray(v) && !isPrimitiveArray(v)) {
|
|
664
|
+
const existingLen = Array.isArray(existing) ? existing.length : 0;
|
|
665
|
+
onNonPrimitiveArrayReplace(k, existingLen, v.length);
|
|
666
|
+
}
|
|
667
|
+
out[k] = v;
|
|
668
|
+
}
|
|
669
|
+
}
|
|
670
|
+
return out;
|
|
671
|
+
}
|
|
672
|
+
|
|
673
|
+
// src/utils/tool-output-serializer.ts
|
|
674
|
+
function createToolOutputSerializer(opts = {}) {
|
|
675
|
+
const capBytes = opts.perIterationOutputCapBytes ?? 1e5;
|
|
676
|
+
function serialize(value) {
|
|
677
|
+
if (typeof value === "string") return value;
|
|
678
|
+
if (value === null || value === void 0) return "";
|
|
679
|
+
if (typeof value === "object") {
|
|
680
|
+
if (Array.isArray(value)) return value.map(serialize).join("\n");
|
|
681
|
+
if ("text" in value) {
|
|
682
|
+
const t = value.text;
|
|
683
|
+
return typeof t === "string" ? t : JSON.stringify(value, null, 2);
|
|
684
|
+
}
|
|
685
|
+
try {
|
|
686
|
+
return JSON.stringify(value, null, 2);
|
|
687
|
+
} catch {
|
|
688
|
+
return String(value);
|
|
689
|
+
}
|
|
690
|
+
}
|
|
691
|
+
return String(value);
|
|
692
|
+
}
|
|
693
|
+
function enforceCap(text, remainingBudget) {
|
|
694
|
+
if (remainingBudget <= 0) {
|
|
695
|
+
return { text: "[truncated: iteration output cap exceeded]", newBudget: 0 };
|
|
696
|
+
}
|
|
697
|
+
const textBytes = Buffer.byteLength(text, "utf8");
|
|
698
|
+
if (textBytes <= remainingBudget) {
|
|
699
|
+
return { text, newBudget: remainingBudget - textBytes };
|
|
700
|
+
}
|
|
701
|
+
const marker = `
|
|
702
|
+
\u2026[truncated ${textBytes - remainingBudget} bytes]\u2026
|
|
703
|
+
`;
|
|
704
|
+
const markerBytes = Buffer.byteLength(marker, "utf8");
|
|
705
|
+
const available = remainingBudget - markerBytes;
|
|
706
|
+
if (available <= 0) {
|
|
707
|
+
return { text: "[truncated: iteration output cap exceeded]", newBudget: 0 };
|
|
708
|
+
}
|
|
709
|
+
const half = Math.floor(available / 2);
|
|
710
|
+
const first = text.slice(0, half);
|
|
711
|
+
const second = text.slice(text.length - half);
|
|
712
|
+
return { text: `${first}${marker}${second}`, newBudget: 0 };
|
|
713
|
+
}
|
|
714
|
+
return { serialize, enforceCap, capBytes };
|
|
715
|
+
}
|
|
716
|
+
|
|
717
|
+
// src/utils/token-estimate.ts
|
|
718
|
+
var RoughTokenEstimate = (text, charsPerToken = 3.5) => Math.max(1, Math.ceil(text.length / charsPerToken));
|
|
719
|
+
var CALIBRATION_GLOBAL_KEY = "__global__";
|
|
720
|
+
var _cals = /* @__PURE__ */ new Map();
|
|
721
|
+
function calState(key) {
|
|
722
|
+
let state = _cals.get(key);
|
|
723
|
+
if (!state) {
|
|
724
|
+
state = { ratio: 1, count: 0, prevEst: 0 };
|
|
725
|
+
_cals.set(key, state);
|
|
726
|
+
}
|
|
727
|
+
return state;
|
|
728
|
+
}
|
|
729
|
+
var MIN_SAMPLES_FOR_CALIBRATION = 3;
|
|
730
|
+
var ESTIMATE_CACHE = /* @__PURE__ */ new Map();
|
|
731
|
+
var ESTIMATE_CACHE_MAX_SIZE = 1e4;
|
|
732
|
+
function getCachedEstimate(key, compute) {
|
|
733
|
+
const existing = ESTIMATE_CACHE.get(key);
|
|
734
|
+
if (existing !== void 0) return existing;
|
|
735
|
+
if (ESTIMATE_CACHE.size >= ESTIMATE_CACHE_MAX_SIZE) {
|
|
736
|
+
for (const k of ESTIMATE_CACHE.keys()) {
|
|
737
|
+
if (ESTIMATE_CACHE.size <= Math.floor(ESTIMATE_CACHE_MAX_SIZE / 2)) break;
|
|
738
|
+
ESTIMATE_CACHE.delete(k);
|
|
739
|
+
}
|
|
740
|
+
}
|
|
741
|
+
const estimate = compute(key);
|
|
742
|
+
ESTIMATE_CACHE.set(key, estimate);
|
|
743
|
+
return estimate;
|
|
744
|
+
}
|
|
745
|
+
function estimateToolInputTokens(input) {
|
|
746
|
+
if (typeof input === "string") return RoughTokenEstimate(input);
|
|
747
|
+
if (input === null || typeof input !== "object") {
|
|
748
|
+
return RoughTokenEstimate(String(input));
|
|
749
|
+
}
|
|
750
|
+
return getCachedEstimate(JSON.stringify(input), (key) => RoughTokenEstimate(key));
|
|
751
|
+
}
|
|
752
|
+
function estimateToolResultTokens(content) {
|
|
753
|
+
if (typeof content === "string") return RoughTokenEstimate(content);
|
|
754
|
+
return getCachedEstimate(JSON.stringify(content), (key) => RoughTokenEstimate(key));
|
|
755
|
+
}
|
|
756
|
+
function estimateTextTokens(text) {
|
|
757
|
+
return RoughTokenEstimate(text);
|
|
758
|
+
}
|
|
759
|
+
function computeMessageTokens(msg) {
|
|
760
|
+
if (typeof msg.content === "string") return estimateTextTokens(msg.content);
|
|
761
|
+
let total = 0;
|
|
762
|
+
for (const b of msg.content) {
|
|
763
|
+
if (b.type === "text") total += estimateTextTokens(b.text);
|
|
764
|
+
else if (b.type === "tool_use") total += estimateToolInputTokens(b.input);
|
|
765
|
+
else if (b.type === "tool_result") total += estimateToolResultTokens(b.content);
|
|
766
|
+
else total += RoughTokenEstimate(JSON.stringify(b));
|
|
767
|
+
}
|
|
768
|
+
return total;
|
|
769
|
+
}
|
|
770
|
+
function estimateMessageTokens(messages) {
|
|
771
|
+
let total = 0;
|
|
772
|
+
for (const m of messages) {
|
|
773
|
+
if (typeof m._estTokens === "number" && m._estTokens > 0) {
|
|
774
|
+
total += m._estTokens;
|
|
775
|
+
continue;
|
|
776
|
+
}
|
|
777
|
+
total += computeMessageTokens(m);
|
|
778
|
+
}
|
|
779
|
+
return total;
|
|
780
|
+
}
|
|
781
|
+
function estimateToolDefTokens(tool) {
|
|
782
|
+
const cached = tool._estDefTokens;
|
|
783
|
+
if (typeof cached === "number" && cached > 0) return cached;
|
|
784
|
+
return RoughTokenEstimate(tool.name) + RoughTokenEstimate(tool.description ?? "") + RoughTokenEstimate(JSON.stringify(tool.inputSchema));
|
|
785
|
+
}
|
|
786
|
+
function estimateRequestTokens(messages, systemPrompt, tools, calibrationKey = CALIBRATION_GLOBAL_KEY) {
|
|
787
|
+
let messagesTokens = 0;
|
|
788
|
+
if (typeof messages === "string") {
|
|
789
|
+
messagesTokens = RoughTokenEstimate(messages);
|
|
790
|
+
} else if (Array.isArray(messages)) {
|
|
791
|
+
for (const m of messages) {
|
|
792
|
+
if (typeof m === "object" && m !== null && "content" in m) {
|
|
793
|
+
const cached = m._estTokens;
|
|
794
|
+
if (typeof cached === "number" && cached > 0) {
|
|
795
|
+
messagesTokens += cached;
|
|
796
|
+
continue;
|
|
797
|
+
}
|
|
798
|
+
const content = m.content;
|
|
799
|
+
if (typeof content === "string") {
|
|
800
|
+
messagesTokens += RoughTokenEstimate(content);
|
|
801
|
+
} else if (Array.isArray(content)) {
|
|
802
|
+
for (const b of content) {
|
|
803
|
+
if (typeof b === "object" && b !== null) {
|
|
804
|
+
if (b.type === "text") {
|
|
805
|
+
messagesTokens += RoughTokenEstimate(b.text);
|
|
806
|
+
} else {
|
|
807
|
+
messagesTokens += RoughTokenEstimate(JSON.stringify(b));
|
|
808
|
+
}
|
|
809
|
+
}
|
|
810
|
+
}
|
|
811
|
+
}
|
|
812
|
+
}
|
|
813
|
+
}
|
|
814
|
+
}
|
|
815
|
+
let systemTokens = 0;
|
|
816
|
+
if (typeof systemPrompt === "string") {
|
|
817
|
+
systemTokens = RoughTokenEstimate(systemPrompt);
|
|
818
|
+
} else if (Array.isArray(systemPrompt)) {
|
|
819
|
+
for (const b of systemPrompt) {
|
|
820
|
+
if (typeof b === "object" && b !== null && b.type === "text") {
|
|
821
|
+
systemTokens += RoughTokenEstimate(b.text);
|
|
822
|
+
}
|
|
823
|
+
}
|
|
824
|
+
}
|
|
825
|
+
let toolsTokens = 0;
|
|
826
|
+
for (const t of tools) {
|
|
827
|
+
toolsTokens += estimateToolDefTokens(t);
|
|
828
|
+
}
|
|
829
|
+
const total = messagesTokens + systemTokens + toolsTokens;
|
|
830
|
+
calState(calibrationKey).prevEst = total;
|
|
831
|
+
return {
|
|
832
|
+
messages: messagesTokens,
|
|
833
|
+
systemPrompt: systemTokens,
|
|
834
|
+
tools: toolsTokens,
|
|
835
|
+
total
|
|
836
|
+
};
|
|
837
|
+
}
|
|
838
|
+
function getCalibrationState(calibrationKey = CALIBRATION_GLOBAL_KEY) {
|
|
839
|
+
const cal = calState(calibrationKey);
|
|
840
|
+
return {
|
|
841
|
+
ratio: cal.ratio,
|
|
842
|
+
count: cal.count,
|
|
843
|
+
calibrated: cal.count >= MIN_SAMPLES_FOR_CALIBRATION
|
|
844
|
+
};
|
|
845
|
+
}
|
|
846
|
+
function estimateRequestTokensCalibrated(messages, systemPrompt, tools, calibrationKey = CALIBRATION_GLOBAL_KEY) {
|
|
847
|
+
const result = estimateRequestTokens(messages, systemPrompt, tools, calibrationKey);
|
|
848
|
+
const cal = calState(calibrationKey);
|
|
849
|
+
if (cal.count >= MIN_SAMPLES_FOR_CALIBRATION) {
|
|
850
|
+
const safeRatio = Math.min(1.5, Math.max(0.5, cal.ratio));
|
|
851
|
+
return {
|
|
852
|
+
messages: Math.round(result.messages * safeRatio),
|
|
853
|
+
systemPrompt: Math.round(result.systemPrompt * safeRatio),
|
|
854
|
+
tools: Math.round(result.tools * safeRatio),
|
|
855
|
+
total: Math.round(result.total * safeRatio)
|
|
856
|
+
};
|
|
857
|
+
}
|
|
858
|
+
return result;
|
|
859
|
+
}
|
|
860
|
+
|
|
861
|
+
// src/utils/json-schema-validate.ts
|
|
862
|
+
function validateAgainstSchema(value, schema) {
|
|
863
|
+
const errors = [];
|
|
864
|
+
walk(value, schema, "", errors);
|
|
865
|
+
return { ok: errors.length === 0, errors };
|
|
866
|
+
}
|
|
867
|
+
function walk(value, schema, path19, errors) {
|
|
868
|
+
if (schema.enum !== void 0) {
|
|
869
|
+
if (!schema.enum.some((e) => deepEqual(e, value))) {
|
|
870
|
+
errors.push({
|
|
871
|
+
path: path19 || "<root>",
|
|
872
|
+
message: `expected one of ${JSON.stringify(schema.enum)}, got ${JSON.stringify(value)}`
|
|
873
|
+
});
|
|
874
|
+
return;
|
|
875
|
+
}
|
|
876
|
+
}
|
|
877
|
+
if (typeof schema.type === "string") {
|
|
878
|
+
if (!checkType(value, schema.type)) {
|
|
879
|
+
errors.push({
|
|
880
|
+
path: path19 || "<root>",
|
|
881
|
+
message: `expected ${schema.type}, got ${describeType(value)}`
|
|
882
|
+
});
|
|
883
|
+
return;
|
|
884
|
+
}
|
|
885
|
+
}
|
|
886
|
+
if (schema.type === "object" && isPlainObject(value)) {
|
|
887
|
+
const obj = value;
|
|
888
|
+
for (const req of schema.required ?? []) {
|
|
889
|
+
if (!(req in obj)) {
|
|
890
|
+
errors.push({ path: joinPath(path19, req), message: "required property missing" });
|
|
891
|
+
}
|
|
892
|
+
}
|
|
893
|
+
if (schema.properties) {
|
|
894
|
+
for (const [key, subSchema] of Object.entries(schema.properties)) {
|
|
895
|
+
if (key in obj) {
|
|
896
|
+
walk(obj[key], subSchema, joinPath(path19, key), errors);
|
|
897
|
+
}
|
|
898
|
+
}
|
|
899
|
+
}
|
|
900
|
+
}
|
|
901
|
+
if (schema.type === "array" && Array.isArray(value) && schema.items) {
|
|
902
|
+
for (let i = 0; i < value.length; i++) {
|
|
903
|
+
walk(value[i], schema.items, `${path19}[${i}]`, errors);
|
|
904
|
+
}
|
|
905
|
+
}
|
|
906
|
+
}
|
|
907
|
+
function checkType(value, type) {
|
|
908
|
+
switch (type) {
|
|
909
|
+
case "string":
|
|
910
|
+
return typeof value === "string";
|
|
911
|
+
case "number":
|
|
912
|
+
return typeof value === "number" && !Number.isNaN(value);
|
|
913
|
+
case "integer":
|
|
914
|
+
return typeof value === "number" && Number.isInteger(value);
|
|
915
|
+
case "boolean":
|
|
916
|
+
return typeof value === "boolean";
|
|
917
|
+
case "null":
|
|
918
|
+
return value === null;
|
|
919
|
+
case "array":
|
|
920
|
+
return Array.isArray(value);
|
|
921
|
+
case "object":
|
|
922
|
+
return isPlainObject(value);
|
|
923
|
+
default:
|
|
924
|
+
return true;
|
|
925
|
+
}
|
|
926
|
+
}
|
|
927
|
+
function isPlainObject(v) {
|
|
928
|
+
return typeof v === "object" && v !== null && !Array.isArray(v);
|
|
929
|
+
}
|
|
930
|
+
function describeType(v) {
|
|
931
|
+
if (v === null) return "null";
|
|
932
|
+
if (Array.isArray(v)) return "array";
|
|
933
|
+
return typeof v;
|
|
934
|
+
}
|
|
935
|
+
function joinPath(parent, key) {
|
|
936
|
+
if (!parent) return key;
|
|
937
|
+
return `${parent}.${key}`;
|
|
938
|
+
}
|
|
939
|
+
function deepEqual(a, b) {
|
|
940
|
+
if (a === b) return true;
|
|
941
|
+
if (typeof a !== typeof b) return false;
|
|
942
|
+
if (a === null || b === null) return a === b;
|
|
943
|
+
if (Array.isArray(a) && Array.isArray(b)) {
|
|
944
|
+
return a.length === b.length && a.every((v, i) => deepEqual(v, b[i]));
|
|
945
|
+
}
|
|
946
|
+
if (typeof a === "object" && typeof b === "object") {
|
|
947
|
+
const ak = Object.keys(a);
|
|
948
|
+
const bk = Object.keys(b);
|
|
949
|
+
if (ak.length !== bk.length) return false;
|
|
950
|
+
return ak.every(
|
|
951
|
+
(k) => deepEqual(a[k], b[k])
|
|
952
|
+
);
|
|
953
|
+
}
|
|
954
|
+
return false;
|
|
955
|
+
}
|
|
956
|
+
|
|
957
|
+
// src/utils/regex-guard.ts
|
|
958
|
+
var MAX_PATTERN_LEN = 512;
|
|
959
|
+
var DANGEROUS_PATTERNS = [
|
|
960
|
+
/(\([^)]*[+*][^)]*\))[+*]/,
|
|
961
|
+
// (a+)+, (.*)+, etc
|
|
962
|
+
/(\(\?:[^)]*[+*][^)]*\))[+*]/
|
|
963
|
+
// same, with non-capturing group
|
|
964
|
+
];
|
|
965
|
+
function compileUserRegex(pattern, flags) {
|
|
966
|
+
if (typeof pattern !== "string") {
|
|
967
|
+
return { ok: false, reason: "pattern must be a string" };
|
|
968
|
+
}
|
|
969
|
+
if (pattern.length === 0) {
|
|
970
|
+
return { ok: false, reason: "pattern is empty" };
|
|
971
|
+
}
|
|
972
|
+
if (pattern.length > MAX_PATTERN_LEN) {
|
|
973
|
+
return { ok: false, reason: `pattern exceeds ${MAX_PATTERN_LEN} characters` };
|
|
974
|
+
}
|
|
975
|
+
for (const rx of DANGEROUS_PATTERNS) {
|
|
976
|
+
if (rx.test(pattern)) {
|
|
977
|
+
return {
|
|
978
|
+
ok: false,
|
|
979
|
+
reason: "pattern looks vulnerable to catastrophic backtracking \u2014 rewrite without nested quantifiers"
|
|
980
|
+
};
|
|
981
|
+
}
|
|
982
|
+
}
|
|
983
|
+
try {
|
|
984
|
+
return { ok: true, regex: new RegExp(pattern, flags) };
|
|
985
|
+
} catch (err) {
|
|
986
|
+
return {
|
|
987
|
+
ok: false,
|
|
988
|
+
reason: err instanceof Error ? err.message : "invalid regex"
|
|
989
|
+
};
|
|
990
|
+
}
|
|
991
|
+
}
|
|
992
|
+
var GLOB_CHARS = /* @__PURE__ */ new Set(["*", "?", "["]);
|
|
993
|
+
var IS_WINDOWS = process.platform === "win32";
|
|
994
|
+
var SEP = IS_WINDOWS ? "\\" : "/";
|
|
995
|
+
function isGlob(p) {
|
|
996
|
+
for (const c of p) {
|
|
997
|
+
if (GLOB_CHARS.has(c)) return true;
|
|
998
|
+
}
|
|
999
|
+
return false;
|
|
1000
|
+
}
|
|
1001
|
+
function globToRegex(pat) {
|
|
1002
|
+
let i = 0;
|
|
1003
|
+
let re = "^";
|
|
1004
|
+
while (i < pat.length) {
|
|
1005
|
+
const c = expectDefined(pat[i]);
|
|
1006
|
+
if (c === "*") {
|
|
1007
|
+
if (pat[i + 1] === "*") {
|
|
1008
|
+
re += ".*";
|
|
1009
|
+
i += 2;
|
|
1010
|
+
if (pat[i] === "/") i++;
|
|
1011
|
+
} else {
|
|
1012
|
+
re += "[^/\\\\]*";
|
|
1013
|
+
i++;
|
|
1014
|
+
}
|
|
1015
|
+
} else if (c === "?") {
|
|
1016
|
+
re += "[^/\\\\]";
|
|
1017
|
+
i++;
|
|
1018
|
+
} else if (c === "[") {
|
|
1019
|
+
let cls = "[";
|
|
1020
|
+
i++;
|
|
1021
|
+
if (pat[i] === "!" || pat[i] === "^") {
|
|
1022
|
+
cls += "^";
|
|
1023
|
+
i++;
|
|
1024
|
+
}
|
|
1025
|
+
while (i < pat.length && pat[i] !== "]") {
|
|
1026
|
+
const ch = pat[i] ?? "";
|
|
1027
|
+
if (ch === "\\") cls += "\\\\";
|
|
1028
|
+
else if (ch === "]" || ch === "^") cls += `\\${ch}`;
|
|
1029
|
+
else cls += ch;
|
|
1030
|
+
i++;
|
|
1031
|
+
}
|
|
1032
|
+
cls += "]";
|
|
1033
|
+
re += cls;
|
|
1034
|
+
i++;
|
|
1035
|
+
} else {
|
|
1036
|
+
re += c.replace(/[.+^${}()|\\]/g, "\\$&");
|
|
1037
|
+
i++;
|
|
1038
|
+
}
|
|
1039
|
+
}
|
|
1040
|
+
return new RegExp(re + "$");
|
|
1041
|
+
}
|
|
1042
|
+
function baseDir(pat) {
|
|
1043
|
+
let i = pat.length - 1;
|
|
1044
|
+
while (i >= 0 && !GLOB_CHARS.has(expectDefined(pat[i])) && pat[i] !== SEP && pat[i] !== "/") i--;
|
|
1045
|
+
const cut = i >= 0 ? pat.lastIndexOf(SEP, i) : pat.lastIndexOf("/", i);
|
|
1046
|
+
return cut < 0 ? "." : pat.slice(0, cut);
|
|
1047
|
+
}
|
|
1048
|
+
async function expandGlob(pattern) {
|
|
1049
|
+
if (!isGlob(pattern)) return [pattern];
|
|
1050
|
+
const results = /* @__PURE__ */ new Set();
|
|
1051
|
+
const abs = isAbsolute(pattern);
|
|
1052
|
+
const base = abs ? baseDir(pattern) : baseDir(pattern);
|
|
1053
|
+
const relPat = base === "." ? pattern : pattern.slice(base.length + 1);
|
|
1054
|
+
async function walk3(dir, pat) {
|
|
1055
|
+
let entries;
|
|
1056
|
+
try {
|
|
1057
|
+
entries = await fsp2.readdir(dir);
|
|
1058
|
+
} catch {
|
|
1059
|
+
return;
|
|
1060
|
+
}
|
|
1061
|
+
const firstGlob = pat.search(/[*?[[]/);
|
|
1062
|
+
if (firstGlob < 0) {
|
|
1063
|
+
const re = globToRegex(pat);
|
|
1064
|
+
for (const e of entries) {
|
|
1065
|
+
if (re.test(e)) {
|
|
1066
|
+
const full = `${dir}${SEP}${e}`;
|
|
1067
|
+
results.add(abs ? resolve(full) : full);
|
|
1068
|
+
}
|
|
1069
|
+
}
|
|
1070
|
+
return;
|
|
1071
|
+
}
|
|
1072
|
+
const before = pat.slice(0, firstGlob);
|
|
1073
|
+
const rest = pat.slice(firstGlob);
|
|
1074
|
+
if (before.endsWith("**")) {
|
|
1075
|
+
await walk3(dir, rest);
|
|
1076
|
+
for (const e of entries) {
|
|
1077
|
+
const full = `${dir}${SEP}${e}`;
|
|
1078
|
+
try {
|
|
1079
|
+
const stat6 = await fsp2.stat(full);
|
|
1080
|
+
if (stat6.isDirectory()) await walk3(full, rest);
|
|
1081
|
+
} catch {
|
|
1082
|
+
}
|
|
1083
|
+
}
|
|
1084
|
+
} else if (before === "") {
|
|
1085
|
+
const re = globToRegex(rest);
|
|
1086
|
+
for (const e of entries) {
|
|
1087
|
+
if (re.test(e)) {
|
|
1088
|
+
const full = `${dir}${SEP}${e}`;
|
|
1089
|
+
results.add(abs ? resolve(full) : full);
|
|
1090
|
+
}
|
|
1091
|
+
}
|
|
1092
|
+
} else {
|
|
1093
|
+
const seg = before.replace(/[*?[\]]/g, "").replace(/\/$/, "");
|
|
1094
|
+
if (entries.includes(seg)) {
|
|
1095
|
+
const full = `${dir}${SEP}${seg}`;
|
|
1096
|
+
try {
|
|
1097
|
+
const stat6 = await fsp2.stat(full);
|
|
1098
|
+
if (stat6.isDirectory()) await walk3(full, rest);
|
|
1099
|
+
} catch {
|
|
1100
|
+
}
|
|
1101
|
+
}
|
|
1102
|
+
}
|
|
1103
|
+
}
|
|
1104
|
+
await walk3(base === "." ? "." : base, relPat);
|
|
1105
|
+
return [...results];
|
|
1106
|
+
}
|
|
1107
|
+
|
|
1108
|
+
// src/utils/json-repair.ts
|
|
1109
|
+
function completePartialObject(s) {
|
|
1110
|
+
if (!s.trim().startsWith("{")) return s;
|
|
1111
|
+
if (tryParse(s).ok) return s;
|
|
1112
|
+
return repairTruncated(s);
|
|
1113
|
+
}
|
|
1114
|
+
function repairTruncated(s) {
|
|
1115
|
+
const stack = [];
|
|
1116
|
+
let inString = false;
|
|
1117
|
+
let escaped = false;
|
|
1118
|
+
let sawKey = false;
|
|
1119
|
+
let prevSig = "";
|
|
1120
|
+
let contentEnd = 0;
|
|
1121
|
+
let stringBraceDepth = 0;
|
|
1122
|
+
for (let i = 0; i < s.length; i++) {
|
|
1123
|
+
const ch = expectDefined(s[i]);
|
|
1124
|
+
if (inString) {
|
|
1125
|
+
contentEnd = i + 1;
|
|
1126
|
+
if (escaped) {
|
|
1127
|
+
escaped = false;
|
|
1128
|
+
continue;
|
|
1129
|
+
}
|
|
1130
|
+
if (ch === "\\") {
|
|
1131
|
+
escaped = true;
|
|
1132
|
+
continue;
|
|
1133
|
+
}
|
|
1134
|
+
if (ch === '"') {
|
|
1135
|
+
inString = false;
|
|
1136
|
+
prevSig = '"';
|
|
1137
|
+
stringBraceDepth = 0;
|
|
1138
|
+
continue;
|
|
1139
|
+
}
|
|
1140
|
+
if (ch === "{") stringBraceDepth++;
|
|
1141
|
+
else if (ch === "}" && stringBraceDepth > 0) stringBraceDepth--;
|
|
1142
|
+
continue;
|
|
1143
|
+
}
|
|
1144
|
+
if (ch === " " || ch === " " || ch === "\n" || ch === "\r") continue;
|
|
1145
|
+
contentEnd = i + 1;
|
|
1146
|
+
if (ch === '"') {
|
|
1147
|
+
inString = true;
|
|
1148
|
+
sawKey = true;
|
|
1149
|
+
stringBraceDepth = 0;
|
|
1150
|
+
prevSig = '"';
|
|
1151
|
+
} else if (ch === "{" || ch === "[") {
|
|
1152
|
+
stack.push(ch);
|
|
1153
|
+
prevSig = ch;
|
|
1154
|
+
} else if (ch === "}" || ch === "]") {
|
|
1155
|
+
stack.pop();
|
|
1156
|
+
prevSig = ch;
|
|
1157
|
+
} else {
|
|
1158
|
+
prevSig = ch;
|
|
1159
|
+
}
|
|
1160
|
+
}
|
|
1161
|
+
if (!sawKey && !inString) return s;
|
|
1162
|
+
let result = s.slice(0, contentEnd);
|
|
1163
|
+
if (inString) {
|
|
1164
|
+
if (escaped) {
|
|
1165
|
+
result = result.slice(0, -1);
|
|
1166
|
+
} else if (endsWithInvalidEscape(result)) {
|
|
1167
|
+
result = result.slice(0, -2);
|
|
1168
|
+
}
|
|
1169
|
+
if (stringBraceDepth > 0) result += "}".repeat(stringBraceDepth);
|
|
1170
|
+
result += '"';
|
|
1171
|
+
} else if (prevSig === ":") {
|
|
1172
|
+
result += "null";
|
|
390
1173
|
}
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
1174
|
+
for (let k = stack.length - 1; k >= 0; k--) {
|
|
1175
|
+
result += stack[k] === "{" ? "}" : "]";
|
|
1176
|
+
}
|
|
1177
|
+
if (!tryParse(result).ok) {
|
|
1178
|
+
const patched = result.replace(/:(\s*)([}\]])/g, ":null$2");
|
|
1179
|
+
if (tryParse(patched).ok) result = patched;
|
|
1180
|
+
}
|
|
1181
|
+
return result;
|
|
395
1182
|
}
|
|
396
|
-
|
|
397
|
-
|
|
1183
|
+
var VALID_ESCAPE = /* @__PURE__ */ new Set(['"', "\\", "/", "b", "f", "n", "r", "t", "u"]);
|
|
1184
|
+
function endsWithInvalidEscape(str) {
|
|
1185
|
+
const last = str[str.length - 1];
|
|
1186
|
+
if (str[str.length - 2] !== "\\" || last === void 0) return false;
|
|
1187
|
+
if (VALID_ESCAPE.has(last)) return false;
|
|
1188
|
+
let backslashes = 0;
|
|
1189
|
+
for (let k = str.length - 2; k >= 0 && str[k] === "\\"; k--) backslashes++;
|
|
1190
|
+
return backslashes % 2 === 1;
|
|
398
1191
|
}
|
|
399
|
-
function
|
|
400
|
-
|
|
1192
|
+
function tryParse(s) {
|
|
1193
|
+
try {
|
|
1194
|
+
return { ok: true, value: JSON.parse(s) };
|
|
1195
|
+
} catch {
|
|
1196
|
+
return { ok: false };
|
|
1197
|
+
}
|
|
401
1198
|
}
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
1199
|
+
|
|
1200
|
+
// src/utils/merge-models-payload.ts
|
|
1201
|
+
function mergeModelsPayload(base, overlay) {
|
|
1202
|
+
const out = {};
|
|
1203
|
+
for (const [id, provider] of Object.entries(base)) {
|
|
1204
|
+
out[id] = cloneProvider(provider);
|
|
407
1205
|
}
|
|
408
|
-
|
|
1206
|
+
for (const [id, ovProvider] of Object.entries(overlay)) {
|
|
1207
|
+
const existing = out[id];
|
|
1208
|
+
out[id] = existing ? mergeProvider(existing, ovProvider) : cloneProvider(ovProvider);
|
|
1209
|
+
}
|
|
1210
|
+
return out;
|
|
409
1211
|
}
|
|
410
|
-
function
|
|
411
|
-
const
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
if (block.type === "tool_result") ids.add(block.tool_use_id);
|
|
1212
|
+
function mergeProvider(base, overlay) {
|
|
1213
|
+
const models = {};
|
|
1214
|
+
for (const [mid, m] of Object.entries(base.models ?? {})) {
|
|
1215
|
+
models[mid] = { ...m };
|
|
415
1216
|
}
|
|
416
|
-
|
|
1217
|
+
for (const [mid, ovModel] of Object.entries(overlay.models ?? {})) {
|
|
1218
|
+
const existing = models[mid];
|
|
1219
|
+
models[mid] = existing ? mergeModel(existing, ovModel) : { ...ovModel };
|
|
1220
|
+
}
|
|
1221
|
+
return {
|
|
1222
|
+
...base,
|
|
1223
|
+
// Overlay scalar fields win when explicitly provided; otherwise keep base.
|
|
1224
|
+
...stripUndefined({
|
|
1225
|
+
id: overlay.id,
|
|
1226
|
+
name: overlay.name,
|
|
1227
|
+
npm: overlay.npm,
|
|
1228
|
+
api: overlay.api,
|
|
1229
|
+
env: overlay.env,
|
|
1230
|
+
doc: overlay.doc
|
|
1231
|
+
}),
|
|
1232
|
+
models
|
|
1233
|
+
};
|
|
417
1234
|
}
|
|
418
|
-
function
|
|
419
|
-
|
|
1235
|
+
function mergeModel(base, overlay) {
|
|
1236
|
+
const merged = { ...base, ...overlay };
|
|
1237
|
+
if (base.limit || overlay.limit) {
|
|
1238
|
+
merged.limit = { ...base.limit, ...overlay.limit };
|
|
1239
|
+
}
|
|
1240
|
+
if (base.cost || overlay.cost) {
|
|
1241
|
+
merged.cost = { ...base.cost, ...overlay.cost };
|
|
1242
|
+
}
|
|
1243
|
+
if (base.modalities || overlay.modalities) {
|
|
1244
|
+
merged.modalities = { ...base.modalities, ...overlay.modalities };
|
|
1245
|
+
}
|
|
1246
|
+
return merged;
|
|
420
1247
|
}
|
|
421
|
-
function
|
|
422
|
-
|
|
423
|
-
const
|
|
424
|
-
|
|
425
|
-
return msg;
|
|
1248
|
+
function cloneProvider(p) {
|
|
1249
|
+
const models = {};
|
|
1250
|
+
for (const [mid, m] of Object.entries(p.models ?? {})) {
|
|
1251
|
+
models[mid] = { ...m };
|
|
426
1252
|
}
|
|
427
|
-
return { ...
|
|
1253
|
+
return { ...p, models };
|
|
428
1254
|
}
|
|
429
|
-
function
|
|
430
|
-
|
|
431
|
-
|
|
1255
|
+
function stripUndefined(obj) {
|
|
1256
|
+
const out = {};
|
|
1257
|
+
for (const [k, v] of Object.entries(obj)) {
|
|
1258
|
+
if (v !== void 0) out[k] = v;
|
|
1259
|
+
}
|
|
1260
|
+
return out;
|
|
432
1261
|
}
|
|
433
1262
|
|
|
434
1263
|
// src/storage/session-store.ts
|
|
@@ -446,11 +1275,34 @@ var DefaultSessionStore = class _DefaultSessionStore {
|
|
|
446
1275
|
dir;
|
|
447
1276
|
events;
|
|
448
1277
|
secretScrubber;
|
|
1278
|
+
/**
|
|
1279
|
+
* In-memory cache for load() results, keyed by session ID. The cache is
|
|
1280
|
+
* invalidated when the file's mtimeMs or size changes (indicating the
|
|
1281
|
+
* file was written to). This eliminates redundant full-file reads and
|
|
1282
|
+
* JSON parses when the same session is loaded multiple times within the
|
|
1283
|
+
* store's lifetime (e.g., webui session detail views, list() fallbacks).
|
|
1284
|
+
*
|
|
1285
|
+
* Max size is capped to prevent unbounded memory growth in long-running
|
|
1286
|
+
* processes. When the limit is reached, the oldest entry is evicted.
|
|
1287
|
+
*/
|
|
1288
|
+
_loadCache = /* @__PURE__ */ new Map();
|
|
1289
|
+
static LOAD_CACHE_MAX_ENTRIES = 50;
|
|
449
1290
|
constructor(opts) {
|
|
450
1291
|
this.dir = opts.dir;
|
|
451
1292
|
this.events = opts.events;
|
|
452
1293
|
this.secretScrubber = opts.secretScrubber;
|
|
453
1294
|
}
|
|
1295
|
+
/**
|
|
1296
|
+
* Clear the load() cache. Useful for testing or when the caller knows
|
|
1297
|
+
* the file has changed externally (e.g., another process wrote to it).
|
|
1298
|
+
*/
|
|
1299
|
+
clearLoadCache(sessionId) {
|
|
1300
|
+
if (sessionId !== void 0) {
|
|
1301
|
+
this._loadCache.delete(sessionId);
|
|
1302
|
+
} else {
|
|
1303
|
+
this._loadCache.clear();
|
|
1304
|
+
}
|
|
1305
|
+
}
|
|
454
1306
|
// ── Storage event helpers ───────────────────────────────────────────────────
|
|
455
1307
|
emitRead(sessionId, filePath, operation, outcome, durationMs, error) {
|
|
456
1308
|
this.events?.emit("storage.read", {
|
|
@@ -487,11 +1339,11 @@ var DefaultSessionStore = class _DefaultSessionStore {
|
|
|
487
1339
|
}
|
|
488
1340
|
/** Absolute path to the session index file. */
|
|
489
1341
|
get indexFile() {
|
|
490
|
-
return
|
|
1342
|
+
return path3.join(this.dir, "_index.jsonl");
|
|
491
1343
|
}
|
|
492
1344
|
/** Join session ID to its absolute path within the store directory. */
|
|
493
1345
|
sessionPath(id, ext) {
|
|
494
|
-
return
|
|
1346
|
+
return path3.join(this.dir, `${id}${ext}`);
|
|
495
1347
|
}
|
|
496
1348
|
/**
|
|
497
1349
|
* Ensure the directory implied by the session ID exists. When the ID
|
|
@@ -499,7 +1351,7 @@ var DefaultSessionStore = class _DefaultSessionStore {
|
|
|
499
1351
|
* subdirectory so sessions group naturally by day.
|
|
500
1352
|
*/
|
|
501
1353
|
async ensureShardDir(id) {
|
|
502
|
-
const dirPath =
|
|
1354
|
+
const dirPath = path3.dirname(path3.join(this.dir, id));
|
|
503
1355
|
await ensureDir(dirPath);
|
|
504
1356
|
return dirPath;
|
|
505
1357
|
}
|
|
@@ -507,15 +1359,15 @@ var DefaultSessionStore = class _DefaultSessionStore {
|
|
|
507
1359
|
const startedAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
508
1360
|
const id = meta.id && meta.id.length > 0 ? meta.id : generateSessionId(startedAt, meta.model ?? meta.provider);
|
|
509
1361
|
const shardDir = await this.ensureShardDir(id);
|
|
510
|
-
const file =
|
|
1362
|
+
const file = path3.join(shardDir, `${path3.basename(id)}.jsonl`);
|
|
511
1363
|
const t0 = Date.now();
|
|
512
1364
|
let handle;
|
|
513
1365
|
try {
|
|
514
|
-
handle = await
|
|
1366
|
+
handle = await fsp2.open(file, "a", 384);
|
|
515
1367
|
} catch (err) {
|
|
516
|
-
this.emitError(id, file, "create",
|
|
1368
|
+
this.emitError(id, file, "create", toErrorMessage(err), false);
|
|
517
1369
|
throw new Error(
|
|
518
|
-
`Failed to open session file: ${
|
|
1370
|
+
`Failed to open session file: ${toErrorMessage(err)}`,
|
|
519
1371
|
{ cause: err }
|
|
520
1372
|
);
|
|
521
1373
|
}
|
|
@@ -535,7 +1387,7 @@ var DefaultSessionStore = class _DefaultSessionStore {
|
|
|
535
1387
|
message: e instanceof Error ? e.message : String(e),
|
|
536
1388
|
timestamp: (/* @__PURE__ */ new Date()).toISOString()
|
|
537
1389
|
})));
|
|
538
|
-
this.emitError(id, file, "create",
|
|
1390
|
+
this.emitError(id, file, "create", toErrorMessage(err), true);
|
|
539
1391
|
throw err;
|
|
540
1392
|
}
|
|
541
1393
|
}
|
|
@@ -545,11 +1397,11 @@ var DefaultSessionStore = class _DefaultSessionStore {
|
|
|
545
1397
|
const data = await this.load(id);
|
|
546
1398
|
let handle;
|
|
547
1399
|
try {
|
|
548
|
-
handle = await
|
|
1400
|
+
handle = await fsp2.open(file, "a", 384);
|
|
549
1401
|
} catch (err) {
|
|
550
|
-
this.emitError(id, file, "resume",
|
|
1402
|
+
this.emitError(id, file, "resume", toErrorMessage(err), false);
|
|
551
1403
|
throw new Error(
|
|
552
|
-
`Failed to open session "${id}" for append: ${
|
|
1404
|
+
`Failed to open session "${id}" for append: ${toErrorMessage(err)}`,
|
|
553
1405
|
{ cause: err }
|
|
554
1406
|
);
|
|
555
1407
|
}
|
|
@@ -569,7 +1421,7 @@ var DefaultSessionStore = class _DefaultSessionStore {
|
|
|
569
1421
|
// Shard directory (sessions/<date>/) — must match create() so the
|
|
570
1422
|
// .summary.json sidecar lands next to the JSONL instead of the
|
|
571
1423
|
// sessions root (where summaryFor() would never find it).
|
|
572
|
-
dir:
|
|
1424
|
+
dir: path3.dirname(file),
|
|
573
1425
|
filePath: file,
|
|
574
1426
|
secretScrubber: this.secretScrubber,
|
|
575
1427
|
onClose: (s) => this.appendToIndex(s)
|
|
@@ -584,7 +1436,7 @@ var DefaultSessionStore = class _DefaultSessionStore {
|
|
|
584
1436
|
message: e instanceof Error ? e.message : String(e),
|
|
585
1437
|
timestamp: (/* @__PURE__ */ new Date()).toISOString()
|
|
586
1438
|
})));
|
|
587
|
-
this.emitError(id, file, "resume",
|
|
1439
|
+
this.emitError(id, file, "resume", toErrorMessage(err), true);
|
|
588
1440
|
throw err;
|
|
589
1441
|
}
|
|
590
1442
|
}
|
|
@@ -593,8 +1445,21 @@ var DefaultSessionStore = class _DefaultSessionStore {
|
|
|
593
1445
|
const t0 = Date.now();
|
|
594
1446
|
let outcome = "success";
|
|
595
1447
|
let errorMsg;
|
|
1448
|
+
let cacheHit = false;
|
|
596
1449
|
try {
|
|
597
|
-
|
|
1450
|
+
let stat6;
|
|
1451
|
+
try {
|
|
1452
|
+
const s = await fsp2.stat(file);
|
|
1453
|
+
stat6 = { mtimeMs: s.mtimeMs, size: s.size };
|
|
1454
|
+
} catch (err) {
|
|
1455
|
+
throw err;
|
|
1456
|
+
}
|
|
1457
|
+
const cached = this._loadCache.get(id);
|
|
1458
|
+
if (cached && cached.mtimeMs === stat6.mtimeMs && cached.size === stat6.size) {
|
|
1459
|
+
cacheHit = true;
|
|
1460
|
+
return cached.data;
|
|
1461
|
+
}
|
|
1462
|
+
const raw = await fsp2.readFile(file, "utf8");
|
|
598
1463
|
const lines = raw.split("\n").filter((l) => l.trim());
|
|
599
1464
|
const events = [];
|
|
600
1465
|
for (const line of lines) {
|
|
@@ -609,13 +1474,30 @@ var DefaultSessionStore = class _DefaultSessionStore {
|
|
|
609
1474
|
const meta = this.metaFromEvents(id, events);
|
|
610
1475
|
const { messages, usage } = this.replay(events, id);
|
|
611
1476
|
const toolCallEnds = extractToolCallEnds(events);
|
|
612
|
-
|
|
1477
|
+
const data = { metadata: meta, events, messages, usage, toolCallEnds };
|
|
1478
|
+
if (this._loadCache.size >= _DefaultSessionStore.LOAD_CACHE_MAX_ENTRIES) {
|
|
1479
|
+
const oldest = this._loadCache.keys().next().value;
|
|
1480
|
+
if (oldest !== void 0) {
|
|
1481
|
+
this._loadCache.delete(oldest);
|
|
1482
|
+
}
|
|
1483
|
+
}
|
|
1484
|
+
this._loadCache.set(id, { mtimeMs: stat6.mtimeMs, size: stat6.size, data });
|
|
1485
|
+
return data;
|
|
613
1486
|
} catch (err) {
|
|
614
1487
|
outcome = "failure";
|
|
615
|
-
errorMsg =
|
|
1488
|
+
errorMsg = toErrorMessage(err);
|
|
616
1489
|
throw err;
|
|
617
1490
|
} finally {
|
|
618
1491
|
this.emitRead(id, file, "load", outcome, Date.now() - t0, errorMsg);
|
|
1492
|
+
if (cacheHit) {
|
|
1493
|
+
this.events?.emit("storage.cache_hit", {
|
|
1494
|
+
sessionId: id,
|
|
1495
|
+
store: "session",
|
|
1496
|
+
filePath: file,
|
|
1497
|
+
operation: "load",
|
|
1498
|
+
durationMs: Date.now() - t0
|
|
1499
|
+
});
|
|
1500
|
+
}
|
|
619
1501
|
}
|
|
620
1502
|
}
|
|
621
1503
|
async list(limit = 20) {
|
|
@@ -659,7 +1541,7 @@ var DefaultSessionStore = class _DefaultSessionStore {
|
|
|
659
1541
|
try {
|
|
660
1542
|
await ensureDir(this.dir);
|
|
661
1543
|
const line = JSON.stringify(summary) + "\n";
|
|
662
|
-
await
|
|
1544
|
+
await fsp2.appendFile(this.indexFile, line, "utf8");
|
|
663
1545
|
this.indexAppendCount++;
|
|
664
1546
|
if (this.indexAppendCount >= _DefaultSessionStore.COMPACT_EVERY) {
|
|
665
1547
|
await this.compactIndex();
|
|
@@ -673,7 +1555,7 @@ var DefaultSessionStore = class _DefaultSessionStore {
|
|
|
673
1555
|
try {
|
|
674
1556
|
await ensureDir(this.dir);
|
|
675
1557
|
const line = JSON.stringify({ action: "delete", id }) + "\n";
|
|
676
|
-
await
|
|
1558
|
+
await fsp2.appendFile(this.indexFile, line, "utf8");
|
|
677
1559
|
this.indexAppendCount++;
|
|
678
1560
|
} catch {
|
|
679
1561
|
}
|
|
@@ -691,11 +1573,11 @@ var DefaultSessionStore = class _DefaultSessionStore {
|
|
|
691
1573
|
if (entries.length === 0) return;
|
|
692
1574
|
const tmp = `${this.indexFile}.compact.tmp`;
|
|
693
1575
|
const lines = entries.map((s) => JSON.stringify(s)).join("\n") + "\n";
|
|
694
|
-
await
|
|
695
|
-
await
|
|
1576
|
+
await fsp2.writeFile(tmp, lines, "utf8");
|
|
1577
|
+
await fsp2.rename(tmp, this.indexFile);
|
|
696
1578
|
} catch (err) {
|
|
697
1579
|
outcome = "failure";
|
|
698
|
-
errorMsg =
|
|
1580
|
+
errorMsg = toErrorMessage(err);
|
|
699
1581
|
} finally {
|
|
700
1582
|
this.emitWrite("~compact~", this.indexFile, "compact", outcome, Date.now() - t0, void 0, errorMsg);
|
|
701
1583
|
}
|
|
@@ -708,7 +1590,7 @@ var DefaultSessionStore = class _DefaultSessionStore {
|
|
|
708
1590
|
async readIndex() {
|
|
709
1591
|
let raw;
|
|
710
1592
|
try {
|
|
711
|
-
raw = await
|
|
1593
|
+
raw = await fsp2.readFile(this.indexFile, "utf8");
|
|
712
1594
|
} catch {
|
|
713
1595
|
return [];
|
|
714
1596
|
}
|
|
@@ -741,8 +1623,8 @@ var DefaultSessionStore = class _DefaultSessionStore {
|
|
|
741
1623
|
const valid = summaries.filter((s) => s !== null);
|
|
742
1624
|
const tmp = `${this.indexFile}.tmp`;
|
|
743
1625
|
const lines = valid.map((s) => JSON.stringify(s)).join("\n") + "\n";
|
|
744
|
-
await
|
|
745
|
-
await
|
|
1626
|
+
await fsp2.writeFile(tmp, lines, "utf8");
|
|
1627
|
+
await fsp2.rename(tmp, this.indexFile);
|
|
746
1628
|
return valid.length;
|
|
747
1629
|
}
|
|
748
1630
|
/** Recursively collect session IDs from date-shard subdirectories.
|
|
@@ -753,7 +1635,7 @@ var DefaultSessionStore = class _DefaultSessionStore {
|
|
|
753
1635
|
const ids = [];
|
|
754
1636
|
let entries;
|
|
755
1637
|
try {
|
|
756
|
-
entries = await
|
|
1638
|
+
entries = await fsp2.readdir(dir, { withFileTypes: true });
|
|
757
1639
|
} catch {
|
|
758
1640
|
return ids;
|
|
759
1641
|
}
|
|
@@ -763,7 +1645,7 @@ var DefaultSessionStore = class _DefaultSessionStore {
|
|
|
763
1645
|
continue;
|
|
764
1646
|
if (entry.isDirectory()) {
|
|
765
1647
|
const childPrefix = depth === 0 ? entry.name : `${prefix}/${entry.name}`;
|
|
766
|
-
ids.push(...await this.collectSessionIds(
|
|
1648
|
+
ids.push(...await this.collectSessionIds(path3.join(dir, entry.name), childPrefix, depth + 1));
|
|
767
1649
|
} else if (entry.isFile() && entry.name.endsWith(".jsonl")) {
|
|
768
1650
|
if (entry.name === "_index.jsonl") continue;
|
|
769
1651
|
const base = entry.name.replace(/\.jsonl$/, "");
|
|
@@ -778,15 +1660,15 @@ var DefaultSessionStore = class _DefaultSessionStore {
|
|
|
778
1660
|
let outcome = "success";
|
|
779
1661
|
let errorMsg;
|
|
780
1662
|
try {
|
|
781
|
-
const raw = await
|
|
1663
|
+
const raw = await fsp2.readFile(manifest, "utf8");
|
|
782
1664
|
this.emitRead(id, manifest, "summary", "success", Date.now() - t0);
|
|
783
1665
|
return JSON.parse(raw);
|
|
784
1666
|
} catch {
|
|
785
1667
|
const full = this.sessionPath(id, ".jsonl");
|
|
786
|
-
const stat6 = await
|
|
1668
|
+
const stat6 = await fsp2.stat(full);
|
|
787
1669
|
const summary = await this.summarize(id, stat6.mtime.toISOString());
|
|
788
1670
|
await atomicWrite(manifest, JSON.stringify(summary), { mode: 384 }).catch((err) => {
|
|
789
|
-
const msg =
|
|
1671
|
+
const msg = toErrorMessage(err);
|
|
790
1672
|
this.emitError(id, manifest, "summary_fallback", msg, true);
|
|
791
1673
|
console.warn(JSON.stringify({
|
|
792
1674
|
level: "warn",
|
|
@@ -814,14 +1696,14 @@ var DefaultSessionStore = class _DefaultSessionStore {
|
|
|
814
1696
|
async deleteSession(id) {
|
|
815
1697
|
const jsonlPath = this.sessionPath(id, ".jsonl");
|
|
816
1698
|
const summaryPath = this.sessionPath(id, ".summary.json");
|
|
817
|
-
const shardDir =
|
|
818
|
-
const base =
|
|
819
|
-
const sessDir =
|
|
1699
|
+
const shardDir = path3.dirname(path3.join(this.dir, id));
|
|
1700
|
+
const base = path3.basename(id);
|
|
1701
|
+
const sessDir = path3.join(shardDir, base);
|
|
820
1702
|
const deletions = [
|
|
821
|
-
|
|
822
|
-
|
|
823
|
-
|
|
824
|
-
|
|
1703
|
+
fsp2.unlink(jsonlPath),
|
|
1704
|
+
fsp2.unlink(summaryPath),
|
|
1705
|
+
fsp2.unlink(path3.join(shardDir, `${base}.plan.json`)),
|
|
1706
|
+
fsp2.unlink(path3.join(shardDir, `${base}.todos.json`))
|
|
825
1707
|
];
|
|
826
1708
|
const results = await Promise.allSettled(deletions);
|
|
827
1709
|
for (const r of results) {
|
|
@@ -838,12 +1720,12 @@ var DefaultSessionStore = class _DefaultSessionStore {
|
|
|
838
1720
|
}
|
|
839
1721
|
}
|
|
840
1722
|
}
|
|
841
|
-
await
|
|
1723
|
+
await fsp2.rm(sessDir, { recursive: true, force: true }).catch((err) => {
|
|
842
1724
|
console.warn(JSON.stringify({
|
|
843
1725
|
level: "warn",
|
|
844
1726
|
event: "session_store.rmdir_failed",
|
|
845
1727
|
sessionId: id,
|
|
846
|
-
message:
|
|
1728
|
+
message: toErrorMessage(err),
|
|
847
1729
|
timestamp: (/* @__PURE__ */ new Date()).toISOString()
|
|
848
1730
|
}));
|
|
849
1731
|
});
|
|
@@ -857,16 +1739,16 @@ var DefaultSessionStore = class _DefaultSessionStore {
|
|
|
857
1739
|
let deleted = 0;
|
|
858
1740
|
let activeSessionId = null;
|
|
859
1741
|
try {
|
|
860
|
-
const raw = await
|
|
1742
|
+
const raw = await fsp2.readFile(path3.join(this.dir, "active.json"), "utf8");
|
|
861
1743
|
const active = JSON.parse(raw);
|
|
862
1744
|
activeSessionId = active.sessionId ?? null;
|
|
863
1745
|
} catch {
|
|
864
1746
|
}
|
|
865
1747
|
const isPrunableJsonl = (name) => name.endsWith(".jsonl") && name !== "_index.jsonl" && name !== "_mailbox.jsonl" && !name.endsWith(".replay.jsonl") && !name.endsWith(".audit.jsonl");
|
|
866
1748
|
const pruneFile = async (dir, name, prefix) => {
|
|
867
|
-
const jsonlPath =
|
|
1749
|
+
const jsonlPath = path3.join(dir, name);
|
|
868
1750
|
try {
|
|
869
|
-
const stat6 = await
|
|
1751
|
+
const stat6 = await fsp2.stat(jsonlPath);
|
|
870
1752
|
if (stat6.mtimeMs >= cutoff) return;
|
|
871
1753
|
} catch {
|
|
872
1754
|
return;
|
|
@@ -877,15 +1759,15 @@ var DefaultSessionStore = class _DefaultSessionStore {
|
|
|
877
1759
|
await this.deleteSession(id);
|
|
878
1760
|
deleted++;
|
|
879
1761
|
};
|
|
880
|
-
const entries = await
|
|
1762
|
+
const entries = await fsp2.readdir(this.dir, { withFileTypes: true }).catch(() => []);
|
|
881
1763
|
for (const entry of entries) {
|
|
882
1764
|
if (entry.isFile()) {
|
|
883
1765
|
if (isPrunableJsonl(entry.name)) await pruneFile(this.dir, entry.name, "");
|
|
884
1766
|
continue;
|
|
885
1767
|
}
|
|
886
1768
|
if (!entry.isDirectory()) continue;
|
|
887
|
-
const dateDir =
|
|
888
|
-
const files = await
|
|
1769
|
+
const dateDir = path3.join(this.dir, entry.name);
|
|
1770
|
+
const files = await fsp2.readdir(dateDir, { withFileTypes: true }).catch(() => []);
|
|
889
1771
|
for (const file of files) {
|
|
890
1772
|
if (!file.isFile() || !isPrunableJsonl(file.name)) continue;
|
|
891
1773
|
await pruneFile(dateDir, file.name, entry.name);
|
|
@@ -896,11 +1778,11 @@ var DefaultSessionStore = class _DefaultSessionStore {
|
|
|
896
1778
|
}
|
|
897
1779
|
for (const entry of entries) {
|
|
898
1780
|
if (!entry.isDirectory()) continue;
|
|
899
|
-
const dateDir =
|
|
1781
|
+
const dateDir = path3.join(this.dir, entry.name);
|
|
900
1782
|
try {
|
|
901
|
-
const remaining = await
|
|
1783
|
+
const remaining = await fsp2.readdir(dateDir);
|
|
902
1784
|
if (remaining.length === 0) {
|
|
903
|
-
await
|
|
1785
|
+
await fsp2.rmdir(dateDir).catch(() => void 0);
|
|
904
1786
|
}
|
|
905
1787
|
} catch {
|
|
906
1788
|
}
|
|
@@ -919,8 +1801,8 @@ var DefaultSessionStore = class _DefaultSessionStore {
|
|
|
919
1801
|
provider: "unknown"
|
|
920
1802
|
})}
|
|
921
1803
|
`;
|
|
922
|
-
await
|
|
923
|
-
await
|
|
1804
|
+
await fsp2.writeFile(file, record, "utf8");
|
|
1805
|
+
await fsp2.unlink(meta).catch(() => void 0);
|
|
924
1806
|
}
|
|
925
1807
|
async summarize(id, mtime) {
|
|
926
1808
|
try {
|
|
@@ -1071,7 +1953,7 @@ var FileSessionWriter = class _FileSessionWriter {
|
|
|
1071
1953
|
this.meta = meta;
|
|
1072
1954
|
this.events = events;
|
|
1073
1955
|
this.resumed = opts.resumed ?? false;
|
|
1074
|
-
this.manifestFile = opts.dir ?
|
|
1956
|
+
this.manifestFile = opts.dir ? path3.join(opts.dir, `${path3.basename(id)}.summary.json`) : "";
|
|
1075
1957
|
this.filePath = opts.filePath ?? "";
|
|
1076
1958
|
this.secretScrubber = opts.secretScrubber;
|
|
1077
1959
|
this.onCloseCb = opts.onClose;
|
|
@@ -1278,7 +2160,7 @@ var FileSessionWriter = class _FileSessionWriter {
|
|
|
1278
2160
|
await this.enqueueWrite(batch);
|
|
1279
2161
|
} catch (err) {
|
|
1280
2162
|
outcome = "failure";
|
|
1281
|
-
errorMsg =
|
|
2163
|
+
errorMsg = toErrorMessage(err);
|
|
1282
2164
|
this.appendFailCount += eventCount;
|
|
1283
2165
|
const now = Date.now();
|
|
1284
2166
|
if (now - this.lastAppendWarnAt > 5e3) {
|
|
@@ -1286,7 +2168,7 @@ var FileSessionWriter = class _FileSessionWriter {
|
|
|
1286
2168
|
const tail = suppressed > 0 ? ` (+${suppressed} suppressed)` : "";
|
|
1287
2169
|
console.warn(
|
|
1288
2170
|
"[session] flush failed:",
|
|
1289
|
-
|
|
2171
|
+
toErrorMessage(err),
|
|
1290
2172
|
tail
|
|
1291
2173
|
);
|
|
1292
2174
|
this.lastAppendWarnAt = now;
|
|
@@ -1376,7 +2258,7 @@ var FileSessionWriter = class _FileSessionWriter {
|
|
|
1376
2258
|
await atomicWrite(this.manifestFile, JSON.stringify(this.summary), { mode: 384 });
|
|
1377
2259
|
} catch (err) {
|
|
1378
2260
|
outcome = "failure";
|
|
1379
|
-
errorMsg =
|
|
2261
|
+
errorMsg = toErrorMessage(err);
|
|
1380
2262
|
} finally {
|
|
1381
2263
|
this.events?.emit("storage.write", {
|
|
1382
2264
|
sessionId: this.id,
|
|
@@ -1397,7 +2279,7 @@ var FileSessionWriter = class _FileSessionWriter {
|
|
|
1397
2279
|
await this.onCloseCb?.(this.summary);
|
|
1398
2280
|
} catch (err) {
|
|
1399
2281
|
idxOutcome = "failure";
|
|
1400
|
-
idxError =
|
|
2282
|
+
idxError = toErrorMessage(err);
|
|
1401
2283
|
} finally {
|
|
1402
2284
|
this.events?.emit("storage.write", {
|
|
1403
2285
|
sessionId: this.summary.id,
|
|
@@ -1450,7 +2332,7 @@ var FileSessionWriter = class _FileSessionWriter {
|
|
|
1450
2332
|
}
|
|
1451
2333
|
await this.flushBuffer();
|
|
1452
2334
|
await this.writeChain;
|
|
1453
|
-
const raw = await
|
|
2335
|
+
const raw = await fsp2.readFile(this.filePath, "utf8");
|
|
1454
2336
|
const lines = raw.split("\n");
|
|
1455
2337
|
const kept = [];
|
|
1456
2338
|
let removedCount = 0;
|
|
@@ -1488,13 +2370,13 @@ var FileSessionWriter = class _FileSessionWriter {
|
|
|
1488
2370
|
}
|
|
1489
2371
|
const truncated = kept.join("\n");
|
|
1490
2372
|
const tmpPath = `${this.filePath}.rewind.tmp`;
|
|
1491
|
-
await
|
|
2373
|
+
await fsp2.writeFile(tmpPath, truncated + "\n", "utf8");
|
|
1492
2374
|
try {
|
|
1493
2375
|
await this.handle.close();
|
|
1494
|
-
await
|
|
1495
|
-
this.handle = await
|
|
2376
|
+
await fsp2.rename(tmpPath, this.filePath);
|
|
2377
|
+
this.handle = await fsp2.open(this.filePath, "a", 384);
|
|
1496
2378
|
} catch (err) {
|
|
1497
|
-
await
|
|
2379
|
+
await fsp2.unlink(tmpPath).catch(() => void 0);
|
|
1498
2380
|
throw err;
|
|
1499
2381
|
}
|
|
1500
2382
|
await this.append({
|
|
@@ -1526,7 +2408,7 @@ var FileSessionWriter = class _FileSessionWriter {
|
|
|
1526
2408
|
provider: this.meta.provider ?? "unknown"
|
|
1527
2409
|
})}
|
|
1528
2410
|
`;
|
|
1529
|
-
await
|
|
2411
|
+
await fsp2.writeFile(this.filePath, record, "utf8");
|
|
1530
2412
|
}
|
|
1531
2413
|
/**
|
|
1532
2414
|
* Idea #1 — write an in-flight marker. The agent loop should call
|
|
@@ -1575,7 +2457,7 @@ var QueueStore = class {
|
|
|
1575
2457
|
events;
|
|
1576
2458
|
traceId;
|
|
1577
2459
|
constructor(opts) {
|
|
1578
|
-
this.file =
|
|
2460
|
+
this.file = path3.join(opts.dir, "queue.json");
|
|
1579
2461
|
this.events = opts.events;
|
|
1580
2462
|
this.traceId = opts.traceId;
|
|
1581
2463
|
}
|
|
@@ -1603,7 +2485,7 @@ var QueueStore = class {
|
|
|
1603
2485
|
filePath: this.file,
|
|
1604
2486
|
operation: "write",
|
|
1605
2487
|
outcome: "failure",
|
|
1606
|
-
error:
|
|
2488
|
+
error: toErrorMessage(err),
|
|
1607
2489
|
recoverable: false,
|
|
1608
2490
|
...this.traceId !== void 0 && { traceId: this.traceId }
|
|
1609
2491
|
});
|
|
@@ -1611,7 +2493,7 @@ var QueueStore = class {
|
|
|
1611
2493
|
level: "warn",
|
|
1612
2494
|
event: "queue_store.write_failed",
|
|
1613
2495
|
path: this.file,
|
|
1614
|
-
message:
|
|
2496
|
+
message: toErrorMessage(err),
|
|
1615
2497
|
timestamp: (/* @__PURE__ */ new Date()).toISOString()
|
|
1616
2498
|
}));
|
|
1617
2499
|
}
|
|
@@ -1620,7 +2502,7 @@ var QueueStore = class {
|
|
|
1620
2502
|
const t0 = Date.now();
|
|
1621
2503
|
let raw;
|
|
1622
2504
|
try {
|
|
1623
|
-
raw = await
|
|
2505
|
+
raw = await fsp2.readFile(this.file, "utf8");
|
|
1624
2506
|
} catch (err) {
|
|
1625
2507
|
const code = err.code;
|
|
1626
2508
|
if (code === "ENOENT") {
|
|
@@ -1641,7 +2523,7 @@ var QueueStore = class {
|
|
|
1641
2523
|
filePath: this.file,
|
|
1642
2524
|
operation: "read",
|
|
1643
2525
|
outcome: "failure",
|
|
1644
|
-
error:
|
|
2526
|
+
error: toErrorMessage(err),
|
|
1645
2527
|
recoverable: true,
|
|
1646
2528
|
...this.traceId !== void 0 && { traceId: this.traceId }
|
|
1647
2529
|
});
|
|
@@ -1649,7 +2531,7 @@ var QueueStore = class {
|
|
|
1649
2531
|
level: "warn",
|
|
1650
2532
|
event: "queue_store.read_failed",
|
|
1651
2533
|
path: this.file,
|
|
1652
|
-
message:
|
|
2534
|
+
message: toErrorMessage(err),
|
|
1653
2535
|
timestamp: (/* @__PURE__ */ new Date()).toISOString()
|
|
1654
2536
|
}));
|
|
1655
2537
|
return [];
|
|
@@ -1701,7 +2583,7 @@ var QueueStore = class {
|
|
|
1701
2583
|
async clear() {
|
|
1702
2584
|
const t0 = Date.now();
|
|
1703
2585
|
try {
|
|
1704
|
-
await
|
|
2586
|
+
await fsp2.unlink(this.file);
|
|
1705
2587
|
this.events?.emit("storage.write", {
|
|
1706
2588
|
sessionId: this.traceId ?? "~boot~",
|
|
1707
2589
|
store: "queue",
|
|
@@ -1720,7 +2602,7 @@ var QueueStore = class {
|
|
|
1720
2602
|
filePath: this.file,
|
|
1721
2603
|
operation: "clear",
|
|
1722
2604
|
outcome: "failure",
|
|
1723
|
-
error:
|
|
2605
|
+
error: toErrorMessage(err),
|
|
1724
2606
|
recoverable: true,
|
|
1725
2607
|
...this.traceId !== void 0 && { traceId: this.traceId }
|
|
1726
2608
|
});
|
|
@@ -1761,8 +2643,8 @@ var DefaultAttachmentStore = class {
|
|
|
1761
2643
|
let spooledPath;
|
|
1762
2644
|
let data = input.data;
|
|
1763
2645
|
if (this.spoolDir && bytes >= this.spoolThreshold) {
|
|
1764
|
-
await
|
|
1765
|
-
spooledPath =
|
|
2646
|
+
await fsp2.mkdir(this.spoolDir, { recursive: true });
|
|
2647
|
+
spooledPath = path3.join(this.spoolDir, `${id}.bin`);
|
|
1766
2648
|
await atomicWrite(spooledPath, input.data, {
|
|
1767
2649
|
encoding: input.kind === "image" ? "base64" : "utf8"
|
|
1768
2650
|
});
|
|
@@ -1824,7 +2706,7 @@ var DefaultAttachmentStore = class {
|
|
|
1824
2706
|
for (const att of this.items.values()) {
|
|
1825
2707
|
if (att.path) toDelete.push(att.path);
|
|
1826
2708
|
}
|
|
1827
|
-
await Promise.all(toDelete.map((p) =>
|
|
2709
|
+
await Promise.all(toDelete.map((p) => fsp2.unlink(p).catch(() => void 0)));
|
|
1828
2710
|
}
|
|
1829
2711
|
this.items.clear();
|
|
1830
2712
|
this.refs.length = 0;
|
|
@@ -1832,7 +2714,7 @@ var DefaultAttachmentStore = class {
|
|
|
1832
2714
|
}
|
|
1833
2715
|
async toBlock(att) {
|
|
1834
2716
|
if (att.kind === "image") {
|
|
1835
|
-
const data = att.data ?? (att.path ? await
|
|
2717
|
+
const data = att.data ?? (att.path ? await fsp2.readFile(att.path, { encoding: "base64" }) : "");
|
|
1836
2718
|
return {
|
|
1837
2719
|
type: "image",
|
|
1838
2720
|
source: {
|
|
@@ -1842,7 +2724,7 @@ var DefaultAttachmentStore = class {
|
|
|
1842
2724
|
}
|
|
1843
2725
|
};
|
|
1844
2726
|
}
|
|
1845
|
-
const raw = att.data ?? (att.path ? await
|
|
2727
|
+
const raw = att.data ?? (att.path ? await fsp2.readFile(att.path, "utf8") : "");
|
|
1846
2728
|
const label = att.meta.filename ? `<file path="${att.meta.filename}">` : "<pasted>";
|
|
1847
2729
|
const close = att.meta.filename ? "</file>" : "</pasted>";
|
|
1848
2730
|
return { type: "text", text: `${label}
|
|
@@ -1974,10 +2856,10 @@ var FileMemoryBackend = class {
|
|
|
1974
2856
|
}
|
|
1975
2857
|
async remember(scope, entry, filePath) {
|
|
1976
2858
|
const file = this.resolveFile(filePath, scope);
|
|
1977
|
-
await ensureDir(
|
|
2859
|
+
await ensureDir(path3.dirname(file));
|
|
1978
2860
|
let existing = "";
|
|
1979
2861
|
try {
|
|
1980
|
-
existing = await
|
|
2862
|
+
existing = await fsp2.readFile(file, "utf8");
|
|
1981
2863
|
} catch {
|
|
1982
2864
|
}
|
|
1983
2865
|
const id = `mem_${Date.now()}_${randomUUID().slice(0, 8)}`;
|
|
@@ -1994,7 +2876,7 @@ ${line}`;
|
|
|
1994
2876
|
return withFileLock(file, async () => {
|
|
1995
2877
|
let existing;
|
|
1996
2878
|
try {
|
|
1997
|
-
existing = await
|
|
2879
|
+
existing = await fsp2.readFile(file, "utf8");
|
|
1998
2880
|
} catch {
|
|
1999
2881
|
return 0;
|
|
2000
2882
|
}
|
|
@@ -2030,7 +2912,7 @@ ${line}`;
|
|
|
2030
2912
|
async readAll(scope, filePath) {
|
|
2031
2913
|
const file = this.resolveFile(filePath, scope);
|
|
2032
2914
|
try {
|
|
2033
|
-
return await
|
|
2915
|
+
return await fsp2.readFile(file, "utf8");
|
|
2034
2916
|
} catch {
|
|
2035
2917
|
return "";
|
|
2036
2918
|
}
|
|
@@ -2065,7 +2947,7 @@ ${line}`;
|
|
|
2065
2947
|
const file = this.resolveFile(filePath, scope);
|
|
2066
2948
|
let existing;
|
|
2067
2949
|
try {
|
|
2068
|
-
existing = await
|
|
2950
|
+
existing = await fsp2.readFile(file, "utf8");
|
|
2069
2951
|
} catch {
|
|
2070
2952
|
return 0;
|
|
2071
2953
|
}
|
|
@@ -2085,7 +2967,7 @@ ${line}`;
|
|
|
2085
2967
|
const next = lines.join("\n");
|
|
2086
2968
|
const backup = `${file}.bak.${Date.now()}`;
|
|
2087
2969
|
try {
|
|
2088
|
-
await
|
|
2970
|
+
await fsp2.copyFile(file, backup);
|
|
2089
2971
|
} catch {
|
|
2090
2972
|
}
|
|
2091
2973
|
try {
|
|
@@ -2202,7 +3084,7 @@ ${body.trim()}`);
|
|
|
2202
3084
|
operation: "readAll",
|
|
2203
3085
|
outcome: "failure",
|
|
2204
3086
|
durationMs: dur,
|
|
2205
|
-
error:
|
|
3087
|
+
error: toErrorMessage(err),
|
|
2206
3088
|
...this.traceId !== void 0 && { traceId: this.traceId }
|
|
2207
3089
|
});
|
|
2208
3090
|
throw err;
|
|
@@ -2235,7 +3117,7 @@ ${body.trim()}`);
|
|
|
2235
3117
|
operation: "read",
|
|
2236
3118
|
outcome: "failure",
|
|
2237
3119
|
durationMs: dur,
|
|
2238
|
-
error:
|
|
3120
|
+
error: toErrorMessage(err),
|
|
2239
3121
|
...this.traceId !== void 0 && { traceId: this.traceId }
|
|
2240
3122
|
});
|
|
2241
3123
|
throw err;
|
|
@@ -2288,7 +3170,7 @@ ${body.trim()}`);
|
|
|
2288
3170
|
operation: "remember",
|
|
2289
3171
|
outcome: "failure",
|
|
2290
3172
|
durationMs: dur,
|
|
2291
|
-
error:
|
|
3173
|
+
error: toErrorMessage(err),
|
|
2292
3174
|
...this.traceId !== void 0 && { traceId: this.traceId }
|
|
2293
3175
|
});
|
|
2294
3176
|
throw err;
|
|
@@ -2444,7 +3326,7 @@ ${body.trim()}`);
|
|
|
2444
3326
|
operation: "forget",
|
|
2445
3327
|
outcome: "failure",
|
|
2446
3328
|
durationMs: dur,
|
|
2447
|
-
error:
|
|
3329
|
+
error: toErrorMessage(err),
|
|
2448
3330
|
...this.traceId !== void 0 && { traceId: this.traceId }
|
|
2449
3331
|
});
|
|
2450
3332
|
throw err;
|
|
@@ -2486,7 +3368,7 @@ ${body.trim()}`);
|
|
|
2486
3368
|
operation: "consolidate",
|
|
2487
3369
|
outcome: "failure",
|
|
2488
3370
|
durationMs: dur,
|
|
2489
|
-
error:
|
|
3371
|
+
error: toErrorMessage(err),
|
|
2490
3372
|
...this.traceId !== void 0 && { traceId: this.traceId }
|
|
2491
3373
|
});
|
|
2492
3374
|
throw err;
|
|
@@ -2526,7 +3408,7 @@ ${body.trim()}`);
|
|
|
2526
3408
|
operation: "clear",
|
|
2527
3409
|
outcome: "failure",
|
|
2528
3410
|
durationMs: dur,
|
|
2529
|
-
error:
|
|
3411
|
+
error: toErrorMessage(err),
|
|
2530
3412
|
...this.traceId !== void 0 && { traceId: this.traceId }
|
|
2531
3413
|
});
|
|
2532
3414
|
throw err;
|
|
@@ -2562,7 +3444,7 @@ ${body.trim()}`);
|
|
|
2562
3444
|
operation: "clear",
|
|
2563
3445
|
outcome: "failure",
|
|
2564
3446
|
durationMs: dur,
|
|
2565
|
-
error:
|
|
3447
|
+
error: toErrorMessage(err),
|
|
2566
3448
|
...this.traceId !== void 0 && { traceId: this.traceId }
|
|
2567
3449
|
});
|
|
2568
3450
|
throw err;
|
|
@@ -2722,7 +3604,7 @@ var AgentError = class extends WrongStackError {
|
|
|
2722
3604
|
};
|
|
2723
3605
|
function toWrongStackError(err, code = ERROR_CODES.AGENT_RUN_FAILED) {
|
|
2724
3606
|
if (err instanceof WrongStackError) return err;
|
|
2725
|
-
const message =
|
|
3607
|
+
const message = toErrorMessage(err);
|
|
2726
3608
|
return new AgentError({
|
|
2727
3609
|
message,
|
|
2728
3610
|
code: code === "UNKNOWN" ? ERROR_CODES.AGENT_RUN_FAILED : code,
|
|
@@ -2806,7 +3688,7 @@ var DefaultConfigStore = class {
|
|
|
2806
3688
|
console.error(JSON.stringify({
|
|
2807
3689
|
level: "error",
|
|
2808
3690
|
event: "config_store.watcher_threw",
|
|
2809
|
-
message:
|
|
3691
|
+
message: toErrorMessage(err),
|
|
2810
3692
|
timestamp: (/* @__PURE__ */ new Date()).toISOString()
|
|
2811
3693
|
}));
|
|
2812
3694
|
}
|
|
@@ -2832,80 +3714,24 @@ function deepFreeze(obj) {
|
|
|
2832
3714
|
}
|
|
2833
3715
|
|
|
2834
3716
|
// src/types/secret-vault.ts
|
|
2835
|
-
var
|
|
2836
|
-
|
|
2837
|
-
|
|
2838
|
-
init_atomic_write();
|
|
2839
|
-
|
|
2840
|
-
// src/utils/deep-merge.ts
|
|
2841
|
-
var FORBIDDEN_PROTO_KEYS = /* @__PURE__ */ new Set([
|
|
2842
|
-
"__proto__",
|
|
2843
|
-
"constructor",
|
|
2844
|
-
"prototype",
|
|
2845
|
-
"__defineGetter__",
|
|
2846
|
-
"__defineSetter__",
|
|
2847
|
-
"__lookupGetter__",
|
|
2848
|
-
"__lookupSetter__"
|
|
2849
|
-
]);
|
|
2850
|
-
function isPrimitiveArray(a) {
|
|
2851
|
-
return a.every((v) => v === null || typeof v !== "object" && typeof v !== "function");
|
|
2852
|
-
}
|
|
2853
|
-
function deepMerge(base, patch, options = {}) {
|
|
2854
|
-
const {
|
|
2855
|
-
conflictResolution = "prefer-patch",
|
|
2856
|
-
arrayMode = "replace",
|
|
2857
|
-
protectProto = true,
|
|
2858
|
-
onNonPrimitiveArrayReplace
|
|
2859
|
-
} = options;
|
|
2860
|
-
if (typeof base !== "object" || base === null) {
|
|
2861
|
-
return conflictResolution === "prefer-patch" ? patch : base;
|
|
2862
|
-
}
|
|
2863
|
-
if (typeof patch !== "object" || patch === null) {
|
|
2864
|
-
return conflictResolution === "prefer-patch" ? patch : base;
|
|
2865
|
-
}
|
|
2866
|
-
if (Array.isArray(base) && Array.isArray(patch)) {
|
|
2867
|
-
if (arrayMode === "concat-primitives" && isPrimitiveArray(base) && isPrimitiveArray(patch)) {
|
|
2868
|
-
return [.../* @__PURE__ */ new Set([...base, ...patch])];
|
|
2869
|
-
}
|
|
2870
|
-
return conflictResolution === "prefer-patch" ? patch : base;
|
|
2871
|
-
}
|
|
2872
|
-
if (Array.isArray(base) || Array.isArray(patch)) {
|
|
2873
|
-
return conflictResolution === "prefer-patch" ? patch : base;
|
|
2874
|
-
}
|
|
2875
|
-
const baseObj = base;
|
|
2876
|
-
const patchObj = patch;
|
|
2877
|
-
const out = { ...baseObj };
|
|
2878
|
-
for (const [k, v] of Object.entries(patchObj)) {
|
|
2879
|
-
if (protectProto && FORBIDDEN_PROTO_KEYS.has(k)) continue;
|
|
2880
|
-
const existing = out[k];
|
|
2881
|
-
if (v !== null && typeof v === "object" && !Array.isArray(v) && existing !== null && typeof existing === "object" && !Array.isArray(existing)) {
|
|
2882
|
-
out[k] = deepMerge(existing, v, options);
|
|
2883
|
-
} else if (Array.isArray(v) && Array.isArray(existing)) {
|
|
2884
|
-
if (onNonPrimitiveArrayReplace && !isPrimitiveArray(v)) {
|
|
2885
|
-
onNonPrimitiveArrayReplace(k, existing.length, v.length);
|
|
2886
|
-
}
|
|
2887
|
-
out[k] = deepMerge(existing, v, options);
|
|
2888
|
-
} else if (v !== void 0) {
|
|
2889
|
-
if (onNonPrimitiveArrayReplace && Array.isArray(v) && !isPrimitiveArray(v)) {
|
|
2890
|
-
const existingLen = Array.isArray(existing) ? existing.length : 0;
|
|
2891
|
-
onNonPrimitiveArrayReplace(k, existingLen, v.length);
|
|
2892
|
-
}
|
|
2893
|
-
out[k] = v;
|
|
2894
|
-
}
|
|
2895
|
-
}
|
|
2896
|
-
return out;
|
|
3717
|
+
var ENCRYPTED_PREFIX_PATTERN = /^enc:v(\d+):/;
|
|
3718
|
+
function encryptedPrefixForVersion(version) {
|
|
3719
|
+
return `enc:v${version}:`;
|
|
2897
3720
|
}
|
|
2898
3721
|
|
|
2899
3722
|
// src/security/secret-vault.ts
|
|
3723
|
+
init_atomic_write();
|
|
2900
3724
|
var KEY_BYTES = 32;
|
|
2901
3725
|
var IV_BYTES = 12;
|
|
2902
3726
|
var TAG_BYTES = 16;
|
|
2903
3727
|
var ALGO = "aes-256-gcm";
|
|
2904
3728
|
var KEY_FILE_MODE = 384;
|
|
3729
|
+
var KEY_FILE_MAGIC = Buffer.from("WSKV", "ascii");
|
|
3730
|
+
var VERSIONED_KEY_FILE_SIZE = KEY_FILE_MAGIC.length + 1 + KEY_BYTES;
|
|
2905
3731
|
function checkKeyFilePermissions(keyFile) {
|
|
2906
3732
|
if (process.platform === "win32") return;
|
|
2907
3733
|
try {
|
|
2908
|
-
const stat6 =
|
|
3734
|
+
const stat6 = fs4.statSync(keyFile);
|
|
2909
3735
|
const actualMode = stat6.mode & 511;
|
|
2910
3736
|
if (actualMode !== KEY_FILE_MODE) {
|
|
2911
3737
|
console.warn(JSON.stringify({
|
|
@@ -2924,11 +3750,17 @@ function checkKeyFilePermissions(keyFile) {
|
|
|
2924
3750
|
var DefaultSecretVault = class {
|
|
2925
3751
|
keyFile;
|
|
2926
3752
|
key;
|
|
3753
|
+
_keyVersion = 1;
|
|
2927
3754
|
constructor(opts) {
|
|
2928
3755
|
this.keyFile = opts.keyFile;
|
|
2929
3756
|
}
|
|
3757
|
+
/** Current key version. Starts at 1; incremented by rotateKey(). */
|
|
3758
|
+
get keyVersion() {
|
|
3759
|
+
if (!this.key) this.loadOrCreateKey();
|
|
3760
|
+
return this._keyVersion;
|
|
3761
|
+
}
|
|
2930
3762
|
isEncrypted(value) {
|
|
2931
|
-
return typeof value === "string" &&
|
|
3763
|
+
return typeof value === "string" && ENCRYPTED_PREFIX_PATTERN.test(value);
|
|
2932
3764
|
}
|
|
2933
3765
|
encrypt(plaintext) {
|
|
2934
3766
|
if (this.isEncrypted(plaintext)) return plaintext;
|
|
@@ -2937,11 +3769,20 @@ var DefaultSecretVault = class {
|
|
|
2937
3769
|
const cipher = createCipheriv(ALGO, key, iv);
|
|
2938
3770
|
const ct = Buffer.concat([cipher.update(plaintext, "utf8"), cipher.final()]);
|
|
2939
3771
|
const tag = cipher.getAuthTag();
|
|
2940
|
-
|
|
3772
|
+
const prefix = encryptedPrefixForVersion(this._keyVersion);
|
|
3773
|
+
return `${prefix}${iv.toString("base64")}:${tag.toString("base64")}:${ct.toString("base64")}`;
|
|
2941
3774
|
}
|
|
2942
3775
|
decrypt(value) {
|
|
2943
3776
|
if (!this.isEncrypted(value)) return value;
|
|
2944
|
-
const
|
|
3777
|
+
const prefixMatch = value.match(ENCRYPTED_PREFIX_PATTERN);
|
|
3778
|
+
if (!prefixMatch) {
|
|
3779
|
+
throw new ConfigError({
|
|
3780
|
+
message: "SecretVault: malformed encrypted value",
|
|
3781
|
+
code: ERROR_CODES.CONFIG_PARSE_FAILED,
|
|
3782
|
+
context: { field: "encrypted_value" }
|
|
3783
|
+
});
|
|
3784
|
+
}
|
|
3785
|
+
const rest = value.slice(prefixMatch[0].length);
|
|
2945
3786
|
const parts = rest.split(":");
|
|
2946
3787
|
if (parts.length !== 3) {
|
|
2947
3788
|
throw new ConfigError({
|
|
@@ -2970,48 +3811,110 @@ var DefaultSecretVault = class {
|
|
|
2970
3811
|
const pt = Buffer.concat([decipher.update(ct), decipher.final()]);
|
|
2971
3812
|
return pt.toString("utf8");
|
|
2972
3813
|
}
|
|
3814
|
+
/**
|
|
3815
|
+
* Generate a new encryption key, write it to disk, and increment the key version.
|
|
3816
|
+
* After rotation, encrypt() emits the new version prefix (e.g. enc:v2:).
|
|
3817
|
+
* The caller must re-encrypt existing config values (see rotateConfigKeys()).
|
|
3818
|
+
*/
|
|
3819
|
+
rotateKey() {
|
|
3820
|
+
const oldVersion = this._keyVersion;
|
|
3821
|
+
const newKey = randomBytes(KEY_BYTES);
|
|
3822
|
+
const newVersion = oldVersion + 1;
|
|
3823
|
+
const keyFileBuf = Buffer.alloc(VERSIONED_KEY_FILE_SIZE);
|
|
3824
|
+
KEY_FILE_MAGIC.copy(keyFileBuf, 0);
|
|
3825
|
+
keyFileBuf[KEY_FILE_MAGIC.length] = newVersion;
|
|
3826
|
+
newKey.copy(keyFileBuf, KEY_FILE_MAGIC.length + 1);
|
|
3827
|
+
fs4.mkdirSync(path3.dirname(this.keyFile), { recursive: true });
|
|
3828
|
+
fs4.writeFileSync(this.keyFile, keyFileBuf, { mode: 384 });
|
|
3829
|
+
checkKeyFilePermissions(this.keyFile);
|
|
3830
|
+
this.key = newKey;
|
|
3831
|
+
this._keyVersion = newVersion;
|
|
3832
|
+
return { oldVersion, newVersion };
|
|
3833
|
+
}
|
|
2973
3834
|
loadOrCreateKey() {
|
|
2974
3835
|
if (this.key) return this.key;
|
|
2975
3836
|
try {
|
|
2976
|
-
const buf =
|
|
2977
|
-
if (buf.length
|
|
2978
|
-
|
|
2979
|
-
|
|
2980
|
-
|
|
2981
|
-
|
|
2982
|
-
|
|
3837
|
+
const buf = fs4.readFileSync(this.keyFile);
|
|
3838
|
+
if (buf.length === KEY_BYTES) {
|
|
3839
|
+
this.key = buf;
|
|
3840
|
+
this._keyVersion = 1;
|
|
3841
|
+
checkKeyFilePermissions(this.keyFile);
|
|
3842
|
+
return this.key;
|
|
3843
|
+
}
|
|
3844
|
+
if (buf.length === VERSIONED_KEY_FILE_SIZE) {
|
|
3845
|
+
const magic = buf.subarray(0, KEY_FILE_MAGIC.length);
|
|
3846
|
+
if (!magic.equals(KEY_FILE_MAGIC)) {
|
|
3847
|
+
throw new ConfigError({
|
|
3848
|
+
message: `SecretVault: key file ${this.keyFile} has invalid magic header`,
|
|
3849
|
+
code: ERROR_CODES.CONFIG_INVALID,
|
|
3850
|
+
context: { keyFile: this.keyFile }
|
|
3851
|
+
});
|
|
3852
|
+
}
|
|
3853
|
+
const version = buf[KEY_FILE_MAGIC.length];
|
|
3854
|
+
const key2 = buf.subarray(KEY_FILE_MAGIC.length + 1);
|
|
3855
|
+
if (key2.length !== KEY_BYTES) {
|
|
3856
|
+
throw new ConfigError({
|
|
3857
|
+
message: `SecretVault: key file ${this.keyFile} has wrong key size (${key2.length} bytes, expected ${KEY_BYTES})`,
|
|
3858
|
+
code: ERROR_CODES.CONFIG_INVALID,
|
|
3859
|
+
context: { keyFile: this.keyFile, expectedBytes: KEY_BYTES, actualBytes: key2.length }
|
|
3860
|
+
});
|
|
3861
|
+
}
|
|
3862
|
+
this.key = Buffer.from(key2);
|
|
3863
|
+
this._keyVersion = version;
|
|
3864
|
+
checkKeyFilePermissions(this.keyFile);
|
|
3865
|
+
return this.key;
|
|
2983
3866
|
}
|
|
2984
|
-
|
|
2985
|
-
|
|
2986
|
-
|
|
3867
|
+
throw new ConfigError({
|
|
3868
|
+
message: `SecretVault: key file ${this.keyFile} is ${buf.length} bytes (expected ${KEY_BYTES} for v1 or ${VERSIONED_KEY_FILE_SIZE} for v2+). Remove it manually to generate a new key.`,
|
|
3869
|
+
code: ERROR_CODES.CONFIG_INVALID,
|
|
3870
|
+
context: { keyFile: this.keyFile, expectedBytes: KEY_BYTES, actualBytes: buf.length }
|
|
3871
|
+
});
|
|
2987
3872
|
} catch (err) {
|
|
2988
3873
|
if (err.code !== "ENOENT") throw err;
|
|
2989
3874
|
}
|
|
2990
|
-
|
|
3875
|
+
fs4.mkdirSync(path3.dirname(this.keyFile), { recursive: true });
|
|
2991
3876
|
const key = randomBytes(KEY_BYTES);
|
|
2992
3877
|
try {
|
|
2993
|
-
|
|
3878
|
+
fs4.writeFileSync(this.keyFile, key, { mode: 384, flag: "wx" });
|
|
2994
3879
|
} catch (err) {
|
|
2995
3880
|
if (err.code !== "EEXIST") throw err;
|
|
2996
|
-
const buf =
|
|
2997
|
-
if (buf.length
|
|
2998
|
-
|
|
2999
|
-
|
|
3000
|
-
|
|
3001
|
-
|
|
3002
|
-
|
|
3881
|
+
const buf = fs4.readFileSync(this.keyFile);
|
|
3882
|
+
if (buf.length === KEY_BYTES) {
|
|
3883
|
+
this.key = buf;
|
|
3884
|
+
this._keyVersion = 1;
|
|
3885
|
+
checkKeyFilePermissions(this.keyFile);
|
|
3886
|
+
return this.key;
|
|
3887
|
+
}
|
|
3888
|
+
if (buf.length === VERSIONED_KEY_FILE_SIZE) {
|
|
3889
|
+
const magic = buf.subarray(0, KEY_FILE_MAGIC.length);
|
|
3890
|
+
if (!magic.equals(KEY_FILE_MAGIC)) {
|
|
3891
|
+
throw new ConfigError({
|
|
3892
|
+
message: `SecretVault: key file ${this.keyFile} has invalid magic header`,
|
|
3893
|
+
code: ERROR_CODES.CONFIG_INVALID,
|
|
3894
|
+
context: { keyFile: this.keyFile }
|
|
3895
|
+
});
|
|
3896
|
+
}
|
|
3897
|
+
const version = buf[KEY_FILE_MAGIC.length];
|
|
3898
|
+
const winnerKey = buf.subarray(KEY_FILE_MAGIC.length + 1);
|
|
3899
|
+
this.key = Buffer.from(winnerKey);
|
|
3900
|
+
this._keyVersion = version;
|
|
3901
|
+
checkKeyFilePermissions(this.keyFile);
|
|
3902
|
+
return this.key;
|
|
3003
3903
|
}
|
|
3004
|
-
|
|
3005
|
-
|
|
3006
|
-
|
|
3904
|
+
throw new ConfigError({
|
|
3905
|
+
message: `SecretVault: key file ${this.keyFile} is ${buf.length} bytes (expected ${KEY_BYTES} for v1 or ${VERSIONED_KEY_FILE_SIZE} for v2+). Remove it manually to generate a new key.`,
|
|
3906
|
+
code: ERROR_CODES.CONFIG_INVALID,
|
|
3907
|
+
context: { keyFile: this.keyFile, expectedBytes: KEY_BYTES, actualBytes: buf.length }
|
|
3908
|
+
});
|
|
3007
3909
|
}
|
|
3008
3910
|
this.key = key;
|
|
3911
|
+
this._keyVersion = 1;
|
|
3009
3912
|
return key;
|
|
3010
3913
|
}
|
|
3011
3914
|
};
|
|
3012
3915
|
function decryptConfigSecrets(cfg, vault, opts) {
|
|
3013
3916
|
const warn = opts?.warn ?? ((msg) => console.warn(msg));
|
|
3014
|
-
return
|
|
3917
|
+
return walk2(cfg, vault, (v, key) => {
|
|
3015
3918
|
try {
|
|
3016
3919
|
return vault.decrypt(v);
|
|
3017
3920
|
} catch (err) {
|
|
@@ -3023,20 +3926,20 @@ function decryptConfigSecrets(cfg, vault, opts) {
|
|
|
3023
3926
|
});
|
|
3024
3927
|
}
|
|
3025
3928
|
function encryptConfigSecrets(cfg, vault, _opts) {
|
|
3026
|
-
return
|
|
3929
|
+
return walk2(cfg, vault, (v) => vault.encrypt(v));
|
|
3027
3930
|
}
|
|
3028
|
-
function
|
|
3931
|
+
function walk2(node, vault, transform) {
|
|
3029
3932
|
if (node === null || node === void 0) return node;
|
|
3030
3933
|
if (typeof node !== "object") return node;
|
|
3031
3934
|
if (Array.isArray(node)) {
|
|
3032
|
-
return node.map((item) =>
|
|
3935
|
+
return node.map((item) => walk2(item, vault, transform));
|
|
3033
3936
|
}
|
|
3034
3937
|
const out = /* @__PURE__ */ Object.create(null);
|
|
3035
3938
|
for (const [k, v] of Object.entries(node)) {
|
|
3036
3939
|
if (typeof v === "string" && isSecretField(k)) {
|
|
3037
3940
|
out[k] = transform(v, k);
|
|
3038
3941
|
} else if (typeof v === "object" && v !== null) {
|
|
3039
|
-
out[k] =
|
|
3942
|
+
out[k] = walk2(v, vault, transform);
|
|
3040
3943
|
} else {
|
|
3041
3944
|
out[k] = v;
|
|
3042
3945
|
}
|
|
@@ -3053,20 +3956,20 @@ function isSecretField(name) {
|
|
|
3053
3956
|
async function rewriteConfigEncrypted(configPath, vault, patch) {
|
|
3054
3957
|
let current = {};
|
|
3055
3958
|
try {
|
|
3056
|
-
const raw = await
|
|
3959
|
+
const raw = await fsp2.readFile(configPath, "utf8");
|
|
3057
3960
|
current = JSON.parse(raw);
|
|
3058
3961
|
} catch {
|
|
3059
3962
|
}
|
|
3060
3963
|
const merged = deepMerge(current, patch ?? {});
|
|
3061
3964
|
const encrypted = encryptConfigSecrets(merged, vault);
|
|
3062
|
-
await
|
|
3965
|
+
await fsp2.mkdir(path3.dirname(configPath), { recursive: true });
|
|
3063
3966
|
await atomicWrite(configPath, JSON.stringify(encrypted, null, 2), { mode: 384 });
|
|
3064
3967
|
await restrictFilePermissions(configPath);
|
|
3065
3968
|
}
|
|
3066
3969
|
async function migratePlaintextSecrets(configPath, vault, logger) {
|
|
3067
3970
|
let raw;
|
|
3068
3971
|
try {
|
|
3069
|
-
raw = await
|
|
3972
|
+
raw = await fsp2.readFile(configPath, "utf8");
|
|
3070
3973
|
} catch {
|
|
3071
3974
|
return { migrated: 0, file: configPath };
|
|
3072
3975
|
}
|
|
@@ -3108,7 +4011,7 @@ async function restrictFilePermissions(filePath, opts) {
|
|
|
3108
4011
|
}
|
|
3109
4012
|
} else {
|
|
3110
4013
|
try {
|
|
3111
|
-
await
|
|
4014
|
+
await fsp2.chmod(filePath, 384);
|
|
3112
4015
|
} catch {
|
|
3113
4016
|
}
|
|
3114
4017
|
}
|
|
@@ -3180,33 +4083,18 @@ var CONTEXT_WINDOW_MODES = Object.freeze([
|
|
|
3180
4083
|
id: "archival",
|
|
3181
4084
|
name: "Archival",
|
|
3182
4085
|
description: "Decision-preserving mode: compacts steadily while keeping summaries prominent.",
|
|
3183
|
-
thresholds: { warn: 0.55, soft: 0.7, hard: 0.84 },
|
|
3184
|
-
aggressiveOn: "soft",
|
|
3185
|
-
preserveK: 8,
|
|
3186
|
-
eliseThreshold: 1200,
|
|
3187
|
-
targetLoad: 0.58
|
|
3188
|
-
}
|
|
3189
|
-
]);
|
|
3190
|
-
function listContextWindowModes() {
|
|
3191
|
-
return CONTEXT_WINDOW_MODES.map((m) => ({ ...m, thresholds: { ...m.thresholds } }));
|
|
3192
|
-
}
|
|
3193
|
-
function isContextWindowModeId(id) {
|
|
3194
|
-
return CONTEXT_WINDOW_MODES.some((m) => m.id === id);
|
|
3195
|
-
}
|
|
3196
|
-
|
|
3197
|
-
// src/utils/safe-json.ts
|
|
3198
|
-
function safeParse(input, maxBytes = 5e6) {
|
|
3199
|
-
if (input.length > maxBytes) {
|
|
3200
|
-
return { ok: false, error: `Input exceeds limit (${maxBytes} bytes)` };
|
|
3201
|
-
}
|
|
3202
|
-
try {
|
|
3203
|
-
return { ok: true, value: JSON.parse(input) };
|
|
3204
|
-
} catch (err) {
|
|
3205
|
-
return {
|
|
3206
|
-
ok: false,
|
|
3207
|
-
error: err instanceof Error ? err.message : String(err)
|
|
3208
|
-
};
|
|
4086
|
+
thresholds: { warn: 0.55, soft: 0.7, hard: 0.84 },
|
|
4087
|
+
aggressiveOn: "soft",
|
|
4088
|
+
preserveK: 8,
|
|
4089
|
+
eliseThreshold: 1200,
|
|
4090
|
+
targetLoad: 0.58
|
|
3209
4091
|
}
|
|
4092
|
+
]);
|
|
4093
|
+
function listContextWindowModes() {
|
|
4094
|
+
return CONTEXT_WINDOW_MODES.map((m) => ({ ...m, thresholds: { ...m.thresholds } }));
|
|
4095
|
+
}
|
|
4096
|
+
function isContextWindowModeId(id) {
|
|
4097
|
+
return CONTEXT_WINDOW_MODES.some((m) => m.id === id);
|
|
3210
4098
|
}
|
|
3211
4099
|
|
|
3212
4100
|
// src/types/default-config.ts
|
|
@@ -3216,7 +4104,8 @@ var DEFAULT_TOOLS_CONFIG = Object.freeze({
|
|
|
3216
4104
|
iterationTimeoutMs: 3e5,
|
|
3217
4105
|
sessionTimeoutMs: 18e5,
|
|
3218
4106
|
perIterationOutputCapBytes: 1e5,
|
|
3219
|
-
autoExtendLimit: true
|
|
4107
|
+
autoExtendLimit: true,
|
|
4108
|
+
restrictToProjectRoot: false
|
|
3220
4109
|
});
|
|
3221
4110
|
var DEFAULT_CONTEXT_CONFIG = Object.freeze({
|
|
3222
4111
|
preserveK: 10,
|
|
@@ -3259,7 +4148,8 @@ var BEHAVIOR_DEFAULTS = {
|
|
|
3259
4148
|
iterationTimeoutMs: DEFAULT_TOOLS_CONFIG.iterationTimeoutMs,
|
|
3260
4149
|
sessionTimeoutMs: DEFAULT_TOOLS_CONFIG.sessionTimeoutMs,
|
|
3261
4150
|
perIterationOutputCapBytes: DEFAULT_TOOLS_CONFIG.perIterationOutputCapBytes,
|
|
3262
|
-
autoExtendLimit: DEFAULT_TOOLS_CONFIG.autoExtendLimit
|
|
4151
|
+
autoExtendLimit: DEFAULT_TOOLS_CONFIG.autoExtendLimit,
|
|
4152
|
+
restrictToProjectRoot: DEFAULT_TOOLS_CONFIG.restrictToProjectRoot
|
|
3263
4153
|
},
|
|
3264
4154
|
log: { level: "info" },
|
|
3265
4155
|
features: {
|
|
@@ -3384,7 +4274,7 @@ var DefaultConfigLoader = class {
|
|
|
3384
4274
|
level: "warn",
|
|
3385
4275
|
event: "config.source_load_failed",
|
|
3386
4276
|
source: src.name,
|
|
3387
|
-
message:
|
|
4277
|
+
message: toErrorMessage(err),
|
|
3388
4278
|
timestamp: (/* @__PURE__ */ new Date()).toISOString()
|
|
3389
4279
|
}));
|
|
3390
4280
|
}
|
|
@@ -3467,7 +4357,7 @@ var DefaultConfigLoader = class {
|
|
|
3467
4357
|
const fp = this.paths.syncConfig;
|
|
3468
4358
|
const t0 = Date.now();
|
|
3469
4359
|
try {
|
|
3470
|
-
const raw = await
|
|
4360
|
+
const raw = await fsp2.readFile(fp, "utf8");
|
|
3471
4361
|
const parsed = safeParse(raw);
|
|
3472
4362
|
if (!parsed.ok || !parsed.value) {
|
|
3473
4363
|
this.events?.emit("storage.read", {
|
|
@@ -3521,7 +4411,7 @@ var DefaultConfigLoader = class {
|
|
|
3521
4411
|
console.warn(JSON.stringify({
|
|
3522
4412
|
level: "warn",
|
|
3523
4413
|
event: "config.sync_load_failed",
|
|
3524
|
-
message:
|
|
4414
|
+
message: toErrorMessage(err),
|
|
3525
4415
|
timestamp: (/* @__PURE__ */ new Date()).toISOString()
|
|
3526
4416
|
}));
|
|
3527
4417
|
return null;
|
|
@@ -3531,7 +4421,7 @@ var DefaultConfigLoader = class {
|
|
|
3531
4421
|
let raw;
|
|
3532
4422
|
const t0 = Date.now();
|
|
3533
4423
|
try {
|
|
3534
|
-
raw = await
|
|
4424
|
+
raw = await fsp2.readFile(file, "utf8");
|
|
3535
4425
|
} catch (err) {
|
|
3536
4426
|
if (err.code !== "ENOENT") {
|
|
3537
4427
|
this.events?.emit("storage.read", {
|
|
@@ -3548,7 +4438,7 @@ var DefaultConfigLoader = class {
|
|
|
3548
4438
|
level: "warn",
|
|
3549
4439
|
event: "config.read_failed",
|
|
3550
4440
|
path: file,
|
|
3551
|
-
message:
|
|
4441
|
+
message: toErrorMessage(err),
|
|
3552
4442
|
timestamp: (/* @__PURE__ */ new Date()).toISOString()
|
|
3553
4443
|
}));
|
|
3554
4444
|
}
|
|
@@ -3702,7 +4592,7 @@ var RecoveryLock = class {
|
|
|
3702
4592
|
sessionStore;
|
|
3703
4593
|
probe;
|
|
3704
4594
|
constructor(opts) {
|
|
3705
|
-
this.file =
|
|
4595
|
+
this.file = path3.join(opts.dir, LOCK_FILE);
|
|
3706
4596
|
this.pid = opts.pid ?? process.pid;
|
|
3707
4597
|
this.hostname = opts.hostname ?? os.hostname();
|
|
3708
4598
|
this.maxAgeMs = opts.maxAgeMs ?? DEFAULT_MAX_AGE_MS;
|
|
@@ -3763,7 +4653,7 @@ var RecoveryLock = class {
|
|
|
3763
4653
|
* null return before calling this.
|
|
3764
4654
|
*/
|
|
3765
4655
|
async write(sessionId) {
|
|
3766
|
-
await ensureDir(
|
|
4656
|
+
await ensureDir(path3.dirname(this.file));
|
|
3767
4657
|
const lock = {
|
|
3768
4658
|
v: 1,
|
|
3769
4659
|
sessionId,
|
|
@@ -3772,7 +4662,7 @@ var RecoveryLock = class {
|
|
|
3772
4662
|
startedAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
3773
4663
|
};
|
|
3774
4664
|
try {
|
|
3775
|
-
await
|
|
4665
|
+
await fsp2.writeFile(this.file, JSON.stringify(lock), { flag: "wx", mode: 384 });
|
|
3776
4666
|
} catch (err) {
|
|
3777
4667
|
const code = err.code;
|
|
3778
4668
|
if (code === "EEXIST") {
|
|
@@ -3788,7 +4678,7 @@ var RecoveryLock = class {
|
|
|
3788
4678
|
*/
|
|
3789
4679
|
async clear() {
|
|
3790
4680
|
try {
|
|
3791
|
-
await
|
|
4681
|
+
await fsp2.unlink(this.file);
|
|
3792
4682
|
} catch (err) {
|
|
3793
4683
|
const code = err.code;
|
|
3794
4684
|
if (code === "ENOENT") return;
|
|
@@ -3798,7 +4688,7 @@ var RecoveryLock = class {
|
|
|
3798
4688
|
async readLock() {
|
|
3799
4689
|
let raw;
|
|
3800
4690
|
try {
|
|
3801
|
-
raw = await
|
|
4691
|
+
raw = await fsp2.readFile(this.file, "utf8");
|
|
3802
4692
|
} catch (err) {
|
|
3803
4693
|
const code = err.code;
|
|
3804
4694
|
if (code === "ENOENT") return null;
|
|
@@ -3830,42 +4720,6 @@ function defaultIsPidAlive(pid) {
|
|
|
3830
4720
|
}
|
|
3831
4721
|
}
|
|
3832
4722
|
|
|
3833
|
-
// src/utils/regex-guard.ts
|
|
3834
|
-
var MAX_PATTERN_LEN = 512;
|
|
3835
|
-
var DANGEROUS_PATTERNS = [
|
|
3836
|
-
/(\([^)]*[+*][^)]*\))[+*]/,
|
|
3837
|
-
// (a+)+, (.*)+, etc
|
|
3838
|
-
/(\(\?:[^)]*[+*][^)]*\))[+*]/
|
|
3839
|
-
// same, with non-capturing group
|
|
3840
|
-
];
|
|
3841
|
-
function compileUserRegex(pattern, flags) {
|
|
3842
|
-
if (typeof pattern !== "string") {
|
|
3843
|
-
return { ok: false, reason: "pattern must be a string" };
|
|
3844
|
-
}
|
|
3845
|
-
if (pattern.length === 0) {
|
|
3846
|
-
return { ok: false, reason: "pattern is empty" };
|
|
3847
|
-
}
|
|
3848
|
-
if (pattern.length > MAX_PATTERN_LEN) {
|
|
3849
|
-
return { ok: false, reason: `pattern exceeds ${MAX_PATTERN_LEN} characters` };
|
|
3850
|
-
}
|
|
3851
|
-
for (const rx of DANGEROUS_PATTERNS) {
|
|
3852
|
-
if (rx.test(pattern)) {
|
|
3853
|
-
return {
|
|
3854
|
-
ok: false,
|
|
3855
|
-
reason: "pattern looks vulnerable to catastrophic backtracking \u2014 rewrite without nested quantifiers"
|
|
3856
|
-
};
|
|
3857
|
-
}
|
|
3858
|
-
}
|
|
3859
|
-
try {
|
|
3860
|
-
return { ok: true, regex: new RegExp(pattern, flags) };
|
|
3861
|
-
} catch (err) {
|
|
3862
|
-
return {
|
|
3863
|
-
ok: false,
|
|
3864
|
-
reason: err instanceof Error ? err.message : "invalid regex"
|
|
3865
|
-
};
|
|
3866
|
-
}
|
|
3867
|
-
}
|
|
3868
|
-
|
|
3869
4723
|
// src/storage/session-reader.ts
|
|
3870
4724
|
var DefaultSessionReader = class {
|
|
3871
4725
|
store;
|
|
@@ -4281,7 +5135,7 @@ function isAllowed(type, level) {
|
|
|
4281
5135
|
return true;
|
|
4282
5136
|
}
|
|
4283
5137
|
function createSessionEventBridge(writer, level = "standard", options = {}) {
|
|
4284
|
-
|
|
5138
|
+
let currentLevel = level ?? "standard";
|
|
4285
5139
|
const resolveWriter = typeof writer === "function" ? writer : () => writer;
|
|
4286
5140
|
const progressCounters = /* @__PURE__ */ new Map();
|
|
4287
5141
|
const toolProgressConfig = options.sampling?.toolProgress ?? {};
|
|
@@ -4302,14 +5156,19 @@ function createSessionEventBridge(writer, level = "standard", options = {}) {
|
|
|
4302
5156
|
return true;
|
|
4303
5157
|
}
|
|
4304
5158
|
return {
|
|
4305
|
-
level
|
|
5159
|
+
get level() {
|
|
5160
|
+
return currentLevel;
|
|
5161
|
+
},
|
|
5162
|
+
setAuditLevel(next) {
|
|
5163
|
+
currentLevel = next ?? "standard";
|
|
5164
|
+
},
|
|
4306
5165
|
allows(type) {
|
|
4307
|
-
return isAllowed(type,
|
|
5166
|
+
return isAllowed(type, currentLevel);
|
|
4308
5167
|
},
|
|
4309
5168
|
async append(event) {
|
|
4310
5169
|
const target = resolveWriter();
|
|
4311
5170
|
if (!target) return;
|
|
4312
|
-
if (!isAllowed(event.type,
|
|
5171
|
+
if (!isAllowed(event.type, currentLevel)) return;
|
|
4313
5172
|
if (!shouldSample(event)) return;
|
|
4314
5173
|
try {
|
|
4315
5174
|
await target.append(event);
|
|
@@ -4320,7 +5179,7 @@ function createSessionEventBridge(writer, level = "standard", options = {}) {
|
|
|
4320
5179
|
const target = resolveWriter();
|
|
4321
5180
|
if (!target || events.length === 0) return;
|
|
4322
5181
|
const allowed = events.filter(
|
|
4323
|
-
(e) => isAllowed(e.type,
|
|
5182
|
+
(e) => isAllowed(e.type, currentLevel) && shouldSample(e)
|
|
4324
5183
|
);
|
|
4325
5184
|
if (allowed.length === 0) return;
|
|
4326
5185
|
try {
|
|
@@ -4357,7 +5216,7 @@ async function loadTodosCheckpoint(filePath, events, traceId) {
|
|
|
4357
5216
|
const t0 = Date.now();
|
|
4358
5217
|
let raw;
|
|
4359
5218
|
try {
|
|
4360
|
-
raw = await
|
|
5219
|
+
raw = await fsp2.readFile(filePath, "utf8");
|
|
4361
5220
|
} catch (err) {
|
|
4362
5221
|
events?.emit("storage.error", {
|
|
4363
5222
|
sessionId: traceId ?? "~boot~",
|
|
@@ -4365,7 +5224,7 @@ async function loadTodosCheckpoint(filePath, events, traceId) {
|
|
|
4365
5224
|
filePath,
|
|
4366
5225
|
operation: "load",
|
|
4367
5226
|
outcome: "failure",
|
|
4368
|
-
error:
|
|
5227
|
+
error: toErrorMessage(err),
|
|
4369
5228
|
recoverable: true
|
|
4370
5229
|
});
|
|
4371
5230
|
return null;
|
|
@@ -4437,13 +5296,13 @@ async function saveTodosCheckpoint(filePath, sessionId, todos, events, traceId)
|
|
|
4437
5296
|
filePath,
|
|
4438
5297
|
operation: "save",
|
|
4439
5298
|
outcome: "failure",
|
|
4440
|
-
error:
|
|
5299
|
+
error: toErrorMessage(err),
|
|
4441
5300
|
recoverable: false
|
|
4442
5301
|
});
|
|
4443
5302
|
console.warn(JSON.stringify({
|
|
4444
5303
|
level: "warn",
|
|
4445
5304
|
event: "todos_checkpoint.save_failed",
|
|
4446
|
-
message:
|
|
5305
|
+
message: toErrorMessage(err),
|
|
4447
5306
|
timestamp: (/* @__PURE__ */ new Date()).toISOString()
|
|
4448
5307
|
}));
|
|
4449
5308
|
}
|
|
@@ -4454,7 +5313,7 @@ function attachTodosCheckpoint(state, filePath, sessionId, events, traceId) {
|
|
|
4454
5313
|
let writeChain = Promise.resolve();
|
|
4455
5314
|
const enqueueWrite = (todos) => {
|
|
4456
5315
|
writeChain = writeChain.then(() => saveTodosCheckpoint(filePath, sessionId, todos, events, traceId)).catch((err) => {
|
|
4457
|
-
const msg =
|
|
5316
|
+
const msg = toErrorMessage(err);
|
|
4458
5317
|
console.error(JSON.stringify({
|
|
4459
5318
|
level: "error",
|
|
4460
5319
|
event: "todos_checkpoint.write_chain_failed",
|
|
@@ -4499,14 +5358,14 @@ async function loadPlan(filePath, events) {
|
|
|
4499
5358
|
const t0 = Date.now();
|
|
4500
5359
|
let raw;
|
|
4501
5360
|
try {
|
|
4502
|
-
raw = await
|
|
5361
|
+
raw = await fsp2.readFile(filePath, "utf8");
|
|
4503
5362
|
} catch (err) {
|
|
4504
5363
|
events?.emit("storage.error", {
|
|
4505
5364
|
sessionId: "~boot~",
|
|
4506
5365
|
store: "plan",
|
|
4507
5366
|
filePath,
|
|
4508
5367
|
operation: "load",
|
|
4509
|
-
error:
|
|
5368
|
+
error: toErrorMessage(err),
|
|
4510
5369
|
recoverable: true
|
|
4511
5370
|
});
|
|
4512
5371
|
return null;
|
|
@@ -4559,19 +5418,21 @@ async function savePlan(filePath, plan, events) {
|
|
|
4559
5418
|
outcome: "success",
|
|
4560
5419
|
durationMs: Date.now() - t0
|
|
4561
5420
|
});
|
|
5421
|
+
return true;
|
|
4562
5422
|
} catch (err) {
|
|
4563
5423
|
events?.emit("storage.error", {
|
|
4564
5424
|
sessionId: "~boot~",
|
|
4565
5425
|
store: "plan",
|
|
4566
5426
|
filePath,
|
|
4567
5427
|
operation: "save",
|
|
4568
|
-
error:
|
|
5428
|
+
error: toErrorMessage(err),
|
|
4569
5429
|
recoverable: false
|
|
4570
5430
|
});
|
|
4571
5431
|
console.warn(
|
|
4572
5432
|
"[plan-store] save failed:",
|
|
4573
|
-
|
|
5433
|
+
toErrorMessage(err)
|
|
4574
5434
|
);
|
|
5435
|
+
return false;
|
|
4575
5436
|
}
|
|
4576
5437
|
}
|
|
4577
5438
|
function emptyPlan(sessionId, title) {
|
|
@@ -4791,7 +5652,7 @@ init_atomic_write();
|
|
|
4791
5652
|
async function loadDirectorState(filePath) {
|
|
4792
5653
|
let raw;
|
|
4793
5654
|
try {
|
|
4794
|
-
raw = await
|
|
5655
|
+
raw = await fsp2.readFile(filePath, "utf8");
|
|
4795
5656
|
} catch {
|
|
4796
5657
|
return null;
|
|
4797
5658
|
}
|
|
@@ -4806,7 +5667,7 @@ async function loadDirectorState(filePath) {
|
|
|
4806
5667
|
async function acquireDirectorStateLock(lockPath, processId = process.pid) {
|
|
4807
5668
|
let existing;
|
|
4808
5669
|
try {
|
|
4809
|
-
existing = await
|
|
5670
|
+
existing = await fsp2.readFile(lockPath, "utf8");
|
|
4810
5671
|
} catch {
|
|
4811
5672
|
}
|
|
4812
5673
|
if (existing) {
|
|
@@ -4830,7 +5691,7 @@ async function acquireDirectorStateLock(lockPath, processId = process.pid) {
|
|
|
4830
5691
|
}
|
|
4831
5692
|
async function releaseDirectorStateLock(lockPath) {
|
|
4832
5693
|
try {
|
|
4833
|
-
await
|
|
5694
|
+
await fsp2.unlink(lockPath);
|
|
4834
5695
|
} catch {
|
|
4835
5696
|
}
|
|
4836
5697
|
}
|
|
@@ -4955,7 +5816,7 @@ var DirectorStateCheckpoint = class {
|
|
|
4955
5816
|
} catch (err) {
|
|
4956
5817
|
console.warn(
|
|
4957
5818
|
"[director-state] checkpoint write failed:",
|
|
4958
|
-
|
|
5819
|
+
toErrorMessage(err)
|
|
4959
5820
|
);
|
|
4960
5821
|
} finally {
|
|
4961
5822
|
this.writing = false;
|
|
@@ -5008,6 +5869,27 @@ var PATTERNS = [
|
|
|
5008
5869
|
{ type: "postgres_uri", regex: /postgres(?:ql)?:\/\/[^\s"'`]+/g },
|
|
5009
5870
|
{ type: "mysql_uri", regex: /mysql:\/\/[^\s"'`]+/g },
|
|
5010
5871
|
{ type: "redis_uri", regex: /redis:\/\/[^\s"'`]+/g },
|
|
5872
|
+
// AI/ML provider keys — modern LLM services with well-known prefixes
|
|
5873
|
+
{
|
|
5874
|
+
type: "huggingface_token",
|
|
5875
|
+
// HuggingFace tokens: hf_ followed by 34 alphanumeric chars
|
|
5876
|
+
regex: /(?<![A-Za-z0-9])hf_[A-Za-z0-9]{34}(?![A-Za-z0-9])/g
|
|
5877
|
+
},
|
|
5878
|
+
{
|
|
5879
|
+
type: "replicate_token",
|
|
5880
|
+
// Replicate tokens: r8_ followed by 40+ alphanumeric chars
|
|
5881
|
+
regex: /(?<![A-Za-z0-9])r8_[A-Za-z0-9]{40,}(?![A-Za-z0-9])/g
|
|
5882
|
+
},
|
|
5883
|
+
{
|
|
5884
|
+
type: "perplexity_key",
|
|
5885
|
+
// Perplexity API keys: pplx- followed by 40+ alphanumeric chars
|
|
5886
|
+
regex: /(?<![A-Za-z0-9])pplx-[A-Za-z0-9]{40,}(?![A-Za-z0-9])/g
|
|
5887
|
+
},
|
|
5888
|
+
{
|
|
5889
|
+
type: "groq_key",
|
|
5890
|
+
// Groq API keys: gsk_ followed by 40+ alphanumeric chars
|
|
5891
|
+
regex: /(?<![A-Za-z0-9])gsk_[A-Za-z0-9]{40,}(?![A-Za-z0-9])/g
|
|
5892
|
+
},
|
|
5011
5893
|
{
|
|
5012
5894
|
type: "bearer_token",
|
|
5013
5895
|
// Anchored with alternation instead of negative lookahead — avoids V8
|
|
@@ -5041,6 +5923,10 @@ function hasCredentialAnchors(text) {
|
|
|
5041
5923
|
text.includes("xox") || // Slack token (xoxa/xoxb/xoxp/xoxo/xoxs)
|
|
5042
5924
|
text.includes("Bearer ") || // Bearer token (space suffix reduces false positives)
|
|
5043
5925
|
text.includes("/bot") || // Telegram bot token (URL path pattern)
|
|
5926
|
+
text.includes("hf_") || // HuggingFace token
|
|
5927
|
+
text.includes("r8_") || // Replicate token
|
|
5928
|
+
text.includes("pplx-") || // Perplexity API key
|
|
5929
|
+
text.includes("gsk_") || // Groq API key
|
|
5044
5930
|
text.includes("_KEY=") || // High-entropy env vars: API_KEY=, SECRET_KEY=, ...
|
|
5045
5931
|
text.includes("_TOKEN=") || // ACCESS_TOKEN=, AUTH_TOKEN=, ...
|
|
5046
5932
|
text.includes("_SECRET=") || // API_SECRET=, CLIENT_SECRET=, ...
|
|
@@ -5165,82 +6051,6 @@ function getDangerousCapabilities(toolOrCaps) {
|
|
|
5165
6051
|
|
|
5166
6052
|
// src/security/permission-policy.ts
|
|
5167
6053
|
init_atomic_write();
|
|
5168
|
-
|
|
5169
|
-
// src/utils/glob-match.ts
|
|
5170
|
-
function escapeRegex(s) {
|
|
5171
|
-
return s.replace(/[.+^${}()|\\]/g, "\\$&");
|
|
5172
|
-
}
|
|
5173
|
-
var COMPILED_GLOB_CACHE = /* @__PURE__ */ new Map();
|
|
5174
|
-
var CACHE_MAX_SIZE = 2e3;
|
|
5175
|
-
function getCachedGlob(pattern) {
|
|
5176
|
-
const cached = COMPILED_GLOB_CACHE.get(pattern);
|
|
5177
|
-
if (cached) return cached;
|
|
5178
|
-
if (COMPILED_GLOB_CACHE.size >= CACHE_MAX_SIZE) {
|
|
5179
|
-
const keys = [...COMPILED_GLOB_CACHE.keys()];
|
|
5180
|
-
for (let i = 0; i < Math.floor(CACHE_MAX_SIZE / 4); i++) {
|
|
5181
|
-
COMPILED_GLOB_CACHE.delete(expectDefined(keys[i]));
|
|
5182
|
-
}
|
|
5183
|
-
}
|
|
5184
|
-
const re = compileGlob(pattern);
|
|
5185
|
-
COMPILED_GLOB_CACHE.set(pattern, re);
|
|
5186
|
-
return re;
|
|
5187
|
-
}
|
|
5188
|
-
var MAX_GLOB_PATTERN_LEN = 1024;
|
|
5189
|
-
function compileGlob(pattern) {
|
|
5190
|
-
if (pattern.length > MAX_GLOB_PATTERN_LEN) {
|
|
5191
|
-
throw new Error(`Glob pattern exceeds ${MAX_GLOB_PATTERN_LEN} characters`);
|
|
5192
|
-
}
|
|
5193
|
-
let i = 0;
|
|
5194
|
-
let re = "^";
|
|
5195
|
-
while (i < pattern.length) {
|
|
5196
|
-
const c = pattern[i];
|
|
5197
|
-
if (c === "*") {
|
|
5198
|
-
if (pattern[i + 1] === "*") {
|
|
5199
|
-
re += ".*";
|
|
5200
|
-
i += 2;
|
|
5201
|
-
if (pattern[i] === "/") i++;
|
|
5202
|
-
} else {
|
|
5203
|
-
re += "[^/]*";
|
|
5204
|
-
i++;
|
|
5205
|
-
}
|
|
5206
|
-
} else if (c === "?") {
|
|
5207
|
-
re += "[^/]";
|
|
5208
|
-
i++;
|
|
5209
|
-
} else if (c === "[") {
|
|
5210
|
-
let cls = "[";
|
|
5211
|
-
i++;
|
|
5212
|
-
if (pattern[i] === "!" || pattern[i] === "^") {
|
|
5213
|
-
cls += "^";
|
|
5214
|
-
i++;
|
|
5215
|
-
}
|
|
5216
|
-
while (i < pattern.length && pattern[i] !== "]") {
|
|
5217
|
-
const ch = pattern[i] ?? "";
|
|
5218
|
-
if (ch === "\\") {
|
|
5219
|
-
cls += "\\\\";
|
|
5220
|
-
} else if (ch === "]" || ch === "^") {
|
|
5221
|
-
cls += `\\${ch}`;
|
|
5222
|
-
} else {
|
|
5223
|
-
cls += ch;
|
|
5224
|
-
}
|
|
5225
|
-
i++;
|
|
5226
|
-
}
|
|
5227
|
-
cls += "]";
|
|
5228
|
-
re += cls;
|
|
5229
|
-
i++;
|
|
5230
|
-
} else {
|
|
5231
|
-
re += escapeRegex(c ?? "");
|
|
5232
|
-
i++;
|
|
5233
|
-
}
|
|
5234
|
-
}
|
|
5235
|
-
re += "$";
|
|
5236
|
-
return new RegExp(re);
|
|
5237
|
-
}
|
|
5238
|
-
function matchGlob(pattern, input) {
|
|
5239
|
-
return getCachedGlob(pattern).test(input);
|
|
5240
|
-
}
|
|
5241
|
-
function matchAny(patterns, input) {
|
|
5242
|
-
return patterns.some((p) => matchGlob(p, input));
|
|
5243
|
-
}
|
|
5244
6054
|
var DESTRUCTIVE_BASH_PATTERNS = [
|
|
5245
6055
|
/\bgit\s+(?:clean\s+-[^\s]*[xdf]|reset\s+--hard)\b/i,
|
|
5246
6056
|
/\b(?:drop|truncate)\s+(?:table|database|schema)\b/i,
|
|
@@ -5263,9 +6073,9 @@ function getInputString(input, key) {
|
|
|
5263
6073
|
function pathLooksInsideProject(rawPath, projectRoot) {
|
|
5264
6074
|
if (!projectRoot) return false;
|
|
5265
6075
|
if (rawPath === "~" || rawPath.startsWith("~/") || rawPath.startsWith("~\\")) return false;
|
|
5266
|
-
const resolved =
|
|
5267
|
-
const relative2 =
|
|
5268
|
-
return !!relative2 && !relative2.startsWith("..") && !
|
|
6076
|
+
const resolved = path3.resolve(projectRoot, rawPath);
|
|
6077
|
+
const relative2 = path3.relative(projectRoot, resolved);
|
|
6078
|
+
return !!relative2 && !relative2.startsWith("..") && !path3.isAbsolute(relative2);
|
|
5269
6079
|
}
|
|
5270
6080
|
function tokenizeShell(command) {
|
|
5271
6081
|
return command.match(/"[^"]*"|'[^']*'|\S+/g)?.map((token) => token.replace(/^['"]|['"]$/g, "")) ?? [];
|
|
@@ -5275,7 +6085,7 @@ function pathTokenIsOutsideProject(token, projectRoot) {
|
|
|
5275
6085
|
if (token === "/" || token === "~" || token === "." || token === "..") return token !== ".";
|
|
5276
6086
|
if (token.includes("*")) return true;
|
|
5277
6087
|
if (token.startsWith("..") || token.includes("../") || token.includes("..\\")) return true;
|
|
5278
|
-
if (
|
|
6088
|
+
if (path3.isAbsolute(token) || token.startsWith("~/")) return !pathLooksInsideProject(token, projectRoot);
|
|
5279
6089
|
return false;
|
|
5280
6090
|
}
|
|
5281
6091
|
function hasDangerousDeleteTarget(tokens, start, projectRoot) {
|
|
@@ -5421,7 +6231,7 @@ var DefaultPermissionPolicy = class {
|
|
|
5421
6231
|
}
|
|
5422
6232
|
async reload() {
|
|
5423
6233
|
try {
|
|
5424
|
-
const raw = await
|
|
6234
|
+
const raw = await fsp2.readFile(this.trustFile, "utf8");
|
|
5425
6235
|
const parsed = safeParse(raw);
|
|
5426
6236
|
if (parsed.ok && parsed.value) this.policy = parsed.value;
|
|
5427
6237
|
} catch {
|
|
@@ -5693,11 +6503,6 @@ var AutoApprovePermissionPolicy = class _AutoApprovePermissionPolicy {
|
|
|
5693
6503
|
}
|
|
5694
6504
|
};
|
|
5695
6505
|
|
|
5696
|
-
// src/utils/string.ts
|
|
5697
|
-
function truncate(s, max) {
|
|
5698
|
-
return s.length <= max ? s : `${s.slice(0, max - 1)}\u2026`;
|
|
5699
|
-
}
|
|
5700
|
-
|
|
5701
6506
|
// src/types/provider.ts
|
|
5702
6507
|
var ProviderError = class extends WrongStackError {
|
|
5703
6508
|
status;
|
|
@@ -5934,12 +6739,12 @@ var DefaultSkillLoader = class {
|
|
|
5934
6739
|
const seen = /* @__PURE__ */ new Set();
|
|
5935
6740
|
for (const { dir, source } of this.dirs) {
|
|
5936
6741
|
try {
|
|
5937
|
-
const entries = await
|
|
6742
|
+
const entries = await fsp2.readdir(dir, { withFileTypes: true });
|
|
5938
6743
|
for (const e of entries) {
|
|
5939
6744
|
if (!e.isDirectory()) continue;
|
|
5940
|
-
const skillFile =
|
|
6745
|
+
const skillFile = path3.join(dir, e.name, "SKILL.md");
|
|
5941
6746
|
try {
|
|
5942
|
-
const raw = await
|
|
6747
|
+
const raw = await fsp2.readFile(skillFile, "utf8");
|
|
5943
6748
|
const meta = parseFrontmatter(raw);
|
|
5944
6749
|
if (!meta.name || !meta.description) continue;
|
|
5945
6750
|
if (seen.has(meta.name)) continue;
|
|
@@ -5962,12 +6767,12 @@ var DefaultSkillLoader = class {
|
|
|
5962
6767
|
}
|
|
5963
6768
|
async find(name) {
|
|
5964
6769
|
const all = await this.list();
|
|
5965
|
-
|
|
6770
|
+
const lower = name.toLowerCase();
|
|
6771
|
+
return all.find((s) => s.name.toLowerCase() === lower);
|
|
5966
6772
|
}
|
|
5967
6773
|
async manifestText() {
|
|
5968
|
-
const skills = await this.list();
|
|
5969
|
-
if (skills.length === 0) return "";
|
|
5970
6774
|
const entries = await this.listEntries();
|
|
6775
|
+
if (entries.length === 0) return "";
|
|
5971
6776
|
const lines = ["## Available skills"];
|
|
5972
6777
|
for (const e of entries) {
|
|
5973
6778
|
const scopeTag = e.scope.length > 0 ? ` \u2014 ${e.scope.slice(0, 3).join(", ")}` : "";
|
|
@@ -5981,12 +6786,8 @@ var DefaultSkillLoader = class {
|
|
|
5981
6786
|
const skills = await this.list();
|
|
5982
6787
|
const entries = [];
|
|
5983
6788
|
for (const s of skills) {
|
|
5984
|
-
|
|
5985
|
-
|
|
5986
|
-
const { trigger, scope } = parseDescription(raw);
|
|
5987
|
-
entries.push({ name: s.name, trigger, scope, source: s.source, path: s.path });
|
|
5988
|
-
} catch {
|
|
5989
|
-
}
|
|
6789
|
+
const { trigger, scope } = parseDescriptionFromText(s.description ?? "");
|
|
6790
|
+
entries.push({ name: s.name, trigger, scope, source: s.source, path: s.path });
|
|
5990
6791
|
}
|
|
5991
6792
|
this.entriesCache = entries;
|
|
5992
6793
|
return entries;
|
|
@@ -5997,183 +6798,88 @@ var DefaultSkillLoader = class {
|
|
|
5997
6798
|
this.bodyCache.clear();
|
|
5998
6799
|
}
|
|
5999
6800
|
async readBody(name) {
|
|
6000
|
-
const
|
|
6801
|
+
const key = name.toLowerCase();
|
|
6802
|
+
const cached = this.bodyCache.get(key);
|
|
6001
6803
|
if (cached !== void 0) return cached;
|
|
6002
6804
|
const m = await this.find(name);
|
|
6003
6805
|
if (!m) throw new Error(`Skill "${name}" not found`);
|
|
6004
|
-
const body = await
|
|
6005
|
-
this.bodyCache.set(
|
|
6806
|
+
const body = await fsp2.readFile(m.path, "utf8");
|
|
6807
|
+
this.bodyCache.set(key, body);
|
|
6006
6808
|
return body;
|
|
6007
6809
|
}
|
|
6008
6810
|
async readSaveBody(name) {
|
|
6009
|
-
const key = `save:${name}`;
|
|
6811
|
+
const key = `save:${name.toLowerCase()}`;
|
|
6010
6812
|
const cached = this.bodyCache.get(key);
|
|
6011
6813
|
if (cached !== void 0) return cached;
|
|
6012
6814
|
const m = await this.find(name);
|
|
6013
6815
|
if (!m) throw new Error(`Skill "${name}" not found`);
|
|
6014
|
-
const savePath =
|
|
6816
|
+
const savePath = path3.join(path3.dirname(m.path), "SKILL.save.md");
|
|
6015
6817
|
let result;
|
|
6016
6818
|
try {
|
|
6017
|
-
result = await
|
|
6819
|
+
result = await fsp2.readFile(savePath, "utf8");
|
|
6018
6820
|
} catch {
|
|
6019
|
-
const full = await
|
|
6020
|
-
const body = stripFrontmatter(full);
|
|
6021
|
-
const compact = compactSkillBody(body);
|
|
6022
|
-
if (compact) {
|
|
6023
|
-
result = `## Overview
|
|
6024
|
-
|
|
6025
|
-
${compact}`;
|
|
6026
|
-
} else {
|
|
6027
|
-
result = body.trim().slice(0, 300);
|
|
6028
|
-
}
|
|
6029
|
-
}
|
|
6030
|
-
this.bodyCache.set(key, result);
|
|
6031
|
-
return result;
|
|
6032
|
-
}
|
|
6033
|
-
};
|
|
6034
|
-
function parseFrontmatter(raw) {
|
|
6035
|
-
if (!raw.startsWith("---")) return {};
|
|
6036
|
-
const end = raw.indexOf("\n---", 4);
|
|
6037
|
-
if (end === -1) return {};
|
|
6038
|
-
const block = raw.slice(4, end);
|
|
6039
|
-
const out = {};
|
|
6040
|
-
let key = null;
|
|
6041
|
-
let value = [];
|
|
6042
|
-
const flush = () => {
|
|
6043
|
-
if (key) {
|
|
6044
|
-
out[key] = value.join("\n").trim();
|
|
6045
|
-
}
|
|
6046
|
-
key = null;
|
|
6047
|
-
value = [];
|
|
6048
|
-
};
|
|
6049
|
-
for (const line of block.split("\n")) {
|
|
6050
|
-
const m = /^([a-zA-Z_]+):\s*(\|?)\s*(.*)$/.exec(line);
|
|
6051
|
-
if (m) {
|
|
6052
|
-
flush();
|
|
6053
|
-
key = m[1] ?? "";
|
|
6054
|
-
const pipe = m[2];
|
|
6055
|
-
const rest = m[3] ?? "";
|
|
6056
|
-
if (pipe === "|") {
|
|
6057
|
-
value = [];
|
|
6058
|
-
} else if (rest) {
|
|
6059
|
-
value = [rest];
|
|
6060
|
-
} else {
|
|
6061
|
-
value = [];
|
|
6062
|
-
}
|
|
6063
|
-
} else if (key) {
|
|
6064
|
-
value.push(line.replace(/^\s+/, ""));
|
|
6065
|
-
}
|
|
6066
|
-
}
|
|
6067
|
-
flush();
|
|
6068
|
-
return out;
|
|
6069
|
-
}
|
|
6070
|
-
function parseDescription(raw) {
|
|
6071
|
-
const fm = parseFrontmatter(raw);
|
|
6072
|
-
const desc = fm.description ?? "";
|
|
6073
|
-
const firstSentenceEnd = desc.indexOf(". ");
|
|
6074
|
-
const trigger = firstSentenceEnd !== -1 ? desc.slice(0, firstSentenceEnd + 1).trim() : desc.trim().split("\n")[0] ?? "";
|
|
6075
|
-
const scope = [];
|
|
6076
|
-
const coversMatch = /(?:covers|for|including)\s+([^.]+)/i.exec(desc);
|
|
6077
|
-
if (coversMatch) {
|
|
6078
|
-
const items = coversMatch[1] ?? "".replace(/[·•]/g, ",").split(",").map((s) => s.trim()).filter(Boolean);
|
|
6079
|
-
scope.push(...items);
|
|
6080
|
-
}
|
|
6081
|
-
return { trigger, scope };
|
|
6082
|
-
}
|
|
6083
|
-
|
|
6084
|
-
// src/utils/json-repair.ts
|
|
6085
|
-
function completePartialObject(s) {
|
|
6086
|
-
if (!s.trim().startsWith("{")) return s;
|
|
6087
|
-
if (tryParse(s).ok) return s;
|
|
6088
|
-
return repairTruncated(s);
|
|
6089
|
-
}
|
|
6090
|
-
function repairTruncated(s) {
|
|
6091
|
-
const stack = [];
|
|
6092
|
-
let inString = false;
|
|
6093
|
-
let escaped = false;
|
|
6094
|
-
let sawKey = false;
|
|
6095
|
-
let prevSig = "";
|
|
6096
|
-
let contentEnd = 0;
|
|
6097
|
-
let stringBraceDepth = 0;
|
|
6098
|
-
for (let i = 0; i < s.length; i++) {
|
|
6099
|
-
const ch = expectDefined(s[i]);
|
|
6100
|
-
if (inString) {
|
|
6101
|
-
contentEnd = i + 1;
|
|
6102
|
-
if (escaped) {
|
|
6103
|
-
escaped = false;
|
|
6104
|
-
continue;
|
|
6105
|
-
}
|
|
6106
|
-
if (ch === "\\") {
|
|
6107
|
-
escaped = true;
|
|
6108
|
-
continue;
|
|
6109
|
-
}
|
|
6110
|
-
if (ch === '"') {
|
|
6111
|
-
inString = false;
|
|
6112
|
-
prevSig = '"';
|
|
6113
|
-
stringBraceDepth = 0;
|
|
6114
|
-
continue;
|
|
6115
|
-
}
|
|
6116
|
-
if (ch === "{") stringBraceDepth++;
|
|
6117
|
-
else if (ch === "}" && stringBraceDepth > 0) stringBraceDepth--;
|
|
6118
|
-
continue;
|
|
6119
|
-
}
|
|
6120
|
-
if (ch === " " || ch === " " || ch === "\n" || ch === "\r") continue;
|
|
6121
|
-
contentEnd = i + 1;
|
|
6122
|
-
if (ch === '"') {
|
|
6123
|
-
inString = true;
|
|
6124
|
-
sawKey = true;
|
|
6125
|
-
stringBraceDepth = 0;
|
|
6126
|
-
prevSig = '"';
|
|
6127
|
-
} else if (ch === "{" || ch === "[") {
|
|
6128
|
-
stack.push(ch);
|
|
6129
|
-
prevSig = ch;
|
|
6130
|
-
} else if (ch === "}" || ch === "]") {
|
|
6131
|
-
stack.pop();
|
|
6132
|
-
prevSig = ch;
|
|
6133
|
-
} else {
|
|
6134
|
-
prevSig = ch;
|
|
6821
|
+
const full = await fsp2.readFile(m.path, "utf8");
|
|
6822
|
+
const body = stripFrontmatter(full);
|
|
6823
|
+
const compact = compactSkillBody(body);
|
|
6824
|
+
if (compact) {
|
|
6825
|
+
result = `## Overview
|
|
6826
|
+
|
|
6827
|
+
${compact}`;
|
|
6828
|
+
} else {
|
|
6829
|
+
result = body.trim().slice(0, 300);
|
|
6830
|
+
}
|
|
6135
6831
|
}
|
|
6832
|
+
this.bodyCache.set(key, result);
|
|
6833
|
+
return result;
|
|
6136
6834
|
}
|
|
6137
|
-
|
|
6138
|
-
|
|
6139
|
-
if (
|
|
6140
|
-
|
|
6141
|
-
|
|
6142
|
-
|
|
6143
|
-
|
|
6835
|
+
};
|
|
6836
|
+
function parseFrontmatter(raw) {
|
|
6837
|
+
if (!raw.startsWith("---")) return {};
|
|
6838
|
+
const end = raw.indexOf("\n---", 4);
|
|
6839
|
+
if (end === -1) return {};
|
|
6840
|
+
const block = raw.slice(4, end);
|
|
6841
|
+
const out = {};
|
|
6842
|
+
let key = null;
|
|
6843
|
+
let value = [];
|
|
6844
|
+
const flush = () => {
|
|
6845
|
+
if (key) {
|
|
6846
|
+
out[key] = value.join("\n").trim();
|
|
6847
|
+
}
|
|
6848
|
+
key = null;
|
|
6849
|
+
value = [];
|
|
6850
|
+
};
|
|
6851
|
+
for (const line of block.split("\n")) {
|
|
6852
|
+
const m = /^([a-zA-Z_]+):\s*(\|?)\s*(.*)$/.exec(line);
|
|
6853
|
+
if (m) {
|
|
6854
|
+
flush();
|
|
6855
|
+
key = m[1] ?? "";
|
|
6856
|
+
const pipe = m[2];
|
|
6857
|
+
const rest = m[3] ?? "";
|
|
6858
|
+
if (pipe === "|") {
|
|
6859
|
+
value = [];
|
|
6860
|
+
} else if (rest) {
|
|
6861
|
+
value = [rest];
|
|
6862
|
+
} else {
|
|
6863
|
+
value = [];
|
|
6864
|
+
}
|
|
6865
|
+
} else if (key) {
|
|
6866
|
+
value.push(line.replace(/^\s+/, ""));
|
|
6144
6867
|
}
|
|
6145
|
-
if (stringBraceDepth > 0) result += "}".repeat(stringBraceDepth);
|
|
6146
|
-
result += '"';
|
|
6147
|
-
} else if (prevSig === ":") {
|
|
6148
|
-
result += "null";
|
|
6149
|
-
}
|
|
6150
|
-
for (let k = stack.length - 1; k >= 0; k--) {
|
|
6151
|
-
result += stack[k] === "{" ? "}" : "]";
|
|
6152
|
-
}
|
|
6153
|
-
if (!tryParse(result).ok) {
|
|
6154
|
-
const patched = result.replace(/:(\s*)([}\]])/g, ":null$2");
|
|
6155
|
-
if (tryParse(patched).ok) result = patched;
|
|
6156
6868
|
}
|
|
6157
|
-
|
|
6158
|
-
|
|
6159
|
-
var VALID_ESCAPE = /* @__PURE__ */ new Set(['"', "\\", "/", "b", "f", "n", "r", "t", "u"]);
|
|
6160
|
-
function endsWithInvalidEscape(str) {
|
|
6161
|
-
const last = str[str.length - 1];
|
|
6162
|
-
if (str[str.length - 2] !== "\\" || last === void 0) return false;
|
|
6163
|
-
if (VALID_ESCAPE.has(last)) return false;
|
|
6164
|
-
let backslashes = 0;
|
|
6165
|
-
for (let k = str.length - 2; k >= 0 && str[k] === "\\"; k--) backslashes++;
|
|
6166
|
-
return backslashes % 2 === 1;
|
|
6869
|
+
flush();
|
|
6870
|
+
return out;
|
|
6167
6871
|
}
|
|
6168
|
-
function
|
|
6169
|
-
|
|
6170
|
-
|
|
6171
|
-
|
|
6172
|
-
|
|
6872
|
+
function parseDescriptionFromText(desc) {
|
|
6873
|
+
const firstSentenceEnd = desc.indexOf(". ");
|
|
6874
|
+
const trigger = firstSentenceEnd !== -1 ? desc.slice(0, firstSentenceEnd + 1).trim() : desc.trim().split("\n")[0] ?? "";
|
|
6875
|
+
const scope = [];
|
|
6876
|
+
const coversMatch = /(?:covers|for|including)\s+([^.]+)/i.exec(desc);
|
|
6877
|
+
if (coversMatch) {
|
|
6878
|
+
const items = (coversMatch[1] ?? "").replace(/[·•]/g, ",").split(",").map((s) => s.trim()).filter(Boolean);
|
|
6879
|
+
scope.push(...items);
|
|
6173
6880
|
}
|
|
6881
|
+
return { trigger, scope };
|
|
6174
6882
|
}
|
|
6175
|
-
|
|
6176
|
-
// src/core/streaming-response-builder.ts
|
|
6177
6883
|
var STREAM_DRAIN_TIMEOUT_MS = 500;
|
|
6178
6884
|
function buildResponse(state) {
|
|
6179
6885
|
const content = [];
|
|
@@ -6530,191 +7236,48 @@ async function runProviderWithRetry(opts) {
|
|
|
6530
7236
|
}
|
|
6531
7237
|
await new Promise((resolve5, reject) => {
|
|
6532
7238
|
let settled = false;
|
|
6533
|
-
const onAbort = () => {
|
|
6534
|
-
if (settled) return;
|
|
6535
|
-
settled = true;
|
|
6536
|
-
clearTimeout(t);
|
|
6537
|
-
reject(new Error("aborted"));
|
|
6538
|
-
};
|
|
6539
|
-
const t = setTimeout(() => {
|
|
6540
|
-
if (settled) return;
|
|
6541
|
-
settled = true;
|
|
6542
|
-
clearTimeout(t);
|
|
6543
|
-
signal.removeEventListener("abort", onAbort);
|
|
6544
|
-
resolve5();
|
|
6545
|
-
}, delay);
|
|
6546
|
-
if (signal.aborted) {
|
|
6547
|
-
onAbort();
|
|
6548
|
-
return;
|
|
6549
|
-
}
|
|
6550
|
-
signal.addEventListener("abort", onAbort, { once: true });
|
|
6551
|
-
});
|
|
6552
|
-
attempt++;
|
|
6553
|
-
}
|
|
6554
|
-
}
|
|
6555
|
-
}
|
|
6556
|
-
|
|
6557
|
-
// src/execution/provider-runner-impl.ts
|
|
6558
|
-
var DefaultProviderRunner = class {
|
|
6559
|
-
async run(opts) {
|
|
6560
|
-
return runProviderWithRetry(opts);
|
|
6561
|
-
}
|
|
6562
|
-
};
|
|
6563
|
-
|
|
6564
|
-
// src/utils/token-estimate.ts
|
|
6565
|
-
var RoughTokenEstimate = (text, charsPerToken = 3.5) => Math.max(1, Math.ceil(text.length / charsPerToken));
|
|
6566
|
-
var CALIBRATION_GLOBAL_KEY = "__global__";
|
|
6567
|
-
var _cals = /* @__PURE__ */ new Map();
|
|
6568
|
-
function calState(key) {
|
|
6569
|
-
let state = _cals.get(key);
|
|
6570
|
-
if (!state) {
|
|
6571
|
-
state = { ratio: 1, count: 0, prevEst: 0 };
|
|
6572
|
-
_cals.set(key, state);
|
|
6573
|
-
}
|
|
6574
|
-
return state;
|
|
6575
|
-
}
|
|
6576
|
-
var MIN_SAMPLES_FOR_CALIBRATION = 3;
|
|
6577
|
-
var ESTIMATE_CACHE = /* @__PURE__ */ new Map();
|
|
6578
|
-
var ESTIMATE_CACHE_MAX_SIZE = 1e4;
|
|
6579
|
-
function getCachedEstimate(key, compute) {
|
|
6580
|
-
const existing = ESTIMATE_CACHE.get(key);
|
|
6581
|
-
if (existing !== void 0) return existing;
|
|
6582
|
-
if (ESTIMATE_CACHE.size >= ESTIMATE_CACHE_MAX_SIZE) {
|
|
6583
|
-
let evicted = 0;
|
|
6584
|
-
const maxEvict = Math.floor(ESTIMATE_CACHE_MAX_SIZE / 4);
|
|
6585
|
-
for (const k of ESTIMATE_CACHE.keys()) {
|
|
6586
|
-
if (evicted >= maxEvict) break;
|
|
6587
|
-
ESTIMATE_CACHE.delete(k);
|
|
6588
|
-
evicted++;
|
|
6589
|
-
}
|
|
6590
|
-
}
|
|
6591
|
-
const estimate = compute(key);
|
|
6592
|
-
ESTIMATE_CACHE.set(key, estimate);
|
|
6593
|
-
return estimate;
|
|
6594
|
-
}
|
|
6595
|
-
function estimateToolInputTokens(input) {
|
|
6596
|
-
if (typeof input === "string") return RoughTokenEstimate(input);
|
|
6597
|
-
if (input === null || typeof input !== "object") {
|
|
6598
|
-
return RoughTokenEstimate(String(input));
|
|
6599
|
-
}
|
|
6600
|
-
return getCachedEstimate(JSON.stringify(input), (key) => RoughTokenEstimate(key));
|
|
6601
|
-
}
|
|
6602
|
-
function estimateToolResultTokens(content) {
|
|
6603
|
-
if (typeof content === "string") return RoughTokenEstimate(content);
|
|
6604
|
-
return getCachedEstimate(JSON.stringify(content), (key) => RoughTokenEstimate(key));
|
|
6605
|
-
}
|
|
6606
|
-
function estimateTextTokens(text) {
|
|
6607
|
-
return RoughTokenEstimate(text);
|
|
6608
|
-
}
|
|
6609
|
-
function computeMessageTokens(msg) {
|
|
6610
|
-
if (typeof msg.content === "string") return estimateTextTokens(msg.content);
|
|
6611
|
-
let total = 0;
|
|
6612
|
-
for (const b of msg.content) {
|
|
6613
|
-
if (b.type === "text") total += estimateTextTokens(b.text);
|
|
6614
|
-
else if (b.type === "tool_use") total += estimateToolInputTokens(b.input);
|
|
6615
|
-
else if (b.type === "tool_result") total += estimateToolResultTokens(b.content);
|
|
6616
|
-
else total += RoughTokenEstimate(JSON.stringify(b));
|
|
6617
|
-
}
|
|
6618
|
-
return total;
|
|
6619
|
-
}
|
|
6620
|
-
function estimateMessageTokens(messages) {
|
|
6621
|
-
let total = 0;
|
|
6622
|
-
for (const m of messages) {
|
|
6623
|
-
if (typeof m._estTokens === "number" && m._estTokens > 0) {
|
|
6624
|
-
total += m._estTokens;
|
|
6625
|
-
continue;
|
|
6626
|
-
}
|
|
6627
|
-
total += computeMessageTokens(m);
|
|
6628
|
-
}
|
|
6629
|
-
return total;
|
|
6630
|
-
}
|
|
6631
|
-
function estimateToolDefTokens(tool) {
|
|
6632
|
-
const cached = tool._estDefTokens;
|
|
6633
|
-
if (typeof cached === "number" && cached > 0) return cached;
|
|
6634
|
-
return RoughTokenEstimate(tool.name) + RoughTokenEstimate(tool.description ?? "") + RoughTokenEstimate(JSON.stringify(tool.inputSchema));
|
|
6635
|
-
}
|
|
6636
|
-
function estimateRequestTokens(messages, systemPrompt, tools, calibrationKey = CALIBRATION_GLOBAL_KEY) {
|
|
6637
|
-
let messagesTokens = 0;
|
|
6638
|
-
if (typeof messages === "string") {
|
|
6639
|
-
messagesTokens = RoughTokenEstimate(messages);
|
|
6640
|
-
} else if (Array.isArray(messages)) {
|
|
6641
|
-
for (const m of messages) {
|
|
6642
|
-
if (typeof m === "object" && m !== null && "content" in m) {
|
|
6643
|
-
const cached = m._estTokens;
|
|
6644
|
-
if (typeof cached === "number" && cached > 0) {
|
|
6645
|
-
messagesTokens += cached;
|
|
6646
|
-
continue;
|
|
6647
|
-
}
|
|
6648
|
-
const content = m.content;
|
|
6649
|
-
if (typeof content === "string") {
|
|
6650
|
-
messagesTokens += RoughTokenEstimate(content);
|
|
6651
|
-
} else if (Array.isArray(content)) {
|
|
6652
|
-
for (const b of content) {
|
|
6653
|
-
if (typeof b === "object" && b !== null) {
|
|
6654
|
-
if (b.type === "text") {
|
|
6655
|
-
messagesTokens += RoughTokenEstimate(b.text);
|
|
6656
|
-
} else {
|
|
6657
|
-
messagesTokens += RoughTokenEstimate(JSON.stringify(b));
|
|
6658
|
-
}
|
|
6659
|
-
}
|
|
6660
|
-
}
|
|
6661
|
-
}
|
|
6662
|
-
}
|
|
6663
|
-
}
|
|
6664
|
-
}
|
|
6665
|
-
let systemTokens = 0;
|
|
6666
|
-
if (typeof systemPrompt === "string") {
|
|
6667
|
-
systemTokens = RoughTokenEstimate(systemPrompt);
|
|
6668
|
-
} else if (Array.isArray(systemPrompt)) {
|
|
6669
|
-
for (const b of systemPrompt) {
|
|
6670
|
-
if (typeof b === "object" && b !== null && b.type === "text") {
|
|
6671
|
-
systemTokens += RoughTokenEstimate(b.text);
|
|
6672
|
-
}
|
|
6673
|
-
}
|
|
6674
|
-
}
|
|
6675
|
-
let toolsTokens = 0;
|
|
6676
|
-
for (const t of tools) {
|
|
6677
|
-
toolsTokens += estimateToolDefTokens(t);
|
|
6678
|
-
}
|
|
6679
|
-
const total = messagesTokens + systemTokens + toolsTokens;
|
|
6680
|
-
calState(calibrationKey).prevEst = total;
|
|
6681
|
-
return {
|
|
6682
|
-
messages: messagesTokens,
|
|
6683
|
-
systemPrompt: systemTokens,
|
|
6684
|
-
tools: toolsTokens,
|
|
6685
|
-
total
|
|
6686
|
-
};
|
|
6687
|
-
}
|
|
6688
|
-
function getCalibrationState(calibrationKey = CALIBRATION_GLOBAL_KEY) {
|
|
6689
|
-
const cal = calState(calibrationKey);
|
|
6690
|
-
return {
|
|
6691
|
-
ratio: cal.ratio,
|
|
6692
|
-
count: cal.count,
|
|
6693
|
-
calibrated: cal.count >= MIN_SAMPLES_FOR_CALIBRATION
|
|
6694
|
-
};
|
|
6695
|
-
}
|
|
6696
|
-
function estimateRequestTokensCalibrated(messages, systemPrompt, tools, calibrationKey = CALIBRATION_GLOBAL_KEY) {
|
|
6697
|
-
const result = estimateRequestTokens(messages, systemPrompt, tools, calibrationKey);
|
|
6698
|
-
const cal = calState(calibrationKey);
|
|
6699
|
-
if (cal.count >= MIN_SAMPLES_FOR_CALIBRATION) {
|
|
6700
|
-
const safeRatio = Math.min(1.5, Math.max(0.5, cal.ratio));
|
|
6701
|
-
return {
|
|
6702
|
-
messages: Math.round(result.messages * safeRatio),
|
|
6703
|
-
systemPrompt: Math.round(result.systemPrompt * safeRatio),
|
|
6704
|
-
tools: Math.round(result.tools * safeRatio),
|
|
6705
|
-
total: Math.round(result.total * safeRatio)
|
|
6706
|
-
};
|
|
7239
|
+
const onAbort = () => {
|
|
7240
|
+
if (settled) return;
|
|
7241
|
+
settled = true;
|
|
7242
|
+
clearTimeout(t);
|
|
7243
|
+
reject(new Error("aborted"));
|
|
7244
|
+
};
|
|
7245
|
+
const t = setTimeout(() => {
|
|
7246
|
+
if (settled) return;
|
|
7247
|
+
settled = true;
|
|
7248
|
+
clearTimeout(t);
|
|
7249
|
+
signal.removeEventListener("abort", onAbort);
|
|
7250
|
+
resolve5();
|
|
7251
|
+
}, delay);
|
|
7252
|
+
if (signal.aborted) {
|
|
7253
|
+
onAbort();
|
|
7254
|
+
return;
|
|
7255
|
+
}
|
|
7256
|
+
signal.addEventListener("abort", onAbort, { once: true });
|
|
7257
|
+
});
|
|
7258
|
+
attempt++;
|
|
7259
|
+
}
|
|
6707
7260
|
}
|
|
6708
|
-
return result;
|
|
6709
7261
|
}
|
|
6710
7262
|
|
|
7263
|
+
// src/execution/provider-runner-impl.ts
|
|
7264
|
+
var DefaultProviderRunner = class {
|
|
7265
|
+
async run(opts) {
|
|
7266
|
+
return runProviderWithRetry(opts);
|
|
7267
|
+
}
|
|
7268
|
+
};
|
|
7269
|
+
|
|
6711
7270
|
// src/types/blocks.ts
|
|
6712
7271
|
function isTextBlock(b) {
|
|
6713
7272
|
return b.type === "text";
|
|
6714
7273
|
}
|
|
6715
7274
|
|
|
6716
7275
|
// src/execution/compaction-core.ts
|
|
7276
|
+
function compactionDebugEnabled() {
|
|
7277
|
+
return process.env["NODE_ENV"] === "development" || process.env["WRONGSTACK_DEBUG"] === "1";
|
|
7278
|
+
}
|
|
6717
7279
|
function emitCompactionMetrics(event, metrics) {
|
|
7280
|
+
if (!compactionDebugEnabled()) return;
|
|
6718
7281
|
console.log(
|
|
6719
7282
|
JSON.stringify({
|
|
6720
7283
|
level: "debug",
|
|
@@ -6769,18 +7332,20 @@ function findPreserveStart(messages, preserveK) {
|
|
|
6769
7332
|
}
|
|
6770
7333
|
}
|
|
6771
7334
|
}
|
|
6772
|
-
|
|
6773
|
-
|
|
6774
|
-
|
|
6775
|
-
|
|
6776
|
-
|
|
6777
|
-
|
|
6778
|
-
|
|
6779
|
-
|
|
6780
|
-
|
|
6781
|
-
|
|
6782
|
-
|
|
6783
|
-
|
|
7335
|
+
if (compactionDebugEnabled()) {
|
|
7336
|
+
console.log(
|
|
7337
|
+
JSON.stringify({
|
|
7338
|
+
level: "debug",
|
|
7339
|
+
event: "compaction.find_preserve_start.ended",
|
|
7340
|
+
messageCount: messages.length,
|
|
7341
|
+
preserveK,
|
|
7342
|
+
preserveStart,
|
|
7343
|
+
forwardWalkIterations,
|
|
7344
|
+
forwardWalkInnerIterations,
|
|
7345
|
+
forwardWalkInnerPerOuter: forwardWalkIterations > 0 ? forwardWalkInnerIterations / forwardWalkIterations : 0
|
|
7346
|
+
})
|
|
7347
|
+
);
|
|
7348
|
+
}
|
|
6784
7349
|
return preserveStart;
|
|
6785
7350
|
}
|
|
6786
7351
|
function eliseOldToolResults(messages, opts) {
|
|
@@ -6847,7 +7412,7 @@ function eliseOldToolResults(messages, opts) {
|
|
|
6847
7412
|
changed = true;
|
|
6848
7413
|
}
|
|
6849
7414
|
fullPassInnerIterations += original.length;
|
|
6850
|
-
if (
|
|
7415
|
+
if (compactionDebugEnabled()) {
|
|
6851
7416
|
const ratio = fullPassInnerIterations / fullPassIterations;
|
|
6852
7417
|
if (ratio > 10) {
|
|
6853
7418
|
console.error(
|
|
@@ -7254,7 +7819,12 @@ var IntelligentCompactor = class {
|
|
|
7254
7819
|
};
|
|
7255
7820
|
const ac = ctx.signal ? void 0 : new AbortController();
|
|
7256
7821
|
const signal = ctx.signal ?? ac?.signal;
|
|
7257
|
-
|
|
7822
|
+
let res;
|
|
7823
|
+
try {
|
|
7824
|
+
res = await this.provider.complete(req, { signal });
|
|
7825
|
+
} finally {
|
|
7826
|
+
ac?.abort();
|
|
7827
|
+
}
|
|
7258
7828
|
const textBlocks = res.content.filter(isTextBlock);
|
|
7259
7829
|
return textBlocks.map((b) => b.text).join("\n").trim() || "(empty summary)";
|
|
7260
7830
|
}
|
|
@@ -7298,9 +7868,9 @@ Rules:
|
|
|
7298
7868
|
- If unsure, keep rather than collapse (errors are more costly than waste)
|
|
7299
7869
|
|
|
7300
7870
|
Return ONLY the JSON object, no markdown, no explanation outside the JSON.`;
|
|
7301
|
-
function formatMessages(messages,
|
|
7871
|
+
function formatMessages(messages, maxTokens = 2048) {
|
|
7302
7872
|
const lines = [];
|
|
7303
|
-
let
|
|
7873
|
+
let usedTokens = 0;
|
|
7304
7874
|
for (let i = 0; i < messages.length; i++) {
|
|
7305
7875
|
const m = expectDefined(messages[i]);
|
|
7306
7876
|
const role = m.role.padEnd(10, " ");
|
|
@@ -7312,13 +7882,14 @@ function formatMessages(messages, maxChars = 8e3) {
|
|
|
7312
7882
|
text = content.filter(isTextBlock).map((b) => b.text).join(" ");
|
|
7313
7883
|
const toolUses = content.filter((b) => b.type === "tool_use");
|
|
7314
7884
|
if (toolUses.length > 0) {
|
|
7315
|
-
text += ` [tools: ${toolUses.map((b) => b.name).join(", ")}]`;
|
|
7885
|
+
text += ` [tools: ${toolUses.map((b) => b.name).filter(Boolean).join(", ")}]`;
|
|
7316
7886
|
}
|
|
7317
7887
|
}
|
|
7318
7888
|
const line = `[${i}][${role}]: ${text}`;
|
|
7319
|
-
|
|
7889
|
+
const lineTokens = estimateTextTokens(line);
|
|
7890
|
+
if (usedTokens + lineTokens > maxTokens) break;
|
|
7320
7891
|
lines.push(line);
|
|
7321
|
-
|
|
7892
|
+
usedTokens += lineTokens;
|
|
7322
7893
|
}
|
|
7323
7894
|
return lines.join("\n");
|
|
7324
7895
|
}
|
|
@@ -7327,20 +7898,29 @@ var LLMSelector = class {
|
|
|
7327
7898
|
model;
|
|
7328
7899
|
maxContextTokens;
|
|
7329
7900
|
systemPrompt;
|
|
7901
|
+
maxOutputTokens;
|
|
7330
7902
|
constructor(opts) {
|
|
7331
7903
|
this.provider = opts.provider;
|
|
7332
7904
|
this.model = opts.model ?? "unknown";
|
|
7905
|
+
if (this.model === "unknown" && (process.env["NODE_ENV"] === "development" || process.env["WRONGSTACK_DEBUG"] === "1")) {
|
|
7906
|
+
console.warn(
|
|
7907
|
+
"[LLMSelector] model not set \u2014 selector will use the provider default. Set `model` explicitly in LLMSelectorOptions to silence this warning."
|
|
7908
|
+
);
|
|
7909
|
+
}
|
|
7333
7910
|
this.maxContextTokens = opts.maxContextTokens ?? 4e4;
|
|
7334
7911
|
this.systemPrompt = opts.systemPrompt ?? DEFAULT_SYSTEM_PROMPT;
|
|
7912
|
+
this.maxOutputTokens = opts.maxOutputTokens ?? 1024;
|
|
7335
7913
|
}
|
|
7336
7914
|
async select(messages, maxToKeep) {
|
|
7337
7915
|
const effectiveBudget = Math.min(maxToKeep, this.maxContextTokens);
|
|
7338
|
-
const historyText = formatMessages(messages);
|
|
7339
7916
|
const totalTokens = estimateMessageTokens(messages);
|
|
7340
7917
|
const systemText = `${this.systemPrompt}
|
|
7341
7918
|
|
|
7342
7919
|
Conversation (${messages.length} messages, ~${totalTokens} tokens, budget: ${effectiveBudget}):
|
|
7343
7920
|
`;
|
|
7921
|
+
const systemTokens = estimateTextTokens(systemText);
|
|
7922
|
+
const historyBudget = Math.max(512, effectiveBudget - systemTokens - this.maxOutputTokens);
|
|
7923
|
+
const historyText = formatMessages(messages, historyBudget);
|
|
7344
7924
|
const budgetInstruction = totalTokens > effectiveBudget ? `
|
|
7345
7925
|
|
|
7346
7926
|
IMPORTANT: Total conversation (${totalTokens} tokens) exceeds budget (${effectiveBudget}). You MUST collapse enough to fit. Prefer collapsing older/lower-importance ranges.` : "";
|
|
@@ -7348,18 +7928,26 @@ IMPORTANT: Total conversation (${totalTokens} tokens) exceeds budget (${effectiv
|
|
|
7348
7928
|
model: this.model,
|
|
7349
7929
|
system: [{ type: "text", text: systemText + budgetInstruction }],
|
|
7350
7930
|
messages: [{ role: "user", content: historyText }],
|
|
7351
|
-
maxTokens:
|
|
7931
|
+
maxTokens: this.maxOutputTokens
|
|
7352
7932
|
};
|
|
7353
7933
|
let raw;
|
|
7934
|
+
const ac = new AbortController();
|
|
7354
7935
|
try {
|
|
7355
|
-
const
|
|
7356
|
-
const res = await this.provider.complete(req, {
|
|
7936
|
+
const timeoutSignal = AbortSignal.timeout(3e4);
|
|
7937
|
+
const res = await this.provider.complete(req, {
|
|
7938
|
+
signal: AbortSignal.any([ac.signal, timeoutSignal])
|
|
7939
|
+
});
|
|
7357
7940
|
const textBlocks = res.content.filter(isTextBlock);
|
|
7358
7941
|
raw = textBlocks.map((b) => b.text).join("\n").trim();
|
|
7359
|
-
} catch (
|
|
7942
|
+
} catch (err) {
|
|
7943
|
+
if (err instanceof Error) {
|
|
7944
|
+
console.warn("[LLMSelector] selector call failed, using recency fallback:", err.message);
|
|
7945
|
+
}
|
|
7360
7946
|
return this.fallbackSelect(messages, effectiveBudget);
|
|
7947
|
+
} finally {
|
|
7948
|
+
ac.abort();
|
|
7361
7949
|
}
|
|
7362
|
-
return this.parseSelectorOutput(raw, messages
|
|
7950
|
+
return this.parseSelectorOutput(raw, messages);
|
|
7363
7951
|
}
|
|
7364
7952
|
fallbackSelect(messages, budget) {
|
|
7365
7953
|
const toKeep = [];
|
|
@@ -7386,34 +7974,63 @@ IMPORTANT: Total conversation (${totalTokens} tokens) exceeds budget (${effectiv
|
|
|
7386
7974
|
reasoning: `Fallback: kept last ${messages.length - startIdx} messages within ${budget} token budget`
|
|
7387
7975
|
};
|
|
7388
7976
|
}
|
|
7389
|
-
|
|
7977
|
+
/**
|
|
7978
|
+
* Parse and validate the raw LLM output into a SelectorResult.
|
|
7979
|
+
* Falls back to recency-based selection if the LLM output is malformed,
|
|
7980
|
+
* out-of-bounds, or internally inconsistent.
|
|
7981
|
+
*/
|
|
7982
|
+
parseSelectorOutput(raw, messages) {
|
|
7983
|
+
const messageCount = messages.length;
|
|
7984
|
+
if (messageCount === 0) {
|
|
7985
|
+
return { kept: [], collapsed: [], reasoning: "empty session" };
|
|
7986
|
+
}
|
|
7390
7987
|
const jsonStart = raw.indexOf("{");
|
|
7391
7988
|
const jsonEnd = raw.lastIndexOf("}");
|
|
7392
7989
|
if (jsonStart === -1 || jsonEnd === -1) {
|
|
7393
|
-
return this.fallbackSelect(
|
|
7394
|
-
Array.from({ length: messageCount }, () => ({ role: "user", content: "" })),
|
|
7395
|
-
this.maxContextTokens
|
|
7396
|
-
);
|
|
7990
|
+
return this.fallbackSelect(messages, this.maxContextTokens);
|
|
7397
7991
|
}
|
|
7398
7992
|
let parsed;
|
|
7399
7993
|
try {
|
|
7400
7994
|
parsed = JSON.parse(raw.slice(jsonStart, jsonEnd + 1));
|
|
7401
7995
|
} catch {
|
|
7402
|
-
return this.fallbackSelect(
|
|
7403
|
-
Array.from({ length: messageCount }, () => ({ role: "user", content: "" })),
|
|
7404
|
-
this.maxContextTokens
|
|
7405
|
-
);
|
|
7996
|
+
return this.fallbackSelect(messages, this.maxContextTokens);
|
|
7406
7997
|
}
|
|
7407
7998
|
const obj = parsed;
|
|
7408
|
-
const
|
|
7409
|
-
const
|
|
7410
|
-
|
|
7411
|
-
|
|
7999
|
+
const keptRaw = obj.kept ?? [];
|
|
8000
|
+
const collapsedRaw = obj.collapsed ?? [];
|
|
8001
|
+
const kept = [];
|
|
8002
|
+
for (const k of keptRaw) {
|
|
8003
|
+
if (typeof k.from !== "number" || typeof k.to !== "number" || k.from < 0 || k.to >= messageCount || k.from > k.to) {
|
|
8004
|
+
return this.fallbackSelect(messages, this.maxContextTokens);
|
|
8005
|
+
}
|
|
8006
|
+
kept.push({
|
|
7412
8007
|
from: k.from,
|
|
7413
8008
|
to: k.to,
|
|
7414
8009
|
importance: k.importance ?? "medium"
|
|
7415
|
-
})
|
|
7416
|
-
|
|
8010
|
+
});
|
|
8011
|
+
}
|
|
8012
|
+
const collapsed = [];
|
|
8013
|
+
for (const c of collapsedRaw) {
|
|
8014
|
+
if (typeof c.from !== "number" || typeof c.to !== "number" || c.from < 0 || c.to >= messageCount || c.from > c.to) {
|
|
8015
|
+
return this.fallbackSelect(messages, this.maxContextTokens);
|
|
8016
|
+
}
|
|
8017
|
+
collapsed.push({ from: c.from, to: c.to, summary: c.summary });
|
|
8018
|
+
}
|
|
8019
|
+
const allRanges = [...kept, ...collapsed];
|
|
8020
|
+
for (let i = 0; i < allRanges.length; i++) {
|
|
8021
|
+
const a = allRanges[i];
|
|
8022
|
+
if (!a) continue;
|
|
8023
|
+
for (let j = i + 1; j < allRanges.length; j++) {
|
|
8024
|
+
const b = allRanges[j];
|
|
8025
|
+
if (!b) continue;
|
|
8026
|
+
if (a.from <= b.to && a.to >= b.from) {
|
|
8027
|
+
return this.fallbackSelect(messages, this.maxContextTokens);
|
|
8028
|
+
}
|
|
8029
|
+
}
|
|
8030
|
+
}
|
|
8031
|
+
return {
|
|
8032
|
+
kept,
|
|
8033
|
+
collapsed,
|
|
7417
8034
|
reasoning: typeof obj.reasoning === "string" ? obj.reasoning : ""
|
|
7418
8035
|
};
|
|
7419
8036
|
}
|
|
@@ -7433,7 +8050,7 @@ var SelectiveCompactor = class {
|
|
|
7433
8050
|
summarizerPrompt;
|
|
7434
8051
|
constructor(opts) {
|
|
7435
8052
|
this.provider = opts.provider;
|
|
7436
|
-
this.selector = opts.selector ?? new LLMSelector({ provider: opts.provider, model: opts.selectorModel });
|
|
8053
|
+
this.selector = opts.selector ?? new LLMSelector({ provider: opts.provider, model: opts.selectorModel, maxOutputTokens: opts.selectorMaxOutputTokens });
|
|
7437
8054
|
this.warnThreshold = opts.warnThreshold ?? 0.6;
|
|
7438
8055
|
this.softThreshold = opts.softThreshold ?? 0.75;
|
|
7439
8056
|
this.hardThreshold = opts.hardThreshold ?? 0.9;
|
|
@@ -7441,6 +8058,11 @@ var SelectiveCompactor = class {
|
|
|
7441
8058
|
this.preserveK = opts.preserveK ?? 4;
|
|
7442
8059
|
this.eliseThreshold = opts.eliseThreshold ?? 500;
|
|
7443
8060
|
this.summarizerModel = opts.summarizerModel ?? opts.selectorModel ?? "unknown";
|
|
8061
|
+
if (this.summarizerModel === "unknown" && (process.env["NODE_ENV"] === "development" || process.env["WRONGSTACK_DEBUG"] === "1")) {
|
|
8062
|
+
console.warn(
|
|
8063
|
+
"[SelectiveCompactor] summarizerModel not set \u2014 will use provider default. Set `summarizerModel` explicitly to silence this warning."
|
|
8064
|
+
);
|
|
8065
|
+
}
|
|
7444
8066
|
this.summarizerPrompt = opts.summarizerPrompt ?? "You are a context summarizer. Given a list of messages, produce a concise summary that preserves all factual information, decisions, file changes, and state changes. Do not add commentary or opinions.";
|
|
7445
8067
|
}
|
|
7446
8068
|
async compact(ctx, opts = {}) {
|
|
@@ -7552,8 +8174,9 @@ Summarize the following message range:`;
|
|
|
7552
8174
|
maxTokens: 512
|
|
7553
8175
|
};
|
|
7554
8176
|
try {
|
|
8177
|
+
const timeoutSignal = AbortSignal.timeout(3e4);
|
|
7555
8178
|
const res = await this.provider.complete(req, {
|
|
7556
|
-
signal: ctx.signal
|
|
8179
|
+
signal: AbortSignal.any([ctx.signal, timeoutSignal])
|
|
7557
8180
|
});
|
|
7558
8181
|
return res.content.filter(isTextBlock).map((b) => b.text).join("\n").trim() || "(empty)";
|
|
7559
8182
|
} catch {
|
|
@@ -7687,6 +8310,7 @@ var ProviderBackedCompactor = class {
|
|
|
7687
8310
|
return new SelectiveCompactor({
|
|
7688
8311
|
...common,
|
|
7689
8312
|
selectorModel: this.opts.summarizerModel,
|
|
8313
|
+
selectorMaxOutputTokens: this.opts.selectorMaxOutputTokens,
|
|
7690
8314
|
summarizerModel: this.opts.summarizerModel
|
|
7691
8315
|
});
|
|
7692
8316
|
}
|
|
@@ -7721,6 +8345,12 @@ var AutoCompactionMiddleware = class _AutoCompactionMiddleware {
|
|
|
7721
8345
|
hardThreshold;
|
|
7722
8346
|
/** Writable so model-switch can update the denominator without re-registering the middleware. */
|
|
7723
8347
|
_maxContext;
|
|
8348
|
+
/**
|
|
8349
|
+
* Runtime on/off gate. The middleware is always installed in the pipeline so
|
|
8350
|
+
* auto-compaction can be toggled live from the TUI `/settings` picker; when
|
|
8351
|
+
* disabled the handler is a pass-through. Defaults to enabled.
|
|
8352
|
+
*/
|
|
8353
|
+
_enabled = true;
|
|
7724
8354
|
aggressiveOn;
|
|
7725
8355
|
events;
|
|
7726
8356
|
failureMode;
|
|
@@ -7778,8 +8408,19 @@ var AutoCompactionMiddleware = class _AutoCompactionMiddleware {
|
|
|
7778
8408
|
setMaxContext(maxContext) {
|
|
7779
8409
|
this._maxContext = maxContext;
|
|
7780
8410
|
}
|
|
8411
|
+
/** Whether auto-compaction is currently active. */
|
|
8412
|
+
get enabled() {
|
|
8413
|
+
return this._enabled;
|
|
8414
|
+
}
|
|
8415
|
+
/** Toggle auto-compaction on a live session (TUI `/settings`). When disabled
|
|
8416
|
+
* the middleware passes every iteration straight through without estimating
|
|
8417
|
+
* tokens or compacting. */
|
|
8418
|
+
setEnabled(enabled) {
|
|
8419
|
+
this._enabled = enabled;
|
|
8420
|
+
}
|
|
7781
8421
|
handler() {
|
|
7782
8422
|
return async (ctx, next) => {
|
|
8423
|
+
if (!this._enabled) return next(ctx);
|
|
7783
8424
|
const msgCount = ctx.messages.length;
|
|
7784
8425
|
const toolCount = (ctx.tools ?? []).length;
|
|
7785
8426
|
let tokens;
|
|
@@ -7921,144 +8562,6 @@ var AutoCompactionMiddleware = class _AutoCompactionMiddleware {
|
|
|
7921
8562
|
}
|
|
7922
8563
|
};
|
|
7923
8564
|
|
|
7924
|
-
// src/utils/json-schema-validate.ts
|
|
7925
|
-
function validateAgainstSchema(value, schema) {
|
|
7926
|
-
const errors = [];
|
|
7927
|
-
walk2(value, schema, "", errors);
|
|
7928
|
-
return { ok: errors.length === 0, errors };
|
|
7929
|
-
}
|
|
7930
|
-
function walk2(value, schema, path19, errors) {
|
|
7931
|
-
if (schema.enum !== void 0) {
|
|
7932
|
-
if (!schema.enum.some((e) => deepEqual(e, value))) {
|
|
7933
|
-
errors.push({
|
|
7934
|
-
path: path19 || "<root>",
|
|
7935
|
-
message: `expected one of ${JSON.stringify(schema.enum)}, got ${JSON.stringify(value)}`
|
|
7936
|
-
});
|
|
7937
|
-
return;
|
|
7938
|
-
}
|
|
7939
|
-
}
|
|
7940
|
-
if (typeof schema.type === "string") {
|
|
7941
|
-
if (!checkType(value, schema.type)) {
|
|
7942
|
-
errors.push({
|
|
7943
|
-
path: path19 || "<root>",
|
|
7944
|
-
message: `expected ${schema.type}, got ${describeType(value)}`
|
|
7945
|
-
});
|
|
7946
|
-
return;
|
|
7947
|
-
}
|
|
7948
|
-
}
|
|
7949
|
-
if (schema.type === "object" && isPlainObject(value)) {
|
|
7950
|
-
const obj = value;
|
|
7951
|
-
for (const req of schema.required ?? []) {
|
|
7952
|
-
if (!(req in obj)) {
|
|
7953
|
-
errors.push({ path: joinPath(path19, req), message: "required property missing" });
|
|
7954
|
-
}
|
|
7955
|
-
}
|
|
7956
|
-
if (schema.properties) {
|
|
7957
|
-
for (const [key, subSchema] of Object.entries(schema.properties)) {
|
|
7958
|
-
if (key in obj) {
|
|
7959
|
-
walk2(obj[key], subSchema, joinPath(path19, key), errors);
|
|
7960
|
-
}
|
|
7961
|
-
}
|
|
7962
|
-
}
|
|
7963
|
-
}
|
|
7964
|
-
if (schema.type === "array" && Array.isArray(value) && schema.items) {
|
|
7965
|
-
value.forEach((item, i) => walk2(item, schema.items, `${path19}[${i}]`, errors));
|
|
7966
|
-
}
|
|
7967
|
-
}
|
|
7968
|
-
function checkType(value, type) {
|
|
7969
|
-
switch (type) {
|
|
7970
|
-
case "string":
|
|
7971
|
-
return typeof value === "string";
|
|
7972
|
-
case "number":
|
|
7973
|
-
return typeof value === "number" && !Number.isNaN(value);
|
|
7974
|
-
case "integer":
|
|
7975
|
-
return typeof value === "number" && Number.isInteger(value);
|
|
7976
|
-
case "boolean":
|
|
7977
|
-
return typeof value === "boolean";
|
|
7978
|
-
case "null":
|
|
7979
|
-
return value === null;
|
|
7980
|
-
case "array":
|
|
7981
|
-
return Array.isArray(value);
|
|
7982
|
-
case "object":
|
|
7983
|
-
return isPlainObject(value);
|
|
7984
|
-
default:
|
|
7985
|
-
return true;
|
|
7986
|
-
}
|
|
7987
|
-
}
|
|
7988
|
-
function isPlainObject(v) {
|
|
7989
|
-
return typeof v === "object" && v !== null && !Array.isArray(v);
|
|
7990
|
-
}
|
|
7991
|
-
function describeType(v) {
|
|
7992
|
-
if (v === null) return "null";
|
|
7993
|
-
if (Array.isArray(v)) return "array";
|
|
7994
|
-
return typeof v;
|
|
7995
|
-
}
|
|
7996
|
-
function joinPath(parent, key) {
|
|
7997
|
-
if (!parent) return key;
|
|
7998
|
-
return `${parent}.${key}`;
|
|
7999
|
-
}
|
|
8000
|
-
function deepEqual(a, b) {
|
|
8001
|
-
if (a === b) return true;
|
|
8002
|
-
if (typeof a !== typeof b) return false;
|
|
8003
|
-
if (a === null || b === null) return a === b;
|
|
8004
|
-
if (Array.isArray(a) && Array.isArray(b)) {
|
|
8005
|
-
return a.length === b.length && a.every((v, i) => deepEqual(v, b[i]));
|
|
8006
|
-
}
|
|
8007
|
-
if (typeof a === "object" && typeof b === "object") {
|
|
8008
|
-
const ak = Object.keys(a);
|
|
8009
|
-
const bk = Object.keys(b);
|
|
8010
|
-
if (ak.length !== bk.length) return false;
|
|
8011
|
-
return ak.every(
|
|
8012
|
-
(k) => deepEqual(a[k], b[k])
|
|
8013
|
-
);
|
|
8014
|
-
}
|
|
8015
|
-
return false;
|
|
8016
|
-
}
|
|
8017
|
-
|
|
8018
|
-
// src/utils/tool-output-serializer.ts
|
|
8019
|
-
function createToolOutputSerializer(opts = {}) {
|
|
8020
|
-
const capBytes = opts.perIterationOutputCapBytes ?? 1e5;
|
|
8021
|
-
function serialize(value) {
|
|
8022
|
-
if (typeof value === "string") return value;
|
|
8023
|
-
if (value === null || value === void 0) return "";
|
|
8024
|
-
if (typeof value === "object") {
|
|
8025
|
-
if (Array.isArray(value)) return value.map(serialize).join("\n");
|
|
8026
|
-
if ("text" in value) {
|
|
8027
|
-
const t = value.text;
|
|
8028
|
-
return typeof t === "string" ? t : JSON.stringify(value, null, 2);
|
|
8029
|
-
}
|
|
8030
|
-
try {
|
|
8031
|
-
return JSON.stringify(value, null, 2);
|
|
8032
|
-
} catch {
|
|
8033
|
-
return String(value);
|
|
8034
|
-
}
|
|
8035
|
-
}
|
|
8036
|
-
return String(value);
|
|
8037
|
-
}
|
|
8038
|
-
function enforceCap(text, remainingBudget) {
|
|
8039
|
-
if (remainingBudget <= 0) {
|
|
8040
|
-
return { text: "[truncated: iteration output cap exceeded]", newBudget: 0 };
|
|
8041
|
-
}
|
|
8042
|
-
const textBytes = Buffer.byteLength(text, "utf8");
|
|
8043
|
-
if (textBytes <= remainingBudget) {
|
|
8044
|
-
return { text, newBudget: remainingBudget - textBytes };
|
|
8045
|
-
}
|
|
8046
|
-
const marker = `
|
|
8047
|
-
\u2026[truncated ${textBytes - remainingBudget} bytes]\u2026
|
|
8048
|
-
`;
|
|
8049
|
-
const markerBytes = Buffer.byteLength(marker, "utf8");
|
|
8050
|
-
const available = remainingBudget - markerBytes;
|
|
8051
|
-
if (available <= 0) {
|
|
8052
|
-
return { text: "[truncated: iteration output cap exceeded]", newBudget: 0 };
|
|
8053
|
-
}
|
|
8054
|
-
const half = Math.floor(available / 2);
|
|
8055
|
-
const first = text.slice(0, half);
|
|
8056
|
-
const second = text.slice(text.length - half);
|
|
8057
|
-
return { text: `${first}${marker}${second}`, newBudget: 0 };
|
|
8058
|
-
}
|
|
8059
|
-
return { serialize, enforceCap, capBytes };
|
|
8060
|
-
}
|
|
8061
|
-
|
|
8062
8565
|
// src/execution/tool-executor.ts
|
|
8063
8566
|
var ToolExecutor = class _ToolExecutor {
|
|
8064
8567
|
constructor(registry, opts) {
|
|
@@ -8222,7 +8725,7 @@ ${post.additionalContext}`;
|
|
|
8222
8725
|
);
|
|
8223
8726
|
return { result, tool, durationMs: Date.now() - start };
|
|
8224
8727
|
} catch (err) {
|
|
8225
|
-
const msg =
|
|
8728
|
+
const msg = toErrorMessage(err);
|
|
8226
8729
|
const scrubbed = this.opts.secretScrubber.scrub(msg);
|
|
8227
8730
|
this.opts.renderer?.writeToolResult(tool.name, scrubbed, true);
|
|
8228
8731
|
const result = {
|
|
@@ -8243,7 +8746,7 @@ ${post.additionalContext}`;
|
|
|
8243
8746
|
try {
|
|
8244
8747
|
return await runOne(use);
|
|
8245
8748
|
} catch (err) {
|
|
8246
|
-
const msg =
|
|
8749
|
+
const msg = toErrorMessage(err);
|
|
8247
8750
|
const scrubbed = this.opts.secretScrubber.scrub(msg);
|
|
8248
8751
|
const result = {
|
|
8249
8752
|
type: "tool_result",
|
|
@@ -8526,15 +9029,6 @@ function extractMalformedRaw(input) {
|
|
|
8526
9029
|
}
|
|
8527
9030
|
}
|
|
8528
9031
|
|
|
8529
|
-
// src/utils/assert-never.ts
|
|
8530
|
-
function assertNever(x, message) {
|
|
8531
|
-
const err = new Error(
|
|
8532
|
-
`Unhandled case: ${JSON.stringify(x)}`
|
|
8533
|
-
);
|
|
8534
|
-
err.name = "AssertNeverError";
|
|
8535
|
-
throw err;
|
|
8536
|
-
}
|
|
8537
|
-
|
|
8538
9032
|
// src/execution/autonomous-runner.ts
|
|
8539
9033
|
var DoneConditionChecker = class {
|
|
8540
9034
|
constructor(condition) {
|
|
@@ -8714,64 +9208,6 @@ var AutonomousRunner = class {
|
|
|
8714
9208
|
|
|
8715
9209
|
// src/storage/goal-store.ts
|
|
8716
9210
|
init_atomic_write();
|
|
8717
|
-
function projectHash(absRoot) {
|
|
8718
|
-
return createHash("sha256").update(path11.resolve(absRoot)).digest("hex").slice(0, 12);
|
|
8719
|
-
}
|
|
8720
|
-
function projectSlug(absRoot) {
|
|
8721
|
-
const base = slugify(path11.basename(absRoot));
|
|
8722
|
-
const hash = createHash("sha256").update(path11.resolve(absRoot)).digest("hex").slice(0, 6);
|
|
8723
|
-
return `${base}-${hash}`;
|
|
8724
|
-
}
|
|
8725
|
-
function slugify(name) {
|
|
8726
|
-
return name.toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-+|-+$/g, "").slice(0, 40) || "project";
|
|
8727
|
-
}
|
|
8728
|
-
function wstackGlobalRoot() {
|
|
8729
|
-
const fromEnv = process.env["WRONGSTACK_HOME"];
|
|
8730
|
-
if (fromEnv && fromEnv.trim().length > 0) return path11.resolve(fromEnv);
|
|
8731
|
-
return path11.join(os.homedir(), ".wrongstack");
|
|
8732
|
-
}
|
|
8733
|
-
function resolveWstackPaths(opts) {
|
|
8734
|
-
const globalRoot = opts.globalRoot ?? (opts.userHome ? path11.join(opts.userHome, ".wrongstack") : wstackGlobalRoot());
|
|
8735
|
-
const hash = projectHash(opts.projectRoot);
|
|
8736
|
-
const slug = projectSlug(opts.projectRoot);
|
|
8737
|
-
const projectDir = path11.join(globalRoot, "projects", slug);
|
|
8738
|
-
return {
|
|
8739
|
-
globalRoot,
|
|
8740
|
-
configDir: globalRoot,
|
|
8741
|
-
globalConfig: path11.join(globalRoot, "config.json"),
|
|
8742
|
-
secretsKey: path11.join(globalRoot, ".key"),
|
|
8743
|
-
globalMemory: path11.join(globalRoot, "memory.md"),
|
|
8744
|
-
globalSkills: path11.join(globalRoot, "skills"),
|
|
8745
|
-
globalPrompts: path11.join(globalRoot, "prompts"),
|
|
8746
|
-
cacheDir: path11.join(globalRoot, "cache"),
|
|
8747
|
-
modelsCache: path11.join(globalRoot, "cache", "models.dev.json"),
|
|
8748
|
-
modelsOverlayCache: path11.join(globalRoot, "cache", "models-overlay.json"),
|
|
8749
|
-
historyFile: path11.join(globalRoot, "history"),
|
|
8750
|
-
logFile: path11.join(globalRoot, "logs", "wrongstack.log"),
|
|
8751
|
-
projectDir,
|
|
8752
|
-
projectCodebaseIndex: path11.join(projectDir, "codebase-index"),
|
|
8753
|
-
projectMemory: path11.join(projectDir, "memory.md"),
|
|
8754
|
-
projectSessions: path11.join(projectDir, "sessions"),
|
|
8755
|
-
projectTrust: path11.join(projectDir, "trust.json"),
|
|
8756
|
-
projectMeta: path11.join(projectDir, "meta.json"),
|
|
8757
|
-
projectLocalConfig: path11.join(projectDir, "config.local.json"),
|
|
8758
|
-
inProjectConfig: path11.join(opts.projectRoot, ".wrongstack", "config.json"),
|
|
8759
|
-
inProjectAgentsFile: path11.join(opts.projectRoot, ".wrongstack", "AGENTS.md"),
|
|
8760
|
-
inProjectSkills: path11.join(opts.projectRoot, ".wrongstack", "skills"),
|
|
8761
|
-
inProjectWorktrees: path11.join(opts.projectRoot, ".wrongstack", "worktrees"),
|
|
8762
|
-
projectHash: hash,
|
|
8763
|
-
projectSlug: slug,
|
|
8764
|
-
projectGoal: path11.join(projectDir, "goal.json"),
|
|
8765
|
-
projectSpecs: path11.join(projectDir, "specs"),
|
|
8766
|
-
projectTaskGraphs: path11.join(projectDir, "task-graphs"),
|
|
8767
|
-
projectSddSession: path11.join(projectDir, "sdd-session.json"),
|
|
8768
|
-
projectPlan: path11.join(projectDir, "plan.json"),
|
|
8769
|
-
projectAutophase: path11.join(projectDir, "autophase"),
|
|
8770
|
-
syncConfig: path11.join(globalRoot, "sync.json")
|
|
8771
|
-
};
|
|
8772
|
-
}
|
|
8773
|
-
|
|
8774
|
-
// src/storage/goal-store.ts
|
|
8775
9211
|
var MAX_JOURNAL_ENTRIES = 500;
|
|
8776
9212
|
function goalFilePath(projectRoot) {
|
|
8777
9213
|
return resolveWstackPaths({ projectRoot }).projectGoal;
|
|
@@ -8780,7 +9216,7 @@ async function loadGoal(filePath, events) {
|
|
|
8780
9216
|
const t0 = Date.now();
|
|
8781
9217
|
let raw;
|
|
8782
9218
|
try {
|
|
8783
|
-
raw = await
|
|
9219
|
+
raw = await fsp2.readFile(filePath, "utf8");
|
|
8784
9220
|
} catch (err) {
|
|
8785
9221
|
const code = err.code;
|
|
8786
9222
|
if (code === "ENOENT") {
|
|
@@ -8799,7 +9235,7 @@ async function loadGoal(filePath, events) {
|
|
|
8799
9235
|
store: "goal",
|
|
8800
9236
|
filePath,
|
|
8801
9237
|
operation: "load",
|
|
8802
|
-
error:
|
|
9238
|
+
error: toErrorMessage(err),
|
|
8803
9239
|
recoverable: false
|
|
8804
9240
|
});
|
|
8805
9241
|
throw err;
|
|
@@ -8872,11 +9308,11 @@ async function saveGoal(filePath, goal, events) {
|
|
|
8872
9308
|
store: "goal",
|
|
8873
9309
|
filePath,
|
|
8874
9310
|
operation: "save",
|
|
8875
|
-
error:
|
|
9311
|
+
error: toErrorMessage(err),
|
|
8876
9312
|
recoverable: false
|
|
8877
9313
|
});
|
|
8878
9314
|
throw new FsError({
|
|
8879
|
-
message:
|
|
9315
|
+
message: toErrorMessage(err),
|
|
8880
9316
|
code: ERROR_CODES.FS_ATOMIC_WRITE_FAILED,
|
|
8881
9317
|
path: filePath,
|
|
8882
9318
|
cause: err
|
|
@@ -8930,11 +9366,6 @@ function computeTrend(history) {
|
|
|
8930
9366
|
return "steady";
|
|
8931
9367
|
}
|
|
8932
9368
|
|
|
8933
|
-
// src/utils/sleep.ts
|
|
8934
|
-
function sleep(ms) {
|
|
8935
|
-
return new Promise((resolve5) => setTimeout(resolve5, ms));
|
|
8936
|
-
}
|
|
8937
|
-
|
|
8938
9369
|
// src/execution/autonomy-brain.ts
|
|
8939
9370
|
function formatDecisionSummary(decision, request) {
|
|
8940
9371
|
const question = request.question.length > 80 ? request.question.slice(0, 77) + "\u2026" : request.question;
|
|
@@ -8985,7 +9416,7 @@ var EternalAutonomyEngine = class {
|
|
|
8985
9416
|
console.error(JSON.stringify({
|
|
8986
9417
|
level: "error",
|
|
8987
9418
|
event: "engine.persist_state_failed",
|
|
8988
|
-
message:
|
|
9419
|
+
message: toErrorMessage(err),
|
|
8989
9420
|
context: { expectedState: "stopped" },
|
|
8990
9421
|
timestamp: (/* @__PURE__ */ new Date()).toISOString()
|
|
8991
9422
|
}));
|
|
@@ -9018,7 +9449,7 @@ var EternalAutonomyEngine = class {
|
|
|
9018
9449
|
} catch (err) {
|
|
9019
9450
|
this.consecutiveFailures++;
|
|
9020
9451
|
this.opts.onError?.(err instanceof Error ? err : new Error(String(err)), this.consecutiveFailures);
|
|
9021
|
-
await this.appendFailure("engine error",
|
|
9452
|
+
await this.appendFailure("engine error", toErrorMessage(err));
|
|
9022
9453
|
}
|
|
9023
9454
|
if (iterationOk) {
|
|
9024
9455
|
this.consecutiveFailures = 0;
|
|
@@ -9119,7 +9550,7 @@ var EternalAutonomyEngine = class {
|
|
|
9119
9550
|
} catch (err) {
|
|
9120
9551
|
const isAbort = err instanceof Error && (err.name === "AbortError" || err.message.includes("abort"));
|
|
9121
9552
|
status = isAbort ? "aborted" : "failure";
|
|
9122
|
-
note =
|
|
9553
|
+
note = toErrorMessage(err);
|
|
9123
9554
|
if (!isAbort && typeof err?.recoverable === "boolean") {
|
|
9124
9555
|
isTransientFailure = err.recoverable;
|
|
9125
9556
|
}
|
|
@@ -9609,6 +10040,7 @@ ${recentJournal}` : ""
|
|
|
9609
10040
|
|
|
9610
10041
|
// src/coordination/subagent-budget.ts
|
|
9611
10042
|
var TIMEOUT_PREEMPT_FRACTION = 0.85;
|
|
10043
|
+
var DECISION_TIMEOUT_MS = 6e4;
|
|
9612
10044
|
var BudgetExceededError = class extends Error {
|
|
9613
10045
|
kind;
|
|
9614
10046
|
limit;
|
|
@@ -9638,6 +10070,31 @@ var BudgetThresholdSignal = class extends Error {
|
|
|
9638
10070
|
};
|
|
9639
10071
|
var SubagentBudget = class _SubagentBudget {
|
|
9640
10072
|
limits;
|
|
10073
|
+
/** Patch one or more budget limits in-place after construction.
|
|
10074
|
+
* Used by the coordinator watchdog when granting an extension.
|
|
10075
|
+
* All fields are optional — only provided fields are updated.
|
|
10076
|
+
* This is the single write path for limit mutations so that future
|
|
10077
|
+
* validation or side-effects live in one place (M1). */
|
|
10078
|
+
patchLimits(ext) {
|
|
10079
|
+
if (ext.maxIterations !== void 0) {
|
|
10080
|
+
this.limits.maxIterations = ext.maxIterations;
|
|
10081
|
+
}
|
|
10082
|
+
if (ext.maxToolCalls !== void 0) {
|
|
10083
|
+
this.limits.maxToolCalls = ext.maxToolCalls;
|
|
10084
|
+
}
|
|
10085
|
+
if (ext.maxTokens !== void 0) {
|
|
10086
|
+
this.limits.maxTokens = ext.maxTokens;
|
|
10087
|
+
}
|
|
10088
|
+
if (ext.maxCostUsd !== void 0) {
|
|
10089
|
+
this.limits.maxCostUsd = ext.maxCostUsd;
|
|
10090
|
+
}
|
|
10091
|
+
if (ext.timeoutMs !== void 0) {
|
|
10092
|
+
this.limits.timeoutMs = ext.timeoutMs;
|
|
10093
|
+
}
|
|
10094
|
+
if (ext.idleTimeoutMs !== void 0) {
|
|
10095
|
+
this.limits.idleTimeoutMs = ext.idleTimeoutMs;
|
|
10096
|
+
}
|
|
10097
|
+
}
|
|
9641
10098
|
iterations = 0;
|
|
9642
10099
|
toolCalls = 0;
|
|
9643
10100
|
tokenInput = 0;
|
|
@@ -9658,12 +10115,44 @@ var SubagentBudget = class _SubagentBudget {
|
|
|
9658
10115
|
* or hung listener (Director not built / event filter detached mid-run)
|
|
9659
10116
|
* leaves the budget over-limit and never enforces anything.
|
|
9660
10117
|
*/
|
|
9661
|
-
static DECISION_TIMEOUT_MS =
|
|
10118
|
+
static DECISION_TIMEOUT_MS = DECISION_TIMEOUT_MS;
|
|
9662
10119
|
/**
|
|
9663
10120
|
* Injected by the runner when wiring the budget to its EventBus.
|
|
9664
10121
|
* Used to emit `budget.threshold_reached` events in `'auto'` mode.
|
|
9665
10122
|
*/
|
|
9666
|
-
_events;
|
|
10123
|
+
_events;
|
|
10124
|
+
/**
|
|
10125
|
+
* Guard against dual-path races between the coordinator watchdog
|
|
10126
|
+
* (`executeWithTimeout`) and the budget's own `checkTimeout()`.
|
|
10127
|
+
* Both paths detect `elapsed >= timeoutMs` and can emit
|
|
10128
|
+
* `budget.threshold_reached` for kind `'timeout'` simultaneously.
|
|
10129
|
+
* Set to the current `timeoutMs` ceiling by the coordinator BEFORE
|
|
10130
|
+
* calling `onThreshold`, and cleared after the negotiation resolves.
|
|
10131
|
+
* `checkTimeout()` skips its wall-clock check while this is set so
|
|
10132
|
+
* the coordinator's watchdog is the sole source of wall-clock timeout
|
|
10133
|
+
* events — `checkTimeout()` focuses exclusively on `idle_timeout`.
|
|
10134
|
+
*/
|
|
10135
|
+
_watchdogActive;
|
|
10136
|
+
/** Returns the timeout ceiling currently being negotiated by the watchdog,
|
|
10137
|
+
* or `undefined` when no wall-clock negotiation is in flight.
|
|
10138
|
+
* Used by `executeWithTimeout` to detect a stale lock (M3). */
|
|
10139
|
+
get watchdogActive() {
|
|
10140
|
+
return this._watchdogActive;
|
|
10141
|
+
}
|
|
10142
|
+
/** Called by the coordinator watchdog BEFORE calling `onThreshold` so that
|
|
10143
|
+
* `checkTimeout()` skips its wall-clock check for this ceiling. Prevents
|
|
10144
|
+
* the budget's own `checkTimeout()` from emitting a second
|
|
10145
|
+
* `budget.threshold_reached` event while the watchdog is already
|
|
10146
|
+
* negotiating the same wall-clock deadline (C1). */
|
|
10147
|
+
setWatchdogNegotiation(timeoutMs) {
|
|
10148
|
+
this._watchdogActive = timeoutMs;
|
|
10149
|
+
}
|
|
10150
|
+
/** Clears the watchdog guard after negotiation resolves. Called in the
|
|
10151
|
+
* `finally` block of both the pre-empt and deadline branches so it fires
|
|
10152
|
+
* on every exit path: grant, deny, throw, or error. */
|
|
10153
|
+
clearWatchdogNegotiation() {
|
|
10154
|
+
this._watchdogActive = void 0;
|
|
10155
|
+
}
|
|
9667
10156
|
/**
|
|
9668
10157
|
* Negotiation mode — controls whether a threshold hit tries to emit
|
|
9669
10158
|
* `budget.threshold_reached` and wait for a coordinator decision, or
|
|
@@ -9764,7 +10253,8 @@ var SubagentBudget = class _SubagentBudget {
|
|
|
9764
10253
|
if (this.limits.idleTimeoutMs !== void 0 && idle > this.limits.idleTimeoutMs) {
|
|
9765
10254
|
exceeded.push({ kind: "idle_timeout", used: idle, limit: this.limits.idleTimeoutMs });
|
|
9766
10255
|
}
|
|
9767
|
-
|
|
10256
|
+
const wallOwnedByWatchdog = this._onThreshold !== void 0 && this._watchdogActive === this.limits.timeoutMs;
|
|
10257
|
+
if (this.limits.timeoutMs !== void 0 && elapsedMs > this.limits.timeoutMs && !wallOwnedByWatchdog) {
|
|
9768
10258
|
exceeded.push({ kind: "timeout", used: elapsedMs, limit: this.limits.timeoutMs });
|
|
9769
10259
|
}
|
|
9770
10260
|
}
|
|
@@ -9778,19 +10268,99 @@ var SubagentBudget = class _SubagentBudget {
|
|
|
9778
10268
|
throw new BudgetExceededError(first2.kind, first2.limit, first2.used);
|
|
9779
10269
|
}
|
|
9780
10270
|
const bus = this._events;
|
|
9781
|
-
if (!bus
|
|
10271
|
+
if (!bus) {
|
|
9782
10272
|
const first2 = exceeded[0] ?? { kind: "iterations", limit: 0, used: 0 };
|
|
9783
10273
|
throw new BudgetExceededError(first2.kind, first2.limit, first2.used);
|
|
9784
10274
|
}
|
|
10275
|
+
const first = exceeded[0] ?? { kind: "iterations", limit: 0, used: 0 };
|
|
10276
|
+
if (bus.hasListenerFor("budget.threshold_reached")) {
|
|
10277
|
+
for (const entry of exceeded) {
|
|
10278
|
+
if (this._pendingNegotiations.has(entry.kind)) continue;
|
|
10279
|
+
this._pendingNegotiations.set(entry.kind, this._negotiateExtension(entry));
|
|
10280
|
+
}
|
|
10281
|
+
const decision = this._pendingNegotiations.get(first.kind);
|
|
10282
|
+
if (!decision) throw new Error(`No pending negotiation for ${first.kind}`);
|
|
10283
|
+
throw new BudgetThresholdSignal(first.kind, first.limit, first.used, decision);
|
|
10284
|
+
}
|
|
10285
|
+
let hardStop = null;
|
|
9785
10286
|
for (const entry of exceeded) {
|
|
9786
10287
|
if (this._pendingNegotiations.has(entry.kind)) continue;
|
|
9787
|
-
const
|
|
9788
|
-
this._pendingNegotiations.set(entry.kind,
|
|
10288
|
+
const marker = Promise.resolve("stop");
|
|
10289
|
+
this._pendingNegotiations.set(entry.kind, marker);
|
|
10290
|
+
void marker.finally(() => this._pendingNegotiations.delete(entry.kind));
|
|
10291
|
+
const sync = this._invokeHandlerSync(entry);
|
|
10292
|
+
if (!sync) hardStop ??= new BudgetExceededError(entry.kind, entry.limit, entry.used);
|
|
9789
10293
|
}
|
|
9790
|
-
|
|
9791
|
-
|
|
9792
|
-
|
|
9793
|
-
|
|
10294
|
+
if (hardStop) throw hardStop;
|
|
10295
|
+
return exceeded;
|
|
10296
|
+
}
|
|
10297
|
+
/**
|
|
10298
|
+
* Invoke `onThreshold` once for `entry` on the NO-LISTENER path and report
|
|
10299
|
+
* whether it decided synchronously. Returns `true` when the handler returned
|
|
10300
|
+
* a synchronous decision (already honored — an `extend` patched the limits),
|
|
10301
|
+
* or `false` when it returned a Promise (async; the caller hard-stops, since
|
|
10302
|
+
* there is no listener to resolve the negotiation). The handler is given the
|
|
10303
|
+
* full info shape (`requestDecision` plus direct `extend`/`deny`) so both
|
|
10304
|
+
* recording handlers and policy handlers work without a wired listener.
|
|
10305
|
+
*/
|
|
10306
|
+
_invokeHandlerSync(entry) {
|
|
10307
|
+
const handler = this._onThreshold;
|
|
10308
|
+
if (!handler) return false;
|
|
10309
|
+
let extendArg;
|
|
10310
|
+
const result = handler({
|
|
10311
|
+
kind: entry.kind,
|
|
10312
|
+
used: entry.used,
|
|
10313
|
+
limit: entry.limit,
|
|
10314
|
+
requestDecision: () => this._busRequestDecision(entry),
|
|
10315
|
+
// Direct hooks for synchronous policy/recording handlers.
|
|
10316
|
+
extend: (extra) => {
|
|
10317
|
+
extendArg = extra;
|
|
10318
|
+
},
|
|
10319
|
+
deny: () => {
|
|
10320
|
+
}
|
|
10321
|
+
});
|
|
10322
|
+
if (result && typeof result.then === "function") return false;
|
|
10323
|
+
if (result === "throw") return false;
|
|
10324
|
+
if (result && typeof result === "object" && "extend" in result) {
|
|
10325
|
+
extendArg = result.extend;
|
|
10326
|
+
}
|
|
10327
|
+
if (extendArg) this.patchLimits(extendArg);
|
|
10328
|
+
return true;
|
|
10329
|
+
}
|
|
10330
|
+
/**
|
|
10331
|
+
* Emit `budget.threshold_reached` and resolve to the listener's verdict.
|
|
10332
|
+
* Resolves to `'stop'` immediately when there is no listener (or no bus) so
|
|
10333
|
+
* no negotiation can hang and no fallback timer leaks. Mirrors the
|
|
10334
|
+
* coordinator watchdog's own request path so both agree on the no-listener
|
|
10335
|
+
* default.
|
|
10336
|
+
*/
|
|
10337
|
+
_busRequestDecision(entry) {
|
|
10338
|
+
const bus = this._events;
|
|
10339
|
+
if (!bus || !bus.hasListenerFor("budget.threshold_reached")) {
|
|
10340
|
+
return Promise.resolve("stop");
|
|
10341
|
+
}
|
|
10342
|
+
return new Promise((resolve5) => {
|
|
10343
|
+
let resolved = false;
|
|
10344
|
+
const respond = (d) => {
|
|
10345
|
+
if (resolved) return;
|
|
10346
|
+
resolved = true;
|
|
10347
|
+
clearTimeout(fallback);
|
|
10348
|
+
resolve5(d);
|
|
10349
|
+
};
|
|
10350
|
+
const fallback = setTimeout(() => respond("stop"), _SubagentBudget.DECISION_TIMEOUT_MS);
|
|
10351
|
+
bus.emit("budget.threshold_reached", {
|
|
10352
|
+
kind: entry.kind,
|
|
10353
|
+
used: entry.used,
|
|
10354
|
+
limit: entry.limit,
|
|
10355
|
+
timeoutMs: _SubagentBudget.DECISION_TIMEOUT_MS,
|
|
10356
|
+
// deny() wins over a same-dispatch extend(): a listener that both grants
|
|
10357
|
+
// and denies (or two listeners disagreeing) is resolved as a stop. The
|
|
10358
|
+
// grant is deferred a microtask so a synchronous deny in the same emit
|
|
10359
|
+
// pre-empts it; async grants still resolve normally.
|
|
10360
|
+
extend: (extra) => queueMicrotask(() => respond({ extend: extra })),
|
|
10361
|
+
deny: () => respond("stop")
|
|
10362
|
+
});
|
|
10363
|
+
});
|
|
9794
10364
|
}
|
|
9795
10365
|
/**
|
|
9796
10366
|
* Per-kind in-flight negotiation Promises. Each budget kind can have its
|
|
@@ -9810,77 +10380,33 @@ var SubagentBudget = class _SubagentBudget {
|
|
|
9810
10380
|
* `{ extend: {} }` — keep going without patching; next overrun fires
|
|
9811
10381
|
* a fresh signal.
|
|
9812
10382
|
*/
|
|
9813
|
-
async _negotiateExtension(
|
|
10383
|
+
async _negotiateExtension(entry) {
|
|
9814
10384
|
if (!this._onThreshold) {
|
|
9815
10385
|
return "stop";
|
|
9816
10386
|
}
|
|
9817
10387
|
try {
|
|
9818
|
-
const first = exceeded[0] ?? { kind: "iterations", limit: 0, used: 0 };
|
|
9819
10388
|
const result = this._onThreshold({
|
|
9820
|
-
kind:
|
|
9821
|
-
used:
|
|
9822
|
-
limit:
|
|
9823
|
-
|
|
9824
|
-
|
|
9825
|
-
|
|
9826
|
-
|
|
9827
|
-
|
|
9828
|
-
|
|
9829
|
-
|
|
9830
|
-
|
|
9831
|
-
if (resolved) return;
|
|
9832
|
-
resolved = true;
|
|
9833
|
-
resolve5(d);
|
|
9834
|
-
};
|
|
9835
|
-
const fallback = setTimeout(
|
|
9836
|
-
() => respond("stop"),
|
|
9837
|
-
_SubagentBudget.DECISION_TIMEOUT_MS
|
|
9838
|
-
);
|
|
9839
|
-
for (const { kind: kind2, used, limit } of exceeded) {
|
|
9840
|
-
bus.emit("budget.threshold_reached", {
|
|
9841
|
-
kind: kind2,
|
|
9842
|
-
used,
|
|
9843
|
-
limit,
|
|
9844
|
-
timeoutMs: _SubagentBudget.DECISION_TIMEOUT_MS,
|
|
9845
|
-
extend: (extra) => {
|
|
9846
|
-
clearTimeout(fallback);
|
|
9847
|
-
respond({ extend: extra });
|
|
9848
|
-
},
|
|
9849
|
-
deny: () => {
|
|
9850
|
-
clearTimeout(fallback);
|
|
9851
|
-
respond("stop");
|
|
9852
|
-
}
|
|
9853
|
-
});
|
|
9854
|
-
}
|
|
9855
|
-
});
|
|
10389
|
+
kind: entry.kind,
|
|
10390
|
+
used: entry.used,
|
|
10391
|
+
limit: entry.limit,
|
|
10392
|
+
// One event for THIS kind only — each exceeded kind has its own
|
|
10393
|
+
// negotiation (and its own resolve), so there is no cross-kind
|
|
10394
|
+
// first-wins drop and no O(N^2) re-emission.
|
|
10395
|
+
requestDecision: () => this._busRequestDecision(entry),
|
|
10396
|
+
extend: (extra) => {
|
|
10397
|
+
this.patchLimits(extra);
|
|
10398
|
+
},
|
|
10399
|
+
deny: () => {
|
|
9856
10400
|
}
|
|
9857
10401
|
});
|
|
9858
10402
|
if (result === "throw") return "stop";
|
|
9859
10403
|
if (result === "continue") return { extend: {} };
|
|
9860
10404
|
const decision = await result;
|
|
9861
10405
|
if (decision === "stop") return "stop";
|
|
9862
|
-
|
|
9863
|
-
if (ext.maxIterations !== void 0) {
|
|
9864
|
-
this.limits.maxIterations = ext.maxIterations;
|
|
9865
|
-
}
|
|
9866
|
-
if (ext.maxToolCalls !== void 0) {
|
|
9867
|
-
this.limits.maxToolCalls = ext.maxToolCalls;
|
|
9868
|
-
}
|
|
9869
|
-
if (ext.maxTokens !== void 0) {
|
|
9870
|
-
this.limits.maxTokens = ext.maxTokens;
|
|
9871
|
-
}
|
|
9872
|
-
if (ext.maxCostUsd !== void 0) {
|
|
9873
|
-
this.limits.maxCostUsd = ext.maxCostUsd;
|
|
9874
|
-
}
|
|
9875
|
-
if (ext.timeoutMs !== void 0) {
|
|
9876
|
-
this.limits.timeoutMs = ext.timeoutMs;
|
|
9877
|
-
}
|
|
9878
|
-
if (ext.idleTimeoutMs !== void 0) {
|
|
9879
|
-
this.limits.idleTimeoutMs = ext.idleTimeoutMs;
|
|
9880
|
-
}
|
|
10406
|
+
this.patchLimits(decision.extend);
|
|
9881
10407
|
return decision;
|
|
9882
10408
|
} finally {
|
|
9883
|
-
this._pendingNegotiations.delete(kind);
|
|
10409
|
+
this._pendingNegotiations.delete(entry.kind);
|
|
9884
10410
|
}
|
|
9885
10411
|
}
|
|
9886
10412
|
recordIteration() {
|
|
@@ -9923,7 +10449,8 @@ var SubagentBudget = class _SubagentBudget {
|
|
|
9923
10449
|
const { timeoutMs, idleTimeoutMs } = this.limits;
|
|
9924
10450
|
if (timeoutMs === void 0 && idleTimeoutMs === void 0) return;
|
|
9925
10451
|
const elapsed = Date.now() - this.startTime;
|
|
9926
|
-
const
|
|
10452
|
+
const wallSkipped = this._onThreshold !== void 0 && this._watchdogActive !== void 0 && timeoutMs !== void 0 && this._watchdogActive === timeoutMs;
|
|
10453
|
+
const wallTripped = wallSkipped ? false : timeoutMs !== void 0 && elapsed > timeoutMs;
|
|
9927
10454
|
const idleTripped = idleTimeoutMs !== void 0 && this.idleMs() > idleTimeoutMs;
|
|
9928
10455
|
if (!wallTripped && !idleTripped) return;
|
|
9929
10456
|
void this.checkLimits(elapsed);
|
|
@@ -12916,7 +13443,7 @@ function classifySubagentError(err, hints = {}) {
|
|
|
12916
13443
|
const baseMessage2 = err.describe();
|
|
12917
13444
|
return providerErrorToSubagentError(err, baseMessage2, cause);
|
|
12918
13445
|
}
|
|
12919
|
-
const baseMessage =
|
|
13446
|
+
const baseMessage = toErrorMessage(err);
|
|
12920
13447
|
if (err instanceof BudgetExceededError) {
|
|
12921
13448
|
const map = {
|
|
12922
13449
|
iterations: "budget_iterations",
|
|
@@ -13431,6 +13958,7 @@ var DefaultMultiAgentCoordinator = class _DefaultMultiAgentCoordinator extends E
|
|
|
13431
13958
|
terminating = /* @__PURE__ */ new Set();
|
|
13432
13959
|
constructor(config, options = {}) {
|
|
13433
13960
|
super();
|
|
13961
|
+
this.setMaxListeners(0);
|
|
13434
13962
|
this.coordinatorId = config.coordinatorId;
|
|
13435
13963
|
this.config = config;
|
|
13436
13964
|
this.runner = options.runner;
|
|
@@ -13825,7 +14353,13 @@ var DefaultMultiAgentCoordinator = class _DefaultMultiAgentCoordinator extends E
|
|
|
13825
14353
|
let result;
|
|
13826
14354
|
budget.start();
|
|
13827
14355
|
try {
|
|
13828
|
-
const outcome = await this.executeWithTimeout(
|
|
14356
|
+
const outcome = await this.executeWithTimeout(
|
|
14357
|
+
this.runner,
|
|
14358
|
+
task,
|
|
14359
|
+
runCtx,
|
|
14360
|
+
budget,
|
|
14361
|
+
subagent.config.preemptFraction
|
|
14362
|
+
);
|
|
13829
14363
|
result = {
|
|
13830
14364
|
subagentId,
|
|
13831
14365
|
taskId: task.id,
|
|
@@ -13852,7 +14386,7 @@ var DefaultMultiAgentCoordinator = class _DefaultMultiAgentCoordinator extends E
|
|
|
13852
14386
|
}
|
|
13853
14387
|
this.recordCompletion(result);
|
|
13854
14388
|
}
|
|
13855
|
-
async executeWithTimeout(runner, task, ctx, budget) {
|
|
14389
|
+
async executeWithTimeout(runner, task, ctx, budget, preemptFraction = TIMEOUT_PREEMPT_FRACTION) {
|
|
13856
14390
|
const initialTimeoutMs = budget.limits.timeoutMs;
|
|
13857
14391
|
const idleLimitMs = budget.limits.idleTimeoutMs;
|
|
13858
14392
|
if (initialTimeoutMs === void 0 && idleLimitMs === void 0) {
|
|
@@ -13860,8 +14394,21 @@ var DefaultMultiAgentCoordinator = class _DefaultMultiAgentCoordinator extends E
|
|
|
13860
14394
|
}
|
|
13861
14395
|
const start = Date.now();
|
|
13862
14396
|
let timer = null;
|
|
13863
|
-
let
|
|
14397
|
+
let PreemptState;
|
|
14398
|
+
((PreemptState2) => {
|
|
14399
|
+
PreemptState2["ACTIVE"] = "active";
|
|
14400
|
+
PreemptState2["LOCKED"] = "locked";
|
|
14401
|
+
})(PreemptState || (PreemptState = {}));
|
|
14402
|
+
let preemptedCeiling = null;
|
|
14403
|
+
let preemptState = "active" /* ACTIVE */;
|
|
14404
|
+
let lastGrantActivityTs = -1;
|
|
13864
14405
|
const timeoutPromise = new Promise((_, reject) => {
|
|
14406
|
+
const terminate = (kind, limit, used) => {
|
|
14407
|
+
this.subagents.get(ctx.subagentId)?.abortController.abort();
|
|
14408
|
+
reject(
|
|
14409
|
+
budget._events?.hasListenerFor("budget.threshold_reached") ? new Error(`subagent stopped: budget ${kind} (limit=${limit}, used=${used})`) : new BudgetExceededError(kind, limit, used)
|
|
14410
|
+
);
|
|
14411
|
+
};
|
|
13865
14412
|
const armFor = (ms) => {
|
|
13866
14413
|
if (timer) clearTimeout(timer);
|
|
13867
14414
|
timer = setTimeout(onTick, Math.max(0, ms));
|
|
@@ -13870,7 +14417,7 @@ var DefaultMultiAgentCoordinator = class _DefaultMultiAgentCoordinator extends E
|
|
|
13870
14417
|
const wallLimit = budget.limits.timeoutMs ?? initialTimeoutMs;
|
|
13871
14418
|
const wallRemaining = initialTimeoutMs === void 0 ? Number.POSITIVE_INFINITY : wallLimit - (Date.now() - start);
|
|
13872
14419
|
const idleRemaining = idleLimitMs === void 0 ? Number.POSITIVE_INFINITY : (budget.limits.idleTimeoutMs ?? idleLimitMs) - budget.idleMs();
|
|
13873
|
-
const preemptRemaining = initialTimeoutMs === void 0 ||
|
|
14420
|
+
const preemptRemaining = initialTimeoutMs === void 0 || preemptedCeiling === wallLimit ? Number.POSITIVE_INFINITY : wallLimit * preemptFraction - (Date.now() - start);
|
|
13874
14421
|
armFor(Math.max(25, Math.min(wallRemaining, idleRemaining, preemptRemaining)));
|
|
13875
14422
|
};
|
|
13876
14423
|
const negotiateTimeout = async (used, limit) => {
|
|
@@ -13880,16 +14427,42 @@ var DefaultMultiAgentCoordinator = class _DefaultMultiAgentCoordinator extends E
|
|
|
13880
14427
|
kind: "timeout",
|
|
13881
14428
|
used,
|
|
13882
14429
|
limit,
|
|
13883
|
-
requestDecision: () =>
|
|
13884
|
-
budget._events?.
|
|
13885
|
-
|
|
13886
|
-
|
|
13887
|
-
|
|
13888
|
-
|
|
13889
|
-
|
|
13890
|
-
|
|
14430
|
+
requestDecision: () => {
|
|
14431
|
+
if (!budget._events?.hasListenerFor("budget.threshold_reached")) {
|
|
14432
|
+
return Promise.resolve("stop");
|
|
14433
|
+
}
|
|
14434
|
+
return new Promise((resolveDecision) => {
|
|
14435
|
+
let settled = false;
|
|
14436
|
+
const resolve5 = (d) => {
|
|
14437
|
+
if (settled) return;
|
|
14438
|
+
settled = true;
|
|
14439
|
+
resolveDecision(d);
|
|
14440
|
+
};
|
|
14441
|
+
const fallback = setTimeout(() => resolve5("stop"), DECISION_TIMEOUT_MS);
|
|
14442
|
+
budget._events?.emit("budget.threshold_reached", {
|
|
14443
|
+
kind: "timeout",
|
|
14444
|
+
used,
|
|
14445
|
+
limit,
|
|
14446
|
+
// Informational: the budget's own decision deadline. Listeners may use
|
|
14447
|
+
// this to display a countdown. The coordinator does NOT enforce it —
|
|
14448
|
+
// it is the budget's own `setTimeout(fallback)` that races against
|
|
14449
|
+
// the listener's `extend()`/`deny()` call to guarantee progress.
|
|
14450
|
+
timeoutMs: DECISION_TIMEOUT_MS,
|
|
14451
|
+
// deny() wins over a same-dispatch extend(): defer the grant a
|
|
14452
|
+
// microtask so a synchronous deny in the same emit pre-empts it
|
|
14453
|
+
// (a listener that both grants and denies, or two listeners
|
|
14454
|
+
// disagreeing, resolves as a stop). Async grants still resolve.
|
|
14455
|
+
extend: (extra) => {
|
|
14456
|
+
clearTimeout(fallback);
|
|
14457
|
+
queueMicrotask(() => resolve5({ extend: extra }));
|
|
14458
|
+
},
|
|
14459
|
+
deny: () => {
|
|
14460
|
+
clearTimeout(fallback);
|
|
14461
|
+
resolve5("stop");
|
|
14462
|
+
}
|
|
14463
|
+
});
|
|
13891
14464
|
});
|
|
13892
|
-
}
|
|
14465
|
+
}
|
|
13893
14466
|
});
|
|
13894
14467
|
return typeof result === "string" ? result : await result;
|
|
13895
14468
|
};
|
|
@@ -13900,21 +14473,45 @@ var DefaultMultiAgentCoordinator = class _DefaultMultiAgentCoordinator extends E
|
|
|
13900
14473
|
const wallExceeded = wallLimit !== void 0 && elapsed >= wallLimit;
|
|
13901
14474
|
const idleExceeded = idleLimit !== void 0 && budget.idleMs() >= idleLimit;
|
|
13902
14475
|
if (idleExceeded && !wallExceeded) {
|
|
14476
|
+
budget._events?.emit("budget.threshold_reached", {
|
|
14477
|
+
kind: "idle_timeout",
|
|
14478
|
+
used: budget.idleMs(),
|
|
14479
|
+
limit: idleLimit ?? 0,
|
|
14480
|
+
timeoutMs: DECISION_TIMEOUT_MS,
|
|
14481
|
+
extend: () => {
|
|
14482
|
+
},
|
|
14483
|
+
deny: () => {
|
|
14484
|
+
}
|
|
14485
|
+
});
|
|
13903
14486
|
this.subagents.get(ctx.subagentId)?.abortController.abort();
|
|
13904
|
-
reject(new BudgetExceededError("
|
|
14487
|
+
reject(new BudgetExceededError("idle_timeout", idleLimit ?? 0, budget.idleMs()));
|
|
13905
14488
|
return;
|
|
13906
14489
|
}
|
|
13907
|
-
if (wallLimit !== void 0 && !wallExceeded && budget.onThreshold &&
|
|
14490
|
+
if (wallLimit !== void 0 && !wallExceeded && budget.onThreshold && preemptState === "active" /* ACTIVE */ && elapsed >= wallLimit * preemptFraction) {
|
|
14491
|
+
const activityTs = Date.now() - budget.idleMs();
|
|
14492
|
+
if (activityTs <= lastGrantActivityTs) {
|
|
14493
|
+
preemptState = "locked" /* LOCKED */;
|
|
14494
|
+
preemptedCeiling = wallLimit;
|
|
14495
|
+
scheduleNext();
|
|
14496
|
+
return;
|
|
14497
|
+
}
|
|
14498
|
+
budget.setWatchdogNegotiation(wallLimit);
|
|
13908
14499
|
try {
|
|
13909
14500
|
const decision = await negotiateTimeout(elapsed, wallLimit);
|
|
13910
14501
|
if (typeof decision !== "string" && decision.extend.timeoutMs !== void 0) {
|
|
13911
|
-
budget.
|
|
13912
|
-
|
|
14502
|
+
budget.patchLimits({ timeoutMs: decision.extend.timeoutMs });
|
|
14503
|
+
lastGrantActivityTs = Date.now() - budget.idleMs();
|
|
14504
|
+
preemptState = "active" /* ACTIVE */;
|
|
14505
|
+
preemptedCeiling = null;
|
|
13913
14506
|
} else {
|
|
13914
|
-
|
|
14507
|
+
preemptState = "locked" /* LOCKED */;
|
|
14508
|
+
preemptedCeiling = wallLimit;
|
|
13915
14509
|
}
|
|
13916
14510
|
} catch {
|
|
13917
|
-
|
|
14511
|
+
preemptState = "locked" /* LOCKED */;
|
|
14512
|
+
preemptedCeiling = wallLimit;
|
|
14513
|
+
} finally {
|
|
14514
|
+
budget.clearWatchdogNegotiation();
|
|
13918
14515
|
}
|
|
13919
14516
|
scheduleNext();
|
|
13920
14517
|
return;
|
|
@@ -13929,26 +14526,41 @@ var DefaultMultiAgentCoordinator = class _DefaultMultiAgentCoordinator extends E
|
|
|
13929
14526
|
reject(new BudgetExceededError("timeout", limit, elapsed));
|
|
13930
14527
|
return;
|
|
13931
14528
|
}
|
|
14529
|
+
budget.setWatchdogNegotiation(limit);
|
|
13932
14530
|
try {
|
|
13933
14531
|
const decision = await negotiateTimeout(elapsed, limit);
|
|
13934
|
-
if (decision === "
|
|
13935
|
-
|
|
14532
|
+
if (decision === "throw") {
|
|
14533
|
+
terminate("timeout", limit, elapsed);
|
|
14534
|
+
return;
|
|
14535
|
+
}
|
|
14536
|
+
if (decision === "continue") {
|
|
14537
|
+
preemptState = "locked" /* LOCKED */;
|
|
14538
|
+
preemptedCeiling = wallLimit;
|
|
13936
14539
|
armFor(Math.max(1e3, limit));
|
|
13937
14540
|
return;
|
|
13938
14541
|
}
|
|
14542
|
+
if (decision === "stop") {
|
|
14543
|
+
terminate("timeout", limit, elapsed);
|
|
14544
|
+
return;
|
|
14545
|
+
}
|
|
13939
14546
|
if (decision.extend.timeoutMs !== void 0) {
|
|
13940
|
-
budget.
|
|
13941
|
-
|
|
14547
|
+
budget.patchLimits({ timeoutMs: decision.extend.timeoutMs });
|
|
14548
|
+
lastGrantActivityTs = Date.now() - budget.idleMs();
|
|
14549
|
+
preemptState = "active" /* ACTIVE */;
|
|
14550
|
+
preemptedCeiling = null;
|
|
13942
14551
|
scheduleNext();
|
|
13943
14552
|
return;
|
|
13944
14553
|
}
|
|
13945
|
-
|
|
13946
|
-
|
|
14554
|
+
terminate("timeout", limit, elapsed);
|
|
14555
|
+
return;
|
|
13947
14556
|
} catch (err) {
|
|
13948
14557
|
this.subagents.get(ctx.subagentId)?.abortController.abort();
|
|
13949
14558
|
reject(
|
|
13950
14559
|
err instanceof BudgetExceededError ? err : new BudgetExceededError("timeout", limit, elapsed)
|
|
13951
14560
|
);
|
|
14561
|
+
return;
|
|
14562
|
+
} finally {
|
|
14563
|
+
budget.clearWatchdogNegotiation();
|
|
13952
14564
|
}
|
|
13953
14565
|
};
|
|
13954
14566
|
scheduleNext();
|
|
@@ -14111,7 +14723,7 @@ var ParallelEternalEngine = class {
|
|
|
14111
14723
|
console.error(JSON.stringify({
|
|
14112
14724
|
level: "error",
|
|
14113
14725
|
event: "engine.persist_state_failed",
|
|
14114
|
-
message:
|
|
14726
|
+
message: toErrorMessage(err),
|
|
14115
14727
|
context: { expectedState: "stopped" },
|
|
14116
14728
|
timestamp: (/* @__PURE__ */ new Date()).toISOString()
|
|
14117
14729
|
}));
|
|
@@ -14147,7 +14759,7 @@ var ParallelEternalEngine = class {
|
|
|
14147
14759
|
);
|
|
14148
14760
|
await this.appendFailure(
|
|
14149
14761
|
"engine error",
|
|
14150
|
-
|
|
14762
|
+
toErrorMessage(err)
|
|
14151
14763
|
);
|
|
14152
14764
|
}
|
|
14153
14765
|
if (this.stopRequested) break;
|
|
@@ -14794,123 +15406,6 @@ function createMessage(type, from, payload, to) {
|
|
|
14794
15406
|
priority: "normal"
|
|
14795
15407
|
};
|
|
14796
15408
|
}
|
|
14797
|
-
var GLOB_CHARS = /* @__PURE__ */ new Set(["*", "?", "["]);
|
|
14798
|
-
var IS_WINDOWS = process.platform === "win32";
|
|
14799
|
-
var SEP = IS_WINDOWS ? "\\" : "/";
|
|
14800
|
-
function isGlob(p) {
|
|
14801
|
-
for (const c of p) {
|
|
14802
|
-
if (GLOB_CHARS.has(c)) return true;
|
|
14803
|
-
}
|
|
14804
|
-
return false;
|
|
14805
|
-
}
|
|
14806
|
-
function globToRegex(pat) {
|
|
14807
|
-
let i = 0;
|
|
14808
|
-
let re = "^";
|
|
14809
|
-
while (i < pat.length) {
|
|
14810
|
-
const c = expectDefined(pat[i]);
|
|
14811
|
-
if (c === "*") {
|
|
14812
|
-
if (pat[i + 1] === "*") {
|
|
14813
|
-
re += ".*";
|
|
14814
|
-
i += 2;
|
|
14815
|
-
if (pat[i] === "/") i++;
|
|
14816
|
-
} else {
|
|
14817
|
-
re += "[^/\\\\]*";
|
|
14818
|
-
i++;
|
|
14819
|
-
}
|
|
14820
|
-
} else if (c === "?") {
|
|
14821
|
-
re += "[^/\\\\]";
|
|
14822
|
-
i++;
|
|
14823
|
-
} else if (c === "[") {
|
|
14824
|
-
let cls = "[";
|
|
14825
|
-
i++;
|
|
14826
|
-
if (pat[i] === "!" || pat[i] === "^") {
|
|
14827
|
-
cls += "^";
|
|
14828
|
-
i++;
|
|
14829
|
-
}
|
|
14830
|
-
while (i < pat.length && pat[i] !== "]") {
|
|
14831
|
-
const ch = pat[i] ?? "";
|
|
14832
|
-
if (ch === "\\") cls += "\\\\";
|
|
14833
|
-
else if (ch === "]" || ch === "^") cls += `\\${ch}`;
|
|
14834
|
-
else cls += ch;
|
|
14835
|
-
i++;
|
|
14836
|
-
}
|
|
14837
|
-
cls += "]";
|
|
14838
|
-
re += cls;
|
|
14839
|
-
i++;
|
|
14840
|
-
} else {
|
|
14841
|
-
re += c.replace(/[.+^${}()|\\]/g, "\\$&");
|
|
14842
|
-
i++;
|
|
14843
|
-
}
|
|
14844
|
-
}
|
|
14845
|
-
return new RegExp(re + "$");
|
|
14846
|
-
}
|
|
14847
|
-
function baseDir(pat) {
|
|
14848
|
-
let i = pat.length - 1;
|
|
14849
|
-
while (i >= 0 && !GLOB_CHARS.has(expectDefined(pat[i])) && pat[i] !== SEP && pat[i] !== "/") i--;
|
|
14850
|
-
const cut = i >= 0 ? pat.lastIndexOf(SEP, i) : pat.lastIndexOf("/", i);
|
|
14851
|
-
return cut < 0 ? "." : pat.slice(0, cut);
|
|
14852
|
-
}
|
|
14853
|
-
async function expandGlob(pattern) {
|
|
14854
|
-
if (!isGlob(pattern)) return [pattern];
|
|
14855
|
-
const results = /* @__PURE__ */ new Set();
|
|
14856
|
-
const abs = isAbsolute(pattern);
|
|
14857
|
-
const base = abs ? baseDir(pattern) : baseDir(pattern);
|
|
14858
|
-
const relPat = base === "." ? pattern : pattern.slice(base.length + 1);
|
|
14859
|
-
async function walk3(dir, pat) {
|
|
14860
|
-
let entries;
|
|
14861
|
-
try {
|
|
14862
|
-
entries = await fsp.readdir(dir);
|
|
14863
|
-
} catch {
|
|
14864
|
-
return;
|
|
14865
|
-
}
|
|
14866
|
-
const firstGlob = pat.search(/[*?[[]/);
|
|
14867
|
-
if (firstGlob < 0) {
|
|
14868
|
-
const re = globToRegex(pat);
|
|
14869
|
-
for (const e of entries) {
|
|
14870
|
-
if (re.test(e)) {
|
|
14871
|
-
const full = `${dir}${SEP}${e}`;
|
|
14872
|
-
results.add(abs ? resolve(full) : full);
|
|
14873
|
-
}
|
|
14874
|
-
}
|
|
14875
|
-
return;
|
|
14876
|
-
}
|
|
14877
|
-
const before = pat.slice(0, firstGlob);
|
|
14878
|
-
const rest = pat.slice(firstGlob);
|
|
14879
|
-
if (before.endsWith("**")) {
|
|
14880
|
-
await walk3(dir, rest);
|
|
14881
|
-
for (const e of entries) {
|
|
14882
|
-
const full = `${dir}${SEP}${e}`;
|
|
14883
|
-
try {
|
|
14884
|
-
const stat6 = await fsp.stat(full);
|
|
14885
|
-
if (stat6.isDirectory()) await walk3(full, rest);
|
|
14886
|
-
} catch {
|
|
14887
|
-
}
|
|
14888
|
-
}
|
|
14889
|
-
} else if (before === "") {
|
|
14890
|
-
const re = globToRegex(rest);
|
|
14891
|
-
for (const e of entries) {
|
|
14892
|
-
if (re.test(e)) {
|
|
14893
|
-
const full = `${dir}${SEP}${e}`;
|
|
14894
|
-
results.add(abs ? resolve(full) : full);
|
|
14895
|
-
}
|
|
14896
|
-
}
|
|
14897
|
-
} else {
|
|
14898
|
-
const seg = before.replace(/[*?[\]]/g, "").replace(/\/$/, "");
|
|
14899
|
-
if (entries.includes(seg)) {
|
|
14900
|
-
const full = `${dir}${SEP}${seg}`;
|
|
14901
|
-
try {
|
|
14902
|
-
const stat6 = await fsp.stat(full);
|
|
14903
|
-
if (stat6.isDirectory()) await walk3(full, rest);
|
|
14904
|
-
} catch {
|
|
14905
|
-
}
|
|
14906
|
-
}
|
|
14907
|
-
}
|
|
14908
|
-
}
|
|
14909
|
-
await walk3(base === "." ? "." : base, relPat);
|
|
14910
|
-
return [...results];
|
|
14911
|
-
}
|
|
14912
|
-
|
|
14913
|
-
// src/coordination/collab-debug.ts
|
|
14914
15409
|
var DEFAULT_MAX_TARGET_FILES = 30;
|
|
14915
15410
|
var CollabSession = class extends EventEmitter {
|
|
14916
15411
|
sessionId;
|
|
@@ -15000,8 +15495,8 @@ var CollabSession = class extends EventEmitter {
|
|
|
15000
15495
|
for (const filePath of allFiles) {
|
|
15001
15496
|
try {
|
|
15002
15497
|
const [content, stat6] = await Promise.all([
|
|
15003
|
-
|
|
15004
|
-
|
|
15498
|
+
fsp2.readFile(filePath, "utf8"),
|
|
15499
|
+
fsp2.stat(filePath)
|
|
15005
15500
|
]);
|
|
15006
15501
|
const ext = filePath.split(".").pop() ?? "";
|
|
15007
15502
|
const language = ext === "ts" || ext === "tsx" ? "typescript" : ext === "js" || ext === "jsx" ? "javascript" : ext === "md" ? "markdown" : ext === "json" ? "json" : void 0;
|
|
@@ -15423,7 +15918,7 @@ Emit each evaluation immediately. Do not wait until you have read all reports.`;
|
|
|
15423
15918
|
for (const file of this.snapshot.files) {
|
|
15424
15919
|
if (file.snapshotMtimeMs === void 0 && file.snapshotSizeBytes === void 0) continue;
|
|
15425
15920
|
try {
|
|
15426
|
-
const stat6 = await
|
|
15921
|
+
const stat6 = await fsp2.stat(file.path);
|
|
15427
15922
|
const mtimeChanged = file.snapshotMtimeMs !== void 0 && stat6.mtimeMs > file.snapshotMtimeMs + 1;
|
|
15428
15923
|
const sizeChanged = file.snapshotSizeBytes !== void 0 && stat6.size !== file.snapshotSizeBytes;
|
|
15429
15924
|
if (mtimeChanged || sizeChanged) {
|
|
@@ -15706,7 +16201,7 @@ function makeSpawnTool(director, roster) {
|
|
|
15706
16201
|
if (err instanceof FleetCostCapError) {
|
|
15707
16202
|
return { error: err.message, kind: err.kind, limit: err.limit, observed: err.observed };
|
|
15708
16203
|
}
|
|
15709
|
-
return { error:
|
|
16204
|
+
return { error: toErrorMessage(err) };
|
|
15710
16205
|
}
|
|
15711
16206
|
}
|
|
15712
16207
|
};
|
|
@@ -15788,7 +16283,7 @@ function makeAskTool(director) {
|
|
|
15788
16283
|
_hint: "Response was large and stored. Use ask_result with the key to retrieve it."
|
|
15789
16284
|
};
|
|
15790
16285
|
} catch (err) {
|
|
15791
|
-
return { ok: false, error:
|
|
16286
|
+
return { ok: false, error: toErrorMessage(err) };
|
|
15792
16287
|
}
|
|
15793
16288
|
}
|
|
15794
16289
|
};
|
|
@@ -16014,7 +16509,7 @@ function makeCollabDebugTool(director) {
|
|
|
16014
16509
|
evaluations: report.evaluations
|
|
16015
16510
|
};
|
|
16016
16511
|
} catch (err) {
|
|
16017
|
-
const msg =
|
|
16512
|
+
const msg = toErrorMessage(err);
|
|
16018
16513
|
return { error: "collab_debug failed: " + msg };
|
|
16019
16514
|
}
|
|
16020
16515
|
}
|
|
@@ -16588,7 +17083,7 @@ var Director = class _Director {
|
|
|
16588
17083
|
) : null;
|
|
16589
17084
|
this.fleetManager = opts.fleetManager;
|
|
16590
17085
|
if (this.sharedScratchpadPath) {
|
|
16591
|
-
void
|
|
17086
|
+
void fsp2.mkdir(this.sharedScratchpadPath, { recursive: true }).catch((err) => this.logShutdownError("shared_scratchpad_mkdir", err));
|
|
16592
17087
|
}
|
|
16593
17088
|
this.transport = new InMemoryBridgeTransport();
|
|
16594
17089
|
this.bridge = new InMemoryAgentBridge(
|
|
@@ -17180,7 +17675,7 @@ var Director = class _Director {
|
|
|
17180
17675
|
})),
|
|
17181
17676
|
usage: this.usage.snapshot()
|
|
17182
17677
|
};
|
|
17183
|
-
await
|
|
17678
|
+
await fsp2.mkdir(path3.dirname(this.manifestPath), { recursive: true });
|
|
17184
17679
|
await atomicWrite(this.manifestPath, JSON.stringify(manifest, null, 2), { mode: 384 });
|
|
17185
17680
|
return this.manifestPath;
|
|
17186
17681
|
}
|
|
@@ -17230,7 +17725,7 @@ var Director = class _Director {
|
|
|
17230
17725
|
* listener for structured collection, and never affects exit code.
|
|
17231
17726
|
*/
|
|
17232
17727
|
logShutdownError(phase, err) {
|
|
17233
|
-
const detail =
|
|
17728
|
+
const detail = toErrorMessage(err);
|
|
17234
17729
|
process.emitWarning(
|
|
17235
17730
|
`Director shutdown phase "${phase}" failed: ${detail}`,
|
|
17236
17731
|
"DirectorShutdownWarning"
|
|
@@ -17394,10 +17889,10 @@ var Director = class _Director {
|
|
|
17394
17889
|
*/
|
|
17395
17890
|
async readSession(subagentId, tail) {
|
|
17396
17891
|
if (!this.sessionsRoot) return null;
|
|
17397
|
-
const filePath =
|
|
17892
|
+
const filePath = path3.join(this.sessionsRoot, this.directorRunId, `${subagentId}.jsonl`);
|
|
17398
17893
|
let raw;
|
|
17399
17894
|
try {
|
|
17400
|
-
raw = await
|
|
17895
|
+
raw = await fsp2.readFile(filePath, "utf8");
|
|
17401
17896
|
} catch {
|
|
17402
17897
|
return null;
|
|
17403
17898
|
}
|
|
@@ -17823,7 +18318,7 @@ function createDelegateTool(opts) {
|
|
|
17823
18318
|
summary
|
|
17824
18319
|
};
|
|
17825
18320
|
} catch (err) {
|
|
17826
|
-
const message =
|
|
18321
|
+
const message = toErrorMessage(err);
|
|
17827
18322
|
opts.events?.emit("delegate.completed", {
|
|
17828
18323
|
target,
|
|
17829
18324
|
task: i.task,
|
|
@@ -17924,13 +18419,13 @@ async function readSubagentPartial(opts, subagentId) {
|
|
|
17924
18419
|
if (!opts.sessionsRoot) return void 0;
|
|
17925
18420
|
const candidates = [];
|
|
17926
18421
|
if (opts.directorRunId) {
|
|
17927
|
-
candidates.push(
|
|
18422
|
+
candidates.push(path3.join(opts.sessionsRoot, opts.directorRunId, `${subagentId}.jsonl`));
|
|
17928
18423
|
} else {
|
|
17929
18424
|
try {
|
|
17930
|
-
const entries = await
|
|
18425
|
+
const entries = await fsp2.readdir(opts.sessionsRoot, { withFileTypes: true });
|
|
17931
18426
|
for (const entry of entries) {
|
|
17932
18427
|
if (entry.isDirectory()) {
|
|
17933
|
-
candidates.push(
|
|
18428
|
+
candidates.push(path3.join(opts.sessionsRoot, entry.name, `${subagentId}.jsonl`));
|
|
17934
18429
|
}
|
|
17935
18430
|
}
|
|
17936
18431
|
} catch {
|
|
@@ -17940,7 +18435,7 @@ async function readSubagentPartial(opts, subagentId) {
|
|
|
17940
18435
|
for (const file of candidates) {
|
|
17941
18436
|
let raw;
|
|
17942
18437
|
try {
|
|
17943
|
-
raw = await
|
|
18438
|
+
raw = await fsp2.readFile(file, "utf8");
|
|
17944
18439
|
} catch {
|
|
17945
18440
|
continue;
|
|
17946
18441
|
}
|
|
@@ -17980,9 +18475,9 @@ function makeDirectorSessionFactory(opts) {
|
|
|
17980
18475
|
let dir;
|
|
17981
18476
|
if (opts.store) {
|
|
17982
18477
|
store = opts.store;
|
|
17983
|
-
dir = opts.sessionsRoot ?
|
|
18478
|
+
dir = opts.sessionsRoot ? path3.join(opts.sessionsRoot, runId) : "(caller-managed)";
|
|
17984
18479
|
} else if (opts.sessionsRoot) {
|
|
17985
|
-
dir =
|
|
18480
|
+
dir = path3.join(opts.sessionsRoot, runId);
|
|
17986
18481
|
store = new DefaultSessionStore({ dir });
|
|
17987
18482
|
} else {
|
|
17988
18483
|
throw new Error("makeDirectorSessionFactory requires either `store` or `sessionsRoot`");
|
|
@@ -18028,6 +18523,7 @@ function attachAutoExtend(events, policy = {}) {
|
|
|
18028
18523
|
const extendCounts = /* @__PURE__ */ new Map();
|
|
18029
18524
|
let progress = 0;
|
|
18030
18525
|
let lastTimeoutProgress = -1;
|
|
18526
|
+
let lastSeenKey = null;
|
|
18031
18527
|
const unsubs = [
|
|
18032
18528
|
events.on("tool.executed", () => {
|
|
18033
18529
|
progress++;
|
|
@@ -18037,6 +18533,9 @@ function attachAutoExtend(events, policy = {}) {
|
|
|
18037
18533
|
}),
|
|
18038
18534
|
events.on("budget.threshold_reached", (e) => {
|
|
18039
18535
|
const { kind, limit, extend, deny } = e;
|
|
18536
|
+
const key = `${kind}:${limit}`;
|
|
18537
|
+
if (key === lastSeenKey) return;
|
|
18538
|
+
lastSeenKey = key;
|
|
18040
18539
|
if (kind === "timeout" || kind === "idle_timeout") {
|
|
18041
18540
|
if (progress > lastTimeoutProgress) {
|
|
18042
18541
|
lastTimeoutProgress = progress;
|
|
@@ -18072,71 +18571,6 @@ var NULL_FLEET_BUS = new FleetBus();
|
|
|
18072
18571
|
|
|
18073
18572
|
// src/models/models-registry.ts
|
|
18074
18573
|
init_atomic_write();
|
|
18075
|
-
|
|
18076
|
-
// src/utils/merge-models-payload.ts
|
|
18077
|
-
function mergeModelsPayload(base, overlay) {
|
|
18078
|
-
const out = {};
|
|
18079
|
-
for (const [id, provider] of Object.entries(base)) {
|
|
18080
|
-
out[id] = cloneProvider(provider);
|
|
18081
|
-
}
|
|
18082
|
-
for (const [id, ovProvider] of Object.entries(overlay)) {
|
|
18083
|
-
const existing = out[id];
|
|
18084
|
-
out[id] = existing ? mergeProvider(existing, ovProvider) : cloneProvider(ovProvider);
|
|
18085
|
-
}
|
|
18086
|
-
return out;
|
|
18087
|
-
}
|
|
18088
|
-
function mergeProvider(base, overlay) {
|
|
18089
|
-
const models = {};
|
|
18090
|
-
for (const [mid, m] of Object.entries(base.models ?? {})) {
|
|
18091
|
-
models[mid] = { ...m };
|
|
18092
|
-
}
|
|
18093
|
-
for (const [mid, ovModel] of Object.entries(overlay.models ?? {})) {
|
|
18094
|
-
const existing = models[mid];
|
|
18095
|
-
models[mid] = existing ? mergeModel(existing, ovModel) : { ...ovModel };
|
|
18096
|
-
}
|
|
18097
|
-
return {
|
|
18098
|
-
...base,
|
|
18099
|
-
// Overlay scalar fields win when explicitly provided; otherwise keep base.
|
|
18100
|
-
...stripUndefined({
|
|
18101
|
-
id: overlay.id,
|
|
18102
|
-
name: overlay.name,
|
|
18103
|
-
npm: overlay.npm,
|
|
18104
|
-
api: overlay.api,
|
|
18105
|
-
env: overlay.env,
|
|
18106
|
-
doc: overlay.doc
|
|
18107
|
-
}),
|
|
18108
|
-
models
|
|
18109
|
-
};
|
|
18110
|
-
}
|
|
18111
|
-
function mergeModel(base, overlay) {
|
|
18112
|
-
const merged = { ...base, ...overlay };
|
|
18113
|
-
if (base.limit || overlay.limit) {
|
|
18114
|
-
merged.limit = { ...base.limit, ...overlay.limit };
|
|
18115
|
-
}
|
|
18116
|
-
if (base.cost || overlay.cost) {
|
|
18117
|
-
merged.cost = { ...base.cost, ...overlay.cost };
|
|
18118
|
-
}
|
|
18119
|
-
if (base.modalities || overlay.modalities) {
|
|
18120
|
-
merged.modalities = { ...base.modalities, ...overlay.modalities };
|
|
18121
|
-
}
|
|
18122
|
-
return merged;
|
|
18123
|
-
}
|
|
18124
|
-
function cloneProvider(p) {
|
|
18125
|
-
const models = {};
|
|
18126
|
-
for (const [mid, m] of Object.entries(p.models ?? {})) {
|
|
18127
|
-
models[mid] = { ...m };
|
|
18128
|
-
}
|
|
18129
|
-
return { ...p, models };
|
|
18130
|
-
}
|
|
18131
|
-
function stripUndefined(obj) {
|
|
18132
|
-
const out = {};
|
|
18133
|
-
for (const [k, v] of Object.entries(obj)) {
|
|
18134
|
-
if (v !== void 0) out[k] = v;
|
|
18135
|
-
}
|
|
18136
|
-
return out;
|
|
18137
|
-
}
|
|
18138
|
-
|
|
18139
|
-
// src/models/models-registry.ts
|
|
18140
18574
|
var DEFAULT_URL = "https://models.dev/api.json";
|
|
18141
18575
|
var DEFAULT_TTL_SECONDS = 24 * 3600;
|
|
18142
18576
|
var DEFAULT_REFRESH_TIMEOUT_MS = 15e3;
|
|
@@ -18193,7 +18627,7 @@ var DefaultModelsRegistry = class {
|
|
|
18193
18627
|
this.overlay = opts.overlay;
|
|
18194
18628
|
this.overlayUrl = opts.overlayUrl;
|
|
18195
18629
|
this.overlayFile = opts.overlayFile;
|
|
18196
|
-
this.overlayCacheFile = opts.overlayCacheFile ?? (opts.overlayUrl ?
|
|
18630
|
+
this.overlayCacheFile = opts.overlayCacheFile ?? (opts.overlayUrl ? path3.join(path3.dirname(opts.cacheFile), "models-overlay-cache.json") : void 0);
|
|
18197
18631
|
}
|
|
18198
18632
|
async load(opts = {}) {
|
|
18199
18633
|
if (this.payload && !opts.force) return this.payload;
|
|
@@ -18232,7 +18666,7 @@ var DefaultModelsRegistry = class {
|
|
|
18232
18666
|
}
|
|
18233
18667
|
if (overlayAvailable) {
|
|
18234
18668
|
console.warn(
|
|
18235
|
-
`ModelsRegistry: models.dev unavailable (${
|
|
18669
|
+
`ModelsRegistry: models.dev unavailable (${toErrorMessage(err)}); serving curated overlay only.`
|
|
18236
18670
|
);
|
|
18237
18671
|
return {};
|
|
18238
18672
|
}
|
|
@@ -18320,7 +18754,7 @@ var DefaultModelsRegistry = class {
|
|
|
18320
18754
|
async readOverlayFile() {
|
|
18321
18755
|
if (!this.overlayFile) return void 0;
|
|
18322
18756
|
try {
|
|
18323
|
-
const raw = await
|
|
18757
|
+
const raw = await fsp2.readFile(this.overlayFile, "utf8");
|
|
18324
18758
|
return JSON.parse(raw);
|
|
18325
18759
|
} catch {
|
|
18326
18760
|
return void 0;
|
|
@@ -18398,7 +18832,7 @@ var DefaultModelsRegistry = class {
|
|
|
18398
18832
|
}
|
|
18399
18833
|
async readCacheAt(file) {
|
|
18400
18834
|
try {
|
|
18401
|
-
const raw = await
|
|
18835
|
+
const raw = await fsp2.readFile(file, "utf8");
|
|
18402
18836
|
return JSON.parse(raw);
|
|
18403
18837
|
} catch {
|
|
18404
18838
|
return void 0;
|
|
@@ -18406,7 +18840,7 @@ var DefaultModelsRegistry = class {
|
|
|
18406
18840
|
}
|
|
18407
18841
|
/** Used by `wstack models refresh` to expose where the cache lives. */
|
|
18408
18842
|
cacheLocation() {
|
|
18409
|
-
return
|
|
18843
|
+
return path3.resolve(this.cacheFile);
|
|
18410
18844
|
}
|
|
18411
18845
|
};
|
|
18412
18846
|
function hasEntries(payload) {
|
|
@@ -18773,8 +19207,8 @@ var DefaultModeStore = class {
|
|
|
18773
19207
|
}
|
|
18774
19208
|
async loadActiveMode() {
|
|
18775
19209
|
try {
|
|
18776
|
-
const configPath =
|
|
18777
|
-
const content = await
|
|
19210
|
+
const configPath = path3.join(this.configDir, "mode.json");
|
|
19211
|
+
const content = await fsp2.readFile(configPath, "utf8");
|
|
18778
19212
|
const data = JSON.parse(content);
|
|
18779
19213
|
this.activeModeId = data.activeMode ?? null;
|
|
18780
19214
|
} catch {
|
|
@@ -18783,8 +19217,8 @@ var DefaultModeStore = class {
|
|
|
18783
19217
|
}
|
|
18784
19218
|
async saveActiveMode() {
|
|
18785
19219
|
try {
|
|
18786
|
-
await
|
|
18787
|
-
const configPath =
|
|
19220
|
+
await fsp2.mkdir(this.configDir, { recursive: true });
|
|
19221
|
+
const configPath = path3.join(this.configDir, "mode.json");
|
|
18788
19222
|
await atomicWrite(
|
|
18789
19223
|
configPath,
|
|
18790
19224
|
JSON.stringify({ activeMode: this.activeModeId }, null, 2)
|
|
@@ -18796,14 +19230,14 @@ var DefaultModeStore = class {
|
|
|
18796
19230
|
async function loadProjectModes(modesDir) {
|
|
18797
19231
|
const modes = [];
|
|
18798
19232
|
try {
|
|
18799
|
-
const entries = await
|
|
19233
|
+
const entries = await fsp2.readdir(modesDir);
|
|
18800
19234
|
for (const entry of entries) {
|
|
18801
19235
|
if (!entry.endsWith(".md") && !entry.endsWith(".txt")) continue;
|
|
18802
|
-
const filePath =
|
|
18803
|
-
const stat6 = await
|
|
19236
|
+
const filePath = path3.join(modesDir, entry);
|
|
19237
|
+
const stat6 = await fsp2.stat(filePath);
|
|
18804
19238
|
if (!stat6.isFile()) continue;
|
|
18805
|
-
const content = await
|
|
18806
|
-
const id =
|
|
19239
|
+
const content = await fsp2.readFile(filePath, "utf8");
|
|
19240
|
+
const id = path3.basename(entry, path3.extname(entry));
|
|
18807
19241
|
modes.push({
|
|
18808
19242
|
id,
|
|
18809
19243
|
name: id.replace(/[-_]/g, " ").replace(/\b\w/g, (c) => c.toUpperCase()),
|
|
@@ -18819,8 +19253,8 @@ async function loadProjectModes(modesDir) {
|
|
|
18819
19253
|
async function loadUserModes(modesDir) {
|
|
18820
19254
|
const modes = [];
|
|
18821
19255
|
try {
|
|
18822
|
-
const manifestPath =
|
|
18823
|
-
const content = await
|
|
19256
|
+
const manifestPath = path3.join(modesDir, "modes.json");
|
|
19257
|
+
const content = await fsp2.readFile(manifestPath, "utf8");
|
|
18824
19258
|
const manifest = JSON.parse(content);
|
|
18825
19259
|
for (const mode of manifest.modes) {
|
|
18826
19260
|
modes.push(mode);
|
|
@@ -19546,7 +19980,7 @@ var TaskTracker = class {
|
|
|
19546
19980
|
this.opts.onPersistError ? this.opts.onPersistError(err) : console.warn(JSON.stringify({
|
|
19547
19981
|
level: "warn",
|
|
19548
19982
|
event: "task_tracker.save_graph_failed",
|
|
19549
|
-
message:
|
|
19983
|
+
message: toErrorMessage(err),
|
|
19550
19984
|
timestamp: (/* @__PURE__ */ new Date()).toISOString()
|
|
19551
19985
|
}));
|
|
19552
19986
|
});
|
|
@@ -19771,7 +20205,7 @@ var SpecStore = class {
|
|
|
19771
20205
|
indexPath;
|
|
19772
20206
|
constructor(opts) {
|
|
19773
20207
|
this.baseDir = opts.baseDir;
|
|
19774
|
-
this.indexPath =
|
|
20208
|
+
this.indexPath = path3.join(this.baseDir, "_index.json");
|
|
19775
20209
|
}
|
|
19776
20210
|
async save(spec) {
|
|
19777
20211
|
await ensureDir(this.baseDir);
|
|
@@ -19781,7 +20215,7 @@ var SpecStore = class {
|
|
|
19781
20215
|
}
|
|
19782
20216
|
async load(id) {
|
|
19783
20217
|
try {
|
|
19784
|
-
const raw = await
|
|
20218
|
+
const raw = await fsp2.readFile(this.filePath(id), "utf8");
|
|
19785
20219
|
return JSON.parse(raw);
|
|
19786
20220
|
} catch {
|
|
19787
20221
|
return null;
|
|
@@ -19793,7 +20227,7 @@ var SpecStore = class {
|
|
|
19793
20227
|
}
|
|
19794
20228
|
async delete(id) {
|
|
19795
20229
|
try {
|
|
19796
|
-
await
|
|
20230
|
+
await fsp2.unlink(this.filePath(id));
|
|
19797
20231
|
await this.removeFromIndex(id);
|
|
19798
20232
|
return true;
|
|
19799
20233
|
} catch {
|
|
@@ -19802,7 +20236,7 @@ var SpecStore = class {
|
|
|
19802
20236
|
}
|
|
19803
20237
|
async exists(id) {
|
|
19804
20238
|
try {
|
|
19805
|
-
await
|
|
20239
|
+
await fsp2.access(this.filePath(id));
|
|
19806
20240
|
return true;
|
|
19807
20241
|
} catch {
|
|
19808
20242
|
return false;
|
|
@@ -19840,11 +20274,11 @@ var SpecStore = class {
|
|
|
19840
20274
|
return updated;
|
|
19841
20275
|
}
|
|
19842
20276
|
filePath(id) {
|
|
19843
|
-
return
|
|
20277
|
+
return path3.join(this.baseDir, `${id}.json`);
|
|
19844
20278
|
}
|
|
19845
20279
|
async readIndex() {
|
|
19846
20280
|
try {
|
|
19847
|
-
const raw = await
|
|
20281
|
+
const raw = await fsp2.readFile(this.indexPath, "utf8");
|
|
19848
20282
|
const parsed = JSON.parse(raw);
|
|
19849
20283
|
if (parsed?.version === 1) return parsed;
|
|
19850
20284
|
} catch {
|
|
@@ -19897,7 +20331,7 @@ var TaskGraphStore = class {
|
|
|
19897
20331
|
indexPath;
|
|
19898
20332
|
constructor(opts) {
|
|
19899
20333
|
this.baseDir = opts.baseDir;
|
|
19900
|
-
this.indexPath =
|
|
20334
|
+
this.indexPath = path3.join(this.baseDir, "_index.json");
|
|
19901
20335
|
}
|
|
19902
20336
|
async save(graph) {
|
|
19903
20337
|
await ensureDir(this.baseDir);
|
|
@@ -19907,7 +20341,7 @@ var TaskGraphStore = class {
|
|
|
19907
20341
|
}
|
|
19908
20342
|
async load(id) {
|
|
19909
20343
|
try {
|
|
19910
|
-
const raw = await
|
|
20344
|
+
const raw = await fsp2.readFile(this.filePath(id), "utf8");
|
|
19911
20345
|
return graphFromJSON(raw);
|
|
19912
20346
|
} catch {
|
|
19913
20347
|
return null;
|
|
@@ -19919,7 +20353,7 @@ var TaskGraphStore = class {
|
|
|
19919
20353
|
}
|
|
19920
20354
|
async delete(id) {
|
|
19921
20355
|
try {
|
|
19922
|
-
await
|
|
20356
|
+
await fsp2.unlink(this.filePath(id));
|
|
19923
20357
|
await this.removeFromIndex(id);
|
|
19924
20358
|
return true;
|
|
19925
20359
|
} catch {
|
|
@@ -19928,18 +20362,18 @@ var TaskGraphStore = class {
|
|
|
19928
20362
|
}
|
|
19929
20363
|
async exists(id) {
|
|
19930
20364
|
try {
|
|
19931
|
-
await
|
|
20365
|
+
await fsp2.access(this.filePath(id));
|
|
19932
20366
|
return true;
|
|
19933
20367
|
} catch {
|
|
19934
20368
|
return false;
|
|
19935
20369
|
}
|
|
19936
20370
|
}
|
|
19937
20371
|
filePath(id) {
|
|
19938
|
-
return
|
|
20372
|
+
return path3.join(this.baseDir, `${id}.json`);
|
|
19939
20373
|
}
|
|
19940
20374
|
async readIndex() {
|
|
19941
20375
|
try {
|
|
19942
|
-
const raw = await
|
|
20376
|
+
const raw = await fsp2.readFile(this.indexPath, "utf8");
|
|
19943
20377
|
const parsed = JSON.parse(raw);
|
|
19944
20378
|
if (parsed?.version === 1) return parsed;
|
|
19945
20379
|
} catch {
|
|
@@ -20216,7 +20650,7 @@ var AISpecBuilder = class {
|
|
|
20216
20650
|
* ENOSPC / EACCES doesn't silently strand session edits in memory. */
|
|
20217
20651
|
autoSave() {
|
|
20218
20652
|
this.saveSession().catch((err) => {
|
|
20219
|
-
const detail =
|
|
20653
|
+
const detail = toErrorMessage(err);
|
|
20220
20654
|
process.emitWarning(
|
|
20221
20655
|
`SpecBuilder autoSave failed: ${detail}`,
|
|
20222
20656
|
"SpecBuilderWarning"
|
|
@@ -21535,7 +21969,7 @@ var SddParallelRun = class {
|
|
|
21535
21969
|
const failCount = results.length - successCount;
|
|
21536
21970
|
for (let i = 0; i < results.length; i++) {
|
|
21537
21971
|
const result = expectDefined(results[i]);
|
|
21538
|
-
const taskId = expectDefined(
|
|
21972
|
+
const taskId = expectDefined(tasks[i]).id;
|
|
21539
21973
|
if (result.status === "success") {
|
|
21540
21974
|
this.opts.tracker.updateNodeStatus(taskId, "completed");
|
|
21541
21975
|
this.retryMap.delete(taskId);
|
|
@@ -21749,7 +22183,7 @@ var DefaultHealthRegistry = class {
|
|
|
21749
22183
|
try {
|
|
21750
22184
|
return await Promise.race([check.check(), timeout]);
|
|
21751
22185
|
} catch (err) {
|
|
21752
|
-
return { status: "unhealthy", detail:
|
|
22186
|
+
return { status: "unhealthy", detail: toErrorMessage(err) };
|
|
21753
22187
|
} finally {
|
|
21754
22188
|
if (timer) clearTimeout(timer);
|
|
21755
22189
|
}
|
|
@@ -21942,7 +22376,7 @@ async function startMetricsServer(opts) {
|
|
|
21942
22376
|
} catch (err) {
|
|
21943
22377
|
res.statusCode = 500;
|
|
21944
22378
|
res.setHeader("content-type", "text/plain; charset=utf-8");
|
|
21945
|
-
res.end(`metrics render failed: ${
|
|
22379
|
+
res.end(`metrics render failed: ${toErrorMessage(err)}`);
|
|
21946
22380
|
return;
|
|
21947
22381
|
}
|
|
21948
22382
|
res.statusCode = 200;
|
|
@@ -21960,7 +22394,7 @@ async function startMetricsServer(opts) {
|
|
|
21960
22394
|
(err) => {
|
|
21961
22395
|
res.statusCode = 500;
|
|
21962
22396
|
res.setHeader("content-type", "text/plain; charset=utf-8");
|
|
21963
|
-
res.end(`health run failed: ${
|
|
22397
|
+
res.end(`health run failed: ${toErrorMessage(err)}`);
|
|
21964
22398
|
}
|
|
21965
22399
|
);
|
|
21966
22400
|
return;
|
|
@@ -22316,11 +22750,12 @@ function createContextManagerTool(opts = {}) {
|
|
|
22316
22750
|
const applyMessages = (next) => {
|
|
22317
22751
|
const repaired = repairToolUseAdjacency(next);
|
|
22318
22752
|
const finalMessages = repaired.messages;
|
|
22753
|
+
if (finalMessages === messages) return repaired.report;
|
|
22319
22754
|
if (ctx.state) {
|
|
22320
22755
|
ctx.state.replaceMessages(finalMessages);
|
|
22321
22756
|
} else {
|
|
22322
22757
|
messages.length = 0;
|
|
22323
|
-
messages.
|
|
22758
|
+
messages.push(...finalMessages);
|
|
22324
22759
|
}
|
|
22325
22760
|
return repaired.report;
|
|
22326
22761
|
};
|
|
@@ -22395,9 +22830,15 @@ function createContextManagerTool(opts = {}) {
|
|
|
22395
22830
|
}
|
|
22396
22831
|
const report = await opts.compactor.compact(ctx);
|
|
22397
22832
|
ctx.clearFileTracking();
|
|
22398
|
-
|
|
22399
|
-
|
|
22400
|
-
|
|
22833
|
+
let repaired = report.repaired;
|
|
22834
|
+
let afterTokens;
|
|
22835
|
+
if (!ctx.state) {
|
|
22836
|
+
const repair = applyMessages([...ctx.messages]);
|
|
22837
|
+
repaired = report.repaired ?? (repair.changed ? repair : void 0);
|
|
22838
|
+
afterTokens = repair.changed ? roughEstimate(ctx.messages) : report.after;
|
|
22839
|
+
} else {
|
|
22840
|
+
afterTokens = report.after;
|
|
22841
|
+
}
|
|
22401
22842
|
const reduced = report.fullRequestTokensBefore > report.fullRequestTokensAfter;
|
|
22402
22843
|
const repairedSomething = !!report.repaired;
|
|
22403
22844
|
if (reduced || repairedSomething) {
|