@wrongstack/core 0.1.10 → 0.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/{agent-bridge-6KPqsFx6.d.ts → agent-bridge-DmBiCipY.d.ts} +1 -1
- package/dist/{compactor-B4mQZXf2.d.ts → compactor-DSl2FK7a.d.ts} +1 -1
- package/dist/{config-BU9f_5yH.d.ts → config-DXrqb41m.d.ts} +1 -1
- package/dist/{context-BmM2xGUZ.d.ts → context-u0bryklF.d.ts} +8 -0
- package/dist/coordination/index.d.ts +210 -12
- package/dist/coordination/index.js +941 -67
- package/dist/coordination/index.js.map +1 -1
- package/dist/defaults/index.d.ts +18 -18
- package/dist/defaults/index.js +953 -41
- package/dist/defaults/index.js.map +1 -1
- package/dist/{events-BMNaEFZl.d.ts → events-B6Q03pTu.d.ts} +73 -1
- package/dist/execution/index.d.ts +11 -11
- package/dist/index.d.ts +61 -28
- package/dist/index.js +1077 -48
- package/dist/index.js.map +1 -1
- package/dist/infrastructure/index.d.ts +6 -6
- package/dist/kernel/index.d.ts +9 -9
- package/dist/kernel/index.js.map +1 -1
- package/dist/{mcp-servers-Dzgg4x1w.d.ts → mcp-servers-BA1Ofmfj.d.ts} +3 -3
- package/dist/models/index.d.ts +2 -2
- package/dist/{multi-agent-fmkRHtof.d.ts → multi-agent-BDfkxL5C.d.ts} +71 -3
- package/dist/observability/index.d.ts +2 -2
- package/dist/{path-resolver-DBjaoXFq.d.ts → path-resolver-Crkt8wTQ.d.ts} +2 -2
- package/dist/{plugin-DJk6LL8B.d.ts → plugin-CoYYZKdn.d.ts} +19 -6
- package/dist/{renderer-rk_1Swwc.d.ts → renderer-0A2ZEtca.d.ts} +1 -1
- package/dist/sdd/index.d.ts +3 -3
- package/dist/{secret-scrubber-CicHLN4G.d.ts → secret-scrubber-3TLUkiCV.d.ts} +1 -1
- package/dist/{secret-scrubber-DF88luOe.d.ts → secret-scrubber-CwYliRWd.d.ts} +1 -1
- package/dist/security/index.d.ts +20 -4
- package/dist/security/index.js +13 -1
- package/dist/security/index.js.map +1 -1
- package/dist/{selector-BbJqiEP4.d.ts → selector-BRqzvugb.d.ts} +1 -1
- package/dist/{session-reader-Drq8RvJu.d.ts → session-reader-C3x96CDR.d.ts} +1 -1
- package/dist/{skill-DhfSizKv.d.ts → skill-Bx8jxznf.d.ts} +1 -1
- package/dist/storage/index.d.ts +164 -6
- package/dist/storage/index.js +273 -1
- package/dist/storage/index.js.map +1 -1
- package/dist/{system-prompt-BC_8ypCG.d.ts → system-prompt-CG9jU5-5.d.ts} +9 -1
- package/dist/{tool-executor-CpuJPYm9.d.ts → tool-executor-CYdZdtno.d.ts} +4 -4
- package/dist/types/index.d.ts +15 -15
- package/dist/utils/index.d.ts +1 -1
- package/package.json +1 -1
package/dist/defaults/index.js
CHANGED
|
@@ -406,6 +406,12 @@ var FileSessionWriter = class {
|
|
|
406
406
|
tokenIn = 0;
|
|
407
407
|
tokenOut = 0;
|
|
408
408
|
filePath;
|
|
409
|
+
/** Public accessor for the JSONL path — required by SessionWriter so
|
|
410
|
+
* observability surfaces (`/fleet log`, FleetPanel) can locate the
|
|
411
|
+
* transcript without recomputing the path from session metadata. */
|
|
412
|
+
get transcriptPath() {
|
|
413
|
+
return this.filePath || void 0;
|
|
414
|
+
}
|
|
409
415
|
initDone = false;
|
|
410
416
|
resumed;
|
|
411
417
|
appendFailCount = 0;
|
|
@@ -1796,6 +1802,272 @@ var SessionAnalyzer = class {
|
|
|
1796
1802
|
return last - first;
|
|
1797
1803
|
}
|
|
1798
1804
|
};
|
|
1805
|
+
async function loadTodosCheckpoint(filePath) {
|
|
1806
|
+
let raw;
|
|
1807
|
+
try {
|
|
1808
|
+
raw = await fsp.readFile(filePath, "utf8");
|
|
1809
|
+
} catch {
|
|
1810
|
+
return null;
|
|
1811
|
+
}
|
|
1812
|
+
try {
|
|
1813
|
+
const parsed = JSON.parse(raw);
|
|
1814
|
+
if (parsed?.version !== 1 || !Array.isArray(parsed.todos)) return null;
|
|
1815
|
+
return parsed.todos.filter(
|
|
1816
|
+
(t) => !!t && typeof t.id === "string" && typeof t.content === "string" && typeof t.status === "string"
|
|
1817
|
+
);
|
|
1818
|
+
} catch {
|
|
1819
|
+
return null;
|
|
1820
|
+
}
|
|
1821
|
+
}
|
|
1822
|
+
async function saveTodosCheckpoint(filePath, sessionId, todos) {
|
|
1823
|
+
const payload = {
|
|
1824
|
+
version: 1,
|
|
1825
|
+
sessionId,
|
|
1826
|
+
updatedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
1827
|
+
todos: [...todos]
|
|
1828
|
+
};
|
|
1829
|
+
try {
|
|
1830
|
+
await atomicWrite(filePath, JSON.stringify(payload, null, 2), { mode: 384 });
|
|
1831
|
+
} catch (err) {
|
|
1832
|
+
console.warn(
|
|
1833
|
+
"[todos-checkpoint] save failed:",
|
|
1834
|
+
err instanceof Error ? err.message : String(err)
|
|
1835
|
+
);
|
|
1836
|
+
}
|
|
1837
|
+
}
|
|
1838
|
+
function attachTodosCheckpoint(state, filePath, sessionId) {
|
|
1839
|
+
let timer = null;
|
|
1840
|
+
let pending = null;
|
|
1841
|
+
const flush = () => {
|
|
1842
|
+
timer = null;
|
|
1843
|
+
if (pending) {
|
|
1844
|
+
void saveTodosCheckpoint(filePath, sessionId, pending);
|
|
1845
|
+
pending = null;
|
|
1846
|
+
}
|
|
1847
|
+
};
|
|
1848
|
+
const unsubscribe = state.onChange((change) => {
|
|
1849
|
+
if (change.kind !== "todos_replaced") return;
|
|
1850
|
+
pending = change.todos;
|
|
1851
|
+
if (timer) clearTimeout(timer);
|
|
1852
|
+
timer = setTimeout(flush, 150);
|
|
1853
|
+
});
|
|
1854
|
+
return () => {
|
|
1855
|
+
unsubscribe();
|
|
1856
|
+
if (timer) {
|
|
1857
|
+
clearTimeout(timer);
|
|
1858
|
+
flush();
|
|
1859
|
+
}
|
|
1860
|
+
};
|
|
1861
|
+
}
|
|
1862
|
+
async function loadPlan(filePath) {
|
|
1863
|
+
let raw;
|
|
1864
|
+
try {
|
|
1865
|
+
raw = await fsp.readFile(filePath, "utf8");
|
|
1866
|
+
} catch {
|
|
1867
|
+
return null;
|
|
1868
|
+
}
|
|
1869
|
+
try {
|
|
1870
|
+
const parsed = JSON.parse(raw);
|
|
1871
|
+
if (parsed?.version !== 1 || !Array.isArray(parsed.items)) return null;
|
|
1872
|
+
return parsed;
|
|
1873
|
+
} catch {
|
|
1874
|
+
return null;
|
|
1875
|
+
}
|
|
1876
|
+
}
|
|
1877
|
+
async function savePlan(filePath, plan) {
|
|
1878
|
+
try {
|
|
1879
|
+
await atomicWrite(filePath, JSON.stringify(plan, null, 2), { mode: 384 });
|
|
1880
|
+
} catch (err) {
|
|
1881
|
+
console.warn(
|
|
1882
|
+
"[plan-store] save failed:",
|
|
1883
|
+
err instanceof Error ? err.message : String(err)
|
|
1884
|
+
);
|
|
1885
|
+
}
|
|
1886
|
+
}
|
|
1887
|
+
function emptyPlan(sessionId, title) {
|
|
1888
|
+
return {
|
|
1889
|
+
version: 1,
|
|
1890
|
+
sessionId,
|
|
1891
|
+
title,
|
|
1892
|
+
updatedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
1893
|
+
items: []
|
|
1894
|
+
};
|
|
1895
|
+
}
|
|
1896
|
+
function addPlanItem(plan, title, details) {
|
|
1897
|
+
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
1898
|
+
const item = {
|
|
1899
|
+
id: `plan_${Date.now()}_${randomUUID().slice(0, 6)}`,
|
|
1900
|
+
title,
|
|
1901
|
+
details,
|
|
1902
|
+
status: "open",
|
|
1903
|
+
createdAt: now,
|
|
1904
|
+
updatedAt: now
|
|
1905
|
+
};
|
|
1906
|
+
return {
|
|
1907
|
+
plan: { ...plan, items: [...plan.items, item], updatedAt: now },
|
|
1908
|
+
item
|
|
1909
|
+
};
|
|
1910
|
+
}
|
|
1911
|
+
function removePlanItem(plan, idOrIndex) {
|
|
1912
|
+
const idx = matchIndex(plan, idOrIndex);
|
|
1913
|
+
if (idx === -1) return plan;
|
|
1914
|
+
return {
|
|
1915
|
+
...plan,
|
|
1916
|
+
items: plan.items.filter((_, i) => i !== idx),
|
|
1917
|
+
updatedAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
1918
|
+
};
|
|
1919
|
+
}
|
|
1920
|
+
function setPlanItemStatus(plan, idOrIndex, status) {
|
|
1921
|
+
const idx = matchIndex(plan, idOrIndex);
|
|
1922
|
+
if (idx === -1) return plan;
|
|
1923
|
+
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
1924
|
+
const items = plan.items.map(
|
|
1925
|
+
(it, i) => i === idx ? { ...it, status, updatedAt: now } : it
|
|
1926
|
+
);
|
|
1927
|
+
return { ...plan, items, updatedAt: now };
|
|
1928
|
+
}
|
|
1929
|
+
function clearPlan(plan) {
|
|
1930
|
+
return { ...plan, items: [], updatedAt: (/* @__PURE__ */ new Date()).toISOString() };
|
|
1931
|
+
}
|
|
1932
|
+
function formatPlan(plan) {
|
|
1933
|
+
if (plan.items.length === 0) return "Plan is empty.";
|
|
1934
|
+
const lines = [];
|
|
1935
|
+
if (plan.title) lines.push(`# ${plan.title}`);
|
|
1936
|
+
plan.items.forEach((it, i) => {
|
|
1937
|
+
const mark = it.status === "done" ? "[x]" : it.status === "in_progress" ? "[~]" : "[ ]";
|
|
1938
|
+
lines.push(`${i + 1}. ${mark} ${it.title}`);
|
|
1939
|
+
if (it.details) {
|
|
1940
|
+
for (const line of it.details.split("\n")) lines.push(` ${line}`);
|
|
1941
|
+
}
|
|
1942
|
+
});
|
|
1943
|
+
return lines.join("\n");
|
|
1944
|
+
}
|
|
1945
|
+
function matchIndex(plan, idOrIndex) {
|
|
1946
|
+
const asNum = Number.parseInt(idOrIndex, 10);
|
|
1947
|
+
if (!Number.isNaN(asNum) && asNum >= 1 && asNum <= plan.items.length) return asNum - 1;
|
|
1948
|
+
const byId = plan.items.findIndex((it) => it.id === idOrIndex);
|
|
1949
|
+
if (byId !== -1) return byId;
|
|
1950
|
+
const lower = idOrIndex.toLowerCase();
|
|
1951
|
+
return plan.items.findIndex((it) => it.title.toLowerCase().includes(lower));
|
|
1952
|
+
}
|
|
1953
|
+
function attachPlanCheckpoint(_state, _filePath, _sessionId) {
|
|
1954
|
+
return () => void 0;
|
|
1955
|
+
}
|
|
1956
|
+
async function loadDirectorState(filePath) {
|
|
1957
|
+
let raw;
|
|
1958
|
+
try {
|
|
1959
|
+
raw = await fsp.readFile(filePath, "utf8");
|
|
1960
|
+
} catch {
|
|
1961
|
+
return null;
|
|
1962
|
+
}
|
|
1963
|
+
try {
|
|
1964
|
+
const parsed = JSON.parse(raw);
|
|
1965
|
+
if (parsed?.version !== 1) return null;
|
|
1966
|
+
return parsed;
|
|
1967
|
+
} catch {
|
|
1968
|
+
return null;
|
|
1969
|
+
}
|
|
1970
|
+
}
|
|
1971
|
+
var DirectorStateCheckpoint = class {
|
|
1972
|
+
snapshot;
|
|
1973
|
+
filePath;
|
|
1974
|
+
timer = null;
|
|
1975
|
+
debounceMs;
|
|
1976
|
+
writing = false;
|
|
1977
|
+
rewriteRequested = false;
|
|
1978
|
+
constructor(filePath, init, debounceMs = 250) {
|
|
1979
|
+
this.filePath = filePath;
|
|
1980
|
+
this.debounceMs = debounceMs;
|
|
1981
|
+
this.snapshot = {
|
|
1982
|
+
version: 1,
|
|
1983
|
+
directorRunId: init.directorRunId,
|
|
1984
|
+
updatedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
1985
|
+
spawnCount: 0,
|
|
1986
|
+
maxSpawns: init.maxSpawns,
|
|
1987
|
+
spawnDepth: init.spawnDepth,
|
|
1988
|
+
maxSpawnDepth: init.maxSpawnDepth,
|
|
1989
|
+
subagents: [],
|
|
1990
|
+
tasks: []
|
|
1991
|
+
};
|
|
1992
|
+
}
|
|
1993
|
+
current() {
|
|
1994
|
+
return this.snapshot;
|
|
1995
|
+
}
|
|
1996
|
+
recordSpawn(sub, spawnCount) {
|
|
1997
|
+
this.snapshot = {
|
|
1998
|
+
...this.snapshot,
|
|
1999
|
+
spawnCount,
|
|
2000
|
+
subagents: [...this.snapshot.subagents.filter((s) => s.id !== sub.id), sub]
|
|
2001
|
+
};
|
|
2002
|
+
this.bumpUpdatedAt();
|
|
2003
|
+
this.schedule();
|
|
2004
|
+
}
|
|
2005
|
+
recordTaskAssigned(task) {
|
|
2006
|
+
const exists = this.snapshot.tasks.some((t) => t.taskId === task.taskId);
|
|
2007
|
+
this.snapshot = {
|
|
2008
|
+
...this.snapshot,
|
|
2009
|
+
tasks: exists ? this.snapshot.tasks.map((t) => t.taskId === task.taskId ? { ...t, ...task } : t) : [...this.snapshot.tasks, task]
|
|
2010
|
+
};
|
|
2011
|
+
this.bumpUpdatedAt();
|
|
2012
|
+
this.schedule();
|
|
2013
|
+
}
|
|
2014
|
+
recordTaskStatus(taskId, patch) {
|
|
2015
|
+
this.snapshot = {
|
|
2016
|
+
...this.snapshot,
|
|
2017
|
+
tasks: this.snapshot.tasks.map(
|
|
2018
|
+
(t) => t.taskId === taskId ? { ...t, ...patch } : t
|
|
2019
|
+
)
|
|
2020
|
+
};
|
|
2021
|
+
this.bumpUpdatedAt();
|
|
2022
|
+
this.schedule();
|
|
2023
|
+
}
|
|
2024
|
+
setUsage(usage) {
|
|
2025
|
+
this.snapshot = { ...this.snapshot, usage };
|
|
2026
|
+
this.bumpUpdatedAt();
|
|
2027
|
+
this.schedule();
|
|
2028
|
+
}
|
|
2029
|
+
/** Force a synchronous flush — used by Director.shutdown(). */
|
|
2030
|
+
async flush() {
|
|
2031
|
+
if (this.timer) {
|
|
2032
|
+
clearTimeout(this.timer);
|
|
2033
|
+
this.timer = null;
|
|
2034
|
+
}
|
|
2035
|
+
await this.persist();
|
|
2036
|
+
}
|
|
2037
|
+
bumpUpdatedAt() {
|
|
2038
|
+
this.snapshot = { ...this.snapshot, updatedAt: (/* @__PURE__ */ new Date()).toISOString() };
|
|
2039
|
+
}
|
|
2040
|
+
schedule() {
|
|
2041
|
+
if (this.timer) return;
|
|
2042
|
+
this.timer = setTimeout(() => {
|
|
2043
|
+
this.timer = null;
|
|
2044
|
+
void this.persist();
|
|
2045
|
+
}, this.debounceMs);
|
|
2046
|
+
}
|
|
2047
|
+
async persist() {
|
|
2048
|
+
if (this.writing) {
|
|
2049
|
+
this.rewriteRequested = true;
|
|
2050
|
+
return;
|
|
2051
|
+
}
|
|
2052
|
+
this.writing = true;
|
|
2053
|
+
try {
|
|
2054
|
+
await atomicWrite(this.filePath, JSON.stringify(this.snapshot, null, 2), {
|
|
2055
|
+
mode: 384
|
|
2056
|
+
});
|
|
2057
|
+
} catch (err) {
|
|
2058
|
+
console.warn(
|
|
2059
|
+
"[director-state] checkpoint write failed:",
|
|
2060
|
+
err instanceof Error ? err.message : String(err)
|
|
2061
|
+
);
|
|
2062
|
+
} finally {
|
|
2063
|
+
this.writing = false;
|
|
2064
|
+
if (this.rewriteRequested) {
|
|
2065
|
+
this.rewriteRequested = false;
|
|
2066
|
+
this.schedule();
|
|
2067
|
+
}
|
|
2068
|
+
}
|
|
2069
|
+
}
|
|
2070
|
+
};
|
|
1799
2071
|
|
|
1800
2072
|
// src/security/secret-scrubber.ts
|
|
1801
2073
|
var PATTERNS = [
|
|
@@ -2071,6 +2343,18 @@ var DefaultPermissionPolicy = class {
|
|
|
2071
2343
|
return void 0;
|
|
2072
2344
|
}
|
|
2073
2345
|
};
|
|
2346
|
+
var AutoApprovePermissionPolicy = class {
|
|
2347
|
+
async evaluate(tool) {
|
|
2348
|
+
if (tool.permission === "deny") {
|
|
2349
|
+
return { permission: "deny", source: "default", reason: "tool default deny" };
|
|
2350
|
+
}
|
|
2351
|
+
return { permission: "auto", source: "yolo" };
|
|
2352
|
+
}
|
|
2353
|
+
async trust() {
|
|
2354
|
+
}
|
|
2355
|
+
async reload() {
|
|
2356
|
+
}
|
|
2357
|
+
};
|
|
2074
2358
|
|
|
2075
2359
|
// src/types/errors.ts
|
|
2076
2360
|
var WrongStackError = class extends Error {
|
|
@@ -3946,6 +4230,10 @@ var FleetBus = class {
|
|
|
3946
4230
|
"iteration.started",
|
|
3947
4231
|
"iteration.completed",
|
|
3948
4232
|
"provider.text_delta",
|
|
4233
|
+
// Subagent extended-thinking output. Forwarded so the FleetPanel /
|
|
4234
|
+
// /fleet log can surface "the planner is thinking…" instead of a
|
|
4235
|
+
// silent gap between iteration.started and the first text_delta.
|
|
4236
|
+
"provider.thinking_delta",
|
|
3949
4237
|
"provider.response",
|
|
3950
4238
|
"provider.retry",
|
|
3951
4239
|
"provider.error",
|
|
@@ -4194,14 +4482,34 @@ var DefaultMultiAgentCoordinator = class extends EventEmitter {
|
|
|
4194
4482
|
completedResults = [];
|
|
4195
4483
|
totalIterations = 0;
|
|
4196
4484
|
inFlight = 0;
|
|
4485
|
+
/**
|
|
4486
|
+
* Subagents currently being stopped. Set on entry to `stop()`, cleared
|
|
4487
|
+
* once `recordCompletion` lands the terminal TaskResult. Used by
|
|
4488
|
+
* `runDispatched` and `findIdleSubagent` to refuse mid-flight dispatch
|
|
4489
|
+
* to a subagent the caller has already asked to terminate — closes the
|
|
4490
|
+
* assign+terminate race where a fresh task could land on a worker that
|
|
4491
|
+
* was about to be killed.
|
|
4492
|
+
*/
|
|
4493
|
+
terminating = /* @__PURE__ */ new Set();
|
|
4197
4494
|
constructor(config, options = {}) {
|
|
4198
4495
|
super();
|
|
4199
4496
|
this.coordinatorId = config.coordinatorId;
|
|
4200
4497
|
this.config = config;
|
|
4201
4498
|
this.runner = options.runner;
|
|
4202
4499
|
}
|
|
4500
|
+
/**
|
|
4501
|
+
* Replace the runner after construction. Used when the runner depends
|
|
4502
|
+
* on infrastructure (e.g. FleetBus) that isn't available until after
|
|
4503
|
+
* the coordinator's owning Director is built.
|
|
4504
|
+
*/
|
|
4505
|
+
setRunner(runner) {
|
|
4506
|
+
this.runner = runner;
|
|
4507
|
+
}
|
|
4203
4508
|
async spawn(subagent) {
|
|
4204
4509
|
const id = subagent.id || randomUUID();
|
|
4510
|
+
if (this.subagents.has(id)) {
|
|
4511
|
+
throw new Error(`Subagent id "${id}" already exists \u2014 refusing to overwrite`);
|
|
4512
|
+
}
|
|
4205
4513
|
const context = {
|
|
4206
4514
|
subagentId: id,
|
|
4207
4515
|
tasks: [],
|
|
@@ -4245,6 +4553,7 @@ var DefaultMultiAgentCoordinator = class extends EventEmitter {
|
|
|
4245
4553
|
async stop(subagentId) {
|
|
4246
4554
|
const subagent = this.subagents.get(subagentId);
|
|
4247
4555
|
if (!subagent) return;
|
|
4556
|
+
this.terminating.add(subagentId);
|
|
4248
4557
|
subagent.abortController.abort();
|
|
4249
4558
|
subagent.status = "stopped";
|
|
4250
4559
|
subagent.currentTask = void 0;
|
|
@@ -4252,6 +4561,7 @@ var DefaultMultiAgentCoordinator = class extends EventEmitter {
|
|
|
4252
4561
|
this.emit("subagent.stopped", { subagentId, reason: "stopped by coordinator" });
|
|
4253
4562
|
}
|
|
4254
4563
|
async stopAll() {
|
|
4564
|
+
this.drainPendingAsAborted("Coordinator stopAll() drained the pending queue");
|
|
4255
4565
|
await Promise.allSettled([...this.subagents.keys()].map((id) => this.stop(id)));
|
|
4256
4566
|
}
|
|
4257
4567
|
getStatus() {
|
|
@@ -4285,7 +4595,14 @@ var DefaultMultiAgentCoordinator = class extends EventEmitter {
|
|
|
4285
4595
|
tryDispatchNext() {
|
|
4286
4596
|
while (this.canDispatch()) {
|
|
4287
4597
|
const subagentId = this.findIdleSubagent();
|
|
4288
|
-
if (!subagentId)
|
|
4598
|
+
if (!subagentId) {
|
|
4599
|
+
if (this.pendingTasks.length > 0 && !this.hasLiveSubagent()) {
|
|
4600
|
+
this.drainPendingAsAborted(
|
|
4601
|
+
"No live subagent available \u2014 all stopped or mid-termination"
|
|
4602
|
+
);
|
|
4603
|
+
}
|
|
4604
|
+
return;
|
|
4605
|
+
}
|
|
4289
4606
|
const task = this.pendingTasks.shift();
|
|
4290
4607
|
if (!task) return;
|
|
4291
4608
|
this.runDispatched(subagentId, task).catch((err) => {
|
|
@@ -4293,7 +4610,7 @@ var DefaultMultiAgentCoordinator = class extends EventEmitter {
|
|
|
4293
4610
|
subagentId,
|
|
4294
4611
|
taskId: task.id,
|
|
4295
4612
|
status: "failed",
|
|
4296
|
-
error:
|
|
4613
|
+
error: classifySubagentError(err),
|
|
4297
4614
|
iterations: 0,
|
|
4298
4615
|
toolCalls: 0,
|
|
4299
4616
|
durationMs: 0
|
|
@@ -4307,13 +4624,76 @@ var DefaultMultiAgentCoordinator = class extends EventEmitter {
|
|
|
4307
4624
|
}
|
|
4308
4625
|
findIdleSubagent() {
|
|
4309
4626
|
for (const [id, s] of this.subagents) {
|
|
4310
|
-
if (s.status === "idle") return id;
|
|
4627
|
+
if (s.status === "idle" && !this.terminating.has(id)) return id;
|
|
4311
4628
|
}
|
|
4312
4629
|
return null;
|
|
4313
4630
|
}
|
|
4631
|
+
/**
|
|
4632
|
+
* Returns true iff at least one spawned subagent could still
|
|
4633
|
+
* process a task. A "live" subagent is one that is not stopped
|
|
4634
|
+
* AND not mid-termination — `running` workers count because they
|
|
4635
|
+
* will eventually finish and become idle.
|
|
4636
|
+
*
|
|
4637
|
+
* When no subagent has ever been spawned, returns `true` so a
|
|
4638
|
+
* pre-spawn `assign()` simply queues (legacy behaviour). The
|
|
4639
|
+
* dead-end detection only fires after `stop()` has retired every
|
|
4640
|
+
* spawned worker.
|
|
4641
|
+
*
|
|
4642
|
+
* Used by `tryDispatchNext` to detect a dead-end pending queue.
|
|
4643
|
+
*/
|
|
4644
|
+
hasLiveSubagent() {
|
|
4645
|
+
if (this.subagents.size === 0) return true;
|
|
4646
|
+
for (const [id, s] of this.subagents) {
|
|
4647
|
+
if (s.status !== "stopped" && !this.terminating.has(id)) return true;
|
|
4648
|
+
}
|
|
4649
|
+
return false;
|
|
4650
|
+
}
|
|
4651
|
+
/**
|
|
4652
|
+
* Drain every pending task with a synthetic `aborted_by_parent`
|
|
4653
|
+
* completion event. Same shape as the `stopAll()` drain — we go
|
|
4654
|
+
* around `recordCompletion` because pending tasks were never
|
|
4655
|
+
* counted in `inFlight` and routing them through would trip the
|
|
4656
|
+
* underflow guard on every task after the first.
|
|
4657
|
+
*/
|
|
4658
|
+
drainPendingAsAborted(message) {
|
|
4659
|
+
const dropped = this.pendingTasks.splice(0, this.pendingTasks.length);
|
|
4660
|
+
for (const t of dropped) {
|
|
4661
|
+
const synthetic = {
|
|
4662
|
+
subagentId: t.subagentId ?? "unassigned",
|
|
4663
|
+
taskId: t.id,
|
|
4664
|
+
status: "stopped",
|
|
4665
|
+
error: {
|
|
4666
|
+
kind: "aborted_by_parent",
|
|
4667
|
+
message,
|
|
4668
|
+
retryable: false
|
|
4669
|
+
},
|
|
4670
|
+
iterations: 0,
|
|
4671
|
+
toolCalls: 0,
|
|
4672
|
+
durationMs: 0
|
|
4673
|
+
};
|
|
4674
|
+
this.completedResults.push(synthetic);
|
|
4675
|
+
this.emit("task.completed", { task: t, result: synthetic });
|
|
4676
|
+
}
|
|
4677
|
+
}
|
|
4314
4678
|
async runDispatched(subagentId, task) {
|
|
4315
4679
|
const subagent = this.subagents.get(subagentId);
|
|
4316
4680
|
if (!subagent) return;
|
|
4681
|
+
if (this.terminating.has(subagentId) || subagent.status === "stopped") {
|
|
4682
|
+
this.recordCompletion({
|
|
4683
|
+
subagentId,
|
|
4684
|
+
taskId: task.id,
|
|
4685
|
+
status: "stopped",
|
|
4686
|
+
error: {
|
|
4687
|
+
kind: "aborted_by_parent",
|
|
4688
|
+
message: "Subagent was terminated before task could start",
|
|
4689
|
+
retryable: false
|
|
4690
|
+
},
|
|
4691
|
+
iterations: 0,
|
|
4692
|
+
toolCalls: 0,
|
|
4693
|
+
durationMs: 0
|
|
4694
|
+
});
|
|
4695
|
+
return;
|
|
4696
|
+
}
|
|
4317
4697
|
subagent.status = "running";
|
|
4318
4698
|
subagent.currentTask = task.id;
|
|
4319
4699
|
task.subagentId = subagentId;
|
|
@@ -4359,7 +4739,9 @@ var DefaultMultiAgentCoordinator = class extends EventEmitter {
|
|
|
4359
4739
|
subagentId,
|
|
4360
4740
|
taskId: task.id,
|
|
4361
4741
|
status,
|
|
4362
|
-
error:
|
|
4742
|
+
error: classifySubagentError(err, {
|
|
4743
|
+
parentAborted: subagent.abortController.signal.aborted
|
|
4744
|
+
}),
|
|
4363
4745
|
iterations: usage.iterations,
|
|
4364
4746
|
toolCalls: usage.toolCalls,
|
|
4365
4747
|
durationMs: Date.now() - startTime
|
|
@@ -4398,19 +4780,14 @@ var DefaultMultiAgentCoordinator = class extends EventEmitter {
|
|
|
4398
4780
|
}
|
|
4399
4781
|
const subagent = this.subagents.get(result.subagentId);
|
|
4400
4782
|
if (subagent && subagent.status !== "stopped") {
|
|
4401
|
-
|
|
4402
|
-
subagent.status =
|
|
4783
|
+
result.status === "failed" || result.status === "timeout";
|
|
4784
|
+
subagent.status = "idle";
|
|
4403
4785
|
subagent.currentTask = void 0;
|
|
4404
4786
|
if (subagent.abortController.signal.aborted) {
|
|
4405
4787
|
subagent.abortController = new AbortController();
|
|
4406
4788
|
}
|
|
4407
|
-
if (subagent.status === "error") {
|
|
4408
|
-
queueMicrotask(() => {
|
|
4409
|
-
if (subagent.status === "error") subagent.status = "idle";
|
|
4410
|
-
this.tryDispatchNext();
|
|
4411
|
-
});
|
|
4412
|
-
}
|
|
4413
4789
|
}
|
|
4790
|
+
this.terminating.delete(result.subagentId);
|
|
4414
4791
|
this.emit("task.completed", {
|
|
4415
4792
|
task: subagent?.context.tasks.find((t) => t.id === result.taskId) ?? { id: result.taskId },
|
|
4416
4793
|
result
|
|
@@ -4433,6 +4810,99 @@ var DefaultMultiAgentCoordinator = class extends EventEmitter {
|
|
|
4433
4810
|
return false;
|
|
4434
4811
|
}
|
|
4435
4812
|
};
|
|
4813
|
+
function classifySubagentError(err, hints = {}) {
|
|
4814
|
+
const cause = err instanceof Error ? { name: err.name, message: err.message, stack: err.stack } : void 0;
|
|
4815
|
+
const baseMessage = err instanceof Error ? err.message : String(err);
|
|
4816
|
+
if (err instanceof ProviderError) {
|
|
4817
|
+
return providerErrorToSubagentError(err, baseMessage, cause);
|
|
4818
|
+
}
|
|
4819
|
+
if (err instanceof BudgetExceededError) {
|
|
4820
|
+
const map = {
|
|
4821
|
+
iterations: "budget_iterations",
|
|
4822
|
+
tool_calls: "budget_tool_calls",
|
|
4823
|
+
tokens: "budget_tokens",
|
|
4824
|
+
cost: "budget_cost",
|
|
4825
|
+
timeout: "budget_timeout"
|
|
4826
|
+
};
|
|
4827
|
+
return {
|
|
4828
|
+
kind: map[err.kind],
|
|
4829
|
+
message: baseMessage,
|
|
4830
|
+
// Budgets are user-configured ceilings, not transient failures —
|
|
4831
|
+
// retrying with the same budget will hit the same ceiling. The
|
|
4832
|
+
// orchestrator must raise the budget or narrow the task first.
|
|
4833
|
+
retryable: false,
|
|
4834
|
+
cause
|
|
4835
|
+
};
|
|
4836
|
+
}
|
|
4837
|
+
if (hints.parentAborted) {
|
|
4838
|
+
return {
|
|
4839
|
+
kind: "aborted_by_parent",
|
|
4840
|
+
message: baseMessage,
|
|
4841
|
+
retryable: false,
|
|
4842
|
+
cause
|
|
4843
|
+
};
|
|
4844
|
+
}
|
|
4845
|
+
const lower = baseMessage.toLowerCase();
|
|
4846
|
+
if (/agent aborted$/i.test(baseMessage)) {
|
|
4847
|
+
return {
|
|
4848
|
+
kind: "aborted_by_parent",
|
|
4849
|
+
message: baseMessage,
|
|
4850
|
+
retryable: false,
|
|
4851
|
+
cause
|
|
4852
|
+
};
|
|
4853
|
+
}
|
|
4854
|
+
if (/agent exhausted iteration limit$/i.test(baseMessage)) {
|
|
4855
|
+
return { kind: "budget_iterations", message: baseMessage, retryable: false, cause };
|
|
4856
|
+
}
|
|
4857
|
+
if (/empty response$/i.test(baseMessage)) {
|
|
4858
|
+
return { kind: "empty_response", message: baseMessage, retryable: false, cause };
|
|
4859
|
+
}
|
|
4860
|
+
if (/^tool failed: /i.test(baseMessage)) {
|
|
4861
|
+
return { kind: "tool_failed", message: baseMessage, retryable: false, cause };
|
|
4862
|
+
}
|
|
4863
|
+
if (lower.includes("bridge transport") || /bridge.*(closed|disconnect)/i.test(baseMessage)) {
|
|
4864
|
+
return { kind: "bridge_failed", message: baseMessage, retryable: false, cause };
|
|
4865
|
+
}
|
|
4866
|
+
if (/context length|max.*tokens?.*exceeded|prompt is too long/i.test(baseMessage)) {
|
|
4867
|
+
return { kind: "context_overflow", message: baseMessage, retryable: false, cause };
|
|
4868
|
+
}
|
|
4869
|
+
return {
|
|
4870
|
+
kind: "unknown",
|
|
4871
|
+
message: baseMessage,
|
|
4872
|
+
retryable: false,
|
|
4873
|
+
cause
|
|
4874
|
+
};
|
|
4875
|
+
}
|
|
4876
|
+
function providerErrorToSubagentError(err, message, cause) {
|
|
4877
|
+
const status = err.status;
|
|
4878
|
+
if (status === 429 || err.body?.type === "rate_limit_error") {
|
|
4879
|
+
return {
|
|
4880
|
+
kind: "provider_rate_limit",
|
|
4881
|
+
message,
|
|
4882
|
+
retryable: true,
|
|
4883
|
+
// Conservative default: 5s. Provider-specific code can override
|
|
4884
|
+
// by emitting an error whose body carries an explicit hint.
|
|
4885
|
+
backoffMs: 5e3,
|
|
4886
|
+
cause
|
|
4887
|
+
};
|
|
4888
|
+
}
|
|
4889
|
+
if (status === 401 || status === 403 || err.body?.type === "authentication_error") {
|
|
4890
|
+
return { kind: "provider_auth", message, retryable: false, cause };
|
|
4891
|
+
}
|
|
4892
|
+
if (status === 408 || status === 0) {
|
|
4893
|
+
return { kind: "provider_timeout", message, retryable: true, cause };
|
|
4894
|
+
}
|
|
4895
|
+
if (status >= 500 && status < 600) {
|
|
4896
|
+
return {
|
|
4897
|
+
kind: "provider_5xx",
|
|
4898
|
+
message,
|
|
4899
|
+
retryable: true,
|
|
4900
|
+
backoffMs: 3e3,
|
|
4901
|
+
cause
|
|
4902
|
+
};
|
|
4903
|
+
}
|
|
4904
|
+
return { kind: "unknown", message, retryable: err.retryable, cause };
|
|
4905
|
+
}
|
|
4436
4906
|
|
|
4437
4907
|
// src/coordination/director.ts
|
|
4438
4908
|
var DirectorBudgetError = class extends Error {
|
|
@@ -4496,6 +4966,27 @@ var Director = class {
|
|
|
4496
4966
|
spawnDepth;
|
|
4497
4967
|
/** Live spawn counter for `maxSpawns` enforcement. */
|
|
4498
4968
|
spawnCount = 0;
|
|
4969
|
+
/** Optional checkpoint mirror — writes the live task graph + roster to disk. */
|
|
4970
|
+
stateCheckpoint;
|
|
4971
|
+
/** Optional session writer for emitting task_* / agent_* lifecycle events. */
|
|
4972
|
+
sessionWriter;
|
|
4973
|
+
/** Debounce timer for periodic manifest writes. */
|
|
4974
|
+
manifestTimer = null;
|
|
4975
|
+
manifestDebounceMs;
|
|
4976
|
+
/** Resolves task descriptions back from `assign()` so completion events
|
|
4977
|
+
* can also carry a human-readable title. */
|
|
4978
|
+
taskDescriptions = /* @__PURE__ */ new Map();
|
|
4979
|
+
/** Snapshot of which subagent owns each task — drives state-checkpoint
|
|
4980
|
+
* status updates without re-walking the manifest. */
|
|
4981
|
+
taskOwners = /* @__PURE__ */ new Map();
|
|
4982
|
+
/**
|
|
4983
|
+
* Handle to the coordinator-side `task.completed` listener so we can
|
|
4984
|
+
* unsubscribe in `shutdown()`. Without this, repeated Director
|
|
4985
|
+
* construction (e.g. tests, hot reloads) accumulates listeners on a
|
|
4986
|
+
* cached coordinator and slowly drifts the EventEmitter past its
|
|
4987
|
+
* default cap.
|
|
4988
|
+
*/
|
|
4989
|
+
taskCompletedListener = null;
|
|
4499
4990
|
constructor(opts) {
|
|
4500
4991
|
this.id = opts.config.coordinatorId || randomUUID();
|
|
4501
4992
|
this.manifestPath = opts.manifestPath;
|
|
@@ -4506,6 +4997,14 @@ var Director = class {
|
|
|
4506
4997
|
this.maxSpawns = opts.maxSpawns ?? Number.POSITIVE_INFINITY;
|
|
4507
4998
|
this.maxSpawnDepth = opts.maxSpawnDepth ?? 2;
|
|
4508
4999
|
this.spawnDepth = opts.spawnDepth ?? 0;
|
|
5000
|
+
this.sessionWriter = opts.sessionWriter ?? null;
|
|
5001
|
+
this.manifestDebounceMs = opts.manifestDebounceMs ?? 2e3;
|
|
5002
|
+
this.stateCheckpoint = opts.stateCheckpointPath ? new DirectorStateCheckpoint(opts.stateCheckpointPath, {
|
|
5003
|
+
directorRunId: this.id,
|
|
5004
|
+
maxSpawns: opts.maxSpawns,
|
|
5005
|
+
spawnDepth: this.spawnDepth,
|
|
5006
|
+
maxSpawnDepth: this.maxSpawnDepth
|
|
5007
|
+
}) : null;
|
|
4509
5008
|
if (this.sharedScratchpadPath) {
|
|
4510
5009
|
void fsp.mkdir(this.sharedScratchpadPath, { recursive: true }).catch(() => void 0);
|
|
4511
5010
|
}
|
|
@@ -4524,7 +5023,7 @@ var Director = class {
|
|
|
4524
5023
|
{ ...opts.config, coordinatorId: this.id },
|
|
4525
5024
|
{ runner: opts.runner }
|
|
4526
5025
|
);
|
|
4527
|
-
this.
|
|
5026
|
+
this.taskCompletedListener = (payload) => {
|
|
4528
5027
|
const r = payload.result;
|
|
4529
5028
|
this.completed.set(r.taskId, r);
|
|
4530
5029
|
const waiter = this.taskWaiters.get(r.taskId);
|
|
@@ -4532,7 +5031,54 @@ var Director = class {
|
|
|
4532
5031
|
waiter.resolve(r);
|
|
4533
5032
|
this.taskWaiters.delete(r.taskId);
|
|
4534
5033
|
}
|
|
4535
|
-
|
|
5034
|
+
const title = this.taskDescriptions.get(r.taskId) ?? payload.task.description ?? r.taskId;
|
|
5035
|
+
const failed = r.status !== "success";
|
|
5036
|
+
const errorString = r.error ? `${r.error.kind}: ${r.error.message}` : void 0;
|
|
5037
|
+
this.stateCheckpoint?.recordTaskStatus(r.taskId, {
|
|
5038
|
+
status: failed ? r.status : "completed",
|
|
5039
|
+
completedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
5040
|
+
iterations: r.iterations,
|
|
5041
|
+
toolCalls: r.toolCalls,
|
|
5042
|
+
durationMs: r.durationMs,
|
|
5043
|
+
error: errorString
|
|
5044
|
+
});
|
|
5045
|
+
this.stateCheckpoint?.setUsage(this.usage.snapshot());
|
|
5046
|
+
void this.appendSessionEvent(
|
|
5047
|
+
failed ? {
|
|
5048
|
+
type: "task_failed",
|
|
5049
|
+
ts: (/* @__PURE__ */ new Date()).toISOString(),
|
|
5050
|
+
taskId: r.taskId,
|
|
5051
|
+
title,
|
|
5052
|
+
error: errorString ?? r.status
|
|
5053
|
+
} : {
|
|
5054
|
+
type: "task_completed",
|
|
5055
|
+
ts: (/* @__PURE__ */ new Date()).toISOString(),
|
|
5056
|
+
taskId: r.taskId,
|
|
5057
|
+
title
|
|
5058
|
+
}
|
|
5059
|
+
);
|
|
5060
|
+
this.scheduleManifest();
|
|
5061
|
+
};
|
|
5062
|
+
this.coordinator.on("task.completed", this.taskCompletedListener);
|
|
5063
|
+
}
|
|
5064
|
+
/** Best-effort session-writer append. Swallows failures — the director
|
|
5065
|
+
* must not break a fleet run because the session JSONL handle closed. */
|
|
5066
|
+
async appendSessionEvent(event) {
|
|
5067
|
+
if (!this.sessionWriter) return;
|
|
5068
|
+
try {
|
|
5069
|
+
await this.sessionWriter.append(event);
|
|
5070
|
+
} catch {
|
|
5071
|
+
}
|
|
5072
|
+
}
|
|
5073
|
+
/** Debounced manifest writer. A burst of spawn/assign/complete events
|
|
5074
|
+
* collapses into one write. Set `manifestDebounceMs` to 0 to disable. */
|
|
5075
|
+
scheduleManifest() {
|
|
5076
|
+
if (!this.manifestPath || this.manifestDebounceMs <= 0) return;
|
|
5077
|
+
if (this.manifestTimer) return;
|
|
5078
|
+
this.manifestTimer = setTimeout(() => {
|
|
5079
|
+
this.manifestTimer = null;
|
|
5080
|
+
void this.writeManifest().catch(() => void 0);
|
|
5081
|
+
}, this.manifestDebounceMs);
|
|
4536
5082
|
}
|
|
4537
5083
|
/**
|
|
4538
5084
|
* Spawn a subagent. Identical to the coordinator's `spawn()` but
|
|
@@ -4571,6 +5117,25 @@ var Director = class {
|
|
|
4571
5117
|
model: config.model,
|
|
4572
5118
|
taskIds: []
|
|
4573
5119
|
});
|
|
5120
|
+
const spawnedAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
5121
|
+
this.stateCheckpoint?.recordSpawn(
|
|
5122
|
+
{
|
|
5123
|
+
id: result.subagentId,
|
|
5124
|
+
name: config.name,
|
|
5125
|
+
role: config.role,
|
|
5126
|
+
provider: config.provider,
|
|
5127
|
+
model: config.model,
|
|
5128
|
+
spawnedAt
|
|
5129
|
+
},
|
|
5130
|
+
this.spawnCount
|
|
5131
|
+
);
|
|
5132
|
+
void this.appendSessionEvent({
|
|
5133
|
+
type: "agent_spawned",
|
|
5134
|
+
ts: spawnedAt,
|
|
5135
|
+
agentId: result.subagentId,
|
|
5136
|
+
role: config.role ?? config.name
|
|
5137
|
+
});
|
|
5138
|
+
this.scheduleManifest();
|
|
4574
5139
|
return result.subagentId;
|
|
4575
5140
|
}
|
|
4576
5141
|
/**
|
|
@@ -4691,13 +5256,42 @@ var Director = class {
|
|
|
4691
5256
|
* — calling shutdown twice is a no-op on the second invocation.
|
|
4692
5257
|
*/
|
|
4693
5258
|
async shutdown() {
|
|
5259
|
+
if (this.manifestTimer) {
|
|
5260
|
+
clearTimeout(this.manifestTimer);
|
|
5261
|
+
this.manifestTimer = null;
|
|
5262
|
+
}
|
|
5263
|
+
if (this.taskCompletedListener) {
|
|
5264
|
+
this.coordinator.off("task.completed", this.taskCompletedListener);
|
|
5265
|
+
this.taskCompletedListener = null;
|
|
5266
|
+
}
|
|
4694
5267
|
await this.coordinator.stopAll();
|
|
4695
5268
|
for (const b of this.subagentBridges.values()) {
|
|
4696
|
-
await b.stop().catch(() =>
|
|
5269
|
+
await b.stop().catch((err) => this.logShutdownError("subagent_bridge_stop", err));
|
|
4697
5270
|
}
|
|
4698
5271
|
this.subagentBridges.clear();
|
|
4699
|
-
await this.bridge.stop().catch(() =>
|
|
4700
|
-
if (this.manifestPath)
|
|
5272
|
+
await this.bridge.stop().catch((err) => this.logShutdownError("director_bridge_stop", err));
|
|
5273
|
+
if (this.manifestPath)
|
|
5274
|
+
await this.writeManifest().catch((err) => this.logShutdownError("manifest_write", err));
|
|
5275
|
+
if (this.stateCheckpoint) {
|
|
5276
|
+
this.stateCheckpoint.setUsage(this.usage.snapshot());
|
|
5277
|
+
await this.stateCheckpoint.flush().catch((err) => this.logShutdownError("state_checkpoint_flush", err));
|
|
5278
|
+
}
|
|
5279
|
+
}
|
|
5280
|
+
/**
|
|
5281
|
+
* Funnel for shutdown-phase errors. We can't throw — `shutdown()` is
|
|
5282
|
+
* called from process-exit paths where an uncaught throw would lose
|
|
5283
|
+
* the manifest write that comes after. But we MUST NOT silently
|
|
5284
|
+
* swallow either — a persistent bridge-close failure would otherwise
|
|
5285
|
+
* mask a real bug. `process.emitWarning` is the right tier:
|
|
5286
|
+
* surfaces on stderr by default, lets the host plug a warning
|
|
5287
|
+
* listener for structured collection, and never affects exit code.
|
|
5288
|
+
*/
|
|
5289
|
+
logShutdownError(phase, err) {
|
|
5290
|
+
const detail = err instanceof Error ? err.message : String(err);
|
|
5291
|
+
process.emitWarning(
|
|
5292
|
+
`Director shutdown phase "${phase}" failed: ${detail}`,
|
|
5293
|
+
"DirectorShutdownWarning"
|
|
5294
|
+
);
|
|
4701
5295
|
}
|
|
4702
5296
|
/**
|
|
4703
5297
|
* Hand a task to the coordinator. Returns the assigned task id so
|
|
@@ -4711,6 +5305,23 @@ var Director = class {
|
|
|
4711
5305
|
if (entry) entry.taskIds.push(taskWithId.id);
|
|
4712
5306
|
}
|
|
4713
5307
|
await this.coordinator.assign(taskWithId);
|
|
5308
|
+
this.taskDescriptions.set(taskWithId.id, taskWithId.description);
|
|
5309
|
+
if (taskWithId.subagentId) this.taskOwners.set(taskWithId.id, taskWithId.subagentId);
|
|
5310
|
+
const assignedAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
5311
|
+
this.stateCheckpoint?.recordTaskAssigned({
|
|
5312
|
+
taskId: taskWithId.id,
|
|
5313
|
+
subagentId: taskWithId.subagentId,
|
|
5314
|
+
description: taskWithId.description,
|
|
5315
|
+
status: "running",
|
|
5316
|
+
assignedAt
|
|
5317
|
+
});
|
|
5318
|
+
void this.appendSessionEvent({
|
|
5319
|
+
type: "task_created",
|
|
5320
|
+
ts: assignedAt,
|
|
5321
|
+
taskId: taskWithId.id,
|
|
5322
|
+
title: taskWithId.description
|
|
5323
|
+
});
|
|
5324
|
+
this.scheduleManifest();
|
|
4714
5325
|
return taskWithId.id;
|
|
4715
5326
|
}
|
|
4716
5327
|
/**
|
|
@@ -4770,6 +5381,23 @@ var Director = class {
|
|
|
4770
5381
|
snapshot() {
|
|
4771
5382
|
return this.usage.snapshot();
|
|
4772
5383
|
}
|
|
5384
|
+
/**
|
|
5385
|
+
* Look up provider/model metadata for a spawned subagent. Returns
|
|
5386
|
+
* undefined when the subagent id is unknown (not yet spawned, or
|
|
5387
|
+
* already torn down). Callers — notably the TUI fleet panel — use
|
|
5388
|
+
* this to render human-readable provider/model tags next to each
|
|
5389
|
+
* subagent row without reaching into private state.
|
|
5390
|
+
*/
|
|
5391
|
+
getSubagentMeta(id) {
|
|
5392
|
+
const usage = this.subagentMeta.get(id);
|
|
5393
|
+
const manifest = this.manifestEntries.get(id);
|
|
5394
|
+
if (!usage && !manifest) return void 0;
|
|
5395
|
+
return {
|
|
5396
|
+
provider: usage?.provider ?? manifest?.provider,
|
|
5397
|
+
model: usage?.model ?? manifest?.model,
|
|
5398
|
+
name: manifest?.name
|
|
5399
|
+
};
|
|
5400
|
+
}
|
|
4773
5401
|
/**
|
|
4774
5402
|
* Compose the leader/director-agent system prompt: fleet preamble +
|
|
4775
5403
|
* (optional) roster summary + user base prompt. Pass the result to your
|
|
@@ -5069,12 +5697,260 @@ function makeFleetUsageTool(director) {
|
|
|
5069
5697
|
}
|
|
5070
5698
|
};
|
|
5071
5699
|
}
|
|
5700
|
+
function createDelegateTool(opts) {
|
|
5701
|
+
const defaultTimeoutMs = opts.defaultTimeoutMs ?? 4 * 60 * 60 * 1e3;
|
|
5702
|
+
const rosterIds = opts.roster ? Object.keys(opts.roster) : [];
|
|
5703
|
+
const inputSchema = {
|
|
5704
|
+
type: "object",
|
|
5705
|
+
properties: {
|
|
5706
|
+
task: {
|
|
5707
|
+
type: "string",
|
|
5708
|
+
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."
|
|
5709
|
+
},
|
|
5710
|
+
role: {
|
|
5711
|
+
type: "string",
|
|
5712
|
+
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.",
|
|
5713
|
+
enum: rosterIds.length > 0 ? rosterIds : void 0
|
|
5714
|
+
},
|
|
5715
|
+
name: {
|
|
5716
|
+
type: "string",
|
|
5717
|
+
description: "Display name for the subagent when not using a roster role. Required when `role` is omitted."
|
|
5718
|
+
},
|
|
5719
|
+
provider: {
|
|
5720
|
+
type: "string",
|
|
5721
|
+
description: 'Provider id (e.g. "anthropic", "openai"). Defaults to the host provider when omitted.'
|
|
5722
|
+
},
|
|
5723
|
+
model: {
|
|
5724
|
+
type: "string",
|
|
5725
|
+
description: "Model id within the provider. Defaults to the host model when omitted."
|
|
5726
|
+
},
|
|
5727
|
+
systemPromptOverride: {
|
|
5728
|
+
type: "string",
|
|
5729
|
+
description: "Optional extra prompt text appended to the role baseline."
|
|
5730
|
+
},
|
|
5731
|
+
timeoutMs: {
|
|
5732
|
+
type: "number",
|
|
5733
|
+
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.`
|
|
5734
|
+
},
|
|
5735
|
+
maxIterations: {
|
|
5736
|
+
type: "number",
|
|
5737
|
+
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)."
|
|
5738
|
+
},
|
|
5739
|
+
maxToolCalls: {
|
|
5740
|
+
type: "number",
|
|
5741
|
+
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)."
|
|
5742
|
+
}
|
|
5743
|
+
},
|
|
5744
|
+
required: ["task"]
|
|
5745
|
+
};
|
|
5746
|
+
return {
|
|
5747
|
+
name: "delegate",
|
|
5748
|
+
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.",
|
|
5749
|
+
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.",
|
|
5750
|
+
permission: "auto",
|
|
5751
|
+
mutating: false,
|
|
5752
|
+
inputSchema,
|
|
5753
|
+
async execute(input) {
|
|
5754
|
+
const i = input ?? {};
|
|
5755
|
+
if (typeof i.task !== "string" || !i.task.trim()) {
|
|
5756
|
+
return { ok: false, error: "`task` is required." };
|
|
5757
|
+
}
|
|
5758
|
+
let director = await opts.host.ensureDirector();
|
|
5759
|
+
if (!director) {
|
|
5760
|
+
director = await opts.host.promoteToDirector();
|
|
5761
|
+
}
|
|
5762
|
+
if (!director) {
|
|
5763
|
+
const reason = opts.host.getPromotionBlockReason?.();
|
|
5764
|
+
return {
|
|
5765
|
+
ok: false,
|
|
5766
|
+
error: reason ?? "Director could not be activated \u2014 multi-agent host already running in legacy non-director mode. Restart with `--director` for fleet support."
|
|
5767
|
+
};
|
|
5768
|
+
}
|
|
5769
|
+
const timeoutMs = i.timeoutMs ?? defaultTimeoutMs;
|
|
5770
|
+
let cfg;
|
|
5771
|
+
if (i.role) {
|
|
5772
|
+
const base = opts.roster?.[i.role];
|
|
5773
|
+
if (!base) {
|
|
5774
|
+
return {
|
|
5775
|
+
ok: false,
|
|
5776
|
+
error: `Unknown role "${i.role}". Available: ${rosterIds.join(", ") || "(no roster configured)"}.`
|
|
5777
|
+
};
|
|
5778
|
+
}
|
|
5779
|
+
cfg = { ...base };
|
|
5780
|
+
if (i.systemPromptOverride) cfg.systemPromptOverride = i.systemPromptOverride;
|
|
5781
|
+
if (i.provider) cfg.provider = i.provider;
|
|
5782
|
+
if (i.model) cfg.model = i.model;
|
|
5783
|
+
} else {
|
|
5784
|
+
if (!i.name) {
|
|
5785
|
+
return {
|
|
5786
|
+
ok: false,
|
|
5787
|
+
error: "Either `role` (from the roster) or `name` is required."
|
|
5788
|
+
};
|
|
5789
|
+
}
|
|
5790
|
+
cfg = {
|
|
5791
|
+
name: i.name,
|
|
5792
|
+
provider: i.provider,
|
|
5793
|
+
model: i.model,
|
|
5794
|
+
systemPromptOverride: i.systemPromptOverride
|
|
5795
|
+
};
|
|
5796
|
+
}
|
|
5797
|
+
if (typeof i.maxIterations === "number" && i.maxIterations > 0) {
|
|
5798
|
+
cfg.maxIterations = i.maxIterations;
|
|
5799
|
+
}
|
|
5800
|
+
if (typeof i.maxToolCalls === "number" && i.maxToolCalls > 0) {
|
|
5801
|
+
cfg.maxToolCalls = i.maxToolCalls;
|
|
5802
|
+
}
|
|
5803
|
+
const SUBAGENT_TIMEOUT_BUFFER_MS = 3e4;
|
|
5804
|
+
const desiredSubTimeout = Math.max(3e4, timeoutMs - SUBAGENT_TIMEOUT_BUFFER_MS);
|
|
5805
|
+
if (!cfg.timeoutMs || cfg.timeoutMs > desiredSubTimeout) {
|
|
5806
|
+
cfg.timeoutMs = desiredSubTimeout;
|
|
5807
|
+
}
|
|
5808
|
+
try {
|
|
5809
|
+
const subagentId = await director.spawn(cfg);
|
|
5810
|
+
const taskId = await director.assign({
|
|
5811
|
+
id: "",
|
|
5812
|
+
description: i.task,
|
|
5813
|
+
subagentId
|
|
5814
|
+
});
|
|
5815
|
+
const result = await Promise.race([
|
|
5816
|
+
director.awaitTasks([taskId]).then((r) => r[0]),
|
|
5817
|
+
new Promise(
|
|
5818
|
+
(resolve2) => setTimeout(() => resolve2({ __timeout: true }), timeoutMs)
|
|
5819
|
+
)
|
|
5820
|
+
]);
|
|
5821
|
+
if ("__timeout" in result) {
|
|
5822
|
+
const partial2 = await readSubagentPartial(opts, subagentId);
|
|
5823
|
+
return {
|
|
5824
|
+
ok: false,
|
|
5825
|
+
stopReason: "host_timeout",
|
|
5826
|
+
error: `Subagent did not finish within ${timeoutMs}ms.`,
|
|
5827
|
+
hint: "Reduce scope of the next delegate, raise timeoutMs, or use spawn_subagent + await_tasks for long-running work.",
|
|
5828
|
+
subagentId,
|
|
5829
|
+
taskId,
|
|
5830
|
+
partial: partial2
|
|
5831
|
+
};
|
|
5832
|
+
}
|
|
5833
|
+
const baseStopReason = result.status === "success" ? "end_turn" : result.status === "timeout" ? "subagent_timeout" : result.status === "stopped" ? "aborted" : "budget_exhausted";
|
|
5834
|
+
const partial = result.status === "success" ? void 0 : await readSubagentPartial(opts, subagentId);
|
|
5835
|
+
const errorKind = result.error?.kind;
|
|
5836
|
+
const retryable = result.error?.retryable;
|
|
5837
|
+
const backoffMs = result.error?.backoffMs;
|
|
5838
|
+
return {
|
|
5839
|
+
ok: result.status === "success",
|
|
5840
|
+
status: result.status,
|
|
5841
|
+
stopReason: baseStopReason,
|
|
5842
|
+
errorKind,
|
|
5843
|
+
retryable,
|
|
5844
|
+
backoffMs,
|
|
5845
|
+
subagentId: result.subagentId,
|
|
5846
|
+
taskId: result.taskId,
|
|
5847
|
+
result: result.result,
|
|
5848
|
+
error: result.error,
|
|
5849
|
+
iterations: result.iterations,
|
|
5850
|
+
toolCalls: result.toolCalls,
|
|
5851
|
+
durationMs: result.durationMs,
|
|
5852
|
+
...partial ? { partial } : {},
|
|
5853
|
+
...hintForKind(errorKind, retryable, backoffMs) ? { hint: hintForKind(errorKind, retryable, backoffMs) } : {}
|
|
5854
|
+
};
|
|
5855
|
+
} catch (err) {
|
|
5856
|
+
return {
|
|
5857
|
+
ok: false,
|
|
5858
|
+
stopReason: "error",
|
|
5859
|
+
error: err instanceof Error ? err.message : String(err)
|
|
5860
|
+
};
|
|
5861
|
+
}
|
|
5862
|
+
}
|
|
5863
|
+
};
|
|
5864
|
+
}
|
|
5865
|
+
function hintForKind(kind, retryable, backoffMs) {
|
|
5866
|
+
if (!kind) return void 0;
|
|
5867
|
+
switch (kind) {
|
|
5868
|
+
case "provider_rate_limit":
|
|
5869
|
+
return `Provider rate-limited. Retry safe after ${backoffMs ?? 5e3}ms backoff. Consider a smaller model or fewer parallel delegates.`;
|
|
5870
|
+
case "provider_5xx":
|
|
5871
|
+
return `Provider server error. Retry safe after ${backoffMs ?? 3e3}ms backoff \u2014 usually transient.`;
|
|
5872
|
+
case "provider_timeout":
|
|
5873
|
+
return "Provider network timeout. Retry safe; reduce input size if it persists.";
|
|
5874
|
+
case "provider_auth":
|
|
5875
|
+
return "Provider rejected credentials. Cannot retry \u2014 fix the API key / config and re-invoke.";
|
|
5876
|
+
case "context_overflow":
|
|
5877
|
+
return "Subagent context exceeded the model limit. Narrow the task, use a larger-context model, or split into multiple delegates.";
|
|
5878
|
+
case "budget_iterations":
|
|
5879
|
+
case "budget_tool_calls":
|
|
5880
|
+
case "budget_tokens":
|
|
5881
|
+
case "budget_cost":
|
|
5882
|
+
return "Subagent exhausted its budget. Raise the matching `max*` field on the next delegate or narrow task scope.";
|
|
5883
|
+
case "budget_timeout":
|
|
5884
|
+
return "Subagent hit its wall-clock budget. Raise `timeoutMs` on the next delegate or split the task.";
|
|
5885
|
+
case "aborted_by_parent":
|
|
5886
|
+
return "Subagent was aborted (user Ctrl+C, parent unwound, or sibling failure cascade). Not retryable until the abort condition is resolved.";
|
|
5887
|
+
case "empty_response":
|
|
5888
|
+
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.";
|
|
5889
|
+
case "tool_failed":
|
|
5890
|
+
return "A tool inside the subagent returned ok:false. Inspect `partial.lastAssistantText` for the agent reasoning, then retry with corrected inputs.";
|
|
5891
|
+
case "bridge_failed":
|
|
5892
|
+
return "Parent-child bridge transport failed. This is rare \u2014 restart the session and retry.";
|
|
5893
|
+
default:
|
|
5894
|
+
return retryable ? "Failure classified as retryable. Try again with the same input." : void 0;
|
|
5895
|
+
}
|
|
5896
|
+
}
|
|
5897
|
+
async function readSubagentPartial(opts, subagentId) {
|
|
5898
|
+
if (!opts.sessionsRoot) return void 0;
|
|
5899
|
+
const candidates = [];
|
|
5900
|
+
if (opts.directorRunId) {
|
|
5901
|
+
candidates.push(path3.join(opts.sessionsRoot, opts.directorRunId, `${subagentId}.jsonl`));
|
|
5902
|
+
} else {
|
|
5903
|
+
try {
|
|
5904
|
+
const runDirs = await fsp.readdir(opts.sessionsRoot);
|
|
5905
|
+
for (const r of runDirs) {
|
|
5906
|
+
candidates.push(path3.join(opts.sessionsRoot, r, `${subagentId}.jsonl`));
|
|
5907
|
+
}
|
|
5908
|
+
} catch {
|
|
5909
|
+
return void 0;
|
|
5910
|
+
}
|
|
5911
|
+
}
|
|
5912
|
+
for (const file of candidates) {
|
|
5913
|
+
let raw;
|
|
5914
|
+
try {
|
|
5915
|
+
raw = await fsp.readFile(file, "utf8");
|
|
5916
|
+
} catch {
|
|
5917
|
+
continue;
|
|
5918
|
+
}
|
|
5919
|
+
const lines = raw.split("\n").filter((l) => l.trim());
|
|
5920
|
+
let lastAssistantText;
|
|
5921
|
+
let lastStopReason;
|
|
5922
|
+
let toolUses = 0;
|
|
5923
|
+
for (const line of lines) {
|
|
5924
|
+
try {
|
|
5925
|
+
const ev = JSON.parse(line);
|
|
5926
|
+
if (ev.type === "tool_use") toolUses += 1;
|
|
5927
|
+
if (ev.type === "llm_response") {
|
|
5928
|
+
if (typeof ev.stopReason === "string") lastStopReason = ev.stopReason;
|
|
5929
|
+
if (Array.isArray(ev.content)) {
|
|
5930
|
+
const txt = ev.content.filter((b) => b.type === "text").map((b) => b.text ?? "").join("\n").trim();
|
|
5931
|
+
if (txt) lastAssistantText = txt;
|
|
5932
|
+
}
|
|
5933
|
+
}
|
|
5934
|
+
} catch {
|
|
5935
|
+
}
|
|
5936
|
+
}
|
|
5937
|
+
return {
|
|
5938
|
+
lastAssistantText,
|
|
5939
|
+
lastStopReason,
|
|
5940
|
+
toolUsesObserved: toolUses,
|
|
5941
|
+
events: lines.length
|
|
5942
|
+
};
|
|
5943
|
+
}
|
|
5944
|
+
return void 0;
|
|
5945
|
+
}
|
|
5072
5946
|
|
|
5073
5947
|
// src/coordination/agent-subagent-runner.ts
|
|
5074
5948
|
function makeAgentSubagentRunner(opts) {
|
|
5075
5949
|
const format = opts.formatTaskInput ?? defaultFormatTaskInput;
|
|
5076
5950
|
return async (task, ctx) => {
|
|
5077
|
-
const
|
|
5951
|
+
const factoryResult = await opts.factory(ctx.config);
|
|
5952
|
+
const { agent, events } = factoryResult;
|
|
5953
|
+
const detachFleet = opts.fleetBus?.attach(ctx.subagentId, events, task.id);
|
|
5078
5954
|
const aborter = new AbortController();
|
|
5079
5955
|
let budgetError = null;
|
|
5080
5956
|
const onBudgetError = (err) => {
|
|
@@ -5088,13 +5964,19 @@ function makeAgentSubagentRunner(opts) {
|
|
|
5088
5964
|
budgetError.message += ` (caused by: ${err.message})`;
|
|
5089
5965
|
}
|
|
5090
5966
|
};
|
|
5967
|
+
let lastToolFailed = null;
|
|
5091
5968
|
const unsub = [];
|
|
5092
5969
|
unsub.push(
|
|
5093
|
-
events.on("tool.
|
|
5970
|
+
events.on("tool.executed", (e) => {
|
|
5094
5971
|
try {
|
|
5095
5972
|
ctx.budget.recordToolCall();
|
|
5096
|
-
} catch (
|
|
5097
|
-
onBudgetError(
|
|
5973
|
+
} catch (eb) {
|
|
5974
|
+
onBudgetError(eb);
|
|
5975
|
+
}
|
|
5976
|
+
if (e.ok === false) {
|
|
5977
|
+
lastToolFailed = e.name;
|
|
5978
|
+
} else if (e.ok === true) {
|
|
5979
|
+
lastToolFailed = null;
|
|
5098
5980
|
}
|
|
5099
5981
|
}),
|
|
5100
5982
|
events.on("provider.response", (e) => {
|
|
@@ -5111,6 +5993,26 @@ function makeAgentSubagentRunner(opts) {
|
|
|
5111
5993
|
} catch (e) {
|
|
5112
5994
|
onBudgetError(e);
|
|
5113
5995
|
}
|
|
5996
|
+
}),
|
|
5997
|
+
// D3: cooperative timeout enforcement DURING a long tool call.
|
|
5998
|
+
// The iteration-loop checkTimeout() only fires between agent
|
|
5999
|
+
// iterations — a single `bash sleep 3600` call would otherwise
|
|
6000
|
+
// park inside one tool execution while the timeout silently
|
|
6001
|
+
// passes, relying solely on the coordinator's hard Promise.race
|
|
6002
|
+
// to interrupt. Tools that emit `tool.progress` (bash chunks,
|
|
6003
|
+
// fetch byte progress, spawn-stream stdout) give us a heartbeat
|
|
6004
|
+
// we can hang the check on. When the budget trips here:
|
|
6005
|
+
// 1. onBudgetError sets budgetError + aborter.abort()
|
|
6006
|
+
// 2. aborter signal propagates to agent.run → tool executor
|
|
6007
|
+
// 3. tool's own signal listener kills the child process
|
|
6008
|
+
// Cheap: O(1) per progress event, and the budget short-circuits
|
|
6009
|
+
// when timeoutMs is unset (most subagents have one set anyway).
|
|
6010
|
+
events.on("tool.progress", () => {
|
|
6011
|
+
try {
|
|
6012
|
+
ctx.budget.checkTimeout();
|
|
6013
|
+
} catch (e) {
|
|
6014
|
+
onBudgetError(e);
|
|
6015
|
+
}
|
|
5114
6016
|
})
|
|
5115
6017
|
);
|
|
5116
6018
|
const onParentAbort = () => aborter.abort();
|
|
@@ -5119,8 +6021,15 @@ function makeAgentSubagentRunner(opts) {
|
|
|
5119
6021
|
try {
|
|
5120
6022
|
result = await agent.run(format(task, ctx.config), { signal: aborter.signal });
|
|
5121
6023
|
} finally {
|
|
6024
|
+
detachFleet?.();
|
|
5122
6025
|
ctx.signal.removeEventListener("abort", onParentAbort);
|
|
5123
6026
|
for (const u of unsub) u();
|
|
6027
|
+
if (factoryResult.dispose) {
|
|
6028
|
+
try {
|
|
6029
|
+
await factoryResult.dispose();
|
|
6030
|
+
} catch {
|
|
6031
|
+
}
|
|
6032
|
+
}
|
|
5124
6033
|
}
|
|
5125
6034
|
if (budgetError) throw budgetError;
|
|
5126
6035
|
if (result.status === "failed") {
|
|
@@ -5133,6 +6042,13 @@ function makeAgentSubagentRunner(opts) {
|
|
|
5133
6042
|
throw new Error("agent exhausted iteration limit");
|
|
5134
6043
|
}
|
|
5135
6044
|
const usage = ctx.budget.usage();
|
|
6045
|
+
const finalText = (result.finalText ?? "").trim();
|
|
6046
|
+
if (finalText.length === 0 && usage.toolCalls === 0) {
|
|
6047
|
+
throw new Error("empty response");
|
|
6048
|
+
}
|
|
6049
|
+
if (finalText.length === 0 && lastToolFailed !== null) {
|
|
6050
|
+
throw new Error(`tool failed: ${lastToolFailed}`);
|
|
6051
|
+
}
|
|
5136
6052
|
return {
|
|
5137
6053
|
result: result.finalText,
|
|
5138
6054
|
iterations: result.iterations,
|
|
@@ -5198,10 +6114,12 @@ Working rules:
|
|
|
5198
6114
|
- Never fabricate numbers \u2014 read the actual logs first
|
|
5199
6115
|
- Always include file:line references for errors
|
|
5200
6116
|
- If sessionPath is missing, ask the director to provide it
|
|
5201
|
-
- Report confidence level: high (>90% accuracy), medium, low
|
|
5202
|
-
|
|
5203
|
-
|
|
5204
|
-
|
|
6117
|
+
- Report confidence level: high (>90% accuracy), medium, low`
|
|
6118
|
+
// No hardcoded budgets — the orchestrator (delegate tool or
|
|
6119
|
+
// spawn_subagent) decides per-task how much room a subagent gets.
|
|
6120
|
+
// A monorepo audit needs hours; a single-file lint check needs
|
|
6121
|
+
// seconds. Pinning a number here forces the orchestrator to fight
|
|
6122
|
+
// the role's default instead of just asking for what it needs.
|
|
5205
6123
|
};
|
|
5206
6124
|
var BUG_HUNTER_AGENT = {
|
|
5207
6125
|
id: "bug-hunter",
|
|
@@ -5241,10 +6159,8 @@ Working rules:
|
|
|
5241
6159
|
- Never scan node_modules \u2014 it's noise
|
|
5242
6160
|
- Always include file:line for every finding
|
|
5243
6161
|
- If >30% of findings are false positives, note the confidence level
|
|
5244
|
-
- Ask director for clarification if paths are ambiguous
|
|
5245
|
-
|
|
5246
|
-
maxToolCalls: 300,
|
|
5247
|
-
timeoutMs: 18e4
|
|
6162
|
+
- Ask director for clarification if paths are ambiguous`
|
|
6163
|
+
// Budgets are set by the orchestrator per task — see fleet.ts header.
|
|
5248
6164
|
};
|
|
5249
6165
|
var REFACTOR_PLANNER_AGENT = {
|
|
5250
6166
|
id: "refactor-planner",
|
|
@@ -5284,10 +6200,8 @@ Working rules:
|
|
|
5284
6200
|
- Always include rollback strategy \u2014 every refactor can fail
|
|
5285
6201
|
- Merge tasks that take <1h into a single phase
|
|
5286
6202
|
- Respect team constraints (reviewer availability, parallelization)
|
|
5287
|
-
- Never plan without analyzing the actual code first
|
|
5288
|
-
|
|
5289
|
-
maxToolCalls: 250,
|
|
5290
|
-
timeoutMs: 15e4
|
|
6203
|
+
- Never plan without analyzing the actual code first`
|
|
6204
|
+
// Budgets are set by the orchestrator per task — see fleet.ts header.
|
|
5291
6205
|
};
|
|
5292
6206
|
var SECURITY_SCANNER_AGENT = {
|
|
5293
6207
|
id: "security-scanner",
|
|
@@ -5335,10 +6249,8 @@ Working rules:
|
|
|
5335
6249
|
- Never scan node_modules \u2014 use npm audit instead
|
|
5336
6250
|
- Always provide remediation steps, not just findings
|
|
5337
6251
|
- Verify regex-based secrets before flagging (false positive risk)
|
|
5338
|
-
- When in doubt, flag as medium rather than ignoring potential issues
|
|
5339
|
-
|
|
5340
|
-
maxToolCalls: 280,
|
|
5341
|
-
timeoutMs: 16e4
|
|
6252
|
+
- When in doubt, flag as medium rather than ignoring potential issues`
|
|
6253
|
+
// Budgets are set by the orchestrator per task — see fleet.ts header.
|
|
5342
6254
|
};
|
|
5343
6255
|
var FLEET_ROSTER = {
|
|
5344
6256
|
"audit-log": AUDIT_LOG_AGENT,
|
|
@@ -6959,7 +7871,7 @@ async function startMetricsServer(opts) {
|
|
|
6959
7871
|
const tls = opts.tls;
|
|
6960
7872
|
const useHttps = !!(tls?.cert && tls?.key);
|
|
6961
7873
|
const host = opts.host ?? "127.0.0.1";
|
|
6962
|
-
const
|
|
7874
|
+
const path15 = opts.path ?? "/metrics";
|
|
6963
7875
|
const healthPath = opts.healthPath ?? "/healthz";
|
|
6964
7876
|
const healthRegistry = opts.healthRegistry;
|
|
6965
7877
|
const listener = (req, res) => {
|
|
@@ -6969,7 +7881,7 @@ async function startMetricsServer(opts) {
|
|
|
6969
7881
|
return;
|
|
6970
7882
|
}
|
|
6971
7883
|
const url = req.url.split("?")[0];
|
|
6972
|
-
if (url ===
|
|
7884
|
+
if (url === path15) {
|
|
6973
7885
|
let body;
|
|
6974
7886
|
try {
|
|
6975
7887
|
body = renderPrometheus(opts.sink.snapshot());
|
|
@@ -7033,7 +7945,7 @@ async function startMetricsServer(opts) {
|
|
|
7033
7945
|
const protocol = useHttps ? "https" : "http";
|
|
7034
7946
|
return {
|
|
7035
7947
|
port: boundPort,
|
|
7036
|
-
url: `${protocol}://${host}:${boundPort}${
|
|
7948
|
+
url: `${protocol}://${host}:${boundPort}${path15}`,
|
|
7037
7949
|
close: () => new Promise((resolve2, reject) => {
|
|
7038
7950
|
server.close((err) => err ? reject(err) : resolve2());
|
|
7039
7951
|
})
|
|
@@ -7579,6 +8491,6 @@ var allServers = () => ({
|
|
|
7579
8491
|
sentinel: { ...sentinelServer(), enabled: false }
|
|
7580
8492
|
});
|
|
7581
8493
|
|
|
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 };
|
|
8494
|
+
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, 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
8495
|
//# sourceMappingURL=index.js.map
|
|
7584
8496
|
//# sourceMappingURL=index.js.map
|