@wrongstack/core 0.267.0 → 0.269.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 (78) hide show
  1. package/dist/{agent-bridge-STJ3JwwK.d.ts → agent-bridge-PcHQl_UQ.d.ts} +1 -1
  2. package/dist/{agent-subagent-runner-CzPGP3jA.d.ts → agent-subagent-runner-SHJW7t8q.d.ts} +8 -8
  3. package/dist/{brain-Cdg77tVN.d.ts → brain-BYcK__Ym.d.ts} +1 -1
  4. package/dist/{compactor-iMZ84CXq.d.ts → compactor-C2RKEBtC.d.ts} +1 -1
  5. package/dist/{config-Du3pYYln.d.ts → config-C_ae2k86.d.ts} +79 -2
  6. package/dist/{context-dT5Ueund.d.ts → context-Dp87Bcaq.d.ts} +47 -1
  7. package/dist/coordination/index.d.ts +62 -160
  8. package/dist/coordination/index.js +566 -149
  9. package/dist/coordination/index.js.map +1 -1
  10. package/dist/defaults/index.d.ts +26 -25
  11. package/dist/defaults/index.js +366 -137
  12. package/dist/defaults/index.js.map +1 -1
  13. package/dist/execution/index.d.ts +72 -16
  14. package/dist/execution/index.js +267 -55
  15. package/dist/execution/index.js.map +1 -1
  16. package/dist/execution/prompt-enhancer.d.ts +1 -1
  17. package/dist/extension/index.d.ts +7 -6
  18. package/dist/global-mailbox-Bvrz1P3f.d.ts +664 -0
  19. package/dist/{goal-preamble-SulMTowG.d.ts → goal-preamble-CA_4yiGQ.d.ts} +9 -9
  20. package/dist/{goal-store-CABDwdFE.d.ts → goal-store-DhuJoUNG.d.ts} +1 -1
  21. package/dist/hq/index.d.ts +204 -0
  22. package/dist/hq/index.js +1931 -0
  23. package/dist/hq/index.js.map +1 -0
  24. package/dist/{index-DtCVWel4.d.ts → index-CZQ6Pwbs.d.ts} +8 -8
  25. package/dist/{index-Bms0m4oy.d.ts → index-W4VJCzHa.d.ts} +5 -5
  26. package/dist/{index-IEuxQd-E.d.ts → index-whDfTANu.d.ts} +2 -2
  27. package/dist/index.d.ts +46 -42
  28. package/dist/index.js +3472 -1651
  29. package/dist/index.js.map +1 -1
  30. package/dist/infrastructure/index.d.ts +6 -6
  31. package/dist/infrastructure/index.js +48 -21
  32. package/dist/infrastructure/index.js.map +1 -1
  33. package/dist/kernel/index.d.ts +10 -9
  34. package/dist/{pipeline-BfD2k1rT.d.ts → mailbox-types-Ct2hJq0P.d.ts} +1 -244
  35. package/dist/{mcp-servers-C2cBTxUR.d.ts → mcp-servers-DJdZiRcv.d.ts} +10 -4
  36. package/dist/models/index.d.ts +5 -5
  37. package/dist/models/index.js +4 -3
  38. package/dist/models/index.js.map +1 -1
  39. package/dist/{models-registry-BqGZNJQ-.d.ts → models-registry-C3a-2-Yd.d.ts} +1 -1
  40. package/dist/{multi-agent-coordinator-B8R43uPz.d.ts → multi-agent-coordinator-CJSpTe5O.d.ts} +1 -1
  41. package/dist/{null-fleet-bus-CnXa5oTH.d.ts → null-fleet-bus-QVshIsDx.d.ts} +6 -6
  42. package/dist/observability/index.d.ts +2 -2
  43. package/dist/{parallel-eternal-engine-DdNnw9BQ.d.ts → parallel-eternal-engine-D9y5Pkcc.d.ts} +9 -15
  44. package/dist/{path-resolver-COIMLCQL.d.ts → path-resolver-CnQ8SIfh.d.ts} +4 -3
  45. package/dist/{permission-B75JAi3-.d.ts → permission-CvYQNUqZ.d.ts} +1 -1
  46. package/dist/{permission-policy-DlR9eJAM.d.ts → permission-policy-D5Ss8j4B.d.ts} +2 -3
  47. package/dist/pipeline-l_zzFRh3.d.ts +245 -0
  48. package/dist/{plan-templates-DSIKCXZN.d.ts → plan-templates-NtPgyeJA.d.ts} +6 -5
  49. package/dist/{provider-model-resolve-BNRsNuJx.d.ts → provider-model-resolve-d5poT5y0.d.ts} +3 -3
  50. package/dist/{provider-runner-CX7iIvox.d.ts → provider-runner-gkctlQV_.d.ts} +3 -3
  51. package/dist/{retry-policy-BilV1ujH.d.ts → retry-policy-CtFhfwa8.d.ts} +1 -1
  52. package/dist/sdd/index.d.ts +9 -8
  53. package/dist/sdd/index.js +33 -3
  54. package/dist/sdd/index.js.map +1 -1
  55. package/dist/{secret-vault-gkvEZZfE.d.ts → secret-vault-BLsVmTIK.d.ts} +1 -1
  56. package/dist/security/index.d.ts +5 -5
  57. package/dist/security/index.js +39 -29
  58. package/dist/security/index.js.map +1 -1
  59. package/dist/{selector-Bc7eWtT3.d.ts → selector-CXl2_y9W.d.ts} +1 -1
  60. package/dist/{session-event-bridge-D-araDEz.d.ts → session-event-bridge-Ccud20CC.d.ts} +1 -1
  61. package/dist/{session-reader-D7Dapswh.d.ts → session-reader-ZeXQmsmE.d.ts} +1 -1
  62. package/dist/skills/index.js.map +1 -1
  63. package/dist/storage/index.d.ts +16 -12
  64. package/dist/storage/index.js +273 -100
  65. package/dist/storage/index.js.map +1 -1
  66. package/dist/tools/index.d.ts +2 -2
  67. package/dist/tools/index.js +166 -31
  68. package/dist/tools/index.js.map +1 -1
  69. package/dist/types/index.d.ts +22 -21
  70. package/dist/types/index.js +178 -70
  71. package/dist/types/index.js.map +1 -1
  72. package/dist/utils/index.d.ts +22 -3
  73. package/dist/utils/index.js +197 -25
  74. package/dist/utils/index.js.map +1 -1
  75. package/package.json +5 -1
  76. package/skills/chimera/SKILL.md +1 -1
  77. package/skills/typescript-strict/SKILL.md +3 -3
  78. package/skills/typescript-strict/SKILL.save.md +1 -1
@@ -172,8 +172,8 @@ async function atomicWrite(targetPath, content, opts = {}) {
172
172
  }
173
173
  let mode;
174
174
  try {
175
- const stat6 = await fsp6.stat(targetPath);
176
- mode = stat6.mode & 511;
175
+ const stat7 = await fsp6.stat(targetPath);
176
+ mode = stat7.mode & 511;
177
177
  } catch {
178
178
  mode = opts.mode;
179
179
  }
@@ -213,8 +213,8 @@ async function withFileLock(targetPath, fn, opts = {}) {
213
213
  }
214
214
  if (code !== "EEXIST") throw err;
215
215
  try {
216
- const stat6 = await fsp6.stat(lockPath);
217
- if (Date.now() - stat6.mtimeMs > staleMs) {
216
+ const stat7 = await fsp6.stat(lockPath);
217
+ if (Date.now() - stat7.mtimeMs > staleMs) {
218
218
  await fsp6.unlink(lockPath);
219
219
  continue;
220
220
  }
@@ -545,8 +545,8 @@ async function expandGlob(pattern) {
545
545
  for (const e of entries) {
546
546
  const full = `${dir}${SEP}${e}`;
547
547
  try {
548
- const stat6 = await fsp6.stat(full);
549
- if (stat6.isDirectory()) await walk(full, rest);
548
+ const stat7 = await fsp6.stat(full);
549
+ if (stat7.isDirectory()) await walk(full, rest);
550
550
  } catch {
551
551
  }
552
552
  }
@@ -563,8 +563,8 @@ async function expandGlob(pattern) {
563
563
  if (entries.includes(seg)) {
564
564
  const full = `${dir}${SEP}${seg}`;
565
565
  try {
566
- const stat6 = await fsp6.stat(full);
567
- if (stat6.isDirectory()) await walk(full, rest);
566
+ const stat7 = await fsp6.stat(full);
567
+ if (stat7.isDirectory()) await walk(full, rest);
568
568
  } catch {
569
569
  }
570
570
  }
@@ -636,7 +636,7 @@ function hasToolResult(msg) {
636
636
  }
637
637
  function toolUseIds(msg) {
638
638
  const ids = /* @__PURE__ */ new Set();
639
- if (!msg || msg.role !== "assistant") return ids;
639
+ if (msg?.role !== "assistant") return ids;
640
640
  for (const block of contentBlocks(msg)) {
641
641
  if (block.type === "tool_use") ids.add(block.id);
642
642
  }
@@ -644,7 +644,7 @@ function toolUseIds(msg) {
644
644
  }
645
645
  function toolResultIds(msg) {
646
646
  const ids = /* @__PURE__ */ new Set();
647
- if (!msg || msg.role !== "user") return ids;
647
+ if (msg?.role !== "user") return ids;
648
648
  for (const block of contentBlocks(msg)) {
649
649
  if (block.type === "tool_result") ids.add(block.tool_use_id);
650
650
  }
@@ -996,7 +996,7 @@ var CollabSession = class extends EventEmitter {
996
996
  }
997
997
  for (const filePath of allFiles) {
998
998
  try {
999
- const [content, stat6] = await Promise.all([
999
+ const [content, stat7] = await Promise.all([
1000
1000
  fsp6.readFile(filePath, "utf8"),
1001
1001
  fsp6.stat(filePath)
1002
1002
  ]);
@@ -1006,8 +1006,8 @@ var CollabSession = class extends EventEmitter {
1006
1006
  path: filePath,
1007
1007
  content,
1008
1008
  language,
1009
- snapshotMtimeMs: stat6.mtimeMs,
1010
- snapshotSizeBytes: stat6.size
1009
+ snapshotMtimeMs: stat7.mtimeMs,
1010
+ snapshotSizeBytes: stat7.size
1011
1011
  });
1012
1012
  } catch {
1013
1013
  this.snapshot.files.push({ path: filePath, content: "", language: void 0 });
@@ -1420,9 +1420,9 @@ Emit each evaluation immediately. Do not wait until you have read all reports.`;
1420
1420
  for (const file of this.snapshot.files) {
1421
1421
  if (file.snapshotMtimeMs === void 0 && file.snapshotSizeBytes === void 0) continue;
1422
1422
  try {
1423
- const stat6 = await fsp6.stat(file.path);
1424
- const mtimeChanged = file.snapshotMtimeMs !== void 0 && stat6.mtimeMs > file.snapshotMtimeMs + 1;
1425
- const sizeChanged = file.snapshotSizeBytes !== void 0 && stat6.size !== file.snapshotSizeBytes;
1423
+ const stat7 = await fsp6.stat(file.path);
1424
+ const mtimeChanged = file.snapshotMtimeMs !== void 0 && stat7.mtimeMs > file.snapshotMtimeMs + 1;
1425
+ const sizeChanged = file.snapshotSizeBytes !== void 0 && stat7.size !== file.snapshotSizeBytes;
1426
1426
  if (mtimeChanged || sizeChanged) {
1427
1427
  warnings.push(`${file.path} changed after the collab snapshot was captured.`);
1428
1428
  }
@@ -3765,7 +3765,27 @@ Working rules:
3765
3765
  id: "devops",
3766
3766
  name: "DevOps",
3767
3767
  role: "devops",
3768
- tools: [...TOOLS.build],
3768
+ tools: [
3769
+ ...TOOLS.build,
3770
+ "mcp__ssh__ssh_list_servers",
3771
+ "mcp__ssh__ssh_connection_status",
3772
+ "mcp__ssh__ssh_execute",
3773
+ "mcp__ssh__ssh_execute_sudo",
3774
+ "mcp__ssh__ssh_upload",
3775
+ "mcp__ssh__ssh_download",
3776
+ "mcp__ssh__ssh_sync",
3777
+ "mcp__ssh__ssh_deploy",
3778
+ "mcp__ssh__ssh_health_check",
3779
+ "mcp__ssh__ssh_service_status",
3780
+ "mcp__ssh__ssh_process_manager",
3781
+ "mcp__ssh__ssh_tunnel",
3782
+ "mcp__ssh__ssh_backup_create",
3783
+ "mcp__ssh__ssh_backup_list",
3784
+ "mcp__ssh__ssh_backup_restore",
3785
+ "mcp__ssh__ssh_db_list",
3786
+ "mcp__ssh__ssh_db_query",
3787
+ "mcp__ssh__ssh_profile"
3788
+ ],
3769
3789
  prompt: `You are the DevOps agent. Your job is CI/CD, containerization, and
3770
3790
  deployment configuration: make builds reproducible and deploys safe.
3771
3791
 
@@ -3774,6 +3794,7 @@ Scope:
3774
3794
  - Write Dockerfiles/compose and optimize image size and layer caching
3775
3795
  - Configure deployment (env, secrets handling, health checks, rollback)
3776
3796
  - Diagnose flaky/broken pipelines
3797
+ - Use optional SSH MCP tools for remote hosts when the 'ssh' MCP server is enabled: list servers, run health checks, inspect services, transfer/deploy files, and open tunnels
3777
3798
 
3778
3799
  Input format you accept:
3779
3800
  { "task": "ci | container | deploy | fix-pipeline", "platform": "github-actions | gitlab | docker | k8s", "target": "<what>" }
@@ -3788,7 +3809,8 @@ Working rules:
3788
3809
  - Never hardcode secrets in config; reference the secret store
3789
3810
  - Pin versions for reproducible builds; avoid floating :latest
3790
3811
  - Every deploy path needs a rollback and a health check
3791
- - Treat CI/CD changes as high-risk \u2014 explain blast radius before applying`
3812
+ - Treat CI/CD changes as high-risk \u2014 explain blast radius before applying
3813
+ - For remote SSH work, start with ssh_list_servers / ssh_connection_status, prefer read-only checks first, and do not run destructive commands without explicit user approval`
3792
3814
  },
3793
3815
  budget: MEDIUM_BUDGET,
3794
3816
  capability: {
@@ -3805,6 +3827,13 @@ Working rules:
3805
3827
  "kubernetes",
3806
3828
  "k8s",
3807
3829
  "deploy",
3830
+ "ssh",
3831
+ "remote ssh",
3832
+ "remote server",
3833
+ "sftp",
3834
+ "tunnel",
3835
+ "bastion",
3836
+ "jump host",
3808
3837
  "github actions",
3809
3838
  "container"
3810
3839
  ]
@@ -5403,7 +5432,7 @@ var SubagentBudget = class _SubagentBudget {
5403
5432
  */
5404
5433
  _busRequestDecision(entry) {
5405
5434
  const bus = this._events;
5406
- if (!bus || !bus.hasListenerFor("budget.threshold_reached")) {
5435
+ if (!bus?.hasListenerFor("budget.threshold_reached")) {
5407
5436
  return Promise.resolve("stop");
5408
5437
  }
5409
5438
  return new Promise((resolve3) => {
@@ -6800,6 +6829,7 @@ var DefaultMultiAgentCoordinator = class _DefaultMultiAgentCoordinator extends E
6800
6829
  subagentId: result.subagentId,
6801
6830
  taskId: result.taskId,
6802
6831
  status: result.status,
6832
+ result: result.result,
6803
6833
  iterations: result.iterations,
6804
6834
  toolCalls: result.toolCalls,
6805
6835
  durationMs: result.durationMs
@@ -8733,6 +8763,7 @@ var DefaultSessionStore = class _DefaultSessionStore {
8733
8763
  * processes. When the limit is reached, the oldest entry is evicted.
8734
8764
  */
8735
8765
  _loadCache = /* @__PURE__ */ new Map();
8766
+ _indexCache = null;
8736
8767
  static LOAD_CACHE_MAX_ENTRIES = 50;
8737
8768
  constructor(opts) {
8738
8769
  this.dir = opts.dir;
@@ -8894,16 +8925,13 @@ var DefaultSessionStore = class _DefaultSessionStore {
8894
8925
  let errorMsg;
8895
8926
  let cacheHit = false;
8896
8927
  try {
8897
- let stat6;
8898
- try {
8899
- const s = await fsp6.stat(file);
8900
- stat6 = { mtimeMs: s.mtimeMs, size: s.size };
8901
- } catch (err) {
8902
- throw err;
8903
- }
8928
+ const s = await fsp6.stat(file);
8929
+ const stat7 = { mtimeMs: s.mtimeMs, size: s.size };
8904
8930
  const cached = this._loadCache.get(id);
8905
- if (cached && cached.mtimeMs === stat6.mtimeMs && cached.size === stat6.size) {
8931
+ if (cached && cached.mtimeMs === stat7.mtimeMs && cached.size === stat7.size) {
8906
8932
  cacheHit = true;
8933
+ this._loadCache.delete(id);
8934
+ this._loadCache.set(id, cached);
8907
8935
  return cached.data;
8908
8936
  }
8909
8937
  const raw = await fsp6.readFile(file, "utf8");
@@ -8928,7 +8956,7 @@ var DefaultSessionStore = class _DefaultSessionStore {
8928
8956
  this._loadCache.delete(oldest);
8929
8957
  }
8930
8958
  }
8931
- this._loadCache.set(id, { mtimeMs: stat6.mtimeMs, size: stat6.size, data });
8959
+ this._loadCache.set(id, { mtimeMs: stat7.mtimeMs, size: stat7.size, data });
8932
8960
  return data;
8933
8961
  } catch (err) {
8934
8962
  outcome = "failure";
@@ -8989,6 +9017,7 @@ var DefaultSessionStore = class _DefaultSessionStore {
8989
9017
  await ensureDir(this.dir);
8990
9018
  const line = JSON.stringify(summary) + "\n";
8991
9019
  await fsp6.appendFile(this.indexFile, line, "utf8");
9020
+ this._indexCache = null;
8992
9021
  this.indexAppendCount++;
8993
9022
  if (this.indexAppendCount >= _DefaultSessionStore.COMPACT_EVERY) {
8994
9023
  await this.compactIndex();
@@ -9003,6 +9032,7 @@ var DefaultSessionStore = class _DefaultSessionStore {
9003
9032
  await ensureDir(this.dir);
9004
9033
  const line = JSON.stringify({ action: "delete", id }) + "\n";
9005
9034
  await fsp6.appendFile(this.indexFile, line, "utf8");
9035
+ this._indexCache = null;
9006
9036
  this.indexAppendCount++;
9007
9037
  } catch {
9008
9038
  }
@@ -9022,6 +9052,7 @@ var DefaultSessionStore = class _DefaultSessionStore {
9022
9052
  const lines = entries.map((s) => JSON.stringify(s)).join("\n") + "\n";
9023
9053
  await fsp6.writeFile(tmp, lines, "utf8");
9024
9054
  await fsp6.rename(tmp, this.indexFile);
9055
+ this._indexCache = null;
9025
9056
  } catch (err) {
9026
9057
  outcome = "failure";
9027
9058
  errorMsg = toErrorMessage(err);
@@ -9035,10 +9066,22 @@ var DefaultSessionStore = class _DefaultSessionStore {
9035
9066
  * Returns empty array when the index doesn't exist or is corrupt.
9036
9067
  */
9037
9068
  async readIndex() {
9069
+ let stat7;
9070
+ try {
9071
+ const s = await fsp6.stat(this.indexFile);
9072
+ stat7 = { mtimeMs: s.mtimeMs, size: s.size };
9073
+ } catch {
9074
+ this._indexCache = null;
9075
+ return [];
9076
+ }
9077
+ if (this._indexCache !== null && this._indexCache.mtimeMs === stat7.mtimeMs && this._indexCache.size === stat7.size) {
9078
+ return [...this._indexCache.summaries];
9079
+ }
9038
9080
  let raw;
9039
9081
  try {
9040
9082
  raw = await fsp6.readFile(this.indexFile, "utf8");
9041
9083
  } catch {
9084
+ this._indexCache = null;
9042
9085
  return [];
9043
9086
  }
9044
9087
  const deleted = /* @__PURE__ */ new Set();
@@ -9058,7 +9101,9 @@ var DefaultSessionStore = class _DefaultSessionStore {
9058
9101
  } catch {
9059
9102
  }
9060
9103
  }
9061
- return Array.from(seen.values());
9104
+ const summaries = Array.from(seen.values());
9105
+ this._indexCache = { ...stat7, summaries };
9106
+ return [...summaries];
9062
9107
  }
9063
9108
  /**
9064
9109
  * Rebuild the index from disk by scanning all sessions and writing a
@@ -9072,6 +9117,7 @@ var DefaultSessionStore = class _DefaultSessionStore {
9072
9117
  const lines = valid.map((s) => JSON.stringify(s)).join("\n") + "\n";
9073
9118
  await fsp6.writeFile(tmp, lines, "utf8");
9074
9119
  await fsp6.rename(tmp, this.indexFile);
9120
+ this._indexCache = null;
9075
9121
  return valid.length;
9076
9122
  }
9077
9123
  /** Recursively collect session IDs from date-shard subdirectories.
@@ -9112,8 +9158,8 @@ var DefaultSessionStore = class _DefaultSessionStore {
9112
9158
  return JSON.parse(raw);
9113
9159
  } catch {
9114
9160
  const full = this.sessionPath(id, ".jsonl");
9115
- const stat6 = await fsp6.stat(full);
9116
- const summary = await this.summarize(id, stat6.mtime.toISOString());
9161
+ const stat7 = await fsp6.stat(full);
9162
+ const summary = await this.summarize(id, stat7.mtime.toISOString());
9117
9163
  await atomicWrite(manifest, JSON.stringify(summary), { mode: 384 }).catch((err) => {
9118
9164
  const msg = toErrorMessage(err);
9119
9165
  this.emitError(id, manifest, "summary_fallback", msg, true);
@@ -9195,8 +9241,8 @@ var DefaultSessionStore = class _DefaultSessionStore {
9195
9241
  const pruneFile = async (dir, name, prefix) => {
9196
9242
  const jsonlPath = path5.join(dir, name);
9197
9243
  try {
9198
- const stat6 = await fsp6.stat(jsonlPath);
9199
- if (stat6.mtimeMs >= cutoff) return;
9244
+ const stat7 = await fsp6.stat(jsonlPath);
9245
+ if (stat7.mtimeMs >= cutoff) return;
9200
9246
  } catch {
9201
9247
  return;
9202
9248
  }
@@ -9771,6 +9817,12 @@ var FileSessionWriter = class _FileSessionWriter {
9771
9817
  files
9772
9818
  });
9773
9819
  }
9820
+ /**
9821
+ * Truncate the session file to the checkpoint with the given promptIndex,
9822
+ * removing all events that follow it. Uses a single-pass byte-offset scan
9823
+ * so post-checkpoint content is never read or parsed — O(1) memory instead
9824
+ * of O(N) JSON.parse calls over the full file.
9825
+ */
9774
9826
  async truncateToCheckpoint(targetPromptIndex) {
9775
9827
  if (!this.filePath) return 0;
9776
9828
  if (this.flushTimer) {
@@ -9779,51 +9831,118 @@ var FileSessionWriter = class _FileSessionWriter {
9779
9831
  }
9780
9832
  await this.flushBuffer();
9781
9833
  await this.writeChain;
9782
- const raw = await fsp6.readFile(this.filePath, "utf8");
9783
- const lines = raw.split("\n");
9784
- const kept = [];
9834
+ const CHUNK_SIZE = 65536;
9835
+ let fd;
9836
+ let fileOffset = 0;
9837
+ let lineStartOffset = 0;
9838
+ let checkpointByteOffset = -1;
9785
9839
  let removedCount = 0;
9786
- let targetCheckpointLine = -1;
9787
- let afterTarget = false;
9788
- for (let i = 0; i < lines.length; i++) {
9789
- const line = expectDefined(lines[i]);
9790
- if (!line.trim()) continue;
9791
- let event;
9792
- try {
9793
- event = JSON.parse(line);
9794
- } catch {
9795
- kept.push(line);
9796
- continue;
9797
- }
9798
- if (event.type === "checkpoint") {
9799
- if (event.promptIndex === targetPromptIndex) {
9800
- targetCheckpointLine = kept.length;
9801
- afterTarget = true;
9802
- } else if (event.promptIndex > targetPromptIndex) {
9803
- afterTarget = true;
9840
+ let targetCheckpointSeen = false;
9841
+ try {
9842
+ fd = await fsp6.open(this.filePath, "r", 384);
9843
+ while (true) {
9844
+ const buf = Buffer.alloc(CHUNK_SIZE);
9845
+ const { bytesRead } = await fd.read(buf, 0, CHUNK_SIZE, fileOffset);
9846
+ if (bytesRead === 0) break;
9847
+ let chunkPos = 0;
9848
+ while (chunkPos < bytesRead) {
9849
+ const idx = buf.indexOf("\n", chunkPos);
9850
+ if (idx === -1) {
9851
+ lineStartOffset = fileOffset + chunkPos;
9852
+ break;
9853
+ }
9854
+ if (checkpointByteOffset !== -1) {
9855
+ removedCount++;
9856
+ } else {
9857
+ const lineBytes = buf.subarray(chunkPos, idx);
9858
+ const line = new TextDecoder("utf-8", { fatal: false }).decode(lineBytes);
9859
+ if (line.trim()) {
9860
+ try {
9861
+ const event = JSON.parse(line);
9862
+ if (event.type === "checkpoint") {
9863
+ if (event.promptIndex === targetPromptIndex) {
9864
+ checkpointByteOffset = lineStartOffset;
9865
+ targetCheckpointSeen = true;
9866
+ } else if (event.promptIndex !== void 0 && event.promptIndex > targetPromptIndex) {
9867
+ checkpointByteOffset = lineStartOffset;
9868
+ }
9869
+ } else if (targetCheckpointSeen && event.promptIndex !== void 0 && event.promptIndex > targetPromptIndex) {
9870
+ removedCount++;
9871
+ } else if (targetCheckpointSeen && event.promptIndex === void 0) {
9872
+ removedCount++;
9873
+ } else if (!targetCheckpointSeen && event.promptIndex === void 0) {
9874
+ removedCount++;
9875
+ } else if (!targetCheckpointSeen && event.promptIndex !== void 0 && event.promptIndex > targetPromptIndex) {
9876
+ removedCount++;
9877
+ }
9878
+ } catch {
9879
+ }
9880
+ }
9881
+ }
9882
+ chunkPos = idx + 1;
9883
+ lineStartOffset = fileOffset + chunkPos;
9804
9884
  }
9805
- }
9806
- if (event.promptIndex !== void 0 && event.promptIndex > targetPromptIndex) {
9807
- removedCount++;
9808
- } else if (event.promptIndex === void 0) {
9809
- if (!afterTarget || targetCheckpointLine === -1) {
9810
- kept.push(line);
9811
- } else {
9812
- removedCount++;
9885
+ fileOffset += bytesRead;
9886
+ if (chunkPos >= bytesRead) {
9887
+ lineStartOffset = fileOffset;
9813
9888
  }
9814
- } else {
9815
- kept.push(line);
9816
9889
  }
9890
+ } finally {
9891
+ await fd?.close();
9817
9892
  }
9818
- const truncated = kept.join("\n");
9893
+ if (checkpointByteOffset === -1) return 0;
9894
+ await this.writeChain;
9895
+ await this.handle.close();
9819
9896
  const tmpPath = `${this.filePath}.rewind.tmp`;
9820
- await fsp6.writeFile(tmpPath, truncated + "\n", "utf8");
9897
+ const src = await fsp6.open(this.filePath, "r", 384);
9821
9898
  try {
9822
- await this.handle.close();
9899
+ const statResult = await src.stat();
9900
+ const totalSize = statResult.size;
9901
+ const prefixBytes = checkpointByteOffset;
9902
+ let newlineAfterCheckpoint = prefixBytes;
9903
+ if (prefixBytes < totalSize) {
9904
+ const probeBuf = Buffer.alloc(Math.min(CHUNK_SIZE, totalSize - prefixBytes));
9905
+ const { bytesRead: probeRead } = await src.read(probeBuf, 0, probeBuf.length, prefixBytes);
9906
+ if (probeRead > 0) {
9907
+ const nl = probeBuf.indexOf("\n");
9908
+ newlineAfterCheckpoint = nl !== -1 ? prefixBytes + nl + 1 : totalSize;
9909
+ }
9910
+ } else {
9911
+ newlineAfterCheckpoint = totalSize;
9912
+ }
9913
+ const writeFd = await fsp6.open(tmpPath, "w", 384);
9914
+ try {
9915
+ let copied = 0;
9916
+ let readOffset = 0;
9917
+ while (readOffset < newlineAfterCheckpoint) {
9918
+ const toCopy = Math.min(CHUNK_SIZE, newlineAfterCheckpoint - readOffset);
9919
+ const copyBuf = Buffer.alloc(toCopy);
9920
+ const { bytesRead: r } = await src.read(copyBuf, 0, toCopy, readOffset);
9921
+ if (r === 0) break;
9922
+ await writeFd.write(copyBuf, 0, r);
9923
+ readOffset += r;
9924
+ copied += r;
9925
+ }
9926
+ const raw = await fsp6.readFile(this.filePath);
9927
+ const tail = raw.subarray(newlineAfterCheckpoint).toString("utf8");
9928
+ for (const line of tail.split("\n")) {
9929
+ if (!line.trim()) continue;
9930
+ try {
9931
+ JSON.parse(line);
9932
+ } catch {
9933
+ await writeFd.write(`${line}
9934
+ `, void 0, "utf8");
9935
+ }
9936
+ }
9937
+ } finally {
9938
+ await writeFd.close();
9939
+ }
9940
+ await src.close();
9823
9941
  await fsp6.rename(tmpPath, this.filePath);
9824
9942
  this.handle = await fsp6.open(this.filePath, "a", 384);
9825
9943
  } catch (err) {
9826
9944
  await fsp6.unlink(tmpPath).catch(() => void 0);
9945
+ this.handle = await fsp6.open(this.filePath, "a", 384).catch(() => this.handle);
9827
9946
  throw err;
9828
9947
  }
9829
9948
  await this.append({
@@ -10362,8 +10481,12 @@ function normalizeRecipient(to) {
10362
10481
  // src/coordination/mailbox.ts
10363
10482
  var MAILBOX_FILE = "_mailbox.jsonl";
10364
10483
  var LINE_SEPARATOR = "\n";
10484
+ var MESSAGE_CACHE_MAX_ENTRIES = 1e4;
10365
10485
  var DefaultMailbox = class {
10366
10486
  filePath;
10487
+ _messageCache = null;
10488
+ _messageCacheMtime = -1;
10489
+ _messageCacheSize = -1;
10367
10490
  constructor(sessionDir) {
10368
10491
  this.filePath = path5.join(sessionDir, MAILBOX_FILE);
10369
10492
  }
@@ -10393,37 +10516,26 @@ var DefaultMailbox = class {
10393
10516
  await fsp6.mkdir(path5.dirname(this.filePath), { recursive: true });
10394
10517
  await withFileLock(this.filePath, async () => {
10395
10518
  await fsp6.appendFile(this.filePath, line, "utf8");
10519
+ this._pushToCache(msg);
10396
10520
  });
10397
10521
  return msg;
10398
10522
  }
10399
10523
  // ── Query ─────────────────────────────────────────────────────────────
10400
10524
  async query(q) {
10401
- const all = await this._readAll();
10525
+ const all = await this._readAllCached();
10402
10526
  const limit = q.limit ?? 50;
10403
- let filtered = all;
10404
- if (q.to !== void 0) {
10405
- filtered = filtered.filter((m) => m.to === q.to || m.to === "*");
10406
- }
10407
- if (q.from !== void 0) {
10408
- filtered = filtered.filter((m) => m.from === q.from);
10409
- }
10410
- if (q.unreadBy !== void 0) {
10411
- filtered = filtered.filter((m) => !(q.unreadBy in m.readBy));
10412
- }
10413
- if (q.incompleteOnly) {
10414
- filtered = filtered.filter((m) => !m.completed);
10415
- }
10416
- if (q.type !== void 0) {
10417
- filtered = filtered.filter((m) => m.type === q.type);
10418
- }
10419
- if (q.minPriority !== void 0) {
10420
- const order = { low: 0, normal: 1, high: 2 };
10421
- const min = order[q.minPriority];
10422
- filtered = filtered.filter((m) => (order[m.priority] ?? 1) >= min);
10423
- }
10424
- if (q.since !== void 0) {
10425
- const since = q.since;
10426
- filtered = filtered.filter((m) => m.timestamp > since);
10527
+ const order = q.minPriority !== void 0 ? { low: 0, normal: 1, high: 2 } : null;
10528
+ const minPriorityRank = order && q.minPriority !== void 0 ? order[q.minPriority] : 0;
10529
+ const filtered = [];
10530
+ for (const msg of all) {
10531
+ if (q.to !== void 0 && msg.to !== q.to && msg.to !== "*") continue;
10532
+ if (q.from !== void 0 && msg.from !== q.from) continue;
10533
+ if (q.unreadBy !== void 0 && q.unreadBy in msg.readBy) continue;
10534
+ if (q.incompleteOnly && msg.completed) continue;
10535
+ if (q.type !== void 0 && msg.type !== q.type) continue;
10536
+ if (order !== null && (order[msg.priority] ?? 1) < minPriorityRank) continue;
10537
+ if (q.since !== void 0 && msg.timestamp <= q.since) continue;
10538
+ filtered.push(msg);
10427
10539
  }
10428
10540
  filtered.sort((a, b) => b.timestamp.localeCompare(a.timestamp));
10429
10541
  return filtered.slice(0, limit);
@@ -10465,12 +10577,13 @@ var DefaultMailbox = class {
10465
10577
  const serialized = all.map((m) => JSON.stringify(m)).join(LINE_SEPARATOR) + LINE_SEPARATOR;
10466
10578
  await fsp6.writeFile(this.filePath, serialized, "utf8");
10467
10579
  }
10580
+ this._setMessageCache(all);
10468
10581
  });
10469
10582
  return updated;
10470
10583
  }
10471
10584
  // ── Agent statuses ────────────────────────────────────────────────────
10472
10585
  async getAgentStatuses() {
10473
- const all = await this._readAll();
10586
+ const all = await this._readAllCached();
10474
10587
  const latest = /* @__PURE__ */ new Map();
10475
10588
  for (const m of all) {
10476
10589
  if (m.type !== "status") continue;
@@ -10506,16 +10619,20 @@ var DefaultMailbox = class {
10506
10619
  async heartbeat(_input) {
10507
10620
  }
10508
10621
  async unreadCount(forAgentId) {
10509
- const all = await this._readAll();
10622
+ const all = await this._readAllCached();
10510
10623
  return all.filter(
10511
10624
  (m) => (m.to === forAgentId || m.to === "*") && !(forAgentId in m.readBy) && !m.completed
10512
10625
  ).length;
10513
10626
  }
10514
10627
  async close() {
10628
+ this._messageCache = null;
10629
+ this._messageCacheMtime = -1;
10630
+ this._messageCacheSize = -1;
10515
10631
  }
10516
10632
  async clearAll() {
10517
10633
  await withFileLock(this.filePath, async () => {
10518
10634
  await fsp6.writeFile(this.filePath, "", "utf8");
10635
+ this._setMessageCache([]);
10519
10636
  });
10520
10637
  }
10521
10638
  async purgeStale(opts) {
@@ -10523,13 +10640,14 @@ var DefaultMailbox = class {
10523
10640
  const INCOMPLETE_MAX_AGE_MS = opts?.incompleteMaxAgeMs ?? 6048e5;
10524
10641
  let completedPurged = 0;
10525
10642
  let incompletePurged = 0;
10643
+ let remaining = 0;
10526
10644
  await withFileLock(this.filePath, async () => {
10527
- const all2 = await this._readAll();
10645
+ const all = await this._readAll();
10528
10646
  const now = Date.now();
10529
10647
  const cutoffCompleted = now - COMPLETED_MAX_AGE_MS;
10530
10648
  const cutoffIncomplete = now - INCOMPLETE_MAX_AGE_MS;
10531
10649
  const kept = [];
10532
- for (const msg of all2) {
10650
+ for (const msg of all) {
10533
10651
  const msgTime = new Date(msg.timestamp).getTime();
10534
10652
  const completedTime = msg.completedAt ? new Date(msg.completedAt).getTime() : 0;
10535
10653
  if (msg.completed && completedTime < cutoffCompleted) {
@@ -10542,17 +10660,18 @@ var DefaultMailbox = class {
10542
10660
  }
10543
10661
  kept.push(msg);
10544
10662
  }
10545
- if (kept.length < all2.length) {
10663
+ remaining = kept.length;
10664
+ if (kept.length < all.length) {
10546
10665
  const content = kept.map((m) => JSON.stringify(m)).join(LINE_SEPARATOR) + LINE_SEPARATOR;
10547
10666
  await fsp6.writeFile(this.filePath, content, "utf8");
10548
10667
  }
10668
+ this._setMessageCache(kept);
10549
10669
  });
10550
- const all = await this._readAll();
10551
10670
  return {
10552
10671
  completedPurged,
10553
10672
  incompletePurged,
10554
10673
  totalPurged: completedPurged + incompletePurged,
10555
- remaining: all.length
10674
+ remaining
10556
10675
  };
10557
10676
  }
10558
10677
  // ── Client registry stubs (not applicable per-session) ─────────────────
@@ -10591,6 +10710,52 @@ var DefaultMailbox = class {
10591
10710
  throw err;
10592
10711
  }
10593
10712
  }
10713
+ async _readAllCached() {
10714
+ try {
10715
+ const st = await fsp6.stat(this.filePath);
10716
+ if (this._messageCache !== null && this._messageCacheMtime === st.mtimeMs && this._messageCacheSize === st.size) {
10717
+ return this._messageCache;
10718
+ }
10719
+ const all = await this._readAll();
10720
+ this._setMessageCache(all, st.mtimeMs, st.size);
10721
+ return all;
10722
+ } catch (err) {
10723
+ if (err.code === "ENOENT") {
10724
+ this._setMessageCache([], -1, -1);
10725
+ return [];
10726
+ }
10727
+ throw err;
10728
+ }
10729
+ }
10730
+ _setMessageCache(messages, mtime, size) {
10731
+ if (messages.length > MESSAGE_CACHE_MAX_ENTRIES) {
10732
+ this._messageCache = null;
10733
+ this._messageCacheMtime = -1;
10734
+ this._messageCacheSize = -1;
10735
+ return;
10736
+ }
10737
+ this._messageCache = messages;
10738
+ if (mtime !== void 0 && size !== void 0) {
10739
+ this._messageCacheMtime = mtime;
10740
+ this._messageCacheSize = size;
10741
+ return;
10742
+ }
10743
+ void fsp6.stat(this.filePath).then((st) => {
10744
+ this._messageCacheMtime = st.mtimeMs;
10745
+ this._messageCacheSize = st.size;
10746
+ }).catch(() => {
10747
+ });
10748
+ }
10749
+ _pushToCache(msg) {
10750
+ if (this._messageCache === null) return;
10751
+ if (this._messageCache.length >= MESSAGE_CACHE_MAX_ENTRIES) {
10752
+ this._messageCache = null;
10753
+ this._messageCacheMtime = -1;
10754
+ this._messageCacheSize = -1;
10755
+ return;
10756
+ }
10757
+ this._messageCache.push(msg);
10758
+ }
10594
10759
  };
10595
10760
  var BrainMonitor = class {
10596
10761
  constructor(opts) {
@@ -10731,7 +10896,7 @@ var CLIENT_STALE_MS = 6e4;
10731
10896
  var HEARTBEAT_THROTTLE_MS = 5e3;
10732
10897
  var REGISTRY_CACHE_TTL_MS = 2e3;
10733
10898
  var LINE_SEPARATOR2 = "\n";
10734
- var MESSAGE_CACHE_MAX_ENTRIES = 1e4;
10899
+ var MESSAGE_CACHE_MAX_ENTRIES2 = 1e4;
10735
10900
  function resolveProjectDir(projectRoot, globalRoot) {
10736
10901
  return path5.join(globalRoot, "projects", projectSlug(projectRoot));
10737
10902
  }
@@ -10744,6 +10909,8 @@ var GlobalMailbox = class {
10744
10909
  clientRegistryPath;
10745
10910
  /** Optional event bus for emitting agent registration/heartbeat events. */
10746
10911
  _events;
10912
+ /** Optional HQ publisher for cross-project command-center telemetry. */
10913
+ _hqPublisher;
10747
10914
  /**
10748
10915
  * Local cache of the agent registry to avoid re-reading on every call.
10749
10916
  * Time-bounded: the registry file is shared ACROSS PROCESSES (that's the
@@ -10781,12 +10948,28 @@ var GlobalMailbox = class {
10781
10948
  /**
10782
10949
  * @param projectDir — `~/.wrongstack/projects/<slug>/`
10783
10950
  * @param events — optional EventBus for real-time TUI/WebUI notifications
10951
+ * @param hqPublisher — optional HQ publisher for cross-project telemetry
10784
10952
  */
10785
- constructor(projectDir, events) {
10953
+ constructor(projectDir, events, hqPublisher) {
10786
10954
  this.messagePath = path5.join(projectDir, MAILBOX_FILE2);
10787
10955
  this.registryPath = path5.join(projectDir, "_mailbox.registry.json");
10788
10956
  this.clientRegistryPath = path5.join(projectDir, CLIENT_REGISTRY_FILE);
10789
10957
  this._events = events;
10958
+ this._hqPublisher = hqPublisher;
10959
+ }
10960
+ get hqMailboxId() {
10961
+ return `${path5.basename(path5.dirname(this.messagePath))}:mailbox`;
10962
+ }
10963
+ publishHqMailboxEvent(input) {
10964
+ try {
10965
+ this._hqPublisher?.publishMailboxEvent(input);
10966
+ } catch {
10967
+ }
10968
+ }
10969
+ publishHqMailboxSnapshot() {
10970
+ if (this._hqPublisher === void 0) return;
10971
+ void this._hqPublisher.publishMailboxSnapshot(this, { mailboxId: this.hqMailboxId }).catch(() => {
10972
+ });
10790
10973
  }
10791
10974
  // ── Messages ────────────────────────────────────────────────────────────
10792
10975
  async send(input) {
@@ -10813,6 +10996,8 @@ var GlobalMailbox = class {
10813
10996
  await fsp6.appendFile(this.messagePath, line, "utf8");
10814
10997
  this._pushToCache(msg);
10815
10998
  });
10999
+ this.publishHqMailboxEvent({ mailboxId: this.hqMailboxId, action: "message.sent", message: msg });
11000
+ this.publishHqMailboxSnapshot();
10816
11001
  return msg;
10817
11002
  }
10818
11003
  async query(q) {
@@ -10879,6 +11064,14 @@ var GlobalMailbox = class {
10879
11064
  cacheSnapshot = all;
10880
11065
  });
10881
11066
  if (cacheSnapshot) this._setMessageCache(cacheSnapshot);
11067
+ for (const message of updated) {
11068
+ this.publishHqMailboxEvent({
11069
+ mailboxId: this.hqMailboxId,
11070
+ action: message.completed ? "message.completed" : "message.read",
11071
+ message
11072
+ });
11073
+ }
11074
+ if (updated.length > 0) this.publishHqMailboxSnapshot();
10882
11075
  return updated;
10883
11076
  }
10884
11077
  async unreadCount(forAgentId) {
@@ -10926,6 +11119,25 @@ var GlobalMailbox = class {
10926
11119
  role: input.role,
10927
11120
  source: input.source
10928
11121
  });
11122
+ this.publishHqMailboxEvent({
11123
+ mailboxId: this.hqMailboxId,
11124
+ action: "agent.registered",
11125
+ agent: {
11126
+ agentId: input.agentId,
11127
+ name: input.name,
11128
+ ...input.role !== void 0 ? { role: input.role } : {},
11129
+ sessionId: input.sessionId,
11130
+ status: "idle",
11131
+ iterations: 0,
11132
+ toolCalls: 0,
11133
+ lastActivityAt: now,
11134
+ lastSeenAt: now,
11135
+ online: true,
11136
+ pid: input.pid,
11137
+ ...input.source !== void 0 ? { source: input.source } : {}
11138
+ }
11139
+ });
11140
+ this.publishHqMailboxSnapshot();
10929
11141
  }
10930
11142
  async heartbeat(input) {
10931
11143
  const last = this._lastHeartbeat.get(input.agentId) ?? 0;
@@ -10956,6 +11168,12 @@ var GlobalMailbox = class {
10956
11168
  currentTool: input.currentTool,
10957
11169
  currentTask: input.currentTask
10958
11170
  });
11171
+ this.publishHqMailboxEvent({
11172
+ mailboxId: this.hqMailboxId,
11173
+ action: "agent.heartbeat",
11174
+ summary: input.agentId
11175
+ });
11176
+ this.publishHqMailboxSnapshot();
10959
11177
  }
10960
11178
  async getAgentStatuses() {
10961
11179
  await this._ensureRegistry();
@@ -11010,6 +11228,7 @@ var GlobalMailbox = class {
11010
11228
  name: input.name,
11011
11229
  source: input.source
11012
11230
  });
11231
+ this.publishHqMailboxSnapshot();
11013
11232
  }
11014
11233
  async clientHeartbeat(input) {
11015
11234
  const last = this._lastClientHeartbeat.get(input.clientId) ?? 0;
@@ -11031,6 +11250,7 @@ var GlobalMailbox = class {
11031
11250
  this._events?.emitCustom("mailbox.client_heartbeat", {
11032
11251
  clientId: input.clientId
11033
11252
  });
11253
+ this.publishHqMailboxSnapshot();
11034
11254
  }
11035
11255
  async getClientStatuses() {
11036
11256
  await this._ensureClientRegistry();
@@ -11177,7 +11397,7 @@ var GlobalMailbox = class {
11177
11397
  * read or wrote it under the file lock).
11178
11398
  */
11179
11399
  _setMessageCache(messages, mtime, size) {
11180
- if (messages.length > MESSAGE_CACHE_MAX_ENTRIES) {
11400
+ if (messages.length > MESSAGE_CACHE_MAX_ENTRIES2) {
11181
11401
  this._messageCache = null;
11182
11402
  this._messageCacheMtime = -1;
11183
11403
  this._messageCacheSize = -1;
@@ -11203,7 +11423,7 @@ var GlobalMailbox = class {
11203
11423
  */
11204
11424
  _pushToCache(msg) {
11205
11425
  if (this._messageCache === null) return;
11206
- if (this._messageCache.length >= MESSAGE_CACHE_MAX_ENTRIES) {
11426
+ if (this._messageCache.length >= MESSAGE_CACHE_MAX_ENTRIES2) {
11207
11427
  this._messageCache = null;
11208
11428
  this._messageCacheMtime = -1;
11209
11429
  this._messageCacheSize = -1;
@@ -11252,7 +11472,7 @@ var GlobalMailbox = class {
11252
11472
  obj[id] = agent;
11253
11473
  }
11254
11474
  const tmp = `${this.registryPath}.${randomUUID().slice(0, 8)}.tmp`;
11255
- await fsp6.writeFile(tmp, JSON.stringify(obj, null, 2), "utf8");
11475
+ await fsp6.writeFile(tmp, JSON.stringify(obj), "utf8");
11256
11476
  await fsp6.rename(tmp, this.registryPath);
11257
11477
  }
11258
11478
  // ── Client registry internals ───────────────────────────────────────────
@@ -11297,7 +11517,7 @@ var GlobalMailbox = class {
11297
11517
  obj[id] = client;
11298
11518
  }
11299
11519
  const tmp = `${this.clientRegistryPath}.${randomUUID().slice(0, 8)}.tmp`;
11300
- await fsp6.writeFile(tmp, JSON.stringify(obj, null, 2), "utf8");
11520
+ await fsp6.writeFile(tmp, JSON.stringify(obj), "utf8");
11301
11521
  await fsp6.rename(tmp, this.clientRegistryPath);
11302
11522
  }
11303
11523
  };
@@ -11768,13 +11988,13 @@ function makeDependencyWatcherConfig(opts) {
11768
11988
  const globPatterns = patterns.filter((p) => p.includes("*"));
11769
11989
  const plainPatterns = patterns.filter((p) => !p.includes("*"));
11770
11990
  function matchesPattern(filePath) {
11771
- const basename5 = filePath.split("/").pop()?.split("\\").pop() ?? "";
11772
- if (plainPatterns.includes(basename5)) return true;
11991
+ const basename6 = filePath.split("/").pop()?.split("\\").pop() ?? "";
11992
+ if (plainPatterns.includes(basename6)) return true;
11773
11993
  for (const gp of globPatterns) {
11774
11994
  const regex = new RegExp(
11775
11995
  "^" + gp.replace(/\./g, "\\.").replace(/\*/g, ".*") + "$"
11776
11996
  );
11777
- if (regex.test(basename5)) return true;
11997
+ if (regex.test(basename6)) return true;
11778
11998
  }
11779
11999
  return false;
11780
12000
  }
@@ -12490,7 +12710,7 @@ var TaskDAG = class {
12490
12710
  }
12491
12711
  for (const depId of node.dependents) {
12492
12712
  const dep = this.nodes.get(depId);
12493
- if (dep && dep.deps.every((d) => !this.nodes.has(d) || this.nodes.get(d).status === "done")) {
12713
+ if (dep?.deps.every((d) => !this.nodes.has(d) || this.nodes.get(d).status === "done")) {
12494
12714
  this._transition(depId, "pending", "ready");
12495
12715
  }
12496
12716
  }
@@ -12750,12 +12970,12 @@ var ConsensusProtocol = class {
12750
12970
  * Initiate a vote on a proposed change. Updates the change node's status
12751
12971
  * to 'proposed' and notifies eligible voters via FleetBus.
12752
12972
  */
12753
- initiateVote(changeId) {
12973
+ async initiateVote(changeId) {
12754
12974
  const change = this.graph.get(changeId);
12755
- if (!change || change.type !== "change") {
12975
+ if (change?.type !== "change") {
12756
12976
  throw new Error(`ConsensusProtocol: no change found with id "${changeId}"`);
12757
12977
  }
12758
- this.graph.update(changeId, { status: "proposed", votes: [] });
12978
+ await this.graph.update(changeId, { status: "proposed", votes: [] });
12759
12979
  const eligible = this._eligibleVoters(change);
12760
12980
  this._notifyVoters(change, eligible, "vote_initiated");
12761
12981
  }
@@ -12763,9 +12983,9 @@ var ConsensusProtocol = class {
12763
12983
  * Cast a vote. Updates the change node in the graph and re-evaluates
12764
12984
  * consensus. If the vote triggers a resolution, updates the change status.
12765
12985
  */
12766
- castVote(changeId, voterId, value, rationale) {
12986
+ async castVote(changeId, voterId, value, rationale) {
12767
12987
  const change = this.graph.get(changeId);
12768
- if (!change || change.type !== "change") {
12988
+ if (change?.type !== "change") {
12769
12989
  throw new Error(`ConsensusProtocol: no change found for "${changeId}"`);
12770
12990
  }
12771
12991
  const voter = this.voters.get(voterId);
@@ -12786,7 +13006,7 @@ var ConsensusProtocol = class {
12786
13006
  const existingIdx = change.votes.findIndex((v) => v.agentId === voterId);
12787
13007
  const newVotes = existingIdx >= 0 ? change.votes.with(existingIdx, vote) : [...change.votes, vote];
12788
13008
  const result = this._resolve(changeId, newVotes, eligible);
12789
- this.graph.update(changeId, {
13009
+ await this.graph.update(changeId, {
12790
13010
  votes: newVotes,
12791
13011
  ...result.outcome !== "pending" ? { status: this._toChangeStatus(result.outcome) } : {}
12792
13012
  });
@@ -12797,13 +13017,13 @@ var ConsensusProtocol = class {
12797
13017
  * Resolve the current vote without waiting for all eligible voters.
12798
13018
  * Useful when a timeout fires or an agent decides to finalize early.
12799
13019
  */
12800
- resolveNow(changeId) {
13020
+ async resolveNow(changeId) {
12801
13021
  const change = this.graph.get(changeId);
12802
13022
  if (!change) throw new Error(`ConsensusProtocol: unknown change "${changeId}"`);
12803
13023
  const eligible = this._eligibleVoters(change);
12804
13024
  const result = this._resolve(changeId, change.votes, eligible);
12805
13025
  if (result.outcome !== "pending") {
12806
- this.graph.update(changeId, { status: this._toChangeStatus(result.outcome) });
13026
+ await this.graph.update(changeId, { status: this._toChangeStatus(result.outcome) });
12807
13027
  this._notifyVoters(change, eligible, "vote_resolved", { result });
12808
13028
  }
12809
13029
  return result;
@@ -12819,7 +13039,7 @@ var ConsensusProtocol = class {
12819
13039
  */
12820
13040
  getStatus(changeId) {
12821
13041
  const change = this.graph.get(changeId);
12822
- if (!change || change.type !== "change") return null;
13042
+ if (change?.type !== "change") return null;
12823
13043
  const eligible = this._eligibleVoters(change);
12824
13044
  return this._resolve(changeId, change.votes, eligible);
12825
13045
  }
@@ -13022,7 +13242,7 @@ var ChangeManager = class {
13022
13242
  */
13023
13243
  async submitForReview(changeId) {
13024
13244
  const change = this.graph.get(changeId);
13025
- if (!change || change.type !== "change") {
13245
+ if (change?.type !== "change") {
13026
13246
  throw new Error(`ChangeManager: no change found "${changeId}"`);
13027
13247
  }
13028
13248
  if (change.status !== "proposed") {
@@ -13084,7 +13304,7 @@ var ChangeManager = class {
13084
13304
  */
13085
13305
  async proposeRollback(appliedChangeId, reason) {
13086
13306
  const original = this.graph.get(appliedChangeId);
13087
- if (!original || original.type !== "change") return null;
13307
+ if (original?.type !== "change") return null;
13088
13308
  const rollbackFiles = original.files.map((f) => ({
13089
13309
  path: f.path,
13090
13310
  action: f.action === "create" ? "delete" : f.action === "delete" ? "create" : "modify"
@@ -13638,7 +13858,7 @@ var TaskAuctioneer = class {
13638
13858
  */
13639
13859
  async bid(taskId, agent, rationale) {
13640
13860
  const goal = this.graph.get(taskId);
13641
- if (!goal || goal.type !== "goal") return false;
13861
+ if (goal?.type !== "goal") return false;
13642
13862
  if (goal.status !== "pending") return false;
13643
13863
  const currentCount = this._getAgentTaskCount(agent.agentId);
13644
13864
  if (currentCount >= this.maxTasksPerAgent) return false;
@@ -13687,7 +13907,7 @@ Score: ${score.toFixed(2)}`
13687
13907
  */
13688
13908
  async claim(taskId, agentId, agentName) {
13689
13909
  const goal = this.graph.get(taskId);
13690
- if (!goal || goal.type !== "goal") return false;
13910
+ if (goal?.type !== "goal") return false;
13691
13911
  if (goal.status !== "pending") return false;
13692
13912
  this._cancelBidWindow(taskId);
13693
13913
  await this.graph.update(taskId, {
@@ -13726,7 +13946,8 @@ Priority: ${goal.priority}`,
13726
13946
  this.agentTaskCount(agentId, -1);
13727
13947
  await this.graph.update(taskId, {
13728
13948
  status: "done",
13729
- updatedAt: (/* @__PURE__ */ new Date()).toISOString()
13949
+ updatedAt: (/* @__PURE__ */ new Date()).toISOString(),
13950
+ ..._result !== void 0 ? { result: _result } : {}
13730
13951
  });
13731
13952
  this.bidRetryCounts.delete(taskId);
13732
13953
  this.pendingBids.delete(taskId);
@@ -13971,7 +14192,7 @@ ${goal.description}`,
13971
14192
  this.agentTaskCounts.set(agentId, next);
13972
14193
  }
13973
14194
  };
13974
- var AutonomousCoordinator = class {
14195
+ var AutonomousCoordinator = class _AutonomousCoordinator {
13975
14196
  graph;
13976
14197
  dag;
13977
14198
  auction;
@@ -13987,6 +14208,8 @@ var AutonomousCoordinator = class {
13987
14208
  onCoordinatorEvent;
13988
14209
  running = false;
13989
14210
  iterationCount = 0;
14211
+ lastSyncAt = 0;
14212
+ static SYNC_INTERVAL_MS = 5e3;
13990
14213
  /** Tasks already handled by _onSubagentTerminated (to avoid double goal:failed on fleet event). */
13991
14214
  _handledBySubagent = /* @__PURE__ */ new Set();
13992
14215
  /** FleetBus subscription disposers, detached in dispose(). */
@@ -14043,7 +14266,7 @@ var AutonomousCoordinator = class {
14043
14266
  const taskId = payload?.taskId;
14044
14267
  if (!taskId || this._handledBySubagent.has(taskId)) return;
14045
14268
  this._handledBySubagent.add(taskId);
14046
- this._emit({ type: "goal:failed", goalId: taskId, text: payload?.error ?? "Task failed" });
14269
+ this._recordTaskFailed(taskId, payload?.error ?? "Task failed");
14047
14270
  });
14048
14271
  if (offFailed) this.unsubs.push(offFailed);
14049
14272
  this._emit({ type: "coordinator:mode", mode: this.fleet ? "fleet" : "standalone" });
@@ -14062,6 +14285,7 @@ var AutonomousCoordinator = class {
14062
14285
  const maxCost = opts.maxCostUsd;
14063
14286
  try {
14064
14287
  await this.graph.load();
14288
+ this._rebuildDagFromGraph();
14065
14289
  const goalConfigs = await this._decomposeGoal(goal);
14066
14290
  for (const g of goalConfigs) {
14067
14291
  const goalId = await this.auction.publishTask(g);
@@ -14069,23 +14293,49 @@ var AutonomousCoordinator = class {
14069
14293
  this._emit({ type: "goal:added", goalId, title: g.title, text: g.description });
14070
14294
  }
14071
14295
  while (this.running) {
14296
+ if (this.dag.getRunning().length > 0 && this.auction.getPendingTasks().length === 0) {
14297
+ await this._waitForDagProgress(1e3);
14298
+ continue;
14299
+ }
14072
14300
  this.iterationCount++;
14301
+ await this._maybeSyncFromGraph();
14073
14302
  if (this.iterationCount >= maxIterations) break;
14074
14303
  if (maxCost !== void 0) {
14075
14304
  const cost = this.fleetManager?.snapshot()?.total?.cost ?? 0;
14076
14305
  if (cost >= maxCost) break;
14077
14306
  }
14078
14307
  if (opts.runUntilComplete && this.dag.isDone()) break;
14308
+ const pendingTasks = this.auction.getPendingTasks();
14309
+ const dispatchable = pendingTasks.filter((task) => {
14310
+ const dagNode = this.dag.getNode(task.id);
14311
+ return !dagNode || dagNode.status === "ready";
14312
+ });
14313
+ if (dispatchable.length === 0) {
14314
+ if (this.dag.getRunning().length > 0 || this.dag.getReady().length > 0) {
14315
+ await this._waitForDagProgress(1e3);
14316
+ continue;
14317
+ }
14318
+ if (pendingTasks.length > 0) {
14319
+ await this._waitForDagProgress(2e3);
14320
+ continue;
14321
+ }
14322
+ if (this.dag.hasDeadlock()) {
14323
+ const blocked = this.dag.getBlocked();
14324
+ (this.events?.emit)("autonomous:deadlock", { blocked });
14325
+ this._emit({ type: "deadlock:detected", goalId: blocked[0]?.id ?? "", text: `Deadlock detected: ${blocked.map((n) => n.id).join(", ")}` });
14326
+ }
14327
+ break;
14328
+ }
14079
14329
  const decision = await this.brain.decideAuto({
14080
14330
  id: randomUUID(),
14081
14331
  source: "system",
14082
14332
  decisionType: "prioritize_goals",
14083
- question: `What should we work on next? Open goals: ${this.auction.getPendingTasks().map((g) => g.title).join(", ") || "none"}`,
14333
+ question: `What should we work on next? Open goals: ${dispatchable.map((g) => g.title).join(", ")}`,
14084
14334
  context: {
14085
- goals: this.auction.getPendingTasks(),
14335
+ goals: dispatchable,
14086
14336
  fleetStatus: this._fleetStatus()
14087
14337
  },
14088
- options: this._goalToOptions(this.auction.getPendingTasks()),
14338
+ options: this._goalToOptions(dispatchable),
14089
14339
  risk: "medium",
14090
14340
  requiresConsensus: false
14091
14341
  });
@@ -14132,6 +14382,55 @@ var AutonomousCoordinator = class {
14132
14382
  this.running = false;
14133
14383
  console.error(`[AutonomousCoordinator] stop signal received \u2014 shutting down (iteration ${this.iterationCount})`);
14134
14384
  }
14385
+ /**
14386
+ * Report that a terminal worker (not a Director subagent) completed a claimed
14387
+ * task. This updates the auction, DAG, publishes a task-result fact, and
14388
+ * extracts follow-up goals — the same path as subagent completion.
14389
+ */
14390
+ async reportTaskCompletion(taskId, result) {
14391
+ this._handledBySubagent.add(taskId);
14392
+ await this._completeTask(taskId, result);
14393
+ }
14394
+ /**
14395
+ * Report that a terminal worker failed a claimed task.
14396
+ */
14397
+ async reportTaskFailure(taskId, error) {
14398
+ this._handledBySubagent.add(taskId);
14399
+ await this._failTask(taskId, error);
14400
+ }
14401
+ /**
14402
+ * Reload the KnowledgeGraph from disk and sync the in-memory DAG with any
14403
+ * changes published by other terminal sessions. New goals are added to the
14404
+ * DAG; existing goals whose status changed (e.g. completed by another
14405
+ * terminal) are transitioned accordingly.
14406
+ *
14407
+ * Safe to call at any time — also used internally by the run loop.
14408
+ */
14409
+ async syncFromGraph() {
14410
+ await this.graph.load();
14411
+ this._rebuildDagFromGraph();
14412
+ this._syncDagStatuses();
14413
+ }
14414
+ _syncDagStatuses() {
14415
+ const goals = this.graph.getGoals({});
14416
+ for (const goal of goals) {
14417
+ const dagNode = this.dag.getNode(goal.id);
14418
+ if (!dagNode) continue;
14419
+ if (goal.status === "done" && dagNode.status !== "done" && dagNode.status !== "failed") {
14420
+ this.dag.complete(goal.id, goal.result ?? "Completed by another session");
14421
+ } else if (goal.status === "failed" && dagNode.status !== "failed" && dagNode.status !== "done") {
14422
+ this.dag.fail(goal.id, goal.result ?? "Failed by another session");
14423
+ } else if (goal.status === "in_progress" && (dagNode.status === "ready" || dagNode.status === "pending")) {
14424
+ this.dag.start(goal.id, goal.assignee ?? "another-session");
14425
+ }
14426
+ }
14427
+ }
14428
+ async _maybeSyncFromGraph() {
14429
+ const now = Date.now();
14430
+ if (now - this.lastSyncAt < _AutonomousCoordinator.SYNC_INTERVAL_MS) return;
14431
+ this.lastSyncAt = now;
14432
+ await this.syncFromGraph();
14433
+ }
14135
14434
  /**
14136
14435
  * Tear down the coordinator for good: stop the loop and detach all FleetBus
14137
14436
  * subscriptions (this coordinator's + the auctioneer's) plus any open bid
@@ -14228,6 +14527,65 @@ ${input.detail}`
14228
14527
  return goal;
14229
14528
  }
14230
14529
  // ── Private ───────────────────────────────────────────────────────────
14530
+ _waitForDagProgress(timeoutMs) {
14531
+ const before = this._dagProgressKey();
14532
+ if (this.dag.isDone()) return Promise.resolve();
14533
+ return new Promise((resolve3) => {
14534
+ let off;
14535
+ const timer = setTimeout(() => {
14536
+ off?.();
14537
+ resolve3();
14538
+ }, timeoutMs);
14539
+ off = this.dag.onEvent(() => {
14540
+ if (this._dagProgressKey() === before) return;
14541
+ clearTimeout(timer);
14542
+ off?.();
14543
+ resolve3();
14544
+ });
14545
+ });
14546
+ }
14547
+ _dagProgressKey() {
14548
+ const s = this.dag.stats();
14549
+ return `${s.pending}:${s.ready}:${s.running}:${s.done}:${s.failed}:${s.skipped}`;
14550
+ }
14551
+ _rebuildDagFromGraph() {
14552
+ const goals = this.graph.getGoals({});
14553
+ const knownGoalIds = new Set(goals.map((goal) => goal.id));
14554
+ const added = /* @__PURE__ */ new Set();
14555
+ const remaining = new Map(goals.map((goal) => [goal.id, goal]));
14556
+ while (remaining.size > 0) {
14557
+ let progressed = false;
14558
+ for (const [id, goal] of Array.from(remaining.entries())) {
14559
+ const deps = goal.blockedBy.filter((depId) => knownGoalIds.has(depId));
14560
+ if (!deps.every((depId) => added.has(depId))) continue;
14561
+ this._rebuildDagNode(goal, deps);
14562
+ added.add(id);
14563
+ remaining.delete(id);
14564
+ progressed = true;
14565
+ }
14566
+ if (!progressed) {
14567
+ for (const [id, goal] of Array.from(remaining.entries())) {
14568
+ this._rebuildDagNode(goal, []);
14569
+ added.add(id);
14570
+ remaining.delete(id);
14571
+ }
14572
+ }
14573
+ }
14574
+ }
14575
+ _rebuildDagNode(goal, deps) {
14576
+ this.dag.addNode(goal.id, goal.description, deps, { tags: goal.tags });
14577
+ if (goal.status === "in_progress") {
14578
+ this.dag.start(goal.id, goal.assignee ?? "unknown");
14579
+ return;
14580
+ }
14581
+ if (goal.status === "done") {
14582
+ this.dag.complete(goal.id, goal.result ?? "Persisted completion");
14583
+ return;
14584
+ }
14585
+ if (goal.status === "failed") {
14586
+ this.dag.fail(goal.id, goal.result ?? "Persisted failure");
14587
+ }
14588
+ }
14231
14589
  async _decomposeGoal(goalText) {
14232
14590
  const category = this._inferCategory(goalText);
14233
14591
  const subGoals = [];
@@ -14264,13 +14622,7 @@ ${input.detail}`
14264
14622
  const goalNode = this.graph.get(goalId);
14265
14623
  if (!goalNode) return;
14266
14624
  const title = goalNode.title || dagNode.description;
14267
- const taskId = await this.auction.publishTask({
14268
- title,
14269
- description: goalNode.description,
14270
- priority: this._dagPriorityToGoal(dagNode.priority),
14271
- tags: dagNode.tags
14272
- });
14273
- this._emit({ type: "task:ready", goalId, taskId, title });
14625
+ this._emit({ type: "task:ready", goalId, taskId: goalId, title });
14274
14626
  if (this.director) {
14275
14627
  const config = {
14276
14628
  name: `worker-${goalId.slice(0, 8)}`,
@@ -14280,7 +14632,7 @@ ${input.detail}`
14280
14632
  // 10 minutes per goal
14281
14633
  };
14282
14634
  const subagentId = await this.director.spawn(config);
14283
- await this.auction.claim(taskId, subagentId, config.name);
14635
+ await this.auction.claim(goalId, subagentId, config.name);
14284
14636
  await this.director.assign({
14285
14637
  id: goalId,
14286
14638
  subagentId,
@@ -14288,6 +14640,78 @@ ${input.detail}`
14288
14640
  });
14289
14641
  }
14290
14642
  }
14643
+ _stringifyTaskResult(result) {
14644
+ if (typeof result === "string" && result.trim()) return result.trim();
14645
+ if (result === void 0 || result === null) return "Subagent completed successfully";
14646
+ try {
14647
+ return JSON.stringify(result);
14648
+ } catch {
14649
+ return String(result);
14650
+ }
14651
+ }
14652
+ async _completeTask(taskId, result) {
14653
+ await this.auction.complete(taskId, result);
14654
+ if (this.dag.getNode(taskId)) {
14655
+ this.dag.complete(taskId, result);
14656
+ }
14657
+ await this._publishTaskResultFact(taskId, result);
14658
+ await this._createFollowUpGoalsFromResult(taskId, result);
14659
+ this._emit({ type: "task:completed", goalId: taskId, taskId, text: result });
14660
+ }
14661
+ async _publishTaskResultFact(taskId, result) {
14662
+ const key = `task-result:${taskId}`;
14663
+ if (this.graph.getFacts({ category: "quality" }).some((fact2) => fact2.key === key)) return;
14664
+ const goal = this.graph.get(taskId);
14665
+ const subject = goal?.type === "goal" ? `Task completed: ${goal.title}` : `Task completed: ${taskId}`;
14666
+ const fact = await this.graph.add({
14667
+ type: "fact",
14668
+ category: "quality",
14669
+ subject,
14670
+ detail: result,
14671
+ discoveredBy: this.selfAgentId,
14672
+ discoveredAt: (/* @__PURE__ */ new Date()).toISOString(),
14673
+ tags: ["task-result", "autonomous-coordinator"],
14674
+ key,
14675
+ related: [taskId]
14676
+ });
14677
+ this._emit({ type: "knowledge:added", knowledgeId: fact.id, title: subject, text: result });
14678
+ }
14679
+ async _createFollowUpGoalsFromResult(taskId, result) {
14680
+ const followUps = this._extractFollowUps(result);
14681
+ if (followUps.length === 0) return;
14682
+ const existing = this.graph.getGoals({});
14683
+ for (const title of followUps) {
14684
+ if (existing.some((goal2) => goal2.title === title && goal2.tags.includes("follow-up"))) continue;
14685
+ const goal = await this.createGoal({
14686
+ title,
14687
+ description: title,
14688
+ priority: "medium",
14689
+ tags: ["follow-up", "task-result", taskId]
14690
+ });
14691
+ this._emit({ type: "goal:added", goalId: goal.id, title: goal.title, text: goal.description });
14692
+ }
14693
+ }
14694
+ _extractFollowUps(result) {
14695
+ const found = [];
14696
+ for (const line of result.split(/\r?\n/)) {
14697
+ const match = /^\s*(?:[-*]\s*)?(?:NEXT|TODO|FOLLOW-?UP):\s*(.+)$/i.exec(line);
14698
+ const text = match?.[1]?.trim();
14699
+ if (!text || found.includes(text)) continue;
14700
+ found.push(text);
14701
+ if (found.length >= 5) break;
14702
+ }
14703
+ return found;
14704
+ }
14705
+ async _failTask(taskId, error) {
14706
+ await this.auction.fail(taskId, error);
14707
+ this._recordTaskFailed(taskId, error);
14708
+ }
14709
+ _recordTaskFailed(taskId, error) {
14710
+ if (this.dag.getNode(taskId)) {
14711
+ this.dag.fail(taskId, error);
14712
+ }
14713
+ this._emit({ type: "goal:failed", goalId: taskId, text: error });
14714
+ }
14291
14715
  async _handlePendingChange(change) {
14292
14716
  const result = this.consensus.getStatus(change.id);
14293
14717
  if (result?.outcome !== "pending") return;
@@ -14328,16 +14752,15 @@ ${input.detail}`
14328
14752
  _onSubagentTerminated(e) {
14329
14753
  const payload = e.payload;
14330
14754
  const subagentId = payload?.subagentId ?? e.subagentId;
14331
- const stopReason = payload?.stopReason ?? (payload?.status === "ok" ? "end_turn" : payload?.status ?? "unknown");
14332
- const tasks = this.auction.getTasksForAgent(subagentId);
14755
+ const rawStatus = payload?.stopReason ?? payload?.status ?? "unknown";
14756
+ const succeeded = rawStatus === "end_turn" || rawStatus === "ok" || rawStatus === "success";
14757
+ const tasks = payload?.taskId ? this.auction.getTasksForAgent(subagentId).filter((task) => task.id === payload.taskId) : this.auction.getTasksForAgent(subagentId);
14333
14758
  for (const task of tasks) {
14334
14759
  this._handledBySubagent.add(task.id);
14335
- if (stopReason === "end_turn") {
14336
- void this.auction.complete(task.id, "Subagent completed successfully");
14337
- this._emit({ type: "task:completed", goalId: task.id, taskId: task.id, text: "Subagent completed successfully" });
14760
+ if (succeeded) {
14761
+ void this._completeTask(task.id, this._stringifyTaskResult(payload?.result));
14338
14762
  } else {
14339
- void this.auction.fail(task.id, `Subagent terminated: ${stopReason}`);
14340
- this._emit({ type: "goal:failed", goalId: task.id, text: `Subagent terminated: ${stopReason}` });
14763
+ void this._failTask(task.id, `Subagent terminated: ${rawStatus}`);
14341
14764
  }
14342
14765
  }
14343
14766
  }
@@ -14372,12 +14795,6 @@ ${input.detail}`
14372
14795
  _optionToGoal(optionId) {
14373
14796
  return this.graph.get(optionId);
14374
14797
  }
14375
- _dagPriorityToGoal(p) {
14376
- if (p <= 1) return "critical";
14377
- if (p <= 2) return "high";
14378
- if (p <= 4) return "medium";
14379
- return "low";
14380
- }
14381
14798
  async _mailboxBroadcast(msg) {
14382
14799
  if (!this.mailbox) return;
14383
14800
  try {