@wrongstack/core 0.87.0 → 0.89.3
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-C9P_HPez.d.ts → agent-bridge-DbVe1dHT.d.ts} +1 -1
- package/dist/{agent-subagent-runner-2Aq0jOSj.d.ts → agent-subagent-runner-C6eqID1t.d.ts} +477 -476
- package/dist/{events-DnRqXaZ3.d.ts → brain-s9DyD_Vf.d.ts} +185 -92
- package/dist/{compactor-CJq7LQev.d.ts → compactor-DXLxLcmU.d.ts} +2 -2
- package/dist/{config-_DZ7dN-T.d.ts → config-84VaZpH6.d.ts} +45 -38
- package/dist/{context-ToHAp4-U.d.ts → context-CNRYfhUv.d.ts} +1 -1
- package/dist/coordination/index.d.ts +16 -17
- package/dist/coordination/index.js +208 -93
- package/dist/coordination/index.js.map +1 -1
- package/dist/{default-config-DEXI4jsl.d.ts → default-config-CXsDvOmP.d.ts} +3 -1
- package/dist/defaults/index.d.ts +32 -34
- package/dist/defaults/index.js +744 -274
- package/dist/defaults/index.js.map +1 -1
- package/dist/{director-state-CgIc30qi.d.ts → director-state-BfeCUbmk.d.ts} +1 -1
- package/dist/execution/index.d.ts +18 -19
- package/dist/execution/index.js +79 -64
- package/dist/execution/index.js.map +1 -1
- package/dist/extension/index.d.ts +9 -10
- package/dist/extension/index.js +8 -6
- package/dist/extension/index.js.map +1 -1
- package/dist/{index-BNOLadHw.d.ts → index-CqrroYcU.d.ts} +13 -15
- package/dist/{index-N0_c4bHQ.d.ts → index-D3Nax3YU.d.ts} +11 -10
- package/dist/index.d.ts +191 -178
- package/dist/index.js +1419 -526
- package/dist/index.js.map +1 -1
- package/dist/infrastructure/index.d.ts +9 -9
- package/dist/infrastructure/index.js +16 -5
- package/dist/infrastructure/index.js.map +1 -1
- package/dist/kernel/index.d.ts +14 -16
- package/dist/kernel/index.js +17 -0
- package/dist/kernel/index.js.map +1 -1
- package/dist/{logger-B72yyPc6.d.ts → logger-B63L5bTg.d.ts} +1 -1
- package/dist/{logger-C_27pj9i.d.ts → logger-B9J5puGM.d.ts} +1 -1
- package/dist/{mcp-servers-Dck3T85_.d.ts → mcp-servers-D3E5ixAR.d.ts} +3 -3
- package/dist/{mode-CHo2XtHs.d.ts → mode-ARA3HrkY.d.ts} +1 -1
- package/dist/models/index.d.ts +5 -5
- package/dist/models/index.js +8 -6
- package/dist/models/index.js.map +1 -1
- package/dist/{models-registry-Be3osGt5.d.ts → models-registry-B6_KfS65.d.ts} +1 -1
- package/dist/{models-registry-Boz639EI.d.ts → models-registry-DU64QxQa.d.ts} +1 -1
- package/dist/{multi-agent-coordinator-DllpCVkF.d.ts → multi-agent-coordinator-DHNrjPaA.d.ts} +2 -2
- package/dist/{null-fleet-bus-BY0AN-sr.d.ts → null-fleet-bus-BzzciAc4.d.ts} +39 -32
- package/dist/observability/index.d.ts +3 -3
- package/dist/{observability-CoSNZdhX.d.ts → observability-D-HZN_mF.d.ts} +1 -1
- package/dist/{parallel-eternal-engine-D402RASp.d.ts → parallel-eternal-engine-CTpcrb9O.d.ts} +34 -33
- package/dist/{path-resolver-UPFTsDyD.d.ts → path-resolver-B7hgMAVe.d.ts} +3 -3
- package/dist/{permission-14CChMmO.d.ts → permission-BDv7z0mk.d.ts} +7 -2
- package/dist/{permission-policy-gW5htOo1.d.ts → permission-policy-dF74EpDp.d.ts} +2 -2
- package/dist/{system-prompt-C0rLCeyn.d.ts → pipeline-C1Ad9OjI.d.ts} +73 -73
- package/dist/{plan-templates-DRvPgkfZ.d.ts → plan-templates-CNphsz0n.d.ts} +79 -11
- package/dist/{provider-runner-COAJM8tC.d.ts → provider-runner-cqvlB_oS.d.ts} +5 -5
- package/dist/{retry-policy-DSu6O6rD.d.ts → retry-policy-BcmuT_V0.d.ts} +2 -2
- package/dist/sdd/index.d.ts +12 -33
- package/dist/sdd/index.js +110 -50
- package/dist/sdd/index.js.map +1 -1
- package/dist/{secret-scrubber-yGBFQYju.d.ts → secret-vault-DrOhc2i5.d.ts} +7 -7
- package/dist/security/index.d.ts +4 -5
- package/dist/security/index.js +22 -4
- package/dist/security/index.js.map +1 -1
- package/dist/{selector-11-fm95U.d.ts → selector-C7wcdqMA.d.ts} +1 -1
- package/dist/{session-event-bridge-D0u-x576.d.ts → session-event-bridge-IVzs2GpE.d.ts} +1 -1
- package/dist/{session-reader-BQU-toaN.d.ts → session-reader-DDz1Ek4V.d.ts} +2 -2
- package/dist/{skill-BJeF2DwY.d.ts → skill-BJxzIyyN.d.ts} +1 -1
- package/dist/skills/index.d.ts +1 -1
- package/dist/skills/index.js +3 -9
- package/dist/skills/index.js.map +1 -1
- package/dist/storage/index.d.ts +134 -14
- package/dist/storage/index.js +982 -248
- package/dist/storage/index.js.map +1 -1
- package/dist/{task-graph-CikNdRTG.d.ts → task-graph-DJBqO0EY.d.ts} +1 -1
- package/dist/types/index.d.ts +26 -28
- package/dist/types/index.js +62 -37
- package/dist/types/index.js.map +1 -1
- package/dist/utils/expect-defined.d.ts +7 -0
- package/dist/utils/expect-defined.js +11 -0
- package/dist/utils/expect-defined.js.map +1 -0
- package/dist/utils/index.d.ts +11 -20
- package/dist/utils/index.js +61 -43
- package/dist/utils/index.js.map +1 -1
- package/dist/{wstack-paths-BQMvEllz.d.ts → wstack-paths-_lqjzErq.d.ts} +1 -1
- package/package.json +5 -1
- package/dist/memory-CEXuo7sz.d.ts +0 -16
- package/dist/secret-scrubber-3MHDDAtm.d.ts +0 -6
package/dist/defaults/index.js
CHANGED
|
@@ -5,18 +5,13 @@ import * as path3 from 'path';
|
|
|
5
5
|
import { isAbsolute, resolve } from 'path';
|
|
6
6
|
import * as fs5 from 'fs';
|
|
7
7
|
import * as os from 'os';
|
|
8
|
+
import { hostname } from 'os';
|
|
8
9
|
import { execFile } from 'child_process';
|
|
9
10
|
import { promisify } from 'util';
|
|
10
11
|
import { EventEmitter } from 'events';
|
|
11
12
|
|
|
12
13
|
var __defProp = Object.defineProperty;
|
|
13
14
|
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
14
|
-
var __require = /* @__PURE__ */ ((x) => typeof require !== "undefined" ? require : typeof Proxy !== "undefined" ? new Proxy(x, {
|
|
15
|
-
get: (a, b) => (typeof require !== "undefined" ? require : a)[b]
|
|
16
|
-
}) : x)(function(x) {
|
|
17
|
-
if (typeof require !== "undefined") return require.apply(this, arguments);
|
|
18
|
-
throw Error('Dynamic require of "' + x + '" is not supported');
|
|
19
|
-
});
|
|
20
15
|
var __esm = (fn, res) => function __init() {
|
|
21
16
|
return fn && (res = (0, fn[__getOwnPropNames(fn)[0]])(fn = 0)), res;
|
|
22
17
|
};
|
|
@@ -29,7 +24,8 @@ var __export = (target, all) => {
|
|
|
29
24
|
var atomic_write_exports = {};
|
|
30
25
|
__export(atomic_write_exports, {
|
|
31
26
|
atomicWrite: () => atomicWrite,
|
|
32
|
-
ensureDir: () => ensureDir
|
|
27
|
+
ensureDir: () => ensureDir,
|
|
28
|
+
withFileLock: () => withFileLock
|
|
33
29
|
});
|
|
34
30
|
async function atomicWrite(targetPath, content, opts = {}) {
|
|
35
31
|
const dir = path3.dirname(targetPath);
|
|
@@ -52,8 +48,8 @@ async function atomicWrite(targetPath, content, opts = {}) {
|
|
|
52
48
|
}
|
|
53
49
|
let mode;
|
|
54
50
|
try {
|
|
55
|
-
const
|
|
56
|
-
mode =
|
|
51
|
+
const stat6 = await fsp.stat(targetPath);
|
|
52
|
+
mode = stat6.mode & 511;
|
|
57
53
|
} catch {
|
|
58
54
|
mode = opts.mode;
|
|
59
55
|
}
|
|
@@ -72,6 +68,49 @@ async function atomicWrite(targetPath, content, opts = {}) {
|
|
|
72
68
|
async function ensureDir(dir) {
|
|
73
69
|
await fsp.mkdir(dir, { recursive: true });
|
|
74
70
|
}
|
|
71
|
+
async function withFileLock(targetPath, fn, opts = {}) {
|
|
72
|
+
const dir = path3.dirname(targetPath);
|
|
73
|
+
await fsp.mkdir(dir, { recursive: true });
|
|
74
|
+
const lockPath = path3.join(dir, `.${path3.basename(targetPath)}.lock`);
|
|
75
|
+
const timeoutMs = opts.timeoutMs ?? 5e3;
|
|
76
|
+
const staleMs = opts.staleMs ?? 3e4;
|
|
77
|
+
const started = Date.now();
|
|
78
|
+
let handle;
|
|
79
|
+
for (; ; ) {
|
|
80
|
+
try {
|
|
81
|
+
handle = await fsp.open(lockPath, "wx");
|
|
82
|
+
await handle.writeFile(`${process.pid}:${Date.now()}`);
|
|
83
|
+
break;
|
|
84
|
+
} catch (err) {
|
|
85
|
+
if (err.code !== "EEXIST") throw err;
|
|
86
|
+
try {
|
|
87
|
+
const stat6 = await fsp.stat(lockPath);
|
|
88
|
+
if (Date.now() - stat6.mtimeMs > staleMs) {
|
|
89
|
+
await fsp.unlink(lockPath);
|
|
90
|
+
continue;
|
|
91
|
+
}
|
|
92
|
+
} catch {
|
|
93
|
+
continue;
|
|
94
|
+
}
|
|
95
|
+
if (Date.now() - started >= timeoutMs) {
|
|
96
|
+
throw new Error(`Timed out waiting for file lock: ${targetPath}`);
|
|
97
|
+
}
|
|
98
|
+
await new Promise((resolve5) => setTimeout(resolve5, 25));
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
try {
|
|
102
|
+
return await fn();
|
|
103
|
+
} finally {
|
|
104
|
+
try {
|
|
105
|
+
await handle?.close();
|
|
106
|
+
} catch {
|
|
107
|
+
}
|
|
108
|
+
try {
|
|
109
|
+
await fsp.unlink(lockPath);
|
|
110
|
+
} catch {
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
}
|
|
75
114
|
async function renameWithRetry(from, to) {
|
|
76
115
|
if (process.platform !== "win32") {
|
|
77
116
|
await fsp.rename(from, to);
|
|
@@ -119,10 +158,15 @@ function writeErr(s, stream = process.stderr) {
|
|
|
119
158
|
|
|
120
159
|
// src/utils/color.ts
|
|
121
160
|
var isColorTty = () => {
|
|
122
|
-
if (process.env.NO_COLOR) return false;
|
|
123
|
-
if (process.env.FORCE_COLOR) return true;
|
|
161
|
+
if (envFlag(process.env.NO_COLOR)) return false;
|
|
162
|
+
if (envFlag(process.env.FORCE_COLOR)) return true;
|
|
124
163
|
return isStdoutTTY();
|
|
125
164
|
};
|
|
165
|
+
function envFlag(value) {
|
|
166
|
+
if (value === void 0) return false;
|
|
167
|
+
if (value.trim() === "") return false;
|
|
168
|
+
return !/^(0|false|no|off)$/i.test(value.trim());
|
|
169
|
+
}
|
|
126
170
|
var COLOR = isColorTty();
|
|
127
171
|
var wrap = (open3, close) => (s) => COLOR ? `\x1B[${open3}m${s}\x1B[${close}m` : s;
|
|
128
172
|
var color = {
|
|
@@ -159,6 +203,7 @@ var COLORS = {
|
|
|
159
203
|
debug: color.gray,
|
|
160
204
|
trace: color.dim
|
|
161
205
|
};
|
|
206
|
+
var LOG_LEVELS = /* @__PURE__ */ new Set(["error", "warn", "info", "debug", "trace"]);
|
|
162
207
|
var DefaultLogger = class _DefaultLogger {
|
|
163
208
|
level;
|
|
164
209
|
file;
|
|
@@ -166,7 +211,7 @@ var DefaultLogger = class _DefaultLogger {
|
|
|
166
211
|
pretty;
|
|
167
212
|
stderr;
|
|
168
213
|
constructor(opts = {}) {
|
|
169
|
-
this.level = opts.level ?? process.env.WRONGSTACK_LOG_LEVEL
|
|
214
|
+
this.level = opts.level ?? parseLogLevel(process.env.WRONGSTACK_LOG_LEVEL);
|
|
170
215
|
this.file = opts.file;
|
|
171
216
|
this.bindings = opts.bindings ?? {};
|
|
172
217
|
this.pretty = opts.pretty ?? true;
|
|
@@ -231,6 +276,9 @@ var DefaultLogger = class _DefaultLogger {
|
|
|
231
276
|
}
|
|
232
277
|
}
|
|
233
278
|
};
|
|
279
|
+
function parseLogLevel(raw) {
|
|
280
|
+
return raw && LOG_LEVELS.has(raw) ? raw : "info";
|
|
281
|
+
}
|
|
234
282
|
function formatCtx(ctx) {
|
|
235
283
|
if (ctx instanceof Error) return color.dim(ctx.message);
|
|
236
284
|
if (typeof ctx === "string") return color.dim(ctx);
|
|
@@ -241,16 +289,18 @@ function formatCtx(ctx) {
|
|
|
241
289
|
}
|
|
242
290
|
}
|
|
243
291
|
|
|
244
|
-
// src/
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
// src/utils/message-invariants.ts
|
|
248
|
-
function expectDefined(value) {
|
|
292
|
+
// src/utils/expect-defined.ts
|
|
293
|
+
function expectDefined(value, label) {
|
|
249
294
|
if (value === null || value === void 0) {
|
|
250
295
|
throw new Error("Expected value to be defined");
|
|
251
296
|
}
|
|
252
297
|
return value;
|
|
253
298
|
}
|
|
299
|
+
|
|
300
|
+
// src/storage/session-store.ts
|
|
301
|
+
init_atomic_write();
|
|
302
|
+
|
|
303
|
+
// src/utils/message-invariants.ts
|
|
254
304
|
function repairToolUseAdjacency(messages) {
|
|
255
305
|
const removedToolUses = [];
|
|
256
306
|
const removedToolResults = [];
|
|
@@ -343,12 +393,6 @@ function isEmptyMessage(msg) {
|
|
|
343
393
|
}
|
|
344
394
|
|
|
345
395
|
// src/storage/session-store.ts
|
|
346
|
-
function expectDefined2(value) {
|
|
347
|
-
if (value === null || value === void 0) {
|
|
348
|
-
throw new Error("Expected value to be defined");
|
|
349
|
-
}
|
|
350
|
-
return value;
|
|
351
|
-
}
|
|
352
396
|
function sanitizeModel(model) {
|
|
353
397
|
return model.replace(/[^a-zA-Z0-9_-]/g, "-").replace(/-+/g, "-").replace(/^-|-$/g, "").slice(0, 40);
|
|
354
398
|
}
|
|
@@ -614,8 +658,8 @@ var DefaultSessionStore = class _DefaultSessionStore {
|
|
|
614
658
|
return JSON.parse(raw);
|
|
615
659
|
} catch {
|
|
616
660
|
const full = this.sessionPath(id, ".jsonl");
|
|
617
|
-
const
|
|
618
|
-
const summary = await this.summarize(id,
|
|
661
|
+
const stat6 = await fsp.stat(full);
|
|
662
|
+
const summary = await this.summarize(id, stat6.mtime.toISOString());
|
|
619
663
|
await atomicWrite(manifest, JSON.stringify(summary), { mode: 384 }).catch((err) => {
|
|
620
664
|
console.warn(
|
|
621
665
|
`[session-store] Failed to write manifest for "${id}":`,
|
|
@@ -663,8 +707,8 @@ var DefaultSessionStore = class _DefaultSessionStore {
|
|
|
663
707
|
if (!file.isFile() || !file.name.endsWith(".jsonl")) continue;
|
|
664
708
|
const jsonlPath = path3.join(dateDir, file.name);
|
|
665
709
|
try {
|
|
666
|
-
const
|
|
667
|
-
if (
|
|
710
|
+
const stat6 = await fsp.stat(jsonlPath);
|
|
711
|
+
if (stat6.mtimeMs >= cutoff) continue;
|
|
668
712
|
} catch {
|
|
669
713
|
continue;
|
|
670
714
|
}
|
|
@@ -1059,7 +1103,7 @@ var FileSessionWriter = class {
|
|
|
1059
1103
|
let targetCheckpointLine = -1;
|
|
1060
1104
|
let afterTarget = false;
|
|
1061
1105
|
for (let i = 0; i < lines.length; i++) {
|
|
1062
|
-
const line =
|
|
1106
|
+
const line = expectDefined(lines[i]);
|
|
1063
1107
|
if (!line.trim()) continue;
|
|
1064
1108
|
let event;
|
|
1065
1109
|
try {
|
|
@@ -1355,11 +1399,235 @@ function mergeAdjacentText(blocks) {
|
|
|
1355
1399
|
return out;
|
|
1356
1400
|
}
|
|
1357
1401
|
|
|
1358
|
-
// src/
|
|
1402
|
+
// src/types/memory.ts
|
|
1403
|
+
var MEMORY_TYPE_LABELS = {
|
|
1404
|
+
fact: "Fact",
|
|
1405
|
+
decision: "Decision",
|
|
1406
|
+
convention: "Convention",
|
|
1407
|
+
preference: "Preference",
|
|
1408
|
+
reference: "Reference",
|
|
1409
|
+
anti_pattern: "Anti-pattern"
|
|
1410
|
+
};
|
|
1411
|
+
|
|
1412
|
+
// src/storage/memory-backend.ts
|
|
1359
1413
|
init_atomic_write();
|
|
1414
|
+
var TYPE_PRIORITY_RE = /^\[(\w+)\|(\w+)\]\s+/;
|
|
1415
|
+
var TAG_RE = /#([\w-]+)/g;
|
|
1416
|
+
function formatMetadata(entry) {
|
|
1417
|
+
const parts = [];
|
|
1418
|
+
if (entry.type && entry.priority) {
|
|
1419
|
+
parts.push(`[${entry.type}|${entry.priority}]`);
|
|
1420
|
+
} else if (entry.type) {
|
|
1421
|
+
parts.push(`[${entry.type}]`);
|
|
1422
|
+
} else if (entry.priority) {
|
|
1423
|
+
parts.push(`[${entry.priority}]`);
|
|
1424
|
+
}
|
|
1425
|
+
if (entry.tags && entry.tags.length > 0) {
|
|
1426
|
+
parts.push(entry.tags.map((t) => `#${t}`).join(" "));
|
|
1427
|
+
}
|
|
1428
|
+
return parts.length > 0 ? ` ${parts.join(" ")}` : "";
|
|
1429
|
+
}
|
|
1430
|
+
function lineToEntry(line, scope) {
|
|
1431
|
+
const trimmed = line.trim();
|
|
1432
|
+
if (!trimmed.startsWith("- [")) return null;
|
|
1433
|
+
const tsMatch = trimmed.match(/^-\s*\[([^\]]+)\]/);
|
|
1434
|
+
if (!tsMatch) return null;
|
|
1435
|
+
const ts = tsMatch[1] ?? "";
|
|
1436
|
+
let rest = trimmed.slice(tsMatch[0].length).trim();
|
|
1437
|
+
let type;
|
|
1438
|
+
let priority;
|
|
1439
|
+
const tpMatch = rest.match(TYPE_PRIORITY_RE);
|
|
1440
|
+
if (tpMatch) {
|
|
1441
|
+
const a = tpMatch[1] ?? "";
|
|
1442
|
+
const b = tpMatch[2] ?? "";
|
|
1443
|
+
if (isMemoryType(a)) {
|
|
1444
|
+
type = a;
|
|
1445
|
+
priority = isPriority(b) ? b : void 0;
|
|
1446
|
+
} else if (isPriority(a)) {
|
|
1447
|
+
priority = a;
|
|
1448
|
+
}
|
|
1449
|
+
rest = rest.slice(tpMatch[0].length).trim();
|
|
1450
|
+
}
|
|
1451
|
+
const idMatch = rest.match(/^mem_\d+_\w+\s+/);
|
|
1452
|
+
let text;
|
|
1453
|
+
if (idMatch) {
|
|
1454
|
+
text = rest.slice(idMatch[0].length).trim();
|
|
1455
|
+
} else {
|
|
1456
|
+
text = rest.trim();
|
|
1457
|
+
}
|
|
1458
|
+
const tags = [];
|
|
1459
|
+
let tagMatch;
|
|
1460
|
+
TAG_RE.lastIndex = 0;
|
|
1461
|
+
while ((tagMatch = TAG_RE.exec(text)) !== null) {
|
|
1462
|
+
tags.push(tagMatch[1] ?? "");
|
|
1463
|
+
}
|
|
1464
|
+
const cleanText = text.replace(TAG_RE, "").replace(/\s{2,}/g, " ").trim();
|
|
1465
|
+
if (!cleanText) return null;
|
|
1466
|
+
return {
|
|
1467
|
+
scope,
|
|
1468
|
+
text: cleanText,
|
|
1469
|
+
ts,
|
|
1470
|
+
type,
|
|
1471
|
+
priority,
|
|
1472
|
+
tags: tags.length > 0 ? tags : void 0
|
|
1473
|
+
};
|
|
1474
|
+
}
|
|
1475
|
+
function isMemoryType(s) {
|
|
1476
|
+
return s in MEMORY_TYPE_LABELS;
|
|
1477
|
+
}
|
|
1478
|
+
function isPriority(s) {
|
|
1479
|
+
return s === "critical" || s === "high" || s === "medium" || s === "low";
|
|
1480
|
+
}
|
|
1481
|
+
var FileMemoryBackend = class {
|
|
1482
|
+
kind = "file";
|
|
1483
|
+
files;
|
|
1484
|
+
constructor(opts) {
|
|
1485
|
+
this.files = {
|
|
1486
|
+
"project-agents": opts.paths.inProjectAgentsFile,
|
|
1487
|
+
"project-memory": opts.paths.projectMemory,
|
|
1488
|
+
"user-memory": opts.paths.globalMemory
|
|
1489
|
+
};
|
|
1490
|
+
}
|
|
1491
|
+
resolveFile(filePath, scope) {
|
|
1492
|
+
return filePath || this.files[scope];
|
|
1493
|
+
}
|
|
1494
|
+
async remember(scope, entry, filePath) {
|
|
1495
|
+
const file = this.resolveFile(filePath, scope);
|
|
1496
|
+
await ensureDir(path3.dirname(file));
|
|
1497
|
+
let existing = "";
|
|
1498
|
+
try {
|
|
1499
|
+
existing = await fsp.readFile(file, "utf8");
|
|
1500
|
+
} catch {
|
|
1501
|
+
}
|
|
1502
|
+
const id = `mem_${Date.now()}_${randomUUID().slice(0, 8)}`;
|
|
1503
|
+
const meta = formatMetadata(entry);
|
|
1504
|
+
const line = `
|
|
1505
|
+
- [${entry.ts}] ${id}${meta} ${entry.text.replace(/\n/g, " ")}
|
|
1506
|
+
`;
|
|
1507
|
+
const next = existing.trim() ? existing.replace(/\n+$/, "") + line : `# WrongStack Memory
|
|
1508
|
+
${line}`;
|
|
1509
|
+
await atomicWrite(file, next);
|
|
1510
|
+
}
|
|
1511
|
+
async forget(scope, query, filePath) {
|
|
1512
|
+
const file = this.resolveFile(filePath, scope);
|
|
1513
|
+
let existing;
|
|
1514
|
+
try {
|
|
1515
|
+
existing = await fsp.readFile(file, "utf8");
|
|
1516
|
+
} catch {
|
|
1517
|
+
return 0;
|
|
1518
|
+
}
|
|
1519
|
+
const needle = query.toLowerCase();
|
|
1520
|
+
const idMatcher = /mem_\d+_\w+/;
|
|
1521
|
+
let removed = 0;
|
|
1522
|
+
const lines = existing.split("\n").filter((line) => {
|
|
1523
|
+
const trimmed = line.trim();
|
|
1524
|
+
if (!trimmed.startsWith("- ")) return true;
|
|
1525
|
+
if (idMatcher.test(query)) {
|
|
1526
|
+
const entryIdMatch = /mem_\d+_\w+/.exec(trimmed);
|
|
1527
|
+
if (entryIdMatch && entryIdMatch[0] === query) {
|
|
1528
|
+
removed++;
|
|
1529
|
+
return false;
|
|
1530
|
+
}
|
|
1531
|
+
}
|
|
1532
|
+
if (trimmed.toLowerCase().includes(needle)) {
|
|
1533
|
+
removed++;
|
|
1534
|
+
return false;
|
|
1535
|
+
}
|
|
1536
|
+
return true;
|
|
1537
|
+
});
|
|
1538
|
+
if (removed > 0) {
|
|
1539
|
+
if (lines.length === 0 || lines.length === 1 && !lines[0]?.trim()) {
|
|
1540
|
+
await atomicWrite(file, "");
|
|
1541
|
+
} else {
|
|
1542
|
+
await atomicWrite(file, lines.join("\n"));
|
|
1543
|
+
}
|
|
1544
|
+
}
|
|
1545
|
+
return removed;
|
|
1546
|
+
}
|
|
1547
|
+
async readAll(scope, filePath) {
|
|
1548
|
+
const file = this.resolveFile(filePath, scope);
|
|
1549
|
+
try {
|
|
1550
|
+
return await fsp.readFile(file, "utf8");
|
|
1551
|
+
} catch {
|
|
1552
|
+
return "";
|
|
1553
|
+
}
|
|
1554
|
+
}
|
|
1555
|
+
async list(scope, filePath, limit) {
|
|
1556
|
+
const raw = await this.readAll(scope, filePath);
|
|
1557
|
+
if (!raw.trim()) return [];
|
|
1558
|
+
const entries = parseEntries(raw, scope);
|
|
1559
|
+
return limit ? entries.slice(0, limit) : entries;
|
|
1560
|
+
}
|
|
1561
|
+
async search(scope, query, filePath, limit) {
|
|
1562
|
+
const entries = await this.list(scope, filePath);
|
|
1563
|
+
const needle = query.toLowerCase().split(/\s+/);
|
|
1564
|
+
const scored = entries.map((e) => {
|
|
1565
|
+
const words = e.text.toLowerCase().split(/\s+/);
|
|
1566
|
+
let score = 0;
|
|
1567
|
+
for (const n of needle) {
|
|
1568
|
+
if (words.some((w) => w.includes(n))) score += 1;
|
|
1569
|
+
if (e.tags?.some((t) => t.toLowerCase().includes(n))) score += 2;
|
|
1570
|
+
}
|
|
1571
|
+
return { entry: e, score };
|
|
1572
|
+
});
|
|
1573
|
+
scored.sort((a, b) => b.score - a.score);
|
|
1574
|
+
const matched = scored.filter((s) => s.score > 0).map((s) => s.entry);
|
|
1575
|
+
return limit ? matched.slice(0, limit) : matched;
|
|
1576
|
+
}
|
|
1577
|
+
async clear(scope, filePath) {
|
|
1578
|
+
const file = this.resolveFile(filePath, scope);
|
|
1579
|
+
await atomicWrite(file, "");
|
|
1580
|
+
}
|
|
1581
|
+
async consolidate(scope, filePath) {
|
|
1582
|
+
const file = this.resolveFile(filePath, scope);
|
|
1583
|
+
let existing;
|
|
1584
|
+
try {
|
|
1585
|
+
existing = await fsp.readFile(file, "utf8");
|
|
1586
|
+
} catch {
|
|
1587
|
+
return 0;
|
|
1588
|
+
}
|
|
1589
|
+
const seen = /* @__PURE__ */ new Set();
|
|
1590
|
+
let removed = 0;
|
|
1591
|
+
const lines = existing.split("\n").filter((line) => {
|
|
1592
|
+
const trimmed = line.trim();
|
|
1593
|
+
if (!trimmed.startsWith("- ")) return true;
|
|
1594
|
+
const norm = trimmed.replace(/\[[^\]]+\]/g, "").replace(/\bmem_\d+_\w+\s*/, "").replace(/#[\w-]+/g, "").replace(/\s+/g, " ").trim().toLowerCase();
|
|
1595
|
+
if (seen.has(norm)) {
|
|
1596
|
+
removed++;
|
|
1597
|
+
return false;
|
|
1598
|
+
}
|
|
1599
|
+
seen.add(norm);
|
|
1600
|
+
return true;
|
|
1601
|
+
});
|
|
1602
|
+
const next = lines.join("\n");
|
|
1603
|
+
const backup = `${file}.bak.${Date.now()}`;
|
|
1604
|
+
try {
|
|
1605
|
+
await fsp.copyFile(file, backup);
|
|
1606
|
+
} catch {
|
|
1607
|
+
}
|
|
1608
|
+
try {
|
|
1609
|
+
await atomicWrite(file, next);
|
|
1610
|
+
} catch {
|
|
1611
|
+
return 0;
|
|
1612
|
+
}
|
|
1613
|
+
return removed;
|
|
1614
|
+
}
|
|
1615
|
+
};
|
|
1616
|
+
function parseEntries(raw, scope = "project-memory") {
|
|
1617
|
+
const entries = [];
|
|
1618
|
+
for (const line of raw.split("\n")) {
|
|
1619
|
+
const entry = lineToEntry(line, scope);
|
|
1620
|
+
if (entry) entries.push(entry);
|
|
1621
|
+
}
|
|
1622
|
+
return entries.reverse();
|
|
1623
|
+
}
|
|
1624
|
+
|
|
1625
|
+
// src/storage/memory-store.ts
|
|
1360
1626
|
var MAX_BYTES_TOTAL = 32e3;
|
|
1361
1627
|
var DefaultMemoryStore = class {
|
|
1362
1628
|
files;
|
|
1629
|
+
events;
|
|
1630
|
+
backend;
|
|
1363
1631
|
/**
|
|
1364
1632
|
* Per-scope serialization queue. `remember` / `forget` / `consolidate` /
|
|
1365
1633
|
* `clear` are read-modify-write against a single file; without a lock,
|
|
@@ -1376,12 +1644,29 @@ var DefaultMemoryStore = class {
|
|
|
1376
1644
|
writeChain = /* @__PURE__ */ new Map();
|
|
1377
1645
|
/** Last write error per scope — surfaced as warnings on the next readAll(). */
|
|
1378
1646
|
writeErrors = /* @__PURE__ */ new Map();
|
|
1647
|
+
/**
|
|
1648
|
+
* When the global root is a temporary directory (opencode, CI sandboxes),
|
|
1649
|
+
* memory files are also mirrored to the project tree so they survive
|
|
1650
|
+
* session cleanup. The primary path stays in the temp root for isolation;
|
|
1651
|
+
* this backup ensures memory is never lost.
|
|
1652
|
+
*/
|
|
1653
|
+
persistBackup;
|
|
1654
|
+
backupDir;
|
|
1379
1655
|
constructor(opts) {
|
|
1380
1656
|
this.files = {
|
|
1381
1657
|
"project-agents": opts.paths.inProjectAgentsFile,
|
|
1382
1658
|
"project-memory": opts.paths.projectMemory,
|
|
1383
1659
|
"user-memory": opts.paths.globalMemory
|
|
1384
1660
|
};
|
|
1661
|
+
this.events = opts.events;
|
|
1662
|
+
this.backend = opts.backend ?? new FileMemoryBackend({ paths: opts.paths });
|
|
1663
|
+
const root = opts.paths.globalRoot.toLowerCase();
|
|
1664
|
+
this.persistBackup = /[/\\](tmp|temp|cache)[/\\]/.test(root);
|
|
1665
|
+
this.backupDir = this.persistBackup ? opts.paths.inProjectAgentsFile.replace(/AGENTS\.md$/, "memory-persist") : "";
|
|
1666
|
+
}
|
|
1667
|
+
/** Expose the backend for plugin introspection and advanced queries. */
|
|
1668
|
+
getBackend() {
|
|
1669
|
+
return this.backend;
|
|
1385
1670
|
}
|
|
1386
1671
|
async runSerialized(scope, work) {
|
|
1387
1672
|
const prior = this.writeChain.get(scope) ?? Promise.resolve();
|
|
@@ -1408,7 +1693,7 @@ var DefaultMemoryStore = class {
|
|
|
1408
1693
|
if (writeErr2) {
|
|
1409
1694
|
parts.push(`> \u26A0\uFE0F Memory write error (${labelOf(scope)}): ${writeErr2.message}`);
|
|
1410
1695
|
}
|
|
1411
|
-
const body = await this.
|
|
1696
|
+
const body = await this.backend.readAll(scope, this.files[scope]);
|
|
1412
1697
|
if (body.trim()) parts.push(`## ${labelOf(scope)}
|
|
1413
1698
|
|
|
1414
1699
|
${body.trim()}`);
|
|
@@ -1416,117 +1701,214 @@ ${body.trim()}`);
|
|
|
1416
1701
|
return parts.join("\n\n");
|
|
1417
1702
|
}
|
|
1418
1703
|
async read(scope) {
|
|
1419
|
-
|
|
1420
|
-
|
|
1421
|
-
|
|
1422
|
-
|
|
1704
|
+
return this.backend.readAll(scope, this.files[scope]);
|
|
1705
|
+
}
|
|
1706
|
+
/**
|
|
1707
|
+
* List entries from a scope, newest first. Delegates to the backend
|
|
1708
|
+
* so graph/semantic backends can return enriched or filtered results.
|
|
1709
|
+
*/
|
|
1710
|
+
async list(scope = "project-memory", limit) {
|
|
1711
|
+
return this.backend.list(scope, this.files[scope], limit);
|
|
1712
|
+
}
|
|
1713
|
+
/**
|
|
1714
|
+
* Find memories related to the given text via graph traversal.
|
|
1715
|
+
* Falls back to content search when no graph backend is available.
|
|
1716
|
+
*/
|
|
1717
|
+
async findRelated(text, scope = "project-memory", limit = 5) {
|
|
1718
|
+
if (this.backend.findRelated) {
|
|
1719
|
+
return this.backend.findRelated(scope, this.files[scope], text, limit);
|
|
1423
1720
|
}
|
|
1721
|
+
return this.search(text, scope, limit);
|
|
1722
|
+
}
|
|
1723
|
+
async search(query, scope = "project-memory", limit) {
|
|
1724
|
+
return this.backend.search(scope, query, this.files[scope], limit);
|
|
1424
1725
|
}
|
|
1425
|
-
async remember(text, scope = "project-memory") {
|
|
1726
|
+
async remember(text, scope = "project-memory", metadata) {
|
|
1727
|
+
const ts = (/* @__PURE__ */ new Date()).toISOString();
|
|
1426
1728
|
return this.runSerialized(scope, async () => {
|
|
1427
|
-
const
|
|
1428
|
-
await
|
|
1429
|
-
|
|
1430
|
-
|
|
1431
|
-
|
|
1432
|
-
|
|
1433
|
-
|
|
1434
|
-
|
|
1435
|
-
|
|
1436
|
-
|
|
1437
|
-
|
|
1438
|
-
`;
|
|
1439
|
-
const next = existing.trim() ? existing.replace(/\n+$/, "") + entry : `# WrongStack Memory
|
|
1440
|
-
${entry}`;
|
|
1441
|
-
await atomicWrite(file, next);
|
|
1442
|
-
const buf = Buffer.byteLength(next, "utf8");
|
|
1443
|
-
if (buf > MAX_BYTES_TOTAL) {
|
|
1444
|
-
await this.consolidateUnsafe(scope);
|
|
1729
|
+
const entry = { scope, text, ts, ...metadata };
|
|
1730
|
+
await this.backend.remember(scope, entry, this.files[scope]);
|
|
1731
|
+
const raw = await this.backend.readAll(scope, this.files[scope]);
|
|
1732
|
+
if (Buffer.byteLength(raw, "utf8") > MAX_BYTES_TOTAL) {
|
|
1733
|
+
const removed = await this.backend.consolidate(scope, this.files[scope]);
|
|
1734
|
+
if (removed > 0) {
|
|
1735
|
+
this.events?.emit("memory.consolidated", {
|
|
1736
|
+
scope,
|
|
1737
|
+
removed
|
|
1738
|
+
});
|
|
1739
|
+
}
|
|
1445
1740
|
}
|
|
1741
|
+
await this.mirrorBackup(scope);
|
|
1742
|
+
this.events?.emit("memory.remembered", {
|
|
1743
|
+
scope,
|
|
1744
|
+
text,
|
|
1745
|
+
ts,
|
|
1746
|
+
type: entry.type,
|
|
1747
|
+
tags: entry.tags,
|
|
1748
|
+
priority: entry.priority
|
|
1749
|
+
});
|
|
1446
1750
|
});
|
|
1447
1751
|
}
|
|
1448
|
-
|
|
1449
|
-
|
|
1450
|
-
|
|
1451
|
-
|
|
1452
|
-
|
|
1453
|
-
|
|
1454
|
-
|
|
1455
|
-
|
|
1456
|
-
|
|
1457
|
-
|
|
1458
|
-
|
|
1459
|
-
const
|
|
1460
|
-
const
|
|
1461
|
-
|
|
1462
|
-
|
|
1463
|
-
const
|
|
1464
|
-
|
|
1465
|
-
|
|
1466
|
-
|
|
1467
|
-
if (
|
|
1468
|
-
|
|
1469
|
-
|
|
1470
|
-
|
|
1471
|
-
|
|
1472
|
-
|
|
1473
|
-
|
|
1752
|
+
/**
|
|
1753
|
+
* Score and rank memories by relevance to the current context.
|
|
1754
|
+
* Returns entries with score >= MIN_RELEVANCE_SCORE, sorted highest first.
|
|
1755
|
+
*/
|
|
1756
|
+
async scoreRelevant(ctx, scope = "project-memory", limit = 8) {
|
|
1757
|
+
const all = await this.list(scope);
|
|
1758
|
+
if (all.length === 0) return [];
|
|
1759
|
+
const taskWords = ctx.currentTask.toLowerCase().split(/\s+/).filter((w) => w.length > 2);
|
|
1760
|
+
const skillWords = (ctx.activeSkills ?? []).flatMap((s) => s.split("-"));
|
|
1761
|
+
const toolWords = (ctx.toolNames ?? []).flatMap((t) => t.toLowerCase().split("_"));
|
|
1762
|
+
const now = Date.now();
|
|
1763
|
+
const scored = [];
|
|
1764
|
+
for (const entry of all) {
|
|
1765
|
+
let score = 0;
|
|
1766
|
+
const reasons = [];
|
|
1767
|
+
const textLower = entry.text.toLowerCase();
|
|
1768
|
+
const tagsLower = (entry.tags ?? []).map((t) => t.toLowerCase());
|
|
1769
|
+
let taskHits = 0;
|
|
1770
|
+
for (const w of taskWords) {
|
|
1771
|
+
if (textLower.includes(w)) {
|
|
1772
|
+
taskHits++;
|
|
1773
|
+
score += 2;
|
|
1774
|
+
}
|
|
1775
|
+
if (tagsLower.some((t) => t.includes(w))) {
|
|
1776
|
+
taskHits++;
|
|
1777
|
+
score += 3;
|
|
1474
1778
|
}
|
|
1475
1779
|
}
|
|
1476
|
-
if (
|
|
1477
|
-
|
|
1478
|
-
|
|
1780
|
+
if (taskHits > 0) reasons.push(`task match (${taskHits})`);
|
|
1781
|
+
let skillHits = 0;
|
|
1782
|
+
for (const w of skillWords) {
|
|
1783
|
+
if (w.length > 2 && (textLower.includes(w) || tagsLower.some((t) => t.includes(w)))) {
|
|
1784
|
+
skillHits++;
|
|
1785
|
+
}
|
|
1786
|
+
}
|
|
1787
|
+
score += skillHits;
|
|
1788
|
+
if (skillHits > 0) reasons.push(`skill match (${skillHits})`);
|
|
1789
|
+
for (const w of toolWords) {
|
|
1790
|
+
if (w.length > 2 && (textLower.includes(w) || tagsLower.some((t) => t.includes(w)))) {
|
|
1791
|
+
score += 1;
|
|
1792
|
+
reasons.push(`tool mention: ${w}`);
|
|
1793
|
+
}
|
|
1794
|
+
}
|
|
1795
|
+
switch (entry.priority) {
|
|
1796
|
+
case "critical":
|
|
1797
|
+
score += 5;
|
|
1798
|
+
reasons.push("critical");
|
|
1799
|
+
break;
|
|
1800
|
+
case "high":
|
|
1801
|
+
score += 3;
|
|
1802
|
+
reasons.push("high priority");
|
|
1803
|
+
break;
|
|
1804
|
+
case "medium":
|
|
1805
|
+
score += 1;
|
|
1806
|
+
break;
|
|
1807
|
+
case "low":
|
|
1808
|
+
score -= 2;
|
|
1809
|
+
reasons.push("low priority");
|
|
1810
|
+
break;
|
|
1811
|
+
}
|
|
1812
|
+
switch (entry.type) {
|
|
1813
|
+
case "decision":
|
|
1814
|
+
score += 2;
|
|
1815
|
+
reasons.push("decision");
|
|
1816
|
+
break;
|
|
1817
|
+
case "convention":
|
|
1818
|
+
score += 2;
|
|
1819
|
+
reasons.push("convention");
|
|
1820
|
+
break;
|
|
1821
|
+
case "anti_pattern":
|
|
1822
|
+
score += 3;
|
|
1823
|
+
reasons.push("anti-pattern");
|
|
1824
|
+
break;
|
|
1825
|
+
case "preference":
|
|
1826
|
+
score += 1;
|
|
1827
|
+
reasons.push("preference");
|
|
1828
|
+
break;
|
|
1829
|
+
}
|
|
1830
|
+
const ageDays = (now - new Date(entry.ts).getTime()) / (1e3 * 60 * 60 * 24);
|
|
1831
|
+
if (ageDays < 1) score += 1;
|
|
1832
|
+
else if (ageDays > 30) score -= 1;
|
|
1833
|
+
if (entry.confidence !== void 0 && entry.confidence < 0.5) {
|
|
1834
|
+
score -= 2;
|
|
1835
|
+
reasons.push("low confidence");
|
|
1836
|
+
}
|
|
1837
|
+
if (entry.lastAccessed) {
|
|
1838
|
+
const hoursSinceAccess = (now - new Date(entry.lastAccessed).getTime()) / (1e3 * 60 * 60);
|
|
1839
|
+
if (hoursSinceAccess < 1) score -= 1;
|
|
1840
|
+
}
|
|
1841
|
+
if (score > 0) {
|
|
1842
|
+
scored.push({
|
|
1843
|
+
...entry,
|
|
1844
|
+
score,
|
|
1845
|
+
matchReason: reasons.join(", ") || "keyword match"
|
|
1846
|
+
});
|
|
1479
1847
|
}
|
|
1480
|
-
return true;
|
|
1481
|
-
});
|
|
1482
|
-
if (removed > 0) {
|
|
1483
|
-
await atomicWrite(file, lines.join("\n"));
|
|
1484
1848
|
}
|
|
1485
|
-
|
|
1849
|
+
scored.sort((a, b) => b.score - a.score);
|
|
1850
|
+
const threshold = 2;
|
|
1851
|
+
const relevant = scored.filter(
|
|
1852
|
+
(s) => s.score >= threshold || s.priority === "critical" || s.priority === "high"
|
|
1853
|
+
);
|
|
1854
|
+
return relevant.slice(0, Math.min(limit, 15));
|
|
1486
1855
|
}
|
|
1487
|
-
async
|
|
1488
|
-
return this.runSerialized(scope, async () =>
|
|
1856
|
+
async forget(query, scope = "project-memory") {
|
|
1857
|
+
return this.runSerialized(scope, async () => {
|
|
1858
|
+
const removed = await this.backend.forget(scope, query, this.files[scope]);
|
|
1859
|
+
if (removed > 0) {
|
|
1860
|
+
this.events?.emit("memory.forgotten", {
|
|
1861
|
+
scope,
|
|
1862
|
+
query,
|
|
1863
|
+
removed
|
|
1864
|
+
});
|
|
1865
|
+
await this.mirrorBackup(scope);
|
|
1866
|
+
}
|
|
1867
|
+
return removed;
|
|
1868
|
+
});
|
|
1489
1869
|
}
|
|
1490
|
-
async
|
|
1491
|
-
|
|
1492
|
-
|
|
1493
|
-
|
|
1494
|
-
|
|
1495
|
-
|
|
1496
|
-
|
|
1497
|
-
|
|
1498
|
-
|
|
1499
|
-
|
|
1500
|
-
const trimmed = line.trim();
|
|
1501
|
-
if (!trimmed.startsWith("- ")) return true;
|
|
1502
|
-
const norm = trimmed.replace(/\[[^\]]+\]/, "").replace(/\bmem_\d+_\w+\s*/, "").trim().toLowerCase();
|
|
1503
|
-
if (seen.has(norm)) return false;
|
|
1504
|
-
seen.add(norm);
|
|
1505
|
-
return true;
|
|
1870
|
+
async consolidate(scope) {
|
|
1871
|
+
return this.runSerialized(scope, async () => {
|
|
1872
|
+
const removed = await this.backend.consolidate(scope, this.files[scope]);
|
|
1873
|
+
if (removed > 0) {
|
|
1874
|
+
this.events?.emit("memory.consolidated", {
|
|
1875
|
+
scope,
|
|
1876
|
+
removed
|
|
1877
|
+
});
|
|
1878
|
+
await this.mirrorBackup(scope);
|
|
1879
|
+
}
|
|
1506
1880
|
});
|
|
1507
|
-
const next = lines.join("\n");
|
|
1508
|
-
const backup = `${file}.bak.${Date.now()}`;
|
|
1509
|
-
try {
|
|
1510
|
-
await fsp.copyFile(file, backup);
|
|
1511
|
-
} catch {
|
|
1512
|
-
}
|
|
1513
|
-
try {
|
|
1514
|
-
await atomicWrite(file, next);
|
|
1515
|
-
} catch {
|
|
1516
|
-
return;
|
|
1517
|
-
}
|
|
1518
1881
|
}
|
|
1519
1882
|
async clear(scope) {
|
|
1520
1883
|
if (scope) {
|
|
1521
|
-
await this.runSerialized(scope, async () =>
|
|
1884
|
+
await this.runSerialized(scope, async () => {
|
|
1885
|
+
await this.backend.clear(scope, this.files[scope]);
|
|
1886
|
+
this.events?.emit("memory.cleared", { scope });
|
|
1887
|
+
await this.mirrorBackup(scope);
|
|
1888
|
+
});
|
|
1522
1889
|
return;
|
|
1523
1890
|
}
|
|
1524
1891
|
await Promise.all(
|
|
1525
1892
|
["project-agents", "project-memory", "user-memory"].map(
|
|
1526
|
-
(s) => this.runSerialized(s, async () =>
|
|
1893
|
+
(s) => this.runSerialized(s, async () => {
|
|
1894
|
+
await this.backend.clear(s, this.files[s]);
|
|
1895
|
+
this.events?.emit("memory.cleared", { scope: s });
|
|
1896
|
+
await this.mirrorBackup(s);
|
|
1897
|
+
})
|
|
1527
1898
|
)
|
|
1528
1899
|
);
|
|
1529
1900
|
}
|
|
1901
|
+
/** Mirror current memory content to the persistent backup directory. */
|
|
1902
|
+
async mirrorBackup(scope) {
|
|
1903
|
+
if (!this.persistBackup || scope === "project-agents") return;
|
|
1904
|
+
try {
|
|
1905
|
+
const content = await this.backend.readAll(scope, this.files[scope]);
|
|
1906
|
+
const { writeFile: writeFile4, mkdir: mkdir6 } = await import('fs/promises');
|
|
1907
|
+
await mkdir6(this.backupDir, { recursive: true });
|
|
1908
|
+
await writeFile4(`${this.backupDir}/${scope}.md`, content, "utf8");
|
|
1909
|
+
} catch {
|
|
1910
|
+
}
|
|
1911
|
+
}
|
|
1530
1912
|
};
|
|
1531
1913
|
function labelOf(scope) {
|
|
1532
1914
|
switch (scope) {
|
|
@@ -1774,6 +2156,13 @@ var BEHAVIOR_DEFAULTS = {
|
|
|
1774
2156
|
function envBool(v) {
|
|
1775
2157
|
return !/^(0|false|no|off)$/i.test(v.trim());
|
|
1776
2158
|
}
|
|
2159
|
+
function envBoolOptional(v) {
|
|
2160
|
+
return v !== void 0 && envBool(v);
|
|
2161
|
+
}
|
|
2162
|
+
var LOG_LEVELS2 = /* @__PURE__ */ new Set(["error", "warn", "info", "debug", "trace"]);
|
|
2163
|
+
function envLogLevel(v) {
|
|
2164
|
+
return LOG_LEVELS2.has(v) ? v : "info";
|
|
2165
|
+
}
|
|
1777
2166
|
var ENV_MAP = {
|
|
1778
2167
|
WRONGSTACK_PROVIDER: (c, v) => {
|
|
1779
2168
|
c.provider = v;
|
|
@@ -1797,7 +2186,7 @@ var ENV_MAP = {
|
|
|
1797
2186
|
},
|
|
1798
2187
|
WRONGSTACK_LOG_LEVEL: (c, v) => {
|
|
1799
2188
|
if (!c.log) c.log = { level: "info" };
|
|
1800
|
-
c.log.level = v;
|
|
2189
|
+
c.log.level = envLogLevel(v);
|
|
1801
2190
|
},
|
|
1802
2191
|
WRONGSTACK_INDEX_ON_START: (c, v) => {
|
|
1803
2192
|
c.indexing = { ...defaultIndexing, ...c.indexing, onSessionStart: envBool(v) };
|
|
@@ -1839,7 +2228,7 @@ function deepMerge(base, patch) {
|
|
|
1839
2228
|
out[k] = [.../* @__PURE__ */ new Set([...existing, ...v])];
|
|
1840
2229
|
} else {
|
|
1841
2230
|
out[k] = v;
|
|
1842
|
-
if (process.env.WRONGSTACK_DEBUG_CONFIG) {
|
|
2231
|
+
if (envBoolOptional(process.env.WRONGSTACK_DEBUG_CONFIG)) {
|
|
1843
2232
|
console.warn(
|
|
1844
2233
|
`[config] Non-primitive array for "${k}" replaced (global + local config merge). Global entries: ${existing?.length ?? 0}, local entries: ${v.length}.`
|
|
1845
2234
|
);
|
|
@@ -2235,12 +2624,6 @@ function compileUserRegex(pattern, flags) {
|
|
|
2235
2624
|
}
|
|
2236
2625
|
|
|
2237
2626
|
// src/storage/session-reader.ts
|
|
2238
|
-
function expectDefined3(value) {
|
|
2239
|
-
if (value === null || value === void 0) {
|
|
2240
|
-
throw new Error("Expected value to be defined");
|
|
2241
|
-
}
|
|
2242
|
-
return value;
|
|
2243
|
-
}
|
|
2244
2627
|
var DefaultSessionReader = class {
|
|
2245
2628
|
store;
|
|
2246
2629
|
constructor(opts) {
|
|
@@ -2302,7 +2685,7 @@ var DefaultSessionReader = class {
|
|
|
2302
2685
|
continue;
|
|
2303
2686
|
}
|
|
2304
2687
|
for (let i = 0; i < data.events.length; i++) {
|
|
2305
|
-
const ev =
|
|
2688
|
+
const ev = expectDefined(data.events[i]);
|
|
2306
2689
|
if (allowedTypes && !allowedTypes.has(ev.type)) continue;
|
|
2307
2690
|
const text = eventText(ev);
|
|
2308
2691
|
if (text === null) continue;
|
|
@@ -2751,7 +3134,10 @@ function attachTodosCheckpoint(state, filePath, sessionId) {
|
|
|
2751
3134
|
let pending = null;
|
|
2752
3135
|
let writeChain = Promise.resolve();
|
|
2753
3136
|
const enqueueWrite = (todos) => {
|
|
2754
|
-
writeChain = writeChain.then(() => saveTodosCheckpoint(filePath, sessionId, todos))
|
|
3137
|
+
writeChain = writeChain.then(() => saveTodosCheckpoint(filePath, sessionId, todos)).catch((err) => {
|
|
3138
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
3139
|
+
console.error(`[TodosCheckpoint] save failed for session ${sessionId}: ${msg}`);
|
|
3140
|
+
});
|
|
2755
3141
|
return writeChain;
|
|
2756
3142
|
};
|
|
2757
3143
|
const flush = () => {
|
|
@@ -3055,7 +3441,7 @@ async function acquireDirectorStateLock(lockPath, processId = process.pid) {
|
|
|
3055
3441
|
}
|
|
3056
3442
|
const lock = {
|
|
3057
3443
|
pid: processId,
|
|
3058
|
-
hostname:
|
|
3444
|
+
hostname: hostname(),
|
|
3059
3445
|
startedAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
3060
3446
|
};
|
|
3061
3447
|
await atomicWrite(lockPath, JSON.stringify(lock), { mode: 384 });
|
|
@@ -3467,9 +3853,18 @@ async function restrictFilePermissions(filePath, opts) {
|
|
|
3467
3853
|
const { execFile: execFile2 } = await import('child_process');
|
|
3468
3854
|
const { promisify: promisify2 } = await import('util');
|
|
3469
3855
|
const execFileAsync = promisify2(execFile2);
|
|
3470
|
-
|
|
3856
|
+
const user = windowsAccountName();
|
|
3857
|
+
if (!user) {
|
|
3858
|
+
warn(
|
|
3859
|
+
`[secret-vault] Could not determine the current Windows user for ${filePath}; skipping icacls hardening.`
|
|
3860
|
+
);
|
|
3861
|
+
return;
|
|
3862
|
+
}
|
|
3863
|
+
await execFileAsync("icacls", [filePath, "/inheritance:r", "/grant:r", `${user}:(F)`]);
|
|
3471
3864
|
} catch {
|
|
3472
|
-
warn(
|
|
3865
|
+
warn(
|
|
3866
|
+
`[secret-vault] Could not restrict permissions on ${filePath} \u2014 config file may be readable by other users on this system.`
|
|
3867
|
+
);
|
|
3473
3868
|
}
|
|
3474
3869
|
} else {
|
|
3475
3870
|
try {
|
|
@@ -3478,6 +3873,13 @@ async function restrictFilePermissions(filePath, opts) {
|
|
|
3478
3873
|
}
|
|
3479
3874
|
}
|
|
3480
3875
|
}
|
|
3876
|
+
function windowsAccountName() {
|
|
3877
|
+
const username = process.env.USERNAME || process.env.USER;
|
|
3878
|
+
if (!username || username.includes("\0")) return void 0;
|
|
3879
|
+
const domain = process.env.USERDOMAIN;
|
|
3880
|
+
if (domain && !domain.includes("\0")) return `${domain}\\${username}`;
|
|
3881
|
+
return username;
|
|
3882
|
+
}
|
|
3481
3883
|
function walkCount(node, vault, counter) {
|
|
3482
3884
|
if (node === null || node === void 0) return node;
|
|
3483
3885
|
if (typeof node !== "object") return node;
|
|
@@ -3565,12 +3967,6 @@ function getDangerousCapabilities(toolOrCaps) {
|
|
|
3565
3967
|
init_atomic_write();
|
|
3566
3968
|
|
|
3567
3969
|
// src/utils/glob-match.ts
|
|
3568
|
-
function expectDefined4(value) {
|
|
3569
|
-
if (value === null || value === void 0) {
|
|
3570
|
-
throw new Error("Expected value to be defined");
|
|
3571
|
-
}
|
|
3572
|
-
return value;
|
|
3573
|
-
}
|
|
3574
3970
|
function escapeRegex(s) {
|
|
3575
3971
|
return s.replace(/[.+^${}()|\\]/g, "\\$&");
|
|
3576
3972
|
}
|
|
@@ -3582,7 +3978,7 @@ function getCachedGlob(pattern) {
|
|
|
3582
3978
|
if (COMPILED_GLOB_CACHE.size >= CACHE_MAX_SIZE) {
|
|
3583
3979
|
const keys = [...COMPILED_GLOB_CACHE.keys()];
|
|
3584
3980
|
for (let i = 0; i < Math.floor(CACHE_MAX_SIZE / 4); i++) {
|
|
3585
|
-
COMPILED_GLOB_CACHE.delete(
|
|
3981
|
+
COMPILED_GLOB_CACHE.delete(expectDefined(keys[i]));
|
|
3586
3982
|
}
|
|
3587
3983
|
}
|
|
3588
3984
|
const re = compileGlob(pattern);
|
|
@@ -4447,12 +4843,6 @@ function parseDescription(raw) {
|
|
|
4447
4843
|
}
|
|
4448
4844
|
|
|
4449
4845
|
// src/utils/json-repair.ts
|
|
4450
|
-
function expectDefined5(value) {
|
|
4451
|
-
if (value === null || value === void 0) {
|
|
4452
|
-
throw new Error("Expected value to be defined");
|
|
4453
|
-
}
|
|
4454
|
-
return value;
|
|
4455
|
-
}
|
|
4456
4846
|
function completePartialObject(s) {
|
|
4457
4847
|
if (!s.trim().startsWith("{")) return s;
|
|
4458
4848
|
if (tryParse(s).ok) return s;
|
|
@@ -4464,7 +4854,7 @@ function completePartialObject(s) {
|
|
|
4464
4854
|
let contentEnd = 0;
|
|
4465
4855
|
let stringBraceDepth = 0;
|
|
4466
4856
|
for (let i = 0; i < s.length; i++) {
|
|
4467
|
-
const ch =
|
|
4857
|
+
const ch = expectDefined(s[i]);
|
|
4468
4858
|
if (inString) {
|
|
4469
4859
|
contentEnd = i + 1;
|
|
4470
4860
|
if (escaped) {
|
|
@@ -4542,6 +4932,7 @@ function tryParse(s) {
|
|
|
4542
4932
|
}
|
|
4543
4933
|
|
|
4544
4934
|
// src/core/streaming-response-builder.ts
|
|
4935
|
+
var STREAM_DRAIN_TIMEOUT_MS = 500;
|
|
4545
4936
|
function buildResponse(state) {
|
|
4546
4937
|
const content = [];
|
|
4547
4938
|
for (const b of state.blockOrder) {
|
|
@@ -4754,7 +5145,7 @@ async function streamProviderToResponse(provider, req, signal, ctx, events) {
|
|
|
4754
5145
|
await Promise.race([
|
|
4755
5146
|
Promise.resolve(iter.return?.()),
|
|
4756
5147
|
new Promise((resolve5) => {
|
|
4757
|
-
drainTimer = setTimeout(resolve5,
|
|
5148
|
+
drainTimer = setTimeout(resolve5, STREAM_DRAIN_TIMEOUT_MS);
|
|
4758
5149
|
})
|
|
4759
5150
|
]);
|
|
4760
5151
|
} finally {
|
|
@@ -4849,12 +5240,6 @@ var DefaultProviderRunner = class {
|
|
|
4849
5240
|
};
|
|
4850
5241
|
|
|
4851
5242
|
// src/utils/token-estimate.ts
|
|
4852
|
-
function expectDefined6(value) {
|
|
4853
|
-
if (value === null || value === void 0) {
|
|
4854
|
-
throw new Error("Expected value to be defined");
|
|
4855
|
-
}
|
|
4856
|
-
return value;
|
|
4857
|
-
}
|
|
4858
5243
|
var RoughTokenEstimate = (text, charsPerToken = 3.5) => Math.max(1, Math.ceil(text.length / charsPerToken));
|
|
4859
5244
|
var ESTIMATE_CACHE = /* @__PURE__ */ new Map();
|
|
4860
5245
|
var ESTIMATE_CACHE_MAX_SIZE = 1e4;
|
|
@@ -4864,7 +5249,7 @@ function getCachedEstimate(key, compute) {
|
|
|
4864
5249
|
if (ESTIMATE_CACHE.size >= ESTIMATE_CACHE_MAX_SIZE) {
|
|
4865
5250
|
const keys = [...ESTIMATE_CACHE.keys()];
|
|
4866
5251
|
for (let i = 0; i < Math.floor(ESTIMATE_CACHE_MAX_SIZE / 4); i++) {
|
|
4867
|
-
ESTIMATE_CACHE.delete(
|
|
5252
|
+
ESTIMATE_CACHE.delete(expectDefined(keys[i]));
|
|
4868
5253
|
}
|
|
4869
5254
|
}
|
|
4870
5255
|
const estimate = compute();
|
|
@@ -5357,12 +5742,6 @@ var IntelligentCompactor = class {
|
|
|
5357
5742
|
};
|
|
5358
5743
|
|
|
5359
5744
|
// src/models/llm-selector.ts
|
|
5360
|
-
function expectDefined7(value) {
|
|
5361
|
-
if (value === null || value === void 0) {
|
|
5362
|
-
throw new Error("Expected value to be defined");
|
|
5363
|
-
}
|
|
5364
|
-
return value;
|
|
5365
|
-
}
|
|
5366
5745
|
var DEFAULT_SYSTEM_PROMPT = `You are a context pruning assistant. Given a conversation history and a token budget, decide which message ranges are worth keeping verbatim and which should be collapsed into summaries.
|
|
5367
5746
|
|
|
5368
5747
|
Output a JSON object with this structure:
|
|
@@ -5403,7 +5782,7 @@ function formatMessages(messages, maxChars = 8e3) {
|
|
|
5403
5782
|
const lines = [];
|
|
5404
5783
|
let used = 0;
|
|
5405
5784
|
for (let i = 0; i < messages.length; i++) {
|
|
5406
|
-
const m =
|
|
5785
|
+
const m = expectDefined(messages[i]);
|
|
5407
5786
|
const role = m.role.padEnd(10, " ");
|
|
5408
5787
|
let text;
|
|
5409
5788
|
if (typeof m.content === "string") {
|
|
@@ -5468,7 +5847,7 @@ IMPORTANT: Total conversation (${totalTokens} tokens) exceeds budget (${effectiv
|
|
|
5468
5847
|
let tokenCount = 0;
|
|
5469
5848
|
let startIdx = 0;
|
|
5470
5849
|
for (let i = messages.length - 1; i >= 0; i--) {
|
|
5471
|
-
const m =
|
|
5850
|
+
const m = expectDefined(messages[i]);
|
|
5472
5851
|
const cost = typeof m.content === "string" ? Math.ceil(m.content.length / 4) : m.content.reduce(
|
|
5473
5852
|
(acc, b) => acc + (b.type === "text" ? Math.ceil(b.text.length / 4) : Math.ceil(JSON.stringify(b).length / 4)),
|
|
5474
5853
|
0
|
|
@@ -6075,17 +6454,12 @@ function createToolOutputSerializer(opts = {}) {
|
|
|
6075
6454
|
}
|
|
6076
6455
|
|
|
6077
6456
|
// src/execution/tool-executor.ts
|
|
6078
|
-
function expectDefined8(value) {
|
|
6079
|
-
if (value === null || value === void 0) {
|
|
6080
|
-
throw new Error("Expected value to be defined");
|
|
6081
|
-
}
|
|
6082
|
-
return value;
|
|
6083
|
-
}
|
|
6084
6457
|
var ToolExecutor = class {
|
|
6085
6458
|
constructor(registry, opts) {
|
|
6086
6459
|
this.registry = registry;
|
|
6087
6460
|
this.opts = opts;
|
|
6088
6461
|
this.iterationTimeoutMs = opts.iterationTimeoutMs ?? 3e5;
|
|
6462
|
+
this.maxToolTimeoutMs = opts.maxToolTimeoutMs ?? 3e5;
|
|
6089
6463
|
this.serializer = createToolOutputSerializer({
|
|
6090
6464
|
perIterationOutputCapBytes: opts.perIterationOutputCapBytes ?? 1e5
|
|
6091
6465
|
});
|
|
@@ -6094,6 +6468,7 @@ var ToolExecutor = class {
|
|
|
6094
6468
|
opts;
|
|
6095
6469
|
serializer;
|
|
6096
6470
|
iterationTimeoutMs;
|
|
6471
|
+
maxToolTimeoutMs;
|
|
6097
6472
|
/**
|
|
6098
6473
|
* Clear the interactive confirm awaiter so the executor returns
|
|
6099
6474
|
* `ToolConfirmPendingResult` instead of blocking on stdin. Used by
|
|
@@ -6331,7 +6706,10 @@ ${post.additionalContext}` };
|
|
|
6331
6706
|
if (parentSignal.reason instanceof Error) throw parentSignal.reason;
|
|
6332
6707
|
throw new Error(typeof parentSignal.reason === "string" ? parentSignal.reason : "aborted");
|
|
6333
6708
|
}
|
|
6334
|
-
const timeoutMs =
|
|
6709
|
+
const timeoutMs = clampTimeoutMs(
|
|
6710
|
+
tool.timeoutMs ?? this.iterationTimeoutMs,
|
|
6711
|
+
this.maxToolTimeoutMs
|
|
6712
|
+
);
|
|
6335
6713
|
const ctrl = new AbortController();
|
|
6336
6714
|
const timer = setTimeout(() => ctrl.abort(new Error("tool timeout")), timeoutMs);
|
|
6337
6715
|
const combined = AbortSignal.any([parentSignal, ctrl.signal]);
|
|
@@ -6466,6 +6844,12 @@ ${excerpt}`;
|
|
|
6466
6844
|
return void 0;
|
|
6467
6845
|
}
|
|
6468
6846
|
};
|
|
6847
|
+
function clampTimeoutMs(timeoutMs, maxTimeoutMs) {
|
|
6848
|
+
const fallback = 3e5;
|
|
6849
|
+
const finiteTimeout = Number.isFinite(timeoutMs) && timeoutMs > 0 ? timeoutMs : fallback;
|
|
6850
|
+
const finiteMax = Number.isFinite(maxTimeoutMs) && maxTimeoutMs > 0 ? maxTimeoutMs : fallback;
|
|
6851
|
+
return Math.max(1, Math.min(finiteTimeout, finiteMax));
|
|
6852
|
+
}
|
|
6469
6853
|
var MALFORMED_ARG_MARKERS = ["__raw", "__raw_arguments", "_raw"];
|
|
6470
6854
|
function hasMalformedArguments(input) {
|
|
6471
6855
|
if (!input || typeof input !== "object" || Array.isArray(input)) return false;
|
|
@@ -6476,7 +6860,7 @@ function hasMalformedArguments(input) {
|
|
|
6476
6860
|
function extractMalformedRaw(input) {
|
|
6477
6861
|
if (!hasMalformedArguments(input)) return void 0;
|
|
6478
6862
|
const obj = input;
|
|
6479
|
-
const value = obj[
|
|
6863
|
+
const value = obj[expectDefined(Object.keys(obj)[0])];
|
|
6480
6864
|
if (value === void 0 || value === null) return void 0;
|
|
6481
6865
|
if (typeof value === "string") return value;
|
|
6482
6866
|
try {
|
|
@@ -7278,6 +7662,7 @@ ${recentJournal}` : "No prior iterations.",
|
|
|
7278
7662
|
};
|
|
7279
7663
|
|
|
7280
7664
|
// src/coordination/subagent-budget.ts
|
|
7665
|
+
var TIMEOUT_PREEMPT_FRACTION = 0.85;
|
|
7281
7666
|
var BudgetExceededError = class extends Error {
|
|
7282
7667
|
kind;
|
|
7283
7668
|
limit;
|
|
@@ -7633,15 +8018,7 @@ function makeAgentSubagentRunner(opts) {
|
|
|
7633
8018
|
let budgetError = null;
|
|
7634
8019
|
const onBudgetError = (err) => {
|
|
7635
8020
|
if (err instanceof BudgetThresholdSignal) {
|
|
7636
|
-
|
|
7637
|
-
if (decision === "stop") {
|
|
7638
|
-
budgetError = new BudgetExceededError(err.kind, err.limit, err.used);
|
|
7639
|
-
aborter.abort();
|
|
7640
|
-
}
|
|
7641
|
-
}).catch(() => {
|
|
7642
|
-
budgetError = new BudgetExceededError(err.kind, err.limit, err.used);
|
|
7643
|
-
aborter.abort();
|
|
7644
|
-
});
|
|
8021
|
+
budgetError = err;
|
|
7645
8022
|
return;
|
|
7646
8023
|
}
|
|
7647
8024
|
aborter.abort();
|
|
@@ -7749,15 +8126,23 @@ function makeAgentSubagentRunner(opts) {
|
|
|
7749
8126
|
}
|
|
7750
8127
|
if (budgetError) {
|
|
7751
8128
|
if ("decision" in budgetError) {
|
|
7752
|
-
|
|
7753
|
-
|
|
8129
|
+
try {
|
|
8130
|
+
const decision = await budgetError.decision;
|
|
8131
|
+
if (decision === "stop") {
|
|
8132
|
+
budgetError = new BudgetExceededError(
|
|
8133
|
+
budgetError.kind,
|
|
8134
|
+
budgetError.limit,
|
|
8135
|
+
budgetError.used
|
|
8136
|
+
);
|
|
8137
|
+
} else {
|
|
8138
|
+
budgetError = null;
|
|
8139
|
+
}
|
|
8140
|
+
} catch {
|
|
7754
8141
|
budgetError = new BudgetExceededError(
|
|
7755
8142
|
budgetError.kind,
|
|
7756
8143
|
budgetError.limit,
|
|
7757
8144
|
budgetError.used
|
|
7758
8145
|
);
|
|
7759
|
-
} else {
|
|
7760
|
-
budgetError = null;
|
|
7761
8146
|
}
|
|
7762
8147
|
}
|
|
7763
8148
|
if (budgetError) throw budgetError;
|
|
@@ -10079,16 +10464,17 @@ Do not add prose, markdown, or code fences.`;
|
|
|
10079
10464
|
const raw = (await complete(prompt)).trim();
|
|
10080
10465
|
const match = raw.match(/\{[\s\S]*\}/);
|
|
10081
10466
|
if (!match) return null;
|
|
10082
|
-
|
|
10083
|
-
|
|
10084
|
-
|
|
10085
|
-
|
|
10086
|
-
|
|
10087
|
-
|
|
10088
|
-
|
|
10089
|
-
|
|
10090
|
-
|
|
10091
|
-
|
|
10467
|
+
const parsed = safeParse(
|
|
10468
|
+
match[0]
|
|
10469
|
+
);
|
|
10470
|
+
if (!parsed.ok || !parsed.value || typeof parsed.value.role !== "string") return null;
|
|
10471
|
+
const role = parsed.value.role.trim();
|
|
10472
|
+
const valid = candidates.some((c) => c.role === role);
|
|
10473
|
+
if (!valid) return null;
|
|
10474
|
+
return {
|
|
10475
|
+
role,
|
|
10476
|
+
reason: typeof parsed.value.reason === "string" ? parsed.value.reason : void 0
|
|
10477
|
+
};
|
|
10092
10478
|
};
|
|
10093
10479
|
}
|
|
10094
10480
|
|
|
@@ -11043,15 +11429,38 @@ var DefaultMultiAgentCoordinator = class _DefaultMultiAgentCoordinator extends E
|
|
|
11043
11429
|
}
|
|
11044
11430
|
const start = Date.now();
|
|
11045
11431
|
let timer = null;
|
|
11432
|
+
let preemptedForLimit = null;
|
|
11046
11433
|
const timeoutPromise = new Promise((_, reject) => {
|
|
11047
11434
|
const armFor = (ms) => {
|
|
11048
11435
|
if (timer) clearTimeout(timer);
|
|
11049
11436
|
timer = setTimeout(onTick, Math.max(0, ms));
|
|
11050
11437
|
};
|
|
11051
11438
|
const scheduleNext = () => {
|
|
11052
|
-
const
|
|
11439
|
+
const wallLimit = budget.limits.timeoutMs ?? initialTimeoutMs;
|
|
11440
|
+
const wallRemaining = initialTimeoutMs === void 0 ? Number.POSITIVE_INFINITY : wallLimit - (Date.now() - start);
|
|
11053
11441
|
const idleRemaining = idleLimitMs === void 0 ? Number.POSITIVE_INFINITY : (budget.limits.idleTimeoutMs ?? idleLimitMs) - budget.idleMs();
|
|
11054
|
-
|
|
11442
|
+
const preemptRemaining = initialTimeoutMs === void 0 || preemptedForLimit === wallLimit ? Number.POSITIVE_INFINITY : wallLimit * TIMEOUT_PREEMPT_FRACTION - (Date.now() - start);
|
|
11443
|
+
armFor(Math.max(25, Math.min(wallRemaining, idleRemaining, preemptRemaining)));
|
|
11444
|
+
};
|
|
11445
|
+
const negotiateTimeout = async (used, limit) => {
|
|
11446
|
+
const handler = budget.onThreshold;
|
|
11447
|
+
if (!handler) return "stop";
|
|
11448
|
+
const result = handler({
|
|
11449
|
+
kind: "timeout",
|
|
11450
|
+
used,
|
|
11451
|
+
limit,
|
|
11452
|
+
requestDecision: () => new Promise((resolveDecision) => {
|
|
11453
|
+
budget._events?.emit("budget.threshold_reached", {
|
|
11454
|
+
kind: "timeout",
|
|
11455
|
+
used,
|
|
11456
|
+
limit,
|
|
11457
|
+
timeoutMs: 6e4,
|
|
11458
|
+
extend: (extra) => resolveDecision({ extend: extra }),
|
|
11459
|
+
deny: () => resolveDecision("stop")
|
|
11460
|
+
});
|
|
11461
|
+
})
|
|
11462
|
+
});
|
|
11463
|
+
return typeof result === "string" ? result : await result;
|
|
11055
11464
|
};
|
|
11056
11465
|
const onTick = async () => {
|
|
11057
11466
|
const elapsed = Date.now() - start;
|
|
@@ -11064,6 +11473,21 @@ var DefaultMultiAgentCoordinator = class _DefaultMultiAgentCoordinator extends E
|
|
|
11064
11473
|
reject(new BudgetExceededError("timeout", idleLimit ?? 0, budget.idleMs()));
|
|
11065
11474
|
return;
|
|
11066
11475
|
}
|
|
11476
|
+
if (wallLimit !== void 0 && !wallExceeded && budget.onThreshold && preemptedForLimit !== wallLimit && elapsed >= wallLimit * TIMEOUT_PREEMPT_FRACTION) {
|
|
11477
|
+
try {
|
|
11478
|
+
const decision = await negotiateTimeout(elapsed, wallLimit);
|
|
11479
|
+
if (typeof decision !== "string" && decision.extend.timeoutMs !== void 0) {
|
|
11480
|
+
budget.limits.timeoutMs = decision.extend.timeoutMs;
|
|
11481
|
+
preemptedForLimit = null;
|
|
11482
|
+
} else {
|
|
11483
|
+
preemptedForLimit = wallLimit;
|
|
11484
|
+
}
|
|
11485
|
+
} catch {
|
|
11486
|
+
preemptedForLimit = wallLimit;
|
|
11487
|
+
}
|
|
11488
|
+
scheduleNext();
|
|
11489
|
+
return;
|
|
11490
|
+
}
|
|
11067
11491
|
if (!wallExceeded) {
|
|
11068
11492
|
scheduleNext();
|
|
11069
11493
|
return;
|
|
@@ -11075,28 +11499,15 @@ var DefaultMultiAgentCoordinator = class _DefaultMultiAgentCoordinator extends E
|
|
|
11075
11499
|
return;
|
|
11076
11500
|
}
|
|
11077
11501
|
try {
|
|
11078
|
-
const
|
|
11079
|
-
kind: "timeout",
|
|
11080
|
-
used: elapsed,
|
|
11081
|
-
limit,
|
|
11082
|
-
requestDecision: () => new Promise((resolveDecision) => {
|
|
11083
|
-
budget._events?.emit("budget.threshold_reached", {
|
|
11084
|
-
kind: "timeout",
|
|
11085
|
-
used: elapsed,
|
|
11086
|
-
limit,
|
|
11087
|
-
timeoutMs: 6e4,
|
|
11088
|
-
extend: (extra) => resolveDecision({ extend: extra }),
|
|
11089
|
-
deny: () => resolveDecision("stop")
|
|
11090
|
-
});
|
|
11091
|
-
})
|
|
11092
|
-
});
|
|
11093
|
-
const decision = typeof result === "string" ? result : await result;
|
|
11502
|
+
const decision = await negotiateTimeout(elapsed, limit);
|
|
11094
11503
|
if (decision === "continue" || decision === "throw" || decision === "stop") {
|
|
11504
|
+
preemptedForLimit = null;
|
|
11095
11505
|
armFor(Math.max(1e3, limit));
|
|
11096
11506
|
return;
|
|
11097
11507
|
}
|
|
11098
11508
|
if (decision.extend.timeoutMs !== void 0) {
|
|
11099
11509
|
budget.limits.timeoutMs = decision.extend.timeoutMs;
|
|
11510
|
+
preemptedForLimit = null;
|
|
11100
11511
|
scheduleNext();
|
|
11101
11512
|
return;
|
|
11102
11513
|
}
|
|
@@ -11227,12 +11638,6 @@ var DefaultMultiAgentCoordinator = class _DefaultMultiAgentCoordinator extends E
|
|
|
11227
11638
|
};
|
|
11228
11639
|
|
|
11229
11640
|
// src/execution/parallel-eternal-engine.ts
|
|
11230
|
-
function expectDefined9(value) {
|
|
11231
|
-
if (value === null || value === void 0) {
|
|
11232
|
-
throw new Error("Expected value to be defined");
|
|
11233
|
-
}
|
|
11234
|
-
return value;
|
|
11235
|
-
}
|
|
11236
11641
|
var GOAL_COMPLETE_MARKER2 = /^\s*\[goal[_\s-]?complete\]\s*$/im;
|
|
11237
11642
|
var ParallelEternalEngine = class {
|
|
11238
11643
|
constructor(opts) {
|
|
@@ -11388,7 +11793,7 @@ var ParallelEternalEngine = class {
|
|
|
11388
11793
|
// Fan-out
|
|
11389
11794
|
// -------------------------------------------------------------------------
|
|
11390
11795
|
async fanOut(goal, tasks) {
|
|
11391
|
-
const coordinator =
|
|
11796
|
+
const coordinator = expectDefined(this.coordinator);
|
|
11392
11797
|
const slotCount = Math.min(this.slots, tasks.length);
|
|
11393
11798
|
const routes = this.dispatchEnabled ? await Promise.all(
|
|
11394
11799
|
tasks.slice(0, slotCount).map(
|
|
@@ -11420,7 +11825,7 @@ ${recentJournal}` : "No prior iterations.",
|
|
|
11420
11825
|
const routeInfo = [];
|
|
11421
11826
|
const spawnPromises = [];
|
|
11422
11827
|
for (let i = 0; i < slotCount; i++) {
|
|
11423
|
-
const task =
|
|
11828
|
+
const task = expectDefined(tasks[i]);
|
|
11424
11829
|
const route = routes[i] ?? null;
|
|
11425
11830
|
const subagentId = `parallel-${this.iterations}-${i}`;
|
|
11426
11831
|
const taskId = randomUUID();
|
|
@@ -11911,12 +12316,6 @@ function createMessage(type, from, payload, to) {
|
|
|
11911
12316
|
priority: "normal"
|
|
11912
12317
|
};
|
|
11913
12318
|
}
|
|
11914
|
-
function expectDefined10(value) {
|
|
11915
|
-
if (value === null || value === void 0) {
|
|
11916
|
-
throw new Error("Expected value to be defined");
|
|
11917
|
-
}
|
|
11918
|
-
return value;
|
|
11919
|
-
}
|
|
11920
12319
|
var GLOB_CHARS = /* @__PURE__ */ new Set(["*", "?", "["]);
|
|
11921
12320
|
var IS_WINDOWS = process.platform === "win32";
|
|
11922
12321
|
var SEP = IS_WINDOWS ? "\\" : "/";
|
|
@@ -11930,7 +12329,7 @@ function globToRegex(pat) {
|
|
|
11930
12329
|
let i = 0;
|
|
11931
12330
|
let re = "^";
|
|
11932
12331
|
while (i < pat.length) {
|
|
11933
|
-
const c =
|
|
12332
|
+
const c = expectDefined(pat[i]);
|
|
11934
12333
|
if (c === "*") {
|
|
11935
12334
|
if (pat[i + 1] === "*") {
|
|
11936
12335
|
re += ".*";
|
|
@@ -11969,7 +12368,7 @@ function globToRegex(pat) {
|
|
|
11969
12368
|
}
|
|
11970
12369
|
function baseDir(pat) {
|
|
11971
12370
|
let i = pat.length - 1;
|
|
11972
|
-
while (i >= 0 && !GLOB_CHARS.has(
|
|
12371
|
+
while (i >= 0 && !GLOB_CHARS.has(expectDefined(pat[i])) && pat[i] !== SEP && pat[i] !== "/") i--;
|
|
11973
12372
|
const cut = i >= 0 ? pat.lastIndexOf(SEP, i) : pat.lastIndexOf("/", i);
|
|
11974
12373
|
return cut < 0 ? "." : pat.slice(0, cut);
|
|
11975
12374
|
}
|
|
@@ -12004,8 +12403,8 @@ async function expandGlob(pattern) {
|
|
|
12004
12403
|
for (const e of entries) {
|
|
12005
12404
|
const full = `${dir}${SEP}${e}`;
|
|
12006
12405
|
try {
|
|
12007
|
-
const
|
|
12008
|
-
if (
|
|
12406
|
+
const stat6 = await fsp.stat(full);
|
|
12407
|
+
if (stat6.isDirectory()) await walk4(full, rest);
|
|
12009
12408
|
} catch {
|
|
12010
12409
|
}
|
|
12011
12410
|
}
|
|
@@ -12022,8 +12421,8 @@ async function expandGlob(pattern) {
|
|
|
12022
12421
|
if (entries.includes(seg)) {
|
|
12023
12422
|
const full = `${dir}${SEP}${seg}`;
|
|
12024
12423
|
try {
|
|
12025
|
-
const
|
|
12026
|
-
if (
|
|
12424
|
+
const stat6 = await fsp.stat(full);
|
|
12425
|
+
if (stat6.isDirectory()) await walk4(full, rest);
|
|
12027
12426
|
} catch {
|
|
12028
12427
|
}
|
|
12029
12428
|
}
|
|
@@ -12050,7 +12449,9 @@ var CollabSession = class extends EventEmitter {
|
|
|
12050
12449
|
settled = false;
|
|
12051
12450
|
timeoutMs;
|
|
12052
12451
|
cancelled = false;
|
|
12452
|
+
_raceResolved = false;
|
|
12053
12453
|
alerts = [];
|
|
12454
|
+
snapshotWarnings = [];
|
|
12054
12455
|
/** Tracks tool call counts per subagent for progress-based timeout decisions. */
|
|
12055
12456
|
progressBySubagent = /* @__PURE__ */ new Map();
|
|
12056
12457
|
/** Last tool call count when a timeout warning was handled. */
|
|
@@ -12120,10 +12521,19 @@ var CollabSession = class extends EventEmitter {
|
|
|
12120
12521
|
}
|
|
12121
12522
|
for (const filePath of allFiles) {
|
|
12122
12523
|
try {
|
|
12123
|
-
const content = await
|
|
12524
|
+
const [content, stat6] = await Promise.all([
|
|
12525
|
+
fsp.readFile(filePath, "utf8"),
|
|
12526
|
+
fsp.stat(filePath)
|
|
12527
|
+
]);
|
|
12124
12528
|
const ext = filePath.split(".").pop() ?? "";
|
|
12125
12529
|
const language = ext === "ts" || ext === "tsx" ? "typescript" : ext === "js" || ext === "jsx" ? "javascript" : ext === "md" ? "markdown" : ext === "json" ? "json" : void 0;
|
|
12126
|
-
this.snapshot.files.push({
|
|
12530
|
+
this.snapshot.files.push({
|
|
12531
|
+
path: filePath,
|
|
12532
|
+
content,
|
|
12533
|
+
language,
|
|
12534
|
+
snapshotMtimeMs: stat6.mtimeMs,
|
|
12535
|
+
snapshotSizeBytes: stat6.size
|
|
12536
|
+
});
|
|
12127
12537
|
} catch {
|
|
12128
12538
|
this.snapshot.files.push({ path: filePath, content: "", language: void 0 });
|
|
12129
12539
|
}
|
|
@@ -12136,7 +12546,7 @@ var CollabSession = class extends EventEmitter {
|
|
|
12136
12546
|
* Safe to call multiple times (idempotent after first call).
|
|
12137
12547
|
*/
|
|
12138
12548
|
cancel(reason = "Director cancelled collab session") {
|
|
12139
|
-
if (this.
|
|
12549
|
+
if (this.cancelled) return;
|
|
12140
12550
|
this.cancelled = true;
|
|
12141
12551
|
if (this._timeoutTimer) {
|
|
12142
12552
|
clearTimeout(this._timeoutTimer);
|
|
@@ -12146,7 +12556,11 @@ var CollabSession = class extends EventEmitter {
|
|
|
12146
12556
|
subagentId: this.director.id,
|
|
12147
12557
|
ts: Date.now(),
|
|
12148
12558
|
type: "director.cancel_collab",
|
|
12149
|
-
payload: {
|
|
12559
|
+
payload: {
|
|
12560
|
+
sessionId: this.sessionId,
|
|
12561
|
+
reason,
|
|
12562
|
+
cancelledAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
12563
|
+
}
|
|
12150
12564
|
});
|
|
12151
12565
|
this.fleetBus.emit({
|
|
12152
12566
|
subagentId: this.director.id,
|
|
@@ -12170,6 +12584,7 @@ var CollabSession = class extends EventEmitter {
|
|
|
12170
12584
|
this.subagentIds.set("critic", criticId);
|
|
12171
12585
|
const timeout = new Promise((_, reject) => {
|
|
12172
12586
|
this._timeoutTimer = setTimeout(() => {
|
|
12587
|
+
if (this._raceResolved) return;
|
|
12173
12588
|
this.cancel("Session-level timeout reached");
|
|
12174
12589
|
reject(new Error(`CollabSession timed out after ${this.timeoutMs}ms`));
|
|
12175
12590
|
}, this.timeoutMs);
|
|
@@ -12185,6 +12600,7 @@ var CollabSession = class extends EventEmitter {
|
|
|
12185
12600
|
timeout
|
|
12186
12601
|
]);
|
|
12187
12602
|
} catch (err) {
|
|
12603
|
+
this._raceResolved = true;
|
|
12188
12604
|
if (this._timeoutTimer) {
|
|
12189
12605
|
clearTimeout(this._timeoutTimer);
|
|
12190
12606
|
this._timeoutTimer = void 0;
|
|
@@ -12197,7 +12613,9 @@ var CollabSession = class extends EventEmitter {
|
|
|
12197
12613
|
for (const result of results?.flat() ?? []) {
|
|
12198
12614
|
await this.parseAndEmit(result);
|
|
12199
12615
|
}
|
|
12616
|
+
this.snapshotWarnings = await this.checkSnapshotFreshness();
|
|
12200
12617
|
const report = this.assembleReport();
|
|
12618
|
+
this._raceResolved = true;
|
|
12201
12619
|
this.cleanup();
|
|
12202
12620
|
this.emit("session.done", report);
|
|
12203
12621
|
return report;
|
|
@@ -12260,7 +12678,7 @@ var CollabSession = class extends EventEmitter {
|
|
|
12260
12678
|
const defaults = {
|
|
12261
12679
|
"bug-hunter": { maxIterations: 2e3, maxToolCalls: 5e3, timeoutMs: 10 * 60 * 1e3 },
|
|
12262
12680
|
"refactor-planner": { maxIterations: 1500, maxToolCalls: 4e3, timeoutMs: 8 * 60 * 1e3 },
|
|
12263
|
-
|
|
12681
|
+
critic: { maxIterations: 1e3, maxToolCalls: 3e3, timeoutMs: 6 * 60 * 1e3 }
|
|
12264
12682
|
};
|
|
12265
12683
|
return defaults[role] ?? { maxIterations: 1500, maxToolCalls: 4e3, timeoutMs: 8 * 60 * 1e3 };
|
|
12266
12684
|
}
|
|
@@ -12340,7 +12758,10 @@ Emit each evaluation immediately. Do not wait until you have read all reports.`;
|
|
|
12340
12758
|
}
|
|
12341
12759
|
wireFleetBus() {
|
|
12342
12760
|
const dTool = this.fleetBus.filter("tool.executed", (e) => {
|
|
12343
|
-
this.progressBySubagent.set(
|
|
12761
|
+
this.progressBySubagent.set(
|
|
12762
|
+
e.subagentId,
|
|
12763
|
+
(this.progressBySubagent.get(e.subagentId) ?? 0) + 1
|
|
12764
|
+
);
|
|
12344
12765
|
});
|
|
12345
12766
|
this.disposers.push(dTool);
|
|
12346
12767
|
const dBudget = this.fleetBus.filter("budget.threshold_reached", (e) => {
|
|
@@ -12379,7 +12800,10 @@ Emit each evaluation immediately. Do not wait until you have read all reports.`;
|
|
|
12379
12800
|
return;
|
|
12380
12801
|
}
|
|
12381
12802
|
this.lastTimeoutProgress.set(e.subagentId, progress);
|
|
12382
|
-
const newLimit = Math.min(
|
|
12803
|
+
const newLimit = Math.min(
|
|
12804
|
+
Math.ceil((payload.timeoutMs ?? payload.limit) * 2),
|
|
12805
|
+
24 * 60 * 6e4
|
|
12806
|
+
);
|
|
12383
12807
|
setImmediate(() => {
|
|
12384
12808
|
payload.extend({ timeoutMs: newLimit });
|
|
12385
12809
|
});
|
|
@@ -12489,15 +12913,18 @@ Emit each evaluation immediately. Do not wait until you have read all reports.`;
|
|
|
12489
12913
|
needs_revision: 1,
|
|
12490
12914
|
reject: 2
|
|
12491
12915
|
};
|
|
12492
|
-
const overallVerdict = evalList.reduce(
|
|
12493
|
-
|
|
12494
|
-
|
|
12495
|
-
|
|
12496
|
-
|
|
12497
|
-
|
|
12498
|
-
|
|
12916
|
+
const overallVerdict = evalList.reduce((worst, eval_) => {
|
|
12917
|
+
const w = verdictOrder[worst];
|
|
12918
|
+
const c = verdictOrder[eval_.verdict];
|
|
12919
|
+
return c > w ? eval_.verdict : worst;
|
|
12920
|
+
}, "approve");
|
|
12921
|
+
const summary = this.buildMarkdownSummary(
|
|
12922
|
+
bugList,
|
|
12923
|
+
planList,
|
|
12924
|
+
evalList,
|
|
12925
|
+
overallVerdict,
|
|
12926
|
+
disposition
|
|
12499
12927
|
);
|
|
12500
|
-
const summary = this.buildMarkdownSummary(bugList, planList, evalList, overallVerdict, disposition);
|
|
12501
12928
|
return {
|
|
12502
12929
|
sessionId: this.sessionId,
|
|
12503
12930
|
startedAt: this.snapshot.createdAt,
|
|
@@ -12508,10 +12935,28 @@ Emit each evaluation immediately. Do not wait until you have read all reports.`;
|
|
|
12508
12935
|
refactorPlans: planList,
|
|
12509
12936
|
evaluations: evalList,
|
|
12510
12937
|
alerts: [...this.alerts],
|
|
12938
|
+
...this.snapshotWarnings.length > 0 ? { snapshotWarnings: this.snapshotWarnings } : {},
|
|
12511
12939
|
overallVerdict,
|
|
12512
12940
|
summary
|
|
12513
12941
|
};
|
|
12514
12942
|
}
|
|
12943
|
+
async checkSnapshotFreshness() {
|
|
12944
|
+
const warnings = [];
|
|
12945
|
+
for (const file of this.snapshot.files) {
|
|
12946
|
+
if (file.snapshotMtimeMs === void 0 && file.snapshotSizeBytes === void 0) continue;
|
|
12947
|
+
try {
|
|
12948
|
+
const stat6 = await fsp.stat(file.path);
|
|
12949
|
+
const mtimeChanged = file.snapshotMtimeMs !== void 0 && stat6.mtimeMs > file.snapshotMtimeMs + 1;
|
|
12950
|
+
const sizeChanged = file.snapshotSizeBytes !== void 0 && stat6.size !== file.snapshotSizeBytes;
|
|
12951
|
+
if (mtimeChanged || sizeChanged) {
|
|
12952
|
+
warnings.push(`${file.path} changed after the collab snapshot was captured.`);
|
|
12953
|
+
}
|
|
12954
|
+
} catch {
|
|
12955
|
+
warnings.push(`${file.path} could not be checked after the collab snapshot was captured.`);
|
|
12956
|
+
}
|
|
12957
|
+
}
|
|
12958
|
+
return warnings;
|
|
12959
|
+
}
|
|
12515
12960
|
buildMarkdownSummary(bugs, plans, evals, overallVerdict, disposition) {
|
|
12516
12961
|
const lines = [
|
|
12517
12962
|
`## Collaborative Debugging Report \u2014 ${this.sessionId}`,
|
|
@@ -12528,10 +12973,19 @@ Emit each evaluation immediately. Do not wait until you have read all reports.`;
|
|
|
12528
12973
|
}
|
|
12529
12974
|
lines.push("");
|
|
12530
12975
|
}
|
|
12976
|
+
if (this.snapshotWarnings.length > 0) {
|
|
12977
|
+
lines.push("### Snapshot Warnings", "");
|
|
12978
|
+
for (const warning of this.snapshotWarnings) {
|
|
12979
|
+
lines.push(`- ${warning}`);
|
|
12980
|
+
}
|
|
12981
|
+
lines.push("");
|
|
12982
|
+
}
|
|
12531
12983
|
if (bugs.length > 0) {
|
|
12532
12984
|
lines.push("### Bugs Found", "");
|
|
12533
12985
|
for (const b of bugs) {
|
|
12534
|
-
lines.push(
|
|
12986
|
+
lines.push(
|
|
12987
|
+
`- **[${b.severity.toUpperCase()}]** \`${b.location.file}:${b.location.line}\` \u2014 ${b.description}`
|
|
12988
|
+
);
|
|
12535
12989
|
}
|
|
12536
12990
|
lines.push("");
|
|
12537
12991
|
}
|
|
@@ -13134,6 +13588,7 @@ var FleetBus = class {
|
|
|
13134
13588
|
set.add(handler);
|
|
13135
13589
|
return () => {
|
|
13136
13590
|
set.delete(handler);
|
|
13591
|
+
if (set.size === 0) this.byId.delete(subagentId);
|
|
13137
13592
|
};
|
|
13138
13593
|
}
|
|
13139
13594
|
/** Subscribe to one event type across all subagents. */
|
|
@@ -13146,6 +13601,7 @@ var FleetBus = class {
|
|
|
13146
13601
|
set.add(handler);
|
|
13147
13602
|
return () => {
|
|
13148
13603
|
set.delete(handler);
|
|
13604
|
+
if (set.size === 0) this.byType.delete(type);
|
|
13149
13605
|
};
|
|
13150
13606
|
}
|
|
13151
13607
|
/** Subscribe to literally everything. The fleet roll-up uses this. */
|
|
@@ -13179,6 +13635,11 @@ var FleetBus = class {
|
|
|
13179
13635
|
}
|
|
13180
13636
|
}
|
|
13181
13637
|
}
|
|
13638
|
+
clear() {
|
|
13639
|
+
this.byId.clear();
|
|
13640
|
+
this.byType.clear();
|
|
13641
|
+
this.any.clear();
|
|
13642
|
+
}
|
|
13182
13643
|
};
|
|
13183
13644
|
var FleetUsageAggregator = class {
|
|
13184
13645
|
constructor(bus, priceLookup, metaLookup) {
|
|
@@ -14411,7 +14872,9 @@ var Director = class _Director {
|
|
|
14411
14872
|
let toolUses = 0;
|
|
14412
14873
|
for (const line of targetLines) {
|
|
14413
14874
|
try {
|
|
14414
|
-
const
|
|
14875
|
+
const parsed = safeParse(line);
|
|
14876
|
+
if (!parsed.ok || !parsed.value) continue;
|
|
14877
|
+
const ev = parsed.value;
|
|
14415
14878
|
if (ev.type === "assistant" && typeof ev.text === "string") {
|
|
14416
14879
|
lastAssistantText = ev.text;
|
|
14417
14880
|
} else if (ev.type === "stop" && ev.stopReason) {
|
|
@@ -14725,7 +15188,7 @@ function createDelegateTool(opts) {
|
|
|
14725
15188
|
const offIter = dir.fleet.filter("iteration.started", bump);
|
|
14726
15189
|
const offProgress = dir.fleet.filter("tool.progress", bump);
|
|
14727
15190
|
arm();
|
|
14728
|
-
dir.awaitTasks([taskId]).then((r) => finish(r[0] ?? {
|
|
15191
|
+
dir.awaitTasks([taskId]).then((r) => finish(r[0] ?? { __emptyResult: true })).catch(() => finish({ __timeout: true }));
|
|
14729
15192
|
});
|
|
14730
15193
|
if ("__timeout" in result) {
|
|
14731
15194
|
const partial2 = await readSubagentPartial(opts, subagentId);
|
|
@@ -14750,6 +15213,29 @@ function createDelegateTool(opts) {
|
|
|
14750
15213
|
partial: partial2
|
|
14751
15214
|
};
|
|
14752
15215
|
}
|
|
15216
|
+
if ("__emptyResult" in result) {
|
|
15217
|
+
const partial2 = await readSubagentPartial(opts, subagentId);
|
|
15218
|
+
opts.events?.emit("delegate.completed", {
|
|
15219
|
+
target,
|
|
15220
|
+
task: i.task,
|
|
15221
|
+
ok: false,
|
|
15222
|
+
status: "empty_result",
|
|
15223
|
+
summary: `[${target}] completed without a task result`,
|
|
15224
|
+
durationMs: 0,
|
|
15225
|
+
iterations: partial2?.events ?? 0,
|
|
15226
|
+
toolCalls: partial2?.toolUsesObserved ?? 0,
|
|
15227
|
+
subagentId
|
|
15228
|
+
});
|
|
15229
|
+
return {
|
|
15230
|
+
ok: false,
|
|
15231
|
+
stopReason: "error",
|
|
15232
|
+
error: "Director returned no task result for the delegated task.",
|
|
15233
|
+
hint: "Check fleet state with /fleet status, then retry or reassign the task.",
|
|
15234
|
+
subagentId,
|
|
15235
|
+
taskId,
|
|
15236
|
+
partial: partial2
|
|
15237
|
+
};
|
|
15238
|
+
}
|
|
14753
15239
|
const baseStopReason = result.status === "success" ? "end_turn" : result.status === "timeout" ? "subagent_timeout" : result.status === "stopped" ? "aborted" : "budget_exhausted";
|
|
14754
15240
|
const partial = result.status === "success" ? void 0 : await readSubagentPartial(opts, subagentId);
|
|
14755
15241
|
const errorKind = result.error?.kind;
|
|
@@ -14922,7 +15408,9 @@ async function readSubagentPartial(opts, subagentId) {
|
|
|
14922
15408
|
let toolUses = 0;
|
|
14923
15409
|
for (const line of lines) {
|
|
14924
15410
|
try {
|
|
14925
|
-
const
|
|
15411
|
+
const parsed = safeParse(line);
|
|
15412
|
+
if (!parsed.ok || !parsed.value) continue;
|
|
15413
|
+
const ev = parsed.value;
|
|
14926
15414
|
if (ev.type === "tool_use") toolUses += 1;
|
|
14927
15415
|
if (ev.type === "llm_response") {
|
|
14928
15416
|
if (typeof ev.stopReason === "string") lastStopReason = ev.stopReason;
|
|
@@ -15694,8 +16182,8 @@ async function loadProjectModes(modesDir) {
|
|
|
15694
16182
|
for (const entry of entries) {
|
|
15695
16183
|
if (!entry.endsWith(".md") && !entry.endsWith(".txt")) continue;
|
|
15696
16184
|
const filePath = path3.join(modesDir, entry);
|
|
15697
|
-
const
|
|
15698
|
-
if (!
|
|
16185
|
+
const stat6 = await fsp.stat(filePath);
|
|
16186
|
+
if (!stat6.isFile()) continue;
|
|
15699
16187
|
const content = await fsp.readFile(filePath, "utf8");
|
|
15700
16188
|
const id = path3.basename(entry, path3.extname(entry));
|
|
15701
16189
|
modes.push({
|
|
@@ -16835,12 +17323,6 @@ var TaskGraphStore = class {
|
|
|
16835
17323
|
};
|
|
16836
17324
|
|
|
16837
17325
|
// src/sdd/spec-builder.ts
|
|
16838
|
-
function expectDefined11(value) {
|
|
16839
|
-
if (value === null || value === void 0) {
|
|
16840
|
-
throw new Error("Expected value to be defined");
|
|
16841
|
-
}
|
|
16842
|
-
return value;
|
|
16843
|
-
}
|
|
16844
17326
|
function buildQuestioningPrompt(session, min, max) {
|
|
16845
17327
|
const answered = session.answers.length;
|
|
16846
17328
|
const remaining = Math.max(0, min - answered);
|
|
@@ -16886,7 +17368,7 @@ function buildQuestioningPrompt(session, min, max) {
|
|
|
16886
17368
|
if (answered > 0) {
|
|
16887
17369
|
lines.push("", "**Conversation so far:**");
|
|
16888
17370
|
for (let i = 0; i < answered; i++) {
|
|
16889
|
-
const a =
|
|
17371
|
+
const a = expectDefined(session.answers[i]);
|
|
16890
17372
|
lines.push(``, `Q${i + 1}: ${a.question}`, `A${i + 1}: ${a.answer}`);
|
|
16891
17373
|
}
|
|
16892
17374
|
}
|
|
@@ -17628,12 +18110,6 @@ function truncate2(str, maxLen) {
|
|
|
17628
18110
|
}
|
|
17629
18111
|
|
|
17630
18112
|
// src/sdd/critical-path.ts
|
|
17631
|
-
function expectDefined12(value) {
|
|
17632
|
-
if (value === null || value === void 0) {
|
|
17633
|
-
throw new Error("Expected value to be defined");
|
|
17634
|
-
}
|
|
17635
|
-
return value;
|
|
17636
|
-
}
|
|
17637
18113
|
function analyzeCriticalPath(graph) {
|
|
17638
18114
|
const nodes = Array.from(graph.nodes.values());
|
|
17639
18115
|
const topoOrder = topologicalSort(graph);
|
|
@@ -17709,7 +18185,7 @@ function getTransitiveBlocked(_graph, taskId, blocksMap) {
|
|
|
17709
18185
|
const visited = /* @__PURE__ */ new Set();
|
|
17710
18186
|
const queue = [taskId];
|
|
17711
18187
|
while (queue.length > 0) {
|
|
17712
|
-
const current =
|
|
18188
|
+
const current = expectDefined(queue.shift());
|
|
17713
18189
|
const blocked = blocksMap.get(current);
|
|
17714
18190
|
if (!blocked) continue;
|
|
17715
18191
|
for (const id of blocked) {
|
|
@@ -17755,7 +18231,7 @@ function computeCriticalPath(graph, _topoOrder, blockedByMap) {
|
|
|
17755
18231
|
if (!changed) break;
|
|
17756
18232
|
}
|
|
17757
18233
|
let maxDist = 0;
|
|
17758
|
-
let maxId =
|
|
18234
|
+
let maxId = expectDefined(allIds[0]);
|
|
17759
18235
|
for (const id of allIds) {
|
|
17760
18236
|
const d = dist.get(id) ?? 0;
|
|
17761
18237
|
if (d > maxDist) {
|
|
@@ -18224,12 +18700,6 @@ var SddTaskDecomposer = class {
|
|
|
18224
18700
|
return nodes.some((n) => n.status === "blocked");
|
|
18225
18701
|
}
|
|
18226
18702
|
};
|
|
18227
|
-
function expectDefined13(value) {
|
|
18228
|
-
if (value === null || value === void 0) {
|
|
18229
|
-
throw new Error("Expected value to be defined");
|
|
18230
|
-
}
|
|
18231
|
-
return value;
|
|
18232
|
-
}
|
|
18233
18703
|
var SddParallelRun = class {
|
|
18234
18704
|
constructor(opts) {
|
|
18235
18705
|
this.opts = opts;
|
|
@@ -18378,8 +18848,8 @@ var SddParallelRun = class {
|
|
|
18378
18848
|
const successCount = results.filter((r) => r.status === "success").length;
|
|
18379
18849
|
const failCount = results.length - successCount;
|
|
18380
18850
|
for (let i = 0; i < results.length; i++) {
|
|
18381
|
-
const result =
|
|
18382
|
-
const taskId =
|
|
18851
|
+
const result = expectDefined(results[i]);
|
|
18852
|
+
const taskId = expectDefined(taskIds[i]);
|
|
18383
18853
|
if (result.status === "success") {
|
|
18384
18854
|
this.opts.tracker.updateNodeStatus(taskId, "completed");
|
|
18385
18855
|
} else {
|