@wrongstack/core 0.260.0 → 0.265.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/{agent-bridge-BbskZ7HH.d.ts → agent-bridge-DrkBxszZ.d.ts} +1 -1
- package/dist/{agent-subagent-runner-BNIGZx18.d.ts → agent-subagent-runner-DM2pP-B6.d.ts} +116 -12
- package/dist/{brain-C2yDd7Lw.d.ts → brain-BXd_61kQ.d.ts} +32 -3
- package/dist/{compactor-t0R_AIt_.d.ts → compactor-B8pOf45Y.d.ts} +1 -1
- package/dist/{config-FG6As4H5.d.ts → config-BMCj_XDs.d.ts} +86 -12
- package/dist/{context-JFOVvu6z.d.ts → context-MRk5PhNv.d.ts} +26 -1
- package/dist/coordination/index.d.ts +1737 -15
- package/dist/coordination/index.js +3152 -494
- package/dist/coordination/index.js.map +1 -1
- package/dist/{default-config-CXsDvOmP.d.ts → default-config-B0cj-Hry.d.ts} +11 -1
- package/dist/defaults/index.d.ts +28 -28
- package/dist/defaults/index.js +1804 -1363
- package/dist/defaults/index.js.map +1 -1
- package/dist/dispatcher-types.d-BBeXBQgS.d.ts +66 -0
- package/dist/execution/index.d.ts +16 -16
- package/dist/execution/index.js +933 -672
- package/dist/execution/index.js.map +1 -1
- package/dist/execution/prompt-enhancer.d.ts +1 -1
- package/dist/execution/prompt-enhancer.js +7 -1
- package/dist/execution/prompt-enhancer.js.map +1 -1
- package/dist/extension/index.d.ts +6 -6
- package/dist/extension/index.js.map +1 -1
- package/dist/{goal-preamble-B1IXJtLX.d.ts → goal-preamble-DvHDSKSe.d.ts} +26 -10
- package/dist/{goal-store-CPXz6Mml.d.ts → goal-store-DtLMySNb.d.ts} +1 -1
- package/dist/{index-CebbJB94.d.ts → index-B-ch8K9C.d.ts} +8 -8
- package/dist/{index-BPcg4N3M.d.ts → index-CEDeNodM.d.ts} +5 -5
- package/dist/index.d.ts +189 -104
- package/dist/index.js +24693 -21162
- package/dist/index.js.map +1 -1
- package/dist/infrastructure/index.d.ts +6 -6
- package/dist/infrastructure/index.js +12 -8
- package/dist/infrastructure/index.js.map +1 -1
- package/dist/kernel/index.d.ts +9 -9
- package/dist/kernel/index.js +7 -2
- package/dist/kernel/index.js.map +1 -1
- package/dist/{llm-selector-DXxI2tlu.d.ts → llm-selector-C0tfTCUe.d.ts} +14 -2
- package/dist/{mcp-servers-OwNHo43-.d.ts → mcp-servers-2x4w6Jn9.d.ts} +3 -3
- package/dist/models/index.d.ts +5 -5
- package/dist/models/index.js +80 -31
- package/dist/models/index.js.map +1 -1
- package/dist/{models-registry-Djlmq4uB.d.ts → models-registry-DmJlKuNp.d.ts} +1 -1
- package/dist/{multi-agent-coordinator-CEmrSCMJ.d.ts → multi-agent-coordinator-DyCkCZnU.d.ts} +2 -2
- package/dist/{null-fleet-bus-DT92xqgJ.d.ts → null-fleet-bus-CG9QY2aP.d.ts} +6 -6
- package/dist/observability/index.d.ts +2 -2
- package/dist/observability/index.js +8 -3
- package/dist/observability/index.js.map +1 -1
- package/dist/{parallel-eternal-engine-0SItuq5r.d.ts → parallel-eternal-engine-Jw9uhEoT.d.ts} +9 -9
- package/dist/{path-resolver-DKBh6Jlo.d.ts → path-resolver-Dy2ej-gE.d.ts} +3 -3
- package/dist/{permission-BJ7eO9Vl.d.ts → permission-B9SB45lp.d.ts} +1 -1
- package/dist/{permission-policy-DEXOfnpm.d.ts → permission-policy-CkjSXabK.d.ts} +2 -2
- package/dist/{pipeline-zflkI2dp.d.ts → pipeline-DPDxH_7m.d.ts} +59 -4
- package/dist/{plan-templates-BFXyRkEK.d.ts → plan-templates-CzD9GnAU.d.ts} +32 -8
- package/dist/{provider-runner-BC-uywtT.d.ts → provider-runner-DMa70ODu.d.ts} +3 -3
- package/dist/{retry-policy-Cavrzmtk.d.ts → retry-policy-CN0khdlj.d.ts} +1 -1
- package/dist/sdd/index.d.ts +8 -8
- package/dist/sdd/index.js +313 -122
- package/dist/sdd/index.js.map +1 -1
- package/dist/{secret-vault-CDvDYXWX.d.ts → secret-vault-B2yw84VT.d.ts} +43 -4
- package/dist/secret-vault-BAKpgFw_.d.ts +57 -0
- package/dist/security/index.d.ts +5 -5
- package/dist/security/index.js +411 -225
- package/dist/security/index.js.map +1 -1
- package/dist/{selector-B7AivHsu.d.ts → selector-CzHh_igB.d.ts} +1 -1
- package/dist/{session-event-bridge-BmIDxdJd.d.ts → session-event-bridge-BUI6Jf-4.d.ts} +8 -2
- package/dist/{session-reader-DtofsB-2.d.ts → session-reader-CMgdMSRP.d.ts} +1 -1
- package/dist/skills/index.js +67 -64
- package/dist/skills/index.js.map +1 -1
- package/dist/storage/index.d.ts +132 -16
- package/dist/storage/index.js +851 -432
- package/dist/storage/index.js.map +1 -1
- package/dist/tools/index.d.ts +57 -0
- package/dist/tools/index.js +411 -0
- package/dist/tools/index.js.map +1 -0
- package/dist/types/index.d.ts +21 -21
- package/dist/types/index.js +928 -711
- package/dist/types/index.js.map +1 -1
- package/dist/utils/error.d.ts +7 -0
- package/dist/utils/error.js +8 -0
- package/dist/utils/error.js.map +1 -0
- package/dist/utils/index.d.ts +8 -68
- package/dist/utils/index.js +20 -10
- package/dist/utils/index.js.map +1 -1
- package/dist/{wstack-paths-CJjEwPXn.d.ts → wstack-paths-hOpNLmvf.d.ts} +2 -0
- package/package.json +5 -1
- package/skills/api-design/SKILL.md +1 -1
- package/skills/audit-log/SKILL.md +6 -6
- package/skills/bug-hunter/SKILL.md +5 -5
- package/skills/chimera/SKILL.md +4 -4
- package/skills/docker-deploy/SKILL.md +1 -1
- package/skills/git-flow/SKILL.md +3 -3
- package/skills/multi-agent/SKILL.md +3 -3
- package/skills/node-modern/SKILL.md +1 -0
- package/skills/observability/SKILL.md +2 -2
- package/skills/output-standards/SKILL.md +51 -28
- package/skills/refactor-planner/SKILL.md +3 -3
- package/skills/security-scanner/SKILL.md +4 -3
- package/skills/tech-stack/SKILL.md +1 -2
- package/dist/package-outdated-watcher-C70ag2G9.d.ts +0 -581
- package/dist/secret-vault-BJDY28ev.d.ts +0 -25
package/dist/storage/index.js
CHANGED
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
import { randomBytes, randomUUID, createHash } from 'crypto';
|
|
2
2
|
import * as fsp from 'fs/promises';
|
|
3
|
-
import * as
|
|
4
|
-
import 'fs';
|
|
3
|
+
import * as path2 from 'path';
|
|
5
4
|
import * as os from 'os';
|
|
6
5
|
import { hostname } from 'os';
|
|
6
|
+
import 'fs';
|
|
7
7
|
|
|
8
8
|
// src/utils/expect-defined.ts
|
|
9
9
|
function expectDefined(value, label) {
|
|
@@ -15,9 +15,9 @@ function expectDefined(value, label) {
|
|
|
15
15
|
return value;
|
|
16
16
|
}
|
|
17
17
|
async function atomicWrite(targetPath, content, opts = {}) {
|
|
18
|
-
const dir =
|
|
18
|
+
const dir = path2.dirname(targetPath);
|
|
19
19
|
await fsp.mkdir(dir, { recursive: true });
|
|
20
|
-
const tmp =
|
|
20
|
+
const tmp = path2.join(dir, `.${path2.basename(targetPath)}.${randomBytes(6).toString("hex")}.tmp`);
|
|
21
21
|
try {
|
|
22
22
|
if (typeof content === "string") {
|
|
23
23
|
await fsp.writeFile(tmp, content, { flag: "wx", encoding: opts.encoding ?? "utf8" });
|
|
@@ -35,8 +35,8 @@ async function atomicWrite(targetPath, content, opts = {}) {
|
|
|
35
35
|
}
|
|
36
36
|
let mode;
|
|
37
37
|
try {
|
|
38
|
-
const
|
|
39
|
-
mode =
|
|
38
|
+
const stat7 = await fsp.stat(targetPath);
|
|
39
|
+
mode = stat7.mode & 511;
|
|
40
40
|
} catch {
|
|
41
41
|
mode = opts.mode;
|
|
42
42
|
}
|
|
@@ -56,9 +56,9 @@ async function ensureDir(dir) {
|
|
|
56
56
|
await fsp.mkdir(dir, { recursive: true });
|
|
57
57
|
}
|
|
58
58
|
async function withFileLock(targetPath, fn, opts = {}) {
|
|
59
|
-
const dir =
|
|
59
|
+
const dir = path2.dirname(targetPath);
|
|
60
60
|
await fsp.mkdir(dir, { recursive: true });
|
|
61
|
-
const lockPath =
|
|
61
|
+
const lockPath = path2.join(dir, `.${path2.basename(targetPath)}.lock`);
|
|
62
62
|
const timeoutMs = opts.timeoutMs ?? 5e3;
|
|
63
63
|
const staleMs = opts.staleMs ?? 3e4;
|
|
64
64
|
const started = Date.now();
|
|
@@ -69,10 +69,15 @@ async function withFileLock(targetPath, fn, opts = {}) {
|
|
|
69
69
|
await handle.writeFile(`${process.pid}:${Date.now()}`);
|
|
70
70
|
break;
|
|
71
71
|
} catch (err) {
|
|
72
|
-
|
|
72
|
+
const code = err.code;
|
|
73
|
+
if (code === "ENOENT") {
|
|
74
|
+
await fsp.mkdir(dir, { recursive: true });
|
|
75
|
+
continue;
|
|
76
|
+
}
|
|
77
|
+
if (code !== "EEXIST") throw err;
|
|
73
78
|
try {
|
|
74
|
-
const
|
|
75
|
-
if (Date.now() -
|
|
79
|
+
const stat7 = await fsp.stat(lockPath);
|
|
80
|
+
if (Date.now() - stat7.mtimeMs > staleMs) {
|
|
76
81
|
await fsp.unlink(lockPath);
|
|
77
82
|
continue;
|
|
78
83
|
}
|
|
@@ -82,7 +87,7 @@ async function withFileLock(targetPath, fn, opts = {}) {
|
|
|
82
87
|
if (Date.now() - started >= timeoutMs) {
|
|
83
88
|
throw new Error(`Timed out waiting for file lock: ${targetPath}`);
|
|
84
89
|
}
|
|
85
|
-
await new Promise((
|
|
90
|
+
await new Promise((resolve6) => setTimeout(resolve6, 25));
|
|
86
91
|
}
|
|
87
92
|
}
|
|
88
93
|
try {
|
|
@@ -116,7 +121,7 @@ async function renameWithRetry(from, to) {
|
|
|
116
121
|
if (!code || !TRANSIENT_RENAME_CODES.has(code) || i === delays.length) {
|
|
117
122
|
throw err;
|
|
118
123
|
}
|
|
119
|
-
await new Promise((
|
|
124
|
+
await new Promise((resolve6) => setTimeout(resolve6, delays[i]));
|
|
120
125
|
}
|
|
121
126
|
}
|
|
122
127
|
throw lastErr;
|
|
@@ -214,6 +219,216 @@ function isEmptyMessage(msg) {
|
|
|
214
219
|
return msg.content.length === 0;
|
|
215
220
|
}
|
|
216
221
|
|
|
222
|
+
// src/utils/error.ts
|
|
223
|
+
function toErrorMessage(err) {
|
|
224
|
+
return err instanceof Error ? err.message : String(err);
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
// src/utils/safe-json.ts
|
|
228
|
+
function safeParse(input, maxBytes = 5e6) {
|
|
229
|
+
if (input.length > maxBytes) {
|
|
230
|
+
return { ok: false, error: `Input exceeds limit (${maxBytes} bytes)` };
|
|
231
|
+
}
|
|
232
|
+
try {
|
|
233
|
+
return { ok: true, value: JSON.parse(input) };
|
|
234
|
+
} catch (err) {
|
|
235
|
+
return {
|
|
236
|
+
ok: false,
|
|
237
|
+
error: toErrorMessage(err)
|
|
238
|
+
};
|
|
239
|
+
}
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
// src/utils/term.ts
|
|
243
|
+
var hasStdout = () => typeof process !== "undefined" && !!process.stdout;
|
|
244
|
+
function isStdoutTTY() {
|
|
245
|
+
return hasStdout() && Boolean(process.stdout.isTTY);
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
// src/utils/color.ts
|
|
249
|
+
var isColorTty = () => {
|
|
250
|
+
if (envFlag(process.env.NO_COLOR)) return false;
|
|
251
|
+
if (envFlag(process.env.FORCE_COLOR)) return true;
|
|
252
|
+
return isStdoutTTY();
|
|
253
|
+
};
|
|
254
|
+
function envFlag(value) {
|
|
255
|
+
if (value === void 0) return false;
|
|
256
|
+
if (value.trim() === "") return false;
|
|
257
|
+
return !/^(0|false|no|off)$/i.test(value.trim());
|
|
258
|
+
}
|
|
259
|
+
var COLOR = isColorTty();
|
|
260
|
+
var wrap = (open6, close) => (s) => COLOR ? `\x1B[${open6}m${s}\x1B[${close}m` : s;
|
|
261
|
+
var color = {
|
|
262
|
+
reset: wrap("0", "0"),
|
|
263
|
+
bold: wrap("1", "22"),
|
|
264
|
+
dim: wrap("2", "22"),
|
|
265
|
+
italic: wrap("3", "23"),
|
|
266
|
+
underline: wrap("4", "24"),
|
|
267
|
+
red: wrap("31", "39"),
|
|
268
|
+
green: wrap("32", "39"),
|
|
269
|
+
yellow: wrap("33", "39"),
|
|
270
|
+
blue: wrap("34", "39"),
|
|
271
|
+
magenta: wrap("35", "39"),
|
|
272
|
+
cyan: wrap("36", "39"),
|
|
273
|
+
gray: wrap("90", "39"),
|
|
274
|
+
amber: wrap("38;5;214", "39"),
|
|
275
|
+
pink: wrap("38;5;205", "39"),
|
|
276
|
+
bgRed: wrap("41", "49"),
|
|
277
|
+
bgGreen: wrap("42", "49")
|
|
278
|
+
};
|
|
279
|
+
function projectHash(absRoot) {
|
|
280
|
+
return createHash("sha256").update(path2.resolve(absRoot)).digest("hex").slice(0, 12);
|
|
281
|
+
}
|
|
282
|
+
function projectSlug(absRoot) {
|
|
283
|
+
const base = slugify(path2.basename(absRoot));
|
|
284
|
+
const hash = createHash("sha256").update(path2.resolve(absRoot)).digest("hex").slice(0, 6);
|
|
285
|
+
return `${base}-${hash}`;
|
|
286
|
+
}
|
|
287
|
+
function slugify(name) {
|
|
288
|
+
return name.toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-+|-+$/g, "").slice(0, 40) || "project";
|
|
289
|
+
}
|
|
290
|
+
function wstackGlobalRoot() {
|
|
291
|
+
const fromEnv = process.env["WRONGSTACK_HOME"];
|
|
292
|
+
if (fromEnv && fromEnv.trim().length > 0) return path2.resolve(fromEnv);
|
|
293
|
+
return path2.join(os.homedir(), ".wrongstack");
|
|
294
|
+
}
|
|
295
|
+
function resolveWstackPaths(opts) {
|
|
296
|
+
const globalRoot = opts.globalRoot ?? (opts.userHome ? path2.join(opts.userHome, ".wrongstack") : wstackGlobalRoot());
|
|
297
|
+
const hash = projectHash(opts.projectRoot);
|
|
298
|
+
const slug = projectSlug(opts.projectRoot);
|
|
299
|
+
const projectDir = path2.join(globalRoot, "projects", slug);
|
|
300
|
+
return {
|
|
301
|
+
globalRoot,
|
|
302
|
+
configDir: globalRoot,
|
|
303
|
+
globalConfig: path2.join(globalRoot, "config.json"),
|
|
304
|
+
secretsKey: path2.join(globalRoot, ".key"),
|
|
305
|
+
globalMemory: path2.join(globalRoot, "memory.md"),
|
|
306
|
+
globalSkills: path2.join(globalRoot, "skills"),
|
|
307
|
+
globalPrompts: path2.join(globalRoot, "prompts"),
|
|
308
|
+
cacheDir: path2.join(globalRoot, "cache"),
|
|
309
|
+
modelsCache: path2.join(globalRoot, "cache", "models.dev.json"),
|
|
310
|
+
modelsOverlayCache: path2.join(globalRoot, "cache", "models-overlay.json"),
|
|
311
|
+
historyFile: path2.join(globalRoot, "history"),
|
|
312
|
+
logFile: path2.join(globalRoot, "logs", "wrongstack.log"),
|
|
313
|
+
projectDir,
|
|
314
|
+
projectCodebaseIndex: path2.join(projectDir, "codebase-index"),
|
|
315
|
+
projectMemory: path2.join(projectDir, "memory.md"),
|
|
316
|
+
projectSessions: path2.join(projectDir, "sessions"),
|
|
317
|
+
projectTrust: path2.join(projectDir, "trust.json"),
|
|
318
|
+
projectMeta: path2.join(projectDir, "meta.json"),
|
|
319
|
+
projectLocalConfig: path2.join(projectDir, "config.local.json"),
|
|
320
|
+
inProjectConfig: path2.join(opts.projectRoot, ".wrongstack", "config.json"),
|
|
321
|
+
inProjectAgentsFile: path2.join(opts.projectRoot, ".wrongstack", "AGENTS.md"),
|
|
322
|
+
inProjectSkills: path2.join(opts.projectRoot, ".wrongstack", "skills"),
|
|
323
|
+
inProjectWorktrees: path2.join(opts.projectRoot, ".wrongstack", "worktrees"),
|
|
324
|
+
projectHash: hash,
|
|
325
|
+
projectSlug: slug,
|
|
326
|
+
projectGoal: path2.join(projectDir, "goal.json"),
|
|
327
|
+
projectSpecs: path2.join(projectDir, "specs"),
|
|
328
|
+
projectTaskGraphs: path2.join(projectDir, "task-graphs"),
|
|
329
|
+
projectSddSession: path2.join(projectDir, "sdd-session.json"),
|
|
330
|
+
projectPlan: path2.join(projectDir, "plan.json"),
|
|
331
|
+
projectAutophase: path2.join(projectDir, "autophase"),
|
|
332
|
+
syncConfig: path2.join(globalRoot, "sync.json"),
|
|
333
|
+
projectStatus: (projectHash2) => path2.join(globalRoot, "projects", projectHash2, "status.json")
|
|
334
|
+
};
|
|
335
|
+
}
|
|
336
|
+
|
|
337
|
+
// src/utils/deep-merge.ts
|
|
338
|
+
var FORBIDDEN_PROTO_KEYS = /* @__PURE__ */ new Set([
|
|
339
|
+
"__proto__",
|
|
340
|
+
"constructor",
|
|
341
|
+
"prototype",
|
|
342
|
+
"__defineGetter__",
|
|
343
|
+
"__defineSetter__",
|
|
344
|
+
"__lookupGetter__",
|
|
345
|
+
"__lookupSetter__"
|
|
346
|
+
]);
|
|
347
|
+
function isPrimitiveArray(a) {
|
|
348
|
+
return a.every((v) => v === null || typeof v !== "object" && typeof v !== "function");
|
|
349
|
+
}
|
|
350
|
+
function deepMerge(base, patch, options = {}) {
|
|
351
|
+
const {
|
|
352
|
+
conflictResolution = "prefer-patch",
|
|
353
|
+
arrayMode = "replace",
|
|
354
|
+
protectProto = true,
|
|
355
|
+
onNonPrimitiveArrayReplace
|
|
356
|
+
} = options;
|
|
357
|
+
if (typeof base !== "object" || base === null) {
|
|
358
|
+
return conflictResolution === "prefer-patch" ? patch : base;
|
|
359
|
+
}
|
|
360
|
+
if (typeof patch !== "object" || patch === null) {
|
|
361
|
+
return conflictResolution === "prefer-patch" ? patch : base;
|
|
362
|
+
}
|
|
363
|
+
if (Array.isArray(base) && Array.isArray(patch)) {
|
|
364
|
+
if (arrayMode === "concat-primitives" && isPrimitiveArray(base) && isPrimitiveArray(patch)) {
|
|
365
|
+
return [.../* @__PURE__ */ new Set([...base, ...patch])];
|
|
366
|
+
}
|
|
367
|
+
return conflictResolution === "prefer-patch" ? patch : base;
|
|
368
|
+
}
|
|
369
|
+
if (Array.isArray(base) || Array.isArray(patch)) {
|
|
370
|
+
return conflictResolution === "prefer-patch" ? patch : base;
|
|
371
|
+
}
|
|
372
|
+
const baseObj = base;
|
|
373
|
+
const patchObj = patch;
|
|
374
|
+
const out = { ...baseObj };
|
|
375
|
+
for (const [k, v] of Object.entries(patchObj)) {
|
|
376
|
+
if (protectProto && FORBIDDEN_PROTO_KEYS.has(k)) continue;
|
|
377
|
+
const existing = out[k];
|
|
378
|
+
if (v !== null && typeof v === "object" && !Array.isArray(v) && existing !== null && typeof existing === "object" && !Array.isArray(existing)) {
|
|
379
|
+
out[k] = deepMerge(existing, v, options);
|
|
380
|
+
} else if (Array.isArray(v) && Array.isArray(existing)) {
|
|
381
|
+
if (onNonPrimitiveArrayReplace && !isPrimitiveArray(v)) {
|
|
382
|
+
onNonPrimitiveArrayReplace(k, existing.length, v.length);
|
|
383
|
+
}
|
|
384
|
+
out[k] = deepMerge(existing, v, options);
|
|
385
|
+
} else if (v !== void 0) {
|
|
386
|
+
if (onNonPrimitiveArrayReplace && Array.isArray(v) && !isPrimitiveArray(v)) {
|
|
387
|
+
const existingLen = Array.isArray(existing) ? existing.length : 0;
|
|
388
|
+
onNonPrimitiveArrayReplace(k, existingLen, v.length);
|
|
389
|
+
}
|
|
390
|
+
out[k] = v;
|
|
391
|
+
}
|
|
392
|
+
}
|
|
393
|
+
return out;
|
|
394
|
+
}
|
|
395
|
+
|
|
396
|
+
// src/utils/regex-guard.ts
|
|
397
|
+
var MAX_PATTERN_LEN = 512;
|
|
398
|
+
var DANGEROUS_PATTERNS = [
|
|
399
|
+
/(\([^)]*[+*][^)]*\))[+*]/,
|
|
400
|
+
// (a+)+, (.*)+, etc
|
|
401
|
+
/(\(\?:[^)]*[+*][^)]*\))[+*]/
|
|
402
|
+
// same, with non-capturing group
|
|
403
|
+
];
|
|
404
|
+
function compileUserRegex(pattern, flags) {
|
|
405
|
+
if (typeof pattern !== "string") {
|
|
406
|
+
return { ok: false, reason: "pattern must be a string" };
|
|
407
|
+
}
|
|
408
|
+
if (pattern.length === 0) {
|
|
409
|
+
return { ok: false, reason: "pattern is empty" };
|
|
410
|
+
}
|
|
411
|
+
if (pattern.length > MAX_PATTERN_LEN) {
|
|
412
|
+
return { ok: false, reason: `pattern exceeds ${MAX_PATTERN_LEN} characters` };
|
|
413
|
+
}
|
|
414
|
+
for (const rx of DANGEROUS_PATTERNS) {
|
|
415
|
+
if (rx.test(pattern)) {
|
|
416
|
+
return {
|
|
417
|
+
ok: false,
|
|
418
|
+
reason: "pattern looks vulnerable to catastrophic backtracking \u2014 rewrite without nested quantifiers"
|
|
419
|
+
};
|
|
420
|
+
}
|
|
421
|
+
}
|
|
422
|
+
try {
|
|
423
|
+
return { ok: true, regex: new RegExp(pattern, flags) };
|
|
424
|
+
} catch (err) {
|
|
425
|
+
return {
|
|
426
|
+
ok: false,
|
|
427
|
+
reason: err instanceof Error ? err.message : "invalid regex"
|
|
428
|
+
};
|
|
429
|
+
}
|
|
430
|
+
}
|
|
431
|
+
|
|
217
432
|
// src/storage/session-store.ts
|
|
218
433
|
function sanitizeModel(model) {
|
|
219
434
|
return model.replace(/[^a-zA-Z0-9_-]/g, "-").replace(/-+/g, "-").replace(/^-|-$/g, "").slice(0, 40);
|
|
@@ -229,11 +444,34 @@ var DefaultSessionStore = class _DefaultSessionStore {
|
|
|
229
444
|
dir;
|
|
230
445
|
events;
|
|
231
446
|
secretScrubber;
|
|
447
|
+
/**
|
|
448
|
+
* In-memory cache for load() results, keyed by session ID. The cache is
|
|
449
|
+
* invalidated when the file's mtimeMs or size changes (indicating the
|
|
450
|
+
* file was written to). This eliminates redundant full-file reads and
|
|
451
|
+
* JSON parses when the same session is loaded multiple times within the
|
|
452
|
+
* store's lifetime (e.g., webui session detail views, list() fallbacks).
|
|
453
|
+
*
|
|
454
|
+
* Max size is capped to prevent unbounded memory growth in long-running
|
|
455
|
+
* processes. When the limit is reached, the oldest entry is evicted.
|
|
456
|
+
*/
|
|
457
|
+
_loadCache = /* @__PURE__ */ new Map();
|
|
458
|
+
static LOAD_CACHE_MAX_ENTRIES = 50;
|
|
232
459
|
constructor(opts) {
|
|
233
460
|
this.dir = opts.dir;
|
|
234
461
|
this.events = opts.events;
|
|
235
462
|
this.secretScrubber = opts.secretScrubber;
|
|
236
463
|
}
|
|
464
|
+
/**
|
|
465
|
+
* Clear the load() cache. Useful for testing or when the caller knows
|
|
466
|
+
* the file has changed externally (e.g., another process wrote to it).
|
|
467
|
+
*/
|
|
468
|
+
clearLoadCache(sessionId) {
|
|
469
|
+
if (sessionId !== void 0) {
|
|
470
|
+
this._loadCache.delete(sessionId);
|
|
471
|
+
} else {
|
|
472
|
+
this._loadCache.clear();
|
|
473
|
+
}
|
|
474
|
+
}
|
|
237
475
|
// ── Storage event helpers ───────────────────────────────────────────────────
|
|
238
476
|
emitRead(sessionId, filePath, operation, outcome, durationMs, error) {
|
|
239
477
|
this.events?.emit("storage.read", {
|
|
@@ -270,11 +508,11 @@ var DefaultSessionStore = class _DefaultSessionStore {
|
|
|
270
508
|
}
|
|
271
509
|
/** Absolute path to the session index file. */
|
|
272
510
|
get indexFile() {
|
|
273
|
-
return
|
|
511
|
+
return path2.join(this.dir, "_index.jsonl");
|
|
274
512
|
}
|
|
275
513
|
/** Join session ID to its absolute path within the store directory. */
|
|
276
514
|
sessionPath(id, ext) {
|
|
277
|
-
return
|
|
515
|
+
return path2.join(this.dir, `${id}${ext}`);
|
|
278
516
|
}
|
|
279
517
|
/**
|
|
280
518
|
* Ensure the directory implied by the session ID exists. When the ID
|
|
@@ -282,7 +520,7 @@ var DefaultSessionStore = class _DefaultSessionStore {
|
|
|
282
520
|
* subdirectory so sessions group naturally by day.
|
|
283
521
|
*/
|
|
284
522
|
async ensureShardDir(id) {
|
|
285
|
-
const dirPath =
|
|
523
|
+
const dirPath = path2.dirname(path2.join(this.dir, id));
|
|
286
524
|
await ensureDir(dirPath);
|
|
287
525
|
return dirPath;
|
|
288
526
|
}
|
|
@@ -290,15 +528,15 @@ var DefaultSessionStore = class _DefaultSessionStore {
|
|
|
290
528
|
const startedAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
291
529
|
const id = meta.id && meta.id.length > 0 ? meta.id : generateSessionId(startedAt, meta.model ?? meta.provider);
|
|
292
530
|
const shardDir = await this.ensureShardDir(id);
|
|
293
|
-
const file =
|
|
531
|
+
const file = path2.join(shardDir, `${path2.basename(id)}.jsonl`);
|
|
294
532
|
const t0 = Date.now();
|
|
295
533
|
let handle;
|
|
296
534
|
try {
|
|
297
535
|
handle = await fsp.open(file, "a", 384);
|
|
298
536
|
} catch (err) {
|
|
299
|
-
this.emitError(id, file, "create",
|
|
537
|
+
this.emitError(id, file, "create", toErrorMessage(err), false);
|
|
300
538
|
throw new Error(
|
|
301
|
-
`Failed to open session file: ${
|
|
539
|
+
`Failed to open session file: ${toErrorMessage(err)}`,
|
|
302
540
|
{ cause: err }
|
|
303
541
|
);
|
|
304
542
|
}
|
|
@@ -318,7 +556,7 @@ var DefaultSessionStore = class _DefaultSessionStore {
|
|
|
318
556
|
message: e instanceof Error ? e.message : String(e),
|
|
319
557
|
timestamp: (/* @__PURE__ */ new Date()).toISOString()
|
|
320
558
|
})));
|
|
321
|
-
this.emitError(id, file, "create",
|
|
559
|
+
this.emitError(id, file, "create", toErrorMessage(err), true);
|
|
322
560
|
throw err;
|
|
323
561
|
}
|
|
324
562
|
}
|
|
@@ -330,9 +568,9 @@ var DefaultSessionStore = class _DefaultSessionStore {
|
|
|
330
568
|
try {
|
|
331
569
|
handle = await fsp.open(file, "a", 384);
|
|
332
570
|
} catch (err) {
|
|
333
|
-
this.emitError(id, file, "resume",
|
|
571
|
+
this.emitError(id, file, "resume", toErrorMessage(err), false);
|
|
334
572
|
throw new Error(
|
|
335
|
-
`Failed to open session "${id}" for append: ${
|
|
573
|
+
`Failed to open session "${id}" for append: ${toErrorMessage(err)}`,
|
|
336
574
|
{ cause: err }
|
|
337
575
|
);
|
|
338
576
|
}
|
|
@@ -352,7 +590,7 @@ var DefaultSessionStore = class _DefaultSessionStore {
|
|
|
352
590
|
// Shard directory (sessions/<date>/) — must match create() so the
|
|
353
591
|
// .summary.json sidecar lands next to the JSONL instead of the
|
|
354
592
|
// sessions root (where summaryFor() would never find it).
|
|
355
|
-
dir:
|
|
593
|
+
dir: path2.dirname(file),
|
|
356
594
|
filePath: file,
|
|
357
595
|
secretScrubber: this.secretScrubber,
|
|
358
596
|
onClose: (s) => this.appendToIndex(s)
|
|
@@ -367,7 +605,7 @@ var DefaultSessionStore = class _DefaultSessionStore {
|
|
|
367
605
|
message: e instanceof Error ? e.message : String(e),
|
|
368
606
|
timestamp: (/* @__PURE__ */ new Date()).toISOString()
|
|
369
607
|
})));
|
|
370
|
-
this.emitError(id, file, "resume",
|
|
608
|
+
this.emitError(id, file, "resume", toErrorMessage(err), true);
|
|
371
609
|
throw err;
|
|
372
610
|
}
|
|
373
611
|
}
|
|
@@ -376,7 +614,20 @@ var DefaultSessionStore = class _DefaultSessionStore {
|
|
|
376
614
|
const t0 = Date.now();
|
|
377
615
|
let outcome = "success";
|
|
378
616
|
let errorMsg;
|
|
617
|
+
let cacheHit = false;
|
|
379
618
|
try {
|
|
619
|
+
let stat7;
|
|
620
|
+
try {
|
|
621
|
+
const s = await fsp.stat(file);
|
|
622
|
+
stat7 = { mtimeMs: s.mtimeMs, size: s.size };
|
|
623
|
+
} catch (err) {
|
|
624
|
+
throw err;
|
|
625
|
+
}
|
|
626
|
+
const cached = this._loadCache.get(id);
|
|
627
|
+
if (cached && cached.mtimeMs === stat7.mtimeMs && cached.size === stat7.size) {
|
|
628
|
+
cacheHit = true;
|
|
629
|
+
return cached.data;
|
|
630
|
+
}
|
|
380
631
|
const raw = await fsp.readFile(file, "utf8");
|
|
381
632
|
const lines = raw.split("\n").filter((l) => l.trim());
|
|
382
633
|
const events = [];
|
|
@@ -392,13 +643,30 @@ var DefaultSessionStore = class _DefaultSessionStore {
|
|
|
392
643
|
const meta = this.metaFromEvents(id, events);
|
|
393
644
|
const { messages, usage } = this.replay(events, id);
|
|
394
645
|
const toolCallEnds = extractToolCallEnds(events);
|
|
395
|
-
|
|
646
|
+
const data = { metadata: meta, events, messages, usage, toolCallEnds };
|
|
647
|
+
if (this._loadCache.size >= _DefaultSessionStore.LOAD_CACHE_MAX_ENTRIES) {
|
|
648
|
+
const oldest = this._loadCache.keys().next().value;
|
|
649
|
+
if (oldest !== void 0) {
|
|
650
|
+
this._loadCache.delete(oldest);
|
|
651
|
+
}
|
|
652
|
+
}
|
|
653
|
+
this._loadCache.set(id, { mtimeMs: stat7.mtimeMs, size: stat7.size, data });
|
|
654
|
+
return data;
|
|
396
655
|
} catch (err) {
|
|
397
656
|
outcome = "failure";
|
|
398
|
-
errorMsg =
|
|
657
|
+
errorMsg = toErrorMessage(err);
|
|
399
658
|
throw err;
|
|
400
659
|
} finally {
|
|
401
660
|
this.emitRead(id, file, "load", outcome, Date.now() - t0, errorMsg);
|
|
661
|
+
if (cacheHit) {
|
|
662
|
+
this.events?.emit("storage.cache_hit", {
|
|
663
|
+
sessionId: id,
|
|
664
|
+
store: "session",
|
|
665
|
+
filePath: file,
|
|
666
|
+
operation: "load",
|
|
667
|
+
durationMs: Date.now() - t0
|
|
668
|
+
});
|
|
669
|
+
}
|
|
402
670
|
}
|
|
403
671
|
}
|
|
404
672
|
async list(limit = 20) {
|
|
@@ -478,7 +746,7 @@ var DefaultSessionStore = class _DefaultSessionStore {
|
|
|
478
746
|
await fsp.rename(tmp, this.indexFile);
|
|
479
747
|
} catch (err) {
|
|
480
748
|
outcome = "failure";
|
|
481
|
-
errorMsg =
|
|
749
|
+
errorMsg = toErrorMessage(err);
|
|
482
750
|
} finally {
|
|
483
751
|
this.emitWrite("~compact~", this.indexFile, "compact", outcome, Date.now() - t0, void 0, errorMsg);
|
|
484
752
|
}
|
|
@@ -546,7 +814,7 @@ var DefaultSessionStore = class _DefaultSessionStore {
|
|
|
546
814
|
continue;
|
|
547
815
|
if (entry.isDirectory()) {
|
|
548
816
|
const childPrefix = depth === 0 ? entry.name : `${prefix}/${entry.name}`;
|
|
549
|
-
ids.push(...await this.collectSessionIds(
|
|
817
|
+
ids.push(...await this.collectSessionIds(path2.join(dir, entry.name), childPrefix, depth + 1));
|
|
550
818
|
} else if (entry.isFile() && entry.name.endsWith(".jsonl")) {
|
|
551
819
|
if (entry.name === "_index.jsonl") continue;
|
|
552
820
|
const base = entry.name.replace(/\.jsonl$/, "");
|
|
@@ -566,10 +834,10 @@ var DefaultSessionStore = class _DefaultSessionStore {
|
|
|
566
834
|
return JSON.parse(raw);
|
|
567
835
|
} catch {
|
|
568
836
|
const full = this.sessionPath(id, ".jsonl");
|
|
569
|
-
const
|
|
570
|
-
const summary = await this.summarize(id,
|
|
837
|
+
const stat7 = await fsp.stat(full);
|
|
838
|
+
const summary = await this.summarize(id, stat7.mtime.toISOString());
|
|
571
839
|
await atomicWrite(manifest, JSON.stringify(summary), { mode: 384 }).catch((err) => {
|
|
572
|
-
const msg =
|
|
840
|
+
const msg = toErrorMessage(err);
|
|
573
841
|
this.emitError(id, manifest, "summary_fallback", msg, true);
|
|
574
842
|
console.warn(JSON.stringify({
|
|
575
843
|
level: "warn",
|
|
@@ -597,14 +865,14 @@ var DefaultSessionStore = class _DefaultSessionStore {
|
|
|
597
865
|
async deleteSession(id) {
|
|
598
866
|
const jsonlPath = this.sessionPath(id, ".jsonl");
|
|
599
867
|
const summaryPath = this.sessionPath(id, ".summary.json");
|
|
600
|
-
const shardDir =
|
|
601
|
-
const base =
|
|
602
|
-
const sessDir =
|
|
868
|
+
const shardDir = path2.dirname(path2.join(this.dir, id));
|
|
869
|
+
const base = path2.basename(id);
|
|
870
|
+
const sessDir = path2.join(shardDir, base);
|
|
603
871
|
const deletions = [
|
|
604
872
|
fsp.unlink(jsonlPath),
|
|
605
873
|
fsp.unlink(summaryPath),
|
|
606
|
-
fsp.unlink(
|
|
607
|
-
fsp.unlink(
|
|
874
|
+
fsp.unlink(path2.join(shardDir, `${base}.plan.json`)),
|
|
875
|
+
fsp.unlink(path2.join(shardDir, `${base}.todos.json`))
|
|
608
876
|
];
|
|
609
877
|
const results = await Promise.allSettled(deletions);
|
|
610
878
|
for (const r of results) {
|
|
@@ -626,7 +894,7 @@ var DefaultSessionStore = class _DefaultSessionStore {
|
|
|
626
894
|
level: "warn",
|
|
627
895
|
event: "session_store.rmdir_failed",
|
|
628
896
|
sessionId: id,
|
|
629
|
-
message:
|
|
897
|
+
message: toErrorMessage(err),
|
|
630
898
|
timestamp: (/* @__PURE__ */ new Date()).toISOString()
|
|
631
899
|
}));
|
|
632
900
|
});
|
|
@@ -640,17 +908,17 @@ var DefaultSessionStore = class _DefaultSessionStore {
|
|
|
640
908
|
let deleted = 0;
|
|
641
909
|
let activeSessionId = null;
|
|
642
910
|
try {
|
|
643
|
-
const raw = await fsp.readFile(
|
|
911
|
+
const raw = await fsp.readFile(path2.join(this.dir, "active.json"), "utf8");
|
|
644
912
|
const active = JSON.parse(raw);
|
|
645
913
|
activeSessionId = active.sessionId ?? null;
|
|
646
914
|
} catch {
|
|
647
915
|
}
|
|
648
916
|
const isPrunableJsonl = (name) => name.endsWith(".jsonl") && name !== "_index.jsonl" && name !== "_mailbox.jsonl" && !name.endsWith(".replay.jsonl") && !name.endsWith(".audit.jsonl");
|
|
649
917
|
const pruneFile = async (dir, name, prefix) => {
|
|
650
|
-
const jsonlPath =
|
|
918
|
+
const jsonlPath = path2.join(dir, name);
|
|
651
919
|
try {
|
|
652
|
-
const
|
|
653
|
-
if (
|
|
920
|
+
const stat7 = await fsp.stat(jsonlPath);
|
|
921
|
+
if (stat7.mtimeMs >= cutoff) return;
|
|
654
922
|
} catch {
|
|
655
923
|
return;
|
|
656
924
|
}
|
|
@@ -667,7 +935,7 @@ var DefaultSessionStore = class _DefaultSessionStore {
|
|
|
667
935
|
continue;
|
|
668
936
|
}
|
|
669
937
|
if (!entry.isDirectory()) continue;
|
|
670
|
-
const dateDir =
|
|
938
|
+
const dateDir = path2.join(this.dir, entry.name);
|
|
671
939
|
const files = await fsp.readdir(dateDir, { withFileTypes: true }).catch(() => []);
|
|
672
940
|
for (const file of files) {
|
|
673
941
|
if (!file.isFile() || !isPrunableJsonl(file.name)) continue;
|
|
@@ -679,7 +947,7 @@ var DefaultSessionStore = class _DefaultSessionStore {
|
|
|
679
947
|
}
|
|
680
948
|
for (const entry of entries) {
|
|
681
949
|
if (!entry.isDirectory()) continue;
|
|
682
|
-
const dateDir =
|
|
950
|
+
const dateDir = path2.join(this.dir, entry.name);
|
|
683
951
|
try {
|
|
684
952
|
const remaining = await fsp.readdir(dateDir);
|
|
685
953
|
if (remaining.length === 0) {
|
|
@@ -854,7 +1122,7 @@ var FileSessionWriter = class _FileSessionWriter {
|
|
|
854
1122
|
this.meta = meta;
|
|
855
1123
|
this.events = events;
|
|
856
1124
|
this.resumed = opts.resumed ?? false;
|
|
857
|
-
this.manifestFile = opts.dir ?
|
|
1125
|
+
this.manifestFile = opts.dir ? path2.join(opts.dir, `${path2.basename(id)}.summary.json`) : "";
|
|
858
1126
|
this.filePath = opts.filePath ?? "";
|
|
859
1127
|
this.secretScrubber = opts.secretScrubber;
|
|
860
1128
|
this.onCloseCb = opts.onClose;
|
|
@@ -1061,7 +1329,7 @@ var FileSessionWriter = class _FileSessionWriter {
|
|
|
1061
1329
|
await this.enqueueWrite(batch);
|
|
1062
1330
|
} catch (err) {
|
|
1063
1331
|
outcome = "failure";
|
|
1064
|
-
errorMsg =
|
|
1332
|
+
errorMsg = toErrorMessage(err);
|
|
1065
1333
|
this.appendFailCount += eventCount;
|
|
1066
1334
|
const now = Date.now();
|
|
1067
1335
|
if (now - this.lastAppendWarnAt > 5e3) {
|
|
@@ -1069,7 +1337,7 @@ var FileSessionWriter = class _FileSessionWriter {
|
|
|
1069
1337
|
const tail = suppressed > 0 ? ` (+${suppressed} suppressed)` : "";
|
|
1070
1338
|
console.warn(
|
|
1071
1339
|
"[session] flush failed:",
|
|
1072
|
-
|
|
1340
|
+
toErrorMessage(err),
|
|
1073
1341
|
tail
|
|
1074
1342
|
);
|
|
1075
1343
|
this.lastAppendWarnAt = now;
|
|
@@ -1159,7 +1427,7 @@ var FileSessionWriter = class _FileSessionWriter {
|
|
|
1159
1427
|
await atomicWrite(this.manifestFile, JSON.stringify(this.summary), { mode: 384 });
|
|
1160
1428
|
} catch (err) {
|
|
1161
1429
|
outcome = "failure";
|
|
1162
|
-
errorMsg =
|
|
1430
|
+
errorMsg = toErrorMessage(err);
|
|
1163
1431
|
} finally {
|
|
1164
1432
|
this.events?.emit("storage.write", {
|
|
1165
1433
|
sessionId: this.id,
|
|
@@ -1180,7 +1448,7 @@ var FileSessionWriter = class _FileSessionWriter {
|
|
|
1180
1448
|
await this.onCloseCb?.(this.summary);
|
|
1181
1449
|
} catch (err) {
|
|
1182
1450
|
idxOutcome = "failure";
|
|
1183
|
-
idxError =
|
|
1451
|
+
idxError = toErrorMessage(err);
|
|
1184
1452
|
} finally {
|
|
1185
1453
|
this.events?.emit("storage.write", {
|
|
1186
1454
|
sessionId: this.summary.id,
|
|
@@ -1355,7 +1623,7 @@ var QueueStore = class {
|
|
|
1355
1623
|
events;
|
|
1356
1624
|
traceId;
|
|
1357
1625
|
constructor(opts) {
|
|
1358
|
-
this.file =
|
|
1626
|
+
this.file = path2.join(opts.dir, "queue.json");
|
|
1359
1627
|
this.events = opts.events;
|
|
1360
1628
|
this.traceId = opts.traceId;
|
|
1361
1629
|
}
|
|
@@ -1383,7 +1651,7 @@ var QueueStore = class {
|
|
|
1383
1651
|
filePath: this.file,
|
|
1384
1652
|
operation: "write",
|
|
1385
1653
|
outcome: "failure",
|
|
1386
|
-
error:
|
|
1654
|
+
error: toErrorMessage(err),
|
|
1387
1655
|
recoverable: false,
|
|
1388
1656
|
...this.traceId !== void 0 && { traceId: this.traceId }
|
|
1389
1657
|
});
|
|
@@ -1391,7 +1659,7 @@ var QueueStore = class {
|
|
|
1391
1659
|
level: "warn",
|
|
1392
1660
|
event: "queue_store.write_failed",
|
|
1393
1661
|
path: this.file,
|
|
1394
|
-
message:
|
|
1662
|
+
message: toErrorMessage(err),
|
|
1395
1663
|
timestamp: (/* @__PURE__ */ new Date()).toISOString()
|
|
1396
1664
|
}));
|
|
1397
1665
|
}
|
|
@@ -1421,7 +1689,7 @@ var QueueStore = class {
|
|
|
1421
1689
|
filePath: this.file,
|
|
1422
1690
|
operation: "read",
|
|
1423
1691
|
outcome: "failure",
|
|
1424
|
-
error:
|
|
1692
|
+
error: toErrorMessage(err),
|
|
1425
1693
|
recoverable: true,
|
|
1426
1694
|
...this.traceId !== void 0 && { traceId: this.traceId }
|
|
1427
1695
|
});
|
|
@@ -1429,7 +1697,7 @@ var QueueStore = class {
|
|
|
1429
1697
|
level: "warn",
|
|
1430
1698
|
event: "queue_store.read_failed",
|
|
1431
1699
|
path: this.file,
|
|
1432
|
-
message:
|
|
1700
|
+
message: toErrorMessage(err),
|
|
1433
1701
|
timestamp: (/* @__PURE__ */ new Date()).toISOString()
|
|
1434
1702
|
}));
|
|
1435
1703
|
return [];
|
|
@@ -1500,7 +1768,7 @@ var QueueStore = class {
|
|
|
1500
1768
|
filePath: this.file,
|
|
1501
1769
|
operation: "clear",
|
|
1502
1770
|
outcome: "failure",
|
|
1503
|
-
error:
|
|
1771
|
+
error: toErrorMessage(err),
|
|
1504
1772
|
recoverable: true,
|
|
1505
1773
|
...this.traceId !== void 0 && { traceId: this.traceId }
|
|
1506
1774
|
});
|
|
@@ -1539,7 +1807,7 @@ var DefaultAttachmentStore = class {
|
|
|
1539
1807
|
let data = input.data;
|
|
1540
1808
|
if (this.spoolDir && bytes >= this.spoolThreshold) {
|
|
1541
1809
|
await fsp.mkdir(this.spoolDir, { recursive: true });
|
|
1542
|
-
spooledPath =
|
|
1810
|
+
spooledPath = path2.join(this.spoolDir, `${id}.bin`);
|
|
1543
1811
|
await atomicWrite(spooledPath, input.data, {
|
|
1544
1812
|
encoding: input.kind === "image" ? "base64" : "utf8"
|
|
1545
1813
|
});
|
|
@@ -1750,7 +2018,7 @@ var FileMemoryBackend = class {
|
|
|
1750
2018
|
}
|
|
1751
2019
|
async remember(scope, entry, filePath) {
|
|
1752
2020
|
const file = this.resolveFile(filePath, scope);
|
|
1753
|
-
await ensureDir(
|
|
2021
|
+
await ensureDir(path2.dirname(file));
|
|
1754
2022
|
let existing = "";
|
|
1755
2023
|
try {
|
|
1756
2024
|
existing = await fsp.readFile(file, "utf8");
|
|
@@ -1978,7 +2246,7 @@ ${body.trim()}`);
|
|
|
1978
2246
|
operation: "readAll",
|
|
1979
2247
|
outcome: "failure",
|
|
1980
2248
|
durationMs: dur,
|
|
1981
|
-
error:
|
|
2249
|
+
error: toErrorMessage(err),
|
|
1982
2250
|
...this.traceId !== void 0 && { traceId: this.traceId }
|
|
1983
2251
|
});
|
|
1984
2252
|
throw err;
|
|
@@ -2011,7 +2279,7 @@ ${body.trim()}`);
|
|
|
2011
2279
|
operation: "read",
|
|
2012
2280
|
outcome: "failure",
|
|
2013
2281
|
durationMs: dur,
|
|
2014
|
-
error:
|
|
2282
|
+
error: toErrorMessage(err),
|
|
2015
2283
|
...this.traceId !== void 0 && { traceId: this.traceId }
|
|
2016
2284
|
});
|
|
2017
2285
|
throw err;
|
|
@@ -2064,7 +2332,7 @@ ${body.trim()}`);
|
|
|
2064
2332
|
operation: "remember",
|
|
2065
2333
|
outcome: "failure",
|
|
2066
2334
|
durationMs: dur,
|
|
2067
|
-
error:
|
|
2335
|
+
error: toErrorMessage(err),
|
|
2068
2336
|
...this.traceId !== void 0 && { traceId: this.traceId }
|
|
2069
2337
|
});
|
|
2070
2338
|
throw err;
|
|
@@ -2220,7 +2488,7 @@ ${body.trim()}`);
|
|
|
2220
2488
|
operation: "forget",
|
|
2221
2489
|
outcome: "failure",
|
|
2222
2490
|
durationMs: dur,
|
|
2223
|
-
error:
|
|
2491
|
+
error: toErrorMessage(err),
|
|
2224
2492
|
...this.traceId !== void 0 && { traceId: this.traceId }
|
|
2225
2493
|
});
|
|
2226
2494
|
throw err;
|
|
@@ -2262,7 +2530,7 @@ ${body.trim()}`);
|
|
|
2262
2530
|
operation: "consolidate",
|
|
2263
2531
|
outcome: "failure",
|
|
2264
2532
|
durationMs: dur,
|
|
2265
|
-
error:
|
|
2533
|
+
error: toErrorMessage(err),
|
|
2266
2534
|
...this.traceId !== void 0 && { traceId: this.traceId }
|
|
2267
2535
|
});
|
|
2268
2536
|
throw err;
|
|
@@ -2302,7 +2570,7 @@ ${body.trim()}`);
|
|
|
2302
2570
|
operation: "clear",
|
|
2303
2571
|
outcome: "failure",
|
|
2304
2572
|
durationMs: dur,
|
|
2305
|
-
error:
|
|
2573
|
+
error: toErrorMessage(err),
|
|
2306
2574
|
...this.traceId !== void 0 && { traceId: this.traceId }
|
|
2307
2575
|
});
|
|
2308
2576
|
throw err;
|
|
@@ -2338,7 +2606,7 @@ ${body.trim()}`);
|
|
|
2338
2606
|
operation: "clear",
|
|
2339
2607
|
outcome: "failure",
|
|
2340
2608
|
durationMs: dur,
|
|
2341
|
-
error:
|
|
2609
|
+
error: toErrorMessage(err),
|
|
2342
2610
|
...this.traceId !== void 0 && { traceId: this.traceId }
|
|
2343
2611
|
});
|
|
2344
2612
|
throw err;
|
|
@@ -2899,7 +3167,7 @@ var DefaultConfigStore = class {
|
|
|
2899
3167
|
console.error(JSON.stringify({
|
|
2900
3168
|
level: "error",
|
|
2901
3169
|
event: "config_store.watcher_threw",
|
|
2902
|
-
message:
|
|
3170
|
+
message: toErrorMessage(err),
|
|
2903
3171
|
timestamp: (/* @__PURE__ */ new Date()).toISOString()
|
|
2904
3172
|
}));
|
|
2905
3173
|
}
|
|
@@ -2923,67 +3191,9 @@ function deepFreeze(obj) {
|
|
|
2923
3191
|
}
|
|
2924
3192
|
return Object.freeze(obj);
|
|
2925
3193
|
}
|
|
2926
|
-
|
|
2927
|
-
|
|
2928
|
-
|
|
2929
|
-
"__proto__",
|
|
2930
|
-
"constructor",
|
|
2931
|
-
"prototype",
|
|
2932
|
-
"__defineGetter__",
|
|
2933
|
-
"__defineSetter__",
|
|
2934
|
-
"__lookupGetter__",
|
|
2935
|
-
"__lookupSetter__"
|
|
2936
|
-
]);
|
|
2937
|
-
function isPrimitiveArray(a) {
|
|
2938
|
-
return a.every((v) => v === null || typeof v !== "object" && typeof v !== "function");
|
|
2939
|
-
}
|
|
2940
|
-
function deepMerge(base, patch, options = {}) {
|
|
2941
|
-
const {
|
|
2942
|
-
conflictResolution = "prefer-patch",
|
|
2943
|
-
arrayMode = "replace",
|
|
2944
|
-
protectProto = true,
|
|
2945
|
-
onNonPrimitiveArrayReplace
|
|
2946
|
-
} = options;
|
|
2947
|
-
if (typeof base !== "object" || base === null) {
|
|
2948
|
-
return conflictResolution === "prefer-patch" ? patch : base;
|
|
2949
|
-
}
|
|
2950
|
-
if (typeof patch !== "object" || patch === null) {
|
|
2951
|
-
return conflictResolution === "prefer-patch" ? patch : base;
|
|
2952
|
-
}
|
|
2953
|
-
if (Array.isArray(base) && Array.isArray(patch)) {
|
|
2954
|
-
if (arrayMode === "concat-primitives" && isPrimitiveArray(base) && isPrimitiveArray(patch)) {
|
|
2955
|
-
return [.../* @__PURE__ */ new Set([...base, ...patch])];
|
|
2956
|
-
}
|
|
2957
|
-
return conflictResolution === "prefer-patch" ? patch : base;
|
|
2958
|
-
}
|
|
2959
|
-
if (Array.isArray(base) || Array.isArray(patch)) {
|
|
2960
|
-
return conflictResolution === "prefer-patch" ? patch : base;
|
|
2961
|
-
}
|
|
2962
|
-
const baseObj = base;
|
|
2963
|
-
const patchObj = patch;
|
|
2964
|
-
const out = { ...baseObj };
|
|
2965
|
-
for (const [k, v] of Object.entries(patchObj)) {
|
|
2966
|
-
if (protectProto && FORBIDDEN_PROTO_KEYS.has(k)) continue;
|
|
2967
|
-
const existing = out[k];
|
|
2968
|
-
if (v !== null && typeof v === "object" && !Array.isArray(v) && existing !== null && typeof existing === "object" && !Array.isArray(existing)) {
|
|
2969
|
-
out[k] = deepMerge(existing, v, options);
|
|
2970
|
-
} else if (Array.isArray(v) && Array.isArray(existing)) {
|
|
2971
|
-
if (onNonPrimitiveArrayReplace && !isPrimitiveArray(v)) {
|
|
2972
|
-
onNonPrimitiveArrayReplace(k, existing.length, v.length);
|
|
2973
|
-
}
|
|
2974
|
-
out[k] = deepMerge(existing, v, options);
|
|
2975
|
-
} else if (v !== void 0) {
|
|
2976
|
-
if (onNonPrimitiveArrayReplace && Array.isArray(v) && !isPrimitiveArray(v)) {
|
|
2977
|
-
const existingLen = Array.isArray(existing) ? existing.length : 0;
|
|
2978
|
-
onNonPrimitiveArrayReplace(k, existingLen, v.length);
|
|
2979
|
-
}
|
|
2980
|
-
out[k] = v;
|
|
2981
|
-
}
|
|
2982
|
-
}
|
|
2983
|
-
return out;
|
|
2984
|
-
}
|
|
2985
|
-
|
|
2986
|
-
// src/security/secret-vault.ts
|
|
3194
|
+
var KEY_BYTES = 32;
|
|
3195
|
+
var KEY_FILE_MAGIC = Buffer.from("WSKV", "ascii");
|
|
3196
|
+
KEY_FILE_MAGIC.length + 1 + KEY_BYTES;
|
|
2987
3197
|
function decryptConfigSecrets(cfg, vault, opts) {
|
|
2988
3198
|
const warn = ((msg) => console.warn(msg));
|
|
2989
3199
|
return walk(cfg, vault, (v, key) => {
|
|
@@ -3063,30 +3273,15 @@ var CONTEXT_WINDOW_MODES = Object.freeze([
|
|
|
3063
3273
|
thresholds: { warn: 0.55, soft: 0.7, hard: 0.84 },
|
|
3064
3274
|
aggressiveOn: "soft",
|
|
3065
3275
|
preserveK: 8,
|
|
3066
|
-
eliseThreshold: 1200,
|
|
3067
|
-
targetLoad: 0.58
|
|
3068
|
-
}
|
|
3069
|
-
]);
|
|
3070
|
-
function listContextWindowModes() {
|
|
3071
|
-
return CONTEXT_WINDOW_MODES.map((m) => ({ ...m, thresholds: { ...m.thresholds } }));
|
|
3072
|
-
}
|
|
3073
|
-
function isContextWindowModeId(id) {
|
|
3074
|
-
return CONTEXT_WINDOW_MODES.some((m) => m.id === id);
|
|
3075
|
-
}
|
|
3076
|
-
|
|
3077
|
-
// src/utils/safe-json.ts
|
|
3078
|
-
function safeParse(input, maxBytes = 5e6) {
|
|
3079
|
-
if (input.length > maxBytes) {
|
|
3080
|
-
return { ok: false, error: `Input exceeds limit (${maxBytes} bytes)` };
|
|
3081
|
-
}
|
|
3082
|
-
try {
|
|
3083
|
-
return { ok: true, value: JSON.parse(input) };
|
|
3084
|
-
} catch (err) {
|
|
3085
|
-
return {
|
|
3086
|
-
ok: false,
|
|
3087
|
-
error: err instanceof Error ? err.message : String(err)
|
|
3088
|
-
};
|
|
3276
|
+
eliseThreshold: 1200,
|
|
3277
|
+
targetLoad: 0.58
|
|
3089
3278
|
}
|
|
3279
|
+
]);
|
|
3280
|
+
function listContextWindowModes() {
|
|
3281
|
+
return CONTEXT_WINDOW_MODES.map((m) => ({ ...m, thresholds: { ...m.thresholds } }));
|
|
3282
|
+
}
|
|
3283
|
+
function isContextWindowModeId(id) {
|
|
3284
|
+
return CONTEXT_WINDOW_MODES.some((m) => m.id === id);
|
|
3090
3285
|
}
|
|
3091
3286
|
|
|
3092
3287
|
// src/types/default-config.ts
|
|
@@ -3096,7 +3291,8 @@ var DEFAULT_TOOLS_CONFIG = Object.freeze({
|
|
|
3096
3291
|
iterationTimeoutMs: 3e5,
|
|
3097
3292
|
sessionTimeoutMs: 18e5,
|
|
3098
3293
|
perIterationOutputCapBytes: 1e5,
|
|
3099
|
-
autoExtendLimit: true
|
|
3294
|
+
autoExtendLimit: true,
|
|
3295
|
+
restrictToProjectRoot: false
|
|
3100
3296
|
});
|
|
3101
3297
|
var DEFAULT_CONTEXT_CONFIG = Object.freeze({
|
|
3102
3298
|
preserveK: 10,
|
|
@@ -3136,7 +3332,8 @@ var BEHAVIOR_DEFAULTS = {
|
|
|
3136
3332
|
iterationTimeoutMs: DEFAULT_TOOLS_CONFIG.iterationTimeoutMs,
|
|
3137
3333
|
sessionTimeoutMs: DEFAULT_TOOLS_CONFIG.sessionTimeoutMs,
|
|
3138
3334
|
perIterationOutputCapBytes: DEFAULT_TOOLS_CONFIG.perIterationOutputCapBytes,
|
|
3139
|
-
autoExtendLimit: DEFAULT_TOOLS_CONFIG.autoExtendLimit
|
|
3335
|
+
autoExtendLimit: DEFAULT_TOOLS_CONFIG.autoExtendLimit,
|
|
3336
|
+
restrictToProjectRoot: DEFAULT_TOOLS_CONFIG.restrictToProjectRoot
|
|
3140
3337
|
},
|
|
3141
3338
|
log: { level: "info" },
|
|
3142
3339
|
features: {
|
|
@@ -3261,7 +3458,7 @@ var DefaultConfigLoader = class {
|
|
|
3261
3458
|
level: "warn",
|
|
3262
3459
|
event: "config.source_load_failed",
|
|
3263
3460
|
source: src.name,
|
|
3264
|
-
message:
|
|
3461
|
+
message: toErrorMessage(err),
|
|
3265
3462
|
timestamp: (/* @__PURE__ */ new Date()).toISOString()
|
|
3266
3463
|
}));
|
|
3267
3464
|
}
|
|
@@ -3398,7 +3595,7 @@ var DefaultConfigLoader = class {
|
|
|
3398
3595
|
console.warn(JSON.stringify({
|
|
3399
3596
|
level: "warn",
|
|
3400
3597
|
event: "config.sync_load_failed",
|
|
3401
|
-
message:
|
|
3598
|
+
message: toErrorMessage(err),
|
|
3402
3599
|
timestamp: (/* @__PURE__ */ new Date()).toISOString()
|
|
3403
3600
|
}));
|
|
3404
3601
|
return null;
|
|
@@ -3425,7 +3622,7 @@ var DefaultConfigLoader = class {
|
|
|
3425
3622
|
level: "warn",
|
|
3426
3623
|
event: "config.read_failed",
|
|
3427
3624
|
path: file,
|
|
3428
|
-
message:
|
|
3625
|
+
message: toErrorMessage(err),
|
|
3429
3626
|
timestamp: (/* @__PURE__ */ new Date()).toISOString()
|
|
3430
3627
|
}));
|
|
3431
3628
|
}
|
|
@@ -3576,7 +3773,7 @@ var RecoveryLock = class {
|
|
|
3576
3773
|
sessionStore;
|
|
3577
3774
|
probe;
|
|
3578
3775
|
constructor(opts) {
|
|
3579
|
-
this.file =
|
|
3776
|
+
this.file = path2.join(opts.dir, LOCK_FILE);
|
|
3580
3777
|
this.pid = opts.pid ?? process.pid;
|
|
3581
3778
|
this.hostname = opts.hostname ?? os.hostname();
|
|
3582
3779
|
this.maxAgeMs = opts.maxAgeMs ?? DEFAULT_MAX_AGE_MS;
|
|
@@ -3637,7 +3834,7 @@ var RecoveryLock = class {
|
|
|
3637
3834
|
* null return before calling this.
|
|
3638
3835
|
*/
|
|
3639
3836
|
async write(sessionId) {
|
|
3640
|
-
await ensureDir(
|
|
3837
|
+
await ensureDir(path2.dirname(this.file));
|
|
3641
3838
|
const lock = {
|
|
3642
3839
|
v: 1,
|
|
3643
3840
|
sessionId,
|
|
@@ -3704,42 +3901,6 @@ function defaultIsPidAlive(pid) {
|
|
|
3704
3901
|
}
|
|
3705
3902
|
}
|
|
3706
3903
|
|
|
3707
|
-
// src/utils/regex-guard.ts
|
|
3708
|
-
var MAX_PATTERN_LEN = 512;
|
|
3709
|
-
var DANGEROUS_PATTERNS = [
|
|
3710
|
-
/(\([^)]*[+*][^)]*\))[+*]/,
|
|
3711
|
-
// (a+)+, (.*)+, etc
|
|
3712
|
-
/(\(\?:[^)]*[+*][^)]*\))[+*]/
|
|
3713
|
-
// same, with non-capturing group
|
|
3714
|
-
];
|
|
3715
|
-
function compileUserRegex(pattern, flags) {
|
|
3716
|
-
if (typeof pattern !== "string") {
|
|
3717
|
-
return { ok: false, reason: "pattern must be a string" };
|
|
3718
|
-
}
|
|
3719
|
-
if (pattern.length === 0) {
|
|
3720
|
-
return { ok: false, reason: "pattern is empty" };
|
|
3721
|
-
}
|
|
3722
|
-
if (pattern.length > MAX_PATTERN_LEN) {
|
|
3723
|
-
return { ok: false, reason: `pattern exceeds ${MAX_PATTERN_LEN} characters` };
|
|
3724
|
-
}
|
|
3725
|
-
for (const rx of DANGEROUS_PATTERNS) {
|
|
3726
|
-
if (rx.test(pattern)) {
|
|
3727
|
-
return {
|
|
3728
|
-
ok: false,
|
|
3729
|
-
reason: "pattern looks vulnerable to catastrophic backtracking \u2014 rewrite without nested quantifiers"
|
|
3730
|
-
};
|
|
3731
|
-
}
|
|
3732
|
-
}
|
|
3733
|
-
try {
|
|
3734
|
-
return { ok: true, regex: new RegExp(pattern, flags) };
|
|
3735
|
-
} catch (err) {
|
|
3736
|
-
return {
|
|
3737
|
-
ok: false,
|
|
3738
|
-
reason: err instanceof Error ? err.message : "invalid regex"
|
|
3739
|
-
};
|
|
3740
|
-
}
|
|
3741
|
-
}
|
|
3742
|
-
|
|
3743
3904
|
// src/storage/session-reader.ts
|
|
3744
3905
|
var DefaultSessionReader = class {
|
|
3745
3906
|
store;
|
|
@@ -4019,9 +4180,9 @@ function sessionScopedPath(dir, sessionId, suffix) {
|
|
|
4019
4180
|
if (!sessionId || sessionId.includes("\\") || sessionId.includes("..")) {
|
|
4020
4181
|
throw invalid(sessionId);
|
|
4021
4182
|
}
|
|
4022
|
-
const resolved =
|
|
4023
|
-
const rel =
|
|
4024
|
-
if (rel.startsWith("..") ||
|
|
4183
|
+
const resolved = path2.resolve(dir, `${sessionId}${suffix}`);
|
|
4184
|
+
const rel = path2.relative(path2.resolve(dir), resolved);
|
|
4185
|
+
if (rel.startsWith("..") || path2.isAbsolute(rel)) {
|
|
4025
4186
|
throw invalid(sessionId);
|
|
4026
4187
|
}
|
|
4027
4188
|
return resolved;
|
|
@@ -4083,7 +4244,7 @@ var AnnotationsStore = class {
|
|
|
4083
4244
|
operation: "list",
|
|
4084
4245
|
outcome: "failure",
|
|
4085
4246
|
durationMs: Date.now() - t0,
|
|
4086
|
-
error:
|
|
4247
|
+
error: toErrorMessage(err),
|
|
4087
4248
|
...this.traceId !== void 0 ? { traceId: this.traceId } : {}
|
|
4088
4249
|
});
|
|
4089
4250
|
return [];
|
|
@@ -4188,7 +4349,7 @@ var AnnotationsStore = class {
|
|
|
4188
4349
|
filePath: fp,
|
|
4189
4350
|
operation: "add",
|
|
4190
4351
|
outcome: "failure",
|
|
4191
|
-
error:
|
|
4352
|
+
error: toErrorMessage(err),
|
|
4192
4353
|
recoverable: false,
|
|
4193
4354
|
durationMs: Date.now() - t0,
|
|
4194
4355
|
...this.traceId !== void 0 ? { traceId: this.traceId } : {}
|
|
@@ -4244,7 +4405,7 @@ var AnnotationsStore = class {
|
|
|
4244
4405
|
filePath: fp,
|
|
4245
4406
|
operation: "resolve",
|
|
4246
4407
|
outcome: "failure",
|
|
4247
|
-
error:
|
|
4408
|
+
error: toErrorMessage(err),
|
|
4248
4409
|
recoverable: false,
|
|
4249
4410
|
durationMs: Date.now() - t0,
|
|
4250
4411
|
...this.traceId !== void 0 ? { traceId: this.traceId } : {}
|
|
@@ -4364,22 +4525,40 @@ var ReplayLogStore = class {
|
|
|
4364
4525
|
const t0 = Date.now();
|
|
4365
4526
|
try {
|
|
4366
4527
|
await this.enqueue(input.sessionId, async () => {
|
|
4367
|
-
await withFileLock(
|
|
4368
|
-
const
|
|
4369
|
-
if (
|
|
4528
|
+
await withFileLock(fp, async () => {
|
|
4529
|
+
const cache = await this.ensureCache(input.sessionId);
|
|
4530
|
+
if (cache.has(hash)) return;
|
|
4370
4531
|
const entry = {
|
|
4371
4532
|
hash,
|
|
4372
4533
|
ts: (/* @__PURE__ */ new Date()).toISOString(),
|
|
4373
4534
|
request: input.request,
|
|
4374
4535
|
response: input.response
|
|
4375
4536
|
};
|
|
4376
|
-
|
|
4377
|
-
const
|
|
4378
|
-
|
|
4379
|
-
|
|
4380
|
-
|
|
4381
|
-
|
|
4382
|
-
|
|
4537
|
+
const currentCount = this.diskCount.get(input.sessionId) ?? 0;
|
|
4538
|
+
const willEvict = currentCount + 1 > this.maxEntries;
|
|
4539
|
+
if (!willEvict) {
|
|
4540
|
+
await fsp.appendFile(fp, JSON.stringify(entry) + "\n", "utf8");
|
|
4541
|
+
cache.set(hash, entry);
|
|
4542
|
+
this.diskCount.set(input.sessionId, currentCount + 1);
|
|
4543
|
+
this.events?.emit("storage.write", {
|
|
4544
|
+
sessionId: input.sessionId,
|
|
4545
|
+
store: "replay",
|
|
4546
|
+
filePath: fp,
|
|
4547
|
+
operation: "record",
|
|
4548
|
+
outcome: "success",
|
|
4549
|
+
durationMs: Date.now() - t0,
|
|
4550
|
+
...this.traceId !== void 0 ? { traceId: this.traceId } : {}
|
|
4551
|
+
});
|
|
4552
|
+
return;
|
|
4553
|
+
}
|
|
4554
|
+
const all = await this.readAll(input.sessionId);
|
|
4555
|
+
all.push(entry);
|
|
4556
|
+
const keep = all.slice(-this.maxEntries);
|
|
4557
|
+
const refreshed = /* @__PURE__ */ new Map();
|
|
4558
|
+
for (const e of keep) refreshed.set(e.hash, e);
|
|
4559
|
+
this.cache.set(input.sessionId, refreshed);
|
|
4560
|
+
this.diskCount.set(input.sessionId, keep.length);
|
|
4561
|
+
await this.writeAll(input.sessionId, keep, "compact");
|
|
4383
4562
|
});
|
|
4384
4563
|
});
|
|
4385
4564
|
return hash;
|
|
@@ -4482,7 +4661,7 @@ var ReplayLogStore = class {
|
|
|
4482
4661
|
level: "warn",
|
|
4483
4662
|
event: "replay_log_store.list_readdir_failed",
|
|
4484
4663
|
dir,
|
|
4485
|
-
message:
|
|
4664
|
+
message: toErrorMessage(err),
|
|
4486
4665
|
timestamp: (/* @__PURE__ */ new Date()).toISOString()
|
|
4487
4666
|
}));
|
|
4488
4667
|
}
|
|
@@ -4491,7 +4670,7 @@ var ReplayLogStore = class {
|
|
|
4491
4670
|
for (const entry of entries) {
|
|
4492
4671
|
if (entry.name.startsWith(".")) continue;
|
|
4493
4672
|
if (entry.isDirectory()) {
|
|
4494
|
-
if (depth === 0) await scan(
|
|
4673
|
+
if (depth === 0) await scan(path2.join(dir, entry.name), entry.name, depth + 1);
|
|
4495
4674
|
continue;
|
|
4496
4675
|
}
|
|
4497
4676
|
if (!entry.isFile() || !entry.name.endsWith(".replay.jsonl")) continue;
|
|
@@ -4501,7 +4680,7 @@ var ReplayLogStore = class {
|
|
|
4501
4680
|
out.push({
|
|
4502
4681
|
sessionId,
|
|
4503
4682
|
entryCount: all.length,
|
|
4504
|
-
path:
|
|
4683
|
+
path: path2.join(dir, entry.name)
|
|
4505
4684
|
});
|
|
4506
4685
|
}
|
|
4507
4686
|
};
|
|
@@ -4590,15 +4769,15 @@ var SessionRecovery = class {
|
|
|
4590
4769
|
async detectStale(sessionId) {
|
|
4591
4770
|
const fp = this.filePath(sessionId);
|
|
4592
4771
|
const TAIL_SIZE = 8192;
|
|
4593
|
-
let
|
|
4772
|
+
let stat7;
|
|
4594
4773
|
try {
|
|
4595
|
-
|
|
4774
|
+
stat7 = await fsp.stat(fp);
|
|
4596
4775
|
} catch (err) {
|
|
4597
4776
|
if (err.code === "ENOENT") return null;
|
|
4598
4777
|
return null;
|
|
4599
4778
|
}
|
|
4600
|
-
if (
|
|
4601
|
-
const position = Math.max(0,
|
|
4779
|
+
if (stat7.size === 0) return null;
|
|
4780
|
+
const position = Math.max(0, stat7.size - TAIL_SIZE);
|
|
4602
4781
|
const buf = Buffer.alloc(TAIL_SIZE);
|
|
4603
4782
|
let fh;
|
|
4604
4783
|
try {
|
|
@@ -4702,7 +4881,7 @@ var SessionRecovery = class {
|
|
|
4702
4881
|
continue;
|
|
4703
4882
|
if (entry.isDirectory()) {
|
|
4704
4883
|
if (depth === 0) {
|
|
4705
|
-
await collect(
|
|
4884
|
+
await collect(path2.join(dir, entry.name), entry.name, depth + 1);
|
|
4706
4885
|
}
|
|
4707
4886
|
continue;
|
|
4708
4887
|
}
|
|
@@ -4734,6 +4913,13 @@ var ToolAuditLog = class {
|
|
|
4734
4913
|
tailHash = /* @__PURE__ */ new Map();
|
|
4735
4914
|
/** In-memory counter for entry indices — avoids re-reading the file on every write. */
|
|
4736
4915
|
tailIndex = /* @__PURE__ */ new Map();
|
|
4916
|
+
/**
|
|
4917
|
+
* File mtime+size recorded after our last write, per session. Used to
|
|
4918
|
+
* detect cross-process writes (session handoff, recovery) that would
|
|
4919
|
+
* invalidate the in-memory tail cache: if the stat no longer matches
|
|
4920
|
+
* we re-read the file to re-establish the chain tip before appending.
|
|
4921
|
+
*/
|
|
4922
|
+
tailStat = /* @__PURE__ */ new Map();
|
|
4737
4923
|
/** Tracks writes since last fsync, per session. */
|
|
4738
4924
|
unSyncedWrites = /* @__PURE__ */ new Map();
|
|
4739
4925
|
writeChains = /* @__PURE__ */ new Map();
|
|
@@ -4757,10 +4943,9 @@ var ToolAuditLog = class {
|
|
|
4757
4943
|
try {
|
|
4758
4944
|
await this.enqueue(input.sessionId, async () => {
|
|
4759
4945
|
await withFileLock(fp, async () => {
|
|
4760
|
-
const
|
|
4761
|
-
const
|
|
4762
|
-
const
|
|
4763
|
-
const index = prev ? prev.index + 1 : 0;
|
|
4946
|
+
const tip = await this._resolveChainTip(input.sessionId, fp);
|
|
4947
|
+
const prevHash = tip.prevHash;
|
|
4948
|
+
const index = tip.nextIndex;
|
|
4764
4949
|
const id = randomUUID();
|
|
4765
4950
|
const ts = (/* @__PURE__ */ new Date()).toISOString();
|
|
4766
4951
|
const content = {
|
|
@@ -4787,10 +4972,15 @@ var ToolAuditLog = class {
|
|
|
4787
4972
|
isError: input.isError,
|
|
4788
4973
|
index
|
|
4789
4974
|
};
|
|
4790
|
-
|
|
4791
|
-
|
|
4975
|
+
await fsp.appendFile(fp, JSON.stringify(entry) + "\n", "utf8");
|
|
4976
|
+
try {
|
|
4977
|
+
const st = await fsp.stat(fp);
|
|
4978
|
+
this.tailStat.set(input.sessionId, { mtimeMs: st.mtimeMs, size: st.size });
|
|
4979
|
+
} catch {
|
|
4980
|
+
}
|
|
4792
4981
|
this.tailHash.set(input.sessionId, hash);
|
|
4793
4982
|
this.tailIndex.set(input.sessionId, index + 1);
|
|
4983
|
+
await this._trackUnsynced(input.sessionId, fp);
|
|
4794
4984
|
const durationMs = Date.now() - t0;
|
|
4795
4985
|
this.events?.emit("storage.write", {
|
|
4796
4986
|
sessionId: input.sessionId,
|
|
@@ -4811,7 +5001,7 @@ var ToolAuditLog = class {
|
|
|
4811
5001
|
filePath: fp,
|
|
4812
5002
|
operation: "record",
|
|
4813
5003
|
outcome: "failure",
|
|
4814
|
-
error:
|
|
5004
|
+
error: toErrorMessage(err),
|
|
4815
5005
|
recoverable: false,
|
|
4816
5006
|
durationMs: Date.now() - t0,
|
|
4817
5007
|
...this.traceId !== void 0 ? { traceId: this.traceId } : {}
|
|
@@ -4819,6 +5009,45 @@ var ToolAuditLog = class {
|
|
|
4819
5009
|
throw err;
|
|
4820
5010
|
}
|
|
4821
5011
|
}
|
|
5012
|
+
/**
|
|
5013
|
+
* Resolve the chain tip (previous hash + next index) for an append.
|
|
5014
|
+
* Uses the in-memory `tailHash`/`tailIndex` cache when the file's
|
|
5015
|
+
* stat matches our last known write; falls back to a full read on
|
|
5016
|
+
* cache miss or when an external writer has extended the file.
|
|
5017
|
+
*/
|
|
5018
|
+
async _resolveChainTip(sessionId, fp) {
|
|
5019
|
+
const cachedHash = this.tailHash.get(sessionId);
|
|
5020
|
+
const cachedIndex = this.tailIndex.get(sessionId);
|
|
5021
|
+
const cachedStat = this.tailStat.get(sessionId);
|
|
5022
|
+
if (cachedHash !== void 0 && cachedIndex !== void 0 && cachedStat) {
|
|
5023
|
+
try {
|
|
5024
|
+
const st = await fsp.stat(fp);
|
|
5025
|
+
if (st.mtimeMs === cachedStat.mtimeMs && st.size === cachedStat.size) {
|
|
5026
|
+
return { prevHash: cachedHash, nextIndex: cachedIndex };
|
|
5027
|
+
}
|
|
5028
|
+
} catch (err) {
|
|
5029
|
+
if (err.code === "ENOENT") {
|
|
5030
|
+
this.tailHash.delete(sessionId);
|
|
5031
|
+
this.tailIndex.delete(sessionId);
|
|
5032
|
+
this.tailStat.delete(sessionId);
|
|
5033
|
+
return { prevHash: GENESIS_PREV, nextIndex: 0 };
|
|
5034
|
+
}
|
|
5035
|
+
throw err;
|
|
5036
|
+
}
|
|
5037
|
+
}
|
|
5038
|
+
const entries = await this.readAll(sessionId);
|
|
5039
|
+
const prev = entries.at(-1);
|
|
5040
|
+
const prevHash = prev?.hash ?? GENESIS_PREV;
|
|
5041
|
+
const nextIndex = prev ? prev.index + 1 : 0;
|
|
5042
|
+
this.tailHash.set(sessionId, prevHash);
|
|
5043
|
+
this.tailIndex.set(sessionId, nextIndex);
|
|
5044
|
+
try {
|
|
5045
|
+
const st = await fsp.stat(fp);
|
|
5046
|
+
this.tailStat.set(sessionId, { mtimeMs: st.mtimeMs, size: st.size });
|
|
5047
|
+
} catch {
|
|
5048
|
+
}
|
|
5049
|
+
return { prevHash, nextIndex };
|
|
5050
|
+
}
|
|
4822
5051
|
/**
|
|
4823
5052
|
* Walk the chain and verify every entry's hash and prevHash.
|
|
4824
5053
|
* Returns a structured verdict — never throws.
|
|
@@ -4837,7 +5066,7 @@ var ToolAuditLog = class {
|
|
|
4837
5066
|
operation: "verify",
|
|
4838
5067
|
outcome: "failure",
|
|
4839
5068
|
durationMs: Date.now() - t0,
|
|
4840
|
-
error:
|
|
5069
|
+
error: toErrorMessage(err),
|
|
4841
5070
|
...this.traceId !== void 0 ? { traceId: this.traceId } : {}
|
|
4842
5071
|
});
|
|
4843
5072
|
return { ok: true, entries: 0 };
|
|
@@ -4921,7 +5150,7 @@ var ToolAuditLog = class {
|
|
|
4921
5150
|
operation: "load",
|
|
4922
5151
|
outcome: "failure",
|
|
4923
5152
|
durationMs,
|
|
4924
|
-
error:
|
|
5153
|
+
error: toErrorMessage(err),
|
|
4925
5154
|
...this.traceId !== void 0 ? { traceId: this.traceId } : {}
|
|
4926
5155
|
});
|
|
4927
5156
|
throw err;
|
|
@@ -4950,10 +5179,12 @@ var ToolAuditLog = class {
|
|
|
4950
5179
|
throw err;
|
|
4951
5180
|
}
|
|
4952
5181
|
}
|
|
4953
|
-
|
|
4954
|
-
|
|
4955
|
-
|
|
4956
|
-
|
|
5182
|
+
/**
|
|
5183
|
+
* Tracks writes since last fsync and triggers periodic fsync.
|
|
5184
|
+
* Called after each O(1) append to maintain the same durability
|
|
5185
|
+
* guarantees as the old writeAll approach.
|
|
5186
|
+
*/
|
|
5187
|
+
async _trackUnsynced(sessionId, fp) {
|
|
4957
5188
|
const count = (this.unSyncedWrites.get(sessionId) ?? 0) + 1;
|
|
4958
5189
|
this.unSyncedWrites.set(sessionId, count);
|
|
4959
5190
|
if (this.fsyncEvery !== Number.POSITIVE_INFINITY && count % this.fsyncEvery === 0) {
|
|
@@ -5109,6 +5340,8 @@ var SessionAnalyzer = class {
|
|
|
5109
5340
|
var REGISTRY_FILE = "session-registry.json";
|
|
5110
5341
|
var HEARTBEAT_INTERVAL_MS = 5e3;
|
|
5111
5342
|
var STALE_TIMEOUT_MS = 3e4;
|
|
5343
|
+
var CLOSING_GRACE_MS = 15e3;
|
|
5344
|
+
var STALE_LOCK_MS = 1e4;
|
|
5112
5345
|
function pidAlive(pid) {
|
|
5113
5346
|
try {
|
|
5114
5347
|
process.kill(pid, 0);
|
|
@@ -5121,8 +5354,14 @@ var SessionRegistry = class {
|
|
|
5121
5354
|
filePath;
|
|
5122
5355
|
heartbeatTimer = null;
|
|
5123
5356
|
currentSessionId = null;
|
|
5357
|
+
/**
|
|
5358
|
+
* Last full entry this process registered. Kept so the heartbeat can
|
|
5359
|
+
* re-create our entry if it ever goes missing — e.g. our initial register()
|
|
5360
|
+
* write was dropped (a wedged lock), the file was reset, or we were pruned.
|
|
5361
|
+
*/
|
|
5362
|
+
lastEntry = null;
|
|
5124
5363
|
constructor(globalRoot) {
|
|
5125
|
-
this.filePath =
|
|
5364
|
+
this.filePath = path2.join(globalRoot, REGISTRY_FILE);
|
|
5126
5365
|
}
|
|
5127
5366
|
// ── Public API ──────────────────────────────────────────────────────────
|
|
5128
5367
|
/**
|
|
@@ -5142,6 +5381,7 @@ var SessionRegistry = class {
|
|
|
5142
5381
|
agentCount: entry.agents?.length ?? 0,
|
|
5143
5382
|
agents: entry.agents ?? []
|
|
5144
5383
|
};
|
|
5384
|
+
this.lastEntry = full;
|
|
5145
5385
|
await this.atomicUpdate((registry) => {
|
|
5146
5386
|
const now = Date.now();
|
|
5147
5387
|
for (const [id, existing] of Object.entries(registry)) {
|
|
@@ -5167,16 +5407,28 @@ var SessionRegistry = class {
|
|
|
5167
5407
|
*/
|
|
5168
5408
|
async updateAgents(agents) {
|
|
5169
5409
|
if (!this.currentSessionId) return;
|
|
5410
|
+
const hasRunning = agents.some((a) => a.status === "running" || a.status === "streaming");
|
|
5411
|
+
const hasWaiting = agents.some((a) => a.status === "waiting_user");
|
|
5412
|
+
const hasError = agents.some((a) => a.status === "error");
|
|
5413
|
+
const status = hasRunning || hasWaiting || hasError ? "active" : "idle";
|
|
5414
|
+
const nowIso = (/* @__PURE__ */ new Date()).toISOString();
|
|
5415
|
+
if (this.lastEntry) {
|
|
5416
|
+
this.lastEntry.agents = agents;
|
|
5417
|
+
this.lastEntry.agentCount = agents.length;
|
|
5418
|
+
this.lastEntry.status = status;
|
|
5419
|
+
this.lastEntry.lastHeartbeatAt = nowIso;
|
|
5420
|
+
}
|
|
5170
5421
|
await this.atomicUpdate((registry) => {
|
|
5171
|
-
|
|
5172
|
-
if (!entry)
|
|
5422
|
+
let entry = registry[this.currentSessionId];
|
|
5423
|
+
if (!entry) {
|
|
5424
|
+
if (!this.lastEntry) return;
|
|
5425
|
+
entry = { ...this.lastEntry };
|
|
5426
|
+
registry[this.currentSessionId] = entry;
|
|
5427
|
+
}
|
|
5173
5428
|
entry.agents = agents;
|
|
5174
5429
|
entry.agentCount = agents.length;
|
|
5175
|
-
|
|
5176
|
-
|
|
5177
|
-
const hasError = agents.some((a) => a.status === "error");
|
|
5178
|
-
entry.status = hasRunning ? "active" : hasWaiting ? "active" : hasError ? "active" : "idle";
|
|
5179
|
-
entry.lastHeartbeatAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
5430
|
+
entry.status = status;
|
|
5431
|
+
entry.lastHeartbeatAt = nowIso;
|
|
5180
5432
|
});
|
|
5181
5433
|
}
|
|
5182
5434
|
/**
|
|
@@ -5254,6 +5506,12 @@ var SessionRegistry = class {
|
|
|
5254
5506
|
entry.status = hasRunning ? "active" : "idle";
|
|
5255
5507
|
}
|
|
5256
5508
|
await this.writeAtomic(registry);
|
|
5509
|
+
} else if (this.lastEntry) {
|
|
5510
|
+
await this.atomicUpdate((reg) => {
|
|
5511
|
+
if (!reg[this.currentSessionId] && this.lastEntry) {
|
|
5512
|
+
reg[this.currentSessionId] = { ...this.lastEntry, lastHeartbeatAt: (/* @__PURE__ */ new Date()).toISOString() };
|
|
5513
|
+
}
|
|
5514
|
+
});
|
|
5257
5515
|
}
|
|
5258
5516
|
} catch {
|
|
5259
5517
|
}
|
|
@@ -5266,6 +5524,11 @@ var SessionRegistry = class {
|
|
|
5266
5524
|
let pruned = false;
|
|
5267
5525
|
for (const [id, entry] of Object.entries(registry)) {
|
|
5268
5526
|
const heartbeatAge = now - new Date(entry.lastHeartbeatAt).getTime();
|
|
5527
|
+
if (entry.status === "closing" && heartbeatAge > CLOSING_GRACE_MS) {
|
|
5528
|
+
delete registry[id];
|
|
5529
|
+
pruned = true;
|
|
5530
|
+
continue;
|
|
5531
|
+
}
|
|
5269
5532
|
if (heartbeatAge > STALE_TIMEOUT_MS && !pidAlive(entry.pid)) {
|
|
5270
5533
|
entry.status = "stale";
|
|
5271
5534
|
const startedAge = now - new Date(entry.startedAt).getTime();
|
|
@@ -5285,17 +5548,23 @@ var SessionRegistry = class {
|
|
|
5285
5548
|
}
|
|
5286
5549
|
async atomicUpdate(fn) {
|
|
5287
5550
|
const lockPath = `${this.filePath}.lock`;
|
|
5288
|
-
const maxRetries =
|
|
5551
|
+
const maxRetries = 8;
|
|
5289
5552
|
const retryDelayMs = 20;
|
|
5290
5553
|
for (let attempt = 0; attempt < maxRetries; attempt++) {
|
|
5291
5554
|
try {
|
|
5292
|
-
await fsp.mkdir(
|
|
5293
|
-
|
|
5555
|
+
await fsp.mkdir(path2.dirname(this.filePath), { recursive: true });
|
|
5556
|
+
let lockHandle = await fsp.open(lockPath, "wx").catch(() => null);
|
|
5294
5557
|
if (!lockHandle) {
|
|
5295
|
-
|
|
5296
|
-
|
|
5558
|
+
if (await this.breakStaleLock(lockPath)) {
|
|
5559
|
+
lockHandle = await fsp.open(lockPath, "wx").catch(() => null);
|
|
5560
|
+
}
|
|
5561
|
+
if (!lockHandle) {
|
|
5562
|
+
await new Promise((r) => setTimeout(r, retryDelayMs * (attempt + 1)));
|
|
5563
|
+
continue;
|
|
5564
|
+
}
|
|
5297
5565
|
}
|
|
5298
5566
|
try {
|
|
5567
|
+
await lockHandle.writeFile(String(process.pid)).catch(() => void 0);
|
|
5299
5568
|
const raw = await fsp.readFile(this.filePath, "utf8").catch(() => "{}");
|
|
5300
5569
|
const registry = JSON.parse(raw);
|
|
5301
5570
|
fn(registry);
|
|
@@ -5310,6 +5579,31 @@ var SessionRegistry = class {
|
|
|
5310
5579
|
}
|
|
5311
5580
|
}
|
|
5312
5581
|
}
|
|
5582
|
+
/**
|
|
5583
|
+
* Break a contended lock if it is stale: the recorded owner pid is no longer
|
|
5584
|
+
* alive, or the lock is older than {@link STALE_LOCK_MS}. Returns true when the
|
|
5585
|
+
* lock was removed (caller should retry acquisition). Best-effort and
|
|
5586
|
+
* race-tolerant — a fresh lock (age ~0, live owner) is never broken, so the
|
|
5587
|
+
* common concurrent case self-heals on the next heartbeat.
|
|
5588
|
+
*/
|
|
5589
|
+
async breakStaleLock(lockPath) {
|
|
5590
|
+
try {
|
|
5591
|
+
const [stat7, content] = await Promise.all([
|
|
5592
|
+
fsp.stat(lockPath),
|
|
5593
|
+
fsp.readFile(lockPath, "utf8").catch(() => "")
|
|
5594
|
+
]);
|
|
5595
|
+
const ageMs = Date.now() - stat7.mtimeMs;
|
|
5596
|
+
const ownerPid = Number.parseInt(content.trim(), 10);
|
|
5597
|
+
const ownerDead = Number.isInteger(ownerPid) && ownerPid > 0 && ownerPid !== process.pid && !pidAlive(ownerPid);
|
|
5598
|
+
if (ownerDead || ageMs > STALE_LOCK_MS) {
|
|
5599
|
+
await fsp.unlink(lockPath).catch(() => void 0);
|
|
5600
|
+
return true;
|
|
5601
|
+
}
|
|
5602
|
+
return false;
|
|
5603
|
+
} catch {
|
|
5604
|
+
return true;
|
|
5605
|
+
}
|
|
5606
|
+
}
|
|
5313
5607
|
async writeAtomicLocked(registry) {
|
|
5314
5608
|
const tmp = `${this.filePath}.${randomUUID().slice(0, 8)}.tmp`;
|
|
5315
5609
|
await fsp.writeFile(tmp, JSON.stringify(registry, null, 2), "utf8");
|
|
@@ -5337,6 +5631,10 @@ function hasSessionRegistry() {
|
|
|
5337
5631
|
}
|
|
5338
5632
|
|
|
5339
5633
|
// src/agent-status-tracker.ts
|
|
5634
|
+
var AGENT_REAP_MS = 3e4;
|
|
5635
|
+
var AGENT_SWEEP_INTERVAL_MS = 1e4;
|
|
5636
|
+
var PARTIAL_TEXT_CAP = 1200;
|
|
5637
|
+
var PARTIAL_FLUSH_THROTTLE_MS = 300;
|
|
5340
5638
|
var AgentStatusTracker = class {
|
|
5341
5639
|
events;
|
|
5342
5640
|
registry;
|
|
@@ -5348,11 +5646,21 @@ var AgentStatusTracker = class {
|
|
|
5348
5646
|
leaderCurrentTool;
|
|
5349
5647
|
leaderIterations = 0;
|
|
5350
5648
|
leaderToolCalls = 0;
|
|
5649
|
+
leaderCostUsd = 0;
|
|
5650
|
+
leaderTokensIn = 0;
|
|
5651
|
+
leaderTokensOut = 0;
|
|
5652
|
+
leaderCtxPct;
|
|
5653
|
+
leaderModel;
|
|
5654
|
+
leaderPartialText = "";
|
|
5351
5655
|
unsubscribers = [];
|
|
5656
|
+
onUpdate;
|
|
5657
|
+
sweepTimer = null;
|
|
5658
|
+
partialTimer = null;
|
|
5352
5659
|
constructor(opts) {
|
|
5353
5660
|
this.events = opts.events;
|
|
5354
5661
|
this.registry = opts.registry;
|
|
5355
5662
|
this.leaderName = opts.leaderName ?? "leader";
|
|
5663
|
+
this.onUpdate = opts.onUpdate;
|
|
5356
5664
|
}
|
|
5357
5665
|
start() {
|
|
5358
5666
|
this.unsubscribers.push(
|
|
@@ -5362,10 +5670,22 @@ var AgentStatusTracker = class {
|
|
|
5362
5670
|
this.flush();
|
|
5363
5671
|
})
|
|
5364
5672
|
);
|
|
5673
|
+
this.unsubscribers.push(
|
|
5674
|
+
this.events.onPattern("iteration.started", (_e, payload) => {
|
|
5675
|
+
const ctx = payload?.ctx;
|
|
5676
|
+
if (!ctx) return;
|
|
5677
|
+
if (ctx.model) this.leaderModel = ctx.model;
|
|
5678
|
+
if (typeof ctx.tokenCount === "number" && typeof ctx.maxContext === "number" && ctx.maxContext > 0) {
|
|
5679
|
+
this.leaderCtxPct = Math.round(ctx.tokenCount / ctx.maxContext * 100);
|
|
5680
|
+
}
|
|
5681
|
+
this.flush();
|
|
5682
|
+
})
|
|
5683
|
+
);
|
|
5365
5684
|
this.unsubscribers.push(
|
|
5366
5685
|
this.events.onPattern("agent.run.completed", () => {
|
|
5367
5686
|
this.leaderStatus = "idle";
|
|
5368
5687
|
this.leaderCurrentTool = void 0;
|
|
5688
|
+
this.leaderPartialText = "";
|
|
5369
5689
|
this.flush();
|
|
5370
5690
|
})
|
|
5371
5691
|
);
|
|
@@ -5373,6 +5693,7 @@ var AgentStatusTracker = class {
|
|
|
5373
5693
|
this.events.onPattern("agent.run.error", () => {
|
|
5374
5694
|
this.leaderStatus = "error";
|
|
5375
5695
|
this.leaderCurrentTool = void 0;
|
|
5696
|
+
this.leaderPartialText = "";
|
|
5376
5697
|
this.flush();
|
|
5377
5698
|
})
|
|
5378
5699
|
);
|
|
@@ -5402,74 +5723,120 @@ var AgentStatusTracker = class {
|
|
|
5402
5723
|
this.unsubscribers.push(
|
|
5403
5724
|
this.events.onPattern("llm.stream_started", () => {
|
|
5404
5725
|
this.leaderStatus = "streaming";
|
|
5726
|
+
this.leaderPartialText = "";
|
|
5405
5727
|
this.flush();
|
|
5406
5728
|
})
|
|
5407
5729
|
);
|
|
5408
5730
|
this.unsubscribers.push(
|
|
5409
|
-
this.events.onPattern("
|
|
5731
|
+
this.events.onPattern("provider.text_delta", (_e, payload) => {
|
|
5732
|
+
const text = payload?.text;
|
|
5733
|
+
if (!text) return;
|
|
5734
|
+
this.leaderStatus = "streaming";
|
|
5735
|
+
const next = this.leaderPartialText + text;
|
|
5736
|
+
this.leaderPartialText = next.length > PARTIAL_TEXT_CAP ? next.slice(next.length - PARTIAL_TEXT_CAP) : next;
|
|
5737
|
+
this.schedulePartialFlush();
|
|
5738
|
+
})
|
|
5739
|
+
);
|
|
5740
|
+
this.unsubscribers.push(
|
|
5741
|
+
this.events.onPattern("token.accounted", (_e, payload) => {
|
|
5410
5742
|
const p = payload;
|
|
5411
|
-
if (p
|
|
5412
|
-
|
|
5413
|
-
|
|
5414
|
-
|
|
5415
|
-
|
|
5416
|
-
|
|
5417
|
-
|
|
5418
|
-
|
|
5419
|
-
|
|
5420
|
-
|
|
5421
|
-
}
|
|
5743
|
+
if (!p) return;
|
|
5744
|
+
this.leaderTokensIn += p.usage?.input ?? 0;
|
|
5745
|
+
this.leaderTokensOut += p.usage?.output ?? 0;
|
|
5746
|
+
this.leaderCostUsd += p.cost?.total ?? 0;
|
|
5747
|
+
this.flush();
|
|
5748
|
+
})
|
|
5749
|
+
);
|
|
5750
|
+
const touch = (id) => {
|
|
5751
|
+
let entry = this.agents.get(id);
|
|
5752
|
+
if (!entry) {
|
|
5753
|
+
entry = { id, name: id, status: "idle", iterations: 0, toolCalls: 0, lastActivityAt: (/* @__PURE__ */ new Date()).toISOString() };
|
|
5754
|
+
this.agents.set(id, entry);
|
|
5755
|
+
}
|
|
5756
|
+
entry.lastActivityAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
5757
|
+
return entry;
|
|
5758
|
+
};
|
|
5759
|
+
this.unsubscribers.push(
|
|
5760
|
+
this.events.onPattern("subagent.spawned", (_e, payload) => {
|
|
5761
|
+
const p = payload;
|
|
5762
|
+
if (!p?.subagentId) return;
|
|
5763
|
+
const entry = touch(p.subagentId);
|
|
5764
|
+
entry.name = p.name?.trim() || entry.name;
|
|
5765
|
+
if (p.model) entry.model = p.model;
|
|
5766
|
+
entry.status = "running";
|
|
5767
|
+
this.flush();
|
|
5422
5768
|
})
|
|
5423
5769
|
);
|
|
5424
5770
|
this.unsubscribers.push(
|
|
5425
|
-
this.events.onPattern("
|
|
5771
|
+
this.events.onPattern("subagent.ctx_pct", (_e, payload) => {
|
|
5426
5772
|
const p = payload;
|
|
5427
|
-
if (p?.subagentId)
|
|
5428
|
-
|
|
5429
|
-
|
|
5430
|
-
|
|
5431
|
-
entry.iterations++;
|
|
5432
|
-
entry.lastActivityAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
5433
|
-
this.flush();
|
|
5434
|
-
}
|
|
5435
|
-
}
|
|
5773
|
+
if (!p?.subagentId) return;
|
|
5774
|
+
const entry = touch(p.subagentId);
|
|
5775
|
+
if (typeof p.load === "number") entry.ctxPct = Math.round(p.load * 100);
|
|
5776
|
+
this.flush();
|
|
5436
5777
|
})
|
|
5437
5778
|
);
|
|
5438
5779
|
this.unsubscribers.push(
|
|
5439
|
-
this.events.onPattern("
|
|
5780
|
+
this.events.onPattern("subagent.task_started", (_e, payload) => {
|
|
5440
5781
|
const p = payload;
|
|
5441
|
-
if (p?.subagentId)
|
|
5442
|
-
|
|
5443
|
-
|
|
5444
|
-
|
|
5445
|
-
|
|
5446
|
-
this.flush();
|
|
5447
|
-
}
|
|
5448
|
-
}
|
|
5782
|
+
if (!p?.subagentId) return;
|
|
5783
|
+
const entry = touch(p.subagentId);
|
|
5784
|
+
entry.status = "running";
|
|
5785
|
+
entry.iterations++;
|
|
5786
|
+
this.flush();
|
|
5449
5787
|
})
|
|
5450
5788
|
);
|
|
5451
5789
|
this.unsubscribers.push(
|
|
5452
|
-
this.events.onPattern("
|
|
5790
|
+
this.events.onPattern("subagent.tool_executed", (_e, payload) => {
|
|
5453
5791
|
const p = payload;
|
|
5454
|
-
if (p?.subagentId)
|
|
5455
|
-
|
|
5456
|
-
|
|
5457
|
-
|
|
5458
|
-
|
|
5459
|
-
|
|
5460
|
-
}
|
|
5461
|
-
}
|
|
5792
|
+
if (!p?.subagentId) return;
|
|
5793
|
+
const entry = touch(p.subagentId);
|
|
5794
|
+
entry.status = "running";
|
|
5795
|
+
entry.currentTool = p.name;
|
|
5796
|
+
entry.toolCalls++;
|
|
5797
|
+
this.flush();
|
|
5462
5798
|
})
|
|
5463
5799
|
);
|
|
5464
5800
|
this.unsubscribers.push(
|
|
5465
|
-
this.events.onPattern("
|
|
5801
|
+
this.events.onPattern("subagent.iteration_summary", (_e, payload) => {
|
|
5466
5802
|
const p = payload;
|
|
5467
|
-
if (p?.subagentId)
|
|
5468
|
-
|
|
5469
|
-
|
|
5803
|
+
if (!p?.subagentId) return;
|
|
5804
|
+
const entry = touch(p.subagentId);
|
|
5805
|
+
entry.status = "running";
|
|
5806
|
+
if (typeof p.iteration === "number") entry.iterations = p.iteration;
|
|
5807
|
+
if (typeof p.toolCalls === "number") entry.toolCalls = p.toolCalls;
|
|
5808
|
+
if (typeof p.costUsd === "number") entry.costUsd = p.costUsd;
|
|
5809
|
+
if (p.currentTool) entry.currentTool = p.currentTool;
|
|
5810
|
+
if (typeof p.partialText === "string") {
|
|
5811
|
+
entry.partialText = p.partialText.length > PARTIAL_TEXT_CAP ? p.partialText.slice(p.partialText.length - PARTIAL_TEXT_CAP) : p.partialText;
|
|
5470
5812
|
}
|
|
5813
|
+
this.flush();
|
|
5814
|
+
})
|
|
5815
|
+
);
|
|
5816
|
+
this.unsubscribers.push(
|
|
5817
|
+
this.events.onPattern("subagent.task_completed", (_e, payload) => {
|
|
5818
|
+
const p = payload;
|
|
5819
|
+
if (!p?.subagentId) return;
|
|
5820
|
+
const entry = this.agents.get(p.subagentId);
|
|
5821
|
+
if (!entry) return;
|
|
5822
|
+
entry.status = p.status === "failed" || p.status === "timeout" ? "error" : "idle";
|
|
5823
|
+
entry.currentTool = void 0;
|
|
5824
|
+
entry.partialText = void 0;
|
|
5825
|
+
if (typeof p.iterations === "number") entry.iterations = p.iterations;
|
|
5826
|
+
if (typeof p.toolCalls === "number") entry.toolCalls = p.toolCalls;
|
|
5827
|
+
entry.lastActivityAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
5828
|
+
this.flush();
|
|
5829
|
+
})
|
|
5830
|
+
);
|
|
5831
|
+
this.unsubscribers.push(
|
|
5832
|
+
this.events.onPattern("subagent.stopped", (_e, payload) => {
|
|
5833
|
+
const p = payload;
|
|
5834
|
+
if (!p?.subagentId) return;
|
|
5835
|
+
if (this.agents.delete(p.subagentId)) this.flush();
|
|
5471
5836
|
})
|
|
5472
5837
|
);
|
|
5838
|
+
this.sweepTimer = setInterval(() => this.sweep(), AGENT_SWEEP_INTERVAL_MS);
|
|
5839
|
+
if (typeof this.sweepTimer.unref === "function") this.sweepTimer.unref();
|
|
5473
5840
|
}
|
|
5474
5841
|
stop() {
|
|
5475
5842
|
for (const unsub of this.unsubscribers) {
|
|
@@ -5479,6 +5846,45 @@ var AgentStatusTracker = class {
|
|
|
5479
5846
|
}
|
|
5480
5847
|
}
|
|
5481
5848
|
this.unsubscribers = [];
|
|
5849
|
+
if (this.sweepTimer) {
|
|
5850
|
+
clearInterval(this.sweepTimer);
|
|
5851
|
+
this.sweepTimer = null;
|
|
5852
|
+
}
|
|
5853
|
+
if (this.partialTimer) {
|
|
5854
|
+
clearTimeout(this.partialTimer);
|
|
5855
|
+
this.partialTimer = null;
|
|
5856
|
+
}
|
|
5857
|
+
}
|
|
5858
|
+
/**
|
|
5859
|
+
* Coalesce streamed-text flushes: at most one registry write per
|
|
5860
|
+
* {@link PARTIAL_FLUSH_THROTTLE_MS} while text streams in, so per-token
|
|
5861
|
+
* deltas never thrash the cross-process registry file.
|
|
5862
|
+
*/
|
|
5863
|
+
schedulePartialFlush() {
|
|
5864
|
+
if (this.partialTimer) return;
|
|
5865
|
+
this.partialTimer = setTimeout(() => {
|
|
5866
|
+
this.partialTimer = null;
|
|
5867
|
+
this.flush();
|
|
5868
|
+
}, PARTIAL_FLUSH_THROTTLE_MS);
|
|
5869
|
+
if (typeof this.partialTimer.unref === "function") this.partialTimer.unref();
|
|
5870
|
+
}
|
|
5871
|
+
/**
|
|
5872
|
+
* Remove subagents that have been finished (idle/error) for longer than
|
|
5873
|
+
* {@link AGENT_REAP_MS}. Running / streaming / waiting_user agents are kept
|
|
5874
|
+
* regardless of age — only *not-working* agents are reaped.
|
|
5875
|
+
*/
|
|
5876
|
+
sweep() {
|
|
5877
|
+
const now = Date.now();
|
|
5878
|
+
let removed = false;
|
|
5879
|
+
for (const [id, a] of this.agents) {
|
|
5880
|
+
const finished = a.status !== "running" && a.status !== "streaming" && a.status !== "waiting_user";
|
|
5881
|
+
const age = now - Date.parse(a.lastActivityAt);
|
|
5882
|
+
if (finished && Number.isFinite(age) && age > AGENT_REAP_MS) {
|
|
5883
|
+
this.agents.delete(id);
|
|
5884
|
+
removed = true;
|
|
5885
|
+
}
|
|
5886
|
+
}
|
|
5887
|
+
if (removed) this.flush();
|
|
5482
5888
|
}
|
|
5483
5889
|
flush() {
|
|
5484
5890
|
const leaderEntry = {
|
|
@@ -5488,12 +5894,105 @@ var AgentStatusTracker = class {
|
|
|
5488
5894
|
currentTool: this.leaderCurrentTool,
|
|
5489
5895
|
iterations: this.leaderIterations,
|
|
5490
5896
|
toolCalls: this.leaderToolCalls,
|
|
5897
|
+
costUsd: this.leaderCostUsd,
|
|
5898
|
+
tokensIn: this.leaderTokensIn,
|
|
5899
|
+
tokensOut: this.leaderTokensOut,
|
|
5900
|
+
ctxPct: this.leaderCtxPct,
|
|
5901
|
+
model: this.leaderModel,
|
|
5902
|
+
partialText: this.leaderPartialText || void 0,
|
|
5491
5903
|
lastActivityAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
5492
5904
|
};
|
|
5493
5905
|
const allAgents = [leaderEntry, ...this.agents.values()];
|
|
5494
|
-
this.registry.updateAgents(allAgents).
|
|
5906
|
+
this.registry.updateAgents(allAgents).then(() => {
|
|
5907
|
+
try {
|
|
5908
|
+
this.onUpdate?.();
|
|
5909
|
+
} catch {
|
|
5910
|
+
}
|
|
5911
|
+
}).catch(() => void 0);
|
|
5912
|
+
}
|
|
5913
|
+
};
|
|
5914
|
+
var INSTANCES_FILE = "webui-instances.json";
|
|
5915
|
+
var DISCOVERY_TTL_MS = 2500;
|
|
5916
|
+
var COALESCE_MS = 50;
|
|
5917
|
+
var POST_TIMEOUT_MS = 500;
|
|
5918
|
+
function pidAlive2(pid) {
|
|
5919
|
+
if (!Number.isInteger(pid) || pid <= 0) return false;
|
|
5920
|
+
try {
|
|
5921
|
+
process.kill(pid, 0);
|
|
5922
|
+
return true;
|
|
5923
|
+
} catch (err) {
|
|
5924
|
+
return err.code !== "ESRCH";
|
|
5925
|
+
}
|
|
5926
|
+
}
|
|
5927
|
+
function normRoot(root) {
|
|
5928
|
+
const resolved = path2.resolve(root);
|
|
5929
|
+
return process.platform === "win32" ? resolved.toLowerCase() : resolved;
|
|
5930
|
+
}
|
|
5931
|
+
var FleetNotifier = class {
|
|
5932
|
+
baseDir;
|
|
5933
|
+
projectRoot;
|
|
5934
|
+
selfPid;
|
|
5935
|
+
doPost;
|
|
5936
|
+
cache = null;
|
|
5937
|
+
timer = null;
|
|
5938
|
+
disposed = false;
|
|
5939
|
+
constructor(opts) {
|
|
5940
|
+
this.baseDir = opts.baseDir;
|
|
5941
|
+
this.projectRoot = normRoot(opts.projectRoot);
|
|
5942
|
+
this.selfPid = opts.selfPid ?? process.pid;
|
|
5943
|
+
this.doPost = opts.post ?? defaultPost;
|
|
5944
|
+
}
|
|
5945
|
+
/** Coalesced, best-effort nudge. Safe to call on every status change. */
|
|
5946
|
+
notify() {
|
|
5947
|
+
if (this.disposed || this.timer) return;
|
|
5948
|
+
this.timer = setTimeout(() => {
|
|
5949
|
+
this.timer = null;
|
|
5950
|
+
void this.flush();
|
|
5951
|
+
}, COALESCE_MS);
|
|
5952
|
+
if (typeof this.timer.unref === "function") this.timer.unref();
|
|
5953
|
+
}
|
|
5954
|
+
/** Resolve same-project WebUI ping URLs (cached briefly). Exposed for tests. */
|
|
5955
|
+
async endpoints() {
|
|
5956
|
+
const now = Date.now();
|
|
5957
|
+
if (this.cache && now - this.cache.at < DISCOVERY_TTL_MS) return this.cache.urls;
|
|
5958
|
+
const urls = await this.discover();
|
|
5959
|
+
this.cache = { at: now, urls };
|
|
5960
|
+
return urls;
|
|
5961
|
+
}
|
|
5962
|
+
dispose() {
|
|
5963
|
+
this.disposed = true;
|
|
5964
|
+
if (this.timer) {
|
|
5965
|
+
clearTimeout(this.timer);
|
|
5966
|
+
this.timer = null;
|
|
5967
|
+
}
|
|
5968
|
+
}
|
|
5969
|
+
async flush() {
|
|
5970
|
+
const urls = await this.endpoints();
|
|
5971
|
+
await Promise.all(urls.map((u) => this.doPost(u).catch(() => void 0)));
|
|
5972
|
+
}
|
|
5973
|
+
async discover() {
|
|
5974
|
+
try {
|
|
5975
|
+
const raw = await fsp.readFile(path2.join(this.baseDir, INSTANCES_FILE), "utf8");
|
|
5976
|
+
const data = JSON.parse(raw);
|
|
5977
|
+
const list = Array.isArray(data?.instances) ? data.instances : [];
|
|
5978
|
+
return list.filter((i) => i && typeof i.httpPort === "number").filter((i) => i.pid !== this.selfPid).filter((i) => normRoot(i.projectRoot) === this.projectRoot).filter((i) => pidAlive2(i.pid)).map((i) => {
|
|
5979
|
+
const host = i.host === "0.0.0.0" || i.host === "::" || !i.host ? "127.0.0.1" : i.host;
|
|
5980
|
+
return `http://${host}:${i.httpPort}/api/fleet/ping`;
|
|
5981
|
+
});
|
|
5982
|
+
} catch {
|
|
5983
|
+
return [];
|
|
5984
|
+
}
|
|
5495
5985
|
}
|
|
5496
5986
|
};
|
|
5987
|
+
async function defaultPost(url) {
|
|
5988
|
+
const ac = new AbortController();
|
|
5989
|
+
const t = setTimeout(() => ac.abort(), POST_TIMEOUT_MS);
|
|
5990
|
+
try {
|
|
5991
|
+
await fetch(url, { method: "POST", signal: ac.signal });
|
|
5992
|
+
} finally {
|
|
5993
|
+
clearTimeout(t);
|
|
5994
|
+
}
|
|
5995
|
+
}
|
|
5497
5996
|
var DefaultSessionRewinder = class {
|
|
5498
5997
|
constructor(sessionsDir, projectRoot) {
|
|
5499
5998
|
this.sessionsDir = sessionsDir;
|
|
@@ -5502,7 +6001,7 @@ var DefaultSessionRewinder = class {
|
|
|
5502
6001
|
sessionsDir;
|
|
5503
6002
|
projectRoot;
|
|
5504
6003
|
async listCheckpoints(sessionId) {
|
|
5505
|
-
const file =
|
|
6004
|
+
const file = path2.join(this.sessionsDir, `${sessionId}.jsonl`);
|
|
5506
6005
|
const raw = await fsp.readFile(file, "utf8");
|
|
5507
6006
|
const events = parseEvents(raw);
|
|
5508
6007
|
const fileCountMap = /* @__PURE__ */ new Map();
|
|
@@ -5527,7 +6026,7 @@ var DefaultSessionRewinder = class {
|
|
|
5527
6026
|
return checkpoints;
|
|
5528
6027
|
}
|
|
5529
6028
|
async rewindToCheckpoint(sessionId, checkpointIndex) {
|
|
5530
|
-
const file =
|
|
6029
|
+
const file = path2.join(this.sessionsDir, `${sessionId}.jsonl`);
|
|
5531
6030
|
const raw = await fsp.readFile(file, "utf8");
|
|
5532
6031
|
const events = parseEvents(raw);
|
|
5533
6032
|
let targetIdx = -1;
|
|
@@ -5566,7 +6065,7 @@ var DefaultSessionRewinder = class {
|
|
|
5566
6065
|
return { ...result, toPromptIndex: checkpointIndex, removedEvents };
|
|
5567
6066
|
}
|
|
5568
6067
|
async rewindLastN(sessionId, n) {
|
|
5569
|
-
const file =
|
|
6068
|
+
const file = path2.join(this.sessionsDir, `${sessionId}.jsonl`);
|
|
5570
6069
|
const raw = await fsp.readFile(file, "utf8");
|
|
5571
6070
|
const events = parseEvents(raw);
|
|
5572
6071
|
const checkpoints = [];
|
|
@@ -5595,7 +6094,7 @@ var DefaultSessionRewinder = class {
|
|
|
5595
6094
|
return { ...result, toPromptIndex: targetIndex, removedEvents: snapshotsToRevert.length };
|
|
5596
6095
|
}
|
|
5597
6096
|
async rewindToStart(sessionId) {
|
|
5598
|
-
const file =
|
|
6097
|
+
const file = path2.join(this.sessionsDir, `${sessionId}.jsonl`);
|
|
5599
6098
|
const raw = await fsp.readFile(file, "utf8");
|
|
5600
6099
|
const events = parseEvents(raw);
|
|
5601
6100
|
const allSnapshots = [];
|
|
@@ -5631,10 +6130,10 @@ async function revertSnapshots(snapshots, projectRoot) {
|
|
|
5631
6130
|
for (const snapshot of snapshots) {
|
|
5632
6131
|
for (const file of snapshot.files) {
|
|
5633
6132
|
try {
|
|
5634
|
-
const absPath =
|
|
5635
|
-
const root =
|
|
5636
|
-
const rel =
|
|
5637
|
-
if (rel.startsWith("..") ||
|
|
6133
|
+
const absPath = path2.resolve(file.path);
|
|
6134
|
+
const root = path2.resolve(projectRoot);
|
|
6135
|
+
const rel = path2.relative(root, absPath);
|
|
6136
|
+
if (rel.startsWith("..") || path2.isAbsolute(rel)) {
|
|
5638
6137
|
errors.push(`${file.path}: path resolves outside project root \u2014 skipping`);
|
|
5639
6138
|
continue;
|
|
5640
6139
|
}
|
|
@@ -5653,7 +6152,7 @@ async function revertSnapshots(snapshots, projectRoot) {
|
|
|
5653
6152
|
}
|
|
5654
6153
|
}
|
|
5655
6154
|
} catch (err) {
|
|
5656
|
-
errors.push(`${file.path}: ${
|
|
6155
|
+
errors.push(`${file.path}: ${toErrorMessage(err)}`);
|
|
5657
6156
|
}
|
|
5658
6157
|
}
|
|
5659
6158
|
}
|
|
@@ -5671,7 +6170,7 @@ async function loadTodosCheckpoint(filePath, events, traceId) {
|
|
|
5671
6170
|
filePath,
|
|
5672
6171
|
operation: "load",
|
|
5673
6172
|
outcome: "failure",
|
|
5674
|
-
error:
|
|
6173
|
+
error: toErrorMessage(err),
|
|
5675
6174
|
recoverable: true
|
|
5676
6175
|
});
|
|
5677
6176
|
return null;
|
|
@@ -5743,13 +6242,13 @@ async function saveTodosCheckpoint(filePath, sessionId, todos, events, traceId)
|
|
|
5743
6242
|
filePath,
|
|
5744
6243
|
operation: "save",
|
|
5745
6244
|
outcome: "failure",
|
|
5746
|
-
error:
|
|
6245
|
+
error: toErrorMessage(err),
|
|
5747
6246
|
recoverable: false
|
|
5748
6247
|
});
|
|
5749
6248
|
console.warn(JSON.stringify({
|
|
5750
6249
|
level: "warn",
|
|
5751
6250
|
event: "todos_checkpoint.save_failed",
|
|
5752
|
-
message:
|
|
6251
|
+
message: toErrorMessage(err),
|
|
5753
6252
|
timestamp: (/* @__PURE__ */ new Date()).toISOString()
|
|
5754
6253
|
}));
|
|
5755
6254
|
}
|
|
@@ -5760,7 +6259,7 @@ function attachTodosCheckpoint(state, filePath, sessionId, events, traceId) {
|
|
|
5760
6259
|
let writeChain = Promise.resolve();
|
|
5761
6260
|
const enqueueWrite = (todos) => {
|
|
5762
6261
|
writeChain = writeChain.then(() => saveTodosCheckpoint(filePath, sessionId, todos, events, traceId)).catch((err) => {
|
|
5763
|
-
const msg =
|
|
6262
|
+
const msg = toErrorMessage(err);
|
|
5764
6263
|
console.error(JSON.stringify({
|
|
5765
6264
|
level: "error",
|
|
5766
6265
|
event: "todos_checkpoint.write_chain_failed",
|
|
@@ -5809,7 +6308,7 @@ async function loadPlan(filePath, events) {
|
|
|
5809
6308
|
store: "plan",
|
|
5810
6309
|
filePath,
|
|
5811
6310
|
operation: "load",
|
|
5812
|
-
error:
|
|
6311
|
+
error: toErrorMessage(err),
|
|
5813
6312
|
recoverable: true
|
|
5814
6313
|
});
|
|
5815
6314
|
return null;
|
|
@@ -5862,19 +6361,21 @@ async function savePlan(filePath, plan, events) {
|
|
|
5862
6361
|
outcome: "success",
|
|
5863
6362
|
durationMs: Date.now() - t0
|
|
5864
6363
|
});
|
|
6364
|
+
return true;
|
|
5865
6365
|
} catch (err) {
|
|
5866
6366
|
events?.emit("storage.error", {
|
|
5867
6367
|
sessionId: "~boot~",
|
|
5868
6368
|
store: "plan",
|
|
5869
6369
|
filePath,
|
|
5870
6370
|
operation: "save",
|
|
5871
|
-
error:
|
|
6371
|
+
error: toErrorMessage(err),
|
|
5872
6372
|
recoverable: false
|
|
5873
6373
|
});
|
|
5874
6374
|
console.warn(
|
|
5875
6375
|
"[plan-store] save failed:",
|
|
5876
|
-
|
|
6376
|
+
toErrorMessage(err)
|
|
5877
6377
|
);
|
|
6378
|
+
return false;
|
|
5878
6379
|
}
|
|
5879
6380
|
}
|
|
5880
6381
|
function emptyPlan(sessionId, title) {
|
|
@@ -5976,7 +6477,10 @@ async function mutatePlan(filePath, sessionId, fn) {
|
|
|
5976
6477
|
return withFileLock(filePath, async () => {
|
|
5977
6478
|
const plan = await loadPlan(filePath) ?? emptyPlan(sessionId);
|
|
5978
6479
|
const updated = await fn(plan);
|
|
5979
|
-
await savePlan(filePath, updated);
|
|
6480
|
+
const persisted = await savePlan(filePath, updated);
|
|
6481
|
+
if (!persisted) {
|
|
6482
|
+
throw new Error(`Failed to persist plan to ${filePath} \u2014 the change was NOT saved.`);
|
|
6483
|
+
}
|
|
5980
6484
|
return updated;
|
|
5981
6485
|
});
|
|
5982
6486
|
}
|
|
@@ -6116,7 +6620,7 @@ async function loadTasks(filePath, events, traceId) {
|
|
|
6116
6620
|
filePath,
|
|
6117
6621
|
operation: "load",
|
|
6118
6622
|
outcome: "failure",
|
|
6119
|
-
error:
|
|
6623
|
+
error: toErrorMessage(err),
|
|
6120
6624
|
recoverable: true
|
|
6121
6625
|
});
|
|
6122
6626
|
return null;
|
|
@@ -6174,6 +6678,7 @@ async function saveTasks(filePath, tasks, events, traceId) {
|
|
|
6174
6678
|
durationMs: Date.now() - t0,
|
|
6175
6679
|
...traceId !== void 0 && { traceId }
|
|
6176
6680
|
});
|
|
6681
|
+
return true;
|
|
6177
6682
|
} catch (err) {
|
|
6178
6683
|
events?.emit("storage.error", {
|
|
6179
6684
|
sessionId: traceId ?? "~boot~",
|
|
@@ -6181,21 +6686,25 @@ async function saveTasks(filePath, tasks, events, traceId) {
|
|
|
6181
6686
|
filePath,
|
|
6182
6687
|
operation: "save",
|
|
6183
6688
|
outcome: "failure",
|
|
6184
|
-
error:
|
|
6689
|
+
error: toErrorMessage(err),
|
|
6185
6690
|
recoverable: false,
|
|
6186
6691
|
...traceId !== void 0 && { traceId }
|
|
6187
6692
|
});
|
|
6188
6693
|
console.warn(
|
|
6189
6694
|
"[task-store] save failed:",
|
|
6190
|
-
|
|
6695
|
+
toErrorMessage(err)
|
|
6191
6696
|
);
|
|
6697
|
+
return false;
|
|
6192
6698
|
}
|
|
6193
6699
|
}
|
|
6194
6700
|
async function mutateTasks(filePath, sessionId, fn, events, traceId) {
|
|
6195
6701
|
return withFileLock(filePath, async () => {
|
|
6196
6702
|
const file = await loadTasks(filePath, events, traceId) ?? emptyTaskFile(sessionId);
|
|
6197
6703
|
const updated = await fn(file);
|
|
6198
|
-
await saveTasks(filePath, updated, events, traceId);
|
|
6704
|
+
const persisted = await saveTasks(filePath, updated, events, traceId);
|
|
6705
|
+
if (!persisted) {
|
|
6706
|
+
throw new Error(`Failed to persist tasks to ${filePath} \u2014 the change was NOT saved.`);
|
|
6707
|
+
}
|
|
6199
6708
|
return updated;
|
|
6200
6709
|
});
|
|
6201
6710
|
}
|
|
@@ -6366,7 +6875,7 @@ var DirectorStateCheckpoint = class {
|
|
|
6366
6875
|
} catch (err) {
|
|
6367
6876
|
console.warn(
|
|
6368
6877
|
"[director-state] checkpoint write failed:",
|
|
6369
|
-
|
|
6878
|
+
toErrorMessage(err)
|
|
6370
6879
|
);
|
|
6371
6880
|
} finally {
|
|
6372
6881
|
this.writing = false;
|
|
@@ -6377,102 +6886,6 @@ var DirectorStateCheckpoint = class {
|
|
|
6377
6886
|
}
|
|
6378
6887
|
}
|
|
6379
6888
|
};
|
|
6380
|
-
|
|
6381
|
-
// src/utils/term.ts
|
|
6382
|
-
var hasStdout = () => typeof process !== "undefined" && !!process.stdout;
|
|
6383
|
-
function isStdoutTTY() {
|
|
6384
|
-
return hasStdout() && Boolean(process.stdout.isTTY);
|
|
6385
|
-
}
|
|
6386
|
-
|
|
6387
|
-
// src/utils/color.ts
|
|
6388
|
-
var isColorTty = () => {
|
|
6389
|
-
if (envFlag(process.env.NO_COLOR)) return false;
|
|
6390
|
-
if (envFlag(process.env.FORCE_COLOR)) return true;
|
|
6391
|
-
return isStdoutTTY();
|
|
6392
|
-
};
|
|
6393
|
-
function envFlag(value) {
|
|
6394
|
-
if (value === void 0) return false;
|
|
6395
|
-
if (value.trim() === "") return false;
|
|
6396
|
-
return !/^(0|false|no|off)$/i.test(value.trim());
|
|
6397
|
-
}
|
|
6398
|
-
var COLOR = isColorTty();
|
|
6399
|
-
var wrap = (open6, close) => (s) => COLOR ? `\x1B[${open6}m${s}\x1B[${close}m` : s;
|
|
6400
|
-
var color = {
|
|
6401
|
-
reset: wrap("0", "0"),
|
|
6402
|
-
bold: wrap("1", "22"),
|
|
6403
|
-
dim: wrap("2", "22"),
|
|
6404
|
-
italic: wrap("3", "23"),
|
|
6405
|
-
underline: wrap("4", "24"),
|
|
6406
|
-
red: wrap("31", "39"),
|
|
6407
|
-
green: wrap("32", "39"),
|
|
6408
|
-
yellow: wrap("33", "39"),
|
|
6409
|
-
blue: wrap("34", "39"),
|
|
6410
|
-
magenta: wrap("35", "39"),
|
|
6411
|
-
cyan: wrap("36", "39"),
|
|
6412
|
-
gray: wrap("90", "39"),
|
|
6413
|
-
amber: wrap("38;5;214", "39"),
|
|
6414
|
-
pink: wrap("38;5;205", "39"),
|
|
6415
|
-
bgRed: wrap("41", "49"),
|
|
6416
|
-
bgGreen: wrap("42", "49")
|
|
6417
|
-
};
|
|
6418
|
-
function projectHash(absRoot) {
|
|
6419
|
-
return createHash("sha256").update(path13.resolve(absRoot)).digest("hex").slice(0, 12);
|
|
6420
|
-
}
|
|
6421
|
-
function projectSlug(absRoot) {
|
|
6422
|
-
const base = slugify(path13.basename(absRoot));
|
|
6423
|
-
const hash = createHash("sha256").update(path13.resolve(absRoot)).digest("hex").slice(0, 6);
|
|
6424
|
-
return `${base}-${hash}`;
|
|
6425
|
-
}
|
|
6426
|
-
function slugify(name) {
|
|
6427
|
-
return name.toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-+|-+$/g, "").slice(0, 40) || "project";
|
|
6428
|
-
}
|
|
6429
|
-
function wstackGlobalRoot() {
|
|
6430
|
-
const fromEnv = process.env["WRONGSTACK_HOME"];
|
|
6431
|
-
if (fromEnv && fromEnv.trim().length > 0) return path13.resolve(fromEnv);
|
|
6432
|
-
return path13.join(os.homedir(), ".wrongstack");
|
|
6433
|
-
}
|
|
6434
|
-
function resolveWstackPaths(opts) {
|
|
6435
|
-
const globalRoot = opts.globalRoot ?? (opts.userHome ? path13.join(opts.userHome, ".wrongstack") : wstackGlobalRoot());
|
|
6436
|
-
const hash = projectHash(opts.projectRoot);
|
|
6437
|
-
const slug = projectSlug(opts.projectRoot);
|
|
6438
|
-
const projectDir = path13.join(globalRoot, "projects", slug);
|
|
6439
|
-
return {
|
|
6440
|
-
globalRoot,
|
|
6441
|
-
configDir: globalRoot,
|
|
6442
|
-
globalConfig: path13.join(globalRoot, "config.json"),
|
|
6443
|
-
secretsKey: path13.join(globalRoot, ".key"),
|
|
6444
|
-
globalMemory: path13.join(globalRoot, "memory.md"),
|
|
6445
|
-
globalSkills: path13.join(globalRoot, "skills"),
|
|
6446
|
-
globalPrompts: path13.join(globalRoot, "prompts"),
|
|
6447
|
-
cacheDir: path13.join(globalRoot, "cache"),
|
|
6448
|
-
modelsCache: path13.join(globalRoot, "cache", "models.dev.json"),
|
|
6449
|
-
modelsOverlayCache: path13.join(globalRoot, "cache", "models-overlay.json"),
|
|
6450
|
-
historyFile: path13.join(globalRoot, "history"),
|
|
6451
|
-
logFile: path13.join(globalRoot, "logs", "wrongstack.log"),
|
|
6452
|
-
projectDir,
|
|
6453
|
-
projectCodebaseIndex: path13.join(projectDir, "codebase-index"),
|
|
6454
|
-
projectMemory: path13.join(projectDir, "memory.md"),
|
|
6455
|
-
projectSessions: path13.join(projectDir, "sessions"),
|
|
6456
|
-
projectTrust: path13.join(projectDir, "trust.json"),
|
|
6457
|
-
projectMeta: path13.join(projectDir, "meta.json"),
|
|
6458
|
-
projectLocalConfig: path13.join(projectDir, "config.local.json"),
|
|
6459
|
-
inProjectConfig: path13.join(opts.projectRoot, ".wrongstack", "config.json"),
|
|
6460
|
-
inProjectAgentsFile: path13.join(opts.projectRoot, ".wrongstack", "AGENTS.md"),
|
|
6461
|
-
inProjectSkills: path13.join(opts.projectRoot, ".wrongstack", "skills"),
|
|
6462
|
-
inProjectWorktrees: path13.join(opts.projectRoot, ".wrongstack", "worktrees"),
|
|
6463
|
-
projectHash: hash,
|
|
6464
|
-
projectSlug: slug,
|
|
6465
|
-
projectGoal: path13.join(projectDir, "goal.json"),
|
|
6466
|
-
projectSpecs: path13.join(projectDir, "specs"),
|
|
6467
|
-
projectTaskGraphs: path13.join(projectDir, "task-graphs"),
|
|
6468
|
-
projectSddSession: path13.join(projectDir, "sdd-session.json"),
|
|
6469
|
-
projectPlan: path13.join(projectDir, "plan.json"),
|
|
6470
|
-
projectAutophase: path13.join(projectDir, "autophase"),
|
|
6471
|
-
syncConfig: path13.join(globalRoot, "sync.json")
|
|
6472
|
-
};
|
|
6473
|
-
}
|
|
6474
|
-
|
|
6475
|
-
// src/storage/goal-store.ts
|
|
6476
6889
|
var MAX_JOURNAL_ENTRIES = 500;
|
|
6477
6890
|
function goalFilePath(projectRoot) {
|
|
6478
6891
|
return resolveWstackPaths({ projectRoot }).projectGoal;
|
|
@@ -6500,7 +6913,7 @@ async function loadGoal(filePath, events) {
|
|
|
6500
6913
|
store: "goal",
|
|
6501
6914
|
filePath,
|
|
6502
6915
|
operation: "load",
|
|
6503
|
-
error:
|
|
6916
|
+
error: toErrorMessage(err),
|
|
6504
6917
|
recoverable: false
|
|
6505
6918
|
});
|
|
6506
6919
|
throw err;
|
|
@@ -6573,11 +6986,11 @@ async function saveGoal(filePath, goal, events) {
|
|
|
6573
6986
|
store: "goal",
|
|
6574
6987
|
filePath,
|
|
6575
6988
|
operation: "save",
|
|
6576
|
-
error:
|
|
6989
|
+
error: toErrorMessage(err),
|
|
6577
6990
|
recoverable: false
|
|
6578
6991
|
});
|
|
6579
6992
|
throw new FsError({
|
|
6580
|
-
message:
|
|
6993
|
+
message: toErrorMessage(err),
|
|
6581
6994
|
code: ERROR_CODES.FS_ATOMIC_WRITE_FAILED,
|
|
6582
6995
|
path: filePath,
|
|
6583
6996
|
cause: err
|
|
@@ -6739,7 +7152,7 @@ var DefaultPromptStore = class {
|
|
|
6739
7152
|
if (!file.endsWith(".json")) continue;
|
|
6740
7153
|
try {
|
|
6741
7154
|
const raw = JSON.parse(
|
|
6742
|
-
await fsp.readFile(
|
|
7155
|
+
await fsp.readFile(path2.join(this.dir, file), "utf8")
|
|
6743
7156
|
);
|
|
6744
7157
|
entries.push(raw.entry);
|
|
6745
7158
|
} catch {
|
|
@@ -6752,7 +7165,7 @@ var DefaultPromptStore = class {
|
|
|
6752
7165
|
);
|
|
6753
7166
|
}
|
|
6754
7167
|
async get(id) {
|
|
6755
|
-
const file =
|
|
7168
|
+
const file = path2.join(this.dir, `${id}.json`);
|
|
6756
7169
|
try {
|
|
6757
7170
|
const raw = JSON.parse(await fsp.readFile(file, "utf8"));
|
|
6758
7171
|
return raw.entry;
|
|
@@ -6762,12 +7175,12 @@ var DefaultPromptStore = class {
|
|
|
6762
7175
|
}
|
|
6763
7176
|
async save(entry) {
|
|
6764
7177
|
await ensureDir(this.dir);
|
|
6765
|
-
const file =
|
|
7178
|
+
const file = path2.join(this.dir, `${entry.id}.json`);
|
|
6766
7179
|
const raw = { version: 1, entry };
|
|
6767
7180
|
await atomicWrite(file, JSON.stringify(raw, null, 2));
|
|
6768
7181
|
}
|
|
6769
7182
|
async delete(id) {
|
|
6770
|
-
const file =
|
|
7183
|
+
const file = path2.join(this.dir, `${id}.json`);
|
|
6771
7184
|
try {
|
|
6772
7185
|
await fsp.unlink(file);
|
|
6773
7186
|
return true;
|
|
@@ -6801,7 +7214,7 @@ var CloudSync = class {
|
|
|
6801
7214
|
this.paths = paths;
|
|
6802
7215
|
this.getConfig = getConfig;
|
|
6803
7216
|
this.setConfig = setConfig;
|
|
6804
|
-
this.statePath =
|
|
7217
|
+
this.statePath = path2.join(paths.globalRoot, "sync-state.json");
|
|
6805
7218
|
}
|
|
6806
7219
|
paths;
|
|
6807
7220
|
getConfig;
|
|
@@ -6908,7 +7321,7 @@ var CloudSync = class {
|
|
|
6908
7321
|
const rel = segments.slice(2).join("/");
|
|
6909
7322
|
const destPath = resolvePulledCategoryPath(cat, localPath, rel, entry.path);
|
|
6910
7323
|
const blobData = await this.getBlob(token, owner, repoName, entry.sha);
|
|
6911
|
-
await fsp.mkdir(
|
|
7324
|
+
await fsp.mkdir(path2.dirname(destPath), { recursive: true });
|
|
6912
7325
|
await fsp.writeFile(destPath, Buffer.from(blobData, "base64"));
|
|
6913
7326
|
}
|
|
6914
7327
|
const localRev = await this.hashLocalCategories(cfg.categories);
|
|
@@ -7015,12 +7428,12 @@ var CloudSync = class {
|
|
|
7015
7428
|
const localPath = this.categoryToPath(cat);
|
|
7016
7429
|
if (!localPath) continue;
|
|
7017
7430
|
try {
|
|
7018
|
-
const
|
|
7019
|
-
if (
|
|
7431
|
+
const stat7 = await fsp.stat(localPath);
|
|
7432
|
+
if (stat7.isDirectory()) {
|
|
7020
7433
|
const files = await this.walkDir(localPath, localPath);
|
|
7021
7434
|
for (const file of files) {
|
|
7022
7435
|
const content = await fsp.readFile(file, "utf8");
|
|
7023
|
-
const rel =
|
|
7436
|
+
const rel = path2.relative(localPath, file).replace(/\\/g, "/");
|
|
7024
7437
|
entries.push({ path: `data/${cat}/${rel}`, content, mode: "100644" });
|
|
7025
7438
|
hashes.push(content);
|
|
7026
7439
|
}
|
|
@@ -7041,8 +7454,8 @@ var CloudSync = class {
|
|
|
7041
7454
|
const localPath = this.categoryToPath(cat);
|
|
7042
7455
|
if (!localPath) continue;
|
|
7043
7456
|
try {
|
|
7044
|
-
const
|
|
7045
|
-
if (
|
|
7457
|
+
const stat7 = await fsp.stat(localPath);
|
|
7458
|
+
if (stat7.isDirectory()) {
|
|
7046
7459
|
const files = await this.walkDir(localPath, localPath);
|
|
7047
7460
|
for (const file of files) {
|
|
7048
7461
|
const content = await fsp.readFile(file);
|
|
@@ -7069,6 +7482,7 @@ var CloudSync = class {
|
|
|
7069
7482
|
return this.paths.globalMemory;
|
|
7070
7483
|
case "history":
|
|
7071
7484
|
return this.paths.historyFile;
|
|
7485
|
+
/* v8 ignore next -- unreachable: SyncCategory is exhaustively matched above */
|
|
7072
7486
|
default:
|
|
7073
7487
|
return null;
|
|
7074
7488
|
}
|
|
@@ -7077,7 +7491,7 @@ var CloudSync = class {
|
|
|
7077
7491
|
const results = [];
|
|
7078
7492
|
const entries = await fsp.readdir(dir, { withFileTypes: true });
|
|
7079
7493
|
for (const entry of entries) {
|
|
7080
|
-
const full =
|
|
7494
|
+
const full = path2.join(dir, entry.name);
|
|
7081
7495
|
if (entry.isDirectory()) {
|
|
7082
7496
|
results.push(...await this.walkDir(full, base));
|
|
7083
7497
|
} else {
|
|
@@ -7099,9 +7513,9 @@ function resolvePulledCategoryPath(cat, localPath, rel, remotePath) {
|
|
|
7099
7513
|
return localPath;
|
|
7100
7514
|
}
|
|
7101
7515
|
if (!rel) return localPath;
|
|
7102
|
-
const normalizedRel =
|
|
7103
|
-
const traversesUp = normalizedRel === ".." || normalizedRel.startsWith(`..${
|
|
7104
|
-
if (
|
|
7516
|
+
const normalizedRel = path2.normalize(rel);
|
|
7517
|
+
const traversesUp = normalizedRel === ".." || normalizedRel.startsWith(`..${path2.sep}`);
|
|
7518
|
+
if (path2.isAbsolute(normalizedRel) || traversesUp) {
|
|
7105
7519
|
throw new FsError({
|
|
7106
7520
|
message: `Refusing CloudSync path traversal: ${remotePath}`,
|
|
7107
7521
|
code: ERROR_CODES.FS_DELETE_FAILED,
|
|
@@ -7109,10 +7523,10 @@ function resolvePulledCategoryPath(cat, localPath, rel, remotePath) {
|
|
|
7109
7523
|
context: { reason: "path_traversal", normalizedRel }
|
|
7110
7524
|
});
|
|
7111
7525
|
}
|
|
7112
|
-
const dest =
|
|
7113
|
-
const root =
|
|
7114
|
-
const relative4 =
|
|
7115
|
-
if (relative4.startsWith("..") ||
|
|
7526
|
+
const dest = path2.resolve(localPath, normalizedRel);
|
|
7527
|
+
const root = path2.resolve(localPath);
|
|
7528
|
+
const relative4 = path2.relative(root, dest);
|
|
7529
|
+
if (relative4.startsWith("..") || path2.isAbsolute(relative4)) {
|
|
7116
7530
|
throw new FsError({
|
|
7117
7531
|
message: `Refusing CloudSync path outside category root: ${remotePath}`,
|
|
7118
7532
|
code: ERROR_CODES.FS_DELETE_FAILED,
|
|
@@ -7172,7 +7586,7 @@ function isAllowed(type, level) {
|
|
|
7172
7586
|
return true;
|
|
7173
7587
|
}
|
|
7174
7588
|
function createSessionEventBridge(writer, level = "standard", options = {}) {
|
|
7175
|
-
|
|
7589
|
+
let currentLevel = level ?? "standard";
|
|
7176
7590
|
const resolveWriter = typeof writer === "function" ? writer : () => writer;
|
|
7177
7591
|
const progressCounters = /* @__PURE__ */ new Map();
|
|
7178
7592
|
const toolProgressConfig = options.sampling?.toolProgress ?? {};
|
|
@@ -7193,14 +7607,19 @@ function createSessionEventBridge(writer, level = "standard", options = {}) {
|
|
|
7193
7607
|
return true;
|
|
7194
7608
|
}
|
|
7195
7609
|
return {
|
|
7196
|
-
level
|
|
7610
|
+
get level() {
|
|
7611
|
+
return currentLevel;
|
|
7612
|
+
},
|
|
7613
|
+
setAuditLevel(next) {
|
|
7614
|
+
currentLevel = next ?? "standard";
|
|
7615
|
+
},
|
|
7197
7616
|
allows(type) {
|
|
7198
|
-
return isAllowed(type,
|
|
7617
|
+
return isAllowed(type, currentLevel);
|
|
7199
7618
|
},
|
|
7200
7619
|
async append(event) {
|
|
7201
7620
|
const target = resolveWriter();
|
|
7202
7621
|
if (!target) return;
|
|
7203
|
-
if (!isAllowed(event.type,
|
|
7622
|
+
if (!isAllowed(event.type, currentLevel)) return;
|
|
7204
7623
|
if (!shouldSample(event)) return;
|
|
7205
7624
|
try {
|
|
7206
7625
|
await target.append(event);
|
|
@@ -7211,7 +7630,7 @@ function createSessionEventBridge(writer, level = "standard", options = {}) {
|
|
|
7211
7630
|
const target = resolveWriter();
|
|
7212
7631
|
if (!target || events.length === 0) return;
|
|
7213
7632
|
const allowed = events.filter(
|
|
7214
|
-
(e) => isAllowed(e.type,
|
|
7633
|
+
(e) => isAllowed(e.type, currentLevel) && shouldSample(e)
|
|
7215
7634
|
);
|
|
7216
7635
|
if (allowed.length === 0) return;
|
|
7217
7636
|
try {
|
|
@@ -7242,6 +7661,6 @@ function resolveSessionLoggingConfig(cfg) {
|
|
|
7242
7661
|
};
|
|
7243
7662
|
}
|
|
7244
7663
|
|
|
7245
|
-
export { ALL_SYNC_CATEGORIES, AgentStatusTracker, AnnotationsStore, CORE_RECONSTRUCT_EVENTS, CloudSync, ConfigMigrationError, DEFAULT_CONFIG_MIGRATIONS, DefaultAttachmentStore, DefaultConfigLoader, DefaultConfigStore, DefaultMemoryStore, DefaultPromptStore, DefaultSessionReader, DefaultSessionRewinder, DefaultSessionStore, DirectorStateCheckpoint, FileMemoryBackend, GraphMemoryBackend, MAX_JOURNAL_ENTRIES, MAX_PROGRESS_HISTORY, QueueStore, RecoveryLock, ReplayLogStore, STANDARD_AUDIT_EVENTS, SessionAnalyzer, SessionMemoryConsolidator, SessionRecovery, SessionRegistry, ToolAuditLog, addPlanItem, appendJournal, attachPlanCheckpoint, attachTodosCheckpoint, clearPlan, createSessionEventBridge, deriveTodosFromPlanItem, emptyGoal, emptyPlan, emptyTaskFile, formatGoal, formatPlan, formatPlanTemplates, getPlanTemplate, getSessionRegistry, goalFilePath, hasSessionRegistry, listPlanTemplates, loadDirectorState, loadGoal, loadPlan, loadTasks, loadTodosCheckpoint, mutatePlan, mutateTasks, parseEntries, parseProgressFromText, recordProgress, removePlanItem, resolveAuditLevel, resolveSessionLoggingConfig, runConfigMigrations, saveGoal, savePlan, saveTasks, saveTodosCheckpoint, setPlanItemStatus, setProgress, summarizeUsage };
|
|
7664
|
+
export { ALL_SYNC_CATEGORIES, AgentStatusTracker, AnnotationsStore, CORE_RECONSTRUCT_EVENTS, CloudSync, ConfigMigrationError, DEFAULT_CONFIG_MIGRATIONS, DefaultAttachmentStore, DefaultConfigLoader, DefaultConfigStore, DefaultMemoryStore, DefaultPromptStore, DefaultSessionReader, DefaultSessionRewinder, DefaultSessionStore, DirectorStateCheckpoint, FileMemoryBackend, FleetNotifier, GraphMemoryBackend, MAX_JOURNAL_ENTRIES, MAX_PROGRESS_HISTORY, QueueStore, RecoveryLock, ReplayLogStore, STANDARD_AUDIT_EVENTS, SessionAnalyzer, SessionMemoryConsolidator, SessionRecovery, SessionRegistry, ToolAuditLog, addPlanItem, appendJournal, attachPlanCheckpoint, attachTodosCheckpoint, clearPlan, createSessionEventBridge, deriveTodosFromPlanItem, emptyGoal, emptyPlan, emptyTaskFile, formatGoal, formatPlan, formatPlanTemplates, getPlanTemplate, getSessionRegistry, goalFilePath, hasSessionRegistry, listPlanTemplates, loadDirectorState, loadGoal, loadPlan, loadTasks, loadTodosCheckpoint, mutatePlan, mutateTasks, parseEntries, parseProgressFromText, recordProgress, removePlanItem, resolveAuditLevel, resolveSessionLoggingConfig, runConfigMigrations, saveGoal, savePlan, saveTasks, saveTodosCheckpoint, setPlanItemStatus, setProgress, summarizeUsage };
|
|
7246
7665
|
//# sourceMappingURL=index.js.map
|
|
7247
7666
|
//# sourceMappingURL=index.js.map
|