@wrongstack/core 0.277.1 → 0.280.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 (83) hide show
  1. package/dist/{agent-bridge-BFJ2ODzI.d.ts → agent-bridge-DXC6QDJ4.d.ts} +1 -1
  2. package/dist/{agent-subagent-runner-BimKihiC.d.ts → agent-subagent-runner-PoqNKiR4.d.ts} +563 -471
  3. package/dist/{compactor-D3BGw26y.d.ts → compactor-U3agvUIG.d.ts} +1 -1
  4. package/dist/{config-DAOjriz9.d.ts → config-Cr3312zc.d.ts} +102 -4
  5. package/dist/coordination/index.d.ts +1087 -998
  6. package/dist/coordination/index.js +12235 -12052
  7. package/dist/coordination/index.js.map +1 -1
  8. package/dist/defaults/index.d.ts +31 -30
  9. package/dist/defaults/index.js +403 -189
  10. package/dist/defaults/index.js.map +1 -1
  11. package/dist/{brain-CCfuEOdp.d.ts → events-Bs2fmldo.d.ts} +117 -112
  12. package/dist/execution/index.d.ts +27 -19
  13. package/dist/execution/index.js +216 -63
  14. package/dist/execution/index.js.map +1 -1
  15. package/dist/execution/prompt-enhancer.d.ts +1 -1
  16. package/dist/execution/prompt-enhancer.js.map +1 -1
  17. package/dist/extension/index.d.ts +8 -7
  18. package/dist/{global-mailbox-Dr4cTKqL.d.ts → global-mailbox-Ct7IorLJ.d.ts} +84 -6
  19. package/dist/{goal-store-C1uH4srH.d.ts → goal-store-C4F6DjC0.d.ts} +1 -1
  20. package/dist/hq/index.d.ts +504 -7
  21. package/dist/hq/index.js +1069 -20
  22. package/dist/hq/index.js.map +1 -1
  23. package/dist/{index-DJXj-dcr.d.ts → index-kidebiDh.d.ts} +8 -5
  24. package/dist/{index-cMEmzCVN.d.ts → index-nP09-oP2.d.ts} +2 -2
  25. package/dist/index.d.ts +153 -76
  26. package/dist/index.js +5791 -3163
  27. package/dist/index.js.map +1 -1
  28. package/dist/infrastructure/index.d.ts +7 -6
  29. package/dist/kernel/index.d.ts +14 -13
  30. package/dist/kernel/index.js +31 -15
  31. package/dist/kernel/index.js.map +1 -1
  32. package/dist/{mailbox-types-DTl7bRH3.d.ts → mailbox-types-BGZWrYTJ.d.ts} +38 -0
  33. package/dist/{mcp-servers-CFb60-pH.d.ts → mcp-servers-D910X5_r.d.ts} +3 -3
  34. package/dist/models/index.d.ts +5 -5
  35. package/dist/models/index.js.map +1 -1
  36. package/dist/{models-registry-5Ufn7f2m.d.ts → models-registry-CLkoOcHk.d.ts} +1 -1
  37. package/dist/{multi-agent-coordinator-CcrcncvG.d.ts → multi-agent-coordinator-CieyUoEL.d.ts} +1 -1
  38. package/dist/{null-fleet-bus-C9KsYyrI.d.ts → null-fleet-bus-DkdmZJ_W.d.ts} +464 -464
  39. package/dist/observability/index.d.ts +3 -2
  40. package/dist/{path-resolver-CEeX9I7O.d.ts → path-resolver-XfZ9eLxG.d.ts} +3 -3
  41. package/dist/{permission-DbsGOA1C.d.ts → permission-Dx6dIqS2.d.ts} +2 -7
  42. package/dist/{permission-policy-BpEea3r7.d.ts → permission-policy-C8vJcnX5.d.ts} +2 -2
  43. package/dist/{pipeline-CEjBjzVA.d.ts → pipeline-BwAP21_4.d.ts} +9 -4
  44. package/dist/{provider-model-resolve-BpfXp3Jj.d.ts → provider-model-resolve-CwQNZWt_.d.ts} +3 -3
  45. package/dist/{provider-runner-CnOSr5BN.d.ts → provider-runner-CYHFImzV.d.ts} +3 -3
  46. package/dist/{retry-policy-Git9WF6d.d.ts → retry-policy-D4feSLk3.d.ts} +1 -1
  47. package/dist/sdd/index.d.ts +11 -10
  48. package/dist/sdd/index.js +2 -2
  49. package/dist/sdd/index.js.map +1 -1
  50. package/dist/secret-scrubber-3MHDDAtm.d.ts +6 -0
  51. package/dist/{secret-vault-DDSMHqIm.d.ts → secret-vault-CImt2XrR.d.ts} +1 -1
  52. package/dist/security/index.d.ts +6 -5
  53. package/dist/security/index.js.map +1 -1
  54. package/dist/{selector-Cq72C0Oy.d.ts → selector-Dy-MzKp1.d.ts} +1 -1
  55. package/dist/{session-event-bridge-DG94B3Bk.d.ts → session-event-bridge-CqdiGnfU.d.ts} +1 -1
  56. package/dist/{session-reader-BzT-iMQT.d.ts → session-reader-Hk0WbNm9.d.ts} +1 -1
  57. package/dist/{skill-DGIXCtdv.d.ts → skill-DHniprNl.d.ts} +15 -1
  58. package/dist/skills/index.d.ts +472 -26
  59. package/dist/skills/index.js +872 -129
  60. package/dist/skills/index.js.map +1 -1
  61. package/dist/storage/index.d.ts +27 -14
  62. package/dist/storage/index.js +264 -85
  63. package/dist/storage/index.js.map +1 -1
  64. package/dist/{strategy-compactor-Bt_ZH6R0.d.ts → strategy-compactor-CQwhbErd.d.ts} +32 -17
  65. package/dist/{todos-checkpoint-CH1pcua9.d.ts → todos-checkpoint-Bk2uP7Ex.d.ts} +6 -6
  66. package/dist/{context-DPlA6kid.d.ts → tool-BkOgs_KL.d.ts} +306 -286
  67. package/dist/{tool-executor-SVFq7IOR.d.ts → tool-executor-SiE1wlZo.d.ts} +9 -9
  68. package/dist/tools/index.d.ts +2 -2
  69. package/dist/tools/index.js.map +1 -1
  70. package/dist/types/index.d.ts +22 -21
  71. package/dist/types/index.js +7 -9
  72. package/dist/types/index.js.map +1 -1
  73. package/dist/utils/index.d.ts +30 -4
  74. package/dist/utils/index.js +50 -1
  75. package/dist/utils/index.js.map +1 -1
  76. package/dist/{worktree-manager-C4YIf1Fa.d.ts → worktree-manager-BjOFF6bt.d.ts} +1 -1
  77. package/dist/{wstack-paths-_NrRovdr.d.ts → wstack-paths-CMl_cYgq.d.ts} +8 -0
  78. package/package.json +1 -1
  79. package/skills/mailbox-bridge/SKILL.md +1 -0
  80. package/skills/plugin-author/SKILL.md +350 -0
  81. package/skills/sdd/SKILL.md +134 -134
  82. package/skills/skill-creator/SKILL.md +45 -7
  83. package/skills/wrongstack-mailbox/SKILL.md +40 -21
package/dist/hq/index.js CHANGED
@@ -96,7 +96,8 @@ function parseHqFrame(raw) {
96
96
  clientId: obj.clientId,
97
97
  projectId: obj.projectId,
98
98
  commandId: obj.commandId,
99
- status: obj.status
99
+ status: obj.status,
100
+ ...typeof obj.message === "string" ? { message: obj.message } : {}
100
101
  }
101
102
  };
102
103
  default: {
@@ -110,7 +111,14 @@ var KNOWN_HQ_EVENT_PAYLOAD_TYPES = /* @__PURE__ */ new Set([
110
111
  "mailbox.event",
111
112
  "session.snapshot",
112
113
  "session.transcript",
113
- "session.ended"
114
+ "session.ended",
115
+ "fleet.snapshot",
116
+ "fleet.event",
117
+ "brain.event",
118
+ "worktree.event",
119
+ "tool.started",
120
+ "tool.completed",
121
+ "session.usage"
114
122
  ]);
115
123
  function isHqMailboxMessageSummary(x) {
116
124
  if (typeof x !== "object" || x === null) return false;
@@ -147,7 +155,8 @@ var HQ_MAILBOX_EVENT_ACTIONS = /* @__PURE__ */ new Set([
147
155
  "message.updated",
148
156
  "agent.registered",
149
157
  "agent.heartbeat",
150
- "agent.offline"
158
+ "agent.offline",
159
+ "agent.deregistered"
151
160
  ]);
152
161
  function isHqMailboxEventPayload(x) {
153
162
  if (typeof x !== "object" || x === null) return false;
@@ -204,6 +213,79 @@ function isHqSessionEndedPayload(x) {
204
213
  const v = x;
205
214
  return typeof v.sessionId === "string" && typeof v.endedAt === "string";
206
215
  }
216
+ var HQ_FLEET_SUBAGENT_STATUS = /* @__PURE__ */ new Set([
217
+ "pending",
218
+ "running",
219
+ "idle",
220
+ "completed",
221
+ "failed",
222
+ "stopped"
223
+ ]);
224
+ function isHqSubagentSummary(x) {
225
+ if (typeof x !== "object" || x === null) return false;
226
+ const v = x;
227
+ return typeof v.subagentId === "string" && typeof v.status === "string" && HQ_FLEET_SUBAGENT_STATUS.has(v.status);
228
+ }
229
+ function isHqFleetSnapshotPayload(x) {
230
+ if (typeof x !== "object" || x === null) return false;
231
+ const v = x;
232
+ if (typeof v.runId !== "string" || typeof v.activeSubagents !== "number" || typeof v.queuedTasks !== "number" || typeof v.completedTasks !== "number" || typeof v.failedTasks !== "number" || !Array.isArray(v.subagents)) {
233
+ return false;
234
+ }
235
+ for (const s of v.subagents) {
236
+ if (!isHqSubagentSummary(s)) return false;
237
+ }
238
+ return true;
239
+ }
240
+ function isHqFleetEventPayload(x) {
241
+ if (typeof x !== "object" || x === null) return false;
242
+ const v = x;
243
+ return typeof v.runId === "string" && typeof v.event === "string";
244
+ }
245
+ var HQ_BRAIN_EVENT_KINDS = /* @__PURE__ */ new Set([
246
+ "decision_requested",
247
+ "decision_answered",
248
+ "decision_ask_human",
249
+ "human_answered",
250
+ "decision_denied",
251
+ "intervention"
252
+ ]);
253
+ function isHqBrainEventPayload(x) {
254
+ if (typeof x !== "object" || x === null) return false;
255
+ const v = x;
256
+ if (typeof v.kind !== "string" || !HQ_BRAIN_EVENT_KINDS.has(v.kind)) return false;
257
+ if (typeof v.at !== "number") return false;
258
+ return true;
259
+ }
260
+ var HQ_WORKTREE_EVENT_KINDS = /* @__PURE__ */ new Set([
261
+ "allocated",
262
+ "committed",
263
+ "merged",
264
+ "conflict",
265
+ "released",
266
+ "failed"
267
+ ]);
268
+ function isHqWorktreeEventPayload(x) {
269
+ if (typeof x !== "object" || x === null) return false;
270
+ const v = x;
271
+ if (typeof v.kind !== "string" || !HQ_WORKTREE_EVENT_KINDS.has(v.kind)) return false;
272
+ if (typeof v.handleId !== "string" || typeof v.ownerId !== "string") return false;
273
+ return true;
274
+ }
275
+ function isHqToolStartedPayload(x) {
276
+ if (typeof x !== "object" || x === null) return false;
277
+ const v = x;
278
+ return typeof v.toolName === "string";
279
+ }
280
+ function isHqToolCompletedPayload(x) {
281
+ if (typeof x !== "object" || x === null) return false;
282
+ const v = x;
283
+ return typeof v.toolName === "string" && typeof v.status === "string" && typeof v.durationMs === "number";
284
+ }
285
+ function isHqUsagePayload(x) {
286
+ if (typeof x !== "object" || x === null) return false;
287
+ return !Array.isArray(x);
288
+ }
207
289
  function parseHqEventPayload(eventType, payload) {
208
290
  if (!KNOWN_HQ_EVENT_PAYLOAD_TYPES.has(eventType)) {
209
291
  return { ok: true, payload };
@@ -219,6 +301,20 @@ function parseHqEventPayload(eventType, payload) {
219
301
  return isHqTranscriptAppendPayload(payload) ? { ok: true, payload } : { ok: false, reason: "malformed-payload" };
220
302
  case "session.ended":
221
303
  return isHqSessionEndedPayload(payload) ? { ok: true, payload } : { ok: false, reason: "malformed-payload" };
304
+ case "fleet.snapshot":
305
+ return isHqFleetSnapshotPayload(payload) ? { ok: true, payload } : { ok: false, reason: "malformed-payload" };
306
+ case "fleet.event":
307
+ return isHqFleetEventPayload(payload) ? { ok: true, payload } : { ok: false, reason: "malformed-payload" };
308
+ case "brain.event":
309
+ return isHqBrainEventPayload(payload) ? { ok: true, payload } : { ok: false, reason: "malformed-payload" };
310
+ case "worktree.event":
311
+ return isHqWorktreeEventPayload(payload) ? { ok: true, payload } : { ok: false, reason: "malformed-payload" };
312
+ case "tool.started":
313
+ return isHqToolStartedPayload(payload) ? { ok: true, payload } : { ok: false, reason: "malformed-payload" };
314
+ case "tool.completed":
315
+ return isHqToolCompletedPayload(payload) ? { ok: true, payload } : { ok: false, reason: "malformed-payload" };
316
+ case "session.usage":
317
+ return isHqUsagePayload(payload) ? { ok: true, payload } : { ok: false, reason: "malformed-payload" };
222
318
  default: {
223
319
  const _exhaustive = eventType;
224
320
  return _exhaustive;
@@ -723,6 +819,7 @@ function createMailboxEventPayload(input) {
723
819
  var OPEN_STATE = 1;
724
820
  var DEFAULT_RECONNECT_BASE_MS = 1e3;
725
821
  var DEFAULT_RECONNECT_MAX_MS = 3e4;
822
+ var DEFAULT_DISCOVERY_POLL_MS = 5e3;
726
823
  var DEFAULT_MAX_QUEUED_MESSAGES = 2e3;
727
824
  var DEFAULT_COMMAND_POLL_INTERVAL_MS = 2e3;
728
825
  var DEFAULT_COMMAND_POLL_LIMIT = 25;
@@ -760,7 +857,7 @@ var HqPublisher = class {
760
857
  this.socketFactory = options.socketFactory ?? defaultSocketFactory;
761
858
  this.now = options.now ?? (() => (/* @__PURE__ */ new Date()).toISOString());
762
859
  this.idFactory = options.idFactory ?? randomUUID;
763
- this.capabilities = options.capabilities ?? ["telemetry.publish", "mailbox.summary"];
860
+ this.capabilities = options.capabilities ?? ["telemetry.publish", "mailbox.summary", "fleet.summary", "session.summary"];
764
861
  this.reconnect = options.reconnect ?? true;
765
862
  this.reconnectBaseMs = options.reconnectBaseMs ?? DEFAULT_RECONNECT_BASE_MS;
766
863
  this.reconnectMaxMs = options.reconnectMaxMs ?? DEFAULT_RECONNECT_MAX_MS;
@@ -785,10 +882,22 @@ var HqPublisher = class {
785
882
  lastCommandId;
786
883
  connect() {
787
884
  if (this.socket !== null || this.stopped) return;
885
+ if (this.reconnectTimer !== null) return;
886
+ let url = this.options.url;
887
+ let token = this.options.token;
888
+ if (this.options.resolveEndpoint !== void 0) {
889
+ const endpoint = this.options.resolveEndpoint();
890
+ if (endpoint === void 0) {
891
+ this.scheduleDiscoveryPoll();
892
+ return;
893
+ }
894
+ url = endpoint.url;
895
+ token = endpoint.token ?? token;
896
+ }
788
897
  let socket;
789
898
  try {
790
- socket = this.socketFactory(toClientUrl(this.options.url, this.options.token), {
791
- ...this.options.token !== void 0 ? { token: this.options.token } : {}
899
+ socket = this.socketFactory(toClientUrl(url, token), {
900
+ ...token !== void 0 ? { token } : {}
792
901
  });
793
902
  } catch {
794
903
  this.scheduleReconnect();
@@ -913,6 +1022,16 @@ var HqPublisher = class {
913
1022
  ...opts?.timestamp !== void 0 ? { timestamp: opts.timestamp } : {}
914
1023
  });
915
1024
  }
1025
+ /** Publish a fleet (multi-agent coordinator) snapshot. */
1026
+ publishFleetSnapshot(payload, opts) {
1027
+ return this.publishEvent({
1028
+ type: "fleet.snapshot",
1029
+ payload,
1030
+ runId: payload.runId,
1031
+ ...opts?.sessionId !== void 0 ? { sessionId: opts.sessionId } : {},
1032
+ ...opts?.timestamp !== void 0 ? { timestamp: opts.timestamp } : {}
1033
+ });
1034
+ }
916
1035
  pollCommands() {
917
1036
  this.sendFrame({
918
1037
  type: "client.command_poll",
@@ -1028,6 +1147,21 @@ var HqPublisher = class {
1028
1147
  }, delay);
1029
1148
  this.reconnectTimer.unref?.();
1030
1149
  }
1150
+ /**
1151
+ * Dormant re-check while no HQ endpoint is discoverable. Uses a FIXED
1152
+ * interval (not the exponential reconnect backoff): the check is a cheap
1153
+ * local file read, and backing off would delay attaching to an HQ the
1154
+ * user just started — the whole point of auto-discovery.
1155
+ */
1156
+ scheduleDiscoveryPoll() {
1157
+ if (this.stopped || !this.reconnect || this.reconnectTimer !== null) return;
1158
+ this.reconnectAttempt = 0;
1159
+ this.reconnectTimer = setTimeout(() => {
1160
+ this.reconnectTimer = null;
1161
+ this.connect();
1162
+ }, this.options.discoveryPollMs ?? DEFAULT_DISCOVERY_POLL_MS);
1163
+ this.reconnectTimer.unref?.();
1164
+ }
1031
1165
  };
1032
1166
  function sessionScopedPath(dir, sessionId, suffix) {
1033
1167
  if (!sessionId || sessionId.includes("\\") || sessionId.includes("..")) {
@@ -1066,16 +1200,20 @@ function wstackGlobalRoot() {
1066
1200
  }
1067
1201
  function resolveWstackPaths(opts) {
1068
1202
  const globalRoot = opts.globalRoot ?? (opts.userHome ? path2.join(opts.userHome, ".wrongstack") : wstackGlobalRoot());
1203
+ const homeDir = opts.userHome ?? os.homedir();
1069
1204
  const hash = projectHash(opts.projectRoot);
1070
1205
  const slug = projectSlug(opts.projectRoot);
1071
1206
  const projectDir = path2.join(globalRoot, "projects", slug);
1072
1207
  return {
1073
1208
  globalRoot,
1209
+ projectRoot: opts.projectRoot,
1210
+ homeDir,
1074
1211
  configDir: globalRoot,
1075
1212
  globalConfig: path2.join(globalRoot, "config.json"),
1076
1213
  secretsKey: path2.join(globalRoot, ".key"),
1077
1214
  globalMemory: path2.join(globalRoot, "memory.md"),
1078
1215
  globalSkills: path2.join(globalRoot, "skills"),
1216
+ globalClaudeSkills: path2.join(homeDir, ".claude", "skills"),
1079
1217
  globalDesignKits: path2.join(globalRoot, "design-kits"),
1080
1218
  globalPrompts: path2.join(globalRoot, "prompts"),
1081
1219
  globalInstructions: path2.join(globalRoot, "instructions"),
@@ -1095,6 +1233,7 @@ function resolveWstackPaths(opts) {
1095
1233
  inProjectConfig: path2.join(opts.projectRoot, ".wrongstack", "config.json"),
1096
1234
  inProjectAgentsFile: path2.join(opts.projectRoot, ".wrongstack", "AGENTS.md"),
1097
1235
  inProjectSkills: path2.join(opts.projectRoot, ".wrongstack", "skills"),
1236
+ inProjectClaudeSkills: path2.join(opts.projectRoot, ".claude", "skills"),
1098
1237
  inProjectPrompts: path2.join(opts.projectRoot, ".wrongstack", "prompts"),
1099
1238
  inProjectInstructions: path2.join(opts.projectRoot, ".wrongstack", "instructions"),
1100
1239
  inProjectDesignKits: path2.join(opts.projectRoot, ".wrongstack", "design-kits"),
@@ -1294,6 +1433,7 @@ function normalizeRecipient(to) {
1294
1433
  var MAILBOX_FILE = "_mailbox.jsonl";
1295
1434
  var CLIENT_REGISTRY_FILE = "_mailbox.clients.json";
1296
1435
  var AGENT_STALE_MS = 6e4;
1436
+ var AGENT_PURGE_MS = 864e5;
1297
1437
  var CLIENT_STALE_MS = 6e4;
1298
1438
  var HEARTBEAT_THROTTLE_MS = 5e3;
1299
1439
  var REGISTRY_CACHE_TTL_MS = 2e3;
@@ -1399,7 +1539,11 @@ var GlobalMailbox = class {
1399
1539
  await fsp.appendFile(this.messagePath, line, "utf8");
1400
1540
  this._pushToCache(msg);
1401
1541
  });
1402
- this.publishHqMailboxEvent({ mailboxId: this.hqMailboxId, action: "message.sent", message: msg });
1542
+ this.publishHqMailboxEvent({
1543
+ mailboxId: this.hqMailboxId,
1544
+ action: "message.sent",
1545
+ message: msg
1546
+ });
1403
1547
  this.publishHqMailboxSnapshot();
1404
1548
  return msg;
1405
1549
  }
@@ -1420,6 +1564,7 @@ var GlobalMailbox = class {
1420
1564
  continue;
1421
1565
  }
1422
1566
  if (q.since !== void 0 && m.timestamp <= q.since) continue;
1567
+ if (!q.includeDeleted && m.deletedAt !== void 0) continue;
1423
1568
  out.push(m);
1424
1569
  }
1425
1570
  out.sort((a, b) => b.timestamp.localeCompare(a.timestamp));
@@ -1488,6 +1633,63 @@ var GlobalMailbox = class {
1488
1633
  }
1489
1634
  return count;
1490
1635
  }
1636
+ async softDelete(mailId, by) {
1637
+ let updated = null;
1638
+ let cacheSnapshot = null;
1639
+ await withFileLock(this.messagePath, async () => {
1640
+ const all = await this._readMessagesFresh();
1641
+ const now = (/* @__PURE__ */ new Date()).toISOString();
1642
+ for (const m of all) {
1643
+ if (m.id !== mailId) continue;
1644
+ if (m.deletedAt !== void 0) {
1645
+ updated = m;
1646
+ continue;
1647
+ }
1648
+ m.deletedAt = now;
1649
+ m.deletedBy = by;
1650
+ updated = m;
1651
+ }
1652
+ const serialized = all.map((m) => JSON.stringify(m)).join(LINE_SEPARATOR) + LINE_SEPARATOR;
1653
+ await fsp.writeFile(this.messagePath, serialized, "utf8");
1654
+ cacheSnapshot = all;
1655
+ });
1656
+ if (cacheSnapshot) this._setMessageCache(cacheSnapshot);
1657
+ if (updated !== null) {
1658
+ this.publishHqMailboxEvent({
1659
+ mailboxId: this.hqMailboxId,
1660
+ action: "message.updated",
1661
+ message: updated
1662
+ });
1663
+ this.publishHqMailboxSnapshot();
1664
+ }
1665
+ return updated;
1666
+ }
1667
+ async restore(mailId) {
1668
+ let updated = null;
1669
+ let cacheSnapshot = null;
1670
+ await withFileLock(this.messagePath, async () => {
1671
+ const all = await this._readMessagesFresh();
1672
+ for (const m of all) {
1673
+ if (m.id !== mailId) continue;
1674
+ delete m.deletedAt;
1675
+ delete m.deletedBy;
1676
+ updated = m;
1677
+ }
1678
+ const serialized = all.map((m) => JSON.stringify(m)).join(LINE_SEPARATOR) + LINE_SEPARATOR;
1679
+ await fsp.writeFile(this.messagePath, serialized, "utf8");
1680
+ cacheSnapshot = all;
1681
+ });
1682
+ if (cacheSnapshot) this._setMessageCache(cacheSnapshot);
1683
+ if (updated !== null) {
1684
+ this.publishHqMailboxEvent({
1685
+ mailboxId: this.hqMailboxId,
1686
+ action: "message.updated",
1687
+ message: updated
1688
+ });
1689
+ this.publishHqMailboxSnapshot();
1690
+ }
1691
+ return updated;
1692
+ }
1491
1693
  // ── Agent registry ──────────────────────────────────────────────────────
1492
1694
  async registerAgent(input) {
1493
1695
  await this._ensureRegistry();
@@ -1542,6 +1744,43 @@ var GlobalMailbox = class {
1542
1744
  });
1543
1745
  this.publishHqMailboxSnapshot();
1544
1746
  }
1747
+ async deregisterAgent(agentId) {
1748
+ await this._ensureRegistry();
1749
+ let removed;
1750
+ await withFileLock(this.registryPath, async () => {
1751
+ const registry = await this._readRegistry({ fresh: true });
1752
+ this._pruneStaleInPlace(registry);
1753
+ removed = registry.get(agentId);
1754
+ registry.delete(agentId);
1755
+ this._registryCache = registry;
1756
+ this._registryCacheAt = Date.now();
1757
+ await this._writeRegistry(registry);
1758
+ });
1759
+ this._events?.emitCustom("mailbox.agent_deregistered", {
1760
+ agentId
1761
+ });
1762
+ this.publishHqMailboxEvent({
1763
+ mailboxId: this.hqMailboxId,
1764
+ action: "agent.deregistered",
1765
+ agent: {
1766
+ agentId,
1767
+ name: removed?.name ?? agentId,
1768
+ ...removed?.role !== void 0 ? { role: removed.role } : {},
1769
+ sessionId: removed?.sessionId ?? "",
1770
+ status: "offline",
1771
+ ...removed?.currentTool !== void 0 ? { currentTool: removed.currentTool } : {},
1772
+ ...removed?.currentTask !== void 0 ? { currentTask: removed.currentTask } : {},
1773
+ iterations: removed?.iterations ?? 0,
1774
+ toolCalls: removed?.toolCalls ?? 0,
1775
+ lastActivityAt: removed?.lastSeenAt ?? (/* @__PURE__ */ new Date()).toISOString(),
1776
+ lastSeenAt: removed?.lastSeenAt ?? (/* @__PURE__ */ new Date()).toISOString(),
1777
+ online: false,
1778
+ pid: removed?.pid ?? 0,
1779
+ ...removed?.source !== void 0 ? { source: removed.source } : {}
1780
+ }
1781
+ });
1782
+ this.publishHqMailboxSnapshot();
1783
+ }
1545
1784
  async heartbeat(input) {
1546
1785
  const last = this._lastHeartbeat.get(input.agentId) ?? 0;
1547
1786
  const now = Date.now();
@@ -1920,12 +2159,22 @@ var GlobalMailbox = class {
1920
2159
  }
1921
2160
  }
1922
2161
  _pruneStaleInPlace(registry) {
1923
- const cutoff = Date.now() - AGENT_STALE_MS;
1924
- for (const agent of registry.values()) {
1925
- if (new Date(agent.lastSeenAt).getTime() < cutoff) {
2162
+ const staleCutoff = Date.now() - AGENT_STALE_MS;
2163
+ const purgeCutoff = Date.now() - AGENT_PURGE_MS;
2164
+ const toDelete = [];
2165
+ for (const [id, agent] of registry) {
2166
+ const lastSeen = new Date(agent.lastSeenAt).getTime();
2167
+ if (lastSeen < purgeCutoff) {
2168
+ toDelete.push(id);
2169
+ continue;
2170
+ }
2171
+ if (lastSeen < staleCutoff) {
1926
2172
  agent.status = "idle";
1927
2173
  }
1928
2174
  }
2175
+ for (const id of toDelete) {
2176
+ registry.delete(id);
2177
+ }
1929
2178
  }
1930
2179
  async _writeRegistry(registry) {
1931
2180
  const obj = {};
@@ -1991,6 +2240,11 @@ function resolveHqDataDir(override, env = process.env) {
1991
2240
  if (!raw) return defaultHqDataDir();
1992
2241
  return path2.isAbsolute(raw) ? path2.resolve(raw) : path2.resolve(process.cwd(), raw);
1993
2242
  }
2243
+ function tokenHasCapability(token, capability) {
2244
+ if (token === void 0) return false;
2245
+ if (token.capabilities === void 0) return true;
2246
+ return token.capabilities.includes(capability);
2247
+ }
1994
2248
  function emptyHqAuthFile() {
1995
2249
  return {
1996
2250
  version: HQ_AUTH_FILE_VERSION,
@@ -2155,6 +2409,13 @@ function watchHqAuthFile(dataDir, onChange, opts = {}) {
2155
2409
  }
2156
2410
 
2157
2411
  // src/hq/factory.ts
2412
+ function discoverLocalHqEndpoint(options = {}) {
2413
+ const dataDir = resolveHqDataDir(options.dataDir, options.env ?? process.env);
2414
+ const runtime = readHqRuntimeFileSync(dataDir);
2415
+ if (runtime === void 0) return void 0;
2416
+ const token = readFirstClientTokenFromAuthFile(dataDir);
2417
+ return { url: runtime.url, ...token ? { token } : {} };
2418
+ }
2158
2419
  function readFirstClientTokenFromAuthFile(dataDir) {
2159
2420
  try {
2160
2421
  const raw = syncFs.readFileSync(hqAuthFilePath(dataDir), "utf8");
@@ -2182,14 +2443,13 @@ function resolveHqConfig(options = {}) {
2182
2443
  const url = envUrl || configUrl;
2183
2444
  if (!url) {
2184
2445
  if (enabled === false) return void 0;
2185
- if (enabled === true || token || runtimeUrl) {
2186
- return {
2187
- url: runtimeUrl || "http://127.0.0.1:3499",
2188
- enabled: true,
2189
- ...token ? { token } : {}
2190
- };
2191
- }
2192
- return void 0;
2446
+ return {
2447
+ url: runtimeUrl || "http://127.0.0.1:3499",
2448
+ enabled: true,
2449
+ discover: true,
2450
+ dataDir,
2451
+ ...token ? { token } : {}
2452
+ };
2193
2453
  }
2194
2454
  const rawContentEnv = env["WRONGSTACK_HQ_RAW_CONTENT"]?.trim();
2195
2455
  const projectAliasEnv = env["WRONGSTACK_HQ_PROJECT_ALIAS"]?.trim();
@@ -2232,13 +2492,20 @@ function createHqPublisherFromEnv(options) {
2232
2492
  ...config.rawContent !== void 0 ? { rawContent: config.rawContent } : {},
2233
2493
  ...options.redactionPolicy ?? {}
2234
2494
  } : void 0;
2495
+ const discoveryDataDir = config.dataDir;
2235
2496
  return new HqPublisher({
2236
2497
  url: config.url,
2237
2498
  ...config.token ? { token: config.token } : {},
2238
2499
  client,
2239
2500
  project,
2240
2501
  ...options.socketFactory ? { socketFactory: options.socketFactory } : {},
2241
- ...redactionPolicy !== void 0 ? { redactionPolicy } : {}
2502
+ ...redactionPolicy !== void 0 ? { redactionPolicy } : {},
2503
+ ...options.capabilities !== void 0 ? { capabilities: options.capabilities } : {},
2504
+ ...options.onCommand !== void 0 ? { onCommand: options.onCommand } : {},
2505
+ // Auto-discovery: re-read the local HQ runtime marker + client token on
2506
+ // every connect attempt so late-started/restarted HQs are picked up.
2507
+ ...config.discover ? { resolveEndpoint: () => discoverLocalHqEndpoint({ dataDir: discoveryDataDir }) } : {},
2508
+ ...options.discoveryPollMs !== void 0 ? { discoveryPollMs: options.discoveryPollMs } : {}
2242
2509
  });
2243
2510
  }
2244
2511
  function createGlobalMailbox(options) {
@@ -2620,6 +2887,788 @@ function startSessionTelemetryBridge(opts) {
2620
2887
  };
2621
2888
  }
2622
2889
 
2623
- export { DEFAULT_HQ_REDACTION_POLICY, HQ_AUTH_FILE_VERSION, HQ_PROTOCOL_VERSION, HqPublisher, buildTranscriptFromEvents, createGlobalMailbox, createHqEventEnvelope, createHqPublisherFromEnv, createMailboxEventPayload, createMailboxSnapshotPayload, createMailboxSnapshotPayloadFromMailbox, defaultHqDataDir, emptyHqAuthFile, ensureHqFirstRunAuthFile, hqAuthFilePath, hqRuntimeFilePath, mapMailboxAgentToHqSummary, mapMailboxMessageToHqSummary, mapSessionEventToEntries, mergeToolResults, mintHqBrowserToken, mintHqToken, mutateHqAuthFile, parseHqEventPayload, parseHqFrame, readHqAuthFile, readHqRuntimeFileSync, redactHqEvent, redactHqValue, resolveHqConfig, resolveHqConfigFromEnv, resolveHqDataDir, scrubAndTruncateHqPreview, startAgentMonitorEventBridge, startSessionTelemetryBridge, summarizeHqToolArgs, watchHqAuthFile, writeHqAuthFile, writeHqRuntimeFile };
2890
+ // src/hq/fleet-bridge.ts
2891
+ function startFleetTelemetryBridge(opts) {
2892
+ const { events, publisher, runId } = opts;
2893
+ const now = opts.now ?? (() => (/* @__PURE__ */ new Date()).toISOString());
2894
+ let lastHash = "";
2895
+ function buildPayload(stats) {
2896
+ const subagents = stats.subagentStatuses.map((s) => ({
2897
+ subagentId: s.subagentId,
2898
+ ...s.taskId ? { task: s.taskId } : {},
2899
+ status: normalizeSubagentStatus(s.status)
2900
+ }));
2901
+ return {
2902
+ runId,
2903
+ activeSubagents: stats.running + stats.idle,
2904
+ queuedTasks: stats.pending,
2905
+ completedTasks: stats.completed,
2906
+ failedTasks: stats.stopped,
2907
+ subagents
2908
+ };
2909
+ }
2910
+ const off = events.on("coordinator.stats", (stats) => {
2911
+ try {
2912
+ const payload = buildPayload(stats);
2913
+ const hash = JSON.stringify(payload);
2914
+ if (hash === lastHash) return;
2915
+ lastHash = hash;
2916
+ publisher.publishFleetSnapshot(payload, {
2917
+ ...opts.sessionId !== void 0 ? { sessionId: opts.sessionId } : {},
2918
+ timestamp: now()
2919
+ });
2920
+ } catch {
2921
+ }
2922
+ });
2923
+ return () => {
2924
+ off();
2925
+ };
2926
+ }
2927
+ var FLEET_STATUS_MAP = {
2928
+ running: "running",
2929
+ idle: "idle",
2930
+ pending: "pending",
2931
+ completed: "completed",
2932
+ failed: "failed",
2933
+ stopped: "stopped",
2934
+ timeout: "stopped",
2935
+ budget_exhausted: "stopped"
2936
+ };
2937
+ function normalizeSubagentStatus(raw) {
2938
+ return FLEET_STATUS_MAP[raw] ?? "idle";
2939
+ }
2940
+
2941
+ // src/hq/brain-bridge.ts
2942
+ function extractRequestFields(req) {
2943
+ if (req === void 0) return {};
2944
+ const out = {};
2945
+ if (typeof req.id === "string") out.requestId = req.id;
2946
+ if (typeof req.question === "string") out.question = req.question;
2947
+ if (typeof req.source === "string") out.source = req.source;
2948
+ if (typeof req.risk === "string") out.risk = req.risk;
2949
+ return out;
2950
+ }
2951
+ function extractDecisionFields(dec) {
2952
+ if (dec === void 0) return {};
2953
+ const out = {};
2954
+ out.decision = dec.type;
2955
+ if (dec.type === "answer") {
2956
+ out.detail = dec.text;
2957
+ } else if (dec.type === "ask_human") {
2958
+ out.detail = dec.prompt;
2959
+ } else if (dec.type === "deny") {
2960
+ out.detail = dec.reason;
2961
+ }
2962
+ return out;
2963
+ }
2964
+ function startBrainTelemetryBridge(opts) {
2965
+ const { events, publisher } = opts;
2966
+ const now = opts.now ?? (() => (/* @__PURE__ */ new Date()).toISOString());
2967
+ function publish(kind, payload, at) {
2968
+ try {
2969
+ const full = { kind, at, ...payload };
2970
+ publisher.publishEvent({
2971
+ type: "brain.event",
2972
+ payload: full,
2973
+ ...opts.sessionId !== void 0 ? { sessionId: opts.sessionId } : {},
2974
+ timestamp: now()
2975
+ });
2976
+ } catch {
2977
+ }
2978
+ }
2979
+ const offs = [];
2980
+ offs.push(
2981
+ events.on("brain.decision_requested", (p) => {
2982
+ publish("decision_requested", extractRequestFields(p.request), p.at);
2983
+ })
2984
+ );
2985
+ offs.push(
2986
+ events.on("brain.decision_answered", (p) => {
2987
+ publish("decision_answered", {
2988
+ ...extractRequestFields(p.request),
2989
+ ...extractDecisionFields(p.decision)
2990
+ }, p.at);
2991
+ })
2992
+ );
2993
+ offs.push(
2994
+ events.on("brain.decision_ask_human", (p) => {
2995
+ publish("decision_ask_human", {
2996
+ ...extractRequestFields(p.request),
2997
+ ...extractDecisionFields(p.decision)
2998
+ }, p.at);
2999
+ })
3000
+ );
3001
+ offs.push(
3002
+ events.on("brain.decision_denied", (p) => {
3003
+ publish("decision_denied", {
3004
+ ...extractRequestFields(p.request),
3005
+ ...extractDecisionFields(p.decision)
3006
+ }, p.at);
3007
+ })
3008
+ );
3009
+ offs.push(
3010
+ events.on("brain.human_answered", (p) => {
3011
+ const detail = typeof p.text === "string" ? p.text : p.optionId;
3012
+ publish("human_answered", {
3013
+ requestId: p.id,
3014
+ ...detail !== void 0 ? { detail } : {},
3015
+ ...p.deny === true ? { decision: "deny" } : {}
3016
+ }, p.at);
3017
+ })
3018
+ );
3019
+ offs.push(
3020
+ events.on("brain.intervention", (p) => {
3021
+ publish("intervention", {
3022
+ ...extractRequestFields(p.request),
3023
+ ...extractDecisionFields(p.decision),
3024
+ interventionKind: p.kind,
3025
+ intervened: p.intervened
3026
+ }, p.at);
3027
+ })
3028
+ );
3029
+ return () => {
3030
+ for (const off of offs) off();
3031
+ };
3032
+ }
3033
+
3034
+ // src/hq/worktree-bridge.ts
3035
+ function startWorktreeTelemetryBridge(opts) {
3036
+ const { events, publisher } = opts;
3037
+ const now = opts.now ?? (() => (/* @__PURE__ */ new Date()).toISOString());
3038
+ function publish(payload, sessionId) {
3039
+ try {
3040
+ publisher.publishEvent({
3041
+ type: "worktree.event",
3042
+ payload,
3043
+ ...sessionId !== void 0 && sessionId !== "" ? { sessionId } : {},
3044
+ ...opts.sessionId !== void 0 ? { sessionId: opts.sessionId } : {},
3045
+ timestamp: now()
3046
+ });
3047
+ } catch {
3048
+ }
3049
+ }
3050
+ const offs = [];
3051
+ offs.push(
3052
+ events.on("worktree.allocated", (p) => {
3053
+ publish(
3054
+ {
3055
+ kind: "allocated",
3056
+ handleId: p.handleId,
3057
+ ownerId: p.ownerId,
3058
+ ownerLabel: p.ownerLabel,
3059
+ slug: p.slug,
3060
+ branch: p.branch,
3061
+ baseBranch: p.baseBranch
3062
+ },
3063
+ p.sessionId
3064
+ );
3065
+ })
3066
+ );
3067
+ offs.push(
3068
+ events.on("worktree.committed", (p) => {
3069
+ publish(
3070
+ {
3071
+ kind: "committed",
3072
+ handleId: p.handleId,
3073
+ ownerId: p.ownerId,
3074
+ branch: p.branch,
3075
+ insertions: p.insertions,
3076
+ deletions: p.deletions,
3077
+ files: p.files,
3078
+ ...p.sha !== void 0 ? { sha: p.sha } : {}
3079
+ },
3080
+ p.sessionId
3081
+ );
3082
+ })
3083
+ );
3084
+ offs.push(
3085
+ events.on("worktree.merged", (p) => {
3086
+ publish(
3087
+ {
3088
+ kind: "merged",
3089
+ handleId: p.handleId,
3090
+ ownerId: p.ownerId,
3091
+ branch: p.branch,
3092
+ baseBranch: p.baseBranch,
3093
+ squash: p.squash
3094
+ },
3095
+ p.sessionId
3096
+ );
3097
+ })
3098
+ );
3099
+ offs.push(
3100
+ events.on("worktree.conflict", (p) => {
3101
+ publish(
3102
+ {
3103
+ kind: "conflict",
3104
+ handleId: p.handleId,
3105
+ ownerId: p.ownerId,
3106
+ branch: p.branch,
3107
+ conflictFiles: p.conflictFiles
3108
+ },
3109
+ p.sessionId
3110
+ );
3111
+ })
3112
+ );
3113
+ offs.push(
3114
+ events.on("worktree.released", (p) => {
3115
+ publish(
3116
+ {
3117
+ kind: "released",
3118
+ handleId: p.handleId,
3119
+ ownerId: p.ownerId,
3120
+ branch: p.branch,
3121
+ kept: p.kept
3122
+ },
3123
+ p.sessionId
3124
+ );
3125
+ })
3126
+ );
3127
+ offs.push(
3128
+ events.on("worktree.failed", (p) => {
3129
+ publish(
3130
+ {
3131
+ kind: "failed",
3132
+ handleId: p.handleId,
3133
+ ownerId: p.ownerId,
3134
+ ...p.branch !== void 0 ? { branch: p.branch } : {},
3135
+ error: p.error
3136
+ },
3137
+ p.sessionId
3138
+ );
3139
+ })
3140
+ );
3141
+ return () => {
3142
+ for (const off of offs) off();
3143
+ };
3144
+ }
3145
+
3146
+ // src/hq/tool-bridge.ts
3147
+ function startToolTelemetryBridge(opts) {
3148
+ const { events, publisher } = opts;
3149
+ const now = opts.now ?? (() => (/* @__PURE__ */ new Date()).toISOString());
3150
+ const inFlight = /* @__PURE__ */ new Map();
3151
+ function sessionIdTag(sessionId) {
3152
+ const tag = sessionId ?? opts.sessionId;
3153
+ return tag !== void 0 ? { sessionId: tag } : {};
3154
+ }
3155
+ const offStarted = events.on("tool.started", (p) => {
3156
+ inFlight.set(p.id, { name: p.name, startedAt: Date.now() });
3157
+ try {
3158
+ const payload = {
3159
+ toolName: p.name,
3160
+ ...p.input !== void 0 ? {
3161
+ inputSummary: summarizeHqToolArgs(p.input, {
3162
+ ...opts.projectRoot !== void 0 ? { projectRoot: opts.projectRoot } : {}
3163
+ })
3164
+ } : {}
3165
+ };
3166
+ publisher.publishEvent({
3167
+ type: "tool.started",
3168
+ payload,
3169
+ ...sessionIdTag(p.sessionId),
3170
+ timestamp: now()
3171
+ });
3172
+ } catch {
3173
+ }
3174
+ });
3175
+ const offExecuted = events.on("tool.executed", (p) => {
3176
+ const id = p.id;
3177
+ if (id !== void 0) inFlight.delete(id);
3178
+ try {
3179
+ const payload = {
3180
+ toolName: p.name,
3181
+ status: p.ok ? "success" : "error",
3182
+ durationMs: p.durationMs,
3183
+ ...p.output !== void 0 && p.output.length > 0 ? { outputSummary: truncateForSummary(p.output) } : {}
3184
+ };
3185
+ publisher.publishEvent({
3186
+ type: "tool.completed",
3187
+ payload,
3188
+ ...sessionIdTag(p.sessionId),
3189
+ timestamp: now()
3190
+ });
3191
+ } catch {
3192
+ }
3193
+ });
3194
+ return () => {
3195
+ offStarted();
3196
+ offExecuted();
3197
+ inFlight.clear();
3198
+ };
3199
+ }
3200
+ function truncateForSummary(output, max = 280) {
3201
+ if (output.length <= max) return output;
3202
+ return `${output.slice(0, max)}\u2026[truncated:${output.length - max}]`;
3203
+ }
3204
+
3205
+ // src/hq/cost-bridge.ts
3206
+ function startCostTelemetryBridge(opts) {
3207
+ const { events, publisher } = opts;
3208
+ const now = opts.now ?? (() => (/* @__PURE__ */ new Date()).toISOString());
3209
+ const off = events.on("token.accounted", (p) => {
3210
+ try {
3211
+ const payload = {
3212
+ inputTokens: p.usage.input,
3213
+ outputTokens: p.usage.output,
3214
+ totalTokens: p.usage.input + p.usage.output,
3215
+ costUsd: p.cost.total
3216
+ };
3217
+ const sessionId = opts.sessionId ?? p.sessionId;
3218
+ publisher.publishEvent({
3219
+ type: "session.usage",
3220
+ payload,
3221
+ ...sessionId !== void 0 ? { sessionId } : {},
3222
+ timestamp: now()
3223
+ });
3224
+ } catch {
3225
+ }
3226
+ });
3227
+ return () => {
3228
+ off();
3229
+ };
3230
+ }
3231
+ var DEFAULT_EVENT_LOG_MAX_LINES = 5e4;
3232
+ var DEFAULT_EVENT_LOG_ROTATE_KEEP = 2e4;
3233
+ var HqEventLog = class {
3234
+ filePath;
3235
+ maxLines;
3236
+ rotateKeep;
3237
+ writeChain = Promise.resolve();
3238
+ lineCount = 0;
3239
+ counted = false;
3240
+ constructor(opts) {
3241
+ this.filePath = path2.join(opts.dataDir, "events.jsonl");
3242
+ this.maxLines = opts.maxLines ?? DEFAULT_EVENT_LOG_MAX_LINES;
3243
+ this.rotateKeep = opts.rotateKeep ?? DEFAULT_EVENT_LOG_ROTATE_KEEP;
3244
+ }
3245
+ /** Append an event envelope as one JSON line. Best-effort, never rejects. */
3246
+ append(event) {
3247
+ this.writeChain = this.writeChain.then(() => this.appendInternal(event)).catch(() => {
3248
+ });
3249
+ }
3250
+ /** Resolves once all queued appends have settled. For tests. */
3251
+ async drain() {
3252
+ await this.writeChain.catch(() => {
3253
+ });
3254
+ }
3255
+ async appendInternal(event) {
3256
+ if (!this.counted) {
3257
+ this.lineCount = await this.countLines();
3258
+ this.counted = true;
3259
+ }
3260
+ const line = JSON.stringify(event) + "\n";
3261
+ await fsp.appendFile(this.filePath, line, { encoding: "utf8" });
3262
+ this.lineCount += 1;
3263
+ if (this.lineCount >= this.maxLines) {
3264
+ await this.rotate();
3265
+ }
3266
+ }
3267
+ async rotate() {
3268
+ await withFileLock(this.filePath, async () => {
3269
+ try {
3270
+ const content = await fsp.readFile(this.filePath, "utf8");
3271
+ const lines = content.split("\n").filter((l) => l.length > 0);
3272
+ if (lines.length <= this.rotateKeep) {
3273
+ this.lineCount = lines.length;
3274
+ return;
3275
+ }
3276
+ const kept = lines.slice(lines.length - this.rotateKeep);
3277
+ await atomicWrite(this.filePath, kept.join("\n") + "\n");
3278
+ this.lineCount = kept.length;
3279
+ } catch {
3280
+ }
3281
+ });
3282
+ }
3283
+ async countLines() {
3284
+ try {
3285
+ const content = await fsp.readFile(this.filePath, "utf8");
3286
+ return content.split("\n").filter((l) => l.length > 0).length;
3287
+ } catch {
3288
+ return 0;
3289
+ }
3290
+ }
3291
+ /**
3292
+ * Read the most recent `limit` events, optionally filtered by envelope
3293
+ * `type`. Newest first. Returns `[]` if the file doesn't exist yet.
3294
+ */
3295
+ async recent(limit, typeFilter) {
3296
+ let content;
3297
+ try {
3298
+ content = await fsp.readFile(this.filePath, "utf8");
3299
+ } catch {
3300
+ return [];
3301
+ }
3302
+ const lines = content.split("\n").filter((l) => l.length > 0);
3303
+ const out = [];
3304
+ for (let i = lines.length - 1; i >= 0 && out.length < limit; i--) {
3305
+ try {
3306
+ const env = JSON.parse(lines[i]);
3307
+ if (typeFilter === void 0 || env.type === typeFilter) {
3308
+ out.push(env);
3309
+ }
3310
+ } catch {
3311
+ }
3312
+ }
3313
+ return out;
3314
+ }
3315
+ /** Initialize the line count cache from disk (call once at boot). */
3316
+ async hydrate() {
3317
+ this.lineCount = await this.countLines();
3318
+ this.counted = true;
3319
+ }
3320
+ };
3321
+ var HqSnapshotStore = class {
3322
+ filePath;
3323
+ writeChain = Promise.resolve();
3324
+ constructor(opts) {
3325
+ this.filePath = path2.join(opts.dataDir, "snapshot.json");
3326
+ }
3327
+ /** Persist a snapshot. Best-effort, never rejects. */
3328
+ save(snapshot) {
3329
+ this.writeChain = this.writeChain.then(() => atomicWrite(this.filePath, JSON.stringify(snapshot), { mode: 384 })).catch(() => {
3330
+ });
3331
+ }
3332
+ /** Resolves once all queued saves have settled. For tests. */
3333
+ async drain() {
3334
+ await this.writeChain.catch(() => {
3335
+ });
3336
+ }
3337
+ /** Read the last persisted snapshot, or `null` if none. */
3338
+ async load() {
3339
+ try {
3340
+ const content = await fsp.readFile(this.filePath, "utf8");
3341
+ return JSON.parse(content);
3342
+ } catch {
3343
+ return null;
3344
+ }
3345
+ }
3346
+ };
3347
+ var HqTimeseriesStore = class {
3348
+ filePath;
3349
+ bucketMs;
3350
+ maxBuckets;
3351
+ buckets = /* @__PURE__ */ new Map();
3352
+ flushChain = Promise.resolve();
3353
+ constructor(opts) {
3354
+ this.filePath = path2.join(opts.dataDir, "timeseries.jsonl");
3355
+ this.bucketMs = opts.bucketMs ?? 5 * 60 * 1e3;
3356
+ this.maxBuckets = opts.maxBuckets ?? 2016;
3357
+ }
3358
+ bucketStart(ts) {
3359
+ return Math.floor(ts / this.bucketMs) * this.bucketMs;
3360
+ }
3361
+ /** Fold a cost/tool signal into the current bucket. Best-effort. */
3362
+ record(signal) {
3363
+ const start = this.bucketStart(signal.ts ?? Date.now());
3364
+ let bucket = this.buckets.get(start);
3365
+ if (!bucket) {
3366
+ bucket = { ts: start, costUsd: 0, inputTokens: 0, outputTokens: 0, toolCalls: 0 };
3367
+ this.buckets.set(start, bucket);
3368
+ }
3369
+ if (signal.costUsd !== void 0) bucket.costUsd += signal.costUsd;
3370
+ if (signal.inputTokens !== void 0) bucket.inputTokens += signal.inputTokens;
3371
+ if (signal.outputTokens !== void 0) bucket.outputTokens += signal.outputTokens;
3372
+ if (signal.toolCalls !== void 0) bucket.toolCalls += signal.toolCalls;
3373
+ if (signal.activeAgents !== void 0) bucket.activeAgents = signal.activeAgents;
3374
+ if (this.buckets.size > this.maxBuckets) {
3375
+ const sorted = Array.from(this.buckets.keys()).sort((a, b) => a - b);
3376
+ while (this.buckets.size > this.maxBuckets && sorted.length > 0) {
3377
+ const oldest = sorted.shift();
3378
+ if (oldest === void 0) break;
3379
+ this.buckets.delete(oldest);
3380
+ }
3381
+ }
3382
+ }
3383
+ /** Persist accumulated buckets to disk (append under lock), prune to maxBuckets. */
3384
+ flush() {
3385
+ const snapshot = Array.from(this.buckets.values()).sort((a, b) => a.ts - b.ts);
3386
+ if (snapshot.length === 0) return;
3387
+ this.flushChain = this.flushChain.then(() => this.flushInternal(snapshot)).catch(() => {
3388
+ });
3389
+ }
3390
+ /** Resolves once all queued flushes have settled. For tests. */
3391
+ async drain() {
3392
+ await this.flushChain.catch(() => {
3393
+ });
3394
+ }
3395
+ async flushInternal(toWrite) {
3396
+ const lines = toWrite.map((b) => JSON.stringify(b)).join("\n") + "\n";
3397
+ await withFileLock(this.filePath, async () => {
3398
+ await fsp.appendFile(this.filePath, lines, { encoding: "utf8" });
3399
+ });
3400
+ const sorted = Array.from(this.buckets.keys()).sort((a, b) => a - b);
3401
+ while (this.buckets.size > this.maxBuckets) {
3402
+ const oldest = sorted.shift();
3403
+ if (oldest === void 0) break;
3404
+ this.buckets.delete(oldest);
3405
+ }
3406
+ }
3407
+ /** Read buckets within `[since, now]`, oldest-first. */
3408
+ async read(sinceMs) {
3409
+ if (this.buckets.size === 0) await this.load();
3410
+ const since = sinceMs ?? 0;
3411
+ return Array.from(this.buckets.values()).filter((b) => b.ts >= since).sort((a, b) => a.ts - b.ts);
3412
+ }
3413
+ /** Rehydrate buckets from disk (deduped, latest-per-bucket wins). */
3414
+ async load() {
3415
+ let content;
3416
+ try {
3417
+ content = await fsp.readFile(this.filePath, "utf8");
3418
+ } catch {
3419
+ return;
3420
+ }
3421
+ for (const line of content.split("\n")) {
3422
+ const trimmed = line.trim();
3423
+ if (!trimmed) continue;
3424
+ try {
3425
+ const sample = JSON.parse(trimmed);
3426
+ this.buckets.set(sample.ts, sample);
3427
+ } catch {
3428
+ }
3429
+ }
3430
+ const sorted = Array.from(this.buckets.keys()).sort((a, b) => a - b);
3431
+ while (this.buckets.size > this.maxBuckets) {
3432
+ const oldest = sorted.shift();
3433
+ if (oldest === void 0) break;
3434
+ this.buckets.delete(oldest);
3435
+ }
3436
+ }
3437
+ };
3438
+ function createHqPersistence(dataDir) {
3439
+ return {
3440
+ eventLog: new HqEventLog({ dataDir }),
3441
+ snapshotStore: new HqSnapshotStore({ dataDir }),
3442
+ timeseries: new HqTimeseriesStore({ dataDir })
3443
+ };
3444
+ }
3445
+
3446
+ // src/hq/commands.ts
3447
+ var HQ_COMMAND_TYPES = [
3448
+ "steer",
3449
+ "abort",
3450
+ "spawn",
3451
+ "broadcast",
3452
+ "run-command"
3453
+ ];
3454
+ var HQ_COMMAND_TYPE_SET = new Set(HQ_COMMAND_TYPES);
3455
+ function validateHqCommand(queued) {
3456
+ if (!HQ_COMMAND_TYPE_SET.has(queued.type)) return null;
3457
+ const p = queued.payload;
3458
+ if (p === null || typeof p !== "object") return null;
3459
+ switch (queued.type) {
3460
+ case "steer": {
3461
+ if (typeof p["to"] !== "string" || typeof p["subject"] !== "string" || typeof p["body"] !== "string") {
3462
+ return null;
3463
+ }
3464
+ const result = { type: "steer", to: p["to"], subject: p["subject"], body: p["body"] };
3465
+ if (p["priority"] === "low" || p["priority"] === "normal" || p["priority"] === "high") {
3466
+ result.priority = p["priority"];
3467
+ }
3468
+ return result;
3469
+ }
3470
+ case "abort":
3471
+ if (typeof p["target"] !== "string") return null;
3472
+ return { type: "abort", target: p["target"] };
3473
+ case "spawn": {
3474
+ if (typeof p["role"] !== "string") return null;
3475
+ const result = { type: "spawn", role: p["role"] };
3476
+ if (typeof p["task"] === "string") result.task = p["task"];
3477
+ if (typeof p["maxIterations"] === "number") result.maxIterations = p["maxIterations"];
3478
+ return result;
3479
+ }
3480
+ case "broadcast": {
3481
+ if (typeof p["subject"] !== "string" || typeof p["body"] !== "string") return null;
3482
+ const result = { type: "broadcast", subject: p["subject"], body: p["body"] };
3483
+ if (p["priority"] === "low" || p["priority"] === "normal" || p["priority"] === "high") {
3484
+ result.priority = p["priority"];
3485
+ }
3486
+ return result;
3487
+ }
3488
+ case "run-command": {
3489
+ if (typeof p["command"] !== "string") return null;
3490
+ const result = { type: "run-command", command: p["command"] };
3491
+ if (typeof p["cwd"] === "string") result.cwd = p["cwd"];
3492
+ return result;
3493
+ }
3494
+ default:
3495
+ return null;
3496
+ }
3497
+ }
3498
+ var HqCommandAuditLog = class {
3499
+ entries = [];
3500
+ max;
3501
+ constructor(max = 1e3) {
3502
+ this.max = max;
3503
+ }
3504
+ record(entry) {
3505
+ this.entries.push(entry);
3506
+ if (this.entries.length > this.max) {
3507
+ this.entries.splice(0, this.entries.length - this.max);
3508
+ }
3509
+ }
3510
+ update(commandId, patch) {
3511
+ const entry = this.entries.find((e) => e.commandId === commandId);
3512
+ if (entry) Object.assign(entry, patch);
3513
+ }
3514
+ recent(limit = 200) {
3515
+ return this.entries.slice(-limit);
3516
+ }
3517
+ };
3518
+
3519
+ // src/hq/alerts.ts
3520
+ var DEFAULT_CONFIG = {
3521
+ costThresholdUsd: 50,
3522
+ staleMachineSeconds: 120,
3523
+ maxAgents: 0
3524
+ };
3525
+ function resolveConfig(config) {
3526
+ return { ...DEFAULT_CONFIG, ...config ?? {} };
3527
+ }
3528
+ var RULES = [
3529
+ {
3530
+ id: "fleet-cost-threshold",
3531
+ severity: "warn",
3532
+ evaluate: (snapshot, config) => {
3533
+ if (snapshot === null) return null;
3534
+ const cost = snapshot.totals.totalCostUsd ?? 0;
3535
+ if (cost >= config.costThresholdUsd) {
3536
+ return `Fleet cost $${cost.toFixed(2)} exceeded threshold $${config.costThresholdUsd.toFixed(2)}`;
3537
+ }
3538
+ return null;
3539
+ }
3540
+ },
3541
+ {
3542
+ id: "all-machines-stale",
3543
+ severity: "warn",
3544
+ evaluate: (snapshot, config, now) => {
3545
+ if (snapshot === null) return null;
3546
+ const machines = snapshot.machines ?? [];
3547
+ if (machines.length === 0) return null;
3548
+ const cutoff = now - config.staleMachineSeconds * 1e3;
3549
+ const stale = machines.filter((m) => Date.parse(m.lastActivityAt) < cutoff);
3550
+ if (stale.length === machines.length) {
3551
+ return `All ${machines.length} machine(s) silent for ${config.staleMachineSeconds}s`;
3552
+ }
3553
+ return null;
3554
+ }
3555
+ },
3556
+ {
3557
+ id: "high-concurrency",
3558
+ severity: "info",
3559
+ evaluate: (snapshot, config) => {
3560
+ if (snapshot === null || config.maxAgents <= 0) return null;
3561
+ const agents = snapshot.totals.activeAgents ?? 0;
3562
+ if (agents >= config.maxAgents) {
3563
+ return `High fleet concurrency: ${agents} active agents (limit ${config.maxAgents})`;
3564
+ }
3565
+ return null;
3566
+ }
3567
+ },
3568
+ {
3569
+ id: "fleet-failure-spike",
3570
+ severity: "warn",
3571
+ evaluate: (snapshot) => {
3572
+ if (snapshot === null) return null;
3573
+ const fleets = snapshot.fleets ?? [];
3574
+ const failed = fleets.reduce((sum, f) => sum + f.failedTasks, 0);
3575
+ if (failed >= 5) {
3576
+ return `${failed} failed task(s) across the fleet`;
3577
+ }
3578
+ return null;
3579
+ }
3580
+ }
3581
+ ];
3582
+ var HqAlertEngine = class {
3583
+ active = /* @__PURE__ */ new Map();
3584
+ history = [];
3585
+ maxHistory;
3586
+ timer = null;
3587
+ onAlert;
3588
+ constructor(opts) {
3589
+ this.onAlert = opts.onAlert;
3590
+ this.maxHistory = opts.maxHistory ?? 500;
3591
+ }
3592
+ /**
3593
+ * Evaluate all rules against the snapshot. Emits (via the `onAlert`
3594
+ * callback) only for rules that newly transition to firing. Clears rules
3595
+ * that are no longer firing. Returns the list of newly-fired alerts.
3596
+ */
3597
+ evaluate(snapshot, config, now = Date.now()) {
3598
+ const resolved = resolveConfig(config);
3599
+ const fired = [];
3600
+ const firingIds = /* @__PURE__ */ new Set();
3601
+ for (const rule of RULES) {
3602
+ const message = rule.evaluate(snapshot, resolved, now);
3603
+ if (message !== null) {
3604
+ firingIds.add(rule.id);
3605
+ const existing = this.active.get(rule.id);
3606
+ if (existing === void 0) {
3607
+ const alert = {
3608
+ id: `${rule.id}-${now}`,
3609
+ ruleId: rule.id,
3610
+ severity: rule.severity,
3611
+ message,
3612
+ firstFiredAt: now,
3613
+ lastFiredAt: now
3614
+ };
3615
+ this.active.set(rule.id, alert);
3616
+ this.history.push(alert);
3617
+ if (this.history.length > this.maxHistory) this.history.splice(0, this.history.length - this.maxHistory);
3618
+ fired.push(alert);
3619
+ this.onAlert(alert);
3620
+ } else {
3621
+ existing.lastFiredAt = now;
3622
+ }
3623
+ }
3624
+ }
3625
+ for (const id of Array.from(this.active.keys())) {
3626
+ if (!firingIds.has(id)) {
3627
+ this.active.delete(id);
3628
+ }
3629
+ }
3630
+ return fired;
3631
+ }
3632
+ /** Currently-active (firing) alerts. */
3633
+ activeAlerts() {
3634
+ return Array.from(this.active.values());
3635
+ }
3636
+ /** Historical alerts (newest-last), capped at maxHistory. */
3637
+ recentAlerts(limit = 100) {
3638
+ return this.history.slice(-limit);
3639
+ }
3640
+ /**
3641
+ * Start periodic evaluation against a snapshot getter. The timer is
3642
+ * unref'd so it never keeps the process alive. Returns a disposer.
3643
+ */
3644
+ startPeriodic(getSnapshot, config, intervalMs = 15e3) {
3645
+ if (this.timer !== null) return () => void 0;
3646
+ const tick = () => {
3647
+ try {
3648
+ const cfg = typeof config === "function" ? config() : config;
3649
+ this.evaluate(getSnapshot(), cfg);
3650
+ } catch {
3651
+ }
3652
+ };
3653
+ this.timer = setInterval(tick, intervalMs);
3654
+ this.timer.unref?.();
3655
+ return () => {
3656
+ if (this.timer !== null) {
3657
+ clearInterval(this.timer);
3658
+ this.timer = null;
3659
+ }
3660
+ };
3661
+ }
3662
+ };
3663
+ function toAlertMessage(alert) {
3664
+ return {
3665
+ type: "hq.alert",
3666
+ severity: alert.severity,
3667
+ message: `[${alert.ruleId}] ${alert.message}`,
3668
+ timestamp: new Date(alert.lastFiredAt).toISOString()
3669
+ };
3670
+ }
3671
+
3672
+ export { DEFAULT_HQ_REDACTION_POLICY, HQ_AUTH_FILE_VERSION, HQ_COMMAND_TYPES, HQ_PROTOCOL_VERSION, HqAlertEngine, HqCommandAuditLog, HqEventLog, HqPublisher, HqSnapshotStore, HqTimeseriesStore, buildTranscriptFromEvents, createGlobalMailbox, createHqEventEnvelope, createHqPersistence, createHqPublisherFromEnv, createMailboxEventPayload, createMailboxSnapshotPayload, createMailboxSnapshotPayloadFromMailbox, defaultHqDataDir, discoverLocalHqEndpoint, emptyHqAuthFile, ensureHqFirstRunAuthFile, hqAuthFilePath, hqRuntimeFilePath, mapMailboxAgentToHqSummary, mapMailboxMessageToHqSummary, mapSessionEventToEntries, mergeToolResults, mintHqBrowserToken, mintHqToken, mutateHqAuthFile, parseHqEventPayload, parseHqFrame, readHqAuthFile, readHqRuntimeFileSync, redactHqEvent, redactHqValue, resolveHqConfig, resolveHqConfigFromEnv, resolveHqDataDir, scrubAndTruncateHqPreview, startAgentMonitorEventBridge, startBrainTelemetryBridge, startCostTelemetryBridge, startFleetTelemetryBridge, startSessionTelemetryBridge, startToolTelemetryBridge, startWorktreeTelemetryBridge, summarizeHqToolArgs, toAlertMessage, tokenHasCapability, validateHqCommand, watchHqAuthFile, writeHqAuthFile, writeHqRuntimeFile };
2624
3673
  //# sourceMappingURL=index.js.map
2625
3674
  //# sourceMappingURL=index.js.map