@wrongstack/core 0.270.0 → 0.272.1

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 (76) hide show
  1. package/dist/{agent-bridge-PcHQl_UQ.d.ts → agent-bridge-DFQYEeXf.d.ts} +1 -1
  2. package/dist/{agent-subagent-runner-SHJW7t8q.d.ts → agent-subagent-runner-BZa_IEcd.d.ts} +7 -7
  3. package/dist/{brain-BYcK__Ym.d.ts → brain-etbcbRwV.d.ts} +95 -2
  4. package/dist/{compactor-C2RKEBtC.d.ts → compactor-72ug-ZRB.d.ts} +1 -1
  5. package/dist/{config-C_ae2k86.d.ts → config-rRS8yorV.d.ts} +71 -2
  6. package/dist/{context-Dp87Bcaq.d.ts → context-Dw55zZ_Q.d.ts} +110 -1
  7. package/dist/coordination/index.d.ts +181 -17
  8. package/dist/coordination/index.js +1018 -166
  9. package/dist/coordination/index.js.map +1 -1
  10. package/dist/defaults/index.d.ts +25 -25
  11. package/dist/defaults/index.js +804 -222
  12. package/dist/defaults/index.js.map +1 -1
  13. package/dist/execution/index.d.ts +23 -18
  14. package/dist/execution/index.js +136 -41
  15. package/dist/execution/index.js.map +1 -1
  16. package/dist/execution/prompt-enhancer.d.ts +36 -6
  17. package/dist/execution/prompt-enhancer.js +35 -9
  18. package/dist/execution/prompt-enhancer.js.map +1 -1
  19. package/dist/extension/index.d.ts +6 -6
  20. package/dist/{global-mailbox-Bvrz1P3f.d.ts → global-mailbox-DJ4EoRr0.d.ts} +145 -5
  21. package/dist/{goal-preamble-CA_4yiGQ.d.ts → goal-preamble-hM8BH7TK.d.ts} +9 -9
  22. package/dist/{goal-store-DhuJoUNG.d.ts → goal-store-CWlbT0TO.d.ts} +1 -1
  23. package/dist/hq/index.d.ts +95 -6
  24. package/dist/hq/index.js +628 -50
  25. package/dist/hq/index.js.map +1 -1
  26. package/dist/{index-whDfTANu.d.ts → index-2Lhk5v0o.d.ts} +2 -2
  27. package/dist/{index-W4VJCzHa.d.ts → index-DWm_PE9L.d.ts} +5 -5
  28. package/dist/{index-CZQ6Pwbs.d.ts → index-DqW4o62H.d.ts} +8 -8
  29. package/dist/index.d.ts +96 -56
  30. package/dist/index.js +2464 -519
  31. package/dist/index.js.map +1 -1
  32. package/dist/infrastructure/index.d.ts +6 -6
  33. package/dist/infrastructure/index.js +5 -3
  34. package/dist/infrastructure/index.js.map +1 -1
  35. package/dist/kernel/index.d.ts +9 -9
  36. package/dist/kernel/index.js.map +1 -1
  37. package/dist/{mcp-servers-DJdZiRcv.d.ts → mcp-servers-BpWHTKlE.d.ts} +3 -3
  38. package/dist/models/index.d.ts +5 -5
  39. package/dist/models/index.js +28 -5
  40. package/dist/models/index.js.map +1 -1
  41. package/dist/{models-registry-C3a-2-Yd.d.ts → models-registry-CXQFUn5t.d.ts} +1 -1
  42. package/dist/{multi-agent-coordinator-CJSpTe5O.d.ts → multi-agent-coordinator-jyimfo7D.d.ts} +1 -1
  43. package/dist/{null-fleet-bus-QVshIsDx.d.ts → null-fleet-bus-DOGQcvrY.d.ts} +6 -6
  44. package/dist/observability/index.d.ts +2 -2
  45. package/dist/{parallel-eternal-engine-D9y5Pkcc.d.ts → parallel-eternal-engine-rItJBYp9.d.ts} +9 -9
  46. package/dist/{path-resolver-CnQ8SIfh.d.ts → path-resolver-DrpF5MGK.d.ts} +3 -3
  47. package/dist/{permission-CvYQNUqZ.d.ts → permission-CC7XFYWG.d.ts} +1 -1
  48. package/dist/{permission-policy-D5Ss8j4B.d.ts → permission-policy-cYR4RJmw.d.ts} +2 -2
  49. package/dist/{pipeline-l_zzFRh3.d.ts → pipeline-Ckkn3AOA.d.ts} +2 -2
  50. package/dist/{plan-templates-NtPgyeJA.d.ts → plan-templates-BvHw5Znw.d.ts} +33 -9
  51. package/dist/{provider-model-resolve-d5poT5y0.d.ts → provider-model-resolve-nZqnCeaR.d.ts} +3 -3
  52. package/dist/{provider-runner-gkctlQV_.d.ts → provider-runner-zVOn1p67.d.ts} +3 -3
  53. package/dist/{retry-policy-CtFhfwa8.d.ts → retry-policy-BV7nzeAd.d.ts} +1 -1
  54. package/dist/sdd/index.d.ts +8 -8
  55. package/dist/sdd/index.js +2 -0
  56. package/dist/sdd/index.js.map +1 -1
  57. package/dist/{secret-vault-BLsVmTIK.d.ts → secret-vault-eMBKfheR.d.ts} +9 -1
  58. package/dist/security/index.d.ts +5 -5
  59. package/dist/security/index.js +137 -10
  60. package/dist/security/index.js.map +1 -1
  61. package/dist/{selector-CXl2_y9W.d.ts → selector-C4ORTOid.d.ts} +1 -1
  62. package/dist/{session-event-bridge-Ccud20CC.d.ts → session-event-bridge-CeNpUL9w.d.ts} +1 -1
  63. package/dist/{session-reader-ZeXQmsmE.d.ts → session-reader-BepLSnGL.d.ts} +1 -1
  64. package/dist/storage/index.d.ts +50 -13
  65. package/dist/storage/index.js +620 -220
  66. package/dist/storage/index.js.map +1 -1
  67. package/dist/tools/index.d.ts +2 -2
  68. package/dist/tools/index.js +9 -2
  69. package/dist/tools/index.js.map +1 -1
  70. package/dist/types/index.d.ts +19 -19
  71. package/dist/types/index.js +202 -41
  72. package/dist/types/index.js.map +1 -1
  73. package/dist/utils/index.d.ts +17 -4
  74. package/dist/utils/index.js +48 -9
  75. package/dist/utils/index.js.map +1 -1
  76. package/package.json +1 -1
@@ -5,6 +5,8 @@ import { isAbsolute, resolve } from 'path';
5
5
  import * as os from 'os';
6
6
  import { hostname } from 'os';
7
7
  import { EventEmitter } from 'events';
8
+ import { createReadStream } from 'fs';
9
+ import { createInterface } from 'readline';
8
10
 
9
11
  // src/coordination/brain.ts
10
12
  var ObservableBrainArbiter = class {
@@ -5877,6 +5879,68 @@ Working rules:
5877
5879
  - When in doubt, flag as medium rather than ignoring potential issues`
5878
5880
  // Budgets are set by the orchestrator per task — see fleet.ts header.
5879
5881
  };
5882
+ var SHADOW_AGENT = {
5883
+ id: "shadow-agent",
5884
+ name: "Shadow",
5885
+ role: "shadow-agent",
5886
+ prompt: `You are the Shadow Agent \u2014 a silent background monitor for the WrongStack fleet.
5887
+
5888
+ Your job is to observe, detect anomalies, and be ready to intervene \u2014 but only when commanded.
5889
+
5890
+ ## Core Responsibilities
5891
+
5892
+ 1. **Fleet Monitoring** (every 30s)
5893
+ - Call \`fleet_status\` + \`fleet_health\` on each heartbeat
5894
+ - Track what each agent is doing (task descriptions)
5895
+ - Detect stuck agents (>5min no events), idle agents, crashed agents
5896
+
5897
+ 2. **FleetBus Subscription**
5898
+ - Subscribe to \`subagent.*\` events to track lifecycle
5899
+ - Subscribe to \`tool.executed\` to monitor activity
5900
+ - Track agent joins (subagent.started) and leaves (subagent.stopped)
5901
+
5902
+ 3. **Mailbox Surveillance**
5903
+ - Monitor for \`control\` type messages starting with "hoop"
5904
+ - Detect orphan assigns (assign without result within 5min)
5905
+ - Cross-session awareness via shared project mailbox
5906
+
5907
+ 4. **Spike Detection**
5908
+ - Track task duration per agent
5909
+ - Flag agents that spawn and die within <5 seconds
5910
+ - Log spike events with reason (completed/error/killed/timeout)
5911
+
5912
+ 5. **Intervention Commands**
5913
+ Parse these from mailbox control messages:
5914
+ - \`hoop <agentId>\` \u2014 terminate specific agent
5915
+ - \`hoop all\` \u2014 terminate all running agents
5916
+ - \`shadow status\` \u2014 report current fleet snapshot
5917
+ - \`shadow mute\` \u2014 pause heartbeat monitoring
5918
+ - \`shadow resume\` \u2014 resume heartbeat monitoring
5919
+ - \`shadow interval <ms>\` \u2014 change heartbeat interval
5920
+ - \`shadow model <model-id>\` \u2014 change analysis model
5921
+
5922
+ ## Operating Rules
5923
+
5924
+ - **Silent by default**: Use DEBUG level logging unless anomaly detected
5925
+ - **Deterministic**: Same state always produces same actions \u2014 no randomness
5926
+ - **Report on anomaly**: When anomaly detected, use \`mail_send\` to broadcast warning
5927
+ - **Never auto-intervene**: Always report unless explicitly commanded
5928
+ - **Minimal footprint**: Small state, efficient snapshots
5929
+
5930
+ ## Startup Sequence
5931
+
5932
+ 1. Send broadcast: \`shadow:started { intervalMs, model, startTime }\`
5933
+ 2. Subscribe to FleetBus for all relevant events
5934
+ 3. Schedule heartbeat cron job at configured interval
5935
+ 4. Wait for commands or anomalies
5936
+
5937
+ ## Shutdown Sequence
5938
+
5939
+ 1. Cancel all cron jobs (\`cron_cancel\`)
5940
+ 2. Send broadcast: \`shadow:stopped { reason, finalState }\`
5941
+ 3. Clean up FleetBus subscriptions`
5942
+ // Budgets are set by the orchestrator per task — see fleet.ts header.
5943
+ };
5880
5944
  var CRITIC_AGENT = {
5881
5945
  id: "critic",
5882
5946
  name: "Critic",
@@ -5917,6 +5981,7 @@ var FLEET_ROSTER = {
5917
5981
  "refactor-planner": REFACTOR_PLANNER_AGENT,
5918
5982
  "security-scanner": SECURITY_SCANNER_AGENT,
5919
5983
  "critic": CRITIC_AGENT,
5984
+ "shadow-agent": SHADOW_AGENT,
5920
5985
  ...Object.fromEntries(
5921
5986
  ALL_AGENT_DEFINITIONS.map((d) => [d.config.role, d.config])
5922
5987
  )
@@ -5928,6 +5993,8 @@ var FLEET_ROSTER_BUDGETS = {
5928
5993
  "refactor-planner": { timeoutMs: 7.5 * 60 * 60 * 1e3, maxIterations: 6e3, maxToolCalls: 18e3 },
5929
5994
  "security-scanner": { timeoutMs: 10 * 60 * 60 * 1e3, maxIterations: 8e3, maxToolCalls: 2e4 },
5930
5995
  "critic": { timeoutMs: 5 * 60 * 60 * 1e3, maxIterations: 4e3, maxToolCalls: 12e3 },
5996
+ "shadow-agent": { timeoutMs: 24 * 60 * 60 * 1e3, maxIterations: 1e4, maxToolCalls: 5e3 },
5997
+ // Long-running background monitor
5931
5998
  ...Object.fromEntries(
5932
5999
  ALL_AGENT_DEFINITIONS.map((d) => [d.config.role, d.budget])
5933
6000
  )
@@ -8748,6 +8815,8 @@ function generateSessionId(startedAt, model) {
8748
8815
  const modelPart = model ? `_${sanitizeModel(model)}` : "";
8749
8816
  return `${date}/${time}Z${modelPart}_${suffix}`;
8750
8817
  }
8818
+
8819
+ // src/storage/session-store.ts
8751
8820
  var DefaultSessionStore = class _DefaultSessionStore {
8752
8821
  dir;
8753
8822
  events;
@@ -8765,6 +8834,7 @@ var DefaultSessionStore = class _DefaultSessionStore {
8765
8834
  _loadCache = /* @__PURE__ */ new Map();
8766
8835
  _indexCache = null;
8767
8836
  static LOAD_CACHE_MAX_ENTRIES = 50;
8837
+ static LIST_SCAN_CONCURRENCY = 32;
8768
8838
  constructor(opts) {
8769
8839
  this.dir = opts.dir;
8770
8840
  this.events = opts.events;
@@ -8781,7 +8851,7 @@ var DefaultSessionStore = class _DefaultSessionStore {
8781
8851
  this._loadCache.clear();
8782
8852
  }
8783
8853
  }
8784
- // ── Storage event helpers ───────────────────────────────────────────────────
8854
+ // ── Storage event helpers ───────────────────────────────────────────────────
8785
8855
  emitRead(sessionId, filePath, operation, outcome, durationMs, error) {
8786
8856
  this.events?.emit("storage.read", {
8787
8857
  sessionId,
@@ -8896,7 +8966,7 @@ var DefaultSessionStore = class _DefaultSessionStore {
8896
8966
  this.events,
8897
8967
  {
8898
8968
  resumed: true,
8899
- // Shard directory (sessions/<date>/) must match create() so the
8969
+ // Shard directory (sessions/<date>/) — must match create() so the
8900
8970
  // .summary.json sidecar lands next to the JSONL instead of the
8901
8971
  // sessions root (where summaryFor() would never find it).
8902
8972
  dir: path5.dirname(file),
@@ -8937,19 +9007,93 @@ var DefaultSessionStore = class _DefaultSessionStore {
8937
9007
  const raw = await fsp6.readFile(file, "utf8");
8938
9008
  const lines = raw.split("\n").filter((l) => l.trim());
8939
9009
  const events = [];
9010
+ let sessionStartEvent;
9011
+ let sessionEndEvent;
9012
+ let sessionModel;
9013
+ let sessionProvider;
9014
+ let sessionPendingToolUses;
9015
+ const messages = [];
9016
+ const openToolUses = /* @__PURE__ */ new Set();
9017
+ let usage = { input: 0, output: 0, cacheRead: 0, cacheWrite: 0 };
8940
9018
  for (const line of lines) {
8941
9019
  try {
8942
9020
  const parsed = JSON.parse(line);
8943
9021
  if (parsed !== null && typeof parsed === "object" && typeof parsed.type === "string" && typeof parsed.ts === "string") {
8944
- events.push(parsed);
9022
+ const ev = parsed;
9023
+ events.push(ev);
9024
+ if (ev.type === "session_start" && !sessionStartEvent) {
9025
+ sessionStartEvent = ev;
9026
+ sessionModel = ev.model;
9027
+ sessionProvider = ev.provider;
9028
+ }
9029
+ if (ev.type === "session_end") {
9030
+ sessionEndEvent = ev;
9031
+ sessionPendingToolUses = ev.pendingToolUses;
9032
+ }
9033
+ if (ev.type === "user_input") {
9034
+ openToolUses.clear();
9035
+ messages.push({ role: "user", content: ev.content, ts: ev.ts });
9036
+ } else if (ev.type === "llm_response") {
9037
+ messages.push({ role: "assistant", content: ev.content, ts: ev.ts });
9038
+ for (const b of ev.content) {
9039
+ if (b.type === "tool_use") openToolUses.add(b.id);
9040
+ }
9041
+ usage = {
9042
+ input: usage.input + (ev.usage.input ?? 0),
9043
+ output: usage.output + (ev.usage.output ?? 0),
9044
+ cacheRead: (usage.cacheRead ?? 0) + (ev.usage.cacheRead ?? 0),
9045
+ cacheWrite: (usage.cacheWrite ?? 0) + (ev.usage.cacheWrite ?? 0)
9046
+ };
9047
+ } else if (ev.type === "tool_result") {
9048
+ if (!openToolUses.has(ev.id)) {
9049
+ this.events?.emit("session.damaged", {
9050
+ sessionId: id,
9051
+ detail: `Orphan tool_result "${ev.id}" has no matching tool_use`
9052
+ });
9053
+ continue;
9054
+ }
9055
+ openToolUses.delete(ev.id);
9056
+ const resultBlock = {
9057
+ type: "tool_result",
9058
+ tool_use_id: ev.id,
9059
+ content: typeof ev.content === "string" ? ev.content : JSON.stringify(ev.content),
9060
+ is_error: ev.isError
9061
+ };
9062
+ const last = messages[messages.length - 1];
9063
+ const lastIsToolResultUser = last?.role === "user" && Array.isArray(last.content) && last.content.every((b) => b.type === "tool_result");
9064
+ if (lastIsToolResultUser && Array.isArray(last.content)) {
9065
+ last.content.push(resultBlock);
9066
+ } else {
9067
+ messages.push({ role: "user", content: [resultBlock], ts: ev.ts });
9068
+ }
9069
+ }
8945
9070
  }
8946
9071
  } catch {
8947
9072
  }
8948
9073
  }
8949
- const meta = this.metaFromEvents(id, events);
8950
- const { messages, usage } = this.replay(events, id);
9074
+ if (openToolUses.size > 0) {
9075
+ this.events?.emit("session.damaged", {
9076
+ sessionId: id,
9077
+ detail: `${openToolUses.size} tool_use blocks without matching results - replay repaired`
9078
+ });
9079
+ }
9080
+ const repaired = repairToolUseAdjacency(messages);
9081
+ if (repaired.report.changed) {
9082
+ this.events?.emit("session.damaged", {
9083
+ sessionId: id,
9084
+ detail: `Repaired replay adjacency: removed ${repaired.report.removedToolUses.length} tool_use, ${repaired.report.removedToolResults.length} tool_result, ${repaired.report.removedMessages} empty messages`
9085
+ });
9086
+ }
9087
+ const meta = {
9088
+ id,
9089
+ startedAt: sessionStartEvent?.ts ?? (/* @__PURE__ */ new Date(0)).toISOString(),
9090
+ endedAt: sessionEndEvent?.ts,
9091
+ model: sessionModel,
9092
+ provider: sessionProvider,
9093
+ pendingToolUses: sessionPendingToolUses
9094
+ };
8951
9095
  const toolCallEnds = extractToolCallEnds(events);
8952
- const data = { metadata: meta, events, messages, usage, toolCallEnds };
9096
+ const data = { metadata: meta, events, messages: repaired.messages, usage, toolCallEnds };
8953
9097
  if (this._loadCache.size >= _DefaultSessionStore.LOAD_CACHE_MAX_ENTRIES) {
8954
9098
  const oldest = this._loadCache.keys().next().value;
8955
9099
  if (oldest !== void 0) {
@@ -8987,20 +9131,12 @@ var DefaultSessionStore = class _DefaultSessionStore {
8987
9131
  });
8988
9132
  return indexed.slice(0, limit);
8989
9133
  }
8990
- const ids = await this.collectSessionIds(this.dir);
8991
- const sessions = await Promise.all(ids.map((id) => this.summaryFor(id).catch(() => null)));
8992
- const out = sessions.filter((s) => s !== null);
8993
- out.sort((a, b) => {
8994
- if (a.startedAt < b.startedAt) return 1;
8995
- if (a.startedAt > b.startedAt) return -1;
8996
- return a.id.localeCompare(b.id);
8997
- });
8998
- return out.slice(0, limit);
9134
+ return await this.listFromDirectoryScan(limit);
8999
9135
  } catch {
9000
9136
  return [];
9001
9137
  }
9002
9138
  }
9003
- // ── Session index (_index.jsonl) ─────────────────────────────────────────
9139
+ // ── Session index (_index.jsonl) ─────────────────────────────────────────
9004
9140
  //
9005
9141
  // One JSON line per closed session, appended atomically on close().
9006
9142
  // When a session is deleted, a tombstone {action:"delete",id:"..."} is
@@ -9120,43 +9256,102 @@ var DefaultSessionStore = class _DefaultSessionStore {
9120
9256
  this._indexCache = null;
9121
9257
  return valid.length;
9122
9258
  }
9259
+ async listFromDirectoryScan(limit) {
9260
+ const refs = await this.collectSessionFiles(this.dir);
9261
+ const candidates = await mapWithConcurrency(
9262
+ refs,
9263
+ _DefaultSessionStore.LIST_SCAN_CONCURRENCY,
9264
+ async (ref) => {
9265
+ const manifest = await this.readSummaryManifest(ref.id);
9266
+ if (manifest) return { summary: manifest, needsBackfill: false };
9267
+ const summary = await this.summaryHeaderFor(ref);
9268
+ return summary ? { summary, needsBackfill: true } : null;
9269
+ }
9270
+ );
9271
+ const out = candidates.filter((s) => s !== null);
9272
+ out.sort((a, b) => compareSessionSummaries(a.summary, b.summary));
9273
+ const selected = out.slice(0, limit);
9274
+ const summaries = await mapWithConcurrency(
9275
+ selected,
9276
+ Math.min(_DefaultSessionStore.LIST_SCAN_CONCURRENCY, Math.max(1, limit)),
9277
+ async (candidate) => {
9278
+ if (!candidate.needsBackfill) return candidate.summary;
9279
+ return await this.summaryFor(candidate.summary.id).catch(() => candidate.summary);
9280
+ }
9281
+ );
9282
+ return summaries.filter((s) => s !== null);
9283
+ }
9284
+ async collectSessionFiles(dir, prefix = "", depth = 0) {
9285
+ let entries;
9286
+ try {
9287
+ entries = await fsp6.readdir(dir, { withFileTypes: true });
9288
+ } catch {
9289
+ return [];
9290
+ }
9291
+ const dirEntries = [];
9292
+ const files = [];
9293
+ for (const entry of entries) {
9294
+ if (entry.name.startsWith(".") && entry.name !== ".wrongstack") continue;
9295
+ if (entry.name === "shared" || entry.name === "subagents" || entry.name === "attachments")
9296
+ continue;
9297
+ if (entry.isDirectory()) {
9298
+ dirEntries.push(entry);
9299
+ } else if (entry.isFile() && entry.name.endsWith(".jsonl")) {
9300
+ if (entry.name === "_index.jsonl") continue;
9301
+ const base = entry.name.replace(/\.jsonl$/, "");
9302
+ const id = prefix ? `${prefix}/${base}` : base;
9303
+ files.push({ id, filePath: path5.join(dir, entry.name) });
9304
+ }
9305
+ }
9306
+ const childFileArrays = await Promise.all(
9307
+ dirEntries.map((entry) => {
9308
+ const childPrefix = depth === 0 ? entry.name : `${prefix}/${entry.name}`;
9309
+ return this.collectSessionFiles(path5.join(dir, entry.name), childPrefix, depth + 1);
9310
+ })
9311
+ );
9312
+ return [...childFileArrays.flat(), ...files];
9313
+ }
9123
9314
  /** Recursively collect session IDs from date-shard subdirectories.
9124
- * IDs include the date-prefix path (e.g. "2026-06-06/17-46-57Z_").
9315
+ * IDs include the date-prefix path (e.g. "2026-06-06/17-46-57Z_…").
9125
9316
  * Skips `.jsonl`/`.summary.json` root files, dot-files, and
9126
9317
  * sub-directories that belong to fleet/subagent sessions. */
9127
9318
  async collectSessionIds(dir, prefix = "", depth = 0) {
9128
- const ids = [];
9129
9319
  let entries;
9130
9320
  try {
9131
9321
  entries = await fsp6.readdir(dir, { withFileTypes: true });
9132
9322
  } catch {
9133
- return ids;
9323
+ return [];
9134
9324
  }
9325
+ const dirEntries = [];
9326
+ const fileIds = [];
9135
9327
  for (const entry of entries) {
9136
9328
  if (entry.name.startsWith(".") && entry.name !== ".wrongstack") continue;
9137
9329
  if (entry.name === "shared" || entry.name === "subagents" || entry.name === "attachments")
9138
9330
  continue;
9139
9331
  if (entry.isDirectory()) {
9140
- const childPrefix = depth === 0 ? entry.name : `${prefix}/${entry.name}`;
9141
- ids.push(...await this.collectSessionIds(path5.join(dir, entry.name), childPrefix, depth + 1));
9332
+ dirEntries.push(entry);
9142
9333
  } else if (entry.isFile() && entry.name.endsWith(".jsonl")) {
9143
9334
  if (entry.name === "_index.jsonl") continue;
9144
9335
  const base = entry.name.replace(/\.jsonl$/, "");
9145
- ids.push(prefix ? `${prefix}/${base}` : base);
9336
+ fileIds.push(prefix ? `${prefix}/${base}` : base);
9146
9337
  }
9147
9338
  }
9148
- return ids;
9339
+ const childIdArrays = await Promise.all(
9340
+ dirEntries.map((entry) => {
9341
+ const childPrefix = depth === 0 ? entry.name : `${prefix}/${entry.name}`;
9342
+ return this.collectSessionIds(path5.join(dir, entry.name), childPrefix, depth + 1);
9343
+ })
9344
+ );
9345
+ return [...childIdArrays.flat(), ...fileIds];
9149
9346
  }
9150
9347
  async summaryFor(id) {
9151
9348
  const manifest = this.sessionPath(id, ".summary.json");
9152
9349
  const t0 = Date.now();
9153
9350
  let outcome = "success";
9154
9351
  let errorMsg;
9352
+ const fromManifest = await this.readSummaryManifest(id, t0);
9353
+ if (fromManifest) return fromManifest;
9155
9354
  try {
9156
- const raw = await fsp6.readFile(manifest, "utf8");
9157
- this.emitRead(id, manifest, "summary", "success", Date.now() - t0);
9158
- return JSON.parse(raw);
9159
- } catch {
9160
9355
  const full = this.sessionPath(id, ".jsonl");
9161
9356
  const stat7 = await fsp6.stat(full);
9162
9357
  const summary = await this.summarize(id, stat7.mtime.toISOString());
@@ -9172,9 +9367,81 @@ var DefaultSessionStore = class _DefaultSessionStore {
9172
9367
  }));
9173
9368
  });
9174
9369
  outcome = "failure";
9175
- errorMsg = "summary fallback \u2014 manifest rebuilt";
9370
+ errorMsg = "summary fallback \xE2\u20AC\u201D manifest rebuilt";
9176
9371
  this.emitRead(id, manifest, "summary", outcome, Date.now() - t0, errorMsg);
9177
9372
  return summary;
9373
+ } catch (err) {
9374
+ outcome = "failure";
9375
+ errorMsg = toErrorMessage(err);
9376
+ this.emitRead(id, manifest, "summary", outcome, Date.now() - t0, errorMsg);
9377
+ return {
9378
+ id,
9379
+ title: "(damaged)",
9380
+ startedAt: (/* @__PURE__ */ new Date()).toISOString(),
9381
+ model: "unknown",
9382
+ provider: "unknown",
9383
+ tokenTotal: 0
9384
+ };
9385
+ }
9386
+ }
9387
+ async readSummaryManifest(id, startTime = Date.now()) {
9388
+ const manifest = this.sessionPath(id, ".summary.json");
9389
+ try {
9390
+ const raw = await fsp6.readFile(manifest, "utf8");
9391
+ this.emitRead(id, manifest, "summary", "success", Date.now() - startTime);
9392
+ return JSON.parse(raw);
9393
+ } catch {
9394
+ return null;
9395
+ }
9396
+ }
9397
+ async summaryHeaderFor(ref) {
9398
+ let mtime = (/* @__PURE__ */ new Date(0)).toISOString();
9399
+ try {
9400
+ const stat7 = await fsp6.stat(ref.filePath);
9401
+ if (!stat7.isFile()) {
9402
+ return {
9403
+ id: ref.id,
9404
+ title: "(damaged)",
9405
+ startedAt: stat7.mtime.toISOString(),
9406
+ model: "unknown",
9407
+ provider: "unknown",
9408
+ tokenTotal: 0
9409
+ };
9410
+ }
9411
+ mtime = stat7.mtime.toISOString();
9412
+ } catch {
9413
+ return null;
9414
+ }
9415
+ try {
9416
+ for await (const event of this.iterSessionEvents(ref.filePath)) {
9417
+ if (event.type === "session_start") {
9418
+ return {
9419
+ id: ref.id,
9420
+ title: "(empty session)",
9421
+ startedAt: event.ts,
9422
+ model: event.model ?? "unknown",
9423
+ provider: event.provider ?? "unknown",
9424
+ tokenTotal: 0
9425
+ };
9426
+ }
9427
+ }
9428
+ return {
9429
+ id: ref.id,
9430
+ title: "(empty session)",
9431
+ startedAt: (/* @__PURE__ */ new Date(0)).toISOString(),
9432
+ model: "unknown",
9433
+ provider: "unknown",
9434
+ tokenTotal: 0
9435
+ };
9436
+ } catch {
9437
+ return {
9438
+ id: ref.id,
9439
+ title: "(damaged)",
9440
+ startedAt: mtime,
9441
+ model: "unknown",
9442
+ provider: "unknown",
9443
+ tokenTotal: 0
9444
+ };
9178
9445
  }
9179
9446
  }
9180
9447
  /**
@@ -9299,39 +9566,62 @@ var DefaultSessionStore = class _DefaultSessionStore {
9299
9566
  }
9300
9567
  async summarize(id, mtime) {
9301
9568
  try {
9302
- const data = await this.load(id);
9303
- const firstUser = data.events.find((e) => e.type === "user_input");
9304
- const title = firstUser && firstUser.type === "user_input" ? userInputTitle(firstUser.content) : "(empty session)";
9569
+ const file = this.sessionPath(id, ".jsonl");
9570
+ let title = "(empty session)";
9571
+ let startedAt = (/* @__PURE__ */ new Date(0)).toISOString();
9572
+ let endedAt;
9573
+ let model = "unknown";
9574
+ let provider = "unknown";
9575
+ let tokenIn = 0;
9576
+ let tokenOut = 0;
9305
9577
  let iterationCount = 0;
9306
9578
  let toolCallCount = 0;
9307
9579
  let toolErrorCount = 0;
9308
9580
  let fileChangeCount = 0;
9309
9581
  const toolBreakdown = {};
9310
9582
  let outcome;
9311
- const lastEvent = data.events[data.events.length - 1];
9312
- for (const e of data.events) {
9313
- if (e.type === "in_flight_start") iterationCount++;
9583
+ let lastEventType;
9584
+ let hasError = false;
9585
+ let sawStart = false;
9586
+ for await (const e of this.iterSessionEvents(file)) {
9587
+ lastEventType = e.type;
9588
+ if (e.type === "session_start") {
9589
+ if (!sawStart) {
9590
+ sawStart = true;
9591
+ startedAt = e.ts;
9592
+ model = e.model ?? "unknown";
9593
+ provider = e.provider ?? "unknown";
9594
+ }
9595
+ } else if (e.type === "session_end") {
9596
+ endedAt = e.ts;
9597
+ } else if (e.type === "user_input") {
9598
+ if (title === "(empty session)") title = userInputTitle(e.content);
9599
+ } else if (e.type === "llm_response") {
9600
+ tokenIn += e.usage.input ?? 0;
9601
+ tokenOut += e.usage.output ?? 0;
9602
+ } else if (e.type === "in_flight_start") iterationCount++;
9314
9603
  else if (e.type === "tool_call_start") {
9315
9604
  toolCallCount++;
9316
9605
  toolBreakdown[e.name] = (toolBreakdown[e.name] ?? 0) + 1;
9317
9606
  } else if (e.type === "tool_result" && e.isError) toolErrorCount++;
9318
9607
  else if (e.type === "file_snapshot") fileChangeCount += e.files.length;
9608
+ else if (e.type === "error" || e.type === "provider_error") hasError = true;
9319
9609
  }
9320
- if (lastEvent?.type === "session_end") {
9610
+ if (lastEventType === "session_end") {
9321
9611
  outcome = "completed";
9322
- } else if (lastEvent?.type === "in_flight_start") {
9612
+ } else if (lastEventType === "in_flight_start") {
9323
9613
  outcome = "aborted";
9324
- } else if (data.events.some((e) => e.type === "error")) {
9614
+ } else if (hasError) {
9325
9615
  outcome = "error";
9326
9616
  }
9327
9617
  return {
9328
9618
  id,
9329
9619
  title,
9330
- startedAt: data.metadata.startedAt,
9331
- endedAt: data.metadata.endedAt,
9332
- model: data.metadata.model ?? "unknown",
9333
- provider: data.metadata.provider ?? "unknown",
9334
- tokenTotal: data.usage.input + data.usage.output,
9620
+ startedAt,
9621
+ endedAt,
9622
+ model,
9623
+ provider,
9624
+ tokenTotal: tokenIn + tokenOut,
9335
9625
  iterationCount: iterationCount > 0 ? iterationCount : void 0,
9336
9626
  toolCallCount: toolCallCount > 0 ? toolCallCount : void 0,
9337
9627
  toolErrorCount: toolErrorCount > 0 ? toolErrorCount : void 0,
@@ -9350,75 +9640,24 @@ var DefaultSessionStore = class _DefaultSessionStore {
9350
9640
  };
9351
9641
  }
9352
9642
  }
9353
- metaFromEvents(id, events) {
9354
- const start = events.find((e) => e.type === "session_start");
9355
- const end = events.findLast((e) => e.type === "session_end");
9356
- return {
9357
- id,
9358
- startedAt: start?.ts ?? (/* @__PURE__ */ new Date(0)).toISOString(),
9359
- endedAt: end?.ts,
9360
- model: start?.model,
9361
- provider: start?.provider,
9362
- pendingToolUses: end?.pendingToolUses
9363
- };
9364
- }
9365
- replay(events, sessionId = "unknown") {
9366
- const messages = [];
9367
- let usage = { input: 0, output: 0, cacheRead: 0, cacheWrite: 0 };
9368
- const openToolUses = /* @__PURE__ */ new Set();
9369
- for (const e of events) {
9370
- if (e.type === "user_input") {
9371
- openToolUses.clear();
9372
- messages.push({ role: "user", content: e.content, ts: e.ts });
9373
- } else if (e.type === "llm_response") {
9374
- messages.push({ role: "assistant", content: e.content, ts: e.ts });
9375
- for (const b of e.content) {
9376
- if (b.type === "tool_use") openToolUses.add(b.id);
9377
- }
9378
- usage = {
9379
- input: usage.input + (e.usage.input ?? 0),
9380
- output: usage.output + (e.usage.output ?? 0),
9381
- cacheRead: (usage.cacheRead ?? 0) + (e.usage.cacheRead ?? 0),
9382
- cacheWrite: (usage.cacheWrite ?? 0) + (e.usage.cacheWrite ?? 0)
9383
- };
9384
- } else if (e.type === "tool_result") {
9385
- if (!openToolUses.has(e.id)) {
9386
- this.events?.emit("session.damaged", {
9387
- sessionId,
9388
- detail: `Orphan tool_result "${e.id}" has no matching tool_use`
9389
- });
9390
- continue;
9391
- }
9392
- openToolUses.delete(e.id);
9393
- const resultBlock = {
9394
- type: "tool_result",
9395
- tool_use_id: e.id,
9396
- content: typeof e.content === "string" ? e.content : JSON.stringify(e.content),
9397
- is_error: e.isError
9398
- };
9399
- const last = messages[messages.length - 1];
9400
- const lastIsToolResultUser = last?.role === "user" && Array.isArray(last.content) && last.content.every((b) => b.type === "tool_result");
9401
- if (lastIsToolResultUser && Array.isArray(last.content)) {
9402
- last.content.push(resultBlock);
9403
- } else {
9404
- messages.push({ role: "user", content: [resultBlock], ts: e.ts });
9643
+ async *iterSessionEvents(file) {
9644
+ const stream = createReadStream(file, { encoding: "utf8" });
9645
+ const lines = createInterface({ input: stream, crlfDelay: Infinity });
9646
+ try {
9647
+ for await (const line of lines) {
9648
+ if (!line.trim()) continue;
9649
+ try {
9650
+ const parsed = JSON.parse(line);
9651
+ if (parsed !== null && typeof parsed === "object" && typeof parsed.type === "string" && typeof parsed.ts === "string") {
9652
+ yield parsed;
9653
+ }
9654
+ } catch {
9405
9655
  }
9406
9656
  }
9657
+ } finally {
9658
+ lines.close();
9659
+ stream.destroy();
9407
9660
  }
9408
- if (openToolUses.size > 0) {
9409
- this.events?.emit("session.damaged", {
9410
- sessionId,
9411
- detail: `${openToolUses.size} tool_use blocks without matching results - replay repaired`
9412
- });
9413
- }
9414
- const repaired = repairToolUseAdjacency(messages);
9415
- if (repaired.report.changed) {
9416
- this.events?.emit("session.damaged", {
9417
- sessionId,
9418
- detail: `Repaired replay adjacency: removed ${repaired.report.removedToolUses.length} tool_use, ${repaired.report.removedToolResults.length} tool_result, ${repaired.report.removedMessages} empty messages`
9419
- });
9420
- }
9421
- return { messages: repaired.messages, usage };
9422
9661
  }
9423
9662
  };
9424
9663
  function extractToolCallEnds(events) {
@@ -9478,7 +9717,7 @@ var FileSessionWriter = class _FileSessionWriter {
9478
9717
  /**
9479
9718
  * Lazy session_start/session_resumed init, shared by all appenders.
9480
9719
  * A single promise (not a boolean) so a second append racing the first
9481
- * can't push its event into the buffer BEFORE the first append's event
9720
+ * can't push its event into the buffer BEFORE the first append's event —
9482
9721
  * every appender awaits the same init and resumes in FIFO call order.
9483
9722
  */
9484
9723
  initPromise = null;
@@ -9491,24 +9730,24 @@ var FileSessionWriter = class _FileSessionWriter {
9491
9730
  lastAppendWarnAt = 0;
9492
9731
  secretScrubber;
9493
9732
  onCloseCb;
9494
- /** Implements SessionWriter.traceId propagated from ContextInit.traceId. */
9733
+ /** Implements SessionWriter.traceId — propagated from ContextInit.traceId. */
9495
9734
  traceId;
9496
- // ── Write buffer batches events to reduce per-event disk I/O ─────────
9735
+ // ── Write buffer — batches events to reduce per-event disk I/O ─────────
9497
9736
  //
9498
9737
  // Every append() pushes the scrubbed event into an in-memory buffer instead
9499
9738
  // of calling handle.appendFile() synchronously. The buffer flushes to disk
9500
9739
  // when it reaches FLUSH_SIZE events OR after FLUSH_INTERVAL_MS of inactivity.
9501
9740
  // This cuts the number of disk writes by ~95% without changing the on-disk
9502
- // format the JSONL is still one JSON object per line.
9741
+ // format — the JSONL is still one JSON object per line.
9503
9742
  writeBuffer = [];
9504
9743
  flushTimer = null;
9505
9744
  static FLUSH_INTERVAL_MS = 500;
9506
9745
  static FLUSH_SIZE = 50;
9507
- // ── Write serialization ─────────────────────────────────────────────────
9746
+ // ── Write serialization ─────────────────────────────────────────────────
9508
9747
  //
9509
9748
  // All disk writes are funneled through a FIFO promise chain. Without it,
9510
9749
  // a timer-driven flush racing an explicit flush()/close() issues two
9511
- // concurrent appendFile() calls on the shared O_APPEND handle the kernel
9750
+ // concurrent appendFile() calls on the shared O_APPEND handle — the kernel
9512
9751
  // may complete them out of order (chronology breaks) or, for large
9513
9752
  // batches, interleave partial writes (torn JSONL lines). The chain keeps
9514
9753
  // exactly one write in flight; failures don't break the chain.
@@ -9522,7 +9761,7 @@ var FileSessionWriter = class _FileSessionWriter {
9522
9761
  );
9523
9762
  return write;
9524
9763
  }
9525
- // ── Enriched summary tracking ──────────────────────────────────────────
9764
+ // ── Enriched summary tracking ──────────────────────────────────────────
9526
9765
  iterationCount = 0;
9527
9766
  toolCallCount = 0;
9528
9767
  toolErrorCount = 0;
@@ -9614,7 +9853,7 @@ var FileSessionWriter = class _FileSessionWriter {
9614
9853
  * (user_input, llm_response) call this so they survive SIGKILL/crash
9615
9854
  * instead of sitting in the in-memory buffer for up to 500ms.
9616
9855
  *
9617
- * Idempotent cancels any pending timer and writes whatever has
9856
+ * Idempotent — cancels any pending timer and writes whatever has
9618
9857
  * accumulated in the buffer. Safe to call even when the buffer
9619
9858
  * is empty (no-op).
9620
9859
  */
@@ -9637,7 +9876,7 @@ var FileSessionWriter = class _FileSessionWriter {
9637
9876
  /**
9638
9877
  * Flush all buffered events to disk as a single appendFile call.
9639
9878
  * Errors use the same throttled-warning pattern the old per-event
9640
- * append path used one warning every 5s with a suppressed count.
9879
+ * append path used — one warning every 5s with a suppressed count.
9641
9880
  * On failure the buffer is cleared (events are best-effort, same as
9642
9881
  * the old per-event path where a failed write was silently dropped).
9643
9882
  */
@@ -9820,7 +10059,7 @@ var FileSessionWriter = class _FileSessionWriter {
9820
10059
  /**
9821
10060
  * Truncate the session file to the checkpoint with the given promptIndex,
9822
10061
  * 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
10062
+ * so post-checkpoint content is never read or parsed — O(1) memory instead
9824
10063
  * of O(N) JSON.parse calls over the full file.
9825
10064
  */
9826
10065
  async truncateToCheckpoint(targetPromptIndex) {
@@ -9977,7 +10216,7 @@ var FileSessionWriter = class _FileSessionWriter {
9977
10216
  await fsp6.writeFile(this.filePath, record, "utf8");
9978
10217
  }
9979
10218
  /**
9980
- * Idea #1 write an in-flight marker. The agent loop should call
10219
+ * Idea #1 — write an in-flight marker. The agent loop should call
9981
10220
  * this at the start of each long-running operation; a matching
9982
10221
  * `clearInFlightMarker` follows on clean exit. A stale marker
9983
10222
  * (no end) is what `SessionRecovery.detectStale` looks for.
@@ -9994,9 +10233,9 @@ var FileSessionWriter = class _FileSessionWriter {
9994
10233
  this.events?.emit("in_flight.started", { context, ts: (/* @__PURE__ */ new Date()).toISOString() });
9995
10234
  }
9996
10235
  /**
9997
- * Idea #1 close the in-flight marker. Idempotent in spirit
10236
+ * Idea #1 — close the in-flight marker. Idempotent in spirit
9998
10237
  * (you can call it after a successful iteration even if you
9999
- * didn't open one this round) but the session log records
10238
+ * didn't open one this round) — but the session log records
10000
10239
  * every call so postmortem tooling can see "the agent finished
10001
10240
  * cleanly X times, then died without finishing Y".
10002
10241
  */
@@ -10013,6 +10252,27 @@ function userInputTitle(content) {
10013
10252
  const text = typeof content === "string" ? content : content.filter((b) => b.type === "text").map((b) => b.text).join(" ");
10014
10253
  return (text || "(non-text input)").slice(0, 60);
10015
10254
  }
10255
+ function compareSessionSummaries(a, b) {
10256
+ if (a.startedAt < b.startedAt) return 1;
10257
+ if (a.startedAt > b.startedAt) return -1;
10258
+ return a.id.localeCompare(b.id);
10259
+ }
10260
+ async function mapWithConcurrency(items, concurrency, fn) {
10261
+ if (items.length === 0) return [];
10262
+ const out = new Array(items.length);
10263
+ let next = 0;
10264
+ const workerCount = Math.min(Math.max(1, concurrency), items.length);
10265
+ const workers = Array.from({ length: workerCount }, async () => {
10266
+ for (; ; ) {
10267
+ const idx = next++;
10268
+ if (idx >= items.length) return;
10269
+ const item = items[idx];
10270
+ if (item !== void 0) out[idx] = await fn(item);
10271
+ }
10272
+ });
10273
+ await Promise.all(workers);
10274
+ return out;
10275
+ }
10016
10276
 
10017
10277
  // src/coordination/director-session.ts
10018
10278
  function makeDirectorSessionFactory(opts) {
@@ -10487,6 +10747,10 @@ var DefaultMailbox = class {
10487
10747
  _messageCache = null;
10488
10748
  _messageCacheMtime = -1;
10489
10749
  _messageCacheSize = -1;
10750
+ /** Primary index: recipient → Set of messages (points into _messageCache). */
10751
+ _byTo = /* @__PURE__ */ new Map();
10752
+ /** Secondary index: sender → Set of messages (points into _messageCache). */
10753
+ _byFrom = /* @__PURE__ */ new Map();
10490
10754
  constructor(sessionDir) {
10491
10755
  this.filePath = path5.join(sessionDir, MAILBOX_FILE);
10492
10756
  }
@@ -10522,12 +10786,30 @@ var DefaultMailbox = class {
10522
10786
  }
10523
10787
  // ── Query ─────────────────────────────────────────────────────────────
10524
10788
  async query(q) {
10525
- const all = await this._readAllCached();
10789
+ const needFullScan = q.unreadBy !== void 0 || q.since !== void 0;
10790
+ let candidates;
10791
+ if (needFullScan) {
10792
+ candidates = await this._readAllCached();
10793
+ } else {
10794
+ await this._readAllCached();
10795
+ if (q.to !== void 0) {
10796
+ const direct = this._byTo.get(q.to);
10797
+ const broadcast = this._byTo.get("*");
10798
+ const combined = /* @__PURE__ */ new Map();
10799
+ if (direct) for (const m of direct) combined.set(m.id, m);
10800
+ if (broadcast) for (const m of broadcast) combined.set(m.id, m);
10801
+ candidates = Array.from(combined.values());
10802
+ } else if (q.from !== void 0) {
10803
+ candidates = Array.from(this._byFrom.get(q.from) ?? []);
10804
+ } else {
10805
+ candidates = await this._readAllCached();
10806
+ }
10807
+ }
10526
10808
  const limit = q.limit ?? 50;
10527
10809
  const order = q.minPriority !== void 0 ? { low: 0, normal: 1, high: 2 } : null;
10528
10810
  const minPriorityRank = order && q.minPriority !== void 0 ? order[q.minPriority] : 0;
10529
10811
  const filtered = [];
10530
- for (const msg of all) {
10812
+ for (const msg of candidates) {
10531
10813
  if (q.to !== void 0 && msg.to !== q.to && msg.to !== "*") continue;
10532
10814
  if (q.from !== void 0 && msg.from !== q.from) continue;
10533
10815
  if (q.unreadBy !== void 0 && q.unreadBy in msg.readBy) continue;
@@ -10628,6 +10910,8 @@ var DefaultMailbox = class {
10628
10910
  this._messageCache = null;
10629
10911
  this._messageCacheMtime = -1;
10630
10912
  this._messageCacheSize = -1;
10913
+ this._byTo.clear();
10914
+ this._byFrom.clear();
10631
10915
  }
10632
10916
  async clearAll() {
10633
10917
  await withFileLock(this.filePath, async () => {
@@ -10686,36 +10970,81 @@ var DefaultMailbox = class {
10686
10970
  async _readAll() {
10687
10971
  try {
10688
10972
  const raw = await fsp6.readFile(this.filePath, "utf8");
10689
- const lines = raw.split(LINE_SEPARATOR).filter((l) => l.trim().length > 0);
10690
- const messages = [];
10691
- for (const line of lines) {
10692
- try {
10693
- const parsed = JSON.parse(line);
10694
- if (!parsed["readBy"]) {
10695
- const readBy = {};
10696
- if (parsed["read"] && parsed["readAt"]) {
10697
- readBy[parsed["to"] ?? "unknown"] = parsed["readAt"];
10698
- }
10699
- parsed["readBy"] = readBy;
10700
- delete parsed["read"];
10701
- delete parsed["readAt"];
10702
- }
10703
- messages.push(parsed);
10704
- } catch {
10705
- }
10706
- }
10707
- return messages;
10973
+ return this._parseLines(raw);
10708
10974
  } catch (err) {
10709
10975
  if (err.code === "ENOENT") return [];
10710
10976
  throw err;
10711
10977
  }
10712
10978
  }
10979
+ /**
10980
+ * Read only newly-appended bytes from the file and append them to the
10981
+ * in-memory cache, avoiding a full re-read when the file only grew.
10982
+ */
10983
+ async _readNewMessagesOnly(fd, oldSize, newSize) {
10984
+ const tailLen = newSize - oldSize;
10985
+ const buf = Buffer.alloc(tailLen);
10986
+ await fd.read(buf, 0, tailLen, oldSize);
10987
+ const tail = buf.toString("utf8");
10988
+ for (const line of tail.split(LINE_SEPARATOR)) {
10989
+ if (!line.trim()) continue;
10990
+ try {
10991
+ const parsed = JSON.parse(line);
10992
+ if (!parsed["readBy"]) {
10993
+ const readBy = {};
10994
+ if (parsed["read"] && parsed["readAt"]) {
10995
+ readBy[parsed["to"] ?? "unknown"] = parsed["readAt"];
10996
+ }
10997
+ parsed["readBy"] = readBy;
10998
+ delete parsed["read"];
10999
+ delete parsed["readAt"];
11000
+ }
11001
+ const msg = parsed;
11002
+ this._messageCache.push(msg);
11003
+ this._indexMsg(msg);
11004
+ } catch {
11005
+ }
11006
+ }
11007
+ return this._messageCache;
11008
+ }
11009
+ /** Parse a JSONL string into MailboxMessage[], including migration. */
11010
+ _parseLines(raw) {
11011
+ const lines = raw.split(LINE_SEPARATOR).filter((l) => l.trim().length > 0);
11012
+ const messages = [];
11013
+ for (const line of lines) {
11014
+ try {
11015
+ const parsed = JSON.parse(line);
11016
+ if (!parsed["readBy"]) {
11017
+ const readBy = {};
11018
+ if (parsed["read"] && parsed["readAt"]) {
11019
+ readBy[parsed["to"] ?? "unknown"] = parsed["readAt"];
11020
+ }
11021
+ parsed["readBy"] = readBy;
11022
+ delete parsed["read"];
11023
+ delete parsed["readAt"];
11024
+ }
11025
+ messages.push(parsed);
11026
+ } catch {
11027
+ }
11028
+ }
11029
+ return messages;
11030
+ }
10713
11031
  async _readAllCached() {
10714
11032
  try {
10715
11033
  const st = await fsp6.stat(this.filePath);
10716
11034
  if (this._messageCache !== null && this._messageCacheMtime === st.mtimeMs && this._messageCacheSize === st.size) {
10717
11035
  return this._messageCache;
10718
11036
  }
11037
+ if (this._messageCache !== null && this._messageCacheSize >= 0 && st.size > this._messageCacheSize) {
11038
+ const fd = await fsp6.open(this.filePath, "r");
11039
+ try {
11040
+ const updated = await this._readNewMessagesOnly(fd, this._messageCacheSize, st.size);
11041
+ this._messageCacheMtime = st.mtimeMs;
11042
+ this._messageCacheSize = st.size;
11043
+ return updated;
11044
+ } finally {
11045
+ await fd.close();
11046
+ }
11047
+ }
10719
11048
  const all = await this._readAll();
10720
11049
  this._setMessageCache(all, st.mtimeMs, st.size);
10721
11050
  return all;
@@ -10732,9 +11061,12 @@ var DefaultMailbox = class {
10732
11061
  this._messageCache = null;
10733
11062
  this._messageCacheMtime = -1;
10734
11063
  this._messageCacheSize = -1;
11064
+ this._byTo.clear();
11065
+ this._byFrom.clear();
10735
11066
  return;
10736
11067
  }
10737
11068
  this._messageCache = messages;
11069
+ this._buildIndexes(messages);
10738
11070
  if (mtime !== void 0 && size !== void 0) {
10739
11071
  this._messageCacheMtime = mtime;
10740
11072
  this._messageCacheSize = size;
@@ -10752,9 +11084,35 @@ var DefaultMailbox = class {
10752
11084
  this._messageCache = null;
10753
11085
  this._messageCacheMtime = -1;
10754
11086
  this._messageCacheSize = -1;
11087
+ this._byTo.clear();
11088
+ this._byFrom.clear();
10755
11089
  return;
10756
11090
  }
10757
11091
  this._messageCache.push(msg);
11092
+ this._indexMsg(msg);
11093
+ }
11094
+ /** Rebuild both indexes from a full message list. */
11095
+ _buildIndexes(messages) {
11096
+ this._byTo.clear();
11097
+ this._byFrom.clear();
11098
+ for (const msg of messages) {
11099
+ this._indexMsg(msg);
11100
+ }
11101
+ }
11102
+ /** Add a single message to both indexes. */
11103
+ _indexMsg(msg) {
11104
+ const toSet = this._byTo.get(msg.to);
11105
+ if (toSet) {
11106
+ toSet.add(msg);
11107
+ } else {
11108
+ this._byTo.set(msg.to, /* @__PURE__ */ new Set([msg]));
11109
+ }
11110
+ const fromSet = this._byFrom.get(msg.from);
11111
+ if (fromSet) {
11112
+ fromSet.add(msg);
11113
+ } else {
11114
+ this._byFrom.set(msg.from, /* @__PURE__ */ new Set([msg]));
11115
+ }
10758
11116
  }
10759
11117
  };
10760
11118
  var BrainMonitor = class {
@@ -10948,7 +11306,7 @@ var GlobalMailbox = class {
10948
11306
  /**
10949
11307
  * @param projectDir — `~/.wrongstack/projects/<slug>/`
10950
11308
  * @param events — optional EventBus for real-time TUI/WebUI notifications
10951
- * @param hqPublisher — optional HQ publisher for cross-project telemetry
11309
+ * @param hqPublisher — optional HQ publisher, or getter, for cross-project telemetry
10952
11310
  */
10953
11311
  constructor(projectDir, events, hqPublisher) {
10954
11312
  this.messagePath = path5.join(projectDir, MAILBOX_FILE2);
@@ -10960,15 +11318,19 @@ var GlobalMailbox = class {
10960
11318
  get hqMailboxId() {
10961
11319
  return `${path5.basename(path5.dirname(this.messagePath))}:mailbox`;
10962
11320
  }
11321
+ get hqPublisher() {
11322
+ return typeof this._hqPublisher === "function" ? this._hqPublisher() : this._hqPublisher;
11323
+ }
10963
11324
  publishHqMailboxEvent(input) {
10964
11325
  try {
10965
- this._hqPublisher?.publishMailboxEvent(input);
11326
+ this.hqPublisher?.publishMailboxEvent(input);
10966
11327
  } catch {
10967
11328
  }
10968
11329
  }
10969
11330
  publishHqMailboxSnapshot() {
10970
- if (this._hqPublisher === void 0) return;
10971
- void this._hqPublisher.publishMailboxSnapshot(this, { mailboxId: this.hqMailboxId }).catch(() => {
11331
+ const publisher = this.hqPublisher;
11332
+ if (publisher === void 0) return;
11333
+ void publisher.publishMailboxSnapshot(this, { mailboxId: this.hqMailboxId }).catch(() => {
10972
11334
  });
10973
11335
  }
10974
11336
  // ── Messages ────────────────────────────────────────────────────────────
@@ -11330,30 +11692,69 @@ var GlobalMailbox = class {
11330
11692
  async _readMessages() {
11331
11693
  try {
11332
11694
  const raw = await fsp6.readFile(this.messagePath, "utf8");
11333
- const lines = raw.split(LINE_SEPARATOR2).filter((l) => l.trim().length > 0);
11334
- const messages = [];
11335
- for (const line of lines) {
11336
- try {
11337
- const parsed = JSON.parse(line);
11338
- if (!parsed["readBy"]) {
11339
- const readBy = {};
11340
- if (parsed["read"] && parsed["readAt"]) {
11341
- readBy[parsed["to"]] = parsed["readAt"];
11342
- }
11343
- parsed["readBy"] = readBy;
11344
- delete parsed["read"];
11345
- delete parsed["readAt"];
11346
- }
11347
- messages.push(parsed);
11348
- } catch {
11349
- }
11350
- }
11351
- return messages;
11695
+ return this._parseLines(raw);
11352
11696
  } catch (err) {
11353
11697
  if (err.code === "ENOENT") return [];
11354
11698
  throw err;
11355
11699
  }
11356
11700
  }
11701
+ /**
11702
+ * Read only newly-appended bytes from the file and append them to the
11703
+ * in-memory cache. This avoids re-reading and re-parsing the entire file
11704
+ * when another process appended messages since our last read.
11705
+ *
11706
+ * Only safe when the file grew in size (messages are append-only). When
11707
+ * the file was rewritten (ack/purge changed existing content), callers
11708
+ * must fall back to {@link _readMessages}.
11709
+ *
11710
+ * @returns The (now up-to-date) message cache.
11711
+ */
11712
+ async _readNewMessagesOnly(fd, oldSize, newSize) {
11713
+ const tailLen = newSize - oldSize;
11714
+ const buf = Buffer.alloc(tailLen);
11715
+ await fd.read(buf, 0, tailLen, oldSize);
11716
+ const tail = buf.toString("utf8");
11717
+ for (const line of tail.split(LINE_SEPARATOR2)) {
11718
+ if (!line.trim()) continue;
11719
+ try {
11720
+ const parsed = JSON.parse(line);
11721
+ if (!parsed["readBy"]) {
11722
+ const readBy = {};
11723
+ if (parsed["read"] && parsed["readAt"]) {
11724
+ readBy[parsed["to"]] = parsed["readAt"];
11725
+ }
11726
+ parsed["readBy"] = readBy;
11727
+ delete parsed["read"];
11728
+ delete parsed["readAt"];
11729
+ }
11730
+ this._messageCache.push(parsed);
11731
+ } catch {
11732
+ }
11733
+ }
11734
+ return this._messageCache;
11735
+ }
11736
+ /** Parse a JSONL string into MailboxMessage[], including migration. */
11737
+ _parseLines(raw) {
11738
+ const lines = raw.split(LINE_SEPARATOR2).filter((l) => l.trim().length > 0);
11739
+ const messages = [];
11740
+ for (const line of lines) {
11741
+ try {
11742
+ const parsed = JSON.parse(line);
11743
+ if (!parsed["readBy"]) {
11744
+ const readBy = {};
11745
+ if (parsed["read"] && parsed["readAt"]) {
11746
+ readBy[parsed["to"]] = parsed["readAt"];
11747
+ }
11748
+ parsed["readBy"] = readBy;
11749
+ delete parsed["read"];
11750
+ delete parsed["readAt"];
11751
+ }
11752
+ messages.push(parsed);
11753
+ } catch {
11754
+ }
11755
+ }
11756
+ return messages;
11757
+ }
11357
11758
  /**
11358
11759
  * Read messages, then adopt the result as the in-memory cache. Use this
11359
11760
  * from writers that just took the file lock — the read reflects the
@@ -11373,6 +11774,10 @@ var GlobalMailbox = class {
11373
11774
  * stat matches the cached mtime+size we return the cached array — no
11374
11775
  * file read and no JSON.parse — collapsing the per-iteration query
11375
11776
  * cost on the mailbox-loop hot path.
11777
+ *
11778
+ * When the file only grew (new messages appended by another process),
11779
+ * we read and parse just the tail bytes instead of the entire file.
11780
+ * This avoids re-parsing the full 10K-message history on every check.
11376
11781
  */
11377
11782
  async _readMessagesCached() {
11378
11783
  try {
@@ -11380,6 +11785,17 @@ var GlobalMailbox = class {
11380
11785
  if (this._messageCache !== null && this._messageCacheMtime === st.mtimeMs && this._messageCacheSize === st.size) {
11381
11786
  return this._messageCache;
11382
11787
  }
11788
+ if (this._messageCache !== null && this._messageCacheSize >= 0 && st.size > this._messageCacheSize) {
11789
+ const fd = await fsp6.open(this.messagePath, "r");
11790
+ try {
11791
+ const updated = await this._readNewMessagesOnly(fd, this._messageCacheSize, st.size);
11792
+ this._messageCacheMtime = st.mtimeMs;
11793
+ this._messageCacheSize = st.size;
11794
+ return updated;
11795
+ } finally {
11796
+ await fd.close();
11797
+ }
11798
+ }
11383
11799
  const all = await this._readMessages();
11384
11800
  this._setMessageCache(all, st.mtimeMs, st.size);
11385
11801
  return all;
@@ -14814,7 +15230,443 @@ ${input.detail}`
14814
15230
  this.onCoordinatorEvent?.(event);
14815
15231
  }
14816
15232
  };
15233
+ var AgentMonitorService = class {
15234
+ _fleetBus;
15235
+ _events;
15236
+ _transcriptsDir;
15237
+ _maxEntries;
15238
+ _streamEnabled;
15239
+ _onEntry;
15240
+ /** Per-subagent virtual sessions. */
15241
+ _sessions = /* @__PURE__ */ new Map();
15242
+ /** Disposers for FleetBus subscriptions, keyed by subagentId. */
15243
+ _subscriptions = /* @__PURE__ */ new Map();
15244
+ /** Generic fleet-wide subscription disposer. */
15245
+ _fleetDisposer;
15246
+ /** Track whether service is running. */
15247
+ _started = false;
15248
+ constructor(opts) {
15249
+ this._fleetBus = opts.fleetBus;
15250
+ this._events = opts.events;
15251
+ this._transcriptsDir = opts.transcriptsDir;
15252
+ this._maxEntries = opts.maxEntriesPerAgent ?? 500;
15253
+ this._streamEnabled = opts.streamEnabled ?? false;
15254
+ this._onEntry = opts.onEntry;
15255
+ }
15256
+ // ── Public API ────────────────────────────────────────────────────
15257
+ /** Set the FleetBus to listen on. Must be called before `start()`. */
15258
+ setFleetBus(bus) {
15259
+ this._fleetBus = bus;
15260
+ }
15261
+ get streamEnabled() {
15262
+ return this._streamEnabled;
15263
+ }
15264
+ /** Enable/disable streaming agent conversations to the main chat timeline. */
15265
+ setStreamEnabled(enabled) {
15266
+ this._streamEnabled = enabled;
15267
+ }
15268
+ /** Get a snapshot of all known agent sessions. */
15269
+ getAllSessions() {
15270
+ return Array.from(this._sessions.values());
15271
+ }
15272
+ /** Get a specific agent's virtual session, or undefined. */
15273
+ getSession(subagentId) {
15274
+ return this._sessions.get(subagentId);
15275
+ }
15276
+ /** Get transcript entries for a specific agent, newest first. */
15277
+ getTranscript(subagentId, limit = 50) {
15278
+ const session = this._sessions.get(subagentId);
15279
+ if (!session) return [];
15280
+ return session.transcript.slice(-limit).reverse();
15281
+ }
15282
+ /** Set a callback for each new timeline entry (HQ bridge). */
15283
+ setOnEntry(handler) {
15284
+ this._onEntry = handler;
15285
+ }
15286
+ // ── Lifecycle ──────────────────────────────────────────────────────
15287
+ /** Start listening to FleetBus events. */
15288
+ start() {
15289
+ if (this._started) return;
15290
+ if (!this._fleetBus) {
15291
+ this._started = true;
15292
+ return;
15293
+ }
15294
+ this._started = true;
15295
+ this._fleetDisposer = this._fleetBus.onAny((event) => {
15296
+ this._routeEvent(event.subagentId, event.type, event.payload);
15297
+ });
15298
+ }
15299
+ /** Stop listening and clean up all subscriptions. */
15300
+ stop() {
15301
+ if (!this._started) return;
15302
+ this._started = false;
15303
+ if (this._fleetDisposer) {
15304
+ this._fleetDisposer();
15305
+ this._fleetDisposer = void 0;
15306
+ }
15307
+ for (const disposer of this._subscriptions.values()) {
15308
+ disposer();
15309
+ }
15310
+ this._subscriptions.clear();
15311
+ }
15312
+ /** Ensure a subagent is being tracked. Called when a subagent spawns. */
15313
+ trackSubagent(subagentId, agentName, task) {
15314
+ if (this._sessions.has(subagentId)) return;
15315
+ const now = (/* @__PURE__ */ new Date()).toISOString();
15316
+ const session = {
15317
+ subagentId,
15318
+ agentName,
15319
+ createdAt: now,
15320
+ status: "spawned",
15321
+ task,
15322
+ transcript: []
15323
+ };
15324
+ this._sessions.set(subagentId, session);
15325
+ this._addEntry(subagentId, {
15326
+ id: this._uid(),
15327
+ subagentId,
15328
+ agentName,
15329
+ ts: now,
15330
+ kind: "system",
15331
+ content: task ? `\u{1F3AF} Spawned: ${task}` : "\u{1F916} Agent spawned",
15332
+ iteration: 0
15333
+ });
15334
+ this._events.emit("agent.status_changed", {
15335
+ subagentId,
15336
+ agentName,
15337
+ status: "spawned",
15338
+ ts: now,
15339
+ summary: task,
15340
+ task
15341
+ });
15342
+ }
15343
+ /** Mark a subagent as completed/failed/etc. Called on subagent finish. */
15344
+ completeSubagent(subagentId, status, summary) {
15345
+ const session = this._sessions.get(subagentId);
15346
+ if (!session) return;
15347
+ const now = (/* @__PURE__ */ new Date()).toISOString();
15348
+ session.status = status;
15349
+ this._addEntry(subagentId, {
15350
+ id: this._uid(),
15351
+ subagentId,
15352
+ agentName: session.agentName,
15353
+ ts: now,
15354
+ kind: "status",
15355
+ content: summary ?? `Agent ${status}`,
15356
+ iteration: 999
15357
+ });
15358
+ this._events.emit("agent.status_changed", {
15359
+ subagentId,
15360
+ agentName: session.agentName,
15361
+ status,
15362
+ ts: now,
15363
+ summary,
15364
+ task: session.task
15365
+ });
15366
+ }
15367
+ // ── Internal ───────────────────────────────────────────────────────
15368
+ _routeEvent(subagentId, type, payload) {
15369
+ const session = this._sessions.get(subagentId);
15370
+ if (!session) return;
15371
+ switch (type) {
15372
+ case "provider.text_delta": {
15373
+ const text = payload.text;
15374
+ if (!text || text.length === 0) return;
15375
+ const iteration = payload.iteration ?? 0;
15376
+ this._addEntry(subagentId, {
15377
+ id: this._uid(),
15378
+ subagentId,
15379
+ agentName: session.agentName,
15380
+ ts: (/* @__PURE__ */ new Date()).toISOString(),
15381
+ kind: "text",
15382
+ content: text,
15383
+ iteration
15384
+ });
15385
+ break;
15386
+ }
15387
+ case "provider.thinking_delta": {
15388
+ const text = payload.text;
15389
+ if (!text || text.length === 0) return;
15390
+ const iteration = payload.iteration ?? 0;
15391
+ this._addEntry(subagentId, {
15392
+ id: this._uid(),
15393
+ subagentId,
15394
+ agentName: session.agentName,
15395
+ ts: (/* @__PURE__ */ new Date()).toISOString(),
15396
+ kind: "text",
15397
+ content: `\u{1F9E0} ${text}`,
15398
+ iteration
15399
+ });
15400
+ break;
15401
+ }
15402
+ case "tool.started": {
15403
+ const name = payload.name;
15404
+ if (!name) return;
15405
+ this._addEntry(subagentId, {
15406
+ id: this._uid(),
15407
+ subagentId,
15408
+ agentName: session.agentName,
15409
+ ts: (/* @__PURE__ */ new Date()).toISOString(),
15410
+ kind: "tool_use",
15411
+ content: `\u{1F527} ${name}()`,
15412
+ iteration: payload.iteration ?? 0,
15413
+ toolName: name
15414
+ });
15415
+ break;
15416
+ }
15417
+ case "tool.executed": {
15418
+ const name = payload.name;
15419
+ const ok = payload.ok;
15420
+ const durationMs = payload.durationMs;
15421
+ if (!name) return;
15422
+ const statusIcon = ok ? "\u2705" : "\u274C";
15423
+ const duration = durationMs !== void 0 ? ` (${durationMs}ms)` : "";
15424
+ this._addEntry(subagentId, {
15425
+ id: this._uid(),
15426
+ subagentId,
15427
+ agentName: session.agentName,
15428
+ ts: (/* @__PURE__ */ new Date()).toISOString(),
15429
+ kind: "tool_result",
15430
+ content: `${statusIcon} ${name}${duration}`,
15431
+ iteration: payload.iteration ?? 0,
15432
+ toolName: name,
15433
+ toolOk: ok
15434
+ });
15435
+ break;
15436
+ }
15437
+ case "iteration.completed": {
15438
+ const index = payload.index ?? 0;
15439
+ if (index > 0 && index % 5 === 0) {
15440
+ this._addEntry(subagentId, {
15441
+ id: this._uid(),
15442
+ subagentId,
15443
+ agentName: session.agentName,
15444
+ ts: (/* @__PURE__ */ new Date()).toISOString(),
15445
+ kind: "status",
15446
+ content: `\u{1F504} Iteration ${index}`,
15447
+ iteration: index
15448
+ });
15449
+ }
15450
+ break;
15451
+ }
15452
+ }
15453
+ }
15454
+ _addEntry(subagentId, entry) {
15455
+ const session = this._sessions.get(subagentId);
15456
+ if (!session) return;
15457
+ session.transcript.push(entry);
15458
+ if (session.transcript.length > this._maxEntries) {
15459
+ session.transcript.splice(0, session.transcript.length - this._maxEntries);
15460
+ }
15461
+ this._appendToFile(subagentId, entry).catch(() => {
15462
+ });
15463
+ this._events.emit("agent.timeline.message", {
15464
+ subagentId: entry.subagentId,
15465
+ agentName: entry.agentName,
15466
+ content: entry.content,
15467
+ kind: entry.kind === "tool_result" ? "tool_use" : entry.kind === "system" ? "status" : entry.kind,
15468
+ iteration: entry.iteration,
15469
+ ts: entry.ts,
15470
+ toolName: entry.toolName,
15471
+ costUsd: entry.costUsd
15472
+ });
15473
+ this._onEntry?.(entry);
15474
+ }
15475
+ async _appendToFile(subagentId, entry) {
15476
+ const dir = path5.join(this._transcriptsDir, subagentId);
15477
+ await fsp6.mkdir(dir, { recursive: true });
15478
+ const filePath = path5.join(dir, "transcript.jsonl");
15479
+ const line = JSON.stringify(entry) + "\n";
15480
+ await fsp6.appendFile(filePath, line, { encoding: "utf8" });
15481
+ }
15482
+ _uid() {
15483
+ return `${Date.now().toString(36)}${Math.random().toString(36).slice(2, 8)}`;
15484
+ }
15485
+ };
15486
+ function createAgentMonitorService(opts) {
15487
+ return new AgentMonitorService(opts);
15488
+ }
15489
+
15490
+ // src/coordination/adaptive-concurrency.ts
15491
+ var DEFAULT_CONFIG = Object.freeze({
15492
+ enabled: false,
15493
+ minConcurrent: 1,
15494
+ maxConcurrent: 16,
15495
+ decreaseFactor: 0.5,
15496
+ successThreshold: 10,
15497
+ recoveryIntervalMs: 3e4
15498
+ });
15499
+ var AdaptiveConcurrencyController = class {
15500
+ config;
15501
+ state;
15502
+ disposers = [];
15503
+ stateChangeHandlers = [];
15504
+ constructor(fleetBus, setMaxConcurrent, config = {}, onStateChange) {
15505
+ this.config = {
15506
+ enabled: config.enabled ?? DEFAULT_CONFIG.enabled,
15507
+ minConcurrent: config.minConcurrent ?? DEFAULT_CONFIG.minConcurrent,
15508
+ maxConcurrent: config.maxConcurrent ?? DEFAULT_CONFIG.maxConcurrent,
15509
+ decreaseFactor: config.decreaseFactor ?? DEFAULT_CONFIG.decreaseFactor,
15510
+ successThreshold: config.successThreshold ?? DEFAULT_CONFIG.successThreshold,
15511
+ recoveryIntervalMs: config.recoveryIntervalMs ?? DEFAULT_CONFIG.recoveryIntervalMs
15512
+ };
15513
+ this.state = {
15514
+ current: this.config.maxConcurrent,
15515
+ min: this.config.minConcurrent,
15516
+ max: this.config.maxConcurrent,
15517
+ consecutiveSuccesses: 0,
15518
+ consecutiveFailures: 0,
15519
+ totalDecreases: 0,
15520
+ totalIncreases: 0,
15521
+ enabled: this.config.enabled
15522
+ };
15523
+ if (onStateChange) {
15524
+ this.stateChangeHandlers.push(onStateChange);
15525
+ }
15526
+ if (this.config.enabled) {
15527
+ setMaxConcurrent(this.state.current);
15528
+ }
15529
+ this.setupEventHandlers(fleetBus, setMaxConcurrent);
15530
+ }
15531
+ setupEventHandlers(fleetBus, setMaxConcurrent) {
15532
+ if (!this.config.enabled) return;
15533
+ const off = fleetBus.onAny((event) => {
15534
+ if (!this.config.enabled) return;
15535
+ if (event.type === "error" || event.type === "provider_error") {
15536
+ const payload = event.payload;
15537
+ if (payload?.status === 429 || payload?.code === "rate_limit_error" || payload?.kind === "rate_limit") {
15538
+ this.handleRateLimit(setMaxConcurrent);
15539
+ }
15540
+ }
15541
+ });
15542
+ this.disposers.push(off);
15543
+ }
15544
+ /**
15545
+ * Handle a rate limit (429) error - decrease concurrency
15546
+ */
15547
+ handleRateLimit(setMaxConcurrent) {
15548
+ if (this.state.current <= this.config.minConcurrent) {
15549
+ this.state.consecutiveFailures++;
15550
+ this.state.consecutiveSuccesses = 0;
15551
+ this.notifyStateChange();
15552
+ return;
15553
+ }
15554
+ const newConcurrent = Math.max(
15555
+ this.config.minConcurrent,
15556
+ Math.floor(this.state.current * this.config.decreaseFactor)
15557
+ );
15558
+ if (newConcurrent < this.state.current) {
15559
+ const previousConcurrent = this.state.current;
15560
+ this.state.current = newConcurrent;
15561
+ this.state.consecutiveFailures++;
15562
+ this.state.consecutiveSuccesses = 0;
15563
+ this.state.totalDecreases++;
15564
+ setMaxConcurrent(this.state.current);
15565
+ this.notifyStateChange();
15566
+ console.log(
15567
+ JSON.stringify({
15568
+ level: "warn",
15569
+ event: "adaptive_concurrency.decreased",
15570
+ reason: "rate_limit",
15571
+ previousConcurrent,
15572
+ newConcurrent: this.state.current,
15573
+ decreaseFactor: this.config.decreaseFactor,
15574
+ totalDecreases: this.state.totalDecreases
15575
+ })
15576
+ );
15577
+ }
15578
+ }
15579
+ /**
15580
+ * Force a decrease (e.g., manual trigger or other error types)
15581
+ */
15582
+ decrease(target) {
15583
+ if (!this.config.enabled) return;
15584
+ const newConcurrent = target ?? Math.max(
15585
+ this.config.minConcurrent,
15586
+ Math.floor(this.state.current * this.config.decreaseFactor)
15587
+ );
15588
+ if (newConcurrent < this.state.current) {
15589
+ const previousConcurrent = this.state.current;
15590
+ this.state.current = newConcurrent;
15591
+ this.state.consecutiveSuccesses = 0;
15592
+ this.state.totalDecreases++;
15593
+ this.notifyStateChange();
15594
+ console.log(
15595
+ JSON.stringify({
15596
+ level: "warn",
15597
+ event: "adaptive_concurrency.decreased",
15598
+ reason: "manual",
15599
+ previousConcurrent,
15600
+ newConcurrent: this.state.current,
15601
+ totalDecreases: this.state.totalDecreases
15602
+ })
15603
+ );
15604
+ }
15605
+ }
15606
+ /**
15607
+ * Get the current state
15608
+ */
15609
+ getState() {
15610
+ return { ...this.state };
15611
+ }
15612
+ /**
15613
+ * Update configuration at runtime
15614
+ */
15615
+ updateConfig(config) {
15616
+ if (config.enabled !== void 0) {
15617
+ this.config.enabled = config.enabled;
15618
+ }
15619
+ if (config.minConcurrent !== void 0) {
15620
+ this.config.minConcurrent = config.minConcurrent;
15621
+ }
15622
+ if (config.maxConcurrent !== void 0) {
15623
+ this.config.maxConcurrent = config.maxConcurrent;
15624
+ }
15625
+ if (config.decreaseFactor !== void 0) {
15626
+ this.config.decreaseFactor = config.decreaseFactor;
15627
+ }
15628
+ if (config.successThreshold !== void 0) {
15629
+ this.config.successThreshold = config.successThreshold;
15630
+ }
15631
+ if (config.recoveryIntervalMs !== void 0) {
15632
+ this.config.recoveryIntervalMs = config.recoveryIntervalMs;
15633
+ }
15634
+ this.state.current = Math.max(this.config.minConcurrent, Math.min(this.state.current, this.config.maxConcurrent));
15635
+ this.state.enabled = this.config.enabled;
15636
+ this.state.min = this.config.minConcurrent;
15637
+ this.state.max = this.config.maxConcurrent;
15638
+ this.notifyStateChange();
15639
+ }
15640
+ /**
15641
+ * Dispose of the controller and clean up event listeners
15642
+ */
15643
+ dispose() {
15644
+ for (const dispose of this.disposers) {
15645
+ dispose();
15646
+ }
15647
+ this.disposers.length = 0;
15648
+ this.stateChangeHandlers = [];
15649
+ }
15650
+ notifyStateChange() {
15651
+ const state = this.getState();
15652
+ for (const handler of this.stateChangeHandlers) {
15653
+ handler(state);
15654
+ }
15655
+ }
15656
+ /**
15657
+ * Register a state change handler
15658
+ */
15659
+ onStateChange(handler) {
15660
+ this.stateChangeHandlers.push(handler);
15661
+ return () => {
15662
+ const index = this.stateChangeHandlers.indexOf(handler);
15663
+ if (index !== -1) {
15664
+ this.stateChangeHandlers.splice(index, 1);
15665
+ }
15666
+ };
15667
+ }
15668
+ };
14817
15669
 
14818
- export { ACP_AGENTS, AGENTS_BY_PHASE, AGENT_CATALOG, TOOLS as AGENT_TOOL_PRESETS, ALL_AGENT_DEFINITIONS, ALL_FLEET_AGENTS, AUDIT_LOG_AGENT, AutonomousBrain, AutonomousCoordinator, BUG_HUNTER_AGENT, BUILD_AGENTS, BrainDecisionQueue, BrainMonitor, BudgetExceededError, BudgetThresholdSignal, ChangeManager, CollabSession, ConsensusProtocol, DECISION_TIMEOUT_MS, DEFAULT_DIRECTOR_PREAMBLE, DEFAULT_DISPATCH_ROLE, DEFAULT_QUALITY_CHECKS, DEFAULT_SUBAGENT_BASELINE, DELIVERY_AGENTS, DEPENDENCY_FILE_PATTERNS, DISCOVERY_AGENTS, DOMAIN_AGENTS, DefaultBrainArbiter, DefaultMailbox, DefaultMultiAgentCoordinator, Director, DirectorAlertLevel, FLEET_ROSTER, FLEET_ROSTER_BUDGETS, FLEET_ROSTER_WITHACP, FleetBus, FleetCostCapError, FleetManager, FleetSpawnBudgetError, FleetUsageAggregator, GlobalMailbox, HEAVY_BUDGET, HumanEscalatingBrainArbiter, InMemoryAgentBridge, InMemoryBridgeTransport, KNOWLEDGE_AGENTS, KnowledgeGraph, LIGHT_BUDGET, LargeAnswerStore, MEDIUM_BUDGET, META_AGENTS, NULL_FLEET_BUS, ObservableBrainArbiter, PLANNING_AGENTS, REFACTOR_PLANNER_AGENT, REVIEW_AGENTS, SECURITY_SCANNER_AGENT, SubagentBudget, TIMEOUT_PREEMPT_FRACTION, TaskAuctioneer, TaskDAG, VERIFY_AGENTS, applyRosterBudget, attachAutoExtend, attachDepWatcherBridge, composeDirectorPrompt, composeSubagentPrompt, createDelegateTool, createMailboxHooks, createMessage, detectEcosystem, dispatchAgent, formatHumanPrompt, getAgentDefinition, getFullPackageLog, getManifestPackages, getPackageAuthor, getPackagesByAgent, mailboxSessionTag, makeAgentSubagentRunner, makeAskResultTool, makeAskTool, makeAssignTool, makeAwaitTasksTool, makeCollabDebugTool, makeDependencyWatcherConfig, makeDirectorSessionFactory, makeFleetEmitTool, makeFleetHealthTool, makeFleetSessionTool, makeFleetStatusTool, makeFleetUsageTool, makeLLMClassifier, makeMailInboxTool, makeMailSendTool, makeMailboxTool, makeRollUpTool, makeSpawnTool, makeTerminateTool, makeWorkCompleteTool, normalizeRecipient, recordPackageAction, resolveMailboxIdentity, resolveProjectDir, rosterSummaryFromConfigs, scoreAgents, startPackageOutdatedWatcher, updatePackageOutdatedStatus, withDisabledToolFiltering };
15670
+ export { ACP_AGENTS, AGENTS_BY_PHASE, AGENT_CATALOG, TOOLS as AGENT_TOOL_PRESETS, ALL_AGENT_DEFINITIONS, ALL_FLEET_AGENTS, AUDIT_LOG_AGENT, AdaptiveConcurrencyController, AgentMonitorService, AutonomousBrain, AutonomousCoordinator, BUG_HUNTER_AGENT, BUILD_AGENTS, BrainDecisionQueue, BrainMonitor, BudgetExceededError, BudgetThresholdSignal, ChangeManager, CollabSession, ConsensusProtocol, DECISION_TIMEOUT_MS, DEFAULT_DIRECTOR_PREAMBLE, DEFAULT_DISPATCH_ROLE, DEFAULT_QUALITY_CHECKS, DEFAULT_SUBAGENT_BASELINE, DELIVERY_AGENTS, DEPENDENCY_FILE_PATTERNS, DISCOVERY_AGENTS, DOMAIN_AGENTS, DefaultBrainArbiter, DefaultMailbox, DefaultMultiAgentCoordinator, Director, DirectorAlertLevel, FLEET_ROSTER, FLEET_ROSTER_BUDGETS, FLEET_ROSTER_WITHACP, FleetBus, FleetCostCapError, FleetManager, FleetSpawnBudgetError, FleetUsageAggregator, GlobalMailbox, HEAVY_BUDGET, HumanEscalatingBrainArbiter, InMemoryAgentBridge, InMemoryBridgeTransport, KNOWLEDGE_AGENTS, KnowledgeGraph, LIGHT_BUDGET, LargeAnswerStore, MEDIUM_BUDGET, META_AGENTS, NULL_FLEET_BUS, ObservableBrainArbiter, PLANNING_AGENTS, REFACTOR_PLANNER_AGENT, REVIEW_AGENTS, SECURITY_SCANNER_AGENT, SubagentBudget, TIMEOUT_PREEMPT_FRACTION, TaskAuctioneer, TaskDAG, VERIFY_AGENTS, applyRosterBudget, attachAutoExtend, attachDepWatcherBridge, composeDirectorPrompt, composeSubagentPrompt, createAgentMonitorService, createDelegateTool, createMailboxHooks, createMessage, detectEcosystem, dispatchAgent, formatHumanPrompt, getAgentDefinition, getFullPackageLog, getManifestPackages, getPackageAuthor, getPackagesByAgent, mailboxSessionTag, makeAgentSubagentRunner, makeAskResultTool, makeAskTool, makeAssignTool, makeAwaitTasksTool, makeCollabDebugTool, makeDependencyWatcherConfig, makeDirectorSessionFactory, makeFleetEmitTool, makeFleetHealthTool, makeFleetSessionTool, makeFleetStatusTool, makeFleetUsageTool, makeLLMClassifier, makeMailInboxTool, makeMailSendTool, makeMailboxTool, makeRollUpTool, makeSpawnTool, makeTerminateTool, makeWorkCompleteTool, normalizeRecipient, recordPackageAction, resolveMailboxIdentity, resolveProjectDir, rosterSummaryFromConfigs, scoreAgents, startPackageOutdatedWatcher, updatePackageOutdatedStatus, withDisabledToolFiltering };
14819
15671
  //# sourceMappingURL=index.js.map
14820
15672
  //# sourceMappingURL=index.js.map