@wrongstack/core 0.1.10 → 0.3.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 (54) hide show
  1. package/dist/{agent-bridge-6KPqsFx6.d.ts → agent-bridge-C3DUGjSb.d.ts} +1 -1
  2. package/dist/{compactor-B4mQZXf2.d.ts → compactor-BUU6Zm_3.d.ts} +1 -1
  3. package/dist/{config-BU9f_5yH.d.ts → config-CKLYPkCi.d.ts} +1 -1
  4. package/dist/{context-BmM2xGUZ.d.ts → context-IovtuTf8.d.ts} +10 -0
  5. package/dist/coordination/index.d.ts +211 -13
  6. package/dist/coordination/index.js +964 -67
  7. package/dist/coordination/index.js.map +1 -1
  8. package/dist/defaults/index.d.ts +33 -18
  9. package/dist/defaults/index.js +1273 -42
  10. package/dist/defaults/index.js.map +1 -1
  11. package/dist/{events-BMNaEFZl.d.ts → events-CNB9PALO.d.ts} +99 -1
  12. package/dist/execution/index.d.ts +12 -12
  13. package/dist/extension/index.d.ts +9 -0
  14. package/dist/extension/index.js +234 -0
  15. package/dist/extension/index.js.map +1 -0
  16. package/dist/index-BDb0cAMP.d.ts +806 -0
  17. package/dist/index.d.ts +112 -29
  18. package/dist/index.js +2036 -490
  19. package/dist/index.js.map +1 -1
  20. package/dist/infrastructure/index.d.ts +6 -6
  21. package/dist/kernel/index.d.ts +12 -9
  22. package/dist/kernel/index.js +73 -7
  23. package/dist/kernel/index.js.map +1 -1
  24. package/dist/{mcp-servers-Dzgg4x1w.d.ts → mcp-servers-DR35ojJZ.d.ts} +3 -3
  25. package/dist/models/index.d.ts +2 -2
  26. package/dist/models/index.js +24 -1
  27. package/dist/models/index.js.map +1 -1
  28. package/dist/{multi-agent-fmkRHtof.d.ts → multi-agent-B9a6sflH.d.ts} +71 -3
  29. package/dist/observability/index.d.ts +2 -2
  30. package/dist/{path-resolver-DBjaoXFq.d.ts → path-resolver-Cl_q0u-R.d.ts} +2 -2
  31. package/dist/provider-runner-BXuADQqQ.d.ts +36 -0
  32. package/dist/sdd/index.d.ts +3 -3
  33. package/dist/{secret-scrubber-CicHLN4G.d.ts → secret-scrubber-CgG2tV2B.d.ts} +1 -1
  34. package/dist/{secret-scrubber-DF88luOe.d.ts → secret-scrubber-Cuy5afaQ.d.ts} +1 -1
  35. package/dist/security/index.d.ts +20 -4
  36. package/dist/security/index.js +37 -2
  37. package/dist/security/index.js.map +1 -1
  38. package/dist/{selector-BbJqiEP4.d.ts → selector-wT2fv9Fg.d.ts} +1 -1
  39. package/dist/{session-reader-Drq8RvJu.d.ts → session-reader-CcPi4BQ8.d.ts} +1 -1
  40. package/dist/{skill-DhfSizKv.d.ts → skill-C_7znCIC.d.ts} +2 -2
  41. package/dist/storage/index.d.ts +164 -6
  42. package/dist/storage/index.js +297 -2
  43. package/dist/storage/index.js.map +1 -1
  44. package/dist/{renderer-rk_1Swwc.d.ts → system-prompt-Dk1qm8ey.d.ts} +30 -2
  45. package/dist/{tool-executor-CpuJPYm9.d.ts → tool-executor-DKu4A6nB.d.ts} +5 -5
  46. package/dist/types/index.d.ts +16 -16
  47. package/dist/types/index.js +24 -1
  48. package/dist/types/index.js.map +1 -1
  49. package/dist/utils/index.d.ts +1 -1
  50. package/dist/utils/index.js +24 -1
  51. package/dist/utils/index.js.map +1 -1
  52. package/package.json +5 -1
  53. package/dist/plugin-DJk6LL8B.d.ts +0 -434
  54. package/dist/system-prompt-BC_8ypCG.d.ts +0 -23
@@ -156,7 +156,7 @@ async function atomicWrite(targetPath, content, opts = {}) {
156
156
  if (mode !== void 0) {
157
157
  await fsp.chmod(tmp, mode);
158
158
  }
159
- await fsp.rename(tmp, targetPath);
159
+ await renameWithRetry(tmp, targetPath);
160
160
  } catch (err) {
161
161
  try {
162
162
  await fsp.unlink(tmp);
@@ -168,6 +168,29 @@ async function atomicWrite(targetPath, content, opts = {}) {
168
168
  async function ensureDir(dir) {
169
169
  await fsp.mkdir(dir, { recursive: true });
170
170
  }
171
+ var TRANSIENT_RENAME_CODES = /* @__PURE__ */ new Set(["EPERM", "EBUSY", "EACCES", "ENOTEMPTY"]);
172
+ async function renameWithRetry(from, to) {
173
+ if (process.platform !== "win32") {
174
+ await fsp.rename(from, to);
175
+ return;
176
+ }
177
+ const delays = [10, 25, 60, 120, 250];
178
+ let lastErr;
179
+ for (let i = 0; i <= delays.length; i++) {
180
+ try {
181
+ await fsp.rename(from, to);
182
+ return;
183
+ } catch (err) {
184
+ lastErr = err;
185
+ const code = err?.code;
186
+ if (!code || !TRANSIENT_RENAME_CODES.has(code) || i === delays.length) {
187
+ throw err;
188
+ }
189
+ await new Promise((resolve2) => setTimeout(resolve2, delays[i]));
190
+ }
191
+ }
192
+ throw lastErr;
193
+ }
171
194
 
172
195
  // src/storage/session-store.ts
173
196
  var DefaultSessionStore = class {
@@ -406,6 +429,12 @@ var FileSessionWriter = class {
406
429
  tokenIn = 0;
407
430
  tokenOut = 0;
408
431
  filePath;
432
+ /** Public accessor for the JSONL path — required by SessionWriter so
433
+ * observability surfaces (`/fleet log`, FleetPanel) can locate the
434
+ * transcript without recomputing the path from session metadata. */
435
+ get transcriptPath() {
436
+ return this.filePath || void 0;
437
+ }
409
438
  initDone = false;
410
439
  resumed;
411
440
  appendFailCount = 0;
@@ -1796,6 +1825,272 @@ var SessionAnalyzer = class {
1796
1825
  return last - first;
1797
1826
  }
1798
1827
  };
1828
+ async function loadTodosCheckpoint(filePath) {
1829
+ let raw;
1830
+ try {
1831
+ raw = await fsp.readFile(filePath, "utf8");
1832
+ } catch {
1833
+ return null;
1834
+ }
1835
+ try {
1836
+ const parsed = JSON.parse(raw);
1837
+ if (parsed?.version !== 1 || !Array.isArray(parsed.todos)) return null;
1838
+ return parsed.todos.filter(
1839
+ (t) => !!t && typeof t.id === "string" && typeof t.content === "string" && typeof t.status === "string"
1840
+ );
1841
+ } catch {
1842
+ return null;
1843
+ }
1844
+ }
1845
+ async function saveTodosCheckpoint(filePath, sessionId, todos) {
1846
+ const payload = {
1847
+ version: 1,
1848
+ sessionId,
1849
+ updatedAt: (/* @__PURE__ */ new Date()).toISOString(),
1850
+ todos: [...todos]
1851
+ };
1852
+ try {
1853
+ await atomicWrite(filePath, JSON.stringify(payload, null, 2), { mode: 384 });
1854
+ } catch (err) {
1855
+ console.warn(
1856
+ "[todos-checkpoint] save failed:",
1857
+ err instanceof Error ? err.message : String(err)
1858
+ );
1859
+ }
1860
+ }
1861
+ function attachTodosCheckpoint(state, filePath, sessionId) {
1862
+ let timer = null;
1863
+ let pending = null;
1864
+ const flush = () => {
1865
+ timer = null;
1866
+ if (pending) {
1867
+ void saveTodosCheckpoint(filePath, sessionId, pending);
1868
+ pending = null;
1869
+ }
1870
+ };
1871
+ const unsubscribe = state.onChange((change) => {
1872
+ if (change.kind !== "todos_replaced") return;
1873
+ pending = change.todos;
1874
+ if (timer) clearTimeout(timer);
1875
+ timer = setTimeout(flush, 150);
1876
+ });
1877
+ return () => {
1878
+ unsubscribe();
1879
+ if (timer) {
1880
+ clearTimeout(timer);
1881
+ flush();
1882
+ }
1883
+ };
1884
+ }
1885
+ async function loadPlan(filePath) {
1886
+ let raw;
1887
+ try {
1888
+ raw = await fsp.readFile(filePath, "utf8");
1889
+ } catch {
1890
+ return null;
1891
+ }
1892
+ try {
1893
+ const parsed = JSON.parse(raw);
1894
+ if (parsed?.version !== 1 || !Array.isArray(parsed.items)) return null;
1895
+ return parsed;
1896
+ } catch {
1897
+ return null;
1898
+ }
1899
+ }
1900
+ async function savePlan(filePath, plan) {
1901
+ try {
1902
+ await atomicWrite(filePath, JSON.stringify(plan, null, 2), { mode: 384 });
1903
+ } catch (err) {
1904
+ console.warn(
1905
+ "[plan-store] save failed:",
1906
+ err instanceof Error ? err.message : String(err)
1907
+ );
1908
+ }
1909
+ }
1910
+ function emptyPlan(sessionId, title) {
1911
+ return {
1912
+ version: 1,
1913
+ sessionId,
1914
+ title,
1915
+ updatedAt: (/* @__PURE__ */ new Date()).toISOString(),
1916
+ items: []
1917
+ };
1918
+ }
1919
+ function addPlanItem(plan, title, details) {
1920
+ const now = (/* @__PURE__ */ new Date()).toISOString();
1921
+ const item = {
1922
+ id: `plan_${Date.now()}_${randomUUID().slice(0, 6)}`,
1923
+ title,
1924
+ details,
1925
+ status: "open",
1926
+ createdAt: now,
1927
+ updatedAt: now
1928
+ };
1929
+ return {
1930
+ plan: { ...plan, items: [...plan.items, item], updatedAt: now },
1931
+ item
1932
+ };
1933
+ }
1934
+ function removePlanItem(plan, idOrIndex) {
1935
+ const idx = matchIndex(plan, idOrIndex);
1936
+ if (idx === -1) return plan;
1937
+ return {
1938
+ ...plan,
1939
+ items: plan.items.filter((_, i) => i !== idx),
1940
+ updatedAt: (/* @__PURE__ */ new Date()).toISOString()
1941
+ };
1942
+ }
1943
+ function setPlanItemStatus(plan, idOrIndex, status) {
1944
+ const idx = matchIndex(plan, idOrIndex);
1945
+ if (idx === -1) return plan;
1946
+ const now = (/* @__PURE__ */ new Date()).toISOString();
1947
+ const items = plan.items.map(
1948
+ (it, i) => i === idx ? { ...it, status, updatedAt: now } : it
1949
+ );
1950
+ return { ...plan, items, updatedAt: now };
1951
+ }
1952
+ function clearPlan(plan) {
1953
+ return { ...plan, items: [], updatedAt: (/* @__PURE__ */ new Date()).toISOString() };
1954
+ }
1955
+ function formatPlan(plan) {
1956
+ if (plan.items.length === 0) return "Plan is empty.";
1957
+ const lines = [];
1958
+ if (plan.title) lines.push(`# ${plan.title}`);
1959
+ plan.items.forEach((it, i) => {
1960
+ const mark = it.status === "done" ? "[x]" : it.status === "in_progress" ? "[~]" : "[ ]";
1961
+ lines.push(`${i + 1}. ${mark} ${it.title}`);
1962
+ if (it.details) {
1963
+ for (const line of it.details.split("\n")) lines.push(` ${line}`);
1964
+ }
1965
+ });
1966
+ return lines.join("\n");
1967
+ }
1968
+ function matchIndex(plan, idOrIndex) {
1969
+ const asNum = Number.parseInt(idOrIndex, 10);
1970
+ if (!Number.isNaN(asNum) && asNum >= 1 && asNum <= plan.items.length) return asNum - 1;
1971
+ const byId = plan.items.findIndex((it) => it.id === idOrIndex);
1972
+ if (byId !== -1) return byId;
1973
+ const lower = idOrIndex.toLowerCase();
1974
+ return plan.items.findIndex((it) => it.title.toLowerCase().includes(lower));
1975
+ }
1976
+ function attachPlanCheckpoint(_state, _filePath, _sessionId) {
1977
+ return () => void 0;
1978
+ }
1979
+ async function loadDirectorState(filePath) {
1980
+ let raw;
1981
+ try {
1982
+ raw = await fsp.readFile(filePath, "utf8");
1983
+ } catch {
1984
+ return null;
1985
+ }
1986
+ try {
1987
+ const parsed = JSON.parse(raw);
1988
+ if (parsed?.version !== 1) return null;
1989
+ return parsed;
1990
+ } catch {
1991
+ return null;
1992
+ }
1993
+ }
1994
+ var DirectorStateCheckpoint = class {
1995
+ snapshot;
1996
+ filePath;
1997
+ timer = null;
1998
+ debounceMs;
1999
+ writing = false;
2000
+ rewriteRequested = false;
2001
+ constructor(filePath, init, debounceMs = 250) {
2002
+ this.filePath = filePath;
2003
+ this.debounceMs = debounceMs;
2004
+ this.snapshot = {
2005
+ version: 1,
2006
+ directorRunId: init.directorRunId,
2007
+ updatedAt: (/* @__PURE__ */ new Date()).toISOString(),
2008
+ spawnCount: 0,
2009
+ maxSpawns: init.maxSpawns,
2010
+ spawnDepth: init.spawnDepth,
2011
+ maxSpawnDepth: init.maxSpawnDepth,
2012
+ subagents: [],
2013
+ tasks: []
2014
+ };
2015
+ }
2016
+ current() {
2017
+ return this.snapshot;
2018
+ }
2019
+ recordSpawn(sub, spawnCount) {
2020
+ this.snapshot = {
2021
+ ...this.snapshot,
2022
+ spawnCount,
2023
+ subagents: [...this.snapshot.subagents.filter((s) => s.id !== sub.id), sub]
2024
+ };
2025
+ this.bumpUpdatedAt();
2026
+ this.schedule();
2027
+ }
2028
+ recordTaskAssigned(task) {
2029
+ const exists = this.snapshot.tasks.some((t) => t.taskId === task.taskId);
2030
+ this.snapshot = {
2031
+ ...this.snapshot,
2032
+ tasks: exists ? this.snapshot.tasks.map((t) => t.taskId === task.taskId ? { ...t, ...task } : t) : [...this.snapshot.tasks, task]
2033
+ };
2034
+ this.bumpUpdatedAt();
2035
+ this.schedule();
2036
+ }
2037
+ recordTaskStatus(taskId, patch) {
2038
+ this.snapshot = {
2039
+ ...this.snapshot,
2040
+ tasks: this.snapshot.tasks.map(
2041
+ (t) => t.taskId === taskId ? { ...t, ...patch } : t
2042
+ )
2043
+ };
2044
+ this.bumpUpdatedAt();
2045
+ this.schedule();
2046
+ }
2047
+ setUsage(usage) {
2048
+ this.snapshot = { ...this.snapshot, usage };
2049
+ this.bumpUpdatedAt();
2050
+ this.schedule();
2051
+ }
2052
+ /** Force a synchronous flush — used by Director.shutdown(). */
2053
+ async flush() {
2054
+ if (this.timer) {
2055
+ clearTimeout(this.timer);
2056
+ this.timer = null;
2057
+ }
2058
+ await this.persist();
2059
+ }
2060
+ bumpUpdatedAt() {
2061
+ this.snapshot = { ...this.snapshot, updatedAt: (/* @__PURE__ */ new Date()).toISOString() };
2062
+ }
2063
+ schedule() {
2064
+ if (this.timer) return;
2065
+ this.timer = setTimeout(() => {
2066
+ this.timer = null;
2067
+ void this.persist();
2068
+ }, this.debounceMs);
2069
+ }
2070
+ async persist() {
2071
+ if (this.writing) {
2072
+ this.rewriteRequested = true;
2073
+ return;
2074
+ }
2075
+ this.writing = true;
2076
+ try {
2077
+ await atomicWrite(this.filePath, JSON.stringify(this.snapshot, null, 2), {
2078
+ mode: 384
2079
+ });
2080
+ } catch (err) {
2081
+ console.warn(
2082
+ "[director-state] checkpoint write failed:",
2083
+ err instanceof Error ? err.message : String(err)
2084
+ );
2085
+ } finally {
2086
+ this.writing = false;
2087
+ if (this.rewriteRequested) {
2088
+ this.rewriteRequested = false;
2089
+ this.schedule();
2090
+ }
2091
+ }
2092
+ }
2093
+ };
1799
2094
 
1800
2095
  // src/security/secret-scrubber.ts
1801
2096
  var PATTERNS = [
@@ -2071,6 +2366,18 @@ var DefaultPermissionPolicy = class {
2071
2366
  return void 0;
2072
2367
  }
2073
2368
  };
2369
+ var AutoApprovePermissionPolicy = class {
2370
+ async evaluate(tool) {
2371
+ if (tool.permission === "deny") {
2372
+ return { permission: "deny", source: "default", reason: "tool default deny" };
2373
+ }
2374
+ return { permission: "auto", source: "yolo" };
2375
+ }
2376
+ async trust() {
2377
+ }
2378
+ async reload() {
2379
+ }
2380
+ };
2074
2381
 
2075
2382
  // src/types/errors.ts
2076
2383
  var WrongStackError = class extends Error {
@@ -2434,6 +2741,302 @@ function parseDescription(raw) {
2434
2741
  return { trigger, scope };
2435
2742
  }
2436
2743
 
2744
+ // src/core/streaming-response-builder.ts
2745
+ function buildResponse(state) {
2746
+ const content = [];
2747
+ for (const b of state.blockOrder) {
2748
+ if (b.kind === "text") {
2749
+ const txt = state.textBuffers[b.idx] ?? "";
2750
+ if (txt) content.push({ type: "text", text: txt });
2751
+ } else if (b.kind === "thinking") {
2752
+ const t = state.thinking[b.idx];
2753
+ if (!t) continue;
2754
+ if (!t.textBuf && !t.signature) continue;
2755
+ const block = { type: "thinking", thinking: t.textBuf };
2756
+ if (t.signature) block.signature = t.signature;
2757
+ if (t.providerMeta && Object.keys(t.providerMeta).length > 0) {
2758
+ block.providerMeta = t.providerMeta;
2759
+ }
2760
+ content.push(block);
2761
+ } else {
2762
+ const tb = state.tools.get(b.id);
2763
+ if (tb) {
2764
+ const block = {
2765
+ type: "tool_use",
2766
+ id: b.id,
2767
+ name: tb.name,
2768
+ input: tb.input ?? {}
2769
+ };
2770
+ if (tb.providerMeta && Object.keys(tb.providerMeta).length > 0) {
2771
+ block.providerMeta = tb.providerMeta;
2772
+ }
2773
+ content.push(block);
2774
+ }
2775
+ }
2776
+ }
2777
+ if (content.length === 0) content.push({ type: "text", text: "" });
2778
+ return { content, stopReason: state.stopReason, usage: state.usage, model: state.model };
2779
+ }
2780
+ function createStreamingState(model) {
2781
+ return {
2782
+ model,
2783
+ stopReason: "end_turn",
2784
+ usage: { input: 0, output: 0 },
2785
+ textBuffers: [],
2786
+ currentTextIndex: -1,
2787
+ tools: /* @__PURE__ */ new Map(),
2788
+ thinking: [],
2789
+ currentThinkingIndex: -1,
2790
+ blockOrder: []
2791
+ };
2792
+ }
2793
+ function handleMessageStart(state, model) {
2794
+ state.model = model;
2795
+ }
2796
+ function handleContentBlockStart(state, ev) {
2797
+ const kind = ev.kind ?? "text";
2798
+ if (kind === "text") {
2799
+ state.currentTextIndex = state.textBuffers.length;
2800
+ state.textBuffers.push("");
2801
+ state.blockOrder.push({ kind: "text", idx: state.currentTextIndex });
2802
+ } else if (kind === "tool_use") {
2803
+ const id = ev.id ?? crypto.randomUUID();
2804
+ state.tools.set(id, { name: ev.name ?? "unknown", partial: "" });
2805
+ state.blockOrder.push({ kind: "tool", id });
2806
+ state.currentTextIndex = -1;
2807
+ } else if (kind === "thinking") {
2808
+ state.currentThinkingIndex = state.thinking.length;
2809
+ state.thinking.push({
2810
+ textBuf: "",
2811
+ ...ev.providerMeta ? { providerMeta: ev.providerMeta } : {}
2812
+ });
2813
+ state.blockOrder.push({ kind: "thinking", idx: state.currentThinkingIndex });
2814
+ state.currentTextIndex = -1;
2815
+ }
2816
+ }
2817
+ function handleContentBlockStop(state, ev) {
2818
+ }
2819
+ function handleTextDelta(state, text) {
2820
+ if (state.currentTextIndex === -1) {
2821
+ state.currentTextIndex = state.textBuffers.length;
2822
+ state.textBuffers.push("");
2823
+ state.blockOrder.push({ kind: "text", idx: state.currentTextIndex });
2824
+ }
2825
+ state.textBuffers[state.currentTextIndex] = (state.textBuffers[state.currentTextIndex] ?? "") + text;
2826
+ }
2827
+ function handleToolUseStart(state, ev) {
2828
+ state.currentTextIndex = -1;
2829
+ state.tools.set(ev.id, { name: ev.name, partial: "" });
2830
+ state.blockOrder.push({ kind: "tool", id: ev.id });
2831
+ }
2832
+ function handleToolUseInputDelta(state, ev) {
2833
+ const t = state.tools.get(ev.id);
2834
+ if (t) t.partial += ev.partial;
2835
+ }
2836
+ function safeJsonOrRaw(s) {
2837
+ if (!s) return {};
2838
+ try {
2839
+ return JSON.parse(s);
2840
+ } catch {
2841
+ return { _raw: s };
2842
+ }
2843
+ }
2844
+ function handleToolUseStop(state, ev) {
2845
+ const t = state.tools.get(ev.id);
2846
+ if (t) {
2847
+ t.input = ev.input !== void 0 ? ev.input : safeJsonOrRaw(t.partial);
2848
+ if (ev.providerMeta) t.providerMeta = ev.providerMeta;
2849
+ }
2850
+ state.currentTextIndex = -1;
2851
+ }
2852
+ function handleThinkingStart(state, ev) {
2853
+ state.currentThinkingIndex = state.thinking.length;
2854
+ state.thinking.push({
2855
+ textBuf: "",
2856
+ ...ev.providerMeta ? { providerMeta: ev.providerMeta } : {}
2857
+ });
2858
+ state.blockOrder.push({ kind: "thinking", idx: state.currentThinkingIndex });
2859
+ state.currentTextIndex = -1;
2860
+ }
2861
+ function handleThinkingDelta(state, text) {
2862
+ if (state.currentThinkingIndex === -1) {
2863
+ handleThinkingStart(state, {});
2864
+ }
2865
+ const t = state.thinking[state.currentThinkingIndex];
2866
+ if (t) t.textBuf += text;
2867
+ }
2868
+ function handleThinkingSignature(state, signature) {
2869
+ if (state.currentThinkingIndex === -1) {
2870
+ handleThinkingStart(state, {});
2871
+ }
2872
+ const t = state.thinking[state.currentThinkingIndex];
2873
+ if (t) t.signature = signature;
2874
+ }
2875
+ function handleThinkingStop(state) {
2876
+ state.currentThinkingIndex = -1;
2877
+ }
2878
+ function handleMessageStop(state, ev) {
2879
+ state.stopReason = ev.stopReason ?? "end_turn";
2880
+ state.usage = ev.usage ?? { input: 0, output: 0 };
2881
+ }
2882
+ async function streamProviderToResponse(provider, req, signal, ctx, events) {
2883
+ const state = createStreamingState(req.model);
2884
+ const iter = provider.stream(req, { signal })[Symbol.asyncIterator]();
2885
+ try {
2886
+ for (; ; ) {
2887
+ const next = await iter.next();
2888
+ if (next.done) break;
2889
+ const ev = next.value;
2890
+ switch (ev.type) {
2891
+ case "message_start":
2892
+ handleMessageStart(state, ev.model);
2893
+ break;
2894
+ case "content_block_start":
2895
+ handleContentBlockStart(state, ev);
2896
+ break;
2897
+ case "content_block_stop":
2898
+ handleContentBlockStop(state, ev);
2899
+ break;
2900
+ case "text_delta":
2901
+ handleTextDelta(state, ev.text);
2902
+ events.emit("provider.text_delta", { ctx, text: ev.text });
2903
+ break;
2904
+ case "tool_use_start":
2905
+ handleToolUseStart(state, ev);
2906
+ events.emit("provider.tool_use_start", { ctx, id: ev.id, name: ev.name });
2907
+ break;
2908
+ case "tool_use_input_delta":
2909
+ handleToolUseInputDelta(state, ev);
2910
+ break;
2911
+ case "tool_use_stop":
2912
+ handleToolUseStop(state, ev);
2913
+ events.emit("provider.tool_use_stop", { ctx, id: ev.id });
2914
+ break;
2915
+ case "thinking_start":
2916
+ handleThinkingStart(state, ev);
2917
+ break;
2918
+ case "thinking_delta":
2919
+ handleThinkingDelta(state, ev.text);
2920
+ events.emit("provider.thinking_delta", { ctx, text: ev.text });
2921
+ break;
2922
+ case "thinking_signature":
2923
+ handleThinkingSignature(state, ev.signature);
2924
+ break;
2925
+ case "thinking_stop":
2926
+ handleThinkingStop(state);
2927
+ break;
2928
+ case "message_stop":
2929
+ handleMessageStop(state, ev);
2930
+ break;
2931
+ }
2932
+ }
2933
+ } catch (err) {
2934
+ if (signal.aborted) {
2935
+ state.stopReason = "end_turn";
2936
+ return buildResponse(state);
2937
+ }
2938
+ throw err;
2939
+ } finally {
2940
+ try {
2941
+ let drainTimer = null;
2942
+ try {
2943
+ await Promise.race([
2944
+ Promise.resolve(iter.return?.()),
2945
+ new Promise((resolve2) => {
2946
+ drainTimer = setTimeout(resolve2, 500);
2947
+ })
2948
+ ]);
2949
+ } finally {
2950
+ if (drainTimer) clearTimeout(drainTimer);
2951
+ }
2952
+ } catch {
2953
+ }
2954
+ }
2955
+ return buildResponse(state);
2956
+ }
2957
+
2958
+ // src/core/provider-runner.ts
2959
+ async function runProviderWithRetry(opts) {
2960
+ const { provider, request, signal, ctx, events, retry, logger, tracer } = opts;
2961
+ let attempt = 0;
2962
+ for (; ; ) {
2963
+ const span = tracer?.startSpan("provider.complete", {
2964
+ "provider.id": provider.id,
2965
+ "provider.model": request.model,
2966
+ "provider.streaming": provider.capabilities.streaming,
2967
+ "provider.attempt": attempt
2968
+ });
2969
+ try {
2970
+ const res = provider.capabilities.streaming ? await streamProviderToResponse(provider, request, signal, ctx, events) : await provider.complete(request, { signal });
2971
+ span?.setAttribute("provider.stopReason", res.stopReason);
2972
+ span?.setAttribute("provider.usage_in", res.usage.input);
2973
+ span?.setAttribute("provider.usage_out", res.usage.output);
2974
+ span?.end();
2975
+ return res;
2976
+ } catch (err) {
2977
+ if (err instanceof Error) span?.recordError(err);
2978
+ span?.end();
2979
+ if (signal.aborted) throw err;
2980
+ const isProviderErr = err instanceof ProviderError;
2981
+ const errAsErr = err instanceof Error ? err : new Error(String(err));
2982
+ const canRetry = retry.shouldRetry(isProviderErr ? err : errAsErr, attempt);
2983
+ const description = isProviderErr ? err.describe() : errAsErr.message;
2984
+ if (!canRetry) {
2985
+ if (isProviderErr) {
2986
+ events.emit("provider.error", {
2987
+ providerId: err.providerId,
2988
+ status: err.status,
2989
+ description,
2990
+ retryable: false
2991
+ });
2992
+ }
2993
+ throw err;
2994
+ }
2995
+ const delay = Math.round(retry.delayMs(attempt));
2996
+ const attemptNum = attempt + 1;
2997
+ logger.warn(`Provider retry ${attemptNum} in ${delay}ms \u2014 ${description}`);
2998
+ if (isProviderErr) {
2999
+ events.emit("provider.retry", {
3000
+ providerId: err.providerId,
3001
+ attempt: attemptNum,
3002
+ delayMs: delay,
3003
+ status: err.status,
3004
+ description
3005
+ });
3006
+ }
3007
+ await new Promise((resolve2, reject) => {
3008
+ let settled = false;
3009
+ const onAbort = () => {
3010
+ if (settled) return;
3011
+ settled = true;
3012
+ clearTimeout(t);
3013
+ reject(new Error("aborted"));
3014
+ };
3015
+ const t = setTimeout(() => {
3016
+ if (settled) return;
3017
+ settled = true;
3018
+ clearTimeout(t);
3019
+ signal.removeEventListener("abort", onAbort);
3020
+ resolve2();
3021
+ }, delay);
3022
+ if (signal.aborted) {
3023
+ onAbort();
3024
+ return;
3025
+ }
3026
+ signal.addEventListener("abort", onAbort, { once: true });
3027
+ });
3028
+ attempt++;
3029
+ }
3030
+ }
3031
+ }
3032
+
3033
+ // src/execution/provider-runner-impl.ts
3034
+ var DefaultProviderRunner = class {
3035
+ async run(opts) {
3036
+ return runProviderWithRetry(opts);
3037
+ }
3038
+ };
3039
+
2437
3040
  // src/utils/token-estimate.ts
2438
3041
  var RoughTokenEstimate = (text) => Math.max(1, Math.ceil(text.length / 4));
2439
3042
  var ESTIMATE_CACHE = /* @__PURE__ */ new Map();
@@ -3946,6 +4549,10 @@ var FleetBus = class {
3946
4549
  "iteration.started",
3947
4550
  "iteration.completed",
3948
4551
  "provider.text_delta",
4552
+ // Subagent extended-thinking output. Forwarded so the FleetPanel /
4553
+ // /fleet log can surface "the planner is thinking…" instead of a
4554
+ // silent gap between iteration.started and the first text_delta.
4555
+ "provider.thinking_delta",
3949
4556
  "provider.response",
3950
4557
  "provider.retry",
3951
4558
  "provider.error",
@@ -4194,14 +4801,34 @@ var DefaultMultiAgentCoordinator = class extends EventEmitter {
4194
4801
  completedResults = [];
4195
4802
  totalIterations = 0;
4196
4803
  inFlight = 0;
4804
+ /**
4805
+ * Subagents currently being stopped. Set on entry to `stop()`, cleared
4806
+ * once `recordCompletion` lands the terminal TaskResult. Used by
4807
+ * `runDispatched` and `findIdleSubagent` to refuse mid-flight dispatch
4808
+ * to a subagent the caller has already asked to terminate — closes the
4809
+ * assign+terminate race where a fresh task could land on a worker that
4810
+ * was about to be killed.
4811
+ */
4812
+ terminating = /* @__PURE__ */ new Set();
4197
4813
  constructor(config, options = {}) {
4198
4814
  super();
4199
4815
  this.coordinatorId = config.coordinatorId;
4200
4816
  this.config = config;
4201
4817
  this.runner = options.runner;
4202
4818
  }
4819
+ /**
4820
+ * Replace the runner after construction. Used when the runner depends
4821
+ * on infrastructure (e.g. FleetBus) that isn't available until after
4822
+ * the coordinator's owning Director is built.
4823
+ */
4824
+ setRunner(runner) {
4825
+ this.runner = runner;
4826
+ }
4203
4827
  async spawn(subagent) {
4204
4828
  const id = subagent.id || randomUUID();
4829
+ if (this.subagents.has(id)) {
4830
+ throw new Error(`Subagent id "${id}" already exists \u2014 refusing to overwrite`);
4831
+ }
4205
4832
  const context = {
4206
4833
  subagentId: id,
4207
4834
  tasks: [],
@@ -4245,6 +4872,7 @@ var DefaultMultiAgentCoordinator = class extends EventEmitter {
4245
4872
  async stop(subagentId) {
4246
4873
  const subagent = this.subagents.get(subagentId);
4247
4874
  if (!subagent) return;
4875
+ this.terminating.add(subagentId);
4248
4876
  subagent.abortController.abort();
4249
4877
  subagent.status = "stopped";
4250
4878
  subagent.currentTask = void 0;
@@ -4252,6 +4880,7 @@ var DefaultMultiAgentCoordinator = class extends EventEmitter {
4252
4880
  this.emit("subagent.stopped", { subagentId, reason: "stopped by coordinator" });
4253
4881
  }
4254
4882
  async stopAll() {
4883
+ this.drainPendingAsAborted("Coordinator stopAll() drained the pending queue");
4255
4884
  await Promise.allSettled([...this.subagents.keys()].map((id) => this.stop(id)));
4256
4885
  }
4257
4886
  getStatus() {
@@ -4285,7 +4914,14 @@ var DefaultMultiAgentCoordinator = class extends EventEmitter {
4285
4914
  tryDispatchNext() {
4286
4915
  while (this.canDispatch()) {
4287
4916
  const subagentId = this.findIdleSubagent();
4288
- if (!subagentId) return;
4917
+ if (!subagentId) {
4918
+ if (this.pendingTasks.length > 0 && !this.hasLiveSubagent()) {
4919
+ this.drainPendingAsAborted(
4920
+ "No live subagent available \u2014 all stopped or mid-termination"
4921
+ );
4922
+ }
4923
+ return;
4924
+ }
4289
4925
  const task = this.pendingTasks.shift();
4290
4926
  if (!task) return;
4291
4927
  this.runDispatched(subagentId, task).catch((err) => {
@@ -4293,7 +4929,7 @@ var DefaultMultiAgentCoordinator = class extends EventEmitter {
4293
4929
  subagentId,
4294
4930
  taskId: task.id,
4295
4931
  status: "failed",
4296
- error: err instanceof Error ? err.message : String(err),
4932
+ error: classifySubagentError(err),
4297
4933
  iterations: 0,
4298
4934
  toolCalls: 0,
4299
4935
  durationMs: 0
@@ -4307,13 +4943,76 @@ var DefaultMultiAgentCoordinator = class extends EventEmitter {
4307
4943
  }
4308
4944
  findIdleSubagent() {
4309
4945
  for (const [id, s] of this.subagents) {
4310
- if (s.status === "idle") return id;
4946
+ if (s.status === "idle" && !this.terminating.has(id)) return id;
4311
4947
  }
4312
4948
  return null;
4313
4949
  }
4950
+ /**
4951
+ * Returns true iff at least one spawned subagent could still
4952
+ * process a task. A "live" subagent is one that is not stopped
4953
+ * AND not mid-termination — `running` workers count because they
4954
+ * will eventually finish and become idle.
4955
+ *
4956
+ * When no subagent has ever been spawned, returns `true` so a
4957
+ * pre-spawn `assign()` simply queues (legacy behaviour). The
4958
+ * dead-end detection only fires after `stop()` has retired every
4959
+ * spawned worker.
4960
+ *
4961
+ * Used by `tryDispatchNext` to detect a dead-end pending queue.
4962
+ */
4963
+ hasLiveSubagent() {
4964
+ if (this.subagents.size === 0) return true;
4965
+ for (const [id, s] of this.subagents) {
4966
+ if (s.status !== "stopped" && !this.terminating.has(id)) return true;
4967
+ }
4968
+ return false;
4969
+ }
4970
+ /**
4971
+ * Drain every pending task with a synthetic `aborted_by_parent`
4972
+ * completion event. Same shape as the `stopAll()` drain — we go
4973
+ * around `recordCompletion` because pending tasks were never
4974
+ * counted in `inFlight` and routing them through would trip the
4975
+ * underflow guard on every task after the first.
4976
+ */
4977
+ drainPendingAsAborted(message) {
4978
+ const dropped = this.pendingTasks.splice(0, this.pendingTasks.length);
4979
+ for (const t of dropped) {
4980
+ const synthetic = {
4981
+ subagentId: t.subagentId ?? "unassigned",
4982
+ taskId: t.id,
4983
+ status: "stopped",
4984
+ error: {
4985
+ kind: "aborted_by_parent",
4986
+ message,
4987
+ retryable: false
4988
+ },
4989
+ iterations: 0,
4990
+ toolCalls: 0,
4991
+ durationMs: 0
4992
+ };
4993
+ this.completedResults.push(synthetic);
4994
+ this.emit("task.completed", { task: t, result: synthetic });
4995
+ }
4996
+ }
4314
4997
  async runDispatched(subagentId, task) {
4315
4998
  const subagent = this.subagents.get(subagentId);
4316
4999
  if (!subagent) return;
5000
+ if (this.terminating.has(subagentId) || subagent.status === "stopped") {
5001
+ this.recordCompletion({
5002
+ subagentId,
5003
+ taskId: task.id,
5004
+ status: "stopped",
5005
+ error: {
5006
+ kind: "aborted_by_parent",
5007
+ message: "Subagent was terminated before task could start",
5008
+ retryable: false
5009
+ },
5010
+ iterations: 0,
5011
+ toolCalls: 0,
5012
+ durationMs: 0
5013
+ });
5014
+ return;
5015
+ }
4317
5016
  subagent.status = "running";
4318
5017
  subagent.currentTask = task.id;
4319
5018
  task.subagentId = subagentId;
@@ -4359,7 +5058,9 @@ var DefaultMultiAgentCoordinator = class extends EventEmitter {
4359
5058
  subagentId,
4360
5059
  taskId: task.id,
4361
5060
  status,
4362
- error: err instanceof Error ? err.message : String(err),
5061
+ error: classifySubagentError(err, {
5062
+ parentAborted: subagent.abortController.signal.aborted
5063
+ }),
4363
5064
  iterations: usage.iterations,
4364
5065
  toolCalls: usage.toolCalls,
4365
5066
  durationMs: Date.now() - startTime
@@ -4398,19 +5099,14 @@ var DefaultMultiAgentCoordinator = class extends EventEmitter {
4398
5099
  }
4399
5100
  const subagent = this.subagents.get(result.subagentId);
4400
5101
  if (subagent && subagent.status !== "stopped") {
4401
- const failed = result.status === "failed" || result.status === "timeout";
4402
- subagent.status = failed ? "error" : "idle";
5102
+ result.status === "failed" || result.status === "timeout";
5103
+ subagent.status = "idle";
4403
5104
  subagent.currentTask = void 0;
4404
5105
  if (subagent.abortController.signal.aborted) {
4405
5106
  subagent.abortController = new AbortController();
4406
5107
  }
4407
- if (subagent.status === "error") {
4408
- queueMicrotask(() => {
4409
- if (subagent.status === "error") subagent.status = "idle";
4410
- this.tryDispatchNext();
4411
- });
4412
- }
4413
5108
  }
5109
+ this.terminating.delete(result.subagentId);
4414
5110
  this.emit("task.completed", {
4415
5111
  task: subagent?.context.tasks.find((t) => t.id === result.taskId) ?? { id: result.taskId },
4416
5112
  result
@@ -4433,6 +5129,99 @@ var DefaultMultiAgentCoordinator = class extends EventEmitter {
4433
5129
  return false;
4434
5130
  }
4435
5131
  };
5132
+ function classifySubagentError(err, hints = {}) {
5133
+ const cause = err instanceof Error ? { name: err.name, message: err.message, stack: err.stack } : void 0;
5134
+ const baseMessage = err instanceof Error ? err.message : String(err);
5135
+ if (err instanceof ProviderError) {
5136
+ return providerErrorToSubagentError(err, baseMessage, cause);
5137
+ }
5138
+ if (err instanceof BudgetExceededError) {
5139
+ const map = {
5140
+ iterations: "budget_iterations",
5141
+ tool_calls: "budget_tool_calls",
5142
+ tokens: "budget_tokens",
5143
+ cost: "budget_cost",
5144
+ timeout: "budget_timeout"
5145
+ };
5146
+ return {
5147
+ kind: map[err.kind],
5148
+ message: baseMessage,
5149
+ // Budgets are user-configured ceilings, not transient failures —
5150
+ // retrying with the same budget will hit the same ceiling. The
5151
+ // orchestrator must raise the budget or narrow the task first.
5152
+ retryable: false,
5153
+ cause
5154
+ };
5155
+ }
5156
+ if (hints.parentAborted) {
5157
+ return {
5158
+ kind: "aborted_by_parent",
5159
+ message: baseMessage,
5160
+ retryable: false,
5161
+ cause
5162
+ };
5163
+ }
5164
+ const lower = baseMessage.toLowerCase();
5165
+ if (/agent aborted$/i.test(baseMessage)) {
5166
+ return {
5167
+ kind: "aborted_by_parent",
5168
+ message: baseMessage,
5169
+ retryable: false,
5170
+ cause
5171
+ };
5172
+ }
5173
+ if (/agent exhausted iteration limit$/i.test(baseMessage)) {
5174
+ return { kind: "budget_iterations", message: baseMessage, retryable: false, cause };
5175
+ }
5176
+ if (/empty response$/i.test(baseMessage)) {
5177
+ return { kind: "empty_response", message: baseMessage, retryable: false, cause };
5178
+ }
5179
+ if (/^tool failed: /i.test(baseMessage)) {
5180
+ return { kind: "tool_failed", message: baseMessage, retryable: false, cause };
5181
+ }
5182
+ if (lower.includes("bridge transport") || /bridge.*(closed|disconnect)/i.test(baseMessage)) {
5183
+ return { kind: "bridge_failed", message: baseMessage, retryable: false, cause };
5184
+ }
5185
+ if (/context length|max.*tokens?.*exceeded|prompt is too long/i.test(baseMessage)) {
5186
+ return { kind: "context_overflow", message: baseMessage, retryable: false, cause };
5187
+ }
5188
+ return {
5189
+ kind: "unknown",
5190
+ message: baseMessage,
5191
+ retryable: false,
5192
+ cause
5193
+ };
5194
+ }
5195
+ function providerErrorToSubagentError(err, message, cause) {
5196
+ const status = err.status;
5197
+ if (status === 429 || err.body?.type === "rate_limit_error") {
5198
+ return {
5199
+ kind: "provider_rate_limit",
5200
+ message,
5201
+ retryable: true,
5202
+ // Conservative default: 5s. Provider-specific code can override
5203
+ // by emitting an error whose body carries an explicit hint.
5204
+ backoffMs: 5e3,
5205
+ cause
5206
+ };
5207
+ }
5208
+ if (status === 401 || status === 403 || err.body?.type === "authentication_error") {
5209
+ return { kind: "provider_auth", message, retryable: false, cause };
5210
+ }
5211
+ if (status === 408 || status === 0) {
5212
+ return { kind: "provider_timeout", message, retryable: true, cause };
5213
+ }
5214
+ if (status >= 500 && status < 600) {
5215
+ return {
5216
+ kind: "provider_5xx",
5217
+ message,
5218
+ retryable: true,
5219
+ backoffMs: 3e3,
5220
+ cause
5221
+ };
5222
+ }
5223
+ return { kind: "unknown", message, retryable: err.retryable, cause };
5224
+ }
4436
5225
 
4437
5226
  // src/coordination/director.ts
4438
5227
  var DirectorBudgetError = class extends Error {
@@ -4496,6 +5285,27 @@ var Director = class {
4496
5285
  spawnDepth;
4497
5286
  /** Live spawn counter for `maxSpawns` enforcement. */
4498
5287
  spawnCount = 0;
5288
+ /** Optional checkpoint mirror — writes the live task graph + roster to disk. */
5289
+ stateCheckpoint;
5290
+ /** Optional session writer for emitting task_* / agent_* lifecycle events. */
5291
+ sessionWriter;
5292
+ /** Debounce timer for periodic manifest writes. */
5293
+ manifestTimer = null;
5294
+ manifestDebounceMs;
5295
+ /** Resolves task descriptions back from `assign()` so completion events
5296
+ * can also carry a human-readable title. */
5297
+ taskDescriptions = /* @__PURE__ */ new Map();
5298
+ /** Snapshot of which subagent owns each task — drives state-checkpoint
5299
+ * status updates without re-walking the manifest. */
5300
+ taskOwners = /* @__PURE__ */ new Map();
5301
+ /**
5302
+ * Handle to the coordinator-side `task.completed` listener so we can
5303
+ * unsubscribe in `shutdown()`. Without this, repeated Director
5304
+ * construction (e.g. tests, hot reloads) accumulates listeners on a
5305
+ * cached coordinator and slowly drifts the EventEmitter past its
5306
+ * default cap.
5307
+ */
5308
+ taskCompletedListener = null;
4499
5309
  constructor(opts) {
4500
5310
  this.id = opts.config.coordinatorId || randomUUID();
4501
5311
  this.manifestPath = opts.manifestPath;
@@ -4506,6 +5316,14 @@ var Director = class {
4506
5316
  this.maxSpawns = opts.maxSpawns ?? Number.POSITIVE_INFINITY;
4507
5317
  this.maxSpawnDepth = opts.maxSpawnDepth ?? 2;
4508
5318
  this.spawnDepth = opts.spawnDepth ?? 0;
5319
+ this.sessionWriter = opts.sessionWriter ?? null;
5320
+ this.manifestDebounceMs = opts.manifestDebounceMs ?? 2e3;
5321
+ this.stateCheckpoint = opts.stateCheckpointPath ? new DirectorStateCheckpoint(opts.stateCheckpointPath, {
5322
+ directorRunId: this.id,
5323
+ maxSpawns: opts.maxSpawns,
5324
+ spawnDepth: this.spawnDepth,
5325
+ maxSpawnDepth: this.maxSpawnDepth
5326
+ }) : null;
4509
5327
  if (this.sharedScratchpadPath) {
4510
5328
  void fsp.mkdir(this.sharedScratchpadPath, { recursive: true }).catch(() => void 0);
4511
5329
  }
@@ -4524,7 +5342,7 @@ var Director = class {
4524
5342
  { ...opts.config, coordinatorId: this.id },
4525
5343
  { runner: opts.runner }
4526
5344
  );
4527
- this.coordinator.on("task.completed", (payload) => {
5345
+ this.taskCompletedListener = (payload) => {
4528
5346
  const r = payload.result;
4529
5347
  this.completed.set(r.taskId, r);
4530
5348
  const waiter = this.taskWaiters.get(r.taskId);
@@ -4532,7 +5350,54 @@ var Director = class {
4532
5350
  waiter.resolve(r);
4533
5351
  this.taskWaiters.delete(r.taskId);
4534
5352
  }
4535
- });
5353
+ const title = this.taskDescriptions.get(r.taskId) ?? payload.task.description ?? r.taskId;
5354
+ const failed = r.status !== "success";
5355
+ const errorString = r.error ? `${r.error.kind}: ${r.error.message}` : void 0;
5356
+ this.stateCheckpoint?.recordTaskStatus(r.taskId, {
5357
+ status: failed ? r.status : "completed",
5358
+ completedAt: (/* @__PURE__ */ new Date()).toISOString(),
5359
+ iterations: r.iterations,
5360
+ toolCalls: r.toolCalls,
5361
+ durationMs: r.durationMs,
5362
+ error: errorString
5363
+ });
5364
+ this.stateCheckpoint?.setUsage(this.usage.snapshot());
5365
+ void this.appendSessionEvent(
5366
+ failed ? {
5367
+ type: "task_failed",
5368
+ ts: (/* @__PURE__ */ new Date()).toISOString(),
5369
+ taskId: r.taskId,
5370
+ title,
5371
+ error: errorString ?? r.status
5372
+ } : {
5373
+ type: "task_completed",
5374
+ ts: (/* @__PURE__ */ new Date()).toISOString(),
5375
+ taskId: r.taskId,
5376
+ title
5377
+ }
5378
+ );
5379
+ this.scheduleManifest();
5380
+ };
5381
+ this.coordinator.on("task.completed", this.taskCompletedListener);
5382
+ }
5383
+ /** Best-effort session-writer append. Swallows failures — the director
5384
+ * must not break a fleet run because the session JSONL handle closed. */
5385
+ async appendSessionEvent(event) {
5386
+ if (!this.sessionWriter) return;
5387
+ try {
5388
+ await this.sessionWriter.append(event);
5389
+ } catch {
5390
+ }
5391
+ }
5392
+ /** Debounced manifest writer. A burst of spawn/assign/complete events
5393
+ * collapses into one write. Set `manifestDebounceMs` to 0 to disable. */
5394
+ scheduleManifest() {
5395
+ if (!this.manifestPath || this.manifestDebounceMs <= 0) return;
5396
+ if (this.manifestTimer) return;
5397
+ this.manifestTimer = setTimeout(() => {
5398
+ this.manifestTimer = null;
5399
+ void this.writeManifest().catch(() => void 0);
5400
+ }, this.manifestDebounceMs);
4536
5401
  }
4537
5402
  /**
4538
5403
  * Spawn a subagent. Identical to the coordinator's `spawn()` but
@@ -4571,6 +5436,25 @@ var Director = class {
4571
5436
  model: config.model,
4572
5437
  taskIds: []
4573
5438
  });
5439
+ const spawnedAt = (/* @__PURE__ */ new Date()).toISOString();
5440
+ this.stateCheckpoint?.recordSpawn(
5441
+ {
5442
+ id: result.subagentId,
5443
+ name: config.name,
5444
+ role: config.role,
5445
+ provider: config.provider,
5446
+ model: config.model,
5447
+ spawnedAt
5448
+ },
5449
+ this.spawnCount
5450
+ );
5451
+ void this.appendSessionEvent({
5452
+ type: "agent_spawned",
5453
+ ts: spawnedAt,
5454
+ agentId: result.subagentId,
5455
+ role: config.role ?? config.name
5456
+ });
5457
+ this.scheduleManifest();
4574
5458
  return result.subagentId;
4575
5459
  }
4576
5460
  /**
@@ -4691,13 +5575,42 @@ var Director = class {
4691
5575
  * — calling shutdown twice is a no-op on the second invocation.
4692
5576
  */
4693
5577
  async shutdown() {
5578
+ if (this.manifestTimer) {
5579
+ clearTimeout(this.manifestTimer);
5580
+ this.manifestTimer = null;
5581
+ }
5582
+ if (this.taskCompletedListener) {
5583
+ this.coordinator.off("task.completed", this.taskCompletedListener);
5584
+ this.taskCompletedListener = null;
5585
+ }
4694
5586
  await this.coordinator.stopAll();
4695
5587
  for (const b of this.subagentBridges.values()) {
4696
- await b.stop().catch(() => void 0);
5588
+ await b.stop().catch((err) => this.logShutdownError("subagent_bridge_stop", err));
4697
5589
  }
4698
5590
  this.subagentBridges.clear();
4699
- await this.bridge.stop().catch(() => void 0);
4700
- if (this.manifestPath) await this.writeManifest().catch(() => void 0);
5591
+ await this.bridge.stop().catch((err) => this.logShutdownError("director_bridge_stop", err));
5592
+ if (this.manifestPath)
5593
+ await this.writeManifest().catch((err) => this.logShutdownError("manifest_write", err));
5594
+ if (this.stateCheckpoint) {
5595
+ this.stateCheckpoint.setUsage(this.usage.snapshot());
5596
+ await this.stateCheckpoint.flush().catch((err) => this.logShutdownError("state_checkpoint_flush", err));
5597
+ }
5598
+ }
5599
+ /**
5600
+ * Funnel for shutdown-phase errors. We can't throw — `shutdown()` is
5601
+ * called from process-exit paths where an uncaught throw would lose
5602
+ * the manifest write that comes after. But we MUST NOT silently
5603
+ * swallow either — a persistent bridge-close failure would otherwise
5604
+ * mask a real bug. `process.emitWarning` is the right tier:
5605
+ * surfaces on stderr by default, lets the host plug a warning
5606
+ * listener for structured collection, and never affects exit code.
5607
+ */
5608
+ logShutdownError(phase, err) {
5609
+ const detail = err instanceof Error ? err.message : String(err);
5610
+ process.emitWarning(
5611
+ `Director shutdown phase "${phase}" failed: ${detail}`,
5612
+ "DirectorShutdownWarning"
5613
+ );
4701
5614
  }
4702
5615
  /**
4703
5616
  * Hand a task to the coordinator. Returns the assigned task id so
@@ -4711,6 +5624,23 @@ var Director = class {
4711
5624
  if (entry) entry.taskIds.push(taskWithId.id);
4712
5625
  }
4713
5626
  await this.coordinator.assign(taskWithId);
5627
+ this.taskDescriptions.set(taskWithId.id, taskWithId.description);
5628
+ if (taskWithId.subagentId) this.taskOwners.set(taskWithId.id, taskWithId.subagentId);
5629
+ const assignedAt = (/* @__PURE__ */ new Date()).toISOString();
5630
+ this.stateCheckpoint?.recordTaskAssigned({
5631
+ taskId: taskWithId.id,
5632
+ subagentId: taskWithId.subagentId,
5633
+ description: taskWithId.description,
5634
+ status: "running",
5635
+ assignedAt
5636
+ });
5637
+ void this.appendSessionEvent({
5638
+ type: "task_created",
5639
+ ts: assignedAt,
5640
+ taskId: taskWithId.id,
5641
+ title: taskWithId.description
5642
+ });
5643
+ this.scheduleManifest();
4714
5644
  return taskWithId.id;
4715
5645
  }
4716
5646
  /**
@@ -4770,6 +5700,23 @@ var Director = class {
4770
5700
  snapshot() {
4771
5701
  return this.usage.snapshot();
4772
5702
  }
5703
+ /**
5704
+ * Look up provider/model metadata for a spawned subagent. Returns
5705
+ * undefined when the subagent id is unknown (not yet spawned, or
5706
+ * already torn down). Callers — notably the TUI fleet panel — use
5707
+ * this to render human-readable provider/model tags next to each
5708
+ * subagent row without reaching into private state.
5709
+ */
5710
+ getSubagentMeta(id) {
5711
+ const usage = this.subagentMeta.get(id);
5712
+ const manifest = this.manifestEntries.get(id);
5713
+ if (!usage && !manifest) return void 0;
5714
+ return {
5715
+ provider: usage?.provider ?? manifest?.provider,
5716
+ model: usage?.model ?? manifest?.model,
5717
+ name: manifest?.name
5718
+ };
5719
+ }
4773
5720
  /**
4774
5721
  * Compose the leader/director-agent system prompt: fleet preamble +
4775
5722
  * (optional) roster summary + user base prompt. Pass the result to your
@@ -5069,12 +6016,260 @@ function makeFleetUsageTool(director) {
5069
6016
  }
5070
6017
  };
5071
6018
  }
6019
+ function createDelegateTool(opts) {
6020
+ const defaultTimeoutMs = opts.defaultTimeoutMs ?? 4 * 60 * 60 * 1e3;
6021
+ const rosterIds = opts.roster ? Object.keys(opts.roster) : [];
6022
+ const inputSchema = {
6023
+ type: "object",
6024
+ properties: {
6025
+ task: {
6026
+ type: "string",
6027
+ description: "What the subagent should do \u2014 natural language, complete sentence(s). The subagent has its own tool slice, its own LLM call, and returns when its task is done."
6028
+ },
6029
+ role: {
6030
+ type: "string",
6031
+ description: rosterIds.length > 0 ? `Roster role (preferred). One of: ${rosterIds.join(", ")}. Picks a pre-tuned config (prompt, budgets, tools) for that role.` : "No roster is configured \u2014 pass `name` instead.",
6032
+ enum: rosterIds.length > 0 ? rosterIds : void 0
6033
+ },
6034
+ name: {
6035
+ type: "string",
6036
+ description: "Display name for the subagent when not using a roster role. Required when `role` is omitted."
6037
+ },
6038
+ provider: {
6039
+ type: "string",
6040
+ description: 'Provider id (e.g. "anthropic", "openai"). Defaults to the host provider when omitted.'
6041
+ },
6042
+ model: {
6043
+ type: "string",
6044
+ description: "Model id within the provider. Defaults to the host model when omitted."
6045
+ },
6046
+ systemPromptOverride: {
6047
+ type: "string",
6048
+ description: "Optional extra prompt text appended to the role baseline."
6049
+ },
6050
+ timeoutMs: {
6051
+ type: "number",
6052
+ description: `Wall-clock budget for this delegate in milliseconds. No hard cap \u2014 set as high as the task realistically needs (a monorepo audit can take hours, a single-file lint takes seconds). Default ${Math.round(defaultTimeoutMs / 1e3 / 60)} minutes.`
6053
+ },
6054
+ maxIterations: {
6055
+ type: "number",
6056
+ description: "Maximum LLM iterations the subagent may take. Unset = use the role/coordinator default. Raise this for tasks with many tool-think-tool cycles (deep code analysis, multi-file refactors)."
6057
+ },
6058
+ maxToolCalls: {
6059
+ type: "number",
6060
+ description: "Maximum number of tool invocations the subagent may make. Unset = use the role/coordinator default. Raise this for tasks that touch many files (large grep + read + report)."
6061
+ }
6062
+ },
6063
+ required: ["task"]
6064
+ };
6065
+ return {
6066
+ name: "delegate",
6067
+ description: "Hand a discrete piece of work to a dedicated subagent and wait for its result. The subagent has its own context, its own LLM call, and its own budget \u2014 use this when a task is self-contained, would otherwise blow up your context, or benefits from a specialized role (bug-hunter, security-scanner, refactor-planner, audit-log). YOU decide how big the budget is: pass `timeoutMs`, `maxIterations`, and `maxToolCalls` sized to the actual work. There is no hidden cap forcing a 3-minute / 80-iteration limit \u2014 if a monorepo audit needs 2 hours and 500 tool calls, ask for that. Call multiple delegates in parallel through the provider's parallel-tool-call surface to fan work out across roles.",
6068
+ usageHint: "Set `task` to a complete instruction. Either pick `role` from the roster or pass `name` + `provider` + `model`. For non-trivial work, also pass `timeoutMs` (the wall-clock budget you actually need), `maxIterations`, and `maxToolCalls` \u2014 defaults are intentionally generous (4 hours) but the right values depend on scope. Returns the subagent's `TaskResult` \u2014 including the textual `result`, iteration count, tool count, and duration. Auto-promotes the host into director mode on first call.",
6069
+ permission: "auto",
6070
+ mutating: false,
6071
+ inputSchema,
6072
+ async execute(input) {
6073
+ const i = input ?? {};
6074
+ if (typeof i.task !== "string" || !i.task.trim()) {
6075
+ return { ok: false, error: "`task` is required." };
6076
+ }
6077
+ let director = await opts.host.ensureDirector();
6078
+ if (!director) {
6079
+ director = await opts.host.promoteToDirector();
6080
+ }
6081
+ if (!director) {
6082
+ const reason = opts.host.getPromotionBlockReason?.();
6083
+ return {
6084
+ ok: false,
6085
+ error: reason ?? "Director could not be activated \u2014 multi-agent host already running in legacy non-director mode. Restart with `--director` for fleet support."
6086
+ };
6087
+ }
6088
+ const timeoutMs = i.timeoutMs ?? defaultTimeoutMs;
6089
+ let cfg;
6090
+ if (i.role) {
6091
+ const base = opts.roster?.[i.role];
6092
+ if (!base) {
6093
+ return {
6094
+ ok: false,
6095
+ error: `Unknown role "${i.role}". Available: ${rosterIds.join(", ") || "(no roster configured)"}.`
6096
+ };
6097
+ }
6098
+ cfg = { ...base };
6099
+ if (i.systemPromptOverride) cfg.systemPromptOverride = i.systemPromptOverride;
6100
+ if (i.provider) cfg.provider = i.provider;
6101
+ if (i.model) cfg.model = i.model;
6102
+ } else {
6103
+ if (!i.name) {
6104
+ return {
6105
+ ok: false,
6106
+ error: "Either `role` (from the roster) or `name` is required."
6107
+ };
6108
+ }
6109
+ cfg = {
6110
+ name: i.name,
6111
+ provider: i.provider,
6112
+ model: i.model,
6113
+ systemPromptOverride: i.systemPromptOverride
6114
+ };
6115
+ }
6116
+ if (typeof i.maxIterations === "number" && i.maxIterations > 0) {
6117
+ cfg.maxIterations = i.maxIterations;
6118
+ }
6119
+ if (typeof i.maxToolCalls === "number" && i.maxToolCalls > 0) {
6120
+ cfg.maxToolCalls = i.maxToolCalls;
6121
+ }
6122
+ const SUBAGENT_TIMEOUT_BUFFER_MS = 3e4;
6123
+ const desiredSubTimeout = Math.max(3e4, timeoutMs - SUBAGENT_TIMEOUT_BUFFER_MS);
6124
+ if (!cfg.timeoutMs || cfg.timeoutMs > desiredSubTimeout) {
6125
+ cfg.timeoutMs = desiredSubTimeout;
6126
+ }
6127
+ try {
6128
+ const subagentId = await director.spawn(cfg);
6129
+ const taskId = await director.assign({
6130
+ id: "",
6131
+ description: i.task,
6132
+ subagentId
6133
+ });
6134
+ const result = await Promise.race([
6135
+ director.awaitTasks([taskId]).then((r) => r[0]),
6136
+ new Promise(
6137
+ (resolve2) => setTimeout(() => resolve2({ __timeout: true }), timeoutMs)
6138
+ )
6139
+ ]);
6140
+ if ("__timeout" in result) {
6141
+ const partial2 = await readSubagentPartial(opts, subagentId);
6142
+ return {
6143
+ ok: false,
6144
+ stopReason: "host_timeout",
6145
+ error: `Subagent did not finish within ${timeoutMs}ms.`,
6146
+ hint: "Reduce scope of the next delegate, raise timeoutMs, or use spawn_subagent + await_tasks for long-running work.",
6147
+ subagentId,
6148
+ taskId,
6149
+ partial: partial2
6150
+ };
6151
+ }
6152
+ const baseStopReason = result.status === "success" ? "end_turn" : result.status === "timeout" ? "subagent_timeout" : result.status === "stopped" ? "aborted" : "budget_exhausted";
6153
+ const partial = result.status === "success" ? void 0 : await readSubagentPartial(opts, subagentId);
6154
+ const errorKind = result.error?.kind;
6155
+ const retryable = result.error?.retryable;
6156
+ const backoffMs = result.error?.backoffMs;
6157
+ return {
6158
+ ok: result.status === "success",
6159
+ status: result.status,
6160
+ stopReason: baseStopReason,
6161
+ errorKind,
6162
+ retryable,
6163
+ backoffMs,
6164
+ subagentId: result.subagentId,
6165
+ taskId: result.taskId,
6166
+ result: result.result,
6167
+ error: result.error,
6168
+ iterations: result.iterations,
6169
+ toolCalls: result.toolCalls,
6170
+ durationMs: result.durationMs,
6171
+ ...partial ? { partial } : {},
6172
+ ...hintForKind(errorKind, retryable, backoffMs) ? { hint: hintForKind(errorKind, retryable, backoffMs) } : {}
6173
+ };
6174
+ } catch (err) {
6175
+ return {
6176
+ ok: false,
6177
+ stopReason: "error",
6178
+ error: err instanceof Error ? err.message : String(err)
6179
+ };
6180
+ }
6181
+ }
6182
+ };
6183
+ }
6184
+ function hintForKind(kind, retryable, backoffMs) {
6185
+ if (!kind) return void 0;
6186
+ switch (kind) {
6187
+ case "provider_rate_limit":
6188
+ return `Provider rate-limited. Retry safe after ${backoffMs ?? 5e3}ms backoff. Consider a smaller model or fewer parallel delegates.`;
6189
+ case "provider_5xx":
6190
+ return `Provider server error. Retry safe after ${backoffMs ?? 3e3}ms backoff \u2014 usually transient.`;
6191
+ case "provider_timeout":
6192
+ return "Provider network timeout. Retry safe; reduce input size if it persists.";
6193
+ case "provider_auth":
6194
+ return "Provider rejected credentials. Cannot retry \u2014 fix the API key / config and re-invoke.";
6195
+ case "context_overflow":
6196
+ return "Subagent context exceeded the model limit. Narrow the task, use a larger-context model, or split into multiple delegates.";
6197
+ case "budget_iterations":
6198
+ case "budget_tool_calls":
6199
+ case "budget_tokens":
6200
+ case "budget_cost":
6201
+ return "Subagent exhausted its budget. Raise the matching `max*` field on the next delegate or narrow task scope.";
6202
+ case "budget_timeout":
6203
+ return "Subagent hit its wall-clock budget. Raise `timeoutMs` on the next delegate or split the task.";
6204
+ case "aborted_by_parent":
6205
+ return "Subagent was aborted (user Ctrl+C, parent unwound, or sibling failure cascade). Not retryable until the abort condition is resolved.";
6206
+ case "empty_response":
6207
+ 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.";
6208
+ case "tool_failed":
6209
+ return "A tool inside the subagent returned ok:false. Inspect `partial.lastAssistantText` for the agent reasoning, then retry with corrected inputs.";
6210
+ case "bridge_failed":
6211
+ return "Parent-child bridge transport failed. This is rare \u2014 restart the session and retry.";
6212
+ default:
6213
+ return retryable ? "Failure classified as retryable. Try again with the same input." : void 0;
6214
+ }
6215
+ }
6216
+ async function readSubagentPartial(opts, subagentId) {
6217
+ if (!opts.sessionsRoot) return void 0;
6218
+ const candidates = [];
6219
+ if (opts.directorRunId) {
6220
+ candidates.push(path3.join(opts.sessionsRoot, opts.directorRunId, `${subagentId}.jsonl`));
6221
+ } else {
6222
+ try {
6223
+ const runDirs = await fsp.readdir(opts.sessionsRoot);
6224
+ for (const r of runDirs) {
6225
+ candidates.push(path3.join(opts.sessionsRoot, r, `${subagentId}.jsonl`));
6226
+ }
6227
+ } catch {
6228
+ return void 0;
6229
+ }
6230
+ }
6231
+ for (const file of candidates) {
6232
+ let raw;
6233
+ try {
6234
+ raw = await fsp.readFile(file, "utf8");
6235
+ } catch {
6236
+ continue;
6237
+ }
6238
+ const lines = raw.split("\n").filter((l) => l.trim());
6239
+ let lastAssistantText;
6240
+ let lastStopReason;
6241
+ let toolUses = 0;
6242
+ for (const line of lines) {
6243
+ try {
6244
+ const ev = JSON.parse(line);
6245
+ if (ev.type === "tool_use") toolUses += 1;
6246
+ if (ev.type === "llm_response") {
6247
+ if (typeof ev.stopReason === "string") lastStopReason = ev.stopReason;
6248
+ if (Array.isArray(ev.content)) {
6249
+ const txt = ev.content.filter((b) => b.type === "text").map((b) => b.text ?? "").join("\n").trim();
6250
+ if (txt) lastAssistantText = txt;
6251
+ }
6252
+ }
6253
+ } catch {
6254
+ }
6255
+ }
6256
+ return {
6257
+ lastAssistantText,
6258
+ lastStopReason,
6259
+ toolUsesObserved: toolUses,
6260
+ events: lines.length
6261
+ };
6262
+ }
6263
+ return void 0;
6264
+ }
5072
6265
 
5073
6266
  // src/coordination/agent-subagent-runner.ts
5074
6267
  function makeAgentSubagentRunner(opts) {
5075
6268
  const format = opts.formatTaskInput ?? defaultFormatTaskInput;
5076
6269
  return async (task, ctx) => {
5077
- const { agent, events } = await opts.factory(ctx.config);
6270
+ const factoryResult = await opts.factory(ctx.config);
6271
+ const { agent, events } = factoryResult;
6272
+ const detachFleet = opts.fleetBus?.attach(ctx.subagentId, events, task.id);
5078
6273
  const aborter = new AbortController();
5079
6274
  let budgetError = null;
5080
6275
  const onBudgetError = (err) => {
@@ -5088,13 +6283,19 @@ function makeAgentSubagentRunner(opts) {
5088
6283
  budgetError.message += ` (caused by: ${err.message})`;
5089
6284
  }
5090
6285
  };
6286
+ let lastToolFailed = null;
5091
6287
  const unsub = [];
5092
6288
  unsub.push(
5093
- events.on("tool.started", () => {
6289
+ events.on("tool.executed", (e) => {
5094
6290
  try {
5095
6291
  ctx.budget.recordToolCall();
5096
- } catch (e) {
5097
- onBudgetError(e);
6292
+ } catch (eb) {
6293
+ onBudgetError(eb);
6294
+ }
6295
+ if (e.ok === false) {
6296
+ lastToolFailed = e.name;
6297
+ } else if (e.ok === true) {
6298
+ lastToolFailed = null;
5098
6299
  }
5099
6300
  }),
5100
6301
  events.on("provider.response", (e) => {
@@ -5111,6 +6312,26 @@ function makeAgentSubagentRunner(opts) {
5111
6312
  } catch (e) {
5112
6313
  onBudgetError(e);
5113
6314
  }
6315
+ }),
6316
+ // D3: cooperative timeout enforcement DURING a long tool call.
6317
+ // The iteration-loop checkTimeout() only fires between agent
6318
+ // iterations — a single `bash sleep 3600` call would otherwise
6319
+ // park inside one tool execution while the timeout silently
6320
+ // passes, relying solely on the coordinator's hard Promise.race
6321
+ // to interrupt. Tools that emit `tool.progress` (bash chunks,
6322
+ // fetch byte progress, spawn-stream stdout) give us a heartbeat
6323
+ // we can hang the check on. When the budget trips here:
6324
+ // 1. onBudgetError sets budgetError + aborter.abort()
6325
+ // 2. aborter signal propagates to agent.run → tool executor
6326
+ // 3. tool's own signal listener kills the child process
6327
+ // Cheap: O(1) per progress event, and the budget short-circuits
6328
+ // when timeoutMs is unset (most subagents have one set anyway).
6329
+ events.on("tool.progress", () => {
6330
+ try {
6331
+ ctx.budget.checkTimeout();
6332
+ } catch (e) {
6333
+ onBudgetError(e);
6334
+ }
5114
6335
  })
5115
6336
  );
5116
6337
  const onParentAbort = () => aborter.abort();
@@ -5119,8 +6340,15 @@ function makeAgentSubagentRunner(opts) {
5119
6340
  try {
5120
6341
  result = await agent.run(format(task, ctx.config), { signal: aborter.signal });
5121
6342
  } finally {
6343
+ detachFleet?.();
5122
6344
  ctx.signal.removeEventListener("abort", onParentAbort);
5123
6345
  for (const u of unsub) u();
6346
+ if (factoryResult.dispose) {
6347
+ try {
6348
+ await factoryResult.dispose();
6349
+ } catch {
6350
+ }
6351
+ }
5124
6352
  }
5125
6353
  if (budgetError) throw budgetError;
5126
6354
  if (result.status === "failed") {
@@ -5133,6 +6361,13 @@ function makeAgentSubagentRunner(opts) {
5133
6361
  throw new Error("agent exhausted iteration limit");
5134
6362
  }
5135
6363
  const usage = ctx.budget.usage();
6364
+ const finalText = (result.finalText ?? "").trim();
6365
+ if (finalText.length === 0 && usage.toolCalls === 0) {
6366
+ throw new Error("empty response");
6367
+ }
6368
+ if (finalText.length === 0 && lastToolFailed !== null) {
6369
+ throw new Error(`tool failed: ${lastToolFailed}`);
6370
+ }
5136
6371
  return {
5137
6372
  result: result.finalText,
5138
6373
  iterations: result.iterations,
@@ -5198,10 +6433,12 @@ Working rules:
5198
6433
  - Never fabricate numbers \u2014 read the actual logs first
5199
6434
  - Always include file:line references for errors
5200
6435
  - If sessionPath is missing, ask the director to provide it
5201
- - Report confidence level: high (>90% accuracy), medium, low`,
5202
- maxIterations: 50,
5203
- maxToolCalls: 200,
5204
- timeoutMs: 12e4
6436
+ - Report confidence level: high (>90% accuracy), medium, low`
6437
+ // No hardcoded budgets — the orchestrator (delegate tool or
6438
+ // spawn_subagent) decides per-task how much room a subagent gets.
6439
+ // A monorepo audit needs hours; a single-file lint check needs
6440
+ // seconds. Pinning a number here forces the orchestrator to fight
6441
+ // the role's default instead of just asking for what it needs.
5205
6442
  };
5206
6443
  var BUG_HUNTER_AGENT = {
5207
6444
  id: "bug-hunter",
@@ -5241,10 +6478,8 @@ Working rules:
5241
6478
  - Never scan node_modules \u2014 it's noise
5242
6479
  - Always include file:line for every finding
5243
6480
  - If >30% of findings are false positives, note the confidence level
5244
- - Ask director for clarification if paths are ambiguous`,
5245
- maxIterations: 80,
5246
- maxToolCalls: 300,
5247
- timeoutMs: 18e4
6481
+ - Ask director for clarification if paths are ambiguous`
6482
+ // Budgets are set by the orchestrator per task — see fleet.ts header.
5248
6483
  };
5249
6484
  var REFACTOR_PLANNER_AGENT = {
5250
6485
  id: "refactor-planner",
@@ -5284,10 +6519,8 @@ Working rules:
5284
6519
  - Always include rollback strategy \u2014 every refactor can fail
5285
6520
  - Merge tasks that take <1h into a single phase
5286
6521
  - Respect team constraints (reviewer availability, parallelization)
5287
- - Never plan without analyzing the actual code first`,
5288
- maxIterations: 60,
5289
- maxToolCalls: 250,
5290
- timeoutMs: 15e4
6522
+ - Never plan without analyzing the actual code first`
6523
+ // Budgets are set by the orchestrator per task — see fleet.ts header.
5291
6524
  };
5292
6525
  var SECURITY_SCANNER_AGENT = {
5293
6526
  id: "security-scanner",
@@ -5335,10 +6568,8 @@ Working rules:
5335
6568
  - Never scan node_modules \u2014 use npm audit instead
5336
6569
  - Always provide remediation steps, not just findings
5337
6570
  - Verify regex-based secrets before flagging (false positive risk)
5338
- - When in doubt, flag as medium rather than ignoring potential issues`,
5339
- maxIterations: 70,
5340
- maxToolCalls: 280,
5341
- timeoutMs: 16e4
6571
+ - When in doubt, flag as medium rather than ignoring potential issues`
6572
+ // Budgets are set by the orchestrator per task — see fleet.ts header.
5342
6573
  };
5343
6574
  var FLEET_ROSTER = {
5344
6575
  "audit-log": AUDIT_LOG_AGENT,
@@ -6959,7 +8190,7 @@ async function startMetricsServer(opts) {
6959
8190
  const tls = opts.tls;
6960
8191
  const useHttps = !!(tls?.cert && tls?.key);
6961
8192
  const host = opts.host ?? "127.0.0.1";
6962
- const path14 = opts.path ?? "/metrics";
8193
+ const path15 = opts.path ?? "/metrics";
6963
8194
  const healthPath = opts.healthPath ?? "/healthz";
6964
8195
  const healthRegistry = opts.healthRegistry;
6965
8196
  const listener = (req, res) => {
@@ -6969,7 +8200,7 @@ async function startMetricsServer(opts) {
6969
8200
  return;
6970
8201
  }
6971
8202
  const url = req.url.split("?")[0];
6972
- if (url === path14) {
8203
+ if (url === path15) {
6973
8204
  let body;
6974
8205
  try {
6975
8206
  body = renderPrometheus(opts.sink.snapshot());
@@ -7033,7 +8264,7 @@ async function startMetricsServer(opts) {
7033
8264
  const protocol = useHttps ? "https" : "http";
7034
8265
  return {
7035
8266
  port: boundPort,
7036
- url: `${protocol}://${host}:${boundPort}${path14}`,
8267
+ url: `${protocol}://${host}:${boundPort}${path15}`,
7037
8268
  close: () => new Promise((resolve2, reject) => {
7038
8269
  server.close((err) => err ? reject(err) : resolve2());
7039
8270
  })
@@ -7579,6 +8810,6 @@ var allServers = () => ({
7579
8810
  sentinel: { ...sentinelServer(), enabled: false }
7580
8811
  });
7581
8812
 
7582
- export { ALL_FLEET_AGENTS, AUDIT_LOG_AGENT, AutoCompactionMiddleware, AutonomousRunner, BUG_HUNTER_AGENT, BudgetExceededError, ConfigMigrationError, DEFAULT_CONFIG_MIGRATIONS, DEFAULT_DIRECTOR_PREAMBLE, DEFAULT_SUBAGENT_BASELINE, DefaultAttachmentStore, DefaultConfigLoader, DefaultConfigStore, DefaultErrorHandler, DefaultHealthRegistry, DefaultLogger, DefaultMemoryStore, DefaultModeStore, DefaultModelsRegistry, DefaultMultiAgentCoordinator, DefaultPermissionPolicy, DefaultRetryPolicy, DefaultSecretScrubber, DefaultSecretVault, DefaultSessionReader, DefaultSessionStore, DefaultSkillLoader, DefaultTaskStore, Director, DirectorBudgetError, DoneConditionChecker, FLEET_ROSTER, FleetBus, FleetUsageAggregator, HybridCompactor, InMemoryAgentBridge, InMemoryBridgeTransport, InMemoryMetricsSink, IntelligentCompactor, LLMSelector, NoopMetricsSink, NoopTracer, OTelTracer, PROMETHEUS_CONTENT_TYPE, QueueStore, REFACTOR_PLANNER_AGENT, RecoveryLock, SECURITY_SCANNER_AGENT, SelectiveCompactor, SessionAnalyzer, SpecDrivenDev, SpecParser, SubagentBudget, TaskFlow, TaskGenerator, TaskTracker, ToolExecutor, allServers, awsServer, blockServer, braveSearchServer, buildOtlpMetricsRequest, buildOtlpTracesRequest, classifyFamily, composeDirectorPrompt, composeSubagentPrompt, context7Server, contextManagerTool, createContextManagerTool, createMessage, decryptConfigSecrets, encryptConfigSecrets, everArtServer, filesystemServer, githubServer, googleMapsServer, loadProjectModes, loadUserModes, makeAgentSubagentRunner, makeDirectorSessionFactory, migratePlaintextSecrets, renderPrometheus, rewriteConfigEncrypted, rosterSummaryFromConfigs, runConfigMigrations, sentinelServer, slackServer, startMetricsServer, startOtlpMetricsExporter, startOtlpTraceExporter, wireMetricsToEvents };
8813
+ export { ALL_FLEET_AGENTS, AUDIT_LOG_AGENT, AutoApprovePermissionPolicy, AutoCompactionMiddleware, AutonomousRunner, BUG_HUNTER_AGENT, BudgetExceededError, ConfigMigrationError, DEFAULT_CONFIG_MIGRATIONS, DEFAULT_DIRECTOR_PREAMBLE, DEFAULT_SUBAGENT_BASELINE, DefaultAttachmentStore, DefaultConfigLoader, DefaultConfigStore, DefaultErrorHandler, DefaultHealthRegistry, DefaultLogger, DefaultMemoryStore, DefaultModeStore, DefaultModelsRegistry, DefaultMultiAgentCoordinator, DefaultPermissionPolicy, DefaultProviderRunner, DefaultRetryPolicy, DefaultSecretScrubber, DefaultSecretVault, DefaultSessionReader, DefaultSessionStore, DefaultSkillLoader, DefaultTaskStore, Director, DirectorBudgetError, DirectorStateCheckpoint, DoneConditionChecker, FLEET_ROSTER, FleetBus, FleetUsageAggregator, HybridCompactor, InMemoryAgentBridge, InMemoryBridgeTransport, InMemoryMetricsSink, IntelligentCompactor, LLMSelector, NoopMetricsSink, NoopTracer, OTelTracer, PROMETHEUS_CONTENT_TYPE, QueueStore, REFACTOR_PLANNER_AGENT, RecoveryLock, SECURITY_SCANNER_AGENT, SelectiveCompactor, SessionAnalyzer, SpecDrivenDev, SpecParser, SubagentBudget, TaskFlow, TaskGenerator, TaskTracker, ToolExecutor, addPlanItem, allServers, attachPlanCheckpoint, attachTodosCheckpoint, awsServer, blockServer, braveSearchServer, buildOtlpMetricsRequest, buildOtlpTracesRequest, classifyFamily, clearPlan, composeDirectorPrompt, composeSubagentPrompt, context7Server, contextManagerTool, createContextManagerTool, createDelegateTool, createMessage, decryptConfigSecrets, emptyPlan, encryptConfigSecrets, everArtServer, filesystemServer, formatPlan, githubServer, googleMapsServer, loadDirectorState, loadPlan, loadProjectModes, loadTodosCheckpoint, loadUserModes, makeAgentSubagentRunner, makeDirectorSessionFactory, migratePlaintextSecrets, removePlanItem, renderPrometheus, rewriteConfigEncrypted, rosterSummaryFromConfigs, runConfigMigrations, savePlan, saveTodosCheckpoint, sentinelServer, setPlanItemStatus, slackServer, startMetricsServer, startOtlpMetricsExporter, startOtlpTraceExporter, wireMetricsToEvents };
7583
8814
  //# sourceMappingURL=index.js.map
7584
8815
  //# sourceMappingURL=index.js.map