@wrongstack/core 0.264.0 → 0.267.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/{agent-bridge-D8sa1vtv.d.ts → agent-bridge-STJ3JwwK.d.ts} +1 -1
- package/dist/{agent-subagent-runner-c9DLkaas.d.ts → agent-subagent-runner-CzPGP3jA.d.ts} +131 -11
- package/dist/{brain-O1IdKPaK.d.ts → brain-Cdg77tVN.d.ts} +103 -2
- package/dist/{compactor-BBy0rCtB.d.ts → compactor-iMZ84CXq.d.ts} +19 -1
- package/dist/{config-Dz2F3H2K.d.ts → config-Du3pYYln.d.ts} +132 -13
- package/dist/{context-BGSpZNSE.d.ts → context-dT5Ueund.d.ts} +90 -12
- package/dist/coordination/index.d.ts +78 -22
- package/dist/coordination/index.js +695 -273
- 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 +2327 -965
- package/dist/defaults/index.js.map +1 -1
- package/dist/execution/index.d.ts +16 -16
- package/dist/execution/index.js +1500 -371
- package/dist/execution/index.js.map +1 -1
- package/dist/execution/prompt-enhancer.d.ts +2 -2
- package/dist/execution/prompt-enhancer.js +1 -1
- package/dist/execution/prompt-enhancer.js.map +1 -1
- package/dist/extension/index.d.ts +6 -6
- package/dist/{goal-preamble-DzjFuN3p.d.ts → goal-preamble-SulMTowG.d.ts} +33 -12
- package/dist/{goal-store-CxWmCGbH.d.ts → goal-store-CABDwdFE.d.ts} +1 -1
- package/dist/{index-CbLSI66_.d.ts → index-Bms0m4oy.d.ts} +5 -5
- package/dist/{index-CYIQrXVF.d.ts → index-DtCVWel4.d.ts} +8 -8
- package/dist/index-IEuxQd-E.d.ts +82 -0
- package/dist/index.d.ts +261 -57
- package/dist/index.js +4799 -2212
- package/dist/index.js.map +1 -1
- package/dist/infrastructure/index.d.ts +6 -6
- package/dist/infrastructure/index.js +84 -9
- package/dist/infrastructure/index.js.map +1 -1
- package/dist/kernel/index.d.ts +9 -9
- package/dist/kernel/index.js +1 -1
- package/dist/kernel/index.js.map +1 -1
- package/dist/{mcp-servers-DC4QRPUI.d.ts → mcp-servers-C2cBTxUR.d.ts} +3 -3
- package/dist/models/index.d.ts +5 -5
- package/dist/models/index.js +104 -31
- package/dist/models/index.js.map +1 -1
- package/dist/{models-registry-B_siPxqN.d.ts → models-registry-BqGZNJQ-.d.ts} +1 -1
- package/dist/{multi-agent-coordinator-CK5Jdj9K.d.ts → multi-agent-coordinator-B8R43uPz.d.ts} +1 -1
- package/dist/{null-fleet-bus-DgvD4SCO.d.ts → null-fleet-bus-CnXa5oTH.d.ts} +14 -9
- package/dist/observability/index.d.ts +2 -2
- package/dist/{parallel-eternal-engine-bK0JQBR_.d.ts → parallel-eternal-engine-DdNnw9BQ.d.ts} +11 -9
- package/dist/{path-resolver-BPEDlN38.d.ts → path-resolver-COIMLCQL.d.ts} +3 -3
- package/dist/{permission-4yvGmMRB.d.ts → permission-B75JAi3-.d.ts} +1 -1
- package/dist/{permission-policy-C6XpsBOy.d.ts → permission-policy-DlR9eJAM.d.ts} +2 -2
- package/dist/{pipeline-CXCeMz8J.d.ts → pipeline-BfD2k1rT.d.ts} +3 -3
- package/dist/{plan-templates-BvzRBkJc.d.ts → plan-templates-DSIKCXZN.d.ts} +32 -8
- package/dist/provider-model-resolve-BNRsNuJx.d.ts +107 -0
- package/dist/{provider-runner-C5aQpDWE.d.ts → provider-runner-CX7iIvox.d.ts} +3 -3
- package/dist/{retry-policy-CFhdtRzz.d.ts → retry-policy-BilV1ujH.d.ts} +1 -1
- package/dist/sdd/index.d.ts +8 -8
- package/dist/sdd/index.js +286 -105
- package/dist/sdd/index.js.map +1 -1
- package/dist/secret-vault-BAKpgFw_.d.ts +57 -0
- package/dist/{secret-vault-CxiVLbt1.d.ts → secret-vault-gkvEZZfE.d.ts} +43 -4
- package/dist/security/index.d.ts +6 -68
- package/dist/security/index.js +296 -95
- package/dist/security/index.js.map +1 -1
- package/dist/{selector-gIuhRTkN.d.ts → selector-Bc7eWtT3.d.ts} +1 -1
- package/dist/{session-event-bridge-DkvvrpDt.d.ts → session-event-bridge-D-araDEz.d.ts} +1 -1
- package/dist/{session-reader-KdfVwkKP.d.ts → session-reader-D7Dapswh.d.ts} +1 -1
- package/dist/storage/index.d.ts +112 -15
- package/dist/storage/index.js +491 -156
- package/dist/storage/index.js.map +1 -1
- package/dist/tools/index.d.ts +4 -2
- package/dist/tools/index.js.map +1 -1
- package/dist/types/index.d.ts +21 -21
- package/dist/types/index.js +1523 -450
- package/dist/types/index.js.map +1 -1
- package/dist/utils/index.d.ts +455 -407
- package/dist/utils/index.js +2191 -1203
- 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 +1 -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/llm-selector-DzxuZnNz.d.ts +0 -58
- 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
3
|
import * as fsp2 from 'fs/promises';
|
|
4
|
-
import * as
|
|
4
|
+
import * as path4 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,9 +33,9 @@ __export(atomic_write_exports, {
|
|
|
33
33
|
withFileLock: () => withFileLock
|
|
34
34
|
});
|
|
35
35
|
async function atomicWrite(targetPath, content, opts = {}) {
|
|
36
|
-
const dir =
|
|
36
|
+
const dir = path4.dirname(targetPath);
|
|
37
37
|
await fsp2.mkdir(dir, { recursive: true });
|
|
38
|
-
const tmp =
|
|
38
|
+
const tmp = path4.join(dir, `.${path4.basename(targetPath)}.${randomBytes(6).toString("hex")}.tmp`);
|
|
39
39
|
try {
|
|
40
40
|
if (typeof content === "string") {
|
|
41
41
|
await fsp2.writeFile(tmp, content, { flag: "wx", encoding: opts.encoding ?? "utf8" });
|
|
@@ -74,9 +74,9 @@ async function ensureDir(dir) {
|
|
|
74
74
|
await fsp2.mkdir(dir, { recursive: true });
|
|
75
75
|
}
|
|
76
76
|
async function withFileLock(targetPath, fn, opts = {}) {
|
|
77
|
-
const dir =
|
|
77
|
+
const dir = path4.dirname(targetPath);
|
|
78
78
|
await fsp2.mkdir(dir, { recursive: true });
|
|
79
|
-
const lockPath =
|
|
79
|
+
const lockPath = path4.join(dir, `.${path4.basename(targetPath)}.lock`);
|
|
80
80
|
const timeoutMs = opts.timeoutMs ?? 5e3;
|
|
81
81
|
const staleMs = opts.staleMs ?? 3e4;
|
|
82
82
|
const started = Date.now();
|
|
@@ -105,7 +105,7 @@ async function withFileLock(targetPath, fn, opts = {}) {
|
|
|
105
105
|
if (Date.now() - started >= timeoutMs) {
|
|
106
106
|
throw new Error(`Timed out waiting for file lock: ${targetPath}`);
|
|
107
107
|
}
|
|
108
|
-
await new Promise((
|
|
108
|
+
await new Promise((resolve6) => setTimeout(resolve6, 25));
|
|
109
109
|
}
|
|
110
110
|
}
|
|
111
111
|
try {
|
|
@@ -138,7 +138,7 @@ async function renameWithRetry(from, to) {
|
|
|
138
138
|
if (!code || !TRANSIENT_RENAME_CODES.has(code) || i === delays.length) {
|
|
139
139
|
throw err;
|
|
140
140
|
}
|
|
141
|
-
await new Promise((
|
|
141
|
+
await new Promise((resolve6) => setTimeout(resolve6, delays[i]));
|
|
142
142
|
}
|
|
143
143
|
}
|
|
144
144
|
throw lastErr;
|
|
@@ -234,7 +234,7 @@ var DefaultLogger = class _DefaultLogger {
|
|
|
234
234
|
this.maxFileBytes = opts.maxFileBytes ?? 10 * 1024 * 1024;
|
|
235
235
|
if (this.file) {
|
|
236
236
|
try {
|
|
237
|
-
|
|
237
|
+
fs4.mkdirSync(path4.dirname(this.file), { recursive: true });
|
|
238
238
|
} catch {
|
|
239
239
|
}
|
|
240
240
|
}
|
|
@@ -275,10 +275,10 @@ var DefaultLogger = class _DefaultLogger {
|
|
|
275
275
|
maybeRotate(file) {
|
|
276
276
|
if (this.writesSinceRotateCheck++ % _DefaultLogger.ROTATE_CHECK_EVERY !== 0) return;
|
|
277
277
|
try {
|
|
278
|
-
const st =
|
|
278
|
+
const st = fs4.statSync(file);
|
|
279
279
|
if (st.size < this.maxFileBytes) return;
|
|
280
|
-
|
|
281
|
-
|
|
280
|
+
fs4.rmSync(`${file}.1`, { force: true });
|
|
281
|
+
fs4.renameSync(file, `${file}.1`);
|
|
282
282
|
} catch {
|
|
283
283
|
}
|
|
284
284
|
}
|
|
@@ -294,7 +294,7 @@ var DefaultLogger = class _DefaultLogger {
|
|
|
294
294
|
if (this.file) {
|
|
295
295
|
try {
|
|
296
296
|
this.maybeRotate(this.file);
|
|
297
|
-
|
|
297
|
+
fs4.appendFileSync(this.file, `${JSON.stringify(entry)}
|
|
298
298
|
`);
|
|
299
299
|
} catch {
|
|
300
300
|
}
|
|
@@ -436,32 +436,268 @@ function isEmptyMessage(msg) {
|
|
|
436
436
|
return msg.content.length === 0;
|
|
437
437
|
}
|
|
438
438
|
|
|
439
|
+
// src/utils/assert-never.ts
|
|
440
|
+
function assertNever(x, message) {
|
|
441
|
+
const err = new Error(
|
|
442
|
+
`Unhandled case: ${JSON.stringify(x)}`
|
|
443
|
+
);
|
|
444
|
+
err.name = "AssertNeverError";
|
|
445
|
+
throw err;
|
|
446
|
+
}
|
|
447
|
+
|
|
439
448
|
// src/utils/index.ts
|
|
440
449
|
init_atomic_write();
|
|
450
|
+
var MAX_DIGEST_CHARS = 4e3;
|
|
451
|
+
function createContextEvidenceState() {
|
|
452
|
+
return {
|
|
453
|
+
sessionGoals: [],
|
|
454
|
+
implicitFacts: [],
|
|
455
|
+
activeErrors: [],
|
|
456
|
+
toolCalls: [],
|
|
457
|
+
fileGraph: {},
|
|
458
|
+
repeatedReads: [],
|
|
459
|
+
updatedAt: Date.now()
|
|
460
|
+
};
|
|
461
|
+
}
|
|
462
|
+
function buildContextEvidenceDigest(ctx) {
|
|
463
|
+
const state = ensureEvidence(ctx);
|
|
464
|
+
const lines = [];
|
|
465
|
+
if (state.currentIntent?.text) {
|
|
466
|
+
lines.push(`intent: ${state.currentIntent.text}`);
|
|
467
|
+
}
|
|
468
|
+
const goals = state.sessionGoals.slice(-3);
|
|
469
|
+
if (goals.length > 0) {
|
|
470
|
+
lines.push("session_goals:");
|
|
471
|
+
for (const goal of goals) lines.push(`- ${goal}`);
|
|
472
|
+
}
|
|
473
|
+
const activeErrors = state.activeErrors.slice(-5);
|
|
474
|
+
if (activeErrors.length > 0) {
|
|
475
|
+
lines.push("active_errors:");
|
|
476
|
+
for (const err of activeErrors) lines.push(`- ${err}`);
|
|
477
|
+
}
|
|
478
|
+
const files = Object.values(state.fileGraph).sort((a, b) => b.writes - a.writes || b.reads - a.reads || a.path.localeCompare(b.path)).slice(0, 12);
|
|
479
|
+
if (files.length > 0) {
|
|
480
|
+
lines.push("dependency_graph:");
|
|
481
|
+
for (const file of files) {
|
|
482
|
+
const actions = [
|
|
483
|
+
file.reads > 0 ? `read ${file.reads}x` : "",
|
|
484
|
+
file.writes > 0 ? `write ${file.writes}x` : ""
|
|
485
|
+
].filter(Boolean).join(", ");
|
|
486
|
+
const refs = file.referenced ? "; referenced by assistant" : "";
|
|
487
|
+
const via = file.lastToolUseId ? `; last via ${file.lastToolUseId}` : "";
|
|
488
|
+
lines.push(`- ${file.path} (${actions || "seen"}${refs}${via})`);
|
|
489
|
+
}
|
|
490
|
+
}
|
|
491
|
+
const referenced = state.toolCalls.filter((tool) => tool.status === "referenced").slice(-10);
|
|
492
|
+
const recentSeen = state.toolCalls.filter((tool) => tool.status === "seen").slice(-5);
|
|
493
|
+
const trail = [...referenced, ...recentSeen];
|
|
494
|
+
if (trail.length > 0) {
|
|
495
|
+
lines.push("tool_trail:");
|
|
496
|
+
for (const tool of trail) {
|
|
497
|
+
const size = tool.outputTokens ? `; ~${tool.outputTokens} tokens` : "";
|
|
498
|
+
const filesText = tool.files.length > 0 ? `; files=${tool.files.slice(0, 4).join(", ")}` : "";
|
|
499
|
+
const symbolsText = tool.symbols.length > 0 ? `; symbols=${tool.symbols.slice(0, 4).join(", ")}` : "";
|
|
500
|
+
lines.push(
|
|
501
|
+
`- ${tool.toolUseId} ${tool.toolName} ${tool.status}: ${tool.summary}${filesText}${symbolsText}${size}`
|
|
502
|
+
);
|
|
503
|
+
}
|
|
504
|
+
}
|
|
505
|
+
const facts = state.implicitFacts.slice(-8);
|
|
506
|
+
if (facts.length > 0) {
|
|
507
|
+
lines.push("implicit_facts:");
|
|
508
|
+
for (const fact of facts) lines.push(`- ${fact}`);
|
|
509
|
+
}
|
|
510
|
+
const digest = lines.join("\n");
|
|
511
|
+
if (digest.length <= MAX_DIGEST_CHARS) return digest;
|
|
512
|
+
return `${digest.slice(0, MAX_DIGEST_CHARS)}... [+${digest.length - MAX_DIGEST_CHARS} chars]`;
|
|
513
|
+
}
|
|
514
|
+
function repeatedReadPressure(ctx) {
|
|
515
|
+
return ensureEvidence(ctx).repeatedReads.reduce((max, item) => Math.max(max, item.count), 0);
|
|
516
|
+
}
|
|
517
|
+
function ensureEvidence(ctx) {
|
|
518
|
+
if (!ctx.contextEvidence) {
|
|
519
|
+
ctx.contextEvidence = createContextEvidenceState();
|
|
520
|
+
}
|
|
521
|
+
return ctx.contextEvidence;
|
|
522
|
+
}
|
|
523
|
+
|
|
524
|
+
// src/utils/deep-merge.ts
|
|
525
|
+
var FORBIDDEN_PROTO_KEYS = /* @__PURE__ */ new Set([
|
|
526
|
+
"__proto__",
|
|
527
|
+
"constructor",
|
|
528
|
+
"prototype",
|
|
529
|
+
"__defineGetter__",
|
|
530
|
+
"__defineSetter__",
|
|
531
|
+
"__lookupGetter__",
|
|
532
|
+
"__lookupSetter__"
|
|
533
|
+
]);
|
|
534
|
+
function isPrimitiveArray(a) {
|
|
535
|
+
return a.every((v) => v === null || typeof v !== "object" && typeof v !== "function");
|
|
536
|
+
}
|
|
537
|
+
function deepMerge(base, patch, options = {}) {
|
|
538
|
+
const {
|
|
539
|
+
conflictResolution = "prefer-patch",
|
|
540
|
+
arrayMode = "replace",
|
|
541
|
+
protectProto = true,
|
|
542
|
+
onNonPrimitiveArrayReplace
|
|
543
|
+
} = options;
|
|
544
|
+
if (typeof base !== "object" || base === null) {
|
|
545
|
+
return conflictResolution === "prefer-patch" ? patch : base;
|
|
546
|
+
}
|
|
547
|
+
if (typeof patch !== "object" || patch === null) {
|
|
548
|
+
return conflictResolution === "prefer-patch" ? patch : base;
|
|
549
|
+
}
|
|
550
|
+
if (Array.isArray(base) && Array.isArray(patch)) {
|
|
551
|
+
if (arrayMode === "concat-primitives" && isPrimitiveArray(base) && isPrimitiveArray(patch)) {
|
|
552
|
+
return [.../* @__PURE__ */ new Set([...base, ...patch])];
|
|
553
|
+
}
|
|
554
|
+
return conflictResolution === "prefer-patch" ? patch : base;
|
|
555
|
+
}
|
|
556
|
+
if (Array.isArray(base) || Array.isArray(patch)) {
|
|
557
|
+
return conflictResolution === "prefer-patch" ? patch : base;
|
|
558
|
+
}
|
|
559
|
+
const baseObj = base;
|
|
560
|
+
const patchObj = patch;
|
|
561
|
+
const out = { ...baseObj };
|
|
562
|
+
for (const [k, v] of Object.entries(patchObj)) {
|
|
563
|
+
if (protectProto && FORBIDDEN_PROTO_KEYS.has(k)) continue;
|
|
564
|
+
const existing = out[k];
|
|
565
|
+
if (v !== null && typeof v === "object" && !Array.isArray(v) && existing !== null && typeof existing === "object" && !Array.isArray(existing)) {
|
|
566
|
+
out[k] = deepMerge(existing, v, options);
|
|
567
|
+
} else if (Array.isArray(v) && Array.isArray(existing)) {
|
|
568
|
+
if (onNonPrimitiveArrayReplace && !isPrimitiveArray(v)) {
|
|
569
|
+
onNonPrimitiveArrayReplace(k, existing.length, v.length);
|
|
570
|
+
}
|
|
571
|
+
out[k] = deepMerge(existing, v, options);
|
|
572
|
+
} else if (v !== void 0) {
|
|
573
|
+
if (onNonPrimitiveArrayReplace && Array.isArray(v) && !isPrimitiveArray(v)) {
|
|
574
|
+
const existingLen = Array.isArray(existing) ? existing.length : 0;
|
|
575
|
+
onNonPrimitiveArrayReplace(k, existingLen, v.length);
|
|
576
|
+
}
|
|
577
|
+
out[k] = v;
|
|
578
|
+
}
|
|
579
|
+
}
|
|
580
|
+
return out;
|
|
581
|
+
}
|
|
441
582
|
|
|
442
583
|
// src/utils/error.ts
|
|
443
584
|
function toErrorMessage(err) {
|
|
444
585
|
return err instanceof Error ? err.message : String(err);
|
|
445
586
|
}
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
587
|
+
var GLOB_CHARS = /* @__PURE__ */ new Set(["*", "?", "["]);
|
|
588
|
+
var IS_WINDOWS = process.platform === "win32";
|
|
589
|
+
var SEP = IS_WINDOWS ? "\\" : "/";
|
|
590
|
+
function isGlob(p) {
|
|
591
|
+
for (const c of p) {
|
|
592
|
+
if (GLOB_CHARS.has(c)) return true;
|
|
451
593
|
}
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
594
|
+
return false;
|
|
595
|
+
}
|
|
596
|
+
function globToRegex(pat) {
|
|
597
|
+
let i = 0;
|
|
598
|
+
let re = "^";
|
|
599
|
+
while (i < pat.length) {
|
|
600
|
+
const c = expectDefined(pat[i]);
|
|
601
|
+
if (c === "*") {
|
|
602
|
+
if (pat[i + 1] === "*") {
|
|
603
|
+
re += ".*";
|
|
604
|
+
i += 2;
|
|
605
|
+
if (pat[i] === "/") i++;
|
|
606
|
+
} else {
|
|
607
|
+
re += "[^/\\\\]*";
|
|
608
|
+
i++;
|
|
609
|
+
}
|
|
610
|
+
} else if (c === "?") {
|
|
611
|
+
re += "[^/\\\\]";
|
|
612
|
+
i++;
|
|
613
|
+
} else if (c === "[") {
|
|
614
|
+
let cls = "[";
|
|
615
|
+
i++;
|
|
616
|
+
if (pat[i] === "!" || pat[i] === "^") {
|
|
617
|
+
cls += "^";
|
|
618
|
+
i++;
|
|
619
|
+
}
|
|
620
|
+
while (i < pat.length && pat[i] !== "]") {
|
|
621
|
+
const ch = pat[i] ?? "";
|
|
622
|
+
if (ch === "\\") cls += "\\\\";
|
|
623
|
+
else if (ch === "]" || ch === "^") cls += `\\${ch}`;
|
|
624
|
+
else cls += ch;
|
|
625
|
+
i++;
|
|
626
|
+
}
|
|
627
|
+
cls += "]";
|
|
628
|
+
re += cls;
|
|
629
|
+
i++;
|
|
630
|
+
} else {
|
|
631
|
+
re += c.replace(/[.+^${}()|\\]/g, "\\$&");
|
|
632
|
+
i++;
|
|
633
|
+
}
|
|
459
634
|
}
|
|
635
|
+
return new RegExp(re + "$");
|
|
460
636
|
}
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
637
|
+
function baseDir(pat) {
|
|
638
|
+
let i = pat.length - 1;
|
|
639
|
+
while (i >= 0 && !GLOB_CHARS.has(expectDefined(pat[i])) && pat[i] !== SEP && pat[i] !== "/") i--;
|
|
640
|
+
const cut = i >= 0 ? pat.lastIndexOf(SEP, i) : pat.lastIndexOf("/", i);
|
|
641
|
+
return cut < 0 ? "." : pat.slice(0, cut);
|
|
642
|
+
}
|
|
643
|
+
async function expandGlob(pattern) {
|
|
644
|
+
if (!isGlob(pattern)) return [pattern];
|
|
645
|
+
const results = /* @__PURE__ */ new Set();
|
|
646
|
+
const abs = isAbsolute(pattern);
|
|
647
|
+
const base = abs ? baseDir(pattern) : baseDir(pattern);
|
|
648
|
+
const relPat = base === "." ? pattern : pattern.slice(base.length + 1);
|
|
649
|
+
async function walk3(dir, pat) {
|
|
650
|
+
let entries;
|
|
651
|
+
try {
|
|
652
|
+
entries = await fsp2.readdir(dir);
|
|
653
|
+
} catch {
|
|
654
|
+
return;
|
|
655
|
+
}
|
|
656
|
+
const firstGlob = pat.search(/[*?[[]/);
|
|
657
|
+
if (firstGlob < 0) {
|
|
658
|
+
const re = globToRegex(pat);
|
|
659
|
+
for (const e of entries) {
|
|
660
|
+
if (re.test(e)) {
|
|
661
|
+
const full = `${dir}${SEP}${e}`;
|
|
662
|
+
results.add(abs ? resolve(full) : full);
|
|
663
|
+
}
|
|
664
|
+
}
|
|
665
|
+
return;
|
|
666
|
+
}
|
|
667
|
+
const before = pat.slice(0, firstGlob);
|
|
668
|
+
const rest = pat.slice(firstGlob);
|
|
669
|
+
if (before.endsWith("**")) {
|
|
670
|
+
await walk3(dir, rest);
|
|
671
|
+
for (const e of entries) {
|
|
672
|
+
const full = `${dir}${SEP}${e}`;
|
|
673
|
+
try {
|
|
674
|
+
const stat6 = await fsp2.stat(full);
|
|
675
|
+
if (stat6.isDirectory()) await walk3(full, rest);
|
|
676
|
+
} catch {
|
|
677
|
+
}
|
|
678
|
+
}
|
|
679
|
+
} else if (before === "") {
|
|
680
|
+
const re = globToRegex(rest);
|
|
681
|
+
for (const e of entries) {
|
|
682
|
+
if (re.test(e)) {
|
|
683
|
+
const full = `${dir}${SEP}${e}`;
|
|
684
|
+
results.add(abs ? resolve(full) : full);
|
|
685
|
+
}
|
|
686
|
+
}
|
|
687
|
+
} else {
|
|
688
|
+
const seg = before.replace(/[*?[\]]/g, "").replace(/\/$/, "");
|
|
689
|
+
if (entries.includes(seg)) {
|
|
690
|
+
const full = `${dir}${SEP}${seg}`;
|
|
691
|
+
try {
|
|
692
|
+
const stat6 = await fsp2.stat(full);
|
|
693
|
+
if (stat6.isDirectory()) await walk3(full, rest);
|
|
694
|
+
} catch {
|
|
695
|
+
}
|
|
696
|
+
}
|
|
697
|
+
}
|
|
698
|
+
}
|
|
699
|
+
await walk3(base === "." ? "." : base, relPat);
|
|
700
|
+
return [...results];
|
|
465
701
|
}
|
|
466
702
|
|
|
467
703
|
// src/utils/glob-match.ts
|
|
@@ -539,235 +775,441 @@ function matchGlob(pattern, input) {
|
|
|
539
775
|
function matchAny(patterns, input) {
|
|
540
776
|
return patterns.some((p) => matchGlob(p, input));
|
|
541
777
|
}
|
|
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
|
-
};
|
|
597
|
-
}
|
|
598
|
-
|
|
599
|
-
// src/utils/sleep.ts
|
|
600
|
-
function sleep(ms) {
|
|
601
|
-
return new Promise((resolve5) => setTimeout(resolve5, ms));
|
|
602
|
-
}
|
|
603
778
|
|
|
604
|
-
// src/utils/
|
|
605
|
-
function
|
|
606
|
-
|
|
607
|
-
|
|
608
|
-
);
|
|
609
|
-
err.name = "AssertNeverError";
|
|
610
|
-
throw err;
|
|
779
|
+
// src/utils/json-repair.ts
|
|
780
|
+
function completePartialObject(s) {
|
|
781
|
+
if (!s.trim().startsWith("{")) return s;
|
|
782
|
+
if (tryParse(s).ok) return s;
|
|
783
|
+
return repairTruncated(s);
|
|
611
784
|
}
|
|
612
|
-
|
|
613
|
-
|
|
614
|
-
|
|
615
|
-
|
|
616
|
-
|
|
617
|
-
"
|
|
618
|
-
|
|
619
|
-
|
|
620
|
-
|
|
621
|
-
|
|
622
|
-
|
|
623
|
-
|
|
624
|
-
|
|
625
|
-
|
|
626
|
-
|
|
627
|
-
|
|
628
|
-
|
|
629
|
-
|
|
630
|
-
|
|
631
|
-
|
|
632
|
-
|
|
633
|
-
|
|
634
|
-
|
|
635
|
-
|
|
636
|
-
|
|
637
|
-
|
|
785
|
+
function repairTruncated(s) {
|
|
786
|
+
const stack = [];
|
|
787
|
+
let inString = false;
|
|
788
|
+
let escaped = false;
|
|
789
|
+
let sawKey = false;
|
|
790
|
+
let prevSig = "";
|
|
791
|
+
let contentEnd = 0;
|
|
792
|
+
let stringBraceDepth = 0;
|
|
793
|
+
for (let i = 0; i < s.length; i++) {
|
|
794
|
+
const ch = expectDefined(s[i]);
|
|
795
|
+
if (inString) {
|
|
796
|
+
contentEnd = i + 1;
|
|
797
|
+
if (escaped) {
|
|
798
|
+
escaped = false;
|
|
799
|
+
continue;
|
|
800
|
+
}
|
|
801
|
+
if (ch === "\\") {
|
|
802
|
+
escaped = true;
|
|
803
|
+
continue;
|
|
804
|
+
}
|
|
805
|
+
if (ch === '"') {
|
|
806
|
+
inString = false;
|
|
807
|
+
prevSig = '"';
|
|
808
|
+
stringBraceDepth = 0;
|
|
809
|
+
continue;
|
|
810
|
+
}
|
|
811
|
+
if (ch === "{") stringBraceDepth++;
|
|
812
|
+
else if (ch === "}" && stringBraceDepth > 0) stringBraceDepth--;
|
|
813
|
+
continue;
|
|
814
|
+
}
|
|
815
|
+
if (ch === " " || ch === " " || ch === "\n" || ch === "\r") continue;
|
|
816
|
+
contentEnd = i + 1;
|
|
817
|
+
if (ch === '"') {
|
|
818
|
+
inString = true;
|
|
819
|
+
sawKey = true;
|
|
820
|
+
stringBraceDepth = 0;
|
|
821
|
+
prevSig = '"';
|
|
822
|
+
} else if (ch === "{" || ch === "[") {
|
|
823
|
+
stack.push(ch);
|
|
824
|
+
prevSig = ch;
|
|
825
|
+
} else if (ch === "}" || ch === "]") {
|
|
826
|
+
stack.pop();
|
|
827
|
+
prevSig = ch;
|
|
828
|
+
} else {
|
|
829
|
+
prevSig = ch;
|
|
830
|
+
}
|
|
638
831
|
}
|
|
639
|
-
if (
|
|
640
|
-
|
|
641
|
-
|
|
832
|
+
if (!sawKey && !inString) return s;
|
|
833
|
+
let result = s.slice(0, contentEnd);
|
|
834
|
+
if (inString) {
|
|
835
|
+
if (escaped) {
|
|
836
|
+
result = result.slice(0, -1);
|
|
837
|
+
} else if (endsWithInvalidEscape(result)) {
|
|
838
|
+
result = result.slice(0, -2);
|
|
642
839
|
}
|
|
643
|
-
|
|
840
|
+
if (stringBraceDepth > 0) result += "}".repeat(stringBraceDepth);
|
|
841
|
+
result += '"';
|
|
842
|
+
} else if (prevSig === ":") {
|
|
843
|
+
result += "null";
|
|
644
844
|
}
|
|
645
|
-
|
|
646
|
-
|
|
845
|
+
for (let k = stack.length - 1; k >= 0; k--) {
|
|
846
|
+
result += stack[k] === "{" ? "}" : "]";
|
|
647
847
|
}
|
|
648
|
-
|
|
649
|
-
|
|
650
|
-
|
|
651
|
-
|
|
652
|
-
|
|
653
|
-
|
|
654
|
-
|
|
655
|
-
|
|
656
|
-
|
|
657
|
-
|
|
658
|
-
|
|
659
|
-
|
|
660
|
-
|
|
661
|
-
|
|
662
|
-
|
|
663
|
-
|
|
664
|
-
|
|
665
|
-
|
|
666
|
-
|
|
667
|
-
}
|
|
848
|
+
if (!tryParse(result).ok) {
|
|
849
|
+
const patched = result.replace(/:(\s*)([}\]])/g, ":null$2");
|
|
850
|
+
if (tryParse(patched).ok) result = patched;
|
|
851
|
+
}
|
|
852
|
+
return result;
|
|
853
|
+
}
|
|
854
|
+
var VALID_ESCAPE = /* @__PURE__ */ new Set(['"', "\\", "/", "b", "f", "n", "r", "t", "u"]);
|
|
855
|
+
function endsWithInvalidEscape(str) {
|
|
856
|
+
const last = str[str.length - 1];
|
|
857
|
+
if (str[str.length - 2] !== "\\" || last === void 0) return false;
|
|
858
|
+
if (VALID_ESCAPE.has(last)) return false;
|
|
859
|
+
let backslashes = 0;
|
|
860
|
+
for (let k = str.length - 2; k >= 0 && str[k] === "\\"; k--) backslashes++;
|
|
861
|
+
return backslashes % 2 === 1;
|
|
862
|
+
}
|
|
863
|
+
function tryParse(s) {
|
|
864
|
+
try {
|
|
865
|
+
return { ok: true, value: JSON.parse(s) };
|
|
866
|
+
} catch {
|
|
867
|
+
return { ok: false };
|
|
668
868
|
}
|
|
669
|
-
return out;
|
|
670
869
|
}
|
|
671
870
|
|
|
672
|
-
// src/utils/
|
|
673
|
-
function
|
|
674
|
-
const
|
|
675
|
-
|
|
676
|
-
|
|
677
|
-
|
|
678
|
-
|
|
679
|
-
|
|
680
|
-
|
|
681
|
-
|
|
682
|
-
|
|
683
|
-
|
|
684
|
-
|
|
685
|
-
|
|
686
|
-
} catch {
|
|
687
|
-
return String(value);
|
|
688
|
-
}
|
|
871
|
+
// src/utils/json-schema-validate.ts
|
|
872
|
+
function validateAgainstSchema(value, schema) {
|
|
873
|
+
const errors = [];
|
|
874
|
+
walk(value, schema, "", errors);
|
|
875
|
+
return { ok: errors.length === 0, errors };
|
|
876
|
+
}
|
|
877
|
+
function walk(value, schema, path21, errors) {
|
|
878
|
+
if (schema.enum !== void 0) {
|
|
879
|
+
if (!schema.enum.some((e) => deepEqual(e, value))) {
|
|
880
|
+
errors.push({
|
|
881
|
+
path: path21 || "<root>",
|
|
882
|
+
message: `expected one of ${JSON.stringify(schema.enum)}, got ${JSON.stringify(value)}`
|
|
883
|
+
});
|
|
884
|
+
return;
|
|
689
885
|
}
|
|
690
|
-
return String(value);
|
|
691
886
|
}
|
|
692
|
-
|
|
693
|
-
if (
|
|
694
|
-
|
|
887
|
+
if (typeof schema.type === "string") {
|
|
888
|
+
if (!checkType(value, schema.type)) {
|
|
889
|
+
errors.push({
|
|
890
|
+
path: path21 || "<root>",
|
|
891
|
+
message: `expected ${schema.type}, got ${describeType(value)}`
|
|
892
|
+
});
|
|
893
|
+
return;
|
|
695
894
|
}
|
|
696
|
-
|
|
697
|
-
|
|
698
|
-
|
|
895
|
+
}
|
|
896
|
+
if (schema.type === "object" && isPlainObject(value)) {
|
|
897
|
+
const obj = value;
|
|
898
|
+
for (const req of schema.required ?? []) {
|
|
899
|
+
if (!(req in obj)) {
|
|
900
|
+
errors.push({ path: joinPath(path21, req), message: "required property missing" });
|
|
901
|
+
}
|
|
699
902
|
}
|
|
700
|
-
|
|
701
|
-
|
|
702
|
-
|
|
703
|
-
|
|
704
|
-
|
|
705
|
-
|
|
706
|
-
return { text: "[truncated: iteration output cap exceeded]", newBudget: 0 };
|
|
903
|
+
if (schema.properties) {
|
|
904
|
+
for (const [key, subSchema] of Object.entries(schema.properties)) {
|
|
905
|
+
if (key in obj) {
|
|
906
|
+
walk(obj[key], subSchema, joinPath(path21, key), errors);
|
|
907
|
+
}
|
|
908
|
+
}
|
|
707
909
|
}
|
|
708
|
-
const half = Math.floor(available / 2);
|
|
709
|
-
const first = text.slice(0, half);
|
|
710
|
-
const second = text.slice(text.length - half);
|
|
711
|
-
return { text: `${first}${marker}${second}`, newBudget: 0 };
|
|
712
|
-
}
|
|
713
|
-
return { serialize, enforceCap, capBytes };
|
|
714
|
-
}
|
|
715
|
-
|
|
716
|
-
// src/utils/token-estimate.ts
|
|
717
|
-
var RoughTokenEstimate = (text, charsPerToken = 3.5) => Math.max(1, Math.ceil(text.length / charsPerToken));
|
|
718
|
-
var CALIBRATION_GLOBAL_KEY = "__global__";
|
|
719
|
-
var _cals = /* @__PURE__ */ new Map();
|
|
720
|
-
function calState(key) {
|
|
721
|
-
let state = _cals.get(key);
|
|
722
|
-
if (!state) {
|
|
723
|
-
state = { ratio: 1, count: 0, prevEst: 0 };
|
|
724
|
-
_cals.set(key, state);
|
|
725
910
|
}
|
|
726
|
-
|
|
727
|
-
|
|
728
|
-
|
|
729
|
-
var ESTIMATE_CACHE = /* @__PURE__ */ new Map();
|
|
730
|
-
var ESTIMATE_CACHE_MAX_SIZE = 1e4;
|
|
731
|
-
function getCachedEstimate(key, compute) {
|
|
732
|
-
const existing = ESTIMATE_CACHE.get(key);
|
|
733
|
-
if (existing !== void 0) return existing;
|
|
734
|
-
if (ESTIMATE_CACHE.size >= ESTIMATE_CACHE_MAX_SIZE) {
|
|
735
|
-
let evicted = 0;
|
|
736
|
-
const maxEvict = Math.floor(ESTIMATE_CACHE_MAX_SIZE / 4);
|
|
737
|
-
for (const k of ESTIMATE_CACHE.keys()) {
|
|
738
|
-
if (evicted >= maxEvict) break;
|
|
739
|
-
ESTIMATE_CACHE.delete(k);
|
|
740
|
-
evicted++;
|
|
911
|
+
if (schema.type === "array" && Array.isArray(value) && schema.items) {
|
|
912
|
+
for (let i = 0; i < value.length; i++) {
|
|
913
|
+
walk(value[i], schema.items, `${path21}[${i}]`, errors);
|
|
741
914
|
}
|
|
742
915
|
}
|
|
743
|
-
const estimate = compute(key);
|
|
744
|
-
ESTIMATE_CACHE.set(key, estimate);
|
|
745
|
-
return estimate;
|
|
746
916
|
}
|
|
747
|
-
function
|
|
748
|
-
|
|
749
|
-
|
|
750
|
-
|
|
917
|
+
function checkType(value, type) {
|
|
918
|
+
switch (type) {
|
|
919
|
+
case "string":
|
|
920
|
+
return typeof value === "string";
|
|
921
|
+
case "number":
|
|
922
|
+
return typeof value === "number" && !Number.isNaN(value);
|
|
923
|
+
case "integer":
|
|
924
|
+
return typeof value === "number" && Number.isInteger(value);
|
|
925
|
+
case "boolean":
|
|
926
|
+
return typeof value === "boolean";
|
|
927
|
+
case "null":
|
|
928
|
+
return value === null;
|
|
929
|
+
case "array":
|
|
930
|
+
return Array.isArray(value);
|
|
931
|
+
case "object":
|
|
932
|
+
return isPlainObject(value);
|
|
933
|
+
default:
|
|
934
|
+
return true;
|
|
751
935
|
}
|
|
752
|
-
return getCachedEstimate(JSON.stringify(input), (key) => RoughTokenEstimate(key));
|
|
753
936
|
}
|
|
754
|
-
function
|
|
755
|
-
|
|
756
|
-
return getCachedEstimate(JSON.stringify(content), (key) => RoughTokenEstimate(key));
|
|
937
|
+
function isPlainObject(v) {
|
|
938
|
+
return typeof v === "object" && v !== null && !Array.isArray(v);
|
|
757
939
|
}
|
|
758
|
-
function
|
|
759
|
-
|
|
940
|
+
function describeType(v) {
|
|
941
|
+
if (v === null) return "null";
|
|
942
|
+
if (Array.isArray(v)) return "array";
|
|
943
|
+
return typeof v;
|
|
760
944
|
}
|
|
761
|
-
function
|
|
762
|
-
if (
|
|
763
|
-
|
|
764
|
-
|
|
765
|
-
|
|
766
|
-
|
|
767
|
-
|
|
768
|
-
|
|
769
|
-
|
|
770
|
-
|
|
945
|
+
function joinPath(parent, key) {
|
|
946
|
+
if (!parent) return key;
|
|
947
|
+
return `${parent}.${key}`;
|
|
948
|
+
}
|
|
949
|
+
function deepEqual(a, b) {
|
|
950
|
+
if (a === b) return true;
|
|
951
|
+
if (typeof a !== typeof b) return false;
|
|
952
|
+
if (a === null || b === null) return a === b;
|
|
953
|
+
if (Array.isArray(a) && Array.isArray(b)) {
|
|
954
|
+
return a.length === b.length && a.every((v, i) => deepEqual(v, b[i]));
|
|
955
|
+
}
|
|
956
|
+
if (typeof a === "object" && typeof b === "object") {
|
|
957
|
+
const ak = Object.keys(a);
|
|
958
|
+
const bk = Object.keys(b);
|
|
959
|
+
if (ak.length !== bk.length) return false;
|
|
960
|
+
return ak.every(
|
|
961
|
+
(k) => deepEqual(a[k], b[k])
|
|
962
|
+
);
|
|
963
|
+
}
|
|
964
|
+
return false;
|
|
965
|
+
}
|
|
966
|
+
|
|
967
|
+
// src/utils/merge-models-payload.ts
|
|
968
|
+
function mergeModelsPayload(base, overlay) {
|
|
969
|
+
const out = {};
|
|
970
|
+
for (const [id, provider] of Object.entries(base)) {
|
|
971
|
+
out[id] = cloneProvider(provider);
|
|
972
|
+
}
|
|
973
|
+
for (const [id, ovProvider] of Object.entries(overlay)) {
|
|
974
|
+
const existing = out[id];
|
|
975
|
+
out[id] = existing ? mergeProvider(existing, ovProvider) : cloneProvider(ovProvider);
|
|
976
|
+
}
|
|
977
|
+
return out;
|
|
978
|
+
}
|
|
979
|
+
function mergeProvider(base, overlay) {
|
|
980
|
+
const models = {};
|
|
981
|
+
for (const [mid, m] of Object.entries(base.models ?? {})) {
|
|
982
|
+
models[mid] = { ...m };
|
|
983
|
+
}
|
|
984
|
+
for (const [mid, ovModel] of Object.entries(overlay.models ?? {})) {
|
|
985
|
+
const existing = models[mid];
|
|
986
|
+
models[mid] = existing ? mergeModel(existing, ovModel) : { ...ovModel };
|
|
987
|
+
}
|
|
988
|
+
return {
|
|
989
|
+
...base,
|
|
990
|
+
// Overlay scalar fields win when explicitly provided; otherwise keep base.
|
|
991
|
+
...stripUndefined({
|
|
992
|
+
id: overlay.id,
|
|
993
|
+
name: overlay.name,
|
|
994
|
+
npm: overlay.npm,
|
|
995
|
+
api: overlay.api,
|
|
996
|
+
env: overlay.env,
|
|
997
|
+
doc: overlay.doc
|
|
998
|
+
}),
|
|
999
|
+
models
|
|
1000
|
+
};
|
|
1001
|
+
}
|
|
1002
|
+
function mergeModel(base, overlay) {
|
|
1003
|
+
const merged = { ...base, ...overlay };
|
|
1004
|
+
if (base.limit || overlay.limit) {
|
|
1005
|
+
merged.limit = { ...base.limit, ...overlay.limit };
|
|
1006
|
+
}
|
|
1007
|
+
if (base.cost || overlay.cost) {
|
|
1008
|
+
merged.cost = { ...base.cost, ...overlay.cost };
|
|
1009
|
+
}
|
|
1010
|
+
if (base.modalities || overlay.modalities) {
|
|
1011
|
+
merged.modalities = { ...base.modalities, ...overlay.modalities };
|
|
1012
|
+
}
|
|
1013
|
+
return merged;
|
|
1014
|
+
}
|
|
1015
|
+
function cloneProvider(p) {
|
|
1016
|
+
const models = {};
|
|
1017
|
+
for (const [mid, m] of Object.entries(p.models ?? {})) {
|
|
1018
|
+
models[mid] = { ...m };
|
|
1019
|
+
}
|
|
1020
|
+
return { ...p, models };
|
|
1021
|
+
}
|
|
1022
|
+
function stripUndefined(obj) {
|
|
1023
|
+
const out = {};
|
|
1024
|
+
for (const [k, v] of Object.entries(obj)) {
|
|
1025
|
+
if (v !== void 0) out[k] = v;
|
|
1026
|
+
}
|
|
1027
|
+
return out;
|
|
1028
|
+
}
|
|
1029
|
+
|
|
1030
|
+
// src/utils/regex-guard.ts
|
|
1031
|
+
var MAX_PATTERN_LEN = 512;
|
|
1032
|
+
var DANGEROUS_PATTERNS = [
|
|
1033
|
+
/(\([^)]*[+*][^)]*\))[+*]/,
|
|
1034
|
+
// (a+)+, (.*)+, etc
|
|
1035
|
+
/(\(\?:[^)]*[+*][^)]*\))[+*]/
|
|
1036
|
+
// same, with non-capturing group
|
|
1037
|
+
];
|
|
1038
|
+
function compileUserRegex(pattern, flags) {
|
|
1039
|
+
if (typeof pattern !== "string") {
|
|
1040
|
+
return { ok: false, reason: "pattern must be a string" };
|
|
1041
|
+
}
|
|
1042
|
+
if (pattern.length === 0) {
|
|
1043
|
+
return { ok: false, reason: "pattern is empty" };
|
|
1044
|
+
}
|
|
1045
|
+
if (pattern.length > MAX_PATTERN_LEN) {
|
|
1046
|
+
return { ok: false, reason: `pattern exceeds ${MAX_PATTERN_LEN} characters` };
|
|
1047
|
+
}
|
|
1048
|
+
for (const rx of DANGEROUS_PATTERNS) {
|
|
1049
|
+
if (rx.test(pattern)) {
|
|
1050
|
+
return {
|
|
1051
|
+
ok: false,
|
|
1052
|
+
reason: "pattern looks vulnerable to catastrophic backtracking \u2014 rewrite without nested quantifiers"
|
|
1053
|
+
};
|
|
1054
|
+
}
|
|
1055
|
+
}
|
|
1056
|
+
try {
|
|
1057
|
+
return { ok: true, regex: new RegExp(pattern, flags) };
|
|
1058
|
+
} catch (err) {
|
|
1059
|
+
return {
|
|
1060
|
+
ok: false,
|
|
1061
|
+
reason: err instanceof Error ? err.message : "invalid regex"
|
|
1062
|
+
};
|
|
1063
|
+
}
|
|
1064
|
+
}
|
|
1065
|
+
|
|
1066
|
+
// src/utils/safe-json.ts
|
|
1067
|
+
function safeParse(input, maxBytes = 5e6) {
|
|
1068
|
+
if (input.length > maxBytes) {
|
|
1069
|
+
return { ok: false, error: `Input exceeds limit (${maxBytes} bytes)` };
|
|
1070
|
+
}
|
|
1071
|
+
try {
|
|
1072
|
+
return { ok: true, value: JSON.parse(input) };
|
|
1073
|
+
} catch (err) {
|
|
1074
|
+
return {
|
|
1075
|
+
ok: false,
|
|
1076
|
+
error: toErrorMessage(err)
|
|
1077
|
+
};
|
|
1078
|
+
}
|
|
1079
|
+
}
|
|
1080
|
+
|
|
1081
|
+
// src/utils/sleep.ts
|
|
1082
|
+
function sleep(ms) {
|
|
1083
|
+
return new Promise((resolve6) => setTimeout(resolve6, ms));
|
|
1084
|
+
}
|
|
1085
|
+
|
|
1086
|
+
// src/utils/string.ts
|
|
1087
|
+
function truncate(s, max) {
|
|
1088
|
+
return s.length <= max ? s : `${s.slice(0, max - 1)}\u2026`;
|
|
1089
|
+
}
|
|
1090
|
+
|
|
1091
|
+
// src/utils/tool-wire-compact.ts
|
|
1092
|
+
var TOOL_DESCRIPTION_MAX_CHARS = 640;
|
|
1093
|
+
var SCHEMA_DESCRIPTION_MAX_CHARS = 180;
|
|
1094
|
+
var compactCache = /* @__PURE__ */ new WeakMap();
|
|
1095
|
+
function compactToolDefinitionForWire(tool, opts = {}) {
|
|
1096
|
+
const useDefaultOptions = opts.descriptionMaxChars === void 0 && opts.schemaDescriptionMaxChars === void 0;
|
|
1097
|
+
if (useDefaultOptions && typeof tool === "object" && tool !== null) {
|
|
1098
|
+
const cached = compactCache.get(tool);
|
|
1099
|
+
if (cached) return cached;
|
|
1100
|
+
}
|
|
1101
|
+
const compact = {
|
|
1102
|
+
name: tool.name,
|
|
1103
|
+
description: compactDescription(
|
|
1104
|
+
tool.description ?? "",
|
|
1105
|
+
opts.descriptionMaxChars ?? TOOL_DESCRIPTION_MAX_CHARS
|
|
1106
|
+
),
|
|
1107
|
+
inputSchema: compactSchemaDescriptions(
|
|
1108
|
+
tool.inputSchema,
|
|
1109
|
+
opts.schemaDescriptionMaxChars ?? SCHEMA_DESCRIPTION_MAX_CHARS
|
|
1110
|
+
)
|
|
1111
|
+
};
|
|
1112
|
+
if (useDefaultOptions && typeof tool === "object" && tool !== null) {
|
|
1113
|
+
compactCache.set(tool, compact);
|
|
1114
|
+
}
|
|
1115
|
+
return compact;
|
|
1116
|
+
}
|
|
1117
|
+
function compactSchemaDescriptions(schema, maxDescriptionChars = SCHEMA_DESCRIPTION_MAX_CHARS) {
|
|
1118
|
+
const compact = compactSchemaNode(schema, maxDescriptionChars);
|
|
1119
|
+
return isRecord(compact) ? compact : { type: "object", properties: {} };
|
|
1120
|
+
}
|
|
1121
|
+
function compactSchemaNode(node, maxDescriptionChars) {
|
|
1122
|
+
if (Array.isArray(node)) {
|
|
1123
|
+
return node.map((item) => compactSchemaNode(item, maxDescriptionChars));
|
|
1124
|
+
}
|
|
1125
|
+
if (!isRecord(node)) return node;
|
|
1126
|
+
const out = {};
|
|
1127
|
+
for (const [key, value] of Object.entries(node)) {
|
|
1128
|
+
if (key === "description" && typeof value === "string") {
|
|
1129
|
+
out[key] = compactDescription(value, maxDescriptionChars);
|
|
1130
|
+
} else {
|
|
1131
|
+
out[key] = compactSchemaNode(value, maxDescriptionChars);
|
|
1132
|
+
}
|
|
1133
|
+
}
|
|
1134
|
+
return out;
|
|
1135
|
+
}
|
|
1136
|
+
function compactDescription(text, maxChars) {
|
|
1137
|
+
const normalized = text.replace(/\s+/g, " ").trim();
|
|
1138
|
+
if (normalized.length <= maxChars) return normalized;
|
|
1139
|
+
if (maxChars <= 20) return normalized.slice(0, maxChars);
|
|
1140
|
+
const hardLimit = maxChars - 12;
|
|
1141
|
+
const boundary = findSemanticBoundary(normalized, hardLimit);
|
|
1142
|
+
const head = normalized.slice(0, boundary > 0 ? boundary : hardLimit).trimEnd();
|
|
1143
|
+
return `${head} ...`;
|
|
1144
|
+
}
|
|
1145
|
+
function findSemanticBoundary(text, limit) {
|
|
1146
|
+
const punctuation = Math.max(
|
|
1147
|
+
text.lastIndexOf(". ", limit),
|
|
1148
|
+
text.lastIndexOf("; ", limit),
|
|
1149
|
+
text.lastIndexOf(": ", limit)
|
|
1150
|
+
);
|
|
1151
|
+
if (punctuation >= Math.floor(limit * 0.45)) return punctuation + 1;
|
|
1152
|
+
const comma = text.lastIndexOf(", ", limit);
|
|
1153
|
+
if (comma >= Math.floor(limit * 0.6)) return comma + 1;
|
|
1154
|
+
const space = text.lastIndexOf(" ", limit);
|
|
1155
|
+
return space >= Math.floor(limit * 0.6) ? space : limit;
|
|
1156
|
+
}
|
|
1157
|
+
function isRecord(value) {
|
|
1158
|
+
return !!value && typeof value === "object" && !Array.isArray(value);
|
|
1159
|
+
}
|
|
1160
|
+
|
|
1161
|
+
// src/utils/token-estimate.ts
|
|
1162
|
+
var RoughTokenEstimate = (text, charsPerToken = 3.5) => Math.max(1, Math.ceil(text.length / charsPerToken));
|
|
1163
|
+
var CALIBRATION_GLOBAL_KEY = "__global__";
|
|
1164
|
+
var _cals = /* @__PURE__ */ new Map();
|
|
1165
|
+
function calState(key) {
|
|
1166
|
+
let state = _cals.get(key);
|
|
1167
|
+
if (!state) {
|
|
1168
|
+
state = { ratio: 1, count: 0, prevEst: 0 };
|
|
1169
|
+
_cals.set(key, state);
|
|
1170
|
+
}
|
|
1171
|
+
return state;
|
|
1172
|
+
}
|
|
1173
|
+
var MIN_SAMPLES_FOR_CALIBRATION = 3;
|
|
1174
|
+
var ESTIMATE_CACHE = /* @__PURE__ */ new Map();
|
|
1175
|
+
var ESTIMATE_CACHE_MAX_SIZE = 1e4;
|
|
1176
|
+
function getCachedEstimate(key, compute) {
|
|
1177
|
+
const existing = ESTIMATE_CACHE.get(key);
|
|
1178
|
+
if (existing !== void 0) return existing;
|
|
1179
|
+
if (ESTIMATE_CACHE.size >= ESTIMATE_CACHE_MAX_SIZE) {
|
|
1180
|
+
for (const k of ESTIMATE_CACHE.keys()) {
|
|
1181
|
+
if (ESTIMATE_CACHE.size <= Math.floor(ESTIMATE_CACHE_MAX_SIZE / 2)) break;
|
|
1182
|
+
ESTIMATE_CACHE.delete(k);
|
|
1183
|
+
}
|
|
1184
|
+
}
|
|
1185
|
+
const estimate = compute(key);
|
|
1186
|
+
ESTIMATE_CACHE.set(key, estimate);
|
|
1187
|
+
return estimate;
|
|
1188
|
+
}
|
|
1189
|
+
function estimateToolInputTokens(input) {
|
|
1190
|
+
if (typeof input === "string") return RoughTokenEstimate(input);
|
|
1191
|
+
if (input === null || typeof input !== "object") {
|
|
1192
|
+
return RoughTokenEstimate(String(input));
|
|
1193
|
+
}
|
|
1194
|
+
return getCachedEstimate(JSON.stringify(input), (key) => RoughTokenEstimate(key));
|
|
1195
|
+
}
|
|
1196
|
+
function estimateToolResultTokens(content) {
|
|
1197
|
+
if (typeof content === "string") return RoughTokenEstimate(content);
|
|
1198
|
+
return getCachedEstimate(JSON.stringify(content), (key) => RoughTokenEstimate(key));
|
|
1199
|
+
}
|
|
1200
|
+
function estimateTextTokens(text) {
|
|
1201
|
+
return RoughTokenEstimate(text);
|
|
1202
|
+
}
|
|
1203
|
+
function computeMessageTokens(msg) {
|
|
1204
|
+
if (typeof msg.content === "string") return estimateTextTokens(msg.content);
|
|
1205
|
+
let total = 0;
|
|
1206
|
+
for (const b of msg.content) {
|
|
1207
|
+
if (b.type === "text") total += estimateTextTokens(b.text);
|
|
1208
|
+
else if (b.type === "tool_use") total += estimateToolInputTokens(b.input);
|
|
1209
|
+
else if (b.type === "tool_result") total += estimateToolResultTokens(b.content);
|
|
1210
|
+
else total += RoughTokenEstimate(JSON.stringify(b));
|
|
1211
|
+
}
|
|
1212
|
+
return total;
|
|
771
1213
|
}
|
|
772
1214
|
function estimateMessageTokens(messages) {
|
|
773
1215
|
let total = 0;
|
|
@@ -783,7 +1225,8 @@ function estimateMessageTokens(messages) {
|
|
|
783
1225
|
function estimateToolDefTokens(tool) {
|
|
784
1226
|
const cached = tool._estDefTokens;
|
|
785
1227
|
if (typeof cached === "number" && cached > 0) return cached;
|
|
786
|
-
|
|
1228
|
+
const compact = compactToolDefinitionForWire(tool);
|
|
1229
|
+
return RoughTokenEstimate(tool.name) + RoughTokenEstimate(compact.description) + RoughTokenEstimate(JSON.stringify(compact.inputSchema));
|
|
787
1230
|
}
|
|
788
1231
|
function estimateRequestTokens(messages, systemPrompt, tools, calibrationKey = CALIBRATION_GLOBAL_KEY) {
|
|
789
1232
|
let messagesTokens = 0;
|
|
@@ -860,406 +1303,702 @@ function estimateRequestTokensCalibrated(messages, systemPrompt, tools, calibrat
|
|
|
860
1303
|
return result;
|
|
861
1304
|
}
|
|
862
1305
|
|
|
863
|
-
// src/utils/
|
|
864
|
-
|
|
865
|
-
|
|
866
|
-
|
|
867
|
-
|
|
868
|
-
|
|
869
|
-
|
|
870
|
-
|
|
871
|
-
|
|
872
|
-
|
|
873
|
-
|
|
874
|
-
|
|
875
|
-
|
|
876
|
-
|
|
877
|
-
|
|
878
|
-
|
|
879
|
-
|
|
880
|
-
|
|
881
|
-
|
|
882
|
-
|
|
883
|
-
message: `expected ${schema.type}, got ${describeType(value)}`
|
|
884
|
-
});
|
|
885
|
-
return;
|
|
886
|
-
}
|
|
887
|
-
}
|
|
888
|
-
if (schema.type === "object" && isPlainObject(value)) {
|
|
889
|
-
const obj = value;
|
|
890
|
-
for (const req of schema.required ?? []) {
|
|
891
|
-
if (!(req in obj)) {
|
|
892
|
-
errors.push({ path: joinPath(path19, req), message: "required property missing" });
|
|
893
|
-
}
|
|
894
|
-
}
|
|
895
|
-
if (schema.properties) {
|
|
896
|
-
for (const [key, subSchema] of Object.entries(schema.properties)) {
|
|
897
|
-
if (key in obj) {
|
|
898
|
-
walk(obj[key], subSchema, joinPath(path19, key), errors);
|
|
899
|
-
}
|
|
900
|
-
}
|
|
901
|
-
}
|
|
902
|
-
}
|
|
903
|
-
if (schema.type === "array" && Array.isArray(value) && schema.items) {
|
|
904
|
-
for (let i = 0; i < value.length; i++) {
|
|
905
|
-
walk(value[i], schema.items, `${path19}[${i}]`, errors);
|
|
906
|
-
}
|
|
907
|
-
}
|
|
908
|
-
}
|
|
909
|
-
function checkType(value, type) {
|
|
910
|
-
switch (type) {
|
|
911
|
-
case "string":
|
|
912
|
-
return typeof value === "string";
|
|
913
|
-
case "number":
|
|
914
|
-
return typeof value === "number" && !Number.isNaN(value);
|
|
915
|
-
case "integer":
|
|
916
|
-
return typeof value === "number" && Number.isInteger(value);
|
|
917
|
-
case "boolean":
|
|
918
|
-
return typeof value === "boolean";
|
|
919
|
-
case "null":
|
|
920
|
-
return value === null;
|
|
921
|
-
case "array":
|
|
922
|
-
return Array.isArray(value);
|
|
923
|
-
case "object":
|
|
924
|
-
return isPlainObject(value);
|
|
925
|
-
default:
|
|
926
|
-
return true;
|
|
927
|
-
}
|
|
928
|
-
}
|
|
929
|
-
function isPlainObject(v) {
|
|
930
|
-
return typeof v === "object" && v !== null && !Array.isArray(v);
|
|
931
|
-
}
|
|
932
|
-
function describeType(v) {
|
|
933
|
-
if (v === null) return "null";
|
|
934
|
-
if (Array.isArray(v)) return "array";
|
|
935
|
-
return typeof v;
|
|
936
|
-
}
|
|
937
|
-
function joinPath(parent, key) {
|
|
938
|
-
if (!parent) return key;
|
|
939
|
-
return `${parent}.${key}`;
|
|
940
|
-
}
|
|
941
|
-
function deepEqual(a, b) {
|
|
942
|
-
if (a === b) return true;
|
|
943
|
-
if (typeof a !== typeof b) return false;
|
|
944
|
-
if (a === null || b === null) return a === b;
|
|
945
|
-
if (Array.isArray(a) && Array.isArray(b)) {
|
|
946
|
-
return a.length === b.length && a.every((v, i) => deepEqual(v, b[i]));
|
|
947
|
-
}
|
|
948
|
-
if (typeof a === "object" && typeof b === "object") {
|
|
949
|
-
const ak = Object.keys(a);
|
|
950
|
-
const bk = Object.keys(b);
|
|
951
|
-
if (ak.length !== bk.length) return false;
|
|
952
|
-
return ak.every(
|
|
953
|
-
(k) => deepEqual(a[k], b[k])
|
|
954
|
-
);
|
|
955
|
-
}
|
|
956
|
-
return false;
|
|
957
|
-
}
|
|
958
|
-
|
|
959
|
-
// src/utils/regex-guard.ts
|
|
960
|
-
var MAX_PATTERN_LEN = 512;
|
|
961
|
-
var DANGEROUS_PATTERNS = [
|
|
962
|
-
/(\([^)]*[+*][^)]*\))[+*]/,
|
|
963
|
-
// (a+)+, (.*)+, etc
|
|
964
|
-
/(\(\?:[^)]*[+*][^)]*\))[+*]/
|
|
965
|
-
// same, with non-capturing group
|
|
966
|
-
];
|
|
967
|
-
function compileUserRegex(pattern, flags) {
|
|
968
|
-
if (typeof pattern !== "string") {
|
|
969
|
-
return { ok: false, reason: "pattern must be a string" };
|
|
970
|
-
}
|
|
971
|
-
if (pattern.length === 0) {
|
|
972
|
-
return { ok: false, reason: "pattern is empty" };
|
|
973
|
-
}
|
|
974
|
-
if (pattern.length > MAX_PATTERN_LEN) {
|
|
975
|
-
return { ok: false, reason: `pattern exceeds ${MAX_PATTERN_LEN} characters` };
|
|
976
|
-
}
|
|
977
|
-
for (const rx of DANGEROUS_PATTERNS) {
|
|
978
|
-
if (rx.test(pattern)) {
|
|
979
|
-
return {
|
|
980
|
-
ok: false,
|
|
981
|
-
reason: "pattern looks vulnerable to catastrophic backtracking \u2014 rewrite without nested quantifiers"
|
|
982
|
-
};
|
|
983
|
-
}
|
|
984
|
-
}
|
|
985
|
-
try {
|
|
986
|
-
return { ok: true, regex: new RegExp(pattern, flags) };
|
|
987
|
-
} catch (err) {
|
|
988
|
-
return {
|
|
989
|
-
ok: false,
|
|
990
|
-
reason: err instanceof Error ? err.message : "invalid regex"
|
|
991
|
-
};
|
|
992
|
-
}
|
|
993
|
-
}
|
|
994
|
-
var GLOB_CHARS = /* @__PURE__ */ new Set(["*", "?", "["]);
|
|
995
|
-
var IS_WINDOWS = process.platform === "win32";
|
|
996
|
-
var SEP = IS_WINDOWS ? "\\" : "/";
|
|
997
|
-
function isGlob(p) {
|
|
998
|
-
for (const c of p) {
|
|
999
|
-
if (GLOB_CHARS.has(c)) return true;
|
|
1000
|
-
}
|
|
1001
|
-
return false;
|
|
1002
|
-
}
|
|
1003
|
-
function globToRegex(pat) {
|
|
1004
|
-
let i = 0;
|
|
1005
|
-
let re = "^";
|
|
1006
|
-
while (i < pat.length) {
|
|
1007
|
-
const c = expectDefined(pat[i]);
|
|
1008
|
-
if (c === "*") {
|
|
1009
|
-
if (pat[i + 1] === "*") {
|
|
1010
|
-
re += ".*";
|
|
1011
|
-
i += 2;
|
|
1012
|
-
if (pat[i] === "/") i++;
|
|
1013
|
-
} else {
|
|
1014
|
-
re += "[^/\\\\]*";
|
|
1015
|
-
i++;
|
|
1016
|
-
}
|
|
1017
|
-
} else if (c === "?") {
|
|
1018
|
-
re += "[^/\\\\]";
|
|
1019
|
-
i++;
|
|
1020
|
-
} else if (c === "[") {
|
|
1021
|
-
let cls = "[";
|
|
1022
|
-
i++;
|
|
1023
|
-
if (pat[i] === "!" || pat[i] === "^") {
|
|
1024
|
-
cls += "^";
|
|
1025
|
-
i++;
|
|
1026
|
-
}
|
|
1027
|
-
while (i < pat.length && pat[i] !== "]") {
|
|
1028
|
-
const ch = pat[i] ?? "";
|
|
1029
|
-
if (ch === "\\") cls += "\\\\";
|
|
1030
|
-
else if (ch === "]" || ch === "^") cls += `\\${ch}`;
|
|
1031
|
-
else cls += ch;
|
|
1032
|
-
i++;
|
|
1033
|
-
}
|
|
1034
|
-
cls += "]";
|
|
1035
|
-
re += cls;
|
|
1036
|
-
i++;
|
|
1037
|
-
} else {
|
|
1038
|
-
re += c.replace(/[.+^${}()|\\]/g, "\\$&");
|
|
1039
|
-
i++;
|
|
1040
|
-
}
|
|
1041
|
-
}
|
|
1042
|
-
return new RegExp(re + "$");
|
|
1043
|
-
}
|
|
1044
|
-
function baseDir(pat) {
|
|
1045
|
-
let i = pat.length - 1;
|
|
1046
|
-
while (i >= 0 && !GLOB_CHARS.has(expectDefined(pat[i])) && pat[i] !== SEP && pat[i] !== "/") i--;
|
|
1047
|
-
const cut = i >= 0 ? pat.lastIndexOf(SEP, i) : pat.lastIndexOf("/", i);
|
|
1048
|
-
return cut < 0 ? "." : pat.slice(0, cut);
|
|
1049
|
-
}
|
|
1050
|
-
async function expandGlob(pattern) {
|
|
1051
|
-
if (!isGlob(pattern)) return [pattern];
|
|
1052
|
-
const results = /* @__PURE__ */ new Set();
|
|
1053
|
-
const abs = isAbsolute(pattern);
|
|
1054
|
-
const base = abs ? baseDir(pattern) : baseDir(pattern);
|
|
1055
|
-
const relPat = base === "." ? pattern : pattern.slice(base.length + 1);
|
|
1056
|
-
async function walk3(dir, pat) {
|
|
1057
|
-
let entries;
|
|
1058
|
-
try {
|
|
1059
|
-
entries = await fsp2.readdir(dir);
|
|
1060
|
-
} catch {
|
|
1061
|
-
return;
|
|
1062
|
-
}
|
|
1063
|
-
const firstGlob = pat.search(/[*?[[]/);
|
|
1064
|
-
if (firstGlob < 0) {
|
|
1065
|
-
const re = globToRegex(pat);
|
|
1066
|
-
for (const e of entries) {
|
|
1067
|
-
if (re.test(e)) {
|
|
1068
|
-
const full = `${dir}${SEP}${e}`;
|
|
1069
|
-
results.add(abs ? resolve(full) : full);
|
|
1070
|
-
}
|
|
1071
|
-
}
|
|
1072
|
-
return;
|
|
1073
|
-
}
|
|
1074
|
-
const before = pat.slice(0, firstGlob);
|
|
1075
|
-
const rest = pat.slice(firstGlob);
|
|
1076
|
-
if (before.endsWith("**")) {
|
|
1077
|
-
await walk3(dir, rest);
|
|
1078
|
-
for (const e of entries) {
|
|
1079
|
-
const full = `${dir}${SEP}${e}`;
|
|
1080
|
-
try {
|
|
1081
|
-
const stat6 = await fsp2.stat(full);
|
|
1082
|
-
if (stat6.isDirectory()) await walk3(full, rest);
|
|
1083
|
-
} catch {
|
|
1084
|
-
}
|
|
1306
|
+
// src/utils/tool-output-serializer.ts
|
|
1307
|
+
var DEFAULT_LIST_LIMIT = 500;
|
|
1308
|
+
var LOG_ENTRY_LIMIT = 200;
|
|
1309
|
+
var INLINE_LIMIT = 240;
|
|
1310
|
+
var GREP_FILE_LIMIT = 80;
|
|
1311
|
+
var GREP_MATCHES_PER_FILE = 3;
|
|
1312
|
+
var DIFF_INLINE_LINE_LIMIT = 260;
|
|
1313
|
+
var DIFF_HUNK_LIMIT = 8;
|
|
1314
|
+
var DIFF_HUNK_CONTEXT = 14;
|
|
1315
|
+
function createToolOutputSerializer(opts = {}) {
|
|
1316
|
+
const capBytes = opts.perIterationOutputCapBytes ?? 1e5;
|
|
1317
|
+
function serialize(value, context = {}) {
|
|
1318
|
+
if (typeof value === "string") return value;
|
|
1319
|
+
if (value === null || value === void 0) return "";
|
|
1320
|
+
if (typeof value === "object") {
|
|
1321
|
+
if (Array.isArray(value)) return value.map((item) => serialize(item)).join("\n");
|
|
1322
|
+
if (context.toolName) {
|
|
1323
|
+
const compact = renderToolObject(context.toolName, value, context.input);
|
|
1324
|
+
if (compact !== void 0) return compact;
|
|
1325
|
+
return renderGenericToolObject(context.toolName, value);
|
|
1085
1326
|
}
|
|
1086
|
-
|
|
1087
|
-
|
|
1088
|
-
|
|
1089
|
-
if (re.test(e)) {
|
|
1090
|
-
const full = `${dir}${SEP}${e}`;
|
|
1091
|
-
results.add(abs ? resolve(full) : full);
|
|
1092
|
-
}
|
|
1327
|
+
if ("text" in value) {
|
|
1328
|
+
const t = value.text;
|
|
1329
|
+
return typeof t === "string" ? t : JSON.stringify(value, null, 2);
|
|
1093
1330
|
}
|
|
1094
|
-
|
|
1095
|
-
|
|
1096
|
-
|
|
1097
|
-
|
|
1098
|
-
try {
|
|
1099
|
-
const stat6 = await fsp2.stat(full);
|
|
1100
|
-
if (stat6.isDirectory()) await walk3(full, rest);
|
|
1101
|
-
} catch {
|
|
1102
|
-
}
|
|
1331
|
+
try {
|
|
1332
|
+
return JSON.stringify(value, null, 2);
|
|
1333
|
+
} catch {
|
|
1334
|
+
return String(value);
|
|
1103
1335
|
}
|
|
1104
1336
|
}
|
|
1337
|
+
return String(value);
|
|
1105
1338
|
}
|
|
1106
|
-
|
|
1107
|
-
|
|
1108
|
-
}
|
|
1109
|
-
|
|
1110
|
-
// src/utils/json-repair.ts
|
|
1111
|
-
function completePartialObject(s) {
|
|
1112
|
-
if (!s.trim().startsWith("{")) return s;
|
|
1113
|
-
if (tryParse(s).ok) return s;
|
|
1114
|
-
return repairTruncated(s);
|
|
1115
|
-
}
|
|
1116
|
-
function repairTruncated(s) {
|
|
1117
|
-
const stack = [];
|
|
1118
|
-
let inString = false;
|
|
1119
|
-
let escaped = false;
|
|
1120
|
-
let sawKey = false;
|
|
1121
|
-
let prevSig = "";
|
|
1122
|
-
let contentEnd = 0;
|
|
1123
|
-
let stringBraceDepth = 0;
|
|
1124
|
-
for (let i = 0; i < s.length; i++) {
|
|
1125
|
-
const ch = expectDefined(s[i]);
|
|
1126
|
-
if (inString) {
|
|
1127
|
-
contentEnd = i + 1;
|
|
1128
|
-
if (escaped) {
|
|
1129
|
-
escaped = false;
|
|
1130
|
-
continue;
|
|
1131
|
-
}
|
|
1132
|
-
if (ch === "\\") {
|
|
1133
|
-
escaped = true;
|
|
1134
|
-
continue;
|
|
1135
|
-
}
|
|
1136
|
-
if (ch === '"') {
|
|
1137
|
-
inString = false;
|
|
1138
|
-
prevSig = '"';
|
|
1139
|
-
stringBraceDepth = 0;
|
|
1140
|
-
continue;
|
|
1141
|
-
}
|
|
1142
|
-
if (ch === "{") stringBraceDepth++;
|
|
1143
|
-
else if (ch === "}" && stringBraceDepth > 0) stringBraceDepth--;
|
|
1144
|
-
continue;
|
|
1339
|
+
function enforceCap(text, remainingBudget) {
|
|
1340
|
+
if (remainingBudget <= 0) {
|
|
1341
|
+
return { text: "[truncated: iteration output cap exceeded]", newBudget: 0 };
|
|
1145
1342
|
}
|
|
1146
|
-
|
|
1147
|
-
|
|
1148
|
-
|
|
1149
|
-
|
|
1150
|
-
|
|
1151
|
-
|
|
1152
|
-
|
|
1153
|
-
|
|
1154
|
-
|
|
1155
|
-
|
|
1156
|
-
|
|
1157
|
-
stack.pop();
|
|
1158
|
-
prevSig = ch;
|
|
1159
|
-
} else {
|
|
1160
|
-
prevSig = ch;
|
|
1343
|
+
const textBytes = Buffer.byteLength(text, "utf8");
|
|
1344
|
+
if (textBytes <= remainingBudget) {
|
|
1345
|
+
return { text, newBudget: remainingBudget - textBytes };
|
|
1346
|
+
}
|
|
1347
|
+
const marker = `
|
|
1348
|
+
\u2026[truncated ${textBytes - remainingBudget} bytes]\u2026
|
|
1349
|
+
`;
|
|
1350
|
+
const markerBytes = Buffer.byteLength(marker, "utf8");
|
|
1351
|
+
const available = remainingBudget - markerBytes;
|
|
1352
|
+
if (available <= 0) {
|
|
1353
|
+
return { text: "[truncated: iteration output cap exceeded]", newBudget: 0 };
|
|
1161
1354
|
}
|
|
1355
|
+
const half = Math.floor(available / 2);
|
|
1356
|
+
const first = text.slice(0, half);
|
|
1357
|
+
const second = text.slice(text.length - half);
|
|
1358
|
+
return { text: `${first}${marker}${second}`, newBudget: 0 };
|
|
1162
1359
|
}
|
|
1163
|
-
|
|
1164
|
-
|
|
1165
|
-
|
|
1166
|
-
|
|
1167
|
-
|
|
1168
|
-
|
|
1169
|
-
|
|
1360
|
+
return { serialize, enforceCap, capBytes };
|
|
1361
|
+
}
|
|
1362
|
+
function renderToolObject(toolName, obj, input) {
|
|
1363
|
+
if (toolName === "read" && typeof obj["text"] === "string") {
|
|
1364
|
+
return joinSections([
|
|
1365
|
+
renderHeader(
|
|
1366
|
+
`read: ${stringFromInput(input, "path") ?? stringField(obj, "path") ?? "<unknown>"}`,
|
|
1367
|
+
{
|
|
1368
|
+
offset: numberFromInput(input, "offset"),
|
|
1369
|
+
limit: numberFromInput(input, "limit"),
|
|
1370
|
+
total_lines: obj["total_lines"],
|
|
1371
|
+
encoding: obj["encoding"],
|
|
1372
|
+
truncated: obj["truncated"],
|
|
1373
|
+
cached: obj["cached"],
|
|
1374
|
+
note: obj["note"]
|
|
1375
|
+
}
|
|
1376
|
+
),
|
|
1377
|
+
obj["text"]
|
|
1378
|
+
]);
|
|
1379
|
+
}
|
|
1380
|
+
if (toolName === "grep" && Array.isArray(obj["matches"])) {
|
|
1381
|
+
const matches = stringArrayField(obj, "matches");
|
|
1382
|
+
return joinSections([
|
|
1383
|
+
renderHeader(`grep: ${stringFromInput(input, "pattern") ?? "<pattern>"}`, {
|
|
1384
|
+
path: stringFromInput(input, "path"),
|
|
1385
|
+
glob: stringFromInput(input, "glob"),
|
|
1386
|
+
mode: stringFromInput(input, "output_mode"),
|
|
1387
|
+
count: obj["count"],
|
|
1388
|
+
shown: matches.length,
|
|
1389
|
+
truncated: obj["truncated"],
|
|
1390
|
+
used: obj["used"]
|
|
1391
|
+
}),
|
|
1392
|
+
renderGrepMatches(matches, stringFromInput(input, "output_mode"))
|
|
1393
|
+
]);
|
|
1394
|
+
}
|
|
1395
|
+
if (toolName === "patch" && Array.isArray(obj["files"])) {
|
|
1396
|
+
const files = stringArrayField(obj, "files");
|
|
1397
|
+
return joinSections([
|
|
1398
|
+
renderHeader("patch", {
|
|
1399
|
+
applied: obj["applied"],
|
|
1400
|
+
rejected: obj["rejected"],
|
|
1401
|
+
files: files.length,
|
|
1402
|
+
dry_run: obj["dry_run"]
|
|
1403
|
+
}),
|
|
1404
|
+
typeof obj["message"] === "string" ? `message:
|
|
1405
|
+
${obj["message"]}` : void 0,
|
|
1406
|
+
files.length > 0 ? `files:
|
|
1407
|
+
${renderStringList(files)}` : void 0
|
|
1408
|
+
]);
|
|
1409
|
+
}
|
|
1410
|
+
if (toolName === "glob" && Array.isArray(obj["files"])) {
|
|
1411
|
+
const files = stringArrayField(obj, "files");
|
|
1412
|
+
return joinSections([
|
|
1413
|
+
renderHeader(
|
|
1414
|
+
`${toolName}: ${stringFromInput(input, "pattern") ?? stringFromInput(input, "files") ?? stringFromInput(input, "path") ?? ""}`.trim(),
|
|
1415
|
+
{
|
|
1416
|
+
path: stringFromInput(input, "path"),
|
|
1417
|
+
files: files.length,
|
|
1418
|
+
truncated: obj["truncated"]
|
|
1419
|
+
}
|
|
1420
|
+
),
|
|
1421
|
+
renderStringList(files, "(no files)")
|
|
1422
|
+
]);
|
|
1423
|
+
}
|
|
1424
|
+
if (toolName === "tree" && typeof obj["tree"] === "string") {
|
|
1425
|
+
return joinSections([
|
|
1426
|
+
renderHeader(
|
|
1427
|
+
`tree: ${stringField(obj, "path") ?? stringFromInput(input, "path") ?? "<cwd>"}`,
|
|
1428
|
+
{
|
|
1429
|
+
total_files: obj["total_files"],
|
|
1430
|
+
total_dirs: obj["total_dirs"],
|
|
1431
|
+
truncated: obj["truncated"]
|
|
1432
|
+
}
|
|
1433
|
+
),
|
|
1434
|
+
obj["tree"]
|
|
1435
|
+
]);
|
|
1436
|
+
}
|
|
1437
|
+
if (toolName === "fetch" && typeof obj["content"] === "string") {
|
|
1438
|
+
return joinSections([
|
|
1439
|
+
renderHeader(
|
|
1440
|
+
`fetch: ${stringField(obj, "url") ?? stringFromInput(input, "url") ?? "<url>"}`,
|
|
1441
|
+
{
|
|
1442
|
+
status: obj["status"],
|
|
1443
|
+
content_type: obj["content_type"]
|
|
1444
|
+
}
|
|
1445
|
+
),
|
|
1446
|
+
obj["content"]
|
|
1447
|
+
]);
|
|
1448
|
+
}
|
|
1449
|
+
if (toolName === "replace" && Array.isArray(obj["results"])) {
|
|
1450
|
+
const results = obj["results"].filter(isRecord2);
|
|
1451
|
+
const sections = [
|
|
1452
|
+
renderHeader("replace", {
|
|
1453
|
+
files_modified: obj["files_modified"],
|
|
1454
|
+
total_replacements: obj["total_replacements"],
|
|
1455
|
+
dry_run: obj["dry_run"]
|
|
1456
|
+
})
|
|
1457
|
+
];
|
|
1458
|
+
for (const r of results.slice(0, DEFAULT_LIST_LIMIT)) {
|
|
1459
|
+
sections.push(
|
|
1460
|
+
joinSections([
|
|
1461
|
+
renderHeader(`file: ${stringField(r, "path") ?? "<unknown>"}`, {
|
|
1462
|
+
replacements: r["replacements"]
|
|
1463
|
+
}),
|
|
1464
|
+
typeof r["diff"] === "string" ? r["diff"] : void 0
|
|
1465
|
+
])
|
|
1466
|
+
);
|
|
1170
1467
|
}
|
|
1171
|
-
if (
|
|
1172
|
-
|
|
1173
|
-
|
|
1174
|
-
|
|
1468
|
+
if (results.length > DEFAULT_LIST_LIMIT) {
|
|
1469
|
+
sections.push(`[serializer omitted ${results.length - DEFAULT_LIST_LIMIT} result item(s)]`);
|
|
1470
|
+
}
|
|
1471
|
+
return joinSections(sections);
|
|
1472
|
+
}
|
|
1473
|
+
if (typeof obj["diff"] === "string") {
|
|
1474
|
+
const diff = obj["diff"];
|
|
1475
|
+
return joinSections([
|
|
1476
|
+
renderHeader(toolName, {
|
|
1477
|
+
path: obj["path"],
|
|
1478
|
+
replacements: obj["replacements"],
|
|
1479
|
+
bytes_written: obj["bytes_written"],
|
|
1480
|
+
created: obj["created"],
|
|
1481
|
+
note: obj["note"],
|
|
1482
|
+
files: Array.isArray(obj["files"]) ? obj["files"].length : void 0,
|
|
1483
|
+
truncated: obj["truncated"],
|
|
1484
|
+
mode: obj["mode"]
|
|
1485
|
+
}),
|
|
1486
|
+
compactDiff(diff)
|
|
1487
|
+
]);
|
|
1175
1488
|
}
|
|
1176
|
-
|
|
1177
|
-
|
|
1489
|
+
if (toolName === "test" && typeof obj["output"] === "string") {
|
|
1490
|
+
return renderTestOutput(obj, input);
|
|
1178
1491
|
}
|
|
1179
|
-
if (
|
|
1180
|
-
|
|
1181
|
-
if (tryParse(patched).ok) result = patched;
|
|
1492
|
+
if ((toolName === "typecheck" || toolName === "lint" || toolName === "format") && typeof obj["output"] === "string") {
|
|
1493
|
+
return renderVerifierOutput(toolName, obj, input);
|
|
1182
1494
|
}
|
|
1183
|
-
|
|
1184
|
-
|
|
1185
|
-
var VALID_ESCAPE = /* @__PURE__ */ new Set(['"', "\\", "/", "b", "f", "n", "r", "t", "u"]);
|
|
1186
|
-
function endsWithInvalidEscape(str) {
|
|
1187
|
-
const last = str[str.length - 1];
|
|
1188
|
-
if (str[str.length - 2] !== "\\" || last === void 0) return false;
|
|
1189
|
-
if (VALID_ESCAPE.has(last)) return false;
|
|
1190
|
-
let backslashes = 0;
|
|
1191
|
-
for (let k = str.length - 2; k >= 0 && str[k] === "\\"; k--) backslashes++;
|
|
1192
|
-
return backslashes % 2 === 1;
|
|
1193
|
-
}
|
|
1194
|
-
function tryParse(s) {
|
|
1195
|
-
try {
|
|
1196
|
-
return { ok: true, value: JSON.parse(s) };
|
|
1197
|
-
} catch {
|
|
1198
|
-
return { ok: false };
|
|
1495
|
+
if (hasCommandOutputShape(obj)) {
|
|
1496
|
+
return renderCommandOutput(toolName, obj, input);
|
|
1199
1497
|
}
|
|
1200
|
-
|
|
1201
|
-
|
|
1202
|
-
|
|
1203
|
-
|
|
1204
|
-
|
|
1205
|
-
|
|
1206
|
-
|
|
1498
|
+
if (toolName === "json" && typeof obj["formatted"] === "string") {
|
|
1499
|
+
return joinSections([
|
|
1500
|
+
renderHeader("json", {
|
|
1501
|
+
type: obj["type"],
|
|
1502
|
+
keys: Array.isArray(obj["keys"]) ? obj["keys"].length : void 0,
|
|
1503
|
+
query: stringFromInput(input, "query"),
|
|
1504
|
+
error: obj["error"]
|
|
1505
|
+
}),
|
|
1506
|
+
obj["formatted"]
|
|
1507
|
+
]);
|
|
1207
1508
|
}
|
|
1208
|
-
|
|
1209
|
-
const
|
|
1210
|
-
|
|
1509
|
+
if (toolName === "logs" && Array.isArray(obj["entries"])) {
|
|
1510
|
+
const entries = obj["entries"].filter(isRecord2);
|
|
1511
|
+
const lines = entries.slice(0, LOG_ENTRY_LIMIT).map((entry) => {
|
|
1512
|
+
const ts = stringField(entry, "timestamp") ?? "";
|
|
1513
|
+
const level = stringField(entry, "level") ?? "info";
|
|
1514
|
+
const message = stringField(entry, "message") ?? "";
|
|
1515
|
+
const source = stringField(entry, "source");
|
|
1516
|
+
return [ts, level, source, message].filter(Boolean).join(" ");
|
|
1517
|
+
});
|
|
1518
|
+
if (entries.length > LOG_ENTRY_LIMIT) {
|
|
1519
|
+
lines.push(`[serializer omitted ${entries.length - LOG_ENTRY_LIMIT} log entry item(s)]`);
|
|
1520
|
+
}
|
|
1521
|
+
return joinSections([
|
|
1522
|
+
renderHeader(`logs: ${stringField(obj, "source") ?? "<source>"}`, {
|
|
1523
|
+
total: obj["total"],
|
|
1524
|
+
shown: Math.min(entries.length, LOG_ENTRY_LIMIT),
|
|
1525
|
+
truncated: obj["truncated"],
|
|
1526
|
+
stream_mode: obj["stream_mode"]
|
|
1527
|
+
}),
|
|
1528
|
+
lines.length > 0 ? lines.join("\n") : "(no log entries)"
|
|
1529
|
+
]);
|
|
1211
1530
|
}
|
|
1212
|
-
|
|
1213
|
-
|
|
1214
|
-
|
|
1215
|
-
|
|
1216
|
-
|
|
1217
|
-
|
|
1531
|
+
if (toolName === "audit" && Array.isArray(obj["vulnerabilities"])) {
|
|
1532
|
+
const vulns = obj["vulnerabilities"].filter(isRecord2);
|
|
1533
|
+
const lines = vulns.slice(0, DEFAULT_LIST_LIMIT).map((v) => {
|
|
1534
|
+
const severity = stringField(v, "severity") ?? "unknown";
|
|
1535
|
+
const pkg = stringField(v, "package") ?? "<package>";
|
|
1536
|
+
const title = stringField(v, "title") ?? "";
|
|
1537
|
+
const url = stringField(v, "url");
|
|
1538
|
+
return [severity, pkg, title, url].filter(Boolean).join(" | ");
|
|
1539
|
+
});
|
|
1540
|
+
if (vulns.length > DEFAULT_LIST_LIMIT) {
|
|
1541
|
+
lines.push(`[serializer omitted ${vulns.length - DEFAULT_LIST_LIMIT} vulnerability item(s)]`);
|
|
1542
|
+
}
|
|
1543
|
+
return joinSections([
|
|
1544
|
+
renderHeader("audit", {
|
|
1545
|
+
exit_code: obj["exit_code"],
|
|
1546
|
+
total: obj["total"],
|
|
1547
|
+
summary: obj["summary"],
|
|
1548
|
+
truncated: obj["truncated"]
|
|
1549
|
+
}),
|
|
1550
|
+
lines.length > 0 ? lines.join("\n") : stringField(obj, "output")
|
|
1551
|
+
]);
|
|
1218
1552
|
}
|
|
1219
|
-
|
|
1220
|
-
const
|
|
1221
|
-
|
|
1553
|
+
if (toolName === "outdated" && Array.isArray(obj["packages"])) {
|
|
1554
|
+
const packages = obj["packages"].filter(isRecord2);
|
|
1555
|
+
const lines = packages.slice(0, DEFAULT_LIST_LIMIT).map(
|
|
1556
|
+
(p) => [
|
|
1557
|
+
stringField(p, "name") ?? "<package>",
|
|
1558
|
+
`current=${stringField(p, "current") ?? "unknown"}`,
|
|
1559
|
+
`wanted=${stringField(p, "wanted") ?? "unknown"}`,
|
|
1560
|
+
`latest=${stringField(p, "latest") ?? "unknown"}`,
|
|
1561
|
+
stringField(p, "type")
|
|
1562
|
+
].filter(Boolean).join(" | ")
|
|
1563
|
+
);
|
|
1564
|
+
if (packages.length > DEFAULT_LIST_LIMIT) {
|
|
1565
|
+
lines.push(`[serializer omitted ${packages.length - DEFAULT_LIST_LIMIT} package item(s)]`);
|
|
1566
|
+
}
|
|
1567
|
+
return joinSections([
|
|
1568
|
+
renderHeader("outdated", {
|
|
1569
|
+
exit_code: obj["exit_code"],
|
|
1570
|
+
total: obj["total"],
|
|
1571
|
+
truncated: obj["truncated"]
|
|
1572
|
+
}),
|
|
1573
|
+
lines.length > 0 ? lines.join("\n") : stringField(obj, "output")
|
|
1574
|
+
]);
|
|
1222
1575
|
}
|
|
1223
|
-
return
|
|
1224
|
-
...base,
|
|
1225
|
-
// Overlay scalar fields win when explicitly provided; otherwise keep base.
|
|
1226
|
-
...stripUndefined({
|
|
1227
|
-
id: overlay.id,
|
|
1228
|
-
name: overlay.name,
|
|
1229
|
-
npm: overlay.npm,
|
|
1230
|
-
api: overlay.api,
|
|
1231
|
-
env: overlay.env,
|
|
1232
|
-
doc: overlay.doc
|
|
1233
|
-
}),
|
|
1234
|
-
models
|
|
1235
|
-
};
|
|
1576
|
+
return void 0;
|
|
1236
1577
|
}
|
|
1237
|
-
function
|
|
1238
|
-
const
|
|
1239
|
-
|
|
1240
|
-
|
|
1578
|
+
function renderTestOutput(obj, input) {
|
|
1579
|
+
const exitCode = numberField(obj, "exit_code") ?? 0;
|
|
1580
|
+
const failed = numberField(obj, "failed") ?? 0;
|
|
1581
|
+
const output = stringField(obj, "output") ?? "";
|
|
1582
|
+
const header = renderHeader(`test: ${stringField(obj, "runner") ?? "runner"}`, {
|
|
1583
|
+
exit_code: obj["exit_code"],
|
|
1584
|
+
tests_run: obj["tests_run"],
|
|
1585
|
+
passed: obj["passed"],
|
|
1586
|
+
failed: obj["failed"],
|
|
1587
|
+
duration_ms: obj["duration_ms"],
|
|
1588
|
+
truncated: obj["truncated"],
|
|
1589
|
+
files: inputListSummary(input, "files"),
|
|
1590
|
+
grep: stringFromInput(input, "grep")
|
|
1591
|
+
});
|
|
1592
|
+
if (exitCode === 0 && failed === 0) {
|
|
1593
|
+
return joinSections([
|
|
1594
|
+
header,
|
|
1595
|
+
joinSections([
|
|
1596
|
+
"report:",
|
|
1597
|
+
`status=passed`,
|
|
1598
|
+
`tests_run=${obj["tests_run"] ?? 0}`,
|
|
1599
|
+
`passed=${obj["passed"] ?? 0}`,
|
|
1600
|
+
`failed=${obj["failed"] ?? 0}`,
|
|
1601
|
+
`duration_ms=${obj["duration_ms"] ?? 0}`,
|
|
1602
|
+
extractSpoolNote(output)
|
|
1603
|
+
])
|
|
1604
|
+
]);
|
|
1241
1605
|
}
|
|
1242
|
-
|
|
1243
|
-
|
|
1606
|
+
return joinSections([
|
|
1607
|
+
header,
|
|
1608
|
+
`error_context:
|
|
1609
|
+
${compactFailureOutput(output || "(no runner output)")}`
|
|
1610
|
+
]);
|
|
1611
|
+
}
|
|
1612
|
+
function renderVerifierOutput(toolName, obj, input) {
|
|
1613
|
+
const exitCode = numberField(obj, "exit_code") ?? 0;
|
|
1614
|
+
const errors = numberField(obj, "errors") ?? 0;
|
|
1615
|
+
const warnings = numberField(obj, "warnings") ?? 0;
|
|
1616
|
+
const output = stringField(obj, "output") ?? "";
|
|
1617
|
+
const changed = numberField(obj, "files_changed") ?? 0;
|
|
1618
|
+
const header = renderHeader(toolName, {
|
|
1619
|
+
exit_code: obj["exit_code"],
|
|
1620
|
+
errors: obj["errors"],
|
|
1621
|
+
warnings: obj["warnings"],
|
|
1622
|
+
files_checked: obj["files_checked"],
|
|
1623
|
+
files_changed: obj["files_changed"],
|
|
1624
|
+
fix_applied: obj["fix_applied"],
|
|
1625
|
+
fixer: obj["fixer"],
|
|
1626
|
+
linter: obj["linter"],
|
|
1627
|
+
project: obj["project"],
|
|
1628
|
+
truncated: obj["truncated"],
|
|
1629
|
+
files: inputListSummary(input, "files"),
|
|
1630
|
+
cwd: stringFromInput(input, "cwd")
|
|
1631
|
+
});
|
|
1632
|
+
if (exitCode === 0 && errors === 0 && (toolName !== "format" || changed === 0)) {
|
|
1633
|
+
return joinSections([
|
|
1634
|
+
header,
|
|
1635
|
+
joinSections([
|
|
1636
|
+
"report:",
|
|
1637
|
+
"status=passed",
|
|
1638
|
+
`errors=${errors}`,
|
|
1639
|
+
`warnings=${warnings}`,
|
|
1640
|
+
toolName === "format" ? `files_changed=${changed}` : void 0,
|
|
1641
|
+
extractSpoolNote(output)
|
|
1642
|
+
])
|
|
1643
|
+
]);
|
|
1244
1644
|
}
|
|
1245
|
-
if (
|
|
1246
|
-
|
|
1645
|
+
if (exitCode === 0 && toolName === "format") {
|
|
1646
|
+
return joinSections([
|
|
1647
|
+
header,
|
|
1648
|
+
joinSections([
|
|
1649
|
+
"report:",
|
|
1650
|
+
"status=changed",
|
|
1651
|
+
`files_changed=${changed}`,
|
|
1652
|
+
extractSpoolNote(output)
|
|
1653
|
+
])
|
|
1654
|
+
]);
|
|
1247
1655
|
}
|
|
1248
|
-
return
|
|
1656
|
+
return joinSections([
|
|
1657
|
+
header,
|
|
1658
|
+
`error_context:
|
|
1659
|
+
${compactFailureOutput(output || "(no verifier output)")}`
|
|
1660
|
+
]);
|
|
1249
1661
|
}
|
|
1250
|
-
function
|
|
1251
|
-
|
|
1252
|
-
|
|
1253
|
-
|
|
1662
|
+
function renderGrepMatches(matches, mode) {
|
|
1663
|
+
if (matches.length === 0) return "(no matches)";
|
|
1664
|
+
if (mode === "files_with_matches") return renderStringList(matches, "(no files)");
|
|
1665
|
+
if (mode === "count") return renderStringList(matches, "(no counts)");
|
|
1666
|
+
const groups = /* @__PURE__ */ new Map();
|
|
1667
|
+
const passthrough = [];
|
|
1668
|
+
for (const match of matches) {
|
|
1669
|
+
const parsed = parseGrepContentLine(match);
|
|
1670
|
+
if (!parsed) {
|
|
1671
|
+
passthrough.push(match);
|
|
1672
|
+
continue;
|
|
1673
|
+
}
|
|
1674
|
+
const list = groups.get(parsed.file) ?? [];
|
|
1675
|
+
list.push(`${parsed.line}:${parsed.text}`);
|
|
1676
|
+
groups.set(parsed.file, list);
|
|
1254
1677
|
}
|
|
1255
|
-
return
|
|
1678
|
+
if (groups.size === 0) return renderStringList(matches, "(no matches)");
|
|
1679
|
+
const sections = [];
|
|
1680
|
+
let fileIndex = 0;
|
|
1681
|
+
for (const [file, lines] of groups) {
|
|
1682
|
+
fileIndex++;
|
|
1683
|
+
if (fileIndex > GREP_FILE_LIMIT) break;
|
|
1684
|
+
const shown = lines.slice(0, GREP_MATCHES_PER_FILE);
|
|
1685
|
+
sections.push(
|
|
1686
|
+
`${file} (${lines.length} match(es), showing ${shown.length})
|
|
1687
|
+
${shown.join("\n")}`
|
|
1688
|
+
);
|
|
1689
|
+
}
|
|
1690
|
+
if (groups.size > GREP_FILE_LIMIT) {
|
|
1691
|
+
sections.push(`[serializer omitted ${groups.size - GREP_FILE_LIMIT} file group(s)]`);
|
|
1692
|
+
}
|
|
1693
|
+
if (passthrough.length > 0) {
|
|
1694
|
+
sections.push(`ungrouped:
|
|
1695
|
+
${renderStringList(passthrough, "", 50)}`);
|
|
1696
|
+
}
|
|
1697
|
+
return sections.join("\n");
|
|
1698
|
+
}
|
|
1699
|
+
function parseGrepContentLine(line) {
|
|
1700
|
+
const match = /^(.+?):(\d+):(.*)$/.exec(line);
|
|
1701
|
+
if (!match?.[1] || !match[2]) return void 0;
|
|
1702
|
+
return { file: match[1], line: match[2], text: match[3] ?? "" };
|
|
1703
|
+
}
|
|
1704
|
+
function compactDiff(diff) {
|
|
1705
|
+
const lines = diff.split(/\r?\n/);
|
|
1706
|
+
if (lines.length <= DIFF_INLINE_LINE_LIMIT) return diff;
|
|
1707
|
+
const fileCount = Math.max(
|
|
1708
|
+
new Set(
|
|
1709
|
+
lines.map(
|
|
1710
|
+
(line) => /^diff --git\s+a\/(.+?)\s+b\//.exec(line)?.[1] ?? /^---\s+(.+)/.exec(line)?.[1]
|
|
1711
|
+
).filter(Boolean)
|
|
1712
|
+
).size,
|
|
1713
|
+
0
|
|
1714
|
+
);
|
|
1715
|
+
const hunks = lines.filter((line) => line.startsWith("@@")).length;
|
|
1716
|
+
const added = lines.filter((line) => line.startsWith("+") && !line.startsWith("+++")).length;
|
|
1717
|
+
const removed = lines.filter((line) => line.startsWith("-") && !line.startsWith("---")).length;
|
|
1718
|
+
const selected = /* @__PURE__ */ new Set();
|
|
1719
|
+
let hunkCount = 0;
|
|
1720
|
+
for (let i = 0; i < lines.length; i++) {
|
|
1721
|
+
const line = lines[i] ?? "";
|
|
1722
|
+
if (line.startsWith("diff --git") || line.startsWith("--- ") || line.startsWith("+++ ")) {
|
|
1723
|
+
selected.add(i);
|
|
1724
|
+
continue;
|
|
1725
|
+
}
|
|
1726
|
+
if (!line.startsWith("@@")) continue;
|
|
1727
|
+
if (hunkCount >= DIFF_HUNK_LIMIT) continue;
|
|
1728
|
+
hunkCount++;
|
|
1729
|
+
for (let j = i; j <= Math.min(lines.length - 1, i + DIFF_HUNK_CONTEXT); j++) {
|
|
1730
|
+
selected.add(j);
|
|
1731
|
+
}
|
|
1732
|
+
}
|
|
1733
|
+
if (selected.size === 0) {
|
|
1734
|
+
return joinSections([
|
|
1735
|
+
renderHeader("diff_summary", {
|
|
1736
|
+
files: fileCount,
|
|
1737
|
+
hunks,
|
|
1738
|
+
added,
|
|
1739
|
+
removed,
|
|
1740
|
+
lines: lines.length
|
|
1741
|
+
}),
|
|
1742
|
+
lines.slice(0, DIFF_INLINE_LINE_LIMIT).join("\n"),
|
|
1743
|
+
`[serializer omitted ${Math.max(0, lines.length - DIFF_INLINE_LINE_LIMIT)} diff line(s)]`
|
|
1744
|
+
]);
|
|
1745
|
+
}
|
|
1746
|
+
const excerpt = [];
|
|
1747
|
+
let previous = -1;
|
|
1748
|
+
for (const index of [...selected].sort((a, b) => a - b)) {
|
|
1749
|
+
if (index > previous + 1) {
|
|
1750
|
+
const omitted = previous === -1 ? index : index - previous - 1;
|
|
1751
|
+
excerpt.push(`[serializer omitted ${omitted} diff line(s)]`);
|
|
1752
|
+
}
|
|
1753
|
+
excerpt.push(lines[index] ?? "");
|
|
1754
|
+
previous = index;
|
|
1755
|
+
}
|
|
1756
|
+
const trailing = lines.length - previous - 1;
|
|
1757
|
+
if (trailing > 0) excerpt.push(`[serializer omitted ${trailing} trailing diff line(s)]`);
|
|
1758
|
+
return joinSections([
|
|
1759
|
+
renderHeader("diff_summary", {
|
|
1760
|
+
files: fileCount,
|
|
1761
|
+
hunks,
|
|
1762
|
+
shown_hunks: Math.min(hunks, DIFF_HUNK_LIMIT),
|
|
1763
|
+
added,
|
|
1764
|
+
removed,
|
|
1765
|
+
lines: lines.length
|
|
1766
|
+
}),
|
|
1767
|
+
excerpt.join("\n")
|
|
1768
|
+
]);
|
|
1769
|
+
}
|
|
1770
|
+
function compactFailureOutput(output) {
|
|
1771
|
+
const lines = output.split(/\r?\n/);
|
|
1772
|
+
if (lines.length <= 260) return output.trimEnd();
|
|
1773
|
+
const selected = /* @__PURE__ */ new Set();
|
|
1774
|
+
const marker = /\b(fail|failed|failure|error|exception|assertionerror|expected|received|actual|timeout|stack)\b/i;
|
|
1775
|
+
let markerHits = 0;
|
|
1776
|
+
for (let i = 0; i < lines.length; i++) {
|
|
1777
|
+
if (!marker.test(lines[i] ?? "")) continue;
|
|
1778
|
+
markerHits++;
|
|
1779
|
+
for (let j = Math.max(0, i - 4); j <= Math.min(lines.length - 1, i + 10); j++) {
|
|
1780
|
+
selected.add(j);
|
|
1781
|
+
}
|
|
1782
|
+
}
|
|
1783
|
+
if (markerHits === 0) {
|
|
1784
|
+
return lines.slice(-220).join("\n").trimEnd();
|
|
1785
|
+
}
|
|
1786
|
+
const ordered = [...selected].sort((a, b) => a - b);
|
|
1787
|
+
const out = [];
|
|
1788
|
+
let previous = -1;
|
|
1789
|
+
for (const index of ordered) {
|
|
1790
|
+
if (index > previous + 1) {
|
|
1791
|
+
const omitted = previous === -1 ? index : index - previous - 1;
|
|
1792
|
+
out.push(`[serializer omitted ${omitted} line(s)]`);
|
|
1793
|
+
}
|
|
1794
|
+
out.push(lines[index] ?? "");
|
|
1795
|
+
previous = index;
|
|
1796
|
+
}
|
|
1797
|
+
return out.join("\n").trimEnd();
|
|
1798
|
+
}
|
|
1799
|
+
function extractSpoolNote(output) {
|
|
1800
|
+
return output.split(/\r?\n/).find((line) => line.startsWith("[output truncated") && line.includes("full"));
|
|
1801
|
+
}
|
|
1802
|
+
function hasCommandOutputShape(obj) {
|
|
1803
|
+
return typeof obj["stdout"] === "string" || typeof obj["stderr"] === "string" || typeof obj["output"] === "string" || typeof obj["exitCode"] === "number" || typeof obj["exit_code"] === "number";
|
|
1804
|
+
}
|
|
1805
|
+
function renderCommandOutput(toolName, obj, input) {
|
|
1806
|
+
const command = stringField(obj, "command") ?? stringFromInput(input, "command");
|
|
1807
|
+
const args = stringArrayField(obj, "args");
|
|
1808
|
+
const commandLine = command ? [command, ...args].join(" ") : void 0;
|
|
1809
|
+
const output = stringField(obj, "output");
|
|
1810
|
+
const stdout = stringField(obj, "stdout");
|
|
1811
|
+
const stderr = stringField(obj, "stderr");
|
|
1812
|
+
return joinSections([
|
|
1813
|
+
renderHeader(commandLine ? `${toolName}: ${commandLine}` : toolName, {
|
|
1814
|
+
exit_code: obj["exit_code"] ?? obj["exitCode"],
|
|
1815
|
+
timed_out: obj["timed_out"],
|
|
1816
|
+
pid: obj["pid"],
|
|
1817
|
+
allowed: obj["allowed"],
|
|
1818
|
+
truncated: obj["truncated"],
|
|
1819
|
+
runner: obj["runner"],
|
|
1820
|
+
linter: obj["linter"],
|
|
1821
|
+
fixer: obj["fixer"],
|
|
1822
|
+
project: obj["project"],
|
|
1823
|
+
tests_run: obj["tests_run"],
|
|
1824
|
+
passed: obj["passed"],
|
|
1825
|
+
failed: obj["failed"],
|
|
1826
|
+
duration_ms: obj["duration_ms"],
|
|
1827
|
+
errors: obj["errors"],
|
|
1828
|
+
warnings: obj["warnings"],
|
|
1829
|
+
files_checked: obj["files_checked"],
|
|
1830
|
+
files_changed: obj["files_changed"],
|
|
1831
|
+
fix_applied: obj["fix_applied"]
|
|
1832
|
+
}),
|
|
1833
|
+
stringField(obj, "error") ? `error:
|
|
1834
|
+
${stringField(obj, "error")}` : void 0,
|
|
1835
|
+
output ? `output:
|
|
1836
|
+
${output}` : void 0,
|
|
1837
|
+
stdout ? `stdout:
|
|
1838
|
+
${stdout}` : void 0,
|
|
1839
|
+
stderr ? `stderr:
|
|
1840
|
+
${stderr}` : void 0
|
|
1841
|
+
]);
|
|
1842
|
+
}
|
|
1843
|
+
function renderGenericToolObject(toolName, obj) {
|
|
1844
|
+
const scalars = {};
|
|
1845
|
+
const blocks = [];
|
|
1846
|
+
for (const [key, value] of Object.entries(obj)) {
|
|
1847
|
+
if (value === void 0) continue;
|
|
1848
|
+
if (isScalar(value)) {
|
|
1849
|
+
const inline = String(value);
|
|
1850
|
+
if (inline.length <= INLINE_LIMIT && !inline.includes("\n")) {
|
|
1851
|
+
scalars[key] = value;
|
|
1852
|
+
} else {
|
|
1853
|
+
blocks.push(`${key}:
|
|
1854
|
+
${inline}`);
|
|
1855
|
+
}
|
|
1856
|
+
continue;
|
|
1857
|
+
}
|
|
1858
|
+
if (Array.isArray(value)) {
|
|
1859
|
+
if (value.every((item) => typeof item === "string")) {
|
|
1860
|
+
blocks.push(`${key}:
|
|
1861
|
+
${renderStringList(value)}`);
|
|
1862
|
+
} else {
|
|
1863
|
+
blocks.push(`${key}:
|
|
1864
|
+
${renderUnknownList(value)}`);
|
|
1865
|
+
}
|
|
1866
|
+
continue;
|
|
1867
|
+
}
|
|
1868
|
+
blocks.push(`${key}: ${clipInline(oneLineJson(value))}`);
|
|
1869
|
+
}
|
|
1870
|
+
return joinSections([renderHeader(toolName, scalars), ...blocks]);
|
|
1256
1871
|
}
|
|
1257
|
-
function
|
|
1258
|
-
const
|
|
1259
|
-
|
|
1260
|
-
|
|
1872
|
+
function renderHeader(label, fields) {
|
|
1873
|
+
const parts = Object.entries(fields).filter(([, value]) => value !== void 0 && value !== null && value !== "").map(([key, value]) => `${key}=${clipInline(formatInlineValue(value))}`);
|
|
1874
|
+
return parts.length > 0 ? `${label} (${parts.join(" ")})` : label;
|
|
1875
|
+
}
|
|
1876
|
+
function renderStringList(items, empty = "", limit = DEFAULT_LIST_LIMIT) {
|
|
1877
|
+
if (items.length === 0) return empty;
|
|
1878
|
+
const shown = items.slice(0, limit);
|
|
1879
|
+
const omitted = items.length - shown.length;
|
|
1880
|
+
return [
|
|
1881
|
+
...shown,
|
|
1882
|
+
...omitted > 0 ? [`[serializer omitted ${omitted} item(s); narrow the request for more]`] : []
|
|
1883
|
+
].join("\n");
|
|
1884
|
+
}
|
|
1885
|
+
function renderUnknownList(items, limit = DEFAULT_LIST_LIMIT) {
|
|
1886
|
+
const shown = items.slice(0, limit).map((item) => clipInline(oneLineJson(item), 1e3));
|
|
1887
|
+
const omitted = items.length - shown.length;
|
|
1888
|
+
if (omitted > 0)
|
|
1889
|
+
shown.push(`[serializer omitted ${omitted} item(s); narrow the request for more]`);
|
|
1890
|
+
return shown.join("\n");
|
|
1891
|
+
}
|
|
1892
|
+
function joinSections(sections) {
|
|
1893
|
+
return sections.map((section) => typeof section === "string" ? section.trimEnd() : void 0).filter((section) => !!section).join("\n");
|
|
1894
|
+
}
|
|
1895
|
+
function formatInlineValue(value) {
|
|
1896
|
+
if (Array.isArray(value)) return `[${value.map(formatInlineValue).join(",")}]`;
|
|
1897
|
+
if (isScalar(value)) return String(value);
|
|
1898
|
+
return oneLineJson(value);
|
|
1899
|
+
}
|
|
1900
|
+
function clipInline(value, max = INLINE_LIMIT) {
|
|
1901
|
+
const compact = value.replace(/\s+/g, " ").trim();
|
|
1902
|
+
return compact.length <= max ? compact : `${compact.slice(0, max - 15)}...(${compact.length} chars)`;
|
|
1903
|
+
}
|
|
1904
|
+
function oneLineJson(value) {
|
|
1905
|
+
try {
|
|
1906
|
+
return JSON.stringify(value);
|
|
1907
|
+
} catch {
|
|
1908
|
+
return String(value);
|
|
1261
1909
|
}
|
|
1262
|
-
|
|
1910
|
+
}
|
|
1911
|
+
function stringField(obj, key) {
|
|
1912
|
+
const value = obj[key];
|
|
1913
|
+
return typeof value === "string" ? value : void 0;
|
|
1914
|
+
}
|
|
1915
|
+
function numberField(obj, key) {
|
|
1916
|
+
const value = obj[key];
|
|
1917
|
+
return typeof value === "number" ? value : void 0;
|
|
1918
|
+
}
|
|
1919
|
+
function stringArrayField(obj, key) {
|
|
1920
|
+
const value = obj[key];
|
|
1921
|
+
return Array.isArray(value) ? value.filter((item) => typeof item === "string") : [];
|
|
1922
|
+
}
|
|
1923
|
+
function stringFromInput(input, key) {
|
|
1924
|
+
if (!isRecord2(input)) return void 0;
|
|
1925
|
+
const value = input[key];
|
|
1926
|
+
return typeof value === "string" ? value : void 0;
|
|
1927
|
+
}
|
|
1928
|
+
function numberFromInput(input, key) {
|
|
1929
|
+
if (!isRecord2(input)) return void 0;
|
|
1930
|
+
const value = input[key];
|
|
1931
|
+
return typeof value === "number" ? value : void 0;
|
|
1932
|
+
}
|
|
1933
|
+
function inputListSummary(input, key) {
|
|
1934
|
+
if (!isRecord2(input)) return void 0;
|
|
1935
|
+
const value = input[key];
|
|
1936
|
+
if (typeof value === "string") return value;
|
|
1937
|
+
if (Array.isArray(value)) return value.filter((item) => typeof item === "string").join(",");
|
|
1938
|
+
return void 0;
|
|
1939
|
+
}
|
|
1940
|
+
function isRecord2(value) {
|
|
1941
|
+
return !!value && typeof value === "object" && !Array.isArray(value);
|
|
1942
|
+
}
|
|
1943
|
+
function isScalar(value) {
|
|
1944
|
+
return value === null || ["string", "number", "boolean"].includes(typeof value);
|
|
1945
|
+
}
|
|
1946
|
+
function projectHash(absRoot) {
|
|
1947
|
+
return createHash("sha256").update(path4.resolve(absRoot)).digest("hex").slice(0, 12);
|
|
1948
|
+
}
|
|
1949
|
+
function projectSlug(absRoot) {
|
|
1950
|
+
const base = slugify(path4.basename(absRoot));
|
|
1951
|
+
const hash = createHash("sha256").update(path4.resolve(absRoot)).digest("hex").slice(0, 6);
|
|
1952
|
+
return `${base}-${hash}`;
|
|
1953
|
+
}
|
|
1954
|
+
function slugify(name) {
|
|
1955
|
+
return name.toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-+|-+$/g, "").slice(0, 40) || "project";
|
|
1956
|
+
}
|
|
1957
|
+
function wstackGlobalRoot() {
|
|
1958
|
+
const fromEnv = process.env["WRONGSTACK_HOME"];
|
|
1959
|
+
if (fromEnv && fromEnv.trim().length > 0) return path4.resolve(fromEnv);
|
|
1960
|
+
return path4.join(os.homedir(), ".wrongstack");
|
|
1961
|
+
}
|
|
1962
|
+
function resolveWstackPaths(opts) {
|
|
1963
|
+
const globalRoot = opts.globalRoot ?? (opts.userHome ? path4.join(opts.userHome, ".wrongstack") : wstackGlobalRoot());
|
|
1964
|
+
const hash = projectHash(opts.projectRoot);
|
|
1965
|
+
const slug = projectSlug(opts.projectRoot);
|
|
1966
|
+
const projectDir = path4.join(globalRoot, "projects", slug);
|
|
1967
|
+
return {
|
|
1968
|
+
globalRoot,
|
|
1969
|
+
configDir: globalRoot,
|
|
1970
|
+
globalConfig: path4.join(globalRoot, "config.json"),
|
|
1971
|
+
secretsKey: path4.join(globalRoot, ".key"),
|
|
1972
|
+
globalMemory: path4.join(globalRoot, "memory.md"),
|
|
1973
|
+
globalSkills: path4.join(globalRoot, "skills"),
|
|
1974
|
+
globalPrompts: path4.join(globalRoot, "prompts"),
|
|
1975
|
+
cacheDir: path4.join(globalRoot, "cache"),
|
|
1976
|
+
modelsCache: path4.join(globalRoot, "cache", "models.dev.json"),
|
|
1977
|
+
modelsOverlayCache: path4.join(globalRoot, "cache", "models-overlay.json"),
|
|
1978
|
+
historyFile: path4.join(globalRoot, "history"),
|
|
1979
|
+
logFile: path4.join(globalRoot, "logs", "wrongstack.log"),
|
|
1980
|
+
projectDir,
|
|
1981
|
+
projectCodebaseIndex: path4.join(projectDir, "codebase-index"),
|
|
1982
|
+
projectMemory: path4.join(projectDir, "memory.md"),
|
|
1983
|
+
projectSessions: path4.join(projectDir, "sessions"),
|
|
1984
|
+
projectTrust: path4.join(projectDir, "trust.json"),
|
|
1985
|
+
projectMeta: path4.join(projectDir, "meta.json"),
|
|
1986
|
+
projectLocalConfig: path4.join(projectDir, "config.local.json"),
|
|
1987
|
+
inProjectConfig: path4.join(opts.projectRoot, ".wrongstack", "config.json"),
|
|
1988
|
+
inProjectAgentsFile: path4.join(opts.projectRoot, ".wrongstack", "AGENTS.md"),
|
|
1989
|
+
inProjectSkills: path4.join(opts.projectRoot, ".wrongstack", "skills"),
|
|
1990
|
+
inProjectWorktrees: path4.join(opts.projectRoot, ".wrongstack", "worktrees"),
|
|
1991
|
+
projectHash: hash,
|
|
1992
|
+
projectSlug: slug,
|
|
1993
|
+
projectGoal: path4.join(projectDir, "goal.json"),
|
|
1994
|
+
projectSpecs: path4.join(projectDir, "specs"),
|
|
1995
|
+
projectTaskGraphs: path4.join(projectDir, "task-graphs"),
|
|
1996
|
+
projectSddSession: path4.join(projectDir, "sdd-session.json"),
|
|
1997
|
+
projectPlan: path4.join(projectDir, "plan.json"),
|
|
1998
|
+
projectAutophase: path4.join(projectDir, "autophase"),
|
|
1999
|
+
syncConfig: path4.join(globalRoot, "sync.json"),
|
|
2000
|
+
projectStatus: (projectHash2) => path4.join(globalRoot, "projects", projectHash2, "status.json")
|
|
2001
|
+
};
|
|
1263
2002
|
}
|
|
1264
2003
|
|
|
1265
2004
|
// src/storage/session-store.ts
|
|
@@ -1277,11 +2016,34 @@ var DefaultSessionStore = class _DefaultSessionStore {
|
|
|
1277
2016
|
dir;
|
|
1278
2017
|
events;
|
|
1279
2018
|
secretScrubber;
|
|
2019
|
+
/**
|
|
2020
|
+
* In-memory cache for load() results, keyed by session ID. The cache is
|
|
2021
|
+
* invalidated when the file's mtimeMs or size changes (indicating the
|
|
2022
|
+
* file was written to). This eliminates redundant full-file reads and
|
|
2023
|
+
* JSON parses when the same session is loaded multiple times within the
|
|
2024
|
+
* store's lifetime (e.g., webui session detail views, list() fallbacks).
|
|
2025
|
+
*
|
|
2026
|
+
* Max size is capped to prevent unbounded memory growth in long-running
|
|
2027
|
+
* processes. When the limit is reached, the oldest entry is evicted.
|
|
2028
|
+
*/
|
|
2029
|
+
_loadCache = /* @__PURE__ */ new Map();
|
|
2030
|
+
static LOAD_CACHE_MAX_ENTRIES = 50;
|
|
1280
2031
|
constructor(opts) {
|
|
1281
2032
|
this.dir = opts.dir;
|
|
1282
2033
|
this.events = opts.events;
|
|
1283
2034
|
this.secretScrubber = opts.secretScrubber;
|
|
1284
2035
|
}
|
|
2036
|
+
/**
|
|
2037
|
+
* Clear the load() cache. Useful for testing or when the caller knows
|
|
2038
|
+
* the file has changed externally (e.g., another process wrote to it).
|
|
2039
|
+
*/
|
|
2040
|
+
clearLoadCache(sessionId) {
|
|
2041
|
+
if (sessionId !== void 0) {
|
|
2042
|
+
this._loadCache.delete(sessionId);
|
|
2043
|
+
} else {
|
|
2044
|
+
this._loadCache.clear();
|
|
2045
|
+
}
|
|
2046
|
+
}
|
|
1285
2047
|
// ── Storage event helpers ───────────────────────────────────────────────────
|
|
1286
2048
|
emitRead(sessionId, filePath, operation, outcome, durationMs, error) {
|
|
1287
2049
|
this.events?.emit("storage.read", {
|
|
@@ -1318,11 +2080,11 @@ var DefaultSessionStore = class _DefaultSessionStore {
|
|
|
1318
2080
|
}
|
|
1319
2081
|
/** Absolute path to the session index file. */
|
|
1320
2082
|
get indexFile() {
|
|
1321
|
-
return
|
|
2083
|
+
return path4.join(this.dir, "_index.jsonl");
|
|
1322
2084
|
}
|
|
1323
2085
|
/** Join session ID to its absolute path within the store directory. */
|
|
1324
2086
|
sessionPath(id, ext) {
|
|
1325
|
-
return
|
|
2087
|
+
return path4.join(this.dir, `${id}${ext}`);
|
|
1326
2088
|
}
|
|
1327
2089
|
/**
|
|
1328
2090
|
* Ensure the directory implied by the session ID exists. When the ID
|
|
@@ -1330,7 +2092,7 @@ var DefaultSessionStore = class _DefaultSessionStore {
|
|
|
1330
2092
|
* subdirectory so sessions group naturally by day.
|
|
1331
2093
|
*/
|
|
1332
2094
|
async ensureShardDir(id) {
|
|
1333
|
-
const dirPath =
|
|
2095
|
+
const dirPath = path4.dirname(path4.join(this.dir, id));
|
|
1334
2096
|
await ensureDir(dirPath);
|
|
1335
2097
|
return dirPath;
|
|
1336
2098
|
}
|
|
@@ -1338,7 +2100,7 @@ var DefaultSessionStore = class _DefaultSessionStore {
|
|
|
1338
2100
|
const startedAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
1339
2101
|
const id = meta.id && meta.id.length > 0 ? meta.id : generateSessionId(startedAt, meta.model ?? meta.provider);
|
|
1340
2102
|
const shardDir = await this.ensureShardDir(id);
|
|
1341
|
-
const file =
|
|
2103
|
+
const file = path4.join(shardDir, `${path4.basename(id)}.jsonl`);
|
|
1342
2104
|
const t0 = Date.now();
|
|
1343
2105
|
let handle;
|
|
1344
2106
|
try {
|
|
@@ -1400,7 +2162,7 @@ var DefaultSessionStore = class _DefaultSessionStore {
|
|
|
1400
2162
|
// Shard directory (sessions/<date>/) — must match create() so the
|
|
1401
2163
|
// .summary.json sidecar lands next to the JSONL instead of the
|
|
1402
2164
|
// sessions root (where summaryFor() would never find it).
|
|
1403
|
-
dir:
|
|
2165
|
+
dir: path4.dirname(file),
|
|
1404
2166
|
filePath: file,
|
|
1405
2167
|
secretScrubber: this.secretScrubber,
|
|
1406
2168
|
onClose: (s) => this.appendToIndex(s)
|
|
@@ -1424,7 +2186,20 @@ var DefaultSessionStore = class _DefaultSessionStore {
|
|
|
1424
2186
|
const t0 = Date.now();
|
|
1425
2187
|
let outcome = "success";
|
|
1426
2188
|
let errorMsg;
|
|
2189
|
+
let cacheHit = false;
|
|
1427
2190
|
try {
|
|
2191
|
+
let stat6;
|
|
2192
|
+
try {
|
|
2193
|
+
const s = await fsp2.stat(file);
|
|
2194
|
+
stat6 = { mtimeMs: s.mtimeMs, size: s.size };
|
|
2195
|
+
} catch (err) {
|
|
2196
|
+
throw err;
|
|
2197
|
+
}
|
|
2198
|
+
const cached = this._loadCache.get(id);
|
|
2199
|
+
if (cached && cached.mtimeMs === stat6.mtimeMs && cached.size === stat6.size) {
|
|
2200
|
+
cacheHit = true;
|
|
2201
|
+
return cached.data;
|
|
2202
|
+
}
|
|
1428
2203
|
const raw = await fsp2.readFile(file, "utf8");
|
|
1429
2204
|
const lines = raw.split("\n").filter((l) => l.trim());
|
|
1430
2205
|
const events = [];
|
|
@@ -1440,13 +2215,30 @@ var DefaultSessionStore = class _DefaultSessionStore {
|
|
|
1440
2215
|
const meta = this.metaFromEvents(id, events);
|
|
1441
2216
|
const { messages, usage } = this.replay(events, id);
|
|
1442
2217
|
const toolCallEnds = extractToolCallEnds(events);
|
|
1443
|
-
|
|
2218
|
+
const data = { metadata: meta, events, messages, usage, toolCallEnds };
|
|
2219
|
+
if (this._loadCache.size >= _DefaultSessionStore.LOAD_CACHE_MAX_ENTRIES) {
|
|
2220
|
+
const oldest = this._loadCache.keys().next().value;
|
|
2221
|
+
if (oldest !== void 0) {
|
|
2222
|
+
this._loadCache.delete(oldest);
|
|
2223
|
+
}
|
|
2224
|
+
}
|
|
2225
|
+
this._loadCache.set(id, { mtimeMs: stat6.mtimeMs, size: stat6.size, data });
|
|
2226
|
+
return data;
|
|
1444
2227
|
} catch (err) {
|
|
1445
2228
|
outcome = "failure";
|
|
1446
2229
|
errorMsg = toErrorMessage(err);
|
|
1447
2230
|
throw err;
|
|
1448
2231
|
} finally {
|
|
1449
2232
|
this.emitRead(id, file, "load", outcome, Date.now() - t0, errorMsg);
|
|
2233
|
+
if (cacheHit) {
|
|
2234
|
+
this.events?.emit("storage.cache_hit", {
|
|
2235
|
+
sessionId: id,
|
|
2236
|
+
store: "session",
|
|
2237
|
+
filePath: file,
|
|
2238
|
+
operation: "load",
|
|
2239
|
+
durationMs: Date.now() - t0
|
|
2240
|
+
});
|
|
2241
|
+
}
|
|
1450
2242
|
}
|
|
1451
2243
|
}
|
|
1452
2244
|
async list(limit = 20) {
|
|
@@ -1594,7 +2386,7 @@ var DefaultSessionStore = class _DefaultSessionStore {
|
|
|
1594
2386
|
continue;
|
|
1595
2387
|
if (entry.isDirectory()) {
|
|
1596
2388
|
const childPrefix = depth === 0 ? entry.name : `${prefix}/${entry.name}`;
|
|
1597
|
-
ids.push(...await this.collectSessionIds(
|
|
2389
|
+
ids.push(...await this.collectSessionIds(path4.join(dir, entry.name), childPrefix, depth + 1));
|
|
1598
2390
|
} else if (entry.isFile() && entry.name.endsWith(".jsonl")) {
|
|
1599
2391
|
if (entry.name === "_index.jsonl") continue;
|
|
1600
2392
|
const base = entry.name.replace(/\.jsonl$/, "");
|
|
@@ -1645,14 +2437,14 @@ var DefaultSessionStore = class _DefaultSessionStore {
|
|
|
1645
2437
|
async deleteSession(id) {
|
|
1646
2438
|
const jsonlPath = this.sessionPath(id, ".jsonl");
|
|
1647
2439
|
const summaryPath = this.sessionPath(id, ".summary.json");
|
|
1648
|
-
const shardDir =
|
|
1649
|
-
const base =
|
|
1650
|
-
const sessDir =
|
|
2440
|
+
const shardDir = path4.dirname(path4.join(this.dir, id));
|
|
2441
|
+
const base = path4.basename(id);
|
|
2442
|
+
const sessDir = path4.join(shardDir, base);
|
|
1651
2443
|
const deletions = [
|
|
1652
2444
|
fsp2.unlink(jsonlPath),
|
|
1653
2445
|
fsp2.unlink(summaryPath),
|
|
1654
|
-
fsp2.unlink(
|
|
1655
|
-
fsp2.unlink(
|
|
2446
|
+
fsp2.unlink(path4.join(shardDir, `${base}.plan.json`)),
|
|
2447
|
+
fsp2.unlink(path4.join(shardDir, `${base}.todos.json`))
|
|
1656
2448
|
];
|
|
1657
2449
|
const results = await Promise.allSettled(deletions);
|
|
1658
2450
|
for (const r of results) {
|
|
@@ -1688,14 +2480,14 @@ var DefaultSessionStore = class _DefaultSessionStore {
|
|
|
1688
2480
|
let deleted = 0;
|
|
1689
2481
|
let activeSessionId = null;
|
|
1690
2482
|
try {
|
|
1691
|
-
const raw = await fsp2.readFile(
|
|
2483
|
+
const raw = await fsp2.readFile(path4.join(this.dir, "active.json"), "utf8");
|
|
1692
2484
|
const active = JSON.parse(raw);
|
|
1693
2485
|
activeSessionId = active.sessionId ?? null;
|
|
1694
2486
|
} catch {
|
|
1695
2487
|
}
|
|
1696
2488
|
const isPrunableJsonl = (name) => name.endsWith(".jsonl") && name !== "_index.jsonl" && name !== "_mailbox.jsonl" && !name.endsWith(".replay.jsonl") && !name.endsWith(".audit.jsonl");
|
|
1697
2489
|
const pruneFile = async (dir, name, prefix) => {
|
|
1698
|
-
const jsonlPath =
|
|
2490
|
+
const jsonlPath = path4.join(dir, name);
|
|
1699
2491
|
try {
|
|
1700
2492
|
const stat6 = await fsp2.stat(jsonlPath);
|
|
1701
2493
|
if (stat6.mtimeMs >= cutoff) return;
|
|
@@ -1715,7 +2507,7 @@ var DefaultSessionStore = class _DefaultSessionStore {
|
|
|
1715
2507
|
continue;
|
|
1716
2508
|
}
|
|
1717
2509
|
if (!entry.isDirectory()) continue;
|
|
1718
|
-
const dateDir =
|
|
2510
|
+
const dateDir = path4.join(this.dir, entry.name);
|
|
1719
2511
|
const files = await fsp2.readdir(dateDir, { withFileTypes: true }).catch(() => []);
|
|
1720
2512
|
for (const file of files) {
|
|
1721
2513
|
if (!file.isFile() || !isPrunableJsonl(file.name)) continue;
|
|
@@ -1727,7 +2519,7 @@ var DefaultSessionStore = class _DefaultSessionStore {
|
|
|
1727
2519
|
}
|
|
1728
2520
|
for (const entry of entries) {
|
|
1729
2521
|
if (!entry.isDirectory()) continue;
|
|
1730
|
-
const dateDir =
|
|
2522
|
+
const dateDir = path4.join(this.dir, entry.name);
|
|
1731
2523
|
try {
|
|
1732
2524
|
const remaining = await fsp2.readdir(dateDir);
|
|
1733
2525
|
if (remaining.length === 0) {
|
|
@@ -1902,7 +2694,7 @@ var FileSessionWriter = class _FileSessionWriter {
|
|
|
1902
2694
|
this.meta = meta;
|
|
1903
2695
|
this.events = events;
|
|
1904
2696
|
this.resumed = opts.resumed ?? false;
|
|
1905
|
-
this.manifestFile = opts.dir ?
|
|
2697
|
+
this.manifestFile = opts.dir ? path4.join(opts.dir, `${path4.basename(id)}.summary.json`) : "";
|
|
1906
2698
|
this.filePath = opts.filePath ?? "";
|
|
1907
2699
|
this.secretScrubber = opts.secretScrubber;
|
|
1908
2700
|
this.onCloseCb = opts.onClose;
|
|
@@ -2406,7 +3198,7 @@ var QueueStore = class {
|
|
|
2406
3198
|
events;
|
|
2407
3199
|
traceId;
|
|
2408
3200
|
constructor(opts) {
|
|
2409
|
-
this.file =
|
|
3201
|
+
this.file = path4.join(opts.dir, "queue.json");
|
|
2410
3202
|
this.events = opts.events;
|
|
2411
3203
|
this.traceId = opts.traceId;
|
|
2412
3204
|
}
|
|
@@ -2593,7 +3385,7 @@ var DefaultAttachmentStore = class {
|
|
|
2593
3385
|
let data = input.data;
|
|
2594
3386
|
if (this.spoolDir && bytes >= this.spoolThreshold) {
|
|
2595
3387
|
await fsp2.mkdir(this.spoolDir, { recursive: true });
|
|
2596
|
-
spooledPath =
|
|
3388
|
+
spooledPath = path4.join(this.spoolDir, `${id}.bin`);
|
|
2597
3389
|
await atomicWrite(spooledPath, input.data, {
|
|
2598
3390
|
encoding: input.kind === "image" ? "base64" : "utf8"
|
|
2599
3391
|
});
|
|
@@ -2805,7 +3597,7 @@ var FileMemoryBackend = class {
|
|
|
2805
3597
|
}
|
|
2806
3598
|
async remember(scope, entry, filePath) {
|
|
2807
3599
|
const file = this.resolveFile(filePath, scope);
|
|
2808
|
-
await ensureDir(
|
|
3600
|
+
await ensureDir(path4.dirname(file));
|
|
2809
3601
|
let existing = "";
|
|
2810
3602
|
try {
|
|
2811
3603
|
existing = await fsp2.readFile(file, "utf8");
|
|
@@ -3421,9 +4213,9 @@ ${body.trim()}`);
|
|
|
3421
4213
|
if (!this.persistBackup || scope === "project-agents") return;
|
|
3422
4214
|
try {
|
|
3423
4215
|
const content = await this.backend.readAll(scope, this.files[scope]);
|
|
3424
|
-
const { writeFile:
|
|
3425
|
-
await
|
|
3426
|
-
await
|
|
4216
|
+
const { writeFile: writeFile5, mkdir: mkdir7 } = await import('fs/promises');
|
|
4217
|
+
await mkdir7(this.backupDir, { recursive: true });
|
|
4218
|
+
await writeFile5(`${this.backupDir}/${scope}.md`, content, "utf8");
|
|
3427
4219
|
} catch {
|
|
3428
4220
|
}
|
|
3429
4221
|
}
|
|
@@ -3663,7 +4455,10 @@ function deepFreeze(obj) {
|
|
|
3663
4455
|
}
|
|
3664
4456
|
|
|
3665
4457
|
// src/types/secret-vault.ts
|
|
3666
|
-
var
|
|
4458
|
+
var ENCRYPTED_PREFIX_PATTERN = /^enc:v(\d+):/;
|
|
4459
|
+
function encryptedPrefixForVersion(version) {
|
|
4460
|
+
return `enc:v${version}:`;
|
|
4461
|
+
}
|
|
3667
4462
|
|
|
3668
4463
|
// src/security/secret-vault.ts
|
|
3669
4464
|
init_atomic_write();
|
|
@@ -3672,10 +4467,12 @@ var IV_BYTES = 12;
|
|
|
3672
4467
|
var TAG_BYTES = 16;
|
|
3673
4468
|
var ALGO = "aes-256-gcm";
|
|
3674
4469
|
var KEY_FILE_MODE = 384;
|
|
4470
|
+
var KEY_FILE_MAGIC = Buffer.from("WSKV", "ascii");
|
|
4471
|
+
var VERSIONED_KEY_FILE_SIZE = KEY_FILE_MAGIC.length + 1 + KEY_BYTES;
|
|
3675
4472
|
function checkKeyFilePermissions(keyFile) {
|
|
3676
4473
|
if (process.platform === "win32") return;
|
|
3677
4474
|
try {
|
|
3678
|
-
const stat6 =
|
|
4475
|
+
const stat6 = fs4.statSync(keyFile);
|
|
3679
4476
|
const actualMode = stat6.mode & 511;
|
|
3680
4477
|
if (actualMode !== KEY_FILE_MODE) {
|
|
3681
4478
|
console.warn(JSON.stringify({
|
|
@@ -3694,11 +4491,17 @@ function checkKeyFilePermissions(keyFile) {
|
|
|
3694
4491
|
var DefaultSecretVault = class {
|
|
3695
4492
|
keyFile;
|
|
3696
4493
|
key;
|
|
4494
|
+
_keyVersion = 1;
|
|
3697
4495
|
constructor(opts) {
|
|
3698
4496
|
this.keyFile = opts.keyFile;
|
|
3699
4497
|
}
|
|
4498
|
+
/** Current key version. Starts at 1; incremented by rotateKey(). */
|
|
4499
|
+
get keyVersion() {
|
|
4500
|
+
if (!this.key) this.loadOrCreateKey();
|
|
4501
|
+
return this._keyVersion;
|
|
4502
|
+
}
|
|
3700
4503
|
isEncrypted(value) {
|
|
3701
|
-
return typeof value === "string" &&
|
|
4504
|
+
return typeof value === "string" && ENCRYPTED_PREFIX_PATTERN.test(value);
|
|
3702
4505
|
}
|
|
3703
4506
|
encrypt(plaintext) {
|
|
3704
4507
|
if (this.isEncrypted(plaintext)) return plaintext;
|
|
@@ -3707,11 +4510,20 @@ var DefaultSecretVault = class {
|
|
|
3707
4510
|
const cipher = createCipheriv(ALGO, key, iv);
|
|
3708
4511
|
const ct = Buffer.concat([cipher.update(plaintext, "utf8"), cipher.final()]);
|
|
3709
4512
|
const tag = cipher.getAuthTag();
|
|
3710
|
-
|
|
4513
|
+
const prefix = encryptedPrefixForVersion(this._keyVersion);
|
|
4514
|
+
return `${prefix}${iv.toString("base64")}:${tag.toString("base64")}:${ct.toString("base64")}`;
|
|
3711
4515
|
}
|
|
3712
4516
|
decrypt(value) {
|
|
3713
4517
|
if (!this.isEncrypted(value)) return value;
|
|
3714
|
-
const
|
|
4518
|
+
const prefixMatch = value.match(ENCRYPTED_PREFIX_PATTERN);
|
|
4519
|
+
if (!prefixMatch) {
|
|
4520
|
+
throw new ConfigError({
|
|
4521
|
+
message: "SecretVault: malformed encrypted value",
|
|
4522
|
+
code: ERROR_CODES.CONFIG_PARSE_FAILED,
|
|
4523
|
+
context: { field: "encrypted_value" }
|
|
4524
|
+
});
|
|
4525
|
+
}
|
|
4526
|
+
const rest = value.slice(prefixMatch[0].length);
|
|
3715
4527
|
const parts = rest.split(":");
|
|
3716
4528
|
if (parts.length !== 3) {
|
|
3717
4529
|
throw new ConfigError({
|
|
@@ -3740,42 +4552,104 @@ var DefaultSecretVault = class {
|
|
|
3740
4552
|
const pt = Buffer.concat([decipher.update(ct), decipher.final()]);
|
|
3741
4553
|
return pt.toString("utf8");
|
|
3742
4554
|
}
|
|
4555
|
+
/**
|
|
4556
|
+
* Generate a new encryption key, write it to disk, and increment the key version.
|
|
4557
|
+
* After rotation, encrypt() emits the new version prefix (e.g. enc:v2:).
|
|
4558
|
+
* The caller must re-encrypt existing config values (see rotateConfigKeys()).
|
|
4559
|
+
*/
|
|
4560
|
+
rotateKey() {
|
|
4561
|
+
const oldVersion = this._keyVersion;
|
|
4562
|
+
const newKey = randomBytes(KEY_BYTES);
|
|
4563
|
+
const newVersion = oldVersion + 1;
|
|
4564
|
+
const keyFileBuf = Buffer.alloc(VERSIONED_KEY_FILE_SIZE);
|
|
4565
|
+
KEY_FILE_MAGIC.copy(keyFileBuf, 0);
|
|
4566
|
+
keyFileBuf[KEY_FILE_MAGIC.length] = newVersion;
|
|
4567
|
+
newKey.copy(keyFileBuf, KEY_FILE_MAGIC.length + 1);
|
|
4568
|
+
fs4.mkdirSync(path4.dirname(this.keyFile), { recursive: true });
|
|
4569
|
+
fs4.writeFileSync(this.keyFile, keyFileBuf, { mode: 384 });
|
|
4570
|
+
checkKeyFilePermissions(this.keyFile);
|
|
4571
|
+
this.key = newKey;
|
|
4572
|
+
this._keyVersion = newVersion;
|
|
4573
|
+
return { oldVersion, newVersion };
|
|
4574
|
+
}
|
|
3743
4575
|
loadOrCreateKey() {
|
|
3744
4576
|
if (this.key) return this.key;
|
|
3745
4577
|
try {
|
|
3746
|
-
const buf =
|
|
3747
|
-
if (buf.length
|
|
3748
|
-
|
|
3749
|
-
|
|
3750
|
-
|
|
3751
|
-
|
|
3752
|
-
|
|
4578
|
+
const buf = fs4.readFileSync(this.keyFile);
|
|
4579
|
+
if (buf.length === KEY_BYTES) {
|
|
4580
|
+
this.key = buf;
|
|
4581
|
+
this._keyVersion = 1;
|
|
4582
|
+
checkKeyFilePermissions(this.keyFile);
|
|
4583
|
+
return this.key;
|
|
4584
|
+
}
|
|
4585
|
+
if (buf.length === VERSIONED_KEY_FILE_SIZE) {
|
|
4586
|
+
const magic = buf.subarray(0, KEY_FILE_MAGIC.length);
|
|
4587
|
+
if (!magic.equals(KEY_FILE_MAGIC)) {
|
|
4588
|
+
throw new ConfigError({
|
|
4589
|
+
message: `SecretVault: key file ${this.keyFile} has invalid magic header`,
|
|
4590
|
+
code: ERROR_CODES.CONFIG_INVALID,
|
|
4591
|
+
context: { keyFile: this.keyFile }
|
|
4592
|
+
});
|
|
4593
|
+
}
|
|
4594
|
+
const version = buf[KEY_FILE_MAGIC.length];
|
|
4595
|
+
const key2 = buf.subarray(KEY_FILE_MAGIC.length + 1);
|
|
4596
|
+
if (key2.length !== KEY_BYTES) {
|
|
4597
|
+
throw new ConfigError({
|
|
4598
|
+
message: `SecretVault: key file ${this.keyFile} has wrong key size (${key2.length} bytes, expected ${KEY_BYTES})`,
|
|
4599
|
+
code: ERROR_CODES.CONFIG_INVALID,
|
|
4600
|
+
context: { keyFile: this.keyFile, expectedBytes: KEY_BYTES, actualBytes: key2.length }
|
|
4601
|
+
});
|
|
4602
|
+
}
|
|
4603
|
+
this.key = Buffer.from(key2);
|
|
4604
|
+
this._keyVersion = version;
|
|
4605
|
+
checkKeyFilePermissions(this.keyFile);
|
|
4606
|
+
return this.key;
|
|
3753
4607
|
}
|
|
3754
|
-
|
|
3755
|
-
|
|
3756
|
-
|
|
4608
|
+
throw new ConfigError({
|
|
4609
|
+
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.`,
|
|
4610
|
+
code: ERROR_CODES.CONFIG_INVALID,
|
|
4611
|
+
context: { keyFile: this.keyFile, expectedBytes: KEY_BYTES, actualBytes: buf.length }
|
|
4612
|
+
});
|
|
3757
4613
|
} catch (err) {
|
|
3758
4614
|
if (err.code !== "ENOENT") throw err;
|
|
3759
4615
|
}
|
|
3760
|
-
|
|
4616
|
+
fs4.mkdirSync(path4.dirname(this.keyFile), { recursive: true });
|
|
3761
4617
|
const key = randomBytes(KEY_BYTES);
|
|
3762
4618
|
try {
|
|
3763
|
-
|
|
4619
|
+
fs4.writeFileSync(this.keyFile, key, { mode: 384, flag: "wx" });
|
|
3764
4620
|
} catch (err) {
|
|
3765
4621
|
if (err.code !== "EEXIST") throw err;
|
|
3766
|
-
const buf =
|
|
3767
|
-
if (buf.length
|
|
3768
|
-
|
|
3769
|
-
|
|
3770
|
-
|
|
3771
|
-
|
|
3772
|
-
|
|
4622
|
+
const buf = fs4.readFileSync(this.keyFile);
|
|
4623
|
+
if (buf.length === KEY_BYTES) {
|
|
4624
|
+
this.key = buf;
|
|
4625
|
+
this._keyVersion = 1;
|
|
4626
|
+
checkKeyFilePermissions(this.keyFile);
|
|
4627
|
+
return this.key;
|
|
4628
|
+
}
|
|
4629
|
+
if (buf.length === VERSIONED_KEY_FILE_SIZE) {
|
|
4630
|
+
const magic = buf.subarray(0, KEY_FILE_MAGIC.length);
|
|
4631
|
+
if (!magic.equals(KEY_FILE_MAGIC)) {
|
|
4632
|
+
throw new ConfigError({
|
|
4633
|
+
message: `SecretVault: key file ${this.keyFile} has invalid magic header`,
|
|
4634
|
+
code: ERROR_CODES.CONFIG_INVALID,
|
|
4635
|
+
context: { keyFile: this.keyFile }
|
|
4636
|
+
});
|
|
4637
|
+
}
|
|
4638
|
+
const version = buf[KEY_FILE_MAGIC.length];
|
|
4639
|
+
const winnerKey = buf.subarray(KEY_FILE_MAGIC.length + 1);
|
|
4640
|
+
this.key = Buffer.from(winnerKey);
|
|
4641
|
+
this._keyVersion = version;
|
|
4642
|
+
checkKeyFilePermissions(this.keyFile);
|
|
4643
|
+
return this.key;
|
|
3773
4644
|
}
|
|
3774
|
-
|
|
3775
|
-
|
|
3776
|
-
|
|
4645
|
+
throw new ConfigError({
|
|
4646
|
+
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.`,
|
|
4647
|
+
code: ERROR_CODES.CONFIG_INVALID,
|
|
4648
|
+
context: { keyFile: this.keyFile, expectedBytes: KEY_BYTES, actualBytes: buf.length }
|
|
4649
|
+
});
|
|
3777
4650
|
}
|
|
3778
4651
|
this.key = key;
|
|
4652
|
+
this._keyVersion = 1;
|
|
3779
4653
|
return key;
|
|
3780
4654
|
}
|
|
3781
4655
|
};
|
|
@@ -3829,7 +4703,7 @@ async function rewriteConfigEncrypted(configPath, vault, patch) {
|
|
|
3829
4703
|
}
|
|
3830
4704
|
const merged = deepMerge(current, patch ?? {});
|
|
3831
4705
|
const encrypted = encryptConfigSecrets(merged, vault);
|
|
3832
|
-
await fsp2.mkdir(
|
|
4706
|
+
await fsp2.mkdir(path4.dirname(configPath), { recursive: true });
|
|
3833
4707
|
await atomicWrite(configPath, JSON.stringify(encrypted, null, 2), { mode: 384 });
|
|
3834
4708
|
await restrictFilePermissions(configPath);
|
|
3835
4709
|
}
|
|
@@ -3971,7 +4845,8 @@ var DEFAULT_TOOLS_CONFIG = Object.freeze({
|
|
|
3971
4845
|
iterationTimeoutMs: 3e5,
|
|
3972
4846
|
sessionTimeoutMs: 18e5,
|
|
3973
4847
|
perIterationOutputCapBytes: 1e5,
|
|
3974
|
-
autoExtendLimit: true
|
|
4848
|
+
autoExtendLimit: true,
|
|
4849
|
+
restrictToProjectRoot: false
|
|
3975
4850
|
});
|
|
3976
4851
|
var DEFAULT_CONTEXT_CONFIG = Object.freeze({
|
|
3977
4852
|
preserveK: 10,
|
|
@@ -4014,7 +4889,8 @@ var BEHAVIOR_DEFAULTS = {
|
|
|
4014
4889
|
iterationTimeoutMs: DEFAULT_TOOLS_CONFIG.iterationTimeoutMs,
|
|
4015
4890
|
sessionTimeoutMs: DEFAULT_TOOLS_CONFIG.sessionTimeoutMs,
|
|
4016
4891
|
perIterationOutputCapBytes: DEFAULT_TOOLS_CONFIG.perIterationOutputCapBytes,
|
|
4017
|
-
autoExtendLimit: DEFAULT_TOOLS_CONFIG.autoExtendLimit
|
|
4892
|
+
autoExtendLimit: DEFAULT_TOOLS_CONFIG.autoExtendLimit,
|
|
4893
|
+
restrictToProjectRoot: DEFAULT_TOOLS_CONFIG.restrictToProjectRoot
|
|
4018
4894
|
},
|
|
4019
4895
|
log: { level: "info" },
|
|
4020
4896
|
features: {
|
|
@@ -4457,7 +5333,7 @@ var RecoveryLock = class {
|
|
|
4457
5333
|
sessionStore;
|
|
4458
5334
|
probe;
|
|
4459
5335
|
constructor(opts) {
|
|
4460
|
-
this.file =
|
|
5336
|
+
this.file = path4.join(opts.dir, LOCK_FILE);
|
|
4461
5337
|
this.pid = opts.pid ?? process.pid;
|
|
4462
5338
|
this.hostname = opts.hostname ?? os.hostname();
|
|
4463
5339
|
this.maxAgeMs = opts.maxAgeMs ?? DEFAULT_MAX_AGE_MS;
|
|
@@ -4518,7 +5394,7 @@ var RecoveryLock = class {
|
|
|
4518
5394
|
* null return before calling this.
|
|
4519
5395
|
*/
|
|
4520
5396
|
async write(sessionId) {
|
|
4521
|
-
await ensureDir(
|
|
5397
|
+
await ensureDir(path4.dirname(this.file));
|
|
4522
5398
|
const lock = {
|
|
4523
5399
|
v: 1,
|
|
4524
5400
|
sessionId,
|
|
@@ -5283,6 +6159,7 @@ async function savePlan(filePath, plan, events) {
|
|
|
5283
6159
|
outcome: "success",
|
|
5284
6160
|
durationMs: Date.now() - t0
|
|
5285
6161
|
});
|
|
6162
|
+
return true;
|
|
5286
6163
|
} catch (err) {
|
|
5287
6164
|
events?.emit("storage.error", {
|
|
5288
6165
|
sessionId: "~boot~",
|
|
@@ -5296,6 +6173,7 @@ async function savePlan(filePath, plan, events) {
|
|
|
5296
6173
|
"[plan-store] save failed:",
|
|
5297
6174
|
toErrorMessage(err)
|
|
5298
6175
|
);
|
|
6176
|
+
return false;
|
|
5299
6177
|
}
|
|
5300
6178
|
}
|
|
5301
6179
|
function emptyPlan(sessionId, title) {
|
|
@@ -5732,6 +6610,27 @@ var PATTERNS = [
|
|
|
5732
6610
|
{ type: "postgres_uri", regex: /postgres(?:ql)?:\/\/[^\s"'`]+/g },
|
|
5733
6611
|
{ type: "mysql_uri", regex: /mysql:\/\/[^\s"'`]+/g },
|
|
5734
6612
|
{ type: "redis_uri", regex: /redis:\/\/[^\s"'`]+/g },
|
|
6613
|
+
// AI/ML provider keys — modern LLM services with well-known prefixes
|
|
6614
|
+
{
|
|
6615
|
+
type: "huggingface_token",
|
|
6616
|
+
// HuggingFace tokens: hf_ followed by 34 alphanumeric chars
|
|
6617
|
+
regex: /(?<![A-Za-z0-9])hf_[A-Za-z0-9]{34}(?![A-Za-z0-9])/g
|
|
6618
|
+
},
|
|
6619
|
+
{
|
|
6620
|
+
type: "replicate_token",
|
|
6621
|
+
// Replicate tokens: r8_ followed by 40+ alphanumeric chars
|
|
6622
|
+
regex: /(?<![A-Za-z0-9])r8_[A-Za-z0-9]{40,}(?![A-Za-z0-9])/g
|
|
6623
|
+
},
|
|
6624
|
+
{
|
|
6625
|
+
type: "perplexity_key",
|
|
6626
|
+
// Perplexity API keys: pplx- followed by 40+ alphanumeric chars
|
|
6627
|
+
regex: /(?<![A-Za-z0-9])pplx-[A-Za-z0-9]{40,}(?![A-Za-z0-9])/g
|
|
6628
|
+
},
|
|
6629
|
+
{
|
|
6630
|
+
type: "groq_key",
|
|
6631
|
+
// Groq API keys: gsk_ followed by 40+ alphanumeric chars
|
|
6632
|
+
regex: /(?<![A-Za-z0-9])gsk_[A-Za-z0-9]{40,}(?![A-Za-z0-9])/g
|
|
6633
|
+
},
|
|
5735
6634
|
{
|
|
5736
6635
|
type: "bearer_token",
|
|
5737
6636
|
// Anchored with alternation instead of negative lookahead — avoids V8
|
|
@@ -5765,6 +6664,10 @@ function hasCredentialAnchors(text) {
|
|
|
5765
6664
|
text.includes("xox") || // Slack token (xoxa/xoxb/xoxp/xoxo/xoxs)
|
|
5766
6665
|
text.includes("Bearer ") || // Bearer token (space suffix reduces false positives)
|
|
5767
6666
|
text.includes("/bot") || // Telegram bot token (URL path pattern)
|
|
6667
|
+
text.includes("hf_") || // HuggingFace token
|
|
6668
|
+
text.includes("r8_") || // Replicate token
|
|
6669
|
+
text.includes("pplx-") || // Perplexity API key
|
|
6670
|
+
text.includes("gsk_") || // Groq API key
|
|
5768
6671
|
text.includes("_KEY=") || // High-entropy env vars: API_KEY=, SECRET_KEY=, ...
|
|
5769
6672
|
text.includes("_TOKEN=") || // ACCESS_TOKEN=, AUTH_TOKEN=, ...
|
|
5770
6673
|
text.includes("_SECRET=") || // API_SECRET=, CLIENT_SECRET=, ...
|
|
@@ -5911,9 +6814,9 @@ function getInputString(input, key) {
|
|
|
5911
6814
|
function pathLooksInsideProject(rawPath, projectRoot) {
|
|
5912
6815
|
if (!projectRoot) return false;
|
|
5913
6816
|
if (rawPath === "~" || rawPath.startsWith("~/") || rawPath.startsWith("~\\")) return false;
|
|
5914
|
-
const resolved =
|
|
5915
|
-
const
|
|
5916
|
-
return !!
|
|
6817
|
+
const resolved = path4.resolve(projectRoot, rawPath);
|
|
6818
|
+
const relative3 = path4.relative(projectRoot, resolved);
|
|
6819
|
+
return !!relative3 && !relative3.startsWith("..") && !path4.isAbsolute(relative3);
|
|
5917
6820
|
}
|
|
5918
6821
|
function tokenizeShell(command) {
|
|
5919
6822
|
return command.match(/"[^"]*"|'[^']*'|\S+/g)?.map((token) => token.replace(/^['"]|['"]$/g, "")) ?? [];
|
|
@@ -5923,7 +6826,7 @@ function pathTokenIsOutsideProject(token, projectRoot) {
|
|
|
5923
6826
|
if (token === "/" || token === "~" || token === "." || token === "..") return token !== ".";
|
|
5924
6827
|
if (token.includes("*")) return true;
|
|
5925
6828
|
if (token.startsWith("..") || token.includes("../") || token.includes("..\\")) return true;
|
|
5926
|
-
if (
|
|
6829
|
+
if (path4.isAbsolute(token) || token.startsWith("~/")) return !pathLooksInsideProject(token, projectRoot);
|
|
5927
6830
|
return false;
|
|
5928
6831
|
}
|
|
5929
6832
|
function hasDangerousDeleteTarget(tokens, start, projectRoot) {
|
|
@@ -6318,9 +7221,12 @@ var AutoApprovePermissionPolicy = class _AutoApprovePermissionPolicy {
|
|
|
6318
7221
|
const caps = tool.capabilities ?? [];
|
|
6319
7222
|
const hasAllowedCap = caps.some((c) => this.allowedCapabilities.includes(c));
|
|
6320
7223
|
const isMcp = _AutoApprovePermissionPolicy.isMcpTool(tool.name);
|
|
6321
|
-
const
|
|
7224
|
+
const dangerousNotAllowed = getDangerousCapabilities(tool).filter(
|
|
7225
|
+
(c) => !this.allowedCapabilities.includes(c)
|
|
7226
|
+
);
|
|
7227
|
+
const blocked = tool.permission === "deny" || isMcp || !hasAllowedCap || dangerousNotAllowed.length > 0;
|
|
6322
7228
|
if (blocked) {
|
|
6323
|
-
const reason = isMcp ? `MCP tool ${tool.name} is not auto-approved for subagents \u2014 ask the leader to allow it explicitly` : tool.permission === "deny" ? "tool default deny" : `tool lacks allowed capability (has: ${caps.join(", ") || "none"}, allowed: ${this.allowedCapabilities.join(", ")})`;
|
|
7229
|
+
const reason = isMcp ? `MCP tool ${tool.name} is not auto-approved for subagents \u2014 ask the leader to allow it explicitly` : tool.permission === "deny" ? "tool default deny" : dangerousNotAllowed.length > 0 ? `tool requires un-granted dangerous capability (needs: ${dangerousNotAllowed.join(", ")}, allowed: ${this.allowedCapabilities.join(", ")})` : `tool lacks allowed capability (has: ${caps.join(", ") || "none"}, allowed: ${this.allowedCapabilities.join(", ")})`;
|
|
6324
7230
|
return {
|
|
6325
7231
|
permission: "deny",
|
|
6326
7232
|
source: "subagent_guard",
|
|
@@ -6580,7 +7486,7 @@ var DefaultSkillLoader = class {
|
|
|
6580
7486
|
const entries = await fsp2.readdir(dir, { withFileTypes: true });
|
|
6581
7487
|
for (const e of entries) {
|
|
6582
7488
|
if (!e.isDirectory()) continue;
|
|
6583
|
-
const skillFile =
|
|
7489
|
+
const skillFile = path4.join(dir, e.name, "SKILL.md");
|
|
6584
7490
|
try {
|
|
6585
7491
|
const raw = await fsp2.readFile(skillFile, "utf8");
|
|
6586
7492
|
const meta = parseFrontmatter(raw);
|
|
@@ -6605,12 +7511,12 @@ var DefaultSkillLoader = class {
|
|
|
6605
7511
|
}
|
|
6606
7512
|
async find(name) {
|
|
6607
7513
|
const all = await this.list();
|
|
6608
|
-
|
|
7514
|
+
const lower = name.toLowerCase();
|
|
7515
|
+
return all.find((s) => s.name.toLowerCase() === lower);
|
|
6609
7516
|
}
|
|
6610
7517
|
async manifestText() {
|
|
6611
|
-
const skills = await this.list();
|
|
6612
|
-
if (skills.length === 0) return "";
|
|
6613
7518
|
const entries = await this.listEntries();
|
|
7519
|
+
if (entries.length === 0) return "";
|
|
6614
7520
|
const lines = ["## Available skills"];
|
|
6615
7521
|
for (const e of entries) {
|
|
6616
7522
|
const scopeTag = e.scope.length > 0 ? ` \u2014 ${e.scope.slice(0, 3).join(", ")}` : "";
|
|
@@ -6624,12 +7530,8 @@ var DefaultSkillLoader = class {
|
|
|
6624
7530
|
const skills = await this.list();
|
|
6625
7531
|
const entries = [];
|
|
6626
7532
|
for (const s of skills) {
|
|
6627
|
-
|
|
6628
|
-
|
|
6629
|
-
const { trigger, scope } = parseDescription(raw);
|
|
6630
|
-
entries.push({ name: s.name, trigger, scope, source: s.source, path: s.path });
|
|
6631
|
-
} catch {
|
|
6632
|
-
}
|
|
7533
|
+
const { trigger, scope } = parseDescriptionFromText(s.description ?? "");
|
|
7534
|
+
entries.push({ name: s.name, trigger, scope, source: s.source, path: s.path });
|
|
6633
7535
|
}
|
|
6634
7536
|
this.entriesCache = entries;
|
|
6635
7537
|
return entries;
|
|
@@ -6640,21 +7542,22 @@ var DefaultSkillLoader = class {
|
|
|
6640
7542
|
this.bodyCache.clear();
|
|
6641
7543
|
}
|
|
6642
7544
|
async readBody(name) {
|
|
6643
|
-
const
|
|
7545
|
+
const key = name.toLowerCase();
|
|
7546
|
+
const cached = this.bodyCache.get(key);
|
|
6644
7547
|
if (cached !== void 0) return cached;
|
|
6645
7548
|
const m = await this.find(name);
|
|
6646
7549
|
if (!m) throw new Error(`Skill "${name}" not found`);
|
|
6647
7550
|
const body = await fsp2.readFile(m.path, "utf8");
|
|
6648
|
-
this.bodyCache.set(
|
|
7551
|
+
this.bodyCache.set(key, body);
|
|
6649
7552
|
return body;
|
|
6650
7553
|
}
|
|
6651
7554
|
async readSaveBody(name) {
|
|
6652
|
-
const key = `save:${name}`;
|
|
7555
|
+
const key = `save:${name.toLowerCase()}`;
|
|
6653
7556
|
const cached = this.bodyCache.get(key);
|
|
6654
7557
|
if (cached !== void 0) return cached;
|
|
6655
7558
|
const m = await this.find(name);
|
|
6656
7559
|
if (!m) throw new Error(`Skill "${name}" not found`);
|
|
6657
|
-
const savePath =
|
|
7560
|
+
const savePath = path4.join(path4.dirname(m.path), "SKILL.save.md");
|
|
6658
7561
|
let result;
|
|
6659
7562
|
try {
|
|
6660
7563
|
result = await fsp2.readFile(savePath, "utf8");
|
|
@@ -6710,9 +7613,7 @@ function parseFrontmatter(raw) {
|
|
|
6710
7613
|
flush();
|
|
6711
7614
|
return out;
|
|
6712
7615
|
}
|
|
6713
|
-
function
|
|
6714
|
-
const fm = parseFrontmatter(raw);
|
|
6715
|
-
const desc = fm.description ?? "";
|
|
7616
|
+
function parseDescriptionFromText(desc) {
|
|
6716
7617
|
const firstSentenceEnd = desc.indexOf(". ");
|
|
6717
7618
|
const trigger = firstSentenceEnd !== -1 ? desc.slice(0, firstSentenceEnd + 1).trim() : desc.trim().split("\n")[0] ?? "";
|
|
6718
7619
|
const scope = [];
|
|
@@ -6970,8 +7871,8 @@ async function streamProviderToResponse(provider, req, signal, ctx, events, logg
|
|
|
6970
7871
|
});
|
|
6971
7872
|
await Promise.race([
|
|
6972
7873
|
drainPromise,
|
|
6973
|
-
new Promise((
|
|
6974
|
-
drainTimer = setTimeout(
|
|
7874
|
+
new Promise((resolve6) => {
|
|
7875
|
+
drainTimer = setTimeout(resolve6, STREAM_DRAIN_TIMEOUT_MS);
|
|
6975
7876
|
})
|
|
6976
7877
|
]);
|
|
6977
7878
|
} finally {
|
|
@@ -7077,26 +7978,29 @@ async function runProviderWithRetry(opts) {
|
|
|
7077
7978
|
description
|
|
7078
7979
|
});
|
|
7079
7980
|
}
|
|
7080
|
-
await new Promise((
|
|
7981
|
+
await new Promise((resolve6, reject) => {
|
|
7081
7982
|
let settled = false;
|
|
7983
|
+
const cleanup = () => {
|
|
7984
|
+
clearTimeout(t);
|
|
7985
|
+
signal.removeEventListener("abort", onAbort);
|
|
7986
|
+
};
|
|
7082
7987
|
const onAbort = () => {
|
|
7083
7988
|
if (settled) return;
|
|
7084
7989
|
settled = true;
|
|
7085
|
-
|
|
7990
|
+
cleanup();
|
|
7086
7991
|
reject(new Error("aborted"));
|
|
7087
7992
|
};
|
|
7088
7993
|
const t = setTimeout(() => {
|
|
7089
7994
|
if (settled) return;
|
|
7090
7995
|
settled = true;
|
|
7091
|
-
|
|
7092
|
-
|
|
7093
|
-
resolve5();
|
|
7996
|
+
cleanup();
|
|
7997
|
+
resolve6();
|
|
7094
7998
|
}, delay);
|
|
7095
7999
|
if (signal.aborted) {
|
|
7096
8000
|
onAbort();
|
|
7097
8001
|
return;
|
|
7098
8002
|
}
|
|
7099
|
-
signal.addEventListener("abort", onAbort
|
|
8003
|
+
signal.addEventListener("abort", onAbort);
|
|
7100
8004
|
});
|
|
7101
8005
|
attempt++;
|
|
7102
8006
|
}
|
|
@@ -7116,7 +8020,11 @@ function isTextBlock(b) {
|
|
|
7116
8020
|
}
|
|
7117
8021
|
|
|
7118
8022
|
// src/execution/compaction-core.ts
|
|
8023
|
+
function compactionDebugEnabled() {
|
|
8024
|
+
return process.env["NODE_ENV"] === "development" || process.env["WRONGSTACK_DEBUG"] === "1";
|
|
8025
|
+
}
|
|
7119
8026
|
function emitCompactionMetrics(event, metrics) {
|
|
8027
|
+
if (!compactionDebugEnabled()) return;
|
|
7120
8028
|
console.log(
|
|
7121
8029
|
JSON.stringify({
|
|
7122
8030
|
level: "debug",
|
|
@@ -7151,38 +8059,41 @@ function findPreserveStart(messages, preserveK) {
|
|
|
7151
8059
|
preserveStart = i;
|
|
7152
8060
|
}
|
|
7153
8061
|
}
|
|
7154
|
-
let
|
|
7155
|
-
let
|
|
7156
|
-
|
|
7157
|
-
|
|
7158
|
-
const
|
|
7159
|
-
|
|
7160
|
-
|
|
7161
|
-
|
|
7162
|
-
|
|
8062
|
+
let pairRepairIterations = 0;
|
|
8063
|
+
let pairRepairInnerIterations = 0;
|
|
8064
|
+
while (preserveStart > 0) {
|
|
8065
|
+
pairRepairIterations++;
|
|
8066
|
+
const first = messages[preserveStart];
|
|
8067
|
+
const prev = messages[preserveStart - 1];
|
|
8068
|
+
if (!first || !prev || first.role !== "user" || prev.role !== "assistant") break;
|
|
8069
|
+
if (typeof first.content === "string" || typeof prev.content === "string") break;
|
|
8070
|
+
const resultIds = /* @__PURE__ */ new Set();
|
|
8071
|
+
for (const block of first.content) {
|
|
8072
|
+
pairRepairInnerIterations++;
|
|
8073
|
+
if (block.type === "tool_result") resultIds.add(block.tool_use_id);
|
|
8074
|
+
}
|
|
8075
|
+
if (resultIds.size === 0) break;
|
|
8076
|
+
const hasMatchingUse = prev.content.some((block) => {
|
|
8077
|
+
pairRepairInnerIterations++;
|
|
8078
|
+
return block.type === "tool_use" && resultIds.has(block.id);
|
|
7163
8079
|
});
|
|
7164
|
-
if (
|
|
7165
|
-
|
|
7166
|
-
|
|
7167
|
-
|
|
7168
|
-
|
|
7169
|
-
|
|
7170
|
-
|
|
7171
|
-
|
|
7172
|
-
|
|
8080
|
+
if (!hasMatchingUse) break;
|
|
8081
|
+
preserveStart--;
|
|
8082
|
+
}
|
|
8083
|
+
if (compactionDebugEnabled()) {
|
|
8084
|
+
console.log(
|
|
8085
|
+
JSON.stringify({
|
|
8086
|
+
level: "debug",
|
|
8087
|
+
event: "compaction.find_preserve_start.ended",
|
|
8088
|
+
messageCount: messages.length,
|
|
8089
|
+
preserveK,
|
|
8090
|
+
preserveStart,
|
|
8091
|
+
pairRepairIterations,
|
|
8092
|
+
pairRepairInnerIterations,
|
|
8093
|
+
pairRepairInnerPerOuter: pairRepairIterations > 0 ? pairRepairInnerIterations / pairRepairIterations : 0
|
|
8094
|
+
})
|
|
8095
|
+
);
|
|
7173
8096
|
}
|
|
7174
|
-
console.log(
|
|
7175
|
-
JSON.stringify({
|
|
7176
|
-
level: "debug",
|
|
7177
|
-
event: "compaction.find_preserve_start.ended",
|
|
7178
|
-
messageCount: messages.length,
|
|
7179
|
-
preserveK,
|
|
7180
|
-
preserveStart,
|
|
7181
|
-
forwardWalkIterations,
|
|
7182
|
-
forwardWalkInnerIterations,
|
|
7183
|
-
forwardWalkInnerPerOuter: forwardWalkIterations > 0 ? forwardWalkInnerIterations / forwardWalkIterations : 0
|
|
7184
|
-
})
|
|
7185
|
-
);
|
|
7186
8097
|
return preserveStart;
|
|
7187
8098
|
}
|
|
7188
8099
|
function eliseOldToolResults(messages, opts) {
|
|
@@ -7196,7 +8107,8 @@ function eliseOldToolResults(messages, opts) {
|
|
|
7196
8107
|
if (!msg || !Array.isArray(msg.content)) continue;
|
|
7197
8108
|
for (const b of msg.content) {
|
|
7198
8109
|
fastPathInnerIterations++;
|
|
7199
|
-
|
|
8110
|
+
const oversized = b.type === "tool_result" && estimateToolResultTokens(b.content) >= opts.eliseThreshold || b.type === "tool_use" && estimateToolInputTokens(b.input) >= opts.eliseThreshold;
|
|
8111
|
+
if (oversized) {
|
|
7200
8112
|
hasOversized = true;
|
|
7201
8113
|
break;
|
|
7202
8114
|
}
|
|
@@ -7230,6 +8142,13 @@ function eliseOldToolResults(messages, opts) {
|
|
|
7230
8142
|
}
|
|
7231
8143
|
const original = msg.content;
|
|
7232
8144
|
const newContent = original.map((b) => {
|
|
8145
|
+
if (b.type === "tool_use") {
|
|
8146
|
+
const tokens2 = estimateToolInputTokens(b.input);
|
|
8147
|
+
if (tokens2 < opts.eliseThreshold) return b;
|
|
8148
|
+
const elidedInput = summarizeToolUseInputElision(b, tokens2);
|
|
8149
|
+
saved += Math.max(0, tokens2 - estimateToolInputTokens(elidedInput));
|
|
8150
|
+
return { ...b, input: elidedInput };
|
|
8151
|
+
}
|
|
7233
8152
|
if (b.type !== "tool_result") return b;
|
|
7234
8153
|
const tokens = estimateToolResultTokens(b.content);
|
|
7235
8154
|
if (tokens < opts.eliseThreshold) return b;
|
|
@@ -7237,7 +8156,7 @@ function eliseOldToolResults(messages, opts) {
|
|
|
7237
8156
|
const elided = {
|
|
7238
8157
|
type: "tool_result",
|
|
7239
8158
|
tool_use_id: b.tool_use_id,
|
|
7240
|
-
content:
|
|
8159
|
+
content: summarizeToolResultElision(b, tokens),
|
|
7241
8160
|
is_error: b.is_error
|
|
7242
8161
|
};
|
|
7243
8162
|
return elided;
|
|
@@ -7249,7 +8168,7 @@ function eliseOldToolResults(messages, opts) {
|
|
|
7249
8168
|
changed = true;
|
|
7250
8169
|
}
|
|
7251
8170
|
fullPassInnerIterations += original.length;
|
|
7252
|
-
if (
|
|
8171
|
+
if (compactionDebugEnabled()) {
|
|
7253
8172
|
const ratio = fullPassInnerIterations / fullPassIterations;
|
|
7254
8173
|
if (ratio > 10) {
|
|
7255
8174
|
console.error(
|
|
@@ -7277,6 +8196,65 @@ function eliseOldToolResults(messages, opts) {
|
|
|
7277
8196
|
});
|
|
7278
8197
|
return { messages: changed ? next : messages, saved, changed };
|
|
7279
8198
|
}
|
|
8199
|
+
function summarizeToolUseInputElision(block, tokens) {
|
|
8200
|
+
const fields = {};
|
|
8201
|
+
for (const [key, value] of Object.entries(block.input ?? {})) {
|
|
8202
|
+
fields[key] = summarizeToolUseInputValue(value);
|
|
8203
|
+
}
|
|
8204
|
+
return {
|
|
8205
|
+
__elided_tool_input: `~${tokens} tokens; original arguments are in the session log`,
|
|
8206
|
+
tool: block.name,
|
|
8207
|
+
fields
|
|
8208
|
+
};
|
|
8209
|
+
}
|
|
8210
|
+
function summarizeToolUseInputValue(value) {
|
|
8211
|
+
if (value === null || value === void 0) return value;
|
|
8212
|
+
if (typeof value === "number" || typeof value === "boolean") return value;
|
|
8213
|
+
if (typeof value === "string") {
|
|
8214
|
+
const oneLine = value.replace(/\s+/g, " ").trim();
|
|
8215
|
+
return oneLine.length <= 160 ? oneLine : `${oneLine.slice(0, 120)}...(${oneLine.length} chars)`;
|
|
8216
|
+
}
|
|
8217
|
+
if (Array.isArray(value)) {
|
|
8218
|
+
return `[array:${value.length}]`;
|
|
8219
|
+
}
|
|
8220
|
+
if (typeof value === "object") {
|
|
8221
|
+
const keys = Object.keys(value);
|
|
8222
|
+
return `[object:${keys.slice(0, 8).join(",")}${keys.length > 8 ? ",..." : ""}]`;
|
|
8223
|
+
}
|
|
8224
|
+
return String(value);
|
|
8225
|
+
}
|
|
8226
|
+
function summarizeToolResultElision(block, tokens) {
|
|
8227
|
+
const parts = [`elided: ~${tokens} tokens`];
|
|
8228
|
+
if (block.name) parts.push(`tool=${block.name}`);
|
|
8229
|
+
const files = extractPathHints(block.content).slice(0, 5);
|
|
8230
|
+
if (files.length > 0) parts.push(`files=${files.join(", ")}`);
|
|
8231
|
+
const error = firstErrorLine(block.content);
|
|
8232
|
+
if (error) parts.push(`error=${error}`);
|
|
8233
|
+
return `[${parts.join("; ")}]`;
|
|
8234
|
+
}
|
|
8235
|
+
function extractPathHints(content) {
|
|
8236
|
+
const text = typeof content === "string" ? content : JSON.stringify(content);
|
|
8237
|
+
const out = /* @__PURE__ */ new Set();
|
|
8238
|
+
const re = /(?:(?:[A-Za-z]:)?[./\\]?[\w@.-]+(?:[\\/][\w@(). -]+)+\.[A-Za-z0-9]{1,12})/g;
|
|
8239
|
+
for (const match of text.matchAll(re)) {
|
|
8240
|
+
const clean = match[0]?.replace(/\\/g, "/").replace(/^["'`]+|["'`),;:]+$/g, "");
|
|
8241
|
+
if (clean && clean.length <= 220) out.add(clean);
|
|
8242
|
+
if (out.size >= 5) break;
|
|
8243
|
+
}
|
|
8244
|
+
return [...out];
|
|
8245
|
+
}
|
|
8246
|
+
function firstErrorLine(content) {
|
|
8247
|
+
const text = typeof content === "string" ? content : JSON.stringify(content);
|
|
8248
|
+
for (const line of text.split(/\r?\n/)) {
|
|
8249
|
+
if (!/\b(error|exception|failed|failure|fatal|panic|timeout|denied|enoent|eacces|eperm)\b/i.test(
|
|
8250
|
+
line
|
|
8251
|
+
))
|
|
8252
|
+
continue;
|
|
8253
|
+
const trimmed = line.replace(/\s+/g, " ").trim();
|
|
8254
|
+
if (trimmed) return trimmed.slice(0, 180);
|
|
8255
|
+
}
|
|
8256
|
+
return void 0;
|
|
8257
|
+
}
|
|
7280
8258
|
function buildLosslessDigest(messages) {
|
|
7281
8259
|
const lines = [];
|
|
7282
8260
|
for (const m of messages) {
|
|
@@ -7387,15 +8365,15 @@ function buildSmartDigest(messages) {
|
|
|
7387
8365
|
lines.push(`[${m.role}]: ${display}${marker}`);
|
|
7388
8366
|
}
|
|
7389
8367
|
if (noiseCount > 0) {
|
|
7390
|
-
lines.push(
|
|
8368
|
+
lines.push(
|
|
8369
|
+
`[system]: ${noiseCount} low-importance turn(s) collapsed (repeated failures / pure tool I/O)`
|
|
8370
|
+
);
|
|
7391
8371
|
}
|
|
7392
8372
|
return lines.join("\n");
|
|
7393
8373
|
}
|
|
7394
8374
|
function countToolBlocks(m) {
|
|
7395
8375
|
if (typeof m.content === "string") return 0;
|
|
7396
|
-
return m.content.filter(
|
|
7397
|
-
(b) => b.type === "tool_use" || b.type === "tool_result"
|
|
7398
|
-
).length;
|
|
8376
|
+
return m.content.filter((b) => b.type === "tool_use" || b.type === "tool_result").length;
|
|
7399
8377
|
}
|
|
7400
8378
|
function firstSentence(text) {
|
|
7401
8379
|
const trimmed = text.trim();
|
|
@@ -7464,10 +8442,12 @@ var HybridCompactor = class {
|
|
|
7464
8442
|
if (elide.changed) ctx.state.replaceMessages(elide.messages);
|
|
7465
8443
|
if (elide.saved > 0) reductions.push({ phase: "elision", saved: elide.saved });
|
|
7466
8444
|
let collapsedDigest;
|
|
8445
|
+
let evidenceDigest;
|
|
7467
8446
|
if (opts.aggressive) {
|
|
7468
8447
|
const phase2 = this.collapseAncientTurns(ctx, preserveK);
|
|
7469
8448
|
if (phase2.saved > 0) reductions.push({ phase: "summary", saved: phase2.saved });
|
|
7470
8449
|
collapsedDigest = phase2.digest;
|
|
8450
|
+
evidenceDigest = phase2.evidenceDigest;
|
|
7471
8451
|
}
|
|
7472
8452
|
const repaired = repairToolUseAdjacency(ctx.messages);
|
|
7473
8453
|
if (repaired.report.changed) {
|
|
@@ -7475,6 +8455,11 @@ var HybridCompactor = class {
|
|
|
7475
8455
|
}
|
|
7476
8456
|
const afterTokens = estimateMessages(ctx.messages);
|
|
7477
8457
|
const afterFull = this.estimateFullRequest(ctx);
|
|
8458
|
+
const quality = checkCompactionQuality(ctx, {
|
|
8459
|
+
collapsedDigest,
|
|
8460
|
+
evidenceDigest,
|
|
8461
|
+
reduced: beforeTokens > afterTokens || beforeFull > afterFull
|
|
8462
|
+
});
|
|
7478
8463
|
return {
|
|
7479
8464
|
before: beforeTokens,
|
|
7480
8465
|
after: afterTokens,
|
|
@@ -7482,6 +8467,8 @@ var HybridCompactor = class {
|
|
|
7482
8467
|
fullRequestTokensAfter: afterFull,
|
|
7483
8468
|
reductions,
|
|
7484
8469
|
collapsedDigest,
|
|
8470
|
+
evidenceDigest,
|
|
8471
|
+
quality,
|
|
7485
8472
|
repaired: repaired.report.changed ? {
|
|
7486
8473
|
removedToolUses: repaired.report.removedToolUses,
|
|
7487
8474
|
removedToolResults: repaired.report.removedToolResults,
|
|
@@ -7523,7 +8510,13 @@ var HybridCompactor = class {
|
|
|
7523
8510
|
if (boundary <= 0) return { saved: 0 };
|
|
7524
8511
|
const removed = messages.slice(0, boundary);
|
|
7525
8512
|
const removedTokens = estimateMessages(removed);
|
|
7526
|
-
const
|
|
8513
|
+
const historyDigest = this.smart ? buildSmartDigest(removed) || `${removed.length} earlier turns (no textual content; tool I/O omitted; see session log)` : buildLosslessDigest(removed) || `${removed.length} earlier turns (no textual content; tool I/O omitted; see session log)`;
|
|
8514
|
+
const evidenceDigest = buildContextEvidenceDigest(ctx);
|
|
8515
|
+
const digest = evidenceDigest ? `[context_state]
|
|
8516
|
+
${evidenceDigest}
|
|
8517
|
+
|
|
8518
|
+
[prior_history]
|
|
8519
|
+
${historyDigest}` : historyDigest;
|
|
7527
8520
|
const summaryMsg = {
|
|
7528
8521
|
role: "system",
|
|
7529
8522
|
content: `[prior_turns_digest: ${digest}]`
|
|
@@ -7532,10 +8525,29 @@ var HybridCompactor = class {
|
|
|
7532
8525
|
ctx.state.replaceMessages([summaryMsg, ...tail]);
|
|
7533
8526
|
return {
|
|
7534
8527
|
saved: Math.max(0, removedTokens - estimateMessages([summaryMsg])),
|
|
7535
|
-
digest
|
|
8528
|
+
digest,
|
|
8529
|
+
evidenceDigest: evidenceDigest || void 0
|
|
7536
8530
|
};
|
|
7537
8531
|
}
|
|
7538
8532
|
};
|
|
8533
|
+
function checkCompactionQuality(ctx, opts) {
|
|
8534
|
+
const evidence = ctx.contextEvidence;
|
|
8535
|
+
const digest = `${opts.collapsedDigest ?? ""}
|
|
8536
|
+
${opts.evidenceDigest ?? ""}`;
|
|
8537
|
+
const hasIntent = Boolean(evidence?.currentIntent?.text || /\b(intent|goal|session_goals)\b/i.test(digest));
|
|
8538
|
+
const hasPathTrail = Boolean(
|
|
8539
|
+
Object.keys(evidence?.fileGraph ?? {}).length > 0 || (evidence?.toolCalls.length ?? 0) > 0 || /\b(dependency_graph|tool_trail|files=)\b/i.test(digest)
|
|
8540
|
+
);
|
|
8541
|
+
const issues = [];
|
|
8542
|
+
if (opts.reduced && !hasIntent) issues.push("missing intent anchor");
|
|
8543
|
+
if (opts.reduced && !hasPathTrail) issues.push("missing tool/path trail");
|
|
8544
|
+
return {
|
|
8545
|
+
ok: issues.length === 0,
|
|
8546
|
+
hasIntent,
|
|
8547
|
+
hasPathTrail,
|
|
8548
|
+
issues
|
|
8549
|
+
};
|
|
8550
|
+
}
|
|
7539
8551
|
function readContextWindowPolicy(ctx) {
|
|
7540
8552
|
const policy = ctx.meta?.["contextWindowPolicy"];
|
|
7541
8553
|
if (!policy || typeof policy !== "object") return null;
|
|
@@ -7656,7 +8668,12 @@ var IntelligentCompactor = class {
|
|
|
7656
8668
|
};
|
|
7657
8669
|
const ac = ctx.signal ? void 0 : new AbortController();
|
|
7658
8670
|
const signal = ctx.signal ?? ac?.signal;
|
|
7659
|
-
|
|
8671
|
+
let res;
|
|
8672
|
+
try {
|
|
8673
|
+
res = await this.provider.complete(req, { signal });
|
|
8674
|
+
} finally {
|
|
8675
|
+
ac?.abort();
|
|
8676
|
+
}
|
|
7660
8677
|
const textBlocks = res.content.filter(isTextBlock);
|
|
7661
8678
|
return textBlocks.map((b) => b.text).join("\n").trim() || "(empty summary)";
|
|
7662
8679
|
}
|
|
@@ -7700,9 +8717,9 @@ Rules:
|
|
|
7700
8717
|
- If unsure, keep rather than collapse (errors are more costly than waste)
|
|
7701
8718
|
|
|
7702
8719
|
Return ONLY the JSON object, no markdown, no explanation outside the JSON.`;
|
|
7703
|
-
function formatMessages(messages,
|
|
8720
|
+
function formatMessages(messages, maxTokens = 2048) {
|
|
7704
8721
|
const lines = [];
|
|
7705
|
-
let
|
|
8722
|
+
let usedTokens = 0;
|
|
7706
8723
|
for (let i = 0; i < messages.length; i++) {
|
|
7707
8724
|
const m = expectDefined(messages[i]);
|
|
7708
8725
|
const role = m.role.padEnd(10, " ");
|
|
@@ -7714,13 +8731,14 @@ function formatMessages(messages, maxChars = 8e3) {
|
|
|
7714
8731
|
text = content.filter(isTextBlock).map((b) => b.text).join(" ");
|
|
7715
8732
|
const toolUses = content.filter((b) => b.type === "tool_use");
|
|
7716
8733
|
if (toolUses.length > 0) {
|
|
7717
|
-
text += ` [tools: ${toolUses.map((b) => b.name).join(", ")}]`;
|
|
8734
|
+
text += ` [tools: ${toolUses.map((b) => b.name).filter(Boolean).join(", ")}]`;
|
|
7718
8735
|
}
|
|
7719
8736
|
}
|
|
7720
8737
|
const line = `[${i}][${role}]: ${text}`;
|
|
7721
|
-
|
|
8738
|
+
const lineTokens = estimateTextTokens(line);
|
|
8739
|
+
if (usedTokens + lineTokens > maxTokens) break;
|
|
7722
8740
|
lines.push(line);
|
|
7723
|
-
|
|
8741
|
+
usedTokens += lineTokens;
|
|
7724
8742
|
}
|
|
7725
8743
|
return lines.join("\n");
|
|
7726
8744
|
}
|
|
@@ -7729,20 +8747,29 @@ var LLMSelector = class {
|
|
|
7729
8747
|
model;
|
|
7730
8748
|
maxContextTokens;
|
|
7731
8749
|
systemPrompt;
|
|
8750
|
+
maxOutputTokens;
|
|
7732
8751
|
constructor(opts) {
|
|
7733
8752
|
this.provider = opts.provider;
|
|
7734
8753
|
this.model = opts.model ?? "unknown";
|
|
8754
|
+
if (this.model === "unknown" && (process.env["NODE_ENV"] === "development" || process.env["WRONGSTACK_DEBUG"] === "1")) {
|
|
8755
|
+
console.warn(
|
|
8756
|
+
"[LLMSelector] model not set \u2014 selector will use the provider default. Set `model` explicitly in LLMSelectorOptions to silence this warning."
|
|
8757
|
+
);
|
|
8758
|
+
}
|
|
7735
8759
|
this.maxContextTokens = opts.maxContextTokens ?? 4e4;
|
|
7736
8760
|
this.systemPrompt = opts.systemPrompt ?? DEFAULT_SYSTEM_PROMPT;
|
|
8761
|
+
this.maxOutputTokens = opts.maxOutputTokens ?? 1024;
|
|
7737
8762
|
}
|
|
7738
8763
|
async select(messages, maxToKeep) {
|
|
7739
8764
|
const effectiveBudget = Math.min(maxToKeep, this.maxContextTokens);
|
|
7740
|
-
const historyText = formatMessages(messages);
|
|
7741
8765
|
const totalTokens = estimateMessageTokens(messages);
|
|
7742
8766
|
const systemText = `${this.systemPrompt}
|
|
7743
8767
|
|
|
7744
8768
|
Conversation (${messages.length} messages, ~${totalTokens} tokens, budget: ${effectiveBudget}):
|
|
7745
8769
|
`;
|
|
8770
|
+
const systemTokens = estimateTextTokens(systemText);
|
|
8771
|
+
const historyBudget = Math.max(512, effectiveBudget - systemTokens - this.maxOutputTokens);
|
|
8772
|
+
const historyText = formatMessages(messages, historyBudget);
|
|
7746
8773
|
const budgetInstruction = totalTokens > effectiveBudget ? `
|
|
7747
8774
|
|
|
7748
8775
|
IMPORTANT: Total conversation (${totalTokens} tokens) exceeds budget (${effectiveBudget}). You MUST collapse enough to fit. Prefer collapsing older/lower-importance ranges.` : "";
|
|
@@ -7750,18 +8777,26 @@ IMPORTANT: Total conversation (${totalTokens} tokens) exceeds budget (${effectiv
|
|
|
7750
8777
|
model: this.model,
|
|
7751
8778
|
system: [{ type: "text", text: systemText + budgetInstruction }],
|
|
7752
8779
|
messages: [{ role: "user", content: historyText }],
|
|
7753
|
-
maxTokens:
|
|
8780
|
+
maxTokens: this.maxOutputTokens
|
|
7754
8781
|
};
|
|
7755
8782
|
let raw;
|
|
8783
|
+
const ac = new AbortController();
|
|
7756
8784
|
try {
|
|
7757
|
-
const
|
|
7758
|
-
const res = await this.provider.complete(req, {
|
|
8785
|
+
const timeoutSignal = AbortSignal.timeout(3e4);
|
|
8786
|
+
const res = await this.provider.complete(req, {
|
|
8787
|
+
signal: AbortSignal.any([ac.signal, timeoutSignal])
|
|
8788
|
+
});
|
|
7759
8789
|
const textBlocks = res.content.filter(isTextBlock);
|
|
7760
8790
|
raw = textBlocks.map((b) => b.text).join("\n").trim();
|
|
7761
|
-
} catch (
|
|
8791
|
+
} catch (err) {
|
|
8792
|
+
if (err instanceof Error) {
|
|
8793
|
+
console.warn("[LLMSelector] selector call failed, using recency fallback:", err.message);
|
|
8794
|
+
}
|
|
7762
8795
|
return this.fallbackSelect(messages, effectiveBudget);
|
|
8796
|
+
} finally {
|
|
8797
|
+
ac.abort();
|
|
7763
8798
|
}
|
|
7764
|
-
return this.parseSelectorOutput(raw, messages
|
|
8799
|
+
return this.parseSelectorOutput(raw, messages);
|
|
7765
8800
|
}
|
|
7766
8801
|
fallbackSelect(messages, budget) {
|
|
7767
8802
|
const toKeep = [];
|
|
@@ -7788,34 +8823,63 @@ IMPORTANT: Total conversation (${totalTokens} tokens) exceeds budget (${effectiv
|
|
|
7788
8823
|
reasoning: `Fallback: kept last ${messages.length - startIdx} messages within ${budget} token budget`
|
|
7789
8824
|
};
|
|
7790
8825
|
}
|
|
7791
|
-
|
|
8826
|
+
/**
|
|
8827
|
+
* Parse and validate the raw LLM output into a SelectorResult.
|
|
8828
|
+
* Falls back to recency-based selection if the LLM output is malformed,
|
|
8829
|
+
* out-of-bounds, or internally inconsistent.
|
|
8830
|
+
*/
|
|
8831
|
+
parseSelectorOutput(raw, messages) {
|
|
8832
|
+
const messageCount = messages.length;
|
|
8833
|
+
if (messageCount === 0) {
|
|
8834
|
+
return { kept: [], collapsed: [], reasoning: "empty session" };
|
|
8835
|
+
}
|
|
7792
8836
|
const jsonStart = raw.indexOf("{");
|
|
7793
8837
|
const jsonEnd = raw.lastIndexOf("}");
|
|
7794
8838
|
if (jsonStart === -1 || jsonEnd === -1) {
|
|
7795
|
-
return this.fallbackSelect(
|
|
7796
|
-
Array.from({ length: messageCount }, () => ({ role: "user", content: "" })),
|
|
7797
|
-
this.maxContextTokens
|
|
7798
|
-
);
|
|
8839
|
+
return this.fallbackSelect(messages, this.maxContextTokens);
|
|
7799
8840
|
}
|
|
7800
8841
|
let parsed;
|
|
7801
8842
|
try {
|
|
7802
8843
|
parsed = JSON.parse(raw.slice(jsonStart, jsonEnd + 1));
|
|
7803
8844
|
} catch {
|
|
7804
|
-
return this.fallbackSelect(
|
|
7805
|
-
Array.from({ length: messageCount }, () => ({ role: "user", content: "" })),
|
|
7806
|
-
this.maxContextTokens
|
|
7807
|
-
);
|
|
8845
|
+
return this.fallbackSelect(messages, this.maxContextTokens);
|
|
7808
8846
|
}
|
|
7809
8847
|
const obj = parsed;
|
|
7810
|
-
const
|
|
7811
|
-
const
|
|
7812
|
-
|
|
7813
|
-
|
|
8848
|
+
const keptRaw = obj.kept ?? [];
|
|
8849
|
+
const collapsedRaw = obj.collapsed ?? [];
|
|
8850
|
+
const kept = [];
|
|
8851
|
+
for (const k of keptRaw) {
|
|
8852
|
+
if (typeof k.from !== "number" || typeof k.to !== "number" || k.from < 0 || k.to >= messageCount || k.from > k.to) {
|
|
8853
|
+
return this.fallbackSelect(messages, this.maxContextTokens);
|
|
8854
|
+
}
|
|
8855
|
+
kept.push({
|
|
7814
8856
|
from: k.from,
|
|
7815
8857
|
to: k.to,
|
|
7816
8858
|
importance: k.importance ?? "medium"
|
|
7817
|
-
})
|
|
7818
|
-
|
|
8859
|
+
});
|
|
8860
|
+
}
|
|
8861
|
+
const collapsed = [];
|
|
8862
|
+
for (const c of collapsedRaw) {
|
|
8863
|
+
if (typeof c.from !== "number" || typeof c.to !== "number" || c.from < 0 || c.to >= messageCount || c.from > c.to) {
|
|
8864
|
+
return this.fallbackSelect(messages, this.maxContextTokens);
|
|
8865
|
+
}
|
|
8866
|
+
collapsed.push({ from: c.from, to: c.to, summary: c.summary });
|
|
8867
|
+
}
|
|
8868
|
+
const allRanges = [...kept, ...collapsed];
|
|
8869
|
+
for (let i = 0; i < allRanges.length; i++) {
|
|
8870
|
+
const a = allRanges[i];
|
|
8871
|
+
if (!a) continue;
|
|
8872
|
+
for (let j = i + 1; j < allRanges.length; j++) {
|
|
8873
|
+
const b = allRanges[j];
|
|
8874
|
+
if (!b) continue;
|
|
8875
|
+
if (a.from <= b.to && a.to >= b.from) {
|
|
8876
|
+
return this.fallbackSelect(messages, this.maxContextTokens);
|
|
8877
|
+
}
|
|
8878
|
+
}
|
|
8879
|
+
}
|
|
8880
|
+
return {
|
|
8881
|
+
kept,
|
|
8882
|
+
collapsed,
|
|
7819
8883
|
reasoning: typeof obj.reasoning === "string" ? obj.reasoning : ""
|
|
7820
8884
|
};
|
|
7821
8885
|
}
|
|
@@ -7835,14 +8899,19 @@ var SelectiveCompactor = class {
|
|
|
7835
8899
|
summarizerPrompt;
|
|
7836
8900
|
constructor(opts) {
|
|
7837
8901
|
this.provider = opts.provider;
|
|
7838
|
-
this.selector = opts.selector ?? new LLMSelector({ provider: opts.provider, model: opts.selectorModel });
|
|
8902
|
+
this.selector = opts.selector ?? new LLMSelector({ provider: opts.provider, model: opts.selectorModel, maxOutputTokens: opts.selectorMaxOutputTokens });
|
|
7839
8903
|
this.warnThreshold = opts.warnThreshold ?? 0.6;
|
|
7840
8904
|
this.softThreshold = opts.softThreshold ?? 0.75;
|
|
7841
8905
|
this.hardThreshold = opts.hardThreshold ?? 0.9;
|
|
7842
8906
|
this.maxContext = opts.maxContext ?? 128e3;
|
|
7843
8907
|
this.preserveK = opts.preserveK ?? 4;
|
|
7844
8908
|
this.eliseThreshold = opts.eliseThreshold ?? 500;
|
|
7845
|
-
this.summarizerModel = opts.summarizerModel ?? opts.selectorModel
|
|
8909
|
+
this.summarizerModel = opts.summarizerModel ?? opts.selectorModel;
|
|
8910
|
+
if (this.summarizerModel === void 0 && (process.env["NODE_ENV"] === "development" || process.env["WRONGSTACK_DEBUG"] === "1")) {
|
|
8911
|
+
console.warn(
|
|
8912
|
+
"[SelectiveCompactor] summarizerModel not set \u2014 will fall back to ctx.model at summarize time. Set `summarizerModel` explicitly to silence this warning."
|
|
8913
|
+
);
|
|
8914
|
+
}
|
|
7846
8915
|
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.";
|
|
7847
8916
|
}
|
|
7848
8917
|
async compact(ctx, opts = {}) {
|
|
@@ -7948,14 +9017,15 @@ var SelectiveCompactor = class {
|
|
|
7948
9017
|
Summarize the following message range:`;
|
|
7949
9018
|
const body = messages.map((m, i) => `[${i}] ${m.role}: ${this.messagePreview(m)}`).join("\n");
|
|
7950
9019
|
const req = {
|
|
7951
|
-
model: this.summarizerModel,
|
|
9020
|
+
model: this.summarizerModel ?? ctx.model,
|
|
7952
9021
|
system: [{ type: "text", text: systemText }],
|
|
7953
9022
|
messages: [{ role: "user", content: body }],
|
|
7954
9023
|
maxTokens: 512
|
|
7955
9024
|
};
|
|
7956
9025
|
try {
|
|
9026
|
+
const timeoutSignal = AbortSignal.timeout(3e4);
|
|
7957
9027
|
const res = await this.provider.complete(req, {
|
|
7958
|
-
signal: ctx.signal
|
|
9028
|
+
signal: AbortSignal.any([ctx.signal, timeoutSignal])
|
|
7959
9029
|
});
|
|
7960
9030
|
return res.content.filter(isTextBlock).map((b) => b.text).join("\n").trim() || "(empty)";
|
|
7961
9031
|
} catch {
|
|
@@ -8014,27 +9084,16 @@ Summarize the following message range:`;
|
|
|
8014
9084
|
if (typeof m.content === "string") return m.content.trim().length > 0;
|
|
8015
9085
|
return m.content.some((b) => b.type === "text" && b.text.trim().length > 0);
|
|
8016
9086
|
}
|
|
9087
|
+
/**
|
|
9088
|
+
* Estimate message-array tokens via the shared `estimateMessages` primitive
|
|
9089
|
+
* so SelectiveCompactor's before/after/load figures agree with the
|
|
9090
|
+
* middleware threshold math and the other compactors. Previously this used a
|
|
9091
|
+
* private `ceil(len/3.5)` walk that diverged from the calibrated shared
|
|
9092
|
+
* estimator, causing the selective `load`/`targetBudget` comparison to mix
|
|
9093
|
+
* two incompatible token scales.
|
|
9094
|
+
*/
|
|
8017
9095
|
estimateTokens(messages) {
|
|
8018
|
-
|
|
8019
|
-
for (const m of messages) {
|
|
8020
|
-
if (typeof m.content === "string") {
|
|
8021
|
-
total += this.roughTokenEstimate(m.content);
|
|
8022
|
-
} else {
|
|
8023
|
-
for (const b of m.content) {
|
|
8024
|
-
if (b.type === "text") total += this.roughTokenEstimate(b.text);
|
|
8025
|
-
else if (b.type === "tool_use") total += this.roughTokenEstimate(JSON.stringify(b.input));
|
|
8026
|
-
else if (b.type === "tool_result") {
|
|
8027
|
-
total += this.roughTokenEstimate(
|
|
8028
|
-
typeof b.content === "string" ? b.content : JSON.stringify(b.content)
|
|
8029
|
-
);
|
|
8030
|
-
}
|
|
8031
|
-
}
|
|
8032
|
-
}
|
|
8033
|
-
}
|
|
8034
|
-
return total;
|
|
8035
|
-
}
|
|
8036
|
-
roughTokenEstimate(text) {
|
|
8037
|
-
return Math.max(1, Math.ceil(text.length / 3.5));
|
|
9096
|
+
return estimateMessages(messages);
|
|
8038
9097
|
}
|
|
8039
9098
|
};
|
|
8040
9099
|
|
|
@@ -8089,6 +9148,7 @@ var ProviderBackedCompactor = class {
|
|
|
8089
9148
|
return new SelectiveCompactor({
|
|
8090
9149
|
...common,
|
|
8091
9150
|
selectorModel: this.opts.summarizerModel,
|
|
9151
|
+
selectorMaxOutputTokens: this.opts.selectorMaxOutputTokens,
|
|
8092
9152
|
summarizerModel: this.opts.summarizerModel
|
|
8093
9153
|
});
|
|
8094
9154
|
}
|
|
@@ -8224,15 +9284,20 @@ var AutoCompactionMiddleware = class _AutoCompactionMiddleware {
|
|
|
8224
9284
|
this._cachedMsgCount = msgCount;
|
|
8225
9285
|
this._cachedToolCount = toolCount;
|
|
8226
9286
|
}
|
|
8227
|
-
const
|
|
9287
|
+
const budget = computeContextWindowBudget(ctx, tokens, this._maxContext);
|
|
9288
|
+
const load = budget.load;
|
|
8228
9289
|
const policy = this.policyProvider?.(ctx);
|
|
8229
9290
|
const thresholds = policy?.thresholds ?? {
|
|
8230
9291
|
warn: this.warnThreshold,
|
|
8231
9292
|
soft: this.softThreshold,
|
|
8232
9293
|
hard: this.hardThreshold
|
|
8233
9294
|
};
|
|
9295
|
+
const repetition = repeatedReadPressure(ctx);
|
|
9296
|
+
const adaptiveThresholds = adaptThresholdsForSignals(thresholds, {
|
|
9297
|
+
repeatedReadCount: repetition
|
|
9298
|
+
});
|
|
8234
9299
|
const aggressiveOn = policy?.aggressiveOn ?? this.aggressiveOn;
|
|
8235
|
-
const level = load >=
|
|
9300
|
+
const level = load >= adaptiveThresholds.hard ? "hard" : load >= adaptiveThresholds.soft ? "soft" : load >= adaptiveThresholds.warn ? "warn" : null;
|
|
8236
9301
|
if (!level) {
|
|
8237
9302
|
this.lastNoopAttempt = null;
|
|
8238
9303
|
return next(ctx);
|
|
@@ -8241,7 +9306,13 @@ var AutoCompactionMiddleware = class _AutoCompactionMiddleware {
|
|
|
8241
9306
|
return next(ctx);
|
|
8242
9307
|
}
|
|
8243
9308
|
const aggressive = level === "hard" ? true : level === "soft" ? aggressiveOn !== "hard" : aggressiveOn === "warn";
|
|
8244
|
-
await this.compact(ctx, aggressive, {
|
|
9309
|
+
await this.compact(ctx, aggressive, {
|
|
9310
|
+
level,
|
|
9311
|
+
tokens,
|
|
9312
|
+
load,
|
|
9313
|
+
budget,
|
|
9314
|
+
signals: { repeatedReadCount: repetition }
|
|
9315
|
+
});
|
|
8245
9316
|
return next(ctx);
|
|
8246
9317
|
};
|
|
8247
9318
|
}
|
|
@@ -8294,6 +9365,8 @@ var AutoCompactionMiddleware = class _AutoCompactionMiddleware {
|
|
|
8294
9365
|
tokens: pressure.tokens,
|
|
8295
9366
|
load: pressure.load,
|
|
8296
9367
|
maxContext: this._maxContext,
|
|
9368
|
+
budget: pressure.budget,
|
|
9369
|
+
signals: pressure.signals,
|
|
8297
9370
|
report,
|
|
8298
9371
|
aggressive
|
|
8299
9372
|
});
|
|
@@ -8305,6 +9378,8 @@ var AutoCompactionMiddleware = class _AutoCompactionMiddleware {
|
|
|
8305
9378
|
level: pressure.level,
|
|
8306
9379
|
aggressive,
|
|
8307
9380
|
reductions: report.reductions?.map((r) => ({ phase: r.phase, saved: r.saved })),
|
|
9381
|
+
budget: pressure.budget,
|
|
9382
|
+
signals: pressure.signals,
|
|
8308
9383
|
// Record what was collapsed so the audit trail shows the preserved
|
|
8309
9384
|
// content, not just token counts. Bounded to keep the log line small;
|
|
8310
9385
|
// the full original turns are already in the session JSONL.
|
|
@@ -8320,6 +9395,8 @@ var AutoCompactionMiddleware = class _AutoCompactionMiddleware {
|
|
|
8320
9395
|
level: pressure.level,
|
|
8321
9396
|
tokens: pressure.tokens,
|
|
8322
9397
|
maxContext: this._maxContext,
|
|
9398
|
+
budget: pressure.budget,
|
|
9399
|
+
signals: pressure.signals,
|
|
8323
9400
|
load: pressure.load,
|
|
8324
9401
|
fatal
|
|
8325
9402
|
});
|
|
@@ -8339,8 +9416,37 @@ var AutoCompactionMiddleware = class _AutoCompactionMiddleware {
|
|
|
8339
9416
|
}
|
|
8340
9417
|
}
|
|
8341
9418
|
};
|
|
8342
|
-
|
|
8343
|
-
|
|
9419
|
+
function computeContextWindowBudget(ctx, inputTokens, maxContext) {
|
|
9420
|
+
const reservedOutputTokens = readPositiveMetaNumber(ctx, "contextOutputReserveTokens") ?? Math.floor(Math.min(8192, maxContext * 0.08));
|
|
9421
|
+
const reservedSafetyTokens = readPositiveMetaNumber(ctx, "contextSafetyBufferTokens") ?? Math.floor(Math.min(4096, maxContext * 0.02));
|
|
9422
|
+
const availableInputTokens = Math.max(
|
|
9423
|
+
1,
|
|
9424
|
+
maxContext - reservedOutputTokens - reservedSafetyTokens
|
|
9425
|
+
);
|
|
9426
|
+
const remainingInputTokens = availableInputTokens - inputTokens;
|
|
9427
|
+
return {
|
|
9428
|
+
maxContext,
|
|
9429
|
+
inputTokens,
|
|
9430
|
+
availableInputTokens,
|
|
9431
|
+
remainingInputTokens,
|
|
9432
|
+
reservedOutputTokens,
|
|
9433
|
+
reservedSafetyTokens,
|
|
9434
|
+
load: inputTokens / availableInputTokens,
|
|
9435
|
+
overflowTokens: Math.max(0, -remainingInputTokens)
|
|
9436
|
+
};
|
|
9437
|
+
}
|
|
9438
|
+
function readPositiveMetaNumber(ctx, key) {
|
|
9439
|
+
const value = ctx.meta?.[key];
|
|
9440
|
+
return typeof value === "number" && Number.isFinite(value) && value >= 0 ? Math.floor(value) : void 0;
|
|
9441
|
+
}
|
|
9442
|
+
function adaptThresholdsForSignals(thresholds, signals) {
|
|
9443
|
+
if (signals.repeatedReadCount < 3) return thresholds;
|
|
9444
|
+
return {
|
|
9445
|
+
warn: Math.max(0.25, thresholds.warn - 0.08),
|
|
9446
|
+
soft: Math.max(0.35, thresholds.soft - 0.04),
|
|
9447
|
+
hard: thresholds.hard
|
|
9448
|
+
};
|
|
9449
|
+
}
|
|
8344
9450
|
var ToolExecutor = class _ToolExecutor {
|
|
8345
9451
|
constructor(registry, opts) {
|
|
8346
9452
|
this.registry = registry;
|
|
@@ -8437,7 +9543,8 @@ ${errorDetails}`,
|
|
|
8437
9543
|
let effectivePermission = decision.permission;
|
|
8438
9544
|
const policy = this.opts.permissionPolicy;
|
|
8439
9545
|
const yolo = policy.getYolo?.() === true || policy.getYoloDestructive?.() === true;
|
|
8440
|
-
|
|
9546
|
+
const authoritativeAuto = decision.source === "yolo";
|
|
9547
|
+
if (toolDangerousCaps.length > 0 && effectivePermission === "auto" && !yolo && !authoritativeAuto) {
|
|
8441
9548
|
effectivePermission = "confirm";
|
|
8442
9549
|
}
|
|
8443
9550
|
if (effectivePermission === "deny") {
|
|
@@ -8579,9 +9686,10 @@ ${post.additionalContext}`;
|
|
|
8579
9686
|
});
|
|
8580
9687
|
this.opts.renderer?.writeToolCall(tool.name, use.input);
|
|
8581
9688
|
const output = await this.runWithTimeout(tool, use.input, ctx.signal, ctx, use.id);
|
|
8582
|
-
const text = this.serializer.serialize(output);
|
|
9689
|
+
const text = this.serializer.serialize(output, { toolName: tool.name, input: use.input });
|
|
8583
9690
|
const scrubbed = this.opts.secretScrubber.scrub(text);
|
|
8584
|
-
const
|
|
9691
|
+
const withArtifact = await maybePersistLargeToolOutput(tool.name, scrubbed, budget);
|
|
9692
|
+
const { text: capped, newBudget } = this.serializer.enforceCap(withArtifact, budget);
|
|
8585
9693
|
this.opts.renderer?.writeToolResult(tool.name, capped, false);
|
|
8586
9694
|
return {
|
|
8587
9695
|
block: {
|
|
@@ -8606,38 +9714,27 @@ ${post.additionalContext}`;
|
|
|
8606
9714
|
tool.timeoutMs ?? this.iterationTimeoutMs,
|
|
8607
9715
|
this.maxToolTimeoutMs
|
|
8608
9716
|
);
|
|
8609
|
-
const
|
|
8610
|
-
const
|
|
8611
|
-
|
|
8612
|
-
let cleanupCalled = false;
|
|
8613
|
-
let caught = false;
|
|
9717
|
+
const timeoutSignal = AbortSignal.timeout(timeoutMs);
|
|
9718
|
+
const combined = AbortSignal.any([parentSignal, timeoutSignal]);
|
|
9719
|
+
let output;
|
|
8614
9720
|
try {
|
|
8615
|
-
|
|
8616
|
-
return await this.runStreamedTool(tool, input, ctx, combined, toolUseId);
|
|
8617
|
-
}
|
|
8618
|
-
return await tool.execute(input, ctx, { signal: combined });
|
|
9721
|
+
output = typeof tool.executeStream === "function" ? await this.runStreamedTool(tool, input, ctx, combined, toolUseId) : await tool.execute(input, ctx, { signal: combined });
|
|
8619
9722
|
} catch (err) {
|
|
8620
|
-
|
|
8621
|
-
if (combined.aborted && typeof tool.cleanup === "function") {
|
|
8622
|
-
cleanupCalled = true;
|
|
8623
|
-
try {
|
|
8624
|
-
await tool.cleanup(input, ctx);
|
|
8625
|
-
} catch {
|
|
8626
|
-
}
|
|
8627
|
-
}
|
|
9723
|
+
if (combined.aborted) await this.runToolCleanup(tool, input, ctx);
|
|
8628
9724
|
throw err;
|
|
8629
|
-
}
|
|
8630
|
-
|
|
8631
|
-
|
|
8632
|
-
|
|
8633
|
-
|
|
8634
|
-
|
|
8635
|
-
|
|
8636
|
-
|
|
8637
|
-
|
|
8638
|
-
|
|
8639
|
-
|
|
8640
|
-
|
|
9725
|
+
}
|
|
9726
|
+
if (combined.aborted) {
|
|
9727
|
+
await this.runToolCleanup(tool, input, ctx);
|
|
9728
|
+
throw combined.reason instanceof Error ? combined.reason : new Error(typeof combined.reason === "string" ? combined.reason : "tool timeout");
|
|
9729
|
+
}
|
|
9730
|
+
return output;
|
|
9731
|
+
}
|
|
9732
|
+
/** Best-effort tool cleanup; never let it mask the original error. */
|
|
9733
|
+
async runToolCleanup(tool, input, ctx) {
|
|
9734
|
+
if (typeof tool.cleanup !== "function") return;
|
|
9735
|
+
try {
|
|
9736
|
+
await tool.cleanup(input, ctx);
|
|
9737
|
+
} catch {
|
|
8641
9738
|
}
|
|
8642
9739
|
}
|
|
8643
9740
|
async runStreamedTool(tool, input, ctx, signal, toolUseId) {
|
|
@@ -8806,6 +9903,25 @@ function extractMalformedRaw(input) {
|
|
|
8806
9903
|
return String(value);
|
|
8807
9904
|
}
|
|
8808
9905
|
}
|
|
9906
|
+
var TOOL_OUTPUT_ARTIFACT_THRESHOLD_BYTES = 64 * 1024;
|
|
9907
|
+
async function maybePersistLargeToolOutput(toolName, content, budget) {
|
|
9908
|
+
const bytes = Buffer.byteLength(content, "utf8");
|
|
9909
|
+
if (bytes <= Math.min(TOOL_OUTPUT_ARTIFACT_THRESHOLD_BYTES, Math.max(0, budget))) {
|
|
9910
|
+
return content;
|
|
9911
|
+
}
|
|
9912
|
+
try {
|
|
9913
|
+
const dir = path4.join(wstackGlobalRoot(), "tool-output");
|
|
9914
|
+
await fsp2.mkdir(dir, { recursive: true });
|
|
9915
|
+
const safeTool = toolName.replace(/[^a-zA-Z0-9._-]+/g, "_").slice(0, 40) || "tool";
|
|
9916
|
+
const stamp = (/* @__PURE__ */ new Date()).toISOString().replace(/[:.]/g, "-");
|
|
9917
|
+
const filePath = path4.join(dir, `${stamp}-${safeTool}-${randomUUID()}.log`);
|
|
9918
|
+
await fsp2.writeFile(filePath, content, "utf8");
|
|
9919
|
+
return content + `
|
|
9920
|
+
[full tool output: ${bytes} bytes at ${filePath}; read/grep that file selectively instead of re-running or requesting more output]`;
|
|
9921
|
+
} catch {
|
|
9922
|
+
return content;
|
|
9923
|
+
}
|
|
9924
|
+
}
|
|
8809
9925
|
|
|
8810
9926
|
// src/execution/autonomous-runner.ts
|
|
8811
9927
|
var DoneConditionChecker = class {
|
|
@@ -9818,6 +10934,7 @@ ${recentJournal}` : ""
|
|
|
9818
10934
|
|
|
9819
10935
|
// src/coordination/subagent-budget.ts
|
|
9820
10936
|
var TIMEOUT_PREEMPT_FRACTION = 0.85;
|
|
10937
|
+
var DECISION_TIMEOUT_MS = 6e4;
|
|
9821
10938
|
var BudgetExceededError = class extends Error {
|
|
9822
10939
|
kind;
|
|
9823
10940
|
limit;
|
|
@@ -9847,6 +10964,31 @@ var BudgetThresholdSignal = class extends Error {
|
|
|
9847
10964
|
};
|
|
9848
10965
|
var SubagentBudget = class _SubagentBudget {
|
|
9849
10966
|
limits;
|
|
10967
|
+
/** Patch one or more budget limits in-place after construction.
|
|
10968
|
+
* Used by the coordinator watchdog when granting an extension.
|
|
10969
|
+
* All fields are optional — only provided fields are updated.
|
|
10970
|
+
* This is the single write path for limit mutations so that future
|
|
10971
|
+
* validation or side-effects live in one place (M1). */
|
|
10972
|
+
patchLimits(ext) {
|
|
10973
|
+
if (ext.maxIterations !== void 0) {
|
|
10974
|
+
this.limits.maxIterations = ext.maxIterations;
|
|
10975
|
+
}
|
|
10976
|
+
if (ext.maxToolCalls !== void 0) {
|
|
10977
|
+
this.limits.maxToolCalls = ext.maxToolCalls;
|
|
10978
|
+
}
|
|
10979
|
+
if (ext.maxTokens !== void 0) {
|
|
10980
|
+
this.limits.maxTokens = ext.maxTokens;
|
|
10981
|
+
}
|
|
10982
|
+
if (ext.maxCostUsd !== void 0) {
|
|
10983
|
+
this.limits.maxCostUsd = ext.maxCostUsd;
|
|
10984
|
+
}
|
|
10985
|
+
if (ext.timeoutMs !== void 0) {
|
|
10986
|
+
this.limits.timeoutMs = ext.timeoutMs;
|
|
10987
|
+
}
|
|
10988
|
+
if (ext.idleTimeoutMs !== void 0) {
|
|
10989
|
+
this.limits.idleTimeoutMs = ext.idleTimeoutMs;
|
|
10990
|
+
}
|
|
10991
|
+
}
|
|
9850
10992
|
iterations = 0;
|
|
9851
10993
|
toolCalls = 0;
|
|
9852
10994
|
tokenInput = 0;
|
|
@@ -9867,12 +11009,44 @@ var SubagentBudget = class _SubagentBudget {
|
|
|
9867
11009
|
* or hung listener (Director not built / event filter detached mid-run)
|
|
9868
11010
|
* leaves the budget over-limit and never enforces anything.
|
|
9869
11011
|
*/
|
|
9870
|
-
static DECISION_TIMEOUT_MS =
|
|
11012
|
+
static DECISION_TIMEOUT_MS = DECISION_TIMEOUT_MS;
|
|
9871
11013
|
/**
|
|
9872
11014
|
* Injected by the runner when wiring the budget to its EventBus.
|
|
9873
11015
|
* Used to emit `budget.threshold_reached` events in `'auto'` mode.
|
|
9874
11016
|
*/
|
|
9875
11017
|
_events;
|
|
11018
|
+
/**
|
|
11019
|
+
* Guard against dual-path races between the coordinator watchdog
|
|
11020
|
+
* (`executeWithTimeout`) and the budget's own `checkTimeout()`.
|
|
11021
|
+
* Both paths detect `elapsed >= timeoutMs` and can emit
|
|
11022
|
+
* `budget.threshold_reached` for kind `'timeout'` simultaneously.
|
|
11023
|
+
* Set to the current `timeoutMs` ceiling by the coordinator BEFORE
|
|
11024
|
+
* calling `onThreshold`, and cleared after the negotiation resolves.
|
|
11025
|
+
* `checkTimeout()` skips its wall-clock check while this is set so
|
|
11026
|
+
* the coordinator's watchdog is the sole source of wall-clock timeout
|
|
11027
|
+
* events — `checkTimeout()` focuses exclusively on `idle_timeout`.
|
|
11028
|
+
*/
|
|
11029
|
+
_watchdogActive;
|
|
11030
|
+
/** Returns the timeout ceiling currently being negotiated by the watchdog,
|
|
11031
|
+
* or `undefined` when no wall-clock negotiation is in flight.
|
|
11032
|
+
* Used by `executeWithTimeout` to detect a stale lock (M3). */
|
|
11033
|
+
get watchdogActive() {
|
|
11034
|
+
return this._watchdogActive;
|
|
11035
|
+
}
|
|
11036
|
+
/** Called by the coordinator watchdog BEFORE calling `onThreshold` so that
|
|
11037
|
+
* `checkTimeout()` skips its wall-clock check for this ceiling. Prevents
|
|
11038
|
+
* the budget's own `checkTimeout()` from emitting a second
|
|
11039
|
+
* `budget.threshold_reached` event while the watchdog is already
|
|
11040
|
+
* negotiating the same wall-clock deadline (C1). */
|
|
11041
|
+
setWatchdogNegotiation(timeoutMs) {
|
|
11042
|
+
this._watchdogActive = timeoutMs;
|
|
11043
|
+
}
|
|
11044
|
+
/** Clears the watchdog guard after negotiation resolves. Called in the
|
|
11045
|
+
* `finally` block of both the pre-empt and deadline branches so it fires
|
|
11046
|
+
* on every exit path: grant, deny, throw, or error. */
|
|
11047
|
+
clearWatchdogNegotiation() {
|
|
11048
|
+
this._watchdogActive = void 0;
|
|
11049
|
+
}
|
|
9876
11050
|
/**
|
|
9877
11051
|
* Negotiation mode — controls whether a threshold hit tries to emit
|
|
9878
11052
|
* `budget.threshold_reached` and wait for a coordinator decision, or
|
|
@@ -9973,7 +11147,8 @@ var SubagentBudget = class _SubagentBudget {
|
|
|
9973
11147
|
if (this.limits.idleTimeoutMs !== void 0 && idle > this.limits.idleTimeoutMs) {
|
|
9974
11148
|
exceeded.push({ kind: "idle_timeout", used: idle, limit: this.limits.idleTimeoutMs });
|
|
9975
11149
|
}
|
|
9976
|
-
|
|
11150
|
+
const wallOwnedByWatchdog = this._onThreshold !== void 0 && this._watchdogActive === this.limits.timeoutMs;
|
|
11151
|
+
if (this.limits.timeoutMs !== void 0 && elapsedMs > this.limits.timeoutMs && !wallOwnedByWatchdog) {
|
|
9977
11152
|
exceeded.push({ kind: "timeout", used: elapsedMs, limit: this.limits.timeoutMs });
|
|
9978
11153
|
}
|
|
9979
11154
|
}
|
|
@@ -9987,19 +11162,99 @@ var SubagentBudget = class _SubagentBudget {
|
|
|
9987
11162
|
throw new BudgetExceededError(first2.kind, first2.limit, first2.used);
|
|
9988
11163
|
}
|
|
9989
11164
|
const bus = this._events;
|
|
9990
|
-
if (!bus
|
|
11165
|
+
if (!bus) {
|
|
9991
11166
|
const first2 = exceeded[0] ?? { kind: "iterations", limit: 0, used: 0 };
|
|
9992
11167
|
throw new BudgetExceededError(first2.kind, first2.limit, first2.used);
|
|
9993
11168
|
}
|
|
11169
|
+
const first = exceeded[0] ?? { kind: "iterations", limit: 0, used: 0 };
|
|
11170
|
+
if (bus.hasListenerFor("budget.threshold_reached")) {
|
|
11171
|
+
for (const entry of exceeded) {
|
|
11172
|
+
if (this._pendingNegotiations.has(entry.kind)) continue;
|
|
11173
|
+
this._pendingNegotiations.set(entry.kind, this._negotiateExtension(entry));
|
|
11174
|
+
}
|
|
11175
|
+
const decision = this._pendingNegotiations.get(first.kind);
|
|
11176
|
+
if (!decision) throw new Error(`No pending negotiation for ${first.kind}`);
|
|
11177
|
+
throw new BudgetThresholdSignal(first.kind, first.limit, first.used, decision);
|
|
11178
|
+
}
|
|
11179
|
+
let hardStop = null;
|
|
9994
11180
|
for (const entry of exceeded) {
|
|
9995
11181
|
if (this._pendingNegotiations.has(entry.kind)) continue;
|
|
9996
|
-
const
|
|
9997
|
-
this._pendingNegotiations.set(entry.kind,
|
|
11182
|
+
const marker = Promise.resolve("stop");
|
|
11183
|
+
this._pendingNegotiations.set(entry.kind, marker);
|
|
11184
|
+
void marker.finally(() => this._pendingNegotiations.delete(entry.kind));
|
|
11185
|
+
const sync = this._invokeHandlerSync(entry);
|
|
11186
|
+
if (!sync) hardStop ??= new BudgetExceededError(entry.kind, entry.limit, entry.used);
|
|
9998
11187
|
}
|
|
9999
|
-
|
|
10000
|
-
|
|
10001
|
-
|
|
10002
|
-
|
|
11188
|
+
if (hardStop) throw hardStop;
|
|
11189
|
+
return exceeded;
|
|
11190
|
+
}
|
|
11191
|
+
/**
|
|
11192
|
+
* Invoke `onThreshold` once for `entry` on the NO-LISTENER path and report
|
|
11193
|
+
* whether it decided synchronously. Returns `true` when the handler returned
|
|
11194
|
+
* a synchronous decision (already honored — an `extend` patched the limits),
|
|
11195
|
+
* or `false` when it returned a Promise (async; the caller hard-stops, since
|
|
11196
|
+
* there is no listener to resolve the negotiation). The handler is given the
|
|
11197
|
+
* full info shape (`requestDecision` plus direct `extend`/`deny`) so both
|
|
11198
|
+
* recording handlers and policy handlers work without a wired listener.
|
|
11199
|
+
*/
|
|
11200
|
+
_invokeHandlerSync(entry) {
|
|
11201
|
+
const handler = this._onThreshold;
|
|
11202
|
+
if (!handler) return false;
|
|
11203
|
+
let extendArg;
|
|
11204
|
+
const result = handler({
|
|
11205
|
+
kind: entry.kind,
|
|
11206
|
+
used: entry.used,
|
|
11207
|
+
limit: entry.limit,
|
|
11208
|
+
requestDecision: () => this._busRequestDecision(entry),
|
|
11209
|
+
// Direct hooks for synchronous policy/recording handlers.
|
|
11210
|
+
extend: (extra) => {
|
|
11211
|
+
extendArg = extra;
|
|
11212
|
+
},
|
|
11213
|
+
deny: () => {
|
|
11214
|
+
}
|
|
11215
|
+
});
|
|
11216
|
+
if (result && typeof result.then === "function") return false;
|
|
11217
|
+
if (result === "throw") return false;
|
|
11218
|
+
if (result && typeof result === "object" && "extend" in result) {
|
|
11219
|
+
extendArg = result.extend;
|
|
11220
|
+
}
|
|
11221
|
+
if (extendArg) this.patchLimits(extendArg);
|
|
11222
|
+
return true;
|
|
11223
|
+
}
|
|
11224
|
+
/**
|
|
11225
|
+
* Emit `budget.threshold_reached` and resolve to the listener's verdict.
|
|
11226
|
+
* Resolves to `'stop'` immediately when there is no listener (or no bus) so
|
|
11227
|
+
* no negotiation can hang and no fallback timer leaks. Mirrors the
|
|
11228
|
+
* coordinator watchdog's own request path so both agree on the no-listener
|
|
11229
|
+
* default.
|
|
11230
|
+
*/
|
|
11231
|
+
_busRequestDecision(entry) {
|
|
11232
|
+
const bus = this._events;
|
|
11233
|
+
if (!bus || !bus.hasListenerFor("budget.threshold_reached")) {
|
|
11234
|
+
return Promise.resolve("stop");
|
|
11235
|
+
}
|
|
11236
|
+
return new Promise((resolve6) => {
|
|
11237
|
+
let resolved = false;
|
|
11238
|
+
const respond = (d) => {
|
|
11239
|
+
if (resolved) return;
|
|
11240
|
+
resolved = true;
|
|
11241
|
+
clearTimeout(fallback);
|
|
11242
|
+
resolve6(d);
|
|
11243
|
+
};
|
|
11244
|
+
const fallback = setTimeout(() => respond("stop"), _SubagentBudget.DECISION_TIMEOUT_MS);
|
|
11245
|
+
bus.emit("budget.threshold_reached", {
|
|
11246
|
+
kind: entry.kind,
|
|
11247
|
+
used: entry.used,
|
|
11248
|
+
limit: entry.limit,
|
|
11249
|
+
timeoutMs: _SubagentBudget.DECISION_TIMEOUT_MS,
|
|
11250
|
+
// deny() wins over a same-dispatch extend(): a listener that both grants
|
|
11251
|
+
// and denies (or two listeners disagreeing) is resolved as a stop. The
|
|
11252
|
+
// grant is deferred a microtask so a synchronous deny in the same emit
|
|
11253
|
+
// pre-empts it; async grants still resolve normally.
|
|
11254
|
+
extend: (extra) => queueMicrotask(() => respond({ extend: extra })),
|
|
11255
|
+
deny: () => respond("stop")
|
|
11256
|
+
});
|
|
11257
|
+
});
|
|
10003
11258
|
}
|
|
10004
11259
|
/**
|
|
10005
11260
|
* Per-kind in-flight negotiation Promises. Each budget kind can have its
|
|
@@ -10019,77 +11274,33 @@ var SubagentBudget = class _SubagentBudget {
|
|
|
10019
11274
|
* `{ extend: {} }` — keep going without patching; next overrun fires
|
|
10020
11275
|
* a fresh signal.
|
|
10021
11276
|
*/
|
|
10022
|
-
async _negotiateExtension(
|
|
11277
|
+
async _negotiateExtension(entry) {
|
|
10023
11278
|
if (!this._onThreshold) {
|
|
10024
11279
|
return "stop";
|
|
10025
11280
|
}
|
|
10026
11281
|
try {
|
|
10027
|
-
const first = exceeded[0] ?? { kind: "iterations", limit: 0, used: 0 };
|
|
10028
11282
|
const result = this._onThreshold({
|
|
10029
|
-
kind:
|
|
10030
|
-
used:
|
|
10031
|
-
limit:
|
|
10032
|
-
|
|
10033
|
-
|
|
10034
|
-
|
|
10035
|
-
|
|
10036
|
-
|
|
10037
|
-
|
|
10038
|
-
|
|
10039
|
-
|
|
10040
|
-
if (resolved) return;
|
|
10041
|
-
resolved = true;
|
|
10042
|
-
resolve5(d);
|
|
10043
|
-
};
|
|
10044
|
-
const fallback = setTimeout(
|
|
10045
|
-
() => respond("stop"),
|
|
10046
|
-
_SubagentBudget.DECISION_TIMEOUT_MS
|
|
10047
|
-
);
|
|
10048
|
-
for (const { kind: kind2, used, limit } of exceeded) {
|
|
10049
|
-
bus.emit("budget.threshold_reached", {
|
|
10050
|
-
kind: kind2,
|
|
10051
|
-
used,
|
|
10052
|
-
limit,
|
|
10053
|
-
timeoutMs: _SubagentBudget.DECISION_TIMEOUT_MS,
|
|
10054
|
-
extend: (extra) => {
|
|
10055
|
-
clearTimeout(fallback);
|
|
10056
|
-
respond({ extend: extra });
|
|
10057
|
-
},
|
|
10058
|
-
deny: () => {
|
|
10059
|
-
clearTimeout(fallback);
|
|
10060
|
-
respond("stop");
|
|
10061
|
-
}
|
|
10062
|
-
});
|
|
10063
|
-
}
|
|
10064
|
-
});
|
|
11283
|
+
kind: entry.kind,
|
|
11284
|
+
used: entry.used,
|
|
11285
|
+
limit: entry.limit,
|
|
11286
|
+
// One event for THIS kind only — each exceeded kind has its own
|
|
11287
|
+
// negotiation (and its own resolve), so there is no cross-kind
|
|
11288
|
+
// first-wins drop and no O(N^2) re-emission.
|
|
11289
|
+
requestDecision: () => this._busRequestDecision(entry),
|
|
11290
|
+
extend: (extra) => {
|
|
11291
|
+
this.patchLimits(extra);
|
|
11292
|
+
},
|
|
11293
|
+
deny: () => {
|
|
10065
11294
|
}
|
|
10066
11295
|
});
|
|
10067
11296
|
if (result === "throw") return "stop";
|
|
10068
11297
|
if (result === "continue") return { extend: {} };
|
|
10069
11298
|
const decision = await result;
|
|
10070
11299
|
if (decision === "stop") return "stop";
|
|
10071
|
-
|
|
10072
|
-
if (ext.maxIterations !== void 0) {
|
|
10073
|
-
this.limits.maxIterations = ext.maxIterations;
|
|
10074
|
-
}
|
|
10075
|
-
if (ext.maxToolCalls !== void 0) {
|
|
10076
|
-
this.limits.maxToolCalls = ext.maxToolCalls;
|
|
10077
|
-
}
|
|
10078
|
-
if (ext.maxTokens !== void 0) {
|
|
10079
|
-
this.limits.maxTokens = ext.maxTokens;
|
|
10080
|
-
}
|
|
10081
|
-
if (ext.maxCostUsd !== void 0) {
|
|
10082
|
-
this.limits.maxCostUsd = ext.maxCostUsd;
|
|
10083
|
-
}
|
|
10084
|
-
if (ext.timeoutMs !== void 0) {
|
|
10085
|
-
this.limits.timeoutMs = ext.timeoutMs;
|
|
10086
|
-
}
|
|
10087
|
-
if (ext.idleTimeoutMs !== void 0) {
|
|
10088
|
-
this.limits.idleTimeoutMs = ext.idleTimeoutMs;
|
|
10089
|
-
}
|
|
11300
|
+
this.patchLimits(decision.extend);
|
|
10090
11301
|
return decision;
|
|
10091
11302
|
} finally {
|
|
10092
|
-
this._pendingNegotiations.delete(kind);
|
|
11303
|
+
this._pendingNegotiations.delete(entry.kind);
|
|
10093
11304
|
}
|
|
10094
11305
|
}
|
|
10095
11306
|
recordIteration() {
|
|
@@ -10132,7 +11343,8 @@ var SubagentBudget = class _SubagentBudget {
|
|
|
10132
11343
|
const { timeoutMs, idleTimeoutMs } = this.limits;
|
|
10133
11344
|
if (timeoutMs === void 0 && idleTimeoutMs === void 0) return;
|
|
10134
11345
|
const elapsed = Date.now() - this.startTime;
|
|
10135
|
-
const
|
|
11346
|
+
const wallSkipped = this._onThreshold !== void 0 && this._watchdogActive !== void 0 && timeoutMs !== void 0 && this._watchdogActive === timeoutMs;
|
|
11347
|
+
const wallTripped = wallSkipped ? false : timeoutMs !== void 0 && elapsed > timeoutMs;
|
|
10136
11348
|
const idleTripped = idleTimeoutMs !== void 0 && this.idleMs() > idleTimeoutMs;
|
|
10137
11349
|
if (!wallTripped && !idleTripped) return;
|
|
10138
11350
|
void this.checkLimits(elapsed);
|
|
@@ -13640,6 +14852,7 @@ var DefaultMultiAgentCoordinator = class _DefaultMultiAgentCoordinator extends E
|
|
|
13640
14852
|
terminating = /* @__PURE__ */ new Set();
|
|
13641
14853
|
constructor(config, options = {}) {
|
|
13642
14854
|
super();
|
|
14855
|
+
this.setMaxListeners(0);
|
|
13643
14856
|
this.coordinatorId = config.coordinatorId;
|
|
13644
14857
|
this.config = config;
|
|
13645
14858
|
this.runner = options.runner;
|
|
@@ -13837,7 +15050,7 @@ var DefaultMultiAgentCoordinator = class _DefaultMultiAgentCoordinator extends E
|
|
|
13837
15050
|
taskIds.map((id) => {
|
|
13838
15051
|
const cached = this.completedResults.find((r) => r.taskId === id);
|
|
13839
15052
|
if (cached) return cached;
|
|
13840
|
-
return new Promise((
|
|
15053
|
+
return new Promise((resolve6, reject) => {
|
|
13841
15054
|
const timeout = setTimeout(() => {
|
|
13842
15055
|
this.off("task.completed", handler);
|
|
13843
15056
|
reject(new Error(`awaitTasks timed out waiting for task "${id}"`));
|
|
@@ -13846,7 +15059,7 @@ var DefaultMultiAgentCoordinator = class _DefaultMultiAgentCoordinator extends E
|
|
|
13846
15059
|
if (result.taskId === id) {
|
|
13847
15060
|
clearTimeout(timeout);
|
|
13848
15061
|
this.off("task.completed", handler);
|
|
13849
|
-
|
|
15062
|
+
resolve6(result);
|
|
13850
15063
|
}
|
|
13851
15064
|
};
|
|
13852
15065
|
this.on("task.completed", handler);
|
|
@@ -14034,7 +15247,13 @@ var DefaultMultiAgentCoordinator = class _DefaultMultiAgentCoordinator extends E
|
|
|
14034
15247
|
let result;
|
|
14035
15248
|
budget.start();
|
|
14036
15249
|
try {
|
|
14037
|
-
const outcome = await this.executeWithTimeout(
|
|
15250
|
+
const outcome = await this.executeWithTimeout(
|
|
15251
|
+
this.runner,
|
|
15252
|
+
task,
|
|
15253
|
+
runCtx,
|
|
15254
|
+
budget,
|
|
15255
|
+
subagent.config.preemptFraction
|
|
15256
|
+
);
|
|
14038
15257
|
result = {
|
|
14039
15258
|
subagentId,
|
|
14040
15259
|
taskId: task.id,
|
|
@@ -14061,7 +15280,7 @@ var DefaultMultiAgentCoordinator = class _DefaultMultiAgentCoordinator extends E
|
|
|
14061
15280
|
}
|
|
14062
15281
|
this.recordCompletion(result);
|
|
14063
15282
|
}
|
|
14064
|
-
async executeWithTimeout(runner, task, ctx, budget) {
|
|
15283
|
+
async executeWithTimeout(runner, task, ctx, budget, preemptFraction = TIMEOUT_PREEMPT_FRACTION) {
|
|
14065
15284
|
const initialTimeoutMs = budget.limits.timeoutMs;
|
|
14066
15285
|
const idleLimitMs = budget.limits.idleTimeoutMs;
|
|
14067
15286
|
if (initialTimeoutMs === void 0 && idleLimitMs === void 0) {
|
|
@@ -14069,8 +15288,21 @@ var DefaultMultiAgentCoordinator = class _DefaultMultiAgentCoordinator extends E
|
|
|
14069
15288
|
}
|
|
14070
15289
|
const start = Date.now();
|
|
14071
15290
|
let timer = null;
|
|
14072
|
-
let
|
|
15291
|
+
let PreemptState;
|
|
15292
|
+
((PreemptState2) => {
|
|
15293
|
+
PreemptState2["ACTIVE"] = "active";
|
|
15294
|
+
PreemptState2["LOCKED"] = "locked";
|
|
15295
|
+
})(PreemptState || (PreemptState = {}));
|
|
15296
|
+
let preemptedCeiling = null;
|
|
15297
|
+
let preemptState = "active" /* ACTIVE */;
|
|
15298
|
+
let lastGrantActivityTs = -1;
|
|
14073
15299
|
const timeoutPromise = new Promise((_, reject) => {
|
|
15300
|
+
const terminate = (kind, limit, used) => {
|
|
15301
|
+
this.subagents.get(ctx.subagentId)?.abortController.abort();
|
|
15302
|
+
reject(
|
|
15303
|
+
budget._events?.hasListenerFor("budget.threshold_reached") ? new Error(`subagent stopped: budget ${kind} (limit=${limit}, used=${used})`) : new BudgetExceededError(kind, limit, used)
|
|
15304
|
+
);
|
|
15305
|
+
};
|
|
14074
15306
|
const armFor = (ms) => {
|
|
14075
15307
|
if (timer) clearTimeout(timer);
|
|
14076
15308
|
timer = setTimeout(onTick, Math.max(0, ms));
|
|
@@ -14079,7 +15311,7 @@ var DefaultMultiAgentCoordinator = class _DefaultMultiAgentCoordinator extends E
|
|
|
14079
15311
|
const wallLimit = budget.limits.timeoutMs ?? initialTimeoutMs;
|
|
14080
15312
|
const wallRemaining = initialTimeoutMs === void 0 ? Number.POSITIVE_INFINITY : wallLimit - (Date.now() - start);
|
|
14081
15313
|
const idleRemaining = idleLimitMs === void 0 ? Number.POSITIVE_INFINITY : (budget.limits.idleTimeoutMs ?? idleLimitMs) - budget.idleMs();
|
|
14082
|
-
const preemptRemaining = initialTimeoutMs === void 0 ||
|
|
15314
|
+
const preemptRemaining = initialTimeoutMs === void 0 || preemptedCeiling === wallLimit ? Number.POSITIVE_INFINITY : wallLimit * preemptFraction - (Date.now() - start);
|
|
14083
15315
|
armFor(Math.max(25, Math.min(wallRemaining, idleRemaining, preemptRemaining)));
|
|
14084
15316
|
};
|
|
14085
15317
|
const negotiateTimeout = async (used, limit) => {
|
|
@@ -14089,16 +15321,42 @@ var DefaultMultiAgentCoordinator = class _DefaultMultiAgentCoordinator extends E
|
|
|
14089
15321
|
kind: "timeout",
|
|
14090
15322
|
used,
|
|
14091
15323
|
limit,
|
|
14092
|
-
requestDecision: () =>
|
|
14093
|
-
budget._events?.
|
|
14094
|
-
|
|
14095
|
-
|
|
14096
|
-
|
|
14097
|
-
|
|
14098
|
-
|
|
14099
|
-
|
|
15324
|
+
requestDecision: () => {
|
|
15325
|
+
if (!budget._events?.hasListenerFor("budget.threshold_reached")) {
|
|
15326
|
+
return Promise.resolve("stop");
|
|
15327
|
+
}
|
|
15328
|
+
return new Promise((resolveDecision) => {
|
|
15329
|
+
let settled = false;
|
|
15330
|
+
const resolve6 = (d) => {
|
|
15331
|
+
if (settled) return;
|
|
15332
|
+
settled = true;
|
|
15333
|
+
resolveDecision(d);
|
|
15334
|
+
};
|
|
15335
|
+
const fallback = setTimeout(() => resolve6("stop"), DECISION_TIMEOUT_MS);
|
|
15336
|
+
budget._events?.emit("budget.threshold_reached", {
|
|
15337
|
+
kind: "timeout",
|
|
15338
|
+
used,
|
|
15339
|
+
limit,
|
|
15340
|
+
// Informational: the budget's own decision deadline. Listeners may use
|
|
15341
|
+
// this to display a countdown. The coordinator does NOT enforce it —
|
|
15342
|
+
// it is the budget's own `setTimeout(fallback)` that races against
|
|
15343
|
+
// the listener's `extend()`/`deny()` call to guarantee progress.
|
|
15344
|
+
timeoutMs: DECISION_TIMEOUT_MS,
|
|
15345
|
+
// deny() wins over a same-dispatch extend(): defer the grant a
|
|
15346
|
+
// microtask so a synchronous deny in the same emit pre-empts it
|
|
15347
|
+
// (a listener that both grants and denies, or two listeners
|
|
15348
|
+
// disagreeing, resolves as a stop). Async grants still resolve.
|
|
15349
|
+
extend: (extra) => {
|
|
15350
|
+
clearTimeout(fallback);
|
|
15351
|
+
queueMicrotask(() => resolve6({ extend: extra }));
|
|
15352
|
+
},
|
|
15353
|
+
deny: () => {
|
|
15354
|
+
clearTimeout(fallback);
|
|
15355
|
+
resolve6("stop");
|
|
15356
|
+
}
|
|
15357
|
+
});
|
|
14100
15358
|
});
|
|
14101
|
-
}
|
|
15359
|
+
}
|
|
14102
15360
|
});
|
|
14103
15361
|
return typeof result === "string" ? result : await result;
|
|
14104
15362
|
};
|
|
@@ -14109,21 +15367,45 @@ var DefaultMultiAgentCoordinator = class _DefaultMultiAgentCoordinator extends E
|
|
|
14109
15367
|
const wallExceeded = wallLimit !== void 0 && elapsed >= wallLimit;
|
|
14110
15368
|
const idleExceeded = idleLimit !== void 0 && budget.idleMs() >= idleLimit;
|
|
14111
15369
|
if (idleExceeded && !wallExceeded) {
|
|
15370
|
+
budget._events?.emit("budget.threshold_reached", {
|
|
15371
|
+
kind: "idle_timeout",
|
|
15372
|
+
used: budget.idleMs(),
|
|
15373
|
+
limit: idleLimit ?? 0,
|
|
15374
|
+
timeoutMs: DECISION_TIMEOUT_MS,
|
|
15375
|
+
extend: () => {
|
|
15376
|
+
},
|
|
15377
|
+
deny: () => {
|
|
15378
|
+
}
|
|
15379
|
+
});
|
|
14112
15380
|
this.subagents.get(ctx.subagentId)?.abortController.abort();
|
|
14113
|
-
reject(new BudgetExceededError("
|
|
15381
|
+
reject(new BudgetExceededError("idle_timeout", idleLimit ?? 0, budget.idleMs()));
|
|
14114
15382
|
return;
|
|
14115
15383
|
}
|
|
14116
|
-
if (wallLimit !== void 0 && !wallExceeded && budget.onThreshold &&
|
|
15384
|
+
if (wallLimit !== void 0 && !wallExceeded && budget.onThreshold && preemptState === "active" /* ACTIVE */ && elapsed >= wallLimit * preemptFraction) {
|
|
15385
|
+
const activityTs = Date.now() - budget.idleMs();
|
|
15386
|
+
if (activityTs <= lastGrantActivityTs) {
|
|
15387
|
+
preemptState = "locked" /* LOCKED */;
|
|
15388
|
+
preemptedCeiling = wallLimit;
|
|
15389
|
+
scheduleNext();
|
|
15390
|
+
return;
|
|
15391
|
+
}
|
|
15392
|
+
budget.setWatchdogNegotiation(wallLimit);
|
|
14117
15393
|
try {
|
|
14118
15394
|
const decision = await negotiateTimeout(elapsed, wallLimit);
|
|
14119
15395
|
if (typeof decision !== "string" && decision.extend.timeoutMs !== void 0) {
|
|
14120
|
-
budget.
|
|
14121
|
-
|
|
15396
|
+
budget.patchLimits({ timeoutMs: decision.extend.timeoutMs });
|
|
15397
|
+
lastGrantActivityTs = Date.now() - budget.idleMs();
|
|
15398
|
+
preemptState = "active" /* ACTIVE */;
|
|
15399
|
+
preemptedCeiling = null;
|
|
14122
15400
|
} else {
|
|
14123
|
-
|
|
15401
|
+
preemptState = "locked" /* LOCKED */;
|
|
15402
|
+
preemptedCeiling = wallLimit;
|
|
14124
15403
|
}
|
|
14125
15404
|
} catch {
|
|
14126
|
-
|
|
15405
|
+
preemptState = "locked" /* LOCKED */;
|
|
15406
|
+
preemptedCeiling = wallLimit;
|
|
15407
|
+
} finally {
|
|
15408
|
+
budget.clearWatchdogNegotiation();
|
|
14127
15409
|
}
|
|
14128
15410
|
scheduleNext();
|
|
14129
15411
|
return;
|
|
@@ -14138,26 +15420,41 @@ var DefaultMultiAgentCoordinator = class _DefaultMultiAgentCoordinator extends E
|
|
|
14138
15420
|
reject(new BudgetExceededError("timeout", limit, elapsed));
|
|
14139
15421
|
return;
|
|
14140
15422
|
}
|
|
15423
|
+
budget.setWatchdogNegotiation(limit);
|
|
14141
15424
|
try {
|
|
14142
15425
|
const decision = await negotiateTimeout(elapsed, limit);
|
|
14143
|
-
if (decision === "
|
|
14144
|
-
|
|
15426
|
+
if (decision === "throw") {
|
|
15427
|
+
terminate("timeout", limit, elapsed);
|
|
15428
|
+
return;
|
|
15429
|
+
}
|
|
15430
|
+
if (decision === "continue") {
|
|
15431
|
+
preemptState = "locked" /* LOCKED */;
|
|
15432
|
+
preemptedCeiling = wallLimit;
|
|
14145
15433
|
armFor(Math.max(1e3, limit));
|
|
14146
15434
|
return;
|
|
14147
15435
|
}
|
|
15436
|
+
if (decision === "stop") {
|
|
15437
|
+
terminate("timeout", limit, elapsed);
|
|
15438
|
+
return;
|
|
15439
|
+
}
|
|
14148
15440
|
if (decision.extend.timeoutMs !== void 0) {
|
|
14149
|
-
budget.
|
|
14150
|
-
|
|
15441
|
+
budget.patchLimits({ timeoutMs: decision.extend.timeoutMs });
|
|
15442
|
+
lastGrantActivityTs = Date.now() - budget.idleMs();
|
|
15443
|
+
preemptState = "active" /* ACTIVE */;
|
|
15444
|
+
preemptedCeiling = null;
|
|
14151
15445
|
scheduleNext();
|
|
14152
15446
|
return;
|
|
14153
15447
|
}
|
|
14154
|
-
|
|
14155
|
-
|
|
15448
|
+
terminate("timeout", limit, elapsed);
|
|
15449
|
+
return;
|
|
14156
15450
|
} catch (err) {
|
|
14157
15451
|
this.subagents.get(ctx.subagentId)?.abortController.abort();
|
|
14158
15452
|
reject(
|
|
14159
15453
|
err instanceof BudgetExceededError ? err : new BudgetExceededError("timeout", limit, elapsed)
|
|
14160
15454
|
);
|
|
15455
|
+
return;
|
|
15456
|
+
} finally {
|
|
15457
|
+
budget.clearWatchdogNegotiation();
|
|
14161
15458
|
}
|
|
14162
15459
|
};
|
|
14163
15460
|
scheduleNext();
|
|
@@ -14943,7 +16240,7 @@ var InMemoryAgentBridge = class {
|
|
|
14943
16240
|
});
|
|
14944
16241
|
}
|
|
14945
16242
|
this.inflightGuards.add(correlationId);
|
|
14946
|
-
return new Promise((
|
|
16243
|
+
return new Promise((resolve6, reject) => {
|
|
14947
16244
|
const timer = setTimeout(() => {
|
|
14948
16245
|
this.inflightGuards.delete(correlationId);
|
|
14949
16246
|
this.pendingRequests.delete(correlationId);
|
|
@@ -14962,7 +16259,7 @@ var InMemoryAgentBridge = class {
|
|
|
14962
16259
|
return;
|
|
14963
16260
|
}
|
|
14964
16261
|
this.pendingRequests.set(correlationId, {
|
|
14965
|
-
resolve:
|
|
16262
|
+
resolve: resolve6,
|
|
14966
16263
|
reject,
|
|
14967
16264
|
timer
|
|
14968
16265
|
});
|
|
@@ -15634,6 +16931,24 @@ Working rules:
|
|
|
15634
16931
|
var DEFAULT_SUBAGENT_BASELINE = `You are a subagent operating under a Director. You were spawned to handle
|
|
15635
16932
|
a specific slice of a larger plan \u2014 do that slice well and report back.
|
|
15636
16933
|
|
|
16934
|
+
Capabilities & operating rules:
|
|
16935
|
+
- You have full developer tools for your task: read, write/edit, search,
|
|
16936
|
+
shell + build (lint, format, typecheck, test), and dependency install.
|
|
16937
|
+
Use them directly to finish the task end-to-end. You run non-interactively
|
|
16938
|
+
\u2014 there is no human to approve individual tool calls, so routine work is
|
|
16939
|
+
pre-authorized; do not stop to ask for permission to read, edit, or build.
|
|
16940
|
+
- Stay inside the project root. Do not write files outside the repository,
|
|
16941
|
+
and do not touch machine config, credentials, or global state \u2014 those
|
|
16942
|
+
require an explicit grant you do not have.
|
|
16943
|
+
- Prefer the least-destructive path. Do not run irreversible or destructive
|
|
16944
|
+
commands (e.g. \`rm -rf\`, \`git push --force\`, history rewrites, dropping
|
|
16945
|
+
databases, mass deletes) unless the task explicitly requires it and names
|
|
16946
|
+
the target.
|
|
16947
|
+
- When you change code, verify it: run the relevant build / typecheck / tests
|
|
16948
|
+
and fix what you broke before reporting done.
|
|
16949
|
+
- Make only the changes the task calls for \u2014 don't refactor or reformat
|
|
16950
|
+
unrelated code.
|
|
16951
|
+
|
|
15637
16952
|
Bridge contract:
|
|
15638
16953
|
- You have a parent (the Director). You may call \`request\` on the
|
|
15639
16954
|
parent bridge to ask a clarifying question. Use this sparingly; the
|
|
@@ -16591,6 +17906,8 @@ var Director = class _Director {
|
|
|
16591
17906
|
sessionsRoot;
|
|
16592
17907
|
/** Director run id for JSONL path resolution. */
|
|
16593
17908
|
directorRunId;
|
|
17909
|
+
/** Optional logger for structured logging. Falls back to noop when omitted. */
|
|
17910
|
+
logger;
|
|
16594
17911
|
/** Resolves task descriptions back from `assign()` so completion events
|
|
16595
17912
|
* can also carry a human-readable title. */
|
|
16596
17913
|
taskDescriptions = /* @__PURE__ */ new Map();
|
|
@@ -16679,6 +17996,7 @@ var Director = class _Director {
|
|
|
16679
17996
|
opts.checkpointDebounceMs ?? 250
|
|
16680
17997
|
) : null;
|
|
16681
17998
|
this.fleetManager = opts.fleetManager;
|
|
17999
|
+
this.logger = opts.logger;
|
|
16682
18000
|
if (this.sharedScratchpadPath) {
|
|
16683
18001
|
void fsp2.mkdir(this.sharedScratchpadPath, { recursive: true }).catch((err) => this.logShutdownError("shared_scratchpad_mkdir", err));
|
|
16684
18002
|
}
|
|
@@ -16978,7 +18296,10 @@ var Director = class _Director {
|
|
|
16978
18296
|
entry.session.cancel(reason);
|
|
16979
18297
|
for (const [_role, subagentId] of entry.session.getSubagentIds()) {
|
|
16980
18298
|
this.coordinator.stop(subagentId).catch((err) => {
|
|
16981
|
-
|
|
18299
|
+
this.logger?.debug(`stop subagent ${subagentId} failed (may have already completed)`, {
|
|
18300
|
+
subagentId,
|
|
18301
|
+
err: err instanceof Error ? err.message : String(err)
|
|
18302
|
+
});
|
|
16982
18303
|
});
|
|
16983
18304
|
}
|
|
16984
18305
|
}
|
|
@@ -17038,7 +18359,7 @@ var Director = class _Director {
|
|
|
17038
18359
|
* Caller-supplied `priceLookup` is optional but recommended — without
|
|
17039
18360
|
* it the `cost` column in `usage.snapshot()` stays at 0.
|
|
17040
18361
|
*/
|
|
17041
|
-
async spawn(
|
|
18362
|
+
async spawn(callerConfig, priceLookup) {
|
|
17042
18363
|
if (this.workCompleteFlag) {
|
|
17043
18364
|
throw new FleetSpawnBudgetError(
|
|
17044
18365
|
"max_spawns",
|
|
@@ -17047,6 +18368,7 @@ var Director = class _Director {
|
|
|
17047
18368
|
"workComplete() has been called \u2014 director closed further spawning"
|
|
17048
18369
|
);
|
|
17049
18370
|
}
|
|
18371
|
+
const config = { ...callerConfig };
|
|
17050
18372
|
if (!config.model && this.modelMatrix) {
|
|
17051
18373
|
const matrix = typeof this.modelMatrix === "function" ? this.modelMatrix() : this.modelMatrix;
|
|
17052
18374
|
const entry = resolveModelMatrix(matrix, config.role);
|
|
@@ -17272,7 +18594,7 @@ var Director = class _Director {
|
|
|
17272
18594
|
})),
|
|
17273
18595
|
usage: this.usage.snapshot()
|
|
17274
18596
|
};
|
|
17275
|
-
await fsp2.mkdir(
|
|
18597
|
+
await fsp2.mkdir(path4.dirname(this.manifestPath), { recursive: true });
|
|
17276
18598
|
await atomicWrite(this.manifestPath, JSON.stringify(manifest, null, 2), { mode: 384 });
|
|
17277
18599
|
return this.manifestPath;
|
|
17278
18600
|
}
|
|
@@ -17399,11 +18721,11 @@ var Director = class _Director {
|
|
|
17399
18721
|
if (cached) return cached;
|
|
17400
18722
|
const existing = this.taskWaiters.get(id);
|
|
17401
18723
|
if (existing) return existing.promise;
|
|
17402
|
-
let
|
|
18724
|
+
let resolve6;
|
|
17403
18725
|
const promise = new Promise((res) => {
|
|
17404
|
-
|
|
18726
|
+
resolve6 = res;
|
|
17405
18727
|
});
|
|
17406
|
-
this.taskWaiters.set(id, { promise, resolve:
|
|
18728
|
+
this.taskWaiters.set(id, { promise, resolve: resolve6 });
|
|
17407
18729
|
return promise;
|
|
17408
18730
|
})
|
|
17409
18731
|
);
|
|
@@ -17486,7 +18808,7 @@ var Director = class _Director {
|
|
|
17486
18808
|
*/
|
|
17487
18809
|
async readSession(subagentId, tail) {
|
|
17488
18810
|
if (!this.sessionsRoot) return null;
|
|
17489
|
-
const filePath =
|
|
18811
|
+
const filePath = path4.join(this.sessionsRoot, this.directorRunId, `${subagentId}.jsonl`);
|
|
17490
18812
|
let raw;
|
|
17491
18813
|
try {
|
|
17492
18814
|
raw = await fsp2.readFile(filePath, "utf8");
|
|
@@ -17799,7 +19121,7 @@ function createDelegateTool(opts) {
|
|
|
17799
19121
|
subagentId
|
|
17800
19122
|
});
|
|
17801
19123
|
const dir = director;
|
|
17802
|
-
const result = await new Promise((
|
|
19124
|
+
const result = await new Promise((resolve6) => {
|
|
17803
19125
|
let settled = false;
|
|
17804
19126
|
let timer;
|
|
17805
19127
|
const finish = (value) => {
|
|
@@ -17809,7 +19131,7 @@ function createDelegateTool(opts) {
|
|
|
17809
19131
|
offTool();
|
|
17810
19132
|
offIter();
|
|
17811
19133
|
offProgress();
|
|
17812
|
-
|
|
19134
|
+
resolve6(value);
|
|
17813
19135
|
};
|
|
17814
19136
|
const arm = () => {
|
|
17815
19137
|
if (timer) clearTimeout(timer);
|
|
@@ -18016,13 +19338,13 @@ async function readSubagentPartial(opts, subagentId) {
|
|
|
18016
19338
|
if (!opts.sessionsRoot) return void 0;
|
|
18017
19339
|
const candidates = [];
|
|
18018
19340
|
if (opts.directorRunId) {
|
|
18019
|
-
candidates.push(
|
|
19341
|
+
candidates.push(path4.join(opts.sessionsRoot, opts.directorRunId, `${subagentId}.jsonl`));
|
|
18020
19342
|
} else {
|
|
18021
19343
|
try {
|
|
18022
19344
|
const entries = await fsp2.readdir(opts.sessionsRoot, { withFileTypes: true });
|
|
18023
19345
|
for (const entry of entries) {
|
|
18024
19346
|
if (entry.isDirectory()) {
|
|
18025
|
-
candidates.push(
|
|
19347
|
+
candidates.push(path4.join(opts.sessionsRoot, entry.name, `${subagentId}.jsonl`));
|
|
18026
19348
|
}
|
|
18027
19349
|
}
|
|
18028
19350
|
} catch {
|
|
@@ -18072,9 +19394,9 @@ function makeDirectorSessionFactory(opts) {
|
|
|
18072
19394
|
let dir;
|
|
18073
19395
|
if (opts.store) {
|
|
18074
19396
|
store = opts.store;
|
|
18075
|
-
dir = opts.sessionsRoot ?
|
|
19397
|
+
dir = opts.sessionsRoot ? path4.join(opts.sessionsRoot, runId) : "(caller-managed)";
|
|
18076
19398
|
} else if (opts.sessionsRoot) {
|
|
18077
|
-
dir =
|
|
19399
|
+
dir = path4.join(opts.sessionsRoot, runId);
|
|
18078
19400
|
store = new DefaultSessionStore({ dir });
|
|
18079
19401
|
} else {
|
|
18080
19402
|
throw new Error("makeDirectorSessionFactory requires either `store` or `sessionsRoot`");
|
|
@@ -18120,6 +19442,7 @@ function attachAutoExtend(events, policy = {}) {
|
|
|
18120
19442
|
const extendCounts = /* @__PURE__ */ new Map();
|
|
18121
19443
|
let progress = 0;
|
|
18122
19444
|
let lastTimeoutProgress = -1;
|
|
19445
|
+
let lastSeenKey = null;
|
|
18123
19446
|
const unsubs = [
|
|
18124
19447
|
events.on("tool.executed", () => {
|
|
18125
19448
|
progress++;
|
|
@@ -18129,6 +19452,9 @@ function attachAutoExtend(events, policy = {}) {
|
|
|
18129
19452
|
}),
|
|
18130
19453
|
events.on("budget.threshold_reached", (e) => {
|
|
18131
19454
|
const { kind, limit, extend, deny } = e;
|
|
19455
|
+
const key = `${kind}:${limit}`;
|
|
19456
|
+
if (key === lastSeenKey) return;
|
|
19457
|
+
lastSeenKey = key;
|
|
18132
19458
|
if (kind === "timeout" || kind === "idle_timeout") {
|
|
18133
19459
|
if (progress > lastTimeoutProgress) {
|
|
18134
19460
|
lastTimeoutProgress = progress;
|
|
@@ -18220,7 +19546,7 @@ var DefaultModelsRegistry = class {
|
|
|
18220
19546
|
this.overlay = opts.overlay;
|
|
18221
19547
|
this.overlayUrl = opts.overlayUrl;
|
|
18222
19548
|
this.overlayFile = opts.overlayFile;
|
|
18223
|
-
this.overlayCacheFile = opts.overlayCacheFile ?? (opts.overlayUrl ?
|
|
19549
|
+
this.overlayCacheFile = opts.overlayCacheFile ?? (opts.overlayUrl ? path4.join(path4.dirname(opts.cacheFile), "models-overlay-cache.json") : void 0);
|
|
18224
19550
|
}
|
|
18225
19551
|
async load(opts = {}) {
|
|
18226
19552
|
if (this.payload && !opts.force) return this.payload;
|
|
@@ -18433,7 +19759,7 @@ var DefaultModelsRegistry = class {
|
|
|
18433
19759
|
}
|
|
18434
19760
|
/** Used by `wstack models refresh` to expose where the cache lives. */
|
|
18435
19761
|
cacheLocation() {
|
|
18436
|
-
return
|
|
19762
|
+
return path4.resolve(this.cacheFile);
|
|
18437
19763
|
}
|
|
18438
19764
|
};
|
|
18439
19765
|
function hasEntries(payload) {
|
|
@@ -18800,7 +20126,7 @@ var DefaultModeStore = class {
|
|
|
18800
20126
|
}
|
|
18801
20127
|
async loadActiveMode() {
|
|
18802
20128
|
try {
|
|
18803
|
-
const configPath =
|
|
20129
|
+
const configPath = path4.join(this.configDir, "mode.json");
|
|
18804
20130
|
const content = await fsp2.readFile(configPath, "utf8");
|
|
18805
20131
|
const data = JSON.parse(content);
|
|
18806
20132
|
this.activeModeId = data.activeMode ?? null;
|
|
@@ -18811,7 +20137,7 @@ var DefaultModeStore = class {
|
|
|
18811
20137
|
async saveActiveMode() {
|
|
18812
20138
|
try {
|
|
18813
20139
|
await fsp2.mkdir(this.configDir, { recursive: true });
|
|
18814
|
-
const configPath =
|
|
20140
|
+
const configPath = path4.join(this.configDir, "mode.json");
|
|
18815
20141
|
await atomicWrite(
|
|
18816
20142
|
configPath,
|
|
18817
20143
|
JSON.stringify({ activeMode: this.activeModeId }, null, 2)
|
|
@@ -18826,11 +20152,11 @@ async function loadProjectModes(modesDir) {
|
|
|
18826
20152
|
const entries = await fsp2.readdir(modesDir);
|
|
18827
20153
|
for (const entry of entries) {
|
|
18828
20154
|
if (!entry.endsWith(".md") && !entry.endsWith(".txt")) continue;
|
|
18829
|
-
const filePath =
|
|
20155
|
+
const filePath = path4.join(modesDir, entry);
|
|
18830
20156
|
const stat6 = await fsp2.stat(filePath);
|
|
18831
20157
|
if (!stat6.isFile()) continue;
|
|
18832
20158
|
const content = await fsp2.readFile(filePath, "utf8");
|
|
18833
|
-
const id =
|
|
20159
|
+
const id = path4.basename(entry, path4.extname(entry));
|
|
18834
20160
|
modes.push({
|
|
18835
20161
|
id,
|
|
18836
20162
|
name: id.replace(/[-_]/g, " ").replace(/\b\w/g, (c) => c.toUpperCase()),
|
|
@@ -18846,7 +20172,7 @@ async function loadProjectModes(modesDir) {
|
|
|
18846
20172
|
async function loadUserModes(modesDir) {
|
|
18847
20173
|
const modes = [];
|
|
18848
20174
|
try {
|
|
18849
|
-
const manifestPath =
|
|
20175
|
+
const manifestPath = path4.join(modesDir, "modes.json");
|
|
18850
20176
|
const content = await fsp2.readFile(manifestPath, "utf8");
|
|
18851
20177
|
const manifest = JSON.parse(content);
|
|
18852
20178
|
for (const mode of manifest.modes) {
|
|
@@ -18857,6 +20183,35 @@ async function loadUserModes(modesDir) {
|
|
|
18857
20183
|
return modes;
|
|
18858
20184
|
}
|
|
18859
20185
|
|
|
20186
|
+
// src/models/provider-model-resolve.ts
|
|
20187
|
+
function describeCatalogModel(m) {
|
|
20188
|
+
return {
|
|
20189
|
+
id: m.id,
|
|
20190
|
+
name: m.name,
|
|
20191
|
+
releaseDate: m.release_date,
|
|
20192
|
+
contextWindow: m.limit?.context,
|
|
20193
|
+
inputCost: m.cost?.input,
|
|
20194
|
+
outputCost: m.cost?.output,
|
|
20195
|
+
capabilities: [
|
|
20196
|
+
...m.tool_call ? ["tools"] : [],
|
|
20197
|
+
...m.reasoning ? ["reasoning"] : [],
|
|
20198
|
+
...m.modalities?.input?.includes("image") ? ["vision"] : [],
|
|
20199
|
+
...m.open_weights ? ["open_weights"] : []
|
|
20200
|
+
]
|
|
20201
|
+
};
|
|
20202
|
+
}
|
|
20203
|
+
function resolveProviderModelList(savedModels, catalog) {
|
|
20204
|
+
if (savedModels && savedModels.length > 0) {
|
|
20205
|
+
const byId = new Map((catalog?.models ?? []).map((m) => [m.id, m]));
|
|
20206
|
+
return savedModels.map((id) => {
|
|
20207
|
+
const hit = byId.get(id);
|
|
20208
|
+
return hit ? describeCatalogModel(hit) : { id, name: id, capabilities: [] };
|
|
20209
|
+
});
|
|
20210
|
+
}
|
|
20211
|
+
if (catalog) return catalog.models.map(describeCatalogModel);
|
|
20212
|
+
return [];
|
|
20213
|
+
}
|
|
20214
|
+
|
|
18860
20215
|
// src/sdd/spec-parser.ts
|
|
18861
20216
|
var SpecParser = class {
|
|
18862
20217
|
parse(content) {
|
|
@@ -19798,7 +21153,7 @@ var SpecStore = class {
|
|
|
19798
21153
|
indexPath;
|
|
19799
21154
|
constructor(opts) {
|
|
19800
21155
|
this.baseDir = opts.baseDir;
|
|
19801
|
-
this.indexPath =
|
|
21156
|
+
this.indexPath = path4.join(this.baseDir, "_index.json");
|
|
19802
21157
|
}
|
|
19803
21158
|
async save(spec) {
|
|
19804
21159
|
await ensureDir(this.baseDir);
|
|
@@ -19867,7 +21222,7 @@ var SpecStore = class {
|
|
|
19867
21222
|
return updated;
|
|
19868
21223
|
}
|
|
19869
21224
|
filePath(id) {
|
|
19870
|
-
return
|
|
21225
|
+
return path4.join(this.baseDir, `${id}.json`);
|
|
19871
21226
|
}
|
|
19872
21227
|
async readIndex() {
|
|
19873
21228
|
try {
|
|
@@ -19924,7 +21279,7 @@ var TaskGraphStore = class {
|
|
|
19924
21279
|
indexPath;
|
|
19925
21280
|
constructor(opts) {
|
|
19926
21281
|
this.baseDir = opts.baseDir;
|
|
19927
|
-
this.indexPath =
|
|
21282
|
+
this.indexPath = path4.join(this.baseDir, "_index.json");
|
|
19928
21283
|
}
|
|
19929
21284
|
async save(graph) {
|
|
19930
21285
|
await ensureDir(this.baseDir);
|
|
@@ -19962,7 +21317,7 @@ var TaskGraphStore = class {
|
|
|
19962
21317
|
}
|
|
19963
21318
|
}
|
|
19964
21319
|
filePath(id) {
|
|
19965
|
-
return
|
|
21320
|
+
return path4.join(this.baseDir, `${id}.json`);
|
|
19966
21321
|
}
|
|
19967
21322
|
async readIndex() {
|
|
19968
21323
|
try {
|
|
@@ -20207,9 +21562,9 @@ var AISpecBuilder = class {
|
|
|
20207
21562
|
if (!this.sessionPath) return;
|
|
20208
21563
|
try {
|
|
20209
21564
|
const fsp16 = await import('fs/promises');
|
|
20210
|
-
const
|
|
21565
|
+
const path21 = await import('path');
|
|
20211
21566
|
const { atomicWrite: atomicWrite2 } = await Promise.resolve().then(() => (init_atomic_write(), atomic_write_exports));
|
|
20212
|
-
await fsp16.mkdir(
|
|
21567
|
+
await fsp16.mkdir(path21.dirname(this.sessionPath), { recursive: true });
|
|
20213
21568
|
await atomicWrite2(this.sessionPath, JSON.stringify(this.session, null, 2));
|
|
20214
21569
|
} catch {
|
|
20215
21570
|
}
|
|
@@ -20936,15 +22291,15 @@ function computeCriticalPath(graph, _topoOrder, blockedByMap) {
|
|
|
20936
22291
|
maxId = id;
|
|
20937
22292
|
}
|
|
20938
22293
|
}
|
|
20939
|
-
const
|
|
22294
|
+
const path21 = [];
|
|
20940
22295
|
let current = maxId;
|
|
20941
22296
|
const visited = /* @__PURE__ */ new Set();
|
|
20942
22297
|
while (current && !visited.has(current)) {
|
|
20943
22298
|
visited.add(current);
|
|
20944
|
-
|
|
22299
|
+
path21.unshift(current);
|
|
20945
22300
|
current = prev.get(current) ?? null;
|
|
20946
22301
|
}
|
|
20947
|
-
return
|
|
22302
|
+
return path21;
|
|
20948
22303
|
}
|
|
20949
22304
|
function computeParallelGroups(graph, blockedByMap) {
|
|
20950
22305
|
const groups = [];
|
|
@@ -21767,9 +23122,9 @@ var DefaultHealthRegistry = class {
|
|
|
21767
23122
|
}
|
|
21768
23123
|
async runOne(check) {
|
|
21769
23124
|
let timer = null;
|
|
21770
|
-
const timeout = new Promise((
|
|
23125
|
+
const timeout = new Promise((resolve6) => {
|
|
21771
23126
|
timer = setTimeout(
|
|
21772
|
-
() =>
|
|
23127
|
+
() => resolve6({ status: "unhealthy", detail: `timeout after ${this.timeoutMs}ms` }),
|
|
21773
23128
|
this.timeoutMs
|
|
21774
23129
|
);
|
|
21775
23130
|
});
|
|
@@ -21952,7 +23307,7 @@ async function startMetricsServer(opts) {
|
|
|
21952
23307
|
const tls = opts.tls;
|
|
21953
23308
|
const useHttps = !!(tls?.cert && tls?.key);
|
|
21954
23309
|
const host = opts.host ?? "127.0.0.1";
|
|
21955
|
-
const
|
|
23310
|
+
const path21 = opts.path ?? "/metrics";
|
|
21956
23311
|
const healthPath = opts.healthPath ?? "/healthz";
|
|
21957
23312
|
const healthRegistry = opts.healthRegistry;
|
|
21958
23313
|
const listener = (req, res) => {
|
|
@@ -21962,7 +23317,7 @@ async function startMetricsServer(opts) {
|
|
|
21962
23317
|
return;
|
|
21963
23318
|
}
|
|
21964
23319
|
const url = req.url.split("?")[0];
|
|
21965
|
-
if (url ===
|
|
23320
|
+
if (url === path21) {
|
|
21966
23321
|
let body;
|
|
21967
23322
|
try {
|
|
21968
23323
|
body = renderPrometheus(opts.sink.snapshot());
|
|
@@ -22008,14 +23363,14 @@ async function startMetricsServer(opts) {
|
|
|
22008
23363
|
const { createServer } = await import('http');
|
|
22009
23364
|
server = createServer(listener);
|
|
22010
23365
|
}
|
|
22011
|
-
await new Promise((
|
|
23366
|
+
await new Promise((resolve6, reject) => {
|
|
22012
23367
|
const onError = (err) => {
|
|
22013
23368
|
server.off("listening", onListening);
|
|
22014
23369
|
reject(err);
|
|
22015
23370
|
};
|
|
22016
23371
|
const onListening = () => {
|
|
22017
23372
|
server.off("error", onError);
|
|
22018
|
-
|
|
23373
|
+
resolve6();
|
|
22019
23374
|
};
|
|
22020
23375
|
server.once("error", onError);
|
|
22021
23376
|
server.once("listening", onListening);
|
|
@@ -22026,9 +23381,9 @@ async function startMetricsServer(opts) {
|
|
|
22026
23381
|
const protocol = useHttps ? "https" : "http";
|
|
22027
23382
|
return {
|
|
22028
23383
|
port: boundPort,
|
|
22029
|
-
url: `${protocol}://${host}:${boundPort}${
|
|
22030
|
-
close: () => new Promise((
|
|
22031
|
-
server.close((err) => err ? reject(err) :
|
|
23384
|
+
url: `${protocol}://${host}:${boundPort}${path21}`,
|
|
23385
|
+
close: () => new Promise((resolve6, reject) => {
|
|
23386
|
+
server.close((err) => err ? reject(err) : resolve6());
|
|
22032
23387
|
})
|
|
22033
23388
|
};
|
|
22034
23389
|
}
|
|
@@ -22343,11 +23698,12 @@ function createContextManagerTool(opts = {}) {
|
|
|
22343
23698
|
const applyMessages = (next) => {
|
|
22344
23699
|
const repaired = repairToolUseAdjacency(next);
|
|
22345
23700
|
const finalMessages = repaired.messages;
|
|
23701
|
+
if (finalMessages === messages) return repaired.report;
|
|
22346
23702
|
if (ctx.state) {
|
|
22347
23703
|
ctx.state.replaceMessages(finalMessages);
|
|
22348
23704
|
} else {
|
|
22349
23705
|
messages.length = 0;
|
|
22350
|
-
messages.
|
|
23706
|
+
messages.push(...finalMessages);
|
|
22351
23707
|
}
|
|
22352
23708
|
return repaired.report;
|
|
22353
23709
|
};
|
|
@@ -22422,9 +23778,15 @@ function createContextManagerTool(opts = {}) {
|
|
|
22422
23778
|
}
|
|
22423
23779
|
const report = await opts.compactor.compact(ctx);
|
|
22424
23780
|
ctx.clearFileTracking();
|
|
22425
|
-
|
|
22426
|
-
|
|
22427
|
-
|
|
23781
|
+
let repaired = report.repaired;
|
|
23782
|
+
let afterTokens;
|
|
23783
|
+
if (!ctx.state) {
|
|
23784
|
+
const repair = applyMessages([...ctx.messages]);
|
|
23785
|
+
repaired = report.repaired ?? (repair.changed ? repair : void 0);
|
|
23786
|
+
afterTokens = repair.changed ? roughEstimate(ctx.messages) : report.after;
|
|
23787
|
+
} else {
|
|
23788
|
+
afterTokens = report.after;
|
|
23789
|
+
}
|
|
22428
23790
|
const reduced = report.fullRequestTokensBefore > report.fullRequestTokensAfter;
|
|
22429
23791
|
const repairedSomething = !!report.repaired;
|
|
22430
23792
|
if (reduced || repairedSomething) {
|
|
@@ -22679,6 +24041,6 @@ var allServers = () => ({
|
|
|
22679
24041
|
playwright: { ...playwrightServer(), enabled: false }
|
|
22680
24042
|
});
|
|
22681
24043
|
|
|
22682
|
-
export { AGENTS_BY_PHASE, AGENT_CATALOG, AISpecBuilder, ALL_AGENT_DEFINITIONS, ALL_FLEET_AGENTS, AUDIT_LOG_AGENT, AutoApprovePermissionPolicy, AutoCompactionMiddleware, AutoExecutor, AutonomousRunner, BUG_HUNTER_AGENT, BudgetExceededError, ConfigMigrationError, DEFAULT_AUTONOMY_CONFIG, DEFAULT_CONFIG_MIGRATIONS, DEFAULT_CONTEXT_CONFIG, DEFAULT_DIRECTOR_PREAMBLE, DEFAULT_DISPATCH_ROLE, DEFAULT_SUBAGENT_BASELINE, DEFAULT_TOOLS_CONFIG, DefaultAttachmentStore, DefaultConfigLoader, DefaultConfigStore, DefaultErrorHandler, DefaultHealthRegistry, DefaultLogger, DefaultMemoryStore, DefaultModeStore, DefaultModelsRegistry, DefaultMultiAgentCoordinator, DefaultPermissionPolicy, DefaultProviderRunner, DefaultRetryPolicy, DefaultSecretScrubber, DefaultSecretVault, DefaultSessionReader, DefaultSessionStore, DefaultSkillLoader, DefaultTaskStore, Director, DirectorStateCheckpoint, DoneConditionChecker, EternalAutonomyEngine, FLEET_ROSTER, FLEET_ROSTER_BUDGETS, FleetBus, FleetSpawnBudgetError, FleetUsageAggregator, HybridCompactor, InMemoryAgentBridge, InMemoryBridgeTransport, InMemoryMetricsSink, IntelligentCompactor, LLMSelector, NULL_FLEET_BUS, NoopMetricsSink, NoopTracer, OTelTracer, PROMETHEUS_CONTENT_TYPE, ParallelEternalEngine, QueueStore, REFACTOR_PLANNER_AGENT, RecoveryLock, SECURITY_SCANNER_AGENT, SPEC_TEMPLATES, SddParallelRun, SddTaskDecomposer, SelectiveCompactor, SessionAnalyzer, SpecDrivenDev, SpecParser, SpecStore, SpecVersioning, SubagentBudget, TaskFlow, TaskGenerator, TaskGraphStore, TaskTracker, ToolExecutor, addPlanItem, allServers, analyzeCriticalPath, applyRosterBudget, attachAutoExtend, attachPlanCheckpoint, attachTodosCheckpoint, awsServer, blockServer, braveSearchServer, buildGoalPreamble, buildOtlpMetricsRequest, buildOtlpTracesRequest, classifyFamily, clearPlan, composeDirectorPrompt, composeSubagentPrompt, context7Server, contextManagerTool, createAutoExecutor, createContextManagerTool, createDelegateTool, createMessage, createSessionEventBridge, createStrategyCompactor, decryptConfigSecrets, deriveTodosFromPlanItem, dispatchAgent, emptyPlan, encryptConfigSecrets, everArtServer, filesystemServer, formatPlan, formatPlanTemplates, getAgentDefinition, getPlanTemplate, getTemplate, githubServer, googleMapsServer, listPlanTemplates, listTemplates, loadDirectorState, loadPlan, loadProjectModes, loadTodosCheckpoint, loadUserModes, makeAgentSubagentRunner, makeAskTool, makeAssignTool, makeAutonomyPromptContributor, makeAwaitTasksTool, makeCollabDebugTool, makeDirectorSessionFactory, makeFleetEmitTool, makeFleetHealthTool, makeFleetSessionTool, makeFleetStatusTool, makeFleetUsageTool, makeLLMClassifier, makeRollUpTool, makeSpawnTool, makeTerminateTool, migratePlaintextSecrets, miniMaxVisionServer, removePlanItem, renderProgress, renderPrometheus, renderSpecAnalysis, renderTaskGraph, renderTaskList, resolveAuditLevel, resolveSessionLoggingConfig, rewriteConfigEncrypted, rosterSummaryFromConfigs, runConfigMigrations, savePlan, saveTodosCheckpoint, scoreAgents, sentinelServer, setPlanItemStatus, slackServer, startMetricsServer, startOtlpMetricsExporter, startOtlpTraceExporter, templateToMarkdown, wireMetricsToEvents, zaiVisionServer };
|
|
24044
|
+
export { AGENTS_BY_PHASE, AGENT_CATALOG, AISpecBuilder, ALL_AGENT_DEFINITIONS, ALL_FLEET_AGENTS, AUDIT_LOG_AGENT, AutoApprovePermissionPolicy, AutoCompactionMiddleware, AutoExecutor, AutonomousRunner, BUG_HUNTER_AGENT, BudgetExceededError, ConfigMigrationError, DEFAULT_AUTONOMY_CONFIG, DEFAULT_CONFIG_MIGRATIONS, DEFAULT_CONTEXT_CONFIG, DEFAULT_DIRECTOR_PREAMBLE, DEFAULT_DISPATCH_ROLE, DEFAULT_SUBAGENT_BASELINE, DEFAULT_TOOLS_CONFIG, DefaultAttachmentStore, DefaultConfigLoader, DefaultConfigStore, DefaultErrorHandler, DefaultHealthRegistry, DefaultLogger, DefaultMemoryStore, DefaultModeStore, DefaultModelsRegistry, DefaultMultiAgentCoordinator, DefaultPermissionPolicy, DefaultProviderRunner, DefaultRetryPolicy, DefaultSecretScrubber, DefaultSecretVault, DefaultSessionReader, DefaultSessionStore, DefaultSkillLoader, DefaultTaskStore, Director, DirectorStateCheckpoint, DoneConditionChecker, EternalAutonomyEngine, FLEET_ROSTER, FLEET_ROSTER_BUDGETS, FleetBus, FleetSpawnBudgetError, FleetUsageAggregator, HybridCompactor, InMemoryAgentBridge, InMemoryBridgeTransport, InMemoryMetricsSink, IntelligentCompactor, LLMSelector, NULL_FLEET_BUS, NoopMetricsSink, NoopTracer, OTelTracer, PROMETHEUS_CONTENT_TYPE, ParallelEternalEngine, QueueStore, REFACTOR_PLANNER_AGENT, RecoveryLock, SECURITY_SCANNER_AGENT, SPEC_TEMPLATES, SddParallelRun, SddTaskDecomposer, SelectiveCompactor, SessionAnalyzer, SpecDrivenDev, SpecParser, SpecStore, SpecVersioning, SubagentBudget, TaskFlow, TaskGenerator, TaskGraphStore, TaskTracker, ToolExecutor, addPlanItem, allServers, analyzeCriticalPath, applyRosterBudget, attachAutoExtend, attachPlanCheckpoint, attachTodosCheckpoint, awsServer, blockServer, braveSearchServer, buildGoalPreamble, buildOtlpMetricsRequest, buildOtlpTracesRequest, classifyFamily, clearPlan, composeDirectorPrompt, composeSubagentPrompt, context7Server, contextManagerTool, createAutoExecutor, createContextManagerTool, createDelegateTool, createMessage, createSessionEventBridge, createStrategyCompactor, decryptConfigSecrets, deriveTodosFromPlanItem, describeCatalogModel, dispatchAgent, emptyPlan, encryptConfigSecrets, everArtServer, filesystemServer, formatPlan, formatPlanTemplates, getAgentDefinition, getPlanTemplate, getTemplate, githubServer, googleMapsServer, listPlanTemplates, listTemplates, loadDirectorState, loadPlan, loadProjectModes, loadTodosCheckpoint, loadUserModes, makeAgentSubagentRunner, makeAskTool, makeAssignTool, makeAutonomyPromptContributor, makeAwaitTasksTool, makeCollabDebugTool, makeDirectorSessionFactory, makeFleetEmitTool, makeFleetHealthTool, makeFleetSessionTool, makeFleetStatusTool, makeFleetUsageTool, makeLLMClassifier, makeRollUpTool, makeSpawnTool, makeTerminateTool, migratePlaintextSecrets, miniMaxVisionServer, removePlanItem, renderProgress, renderPrometheus, renderSpecAnalysis, renderTaskGraph, renderTaskList, resolveAuditLevel, resolveProviderModelList, resolveSessionLoggingConfig, rewriteConfigEncrypted, rosterSummaryFromConfigs, runConfigMigrations, savePlan, saveTodosCheckpoint, scoreAgents, sentinelServer, setPlanItemStatus, slackServer, startMetricsServer, startOtlpMetricsExporter, startOtlpTraceExporter, templateToMarkdown, wireMetricsToEvents, zaiVisionServer };
|
|
22683
24045
|
//# sourceMappingURL=index.js.map
|
|
22684
24046
|
//# sourceMappingURL=index.js.map
|