@wrongstack/core 0.32.0 → 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 +3028 -2976
- package/dist/coordination/index.js.map +1 -1
- package/dist/defaults/index.d.ts +8 -8
- package/dist/defaults/index.js +1166 -1104
- 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 +1467 -1214
- 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-DLsUjOyB.d.ts → null-fleet-bus-FvgHnZah.d.ts} +150 -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/dist/index.js
CHANGED
|
@@ -1295,11 +1295,62 @@ function deepMerge(a, b) {
|
|
|
1295
1295
|
return out;
|
|
1296
1296
|
}
|
|
1297
1297
|
|
|
1298
|
+
// src/utils/term.ts
|
|
1299
|
+
var hasStdout = () => typeof process !== "undefined" && !!process.stdout;
|
|
1300
|
+
var hasStdin = () => typeof process !== "undefined" && !!process.stdin;
|
|
1301
|
+
function isStdoutTTY() {
|
|
1302
|
+
return hasStdout() && Boolean(process.stdout.isTTY);
|
|
1303
|
+
}
|
|
1304
|
+
function isStdinTTY() {
|
|
1305
|
+
return hasStdin() && Boolean(process.stdin.isTTY);
|
|
1306
|
+
}
|
|
1307
|
+
function isInteractive() {
|
|
1308
|
+
return isStdinTTY() && isStdoutTTY();
|
|
1309
|
+
}
|
|
1310
|
+
function getTermSize() {
|
|
1311
|
+
if (!hasStdout()) return { rows: 24, cols: 80 };
|
|
1312
|
+
return {
|
|
1313
|
+
rows: process.stdout.rows ?? 24,
|
|
1314
|
+
cols: process.stdout.columns ?? 80
|
|
1315
|
+
};
|
|
1316
|
+
}
|
|
1317
|
+
function onResize(cb, stream = process.stdout) {
|
|
1318
|
+
if (!stream || typeof stream.on !== "function") return () => {
|
|
1319
|
+
};
|
|
1320
|
+
const handler = () => {
|
|
1321
|
+
cb({
|
|
1322
|
+
rows: stream.rows ?? 24,
|
|
1323
|
+
cols: stream.columns ?? 80
|
|
1324
|
+
});
|
|
1325
|
+
};
|
|
1326
|
+
stream.on("resize", handler);
|
|
1327
|
+
return () => {
|
|
1328
|
+
stream.off("resize", handler);
|
|
1329
|
+
};
|
|
1330
|
+
}
|
|
1331
|
+
function setRawMode(input, mode) {
|
|
1332
|
+
if (!input || input.isTTY !== true) return false;
|
|
1333
|
+
if (typeof input.setRawMode !== "function") return false;
|
|
1334
|
+
input.setRawMode(mode);
|
|
1335
|
+
return true;
|
|
1336
|
+
}
|
|
1337
|
+
function writeTo(s, stream) {
|
|
1338
|
+
if (!stream || typeof stream.write !== "function") return false;
|
|
1339
|
+
stream.write(s);
|
|
1340
|
+
return true;
|
|
1341
|
+
}
|
|
1342
|
+
function writeOut(s, stream = process.stdout) {
|
|
1343
|
+
return writeTo(s, stream);
|
|
1344
|
+
}
|
|
1345
|
+
function writeErr(s, stream = process.stderr) {
|
|
1346
|
+
return writeTo(s, stream);
|
|
1347
|
+
}
|
|
1348
|
+
|
|
1298
1349
|
// src/utils/color.ts
|
|
1299
1350
|
var isColorTty = () => {
|
|
1300
1351
|
if (process.env.NO_COLOR) return false;
|
|
1301
1352
|
if (process.env.FORCE_COLOR) return true;
|
|
1302
|
-
return
|
|
1353
|
+
return isStdoutTTY();
|
|
1303
1354
|
};
|
|
1304
1355
|
var COLOR = isColorTty();
|
|
1305
1356
|
var wrap = (open5, close) => (s) => COLOR ? `\x1B[${open5}m${s}\x1B[${close}m` : s;
|
|
@@ -1403,10 +1454,10 @@ var DefaultLogger = class _DefaultLogger {
|
|
|
1403
1454
|
if (r <= LEVEL_RANK.warn || this.level === "debug" || this.level === "trace") {
|
|
1404
1455
|
const head = `${color.dim(ts)} ${COLORS[level](level.toUpperCase().padEnd(5))} ${msg}`;
|
|
1405
1456
|
if (ctx !== void 0) {
|
|
1406
|
-
|
|
1457
|
+
writeErr(`${head} ${formatCtx(ctx)}
|
|
1407
1458
|
`);
|
|
1408
1459
|
} else {
|
|
1409
|
-
|
|
1460
|
+
writeErr(`${head}
|
|
1410
1461
|
`);
|
|
1411
1462
|
}
|
|
1412
1463
|
}
|
|
@@ -5487,9 +5538,9 @@ var DefaultMemoryStore = class {
|
|
|
5487
5538
|
async readAll() {
|
|
5488
5539
|
const parts = [];
|
|
5489
5540
|
for (const scope of ["project-agents", "project-memory", "user-memory"]) {
|
|
5490
|
-
const
|
|
5491
|
-
if (
|
|
5492
|
-
parts.push(`> \u26A0\uFE0F Memory write error (${labelOf(scope)}): ${
|
|
5541
|
+
const writeErr2 = this.writeErrors.get(scope);
|
|
5542
|
+
if (writeErr2) {
|
|
5543
|
+
parts.push(`> \u26A0\uFE0F Memory write error (${labelOf(scope)}): ${writeErr2.message}`);
|
|
5493
5544
|
}
|
|
5494
5545
|
const body = await this.read(scope);
|
|
5495
5546
|
if (body.trim()) parts.push(`## ${labelOf(scope)}
|
|
@@ -13686,637 +13737,911 @@ function buildGoalPreamble(goal) {
|
|
|
13686
13737
|
"BEGIN.]"
|
|
13687
13738
|
].join("\n");
|
|
13688
13739
|
}
|
|
13689
|
-
|
|
13690
|
-
// src/coordination/director.ts
|
|
13691
13740
|
init_atomic_write();
|
|
13692
|
-
|
|
13693
|
-
|
|
13694
|
-
|
|
13741
|
+
var DEFAULT_MAX_TARGET_FILES = 30;
|
|
13742
|
+
var CollabSession = class extends EventEmitter {
|
|
13743
|
+
sessionId;
|
|
13744
|
+
options;
|
|
13745
|
+
snapshot;
|
|
13746
|
+
director;
|
|
13747
|
+
fleetBus;
|
|
13748
|
+
subagentIds = /* @__PURE__ */ new Map();
|
|
13749
|
+
// role → subagentId
|
|
13750
|
+
bugs = /* @__PURE__ */ new Map();
|
|
13751
|
+
plans = /* @__PURE__ */ new Map();
|
|
13752
|
+
evaluations = /* @__PURE__ */ new Map();
|
|
13753
|
+
disposers = new Array();
|
|
13754
|
+
settled = false;
|
|
13755
|
+
timeoutMs;
|
|
13756
|
+
cancelled = false;
|
|
13757
|
+
alerts = [];
|
|
13758
|
+
/** Tracks tool call counts per subagent for progress-based timeout decisions. */
|
|
13759
|
+
progressBySubagent = /* @__PURE__ */ new Map();
|
|
13760
|
+
/** Last tool call count when a timeout warning was handled. */
|
|
13761
|
+
lastTimeoutProgress = /* @__PURE__ */ new Map();
|
|
13762
|
+
/** Session-level timeout timer handle (cleared on cancel or natural completion). */
|
|
13763
|
+
_timeoutTimer;
|
|
13764
|
+
constructor(director, fleetBus, options) {
|
|
13765
|
+
super();
|
|
13766
|
+
this.sessionId = randomUUID();
|
|
13767
|
+
this.options = options;
|
|
13768
|
+
this.director = director;
|
|
13769
|
+
this.fleetBus = fleetBus;
|
|
13770
|
+
this.timeoutMs = options.timeoutMs ?? 10 * 60 * 1e3;
|
|
13771
|
+
if (options.prebuiltSnapshot) {
|
|
13772
|
+
this.snapshot = options.prebuiltSnapshot;
|
|
13773
|
+
} else {
|
|
13774
|
+
this.snapshot = {
|
|
13775
|
+
id: this.sessionId,
|
|
13776
|
+
createdAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
13777
|
+
files: []
|
|
13778
|
+
};
|
|
13779
|
+
}
|
|
13780
|
+
}
|
|
13781
|
+
get id() {
|
|
13782
|
+
return this.sessionId;
|
|
13783
|
+
}
|
|
13784
|
+
getSessionAlerts() {
|
|
13785
|
+
return [...this.alerts];
|
|
13786
|
+
}
|
|
13787
|
+
isCancelled() {
|
|
13788
|
+
return this.cancelled;
|
|
13789
|
+
}
|
|
13695
13790
|
/**
|
|
13696
|
-
*
|
|
13697
|
-
*
|
|
13698
|
-
*
|
|
13791
|
+
* Snapshot of role → subagentId map. The Director calls coordinator.stop()
|
|
13792
|
+
* for each agent when cancelling the session, using this map to enumerate
|
|
13793
|
+
* all three collab agents.
|
|
13699
13794
|
*/
|
|
13700
|
-
|
|
13701
|
-
|
|
13702
|
-
constructor(sizeThreshold = 2e3) {
|
|
13703
|
-
this.sizeThreshold = sizeThreshold;
|
|
13795
|
+
getSubagentIds() {
|
|
13796
|
+
return new Map(this.subagentIds);
|
|
13704
13797
|
}
|
|
13705
13798
|
/**
|
|
13706
|
-
*
|
|
13707
|
-
*
|
|
13799
|
+
* Returns the effective file limit for this session.
|
|
13800
|
+
* Priority: explicit `maxTargetFiles` > dynamic from `contextWindow` > `DEFAULT_MAX_TARGET_FILES`.
|
|
13708
13801
|
*/
|
|
13709
|
-
|
|
13710
|
-
if (
|
|
13711
|
-
return
|
|
13802
|
+
effectiveFileLimit() {
|
|
13803
|
+
if (this.options.maxTargetFiles !== void 0) {
|
|
13804
|
+
return this.options.maxTargetFiles;
|
|
13712
13805
|
}
|
|
13713
|
-
|
|
13714
|
-
|
|
13715
|
-
if (size <= this.sizeThreshold) {
|
|
13716
|
-
return { summary: serialized.slice(0, 500), inline: true };
|
|
13806
|
+
if (this.options.contextWindow !== void 0) {
|
|
13807
|
+
return Math.max(5, Math.floor(this.options.contextWindow * 0.4 / 2e3));
|
|
13717
13808
|
}
|
|
13718
|
-
|
|
13719
|
-
this.store.set(key, {
|
|
13720
|
-
key,
|
|
13721
|
-
value,
|
|
13722
|
-
size,
|
|
13723
|
-
storedAt: Date.now()
|
|
13724
|
-
});
|
|
13725
|
-
return {
|
|
13726
|
-
key,
|
|
13727
|
-
summary: `[stored: ${size} chars \u2014 use roll_up or ask_result tool to retrieve, key=${key}]`,
|
|
13728
|
-
inline: false
|
|
13729
|
-
};
|
|
13809
|
+
return DEFAULT_MAX_TARGET_FILES;
|
|
13730
13810
|
}
|
|
13731
|
-
|
|
13732
|
-
|
|
13733
|
-
|
|
13734
|
-
|
|
13735
|
-
|
|
13736
|
-
|
|
13811
|
+
async buildSnapshot() {
|
|
13812
|
+
if (this.snapshot.files.length > 0) return this.snapshot;
|
|
13813
|
+
const allFiles = [];
|
|
13814
|
+
for (const pattern of this.options.targetPaths) {
|
|
13815
|
+
const expanded = await expandGlob(pattern);
|
|
13816
|
+
allFiles.push(...expanded);
|
|
13817
|
+
}
|
|
13818
|
+
const limit = this.effectiveFileLimit();
|
|
13819
|
+
if (allFiles.length > limit) {
|
|
13820
|
+
const hint = this.options.contextWindow ? `contextWindow=${this.options.contextWindow} \u2192 calculated limit=${limit}` : `default limit=${DEFAULT_MAX_TARGET_FILES}`;
|
|
13821
|
+
throw new Error(
|
|
13822
|
+
`[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.`
|
|
13823
|
+
);
|
|
13824
|
+
}
|
|
13825
|
+
for (const filePath of allFiles) {
|
|
13826
|
+
try {
|
|
13827
|
+
const content = await fsp3.readFile(filePath, "utf8");
|
|
13828
|
+
const ext = filePath.split(".").pop() ?? "";
|
|
13829
|
+
const language = ext === "ts" || ext === "tsx" ? "typescript" : ext === "js" || ext === "jsx" ? "javascript" : ext === "md" ? "markdown" : ext === "json" ? "json" : void 0;
|
|
13830
|
+
this.snapshot.files.push({ path: filePath, content, language });
|
|
13831
|
+
} catch {
|
|
13832
|
+
this.snapshot.files.push({ path: filePath, content: "", language: void 0 });
|
|
13833
|
+
}
|
|
13834
|
+
}
|
|
13835
|
+
return this.snapshot;
|
|
13737
13836
|
}
|
|
13738
13837
|
/**
|
|
13739
|
-
*
|
|
13838
|
+
* Cancel the session. Emits director.cancel_collab on the FleetBus so all
|
|
13839
|
+
* collab agents finish early. The session-level timeout timer is also cleared.
|
|
13840
|
+
* Safe to call multiple times (idempotent after first call).
|
|
13740
13841
|
*/
|
|
13741
|
-
|
|
13742
|
-
|
|
13743
|
-
|
|
13744
|
-
|
|
13745
|
-
|
|
13746
|
-
|
|
13747
|
-
|
|
13748
|
-
|
|
13749
|
-
|
|
13750
|
-
|
|
13751
|
-
|
|
13752
|
-
|
|
13842
|
+
cancel(reason = "Director cancelled collab session") {
|
|
13843
|
+
if (this.settled) return;
|
|
13844
|
+
this.cancelled = true;
|
|
13845
|
+
if (this._timeoutTimer) {
|
|
13846
|
+
clearTimeout(this._timeoutTimer);
|
|
13847
|
+
this._timeoutTimer = void 0;
|
|
13848
|
+
}
|
|
13849
|
+
this.fleetBus.emit({
|
|
13850
|
+
subagentId: this.director.id,
|
|
13851
|
+
ts: Date.now(),
|
|
13852
|
+
type: "director.cancel_collab",
|
|
13853
|
+
payload: { sessionId: this.sessionId, reason, cancelledAt: (/* @__PURE__ */ new Date()).toISOString() }
|
|
13854
|
+
});
|
|
13855
|
+
this.fleetBus.emit({
|
|
13856
|
+
subagentId: this.director.id,
|
|
13857
|
+
ts: Date.now(),
|
|
13858
|
+
type: "collab.cancelled",
|
|
13859
|
+
payload: { sessionId: this.sessionId, reason }
|
|
13860
|
+
});
|
|
13753
13861
|
}
|
|
13754
|
-
|
|
13755
|
-
|
|
13756
|
-
this.
|
|
13862
|
+
async start() {
|
|
13863
|
+
if (this.settled) throw new Error("session already settled");
|
|
13864
|
+
this.settled = true;
|
|
13865
|
+
await this.buildSnapshot();
|
|
13866
|
+
this.wireFleetBus();
|
|
13867
|
+
const [bugHunterId, refactorPlannerId, criticId] = await Promise.all([
|
|
13868
|
+
this.spawnAgent("bug-hunter", this.buildBugHunterTask()),
|
|
13869
|
+
this.spawnAgent("refactor-planner", this.buildRefactorPlannerTask()),
|
|
13870
|
+
this.spawnAgent("critic", this.buildCriticTask())
|
|
13871
|
+
]);
|
|
13872
|
+
this.subagentIds.set("bug-hunter", bugHunterId);
|
|
13873
|
+
this.subagentIds.set("refactor-planner", refactorPlannerId);
|
|
13874
|
+
this.subagentIds.set("critic", criticId);
|
|
13875
|
+
const timeout = new Promise((_, reject) => {
|
|
13876
|
+
this._timeoutTimer = setTimeout(() => {
|
|
13877
|
+
this.cancel("Session-level timeout reached");
|
|
13878
|
+
reject(new Error(`CollabSession timed out after ${this.timeoutMs}ms`));
|
|
13879
|
+
}, this.timeoutMs);
|
|
13880
|
+
});
|
|
13881
|
+
let results = null;
|
|
13882
|
+
try {
|
|
13883
|
+
results = await Promise.race([
|
|
13884
|
+
Promise.all([
|
|
13885
|
+
this.director.awaitTasks([bugHunterId]),
|
|
13886
|
+
this.director.awaitTasks([refactorPlannerId]),
|
|
13887
|
+
this.director.awaitTasks([criticId])
|
|
13888
|
+
]),
|
|
13889
|
+
timeout
|
|
13890
|
+
]);
|
|
13891
|
+
} catch (err) {
|
|
13892
|
+
if (this._timeoutTimer) {
|
|
13893
|
+
clearTimeout(this._timeoutTimer);
|
|
13894
|
+
this._timeoutTimer = void 0;
|
|
13895
|
+
}
|
|
13896
|
+
this.cleanup();
|
|
13897
|
+
const error = err instanceof Error ? err : new Error(String(err));
|
|
13898
|
+
this.emit("session.error", error);
|
|
13899
|
+
throw error;
|
|
13900
|
+
}
|
|
13901
|
+
for (const result of results.flat()) {
|
|
13902
|
+
await this.parseAndEmit(result);
|
|
13903
|
+
}
|
|
13904
|
+
const report = this.assembleReport();
|
|
13905
|
+
this.cleanup();
|
|
13906
|
+
this.emit("session.done", report);
|
|
13907
|
+
return report;
|
|
13757
13908
|
}
|
|
13758
|
-
|
|
13759
|
-
|
|
13760
|
-
|
|
13761
|
-
|
|
13762
|
-
|
|
13909
|
+
async parseAndEmit(result) {
|
|
13910
|
+
if (result.status !== "success" || result.result == null) return;
|
|
13911
|
+
const text = typeof result.result === "string" ? result.result : JSON.stringify(result.result);
|
|
13912
|
+
for (const obj of this.extractJsonObjects(text)) {
|
|
13913
|
+
const type = "finding" in obj ? "bug.found" : "plan" in obj ? "refactor.plan" : "evaluation" in obj ? "critic.evaluation" : null;
|
|
13914
|
+
if (!type) continue;
|
|
13915
|
+
this.fleetBus.emit({
|
|
13916
|
+
subagentId: result.subagentId,
|
|
13917
|
+
taskId: result.taskId,
|
|
13918
|
+
ts: Date.now(),
|
|
13919
|
+
type,
|
|
13920
|
+
payload: obj
|
|
13921
|
+
});
|
|
13922
|
+
}
|
|
13763
13923
|
}
|
|
13764
|
-
|
|
13765
|
-
|
|
13766
|
-
|
|
13767
|
-
|
|
13768
|
-
|
|
13769
|
-
|
|
13770
|
-
|
|
13771
|
-
|
|
13772
|
-
|
|
13773
|
-
|
|
13774
|
-
|
|
13775
|
-
|
|
13776
|
-
|
|
13777
|
-
|
|
13778
|
-
|
|
13779
|
-
|
|
13780
|
-
|
|
13781
|
-
|
|
13782
|
-
|
|
13783
|
-
|
|
13784
|
-
|
|
13785
|
-
|
|
13786
|
-
|
|
13787
|
-
|
|
13788
|
-
|
|
13789
|
-
|
|
13790
|
-
|
|
13791
|
-
|
|
13792
|
-
|
|
13793
|
-
|
|
13794
|
-
|
|
13795
|
-
|
|
13796
|
-
|
|
13797
|
-
7. Wind down when satisfied. When the results are good enough, call
|
|
13798
|
-
work_complete \u2014 no new subagents will spawn and queued tasks complete
|
|
13799
|
-
as aborted. Running subagents finish naturally. Call terminate_subagent
|
|
13800
|
-
only for ones you need to stop immediately.`;
|
|
13801
|
-
var DEFAULT_SUBAGENT_BASELINE = `You are a subagent operating under a Director. You were spawned to handle
|
|
13802
|
-
a specific slice of a larger plan \u2014 do that slice well and report back.
|
|
13803
|
-
|
|
13804
|
-
Bridge contract:
|
|
13805
|
-
- You have a parent (the Director). You may call \`request\` on the
|
|
13806
|
-
parent bridge to ask a clarifying question. Use this sparingly; the
|
|
13807
|
-
parent is also working.
|
|
13808
|
-
- You MAY NOT request the parent's system prompt, tool list, or other
|
|
13809
|
-
subagents' context. Those are not yours to read.
|
|
13810
|
-
- Your final task output is what the Director sees. Be concise,
|
|
13811
|
-
structured, and self-contained \u2014 assume the Director will paste your
|
|
13812
|
-
output into its own context.`;
|
|
13813
|
-
function composeDirectorPrompt(parts = {}) {
|
|
13814
|
-
const sections = [];
|
|
13815
|
-
const preamble = parts.directorPreamble ?? DEFAULT_DIRECTOR_PREAMBLE;
|
|
13816
|
-
if (preamble && preamble.trim().length > 0) sections.push(preamble.trim());
|
|
13817
|
-
if (parts.rosterSummary && parts.rosterSummary.trim().length > 0) {
|
|
13818
|
-
sections.push(`Available roles you can spawn:
|
|
13819
|
-
${parts.rosterSummary.trim()}`);
|
|
13820
|
-
}
|
|
13821
|
-
if (parts.basePrompt && parts.basePrompt.trim().length > 0) {
|
|
13822
|
-
sections.push(parts.basePrompt.trim());
|
|
13823
|
-
}
|
|
13824
|
-
return sections.join("\n\n");
|
|
13825
|
-
}
|
|
13826
|
-
function composeSubagentPrompt(parts = {}) {
|
|
13827
|
-
const sections = [];
|
|
13828
|
-
const baseline = parts.baseline ?? DEFAULT_SUBAGENT_BASELINE;
|
|
13829
|
-
if (baseline && baseline.trim().length > 0) sections.push(baseline.trim());
|
|
13830
|
-
if (parts.role && parts.role.trim().length > 0) {
|
|
13831
|
-
sections.push(`Role:
|
|
13832
|
-
${parts.role.trim()}`);
|
|
13833
|
-
}
|
|
13834
|
-
if (parts.task && parts.task.trim().length > 0) {
|
|
13835
|
-
sections.push(`Task:
|
|
13836
|
-
${parts.task.trim()}`);
|
|
13837
|
-
}
|
|
13838
|
-
if (parts.sharedScratchpad && parts.sharedScratchpad.trim().length > 0) {
|
|
13839
|
-
sections.push(
|
|
13840
|
-
`Shared notes:
|
|
13841
|
-
A scratchpad shared with the rest of the fleet is mounted at \`${parts.sharedScratchpad.trim()}\`.
|
|
13842
|
-
- Write your final findings as markdown files there (e.g. \`findings.md\`, \`security.md\`).
|
|
13843
|
-
- Before starting, list the directory and read any sibling files relevant to your task \u2014 they may already contain context you can build on.
|
|
13844
|
-
- Use stable filenames (one file per concern); overwrite instead of appending so the Director sees the latest state.`
|
|
13845
|
-
);
|
|
13846
|
-
}
|
|
13847
|
-
if (parts.override && parts.override.trim().length > 0) {
|
|
13848
|
-
sections.push(parts.override.trim());
|
|
13849
|
-
}
|
|
13850
|
-
return sections.join("\n\n");
|
|
13851
|
-
}
|
|
13852
|
-
function rosterSummaryFromConfigs(roster) {
|
|
13853
|
-
const lines = [];
|
|
13854
|
-
for (const [roleId, cfg] of Object.entries(roster)) {
|
|
13855
|
-
const tag = cfg.provider && cfg.model ? ` (${cfg.provider}/${cfg.model})` : "";
|
|
13856
|
-
const headline = cfg.prompt ? (cfg.prompt.split("\n").find((l) => l.trim().length > 0) ?? "").trim().slice(0, 80) : "";
|
|
13857
|
-
const tail = headline ? ` \u2014 ${headline}` : "";
|
|
13858
|
-
lines.push(`- ${roleId}: ${cfg.name}${tag}${tail}`);
|
|
13859
|
-
}
|
|
13860
|
-
return lines.join("\n");
|
|
13861
|
-
}
|
|
13862
|
-
|
|
13863
|
-
// src/coordination/fleet-bus.ts
|
|
13864
|
-
var FleetBus = class {
|
|
13865
|
-
byId = /* @__PURE__ */ new Map();
|
|
13866
|
-
byType = /* @__PURE__ */ new Map();
|
|
13867
|
-
any = /* @__PURE__ */ new Set();
|
|
13868
|
-
/**
|
|
13869
|
-
* Hook a subagent's EventBus into the fleet. Uses `onAny()` (an alias for
|
|
13870
|
-
* `onPattern('*')`) to forward all events with subagent attribution, so
|
|
13871
|
-
* new kernel event types are automatically forwarded without any manual
|
|
13872
|
-
* registration. `subagent.*` events are excluded because they originate
|
|
13873
|
-
* from MultiAgentHost on the parent bus, not the subagent's own bus.
|
|
13874
|
-
*
|
|
13875
|
-
* Returns a disposer that detaches every subscription; call on
|
|
13876
|
-
* subagent teardown so the listeners don't outlive the run.
|
|
13877
|
-
*/
|
|
13878
|
-
attach(subagentId, bus, taskId) {
|
|
13879
|
-
const off = bus.onAny((type, payload) => {
|
|
13880
|
-
if (type.startsWith("subagent.")) return;
|
|
13881
|
-
this.emit({ subagentId, taskId, ts: Date.now(), type, payload });
|
|
13882
|
-
});
|
|
13883
|
-
return () => {
|
|
13884
|
-
off();
|
|
13885
|
-
};
|
|
13886
|
-
}
|
|
13887
|
-
/** Subscribe to every event from one subagent. */
|
|
13888
|
-
subscribe(subagentId, handler) {
|
|
13889
|
-
let set = this.byId.get(subagentId);
|
|
13890
|
-
if (!set) {
|
|
13891
|
-
set = /* @__PURE__ */ new Set();
|
|
13892
|
-
this.byId.set(subagentId, set);
|
|
13924
|
+
extractJsonObjects(text) {
|
|
13925
|
+
const objects = [];
|
|
13926
|
+
let depth = 0;
|
|
13927
|
+
let start = -1;
|
|
13928
|
+
let inString = false;
|
|
13929
|
+
let escaped = false;
|
|
13930
|
+
for (let i = 0; i < text.length; i++) {
|
|
13931
|
+
const ch = text[i];
|
|
13932
|
+
if (inString) {
|
|
13933
|
+
if (escaped) escaped = false;
|
|
13934
|
+
else if (ch === "\\") escaped = true;
|
|
13935
|
+
else if (ch === '"') inString = false;
|
|
13936
|
+
continue;
|
|
13937
|
+
}
|
|
13938
|
+
if (ch === '"') {
|
|
13939
|
+
inString = true;
|
|
13940
|
+
} else if (ch === "{") {
|
|
13941
|
+
if (depth === 0) start = i;
|
|
13942
|
+
depth++;
|
|
13943
|
+
} else if (ch === "}" && depth > 0) {
|
|
13944
|
+
depth--;
|
|
13945
|
+
if (depth === 0 && start >= 0) {
|
|
13946
|
+
const candidate = text.slice(start, i + 1);
|
|
13947
|
+
try {
|
|
13948
|
+
const parsed = JSON.parse(candidate);
|
|
13949
|
+
if (parsed && typeof parsed === "object" && !Array.isArray(parsed)) {
|
|
13950
|
+
objects.push(parsed);
|
|
13951
|
+
}
|
|
13952
|
+
} catch {
|
|
13953
|
+
}
|
|
13954
|
+
start = -1;
|
|
13955
|
+
}
|
|
13956
|
+
}
|
|
13893
13957
|
}
|
|
13894
|
-
|
|
13895
|
-
return () => {
|
|
13896
|
-
set.delete(handler);
|
|
13897
|
-
};
|
|
13958
|
+
return objects;
|
|
13898
13959
|
}
|
|
13899
|
-
|
|
13900
|
-
|
|
13901
|
-
|
|
13902
|
-
if (!set) {
|
|
13903
|
-
set = /* @__PURE__ */ new Set();
|
|
13904
|
-
this.byType.set(type, set);
|
|
13960
|
+
budgetForRole(role) {
|
|
13961
|
+
if (this.options.budgetOverrides?.[role]) {
|
|
13962
|
+
return this.options.budgetOverrides[role];
|
|
13905
13963
|
}
|
|
13906
|
-
|
|
13907
|
-
|
|
13908
|
-
|
|
13964
|
+
const defaults = {
|
|
13965
|
+
"bug-hunter": { maxIterations: 2e3, maxToolCalls: 5e3, timeoutMs: 10 * 60 * 1e3 },
|
|
13966
|
+
"refactor-planner": { maxIterations: 1500, maxToolCalls: 4e3, timeoutMs: 8 * 60 * 1e3 },
|
|
13967
|
+
"critic": { maxIterations: 1e3, maxToolCalls: 3e3, timeoutMs: 6 * 60 * 1e3 }
|
|
13909
13968
|
};
|
|
13969
|
+
return defaults[role] ?? { maxIterations: 1500, maxToolCalls: 4e3, timeoutMs: 8 * 60 * 1e3 };
|
|
13910
13970
|
}
|
|
13911
|
-
|
|
13912
|
-
|
|
13913
|
-
|
|
13914
|
-
|
|
13915
|
-
|
|
13971
|
+
async spawnAgent(role, taskBrief) {
|
|
13972
|
+
const budget = this.budgetForRole(role);
|
|
13973
|
+
const cfg = {
|
|
13974
|
+
id: `${role}-${this.sessionId}`,
|
|
13975
|
+
name: role,
|
|
13976
|
+
role,
|
|
13977
|
+
tools: ["fleet_emit", "fleet_status", "read", "grep", "glob", "bash", "write"],
|
|
13978
|
+
maxIterations: budget.maxIterations,
|
|
13979
|
+
maxToolCalls: budget.maxToolCalls,
|
|
13980
|
+
timeoutMs: budget.timeoutMs
|
|
13916
13981
|
};
|
|
13982
|
+
const subagentId = await this.director.spawn(cfg);
|
|
13983
|
+
await this.director.assign({ id: randomUUID(), subagentId, description: taskBrief });
|
|
13984
|
+
return subagentId;
|
|
13917
13985
|
}
|
|
13918
|
-
|
|
13919
|
-
const
|
|
13920
|
-
|
|
13921
|
-
|
|
13922
|
-
|
|
13923
|
-
|
|
13924
|
-
|
|
13925
|
-
|
|
13926
|
-
|
|
13927
|
-
|
|
13928
|
-
|
|
13929
|
-
|
|
13930
|
-
|
|
13931
|
-
|
|
13932
|
-
|
|
13933
|
-
|
|
13934
|
-
}
|
|
13935
|
-
for (const h of this.any) {
|
|
13936
|
-
try {
|
|
13937
|
-
h(event);
|
|
13938
|
-
} catch {
|
|
13939
|
-
}
|
|
13940
|
-
}
|
|
13941
|
-
}
|
|
13942
|
-
};
|
|
13943
|
-
var FleetUsageAggregator = class {
|
|
13944
|
-
constructor(bus, priceLookup, metaLookup) {
|
|
13945
|
-
this.priceLookup = priceLookup;
|
|
13946
|
-
this.metaLookup = metaLookup;
|
|
13947
|
-
this.unsub.push(bus.filter("provider.response", (e) => this.onProviderResponse(e)));
|
|
13948
|
-
this.unsub.push(bus.filter("tool.executed", (e) => this.onToolExecuted(e)));
|
|
13949
|
-
this.unsub.push(bus.filter("iteration.started", (e) => this.onIterationStarted(e)));
|
|
13986
|
+
buildBugHunterTask() {
|
|
13987
|
+
const scratchpad = this.director.sharedScratchpadPath ?? "/tmp";
|
|
13988
|
+
const fileContents = this.snapshot.files.map((f) => `=== ${f.path} ===
|
|
13989
|
+
${f.content}`).join("\n\n");
|
|
13990
|
+
return `You are BugHunter. Scan the following files for bugs and code smells.
|
|
13991
|
+
|
|
13992
|
+
Target files:
|
|
13993
|
+
${fileContents}
|
|
13994
|
+
|
|
13995
|
+
For each bug found, emit it using the fleet_emit tool immediately:
|
|
13996
|
+
{ "type": "bug.found", "payload": { "finding": { "id": "<uuid>", "type": "<pattern>", "severity": "<critical|high|medium|low>", "location": { "file": "<path>", "line": <n> }, "description": "<explain>", "suggestedFix": "<optional>" } } }
|
|
13997
|
+
|
|
13998
|
+
After scanning all files, write your full markdown bug report to:
|
|
13999
|
+
${scratchpad}/bug-hunter-report-${this.sessionId}.md
|
|
14000
|
+
|
|
14001
|
+
Important: emit each finding as soon as you find it. Do not batch or wait until the end.`;
|
|
13950
14002
|
}
|
|
13951
|
-
|
|
13952
|
-
|
|
13953
|
-
|
|
13954
|
-
|
|
13955
|
-
|
|
13956
|
-
|
|
13957
|
-
|
|
13958
|
-
|
|
13959
|
-
|
|
13960
|
-
|
|
13961
|
-
|
|
13962
|
-
|
|
13963
|
-
|
|
13964
|
-
|
|
13965
|
-
|
|
13966
|
-
|
|
13967
|
-
|
|
13968
|
-
|
|
13969
|
-
|
|
13970
|
-
this.total.cost -= snap.cost;
|
|
13971
|
-
}
|
|
13972
|
-
/** Disposes all fleet-bus subscriptions. Call when the aggregator is no longer needed. */
|
|
13973
|
-
dispose() {
|
|
13974
|
-
for (const off of this.unsub) off();
|
|
13975
|
-
this.unsub.length = 0;
|
|
14003
|
+
buildRefactorPlannerTask() {
|
|
14004
|
+
const scratchpad = this.director.sharedScratchpadPath ?? "/tmp";
|
|
14005
|
+
const bugHunterReportPath = `${scratchpad}/bug-hunter-report-${this.sessionId}.md`;
|
|
14006
|
+
const fileContents = this.snapshot.files.map((f) => `=== ${f.path} ===
|
|
14007
|
+
${f.content}`).join("\n\n");
|
|
14008
|
+
return `You are RefactorPlanner. Plan refactorings for the following files.
|
|
14009
|
+
|
|
14010
|
+
Target files:
|
|
14011
|
+
${fileContents}
|
|
14012
|
+
|
|
14013
|
+
Read the BugHunter report at: ${bugHunterReportPath}
|
|
14014
|
+
|
|
14015
|
+
For each bug you can address, emit a refactor plan using fleet_emit:
|
|
14016
|
+
{ "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>" } } }
|
|
14017
|
+
|
|
14018
|
+
Also write your full markdown plan to:
|
|
14019
|
+
${scratchpad}/refactor-plan-${this.sessionId}.md
|
|
14020
|
+
|
|
14021
|
+
Emit each plan immediately. Do not wait until planning is complete.`;
|
|
13976
14022
|
}
|
|
13977
|
-
|
|
13978
|
-
|
|
13979
|
-
|
|
13980
|
-
|
|
13981
|
-
|
|
13982
|
-
|
|
13983
|
-
|
|
13984
|
-
|
|
14023
|
+
buildCriticTask() {
|
|
14024
|
+
const scratchpad = this.director.sharedScratchpadPath ?? "/tmp";
|
|
14025
|
+
const bugHunterReportPath = `${scratchpad}/bug-hunter-report-${this.sessionId}.md`;
|
|
14026
|
+
const refactorPlanPath = `${scratchpad}/refactor-plan-${this.sessionId}.md`;
|
|
14027
|
+
const fileContents = this.snapshot.files.map((f) => `=== ${f.path} ===
|
|
14028
|
+
${f.content}`).join("\n\n");
|
|
14029
|
+
return `You are Critic. Evaluate bug findings and refactor plans.
|
|
14030
|
+
|
|
14031
|
+
Target files:
|
|
14032
|
+
${fileContents}
|
|
14033
|
+
|
|
14034
|
+
Read the BugHunter report at: ${bugHunterReportPath}
|
|
14035
|
+
Read the RefactorPlanner report at: ${refactorPlanPath}
|
|
14036
|
+
|
|
14037
|
+
For each bug and refactor plan, emit your evaluation using fleet_emit:
|
|
14038
|
+
{ "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>" }] } } }
|
|
14039
|
+
|
|
14040
|
+
After all evaluations, write your markdown report to:
|
|
14041
|
+
${scratchpad}/critic-report-${this.sessionId}.md
|
|
14042
|
+
|
|
14043
|
+
Emit each evaluation immediately. Do not wait until you have read all reports.`;
|
|
13985
14044
|
}
|
|
13986
|
-
|
|
13987
|
-
|
|
13988
|
-
|
|
13989
|
-
|
|
13990
|
-
|
|
13991
|
-
|
|
13992
|
-
|
|
13993
|
-
|
|
13994
|
-
|
|
13995
|
-
|
|
13996
|
-
|
|
13997
|
-
|
|
13998
|
-
|
|
13999
|
-
|
|
14000
|
-
|
|
14001
|
-
|
|
14002
|
-
|
|
14045
|
+
wireFleetBus() {
|
|
14046
|
+
const dTool = this.fleetBus.filter("tool.executed", (e) => {
|
|
14047
|
+
this.progressBySubagent.set(e.subagentId, (this.progressBySubagent.get(e.subagentId) ?? 0) + 1);
|
|
14048
|
+
});
|
|
14049
|
+
this.disposers.push(dTool);
|
|
14050
|
+
const dBudget = this.fleetBus.filter("budget.threshold_reached", (e) => {
|
|
14051
|
+
const payload = e.payload;
|
|
14052
|
+
const role = this.roleFromSubagentId(e.subagentId);
|
|
14053
|
+
if (!role) return;
|
|
14054
|
+
const btwNotes = this.director.getLeaderBtwNotes();
|
|
14055
|
+
const alert = {
|
|
14056
|
+
sessionId: this.sessionId,
|
|
14057
|
+
subagentId: e.subagentId,
|
|
14058
|
+
role,
|
|
14059
|
+
level: "warning" /* WARNING */,
|
|
14060
|
+
message: `${role} hit ${payload.kind} soft limit (${payload.used}/${payload.limit})`,
|
|
14061
|
+
budgetKind: payload.kind,
|
|
14062
|
+
elapsedMs: payload.timeoutMs,
|
|
14063
|
+
limit: payload.limit,
|
|
14064
|
+
btwNotes
|
|
14003
14065
|
};
|
|
14004
|
-
this.
|
|
14005
|
-
|
|
14006
|
-
|
|
14007
|
-
|
|
14008
|
-
|
|
14009
|
-
|
|
14010
|
-
|
|
14011
|
-
|
|
14012
|
-
|
|
14013
|
-
|
|
14014
|
-
|
|
14015
|
-
snap.cacheRead += usage.cacheRead ?? 0;
|
|
14016
|
-
snap.cacheWrite += usage.cacheWrite ?? 0;
|
|
14017
|
-
this.total.input += usage.input ?? 0;
|
|
14018
|
-
this.total.output += usage.output ?? 0;
|
|
14019
|
-
this.total.cacheRead += usage.cacheRead ?? 0;
|
|
14020
|
-
this.total.cacheWrite += usage.cacheWrite ?? 0;
|
|
14021
|
-
const price = this.priceLookup?.(e.subagentId, snap.provider, snap.model);
|
|
14022
|
-
if (price) {
|
|
14023
|
-
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);
|
|
14024
|
-
snap.cost += delta;
|
|
14025
|
-
this.total.cost += delta;
|
|
14026
|
-
}
|
|
14027
|
-
snap.lastEventAt = e.ts;
|
|
14028
|
-
}
|
|
14029
|
-
onToolExecuted(e) {
|
|
14030
|
-
const snap = this.ensure(e.subagentId);
|
|
14031
|
-
snap.toolCalls += 1;
|
|
14032
|
-
snap.lastEventAt = e.ts;
|
|
14033
|
-
}
|
|
14034
|
-
onIterationStarted(e) {
|
|
14035
|
-
const snap = this.ensure(e.subagentId);
|
|
14036
|
-
snap.iterations += 1;
|
|
14037
|
-
snap.lastEventAt = e.ts;
|
|
14038
|
-
}
|
|
14039
|
-
};
|
|
14040
|
-
function makeSpawnTool(director, roster) {
|
|
14041
|
-
const inputSchema = {
|
|
14042
|
-
type: "object",
|
|
14043
|
-
properties: {
|
|
14044
|
-
role: { type: "string", description: "Roster role id. When set, the spawn uses the matching config from the roster and ignores other fields." },
|
|
14045
|
-
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." },
|
|
14046
|
-
name: { type: "string", description: "Display name for the subagent. Used as a fallback when description-based dispatch does not resolve a role." },
|
|
14047
|
-
provider: { type: "string", description: 'Provider id (e.g. "anthropic", "openai"). Defaults to the leader provider when omitted.' },
|
|
14048
|
-
model: { type: "string", description: "Model id within the provider. Defaults to the leader model when omitted." },
|
|
14049
|
-
systemPromptOverride: { type: "string", description: "Extra prompt text appended after the role-base prompt." },
|
|
14050
|
-
maxIterations: { type: "number", minimum: 1 },
|
|
14051
|
-
maxToolCalls: { type: "number", minimum: 1 },
|
|
14052
|
-
maxCostUsd: { type: "number", minimum: 0 },
|
|
14053
|
-
timeoutMs: { type: "number", minimum: 1, description: "Hard wall-clock cap in milliseconds. Defaults to none (idle timeout is the default reaper)." },
|
|
14054
|
-
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." },
|
|
14055
|
-
maxTokens: { type: "number", minimum: 1, description: "Maximum total tokens (input + output) the subagent may use." }
|
|
14056
|
-
},
|
|
14057
|
-
required: []
|
|
14058
|
-
};
|
|
14059
|
-
return {
|
|
14060
|
-
name: "spawn_subagent",
|
|
14061
|
-
description: "Create a new subagent under this director. Returns the subagent id.",
|
|
14062
|
-
usageHint: "Pass `role` (matches the roster), `description` (smart dispatch to best agent), or `name` + `provider`/`model`. Returns `{ subagentId }`.",
|
|
14063
|
-
permission: "auto",
|
|
14064
|
-
mutating: false,
|
|
14065
|
-
inputSchema,
|
|
14066
|
-
async execute(input) {
|
|
14067
|
-
const i = input ?? {};
|
|
14068
|
-
const role = typeof i.role === "string" ? i.role : void 0;
|
|
14069
|
-
const description = typeof i.description === "string" ? i.description : void 0;
|
|
14070
|
-
let cfg;
|
|
14071
|
-
if (role && roster) {
|
|
14072
|
-
const base = roster[role];
|
|
14073
|
-
if (!base) return { error: `unknown role "${role}". roster has: ${Object.keys(roster).join(", ")}` };
|
|
14074
|
-
cfg = instantiateRosterConfig(role, base);
|
|
14075
|
-
} else if (description && !role) {
|
|
14076
|
-
const dispatchResult = await dispatchAgent(description, {
|
|
14077
|
-
classifier: director.dispatchClassifier,
|
|
14078
|
-
catalog: roster
|
|
14079
|
-
});
|
|
14080
|
-
const dispatchRole = dispatchResult.role;
|
|
14081
|
-
if (roster?.[dispatchRole]) {
|
|
14082
|
-
cfg = instantiateRosterConfig(dispatchRole, roster[dispatchRole]);
|
|
14083
|
-
} else {
|
|
14084
|
-
const def = dispatchResult.definition;
|
|
14085
|
-
cfg = {
|
|
14086
|
-
name: def.config.name ?? dispatchRole,
|
|
14087
|
-
role: dispatchRole,
|
|
14088
|
-
provider: def.config.provider,
|
|
14089
|
-
model: def.config.model
|
|
14090
|
-
};
|
|
14091
|
-
}
|
|
14066
|
+
this.alerts.push(alert);
|
|
14067
|
+
this.fleetBus.emit({
|
|
14068
|
+
subagentId: e.subagentId,
|
|
14069
|
+
ts: Date.now(),
|
|
14070
|
+
type: "collab.warning",
|
|
14071
|
+
payload: alert
|
|
14072
|
+
});
|
|
14073
|
+
const decision = this.options.onBudgetWarning?.(alert) ?? "ignore";
|
|
14074
|
+
if (decision === "cancel") {
|
|
14075
|
+
this.cancel(`Director cancelled: ${role} ${payload.kind} threshold`);
|
|
14076
|
+
return;
|
|
14092
14077
|
}
|
|
14093
|
-
|
|
14094
|
-
|
|
14095
|
-
|
|
14096
|
-
|
|
14097
|
-
|
|
14098
|
-
|
|
14099
|
-
if (typeof i.maxToolCalls === "number") cfg.maxToolCalls = i.maxToolCalls;
|
|
14100
|
-
if (typeof i.maxCostUsd === "number") cfg.maxCostUsd = i.maxCostUsd;
|
|
14101
|
-
if (typeof i.timeoutMs === "number") cfg.timeoutMs = i.timeoutMs;
|
|
14102
|
-
if (typeof i.idleTimeoutMs === "number") cfg.idleTimeoutMs = i.idleTimeoutMs;
|
|
14103
|
-
if (typeof i.maxTokens === "number") cfg.maxTokens = i.maxTokens;
|
|
14104
|
-
try {
|
|
14105
|
-
const subagentId = await director.spawn(cfg);
|
|
14106
|
-
return { subagentId, provider: cfg.provider, model: cfg.model, name: cfg.name, role: cfg.role };
|
|
14107
|
-
} catch (err) {
|
|
14108
|
-
if (err instanceof FleetSpawnBudgetError) {
|
|
14109
|
-
return { error: err.message, kind: err.kind, limit: err.limit, observed: err.observed };
|
|
14110
|
-
}
|
|
14111
|
-
if (err instanceof FleetCostCapError) {
|
|
14112
|
-
return { error: err.message, kind: err.kind, limit: err.limit, observed: err.observed };
|
|
14078
|
+
if (payload.kind === "timeout" || payload.kind === "idle_timeout") {
|
|
14079
|
+
const progress = this.progressBySubagent.get(e.subagentId) ?? 0;
|
|
14080
|
+
const lastProgress = this.lastTimeoutProgress.get(e.subagentId) ?? -1;
|
|
14081
|
+
if (progress <= lastProgress) {
|
|
14082
|
+
payload.deny();
|
|
14083
|
+
return;
|
|
14113
14084
|
}
|
|
14114
|
-
|
|
14085
|
+
this.lastTimeoutProgress.set(e.subagentId, progress);
|
|
14086
|
+
const newLimit = Math.min(Math.ceil((payload.timeoutMs ?? payload.limit) * 2), 24 * 60 * 6e4);
|
|
14087
|
+
setImmediate(() => {
|
|
14088
|
+
payload.extend({ timeoutMs: newLimit });
|
|
14089
|
+
});
|
|
14090
|
+
return;
|
|
14115
14091
|
}
|
|
14092
|
+
if (decision === "extend") {
|
|
14093
|
+
setImmediate(() => {
|
|
14094
|
+
const base = Math.max(payload.limit, payload.used);
|
|
14095
|
+
const extra = {};
|
|
14096
|
+
switch (payload.kind) {
|
|
14097
|
+
case "iterations":
|
|
14098
|
+
extra.maxIterations = Math.min(Math.ceil(base * 1.5), 5e4);
|
|
14099
|
+
break;
|
|
14100
|
+
case "tool_calls":
|
|
14101
|
+
extra.maxToolCalls = Math.min(Math.ceil(base * 1.5), 1e5);
|
|
14102
|
+
break;
|
|
14103
|
+
case "tokens":
|
|
14104
|
+
extra.maxTokens = Math.min(Math.ceil(base * 1.5), 5e6);
|
|
14105
|
+
break;
|
|
14106
|
+
case "cost":
|
|
14107
|
+
extra.maxCostUsd = Math.min(base * 1.5, 100);
|
|
14108
|
+
break;
|
|
14109
|
+
}
|
|
14110
|
+
payload.extend(extra);
|
|
14111
|
+
});
|
|
14112
|
+
return;
|
|
14113
|
+
}
|
|
14114
|
+
if (payload.kind !== "timeout") {
|
|
14115
|
+
setImmediate(() => {
|
|
14116
|
+
const base = Math.max(payload.limit, payload.used);
|
|
14117
|
+
const extra = {};
|
|
14118
|
+
switch (payload.kind) {
|
|
14119
|
+
case "iterations":
|
|
14120
|
+
extra.maxIterations = Math.min(Math.ceil(base * 1.25), 5e4);
|
|
14121
|
+
break;
|
|
14122
|
+
case "tool_calls":
|
|
14123
|
+
extra.maxToolCalls = Math.min(Math.ceil(base * 1.25), 1e5);
|
|
14124
|
+
break;
|
|
14125
|
+
case "tokens":
|
|
14126
|
+
extra.maxTokens = Math.min(Math.ceil(base * 1.25), 5e6);
|
|
14127
|
+
break;
|
|
14128
|
+
case "cost":
|
|
14129
|
+
extra.maxCostUsd = Math.min(base * 1.25, 100);
|
|
14130
|
+
break;
|
|
14131
|
+
}
|
|
14132
|
+
payload.extend(extra);
|
|
14133
|
+
});
|
|
14134
|
+
}
|
|
14135
|
+
});
|
|
14136
|
+
this.disposers.push(dBudget);
|
|
14137
|
+
const dCancel = this.fleetBus.filter("director.cancel_collab", (e) => {
|
|
14138
|
+
const payload = e.payload;
|
|
14139
|
+
if (payload.sessionId !== this.sessionId) return;
|
|
14140
|
+
this.cancelled = true;
|
|
14141
|
+
if (this._timeoutTimer) {
|
|
14142
|
+
clearTimeout(this._timeoutTimer);
|
|
14143
|
+
this._timeoutTimer = void 0;
|
|
14144
|
+
}
|
|
14145
|
+
this.fleetBus.emit({
|
|
14146
|
+
subagentId: this.director.id,
|
|
14147
|
+
ts: Date.now(),
|
|
14148
|
+
type: "collab.cancelled",
|
|
14149
|
+
payload: { sessionId: this.sessionId, reason: payload.reason }
|
|
14150
|
+
});
|
|
14151
|
+
});
|
|
14152
|
+
this.disposers.push(dCancel);
|
|
14153
|
+
const d1 = this.fleetBus.filter("bug.found", (e) => {
|
|
14154
|
+
const payload = e.payload;
|
|
14155
|
+
if (payload?.finding) {
|
|
14156
|
+
this.bugs.set(payload.finding.id, payload.finding);
|
|
14157
|
+
this.emit("bug.found", payload);
|
|
14158
|
+
}
|
|
14159
|
+
});
|
|
14160
|
+
this.disposers.push(d1);
|
|
14161
|
+
const d2 = this.fleetBus.filter("refactor.plan", (e) => {
|
|
14162
|
+
const payload = e.payload;
|
|
14163
|
+
if (payload?.plan) {
|
|
14164
|
+
this.plans.set(payload.plan.id, payload.plan);
|
|
14165
|
+
this.emit("refactor.plan", payload);
|
|
14166
|
+
}
|
|
14167
|
+
});
|
|
14168
|
+
this.disposers.push(d2);
|
|
14169
|
+
const d3 = this.fleetBus.filter("critic.evaluation", (e) => {
|
|
14170
|
+
const payload = e.payload;
|
|
14171
|
+
if (payload?.evaluation) {
|
|
14172
|
+
this.evaluations.set(payload.evaluation.id, payload.evaluation);
|
|
14173
|
+
this.emit("critic.evaluation", payload);
|
|
14174
|
+
}
|
|
14175
|
+
});
|
|
14176
|
+
this.disposers.push(d3);
|
|
14177
|
+
}
|
|
14178
|
+
roleFromSubagentId(subagentId) {
|
|
14179
|
+
for (const [role, id] of this.subagentIds) {
|
|
14180
|
+
if (id === subagentId) return role;
|
|
14116
14181
|
}
|
|
14117
|
-
|
|
14118
|
-
|
|
14119
|
-
|
|
14120
|
-
|
|
14121
|
-
|
|
14122
|
-
|
|
14123
|
-
|
|
14124
|
-
|
|
14125
|
-
|
|
14126
|
-
|
|
14127
|
-
|
|
14128
|
-
|
|
14129
|
-
|
|
14130
|
-
|
|
14131
|
-
|
|
14132
|
-
|
|
14133
|
-
|
|
14134
|
-
|
|
14135
|
-
|
|
14136
|
-
required: ["subagentId", "description"]
|
|
14137
|
-
};
|
|
14138
|
-
return {
|
|
14139
|
-
name: "assign_task",
|
|
14140
|
-
description: "Hand a task to a previously spawned subagent. Returns the task id.",
|
|
14141
|
-
permission: "auto",
|
|
14142
|
-
mutating: false,
|
|
14143
|
-
inputSchema,
|
|
14144
|
-
async execute(input) {
|
|
14145
|
-
const i = input;
|
|
14146
|
-
const task = { id: randomUUID(), description: i.description, subagentId: i.subagentId, maxToolCalls: i.maxToolCalls, timeoutMs: i.timeoutMs };
|
|
14147
|
-
const taskId = await director.assign(task);
|
|
14148
|
-
return { taskId, subagentId: i.subagentId };
|
|
14149
|
-
}
|
|
14150
|
-
};
|
|
14151
|
-
}
|
|
14152
|
-
function makeAwaitTasksTool(director) {
|
|
14153
|
-
return {
|
|
14154
|
-
name: "await_tasks",
|
|
14155
|
-
description: "Block until every named task completes. Returns the array of TaskResult.",
|
|
14156
|
-
permission: "auto",
|
|
14157
|
-
mutating: false,
|
|
14158
|
-
inputSchema: { type: "object", properties: { taskIds: { type: "array", items: { type: "string" }, description: "One or more task ids returned by `assign_task`." } }, required: ["taskIds"] },
|
|
14159
|
-
async execute(input) {
|
|
14160
|
-
const i = input;
|
|
14161
|
-
const results = await director.awaitTasks(i.taskIds);
|
|
14162
|
-
return { results };
|
|
14163
|
-
}
|
|
14164
|
-
};
|
|
14165
|
-
}
|
|
14166
|
-
function makeAskTool(director) {
|
|
14167
|
-
return {
|
|
14168
|
-
name: "ask_subagent",
|
|
14169
|
-
description: "Synchronously ask a subagent a question. Blocks until the subagent replies via the bridge.",
|
|
14170
|
-
permission: "auto",
|
|
14171
|
-
mutating: false,
|
|
14172
|
-
inputSchema: {
|
|
14173
|
-
type: "object",
|
|
14174
|
-
properties: {
|
|
14175
|
-
subagentId: { type: "string", minLength: 1, description: "Subagent to ask. Must be a previously spawned id." },
|
|
14176
|
-
question: { type: "string", minLength: 1, description: "The question or instruction." },
|
|
14177
|
-
timeoutMs: { type: "number", minimum: 1, description: "Optional timeout in ms (default 30s)." }
|
|
14182
|
+
const match = subagentId.match(/^(bug-hunter|refactor-planner|critic)/);
|
|
14183
|
+
return match?.[1] ?? null;
|
|
14184
|
+
}
|
|
14185
|
+
assembleReport() {
|
|
14186
|
+
const bugList = Array.from(this.bugs.values());
|
|
14187
|
+
const planList = Array.from(this.plans.values());
|
|
14188
|
+
const evalList = Array.from(this.evaluations.values());
|
|
14189
|
+
let disposition = "completed";
|
|
14190
|
+
if (this.cancelled) disposition = "cancelled";
|
|
14191
|
+
const verdictOrder = {
|
|
14192
|
+
approve: 0,
|
|
14193
|
+
needs_revision: 1,
|
|
14194
|
+
reject: 2
|
|
14195
|
+
};
|
|
14196
|
+
const overallVerdict = evalList.reduce(
|
|
14197
|
+
(worst, eval_) => {
|
|
14198
|
+
const w = verdictOrder[worst];
|
|
14199
|
+
const c = verdictOrder[eval_.verdict];
|
|
14200
|
+
return c > w ? eval_.verdict : worst;
|
|
14178
14201
|
},
|
|
14179
|
-
|
|
14180
|
-
|
|
14181
|
-
|
|
14182
|
-
|
|
14183
|
-
|
|
14184
|
-
|
|
14185
|
-
|
|
14186
|
-
|
|
14187
|
-
|
|
14202
|
+
"approve"
|
|
14203
|
+
);
|
|
14204
|
+
const summary = this.buildMarkdownSummary(bugList, planList, evalList, overallVerdict, disposition);
|
|
14205
|
+
return {
|
|
14206
|
+
sessionId: this.sessionId,
|
|
14207
|
+
startedAt: this.snapshot.createdAt,
|
|
14208
|
+
completedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
14209
|
+
targetPaths: this.options.targetPaths,
|
|
14210
|
+
disposition,
|
|
14211
|
+
bugs: bugList,
|
|
14212
|
+
refactorPlans: planList,
|
|
14213
|
+
evaluations: evalList,
|
|
14214
|
+
alerts: [...this.alerts],
|
|
14215
|
+
overallVerdict,
|
|
14216
|
+
summary
|
|
14217
|
+
};
|
|
14218
|
+
}
|
|
14219
|
+
buildMarkdownSummary(bugs, plans, evals, overallVerdict, disposition) {
|
|
14220
|
+
const lines = [
|
|
14221
|
+
`## Collaborative Debugging Report \u2014 ${this.sessionId}`,
|
|
14222
|
+
"",
|
|
14223
|
+
`**Target:** ${this.options.targetPaths.join(", ")}`,
|
|
14224
|
+
`**Disposition:** ${disposition.toUpperCase()}`,
|
|
14225
|
+
`**Overall Verdict:** **${overallVerdict.toUpperCase()}**`,
|
|
14226
|
+
""
|
|
14227
|
+
];
|
|
14228
|
+
if (this.alerts.length > 0) {
|
|
14229
|
+
lines.push("### Alerts", "");
|
|
14230
|
+
for (const alert of this.alerts) {
|
|
14231
|
+
lines.push(`- **[${alert.level.toUpperCase()}]** ${alert.role}: ${alert.message}`);
|
|
14232
|
+
}
|
|
14233
|
+
lines.push("");
|
|
14234
|
+
}
|
|
14235
|
+
if (bugs.length > 0) {
|
|
14236
|
+
lines.push("### Bugs Found", "");
|
|
14237
|
+
for (const b of bugs) {
|
|
14238
|
+
lines.push(`- **[${b.severity.toUpperCase()}]** \`${b.location.file}:${b.location.line}\` \u2014 ${b.description}`);
|
|
14239
|
+
}
|
|
14240
|
+
lines.push("");
|
|
14241
|
+
}
|
|
14242
|
+
if (plans.length > 0) {
|
|
14243
|
+
lines.push("### Refactor Plans", "");
|
|
14244
|
+
for (const p of plans) {
|
|
14245
|
+
lines.push(`- **Phase plan** (risk: ${p.riskScore}, ~${p.estimatedChangeCount} changes)`);
|
|
14246
|
+
for (const phase of p.phases) {
|
|
14247
|
+
lines.push(` - Phase ${phase.number}: ${phase.title} [${phase.risk}]`);
|
|
14188
14248
|
}
|
|
14189
|
-
return {
|
|
14190
|
-
ok: true,
|
|
14191
|
-
answer: stored.summary,
|
|
14192
|
-
_answerKey: stored.key,
|
|
14193
|
-
_hint: "Response was large and stored. Use ask_result with the key to retrieve it."
|
|
14194
|
-
};
|
|
14195
|
-
} catch (err) {
|
|
14196
|
-
return { ok: false, error: err instanceof Error ? err.message : String(err) };
|
|
14197
14249
|
}
|
|
14250
|
+
lines.push("");
|
|
14198
14251
|
}
|
|
14199
|
-
|
|
14200
|
-
|
|
14201
|
-
|
|
14202
|
-
|
|
14203
|
-
|
|
14204
|
-
|
|
14205
|
-
permission: "auto",
|
|
14206
|
-
mutating: false,
|
|
14207
|
-
inputSchema: {
|
|
14208
|
-
type: "object",
|
|
14209
|
-
properties: {
|
|
14210
|
-
key: {
|
|
14211
|
-
type: "string",
|
|
14212
|
-
minLength: 1,
|
|
14213
|
-
description: "The `_answerKey` returned by `ask_subagent` for a large response."
|
|
14252
|
+
if (evals.length > 0) {
|
|
14253
|
+
lines.push("### Critic Evaluations", "");
|
|
14254
|
+
for (const e of evals) {
|
|
14255
|
+
lines.push(`- [${e.subjectType}] score=${e.score}/10 \u2014 **${e.verdict.toUpperCase()}**`);
|
|
14256
|
+
for (const c of e.concerns) {
|
|
14257
|
+
if (c.severity === "blocking") lines.push(` - ${c.description}`);
|
|
14214
14258
|
}
|
|
14215
|
-
},
|
|
14216
|
-
required: ["key"]
|
|
14217
|
-
},
|
|
14218
|
-
async execute(input) {
|
|
14219
|
-
const i = input;
|
|
14220
|
-
const value = director.largeAnswerStore.retrieveAnswer(i.key);
|
|
14221
|
-
if (value === void 0) {
|
|
14222
|
-
return { ok: false, error: `No stored answer found for key "${i.key}" \u2014 it may have been cleared or the key is invalid.` };
|
|
14223
14259
|
}
|
|
14224
|
-
|
|
14260
|
+
lines.push("");
|
|
14225
14261
|
}
|
|
14226
|
-
|
|
14227
|
-
}
|
|
14228
|
-
|
|
14229
|
-
|
|
14230
|
-
|
|
14231
|
-
|
|
14232
|
-
|
|
14233
|
-
|
|
14234
|
-
|
|
14235
|
-
|
|
14236
|
-
|
|
14237
|
-
|
|
14238
|
-
|
|
14239
|
-
|
|
14240
|
-
|
|
14241
|
-
|
|
14242
|
-
|
|
14243
|
-
|
|
14244
|
-
|
|
14245
|
-
|
|
14246
|
-
|
|
14247
|
-
|
|
14262
|
+
return lines.join("\n");
|
|
14263
|
+
}
|
|
14264
|
+
cleanup() {
|
|
14265
|
+
for (const dispose of this.disposers) dispose();
|
|
14266
|
+
this.disposers.length = 0;
|
|
14267
|
+
}
|
|
14268
|
+
};
|
|
14269
|
+
|
|
14270
|
+
// src/coordination/director-prompts.ts
|
|
14271
|
+
var DEFAULT_DIRECTOR_PREAMBLE = `You are the Director of a multi-agent fleet. You orchestrate worker
|
|
14272
|
+
subagents by spawning them, assigning tasks, awaiting completions, and
|
|
14273
|
+
rolling up their outputs into your next decision.
|
|
14274
|
+
|
|
14275
|
+
Core fleet tools available to you:
|
|
14276
|
+
- spawn_subagent \u2014 create a worker with a chosen provider / model / role
|
|
14277
|
+
- assign_task \u2014 hand a piece of work to a specific subagent
|
|
14278
|
+
- await_tasks \u2014 block until named task ids complete (parallel-safe)
|
|
14279
|
+
- ask_subagent \u2014 synchronously query a running subagent via the bridge
|
|
14280
|
+
- roll_up \u2014 aggregate finished tasks into a markdown/json summary
|
|
14281
|
+
- terminate_subagent \u2014 abort a stuck worker (use sparingly)
|
|
14282
|
+
- fleet_status \u2014 snapshot of all subagents and pending tasks
|
|
14283
|
+
- fleet_usage \u2014 token + cost breakdown per subagent and total
|
|
14284
|
+
|
|
14285
|
+
Working rules:
|
|
14286
|
+
1. Decompose first. Before spawning, decide which sub-tasks are
|
|
14287
|
+
independent and can run in parallel. Sequential work doesn't need a
|
|
14288
|
+
subagent \u2014 do it yourself.
|
|
14289
|
+
2. Match worker to job. Cheap/fast model for triage, capable model for
|
|
14290
|
+
synthesis. Different providers per sibling is allowed and encouraged.
|
|
14291
|
+
3. Always pair an assign with an await. Don't fire-and-forget; you owe
|
|
14292
|
+
the user a single coherent answer at the end.
|
|
14293
|
+
4. Roll up before deciding. After await_tasks resolves, call roll_up so
|
|
14294
|
+
the results are folded back into your context in a compact form.
|
|
14295
|
+
5. Budget is real. Check fleet_usage periodically. If a subagent is
|
|
14296
|
+
thrashing, terminate it rather than letting cost climb silently.
|
|
14297
|
+
6. Never claim a subagent's work as your own without verifying it. If a
|
|
14298
|
+
result looks wrong, ask_subagent for clarification before passing it
|
|
14299
|
+
to the user.
|
|
14300
|
+
7. Wind down when satisfied. When the results are good enough, call
|
|
14301
|
+
work_complete \u2014 no new subagents will spawn and queued tasks complete
|
|
14302
|
+
as aborted. Running subagents finish naturally. Call terminate_subagent
|
|
14303
|
+
only for ones you need to stop immediately.`;
|
|
14304
|
+
var DEFAULT_SUBAGENT_BASELINE = `You are a subagent operating under a Director. You were spawned to handle
|
|
14305
|
+
a specific slice of a larger plan \u2014 do that slice well and report back.
|
|
14306
|
+
|
|
14307
|
+
Bridge contract:
|
|
14308
|
+
- You have a parent (the Director). You may call \`request\` on the
|
|
14309
|
+
parent bridge to ask a clarifying question. Use this sparingly; the
|
|
14310
|
+
parent is also working.
|
|
14311
|
+
- You MAY NOT request the parent's system prompt, tool list, or other
|
|
14312
|
+
subagents' context. Those are not yours to read.
|
|
14313
|
+
- Your final task output is what the Director sees. Be concise,
|
|
14314
|
+
structured, and self-contained \u2014 assume the Director will paste your
|
|
14315
|
+
output into its own context.`;
|
|
14316
|
+
function composeDirectorPrompt(parts = {}) {
|
|
14317
|
+
const sections = [];
|
|
14318
|
+
const preamble = parts.directorPreamble ?? DEFAULT_DIRECTOR_PREAMBLE;
|
|
14319
|
+
if (preamble && preamble.trim().length > 0) sections.push(preamble.trim());
|
|
14320
|
+
if (parts.rosterSummary && parts.rosterSummary.trim().length > 0) {
|
|
14321
|
+
sections.push(`Available roles you can spawn:
|
|
14322
|
+
${parts.rosterSummary.trim()}`);
|
|
14323
|
+
}
|
|
14324
|
+
if (parts.basePrompt && parts.basePrompt.trim().length > 0) {
|
|
14325
|
+
sections.push(parts.basePrompt.trim());
|
|
14326
|
+
}
|
|
14327
|
+
return sections.join("\n\n");
|
|
14248
14328
|
}
|
|
14249
|
-
function
|
|
14329
|
+
function composeSubagentPrompt(parts = {}) {
|
|
14330
|
+
const sections = [];
|
|
14331
|
+
const baseline = parts.baseline ?? DEFAULT_SUBAGENT_BASELINE;
|
|
14332
|
+
if (baseline && baseline.trim().length > 0) sections.push(baseline.trim());
|
|
14333
|
+
if (parts.role && parts.role.trim().length > 0) {
|
|
14334
|
+
sections.push(`Role:
|
|
14335
|
+
${parts.role.trim()}`);
|
|
14336
|
+
}
|
|
14337
|
+
if (parts.task && parts.task.trim().length > 0) {
|
|
14338
|
+
sections.push(`Task:
|
|
14339
|
+
${parts.task.trim()}`);
|
|
14340
|
+
}
|
|
14341
|
+
if (parts.sharedScratchpad && parts.sharedScratchpad.trim().length > 0) {
|
|
14342
|
+
sections.push(
|
|
14343
|
+
`Shared notes:
|
|
14344
|
+
A scratchpad shared with the rest of the fleet is mounted at \`${parts.sharedScratchpad.trim()}\`.
|
|
14345
|
+
- Write your final findings as markdown files there (e.g. \`findings.md\`, \`security.md\`).
|
|
14346
|
+
- Before starting, list the directory and read any sibling files relevant to your task \u2014 they may already contain context you can build on.
|
|
14347
|
+
- Use stable filenames (one file per concern); overwrite instead of appending so the Director sees the latest state.`
|
|
14348
|
+
);
|
|
14349
|
+
}
|
|
14350
|
+
if (parts.override && parts.override.trim().length > 0) {
|
|
14351
|
+
sections.push(parts.override.trim());
|
|
14352
|
+
}
|
|
14353
|
+
return sections.join("\n\n");
|
|
14354
|
+
}
|
|
14355
|
+
function rosterSummaryFromConfigs(roster) {
|
|
14356
|
+
const lines = [];
|
|
14357
|
+
for (const [roleId, cfg] of Object.entries(roster)) {
|
|
14358
|
+
const tag = cfg.provider && cfg.model ? ` (${cfg.provider}/${cfg.model})` : "";
|
|
14359
|
+
const headline = cfg.prompt ? (cfg.prompt.split("\n").find((l) => l.trim().length > 0) ?? "").trim().slice(0, 80) : "";
|
|
14360
|
+
const tail = headline ? ` \u2014 ${headline}` : "";
|
|
14361
|
+
lines.push(`- ${roleId}: ${cfg.name}${tag}${tail}`);
|
|
14362
|
+
}
|
|
14363
|
+
return lines.join("\n");
|
|
14364
|
+
}
|
|
14365
|
+
function makeSpawnTool(director, roster) {
|
|
14366
|
+
const inputSchema = {
|
|
14367
|
+
type: "object",
|
|
14368
|
+
properties: {
|
|
14369
|
+
role: { type: "string", description: "Roster role id. When set, the spawn uses the matching config from the roster and ignores other fields." },
|
|
14370
|
+
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." },
|
|
14371
|
+
name: { type: "string", description: "Display name for the subagent. Used as a fallback when description-based dispatch does not resolve a role." },
|
|
14372
|
+
provider: { type: "string", description: 'Provider id (e.g. "anthropic", "openai"). Defaults to the leader provider when omitted.' },
|
|
14373
|
+
model: { type: "string", description: "Model id within the provider. Defaults to the leader model when omitted." },
|
|
14374
|
+
systemPromptOverride: { type: "string", description: "Extra prompt text appended after the role-base prompt." },
|
|
14375
|
+
maxIterations: { type: "number", minimum: 1 },
|
|
14376
|
+
maxToolCalls: { type: "number", minimum: 1 },
|
|
14377
|
+
maxCostUsd: { type: "number", minimum: 0 },
|
|
14378
|
+
timeoutMs: { type: "number", minimum: 1, description: "Hard wall-clock cap in milliseconds. Defaults to none (idle timeout is the default reaper)." },
|
|
14379
|
+
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." },
|
|
14380
|
+
maxTokens: { type: "number", minimum: 1, description: "Maximum total tokens (input + output) the subagent may use." }
|
|
14381
|
+
},
|
|
14382
|
+
required: []
|
|
14383
|
+
};
|
|
14250
14384
|
return {
|
|
14251
|
-
name: "
|
|
14252
|
-
description:
|
|
14385
|
+
name: "spawn_subagent",
|
|
14386
|
+
description: "Create a new subagent under this director. Returns the subagent id.",
|
|
14387
|
+
usageHint: "Pass `role` (matches the roster), `description` (smart dispatch to best agent), or `name` + `provider`/`model`. Returns `{ subagentId }`.",
|
|
14253
14388
|
permission: "auto",
|
|
14254
|
-
mutating:
|
|
14255
|
-
inputSchema
|
|
14389
|
+
mutating: false,
|
|
14390
|
+
inputSchema,
|
|
14256
14391
|
async execute(input) {
|
|
14257
|
-
const i = input;
|
|
14258
|
-
|
|
14259
|
-
|
|
14392
|
+
const i = input ?? {};
|
|
14393
|
+
const role = typeof i.role === "string" ? i.role : void 0;
|
|
14394
|
+
const description = typeof i.description === "string" ? i.description : void 0;
|
|
14395
|
+
let cfg;
|
|
14396
|
+
if (role && roster) {
|
|
14397
|
+
const base = roster[role];
|
|
14398
|
+
if (!base) return { error: `unknown role "${role}". roster has: ${Object.keys(roster).join(", ")}` };
|
|
14399
|
+
cfg = instantiateRosterConfig(role, base);
|
|
14400
|
+
} else if (description && !role) {
|
|
14401
|
+
const dispatchResult = await dispatchAgent(description, {
|
|
14402
|
+
classifier: director.dispatchClassifier,
|
|
14403
|
+
catalog: roster
|
|
14404
|
+
});
|
|
14405
|
+
const dispatchRole = dispatchResult.role;
|
|
14406
|
+
if (roster?.[dispatchRole]) {
|
|
14407
|
+
cfg = instantiateRosterConfig(dispatchRole, roster[dispatchRole]);
|
|
14408
|
+
} else {
|
|
14409
|
+
const def = dispatchResult.definition;
|
|
14410
|
+
cfg = {
|
|
14411
|
+
name: def.config.name ?? dispatchRole,
|
|
14412
|
+
role: dispatchRole,
|
|
14413
|
+
provider: def.config.provider,
|
|
14414
|
+
model: def.config.model
|
|
14415
|
+
};
|
|
14416
|
+
}
|
|
14417
|
+
}
|
|
14418
|
+
cfg ??= { name: i.name ?? "subagent" };
|
|
14419
|
+
if (typeof i.name === "string") cfg.name = i.name;
|
|
14420
|
+
if (typeof i.provider === "string") cfg.provider = i.provider;
|
|
14421
|
+
if (typeof i.model === "string") cfg.model = i.model;
|
|
14422
|
+
if (typeof i.systemPromptOverride === "string") cfg.systemPromptOverride = i.systemPromptOverride;
|
|
14423
|
+
if (typeof i.maxIterations === "number") cfg.maxIterations = i.maxIterations;
|
|
14424
|
+
if (typeof i.maxToolCalls === "number") cfg.maxToolCalls = i.maxToolCalls;
|
|
14425
|
+
if (typeof i.maxCostUsd === "number") cfg.maxCostUsd = i.maxCostUsd;
|
|
14426
|
+
if (typeof i.timeoutMs === "number") cfg.timeoutMs = i.timeoutMs;
|
|
14427
|
+
if (typeof i.idleTimeoutMs === "number") cfg.idleTimeoutMs = i.idleTimeoutMs;
|
|
14428
|
+
if (typeof i.maxTokens === "number") cfg.maxTokens = i.maxTokens;
|
|
14429
|
+
try {
|
|
14430
|
+
const subagentId = await director.spawn(cfg);
|
|
14431
|
+
return { subagentId, provider: cfg.provider, model: cfg.model, name: cfg.name, role: cfg.role };
|
|
14432
|
+
} catch (err) {
|
|
14433
|
+
if (err instanceof FleetSpawnBudgetError) {
|
|
14434
|
+
return { error: err.message, kind: err.kind, limit: err.limit, observed: err.observed };
|
|
14435
|
+
}
|
|
14436
|
+
if (err instanceof FleetCostCapError) {
|
|
14437
|
+
return { error: err.message, kind: err.kind, limit: err.limit, observed: err.observed };
|
|
14438
|
+
}
|
|
14439
|
+
return { error: err instanceof Error ? err.message : String(err) };
|
|
14440
|
+
}
|
|
14260
14441
|
}
|
|
14261
14442
|
};
|
|
14262
14443
|
}
|
|
14263
|
-
function
|
|
14444
|
+
function instantiateRosterConfig(role, base) {
|
|
14264
14445
|
return {
|
|
14265
|
-
|
|
14266
|
-
|
|
14267
|
-
|
|
14268
|
-
|
|
14269
|
-
inputSchema: { type: "object", properties: {}, required: [] },
|
|
14270
|
-
async execute() {
|
|
14271
|
-
await director.terminateAll();
|
|
14272
|
-
return { ok: true, message: `Fleet shutdown complete \u2014 all subagents stopped, pending tasks drained.` };
|
|
14273
|
-
}
|
|
14446
|
+
...base,
|
|
14447
|
+
// Roster entries are templates. A director may spawn several
|
|
14448
|
+
// workers with the same role, so never reuse the template id.
|
|
14449
|
+
id: `${role}-${randomUUID().slice(0, 8)}`
|
|
14274
14450
|
};
|
|
14275
14451
|
}
|
|
14276
|
-
function
|
|
14452
|
+
function makeAssignTool(director) {
|
|
14453
|
+
const inputSchema = {
|
|
14454
|
+
type: "object",
|
|
14455
|
+
properties: {
|
|
14456
|
+
subagentId: { type: "string", minLength: 1, description: "Target subagent id. Required." },
|
|
14457
|
+
description: { type: "string", minLength: 1, description: "The task in natural language \u2014 what you want this subagent to do." },
|
|
14458
|
+
maxToolCalls: { type: "number", minimum: 1, description: "Optional per-task tool-call budget override." },
|
|
14459
|
+
timeoutMs: { type: "number", minimum: 1, description: "Optional per-task timeout in ms." }
|
|
14460
|
+
},
|
|
14461
|
+
required: ["subagentId", "description"]
|
|
14462
|
+
};
|
|
14277
14463
|
return {
|
|
14278
|
-
name: "
|
|
14279
|
-
description: "
|
|
14464
|
+
name: "assign_task",
|
|
14465
|
+
description: "Hand a task to a previously spawned subagent. Returns the task id.",
|
|
14280
14466
|
permission: "auto",
|
|
14281
14467
|
mutating: false,
|
|
14282
|
-
inputSchema
|
|
14283
|
-
async execute() {
|
|
14284
|
-
const
|
|
14285
|
-
const
|
|
14286
|
-
const
|
|
14287
|
-
|
|
14288
|
-
return {
|
|
14289
|
-
subagents: base.subagents,
|
|
14290
|
-
coordinatorStats: stats ? { total: stats.total, running: stats.running, idle: stats.idle, stopped: stats.stopped } : void 0,
|
|
14291
|
-
pending: fleetStatus?.pending ?? [],
|
|
14292
|
-
usage: fm?.snapshot()
|
|
14293
|
-
};
|
|
14468
|
+
inputSchema,
|
|
14469
|
+
async execute(input) {
|
|
14470
|
+
const i = input;
|
|
14471
|
+
const task = { id: randomUUID(), description: i.description, subagentId: i.subagentId, maxToolCalls: i.maxToolCalls, timeoutMs: i.timeoutMs };
|
|
14472
|
+
const taskId = await director.assign(task);
|
|
14473
|
+
return { taskId, subagentId: i.subagentId };
|
|
14294
14474
|
}
|
|
14295
14475
|
};
|
|
14296
14476
|
}
|
|
14297
|
-
function
|
|
14477
|
+
function makeAwaitTasksTool(director) {
|
|
14298
14478
|
return {
|
|
14299
|
-
name: "
|
|
14300
|
-
description: "
|
|
14479
|
+
name: "await_tasks",
|
|
14480
|
+
description: "Block until every named task completes. Returns the array of TaskResult.",
|
|
14301
14481
|
permission: "auto",
|
|
14302
14482
|
mutating: false,
|
|
14303
|
-
inputSchema: { type: "object", properties: {}, required: [] },
|
|
14304
|
-
async execute() {
|
|
14305
|
-
|
|
14483
|
+
inputSchema: { type: "object", properties: { taskIds: { type: "array", items: { type: "string" }, description: "One or more task ids returned by `assign_task`." } }, required: ["taskIds"] },
|
|
14484
|
+
async execute(input) {
|
|
14485
|
+
const i = input;
|
|
14486
|
+
const results = await director.awaitTasks(i.taskIds);
|
|
14487
|
+
return { results };
|
|
14306
14488
|
}
|
|
14307
14489
|
};
|
|
14308
14490
|
}
|
|
14309
|
-
function
|
|
14491
|
+
function makeAskTool(director) {
|
|
14310
14492
|
return {
|
|
14311
|
-
name: "
|
|
14312
|
-
description: "
|
|
14493
|
+
name: "ask_subagent",
|
|
14494
|
+
description: "Synchronously ask a subagent a question. Blocks until the subagent replies via the bridge.",
|
|
14313
14495
|
permission: "auto",
|
|
14314
14496
|
mutating: false,
|
|
14315
14497
|
inputSchema: {
|
|
14316
14498
|
type: "object",
|
|
14317
14499
|
properties: {
|
|
14318
|
-
subagentId: { type: "string", description: "Subagent
|
|
14319
|
-
|
|
14500
|
+
subagentId: { type: "string", minLength: 1, description: "Subagent to ask. Must be a previously spawned id." },
|
|
14501
|
+
question: { type: "string", minLength: 1, description: "The question or instruction." },
|
|
14502
|
+
timeoutMs: { type: "number", minimum: 1, description: "Optional timeout in ms (default 30s)." }
|
|
14503
|
+
},
|
|
14504
|
+
required: ["subagentId", "question"]
|
|
14505
|
+
},
|
|
14506
|
+
async execute(input) {
|
|
14507
|
+
const i = input;
|
|
14508
|
+
try {
|
|
14509
|
+
const answer = await director.ask(i.subagentId, { question: i.question }, i.timeoutMs);
|
|
14510
|
+
const stored = director.largeAnswerStore.storeAnswer(answer);
|
|
14511
|
+
if (stored.inline) {
|
|
14512
|
+
return { ok: true, answer: stored.summary };
|
|
14513
|
+
}
|
|
14514
|
+
return {
|
|
14515
|
+
ok: true,
|
|
14516
|
+
answer: stored.summary,
|
|
14517
|
+
_answerKey: stored.key,
|
|
14518
|
+
_hint: "Response was large and stored. Use ask_result with the key to retrieve it."
|
|
14519
|
+
};
|
|
14520
|
+
} catch (err) {
|
|
14521
|
+
return { ok: false, error: err instanceof Error ? err.message : String(err) };
|
|
14522
|
+
}
|
|
14523
|
+
}
|
|
14524
|
+
};
|
|
14525
|
+
}
|
|
14526
|
+
function makeAskResultTool(director) {
|
|
14527
|
+
return {
|
|
14528
|
+
name: "ask_result",
|
|
14529
|
+
description: "Retrieve a large `ask_subagent` response that was stored out-of-context (>2K chars). Returns the full stored value.",
|
|
14530
|
+
permission: "auto",
|
|
14531
|
+
mutating: false,
|
|
14532
|
+
inputSchema: {
|
|
14533
|
+
type: "object",
|
|
14534
|
+
properties: {
|
|
14535
|
+
key: {
|
|
14536
|
+
type: "string",
|
|
14537
|
+
minLength: 1,
|
|
14538
|
+
description: "The `_answerKey` returned by `ask_subagent` for a large response."
|
|
14539
|
+
}
|
|
14540
|
+
},
|
|
14541
|
+
required: ["key"]
|
|
14542
|
+
},
|
|
14543
|
+
async execute(input) {
|
|
14544
|
+
const i = input;
|
|
14545
|
+
const value = director.largeAnswerStore.retrieveAnswer(i.key);
|
|
14546
|
+
if (value === void 0) {
|
|
14547
|
+
return { ok: false, error: `No stored answer found for key "${i.key}" \u2014 it may have been cleared or the key is invalid.` };
|
|
14548
|
+
}
|
|
14549
|
+
return { ok: true, value };
|
|
14550
|
+
}
|
|
14551
|
+
};
|
|
14552
|
+
}
|
|
14553
|
+
function makeRollUpTool(director) {
|
|
14554
|
+
return {
|
|
14555
|
+
name: "roll_up",
|
|
14556
|
+
description: "Aggregate completed task results into a single formatted summary.",
|
|
14557
|
+
permission: "auto",
|
|
14558
|
+
mutating: false,
|
|
14559
|
+
inputSchema: {
|
|
14560
|
+
type: "object",
|
|
14561
|
+
properties: {
|
|
14562
|
+
taskIds: { type: "array", items: { type: "string" }, description: "Completed task ids to aggregate." },
|
|
14563
|
+
style: { type: "string", enum: ["markdown", "json"], description: "Output flavor \u2014 markdown (default) or json." }
|
|
14564
|
+
},
|
|
14565
|
+
required: ["taskIds"]
|
|
14566
|
+
},
|
|
14567
|
+
async execute(input) {
|
|
14568
|
+
const i = input;
|
|
14569
|
+
const summary = director.rollUp(i.taskIds, i.style ?? "markdown");
|
|
14570
|
+
return { summary, count: i.taskIds.length };
|
|
14571
|
+
}
|
|
14572
|
+
};
|
|
14573
|
+
}
|
|
14574
|
+
function makeTerminateTool(director) {
|
|
14575
|
+
return {
|
|
14576
|
+
name: "terminate_subagent",
|
|
14577
|
+
description: 'Forcibly abort a subagent. The subagent finishes its current iteration then exits with status "stopped".',
|
|
14578
|
+
permission: "auto",
|
|
14579
|
+
mutating: true,
|
|
14580
|
+
inputSchema: { type: "object", properties: { subagentId: { type: "string", description: "Subagent to abort." } }, required: ["subagentId"] },
|
|
14581
|
+
async execute(input) {
|
|
14582
|
+
const i = input;
|
|
14583
|
+
await director.terminate(i.subagentId);
|
|
14584
|
+
return { ok: true };
|
|
14585
|
+
}
|
|
14586
|
+
};
|
|
14587
|
+
}
|
|
14588
|
+
function makeTerminateAllTool(director) {
|
|
14589
|
+
return {
|
|
14590
|
+
name: "terminate_all",
|
|
14591
|
+
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.',
|
|
14592
|
+
permission: "auto",
|
|
14593
|
+
mutating: true,
|
|
14594
|
+
inputSchema: { type: "object", properties: {}, required: [] },
|
|
14595
|
+
async execute() {
|
|
14596
|
+
await director.terminateAll();
|
|
14597
|
+
return { ok: true, message: `Fleet shutdown complete \u2014 all subagents stopped, pending tasks drained.` };
|
|
14598
|
+
}
|
|
14599
|
+
};
|
|
14600
|
+
}
|
|
14601
|
+
function makeFleetStatusTool(director) {
|
|
14602
|
+
return {
|
|
14603
|
+
name: "fleet_status",
|
|
14604
|
+
description: "Snapshot of the fleet \u2014 every subagent's current status, coordinator counts (total/running/idle/stopped), pending task descriptions, and usage rollup.",
|
|
14605
|
+
permission: "auto",
|
|
14606
|
+
mutating: false,
|
|
14607
|
+
inputSchema: { type: "object", properties: {}, required: [] },
|
|
14608
|
+
async execute() {
|
|
14609
|
+
const base = director.status();
|
|
14610
|
+
const fm = director.fleetManager;
|
|
14611
|
+
const stats = fm?.getFleetStats();
|
|
14612
|
+
const fleetStatus = fm?.getFleetStatus();
|
|
14613
|
+
return {
|
|
14614
|
+
subagents: base.subagents,
|
|
14615
|
+
coordinatorStats: stats ? { total: stats.total, running: stats.running, idle: stats.idle, stopped: stats.stopped } : void 0,
|
|
14616
|
+
pending: fleetStatus?.pending ?? [],
|
|
14617
|
+
usage: fm?.snapshot()
|
|
14618
|
+
};
|
|
14619
|
+
}
|
|
14620
|
+
};
|
|
14621
|
+
}
|
|
14622
|
+
function makeFleetUsageTool(director) {
|
|
14623
|
+
return {
|
|
14624
|
+
name: "fleet_usage",
|
|
14625
|
+
description: "Token + cost breakdown across the fleet, per-subagent and totals.",
|
|
14626
|
+
permission: "auto",
|
|
14627
|
+
mutating: false,
|
|
14628
|
+
inputSchema: { type: "object", properties: {}, required: [] },
|
|
14629
|
+
async execute() {
|
|
14630
|
+
return director.snapshot();
|
|
14631
|
+
}
|
|
14632
|
+
};
|
|
14633
|
+
}
|
|
14634
|
+
function makeFleetSessionTool(director) {
|
|
14635
|
+
return {
|
|
14636
|
+
name: "fleet_session",
|
|
14637
|
+
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.",
|
|
14638
|
+
permission: "auto",
|
|
14639
|
+
mutating: false,
|
|
14640
|
+
inputSchema: {
|
|
14641
|
+
type: "object",
|
|
14642
|
+
properties: {
|
|
14643
|
+
subagentId: { type: "string", description: "Subagent id to read the transcript of." },
|
|
14644
|
+
tail: { type: "number", description: "Number of trailing JSONL lines to return. Omit for the full transcript." }
|
|
14320
14645
|
},
|
|
14321
14646
|
required: ["subagentId"]
|
|
14322
14647
|
},
|
|
@@ -14403,601 +14728,358 @@ function makeCollabDebugTool(director) {
|
|
|
14403
14728
|
targetPaths: i.targetPaths,
|
|
14404
14729
|
timeoutMs: i.timeoutMs,
|
|
14405
14730
|
maxTargetFiles: i.maxTargetFiles,
|
|
14406
|
-
contextWindow: i.contextWindow
|
|
14407
|
-
};
|
|
14408
|
-
try {
|
|
14409
|
-
const report = await director.spawnCollab(options);
|
|
14410
|
-
return {
|
|
14411
|
-
sessionId: report.sessionId,
|
|
14412
|
-
overallVerdict: report.overallVerdict,
|
|
14413
|
-
bugCount: report.bugs.length,
|
|
14414
|
-
planCount: report.refactorPlans.length,
|
|
14415
|
-
evaluationCount: report.evaluations.length,
|
|
14416
|
-
summary: report.summary,
|
|
14417
|
-
bugs: report.bugs,
|
|
14418
|
-
refactorPlans: report.refactorPlans,
|
|
14419
|
-
evaluations: report.evaluations
|
|
14420
|
-
};
|
|
14421
|
-
} catch (err) {
|
|
14422
|
-
const msg = err instanceof Error ? err.message : String(err);
|
|
14423
|
-
return { error: "collab_debug failed: " + msg };
|
|
14424
|
-
}
|
|
14425
|
-
}
|
|
14426
|
-
};
|
|
14427
|
-
}
|
|
14428
|
-
function makeFleetEmitTool(director) {
|
|
14429
|
-
return {
|
|
14430
|
-
name: "fleet_emit",
|
|
14431
|
-
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.",
|
|
14432
|
-
permission: "auto",
|
|
14433
|
-
mutating: false,
|
|
14434
|
-
inputSchema: {
|
|
14435
|
-
type: "object",
|
|
14436
|
-
properties: {
|
|
14437
|
-
type: {
|
|
14438
|
-
type: "string",
|
|
14439
|
-
description: "Event type string (e.g. bug.found, refactor.plan, critic.evaluation, progress, result)."
|
|
14440
|
-
},
|
|
14441
|
-
payload: {
|
|
14442
|
-
type: "object",
|
|
14443
|
-
description: "Event payload. Structure depends on event type. Use null if no payload."
|
|
14444
|
-
}
|
|
14445
|
-
},
|
|
14446
|
-
required: ["type"]
|
|
14447
|
-
},
|
|
14448
|
-
async execute(input) {
|
|
14449
|
-
const i = input;
|
|
14450
|
-
director.fleet.emit({
|
|
14451
|
-
subagentId: director.id,
|
|
14452
|
-
ts: Date.now(),
|
|
14453
|
-
type: i.type,
|
|
14454
|
-
payload: i.payload ?? {}
|
|
14455
|
-
});
|
|
14456
|
-
return { ok: true, event: i.type };
|
|
14457
|
-
}
|
|
14458
|
-
};
|
|
14459
|
-
}
|
|
14460
|
-
function makeWorkCompleteTool(director) {
|
|
14461
|
-
return {
|
|
14462
|
-
name: "work_complete",
|
|
14463
|
-
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.",
|
|
14464
|
-
permission: "auto",
|
|
14465
|
-
mutating: false,
|
|
14466
|
-
inputSchema: { type: "object", properties: {}, required: [] },
|
|
14467
|
-
async execute() {
|
|
14468
|
-
director.workComplete();
|
|
14469
|
-
return { ok: true, message: "Fleet wind-down signaled. No new spawns or task dispatches." };
|
|
14470
|
-
}
|
|
14471
|
-
};
|
|
14472
|
-
}
|
|
14473
|
-
var DEFAULT_MAX_TARGET_FILES = 30;
|
|
14474
|
-
var CollabSession = class extends EventEmitter {
|
|
14475
|
-
sessionId;
|
|
14476
|
-
options;
|
|
14477
|
-
snapshot;
|
|
14478
|
-
director;
|
|
14479
|
-
fleetBus;
|
|
14480
|
-
subagentIds = /* @__PURE__ */ new Map();
|
|
14481
|
-
// role → subagentId
|
|
14482
|
-
bugs = /* @__PURE__ */ new Map();
|
|
14483
|
-
plans = /* @__PURE__ */ new Map();
|
|
14484
|
-
evaluations = /* @__PURE__ */ new Map();
|
|
14485
|
-
disposers = new Array();
|
|
14486
|
-
settled = false;
|
|
14487
|
-
timeoutMs;
|
|
14488
|
-
cancelled = false;
|
|
14489
|
-
alerts = [];
|
|
14490
|
-
/** Tracks tool call counts per subagent for progress-based timeout decisions. */
|
|
14491
|
-
progressBySubagent = /* @__PURE__ */ new Map();
|
|
14492
|
-
/** Last tool call count when a timeout warning was handled. */
|
|
14493
|
-
lastTimeoutProgress = /* @__PURE__ */ new Map();
|
|
14494
|
-
/** Session-level timeout timer handle (cleared on cancel or natural completion). */
|
|
14495
|
-
_timeoutTimer;
|
|
14496
|
-
constructor(director, fleetBus, options) {
|
|
14497
|
-
super();
|
|
14498
|
-
this.sessionId = randomUUID();
|
|
14499
|
-
this.options = options;
|
|
14500
|
-
this.director = director;
|
|
14501
|
-
this.fleetBus = fleetBus;
|
|
14502
|
-
this.timeoutMs = options.timeoutMs ?? 10 * 60 * 1e3;
|
|
14503
|
-
if (options.prebuiltSnapshot) {
|
|
14504
|
-
this.snapshot = options.prebuiltSnapshot;
|
|
14505
|
-
} else {
|
|
14506
|
-
this.snapshot = {
|
|
14507
|
-
id: this.sessionId,
|
|
14508
|
-
createdAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
14509
|
-
files: []
|
|
14510
|
-
};
|
|
14511
|
-
}
|
|
14512
|
-
}
|
|
14513
|
-
get id() {
|
|
14514
|
-
return this.sessionId;
|
|
14515
|
-
}
|
|
14516
|
-
getSessionAlerts() {
|
|
14517
|
-
return [...this.alerts];
|
|
14518
|
-
}
|
|
14519
|
-
isCancelled() {
|
|
14520
|
-
return this.cancelled;
|
|
14521
|
-
}
|
|
14522
|
-
/**
|
|
14523
|
-
* Snapshot of role → subagentId map. The Director calls coordinator.stop()
|
|
14524
|
-
* for each agent when cancelling the session, using this map to enumerate
|
|
14525
|
-
* all three collab agents.
|
|
14526
|
-
*/
|
|
14527
|
-
getSubagentIds() {
|
|
14528
|
-
return new Map(this.subagentIds);
|
|
14529
|
-
}
|
|
14530
|
-
/**
|
|
14531
|
-
* Returns the effective file limit for this session.
|
|
14532
|
-
* Priority: explicit `maxTargetFiles` > dynamic from `contextWindow` > `DEFAULT_MAX_TARGET_FILES`.
|
|
14533
|
-
*/
|
|
14534
|
-
effectiveFileLimit() {
|
|
14535
|
-
if (this.options.maxTargetFiles !== void 0) {
|
|
14536
|
-
return this.options.maxTargetFiles;
|
|
14537
|
-
}
|
|
14538
|
-
if (this.options.contextWindow !== void 0) {
|
|
14539
|
-
return Math.max(5, Math.floor(this.options.contextWindow * 0.4 / 2e3));
|
|
14540
|
-
}
|
|
14541
|
-
return DEFAULT_MAX_TARGET_FILES;
|
|
14542
|
-
}
|
|
14543
|
-
async buildSnapshot() {
|
|
14544
|
-
if (this.snapshot.files.length > 0) return this.snapshot;
|
|
14545
|
-
const allFiles = [];
|
|
14546
|
-
for (const pattern of this.options.targetPaths) {
|
|
14547
|
-
const expanded = await expandGlob(pattern);
|
|
14548
|
-
allFiles.push(...expanded);
|
|
14549
|
-
}
|
|
14550
|
-
const limit = this.effectiveFileLimit();
|
|
14551
|
-
if (allFiles.length > limit) {
|
|
14552
|
-
const hint = this.options.contextWindow ? `contextWindow=${this.options.contextWindow} \u2192 calculated limit=${limit}` : `default limit=${DEFAULT_MAX_TARGET_FILES}`;
|
|
14553
|
-
throw new Error(
|
|
14554
|
-
`[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.`
|
|
14555
|
-
);
|
|
14556
|
-
}
|
|
14557
|
-
for (const filePath of allFiles) {
|
|
14558
|
-
try {
|
|
14559
|
-
const content = await fsp3.readFile(filePath, "utf8");
|
|
14560
|
-
const ext = filePath.split(".").pop() ?? "";
|
|
14561
|
-
const language = ext === "ts" || ext === "tsx" ? "typescript" : ext === "js" || ext === "jsx" ? "javascript" : ext === "md" ? "markdown" : ext === "json" ? "json" : void 0;
|
|
14562
|
-
this.snapshot.files.push({ path: filePath, content, language });
|
|
14563
|
-
} catch {
|
|
14564
|
-
this.snapshot.files.push({ path: filePath, content: "", language: void 0 });
|
|
14565
|
-
}
|
|
14566
|
-
}
|
|
14567
|
-
return this.snapshot;
|
|
14568
|
-
}
|
|
14569
|
-
/**
|
|
14570
|
-
* Cancel the session. Emits director.cancel_collab on the FleetBus so all
|
|
14571
|
-
* collab agents finish early. The session-level timeout timer is also cleared.
|
|
14572
|
-
* Safe to call multiple times (idempotent after first call).
|
|
14573
|
-
*/
|
|
14574
|
-
cancel(reason = "Director cancelled collab session") {
|
|
14575
|
-
if (this.settled) return;
|
|
14576
|
-
this.cancelled = true;
|
|
14577
|
-
if (this._timeoutTimer) {
|
|
14578
|
-
clearTimeout(this._timeoutTimer);
|
|
14579
|
-
this._timeoutTimer = void 0;
|
|
14580
|
-
}
|
|
14581
|
-
this.fleetBus.emit({
|
|
14582
|
-
subagentId: this.director.id,
|
|
14583
|
-
ts: Date.now(),
|
|
14584
|
-
type: "director.cancel_collab",
|
|
14585
|
-
payload: { sessionId: this.sessionId, reason, cancelledAt: (/* @__PURE__ */ new Date()).toISOString() }
|
|
14586
|
-
});
|
|
14587
|
-
this.fleetBus.emit({
|
|
14588
|
-
subagentId: this.director.id,
|
|
14589
|
-
ts: Date.now(),
|
|
14590
|
-
type: "collab.cancelled",
|
|
14591
|
-
payload: { sessionId: this.sessionId, reason }
|
|
14592
|
-
});
|
|
14593
|
-
}
|
|
14594
|
-
async start() {
|
|
14595
|
-
if (this.settled) throw new Error("session already settled");
|
|
14596
|
-
this.settled = true;
|
|
14597
|
-
await this.buildSnapshot();
|
|
14598
|
-
this.wireFleetBus();
|
|
14599
|
-
const [bugHunterId, refactorPlannerId, criticId] = await Promise.all([
|
|
14600
|
-
this.spawnAgent("bug-hunter", this.buildBugHunterTask()),
|
|
14601
|
-
this.spawnAgent("refactor-planner", this.buildRefactorPlannerTask()),
|
|
14602
|
-
this.spawnAgent("critic", this.buildCriticTask())
|
|
14603
|
-
]);
|
|
14604
|
-
this.subagentIds.set("bug-hunter", bugHunterId);
|
|
14605
|
-
this.subagentIds.set("refactor-planner", refactorPlannerId);
|
|
14606
|
-
this.subagentIds.set("critic", criticId);
|
|
14607
|
-
const timeout = new Promise((_, reject) => {
|
|
14608
|
-
this._timeoutTimer = setTimeout(() => {
|
|
14609
|
-
this.cancel("Session-level timeout reached");
|
|
14610
|
-
reject(new Error(`CollabSession timed out after ${this.timeoutMs}ms`));
|
|
14611
|
-
}, this.timeoutMs);
|
|
14612
|
-
});
|
|
14613
|
-
let results = null;
|
|
14614
|
-
try {
|
|
14615
|
-
results = await Promise.race([
|
|
14616
|
-
Promise.all([
|
|
14617
|
-
this.director.awaitTasks([bugHunterId]),
|
|
14618
|
-
this.director.awaitTasks([refactorPlannerId]),
|
|
14619
|
-
this.director.awaitTasks([criticId])
|
|
14620
|
-
]),
|
|
14621
|
-
timeout
|
|
14622
|
-
]);
|
|
14623
|
-
} catch (err) {
|
|
14624
|
-
if (this._timeoutTimer) {
|
|
14625
|
-
clearTimeout(this._timeoutTimer);
|
|
14626
|
-
this._timeoutTimer = void 0;
|
|
14627
|
-
}
|
|
14628
|
-
this.cleanup();
|
|
14629
|
-
const error = err instanceof Error ? err : new Error(String(err));
|
|
14630
|
-
this.emit("session.error", error);
|
|
14631
|
-
throw error;
|
|
14632
|
-
}
|
|
14633
|
-
for (const result of results.flat()) {
|
|
14634
|
-
await this.parseAndEmit(result);
|
|
14635
|
-
}
|
|
14636
|
-
const report = this.assembleReport();
|
|
14637
|
-
this.cleanup();
|
|
14638
|
-
this.emit("session.done", report);
|
|
14639
|
-
return report;
|
|
14640
|
-
}
|
|
14641
|
-
async parseAndEmit(result) {
|
|
14642
|
-
if (result.status !== "success" || result.result == null) return;
|
|
14643
|
-
const text = typeof result.result === "string" ? result.result : JSON.stringify(result.result);
|
|
14644
|
-
for (const obj of this.extractJsonObjects(text)) {
|
|
14645
|
-
const type = "finding" in obj ? "bug.found" : "plan" in obj ? "refactor.plan" : "evaluation" in obj ? "critic.evaluation" : null;
|
|
14646
|
-
if (!type) continue;
|
|
14647
|
-
this.fleetBus.emit({
|
|
14648
|
-
subagentId: result.subagentId,
|
|
14649
|
-
taskId: result.taskId,
|
|
14650
|
-
ts: Date.now(),
|
|
14651
|
-
type,
|
|
14652
|
-
payload: obj
|
|
14653
|
-
});
|
|
14654
|
-
}
|
|
14655
|
-
}
|
|
14656
|
-
extractJsonObjects(text) {
|
|
14657
|
-
const objects = [];
|
|
14658
|
-
let depth = 0;
|
|
14659
|
-
let start = -1;
|
|
14660
|
-
let inString = false;
|
|
14661
|
-
let escaped = false;
|
|
14662
|
-
for (let i = 0; i < text.length; i++) {
|
|
14663
|
-
const ch = text[i];
|
|
14664
|
-
if (inString) {
|
|
14665
|
-
if (escaped) escaped = false;
|
|
14666
|
-
else if (ch === "\\") escaped = true;
|
|
14667
|
-
else if (ch === '"') inString = false;
|
|
14668
|
-
continue;
|
|
14669
|
-
}
|
|
14670
|
-
if (ch === '"') {
|
|
14671
|
-
inString = true;
|
|
14672
|
-
} else if (ch === "{") {
|
|
14673
|
-
if (depth === 0) start = i;
|
|
14674
|
-
depth++;
|
|
14675
|
-
} else if (ch === "}" && depth > 0) {
|
|
14676
|
-
depth--;
|
|
14677
|
-
if (depth === 0 && start >= 0) {
|
|
14678
|
-
const candidate = text.slice(start, i + 1);
|
|
14679
|
-
try {
|
|
14680
|
-
const parsed = JSON.parse(candidate);
|
|
14681
|
-
if (parsed && typeof parsed === "object" && !Array.isArray(parsed)) {
|
|
14682
|
-
objects.push(parsed);
|
|
14683
|
-
}
|
|
14684
|
-
} catch {
|
|
14685
|
-
}
|
|
14686
|
-
start = -1;
|
|
14687
|
-
}
|
|
14688
|
-
}
|
|
14689
|
-
}
|
|
14690
|
-
return objects;
|
|
14691
|
-
}
|
|
14692
|
-
budgetForRole(role) {
|
|
14693
|
-
if (this.options.budgetOverrides?.[role]) {
|
|
14694
|
-
return this.options.budgetOverrides[role];
|
|
14695
|
-
}
|
|
14696
|
-
const defaults = {
|
|
14697
|
-
"bug-hunter": { maxIterations: 2e3, maxToolCalls: 5e3, timeoutMs: 10 * 60 * 1e3 },
|
|
14698
|
-
"refactor-planner": { maxIterations: 1500, maxToolCalls: 4e3, timeoutMs: 8 * 60 * 1e3 },
|
|
14699
|
-
"critic": { maxIterations: 1e3, maxToolCalls: 3e3, timeoutMs: 6 * 60 * 1e3 }
|
|
14700
|
-
};
|
|
14701
|
-
return defaults[role] ?? { maxIterations: 1500, maxToolCalls: 4e3, timeoutMs: 8 * 60 * 1e3 };
|
|
14702
|
-
}
|
|
14703
|
-
async spawnAgent(role, taskBrief) {
|
|
14704
|
-
const budget = this.budgetForRole(role);
|
|
14705
|
-
const cfg = {
|
|
14706
|
-
id: `${role}-${this.sessionId}`,
|
|
14707
|
-
name: role,
|
|
14708
|
-
role,
|
|
14709
|
-
tools: ["fleet_emit", "fleet_status", "read", "grep", "glob", "bash", "write"],
|
|
14710
|
-
maxIterations: budget.maxIterations,
|
|
14711
|
-
maxToolCalls: budget.maxToolCalls,
|
|
14712
|
-
timeoutMs: budget.timeoutMs
|
|
14713
|
-
};
|
|
14714
|
-
const subagentId = await this.director.spawn(cfg);
|
|
14715
|
-
await this.director.assign({ id: randomUUID(), subagentId, description: taskBrief });
|
|
14716
|
-
return subagentId;
|
|
14717
|
-
}
|
|
14718
|
-
buildBugHunterTask() {
|
|
14719
|
-
const scratchpad = this.director.sharedScratchpadPath ?? "/tmp";
|
|
14720
|
-
const fileContents = this.snapshot.files.map((f) => `=== ${f.path} ===
|
|
14721
|
-
${f.content}`).join("\n\n");
|
|
14722
|
-
return `You are BugHunter. Scan the following files for bugs and code smells.
|
|
14723
|
-
|
|
14724
|
-
Target files:
|
|
14725
|
-
${fileContents}
|
|
14726
|
-
|
|
14727
|
-
For each bug found, emit it using the fleet_emit tool immediately:
|
|
14728
|
-
{ "type": "bug.found", "payload": { "finding": { "id": "<uuid>", "type": "<pattern>", "severity": "<critical|high|medium|low>", "location": { "file": "<path>", "line": <n> }, "description": "<explain>", "suggestedFix": "<optional>" } } }
|
|
14729
|
-
|
|
14730
|
-
After scanning all files, write your full markdown bug report to:
|
|
14731
|
-
${scratchpad}/bug-hunter-report-${this.sessionId}.md
|
|
14732
|
-
|
|
14733
|
-
Important: emit each finding as soon as you find it. Do not batch or wait until the end.`;
|
|
14734
|
-
}
|
|
14735
|
-
buildRefactorPlannerTask() {
|
|
14736
|
-
const scratchpad = this.director.sharedScratchpadPath ?? "/tmp";
|
|
14737
|
-
const bugHunterReportPath = `${scratchpad}/bug-hunter-report-${this.sessionId}.md`;
|
|
14738
|
-
const fileContents = this.snapshot.files.map((f) => `=== ${f.path} ===
|
|
14739
|
-
${f.content}`).join("\n\n");
|
|
14740
|
-
return `You are RefactorPlanner. Plan refactorings for the following files.
|
|
14741
|
-
|
|
14742
|
-
Target files:
|
|
14743
|
-
${fileContents}
|
|
14744
|
-
|
|
14745
|
-
Read the BugHunter report at: ${bugHunterReportPath}
|
|
14746
|
-
|
|
14747
|
-
For each bug you can address, emit a refactor plan using fleet_emit:
|
|
14748
|
-
{ "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>" } } }
|
|
14749
|
-
|
|
14750
|
-
Also write your full markdown plan to:
|
|
14751
|
-
${scratchpad}/refactor-plan-${this.sessionId}.md
|
|
14752
|
-
|
|
14753
|
-
Emit each plan immediately. Do not wait until planning is complete.`;
|
|
14754
|
-
}
|
|
14755
|
-
buildCriticTask() {
|
|
14756
|
-
const scratchpad = this.director.sharedScratchpadPath ?? "/tmp";
|
|
14757
|
-
const bugHunterReportPath = `${scratchpad}/bug-hunter-report-${this.sessionId}.md`;
|
|
14758
|
-
const refactorPlanPath = `${scratchpad}/refactor-plan-${this.sessionId}.md`;
|
|
14759
|
-
const fileContents = this.snapshot.files.map((f) => `=== ${f.path} ===
|
|
14760
|
-
${f.content}`).join("\n\n");
|
|
14761
|
-
return `You are Critic. Evaluate bug findings and refactor plans.
|
|
14762
|
-
|
|
14763
|
-
Target files:
|
|
14764
|
-
${fileContents}
|
|
14765
|
-
|
|
14766
|
-
Read the BugHunter report at: ${bugHunterReportPath}
|
|
14767
|
-
Read the RefactorPlanner report at: ${refactorPlanPath}
|
|
14768
|
-
|
|
14769
|
-
For each bug and refactor plan, emit your evaluation using fleet_emit:
|
|
14770
|
-
{ "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>" }] } } }
|
|
14771
|
-
|
|
14772
|
-
After all evaluations, write your markdown report to:
|
|
14773
|
-
${scratchpad}/critic-report-${this.sessionId}.md
|
|
14774
|
-
|
|
14775
|
-
Emit each evaluation immediately. Do not wait until you have read all reports.`;
|
|
14776
|
-
}
|
|
14777
|
-
wireFleetBus() {
|
|
14778
|
-
const dTool = this.fleetBus.filter("tool.executed", (e) => {
|
|
14779
|
-
this.progressBySubagent.set(e.subagentId, (this.progressBySubagent.get(e.subagentId) ?? 0) + 1);
|
|
14780
|
-
});
|
|
14781
|
-
this.disposers.push(dTool);
|
|
14782
|
-
const dBudget = this.fleetBus.filter("budget.threshold_reached", (e) => {
|
|
14783
|
-
const payload = e.payload;
|
|
14784
|
-
const role = this.roleFromSubagentId(e.subagentId);
|
|
14785
|
-
if (!role) return;
|
|
14786
|
-
const btwNotes = this.director.getLeaderBtwNotes();
|
|
14787
|
-
const alert = {
|
|
14788
|
-
sessionId: this.sessionId,
|
|
14789
|
-
subagentId: e.subagentId,
|
|
14790
|
-
role,
|
|
14791
|
-
level: "warning" /* WARNING */,
|
|
14792
|
-
message: `${role} hit ${payload.kind} soft limit (${payload.used}/${payload.limit})`,
|
|
14793
|
-
budgetKind: payload.kind,
|
|
14794
|
-
elapsedMs: payload.timeoutMs,
|
|
14795
|
-
limit: payload.limit,
|
|
14796
|
-
btwNotes
|
|
14797
|
-
};
|
|
14798
|
-
this.alerts.push(alert);
|
|
14799
|
-
this.fleetBus.emit({
|
|
14800
|
-
subagentId: e.subagentId,
|
|
14801
|
-
ts: Date.now(),
|
|
14802
|
-
type: "collab.warning",
|
|
14803
|
-
payload: alert
|
|
14804
|
-
});
|
|
14805
|
-
const decision = this.options.onBudgetWarning?.(alert) ?? "ignore";
|
|
14806
|
-
if (decision === "cancel") {
|
|
14807
|
-
this.cancel(`Director cancelled: ${role} ${payload.kind} threshold`);
|
|
14808
|
-
return;
|
|
14809
|
-
}
|
|
14810
|
-
if (payload.kind === "timeout" || payload.kind === "idle_timeout") {
|
|
14811
|
-
const progress = this.progressBySubagent.get(e.subagentId) ?? 0;
|
|
14812
|
-
const lastProgress = this.lastTimeoutProgress.get(e.subagentId) ?? -1;
|
|
14813
|
-
if (progress <= lastProgress) {
|
|
14814
|
-
payload.deny();
|
|
14815
|
-
return;
|
|
14816
|
-
}
|
|
14817
|
-
this.lastTimeoutProgress.set(e.subagentId, progress);
|
|
14818
|
-
const newLimit = Math.min(Math.ceil((payload.timeoutMs ?? payload.limit) * 2), 24 * 60 * 6e4);
|
|
14819
|
-
setImmediate(() => {
|
|
14820
|
-
payload.extend({ timeoutMs: newLimit });
|
|
14821
|
-
});
|
|
14822
|
-
return;
|
|
14823
|
-
}
|
|
14824
|
-
if (decision === "extend") {
|
|
14825
|
-
setImmediate(() => {
|
|
14826
|
-
const base = Math.max(payload.limit, payload.used);
|
|
14827
|
-
const extra = {};
|
|
14828
|
-
switch (payload.kind) {
|
|
14829
|
-
case "iterations":
|
|
14830
|
-
extra.maxIterations = Math.min(Math.ceil(base * 1.5), 5e4);
|
|
14831
|
-
break;
|
|
14832
|
-
case "tool_calls":
|
|
14833
|
-
extra.maxToolCalls = Math.min(Math.ceil(base * 1.5), 1e5);
|
|
14834
|
-
break;
|
|
14835
|
-
case "tokens":
|
|
14836
|
-
extra.maxTokens = Math.min(Math.ceil(base * 1.5), 5e6);
|
|
14837
|
-
break;
|
|
14838
|
-
case "cost":
|
|
14839
|
-
extra.maxCostUsd = Math.min(base * 1.5, 100);
|
|
14840
|
-
break;
|
|
14841
|
-
}
|
|
14842
|
-
payload.extend(extra);
|
|
14843
|
-
});
|
|
14844
|
-
return;
|
|
14845
|
-
}
|
|
14846
|
-
if (payload.kind !== "timeout") {
|
|
14847
|
-
setImmediate(() => {
|
|
14848
|
-
const base = Math.max(payload.limit, payload.used);
|
|
14849
|
-
const extra = {};
|
|
14850
|
-
switch (payload.kind) {
|
|
14851
|
-
case "iterations":
|
|
14852
|
-
extra.maxIterations = Math.min(Math.ceil(base * 1.25), 5e4);
|
|
14853
|
-
break;
|
|
14854
|
-
case "tool_calls":
|
|
14855
|
-
extra.maxToolCalls = Math.min(Math.ceil(base * 1.25), 1e5);
|
|
14856
|
-
break;
|
|
14857
|
-
case "tokens":
|
|
14858
|
-
extra.maxTokens = Math.min(Math.ceil(base * 1.25), 5e6);
|
|
14859
|
-
break;
|
|
14860
|
-
case "cost":
|
|
14861
|
-
extra.maxCostUsd = Math.min(base * 1.25, 100);
|
|
14862
|
-
break;
|
|
14863
|
-
}
|
|
14864
|
-
payload.extend(extra);
|
|
14865
|
-
});
|
|
14866
|
-
}
|
|
14867
|
-
});
|
|
14868
|
-
this.disposers.push(dBudget);
|
|
14869
|
-
const dCancel = this.fleetBus.filter("director.cancel_collab", (e) => {
|
|
14870
|
-
const payload = e.payload;
|
|
14871
|
-
if (payload.sessionId !== this.sessionId) return;
|
|
14872
|
-
this.cancelled = true;
|
|
14873
|
-
if (this._timeoutTimer) {
|
|
14874
|
-
clearTimeout(this._timeoutTimer);
|
|
14875
|
-
this._timeoutTimer = void 0;
|
|
14731
|
+
contextWindow: i.contextWindow
|
|
14732
|
+
};
|
|
14733
|
+
try {
|
|
14734
|
+
const report = await director.spawnCollab(options);
|
|
14735
|
+
return {
|
|
14736
|
+
sessionId: report.sessionId,
|
|
14737
|
+
overallVerdict: report.overallVerdict,
|
|
14738
|
+
bugCount: report.bugs.length,
|
|
14739
|
+
planCount: report.refactorPlans.length,
|
|
14740
|
+
evaluationCount: report.evaluations.length,
|
|
14741
|
+
summary: report.summary,
|
|
14742
|
+
bugs: report.bugs,
|
|
14743
|
+
refactorPlans: report.refactorPlans,
|
|
14744
|
+
evaluations: report.evaluations
|
|
14745
|
+
};
|
|
14746
|
+
} catch (err) {
|
|
14747
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
14748
|
+
return { error: "collab_debug failed: " + msg };
|
|
14876
14749
|
}
|
|
14877
|
-
|
|
14878
|
-
|
|
14750
|
+
}
|
|
14751
|
+
};
|
|
14752
|
+
}
|
|
14753
|
+
function makeFleetEmitTool(director) {
|
|
14754
|
+
return {
|
|
14755
|
+
name: "fleet_emit",
|
|
14756
|
+
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.",
|
|
14757
|
+
permission: "auto",
|
|
14758
|
+
mutating: false,
|
|
14759
|
+
inputSchema: {
|
|
14760
|
+
type: "object",
|
|
14761
|
+
properties: {
|
|
14762
|
+
type: {
|
|
14763
|
+
type: "string",
|
|
14764
|
+
description: "Event type string (e.g. bug.found, refactor.plan, critic.evaluation, progress, result)."
|
|
14765
|
+
},
|
|
14766
|
+
payload: {
|
|
14767
|
+
type: "object",
|
|
14768
|
+
description: "Event payload. Structure depends on event type. Use null if no payload."
|
|
14769
|
+
}
|
|
14770
|
+
},
|
|
14771
|
+
required: ["type"]
|
|
14772
|
+
},
|
|
14773
|
+
async execute(input) {
|
|
14774
|
+
const i = input;
|
|
14775
|
+
director.fleet.emit({
|
|
14776
|
+
subagentId: director.id,
|
|
14879
14777
|
ts: Date.now(),
|
|
14880
|
-
type:
|
|
14881
|
-
payload:
|
|
14778
|
+
type: i.type,
|
|
14779
|
+
payload: i.payload ?? {}
|
|
14882
14780
|
});
|
|
14781
|
+
return { ok: true, event: i.type };
|
|
14782
|
+
}
|
|
14783
|
+
};
|
|
14784
|
+
}
|
|
14785
|
+
function makeWorkCompleteTool(director) {
|
|
14786
|
+
return {
|
|
14787
|
+
name: "work_complete",
|
|
14788
|
+
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.",
|
|
14789
|
+
permission: "auto",
|
|
14790
|
+
mutating: false,
|
|
14791
|
+
inputSchema: { type: "object", properties: {}, required: [] },
|
|
14792
|
+
async execute() {
|
|
14793
|
+
director.workComplete();
|
|
14794
|
+
return { ok: true, message: "Fleet wind-down signaled. No new spawns or task dispatches." };
|
|
14795
|
+
}
|
|
14796
|
+
};
|
|
14797
|
+
}
|
|
14798
|
+
|
|
14799
|
+
// src/coordination/fleet-bus.ts
|
|
14800
|
+
var FleetBus = class {
|
|
14801
|
+
byId = /* @__PURE__ */ new Map();
|
|
14802
|
+
byType = /* @__PURE__ */ new Map();
|
|
14803
|
+
any = /* @__PURE__ */ new Set();
|
|
14804
|
+
/**
|
|
14805
|
+
* Hook a subagent's EventBus into the fleet. Uses `onAny()` (an alias for
|
|
14806
|
+
* `onPattern('*')`) to forward all events with subagent attribution, so
|
|
14807
|
+
* new kernel event types are automatically forwarded without any manual
|
|
14808
|
+
* registration. `subagent.*` events are excluded because they originate
|
|
14809
|
+
* from MultiAgentHost on the parent bus, not the subagent's own bus.
|
|
14810
|
+
*
|
|
14811
|
+
* Returns a disposer that detaches every subscription; call on
|
|
14812
|
+
* subagent teardown so the listeners don't outlive the run.
|
|
14813
|
+
*/
|
|
14814
|
+
attach(subagentId, bus, taskId) {
|
|
14815
|
+
const off = bus.onAny((type, payload) => {
|
|
14816
|
+
if (type.startsWith("subagent.")) return;
|
|
14817
|
+
this.emit({ subagentId, taskId, ts: Date.now(), type, payload });
|
|
14883
14818
|
});
|
|
14884
|
-
|
|
14885
|
-
|
|
14886
|
-
|
|
14887
|
-
|
|
14888
|
-
|
|
14889
|
-
|
|
14819
|
+
return () => {
|
|
14820
|
+
off();
|
|
14821
|
+
};
|
|
14822
|
+
}
|
|
14823
|
+
/** Subscribe to every event from one subagent. */
|
|
14824
|
+
subscribe(subagentId, handler) {
|
|
14825
|
+
let set = this.byId.get(subagentId);
|
|
14826
|
+
if (!set) {
|
|
14827
|
+
set = /* @__PURE__ */ new Set();
|
|
14828
|
+
this.byId.set(subagentId, set);
|
|
14829
|
+
}
|
|
14830
|
+
set.add(handler);
|
|
14831
|
+
return () => {
|
|
14832
|
+
set.delete(handler);
|
|
14833
|
+
};
|
|
14834
|
+
}
|
|
14835
|
+
/** Subscribe to one event type across all subagents. */
|
|
14836
|
+
filter(type, handler) {
|
|
14837
|
+
let set = this.byType.get(type);
|
|
14838
|
+
if (!set) {
|
|
14839
|
+
set = /* @__PURE__ */ new Set();
|
|
14840
|
+
this.byType.set(type, set);
|
|
14841
|
+
}
|
|
14842
|
+
set.add(handler);
|
|
14843
|
+
return () => {
|
|
14844
|
+
set.delete(handler);
|
|
14845
|
+
};
|
|
14846
|
+
}
|
|
14847
|
+
/** Subscribe to literally everything. The fleet roll-up uses this. */
|
|
14848
|
+
onAny(handler) {
|
|
14849
|
+
this.any.add(handler);
|
|
14850
|
+
return () => {
|
|
14851
|
+
this.any.delete(handler);
|
|
14852
|
+
};
|
|
14853
|
+
}
|
|
14854
|
+
emit(event) {
|
|
14855
|
+
const byId = this.byId.get(event.subagentId);
|
|
14856
|
+
if (byId)
|
|
14857
|
+
for (const h of byId) {
|
|
14858
|
+
try {
|
|
14859
|
+
h(event);
|
|
14860
|
+
} catch {
|
|
14861
|
+
}
|
|
14890
14862
|
}
|
|
14891
|
-
|
|
14892
|
-
|
|
14893
|
-
|
|
14894
|
-
|
|
14895
|
-
|
|
14896
|
-
|
|
14897
|
-
|
|
14863
|
+
const byType = this.byType.get(event.type);
|
|
14864
|
+
if (byType)
|
|
14865
|
+
for (const h of byType) {
|
|
14866
|
+
try {
|
|
14867
|
+
h(event);
|
|
14868
|
+
} catch {
|
|
14869
|
+
}
|
|
14898
14870
|
}
|
|
14899
|
-
|
|
14900
|
-
|
|
14901
|
-
|
|
14902
|
-
|
|
14903
|
-
if (payload?.evaluation) {
|
|
14904
|
-
this.evaluations.set(payload.evaluation.id, payload.evaluation);
|
|
14905
|
-
this.emit("critic.evaluation", payload);
|
|
14871
|
+
for (const h of this.any) {
|
|
14872
|
+
try {
|
|
14873
|
+
h(event);
|
|
14874
|
+
} catch {
|
|
14906
14875
|
}
|
|
14907
|
-
}
|
|
14908
|
-
|
|
14876
|
+
}
|
|
14877
|
+
}
|
|
14878
|
+
};
|
|
14879
|
+
var FleetUsageAggregator = class {
|
|
14880
|
+
constructor(bus, priceLookup, metaLookup) {
|
|
14881
|
+
this.priceLookup = priceLookup;
|
|
14882
|
+
this.metaLookup = metaLookup;
|
|
14883
|
+
this.unsub.push(bus.filter("provider.response", (e) => this.onProviderResponse(e)));
|
|
14884
|
+
this.unsub.push(bus.filter("tool.executed", (e) => this.onToolExecuted(e)));
|
|
14885
|
+
this.unsub.push(bus.filter("iteration.started", (e) => this.onIterationStarted(e)));
|
|
14886
|
+
}
|
|
14887
|
+
priceLookup;
|
|
14888
|
+
metaLookup;
|
|
14889
|
+
perSubagent = /* @__PURE__ */ new Map();
|
|
14890
|
+
total = { input: 0, output: 0, cacheRead: 0, cacheWrite: 0, cost: 0 };
|
|
14891
|
+
unsub = new Array();
|
|
14892
|
+
/**
|
|
14893
|
+
* Remove a terminated subagent's data from the aggregator and subtract its
|
|
14894
|
+
* contribution from the running totals. Call this when a subagent is removed
|
|
14895
|
+
* from the fleet so the aggregator doesn't accumulate unbounded data for
|
|
14896
|
+
* entities that will never emit events again.
|
|
14897
|
+
*/
|
|
14898
|
+
removeSubagent(subagentId) {
|
|
14899
|
+
const snap = this.perSubagent.get(subagentId);
|
|
14900
|
+
if (!snap) return;
|
|
14901
|
+
this.perSubagent.delete(subagentId);
|
|
14902
|
+
this.total.input -= snap.input;
|
|
14903
|
+
this.total.output -= snap.output;
|
|
14904
|
+
this.total.cacheRead -= snap.cacheRead;
|
|
14905
|
+
this.total.cacheWrite -= snap.cacheWrite;
|
|
14906
|
+
this.total.cost -= snap.cost;
|
|
14907
|
+
}
|
|
14908
|
+
/** Disposes all fleet-bus subscriptions. Call when the aggregator is no longer needed. */
|
|
14909
|
+
dispose() {
|
|
14910
|
+
for (const off of this.unsub) off();
|
|
14911
|
+
this.unsub.length = 0;
|
|
14912
|
+
}
|
|
14913
|
+
/** Live snapshot — safe to call from a tool's execute() body. */
|
|
14914
|
+
snapshot() {
|
|
14915
|
+
return {
|
|
14916
|
+
total: { ...this.total },
|
|
14917
|
+
perSubagent: Object.fromEntries(
|
|
14918
|
+
Array.from(this.perSubagent.entries()).map(([k, v]) => [k, { ...v }])
|
|
14919
|
+
)
|
|
14920
|
+
};
|
|
14921
|
+
}
|
|
14922
|
+
ensure(subagentId) {
|
|
14923
|
+
let snap = this.perSubagent.get(subagentId);
|
|
14924
|
+
if (!snap) {
|
|
14925
|
+
const meta = this.metaLookup?.(subagentId);
|
|
14926
|
+
snap = {
|
|
14927
|
+
subagentId,
|
|
14928
|
+
provider: meta?.provider,
|
|
14929
|
+
model: meta?.model,
|
|
14930
|
+
input: 0,
|
|
14931
|
+
output: 0,
|
|
14932
|
+
cacheRead: 0,
|
|
14933
|
+
cacheWrite: 0,
|
|
14934
|
+
cost: 0,
|
|
14935
|
+
toolCalls: 0,
|
|
14936
|
+
iterations: 0,
|
|
14937
|
+
startedAt: Date.now(),
|
|
14938
|
+
lastEventAt: Date.now()
|
|
14939
|
+
};
|
|
14940
|
+
this.perSubagent.set(subagentId, snap);
|
|
14941
|
+
}
|
|
14942
|
+
return snap;
|
|
14943
|
+
}
|
|
14944
|
+
onProviderResponse(e) {
|
|
14945
|
+
const snap = this.ensure(e.subagentId);
|
|
14946
|
+
const p = e.payload;
|
|
14947
|
+
const usage = p?.usage;
|
|
14948
|
+
if (!usage) return;
|
|
14949
|
+
snap.input += usage.input ?? 0;
|
|
14950
|
+
snap.output += usage.output ?? 0;
|
|
14951
|
+
snap.cacheRead += usage.cacheRead ?? 0;
|
|
14952
|
+
snap.cacheWrite += usage.cacheWrite ?? 0;
|
|
14953
|
+
this.total.input += usage.input ?? 0;
|
|
14954
|
+
this.total.output += usage.output ?? 0;
|
|
14955
|
+
this.total.cacheRead += usage.cacheRead ?? 0;
|
|
14956
|
+
this.total.cacheWrite += usage.cacheWrite ?? 0;
|
|
14957
|
+
const price = this.priceLookup?.(e.subagentId, snap.provider, snap.model);
|
|
14958
|
+
if (price) {
|
|
14959
|
+
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);
|
|
14960
|
+
snap.cost += delta;
|
|
14961
|
+
this.total.cost += delta;
|
|
14962
|
+
}
|
|
14963
|
+
snap.lastEventAt = e.ts;
|
|
14964
|
+
}
|
|
14965
|
+
onToolExecuted(e) {
|
|
14966
|
+
const snap = this.ensure(e.subagentId);
|
|
14967
|
+
snap.toolCalls += 1;
|
|
14968
|
+
snap.lastEventAt = e.ts;
|
|
14909
14969
|
}
|
|
14910
|
-
|
|
14911
|
-
|
|
14912
|
-
|
|
14913
|
-
|
|
14914
|
-
const match = subagentId.match(/^(bug-hunter|refactor-planner|critic)/);
|
|
14915
|
-
return match?.[1] ?? null;
|
|
14970
|
+
onIterationStarted(e) {
|
|
14971
|
+
const snap = this.ensure(e.subagentId);
|
|
14972
|
+
snap.iterations += 1;
|
|
14973
|
+
snap.lastEventAt = e.ts;
|
|
14916
14974
|
}
|
|
14917
|
-
|
|
14918
|
-
|
|
14919
|
-
|
|
14920
|
-
|
|
14921
|
-
|
|
14922
|
-
|
|
14923
|
-
|
|
14924
|
-
|
|
14925
|
-
|
|
14926
|
-
|
|
14927
|
-
|
|
14928
|
-
|
|
14929
|
-
|
|
14930
|
-
const w = verdictOrder[worst];
|
|
14931
|
-
const c = verdictOrder[eval_.verdict];
|
|
14932
|
-
return c > w ? eval_.verdict : worst;
|
|
14933
|
-
},
|
|
14934
|
-
"approve"
|
|
14935
|
-
);
|
|
14936
|
-
const summary = this.buildMarkdownSummary(bugList, planList, evalList, overallVerdict, disposition);
|
|
14937
|
-
return {
|
|
14938
|
-
sessionId: this.sessionId,
|
|
14939
|
-
startedAt: this.snapshot.createdAt,
|
|
14940
|
-
completedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
14941
|
-
targetPaths: this.options.targetPaths,
|
|
14942
|
-
disposition,
|
|
14943
|
-
bugs: bugList,
|
|
14944
|
-
refactorPlans: planList,
|
|
14945
|
-
evaluations: evalList,
|
|
14946
|
-
alerts: [...this.alerts],
|
|
14947
|
-
overallVerdict,
|
|
14948
|
-
summary
|
|
14949
|
-
};
|
|
14975
|
+
};
|
|
14976
|
+
|
|
14977
|
+
// src/coordination/large-answer-store.ts
|
|
14978
|
+
var LargeAnswerStore = class {
|
|
14979
|
+
/**
|
|
14980
|
+
* Responses above this size (in characters) are stored out-of-context.
|
|
14981
|
+
* Below this, the full answer is returned inline (no overhead).
|
|
14982
|
+
* Default: 2000 chars ≈ 400-600 tokens.
|
|
14983
|
+
*/
|
|
14984
|
+
sizeThreshold;
|
|
14985
|
+
store = /* @__PURE__ */ new Map();
|
|
14986
|
+
constructor(sizeThreshold = 2e3) {
|
|
14987
|
+
this.sizeThreshold = sizeThreshold;
|
|
14950
14988
|
}
|
|
14951
|
-
|
|
14952
|
-
|
|
14953
|
-
|
|
14954
|
-
|
|
14955
|
-
|
|
14956
|
-
|
|
14957
|
-
|
|
14958
|
-
""
|
|
14959
|
-
];
|
|
14960
|
-
if (this.alerts.length > 0) {
|
|
14961
|
-
lines.push("### Alerts", "");
|
|
14962
|
-
for (const alert of this.alerts) {
|
|
14963
|
-
lines.push(`- **[${alert.level.toUpperCase()}]** ${alert.role}: ${alert.message}`);
|
|
14964
|
-
}
|
|
14965
|
-
lines.push("");
|
|
14966
|
-
}
|
|
14967
|
-
if (bugs.length > 0) {
|
|
14968
|
-
lines.push("### Bugs Found", "");
|
|
14969
|
-
for (const b of bugs) {
|
|
14970
|
-
lines.push(`- **[${b.severity.toUpperCase()}]** \`${b.location.file}:${b.location.line}\` \u2014 ${b.description}`);
|
|
14971
|
-
}
|
|
14972
|
-
lines.push("");
|
|
14973
|
-
}
|
|
14974
|
-
if (plans.length > 0) {
|
|
14975
|
-
lines.push("### Refactor Plans", "");
|
|
14976
|
-
for (const p of plans) {
|
|
14977
|
-
lines.push(`- **Phase plan** (risk: ${p.riskScore}, ~${p.estimatedChangeCount} changes)`);
|
|
14978
|
-
for (const phase of p.phases) {
|
|
14979
|
-
lines.push(` - Phase ${phase.number}: ${phase.title} [${phase.risk}]`);
|
|
14980
|
-
}
|
|
14981
|
-
}
|
|
14982
|
-
lines.push("");
|
|
14989
|
+
/**
|
|
14990
|
+
* Store a value, returning a summary + key for inline use.
|
|
14991
|
+
* If the value is below sizeThreshold, returns it as-is (no store entry).
|
|
14992
|
+
*/
|
|
14993
|
+
storeAnswer(value) {
|
|
14994
|
+
if (value === void 0 || value === null) {
|
|
14995
|
+
return { summary: String(value), inline: true };
|
|
14983
14996
|
}
|
|
14984
|
-
|
|
14985
|
-
|
|
14986
|
-
|
|
14987
|
-
|
|
14988
|
-
for (const c of e.concerns) {
|
|
14989
|
-
if (c.severity === "blocking") lines.push(` - ${c.description}`);
|
|
14990
|
-
}
|
|
14991
|
-
}
|
|
14992
|
-
lines.push("");
|
|
14997
|
+
const serialized = typeof value === "string" ? value : JSON.stringify(value);
|
|
14998
|
+
const size = serialized.length;
|
|
14999
|
+
if (size <= this.sizeThreshold) {
|
|
15000
|
+
return { summary: serialized.slice(0, 500), inline: true };
|
|
14993
15001
|
}
|
|
14994
|
-
|
|
15002
|
+
const key = `a-${hashStr(serialized)}`;
|
|
15003
|
+
this.store.set(key, {
|
|
15004
|
+
key,
|
|
15005
|
+
value,
|
|
15006
|
+
size,
|
|
15007
|
+
storedAt: Date.now()
|
|
15008
|
+
});
|
|
15009
|
+
return {
|
|
15010
|
+
key,
|
|
15011
|
+
summary: `[stored: ${size} chars \u2014 use roll_up or ask_result tool to retrieve, key=${key}]`,
|
|
15012
|
+
inline: false
|
|
15013
|
+
};
|
|
14995
15014
|
}
|
|
14996
|
-
|
|
14997
|
-
|
|
14998
|
-
|
|
15015
|
+
/**
|
|
15016
|
+
* Retrieve a previously stored answer by its key.
|
|
15017
|
+
* Returns undefined if the key is unknown or the store was cleared.
|
|
15018
|
+
*/
|
|
15019
|
+
retrieveAnswer(key) {
|
|
15020
|
+
return this.store.get(key)?.value;
|
|
15021
|
+
}
|
|
15022
|
+
/**
|
|
15023
|
+
* Check if a key exists in the store.
|
|
15024
|
+
*/
|
|
15025
|
+
hasAnswer(key) {
|
|
15026
|
+
return this.store.has(key);
|
|
15027
|
+
}
|
|
15028
|
+
/** Number of stored entries. */
|
|
15029
|
+
get size() {
|
|
15030
|
+
return this.store.size;
|
|
15031
|
+
}
|
|
15032
|
+
/** Total characters stored. */
|
|
15033
|
+
get totalChars() {
|
|
15034
|
+
let total = 0;
|
|
15035
|
+
for (const e of this.store.values()) total += e.size;
|
|
15036
|
+
return total;
|
|
15037
|
+
}
|
|
15038
|
+
/** Clear all stored entries. Call at the end of a director run. */
|
|
15039
|
+
clear() {
|
|
15040
|
+
this.store.clear();
|
|
14999
15041
|
}
|
|
15000
15042
|
};
|
|
15043
|
+
function hashStr(s) {
|
|
15044
|
+
let h = 5381;
|
|
15045
|
+
for (let i = 0; i < s.length; i++) {
|
|
15046
|
+
h = h * 33 ^ s.charCodeAt(i);
|
|
15047
|
+
}
|
|
15048
|
+
return (h >>> 0).toString(36);
|
|
15049
|
+
}
|
|
15050
|
+
|
|
15051
|
+
// src/coordination/model-matrix.ts
|
|
15052
|
+
var MATRIX_PHASE_KEYS = Object.keys(AGENTS_BY_PHASE);
|
|
15053
|
+
var ROLE_TO_PHASE = (() => {
|
|
15054
|
+
const map = {};
|
|
15055
|
+
for (const [phase, defs] of Object.entries(AGENTS_BY_PHASE)) {
|
|
15056
|
+
for (const def of defs) {
|
|
15057
|
+
const role = def.config.role;
|
|
15058
|
+
if (role) map[role] = phase;
|
|
15059
|
+
}
|
|
15060
|
+
}
|
|
15061
|
+
return map;
|
|
15062
|
+
})();
|
|
15063
|
+
function phaseForRole(role) {
|
|
15064
|
+
return role ? ROLE_TO_PHASE[role] : void 0;
|
|
15065
|
+
}
|
|
15066
|
+
function resolveModelMatrix(matrix, role) {
|
|
15067
|
+
if (!matrix) return void 0;
|
|
15068
|
+
if (role && matrix[role]) return matrix[role];
|
|
15069
|
+
const phase = phaseForRole(role);
|
|
15070
|
+
if (phase && matrix[phase]) return matrix[phase];
|
|
15071
|
+
if (matrix["*"]) return matrix["*"];
|
|
15072
|
+
return void 0;
|
|
15073
|
+
}
|
|
15074
|
+
function matrixKeyKind(key) {
|
|
15075
|
+
if (key === "*") return "default";
|
|
15076
|
+
if (key in AGENT_CATALOG) return "role";
|
|
15077
|
+
if (MATRIX_PHASE_KEYS.includes(key)) return "phase";
|
|
15078
|
+
return "unknown";
|
|
15079
|
+
}
|
|
15080
|
+
function isValidMatrixKey(key) {
|
|
15081
|
+
return matrixKeyKind(key) !== "unknown";
|
|
15082
|
+
}
|
|
15001
15083
|
|
|
15002
15084
|
// src/coordination/director.ts
|
|
15003
15085
|
var FleetSpawnBudgetError = class extends Error {
|
|
@@ -15169,6 +15251,9 @@ var Director = class _Director {
|
|
|
15169
15251
|
maxLeaderContextLoad;
|
|
15170
15252
|
/** Provider's max context window in tokens. */
|
|
15171
15253
|
maxContext;
|
|
15254
|
+
/** Per-task model matrix (static record or live getter); resolved
|
|
15255
|
+
* per-spawn when no explicit model is set. */
|
|
15256
|
+
modelMatrix;
|
|
15172
15257
|
/**
|
|
15173
15258
|
* When set by `workComplete()`, the director stops dispatching new tasks
|
|
15174
15259
|
* and terminates all running subagents. Used when the director's LLM decides
|
|
@@ -15199,20 +15284,23 @@ var Director = class _Director {
|
|
|
15199
15284
|
this.maxBudgetExtensions = opts.maxBudgetExtensions ?? 5;
|
|
15200
15285
|
this.maxLeaderContextLoad = opts.maxLeaderContextLoad ?? 0.85;
|
|
15201
15286
|
this.maxContext = opts.maxContext ?? 128e3;
|
|
15287
|
+
this.modelMatrix = opts.modelMatrix;
|
|
15202
15288
|
this.sessionsRoot = opts.sessionsRoot;
|
|
15203
15289
|
this.directorRunId = opts.directorRunId ?? this.id;
|
|
15204
|
-
this.stateCheckpoint = opts.stateCheckpointPath ? new DirectorStateCheckpoint(
|
|
15205
|
-
|
|
15206
|
-
|
|
15207
|
-
|
|
15208
|
-
|
|
15209
|
-
|
|
15210
|
-
|
|
15290
|
+
this.stateCheckpoint = opts.stateCheckpointPath ? new DirectorStateCheckpoint(
|
|
15291
|
+
opts.stateCheckpointPath,
|
|
15292
|
+
{
|
|
15293
|
+
directorRunId: this.id,
|
|
15294
|
+
maxSpawns: opts.maxSpawns,
|
|
15295
|
+
spawnDepth: this.spawnDepth,
|
|
15296
|
+
maxSpawnDepth: this.maxSpawnDepth,
|
|
15297
|
+
directorBudget: opts.directorBudget
|
|
15298
|
+
},
|
|
15299
|
+
opts.checkpointDebounceMs ?? 250
|
|
15300
|
+
) : null;
|
|
15211
15301
|
this.fleetManager = opts.fleetManager;
|
|
15212
15302
|
if (this.sharedScratchpadPath) {
|
|
15213
|
-
void fsp3.mkdir(this.sharedScratchpadPath, { recursive: true }).catch(
|
|
15214
|
-
(err) => this.logShutdownError("shared_scratchpad_mkdir", err)
|
|
15215
|
-
);
|
|
15303
|
+
void fsp3.mkdir(this.sharedScratchpadPath, { recursive: true }).catch((err) => this.logShutdownError("shared_scratchpad_mkdir", err));
|
|
15216
15304
|
}
|
|
15217
15305
|
this.transport = new InMemoryBridgeTransport();
|
|
15218
15306
|
this.bridge = new InMemoryAgentBridge(
|
|
@@ -15390,7 +15478,12 @@ var Director = class _Director {
|
|
|
15390
15478
|
*/
|
|
15391
15479
|
workComplete() {
|
|
15392
15480
|
this.workCompleteFlag = true;
|
|
15393
|
-
this.fleet.emit({
|
|
15481
|
+
this.fleet.emit({
|
|
15482
|
+
subagentId: this.id,
|
|
15483
|
+
ts: Date.now(),
|
|
15484
|
+
type: "director.work_complete",
|
|
15485
|
+
payload: {}
|
|
15486
|
+
});
|
|
15394
15487
|
}
|
|
15395
15488
|
/** Returns true if `workComplete()` has been called on this director. */
|
|
15396
15489
|
isWorkComplete() {
|
|
@@ -15522,13 +15615,25 @@ var Director = class _Director {
|
|
|
15522
15615
|
"workComplete() has been called \u2014 director closed further spawning"
|
|
15523
15616
|
);
|
|
15524
15617
|
}
|
|
15618
|
+
if (!config.model && this.modelMatrix) {
|
|
15619
|
+
const matrix = typeof this.modelMatrix === "function" ? this.modelMatrix() : this.modelMatrix;
|
|
15620
|
+
const entry = resolveModelMatrix(matrix, config.role);
|
|
15621
|
+
if (entry) {
|
|
15622
|
+
config.model = entry.model;
|
|
15623
|
+
if (entry.provider) config.provider = entry.provider;
|
|
15624
|
+
}
|
|
15625
|
+
}
|
|
15525
15626
|
if (this.fleetManager) {
|
|
15526
15627
|
const rejection = this.fleetManager.canSpawn(config);
|
|
15527
15628
|
if (rejection) {
|
|
15528
|
-
if (rejection.kind === "max_spawn_depth")
|
|
15529
|
-
|
|
15530
|
-
if (rejection.kind === "
|
|
15531
|
-
|
|
15629
|
+
if (rejection.kind === "max_spawn_depth")
|
|
15630
|
+
throw new FleetSpawnBudgetError("max_spawn_depth", rejection.limit, rejection.observed);
|
|
15631
|
+
if (rejection.kind === "max_spawns")
|
|
15632
|
+
throw new FleetSpawnBudgetError("max_spawns", rejection.limit, rejection.observed);
|
|
15633
|
+
if (rejection.kind === "max_cost_usd")
|
|
15634
|
+
throw new FleetCostCapError(rejection.limit, rejection.observed);
|
|
15635
|
+
if (rejection.kind === "max_context_load")
|
|
15636
|
+
throw new FleetContextOverflowError(rejection.limit, rejection.observed);
|
|
15532
15637
|
}
|
|
15533
15638
|
} else {
|
|
15534
15639
|
if (this.spawnDepth >= this.maxSpawnDepth) {
|
|
@@ -15558,7 +15663,9 @@ var Director = class _Director {
|
|
|
15558
15663
|
this.fleetManager.assignNicknameAndRecord(config);
|
|
15559
15664
|
} else {
|
|
15560
15665
|
config.name = assignNickname(role, this._usedNicknames);
|
|
15561
|
-
this._usedNicknames.add(
|
|
15666
|
+
this._usedNicknames.add(
|
|
15667
|
+
config.name.split(" ")[0].toLowerCase().replace(/[^a-z0-9-]/g, "-")
|
|
15668
|
+
);
|
|
15562
15669
|
}
|
|
15563
15670
|
}
|
|
15564
15671
|
result = await this.coordinator.spawn(config);
|
|
@@ -15793,7 +15900,11 @@ var Director = class _Director {
|
|
|
15793
15900
|
subagentId: taskWithId.subagentId ?? "unassigned",
|
|
15794
15901
|
taskId: taskWithId.id,
|
|
15795
15902
|
status: "stopped",
|
|
15796
|
-
error: {
|
|
15903
|
+
error: {
|
|
15904
|
+
kind: "aborted_by_parent",
|
|
15905
|
+
message: "Director called workComplete() \u2014 no further tasks will run",
|
|
15906
|
+
retryable: false
|
|
15907
|
+
},
|
|
15797
15908
|
iterations: 0,
|
|
15798
15909
|
toolCalls: 0,
|
|
15799
15910
|
durationMs: 0
|
|
@@ -22983,22 +23094,24 @@ var ReportGenerator = class {
|
|
|
22983
23094
|
medium: "#ca8a04",
|
|
22984
23095
|
low: "#16a34a"
|
|
22985
23096
|
};
|
|
22986
|
-
const rows = result.findings.map(
|
|
22987
|
-
|
|
23097
|
+
const rows = result.findings.map((f) => {
|
|
23098
|
+
const sevColor = severityColors[f.severity] ?? "#000000";
|
|
23099
|
+
const loc = `${escapeHtml(f.file)}${f.line ? `:${escapeHtml(String(f.line))}` : ""}`;
|
|
23100
|
+
return `
|
|
22988
23101
|
<tr>
|
|
22989
|
-
<td style="color: ${
|
|
22990
|
-
<td>${f.category}</td>
|
|
22991
|
-
<td>${f.title}</td>
|
|
22992
|
-
<td><code>${
|
|
22993
|
-
<td>${f.remediation}</td>
|
|
23102
|
+
<td style="color: ${escapeHtml(sevColor)}; font-weight: bold;">${escapeHtml(f.severity.toUpperCase())}</td>
|
|
23103
|
+
<td>${escapeHtml(f.category)}</td>
|
|
23104
|
+
<td>${escapeHtml(f.title)}</td>
|
|
23105
|
+
<td><code>${loc}</code></td>
|
|
23106
|
+
<td>${escapeHtml(f.remediation)}</td>
|
|
22994
23107
|
</tr>
|
|
22995
|
-
|
|
22996
|
-
).join("");
|
|
23108
|
+
`;
|
|
23109
|
+
}).join("");
|
|
22997
23110
|
return `
|
|
22998
23111
|
<!DOCTYPE html>
|
|
22999
23112
|
<html>
|
|
23000
23113
|
<head>
|
|
23001
|
-
<title>Security Scan Report - ${new Date(result.timestamp).toLocaleDateString()}</title>
|
|
23114
|
+
<title>Security Scan Report - ${escapeHtml(new Date(result.timestamp).toLocaleDateString())}</title>
|
|
23002
23115
|
<style>
|
|
23003
23116
|
body { font-family: system-ui, sans-serif; margin: 2rem; }
|
|
23004
23117
|
table { border-collapse: collapse; width: 100%; }
|
|
@@ -23009,9 +23122,9 @@ var ReportGenerator = class {
|
|
|
23009
23122
|
</head>
|
|
23010
23123
|
<body>
|
|
23011
23124
|
<h1>Security Scan Report</h1>
|
|
23012
|
-
<p><strong>Generated:</strong> ${new Date(result.timestamp).toLocaleString()}</p>
|
|
23013
|
-
<p><strong>Project:</strong> ${result.projectRoot}</p>
|
|
23014
|
-
<p><strong>Tech Stack:</strong> ${result.techStack.stack} (${result.techStack.packageManager})</p>
|
|
23125
|
+
<p><strong>Generated:</strong> ${escapeHtml(new Date(result.timestamp).toLocaleString())}</p>
|
|
23126
|
+
<p><strong>Project:</strong> ${escapeHtml(result.projectRoot)}</p>
|
|
23127
|
+
<p><strong>Tech Stack:</strong> ${escapeHtml(result.techStack.stack)} (${escapeHtml(result.techStack.packageManager)})</p>
|
|
23015
23128
|
|
|
23016
23129
|
<h2>Summary</h2>
|
|
23017
23130
|
<ul>
|
|
@@ -23037,6 +23150,9 @@ var ReportGenerator = class {
|
|
|
23037
23150
|
`.trim();
|
|
23038
23151
|
}
|
|
23039
23152
|
};
|
|
23153
|
+
function escapeHtml(value) {
|
|
23154
|
+
return String(value).replace(/&/g, "&").replace(/</g, "<").replace(/>/g, ">").replace(/"/g, """).replace(/'/g, "'");
|
|
23155
|
+
}
|
|
23040
23156
|
var defaultReportGenerator = new ReportGenerator();
|
|
23041
23157
|
|
|
23042
23158
|
// src/security-scanner/gitignore-updater.ts
|
|
@@ -27048,6 +27164,7 @@ var PhaseOrchestrator = class {
|
|
|
27048
27164
|
maxConcurrentPhases: opts.maxConcurrentPhases ?? 1,
|
|
27049
27165
|
maxConcurrentTasks: opts.maxConcurrentTasks ?? 2,
|
|
27050
27166
|
maxRetries: opts.maxRetries ?? 2,
|
|
27167
|
+
maxVerifyAttempts: opts.maxVerifyAttempts ?? 2,
|
|
27051
27168
|
autonomous: opts.autonomous ?? true,
|
|
27052
27169
|
phaseDelayMs: opts.phaseDelayMs ?? 0,
|
|
27053
27170
|
stopOnFailure: opts.stopOnFailure ?? true,
|
|
@@ -27094,7 +27211,10 @@ var PhaseOrchestrator = class {
|
|
|
27094
27211
|
resume() {
|
|
27095
27212
|
this.paused = false;
|
|
27096
27213
|
this.tick().catch((err) => {
|
|
27097
|
-
console.error(
|
|
27214
|
+
console.error(
|
|
27215
|
+
"[phase-orchestrator] tick failed:",
|
|
27216
|
+
err instanceof Error ? err.message : String(err)
|
|
27217
|
+
);
|
|
27098
27218
|
});
|
|
27099
27219
|
}
|
|
27100
27220
|
/** Tamamen durdur — aktif fazlar da durdurulur */
|
|
@@ -27175,33 +27295,30 @@ var PhaseOrchestrator = class {
|
|
|
27175
27295
|
failed: failedTasks
|
|
27176
27296
|
});
|
|
27177
27297
|
if (failedTasks > 0 && this.opts.stopOnFailure) {
|
|
27178
|
-
this.
|
|
27179
|
-
|
|
27180
|
-
|
|
27181
|
-
|
|
27182
|
-
|
|
27183
|
-
this.
|
|
27184
|
-
|
|
27185
|
-
|
|
27186
|
-
|
|
27187
|
-
|
|
27188
|
-
this.ctx.onPhaseFail?.(phase, new Error(`${failedTasks} task(s) failed`));
|
|
27189
|
-
await this.keepWorktreeForReview(phase);
|
|
27190
|
-
} else {
|
|
27191
|
-
this.updatePhaseStatus(phase, "completed");
|
|
27192
|
-
phase.completedAt = Date.now();
|
|
27193
|
-
phase.actualDurationMs = Date.now() - (phase.startedAt ?? Date.now());
|
|
27194
|
-
this.runningPhases.delete(phase.id);
|
|
27195
|
-
this.graph.activePhaseIds = this.graph.activePhaseIds.filter((id) => id !== phase.id);
|
|
27196
|
-
this.graph.completedPhaseIds.push(phase.id);
|
|
27197
|
-
this.emit("phase.completed", {
|
|
27198
|
-
phaseId: phase.id,
|
|
27199
|
-
name: phase.name,
|
|
27200
|
-
durationMs: phase.actualDurationMs
|
|
27201
|
-
});
|
|
27202
|
-
this.ctx.onPhaseComplete?.(phase);
|
|
27203
|
-
await this.commitAndEnqueueMerge(phase);
|
|
27298
|
+
await this.failPhaseAfterTasks(phase, `${failedTasks} task(s) failed`);
|
|
27299
|
+
return;
|
|
27300
|
+
}
|
|
27301
|
+
const verdict = await this.runVerifyGate(phase);
|
|
27302
|
+
if (!verdict.ok) {
|
|
27303
|
+
await this.failPhaseAfterTasks(
|
|
27304
|
+
phase,
|
|
27305
|
+
`verification failed${verdict.output ? `: ${this.truncate(verdict.output)}` : ""}`
|
|
27306
|
+
);
|
|
27307
|
+
return;
|
|
27204
27308
|
}
|
|
27309
|
+
this.updatePhaseStatus(phase, "completed");
|
|
27310
|
+
phase.completedAt = Date.now();
|
|
27311
|
+
phase.actualDurationMs = Date.now() - (phase.startedAt ?? Date.now());
|
|
27312
|
+
this.runningPhases.delete(phase.id);
|
|
27313
|
+
this.graph.activePhaseIds = this.graph.activePhaseIds.filter((id) => id !== phase.id);
|
|
27314
|
+
this.graph.completedPhaseIds.push(phase.id);
|
|
27315
|
+
this.emit("phase.completed", {
|
|
27316
|
+
phaseId: phase.id,
|
|
27317
|
+
name: phase.name,
|
|
27318
|
+
durationMs: phase.actualDurationMs
|
|
27319
|
+
});
|
|
27320
|
+
this.ctx.onPhaseComplete?.(phase);
|
|
27321
|
+
await this.commitAndEnqueueMerge(phase);
|
|
27205
27322
|
} catch (error) {
|
|
27206
27323
|
this.updatePhaseStatus(phase, "failed");
|
|
27207
27324
|
phase.completedAt = Date.now();
|
|
@@ -27218,6 +27335,69 @@ var PhaseOrchestrator = class {
|
|
|
27218
27335
|
await this.keepWorktreeForReview(phase);
|
|
27219
27336
|
}
|
|
27220
27337
|
}
|
|
27338
|
+
// ─── Verification gate ──────────────────────────────────────────────────────
|
|
27339
|
+
/**
|
|
27340
|
+
* Run the verification gate for a phase whose tasks all succeeded. Verifies in
|
|
27341
|
+
* the phase's worktree; on failure, runs the repair pass and re-verifies, up to
|
|
27342
|
+
* `maxVerifyAttempts` repairs. Returns the final verdict. When no `verifyPhase`
|
|
27343
|
+
* callback is wired the gate is a no-op and always passes.
|
|
27344
|
+
*/
|
|
27345
|
+
async runVerifyGate(phase) {
|
|
27346
|
+
if (!this.ctx.verifyPhase) return { ok: true };
|
|
27347
|
+
const env = this.worktreeEnv(phase);
|
|
27348
|
+
for (let attempt = 0; attempt <= this.opts.maxVerifyAttempts; attempt++) {
|
|
27349
|
+
if (this.stopped) return { ok: false, output: "stopped before verification completed" };
|
|
27350
|
+
this.emit("phase.verifying", { phaseId: phase.id, name: phase.name, attempt });
|
|
27351
|
+
let verdict;
|
|
27352
|
+
try {
|
|
27353
|
+
verdict = await this.ctx.verifyPhase(phase, env);
|
|
27354
|
+
} catch (err) {
|
|
27355
|
+
verdict = { ok: false, output: err instanceof Error ? err.message : String(err) };
|
|
27356
|
+
}
|
|
27357
|
+
if (verdict.ok) return { ok: true };
|
|
27358
|
+
this.emit("phase.verifyFailed", {
|
|
27359
|
+
phaseId: phase.id,
|
|
27360
|
+
name: phase.name,
|
|
27361
|
+
attempt,
|
|
27362
|
+
error: verdict.output
|
|
27363
|
+
});
|
|
27364
|
+
if (attempt >= this.opts.maxVerifyAttempts || !this.ctx.repairPhase || this.stopped) {
|
|
27365
|
+
return { ok: false, output: verdict.output };
|
|
27366
|
+
}
|
|
27367
|
+
this.emit("phase.repairing", { phaseId: phase.id, name: phase.name, attempt: attempt + 1 });
|
|
27368
|
+
try {
|
|
27369
|
+
await this.ctx.repairPhase(
|
|
27370
|
+
phase,
|
|
27371
|
+
verdict.output ?? "verification failed",
|
|
27372
|
+
attempt + 1,
|
|
27373
|
+
env
|
|
27374
|
+
);
|
|
27375
|
+
} catch {
|
|
27376
|
+
}
|
|
27377
|
+
}
|
|
27378
|
+
return { ok: false };
|
|
27379
|
+
}
|
|
27380
|
+
/** Worktree env (cwd/branch) for a phase, or undefined if it runs on the shared tree. */
|
|
27381
|
+
worktreeEnv(phase) {
|
|
27382
|
+
const handle = this.phaseWorktrees.get(phase.id);
|
|
27383
|
+
return handle ? { cwd: handle.dir, branch: handle.branch } : void 0;
|
|
27384
|
+
}
|
|
27385
|
+
/** Shared failure bookkeeping for a phase whose tasks ran but the phase failed. */
|
|
27386
|
+
async failPhaseAfterTasks(phase, error) {
|
|
27387
|
+
this.updatePhaseStatus(phase, "failed");
|
|
27388
|
+
phase.completedAt = Date.now();
|
|
27389
|
+
phase.actualDurationMs = Date.now() - (phase.startedAt ?? Date.now());
|
|
27390
|
+
this.runningPhases.delete(phase.id);
|
|
27391
|
+
this.graph.activePhaseIds = this.graph.activePhaseIds.filter((id) => id !== phase.id);
|
|
27392
|
+
this.emit("phase.failed", { phaseId: phase.id, name: phase.name, error });
|
|
27393
|
+
this.ctx.onPhaseFail?.(phase, new Error(error));
|
|
27394
|
+
await this.keepWorktreeForReview(phase);
|
|
27395
|
+
}
|
|
27396
|
+
/** Trim long verifier output so it fits cleanly in an event/error message. */
|
|
27397
|
+
truncate(text, max = 500) {
|
|
27398
|
+
const t2 = text.trim();
|
|
27399
|
+
return t2.length <= max ? t2 : `${t2.slice(0, max)}\u2026 (+${t2.length - max} chars)`;
|
|
27400
|
+
}
|
|
27221
27401
|
// ─── Worktree integration ───────────────────────────────────────────────────
|
|
27222
27402
|
/**
|
|
27223
27403
|
* Commit the phase's worktree changes, then enqueue the merge back into the
|
|
@@ -27240,11 +27420,26 @@ var PhaseOrchestrator = class {
|
|
|
27240
27420
|
})();
|
|
27241
27421
|
this.phaseMergePromise.set(phase.id, merged);
|
|
27242
27422
|
}
|
|
27243
|
-
/**
|
|
27423
|
+
/**
|
|
27424
|
+
* Squash-merge one phase. When a `resolveConflict` callback is wired, a merge
|
|
27425
|
+
* conflict is handed to it (a resolver subagent) before giving up; only if
|
|
27426
|
+
* that fails does the worktree fall to needs-review and the run continues.
|
|
27427
|
+
*/
|
|
27244
27428
|
async mergeOne(phase, handle) {
|
|
27245
27429
|
if (!this.worktrees) return;
|
|
27246
27430
|
try {
|
|
27247
|
-
const
|
|
27431
|
+
const resolve10 = this.ctx.resolveConflict ? async (info) => {
|
|
27432
|
+
this.emit("phase.conflictResolving", {
|
|
27433
|
+
phaseId: phase.id,
|
|
27434
|
+
name: phase.name,
|
|
27435
|
+
files: info.conflictFiles
|
|
27436
|
+
});
|
|
27437
|
+
return this.ctx.resolveConflict(phase, info);
|
|
27438
|
+
} : void 0;
|
|
27439
|
+
const result = await this.worktrees.merge(handle, { squash: true, resolve: resolve10 });
|
|
27440
|
+
if (result.resolved) {
|
|
27441
|
+
this.emit("phase.conflictResolved", { phaseId: phase.id, name: phase.name });
|
|
27442
|
+
}
|
|
27248
27443
|
await this.worktrees.release(handle, { keep: !result.ok });
|
|
27249
27444
|
} catch (err) {
|
|
27250
27445
|
this.emit("phase.failed", {
|
|
@@ -27308,7 +27503,11 @@ var PhaseOrchestrator = class {
|
|
|
27308
27503
|
const currentRetries = this.taskRetryCounts.get(taskKey) ?? 0;
|
|
27309
27504
|
if (currentRetries < this.opts.maxRetries) {
|
|
27310
27505
|
this.taskRetryCounts.set(taskKey, currentRetries + 1);
|
|
27311
|
-
tracker.updateNodeStatus(
|
|
27506
|
+
tracker.updateNodeStatus(
|
|
27507
|
+
task.id,
|
|
27508
|
+
"pending",
|
|
27509
|
+
`Retry ${currentRetries + 1}/${this.opts.maxRetries}`
|
|
27510
|
+
);
|
|
27312
27511
|
this.emit("phase.taskRetrying", {
|
|
27313
27512
|
phaseId: phase.id,
|
|
27314
27513
|
taskId: task.id,
|
|
@@ -27317,7 +27516,11 @@ var PhaseOrchestrator = class {
|
|
|
27317
27516
|
maxRetries: this.opts.maxRetries
|
|
27318
27517
|
});
|
|
27319
27518
|
} else {
|
|
27320
|
-
tracker.updateNodeStatus(
|
|
27519
|
+
tracker.updateNodeStatus(
|
|
27520
|
+
task.id,
|
|
27521
|
+
"failed",
|
|
27522
|
+
error instanceof Error ? error.message : String(error)
|
|
27523
|
+
);
|
|
27321
27524
|
this.emit("phase.taskFailed", {
|
|
27322
27525
|
phaseId: phase.id,
|
|
27323
27526
|
taskId: task.id,
|
|
@@ -28268,6 +28471,10 @@ var WorktreeManager = class {
|
|
|
28268
28471
|
${merged.stderr}`);
|
|
28269
28472
|
const fromIndex = await this.unmergedFiles();
|
|
28270
28473
|
const conflictFiles = [.../* @__PURE__ */ new Set([...fromOutput, ...fromIndex])];
|
|
28474
|
+
if (opts.resolve) {
|
|
28475
|
+
const finalized = await this.tryResolveConflict(handle, conflictFiles, opts);
|
|
28476
|
+
if (finalized) return finalized;
|
|
28477
|
+
}
|
|
28271
28478
|
await this.runGit(["reset", "--hard", "HEAD"], this.projectRoot);
|
|
28272
28479
|
handle.conflictFiles = conflictFiles;
|
|
28273
28480
|
this.setStatus(handle, "needs-review", { lastError: merged.stderr });
|
|
@@ -28298,6 +28505,52 @@ ${merged.stderr}`);
|
|
|
28298
28505
|
});
|
|
28299
28506
|
return { ok: true };
|
|
28300
28507
|
}
|
|
28508
|
+
/**
|
|
28509
|
+
* Run the caller-supplied resolver against a conflicted squash-merge, then
|
|
28510
|
+
* commit if it cleared every marker. Returns a successful `MergeResult` on a
|
|
28511
|
+
* clean resolution, or `null` to signal the caller should fall back to the
|
|
28512
|
+
* abort path. Never leaves the base tree committed-but-dirty: a partial or
|
|
28513
|
+
* failed resolution returns `null` and the caller hard-resets.
|
|
28514
|
+
*/
|
|
28515
|
+
async tryResolveConflict(handle, conflictFiles, opts) {
|
|
28516
|
+
let resolved = false;
|
|
28517
|
+
try {
|
|
28518
|
+
resolved = await opts.resolve({ conflictFiles, cwd: this.projectRoot });
|
|
28519
|
+
} catch {
|
|
28520
|
+
resolved = false;
|
|
28521
|
+
}
|
|
28522
|
+
if (!resolved) return null;
|
|
28523
|
+
await this.runGit(["add", "-A"], this.projectRoot);
|
|
28524
|
+
if (await this.hasConflictMarkers()) return null;
|
|
28525
|
+
const idArgs = await this.identityArgs(this.projectRoot);
|
|
28526
|
+
const msg = opts.message ?? `merge ${handle.branch} (squash, conflict resolved)`;
|
|
28527
|
+
const commit = await this.runGit([...idArgs, "commit", "-m", msg], this.projectRoot);
|
|
28528
|
+
if (commit.code !== 0 && !/nothing to commit/i.test(commit.stdout + commit.stderr)) {
|
|
28529
|
+
return null;
|
|
28530
|
+
}
|
|
28531
|
+
handle.conflictFiles = conflictFiles;
|
|
28532
|
+
this.setStatus(handle, "merged");
|
|
28533
|
+
this.emit("worktree.merged", {
|
|
28534
|
+
handleId: handle.id,
|
|
28535
|
+
ownerId: handle.ownerId,
|
|
28536
|
+
branch: handle.branch,
|
|
28537
|
+
baseBranch: handle.baseBranch,
|
|
28538
|
+
squash: true
|
|
28539
|
+
});
|
|
28540
|
+
return { ok: true, resolved: true, conflictFiles };
|
|
28541
|
+
}
|
|
28542
|
+
/**
|
|
28543
|
+
* True when staged content still carries conflict markers. `git diff --cached
|
|
28544
|
+
* --check` exits nonzero and prints a "leftover conflict marker" line for each
|
|
28545
|
+
* survivor; whitespace-only errors (also flagged by --check) are ignored so a
|
|
28546
|
+
* clean resolution with unrelated whitespace is not rejected.
|
|
28547
|
+
*/
|
|
28548
|
+
async hasConflictMarkers() {
|
|
28549
|
+
const check = await this.runGit(["diff", "--cached", "--check"], this.projectRoot);
|
|
28550
|
+
if (check.code === 0) return false;
|
|
28551
|
+
return /conflict marker/i.test(`${check.stdout}
|
|
28552
|
+
${check.stderr}`);
|
|
28553
|
+
}
|
|
28301
28554
|
/**
|
|
28302
28555
|
* Remove the worktree + branch. Conflicted/failed handles (or `keep:true`)
|
|
28303
28556
|
* are left on disk for inspection.
|
|
@@ -29700,6 +29953,6 @@ ${formatPlan(updated)}`
|
|
|
29700
29953
|
};
|
|
29701
29954
|
}
|
|
29702
29955
|
|
|
29703
|
-
export { ACP_AGENTS, AGENTS_BY_PHASE, AGENT_CATALOG, AISpecBuilder, ALL_AGENT_DEFINITIONS, ALL_FLEET_AGENTS, ALL_SYNC_CATEGORIES, AUDIT_LOG_AGENT, Agent, AgentError, AnnotationsStore, AutoApprovePermissionPolicy, AutoCompactionMiddleware, AutoExecutor, AutoPhasePlanner, AutoPhaseRunner, AutonomousRunner, BUG_HUNTER_AGENT, BudgetExceededError, CONTEXT_WINDOW_MODES, CORE_RECONSTRUCT_EVENTS, CheckpointManager, CloudSync, CollaborationBus, ConfigError, ConfigMigrationError, Container, Context, ConversationState, DEFAULT_AUTONOMY_CONFIG, DEFAULT_CONFIG_MIGRATIONS, DEFAULT_CONTEXT_CONFIG, DEFAULT_CONTEXT_WINDOW_MODE_ID, DEFAULT_DIRECTOR_PREAMBLE, DEFAULT_DISPATCH_ROLE, DEFAULT_MAX_ITERATIONS, DEFAULT_MODES, DEFAULT_RECOVERY_STRATEGIES, DEFAULT_SESSION_LOGGING_CONFIG, DEFAULT_SPEC_TEMPLATE, DEFAULT_SUBAGENT_BASELINE, DEFAULT_TOOLS_CONFIG, DefaultAttachmentStore, DefaultConfigLoader, DefaultConfigStore, DefaultErrorHandler, DefaultHealthRegistry, DefaultLogger, DefaultMemoryStore, DefaultModeStore, DefaultModelsRegistry, DefaultMultiAgentCoordinator, DefaultPathResolver, DefaultPermissionPolicy, DefaultPluginAPI, DefaultPromptStore, DefaultProviderRunner, DefaultRetryPolicy, DefaultSecretScrubber, DefaultSecretVault, DefaultSessionReader, DefaultSessionRewinder, DefaultSessionStore, DefaultSkillLoader, DefaultSystemPromptBuilder, DefaultTaskStore, DefaultTokenCounter, Director, DirectorStateCheckpoint, DoneConditionChecker, ERROR_CODES, EternalAutonomyEngine, EventBus, ExtensionRegistry, FLEET_ROSTER, FLEET_ROSTER_BUDGETS, FLEET_ROSTER_WITHACP, FleetBus, FleetCostCapError, FleetManager, FleetSpawnBudgetError, FleetUsageAggregator, FsError, GitignoreUpdater, HybridCompactor, InMemoryAgentBridge, InMemoryBridgeTransport, InMemoryMetricsSink, InputBuilder, IntelligentCompactor, KERNEL_API_VERSION, LAYER_1_IDENTITY, LLMSelector, MAX_JOURNAL_ENTRIES, NULL_FLEET_BUS, NoopMetricsSink, NoopTracer, OTelTracer, PROMETHEUS_CONTENT_TYPE, ParallelEternalEngine, PhaseGraphBuilder, PhaseOrchestrator, PhaseStore, Pipeline, PluginError, ProviderError, ProviderRegistry, QueueStore, REFACTOR_PLANNER_AGENT, RecoveryLock, ReplayLogStore, ReplayProviderRunner, ReportGenerator, RunController, SECURITY_SCANNER_AGENT, SPEC_TEMPLATES, STANDARD_AUDIT_EVENTS, ScopedEventBus, SddParallelRun, SddTaskDecomposer, SecurityScanner, SecurityScannerOrchestrator, SelectiveCompactor, SessionAnalyzer, SessionError, SessionRecovery, SkillGenerator, SkillInstaller, SkillManifestStore, SlashCommandRegistry, SpecDrivenDev, SpecParser, SpecStore, SpecVersioning, SubagentBudget, TOKENS, TaskFlow, TaskGenerator, TaskGraphStore, TaskTracker, TechStackDetector, ToolAuditLog, ToolError, ToolExecutor, ToolRegistry, WorktreeManager, WrongStackError, addPlanItem, allServers, analyzeCriticalPath, appendJournal, applyRosterBudget, asBlocks, asText, assertSafePath, atomicWrite, attachAutoExtend, attachPlanCheckpoint, attachTodosCheckpoint, awsServer, blockServer, braveSearchServer, buildBtwBlock, buildChildEnv, buildGoalPreamble, buildOtlpMetricsRequest, buildOtlpTracesRequest, buildRecoveryStrategies, classifyFamily, clearPlan, collabInjectMiddleware, collabPauseMiddleware, color, compileGlob, compileUserRegex, completePartialObject, composeDirectorPrompt, composeSubagentPrompt, computeTaskProgress, consumeBtwNotes, context7Server, contextManagerTool, createAutoExecutor, createAutoPhaseFromTaskGraph, createContextManagerTool, createDefaultPipelines, createDelegateTool, createGitPlugin, createMcpControlTool, createMessage, createObservabilityPlugin, createPlanPlugin, createPromptsPlugin, createSecurityPlugin, createSecuritySlashCommand, createSessionEventBridge, createSkillsPlugin, createSyncPlugin, createToolOutputSerializer, decryptConfigSecrets, defaultGitignoreUpdater, defaultOrchestrator, defaultReportGenerator, defaultSecurityScanner, defaultSkillGenerator, defaultTechStackDetector, deriveTodosFromPlanItem, detectNewlineStyle, dispatchAgent, downloadGitHubTarball, emptyGoal, emptyPlan, encryptConfigSecrets, ensureDir, estimateRequestTokens, estimateRequestTokensCalibrated, estimateTextTokens, estimateToolDefTokens, estimateToolInputTokens, estimateToolResultTokens, everArtServer, expandGlob, extractRunEnv, filesystemServer, findCriticalPath, formatContextWindowModeList, formatGoal, formatPlan, formatPlanTemplates, formatTodosList, getAgentDefinition, getCalibrationState, getContextWindowMode, getPlanTemplate, getTemplate, githubServer, goalFilePath, googleMapsServer, hashRequest, isAgentError, isConfigError, isContextWindowModeId, isFsError, isImageBlock, isPluginError, isSessionError, isTextBlock, isThinkingBlock, isToolError, isToolResultBlock, isToolUseBlock, isWrongStackError, listContextWindowModes, listPlanTemplates, listTemplates, loadDirectorState, loadGoal, loadPlan, loadPlugins, loadProjectModes, loadTodosCheckpoint, loadUserModes, makeAgentSubagentRunner, makeAskTool, makeAssignTool, makeAutonomyPromptContributor, makeAwaitTasksTool, makeCollabDebugTool, makeContinueToNextIterationTool, makeDirectorSessionFactory, makeFleetEmitTool, makeFleetHealthTool, makeFleetSessionTool, makeFleetStatusTool, makeFleetUsageTool, makeLLMClassifier, makeRollUpTool, makeSpawnTool, makeTerminateTool, matchAny, matchGlob, mergeModelsPayload, migratePlaintextSecrets, miniMaxVisionServer, normalizeToLf, parseContinueDirective, parseSkillRef, pendingBtwCount, projectHash, recordActualUsage, removePlanItem, renderProgress, renderPrometheus, renderSpecAnalysis, renderTaskGraph, renderTaskList, repairToolUseAdjacency, resetCalibration, resolveAuditLevel, resolveContextWindowPolicy, resolveSessionLoggingConfig, resolveWstackPaths, rewriteConfigEncrypted, rosterSummaryFromConfigs, runConfigMigrations, runProviderWithRetry, safeParse, safeStringify, sanitizeJsonString, saveGoal, savePlan, saveTodosCheckpoint, scoreAgents, securitySlashCommand, sentinelServer, setBtwNote, setPlanItemStatus, slackServer, stableStringify, startMetricsServer, startOtlpMetricsExporter, startOtlpTraceExporter, stripAnsi, summarizeUsage, templateToMarkdown, toStyle, toWrongStackError, topologicalSort, unifiedDiff, unloadPlugins, validateAgainstSchema, wireMetricsToEvents, wrapAsState, zaiVisionServer };
|
|
29956
|
+
export { ACP_AGENTS, AGENTS_BY_PHASE, AGENT_CATALOG, AISpecBuilder, ALL_AGENT_DEFINITIONS, ALL_FLEET_AGENTS, ALL_SYNC_CATEGORIES, AUDIT_LOG_AGENT, Agent, AgentError, AnnotationsStore, AutoApprovePermissionPolicy, AutoCompactionMiddleware, AutoExecutor, AutoPhasePlanner, AutoPhaseRunner, AutonomousRunner, BUG_HUNTER_AGENT, BudgetExceededError, CONTEXT_WINDOW_MODES, CORE_RECONSTRUCT_EVENTS, CheckpointManager, CloudSync, CollaborationBus, ConfigError, ConfigMigrationError, Container, Context, ConversationState, DEFAULT_AUTONOMY_CONFIG, DEFAULT_CONFIG_MIGRATIONS, DEFAULT_CONTEXT_CONFIG, DEFAULT_CONTEXT_WINDOW_MODE_ID, DEFAULT_DIRECTOR_PREAMBLE, DEFAULT_DISPATCH_ROLE, DEFAULT_MAX_ITERATIONS, DEFAULT_MODES, DEFAULT_RECOVERY_STRATEGIES, DEFAULT_SESSION_LOGGING_CONFIG, DEFAULT_SPEC_TEMPLATE, DEFAULT_SUBAGENT_BASELINE, DEFAULT_TOOLS_CONFIG, DefaultAttachmentStore, DefaultConfigLoader, DefaultConfigStore, DefaultErrorHandler, DefaultHealthRegistry, DefaultLogger, DefaultMemoryStore, DefaultModeStore, DefaultModelsRegistry, DefaultMultiAgentCoordinator, DefaultPathResolver, DefaultPermissionPolicy, DefaultPluginAPI, DefaultPromptStore, DefaultProviderRunner, DefaultRetryPolicy, DefaultSecretScrubber, DefaultSecretVault, DefaultSessionReader, DefaultSessionRewinder, DefaultSessionStore, DefaultSkillLoader, DefaultSystemPromptBuilder, DefaultTaskStore, DefaultTokenCounter, Director, DirectorStateCheckpoint, DoneConditionChecker, ERROR_CODES, EternalAutonomyEngine, EventBus, ExtensionRegistry, FLEET_ROSTER, FLEET_ROSTER_BUDGETS, FLEET_ROSTER_WITHACP, FleetBus, FleetCostCapError, FleetManager, FleetSpawnBudgetError, FleetUsageAggregator, FsError, GitignoreUpdater, HybridCompactor, InMemoryAgentBridge, InMemoryBridgeTransport, InMemoryMetricsSink, InputBuilder, IntelligentCompactor, KERNEL_API_VERSION, LAYER_1_IDENTITY, LLMSelector, MATRIX_PHASE_KEYS, MAX_JOURNAL_ENTRIES, NULL_FLEET_BUS, NoopMetricsSink, NoopTracer, OTelTracer, PROMETHEUS_CONTENT_TYPE, ParallelEternalEngine, PhaseGraphBuilder, PhaseOrchestrator, PhaseStore, Pipeline, PluginError, ProviderError, ProviderRegistry, QueueStore, REFACTOR_PLANNER_AGENT, RecoveryLock, ReplayLogStore, ReplayProviderRunner, ReportGenerator, RunController, SECURITY_SCANNER_AGENT, SPEC_TEMPLATES, STANDARD_AUDIT_EVENTS, ScopedEventBus, SddParallelRun, SddTaskDecomposer, SecurityScanner, SecurityScannerOrchestrator, SelectiveCompactor, SessionAnalyzer, SessionError, SessionRecovery, SkillGenerator, SkillInstaller, SkillManifestStore, SlashCommandRegistry, SpecDrivenDev, SpecParser, SpecStore, SpecVersioning, SubagentBudget, TOKENS, TaskFlow, TaskGenerator, TaskGraphStore, TaskTracker, TechStackDetector, ToolAuditLog, ToolError, ToolExecutor, ToolRegistry, WorktreeManager, WrongStackError, addPlanItem, allServers, analyzeCriticalPath, appendJournal, applyRosterBudget, asBlocks, asText, assertSafePath, atomicWrite, attachAutoExtend, attachPlanCheckpoint, attachTodosCheckpoint, awsServer, blockServer, braveSearchServer, buildBtwBlock, buildChildEnv, buildGoalPreamble, buildOtlpMetricsRequest, buildOtlpTracesRequest, buildRecoveryStrategies, classifyFamily, clearPlan, collabInjectMiddleware, collabPauseMiddleware, color, compileGlob, compileUserRegex, completePartialObject, composeDirectorPrompt, composeSubagentPrompt, computeTaskProgress, consumeBtwNotes, context7Server, contextManagerTool, createAutoExecutor, createAutoPhaseFromTaskGraph, createContextManagerTool, createDefaultPipelines, createDelegateTool, createGitPlugin, createMcpControlTool, createMessage, createObservabilityPlugin, createPlanPlugin, createPromptsPlugin, createSecurityPlugin, createSecuritySlashCommand, createSessionEventBridge, createSkillsPlugin, createSyncPlugin, createToolOutputSerializer, decryptConfigSecrets, defaultGitignoreUpdater, defaultOrchestrator, defaultReportGenerator, defaultSecurityScanner, defaultSkillGenerator, defaultTechStackDetector, deriveTodosFromPlanItem, detectNewlineStyle, dispatchAgent, downloadGitHubTarball, emptyGoal, emptyPlan, encryptConfigSecrets, ensureDir, estimateRequestTokens, estimateRequestTokensCalibrated, estimateTextTokens, estimateToolDefTokens, estimateToolInputTokens, estimateToolResultTokens, everArtServer, expandGlob, extractRunEnv, filesystemServer, findCriticalPath, formatContextWindowModeList, formatGoal, formatPlan, formatPlanTemplates, formatTodosList, getAgentDefinition, getCalibrationState, getContextWindowMode, getPlanTemplate, getTemplate, getTermSize, githubServer, goalFilePath, googleMapsServer, hashRequest, isAgentError, isConfigError, isContextWindowModeId, isFsError, isImageBlock, isInteractive, isPluginError, isSessionError, isStdinTTY, isStdoutTTY, isTextBlock, isThinkingBlock, isToolError, isToolResultBlock, isToolUseBlock, isValidMatrixKey, isWrongStackError, listContextWindowModes, listPlanTemplates, listTemplates, loadDirectorState, loadGoal, loadPlan, loadPlugins, loadProjectModes, loadTodosCheckpoint, loadUserModes, makeAgentSubagentRunner, makeAskTool, makeAssignTool, makeAutonomyPromptContributor, makeAwaitTasksTool, makeCollabDebugTool, makeContinueToNextIterationTool, makeDirectorSessionFactory, makeFleetEmitTool, makeFleetHealthTool, makeFleetSessionTool, makeFleetStatusTool, makeFleetUsageTool, makeLLMClassifier, makeRollUpTool, makeSpawnTool, makeTerminateTool, matchAny, matchGlob, matrixKeyKind, mergeModelsPayload, migratePlaintextSecrets, miniMaxVisionServer, normalizeToLf, onResize, parseContinueDirective, parseSkillRef, pendingBtwCount, phaseForRole, projectHash, recordActualUsage, removePlanItem, renderProgress, renderPrometheus, renderSpecAnalysis, renderTaskGraph, renderTaskList, repairToolUseAdjacency, resetCalibration, resolveAuditLevel, resolveContextWindowPolicy, resolveModelMatrix, resolveSessionLoggingConfig, resolveWstackPaths, rewriteConfigEncrypted, rosterSummaryFromConfigs, runConfigMigrations, runProviderWithRetry, safeParse, safeStringify, sanitizeJsonString, saveGoal, savePlan, saveTodosCheckpoint, scoreAgents, securitySlashCommand, sentinelServer, setBtwNote, setPlanItemStatus, setRawMode, slackServer, stableStringify, startMetricsServer, startOtlpMetricsExporter, startOtlpTraceExporter, stripAnsi, summarizeUsage, templateToMarkdown, toStyle, toWrongStackError, topologicalSort, unifiedDiff, unloadPlugins, validateAgainstSchema, wireMetricsToEvents, wrapAsState, writeErr, writeOut, zaiVisionServer };
|
|
29704
29957
|
//# sourceMappingURL=index.js.map
|
|
29705
29958
|
//# sourceMappingURL=index.js.map
|