@wrongstack/core 0.257.0 → 0.260.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 (65) hide show
  1. package/dist/{agent-bridge-BrxWHEOm.d.ts → agent-bridge-BbskZ7HH.d.ts} +1 -1
  2. package/dist/{agent-subagent-runner-US741uBH.d.ts → agent-subagent-runner-BNIGZx18.d.ts} +28 -8
  3. package/dist/{brain-TjEEwSpw.d.ts → brain-C2yDd7Lw.d.ts} +58 -1
  4. package/dist/{compactor-C5sT4U7I.d.ts → compactor-t0R_AIt_.d.ts} +1 -1
  5. package/dist/{config-DuAu23zm.d.ts → config-FG6As4H5.d.ts} +1 -1
  6. package/dist/{context-CGdgA0q6.d.ts → context-JFOVvu6z.d.ts} +22 -0
  7. package/dist/coordination/index.d.ts +14 -14
  8. package/dist/coordination/index.js +195 -33
  9. package/dist/coordination/index.js.map +1 -1
  10. package/dist/defaults/index.d.ts +25 -25
  11. package/dist/defaults/index.js +908 -92
  12. package/dist/defaults/index.js.map +1 -1
  13. package/dist/execution/index.d.ts +15 -15
  14. package/dist/execution/index.js +134 -35
  15. package/dist/execution/index.js.map +1 -1
  16. package/dist/execution/prompt-enhancer.d.ts +1 -1
  17. package/dist/extension/index.d.ts +6 -6
  18. package/dist/{goal-preamble-UiEkbNmW.d.ts → goal-preamble-B1IXJtLX.d.ts} +11 -9
  19. package/dist/{goal-store-CV9Yz2X_.d.ts → goal-store-CPXz6Mml.d.ts} +4 -2
  20. package/dist/{index-CitPrI3a.d.ts → index-BPcg4N3M.d.ts} +5 -5
  21. package/dist/{index-CC0Mcm05.d.ts → index-CebbJB94.d.ts} +8 -8
  22. package/dist/index.d.ts +47 -43
  23. package/dist/index.js +1571 -284
  24. package/dist/index.js.map +1 -1
  25. package/dist/infrastructure/index.d.ts +6 -6
  26. package/dist/kernel/index.d.ts +9 -9
  27. package/dist/kernel/index.js.map +1 -1
  28. package/dist/{llm-selector-CJ4SyAFE.d.ts → llm-selector-DXxI2tlu.d.ts} +2 -2
  29. package/dist/{mcp-servers-D8YnLaEp.d.ts → mcp-servers-OwNHo43-.d.ts} +3 -3
  30. package/dist/models/index.d.ts +5 -5
  31. package/dist/{models-registry-ByZCdFuQ.d.ts → models-registry-Djlmq4uB.d.ts} +1 -1
  32. package/dist/{multi-agent-coordinator-DqTUEAeC.d.ts → multi-agent-coordinator-CEmrSCMJ.d.ts} +1 -1
  33. package/dist/{null-fleet-bus-B5mfTJXT.d.ts → null-fleet-bus-DT92xqgJ.d.ts} +13 -8
  34. package/dist/observability/index.d.ts +2 -2
  35. package/dist/{package-outdated-watcher-BSgR_kK-.d.ts → package-outdated-watcher-C70ag2G9.d.ts} +3 -3
  36. package/dist/{parallel-eternal-engine-C0juOszP.d.ts → parallel-eternal-engine-0SItuq5r.d.ts} +13 -9
  37. package/dist/{path-resolver-CbkT-RMU.d.ts → path-resolver-DKBh6Jlo.d.ts} +3 -3
  38. package/dist/{permission-CwBBpCoF.d.ts → permission-BJ7eO9Vl.d.ts} +1 -1
  39. package/dist/{permission-policy-B8rSu908.d.ts → permission-policy-DEXOfnpm.d.ts} +3 -2
  40. package/dist/{pipeline-JG8XoudC.d.ts → pipeline-zflkI2dp.d.ts} +2 -2
  41. package/dist/{plan-templates-DPiQMkBz.d.ts → plan-templates-BFXyRkEK.d.ts} +32 -11
  42. package/dist/{provider-runner-hM7EXlLI.d.ts → provider-runner-BC-uywtT.d.ts} +3 -3
  43. package/dist/{retry-policy-Tg7LXkoK.d.ts → retry-policy-Cavrzmtk.d.ts} +1 -1
  44. package/dist/sdd/index.d.ts +8 -8
  45. package/dist/sdd/index.js +20 -2
  46. package/dist/sdd/index.js.map +1 -1
  47. package/dist/{secret-vault-gxtFZYBt.d.ts → secret-vault-CDvDYXWX.d.ts} +1 -1
  48. package/dist/security/index.d.ts +4 -4
  49. package/dist/security/index.js +30 -1
  50. package/dist/security/index.js.map +1 -1
  51. package/dist/{selector-DWsqVjGf.d.ts → selector-B7AivHsu.d.ts} +1 -1
  52. package/dist/{session-event-bridge-BAFWdgQ3.d.ts → session-event-bridge-BmIDxdJd.d.ts} +1 -1
  53. package/dist/{session-reader-CqRvaL5v.d.ts → session-reader-DtofsB-2.d.ts} +1 -1
  54. package/dist/storage/index.d.ts +30 -21
  55. package/dist/storage/index.js +1264 -216
  56. package/dist/storage/index.js.map +1 -1
  57. package/dist/types/index.d.ts +19 -19
  58. package/dist/types/index.js +8 -0
  59. package/dist/types/index.js.map +1 -1
  60. package/dist/utils/index.d.ts +101 -3
  61. package/dist/utils/index.js +92 -1
  62. package/dist/utils/index.js.map +1 -1
  63. package/package.json +1 -1
  64. package/skills/output-standards/SKILL.md +14 -9
  65. package/skills/output-standards/SKILL.save.md +3 -2
@@ -234,6 +234,40 @@ var DefaultSessionStore = class _DefaultSessionStore {
234
234
  this.events = opts.events;
235
235
  this.secretScrubber = opts.secretScrubber;
236
236
  }
237
+ // ── Storage event helpers ───────────────────────────────────────────────────
238
+ emitRead(sessionId, filePath, operation, outcome, durationMs, error) {
239
+ this.events?.emit("storage.read", {
240
+ sessionId,
241
+ store: "session",
242
+ filePath,
243
+ operation,
244
+ outcome,
245
+ durationMs,
246
+ ...error !== void 0 ? { error } : {}
247
+ });
248
+ }
249
+ emitWrite(sessionId, filePath, operation, outcome, durationMs, eventCount, error) {
250
+ this.events?.emit("storage.write", {
251
+ sessionId,
252
+ store: "session",
253
+ filePath,
254
+ operation,
255
+ outcome,
256
+ durationMs,
257
+ ...eventCount !== void 0 ? { eventCount } : {},
258
+ ...error !== void 0 ? { error } : {}
259
+ });
260
+ }
261
+ emitError(sessionId, filePath, operation, error, recoverable) {
262
+ this.events?.emit("storage.error", {
263
+ sessionId,
264
+ store: "session",
265
+ filePath,
266
+ operation,
267
+ error,
268
+ recoverable
269
+ });
270
+ }
237
271
  /** Absolute path to the session index file. */
238
272
  get indexFile() {
239
273
  return path13.join(this.dir, "_index.jsonl");
@@ -257,22 +291,26 @@ var DefaultSessionStore = class _DefaultSessionStore {
257
291
  const id = meta.id && meta.id.length > 0 ? meta.id : generateSessionId(startedAt, meta.model ?? meta.provider);
258
292
  const shardDir = await this.ensureShardDir(id);
259
293
  const file = path13.join(shardDir, `${path13.basename(id)}.jsonl`);
294
+ const t0 = Date.now();
260
295
  let handle;
261
296
  try {
262
297
  handle = await fsp.open(file, "a", 384);
263
298
  } catch (err) {
299
+ this.emitError(id, file, "create", err instanceof Error ? err.message : String(err), false);
264
300
  throw new Error(
265
301
  `Failed to open session file: ${err instanceof Error ? err.message : String(err)}`,
266
302
  { cause: err }
267
303
  );
268
304
  }
269
305
  try {
270
- return new FileSessionWriter(id, handle, startedAt, meta, this.events, {
306
+ const writer = new FileSessionWriter(id, handle, startedAt, meta, this.events, {
271
307
  dir: shardDir,
272
308
  filePath: file,
273
309
  secretScrubber: this.secretScrubber,
274
310
  onClose: (s) => this.appendToIndex(s)
275
311
  });
312
+ this.emitWrite(id, file, "create", "success", Date.now() - t0);
313
+ return writer;
276
314
  } catch (err) {
277
315
  await handle.close().catch((e) => console.warn(JSON.stringify({
278
316
  level: "warn",
@@ -280,16 +318,19 @@ var DefaultSessionStore = class _DefaultSessionStore {
280
318
  message: e instanceof Error ? e.message : String(e),
281
319
  timestamp: (/* @__PURE__ */ new Date()).toISOString()
282
320
  })));
321
+ this.emitError(id, file, "create", err instanceof Error ? err.message : String(err), true);
283
322
  throw err;
284
323
  }
285
324
  }
286
325
  async resume(id) {
287
326
  const file = this.sessionPath(id, ".jsonl");
327
+ const t0 = Date.now();
288
328
  const data = await this.load(id);
289
329
  let handle;
290
330
  try {
291
331
  handle = await fsp.open(file, "a", 384);
292
332
  } catch (err) {
333
+ this.emitError(id, file, "resume", err instanceof Error ? err.message : String(err), false);
293
334
  throw new Error(
294
335
  `Failed to open session "${id}" for append: ${err instanceof Error ? err.message : String(err)}`,
295
336
  { cause: err }
@@ -317,6 +358,7 @@ var DefaultSessionStore = class _DefaultSessionStore {
317
358
  onClose: (s) => this.appendToIndex(s)
318
359
  }
319
360
  );
361
+ this.emitWrite(id, file, "resume", "success", Date.now() - t0);
320
362
  return { writer, data };
321
363
  } catch (err) {
322
364
  await handle.close().catch((e) => console.warn(JSON.stringify({
@@ -325,27 +367,39 @@ var DefaultSessionStore = class _DefaultSessionStore {
325
367
  message: e instanceof Error ? e.message : String(e),
326
368
  timestamp: (/* @__PURE__ */ new Date()).toISOString()
327
369
  })));
370
+ this.emitError(id, file, "resume", err instanceof Error ? err.message : String(err), true);
328
371
  throw err;
329
372
  }
330
373
  }
331
374
  async load(id) {
332
375
  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);
376
+ const t0 = Date.now();
377
+ let outcome = "success";
378
+ let errorMsg;
379
+ try {
380
+ const raw = await fsp.readFile(file, "utf8");
381
+ const lines = raw.split("\n").filter((l) => l.trim());
382
+ const events = [];
383
+ for (const line of lines) {
384
+ try {
385
+ const parsed = JSON.parse(line);
386
+ if (parsed !== null && typeof parsed === "object" && typeof parsed.type === "string" && typeof parsed.ts === "string") {
387
+ events.push(parsed);
388
+ }
389
+ } catch {
341
390
  }
342
- } catch {
343
391
  }
392
+ const meta = this.metaFromEvents(id, events);
393
+ const { messages, usage } = this.replay(events, id);
394
+ const toolCallEnds = extractToolCallEnds(events);
395
+ return { metadata: meta, events, messages, usage, toolCallEnds };
396
+ } catch (err) {
397
+ outcome = "failure";
398
+ errorMsg = err instanceof Error ? err.message : String(err);
399
+ throw err;
400
+ } finally {
401
+ this.emitRead(id, file, "load", outcome, Date.now() - t0, errorMsg);
344
402
  }
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
403
  }
350
404
  async list(limit = 20) {
351
405
  try {
@@ -412,12 +466,22 @@ var DefaultSessionStore = class _DefaultSessionStore {
412
466
  * (keep latest per session), and rewrite. Atomic via temp+rename.
413
467
  */
414
468
  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);
469
+ const t0 = Date.now();
470
+ let outcome = "success";
471
+ let errorMsg;
472
+ try {
473
+ const entries = await this.readIndex();
474
+ if (entries.length === 0) return;
475
+ const tmp = `${this.indexFile}.compact.tmp`;
476
+ const lines = entries.map((s) => JSON.stringify(s)).join("\n") + "\n";
477
+ await fsp.writeFile(tmp, lines, "utf8");
478
+ await fsp.rename(tmp, this.indexFile);
479
+ } catch (err) {
480
+ outcome = "failure";
481
+ errorMsg = err instanceof Error ? err.message : String(err);
482
+ } finally {
483
+ this.emitWrite("~compact~", this.indexFile, "compact", outcome, Date.now() - t0, void 0, errorMsg);
484
+ }
421
485
  }
422
486
  /**
423
487
  * Read the index file and return deduplicated session summaries.
@@ -493,22 +557,31 @@ var DefaultSessionStore = class _DefaultSessionStore {
493
557
  }
494
558
  async summaryFor(id) {
495
559
  const manifest = this.sessionPath(id, ".summary.json");
560
+ const t0 = Date.now();
561
+ let outcome = "success";
562
+ let errorMsg;
496
563
  try {
497
564
  const raw = await fsp.readFile(manifest, "utf8");
565
+ this.emitRead(id, manifest, "summary", "success", Date.now() - t0);
498
566
  return JSON.parse(raw);
499
567
  } catch {
500
568
  const full = this.sessionPath(id, ".jsonl");
501
569
  const stat5 = await fsp.stat(full);
502
570
  const summary = await this.summarize(id, stat5.mtime.toISOString());
503
571
  await atomicWrite(manifest, JSON.stringify(summary), { mode: 384 }).catch((err) => {
572
+ const msg = err instanceof Error ? err.message : String(err);
573
+ this.emitError(id, manifest, "summary_fallback", msg, true);
504
574
  console.warn(JSON.stringify({
505
575
  level: "warn",
506
576
  event: "session_store.manifest_write_failed",
507
577
  sessionId: id,
508
- message: err instanceof Error ? err.message : String(err),
578
+ message: msg,
509
579
  timestamp: (/* @__PURE__ */ new Date()).toISOString()
510
580
  }));
511
581
  });
582
+ outcome = "failure";
583
+ errorMsg = "summary fallback \u2014 manifest rebuilt";
584
+ this.emitRead(id, manifest, "summary", outcome, Date.now() - t0, errorMsg);
512
585
  return summary;
513
586
  }
514
587
  }
@@ -774,7 +847,7 @@ function extractToolCallEnds(events) {
774
847
  return result;
775
848
  }
776
849
  var FileSessionWriter = class _FileSessionWriter {
777
- constructor(id, handle, startedAt, meta, events, opts = {}) {
850
+ constructor(id, handle, startedAt, meta, events, opts = {}, traceId) {
778
851
  this.id = id;
779
852
  this.handle = handle;
780
853
  this.startedAt = startedAt;
@@ -793,6 +866,7 @@ var FileSessionWriter = class _FileSessionWriter {
793
866
  provider: meta.provider ?? "unknown",
794
867
  tokenTotal: 0
795
868
  };
869
+ this.traceId = traceId;
796
870
  }
797
871
  id;
798
872
  handle;
@@ -825,6 +899,8 @@ var FileSessionWriter = class _FileSessionWriter {
825
899
  lastAppendWarnAt = 0;
826
900
  secretScrubber;
827
901
  onCloseCb;
902
+ /** Implements SessionWriter.traceId — propagated from ContextInit.traceId. */
903
+ traceId;
828
904
  // ── Write buffer — batches events to reduce per-event disk I/O ─────────
829
905
  //
830
906
  // Every append() pushes the scrubbed event into an in-memory buffer instead
@@ -978,9 +1054,14 @@ var FileSessionWriter = class _FileSessionWriter {
978
1054
  const eventCount = this.writeBuffer.length;
979
1055
  const batch = this.writeBuffer.map((e) => JSON.stringify(e)).join("\n") + "\n";
980
1056
  this.writeBuffer = [];
1057
+ const t0 = Date.now();
1058
+ let outcome = "success";
1059
+ let errorMsg;
981
1060
  try {
982
1061
  await this.enqueueWrite(batch);
983
1062
  } catch (err) {
1063
+ outcome = "failure";
1064
+ errorMsg = err instanceof Error ? err.message : String(err);
984
1065
  this.appendFailCount += eventCount;
985
1066
  const now = Date.now();
986
1067
  if (now - this.lastAppendWarnAt > 5e3) {
@@ -994,6 +1075,18 @@ var FileSessionWriter = class _FileSessionWriter {
994
1075
  this.lastAppendWarnAt = now;
995
1076
  this.appendFailCount = 0;
996
1077
  }
1078
+ } finally {
1079
+ this.events?.emit("storage.write", {
1080
+ sessionId: this.id,
1081
+ store: "session",
1082
+ filePath: this.filePath,
1083
+ operation: "flush",
1084
+ outcome,
1085
+ durationMs: Date.now() - t0,
1086
+ ...errorMsg !== void 0 ? { error: errorMsg } : {},
1087
+ ...eventCount !== void 0 ? { eventCount } : {},
1088
+ ...this.traceId !== void 0 ? { traceId: this.traceId } : {}
1089
+ });
997
1090
  }
998
1091
  }
999
1092
  observeForSummary(event) {
@@ -1059,14 +1152,46 @@ var FileSessionWriter = class _FileSessionWriter {
1059
1152
  outcome: this.outcome ?? "completed"
1060
1153
  };
1061
1154
  if (this.manifestFile) {
1155
+ const t0 = Date.now();
1156
+ let outcome = "success";
1157
+ let errorMsg;
1062
1158
  try {
1063
1159
  await atomicWrite(this.manifestFile, JSON.stringify(this.summary), { mode: 384 });
1064
- } catch {
1160
+ } catch (err) {
1161
+ outcome = "failure";
1162
+ errorMsg = err instanceof Error ? err.message : String(err);
1163
+ } finally {
1164
+ this.events?.emit("storage.write", {
1165
+ sessionId: this.id,
1166
+ store: "session",
1167
+ filePath: this.manifestFile,
1168
+ operation: "close",
1169
+ outcome,
1170
+ durationMs: Date.now() - t0,
1171
+ ...errorMsg !== void 0 ? { error: errorMsg } : {},
1172
+ ...this.traceId !== void 0 ? { traceId: this.traceId } : {}
1173
+ });
1065
1174
  }
1066
1175
  }
1176
+ const idxT0 = Date.now();
1177
+ let idxOutcome = "success";
1178
+ let idxError;
1067
1179
  try {
1068
1180
  await this.onCloseCb?.(this.summary);
1069
- } catch {
1181
+ } catch (err) {
1182
+ idxOutcome = "failure";
1183
+ idxError = err instanceof Error ? err.message : String(err);
1184
+ } finally {
1185
+ this.events?.emit("storage.write", {
1186
+ sessionId: this.summary.id,
1187
+ store: "session",
1188
+ filePath: this.filePath,
1189
+ operation: "index_append",
1190
+ outcome: idxOutcome,
1191
+ durationMs: Date.now() - idxT0,
1192
+ ...idxError !== void 0 ? { error: idxError } : {},
1193
+ ...this.traceId !== void 0 ? { traceId: this.traceId } : {}
1194
+ });
1070
1195
  }
1071
1196
  try {
1072
1197
  await this.handle.close();
@@ -1225,23 +1350,81 @@ function userInputTitle(content) {
1225
1350
  }
1226
1351
  var QueueStore = class {
1227
1352
  file;
1353
+ // Use `| undefined` (not `?`) so exactOptionalPropertyTypes doesn't
1354
+ // reject assigning an optional constructor parameter to these fields.
1355
+ events;
1356
+ traceId;
1228
1357
  constructor(opts) {
1229
1358
  this.file = path13.join(opts.dir, "queue.json");
1359
+ this.events = opts.events;
1360
+ this.traceId = opts.traceId;
1230
1361
  }
1231
1362
  async write(items) {
1363
+ const t0 = Date.now();
1232
1364
  if (items.length === 0) {
1233
1365
  await this.clear();
1234
1366
  return;
1235
1367
  }
1236
- await atomicWrite(this.file, JSON.stringify(items), { mode: 384 });
1368
+ try {
1369
+ await atomicWrite(this.file, JSON.stringify(items), { mode: 384 });
1370
+ this.events?.emit("storage.write", {
1371
+ sessionId: this.traceId ?? "~boot~",
1372
+ store: "queue",
1373
+ filePath: this.file,
1374
+ operation: "write",
1375
+ outcome: "success",
1376
+ durationMs: Date.now() - t0,
1377
+ ...this.traceId !== void 0 && { traceId: this.traceId }
1378
+ });
1379
+ } catch (err) {
1380
+ this.events?.emit("storage.error", {
1381
+ sessionId: this.traceId ?? "~boot~",
1382
+ store: "queue",
1383
+ filePath: this.file,
1384
+ operation: "write",
1385
+ outcome: "failure",
1386
+ error: err instanceof Error ? err.message : String(err),
1387
+ recoverable: false,
1388
+ ...this.traceId !== void 0 && { traceId: this.traceId }
1389
+ });
1390
+ console.warn(JSON.stringify({
1391
+ level: "warn",
1392
+ event: "queue_store.write_failed",
1393
+ path: this.file,
1394
+ message: err instanceof Error ? err.message : String(err),
1395
+ timestamp: (/* @__PURE__ */ new Date()).toISOString()
1396
+ }));
1397
+ }
1237
1398
  }
1238
1399
  async read() {
1400
+ const t0 = Date.now();
1239
1401
  let raw;
1240
1402
  try {
1241
1403
  raw = await fsp.readFile(this.file, "utf8");
1242
1404
  } catch (err) {
1243
1405
  const code = err.code;
1244
- if (code === "ENOENT") return [];
1406
+ if (code === "ENOENT") {
1407
+ this.events?.emit("storage.read", {
1408
+ sessionId: this.traceId ?? "~boot~",
1409
+ store: "queue",
1410
+ filePath: this.file,
1411
+ operation: "read",
1412
+ outcome: "success",
1413
+ durationMs: Date.now() - t0,
1414
+ ...this.traceId !== void 0 && { traceId: this.traceId }
1415
+ });
1416
+ return [];
1417
+ }
1418
+ this.events?.emit("storage.error", {
1419
+ sessionId: this.traceId ?? "~boot~",
1420
+ store: "queue",
1421
+ filePath: this.file,
1422
+ operation: "read",
1423
+ outcome: "failure",
1424
+ error: err instanceof Error ? err.message : String(err),
1425
+ recoverable: true,
1426
+ ...this.traceId !== void 0 && { traceId: this.traceId }
1427
+ });
1245
1428
  console.warn(JSON.stringify({
1246
1429
  level: "warn",
1247
1430
  event: "queue_store.read_failed",
@@ -1255,9 +1438,40 @@ var QueueStore = class {
1255
1438
  try {
1256
1439
  parsed = JSON.parse(raw);
1257
1440
  } catch {
1441
+ this.events?.emit("storage.read", {
1442
+ sessionId: this.traceId ?? "~boot~",
1443
+ store: "queue",
1444
+ filePath: this.file,
1445
+ operation: "read",
1446
+ outcome: "failure",
1447
+ durationMs: Date.now() - t0,
1448
+ error: "parse_failed",
1449
+ ...this.traceId !== void 0 && { traceId: this.traceId }
1450
+ });
1258
1451
  return [];
1259
1452
  }
1260
- if (!Array.isArray(parsed)) return [];
1453
+ if (!Array.isArray(parsed)) {
1454
+ this.events?.emit("storage.read", {
1455
+ sessionId: this.traceId ?? "~boot~",
1456
+ store: "queue",
1457
+ filePath: this.file,
1458
+ operation: "read",
1459
+ outcome: "failure",
1460
+ durationMs: Date.now() - t0,
1461
+ error: "invalid_schema",
1462
+ ...this.traceId !== void 0 && { traceId: this.traceId }
1463
+ });
1464
+ return [];
1465
+ }
1466
+ this.events?.emit("storage.read", {
1467
+ sessionId: this.traceId ?? "~boot~",
1468
+ store: "queue",
1469
+ filePath: this.file,
1470
+ operation: "read",
1471
+ outcome: "success",
1472
+ durationMs: Date.now() - t0,
1473
+ ...this.traceId !== void 0 && { traceId: this.traceId }
1474
+ });
1261
1475
  const out = [];
1262
1476
  for (const v of parsed) {
1263
1477
  if (isPersistedQueueItem(v)) out.push(v);
@@ -1265,11 +1479,31 @@ var QueueStore = class {
1265
1479
  return out;
1266
1480
  }
1267
1481
  async clear() {
1482
+ const t0 = Date.now();
1268
1483
  try {
1269
1484
  await fsp.unlink(this.file);
1485
+ this.events?.emit("storage.write", {
1486
+ sessionId: this.traceId ?? "~boot~",
1487
+ store: "queue",
1488
+ filePath: this.file,
1489
+ operation: "clear",
1490
+ outcome: "success",
1491
+ durationMs: Date.now() - t0,
1492
+ ...this.traceId !== void 0 && { traceId: this.traceId }
1493
+ });
1270
1494
  } catch (err) {
1271
1495
  const code = err.code;
1272
1496
  if (code === "ENOENT") return;
1497
+ this.events?.emit("storage.error", {
1498
+ sessionId: this.traceId ?? "~boot~",
1499
+ store: "queue",
1500
+ filePath: this.file,
1501
+ operation: "clear",
1502
+ outcome: "failure",
1503
+ error: err instanceof Error ? err.message : String(err),
1504
+ recoverable: true,
1505
+ ...this.traceId !== void 0 && { traceId: this.traceId }
1506
+ });
1273
1507
  console.warn(JSON.stringify({
1274
1508
  level: "warn",
1275
1509
  event: "queue_store.clear_failed",
@@ -1652,6 +1886,7 @@ var MAX_BYTES_TOTAL = 32e3;
1652
1886
  var DefaultMemoryStore = class {
1653
1887
  files;
1654
1888
  events;
1889
+ traceId;
1655
1890
  backend;
1656
1891
  /**
1657
1892
  * Per-scope serialization queue. `remember` / `forget` / `consolidate` /
@@ -1717,15 +1952,70 @@ var DefaultMemoryStore = class {
1717
1952
  if (writeErr) {
1718
1953
  parts.push(`> \u26A0\uFE0F Memory write error (${labelOf(scope)}): ${writeErr.message}`);
1719
1954
  }
1720
- const body = await this.backend.readAll(scope, this.files[scope]);
1721
- if (body.trim()) parts.push(`## ${labelOf(scope)}
1955
+ const t0 = Date.now();
1956
+ const filePath = this.files[scope];
1957
+ try {
1958
+ const body = await this.backend.readAll(scope, filePath);
1959
+ const dur = Date.now() - t0;
1960
+ this.events?.emit("storage.read", {
1961
+ sessionId: "~memory~",
1962
+ store: "memory",
1963
+ filePath,
1964
+ operation: "readAll",
1965
+ outcome: "success",
1966
+ durationMs: dur,
1967
+ ...this.traceId !== void 0 && { traceId: this.traceId }
1968
+ });
1969
+ if (body.trim()) parts.push(`## ${labelOf(scope)}
1722
1970
 
1723
1971
  ${body.trim()}`);
1972
+ } catch (err) {
1973
+ const dur = Date.now() - t0;
1974
+ this.events?.emit("storage.read", {
1975
+ sessionId: "~memory~",
1976
+ store: "memory",
1977
+ filePath,
1978
+ operation: "readAll",
1979
+ outcome: "failure",
1980
+ durationMs: dur,
1981
+ error: err instanceof Error ? err.message : String(err),
1982
+ ...this.traceId !== void 0 && { traceId: this.traceId }
1983
+ });
1984
+ throw err;
1985
+ }
1724
1986
  }
1725
1987
  return parts.join("\n\n");
1726
1988
  }
1727
1989
  async read(scope) {
1728
- return this.backend.readAll(scope, this.files[scope]);
1990
+ const t0 = Date.now();
1991
+ const filePath = this.files[scope];
1992
+ try {
1993
+ const body = await this.backend.readAll(scope, filePath);
1994
+ const dur = Date.now() - t0;
1995
+ this.events?.emit("storage.read", {
1996
+ sessionId: "~memory~",
1997
+ store: "memory",
1998
+ filePath,
1999
+ operation: "read",
2000
+ outcome: "success",
2001
+ durationMs: dur,
2002
+ ...this.traceId !== void 0 && { traceId: this.traceId }
2003
+ });
2004
+ return body;
2005
+ } catch (err) {
2006
+ const dur = Date.now() - t0;
2007
+ this.events?.emit("storage.read", {
2008
+ sessionId: "~memory~",
2009
+ store: "memory",
2010
+ filePath,
2011
+ operation: "read",
2012
+ outcome: "failure",
2013
+ durationMs: dur,
2014
+ error: err instanceof Error ? err.message : String(err),
2015
+ ...this.traceId !== void 0 && { traceId: this.traceId }
2016
+ });
2017
+ throw err;
2018
+ }
1729
2019
  }
1730
2020
  /**
1731
2021
  * List entries from a scope, newest first. Delegates to the backend
@@ -1751,7 +2041,34 @@ ${body.trim()}`);
1751
2041
  const ts = (/* @__PURE__ */ new Date()).toISOString();
1752
2042
  return this.runSerialized(scope, async () => {
1753
2043
  const entry = { scope, text, ts, ...metadata };
1754
- await this.backend.remember(scope, entry, this.files[scope]);
2044
+ const filePath = this.files[scope];
2045
+ const t0 = Date.now();
2046
+ try {
2047
+ await this.backend.remember(scope, entry, filePath);
2048
+ const dur = Date.now() - t0;
2049
+ this.events?.emit("storage.write", {
2050
+ sessionId: "~memory~",
2051
+ store: "memory",
2052
+ filePath,
2053
+ operation: "remember",
2054
+ outcome: "success",
2055
+ durationMs: dur,
2056
+ ...this.traceId !== void 0 && { traceId: this.traceId }
2057
+ });
2058
+ } catch (err) {
2059
+ const dur = Date.now() - t0;
2060
+ this.events?.emit("storage.write", {
2061
+ sessionId: "~memory~",
2062
+ store: "memory",
2063
+ filePath,
2064
+ operation: "remember",
2065
+ outcome: "failure",
2066
+ durationMs: dur,
2067
+ error: err instanceof Error ? err.message : String(err),
2068
+ ...this.traceId !== void 0 && { traceId: this.traceId }
2069
+ });
2070
+ throw err;
2071
+ }
1755
2072
  const raw = await this.backend.readAll(scope, this.files[scope]);
1756
2073
  if (Buffer.byteLength(raw, "utf8") > MAX_BYTES_TOTAL) {
1757
2074
  const removed = await this.backend.consolidate(scope, this.files[scope]);
@@ -1879,7 +2196,35 @@ ${body.trim()}`);
1879
2196
  }
1880
2197
  async forget(query, scope = "project-memory") {
1881
2198
  return this.runSerialized(scope, async () => {
1882
- const removed = await this.backend.forget(scope, query, this.files[scope]);
2199
+ const filePath = this.files[scope];
2200
+ const t0 = Date.now();
2201
+ let removed = 0;
2202
+ try {
2203
+ removed = await this.backend.forget(scope, query, filePath);
2204
+ const dur = Date.now() - t0;
2205
+ this.events?.emit("storage.write", {
2206
+ sessionId: "~memory~",
2207
+ store: "memory",
2208
+ filePath,
2209
+ operation: "forget",
2210
+ outcome: "success",
2211
+ durationMs: dur,
2212
+ ...this.traceId !== void 0 && { traceId: this.traceId }
2213
+ });
2214
+ } catch (err) {
2215
+ const dur = Date.now() - t0;
2216
+ this.events?.emit("storage.write", {
2217
+ sessionId: "~memory~",
2218
+ store: "memory",
2219
+ filePath,
2220
+ operation: "forget",
2221
+ outcome: "failure",
2222
+ durationMs: dur,
2223
+ error: err instanceof Error ? err.message : String(err),
2224
+ ...this.traceId !== void 0 && { traceId: this.traceId }
2225
+ });
2226
+ throw err;
2227
+ }
1883
2228
  if (removed > 0) {
1884
2229
  this.events?.emit("memory.forgotten", {
1885
2230
  scope,
@@ -1893,7 +2238,35 @@ ${body.trim()}`);
1893
2238
  }
1894
2239
  async consolidate(scope) {
1895
2240
  return this.runSerialized(scope, async () => {
1896
- const removed = await this.backend.consolidate(scope, this.files[scope]);
2241
+ const filePath = this.files[scope];
2242
+ const t0 = Date.now();
2243
+ let removed = 0;
2244
+ try {
2245
+ removed = await this.backend.consolidate(scope, filePath);
2246
+ const dur = Date.now() - t0;
2247
+ this.events?.emit("storage.write", {
2248
+ sessionId: "~memory~",
2249
+ store: "memory",
2250
+ filePath,
2251
+ operation: "consolidate",
2252
+ outcome: "success",
2253
+ durationMs: dur,
2254
+ ...this.traceId !== void 0 && { traceId: this.traceId }
2255
+ });
2256
+ } catch (err) {
2257
+ const dur = Date.now() - t0;
2258
+ this.events?.emit("storage.write", {
2259
+ sessionId: "~memory~",
2260
+ store: "memory",
2261
+ filePath,
2262
+ operation: "consolidate",
2263
+ outcome: "failure",
2264
+ durationMs: dur,
2265
+ error: err instanceof Error ? err.message : String(err),
2266
+ ...this.traceId !== void 0 && { traceId: this.traceId }
2267
+ });
2268
+ throw err;
2269
+ }
1897
2270
  if (removed > 0) {
1898
2271
  this.events?.emit("memory.consolidated", {
1899
2272
  scope,
@@ -1906,7 +2279,34 @@ ${body.trim()}`);
1906
2279
  async clear(scope) {
1907
2280
  if (scope) {
1908
2281
  await this.runSerialized(scope, async () => {
1909
- await this.backend.clear(scope, this.files[scope]);
2282
+ const filePath = this.files[scope];
2283
+ const t0 = Date.now();
2284
+ try {
2285
+ await this.backend.clear(scope, filePath);
2286
+ const dur = Date.now() - t0;
2287
+ this.events?.emit("storage.write", {
2288
+ sessionId: "~memory~",
2289
+ store: "memory",
2290
+ filePath,
2291
+ operation: "clear",
2292
+ outcome: "success",
2293
+ durationMs: dur,
2294
+ ...this.traceId !== void 0 && { traceId: this.traceId }
2295
+ });
2296
+ } catch (err) {
2297
+ const dur = Date.now() - t0;
2298
+ this.events?.emit("storage.write", {
2299
+ sessionId: "~memory~",
2300
+ store: "memory",
2301
+ filePath,
2302
+ operation: "clear",
2303
+ outcome: "failure",
2304
+ durationMs: dur,
2305
+ error: err instanceof Error ? err.message : String(err),
2306
+ ...this.traceId !== void 0 && { traceId: this.traceId }
2307
+ });
2308
+ throw err;
2309
+ }
1910
2310
  this.events?.emit("memory.cleared", { scope });
1911
2311
  await this.mirrorBackup(scope);
1912
2312
  });
@@ -1914,15 +2314,54 @@ ${body.trim()}`);
1914
2314
  }
1915
2315
  await Promise.all(
1916
2316
  ["project-agents", "project-memory", "user-memory"].map(
1917
- (s) => this.runSerialized(s, async () => {
1918
- await this.backend.clear(s, this.files[s]);
2317
+ async (s) => this.runSerialized(s, async () => {
2318
+ const filePath = this.files[s];
2319
+ const t0 = Date.now();
2320
+ try {
2321
+ await this.backend.clear(s, filePath);
2322
+ const dur = Date.now() - t0;
2323
+ this.events?.emit("storage.write", {
2324
+ sessionId: "~memory~",
2325
+ store: "memory",
2326
+ filePath,
2327
+ operation: "clear",
2328
+ outcome: "success",
2329
+ durationMs: dur,
2330
+ ...this.traceId !== void 0 && { traceId: this.traceId }
2331
+ });
2332
+ } catch (err) {
2333
+ const dur = Date.now() - t0;
2334
+ this.events?.emit("storage.write", {
2335
+ sessionId: "~memory~",
2336
+ store: "memory",
2337
+ filePath,
2338
+ operation: "clear",
2339
+ outcome: "failure",
2340
+ durationMs: dur,
2341
+ error: err instanceof Error ? err.message : String(err),
2342
+ ...this.traceId !== void 0 && { traceId: this.traceId }
2343
+ });
2344
+ throw err;
2345
+ }
1919
2346
  this.events?.emit("memory.cleared", { scope: s });
1920
2347
  await this.mirrorBackup(s);
1921
2348
  })
1922
2349
  )
1923
2350
  );
1924
2351
  }
1925
- /** Mirror current memory content to the persistent backup directory. */
2352
+ /**
2353
+ * Return a new MemoryStore proxy that carries `traceId` on every storage
2354
+ * event. The original store is left unchanged — callers that need a
2355
+ * trace-decorated view (e.g. session-run tools) receive the proxy while
2356
+ * the singleton remains trace-free for boot-time use.
2357
+ *
2358
+ * The proxy implements the full `MemoryStore` interface; all other
2359
+ * properties (backend, etc.) are delegated to the original store.
2360
+ */
2361
+ withTraceId(traceId) {
2362
+ this.traceId = traceId;
2363
+ return this;
2364
+ }
1926
2365
  async mirrorBackup(scope) {
1927
2366
  if (!this.persistBackup || scope === "project-agents") return;
1928
2367
  try {
@@ -2673,6 +3112,13 @@ var DEFAULT_SESSION_LOGGING_CONFIG = Object.freeze({
2673
3112
  });
2674
3113
 
2675
3114
  // src/storage/config-loader.ts
3115
+ function storageErrorString(err) {
3116
+ if (err instanceof Error) {
3117
+ const code = err.code;
3118
+ return code ? `${code}: ${err.message}` : err.message;
3119
+ }
3120
+ return String(err);
3121
+ }
2676
3122
  var BEHAVIOR_DEFAULTS = {
2677
3123
  version: 1,
2678
3124
  context: {
@@ -2775,11 +3221,15 @@ var DefaultConfigLoader = class {
2775
3221
  strict;
2776
3222
  vault;
2777
3223
  extraSources;
3224
+ events;
3225
+ traceId;
2778
3226
  constructor(opts) {
2779
3227
  this.paths = opts.paths;
2780
3228
  this.strict = opts.strict ?? false;
2781
3229
  this.vault = opts.vault;
2782
3230
  this.extraSources = opts.sources ?? [];
3231
+ this.events = opts.events;
3232
+ this.traceId = opts.traceId;
2783
3233
  }
2784
3234
  async load(opts = {}) {
2785
3235
  let cfg = { ...BEHAVIOR_DEFAULTS };
@@ -2856,7 +3306,33 @@ var DefaultConfigLoader = class {
2856
3306
  if (this.vault && toWrite.githubToken && !toWrite.githubToken.startsWith("enc:")) {
2857
3307
  toWrite = { ...toWrite, githubToken: this.vault.encrypt(toWrite.githubToken) };
2858
3308
  }
2859
- await atomicWrite(this.paths.syncConfig, JSON.stringify(toWrite, null, 2), { mode: 384 });
3309
+ const fp = this.paths.syncConfig;
3310
+ const t0 = Date.now();
3311
+ try {
3312
+ await atomicWrite(fp, JSON.stringify(toWrite, null, 2), { mode: 384 });
3313
+ this.events?.emit("storage.write", {
3314
+ sessionId: "~config~",
3315
+ store: "config",
3316
+ filePath: fp,
3317
+ operation: "persist_sync",
3318
+ outcome: "success",
3319
+ durationMs: Date.now() - t0,
3320
+ ...this.traceId !== void 0 ? { traceId: this.traceId } : {}
3321
+ });
3322
+ } catch (err) {
3323
+ this.events?.emit("storage.error", {
3324
+ sessionId: "~config~",
3325
+ store: "config",
3326
+ filePath: fp,
3327
+ operation: "persist_sync",
3328
+ outcome: "failure",
3329
+ error: storageErrorString(err),
3330
+ recoverable: false,
3331
+ durationMs: Date.now() - t0,
3332
+ ...this.traceId !== void 0 ? { traceId: this.traceId } : {}
3333
+ });
3334
+ throw err;
3335
+ }
2860
3336
  }
2861
3337
  /**
2862
3338
  * Read ~/.wrongstack/sync.json (encrypted GitHub token storage) and decrypt
@@ -2865,17 +3341,60 @@ var DefaultConfigLoader = class {
2865
3341
  * isolated — it should never be part of project-local or env-driven config.
2866
3342
  */
2867
3343
  async loadSyncConfig() {
3344
+ const fp = this.paths.syncConfig;
3345
+ const t0 = Date.now();
2868
3346
  try {
2869
- const raw = await fsp.readFile(this.paths.syncConfig, "utf8");
3347
+ const raw = await fsp.readFile(fp, "utf8");
2870
3348
  const parsed = safeParse(raw);
2871
- if (!parsed.ok || !parsed.value) return null;
3349
+ if (!parsed.ok || !parsed.value) {
3350
+ this.events?.emit("storage.read", {
3351
+ sessionId: "~config~",
3352
+ store: "config",
3353
+ filePath: fp,
3354
+ operation: "load_sync",
3355
+ outcome: "failure",
3356
+ durationMs: Date.now() - t0,
3357
+ error: "parse error or empty file",
3358
+ ...this.traceId !== void 0 ? { traceId: this.traceId } : {}
3359
+ });
3360
+ return null;
3361
+ }
2872
3362
  if (this.vault) {
2873
3363
  const decrypted = decryptConfigSecrets({ sync: parsed.value }, this.vault);
2874
- return decrypted.sync ?? null;
3364
+ const result = decrypted.sync ?? null;
3365
+ this.events?.emit("storage.read", {
3366
+ sessionId: "~config~",
3367
+ store: "config",
3368
+ filePath: fp,
3369
+ operation: "load_sync",
3370
+ outcome: "success",
3371
+ durationMs: Date.now() - t0,
3372
+ ...this.traceId !== void 0 ? { traceId: this.traceId } : {}
3373
+ });
3374
+ return result;
2875
3375
  }
3376
+ this.events?.emit("storage.read", {
3377
+ sessionId: "~config~",
3378
+ store: "config",
3379
+ filePath: fp,
3380
+ operation: "load_sync",
3381
+ outcome: "success",
3382
+ durationMs: Date.now() - t0,
3383
+ ...this.traceId !== void 0 ? { traceId: this.traceId } : {}
3384
+ });
2876
3385
  return parsed.value;
2877
3386
  } catch (err) {
2878
3387
  if (err.code === "ENOENT") return null;
3388
+ this.events?.emit("storage.read", {
3389
+ sessionId: "~config~",
3390
+ store: "config",
3391
+ filePath: fp,
3392
+ operation: "load_sync",
3393
+ outcome: "failure",
3394
+ durationMs: Date.now() - t0,
3395
+ error: storageErrorString(err),
3396
+ ...this.traceId !== void 0 ? { traceId: this.traceId } : {}
3397
+ });
2879
3398
  console.warn(JSON.stringify({
2880
3399
  level: "warn",
2881
3400
  event: "config.sync_load_failed",
@@ -2887,10 +3406,21 @@ var DefaultConfigLoader = class {
2887
3406
  }
2888
3407
  async readJson(file) {
2889
3408
  let raw;
3409
+ const t0 = Date.now();
2890
3410
  try {
2891
3411
  raw = await fsp.readFile(file, "utf8");
2892
3412
  } catch (err) {
2893
3413
  if (err.code !== "ENOENT") {
3414
+ this.events?.emit("storage.read", {
3415
+ sessionId: "~config~",
3416
+ store: "config",
3417
+ filePath: file,
3418
+ operation: "read_json",
3419
+ outcome: "failure",
3420
+ durationMs: Date.now() - t0,
3421
+ error: storageErrorString(err),
3422
+ ...this.traceId !== void 0 ? { traceId: this.traceId } : {}
3423
+ });
2894
3424
  console.warn(JSON.stringify({
2895
3425
  level: "warn",
2896
3426
  event: "config.read_failed",
@@ -2903,6 +3433,16 @@ var DefaultConfigLoader = class {
2903
3433
  }
2904
3434
  const parsed = safeParse(raw);
2905
3435
  if (!parsed.ok || !parsed.value) {
3436
+ this.events?.emit("storage.read", {
3437
+ sessionId: "~config~",
3438
+ store: "config",
3439
+ filePath: file,
3440
+ operation: "read_json",
3441
+ outcome: "failure",
3442
+ durationMs: Date.now() - t0,
3443
+ error: "parse error or empty file",
3444
+ ...this.traceId !== void 0 ? { traceId: this.traceId } : {}
3445
+ });
2906
3446
  console.warn(JSON.stringify({
2907
3447
  level: "warn",
2908
3448
  event: "config.parse_failed",
@@ -3501,20 +4041,53 @@ var MAX_TEXT_LENGTH = 2e3;
3501
4041
  var MAX_ANNOTATIONS = 1e3;
3502
4042
  var AnnotationsStore = class {
3503
4043
  dir;
4044
+ events;
4045
+ traceId;
3504
4046
  /** Per-session write queue. Created lazily on first add. */
3505
4047
  writeChains = /* @__PURE__ */ new Map();
3506
4048
  constructor(opts) {
3507
4049
  this.dir = opts.dir;
4050
+ this.events = opts.events;
4051
+ this.traceId = opts.traceId;
3508
4052
  }
3509
4053
  // ── Reads ──────────────────────────────────────────────────────────────
3510
4054
  /**
3511
4055
  * Return all annotations for `sessionId` in insertion order
3512
4056
  * (oldest first). Returns an empty array when no file exists
3513
- * yet (the normal case for a fresh session).
4057
+ * yet (the normal case for a fresh session) and also degrades
4058
+ * gracefully to `[]` on a read error (permissions, corruption) —
4059
+ * the failure is still surfaced via a `storage.read` event so it
4060
+ * never silently hides I/O problems from observers.
3514
4061
  */
3515
4062
  async list(sessionId) {
3516
- const file = await this.readFile(sessionId);
3517
- return file ? file.annotations : [];
4063
+ const t0 = Date.now();
4064
+ const fp = this.filePath(sessionId);
4065
+ try {
4066
+ const file = await this.readFile(sessionId);
4067
+ const durationMs = Date.now() - t0;
4068
+ this.events?.emit("storage.read", {
4069
+ sessionId,
4070
+ store: "annotations",
4071
+ filePath: fp,
4072
+ operation: "list",
4073
+ outcome: "success",
4074
+ durationMs,
4075
+ ...this.traceId !== void 0 ? { traceId: this.traceId } : {}
4076
+ });
4077
+ return file ? file.annotations : [];
4078
+ } catch (err) {
4079
+ this.events?.emit("storage.read", {
4080
+ sessionId,
4081
+ store: "annotations",
4082
+ filePath: fp,
4083
+ operation: "list",
4084
+ outcome: "failure",
4085
+ durationMs: Date.now() - t0,
4086
+ error: err instanceof Error ? err.message : String(err),
4087
+ ...this.traceId !== void 0 ? { traceId: this.traceId } : {}
4088
+ });
4089
+ return [];
4090
+ }
3518
4091
  }
3519
4092
  /**
3520
4093
  * Convenience: only unresolved annotations, newest first — the
@@ -3566,25 +4139,62 @@ var AnnotationsStore = class {
3566
4139
  createdAt: (/* @__PURE__ */ new Date()).toISOString(),
3567
4140
  resolved: false
3568
4141
  };
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
- }
4142
+ const fp = this.filePath(input.sessionId);
4143
+ const t0 = Date.now();
4144
+ try {
4145
+ await this.enqueue(input.sessionId, async () => {
4146
+ await withFileLock(fp, async () => {
4147
+ const all = await this.list(input.sessionId);
4148
+ all.push(annotation);
4149
+ if (all.length > MAX_ANNOTATIONS) {
4150
+ const sorted = all.map((a, i) => ({ a, i })).sort((x, y) => {
4151
+ if (x.a.resolved !== y.a.resolved) return x.a.resolved ? 1 : -1;
4152
+ return x.a.createdAt.localeCompare(y.a.createdAt);
4153
+ });
4154
+ const evictCount = all.length - MAX_ANNOTATIONS;
4155
+ const toEvict = new Set(sorted.slice(0, evictCount).map((s) => s.a.id));
4156
+ const kept = all.filter((a) => !toEvict.has(a.id));
4157
+ await this.writeFile(input.sessionId, { version: FILE_VERSION, annotations: kept });
4158
+ const durationMs = Date.now() - t0;
4159
+ this.events?.emit("storage.write", {
4160
+ sessionId: input.sessionId,
4161
+ store: "annotations",
4162
+ filePath: fp,
4163
+ operation: "evict",
4164
+ outcome: "success",
4165
+ durationMs,
4166
+ ...this.traceId !== void 0 ? { traceId: this.traceId } : {}
4167
+ });
4168
+ } else {
4169
+ await this.writeFile(input.sessionId, { version: FILE_VERSION, annotations: all });
4170
+ const durationMs = Date.now() - t0;
4171
+ this.events?.emit("storage.write", {
4172
+ sessionId: input.sessionId,
4173
+ store: "annotations",
4174
+ filePath: fp,
4175
+ operation: "add",
4176
+ outcome: "success",
4177
+ durationMs,
4178
+ ...this.traceId !== void 0 ? { traceId: this.traceId } : {}
4179
+ });
4180
+ }
4181
+ });
3585
4182
  });
3586
- });
3587
- return annotation;
4183
+ return annotation;
4184
+ } catch (err) {
4185
+ this.events?.emit("storage.error", {
4186
+ sessionId: input.sessionId,
4187
+ store: "annotations",
4188
+ filePath: fp,
4189
+ operation: "add",
4190
+ outcome: "failure",
4191
+ error: err instanceof Error ? err.message : String(err),
4192
+ recoverable: false,
4193
+ durationMs: Date.now() - t0,
4194
+ ...this.traceId !== void 0 ? { traceId: this.traceId } : {}
4195
+ });
4196
+ throw err;
4197
+ }
3588
4198
  }
3589
4199
  /**
3590
4200
  * Mark an annotation as resolved. Returns the updated record, or
@@ -3594,26 +4204,53 @@ var AnnotationsStore = class {
3594
4204
  */
3595
4205
  async resolve(input) {
3596
4206
  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;
4207
+ const fp = this.filePath(input.sessionId);
4208
+ const t0 = Date.now();
4209
+ try {
4210
+ await this.enqueue(input.sessionId, async () => {
4211
+ await withFileLock(fp, async () => {
4212
+ const all = await this.list(input.sessionId);
4213
+ const idx = all.findIndex((a) => a.id === input.annotationId);
4214
+ if (idx === -1) {
4215
+ updated = null;
4216
+ return;
4217
+ }
4218
+ const next = {
4219
+ ...expectDefined(all[idx]),
4220
+ resolved: true,
4221
+ resolvedAt: (/* @__PURE__ */ new Date()).toISOString(),
4222
+ resolvedBy: input.resolvedBy
4223
+ };
4224
+ all[idx] = next;
4225
+ await this.writeFile(input.sessionId, { version: FILE_VERSION, annotations: all });
4226
+ updated = next;
4227
+ const durationMs = Date.now() - t0;
4228
+ this.events?.emit("storage.write", {
4229
+ sessionId: input.sessionId,
4230
+ store: "annotations",
4231
+ filePath: fp,
4232
+ operation: "resolve",
4233
+ outcome: "success",
4234
+ durationMs,
4235
+ ...this.traceId !== void 0 ? { traceId: this.traceId } : {}
4236
+ });
4237
+ });
3614
4238
  });
3615
- });
3616
- return updated;
4239
+ return updated;
4240
+ } catch (err) {
4241
+ this.events?.emit("storage.error", {
4242
+ sessionId: input.sessionId,
4243
+ store: "annotations",
4244
+ filePath: fp,
4245
+ operation: "resolve",
4246
+ outcome: "failure",
4247
+ error: err instanceof Error ? err.message : String(err),
4248
+ recoverable: false,
4249
+ durationMs: Date.now() - t0,
4250
+ ...this.traceId !== void 0 ? { traceId: this.traceId } : {}
4251
+ });
4252
+ throw err;
4253
+ }
3617
4254
  }
3618
4255
  // ── Internals ──────────────────────────────────────────────────────────
3619
4256
  filePath(sessionId) {
@@ -3621,16 +4258,21 @@ var AnnotationsStore = class {
3621
4258
  }
3622
4259
  async readFile(sessionId) {
3623
4260
  const fp = this.filePath(sessionId);
4261
+ let raw;
4262
+ try {
4263
+ raw = await fsp.readFile(fp, "utf8");
4264
+ } catch (err) {
4265
+ if (err.code === "ENOENT") return null;
4266
+ throw err;
4267
+ }
3624
4268
  try {
3625
- const raw = await fsp.readFile(fp, "utf8");
3626
4269
  const parsed = JSON.parse(raw);
3627
4270
  if (parsed.version !== FILE_VERSION) {
3628
4271
  return { version: FILE_VERSION, annotations: [] };
3629
4272
  }
3630
4273
  return parsed;
3631
- } catch (err) {
3632
- if (err.code === "ENOENT") return null;
3633
- return { version: FILE_VERSION, annotations: [] };
4274
+ } catch {
4275
+ return null;
3634
4276
  }
3635
4277
  }
3636
4278
  async writeFile(sessionId, file) {
@@ -3684,9 +4326,20 @@ function hashRequest(request) {
3684
4326
  const digest = createHash("sha256").update(json, "utf8").digest("hex");
3685
4327
  return `sha256:${digest}`;
3686
4328
  }
4329
+
4330
+ // src/storage/replay-log-store.ts
4331
+ function storageErrorString2(err) {
4332
+ if (err instanceof Error) {
4333
+ const code = err.code;
4334
+ return code ? `${code}: ${err.message}` : err.message;
4335
+ }
4336
+ return String(err);
4337
+ }
3687
4338
  var DEFAULT_MAX_ENTRIES = 1e3;
3688
4339
  var ReplayLogStore = class {
3689
4340
  dir;
4341
+ events;
4342
+ traceId;
3690
4343
  writeChains = /* @__PURE__ */ new Map();
3691
4344
  /** Per-session hash → entry index, kept in memory after the first load. */
3692
4345
  cache = /* @__PURE__ */ new Map();
@@ -3695,6 +4348,8 @@ var ReplayLogStore = class {
3695
4348
  maxEntries;
3696
4349
  constructor(opts) {
3697
4350
  this.dir = opts.dir;
4351
+ this.events = opts.events;
4352
+ this.traceId = opts.traceId;
3698
4353
  this.maxEntries = opts.maxEntries ?? DEFAULT_MAX_ENTRIES;
3699
4354
  }
3700
4355
  // ── Writes ──────────────────────────────────────────────────────────────
@@ -3705,38 +4360,43 @@ var ReplayLogStore = class {
3705
4360
  */
3706
4361
  async record(input) {
3707
4362
  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);
4363
+ const fp = this.filePath(input.sessionId);
4364
+ const t0 = Date.now();
4365
+ try {
4366
+ await this.enqueue(input.sessionId, async () => {
4367
+ await withFileLock(this.filePath(input.sessionId), async () => {
4368
+ const entries = await this.readAll(input.sessionId);
4369
+ if (entries.some((entry2) => entry2.hash === hash)) return;
4370
+ const entry = {
4371
+ hash,
4372
+ ts: (/* @__PURE__ */ new Date()).toISOString(),
4373
+ request: input.request,
4374
+ response: input.response
4375
+ };
4376
+ entries.push(entry);
4377
+ const keep = entries.slice(-this.maxEntries);
4378
+ const didEvict = keep.length < entries.length;
4379
+ const cache = /* @__PURE__ */ new Map();
4380
+ for (const e of keep) cache.set(e.hash, e);
4381
+ this.cache.set(input.sessionId, cache);
4382
+ await this.writeAll(input.sessionId, keep, didEvict ? "compact" : "record");
4383
+ });
3724
4384
  });
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);
4385
+ return hash;
4386
+ } catch (err) {
4387
+ this.events?.emit("storage.error", {
4388
+ sessionId: input.sessionId,
4389
+ store: "replay",
4390
+ filePath: fp,
4391
+ operation: "record",
4392
+ outcome: "failure",
4393
+ error: storageErrorString2(err),
4394
+ recoverable: false,
4395
+ durationMs: Date.now() - t0,
4396
+ ...this.traceId !== void 0 ? { traceId: this.traceId } : {}
4397
+ });
4398
+ throw err;
4399
+ }
3740
4400
  }
3741
4401
  // ── Reads ───────────────────────────────────────────────────────────────
3742
4402
  /**
@@ -3745,13 +4405,65 @@ var ReplayLogStore = class {
3745
4405
  * per session (in-memory cache).
3746
4406
  */
3747
4407
  async lookup(sessionId, hash) {
3748
- const cache = await this.ensureCache(sessionId);
3749
- return cache.get(hash) ?? null;
4408
+ const fp = this.filePath(sessionId);
4409
+ const t0 = Date.now();
4410
+ try {
4411
+ const cache = await this.ensureCache(sessionId);
4412
+ this.events?.emit("storage.read", {
4413
+ sessionId,
4414
+ store: "replay",
4415
+ filePath: fp,
4416
+ operation: "lookup",
4417
+ outcome: "success",
4418
+ durationMs: Date.now() - t0,
4419
+ ...this.traceId !== void 0 ? { traceId: this.traceId } : {}
4420
+ });
4421
+ return cache.get(hash) ?? null;
4422
+ } catch (err) {
4423
+ this.events?.emit("storage.read", {
4424
+ sessionId,
4425
+ store: "replay",
4426
+ filePath: fp,
4427
+ operation: "lookup",
4428
+ outcome: "failure",
4429
+ durationMs: Date.now() - t0,
4430
+ error: storageErrorString2(err),
4431
+ ...this.traceId !== void 0 ? { traceId: this.traceId } : {}
4432
+ });
4433
+ throw err;
4434
+ }
3750
4435
  }
3751
4436
  /** All recorded entries for a session, in insertion order. */
3752
4437
  async load(sessionId) {
3753
- const cache = await this.ensureCache(sessionId);
3754
- return [...cache.values()];
4438
+ const fp = this.filePath(sessionId);
4439
+ const t0 = Date.now();
4440
+ try {
4441
+ const cache = await this.ensureCache(sessionId);
4442
+ const durationMs = Date.now() - t0;
4443
+ this.events?.emit("storage.read", {
4444
+ sessionId,
4445
+ store: "replay",
4446
+ filePath: fp,
4447
+ operation: "load",
4448
+ outcome: "success",
4449
+ durationMs,
4450
+ ...this.traceId !== void 0 ? { traceId: this.traceId } : {}
4451
+ });
4452
+ return [...cache.values()];
4453
+ } catch (err) {
4454
+ const durationMs = Date.now() - t0;
4455
+ this.events?.emit("storage.read", {
4456
+ sessionId,
4457
+ store: "replay",
4458
+ filePath: fp,
4459
+ operation: "load",
4460
+ outcome: "failure",
4461
+ durationMs,
4462
+ error: storageErrorString2(err),
4463
+ ...this.traceId !== void 0 ? { traceId: this.traceId } : {}
4464
+ });
4465
+ throw err;
4466
+ }
3755
4467
  }
3756
4468
  /**
3757
4469
  * List every session id that has a replay log in the store dir.
@@ -3821,13 +4533,24 @@ var ReplayLogStore = class {
3821
4533
  return out;
3822
4534
  } catch (err) {
3823
4535
  if (err.code === "ENOENT") return [];
3824
- return [];
4536
+ throw err;
3825
4537
  }
3826
4538
  }
3827
- async writeAll(sessionId, entries) {
4539
+ async writeAll(sessionId, entries, operation = "record") {
3828
4540
  const fp = this.filePath(sessionId);
4541
+ const t0 = Date.now();
3829
4542
  const body = entries.map((e) => JSON.stringify(e)).join("\n") + (entries.length ? "\n" : "");
3830
4543
  await atomicWrite(fp, body);
4544
+ const durationMs = Date.now() - t0;
4545
+ this.events?.emit("storage.write", {
4546
+ sessionId,
4547
+ store: "replay",
4548
+ filePath: fp,
4549
+ operation,
4550
+ outcome: "success",
4551
+ durationMs,
4552
+ ...this.traceId !== void 0 ? { traceId: this.traceId } : {}
4553
+ });
3831
4554
  }
3832
4555
  async ensureCache(sessionId) {
3833
4556
  let cache = this.cache.get(sessionId);
@@ -4005,6 +4728,8 @@ var GENESIS_PREV = "0".repeat(64);
4005
4728
  var DEFAULT_FSYNC_EVERY = 100;
4006
4729
  var ToolAuditLog = class {
4007
4730
  dir;
4731
+ events;
4732
+ traceId;
4008
4733
  /** In-memory cache of the last entry's hash (per session), to compute chains efficiently. */
4009
4734
  tailHash = /* @__PURE__ */ new Map();
4010
4735
  /** In-memory counter for entry indices — avoids re-reading the file on every write. */
@@ -4015,6 +4740,8 @@ var ToolAuditLog = class {
4015
4740
  fsyncEvery;
4016
4741
  constructor(opts) {
4017
4742
  this.dir = opts.dir;
4743
+ this.events = opts.events;
4744
+ this.traceId = opts.traceId;
4018
4745
  this.fsyncEvery = opts.fsyncEvery ?? DEFAULT_FSYNC_EVERY;
4019
4746
  }
4020
4747
  /**
@@ -4025,96 +4752,180 @@ var ToolAuditLog = class {
4025
4752
  */
4026
4753
  async record(input) {
4027
4754
  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);
4755
+ const fp = this.filePath(input.sessionId);
4756
+ const t0 = Date.now();
4757
+ try {
4758
+ await this.enqueue(input.sessionId, async () => {
4759
+ await withFileLock(fp, async () => {
4760
+ const entries = await this.readAll(input.sessionId);
4761
+ const prev = entries.at(-1);
4762
+ const prevHash = prev?.hash ?? GENESIS_PREV;
4763
+ const index = prev ? prev.index + 1 : 0;
4764
+ const id = randomUUID();
4765
+ const ts = (/* @__PURE__ */ new Date()).toISOString();
4766
+ const content = {
4767
+ id,
4768
+ ts,
4769
+ prevHash,
4770
+ toolName: input.toolName,
4771
+ toolUseId: input.toolUseId,
4772
+ input: input.input,
4773
+ output: input.output,
4774
+ isError: input.isError,
4775
+ index
4776
+ };
4777
+ const hash = createHash("sha256").update(stableStringify2(content), "utf8").digest("hex");
4778
+ entry = {
4779
+ id,
4780
+ ts,
4781
+ prevHash,
4782
+ hash,
4783
+ toolName: input.toolName,
4784
+ toolUseId: input.toolUseId,
4785
+ input: input.input,
4786
+ output: input.output,
4787
+ isError: input.isError,
4788
+ index
4789
+ };
4790
+ entries.push(entry);
4791
+ await this.writeAll(input.sessionId, entries);
4792
+ this.tailHash.set(input.sessionId, hash);
4793
+ this.tailIndex.set(input.sessionId, index + 1);
4794
+ const durationMs = Date.now() - t0;
4795
+ this.events?.emit("storage.write", {
4796
+ sessionId: input.sessionId,
4797
+ store: "audit",
4798
+ filePath: fp,
4799
+ operation: "record",
4800
+ outcome: "success",
4801
+ durationMs,
4802
+ ...this.traceId !== void 0 ? { traceId: this.traceId } : {}
4803
+ });
4804
+ });
4064
4805
  });
4065
- });
4066
- return entry;
4806
+ return entry;
4807
+ } catch (err) {
4808
+ this.events?.emit("storage.error", {
4809
+ sessionId: input.sessionId,
4810
+ store: "audit",
4811
+ filePath: fp,
4812
+ operation: "record",
4813
+ outcome: "failure",
4814
+ error: err instanceof Error ? err.message : String(err),
4815
+ recoverable: false,
4816
+ durationMs: Date.now() - t0,
4817
+ ...this.traceId !== void 0 ? { traceId: this.traceId } : {}
4818
+ });
4819
+ throw err;
4820
+ }
4067
4821
  }
4068
4822
  /**
4069
4823
  * Walk the chain and verify every entry's hash and prevHash.
4070
4824
  * Returns a structured verdict — never throws.
4071
4825
  */
4072
4826
  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
- };
4827
+ const fp = this.filePath(sessionId);
4828
+ const t0 = Date.now();
4829
+ let entries;
4830
+ try {
4831
+ entries = await this.readAll(sessionId);
4832
+ } catch (err) {
4833
+ this.events?.emit("storage.read", {
4834
+ sessionId,
4835
+ store: "audit",
4836
+ filePath: fp,
4837
+ operation: "verify",
4838
+ outcome: "failure",
4839
+ durationMs: Date.now() - t0,
4840
+ error: err instanceof Error ? err.message : String(err),
4841
+ ...this.traceId !== void 0 ? { traceId: this.traceId } : {}
4842
+ });
4843
+ return { ok: true, entries: 0 };
4081
4844
  }
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) {
4845
+ const verdict = (() => {
4846
+ if (entries.length === 0) return { ok: true, entries: 0 };
4847
+ if (entries[0]?.prevHash !== GENESIS_PREV) {
4086
4848
  return {
4087
4849
  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)`
4850
+ brokenAt: 0,
4851
+ reason: "first entry is not the genesis (prevHash != 0\u20260)"
4090
4852
  };
4091
4853
  }
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)`
4854
+ let prevHash = GENESIS_PREV;
4855
+ for (let i = 0; i < entries.length; i++) {
4856
+ const e = expectDefined(entries[i]);
4857
+ if (e.prevHash !== prevHash) {
4858
+ return {
4859
+ ok: false,
4860
+ brokenAt: i,
4861
+ reason: `prevHash mismatch at entry ${i} (expected ${prevHash.slice(0, 8)}\u2026, got ${e.prevHash.slice(0, 8)}\u2026)`
4862
+ };
4863
+ }
4864
+ const content = {
4865
+ id: e.id,
4866
+ ts: e.ts,
4867
+ prevHash: e.prevHash,
4868
+ toolName: e.toolName,
4869
+ toolUseId: e.toolUseId,
4870
+ input: e.input,
4871
+ output: e.output,
4872
+ isError: e.isError,
4873
+ index: e.index
4109
4874
  };
4875
+ const expectedHash = createHash("sha256").update(stableStringify2(content), "utf8").digest("hex");
4876
+ if (expectedHash !== e.hash) {
4877
+ return {
4878
+ ok: false,
4879
+ brokenAt: i,
4880
+ reason: `hash mismatch at entry ${i} (entry content was modified)`
4881
+ };
4882
+ }
4883
+ prevHash = e.hash;
4110
4884
  }
4111
- prevHash = e.hash;
4112
- }
4113
- return { ok: true, entries: entries.length };
4885
+ return { ok: true, entries: entries.length };
4886
+ })();
4887
+ this.events?.emit("storage.read", {
4888
+ sessionId,
4889
+ store: "audit",
4890
+ filePath: fp,
4891
+ operation: "verify",
4892
+ outcome: verdict.ok ? "success" : "failure",
4893
+ durationMs: Date.now() - t0,
4894
+ ...this.traceId !== void 0 ? { traceId: this.traceId } : {}
4895
+ });
4896
+ return verdict;
4114
4897
  }
4115
4898
  /** All entries for a session, in insertion order. */
4116
4899
  async load(sessionId) {
4117
- return this.readAll(sessionId);
4900
+ const fp = this.filePath(sessionId);
4901
+ const t0 = Date.now();
4902
+ try {
4903
+ const entries = await this.readAll(sessionId);
4904
+ const durationMs = Date.now() - t0;
4905
+ this.events?.emit("storage.read", {
4906
+ sessionId,
4907
+ store: "audit",
4908
+ filePath: fp,
4909
+ operation: "load",
4910
+ outcome: "success",
4911
+ durationMs,
4912
+ ...this.traceId !== void 0 ? { traceId: this.traceId } : {}
4913
+ });
4914
+ return entries;
4915
+ } catch (err) {
4916
+ const durationMs = Date.now() - t0;
4917
+ this.events?.emit("storage.read", {
4918
+ sessionId,
4919
+ store: "audit",
4920
+ filePath: fp,
4921
+ operation: "load",
4922
+ outcome: "failure",
4923
+ durationMs,
4924
+ error: err instanceof Error ? err.message : String(err),
4925
+ ...this.traceId !== void 0 ? { traceId: this.traceId } : {}
4926
+ });
4927
+ throw err;
4928
+ }
4118
4929
  }
4119
4930
  // ── Internals ────────────────────────────────────────────────────────────
4120
4931
  filePath(sessionId) {
@@ -4136,7 +4947,7 @@ var ToolAuditLog = class {
4136
4947
  return out;
4137
4948
  } catch (err) {
4138
4949
  if (err.code === "ENOENT") return [];
4139
- return [];
4950
+ throw err;
4140
4951
  }
4141
4952
  }
4142
4953
  async writeAll(sessionId, entries) {
@@ -4848,24 +5659,66 @@ async function revertSnapshots(snapshots, projectRoot) {
4848
5659
  }
4849
5660
  return { revertedFiles, errors };
4850
5661
  }
4851
- async function loadTodosCheckpoint(filePath) {
5662
+ async function loadTodosCheckpoint(filePath, events, traceId) {
5663
+ const t0 = Date.now();
4852
5664
  let raw;
4853
5665
  try {
4854
5666
  raw = await fsp.readFile(filePath, "utf8");
4855
- } catch {
5667
+ } catch (err) {
5668
+ events?.emit("storage.error", {
5669
+ sessionId: traceId ?? "~boot~",
5670
+ store: "todos",
5671
+ filePath,
5672
+ operation: "load",
5673
+ outcome: "failure",
5674
+ error: err instanceof Error ? err.message : String(err),
5675
+ recoverable: true
5676
+ });
4856
5677
  return null;
4857
5678
  }
4858
5679
  try {
4859
5680
  const parsed = JSON.parse(raw);
4860
- if (parsed?.version !== 1 || !Array.isArray(parsed.todos)) return null;
5681
+ if (parsed?.version !== 1 || !Array.isArray(parsed.todos)) {
5682
+ events?.emit("storage.read", {
5683
+ sessionId: traceId ?? "~boot~",
5684
+ store: "todos",
5685
+ filePath,
5686
+ operation: "load",
5687
+ outcome: "failure",
5688
+ durationMs: Date.now() - t0,
5689
+ error: "invalid_schema",
5690
+ ...traceId !== void 0 && { traceId }
5691
+ });
5692
+ return null;
5693
+ }
5694
+ events?.emit("storage.read", {
5695
+ sessionId: traceId ?? "~boot~",
5696
+ store: "todos",
5697
+ filePath,
5698
+ operation: "load",
5699
+ outcome: "success",
5700
+ durationMs: Date.now() - t0,
5701
+ ...traceId !== void 0 && { traceId }
5702
+ });
4861
5703
  return parsed.todos.filter(
4862
5704
  (t) => !!t && typeof t.id === "string" && typeof t.content === "string" && typeof t.status === "string" && (t.activeForm === void 0 || typeof t.activeForm === "string")
4863
5705
  );
4864
5706
  } catch {
5707
+ events?.emit("storage.read", {
5708
+ sessionId: traceId ?? "~boot~",
5709
+ store: "todos",
5710
+ filePath,
5711
+ operation: "load",
5712
+ outcome: "failure",
5713
+ durationMs: Date.now() - t0,
5714
+ error: "parse_failed",
5715
+ ...traceId !== void 0 && { traceId }
5716
+ });
4865
5717
  return null;
4866
5718
  }
4867
5719
  }
4868
- async function saveTodosCheckpoint(filePath, sessionId, todos) {
5720
+ async function saveTodosCheckpoint(filePath, sessionId, todos, events, traceId) {
5721
+ const t0 = Date.now();
4869
5722
  const payload = {
4870
5723
  version: 1,
4871
5724
  sessionId,
@@ -4874,7 +5727,25 @@ async function saveTodosCheckpoint(filePath, sessionId, todos) {
4874
5727
  };
4875
5728
  try {
4876
5729
  await atomicWrite(filePath, JSON.stringify(payload, null, 2), { mode: 384 });
5730
+ events?.emit("storage.write", {
5731
+ sessionId: traceId ?? sessionId,
5732
+ store: "todos",
5733
+ filePath,
5734
+ operation: "save",
5735
+ outcome: "success",
5736
+ durationMs: Date.now() - t0,
5737
+ ...traceId !== void 0 && { traceId }
5738
+ });
4877
5739
  } catch (err) {
5740
+ events?.emit("storage.error", {
5741
+ sessionId: traceId ?? sessionId,
5742
+ store: "todos",
5743
+ filePath,
5744
+ operation: "save",
5745
+ outcome: "failure",
5746
+ error: err instanceof Error ? err.message : String(err),
5747
+ recoverable: false
5748
+ });
4878
5749
  console.warn(JSON.stringify({
4879
5750
  level: "warn",
4880
5751
  event: "todos_checkpoint.save_failed",
@@ -4883,12 +5754,12 @@ async function saveTodosCheckpoint(filePath, sessionId, todos) {
4883
5754
  }));
4884
5755
  }
4885
5756
  }
4886
- function attachTodosCheckpoint(state, filePath, sessionId) {
5757
+ function attachTodosCheckpoint(state, filePath, sessionId, events, traceId) {
4887
5758
  let timer = null;
4888
5759
  let pending = null;
4889
5760
  let writeChain = Promise.resolve();
4890
5761
  const enqueueWrite = (todos) => {
4891
- writeChain = writeChain.then(() => saveTodosCheckpoint(filePath, sessionId, todos)).catch((err) => {
5762
+ writeChain = writeChain.then(() => saveTodosCheckpoint(filePath, sessionId, todos, events, traceId)).catch((err) => {
4892
5763
  const msg = err instanceof Error ? err.message : String(err);
4893
5764
  console.error(JSON.stringify({
4894
5765
  level: "error",
@@ -4927,25 +5798,79 @@ function attachTodosCheckpoint(state, filePath, sessionId) {
4927
5798
  }
4928
5799
  };
4929
5800
  }
4930
- async function loadPlan(filePath) {
5801
+ async function loadPlan(filePath, events) {
5802
+ const t0 = Date.now();
4931
5803
  let raw;
4932
5804
  try {
4933
5805
  raw = await fsp.readFile(filePath, "utf8");
4934
- } catch {
5806
+ } catch (err) {
5807
+ events?.emit("storage.error", {
5808
+ sessionId: "~boot~",
5809
+ store: "plan",
5810
+ filePath,
5811
+ operation: "load",
5812
+ error: err instanceof Error ? err.message : String(err),
5813
+ recoverable: true
5814
+ });
4935
5815
  return null;
4936
5816
  }
4937
5817
  try {
4938
5818
  const parsed = JSON.parse(raw);
4939
- if (parsed?.version !== 1 || !Array.isArray(parsed.items)) return null;
5819
+ if (parsed?.version !== 1 || !Array.isArray(parsed.items)) {
5820
+ events?.emit("storage.read", {
5821
+ sessionId: "~boot~",
5822
+ store: "plan",
5823
+ filePath,
5824
+ operation: "load",
5825
+ outcome: "failure",
5826
+ durationMs: Date.now() - t0,
5827
+ error: "invalid_schema"
5828
+ });
5829
+ return null;
5830
+ }
5831
+ events?.emit("storage.read", {
5832
+ sessionId: "~boot~",
5833
+ store: "plan",
5834
+ filePath,
5835
+ operation: "load",
5836
+ outcome: "success",
5837
+ durationMs: Date.now() - t0
5838
+ });
4940
5839
  return parsed;
4941
5840
  } catch {
5841
+ events?.emit("storage.read", {
5842
+ sessionId: "~boot~",
5843
+ store: "plan",
5844
+ filePath,
5845
+ operation: "load",
5846
+ outcome: "failure",
5847
+ durationMs: Date.now() - t0,
5848
+ error: "parse_failed"
5849
+ });
4942
5850
  return null;
4943
5851
  }
4944
5852
  }
4945
- async function savePlan(filePath, plan) {
5853
+ async function savePlan(filePath, plan, events) {
5854
+ const t0 = Date.now();
4946
5855
  try {
4947
5856
  await atomicWrite(filePath, JSON.stringify(plan, null, 2), { mode: 384 });
5857
+ events?.emit("storage.write", {
5858
+ sessionId: "~boot~",
5859
+ store: "plan",
5860
+ filePath,
5861
+ operation: "save",
5862
+ outcome: "success",
5863
+ durationMs: Date.now() - t0
5864
+ });
4948
5865
  } catch (err) {
5866
+ events?.emit("storage.error", {
5867
+ sessionId: "~boot~",
5868
+ store: "plan",
5869
+ filePath,
5870
+ operation: "save",
5871
+ error: err instanceof Error ? err.message : String(err),
5872
+ recoverable: false
5873
+ });
4949
5874
  console.warn(
4950
5875
  "[plan-store] save failed:",
4951
5876
  err instanceof Error ? err.message : String(err)
@@ -5179,37 +6104,98 @@ function emptyTaskFile(sessionId) {
5179
6104
  tasks: []
5180
6105
  };
5181
6106
  }
5182
- async function loadTasks(filePath) {
6107
+ async function loadTasks(filePath, events, traceId) {
6108
+ const t0 = Date.now();
5183
6109
  let raw;
5184
6110
  try {
5185
6111
  raw = await fsp.readFile(filePath, "utf8");
5186
- } catch {
6112
+ } catch (err) {
6113
+ events?.emit("storage.error", {
6114
+ sessionId: traceId ?? "~boot~",
6115
+ store: "tasks",
6116
+ filePath,
6117
+ operation: "load",
6118
+ outcome: "failure",
6119
+ error: err instanceof Error ? err.message : String(err),
6120
+ recoverable: true
6121
+ });
5187
6122
  return null;
5188
6123
  }
5189
6124
  try {
5190
6125
  const parsed = JSON.parse(raw);
5191
- if (parsed?.version !== 1 || !Array.isArray(parsed.tasks)) return null;
6126
+ if (parsed?.version !== 1 || !Array.isArray(parsed.tasks)) {
6127
+ events?.emit("storage.read", {
6128
+ sessionId: traceId ?? "~boot~",
6129
+ store: "tasks",
6130
+ filePath,
6131
+ operation: "load",
6132
+ outcome: "failure",
6133
+ durationMs: Date.now() - t0,
6134
+ error: "invalid_schema",
6135
+ ...traceId !== void 0 && { traceId }
6136
+ });
6137
+ return null;
6138
+ }
6139
+ events?.emit("storage.read", {
6140
+ sessionId: traceId ?? "~boot~",
6141
+ store: "tasks",
6142
+ filePath,
6143
+ operation: "load",
6144
+ outcome: "success",
6145
+ durationMs: Date.now() - t0,
6146
+ ...traceId !== void 0 && { traceId }
6147
+ });
5192
6148
  return parsed;
5193
6149
  } catch {
6150
+ events?.emit("storage.read", {
6151
+ sessionId: traceId ?? "~boot~",
6152
+ store: "tasks",
6153
+ filePath,
6154
+ operation: "load",
6155
+ outcome: "failure",
6156
+ durationMs: Date.now() - t0,
6157
+ error: "parse_failed",
6158
+ ...traceId !== void 0 && { traceId }
6159
+ });
5194
6160
  return null;
5195
6161
  }
5196
6162
  }
5197
- async function saveTasks(filePath, tasks) {
6163
+ async function saveTasks(filePath, tasks, events, traceId) {
6164
+ const t0 = Date.now();
5198
6165
  try {
5199
6166
  tasks.updatedAt = (/* @__PURE__ */ new Date()).toISOString();
5200
6167
  await atomicWrite(filePath, JSON.stringify(tasks, null, 2), { mode: 384 });
6168
+ events?.emit("storage.write", {
6169
+ sessionId: traceId ?? "~boot~",
6170
+ store: "tasks",
6171
+ filePath,
6172
+ operation: "save",
6173
+ outcome: "success",
6174
+ durationMs: Date.now() - t0,
6175
+ ...traceId !== void 0 && { traceId }
6176
+ });
5201
6177
  } catch (err) {
6178
+ events?.emit("storage.error", {
6179
+ sessionId: traceId ?? "~boot~",
6180
+ store: "tasks",
6181
+ filePath,
6182
+ operation: "save",
6183
+ outcome: "failure",
6184
+ error: err instanceof Error ? err.message : String(err),
6185
+ recoverable: false,
6186
+ ...traceId !== void 0 && { traceId }
6187
+ });
5202
6188
  console.warn(
5203
6189
  "[task-store] save failed:",
5204
6190
  err instanceof Error ? err.message : String(err)
5205
6191
  );
5206
6192
  }
5207
6193
  }
5208
- async function mutateTasks(filePath, sessionId, fn) {
6194
+ async function mutateTasks(filePath, sessionId, fn, events, traceId) {
5209
6195
  return withFileLock(filePath, async () => {
5210
- const file = await loadTasks(filePath) ?? emptyTaskFile(sessionId);
6196
+ const file = await loadTasks(filePath, events, traceId) ?? emptyTaskFile(sessionId);
5211
6197
  const updated = await fn(file);
5212
- await saveTasks(filePath, updated);
6198
+ await saveTasks(filePath, updated, events, traceId);
5213
6199
  return updated;
5214
6200
  });
5215
6201
  }
@@ -5491,13 +6477,32 @@ var MAX_JOURNAL_ENTRIES = 500;
5491
6477
  function goalFilePath(projectRoot) {
5492
6478
  return resolveWstackPaths({ projectRoot }).projectGoal;
5493
6479
  }
5494
- async function loadGoal(filePath) {
6480
+ async function loadGoal(filePath, events) {
6481
+ const t0 = Date.now();
5495
6482
  let raw;
5496
6483
  try {
5497
6484
  raw = await fsp.readFile(filePath, "utf8");
5498
6485
  } catch (err) {
5499
6486
  const code = err.code;
5500
- if (code === "ENOENT") return null;
6487
+ if (code === "ENOENT") {
6488
+ events?.emit("storage.read", {
6489
+ sessionId: "~boot~",
6490
+ store: "goal",
6491
+ filePath,
6492
+ operation: "load",
6493
+ outcome: "success",
6494
+ durationMs: Date.now() - t0
6495
+ });
6496
+ return null;
6497
+ }
6498
+ events?.emit("storage.error", {
6499
+ sessionId: "~boot~",
6500
+ store: "goal",
6501
+ filePath,
6502
+ operation: "load",
6503
+ error: err instanceof Error ? err.message : String(err),
6504
+ recoverable: false
6505
+ });
5501
6506
  throw err;
5502
6507
  }
5503
6508
  try {
@@ -5510,8 +6515,25 @@ async function loadGoal(filePath) {
5510
6515
  message: "invalid schema \u2014 consider deleting and re-creating",
5511
6516
  timestamp: (/* @__PURE__ */ new Date()).toISOString()
5512
6517
  }));
6518
+ events?.emit("storage.read", {
6519
+ sessionId: "~boot~",
6520
+ store: "goal",
6521
+ filePath,
6522
+ operation: "load",
6523
+ outcome: "failure",
6524
+ durationMs: Date.now() - t0,
6525
+ error: "invalid_schema"
6526
+ });
5513
6527
  return null;
5514
6528
  }
6529
+ events?.emit("storage.read", {
6530
+ sessionId: "~boot~",
6531
+ store: "goal",
6532
+ filePath,
6533
+ operation: "load",
6534
+ outcome: "success",
6535
+ durationMs: Date.now() - t0
6536
+ });
5515
6537
  return parsed;
5516
6538
  } catch {
5517
6539
  console.warn(JSON.stringify({
@@ -5521,13 +6543,39 @@ async function loadGoal(filePath) {
5521
6543
  message: "JSON parse failed \u2014 consider deleting and re-creating",
5522
6544
  timestamp: (/* @__PURE__ */ new Date()).toISOString()
5523
6545
  }));
6546
+ events?.emit("storage.read", {
6547
+ sessionId: "~boot~",
6548
+ store: "goal",
6549
+ filePath,
6550
+ operation: "load",
6551
+ outcome: "failure",
6552
+ durationMs: Date.now() - t0,
6553
+ error: "parse_failed"
6554
+ });
5524
6555
  return null;
5525
6556
  }
5526
6557
  }
5527
- async function saveGoal(filePath, goal) {
6558
+ async function saveGoal(filePath, goal, events) {
6559
+ const t0 = Date.now();
5528
6560
  try {
5529
6561
  await atomicWrite(filePath, JSON.stringify(goal, null, 2), { mode: 384 });
6562
+ events?.emit("storage.write", {
6563
+ sessionId: "~boot~",
6564
+ store: "goal",
6565
+ filePath,
6566
+ operation: "save",
6567
+ outcome: "success",
6568
+ durationMs: Date.now() - t0
6569
+ });
5530
6570
  } catch (err) {
6571
+ events?.emit("storage.error", {
6572
+ sessionId: "~boot~",
6573
+ store: "goal",
6574
+ filePath,
6575
+ operation: "save",
6576
+ error: err instanceof Error ? err.message : String(err),
6577
+ recoverable: false
6578
+ });
5531
6579
  throw new FsError({
5532
6580
  message: err instanceof Error ? err.message : String(err),
5533
6581
  code: ERROR_CODES.FS_ATOMIC_WRITE_FAILED,