@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.
Files changed (99) hide show
  1. package/dist/{agent-bridge-BbskZ7HH.d.ts → agent-bridge-DrkBxszZ.d.ts} +1 -1
  2. package/dist/{agent-subagent-runner-BNIGZx18.d.ts → agent-subagent-runner-DM2pP-B6.d.ts} +116 -12
  3. package/dist/{brain-C2yDd7Lw.d.ts → brain-BXd_61kQ.d.ts} +32 -3
  4. package/dist/{compactor-t0R_AIt_.d.ts → compactor-B8pOf45Y.d.ts} +1 -1
  5. package/dist/{config-FG6As4H5.d.ts → config-BMCj_XDs.d.ts} +86 -12
  6. package/dist/{context-JFOVvu6z.d.ts → context-MRk5PhNv.d.ts} +26 -1
  7. package/dist/coordination/index.d.ts +1737 -15
  8. package/dist/coordination/index.js +3152 -494
  9. package/dist/coordination/index.js.map +1 -1
  10. package/dist/{default-config-CXsDvOmP.d.ts → default-config-B0cj-Hry.d.ts} +11 -1
  11. package/dist/defaults/index.d.ts +28 -28
  12. package/dist/defaults/index.js +1804 -1363
  13. package/dist/defaults/index.js.map +1 -1
  14. package/dist/dispatcher-types.d-BBeXBQgS.d.ts +66 -0
  15. package/dist/execution/index.d.ts +16 -16
  16. package/dist/execution/index.js +933 -672
  17. package/dist/execution/index.js.map +1 -1
  18. package/dist/execution/prompt-enhancer.d.ts +1 -1
  19. package/dist/execution/prompt-enhancer.js +7 -1
  20. package/dist/execution/prompt-enhancer.js.map +1 -1
  21. package/dist/extension/index.d.ts +6 -6
  22. package/dist/extension/index.js.map +1 -1
  23. package/dist/{goal-preamble-B1IXJtLX.d.ts → goal-preamble-DvHDSKSe.d.ts} +26 -10
  24. package/dist/{goal-store-CPXz6Mml.d.ts → goal-store-DtLMySNb.d.ts} +1 -1
  25. package/dist/{index-CebbJB94.d.ts → index-B-ch8K9C.d.ts} +8 -8
  26. package/dist/{index-BPcg4N3M.d.ts → index-CEDeNodM.d.ts} +5 -5
  27. package/dist/index.d.ts +189 -104
  28. package/dist/index.js +24693 -21162
  29. package/dist/index.js.map +1 -1
  30. package/dist/infrastructure/index.d.ts +6 -6
  31. package/dist/infrastructure/index.js +12 -8
  32. package/dist/infrastructure/index.js.map +1 -1
  33. package/dist/kernel/index.d.ts +9 -9
  34. package/dist/kernel/index.js +7 -2
  35. package/dist/kernel/index.js.map +1 -1
  36. package/dist/{llm-selector-DXxI2tlu.d.ts → llm-selector-C0tfTCUe.d.ts} +14 -2
  37. package/dist/{mcp-servers-OwNHo43-.d.ts → mcp-servers-2x4w6Jn9.d.ts} +3 -3
  38. package/dist/models/index.d.ts +5 -5
  39. package/dist/models/index.js +80 -31
  40. package/dist/models/index.js.map +1 -1
  41. package/dist/{models-registry-Djlmq4uB.d.ts → models-registry-DmJlKuNp.d.ts} +1 -1
  42. package/dist/{multi-agent-coordinator-CEmrSCMJ.d.ts → multi-agent-coordinator-DyCkCZnU.d.ts} +2 -2
  43. package/dist/{null-fleet-bus-DT92xqgJ.d.ts → null-fleet-bus-CG9QY2aP.d.ts} +6 -6
  44. package/dist/observability/index.d.ts +2 -2
  45. package/dist/observability/index.js +8 -3
  46. package/dist/observability/index.js.map +1 -1
  47. package/dist/{parallel-eternal-engine-0SItuq5r.d.ts → parallel-eternal-engine-Jw9uhEoT.d.ts} +9 -9
  48. package/dist/{path-resolver-DKBh6Jlo.d.ts → path-resolver-Dy2ej-gE.d.ts} +3 -3
  49. package/dist/{permission-BJ7eO9Vl.d.ts → permission-B9SB45lp.d.ts} +1 -1
  50. package/dist/{permission-policy-DEXOfnpm.d.ts → permission-policy-CkjSXabK.d.ts} +2 -2
  51. package/dist/{pipeline-zflkI2dp.d.ts → pipeline-DPDxH_7m.d.ts} +59 -4
  52. package/dist/{plan-templates-BFXyRkEK.d.ts → plan-templates-CzD9GnAU.d.ts} +32 -8
  53. package/dist/{provider-runner-BC-uywtT.d.ts → provider-runner-DMa70ODu.d.ts} +3 -3
  54. package/dist/{retry-policy-Cavrzmtk.d.ts → retry-policy-CN0khdlj.d.ts} +1 -1
  55. package/dist/sdd/index.d.ts +8 -8
  56. package/dist/sdd/index.js +313 -122
  57. package/dist/sdd/index.js.map +1 -1
  58. package/dist/{secret-vault-CDvDYXWX.d.ts → secret-vault-B2yw84VT.d.ts} +43 -4
  59. package/dist/secret-vault-BAKpgFw_.d.ts +57 -0
  60. package/dist/security/index.d.ts +5 -5
  61. package/dist/security/index.js +411 -225
  62. package/dist/security/index.js.map +1 -1
  63. package/dist/{selector-B7AivHsu.d.ts → selector-CzHh_igB.d.ts} +1 -1
  64. package/dist/{session-event-bridge-BmIDxdJd.d.ts → session-event-bridge-BUI6Jf-4.d.ts} +8 -2
  65. package/dist/{session-reader-DtofsB-2.d.ts → session-reader-CMgdMSRP.d.ts} +1 -1
  66. package/dist/skills/index.js +67 -64
  67. package/dist/skills/index.js.map +1 -1
  68. package/dist/storage/index.d.ts +132 -16
  69. package/dist/storage/index.js +851 -432
  70. package/dist/storage/index.js.map +1 -1
  71. package/dist/tools/index.d.ts +57 -0
  72. package/dist/tools/index.js +411 -0
  73. package/dist/tools/index.js.map +1 -0
  74. package/dist/types/index.d.ts +21 -21
  75. package/dist/types/index.js +928 -711
  76. package/dist/types/index.js.map +1 -1
  77. package/dist/utils/error.d.ts +7 -0
  78. package/dist/utils/error.js +8 -0
  79. package/dist/utils/error.js.map +1 -0
  80. package/dist/utils/index.d.ts +8 -68
  81. package/dist/utils/index.js +20 -10
  82. package/dist/utils/index.js.map +1 -1
  83. package/dist/{wstack-paths-CJjEwPXn.d.ts → wstack-paths-hOpNLmvf.d.ts} +2 -0
  84. package/package.json +5 -1
  85. package/skills/api-design/SKILL.md +1 -1
  86. package/skills/audit-log/SKILL.md +6 -6
  87. package/skills/bug-hunter/SKILL.md +5 -5
  88. package/skills/chimera/SKILL.md +4 -4
  89. package/skills/docker-deploy/SKILL.md +1 -1
  90. package/skills/git-flow/SKILL.md +3 -3
  91. package/skills/multi-agent/SKILL.md +3 -3
  92. package/skills/node-modern/SKILL.md +1 -0
  93. package/skills/observability/SKILL.md +2 -2
  94. package/skills/output-standards/SKILL.md +51 -28
  95. package/skills/refactor-planner/SKILL.md +3 -3
  96. package/skills/security-scanner/SKILL.md +4 -3
  97. package/skills/tech-stack/SKILL.md +1 -2
  98. package/dist/package-outdated-watcher-C70ag2G9.d.ts +0 -581
  99. package/dist/secret-vault-BJDY28ev.d.ts +0 -25
@@ -1,9 +1,9 @@
1
1
  import { randomBytes, randomUUID, createHash } from 'crypto';
2
2
  import * as fsp from 'fs/promises';
3
- import * as path13 from 'path';
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 = path13.dirname(targetPath);
18
+ const dir = path2.dirname(targetPath);
19
19
  await fsp.mkdir(dir, { recursive: true });
20
- const tmp = path13.join(dir, `.${path13.basename(targetPath)}.${randomBytes(6).toString("hex")}.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 stat5 = await fsp.stat(targetPath);
39
- mode = stat5.mode & 511;
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 = path13.dirname(targetPath);
59
+ const dir = path2.dirname(targetPath);
60
60
  await fsp.mkdir(dir, { recursive: true });
61
- const lockPath = path13.join(dir, `.${path13.basename(targetPath)}.lock`);
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
- if (err.code !== "EEXIST") throw err;
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 stat5 = await fsp.stat(lockPath);
75
- if (Date.now() - stat5.mtimeMs > staleMs) {
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((resolve5) => setTimeout(resolve5, 25));
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((resolve5) => setTimeout(resolve5, delays[i]));
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 path13.join(this.dir, "_index.jsonl");
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 path13.join(this.dir, `${id}${ext}`);
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 = path13.dirname(path13.join(this.dir, id));
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 = path13.join(shardDir, `${path13.basename(id)}.jsonl`);
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", err instanceof Error ? err.message : String(err), false);
537
+ this.emitError(id, file, "create", toErrorMessage(err), false);
300
538
  throw new Error(
301
- `Failed to open session file: ${err instanceof Error ? err.message : String(err)}`,
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", err instanceof Error ? err.message : String(err), true);
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", err instanceof Error ? err.message : String(err), false);
571
+ this.emitError(id, file, "resume", toErrorMessage(err), false);
334
572
  throw new Error(
335
- `Failed to open session "${id}" for append: ${err instanceof Error ? err.message : String(err)}`,
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: path13.dirname(file),
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", err instanceof Error ? err.message : String(err), true);
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
- return { metadata: meta, events, messages, usage, toolCallEnds };
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 = err instanceof Error ? err.message : String(err);
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 = err instanceof Error ? err.message : String(err);
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(path13.join(dir, entry.name), childPrefix, depth + 1));
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 stat5 = await fsp.stat(full);
570
- const summary = await this.summarize(id, stat5.mtime.toISOString());
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 = err instanceof Error ? err.message : String(err);
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 = path13.dirname(path13.join(this.dir, id));
601
- const base = path13.basename(id);
602
- const sessDir = path13.join(shardDir, base);
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(path13.join(shardDir, `${base}.plan.json`)),
607
- fsp.unlink(path13.join(shardDir, `${base}.todos.json`))
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: err instanceof Error ? err.message : String(err),
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(path13.join(this.dir, "active.json"), "utf8");
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 = path13.join(dir, name);
918
+ const jsonlPath = path2.join(dir, name);
651
919
  try {
652
- const stat5 = await fsp.stat(jsonlPath);
653
- if (stat5.mtimeMs >= cutoff) return;
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 = path13.join(this.dir, entry.name);
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 = path13.join(this.dir, entry.name);
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 ? path13.join(opts.dir, `${path13.basename(id)}.summary.json`) : "";
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 = err instanceof Error ? err.message : String(err);
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
- err instanceof Error ? err.message : String(err),
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 = err instanceof Error ? err.message : String(err);
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 = err instanceof Error ? err.message : String(err);
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 = path13.join(opts.dir, "queue.json");
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: err instanceof Error ? err.message : String(err),
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: err instanceof Error ? err.message : String(err),
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: err instanceof Error ? err.message : String(err),
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: err instanceof Error ? err.message : String(err),
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: err instanceof Error ? err.message : String(err),
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 = path13.join(this.spoolDir, `${id}.bin`);
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(path13.dirname(file));
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: err instanceof Error ? err.message : String(err),
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: err instanceof Error ? err.message : String(err),
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: err instanceof Error ? err.message : String(err),
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: err instanceof Error ? err.message : String(err),
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: err instanceof Error ? err.message : String(err),
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: err instanceof Error ? err.message : String(err),
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: err instanceof Error ? err.message : String(err),
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: err instanceof Error ? err.message : String(err),
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
- // src/utils/deep-merge.ts
2928
- var FORBIDDEN_PROTO_KEYS = /* @__PURE__ */ new Set([
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: err instanceof Error ? err.message : String(err),
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: err instanceof Error ? err.message : String(err),
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: err instanceof Error ? err.message : String(err),
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 = path13.join(opts.dir, LOCK_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(path13.dirname(this.file));
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 = path13.resolve(dir, `${sessionId}${suffix}`);
4023
- const rel = path13.relative(path13.resolve(dir), resolved);
4024
- if (rel.startsWith("..") || path13.isAbsolute(rel)) {
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: err instanceof Error ? err.message : String(err),
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: err instanceof Error ? err.message : String(err),
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: err instanceof Error ? err.message : String(err),
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(this.filePath(input.sessionId), async () => {
4368
- const entries = await this.readAll(input.sessionId);
4369
- if (entries.some((entry2) => entry2.hash === hash)) return;
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
- entries.push(entry);
4377
- const keep = entries.slice(-this.maxEntries);
4378
- const didEvict = keep.length < entries.length;
4379
- const cache = /* @__PURE__ */ new Map();
4380
- for (const e of keep) cache.set(e.hash, e);
4381
- this.cache.set(input.sessionId, cache);
4382
- await this.writeAll(input.sessionId, keep, didEvict ? "compact" : "record");
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: err instanceof Error ? err.message : String(err),
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(path13.join(dir, entry.name), entry.name, depth + 1);
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: path13.join(dir, entry.name)
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 stat5;
4772
+ let stat7;
4594
4773
  try {
4595
- stat5 = await fsp.stat(fp);
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 (stat5.size === 0) return null;
4601
- const position = Math.max(0, stat5.size - TAIL_SIZE);
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(path13.join(dir, entry.name), entry.name, depth + 1);
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 entries = await this.readAll(input.sessionId);
4761
- const prev = entries.at(-1);
4762
- const prevHash = prev?.hash ?? GENESIS_PREV;
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
- entries.push(entry);
4791
- await this.writeAll(input.sessionId, entries);
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: err instanceof Error ? err.message : String(err),
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: err instanceof Error ? err.message : String(err),
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: err instanceof Error ? err.message : String(err),
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
- async writeAll(sessionId, entries) {
4954
- const fp = this.filePath(sessionId);
4955
- const line = entries.map((e) => JSON.stringify(e)).join("\n") + (entries.length ? "\n" : "");
4956
- await atomicWrite(fp, line, { mode: 384 });
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 = path13.join(globalRoot, REGISTRY_FILE);
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
- const entry = registry[this.currentSessionId];
5172
- if (!entry) return;
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
- const hasRunning = agents.some((a) => a.status === "running" || a.status === "streaming");
5176
- const hasWaiting = agents.some((a) => a.status === "waiting_user");
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 = 5;
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(path13.dirname(this.filePath), { recursive: true });
5293
- const lockHandle = await fsp.open(lockPath, "wx").catch(() => null);
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
- await new Promise((r) => setTimeout(r, retryDelayMs * (attempt + 1)));
5296
- continue;
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("fleet.subagent.spawned", (_event, payload) => {
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?.subagentId) {
5412
- this.agents.set(p.subagentId, {
5413
- id: p.subagentId,
5414
- name: p.name ?? p.subagentId,
5415
- status: "idle",
5416
- iterations: 0,
5417
- toolCalls: 0,
5418
- lastActivityAt: (/* @__PURE__ */ new Date()).toISOString()
5419
- });
5420
- this.flush();
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("fleet.subagent.task_started", (_event, payload) => {
5771
+ this.events.onPattern("subagent.ctx_pct", (_e, payload) => {
5426
5772
  const p = payload;
5427
- if (p?.subagentId) {
5428
- const entry = this.agents.get(p.subagentId);
5429
- if (entry) {
5430
- entry.status = "running";
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("fleet.subagent.task_completed", (_event, payload) => {
5780
+ this.events.onPattern("subagent.task_started", (_e, payload) => {
5440
5781
  const p = payload;
5441
- if (p?.subagentId) {
5442
- const entry = this.agents.get(p.subagentId);
5443
- if (entry) {
5444
- entry.status = "idle";
5445
- entry.lastActivityAt = (/* @__PURE__ */ new Date()).toISOString();
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("fleet.subagent.error", (_event, payload) => {
5790
+ this.events.onPattern("subagent.tool_executed", (_e, payload) => {
5453
5791
  const p = payload;
5454
- if (p?.subagentId) {
5455
- const entry = this.agents.get(p.subagentId);
5456
- if (entry) {
5457
- entry.status = "error";
5458
- entry.lastActivityAt = (/* @__PURE__ */ new Date()).toISOString();
5459
- this.flush();
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("fleet.subagent.stopped", (_event, payload) => {
5801
+ this.events.onPattern("subagent.iteration_summary", (_e, payload) => {
5466
5802
  const p = payload;
5467
- if (p?.subagentId) {
5468
- this.agents.delete(p.subagentId);
5469
- this.flush();
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).catch(() => void 0);
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 = path13.join(this.sessionsDir, `${sessionId}.jsonl`);
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 = path13.join(this.sessionsDir, `${sessionId}.jsonl`);
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 = path13.join(this.sessionsDir, `${sessionId}.jsonl`);
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 = path13.join(this.sessionsDir, `${sessionId}.jsonl`);
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 = path13.resolve(file.path);
5635
- const root = path13.resolve(projectRoot);
5636
- const rel = path13.relative(root, absPath);
5637
- if (rel.startsWith("..") || path13.isAbsolute(rel)) {
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}: ${err instanceof Error ? err.message : String(err)}`);
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: err instanceof Error ? err.message : String(err),
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: err instanceof Error ? err.message : String(err),
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: err instanceof Error ? err.message : String(err),
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 = err instanceof Error ? err.message : String(err);
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: err instanceof Error ? err.message : String(err),
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: err instanceof Error ? err.message : String(err),
6371
+ error: toErrorMessage(err),
5872
6372
  recoverable: false
5873
6373
  });
5874
6374
  console.warn(
5875
6375
  "[plan-store] save failed:",
5876
- err instanceof Error ? err.message : String(err)
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: err instanceof Error ? err.message : String(err),
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: err instanceof Error ? err.message : String(err),
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
- err instanceof Error ? err.message : String(err)
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
- err instanceof Error ? err.message : String(err)
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: err instanceof Error ? err.message : String(err),
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: err instanceof Error ? err.message : String(err),
6989
+ error: toErrorMessage(err),
6577
6990
  recoverable: false
6578
6991
  });
6579
6992
  throw new FsError({
6580
- message: err instanceof Error ? err.message : String(err),
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(path13.join(this.dir, file), "utf8")
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 = path13.join(this.dir, `${id}.json`);
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 = path13.join(this.dir, `${entry.id}.json`);
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 = path13.join(this.dir, `${id}.json`);
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 = path13.join(paths.globalRoot, "sync-state.json");
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(path13.dirname(destPath), { recursive: true });
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 stat5 = await fsp.stat(localPath);
7019
- if (stat5.isDirectory()) {
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 = path13.relative(localPath, file).replace(/\\/g, "/");
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 stat5 = await fsp.stat(localPath);
7045
- if (stat5.isDirectory()) {
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 = path13.join(dir, entry.name);
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 = path13.normalize(rel);
7103
- const traversesUp = normalizedRel === ".." || normalizedRel.startsWith(`..${path13.sep}`);
7104
- if (path13.isAbsolute(normalizedRel) || traversesUp) {
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 = path13.resolve(localPath, normalizedRel);
7113
- const root = path13.resolve(localPath);
7114
- const relative4 = path13.relative(root, dest);
7115
- if (relative4.startsWith("..") || path13.isAbsolute(relative4)) {
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
- const normalizedLevel = level ?? "standard";
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: normalizedLevel,
7610
+ get level() {
7611
+ return currentLevel;
7612
+ },
7613
+ setAuditLevel(next) {
7614
+ currentLevel = next ?? "standard";
7615
+ },
7197
7616
  allows(type) {
7198
- return isAllowed(type, normalizedLevel);
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, normalizedLevel)) return;
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, normalizedLevel) && shouldSample(e)
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