@wrongstack/core 0.5.5 → 0.5.7

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 (45) hide show
  1. package/dist/{agent-bridge-B07AYFBk.d.ts → agent-bridge-BKNiE1VH.d.ts} +1 -1
  2. package/dist/{compactor-BWhJXxsW.d.ts → compactor-RIPuTtWK.d.ts} +1 -1
  3. package/dist/{config-BgM0BIpz.d.ts → config-BGGuP_Ar.d.ts} +1 -1
  4. package/dist/{context-CLZXPPYk.d.ts → context-CDRyrkKQ.d.ts} +7 -0
  5. package/dist/coordination/index.d.ts +10 -9
  6. package/dist/coordination/index.js +558 -61
  7. package/dist/coordination/index.js.map +1 -1
  8. package/dist/defaults/index.d.ts +19 -18
  9. package/dist/defaults/index.js +521 -64
  10. package/dist/defaults/index.js.map +1 -1
  11. package/dist/director-state-BmYi3DGA.d.ts +108 -0
  12. package/dist/{events-qnDZbrtb.d.ts → events-DPQKFX7W.d.ts} +1 -1
  13. package/dist/execution/index.d.ts +18 -11
  14. package/dist/execution/index.js +19 -3
  15. package/dist/execution/index.js.map +1 -1
  16. package/dist/extension/index.d.ts +6 -6
  17. package/dist/{index-De4R4rQ7.d.ts → index-Bf9Bpkdc.d.ts} +452 -9
  18. package/dist/{index-DPLJw_ZI.d.ts → index-j2WyAyML.d.ts} +20 -5
  19. package/dist/index.d.ts +108 -27
  20. package/dist/index.js +819 -90
  21. package/dist/index.js.map +1 -1
  22. package/dist/infrastructure/index.d.ts +6 -6
  23. package/dist/kernel/index.d.ts +9 -9
  24. package/dist/{mcp-servers-CSMfaBuL.d.ts → mcp-servers-DBdh3cee.d.ts} +3 -3
  25. package/dist/models/index.d.ts +2 -2
  26. package/dist/{multi-agent-Cv8wk47i.d.ts → multi-agent-Cpp7FXUl.d.ts} +11 -3
  27. package/dist/observability/index.d.ts +2 -2
  28. package/dist/{path-resolver-DiCUvEg6.d.ts → path-resolver--g_hKJ7V.d.ts} +2 -2
  29. package/dist/{director-state-BUxlqkOa.d.ts → plan-templates-Bveo2W8n.d.ts} +37 -76
  30. package/dist/{provider-runner-3SHqk9zB.d.ts → provider-runner-hl4Il3xS.d.ts} +3 -3
  31. package/dist/{retry-policy-LLUxJmYY.d.ts → retry-policy-LKS8MHsB.d.ts} +1 -1
  32. package/dist/sdd/index.d.ts +3 -3
  33. package/dist/{secret-scrubber-Z_VXuXQT.d.ts → secret-scrubber-BzQR5BiL.d.ts} +1 -1
  34. package/dist/{secret-scrubber-BhJTNr9v.d.ts → secret-scrubber-CfMdAJ_l.d.ts} +1 -1
  35. package/dist/security/index.d.ts +3 -3
  36. package/dist/{selector-DB2-byKH.d.ts → selector-C7HqnZJU.d.ts} +1 -1
  37. package/dist/{session-reader-4jxsYLZ8.d.ts → session-reader-CzfRA6Vk.d.ts} +1 -1
  38. package/dist/storage/index.d.ts +6 -5
  39. package/dist/storage/index.js +206 -2
  40. package/dist/storage/index.js.map +1 -1
  41. package/dist/{system-prompt-DI4Dtc1I.d.ts → system-prompt-Dl2QY1_B.d.ts} +1 -1
  42. package/dist/{tool-executor-DSvmOBe6.d.ts → tool-executor-DbAFkHdP.d.ts} +4 -4
  43. package/dist/types/index.d.ts +15 -15
  44. package/dist/utils/index.d.ts +1 -1
  45. package/package.json +1 -1
@@ -3,7 +3,12 @@ import * as fsp4 from 'fs/promises';
3
3
  import * as path4 from 'path';
4
4
  import { EventEmitter } from 'events';
5
5
 
6
- // src/coordination/director.ts
6
+ var __require = /* @__PURE__ */ ((x) => typeof require !== "undefined" ? require : typeof Proxy !== "undefined" ? new Proxy(x, {
7
+ get: (a, b) => (typeof require !== "undefined" ? require : a)[b]
8
+ }) : x)(function(x) {
9
+ if (typeof require !== "undefined") return require.apply(this, arguments);
10
+ throw Error('Dynamic require of "' + x + '" is not supported');
11
+ });
7
12
  async function atomicWrite(targetPath, content, opts = {}) {
8
13
  const dir = path4.dirname(targetPath);
9
14
  await fsp4.mkdir(dir, { recursive: true });
@@ -70,15 +75,48 @@ async function renameWithRetry(from, to) {
70
75
  }
71
76
 
72
77
  // src/storage/director-state.ts
78
+ async function acquireDirectorStateLock(lockPath, processId = process.pid) {
79
+ let existing;
80
+ try {
81
+ existing = await fsp4.readFile(lockPath, "utf8");
82
+ } catch {
83
+ }
84
+ if (existing) {
85
+ try {
86
+ const lock2 = JSON.parse(existing);
87
+ try {
88
+ process.kill(lock2.pid, 0);
89
+ return false;
90
+ } catch {
91
+ }
92
+ } catch {
93
+ }
94
+ }
95
+ const lock = {
96
+ pid: processId,
97
+ hostname: __require("os").hostname(),
98
+ startedAt: (/* @__PURE__ */ new Date()).toISOString()
99
+ };
100
+ await atomicWrite(lockPath, JSON.stringify(lock), { mode: 384 });
101
+ return true;
102
+ }
103
+ async function releaseDirectorStateLock(lockPath) {
104
+ try {
105
+ await fsp4.unlink(lockPath);
106
+ } catch {
107
+ }
108
+ }
73
109
  var DirectorStateCheckpoint = class {
74
110
  snapshot;
75
111
  filePath;
112
+ lockPath;
76
113
  timer = null;
77
114
  debounceMs;
78
115
  writing = false;
79
116
  rewriteRequested = false;
80
117
  constructor(filePath, init, debounceMs = 250) {
81
118
  this.filePath = filePath;
119
+ this.lockPath = `${filePath}.lock`;
82
120
  this.debounceMs = debounceMs;
83
121
  this.snapshot = {
84
122
  version: 1,
@@ -88,10 +126,36 @@ var DirectorStateCheckpoint = class {
88
126
  maxSpawns: init.maxSpawns,
89
127
  spawnDepth: init.spawnDepth,
90
128
  maxSpawnDepth: init.maxSpawnDepth,
129
+ directorBudget: init.directorBudget,
91
130
  subagents: [],
92
131
  tasks: []
93
132
  };
94
133
  }
134
+ /**
135
+ * Attempt to acquire the lock for this checkpoint. Call this before
136
+ * resuming a crashed director run. If it returns false, another
137
+ * director process is still running this fleet — do not resume.
138
+ */
139
+ async acquireLock() {
140
+ return acquireDirectorStateLock(this.lockPath);
141
+ }
142
+ /**
143
+ * Release the lock on graceful shutdown. Call `flush()` first to ensure
144
+ * the final checkpoint state is on disk before removing the lock.
145
+ * Without this, the next resume will see a stale-lock and refuse.
146
+ */
147
+ async releaseLock() {
148
+ return releaseDirectorStateLock(this.lockPath);
149
+ }
150
+ /**
151
+ * Resume from a snapshot previously loaded via `loadDirectorState()`.
152
+ * Use this when `--resume <runId>` is triggered — the snapshot has
153
+ * the full fleet state (subagents, tasks) from before the crash; the
154
+ * checkpoint continues from there.
155
+ */
156
+ resume(snapshot) {
157
+ this.snapshot = snapshot;
158
+ }
95
159
  current() {
96
160
  return this.snapshot;
97
161
  }
@@ -1567,7 +1631,10 @@ function makeSpawnTool(director, roster) {
1567
1631
  const subagentId = await director.spawn(cfg);
1568
1632
  return { subagentId, provider: cfg.provider, model: cfg.model, name: cfg.name };
1569
1633
  } catch (err) {
1570
- if (err instanceof DirectorBudgetError) {
1634
+ if (err instanceof FleetSpawnBudgetError) {
1635
+ return { error: err.message, kind: err.kind, limit: err.limit, observed: err.observed };
1636
+ }
1637
+ if (err instanceof FleetCostCapError) {
1571
1638
  return { error: err.message, kind: err.kind, limit: err.limit, observed: err.observed };
1572
1639
  }
1573
1640
  return { error: err instanceof Error ? err.message : String(err) };
@@ -1707,9 +1774,68 @@ function makeFleetUsageTool(director) {
1707
1774
  }
1708
1775
  };
1709
1776
  }
1777
+ function makeFleetSessionTool(director) {
1778
+ return {
1779
+ name: "fleet_session",
1780
+ description: "Read a subagent's JSONL transcript and extract its last assistant text, stop reason, and tool-use count. Use this to see what a running or timed-out subagent actually produced.",
1781
+ permission: "auto",
1782
+ mutating: false,
1783
+ inputSchema: {
1784
+ type: "object",
1785
+ properties: {
1786
+ subagentId: { type: "string", description: "Subagent id to read the transcript of." },
1787
+ /** Number of trailing lines to return (last N JSONL lines). Default: all. */
1788
+ tail: { type: "number", description: "Number of trailing JSONL lines to return. Omit for the full transcript." }
1789
+ },
1790
+ required: ["subagentId"]
1791
+ },
1792
+ async execute(input) {
1793
+ const i = input;
1794
+ const result = await director.readSession(i.subagentId, i.tail);
1795
+ if (!result) {
1796
+ return {
1797
+ error: `fleet_session: transcript unavailable for "${i.subagentId}". Is sessionsRoot configured?`
1798
+ };
1799
+ }
1800
+ return result;
1801
+ }
1802
+ };
1803
+ }
1804
+ function makeFleetHealthTool(director) {
1805
+ return {
1806
+ name: "fleet_health",
1807
+ description: "Per-subagent health report: budget pressure (pct of limits consumed), last activity timestamp, and current status. Use to decide whether to assign more work to a subagent or spawn a fresh one.",
1808
+ permission: "auto",
1809
+ mutating: false,
1810
+ inputSchema: { type: "object", properties: {}, required: [] },
1811
+ async execute() {
1812
+ const status = director.status();
1813
+ const snapshot = director.snapshot();
1814
+ const subagents = status.subagents ?? [];
1815
+ const perSubagent = snapshot.perSubagent ?? {};
1816
+ return {
1817
+ subagents: subagents.map((s) => {
1818
+ const usage = perSubagent[s.id];
1819
+ return {
1820
+ id: s.id,
1821
+ status: s.status,
1822
+ lastEventAt: usage?.lastEventAt,
1823
+ // Budget pressure: fraction of each limit consumed if we have it.
1824
+ // BudgetWarning events carry used/limit ratios; surface them here.
1825
+ budgetPressure: {
1826
+ iterations: usage?.iterations,
1827
+ toolCalls: usage?.toolCalls,
1828
+ costUsd: usage?.cost
1829
+ }
1830
+ };
1831
+ })
1832
+ };
1833
+ }
1834
+ };
1835
+ }
1710
1836
 
1711
1837
  // src/coordination/director.ts
1712
- var DirectorBudgetError = class extends Error {
1838
+ var FleetSpawnBudgetError = class extends Error {
1713
1839
  kind;
1714
1840
  limit;
1715
1841
  observed;
@@ -1717,16 +1843,48 @@ var DirectorBudgetError = class extends Error {
1717
1843
  super(
1718
1844
  kind === "max_spawns" ? `Director spawn budget exceeded: tried to spawn #${observed} but maxSpawns is ${limit}` : `Director spawn depth budget exceeded: this director is at depth ${observed} and maxSpawnDepth is ${limit}`
1719
1845
  );
1720
- this.name = "DirectorBudgetError";
1846
+ this.name = "FleetSpawnBudgetError";
1721
1847
  this.kind = kind;
1722
1848
  this.limit = limit;
1723
1849
  this.observed = observed;
1724
1850
  }
1725
1851
  };
1852
+ var FleetCostCapError = class extends Error {
1853
+ kind;
1854
+ limit;
1855
+ observed;
1856
+ constructor(limit, observed) {
1857
+ super(
1858
+ `Director cost cap exceeded: total fleet spend ${observed.toFixed(4)} exceeds maxCostUsd ${limit.toFixed(4)}`
1859
+ );
1860
+ this.name = "FleetCostCapError";
1861
+ this.kind = "max_cost_usd";
1862
+ this.limit = limit;
1863
+ this.observed = observed;
1864
+ }
1865
+ };
1726
1866
  var Director = class {
1867
+ /** Alias for the ICoordinator contract. `id` is retained for backward compatibility. */
1868
+ get coordinatorId() {
1869
+ return this.id;
1870
+ }
1727
1871
  id;
1872
+ /**
1873
+ * The fleet event bus. Backed by `fleetManager?.fleet` when a FleetManager
1874
+ * is injected; otherwise own FleetBus instance (preserves existing behavior).
1875
+ */
1728
1876
  fleet;
1877
+ /**
1878
+ * Usage rollup. Backed by `fleetManager?.usage` when a FleetManager is
1879
+ * injected; otherwise own FleetUsageAggregator.
1880
+ */
1729
1881
  usage;
1882
+ /**
1883
+ * Optional fleet-level policy container. When provided the Director
1884
+ * delegates spawn budgeting, manifest entries, and checkpointing to it
1885
+ * instead of managing those internally. All other behavior is unchanged.
1886
+ */
1887
+ fleetManager;
1730
1888
  /**
1731
1889
  * Director-side bridge endpoint. Subagents are wired to the same
1732
1890
  * in-memory transport so the director can `ask()` them synchronously
@@ -1777,6 +1935,14 @@ var Director = class {
1777
1935
  /** Debounce timer for periodic manifest writes. */
1778
1936
  manifestTimer = null;
1779
1937
  manifestDebounceMs;
1938
+ /** Fleet-wide cost cap (entire fleet total, distinct from SubagentBudget limits). Infinity means no cap. */
1939
+ maxFleetCostUsd;
1940
+ /** Max auto-extensions per subagent per budget kind before denying. */
1941
+ maxBudgetExtensions;
1942
+ /** Sessions root for direct subagent JSONL reads (fleet_session tool). */
1943
+ sessionsRoot;
1944
+ /** Director run id for JSONL path resolution. */
1945
+ directorRunId;
1780
1946
  /** Resolves task descriptions back from `assign()` so completion events
1781
1947
  * can also carry a human-readable title. */
1782
1948
  taskDescriptions = /* @__PURE__ */ new Map();
@@ -1803,12 +1969,18 @@ var Director = class {
1803
1969
  this.spawnDepth = opts.spawnDepth ?? 0;
1804
1970
  this.sessionWriter = opts.sessionWriter ?? null;
1805
1971
  this.manifestDebounceMs = opts.manifestDebounceMs ?? 2e3;
1972
+ this.maxFleetCostUsd = opts.directorBudget?.maxCostUsd ?? Number.POSITIVE_INFINITY;
1973
+ this.maxBudgetExtensions = opts.maxBudgetExtensions ?? 2;
1974
+ this.sessionsRoot = opts.sessionsRoot;
1975
+ this.directorRunId = opts.directorRunId ?? this.id;
1806
1976
  this.stateCheckpoint = opts.stateCheckpointPath ? new DirectorStateCheckpoint(opts.stateCheckpointPath, {
1807
1977
  directorRunId: this.id,
1808
1978
  maxSpawns: opts.maxSpawns,
1809
1979
  spawnDepth: this.spawnDepth,
1810
- maxSpawnDepth: this.maxSpawnDepth
1811
- }) : null;
1980
+ maxSpawnDepth: this.maxSpawnDepth,
1981
+ directorBudget: opts.directorBudget
1982
+ }, opts.checkpointDebounceMs ?? 250) : null;
1983
+ this.fleetManager = opts.fleetManager;
1812
1984
  if (this.sharedScratchpadPath) {
1813
1985
  void fsp4.mkdir(this.sharedScratchpadPath, { recursive: true }).catch(() => void 0);
1814
1986
  }
@@ -1817,12 +1989,17 @@ var Director = class {
1817
1989
  { agentId: this.id, coordinatorId: this.id },
1818
1990
  this.transport
1819
1991
  );
1820
- this.fleet = new FleetBus();
1821
- this.usage = new FleetUsageAggregator(
1822
- this.fleet,
1823
- (id) => this.priceLookups.get(id),
1824
- (id) => this.subagentMeta.get(id)
1825
- );
1992
+ if (this.fleetManager) {
1993
+ this.fleet = this.fleetManager.fleet;
1994
+ this.usage = this.fleetManager.usage;
1995
+ } else {
1996
+ this.fleet = new FleetBus();
1997
+ this.usage = new FleetUsageAggregator(
1998
+ this.fleet,
1999
+ (id) => this.priceLookups.get(id),
2000
+ (id) => this.subagentMeta.get(id)
2001
+ );
2002
+ }
1826
2003
  this.coordinator = new DefaultMultiAgentCoordinator(
1827
2004
  { ...opts.config, coordinatorId: this.id },
1828
2005
  { runner: opts.runner }
@@ -1869,7 +2046,7 @@ var Director = class {
1869
2046
  const payload = e.payload;
1870
2047
  const guardKey = `${e.subagentId}:${payload.kind}`;
1871
2048
  const prior = extendCounts.get(guardKey) ?? 0;
1872
- if (prior >= 2) {
2049
+ if (prior >= this.maxBudgetExtensions) {
1873
2050
  payload.deny();
1874
2051
  extendCounts.delete(guardKey);
1875
2052
  return;
@@ -1924,52 +2101,73 @@ var Director = class {
1924
2101
  * it the `cost` column in `usage.snapshot()` stays at 0.
1925
2102
  */
1926
2103
  async spawn(config, priceLookup) {
1927
- if (this.spawnDepth >= this.maxSpawnDepth) {
1928
- throw new DirectorBudgetError("max_spawn_depth", this.maxSpawnDepth, this.spawnDepth);
1929
- }
1930
- if (this.spawnCount >= this.maxSpawns) {
1931
- throw new DirectorBudgetError("max_spawns", this.maxSpawns, this.spawnCount + 1);
2104
+ if (this.fleetManager) {
2105
+ const rejection = this.fleetManager.canSpawn(config);
2106
+ if (rejection) {
2107
+ if (rejection.kind === "max_spawn_depth") throw new FleetSpawnBudgetError("max_spawn_depth", rejection.limit, rejection.observed);
2108
+ if (rejection.kind === "max_spawns") throw new FleetSpawnBudgetError("max_spawns", rejection.limit, rejection.observed);
2109
+ if (rejection.kind === "max_cost_usd") throw new FleetCostCapError(rejection.limit, rejection.observed);
2110
+ }
2111
+ } else {
2112
+ if (this.spawnDepth >= this.maxSpawnDepth) {
2113
+ throw new FleetSpawnBudgetError("max_spawn_depth", this.maxSpawnDepth, this.spawnDepth);
2114
+ }
2115
+ if (this.spawnCount >= this.maxSpawns) {
2116
+ throw new FleetSpawnBudgetError("max_spawns", this.maxSpawns, this.spawnCount + 1);
2117
+ }
2118
+ if (this.maxFleetCostUsd < Number.POSITIVE_INFINITY) {
2119
+ const totalCost = this.usage.snapshot().total?.cost ?? 0;
2120
+ if (totalCost >= this.maxFleetCostUsd) {
2121
+ throw new FleetCostCapError(this.maxFleetCostUsd, totalCost);
2122
+ }
2123
+ }
1932
2124
  }
1933
2125
  const result = await this.coordinator.spawn(config);
1934
- this.spawnCount += 1;
1935
- this.subagentMeta.set(result.subagentId, {
1936
- provider: config.provider,
1937
- model: config.model
1938
- });
1939
- if (priceLookup) this.priceLookups.set(result.subagentId, priceLookup);
2126
+ if (this.fleetManager) {
2127
+ this.fleetManager.recordSpawn(result.subagentId, config, priceLookup);
2128
+ } else {
2129
+ this.spawnCount += 1;
2130
+ this.subagentMeta.set(result.subagentId, {
2131
+ provider: config.provider,
2132
+ model: config.model
2133
+ });
2134
+ if (priceLookup) this.priceLookups.set(result.subagentId, priceLookup);
2135
+ }
1940
2136
  const subagentBridge = new InMemoryAgentBridge(
1941
2137
  { agentId: result.subagentId, coordinatorId: this.id },
1942
2138
  this.transport
1943
2139
  );
1944
2140
  this.coordinator.setSubagentBridge(result.subagentId, subagentBridge);
1945
2141
  this.subagentBridges.set(result.subagentId, subagentBridge);
1946
- this.manifestEntries.set(result.subagentId, {
1947
- subagentId: result.subagentId,
1948
- name: config.name,
1949
- role: config.role,
1950
- provider: config.provider,
1951
- model: config.model,
1952
- taskIds: []
1953
- });
1954
- const spawnedAt = (/* @__PURE__ */ new Date()).toISOString();
1955
- this.stateCheckpoint?.recordSpawn(
1956
- {
1957
- id: result.subagentId,
2142
+ if (!this.fleetManager) {
2143
+ this.manifestEntries.set(result.subagentId, {
2144
+ subagentId: result.subagentId,
1958
2145
  name: config.name,
1959
2146
  role: config.role,
1960
2147
  provider: config.provider,
1961
2148
  model: config.model,
1962
- spawnedAt
1963
- },
1964
- this.spawnCount
1965
- );
1966
- void this.appendSessionEvent({
1967
- type: "agent_spawned",
1968
- ts: spawnedAt,
1969
- agentId: result.subagentId,
1970
- role: config.role ?? config.name
1971
- });
1972
- this.scheduleManifest();
2149
+ taskIds: []
2150
+ });
2151
+ const spawnedAt = (/* @__PURE__ */ new Date()).toISOString();
2152
+ this.stateCheckpoint?.recordSpawn(
2153
+ {
2154
+ id: result.subagentId,
2155
+ name: config.name,
2156
+ role: config.role,
2157
+ provider: config.provider,
2158
+ model: config.model,
2159
+ spawnedAt
2160
+ },
2161
+ this.spawnCount
2162
+ );
2163
+ void this.appendSessionEvent({
2164
+ type: "agent_spawned",
2165
+ ts: spawnedAt,
2166
+ agentId: result.subagentId,
2167
+ role: config.role ?? config.name
2168
+ });
2169
+ this.scheduleManifest();
2170
+ }
1973
2171
  return result.subagentId;
1974
2172
  }
1975
2173
  /**
@@ -2109,6 +2307,7 @@ var Director = class {
2109
2307
  if (this.stateCheckpoint) {
2110
2308
  this.stateCheckpoint.setUsage(this.usage.snapshot());
2111
2309
  await this.stateCheckpoint.flush().catch((err) => this.logShutdownError("state_checkpoint_flush", err));
2310
+ await this.stateCheckpoint.releaseLock().catch((err) => this.logShutdownError("state_checkpoint_lock_release", err));
2112
2311
  }
2113
2312
  }
2114
2313
  /**
@@ -2135,8 +2334,12 @@ var Director = class {
2135
2334
  async assign(task) {
2136
2335
  const taskWithId = task.id ? task : { ...task, id: randomUUID() };
2137
2336
  if (task.subagentId) {
2138
- const entry = this.manifestEntries.get(task.subagentId);
2139
- if (entry) entry.taskIds.push(taskWithId.id);
2337
+ if (this.fleetManager) {
2338
+ this.fleetManager.addTaskToSubagent(task.subagentId, taskWithId.id);
2339
+ } else {
2340
+ const entry = this.manifestEntries.get(task.subagentId);
2341
+ if (entry) entry.taskIds.push(taskWithId.id);
2342
+ }
2140
2343
  }
2141
2344
  await this.coordinator.assign(taskWithId);
2142
2345
  this.taskDescriptions.set(taskWithId.id, taskWithId.description);
@@ -2212,6 +2415,58 @@ var Director = class {
2212
2415
  completedResults() {
2213
2416
  return Array.from(this.completed.values());
2214
2417
  }
2418
+ /**
2419
+ * Inject a previously-saved checkpoint snapshot. Call this right after
2420
+ * constructing a Director during a `--resume` run so the in-memory state
2421
+ * (subagents, tasks, waiters) reflects the pre-crash reality instead of
2422
+ * starting from a blank slate. The director then resumes from there —
2423
+ * completing any in-flight tasks and ignoring tasks that already reached
2424
+ * a terminal state in the prior run.
2425
+ */
2426
+ setCheckpointState(snapshot) {
2427
+ this.stateCheckpoint?.resume(snapshot);
2428
+ }
2429
+ /**
2430
+ * Read a subagent's JSONL transcript directly from disk (no bridge
2431
+ * round-trip needed). Returns the last assistant text, stop reason,
2432
+ * tool-use count, and line count — or null if the file is unavailable.
2433
+ * Requires `sessionsRoot` to be set on construction.
2434
+ */
2435
+ async readSession(subagentId, tail) {
2436
+ if (!this.sessionsRoot) return null;
2437
+ const filePath = path4.join(this.sessionsRoot, this.directorRunId, `${subagentId}.jsonl`);
2438
+ let raw;
2439
+ try {
2440
+ raw = await fsp4.readFile(filePath, "utf8");
2441
+ } catch {
2442
+ return null;
2443
+ }
2444
+ const lines = raw.split("\n").filter((l) => l.trim());
2445
+ const targetLines = tail ? lines.slice(-tail) : lines;
2446
+ let lastAssistantText;
2447
+ let lastStopReason;
2448
+ let toolUses = 0;
2449
+ for (const line of targetLines) {
2450
+ try {
2451
+ const ev = JSON.parse(line);
2452
+ if (ev.type === "assistant" && typeof ev.text === "string") {
2453
+ lastAssistantText = ev.text;
2454
+ } else if (ev.type === "stop" && ev.stopReason) {
2455
+ lastStopReason = ev.stopReason;
2456
+ } else if (ev.type === "tool_use") {
2457
+ toolUses++;
2458
+ }
2459
+ } catch {
2460
+ }
2461
+ }
2462
+ return {
2463
+ lastAssistantText,
2464
+ lastStopReason,
2465
+ toolUsesObserved: toolUses,
2466
+ events: targetLines.length,
2467
+ path: filePath
2468
+ };
2469
+ }
2215
2470
  snapshot() {
2216
2471
  return this.usage.snapshot();
2217
2472
  }
@@ -2282,18 +2537,37 @@ var Director = class {
2282
2537
  * still permission-checked normally.
2283
2538
  */
2284
2539
  tools(roster) {
2540
+ const effectiveRoster = roster ?? this.roster;
2285
2541
  const t = [
2286
- makeSpawnTool(this, roster),
2542
+ makeSpawnTool(this, effectiveRoster),
2287
2543
  makeAssignTool(this),
2288
2544
  makeAwaitTasksTool(this),
2289
2545
  makeAskTool(this),
2290
2546
  makeRollUpTool(this),
2291
2547
  makeTerminateTool(this),
2292
2548
  makeFleetStatusTool(this),
2293
- makeFleetUsageTool(this)
2549
+ makeFleetUsageTool(this),
2550
+ makeFleetSessionTool(this),
2551
+ makeFleetHealthTool(this)
2294
2552
  ];
2295
2553
  return t;
2296
2554
  }
2555
+ /**
2556
+ * Attempt to acquire the checkpoint lock. Must be called before
2557
+ * resuming — if another director process is alive, this returns
2558
+ * false and the caller should not proceed with the resume.
2559
+ */
2560
+ async acquireCheckpointLock() {
2561
+ return this.stateCheckpoint ? this.stateCheckpoint.acquireLock() : true;
2562
+ }
2563
+ /**
2564
+ * Resume from a prior checkpoint snapshot (loaded via
2565
+ * `loadDirectorState()`). Re-attach to the fleet mid-flight so
2566
+ * subsequent spawn/assign calls update the checkpoint normally.
2567
+ */
2568
+ resumeFromCheckpoint(snapshot) {
2569
+ this.stateCheckpoint?.resume(snapshot);
2570
+ }
2297
2571
  };
2298
2572
  function createDelegateTool(opts) {
2299
2573
  const defaultTimeoutMs = opts.defaultTimeoutMs ?? 4 * 60 * 60 * 1e3;
@@ -2399,7 +2673,7 @@ function createDelegateTool(opts) {
2399
2673
  if (typeof i.maxToolCalls === "number" && i.maxToolCalls > 0) {
2400
2674
  cfg.maxToolCalls = i.maxToolCalls;
2401
2675
  }
2402
- const SUBAGENT_TIMEOUT_BUFFER_MS = 3e4;
2676
+ const SUBAGENT_TIMEOUT_BUFFER_MS = opts.subagentTimeoutBufferMs ?? 3e4;
2403
2677
  const desiredSubTimeout = Math.max(3e4, timeoutMs - SUBAGENT_TIMEOUT_BUFFER_MS);
2404
2678
  if (!cfg.timeoutMs || cfg.timeoutMs > desiredSubTimeout) {
2405
2679
  cfg.timeoutMs = desiredSubTimeout;
@@ -2451,7 +2725,7 @@ function createDelegateTool(opts) {
2451
2725
  toolCalls: result.toolCalls,
2452
2726
  durationMs: result.durationMs,
2453
2727
  ...partial ? { partial } : {},
2454
- ...hintForKind(errorKind, retryable, backoffMs) ? { hint: hintForKind(errorKind, retryable, backoffMs) } : {}
2728
+ ...hintForKind(errorKind, retryable, backoffMs, partial) ? { hint: hintForKind(errorKind, retryable, backoffMs, partial) } : {}
2455
2729
  };
2456
2730
  } catch (err) {
2457
2731
  return {
@@ -2471,7 +2745,7 @@ function instantiateRosterConfig2(role, base) {
2471
2745
  id: `${role}-${randomUUID().slice(0, 8)}`
2472
2746
  };
2473
2747
  }
2474
- function hintForKind(kind, retryable, backoffMs) {
2748
+ function hintForKind(kind, retryable, backoffMs, partial) {
2475
2749
  if (!kind) return void 0;
2476
2750
  switch (kind) {
2477
2751
  case "provider_rate_limit":
@@ -2487,16 +2761,40 @@ function hintForKind(kind, retryable, backoffMs) {
2487
2761
  case "budget_iterations":
2488
2762
  case "budget_tool_calls":
2489
2763
  case "budget_tokens":
2490
- case "budget_cost":
2491
- return "Subagent exhausted its budget. The coordinator may auto-extend; otherwise raise the matching `max*` field (e.g. maxToolCalls: 600) on the next delegate, or split the task.";
2492
- case "budget_timeout":
2493
- return "Subagent hit its wall-clock budget. Raise `timeoutMs` on the next delegate or split the task.";
2764
+ case "budget_cost": {
2765
+ const base = "Subagent exhausted its budget. The coordinator may auto-extend; otherwise raise the matching `max*` field (e.g. maxToolCalls: 600) on the next delegate, or split the task.";
2766
+ if (partial?.lastAssistantText) {
2767
+ return `${base}
2768
+
2769
+ Partial output produced before budget hit:
2770
+ ${partial.lastAssistantText}`;
2771
+ }
2772
+ return base;
2773
+ }
2774
+ case "budget_timeout": {
2775
+ const base = "Subagent hit its wall-clock budget. Raise `timeoutMs` on the next delegate or split the task.";
2776
+ if (partial?.lastAssistantText) {
2777
+ return `${base}
2778
+
2779
+ Partial output produced before timeout:
2780
+ ${partial.lastAssistantText}`;
2781
+ }
2782
+ return base;
2783
+ }
2494
2784
  case "aborted_by_parent":
2495
2785
  return "Subagent was aborted (user Ctrl+C, parent unwound, or sibling failure cascade). Not retryable until the abort condition is resolved.";
2496
2786
  case "empty_response":
2497
2787
  return "Subagent ended its turn with no text and no tool calls. Almost always a prompt / config issue \u2014 clarify the task or check the model.";
2498
- case "tool_failed":
2499
- return "A tool inside the subagent returned ok:false. Inspect `partial.lastAssistantText` for the agent reasoning, then retry with corrected inputs.";
2788
+ case "tool_failed": {
2789
+ const base = "A tool inside the subagent returned ok:false. Retry with corrected inputs.";
2790
+ if (partial?.lastAssistantText) {
2791
+ return `${base}
2792
+
2793
+ Agent reasoning before failure:
2794
+ ${partial.lastAssistantText}`;
2795
+ }
2796
+ return base;
2797
+ }
2500
2798
  case "bridge_failed":
2501
2799
  return "Parent-child bridge transport failed. This is rare \u2014 restart the session and retry.";
2502
2800
  default:
@@ -3229,6 +3527,205 @@ function makeDirectorSessionFactory(opts) {
3229
3527
  };
3230
3528
  }
3231
3529
 
3232
- export { ALL_FLEET_AGENTS, AUDIT_LOG_AGENT, BUG_HUNTER_AGENT, BudgetExceededError, BudgetThresholdSignal, DEFAULT_DIRECTOR_PREAMBLE, DEFAULT_SUBAGENT_BASELINE, DefaultMultiAgentCoordinator, Director, DirectorBudgetError, FLEET_ROSTER, FleetBus, FleetUsageAggregator, InMemoryAgentBridge, InMemoryBridgeTransport, REFACTOR_PLANNER_AGENT, SECURITY_SCANNER_AGENT, SubagentBudget, composeDirectorPrompt, composeSubagentPrompt, createDelegateTool, createMessage, makeAgentSubagentRunner, makeDirectorSessionFactory, rosterSummaryFromConfigs };
3530
+ // src/coordination/null-fleet-bus.ts
3531
+ var NULL_FLEET_BUS = new FleetBus();
3532
+ var FleetManager = class {
3533
+ /** The fleet-wide event bus. */
3534
+ fleet;
3535
+ /** Usage rollup across all subagents. */
3536
+ usage;
3537
+ manifestPath;
3538
+ sessionsRoot;
3539
+ directorRunId;
3540
+ /** Spawn cap (lifetime total). Infinity means unlimited. */
3541
+ maxSpawns;
3542
+ /** Nesting cap. */
3543
+ maxSpawnDepth;
3544
+ /** This director's depth in a director chain. Root = 0. */
3545
+ spawnDepth;
3546
+ /** Live spawn counter. */
3547
+ spawnCount = 0;
3548
+ stateCheckpoint;
3549
+ sessionWriter;
3550
+ manifestTimer = null;
3551
+ manifestDebounceMs;
3552
+ /** Fleet-wide cost cap. Infinity = no cap. Distinct from SubagentBudget limits,
3553
+ * which track per-subagent spend — this field caps the entire fleet total. */
3554
+ maxFleetCostUsd;
3555
+ manifestEntries = /* @__PURE__ */ new Map();
3556
+ /** Pending tasks with their descriptions — populated by `addPendingTask`
3557
+ * and cleared by `removePendingTask`. Replaces the host-side `pending`
3558
+ * Map so task descriptions live in one place (FleetManager). */
3559
+ pendingTasks = /* @__PURE__ */ new Map();
3560
+ subagentMeta = /* @__PURE__ */ new Map();
3561
+ priceLookups = /* @__PURE__ */ new Map();
3562
+ constructor(opts = {}) {
3563
+ this.manifestPath = opts.manifestPath;
3564
+ this.sessionsRoot = opts.sessionsRoot;
3565
+ this.directorRunId = opts.directorRunId ?? randomUUID();
3566
+ this.maxSpawns = opts.maxSpawns ?? Number.POSITIVE_INFINITY;
3567
+ this.maxSpawnDepth = opts.maxSpawnDepth ?? 2;
3568
+ this.spawnDepth = opts.spawnDepth ?? 0;
3569
+ this.sessionWriter = opts.sessionWriter ?? null;
3570
+ this.manifestDebounceMs = opts.manifestDebounceMs ?? 2e3;
3571
+ this.maxFleetCostUsd = opts.directorBudget?.maxCostUsd ?? Number.POSITIVE_INFINITY;
3572
+ this.stateCheckpoint = opts.stateCheckpointPath ? new DirectorStateCheckpoint(
3573
+ opts.stateCheckpointPath,
3574
+ {
3575
+ directorRunId: this.directorRunId,
3576
+ maxSpawns: opts.maxSpawns,
3577
+ spawnDepth: this.spawnDepth,
3578
+ maxSpawnDepth: this.maxSpawnDepth,
3579
+ directorBudget: opts.directorBudget
3580
+ },
3581
+ opts.checkpointDebounceMs ?? 250
3582
+ ) : null;
3583
+ this.fleet = new FleetBus();
3584
+ this.usage = new FleetUsageAggregator(
3585
+ this.fleet,
3586
+ (id) => this.priceLookups.get(id),
3587
+ (id) => this.subagentMeta.get(id)
3588
+ );
3589
+ }
3590
+ // -----------------------------------------------------------------------
3591
+ // IFleetManager surface
3592
+ // -----------------------------------------------------------------------
3593
+ get fleetBus() {
3594
+ return this.fleet;
3595
+ }
3596
+ snapshot() {
3597
+ return this.usage.snapshot();
3598
+ }
3599
+ getSubagentMeta(id) {
3600
+ return this.subagentMeta.get(id);
3601
+ }
3602
+ /**
3603
+ * Returns null if the spawn is allowed, or an object describing
3604
+ * which cap was exceeded. Does NOT throw — the caller decides
3605
+ * how to surface the rejection.
3606
+ */
3607
+ canSpawn(config) {
3608
+ if (this.spawnDepth >= this.maxSpawnDepth) {
3609
+ return { kind: "max_spawn_depth", limit: this.maxSpawnDepth, observed: this.spawnDepth };
3610
+ }
3611
+ if (this.spawnCount >= this.maxSpawns) {
3612
+ return { kind: "max_spawns", limit: this.maxSpawns, observed: this.spawnCount + 1 };
3613
+ }
3614
+ if (this.maxFleetCostUsd < Number.POSITIVE_INFINITY) {
3615
+ const totalCost = this.usage.snapshot().total?.cost ?? 0;
3616
+ if (totalCost >= this.maxFleetCostUsd) {
3617
+ return { kind: "max_cost_usd", limit: this.maxFleetCostUsd, observed: totalCost };
3618
+ }
3619
+ }
3620
+ return null;
3621
+ }
3622
+ /**
3623
+ * Records a spawn: increments counter, stores metadata, updates state checkpoint,
3624
+ * and schedules a debounced manifest write. Call AFTER the coordinator
3625
+ * has successfully spawned the subagent.
3626
+ *
3627
+ * @param subagentId The subagent's id (from coordinator.spawn result)
3628
+ * @param config The SubagentConfig that was used
3629
+ * @param priceLookup Optional per-subagent pricing data
3630
+ */
3631
+ recordSpawn(subagentId, config, priceLookup) {
3632
+ this.spawnCount += 1;
3633
+ this.subagentMeta.set(subagentId, {
3634
+ provider: config.provider,
3635
+ model: config.model
3636
+ });
3637
+ if (priceLookup) this.priceLookups.set(subagentId, priceLookup);
3638
+ this.manifestEntries.set(subagentId, {
3639
+ subagentId,
3640
+ name: config.name,
3641
+ role: config.role,
3642
+ provider: config.provider,
3643
+ model: config.model,
3644
+ taskIds: []
3645
+ });
3646
+ this.stateCheckpoint?.recordSpawn({
3647
+ id: subagentId,
3648
+ name: config.name,
3649
+ role: config.role,
3650
+ provider: config.provider,
3651
+ model: config.model,
3652
+ spawnedAt: (/* @__PURE__ */ new Date()).toISOString()
3653
+ }, this.spawnCount);
3654
+ void this.appendSessionEvent({
3655
+ type: "agent_spawned",
3656
+ ts: (/* @__PURE__ */ new Date()).toISOString(),
3657
+ agentId: subagentId,
3658
+ role: config.role ?? config.name
3659
+ });
3660
+ this.scheduleManifest();
3661
+ }
3662
+ async writeManifest() {
3663
+ if (!this.manifestPath) return null;
3664
+ const manifest = {
3665
+ version: 1,
3666
+ directorRunId: this.directorRunId,
3667
+ generatedAt: (/* @__PURE__ */ new Date()).toISOString(),
3668
+ children: Array.from(this.manifestEntries.values()).map((entry) => ({
3669
+ id: entry.subagentId,
3670
+ name: entry.name,
3671
+ role: entry.role,
3672
+ provider: entry.provider,
3673
+ model: entry.model,
3674
+ taskIds: entry.taskIds
3675
+ })),
3676
+ usage: this.usage.snapshot()
3677
+ };
3678
+ await fsp4.mkdir(path4.dirname(this.manifestPath), { recursive: true });
3679
+ await fsp4.writeFile(this.manifestPath, JSON.stringify(manifest, null, 2), { mode: 384 });
3680
+ return this.manifestPath;
3681
+ }
3682
+ /**
3683
+ * Attach task ids to an already-spawned subagent. Called by
3684
+ * `Director.assign()` after the coordinator assigns a task.
3685
+ */
3686
+ addTaskToSubagent(subagentId, taskId) {
3687
+ const entry = this.manifestEntries.get(subagentId);
3688
+ if (entry) entry.taskIds.push(taskId);
3689
+ }
3690
+ /**
3691
+ * Debounced manifest write. Call after any state mutation
3692
+ * (spawn, assign, complete) so a burst collapses into one write.
3693
+ */
3694
+ scheduleManifest() {
3695
+ if (!this.manifestPath || this.manifestDebounceMs <= 0) return;
3696
+ if (this.manifestTimer) return;
3697
+ this.manifestTimer = setTimeout(() => {
3698
+ this.manifestTimer = null;
3699
+ void this.writeManifest().catch(() => void 0);
3700
+ }, this.manifestDebounceMs);
3701
+ }
3702
+ /** Best-effort session event writer. Swallows failures. */
3703
+ async appendSessionEvent(event) {
3704
+ if (!this.sessionWriter) return;
3705
+ try {
3706
+ await this.sessionWriter.append(event);
3707
+ } catch {
3708
+ }
3709
+ }
3710
+ // -----------------------------------------------------------------------
3711
+ // Pending task management — eliminates host-side state duplication
3712
+ // -----------------------------------------------------------------------
3713
+ addPendingTask(taskId, subagentId, description) {
3714
+ this.pendingTasks.set(taskId, { subagentId, description });
3715
+ }
3716
+ removePendingTask(taskId) {
3717
+ this.pendingTasks.delete(taskId);
3718
+ }
3719
+ getFleetStatus() {
3720
+ const pending = Array.from(this.pendingTasks.entries()).map(([taskId, v]) => ({
3721
+ taskId,
3722
+ description: v.description,
3723
+ subagentId: v.subagentId
3724
+ }));
3725
+ return { pending, live: [] };
3726
+ }
3727
+ };
3728
+
3729
+ export { ALL_FLEET_AGENTS, AUDIT_LOG_AGENT, BUG_HUNTER_AGENT, BudgetExceededError, BudgetThresholdSignal, DEFAULT_DIRECTOR_PREAMBLE, DEFAULT_SUBAGENT_BASELINE, DefaultMultiAgentCoordinator, Director, FLEET_ROSTER, FleetBus, FleetCostCapError, FleetManager, FleetSpawnBudgetError, FleetUsageAggregator, InMemoryAgentBridge, InMemoryBridgeTransport, NULL_FLEET_BUS, REFACTOR_PLANNER_AGENT, SECURITY_SCANNER_AGENT, SubagentBudget, composeDirectorPrompt, composeSubagentPrompt, createDelegateTool, createMessage, makeAgentSubagentRunner, makeDirectorSessionFactory, rosterSummaryFromConfigs };
3233
3730
  //# sourceMappingURL=index.js.map
3234
3731
  //# sourceMappingURL=index.js.map