@wrongstack/core 0.9.7 → 0.9.20
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/{agent-bridge-DMVOX0cF.d.ts → agent-bridge-Dti3KXGk.d.ts} +1 -1
- package/dist/{agent-subagent-runner-DaF_EgRG.d.ts → agent-subagent-runner-U-rs7kk7.d.ts} +3 -4
- package/dist/compactor-D7X96RLZ.d.ts +41 -0
- package/dist/{config-SkMIDN9L.d.ts → config-CLXMDOSs.d.ts} +5 -1
- package/dist/{context-BRNbHmRM.d.ts → context-zkZeILpr.d.ts} +46 -0
- package/dist/coordination/index.d.ts +14 -13
- package/dist/coordination/index.js +1263 -123
- package/dist/coordination/index.js.map +1 -1
- package/dist/defaults/index.d.ts +22 -21
- package/dist/defaults/index.js +1477 -152
- package/dist/defaults/index.js.map +1 -1
- package/dist/{events-CiG9qUM_.d.ts → events-DH-9r-_C.d.ts} +42 -1
- package/dist/execution/index.d.ts +42 -30
- package/dist/execution/index.js +359 -112
- package/dist/execution/index.js.map +1 -1
- package/dist/extension/index.d.ts +7 -6
- package/dist/extension/index.js.map +1 -1
- package/dist/{index-CP8638Wm.d.ts → index-BIHJ4uII.d.ts} +12 -8
- package/dist/{index-Bsha5K4D.d.ts → index-CFO9QmJo.d.ts} +9 -8
- package/dist/index.d.ts +319 -37
- package/dist/index.js +2571 -324
- package/dist/index.js.map +1 -1
- package/dist/infrastructure/index.d.ts +6 -6
- package/dist/infrastructure/index.js +36 -0
- package/dist/infrastructure/index.js.map +1 -1
- package/dist/kernel/index.d.ts +10 -9
- package/dist/kernel/index.js.map +1 -1
- package/dist/{mcp-servers-BouUWYW6.d.ts → mcp-servers-DkESgh0G.d.ts} +25 -3
- package/dist/models/index.d.ts +2 -2
- package/dist/models/index.js +1 -1
- package/dist/models/index.js.map +1 -1
- package/dist/{multi-agent-3ZnTB1aT.d.ts → multi-agent-DNp6lAzg.d.ts} +35 -23
- package/dist/{multi-agent-coordinator-DTXF2aAl.d.ts → multi-agent-coordinator-CAhsegPz.d.ts} +20 -2
- package/dist/{null-fleet-bus-Chrc_3Pp.d.ts → null-fleet-bus-Dnl19vmf.d.ts} +496 -15
- package/dist/observability/index.d.ts +2 -2
- package/dist/{path-resolver-TcJfc29Y.d.ts → path-resolver-CHiBL0DD.d.ts} +2 -2
- package/dist/{secret-scrubber-DttNiGYA.d.ts → permission-H35s9Evv.d.ts} +2 -7
- package/dist/{permission-policy-BpCGYBud.d.ts → permission-policy-CT-nRmTn.d.ts} +9 -2
- package/dist/{plan-templates-envSmNlZ.d.ts → plan-templates-Bs8iRwi6.d.ts} +28 -4
- package/dist/{provider-runner-BEpikbbN.d.ts → provider-runner-BZdDrWrS.d.ts} +3 -3
- package/dist/{retry-policy-BYkq0ugs.d.ts → retry-policy-J9N_PM40.d.ts} +1 -1
- package/dist/sdd/index.d.ts +10 -10
- package/dist/sdd/index.js +225 -68
- package/dist/sdd/index.js.map +1 -1
- package/dist/secret-scrubber-3MHDDAtm.d.ts +6 -0
- package/dist/{secret-scrubber-QSeI0ADi.d.ts → secret-scrubber-7rSC_emZ.d.ts} +1 -1
- package/dist/security/index.d.ts +5 -4
- package/dist/security/index.js +37 -6
- package/dist/security/index.js.map +1 -1
- package/dist/{selector-pox8abg0.d.ts → selector-CFTh3Z6p.d.ts} +1 -1
- package/dist/{session-reader-CSWcb5Ga.d.ts → session-reader-C7JJlxOw.d.ts} +2 -2
- package/dist/skills/index.d.ts +1 -1
- package/dist/skills/index.js +1 -1
- package/dist/skills/index.js.map +1 -1
- package/dist/storage/index.d.ts +391 -6
- package/dist/storage/index.js +760 -40
- package/dist/storage/index.js.map +1 -1
- package/dist/{system-prompt-Bs-Wliab.d.ts → system-prompt-CneIxVip.d.ts} +1 -1
- package/dist/{tool-executor-CsktM3h9.d.ts → tool-executor-flTuxsqO.d.ts} +9 -4
- package/dist/types/index.d.ts +16 -15
- package/dist/types/index.js +60 -0
- package/dist/types/index.js.map +1 -1
- package/dist/utils/index.d.ts +25 -2
- package/dist/utils/index.js +90 -1
- package/dist/utils/index.js.map +1 -1
- package/package.json +1 -1
- package/dist/compactor-DVTKL7XD.d.ts +0 -23
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { randomUUID, randomBytes } from 'crypto';
|
|
2
|
-
import * as
|
|
2
|
+
import * as fsp5 from 'fs/promises';
|
|
3
3
|
import * as path4 from 'path';
|
|
4
4
|
import { EventEmitter } from 'events';
|
|
5
5
|
|
|
@@ -11,16 +11,16 @@ var __require = /* @__PURE__ */ ((x) => typeof require !== "undefined" ? require
|
|
|
11
11
|
});
|
|
12
12
|
async function atomicWrite(targetPath, content, opts = {}) {
|
|
13
13
|
const dir = path4.dirname(targetPath);
|
|
14
|
-
await
|
|
14
|
+
await fsp5.mkdir(dir, { recursive: true });
|
|
15
15
|
const tmp = path4.join(dir, `.${path4.basename(targetPath)}.${randomBytes(6).toString("hex")}.tmp`);
|
|
16
16
|
try {
|
|
17
17
|
if (typeof content === "string") {
|
|
18
|
-
await
|
|
18
|
+
await fsp5.writeFile(tmp, content, { flag: "wx", encoding: opts.encoding ?? "utf8" });
|
|
19
19
|
} else {
|
|
20
|
-
await
|
|
20
|
+
await fsp5.writeFile(tmp, content, { flag: "wx" });
|
|
21
21
|
}
|
|
22
22
|
try {
|
|
23
|
-
const fh = await
|
|
23
|
+
const fh = await fsp5.open(tmp, "r+");
|
|
24
24
|
try {
|
|
25
25
|
await fh.sync();
|
|
26
26
|
} finally {
|
|
@@ -30,37 +30,37 @@ async function atomicWrite(targetPath, content, opts = {}) {
|
|
|
30
30
|
}
|
|
31
31
|
let mode;
|
|
32
32
|
try {
|
|
33
|
-
const stat3 = await
|
|
33
|
+
const stat3 = await fsp5.stat(targetPath);
|
|
34
34
|
mode = stat3.mode & 511;
|
|
35
35
|
} catch {
|
|
36
36
|
mode = opts.mode;
|
|
37
37
|
}
|
|
38
38
|
if (mode !== void 0) {
|
|
39
|
-
await
|
|
39
|
+
await fsp5.chmod(tmp, mode);
|
|
40
40
|
}
|
|
41
41
|
await renameWithRetry(tmp, targetPath);
|
|
42
42
|
} catch (err) {
|
|
43
43
|
try {
|
|
44
|
-
await
|
|
44
|
+
await fsp5.unlink(tmp);
|
|
45
45
|
} catch {
|
|
46
46
|
}
|
|
47
47
|
throw err;
|
|
48
48
|
}
|
|
49
49
|
}
|
|
50
50
|
async function ensureDir(dir) {
|
|
51
|
-
await
|
|
51
|
+
await fsp5.mkdir(dir, { recursive: true });
|
|
52
52
|
}
|
|
53
53
|
var TRANSIENT_RENAME_CODES = /* @__PURE__ */ new Set(["EPERM", "EBUSY", "EACCES", "ENOTEMPTY"]);
|
|
54
54
|
async function renameWithRetry(from, to) {
|
|
55
55
|
if (process.platform !== "win32") {
|
|
56
|
-
await
|
|
56
|
+
await fsp5.rename(from, to);
|
|
57
57
|
return;
|
|
58
58
|
}
|
|
59
59
|
const delays = [10, 25, 60, 120, 250];
|
|
60
60
|
let lastErr;
|
|
61
61
|
for (let i = 0; i <= delays.length; i++) {
|
|
62
62
|
try {
|
|
63
|
-
await
|
|
63
|
+
await fsp5.rename(from, to);
|
|
64
64
|
return;
|
|
65
65
|
} catch (err) {
|
|
66
66
|
lastErr = err;
|
|
@@ -76,7 +76,7 @@ async function renameWithRetry(from, to) {
|
|
|
76
76
|
async function acquireDirectorStateLock(lockPath, processId = process.pid) {
|
|
77
77
|
let existing;
|
|
78
78
|
try {
|
|
79
|
-
existing = await
|
|
79
|
+
existing = await fsp5.readFile(lockPath, "utf8");
|
|
80
80
|
} catch {
|
|
81
81
|
}
|
|
82
82
|
if (existing) {
|
|
@@ -100,7 +100,7 @@ async function acquireDirectorStateLock(lockPath, processId = process.pid) {
|
|
|
100
100
|
}
|
|
101
101
|
async function releaseDirectorStateLock(lockPath) {
|
|
102
102
|
try {
|
|
103
|
-
await
|
|
103
|
+
await fsp5.unlink(lockPath);
|
|
104
104
|
} catch {
|
|
105
105
|
}
|
|
106
106
|
}
|
|
@@ -410,7 +410,11 @@ Working rules:
|
|
|
410
410
|
thrashing, terminate it rather than letting cost climb silently.
|
|
411
411
|
6. Never claim a subagent's work as your own without verifying it. If a
|
|
412
412
|
result looks wrong, ask_subagent for clarification before passing it
|
|
413
|
-
to the user
|
|
413
|
+
to the user.
|
|
414
|
+
7. Wind down when satisfied. When the results are good enough, call
|
|
415
|
+
work_complete \u2014 no new subagents will spawn and queued tasks complete
|
|
416
|
+
as aborted. Running subagents finish naturally. Call terminate_subagent
|
|
417
|
+
only for ones you need to stop immediately.`;
|
|
414
418
|
var DEFAULT_SUBAGENT_BASELINE = `You are a subagent operating under a Director. You were spawned to handle
|
|
415
419
|
a specific slice of a larger plan \u2014 do that slice well and report back.
|
|
416
420
|
|
|
@@ -555,14 +559,12 @@ var FleetBus = class {
|
|
|
555
559
|
};
|
|
556
560
|
var FleetUsageAggregator = class {
|
|
557
561
|
constructor(bus, priceLookup, metaLookup) {
|
|
558
|
-
this.bus = bus;
|
|
559
562
|
this.priceLookup = priceLookup;
|
|
560
563
|
this.metaLookup = metaLookup;
|
|
561
564
|
bus.filter("provider.response", (e) => this.onProviderResponse(e));
|
|
562
565
|
bus.filter("tool.executed", (e) => this.onToolExecuted(e));
|
|
563
566
|
bus.filter("iteration.started", (e) => this.onIterationStarted(e));
|
|
564
567
|
}
|
|
565
|
-
bus;
|
|
566
568
|
priceLookup;
|
|
567
569
|
metaLookup;
|
|
568
570
|
perSubagent = /* @__PURE__ */ new Map();
|
|
@@ -631,6 +633,107 @@ var FleetUsageAggregator = class {
|
|
|
631
633
|
}
|
|
632
634
|
};
|
|
633
635
|
|
|
636
|
+
// src/coordination/subagent-nicknames.ts
|
|
637
|
+
var NICKNAME_POOL = {
|
|
638
|
+
// Physics & fundamental sciences
|
|
639
|
+
"einstein": { name: "Einstein", domain: "physics" },
|
|
640
|
+
"newton": { name: "Newton", domain: "physics" },
|
|
641
|
+
"feynman": { name: "Feynman", domain: "physics" },
|
|
642
|
+
"dirac": { name: "Dirac", domain: "physics" },
|
|
643
|
+
"bohr": { name: "Bohr", domain: "physics" },
|
|
644
|
+
"planck": { name: "Planck", domain: "physics" },
|
|
645
|
+
"curie": { name: "Curie", domain: "physics" },
|
|
646
|
+
"fermi": { name: "Fermi", domain: "physics" },
|
|
647
|
+
"heisenberg": { name: "Heisenberg", domain: "physics" },
|
|
648
|
+
"schrodinger": { name: "Schr\xF6dinger", domain: "physics" },
|
|
649
|
+
// Mathematics
|
|
650
|
+
"euclid": { name: "Euclid", domain: "math" },
|
|
651
|
+
"gauss": { name: "Gauss", domain: "math" },
|
|
652
|
+
"turing": { name: "Turing", domain: "math" },
|
|
653
|
+
"poincare": { name: "Poincar\xE9", domain: "math" },
|
|
654
|
+
"riemann": { name: "Riemann", domain: "math" },
|
|
655
|
+
"hilbert": { name: "Hilbert", domain: "math" },
|
|
656
|
+
"pythagoras": { name: "Pythagoras", domain: "math" },
|
|
657
|
+
// Computing & information theory
|
|
658
|
+
"von-neumann": { name: "Von Neumann", domain: "computing" },
|
|
659
|
+
"shannon": { name: "Shannon", domain: "computing" },
|
|
660
|
+
"hopper": { name: "Hopper", domain: "computing" },
|
|
661
|
+
"backus": { name: "Backus", domain: "computing" },
|
|
662
|
+
"knuth": { name: "Knuth", domain: "computing" },
|
|
663
|
+
"torvalds": { name: "Torvalds", domain: "computing" },
|
|
664
|
+
"stallman": { name: "Stallman", domain: "computing" },
|
|
665
|
+
"berners-lee": { name: "Berners-Lee", domain: "computing" },
|
|
666
|
+
"babbage": { name: "Babbage", domain: "computing" },
|
|
667
|
+
"lovelace": { name: "Lovelace", domain: "computing" },
|
|
668
|
+
"klein": { name: "Klein", domain: "computing" },
|
|
669
|
+
// Electronics & electrical engineering
|
|
670
|
+
"edison": { name: "Edison", domain: "ee" },
|
|
671
|
+
"tesla": { name: "Tesla", domain: "ee" },
|
|
672
|
+
"faraday": { name: "Faraday", domain: "ee" },
|
|
673
|
+
"maxwell": { name: "Maxwell", domain: "ee" },
|
|
674
|
+
"ohm": { name: "Ohm", domain: "ee" },
|
|
675
|
+
"bell": { name: "Bell", domain: "ee" },
|
|
676
|
+
"marconi": { name: "Marconi", domain: "ee" },
|
|
677
|
+
"lamarr": { name: "Lamarr", domain: "ee" },
|
|
678
|
+
// General science / multi-disciplinary
|
|
679
|
+
"darwin": { name: "Darwin", domain: "biology" },
|
|
680
|
+
"mendel": { name: "Mendel", domain: "biology" },
|
|
681
|
+
"pasteur": { name: "Pasteur", domain: "biology" },
|
|
682
|
+
"hawking": { name: "Hawking", domain: "cosmology" },
|
|
683
|
+
"sagan": { name: "Sagan", domain: "cosmology" },
|
|
684
|
+
// Chemistry / materials
|
|
685
|
+
"lavoisier": { name: "Lavoisier", domain: "chemistry" },
|
|
686
|
+
"mendeleev": { name: "Mendeleev", domain: "chemistry" }
|
|
687
|
+
};
|
|
688
|
+
var ALL_NICKNAMES = Object.values(NICKNAME_POOL);
|
|
689
|
+
var DOMAIN_PREFERENCES = {
|
|
690
|
+
"security": ["shannon", "turing", "lamarr", "stallman"],
|
|
691
|
+
"bug-hunter": ["darwin", "curie", "feynman", "fermi"],
|
|
692
|
+
"refactor": ["gauss", "hilbert", "euclid", "planck"],
|
|
693
|
+
"audit-log": ["sagan", "hawking", "poincare", "newton"],
|
|
694
|
+
"planner": ["hilbert", "gauss", "turing", "euclid"],
|
|
695
|
+
"researcher": ["sagan", "hawking", "darwin", "pasteur"],
|
|
696
|
+
"explorer": ["marconi", "bell", "columbus", "polo"],
|
|
697
|
+
"testing": ["pasteur", "curie", "fermi", "bohr"],
|
|
698
|
+
"frontend": ["lovelace", "hopper", "babbage", "backus"],
|
|
699
|
+
"backend": ["torvalds", "stallman", "von-neumann", "backus"],
|
|
700
|
+
"database": ["turing", "shannon", "backus", "knuth"],
|
|
701
|
+
"devops": ["tesla", "edison", "faraday", "bell"],
|
|
702
|
+
"security-scanner": ["shannon", "turing", "lamarr", "stallman"],
|
|
703
|
+
"refactor-planner": ["gauss", "hilbert", "planck", "newton"],
|
|
704
|
+
"architect": ["von-neumann", "turing", "gauss", "hilbert"],
|
|
705
|
+
"critic": ["einstein", "feynman", "dirac", "bohr"],
|
|
706
|
+
"e2e": ["hopper", "bell", "marconi", "tesla"],
|
|
707
|
+
"performance": ["knuth", "gauss", "planck", "feynman"],
|
|
708
|
+
"chaos": ["tesla", "edison", "curie", "fermi"],
|
|
709
|
+
"cost": ["ohm", "bell", "marconi", "tesla"],
|
|
710
|
+
// default fallback
|
|
711
|
+
"default": ["einstein", "newton", "curie", "tesla", "edison", "turing", "shannon", "hopper", "knuth", "stallman"]
|
|
712
|
+
};
|
|
713
|
+
function assignNickname(role, used) {
|
|
714
|
+
const preferences = [
|
|
715
|
+
...DOMAIN_PREFERENCES[role] ?? [],
|
|
716
|
+
...DOMAIN_PREFERENCES["default"] ?? []
|
|
717
|
+
];
|
|
718
|
+
for (const key of preferences) {
|
|
719
|
+
const entry = NICKNAME_POOL[key];
|
|
720
|
+
if (entry && !used.has(key)) {
|
|
721
|
+
return `${entry.name} (${formatRole(role)})`;
|
|
722
|
+
}
|
|
723
|
+
}
|
|
724
|
+
for (const entry of ALL_NICKNAMES) {
|
|
725
|
+
const key = Object.entries(NICKNAME_POOL).find(([, v]) => v.name === entry.name)?.[0];
|
|
726
|
+
if (key && !used.has(key)) {
|
|
727
|
+
return `${entry.name} (${formatRole(role)})`;
|
|
728
|
+
}
|
|
729
|
+
}
|
|
730
|
+
const counter = used.size + 1;
|
|
731
|
+
return `Scientist #${counter} (${formatRole(role)})`;
|
|
732
|
+
}
|
|
733
|
+
function formatRole(role) {
|
|
734
|
+
return role.split(/[-_]/).map((w) => w.charAt(0).toUpperCase() + w.slice(1)).join(" ");
|
|
735
|
+
}
|
|
736
|
+
|
|
634
737
|
// src/types/errors.ts
|
|
635
738
|
var ERROR_CODES = {
|
|
636
739
|
// Provider
|
|
@@ -779,17 +882,7 @@ var SubagentBudget = class _SubagentBudget {
|
|
|
779
882
|
startTime = null;
|
|
780
883
|
_onThreshold;
|
|
781
884
|
/**
|
|
782
|
-
*
|
|
783
|
-
* in flight. While a kind is here, further `checkLimit` calls for the
|
|
784
|
-
* same kind are no-ops — without this dedup, every `recordIteration`
|
|
785
|
-
* after the limit is reached spawns a fresh decision Promise (until
|
|
786
|
-
* the first one lands and patches limits), flooding the FleetBus
|
|
787
|
-
* with redundant threshold events. Cleared in `negotiateExtension`'s
|
|
788
|
-
* `finally`.
|
|
789
|
-
*/
|
|
790
|
-
pendingExtensions = /* @__PURE__ */ new Set();
|
|
791
|
-
/**
|
|
792
|
-
* Hard cap on how long `negotiateExtension` waits for the coordinator to
|
|
885
|
+
* Hard cap on how long `_negotiateExtension` waits for the coordinator to
|
|
793
886
|
* respond before defaulting to 'stop'. Without this fallback an absent
|
|
794
887
|
* or hung listener (Director not built / event filter detached mid-run)
|
|
795
888
|
* leaves the budget over-limit and never enforces anything.
|
|
@@ -851,38 +944,83 @@ var SubagentBudget = class _SubagentBudget {
|
|
|
851
944
|
* - `mode === 'auto'` + no listener → throw `BudgetExceededError` (hard stop; no one to ask)
|
|
852
945
|
* - `mode === 'auto'` + listener → throw `BudgetThresholdSignal` with async decision promise
|
|
853
946
|
*/
|
|
854
|
-
|
|
947
|
+
/**
|
|
948
|
+
* Collects all exceeded budget kinds into a single NOOP-free negotiation.
|
|
949
|
+
* Called by recordIteration / recordToolCall / recordUsage — each may call
|
|
950
|
+
* this for its own kind. The first call starts the negotiation and stores
|
|
951
|
+
* the Promise in _pendingNegotiation. Subsequent calls for DIFFERENT
|
|
952
|
+
* kinds (while a negotiation is in flight) are NOOPs — they don't start
|
|
953
|
+
* new conversations with the coordinator. This prevents an EventBus flood
|
|
954
|
+
* when multiple budget kinds are exceeded simultaneously in one iteration.
|
|
955
|
+
*
|
|
956
|
+
* Returns the kinds that were found to be exceeded (for logging/debugging).
|
|
957
|
+
*/
|
|
958
|
+
checkLimits(elapsedMs) {
|
|
959
|
+
const exceeded = [];
|
|
960
|
+
if (this.limits.maxIterations !== void 0 && this.iterations > this.limits.maxIterations) {
|
|
961
|
+
exceeded.push({ kind: "iterations", used: this.iterations, limit: this.limits.maxIterations });
|
|
962
|
+
}
|
|
963
|
+
if (this.limits.maxToolCalls !== void 0 && this.toolCalls > this.limits.maxToolCalls) {
|
|
964
|
+
exceeded.push({ kind: "tool_calls", used: this.toolCalls, limit: this.limits.maxToolCalls });
|
|
965
|
+
}
|
|
966
|
+
const totalTokens = this.tokenInput + this.tokenOutput;
|
|
967
|
+
if (this.limits.maxTokens !== void 0 && totalTokens > this.limits.maxTokens) {
|
|
968
|
+
exceeded.push({ kind: "tokens", used: totalTokens, limit: this.limits.maxTokens });
|
|
969
|
+
}
|
|
970
|
+
if (this.limits.maxCostUsd !== void 0 && this.costUsd > this.limits.maxCostUsd) {
|
|
971
|
+
exceeded.push({ kind: "cost", used: this.costUsd, limit: this.limits.maxCostUsd });
|
|
972
|
+
}
|
|
973
|
+
if (elapsedMs !== void 0 && this.limits.timeoutMs !== void 0 && elapsedMs > this.limits.timeoutMs) {
|
|
974
|
+
exceeded.push({ kind: "timeout", used: elapsedMs, limit: this.limits.timeoutMs });
|
|
975
|
+
}
|
|
976
|
+
if (exceeded.length === 0) return [];
|
|
855
977
|
if (!this._onThreshold) {
|
|
856
|
-
|
|
978
|
+
const first2 = exceeded[0];
|
|
979
|
+
throw new BudgetExceededError(first2.kind, first2.limit, first2.used);
|
|
857
980
|
}
|
|
858
981
|
if (this._mode === "sync") {
|
|
859
|
-
|
|
982
|
+
const first2 = exceeded[0];
|
|
983
|
+
throw new BudgetExceededError(first2.kind, first2.limit, first2.used);
|
|
860
984
|
}
|
|
861
985
|
const bus = this._events;
|
|
862
986
|
if (!bus || !bus.hasListenerFor("budget.threshold_reached")) {
|
|
863
|
-
|
|
987
|
+
const first2 = exceeded[0];
|
|
988
|
+
throw new BudgetExceededError(first2.kind, first2.limit, first2.used);
|
|
989
|
+
}
|
|
990
|
+
for (const entry of exceeded) {
|
|
991
|
+
if (this._pendingNegotiations.has(entry.kind)) continue;
|
|
992
|
+
const decision2 = this._negotiateExtension(entry.kind, exceeded);
|
|
993
|
+
this._pendingNegotiations.set(entry.kind, decision2);
|
|
864
994
|
}
|
|
865
|
-
|
|
866
|
-
this.
|
|
867
|
-
|
|
868
|
-
throw new BudgetThresholdSignal(kind, limit, used, decision);
|
|
995
|
+
const first = exceeded[0];
|
|
996
|
+
const decision = this._pendingNegotiations.get(first.kind);
|
|
997
|
+
throw new BudgetThresholdSignal(first.kind, first.limit, first.used, decision);
|
|
869
998
|
}
|
|
999
|
+
/**
|
|
1000
|
+
* Per-kind in-flight negotiation Promises. Each budget kind can have its
|
|
1001
|
+
* own concurrent negotiation — e.g. iterations and timeout can both
|
|
1002
|
+
* be exceeded simultaneously without blocking each other. The same kind
|
|
1003
|
+
* cannot start two concurrent negotiations (dedup guard).
|
|
1004
|
+
* Cleared in `_negotiateExtension`'s `finally` block.
|
|
1005
|
+
*/
|
|
1006
|
+
_pendingNegotiations = /* @__PURE__ */ new Map();
|
|
870
1007
|
/**
|
|
871
1008
|
* Drive the threshold handler to a decision. Resolves with `'stop'`
|
|
872
1009
|
* (signal the runner to abort) or `{ extend: ... }` (limits already
|
|
873
|
-
* patched in-place; the runner should not abort).
|
|
874
|
-
*
|
|
1010
|
+
* patched in-place; the runner should not abort). Clears the
|
|
1011
|
+
* per-kind slot in `_pendingNegotiations` in `finally`.
|
|
875
1012
|
*
|
|
876
1013
|
* The 'continue' return from a sync handler is treated as
|
|
877
1014
|
* `{ extend: {} }` — keep going without patching; next overrun fires
|
|
878
1015
|
* a fresh signal.
|
|
879
1016
|
*/
|
|
880
|
-
async
|
|
1017
|
+
async _negotiateExtension(kind, exceeded) {
|
|
881
1018
|
try {
|
|
1019
|
+
const first = exceeded[0];
|
|
882
1020
|
const result = this._onThreshold({
|
|
883
|
-
kind,
|
|
884
|
-
used,
|
|
885
|
-
limit,
|
|
1021
|
+
kind: first.kind,
|
|
1022
|
+
used: first.used,
|
|
1023
|
+
limit: first.limit,
|
|
886
1024
|
requestDecision: () => {
|
|
887
1025
|
const bus = this._events;
|
|
888
1026
|
if (!bus || !bus.hasListenerFor("budget.threshold_reached")) {
|
|
@@ -899,20 +1037,22 @@ var SubagentBudget = class _SubagentBudget {
|
|
|
899
1037
|
() => respond("stop"),
|
|
900
1038
|
_SubagentBudget.DECISION_TIMEOUT_MS
|
|
901
1039
|
);
|
|
902
|
-
|
|
903
|
-
|
|
904
|
-
|
|
905
|
-
|
|
906
|
-
|
|
907
|
-
|
|
908
|
-
|
|
909
|
-
|
|
910
|
-
|
|
911
|
-
|
|
912
|
-
|
|
913
|
-
|
|
914
|
-
|
|
915
|
-
|
|
1040
|
+
for (const { kind: kind2, used, limit } of exceeded) {
|
|
1041
|
+
bus.emit("budget.threshold_reached", {
|
|
1042
|
+
kind: kind2,
|
|
1043
|
+
used,
|
|
1044
|
+
limit,
|
|
1045
|
+
timeoutMs: _SubagentBudget.DECISION_TIMEOUT_MS,
|
|
1046
|
+
extend: (extra) => {
|
|
1047
|
+
clearTimeout(fallback);
|
|
1048
|
+
respond({ extend: extra });
|
|
1049
|
+
},
|
|
1050
|
+
deny: () => {
|
|
1051
|
+
clearTimeout(fallback);
|
|
1052
|
+
respond("stop");
|
|
1053
|
+
}
|
|
1054
|
+
});
|
|
1055
|
+
}
|
|
916
1056
|
});
|
|
917
1057
|
}
|
|
918
1058
|
});
|
|
@@ -938,47 +1078,39 @@ var SubagentBudget = class _SubagentBudget {
|
|
|
938
1078
|
}
|
|
939
1079
|
return decision;
|
|
940
1080
|
} finally {
|
|
941
|
-
this.
|
|
1081
|
+
this._pendingNegotiations.delete(kind);
|
|
942
1082
|
}
|
|
943
1083
|
}
|
|
944
1084
|
recordIteration() {
|
|
945
1085
|
this.iterations++;
|
|
946
|
-
|
|
947
|
-
void this.checkLimit("iterations", this.iterations, this.limits.maxIterations);
|
|
948
|
-
}
|
|
1086
|
+
void this.checkLimits();
|
|
949
1087
|
}
|
|
950
1088
|
recordToolCall() {
|
|
951
1089
|
this.toolCalls++;
|
|
952
|
-
|
|
953
|
-
void this.checkLimit("tool_calls", this.toolCalls, this.limits.maxToolCalls);
|
|
954
|
-
}
|
|
1090
|
+
void this.checkLimits();
|
|
955
1091
|
}
|
|
956
1092
|
recordUsage(usage, costUsd = 0) {
|
|
957
1093
|
this.tokenInput += usage.input;
|
|
958
1094
|
this.tokenOutput += usage.output;
|
|
959
1095
|
this.costUsd += costUsd;
|
|
960
|
-
|
|
961
|
-
if (this.limits.maxTokens !== void 0 && totalTokens > this.limits.maxTokens) {
|
|
962
|
-
void this.checkLimit("tokens", totalTokens, this.limits.maxTokens);
|
|
963
|
-
}
|
|
964
|
-
if (this.limits.maxCostUsd !== void 0 && this.costUsd > this.limits.maxCostUsd) {
|
|
965
|
-
void this.checkLimit("cost", this.costUsd, this.limits.maxCostUsd);
|
|
966
|
-
}
|
|
1096
|
+
void this.checkLimits();
|
|
967
1097
|
}
|
|
968
1098
|
/**
|
|
969
|
-
* Wall-clock budget check. Unlike other limits, timeout
|
|
970
|
-
*
|
|
971
|
-
*
|
|
972
|
-
*
|
|
973
|
-
*
|
|
974
|
-
*
|
|
1099
|
+
* Wall-clock budget check. Unlike other limits, timeout is always a hard stop
|
|
1100
|
+
* — wall-clock time cannot be "extended" by the coordinator, so it throws
|
|
1101
|
+
* synchronously rather than entering the negotiation flow.
|
|
1102
|
+
*
|
|
1103
|
+
* Decision table:
|
|
1104
|
+
* - no `onThreshold` handler → throw `BudgetExceededError`
|
|
1105
|
+
* - `mode === 'sync'` → throw `BudgetExceededError`
|
|
1106
|
+
* - `mode === 'auto'` + no listener → throw `BudgetExceededError`
|
|
1107
|
+
* - `mode === 'auto'` + listener → throw `BudgetExceededError` (timeout is not extendable)
|
|
975
1108
|
*/
|
|
976
1109
|
checkTimeout() {
|
|
977
1110
|
if (this.startTime === null || this.limits.timeoutMs === void 0) return;
|
|
978
1111
|
const elapsed = Date.now() - this.startTime;
|
|
979
|
-
if (elapsed
|
|
980
|
-
|
|
981
|
-
}
|
|
1112
|
+
if (elapsed <= this.limits.timeoutMs) return;
|
|
1113
|
+
void this.checkLimits(elapsed);
|
|
982
1114
|
}
|
|
983
1115
|
/** Returns true if a timeout has occurred without throwing. Useful for races. */
|
|
984
1116
|
isTimedOut() {
|
|
@@ -3341,11 +3473,46 @@ Working rules:
|
|
|
3341
3473
|
- When in doubt, flag as medium rather than ignoring potential issues`
|
|
3342
3474
|
// Budgets are set by the orchestrator per task — see fleet.ts header.
|
|
3343
3475
|
};
|
|
3476
|
+
var CRITIC_AGENT = {
|
|
3477
|
+
id: "critic",
|
|
3478
|
+
name: "Critic",
|
|
3479
|
+
role: "critic",
|
|
3480
|
+
prompt: `You are the Critic agent. Your job is to evaluate code quality,
|
|
3481
|
+
architectural decisions, and proposed changes against project conventions,
|
|
3482
|
+
engineering standards, and known quality gates. You do not write code \u2014
|
|
3483
|
+
you judge it.
|
|
3484
|
+
|
|
3485
|
+
Scope:
|
|
3486
|
+
- Evaluate bug severity and fix quality from Bug Hunter reports
|
|
3487
|
+
- Score refactoring plans from Refactor Planner (risk, completeness, trade-offs)
|
|
3488
|
+
- Flag gaps in test coverage, error handling, and edge case coverage
|
|
3489
|
+
- Assess whether a proposed change aligns with existing project patterns
|
|
3490
|
+
- Detect over-engineering or under-engineering relative to the problem scope
|
|
3491
|
+
|
|
3492
|
+
Input format you accept:
|
|
3493
|
+
{ "task": "evaluate | score | review", "subject": "bug_report | refactor_plan | diff", "focus": "correctness | maintainability | risk | all" }
|
|
3494
|
+
|
|
3495
|
+
Output: Markdown critic report:
|
|
3496
|
+
- ## Overall Score (0-10 with rationale)
|
|
3497
|
+
- ## Strengths (what's solid)
|
|
3498
|
+
- ## Weaknesses (what needs work)
|
|
3499
|
+
- ## Specific Concerns (with file:line when applicable)
|
|
3500
|
+
- ## Verdict: **Approve / Needs Revision / Reject**
|
|
3501
|
+
|
|
3502
|
+
Working rules:
|
|
3503
|
+
- Be specific \u2014 "looks fine" is not a review. Cite concrete evidence.
|
|
3504
|
+
- When scoring, explain the delta from a perfect score.
|
|
3505
|
+
- If you have no basis to evaluate a concern, say so rather than speculating.
|
|
3506
|
+
- Prioritise correctness over style; correctness issues block approval.
|
|
3507
|
+
- Score thresholds: \u22657 = Approve, 4-6 = Needs Revision, <4 = Reject`
|
|
3508
|
+
// Budgets are set by the orchestrator per task — see fleet.ts header.
|
|
3509
|
+
};
|
|
3344
3510
|
var FLEET_ROSTER = {
|
|
3345
3511
|
"audit-log": AUDIT_LOG_AGENT,
|
|
3346
3512
|
"bug-hunter": BUG_HUNTER_AGENT,
|
|
3347
3513
|
"refactor-planner": REFACTOR_PLANNER_AGENT,
|
|
3348
3514
|
"security-scanner": SECURITY_SCANNER_AGENT,
|
|
3515
|
+
"critic": CRITIC_AGENT,
|
|
3349
3516
|
...Object.fromEntries(
|
|
3350
3517
|
ALL_AGENT_DEFINITIONS.map((d) => [d.config.role, d.config])
|
|
3351
3518
|
)
|
|
@@ -3355,6 +3522,7 @@ var FLEET_ROSTER_BUDGETS = {
|
|
|
3355
3522
|
"bug-hunter": { timeoutMs: 10 * 60 * 60 * 1e3, maxIterations: 8e3, maxToolCalls: 2e4 },
|
|
3356
3523
|
"refactor-planner": { timeoutMs: 7.5 * 60 * 60 * 1e3, maxIterations: 6e3, maxToolCalls: 18e3 },
|
|
3357
3524
|
"security-scanner": { timeoutMs: 10 * 60 * 60 * 1e3, maxIterations: 8e3, maxToolCalls: 2e4 },
|
|
3525
|
+
"critic": { timeoutMs: 5 * 60 * 60 * 1e3, maxIterations: 4e3, maxToolCalls: 12e3 },
|
|
3358
3526
|
...Object.fromEntries(
|
|
3359
3527
|
ALL_AGENT_DEFINITIONS.map((d) => [d.config.role, d.budget])
|
|
3360
3528
|
)
|
|
@@ -3447,6 +3615,15 @@ var DefaultMultiAgentCoordinator = class extends EventEmitter {
|
|
|
3447
3615
|
runner;
|
|
3448
3616
|
fleetBus;
|
|
3449
3617
|
subagents = /* @__PURE__ */ new Map();
|
|
3618
|
+
/**
|
|
3619
|
+
* Base nickname keys already handed out this run (e.g. `einstein`, `tesla`).
|
|
3620
|
+
* Prevents two workers sharing a name. Direct `coordinator.spawn()` callers
|
|
3621
|
+
* (parallel/eternal engine, SDD parallel run) don't go through
|
|
3622
|
+
* `Director.spawn()` where nicknames are normally assigned, so the
|
|
3623
|
+
* coordinator upgrades placeholder names ("Executor", "slot-ab12cd", role
|
|
3624
|
+
* names) to memorable ones here — that's what surfaces in the fleet monitor.
|
|
3625
|
+
*/
|
|
3626
|
+
usedNicknames = /* @__PURE__ */ new Set();
|
|
3450
3627
|
pendingTasks = [];
|
|
3451
3628
|
completedResults = [];
|
|
3452
3629
|
totalIterations = 0;
|
|
@@ -3495,7 +3672,26 @@ var DefaultMultiAgentCoordinator = class extends EventEmitter {
|
|
|
3495
3672
|
this.config.maxConcurrent = Math.floor(n);
|
|
3496
3673
|
this.tryDispatchNext();
|
|
3497
3674
|
}
|
|
3675
|
+
/**
|
|
3676
|
+
* Upgrade a placeholder/role-derived name to a memorable scientist nickname
|
|
3677
|
+
* (e.g. "Einstein (Executor)"). A name is treated as a placeholder when it is
|
|
3678
|
+
* empty, equals the role (case-insensitive), is a generic default
|
|
3679
|
+
* ("subagent"/"adhoc"/"generic"), or is an auto-generated `slot-…` id.
|
|
3680
|
+
* Explicit, human-chosen names — including nicknames already assigned by
|
|
3681
|
+
* `Director.spawn()` — are left untouched, so this never double-assigns.
|
|
3682
|
+
*/
|
|
3683
|
+
withNickname(subagent) {
|
|
3684
|
+
const role = subagent.role ?? "subagent";
|
|
3685
|
+
const name = subagent.name?.trim() ?? "";
|
|
3686
|
+
const isPlaceholder = name === "" || name.toLowerCase() === role.toLowerCase() || name === "subagent" || name === "adhoc" || name === "generic" || /^slot-/.test(name);
|
|
3687
|
+
if (!isPlaceholder) return subagent;
|
|
3688
|
+
const nickname = assignNickname(role, this.usedNicknames);
|
|
3689
|
+
const baseKey = nickname.split(" ")[0].toLowerCase().replace(/[^a-z0-9-]/g, "-");
|
|
3690
|
+
this.usedNicknames.add(baseKey);
|
|
3691
|
+
return { ...subagent, name: nickname };
|
|
3692
|
+
}
|
|
3498
3693
|
async spawn(subagent) {
|
|
3694
|
+
subagent = this.withNickname(subagent);
|
|
3499
3695
|
const id = subagent.id || randomUUID();
|
|
3500
3696
|
if (this.subagents.has(id)) {
|
|
3501
3697
|
throw new Error(`Subagent id "${id}" already exists \u2014 refusing to overwrite`);
|
|
@@ -4374,7 +4570,7 @@ function makeRollUpTool(director) {
|
|
|
4374
4570
|
function makeTerminateTool(director) {
|
|
4375
4571
|
return {
|
|
4376
4572
|
name: "terminate_subagent",
|
|
4377
|
-
description:
|
|
4573
|
+
description: 'Forcibly abort a subagent. The subagent finishes its current iteration then exits with status "stopped".',
|
|
4378
4574
|
permission: "auto",
|
|
4379
4575
|
mutating: true,
|
|
4380
4576
|
inputSchema: { type: "object", properties: { subagentId: { type: "string", description: "Subagent to abort." } }, required: ["subagentId"] },
|
|
@@ -4385,6 +4581,19 @@ function makeTerminateTool(director) {
|
|
|
4385
4581
|
}
|
|
4386
4582
|
};
|
|
4387
4583
|
}
|
|
4584
|
+
function makeTerminateAllTool(director) {
|
|
4585
|
+
return {
|
|
4586
|
+
name: "terminate_all",
|
|
4587
|
+
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.',
|
|
4588
|
+
permission: "auto",
|
|
4589
|
+
mutating: true,
|
|
4590
|
+
inputSchema: { type: "object", properties: {}, required: [] },
|
|
4591
|
+
async execute() {
|
|
4592
|
+
await director.terminateAll();
|
|
4593
|
+
return { ok: true, message: `Fleet shutdown complete \u2014 all subagents stopped, pending tasks drained.` };
|
|
4594
|
+
}
|
|
4595
|
+
};
|
|
4596
|
+
}
|
|
4388
4597
|
function makeFleetStatusTool(director) {
|
|
4389
4598
|
return {
|
|
4390
4599
|
name: "fleet_status",
|
|
@@ -4428,7 +4637,6 @@ function makeFleetSessionTool(director) {
|
|
|
4428
4637
|
type: "object",
|
|
4429
4638
|
properties: {
|
|
4430
4639
|
subagentId: { type: "string", description: "Subagent id to read the transcript of." },
|
|
4431
|
-
/** Number of trailing lines to return (last N JSONL lines). Default: all. */
|
|
4432
4640
|
tail: { type: "number", description: "Number of trailing JSONL lines to return. Omit for the full transcript." }
|
|
4433
4641
|
},
|
|
4434
4642
|
required: ["subagentId"]
|
|
@@ -4464,8 +4672,6 @@ function makeFleetHealthTool(director) {
|
|
|
4464
4672
|
id: s.id,
|
|
4465
4673
|
status: s.status,
|
|
4466
4674
|
lastEventAt: usage?.lastEventAt,
|
|
4467
|
-
// Budget pressure: fraction of each limit consumed if we have it.
|
|
4468
|
-
// BudgetWarning events carry used/limit ratios; surface them here.
|
|
4469
4675
|
budgetPressure: {
|
|
4470
4676
|
iterations: usage?.iterations,
|
|
4471
4677
|
toolCalls: usage?.toolCalls,
|
|
@@ -4477,16 +4683,618 @@ function makeFleetHealthTool(director) {
|
|
|
4477
4683
|
}
|
|
4478
4684
|
};
|
|
4479
4685
|
}
|
|
4686
|
+
function makeCollabDebugTool(director) {
|
|
4687
|
+
return {
|
|
4688
|
+
name: "collab_debug",
|
|
4689
|
+
description: "Start a collaborative debugging session: BugHunter, RefactorPlanner, and Critic run in parallel on the same target files. BugHunter finds bugs and emits bug.found events. RefactorPlanner listens for bug.found and emits refactor.plan events. Critic evaluates both and emits critic.evaluation events. Returns a structured report with overall verdict (approve / needs_revision / reject).",
|
|
4690
|
+
permission: "auto",
|
|
4691
|
+
mutating: false,
|
|
4692
|
+
inputSchema: {
|
|
4693
|
+
type: "object",
|
|
4694
|
+
properties: {
|
|
4695
|
+
targetPaths: {
|
|
4696
|
+
type: "array",
|
|
4697
|
+
items: { type: "string" },
|
|
4698
|
+
description: "File paths / glob patterns to scan for bugs."
|
|
4699
|
+
},
|
|
4700
|
+
timeoutMs: {
|
|
4701
|
+
type: "number",
|
|
4702
|
+
description: "Timeout in ms. Default: 600000 (10 minutes)."
|
|
4703
|
+
}
|
|
4704
|
+
},
|
|
4705
|
+
required: ["targetPaths"]
|
|
4706
|
+
},
|
|
4707
|
+
async execute(input) {
|
|
4708
|
+
const i = input;
|
|
4709
|
+
if (!i.targetPaths?.length) {
|
|
4710
|
+
return { error: "collab_debug: targetPaths is required and must be non-empty." };
|
|
4711
|
+
}
|
|
4712
|
+
const options = {
|
|
4713
|
+
targetPaths: i.targetPaths,
|
|
4714
|
+
timeoutMs: i.timeoutMs
|
|
4715
|
+
};
|
|
4716
|
+
try {
|
|
4717
|
+
const report = await director.spawnCollab(options);
|
|
4718
|
+
return {
|
|
4719
|
+
sessionId: report.sessionId,
|
|
4720
|
+
overallVerdict: report.overallVerdict,
|
|
4721
|
+
bugCount: report.bugs.length,
|
|
4722
|
+
planCount: report.refactorPlans.length,
|
|
4723
|
+
evaluationCount: report.evaluations.length,
|
|
4724
|
+
summary: report.summary,
|
|
4725
|
+
bugs: report.bugs,
|
|
4726
|
+
refactorPlans: report.refactorPlans,
|
|
4727
|
+
evaluations: report.evaluations
|
|
4728
|
+
};
|
|
4729
|
+
} catch (err) {
|
|
4730
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
4731
|
+
return { error: "collab_debug failed: " + msg };
|
|
4732
|
+
}
|
|
4733
|
+
}
|
|
4734
|
+
};
|
|
4735
|
+
}
|
|
4736
|
+
function makeFleetEmitTool(director) {
|
|
4737
|
+
return {
|
|
4738
|
+
name: "fleet_emit",
|
|
4739
|
+
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.",
|
|
4740
|
+
permission: "auto",
|
|
4741
|
+
mutating: false,
|
|
4742
|
+
inputSchema: {
|
|
4743
|
+
type: "object",
|
|
4744
|
+
properties: {
|
|
4745
|
+
type: {
|
|
4746
|
+
type: "string",
|
|
4747
|
+
description: "Event type string (e.g. bug.found, refactor.plan, critic.evaluation, progress, result)."
|
|
4748
|
+
},
|
|
4749
|
+
payload: {
|
|
4750
|
+
type: "object",
|
|
4751
|
+
description: "Event payload. Structure depends on event type. Use null if no payload."
|
|
4752
|
+
}
|
|
4753
|
+
},
|
|
4754
|
+
required: ["type"]
|
|
4755
|
+
},
|
|
4756
|
+
async execute(input) {
|
|
4757
|
+
const i = input;
|
|
4758
|
+
director.fleet.emit({
|
|
4759
|
+
subagentId: director.id,
|
|
4760
|
+
ts: Date.now(),
|
|
4761
|
+
type: i.type,
|
|
4762
|
+
payload: i.payload ?? {}
|
|
4763
|
+
});
|
|
4764
|
+
return { ok: true, event: i.type };
|
|
4765
|
+
}
|
|
4766
|
+
};
|
|
4767
|
+
}
|
|
4768
|
+
function makeWorkCompleteTool(director) {
|
|
4769
|
+
return {
|
|
4770
|
+
name: "work_complete",
|
|
4771
|
+
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.",
|
|
4772
|
+
permission: "auto",
|
|
4773
|
+
mutating: false,
|
|
4774
|
+
inputSchema: { type: "object", properties: {}, required: [] },
|
|
4775
|
+
async execute() {
|
|
4776
|
+
director.workComplete();
|
|
4777
|
+
return { ok: true, message: "Fleet wind-down signaled. No new spawns or task dispatches." };
|
|
4778
|
+
}
|
|
4779
|
+
};
|
|
4780
|
+
}
|
|
4781
|
+
var DirectorAlertLevel = /* @__PURE__ */ ((DirectorAlertLevel2) => {
|
|
4782
|
+
DirectorAlertLevel2["WARNING"] = "warning";
|
|
4783
|
+
DirectorAlertLevel2["CRITICAL"] = "critical";
|
|
4784
|
+
DirectorAlertLevel2["CANCELLED"] = "cancelled";
|
|
4785
|
+
return DirectorAlertLevel2;
|
|
4786
|
+
})(DirectorAlertLevel || {});
|
|
4787
|
+
var CollabSession = class extends EventEmitter {
|
|
4788
|
+
sessionId;
|
|
4789
|
+
options;
|
|
4790
|
+
snapshot;
|
|
4791
|
+
director;
|
|
4792
|
+
fleetBus;
|
|
4793
|
+
subagentIds = /* @__PURE__ */ new Map();
|
|
4794
|
+
// role → subagentId
|
|
4795
|
+
bugs = /* @__PURE__ */ new Map();
|
|
4796
|
+
plans = /* @__PURE__ */ new Map();
|
|
4797
|
+
evaluations = /* @__PURE__ */ new Map();
|
|
4798
|
+
disposers = new Array();
|
|
4799
|
+
settled = false;
|
|
4800
|
+
timeoutMs;
|
|
4801
|
+
cancelled = false;
|
|
4802
|
+
alerts = [];
|
|
4803
|
+
/** Tracks tool call counts per subagent for progress-based timeout decisions. */
|
|
4804
|
+
progressBySubagent = /* @__PURE__ */ new Map();
|
|
4805
|
+
/** Last tool call count when a timeout warning was handled. */
|
|
4806
|
+
lastTimeoutProgress = /* @__PURE__ */ new Map();
|
|
4807
|
+
/** Session-level timeout timer handle (cleared on cancel or natural completion). */
|
|
4808
|
+
_timeoutTimer;
|
|
4809
|
+
constructor(director, fleetBus, options) {
|
|
4810
|
+
super();
|
|
4811
|
+
this.sessionId = randomUUID();
|
|
4812
|
+
this.options = options;
|
|
4813
|
+
this.director = director;
|
|
4814
|
+
this.fleetBus = fleetBus;
|
|
4815
|
+
this.timeoutMs = options.timeoutMs ?? 10 * 60 * 1e3;
|
|
4816
|
+
if (options.prebuiltSnapshot) {
|
|
4817
|
+
this.snapshot = options.prebuiltSnapshot;
|
|
4818
|
+
} else {
|
|
4819
|
+
this.snapshot = {
|
|
4820
|
+
id: this.sessionId,
|
|
4821
|
+
createdAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
4822
|
+
files: []
|
|
4823
|
+
};
|
|
4824
|
+
}
|
|
4825
|
+
}
|
|
4826
|
+
get id() {
|
|
4827
|
+
return this.sessionId;
|
|
4828
|
+
}
|
|
4829
|
+
getSessionAlerts() {
|
|
4830
|
+
return [...this.alerts];
|
|
4831
|
+
}
|
|
4832
|
+
isCancelled() {
|
|
4833
|
+
return this.cancelled;
|
|
4834
|
+
}
|
|
4835
|
+
/**
|
|
4836
|
+
* Snapshot of role → subagentId map. The Director calls coordinator.stop()
|
|
4837
|
+
* for each agent when cancelling the session, using this map to enumerate
|
|
4838
|
+
* all three collab agents.
|
|
4839
|
+
*/
|
|
4840
|
+
getSubagentIds() {
|
|
4841
|
+
return new Map(this.subagentIds);
|
|
4842
|
+
}
|
|
4843
|
+
async buildSnapshot() {
|
|
4844
|
+
if (this.snapshot.files.length > 0) return this.snapshot;
|
|
4845
|
+
for (const filePath of this.options.targetPaths) {
|
|
4846
|
+
try {
|
|
4847
|
+
const content = await fsp5.readFile(filePath, "utf8");
|
|
4848
|
+
const ext = filePath.split(".").pop() ?? "";
|
|
4849
|
+
const language = ext === "ts" || ext === "tsx" ? "typescript" : ext === "js" || ext === "jsx" ? "javascript" : ext === "md" ? "markdown" : ext === "json" ? "json" : void 0;
|
|
4850
|
+
this.snapshot.files.push({ path: filePath, content, language });
|
|
4851
|
+
} catch {
|
|
4852
|
+
this.snapshot.files.push({ path: filePath, content: "", language: void 0 });
|
|
4853
|
+
}
|
|
4854
|
+
}
|
|
4855
|
+
return this.snapshot;
|
|
4856
|
+
}
|
|
4857
|
+
/**
|
|
4858
|
+
* Cancel the session. Emits director.cancel_collab on the FleetBus so all
|
|
4859
|
+
* collab agents finish early. The session-level timeout timer is also cleared.
|
|
4860
|
+
* Safe to call multiple times (idempotent after first call).
|
|
4861
|
+
*/
|
|
4862
|
+
cancel(reason = "Director cancelled collab session") {
|
|
4863
|
+
if (this.settled) return;
|
|
4864
|
+
this.cancelled = true;
|
|
4865
|
+
if (this._timeoutTimer) {
|
|
4866
|
+
clearTimeout(this._timeoutTimer);
|
|
4867
|
+
this._timeoutTimer = void 0;
|
|
4868
|
+
}
|
|
4869
|
+
this.fleetBus.emit({
|
|
4870
|
+
subagentId: this.director.id,
|
|
4871
|
+
ts: Date.now(),
|
|
4872
|
+
type: "director.cancel_collab",
|
|
4873
|
+
payload: { sessionId: this.sessionId, reason, cancelledAt: (/* @__PURE__ */ new Date()).toISOString() }
|
|
4874
|
+
});
|
|
4875
|
+
this.fleetBus.emit({
|
|
4876
|
+
subagentId: this.director.id,
|
|
4877
|
+
ts: Date.now(),
|
|
4878
|
+
type: "collab.cancelled",
|
|
4879
|
+
payload: { sessionId: this.sessionId, reason }
|
|
4880
|
+
});
|
|
4881
|
+
}
|
|
4882
|
+
async start() {
|
|
4883
|
+
if (this.settled) throw new Error("session already settled");
|
|
4884
|
+
this.settled = true;
|
|
4885
|
+
await this.buildSnapshot();
|
|
4886
|
+
this.wireFleetBus();
|
|
4887
|
+
const [bugHunterId, refactorPlannerId, criticId] = await Promise.all([
|
|
4888
|
+
this.spawnAgent("bug-hunter", this.buildBugHunterTask()),
|
|
4889
|
+
this.spawnAgent("refactor-planner", this.buildRefactorPlannerTask()),
|
|
4890
|
+
this.spawnAgent("critic", this.buildCriticTask())
|
|
4891
|
+
]);
|
|
4892
|
+
this.subagentIds.set("bug-hunter", bugHunterId);
|
|
4893
|
+
this.subagentIds.set("refactor-planner", refactorPlannerId);
|
|
4894
|
+
this.subagentIds.set("critic", criticId);
|
|
4895
|
+
const timeout = new Promise((_, reject) => {
|
|
4896
|
+
this._timeoutTimer = setTimeout(() => {
|
|
4897
|
+
this.cancel("Session-level timeout reached");
|
|
4898
|
+
reject(new Error(`CollabSession timed out after ${this.timeoutMs}ms`));
|
|
4899
|
+
}, this.timeoutMs);
|
|
4900
|
+
});
|
|
4901
|
+
let results;
|
|
4902
|
+
try {
|
|
4903
|
+
results = await Promise.race([
|
|
4904
|
+
Promise.all([
|
|
4905
|
+
this.director.awaitTasks([bugHunterId]),
|
|
4906
|
+
this.director.awaitTasks([refactorPlannerId]),
|
|
4907
|
+
this.director.awaitTasks([criticId])
|
|
4908
|
+
]),
|
|
4909
|
+
timeout
|
|
4910
|
+
]);
|
|
4911
|
+
} catch (err) {
|
|
4912
|
+
if (this._timeoutTimer) {
|
|
4913
|
+
clearTimeout(this._timeoutTimer);
|
|
4914
|
+
this._timeoutTimer = void 0;
|
|
4915
|
+
}
|
|
4916
|
+
this.cleanup();
|
|
4917
|
+
const error = err instanceof Error ? err : new Error(String(err));
|
|
4918
|
+
this.emit("session.error", error);
|
|
4919
|
+
throw error;
|
|
4920
|
+
}
|
|
4921
|
+
for (const result of results.flat()) {
|
|
4922
|
+
await this.parseAndEmit(result);
|
|
4923
|
+
}
|
|
4924
|
+
const report = this.assembleReport();
|
|
4925
|
+
this.cleanup();
|
|
4926
|
+
this.emit("session.done", report);
|
|
4927
|
+
return report;
|
|
4928
|
+
}
|
|
4929
|
+
async parseAndEmit(result) {
|
|
4930
|
+
if (result.status !== "success" || result.result == null) return;
|
|
4931
|
+
const text = typeof result.result === "string" ? result.result : JSON.stringify(result.result);
|
|
4932
|
+
for (const obj of this.extractJsonObjects(text)) {
|
|
4933
|
+
const type = "finding" in obj ? "bug.found" : "plan" in obj ? "refactor.plan" : "evaluation" in obj ? "critic.evaluation" : null;
|
|
4934
|
+
if (!type) continue;
|
|
4935
|
+
this.fleetBus.emit({
|
|
4936
|
+
subagentId: result.subagentId,
|
|
4937
|
+
taskId: result.taskId,
|
|
4938
|
+
ts: Date.now(),
|
|
4939
|
+
type,
|
|
4940
|
+
payload: obj
|
|
4941
|
+
});
|
|
4942
|
+
}
|
|
4943
|
+
}
|
|
4944
|
+
extractJsonObjects(text) {
|
|
4945
|
+
const objects = [];
|
|
4946
|
+
let depth = 0;
|
|
4947
|
+
let start = -1;
|
|
4948
|
+
let inString = false;
|
|
4949
|
+
let escaped = false;
|
|
4950
|
+
for (let i = 0; i < text.length; i++) {
|
|
4951
|
+
const ch = text[i];
|
|
4952
|
+
if (inString) {
|
|
4953
|
+
if (escaped) escaped = false;
|
|
4954
|
+
else if (ch === "\\") escaped = true;
|
|
4955
|
+
else if (ch === '"') inString = false;
|
|
4956
|
+
continue;
|
|
4957
|
+
}
|
|
4958
|
+
if (ch === '"') {
|
|
4959
|
+
inString = true;
|
|
4960
|
+
} else if (ch === "{") {
|
|
4961
|
+
if (depth === 0) start = i;
|
|
4962
|
+
depth++;
|
|
4963
|
+
} else if (ch === "}" && depth > 0) {
|
|
4964
|
+
depth--;
|
|
4965
|
+
if (depth === 0 && start >= 0) {
|
|
4966
|
+
const candidate = text.slice(start, i + 1);
|
|
4967
|
+
try {
|
|
4968
|
+
const parsed = JSON.parse(candidate);
|
|
4969
|
+
if (parsed && typeof parsed === "object" && !Array.isArray(parsed)) {
|
|
4970
|
+
objects.push(parsed);
|
|
4971
|
+
}
|
|
4972
|
+
} catch {
|
|
4973
|
+
}
|
|
4974
|
+
start = -1;
|
|
4975
|
+
}
|
|
4976
|
+
}
|
|
4977
|
+
}
|
|
4978
|
+
return objects;
|
|
4979
|
+
}
|
|
4980
|
+
budgetForRole(role) {
|
|
4981
|
+
if (this.options.budgetOverrides?.[role]) {
|
|
4982
|
+
return this.options.budgetOverrides[role];
|
|
4983
|
+
}
|
|
4984
|
+
const defaults = {
|
|
4985
|
+
"bug-hunter": { maxIterations: 2e3, maxToolCalls: 5e3, timeoutMs: 10 * 60 * 1e3 },
|
|
4986
|
+
"refactor-planner": { maxIterations: 1500, maxToolCalls: 4e3, timeoutMs: 8 * 60 * 1e3 },
|
|
4987
|
+
"critic": { maxIterations: 1e3, maxToolCalls: 3e3, timeoutMs: 6 * 60 * 1e3 }
|
|
4988
|
+
};
|
|
4989
|
+
return defaults[role] ?? { maxIterations: 1500, maxToolCalls: 4e3, timeoutMs: 8 * 60 * 1e3 };
|
|
4990
|
+
}
|
|
4991
|
+
async spawnAgent(role, taskBrief) {
|
|
4992
|
+
const budget = this.budgetForRole(role);
|
|
4993
|
+
const cfg = {
|
|
4994
|
+
id: `${role}-${this.sessionId}`,
|
|
4995
|
+
name: role,
|
|
4996
|
+
role,
|
|
4997
|
+
tools: ["fleet_emit", "fleet_status", "read", "grep", "glob", "bash", "write"],
|
|
4998
|
+
maxIterations: budget.maxIterations,
|
|
4999
|
+
maxToolCalls: budget.maxToolCalls,
|
|
5000
|
+
timeoutMs: budget.timeoutMs
|
|
5001
|
+
};
|
|
5002
|
+
const subagentId = await this.director.spawn(cfg);
|
|
5003
|
+
await this.director.assign({ id: randomUUID(), subagentId, description: taskBrief });
|
|
5004
|
+
return subagentId;
|
|
5005
|
+
}
|
|
5006
|
+
buildBugHunterTask() {
|
|
5007
|
+
const scratchpad = this.director.sharedScratchpadPath ?? "/tmp";
|
|
5008
|
+
const fileContents = this.snapshot.files.map((f) => `=== ${f.path} ===
|
|
5009
|
+
${f.content}`).join("\n\n");
|
|
5010
|
+
return `You are BugHunter. Scan the following files for bugs and code smells.
|
|
5011
|
+
|
|
5012
|
+
Target files:
|
|
5013
|
+
${fileContents}
|
|
5014
|
+
|
|
5015
|
+
For each bug found, emit it using the fleet_emit tool immediately:
|
|
5016
|
+
{ "type": "bug.found", "payload": { "finding": { "id": "<uuid>", "type": "<pattern>", "severity": "<critical|high|medium|low>", "location": { "file": "<path>", "line": <n> }, "description": "<explain>", "suggestedFix": "<optional>" } } }
|
|
5017
|
+
|
|
5018
|
+
After scanning all files, write your full markdown bug report to:
|
|
5019
|
+
${scratchpad}/bug-hunter-report-${this.sessionId}.md
|
|
5020
|
+
|
|
5021
|
+
Important: emit each finding as soon as you find it. Do not batch or wait until the end.`;
|
|
5022
|
+
}
|
|
5023
|
+
buildRefactorPlannerTask() {
|
|
5024
|
+
const scratchpad = this.director.sharedScratchpadPath ?? "/tmp";
|
|
5025
|
+
const bugHunterReportPath = `${scratchpad}/bug-hunter-report-${this.sessionId}.md`;
|
|
5026
|
+
const fileContents = this.snapshot.files.map((f) => `=== ${f.path} ===
|
|
5027
|
+
${f.content}`).join("\n\n");
|
|
5028
|
+
return `You are RefactorPlanner. Plan refactorings for the following files.
|
|
5029
|
+
|
|
5030
|
+
Target files:
|
|
5031
|
+
${fileContents}
|
|
5032
|
+
|
|
5033
|
+
Read the BugHunter report at: ${bugHunterReportPath}
|
|
5034
|
+
|
|
5035
|
+
For each bug you can address, emit a refactor plan using fleet_emit:
|
|
5036
|
+
{ "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>" } } }
|
|
5037
|
+
|
|
5038
|
+
Also write your full markdown plan to:
|
|
5039
|
+
${scratchpad}/refactor-plan-${this.sessionId}.md
|
|
5040
|
+
|
|
5041
|
+
Emit each plan immediately. Do not wait until planning is complete.`;
|
|
5042
|
+
}
|
|
5043
|
+
buildCriticTask() {
|
|
5044
|
+
const scratchpad = this.director.sharedScratchpadPath ?? "/tmp";
|
|
5045
|
+
const bugHunterReportPath = `${scratchpad}/bug-hunter-report-${this.sessionId}.md`;
|
|
5046
|
+
const refactorPlanPath = `${scratchpad}/refactor-plan-${this.sessionId}.md`;
|
|
5047
|
+
const fileContents = this.snapshot.files.map((f) => `=== ${f.path} ===
|
|
5048
|
+
${f.content}`).join("\n\n");
|
|
5049
|
+
return `You are Critic. Evaluate bug findings and refactor plans.
|
|
5050
|
+
|
|
5051
|
+
Target files:
|
|
5052
|
+
${fileContents}
|
|
5053
|
+
|
|
5054
|
+
Read the BugHunter report at: ${bugHunterReportPath}
|
|
5055
|
+
Read the RefactorPlanner report at: ${refactorPlanPath}
|
|
5056
|
+
|
|
5057
|
+
For each bug and refactor plan, emit your evaluation using fleet_emit:
|
|
5058
|
+
{ "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>" }] } } }
|
|
5059
|
+
|
|
5060
|
+
After all evaluations, write your markdown report to:
|
|
5061
|
+
${scratchpad}/critic-report-${this.sessionId}.md
|
|
5062
|
+
|
|
5063
|
+
Emit each evaluation immediately. Do not wait until you have read all reports.`;
|
|
5064
|
+
}
|
|
5065
|
+
wireFleetBus() {
|
|
5066
|
+
const dTool = this.fleetBus.filter("tool.executed", (e) => {
|
|
5067
|
+
this.progressBySubagent.set(e.subagentId, (this.progressBySubagent.get(e.subagentId) ?? 0) + 1);
|
|
5068
|
+
});
|
|
5069
|
+
this.disposers.push(dTool);
|
|
5070
|
+
const dBudget = this.fleetBus.filter("budget.threshold_reached", (e) => {
|
|
5071
|
+
const payload = e.payload;
|
|
5072
|
+
const role = this.roleFromSubagentId(e.subagentId);
|
|
5073
|
+
if (!role) return;
|
|
5074
|
+
const btwNotes = this.director.getLeaderBtwNotes();
|
|
5075
|
+
const alert = {
|
|
5076
|
+
sessionId: this.sessionId,
|
|
5077
|
+
subagentId: e.subagentId,
|
|
5078
|
+
role,
|
|
5079
|
+
level: "warning" /* WARNING */,
|
|
5080
|
+
message: `${role} hit ${payload.kind} soft limit (${payload.used}/${payload.limit})`,
|
|
5081
|
+
budgetKind: payload.kind,
|
|
5082
|
+
elapsedMs: payload.timeoutMs,
|
|
5083
|
+
limit: payload.limit,
|
|
5084
|
+
btwNotes
|
|
5085
|
+
};
|
|
5086
|
+
this.alerts.push(alert);
|
|
5087
|
+
this.fleetBus.emit({
|
|
5088
|
+
subagentId: e.subagentId,
|
|
5089
|
+
ts: Date.now(),
|
|
5090
|
+
type: "collab.warning",
|
|
5091
|
+
payload: alert
|
|
5092
|
+
});
|
|
5093
|
+
const decision = this.options.onBudgetWarning?.(alert) ?? "ignore";
|
|
5094
|
+
if (decision === "cancel") {
|
|
5095
|
+
this.cancel(`Director cancelled: ${role} ${payload.kind} threshold`);
|
|
5096
|
+
return;
|
|
5097
|
+
}
|
|
5098
|
+
if (payload.kind === "timeout") {
|
|
5099
|
+
const progress = this.progressBySubagent.get(e.subagentId) ?? 0;
|
|
5100
|
+
const lastProgress = this.lastTimeoutProgress.get(e.subagentId) ?? -1;
|
|
5101
|
+
if (progress <= lastProgress) {
|
|
5102
|
+
payload.deny();
|
|
5103
|
+
return;
|
|
5104
|
+
}
|
|
5105
|
+
this.lastTimeoutProgress.set(e.subagentId, progress);
|
|
5106
|
+
const newLimit = Math.min(Math.ceil((payload.timeoutMs ?? payload.limit) * 2), 24 * 60 * 6e4);
|
|
5107
|
+
setImmediate(() => {
|
|
5108
|
+
payload.extend({ timeoutMs: newLimit });
|
|
5109
|
+
});
|
|
5110
|
+
return;
|
|
5111
|
+
}
|
|
5112
|
+
if (decision === "extend") {
|
|
5113
|
+
setImmediate(() => {
|
|
5114
|
+
const base = Math.max(payload.limit, payload.used);
|
|
5115
|
+
const extra = {};
|
|
5116
|
+
switch (payload.kind) {
|
|
5117
|
+
case "iterations":
|
|
5118
|
+
extra.maxIterations = Math.min(Math.ceil(base * 1.5), 5e4);
|
|
5119
|
+
break;
|
|
5120
|
+
case "tool_calls":
|
|
5121
|
+
extra.maxToolCalls = Math.min(Math.ceil(base * 1.5), 1e5);
|
|
5122
|
+
break;
|
|
5123
|
+
case "tokens":
|
|
5124
|
+
extra.maxTokens = Math.min(Math.ceil(base * 1.5), 5e6);
|
|
5125
|
+
break;
|
|
5126
|
+
case "cost":
|
|
5127
|
+
extra.maxCostUsd = Math.min(base * 1.5, 100);
|
|
5128
|
+
break;
|
|
5129
|
+
}
|
|
5130
|
+
payload.extend(extra);
|
|
5131
|
+
});
|
|
5132
|
+
return;
|
|
5133
|
+
}
|
|
5134
|
+
if (payload.kind !== "timeout") {
|
|
5135
|
+
setImmediate(() => {
|
|
5136
|
+
const base = Math.max(payload.limit, payload.used);
|
|
5137
|
+
const extra = {};
|
|
5138
|
+
switch (payload.kind) {
|
|
5139
|
+
case "iterations":
|
|
5140
|
+
extra.maxIterations = Math.min(Math.ceil(base * 1.25), 5e4);
|
|
5141
|
+
break;
|
|
5142
|
+
case "tool_calls":
|
|
5143
|
+
extra.maxToolCalls = Math.min(Math.ceil(base * 1.25), 1e5);
|
|
5144
|
+
break;
|
|
5145
|
+
case "tokens":
|
|
5146
|
+
extra.maxTokens = Math.min(Math.ceil(base * 1.25), 5e6);
|
|
5147
|
+
break;
|
|
5148
|
+
case "cost":
|
|
5149
|
+
extra.maxCostUsd = Math.min(base * 1.25, 100);
|
|
5150
|
+
break;
|
|
5151
|
+
}
|
|
5152
|
+
payload.extend(extra);
|
|
5153
|
+
});
|
|
5154
|
+
}
|
|
5155
|
+
});
|
|
5156
|
+
this.disposers.push(dBudget);
|
|
5157
|
+
const dCancel = this.fleetBus.filter("director.cancel_collab", (e) => {
|
|
5158
|
+
const payload = e.payload;
|
|
5159
|
+
if (payload.sessionId !== this.sessionId) return;
|
|
5160
|
+
this.cancelled = true;
|
|
5161
|
+
if (this._timeoutTimer) {
|
|
5162
|
+
clearTimeout(this._timeoutTimer);
|
|
5163
|
+
this._timeoutTimer = void 0;
|
|
5164
|
+
}
|
|
5165
|
+
this.fleetBus.emit({
|
|
5166
|
+
subagentId: this.director.id,
|
|
5167
|
+
ts: Date.now(),
|
|
5168
|
+
type: "collab.cancelled",
|
|
5169
|
+
payload: { sessionId: this.sessionId, reason: payload.reason }
|
|
5170
|
+
});
|
|
5171
|
+
});
|
|
5172
|
+
this.disposers.push(dCancel);
|
|
5173
|
+
const d1 = this.fleetBus.filter("bug.found", (e) => {
|
|
5174
|
+
const payload = e.payload;
|
|
5175
|
+
if (payload?.finding) {
|
|
5176
|
+
this.bugs.set(payload.finding.id, payload.finding);
|
|
5177
|
+
this.emit("bug.found", payload);
|
|
5178
|
+
}
|
|
5179
|
+
});
|
|
5180
|
+
this.disposers.push(d1);
|
|
5181
|
+
const d2 = this.fleetBus.filter("refactor.plan", (e) => {
|
|
5182
|
+
const payload = e.payload;
|
|
5183
|
+
if (payload?.plan) {
|
|
5184
|
+
this.plans.set(payload.plan.id, payload.plan);
|
|
5185
|
+
this.emit("refactor.plan", payload);
|
|
5186
|
+
}
|
|
5187
|
+
});
|
|
5188
|
+
this.disposers.push(d2);
|
|
5189
|
+
const d3 = this.fleetBus.filter("critic.evaluation", (e) => {
|
|
5190
|
+
const payload = e.payload;
|
|
5191
|
+
if (payload?.evaluation) {
|
|
5192
|
+
this.evaluations.set(payload.evaluation.id, payload.evaluation);
|
|
5193
|
+
this.emit("critic.evaluation", payload);
|
|
5194
|
+
}
|
|
5195
|
+
});
|
|
5196
|
+
this.disposers.push(d3);
|
|
5197
|
+
}
|
|
5198
|
+
roleFromSubagentId(subagentId) {
|
|
5199
|
+
for (const [role, id] of this.subagentIds) {
|
|
5200
|
+
if (id === subagentId) return role;
|
|
5201
|
+
}
|
|
5202
|
+
const match = subagentId.match(/^(bug-hunter|refactor-planner|critic)/);
|
|
5203
|
+
return match?.[1] ?? null;
|
|
5204
|
+
}
|
|
5205
|
+
assembleReport() {
|
|
5206
|
+
const bugList = Array.from(this.bugs.values());
|
|
5207
|
+
const planList = Array.from(this.plans.values());
|
|
5208
|
+
const evalList = Array.from(this.evaluations.values());
|
|
5209
|
+
let disposition = "completed";
|
|
5210
|
+
if (this.cancelled) disposition = "cancelled";
|
|
5211
|
+
const verdictOrder = {
|
|
5212
|
+
approve: 0,
|
|
5213
|
+
needs_revision: 1,
|
|
5214
|
+
reject: 2
|
|
5215
|
+
};
|
|
5216
|
+
const overallVerdict = evalList.reduce(
|
|
5217
|
+
(worst, eval_) => {
|
|
5218
|
+
const w = verdictOrder[worst];
|
|
5219
|
+
const c = verdictOrder[eval_.verdict];
|
|
5220
|
+
return c > w ? eval_.verdict : worst;
|
|
5221
|
+
},
|
|
5222
|
+
"approve"
|
|
5223
|
+
);
|
|
5224
|
+
const summary = this.buildMarkdownSummary(bugList, planList, evalList, overallVerdict, disposition);
|
|
5225
|
+
return {
|
|
5226
|
+
sessionId: this.sessionId,
|
|
5227
|
+
startedAt: this.snapshot.createdAt,
|
|
5228
|
+
completedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
5229
|
+
targetPaths: this.options.targetPaths,
|
|
5230
|
+
disposition,
|
|
5231
|
+
bugs: bugList,
|
|
5232
|
+
refactorPlans: planList,
|
|
5233
|
+
evaluations: evalList,
|
|
5234
|
+
alerts: [...this.alerts],
|
|
5235
|
+
overallVerdict,
|
|
5236
|
+
summary
|
|
5237
|
+
};
|
|
5238
|
+
}
|
|
5239
|
+
buildMarkdownSummary(bugs, plans, evals, overallVerdict, disposition) {
|
|
5240
|
+
const lines = [
|
|
5241
|
+
`## Collaborative Debugging Report \u2014 ${this.sessionId}`,
|
|
5242
|
+
"",
|
|
5243
|
+
`**Target:** ${this.options.targetPaths.join(", ")}`,
|
|
5244
|
+
`**Disposition:** ${disposition.toUpperCase()}`,
|
|
5245
|
+
`**Overall Verdict:** **${overallVerdict.toUpperCase()}**`,
|
|
5246
|
+
""
|
|
5247
|
+
];
|
|
5248
|
+
if (this.alerts.length > 0) {
|
|
5249
|
+
lines.push("### Alerts", "");
|
|
5250
|
+
for (const alert of this.alerts) {
|
|
5251
|
+
lines.push(`- **[${alert.level.toUpperCase()}]** ${alert.role}: ${alert.message}`);
|
|
5252
|
+
}
|
|
5253
|
+
lines.push("");
|
|
5254
|
+
}
|
|
5255
|
+
if (bugs.length > 0) {
|
|
5256
|
+
lines.push("### Bugs Found", "");
|
|
5257
|
+
for (const b of bugs) {
|
|
5258
|
+
lines.push(`- **[${b.severity.toUpperCase()}]** \`${b.location.file}:${b.location.line}\` \u2014 ${b.description}`);
|
|
5259
|
+
}
|
|
5260
|
+
lines.push("");
|
|
5261
|
+
}
|
|
5262
|
+
if (plans.length > 0) {
|
|
5263
|
+
lines.push("### Refactor Plans", "");
|
|
5264
|
+
for (const p of plans) {
|
|
5265
|
+
lines.push(`- **Phase plan** (risk: ${p.riskScore}, ~${p.estimatedChangeCount} changes)`);
|
|
5266
|
+
for (const phase of p.phases) {
|
|
5267
|
+
lines.push(` - Phase ${phase.number}: ${phase.title} [${phase.risk}]`);
|
|
5268
|
+
}
|
|
5269
|
+
}
|
|
5270
|
+
lines.push("");
|
|
5271
|
+
}
|
|
5272
|
+
if (evals.length > 0) {
|
|
5273
|
+
lines.push("### Critic Evaluations", "");
|
|
5274
|
+
for (const e of evals) {
|
|
5275
|
+
lines.push(`- [${e.subjectType}] score=${e.score}/10 \u2014 **${e.verdict.toUpperCase()}**`);
|
|
5276
|
+
for (const c of e.concerns) {
|
|
5277
|
+
if (c.severity === "blocking") lines.push(` - ${c.description}`);
|
|
5278
|
+
}
|
|
5279
|
+
}
|
|
5280
|
+
lines.push("");
|
|
5281
|
+
}
|
|
5282
|
+
return lines.join("\n");
|
|
5283
|
+
}
|
|
5284
|
+
cleanup() {
|
|
5285
|
+
for (const dispose of this.disposers) dispose();
|
|
5286
|
+
this.disposers.length = 0;
|
|
5287
|
+
}
|
|
5288
|
+
};
|
|
4480
5289
|
|
|
4481
5290
|
// src/coordination/director.ts
|
|
4482
5291
|
var FleetSpawnBudgetError = class extends Error {
|
|
4483
5292
|
kind;
|
|
4484
5293
|
limit;
|
|
4485
5294
|
observed;
|
|
4486
|
-
constructor(kind, limit, observed) {
|
|
4487
|
-
|
|
4488
|
-
|
|
4489
|
-
);
|
|
5295
|
+
constructor(kind, limit, observed, message) {
|
|
5296
|
+
const defaultMsg = kind === "max_spawns" ? `Director spawn budget exceeded: tried to spawn #${observed} but maxSpawns is ${limit}` : `Director spawn depth budget exceeded: this director is at depth ${observed} and maxSpawnDepth is ${limit}`;
|
|
5297
|
+
super(message ?? defaultMsg);
|
|
4490
5298
|
this.name = "FleetSpawnBudgetError";
|
|
4491
5299
|
this.kind = kind;
|
|
4492
5300
|
this.limit = limit;
|
|
@@ -4507,6 +5315,20 @@ var FleetCostCapError = class extends Error {
|
|
|
4507
5315
|
this.observed = observed;
|
|
4508
5316
|
}
|
|
4509
5317
|
};
|
|
5318
|
+
var FleetContextOverflowError = class extends Error {
|
|
5319
|
+
kind;
|
|
5320
|
+
limit;
|
|
5321
|
+
observed;
|
|
5322
|
+
constructor(limit, observed) {
|
|
5323
|
+
super(
|
|
5324
|
+
`Leader context overflow: leader has ${observed} tokens in flight (limit: ${limit}). Compact the leader context before spawning more subagents.`
|
|
5325
|
+
);
|
|
5326
|
+
this.name = "FleetContextOverflowError";
|
|
5327
|
+
this.kind = "max_context_load";
|
|
5328
|
+
this.limit = limit;
|
|
5329
|
+
this.observed = observed;
|
|
5330
|
+
}
|
|
5331
|
+
};
|
|
4510
5332
|
var Director = class {
|
|
4511
5333
|
/** Alias for the ICoordinator contract. `id` is retained for backward compatibility. */
|
|
4512
5334
|
get coordinatorId() {
|
|
@@ -4523,6 +5345,25 @@ var Director = class {
|
|
|
4523
5345
|
* injected; otherwise own FleetUsageAggregator.
|
|
4524
5346
|
*/
|
|
4525
5347
|
usage;
|
|
5348
|
+
/**
|
|
5349
|
+
* Update the leader agent's current context pressure (full request tokens:
|
|
5350
|
+
* messages + systemPrompt + toolDefs). The director checks this before every
|
|
5351
|
+
* spawn — if the pressure exceeds `maxLeaderContextLoad * maxContext`,
|
|
5352
|
+
* spawning is refused with a `FleetContextOverflowError`.
|
|
5353
|
+
*
|
|
5354
|
+
* Call this after each leader agent iteration to keep the pressure current.
|
|
5355
|
+
* The compactor's `CompactReport.fullRequestTokensAfter` is a natural source.
|
|
5356
|
+
*/
|
|
5357
|
+
setLeaderContextPressure(tokens) {
|
|
5358
|
+
this.leaderContextPressure = tokens;
|
|
5359
|
+
this.fleetManager?.setLeaderContextPressure(tokens);
|
|
5360
|
+
}
|
|
5361
|
+
/**
|
|
5362
|
+
* Read the leader agent's current context pressure.
|
|
5363
|
+
*/
|
|
5364
|
+
getLeaderContextPressure() {
|
|
5365
|
+
return this.leaderContextPressure;
|
|
5366
|
+
}
|
|
4526
5367
|
/**
|
|
4527
5368
|
* Optional fleet-level policy container. When provided the Director
|
|
4528
5369
|
* delegates spawn budgeting, manifest entries, and checkpointing to it
|
|
@@ -4555,6 +5396,8 @@ var Director = class {
|
|
|
4555
5396
|
subagentBridges = /* @__PURE__ */ new Map();
|
|
4556
5397
|
/** Tracks per-spawn config + assigned task ids for manifest writing. */
|
|
4557
5398
|
manifestEntries = /* @__PURE__ */ new Map();
|
|
5399
|
+
/** Tracks assigned nicknames so the same name is never reused in one fleet. */
|
|
5400
|
+
_usedNicknames = /* @__PURE__ */ new Set();
|
|
4558
5401
|
manifestPath;
|
|
4559
5402
|
roster;
|
|
4560
5403
|
directorPreamble;
|
|
@@ -4606,6 +5449,23 @@ var Director = class {
|
|
|
4606
5449
|
taskCompletedListener = null;
|
|
4607
5450
|
/** Optional LLM classifier for smart dispatch. Passed from options. */
|
|
4608
5451
|
dispatchClassifier;
|
|
5452
|
+
/** Leader agent's current context pressure (full request tokens). */
|
|
5453
|
+
leaderContextPressure = 0;
|
|
5454
|
+
/** Maximum context load fraction before spawn is refused. */
|
|
5455
|
+
maxLeaderContextLoad;
|
|
5456
|
+
/** Provider's max context window in tokens. */
|
|
5457
|
+
maxContext;
|
|
5458
|
+
/**
|
|
5459
|
+
* When set by `workComplete()`, the director stops dispatching new tasks
|
|
5460
|
+
* and terminates all running subagents. Used when the director's LLM decides
|
|
5461
|
+
* the goal is satisfied and no further spawns are needed — prevents the
|
|
5462
|
+
* coordinator from keeping workers alive for tasks that will never arrive.
|
|
5463
|
+
*/
|
|
5464
|
+
workCompleteFlag = false;
|
|
5465
|
+
/** Pending /btw notes stashed by the leader agent (see setLeaderBtwNote). */
|
|
5466
|
+
_leaderBtwNotes = [];
|
|
5467
|
+
/** Active collab sessions tracked by sessionId (see spawnCollab). */
|
|
5468
|
+
_activeCollabSessions = /* @__PURE__ */ new Map();
|
|
4609
5469
|
constructor(opts) {
|
|
4610
5470
|
this.id = opts.config.coordinatorId || randomUUID();
|
|
4611
5471
|
this.manifestPath = opts.manifestPath;
|
|
@@ -4621,6 +5481,8 @@ var Director = class {
|
|
|
4621
5481
|
this.dispatchClassifier = opts.dispatchClassifier;
|
|
4622
5482
|
this.maxFleetCostUsd = opts.directorBudget?.maxCostUsd ?? Number.POSITIVE_INFINITY;
|
|
4623
5483
|
this.maxBudgetExtensions = opts.maxBudgetExtensions ?? 5;
|
|
5484
|
+
this.maxLeaderContextLoad = opts.maxLeaderContextLoad ?? 0.85;
|
|
5485
|
+
this.maxContext = opts.maxContext ?? 128e3;
|
|
4624
5486
|
this.sessionsRoot = opts.sessionsRoot;
|
|
4625
5487
|
this.directorRunId = opts.directorRunId ?? this.id;
|
|
4626
5488
|
this.stateCheckpoint = opts.stateCheckpointPath ? new DirectorStateCheckpoint(opts.stateCheckpointPath, {
|
|
@@ -4632,7 +5494,7 @@ var Director = class {
|
|
|
4632
5494
|
}, opts.checkpointDebounceMs ?? 250) : null;
|
|
4633
5495
|
this.fleetManager = opts.fleetManager;
|
|
4634
5496
|
if (this.sharedScratchpadPath) {
|
|
4635
|
-
void
|
|
5497
|
+
void fsp5.mkdir(this.sharedScratchpadPath, { recursive: true }).catch(
|
|
4636
5498
|
(err) => this.logShutdownError("shared_scratchpad_mkdir", err)
|
|
4637
5499
|
);
|
|
4638
5500
|
}
|
|
@@ -4648,7 +5510,7 @@ var Director = class {
|
|
|
4648
5510
|
this.fleet = new FleetBus();
|
|
4649
5511
|
this.usage = new FleetUsageAggregator(
|
|
4650
5512
|
this.fleet,
|
|
4651
|
-
(
|
|
5513
|
+
(_id, provider, model) => {
|
|
4652
5514
|
if (provider && model) return this.priceLookups.get(`${provider}/${model}`);
|
|
4653
5515
|
return void 0;
|
|
4654
5516
|
},
|
|
@@ -4710,6 +5572,9 @@ var Director = class {
|
|
|
4710
5572
|
});
|
|
4711
5573
|
this.fleet.filter("budget.threshold_reached", (e) => {
|
|
4712
5574
|
const payload = e.payload;
|
|
5575
|
+
if (e.subagentId.startsWith("bug-hunter-") || e.subagentId.startsWith("refactor-planner-") || e.subagentId.startsWith("critic-")) {
|
|
5576
|
+
return;
|
|
5577
|
+
}
|
|
4713
5578
|
if (payload.kind === "timeout") {
|
|
4714
5579
|
const progress = progressBySubagent.get(e.subagentId) ?? 0;
|
|
4715
5580
|
const lastProgress = lastTimeoutProgress.get(e.subagentId) ?? -1;
|
|
@@ -4788,6 +5653,107 @@ var Director = class {
|
|
|
4788
5653
|
extensionsFor(subagentId) {
|
|
4789
5654
|
return this.extendTotals.get(subagentId) ?? 0;
|
|
4790
5655
|
}
|
|
5656
|
+
/**
|
|
5657
|
+
* Signal that the director's work is done. Once called:
|
|
5658
|
+
* - `spawn()` throws `FleetSpawnBudgetError('max_spawns', …)` — no new
|
|
5659
|
+
* subagents can be created
|
|
5660
|
+
* - Running subagents are NOT forcibly stopped — they finish naturally,
|
|
5661
|
+
* but no new tasks are dispatched to them
|
|
5662
|
+
*
|
|
5663
|
+
* This lets the director LLM say "I'm satisfied with the results, stop
|
|
5664
|
+
* spawning and wind down" — without killing in-flight work mid-execution.
|
|
5665
|
+
* Call `terminateAll()` separately if you need immediate teardown.
|
|
5666
|
+
*
|
|
5667
|
+
* Idempotent — calling twice is a no-op.
|
|
5668
|
+
*/
|
|
5669
|
+
workComplete() {
|
|
5670
|
+
this.workCompleteFlag = true;
|
|
5671
|
+
this.fleet.emit({ subagentId: this.id, ts: Date.now(), type: "director.work_complete", payload: {} });
|
|
5672
|
+
}
|
|
5673
|
+
/** Returns true if `workComplete()` has been called on this director. */
|
|
5674
|
+
isWorkComplete() {
|
|
5675
|
+
return this.workCompleteFlag;
|
|
5676
|
+
}
|
|
5677
|
+
/**
|
|
5678
|
+
* Stashes a /btw note on the leader agent's context. The leader's agent loop
|
|
5679
|
+
* calls `consumeBtwNotes()` at each iteration boundary and folds pending notes
|
|
5680
|
+
* into the conversation as a visible block — no abort, no restart, just a
|
|
5681
|
+
* "by the way" nudge the model picks up on its next turn.
|
|
5682
|
+
*
|
|
5683
|
+
* This is the entry point for the host (CLI, TUI) to inject /btw notes
|
|
5684
|
+
* programmatically without going through the slash-command path.
|
|
5685
|
+
*/
|
|
5686
|
+
setLeaderBtwNote(note) {
|
|
5687
|
+
const trimmed = note.trim();
|
|
5688
|
+
if (!trimmed) return this._leaderBtwNotes.length;
|
|
5689
|
+
this._leaderBtwNotes = [...this._leaderBtwNotes, trimmed].slice(-20);
|
|
5690
|
+
return this._leaderBtwNotes.length;
|
|
5691
|
+
}
|
|
5692
|
+
/**
|
|
5693
|
+
* Read and clear all pending /btw notes the leader has stashed.
|
|
5694
|
+
* Returns them in FIFO order (empty array when none).
|
|
5695
|
+
*
|
|
5696
|
+
* Called by CollabSession when a budget threshold event fires so the
|
|
5697
|
+
* Director can inspect accumulated /btw notes before deciding whether
|
|
5698
|
+
* to cancel the collab session or let it continue.
|
|
5699
|
+
*/
|
|
5700
|
+
getLeaderBtwNotes() {
|
|
5701
|
+
const notes = this._leaderBtwNotes;
|
|
5702
|
+
this._leaderBtwNotes = [];
|
|
5703
|
+
return notes;
|
|
5704
|
+
}
|
|
5705
|
+
/**
|
|
5706
|
+
* Peek at pending /btw notes without consuming them.
|
|
5707
|
+
* Useful for UI to show "N pending notes" without clearing them.
|
|
5708
|
+
*/
|
|
5709
|
+
peekLeaderBtwNotes() {
|
|
5710
|
+
return [...this._leaderBtwNotes];
|
|
5711
|
+
}
|
|
5712
|
+
/**
|
|
5713
|
+
* Drain (read + clear) all /btw notes in one call.
|
|
5714
|
+
* Alias for getLeaderBtwNotes() — kept for symmetry with consumeBtwNotes()
|
|
5715
|
+
* in the agent's btw.ts. The Director calls this at the point where it
|
|
5716
|
+
* makes a cancellation decision, not on every budget event.
|
|
5717
|
+
*/
|
|
5718
|
+
drainLeaderBtwNotes() {
|
|
5719
|
+
return this.getLeaderBtwNotes();
|
|
5720
|
+
}
|
|
5721
|
+
/**
|
|
5722
|
+
* Cancel an active collab session by its id.
|
|
5723
|
+
* Emits `director.cancel_collab` on the FleetBus so the session's agents
|
|
5724
|
+
* finish early with a 'cancelled' disposition.
|
|
5725
|
+
*
|
|
5726
|
+
* Returns silently if the session id is not tracked or already settled.
|
|
5727
|
+
* The CollabDebugReport will reflect 'cancelled' disposition when awaited.
|
|
5728
|
+
*/
|
|
5729
|
+
cancelCollabSession(sessionId, reason = "Director cancelled") {
|
|
5730
|
+
const session = this._activeCollabSessions.get(sessionId);
|
|
5731
|
+
if (!session || session.isCancelled()) return;
|
|
5732
|
+
session.cancel(reason);
|
|
5733
|
+
for (const [_role, subagentId] of session.getSubagentIds()) {
|
|
5734
|
+
this.coordinator.stop(subagentId).catch(() => {
|
|
5735
|
+
});
|
|
5736
|
+
}
|
|
5737
|
+
}
|
|
5738
|
+
/**
|
|
5739
|
+
* Subscribe a callback to be notified whenever a collab session raises
|
|
5740
|
+
* an alert (warning level). The callback receives the full DirectorAlert
|
|
5741
|
+
* payload so the host (CLI, TUI) can display it to the user.
|
|
5742
|
+
* Returns an unsubscribe function.
|
|
5743
|
+
*/
|
|
5744
|
+
onCollabAlert(handler) {
|
|
5745
|
+
return this.fleet.filter("collab.warning", (e) => {
|
|
5746
|
+
handler(e.payload);
|
|
5747
|
+
});
|
|
5748
|
+
}
|
|
5749
|
+
/**
|
|
5750
|
+
* Returns all active (not yet settled) collab session ids.
|
|
5751
|
+
* Useful for the TUI to render a "N active sessions" badge and for
|
|
5752
|
+
* the host to know what can be cancelled.
|
|
5753
|
+
*/
|
|
5754
|
+
activeCollabSessions() {
|
|
5755
|
+
return Array.from(this._activeCollabSessions.keys());
|
|
5756
|
+
}
|
|
4791
5757
|
/** Best-effort session-writer append. Swallows failures — the director
|
|
4792
5758
|
* must not break a fleet run because the session JSONL handle closed. */
|
|
4793
5759
|
async appendSessionEvent(event) {
|
|
@@ -4826,12 +5792,21 @@ var Director = class {
|
|
|
4826
5792
|
* it the `cost` column in `usage.snapshot()` stays at 0.
|
|
4827
5793
|
*/
|
|
4828
5794
|
async spawn(config, priceLookup) {
|
|
5795
|
+
if (this.workCompleteFlag) {
|
|
5796
|
+
throw new FleetSpawnBudgetError(
|
|
5797
|
+
"max_spawns",
|
|
5798
|
+
this.maxSpawns,
|
|
5799
|
+
this.spawnCount + 1,
|
|
5800
|
+
"workComplete() has been called \u2014 director closed further spawning"
|
|
5801
|
+
);
|
|
5802
|
+
}
|
|
4829
5803
|
if (this.fleetManager) {
|
|
4830
5804
|
const rejection = this.fleetManager.canSpawn(config);
|
|
4831
5805
|
if (rejection) {
|
|
4832
5806
|
if (rejection.kind === "max_spawn_depth") throw new FleetSpawnBudgetError("max_spawn_depth", rejection.limit, rejection.observed);
|
|
4833
5807
|
if (rejection.kind === "max_spawns") throw new FleetSpawnBudgetError("max_spawns", rejection.limit, rejection.observed);
|
|
4834
5808
|
if (rejection.kind === "max_cost_usd") throw new FleetCostCapError(rejection.limit, rejection.observed);
|
|
5809
|
+
if (rejection.kind === "max_context_load") throw new FleetContextOverflowError(rejection.limit, rejection.observed);
|
|
4835
5810
|
}
|
|
4836
5811
|
} else {
|
|
4837
5812
|
if (this.spawnDepth >= this.maxSpawnDepth) {
|
|
@@ -4846,11 +5821,29 @@ var Director = class {
|
|
|
4846
5821
|
throw new FleetCostCapError(this.maxFleetCostUsd, totalCost);
|
|
4847
5822
|
}
|
|
4848
5823
|
}
|
|
5824
|
+
if (this.maxLeaderContextLoad < 1) {
|
|
5825
|
+
const threshold = this.maxContext * this.maxLeaderContextLoad;
|
|
5826
|
+
if (this.leaderContextPressure >= threshold) {
|
|
5827
|
+
throw new FleetContextOverflowError(threshold, this.leaderContextPressure);
|
|
5828
|
+
}
|
|
5829
|
+
}
|
|
4849
5830
|
}
|
|
4850
5831
|
let result;
|
|
5832
|
+
const needsNickname = config.name === config.role || !config.name || config.name === "subagent" || config.name === "adhoc";
|
|
5833
|
+
if (needsNickname) {
|
|
5834
|
+
const role = config.role ?? "subagent";
|
|
5835
|
+
if (this.fleetManager) {
|
|
5836
|
+
this.fleetManager.assignNicknameAndRecord("", config, priceLookup);
|
|
5837
|
+
} else {
|
|
5838
|
+
config.name = assignNickname(role, this._usedNicknames);
|
|
5839
|
+
this._usedNicknames.add(config.name.split(" ")[0].toLowerCase().replace(/[^a-z0-9-]/g, "-"));
|
|
5840
|
+
}
|
|
5841
|
+
}
|
|
4851
5842
|
result = await this.coordinator.spawn(config);
|
|
4852
5843
|
if (this.fleetManager) {
|
|
4853
|
-
|
|
5844
|
+
if (!needsNickname) {
|
|
5845
|
+
this.fleetManager.recordSpawn(result.subagentId, config, priceLookup);
|
|
5846
|
+
}
|
|
4854
5847
|
} else {
|
|
4855
5848
|
this.spawnCount += 1;
|
|
4856
5849
|
this.subagentMeta.set(result.subagentId, {
|
|
@@ -5006,7 +5999,7 @@ var Director = class {
|
|
|
5006
5999
|
})),
|
|
5007
6000
|
usage: this.usage.snapshot()
|
|
5008
6001
|
};
|
|
5009
|
-
await
|
|
6002
|
+
await fsp5.mkdir(path4.dirname(this.manifestPath), { recursive: true });
|
|
5010
6003
|
await atomicWrite(this.manifestPath, JSON.stringify(manifest, null, 2), { mode: 384 });
|
|
5011
6004
|
return this.manifestPath;
|
|
5012
6005
|
}
|
|
@@ -5061,6 +6054,24 @@ var Director = class {
|
|
|
5061
6054
|
*/
|
|
5062
6055
|
async assign(task) {
|
|
5063
6056
|
const taskWithId = task.id ? task : { ...task, id: randomUUID() };
|
|
6057
|
+
if (this.workCompleteFlag) {
|
|
6058
|
+
const synthetic = {
|
|
6059
|
+
subagentId: taskWithId.subagentId ?? "unassigned",
|
|
6060
|
+
taskId: taskWithId.id,
|
|
6061
|
+
status: "stopped",
|
|
6062
|
+
error: { kind: "aborted_by_parent", message: "Director called workComplete() \u2014 no further tasks will run", retryable: false },
|
|
6063
|
+
iterations: 0,
|
|
6064
|
+
toolCalls: 0,
|
|
6065
|
+
durationMs: 0
|
|
6066
|
+
};
|
|
6067
|
+
this.completed.set(taskWithId.id, synthetic);
|
|
6068
|
+
const waiter = this.taskWaiters.get(taskWithId.id);
|
|
6069
|
+
if (waiter) {
|
|
6070
|
+
waiter.resolve(synthetic);
|
|
6071
|
+
this.taskWaiters.delete(taskWithId.id);
|
|
6072
|
+
}
|
|
6073
|
+
return taskWithId.id;
|
|
6074
|
+
}
|
|
5064
6075
|
if (task.subagentId) {
|
|
5065
6076
|
if (this.fleetManager) {
|
|
5066
6077
|
this.fleetManager.addTaskToSubagent(task.subagentId, taskWithId.id);
|
|
@@ -5175,7 +6186,7 @@ var Director = class {
|
|
|
5175
6186
|
const filePath = path4.join(this.sessionsRoot, this.directorRunId, `${subagentId}.jsonl`);
|
|
5176
6187
|
let raw;
|
|
5177
6188
|
try {
|
|
5178
|
-
raw = await
|
|
6189
|
+
raw = await fsp5.readFile(filePath, "utf8");
|
|
5179
6190
|
} catch {
|
|
5180
6191
|
return null;
|
|
5181
6192
|
}
|
|
@@ -5283,10 +6294,14 @@ var Director = class {
|
|
|
5283
6294
|
makeAskTool(this),
|
|
5284
6295
|
makeRollUpTool(this),
|
|
5285
6296
|
makeTerminateTool(this),
|
|
6297
|
+
makeTerminateAllTool(this),
|
|
5286
6298
|
makeFleetStatusTool(this),
|
|
5287
6299
|
makeFleetUsageTool(this),
|
|
5288
6300
|
makeFleetSessionTool(this),
|
|
5289
|
-
makeFleetHealthTool(this)
|
|
6301
|
+
makeFleetHealthTool(this),
|
|
6302
|
+
makeCollabDebugTool(this),
|
|
6303
|
+
makeFleetEmitTool(this),
|
|
6304
|
+
makeWorkCompleteTool(this)
|
|
5290
6305
|
];
|
|
5291
6306
|
return t;
|
|
5292
6307
|
}
|
|
@@ -5298,6 +6313,25 @@ var Director = class {
|
|
|
5298
6313
|
async acquireCheckpointLock() {
|
|
5299
6314
|
return this.stateCheckpoint ? this.stateCheckpoint.acquireLock() : true;
|
|
5300
6315
|
}
|
|
6316
|
+
/**
|
|
6317
|
+
* Start a collaborative debugging session: BugHunter, RefactorPlanner,
|
|
6318
|
+
* and Critic run in parallel on the same target files, with findings
|
|
6319
|
+
* flowing through the FleetBus (bug.found → refactor.plan →
|
|
6320
|
+
* critic.evaluation). Returns a structured CollabDebugReport when all
|
|
6321
|
+
* three agents complete or the session times out.
|
|
6322
|
+
*/
|
|
6323
|
+
async spawnCollab(options) {
|
|
6324
|
+
const session = new CollabSession(this, this.fleet, {
|
|
6325
|
+
...options,
|
|
6326
|
+
onBudgetWarning: (alert) => {
|
|
6327
|
+
return options.onBudgetWarning?.(alert) ?? "ignore";
|
|
6328
|
+
}
|
|
6329
|
+
});
|
|
6330
|
+
this._activeCollabSessions.set(session.sessionId, session);
|
|
6331
|
+
session.on("session.done", () => this._activeCollabSessions.delete(session.sessionId));
|
|
6332
|
+
session.on("session.error", () => this._activeCollabSessions.delete(session.sessionId));
|
|
6333
|
+
return session.start();
|
|
6334
|
+
}
|
|
5301
6335
|
/**
|
|
5302
6336
|
* Resume from a prior checkpoint snapshot (loaded via
|
|
5303
6337
|
* `loadDirectorState()`). Re-attach to the fleet mid-flight so
|
|
@@ -5578,7 +6612,7 @@ async function readSubagentPartial(opts, subagentId) {
|
|
|
5578
6612
|
candidates.push(path4.join(opts.sessionsRoot, opts.directorRunId, `${subagentId}.jsonl`));
|
|
5579
6613
|
} else {
|
|
5580
6614
|
try {
|
|
5581
|
-
const entries = await
|
|
6615
|
+
const entries = await fsp5.readdir(opts.sessionsRoot, { withFileTypes: true });
|
|
5582
6616
|
for (const entry of entries) {
|
|
5583
6617
|
if (entry.isDirectory()) {
|
|
5584
6618
|
candidates.push(path4.join(opts.sessionsRoot, entry.name, `${subagentId}.jsonl`));
|
|
@@ -5591,7 +6625,7 @@ async function readSubagentPartial(opts, subagentId) {
|
|
|
5591
6625
|
for (const file of candidates) {
|
|
5592
6626
|
let raw;
|
|
5593
6627
|
try {
|
|
5594
|
-
raw = await
|
|
6628
|
+
raw = await fsp5.readFile(file, "utf8");
|
|
5595
6629
|
} catch {
|
|
5596
6630
|
continue;
|
|
5597
6631
|
}
|
|
@@ -5887,9 +6921,11 @@ function isEmptyMessage(msg) {
|
|
|
5887
6921
|
var DefaultSessionStore = class {
|
|
5888
6922
|
dir;
|
|
5889
6923
|
events;
|
|
6924
|
+
secretScrubber;
|
|
5890
6925
|
constructor(opts) {
|
|
5891
6926
|
this.dir = opts.dir;
|
|
5892
6927
|
this.events = opts.events;
|
|
6928
|
+
this.secretScrubber = opts.secretScrubber;
|
|
5893
6929
|
}
|
|
5894
6930
|
/** Join session ID to its absolute path within the store directory. */
|
|
5895
6931
|
sessionPath(id, ext) {
|
|
@@ -5906,7 +6942,7 @@ var DefaultSessionStore = class {
|
|
|
5906
6942
|
const file = path4.join(shardDir, `${id}.jsonl`);
|
|
5907
6943
|
let handle;
|
|
5908
6944
|
try {
|
|
5909
|
-
handle = await
|
|
6945
|
+
handle = await fsp5.open(file, "a", 384);
|
|
5910
6946
|
} catch (err) {
|
|
5911
6947
|
throw new Error(
|
|
5912
6948
|
`Failed to open session file: ${err instanceof Error ? err.message : String(err)}`,
|
|
@@ -5914,7 +6950,11 @@ var DefaultSessionStore = class {
|
|
|
5914
6950
|
);
|
|
5915
6951
|
}
|
|
5916
6952
|
try {
|
|
5917
|
-
return new FileSessionWriter(id, handle, startedAt, meta, this.events, {
|
|
6953
|
+
return new FileSessionWriter(id, handle, startedAt, meta, this.events, {
|
|
6954
|
+
dir: shardDir,
|
|
6955
|
+
filePath: file,
|
|
6956
|
+
secretScrubber: this.secretScrubber
|
|
6957
|
+
});
|
|
5918
6958
|
} catch (err) {
|
|
5919
6959
|
await handle.close().catch(() => {
|
|
5920
6960
|
});
|
|
@@ -5926,7 +6966,7 @@ var DefaultSessionStore = class {
|
|
|
5926
6966
|
const data = await this.load(id);
|
|
5927
6967
|
let handle;
|
|
5928
6968
|
try {
|
|
5929
|
-
handle = await
|
|
6969
|
+
handle = await fsp5.open(file, "a", 384);
|
|
5930
6970
|
} catch (err) {
|
|
5931
6971
|
throw new Error(
|
|
5932
6972
|
`Failed to open session "${id}" for append: ${err instanceof Error ? err.message : String(err)}`,
|
|
@@ -5944,7 +6984,7 @@ var DefaultSessionStore = class {
|
|
|
5944
6984
|
provider: data.metadata.provider
|
|
5945
6985
|
},
|
|
5946
6986
|
this.events,
|
|
5947
|
-
{ resumed: true, dir: this.dir, filePath: file }
|
|
6987
|
+
{ resumed: true, dir: this.dir, filePath: file, secretScrubber: this.secretScrubber }
|
|
5948
6988
|
);
|
|
5949
6989
|
return { writer, data };
|
|
5950
6990
|
} catch (err) {
|
|
@@ -5955,7 +6995,7 @@ var DefaultSessionStore = class {
|
|
|
5955
6995
|
}
|
|
5956
6996
|
async load(id) {
|
|
5957
6997
|
const file = this.sessionPath(id, ".jsonl");
|
|
5958
|
-
const raw = await
|
|
6998
|
+
const raw = await fsp5.readFile(file, "utf8");
|
|
5959
6999
|
const lines = raw.split("\n").filter((l) => l.trim());
|
|
5960
7000
|
const events = [];
|
|
5961
7001
|
for (const line of lines) {
|
|
@@ -5990,7 +7030,7 @@ var DefaultSessionStore = class {
|
|
|
5990
7030
|
/** Recursively collect all session IDs from shard subdirectories. */
|
|
5991
7031
|
async collectSessionIds(dir) {
|
|
5992
7032
|
const ids = [];
|
|
5993
|
-
const entries = await
|
|
7033
|
+
const entries = await fsp5.readdir(dir, { withFileTypes: true });
|
|
5994
7034
|
for (const entry of entries) {
|
|
5995
7035
|
const full = path4.join(dir, entry.name);
|
|
5996
7036
|
if (entry.isDirectory()) {
|
|
@@ -6004,11 +7044,11 @@ var DefaultSessionStore = class {
|
|
|
6004
7044
|
async summaryFor(id) {
|
|
6005
7045
|
const manifest = this.sessionPath(id, ".summary.json");
|
|
6006
7046
|
try {
|
|
6007
|
-
const raw = await
|
|
7047
|
+
const raw = await fsp5.readFile(manifest, "utf8");
|
|
6008
7048
|
return JSON.parse(raw);
|
|
6009
7049
|
} catch {
|
|
6010
7050
|
const full = this.sessionPath(id, ".jsonl");
|
|
6011
|
-
const stat3 = await
|
|
7051
|
+
const stat3 = await fsp5.stat(full);
|
|
6012
7052
|
const summary = await this.summarize(id, stat3.mtime.toISOString());
|
|
6013
7053
|
await atomicWrite(manifest, JSON.stringify(summary), { mode: 384 }).catch((err) => {
|
|
6014
7054
|
console.warn(
|
|
@@ -6020,8 +7060,8 @@ var DefaultSessionStore = class {
|
|
|
6020
7060
|
}
|
|
6021
7061
|
}
|
|
6022
7062
|
async delete(id) {
|
|
6023
|
-
await
|
|
6024
|
-
await
|
|
7063
|
+
await fsp5.unlink(this.sessionPath(id, ".jsonl"));
|
|
7064
|
+
await fsp5.unlink(this.sessionPath(id, ".summary.json")).catch(() => void 0);
|
|
6025
7065
|
}
|
|
6026
7066
|
async clearHistory(id) {
|
|
6027
7067
|
await this.ensureShardDir(id);
|
|
@@ -6035,8 +7075,8 @@ var DefaultSessionStore = class {
|
|
|
6035
7075
|
provider: "unknown"
|
|
6036
7076
|
})}
|
|
6037
7077
|
`;
|
|
6038
|
-
await
|
|
6039
|
-
await
|
|
7078
|
+
await fsp5.writeFile(file, record, "utf8");
|
|
7079
|
+
await fsp5.unlink(meta).catch(() => void 0);
|
|
6040
7080
|
}
|
|
6041
7081
|
async summarize(id, mtime) {
|
|
6042
7082
|
try {
|
|
@@ -6150,6 +7190,7 @@ var FileSessionWriter = class {
|
|
|
6150
7190
|
this.resumed = opts.resumed ?? false;
|
|
6151
7191
|
this.manifestFile = opts.dir ? path4.join(opts.dir, `${id}.summary.json`) : "";
|
|
6152
7192
|
this.filePath = opts.filePath ?? "";
|
|
7193
|
+
this.secretScrubber = opts.secretScrubber;
|
|
6153
7194
|
this.summary = {
|
|
6154
7195
|
id,
|
|
6155
7196
|
title: "(empty session)",
|
|
@@ -6178,7 +7219,29 @@ var FileSessionWriter = class {
|
|
|
6178
7219
|
resumed;
|
|
6179
7220
|
appendFailCount = 0;
|
|
6180
7221
|
lastAppendWarnAt = 0;
|
|
6181
|
-
|
|
7222
|
+
secretScrubber;
|
|
7223
|
+
/**
|
|
7224
|
+
* Scrub secrets out of conversation-turn events before they are observed
|
|
7225
|
+
* for the summary, written to the JSONL log, or surfaced on resume. Only
|
|
7226
|
+
* `user_input` / `llm_response` carry free-form user/model text; other event
|
|
7227
|
+
* types either have no secret-bearing content or are already scrubbed
|
|
7228
|
+
* upstream (tool results). Returns the event unchanged when no scrubber is
|
|
7229
|
+
* configured.
|
|
7230
|
+
*/
|
|
7231
|
+
scrubEvent(event) {
|
|
7232
|
+
const s = this.secretScrubber;
|
|
7233
|
+
if (!s) return event;
|
|
7234
|
+
if (event.type === "user_input") {
|
|
7235
|
+
return {
|
|
7236
|
+
...event,
|
|
7237
|
+
content: typeof event.content === "string" ? s.scrub(event.content) : s.scrubObject(event.content)
|
|
7238
|
+
};
|
|
7239
|
+
}
|
|
7240
|
+
if (event.type === "llm_response") {
|
|
7241
|
+
return { ...event, content: s.scrubObject(event.content) };
|
|
7242
|
+
}
|
|
7243
|
+
return event;
|
|
7244
|
+
}
|
|
6182
7245
|
pendingFileSnapshots = [];
|
|
6183
7246
|
/** Tracks open tool_use IDs during the current run to serialize on close for resume. */
|
|
6184
7247
|
openToolUses = /* @__PURE__ */ new Set();
|
|
@@ -6199,7 +7262,7 @@ var FileSessionWriter = class {
|
|
|
6199
7262
|
`;
|
|
6200
7263
|
try {
|
|
6201
7264
|
if (this.filePath) {
|
|
6202
|
-
await
|
|
7265
|
+
await fsp5.writeFile(this.filePath, record, { flag: "a", mode: 384 });
|
|
6203
7266
|
}
|
|
6204
7267
|
} catch {
|
|
6205
7268
|
}
|
|
@@ -6210,9 +7273,10 @@ var FileSessionWriter = class {
|
|
|
6210
7273
|
this.initDone = true;
|
|
6211
7274
|
await this.writeSessionStartLazy();
|
|
6212
7275
|
}
|
|
6213
|
-
this.
|
|
7276
|
+
const scrubbed = this.scrubEvent(event);
|
|
7277
|
+
this.observeForSummary(scrubbed);
|
|
6214
7278
|
try {
|
|
6215
|
-
await this.handle.appendFile(`${JSON.stringify(
|
|
7279
|
+
await this.handle.appendFile(`${JSON.stringify(scrubbed)}
|
|
6216
7280
|
`, "utf8");
|
|
6217
7281
|
} catch (err) {
|
|
6218
7282
|
this.appendFailCount++;
|
|
@@ -6268,7 +7332,6 @@ var FileSessionWriter = class {
|
|
|
6268
7332
|
await this.writeFileSnapshot(promptIndex, [...this.pendingFileSnapshots]);
|
|
6269
7333
|
this.pendingFileSnapshots = [];
|
|
6270
7334
|
}
|
|
6271
|
-
this.promptIndex = promptIndex + 1;
|
|
6272
7335
|
await this.append({
|
|
6273
7336
|
type: "checkpoint",
|
|
6274
7337
|
ts: (/* @__PURE__ */ new Date()).toISOString(),
|
|
@@ -6292,7 +7355,7 @@ var FileSessionWriter = class {
|
|
|
6292
7355
|
}
|
|
6293
7356
|
async truncateToCheckpoint(targetPromptIndex) {
|
|
6294
7357
|
if (!this.filePath) return 0;
|
|
6295
|
-
const raw = await
|
|
7358
|
+
const raw = await fsp5.readFile(this.filePath, "utf8");
|
|
6296
7359
|
const lines = raw.split("\n");
|
|
6297
7360
|
const kept = [];
|
|
6298
7361
|
let removedCount = 0;
|
|
@@ -6330,13 +7393,13 @@ var FileSessionWriter = class {
|
|
|
6330
7393
|
}
|
|
6331
7394
|
const truncated = kept.join("\n");
|
|
6332
7395
|
const tmpPath = `${this.filePath}.rewind.tmp`;
|
|
6333
|
-
await
|
|
7396
|
+
await fsp5.writeFile(tmpPath, truncated + "\n", "utf8");
|
|
6334
7397
|
try {
|
|
6335
7398
|
await this.handle.close();
|
|
6336
|
-
await
|
|
6337
|
-
this.handle = await
|
|
7399
|
+
await fsp5.rename(tmpPath, this.filePath);
|
|
7400
|
+
this.handle = await fsp5.open(this.filePath, "a", 384);
|
|
6338
7401
|
} catch (err) {
|
|
6339
|
-
await
|
|
7402
|
+
await fsp5.unlink(tmpPath).catch(() => void 0);
|
|
6340
7403
|
throw err;
|
|
6341
7404
|
}
|
|
6342
7405
|
await this.append({
|
|
@@ -6362,7 +7425,39 @@ var FileSessionWriter = class {
|
|
|
6362
7425
|
provider: this.meta.provider ?? "unknown"
|
|
6363
7426
|
})}
|
|
6364
7427
|
`;
|
|
6365
|
-
await
|
|
7428
|
+
await fsp5.writeFile(this.filePath, record, "utf8");
|
|
7429
|
+
}
|
|
7430
|
+
/**
|
|
7431
|
+
* Idea #1 — write an in-flight marker. The agent loop should call
|
|
7432
|
+
* this at the start of each long-running operation; a matching
|
|
7433
|
+
* `clearInFlightMarker` follows on clean exit. A stale marker
|
|
7434
|
+
* (no end) is what `SessionRecovery.detectStale` looks for.
|
|
7435
|
+
*/
|
|
7436
|
+
async writeInFlightMarker(context) {
|
|
7437
|
+
if (!context || context.length > 500) {
|
|
7438
|
+
throw new Error("In-flight context must be 1..500 chars");
|
|
7439
|
+
}
|
|
7440
|
+
await this.append({
|
|
7441
|
+
type: "in_flight_start",
|
|
7442
|
+
ts: (/* @__PURE__ */ new Date()).toISOString(),
|
|
7443
|
+
context
|
|
7444
|
+
});
|
|
7445
|
+
this.events?.emit("in_flight.started", { context, ts: (/* @__PURE__ */ new Date()).toISOString() });
|
|
7446
|
+
}
|
|
7447
|
+
/**
|
|
7448
|
+
* Idea #1 — close the in-flight marker. Idempotent in spirit
|
|
7449
|
+
* (you can call it after a successful iteration even if you
|
|
7450
|
+
* didn't open one this round) — but the session log records
|
|
7451
|
+
* every call so postmortem tooling can see "the agent finished
|
|
7452
|
+
* cleanly X times, then died without finishing Y".
|
|
7453
|
+
*/
|
|
7454
|
+
async clearInFlightMarker(reason) {
|
|
7455
|
+
await this.append({
|
|
7456
|
+
type: "in_flight_end",
|
|
7457
|
+
ts: (/* @__PURE__ */ new Date()).toISOString(),
|
|
7458
|
+
reason
|
|
7459
|
+
});
|
|
7460
|
+
this.events?.emit("in_flight.ended", { reason, ts: (/* @__PURE__ */ new Date()).toISOString() });
|
|
6366
7461
|
}
|
|
6367
7462
|
};
|
|
6368
7463
|
function userInputTitle(content) {
|
|
@@ -6464,7 +7559,6 @@ var FleetManager = class {
|
|
|
6464
7559
|
/** Usage rollup across all subagents. */
|
|
6465
7560
|
usage;
|
|
6466
7561
|
manifestPath;
|
|
6467
|
-
sessionsRoot;
|
|
6468
7562
|
directorRunId;
|
|
6469
7563
|
/** Spawn cap (lifetime total). Infinity means unlimited. */
|
|
6470
7564
|
maxSpawns;
|
|
@@ -6488,11 +7582,18 @@ var FleetManager = class {
|
|
|
6488
7582
|
pendingTasks = /* @__PURE__ */ new Map();
|
|
6489
7583
|
subagentMeta = /* @__PURE__ */ new Map();
|
|
6490
7584
|
priceLookups = /* @__PURE__ */ new Map();
|
|
7585
|
+
/** Tracks which nickname keys are already assigned — prevents collisions. */
|
|
7586
|
+
_usedNicknames = /* @__PURE__ */ new Set();
|
|
6491
7587
|
/** The coordinator (wired via setCoordinator by Director after construction). */
|
|
6492
7588
|
coordinator = null;
|
|
7589
|
+
/** Leader agent's current context pressure (full request tokens). */
|
|
7590
|
+
leaderContextPressure = 0;
|
|
7591
|
+
/** Maximum context load fraction before spawn is refused. */
|
|
7592
|
+
maxLeaderContextLoad;
|
|
7593
|
+
/** Provider's max context window in tokens. */
|
|
7594
|
+
maxContext;
|
|
6493
7595
|
constructor(opts = {}) {
|
|
6494
7596
|
this.manifestPath = opts.manifestPath;
|
|
6495
|
-
this.sessionsRoot = opts.sessionsRoot;
|
|
6496
7597
|
this.directorRunId = opts.directorRunId ?? randomUUID();
|
|
6497
7598
|
this.maxSpawns = opts.maxSpawns ?? Number.POSITIVE_INFINITY;
|
|
6498
7599
|
this.maxSpawnDepth = opts.maxSpawnDepth ?? 2;
|
|
@@ -6500,6 +7601,8 @@ var FleetManager = class {
|
|
|
6500
7601
|
this.sessionWriter = opts.sessionWriter ?? null;
|
|
6501
7602
|
this.manifestDebounceMs = opts.manifestDebounceMs ?? 2e3;
|
|
6502
7603
|
this.maxFleetCostUsd = opts.directorBudget?.maxCostUsd ?? Number.POSITIVE_INFINITY;
|
|
7604
|
+
this.maxLeaderContextLoad = opts.maxLeaderContextLoad ?? 0.85;
|
|
7605
|
+
this.maxContext = opts.maxContext ?? 128e3;
|
|
6503
7606
|
this.stateCheckpoint = opts.stateCheckpointPath ? new DirectorStateCheckpoint(
|
|
6504
7607
|
opts.stateCheckpointPath,
|
|
6505
7608
|
{
|
|
@@ -6514,7 +7617,7 @@ var FleetManager = class {
|
|
|
6514
7617
|
this.fleet = new FleetBus();
|
|
6515
7618
|
this.usage = new FleetUsageAggregator(
|
|
6516
7619
|
this.fleet,
|
|
6517
|
-
(
|
|
7620
|
+
(_id, provider, model) => {
|
|
6518
7621
|
if (provider && model) return this.priceLookups.get(`${provider}/${model}`);
|
|
6519
7622
|
return void 0;
|
|
6520
7623
|
},
|
|
@@ -6553,7 +7656,7 @@ var FleetManager = class {
|
|
|
6553
7656
|
* which cap was exceeded. Does NOT throw — the caller decides
|
|
6554
7657
|
* how to surface the rejection.
|
|
6555
7658
|
*/
|
|
6556
|
-
canSpawn(
|
|
7659
|
+
canSpawn(_config) {
|
|
6557
7660
|
if (this.spawnDepth >= this.maxSpawnDepth) {
|
|
6558
7661
|
return { kind: "max_spawn_depth", limit: this.maxSpawnDepth, observed: this.spawnDepth };
|
|
6559
7662
|
}
|
|
@@ -6566,8 +7669,45 @@ var FleetManager = class {
|
|
|
6566
7669
|
return { kind: "max_cost_usd", limit: this.maxFleetCostUsd, observed: totalCost };
|
|
6567
7670
|
}
|
|
6568
7671
|
}
|
|
7672
|
+
if (this.maxLeaderContextLoad < 1) {
|
|
7673
|
+
const threshold = this.maxContext * this.maxLeaderContextLoad;
|
|
7674
|
+
if (this.leaderContextPressure >= threshold) {
|
|
7675
|
+
return {
|
|
7676
|
+
kind: "max_context_load",
|
|
7677
|
+
limit: threshold,
|
|
7678
|
+
observed: this.leaderContextPressure
|
|
7679
|
+
};
|
|
7680
|
+
}
|
|
7681
|
+
}
|
|
6569
7682
|
return null;
|
|
6570
7683
|
}
|
|
7684
|
+
setLeaderContextPressure(tokens) {
|
|
7685
|
+
this.leaderContextPressure = tokens;
|
|
7686
|
+
}
|
|
7687
|
+
/**
|
|
7688
|
+
* Assign a memorable nickname (e.g. "Einstein (Bug Hunter)") to the config,
|
|
7689
|
+
* record it so the same name is never reused, then record the spawn.
|
|
7690
|
+
*
|
|
7691
|
+
* Call this INSTEAD of `recordSpawn` when you want automatic nicknames.
|
|
7692
|
+
* The nickname is written back to `config.name` BEFORE the coordinator
|
|
7693
|
+
* sees the config, so the manifest, logs, and fleet UI all show it.
|
|
7694
|
+
*/
|
|
7695
|
+
assignNicknameAndRecord(subagentId, config, priceLookup) {
|
|
7696
|
+
const role = config.role ?? "subagent";
|
|
7697
|
+
const nickname = assignNickname(role, this._usedNicknames);
|
|
7698
|
+
const baseKey = nickname.split(" ")[0].toLowerCase().replace(/[^a-z0-9-]/g, "-");
|
|
7699
|
+
this._usedNicknames.add(baseKey);
|
|
7700
|
+
config.name = nickname;
|
|
7701
|
+
this.recordSpawn(subagentId, config, priceLookup);
|
|
7702
|
+
return nickname;
|
|
7703
|
+
}
|
|
7704
|
+
/**
|
|
7705
|
+
* Returns the set of already-assigned nickname keys — useful for debugging
|
|
7706
|
+
* and testing.
|
|
7707
|
+
*/
|
|
7708
|
+
get usedNicknames() {
|
|
7709
|
+
return this._usedNicknames;
|
|
7710
|
+
}
|
|
6571
7711
|
/**
|
|
6572
7712
|
* Records a spawn: increments counter, stores metadata, updates state checkpoint,
|
|
6573
7713
|
* and schedules a debounced manifest write. Call AFTER the coordinator
|
|
@@ -6626,7 +7766,7 @@ var FleetManager = class {
|
|
|
6626
7766
|
})),
|
|
6627
7767
|
usage: this.usage.snapshot()
|
|
6628
7768
|
};
|
|
6629
|
-
await
|
|
7769
|
+
await fsp5.mkdir(path4.dirname(this.manifestPath), { recursive: true });
|
|
6630
7770
|
await atomicWrite(this.manifestPath, JSON.stringify(manifest, null, 2), { mode: 384 });
|
|
6631
7771
|
return this.manifestPath;
|
|
6632
7772
|
}
|
|
@@ -6738,6 +7878,6 @@ var FleetManager = class {
|
|
|
6738
7878
|
}
|
|
6739
7879
|
};
|
|
6740
7880
|
|
|
6741
|
-
export { ACP_AGENTS, AGENTS_BY_PHASE, AGENT_CATALOG, TOOLS as AGENT_TOOL_PRESETS, ALL_AGENT_DEFINITIONS, ALL_FLEET_AGENTS, AUDIT_LOG_AGENT, BUG_HUNTER_AGENT, BUILD_AGENTS, BudgetExceededError, BudgetThresholdSignal, DEFAULT_DIRECTOR_PREAMBLE, DEFAULT_DISPATCH_ROLE, DEFAULT_SUBAGENT_BASELINE, DELIVERY_AGENTS, DISCOVERY_AGENTS, DOMAIN_AGENTS, DefaultMultiAgentCoordinator, Director, FLEET_ROSTER, FLEET_ROSTER_BUDGETS, FLEET_ROSTER_WITHACP, FleetBus, FleetCostCapError, FleetManager, FleetSpawnBudgetError, FleetUsageAggregator, HEAVY_BUDGET, InMemoryAgentBridge, InMemoryBridgeTransport, KNOWLEDGE_AGENTS, LIGHT_BUDGET, MEDIUM_BUDGET, META_AGENTS, NULL_FLEET_BUS, PLANNING_AGENTS, REFACTOR_PLANNER_AGENT, REVIEW_AGENTS, SECURITY_SCANNER_AGENT, SubagentBudget, VERIFY_AGENTS, applyRosterBudget, attachAutoExtend, composeDirectorPrompt, composeSubagentPrompt, createDelegateTool, createMessage, dispatchAgent, getAgentDefinition, makeAgentSubagentRunner, makeDirectorSessionFactory, makeLLMClassifier, rosterSummaryFromConfigs, scoreAgents };
|
|
7881
|
+
export { ACP_AGENTS, AGENTS_BY_PHASE, AGENT_CATALOG, TOOLS as AGENT_TOOL_PRESETS, ALL_AGENT_DEFINITIONS, ALL_FLEET_AGENTS, AUDIT_LOG_AGENT, BUG_HUNTER_AGENT, BUILD_AGENTS, BudgetExceededError, BudgetThresholdSignal, CollabSession, DEFAULT_DIRECTOR_PREAMBLE, DEFAULT_DISPATCH_ROLE, DEFAULT_SUBAGENT_BASELINE, DELIVERY_AGENTS, DISCOVERY_AGENTS, DOMAIN_AGENTS, DefaultMultiAgentCoordinator, Director, DirectorAlertLevel, FLEET_ROSTER, FLEET_ROSTER_BUDGETS, FLEET_ROSTER_WITHACP, FleetBus, FleetCostCapError, FleetManager, FleetSpawnBudgetError, FleetUsageAggregator, HEAVY_BUDGET, InMemoryAgentBridge, InMemoryBridgeTransport, KNOWLEDGE_AGENTS, LIGHT_BUDGET, MEDIUM_BUDGET, META_AGENTS, NULL_FLEET_BUS, PLANNING_AGENTS, REFACTOR_PLANNER_AGENT, REVIEW_AGENTS, SECURITY_SCANNER_AGENT, SubagentBudget, VERIFY_AGENTS, applyRosterBudget, attachAutoExtend, composeDirectorPrompt, composeSubagentPrompt, createDelegateTool, createMessage, dispatchAgent, getAgentDefinition, makeAgentSubagentRunner, makeAskTool, makeAssignTool, makeAwaitTasksTool, makeCollabDebugTool, makeDirectorSessionFactory, makeFleetEmitTool, makeFleetHealthTool, makeFleetSessionTool, makeFleetStatusTool, makeFleetUsageTool, makeLLMClassifier, makeRollUpTool, makeSpawnTool, makeTerminateTool, makeWorkCompleteTool, rosterSummaryFromConfigs, scoreAgents };
|
|
6742
7882
|
//# sourceMappingURL=index.js.map
|
|
6743
7883
|
//# sourceMappingURL=index.js.map
|