@wrongstack/core 0.9.19 → 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-C4qt9e5Y.d.ts → agent-subagent-runner-U-rs7kk7.d.ts} +3 -4
- package/dist/compactor-D7X96RLZ.d.ts +41 -0
- package/dist/{config-CWva0qoL.d.ts → config-CLXMDOSs.d.ts} +1 -1
- package/dist/{context-BRNbHmRM.d.ts → context-zkZeILpr.d.ts} +46 -0
- package/dist/coordination/index.d.ts +13 -13
- package/dist/coordination/index.js +660 -146
- package/dist/coordination/index.js.map +1 -1
- package/dist/defaults/index.d.ts +20 -20
- package/dist/defaults/index.js +918 -350
- 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 +41 -30
- package/dist/execution/index.js +358 -112
- package/dist/execution/index.js.map +1 -1
- package/dist/extension/index.d.ts +6 -6
- package/dist/extension/index.js.map +1 -1
- package/dist/{index-aizK8olO.d.ts → index-BIHJ4uII.d.ts} +11 -8
- package/dist/{index-p95HQ22A.d.ts → index-CFO9QmJo.d.ts} +8 -8
- package/dist/index.d.ts +311 -35
- package/dist/index.js +1933 -512
- 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 +9 -9
- package/dist/kernel/index.js.map +1 -1
- package/dist/{mcp-servers-BkVEqkRe.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-bRaI_aD1.d.ts → multi-agent-coordinator-CAhsegPz.d.ts} +20 -2
- package/dist/{null-fleet-bus-DKM3Iy9d.d.ts → null-fleet-bus-Dnl19vmf.d.ts} +411 -110
- package/dist/observability/index.d.ts +2 -2
- package/dist/{path-resolver-TcJfc29Y.d.ts → path-resolver-CHiBL0DD.d.ts} +2 -2
- package/dist/{permission-bPuzAy4x.d.ts → permission-H35s9Evv.d.ts} +1 -1
- package/dist/{permission-policy-BUQSutpl.d.ts → permission-policy-CT-nRmTn.d.ts} +2 -2
- package/dist/{plan-templates-fkQTyz3U.d.ts → plan-templates-Bs8iRwi6.d.ts} +4 -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 +9 -10
- package/dist/sdd/index.js +224 -68
- package/dist/sdd/index.js.map +1 -1
- package/dist/security/index.d.ts +3 -3
- 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 +390 -6
- package/dist/storage/index.js +672 -35
- 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-Boo3dekH.d.ts → tool-executor-flTuxsqO.d.ts} +9 -4
- package/dist/types/index.d.ts +14 -14
- package/dist/types/index.js +60 -0
- package/dist/types/index.js.map +1 -1
- package/dist/utils/index.d.ts +18 -6
- package/dist/utils/index.js +61 -56
- package/dist/utils/index.js.map +1 -1
- package/package.json +1 -1
- package/dist/compactor-DVTKL7XD.d.ts +0 -23
|
@@ -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();
|
|
@@ -704,7 +706,7 @@ var DOMAIN_PREFERENCES = {
|
|
|
704
706
|
"e2e": ["hopper", "bell", "marconi", "tesla"],
|
|
705
707
|
"performance": ["knuth", "gauss", "planck", "feynman"],
|
|
706
708
|
"chaos": ["tesla", "edison", "curie", "fermi"],
|
|
707
|
-
"cost": ["
|
|
709
|
+
"cost": ["ohm", "bell", "marconi", "tesla"],
|
|
708
710
|
// default fallback
|
|
709
711
|
"default": ["einstein", "newton", "curie", "tesla", "edison", "turing", "shannon", "hopper", "knuth", "stallman"]
|
|
710
712
|
};
|
|
@@ -714,8 +716,9 @@ function assignNickname(role, used) {
|
|
|
714
716
|
...DOMAIN_PREFERENCES["default"] ?? []
|
|
715
717
|
];
|
|
716
718
|
for (const key of preferences) {
|
|
717
|
-
|
|
718
|
-
|
|
719
|
+
const entry = NICKNAME_POOL[key];
|
|
720
|
+
if (entry && !used.has(key)) {
|
|
721
|
+
return `${entry.name} (${formatRole(role)})`;
|
|
719
722
|
}
|
|
720
723
|
}
|
|
721
724
|
for (const entry of ALL_NICKNAMES) {
|
|
@@ -879,17 +882,7 @@ var SubagentBudget = class _SubagentBudget {
|
|
|
879
882
|
startTime = null;
|
|
880
883
|
_onThreshold;
|
|
881
884
|
/**
|
|
882
|
-
*
|
|
883
|
-
* in flight. While a kind is here, further `checkLimit` calls for the
|
|
884
|
-
* same kind are no-ops — without this dedup, every `recordIteration`
|
|
885
|
-
* after the limit is reached spawns a fresh decision Promise (until
|
|
886
|
-
* the first one lands and patches limits), flooding the FleetBus
|
|
887
|
-
* with redundant threshold events. Cleared in `negotiateExtension`'s
|
|
888
|
-
* `finally`.
|
|
889
|
-
*/
|
|
890
|
-
pendingExtensions = /* @__PURE__ */ new Set();
|
|
891
|
-
/**
|
|
892
|
-
* Hard cap on how long `negotiateExtension` waits for the coordinator to
|
|
885
|
+
* Hard cap on how long `_negotiateExtension` waits for the coordinator to
|
|
893
886
|
* respond before defaulting to 'stop'. Without this fallback an absent
|
|
894
887
|
* or hung listener (Director not built / event filter detached mid-run)
|
|
895
888
|
* leaves the budget over-limit and never enforces anything.
|
|
@@ -951,38 +944,83 @@ var SubagentBudget = class _SubagentBudget {
|
|
|
951
944
|
* - `mode === 'auto'` + no listener → throw `BudgetExceededError` (hard stop; no one to ask)
|
|
952
945
|
* - `mode === 'auto'` + listener → throw `BudgetThresholdSignal` with async decision promise
|
|
953
946
|
*/
|
|
954
|
-
|
|
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 [];
|
|
955
977
|
if (!this._onThreshold) {
|
|
956
|
-
|
|
978
|
+
const first2 = exceeded[0];
|
|
979
|
+
throw new BudgetExceededError(first2.kind, first2.limit, first2.used);
|
|
957
980
|
}
|
|
958
981
|
if (this._mode === "sync") {
|
|
959
|
-
|
|
982
|
+
const first2 = exceeded[0];
|
|
983
|
+
throw new BudgetExceededError(first2.kind, first2.limit, first2.used);
|
|
960
984
|
}
|
|
961
985
|
const bus = this._events;
|
|
962
986
|
if (!bus || !bus.hasListenerFor("budget.threshold_reached")) {
|
|
963
|
-
|
|
987
|
+
const first2 = exceeded[0];
|
|
988
|
+
throw new BudgetExceededError(first2.kind, first2.limit, first2.used);
|
|
964
989
|
}
|
|
965
|
-
|
|
966
|
-
|
|
967
|
-
|
|
968
|
-
|
|
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);
|
|
994
|
+
}
|
|
995
|
+
const first = exceeded[0];
|
|
996
|
+
const decision = this._pendingNegotiations.get(first.kind);
|
|
997
|
+
throw new BudgetThresholdSignal(first.kind, first.limit, first.used, decision);
|
|
969
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();
|
|
970
1007
|
/**
|
|
971
1008
|
* Drive the threshold handler to a decision. Resolves with `'stop'`
|
|
972
1009
|
* (signal the runner to abort) or `{ extend: ... }` (limits already
|
|
973
|
-
* patched in-place; the runner should not abort).
|
|
974
|
-
*
|
|
1010
|
+
* patched in-place; the runner should not abort). Clears the
|
|
1011
|
+
* per-kind slot in `_pendingNegotiations` in `finally`.
|
|
975
1012
|
*
|
|
976
1013
|
* The 'continue' return from a sync handler is treated as
|
|
977
1014
|
* `{ extend: {} }` — keep going without patching; next overrun fires
|
|
978
1015
|
* a fresh signal.
|
|
979
1016
|
*/
|
|
980
|
-
async
|
|
1017
|
+
async _negotiateExtension(kind, exceeded) {
|
|
981
1018
|
try {
|
|
1019
|
+
const first = exceeded[0];
|
|
982
1020
|
const result = this._onThreshold({
|
|
983
|
-
kind,
|
|
984
|
-
used,
|
|
985
|
-
limit,
|
|
1021
|
+
kind: first.kind,
|
|
1022
|
+
used: first.used,
|
|
1023
|
+
limit: first.limit,
|
|
986
1024
|
requestDecision: () => {
|
|
987
1025
|
const bus = this._events;
|
|
988
1026
|
if (!bus || !bus.hasListenerFor("budget.threshold_reached")) {
|
|
@@ -999,20 +1037,22 @@ var SubagentBudget = class _SubagentBudget {
|
|
|
999
1037
|
() => respond("stop"),
|
|
1000
1038
|
_SubagentBudget.DECISION_TIMEOUT_MS
|
|
1001
1039
|
);
|
|
1002
|
-
|
|
1003
|
-
|
|
1004
|
-
|
|
1005
|
-
|
|
1006
|
-
|
|
1007
|
-
|
|
1008
|
-
|
|
1009
|
-
|
|
1010
|
-
|
|
1011
|
-
|
|
1012
|
-
|
|
1013
|
-
|
|
1014
|
-
|
|
1015
|
-
|
|
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
|
+
}
|
|
1016
1056
|
});
|
|
1017
1057
|
}
|
|
1018
1058
|
});
|
|
@@ -1038,47 +1078,39 @@ var SubagentBudget = class _SubagentBudget {
|
|
|
1038
1078
|
}
|
|
1039
1079
|
return decision;
|
|
1040
1080
|
} finally {
|
|
1041
|
-
this.
|
|
1081
|
+
this._pendingNegotiations.delete(kind);
|
|
1042
1082
|
}
|
|
1043
1083
|
}
|
|
1044
1084
|
recordIteration() {
|
|
1045
1085
|
this.iterations++;
|
|
1046
|
-
|
|
1047
|
-
void this.checkLimit("iterations", this.iterations, this.limits.maxIterations);
|
|
1048
|
-
}
|
|
1086
|
+
void this.checkLimits();
|
|
1049
1087
|
}
|
|
1050
1088
|
recordToolCall() {
|
|
1051
1089
|
this.toolCalls++;
|
|
1052
|
-
|
|
1053
|
-
void this.checkLimit("tool_calls", this.toolCalls, this.limits.maxToolCalls);
|
|
1054
|
-
}
|
|
1090
|
+
void this.checkLimits();
|
|
1055
1091
|
}
|
|
1056
1092
|
recordUsage(usage, costUsd = 0) {
|
|
1057
1093
|
this.tokenInput += usage.input;
|
|
1058
1094
|
this.tokenOutput += usage.output;
|
|
1059
1095
|
this.costUsd += costUsd;
|
|
1060
|
-
|
|
1061
|
-
if (this.limits.maxTokens !== void 0 && totalTokens > this.limits.maxTokens) {
|
|
1062
|
-
void this.checkLimit("tokens", totalTokens, this.limits.maxTokens);
|
|
1063
|
-
}
|
|
1064
|
-
if (this.limits.maxCostUsd !== void 0 && this.costUsd > this.limits.maxCostUsd) {
|
|
1065
|
-
void this.checkLimit("cost", this.costUsd, this.limits.maxCostUsd);
|
|
1066
|
-
}
|
|
1096
|
+
void this.checkLimits();
|
|
1067
1097
|
}
|
|
1068
1098
|
/**
|
|
1069
|
-
* Wall-clock budget check. Unlike other limits, timeout
|
|
1070
|
-
*
|
|
1071
|
-
*
|
|
1072
|
-
*
|
|
1073
|
-
*
|
|
1074
|
-
*
|
|
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)
|
|
1075
1108
|
*/
|
|
1076
1109
|
checkTimeout() {
|
|
1077
1110
|
if (this.startTime === null || this.limits.timeoutMs === void 0) return;
|
|
1078
1111
|
const elapsed = Date.now() - this.startTime;
|
|
1079
|
-
if (elapsed
|
|
1080
|
-
|
|
1081
|
-
}
|
|
1112
|
+
if (elapsed <= this.limits.timeoutMs) return;
|
|
1113
|
+
void this.checkLimits(elapsed);
|
|
1082
1114
|
}
|
|
1083
1115
|
/** Returns true if a timeout has occurred without throwing. Useful for races. */
|
|
1084
1116
|
isTimedOut() {
|
|
@@ -3583,6 +3615,15 @@ var DefaultMultiAgentCoordinator = class extends EventEmitter {
|
|
|
3583
3615
|
runner;
|
|
3584
3616
|
fleetBus;
|
|
3585
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();
|
|
3586
3627
|
pendingTasks = [];
|
|
3587
3628
|
completedResults = [];
|
|
3588
3629
|
totalIterations = 0;
|
|
@@ -3631,7 +3672,26 @@ var DefaultMultiAgentCoordinator = class extends EventEmitter {
|
|
|
3631
3672
|
this.config.maxConcurrent = Math.floor(n);
|
|
3632
3673
|
this.tryDispatchNext();
|
|
3633
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
|
+
}
|
|
3634
3693
|
async spawn(subagent) {
|
|
3694
|
+
subagent = this.withNickname(subagent);
|
|
3635
3695
|
const id = subagent.id || randomUUID();
|
|
3636
3696
|
if (this.subagents.has(id)) {
|
|
3637
3697
|
throw new Error(`Subagent id "${id}" already exists \u2014 refusing to overwrite`);
|
|
@@ -4510,7 +4570,7 @@ function makeRollUpTool(director) {
|
|
|
4510
4570
|
function makeTerminateTool(director) {
|
|
4511
4571
|
return {
|
|
4512
4572
|
name: "terminate_subagent",
|
|
4513
|
-
description:
|
|
4573
|
+
description: 'Forcibly abort a subagent. The subagent finishes its current iteration then exits with status "stopped".',
|
|
4514
4574
|
permission: "auto",
|
|
4515
4575
|
mutating: true,
|
|
4516
4576
|
inputSchema: { type: "object", properties: { subagentId: { type: "string", description: "Subagent to abort." } }, required: ["subagentId"] },
|
|
@@ -4521,6 +4581,19 @@ function makeTerminateTool(director) {
|
|
|
4521
4581
|
}
|
|
4522
4582
|
};
|
|
4523
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
|
+
}
|
|
4524
4597
|
function makeFleetStatusTool(director) {
|
|
4525
4598
|
return {
|
|
4526
4599
|
name: "fleet_status",
|
|
@@ -4663,7 +4736,7 @@ function makeCollabDebugTool(director) {
|
|
|
4663
4736
|
function makeFleetEmitTool(director) {
|
|
4664
4737
|
return {
|
|
4665
4738
|
name: "fleet_emit",
|
|
4666
|
-
description: "Emit a structured event on the FleetBus.
|
|
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.",
|
|
4667
4740
|
permission: "auto",
|
|
4668
4741
|
mutating: false,
|
|
4669
4742
|
inputSchema: {
|
|
@@ -4671,14 +4744,14 @@ function makeFleetEmitTool(director) {
|
|
|
4671
4744
|
properties: {
|
|
4672
4745
|
type: {
|
|
4673
4746
|
type: "string",
|
|
4674
|
-
description: "Event type
|
|
4747
|
+
description: "Event type string (e.g. bug.found, refactor.plan, critic.evaluation, progress, result)."
|
|
4675
4748
|
},
|
|
4676
4749
|
payload: {
|
|
4677
4750
|
type: "object",
|
|
4678
|
-
description: "Event payload
|
|
4751
|
+
description: "Event payload. Structure depends on event type. Use null if no payload."
|
|
4679
4752
|
}
|
|
4680
4753
|
},
|
|
4681
|
-
required: ["type"
|
|
4754
|
+
required: ["type"]
|
|
4682
4755
|
},
|
|
4683
4756
|
async execute(input) {
|
|
4684
4757
|
const i = input;
|
|
@@ -4686,12 +4759,31 @@ function makeFleetEmitTool(director) {
|
|
|
4686
4759
|
subagentId: director.id,
|
|
4687
4760
|
ts: Date.now(),
|
|
4688
4761
|
type: i.type,
|
|
4689
|
-
payload: i.payload
|
|
4762
|
+
payload: i.payload ?? {}
|
|
4690
4763
|
});
|
|
4691
4764
|
return { ok: true, event: i.type };
|
|
4692
4765
|
}
|
|
4693
4766
|
};
|
|
4694
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 || {});
|
|
4695
4787
|
var CollabSession = class extends EventEmitter {
|
|
4696
4788
|
sessionId;
|
|
4697
4789
|
options;
|
|
@@ -4706,6 +4798,14 @@ var CollabSession = class extends EventEmitter {
|
|
|
4706
4798
|
disposers = new Array();
|
|
4707
4799
|
settled = false;
|
|
4708
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;
|
|
4709
4809
|
constructor(director, fleetBus, options) {
|
|
4710
4810
|
super();
|
|
4711
4811
|
this.sessionId = randomUUID();
|
|
@@ -4723,11 +4823,23 @@ var CollabSession = class extends EventEmitter {
|
|
|
4723
4823
|
};
|
|
4724
4824
|
}
|
|
4725
4825
|
}
|
|
4826
|
+
get id() {
|
|
4827
|
+
return this.sessionId;
|
|
4828
|
+
}
|
|
4829
|
+
getSessionAlerts() {
|
|
4830
|
+
return [...this.alerts];
|
|
4831
|
+
}
|
|
4832
|
+
isCancelled() {
|
|
4833
|
+
return this.cancelled;
|
|
4834
|
+
}
|
|
4726
4835
|
/**
|
|
4727
|
-
*
|
|
4728
|
-
*
|
|
4729
|
-
*
|
|
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.
|
|
4730
4839
|
*/
|
|
4840
|
+
getSubagentIds() {
|
|
4841
|
+
return new Map(this.subagentIds);
|
|
4842
|
+
}
|
|
4731
4843
|
async buildSnapshot() {
|
|
4732
4844
|
if (this.snapshot.files.length > 0) return this.snapshot;
|
|
4733
4845
|
for (const filePath of this.options.targetPaths) {
|
|
@@ -4743,9 +4855,30 @@ var CollabSession = class extends EventEmitter {
|
|
|
4743
4855
|
return this.snapshot;
|
|
4744
4856
|
}
|
|
4745
4857
|
/**
|
|
4746
|
-
*
|
|
4747
|
-
*
|
|
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).
|
|
4748
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
|
+
}
|
|
4749
4882
|
async start() {
|
|
4750
4883
|
if (this.settled) throw new Error("session already settled");
|
|
4751
4884
|
this.settled = true;
|
|
@@ -4760,10 +4893,10 @@ var CollabSession = class extends EventEmitter {
|
|
|
4760
4893
|
this.subagentIds.set("refactor-planner", refactorPlannerId);
|
|
4761
4894
|
this.subagentIds.set("critic", criticId);
|
|
4762
4895
|
const timeout = new Promise((_, reject) => {
|
|
4763
|
-
setTimeout(
|
|
4764
|
-
(
|
|
4765
|
-
this.timeoutMs
|
|
4766
|
-
);
|
|
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);
|
|
4767
4900
|
});
|
|
4768
4901
|
let results;
|
|
4769
4902
|
try {
|
|
@@ -4776,6 +4909,10 @@ var CollabSession = class extends EventEmitter {
|
|
|
4776
4909
|
timeout
|
|
4777
4910
|
]);
|
|
4778
4911
|
} catch (err) {
|
|
4912
|
+
if (this._timeoutTimer) {
|
|
4913
|
+
clearTimeout(this._timeoutTimer);
|
|
4914
|
+
this._timeoutTimer = void 0;
|
|
4915
|
+
}
|
|
4779
4916
|
this.cleanup();
|
|
4780
4917
|
const error = err instanceof Error ? err : new Error(String(err));
|
|
4781
4918
|
this.emit("session.error", error);
|
|
@@ -4789,17 +4926,6 @@ var CollabSession = class extends EventEmitter {
|
|
|
4789
4926
|
this.emit("session.done", report);
|
|
4790
4927
|
return report;
|
|
4791
4928
|
}
|
|
4792
|
-
/**
|
|
4793
|
-
* Parse a completed agent's final text output and route the structured
|
|
4794
|
-
* findings onto the FleetBus. Agents are instructed to emit one JSON object
|
|
4795
|
-
* per finding / plan / evaluation; we scan the output for balanced JSON
|
|
4796
|
-
* objects (tolerating prose, markdown, and ```json fences around them) and
|
|
4797
|
-
* dispatch each by its discriminating key. The wired FleetBus listeners then
|
|
4798
|
-
* collect them into the bugs / plans / evaluations maps.
|
|
4799
|
-
*
|
|
4800
|
-
* Failed or empty results are ignored, and a single malformed object never
|
|
4801
|
-
* aborts the collation pass — see {@link extractJsonObjects}.
|
|
4802
|
-
*/
|
|
4803
4929
|
async parseAndEmit(result) {
|
|
4804
4930
|
if (result.status !== "success" || result.result == null) return;
|
|
4805
4931
|
const text = typeof result.result === "string" ? result.result : JSON.stringify(result.result);
|
|
@@ -4815,12 +4941,6 @@ var CollabSession = class extends EventEmitter {
|
|
|
4815
4941
|
});
|
|
4816
4942
|
}
|
|
4817
4943
|
}
|
|
4818
|
-
/**
|
|
4819
|
-
* Extract every top-level JSON object embedded in free-form agent text.
|
|
4820
|
-
* Scans for balanced braces while respecting string literals and escape
|
|
4821
|
-
* sequences, so prose, markdown headers, and code fences around the JSON
|
|
4822
|
-
* are skipped. Malformed spans are dropped silently rather than thrown.
|
|
4823
|
-
*/
|
|
4824
4944
|
extractJsonObjects(text) {
|
|
4825
4945
|
const objects = [];
|
|
4826
4946
|
let depth = 0;
|
|
@@ -4857,25 +4977,33 @@ var CollabSession = class extends EventEmitter {
|
|
|
4857
4977
|
}
|
|
4858
4978
|
return objects;
|
|
4859
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
|
+
}
|
|
4860
4991
|
async spawnAgent(role, taskBrief) {
|
|
4992
|
+
const budget = this.budgetForRole(role);
|
|
4861
4993
|
const cfg = {
|
|
4862
4994
|
id: `${role}-${this.sessionId}`,
|
|
4863
4995
|
name: role,
|
|
4864
|
-
role
|
|
4865
|
-
|
|
4866
|
-
|
|
4867
|
-
|
|
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
|
|
4868
5001
|
};
|
|
4869
5002
|
const subagentId = await this.director.spawn(cfg);
|
|
4870
|
-
await this.director.assign({
|
|
4871
|
-
id: randomUUID(),
|
|
4872
|
-
subagentId,
|
|
4873
|
-
description: taskBrief
|
|
4874
|
-
});
|
|
5003
|
+
await this.director.assign({ id: randomUUID(), subagentId, description: taskBrief });
|
|
4875
5004
|
return subagentId;
|
|
4876
5005
|
}
|
|
4877
5006
|
buildBugHunterTask() {
|
|
4878
|
-
this.options.targetPaths.join(", ");
|
|
4879
5007
|
const scratchpad = this.director.sharedScratchpadPath ?? "/tmp";
|
|
4880
5008
|
const fileContents = this.snapshot.files.map((f) => `=== ${f.path} ===
|
|
4881
5009
|
${f.content}`).join("\n\n");
|
|
@@ -4884,13 +5012,17 @@ ${f.content}`).join("\n\n");
|
|
|
4884
5012
|
Target files:
|
|
4885
5013
|
${fileContents}
|
|
4886
5014
|
|
|
4887
|
-
For each bug found,
|
|
4888
|
-
{ "finding": { "id": "<uuid>", "type": "<pattern>", "severity": "<critical|high|medium|low>", "location": { "file": "<path>", "line": <n> }, "description": "<explain>", "suggestedFix": "<optional>" } }
|
|
4889
|
-
|
|
4890
|
-
|
|
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.`;
|
|
4891
5022
|
}
|
|
4892
5023
|
buildRefactorPlannerTask() {
|
|
4893
5024
|
const scratchpad = this.director.sharedScratchpadPath ?? "/tmp";
|
|
5025
|
+
const bugHunterReportPath = `${scratchpad}/bug-hunter-report-${this.sessionId}.md`;
|
|
4894
5026
|
const fileContents = this.snapshot.files.map((f) => `=== ${f.path} ===
|
|
4895
5027
|
${f.content}`).join("\n\n");
|
|
4896
5028
|
return `You are RefactorPlanner. Plan refactorings for the following files.
|
|
@@ -4898,26 +5030,146 @@ ${f.content}`).join("\n\n");
|
|
|
4898
5030
|
Target files:
|
|
4899
5031
|
${fileContents}
|
|
4900
5032
|
|
|
4901
|
-
|
|
4902
|
-
|
|
4903
|
-
|
|
4904
|
-
|
|
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.`;
|
|
4905
5042
|
}
|
|
4906
5043
|
buildCriticTask() {
|
|
4907
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`;
|
|
4908
5047
|
const fileContents = this.snapshot.files.map((f) => `=== ${f.path} ===
|
|
4909
5048
|
${f.content}`).join("\n\n");
|
|
4910
|
-
return `You are Critic. Evaluate bug findings and refactor plans
|
|
5049
|
+
return `You are Critic. Evaluate bug findings and refactor plans.
|
|
4911
5050
|
|
|
4912
5051
|
Target files:
|
|
4913
5052
|
${fileContents}
|
|
4914
5053
|
|
|
4915
|
-
|
|
4916
|
-
|
|
4917
|
-
|
|
4918
|
-
|
|
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.`;
|
|
4919
5064
|
}
|
|
4920
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);
|
|
4921
5173
|
const d1 = this.fleetBus.filter("bug.found", (e) => {
|
|
4922
5174
|
const payload = e.payload;
|
|
4923
5175
|
if (payload?.finding) {
|
|
@@ -4943,10 +5195,19 @@ ${scratchpad}/critic-report-${this.sessionId}.md`;
|
|
|
4943
5195
|
});
|
|
4944
5196
|
this.disposers.push(d3);
|
|
4945
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
|
+
}
|
|
4946
5205
|
assembleReport() {
|
|
4947
5206
|
const bugList = Array.from(this.bugs.values());
|
|
4948
5207
|
const planList = Array.from(this.plans.values());
|
|
4949
5208
|
const evalList = Array.from(this.evaluations.values());
|
|
5209
|
+
let disposition = "completed";
|
|
5210
|
+
if (this.cancelled) disposition = "cancelled";
|
|
4950
5211
|
const verdictOrder = {
|
|
4951
5212
|
approve: 0,
|
|
4952
5213
|
needs_revision: 1,
|
|
@@ -4960,33 +5221,41 @@ ${scratchpad}/critic-report-${this.sessionId}.md`;
|
|
|
4960
5221
|
},
|
|
4961
5222
|
"approve"
|
|
4962
5223
|
);
|
|
4963
|
-
const summary = this.buildMarkdownSummary(bugList, planList, evalList, overallVerdict);
|
|
5224
|
+
const summary = this.buildMarkdownSummary(bugList, planList, evalList, overallVerdict, disposition);
|
|
4964
5225
|
return {
|
|
4965
5226
|
sessionId: this.sessionId,
|
|
4966
5227
|
startedAt: this.snapshot.createdAt,
|
|
4967
5228
|
completedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
4968
5229
|
targetPaths: this.options.targetPaths,
|
|
5230
|
+
disposition,
|
|
4969
5231
|
bugs: bugList,
|
|
4970
5232
|
refactorPlans: planList,
|
|
4971
5233
|
evaluations: evalList,
|
|
5234
|
+
alerts: [...this.alerts],
|
|
4972
5235
|
overallVerdict,
|
|
4973
5236
|
summary
|
|
4974
5237
|
};
|
|
4975
5238
|
}
|
|
4976
|
-
buildMarkdownSummary(bugs, plans, evals, overallVerdict) {
|
|
5239
|
+
buildMarkdownSummary(bugs, plans, evals, overallVerdict, disposition) {
|
|
4977
5240
|
const lines = [
|
|
4978
5241
|
`## Collaborative Debugging Report \u2014 ${this.sessionId}`,
|
|
4979
5242
|
"",
|
|
4980
5243
|
`**Target:** ${this.options.targetPaths.join(", ")}`,
|
|
5244
|
+
`**Disposition:** ${disposition.toUpperCase()}`,
|
|
4981
5245
|
`**Overall Verdict:** **${overallVerdict.toUpperCase()}**`,
|
|
4982
5246
|
""
|
|
4983
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
|
+
}
|
|
4984
5255
|
if (bugs.length > 0) {
|
|
4985
5256
|
lines.push("### Bugs Found", "");
|
|
4986
5257
|
for (const b of bugs) {
|
|
4987
|
-
lines.push(
|
|
4988
|
-
`- **[${b.severity.toUpperCase()}]** \`${b.location.file}:${b.location.line}\` \u2014 ${b.description}`
|
|
4989
|
-
);
|
|
5258
|
+
lines.push(`- **[${b.severity.toUpperCase()}]** \`${b.location.file}:${b.location.line}\` \u2014 ${b.description}`);
|
|
4990
5259
|
}
|
|
4991
5260
|
lines.push("");
|
|
4992
5261
|
}
|
|
@@ -5005,9 +5274,7 @@ ${scratchpad}/critic-report-${this.sessionId}.md`;
|
|
|
5005
5274
|
for (const e of evals) {
|
|
5006
5275
|
lines.push(`- [${e.subjectType}] score=${e.score}/10 \u2014 **${e.verdict.toUpperCase()}**`);
|
|
5007
5276
|
for (const c of e.concerns) {
|
|
5008
|
-
if (c.severity === "blocking") {
|
|
5009
|
-
lines.push(` - ${c.description}`);
|
|
5010
|
-
}
|
|
5277
|
+
if (c.severity === "blocking") lines.push(` - ${c.description}`);
|
|
5011
5278
|
}
|
|
5012
5279
|
}
|
|
5013
5280
|
lines.push("");
|
|
@@ -5025,10 +5292,9 @@ var FleetSpawnBudgetError = class extends Error {
|
|
|
5025
5292
|
kind;
|
|
5026
5293
|
limit;
|
|
5027
5294
|
observed;
|
|
5028
|
-
constructor(kind, limit, observed) {
|
|
5029
|
-
|
|
5030
|
-
|
|
5031
|
-
);
|
|
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);
|
|
5032
5298
|
this.name = "FleetSpawnBudgetError";
|
|
5033
5299
|
this.kind = kind;
|
|
5034
5300
|
this.limit = limit;
|
|
@@ -5049,6 +5315,20 @@ var FleetCostCapError = class extends Error {
|
|
|
5049
5315
|
this.observed = observed;
|
|
5050
5316
|
}
|
|
5051
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
|
+
};
|
|
5052
5332
|
var Director = class {
|
|
5053
5333
|
/** Alias for the ICoordinator contract. `id` is retained for backward compatibility. */
|
|
5054
5334
|
get coordinatorId() {
|
|
@@ -5065,6 +5345,25 @@ var Director = class {
|
|
|
5065
5345
|
* injected; otherwise own FleetUsageAggregator.
|
|
5066
5346
|
*/
|
|
5067
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
|
+
}
|
|
5068
5367
|
/**
|
|
5069
5368
|
* Optional fleet-level policy container. When provided the Director
|
|
5070
5369
|
* delegates spawn budgeting, manifest entries, and checkpointing to it
|
|
@@ -5150,6 +5449,23 @@ var Director = class {
|
|
|
5150
5449
|
taskCompletedListener = null;
|
|
5151
5450
|
/** Optional LLM classifier for smart dispatch. Passed from options. */
|
|
5152
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();
|
|
5153
5469
|
constructor(opts) {
|
|
5154
5470
|
this.id = opts.config.coordinatorId || randomUUID();
|
|
5155
5471
|
this.manifestPath = opts.manifestPath;
|
|
@@ -5165,6 +5481,8 @@ var Director = class {
|
|
|
5165
5481
|
this.dispatchClassifier = opts.dispatchClassifier;
|
|
5166
5482
|
this.maxFleetCostUsd = opts.directorBudget?.maxCostUsd ?? Number.POSITIVE_INFINITY;
|
|
5167
5483
|
this.maxBudgetExtensions = opts.maxBudgetExtensions ?? 5;
|
|
5484
|
+
this.maxLeaderContextLoad = opts.maxLeaderContextLoad ?? 0.85;
|
|
5485
|
+
this.maxContext = opts.maxContext ?? 128e3;
|
|
5168
5486
|
this.sessionsRoot = opts.sessionsRoot;
|
|
5169
5487
|
this.directorRunId = opts.directorRunId ?? this.id;
|
|
5170
5488
|
this.stateCheckpoint = opts.stateCheckpointPath ? new DirectorStateCheckpoint(opts.stateCheckpointPath, {
|
|
@@ -5192,7 +5510,7 @@ var Director = class {
|
|
|
5192
5510
|
this.fleet = new FleetBus();
|
|
5193
5511
|
this.usage = new FleetUsageAggregator(
|
|
5194
5512
|
this.fleet,
|
|
5195
|
-
(
|
|
5513
|
+
(_id, provider, model) => {
|
|
5196
5514
|
if (provider && model) return this.priceLookups.get(`${provider}/${model}`);
|
|
5197
5515
|
return void 0;
|
|
5198
5516
|
},
|
|
@@ -5254,6 +5572,9 @@ var Director = class {
|
|
|
5254
5572
|
});
|
|
5255
5573
|
this.fleet.filter("budget.threshold_reached", (e) => {
|
|
5256
5574
|
const payload = e.payload;
|
|
5575
|
+
if (e.subagentId.startsWith("bug-hunter-") || e.subagentId.startsWith("refactor-planner-") || e.subagentId.startsWith("critic-")) {
|
|
5576
|
+
return;
|
|
5577
|
+
}
|
|
5257
5578
|
if (payload.kind === "timeout") {
|
|
5258
5579
|
const progress = progressBySubagent.get(e.subagentId) ?? 0;
|
|
5259
5580
|
const lastProgress = lastTimeoutProgress.get(e.subagentId) ?? -1;
|
|
@@ -5332,6 +5653,107 @@ var Director = class {
|
|
|
5332
5653
|
extensionsFor(subagentId) {
|
|
5333
5654
|
return this.extendTotals.get(subagentId) ?? 0;
|
|
5334
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
|
+
}
|
|
5335
5757
|
/** Best-effort session-writer append. Swallows failures — the director
|
|
5336
5758
|
* must not break a fleet run because the session JSONL handle closed. */
|
|
5337
5759
|
async appendSessionEvent(event) {
|
|
@@ -5370,12 +5792,21 @@ var Director = class {
|
|
|
5370
5792
|
* it the `cost` column in `usage.snapshot()` stays at 0.
|
|
5371
5793
|
*/
|
|
5372
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
|
+
}
|
|
5373
5803
|
if (this.fleetManager) {
|
|
5374
5804
|
const rejection = this.fleetManager.canSpawn(config);
|
|
5375
5805
|
if (rejection) {
|
|
5376
5806
|
if (rejection.kind === "max_spawn_depth") throw new FleetSpawnBudgetError("max_spawn_depth", rejection.limit, rejection.observed);
|
|
5377
5807
|
if (rejection.kind === "max_spawns") throw new FleetSpawnBudgetError("max_spawns", rejection.limit, rejection.observed);
|
|
5378
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);
|
|
5379
5810
|
}
|
|
5380
5811
|
} else {
|
|
5381
5812
|
if (this.spawnDepth >= this.maxSpawnDepth) {
|
|
@@ -5390,9 +5821,15 @@ var Director = class {
|
|
|
5390
5821
|
throw new FleetCostCapError(this.maxFleetCostUsd, totalCost);
|
|
5391
5822
|
}
|
|
5392
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
|
+
}
|
|
5393
5830
|
}
|
|
5394
5831
|
let result;
|
|
5395
|
-
const needsNickname = config.name === config.role || !config.name || config.name === "subagent";
|
|
5832
|
+
const needsNickname = config.name === config.role || !config.name || config.name === "subagent" || config.name === "adhoc";
|
|
5396
5833
|
if (needsNickname) {
|
|
5397
5834
|
const role = config.role ?? "subagent";
|
|
5398
5835
|
if (this.fleetManager) {
|
|
@@ -5617,6 +6054,24 @@ var Director = class {
|
|
|
5617
6054
|
*/
|
|
5618
6055
|
async assign(task) {
|
|
5619
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
|
+
}
|
|
5620
6075
|
if (task.subagentId) {
|
|
5621
6076
|
if (this.fleetManager) {
|
|
5622
6077
|
this.fleetManager.addTaskToSubagent(task.subagentId, taskWithId.id);
|
|
@@ -5839,12 +6294,14 @@ var Director = class {
|
|
|
5839
6294
|
makeAskTool(this),
|
|
5840
6295
|
makeRollUpTool(this),
|
|
5841
6296
|
makeTerminateTool(this),
|
|
6297
|
+
makeTerminateAllTool(this),
|
|
5842
6298
|
makeFleetStatusTool(this),
|
|
5843
6299
|
makeFleetUsageTool(this),
|
|
5844
6300
|
makeFleetSessionTool(this),
|
|
5845
6301
|
makeFleetHealthTool(this),
|
|
5846
6302
|
makeCollabDebugTool(this),
|
|
5847
|
-
makeFleetEmitTool(this)
|
|
6303
|
+
makeFleetEmitTool(this),
|
|
6304
|
+
makeWorkCompleteTool(this)
|
|
5848
6305
|
];
|
|
5849
6306
|
return t;
|
|
5850
6307
|
}
|
|
@@ -5864,7 +6321,15 @@ var Director = class {
|
|
|
5864
6321
|
* three agents complete or the session times out.
|
|
5865
6322
|
*/
|
|
5866
6323
|
async spawnCollab(options) {
|
|
5867
|
-
const session = new CollabSession(this, this.fleet,
|
|
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));
|
|
5868
6333
|
return session.start();
|
|
5869
6334
|
}
|
|
5870
6335
|
/**
|
|
@@ -6777,7 +7242,6 @@ var FileSessionWriter = class {
|
|
|
6777
7242
|
}
|
|
6778
7243
|
return event;
|
|
6779
7244
|
}
|
|
6780
|
-
promptIndex = 0;
|
|
6781
7245
|
pendingFileSnapshots = [];
|
|
6782
7246
|
/** Tracks open tool_use IDs during the current run to serialize on close for resume. */
|
|
6783
7247
|
openToolUses = /* @__PURE__ */ new Set();
|
|
@@ -6868,7 +7332,6 @@ var FileSessionWriter = class {
|
|
|
6868
7332
|
await this.writeFileSnapshot(promptIndex, [...this.pendingFileSnapshots]);
|
|
6869
7333
|
this.pendingFileSnapshots = [];
|
|
6870
7334
|
}
|
|
6871
|
-
this.promptIndex = promptIndex + 1;
|
|
6872
7335
|
await this.append({
|
|
6873
7336
|
type: "checkpoint",
|
|
6874
7337
|
ts: (/* @__PURE__ */ new Date()).toISOString(),
|
|
@@ -6964,6 +7427,38 @@ var FileSessionWriter = class {
|
|
|
6964
7427
|
`;
|
|
6965
7428
|
await fsp5.writeFile(this.filePath, record, "utf8");
|
|
6966
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() });
|
|
7461
|
+
}
|
|
6967
7462
|
};
|
|
6968
7463
|
function userInputTitle(content) {
|
|
6969
7464
|
const text = typeof content === "string" ? content : content.filter((b) => b.type === "text").map((b) => b.text).join(" ");
|
|
@@ -7064,7 +7559,6 @@ var FleetManager = class {
|
|
|
7064
7559
|
/** Usage rollup across all subagents. */
|
|
7065
7560
|
usage;
|
|
7066
7561
|
manifestPath;
|
|
7067
|
-
sessionsRoot;
|
|
7068
7562
|
directorRunId;
|
|
7069
7563
|
/** Spawn cap (lifetime total). Infinity means unlimited. */
|
|
7070
7564
|
maxSpawns;
|
|
@@ -7092,9 +7586,14 @@ var FleetManager = class {
|
|
|
7092
7586
|
_usedNicknames = /* @__PURE__ */ new Set();
|
|
7093
7587
|
/** The coordinator (wired via setCoordinator by Director after construction). */
|
|
7094
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;
|
|
7095
7595
|
constructor(opts = {}) {
|
|
7096
7596
|
this.manifestPath = opts.manifestPath;
|
|
7097
|
-
this.sessionsRoot = opts.sessionsRoot;
|
|
7098
7597
|
this.directorRunId = opts.directorRunId ?? randomUUID();
|
|
7099
7598
|
this.maxSpawns = opts.maxSpawns ?? Number.POSITIVE_INFINITY;
|
|
7100
7599
|
this.maxSpawnDepth = opts.maxSpawnDepth ?? 2;
|
|
@@ -7102,6 +7601,8 @@ var FleetManager = class {
|
|
|
7102
7601
|
this.sessionWriter = opts.sessionWriter ?? null;
|
|
7103
7602
|
this.manifestDebounceMs = opts.manifestDebounceMs ?? 2e3;
|
|
7104
7603
|
this.maxFleetCostUsd = opts.directorBudget?.maxCostUsd ?? Number.POSITIVE_INFINITY;
|
|
7604
|
+
this.maxLeaderContextLoad = opts.maxLeaderContextLoad ?? 0.85;
|
|
7605
|
+
this.maxContext = opts.maxContext ?? 128e3;
|
|
7105
7606
|
this.stateCheckpoint = opts.stateCheckpointPath ? new DirectorStateCheckpoint(
|
|
7106
7607
|
opts.stateCheckpointPath,
|
|
7107
7608
|
{
|
|
@@ -7116,7 +7617,7 @@ var FleetManager = class {
|
|
|
7116
7617
|
this.fleet = new FleetBus();
|
|
7117
7618
|
this.usage = new FleetUsageAggregator(
|
|
7118
7619
|
this.fleet,
|
|
7119
|
-
(
|
|
7620
|
+
(_id, provider, model) => {
|
|
7120
7621
|
if (provider && model) return this.priceLookups.get(`${provider}/${model}`);
|
|
7121
7622
|
return void 0;
|
|
7122
7623
|
},
|
|
@@ -7155,7 +7656,7 @@ var FleetManager = class {
|
|
|
7155
7656
|
* which cap was exceeded. Does NOT throw — the caller decides
|
|
7156
7657
|
* how to surface the rejection.
|
|
7157
7658
|
*/
|
|
7158
|
-
canSpawn(
|
|
7659
|
+
canSpawn(_config) {
|
|
7159
7660
|
if (this.spawnDepth >= this.maxSpawnDepth) {
|
|
7160
7661
|
return { kind: "max_spawn_depth", limit: this.maxSpawnDepth, observed: this.spawnDepth };
|
|
7161
7662
|
}
|
|
@@ -7168,8 +7669,21 @@ var FleetManager = class {
|
|
|
7168
7669
|
return { kind: "max_cost_usd", limit: this.maxFleetCostUsd, observed: totalCost };
|
|
7169
7670
|
}
|
|
7170
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
|
+
}
|
|
7171
7682
|
return null;
|
|
7172
7683
|
}
|
|
7684
|
+
setLeaderContextPressure(tokens) {
|
|
7685
|
+
this.leaderContextPressure = tokens;
|
|
7686
|
+
}
|
|
7173
7687
|
/**
|
|
7174
7688
|
* Assign a memorable nickname (e.g. "Einstein (Bug Hunter)") to the config,
|
|
7175
7689
|
* record it so the same name is never reused, then record the spawn.
|
|
@@ -7364,6 +7878,6 @@ var FleetManager = class {
|
|
|
7364
7878
|
}
|
|
7365
7879
|
};
|
|
7366
7880
|
|
|
7367
|
-
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, makeAskTool, makeAssignTool, makeAwaitTasksTool, makeCollabDebugTool, makeDirectorSessionFactory, makeFleetEmitTool, makeFleetHealthTool, makeFleetSessionTool, makeFleetStatusTool, makeFleetUsageTool, makeLLMClassifier, makeRollUpTool, makeSpawnTool, makeTerminateTool, 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 };
|
|
7368
7882
|
//# sourceMappingURL=index.js.map
|
|
7369
7883
|
//# sourceMappingURL=index.js.map
|