@wrongstack/core 0.275.0 → 0.276.2

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 (83) hide show
  1. package/dist/{agent-bridge-D9JkPvJ0.d.ts → agent-bridge-D7A-eu3C.d.ts} +1 -1
  2. package/dist/{agent-subagent-runner-CArSFKFl.d.ts → agent-subagent-runner-CEuw4ATz.d.ts} +16 -10
  3. package/dist/{brain-DCkB5_e7.d.ts → brain-BLOyN5ZP.d.ts} +127 -1
  4. package/dist/{compactor-CzSvxM1g.d.ts → compactor-DcBpaJsI.d.ts} +1 -1
  5. package/dist/{config-BzFRKkg7.d.ts → config-Bf5mj-ad.d.ts} +20 -2
  6. package/dist/{context-BrLe8pJy.d.ts → context-CLnUMW5g.d.ts} +40 -2
  7. package/dist/coordination/index.d.ts +43 -24
  8. package/dist/coordination/index.js +849 -648
  9. package/dist/coordination/index.js.map +1 -1
  10. package/dist/defaults/index.d.ts +28 -28
  11. package/dist/defaults/index.js +1636 -845
  12. package/dist/defaults/index.js.map +1 -1
  13. package/dist/execution/index.d.ts +16 -16
  14. package/dist/execution/index.js +218 -49
  15. package/dist/execution/index.js.map +1 -1
  16. package/dist/execution/prompt-enhancer.d.ts +1 -1
  17. package/dist/extension/index.d.ts +7 -7
  18. package/dist/extension/index.js.map +1 -1
  19. package/dist/{global-mailbox-CXkugtNQ.d.ts → global-mailbox-Iqfkgmwu.d.ts} +3 -3
  20. package/dist/{goal-store-DUwdbdoY.d.ts → goal-store-DGb6b5Ed.d.ts} +1 -1
  21. package/dist/hq/index.d.ts +6 -6
  22. package/dist/hq/index.js +178 -75
  23. package/dist/hq/index.js.map +1 -1
  24. package/dist/{index-CtlizLTK.d.ts → index-Cn0NOshr.d.ts} +10 -5
  25. package/dist/{index-neOCEy6q.d.ts → index-L4RZN9jJ.d.ts} +2 -2
  26. package/dist/index.d.ts +56 -48
  27. package/dist/index.js +2789 -1546
  28. package/dist/index.js.map +1 -1
  29. package/dist/infrastructure/index.d.ts +6 -6
  30. package/dist/infrastructure/index.js +26 -7
  31. package/dist/infrastructure/index.js.map +1 -1
  32. package/dist/kernel/index.d.ts +20 -12
  33. package/dist/kernel/index.js +55 -9
  34. package/dist/kernel/index.js.map +1 -1
  35. package/dist/{mailbox-types-_7gaY0Rl.d.ts → mailbox-types-DTl7bRH3.d.ts} +3 -1
  36. package/dist/{mcp-servers-MLL6bMlv.d.ts → mcp-servers-CuZGf9fI.d.ts} +4 -4
  37. package/dist/models/index.d.ts +5 -5
  38. package/dist/models/index.js +223 -139
  39. package/dist/models/index.js.map +1 -1
  40. package/dist/{models-registry-CrkcxQ-g.d.ts → models-registry-8XOdxWQu.d.ts} +16 -1
  41. package/dist/{multi-agent-coordinator-Dc_HuG9p.d.ts → multi-agent-coordinator-CiRtKVTk.d.ts} +8 -1
  42. package/dist/{null-fleet-bus-BMZwMin7.d.ts → null-fleet-bus-d9G-bVy9.d.ts} +26 -22
  43. package/dist/observability/index.d.ts +2 -2
  44. package/dist/{path-resolver-uVK4BatM.d.ts → path-resolver-BhIb6mtd.d.ts} +8 -3
  45. package/dist/{permission-CJR1qfOi.d.ts → permission-BCbQDR2s.d.ts} +1 -1
  46. package/dist/{permission-policy-DLVKKk4w.d.ts → permission-policy-C0ikndX_.d.ts} +2 -18
  47. package/dist/{pipeline-BYR-Vdau.d.ts → pipeline-Dl6XbfE7.d.ts} +10 -6
  48. package/dist/{provider-model-resolve-iREK_1lG.d.ts → provider-model-resolve-B70epO19.d.ts} +3 -3
  49. package/dist/{provider-runner-i7SQXZuC.d.ts → provider-runner-DZ808MSM.d.ts} +3 -3
  50. package/dist/{retry-policy-BmY5ooh3.d.ts → retry-policy-Dt3_z8Aj.d.ts} +1 -1
  51. package/dist/sdd/index.d.ts +19 -10
  52. package/dist/sdd/index.js +411 -240
  53. package/dist/sdd/index.js.map +1 -1
  54. package/dist/{secret-vault-C9leEMzr.d.ts → secret-vault-BUJ2d1gB.d.ts} +1 -1
  55. package/dist/security/index.d.ts +5 -5
  56. package/dist/security/index.js +30 -6
  57. package/dist/security/index.js.map +1 -1
  58. package/dist/{selector-qjpee9BF.d.ts → selector-BCkWgdwy.d.ts} +1 -1
  59. package/dist/{session-event-bridge-m7y--I-H.d.ts → session-event-bridge-CMvIO59_.d.ts} +1 -1
  60. package/dist/{session-reader-BjLH4V9n.d.ts → session-reader-C8aiChUu.d.ts} +1 -1
  61. package/dist/skills/index.js +1 -0
  62. package/dist/skills/index.js.map +1 -1
  63. package/dist/storage/index.d.ts +68 -30
  64. package/dist/storage/index.js +839 -528
  65. package/dist/storage/index.js.map +1 -1
  66. package/dist/{strategy-compactor-C2bmlWYg.d.ts → strategy-compactor-DI1OHVbB.d.ts} +10 -10
  67. package/dist/{todos-checkpoint-oDS9IBNS.d.ts → todos-checkpoint-Ddd2CGr0.d.ts} +56 -9
  68. package/dist/{tool-executor-D4YdaJ-M.d.ts → tool-executor-Bmd5Ygoo.d.ts} +45 -10
  69. package/dist/tools/index.d.ts +2 -2
  70. package/dist/tools/index.js.map +1 -1
  71. package/dist/types/index.d.ts +20 -20
  72. package/dist/types/index.js +331 -98
  73. package/dist/types/index.js.map +1 -1
  74. package/dist/utils/index.d.ts +16 -3
  75. package/dist/utils/index.js +159 -83
  76. package/dist/utils/index.js.map +1 -1
  77. package/dist/{worktree-manager-A1Efnvs0.d.ts → worktree-manager-DBdl_5rs.d.ts} +4 -1
  78. package/instructions/agents/shadow-agent.md +3 -3
  79. package/instructions/coordination/director-preamble.md +3 -3
  80. package/instructions/modes/research-web.md +4 -4
  81. package/package.json +1 -1
  82. package/skills/research-web/SKILL.md +26 -26
  83. package/skills/research-web/SKILL.save.md +1 -1
@@ -1,3 +1,4 @@
1
+ import * as syncFs from 'fs';
1
2
  import { createReadStream, readFileSync, statSync } from 'fs';
2
3
  import * as fsp2 from 'fs/promises';
3
4
  import * as path2 from 'path';
@@ -8,123 +9,107 @@ import { hostname } from 'os';
8
9
  import { fileURLToPath } from 'url';
9
10
 
10
11
  // src/storage/session-store.ts
11
- async function atomicWrite(targetPath, content, opts = {}) {
12
- const dir = path2.dirname(targetPath);
13
- await fsp2.mkdir(dir, { recursive: true });
14
- const tmp = path2.join(dir, `.${path2.basename(targetPath)}.${randomBytes(6).toString("hex")}.tmp`);
15
- try {
16
- if (typeof content === "string") {
17
- await fsp2.writeFile(tmp, content, { flag: "wx", encoding: opts.encoding ?? "utf8" });
18
- } else {
19
- await fsp2.writeFile(tmp, content, { flag: "wx" });
20
- }
21
- try {
22
- const fh = await fsp2.open(tmp, "r+");
23
- try {
24
- await fh.sync();
25
- } finally {
26
- await fh.close();
27
- }
28
- } catch {
29
- }
30
- let mode;
31
- try {
32
- const stat10 = await fsp2.stat(targetPath);
33
- mode = stat10.mode & 511;
34
- } catch {
35
- mode = opts.mode;
36
- }
37
- if (mode !== void 0) {
38
- await fsp2.chmod(tmp, mode);
39
- }
40
- await renameWithRetry(tmp, targetPath);
41
- if (mode !== void 0 && process.platform === "win32") {
42
- try {
43
- await fsp2.chmod(targetPath, mode);
44
- } catch {
45
- }
46
- }
47
- } catch (err) {
48
- try {
49
- await fsp2.unlink(tmp);
50
- } catch {
51
- }
52
- throw err;
53
- }
12
+
13
+ // src/utils/term.ts
14
+ var hasStdout = () => typeof process !== "undefined" && !!process.stdout;
15
+ function isStdoutTTY() {
16
+ return hasStdout() && Boolean(process.stdout.isTTY);
54
17
  }
55
- async function ensureDir(dir) {
56
- await fsp2.mkdir(dir, { recursive: true });
18
+
19
+ // src/utils/color.ts
20
+ var isColorTty = () => {
21
+ if (envFlag(process.env.NO_COLOR)) return false;
22
+ if (envFlag(process.env.FORCE_COLOR)) return true;
23
+ return isStdoutTTY();
24
+ };
25
+ function envFlag(value) {
26
+ if (value === void 0) return false;
27
+ if (value.trim() === "") return false;
28
+ return !/^(0|false|no|off)$/i.test(value.trim());
57
29
  }
58
- async function withFileLock(targetPath, fn, opts = {}) {
59
- const dir = path2.dirname(targetPath);
60
- await fsp2.mkdir(dir, { recursive: true });
61
- const lockPath = path2.join(dir, `.${path2.basename(targetPath)}.lock`);
62
- const timeoutMs = opts.timeoutMs ?? 5e3;
63
- const staleMs = opts.staleMs ?? 3e4;
64
- const started = Date.now();
65
- let handle;
66
- for (; ; ) {
67
- try {
68
- handle = await fsp2.open(lockPath, "wx");
69
- await handle.writeFile(`${process.pid}:${Date.now()}`);
70
- break;
71
- } catch (err) {
72
- const code = err.code;
73
- if (code === "ENOENT") {
74
- await fsp2.mkdir(dir, { recursive: true });
75
- continue;
76
- }
77
- if (code !== "EEXIST") throw err;
78
- try {
79
- const stat10 = await fsp2.stat(lockPath);
80
- if (Date.now() - stat10.mtimeMs > staleMs) {
81
- await fsp2.unlink(lockPath);
82
- continue;
83
- }
84
- } catch {
85
- continue;
86
- }
87
- if (Date.now() - started >= timeoutMs) {
88
- throw new Error(`Timed out waiting for file lock: ${targetPath}`);
89
- }
90
- await new Promise((resolve8) => setTimeout(resolve8, 25));
91
- }
30
+ var COLOR = isColorTty();
31
+ var wrap = (open8, close) => (s) => COLOR ? `\x1B[${open8}m${s}\x1B[${close}m` : s;
32
+ var color = {
33
+ reset: wrap("0", "0"),
34
+ bold: wrap("1", "22"),
35
+ dim: wrap("2", "22"),
36
+ italic: wrap("3", "23"),
37
+ underline: wrap("4", "24"),
38
+ red: wrap("31", "39"),
39
+ green: wrap("32", "39"),
40
+ yellow: wrap("33", "39"),
41
+ blue: wrap("34", "39"),
42
+ magenta: wrap("35", "39"),
43
+ cyan: wrap("36", "39"),
44
+ gray: wrap("90", "39"),
45
+ amber: wrap("38;5;214", "39"),
46
+ pink: wrap("38;5;205", "39"),
47
+ bgRed: wrap("41", "49"),
48
+ bgGreen: wrap("42", "49")
49
+ };
50
+
51
+ // src/utils/deep-merge.ts
52
+ var FORBIDDEN_PROTO_KEYS = /* @__PURE__ */ new Set([
53
+ "__proto__",
54
+ "constructor",
55
+ "prototype",
56
+ "__defineGetter__",
57
+ "__defineSetter__",
58
+ "__lookupGetter__",
59
+ "__lookupSetter__"
60
+ ]);
61
+ function isPrimitiveArray(a) {
62
+ return a.every((v) => v === null || typeof v !== "object" && typeof v !== "function");
63
+ }
64
+ function deepMerge(base, patch, options = {}) {
65
+ const {
66
+ conflictResolution = "prefer-patch",
67
+ arrayMode = "replace",
68
+ protectProto = true,
69
+ onNonPrimitiveArrayReplace
70
+ } = options;
71
+ if (typeof base !== "object" || base === null) {
72
+ return conflictResolution === "prefer-patch" ? patch : base;
92
73
  }
93
- try {
94
- return await fn();
95
- } finally {
96
- try {
97
- await handle?.close();
98
- } catch {
99
- }
100
- try {
101
- await fsp2.unlink(lockPath);
102
- } catch {
74
+ if (typeof patch !== "object" || patch === null) {
75
+ return conflictResolution === "prefer-patch" ? patch : base;
76
+ }
77
+ if (Array.isArray(base) && Array.isArray(patch)) {
78
+ if (arrayMode === "concat-primitives" && isPrimitiveArray(base) && isPrimitiveArray(patch)) {
79
+ return [.../* @__PURE__ */ new Set([...base, ...patch])];
103
80
  }
81
+ return conflictResolution === "prefer-patch" ? patch : base;
104
82
  }
105
- }
106
- var TRANSIENT_RENAME_CODES = /* @__PURE__ */ new Set(["EPERM", "EBUSY", "EACCES", "ENOTEMPTY"]);
107
- async function renameWithRetry(from, to) {
108
- if (process.platform !== "win32") {
109
- await fsp2.rename(from, to);
110
- return;
83
+ if (Array.isArray(base) || Array.isArray(patch)) {
84
+ return conflictResolution === "prefer-patch" ? patch : base;
111
85
  }
112
- const delays = [10, 25, 60, 120, 250];
113
- let lastErr;
114
- for (let i = 0; i <= delays.length; i++) {
115
- try {
116
- await fsp2.rename(from, to);
117
- return;
118
- } catch (err) {
119
- lastErr = err;
120
- const code = err?.code;
121
- if (!code || !TRANSIENT_RENAME_CODES.has(code) || i === delays.length) {
122
- throw err;
86
+ const baseObj = base;
87
+ const patchObj = patch;
88
+ const out = { ...baseObj };
89
+ for (const [k, v] of Object.entries(patchObj)) {
90
+ if (protectProto && FORBIDDEN_PROTO_KEYS.has(k)) continue;
91
+ const existing = out[k];
92
+ if (v !== null && typeof v === "object" && !Array.isArray(v) && existing !== null && typeof existing === "object" && !Array.isArray(existing)) {
93
+ out[k] = deepMerge(existing, v, options);
94
+ } else if (Array.isArray(v) && Array.isArray(existing)) {
95
+ if (onNonPrimitiveArrayReplace && !isPrimitiveArray(v)) {
96
+ onNonPrimitiveArrayReplace(k, existing.length, v.length);
123
97
  }
124
- await new Promise((resolve8) => setTimeout(resolve8, delays[i]));
98
+ out[k] = deepMerge(existing, v, options);
99
+ } else if (v !== void 0) {
100
+ if (onNonPrimitiveArrayReplace && Array.isArray(v) && !isPrimitiveArray(v)) {
101
+ const existingLen = Array.isArray(existing) ? existing.length : 0;
102
+ onNonPrimitiveArrayReplace(k, existingLen, v.length);
103
+ }
104
+ out[k] = v;
125
105
  }
126
106
  }
127
- throw lastErr;
107
+ return out;
108
+ }
109
+
110
+ // src/utils/error.ts
111
+ function toErrorMessage(err) {
112
+ return err instanceof Error ? err.message : String(err);
128
113
  }
129
114
 
130
115
  // src/utils/expect-defined.ts
@@ -229,108 +214,6 @@ function isEmptyMessage(msg) {
229
214
  return msg.content.length === 0;
230
215
  }
231
216
 
232
- // src/utils/term.ts
233
- var hasStdout = () => typeof process !== "undefined" && !!process.stdout;
234
- function isStdoutTTY() {
235
- return hasStdout() && Boolean(process.stdout.isTTY);
236
- }
237
-
238
- // src/utils/color.ts
239
- var isColorTty = () => {
240
- if (envFlag(process.env.NO_COLOR)) return false;
241
- if (envFlag(process.env.FORCE_COLOR)) return true;
242
- return isStdoutTTY();
243
- };
244
- function envFlag(value) {
245
- if (value === void 0) return false;
246
- if (value.trim() === "") return false;
247
- return !/^(0|false|no|off)$/i.test(value.trim());
248
- }
249
- var COLOR = isColorTty();
250
- var wrap = (open8, close) => (s) => COLOR ? `\x1B[${open8}m${s}\x1B[${close}m` : s;
251
- var color = {
252
- reset: wrap("0", "0"),
253
- bold: wrap("1", "22"),
254
- dim: wrap("2", "22"),
255
- italic: wrap("3", "23"),
256
- underline: wrap("4", "24"),
257
- red: wrap("31", "39"),
258
- green: wrap("32", "39"),
259
- yellow: wrap("33", "39"),
260
- blue: wrap("34", "39"),
261
- magenta: wrap("35", "39"),
262
- cyan: wrap("36", "39"),
263
- gray: wrap("90", "39"),
264
- amber: wrap("38;5;214", "39"),
265
- pink: wrap("38;5;205", "39"),
266
- bgRed: wrap("41", "49"),
267
- bgGreen: wrap("42", "49")
268
- };
269
-
270
- // src/utils/deep-merge.ts
271
- var FORBIDDEN_PROTO_KEYS = /* @__PURE__ */ new Set([
272
- "__proto__",
273
- "constructor",
274
- "prototype",
275
- "__defineGetter__",
276
- "__defineSetter__",
277
- "__lookupGetter__",
278
- "__lookupSetter__"
279
- ]);
280
- function isPrimitiveArray(a) {
281
- return a.every((v) => v === null || typeof v !== "object" && typeof v !== "function");
282
- }
283
- function deepMerge(base, patch, options = {}) {
284
- const {
285
- conflictResolution = "prefer-patch",
286
- arrayMode = "replace",
287
- protectProto = true,
288
- onNonPrimitiveArrayReplace
289
- } = options;
290
- if (typeof base !== "object" || base === null) {
291
- return conflictResolution === "prefer-patch" ? patch : base;
292
- }
293
- if (typeof patch !== "object" || patch === null) {
294
- return conflictResolution === "prefer-patch" ? patch : base;
295
- }
296
- if (Array.isArray(base) && Array.isArray(patch)) {
297
- if (arrayMode === "concat-primitives" && isPrimitiveArray(base) && isPrimitiveArray(patch)) {
298
- return [.../* @__PURE__ */ new Set([...base, ...patch])];
299
- }
300
- return conflictResolution === "prefer-patch" ? patch : base;
301
- }
302
- if (Array.isArray(base) || Array.isArray(patch)) {
303
- return conflictResolution === "prefer-patch" ? patch : base;
304
- }
305
- const baseObj = base;
306
- const patchObj = patch;
307
- const out = { ...baseObj };
308
- for (const [k, v] of Object.entries(patchObj)) {
309
- if (protectProto && FORBIDDEN_PROTO_KEYS.has(k)) continue;
310
- const existing = out[k];
311
- if (v !== null && typeof v === "object" && !Array.isArray(v) && existing !== null && typeof existing === "object" && !Array.isArray(existing)) {
312
- out[k] = deepMerge(existing, v, options);
313
- } else if (Array.isArray(v) && Array.isArray(existing)) {
314
- if (onNonPrimitiveArrayReplace && !isPrimitiveArray(v)) {
315
- onNonPrimitiveArrayReplace(k, existing.length, v.length);
316
- }
317
- out[k] = deepMerge(existing, v, options);
318
- } else if (v !== void 0) {
319
- if (onNonPrimitiveArrayReplace && Array.isArray(v) && !isPrimitiveArray(v)) {
320
- const existingLen = Array.isArray(existing) ? existing.length : 0;
321
- onNonPrimitiveArrayReplace(k, existingLen, v.length);
322
- }
323
- out[k] = v;
324
- }
325
- }
326
- return out;
327
- }
328
-
329
- // src/utils/error.ts
330
- function toErrorMessage(err) {
331
- return err instanceof Error ? err.message : String(err);
332
- }
333
-
334
217
  // src/utils/regex-guard.ts
335
218
  var MAX_PATTERN_LEN = 512;
336
219
  var DANGEROUS_PATTERNS = [
@@ -381,6 +264,25 @@ function safeParse(input, maxBytes = 5e6) {
381
264
  };
382
265
  }
383
266
  }
267
+ function sessionScopedPath(dir, sessionId, suffix) {
268
+ if (!sessionId || sessionId.includes("\\") || sessionId.includes("..")) {
269
+ throw invalid(sessionId);
270
+ }
271
+ const resolved = path2.resolve(dir, `${sessionId}${suffix}`);
272
+ const rel = path2.relative(path2.resolve(dir), resolved);
273
+ if (rel.startsWith("..") || path2.isAbsolute(rel)) {
274
+ throw invalid(sessionId);
275
+ }
276
+ return resolved;
277
+ }
278
+ function invalid(sessionId) {
279
+ return new FsError({
280
+ message: `Invalid sessionId: ${sessionId}`,
281
+ code: ERROR_CODES.FS_DELETE_FAILED,
282
+ path: sessionId,
283
+ context: { reason: "path_traversal" }
284
+ });
285
+ }
384
286
 
385
287
  // src/utils/slug.ts
386
288
  function slugify(name, fallback = "prompt", maxLen = 64) {
@@ -476,6 +378,222 @@ function resolveWstackPaths(opts) {
476
378
  };
477
379
  }
478
380
 
381
+ // src/types/errors.ts
382
+ var ERROR_CODES = {
383
+ // Config
384
+ CONFIG_INVALID: "CONFIG_INVALID",
385
+ // Session
386
+ SESSION_NOT_FOUND: "SESSION_NOT_FOUND",
387
+ SESSION_CORRUPTED: "SESSION_CORRUPTED",
388
+ SESSION_WRITE_FAILED: "SESSION_WRITE_FAILED",
389
+ // File system
390
+ FS_READ_FAILED: "FS_READ_FAILED",
391
+ FS_DELETE_FAILED: "FS_DELETE_FAILED",
392
+ FS_ATOMIC_WRITE_FAILED: "FS_ATOMIC_WRITE_FAILED",
393
+ // General
394
+ VALIDATION_ERROR: "VALIDATION_ERROR",
395
+ UNKNOWN: "UNKNOWN"
396
+ };
397
+ var WrongStackError = class extends Error {
398
+ code;
399
+ subsystem;
400
+ severity;
401
+ recoverable;
402
+ context;
403
+ constructor(opts) {
404
+ super(opts.message, { cause: opts.cause });
405
+ this.name = "WrongStackError";
406
+ this.code = opts.code;
407
+ this.subsystem = opts.subsystem;
408
+ this.severity = opts.severity ?? "error";
409
+ this.recoverable = opts.recoverable ?? false;
410
+ this.context = opts.context;
411
+ }
412
+ /**
413
+ * Render a one-line user-facing description.
414
+ * Subclasses should override for domain-specific formatting.
415
+ */
416
+ describe() {
417
+ const ctx = this.context ? ` ${formatContext(this.context)}` : "";
418
+ return `${this.code}: ${this.message}${ctx}`;
419
+ }
420
+ };
421
+ function formatContext(ctx) {
422
+ const parts = Object.entries(ctx).filter(([, v]) => v !== void 0).slice(0, 3).map(([k, v]) => `${k}=${String(v)}`);
423
+ return parts.length > 0 ? `[${parts.join(" ")}]` : "";
424
+ }
425
+ var ConfigError = class extends WrongStackError {
426
+ constructor(opts) {
427
+ super({
428
+ message: opts.message,
429
+ code: opts.code,
430
+ subsystem: "config",
431
+ severity: "fatal",
432
+ recoverable: false,
433
+ context: opts.context,
434
+ cause: opts.cause
435
+ });
436
+ this.name = "ConfigError";
437
+ }
438
+ };
439
+ var SessionError = class extends WrongStackError {
440
+ sessionId;
441
+ constructor(opts) {
442
+ super({
443
+ message: opts.message,
444
+ code: opts.code,
445
+ subsystem: "session",
446
+ severity: opts.code === ERROR_CODES.SESSION_WRITE_FAILED ? "error" : "warning",
447
+ recoverable: opts.code !== ERROR_CODES.SESSION_CORRUPTED,
448
+ context: { sessionId: opts.sessionId, ...opts.context },
449
+ cause: opts.cause
450
+ });
451
+ this.name = "SessionError";
452
+ this.sessionId = opts.sessionId;
453
+ }
454
+ };
455
+ var FsError = class extends WrongStackError {
456
+ path;
457
+ constructor(opts) {
458
+ super({
459
+ message: opts.message,
460
+ code: opts.code,
461
+ subsystem: "fs",
462
+ severity: "error",
463
+ recoverable: opts.code !== ERROR_CODES.FS_READ_FAILED,
464
+ context: { path: opts.path, ...opts.context },
465
+ cause: opts.cause
466
+ });
467
+ this.name = "FsError";
468
+ this.path = opts.path;
469
+ }
470
+ };
471
+
472
+ // src/utils/atomic-write.ts
473
+ async function atomicWrite(targetPath, content, opts = {}) {
474
+ const dir = path2.dirname(targetPath);
475
+ await fsp2.mkdir(dir, { recursive: true });
476
+ const tmp = path2.join(dir, `.${path2.basename(targetPath)}.${randomBytes(6).toString("hex")}.tmp`);
477
+ try {
478
+ if (typeof content === "string") {
479
+ await fsp2.writeFile(tmp, content, { flag: "wx", encoding: opts.encoding ?? "utf8" });
480
+ } else {
481
+ await fsp2.writeFile(tmp, content, { flag: "wx" });
482
+ }
483
+ try {
484
+ const fh = await fsp2.open(tmp, "r+");
485
+ try {
486
+ await fh.sync();
487
+ } finally {
488
+ await fh.close();
489
+ }
490
+ } catch {
491
+ }
492
+ let mode;
493
+ try {
494
+ const stat11 = await fsp2.stat(targetPath);
495
+ mode = stat11.mode & 511;
496
+ } catch {
497
+ mode = opts.mode;
498
+ }
499
+ if (mode !== void 0) {
500
+ await fsp2.chmod(tmp, mode);
501
+ }
502
+ await renameWithRetry(tmp, targetPath);
503
+ if (mode !== void 0 && process.platform === "win32") {
504
+ try {
505
+ await fsp2.chmod(targetPath, mode);
506
+ } catch {
507
+ }
508
+ }
509
+ } catch (err) {
510
+ try {
511
+ await fsp2.unlink(tmp);
512
+ } catch {
513
+ }
514
+ throw err;
515
+ }
516
+ }
517
+ async function ensureDir(dir) {
518
+ await fsp2.mkdir(dir, { recursive: true });
519
+ }
520
+ async function withFileLock(targetPath, fn, opts = {}) {
521
+ const dir = path2.dirname(targetPath);
522
+ await fsp2.mkdir(dir, { recursive: true });
523
+ const lockPath = path2.join(dir, `.${path2.basename(targetPath)}.lock`);
524
+ const timeoutMs = opts.timeoutMs ?? 5e3;
525
+ const staleMs = opts.staleMs ?? 3e4;
526
+ const started = Date.now();
527
+ let handle;
528
+ for (; ; ) {
529
+ try {
530
+ handle = await fsp2.open(lockPath, "wx");
531
+ await handle.writeFile(`${process.pid}:${Date.now()}`);
532
+ break;
533
+ } catch (err) {
534
+ const code = err.code;
535
+ if (code === "ENOENT") {
536
+ await fsp2.mkdir(dir, { recursive: true });
537
+ continue;
538
+ }
539
+ if (code !== "EEXIST") throw err;
540
+ try {
541
+ const stat11 = await fsp2.stat(lockPath);
542
+ if (Date.now() - stat11.mtimeMs > staleMs) {
543
+ await fsp2.unlink(lockPath);
544
+ continue;
545
+ }
546
+ } catch {
547
+ continue;
548
+ }
549
+ if (Date.now() - started >= timeoutMs) {
550
+ throw new FsError({
551
+ message: `Timed out waiting for file lock: ${targetPath}`,
552
+ code: "FS_ATOMIC_WRITE_FAILED",
553
+ path: targetPath,
554
+ context: { timeoutMs }
555
+ });
556
+ }
557
+ await new Promise((resolve8) => setTimeout(resolve8, 25));
558
+ }
559
+ }
560
+ try {
561
+ return await fn();
562
+ } finally {
563
+ try {
564
+ await handle?.close();
565
+ } catch {
566
+ }
567
+ try {
568
+ await fsp2.unlink(lockPath);
569
+ } catch {
570
+ }
571
+ }
572
+ }
573
+ var TRANSIENT_RENAME_CODES = /* @__PURE__ */ new Set(["EPERM", "EBUSY", "EACCES", "ENOTEMPTY"]);
574
+ async function renameWithRetry(from, to) {
575
+ if (process.platform !== "win32") {
576
+ await fsp2.rename(from, to);
577
+ return;
578
+ }
579
+ const delays = [10, 25, 60, 120, 250];
580
+ let lastErr;
581
+ for (let i = 0; i <= delays.length; i++) {
582
+ try {
583
+ await fsp2.rename(from, to);
584
+ return;
585
+ } catch (err) {
586
+ lastErr = err;
587
+ const code = err?.code;
588
+ if (!code || !TRANSIENT_RENAME_CODES.has(code) || i === delays.length) {
589
+ throw err;
590
+ }
591
+ await new Promise((resolve8) => setTimeout(resolve8, delays[i]));
592
+ }
593
+ }
594
+ throw lastErr;
595
+ }
596
+
479
597
  // src/storage/session-helpers.ts
480
598
  function userInputTitle(content) {
481
599
  const text = typeof content === "string" ? content : content.filter((b) => b.type === "text").map((b) => b.text).join(" ");
@@ -860,6 +978,7 @@ var FileSessionWriter = class _FileSessionWriter {
860
978
  promptPreview
861
979
  });
862
980
  this.events?.emit("checkpoint.written", {
981
+ sessionId: this.id,
863
982
  promptIndex,
864
983
  promptPreview,
865
984
  ts: (/* @__PURE__ */ new Date()).toISOString(),
@@ -1029,6 +1148,7 @@ var FileSessionWriter = class _FileSessionWriter {
1029
1148
  revertedFiles: []
1030
1149
  });
1031
1150
  this.events?.emit("session.rewound", {
1151
+ sessionId: this.id,
1032
1152
  toPromptIndex: targetPromptIndex,
1033
1153
  revertedFiles: [],
1034
1154
  removedEvents: removedCount
@@ -1068,7 +1188,7 @@ var FileSessionWriter = class _FileSessionWriter {
1068
1188
  ts: (/* @__PURE__ */ new Date()).toISOString(),
1069
1189
  context
1070
1190
  });
1071
- this.events?.emit("in_flight.started", { context, ts: (/* @__PURE__ */ new Date()).toISOString() });
1191
+ this.events?.emit("in_flight.started", { sessionId: this.id, context, ts: (/* @__PURE__ */ new Date()).toISOString() });
1072
1192
  }
1073
1193
  /**
1074
1194
  * Close the in-flight marker. Idempotent in spirit
@@ -1083,18 +1203,18 @@ var FileSessionWriter = class _FileSessionWriter {
1083
1203
  ts: (/* @__PURE__ */ new Date()).toISOString(),
1084
1204
  reason
1085
1205
  });
1086
- this.events?.emit("in_flight.ended", { reason, ts: (/* @__PURE__ */ new Date()).toISOString() });
1206
+ this.events?.emit("in_flight.ended", { sessionId: this.id, reason, ts: (/* @__PURE__ */ new Date()).toISOString() });
1087
1207
  }
1088
1208
  };
1209
+
1210
+ // src/storage/session-id.ts
1089
1211
  function sanitizeModel(model) {
1090
1212
  return model.replace(/[^a-zA-Z0-9_-]/g, "-").replace(/-+/g, "-").replace(/^-|-$/g, "").slice(0, 40);
1091
1213
  }
1092
- function generateSessionId(startedAt, model) {
1214
+ function generateSessionId(startedAt, _model) {
1093
1215
  const date = startedAt.slice(0, 10);
1094
- const time = startedAt.slice(11, 19).replace(/:/g, "-");
1095
- const suffix = randomBytes(2).toString("hex");
1096
- const modelPart = model ? `_${sanitizeModel(model)}` : "";
1097
- return `${date}/${time}Z${modelPart}_${suffix}`;
1216
+ const seedTime = Number.isNaN(Date.parse(startedAt)) ? Date.now() : Date.parse(startedAt);
1217
+ return `${date}/sess_${ulid(seedTime)}`;
1098
1218
  }
1099
1219
 
1100
1220
  // src/storage/session-summary.ts
@@ -1229,7 +1349,7 @@ var DefaultSessionStore = class _DefaultSessionStore {
1229
1349
  }
1230
1350
  /** Join session ID to its absolute path within the store directory. */
1231
1351
  sessionPath(id, ext) {
1232
- return path2.join(this.dir, `${id}${ext}`);
1352
+ return sessionScopedPath(this.dir, id, ext);
1233
1353
  }
1234
1354
  shardManifestPath(shardKey) {
1235
1355
  return shardKey ? path2.join(this.dir, shardKey, "_manifest.json") : path2.join(this.dir, "_manifest.json");
@@ -1247,15 +1367,15 @@ var DefaultSessionStore = class _DefaultSessionStore {
1247
1367
  * subdirectory so sessions group naturally by day.
1248
1368
  */
1249
1369
  async ensureShardDir(id) {
1250
- const dirPath = path2.dirname(path2.join(this.dir, id));
1370
+ const dirPath = path2.dirname(sessionScopedPath(this.dir, id, ""));
1251
1371
  await ensureDir(dirPath);
1252
1372
  return dirPath;
1253
1373
  }
1254
1374
  async create(meta) {
1255
1375
  const startedAt = (/* @__PURE__ */ new Date()).toISOString();
1256
- const id = meta.id && meta.id.length > 0 ? meta.id : generateSessionId(startedAt, meta.model ?? meta.provider);
1376
+ const id = meta.id && meta.id.length > 0 ? meta.id : generateSessionId(startedAt);
1257
1377
  const shardDir = await this.ensureShardDir(id);
1258
- const file = path2.join(shardDir, `${path2.basename(id)}.jsonl`);
1378
+ const file = this.sessionPath(id, ".jsonl");
1259
1379
  const t0 = Date.now();
1260
1380
  let handle;
1261
1381
  try {
@@ -1337,6 +1457,26 @@ var DefaultSessionStore = class _DefaultSessionStore {
1337
1457
  }
1338
1458
  }
1339
1459
  async load(id) {
1460
+ return this.loadInternal(id, { full: true });
1461
+ }
1462
+ /**
1463
+ * Fast-path loader that skips message reconstruction and adjacency repair.
1464
+ *
1465
+ * Use this for callers that only need the raw event stream + session
1466
+ * metadata — e.g. session listers, analytics, audit, and the TUI's
1467
+ * "events only" views. It avoids the message array build and
1468
+ * repairToolUseAdjacency cost on large session files (a long agent
1469
+ * run can have 50k+ events; rebuilding messages is O(events) and
1470
+ * allocates per-block, so skipping it is a meaningful win).
1471
+ *
1472
+ * The returned data.messages is an empty array; data.toolCallEnds
1473
+ * is computed from the raw events. usage is the sum across all
1474
+ * llm_response events — same as full load().
1475
+ */
1476
+ async loadEventsOnly(id) {
1477
+ return this.loadInternal(id, { full: false });
1478
+ }
1479
+ async loadInternal(id, mode) {
1340
1480
  const file = this.sessionPath(id, ".jsonl");
1341
1481
  const t0 = Date.now();
1342
1482
  let outcome = "success";
@@ -1344,93 +1484,113 @@ var DefaultSessionStore = class _DefaultSessionStore {
1344
1484
  let cacheHit = false;
1345
1485
  try {
1346
1486
  const s = await fsp2.stat(file);
1347
- const stat10 = { mtimeMs: s.mtimeMs, size: s.size };
1487
+ const stat11 = { mtimeMs: s.mtimeMs, size: s.size };
1348
1488
  const cached = this._loadCache.get(id);
1349
- if (cached && cached.mtimeMs === stat10.mtimeMs && cached.size === stat10.size) {
1489
+ if (cached && cached.mtimeMs === stat11.mtimeMs && cached.size === stat11.size) {
1350
1490
  cacheHit = true;
1351
1491
  this._loadCache.delete(id);
1352
1492
  this._loadCache.set(id, cached);
1353
- return cached.data;
1493
+ if (mode.full) return cached.data;
1494
+ return { ...cached.data, messages: [] };
1354
1495
  }
1355
- const raw = await fsp2.readFile(file, "utf8");
1356
- const lines = raw.split("\n").filter((l) => l.trim());
1357
1496
  const events = [];
1358
1497
  let sessionStartEvent;
1359
1498
  let sessionEndEvent;
1360
1499
  let sessionModel;
1361
1500
  let sessionProvider;
1362
1501
  let sessionPendingToolUses;
1363
- const messages = [];
1364
- const openToolUses = /* @__PURE__ */ new Set();
1502
+ const messages = mode.full ? [] : void 0;
1503
+ const openToolUses = mode.full ? /* @__PURE__ */ new Set() : void 0;
1365
1504
  let usage = { input: 0, output: 0, cacheRead: 0, cacheWrite: 0 };
1366
- for (const line of lines) {
1367
- try {
1368
- const parsed = JSON.parse(line);
1369
- if (parsed !== null && typeof parsed === "object" && typeof parsed.type === "string" && typeof parsed.ts === "string") {
1370
- const ev = parsed;
1371
- events.push(ev);
1372
- if (ev.type === "session_start" && !sessionStartEvent) {
1373
- sessionStartEvent = ev;
1374
- sessionModel = ev.model;
1375
- sessionProvider = ev.provider;
1376
- }
1377
- if (ev.type === "session_end") {
1378
- sessionEndEvent = ev;
1379
- sessionPendingToolUses = ev.pendingToolUses;
1380
- }
1381
- if (ev.type === "user_input") {
1382
- openToolUses.clear();
1383
- messages.push({ role: "user", content: ev.content, ts: ev.ts });
1384
- } else if (ev.type === "llm_response") {
1385
- messages.push({ role: "assistant", content: ev.content, ts: ev.ts });
1386
- for (const b of ev.content) {
1387
- if (b.type === "tool_use") openToolUses.add(b.id);
1505
+ const stream = createReadStream(file, { encoding: "utf8" });
1506
+ const rl = createInterface({ input: stream, crlfDelay: Infinity });
1507
+ try {
1508
+ for await (const line of rl) {
1509
+ if (!line.trim()) continue;
1510
+ try {
1511
+ const parsed = JSON.parse(line);
1512
+ if (parsed !== null && typeof parsed === "object" && typeof parsed.type === "string" && typeof parsed.ts === "string") {
1513
+ const ev = parsed;
1514
+ events.push(ev);
1515
+ if (ev.type === "session_start" && !sessionStartEvent) {
1516
+ sessionStartEvent = ev;
1517
+ sessionModel = ev.model;
1518
+ sessionProvider = ev.provider;
1388
1519
  }
1389
- usage = {
1390
- input: usage.input + (ev.usage.input ?? 0),
1391
- output: usage.output + (ev.usage.output ?? 0),
1392
- cacheRead: (usage.cacheRead ?? 0) + (ev.usage.cacheRead ?? 0),
1393
- cacheWrite: (usage.cacheWrite ?? 0) + (ev.usage.cacheWrite ?? 0)
1394
- };
1395
- } else if (ev.type === "tool_result") {
1396
- if (!openToolUses.has(ev.id)) {
1397
- this.events?.emit("session.damaged", {
1398
- sessionId: id,
1399
- detail: `Orphan tool_result "${ev.id}" has no matching tool_use`
1400
- });
1401
- continue;
1520
+ if (ev.type === "session_end") {
1521
+ sessionEndEvent = ev;
1522
+ sessionPendingToolUses = ev.pendingToolUses;
1402
1523
  }
1403
- openToolUses.delete(ev.id);
1404
- const resultBlock = {
1405
- type: "tool_result",
1406
- tool_use_id: ev.id,
1407
- content: typeof ev.content === "string" ? ev.content : JSON.stringify(ev.content),
1408
- is_error: ev.isError
1409
- };
1410
- const last = messages[messages.length - 1];
1411
- const lastIsToolResultUser = last?.role === "user" && Array.isArray(last.content) && last.content.every((b) => b.type === "tool_result");
1412
- if (lastIsToolResultUser && Array.isArray(last.content)) {
1413
- last.content.push(resultBlock);
1414
- } else {
1415
- messages.push({ role: "user", content: [resultBlock], ts: ev.ts });
1524
+ if (mode.full && messages !== void 0 && openToolUses !== void 0) {
1525
+ if (ev.type === "user_input") {
1526
+ openToolUses.clear();
1527
+ messages.push({ role: "user", content: ev.content, ts: ev.ts });
1528
+ } else if (ev.type === "llm_response") {
1529
+ messages.push({ role: "assistant", content: ev.content, ts: ev.ts });
1530
+ for (const b of ev.content) {
1531
+ if (b.type === "tool_use") openToolUses.add(b.id);
1532
+ }
1533
+ usage = {
1534
+ input: usage.input + (ev.usage.input ?? 0),
1535
+ output: usage.output + (ev.usage.output ?? 0),
1536
+ cacheRead: (usage.cacheRead ?? 0) + (ev.usage.cacheRead ?? 0),
1537
+ cacheWrite: (usage.cacheWrite ?? 0) + (ev.usage.cacheWrite ?? 0)
1538
+ };
1539
+ } else if (ev.type === "tool_result") {
1540
+ if (!openToolUses.has(ev.id)) {
1541
+ this.events?.emit("session.damaged", {
1542
+ sessionId: id,
1543
+ detail: `Orphan tool_result "${ev.id}" has no matching tool_use`
1544
+ });
1545
+ continue;
1546
+ }
1547
+ openToolUses.delete(ev.id);
1548
+ const resultBlock = {
1549
+ type: "tool_result",
1550
+ tool_use_id: ev.id,
1551
+ content: typeof ev.content === "string" ? ev.content : JSON.stringify(ev.content),
1552
+ is_error: ev.isError
1553
+ };
1554
+ const last = messages[messages.length - 1];
1555
+ const lastIsToolResultUser = last?.role === "user" && Array.isArray(last.content) && last.content.every((b) => b.type === "tool_result");
1556
+ if (lastIsToolResultUser && Array.isArray(last.content)) {
1557
+ last.content.push(resultBlock);
1558
+ } else {
1559
+ messages.push({ role: "user", content: [resultBlock], ts: ev.ts });
1560
+ }
1561
+ }
1562
+ } else if (ev.type === "llm_response") {
1563
+ usage = {
1564
+ input: usage.input + (ev.usage.input ?? 0),
1565
+ output: usage.output + (ev.usage.output ?? 0),
1566
+ cacheRead: (usage.cacheRead ?? 0) + (ev.usage.cacheRead ?? 0),
1567
+ cacheWrite: (usage.cacheWrite ?? 0) + (ev.usage.cacheWrite ?? 0)
1568
+ };
1416
1569
  }
1417
1570
  }
1571
+ } catch {
1418
1572
  }
1419
- } catch {
1420
1573
  }
1574
+ } finally {
1575
+ rl.close();
1576
+ stream.close();
1421
1577
  }
1422
- if (openToolUses.size > 0) {
1423
- this.events?.emit("session.damaged", {
1424
- sessionId: id,
1425
- detail: `${openToolUses.size} tool_use blocks without matching results - replay repaired`
1426
- });
1427
- }
1428
- const repaired = repairToolUseAdjacency(messages);
1429
- if (repaired.report.changed) {
1430
- this.events?.emit("session.damaged", {
1431
- sessionId: id,
1432
- detail: `Repaired replay adjacency: removed ${repaired.report.removedToolUses.length} tool_use, ${repaired.report.removedToolResults.length} tool_result, ${repaired.report.removedMessages} empty messages`
1433
- });
1578
+ let finalMessages = [];
1579
+ if (mode.full && messages !== void 0 && openToolUses !== void 0) {
1580
+ if (openToolUses.size > 0) {
1581
+ this.events?.emit("session.damaged", {
1582
+ sessionId: id,
1583
+ detail: `${openToolUses.size} tool_use blocks without matching results - replay repaired`
1584
+ });
1585
+ }
1586
+ const repaired = repairToolUseAdjacency(messages);
1587
+ if (repaired.report.changed) {
1588
+ this.events?.emit("session.damaged", {
1589
+ sessionId: id,
1590
+ detail: `Repaired replay adjacency: removed ${repaired.report.removedToolUses.length} tool_use, ${repaired.report.removedToolResults.length} tool_result, ${repaired.report.removedMessages} empty messages`
1591
+ });
1592
+ }
1593
+ finalMessages = repaired.messages;
1434
1594
  }
1435
1595
  const meta = {
1436
1596
  id,
@@ -1441,27 +1601,29 @@ var DefaultSessionStore = class _DefaultSessionStore {
1441
1601
  pendingToolUses: sessionPendingToolUses
1442
1602
  };
1443
1603
  const toolCallEnds = extractToolCallEnds(events);
1444
- const data = { metadata: meta, events, messages: repaired.messages, usage, toolCallEnds };
1445
- if (this._loadCache.size >= _DefaultSessionStore.LOAD_CACHE_MAX_ENTRIES) {
1446
- const oldest = this._loadCache.keys().next().value;
1447
- if (oldest !== void 0) {
1448
- this._loadCache.delete(oldest);
1604
+ const data = { metadata: meta, events, messages: finalMessages, usage, toolCallEnds };
1605
+ if (mode.full) {
1606
+ if (this._loadCache.size >= _DefaultSessionStore.LOAD_CACHE_MAX_ENTRIES) {
1607
+ const oldest = this._loadCache.keys().next().value;
1608
+ if (oldest !== void 0) {
1609
+ this._loadCache.delete(oldest);
1610
+ }
1449
1611
  }
1612
+ this._loadCache.set(id, { mtimeMs: stat11.mtimeMs, size: stat11.size, data });
1450
1613
  }
1451
- this._loadCache.set(id, { mtimeMs: stat10.mtimeMs, size: stat10.size, data });
1452
1614
  return data;
1453
1615
  } catch (err) {
1454
1616
  outcome = "failure";
1455
1617
  errorMsg = toErrorMessage(err);
1456
1618
  throw err;
1457
1619
  } finally {
1458
- this.emitRead(id, file, "load", outcome, Date.now() - t0, errorMsg);
1620
+ this.emitRead(id, file, mode.full ? "load" : "load_events_only", outcome, Date.now() - t0, errorMsg);
1459
1621
  if (cacheHit) {
1460
1622
  this.events?.emit("storage.cache_hit", {
1461
1623
  sessionId: id,
1462
1624
  store: "session",
1463
1625
  filePath: file,
1464
- operation: "load",
1626
+ operation: mode.full ? "load" : "load_events_only",
1465
1627
  durationMs: Date.now() - t0
1466
1628
  });
1467
1629
  }
@@ -1489,14 +1651,14 @@ var DefaultSessionStore = class _DefaultSessionStore {
1489
1651
  const limit = opts?.limit;
1490
1652
  const signal = opts?.signal;
1491
1653
  const out = [];
1492
- let stat10;
1654
+ let stat11;
1493
1655
  try {
1494
- stat10 = await fsp2.stat(file);
1656
+ stat11 = await fsp2.stat(file);
1495
1657
  } catch (err) {
1496
1658
  if (err.code === "ENOENT") return [];
1497
1659
  throw err;
1498
1660
  }
1499
- if (stat10.size === 0) return [];
1661
+ if (stat11.size === 0) return [];
1500
1662
  let fh;
1501
1663
  try {
1502
1664
  fh = await fsp2.open(file, "r");
@@ -1609,14 +1771,20 @@ var DefaultSessionStore = class _DefaultSessionStore {
1609
1771
  async appendToIndex(summary) {
1610
1772
  try {
1611
1773
  await ensureDir(this.dir);
1612
- const line = JSON.stringify(summary) + "\n";
1613
- await fsp2.appendFile(this.indexFile, line, "utf8");
1614
- this._indexCache = null;
1615
- this.invalidateShardManifestBySessionId(summary.id);
1616
- this.indexAppendCount++;
1617
- if (this.indexAppendCount >= _DefaultSessionStore.COMPACT_EVERY) {
1618
- await this.compactIndex();
1619
- this.indexAppendCount = 0;
1774
+ let shouldCompact = false;
1775
+ await withFileLock(this.indexFile, async () => {
1776
+ const line = JSON.stringify(summary) + "\n";
1777
+ await fsp2.appendFile(this.indexFile, line, "utf8");
1778
+ this._indexCache = null;
1779
+ this.invalidateShardManifestBySessionId(summary.id);
1780
+ this.indexAppendCount++;
1781
+ if (this.indexAppendCount >= _DefaultSessionStore.COMPACT_EVERY) {
1782
+ shouldCompact = true;
1783
+ this.indexAppendCount = 0;
1784
+ }
1785
+ });
1786
+ if (shouldCompact) {
1787
+ await withFileLock(this.indexFile, () => this.compactIndexInner());
1620
1788
  }
1621
1789
  } catch {
1622
1790
  }
@@ -1625,30 +1793,28 @@ var DefaultSessionStore = class _DefaultSessionStore {
1625
1793
  async writeTombstone(id) {
1626
1794
  try {
1627
1795
  await ensureDir(this.dir);
1628
- const line = JSON.stringify({ action: "delete", id }) + "\n";
1629
- await fsp2.appendFile(this.indexFile, line, "utf8");
1630
- this._indexCache = null;
1631
- this.invalidateShardManifestBySessionId(id);
1632
- this.indexAppendCount++;
1796
+ await withFileLock(this.indexFile, async () => {
1797
+ const line = JSON.stringify({ action: "delete", id }) + "\n";
1798
+ await fsp2.appendFile(this.indexFile, line, "utf8");
1799
+ this._indexCache = null;
1800
+ this.invalidateShardManifestBySessionId(id);
1801
+ this.indexAppendCount++;
1802
+ });
1633
1803
  } catch {
1634
1804
  }
1635
1805
  }
1636
1806
  /**
1637
1807
  * Compact the index: read all entries, drop tombstones, deduplicate
1638
- * (keep latest per session), and rewrite. Atomic via temp+rename.
1808
+ * (keep latest per session), and rewrite atomically. Acquires the index
1809
+ * file lock so a concurrent append (this process or another wstack in the
1810
+ * same project) can't be overwritten by the rewrite.
1639
1811
  */
1640
1812
  async compactIndex() {
1641
1813
  const t0 = Date.now();
1642
1814
  let outcome = "success";
1643
1815
  let errorMsg;
1644
1816
  try {
1645
- const entries = await this.readIndex();
1646
- if (entries.length === 0) return;
1647
- const tmp = `${this.indexFile}.compact.tmp`;
1648
- const lines = entries.map((s) => JSON.stringify(s)).join("\n") + "\n";
1649
- await fsp2.writeFile(tmp, lines, "utf8");
1650
- await fsp2.rename(tmp, this.indexFile);
1651
- this._indexCache = null;
1817
+ await withFileLock(this.indexFile, () => this.compactIndexInner());
1652
1818
  } catch (err) {
1653
1819
  outcome = "failure";
1654
1820
  errorMsg = toErrorMessage(err);
@@ -1656,21 +1822,34 @@ var DefaultSessionStore = class _DefaultSessionStore {
1656
1822
  this.emitWrite("~compact~", this.indexFile, "compact", outcome, Date.now() - t0, void 0, errorMsg);
1657
1823
  }
1658
1824
  }
1825
+ /**
1826
+ * Lock-free compaction body. The caller MUST already hold the index file
1827
+ * lock (via withFileLock(this.indexFile, ...)). Uses atomicWrite for the
1828
+ * rewrite so the temp file gets a random suffix (no collision between two
1829
+ * compactions) and the Windows transient-EPERM rename retry.
1830
+ */
1831
+ async compactIndexInner() {
1832
+ const entries = await this.readIndex();
1833
+ if (entries.length === 0) return;
1834
+ const lines = entries.map((s) => JSON.stringify(s)).join("\n") + "\n";
1835
+ await atomicWrite(this.indexFile, lines, { mode: 384 });
1836
+ this._indexCache = null;
1837
+ }
1659
1838
  /**
1660
1839
  * Read the index file and return deduplicated session summaries.
1661
1840
  * Entries with a matching tombstone are filtered out.
1662
1841
  * Returns empty array when the index doesn't exist or is corrupt.
1663
1842
  */
1664
1843
  async readIndex() {
1665
- let stat10;
1844
+ let stat11;
1666
1845
  try {
1667
1846
  const s = await fsp2.stat(this.indexFile);
1668
- stat10 = { mtimeMs: s.mtimeMs, size: s.size };
1847
+ stat11 = { mtimeMs: s.mtimeMs, size: s.size };
1669
1848
  } catch {
1670
1849
  this._indexCache = null;
1671
1850
  return [];
1672
1851
  }
1673
- if (this._indexCache !== null && this._indexCache.mtimeMs === stat10.mtimeMs && this._indexCache.size === stat10.size) {
1852
+ if (this._indexCache !== null && this._indexCache.mtimeMs === stat11.mtimeMs && this._indexCache.size === stat11.size) {
1674
1853
  return [...this._indexCache.summaries];
1675
1854
  }
1676
1855
  let raw;
@@ -1703,7 +1882,7 @@ var DefaultSessionStore = class _DefaultSessionStore {
1703
1882
  if (a.startedAt > b.startedAt) return -1;
1704
1883
  return a.id.localeCompare(b.id);
1705
1884
  });
1706
- this._indexCache = { ...stat10, summaries };
1885
+ this._indexCache = { ...stat11, summaries };
1707
1886
  return [...summaries];
1708
1887
  }
1709
1888
  /**
@@ -1714,11 +1893,11 @@ var DefaultSessionStore = class _DefaultSessionStore {
1714
1893
  const ids = await this.collectSessionIds(this.dir);
1715
1894
  const summaries = await Promise.all(ids.map((id) => this.summaryFor(id).catch(() => null)));
1716
1895
  const valid = summaries.filter((s) => s !== null);
1717
- const tmp = `${this.indexFile}.tmp`;
1718
1896
  const lines = valid.map((s) => JSON.stringify(s)).join("\n") + "\n";
1719
- await fsp2.writeFile(tmp, lines, "utf8");
1720
- await fsp2.rename(tmp, this.indexFile);
1721
- this._indexCache = null;
1897
+ await withFileLock(this.indexFile, async () => {
1898
+ await atomicWrite(this.indexFile, lines, { mode: 384 });
1899
+ this._indexCache = null;
1900
+ });
1722
1901
  return valid.length;
1723
1902
  }
1724
1903
  async listFromDirectoryScan(limit) {
@@ -1870,8 +2049,8 @@ var DefaultSessionStore = class _DefaultSessionStore {
1870
2049
  if (fromManifest) return fromManifest;
1871
2050
  try {
1872
2051
  const full = this.sessionPath(id, ".jsonl");
1873
- const stat10 = await fsp2.stat(full);
1874
- const summary = await this.summarize(id, stat10.mtime.toISOString());
2052
+ const stat11 = await fsp2.stat(full);
2053
+ const summary = await this.summarize(id, stat11.mtime.toISOString());
1875
2054
  await atomicWrite(manifest, JSON.stringify(summary), { mode: 384 }).catch((err) => {
1876
2055
  const msg = toErrorMessage(err);
1877
2056
  this.emitError(id, manifest, "summary_fallback", msg, true);
@@ -1914,18 +2093,18 @@ var DefaultSessionStore = class _DefaultSessionStore {
1914
2093
  async summaryHeaderFor(ref) {
1915
2094
  let mtime = (/* @__PURE__ */ new Date(0)).toISOString();
1916
2095
  try {
1917
- const stat10 = await fsp2.stat(ref.filePath);
1918
- if (!stat10.isFile()) {
2096
+ const stat11 = await fsp2.stat(ref.filePath);
2097
+ if (!stat11.isFile()) {
1919
2098
  return {
1920
2099
  id: ref.id,
1921
2100
  title: "(damaged)",
1922
- startedAt: stat10.mtime.toISOString(),
2101
+ startedAt: stat11.mtime.toISOString(),
1923
2102
  model: "unknown",
1924
2103
  provider: "unknown",
1925
2104
  tokenTotal: 0
1926
2105
  };
1927
2106
  }
1928
- mtime = stat10.mtime.toISOString();
2107
+ mtime = stat11.mtime.toISOString();
1929
2108
  } catch {
1930
2109
  return null;
1931
2110
  }
@@ -1973,14 +2152,15 @@ var DefaultSessionStore = class _DefaultSessionStore {
1973
2152
  async deleteSession(id) {
1974
2153
  const jsonlPath = this.sessionPath(id, ".jsonl");
1975
2154
  const summaryPath = this.sessionPath(id, ".summary.json");
1976
- const shardDir = path2.dirname(path2.join(this.dir, id));
2155
+ const shardDir = path2.dirname(jsonlPath);
1977
2156
  const base = path2.basename(id);
1978
2157
  const sessDir = path2.join(shardDir, base);
1979
2158
  const deletions = [
1980
2159
  fsp2.unlink(jsonlPath),
1981
2160
  fsp2.unlink(summaryPath),
1982
- fsp2.unlink(path2.join(shardDir, `${base}.plan.json`)),
1983
- fsp2.unlink(path2.join(shardDir, `${base}.todos.json`))
2161
+ fsp2.unlink(sessionScopedPath(this.dir, id, ".plan.json")),
2162
+ fsp2.unlink(sessionScopedPath(this.dir, id, ".tasks.json")),
2163
+ fsp2.unlink(sessionScopedPath(this.dir, id, ".todos.json"))
1984
2164
  ];
1985
2165
  const results = await Promise.allSettled(deletions);
1986
2166
  for (const r of results) {
@@ -2025,8 +2205,8 @@ var DefaultSessionStore = class _DefaultSessionStore {
2025
2205
  const pruneFile = async (dir, name, prefix) => {
2026
2206
  const jsonlPath = path2.join(dir, name);
2027
2207
  try {
2028
- const stat10 = await fsp2.stat(jsonlPath);
2029
- if (stat10.mtimeMs >= cutoff) return;
2208
+ const stat11 = await fsp2.stat(jsonlPath);
2209
+ if (stat11.mtimeMs >= cutoff) return;
2030
2210
  } catch {
2031
2211
  return;
2032
2212
  }
@@ -2565,9 +2745,80 @@ function isMemoryType(s) {
2565
2745
  function isPriority(s) {
2566
2746
  return s === "critical" || s === "high" || s === "medium" || s === "low";
2567
2747
  }
2748
+ function buildInvertedIndex(entries) {
2749
+ const wordMap = /* @__PURE__ */ new Map();
2750
+ const tagMap = /* @__PURE__ */ new Map();
2751
+ const indexed = new Array(entries.length);
2752
+ for (let i = 0; i < entries.length; i++) {
2753
+ const e = entries[i];
2754
+ const words = e.text.toLowerCase().split(/\s+/).filter((w) => w.length > 0);
2755
+ const tags = (e.tags ?? []).map((t) => t.toLowerCase());
2756
+ indexed[i] = { entry: e, words, tags };
2757
+ for (const w of words) {
2758
+ const arr = wordMap.get(w);
2759
+ if (arr) arr.push(i);
2760
+ else wordMap.set(w, [i]);
2761
+ }
2762
+ for (const t of tags) {
2763
+ const arr = tagMap.get(t);
2764
+ if (arr) arr.push(i);
2765
+ else tagMap.set(t, [i]);
2766
+ }
2767
+ }
2768
+ return { wordMap, tagMap, entries: indexed, mtimeMs: 0 };
2769
+ }
2770
+ var MIN_SUBSTRING_NEEDLE_LEN = 3;
2771
+ function searchIndex(index, query, limit) {
2772
+ const needles = query.toLowerCase().split(/\s+/).filter((n) => n.length > 0);
2773
+ if (needles.length === 0) return [];
2774
+ const scores = /* @__PURE__ */ new Map();
2775
+ for (const n of needles) {
2776
+ let matched = false;
2777
+ const wordExact = index.wordMap.get(n);
2778
+ if (wordExact) {
2779
+ for (const idx of wordExact) scores.set(idx, (scores.get(idx) ?? 0) + 1);
2780
+ matched = true;
2781
+ }
2782
+ const tagExact = index.tagMap.get(n);
2783
+ if (tagExact) {
2784
+ for (const idx of tagExact) scores.set(idx, (scores.get(idx) ?? 0) + 2);
2785
+ matched = true;
2786
+ }
2787
+ if (matched || n.length < MIN_SUBSTRING_NEEDLE_LEN) continue;
2788
+ for (const [word, indices] of index.wordMap) {
2789
+ if (word.includes(n) || n.includes(word)) {
2790
+ for (const idx of indices) {
2791
+ scores.set(idx, (scores.get(idx) ?? 0) + 1);
2792
+ }
2793
+ }
2794
+ }
2795
+ for (const [tag, indices] of index.tagMap) {
2796
+ if (tag.includes(n) || n.includes(tag)) {
2797
+ for (const idx of indices) {
2798
+ scores.set(idx, (scores.get(idx) ?? 0) + 2);
2799
+ }
2800
+ }
2801
+ }
2802
+ }
2803
+ if (scores.size === 0) return [];
2804
+ const scored = Array.from(scores.entries());
2805
+ scored.sort((a, b) => b[1] - a[1]);
2806
+ const result = [];
2807
+ const max = limit ? Math.min(limit, scored.length) : scored.length;
2808
+ for (let i = 0; i < max; i++) {
2809
+ result.push(index.entries[scored[i][0]].entry);
2810
+ }
2811
+ return result;
2812
+ }
2568
2813
  var FileMemoryBackend = class {
2569
2814
  kind = "file";
2570
2815
  files;
2816
+ /** Cache of parsed entries per file path. */
2817
+ entryCache = /* @__PURE__ */ new Map();
2818
+ /** Inverted index per file path. */
2819
+ indexCache = /* @__PURE__ */ new Map();
2820
+ /** File mtime cache for invalidation. */
2821
+ mtimeCache = /* @__PURE__ */ new Map();
2571
2822
  constructor(opts) {
2572
2823
  this.files = {
2573
2824
  "project-agents": opts.paths.inProjectAgentsFile,
@@ -2578,6 +2829,53 @@ var FileMemoryBackend = class {
2578
2829
  resolveFile(filePath, scope) {
2579
2830
  return filePath || this.files[scope];
2580
2831
  }
2832
+ async getMtime(file) {
2833
+ try {
2834
+ const stat11 = await fsp2.stat(file);
2835
+ return stat11.mtimeMs;
2836
+ } catch {
2837
+ return 0;
2838
+ }
2839
+ }
2840
+ invalidateCache(file) {
2841
+ this.entryCache.delete(file);
2842
+ this.indexCache.delete(file);
2843
+ this.mtimeCache.delete(file);
2844
+ }
2845
+ /**
2846
+ * Load (and cache) the parsed entries for a file. Callers that have already
2847
+ * stat'd the file this tick (e.g. `getIndex`) can pass the known `mtime` to
2848
+ * avoid a redundant `fs.stat` — otherwise it's fetched here.
2849
+ */
2850
+ async loadEntries(file, scope, mtime) {
2851
+ const resolvedMtime = mtime ?? await this.getMtime(file);
2852
+ const cachedMtime = this.mtimeCache.get(file);
2853
+ if (cachedMtime === resolvedMtime && this.entryCache.has(file)) {
2854
+ return this.entryCache.get(file);
2855
+ }
2856
+ const raw = await this.readAll(scope, file);
2857
+ if (!raw.trim()) {
2858
+ this.entryCache.set(file, []);
2859
+ this.mtimeCache.set(file, resolvedMtime);
2860
+ return [];
2861
+ }
2862
+ const entries = parseEntries(raw, scope);
2863
+ this.entryCache.set(file, entries);
2864
+ this.mtimeCache.set(file, resolvedMtime);
2865
+ return entries;
2866
+ }
2867
+ async getIndex(file, scope) {
2868
+ const mtime = await this.getMtime(file);
2869
+ const cached = this.indexCache.get(file);
2870
+ if (cached && cached.mtimeMs === mtime) {
2871
+ return cached;
2872
+ }
2873
+ const entries = await this.loadEntries(file, scope, mtime);
2874
+ const index = buildInvertedIndex(entries);
2875
+ index.mtimeMs = mtime;
2876
+ this.indexCache.set(file, index);
2877
+ return index;
2878
+ }
2581
2879
  async remember(scope, entry, filePath) {
2582
2880
  const file = this.resolveFile(filePath, scope);
2583
2881
  await ensureDir(path2.dirname(file));
@@ -2594,6 +2892,7 @@ var FileMemoryBackend = class {
2594
2892
  const next = existing.trim() ? existing.replace(/\n+$/, "") + line : `# Agent Memory
2595
2893
  ${line}`;
2596
2894
  await atomicWrite(file, next);
2895
+ this.invalidateCache(file);
2597
2896
  }
2598
2897
  async forget(scope, query, filePath) {
2599
2898
  const file = this.resolveFile(filePath, scope);
@@ -2630,6 +2929,7 @@ ${line}`;
2630
2929
  await atomicWrite(file, lines.join("\n"));
2631
2930
  }
2632
2931
  }
2932
+ this.invalidateCache(file);
2633
2933
  return removed;
2634
2934
  });
2635
2935
  }
@@ -2642,30 +2942,19 @@ ${line}`;
2642
2942
  }
2643
2943
  }
2644
2944
  async list(scope, filePath, limit) {
2645
- const raw = await this.readAll(scope, filePath);
2646
- if (!raw.trim()) return [];
2647
- const entries = parseEntries(raw, scope);
2945
+ const file = this.resolveFile(filePath, scope);
2946
+ const entries = await this.loadEntries(file, scope);
2648
2947
  return limit ? entries.slice(0, limit) : entries;
2649
2948
  }
2650
2949
  async search(scope, query, filePath, limit) {
2651
- const entries = await this.list(scope, filePath);
2652
- const needle = query.toLowerCase().split(/\s+/);
2653
- const scored = entries.map((e) => {
2654
- const words = e.text.toLowerCase().split(/\s+/);
2655
- let score = 0;
2656
- for (const n of needle) {
2657
- if (words.some((w) => w.includes(n))) score += 1;
2658
- if (e.tags?.some((t) => t.toLowerCase().includes(n))) score += 2;
2659
- }
2660
- return { entry: e, score };
2661
- });
2662
- scored.sort((a, b) => b.score - a.score);
2663
- const matched = scored.filter((s) => s.score > 0).map((s) => s.entry);
2664
- return limit ? matched.slice(0, limit) : matched;
2950
+ const file = this.resolveFile(filePath, scope);
2951
+ const index = await this.getIndex(file, scope);
2952
+ return searchIndex(index, query, limit);
2665
2953
  }
2666
2954
  async clear(scope, filePath) {
2667
2955
  const file = this.resolveFile(filePath, scope);
2668
2956
  await atomicWrite(file, "");
2957
+ this.invalidateCache(file);
2669
2958
  }
2670
2959
  async consolidate(scope, filePath) {
2671
2960
  const file = this.resolveFile(filePath, scope);
@@ -2700,6 +2989,7 @@ ${line}`;
2700
2989
  } catch {
2701
2990
  return 0;
2702
2991
  }
2992
+ this.invalidateCache(file);
2703
2993
  return removed;
2704
2994
  }
2705
2995
  };
@@ -2766,9 +3056,10 @@ var DefaultMemoryStore = class {
2766
3056
  /** Result cache for scoreRelevant() — keyed by scope + context hash, TTL 30s. */
2767
3057
  _scoreCache = /* @__PURE__ */ new Map();
2768
3058
  /**
2769
- * Per-entry cached lowercase strings — computed once per scoreRelevant() call,
2770
- * stored here so repeated scoring of the same entries avoids re-computation.
2771
- * Cleared on every mutation (remember/forget/consolidate/clear).
3059
+ * Per-entry cached lowercase strings — lazily allocated once and reused
3060
+ * across scoreRelevant() calls so repeated scoring of the same entries skips
3061
+ * re-lowercasing. Keyed by entry object identity; cleared (set to null) on
3062
+ * every mutation (remember/forget/consolidate/clear) via invalidateScoreCaches().
2772
3063
  */
2773
3064
  _cachedLower = null;
2774
3065
  constructor(opts) {
@@ -2918,7 +3209,7 @@ ${body.trim()}`);
2918
3209
  const t0 = Date.now();
2919
3210
  try {
2920
3211
  await this.backend.remember(scope, entry, filePath);
2921
- this._scoreCache.clear();
3212
+ this.invalidateScoreCaches();
2922
3213
  const dur = Date.now() - t0;
2923
3214
  this.events?.emit("storage.write", {
2924
3215
  sessionId: "~memory~",
@@ -2969,6 +3260,15 @@ ${body.trim()}`);
2969
3260
  });
2970
3261
  });
2971
3262
  }
3263
+ /**
3264
+ * Drop the relevance caches after a mutation. Both the per-context score
3265
+ * cache and the per-entry lowercase cache are keyed on entry objects that a
3266
+ * mutation invalidates, so they must be cleared together when entries change.
3267
+ */
3268
+ invalidateScoreCaches() {
3269
+ this._scoreCache.clear();
3270
+ this._cachedLower = null;
3271
+ }
2972
3272
  /**
2973
3273
  * Score and rank memories by relevance to the current context.
2974
3274
  * Returns entries with score >= MIN_RELEVANCE_SCORE, sorted highest first.
@@ -2986,18 +3286,19 @@ ${body.trim()}`);
2986
3286
  const taskWords = ctx.currentTask.toLowerCase().split(/\s+/).filter((w) => w.length > 2);
2987
3287
  const skillWords = (ctx.activeSkills ?? []).flatMap((s) => s.split("-"));
2988
3288
  const toolWords = (ctx.toolNames ?? []).flatMap((t) => t.toLowerCase().split("_"));
2989
- this._cachedLower = /* @__PURE__ */ new WeakMap();
3289
+ this._cachedLower ??= /* @__PURE__ */ new WeakMap();
3290
+ const lowerCache = this._cachedLower;
2990
3291
  const scored = [];
2991
3292
  for (const entry of all) {
2992
3293
  let score = 0;
2993
3294
  const reasons = [];
2994
- let cachedLower = this._cachedLower.get(entry);
3295
+ let cachedLower = lowerCache.get(entry);
2995
3296
  if (!cachedLower) {
2996
3297
  cachedLower = {
2997
3298
  textLower: entry.text.toLowerCase(),
2998
3299
  tagsLower: (entry.tags ?? []).map((t) => t.toLowerCase())
2999
3300
  };
3000
- this._cachedLower.set(entry, cachedLower);
3301
+ lowerCache.set(entry, cachedLower);
3001
3302
  }
3002
3303
  const { textLower, tagsLower } = cachedLower;
3003
3304
  let taskHits = 0;
@@ -3095,7 +3396,7 @@ ${body.trim()}`);
3095
3396
  let removed = 0;
3096
3397
  try {
3097
3398
  removed = await this.backend.forget(scope, query, filePath);
3098
- this._scoreCache.clear();
3399
+ this.invalidateScoreCaches();
3099
3400
  const dur = Date.now() - t0;
3100
3401
  this.events?.emit("storage.write", {
3101
3402
  sessionId: "~memory~",
@@ -3139,7 +3440,7 @@ ${body.trim()}`);
3139
3440
  let removed = 0;
3140
3441
  try {
3141
3442
  removed = await this.backend.consolidate(scope, filePath);
3142
- this._scoreCache.clear();
3443
+ this.invalidateScoreCaches();
3143
3444
  const dur = Date.now() - t0;
3144
3445
  this.events?.emit("storage.write", {
3145
3446
  sessionId: "~memory~",
@@ -3181,7 +3482,7 @@ ${body.trim()}`);
3181
3482
  const t0 = Date.now();
3182
3483
  try {
3183
3484
  await this.backend.clear(scope, filePath);
3184
- this._scoreCache.clear();
3485
+ this.invalidateScoreCaches();
3185
3486
  const dur = Date.now() - t0;
3186
3487
  this.events?.emit("storage.write", {
3187
3488
  sessionId: "~memory~",
@@ -3248,6 +3549,7 @@ ${body.trim()}`);
3248
3549
  })
3249
3550
  )
3250
3551
  );
3552
+ this.invalidateScoreCaches();
3251
3553
  }
3252
3554
  /**
3253
3555
  * Return a new MemoryStore proxy that carries `traceId` on every storage
@@ -3775,97 +4077,6 @@ var SessionMemoryConsolidator = class {
3775
4077
  };
3776
4078
  };
3777
4079
 
3778
- // src/types/errors.ts
3779
- var ERROR_CODES = {
3780
- // Config
3781
- CONFIG_INVALID: "CONFIG_INVALID",
3782
- // Session
3783
- SESSION_NOT_FOUND: "SESSION_NOT_FOUND",
3784
- SESSION_CORRUPTED: "SESSION_CORRUPTED",
3785
- SESSION_WRITE_FAILED: "SESSION_WRITE_FAILED",
3786
- // File system
3787
- FS_READ_FAILED: "FS_READ_FAILED",
3788
- FS_DELETE_FAILED: "FS_DELETE_FAILED",
3789
- FS_ATOMIC_WRITE_FAILED: "FS_ATOMIC_WRITE_FAILED",
3790
- // General
3791
- VALIDATION_ERROR: "VALIDATION_ERROR",
3792
- UNKNOWN: "UNKNOWN"
3793
- };
3794
- var WrongStackError = class extends Error {
3795
- code;
3796
- subsystem;
3797
- severity;
3798
- recoverable;
3799
- context;
3800
- constructor(opts) {
3801
- super(opts.message, { cause: opts.cause });
3802
- this.name = "WrongStackError";
3803
- this.code = opts.code;
3804
- this.subsystem = opts.subsystem;
3805
- this.severity = opts.severity ?? "error";
3806
- this.recoverable = opts.recoverable ?? false;
3807
- this.context = opts.context;
3808
- }
3809
- /**
3810
- * Render a one-line user-facing description.
3811
- * Subclasses should override for domain-specific formatting.
3812
- */
3813
- describe() {
3814
- const ctx = this.context ? ` ${formatContext(this.context)}` : "";
3815
- return `${this.code}: ${this.message}${ctx}`;
3816
- }
3817
- };
3818
- function formatContext(ctx) {
3819
- const parts = Object.entries(ctx).filter(([, v]) => v !== void 0).slice(0, 3).map(([k, v]) => `${k}=${String(v)}`);
3820
- return parts.length > 0 ? `[${parts.join(" ")}]` : "";
3821
- }
3822
- var ConfigError = class extends WrongStackError {
3823
- constructor(opts) {
3824
- super({
3825
- message: opts.message,
3826
- code: opts.code,
3827
- subsystem: "config",
3828
- severity: "fatal",
3829
- recoverable: false,
3830
- context: opts.context,
3831
- cause: opts.cause
3832
- });
3833
- this.name = "ConfigError";
3834
- }
3835
- };
3836
- var SessionError = class extends WrongStackError {
3837
- sessionId;
3838
- constructor(opts) {
3839
- super({
3840
- message: opts.message,
3841
- code: opts.code,
3842
- subsystem: "session",
3843
- severity: opts.code === ERROR_CODES.SESSION_WRITE_FAILED ? "error" : "warning",
3844
- recoverable: opts.code !== ERROR_CODES.SESSION_CORRUPTED,
3845
- context: { sessionId: opts.sessionId, ...opts.context },
3846
- cause: opts.cause
3847
- });
3848
- this.name = "SessionError";
3849
- this.sessionId = opts.sessionId;
3850
- }
3851
- };
3852
- var FsError = class extends WrongStackError {
3853
- path;
3854
- constructor(opts) {
3855
- super({
3856
- message: opts.message,
3857
- code: opts.code,
3858
- subsystem: "fs",
3859
- severity: "error",
3860
- recoverable: opts.code !== ERROR_CODES.FS_READ_FAILED,
3861
- context: { path: opts.path, ...opts.context },
3862
- cause: opts.cause
3863
- });
3864
- this.name = "FsError";
3865
- this.path = opts.path;
3866
- }
3867
- };
3868
-
3869
4080
  // src/storage/config-store.ts
3870
4081
  function stripEphemeralFields(cfg) {
3871
4082
  const env = cfg._envSource;
@@ -3945,7 +4156,7 @@ var KEK_MAGIC = Buffer.from("WSKW", "ascii");
3945
4156
  var KEK_SALT_BYTES = 16;
3946
4157
  KEK_MAGIC.length + 1 + KEK_SALT_BYTES + IV_BYTES + TAG_BYTES + KEY_BYTES;
3947
4158
  function decryptConfigSecrets(cfg, vault, opts) {
3948
- const warn = ((msg) => console.warn(msg));
4159
+ const warn = opts?.warn ?? ((msg) => console.warn(msg));
3949
4160
  return walk(cfg, vault, (v, key) => {
3950
4161
  try {
3951
4162
  return vault.decrypt(v);
@@ -3983,6 +4194,95 @@ function isSecretField(name) {
3983
4194
  return SECRET_KEY_PATTERN.test(lc);
3984
4195
  }
3985
4196
 
4197
+ // src/storage/provider-config-watcher.ts
4198
+ async function readProviderSnapshot(configPath, vault, warn) {
4199
+ let raw;
4200
+ try {
4201
+ raw = await fsp2.readFile(configPath, "utf8");
4202
+ } catch (err) {
4203
+ if (err.code !== "ENOENT") {
4204
+ warn?.(`Could not read ${configPath}: ${err.message}`);
4205
+ }
4206
+ return void 0;
4207
+ }
4208
+ let parsed;
4209
+ try {
4210
+ parsed = JSON.parse(raw);
4211
+ } catch (err) {
4212
+ warn?.(`Config at ${configPath} is not valid JSON: ${err.message}`);
4213
+ return void 0;
4214
+ }
4215
+ const decrypted = decryptConfigSecrets(parsed, vault, warn ? { warn } : {});
4216
+ const snapshot = {
4217
+ providers: decrypted.providers ?? {}
4218
+ };
4219
+ if (typeof decrypted.apiKey === "string") snapshot.apiKey = decrypted.apiKey;
4220
+ if (typeof decrypted.baseUrl === "string") snapshot.baseUrl = decrypted.baseUrl;
4221
+ return snapshot;
4222
+ }
4223
+ function serializeSnapshot(s) {
4224
+ return JSON.stringify({
4225
+ providers: s.providers,
4226
+ apiKey: s.apiKey ?? null,
4227
+ baseUrl: s.baseUrl ?? null
4228
+ });
4229
+ }
4230
+ function watchProviderConfig(configPath, vault, onChange, opts = {}) {
4231
+ const debounceMs = opts.debounceMs ?? 200;
4232
+ const warn = opts.warn;
4233
+ const base = path2.basename(configPath);
4234
+ let timer;
4235
+ let closed = false;
4236
+ let lastSerialized;
4237
+ void readProviderSnapshot(configPath, vault, warn).then((seed) => {
4238
+ if (!closed && seed && lastSerialized === void 0) {
4239
+ lastSerialized = serializeSnapshot(seed);
4240
+ }
4241
+ });
4242
+ let watcher;
4243
+ try {
4244
+ watcher = syncFs.watch(path2.dirname(configPath), { recursive: false });
4245
+ } catch (err) {
4246
+ warn?.(`Provider config watcher could not start: ${err.message}`);
4247
+ return { close: () => {
4248
+ } };
4249
+ }
4250
+ const trigger = () => {
4251
+ if (closed) return;
4252
+ if (timer) clearTimeout(timer);
4253
+ timer = setTimeout(() => {
4254
+ timer = void 0;
4255
+ void readProviderSnapshot(configPath, vault, warn).then(
4256
+ (next) => {
4257
+ if (closed || !next) return;
4258
+ const serialized = serializeSnapshot(next);
4259
+ if (serialized === lastSerialized) return;
4260
+ lastSerialized = serialized;
4261
+ onChange(next);
4262
+ },
4263
+ () => {
4264
+ }
4265
+ );
4266
+ }, debounceMs);
4267
+ };
4268
+ watcher.on("change", (eventType, filename) => {
4269
+ const name = typeof filename === "string" ? filename : "";
4270
+ if (eventType === "rename" || eventType === "change") {
4271
+ if (!name || name === base) trigger();
4272
+ }
4273
+ });
4274
+ watcher.on("error", (err) => {
4275
+ warn?.(`Provider config watcher error: ${err.message}`);
4276
+ });
4277
+ return {
4278
+ close: () => {
4279
+ closed = true;
4280
+ if (timer) clearTimeout(timer);
4281
+ watcher.close();
4282
+ }
4283
+ };
4284
+ }
4285
+
3986
4286
  // src/types/context-window.ts
3987
4287
  var DEFAULT_CONTEXT_WINDOW_MODE_ID = "balanced";
3988
4288
  var CONTEXT_WINDOW_MODES = Object.freeze([
@@ -4676,8 +4976,8 @@ var DefaultConfigLoader = class {
4676
4976
  const t0 = Date.now();
4677
4977
  let mtimeMs = null;
4678
4978
  try {
4679
- const stat10 = await fsp2.stat(file);
4680
- mtimeMs = stat10.mtimeMs;
4979
+ const stat11 = await fsp2.stat(file);
4980
+ mtimeMs = stat11.mtimeMs;
4681
4981
  const cached = this.jsonCache.get(file);
4682
4982
  if (cached && cached.mtimeMs === mtimeMs) {
4683
4983
  return structuredClone(cached.value);
@@ -5019,11 +5319,11 @@ var DefaultSessionReader = class _DefaultSessionReader {
5019
5319
  if (!rootDir) {
5020
5320
  return await this.store.load(sessionId);
5021
5321
  }
5022
- const sessionPath = path2.join(rootDir, `${sessionId}.jsonl`);
5322
+ const sessionPath = sessionScopedPath(rootDir, sessionId, ".jsonl");
5023
5323
  let mtimeMs = null;
5024
5324
  try {
5025
- const stat10 = await fsp2.stat(sessionPath);
5026
- mtimeMs = stat10.mtimeMs;
5325
+ const stat11 = await fsp2.stat(sessionPath);
5326
+ mtimeMs = stat11.mtimeMs;
5027
5327
  } catch {
5028
5328
  this.eventCache.delete(sessionId);
5029
5329
  this.eventCacheMtimes.delete(sessionId);
@@ -5379,27 +5679,6 @@ function renderPlainText(meta, events) {
5379
5679
  }
5380
5680
  return lines.join("\n");
5381
5681
  }
5382
- function sessionScopedPath(dir, sessionId, suffix) {
5383
- if (!sessionId || sessionId.includes("\\") || sessionId.includes("..")) {
5384
- throw invalid(sessionId);
5385
- }
5386
- const resolved = path2.resolve(dir, `${sessionId}${suffix}`);
5387
- const rel = path2.relative(path2.resolve(dir), resolved);
5388
- if (rel.startsWith("..") || path2.isAbsolute(rel)) {
5389
- throw invalid(sessionId);
5390
- }
5391
- return resolved;
5392
- }
5393
- function invalid(sessionId) {
5394
- return new FsError({
5395
- message: `Invalid sessionId: ${sessionId}`,
5396
- code: ERROR_CODES.FS_DELETE_FAILED,
5397
- path: sessionId,
5398
- context: { reason: "path_traversal" }
5399
- });
5400
- }
5401
-
5402
- // src/storage/annotations-store.ts
5403
5682
  var FILE_VERSION = 1;
5404
5683
  var MAX_TEXT_LENGTH = 2e3;
5405
5684
  var MAX_ANNOTATIONS = 1e3;
@@ -5743,8 +6022,8 @@ var ReplayLogStore = class {
5743
6022
  const line = JSON.stringify(entry) + "\n";
5744
6023
  let offset2 = 0;
5745
6024
  try {
5746
- const stat10 = await fsp2.stat(fp);
5747
- offset2 = stat10.size;
6025
+ const stat11 = await fsp2.stat(fp);
6026
+ offset2 = stat11.size;
5748
6027
  } catch (err) {
5749
6028
  if (err.code !== "ENOENT") throw err;
5750
6029
  }
@@ -6084,15 +6363,15 @@ var SessionRecovery = class {
6084
6363
  async detectStale(sessionId) {
6085
6364
  const fp = this.filePath(sessionId);
6086
6365
  const TAIL_SIZE = 8192;
6087
- let stat10;
6366
+ let stat11;
6088
6367
  try {
6089
- stat10 = await fsp2.stat(fp);
6368
+ stat11 = await fsp2.stat(fp);
6090
6369
  } catch (err) {
6091
6370
  if (err.code === "ENOENT") return null;
6092
6371
  return null;
6093
6372
  }
6094
- if (stat10.size === 0) return null;
6095
- const position = Math.max(0, stat10.size - TAIL_SIZE);
6373
+ if (stat11.size === 0) return null;
6374
+ const position = Math.max(0, stat11.size - TAIL_SIZE);
6096
6375
  const buf = Buffer.alloc(TAIL_SIZE);
6097
6376
  let fh;
6098
6377
  try {
@@ -6904,11 +7183,11 @@ var SessionRegistry = class {
6904
7183
  */
6905
7184
  async breakStaleLock(lockPath) {
6906
7185
  try {
6907
- const [stat10, content] = await Promise.all([
7186
+ const [stat11, content] = await Promise.all([
6908
7187
  fsp2.stat(lockPath),
6909
7188
  fsp2.readFile(lockPath, "utf8").catch(() => "")
6910
7189
  ]);
6911
- const ageMs = Date.now() - stat10.mtimeMs;
7190
+ const ageMs = Date.now() - stat11.mtimeMs;
6912
7191
  const ownerPid = Number.parseInt(content.trim(), 10);
6913
7192
  const ownerDead = Number.isInteger(ownerPid) && ownerPid > 0 && ownerPid !== process.pid && !pidAlive(ownerPid);
6914
7193
  if (ownerDead || ageMs > STALE_LOCK_MS) {
@@ -6951,9 +7230,9 @@ var SessionRegistry = class {
6951
7230
  for (const name of await fsp2.readdir(dir)) {
6952
7231
  const isTemp = (name.startsWith(`${base}.`) || name.startsWith(`.${base}.`)) && name.endsWith(".tmp");
6953
7232
  if (!isTemp) continue;
6954
- const stat10 = await fsp2.stat(path2.join(dir, name)).catch(() => null);
6955
- if (!stat10) continue;
6956
- if (now - stat10.mtimeMs > STALE_TMP_MS) stale.push({ name, mtimeMs: stat10.mtimeMs });
7233
+ const stat11 = await fsp2.stat(path2.join(dir, name)).catch(() => null);
7234
+ if (!stat11) continue;
7235
+ if (now - stat11.mtimeMs > STALE_TMP_MS) stale.push({ name, mtimeMs: stat11.mtimeMs });
6957
7236
  }
6958
7237
  stale.sort((a, b) => b.mtimeMs - a.mtimeMs);
6959
7238
  await Promise.all(
@@ -6991,6 +7270,7 @@ function clampPct(pct) {
6991
7270
  var AgentStatusTracker = class {
6992
7271
  events;
6993
7272
  registry;
7273
+ sessionId;
6994
7274
  leaderName;
6995
7275
  // Live agent map: agentId → AgentEntry
6996
7276
  agents = /* @__PURE__ */ new Map();
@@ -7016,6 +7296,7 @@ var AgentStatusTracker = class {
7016
7296
  constructor(opts) {
7017
7297
  this.events = opts.events;
7018
7298
  this.registry = opts.registry;
7299
+ this.sessionId = opts.sessionId;
7019
7300
  this.leaderName = opts.leaderName ?? "leader";
7020
7301
  this.onUpdate = opts.onUpdate;
7021
7302
  }
@@ -7024,8 +7305,12 @@ var AgentStatusTracker = class {
7024
7305
  return this.lastAgents.length > 0 ? [...this.lastAgents] : [];
7025
7306
  }
7026
7307
  start() {
7308
+ const on = (pattern, fn) => this.events.onPattern(pattern, (event, payload) => {
7309
+ if (!this.acceptsSession(payload)) return;
7310
+ fn(event, payload);
7311
+ });
7027
7312
  this.unsubscribers.push(
7028
- this.events.onPattern("agent.run.started", (_event, payload) => {
7313
+ on("agent.run.started", (_event, payload) => {
7029
7314
  const p = payload;
7030
7315
  this.markLeaderStarted(p?.at);
7031
7316
  this.captureLeaderContext(p?.ctx);
@@ -7036,7 +7321,7 @@ var AgentStatusTracker = class {
7036
7321
  })
7037
7322
  );
7038
7323
  this.unsubscribers.push(
7039
- this.events.onPattern("iteration.started", (_e, payload) => {
7324
+ on("iteration.started", (_e, payload) => {
7040
7325
  const p = payload;
7041
7326
  const ctx = p?.ctx;
7042
7327
  this.markLeaderStarted();
@@ -7053,7 +7338,7 @@ var AgentStatusTracker = class {
7053
7338
  })
7054
7339
  );
7055
7340
  this.unsubscribers.push(
7056
- this.events.onPattern("agent.run.completed", (_event, payload) => {
7341
+ on("agent.run.completed", (_event, payload) => {
7057
7342
  const p = payload;
7058
7343
  this.captureLeaderContext(p?.ctx);
7059
7344
  this.leaderStatus = p?.status === "failed" ? "error" : "idle";
@@ -7064,7 +7349,7 @@ var AgentStatusTracker = class {
7064
7349
  })
7065
7350
  );
7066
7351
  this.unsubscribers.push(
7067
- this.events.onPattern("agent.run.error", (_event, payload) => {
7352
+ on("agent.run.error", (_event, payload) => {
7068
7353
  const p = payload;
7069
7354
  this.captureLeaderContext(p?.ctx);
7070
7355
  this.leaderStatus = "error";
@@ -7074,7 +7359,7 @@ var AgentStatusTracker = class {
7074
7359
  })
7075
7360
  );
7076
7361
  this.unsubscribers.push(
7077
- this.events.onPattern("tool.started", (_event, payload) => {
7362
+ on("tool.started", (_event, payload) => {
7078
7363
  const p = payload;
7079
7364
  if (p?.name) {
7080
7365
  this.markLeaderStarted();
@@ -7086,20 +7371,20 @@ var AgentStatusTracker = class {
7086
7371
  })
7087
7372
  );
7088
7373
  this.unsubscribers.push(
7089
- this.events.onPattern("tool.executed", () => {
7374
+ on("tool.executed", () => {
7090
7375
  this.leaderCurrentTool = void 0;
7091
7376
  this.flush();
7092
7377
  })
7093
7378
  );
7094
7379
  this.unsubscribers.push(
7095
- this.events.onPattern("brain.ask_human", () => {
7380
+ on("brain.ask_human", () => {
7096
7381
  this.markLeaderStarted();
7097
7382
  this.leaderStatus = "waiting_user";
7098
7383
  this.flush();
7099
7384
  })
7100
7385
  );
7101
7386
  this.unsubscribers.push(
7102
- this.events.onPattern("llm.stream_started", () => {
7387
+ on("llm.stream_started", () => {
7103
7388
  this.markLeaderStarted();
7104
7389
  this.leaderStatus = "streaming";
7105
7390
  this.leaderPartialText = "";
@@ -7107,7 +7392,7 @@ var AgentStatusTracker = class {
7107
7392
  })
7108
7393
  );
7109
7394
  this.unsubscribers.push(
7110
- this.events.onPattern("provider.text_delta", (_e, payload) => {
7395
+ on("provider.text_delta", (_e, payload) => {
7111
7396
  const p = payload;
7112
7397
  const text = p?.text;
7113
7398
  if (!text) return;
@@ -7120,14 +7405,14 @@ var AgentStatusTracker = class {
7120
7405
  })
7121
7406
  );
7122
7407
  this.unsubscribers.push(
7123
- this.events.onPattern("provider.response", (_e, payload) => {
7408
+ on("provider.response", (_e, payload) => {
7124
7409
  const p = payload;
7125
7410
  this.captureLeaderContext(p?.ctx);
7126
7411
  this.flush();
7127
7412
  })
7128
7413
  );
7129
7414
  this.unsubscribers.push(
7130
- this.events.onPattern("provider.fallback", (_e, payload) => {
7415
+ on("provider.fallback", (_e, payload) => {
7131
7416
  const p = payload;
7132
7417
  if (p?.to?.model) {
7133
7418
  this.leaderModel = p.to.providerId ? `${p.to.providerId}/${p.to.model}` : p.to.model;
@@ -7136,7 +7421,7 @@ var AgentStatusTracker = class {
7136
7421
  })
7137
7422
  );
7138
7423
  this.unsubscribers.push(
7139
- this.events.onPattern("ctx.pct", (_e, payload) => {
7424
+ on("ctx.pct", (_e, payload) => {
7140
7425
  const p = payload;
7141
7426
  if (typeof p?.load === "number" && Number.isFinite(p.load)) {
7142
7427
  this.leaderCtxPct = clampPct(Math.round(p.load * 100));
@@ -7145,7 +7430,7 @@ var AgentStatusTracker = class {
7145
7430
  })
7146
7431
  );
7147
7432
  this.unsubscribers.push(
7148
- this.events.onPattern("token.accounted", (_e, payload) => {
7433
+ on("token.accounted", (_e, payload) => {
7149
7434
  const p = payload;
7150
7435
  if (!p) return;
7151
7436
  this.leaderTokensIn += p.usage?.input ?? 0;
@@ -7165,7 +7450,7 @@ var AgentStatusTracker = class {
7165
7450
  return entry;
7166
7451
  };
7167
7452
  this.unsubscribers.push(
7168
- this.events.onPattern("subagent.spawned", (_e, payload) => {
7453
+ on("subagent.spawned", (_e, payload) => {
7169
7454
  const p = payload;
7170
7455
  if (!p?.subagentId) return;
7171
7456
  const entry = touch(p.subagentId);
@@ -7177,7 +7462,7 @@ var AgentStatusTracker = class {
7177
7462
  })
7178
7463
  );
7179
7464
  this.unsubscribers.push(
7180
- this.events.onPattern("subagent.ctx_pct", (_e, payload) => {
7465
+ on("subagent.ctx_pct", (_e, payload) => {
7181
7466
  const p = payload;
7182
7467
  if (!p?.subagentId) return;
7183
7468
  const entry = touch(p.subagentId);
@@ -7186,7 +7471,7 @@ var AgentStatusTracker = class {
7186
7471
  })
7187
7472
  );
7188
7473
  this.unsubscribers.push(
7189
- this.events.onPattern("subagent.task_started", (_e, payload) => {
7474
+ on("subagent.task_started", (_e, payload) => {
7190
7475
  const p = payload;
7191
7476
  if (!p?.subagentId) return;
7192
7477
  const entry = touch(p.subagentId);
@@ -7197,7 +7482,7 @@ var AgentStatusTracker = class {
7197
7482
  })
7198
7483
  );
7199
7484
  this.unsubscribers.push(
7200
- this.events.onPattern("subagent.tool_executed", (_e, payload) => {
7485
+ on("subagent.tool_executed", (_e, payload) => {
7201
7486
  const p = payload;
7202
7487
  if (!p?.subagentId) return;
7203
7488
  const entry = touch(p.subagentId);
@@ -7209,7 +7494,7 @@ var AgentStatusTracker = class {
7209
7494
  })
7210
7495
  );
7211
7496
  this.unsubscribers.push(
7212
- this.events.onPattern("subagent.iteration_summary", (_e, payload) => {
7497
+ on("subagent.iteration_summary", (_e, payload) => {
7213
7498
  const p = payload;
7214
7499
  if (!p?.subagentId) return;
7215
7500
  const entry = touch(p.subagentId);
@@ -7226,7 +7511,7 @@ var AgentStatusTracker = class {
7226
7511
  })
7227
7512
  );
7228
7513
  this.unsubscribers.push(
7229
- this.events.onPattern("subagent.task_completed", (_e, payload) => {
7514
+ on("subagent.task_completed", (_e, payload) => {
7230
7515
  const p = payload;
7231
7516
  if (!p?.subagentId) return;
7232
7517
  const entry = this.agents.get(p.subagentId);
@@ -7241,7 +7526,7 @@ var AgentStatusTracker = class {
7241
7526
  })
7242
7527
  );
7243
7528
  this.unsubscribers.push(
7244
- this.events.onPattern("subagent.stopped", (_e, payload) => {
7529
+ on("subagent.stopped", (_e, payload) => {
7245
7530
  const p = payload;
7246
7531
  if (!p?.subagentId) return;
7247
7532
  if (this.agents.delete(p.subagentId)) this.flush();
@@ -7318,7 +7603,10 @@ var AgentStatusTracker = class {
7318
7603
  const allAgents = [leaderEntry, ...this.agents.values()];
7319
7604
  this.lastAgents = allAgents;
7320
7605
  try {
7321
- this.events.emit("session.agents_updated", { agents: allAgents });
7606
+ this.events.emit("session.agents_updated", {
7607
+ sessionId: this.currentSessionId(),
7608
+ agents: allAgents
7609
+ });
7322
7610
  } catch {
7323
7611
  }
7324
7612
  this.registry.updateAgents(allAgents).then(() => {
@@ -7328,6 +7616,16 @@ var AgentStatusTracker = class {
7328
7616
  }
7329
7617
  }).catch(() => void 0);
7330
7618
  }
7619
+ currentSessionId() {
7620
+ return typeof this.sessionId === "function" ? this.sessionId() : this.sessionId;
7621
+ }
7622
+ acceptsSession(payload) {
7623
+ const expected = this.currentSessionId();
7624
+ if (!expected) return true;
7625
+ if (typeof payload !== "object" || payload === null) return true;
7626
+ const actual = payload.sessionId;
7627
+ return typeof actual !== "string" || actual.length === 0 || actual === expected;
7628
+ }
7331
7629
  markLeaderStarted(startedAt) {
7332
7630
  if (this.leaderStartedAt && (this.leaderStatus === "running" || this.leaderStatus === "streaming" || this.leaderStatus === "waiting_user")) {
7333
7631
  return;
@@ -7432,8 +7730,11 @@ var DefaultSessionRewinder = class {
7432
7730
  }
7433
7731
  sessionsDir;
7434
7732
  projectRoot;
7733
+ sessionFile(sessionId) {
7734
+ return sessionScopedPath(this.sessionsDir, sessionId, ".jsonl");
7735
+ }
7435
7736
  async listCheckpoints(sessionId) {
7436
- const file = path2.join(this.sessionsDir, `${sessionId}.jsonl`);
7737
+ const file = this.sessionFile(sessionId);
7437
7738
  const raw = await fsp2.readFile(file, "utf8");
7438
7739
  const events = parseEvents(raw);
7439
7740
  const fileCountMap = /* @__PURE__ */ new Map();
@@ -7458,7 +7759,7 @@ var DefaultSessionRewinder = class {
7458
7759
  return checkpoints;
7459
7760
  }
7460
7761
  async rewindToCheckpoint(sessionId, checkpointIndex) {
7461
- const file = path2.join(this.sessionsDir, `${sessionId}.jsonl`);
7762
+ const file = this.sessionFile(sessionId);
7462
7763
  const raw = await fsp2.readFile(file, "utf8");
7463
7764
  const events = parseEvents(raw);
7464
7765
  let targetIdx = -1;
@@ -7497,7 +7798,7 @@ var DefaultSessionRewinder = class {
7497
7798
  return { ...result, toPromptIndex: checkpointIndex, removedEvents };
7498
7799
  }
7499
7800
  async rewindLastN(sessionId, n) {
7500
- const file = path2.join(this.sessionsDir, `${sessionId}.jsonl`);
7801
+ const file = this.sessionFile(sessionId);
7501
7802
  const raw = await fsp2.readFile(file, "utf8");
7502
7803
  const events = parseEvents(raw);
7503
7804
  const checkpoints = [];
@@ -7526,7 +7827,7 @@ var DefaultSessionRewinder = class {
7526
7827
  return { ...result, toPromptIndex: targetIndex, removedEvents: snapshotsToRevert.length };
7527
7828
  }
7528
7829
  async rewindToStart(sessionId) {
7529
- const file = path2.join(this.sessionsDir, `${sessionId}.jsonl`);
7830
+ const file = this.sessionFile(sessionId);
7530
7831
  const raw = await fsp2.readFile(file, "utf8");
7531
7832
  const events = parseEvents(raw);
7532
7833
  const allSnapshots = [];
@@ -7911,7 +8212,12 @@ async function mutatePlan(filePath, sessionId, fn) {
7911
8212
  const updated = await fn(plan);
7912
8213
  const persisted = await savePlan(filePath, updated);
7913
8214
  if (!persisted) {
7914
- throw new Error(`Failed to persist plan to ${filePath} \u2014 the change was NOT saved.`);
8215
+ throw new SessionError({
8216
+ message: `Failed to persist plan to ${filePath} \u2014 the change was NOT saved.`,
8217
+ code: "SESSION_WRITE_FAILED",
8218
+ sessionId,
8219
+ context: { filePath, operation: "mutatePlan" }
8220
+ });
7915
8221
  }
7916
8222
  return updated;
7917
8223
  });
@@ -8135,7 +8441,12 @@ async function mutateTasks(filePath, sessionId, fn, events, traceId) {
8135
8441
  const updated = await fn(file);
8136
8442
  const persisted = await saveTasks(filePath, updated, events, traceId);
8137
8443
  if (!persisted) {
8138
- throw new Error(`Failed to persist tasks to ${filePath} \u2014 the change was NOT saved.`);
8444
+ throw new SessionError({
8445
+ message: `Failed to persist tasks to ${filePath} \u2014 the change was NOT saved.`,
8446
+ code: "SESSION_WRITE_FAILED",
8447
+ sessionId,
8448
+ context: { filePath, operation: "mutateTasks" }
8449
+ });
8139
8450
  }
8140
8451
  return updated;
8141
8452
  });
@@ -8968,8 +9279,8 @@ var CloudSync = class {
8968
9279
  const localPath = this.categoryToPath(cat);
8969
9280
  if (!localPath) continue;
8970
9281
  try {
8971
- const stat10 = await fsp2.stat(localPath);
8972
- if (stat10.isDirectory()) {
9282
+ const stat11 = await fsp2.stat(localPath);
9283
+ if (stat11.isDirectory()) {
8973
9284
  const files = await this.walkDir(localPath, localPath);
8974
9285
  for (const file of files) {
8975
9286
  const content = await fsp2.readFile(file, "utf8");
@@ -8994,8 +9305,8 @@ var CloudSync = class {
8994
9305
  const localPath = this.categoryToPath(cat);
8995
9306
  if (!localPath) continue;
8996
9307
  try {
8997
- const stat10 = await fsp2.stat(localPath);
8998
- if (stat10.isDirectory()) {
9308
+ const stat11 = await fsp2.stat(localPath);
9309
+ if (stat11.isDirectory()) {
8999
9310
  const files = await this.walkDir(localPath, localPath);
9000
9311
  for (const file of files) {
9001
9312
  const content = await fsp2.readFile(file);
@@ -9201,6 +9512,6 @@ function resolveSessionLoggingConfig(cfg) {
9201
9512
  };
9202
9513
  }
9203
9514
 
9204
- 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, PromptUsageStore, QueueStore, RecoveryLock, ReplayLogStore, STANDARD_AUDIT_EVENTS, SessionAnalyzer, SessionMemoryConsolidator, SessionRecovery, SessionRegistry, ToolAuditLog, addPlanItem, appendJournal, attachPlanCheckpoint, attachTodosCheckpoint, clearPlan, createSessionEventBridge, deriveTodosFromPlanItem, emptyGoal, emptyPlan, emptyTaskFile, formatGoal, formatPlan, formatPlanTemplates, generateSessionId, getPlanTemplate, getSessionRegistry, goalFilePath, hasSessionRegistry, listPlanTemplates, loadDirectorState, loadGoal, loadPlan, loadTasks, loadTodosCheckpoint, migratePromptEntry, mutatePlan, mutateTasks, parseEntries, parseProgressFromText, promptChecksum, recordProgress, removePlanItem, resolveAuditLevel, resolveSessionLoggingConfig, runConfigMigrations, sanitizeModel, saveGoal, savePlan, saveTasks, saveTodosCheckpoint, setPlanItemStatus, setProgress, summarizeUsage };
9515
+ 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, PromptUsageStore, QueueStore, RecoveryLock, ReplayLogStore, STANDARD_AUDIT_EVENTS, SessionAnalyzer, SessionMemoryConsolidator, SessionRecovery, SessionRegistry, ToolAuditLog, addPlanItem, appendJournal, attachPlanCheckpoint, attachTodosCheckpoint, clearPlan, createSessionEventBridge, deriveTodosFromPlanItem, emptyGoal, emptyPlan, emptyTaskFile, formatGoal, formatPlan, formatPlanTemplates, generateSessionId, getPlanTemplate, getSessionRegistry, goalFilePath, hasSessionRegistry, listPlanTemplates, loadDirectorState, loadGoal, loadPlan, loadTasks, loadTodosCheckpoint, migratePromptEntry, mutatePlan, mutateTasks, parseEntries, parseProgressFromText, promptChecksum, recordProgress, removePlanItem, resolveAuditLevel, resolveSessionLoggingConfig, runConfigMigrations, sanitizeModel, saveGoal, savePlan, saveTasks, saveTodosCheckpoint, setPlanItemStatus, setProgress, summarizeUsage, watchProviderConfig };
9205
9516
  //# sourceMappingURL=index.js.map
9206
9517
  //# sourceMappingURL=index.js.map