@wrongstack/core 0.257.2 → 0.264.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (82) hide show
  1. package/dist/{agent-bridge-BrxWHEOm.d.ts → agent-bridge-D8sa1vtv.d.ts} +1 -1
  2. package/dist/{agent-subagent-runner-US741uBH.d.ts → agent-subagent-runner-c9DLkaas.d.ts} +31 -9
  3. package/dist/{brain-TjEEwSpw.d.ts → brain-O1IdKPaK.d.ts} +59 -2
  4. package/dist/{compactor-C5sT4U7I.d.ts → compactor-BBy0rCtB.d.ts} +1 -1
  5. package/dist/{config-DuAu23zm.d.ts → config-Dz2F3H2K.d.ts} +7 -1
  6. package/dist/{context-CGdgA0q6.d.ts → context-BGSpZNSE.d.ts} +33 -0
  7. package/dist/coordination/index.d.ts +1681 -15
  8. package/dist/coordination/index.js +2826 -405
  9. package/dist/coordination/index.js.map +1 -1
  10. package/dist/defaults/index.d.ts +25 -25
  11. package/dist/defaults/index.js +2258 -1433
  12. package/dist/defaults/index.js.map +1 -1
  13. package/dist/dispatcher-types.d-BBeXBQgS.d.ts +66 -0
  14. package/dist/execution/index.d.ts +15 -15
  15. package/dist/execution/index.js +502 -398
  16. package/dist/execution/index.js.map +1 -1
  17. package/dist/execution/prompt-enhancer.d.ts +2 -2
  18. package/dist/execution/prompt-enhancer.js +7 -1
  19. package/dist/execution/prompt-enhancer.js.map +1 -1
  20. package/dist/extension/index.d.ts +6 -6
  21. package/dist/extension/index.js.map +1 -1
  22. package/dist/{goal-preamble-CznHTZqP.d.ts → goal-preamble-DzjFuN3p.d.ts} +21 -9
  23. package/dist/{goal-store-CV9Yz2X_.d.ts → goal-store-CxWmCGbH.d.ts} +4 -2
  24. package/dist/{index-CC0Mcm05.d.ts → index-CYIQrXVF.d.ts} +8 -8
  25. package/dist/{index-CitPrI3a.d.ts → index-CbLSI66_.d.ts} +5 -5
  26. package/dist/index.d.ts +50 -94
  27. package/dist/index.js +16009 -12406
  28. package/dist/index.js.map +1 -1
  29. package/dist/infrastructure/index.d.ts +6 -6
  30. package/dist/kernel/index.d.ts +9 -9
  31. package/dist/kernel/index.js +6 -1
  32. package/dist/kernel/index.js.map +1 -1
  33. package/dist/{llm-selector-CJ4SyAFE.d.ts → llm-selector-DzxuZnNz.d.ts} +2 -2
  34. package/dist/{mcp-servers-D8YnLaEp.d.ts → mcp-servers-DC4QRPUI.d.ts} +3 -3
  35. package/dist/models/index.d.ts +5 -5
  36. package/dist/models/index.js +6 -1
  37. package/dist/models/index.js.map +1 -1
  38. package/dist/{models-registry-ByZCdFuQ.d.ts → models-registry-B_siPxqN.d.ts} +1 -1
  39. package/dist/{multi-agent-coordinator-DqTUEAeC.d.ts → multi-agent-coordinator-CK5Jdj9K.d.ts} +2 -2
  40. package/dist/{null-fleet-bus-B5mfTJXT.d.ts → null-fleet-bus-DgvD4SCO.d.ts} +13 -8
  41. package/dist/observability/index.d.ts +2 -2
  42. package/dist/observability/index.js +8 -3
  43. package/dist/observability/index.js.map +1 -1
  44. package/dist/{parallel-eternal-engine-C0juOszP.d.ts → parallel-eternal-engine-bK0JQBR_.d.ts} +13 -9
  45. package/dist/{path-resolver-CbkT-RMU.d.ts → path-resolver-BPEDlN38.d.ts} +3 -3
  46. package/dist/{permission-CwBBpCoF.d.ts → permission-4yvGmMRB.d.ts} +1 -1
  47. package/dist/{permission-policy-B8rSu908.d.ts → permission-policy-C6XpsBOy.d.ts} +3 -2
  48. package/dist/{pipeline-JG8XoudC.d.ts → pipeline-CXCeMz8J.d.ts} +58 -3
  49. package/dist/{plan-templates-DPiQMkBz.d.ts → plan-templates-BvzRBkJc.d.ts} +32 -11
  50. package/dist/{provider-runner-hM7EXlLI.d.ts → provider-runner-C5aQpDWE.d.ts} +3 -3
  51. package/dist/{retry-policy-Tg7LXkoK.d.ts → retry-policy-CFhdtRzz.d.ts} +1 -1
  52. package/dist/sdd/index.d.ts +8 -8
  53. package/dist/sdd/index.js +59 -31
  54. package/dist/sdd/index.js.map +1 -1
  55. package/dist/{secret-vault-gxtFZYBt.d.ts → secret-vault-CxiVLbt1.d.ts} +1 -1
  56. package/dist/security/index.d.ts +4 -4
  57. package/dist/security/index.js +238 -204
  58. package/dist/security/index.js.map +1 -1
  59. package/dist/{selector-DWsqVjGf.d.ts → selector-gIuhRTkN.d.ts} +1 -1
  60. package/dist/{session-event-bridge-BAFWdgQ3.d.ts → session-event-bridge-DkvvrpDt.d.ts} +8 -2
  61. package/dist/{session-reader-CqRvaL5v.d.ts → session-reader-KdfVwkKP.d.ts} +1 -1
  62. package/dist/skills/index.js +67 -64
  63. package/dist/skills/index.js.map +1 -1
  64. package/dist/storage/index.d.ts +50 -22
  65. package/dist/storage/index.js +1654 -525
  66. package/dist/storage/index.js.map +1 -1
  67. package/dist/tools/index.d.ts +57 -0
  68. package/dist/tools/index.js +411 -0
  69. package/dist/tools/index.js.map +1 -0
  70. package/dist/types/index.d.ts +19 -19
  71. package/dist/types/index.js +711 -694
  72. package/dist/types/index.js.map +1 -1
  73. package/dist/utils/error.d.ts +7 -0
  74. package/dist/utils/error.js +8 -0
  75. package/dist/utils/error.js.map +1 -0
  76. package/dist/utils/index.d.ts +7 -67
  77. package/dist/utils/index.js +17 -5
  78. package/dist/utils/index.js.map +1 -1
  79. package/package.json +5 -1
  80. package/skills/output-standards/SKILL.md +14 -9
  81. package/skills/output-standards/SKILL.save.md +3 -2
  82. package/dist/package-outdated-watcher-BSgR_kK-.d.ts +0 -581
@@ -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 stat6 = await fsp.stat(targetPath);
39
+ mode = stat6.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 stat6 = await fsp.stat(lockPath);
80
+ if (Date.now() - stat6.mtimeMs > staleMs) {
76
81
  await fsp.unlink(lockPath);
77
82
  continue;
78
83
  }
@@ -214,6 +219,215 @@ 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
+ };
334
+ }
335
+
336
+ // src/utils/deep-merge.ts
337
+ var FORBIDDEN_PROTO_KEYS = /* @__PURE__ */ new Set([
338
+ "__proto__",
339
+ "constructor",
340
+ "prototype",
341
+ "__defineGetter__",
342
+ "__defineSetter__",
343
+ "__lookupGetter__",
344
+ "__lookupSetter__"
345
+ ]);
346
+ function isPrimitiveArray(a) {
347
+ return a.every((v) => v === null || typeof v !== "object" && typeof v !== "function");
348
+ }
349
+ function deepMerge(base, patch, options = {}) {
350
+ const {
351
+ conflictResolution = "prefer-patch",
352
+ arrayMode = "replace",
353
+ protectProto = true,
354
+ onNonPrimitiveArrayReplace
355
+ } = options;
356
+ if (typeof base !== "object" || base === null) {
357
+ return conflictResolution === "prefer-patch" ? patch : base;
358
+ }
359
+ if (typeof patch !== "object" || patch === null) {
360
+ return conflictResolution === "prefer-patch" ? patch : base;
361
+ }
362
+ if (Array.isArray(base) && Array.isArray(patch)) {
363
+ if (arrayMode === "concat-primitives" && isPrimitiveArray(base) && isPrimitiveArray(patch)) {
364
+ return [.../* @__PURE__ */ new Set([...base, ...patch])];
365
+ }
366
+ return conflictResolution === "prefer-patch" ? patch : base;
367
+ }
368
+ if (Array.isArray(base) || Array.isArray(patch)) {
369
+ return conflictResolution === "prefer-patch" ? patch : base;
370
+ }
371
+ const baseObj = base;
372
+ const patchObj = patch;
373
+ const out = { ...baseObj };
374
+ for (const [k, v] of Object.entries(patchObj)) {
375
+ if (protectProto && FORBIDDEN_PROTO_KEYS.has(k)) continue;
376
+ const existing = out[k];
377
+ if (v !== null && typeof v === "object" && !Array.isArray(v) && existing !== null && typeof existing === "object" && !Array.isArray(existing)) {
378
+ out[k] = deepMerge(existing, v, options);
379
+ } else if (Array.isArray(v) && Array.isArray(existing)) {
380
+ if (onNonPrimitiveArrayReplace && !isPrimitiveArray(v)) {
381
+ onNonPrimitiveArrayReplace(k, existing.length, v.length);
382
+ }
383
+ out[k] = deepMerge(existing, v, options);
384
+ } else if (v !== void 0) {
385
+ if (onNonPrimitiveArrayReplace && Array.isArray(v) && !isPrimitiveArray(v)) {
386
+ const existingLen = Array.isArray(existing) ? existing.length : 0;
387
+ onNonPrimitiveArrayReplace(k, existingLen, v.length);
388
+ }
389
+ out[k] = v;
390
+ }
391
+ }
392
+ return out;
393
+ }
394
+
395
+ // src/utils/regex-guard.ts
396
+ var MAX_PATTERN_LEN = 512;
397
+ var DANGEROUS_PATTERNS = [
398
+ /(\([^)]*[+*][^)]*\))[+*]/,
399
+ // (a+)+, (.*)+, etc
400
+ /(\(\?:[^)]*[+*][^)]*\))[+*]/
401
+ // same, with non-capturing group
402
+ ];
403
+ function compileUserRegex(pattern, flags) {
404
+ if (typeof pattern !== "string") {
405
+ return { ok: false, reason: "pattern must be a string" };
406
+ }
407
+ if (pattern.length === 0) {
408
+ return { ok: false, reason: "pattern is empty" };
409
+ }
410
+ if (pattern.length > MAX_PATTERN_LEN) {
411
+ return { ok: false, reason: `pattern exceeds ${MAX_PATTERN_LEN} characters` };
412
+ }
413
+ for (const rx of DANGEROUS_PATTERNS) {
414
+ if (rx.test(pattern)) {
415
+ return {
416
+ ok: false,
417
+ reason: "pattern looks vulnerable to catastrophic backtracking \u2014 rewrite without nested quantifiers"
418
+ };
419
+ }
420
+ }
421
+ try {
422
+ return { ok: true, regex: new RegExp(pattern, flags) };
423
+ } catch (err) {
424
+ return {
425
+ ok: false,
426
+ reason: err instanceof Error ? err.message : "invalid regex"
427
+ };
428
+ }
429
+ }
430
+
217
431
  // src/storage/session-store.ts
218
432
  function sanitizeModel(model) {
219
433
  return model.replace(/[^a-zA-Z0-9_-]/g, "-").replace(/-+/g, "-").replace(/^-|-$/g, "").slice(0, 40);
@@ -234,13 +448,47 @@ var DefaultSessionStore = class _DefaultSessionStore {
234
448
  this.events = opts.events;
235
449
  this.secretScrubber = opts.secretScrubber;
236
450
  }
451
+ // ── Storage event helpers ───────────────────────────────────────────────────
452
+ emitRead(sessionId, filePath, operation, outcome, durationMs, error) {
453
+ this.events?.emit("storage.read", {
454
+ sessionId,
455
+ store: "session",
456
+ filePath,
457
+ operation,
458
+ outcome,
459
+ durationMs,
460
+ ...error !== void 0 ? { error } : {}
461
+ });
462
+ }
463
+ emitWrite(sessionId, filePath, operation, outcome, durationMs, eventCount, error) {
464
+ this.events?.emit("storage.write", {
465
+ sessionId,
466
+ store: "session",
467
+ filePath,
468
+ operation,
469
+ outcome,
470
+ durationMs,
471
+ ...eventCount !== void 0 ? { eventCount } : {},
472
+ ...error !== void 0 ? { error } : {}
473
+ });
474
+ }
475
+ emitError(sessionId, filePath, operation, error, recoverable) {
476
+ this.events?.emit("storage.error", {
477
+ sessionId,
478
+ store: "session",
479
+ filePath,
480
+ operation,
481
+ error,
482
+ recoverable
483
+ });
484
+ }
237
485
  /** Absolute path to the session index file. */
238
486
  get indexFile() {
239
- return path13.join(this.dir, "_index.jsonl");
487
+ return path2.join(this.dir, "_index.jsonl");
240
488
  }
241
489
  /** Join session ID to its absolute path within the store directory. */
242
490
  sessionPath(id, ext) {
243
- return path13.join(this.dir, `${id}${ext}`);
491
+ return path2.join(this.dir, `${id}${ext}`);
244
492
  }
245
493
  /**
246
494
  * Ensure the directory implied by the session ID exists. When the ID
@@ -248,7 +496,7 @@ var DefaultSessionStore = class _DefaultSessionStore {
248
496
  * subdirectory so sessions group naturally by day.
249
497
  */
250
498
  async ensureShardDir(id) {
251
- const dirPath = path13.dirname(path13.join(this.dir, id));
499
+ const dirPath = path2.dirname(path2.join(this.dir, id));
252
500
  await ensureDir(dirPath);
253
501
  return dirPath;
254
502
  }
@@ -256,23 +504,27 @@ var DefaultSessionStore = class _DefaultSessionStore {
256
504
  const startedAt = (/* @__PURE__ */ new Date()).toISOString();
257
505
  const id = meta.id && meta.id.length > 0 ? meta.id : generateSessionId(startedAt, meta.model ?? meta.provider);
258
506
  const shardDir = await this.ensureShardDir(id);
259
- const file = path13.join(shardDir, `${path13.basename(id)}.jsonl`);
507
+ const file = path2.join(shardDir, `${path2.basename(id)}.jsonl`);
508
+ const t0 = Date.now();
260
509
  let handle;
261
510
  try {
262
511
  handle = await fsp.open(file, "a", 384);
263
512
  } catch (err) {
513
+ this.emitError(id, file, "create", toErrorMessage(err), false);
264
514
  throw new Error(
265
- `Failed to open session file: ${err instanceof Error ? err.message : String(err)}`,
515
+ `Failed to open session file: ${toErrorMessage(err)}`,
266
516
  { cause: err }
267
517
  );
268
518
  }
269
519
  try {
270
- return new FileSessionWriter(id, handle, startedAt, meta, this.events, {
520
+ const writer = new FileSessionWriter(id, handle, startedAt, meta, this.events, {
271
521
  dir: shardDir,
272
522
  filePath: file,
273
523
  secretScrubber: this.secretScrubber,
274
524
  onClose: (s) => this.appendToIndex(s)
275
525
  });
526
+ this.emitWrite(id, file, "create", "success", Date.now() - t0);
527
+ return writer;
276
528
  } catch (err) {
277
529
  await handle.close().catch((e) => console.warn(JSON.stringify({
278
530
  level: "warn",
@@ -280,18 +532,21 @@ var DefaultSessionStore = class _DefaultSessionStore {
280
532
  message: e instanceof Error ? e.message : String(e),
281
533
  timestamp: (/* @__PURE__ */ new Date()).toISOString()
282
534
  })));
535
+ this.emitError(id, file, "create", toErrorMessage(err), true);
283
536
  throw err;
284
537
  }
285
538
  }
286
539
  async resume(id) {
287
540
  const file = this.sessionPath(id, ".jsonl");
541
+ const t0 = Date.now();
288
542
  const data = await this.load(id);
289
543
  let handle;
290
544
  try {
291
545
  handle = await fsp.open(file, "a", 384);
292
546
  } catch (err) {
547
+ this.emitError(id, file, "resume", toErrorMessage(err), false);
293
548
  throw new Error(
294
- `Failed to open session "${id}" for append: ${err instanceof Error ? err.message : String(err)}`,
549
+ `Failed to open session "${id}" for append: ${toErrorMessage(err)}`,
295
550
  { cause: err }
296
551
  );
297
552
  }
@@ -311,12 +566,13 @@ var DefaultSessionStore = class _DefaultSessionStore {
311
566
  // Shard directory (sessions/<date>/) — must match create() so the
312
567
  // .summary.json sidecar lands next to the JSONL instead of the
313
568
  // sessions root (where summaryFor() would never find it).
314
- dir: path13.dirname(file),
569
+ dir: path2.dirname(file),
315
570
  filePath: file,
316
571
  secretScrubber: this.secretScrubber,
317
572
  onClose: (s) => this.appendToIndex(s)
318
573
  }
319
574
  );
575
+ this.emitWrite(id, file, "resume", "success", Date.now() - t0);
320
576
  return { writer, data };
321
577
  } catch (err) {
322
578
  await handle.close().catch((e) => console.warn(JSON.stringify({
@@ -325,27 +581,39 @@ var DefaultSessionStore = class _DefaultSessionStore {
325
581
  message: e instanceof Error ? e.message : String(e),
326
582
  timestamp: (/* @__PURE__ */ new Date()).toISOString()
327
583
  })));
584
+ this.emitError(id, file, "resume", toErrorMessage(err), true);
328
585
  throw err;
329
586
  }
330
587
  }
331
588
  async load(id) {
332
589
  const file = this.sessionPath(id, ".jsonl");
333
- const raw = await fsp.readFile(file, "utf8");
334
- const lines = raw.split("\n").filter((l) => l.trim());
335
- const events = [];
336
- for (const line of lines) {
337
- try {
338
- const parsed = JSON.parse(line);
339
- if (parsed !== null && typeof parsed === "object" && typeof parsed.type === "string" && typeof parsed.ts === "string") {
340
- events.push(parsed);
590
+ const t0 = Date.now();
591
+ let outcome = "success";
592
+ let errorMsg;
593
+ try {
594
+ const raw = await fsp.readFile(file, "utf8");
595
+ const lines = raw.split("\n").filter((l) => l.trim());
596
+ const events = [];
597
+ for (const line of lines) {
598
+ try {
599
+ const parsed = JSON.parse(line);
600
+ if (parsed !== null && typeof parsed === "object" && typeof parsed.type === "string" && typeof parsed.ts === "string") {
601
+ events.push(parsed);
602
+ }
603
+ } catch {
341
604
  }
342
- } catch {
343
605
  }
606
+ const meta = this.metaFromEvents(id, events);
607
+ const { messages, usage } = this.replay(events, id);
608
+ const toolCallEnds = extractToolCallEnds(events);
609
+ return { metadata: meta, events, messages, usage, toolCallEnds };
610
+ } catch (err) {
611
+ outcome = "failure";
612
+ errorMsg = toErrorMessage(err);
613
+ throw err;
614
+ } finally {
615
+ this.emitRead(id, file, "load", outcome, Date.now() - t0, errorMsg);
344
616
  }
345
- const meta = this.metaFromEvents(id, events);
346
- const { messages, usage } = this.replay(events, id);
347
- const toolCallEnds = extractToolCallEnds(events);
348
- return { metadata: meta, events, messages, usage, toolCallEnds };
349
617
  }
350
618
  async list(limit = 20) {
351
619
  try {
@@ -412,12 +680,22 @@ var DefaultSessionStore = class _DefaultSessionStore {
412
680
  * (keep latest per session), and rewrite. Atomic via temp+rename.
413
681
  */
414
682
  async compactIndex() {
415
- const entries = await this.readIndex();
416
- if (entries.length === 0) return;
417
- const tmp = `${this.indexFile}.compact.tmp`;
418
- const lines = entries.map((s) => JSON.stringify(s)).join("\n") + "\n";
419
- await fsp.writeFile(tmp, lines, "utf8");
420
- await fsp.rename(tmp, this.indexFile);
683
+ const t0 = Date.now();
684
+ let outcome = "success";
685
+ let errorMsg;
686
+ try {
687
+ const entries = await this.readIndex();
688
+ if (entries.length === 0) return;
689
+ const tmp = `${this.indexFile}.compact.tmp`;
690
+ const lines = entries.map((s) => JSON.stringify(s)).join("\n") + "\n";
691
+ await fsp.writeFile(tmp, lines, "utf8");
692
+ await fsp.rename(tmp, this.indexFile);
693
+ } catch (err) {
694
+ outcome = "failure";
695
+ errorMsg = toErrorMessage(err);
696
+ } finally {
697
+ this.emitWrite("~compact~", this.indexFile, "compact", outcome, Date.now() - t0, void 0, errorMsg);
698
+ }
421
699
  }
422
700
  /**
423
701
  * Read the index file and return deduplicated session summaries.
@@ -482,7 +760,7 @@ var DefaultSessionStore = class _DefaultSessionStore {
482
760
  continue;
483
761
  if (entry.isDirectory()) {
484
762
  const childPrefix = depth === 0 ? entry.name : `${prefix}/${entry.name}`;
485
- ids.push(...await this.collectSessionIds(path13.join(dir, entry.name), childPrefix, depth + 1));
763
+ ids.push(...await this.collectSessionIds(path2.join(dir, entry.name), childPrefix, depth + 1));
486
764
  } else if (entry.isFile() && entry.name.endsWith(".jsonl")) {
487
765
  if (entry.name === "_index.jsonl") continue;
488
766
  const base = entry.name.replace(/\.jsonl$/, "");
@@ -493,22 +771,31 @@ var DefaultSessionStore = class _DefaultSessionStore {
493
771
  }
494
772
  async summaryFor(id) {
495
773
  const manifest = this.sessionPath(id, ".summary.json");
774
+ const t0 = Date.now();
775
+ let outcome = "success";
776
+ let errorMsg;
496
777
  try {
497
778
  const raw = await fsp.readFile(manifest, "utf8");
779
+ this.emitRead(id, manifest, "summary", "success", Date.now() - t0);
498
780
  return JSON.parse(raw);
499
781
  } catch {
500
782
  const full = this.sessionPath(id, ".jsonl");
501
- const stat5 = await fsp.stat(full);
502
- const summary = await this.summarize(id, stat5.mtime.toISOString());
783
+ const stat6 = await fsp.stat(full);
784
+ const summary = await this.summarize(id, stat6.mtime.toISOString());
503
785
  await atomicWrite(manifest, JSON.stringify(summary), { mode: 384 }).catch((err) => {
786
+ const msg = toErrorMessage(err);
787
+ this.emitError(id, manifest, "summary_fallback", msg, true);
504
788
  console.warn(JSON.stringify({
505
789
  level: "warn",
506
790
  event: "session_store.manifest_write_failed",
507
791
  sessionId: id,
508
- message: err instanceof Error ? err.message : String(err),
792
+ message: msg,
509
793
  timestamp: (/* @__PURE__ */ new Date()).toISOString()
510
794
  }));
511
795
  });
796
+ outcome = "failure";
797
+ errorMsg = "summary fallback \u2014 manifest rebuilt";
798
+ this.emitRead(id, manifest, "summary", outcome, Date.now() - t0, errorMsg);
512
799
  return summary;
513
800
  }
514
801
  }
@@ -524,14 +811,14 @@ var DefaultSessionStore = class _DefaultSessionStore {
524
811
  async deleteSession(id) {
525
812
  const jsonlPath = this.sessionPath(id, ".jsonl");
526
813
  const summaryPath = this.sessionPath(id, ".summary.json");
527
- const shardDir = path13.dirname(path13.join(this.dir, id));
528
- const base = path13.basename(id);
529
- const sessDir = path13.join(shardDir, base);
814
+ const shardDir = path2.dirname(path2.join(this.dir, id));
815
+ const base = path2.basename(id);
816
+ const sessDir = path2.join(shardDir, base);
530
817
  const deletions = [
531
818
  fsp.unlink(jsonlPath),
532
819
  fsp.unlink(summaryPath),
533
- fsp.unlink(path13.join(shardDir, `${base}.plan.json`)),
534
- fsp.unlink(path13.join(shardDir, `${base}.todos.json`))
820
+ fsp.unlink(path2.join(shardDir, `${base}.plan.json`)),
821
+ fsp.unlink(path2.join(shardDir, `${base}.todos.json`))
535
822
  ];
536
823
  const results = await Promise.allSettled(deletions);
537
824
  for (const r of results) {
@@ -553,7 +840,7 @@ var DefaultSessionStore = class _DefaultSessionStore {
553
840
  level: "warn",
554
841
  event: "session_store.rmdir_failed",
555
842
  sessionId: id,
556
- message: err instanceof Error ? err.message : String(err),
843
+ message: toErrorMessage(err),
557
844
  timestamp: (/* @__PURE__ */ new Date()).toISOString()
558
845
  }));
559
846
  });
@@ -567,17 +854,17 @@ var DefaultSessionStore = class _DefaultSessionStore {
567
854
  let deleted = 0;
568
855
  let activeSessionId = null;
569
856
  try {
570
- const raw = await fsp.readFile(path13.join(this.dir, "active.json"), "utf8");
857
+ const raw = await fsp.readFile(path2.join(this.dir, "active.json"), "utf8");
571
858
  const active = JSON.parse(raw);
572
859
  activeSessionId = active.sessionId ?? null;
573
860
  } catch {
574
861
  }
575
862
  const isPrunableJsonl = (name) => name.endsWith(".jsonl") && name !== "_index.jsonl" && name !== "_mailbox.jsonl" && !name.endsWith(".replay.jsonl") && !name.endsWith(".audit.jsonl");
576
863
  const pruneFile = async (dir, name, prefix) => {
577
- const jsonlPath = path13.join(dir, name);
864
+ const jsonlPath = path2.join(dir, name);
578
865
  try {
579
- const stat5 = await fsp.stat(jsonlPath);
580
- if (stat5.mtimeMs >= cutoff) return;
866
+ const stat6 = await fsp.stat(jsonlPath);
867
+ if (stat6.mtimeMs >= cutoff) return;
581
868
  } catch {
582
869
  return;
583
870
  }
@@ -594,7 +881,7 @@ var DefaultSessionStore = class _DefaultSessionStore {
594
881
  continue;
595
882
  }
596
883
  if (!entry.isDirectory()) continue;
597
- const dateDir = path13.join(this.dir, entry.name);
884
+ const dateDir = path2.join(this.dir, entry.name);
598
885
  const files = await fsp.readdir(dateDir, { withFileTypes: true }).catch(() => []);
599
886
  for (const file of files) {
600
887
  if (!file.isFile() || !isPrunableJsonl(file.name)) continue;
@@ -606,7 +893,7 @@ var DefaultSessionStore = class _DefaultSessionStore {
606
893
  }
607
894
  for (const entry of entries) {
608
895
  if (!entry.isDirectory()) continue;
609
- const dateDir = path13.join(this.dir, entry.name);
896
+ const dateDir = path2.join(this.dir, entry.name);
610
897
  try {
611
898
  const remaining = await fsp.readdir(dateDir);
612
899
  if (remaining.length === 0) {
@@ -774,14 +1061,14 @@ function extractToolCallEnds(events) {
774
1061
  return result;
775
1062
  }
776
1063
  var FileSessionWriter = class _FileSessionWriter {
777
- constructor(id, handle, startedAt, meta, events, opts = {}) {
1064
+ constructor(id, handle, startedAt, meta, events, opts = {}, traceId) {
778
1065
  this.id = id;
779
1066
  this.handle = handle;
780
1067
  this.startedAt = startedAt;
781
1068
  this.meta = meta;
782
1069
  this.events = events;
783
1070
  this.resumed = opts.resumed ?? false;
784
- this.manifestFile = opts.dir ? path13.join(opts.dir, `${path13.basename(id)}.summary.json`) : "";
1071
+ this.manifestFile = opts.dir ? path2.join(opts.dir, `${path2.basename(id)}.summary.json`) : "";
785
1072
  this.filePath = opts.filePath ?? "";
786
1073
  this.secretScrubber = opts.secretScrubber;
787
1074
  this.onCloseCb = opts.onClose;
@@ -793,6 +1080,7 @@ var FileSessionWriter = class _FileSessionWriter {
793
1080
  provider: meta.provider ?? "unknown",
794
1081
  tokenTotal: 0
795
1082
  };
1083
+ this.traceId = traceId;
796
1084
  }
797
1085
  id;
798
1086
  handle;
@@ -825,6 +1113,8 @@ var FileSessionWriter = class _FileSessionWriter {
825
1113
  lastAppendWarnAt = 0;
826
1114
  secretScrubber;
827
1115
  onCloseCb;
1116
+ /** Implements SessionWriter.traceId — propagated from ContextInit.traceId. */
1117
+ traceId;
828
1118
  // ── Write buffer — batches events to reduce per-event disk I/O ─────────
829
1119
  //
830
1120
  // Every append() pushes the scrubbed event into an in-memory buffer instead
@@ -978,9 +1268,14 @@ var FileSessionWriter = class _FileSessionWriter {
978
1268
  const eventCount = this.writeBuffer.length;
979
1269
  const batch = this.writeBuffer.map((e) => JSON.stringify(e)).join("\n") + "\n";
980
1270
  this.writeBuffer = [];
1271
+ const t0 = Date.now();
1272
+ let outcome = "success";
1273
+ let errorMsg;
981
1274
  try {
982
1275
  await this.enqueueWrite(batch);
983
1276
  } catch (err) {
1277
+ outcome = "failure";
1278
+ errorMsg = toErrorMessage(err);
984
1279
  this.appendFailCount += eventCount;
985
1280
  const now = Date.now();
986
1281
  if (now - this.lastAppendWarnAt > 5e3) {
@@ -988,12 +1283,24 @@ var FileSessionWriter = class _FileSessionWriter {
988
1283
  const tail = suppressed > 0 ? ` (+${suppressed} suppressed)` : "";
989
1284
  console.warn(
990
1285
  "[session] flush failed:",
991
- err instanceof Error ? err.message : String(err),
1286
+ toErrorMessage(err),
992
1287
  tail
993
1288
  );
994
1289
  this.lastAppendWarnAt = now;
995
1290
  this.appendFailCount = 0;
996
1291
  }
1292
+ } finally {
1293
+ this.events?.emit("storage.write", {
1294
+ sessionId: this.id,
1295
+ store: "session",
1296
+ filePath: this.filePath,
1297
+ operation: "flush",
1298
+ outcome,
1299
+ durationMs: Date.now() - t0,
1300
+ ...errorMsg !== void 0 ? { error: errorMsg } : {},
1301
+ ...eventCount !== void 0 ? { eventCount } : {},
1302
+ ...this.traceId !== void 0 ? { traceId: this.traceId } : {}
1303
+ });
997
1304
  }
998
1305
  }
999
1306
  observeForSummary(event) {
@@ -1059,14 +1366,46 @@ var FileSessionWriter = class _FileSessionWriter {
1059
1366
  outcome: this.outcome ?? "completed"
1060
1367
  };
1061
1368
  if (this.manifestFile) {
1369
+ const t0 = Date.now();
1370
+ let outcome = "success";
1371
+ let errorMsg;
1062
1372
  try {
1063
1373
  await atomicWrite(this.manifestFile, JSON.stringify(this.summary), { mode: 384 });
1064
- } catch {
1374
+ } catch (err) {
1375
+ outcome = "failure";
1376
+ errorMsg = toErrorMessage(err);
1377
+ } finally {
1378
+ this.events?.emit("storage.write", {
1379
+ sessionId: this.id,
1380
+ store: "session",
1381
+ filePath: this.manifestFile,
1382
+ operation: "close",
1383
+ outcome,
1384
+ durationMs: Date.now() - t0,
1385
+ ...errorMsg !== void 0 ? { error: errorMsg } : {},
1386
+ ...this.traceId !== void 0 ? { traceId: this.traceId } : {}
1387
+ });
1065
1388
  }
1066
1389
  }
1390
+ const idxT0 = Date.now();
1391
+ let idxOutcome = "success";
1392
+ let idxError;
1067
1393
  try {
1068
1394
  await this.onCloseCb?.(this.summary);
1069
- } catch {
1395
+ } catch (err) {
1396
+ idxOutcome = "failure";
1397
+ idxError = toErrorMessage(err);
1398
+ } finally {
1399
+ this.events?.emit("storage.write", {
1400
+ sessionId: this.summary.id,
1401
+ store: "session",
1402
+ filePath: this.filePath,
1403
+ operation: "index_append",
1404
+ outcome: idxOutcome,
1405
+ durationMs: Date.now() - idxT0,
1406
+ ...idxError !== void 0 ? { error: idxError } : {},
1407
+ ...this.traceId !== void 0 ? { traceId: this.traceId } : {}
1408
+ });
1070
1409
  }
1071
1410
  try {
1072
1411
  await this.handle.close();
@@ -1225,28 +1564,86 @@ function userInputTitle(content) {
1225
1564
  }
1226
1565
  var QueueStore = class {
1227
1566
  file;
1567
+ // Use `| undefined` (not `?`) so exactOptionalPropertyTypes doesn't
1568
+ // reject assigning an optional constructor parameter to these fields.
1569
+ events;
1570
+ traceId;
1228
1571
  constructor(opts) {
1229
- this.file = path13.join(opts.dir, "queue.json");
1572
+ this.file = path2.join(opts.dir, "queue.json");
1573
+ this.events = opts.events;
1574
+ this.traceId = opts.traceId;
1230
1575
  }
1231
1576
  async write(items) {
1577
+ const t0 = Date.now();
1232
1578
  if (items.length === 0) {
1233
1579
  await this.clear();
1234
1580
  return;
1235
1581
  }
1236
- await atomicWrite(this.file, JSON.stringify(items), { mode: 384 });
1582
+ try {
1583
+ await atomicWrite(this.file, JSON.stringify(items), { mode: 384 });
1584
+ this.events?.emit("storage.write", {
1585
+ sessionId: this.traceId ?? "~boot~",
1586
+ store: "queue",
1587
+ filePath: this.file,
1588
+ operation: "write",
1589
+ outcome: "success",
1590
+ durationMs: Date.now() - t0,
1591
+ ...this.traceId !== void 0 && { traceId: this.traceId }
1592
+ });
1593
+ } catch (err) {
1594
+ this.events?.emit("storage.error", {
1595
+ sessionId: this.traceId ?? "~boot~",
1596
+ store: "queue",
1597
+ filePath: this.file,
1598
+ operation: "write",
1599
+ outcome: "failure",
1600
+ error: toErrorMessage(err),
1601
+ recoverable: false,
1602
+ ...this.traceId !== void 0 && { traceId: this.traceId }
1603
+ });
1604
+ console.warn(JSON.stringify({
1605
+ level: "warn",
1606
+ event: "queue_store.write_failed",
1607
+ path: this.file,
1608
+ message: toErrorMessage(err),
1609
+ timestamp: (/* @__PURE__ */ new Date()).toISOString()
1610
+ }));
1611
+ }
1237
1612
  }
1238
1613
  async read() {
1614
+ const t0 = Date.now();
1239
1615
  let raw;
1240
1616
  try {
1241
1617
  raw = await fsp.readFile(this.file, "utf8");
1242
1618
  } catch (err) {
1243
1619
  const code = err.code;
1244
- if (code === "ENOENT") return [];
1620
+ if (code === "ENOENT") {
1621
+ this.events?.emit("storage.read", {
1622
+ sessionId: this.traceId ?? "~boot~",
1623
+ store: "queue",
1624
+ filePath: this.file,
1625
+ operation: "read",
1626
+ outcome: "success",
1627
+ durationMs: Date.now() - t0,
1628
+ ...this.traceId !== void 0 && { traceId: this.traceId }
1629
+ });
1630
+ return [];
1631
+ }
1632
+ this.events?.emit("storage.error", {
1633
+ sessionId: this.traceId ?? "~boot~",
1634
+ store: "queue",
1635
+ filePath: this.file,
1636
+ operation: "read",
1637
+ outcome: "failure",
1638
+ error: toErrorMessage(err),
1639
+ recoverable: true,
1640
+ ...this.traceId !== void 0 && { traceId: this.traceId }
1641
+ });
1245
1642
  console.warn(JSON.stringify({
1246
1643
  level: "warn",
1247
1644
  event: "queue_store.read_failed",
1248
1645
  path: this.file,
1249
- message: err instanceof Error ? err.message : String(err),
1646
+ message: toErrorMessage(err),
1250
1647
  timestamp: (/* @__PURE__ */ new Date()).toISOString()
1251
1648
  }));
1252
1649
  return [];
@@ -1255,9 +1652,40 @@ var QueueStore = class {
1255
1652
  try {
1256
1653
  parsed = JSON.parse(raw);
1257
1654
  } catch {
1655
+ this.events?.emit("storage.read", {
1656
+ sessionId: this.traceId ?? "~boot~",
1657
+ store: "queue",
1658
+ filePath: this.file,
1659
+ operation: "read",
1660
+ outcome: "failure",
1661
+ durationMs: Date.now() - t0,
1662
+ error: "parse_failed",
1663
+ ...this.traceId !== void 0 && { traceId: this.traceId }
1664
+ });
1665
+ return [];
1666
+ }
1667
+ if (!Array.isArray(parsed)) {
1668
+ this.events?.emit("storage.read", {
1669
+ sessionId: this.traceId ?? "~boot~",
1670
+ store: "queue",
1671
+ filePath: this.file,
1672
+ operation: "read",
1673
+ outcome: "failure",
1674
+ durationMs: Date.now() - t0,
1675
+ error: "invalid_schema",
1676
+ ...this.traceId !== void 0 && { traceId: this.traceId }
1677
+ });
1258
1678
  return [];
1259
1679
  }
1260
- if (!Array.isArray(parsed)) return [];
1680
+ this.events?.emit("storage.read", {
1681
+ sessionId: this.traceId ?? "~boot~",
1682
+ store: "queue",
1683
+ filePath: this.file,
1684
+ operation: "read",
1685
+ outcome: "success",
1686
+ durationMs: Date.now() - t0,
1687
+ ...this.traceId !== void 0 && { traceId: this.traceId }
1688
+ });
1261
1689
  const out = [];
1262
1690
  for (const v of parsed) {
1263
1691
  if (isPersistedQueueItem(v)) out.push(v);
@@ -1265,11 +1693,31 @@ var QueueStore = class {
1265
1693
  return out;
1266
1694
  }
1267
1695
  async clear() {
1696
+ const t0 = Date.now();
1268
1697
  try {
1269
1698
  await fsp.unlink(this.file);
1699
+ this.events?.emit("storage.write", {
1700
+ sessionId: this.traceId ?? "~boot~",
1701
+ store: "queue",
1702
+ filePath: this.file,
1703
+ operation: "clear",
1704
+ outcome: "success",
1705
+ durationMs: Date.now() - t0,
1706
+ ...this.traceId !== void 0 && { traceId: this.traceId }
1707
+ });
1270
1708
  } catch (err) {
1271
1709
  const code = err.code;
1272
1710
  if (code === "ENOENT") return;
1711
+ this.events?.emit("storage.error", {
1712
+ sessionId: this.traceId ?? "~boot~",
1713
+ store: "queue",
1714
+ filePath: this.file,
1715
+ operation: "clear",
1716
+ outcome: "failure",
1717
+ error: toErrorMessage(err),
1718
+ recoverable: true,
1719
+ ...this.traceId !== void 0 && { traceId: this.traceId }
1720
+ });
1273
1721
  console.warn(JSON.stringify({
1274
1722
  level: "warn",
1275
1723
  event: "queue_store.clear_failed",
@@ -1305,7 +1753,7 @@ var DefaultAttachmentStore = class {
1305
1753
  let data = input.data;
1306
1754
  if (this.spoolDir && bytes >= this.spoolThreshold) {
1307
1755
  await fsp.mkdir(this.spoolDir, { recursive: true });
1308
- spooledPath = path13.join(this.spoolDir, `${id}.bin`);
1756
+ spooledPath = path2.join(this.spoolDir, `${id}.bin`);
1309
1757
  await atomicWrite(spooledPath, input.data, {
1310
1758
  encoding: input.kind === "image" ? "base64" : "utf8"
1311
1759
  });
@@ -1516,7 +1964,7 @@ var FileMemoryBackend = class {
1516
1964
  }
1517
1965
  async remember(scope, entry, filePath) {
1518
1966
  const file = this.resolveFile(filePath, scope);
1519
- await ensureDir(path13.dirname(file));
1967
+ await ensureDir(path2.dirname(file));
1520
1968
  let existing = "";
1521
1969
  try {
1522
1970
  existing = await fsp.readFile(file, "utf8");
@@ -1652,6 +2100,7 @@ var MAX_BYTES_TOTAL = 32e3;
1652
2100
  var DefaultMemoryStore = class {
1653
2101
  files;
1654
2102
  events;
2103
+ traceId;
1655
2104
  backend;
1656
2105
  /**
1657
2106
  * Per-scope serialization queue. `remember` / `forget` / `consolidate` /
@@ -1717,15 +2166,70 @@ var DefaultMemoryStore = class {
1717
2166
  if (writeErr) {
1718
2167
  parts.push(`> \u26A0\uFE0F Memory write error (${labelOf(scope)}): ${writeErr.message}`);
1719
2168
  }
1720
- const body = await this.backend.readAll(scope, this.files[scope]);
1721
- if (body.trim()) parts.push(`## ${labelOf(scope)}
2169
+ const t0 = Date.now();
2170
+ const filePath = this.files[scope];
2171
+ try {
2172
+ const body = await this.backend.readAll(scope, filePath);
2173
+ const dur = Date.now() - t0;
2174
+ this.events?.emit("storage.read", {
2175
+ sessionId: "~memory~",
2176
+ store: "memory",
2177
+ filePath,
2178
+ operation: "readAll",
2179
+ outcome: "success",
2180
+ durationMs: dur,
2181
+ ...this.traceId !== void 0 && { traceId: this.traceId }
2182
+ });
2183
+ if (body.trim()) parts.push(`## ${labelOf(scope)}
1722
2184
 
1723
2185
  ${body.trim()}`);
2186
+ } catch (err) {
2187
+ const dur = Date.now() - t0;
2188
+ this.events?.emit("storage.read", {
2189
+ sessionId: "~memory~",
2190
+ store: "memory",
2191
+ filePath,
2192
+ operation: "readAll",
2193
+ outcome: "failure",
2194
+ durationMs: dur,
2195
+ error: toErrorMessage(err),
2196
+ ...this.traceId !== void 0 && { traceId: this.traceId }
2197
+ });
2198
+ throw err;
2199
+ }
1724
2200
  }
1725
2201
  return parts.join("\n\n");
1726
2202
  }
1727
2203
  async read(scope) {
1728
- return this.backend.readAll(scope, this.files[scope]);
2204
+ const t0 = Date.now();
2205
+ const filePath = this.files[scope];
2206
+ try {
2207
+ const body = await this.backend.readAll(scope, filePath);
2208
+ const dur = Date.now() - t0;
2209
+ this.events?.emit("storage.read", {
2210
+ sessionId: "~memory~",
2211
+ store: "memory",
2212
+ filePath,
2213
+ operation: "read",
2214
+ outcome: "success",
2215
+ durationMs: dur,
2216
+ ...this.traceId !== void 0 && { traceId: this.traceId }
2217
+ });
2218
+ return body;
2219
+ } catch (err) {
2220
+ const dur = Date.now() - t0;
2221
+ this.events?.emit("storage.read", {
2222
+ sessionId: "~memory~",
2223
+ store: "memory",
2224
+ filePath,
2225
+ operation: "read",
2226
+ outcome: "failure",
2227
+ durationMs: dur,
2228
+ error: toErrorMessage(err),
2229
+ ...this.traceId !== void 0 && { traceId: this.traceId }
2230
+ });
2231
+ throw err;
2232
+ }
1729
2233
  }
1730
2234
  /**
1731
2235
  * List entries from a scope, newest first. Delegates to the backend
@@ -1751,7 +2255,34 @@ ${body.trim()}`);
1751
2255
  const ts = (/* @__PURE__ */ new Date()).toISOString();
1752
2256
  return this.runSerialized(scope, async () => {
1753
2257
  const entry = { scope, text, ts, ...metadata };
1754
- await this.backend.remember(scope, entry, this.files[scope]);
2258
+ const filePath = this.files[scope];
2259
+ const t0 = Date.now();
2260
+ try {
2261
+ await this.backend.remember(scope, entry, filePath);
2262
+ const dur = Date.now() - t0;
2263
+ this.events?.emit("storage.write", {
2264
+ sessionId: "~memory~",
2265
+ store: "memory",
2266
+ filePath,
2267
+ operation: "remember",
2268
+ outcome: "success",
2269
+ durationMs: dur,
2270
+ ...this.traceId !== void 0 && { traceId: this.traceId }
2271
+ });
2272
+ } catch (err) {
2273
+ const dur = Date.now() - t0;
2274
+ this.events?.emit("storage.write", {
2275
+ sessionId: "~memory~",
2276
+ store: "memory",
2277
+ filePath,
2278
+ operation: "remember",
2279
+ outcome: "failure",
2280
+ durationMs: dur,
2281
+ error: toErrorMessage(err),
2282
+ ...this.traceId !== void 0 && { traceId: this.traceId }
2283
+ });
2284
+ throw err;
2285
+ }
1755
2286
  const raw = await this.backend.readAll(scope, this.files[scope]);
1756
2287
  if (Buffer.byteLength(raw, "utf8") > MAX_BYTES_TOTAL) {
1757
2288
  const removed = await this.backend.consolidate(scope, this.files[scope]);
@@ -1879,7 +2410,35 @@ ${body.trim()}`);
1879
2410
  }
1880
2411
  async forget(query, scope = "project-memory") {
1881
2412
  return this.runSerialized(scope, async () => {
1882
- const removed = await this.backend.forget(scope, query, this.files[scope]);
2413
+ const filePath = this.files[scope];
2414
+ const t0 = Date.now();
2415
+ let removed = 0;
2416
+ try {
2417
+ removed = await this.backend.forget(scope, query, filePath);
2418
+ const dur = Date.now() - t0;
2419
+ this.events?.emit("storage.write", {
2420
+ sessionId: "~memory~",
2421
+ store: "memory",
2422
+ filePath,
2423
+ operation: "forget",
2424
+ outcome: "success",
2425
+ durationMs: dur,
2426
+ ...this.traceId !== void 0 && { traceId: this.traceId }
2427
+ });
2428
+ } catch (err) {
2429
+ const dur = Date.now() - t0;
2430
+ this.events?.emit("storage.write", {
2431
+ sessionId: "~memory~",
2432
+ store: "memory",
2433
+ filePath,
2434
+ operation: "forget",
2435
+ outcome: "failure",
2436
+ durationMs: dur,
2437
+ error: toErrorMessage(err),
2438
+ ...this.traceId !== void 0 && { traceId: this.traceId }
2439
+ });
2440
+ throw err;
2441
+ }
1883
2442
  if (removed > 0) {
1884
2443
  this.events?.emit("memory.forgotten", {
1885
2444
  scope,
@@ -1893,7 +2452,35 @@ ${body.trim()}`);
1893
2452
  }
1894
2453
  async consolidate(scope) {
1895
2454
  return this.runSerialized(scope, async () => {
1896
- const removed = await this.backend.consolidate(scope, this.files[scope]);
2455
+ const filePath = this.files[scope];
2456
+ const t0 = Date.now();
2457
+ let removed = 0;
2458
+ try {
2459
+ removed = await this.backend.consolidate(scope, filePath);
2460
+ const dur = Date.now() - t0;
2461
+ this.events?.emit("storage.write", {
2462
+ sessionId: "~memory~",
2463
+ store: "memory",
2464
+ filePath,
2465
+ operation: "consolidate",
2466
+ outcome: "success",
2467
+ durationMs: dur,
2468
+ ...this.traceId !== void 0 && { traceId: this.traceId }
2469
+ });
2470
+ } catch (err) {
2471
+ const dur = Date.now() - t0;
2472
+ this.events?.emit("storage.write", {
2473
+ sessionId: "~memory~",
2474
+ store: "memory",
2475
+ filePath,
2476
+ operation: "consolidate",
2477
+ outcome: "failure",
2478
+ durationMs: dur,
2479
+ error: toErrorMessage(err),
2480
+ ...this.traceId !== void 0 && { traceId: this.traceId }
2481
+ });
2482
+ throw err;
2483
+ }
1897
2484
  if (removed > 0) {
1898
2485
  this.events?.emit("memory.consolidated", {
1899
2486
  scope,
@@ -1906,7 +2493,34 @@ ${body.trim()}`);
1906
2493
  async clear(scope) {
1907
2494
  if (scope) {
1908
2495
  await this.runSerialized(scope, async () => {
1909
- await this.backend.clear(scope, this.files[scope]);
2496
+ const filePath = this.files[scope];
2497
+ const t0 = Date.now();
2498
+ try {
2499
+ await this.backend.clear(scope, filePath);
2500
+ const dur = Date.now() - t0;
2501
+ this.events?.emit("storage.write", {
2502
+ sessionId: "~memory~",
2503
+ store: "memory",
2504
+ filePath,
2505
+ operation: "clear",
2506
+ outcome: "success",
2507
+ durationMs: dur,
2508
+ ...this.traceId !== void 0 && { traceId: this.traceId }
2509
+ });
2510
+ } catch (err) {
2511
+ const dur = Date.now() - t0;
2512
+ this.events?.emit("storage.write", {
2513
+ sessionId: "~memory~",
2514
+ store: "memory",
2515
+ filePath,
2516
+ operation: "clear",
2517
+ outcome: "failure",
2518
+ durationMs: dur,
2519
+ error: toErrorMessage(err),
2520
+ ...this.traceId !== void 0 && { traceId: this.traceId }
2521
+ });
2522
+ throw err;
2523
+ }
1910
2524
  this.events?.emit("memory.cleared", { scope });
1911
2525
  await this.mirrorBackup(scope);
1912
2526
  });
@@ -1914,15 +2528,54 @@ ${body.trim()}`);
1914
2528
  }
1915
2529
  await Promise.all(
1916
2530
  ["project-agents", "project-memory", "user-memory"].map(
1917
- (s) => this.runSerialized(s, async () => {
1918
- await this.backend.clear(s, this.files[s]);
2531
+ async (s) => this.runSerialized(s, async () => {
2532
+ const filePath = this.files[s];
2533
+ const t0 = Date.now();
2534
+ try {
2535
+ await this.backend.clear(s, filePath);
2536
+ const dur = Date.now() - t0;
2537
+ this.events?.emit("storage.write", {
2538
+ sessionId: "~memory~",
2539
+ store: "memory",
2540
+ filePath,
2541
+ operation: "clear",
2542
+ outcome: "success",
2543
+ durationMs: dur,
2544
+ ...this.traceId !== void 0 && { traceId: this.traceId }
2545
+ });
2546
+ } catch (err) {
2547
+ const dur = Date.now() - t0;
2548
+ this.events?.emit("storage.write", {
2549
+ sessionId: "~memory~",
2550
+ store: "memory",
2551
+ filePath,
2552
+ operation: "clear",
2553
+ outcome: "failure",
2554
+ durationMs: dur,
2555
+ error: toErrorMessage(err),
2556
+ ...this.traceId !== void 0 && { traceId: this.traceId }
2557
+ });
2558
+ throw err;
2559
+ }
1919
2560
  this.events?.emit("memory.cleared", { scope: s });
1920
2561
  await this.mirrorBackup(s);
1921
2562
  })
1922
2563
  )
1923
2564
  );
1924
2565
  }
1925
- /** Mirror current memory content to the persistent backup directory. */
2566
+ /**
2567
+ * Return a new MemoryStore proxy that carries `traceId` on every storage
2568
+ * event. The original store is left unchanged — callers that need a
2569
+ * trace-decorated view (e.g. session-run tools) receive the proxy while
2570
+ * the singleton remains trace-free for boot-time use.
2571
+ *
2572
+ * The proxy implements the full `MemoryStore` interface; all other
2573
+ * properties (backend, etc.) are delegated to the original store.
2574
+ */
2575
+ withTraceId(traceId) {
2576
+ this.traceId = traceId;
2577
+ return this;
2578
+ }
1926
2579
  async mirrorBackup(scope) {
1927
2580
  if (!this.persistBackup || scope === "project-agents") return;
1928
2581
  try {
@@ -2460,7 +3113,7 @@ var DefaultConfigStore = class {
2460
3113
  console.error(JSON.stringify({
2461
3114
  level: "error",
2462
3115
  event: "config_store.watcher_threw",
2463
- message: err instanceof Error ? err.message : String(err),
3116
+ message: toErrorMessage(err),
2464
3117
  timestamp: (/* @__PURE__ */ new Date()).toISOString()
2465
3118
  }));
2466
3119
  }
@@ -2484,67 +3137,6 @@ function deepFreeze(obj) {
2484
3137
  }
2485
3138
  return Object.freeze(obj);
2486
3139
  }
2487
-
2488
- // src/utils/deep-merge.ts
2489
- var FORBIDDEN_PROTO_KEYS = /* @__PURE__ */ new Set([
2490
- "__proto__",
2491
- "constructor",
2492
- "prototype",
2493
- "__defineGetter__",
2494
- "__defineSetter__",
2495
- "__lookupGetter__",
2496
- "__lookupSetter__"
2497
- ]);
2498
- function isPrimitiveArray(a) {
2499
- return a.every((v) => v === null || typeof v !== "object" && typeof v !== "function");
2500
- }
2501
- function deepMerge(base, patch, options = {}) {
2502
- const {
2503
- conflictResolution = "prefer-patch",
2504
- arrayMode = "replace",
2505
- protectProto = true,
2506
- onNonPrimitiveArrayReplace
2507
- } = options;
2508
- if (typeof base !== "object" || base === null) {
2509
- return conflictResolution === "prefer-patch" ? patch : base;
2510
- }
2511
- if (typeof patch !== "object" || patch === null) {
2512
- return conflictResolution === "prefer-patch" ? patch : base;
2513
- }
2514
- if (Array.isArray(base) && Array.isArray(patch)) {
2515
- if (arrayMode === "concat-primitives" && isPrimitiveArray(base) && isPrimitiveArray(patch)) {
2516
- return [.../* @__PURE__ */ new Set([...base, ...patch])];
2517
- }
2518
- return conflictResolution === "prefer-patch" ? patch : base;
2519
- }
2520
- if (Array.isArray(base) || Array.isArray(patch)) {
2521
- return conflictResolution === "prefer-patch" ? patch : base;
2522
- }
2523
- const baseObj = base;
2524
- const patchObj = patch;
2525
- const out = { ...baseObj };
2526
- for (const [k, v] of Object.entries(patchObj)) {
2527
- if (protectProto && FORBIDDEN_PROTO_KEYS.has(k)) continue;
2528
- const existing = out[k];
2529
- if (v !== null && typeof v === "object" && !Array.isArray(v) && existing !== null && typeof existing === "object" && !Array.isArray(existing)) {
2530
- out[k] = deepMerge(existing, v, options);
2531
- } else if (Array.isArray(v) && Array.isArray(existing)) {
2532
- if (onNonPrimitiveArrayReplace && !isPrimitiveArray(v)) {
2533
- onNonPrimitiveArrayReplace(k, existing.length, v.length);
2534
- }
2535
- out[k] = deepMerge(existing, v, options);
2536
- } else if (v !== void 0) {
2537
- if (onNonPrimitiveArrayReplace && Array.isArray(v) && !isPrimitiveArray(v)) {
2538
- const existingLen = Array.isArray(existing) ? existing.length : 0;
2539
- onNonPrimitiveArrayReplace(k, existingLen, v.length);
2540
- }
2541
- out[k] = v;
2542
- }
2543
- }
2544
- return out;
2545
- }
2546
-
2547
- // src/security/secret-vault.ts
2548
3140
  function decryptConfigSecrets(cfg, vault, opts) {
2549
3141
  const warn = ((msg) => console.warn(msg));
2550
3142
  return walk(cfg, vault, (v, key) => {
@@ -2635,21 +3227,6 @@ function isContextWindowModeId(id) {
2635
3227
  return CONTEXT_WINDOW_MODES.some((m) => m.id === id);
2636
3228
  }
2637
3229
 
2638
- // src/utils/safe-json.ts
2639
- function safeParse(input, maxBytes = 5e6) {
2640
- if (input.length > maxBytes) {
2641
- return { ok: false, error: `Input exceeds limit (${maxBytes} bytes)` };
2642
- }
2643
- try {
2644
- return { ok: true, value: JSON.parse(input) };
2645
- } catch (err) {
2646
- return {
2647
- ok: false,
2648
- error: err instanceof Error ? err.message : String(err)
2649
- };
2650
- }
2651
- }
2652
-
2653
3230
  // src/types/default-config.ts
2654
3231
  var DEFAULT_TOOLS_CONFIG = Object.freeze({
2655
3232
  defaultExecutionStrategy: "smart",
@@ -2673,6 +3250,13 @@ var DEFAULT_SESSION_LOGGING_CONFIG = Object.freeze({
2673
3250
  });
2674
3251
 
2675
3252
  // src/storage/config-loader.ts
3253
+ function storageErrorString(err) {
3254
+ if (err instanceof Error) {
3255
+ const code = err.code;
3256
+ return code ? `${code}: ${err.message}` : err.message;
3257
+ }
3258
+ return String(err);
3259
+ }
2676
3260
  var BEHAVIOR_DEFAULTS = {
2677
3261
  version: 1,
2678
3262
  context: {
@@ -2775,11 +3359,15 @@ var DefaultConfigLoader = class {
2775
3359
  strict;
2776
3360
  vault;
2777
3361
  extraSources;
3362
+ events;
3363
+ traceId;
2778
3364
  constructor(opts) {
2779
3365
  this.paths = opts.paths;
2780
3366
  this.strict = opts.strict ?? false;
2781
3367
  this.vault = opts.vault;
2782
3368
  this.extraSources = opts.sources ?? [];
3369
+ this.events = opts.events;
3370
+ this.traceId = opts.traceId;
2783
3371
  }
2784
3372
  async load(opts = {}) {
2785
3373
  let cfg = { ...BEHAVIOR_DEFAULTS };
@@ -2811,7 +3399,7 @@ var DefaultConfigLoader = class {
2811
3399
  level: "warn",
2812
3400
  event: "config.source_load_failed",
2813
3401
  source: src.name,
2814
- message: err instanceof Error ? err.message : String(err),
3402
+ message: toErrorMessage(err),
2815
3403
  timestamp: (/* @__PURE__ */ new Date()).toISOString()
2816
3404
  }));
2817
3405
  }
@@ -2856,7 +3444,33 @@ var DefaultConfigLoader = class {
2856
3444
  if (this.vault && toWrite.githubToken && !toWrite.githubToken.startsWith("enc:")) {
2857
3445
  toWrite = { ...toWrite, githubToken: this.vault.encrypt(toWrite.githubToken) };
2858
3446
  }
2859
- await atomicWrite(this.paths.syncConfig, JSON.stringify(toWrite, null, 2), { mode: 384 });
3447
+ const fp = this.paths.syncConfig;
3448
+ const t0 = Date.now();
3449
+ try {
3450
+ await atomicWrite(fp, JSON.stringify(toWrite, null, 2), { mode: 384 });
3451
+ this.events?.emit("storage.write", {
3452
+ sessionId: "~config~",
3453
+ store: "config",
3454
+ filePath: fp,
3455
+ operation: "persist_sync",
3456
+ outcome: "success",
3457
+ durationMs: Date.now() - t0,
3458
+ ...this.traceId !== void 0 ? { traceId: this.traceId } : {}
3459
+ });
3460
+ } catch (err) {
3461
+ this.events?.emit("storage.error", {
3462
+ sessionId: "~config~",
3463
+ store: "config",
3464
+ filePath: fp,
3465
+ operation: "persist_sync",
3466
+ outcome: "failure",
3467
+ error: storageErrorString(err),
3468
+ recoverable: false,
3469
+ durationMs: Date.now() - t0,
3470
+ ...this.traceId !== void 0 ? { traceId: this.traceId } : {}
3471
+ });
3472
+ throw err;
3473
+ }
2860
3474
  }
2861
3475
  /**
2862
3476
  * Read ~/.wrongstack/sync.json (encrypted GitHub token storage) and decrypt
@@ -2865,21 +3479,64 @@ var DefaultConfigLoader = class {
2865
3479
  * isolated — it should never be part of project-local or env-driven config.
2866
3480
  */
2867
3481
  async loadSyncConfig() {
3482
+ const fp = this.paths.syncConfig;
3483
+ const t0 = Date.now();
2868
3484
  try {
2869
- const raw = await fsp.readFile(this.paths.syncConfig, "utf8");
3485
+ const raw = await fsp.readFile(fp, "utf8");
2870
3486
  const parsed = safeParse(raw);
2871
- if (!parsed.ok || !parsed.value) return null;
3487
+ if (!parsed.ok || !parsed.value) {
3488
+ this.events?.emit("storage.read", {
3489
+ sessionId: "~config~",
3490
+ store: "config",
3491
+ filePath: fp,
3492
+ operation: "load_sync",
3493
+ outcome: "failure",
3494
+ durationMs: Date.now() - t0,
3495
+ error: "parse error or empty file",
3496
+ ...this.traceId !== void 0 ? { traceId: this.traceId } : {}
3497
+ });
3498
+ return null;
3499
+ }
2872
3500
  if (this.vault) {
2873
3501
  const decrypted = decryptConfigSecrets({ sync: parsed.value }, this.vault);
2874
- return decrypted.sync ?? null;
3502
+ const result = decrypted.sync ?? null;
3503
+ this.events?.emit("storage.read", {
3504
+ sessionId: "~config~",
3505
+ store: "config",
3506
+ filePath: fp,
3507
+ operation: "load_sync",
3508
+ outcome: "success",
3509
+ durationMs: Date.now() - t0,
3510
+ ...this.traceId !== void 0 ? { traceId: this.traceId } : {}
3511
+ });
3512
+ return result;
2875
3513
  }
3514
+ this.events?.emit("storage.read", {
3515
+ sessionId: "~config~",
3516
+ store: "config",
3517
+ filePath: fp,
3518
+ operation: "load_sync",
3519
+ outcome: "success",
3520
+ durationMs: Date.now() - t0,
3521
+ ...this.traceId !== void 0 ? { traceId: this.traceId } : {}
3522
+ });
2876
3523
  return parsed.value;
2877
3524
  } catch (err) {
2878
3525
  if (err.code === "ENOENT") return null;
3526
+ this.events?.emit("storage.read", {
3527
+ sessionId: "~config~",
3528
+ store: "config",
3529
+ filePath: fp,
3530
+ operation: "load_sync",
3531
+ outcome: "failure",
3532
+ durationMs: Date.now() - t0,
3533
+ error: storageErrorString(err),
3534
+ ...this.traceId !== void 0 ? { traceId: this.traceId } : {}
3535
+ });
2879
3536
  console.warn(JSON.stringify({
2880
3537
  level: "warn",
2881
3538
  event: "config.sync_load_failed",
2882
- message: err instanceof Error ? err.message : String(err),
3539
+ message: toErrorMessage(err),
2883
3540
  timestamp: (/* @__PURE__ */ new Date()).toISOString()
2884
3541
  }));
2885
3542
  return null;
@@ -2887,15 +3544,26 @@ var DefaultConfigLoader = class {
2887
3544
  }
2888
3545
  async readJson(file) {
2889
3546
  let raw;
3547
+ const t0 = Date.now();
2890
3548
  try {
2891
3549
  raw = await fsp.readFile(file, "utf8");
2892
3550
  } catch (err) {
2893
3551
  if (err.code !== "ENOENT") {
3552
+ this.events?.emit("storage.read", {
3553
+ sessionId: "~config~",
3554
+ store: "config",
3555
+ filePath: file,
3556
+ operation: "read_json",
3557
+ outcome: "failure",
3558
+ durationMs: Date.now() - t0,
3559
+ error: storageErrorString(err),
3560
+ ...this.traceId !== void 0 ? { traceId: this.traceId } : {}
3561
+ });
2894
3562
  console.warn(JSON.stringify({
2895
3563
  level: "warn",
2896
3564
  event: "config.read_failed",
2897
3565
  path: file,
2898
- message: err instanceof Error ? err.message : String(err),
3566
+ message: toErrorMessage(err),
2899
3567
  timestamp: (/* @__PURE__ */ new Date()).toISOString()
2900
3568
  }));
2901
3569
  }
@@ -2903,6 +3571,16 @@ var DefaultConfigLoader = class {
2903
3571
  }
2904
3572
  const parsed = safeParse(raw);
2905
3573
  if (!parsed.ok || !parsed.value) {
3574
+ this.events?.emit("storage.read", {
3575
+ sessionId: "~config~",
3576
+ store: "config",
3577
+ filePath: file,
3578
+ operation: "read_json",
3579
+ outcome: "failure",
3580
+ durationMs: Date.now() - t0,
3581
+ error: "parse error or empty file",
3582
+ ...this.traceId !== void 0 ? { traceId: this.traceId } : {}
3583
+ });
2906
3584
  console.warn(JSON.stringify({
2907
3585
  level: "warn",
2908
3586
  event: "config.parse_failed",
@@ -3036,7 +3714,7 @@ var RecoveryLock = class {
3036
3714
  sessionStore;
3037
3715
  probe;
3038
3716
  constructor(opts) {
3039
- this.file = path13.join(opts.dir, LOCK_FILE);
3717
+ this.file = path2.join(opts.dir, LOCK_FILE);
3040
3718
  this.pid = opts.pid ?? process.pid;
3041
3719
  this.hostname = opts.hostname ?? os.hostname();
3042
3720
  this.maxAgeMs = opts.maxAgeMs ?? DEFAULT_MAX_AGE_MS;
@@ -3097,7 +3775,7 @@ var RecoveryLock = class {
3097
3775
  * null return before calling this.
3098
3776
  */
3099
3777
  async write(sessionId) {
3100
- await ensureDir(path13.dirname(this.file));
3778
+ await ensureDir(path2.dirname(this.file));
3101
3779
  const lock = {
3102
3780
  v: 1,
3103
3781
  sessionId,
@@ -3164,42 +3842,6 @@ function defaultIsPidAlive(pid) {
3164
3842
  }
3165
3843
  }
3166
3844
 
3167
- // src/utils/regex-guard.ts
3168
- var MAX_PATTERN_LEN = 512;
3169
- var DANGEROUS_PATTERNS = [
3170
- /(\([^)]*[+*][^)]*\))[+*]/,
3171
- // (a+)+, (.*)+, etc
3172
- /(\(\?:[^)]*[+*][^)]*\))[+*]/
3173
- // same, with non-capturing group
3174
- ];
3175
- function compileUserRegex(pattern, flags) {
3176
- if (typeof pattern !== "string") {
3177
- return { ok: false, reason: "pattern must be a string" };
3178
- }
3179
- if (pattern.length === 0) {
3180
- return { ok: false, reason: "pattern is empty" };
3181
- }
3182
- if (pattern.length > MAX_PATTERN_LEN) {
3183
- return { ok: false, reason: `pattern exceeds ${MAX_PATTERN_LEN} characters` };
3184
- }
3185
- for (const rx of DANGEROUS_PATTERNS) {
3186
- if (rx.test(pattern)) {
3187
- return {
3188
- ok: false,
3189
- reason: "pattern looks vulnerable to catastrophic backtracking \u2014 rewrite without nested quantifiers"
3190
- };
3191
- }
3192
- }
3193
- try {
3194
- return { ok: true, regex: new RegExp(pattern, flags) };
3195
- } catch (err) {
3196
- return {
3197
- ok: false,
3198
- reason: err instanceof Error ? err.message : "invalid regex"
3199
- };
3200
- }
3201
- }
3202
-
3203
3845
  // src/storage/session-reader.ts
3204
3846
  var DefaultSessionReader = class {
3205
3847
  store;
@@ -3479,9 +4121,9 @@ function sessionScopedPath(dir, sessionId, suffix) {
3479
4121
  if (!sessionId || sessionId.includes("\\") || sessionId.includes("..")) {
3480
4122
  throw invalid(sessionId);
3481
4123
  }
3482
- const resolved = path13.resolve(dir, `${sessionId}${suffix}`);
3483
- const rel = path13.relative(path13.resolve(dir), resolved);
3484
- if (rel.startsWith("..") || path13.isAbsolute(rel)) {
4124
+ const resolved = path2.resolve(dir, `${sessionId}${suffix}`);
4125
+ const rel = path2.relative(path2.resolve(dir), resolved);
4126
+ if (rel.startsWith("..") || path2.isAbsolute(rel)) {
3485
4127
  throw invalid(sessionId);
3486
4128
  }
3487
4129
  return resolved;
@@ -3501,20 +4143,53 @@ var MAX_TEXT_LENGTH = 2e3;
3501
4143
  var MAX_ANNOTATIONS = 1e3;
3502
4144
  var AnnotationsStore = class {
3503
4145
  dir;
4146
+ events;
4147
+ traceId;
3504
4148
  /** Per-session write queue. Created lazily on first add. */
3505
4149
  writeChains = /* @__PURE__ */ new Map();
3506
4150
  constructor(opts) {
3507
4151
  this.dir = opts.dir;
4152
+ this.events = opts.events;
4153
+ this.traceId = opts.traceId;
3508
4154
  }
3509
4155
  // ── Reads ──────────────────────────────────────────────────────────────
3510
4156
  /**
3511
4157
  * Return all annotations for `sessionId` in insertion order
3512
4158
  * (oldest first). Returns an empty array when no file exists
3513
- * yet (the normal case for a fresh session).
4159
+ * yet (the normal case for a fresh session) and also degrades
4160
+ * gracefully to `[]` on a read error (permissions, corruption) —
4161
+ * the failure is still surfaced via a `storage.read` event so it
4162
+ * never silently hides I/O problems from observers.
3514
4163
  */
3515
4164
  async list(sessionId) {
3516
- const file = await this.readFile(sessionId);
3517
- return file ? file.annotations : [];
4165
+ const t0 = Date.now();
4166
+ const fp = this.filePath(sessionId);
4167
+ try {
4168
+ const file = await this.readFile(sessionId);
4169
+ const durationMs = Date.now() - t0;
4170
+ this.events?.emit("storage.read", {
4171
+ sessionId,
4172
+ store: "annotations",
4173
+ filePath: fp,
4174
+ operation: "list",
4175
+ outcome: "success",
4176
+ durationMs,
4177
+ ...this.traceId !== void 0 ? { traceId: this.traceId } : {}
4178
+ });
4179
+ return file ? file.annotations : [];
4180
+ } catch (err) {
4181
+ this.events?.emit("storage.read", {
4182
+ sessionId,
4183
+ store: "annotations",
4184
+ filePath: fp,
4185
+ operation: "list",
4186
+ outcome: "failure",
4187
+ durationMs: Date.now() - t0,
4188
+ error: toErrorMessage(err),
4189
+ ...this.traceId !== void 0 ? { traceId: this.traceId } : {}
4190
+ });
4191
+ return [];
4192
+ }
3518
4193
  }
3519
4194
  /**
3520
4195
  * Convenience: only unresolved annotations, newest first — the
@@ -3566,25 +4241,62 @@ var AnnotationsStore = class {
3566
4241
  createdAt: (/* @__PURE__ */ new Date()).toISOString(),
3567
4242
  resolved: false
3568
4243
  };
3569
- await this.enqueue(input.sessionId, async () => {
3570
- await withFileLock(this.filePath(input.sessionId), async () => {
3571
- const all = await this.list(input.sessionId);
3572
- all.push(annotation);
3573
- if (all.length > MAX_ANNOTATIONS) {
3574
- const sorted = all.map((a, i) => ({ a, i })).sort((x, y) => {
3575
- if (x.a.resolved !== y.a.resolved) return x.a.resolved ? 1 : -1;
3576
- return x.a.createdAt.localeCompare(y.a.createdAt);
3577
- });
3578
- const evictCount = all.length - MAX_ANNOTATIONS;
3579
- const toEvict = new Set(sorted.slice(0, evictCount).map((s) => s.a.id));
3580
- const kept = all.filter((a) => !toEvict.has(a.id));
3581
- await this.writeFile(input.sessionId, { version: FILE_VERSION, annotations: kept });
3582
- } else {
3583
- await this.writeFile(input.sessionId, { version: FILE_VERSION, annotations: all });
3584
- }
4244
+ const fp = this.filePath(input.sessionId);
4245
+ const t0 = Date.now();
4246
+ try {
4247
+ await this.enqueue(input.sessionId, async () => {
4248
+ await withFileLock(fp, async () => {
4249
+ const all = await this.list(input.sessionId);
4250
+ all.push(annotation);
4251
+ if (all.length > MAX_ANNOTATIONS) {
4252
+ const sorted = all.map((a, i) => ({ a, i })).sort((x, y) => {
4253
+ if (x.a.resolved !== y.a.resolved) return x.a.resolved ? 1 : -1;
4254
+ return x.a.createdAt.localeCompare(y.a.createdAt);
4255
+ });
4256
+ const evictCount = all.length - MAX_ANNOTATIONS;
4257
+ const toEvict = new Set(sorted.slice(0, evictCount).map((s) => s.a.id));
4258
+ const kept = all.filter((a) => !toEvict.has(a.id));
4259
+ await this.writeFile(input.sessionId, { version: FILE_VERSION, annotations: kept });
4260
+ const durationMs = Date.now() - t0;
4261
+ this.events?.emit("storage.write", {
4262
+ sessionId: input.sessionId,
4263
+ store: "annotations",
4264
+ filePath: fp,
4265
+ operation: "evict",
4266
+ outcome: "success",
4267
+ durationMs,
4268
+ ...this.traceId !== void 0 ? { traceId: this.traceId } : {}
4269
+ });
4270
+ } else {
4271
+ await this.writeFile(input.sessionId, { version: FILE_VERSION, annotations: all });
4272
+ const durationMs = Date.now() - t0;
4273
+ this.events?.emit("storage.write", {
4274
+ sessionId: input.sessionId,
4275
+ store: "annotations",
4276
+ filePath: fp,
4277
+ operation: "add",
4278
+ outcome: "success",
4279
+ durationMs,
4280
+ ...this.traceId !== void 0 ? { traceId: this.traceId } : {}
4281
+ });
4282
+ }
4283
+ });
3585
4284
  });
3586
- });
3587
- return annotation;
4285
+ return annotation;
4286
+ } catch (err) {
4287
+ this.events?.emit("storage.error", {
4288
+ sessionId: input.sessionId,
4289
+ store: "annotations",
4290
+ filePath: fp,
4291
+ operation: "add",
4292
+ outcome: "failure",
4293
+ error: toErrorMessage(err),
4294
+ recoverable: false,
4295
+ durationMs: Date.now() - t0,
4296
+ ...this.traceId !== void 0 ? { traceId: this.traceId } : {}
4297
+ });
4298
+ throw err;
4299
+ }
3588
4300
  }
3589
4301
  /**
3590
4302
  * Mark an annotation as resolved. Returns the updated record, or
@@ -3594,26 +4306,53 @@ var AnnotationsStore = class {
3594
4306
  */
3595
4307
  async resolve(input) {
3596
4308
  let updated = null;
3597
- await this.enqueue(input.sessionId, async () => {
3598
- await withFileLock(this.filePath(input.sessionId), async () => {
3599
- const all = await this.list(input.sessionId);
3600
- const idx = all.findIndex((a) => a.id === input.annotationId);
3601
- if (idx === -1) {
3602
- updated = null;
3603
- return;
3604
- }
3605
- const next = {
3606
- ...expectDefined(all[idx]),
3607
- resolved: true,
3608
- resolvedAt: (/* @__PURE__ */ new Date()).toISOString(),
3609
- resolvedBy: input.resolvedBy
3610
- };
3611
- all[idx] = next;
3612
- await this.writeFile(input.sessionId, { version: FILE_VERSION, annotations: all });
3613
- updated = next;
4309
+ const fp = this.filePath(input.sessionId);
4310
+ const t0 = Date.now();
4311
+ try {
4312
+ await this.enqueue(input.sessionId, async () => {
4313
+ await withFileLock(fp, async () => {
4314
+ const all = await this.list(input.sessionId);
4315
+ const idx = all.findIndex((a) => a.id === input.annotationId);
4316
+ if (idx === -1) {
4317
+ updated = null;
4318
+ return;
4319
+ }
4320
+ const next = {
4321
+ ...expectDefined(all[idx]),
4322
+ resolved: true,
4323
+ resolvedAt: (/* @__PURE__ */ new Date()).toISOString(),
4324
+ resolvedBy: input.resolvedBy
4325
+ };
4326
+ all[idx] = next;
4327
+ await this.writeFile(input.sessionId, { version: FILE_VERSION, annotations: all });
4328
+ updated = next;
4329
+ const durationMs = Date.now() - t0;
4330
+ this.events?.emit("storage.write", {
4331
+ sessionId: input.sessionId,
4332
+ store: "annotations",
4333
+ filePath: fp,
4334
+ operation: "resolve",
4335
+ outcome: "success",
4336
+ durationMs,
4337
+ ...this.traceId !== void 0 ? { traceId: this.traceId } : {}
4338
+ });
4339
+ });
3614
4340
  });
3615
- });
3616
- return updated;
4341
+ return updated;
4342
+ } catch (err) {
4343
+ this.events?.emit("storage.error", {
4344
+ sessionId: input.sessionId,
4345
+ store: "annotations",
4346
+ filePath: fp,
4347
+ operation: "resolve",
4348
+ outcome: "failure",
4349
+ error: toErrorMessage(err),
4350
+ recoverable: false,
4351
+ durationMs: Date.now() - t0,
4352
+ ...this.traceId !== void 0 ? { traceId: this.traceId } : {}
4353
+ });
4354
+ throw err;
4355
+ }
3617
4356
  }
3618
4357
  // ── Internals ──────────────────────────────────────────────────────────
3619
4358
  filePath(sessionId) {
@@ -3621,16 +4360,21 @@ var AnnotationsStore = class {
3621
4360
  }
3622
4361
  async readFile(sessionId) {
3623
4362
  const fp = this.filePath(sessionId);
4363
+ let raw;
4364
+ try {
4365
+ raw = await fsp.readFile(fp, "utf8");
4366
+ } catch (err) {
4367
+ if (err.code === "ENOENT") return null;
4368
+ throw err;
4369
+ }
3624
4370
  try {
3625
- const raw = await fsp.readFile(fp, "utf8");
3626
4371
  const parsed = JSON.parse(raw);
3627
4372
  if (parsed.version !== FILE_VERSION) {
3628
4373
  return { version: FILE_VERSION, annotations: [] };
3629
4374
  }
3630
4375
  return parsed;
3631
- } catch (err) {
3632
- if (err.code === "ENOENT") return null;
3633
- return { version: FILE_VERSION, annotations: [] };
4376
+ } catch {
4377
+ return null;
3634
4378
  }
3635
4379
  }
3636
4380
  async writeFile(sessionId, file) {
@@ -3684,9 +4428,20 @@ function hashRequest(request) {
3684
4428
  const digest = createHash("sha256").update(json, "utf8").digest("hex");
3685
4429
  return `sha256:${digest}`;
3686
4430
  }
4431
+
4432
+ // src/storage/replay-log-store.ts
4433
+ function storageErrorString2(err) {
4434
+ if (err instanceof Error) {
4435
+ const code = err.code;
4436
+ return code ? `${code}: ${err.message}` : err.message;
4437
+ }
4438
+ return String(err);
4439
+ }
3687
4440
  var DEFAULT_MAX_ENTRIES = 1e3;
3688
4441
  var ReplayLogStore = class {
3689
4442
  dir;
4443
+ events;
4444
+ traceId;
3690
4445
  writeChains = /* @__PURE__ */ new Map();
3691
4446
  /** Per-session hash → entry index, kept in memory after the first load. */
3692
4447
  cache = /* @__PURE__ */ new Map();
@@ -3695,6 +4450,8 @@ var ReplayLogStore = class {
3695
4450
  maxEntries;
3696
4451
  constructor(opts) {
3697
4452
  this.dir = opts.dir;
4453
+ this.events = opts.events;
4454
+ this.traceId = opts.traceId;
3698
4455
  this.maxEntries = opts.maxEntries ?? DEFAULT_MAX_ENTRIES;
3699
4456
  }
3700
4457
  // ── Writes ──────────────────────────────────────────────────────────────
@@ -3705,38 +4462,61 @@ var ReplayLogStore = class {
3705
4462
  */
3706
4463
  async record(input) {
3707
4464
  const hash = hashRequest(input.request);
3708
- await this.enqueue(input.sessionId, async () => {
3709
- await withFileLock(this.filePath(input.sessionId), async () => {
3710
- const entries = await this.readAll(input.sessionId);
3711
- if (entries.some((entry2) => entry2.hash === hash)) return;
3712
- const entry = {
3713
- hash,
3714
- ts: (/* @__PURE__ */ new Date()).toISOString(),
3715
- request: input.request,
3716
- response: input.response
3717
- };
3718
- entries.push(entry);
3719
- const keep = entries.slice(-this.maxEntries);
3720
- const cache = /* @__PURE__ */ new Map();
3721
- for (const e of keep) cache.set(e.hash, e);
3722
- this.cache.set(input.sessionId, cache);
3723
- await this.rewriteCache(input.sessionId, cache);
4465
+ const fp = this.filePath(input.sessionId);
4466
+ const t0 = Date.now();
4467
+ try {
4468
+ await this.enqueue(input.sessionId, async () => {
4469
+ await withFileLock(fp, async () => {
4470
+ const cache = await this.ensureCache(input.sessionId);
4471
+ if (cache.has(hash)) return;
4472
+ const entry = {
4473
+ hash,
4474
+ ts: (/* @__PURE__ */ new Date()).toISOString(),
4475
+ request: input.request,
4476
+ response: input.response
4477
+ };
4478
+ const currentCount = this.diskCount.get(input.sessionId) ?? 0;
4479
+ const willEvict = currentCount + 1 > this.maxEntries;
4480
+ if (!willEvict) {
4481
+ await fsp.appendFile(fp, JSON.stringify(entry) + "\n", "utf8");
4482
+ cache.set(hash, entry);
4483
+ this.diskCount.set(input.sessionId, currentCount + 1);
4484
+ this.events?.emit("storage.write", {
4485
+ sessionId: input.sessionId,
4486
+ store: "replay",
4487
+ filePath: fp,
4488
+ operation: "record",
4489
+ outcome: "success",
4490
+ durationMs: Date.now() - t0,
4491
+ ...this.traceId !== void 0 ? { traceId: this.traceId } : {}
4492
+ });
4493
+ return;
4494
+ }
4495
+ const all = await this.readAll(input.sessionId);
4496
+ all.push(entry);
4497
+ const keep = all.slice(-this.maxEntries);
4498
+ const refreshed = /* @__PURE__ */ new Map();
4499
+ for (const e of keep) refreshed.set(e.hash, e);
4500
+ this.cache.set(input.sessionId, refreshed);
4501
+ this.diskCount.set(input.sessionId, keep.length);
4502
+ await this.writeAll(input.sessionId, keep, "compact");
4503
+ });
3724
4504
  });
3725
- });
3726
- return hash;
3727
- }
3728
- /**
3729
- * Compact the replay log to keep only the most recent maxEntries.
3730
- * Called when entry count exceeds the cap. Rewrites the entire file
3731
- * but only happens O(n / maxEntries) times per session.
3732
- */
3733
- async rewriteCache(sessionId, cache) {
3734
- const all = [...cache.values()];
3735
- const keep = all.slice(-this.maxEntries);
3736
- await this.writeAll(sessionId, keep);
3737
- cache.clear();
3738
- for (const e of keep) cache.set(e.hash, e);
3739
- this.diskCount.set(sessionId, keep.length);
4505
+ return hash;
4506
+ } catch (err) {
4507
+ this.events?.emit("storage.error", {
4508
+ sessionId: input.sessionId,
4509
+ store: "replay",
4510
+ filePath: fp,
4511
+ operation: "record",
4512
+ outcome: "failure",
4513
+ error: storageErrorString2(err),
4514
+ recoverable: false,
4515
+ durationMs: Date.now() - t0,
4516
+ ...this.traceId !== void 0 ? { traceId: this.traceId } : {}
4517
+ });
4518
+ throw err;
4519
+ }
3740
4520
  }
3741
4521
  // ── Reads ───────────────────────────────────────────────────────────────
3742
4522
  /**
@@ -3745,13 +4525,65 @@ var ReplayLogStore = class {
3745
4525
  * per session (in-memory cache).
3746
4526
  */
3747
4527
  async lookup(sessionId, hash) {
3748
- const cache = await this.ensureCache(sessionId);
3749
- return cache.get(hash) ?? null;
4528
+ const fp = this.filePath(sessionId);
4529
+ const t0 = Date.now();
4530
+ try {
4531
+ const cache = await this.ensureCache(sessionId);
4532
+ this.events?.emit("storage.read", {
4533
+ sessionId,
4534
+ store: "replay",
4535
+ filePath: fp,
4536
+ operation: "lookup",
4537
+ outcome: "success",
4538
+ durationMs: Date.now() - t0,
4539
+ ...this.traceId !== void 0 ? { traceId: this.traceId } : {}
4540
+ });
4541
+ return cache.get(hash) ?? null;
4542
+ } catch (err) {
4543
+ this.events?.emit("storage.read", {
4544
+ sessionId,
4545
+ store: "replay",
4546
+ filePath: fp,
4547
+ operation: "lookup",
4548
+ outcome: "failure",
4549
+ durationMs: Date.now() - t0,
4550
+ error: storageErrorString2(err),
4551
+ ...this.traceId !== void 0 ? { traceId: this.traceId } : {}
4552
+ });
4553
+ throw err;
4554
+ }
3750
4555
  }
3751
4556
  /** All recorded entries for a session, in insertion order. */
3752
4557
  async load(sessionId) {
3753
- const cache = await this.ensureCache(sessionId);
3754
- return [...cache.values()];
4558
+ const fp = this.filePath(sessionId);
4559
+ const t0 = Date.now();
4560
+ try {
4561
+ const cache = await this.ensureCache(sessionId);
4562
+ const durationMs = Date.now() - t0;
4563
+ this.events?.emit("storage.read", {
4564
+ sessionId,
4565
+ store: "replay",
4566
+ filePath: fp,
4567
+ operation: "load",
4568
+ outcome: "success",
4569
+ durationMs,
4570
+ ...this.traceId !== void 0 ? { traceId: this.traceId } : {}
4571
+ });
4572
+ return [...cache.values()];
4573
+ } catch (err) {
4574
+ const durationMs = Date.now() - t0;
4575
+ this.events?.emit("storage.read", {
4576
+ sessionId,
4577
+ store: "replay",
4578
+ filePath: fp,
4579
+ operation: "load",
4580
+ outcome: "failure",
4581
+ durationMs,
4582
+ error: storageErrorString2(err),
4583
+ ...this.traceId !== void 0 ? { traceId: this.traceId } : {}
4584
+ });
4585
+ throw err;
4586
+ }
3755
4587
  }
3756
4588
  /**
3757
4589
  * List every session id that has a replay log in the store dir.
@@ -3770,7 +4602,7 @@ var ReplayLogStore = class {
3770
4602
  level: "warn",
3771
4603
  event: "replay_log_store.list_readdir_failed",
3772
4604
  dir,
3773
- message: err instanceof Error ? err.message : String(err),
4605
+ message: toErrorMessage(err),
3774
4606
  timestamp: (/* @__PURE__ */ new Date()).toISOString()
3775
4607
  }));
3776
4608
  }
@@ -3779,7 +4611,7 @@ var ReplayLogStore = class {
3779
4611
  for (const entry of entries) {
3780
4612
  if (entry.name.startsWith(".")) continue;
3781
4613
  if (entry.isDirectory()) {
3782
- if (depth === 0) await scan(path13.join(dir, entry.name), entry.name, depth + 1);
4614
+ if (depth === 0) await scan(path2.join(dir, entry.name), entry.name, depth + 1);
3783
4615
  continue;
3784
4616
  }
3785
4617
  if (!entry.isFile() || !entry.name.endsWith(".replay.jsonl")) continue;
@@ -3789,7 +4621,7 @@ var ReplayLogStore = class {
3789
4621
  out.push({
3790
4622
  sessionId,
3791
4623
  entryCount: all.length,
3792
- path: path13.join(dir, entry.name)
4624
+ path: path2.join(dir, entry.name)
3793
4625
  });
3794
4626
  }
3795
4627
  };
@@ -3821,13 +4653,24 @@ var ReplayLogStore = class {
3821
4653
  return out;
3822
4654
  } catch (err) {
3823
4655
  if (err.code === "ENOENT") return [];
3824
- return [];
4656
+ throw err;
3825
4657
  }
3826
4658
  }
3827
- async writeAll(sessionId, entries) {
4659
+ async writeAll(sessionId, entries, operation = "record") {
3828
4660
  const fp = this.filePath(sessionId);
4661
+ const t0 = Date.now();
3829
4662
  const body = entries.map((e) => JSON.stringify(e)).join("\n") + (entries.length ? "\n" : "");
3830
4663
  await atomicWrite(fp, body);
4664
+ const durationMs = Date.now() - t0;
4665
+ this.events?.emit("storage.write", {
4666
+ sessionId,
4667
+ store: "replay",
4668
+ filePath: fp,
4669
+ operation,
4670
+ outcome: "success",
4671
+ durationMs,
4672
+ ...this.traceId !== void 0 ? { traceId: this.traceId } : {}
4673
+ });
3831
4674
  }
3832
4675
  async ensureCache(sessionId) {
3833
4676
  let cache = this.cache.get(sessionId);
@@ -3867,15 +4710,15 @@ var SessionRecovery = class {
3867
4710
  async detectStale(sessionId) {
3868
4711
  const fp = this.filePath(sessionId);
3869
4712
  const TAIL_SIZE = 8192;
3870
- let stat5;
4713
+ let stat6;
3871
4714
  try {
3872
- stat5 = await fsp.stat(fp);
4715
+ stat6 = await fsp.stat(fp);
3873
4716
  } catch (err) {
3874
4717
  if (err.code === "ENOENT") return null;
3875
4718
  return null;
3876
4719
  }
3877
- if (stat5.size === 0) return null;
3878
- const position = Math.max(0, stat5.size - TAIL_SIZE);
4720
+ if (stat6.size === 0) return null;
4721
+ const position = Math.max(0, stat6.size - TAIL_SIZE);
3879
4722
  const buf = Buffer.alloc(TAIL_SIZE);
3880
4723
  let fh;
3881
4724
  try {
@@ -3979,7 +4822,7 @@ var SessionRecovery = class {
3979
4822
  continue;
3980
4823
  if (entry.isDirectory()) {
3981
4824
  if (depth === 0) {
3982
- await collect(path13.join(dir, entry.name), entry.name, depth + 1);
4825
+ await collect(path2.join(dir, entry.name), entry.name, depth + 1);
3983
4826
  }
3984
4827
  continue;
3985
4828
  }
@@ -4005,16 +4848,27 @@ var GENESIS_PREV = "0".repeat(64);
4005
4848
  var DEFAULT_FSYNC_EVERY = 100;
4006
4849
  var ToolAuditLog = class {
4007
4850
  dir;
4851
+ events;
4852
+ traceId;
4008
4853
  /** In-memory cache of the last entry's hash (per session), to compute chains efficiently. */
4009
4854
  tailHash = /* @__PURE__ */ new Map();
4010
4855
  /** In-memory counter for entry indices — avoids re-reading the file on every write. */
4011
4856
  tailIndex = /* @__PURE__ */ new Map();
4857
+ /**
4858
+ * File mtime+size recorded after our last write, per session. Used to
4859
+ * detect cross-process writes (session handoff, recovery) that would
4860
+ * invalidate the in-memory tail cache: if the stat no longer matches
4861
+ * we re-read the file to re-establish the chain tip before appending.
4862
+ */
4863
+ tailStat = /* @__PURE__ */ new Map();
4012
4864
  /** Tracks writes since last fsync, per session. */
4013
4865
  unSyncedWrites = /* @__PURE__ */ new Map();
4014
4866
  writeChains = /* @__PURE__ */ new Map();
4015
4867
  fsyncEvery;
4016
4868
  constructor(opts) {
4017
4869
  this.dir = opts.dir;
4870
+ this.events = opts.events;
4871
+ this.traceId = opts.traceId;
4018
4872
  this.fsyncEvery = opts.fsyncEvery ?? DEFAULT_FSYNC_EVERY;
4019
4873
  }
4020
4874
  /**
@@ -4025,96 +4879,223 @@ var ToolAuditLog = class {
4025
4879
  */
4026
4880
  async record(input) {
4027
4881
  let entry;
4028
- await this.enqueue(input.sessionId, async () => {
4029
- await withFileLock(this.filePath(input.sessionId), async () => {
4030
- const entries = await this.readAll(input.sessionId);
4031
- const prev = entries.at(-1);
4032
- const prevHash = prev?.hash ?? GENESIS_PREV;
4033
- const index = prev ? prev.index + 1 : 0;
4034
- const id = randomUUID();
4035
- const ts = (/* @__PURE__ */ new Date()).toISOString();
4036
- const content = {
4037
- id,
4038
- ts,
4039
- prevHash,
4040
- toolName: input.toolName,
4041
- toolUseId: input.toolUseId,
4042
- input: input.input,
4043
- output: input.output,
4044
- isError: input.isError,
4045
- index
4046
- };
4047
- const hash = createHash("sha256").update(stableStringify2(content), "utf8").digest("hex");
4048
- entry = {
4049
- id,
4050
- ts,
4051
- prevHash,
4052
- hash,
4053
- toolName: input.toolName,
4054
- toolUseId: input.toolUseId,
4055
- input: input.input,
4056
- output: input.output,
4057
- isError: input.isError,
4058
- index
4059
- };
4060
- entries.push(entry);
4061
- await this.writeAll(input.sessionId, entries);
4062
- this.tailHash.set(input.sessionId, hash);
4063
- this.tailIndex.set(input.sessionId, index + 1);
4882
+ const fp = this.filePath(input.sessionId);
4883
+ const t0 = Date.now();
4884
+ try {
4885
+ await this.enqueue(input.sessionId, async () => {
4886
+ await withFileLock(fp, async () => {
4887
+ const tip = await this._resolveChainTip(input.sessionId, fp);
4888
+ const prevHash = tip.prevHash;
4889
+ const index = tip.nextIndex;
4890
+ const id = randomUUID();
4891
+ const ts = (/* @__PURE__ */ new Date()).toISOString();
4892
+ const content = {
4893
+ id,
4894
+ ts,
4895
+ prevHash,
4896
+ toolName: input.toolName,
4897
+ toolUseId: input.toolUseId,
4898
+ input: input.input,
4899
+ output: input.output,
4900
+ isError: input.isError,
4901
+ index
4902
+ };
4903
+ const hash = createHash("sha256").update(stableStringify2(content), "utf8").digest("hex");
4904
+ entry = {
4905
+ id,
4906
+ ts,
4907
+ prevHash,
4908
+ hash,
4909
+ toolName: input.toolName,
4910
+ toolUseId: input.toolUseId,
4911
+ input: input.input,
4912
+ output: input.output,
4913
+ isError: input.isError,
4914
+ index
4915
+ };
4916
+ await fsp.appendFile(fp, JSON.stringify(entry) + "\n", "utf8");
4917
+ try {
4918
+ const st = await fsp.stat(fp);
4919
+ this.tailStat.set(input.sessionId, { mtimeMs: st.mtimeMs, size: st.size });
4920
+ } catch {
4921
+ }
4922
+ this.tailHash.set(input.sessionId, hash);
4923
+ this.tailIndex.set(input.sessionId, index + 1);
4924
+ await this._trackUnsynced(input.sessionId, fp);
4925
+ const durationMs = Date.now() - t0;
4926
+ this.events?.emit("storage.write", {
4927
+ sessionId: input.sessionId,
4928
+ store: "audit",
4929
+ filePath: fp,
4930
+ operation: "record",
4931
+ outcome: "success",
4932
+ durationMs,
4933
+ ...this.traceId !== void 0 ? { traceId: this.traceId } : {}
4934
+ });
4935
+ });
4064
4936
  });
4065
- });
4066
- return entry;
4937
+ return entry;
4938
+ } catch (err) {
4939
+ this.events?.emit("storage.error", {
4940
+ sessionId: input.sessionId,
4941
+ store: "audit",
4942
+ filePath: fp,
4943
+ operation: "record",
4944
+ outcome: "failure",
4945
+ error: toErrorMessage(err),
4946
+ recoverable: false,
4947
+ durationMs: Date.now() - t0,
4948
+ ...this.traceId !== void 0 ? { traceId: this.traceId } : {}
4949
+ });
4950
+ throw err;
4951
+ }
4952
+ }
4953
+ /**
4954
+ * Resolve the chain tip (previous hash + next index) for an append.
4955
+ * Uses the in-memory `tailHash`/`tailIndex` cache when the file's
4956
+ * stat matches our last known write; falls back to a full read on
4957
+ * cache miss or when an external writer has extended the file.
4958
+ */
4959
+ async _resolveChainTip(sessionId, fp) {
4960
+ const cachedHash = this.tailHash.get(sessionId);
4961
+ const cachedIndex = this.tailIndex.get(sessionId);
4962
+ const cachedStat = this.tailStat.get(sessionId);
4963
+ if (cachedHash !== void 0 && cachedIndex !== void 0 && cachedStat) {
4964
+ try {
4965
+ const st = await fsp.stat(fp);
4966
+ if (st.mtimeMs === cachedStat.mtimeMs && st.size === cachedStat.size) {
4967
+ return { prevHash: cachedHash, nextIndex: cachedIndex };
4968
+ }
4969
+ } catch (err) {
4970
+ if (err.code === "ENOENT") {
4971
+ this.tailHash.delete(sessionId);
4972
+ this.tailIndex.delete(sessionId);
4973
+ this.tailStat.delete(sessionId);
4974
+ return { prevHash: GENESIS_PREV, nextIndex: 0 };
4975
+ }
4976
+ throw err;
4977
+ }
4978
+ }
4979
+ const entries = await this.readAll(sessionId);
4980
+ const prev = entries.at(-1);
4981
+ const prevHash = prev?.hash ?? GENESIS_PREV;
4982
+ const nextIndex = prev ? prev.index + 1 : 0;
4983
+ this.tailHash.set(sessionId, prevHash);
4984
+ this.tailIndex.set(sessionId, nextIndex);
4985
+ try {
4986
+ const st = await fsp.stat(fp);
4987
+ this.tailStat.set(sessionId, { mtimeMs: st.mtimeMs, size: st.size });
4988
+ } catch {
4989
+ }
4990
+ return { prevHash, nextIndex };
4067
4991
  }
4068
4992
  /**
4069
4993
  * Walk the chain and verify every entry's hash and prevHash.
4070
4994
  * Returns a structured verdict — never throws.
4071
4995
  */
4072
4996
  async verify(sessionId) {
4073
- const entries = await this.readAll(sessionId);
4074
- if (entries.length === 0) return { ok: true, entries: 0 };
4075
- if (entries[0]?.prevHash !== GENESIS_PREV) {
4076
- return {
4077
- ok: false,
4078
- brokenAt: 0,
4079
- reason: "first entry is not the genesis (prevHash != 0\u20260)"
4080
- };
4997
+ const fp = this.filePath(sessionId);
4998
+ const t0 = Date.now();
4999
+ let entries;
5000
+ try {
5001
+ entries = await this.readAll(sessionId);
5002
+ } catch (err) {
5003
+ this.events?.emit("storage.read", {
5004
+ sessionId,
5005
+ store: "audit",
5006
+ filePath: fp,
5007
+ operation: "verify",
5008
+ outcome: "failure",
5009
+ durationMs: Date.now() - t0,
5010
+ error: toErrorMessage(err),
5011
+ ...this.traceId !== void 0 ? { traceId: this.traceId } : {}
5012
+ });
5013
+ return { ok: true, entries: 0 };
4081
5014
  }
4082
- let prevHash = GENESIS_PREV;
4083
- for (let i = 0; i < entries.length; i++) {
4084
- const e = expectDefined(entries[i]);
4085
- if (e.prevHash !== prevHash) {
5015
+ const verdict = (() => {
5016
+ if (entries.length === 0) return { ok: true, entries: 0 };
5017
+ if (entries[0]?.prevHash !== GENESIS_PREV) {
4086
5018
  return {
4087
5019
  ok: false,
4088
- brokenAt: i,
4089
- reason: `prevHash mismatch at entry ${i} (expected ${prevHash.slice(0, 8)}\u2026, got ${e.prevHash.slice(0, 8)}\u2026)`
5020
+ brokenAt: 0,
5021
+ reason: "first entry is not the genesis (prevHash != 0\u20260)"
4090
5022
  };
4091
5023
  }
4092
- const content = {
4093
- id: e.id,
4094
- ts: e.ts,
4095
- prevHash: e.prevHash,
4096
- toolName: e.toolName,
4097
- toolUseId: e.toolUseId,
4098
- input: e.input,
4099
- output: e.output,
4100
- isError: e.isError,
4101
- index: e.index
4102
- };
4103
- const expectedHash = createHash("sha256").update(stableStringify2(content), "utf8").digest("hex");
4104
- if (expectedHash !== e.hash) {
4105
- return {
4106
- ok: false,
4107
- brokenAt: i,
4108
- reason: `hash mismatch at entry ${i} (entry content was modified)`
5024
+ let prevHash = GENESIS_PREV;
5025
+ for (let i = 0; i < entries.length; i++) {
5026
+ const e = expectDefined(entries[i]);
5027
+ if (e.prevHash !== prevHash) {
5028
+ return {
5029
+ ok: false,
5030
+ brokenAt: i,
5031
+ reason: `prevHash mismatch at entry ${i} (expected ${prevHash.slice(0, 8)}\u2026, got ${e.prevHash.slice(0, 8)}\u2026)`
5032
+ };
5033
+ }
5034
+ const content = {
5035
+ id: e.id,
5036
+ ts: e.ts,
5037
+ prevHash: e.prevHash,
5038
+ toolName: e.toolName,
5039
+ toolUseId: e.toolUseId,
5040
+ input: e.input,
5041
+ output: e.output,
5042
+ isError: e.isError,
5043
+ index: e.index
4109
5044
  };
5045
+ const expectedHash = createHash("sha256").update(stableStringify2(content), "utf8").digest("hex");
5046
+ if (expectedHash !== e.hash) {
5047
+ return {
5048
+ ok: false,
5049
+ brokenAt: i,
5050
+ reason: `hash mismatch at entry ${i} (entry content was modified)`
5051
+ };
5052
+ }
5053
+ prevHash = e.hash;
4110
5054
  }
4111
- prevHash = e.hash;
4112
- }
4113
- return { ok: true, entries: entries.length };
5055
+ return { ok: true, entries: entries.length };
5056
+ })();
5057
+ this.events?.emit("storage.read", {
5058
+ sessionId,
5059
+ store: "audit",
5060
+ filePath: fp,
5061
+ operation: "verify",
5062
+ outcome: verdict.ok ? "success" : "failure",
5063
+ durationMs: Date.now() - t0,
5064
+ ...this.traceId !== void 0 ? { traceId: this.traceId } : {}
5065
+ });
5066
+ return verdict;
4114
5067
  }
4115
5068
  /** All entries for a session, in insertion order. */
4116
5069
  async load(sessionId) {
4117
- return this.readAll(sessionId);
5070
+ const fp = this.filePath(sessionId);
5071
+ const t0 = Date.now();
5072
+ try {
5073
+ const entries = await this.readAll(sessionId);
5074
+ const durationMs = Date.now() - t0;
5075
+ this.events?.emit("storage.read", {
5076
+ sessionId,
5077
+ store: "audit",
5078
+ filePath: fp,
5079
+ operation: "load",
5080
+ outcome: "success",
5081
+ durationMs,
5082
+ ...this.traceId !== void 0 ? { traceId: this.traceId } : {}
5083
+ });
5084
+ return entries;
5085
+ } catch (err) {
5086
+ const durationMs = Date.now() - t0;
5087
+ this.events?.emit("storage.read", {
5088
+ sessionId,
5089
+ store: "audit",
5090
+ filePath: fp,
5091
+ operation: "load",
5092
+ outcome: "failure",
5093
+ durationMs,
5094
+ error: toErrorMessage(err),
5095
+ ...this.traceId !== void 0 ? { traceId: this.traceId } : {}
5096
+ });
5097
+ throw err;
5098
+ }
4118
5099
  }
4119
5100
  // ── Internals ────────────────────────────────────────────────────────────
4120
5101
  filePath(sessionId) {
@@ -4136,13 +5117,15 @@ var ToolAuditLog = class {
4136
5117
  return out;
4137
5118
  } catch (err) {
4138
5119
  if (err.code === "ENOENT") return [];
4139
- return [];
5120
+ throw err;
4140
5121
  }
4141
5122
  }
4142
- async writeAll(sessionId, entries) {
4143
- const fp = this.filePath(sessionId);
4144
- const line = entries.map((e) => JSON.stringify(e)).join("\n") + (entries.length ? "\n" : "");
4145
- await atomicWrite(fp, line, { mode: 384 });
5123
+ /**
5124
+ * Tracks writes since last fsync and triggers periodic fsync.
5125
+ * Called after each O(1) append to maintain the same durability
5126
+ * guarantees as the old writeAll approach.
5127
+ */
5128
+ async _trackUnsynced(sessionId, fp) {
4146
5129
  const count = (this.unSyncedWrites.get(sessionId) ?? 0) + 1;
4147
5130
  this.unSyncedWrites.set(sessionId, count);
4148
5131
  if (this.fsyncEvery !== Number.POSITIVE_INFINITY && count % this.fsyncEvery === 0) {
@@ -4311,7 +5294,7 @@ var SessionRegistry = class {
4311
5294
  heartbeatTimer = null;
4312
5295
  currentSessionId = null;
4313
5296
  constructor(globalRoot) {
4314
- this.filePath = path13.join(globalRoot, REGISTRY_FILE);
5297
+ this.filePath = path2.join(globalRoot, REGISTRY_FILE);
4315
5298
  }
4316
5299
  // ── Public API ──────────────────────────────────────────────────────────
4317
5300
  /**
@@ -4478,7 +5461,7 @@ var SessionRegistry = class {
4478
5461
  const retryDelayMs = 20;
4479
5462
  for (let attempt = 0; attempt < maxRetries; attempt++) {
4480
5463
  try {
4481
- await fsp.mkdir(path13.dirname(this.filePath), { recursive: true });
5464
+ await fsp.mkdir(path2.dirname(this.filePath), { recursive: true });
4482
5465
  const lockHandle = await fsp.open(lockPath, "wx").catch(() => null);
4483
5466
  if (!lockHandle) {
4484
5467
  await new Promise((r) => setTimeout(r, retryDelayMs * (attempt + 1)));
@@ -4691,7 +5674,7 @@ var DefaultSessionRewinder = class {
4691
5674
  sessionsDir;
4692
5675
  projectRoot;
4693
5676
  async listCheckpoints(sessionId) {
4694
- const file = path13.join(this.sessionsDir, `${sessionId}.jsonl`);
5677
+ const file = path2.join(this.sessionsDir, `${sessionId}.jsonl`);
4695
5678
  const raw = await fsp.readFile(file, "utf8");
4696
5679
  const events = parseEvents(raw);
4697
5680
  const fileCountMap = /* @__PURE__ */ new Map();
@@ -4716,7 +5699,7 @@ var DefaultSessionRewinder = class {
4716
5699
  return checkpoints;
4717
5700
  }
4718
5701
  async rewindToCheckpoint(sessionId, checkpointIndex) {
4719
- const file = path13.join(this.sessionsDir, `${sessionId}.jsonl`);
5702
+ const file = path2.join(this.sessionsDir, `${sessionId}.jsonl`);
4720
5703
  const raw = await fsp.readFile(file, "utf8");
4721
5704
  const events = parseEvents(raw);
4722
5705
  let targetIdx = -1;
@@ -4755,7 +5738,7 @@ var DefaultSessionRewinder = class {
4755
5738
  return { ...result, toPromptIndex: checkpointIndex, removedEvents };
4756
5739
  }
4757
5740
  async rewindLastN(sessionId, n) {
4758
- const file = path13.join(this.sessionsDir, `${sessionId}.jsonl`);
5741
+ const file = path2.join(this.sessionsDir, `${sessionId}.jsonl`);
4759
5742
  const raw = await fsp.readFile(file, "utf8");
4760
5743
  const events = parseEvents(raw);
4761
5744
  const checkpoints = [];
@@ -4784,7 +5767,7 @@ var DefaultSessionRewinder = class {
4784
5767
  return { ...result, toPromptIndex: targetIndex, removedEvents: snapshotsToRevert.length };
4785
5768
  }
4786
5769
  async rewindToStart(sessionId) {
4787
- const file = path13.join(this.sessionsDir, `${sessionId}.jsonl`);
5770
+ const file = path2.join(this.sessionsDir, `${sessionId}.jsonl`);
4788
5771
  const raw = await fsp.readFile(file, "utf8");
4789
5772
  const events = parseEvents(raw);
4790
5773
  const allSnapshots = [];
@@ -4820,10 +5803,10 @@ async function revertSnapshots(snapshots, projectRoot) {
4820
5803
  for (const snapshot of snapshots) {
4821
5804
  for (const file of snapshot.files) {
4822
5805
  try {
4823
- const absPath = path13.resolve(file.path);
4824
- const root = path13.resolve(projectRoot);
4825
- const rel = path13.relative(root, absPath);
4826
- if (rel.startsWith("..") || path13.isAbsolute(rel)) {
5806
+ const absPath = path2.resolve(file.path);
5807
+ const root = path2.resolve(projectRoot);
5808
+ const rel = path2.relative(root, absPath);
5809
+ if (rel.startsWith("..") || path2.isAbsolute(rel)) {
4827
5810
  errors.push(`${file.path}: path resolves outside project root \u2014 skipping`);
4828
5811
  continue;
4829
5812
  }
@@ -4842,30 +5825,72 @@ async function revertSnapshots(snapshots, projectRoot) {
4842
5825
  }
4843
5826
  }
4844
5827
  } catch (err) {
4845
- errors.push(`${file.path}: ${err instanceof Error ? err.message : String(err)}`);
5828
+ errors.push(`${file.path}: ${toErrorMessage(err)}`);
4846
5829
  }
4847
5830
  }
4848
5831
  }
4849
5832
  return { revertedFiles, errors };
4850
5833
  }
4851
- async function loadTodosCheckpoint(filePath) {
5834
+ async function loadTodosCheckpoint(filePath, events, traceId) {
5835
+ const t0 = Date.now();
4852
5836
  let raw;
4853
5837
  try {
4854
5838
  raw = await fsp.readFile(filePath, "utf8");
4855
- } catch {
5839
+ } catch (err) {
5840
+ events?.emit("storage.error", {
5841
+ sessionId: traceId ?? "~boot~",
5842
+ store: "todos",
5843
+ filePath,
5844
+ operation: "load",
5845
+ outcome: "failure",
5846
+ error: toErrorMessage(err),
5847
+ recoverable: true
5848
+ });
4856
5849
  return null;
4857
5850
  }
4858
5851
  try {
4859
5852
  const parsed = JSON.parse(raw);
4860
- if (parsed?.version !== 1 || !Array.isArray(parsed.todos)) return null;
5853
+ if (parsed?.version !== 1 || !Array.isArray(parsed.todos)) {
5854
+ events?.emit("storage.read", {
5855
+ sessionId: traceId ?? "~boot~",
5856
+ store: "todos",
5857
+ filePath,
5858
+ operation: "load",
5859
+ outcome: "failure",
5860
+ durationMs: Date.now() - t0,
5861
+ error: "invalid_schema",
5862
+ ...traceId !== void 0 && { traceId }
5863
+ });
5864
+ return null;
5865
+ }
5866
+ events?.emit("storage.read", {
5867
+ sessionId: traceId ?? "~boot~",
5868
+ store: "todos",
5869
+ filePath,
5870
+ operation: "load",
5871
+ outcome: "success",
5872
+ durationMs: Date.now() - t0,
5873
+ ...traceId !== void 0 && { traceId }
5874
+ });
4861
5875
  return parsed.todos.filter(
4862
5876
  (t) => !!t && typeof t.id === "string" && typeof t.content === "string" && typeof t.status === "string" && (t.activeForm === void 0 || typeof t.activeForm === "string")
4863
5877
  );
4864
5878
  } catch {
5879
+ events?.emit("storage.read", {
5880
+ sessionId: traceId ?? "~boot~",
5881
+ store: "todos",
5882
+ filePath,
5883
+ operation: "load",
5884
+ outcome: "failure",
5885
+ durationMs: Date.now() - t0,
5886
+ error: "parse_failed",
5887
+ ...traceId !== void 0 && { traceId }
5888
+ });
4865
5889
  return null;
4866
5890
  }
4867
5891
  }
4868
- async function saveTodosCheckpoint(filePath, sessionId, todos) {
5892
+ async function saveTodosCheckpoint(filePath, sessionId, todos, events, traceId) {
5893
+ const t0 = Date.now();
4869
5894
  const payload = {
4870
5895
  version: 1,
4871
5896
  sessionId,
@@ -4874,22 +5899,40 @@ async function saveTodosCheckpoint(filePath, sessionId, todos) {
4874
5899
  };
4875
5900
  try {
4876
5901
  await atomicWrite(filePath, JSON.stringify(payload, null, 2), { mode: 384 });
5902
+ events?.emit("storage.write", {
5903
+ sessionId: traceId ?? sessionId,
5904
+ store: "todos",
5905
+ filePath,
5906
+ operation: "save",
5907
+ outcome: "success",
5908
+ durationMs: Date.now() - t0,
5909
+ ...traceId !== void 0 && { traceId }
5910
+ });
4877
5911
  } catch (err) {
5912
+ events?.emit("storage.error", {
5913
+ sessionId: traceId ?? sessionId,
5914
+ store: "todos",
5915
+ filePath,
5916
+ operation: "save",
5917
+ outcome: "failure",
5918
+ error: toErrorMessage(err),
5919
+ recoverable: false
5920
+ });
4878
5921
  console.warn(JSON.stringify({
4879
5922
  level: "warn",
4880
5923
  event: "todos_checkpoint.save_failed",
4881
- message: err instanceof Error ? err.message : String(err),
5924
+ message: toErrorMessage(err),
4882
5925
  timestamp: (/* @__PURE__ */ new Date()).toISOString()
4883
5926
  }));
4884
5927
  }
4885
5928
  }
4886
- function attachTodosCheckpoint(state, filePath, sessionId) {
5929
+ function attachTodosCheckpoint(state, filePath, sessionId, events, traceId) {
4887
5930
  let timer = null;
4888
5931
  let pending = null;
4889
5932
  let writeChain = Promise.resolve();
4890
5933
  const enqueueWrite = (todos) => {
4891
- writeChain = writeChain.then(() => saveTodosCheckpoint(filePath, sessionId, todos)).catch((err) => {
4892
- const msg = err instanceof Error ? err.message : String(err);
5934
+ writeChain = writeChain.then(() => saveTodosCheckpoint(filePath, sessionId, todos, events, traceId)).catch((err) => {
5935
+ const msg = toErrorMessage(err);
4893
5936
  console.error(JSON.stringify({
4894
5937
  level: "error",
4895
5938
  event: "todos_checkpoint.write_chain_failed",
@@ -4927,28 +5970,82 @@ function attachTodosCheckpoint(state, filePath, sessionId) {
4927
5970
  }
4928
5971
  };
4929
5972
  }
4930
- async function loadPlan(filePath) {
5973
+ async function loadPlan(filePath, events) {
5974
+ const t0 = Date.now();
4931
5975
  let raw;
4932
5976
  try {
4933
5977
  raw = await fsp.readFile(filePath, "utf8");
4934
- } catch {
5978
+ } catch (err) {
5979
+ events?.emit("storage.error", {
5980
+ sessionId: "~boot~",
5981
+ store: "plan",
5982
+ filePath,
5983
+ operation: "load",
5984
+ error: toErrorMessage(err),
5985
+ recoverable: true
5986
+ });
4935
5987
  return null;
4936
5988
  }
4937
5989
  try {
4938
5990
  const parsed = JSON.parse(raw);
4939
- if (parsed?.version !== 1 || !Array.isArray(parsed.items)) return null;
5991
+ if (parsed?.version !== 1 || !Array.isArray(parsed.items)) {
5992
+ events?.emit("storage.read", {
5993
+ sessionId: "~boot~",
5994
+ store: "plan",
5995
+ filePath,
5996
+ operation: "load",
5997
+ outcome: "failure",
5998
+ durationMs: Date.now() - t0,
5999
+ error: "invalid_schema"
6000
+ });
6001
+ return null;
6002
+ }
6003
+ events?.emit("storage.read", {
6004
+ sessionId: "~boot~",
6005
+ store: "plan",
6006
+ filePath,
6007
+ operation: "load",
6008
+ outcome: "success",
6009
+ durationMs: Date.now() - t0
6010
+ });
4940
6011
  return parsed;
4941
6012
  } catch {
6013
+ events?.emit("storage.read", {
6014
+ sessionId: "~boot~",
6015
+ store: "plan",
6016
+ filePath,
6017
+ operation: "load",
6018
+ outcome: "failure",
6019
+ durationMs: Date.now() - t0,
6020
+ error: "parse_failed"
6021
+ });
4942
6022
  return null;
4943
6023
  }
4944
6024
  }
4945
- async function savePlan(filePath, plan) {
6025
+ async function savePlan(filePath, plan, events) {
6026
+ const t0 = Date.now();
4946
6027
  try {
4947
6028
  await atomicWrite(filePath, JSON.stringify(plan, null, 2), { mode: 384 });
6029
+ events?.emit("storage.write", {
6030
+ sessionId: "~boot~",
6031
+ store: "plan",
6032
+ filePath,
6033
+ operation: "save",
6034
+ outcome: "success",
6035
+ durationMs: Date.now() - t0
6036
+ });
4948
6037
  } catch (err) {
6038
+ events?.emit("storage.error", {
6039
+ sessionId: "~boot~",
6040
+ store: "plan",
6041
+ filePath,
6042
+ operation: "save",
6043
+ error: toErrorMessage(err),
6044
+ recoverable: false
6045
+ });
4949
6046
  console.warn(
4950
6047
  "[plan-store] save failed:",
4951
- err instanceof Error ? err.message : String(err)
6048
+ toErrorMessage(err)
4952
6049
  );
4953
6050
  }
4954
6051
  }
@@ -5179,37 +6276,98 @@ function emptyTaskFile(sessionId) {
5179
6276
  tasks: []
5180
6277
  };
5181
6278
  }
5182
- async function loadTasks(filePath) {
6279
+ async function loadTasks(filePath, events, traceId) {
6280
+ const t0 = Date.now();
5183
6281
  let raw;
5184
6282
  try {
5185
6283
  raw = await fsp.readFile(filePath, "utf8");
5186
- } catch {
6284
+ } catch (err) {
6285
+ events?.emit("storage.error", {
6286
+ sessionId: traceId ?? "~boot~",
6287
+ store: "tasks",
6288
+ filePath,
6289
+ operation: "load",
6290
+ outcome: "failure",
6291
+ error: toErrorMessage(err),
6292
+ recoverable: true
6293
+ });
5187
6294
  return null;
5188
6295
  }
5189
6296
  try {
5190
6297
  const parsed = JSON.parse(raw);
5191
- if (parsed?.version !== 1 || !Array.isArray(parsed.tasks)) return null;
6298
+ if (parsed?.version !== 1 || !Array.isArray(parsed.tasks)) {
6299
+ events?.emit("storage.read", {
6300
+ sessionId: traceId ?? "~boot~",
6301
+ store: "tasks",
6302
+ filePath,
6303
+ operation: "load",
6304
+ outcome: "failure",
6305
+ durationMs: Date.now() - t0,
6306
+ error: "invalid_schema",
6307
+ ...traceId !== void 0 && { traceId }
6308
+ });
6309
+ return null;
6310
+ }
6311
+ events?.emit("storage.read", {
6312
+ sessionId: traceId ?? "~boot~",
6313
+ store: "tasks",
6314
+ filePath,
6315
+ operation: "load",
6316
+ outcome: "success",
6317
+ durationMs: Date.now() - t0,
6318
+ ...traceId !== void 0 && { traceId }
6319
+ });
5192
6320
  return parsed;
5193
6321
  } catch {
6322
+ events?.emit("storage.read", {
6323
+ sessionId: traceId ?? "~boot~",
6324
+ store: "tasks",
6325
+ filePath,
6326
+ operation: "load",
6327
+ outcome: "failure",
6328
+ durationMs: Date.now() - t0,
6329
+ error: "parse_failed",
6330
+ ...traceId !== void 0 && { traceId }
6331
+ });
5194
6332
  return null;
5195
6333
  }
5196
6334
  }
5197
- async function saveTasks(filePath, tasks) {
6335
+ async function saveTasks(filePath, tasks, events, traceId) {
6336
+ const t0 = Date.now();
5198
6337
  try {
5199
6338
  tasks.updatedAt = (/* @__PURE__ */ new Date()).toISOString();
5200
6339
  await atomicWrite(filePath, JSON.stringify(tasks, null, 2), { mode: 384 });
6340
+ events?.emit("storage.write", {
6341
+ sessionId: traceId ?? "~boot~",
6342
+ store: "tasks",
6343
+ filePath,
6344
+ operation: "save",
6345
+ outcome: "success",
6346
+ durationMs: Date.now() - t0,
6347
+ ...traceId !== void 0 && { traceId }
6348
+ });
5201
6349
  } catch (err) {
6350
+ events?.emit("storage.error", {
6351
+ sessionId: traceId ?? "~boot~",
6352
+ store: "tasks",
6353
+ filePath,
6354
+ operation: "save",
6355
+ outcome: "failure",
6356
+ error: toErrorMessage(err),
6357
+ recoverable: false,
6358
+ ...traceId !== void 0 && { traceId }
6359
+ });
5202
6360
  console.warn(
5203
6361
  "[task-store] save failed:",
5204
- err instanceof Error ? err.message : String(err)
6362
+ toErrorMessage(err)
5205
6363
  );
5206
6364
  }
5207
6365
  }
5208
- async function mutateTasks(filePath, sessionId, fn) {
6366
+ async function mutateTasks(filePath, sessionId, fn, events, traceId) {
5209
6367
  return withFileLock(filePath, async () => {
5210
- const file = await loadTasks(filePath) ?? emptyTaskFile(sessionId);
6368
+ const file = await loadTasks(filePath, events, traceId) ?? emptyTaskFile(sessionId);
5211
6369
  const updated = await fn(file);
5212
- await saveTasks(filePath, updated);
6370
+ await saveTasks(filePath, updated, events, traceId);
5213
6371
  return updated;
5214
6372
  });
5215
6373
  }
@@ -5380,7 +6538,7 @@ var DirectorStateCheckpoint = class {
5380
6538
  } catch (err) {
5381
6539
  console.warn(
5382
6540
  "[director-state] checkpoint write failed:",
5383
- err instanceof Error ? err.message : String(err)
6541
+ toErrorMessage(err)
5384
6542
  );
5385
6543
  } finally {
5386
6544
  this.writing = false;
@@ -5391,113 +6549,36 @@ var DirectorStateCheckpoint = class {
5391
6549
  }
5392
6550
  }
5393
6551
  };
5394
-
5395
- // src/utils/term.ts
5396
- var hasStdout = () => typeof process !== "undefined" && !!process.stdout;
5397
- function isStdoutTTY() {
5398
- return hasStdout() && Boolean(process.stdout.isTTY);
5399
- }
5400
-
5401
- // src/utils/color.ts
5402
- var isColorTty = () => {
5403
- if (envFlag(process.env.NO_COLOR)) return false;
5404
- if (envFlag(process.env.FORCE_COLOR)) return true;
5405
- return isStdoutTTY();
5406
- };
5407
- function envFlag(value) {
5408
- if (value === void 0) return false;
5409
- if (value.trim() === "") return false;
5410
- return !/^(0|false|no|off)$/i.test(value.trim());
5411
- }
5412
- var COLOR = isColorTty();
5413
- var wrap = (open6, close) => (s) => COLOR ? `\x1B[${open6}m${s}\x1B[${close}m` : s;
5414
- var color = {
5415
- reset: wrap("0", "0"),
5416
- bold: wrap("1", "22"),
5417
- dim: wrap("2", "22"),
5418
- italic: wrap("3", "23"),
5419
- underline: wrap("4", "24"),
5420
- red: wrap("31", "39"),
5421
- green: wrap("32", "39"),
5422
- yellow: wrap("33", "39"),
5423
- blue: wrap("34", "39"),
5424
- magenta: wrap("35", "39"),
5425
- cyan: wrap("36", "39"),
5426
- gray: wrap("90", "39"),
5427
- amber: wrap("38;5;214", "39"),
5428
- pink: wrap("38;5;205", "39"),
5429
- bgRed: wrap("41", "49"),
5430
- bgGreen: wrap("42", "49")
5431
- };
5432
- function projectHash(absRoot) {
5433
- return createHash("sha256").update(path13.resolve(absRoot)).digest("hex").slice(0, 12);
5434
- }
5435
- function projectSlug(absRoot) {
5436
- const base = slugify(path13.basename(absRoot));
5437
- const hash = createHash("sha256").update(path13.resolve(absRoot)).digest("hex").slice(0, 6);
5438
- return `${base}-${hash}`;
5439
- }
5440
- function slugify(name) {
5441
- return name.toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-+|-+$/g, "").slice(0, 40) || "project";
5442
- }
5443
- function wstackGlobalRoot() {
5444
- const fromEnv = process.env["WRONGSTACK_HOME"];
5445
- if (fromEnv && fromEnv.trim().length > 0) return path13.resolve(fromEnv);
5446
- return path13.join(os.homedir(), ".wrongstack");
5447
- }
5448
- function resolveWstackPaths(opts) {
5449
- const globalRoot = opts.globalRoot ?? (opts.userHome ? path13.join(opts.userHome, ".wrongstack") : wstackGlobalRoot());
5450
- const hash = projectHash(opts.projectRoot);
5451
- const slug = projectSlug(opts.projectRoot);
5452
- const projectDir = path13.join(globalRoot, "projects", slug);
5453
- return {
5454
- globalRoot,
5455
- configDir: globalRoot,
5456
- globalConfig: path13.join(globalRoot, "config.json"),
5457
- secretsKey: path13.join(globalRoot, ".key"),
5458
- globalMemory: path13.join(globalRoot, "memory.md"),
5459
- globalSkills: path13.join(globalRoot, "skills"),
5460
- globalPrompts: path13.join(globalRoot, "prompts"),
5461
- cacheDir: path13.join(globalRoot, "cache"),
5462
- modelsCache: path13.join(globalRoot, "cache", "models.dev.json"),
5463
- modelsOverlayCache: path13.join(globalRoot, "cache", "models-overlay.json"),
5464
- historyFile: path13.join(globalRoot, "history"),
5465
- logFile: path13.join(globalRoot, "logs", "wrongstack.log"),
5466
- projectDir,
5467
- projectCodebaseIndex: path13.join(projectDir, "codebase-index"),
5468
- projectMemory: path13.join(projectDir, "memory.md"),
5469
- projectSessions: path13.join(projectDir, "sessions"),
5470
- projectTrust: path13.join(projectDir, "trust.json"),
5471
- projectMeta: path13.join(projectDir, "meta.json"),
5472
- projectLocalConfig: path13.join(projectDir, "config.local.json"),
5473
- inProjectConfig: path13.join(opts.projectRoot, ".wrongstack", "config.json"),
5474
- inProjectAgentsFile: path13.join(opts.projectRoot, ".wrongstack", "AGENTS.md"),
5475
- inProjectSkills: path13.join(opts.projectRoot, ".wrongstack", "skills"),
5476
- inProjectWorktrees: path13.join(opts.projectRoot, ".wrongstack", "worktrees"),
5477
- projectHash: hash,
5478
- projectSlug: slug,
5479
- projectGoal: path13.join(projectDir, "goal.json"),
5480
- projectSpecs: path13.join(projectDir, "specs"),
5481
- projectTaskGraphs: path13.join(projectDir, "task-graphs"),
5482
- projectSddSession: path13.join(projectDir, "sdd-session.json"),
5483
- projectPlan: path13.join(projectDir, "plan.json"),
5484
- projectAutophase: path13.join(projectDir, "autophase"),
5485
- syncConfig: path13.join(globalRoot, "sync.json")
5486
- };
5487
- }
5488
-
5489
- // src/storage/goal-store.ts
5490
6552
  var MAX_JOURNAL_ENTRIES = 500;
5491
6553
  function goalFilePath(projectRoot) {
5492
6554
  return resolveWstackPaths({ projectRoot }).projectGoal;
5493
6555
  }
5494
- async function loadGoal(filePath) {
6556
+ async function loadGoal(filePath, events) {
6557
+ const t0 = Date.now();
5495
6558
  let raw;
5496
6559
  try {
5497
6560
  raw = await fsp.readFile(filePath, "utf8");
5498
6561
  } catch (err) {
5499
6562
  const code = err.code;
5500
- if (code === "ENOENT") return null;
6563
+ if (code === "ENOENT") {
6564
+ events?.emit("storage.read", {
6565
+ sessionId: "~boot~",
6566
+ store: "goal",
6567
+ filePath,
6568
+ operation: "load",
6569
+ outcome: "success",
6570
+ durationMs: Date.now() - t0
6571
+ });
6572
+ return null;
6573
+ }
6574
+ events?.emit("storage.error", {
6575
+ sessionId: "~boot~",
6576
+ store: "goal",
6577
+ filePath,
6578
+ operation: "load",
6579
+ error: toErrorMessage(err),
6580
+ recoverable: false
6581
+ });
5501
6582
  throw err;
5502
6583
  }
5503
6584
  try {
@@ -5510,8 +6591,25 @@ async function loadGoal(filePath) {
5510
6591
  message: "invalid schema \u2014 consider deleting and re-creating",
5511
6592
  timestamp: (/* @__PURE__ */ new Date()).toISOString()
5512
6593
  }));
6594
+ events?.emit("storage.read", {
6595
+ sessionId: "~boot~",
6596
+ store: "goal",
6597
+ filePath,
6598
+ operation: "load",
6599
+ outcome: "failure",
6600
+ durationMs: Date.now() - t0,
6601
+ error: "invalid_schema"
6602
+ });
5513
6603
  return null;
5514
6604
  }
6605
+ events?.emit("storage.read", {
6606
+ sessionId: "~boot~",
6607
+ store: "goal",
6608
+ filePath,
6609
+ operation: "load",
6610
+ outcome: "success",
6611
+ durationMs: Date.now() - t0
6612
+ });
5515
6613
  return parsed;
5516
6614
  } catch {
5517
6615
  console.warn(JSON.stringify({
@@ -5521,15 +6619,41 @@ async function loadGoal(filePath) {
5521
6619
  message: "JSON parse failed \u2014 consider deleting and re-creating",
5522
6620
  timestamp: (/* @__PURE__ */ new Date()).toISOString()
5523
6621
  }));
6622
+ events?.emit("storage.read", {
6623
+ sessionId: "~boot~",
6624
+ store: "goal",
6625
+ filePath,
6626
+ operation: "load",
6627
+ outcome: "failure",
6628
+ durationMs: Date.now() - t0,
6629
+ error: "parse_failed"
6630
+ });
5524
6631
  return null;
5525
6632
  }
5526
6633
  }
5527
- async function saveGoal(filePath, goal) {
6634
+ async function saveGoal(filePath, goal, events) {
6635
+ const t0 = Date.now();
5528
6636
  try {
5529
6637
  await atomicWrite(filePath, JSON.stringify(goal, null, 2), { mode: 384 });
6638
+ events?.emit("storage.write", {
6639
+ sessionId: "~boot~",
6640
+ store: "goal",
6641
+ filePath,
6642
+ operation: "save",
6643
+ outcome: "success",
6644
+ durationMs: Date.now() - t0
6645
+ });
5530
6646
  } catch (err) {
6647
+ events?.emit("storage.error", {
6648
+ sessionId: "~boot~",
6649
+ store: "goal",
6650
+ filePath,
6651
+ operation: "save",
6652
+ error: toErrorMessage(err),
6653
+ recoverable: false
6654
+ });
5531
6655
  throw new FsError({
5532
- message: err instanceof Error ? err.message : String(err),
6656
+ message: toErrorMessage(err),
5533
6657
  code: ERROR_CODES.FS_ATOMIC_WRITE_FAILED,
5534
6658
  path: filePath,
5535
6659
  cause: err
@@ -5691,7 +6815,7 @@ var DefaultPromptStore = class {
5691
6815
  if (!file.endsWith(".json")) continue;
5692
6816
  try {
5693
6817
  const raw = JSON.parse(
5694
- await fsp.readFile(path13.join(this.dir, file), "utf8")
6818
+ await fsp.readFile(path2.join(this.dir, file), "utf8")
5695
6819
  );
5696
6820
  entries.push(raw.entry);
5697
6821
  } catch {
@@ -5704,7 +6828,7 @@ var DefaultPromptStore = class {
5704
6828
  );
5705
6829
  }
5706
6830
  async get(id) {
5707
- const file = path13.join(this.dir, `${id}.json`);
6831
+ const file = path2.join(this.dir, `${id}.json`);
5708
6832
  try {
5709
6833
  const raw = JSON.parse(await fsp.readFile(file, "utf8"));
5710
6834
  return raw.entry;
@@ -5714,12 +6838,12 @@ var DefaultPromptStore = class {
5714
6838
  }
5715
6839
  async save(entry) {
5716
6840
  await ensureDir(this.dir);
5717
- const file = path13.join(this.dir, `${entry.id}.json`);
6841
+ const file = path2.join(this.dir, `${entry.id}.json`);
5718
6842
  const raw = { version: 1, entry };
5719
6843
  await atomicWrite(file, JSON.stringify(raw, null, 2));
5720
6844
  }
5721
6845
  async delete(id) {
5722
- const file = path13.join(this.dir, `${id}.json`);
6846
+ const file = path2.join(this.dir, `${id}.json`);
5723
6847
  try {
5724
6848
  await fsp.unlink(file);
5725
6849
  return true;
@@ -5753,7 +6877,7 @@ var CloudSync = class {
5753
6877
  this.paths = paths;
5754
6878
  this.getConfig = getConfig;
5755
6879
  this.setConfig = setConfig;
5756
- this.statePath = path13.join(paths.globalRoot, "sync-state.json");
6880
+ this.statePath = path2.join(paths.globalRoot, "sync-state.json");
5757
6881
  }
5758
6882
  paths;
5759
6883
  getConfig;
@@ -5860,7 +6984,7 @@ var CloudSync = class {
5860
6984
  const rel = segments.slice(2).join("/");
5861
6985
  const destPath = resolvePulledCategoryPath(cat, localPath, rel, entry.path);
5862
6986
  const blobData = await this.getBlob(token, owner, repoName, entry.sha);
5863
- await fsp.mkdir(path13.dirname(destPath), { recursive: true });
6987
+ await fsp.mkdir(path2.dirname(destPath), { recursive: true });
5864
6988
  await fsp.writeFile(destPath, Buffer.from(blobData, "base64"));
5865
6989
  }
5866
6990
  const localRev = await this.hashLocalCategories(cfg.categories);
@@ -5967,12 +7091,12 @@ var CloudSync = class {
5967
7091
  const localPath = this.categoryToPath(cat);
5968
7092
  if (!localPath) continue;
5969
7093
  try {
5970
- const stat5 = await fsp.stat(localPath);
5971
- if (stat5.isDirectory()) {
7094
+ const stat6 = await fsp.stat(localPath);
7095
+ if (stat6.isDirectory()) {
5972
7096
  const files = await this.walkDir(localPath, localPath);
5973
7097
  for (const file of files) {
5974
7098
  const content = await fsp.readFile(file, "utf8");
5975
- const rel = path13.relative(localPath, file).replace(/\\/g, "/");
7099
+ const rel = path2.relative(localPath, file).replace(/\\/g, "/");
5976
7100
  entries.push({ path: `data/${cat}/${rel}`, content, mode: "100644" });
5977
7101
  hashes.push(content);
5978
7102
  }
@@ -5993,8 +7117,8 @@ var CloudSync = class {
5993
7117
  const localPath = this.categoryToPath(cat);
5994
7118
  if (!localPath) continue;
5995
7119
  try {
5996
- const stat5 = await fsp.stat(localPath);
5997
- if (stat5.isDirectory()) {
7120
+ const stat6 = await fsp.stat(localPath);
7121
+ if (stat6.isDirectory()) {
5998
7122
  const files = await this.walkDir(localPath, localPath);
5999
7123
  for (const file of files) {
6000
7124
  const content = await fsp.readFile(file);
@@ -6029,7 +7153,7 @@ var CloudSync = class {
6029
7153
  const results = [];
6030
7154
  const entries = await fsp.readdir(dir, { withFileTypes: true });
6031
7155
  for (const entry of entries) {
6032
- const full = path13.join(dir, entry.name);
7156
+ const full = path2.join(dir, entry.name);
6033
7157
  if (entry.isDirectory()) {
6034
7158
  results.push(...await this.walkDir(full, base));
6035
7159
  } else {
@@ -6051,9 +7175,9 @@ function resolvePulledCategoryPath(cat, localPath, rel, remotePath) {
6051
7175
  return localPath;
6052
7176
  }
6053
7177
  if (!rel) return localPath;
6054
- const normalizedRel = path13.normalize(rel);
6055
- const traversesUp = normalizedRel === ".." || normalizedRel.startsWith(`..${path13.sep}`);
6056
- if (path13.isAbsolute(normalizedRel) || traversesUp) {
7178
+ const normalizedRel = path2.normalize(rel);
7179
+ const traversesUp = normalizedRel === ".." || normalizedRel.startsWith(`..${path2.sep}`);
7180
+ if (path2.isAbsolute(normalizedRel) || traversesUp) {
6057
7181
  throw new FsError({
6058
7182
  message: `Refusing CloudSync path traversal: ${remotePath}`,
6059
7183
  code: ERROR_CODES.FS_DELETE_FAILED,
@@ -6061,10 +7185,10 @@ function resolvePulledCategoryPath(cat, localPath, rel, remotePath) {
6061
7185
  context: { reason: "path_traversal", normalizedRel }
6062
7186
  });
6063
7187
  }
6064
- const dest = path13.resolve(localPath, normalizedRel);
6065
- const root = path13.resolve(localPath);
6066
- const relative4 = path13.relative(root, dest);
6067
- if (relative4.startsWith("..") || path13.isAbsolute(relative4)) {
7188
+ const dest = path2.resolve(localPath, normalizedRel);
7189
+ const root = path2.resolve(localPath);
7190
+ const relative4 = path2.relative(root, dest);
7191
+ if (relative4.startsWith("..") || path2.isAbsolute(relative4)) {
6068
7192
  throw new FsError({
6069
7193
  message: `Refusing CloudSync path outside category root: ${remotePath}`,
6070
7194
  code: ERROR_CODES.FS_DELETE_FAILED,
@@ -6124,7 +7248,7 @@ function isAllowed(type, level) {
6124
7248
  return true;
6125
7249
  }
6126
7250
  function createSessionEventBridge(writer, level = "standard", options = {}) {
6127
- const normalizedLevel = level ?? "standard";
7251
+ let currentLevel = level ?? "standard";
6128
7252
  const resolveWriter = typeof writer === "function" ? writer : () => writer;
6129
7253
  const progressCounters = /* @__PURE__ */ new Map();
6130
7254
  const toolProgressConfig = options.sampling?.toolProgress ?? {};
@@ -6145,14 +7269,19 @@ function createSessionEventBridge(writer, level = "standard", options = {}) {
6145
7269
  return true;
6146
7270
  }
6147
7271
  return {
6148
- level: normalizedLevel,
7272
+ get level() {
7273
+ return currentLevel;
7274
+ },
7275
+ setAuditLevel(next) {
7276
+ currentLevel = next ?? "standard";
7277
+ },
6149
7278
  allows(type) {
6150
- return isAllowed(type, normalizedLevel);
7279
+ return isAllowed(type, currentLevel);
6151
7280
  },
6152
7281
  async append(event) {
6153
7282
  const target = resolveWriter();
6154
7283
  if (!target) return;
6155
- if (!isAllowed(event.type, normalizedLevel)) return;
7284
+ if (!isAllowed(event.type, currentLevel)) return;
6156
7285
  if (!shouldSample(event)) return;
6157
7286
  try {
6158
7287
  await target.append(event);
@@ -6163,7 +7292,7 @@ function createSessionEventBridge(writer, level = "standard", options = {}) {
6163
7292
  const target = resolveWriter();
6164
7293
  if (!target || events.length === 0) return;
6165
7294
  const allowed = events.filter(
6166
- (e) => isAllowed(e.type, normalizedLevel) && shouldSample(e)
7295
+ (e) => isAllowed(e.type, currentLevel) && shouldSample(e)
6167
7296
  );
6168
7297
  if (allowed.length === 0) return;
6169
7298
  try {