@wrongstack/core 0.31.1 → 0.41.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-subagent-runner-DpZTLdBe.d.ts → agent-subagent-runner-C66vi4Gq.d.ts} +1 -1
- package/dist/{config-BUEGM4JP.d.ts → config-ZRCf7sTu.d.ts} +21 -1
- package/dist/coordination/index.d.ts +7 -7
- package/dist/coordination/index.js +3051 -2947
- package/dist/coordination/index.js.map +1 -1
- package/dist/defaults/index.d.ts +8 -8
- package/dist/defaults/index.js +1435 -1321
- package/dist/defaults/index.js.map +1 -1
- package/dist/execution/index.d.ts +5 -5
- package/dist/extension/index.d.ts +2 -2
- package/dist/{index-ysfO_DlX.d.ts → index-6_csX32J.d.ts} +1 -1
- package/dist/{index-pXJdVLe0.d.ts → index-DkVgH3wC.d.ts} +31 -1
- package/dist/index.d.ts +135 -15
- package/dist/index.js +1236 -931
- package/dist/index.js.map +1 -1
- package/dist/infrastructure/index.d.ts +2 -2
- package/dist/infrastructure/index.js +17 -3
- package/dist/infrastructure/index.js.map +1 -1
- package/dist/kernel/index.d.ts +2 -2
- package/dist/{mcp-servers-BzB3r7_c.d.ts → mcp-servers-DONdo-XM.d.ts} +1 -1
- package/dist/{multi-agent-coordinator-DOXSgtom.d.ts → multi-agent-coordinator-BUsjiRWl.d.ts} +1 -1
- package/dist/{null-fleet-bus-CAQDGsKc.d.ts → null-fleet-bus-FvgHnZah.d.ts} +169 -131
- package/dist/{plan-templates-BZMi-VpU.d.ts → plan-templates-DYCeRCDN.d.ts} +1 -1
- package/dist/sdd/index.d.ts +3 -3
- package/dist/storage/index.d.ts +2 -2
- package/dist/{tool-executor-BAi4WI2d.d.ts → tool-executor-BpK-SWtJ.d.ts} +1 -1
- package/dist/types/index.d.ts +3 -3
- package/dist/types/index.js +17 -3
- package/dist/types/index.js.map +1 -1
- package/dist/utils/index.d.ts +107 -1
- package/dist/utils/index.js +53 -2
- package/dist/utils/index.js.map +1 -1
- package/package.json +1 -1
- package/skills/multi-agent/SKILL.md +57 -1
package/dist/defaults/index.js
CHANGED
|
@@ -101,11 +101,25 @@ var init_atomic_write = __esm({
|
|
|
101
101
|
}
|
|
102
102
|
});
|
|
103
103
|
|
|
104
|
+
// src/utils/term.ts
|
|
105
|
+
var hasStdout = () => typeof process !== "undefined" && !!process.stdout;
|
|
106
|
+
function isStdoutTTY() {
|
|
107
|
+
return hasStdout() && Boolean(process.stdout.isTTY);
|
|
108
|
+
}
|
|
109
|
+
function writeTo(s, stream) {
|
|
110
|
+
if (!stream || typeof stream.write !== "function") return false;
|
|
111
|
+
stream.write(s);
|
|
112
|
+
return true;
|
|
113
|
+
}
|
|
114
|
+
function writeErr(s, stream = process.stderr) {
|
|
115
|
+
return writeTo(s, stream);
|
|
116
|
+
}
|
|
117
|
+
|
|
104
118
|
// src/utils/color.ts
|
|
105
119
|
var isColorTty = () => {
|
|
106
120
|
if (process.env.NO_COLOR) return false;
|
|
107
121
|
if (process.env.FORCE_COLOR) return true;
|
|
108
|
-
return
|
|
122
|
+
return isStdoutTTY();
|
|
109
123
|
};
|
|
110
124
|
var COLOR = isColorTty();
|
|
111
125
|
var wrap = (open3, close) => (s) => COLOR ? `\x1B[${open3}m${s}\x1B[${close}m` : s;
|
|
@@ -202,10 +216,10 @@ var DefaultLogger = class _DefaultLogger {
|
|
|
202
216
|
if (r <= LEVEL_RANK.warn || this.level === "debug" || this.level === "trace") {
|
|
203
217
|
const head = `${color.dim(ts)} ${COLORS[level](level.toUpperCase().padEnd(5))} ${msg}`;
|
|
204
218
|
if (ctx !== void 0) {
|
|
205
|
-
|
|
219
|
+
writeErr(`${head} ${formatCtx(ctx)}
|
|
206
220
|
`);
|
|
207
221
|
} else {
|
|
208
|
-
|
|
222
|
+
writeErr(`${head}
|
|
209
223
|
`);
|
|
210
224
|
}
|
|
211
225
|
}
|
|
@@ -1106,9 +1120,9 @@ var DefaultMemoryStore = class {
|
|
|
1106
1120
|
async readAll() {
|
|
1107
1121
|
const parts = [];
|
|
1108
1122
|
for (const scope of ["project-agents", "project-memory", "user-memory"]) {
|
|
1109
|
-
const
|
|
1110
|
-
if (
|
|
1111
|
-
parts.push(`> \u26A0\uFE0F Memory write error (${labelOf(scope)}): ${
|
|
1123
|
+
const writeErr2 = this.writeErrors.get(scope);
|
|
1124
|
+
if (writeErr2) {
|
|
1125
|
+
parts.push(`> \u26A0\uFE0F Memory write error (${labelOf(scope)}): ${writeErr2.message}`);
|
|
1112
1126
|
}
|
|
1113
1127
|
const body = await this.read(scope);
|
|
1114
1128
|
if (body.trim()) parts.push(`## ${labelOf(scope)}
|
|
@@ -11200,84 +11214,8 @@ function buildGoalPreamble(goal) {
|
|
|
11200
11214
|
"BEGIN.]"
|
|
11201
11215
|
].join("\n");
|
|
11202
11216
|
}
|
|
11203
|
-
|
|
11204
|
-
// src/coordination/director.ts
|
|
11205
11217
|
init_atomic_write();
|
|
11206
11218
|
|
|
11207
|
-
// src/coordination/large-answer-store.ts
|
|
11208
|
-
var LargeAnswerStore = class {
|
|
11209
|
-
/**
|
|
11210
|
-
* Responses above this size (in characters) are stored out-of-context.
|
|
11211
|
-
* Below this, the full answer is returned inline (no overhead).
|
|
11212
|
-
* Default: 2000 chars ≈ 400-600 tokens.
|
|
11213
|
-
*/
|
|
11214
|
-
sizeThreshold;
|
|
11215
|
-
store = /* @__PURE__ */ new Map();
|
|
11216
|
-
constructor(sizeThreshold = 2e3) {
|
|
11217
|
-
this.sizeThreshold = sizeThreshold;
|
|
11218
|
-
}
|
|
11219
|
-
/**
|
|
11220
|
-
* Store a value, returning a summary + key for inline use.
|
|
11221
|
-
* If the value is below sizeThreshold, returns it as-is (no store entry).
|
|
11222
|
-
*/
|
|
11223
|
-
storeAnswer(value) {
|
|
11224
|
-
if (value === void 0 || value === null) {
|
|
11225
|
-
return { summary: String(value), inline: true };
|
|
11226
|
-
}
|
|
11227
|
-
const serialized = typeof value === "string" ? value : JSON.stringify(value);
|
|
11228
|
-
const size = serialized.length;
|
|
11229
|
-
if (size <= this.sizeThreshold) {
|
|
11230
|
-
return { summary: serialized.slice(0, 500), inline: true };
|
|
11231
|
-
}
|
|
11232
|
-
const key = `a-${hashStr(serialized)}`;
|
|
11233
|
-
this.store.set(key, {
|
|
11234
|
-
key,
|
|
11235
|
-
value,
|
|
11236
|
-
size,
|
|
11237
|
-
storedAt: Date.now()
|
|
11238
|
-
});
|
|
11239
|
-
return {
|
|
11240
|
-
key,
|
|
11241
|
-
summary: `[stored: ${size} chars \u2014 use roll_up or ask_result tool to retrieve, key=${key}]`,
|
|
11242
|
-
inline: false
|
|
11243
|
-
};
|
|
11244
|
-
}
|
|
11245
|
-
/**
|
|
11246
|
-
* Retrieve a previously stored answer by its key.
|
|
11247
|
-
* Returns undefined if the key is unknown or the store was cleared.
|
|
11248
|
-
*/
|
|
11249
|
-
retrieveAnswer(key) {
|
|
11250
|
-
return this.store.get(key)?.value;
|
|
11251
|
-
}
|
|
11252
|
-
/**
|
|
11253
|
-
* Check if a key exists in the store.
|
|
11254
|
-
*/
|
|
11255
|
-
hasAnswer(key) {
|
|
11256
|
-
return this.store.has(key);
|
|
11257
|
-
}
|
|
11258
|
-
/** Number of stored entries. */
|
|
11259
|
-
get size() {
|
|
11260
|
-
return this.store.size;
|
|
11261
|
-
}
|
|
11262
|
-
/** Total characters stored. */
|
|
11263
|
-
get totalChars() {
|
|
11264
|
-
let total = 0;
|
|
11265
|
-
for (const e of this.store.values()) total += e.size;
|
|
11266
|
-
return total;
|
|
11267
|
-
}
|
|
11268
|
-
/** Clear all stored entries. Call at the end of a director run. */
|
|
11269
|
-
clear() {
|
|
11270
|
-
this.store.clear();
|
|
11271
|
-
}
|
|
11272
|
-
};
|
|
11273
|
-
function hashStr(s) {
|
|
11274
|
-
let h = 5381;
|
|
11275
|
-
for (let i = 0; i < s.length; i++) {
|
|
11276
|
-
h = h * 33 ^ s.charCodeAt(i);
|
|
11277
|
-
}
|
|
11278
|
-
return (h >>> 0).toString(36);
|
|
11279
|
-
}
|
|
11280
|
-
|
|
11281
11219
|
// src/coordination/in-memory-transport.ts
|
|
11282
11220
|
var InMemoryBridgeTransport = class {
|
|
11283
11221
|
subs = /* @__PURE__ */ new Map();
|
|
@@ -11425,1319 +11363,1452 @@ function createMessage(type, from, payload, to) {
|
|
|
11425
11363
|
priority: "normal"
|
|
11426
11364
|
};
|
|
11427
11365
|
}
|
|
11428
|
-
|
|
11429
|
-
|
|
11430
|
-
var
|
|
11431
|
-
|
|
11432
|
-
|
|
11433
|
-
|
|
11434
|
-
Core fleet tools available to you:
|
|
11435
|
-
- spawn_subagent \u2014 create a worker with a chosen provider / model / role
|
|
11436
|
-
- assign_task \u2014 hand a piece of work to a specific subagent
|
|
11437
|
-
- await_tasks \u2014 block until named task ids complete (parallel-safe)
|
|
11438
|
-
- ask_subagent \u2014 synchronously query a running subagent via the bridge
|
|
11439
|
-
- roll_up \u2014 aggregate finished tasks into a markdown/json summary
|
|
11440
|
-
- terminate_subagent \u2014 abort a stuck worker (use sparingly)
|
|
11441
|
-
- fleet_status \u2014 snapshot of all subagents and pending tasks
|
|
11442
|
-
- fleet_usage \u2014 token + cost breakdown per subagent and total
|
|
11443
|
-
|
|
11444
|
-
Working rules:
|
|
11445
|
-
1. Decompose first. Before spawning, decide which sub-tasks are
|
|
11446
|
-
independent and can run in parallel. Sequential work doesn't need a
|
|
11447
|
-
subagent \u2014 do it yourself.
|
|
11448
|
-
2. Match worker to job. Cheap/fast model for triage, capable model for
|
|
11449
|
-
synthesis. Different providers per sibling is allowed and encouraged.
|
|
11450
|
-
3. Always pair an assign with an await. Don't fire-and-forget; you owe
|
|
11451
|
-
the user a single coherent answer at the end.
|
|
11452
|
-
4. Roll up before deciding. After await_tasks resolves, call roll_up so
|
|
11453
|
-
the results are folded back into your context in a compact form.
|
|
11454
|
-
5. Budget is real. Check fleet_usage periodically. If a subagent is
|
|
11455
|
-
thrashing, terminate it rather than letting cost climb silently.
|
|
11456
|
-
6. Never claim a subagent's work as your own without verifying it. If a
|
|
11457
|
-
result looks wrong, ask_subagent for clarification before passing it
|
|
11458
|
-
to the user.
|
|
11459
|
-
7. Wind down when satisfied. When the results are good enough, call
|
|
11460
|
-
work_complete \u2014 no new subagents will spawn and queued tasks complete
|
|
11461
|
-
as aborted. Running subagents finish naturally. Call terminate_subagent
|
|
11462
|
-
only for ones you need to stop immediately.`;
|
|
11463
|
-
var DEFAULT_SUBAGENT_BASELINE = `You are a subagent operating under a Director. You were spawned to handle
|
|
11464
|
-
a specific slice of a larger plan \u2014 do that slice well and report back.
|
|
11465
|
-
|
|
11466
|
-
Bridge contract:
|
|
11467
|
-
- You have a parent (the Director). You may call \`request\` on the
|
|
11468
|
-
parent bridge to ask a clarifying question. Use this sparingly; the
|
|
11469
|
-
parent is also working.
|
|
11470
|
-
- You MAY NOT request the parent's system prompt, tool list, or other
|
|
11471
|
-
subagents' context. Those are not yours to read.
|
|
11472
|
-
- Your final task output is what the Director sees. Be concise,
|
|
11473
|
-
structured, and self-contained \u2014 assume the Director will paste your
|
|
11474
|
-
output into its own context.`;
|
|
11475
|
-
function composeDirectorPrompt(parts = {}) {
|
|
11476
|
-
const sections = [];
|
|
11477
|
-
const preamble = parts.directorPreamble ?? DEFAULT_DIRECTOR_PREAMBLE;
|
|
11478
|
-
if (preamble && preamble.trim().length > 0) sections.push(preamble.trim());
|
|
11479
|
-
if (parts.rosterSummary && parts.rosterSummary.trim().length > 0) {
|
|
11480
|
-
sections.push(`Available roles you can spawn:
|
|
11481
|
-
${parts.rosterSummary.trim()}`);
|
|
11482
|
-
}
|
|
11483
|
-
if (parts.basePrompt && parts.basePrompt.trim().length > 0) {
|
|
11484
|
-
sections.push(parts.basePrompt.trim());
|
|
11366
|
+
var GLOB_CHARS = /* @__PURE__ */ new Set(["*", "?", "["]);
|
|
11367
|
+
var IS_WINDOWS = process.platform === "win32";
|
|
11368
|
+
var SEP = IS_WINDOWS ? "\\" : "/";
|
|
11369
|
+
function isGlob(p) {
|
|
11370
|
+
for (const c of p) {
|
|
11371
|
+
if (GLOB_CHARS.has(c)) return true;
|
|
11485
11372
|
}
|
|
11486
|
-
return
|
|
11373
|
+
return false;
|
|
11487
11374
|
}
|
|
11488
|
-
function
|
|
11489
|
-
|
|
11490
|
-
|
|
11491
|
-
|
|
11492
|
-
|
|
11493
|
-
|
|
11494
|
-
|
|
11495
|
-
|
|
11496
|
-
|
|
11497
|
-
|
|
11498
|
-
|
|
11499
|
-
|
|
11500
|
-
|
|
11501
|
-
|
|
11502
|
-
|
|
11503
|
-
|
|
11504
|
-
|
|
11505
|
-
|
|
11506
|
-
|
|
11507
|
-
|
|
11508
|
-
|
|
11509
|
-
|
|
11510
|
-
|
|
11375
|
+
function globToRegex(pat) {
|
|
11376
|
+
let i = 0, re = "^";
|
|
11377
|
+
while (i < pat.length) {
|
|
11378
|
+
const c = pat[i];
|
|
11379
|
+
if (c === "*") {
|
|
11380
|
+
if (pat[i + 1] === "*") {
|
|
11381
|
+
re += ".*";
|
|
11382
|
+
i += 2;
|
|
11383
|
+
if (pat[i] === "/") i++;
|
|
11384
|
+
} else {
|
|
11385
|
+
re += "[^/\\\\]*";
|
|
11386
|
+
i++;
|
|
11387
|
+
}
|
|
11388
|
+
} else if (c === "?") {
|
|
11389
|
+
re += "[^/\\\\]";
|
|
11390
|
+
i++;
|
|
11391
|
+
} else if (c === "[") {
|
|
11392
|
+
let cls = "[";
|
|
11393
|
+
i++;
|
|
11394
|
+
if (pat[i] === "!" || pat[i] === "^") {
|
|
11395
|
+
cls += "^";
|
|
11396
|
+
i++;
|
|
11397
|
+
}
|
|
11398
|
+
while (i < pat.length && pat[i] !== "]") {
|
|
11399
|
+
const ch = pat[i] ?? "";
|
|
11400
|
+
if (ch === "\\") cls += "\\\\";
|
|
11401
|
+
else if (ch === "]" || ch === "^") cls += `\\${ch}`;
|
|
11402
|
+
else cls += ch;
|
|
11403
|
+
i++;
|
|
11404
|
+
}
|
|
11405
|
+
cls += "]";
|
|
11406
|
+
re += cls;
|
|
11407
|
+
i++;
|
|
11408
|
+
} else {
|
|
11409
|
+
re += c.replace(/[.+^${}()|\\]/g, "\\$&");
|
|
11410
|
+
i++;
|
|
11411
|
+
}
|
|
11511
11412
|
}
|
|
11512
|
-
return
|
|
11413
|
+
return new RegExp(re + "$");
|
|
11513
11414
|
}
|
|
11514
|
-
function
|
|
11515
|
-
|
|
11516
|
-
|
|
11517
|
-
|
|
11518
|
-
|
|
11519
|
-
const tail = headline ? ` \u2014 ${headline}` : "";
|
|
11520
|
-
lines.push(`- ${roleId}: ${cfg.name}${tag}${tail}`);
|
|
11521
|
-
}
|
|
11522
|
-
return lines.join("\n");
|
|
11415
|
+
function baseDir(pat) {
|
|
11416
|
+
let i = pat.length - 1;
|
|
11417
|
+
while (i >= 0 && !GLOB_CHARS.has(pat[i]) && pat[i] !== SEP && pat[i] !== "/") i--;
|
|
11418
|
+
const cut = i >= 0 ? pat.lastIndexOf(SEP, i) : pat.lastIndexOf("/", i);
|
|
11419
|
+
return cut < 0 ? "." : pat.slice(0, cut);
|
|
11523
11420
|
}
|
|
11524
|
-
|
|
11525
|
-
|
|
11526
|
-
|
|
11527
|
-
|
|
11528
|
-
|
|
11529
|
-
|
|
11530
|
-
|
|
11531
|
-
|
|
11532
|
-
|
|
11533
|
-
|
|
11534
|
-
|
|
11535
|
-
|
|
11536
|
-
*
|
|
11537
|
-
* Returns a disposer that detaches every subscription; call on
|
|
11538
|
-
* subagent teardown so the listeners don't outlive the run.
|
|
11539
|
-
*/
|
|
11540
|
-
attach(subagentId, bus, taskId) {
|
|
11541
|
-
const off = bus.onAny((type, payload) => {
|
|
11542
|
-
if (type.startsWith("subagent.")) return;
|
|
11543
|
-
this.emit({ subagentId, taskId, ts: Date.now(), type, payload });
|
|
11544
|
-
});
|
|
11545
|
-
return () => {
|
|
11546
|
-
off();
|
|
11547
|
-
};
|
|
11548
|
-
}
|
|
11549
|
-
/** Subscribe to every event from one subagent. */
|
|
11550
|
-
subscribe(subagentId, handler) {
|
|
11551
|
-
let set = this.byId.get(subagentId);
|
|
11552
|
-
if (!set) {
|
|
11553
|
-
set = /* @__PURE__ */ new Set();
|
|
11554
|
-
this.byId.set(subagentId, set);
|
|
11555
|
-
}
|
|
11556
|
-
set.add(handler);
|
|
11557
|
-
return () => {
|
|
11558
|
-
set.delete(handler);
|
|
11559
|
-
};
|
|
11560
|
-
}
|
|
11561
|
-
/** Subscribe to one event type across all subagents. */
|
|
11562
|
-
filter(type, handler) {
|
|
11563
|
-
let set = this.byType.get(type);
|
|
11564
|
-
if (!set) {
|
|
11565
|
-
set = /* @__PURE__ */ new Set();
|
|
11566
|
-
this.byType.set(type, set);
|
|
11421
|
+
async function expandGlob(pattern) {
|
|
11422
|
+
if (!isGlob(pattern)) return [pattern];
|
|
11423
|
+
const results = /* @__PURE__ */ new Set();
|
|
11424
|
+
const abs = isAbsolute(pattern);
|
|
11425
|
+
const base = abs ? baseDir(pattern) : baseDir(pattern);
|
|
11426
|
+
const relPat = base === "." ? pattern : pattern.slice(base.length + 1);
|
|
11427
|
+
async function walk4(dir, pat) {
|
|
11428
|
+
let entries;
|
|
11429
|
+
try {
|
|
11430
|
+
entries = await fsp.readdir(dir);
|
|
11431
|
+
} catch {
|
|
11432
|
+
return;
|
|
11567
11433
|
}
|
|
11568
|
-
|
|
11569
|
-
|
|
11570
|
-
|
|
11571
|
-
|
|
11572
|
-
|
|
11573
|
-
|
|
11574
|
-
|
|
11575
|
-
|
|
11576
|
-
|
|
11577
|
-
|
|
11578
|
-
}
|
|
11579
|
-
|
|
11580
|
-
|
|
11581
|
-
|
|
11582
|
-
|
|
11583
|
-
for (const
|
|
11434
|
+
const firstGlob = pat.search(/[*?[\[]/);
|
|
11435
|
+
if (firstGlob < 0) {
|
|
11436
|
+
const re = globToRegex(pat);
|
|
11437
|
+
for (const e of entries) {
|
|
11438
|
+
if (re.test(e)) {
|
|
11439
|
+
const full = `${dir}${SEP}${e}`;
|
|
11440
|
+
results.add(abs ? resolve(full) : full);
|
|
11441
|
+
}
|
|
11442
|
+
}
|
|
11443
|
+
return;
|
|
11444
|
+
}
|
|
11445
|
+
const before = pat.slice(0, firstGlob);
|
|
11446
|
+
const rest = pat.slice(firstGlob);
|
|
11447
|
+
if (before.endsWith("**")) {
|
|
11448
|
+
await walk4(dir, rest);
|
|
11449
|
+
for (const e of entries) {
|
|
11450
|
+
const full = `${dir}${SEP}${e}`;
|
|
11584
11451
|
try {
|
|
11585
|
-
|
|
11452
|
+
const stat5 = await fsp.stat(full);
|
|
11453
|
+
if (stat5.isDirectory()) await walk4(full, rest);
|
|
11586
11454
|
} catch {
|
|
11587
11455
|
}
|
|
11588
11456
|
}
|
|
11589
|
-
|
|
11590
|
-
|
|
11591
|
-
for (const
|
|
11457
|
+
} else if (before === "") {
|
|
11458
|
+
const re = globToRegex(rest);
|
|
11459
|
+
for (const e of entries) {
|
|
11460
|
+
if (re.test(e)) {
|
|
11461
|
+
const full = `${dir}${SEP}${e}`;
|
|
11462
|
+
results.add(abs ? resolve(full) : full);
|
|
11463
|
+
}
|
|
11464
|
+
}
|
|
11465
|
+
} else {
|
|
11466
|
+
const seg = before.replace(/[*?[\]]/g, "").replace(/\/$/, "");
|
|
11467
|
+
if (entries.includes(seg)) {
|
|
11468
|
+
const full = `${dir}${SEP}${seg}`;
|
|
11592
11469
|
try {
|
|
11593
|
-
|
|
11470
|
+
const stat5 = await fsp.stat(full);
|
|
11471
|
+
if (stat5.isDirectory()) await walk4(full, rest);
|
|
11594
11472
|
} catch {
|
|
11595
11473
|
}
|
|
11596
11474
|
}
|
|
11597
|
-
for (const h of this.any) {
|
|
11598
|
-
try {
|
|
11599
|
-
h(event);
|
|
11600
|
-
} catch {
|
|
11601
|
-
}
|
|
11602
11475
|
}
|
|
11603
11476
|
}
|
|
11604
|
-
|
|
11605
|
-
|
|
11606
|
-
|
|
11607
|
-
|
|
11608
|
-
|
|
11609
|
-
|
|
11610
|
-
|
|
11611
|
-
|
|
11477
|
+
await walk4(base === "." ? "." : base, relPat);
|
|
11478
|
+
return [...results];
|
|
11479
|
+
}
|
|
11480
|
+
|
|
11481
|
+
// src/coordination/collab-debug.ts
|
|
11482
|
+
var DEFAULT_MAX_TARGET_FILES = 30;
|
|
11483
|
+
var CollabSession = class extends EventEmitter {
|
|
11484
|
+
sessionId;
|
|
11485
|
+
options;
|
|
11486
|
+
snapshot;
|
|
11487
|
+
director;
|
|
11488
|
+
fleetBus;
|
|
11489
|
+
subagentIds = /* @__PURE__ */ new Map();
|
|
11490
|
+
// role → subagentId
|
|
11491
|
+
bugs = /* @__PURE__ */ new Map();
|
|
11492
|
+
plans = /* @__PURE__ */ new Map();
|
|
11493
|
+
evaluations = /* @__PURE__ */ new Map();
|
|
11494
|
+
disposers = new Array();
|
|
11495
|
+
settled = false;
|
|
11496
|
+
timeoutMs;
|
|
11497
|
+
cancelled = false;
|
|
11498
|
+
alerts = [];
|
|
11499
|
+
/** Tracks tool call counts per subagent for progress-based timeout decisions. */
|
|
11500
|
+
progressBySubagent = /* @__PURE__ */ new Map();
|
|
11501
|
+
/** Last tool call count when a timeout warning was handled. */
|
|
11502
|
+
lastTimeoutProgress = /* @__PURE__ */ new Map();
|
|
11503
|
+
/** Session-level timeout timer handle (cleared on cancel or natural completion). */
|
|
11504
|
+
_timeoutTimer;
|
|
11505
|
+
constructor(director, fleetBus, options) {
|
|
11506
|
+
super();
|
|
11507
|
+
this.sessionId = randomUUID();
|
|
11508
|
+
this.options = options;
|
|
11509
|
+
this.director = director;
|
|
11510
|
+
this.fleetBus = fleetBus;
|
|
11511
|
+
this.timeoutMs = options.timeoutMs ?? 10 * 60 * 1e3;
|
|
11512
|
+
if (options.prebuiltSnapshot) {
|
|
11513
|
+
this.snapshot = options.prebuiltSnapshot;
|
|
11514
|
+
} else {
|
|
11515
|
+
this.snapshot = {
|
|
11516
|
+
id: this.sessionId,
|
|
11517
|
+
createdAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
11518
|
+
files: []
|
|
11519
|
+
};
|
|
11520
|
+
}
|
|
11521
|
+
}
|
|
11522
|
+
get id() {
|
|
11523
|
+
return this.sessionId;
|
|
11524
|
+
}
|
|
11525
|
+
getSessionAlerts() {
|
|
11526
|
+
return [...this.alerts];
|
|
11527
|
+
}
|
|
11528
|
+
isCancelled() {
|
|
11529
|
+
return this.cancelled;
|
|
11612
11530
|
}
|
|
11613
|
-
priceLookup;
|
|
11614
|
-
metaLookup;
|
|
11615
|
-
perSubagent = /* @__PURE__ */ new Map();
|
|
11616
|
-
total = { input: 0, output: 0, cacheRead: 0, cacheWrite: 0, cost: 0 };
|
|
11617
|
-
unsub = new Array();
|
|
11618
11531
|
/**
|
|
11619
|
-
*
|
|
11620
|
-
*
|
|
11621
|
-
*
|
|
11622
|
-
* entities that will never emit events again.
|
|
11532
|
+
* Snapshot of role → subagentId map. The Director calls coordinator.stop()
|
|
11533
|
+
* for each agent when cancelling the session, using this map to enumerate
|
|
11534
|
+
* all three collab agents.
|
|
11623
11535
|
*/
|
|
11624
|
-
|
|
11625
|
-
|
|
11626
|
-
if (!snap) return;
|
|
11627
|
-
this.perSubagent.delete(subagentId);
|
|
11628
|
-
this.total.input -= snap.input;
|
|
11629
|
-
this.total.output -= snap.output;
|
|
11630
|
-
this.total.cacheRead -= snap.cacheRead;
|
|
11631
|
-
this.total.cacheWrite -= snap.cacheWrite;
|
|
11632
|
-
this.total.cost -= snap.cost;
|
|
11633
|
-
}
|
|
11634
|
-
/** Disposes all fleet-bus subscriptions. Call when the aggregator is no longer needed. */
|
|
11635
|
-
dispose() {
|
|
11636
|
-
for (const off of this.unsub) off();
|
|
11637
|
-
this.unsub.length = 0;
|
|
11536
|
+
getSubagentIds() {
|
|
11537
|
+
return new Map(this.subagentIds);
|
|
11638
11538
|
}
|
|
11639
|
-
/**
|
|
11640
|
-
|
|
11641
|
-
|
|
11642
|
-
|
|
11643
|
-
|
|
11644
|
-
|
|
11645
|
-
|
|
11646
|
-
}
|
|
11539
|
+
/**
|
|
11540
|
+
* Returns the effective file limit for this session.
|
|
11541
|
+
* Priority: explicit `maxTargetFiles` > dynamic from `contextWindow` > `DEFAULT_MAX_TARGET_FILES`.
|
|
11542
|
+
*/
|
|
11543
|
+
effectiveFileLimit() {
|
|
11544
|
+
if (this.options.maxTargetFiles !== void 0) {
|
|
11545
|
+
return this.options.maxTargetFiles;
|
|
11546
|
+
}
|
|
11547
|
+
if (this.options.contextWindow !== void 0) {
|
|
11548
|
+
return Math.max(5, Math.floor(this.options.contextWindow * 0.4 / 2e3));
|
|
11549
|
+
}
|
|
11550
|
+
return DEFAULT_MAX_TARGET_FILES;
|
|
11647
11551
|
}
|
|
11648
|
-
|
|
11649
|
-
|
|
11650
|
-
|
|
11651
|
-
|
|
11652
|
-
|
|
11653
|
-
|
|
11654
|
-
|
|
11655
|
-
|
|
11656
|
-
|
|
11657
|
-
|
|
11658
|
-
|
|
11659
|
-
|
|
11660
|
-
|
|
11661
|
-
toolCalls: 0,
|
|
11662
|
-
iterations: 0,
|
|
11663
|
-
startedAt: Date.now(),
|
|
11664
|
-
lastEventAt: Date.now()
|
|
11665
|
-
};
|
|
11666
|
-
this.perSubagent.set(subagentId, snap);
|
|
11552
|
+
async buildSnapshot() {
|
|
11553
|
+
if (this.snapshot.files.length > 0) return this.snapshot;
|
|
11554
|
+
const allFiles = [];
|
|
11555
|
+
for (const pattern of this.options.targetPaths) {
|
|
11556
|
+
const expanded = await expandGlob(pattern);
|
|
11557
|
+
allFiles.push(...expanded);
|
|
11558
|
+
}
|
|
11559
|
+
const limit = this.effectiveFileLimit();
|
|
11560
|
+
if (allFiles.length > limit) {
|
|
11561
|
+
const hint = this.options.contextWindow ? `contextWindow=${this.options.contextWindow} \u2192 calculated limit=${limit}` : `default limit=${DEFAULT_MAX_TARGET_FILES}`;
|
|
11562
|
+
throw new Error(
|
|
11563
|
+
`[collab_debug] Target has ${allFiles.length} files, which exceeds the limit (${hint}). Narrow the target or pass maxTargetFiles / contextWindow to override. For large codebases, run package-by-package or module-by-module sessions instead of targeting the entire repo.`
|
|
11564
|
+
);
|
|
11667
11565
|
}
|
|
11668
|
-
|
|
11566
|
+
for (const filePath of allFiles) {
|
|
11567
|
+
try {
|
|
11568
|
+
const content = await fsp.readFile(filePath, "utf8");
|
|
11569
|
+
const ext = filePath.split(".").pop() ?? "";
|
|
11570
|
+
const language = ext === "ts" || ext === "tsx" ? "typescript" : ext === "js" || ext === "jsx" ? "javascript" : ext === "md" ? "markdown" : ext === "json" ? "json" : void 0;
|
|
11571
|
+
this.snapshot.files.push({ path: filePath, content, language });
|
|
11572
|
+
} catch {
|
|
11573
|
+
this.snapshot.files.push({ path: filePath, content: "", language: void 0 });
|
|
11574
|
+
}
|
|
11575
|
+
}
|
|
11576
|
+
return this.snapshot;
|
|
11669
11577
|
}
|
|
11670
|
-
|
|
11671
|
-
|
|
11672
|
-
|
|
11673
|
-
|
|
11674
|
-
|
|
11675
|
-
|
|
11676
|
-
|
|
11677
|
-
|
|
11678
|
-
|
|
11679
|
-
|
|
11680
|
-
|
|
11681
|
-
this.total.cacheRead += usage.cacheRead ?? 0;
|
|
11682
|
-
this.total.cacheWrite += usage.cacheWrite ?? 0;
|
|
11683
|
-
const price = this.priceLookup?.(e.subagentId, snap.provider, snap.model);
|
|
11684
|
-
if (price) {
|
|
11685
|
-
const delta = (usage.input ?? 0) / 1e6 * (price.input ?? 0) + (usage.output ?? 0) / 1e6 * (price.output ?? 0) + (usage.cacheRead ?? 0) / 1e6 * (price.cacheRead ?? 0) + (usage.cacheWrite ?? 0) / 1e6 * (price.cacheWrite ?? 0);
|
|
11686
|
-
snap.cost += delta;
|
|
11687
|
-
this.total.cost += delta;
|
|
11578
|
+
/**
|
|
11579
|
+
* Cancel the session. Emits director.cancel_collab on the FleetBus so all
|
|
11580
|
+
* collab agents finish early. The session-level timeout timer is also cleared.
|
|
11581
|
+
* Safe to call multiple times (idempotent after first call).
|
|
11582
|
+
*/
|
|
11583
|
+
cancel(reason = "Director cancelled collab session") {
|
|
11584
|
+
if (this.settled) return;
|
|
11585
|
+
this.cancelled = true;
|
|
11586
|
+
if (this._timeoutTimer) {
|
|
11587
|
+
clearTimeout(this._timeoutTimer);
|
|
11588
|
+
this._timeoutTimer = void 0;
|
|
11688
11589
|
}
|
|
11689
|
-
|
|
11590
|
+
this.fleetBus.emit({
|
|
11591
|
+
subagentId: this.director.id,
|
|
11592
|
+
ts: Date.now(),
|
|
11593
|
+
type: "director.cancel_collab",
|
|
11594
|
+
payload: { sessionId: this.sessionId, reason, cancelledAt: (/* @__PURE__ */ new Date()).toISOString() }
|
|
11595
|
+
});
|
|
11596
|
+
this.fleetBus.emit({
|
|
11597
|
+
subagentId: this.director.id,
|
|
11598
|
+
ts: Date.now(),
|
|
11599
|
+
type: "collab.cancelled",
|
|
11600
|
+
payload: { sessionId: this.sessionId, reason }
|
|
11601
|
+
});
|
|
11690
11602
|
}
|
|
11691
|
-
|
|
11692
|
-
|
|
11693
|
-
|
|
11694
|
-
|
|
11695
|
-
|
|
11696
|
-
|
|
11697
|
-
|
|
11698
|
-
|
|
11699
|
-
|
|
11700
|
-
|
|
11701
|
-
|
|
11702
|
-
|
|
11703
|
-
|
|
11704
|
-
|
|
11705
|
-
|
|
11706
|
-
|
|
11707
|
-
|
|
11708
|
-
|
|
11709
|
-
|
|
11710
|
-
|
|
11711
|
-
|
|
11712
|
-
|
|
11713
|
-
|
|
11714
|
-
|
|
11715
|
-
|
|
11716
|
-
|
|
11717
|
-
|
|
11718
|
-
|
|
11719
|
-
|
|
11720
|
-
|
|
11721
|
-
|
|
11722
|
-
|
|
11723
|
-
|
|
11724
|
-
usageHint: "Pass `role` (matches the roster), `description` (smart dispatch to best agent), or `name` + `provider`/`model`. Returns `{ subagentId }`.",
|
|
11725
|
-
permission: "auto",
|
|
11726
|
-
mutating: false,
|
|
11727
|
-
inputSchema,
|
|
11728
|
-
async execute(input) {
|
|
11729
|
-
const i = input ?? {};
|
|
11730
|
-
const role = typeof i.role === "string" ? i.role : void 0;
|
|
11731
|
-
const description = typeof i.description === "string" ? i.description : void 0;
|
|
11732
|
-
let cfg;
|
|
11733
|
-
if (role && roster) {
|
|
11734
|
-
const base = roster[role];
|
|
11735
|
-
if (!base) return { error: `unknown role "${role}". roster has: ${Object.keys(roster).join(", ")}` };
|
|
11736
|
-
cfg = instantiateRosterConfig(role, base);
|
|
11737
|
-
} else if (description && !role) {
|
|
11738
|
-
const dispatchResult = await dispatchAgent(description, {
|
|
11739
|
-
classifier: director.dispatchClassifier,
|
|
11740
|
-
catalog: roster
|
|
11741
|
-
});
|
|
11742
|
-
const dispatchRole = dispatchResult.role;
|
|
11743
|
-
if (roster?.[dispatchRole]) {
|
|
11744
|
-
cfg = instantiateRosterConfig(dispatchRole, roster[dispatchRole]);
|
|
11745
|
-
} else {
|
|
11746
|
-
const def = dispatchResult.definition;
|
|
11747
|
-
cfg = {
|
|
11748
|
-
name: def.config.name ?? dispatchRole,
|
|
11749
|
-
role: dispatchRole,
|
|
11750
|
-
provider: def.config.provider,
|
|
11751
|
-
model: def.config.model
|
|
11752
|
-
};
|
|
11753
|
-
}
|
|
11754
|
-
}
|
|
11755
|
-
cfg ??= { name: i.name ?? "subagent" };
|
|
11756
|
-
if (typeof i.name === "string") cfg.name = i.name;
|
|
11757
|
-
if (typeof i.provider === "string") cfg.provider = i.provider;
|
|
11758
|
-
if (typeof i.model === "string") cfg.model = i.model;
|
|
11759
|
-
if (typeof i.systemPromptOverride === "string") cfg.systemPromptOverride = i.systemPromptOverride;
|
|
11760
|
-
if (typeof i.maxIterations === "number") cfg.maxIterations = i.maxIterations;
|
|
11761
|
-
if (typeof i.maxToolCalls === "number") cfg.maxToolCalls = i.maxToolCalls;
|
|
11762
|
-
if (typeof i.maxCostUsd === "number") cfg.maxCostUsd = i.maxCostUsd;
|
|
11763
|
-
if (typeof i.timeoutMs === "number") cfg.timeoutMs = i.timeoutMs;
|
|
11764
|
-
if (typeof i.idleTimeoutMs === "number") cfg.idleTimeoutMs = i.idleTimeoutMs;
|
|
11765
|
-
if (typeof i.maxTokens === "number") cfg.maxTokens = i.maxTokens;
|
|
11766
|
-
try {
|
|
11767
|
-
const subagentId = await director.spawn(cfg);
|
|
11768
|
-
return { subagentId, provider: cfg.provider, model: cfg.model, name: cfg.name, role: cfg.role };
|
|
11769
|
-
} catch (err) {
|
|
11770
|
-
if (err instanceof FleetSpawnBudgetError) {
|
|
11771
|
-
return { error: err.message, kind: err.kind, limit: err.limit, observed: err.observed };
|
|
11772
|
-
}
|
|
11773
|
-
if (err instanceof FleetCostCapError) {
|
|
11774
|
-
return { error: err.message, kind: err.kind, limit: err.limit, observed: err.observed };
|
|
11775
|
-
}
|
|
11776
|
-
return { error: err instanceof Error ? err.message : String(err) };
|
|
11603
|
+
async start() {
|
|
11604
|
+
if (this.settled) throw new Error("session already settled");
|
|
11605
|
+
this.settled = true;
|
|
11606
|
+
await this.buildSnapshot();
|
|
11607
|
+
this.wireFleetBus();
|
|
11608
|
+
const [bugHunterId, refactorPlannerId, criticId] = await Promise.all([
|
|
11609
|
+
this.spawnAgent("bug-hunter", this.buildBugHunterTask()),
|
|
11610
|
+
this.spawnAgent("refactor-planner", this.buildRefactorPlannerTask()),
|
|
11611
|
+
this.spawnAgent("critic", this.buildCriticTask())
|
|
11612
|
+
]);
|
|
11613
|
+
this.subagentIds.set("bug-hunter", bugHunterId);
|
|
11614
|
+
this.subagentIds.set("refactor-planner", refactorPlannerId);
|
|
11615
|
+
this.subagentIds.set("critic", criticId);
|
|
11616
|
+
const timeout = new Promise((_, reject) => {
|
|
11617
|
+
this._timeoutTimer = setTimeout(() => {
|
|
11618
|
+
this.cancel("Session-level timeout reached");
|
|
11619
|
+
reject(new Error(`CollabSession timed out after ${this.timeoutMs}ms`));
|
|
11620
|
+
}, this.timeoutMs);
|
|
11621
|
+
});
|
|
11622
|
+
let results = null;
|
|
11623
|
+
try {
|
|
11624
|
+
results = await Promise.race([
|
|
11625
|
+
Promise.all([
|
|
11626
|
+
this.director.awaitTasks([bugHunterId]),
|
|
11627
|
+
this.director.awaitTasks([refactorPlannerId]),
|
|
11628
|
+
this.director.awaitTasks([criticId])
|
|
11629
|
+
]),
|
|
11630
|
+
timeout
|
|
11631
|
+
]);
|
|
11632
|
+
} catch (err) {
|
|
11633
|
+
if (this._timeoutTimer) {
|
|
11634
|
+
clearTimeout(this._timeoutTimer);
|
|
11635
|
+
this._timeoutTimer = void 0;
|
|
11777
11636
|
}
|
|
11637
|
+
this.cleanup();
|
|
11638
|
+
const error = err instanceof Error ? err : new Error(String(err));
|
|
11639
|
+
this.emit("session.error", error);
|
|
11640
|
+
throw error;
|
|
11778
11641
|
}
|
|
11779
|
-
|
|
11780
|
-
|
|
11781
|
-
function instantiateRosterConfig(role, base) {
|
|
11782
|
-
return {
|
|
11783
|
-
...base,
|
|
11784
|
-
// Roster entries are templates. A director may spawn several
|
|
11785
|
-
// workers with the same role, so never reuse the template id.
|
|
11786
|
-
id: `${role}-${randomUUID().slice(0, 8)}`
|
|
11787
|
-
};
|
|
11788
|
-
}
|
|
11789
|
-
function makeAssignTool(director) {
|
|
11790
|
-
const inputSchema = {
|
|
11791
|
-
type: "object",
|
|
11792
|
-
properties: {
|
|
11793
|
-
subagentId: { type: "string", minLength: 1, description: "Target subagent id. Required." },
|
|
11794
|
-
description: { type: "string", minLength: 1, description: "The task in natural language \u2014 what you want this subagent to do." },
|
|
11795
|
-
maxToolCalls: { type: "number", minimum: 1, description: "Optional per-task tool-call budget override." },
|
|
11796
|
-
timeoutMs: { type: "number", minimum: 1, description: "Optional per-task timeout in ms." }
|
|
11797
|
-
},
|
|
11798
|
-
required: ["subagentId", "description"]
|
|
11799
|
-
};
|
|
11800
|
-
return {
|
|
11801
|
-
name: "assign_task",
|
|
11802
|
-
description: "Hand a task to a previously spawned subagent. Returns the task id.",
|
|
11803
|
-
permission: "auto",
|
|
11804
|
-
mutating: false,
|
|
11805
|
-
inputSchema,
|
|
11806
|
-
async execute(input) {
|
|
11807
|
-
const i = input;
|
|
11808
|
-
const task = { id: randomUUID(), description: i.description, subagentId: i.subagentId, maxToolCalls: i.maxToolCalls, timeoutMs: i.timeoutMs };
|
|
11809
|
-
const taskId = await director.assign(task);
|
|
11810
|
-
return { taskId, subagentId: i.subagentId };
|
|
11642
|
+
for (const result of results.flat()) {
|
|
11643
|
+
await this.parseAndEmit(result);
|
|
11811
11644
|
}
|
|
11812
|
-
|
|
11813
|
-
|
|
11814
|
-
|
|
11815
|
-
|
|
11816
|
-
|
|
11817
|
-
|
|
11818
|
-
|
|
11819
|
-
|
|
11820
|
-
|
|
11821
|
-
|
|
11822
|
-
|
|
11823
|
-
|
|
11824
|
-
|
|
11645
|
+
const report = this.assembleReport();
|
|
11646
|
+
this.cleanup();
|
|
11647
|
+
this.emit("session.done", report);
|
|
11648
|
+
return report;
|
|
11649
|
+
}
|
|
11650
|
+
async parseAndEmit(result) {
|
|
11651
|
+
if (result.status !== "success" || result.result == null) return;
|
|
11652
|
+
const text = typeof result.result === "string" ? result.result : JSON.stringify(result.result);
|
|
11653
|
+
for (const obj of this.extractJsonObjects(text)) {
|
|
11654
|
+
const type = "finding" in obj ? "bug.found" : "plan" in obj ? "refactor.plan" : "evaluation" in obj ? "critic.evaluation" : null;
|
|
11655
|
+
if (!type) continue;
|
|
11656
|
+
this.fleetBus.emit({
|
|
11657
|
+
subagentId: result.subagentId,
|
|
11658
|
+
taskId: result.taskId,
|
|
11659
|
+
ts: Date.now(),
|
|
11660
|
+
type,
|
|
11661
|
+
payload: obj
|
|
11662
|
+
});
|
|
11825
11663
|
}
|
|
11826
|
-
}
|
|
11827
|
-
|
|
11828
|
-
|
|
11829
|
-
|
|
11830
|
-
|
|
11831
|
-
|
|
11832
|
-
|
|
11833
|
-
|
|
11834
|
-
|
|
11835
|
-
|
|
11836
|
-
|
|
11837
|
-
|
|
11838
|
-
|
|
11839
|
-
|
|
11840
|
-
}
|
|
11841
|
-
|
|
11842
|
-
|
|
11843
|
-
|
|
11844
|
-
|
|
11845
|
-
|
|
11846
|
-
|
|
11847
|
-
|
|
11848
|
-
if (
|
|
11849
|
-
|
|
11664
|
+
}
|
|
11665
|
+
extractJsonObjects(text) {
|
|
11666
|
+
const objects = [];
|
|
11667
|
+
let depth = 0;
|
|
11668
|
+
let start = -1;
|
|
11669
|
+
let inString = false;
|
|
11670
|
+
let escaped = false;
|
|
11671
|
+
for (let i = 0; i < text.length; i++) {
|
|
11672
|
+
const ch = text[i];
|
|
11673
|
+
if (inString) {
|
|
11674
|
+
if (escaped) escaped = false;
|
|
11675
|
+
else if (ch === "\\") escaped = true;
|
|
11676
|
+
else if (ch === '"') inString = false;
|
|
11677
|
+
continue;
|
|
11678
|
+
}
|
|
11679
|
+
if (ch === '"') {
|
|
11680
|
+
inString = true;
|
|
11681
|
+
} else if (ch === "{") {
|
|
11682
|
+
if (depth === 0) start = i;
|
|
11683
|
+
depth++;
|
|
11684
|
+
} else if (ch === "}" && depth > 0) {
|
|
11685
|
+
depth--;
|
|
11686
|
+
if (depth === 0 && start >= 0) {
|
|
11687
|
+
const candidate = text.slice(start, i + 1);
|
|
11688
|
+
try {
|
|
11689
|
+
const parsed = JSON.parse(candidate);
|
|
11690
|
+
if (parsed && typeof parsed === "object" && !Array.isArray(parsed)) {
|
|
11691
|
+
objects.push(parsed);
|
|
11692
|
+
}
|
|
11693
|
+
} catch {
|
|
11694
|
+
}
|
|
11695
|
+
start = -1;
|
|
11850
11696
|
}
|
|
11851
|
-
return {
|
|
11852
|
-
ok: true,
|
|
11853
|
-
answer: stored.summary,
|
|
11854
|
-
_answerKey: stored.key,
|
|
11855
|
-
_hint: "Response was large and stored. Use ask_result with the key to retrieve it."
|
|
11856
|
-
};
|
|
11857
|
-
} catch (err) {
|
|
11858
|
-
return { ok: false, error: err instanceof Error ? err.message : String(err) };
|
|
11859
11697
|
}
|
|
11860
11698
|
}
|
|
11861
|
-
|
|
11862
|
-
}
|
|
11863
|
-
|
|
11864
|
-
|
|
11865
|
-
|
|
11866
|
-
description: "Retrieve a large `ask_subagent` response that was stored out-of-context (>2K chars). Returns the full stored value.",
|
|
11867
|
-
permission: "auto",
|
|
11868
|
-
mutating: false,
|
|
11869
|
-
inputSchema: {
|
|
11870
|
-
type: "object",
|
|
11871
|
-
properties: {
|
|
11872
|
-
key: {
|
|
11873
|
-
type: "string",
|
|
11874
|
-
minLength: 1,
|
|
11875
|
-
description: "The `_answerKey` returned by `ask_subagent` for a large response."
|
|
11876
|
-
}
|
|
11877
|
-
},
|
|
11878
|
-
required: ["key"]
|
|
11879
|
-
},
|
|
11880
|
-
async execute(input) {
|
|
11881
|
-
const i = input;
|
|
11882
|
-
const value = director.largeAnswerStore.retrieveAnswer(i.key);
|
|
11883
|
-
if (value === void 0) {
|
|
11884
|
-
return { ok: false, error: `No stored answer found for key "${i.key}" \u2014 it may have been cleared or the key is invalid.` };
|
|
11885
|
-
}
|
|
11886
|
-
return { ok: true, value };
|
|
11887
|
-
}
|
|
11888
|
-
};
|
|
11889
|
-
}
|
|
11890
|
-
function makeRollUpTool(director) {
|
|
11891
|
-
return {
|
|
11892
|
-
name: "roll_up",
|
|
11893
|
-
description: "Aggregate completed task results into a single formatted summary.",
|
|
11894
|
-
permission: "auto",
|
|
11895
|
-
mutating: false,
|
|
11896
|
-
inputSchema: {
|
|
11897
|
-
type: "object",
|
|
11898
|
-
properties: {
|
|
11899
|
-
taskIds: { type: "array", items: { type: "string" }, description: "Completed task ids to aggregate." },
|
|
11900
|
-
style: { type: "string", enum: ["markdown", "json"], description: "Output flavor \u2014 markdown (default) or json." }
|
|
11901
|
-
},
|
|
11902
|
-
required: ["taskIds"]
|
|
11903
|
-
},
|
|
11904
|
-
async execute(input) {
|
|
11905
|
-
const i = input;
|
|
11906
|
-
const summary = director.rollUp(i.taskIds, i.style ?? "markdown");
|
|
11907
|
-
return { summary, count: i.taskIds.length };
|
|
11908
|
-
}
|
|
11909
|
-
};
|
|
11910
|
-
}
|
|
11911
|
-
function makeTerminateTool(director) {
|
|
11912
|
-
return {
|
|
11913
|
-
name: "terminate_subagent",
|
|
11914
|
-
description: 'Forcibly abort a subagent. The subagent finishes its current iteration then exits with status "stopped".',
|
|
11915
|
-
permission: "auto",
|
|
11916
|
-
mutating: true,
|
|
11917
|
-
inputSchema: { type: "object", properties: { subagentId: { type: "string", description: "Subagent to abort." } }, required: ["subagentId"] },
|
|
11918
|
-
async execute(input) {
|
|
11919
|
-
const i = input;
|
|
11920
|
-
await director.terminate(i.subagentId);
|
|
11921
|
-
return { ok: true };
|
|
11922
|
-
}
|
|
11923
|
-
};
|
|
11924
|
-
}
|
|
11925
|
-
function makeTerminateAllTool(director) {
|
|
11926
|
-
return {
|
|
11927
|
-
name: "terminate_all",
|
|
11928
|
-
description: 'Forcibly stop every subagent in the fleet and drain the pending task queue. In-flight tasks are terminated mid-execution; pending tasks receive "aborted_by_parent" completion immediately. Use this when the fleet is wedged, looping, or you need a clean slate. Compare: work_complete stops spawning but lets running agents finish naturally.',
|
|
11929
|
-
permission: "auto",
|
|
11930
|
-
mutating: true,
|
|
11931
|
-
inputSchema: { type: "object", properties: {}, required: [] },
|
|
11932
|
-
async execute() {
|
|
11933
|
-
await director.terminateAll();
|
|
11934
|
-
return { ok: true, message: `Fleet shutdown complete \u2014 all subagents stopped, pending tasks drained.` };
|
|
11699
|
+
return objects;
|
|
11700
|
+
}
|
|
11701
|
+
budgetForRole(role) {
|
|
11702
|
+
if (this.options.budgetOverrides?.[role]) {
|
|
11703
|
+
return this.options.budgetOverrides[role];
|
|
11935
11704
|
}
|
|
11936
|
-
|
|
11937
|
-
}
|
|
11938
|
-
|
|
11939
|
-
|
|
11940
|
-
|
|
11941
|
-
|
|
11942
|
-
|
|
11943
|
-
|
|
11944
|
-
|
|
11945
|
-
|
|
11946
|
-
|
|
11947
|
-
|
|
11948
|
-
|
|
11949
|
-
|
|
11950
|
-
|
|
11951
|
-
|
|
11952
|
-
|
|
11953
|
-
|
|
11954
|
-
|
|
11705
|
+
const defaults = {
|
|
11706
|
+
"bug-hunter": { maxIterations: 2e3, maxToolCalls: 5e3, timeoutMs: 10 * 60 * 1e3 },
|
|
11707
|
+
"refactor-planner": { maxIterations: 1500, maxToolCalls: 4e3, timeoutMs: 8 * 60 * 1e3 },
|
|
11708
|
+
"critic": { maxIterations: 1e3, maxToolCalls: 3e3, timeoutMs: 6 * 60 * 1e3 }
|
|
11709
|
+
};
|
|
11710
|
+
return defaults[role] ?? { maxIterations: 1500, maxToolCalls: 4e3, timeoutMs: 8 * 60 * 1e3 };
|
|
11711
|
+
}
|
|
11712
|
+
async spawnAgent(role, taskBrief) {
|
|
11713
|
+
const budget = this.budgetForRole(role);
|
|
11714
|
+
const cfg = {
|
|
11715
|
+
id: `${role}-${this.sessionId}`,
|
|
11716
|
+
name: role,
|
|
11717
|
+
role,
|
|
11718
|
+
tools: ["fleet_emit", "fleet_status", "read", "grep", "glob", "bash", "write"],
|
|
11719
|
+
maxIterations: budget.maxIterations,
|
|
11720
|
+
maxToolCalls: budget.maxToolCalls,
|
|
11721
|
+
timeoutMs: budget.timeoutMs
|
|
11722
|
+
};
|
|
11723
|
+
const subagentId = await this.director.spawn(cfg);
|
|
11724
|
+
await this.director.assign({ id: randomUUID(), subagentId, description: taskBrief });
|
|
11725
|
+
return subagentId;
|
|
11726
|
+
}
|
|
11727
|
+
buildBugHunterTask() {
|
|
11728
|
+
const scratchpad = this.director.sharedScratchpadPath ?? "/tmp";
|
|
11729
|
+
const fileContents = this.snapshot.files.map((f) => `=== ${f.path} ===
|
|
11730
|
+
${f.content}`).join("\n\n");
|
|
11731
|
+
return `You are BugHunter. Scan the following files for bugs and code smells.
|
|
11732
|
+
|
|
11733
|
+
Target files:
|
|
11734
|
+
${fileContents}
|
|
11735
|
+
|
|
11736
|
+
For each bug found, emit it using the fleet_emit tool immediately:
|
|
11737
|
+
{ "type": "bug.found", "payload": { "finding": { "id": "<uuid>", "type": "<pattern>", "severity": "<critical|high|medium|low>", "location": { "file": "<path>", "line": <n> }, "description": "<explain>", "suggestedFix": "<optional>" } } }
|
|
11738
|
+
|
|
11739
|
+
After scanning all files, write your full markdown bug report to:
|
|
11740
|
+
${scratchpad}/bug-hunter-report-${this.sessionId}.md
|
|
11741
|
+
|
|
11742
|
+
Important: emit each finding as soon as you find it. Do not batch or wait until the end.`;
|
|
11743
|
+
}
|
|
11744
|
+
buildRefactorPlannerTask() {
|
|
11745
|
+
const scratchpad = this.director.sharedScratchpadPath ?? "/tmp";
|
|
11746
|
+
const bugHunterReportPath = `${scratchpad}/bug-hunter-report-${this.sessionId}.md`;
|
|
11747
|
+
const fileContents = this.snapshot.files.map((f) => `=== ${f.path} ===
|
|
11748
|
+
${f.content}`).join("\n\n");
|
|
11749
|
+
return `You are RefactorPlanner. Plan refactorings for the following files.
|
|
11750
|
+
|
|
11751
|
+
Target files:
|
|
11752
|
+
${fileContents}
|
|
11753
|
+
|
|
11754
|
+
Read the BugHunter report at: ${bugHunterReportPath}
|
|
11755
|
+
|
|
11756
|
+
For each bug you can address, emit a refactor plan using fleet_emit:
|
|
11757
|
+
{ "type": "refactor.plan", "payload": { "plan": { "id": "<uuid>", "basedOnBugIds": ["<bug-id>"], "phases": [{ "number": 1, "title": "<phase>", "tasks": ["<task>"], "risk": "<low|medium|high>" }], "riskScore": "<low|medium|high>", "estimatedChangeCount": <n>, "rollbackStrategy": "<text>" } } }
|
|
11758
|
+
|
|
11759
|
+
Also write your full markdown plan to:
|
|
11760
|
+
${scratchpad}/refactor-plan-${this.sessionId}.md
|
|
11761
|
+
|
|
11762
|
+
Emit each plan immediately. Do not wait until planning is complete.`;
|
|
11763
|
+
}
|
|
11764
|
+
buildCriticTask() {
|
|
11765
|
+
const scratchpad = this.director.sharedScratchpadPath ?? "/tmp";
|
|
11766
|
+
const bugHunterReportPath = `${scratchpad}/bug-hunter-report-${this.sessionId}.md`;
|
|
11767
|
+
const refactorPlanPath = `${scratchpad}/refactor-plan-${this.sessionId}.md`;
|
|
11768
|
+
const fileContents = this.snapshot.files.map((f) => `=== ${f.path} ===
|
|
11769
|
+
${f.content}`).join("\n\n");
|
|
11770
|
+
return `You are Critic. Evaluate bug findings and refactor plans.
|
|
11771
|
+
|
|
11772
|
+
Target files:
|
|
11773
|
+
${fileContents}
|
|
11774
|
+
|
|
11775
|
+
Read the BugHunter report at: ${bugHunterReportPath}
|
|
11776
|
+
Read the RefactorPlanner report at: ${refactorPlanPath}
|
|
11777
|
+
|
|
11778
|
+
For each bug and refactor plan, emit your evaluation using fleet_emit:
|
|
11779
|
+
{ "type": "critic.evaluation", "payload": { "evaluation": { "id": "<uuid>", "subjectType": "<bug_finding|refactor_plan>", "subjectId": "<id>", "score": <0-10>, "verdict": "<approve|needs_revision|reject>", "strengths": ["<strength>"], "weaknesses": ["<weakness>"], "concerns": [{ "description": "<concern>", "severity": "<blocking|advisory>" }] } } }
|
|
11780
|
+
|
|
11781
|
+
After all evaluations, write your markdown report to:
|
|
11782
|
+
${scratchpad}/critic-report-${this.sessionId}.md
|
|
11783
|
+
|
|
11784
|
+
Emit each evaluation immediately. Do not wait until you have read all reports.`;
|
|
11785
|
+
}
|
|
11786
|
+
wireFleetBus() {
|
|
11787
|
+
const dTool = this.fleetBus.filter("tool.executed", (e) => {
|
|
11788
|
+
this.progressBySubagent.set(e.subagentId, (this.progressBySubagent.get(e.subagentId) ?? 0) + 1);
|
|
11789
|
+
});
|
|
11790
|
+
this.disposers.push(dTool);
|
|
11791
|
+
const dBudget = this.fleetBus.filter("budget.threshold_reached", (e) => {
|
|
11792
|
+
const payload = e.payload;
|
|
11793
|
+
const role = this.roleFromSubagentId(e.subagentId);
|
|
11794
|
+
if (!role) return;
|
|
11795
|
+
const btwNotes = this.director.getLeaderBtwNotes();
|
|
11796
|
+
const alert = {
|
|
11797
|
+
sessionId: this.sessionId,
|
|
11798
|
+
subagentId: e.subagentId,
|
|
11799
|
+
role,
|
|
11800
|
+
level: "warning" /* WARNING */,
|
|
11801
|
+
message: `${role} hit ${payload.kind} soft limit (${payload.used}/${payload.limit})`,
|
|
11802
|
+
budgetKind: payload.kind,
|
|
11803
|
+
elapsedMs: payload.timeoutMs,
|
|
11804
|
+
limit: payload.limit,
|
|
11805
|
+
btwNotes
|
|
11955
11806
|
};
|
|
11956
|
-
|
|
11957
|
-
|
|
11958
|
-
|
|
11959
|
-
|
|
11960
|
-
|
|
11961
|
-
|
|
11962
|
-
|
|
11963
|
-
|
|
11964
|
-
|
|
11965
|
-
|
|
11966
|
-
|
|
11967
|
-
return director.snapshot();
|
|
11968
|
-
}
|
|
11969
|
-
};
|
|
11970
|
-
}
|
|
11971
|
-
function makeFleetSessionTool(director) {
|
|
11972
|
-
return {
|
|
11973
|
-
name: "fleet_session",
|
|
11974
|
-
description: "Read a subagent's JSONL transcript and extract its last assistant text, stop reason, and tool-use count. Use this to see what a running or timed-out subagent actually produced.",
|
|
11975
|
-
permission: "auto",
|
|
11976
|
-
mutating: false,
|
|
11977
|
-
inputSchema: {
|
|
11978
|
-
type: "object",
|
|
11979
|
-
properties: {
|
|
11980
|
-
subagentId: { type: "string", description: "Subagent id to read the transcript of." },
|
|
11981
|
-
tail: { type: "number", description: "Number of trailing JSONL lines to return. Omit for the full transcript." }
|
|
11982
|
-
},
|
|
11983
|
-
required: ["subagentId"]
|
|
11984
|
-
},
|
|
11985
|
-
async execute(input) {
|
|
11986
|
-
const i = input;
|
|
11987
|
-
const result = await director.readSession(i.subagentId, i.tail);
|
|
11988
|
-
if (!result) {
|
|
11989
|
-
return {
|
|
11990
|
-
error: `fleet_session: transcript unavailable for "${i.subagentId}". Is sessionsRoot configured?`
|
|
11991
|
-
};
|
|
11807
|
+
this.alerts.push(alert);
|
|
11808
|
+
this.fleetBus.emit({
|
|
11809
|
+
subagentId: e.subagentId,
|
|
11810
|
+
ts: Date.now(),
|
|
11811
|
+
type: "collab.warning",
|
|
11812
|
+
payload: alert
|
|
11813
|
+
});
|
|
11814
|
+
const decision = this.options.onBudgetWarning?.(alert) ?? "ignore";
|
|
11815
|
+
if (decision === "cancel") {
|
|
11816
|
+
this.cancel(`Director cancelled: ${role} ${payload.kind} threshold`);
|
|
11817
|
+
return;
|
|
11992
11818
|
}
|
|
11993
|
-
|
|
11994
|
-
|
|
11995
|
-
|
|
11996
|
-
|
|
11997
|
-
|
|
11998
|
-
|
|
11999
|
-
name: "fleet_health",
|
|
12000
|
-
description: "Per-subagent health report: budget pressure (pct of limits consumed), last activity timestamp, and current status. Use to decide whether to assign more work to a subagent or spawn a fresh one.",
|
|
12001
|
-
permission: "auto",
|
|
12002
|
-
mutating: false,
|
|
12003
|
-
inputSchema: { type: "object", properties: {}, required: [] },
|
|
12004
|
-
async execute() {
|
|
12005
|
-
const status = director.status();
|
|
12006
|
-
const snapshot = director.snapshot();
|
|
12007
|
-
const subagents = status.subagents ?? [];
|
|
12008
|
-
const perSubagent = snapshot.perSubagent ?? {};
|
|
12009
|
-
return {
|
|
12010
|
-
subagents: subagents.map((s) => {
|
|
12011
|
-
const usage = perSubagent[s.id];
|
|
12012
|
-
return {
|
|
12013
|
-
id: s.id,
|
|
12014
|
-
status: s.status,
|
|
12015
|
-
lastEventAt: usage?.lastEventAt,
|
|
12016
|
-
budgetPressure: {
|
|
12017
|
-
iterations: usage?.iterations,
|
|
12018
|
-
toolCalls: usage?.toolCalls,
|
|
12019
|
-
costUsd: usage?.cost
|
|
12020
|
-
}
|
|
12021
|
-
};
|
|
12022
|
-
})
|
|
12023
|
-
};
|
|
12024
|
-
}
|
|
12025
|
-
};
|
|
12026
|
-
}
|
|
12027
|
-
function makeCollabDebugTool(director) {
|
|
12028
|
-
return {
|
|
12029
|
-
name: "collab_debug",
|
|
12030
|
-
description: "Start a collaborative debugging session: BugHunter, RefactorPlanner, and Critic run in parallel on the same target files. BugHunter finds bugs and emits bug.found events. RefactorPlanner listens for bug.found and emits refactor.plan events. Critic evaluates both and emits critic.evaluation events. Returns a structured report with overall verdict (approve / needs_revision / reject).",
|
|
12031
|
-
permission: "auto",
|
|
12032
|
-
mutating: false,
|
|
12033
|
-
inputSchema: {
|
|
12034
|
-
type: "object",
|
|
12035
|
-
properties: {
|
|
12036
|
-
targetPaths: {
|
|
12037
|
-
type: "array",
|
|
12038
|
-
items: { type: "string" },
|
|
12039
|
-
description: "File paths / glob patterns to scan for bugs."
|
|
12040
|
-
},
|
|
12041
|
-
timeoutMs: {
|
|
12042
|
-
type: "number",
|
|
12043
|
-
minimum: 1,
|
|
12044
|
-
description: "Timeout in ms. Default: 600000 (10 minutes)."
|
|
11819
|
+
if (payload.kind === "timeout" || payload.kind === "idle_timeout") {
|
|
11820
|
+
const progress = this.progressBySubagent.get(e.subagentId) ?? 0;
|
|
11821
|
+
const lastProgress = this.lastTimeoutProgress.get(e.subagentId) ?? -1;
|
|
11822
|
+
if (progress <= lastProgress) {
|
|
11823
|
+
payload.deny();
|
|
11824
|
+
return;
|
|
12045
11825
|
}
|
|
12046
|
-
|
|
12047
|
-
|
|
12048
|
-
|
|
12049
|
-
|
|
12050
|
-
|
|
12051
|
-
|
|
12052
|
-
|
|
11826
|
+
this.lastTimeoutProgress.set(e.subagentId, progress);
|
|
11827
|
+
const newLimit = Math.min(Math.ceil((payload.timeoutMs ?? payload.limit) * 2), 24 * 60 * 6e4);
|
|
11828
|
+
setImmediate(() => {
|
|
11829
|
+
payload.extend({ timeoutMs: newLimit });
|
|
11830
|
+
});
|
|
11831
|
+
return;
|
|
11832
|
+
}
|
|
11833
|
+
if (decision === "extend") {
|
|
11834
|
+
setImmediate(() => {
|
|
11835
|
+
const base = Math.max(payload.limit, payload.used);
|
|
11836
|
+
const extra = {};
|
|
11837
|
+
switch (payload.kind) {
|
|
11838
|
+
case "iterations":
|
|
11839
|
+
extra.maxIterations = Math.min(Math.ceil(base * 1.5), 5e4);
|
|
11840
|
+
break;
|
|
11841
|
+
case "tool_calls":
|
|
11842
|
+
extra.maxToolCalls = Math.min(Math.ceil(base * 1.5), 1e5);
|
|
11843
|
+
break;
|
|
11844
|
+
case "tokens":
|
|
11845
|
+
extra.maxTokens = Math.min(Math.ceil(base * 1.5), 5e6);
|
|
11846
|
+
break;
|
|
11847
|
+
case "cost":
|
|
11848
|
+
extra.maxCostUsd = Math.min(base * 1.5, 100);
|
|
11849
|
+
break;
|
|
11850
|
+
}
|
|
11851
|
+
payload.extend(extra);
|
|
11852
|
+
});
|
|
11853
|
+
return;
|
|
11854
|
+
}
|
|
11855
|
+
if (payload.kind !== "timeout") {
|
|
11856
|
+
setImmediate(() => {
|
|
11857
|
+
const base = Math.max(payload.limit, payload.used);
|
|
11858
|
+
const extra = {};
|
|
11859
|
+
switch (payload.kind) {
|
|
11860
|
+
case "iterations":
|
|
11861
|
+
extra.maxIterations = Math.min(Math.ceil(base * 1.25), 5e4);
|
|
11862
|
+
break;
|
|
11863
|
+
case "tool_calls":
|
|
11864
|
+
extra.maxToolCalls = Math.min(Math.ceil(base * 1.25), 1e5);
|
|
11865
|
+
break;
|
|
11866
|
+
case "tokens":
|
|
11867
|
+
extra.maxTokens = Math.min(Math.ceil(base * 1.25), 5e6);
|
|
11868
|
+
break;
|
|
11869
|
+
case "cost":
|
|
11870
|
+
extra.maxCostUsd = Math.min(base * 1.25, 100);
|
|
11871
|
+
break;
|
|
11872
|
+
}
|
|
11873
|
+
payload.extend(extra);
|
|
11874
|
+
});
|
|
12053
11875
|
}
|
|
12054
|
-
|
|
12055
|
-
|
|
12056
|
-
|
|
12057
|
-
|
|
12058
|
-
|
|
12059
|
-
|
|
12060
|
-
|
|
12061
|
-
|
|
12062
|
-
|
|
12063
|
-
bugCount: report.bugs.length,
|
|
12064
|
-
planCount: report.refactorPlans.length,
|
|
12065
|
-
evaluationCount: report.evaluations.length,
|
|
12066
|
-
summary: report.summary,
|
|
12067
|
-
bugs: report.bugs,
|
|
12068
|
-
refactorPlans: report.refactorPlans,
|
|
12069
|
-
evaluations: report.evaluations
|
|
12070
|
-
};
|
|
12071
|
-
} catch (err) {
|
|
12072
|
-
const msg = err instanceof Error ? err.message : String(err);
|
|
12073
|
-
return { error: "collab_debug failed: " + msg };
|
|
11876
|
+
});
|
|
11877
|
+
this.disposers.push(dBudget);
|
|
11878
|
+
const dCancel = this.fleetBus.filter("director.cancel_collab", (e) => {
|
|
11879
|
+
const payload = e.payload;
|
|
11880
|
+
if (payload.sessionId !== this.sessionId) return;
|
|
11881
|
+
this.cancelled = true;
|
|
11882
|
+
if (this._timeoutTimer) {
|
|
11883
|
+
clearTimeout(this._timeoutTimer);
|
|
11884
|
+
this._timeoutTimer = void 0;
|
|
12074
11885
|
}
|
|
12075
|
-
|
|
12076
|
-
|
|
12077
|
-
}
|
|
12078
|
-
function makeFleetEmitTool(director) {
|
|
12079
|
-
return {
|
|
12080
|
-
name: "fleet_emit",
|
|
12081
|
-
description: "Emit a structured event on the FleetBus. Any subagent can emit any event type; the Director routes it to all listeners. Use it to stream findings, progress updates, or final results to other agents in real time.",
|
|
12082
|
-
permission: "auto",
|
|
12083
|
-
mutating: false,
|
|
12084
|
-
inputSchema: {
|
|
12085
|
-
type: "object",
|
|
12086
|
-
properties: {
|
|
12087
|
-
type: {
|
|
12088
|
-
type: "string",
|
|
12089
|
-
description: "Event type string (e.g. bug.found, refactor.plan, critic.evaluation, progress, result)."
|
|
12090
|
-
},
|
|
12091
|
-
payload: {
|
|
12092
|
-
type: "object",
|
|
12093
|
-
description: "Event payload. Structure depends on event type. Use null if no payload."
|
|
12094
|
-
}
|
|
12095
|
-
},
|
|
12096
|
-
required: ["type"]
|
|
12097
|
-
},
|
|
12098
|
-
async execute(input) {
|
|
12099
|
-
const i = input;
|
|
12100
|
-
director.fleet.emit({
|
|
12101
|
-
subagentId: director.id,
|
|
11886
|
+
this.fleetBus.emit({
|
|
11887
|
+
subagentId: this.director.id,
|
|
12102
11888
|
ts: Date.now(),
|
|
12103
|
-
type:
|
|
12104
|
-
payload:
|
|
11889
|
+
type: "collab.cancelled",
|
|
11890
|
+
payload: { sessionId: this.sessionId, reason: payload.reason }
|
|
12105
11891
|
});
|
|
12106
|
-
|
|
12107
|
-
|
|
12108
|
-
|
|
12109
|
-
|
|
12110
|
-
|
|
12111
|
-
|
|
12112
|
-
|
|
12113
|
-
description: "Signal that the director is satisfied with the results and the fleet should wind down. After calling this, spawn_subagent will refuse with a budget error and assign_task will instantly complete any queued tasks as aborted. Running subagents finish naturally. Call terminate_subagent separately to stop specific subagents immediately.",
|
|
12114
|
-
permission: "auto",
|
|
12115
|
-
mutating: false,
|
|
12116
|
-
inputSchema: { type: "object", properties: {}, required: [] },
|
|
12117
|
-
async execute() {
|
|
12118
|
-
director.workComplete();
|
|
12119
|
-
return { ok: true, message: "Fleet wind-down signaled. No new spawns or task dispatches." };
|
|
12120
|
-
}
|
|
12121
|
-
};
|
|
12122
|
-
}
|
|
12123
|
-
var GLOB_CHARS = /* @__PURE__ */ new Set(["*", "?", "["]);
|
|
12124
|
-
var IS_WINDOWS = process.platform === "win32";
|
|
12125
|
-
var SEP = IS_WINDOWS ? "\\" : "/";
|
|
12126
|
-
function isGlob(p) {
|
|
12127
|
-
for (const c of p) {
|
|
12128
|
-
if (GLOB_CHARS.has(c)) return true;
|
|
12129
|
-
}
|
|
12130
|
-
return false;
|
|
12131
|
-
}
|
|
12132
|
-
function globToRegex(pat) {
|
|
12133
|
-
let i = 0, re = "^";
|
|
12134
|
-
while (i < pat.length) {
|
|
12135
|
-
const c = pat[i];
|
|
12136
|
-
if (c === "*") {
|
|
12137
|
-
if (pat[i + 1] === "*") {
|
|
12138
|
-
re += ".*";
|
|
12139
|
-
i += 2;
|
|
12140
|
-
if (pat[i] === "/") i++;
|
|
12141
|
-
} else {
|
|
12142
|
-
re += "[^/\\\\]*";
|
|
12143
|
-
i++;
|
|
11892
|
+
});
|
|
11893
|
+
this.disposers.push(dCancel);
|
|
11894
|
+
const d1 = this.fleetBus.filter("bug.found", (e) => {
|
|
11895
|
+
const payload = e.payload;
|
|
11896
|
+
if (payload?.finding) {
|
|
11897
|
+
this.bugs.set(payload.finding.id, payload.finding);
|
|
11898
|
+
this.emit("bug.found", payload);
|
|
12144
11899
|
}
|
|
12145
|
-
}
|
|
12146
|
-
|
|
12147
|
-
|
|
12148
|
-
|
|
12149
|
-
|
|
12150
|
-
|
|
12151
|
-
|
|
12152
|
-
cls += "^";
|
|
12153
|
-
i++;
|
|
11900
|
+
});
|
|
11901
|
+
this.disposers.push(d1);
|
|
11902
|
+
const d2 = this.fleetBus.filter("refactor.plan", (e) => {
|
|
11903
|
+
const payload = e.payload;
|
|
11904
|
+
if (payload?.plan) {
|
|
11905
|
+
this.plans.set(payload.plan.id, payload.plan);
|
|
11906
|
+
this.emit("refactor.plan", payload);
|
|
12154
11907
|
}
|
|
12155
|
-
|
|
12156
|
-
|
|
12157
|
-
|
|
12158
|
-
|
|
12159
|
-
|
|
12160
|
-
|
|
11908
|
+
});
|
|
11909
|
+
this.disposers.push(d2);
|
|
11910
|
+
const d3 = this.fleetBus.filter("critic.evaluation", (e) => {
|
|
11911
|
+
const payload = e.payload;
|
|
11912
|
+
if (payload?.evaluation) {
|
|
11913
|
+
this.evaluations.set(payload.evaluation.id, payload.evaluation);
|
|
11914
|
+
this.emit("critic.evaluation", payload);
|
|
12161
11915
|
}
|
|
12162
|
-
|
|
12163
|
-
|
|
12164
|
-
i++;
|
|
12165
|
-
} else {
|
|
12166
|
-
re += c.replace(/[.+^${}()|\\]/g, "\\$&");
|
|
12167
|
-
i++;
|
|
12168
|
-
}
|
|
11916
|
+
});
|
|
11917
|
+
this.disposers.push(d3);
|
|
12169
11918
|
}
|
|
12170
|
-
|
|
12171
|
-
|
|
12172
|
-
|
|
12173
|
-
let i = pat.length - 1;
|
|
12174
|
-
while (i >= 0 && !GLOB_CHARS.has(pat[i]) && pat[i] !== SEP && pat[i] !== "/") i--;
|
|
12175
|
-
const cut = i >= 0 ? pat.lastIndexOf(SEP, i) : pat.lastIndexOf("/", i);
|
|
12176
|
-
return cut < 0 ? "." : pat.slice(0, cut);
|
|
12177
|
-
}
|
|
12178
|
-
async function expandGlob(pattern) {
|
|
12179
|
-
if (!isGlob(pattern)) return [pattern];
|
|
12180
|
-
const results = /* @__PURE__ */ new Set();
|
|
12181
|
-
const abs = isAbsolute(pattern);
|
|
12182
|
-
const base = abs ? baseDir(pattern) : baseDir(pattern);
|
|
12183
|
-
const relPat = base === "." ? pattern : pattern.slice(base.length + 1);
|
|
12184
|
-
async function walk4(dir, pat) {
|
|
12185
|
-
let entries;
|
|
12186
|
-
try {
|
|
12187
|
-
entries = await fsp.readdir(dir);
|
|
12188
|
-
} catch {
|
|
12189
|
-
return;
|
|
12190
|
-
}
|
|
12191
|
-
const firstGlob = pat.search(/[*?[\[]/);
|
|
12192
|
-
if (firstGlob < 0) {
|
|
12193
|
-
const re = globToRegex(pat);
|
|
12194
|
-
for (const e of entries) {
|
|
12195
|
-
if (re.test(e)) {
|
|
12196
|
-
const full = `${dir}${SEP}${e}`;
|
|
12197
|
-
results.add(abs ? resolve(full) : full);
|
|
12198
|
-
}
|
|
12199
|
-
}
|
|
12200
|
-
return;
|
|
11919
|
+
roleFromSubagentId(subagentId) {
|
|
11920
|
+
for (const [role, id] of this.subagentIds) {
|
|
11921
|
+
if (id === subagentId) return role;
|
|
12201
11922
|
}
|
|
12202
|
-
const
|
|
12203
|
-
|
|
12204
|
-
|
|
12205
|
-
|
|
12206
|
-
|
|
12207
|
-
|
|
12208
|
-
|
|
12209
|
-
|
|
12210
|
-
|
|
12211
|
-
|
|
12212
|
-
|
|
11923
|
+
const match = subagentId.match(/^(bug-hunter|refactor-planner|critic)/);
|
|
11924
|
+
return match?.[1] ?? null;
|
|
11925
|
+
}
|
|
11926
|
+
assembleReport() {
|
|
11927
|
+
const bugList = Array.from(this.bugs.values());
|
|
11928
|
+
const planList = Array.from(this.plans.values());
|
|
11929
|
+
const evalList = Array.from(this.evaluations.values());
|
|
11930
|
+
let disposition = "completed";
|
|
11931
|
+
if (this.cancelled) disposition = "cancelled";
|
|
11932
|
+
const verdictOrder = {
|
|
11933
|
+
approve: 0,
|
|
11934
|
+
needs_revision: 1,
|
|
11935
|
+
reject: 2
|
|
11936
|
+
};
|
|
11937
|
+
const overallVerdict = evalList.reduce(
|
|
11938
|
+
(worst, eval_) => {
|
|
11939
|
+
const w = verdictOrder[worst];
|
|
11940
|
+
const c = verdictOrder[eval_.verdict];
|
|
11941
|
+
return c > w ? eval_.verdict : worst;
|
|
11942
|
+
},
|
|
11943
|
+
"approve"
|
|
11944
|
+
);
|
|
11945
|
+
const summary = this.buildMarkdownSummary(bugList, planList, evalList, overallVerdict, disposition);
|
|
11946
|
+
return {
|
|
11947
|
+
sessionId: this.sessionId,
|
|
11948
|
+
startedAt: this.snapshot.createdAt,
|
|
11949
|
+
completedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
11950
|
+
targetPaths: this.options.targetPaths,
|
|
11951
|
+
disposition,
|
|
11952
|
+
bugs: bugList,
|
|
11953
|
+
refactorPlans: planList,
|
|
11954
|
+
evaluations: evalList,
|
|
11955
|
+
alerts: [...this.alerts],
|
|
11956
|
+
overallVerdict,
|
|
11957
|
+
summary
|
|
11958
|
+
};
|
|
11959
|
+
}
|
|
11960
|
+
buildMarkdownSummary(bugs, plans, evals, overallVerdict, disposition) {
|
|
11961
|
+
const lines = [
|
|
11962
|
+
`## Collaborative Debugging Report \u2014 ${this.sessionId}`,
|
|
11963
|
+
"",
|
|
11964
|
+
`**Target:** ${this.options.targetPaths.join(", ")}`,
|
|
11965
|
+
`**Disposition:** ${disposition.toUpperCase()}`,
|
|
11966
|
+
`**Overall Verdict:** **${overallVerdict.toUpperCase()}**`,
|
|
11967
|
+
""
|
|
11968
|
+
];
|
|
11969
|
+
if (this.alerts.length > 0) {
|
|
11970
|
+
lines.push("### Alerts", "");
|
|
11971
|
+
for (const alert of this.alerts) {
|
|
11972
|
+
lines.push(`- **[${alert.level.toUpperCase()}]** ${alert.role}: ${alert.message}`);
|
|
12213
11973
|
}
|
|
12214
|
-
|
|
12215
|
-
|
|
12216
|
-
|
|
12217
|
-
|
|
12218
|
-
|
|
12219
|
-
|
|
11974
|
+
lines.push("");
|
|
11975
|
+
}
|
|
11976
|
+
if (bugs.length > 0) {
|
|
11977
|
+
lines.push("### Bugs Found", "");
|
|
11978
|
+
for (const b of bugs) {
|
|
11979
|
+
lines.push(`- **[${b.severity.toUpperCase()}]** \`${b.location.file}:${b.location.line}\` \u2014 ${b.description}`);
|
|
11980
|
+
}
|
|
11981
|
+
lines.push("");
|
|
11982
|
+
}
|
|
11983
|
+
if (plans.length > 0) {
|
|
11984
|
+
lines.push("### Refactor Plans", "");
|
|
11985
|
+
for (const p of plans) {
|
|
11986
|
+
lines.push(`- **Phase plan** (risk: ${p.riskScore}, ~${p.estimatedChangeCount} changes)`);
|
|
11987
|
+
for (const phase of p.phases) {
|
|
11988
|
+
lines.push(` - Phase ${phase.number}: ${phase.title} [${phase.risk}]`);
|
|
12220
11989
|
}
|
|
12221
11990
|
}
|
|
12222
|
-
|
|
12223
|
-
|
|
12224
|
-
|
|
12225
|
-
|
|
12226
|
-
|
|
12227
|
-
|
|
12228
|
-
|
|
12229
|
-
|
|
11991
|
+
lines.push("");
|
|
11992
|
+
}
|
|
11993
|
+
if (evals.length > 0) {
|
|
11994
|
+
lines.push("### Critic Evaluations", "");
|
|
11995
|
+
for (const e of evals) {
|
|
11996
|
+
lines.push(`- [${e.subjectType}] score=${e.score}/10 \u2014 **${e.verdict.toUpperCase()}**`);
|
|
11997
|
+
for (const c of e.concerns) {
|
|
11998
|
+
if (c.severity === "blocking") lines.push(` - ${c.description}`);
|
|
12230
11999
|
}
|
|
12231
12000
|
}
|
|
12001
|
+
lines.push("");
|
|
12232
12002
|
}
|
|
12003
|
+
return lines.join("\n");
|
|
12233
12004
|
}
|
|
12234
|
-
|
|
12235
|
-
|
|
12236
|
-
|
|
12005
|
+
cleanup() {
|
|
12006
|
+
for (const dispose of this.disposers) dispose();
|
|
12007
|
+
this.disposers.length = 0;
|
|
12008
|
+
}
|
|
12009
|
+
};
|
|
12237
12010
|
|
|
12238
|
-
// src/coordination/
|
|
12239
|
-
var
|
|
12240
|
-
|
|
12241
|
-
|
|
12242
|
-
|
|
12243
|
-
|
|
12244
|
-
|
|
12245
|
-
|
|
12246
|
-
|
|
12247
|
-
|
|
12248
|
-
|
|
12249
|
-
|
|
12250
|
-
|
|
12251
|
-
|
|
12252
|
-
|
|
12253
|
-
|
|
12254
|
-
|
|
12255
|
-
|
|
12256
|
-
|
|
12257
|
-
|
|
12258
|
-
|
|
12259
|
-
|
|
12260
|
-
|
|
12261
|
-
|
|
12262
|
-
|
|
12263
|
-
|
|
12264
|
-
|
|
12265
|
-
|
|
12266
|
-
|
|
12267
|
-
|
|
12268
|
-
|
|
12269
|
-
|
|
12270
|
-
|
|
12271
|
-
|
|
12272
|
-
|
|
12273
|
-
|
|
12274
|
-
|
|
12275
|
-
|
|
12276
|
-
|
|
12011
|
+
// src/coordination/director-prompts.ts
|
|
12012
|
+
var DEFAULT_DIRECTOR_PREAMBLE = `You are the Director of a multi-agent fleet. You orchestrate worker
|
|
12013
|
+
subagents by spawning them, assigning tasks, awaiting completions, and
|
|
12014
|
+
rolling up their outputs into your next decision.
|
|
12015
|
+
|
|
12016
|
+
Core fleet tools available to you:
|
|
12017
|
+
- spawn_subagent \u2014 create a worker with a chosen provider / model / role
|
|
12018
|
+
- assign_task \u2014 hand a piece of work to a specific subagent
|
|
12019
|
+
- await_tasks \u2014 block until named task ids complete (parallel-safe)
|
|
12020
|
+
- ask_subagent \u2014 synchronously query a running subagent via the bridge
|
|
12021
|
+
- roll_up \u2014 aggregate finished tasks into a markdown/json summary
|
|
12022
|
+
- terminate_subagent \u2014 abort a stuck worker (use sparingly)
|
|
12023
|
+
- fleet_status \u2014 snapshot of all subagents and pending tasks
|
|
12024
|
+
- fleet_usage \u2014 token + cost breakdown per subagent and total
|
|
12025
|
+
|
|
12026
|
+
Working rules:
|
|
12027
|
+
1. Decompose first. Before spawning, decide which sub-tasks are
|
|
12028
|
+
independent and can run in parallel. Sequential work doesn't need a
|
|
12029
|
+
subagent \u2014 do it yourself.
|
|
12030
|
+
2. Match worker to job. Cheap/fast model for triage, capable model for
|
|
12031
|
+
synthesis. Different providers per sibling is allowed and encouraged.
|
|
12032
|
+
3. Always pair an assign with an await. Don't fire-and-forget; you owe
|
|
12033
|
+
the user a single coherent answer at the end.
|
|
12034
|
+
4. Roll up before deciding. After await_tasks resolves, call roll_up so
|
|
12035
|
+
the results are folded back into your context in a compact form.
|
|
12036
|
+
5. Budget is real. Check fleet_usage periodically. If a subagent is
|
|
12037
|
+
thrashing, terminate it rather than letting cost climb silently.
|
|
12038
|
+
6. Never claim a subagent's work as your own without verifying it. If a
|
|
12039
|
+
result looks wrong, ask_subagent for clarification before passing it
|
|
12040
|
+
to the user.
|
|
12041
|
+
7. Wind down when satisfied. When the results are good enough, call
|
|
12042
|
+
work_complete \u2014 no new subagents will spawn and queued tasks complete
|
|
12043
|
+
as aborted. Running subagents finish naturally. Call terminate_subagent
|
|
12044
|
+
only for ones you need to stop immediately.`;
|
|
12045
|
+
var DEFAULT_SUBAGENT_BASELINE = `You are a subagent operating under a Director. You were spawned to handle
|
|
12046
|
+
a specific slice of a larger plan \u2014 do that slice well and report back.
|
|
12047
|
+
|
|
12048
|
+
Bridge contract:
|
|
12049
|
+
- You have a parent (the Director). You may call \`request\` on the
|
|
12050
|
+
parent bridge to ask a clarifying question. Use this sparingly; the
|
|
12051
|
+
parent is also working.
|
|
12052
|
+
- You MAY NOT request the parent's system prompt, tool list, or other
|
|
12053
|
+
subagents' context. Those are not yours to read.
|
|
12054
|
+
- Your final task output is what the Director sees. Be concise,
|
|
12055
|
+
structured, and self-contained \u2014 assume the Director will paste your
|
|
12056
|
+
output into its own context.`;
|
|
12057
|
+
function composeDirectorPrompt(parts = {}) {
|
|
12058
|
+
const sections = [];
|
|
12059
|
+
const preamble = parts.directorPreamble ?? DEFAULT_DIRECTOR_PREAMBLE;
|
|
12060
|
+
if (preamble && preamble.trim().length > 0) sections.push(preamble.trim());
|
|
12061
|
+
if (parts.rosterSummary && parts.rosterSummary.trim().length > 0) {
|
|
12062
|
+
sections.push(`Available roles you can spawn:
|
|
12063
|
+
${parts.rosterSummary.trim()}`);
|
|
12277
12064
|
}
|
|
12278
|
-
|
|
12279
|
-
|
|
12065
|
+
if (parts.basePrompt && parts.basePrompt.trim().length > 0) {
|
|
12066
|
+
sections.push(parts.basePrompt.trim());
|
|
12280
12067
|
}
|
|
12281
|
-
|
|
12282
|
-
|
|
12068
|
+
return sections.join("\n\n");
|
|
12069
|
+
}
|
|
12070
|
+
function composeSubagentPrompt(parts = {}) {
|
|
12071
|
+
const sections = [];
|
|
12072
|
+
const baseline = parts.baseline ?? DEFAULT_SUBAGENT_BASELINE;
|
|
12073
|
+
if (baseline && baseline.trim().length > 0) sections.push(baseline.trim());
|
|
12074
|
+
if (parts.role && parts.role.trim().length > 0) {
|
|
12075
|
+
sections.push(`Role:
|
|
12076
|
+
${parts.role.trim()}`);
|
|
12283
12077
|
}
|
|
12284
|
-
|
|
12285
|
-
|
|
12078
|
+
if (parts.task && parts.task.trim().length > 0) {
|
|
12079
|
+
sections.push(`Task:
|
|
12080
|
+
${parts.task.trim()}`);
|
|
12286
12081
|
}
|
|
12287
|
-
|
|
12288
|
-
|
|
12289
|
-
|
|
12290
|
-
|
|
12291
|
-
|
|
12292
|
-
|
|
12293
|
-
|
|
12082
|
+
if (parts.sharedScratchpad && parts.sharedScratchpad.trim().length > 0) {
|
|
12083
|
+
sections.push(
|
|
12084
|
+
`Shared notes:
|
|
12085
|
+
A scratchpad shared with the rest of the fleet is mounted at \`${parts.sharedScratchpad.trim()}\`.
|
|
12086
|
+
- Write your final findings as markdown files there (e.g. \`findings.md\`, \`security.md\`).
|
|
12087
|
+
- Before starting, list the directory and read any sibling files relevant to your task \u2014 they may already contain context you can build on.
|
|
12088
|
+
- Use stable filenames (one file per concern); overwrite instead of appending so the Director sees the latest state.`
|
|
12089
|
+
);
|
|
12294
12090
|
}
|
|
12295
|
-
|
|
12296
|
-
|
|
12297
|
-
|
|
12091
|
+
if (parts.override && parts.override.trim().length > 0) {
|
|
12092
|
+
sections.push(parts.override.trim());
|
|
12093
|
+
}
|
|
12094
|
+
return sections.join("\n\n");
|
|
12095
|
+
}
|
|
12096
|
+
function rosterSummaryFromConfigs(roster) {
|
|
12097
|
+
const lines = [];
|
|
12098
|
+
for (const [roleId, cfg] of Object.entries(roster)) {
|
|
12099
|
+
const tag = cfg.provider && cfg.model ? ` (${cfg.provider}/${cfg.model})` : "";
|
|
12100
|
+
const headline = cfg.prompt ? (cfg.prompt.split("\n").find((l) => l.trim().length > 0) ?? "").trim().slice(0, 80) : "";
|
|
12101
|
+
const tail = headline ? ` \u2014 ${headline}` : "";
|
|
12102
|
+
lines.push(`- ${roleId}: ${cfg.name}${tag}${tail}`);
|
|
12103
|
+
}
|
|
12104
|
+
return lines.join("\n");
|
|
12105
|
+
}
|
|
12106
|
+
function makeSpawnTool(director, roster) {
|
|
12107
|
+
const inputSchema = {
|
|
12108
|
+
type: "object",
|
|
12109
|
+
properties: {
|
|
12110
|
+
role: { type: "string", description: "Roster role id. When set, the spawn uses the matching config from the roster and ignores other fields." },
|
|
12111
|
+
description: { type: "string", description: "Free-form task description. When `role` is not set, the director uses the smart dispatcher to route this to the best-matching catalog agent. Use this when you don't know the exact role name." },
|
|
12112
|
+
name: { type: "string", description: "Display name for the subagent. Used as a fallback when description-based dispatch does not resolve a role." },
|
|
12113
|
+
provider: { type: "string", description: 'Provider id (e.g. "anthropic", "openai"). Defaults to the leader provider when omitted.' },
|
|
12114
|
+
model: { type: "string", description: "Model id within the provider. Defaults to the leader model when omitted." },
|
|
12115
|
+
systemPromptOverride: { type: "string", description: "Extra prompt text appended after the role-base prompt." },
|
|
12116
|
+
maxIterations: { type: "number", minimum: 1 },
|
|
12117
|
+
maxToolCalls: { type: "number", minimum: 1 },
|
|
12118
|
+
maxCostUsd: { type: "number", minimum: 0 },
|
|
12119
|
+
timeoutMs: { type: "number", minimum: 1, description: "Hard wall-clock cap in milliseconds. Defaults to none (idle timeout is the default reaper)." },
|
|
12120
|
+
idleTimeoutMs: { type: "number", minimum: 1, description: "Idle timeout in ms: reap the subagent after this long with no activity. Resets on every iteration/tool call. Default is role/coordinator-specific." },
|
|
12121
|
+
maxTokens: { type: "number", minimum: 1, description: "Maximum total tokens (input + output) the subagent may use." }
|
|
12122
|
+
},
|
|
12123
|
+
required: []
|
|
12124
|
+
};
|
|
12125
|
+
return {
|
|
12126
|
+
name: "spawn_subagent",
|
|
12127
|
+
description: "Create a new subagent under this director. Returns the subagent id.",
|
|
12128
|
+
usageHint: "Pass `role` (matches the roster), `description` (smart dispatch to best agent), or `name` + `provider`/`model`. Returns `{ subagentId }`.",
|
|
12129
|
+
permission: "auto",
|
|
12130
|
+
mutating: false,
|
|
12131
|
+
inputSchema,
|
|
12132
|
+
async execute(input) {
|
|
12133
|
+
const i = input ?? {};
|
|
12134
|
+
const role = typeof i.role === "string" ? i.role : void 0;
|
|
12135
|
+
const description = typeof i.description === "string" ? i.description : void 0;
|
|
12136
|
+
let cfg;
|
|
12137
|
+
if (role && roster) {
|
|
12138
|
+
const base = roster[role];
|
|
12139
|
+
if (!base) return { error: `unknown role "${role}". roster has: ${Object.keys(roster).join(", ")}` };
|
|
12140
|
+
cfg = instantiateRosterConfig(role, base);
|
|
12141
|
+
} else if (description && !role) {
|
|
12142
|
+
const dispatchResult = await dispatchAgent(description, {
|
|
12143
|
+
classifier: director.dispatchClassifier,
|
|
12144
|
+
catalog: roster
|
|
12145
|
+
});
|
|
12146
|
+
const dispatchRole = dispatchResult.role;
|
|
12147
|
+
if (roster?.[dispatchRole]) {
|
|
12148
|
+
cfg = instantiateRosterConfig(dispatchRole, roster[dispatchRole]);
|
|
12149
|
+
} else {
|
|
12150
|
+
const def = dispatchResult.definition;
|
|
12151
|
+
cfg = {
|
|
12152
|
+
name: def.config.name ?? dispatchRole,
|
|
12153
|
+
role: dispatchRole,
|
|
12154
|
+
provider: def.config.provider,
|
|
12155
|
+
model: def.config.model
|
|
12156
|
+
};
|
|
12157
|
+
}
|
|
12158
|
+
}
|
|
12159
|
+
cfg ??= { name: i.name ?? "subagent" };
|
|
12160
|
+
if (typeof i.name === "string") cfg.name = i.name;
|
|
12161
|
+
if (typeof i.provider === "string") cfg.provider = i.provider;
|
|
12162
|
+
if (typeof i.model === "string") cfg.model = i.model;
|
|
12163
|
+
if (typeof i.systemPromptOverride === "string") cfg.systemPromptOverride = i.systemPromptOverride;
|
|
12164
|
+
if (typeof i.maxIterations === "number") cfg.maxIterations = i.maxIterations;
|
|
12165
|
+
if (typeof i.maxToolCalls === "number") cfg.maxToolCalls = i.maxToolCalls;
|
|
12166
|
+
if (typeof i.maxCostUsd === "number") cfg.maxCostUsd = i.maxCostUsd;
|
|
12167
|
+
if (typeof i.timeoutMs === "number") cfg.timeoutMs = i.timeoutMs;
|
|
12168
|
+
if (typeof i.idleTimeoutMs === "number") cfg.idleTimeoutMs = i.idleTimeoutMs;
|
|
12169
|
+
if (typeof i.maxTokens === "number") cfg.maxTokens = i.maxTokens;
|
|
12298
12170
|
try {
|
|
12299
|
-
const
|
|
12300
|
-
|
|
12301
|
-
|
|
12302
|
-
|
|
12303
|
-
|
|
12304
|
-
|
|
12305
|
-
|
|
12306
|
-
|
|
12307
|
-
|
|
12308
|
-
|
|
12309
|
-
/**
|
|
12310
|
-
* Cancel the session. Emits director.cancel_collab on the FleetBus so all
|
|
12311
|
-
* collab agents finish early. The session-level timeout timer is also cleared.
|
|
12312
|
-
* Safe to call multiple times (idempotent after first call).
|
|
12313
|
-
*/
|
|
12314
|
-
cancel(reason = "Director cancelled collab session") {
|
|
12315
|
-
if (this.settled) return;
|
|
12316
|
-
this.cancelled = true;
|
|
12317
|
-
if (this._timeoutTimer) {
|
|
12318
|
-
clearTimeout(this._timeoutTimer);
|
|
12319
|
-
this._timeoutTimer = void 0;
|
|
12320
|
-
}
|
|
12321
|
-
this.fleetBus.emit({
|
|
12322
|
-
subagentId: this.director.id,
|
|
12323
|
-
ts: Date.now(),
|
|
12324
|
-
type: "director.cancel_collab",
|
|
12325
|
-
payload: { sessionId: this.sessionId, reason, cancelledAt: (/* @__PURE__ */ new Date()).toISOString() }
|
|
12326
|
-
});
|
|
12327
|
-
this.fleetBus.emit({
|
|
12328
|
-
subagentId: this.director.id,
|
|
12329
|
-
ts: Date.now(),
|
|
12330
|
-
type: "collab.cancelled",
|
|
12331
|
-
payload: { sessionId: this.sessionId, reason }
|
|
12332
|
-
});
|
|
12333
|
-
}
|
|
12334
|
-
async start() {
|
|
12335
|
-
if (this.settled) throw new Error("session already settled");
|
|
12336
|
-
this.settled = true;
|
|
12337
|
-
await this.buildSnapshot();
|
|
12338
|
-
this.wireFleetBus();
|
|
12339
|
-
const [bugHunterId, refactorPlannerId, criticId] = await Promise.all([
|
|
12340
|
-
this.spawnAgent("bug-hunter", this.buildBugHunterTask()),
|
|
12341
|
-
this.spawnAgent("refactor-planner", this.buildRefactorPlannerTask()),
|
|
12342
|
-
this.spawnAgent("critic", this.buildCriticTask())
|
|
12343
|
-
]);
|
|
12344
|
-
this.subagentIds.set("bug-hunter", bugHunterId);
|
|
12345
|
-
this.subagentIds.set("refactor-planner", refactorPlannerId);
|
|
12346
|
-
this.subagentIds.set("critic", criticId);
|
|
12347
|
-
const timeout = new Promise((_, reject) => {
|
|
12348
|
-
this._timeoutTimer = setTimeout(() => {
|
|
12349
|
-
this.cancel("Session-level timeout reached");
|
|
12350
|
-
reject(new Error(`CollabSession timed out after ${this.timeoutMs}ms`));
|
|
12351
|
-
}, this.timeoutMs);
|
|
12352
|
-
});
|
|
12353
|
-
let results = null;
|
|
12354
|
-
try {
|
|
12355
|
-
results = await Promise.race([
|
|
12356
|
-
Promise.all([
|
|
12357
|
-
this.director.awaitTasks([bugHunterId]),
|
|
12358
|
-
this.director.awaitTasks([refactorPlannerId]),
|
|
12359
|
-
this.director.awaitTasks([criticId])
|
|
12360
|
-
]),
|
|
12361
|
-
timeout
|
|
12362
|
-
]);
|
|
12363
|
-
} catch (err) {
|
|
12364
|
-
if (this._timeoutTimer) {
|
|
12365
|
-
clearTimeout(this._timeoutTimer);
|
|
12366
|
-
this._timeoutTimer = void 0;
|
|
12171
|
+
const subagentId = await director.spawn(cfg);
|
|
12172
|
+
return { subagentId, provider: cfg.provider, model: cfg.model, name: cfg.name, role: cfg.role };
|
|
12173
|
+
} catch (err) {
|
|
12174
|
+
if (err instanceof FleetSpawnBudgetError) {
|
|
12175
|
+
return { error: err.message, kind: err.kind, limit: err.limit, observed: err.observed };
|
|
12176
|
+
}
|
|
12177
|
+
if (err instanceof FleetCostCapError) {
|
|
12178
|
+
return { error: err.message, kind: err.kind, limit: err.limit, observed: err.observed };
|
|
12179
|
+
}
|
|
12180
|
+
return { error: err instanceof Error ? err.message : String(err) };
|
|
12367
12181
|
}
|
|
12368
|
-
this.cleanup();
|
|
12369
|
-
const error = err instanceof Error ? err : new Error(String(err));
|
|
12370
|
-
this.emit("session.error", error);
|
|
12371
|
-
throw error;
|
|
12372
12182
|
}
|
|
12373
|
-
|
|
12374
|
-
|
|
12183
|
+
};
|
|
12184
|
+
}
|
|
12185
|
+
function instantiateRosterConfig(role, base) {
|
|
12186
|
+
return {
|
|
12187
|
+
...base,
|
|
12188
|
+
// Roster entries are templates. A director may spawn several
|
|
12189
|
+
// workers with the same role, so never reuse the template id.
|
|
12190
|
+
id: `${role}-${randomUUID().slice(0, 8)}`
|
|
12191
|
+
};
|
|
12192
|
+
}
|
|
12193
|
+
function makeAssignTool(director) {
|
|
12194
|
+
const inputSchema = {
|
|
12195
|
+
type: "object",
|
|
12196
|
+
properties: {
|
|
12197
|
+
subagentId: { type: "string", minLength: 1, description: "Target subagent id. Required." },
|
|
12198
|
+
description: { type: "string", minLength: 1, description: "The task in natural language \u2014 what you want this subagent to do." },
|
|
12199
|
+
maxToolCalls: { type: "number", minimum: 1, description: "Optional per-task tool-call budget override." },
|
|
12200
|
+
timeoutMs: { type: "number", minimum: 1, description: "Optional per-task timeout in ms." }
|
|
12201
|
+
},
|
|
12202
|
+
required: ["subagentId", "description"]
|
|
12203
|
+
};
|
|
12204
|
+
return {
|
|
12205
|
+
name: "assign_task",
|
|
12206
|
+
description: "Hand a task to a previously spawned subagent. Returns the task id.",
|
|
12207
|
+
permission: "auto",
|
|
12208
|
+
mutating: false,
|
|
12209
|
+
inputSchema,
|
|
12210
|
+
async execute(input) {
|
|
12211
|
+
const i = input;
|
|
12212
|
+
const task = { id: randomUUID(), description: i.description, subagentId: i.subagentId, maxToolCalls: i.maxToolCalls, timeoutMs: i.timeoutMs };
|
|
12213
|
+
const taskId = await director.assign(task);
|
|
12214
|
+
return { taskId, subagentId: i.subagentId };
|
|
12375
12215
|
}
|
|
12376
|
-
|
|
12377
|
-
|
|
12378
|
-
|
|
12379
|
-
|
|
12380
|
-
|
|
12381
|
-
|
|
12382
|
-
|
|
12383
|
-
|
|
12384
|
-
|
|
12385
|
-
|
|
12386
|
-
|
|
12387
|
-
|
|
12388
|
-
|
|
12389
|
-
taskId: result.taskId,
|
|
12390
|
-
ts: Date.now(),
|
|
12391
|
-
type,
|
|
12392
|
-
payload: obj
|
|
12393
|
-
});
|
|
12216
|
+
};
|
|
12217
|
+
}
|
|
12218
|
+
function makeAwaitTasksTool(director) {
|
|
12219
|
+
return {
|
|
12220
|
+
name: "await_tasks",
|
|
12221
|
+
description: "Block until every named task completes. Returns the array of TaskResult.",
|
|
12222
|
+
permission: "auto",
|
|
12223
|
+
mutating: false,
|
|
12224
|
+
inputSchema: { type: "object", properties: { taskIds: { type: "array", items: { type: "string" }, description: "One or more task ids returned by `assign_task`." } }, required: ["taskIds"] },
|
|
12225
|
+
async execute(input) {
|
|
12226
|
+
const i = input;
|
|
12227
|
+
const results = await director.awaitTasks(i.taskIds);
|
|
12228
|
+
return { results };
|
|
12394
12229
|
}
|
|
12395
|
-
}
|
|
12396
|
-
|
|
12397
|
-
|
|
12398
|
-
|
|
12399
|
-
|
|
12400
|
-
|
|
12401
|
-
|
|
12402
|
-
|
|
12403
|
-
|
|
12404
|
-
|
|
12405
|
-
|
|
12406
|
-
|
|
12407
|
-
|
|
12408
|
-
|
|
12230
|
+
};
|
|
12231
|
+
}
|
|
12232
|
+
function makeAskTool(director) {
|
|
12233
|
+
return {
|
|
12234
|
+
name: "ask_subagent",
|
|
12235
|
+
description: "Synchronously ask a subagent a question. Blocks until the subagent replies via the bridge.",
|
|
12236
|
+
permission: "auto",
|
|
12237
|
+
mutating: false,
|
|
12238
|
+
inputSchema: {
|
|
12239
|
+
type: "object",
|
|
12240
|
+
properties: {
|
|
12241
|
+
subagentId: { type: "string", minLength: 1, description: "Subagent to ask. Must be a previously spawned id." },
|
|
12242
|
+
question: { type: "string", minLength: 1, description: "The question or instruction." },
|
|
12243
|
+
timeoutMs: { type: "number", minimum: 1, description: "Optional timeout in ms (default 30s)." }
|
|
12244
|
+
},
|
|
12245
|
+
required: ["subagentId", "question"]
|
|
12246
|
+
},
|
|
12247
|
+
async execute(input) {
|
|
12248
|
+
const i = input;
|
|
12249
|
+
try {
|
|
12250
|
+
const answer = await director.ask(i.subagentId, { question: i.question }, i.timeoutMs);
|
|
12251
|
+
const stored = director.largeAnswerStore.storeAnswer(answer);
|
|
12252
|
+
if (stored.inline) {
|
|
12253
|
+
return { ok: true, answer: stored.summary };
|
|
12254
|
+
}
|
|
12255
|
+
return {
|
|
12256
|
+
ok: true,
|
|
12257
|
+
answer: stored.summary,
|
|
12258
|
+
_answerKey: stored.key,
|
|
12259
|
+
_hint: "Response was large and stored. Use ask_result with the key to retrieve it."
|
|
12260
|
+
};
|
|
12261
|
+
} catch (err) {
|
|
12262
|
+
return { ok: false, error: err instanceof Error ? err.message : String(err) };
|
|
12409
12263
|
}
|
|
12410
|
-
|
|
12411
|
-
|
|
12412
|
-
|
|
12413
|
-
|
|
12414
|
-
|
|
12415
|
-
|
|
12416
|
-
|
|
12417
|
-
|
|
12418
|
-
|
|
12419
|
-
|
|
12420
|
-
|
|
12421
|
-
|
|
12422
|
-
|
|
12423
|
-
|
|
12424
|
-
|
|
12425
|
-
|
|
12426
|
-
start = -1;
|
|
12264
|
+
}
|
|
12265
|
+
};
|
|
12266
|
+
}
|
|
12267
|
+
function makeAskResultTool(director) {
|
|
12268
|
+
return {
|
|
12269
|
+
name: "ask_result",
|
|
12270
|
+
description: "Retrieve a large `ask_subagent` response that was stored out-of-context (>2K chars). Returns the full stored value.",
|
|
12271
|
+
permission: "auto",
|
|
12272
|
+
mutating: false,
|
|
12273
|
+
inputSchema: {
|
|
12274
|
+
type: "object",
|
|
12275
|
+
properties: {
|
|
12276
|
+
key: {
|
|
12277
|
+
type: "string",
|
|
12278
|
+
minLength: 1,
|
|
12279
|
+
description: "The `_answerKey` returned by `ask_subagent` for a large response."
|
|
12427
12280
|
}
|
|
12281
|
+
},
|
|
12282
|
+
required: ["key"]
|
|
12283
|
+
},
|
|
12284
|
+
async execute(input) {
|
|
12285
|
+
const i = input;
|
|
12286
|
+
const value = director.largeAnswerStore.retrieveAnswer(i.key);
|
|
12287
|
+
if (value === void 0) {
|
|
12288
|
+
return { ok: false, error: `No stored answer found for key "${i.key}" \u2014 it may have been cleared or the key is invalid.` };
|
|
12428
12289
|
}
|
|
12290
|
+
return { ok: true, value };
|
|
12291
|
+
}
|
|
12292
|
+
};
|
|
12293
|
+
}
|
|
12294
|
+
function makeRollUpTool(director) {
|
|
12295
|
+
return {
|
|
12296
|
+
name: "roll_up",
|
|
12297
|
+
description: "Aggregate completed task results into a single formatted summary.",
|
|
12298
|
+
permission: "auto",
|
|
12299
|
+
mutating: false,
|
|
12300
|
+
inputSchema: {
|
|
12301
|
+
type: "object",
|
|
12302
|
+
properties: {
|
|
12303
|
+
taskIds: { type: "array", items: { type: "string" }, description: "Completed task ids to aggregate." },
|
|
12304
|
+
style: { type: "string", enum: ["markdown", "json"], description: "Output flavor \u2014 markdown (default) or json." }
|
|
12305
|
+
},
|
|
12306
|
+
required: ["taskIds"]
|
|
12307
|
+
},
|
|
12308
|
+
async execute(input) {
|
|
12309
|
+
const i = input;
|
|
12310
|
+
const summary = director.rollUp(i.taskIds, i.style ?? "markdown");
|
|
12311
|
+
return { summary, count: i.taskIds.length };
|
|
12429
12312
|
}
|
|
12430
|
-
|
|
12431
|
-
|
|
12432
|
-
|
|
12433
|
-
|
|
12434
|
-
|
|
12313
|
+
};
|
|
12314
|
+
}
|
|
12315
|
+
function makeTerminateTool(director) {
|
|
12316
|
+
return {
|
|
12317
|
+
name: "terminate_subagent",
|
|
12318
|
+
description: 'Forcibly abort a subagent. The subagent finishes its current iteration then exits with status "stopped".',
|
|
12319
|
+
permission: "auto",
|
|
12320
|
+
mutating: true,
|
|
12321
|
+
inputSchema: { type: "object", properties: { subagentId: { type: "string", description: "Subagent to abort." } }, required: ["subagentId"] },
|
|
12322
|
+
async execute(input) {
|
|
12323
|
+
const i = input;
|
|
12324
|
+
await director.terminate(i.subagentId);
|
|
12325
|
+
return { ok: true };
|
|
12435
12326
|
}
|
|
12436
|
-
|
|
12437
|
-
|
|
12438
|
-
|
|
12439
|
-
|
|
12440
|
-
|
|
12441
|
-
|
|
12442
|
-
|
|
12443
|
-
|
|
12444
|
-
|
|
12445
|
-
|
|
12446
|
-
|
|
12447
|
-
|
|
12448
|
-
|
|
12449
|
-
|
|
12450
|
-
|
|
12451
|
-
|
|
12452
|
-
|
|
12453
|
-
|
|
12454
|
-
|
|
12455
|
-
|
|
12456
|
-
|
|
12457
|
-
|
|
12458
|
-
|
|
12459
|
-
|
|
12460
|
-
|
|
12461
|
-
|
|
12462
|
-
|
|
12463
|
-
|
|
12464
|
-
|
|
12465
|
-
|
|
12466
|
-
|
|
12467
|
-
|
|
12468
|
-
{ "type": "bug.found", "payload": { "finding": { "id": "<uuid>", "type": "<pattern>", "severity": "<critical|high|medium|low>", "location": { "file": "<path>", "line": <n> }, "description": "<explain>", "suggestedFix": "<optional>" } } }
|
|
12469
|
-
|
|
12470
|
-
After scanning all files, write your full markdown bug report to:
|
|
12471
|
-
${scratchpad}/bug-hunter-report-${this.sessionId}.md
|
|
12472
|
-
|
|
12473
|
-
Important: emit each finding as soon as you find it. Do not batch or wait until the end.`;
|
|
12474
|
-
}
|
|
12475
|
-
buildRefactorPlannerTask() {
|
|
12476
|
-
const scratchpad = this.director.sharedScratchpadPath ?? "/tmp";
|
|
12477
|
-
const bugHunterReportPath = `${scratchpad}/bug-hunter-report-${this.sessionId}.md`;
|
|
12478
|
-
const fileContents = this.snapshot.files.map((f) => `=== ${f.path} ===
|
|
12479
|
-
${f.content}`).join("\n\n");
|
|
12480
|
-
return `You are RefactorPlanner. Plan refactorings for the following files.
|
|
12481
|
-
|
|
12482
|
-
Target files:
|
|
12483
|
-
${fileContents}
|
|
12484
|
-
|
|
12485
|
-
Read the BugHunter report at: ${bugHunterReportPath}
|
|
12486
|
-
|
|
12487
|
-
For each bug you can address, emit a refactor plan using fleet_emit:
|
|
12488
|
-
{ "type": "refactor.plan", "payload": { "plan": { "id": "<uuid>", "basedOnBugIds": ["<bug-id>"], "phases": [{ "number": 1, "title": "<phase>", "tasks": ["<task>"], "risk": "<low|medium|high>" }], "riskScore": "<low|medium|high>", "estimatedChangeCount": <n>, "rollbackStrategy": "<text>" } } }
|
|
12489
|
-
|
|
12490
|
-
Also write your full markdown plan to:
|
|
12491
|
-
${scratchpad}/refactor-plan-${this.sessionId}.md
|
|
12492
|
-
|
|
12493
|
-
Emit each plan immediately. Do not wait until planning is complete.`;
|
|
12494
|
-
}
|
|
12495
|
-
buildCriticTask() {
|
|
12496
|
-
const scratchpad = this.director.sharedScratchpadPath ?? "/tmp";
|
|
12497
|
-
const bugHunterReportPath = `${scratchpad}/bug-hunter-report-${this.sessionId}.md`;
|
|
12498
|
-
const refactorPlanPath = `${scratchpad}/refactor-plan-${this.sessionId}.md`;
|
|
12499
|
-
const fileContents = this.snapshot.files.map((f) => `=== ${f.path} ===
|
|
12500
|
-
${f.content}`).join("\n\n");
|
|
12501
|
-
return `You are Critic. Evaluate bug findings and refactor plans.
|
|
12502
|
-
|
|
12503
|
-
Target files:
|
|
12504
|
-
${fileContents}
|
|
12505
|
-
|
|
12506
|
-
Read the BugHunter report at: ${bugHunterReportPath}
|
|
12507
|
-
Read the RefactorPlanner report at: ${refactorPlanPath}
|
|
12508
|
-
|
|
12509
|
-
For each bug and refactor plan, emit your evaluation using fleet_emit:
|
|
12510
|
-
{ "type": "critic.evaluation", "payload": { "evaluation": { "id": "<uuid>", "subjectType": "<bug_finding|refactor_plan>", "subjectId": "<id>", "score": <0-10>, "verdict": "<approve|needs_revision|reject>", "strengths": ["<strength>"], "weaknesses": ["<weakness>"], "concerns": [{ "description": "<concern>", "severity": "<blocking|advisory>" }] } } }
|
|
12511
|
-
|
|
12512
|
-
After all evaluations, write your markdown report to:
|
|
12513
|
-
${scratchpad}/critic-report-${this.sessionId}.md
|
|
12514
|
-
|
|
12515
|
-
Emit each evaluation immediately. Do not wait until you have read all reports.`;
|
|
12516
|
-
}
|
|
12517
|
-
wireFleetBus() {
|
|
12518
|
-
const dTool = this.fleetBus.filter("tool.executed", (e) => {
|
|
12519
|
-
this.progressBySubagent.set(e.subagentId, (this.progressBySubagent.get(e.subagentId) ?? 0) + 1);
|
|
12520
|
-
});
|
|
12521
|
-
this.disposers.push(dTool);
|
|
12522
|
-
const dBudget = this.fleetBus.filter("budget.threshold_reached", (e) => {
|
|
12523
|
-
const payload = e.payload;
|
|
12524
|
-
const role = this.roleFromSubagentId(e.subagentId);
|
|
12525
|
-
if (!role) return;
|
|
12526
|
-
const btwNotes = this.director.getLeaderBtwNotes();
|
|
12527
|
-
const alert = {
|
|
12528
|
-
sessionId: this.sessionId,
|
|
12529
|
-
subagentId: e.subagentId,
|
|
12530
|
-
role,
|
|
12531
|
-
level: "warning" /* WARNING */,
|
|
12532
|
-
message: `${role} hit ${payload.kind} soft limit (${payload.used}/${payload.limit})`,
|
|
12533
|
-
budgetKind: payload.kind,
|
|
12534
|
-
elapsedMs: payload.timeoutMs,
|
|
12535
|
-
limit: payload.limit,
|
|
12536
|
-
btwNotes
|
|
12327
|
+
};
|
|
12328
|
+
}
|
|
12329
|
+
function makeTerminateAllTool(director) {
|
|
12330
|
+
return {
|
|
12331
|
+
name: "terminate_all",
|
|
12332
|
+
description: 'Forcibly stop every subagent in the fleet and drain the pending task queue. In-flight tasks are terminated mid-execution; pending tasks receive "aborted_by_parent" completion immediately. Use this when the fleet is wedged, looping, or you need a clean slate. Compare: work_complete stops spawning but lets running agents finish naturally.',
|
|
12333
|
+
permission: "auto",
|
|
12334
|
+
mutating: true,
|
|
12335
|
+
inputSchema: { type: "object", properties: {}, required: [] },
|
|
12336
|
+
async execute() {
|
|
12337
|
+
await director.terminateAll();
|
|
12338
|
+
return { ok: true, message: `Fleet shutdown complete \u2014 all subagents stopped, pending tasks drained.` };
|
|
12339
|
+
}
|
|
12340
|
+
};
|
|
12341
|
+
}
|
|
12342
|
+
function makeFleetStatusTool(director) {
|
|
12343
|
+
return {
|
|
12344
|
+
name: "fleet_status",
|
|
12345
|
+
description: "Snapshot of the fleet \u2014 every subagent's current status, coordinator counts (total/running/idle/stopped), pending task descriptions, and usage rollup.",
|
|
12346
|
+
permission: "auto",
|
|
12347
|
+
mutating: false,
|
|
12348
|
+
inputSchema: { type: "object", properties: {}, required: [] },
|
|
12349
|
+
async execute() {
|
|
12350
|
+
const base = director.status();
|
|
12351
|
+
const fm = director.fleetManager;
|
|
12352
|
+
const stats = fm?.getFleetStats();
|
|
12353
|
+
const fleetStatus = fm?.getFleetStatus();
|
|
12354
|
+
return {
|
|
12355
|
+
subagents: base.subagents,
|
|
12356
|
+
coordinatorStats: stats ? { total: stats.total, running: stats.running, idle: stats.idle, stopped: stats.stopped } : void 0,
|
|
12357
|
+
pending: fleetStatus?.pending ?? [],
|
|
12358
|
+
usage: fm?.snapshot()
|
|
12537
12359
|
};
|
|
12538
|
-
|
|
12539
|
-
|
|
12540
|
-
|
|
12541
|
-
|
|
12542
|
-
|
|
12543
|
-
|
|
12544
|
-
|
|
12545
|
-
|
|
12546
|
-
|
|
12547
|
-
|
|
12548
|
-
|
|
12360
|
+
}
|
|
12361
|
+
};
|
|
12362
|
+
}
|
|
12363
|
+
function makeFleetUsageTool(director) {
|
|
12364
|
+
return {
|
|
12365
|
+
name: "fleet_usage",
|
|
12366
|
+
description: "Token + cost breakdown across the fleet, per-subagent and totals.",
|
|
12367
|
+
permission: "auto",
|
|
12368
|
+
mutating: false,
|
|
12369
|
+
inputSchema: { type: "object", properties: {}, required: [] },
|
|
12370
|
+
async execute() {
|
|
12371
|
+
return director.snapshot();
|
|
12372
|
+
}
|
|
12373
|
+
};
|
|
12374
|
+
}
|
|
12375
|
+
function makeFleetSessionTool(director) {
|
|
12376
|
+
return {
|
|
12377
|
+
name: "fleet_session",
|
|
12378
|
+
description: "Read a subagent's JSONL transcript and extract its last assistant text, stop reason, and tool-use count. Use this to see what a running or timed-out subagent actually produced.",
|
|
12379
|
+
permission: "auto",
|
|
12380
|
+
mutating: false,
|
|
12381
|
+
inputSchema: {
|
|
12382
|
+
type: "object",
|
|
12383
|
+
properties: {
|
|
12384
|
+
subagentId: { type: "string", description: "Subagent id to read the transcript of." },
|
|
12385
|
+
tail: { type: "number", description: "Number of trailing JSONL lines to return. Omit for the full transcript." }
|
|
12386
|
+
},
|
|
12387
|
+
required: ["subagentId"]
|
|
12388
|
+
},
|
|
12389
|
+
async execute(input) {
|
|
12390
|
+
const i = input;
|
|
12391
|
+
const result = await director.readSession(i.subagentId, i.tail);
|
|
12392
|
+
if (!result) {
|
|
12393
|
+
return {
|
|
12394
|
+
error: `fleet_session: transcript unavailable for "${i.subagentId}". Is sessionsRoot configured?`
|
|
12395
|
+
};
|
|
12549
12396
|
}
|
|
12550
|
-
|
|
12551
|
-
|
|
12552
|
-
|
|
12553
|
-
|
|
12554
|
-
|
|
12555
|
-
|
|
12397
|
+
return result;
|
|
12398
|
+
}
|
|
12399
|
+
};
|
|
12400
|
+
}
|
|
12401
|
+
function makeFleetHealthTool(director) {
|
|
12402
|
+
return {
|
|
12403
|
+
name: "fleet_health",
|
|
12404
|
+
description: "Per-subagent health report: budget pressure (pct of limits consumed), last activity timestamp, and current status. Use to decide whether to assign more work to a subagent or spawn a fresh one.",
|
|
12405
|
+
permission: "auto",
|
|
12406
|
+
mutating: false,
|
|
12407
|
+
inputSchema: { type: "object", properties: {}, required: [] },
|
|
12408
|
+
async execute() {
|
|
12409
|
+
const status = director.status();
|
|
12410
|
+
const snapshot = director.snapshot();
|
|
12411
|
+
const subagents = status.subagents ?? [];
|
|
12412
|
+
const perSubagent = snapshot.perSubagent ?? {};
|
|
12413
|
+
return {
|
|
12414
|
+
subagents: subagents.map((s) => {
|
|
12415
|
+
const usage = perSubagent[s.id];
|
|
12416
|
+
return {
|
|
12417
|
+
id: s.id,
|
|
12418
|
+
status: s.status,
|
|
12419
|
+
lastEventAt: usage?.lastEventAt,
|
|
12420
|
+
budgetPressure: {
|
|
12421
|
+
iterations: usage?.iterations,
|
|
12422
|
+
toolCalls: usage?.toolCalls,
|
|
12423
|
+
costUsd: usage?.cost
|
|
12424
|
+
}
|
|
12425
|
+
};
|
|
12426
|
+
})
|
|
12427
|
+
};
|
|
12428
|
+
}
|
|
12429
|
+
};
|
|
12430
|
+
}
|
|
12431
|
+
function makeCollabDebugTool(director) {
|
|
12432
|
+
return {
|
|
12433
|
+
name: "collab_debug",
|
|
12434
|
+
description: "Start a collaborative debugging session: BugHunter, RefactorPlanner, and Critic run in parallel on the same target files. BugHunter finds bugs and emits bug.found events. RefactorPlanner listens for bug.found and emits refactor.plan events. Critic evaluates both and emits critic.evaluation events. Returns a structured report with overall verdict (approve / needs_revision / reject).",
|
|
12435
|
+
permission: "auto",
|
|
12436
|
+
mutating: false,
|
|
12437
|
+
inputSchema: {
|
|
12438
|
+
type: "object",
|
|
12439
|
+
properties: {
|
|
12440
|
+
targetPaths: {
|
|
12441
|
+
type: "array",
|
|
12442
|
+
items: { type: "string" },
|
|
12443
|
+
description: "File paths / glob patterns to scan for bugs."
|
|
12444
|
+
},
|
|
12445
|
+
timeoutMs: {
|
|
12446
|
+
type: "number",
|
|
12447
|
+
minimum: 1,
|
|
12448
|
+
description: "Timeout in ms. Default: 600000 (10 minutes)."
|
|
12449
|
+
},
|
|
12450
|
+
maxTargetFiles: {
|
|
12451
|
+
type: "number",
|
|
12452
|
+
minimum: 1,
|
|
12453
|
+
description: "Maximum number of files to include in the snapshot. If not set, the limit is computed dynamically from contextWindow or falls back to the default (30)."
|
|
12454
|
+
},
|
|
12455
|
+
contextWindow: {
|
|
12456
|
+
type: "number",
|
|
12457
|
+
minimum: 1,
|
|
12458
|
+
description: "Context window size (tokens) of the model. When provided and maxTargetFiles is not set, the file limit is computed dynamically as floor((contextWindow * 0.4) / 2000)."
|
|
12556
12459
|
}
|
|
12557
|
-
|
|
12558
|
-
|
|
12559
|
-
|
|
12560
|
-
|
|
12561
|
-
|
|
12562
|
-
|
|
12563
|
-
|
|
12564
|
-
if (decision === "extend") {
|
|
12565
|
-
setImmediate(() => {
|
|
12566
|
-
const base = Math.max(payload.limit, payload.used);
|
|
12567
|
-
const extra = {};
|
|
12568
|
-
switch (payload.kind) {
|
|
12569
|
-
case "iterations":
|
|
12570
|
-
extra.maxIterations = Math.min(Math.ceil(base * 1.5), 5e4);
|
|
12571
|
-
break;
|
|
12572
|
-
case "tool_calls":
|
|
12573
|
-
extra.maxToolCalls = Math.min(Math.ceil(base * 1.5), 1e5);
|
|
12574
|
-
break;
|
|
12575
|
-
case "tokens":
|
|
12576
|
-
extra.maxTokens = Math.min(Math.ceil(base * 1.5), 5e6);
|
|
12577
|
-
break;
|
|
12578
|
-
case "cost":
|
|
12579
|
-
extra.maxCostUsd = Math.min(base * 1.5, 100);
|
|
12580
|
-
break;
|
|
12581
|
-
}
|
|
12582
|
-
payload.extend(extra);
|
|
12583
|
-
});
|
|
12584
|
-
return;
|
|
12585
|
-
}
|
|
12586
|
-
if (payload.kind !== "timeout") {
|
|
12587
|
-
setImmediate(() => {
|
|
12588
|
-
const base = Math.max(payload.limit, payload.used);
|
|
12589
|
-
const extra = {};
|
|
12590
|
-
switch (payload.kind) {
|
|
12591
|
-
case "iterations":
|
|
12592
|
-
extra.maxIterations = Math.min(Math.ceil(base * 1.25), 5e4);
|
|
12593
|
-
break;
|
|
12594
|
-
case "tool_calls":
|
|
12595
|
-
extra.maxToolCalls = Math.min(Math.ceil(base * 1.25), 1e5);
|
|
12596
|
-
break;
|
|
12597
|
-
case "tokens":
|
|
12598
|
-
extra.maxTokens = Math.min(Math.ceil(base * 1.25), 5e6);
|
|
12599
|
-
break;
|
|
12600
|
-
case "cost":
|
|
12601
|
-
extra.maxCostUsd = Math.min(base * 1.25, 100);
|
|
12602
|
-
break;
|
|
12603
|
-
}
|
|
12604
|
-
payload.extend(extra);
|
|
12605
|
-
});
|
|
12460
|
+
},
|
|
12461
|
+
required: ["targetPaths"]
|
|
12462
|
+
},
|
|
12463
|
+
async execute(input) {
|
|
12464
|
+
const i = input;
|
|
12465
|
+
if (!i.targetPaths?.length) {
|
|
12466
|
+
return { error: "collab_debug: targetPaths is required and must be non-empty." };
|
|
12606
12467
|
}
|
|
12607
|
-
|
|
12608
|
-
|
|
12609
|
-
|
|
12610
|
-
|
|
12611
|
-
|
|
12612
|
-
|
|
12613
|
-
|
|
12614
|
-
|
|
12615
|
-
|
|
12468
|
+
const options = {
|
|
12469
|
+
targetPaths: i.targetPaths,
|
|
12470
|
+
timeoutMs: i.timeoutMs,
|
|
12471
|
+
maxTargetFiles: i.maxTargetFiles,
|
|
12472
|
+
contextWindow: i.contextWindow
|
|
12473
|
+
};
|
|
12474
|
+
try {
|
|
12475
|
+
const report = await director.spawnCollab(options);
|
|
12476
|
+
return {
|
|
12477
|
+
sessionId: report.sessionId,
|
|
12478
|
+
overallVerdict: report.overallVerdict,
|
|
12479
|
+
bugCount: report.bugs.length,
|
|
12480
|
+
planCount: report.refactorPlans.length,
|
|
12481
|
+
evaluationCount: report.evaluations.length,
|
|
12482
|
+
summary: report.summary,
|
|
12483
|
+
bugs: report.bugs,
|
|
12484
|
+
refactorPlans: report.refactorPlans,
|
|
12485
|
+
evaluations: report.evaluations
|
|
12486
|
+
};
|
|
12487
|
+
} catch (err) {
|
|
12488
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
12489
|
+
return { error: "collab_debug failed: " + msg };
|
|
12616
12490
|
}
|
|
12617
|
-
|
|
12618
|
-
|
|
12491
|
+
}
|
|
12492
|
+
};
|
|
12493
|
+
}
|
|
12494
|
+
function makeFleetEmitTool(director) {
|
|
12495
|
+
return {
|
|
12496
|
+
name: "fleet_emit",
|
|
12497
|
+
description: "Emit a structured event on the FleetBus. Any subagent can emit any event type; the Director routes it to all listeners. Use it to stream findings, progress updates, or final results to other agents in real time.",
|
|
12498
|
+
permission: "auto",
|
|
12499
|
+
mutating: false,
|
|
12500
|
+
inputSchema: {
|
|
12501
|
+
type: "object",
|
|
12502
|
+
properties: {
|
|
12503
|
+
type: {
|
|
12504
|
+
type: "string",
|
|
12505
|
+
description: "Event type string (e.g. bug.found, refactor.plan, critic.evaluation, progress, result)."
|
|
12506
|
+
},
|
|
12507
|
+
payload: {
|
|
12508
|
+
type: "object",
|
|
12509
|
+
description: "Event payload. Structure depends on event type. Use null if no payload."
|
|
12510
|
+
}
|
|
12511
|
+
},
|
|
12512
|
+
required: ["type"]
|
|
12513
|
+
},
|
|
12514
|
+
async execute(input) {
|
|
12515
|
+
const i = input;
|
|
12516
|
+
director.fleet.emit({
|
|
12517
|
+
subagentId: director.id,
|
|
12619
12518
|
ts: Date.now(),
|
|
12620
|
-
type:
|
|
12621
|
-
payload:
|
|
12519
|
+
type: i.type,
|
|
12520
|
+
payload: i.payload ?? {}
|
|
12622
12521
|
});
|
|
12522
|
+
return { ok: true, event: i.type };
|
|
12523
|
+
}
|
|
12524
|
+
};
|
|
12525
|
+
}
|
|
12526
|
+
function makeWorkCompleteTool(director) {
|
|
12527
|
+
return {
|
|
12528
|
+
name: "work_complete",
|
|
12529
|
+
description: "Signal that the director is satisfied with the results and the fleet should wind down. After calling this, spawn_subagent will refuse with a budget error and assign_task will instantly complete any queued tasks as aborted. Running subagents finish naturally. Call terminate_subagent separately to stop specific subagents immediately.",
|
|
12530
|
+
permission: "auto",
|
|
12531
|
+
mutating: false,
|
|
12532
|
+
inputSchema: { type: "object", properties: {}, required: [] },
|
|
12533
|
+
async execute() {
|
|
12534
|
+
director.workComplete();
|
|
12535
|
+
return { ok: true, message: "Fleet wind-down signaled. No new spawns or task dispatches." };
|
|
12536
|
+
}
|
|
12537
|
+
};
|
|
12538
|
+
}
|
|
12539
|
+
|
|
12540
|
+
// src/coordination/fleet-bus.ts
|
|
12541
|
+
var FleetBus = class {
|
|
12542
|
+
byId = /* @__PURE__ */ new Map();
|
|
12543
|
+
byType = /* @__PURE__ */ new Map();
|
|
12544
|
+
any = /* @__PURE__ */ new Set();
|
|
12545
|
+
/**
|
|
12546
|
+
* Hook a subagent's EventBus into the fleet. Uses `onAny()` (an alias for
|
|
12547
|
+
* `onPattern('*')`) to forward all events with subagent attribution, so
|
|
12548
|
+
* new kernel event types are automatically forwarded without any manual
|
|
12549
|
+
* registration. `subagent.*` events are excluded because they originate
|
|
12550
|
+
* from MultiAgentHost on the parent bus, not the subagent's own bus.
|
|
12551
|
+
*
|
|
12552
|
+
* Returns a disposer that detaches every subscription; call on
|
|
12553
|
+
* subagent teardown so the listeners don't outlive the run.
|
|
12554
|
+
*/
|
|
12555
|
+
attach(subagentId, bus, taskId) {
|
|
12556
|
+
const off = bus.onAny((type, payload) => {
|
|
12557
|
+
if (type.startsWith("subagent.")) return;
|
|
12558
|
+
this.emit({ subagentId, taskId, ts: Date.now(), type, payload });
|
|
12623
12559
|
});
|
|
12624
|
-
|
|
12625
|
-
|
|
12626
|
-
|
|
12627
|
-
|
|
12628
|
-
|
|
12629
|
-
|
|
12560
|
+
return () => {
|
|
12561
|
+
off();
|
|
12562
|
+
};
|
|
12563
|
+
}
|
|
12564
|
+
/** Subscribe to every event from one subagent. */
|
|
12565
|
+
subscribe(subagentId, handler) {
|
|
12566
|
+
let set = this.byId.get(subagentId);
|
|
12567
|
+
if (!set) {
|
|
12568
|
+
set = /* @__PURE__ */ new Set();
|
|
12569
|
+
this.byId.set(subagentId, set);
|
|
12570
|
+
}
|
|
12571
|
+
set.add(handler);
|
|
12572
|
+
return () => {
|
|
12573
|
+
set.delete(handler);
|
|
12574
|
+
};
|
|
12575
|
+
}
|
|
12576
|
+
/** Subscribe to one event type across all subagents. */
|
|
12577
|
+
filter(type, handler) {
|
|
12578
|
+
let set = this.byType.get(type);
|
|
12579
|
+
if (!set) {
|
|
12580
|
+
set = /* @__PURE__ */ new Set();
|
|
12581
|
+
this.byType.set(type, set);
|
|
12582
|
+
}
|
|
12583
|
+
set.add(handler);
|
|
12584
|
+
return () => {
|
|
12585
|
+
set.delete(handler);
|
|
12586
|
+
};
|
|
12587
|
+
}
|
|
12588
|
+
/** Subscribe to literally everything. The fleet roll-up uses this. */
|
|
12589
|
+
onAny(handler) {
|
|
12590
|
+
this.any.add(handler);
|
|
12591
|
+
return () => {
|
|
12592
|
+
this.any.delete(handler);
|
|
12593
|
+
};
|
|
12594
|
+
}
|
|
12595
|
+
emit(event) {
|
|
12596
|
+
const byId = this.byId.get(event.subagentId);
|
|
12597
|
+
if (byId)
|
|
12598
|
+
for (const h of byId) {
|
|
12599
|
+
try {
|
|
12600
|
+
h(event);
|
|
12601
|
+
} catch {
|
|
12602
|
+
}
|
|
12630
12603
|
}
|
|
12631
|
-
|
|
12632
|
-
|
|
12633
|
-
|
|
12634
|
-
|
|
12635
|
-
|
|
12636
|
-
|
|
12637
|
-
|
|
12604
|
+
const byType = this.byType.get(event.type);
|
|
12605
|
+
if (byType)
|
|
12606
|
+
for (const h of byType) {
|
|
12607
|
+
try {
|
|
12608
|
+
h(event);
|
|
12609
|
+
} catch {
|
|
12610
|
+
}
|
|
12638
12611
|
}
|
|
12639
|
-
|
|
12640
|
-
|
|
12641
|
-
|
|
12642
|
-
|
|
12643
|
-
if (payload?.evaluation) {
|
|
12644
|
-
this.evaluations.set(payload.evaluation.id, payload.evaluation);
|
|
12645
|
-
this.emit("critic.evaluation", payload);
|
|
12612
|
+
for (const h of this.any) {
|
|
12613
|
+
try {
|
|
12614
|
+
h(event);
|
|
12615
|
+
} catch {
|
|
12646
12616
|
}
|
|
12647
|
-
}
|
|
12648
|
-
|
|
12617
|
+
}
|
|
12618
|
+
}
|
|
12619
|
+
};
|
|
12620
|
+
var FleetUsageAggregator = class {
|
|
12621
|
+
constructor(bus, priceLookup, metaLookup) {
|
|
12622
|
+
this.priceLookup = priceLookup;
|
|
12623
|
+
this.metaLookup = metaLookup;
|
|
12624
|
+
this.unsub.push(bus.filter("provider.response", (e) => this.onProviderResponse(e)));
|
|
12625
|
+
this.unsub.push(bus.filter("tool.executed", (e) => this.onToolExecuted(e)));
|
|
12626
|
+
this.unsub.push(bus.filter("iteration.started", (e) => this.onIterationStarted(e)));
|
|
12627
|
+
}
|
|
12628
|
+
priceLookup;
|
|
12629
|
+
metaLookup;
|
|
12630
|
+
perSubagent = /* @__PURE__ */ new Map();
|
|
12631
|
+
total = { input: 0, output: 0, cacheRead: 0, cacheWrite: 0, cost: 0 };
|
|
12632
|
+
unsub = new Array();
|
|
12633
|
+
/**
|
|
12634
|
+
* Remove a terminated subagent's data from the aggregator and subtract its
|
|
12635
|
+
* contribution from the running totals. Call this when a subagent is removed
|
|
12636
|
+
* from the fleet so the aggregator doesn't accumulate unbounded data for
|
|
12637
|
+
* entities that will never emit events again.
|
|
12638
|
+
*/
|
|
12639
|
+
removeSubagent(subagentId) {
|
|
12640
|
+
const snap = this.perSubagent.get(subagentId);
|
|
12641
|
+
if (!snap) return;
|
|
12642
|
+
this.perSubagent.delete(subagentId);
|
|
12643
|
+
this.total.input -= snap.input;
|
|
12644
|
+
this.total.output -= snap.output;
|
|
12645
|
+
this.total.cacheRead -= snap.cacheRead;
|
|
12646
|
+
this.total.cacheWrite -= snap.cacheWrite;
|
|
12647
|
+
this.total.cost -= snap.cost;
|
|
12649
12648
|
}
|
|
12650
|
-
|
|
12651
|
-
|
|
12652
|
-
|
|
12653
|
-
|
|
12654
|
-
const match = subagentId.match(/^(bug-hunter|refactor-planner|critic)/);
|
|
12655
|
-
return match?.[1] ?? null;
|
|
12649
|
+
/** Disposes all fleet-bus subscriptions. Call when the aggregator is no longer needed. */
|
|
12650
|
+
dispose() {
|
|
12651
|
+
for (const off of this.unsub) off();
|
|
12652
|
+
this.unsub.length = 0;
|
|
12656
12653
|
}
|
|
12657
|
-
|
|
12658
|
-
|
|
12659
|
-
const planList = Array.from(this.plans.values());
|
|
12660
|
-
const evalList = Array.from(this.evaluations.values());
|
|
12661
|
-
let disposition = "completed";
|
|
12662
|
-
if (this.cancelled) disposition = "cancelled";
|
|
12663
|
-
const verdictOrder = {
|
|
12664
|
-
approve: 0,
|
|
12665
|
-
needs_revision: 1,
|
|
12666
|
-
reject: 2
|
|
12667
|
-
};
|
|
12668
|
-
const overallVerdict = evalList.reduce(
|
|
12669
|
-
(worst, eval_) => {
|
|
12670
|
-
const w = verdictOrder[worst];
|
|
12671
|
-
const c = verdictOrder[eval_.verdict];
|
|
12672
|
-
return c > w ? eval_.verdict : worst;
|
|
12673
|
-
},
|
|
12674
|
-
"approve"
|
|
12675
|
-
);
|
|
12676
|
-
const summary = this.buildMarkdownSummary(bugList, planList, evalList, overallVerdict, disposition);
|
|
12654
|
+
/** Live snapshot — safe to call from a tool's execute() body. */
|
|
12655
|
+
snapshot() {
|
|
12677
12656
|
return {
|
|
12678
|
-
|
|
12679
|
-
|
|
12680
|
-
|
|
12681
|
-
|
|
12682
|
-
disposition,
|
|
12683
|
-
bugs: bugList,
|
|
12684
|
-
refactorPlans: planList,
|
|
12685
|
-
evaluations: evalList,
|
|
12686
|
-
alerts: [...this.alerts],
|
|
12687
|
-
overallVerdict,
|
|
12688
|
-
summary
|
|
12657
|
+
total: { ...this.total },
|
|
12658
|
+
perSubagent: Object.fromEntries(
|
|
12659
|
+
Array.from(this.perSubagent.entries()).map(([k, v]) => [k, { ...v }])
|
|
12660
|
+
)
|
|
12689
12661
|
};
|
|
12690
12662
|
}
|
|
12691
|
-
|
|
12692
|
-
|
|
12693
|
-
|
|
12694
|
-
|
|
12695
|
-
|
|
12696
|
-
|
|
12697
|
-
|
|
12698
|
-
|
|
12699
|
-
|
|
12700
|
-
|
|
12701
|
-
|
|
12702
|
-
|
|
12703
|
-
|
|
12704
|
-
|
|
12705
|
-
|
|
12663
|
+
ensure(subagentId) {
|
|
12664
|
+
let snap = this.perSubagent.get(subagentId);
|
|
12665
|
+
if (!snap) {
|
|
12666
|
+
const meta = this.metaLookup?.(subagentId);
|
|
12667
|
+
snap = {
|
|
12668
|
+
subagentId,
|
|
12669
|
+
provider: meta?.provider,
|
|
12670
|
+
model: meta?.model,
|
|
12671
|
+
input: 0,
|
|
12672
|
+
output: 0,
|
|
12673
|
+
cacheRead: 0,
|
|
12674
|
+
cacheWrite: 0,
|
|
12675
|
+
cost: 0,
|
|
12676
|
+
toolCalls: 0,
|
|
12677
|
+
iterations: 0,
|
|
12678
|
+
startedAt: Date.now(),
|
|
12679
|
+
lastEventAt: Date.now()
|
|
12680
|
+
};
|
|
12681
|
+
this.perSubagent.set(subagentId, snap);
|
|
12706
12682
|
}
|
|
12707
|
-
|
|
12708
|
-
|
|
12709
|
-
|
|
12710
|
-
|
|
12711
|
-
|
|
12712
|
-
|
|
12683
|
+
return snap;
|
|
12684
|
+
}
|
|
12685
|
+
onProviderResponse(e) {
|
|
12686
|
+
const snap = this.ensure(e.subagentId);
|
|
12687
|
+
const p = e.payload;
|
|
12688
|
+
const usage = p?.usage;
|
|
12689
|
+
if (!usage) return;
|
|
12690
|
+
snap.input += usage.input ?? 0;
|
|
12691
|
+
snap.output += usage.output ?? 0;
|
|
12692
|
+
snap.cacheRead += usage.cacheRead ?? 0;
|
|
12693
|
+
snap.cacheWrite += usage.cacheWrite ?? 0;
|
|
12694
|
+
this.total.input += usage.input ?? 0;
|
|
12695
|
+
this.total.output += usage.output ?? 0;
|
|
12696
|
+
this.total.cacheRead += usage.cacheRead ?? 0;
|
|
12697
|
+
this.total.cacheWrite += usage.cacheWrite ?? 0;
|
|
12698
|
+
const price = this.priceLookup?.(e.subagentId, snap.provider, snap.model);
|
|
12699
|
+
if (price) {
|
|
12700
|
+
const delta = (usage.input ?? 0) / 1e6 * (price.input ?? 0) + (usage.output ?? 0) / 1e6 * (price.output ?? 0) + (usage.cacheRead ?? 0) / 1e6 * (price.cacheRead ?? 0) + (usage.cacheWrite ?? 0) / 1e6 * (price.cacheWrite ?? 0);
|
|
12701
|
+
snap.cost += delta;
|
|
12702
|
+
this.total.cost += delta;
|
|
12713
12703
|
}
|
|
12714
|
-
|
|
12715
|
-
|
|
12716
|
-
|
|
12717
|
-
|
|
12718
|
-
|
|
12719
|
-
|
|
12720
|
-
|
|
12721
|
-
|
|
12722
|
-
|
|
12704
|
+
snap.lastEventAt = e.ts;
|
|
12705
|
+
}
|
|
12706
|
+
onToolExecuted(e) {
|
|
12707
|
+
const snap = this.ensure(e.subagentId);
|
|
12708
|
+
snap.toolCalls += 1;
|
|
12709
|
+
snap.lastEventAt = e.ts;
|
|
12710
|
+
}
|
|
12711
|
+
onIterationStarted(e) {
|
|
12712
|
+
const snap = this.ensure(e.subagentId);
|
|
12713
|
+
snap.iterations += 1;
|
|
12714
|
+
snap.lastEventAt = e.ts;
|
|
12715
|
+
}
|
|
12716
|
+
};
|
|
12717
|
+
|
|
12718
|
+
// src/coordination/large-answer-store.ts
|
|
12719
|
+
var LargeAnswerStore = class {
|
|
12720
|
+
/**
|
|
12721
|
+
* Responses above this size (in characters) are stored out-of-context.
|
|
12722
|
+
* Below this, the full answer is returned inline (no overhead).
|
|
12723
|
+
* Default: 2000 chars ≈ 400-600 tokens.
|
|
12724
|
+
*/
|
|
12725
|
+
sizeThreshold;
|
|
12726
|
+
store = /* @__PURE__ */ new Map();
|
|
12727
|
+
constructor(sizeThreshold = 2e3) {
|
|
12728
|
+
this.sizeThreshold = sizeThreshold;
|
|
12729
|
+
}
|
|
12730
|
+
/**
|
|
12731
|
+
* Store a value, returning a summary + key for inline use.
|
|
12732
|
+
* If the value is below sizeThreshold, returns it as-is (no store entry).
|
|
12733
|
+
*/
|
|
12734
|
+
storeAnswer(value) {
|
|
12735
|
+
if (value === void 0 || value === null) {
|
|
12736
|
+
return { summary: String(value), inline: true };
|
|
12723
12737
|
}
|
|
12724
|
-
|
|
12725
|
-
|
|
12726
|
-
|
|
12727
|
-
|
|
12728
|
-
for (const c of e.concerns) {
|
|
12729
|
-
if (c.severity === "blocking") lines.push(` - ${c.description}`);
|
|
12730
|
-
}
|
|
12731
|
-
}
|
|
12732
|
-
lines.push("");
|
|
12738
|
+
const serialized = typeof value === "string" ? value : JSON.stringify(value);
|
|
12739
|
+
const size = serialized.length;
|
|
12740
|
+
if (size <= this.sizeThreshold) {
|
|
12741
|
+
return { summary: serialized.slice(0, 500), inline: true };
|
|
12733
12742
|
}
|
|
12734
|
-
|
|
12743
|
+
const key = `a-${hashStr(serialized)}`;
|
|
12744
|
+
this.store.set(key, {
|
|
12745
|
+
key,
|
|
12746
|
+
value,
|
|
12747
|
+
size,
|
|
12748
|
+
storedAt: Date.now()
|
|
12749
|
+
});
|
|
12750
|
+
return {
|
|
12751
|
+
key,
|
|
12752
|
+
summary: `[stored: ${size} chars \u2014 use roll_up or ask_result tool to retrieve, key=${key}]`,
|
|
12753
|
+
inline: false
|
|
12754
|
+
};
|
|
12735
12755
|
}
|
|
12736
|
-
|
|
12737
|
-
|
|
12738
|
-
|
|
12756
|
+
/**
|
|
12757
|
+
* Retrieve a previously stored answer by its key.
|
|
12758
|
+
* Returns undefined if the key is unknown or the store was cleared.
|
|
12759
|
+
*/
|
|
12760
|
+
retrieveAnswer(key) {
|
|
12761
|
+
return this.store.get(key)?.value;
|
|
12762
|
+
}
|
|
12763
|
+
/**
|
|
12764
|
+
* Check if a key exists in the store.
|
|
12765
|
+
*/
|
|
12766
|
+
hasAnswer(key) {
|
|
12767
|
+
return this.store.has(key);
|
|
12768
|
+
}
|
|
12769
|
+
/** Number of stored entries. */
|
|
12770
|
+
get size() {
|
|
12771
|
+
return this.store.size;
|
|
12772
|
+
}
|
|
12773
|
+
/** Total characters stored. */
|
|
12774
|
+
get totalChars() {
|
|
12775
|
+
let total = 0;
|
|
12776
|
+
for (const e of this.store.values()) total += e.size;
|
|
12777
|
+
return total;
|
|
12778
|
+
}
|
|
12779
|
+
/** Clear all stored entries. Call at the end of a director run. */
|
|
12780
|
+
clear() {
|
|
12781
|
+
this.store.clear();
|
|
12739
12782
|
}
|
|
12740
12783
|
};
|
|
12784
|
+
function hashStr(s) {
|
|
12785
|
+
let h = 5381;
|
|
12786
|
+
for (let i = 0; i < s.length; i++) {
|
|
12787
|
+
h = h * 33 ^ s.charCodeAt(i);
|
|
12788
|
+
}
|
|
12789
|
+
return (h >>> 0).toString(36);
|
|
12790
|
+
}
|
|
12791
|
+
var ROLE_TO_PHASE = (() => {
|
|
12792
|
+
const map = {};
|
|
12793
|
+
for (const [phase, defs] of Object.entries(AGENTS_BY_PHASE)) {
|
|
12794
|
+
for (const def of defs) {
|
|
12795
|
+
const role = def.config.role;
|
|
12796
|
+
if (role) map[role] = phase;
|
|
12797
|
+
}
|
|
12798
|
+
}
|
|
12799
|
+
return map;
|
|
12800
|
+
})();
|
|
12801
|
+
function phaseForRole(role) {
|
|
12802
|
+
return role ? ROLE_TO_PHASE[role] : void 0;
|
|
12803
|
+
}
|
|
12804
|
+
function resolveModelMatrix(matrix, role) {
|
|
12805
|
+
if (!matrix) return void 0;
|
|
12806
|
+
if (role && matrix[role]) return matrix[role];
|
|
12807
|
+
const phase = phaseForRole(role);
|
|
12808
|
+
if (phase && matrix[phase]) return matrix[phase];
|
|
12809
|
+
if (matrix["*"]) return matrix["*"];
|
|
12810
|
+
return void 0;
|
|
12811
|
+
}
|
|
12741
12812
|
|
|
12742
12813
|
// src/coordination/director.ts
|
|
12743
12814
|
var FleetSpawnBudgetError = class extends Error {
|
|
@@ -12909,6 +12980,9 @@ var Director = class _Director {
|
|
|
12909
12980
|
maxLeaderContextLoad;
|
|
12910
12981
|
/** Provider's max context window in tokens. */
|
|
12911
12982
|
maxContext;
|
|
12983
|
+
/** Per-task model matrix (static record or live getter); resolved
|
|
12984
|
+
* per-spawn when no explicit model is set. */
|
|
12985
|
+
modelMatrix;
|
|
12912
12986
|
/**
|
|
12913
12987
|
* When set by `workComplete()`, the director stops dispatching new tasks
|
|
12914
12988
|
* and terminates all running subagents. Used when the director's LLM decides
|
|
@@ -12939,20 +13013,23 @@ var Director = class _Director {
|
|
|
12939
13013
|
this.maxBudgetExtensions = opts.maxBudgetExtensions ?? 5;
|
|
12940
13014
|
this.maxLeaderContextLoad = opts.maxLeaderContextLoad ?? 0.85;
|
|
12941
13015
|
this.maxContext = opts.maxContext ?? 128e3;
|
|
13016
|
+
this.modelMatrix = opts.modelMatrix;
|
|
12942
13017
|
this.sessionsRoot = opts.sessionsRoot;
|
|
12943
13018
|
this.directorRunId = opts.directorRunId ?? this.id;
|
|
12944
|
-
this.stateCheckpoint = opts.stateCheckpointPath ? new DirectorStateCheckpoint(
|
|
12945
|
-
|
|
12946
|
-
|
|
12947
|
-
|
|
12948
|
-
|
|
12949
|
-
|
|
12950
|
-
|
|
13019
|
+
this.stateCheckpoint = opts.stateCheckpointPath ? new DirectorStateCheckpoint(
|
|
13020
|
+
opts.stateCheckpointPath,
|
|
13021
|
+
{
|
|
13022
|
+
directorRunId: this.id,
|
|
13023
|
+
maxSpawns: opts.maxSpawns,
|
|
13024
|
+
spawnDepth: this.spawnDepth,
|
|
13025
|
+
maxSpawnDepth: this.maxSpawnDepth,
|
|
13026
|
+
directorBudget: opts.directorBudget
|
|
13027
|
+
},
|
|
13028
|
+
opts.checkpointDebounceMs ?? 250
|
|
13029
|
+
) : null;
|
|
12951
13030
|
this.fleetManager = opts.fleetManager;
|
|
12952
13031
|
if (this.sharedScratchpadPath) {
|
|
12953
|
-
void fsp.mkdir(this.sharedScratchpadPath, { recursive: true }).catch(
|
|
12954
|
-
(err) => this.logShutdownError("shared_scratchpad_mkdir", err)
|
|
12955
|
-
);
|
|
13032
|
+
void fsp.mkdir(this.sharedScratchpadPath, { recursive: true }).catch((err) => this.logShutdownError("shared_scratchpad_mkdir", err));
|
|
12956
13033
|
}
|
|
12957
13034
|
this.transport = new InMemoryBridgeTransport();
|
|
12958
13035
|
this.bridge = new InMemoryAgentBridge(
|
|
@@ -13130,7 +13207,12 @@ var Director = class _Director {
|
|
|
13130
13207
|
*/
|
|
13131
13208
|
workComplete() {
|
|
13132
13209
|
this.workCompleteFlag = true;
|
|
13133
|
-
this.fleet.emit({
|
|
13210
|
+
this.fleet.emit({
|
|
13211
|
+
subagentId: this.id,
|
|
13212
|
+
ts: Date.now(),
|
|
13213
|
+
type: "director.work_complete",
|
|
13214
|
+
payload: {}
|
|
13215
|
+
});
|
|
13134
13216
|
}
|
|
13135
13217
|
/** Returns true if `workComplete()` has been called on this director. */
|
|
13136
13218
|
isWorkComplete() {
|
|
@@ -13262,13 +13344,25 @@ var Director = class _Director {
|
|
|
13262
13344
|
"workComplete() has been called \u2014 director closed further spawning"
|
|
13263
13345
|
);
|
|
13264
13346
|
}
|
|
13347
|
+
if (!config.model && this.modelMatrix) {
|
|
13348
|
+
const matrix = typeof this.modelMatrix === "function" ? this.modelMatrix() : this.modelMatrix;
|
|
13349
|
+
const entry = resolveModelMatrix(matrix, config.role);
|
|
13350
|
+
if (entry) {
|
|
13351
|
+
config.model = entry.model;
|
|
13352
|
+
if (entry.provider) config.provider = entry.provider;
|
|
13353
|
+
}
|
|
13354
|
+
}
|
|
13265
13355
|
if (this.fleetManager) {
|
|
13266
13356
|
const rejection = this.fleetManager.canSpawn(config);
|
|
13267
13357
|
if (rejection) {
|
|
13268
|
-
if (rejection.kind === "max_spawn_depth")
|
|
13269
|
-
|
|
13270
|
-
if (rejection.kind === "
|
|
13271
|
-
|
|
13358
|
+
if (rejection.kind === "max_spawn_depth")
|
|
13359
|
+
throw new FleetSpawnBudgetError("max_spawn_depth", rejection.limit, rejection.observed);
|
|
13360
|
+
if (rejection.kind === "max_spawns")
|
|
13361
|
+
throw new FleetSpawnBudgetError("max_spawns", rejection.limit, rejection.observed);
|
|
13362
|
+
if (rejection.kind === "max_cost_usd")
|
|
13363
|
+
throw new FleetCostCapError(rejection.limit, rejection.observed);
|
|
13364
|
+
if (rejection.kind === "max_context_load")
|
|
13365
|
+
throw new FleetContextOverflowError(rejection.limit, rejection.observed);
|
|
13272
13366
|
}
|
|
13273
13367
|
} else {
|
|
13274
13368
|
if (this.spawnDepth >= this.maxSpawnDepth) {
|
|
@@ -13298,7 +13392,9 @@ var Director = class _Director {
|
|
|
13298
13392
|
this.fleetManager.assignNicknameAndRecord(config);
|
|
13299
13393
|
} else {
|
|
13300
13394
|
config.name = assignNickname(role, this._usedNicknames);
|
|
13301
|
-
this._usedNicknames.add(
|
|
13395
|
+
this._usedNicknames.add(
|
|
13396
|
+
config.name.split(" ")[0].toLowerCase().replace(/[^a-z0-9-]/g, "-")
|
|
13397
|
+
);
|
|
13302
13398
|
}
|
|
13303
13399
|
}
|
|
13304
13400
|
result = await this.coordinator.spawn(config);
|
|
@@ -13320,6 +13416,20 @@ var Director = class _Director {
|
|
|
13320
13416
|
);
|
|
13321
13417
|
this.coordinator.setSubagentBridge(result.subagentId, subagentBridge);
|
|
13322
13418
|
this.subagentBridges.set(result.subagentId, subagentBridge);
|
|
13419
|
+
this.fleet.emit({
|
|
13420
|
+
subagentId: result.subagentId,
|
|
13421
|
+
ts: Date.now(),
|
|
13422
|
+
type: "subagent.spawned",
|
|
13423
|
+
payload: {
|
|
13424
|
+
subagentId: result.subagentId,
|
|
13425
|
+
taskId: "",
|
|
13426
|
+
// taskId will be set when assign() is called
|
|
13427
|
+
name: config.name,
|
|
13428
|
+
role: config.role,
|
|
13429
|
+
provider: config.provider,
|
|
13430
|
+
model: config.model
|
|
13431
|
+
}
|
|
13432
|
+
});
|
|
13323
13433
|
if (!this.fleetManager) {
|
|
13324
13434
|
this.manifestEntries.set(result.subagentId, {
|
|
13325
13435
|
subagentId: result.subagentId,
|
|
@@ -13519,7 +13629,11 @@ var Director = class _Director {
|
|
|
13519
13629
|
subagentId: taskWithId.subagentId ?? "unassigned",
|
|
13520
13630
|
taskId: taskWithId.id,
|
|
13521
13631
|
status: "stopped",
|
|
13522
|
-
error: {
|
|
13632
|
+
error: {
|
|
13633
|
+
kind: "aborted_by_parent",
|
|
13634
|
+
message: "Director called workComplete() \u2014 no further tasks will run",
|
|
13635
|
+
retryable: false
|
|
13636
|
+
},
|
|
13523
13637
|
iterations: 0,
|
|
13524
13638
|
toolCalls: 0,
|
|
13525
13639
|
durationMs: 0
|