@wrongstack/core 0.257.2 → 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 (63) 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 +189 -28
  9. package/dist/coordination/index.js.map +1 -1
  10. package/dist/defaults/index.d.ts +25 -25
  11. package/dist/defaults/index.js +881 -83
  12. package/dist/defaults/index.js.map +1 -1
  13. package/dist/execution/index.d.ts +15 -15
  14. package/dist/execution/index.js +108 -26
  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-CznHTZqP.d.ts → goal-preamble-B1IXJtLX.d.ts} +9 -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 +44 -42
  23. package/dist/index.js +1452 -274
  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 +2 -2
  61. package/package.json +1 -1
  62. package/skills/output-standards/SKILL.md +14 -9
  63. package/skills/output-standards/SKILL.save.md +3 -2
package/dist/index.js CHANGED
@@ -4279,6 +4279,13 @@ function hasDangerousCapabilityForSubagents(toolOrCaps) {
4279
4279
  const caps = Array.isArray(toolOrCaps) ? toolOrCaps : input.capabilities ?? [];
4280
4280
  return caps.some((c) => DANGEROUS_FOR_SUBAGENTS.includes(c));
4281
4281
  }
4282
+ function hasCapability(toolOrCaps, capability) {
4283
+ if (!toolOrCaps) return false;
4284
+ const input = toolOrCaps;
4285
+ const caps = Array.isArray(toolOrCaps) ? toolOrCaps : input.capabilities ?? [];
4286
+ const toCheck = Array.isArray(capability) ? capability : [capability];
4287
+ return toCheck.some((c) => caps.includes(c));
4288
+ }
4282
4289
  function getDangerousCapabilities(toolOrCaps) {
4283
4290
  if (!toolOrCaps) return [];
4284
4291
  const input = toolOrCaps;
@@ -5052,6 +5059,12 @@ var Context = class {
5052
5059
  agentId;
5053
5060
  /** Human-readable agent name. */
5054
5061
  agentName;
5062
+ /**
5063
+ * Session-level trace ID for correlating storage events with agent
5064
+ * iterations. Stored here and also propagated to `session.traceId`
5065
+ * so storage operations can include it in `storage.*` events.
5066
+ */
5067
+ traceId;
5055
5068
  /** Callbacks fired when `setWorkingDir()` changes the working directory. */
5056
5069
  _onWorkingDirChanged = [];
5057
5070
  /**
@@ -5087,6 +5100,8 @@ var Context = class {
5087
5100
  this.tools = init.tools ?? [];
5088
5101
  this.agentId = init.agentId ?? "unknown";
5089
5102
  this.agentName = init.agentName ?? "Unknown Agent";
5103
+ this.traceId = init.traceId;
5104
+ this.session.traceId = init.traceId;
5090
5105
  }
5091
5106
  /**
5092
5107
  * Observable wrapper over the mutable conversation state. Lazy so
@@ -6487,6 +6502,40 @@ var DefaultSessionStore = class _DefaultSessionStore {
6487
6502
  this.events = opts.events;
6488
6503
  this.secretScrubber = opts.secretScrubber;
6489
6504
  }
6505
+ // ── Storage event helpers ───────────────────────────────────────────────────
6506
+ emitRead(sessionId, filePath, operation, outcome, durationMs, error) {
6507
+ this.events?.emit("storage.read", {
6508
+ sessionId,
6509
+ store: "session",
6510
+ filePath,
6511
+ operation,
6512
+ outcome,
6513
+ durationMs,
6514
+ ...error !== void 0 ? { error } : {}
6515
+ });
6516
+ }
6517
+ emitWrite(sessionId, filePath, operation, outcome, durationMs, eventCount, error) {
6518
+ this.events?.emit("storage.write", {
6519
+ sessionId,
6520
+ store: "session",
6521
+ filePath,
6522
+ operation,
6523
+ outcome,
6524
+ durationMs,
6525
+ ...eventCount !== void 0 ? { eventCount } : {},
6526
+ ...error !== void 0 ? { error } : {}
6527
+ });
6528
+ }
6529
+ emitError(sessionId, filePath, operation, error, recoverable) {
6530
+ this.events?.emit("storage.error", {
6531
+ sessionId,
6532
+ store: "session",
6533
+ filePath,
6534
+ operation,
6535
+ error,
6536
+ recoverable
6537
+ });
6538
+ }
6490
6539
  /** Absolute path to the session index file. */
6491
6540
  get indexFile() {
6492
6541
  return path7.join(this.dir, "_index.jsonl");
@@ -6510,22 +6559,26 @@ var DefaultSessionStore = class _DefaultSessionStore {
6510
6559
  const id = meta.id && meta.id.length > 0 ? meta.id : generateSessionId(startedAt, meta.model ?? meta.provider);
6511
6560
  const shardDir = await this.ensureShardDir(id);
6512
6561
  const file = path7.join(shardDir, `${path7.basename(id)}.jsonl`);
6562
+ const t0 = Date.now();
6513
6563
  let handle;
6514
6564
  try {
6515
6565
  handle = await fsp3.open(file, "a", 384);
6516
6566
  } catch (err) {
6567
+ this.emitError(id, file, "create", err instanceof Error ? err.message : String(err), false);
6517
6568
  throw new Error(
6518
6569
  `Failed to open session file: ${err instanceof Error ? err.message : String(err)}`,
6519
6570
  { cause: err }
6520
6571
  );
6521
6572
  }
6522
6573
  try {
6523
- return new FileSessionWriter(id, handle, startedAt, meta, this.events, {
6574
+ const writer = new FileSessionWriter(id, handle, startedAt, meta, this.events, {
6524
6575
  dir: shardDir,
6525
6576
  filePath: file,
6526
6577
  secretScrubber: this.secretScrubber,
6527
6578
  onClose: (s) => this.appendToIndex(s)
6528
6579
  });
6580
+ this.emitWrite(id, file, "create", "success", Date.now() - t0);
6581
+ return writer;
6529
6582
  } catch (err) {
6530
6583
  await handle.close().catch((e) => console.warn(JSON.stringify({
6531
6584
  level: "warn",
@@ -6533,16 +6586,19 @@ var DefaultSessionStore = class _DefaultSessionStore {
6533
6586
  message: e instanceof Error ? e.message : String(e),
6534
6587
  timestamp: (/* @__PURE__ */ new Date()).toISOString()
6535
6588
  })));
6589
+ this.emitError(id, file, "create", err instanceof Error ? err.message : String(err), true);
6536
6590
  throw err;
6537
6591
  }
6538
6592
  }
6539
6593
  async resume(id) {
6540
6594
  const file = this.sessionPath(id, ".jsonl");
6595
+ const t0 = Date.now();
6541
6596
  const data = await this.load(id);
6542
6597
  let handle;
6543
6598
  try {
6544
6599
  handle = await fsp3.open(file, "a", 384);
6545
6600
  } catch (err) {
6601
+ this.emitError(id, file, "resume", err instanceof Error ? err.message : String(err), false);
6546
6602
  throw new Error(
6547
6603
  `Failed to open session "${id}" for append: ${err instanceof Error ? err.message : String(err)}`,
6548
6604
  { cause: err }
@@ -6570,6 +6626,7 @@ var DefaultSessionStore = class _DefaultSessionStore {
6570
6626
  onClose: (s) => this.appendToIndex(s)
6571
6627
  }
6572
6628
  );
6629
+ this.emitWrite(id, file, "resume", "success", Date.now() - t0);
6573
6630
  return { writer, data };
6574
6631
  } catch (err) {
6575
6632
  await handle.close().catch((e) => console.warn(JSON.stringify({
@@ -6578,27 +6635,39 @@ var DefaultSessionStore = class _DefaultSessionStore {
6578
6635
  message: e instanceof Error ? e.message : String(e),
6579
6636
  timestamp: (/* @__PURE__ */ new Date()).toISOString()
6580
6637
  })));
6638
+ this.emitError(id, file, "resume", err instanceof Error ? err.message : String(err), true);
6581
6639
  throw err;
6582
6640
  }
6583
6641
  }
6584
6642
  async load(id) {
6585
6643
  const file = this.sessionPath(id, ".jsonl");
6586
- const raw = await fsp3.readFile(file, "utf8");
6587
- const lines = raw.split("\n").filter((l) => l.trim());
6588
- const events = [];
6589
- for (const line of lines) {
6590
- try {
6591
- const parsed = JSON.parse(line);
6592
- if (parsed !== null && typeof parsed === "object" && typeof parsed.type === "string" && typeof parsed.ts === "string") {
6593
- events.push(parsed);
6644
+ const t0 = Date.now();
6645
+ let outcome = "success";
6646
+ let errorMsg;
6647
+ try {
6648
+ const raw = await fsp3.readFile(file, "utf8");
6649
+ const lines = raw.split("\n").filter((l) => l.trim());
6650
+ const events = [];
6651
+ for (const line of lines) {
6652
+ try {
6653
+ const parsed = JSON.parse(line);
6654
+ if (parsed !== null && typeof parsed === "object" && typeof parsed.type === "string" && typeof parsed.ts === "string") {
6655
+ events.push(parsed);
6656
+ }
6657
+ } catch {
6594
6658
  }
6595
- } catch {
6596
6659
  }
6660
+ const meta = this.metaFromEvents(id, events);
6661
+ const { messages, usage } = this.replay(events, id);
6662
+ const toolCallEnds = extractToolCallEnds(events);
6663
+ return { metadata: meta, events, messages, usage, toolCallEnds };
6664
+ } catch (err) {
6665
+ outcome = "failure";
6666
+ errorMsg = err instanceof Error ? err.message : String(err);
6667
+ throw err;
6668
+ } finally {
6669
+ this.emitRead(id, file, "load", outcome, Date.now() - t0, errorMsg);
6597
6670
  }
6598
- const meta = this.metaFromEvents(id, events);
6599
- const { messages, usage } = this.replay(events, id);
6600
- const toolCallEnds = extractToolCallEnds(events);
6601
- return { metadata: meta, events, messages, usage, toolCallEnds };
6602
6671
  }
6603
6672
  async list(limit = 20) {
6604
6673
  try {
@@ -6665,12 +6734,22 @@ var DefaultSessionStore = class _DefaultSessionStore {
6665
6734
  * (keep latest per session), and rewrite. Atomic via temp+rename.
6666
6735
  */
6667
6736
  async compactIndex() {
6668
- const entries = await this.readIndex();
6669
- if (entries.length === 0) return;
6670
- const tmp = `${this.indexFile}.compact.tmp`;
6671
- const lines = entries.map((s) => JSON.stringify(s)).join("\n") + "\n";
6672
- await fsp3.writeFile(tmp, lines, "utf8");
6673
- await fsp3.rename(tmp, this.indexFile);
6737
+ const t0 = Date.now();
6738
+ let outcome = "success";
6739
+ let errorMsg;
6740
+ try {
6741
+ const entries = await this.readIndex();
6742
+ if (entries.length === 0) return;
6743
+ const tmp = `${this.indexFile}.compact.tmp`;
6744
+ const lines = entries.map((s) => JSON.stringify(s)).join("\n") + "\n";
6745
+ await fsp3.writeFile(tmp, lines, "utf8");
6746
+ await fsp3.rename(tmp, this.indexFile);
6747
+ } catch (err) {
6748
+ outcome = "failure";
6749
+ errorMsg = err instanceof Error ? err.message : String(err);
6750
+ } finally {
6751
+ this.emitWrite("~compact~", this.indexFile, "compact", outcome, Date.now() - t0, void 0, errorMsg);
6752
+ }
6674
6753
  }
6675
6754
  /**
6676
6755
  * Read the index file and return deduplicated session summaries.
@@ -6746,22 +6825,31 @@ var DefaultSessionStore = class _DefaultSessionStore {
6746
6825
  }
6747
6826
  async summaryFor(id) {
6748
6827
  const manifest = this.sessionPath(id, ".summary.json");
6828
+ const t0 = Date.now();
6829
+ let outcome = "success";
6830
+ let errorMsg;
6749
6831
  try {
6750
6832
  const raw = await fsp3.readFile(manifest, "utf8");
6833
+ this.emitRead(id, manifest, "summary", "success", Date.now() - t0);
6751
6834
  return JSON.parse(raw);
6752
6835
  } catch {
6753
6836
  const full = this.sessionPath(id, ".jsonl");
6754
6837
  const stat11 = await fsp3.stat(full);
6755
6838
  const summary = await this.summarize(id, stat11.mtime.toISOString());
6756
6839
  await atomicWrite(manifest, JSON.stringify(summary), { mode: 384 }).catch((err) => {
6840
+ const msg = err instanceof Error ? err.message : String(err);
6841
+ this.emitError(id, manifest, "summary_fallback", msg, true);
6757
6842
  console.warn(JSON.stringify({
6758
6843
  level: "warn",
6759
6844
  event: "session_store.manifest_write_failed",
6760
6845
  sessionId: id,
6761
- message: err instanceof Error ? err.message : String(err),
6846
+ message: msg,
6762
6847
  timestamp: (/* @__PURE__ */ new Date()).toISOString()
6763
6848
  }));
6764
6849
  });
6850
+ outcome = "failure";
6851
+ errorMsg = "summary fallback \u2014 manifest rebuilt";
6852
+ this.emitRead(id, manifest, "summary", outcome, Date.now() - t0, errorMsg);
6765
6853
  return summary;
6766
6854
  }
6767
6855
  }
@@ -7027,7 +7115,7 @@ function extractToolCallEnds(events) {
7027
7115
  return result;
7028
7116
  }
7029
7117
  var FileSessionWriter = class _FileSessionWriter {
7030
- constructor(id, handle, startedAt, meta, events, opts = {}) {
7118
+ constructor(id, handle, startedAt, meta, events, opts = {}, traceId) {
7031
7119
  this.id = id;
7032
7120
  this.handle = handle;
7033
7121
  this.startedAt = startedAt;
@@ -7046,6 +7134,7 @@ var FileSessionWriter = class _FileSessionWriter {
7046
7134
  provider: meta.provider ?? "unknown",
7047
7135
  tokenTotal: 0
7048
7136
  };
7137
+ this.traceId = traceId;
7049
7138
  }
7050
7139
  id;
7051
7140
  handle;
@@ -7078,6 +7167,8 @@ var FileSessionWriter = class _FileSessionWriter {
7078
7167
  lastAppendWarnAt = 0;
7079
7168
  secretScrubber;
7080
7169
  onCloseCb;
7170
+ /** Implements SessionWriter.traceId — propagated from ContextInit.traceId. */
7171
+ traceId;
7081
7172
  // ── Write buffer — batches events to reduce per-event disk I/O ─────────
7082
7173
  //
7083
7174
  // Every append() pushes the scrubbed event into an in-memory buffer instead
@@ -7231,9 +7322,14 @@ var FileSessionWriter = class _FileSessionWriter {
7231
7322
  const eventCount = this.writeBuffer.length;
7232
7323
  const batch = this.writeBuffer.map((e) => JSON.stringify(e)).join("\n") + "\n";
7233
7324
  this.writeBuffer = [];
7325
+ const t0 = Date.now();
7326
+ let outcome = "success";
7327
+ let errorMsg;
7234
7328
  try {
7235
7329
  await this.enqueueWrite(batch);
7236
7330
  } catch (err) {
7331
+ outcome = "failure";
7332
+ errorMsg = err instanceof Error ? err.message : String(err);
7237
7333
  this.appendFailCount += eventCount;
7238
7334
  const now = Date.now();
7239
7335
  if (now - this.lastAppendWarnAt > 5e3) {
@@ -7247,6 +7343,18 @@ var FileSessionWriter = class _FileSessionWriter {
7247
7343
  this.lastAppendWarnAt = now;
7248
7344
  this.appendFailCount = 0;
7249
7345
  }
7346
+ } finally {
7347
+ this.events?.emit("storage.write", {
7348
+ sessionId: this.id,
7349
+ store: "session",
7350
+ filePath: this.filePath,
7351
+ operation: "flush",
7352
+ outcome,
7353
+ durationMs: Date.now() - t0,
7354
+ ...errorMsg !== void 0 ? { error: errorMsg } : {},
7355
+ ...eventCount !== void 0 ? { eventCount } : {},
7356
+ ...this.traceId !== void 0 ? { traceId: this.traceId } : {}
7357
+ });
7250
7358
  }
7251
7359
  }
7252
7360
  observeForSummary(event) {
@@ -7312,14 +7420,46 @@ var FileSessionWriter = class _FileSessionWriter {
7312
7420
  outcome: this.outcome ?? "completed"
7313
7421
  };
7314
7422
  if (this.manifestFile) {
7423
+ const t0 = Date.now();
7424
+ let outcome = "success";
7425
+ let errorMsg;
7315
7426
  try {
7316
7427
  await atomicWrite(this.manifestFile, JSON.stringify(this.summary), { mode: 384 });
7317
- } catch {
7428
+ } catch (err) {
7429
+ outcome = "failure";
7430
+ errorMsg = err instanceof Error ? err.message : String(err);
7431
+ } finally {
7432
+ this.events?.emit("storage.write", {
7433
+ sessionId: this.id,
7434
+ store: "session",
7435
+ filePath: this.manifestFile,
7436
+ operation: "close",
7437
+ outcome,
7438
+ durationMs: Date.now() - t0,
7439
+ ...errorMsg !== void 0 ? { error: errorMsg } : {},
7440
+ ...this.traceId !== void 0 ? { traceId: this.traceId } : {}
7441
+ });
7318
7442
  }
7319
7443
  }
7444
+ const idxT0 = Date.now();
7445
+ let idxOutcome = "success";
7446
+ let idxError;
7320
7447
  try {
7321
7448
  await this.onCloseCb?.(this.summary);
7322
- } catch {
7449
+ } catch (err) {
7450
+ idxOutcome = "failure";
7451
+ idxError = err instanceof Error ? err.message : String(err);
7452
+ } finally {
7453
+ this.events?.emit("storage.write", {
7454
+ sessionId: this.summary.id,
7455
+ store: "session",
7456
+ filePath: this.filePath,
7457
+ operation: "index_append",
7458
+ outcome: idxOutcome,
7459
+ durationMs: Date.now() - idxT0,
7460
+ ...idxError !== void 0 ? { error: idxError } : {},
7461
+ ...this.traceId !== void 0 ? { traceId: this.traceId } : {}
7462
+ });
7323
7463
  }
7324
7464
  try {
7325
7465
  await this.handle.close();
@@ -7481,23 +7621,81 @@ function userInputTitle(content) {
7481
7621
  init_atomic_write();
7482
7622
  var QueueStore = class {
7483
7623
  file;
7624
+ // Use `| undefined` (not `?`) so exactOptionalPropertyTypes doesn't
7625
+ // reject assigning an optional constructor parameter to these fields.
7626
+ events;
7627
+ traceId;
7484
7628
  constructor(opts) {
7485
7629
  this.file = path7.join(opts.dir, "queue.json");
7630
+ this.events = opts.events;
7631
+ this.traceId = opts.traceId;
7486
7632
  }
7487
7633
  async write(items) {
7634
+ const t0 = Date.now();
7488
7635
  if (items.length === 0) {
7489
7636
  await this.clear();
7490
7637
  return;
7491
7638
  }
7492
- await atomicWrite(this.file, JSON.stringify(items), { mode: 384 });
7639
+ try {
7640
+ await atomicWrite(this.file, JSON.stringify(items), { mode: 384 });
7641
+ this.events?.emit("storage.write", {
7642
+ sessionId: this.traceId ?? "~boot~",
7643
+ store: "queue",
7644
+ filePath: this.file,
7645
+ operation: "write",
7646
+ outcome: "success",
7647
+ durationMs: Date.now() - t0,
7648
+ ...this.traceId !== void 0 && { traceId: this.traceId }
7649
+ });
7650
+ } catch (err) {
7651
+ this.events?.emit("storage.error", {
7652
+ sessionId: this.traceId ?? "~boot~",
7653
+ store: "queue",
7654
+ filePath: this.file,
7655
+ operation: "write",
7656
+ outcome: "failure",
7657
+ error: err instanceof Error ? err.message : String(err),
7658
+ recoverable: false,
7659
+ ...this.traceId !== void 0 && { traceId: this.traceId }
7660
+ });
7661
+ console.warn(JSON.stringify({
7662
+ level: "warn",
7663
+ event: "queue_store.write_failed",
7664
+ path: this.file,
7665
+ message: err instanceof Error ? err.message : String(err),
7666
+ timestamp: (/* @__PURE__ */ new Date()).toISOString()
7667
+ }));
7668
+ }
7493
7669
  }
7494
7670
  async read() {
7671
+ const t0 = Date.now();
7495
7672
  let raw;
7496
7673
  try {
7497
7674
  raw = await fsp3.readFile(this.file, "utf8");
7498
7675
  } catch (err) {
7499
7676
  const code = err.code;
7500
- if (code === "ENOENT") return [];
7677
+ if (code === "ENOENT") {
7678
+ this.events?.emit("storage.read", {
7679
+ sessionId: this.traceId ?? "~boot~",
7680
+ store: "queue",
7681
+ filePath: this.file,
7682
+ operation: "read",
7683
+ outcome: "success",
7684
+ durationMs: Date.now() - t0,
7685
+ ...this.traceId !== void 0 && { traceId: this.traceId }
7686
+ });
7687
+ return [];
7688
+ }
7689
+ this.events?.emit("storage.error", {
7690
+ sessionId: this.traceId ?? "~boot~",
7691
+ store: "queue",
7692
+ filePath: this.file,
7693
+ operation: "read",
7694
+ outcome: "failure",
7695
+ error: err instanceof Error ? err.message : String(err),
7696
+ recoverable: true,
7697
+ ...this.traceId !== void 0 && { traceId: this.traceId }
7698
+ });
7501
7699
  console.warn(JSON.stringify({
7502
7700
  level: "warn",
7503
7701
  event: "queue_store.read_failed",
@@ -7511,9 +7709,40 @@ var QueueStore = class {
7511
7709
  try {
7512
7710
  parsed = JSON.parse(raw);
7513
7711
  } catch {
7712
+ this.events?.emit("storage.read", {
7713
+ sessionId: this.traceId ?? "~boot~",
7714
+ store: "queue",
7715
+ filePath: this.file,
7716
+ operation: "read",
7717
+ outcome: "failure",
7718
+ durationMs: Date.now() - t0,
7719
+ error: "parse_failed",
7720
+ ...this.traceId !== void 0 && { traceId: this.traceId }
7721
+ });
7722
+ return [];
7723
+ }
7724
+ if (!Array.isArray(parsed)) {
7725
+ this.events?.emit("storage.read", {
7726
+ sessionId: this.traceId ?? "~boot~",
7727
+ store: "queue",
7728
+ filePath: this.file,
7729
+ operation: "read",
7730
+ outcome: "failure",
7731
+ durationMs: Date.now() - t0,
7732
+ error: "invalid_schema",
7733
+ ...this.traceId !== void 0 && { traceId: this.traceId }
7734
+ });
7514
7735
  return [];
7515
7736
  }
7516
- if (!Array.isArray(parsed)) return [];
7737
+ this.events?.emit("storage.read", {
7738
+ sessionId: this.traceId ?? "~boot~",
7739
+ store: "queue",
7740
+ filePath: this.file,
7741
+ operation: "read",
7742
+ outcome: "success",
7743
+ durationMs: Date.now() - t0,
7744
+ ...this.traceId !== void 0 && { traceId: this.traceId }
7745
+ });
7517
7746
  const out = [];
7518
7747
  for (const v of parsed) {
7519
7748
  if (isPersistedQueueItem(v)) out.push(v);
@@ -7521,11 +7750,31 @@ var QueueStore = class {
7521
7750
  return out;
7522
7751
  }
7523
7752
  async clear() {
7753
+ const t0 = Date.now();
7524
7754
  try {
7525
7755
  await fsp3.unlink(this.file);
7756
+ this.events?.emit("storage.write", {
7757
+ sessionId: this.traceId ?? "~boot~",
7758
+ store: "queue",
7759
+ filePath: this.file,
7760
+ operation: "clear",
7761
+ outcome: "success",
7762
+ durationMs: Date.now() - t0,
7763
+ ...this.traceId !== void 0 && { traceId: this.traceId }
7764
+ });
7526
7765
  } catch (err) {
7527
7766
  const code = err.code;
7528
7767
  if (code === "ENOENT") return;
7768
+ this.events?.emit("storage.error", {
7769
+ sessionId: this.traceId ?? "~boot~",
7770
+ store: "queue",
7771
+ filePath: this.file,
7772
+ operation: "clear",
7773
+ outcome: "failure",
7774
+ error: err instanceof Error ? err.message : String(err),
7775
+ recoverable: true,
7776
+ ...this.traceId !== void 0 && { traceId: this.traceId }
7777
+ });
7529
7778
  console.warn(JSON.stringify({
7530
7779
  level: "warn",
7531
7780
  event: "queue_store.clear_failed",
@@ -7900,6 +8149,7 @@ var MAX_BYTES_TOTAL = 32e3;
7900
8149
  var DefaultMemoryStore = class {
7901
8150
  files;
7902
8151
  events;
8152
+ traceId;
7903
8153
  backend;
7904
8154
  /**
7905
8155
  * Per-scope serialization queue. `remember` / `forget` / `consolidate` /
@@ -7965,15 +8215,70 @@ var DefaultMemoryStore = class {
7965
8215
  if (writeErr2) {
7966
8216
  parts.push(`> \u26A0\uFE0F Memory write error (${labelOf(scope)}): ${writeErr2.message}`);
7967
8217
  }
7968
- const body = await this.backend.readAll(scope, this.files[scope]);
7969
- if (body.trim()) parts.push(`## ${labelOf(scope)}
8218
+ const t0 = Date.now();
8219
+ const filePath = this.files[scope];
8220
+ try {
8221
+ const body = await this.backend.readAll(scope, filePath);
8222
+ const dur = Date.now() - t0;
8223
+ this.events?.emit("storage.read", {
8224
+ sessionId: "~memory~",
8225
+ store: "memory",
8226
+ filePath,
8227
+ operation: "readAll",
8228
+ outcome: "success",
8229
+ durationMs: dur,
8230
+ ...this.traceId !== void 0 && { traceId: this.traceId }
8231
+ });
8232
+ if (body.trim()) parts.push(`## ${labelOf(scope)}
7970
8233
 
7971
8234
  ${body.trim()}`);
8235
+ } catch (err) {
8236
+ const dur = Date.now() - t0;
8237
+ this.events?.emit("storage.read", {
8238
+ sessionId: "~memory~",
8239
+ store: "memory",
8240
+ filePath,
8241
+ operation: "readAll",
8242
+ outcome: "failure",
8243
+ durationMs: dur,
8244
+ error: err instanceof Error ? err.message : String(err),
8245
+ ...this.traceId !== void 0 && { traceId: this.traceId }
8246
+ });
8247
+ throw err;
8248
+ }
7972
8249
  }
7973
8250
  return parts.join("\n\n");
7974
8251
  }
7975
8252
  async read(scope) {
7976
- return this.backend.readAll(scope, this.files[scope]);
8253
+ const t0 = Date.now();
8254
+ const filePath = this.files[scope];
8255
+ try {
8256
+ const body = await this.backend.readAll(scope, filePath);
8257
+ const dur = Date.now() - t0;
8258
+ this.events?.emit("storage.read", {
8259
+ sessionId: "~memory~",
8260
+ store: "memory",
8261
+ filePath,
8262
+ operation: "read",
8263
+ outcome: "success",
8264
+ durationMs: dur,
8265
+ ...this.traceId !== void 0 && { traceId: this.traceId }
8266
+ });
8267
+ return body;
8268
+ } catch (err) {
8269
+ const dur = Date.now() - t0;
8270
+ this.events?.emit("storage.read", {
8271
+ sessionId: "~memory~",
8272
+ store: "memory",
8273
+ filePath,
8274
+ operation: "read",
8275
+ outcome: "failure",
8276
+ durationMs: dur,
8277
+ error: err instanceof Error ? err.message : String(err),
8278
+ ...this.traceId !== void 0 && { traceId: this.traceId }
8279
+ });
8280
+ throw err;
8281
+ }
7977
8282
  }
7978
8283
  /**
7979
8284
  * List entries from a scope, newest first. Delegates to the backend
@@ -7999,7 +8304,34 @@ ${body.trim()}`);
7999
8304
  const ts = (/* @__PURE__ */ new Date()).toISOString();
8000
8305
  return this.runSerialized(scope, async () => {
8001
8306
  const entry = { scope, text, ts, ...metadata };
8002
- await this.backend.remember(scope, entry, this.files[scope]);
8307
+ const filePath = this.files[scope];
8308
+ const t0 = Date.now();
8309
+ try {
8310
+ await this.backend.remember(scope, entry, filePath);
8311
+ const dur = Date.now() - t0;
8312
+ this.events?.emit("storage.write", {
8313
+ sessionId: "~memory~",
8314
+ store: "memory",
8315
+ filePath,
8316
+ operation: "remember",
8317
+ outcome: "success",
8318
+ durationMs: dur,
8319
+ ...this.traceId !== void 0 && { traceId: this.traceId }
8320
+ });
8321
+ } catch (err) {
8322
+ const dur = Date.now() - t0;
8323
+ this.events?.emit("storage.write", {
8324
+ sessionId: "~memory~",
8325
+ store: "memory",
8326
+ filePath,
8327
+ operation: "remember",
8328
+ outcome: "failure",
8329
+ durationMs: dur,
8330
+ error: err instanceof Error ? err.message : String(err),
8331
+ ...this.traceId !== void 0 && { traceId: this.traceId }
8332
+ });
8333
+ throw err;
8334
+ }
8003
8335
  const raw = await this.backend.readAll(scope, this.files[scope]);
8004
8336
  if (Buffer.byteLength(raw, "utf8") > MAX_BYTES_TOTAL) {
8005
8337
  const removed = await this.backend.consolidate(scope, this.files[scope]);
@@ -8127,7 +8459,35 @@ ${body.trim()}`);
8127
8459
  }
8128
8460
  async forget(query, scope = "project-memory") {
8129
8461
  return this.runSerialized(scope, async () => {
8130
- const removed = await this.backend.forget(scope, query, this.files[scope]);
8462
+ const filePath = this.files[scope];
8463
+ const t0 = Date.now();
8464
+ let removed = 0;
8465
+ try {
8466
+ removed = await this.backend.forget(scope, query, filePath);
8467
+ const dur = Date.now() - t0;
8468
+ this.events?.emit("storage.write", {
8469
+ sessionId: "~memory~",
8470
+ store: "memory",
8471
+ filePath,
8472
+ operation: "forget",
8473
+ outcome: "success",
8474
+ durationMs: dur,
8475
+ ...this.traceId !== void 0 && { traceId: this.traceId }
8476
+ });
8477
+ } catch (err) {
8478
+ const dur = Date.now() - t0;
8479
+ this.events?.emit("storage.write", {
8480
+ sessionId: "~memory~",
8481
+ store: "memory",
8482
+ filePath,
8483
+ operation: "forget",
8484
+ outcome: "failure",
8485
+ durationMs: dur,
8486
+ error: err instanceof Error ? err.message : String(err),
8487
+ ...this.traceId !== void 0 && { traceId: this.traceId }
8488
+ });
8489
+ throw err;
8490
+ }
8131
8491
  if (removed > 0) {
8132
8492
  this.events?.emit("memory.forgotten", {
8133
8493
  scope,
@@ -8141,7 +8501,35 @@ ${body.trim()}`);
8141
8501
  }
8142
8502
  async consolidate(scope) {
8143
8503
  return this.runSerialized(scope, async () => {
8144
- const removed = await this.backend.consolidate(scope, this.files[scope]);
8504
+ const filePath = this.files[scope];
8505
+ const t0 = Date.now();
8506
+ let removed = 0;
8507
+ try {
8508
+ removed = await this.backend.consolidate(scope, filePath);
8509
+ const dur = Date.now() - t0;
8510
+ this.events?.emit("storage.write", {
8511
+ sessionId: "~memory~",
8512
+ store: "memory",
8513
+ filePath,
8514
+ operation: "consolidate",
8515
+ outcome: "success",
8516
+ durationMs: dur,
8517
+ ...this.traceId !== void 0 && { traceId: this.traceId }
8518
+ });
8519
+ } catch (err) {
8520
+ const dur = Date.now() - t0;
8521
+ this.events?.emit("storage.write", {
8522
+ sessionId: "~memory~",
8523
+ store: "memory",
8524
+ filePath,
8525
+ operation: "consolidate",
8526
+ outcome: "failure",
8527
+ durationMs: dur,
8528
+ error: err instanceof Error ? err.message : String(err),
8529
+ ...this.traceId !== void 0 && { traceId: this.traceId }
8530
+ });
8531
+ throw err;
8532
+ }
8145
8533
  if (removed > 0) {
8146
8534
  this.events?.emit("memory.consolidated", {
8147
8535
  scope,
@@ -8154,7 +8542,34 @@ ${body.trim()}`);
8154
8542
  async clear(scope) {
8155
8543
  if (scope) {
8156
8544
  await this.runSerialized(scope, async () => {
8157
- await this.backend.clear(scope, this.files[scope]);
8545
+ const filePath = this.files[scope];
8546
+ const t0 = Date.now();
8547
+ try {
8548
+ await this.backend.clear(scope, filePath);
8549
+ const dur = Date.now() - t0;
8550
+ this.events?.emit("storage.write", {
8551
+ sessionId: "~memory~",
8552
+ store: "memory",
8553
+ filePath,
8554
+ operation: "clear",
8555
+ outcome: "success",
8556
+ durationMs: dur,
8557
+ ...this.traceId !== void 0 && { traceId: this.traceId }
8558
+ });
8559
+ } catch (err) {
8560
+ const dur = Date.now() - t0;
8561
+ this.events?.emit("storage.write", {
8562
+ sessionId: "~memory~",
8563
+ store: "memory",
8564
+ filePath,
8565
+ operation: "clear",
8566
+ outcome: "failure",
8567
+ durationMs: dur,
8568
+ error: err instanceof Error ? err.message : String(err),
8569
+ ...this.traceId !== void 0 && { traceId: this.traceId }
8570
+ });
8571
+ throw err;
8572
+ }
8158
8573
  this.events?.emit("memory.cleared", { scope });
8159
8574
  await this.mirrorBackup(scope);
8160
8575
  });
@@ -8162,15 +8577,54 @@ ${body.trim()}`);
8162
8577
  }
8163
8578
  await Promise.all(
8164
8579
  ["project-agents", "project-memory", "user-memory"].map(
8165
- (s) => this.runSerialized(s, async () => {
8166
- await this.backend.clear(s, this.files[s]);
8580
+ async (s) => this.runSerialized(s, async () => {
8581
+ const filePath = this.files[s];
8582
+ const t0 = Date.now();
8583
+ try {
8584
+ await this.backend.clear(s, filePath);
8585
+ const dur = Date.now() - t0;
8586
+ this.events?.emit("storage.write", {
8587
+ sessionId: "~memory~",
8588
+ store: "memory",
8589
+ filePath,
8590
+ operation: "clear",
8591
+ outcome: "success",
8592
+ durationMs: dur,
8593
+ ...this.traceId !== void 0 && { traceId: this.traceId }
8594
+ });
8595
+ } catch (err) {
8596
+ const dur = Date.now() - t0;
8597
+ this.events?.emit("storage.write", {
8598
+ sessionId: "~memory~",
8599
+ store: "memory",
8600
+ filePath,
8601
+ operation: "clear",
8602
+ outcome: "failure",
8603
+ durationMs: dur,
8604
+ error: err instanceof Error ? err.message : String(err),
8605
+ ...this.traceId !== void 0 && { traceId: this.traceId }
8606
+ });
8607
+ throw err;
8608
+ }
8167
8609
  this.events?.emit("memory.cleared", { scope: s });
8168
8610
  await this.mirrorBackup(s);
8169
8611
  })
8170
8612
  )
8171
8613
  );
8172
8614
  }
8173
- /** Mirror current memory content to the persistent backup directory. */
8615
+ /**
8616
+ * Return a new MemoryStore proxy that carries `traceId` on every storage
8617
+ * event. The original store is left unchanged — callers that need a
8618
+ * trace-decorated view (e.g. session-run tools) receive the proxy while
8619
+ * the singleton remains trace-free for boot-time use.
8620
+ *
8621
+ * The proxy implements the full `MemoryStore` interface; all other
8622
+ * properties (backend, etc.) are delegated to the original store.
8623
+ */
8624
+ withTraceId(traceId) {
8625
+ this.traceId = traceId;
8626
+ return this;
8627
+ }
8174
8628
  async mirrorBackup(scope) {
8175
8629
  if (!this.persistBackup || scope === "project-agents") return;
8176
8630
  try {
@@ -8264,6 +8718,13 @@ function deepFreeze(obj) {
8264
8718
  return Object.freeze(obj);
8265
8719
  }
8266
8720
  init_atomic_write();
8721
+ function storageErrorString(err) {
8722
+ if (err instanceof Error) {
8723
+ const code = err.code;
8724
+ return code ? `${code}: ${err.message}` : err.message;
8725
+ }
8726
+ return String(err);
8727
+ }
8267
8728
  var BEHAVIOR_DEFAULTS = {
8268
8729
  version: 1,
8269
8730
  context: {
@@ -8366,11 +8827,15 @@ var DefaultConfigLoader = class {
8366
8827
  strict;
8367
8828
  vault;
8368
8829
  extraSources;
8830
+ events;
8831
+ traceId;
8369
8832
  constructor(opts) {
8370
8833
  this.paths = opts.paths;
8371
8834
  this.strict = opts.strict ?? false;
8372
8835
  this.vault = opts.vault;
8373
8836
  this.extraSources = opts.sources ?? [];
8837
+ this.events = opts.events;
8838
+ this.traceId = opts.traceId;
8374
8839
  }
8375
8840
  async load(opts = {}) {
8376
8841
  let cfg = { ...BEHAVIOR_DEFAULTS };
@@ -8447,7 +8912,33 @@ var DefaultConfigLoader = class {
8447
8912
  if (this.vault && toWrite.githubToken && !toWrite.githubToken.startsWith("enc:")) {
8448
8913
  toWrite = { ...toWrite, githubToken: this.vault.encrypt(toWrite.githubToken) };
8449
8914
  }
8450
- await atomicWrite(this.paths.syncConfig, JSON.stringify(toWrite, null, 2), { mode: 384 });
8915
+ const fp = this.paths.syncConfig;
8916
+ const t0 = Date.now();
8917
+ try {
8918
+ await atomicWrite(fp, JSON.stringify(toWrite, null, 2), { mode: 384 });
8919
+ this.events?.emit("storage.write", {
8920
+ sessionId: "~config~",
8921
+ store: "config",
8922
+ filePath: fp,
8923
+ operation: "persist_sync",
8924
+ outcome: "success",
8925
+ durationMs: Date.now() - t0,
8926
+ ...this.traceId !== void 0 ? { traceId: this.traceId } : {}
8927
+ });
8928
+ } catch (err) {
8929
+ this.events?.emit("storage.error", {
8930
+ sessionId: "~config~",
8931
+ store: "config",
8932
+ filePath: fp,
8933
+ operation: "persist_sync",
8934
+ outcome: "failure",
8935
+ error: storageErrorString(err),
8936
+ recoverable: false,
8937
+ durationMs: Date.now() - t0,
8938
+ ...this.traceId !== void 0 ? { traceId: this.traceId } : {}
8939
+ });
8940
+ throw err;
8941
+ }
8451
8942
  }
8452
8943
  /**
8453
8944
  * Read ~/.wrongstack/sync.json (encrypted GitHub token storage) and decrypt
@@ -8456,17 +8947,60 @@ var DefaultConfigLoader = class {
8456
8947
  * isolated — it should never be part of project-local or env-driven config.
8457
8948
  */
8458
8949
  async loadSyncConfig() {
8950
+ const fp = this.paths.syncConfig;
8951
+ const t0 = Date.now();
8459
8952
  try {
8460
- const raw = await fsp3.readFile(this.paths.syncConfig, "utf8");
8953
+ const raw = await fsp3.readFile(fp, "utf8");
8461
8954
  const parsed = safeParse(raw);
8462
- if (!parsed.ok || !parsed.value) return null;
8955
+ if (!parsed.ok || !parsed.value) {
8956
+ this.events?.emit("storage.read", {
8957
+ sessionId: "~config~",
8958
+ store: "config",
8959
+ filePath: fp,
8960
+ operation: "load_sync",
8961
+ outcome: "failure",
8962
+ durationMs: Date.now() - t0,
8963
+ error: "parse error or empty file",
8964
+ ...this.traceId !== void 0 ? { traceId: this.traceId } : {}
8965
+ });
8966
+ return null;
8967
+ }
8463
8968
  if (this.vault) {
8464
8969
  const decrypted = decryptConfigSecrets({ sync: parsed.value }, this.vault);
8465
- return decrypted.sync ?? null;
8970
+ const result = decrypted.sync ?? null;
8971
+ this.events?.emit("storage.read", {
8972
+ sessionId: "~config~",
8973
+ store: "config",
8974
+ filePath: fp,
8975
+ operation: "load_sync",
8976
+ outcome: "success",
8977
+ durationMs: Date.now() - t0,
8978
+ ...this.traceId !== void 0 ? { traceId: this.traceId } : {}
8979
+ });
8980
+ return result;
8466
8981
  }
8982
+ this.events?.emit("storage.read", {
8983
+ sessionId: "~config~",
8984
+ store: "config",
8985
+ filePath: fp,
8986
+ operation: "load_sync",
8987
+ outcome: "success",
8988
+ durationMs: Date.now() - t0,
8989
+ ...this.traceId !== void 0 ? { traceId: this.traceId } : {}
8990
+ });
8467
8991
  return parsed.value;
8468
8992
  } catch (err) {
8469
8993
  if (err.code === "ENOENT") return null;
8994
+ this.events?.emit("storage.read", {
8995
+ sessionId: "~config~",
8996
+ store: "config",
8997
+ filePath: fp,
8998
+ operation: "load_sync",
8999
+ outcome: "failure",
9000
+ durationMs: Date.now() - t0,
9001
+ error: storageErrorString(err),
9002
+ ...this.traceId !== void 0 ? { traceId: this.traceId } : {}
9003
+ });
8470
9004
  console.warn(JSON.stringify({
8471
9005
  level: "warn",
8472
9006
  event: "config.sync_load_failed",
@@ -8478,10 +9012,21 @@ var DefaultConfigLoader = class {
8478
9012
  }
8479
9013
  async readJson(file) {
8480
9014
  let raw;
9015
+ const t0 = Date.now();
8481
9016
  try {
8482
9017
  raw = await fsp3.readFile(file, "utf8");
8483
9018
  } catch (err) {
8484
9019
  if (err.code !== "ENOENT") {
9020
+ this.events?.emit("storage.read", {
9021
+ sessionId: "~config~",
9022
+ store: "config",
9023
+ filePath: file,
9024
+ operation: "read_json",
9025
+ outcome: "failure",
9026
+ durationMs: Date.now() - t0,
9027
+ error: storageErrorString(err),
9028
+ ...this.traceId !== void 0 ? { traceId: this.traceId } : {}
9029
+ });
8485
9030
  console.warn(JSON.stringify({
8486
9031
  level: "warn",
8487
9032
  event: "config.read_failed",
@@ -8494,6 +9039,16 @@ var DefaultConfigLoader = class {
8494
9039
  }
8495
9040
  const parsed = safeParse(raw);
8496
9041
  if (!parsed.ok || !parsed.value) {
9042
+ this.events?.emit("storage.read", {
9043
+ sessionId: "~config~",
9044
+ store: "config",
9045
+ filePath: file,
9046
+ operation: "read_json",
9047
+ outcome: "failure",
9048
+ durationMs: Date.now() - t0,
9049
+ error: "parse error or empty file",
9050
+ ...this.traceId !== void 0 ? { traceId: this.traceId } : {}
9051
+ });
8497
9052
  console.warn(JSON.stringify({
8498
9053
  level: "warn",
8499
9054
  event: "config.parse_failed",
@@ -8969,24 +9524,66 @@ function resolveSessionLoggingConfig(cfg) {
8969
9524
 
8970
9525
  // src/storage/todos-checkpoint.ts
8971
9526
  init_atomic_write();
8972
- async function loadTodosCheckpoint(filePath) {
9527
+ async function loadTodosCheckpoint(filePath, events, traceId) {
9528
+ const t0 = Date.now();
8973
9529
  let raw;
8974
9530
  try {
8975
9531
  raw = await fsp3.readFile(filePath, "utf8");
8976
- } catch {
9532
+ } catch (err) {
9533
+ events?.emit("storage.error", {
9534
+ sessionId: traceId ?? "~boot~",
9535
+ store: "todos",
9536
+ filePath,
9537
+ operation: "load",
9538
+ outcome: "failure",
9539
+ error: err instanceof Error ? err.message : String(err),
9540
+ recoverable: true
9541
+ });
8977
9542
  return null;
8978
9543
  }
8979
9544
  try {
8980
9545
  const parsed = JSON.parse(raw);
8981
- if (parsed?.version !== 1 || !Array.isArray(parsed.todos)) return null;
9546
+ if (parsed?.version !== 1 || !Array.isArray(parsed.todos)) {
9547
+ events?.emit("storage.read", {
9548
+ sessionId: traceId ?? "~boot~",
9549
+ store: "todos",
9550
+ filePath,
9551
+ operation: "load",
9552
+ outcome: "failure",
9553
+ durationMs: Date.now() - t0,
9554
+ error: "invalid_schema",
9555
+ ...traceId !== void 0 && { traceId }
9556
+ });
9557
+ return null;
9558
+ }
9559
+ events?.emit("storage.read", {
9560
+ sessionId: traceId ?? "~boot~",
9561
+ store: "todos",
9562
+ filePath,
9563
+ operation: "load",
9564
+ outcome: "success",
9565
+ durationMs: Date.now() - t0,
9566
+ ...traceId !== void 0 && { traceId }
9567
+ });
8982
9568
  return parsed.todos.filter(
8983
9569
  (t2) => !!t2 && typeof t2.id === "string" && typeof t2.content === "string" && typeof t2.status === "string" && (t2.activeForm === void 0 || typeof t2.activeForm === "string")
8984
9570
  );
8985
9571
  } catch {
9572
+ events?.emit("storage.read", {
9573
+ sessionId: traceId ?? "~boot~",
9574
+ store: "todos",
9575
+ filePath,
9576
+ operation: "load",
9577
+ outcome: "failure",
9578
+ durationMs: Date.now() - t0,
9579
+ error: "parse_failed",
9580
+ ...traceId !== void 0 && { traceId }
9581
+ });
8986
9582
  return null;
8987
9583
  }
8988
9584
  }
8989
- async function saveTodosCheckpoint(filePath, sessionId, todos) {
9585
+ async function saveTodosCheckpoint(filePath, sessionId, todos, events, traceId) {
9586
+ const t0 = Date.now();
8990
9587
  const payload = {
8991
9588
  version: 1,
8992
9589
  sessionId,
@@ -8995,7 +9592,25 @@ async function saveTodosCheckpoint(filePath, sessionId, todos) {
8995
9592
  };
8996
9593
  try {
8997
9594
  await atomicWrite(filePath, JSON.stringify(payload, null, 2), { mode: 384 });
9595
+ events?.emit("storage.write", {
9596
+ sessionId: traceId ?? sessionId,
9597
+ store: "todos",
9598
+ filePath,
9599
+ operation: "save",
9600
+ outcome: "success",
9601
+ durationMs: Date.now() - t0,
9602
+ ...traceId !== void 0 && { traceId }
9603
+ });
8998
9604
  } catch (err) {
9605
+ events?.emit("storage.error", {
9606
+ sessionId: traceId ?? sessionId,
9607
+ store: "todos",
9608
+ filePath,
9609
+ operation: "save",
9610
+ outcome: "failure",
9611
+ error: err instanceof Error ? err.message : String(err),
9612
+ recoverable: false
9613
+ });
8999
9614
  console.warn(JSON.stringify({
9000
9615
  level: "warn",
9001
9616
  event: "todos_checkpoint.save_failed",
@@ -9004,12 +9619,12 @@ async function saveTodosCheckpoint(filePath, sessionId, todos) {
9004
9619
  }));
9005
9620
  }
9006
9621
  }
9007
- function attachTodosCheckpoint(state, filePath, sessionId) {
9622
+ function attachTodosCheckpoint(state, filePath, sessionId, events, traceId) {
9008
9623
  let timer = null;
9009
9624
  let pending = null;
9010
9625
  let writeChain = Promise.resolve();
9011
9626
  const enqueueWrite = (todos) => {
9012
- writeChain = writeChain.then(() => saveTodosCheckpoint(filePath, sessionId, todos)).catch((err) => {
9627
+ writeChain = writeChain.then(() => saveTodosCheckpoint(filePath, sessionId, todos, events, traceId)).catch((err) => {
9013
9628
  const msg = err instanceof Error ? err.message : String(err);
9014
9629
  console.error(JSON.stringify({
9015
9630
  level: "error",
@@ -9051,25 +9666,79 @@ function attachTodosCheckpoint(state, filePath, sessionId) {
9051
9666
 
9052
9667
  // src/storage/plan-store.ts
9053
9668
  init_atomic_write();
9054
- async function loadPlan(filePath) {
9669
+ async function loadPlan(filePath, events) {
9670
+ const t0 = Date.now();
9055
9671
  let raw;
9056
9672
  try {
9057
9673
  raw = await fsp3.readFile(filePath, "utf8");
9058
- } catch {
9674
+ } catch (err) {
9675
+ events?.emit("storage.error", {
9676
+ sessionId: "~boot~",
9677
+ store: "plan",
9678
+ filePath,
9679
+ operation: "load",
9680
+ error: err instanceof Error ? err.message : String(err),
9681
+ recoverable: true
9682
+ });
9059
9683
  return null;
9060
9684
  }
9061
9685
  try {
9062
9686
  const parsed = JSON.parse(raw);
9063
- if (parsed?.version !== 1 || !Array.isArray(parsed.items)) return null;
9687
+ if (parsed?.version !== 1 || !Array.isArray(parsed.items)) {
9688
+ events?.emit("storage.read", {
9689
+ sessionId: "~boot~",
9690
+ store: "plan",
9691
+ filePath,
9692
+ operation: "load",
9693
+ outcome: "failure",
9694
+ durationMs: Date.now() - t0,
9695
+ error: "invalid_schema"
9696
+ });
9697
+ return null;
9698
+ }
9699
+ events?.emit("storage.read", {
9700
+ sessionId: "~boot~",
9701
+ store: "plan",
9702
+ filePath,
9703
+ operation: "load",
9704
+ outcome: "success",
9705
+ durationMs: Date.now() - t0
9706
+ });
9064
9707
  return parsed;
9065
9708
  } catch {
9709
+ events?.emit("storage.read", {
9710
+ sessionId: "~boot~",
9711
+ store: "plan",
9712
+ filePath,
9713
+ operation: "load",
9714
+ outcome: "failure",
9715
+ durationMs: Date.now() - t0,
9716
+ error: "parse_failed"
9717
+ });
9066
9718
  return null;
9067
9719
  }
9068
9720
  }
9069
- async function savePlan(filePath, plan) {
9721
+ async function savePlan(filePath, plan, events) {
9722
+ const t0 = Date.now();
9070
9723
  try {
9071
9724
  await atomicWrite(filePath, JSON.stringify(plan, null, 2), { mode: 384 });
9725
+ events?.emit("storage.write", {
9726
+ sessionId: "~boot~",
9727
+ store: "plan",
9728
+ filePath,
9729
+ operation: "save",
9730
+ outcome: "success",
9731
+ durationMs: Date.now() - t0
9732
+ });
9072
9733
  } catch (err) {
9734
+ events?.emit("storage.error", {
9735
+ sessionId: "~boot~",
9736
+ store: "plan",
9737
+ filePath,
9738
+ operation: "save",
9739
+ error: err instanceof Error ? err.message : String(err),
9740
+ recoverable: false
9741
+ });
9073
9742
  console.warn(
9074
9743
  "[plan-store] save failed:",
9075
9744
  err instanceof Error ? err.message : String(err)
@@ -9753,7 +10422,16 @@ var DefaultPermissionPolicy = class {
9753
10422
  };
9754
10423
  }
9755
10424
  }
9756
- if (tool.permission === "auto" && !tool.mutating) {
10425
+ const hasWriteCap = hasCapability(tool, ToolCapabilities.FS_WRITE);
10426
+ const hasShellCap = hasCapability(tool, [
10427
+ ToolCapabilities.SHELL_ARBITRARY,
10428
+ ToolCapabilities.SHELL_RESTRICTED
10429
+ ]);
10430
+ const hasInstallCap = hasCapability(tool, ToolCapabilities.PACKAGE_INSTALL);
10431
+ const hasConfigCap = hasCapability(tool, ToolCapabilities.CONFIG_MUTATE);
10432
+ const hasSubagentCap = hasCapability(tool, ToolCapabilities.SUBAGENT_SPAWN);
10433
+ const isMutating = tool.mutating || hasWriteCap || hasShellCap || hasInstallCap || hasConfigCap || hasSubagentCap;
10434
+ if (tool.permission === "auto" && !isMutating) {
9757
10435
  const decision = { permission: "auto", source: "default" };
9758
10436
  this._evalCache.set(cacheKey, decision);
9759
10437
  return decision;
@@ -9772,7 +10450,27 @@ var DefaultPermissionPolicy = class {
9772
10450
  }
9773
10451
  return { permission: "confirm", source: "default" };
9774
10452
  }
10453
+ // Capability-based destructive check (preferred over name-based)
10454
+ isDestructiveByCapability(tool) {
10455
+ const caps = tool.capabilities ?? [];
10456
+ if (caps.includes("shell.arbitrary")) return true;
10457
+ if (caps.includes("fs.write")) return true;
10458
+ if (caps.includes("fs.write.outside-project")) return true;
10459
+ return false;
10460
+ }
9775
10461
  isDestructiveYoloCall(tool, input, ctx) {
10462
+ if (this.isDestructiveByCapability(tool)) {
10463
+ if (tool.name === "bash") {
10464
+ const command = getInputString(input, "command");
10465
+ return command ? isClearlyDestructiveBashCommand(command, ctx.projectRoot) : true;
10466
+ }
10467
+ if (tool.name === "write" || tool.name === "edit" || tool.name === "replace" || tool.name === "patch") {
10468
+ const targetPath = getInputString(input, "path") ?? getInputString(input, "file");
10469
+ if (!targetPath || !ctx.projectRoot) return false;
10470
+ return !pathLooksInsideProject(targetPath, ctx.projectRoot);
10471
+ }
10472
+ return true;
10473
+ }
9776
10474
  if (tool.name === "bash") {
9777
10475
  const command = getInputString(input, "command");
9778
10476
  return command ? isClearlyDestructiveBashCommand(command, ctx.projectRoot) : true;
@@ -11441,13 +12139,32 @@ var MAX_JOURNAL_ENTRIES = 500;
11441
12139
  function goalFilePath(projectRoot) {
11442
12140
  return resolveWstackPaths({ projectRoot }).projectGoal;
11443
12141
  }
11444
- async function loadGoal(filePath) {
12142
+ async function loadGoal(filePath, events) {
12143
+ const t0 = Date.now();
11445
12144
  let raw;
11446
12145
  try {
11447
12146
  raw = await fsp3.readFile(filePath, "utf8");
11448
12147
  } catch (err) {
11449
12148
  const code = err.code;
11450
- if (code === "ENOENT") return null;
12149
+ if (code === "ENOENT") {
12150
+ events?.emit("storage.read", {
12151
+ sessionId: "~boot~",
12152
+ store: "goal",
12153
+ filePath,
12154
+ operation: "load",
12155
+ outcome: "success",
12156
+ durationMs: Date.now() - t0
12157
+ });
12158
+ return null;
12159
+ }
12160
+ events?.emit("storage.error", {
12161
+ sessionId: "~boot~",
12162
+ store: "goal",
12163
+ filePath,
12164
+ operation: "load",
12165
+ error: err instanceof Error ? err.message : String(err),
12166
+ recoverable: false
12167
+ });
11451
12168
  throw err;
11452
12169
  }
11453
12170
  try {
@@ -11460,8 +12177,25 @@ async function loadGoal(filePath) {
11460
12177
  message: "invalid schema \u2014 consider deleting and re-creating",
11461
12178
  timestamp: (/* @__PURE__ */ new Date()).toISOString()
11462
12179
  }));
12180
+ events?.emit("storage.read", {
12181
+ sessionId: "~boot~",
12182
+ store: "goal",
12183
+ filePath,
12184
+ operation: "load",
12185
+ outcome: "failure",
12186
+ durationMs: Date.now() - t0,
12187
+ error: "invalid_schema"
12188
+ });
11463
12189
  return null;
11464
12190
  }
12191
+ events?.emit("storage.read", {
12192
+ sessionId: "~boot~",
12193
+ store: "goal",
12194
+ filePath,
12195
+ operation: "load",
12196
+ outcome: "success",
12197
+ durationMs: Date.now() - t0
12198
+ });
11465
12199
  return parsed;
11466
12200
  } catch {
11467
12201
  console.warn(JSON.stringify({
@@ -11471,13 +12205,39 @@ async function loadGoal(filePath) {
11471
12205
  message: "JSON parse failed \u2014 consider deleting and re-creating",
11472
12206
  timestamp: (/* @__PURE__ */ new Date()).toISOString()
11473
12207
  }));
12208
+ events?.emit("storage.read", {
12209
+ sessionId: "~boot~",
12210
+ store: "goal",
12211
+ filePath,
12212
+ operation: "load",
12213
+ outcome: "failure",
12214
+ durationMs: Date.now() - t0,
12215
+ error: "parse_failed"
12216
+ });
11474
12217
  return null;
11475
12218
  }
11476
12219
  }
11477
- async function saveGoal(filePath, goal) {
12220
+ async function saveGoal(filePath, goal, events) {
12221
+ const t0 = Date.now();
11478
12222
  try {
11479
12223
  await atomicWrite(filePath, JSON.stringify(goal, null, 2), { mode: 384 });
12224
+ events?.emit("storage.write", {
12225
+ sessionId: "~boot~",
12226
+ store: "goal",
12227
+ filePath,
12228
+ operation: "save",
12229
+ outcome: "success",
12230
+ durationMs: Date.now() - t0
12231
+ });
11480
12232
  } catch (err) {
12233
+ events?.emit("storage.error", {
12234
+ sessionId: "~boot~",
12235
+ store: "goal",
12236
+ filePath,
12237
+ operation: "save",
12238
+ error: err instanceof Error ? err.message : String(err),
12239
+ recoverable: false
12240
+ });
11481
12241
  throw new FsError({
11482
12242
  message: err instanceof Error ? err.message : String(err),
11483
12243
  code: ERROR_CODES.FS_ATOMIC_WRITE_FAILED,
@@ -11923,7 +12683,7 @@ var EternalAutonomyEngine = class {
11923
12683
  const emit = (stage) => {
11924
12684
  this.opts.onStage?.(stage);
11925
12685
  };
11926
- const goal = await loadGoal(this.goalPath);
12686
+ const goal = await loadGoal(this.goalPath, this.opts.events);
11927
12687
  if (!goal) {
11928
12688
  emit({ phase: "stopped" });
11929
12689
  this.stopRequested = true;
@@ -12027,7 +12787,7 @@ var EternalAutonomyEngine = class {
12027
12787
  emit({ phase: "reflect", status, note });
12028
12788
  let iterationIndex = 0;
12029
12789
  try {
12030
- const reloaded = await loadGoal(this.goalPath);
12790
+ const reloaded = await loadGoal(this.goalPath, this.opts.events);
12031
12791
  iterationIndex = reloaded?.iterations ?? 0;
12032
12792
  } catch {
12033
12793
  }
@@ -12347,12 +13107,12 @@ ${recentJournal}` : "No prior iterations.",
12347
13107
  }
12348
13108
  }
12349
13109
  async appendIterationEntry(entry) {
12350
- const current = await loadGoal(this.goalPath);
13110
+ const current = await loadGoal(this.goalPath, this.opts.events);
12351
13111
  if (!current) {
12352
13112
  return;
12353
13113
  }
12354
13114
  const updated = appendJournal(current, entry);
12355
- await saveGoal(this.goalPath, updated);
13115
+ await saveGoal(this.goalPath, updated, this.opts.events);
12356
13116
  }
12357
13117
  /**
12358
13118
  * Persistent per-todo failure counter. Skipped silently when the goal
@@ -12361,11 +13121,11 @@ ${recentJournal}` : "No prior iterations.",
12361
13121
  * the counter to rotate past stuck todos once they cross `todoMaxAttempts`.
12362
13122
  */
12363
13123
  async bumpTodoAttempt(todoId) {
12364
- const current = await loadGoal(this.goalPath);
13124
+ const current = await loadGoal(this.goalPath, this.opts.events);
12365
13125
  if (!current) return;
12366
13126
  const attempts = { ...current.todoAttempts ?? {} };
12367
13127
  attempts[todoId] = (attempts[todoId] ?? 0) + 1;
12368
- await saveGoal(this.goalPath, { ...current, todoAttempts: attempts });
13128
+ await saveGoal(this.goalPath, { ...current, todoAttempts: attempts }, this.opts.events);
12369
13129
  }
12370
13130
  /**
12371
13131
  * Flip the mission to `completed` and journal it. Called from two
@@ -12375,7 +13135,7 @@ ${recentJournal}` : "No prior iterations.",
12375
13135
  * goal is already `completed`.
12376
13136
  */
12377
13137
  async markGoalCompleted(action, note) {
12378
- const current = await loadGoal(this.goalPath);
13138
+ const current = await loadGoal(this.goalPath, this.opts.events);
12379
13139
  if (!current) return;
12380
13140
  if (current.goalState === "completed") return;
12381
13141
  const withFlag = { ...current, goalState: "completed" };
@@ -12385,7 +13145,7 @@ ${recentJournal}` : "No prior iterations.",
12385
13145
  status: "success",
12386
13146
  note: note.slice(0, 240)
12387
13147
  });
12388
- await saveGoal(this.goalPath, withEntry);
13148
+ await saveGoal(this.goalPath, withEntry, this.opts.events);
12389
13149
  this.opts.onEternalStop?.();
12390
13150
  }
12391
13151
  /**
@@ -12394,10 +13154,10 @@ ${recentJournal}` : "No prior iterations.",
12394
13154
  * `onEternalStop` so the REPL returns to normal mode.
12395
13155
  */
12396
13156
  async clearGoalManually(note) {
12397
- const current = await loadGoal(this.goalPath);
13157
+ const current = await loadGoal(this.goalPath, this.opts.events);
12398
13158
  if (current) {
12399
13159
  const abandoned = { ...current, goalState: "abandoned" };
12400
- await saveGoal(this.goalPath, abandoned);
13160
+ await saveGoal(this.goalPath, abandoned, this.opts.events);
12401
13161
  }
12402
13162
  try {
12403
13163
  const { unlink: unlink16 } = await import('fs/promises');
@@ -12473,16 +13233,16 @@ ${recentJournal}` : ""
12473
13233
  * Persist a progress update from the agent's [PROGRESS: N%] output.
12474
13234
  */
12475
13235
  async updateProgress(progress, note) {
12476
- const current = await loadGoal(this.goalPath);
13236
+ const current = await loadGoal(this.goalPath, this.opts.events);
12477
13237
  if (!current) return;
12478
13238
  const updated = recordProgress(current, progress, note);
12479
- await saveGoal(this.goalPath, updated);
13239
+ await saveGoal(this.goalPath, updated, this.opts.events);
12480
13240
  }
12481
13241
  async persistEngineState(state) {
12482
- const current = await loadGoal(this.goalPath);
13242
+ const current = await loadGoal(this.goalPath, this.opts.events);
12483
13243
  if (!current) return;
12484
13244
  if (current.engineState === state) return;
12485
- await saveGoal(this.goalPath, { ...current, engineState: state });
13245
+ await saveGoal(this.goalPath, { ...current, engineState: state }, this.opts.events);
12486
13246
  }
12487
13247
  };
12488
13248
 
@@ -12831,6 +13591,20 @@ var SubagentBudget = class _SubagentBudget {
12831
13591
  };
12832
13592
 
12833
13593
  // src/coordination/agent-subagent-runner.ts
13594
+ function withDisabledToolFiltering(factory) {
13595
+ return async (config) => {
13596
+ const result = await factory(config);
13597
+ const disabled = config.disabledTools ?? [];
13598
+ if (disabled.length === 0) return result;
13599
+ const registry = result.agent.tools;
13600
+ if (registry && typeof registry.unregister === "function") {
13601
+ for (const toolName of disabled) {
13602
+ registry.unregister(toolName);
13603
+ }
13604
+ }
13605
+ return result;
13606
+ };
13607
+ }
12834
13608
  function makeAgentSubagentRunner(opts) {
12835
13609
  const format = opts.formatTaskInput ?? defaultFormatTaskInput;
12836
13610
  return async (task, ctx) => {
@@ -16997,7 +17771,8 @@ var ParallelEternalEngine = class {
16997
17771
  doneCondition: { type: "all_tasks_done" }
16998
17772
  };
16999
17773
  this.coordinator = new DefaultMultiAgentCoordinator(config);
17000
- const runner = makeAgentSubagentRunner({ factory: this.agentFactory });
17774
+ const filteredFactory = withDisabledToolFiltering(this.agentFactory);
17775
+ const runner = makeAgentSubagentRunner({ factory: filteredFactory });
17001
17776
  this.coordinator.setRunner?.(runner);
17002
17777
  try {
17003
17778
  while (!this.stopRequested) {
@@ -17032,7 +17807,7 @@ var ParallelEternalEngine = class {
17032
17807
  this.opts.onStage?.(stage);
17033
17808
  };
17034
17809
  this.iterations++;
17035
- const goal = await loadGoal(this.goalPath);
17810
+ const goal = await loadGoal(this.goalPath, this.opts.events);
17036
17811
  if (!goal) {
17037
17812
  this.stopRequested = true;
17038
17813
  emit({ phase: "stopped" });
@@ -17050,7 +17825,8 @@ var ParallelEternalEngine = class {
17050
17825
  doneCondition: { type: "all_tasks_done" }
17051
17826
  };
17052
17827
  this.coordinator = new DefaultMultiAgentCoordinator(config);
17053
- const runner = makeAgentSubagentRunner({ factory: this.agentFactory });
17828
+ const filteredFactory = withDisabledToolFiltering(this.agentFactory);
17829
+ const runner = makeAgentSubagentRunner({ factory: filteredFactory });
17054
17830
  this.coordinator.setRunner?.(runner);
17055
17831
  }
17056
17832
  emit({ phase: "decompose" });
@@ -17159,13 +17935,17 @@ ${personaLine}Task: ${task}
17159
17935
  role: route.role,
17160
17936
  tools: route.definition.config.tools,
17161
17937
  systemPromptOverride: route.definition.config.prompt,
17162
- timeoutMs: this.timeoutMs
17938
+ timeoutMs: this.timeoutMs,
17939
+ // Disable delegation — subagents are leaf workers, not orchestrators
17940
+ disabledTools: ["delegate"]
17163
17941
  } : {
17164
17942
  id: subagentId,
17165
17943
  name: `slot-${subagentId.slice(-6)}`,
17166
17944
  // Let the coordinator apply its default budget (roster or generic).
17167
17945
  // Hardcoding low limits here defeats the x10 budget improvement.
17168
- timeoutMs: this.timeoutMs
17946
+ timeoutMs: this.timeoutMs,
17947
+ // Disable delegation — subagents are leaf workers, not orchestrators
17948
+ disabledTools: ["delegate"]
17169
17949
  }
17170
17950
  );
17171
17951
  subagentIds.push(subagentId);
@@ -17311,10 +18091,10 @@ ${lastFew}` : "No prior iterations.",
17311
18091
  // Helpers
17312
18092
  // -------------------------------------------------------------------------
17313
18093
  async appendIterationEntry(entry) {
17314
- const current = await loadGoal(this.goalPath);
18094
+ const current = await loadGoal(this.goalPath, this.opts.events);
17315
18095
  if (!current) return;
17316
18096
  const updated = appendJournal(current, entry);
17317
- await saveGoal(this.goalPath, updated);
18097
+ await saveGoal(this.goalPath, updated, this.opts.events);
17318
18098
  const entryWithMeta = {
17319
18099
  at: (this.opts.now?.() ?? /* @__PURE__ */ new Date()).toISOString(),
17320
18100
  iteration: updated.iterations,
@@ -17326,10 +18106,10 @@ ${lastFew}` : "No prior iterations.",
17326
18106
  await this.appendIterationEntry({ source: "manual", task, status: "failure", note });
17327
18107
  }
17328
18108
  async persistState(state) {
17329
- const current = await loadGoal(this.goalPath);
18109
+ const current = await loadGoal(this.goalPath, this.opts.events);
17330
18110
  if (!current) return;
17331
18111
  if (current.engineState === state) return;
17332
- await saveGoal(this.goalPath, { ...current, engineState: state });
18112
+ await saveGoal(this.goalPath, { ...current, engineState: state }, this.opts.events);
17333
18113
  }
17334
18114
  };
17335
18115
 
@@ -18109,7 +18889,12 @@ Working rules:
18109
18889
  6. Never claim a subagent's work as your own without verifying it. If a
18110
18890
  result looks wrong, ask_subagent for clarification before passing it
18111
18891
  to the user.
18112
- 7. Wind down when satisfied. When the results are good enough, call
18892
+ 7. **Act on subagent mail immediately**. Subagent messages (result, ask,
18893
+ assign, note) are injected inline before every step \u2014 even mid-task.
18894
+ When you see one, address it before continuing: reply to asks, factor
18895
+ in results, act on assignments. Use \`mailbox action=ack\` to mark
18896
+ completed messages.
18897
+ 8. Wind down when satisfied. When the results are good enough, call
18113
18898
  work_complete \u2014 no new subagents will spawn and queued tasks complete
18114
18899
  as aborted. Running subagents finish naturally. Call terminate_subagent
18115
18900
  only for ones you need to stop immediately.`;
@@ -18126,6 +18911,13 @@ Bridge contract:
18126
18911
  structured, and self-contained \u2014 assume the Director will paste your
18127
18912
  output into its own context.
18128
18913
 
18914
+ CRITICAL CONSTRAINT \u2014 NO FURTHER DELEGATION:
18915
+ - You MUST NOT call the \`delegate\` tool or attempt to spawn subagents.
18916
+ - You MUST NOT use \`spawn_subagent\`, \`assign_task\`, or any equivalent.
18917
+ - Your role is to execute the assigned task yourself, not to orchestrate.
18918
+ - If a subtask is too complex, report back to the Director with what you
18919
+ found and let the Director decide how to decompose.
18920
+
18129
18921
  Inter-agent mailbox (if you have the \`mail_send\`/\`mail_inbox\`/\`mailbox\` tools):
18130
18922
  - You are part of a project-wide fleet that may span other terminals and
18131
18923
  WebUIs. Your mailbox identity is \`<your-name>@<session-tag>\` (unique
@@ -18140,7 +18932,12 @@ Inter-agent mailbox (if you have the \`mail_send\`/\`mail_inbox\`/\`mailbox\` to
18140
18932
  their exact id instead of doing everything yourself. Discover ids with
18141
18933
  \`mailbox action=online\`.
18142
18934
  - Answer your mail: reply to the sender's exact \`from\` id. When done with
18143
- an assigned task, post a \`result\` back to whoever assigned it.`;
18935
+ an assigned task, post a \`result\` back to whoever assigned it.
18936
+ - **Mail to the leader is always seen**: when you send \`ask\`, \`result\`,
18937
+ or \`assign\` to the director/leader, the message is injected inline into
18938
+ the leader's conversation before their next step \u2014 even if the leader is
18939
+ mid-task. Use \`mail_send\` to reliably reach the leader instead of
18940
+ waiting for them to check in.`;
18144
18941
  function composeDirectorPrompt(parts = {}) {
18145
18942
  const sections = [];
18146
18943
  const preamble = parts.directorPreamble ?? DEFAULT_DIRECTOR_PREAMBLE;
@@ -20549,6 +21346,7 @@ async function readSubagentPartial(opts, subagentId) {
20549
21346
  }
20550
21347
  function makeDirectorSessionFactory(opts) {
20551
21348
  const runId = opts.directorRunId ?? `${(/* @__PURE__ */ new Date()).toISOString().replace(/[:.]/g, "-")}-director`;
21349
+ const { traceId } = opts;
20552
21350
  let store;
20553
21351
  let dir;
20554
21352
  if (opts.store) {
@@ -20564,12 +21362,16 @@ function makeDirectorSessionFactory(opts) {
20564
21362
  dir,
20565
21363
  directorRunId: runId,
20566
21364
  async createSubagentSession({ subagentId, provider, model, title }) {
20567
- return store.create({
21365
+ const writer = await store.create({
20568
21366
  id: subagentId,
20569
21367
  title: title ?? subagentId,
20570
21368
  provider: provider ?? "unknown",
20571
21369
  model: model ?? "unknown"
20572
21370
  });
21371
+ if (traceId !== void 0) {
21372
+ writer.traceId = traceId;
21373
+ }
21374
+ return writer;
20573
21375
  }
20574
21376
  };
20575
21377
  }
@@ -23288,7 +24090,9 @@ var SddParallelRun = class {
23288
24090
  doneCondition: { type: "all_tasks_done" }
23289
24091
  };
23290
24092
  this.coordinator = new DefaultMultiAgentCoordinator(config);
23291
- const runner = makeAgentSubagentRunner({ factory: this.opts.subagentFactory ?? this.defaultFactory() });
24093
+ const baseFactory = this.opts.subagentFactory ?? this.defaultFactory();
24094
+ const filteredFactory = withDisabledToolFiltering(baseFactory);
24095
+ const runner = makeAgentSubagentRunner({ factory: filteredFactory });
23292
24096
  this.coordinator.setRunner?.(runner);
23293
24097
  }
23294
24098
  defaultFactory() {
@@ -23330,7 +24134,9 @@ var SddParallelRun = class {
23330
24134
  id: subagentId,
23331
24135
  name: subagentId,
23332
24136
  role: "executor",
23333
- timeoutMs: this.timeoutMs
24137
+ timeoutMs: this.timeoutMs,
24138
+ // Disable delegation — subagents are leaf workers, not orchestrators
24139
+ disabledTools: ["delegate"]
23334
24140
  })
23335
24141
  );
23336
24142
  const spawnResults = await Promise.all(spawns);
@@ -25417,20 +26223,53 @@ var MAX_TEXT_LENGTH = 2e3;
25417
26223
  var MAX_ANNOTATIONS = 1e3;
25418
26224
  var AnnotationsStore = class {
25419
26225
  dir;
26226
+ events;
26227
+ traceId;
25420
26228
  /** Per-session write queue. Created lazily on first add. */
25421
26229
  writeChains = /* @__PURE__ */ new Map();
25422
26230
  constructor(opts) {
25423
26231
  this.dir = opts.dir;
26232
+ this.events = opts.events;
26233
+ this.traceId = opts.traceId;
25424
26234
  }
25425
26235
  // ── Reads ──────────────────────────────────────────────────────────────
25426
26236
  /**
25427
26237
  * Return all annotations for `sessionId` in insertion order
25428
26238
  * (oldest first). Returns an empty array when no file exists
25429
- * yet (the normal case for a fresh session).
26239
+ * yet (the normal case for a fresh session) and also degrades
26240
+ * gracefully to `[]` on a read error (permissions, corruption) —
26241
+ * the failure is still surfaced via a `storage.read` event so it
26242
+ * never silently hides I/O problems from observers.
25430
26243
  */
25431
26244
  async list(sessionId) {
25432
- const file = await this.readFile(sessionId);
25433
- return file ? file.annotations : [];
26245
+ const t0 = Date.now();
26246
+ const fp = this.filePath(sessionId);
26247
+ try {
26248
+ const file = await this.readFile(sessionId);
26249
+ const durationMs = Date.now() - t0;
26250
+ this.events?.emit("storage.read", {
26251
+ sessionId,
26252
+ store: "annotations",
26253
+ filePath: fp,
26254
+ operation: "list",
26255
+ outcome: "success",
26256
+ durationMs,
26257
+ ...this.traceId !== void 0 ? { traceId: this.traceId } : {}
26258
+ });
26259
+ return file ? file.annotations : [];
26260
+ } catch (err) {
26261
+ this.events?.emit("storage.read", {
26262
+ sessionId,
26263
+ store: "annotations",
26264
+ filePath: fp,
26265
+ operation: "list",
26266
+ outcome: "failure",
26267
+ durationMs: Date.now() - t0,
26268
+ error: err instanceof Error ? err.message : String(err),
26269
+ ...this.traceId !== void 0 ? { traceId: this.traceId } : {}
26270
+ });
26271
+ return [];
26272
+ }
25434
26273
  }
25435
26274
  /**
25436
26275
  * Convenience: only unresolved annotations, newest first — the
@@ -25482,25 +26321,62 @@ var AnnotationsStore = class {
25482
26321
  createdAt: (/* @__PURE__ */ new Date()).toISOString(),
25483
26322
  resolved: false
25484
26323
  };
25485
- await this.enqueue(input.sessionId, async () => {
25486
- await withFileLock(this.filePath(input.sessionId), async () => {
25487
- const all = await this.list(input.sessionId);
25488
- all.push(annotation);
25489
- if (all.length > MAX_ANNOTATIONS) {
25490
- const sorted = all.map((a, i) => ({ a, i })).sort((x, y) => {
25491
- if (x.a.resolved !== y.a.resolved) return x.a.resolved ? 1 : -1;
25492
- return x.a.createdAt.localeCompare(y.a.createdAt);
25493
- });
25494
- const evictCount = all.length - MAX_ANNOTATIONS;
25495
- const toEvict = new Set(sorted.slice(0, evictCount).map((s) => s.a.id));
25496
- const kept = all.filter((a) => !toEvict.has(a.id));
25497
- await this.writeFile(input.sessionId, { version: FILE_VERSION, annotations: kept });
25498
- } else {
25499
- await this.writeFile(input.sessionId, { version: FILE_VERSION, annotations: all });
25500
- }
26324
+ const fp = this.filePath(input.sessionId);
26325
+ const t0 = Date.now();
26326
+ try {
26327
+ await this.enqueue(input.sessionId, async () => {
26328
+ await withFileLock(fp, async () => {
26329
+ const all = await this.list(input.sessionId);
26330
+ all.push(annotation);
26331
+ if (all.length > MAX_ANNOTATIONS) {
26332
+ const sorted = all.map((a, i) => ({ a, i })).sort((x, y) => {
26333
+ if (x.a.resolved !== y.a.resolved) return x.a.resolved ? 1 : -1;
26334
+ return x.a.createdAt.localeCompare(y.a.createdAt);
26335
+ });
26336
+ const evictCount = all.length - MAX_ANNOTATIONS;
26337
+ const toEvict = new Set(sorted.slice(0, evictCount).map((s) => s.a.id));
26338
+ const kept = all.filter((a) => !toEvict.has(a.id));
26339
+ await this.writeFile(input.sessionId, { version: FILE_VERSION, annotations: kept });
26340
+ const durationMs = Date.now() - t0;
26341
+ this.events?.emit("storage.write", {
26342
+ sessionId: input.sessionId,
26343
+ store: "annotations",
26344
+ filePath: fp,
26345
+ operation: "evict",
26346
+ outcome: "success",
26347
+ durationMs,
26348
+ ...this.traceId !== void 0 ? { traceId: this.traceId } : {}
26349
+ });
26350
+ } else {
26351
+ await this.writeFile(input.sessionId, { version: FILE_VERSION, annotations: all });
26352
+ const durationMs = Date.now() - t0;
26353
+ this.events?.emit("storage.write", {
26354
+ sessionId: input.sessionId,
26355
+ store: "annotations",
26356
+ filePath: fp,
26357
+ operation: "add",
26358
+ outcome: "success",
26359
+ durationMs,
26360
+ ...this.traceId !== void 0 ? { traceId: this.traceId } : {}
26361
+ });
26362
+ }
26363
+ });
25501
26364
  });
25502
- });
25503
- return annotation;
26365
+ return annotation;
26366
+ } catch (err) {
26367
+ this.events?.emit("storage.error", {
26368
+ sessionId: input.sessionId,
26369
+ store: "annotations",
26370
+ filePath: fp,
26371
+ operation: "add",
26372
+ outcome: "failure",
26373
+ error: err instanceof Error ? err.message : String(err),
26374
+ recoverable: false,
26375
+ durationMs: Date.now() - t0,
26376
+ ...this.traceId !== void 0 ? { traceId: this.traceId } : {}
26377
+ });
26378
+ throw err;
26379
+ }
25504
26380
  }
25505
26381
  /**
25506
26382
  * Mark an annotation as resolved. Returns the updated record, or
@@ -25510,26 +26386,53 @@ var AnnotationsStore = class {
25510
26386
  */
25511
26387
  async resolve(input) {
25512
26388
  let updated = null;
25513
- await this.enqueue(input.sessionId, async () => {
25514
- await withFileLock(this.filePath(input.sessionId), async () => {
25515
- const all = await this.list(input.sessionId);
25516
- const idx = all.findIndex((a) => a.id === input.annotationId);
25517
- if (idx === -1) {
25518
- updated = null;
25519
- return;
25520
- }
25521
- const next = {
25522
- ...expectDefined(all[idx]),
25523
- resolved: true,
25524
- resolvedAt: (/* @__PURE__ */ new Date()).toISOString(),
25525
- resolvedBy: input.resolvedBy
25526
- };
25527
- all[idx] = next;
25528
- await this.writeFile(input.sessionId, { version: FILE_VERSION, annotations: all });
25529
- updated = next;
26389
+ const fp = this.filePath(input.sessionId);
26390
+ const t0 = Date.now();
26391
+ try {
26392
+ await this.enqueue(input.sessionId, async () => {
26393
+ await withFileLock(fp, async () => {
26394
+ const all = await this.list(input.sessionId);
26395
+ const idx = all.findIndex((a) => a.id === input.annotationId);
26396
+ if (idx === -1) {
26397
+ updated = null;
26398
+ return;
26399
+ }
26400
+ const next = {
26401
+ ...expectDefined(all[idx]),
26402
+ resolved: true,
26403
+ resolvedAt: (/* @__PURE__ */ new Date()).toISOString(),
26404
+ resolvedBy: input.resolvedBy
26405
+ };
26406
+ all[idx] = next;
26407
+ await this.writeFile(input.sessionId, { version: FILE_VERSION, annotations: all });
26408
+ updated = next;
26409
+ const durationMs = Date.now() - t0;
26410
+ this.events?.emit("storage.write", {
26411
+ sessionId: input.sessionId,
26412
+ store: "annotations",
26413
+ filePath: fp,
26414
+ operation: "resolve",
26415
+ outcome: "success",
26416
+ durationMs,
26417
+ ...this.traceId !== void 0 ? { traceId: this.traceId } : {}
26418
+ });
26419
+ });
25530
26420
  });
25531
- });
25532
- return updated;
26421
+ return updated;
26422
+ } catch (err) {
26423
+ this.events?.emit("storage.error", {
26424
+ sessionId: input.sessionId,
26425
+ store: "annotations",
26426
+ filePath: fp,
26427
+ operation: "resolve",
26428
+ outcome: "failure",
26429
+ error: err instanceof Error ? err.message : String(err),
26430
+ recoverable: false,
26431
+ durationMs: Date.now() - t0,
26432
+ ...this.traceId !== void 0 ? { traceId: this.traceId } : {}
26433
+ });
26434
+ throw err;
26435
+ }
25533
26436
  }
25534
26437
  // ── Internals ──────────────────────────────────────────────────────────
25535
26438
  filePath(sessionId) {
@@ -25537,16 +26440,21 @@ var AnnotationsStore = class {
25537
26440
  }
25538
26441
  async readFile(sessionId) {
25539
26442
  const fp = this.filePath(sessionId);
26443
+ let raw;
26444
+ try {
26445
+ raw = await fsp3.readFile(fp, "utf8");
26446
+ } catch (err) {
26447
+ if (err.code === "ENOENT") return null;
26448
+ throw err;
26449
+ }
25540
26450
  try {
25541
- const raw = await fsp3.readFile(fp, "utf8");
25542
26451
  const parsed = JSON.parse(raw);
25543
26452
  if (parsed.version !== FILE_VERSION) {
25544
26453
  return { version: FILE_VERSION, annotations: [] };
25545
26454
  }
25546
26455
  return parsed;
25547
- } catch (err) {
25548
- if (err.code === "ENOENT") return null;
25549
- return { version: FILE_VERSION, annotations: [] };
26456
+ } catch {
26457
+ return null;
25550
26458
  }
25551
26459
  }
25552
26460
  async writeFile(sessionId, file) {
@@ -25603,9 +26511,20 @@ function hashRequest(request) {
25603
26511
  const digest = createHash("sha256").update(json, "utf8").digest("hex");
25604
26512
  return `sha256:${digest}`;
25605
26513
  }
26514
+
26515
+ // src/storage/replay-log-store.ts
26516
+ function storageErrorString2(err) {
26517
+ if (err instanceof Error) {
26518
+ const code = err.code;
26519
+ return code ? `${code}: ${err.message}` : err.message;
26520
+ }
26521
+ return String(err);
26522
+ }
25606
26523
  var DEFAULT_MAX_ENTRIES = 1e3;
25607
26524
  var ReplayLogStore = class {
25608
26525
  dir;
26526
+ events;
26527
+ traceId;
25609
26528
  writeChains = /* @__PURE__ */ new Map();
25610
26529
  /** Per-session hash → entry index, kept in memory after the first load. */
25611
26530
  cache = /* @__PURE__ */ new Map();
@@ -25614,6 +26533,8 @@ var ReplayLogStore = class {
25614
26533
  maxEntries;
25615
26534
  constructor(opts) {
25616
26535
  this.dir = opts.dir;
26536
+ this.events = opts.events;
26537
+ this.traceId = opts.traceId;
25617
26538
  this.maxEntries = opts.maxEntries ?? DEFAULT_MAX_ENTRIES;
25618
26539
  }
25619
26540
  // ── Writes ──────────────────────────────────────────────────────────────
@@ -25624,38 +26545,43 @@ var ReplayLogStore = class {
25624
26545
  */
25625
26546
  async record(input) {
25626
26547
  const hash = hashRequest(input.request);
25627
- await this.enqueue(input.sessionId, async () => {
25628
- await withFileLock(this.filePath(input.sessionId), async () => {
25629
- const entries = await this.readAll(input.sessionId);
25630
- if (entries.some((entry2) => entry2.hash === hash)) return;
25631
- const entry = {
25632
- hash,
25633
- ts: (/* @__PURE__ */ new Date()).toISOString(),
25634
- request: input.request,
25635
- response: input.response
25636
- };
25637
- entries.push(entry);
25638
- const keep = entries.slice(-this.maxEntries);
25639
- const cache = /* @__PURE__ */ new Map();
25640
- for (const e of keep) cache.set(e.hash, e);
25641
- this.cache.set(input.sessionId, cache);
25642
- await this.rewriteCache(input.sessionId, cache);
26548
+ const fp = this.filePath(input.sessionId);
26549
+ const t0 = Date.now();
26550
+ try {
26551
+ await this.enqueue(input.sessionId, async () => {
26552
+ await withFileLock(this.filePath(input.sessionId), async () => {
26553
+ const entries = await this.readAll(input.sessionId);
26554
+ if (entries.some((entry2) => entry2.hash === hash)) return;
26555
+ const entry = {
26556
+ hash,
26557
+ ts: (/* @__PURE__ */ new Date()).toISOString(),
26558
+ request: input.request,
26559
+ response: input.response
26560
+ };
26561
+ entries.push(entry);
26562
+ const keep = entries.slice(-this.maxEntries);
26563
+ const didEvict = keep.length < entries.length;
26564
+ const cache = /* @__PURE__ */ new Map();
26565
+ for (const e of keep) cache.set(e.hash, e);
26566
+ this.cache.set(input.sessionId, cache);
26567
+ await this.writeAll(input.sessionId, keep, didEvict ? "compact" : "record");
26568
+ });
25643
26569
  });
25644
- });
25645
- return hash;
25646
- }
25647
- /**
25648
- * Compact the replay log to keep only the most recent maxEntries.
25649
- * Called when entry count exceeds the cap. Rewrites the entire file
25650
- * but only happens O(n / maxEntries) times per session.
25651
- */
25652
- async rewriteCache(sessionId, cache) {
25653
- const all = [...cache.values()];
25654
- const keep = all.slice(-this.maxEntries);
25655
- await this.writeAll(sessionId, keep);
25656
- cache.clear();
25657
- for (const e of keep) cache.set(e.hash, e);
25658
- this.diskCount.set(sessionId, keep.length);
26570
+ return hash;
26571
+ } catch (err) {
26572
+ this.events?.emit("storage.error", {
26573
+ sessionId: input.sessionId,
26574
+ store: "replay",
26575
+ filePath: fp,
26576
+ operation: "record",
26577
+ outcome: "failure",
26578
+ error: storageErrorString2(err),
26579
+ recoverable: false,
26580
+ durationMs: Date.now() - t0,
26581
+ ...this.traceId !== void 0 ? { traceId: this.traceId } : {}
26582
+ });
26583
+ throw err;
26584
+ }
25659
26585
  }
25660
26586
  // ── Reads ───────────────────────────────────────────────────────────────
25661
26587
  /**
@@ -25664,13 +26590,65 @@ var ReplayLogStore = class {
25664
26590
  * per session (in-memory cache).
25665
26591
  */
25666
26592
  async lookup(sessionId, hash) {
25667
- const cache = await this.ensureCache(sessionId);
25668
- return cache.get(hash) ?? null;
26593
+ const fp = this.filePath(sessionId);
26594
+ const t0 = Date.now();
26595
+ try {
26596
+ const cache = await this.ensureCache(sessionId);
26597
+ this.events?.emit("storage.read", {
26598
+ sessionId,
26599
+ store: "replay",
26600
+ filePath: fp,
26601
+ operation: "lookup",
26602
+ outcome: "success",
26603
+ durationMs: Date.now() - t0,
26604
+ ...this.traceId !== void 0 ? { traceId: this.traceId } : {}
26605
+ });
26606
+ return cache.get(hash) ?? null;
26607
+ } catch (err) {
26608
+ this.events?.emit("storage.read", {
26609
+ sessionId,
26610
+ store: "replay",
26611
+ filePath: fp,
26612
+ operation: "lookup",
26613
+ outcome: "failure",
26614
+ durationMs: Date.now() - t0,
26615
+ error: storageErrorString2(err),
26616
+ ...this.traceId !== void 0 ? { traceId: this.traceId } : {}
26617
+ });
26618
+ throw err;
26619
+ }
25669
26620
  }
25670
26621
  /** All recorded entries for a session, in insertion order. */
25671
26622
  async load(sessionId) {
25672
- const cache = await this.ensureCache(sessionId);
25673
- return [...cache.values()];
26623
+ const fp = this.filePath(sessionId);
26624
+ const t0 = Date.now();
26625
+ try {
26626
+ const cache = await this.ensureCache(sessionId);
26627
+ const durationMs = Date.now() - t0;
26628
+ this.events?.emit("storage.read", {
26629
+ sessionId,
26630
+ store: "replay",
26631
+ filePath: fp,
26632
+ operation: "load",
26633
+ outcome: "success",
26634
+ durationMs,
26635
+ ...this.traceId !== void 0 ? { traceId: this.traceId } : {}
26636
+ });
26637
+ return [...cache.values()];
26638
+ } catch (err) {
26639
+ const durationMs = Date.now() - t0;
26640
+ this.events?.emit("storage.read", {
26641
+ sessionId,
26642
+ store: "replay",
26643
+ filePath: fp,
26644
+ operation: "load",
26645
+ outcome: "failure",
26646
+ durationMs,
26647
+ error: storageErrorString2(err),
26648
+ ...this.traceId !== void 0 ? { traceId: this.traceId } : {}
26649
+ });
26650
+ throw err;
26651
+ }
25674
26652
  }
25675
26653
  /**
25676
26654
  * List every session id that has a replay log in the store dir.
@@ -25740,13 +26718,24 @@ var ReplayLogStore = class {
25740
26718
  return out;
25741
26719
  } catch (err) {
25742
26720
  if (err.code === "ENOENT") return [];
25743
- return [];
26721
+ throw err;
25744
26722
  }
25745
26723
  }
25746
- async writeAll(sessionId, entries) {
26724
+ async writeAll(sessionId, entries, operation = "record") {
25747
26725
  const fp = this.filePath(sessionId);
26726
+ const t0 = Date.now();
25748
26727
  const body = entries.map((e) => JSON.stringify(e)).join("\n") + (entries.length ? "\n" : "");
25749
26728
  await atomicWrite(fp, body);
26729
+ const durationMs = Date.now() - t0;
26730
+ this.events?.emit("storage.write", {
26731
+ sessionId,
26732
+ store: "replay",
26733
+ filePath: fp,
26734
+ operation,
26735
+ outcome: "success",
26736
+ durationMs,
26737
+ ...this.traceId !== void 0 ? { traceId: this.traceId } : {}
26738
+ });
25750
26739
  }
25751
26740
  async ensureCache(sessionId) {
25752
26741
  let cache = this.cache.get(sessionId);
@@ -25927,6 +26916,8 @@ var GENESIS_PREV = "0".repeat(64);
25927
26916
  var DEFAULT_FSYNC_EVERY = 100;
25928
26917
  var ToolAuditLog = class {
25929
26918
  dir;
26919
+ events;
26920
+ traceId;
25930
26921
  /** In-memory cache of the last entry's hash (per session), to compute chains efficiently. */
25931
26922
  tailHash = /* @__PURE__ */ new Map();
25932
26923
  /** In-memory counter for entry indices — avoids re-reading the file on every write. */
@@ -25937,6 +26928,8 @@ var ToolAuditLog = class {
25937
26928
  fsyncEvery;
25938
26929
  constructor(opts) {
25939
26930
  this.dir = opts.dir;
26931
+ this.events = opts.events;
26932
+ this.traceId = opts.traceId;
25940
26933
  this.fsyncEvery = opts.fsyncEvery ?? DEFAULT_FSYNC_EVERY;
25941
26934
  }
25942
26935
  /**
@@ -25947,96 +26940,180 @@ var ToolAuditLog = class {
25947
26940
  */
25948
26941
  async record(input) {
25949
26942
  let entry;
25950
- await this.enqueue(input.sessionId, async () => {
25951
- await withFileLock(this.filePath(input.sessionId), async () => {
25952
- const entries = await this.readAll(input.sessionId);
25953
- const prev = entries.at(-1);
25954
- const prevHash = prev?.hash ?? GENESIS_PREV;
25955
- const index = prev ? prev.index + 1 : 0;
25956
- const id = randomUUID();
25957
- const ts = (/* @__PURE__ */ new Date()).toISOString();
25958
- const content = {
25959
- id,
25960
- ts,
25961
- prevHash,
25962
- toolName: input.toolName,
25963
- toolUseId: input.toolUseId,
25964
- input: input.input,
25965
- output: input.output,
25966
- isError: input.isError,
25967
- index
25968
- };
25969
- const hash = createHash("sha256").update(stableStringify2(content), "utf8").digest("hex");
25970
- entry = {
25971
- id,
25972
- ts,
25973
- prevHash,
25974
- hash,
25975
- toolName: input.toolName,
25976
- toolUseId: input.toolUseId,
25977
- input: input.input,
25978
- output: input.output,
25979
- isError: input.isError,
25980
- index
25981
- };
25982
- entries.push(entry);
25983
- await this.writeAll(input.sessionId, entries);
25984
- this.tailHash.set(input.sessionId, hash);
25985
- this.tailIndex.set(input.sessionId, index + 1);
26943
+ const fp = this.filePath(input.sessionId);
26944
+ const t0 = Date.now();
26945
+ try {
26946
+ await this.enqueue(input.sessionId, async () => {
26947
+ await withFileLock(fp, async () => {
26948
+ const entries = await this.readAll(input.sessionId);
26949
+ const prev = entries.at(-1);
26950
+ const prevHash = prev?.hash ?? GENESIS_PREV;
26951
+ const index = prev ? prev.index + 1 : 0;
26952
+ const id = randomUUID();
26953
+ const ts = (/* @__PURE__ */ new Date()).toISOString();
26954
+ const content = {
26955
+ id,
26956
+ ts,
26957
+ prevHash,
26958
+ toolName: input.toolName,
26959
+ toolUseId: input.toolUseId,
26960
+ input: input.input,
26961
+ output: input.output,
26962
+ isError: input.isError,
26963
+ index
26964
+ };
26965
+ const hash = createHash("sha256").update(stableStringify2(content), "utf8").digest("hex");
26966
+ entry = {
26967
+ id,
26968
+ ts,
26969
+ prevHash,
26970
+ hash,
26971
+ toolName: input.toolName,
26972
+ toolUseId: input.toolUseId,
26973
+ input: input.input,
26974
+ output: input.output,
26975
+ isError: input.isError,
26976
+ index
26977
+ };
26978
+ entries.push(entry);
26979
+ await this.writeAll(input.sessionId, entries);
26980
+ this.tailHash.set(input.sessionId, hash);
26981
+ this.tailIndex.set(input.sessionId, index + 1);
26982
+ const durationMs = Date.now() - t0;
26983
+ this.events?.emit("storage.write", {
26984
+ sessionId: input.sessionId,
26985
+ store: "audit",
26986
+ filePath: fp,
26987
+ operation: "record",
26988
+ outcome: "success",
26989
+ durationMs,
26990
+ ...this.traceId !== void 0 ? { traceId: this.traceId } : {}
26991
+ });
26992
+ });
25986
26993
  });
25987
- });
25988
- return entry;
26994
+ return entry;
26995
+ } catch (err) {
26996
+ this.events?.emit("storage.error", {
26997
+ sessionId: input.sessionId,
26998
+ store: "audit",
26999
+ filePath: fp,
27000
+ operation: "record",
27001
+ outcome: "failure",
27002
+ error: err instanceof Error ? err.message : String(err),
27003
+ recoverable: false,
27004
+ durationMs: Date.now() - t0,
27005
+ ...this.traceId !== void 0 ? { traceId: this.traceId } : {}
27006
+ });
27007
+ throw err;
27008
+ }
25989
27009
  }
25990
27010
  /**
25991
27011
  * Walk the chain and verify every entry's hash and prevHash.
25992
27012
  * Returns a structured verdict — never throws.
25993
27013
  */
25994
27014
  async verify(sessionId) {
25995
- const entries = await this.readAll(sessionId);
25996
- if (entries.length === 0) return { ok: true, entries: 0 };
25997
- if (entries[0]?.prevHash !== GENESIS_PREV) {
25998
- return {
25999
- ok: false,
26000
- brokenAt: 0,
26001
- reason: "first entry is not the genesis (prevHash != 0\u20260)"
26002
- };
27015
+ const fp = this.filePath(sessionId);
27016
+ const t0 = Date.now();
27017
+ let entries;
27018
+ try {
27019
+ entries = await this.readAll(sessionId);
27020
+ } catch (err) {
27021
+ this.events?.emit("storage.read", {
27022
+ sessionId,
27023
+ store: "audit",
27024
+ filePath: fp,
27025
+ operation: "verify",
27026
+ outcome: "failure",
27027
+ durationMs: Date.now() - t0,
27028
+ error: err instanceof Error ? err.message : String(err),
27029
+ ...this.traceId !== void 0 ? { traceId: this.traceId } : {}
27030
+ });
27031
+ return { ok: true, entries: 0 };
26003
27032
  }
26004
- let prevHash = GENESIS_PREV;
26005
- for (let i = 0; i < entries.length; i++) {
26006
- const e = expectDefined(entries[i]);
26007
- if (e.prevHash !== prevHash) {
27033
+ const verdict = (() => {
27034
+ if (entries.length === 0) return { ok: true, entries: 0 };
27035
+ if (entries[0]?.prevHash !== GENESIS_PREV) {
26008
27036
  return {
26009
27037
  ok: false,
26010
- brokenAt: i,
26011
- reason: `prevHash mismatch at entry ${i} (expected ${prevHash.slice(0, 8)}\u2026, got ${e.prevHash.slice(0, 8)}\u2026)`
27038
+ brokenAt: 0,
27039
+ reason: "first entry is not the genesis (prevHash != 0\u20260)"
26012
27040
  };
26013
27041
  }
26014
- const content = {
26015
- id: e.id,
26016
- ts: e.ts,
26017
- prevHash: e.prevHash,
26018
- toolName: e.toolName,
26019
- toolUseId: e.toolUseId,
26020
- input: e.input,
26021
- output: e.output,
26022
- isError: e.isError,
26023
- index: e.index
26024
- };
26025
- const expectedHash = createHash("sha256").update(stableStringify2(content), "utf8").digest("hex");
26026
- if (expectedHash !== e.hash) {
26027
- return {
26028
- ok: false,
26029
- brokenAt: i,
26030
- reason: `hash mismatch at entry ${i} (entry content was modified)`
27042
+ let prevHash = GENESIS_PREV;
27043
+ for (let i = 0; i < entries.length; i++) {
27044
+ const e = expectDefined(entries[i]);
27045
+ if (e.prevHash !== prevHash) {
27046
+ return {
27047
+ ok: false,
27048
+ brokenAt: i,
27049
+ reason: `prevHash mismatch at entry ${i} (expected ${prevHash.slice(0, 8)}\u2026, got ${e.prevHash.slice(0, 8)}\u2026)`
27050
+ };
27051
+ }
27052
+ const content = {
27053
+ id: e.id,
27054
+ ts: e.ts,
27055
+ prevHash: e.prevHash,
27056
+ toolName: e.toolName,
27057
+ toolUseId: e.toolUseId,
27058
+ input: e.input,
27059
+ output: e.output,
27060
+ isError: e.isError,
27061
+ index: e.index
26031
27062
  };
27063
+ const expectedHash = createHash("sha256").update(stableStringify2(content), "utf8").digest("hex");
27064
+ if (expectedHash !== e.hash) {
27065
+ return {
27066
+ ok: false,
27067
+ brokenAt: i,
27068
+ reason: `hash mismatch at entry ${i} (entry content was modified)`
27069
+ };
27070
+ }
27071
+ prevHash = e.hash;
26032
27072
  }
26033
- prevHash = e.hash;
26034
- }
26035
- return { ok: true, entries: entries.length };
27073
+ return { ok: true, entries: entries.length };
27074
+ })();
27075
+ this.events?.emit("storage.read", {
27076
+ sessionId,
27077
+ store: "audit",
27078
+ filePath: fp,
27079
+ operation: "verify",
27080
+ outcome: verdict.ok ? "success" : "failure",
27081
+ durationMs: Date.now() - t0,
27082
+ ...this.traceId !== void 0 ? { traceId: this.traceId } : {}
27083
+ });
27084
+ return verdict;
26036
27085
  }
26037
27086
  /** All entries for a session, in insertion order. */
26038
27087
  async load(sessionId) {
26039
- return this.readAll(sessionId);
27088
+ const fp = this.filePath(sessionId);
27089
+ const t0 = Date.now();
27090
+ try {
27091
+ const entries = await this.readAll(sessionId);
27092
+ const durationMs = Date.now() - t0;
27093
+ this.events?.emit("storage.read", {
27094
+ sessionId,
27095
+ store: "audit",
27096
+ filePath: fp,
27097
+ operation: "load",
27098
+ outcome: "success",
27099
+ durationMs,
27100
+ ...this.traceId !== void 0 ? { traceId: this.traceId } : {}
27101
+ });
27102
+ return entries;
27103
+ } catch (err) {
27104
+ const durationMs = Date.now() - t0;
27105
+ this.events?.emit("storage.read", {
27106
+ sessionId,
27107
+ store: "audit",
27108
+ filePath: fp,
27109
+ operation: "load",
27110
+ outcome: "failure",
27111
+ durationMs,
27112
+ error: err instanceof Error ? err.message : String(err),
27113
+ ...this.traceId !== void 0 ? { traceId: this.traceId } : {}
27114
+ });
27115
+ throw err;
27116
+ }
26040
27117
  }
26041
27118
  // ── Internals ────────────────────────────────────────────────────────────
26042
27119
  filePath(sessionId) {
@@ -26058,7 +27135,7 @@ var ToolAuditLog = class {
26058
27135
  return out;
26059
27136
  } catch (err) {
26060
27137
  if (err.code === "ENOENT") return [];
26061
- return [];
27138
+ throw err;
26062
27139
  }
26063
27140
  }
26064
27141
  async writeAll(sessionId, entries) {
@@ -26458,37 +27535,98 @@ function emptyTaskFile(sessionId) {
26458
27535
  tasks: []
26459
27536
  };
26460
27537
  }
26461
- async function loadTasks(filePath) {
27538
+ async function loadTasks(filePath, events, traceId) {
27539
+ const t0 = Date.now();
26462
27540
  let raw;
26463
27541
  try {
26464
27542
  raw = await fsp3.readFile(filePath, "utf8");
26465
- } catch {
27543
+ } catch (err) {
27544
+ events?.emit("storage.error", {
27545
+ sessionId: traceId ?? "~boot~",
27546
+ store: "tasks",
27547
+ filePath,
27548
+ operation: "load",
27549
+ outcome: "failure",
27550
+ error: err instanceof Error ? err.message : String(err),
27551
+ recoverable: true
27552
+ });
26466
27553
  return null;
26467
27554
  }
26468
27555
  try {
26469
27556
  const parsed = JSON.parse(raw);
26470
- if (parsed?.version !== 1 || !Array.isArray(parsed.tasks)) return null;
27557
+ if (parsed?.version !== 1 || !Array.isArray(parsed.tasks)) {
27558
+ events?.emit("storage.read", {
27559
+ sessionId: traceId ?? "~boot~",
27560
+ store: "tasks",
27561
+ filePath,
27562
+ operation: "load",
27563
+ outcome: "failure",
27564
+ durationMs: Date.now() - t0,
27565
+ error: "invalid_schema",
27566
+ ...traceId !== void 0 && { traceId }
27567
+ });
27568
+ return null;
27569
+ }
27570
+ events?.emit("storage.read", {
27571
+ sessionId: traceId ?? "~boot~",
27572
+ store: "tasks",
27573
+ filePath,
27574
+ operation: "load",
27575
+ outcome: "success",
27576
+ durationMs: Date.now() - t0,
27577
+ ...traceId !== void 0 && { traceId }
27578
+ });
26471
27579
  return parsed;
26472
27580
  } catch {
27581
+ events?.emit("storage.read", {
27582
+ sessionId: traceId ?? "~boot~",
27583
+ store: "tasks",
27584
+ filePath,
27585
+ operation: "load",
27586
+ outcome: "failure",
27587
+ durationMs: Date.now() - t0,
27588
+ error: "parse_failed",
27589
+ ...traceId !== void 0 && { traceId }
27590
+ });
26473
27591
  return null;
26474
27592
  }
26475
27593
  }
26476
- async function saveTasks(filePath, tasks) {
27594
+ async function saveTasks(filePath, tasks, events, traceId) {
27595
+ const t0 = Date.now();
26477
27596
  try {
26478
27597
  tasks.updatedAt = (/* @__PURE__ */ new Date()).toISOString();
26479
27598
  await atomicWrite(filePath, JSON.stringify(tasks, null, 2), { mode: 384 });
27599
+ events?.emit("storage.write", {
27600
+ sessionId: traceId ?? "~boot~",
27601
+ store: "tasks",
27602
+ filePath,
27603
+ operation: "save",
27604
+ outcome: "success",
27605
+ durationMs: Date.now() - t0,
27606
+ ...traceId !== void 0 && { traceId }
27607
+ });
26480
27608
  } catch (err) {
27609
+ events?.emit("storage.error", {
27610
+ sessionId: traceId ?? "~boot~",
27611
+ store: "tasks",
27612
+ filePath,
27613
+ operation: "save",
27614
+ outcome: "failure",
27615
+ error: err instanceof Error ? err.message : String(err),
27616
+ recoverable: false,
27617
+ ...traceId !== void 0 && { traceId }
27618
+ });
26481
27619
  console.warn(
26482
27620
  "[task-store] save failed:",
26483
27621
  err instanceof Error ? err.message : String(err)
26484
27622
  );
26485
27623
  }
26486
27624
  }
26487
- async function mutateTasks(filePath, sessionId, fn) {
27625
+ async function mutateTasks(filePath, sessionId, fn, events, traceId) {
26488
27626
  return withFileLock(filePath, async () => {
26489
- const file = await loadTasks(filePath) ?? emptyTaskFile(sessionId);
27627
+ const file = await loadTasks(filePath, events, traceId) ?? emptyTaskFile(sessionId);
26490
27628
  const updated = await fn(file);
26491
- await saveTasks(filePath, updated);
27629
+ await saveTasks(filePath, updated, events, traceId);
26492
27630
  return updated;
26493
27631
  });
26494
27632
  }
@@ -30795,8 +31933,9 @@ function buildMailboxBlock(messages) {
30795
31933
  const parts = [];
30796
31934
  parts.push("[MAILBOX] New message(s) from other agents:");
30797
31935
  parts.push("");
31936
+ const hasActionable = messages.some((m) => m.type === "ask" || m.type === "assign" || m.type === "result");
30798
31937
  for (const m of messages) {
30799
- const typeLabel = m.type === "steer" ? "\u{1F504} STEER" : m.type === "btw" ? "\u{1F4AC} BTW" : `\u{1F4E8} ${m.type.toUpperCase()}`;
31938
+ const typeLabel = m.type === "steer" ? "\u{1F504} STEER" : m.type === "btw" ? "\u{1F4AC} BTW" : m.type === "ask" ? "\u2753 ASK" : m.type === "assign" ? "\u{1F4CB} ASSIGN" : m.type === "result" ? "\u2705 RESULT" : `\u{1F4E8} ${m.type.toUpperCase()}`;
30800
31939
  parts.push(`--- ${typeLabel} from ${m.from} ---`);
30801
31940
  parts.push(`Subject: ${m.subject}`);
30802
31941
  parts.push("");
@@ -30806,6 +31945,22 @@ function buildMailboxBlock(messages) {
30806
31945
  parts.push("After your current operation reaches a stopping point, adjust your approach per the instruction above.");
30807
31946
  parts.push("");
30808
31947
  }
31948
+ if (m.type === "ask") {
31949
+ parts.push("\u21B3 This agent is waiting for your answer. Reply directly or use mailbox action=send to respond.");
31950
+ parts.push("");
31951
+ }
31952
+ if (m.type === "assign") {
31953
+ parts.push("\u21B3 This is a task assignment. Act on it when your current operation allows.");
31954
+ parts.push("");
31955
+ }
31956
+ if (m.type === "result") {
31957
+ parts.push("\u21B3 A subagent has completed its work. Factor this result into your next decision.");
31958
+ parts.push("");
31959
+ }
31960
+ }
31961
+ if (hasActionable) {
31962
+ parts.push("Action required: address the items above. When done, use `mailbox action=ack messageId=<id> completed=true` to mark them complete.");
31963
+ parts.push("");
30809
31964
  }
30810
31965
  parts.push("[END MAILBOX]");
30811
31966
  return { type: "text", text: parts.join("\n") };
@@ -30826,26 +31981,12 @@ async function injectPendingMailboxMessages(checkMailbox, foldFn, a) {
30826
31981
  });
30827
31982
  }
30828
31983
  if (messages.length === 0) return;
30829
- const injectable = messages.filter((m) => m.type === "steer" || m.type === "btw");
30830
- const others = messages.filter((m) => m.type !== "steer" && m.type !== "btw");
30831
- if (injectable.length > 0) {
30832
- try {
30833
- foldFn(buildMailboxBlock(injectable));
30834
- } catch (err) {
30835
- (a.logger.debug ?? console.debug)?.(
30836
- `mailbox: failed to fold messages: ${err instanceof Error ? err.message : String(err)}`
30837
- );
30838
- }
30839
- }
30840
- if (others.length > 0) {
30841
- const otherSubjects = others.map((m) => ` - [${m.type}] ${m.from}: ${m.subject}`).join("\n");
30842
- const note = `[MAILBOX] You have ${others.length} other unread message(s). Use \`mailbox action=check\` to read them:
30843
- ${otherSubjects}
30844
- [END MAILBOX]`;
30845
- try {
30846
- foldFn({ type: "text", text: note });
30847
- } catch {
30848
- }
31984
+ try {
31985
+ foldFn(buildMailboxBlock(messages));
31986
+ } catch (err) {
31987
+ (a.logger.debug ?? console.debug)?.(
31988
+ `mailbox: failed to fold messages: ${err instanceof Error ? err.message : String(err)}`
31989
+ );
30849
31990
  }
30850
31991
  }
30851
31992
 
@@ -31860,9 +33001,26 @@ async function writeProjectMeta(paths, projectRoot) {
31860
33001
  } catch {
31861
33002
  }
31862
33003
  }
31863
- async function registerProjectInManifest(paths, projectRoot) {
33004
+ async function registerProjectInManifest(paths, projectRoot, events) {
33005
+ const manifestPath = path7.join(paths.globalRoot, "projects.json");
33006
+ try {
33007
+ const t0 = Date.now();
33008
+ const raw = await fsp3.readFile(manifestPath, "utf8");
33009
+ events?.emit("storage.read", {
33010
+ sessionId: "~boot~",
33011
+ store: "project",
33012
+ filePath: manifestPath,
33013
+ operation: "manifest_read",
33014
+ outcome: "success",
33015
+ durationMs: Date.now() - t0
33016
+ });
33017
+ try {
33018
+ JSON.parse(raw);
33019
+ } catch {
33020
+ }
33021
+ } catch (err) {
33022
+ }
31864
33023
  try {
31865
- const manifestPath = path7.join(paths.globalRoot, "projects.json");
31866
33024
  let manifest;
31867
33025
  try {
31868
33026
  const raw = await fsp3.readFile(manifestPath, "utf8");
@@ -31879,8 +33037,17 @@ async function registerProjectInManifest(paths, projectRoot) {
31879
33037
  const name = path7.basename(projectRoot);
31880
33038
  manifest.projects.push({ name, root: projectRoot, slug, lastSeen: now, createdAt: now });
31881
33039
  }
33040
+ const writeT0 = Date.now();
31882
33041
  await fsp3.writeFile(manifestPath, JSON.stringify(manifest, null, 2), "utf8");
31883
- } catch {
33042
+ events?.emit("storage.write", {
33043
+ sessionId: "~boot~",
33044
+ store: "project",
33045
+ filePath: manifestPath,
33046
+ operation: "manifest_write",
33047
+ outcome: "success",
33048
+ durationMs: Date.now() - writeT0
33049
+ });
33050
+ } catch (err) {
31884
33051
  }
31885
33052
  }
31886
33053
  var GITIGNORE_ENTRY = ".wrongstack/\n";
@@ -32145,9 +33312,10 @@ Never silently skip a failure \u2014 always report it, even when you choose not
32145
33312
 
32146
33313
  ## After-task suggestions
32147
33314
 
32148
- After completing a significant task, end your response with 2\u20134 suggested next
32149
- actions in a \`<next_steps>\` block. Use this exact format so the user can
32150
- select them with \`/next 1\`, \`/next 2\`, or \`/next 1 2 3\`:
33315
+ **You are the leader agent.** After completing a significant task, end your
33316
+ response with 2\u20134 suggested next actions in a \`<next_steps>\` block. Use this
33317
+ exact format so the user can select them with \`/next 1\`, \`/next 2\`, or
33318
+ \`/next 1 2 3\`:
32151
33319
 
32152
33320
  \`\`\`
32153
33321
  <next_steps>
@@ -32160,6 +33328,7 @@ select them with \`/next 1\`, \`/next 2\`, or \`/next 1 2 3\`:
32160
33328
  Rules:
32161
33329
  - Each line is a single imperative sentence the user can act on immediately.
32162
33330
  - Be specific: mention file names, tool names, or commands.
33331
+ - **Concrete actions only** \u2014 never write declarations of intent ("we should fix X", "consider refactoring Y") or manual suggestions ("manually check Z"). Write exactly what should be done: "Fix null deref in auth/session.ts:42", "Run pnpm typecheck".
32163
33332
  - Order by priority. Keep each suggestion to one line.
32164
33333
  - Skip during multi-step operations \u2014 only show after completion.
32165
33334
  - If nothing is pending, say "No pending actions."
@@ -32489,8 +33658,17 @@ sender's exact \`from\` id.
32489
33658
  ### Receiving
32490
33659
 
32491
33660
  Unread mail (direct, base-name, and \`*\` broadcasts) is injected into
32492
- your conversation automatically before each step \u2014 urgent steer/btw
32493
- inline, the rest as a summary. To catch up explicitly:
33661
+ your conversation automatically before each step \u2014 ALL message types
33662
+ (steer, btw, ask, assign, result, note) appear inline with a call to
33663
+ action. You do NOT need to manually check the mailbox; subagent results
33664
+ and questions reach you even while you are mid-task.
33665
+
33666
+ When a message includes a call to action:
33667
+ - **ask**: reply to the agent directly or use \`mail_send\` to respond
33668
+ - **assign**: act on the task when your current operation allows
33669
+ - **result**: factor the outcome into your next decision
33670
+
33671
+ To catch up explicitly:
32494
33672
 
32495
33673
  - \`mail_inbox\` \u2014 read your unread mail and mark it read
32496
33674
  - \`mailbox action=query from=<agent> type=result\` \u2014 find specific results