@wrongstack/core 0.9.19 → 0.10.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +37 -33
- package/dist/{agent-bridge-DMVOX0cF.d.ts → agent-bridge-DBBGP5O4.d.ts} +1 -1
- package/dist/{agent-subagent-runner-C4qt9e5Y.d.ts → agent-subagent-runner-KwufTWwn.d.ts} +3 -4
- package/dist/compactor-D1RHFRmF.d.ts +41 -0
- package/dist/{config-CWva0qoL.d.ts → config-CJXBka2r.d.ts} +38 -2
- package/dist/{context-BRNbHmRM.d.ts → context-7u93AcGD.d.ts} +140 -0
- package/dist/coordination/index.d.ts +13 -13
- package/dist/coordination/index.js +786 -200
- package/dist/coordination/index.js.map +1 -1
- package/dist/{default-config-Uzla85H-.d.ts → default-config-DEXI4jsl.d.ts} +10 -1
- package/dist/defaults/index.d.ts +24 -24
- package/dist/defaults/index.js +1612 -528
- package/dist/defaults/index.js.map +1 -1
- package/dist/{events-CiG9qUM_.d.ts → events-Bw1K5E6K.d.ts} +49 -1
- package/dist/execution/index.d.ts +46 -32
- package/dist/execution/index.js +663 -169
- 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/goal-store-BeRsj7YX.d.ts +188 -0
- package/dist/{index-p95HQ22A.d.ts → index-DIt5TIaF.d.ts} +8 -8
- package/dist/{index-aizK8olO.d.ts → index-DgQWnZxP.d.ts} +11 -8
- package/dist/index.d.ts +338 -41
- package/dist/index.js +2747 -787
- 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 +57 -8
- package/dist/kernel/index.js.map +1 -1
- package/dist/{mcp-servers-BkVEqkRe.d.ts → mcp-servers-BgINZzuo.d.ts} +25 -3
- package/dist/models/index.d.ts +3 -3
- package/dist/models/index.js +164 -11
- package/dist/models/index.js.map +1 -1
- package/dist/models-registry-gwMAo6E3.d.ts +86 -0
- package/dist/{multi-agent-3ZnTB1aT.d.ts → multi-agent-C1O8EFzb.d.ts} +77 -24
- package/dist/{multi-agent-coordinator-bRaI_aD1.d.ts → multi-agent-coordinator-DoVSBFNx.d.ts} +20 -2
- package/dist/{null-fleet-bus-DKM3Iy9d.d.ts → null-fleet-bus-T-s3JaOt.d.ts} +413 -110
- package/dist/observability/index.d.ts +2 -2
- package/dist/{path-resolver-TcJfc29Y.d.ts → path-resolver-BfyK-I7w.d.ts} +2 -2
- package/dist/{permission-bPuzAy4x.d.ts → permission-B6sldrSp.d.ts} +1 -1
- package/dist/{permission-policy-BUQSutpl.d.ts → permission-policy-CtNscWOA.d.ts} +10 -9
- package/dist/{plan-templates-fkQTyz3U.d.ts → plan-templates-BWs6Je7j.d.ts} +8 -8
- package/dist/{provider-runner-BEpikbbN.d.ts → provider-runner-BmPe2RuU.d.ts} +3 -3
- package/dist/{retry-policy-BYkq0ugs.d.ts → retry-policy-KF18W4dg.d.ts} +1 -1
- package/dist/sdd/index.d.ts +9 -10
- package/dist/sdd/index.js +350 -122
- package/dist/sdd/index.js.map +1 -1
- package/dist/security/index.d.ts +65 -4
- package/dist/security/index.js +67 -19
- package/dist/security/index.js.map +1 -1
- package/dist/{selector-pox8abg0.d.ts → selector-DmXxpFyM.d.ts} +1 -1
- package/dist/{session-reader-CSWcb5Ga.d.ts → session-reader-CCSBfQmd.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 +392 -8
- package/dist/storage/index.js +796 -40
- package/dist/storage/index.js.map +1 -1
- package/dist/{system-prompt-Bs-Wliab.d.ts → system-prompt-CM6zOhd2.d.ts} +19 -1
- package/dist/{tool-executor-Boo3dekH.d.ts → tool-executor-D7RxLtg4.d.ts} +9 -4
- package/dist/types/index.d.ts +16 -16
- package/dist/types/index.js +404 -14
- package/dist/types/index.js.map +1 -1
- package/dist/utils/index.d.ts +40 -8
- package/dist/utils/index.js +127 -57
- package/dist/utils/index.js.map +1 -1
- package/dist/{wstack-paths-BCgmTNlG.d.ts → wstack-paths-eMXnY1_X.d.ts} +8 -0
- package/package.json +1 -1
- package/dist/compactor-DVTKL7XD.d.ts +0 -23
- package/dist/goal-store-C7jcumEh.d.ts +0 -96
- package/dist/models-registry-OG_30xqZ.d.ts +0 -46
|
@@ -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) {
|
|
@@ -877,19 +880,16 @@ var SubagentBudget = class _SubagentBudget {
|
|
|
877
880
|
tokenOutput = 0;
|
|
878
881
|
costUsd = 0;
|
|
879
882
|
startTime = null;
|
|
880
|
-
_onThreshold;
|
|
881
883
|
/**
|
|
882
|
-
*
|
|
883
|
-
*
|
|
884
|
-
*
|
|
885
|
-
*
|
|
886
|
-
* the first one lands and patches limits), flooding the FleetBus
|
|
887
|
-
* with redundant threshold events. Cleared in `negotiateExtension`'s
|
|
888
|
-
* `finally`.
|
|
884
|
+
* Timestamp of the most recent activity (iteration / tool call / token
|
|
885
|
+
* usage / streamed progress). Drives the idle timeout — reset by
|
|
886
|
+
* `markActivity()`. Initialised to `start()` time so a never-active agent
|
|
887
|
+
* still eventually trips its idle window.
|
|
889
888
|
*/
|
|
890
|
-
|
|
889
|
+
lastActivityTime = null;
|
|
890
|
+
_onThreshold;
|
|
891
891
|
/**
|
|
892
|
-
* Hard cap on how long `
|
|
892
|
+
* Hard cap on how long `_negotiateExtension` waits for the coordinator to
|
|
893
893
|
* respond before defaulting to 'stop'. Without this fallback an absent
|
|
894
894
|
* or hung listener (Director not built / event filter detached mid-run)
|
|
895
895
|
* leaves the budget over-limit and never enforces anything.
|
|
@@ -930,6 +930,24 @@ var SubagentBudget = class _SubagentBudget {
|
|
|
930
930
|
}
|
|
931
931
|
start() {
|
|
932
932
|
this.startTime = Date.now();
|
|
933
|
+
this.lastActivityTime = this.startTime;
|
|
934
|
+
}
|
|
935
|
+
/**
|
|
936
|
+
* Reset the idle clock. Called on any sign of forward progress —
|
|
937
|
+
* iterations, tool calls, token usage, and streamed tool/text progress —
|
|
938
|
+
* so a long-but-productive subagent never trips its `idleTimeoutMs`.
|
|
939
|
+
*/
|
|
940
|
+
markActivity() {
|
|
941
|
+
this.lastActivityTime = Date.now();
|
|
942
|
+
}
|
|
943
|
+
/**
|
|
944
|
+
* Milliseconds since the last activity signal. Returns 0 before `start()`
|
|
945
|
+
* (nothing to measure yet). Used by the coordinator watchdog to decide
|
|
946
|
+
* whether to re-arm (still active) or reap (genuinely idle).
|
|
947
|
+
*/
|
|
948
|
+
idleMs() {
|
|
949
|
+
const since = this.lastActivityTime ?? this.startTime;
|
|
950
|
+
return since === null ? 0 : Date.now() - since;
|
|
933
951
|
}
|
|
934
952
|
/** Returns true if we're within 10% of any limit — useful for pre-flight checks. */
|
|
935
953
|
isNearLimit() {
|
|
@@ -951,38 +969,88 @@ var SubagentBudget = class _SubagentBudget {
|
|
|
951
969
|
* - `mode === 'auto'` + no listener → throw `BudgetExceededError` (hard stop; no one to ask)
|
|
952
970
|
* - `mode === 'auto'` + listener → throw `BudgetThresholdSignal` with async decision promise
|
|
953
971
|
*/
|
|
954
|
-
|
|
972
|
+
/**
|
|
973
|
+
* Collects all exceeded budget kinds into a single NOOP-free negotiation.
|
|
974
|
+
* Called by recordIteration / recordToolCall / recordUsage — each may call
|
|
975
|
+
* this for its own kind. The first call starts the negotiation and stores
|
|
976
|
+
* the Promise in _pendingNegotiation. Subsequent calls for DIFFERENT
|
|
977
|
+
* kinds (while a negotiation is in flight) are NOOPs — they don't start
|
|
978
|
+
* new conversations with the coordinator. This prevents an EventBus flood
|
|
979
|
+
* when multiple budget kinds are exceeded simultaneously in one iteration.
|
|
980
|
+
*
|
|
981
|
+
* Returns the kinds that were found to be exceeded (for logging/debugging).
|
|
982
|
+
*/
|
|
983
|
+
checkLimits(elapsedMs) {
|
|
984
|
+
const exceeded = [];
|
|
985
|
+
if (this.limits.maxIterations !== void 0 && this.iterations > this.limits.maxIterations) {
|
|
986
|
+
exceeded.push({ kind: "iterations", used: this.iterations, limit: this.limits.maxIterations });
|
|
987
|
+
}
|
|
988
|
+
if (this.limits.maxToolCalls !== void 0 && this.toolCalls > this.limits.maxToolCalls) {
|
|
989
|
+
exceeded.push({ kind: "tool_calls", used: this.toolCalls, limit: this.limits.maxToolCalls });
|
|
990
|
+
}
|
|
991
|
+
const totalTokens = this.tokenInput + this.tokenOutput;
|
|
992
|
+
if (this.limits.maxTokens !== void 0 && totalTokens > this.limits.maxTokens) {
|
|
993
|
+
exceeded.push({ kind: "tokens", used: totalTokens, limit: this.limits.maxTokens });
|
|
994
|
+
}
|
|
995
|
+
if (this.limits.maxCostUsd !== void 0 && this.costUsd > this.limits.maxCostUsd) {
|
|
996
|
+
exceeded.push({ kind: "cost", used: this.costUsd, limit: this.limits.maxCostUsd });
|
|
997
|
+
}
|
|
998
|
+
if (elapsedMs !== void 0) {
|
|
999
|
+
const idle = this.idleMs();
|
|
1000
|
+
if (this.limits.idleTimeoutMs !== void 0 && idle > this.limits.idleTimeoutMs) {
|
|
1001
|
+
exceeded.push({ kind: "timeout", used: idle, limit: this.limits.idleTimeoutMs });
|
|
1002
|
+
} else if (this.limits.timeoutMs !== void 0 && elapsedMs > this.limits.timeoutMs) {
|
|
1003
|
+
exceeded.push({ kind: "timeout", used: elapsedMs, limit: this.limits.timeoutMs });
|
|
1004
|
+
}
|
|
1005
|
+
}
|
|
1006
|
+
if (exceeded.length === 0) return [];
|
|
955
1007
|
if (!this._onThreshold) {
|
|
956
|
-
|
|
1008
|
+
const first2 = exceeded[0];
|
|
1009
|
+
throw new BudgetExceededError(first2.kind, first2.limit, first2.used);
|
|
957
1010
|
}
|
|
958
1011
|
if (this._mode === "sync") {
|
|
959
|
-
|
|
1012
|
+
const first2 = exceeded[0];
|
|
1013
|
+
throw new BudgetExceededError(first2.kind, first2.limit, first2.used);
|
|
960
1014
|
}
|
|
961
1015
|
const bus = this._events;
|
|
962
1016
|
if (!bus || !bus.hasListenerFor("budget.threshold_reached")) {
|
|
963
|
-
|
|
1017
|
+
const first2 = exceeded[0];
|
|
1018
|
+
throw new BudgetExceededError(first2.kind, first2.limit, first2.used);
|
|
964
1019
|
}
|
|
965
|
-
|
|
966
|
-
|
|
967
|
-
|
|
968
|
-
|
|
1020
|
+
for (const entry of exceeded) {
|
|
1021
|
+
if (this._pendingNegotiations.has(entry.kind)) continue;
|
|
1022
|
+
const decision2 = this._negotiateExtension(entry.kind, exceeded);
|
|
1023
|
+
this._pendingNegotiations.set(entry.kind, decision2);
|
|
1024
|
+
}
|
|
1025
|
+
const first = exceeded[0];
|
|
1026
|
+
const decision = this._pendingNegotiations.get(first.kind);
|
|
1027
|
+
throw new BudgetThresholdSignal(first.kind, first.limit, first.used, decision);
|
|
969
1028
|
}
|
|
1029
|
+
/**
|
|
1030
|
+
* Per-kind in-flight negotiation Promises. Each budget kind can have its
|
|
1031
|
+
* own concurrent negotiation — e.g. iterations and timeout can both
|
|
1032
|
+
* be exceeded simultaneously without blocking each other. The same kind
|
|
1033
|
+
* cannot start two concurrent negotiations (dedup guard).
|
|
1034
|
+
* Cleared in `_negotiateExtension`'s `finally` block.
|
|
1035
|
+
*/
|
|
1036
|
+
_pendingNegotiations = /* @__PURE__ */ new Map();
|
|
970
1037
|
/**
|
|
971
1038
|
* Drive the threshold handler to a decision. Resolves with `'stop'`
|
|
972
1039
|
* (signal the runner to abort) or `{ extend: ... }` (limits already
|
|
973
|
-
* patched in-place; the runner should not abort).
|
|
974
|
-
*
|
|
1040
|
+
* patched in-place; the runner should not abort). Clears the
|
|
1041
|
+
* per-kind slot in `_pendingNegotiations` in `finally`.
|
|
975
1042
|
*
|
|
976
1043
|
* The 'continue' return from a sync handler is treated as
|
|
977
1044
|
* `{ extend: {} }` — keep going without patching; next overrun fires
|
|
978
1045
|
* a fresh signal.
|
|
979
1046
|
*/
|
|
980
|
-
async
|
|
1047
|
+
async _negotiateExtension(kind, exceeded) {
|
|
981
1048
|
try {
|
|
1049
|
+
const first = exceeded[0];
|
|
982
1050
|
const result = this._onThreshold({
|
|
983
|
-
kind,
|
|
984
|
-
used,
|
|
985
|
-
limit,
|
|
1051
|
+
kind: first.kind,
|
|
1052
|
+
used: first.used,
|
|
1053
|
+
limit: first.limit,
|
|
986
1054
|
requestDecision: () => {
|
|
987
1055
|
const bus = this._events;
|
|
988
1056
|
if (!bus || !bus.hasListenerFor("budget.threshold_reached")) {
|
|
@@ -999,20 +1067,22 @@ var SubagentBudget = class _SubagentBudget {
|
|
|
999
1067
|
() => respond("stop"),
|
|
1000
1068
|
_SubagentBudget.DECISION_TIMEOUT_MS
|
|
1001
1069
|
);
|
|
1002
|
-
|
|
1003
|
-
|
|
1004
|
-
|
|
1005
|
-
|
|
1006
|
-
|
|
1007
|
-
|
|
1008
|
-
|
|
1009
|
-
|
|
1010
|
-
|
|
1011
|
-
|
|
1012
|
-
|
|
1013
|
-
|
|
1014
|
-
|
|
1015
|
-
|
|
1070
|
+
for (const { kind: kind2, used, limit } of exceeded) {
|
|
1071
|
+
bus.emit("budget.threshold_reached", {
|
|
1072
|
+
kind: kind2,
|
|
1073
|
+
used,
|
|
1074
|
+
limit,
|
|
1075
|
+
timeoutMs: _SubagentBudget.DECISION_TIMEOUT_MS,
|
|
1076
|
+
extend: (extra) => {
|
|
1077
|
+
clearTimeout(fallback);
|
|
1078
|
+
respond({ extend: extra });
|
|
1079
|
+
},
|
|
1080
|
+
deny: () => {
|
|
1081
|
+
clearTimeout(fallback);
|
|
1082
|
+
respond("stop");
|
|
1083
|
+
}
|
|
1084
|
+
});
|
|
1085
|
+
}
|
|
1016
1086
|
});
|
|
1017
1087
|
}
|
|
1018
1088
|
});
|
|
@@ -1036,54 +1106,59 @@ var SubagentBudget = class _SubagentBudget {
|
|
|
1036
1106
|
if (ext.timeoutMs !== void 0) {
|
|
1037
1107
|
this.limits.timeoutMs = ext.timeoutMs;
|
|
1038
1108
|
}
|
|
1109
|
+
if (ext.idleTimeoutMs !== void 0) {
|
|
1110
|
+
this.limits.idleTimeoutMs = ext.idleTimeoutMs;
|
|
1111
|
+
}
|
|
1039
1112
|
return decision;
|
|
1040
1113
|
} finally {
|
|
1041
|
-
this.
|
|
1114
|
+
this._pendingNegotiations.delete(kind);
|
|
1042
1115
|
}
|
|
1043
1116
|
}
|
|
1044
1117
|
recordIteration() {
|
|
1045
1118
|
this.iterations++;
|
|
1046
|
-
|
|
1047
|
-
|
|
1048
|
-
}
|
|
1119
|
+
this.markActivity();
|
|
1120
|
+
void this.checkLimits();
|
|
1049
1121
|
}
|
|
1050
1122
|
recordToolCall() {
|
|
1051
1123
|
this.toolCalls++;
|
|
1052
|
-
|
|
1053
|
-
|
|
1054
|
-
}
|
|
1124
|
+
this.markActivity();
|
|
1125
|
+
void this.checkLimits();
|
|
1055
1126
|
}
|
|
1056
1127
|
recordUsage(usage, costUsd = 0) {
|
|
1057
1128
|
this.tokenInput += usage.input;
|
|
1058
1129
|
this.tokenOutput += usage.output;
|
|
1059
1130
|
this.costUsd += costUsd;
|
|
1060
|
-
|
|
1061
|
-
|
|
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
|
-
}
|
|
1131
|
+
this.markActivity();
|
|
1132
|
+
void this.checkLimits();
|
|
1067
1133
|
}
|
|
1068
1134
|
/**
|
|
1069
|
-
* Wall-clock budget check. Unlike other limits, timeout
|
|
1070
|
-
*
|
|
1071
|
-
*
|
|
1072
|
-
*
|
|
1073
|
-
*
|
|
1074
|
-
*
|
|
1135
|
+
* Wall-clock budget check. Unlike other limits, timeout is always a hard stop
|
|
1136
|
+
* — wall-clock time cannot be "extended" by the coordinator, so it throws
|
|
1137
|
+
* synchronously rather than entering the negotiation flow.
|
|
1138
|
+
*
|
|
1139
|
+
* Decision table:
|
|
1140
|
+
* - no `onThreshold` handler → throw `BudgetExceededError`
|
|
1141
|
+
* - `mode === 'sync'` → throw `BudgetExceededError`
|
|
1142
|
+
* - `mode === 'auto'` + no listener → throw `BudgetExceededError`
|
|
1143
|
+
* - `mode === 'auto'` + listener → throw `BudgetExceededError` (timeout is not extendable)
|
|
1075
1144
|
*/
|
|
1076
1145
|
checkTimeout() {
|
|
1077
|
-
if (this.startTime === null
|
|
1146
|
+
if (this.startTime === null) return;
|
|
1147
|
+
const { timeoutMs, idleTimeoutMs } = this.limits;
|
|
1148
|
+
if (timeoutMs === void 0 && idleTimeoutMs === void 0) return;
|
|
1078
1149
|
const elapsed = Date.now() - this.startTime;
|
|
1079
|
-
|
|
1080
|
-
|
|
1081
|
-
|
|
1150
|
+
const wallTripped = timeoutMs !== void 0 && elapsed > timeoutMs;
|
|
1151
|
+
const idleTripped = idleTimeoutMs !== void 0 && this.idleMs() > idleTimeoutMs;
|
|
1152
|
+
if (!wallTripped && !idleTripped) return;
|
|
1153
|
+
void this.checkLimits(elapsed);
|
|
1082
1154
|
}
|
|
1083
|
-
/** Returns true if a timeout has occurred without throwing.
|
|
1155
|
+
/** Returns true if a wall-clock or idle timeout has occurred without throwing. */
|
|
1084
1156
|
isTimedOut() {
|
|
1085
|
-
if (this.startTime === null
|
|
1086
|
-
|
|
1157
|
+
if (this.startTime === null) return false;
|
|
1158
|
+
const { timeoutMs, idleTimeoutMs } = this.limits;
|
|
1159
|
+
if (timeoutMs !== void 0 && Date.now() - this.startTime > timeoutMs) return true;
|
|
1160
|
+
if (idleTimeoutMs !== void 0 && this.idleMs() > idleTimeoutMs) return true;
|
|
1161
|
+
return false;
|
|
1087
1162
|
}
|
|
1088
1163
|
usage() {
|
|
1089
1164
|
return {
|
|
@@ -3485,6 +3560,7 @@ var FLEET_ROSTER = {
|
|
|
3485
3560
|
ALL_AGENT_DEFINITIONS.map((d) => [d.config.role, d.config])
|
|
3486
3561
|
)
|
|
3487
3562
|
};
|
|
3563
|
+
var DEFAULT_IDLE_TIMEOUT_MS = 15 * 60 * 1e3;
|
|
3488
3564
|
var FLEET_ROSTER_BUDGETS = {
|
|
3489
3565
|
"audit-log": { timeoutMs: 7.5 * 60 * 60 * 1e3, maxIterations: 5e3, maxToolCalls: 15e3 },
|
|
3490
3566
|
"bug-hunter": { timeoutMs: 10 * 60 * 60 * 1e3, maxIterations: 8e3, maxToolCalls: 2e4 },
|
|
@@ -3496,7 +3572,7 @@ var FLEET_ROSTER_BUDGETS = {
|
|
|
3496
3572
|
)
|
|
3497
3573
|
};
|
|
3498
3574
|
var GENERIC_SUBAGENT_BUDGET = {
|
|
3499
|
-
|
|
3575
|
+
idleTimeoutMs: DEFAULT_IDLE_TIMEOUT_MS,
|
|
3500
3576
|
maxIterations: 5e3,
|
|
3501
3577
|
maxToolCalls: 15e3
|
|
3502
3578
|
};
|
|
@@ -3506,7 +3582,13 @@ function applyRosterBudget(cfg) {
|
|
|
3506
3582
|
if (!defaultBudget) return cfg;
|
|
3507
3583
|
return {
|
|
3508
3584
|
...cfg,
|
|
3509
|
-
|
|
3585
|
+
// Wall-clock cap is opt-in only: forward an explicit `cfg.timeoutMs`, but
|
|
3586
|
+
// do NOT impose the roster's historical multi-hour wall-clock default — it
|
|
3587
|
+
// killed agents that were still actively working. Reaping is idle-based.
|
|
3588
|
+
timeoutMs: cfg.timeoutMs,
|
|
3589
|
+
// Idle window is the default reaper. Resets on activity, so a long-but-
|
|
3590
|
+
// productive run is never killed; only a genuine stall is reaped.
|
|
3591
|
+
idleTimeoutMs: cfg.idleTimeoutMs ?? defaultBudget.idleTimeoutMs ?? DEFAULT_IDLE_TIMEOUT_MS,
|
|
3510
3592
|
maxIterations: cfg.maxIterations ?? defaultBudget.maxIterations,
|
|
3511
3593
|
maxToolCalls: cfg.maxToolCalls ?? defaultBudget.maxToolCalls,
|
|
3512
3594
|
maxTokens: cfg.maxTokens ?? defaultBudget.maxTokens,
|
|
@@ -3583,6 +3665,15 @@ var DefaultMultiAgentCoordinator = class extends EventEmitter {
|
|
|
3583
3665
|
runner;
|
|
3584
3666
|
fleetBus;
|
|
3585
3667
|
subagents = /* @__PURE__ */ new Map();
|
|
3668
|
+
/**
|
|
3669
|
+
* Base nickname keys already handed out this run (e.g. `einstein`, `tesla`).
|
|
3670
|
+
* Prevents two workers sharing a name. Direct `coordinator.spawn()` callers
|
|
3671
|
+
* (parallel/eternal engine, SDD parallel run) don't go through
|
|
3672
|
+
* `Director.spawn()` where nicknames are normally assigned, so the
|
|
3673
|
+
* coordinator upgrades placeholder names ("Executor", "slot-ab12cd", role
|
|
3674
|
+
* names) to memorable ones here — that's what surfaces in the fleet monitor.
|
|
3675
|
+
*/
|
|
3676
|
+
usedNicknames = /* @__PURE__ */ new Set();
|
|
3586
3677
|
pendingTasks = [];
|
|
3587
3678
|
completedResults = [];
|
|
3588
3679
|
totalIterations = 0;
|
|
@@ -3631,7 +3722,26 @@ var DefaultMultiAgentCoordinator = class extends EventEmitter {
|
|
|
3631
3722
|
this.config.maxConcurrent = Math.floor(n);
|
|
3632
3723
|
this.tryDispatchNext();
|
|
3633
3724
|
}
|
|
3725
|
+
/**
|
|
3726
|
+
* Upgrade a placeholder/role-derived name to a memorable scientist nickname
|
|
3727
|
+
* (e.g. "Einstein (Executor)"). A name is treated as a placeholder when it is
|
|
3728
|
+
* empty, equals the role (case-insensitive), is a generic default
|
|
3729
|
+
* ("subagent"/"adhoc"/"generic"), or is an auto-generated `slot-…` id.
|
|
3730
|
+
* Explicit, human-chosen names — including nicknames already assigned by
|
|
3731
|
+
* `Director.spawn()` — are left untouched, so this never double-assigns.
|
|
3732
|
+
*/
|
|
3733
|
+
withNickname(subagent) {
|
|
3734
|
+
const role = subagent.role ?? "subagent";
|
|
3735
|
+
const name = subagent.name?.trim() ?? "";
|
|
3736
|
+
const isPlaceholder = name === "" || name.toLowerCase() === role.toLowerCase() || name === "subagent" || name === "adhoc" || name === "generic" || /^slot-/.test(name);
|
|
3737
|
+
if (!isPlaceholder) return subagent;
|
|
3738
|
+
const nickname = assignNickname(role, this.usedNicknames);
|
|
3739
|
+
const baseKey = nickname.split(" ")[0].toLowerCase().replace(/[^a-z0-9-]/g, "-");
|
|
3740
|
+
this.usedNicknames.add(baseKey);
|
|
3741
|
+
return { ...subagent, name: nickname };
|
|
3742
|
+
}
|
|
3634
3743
|
async spawn(subagent) {
|
|
3744
|
+
subagent = this.withNickname(subagent);
|
|
3635
3745
|
const id = subagent.id || randomUUID();
|
|
3636
3746
|
if (this.subagents.has(id)) {
|
|
3637
3747
|
throw new Error(`Subagent id "${id}" already exists \u2014 refusing to overwrite`);
|
|
@@ -3939,13 +4049,17 @@ var DefaultMultiAgentCoordinator = class extends EventEmitter {
|
|
|
3939
4049
|
const rawMaxTokens = subagent.config.maxTokens;
|
|
3940
4050
|
const rawMaxCostUsd = subagent.config.maxCostUsd;
|
|
3941
4051
|
const rawTimeoutMs = subagent.config.timeoutMs;
|
|
4052
|
+
const rawIdleTimeoutMs = subagent.config.idleTimeoutMs;
|
|
3942
4053
|
const configWithRosterDefaults = applyRosterBudget(subagent.config);
|
|
3943
4054
|
const budget = new SubagentBudget({
|
|
3944
4055
|
maxIterations: rawMaxIterations ?? this.config.defaultBudget?.maxIterations ?? configWithRosterDefaults.maxIterations,
|
|
3945
4056
|
maxToolCalls: rawMaxToolCalls ?? this.config.defaultBudget?.maxToolCalls ?? configWithRosterDefaults.maxToolCalls,
|
|
3946
4057
|
maxTokens: rawMaxTokens ?? this.config.defaultBudget?.maxTokens ?? configWithRosterDefaults.maxTokens,
|
|
3947
4058
|
maxCostUsd: rawMaxCostUsd ?? this.config.defaultBudget?.maxCostUsd ?? configWithRosterDefaults.maxCostUsd,
|
|
3948
|
-
|
|
4059
|
+
// Wall-clock cap is opt-in (explicit config / defaultBudget only); the
|
|
4060
|
+
// roster no longer supplies one. Idle is the default reaper.
|
|
4061
|
+
timeoutMs: rawTimeoutMs ?? this.config.defaultBudget?.timeoutMs ?? configWithRosterDefaults.timeoutMs,
|
|
4062
|
+
idleTimeoutMs: rawIdleTimeoutMs ?? this.config.defaultBudget?.idleTimeoutMs ?? configWithRosterDefaults.idleTimeoutMs
|
|
3949
4063
|
});
|
|
3950
4064
|
subagent.activeBudget = budget;
|
|
3951
4065
|
if (!this.runner) {
|
|
@@ -3992,63 +4106,79 @@ var DefaultMultiAgentCoordinator = class extends EventEmitter {
|
|
|
3992
4106
|
}
|
|
3993
4107
|
async executeWithTimeout(runner, task, ctx, budget) {
|
|
3994
4108
|
const initialTimeoutMs = budget.limits.timeoutMs;
|
|
3995
|
-
|
|
4109
|
+
const idleLimitMs = budget.limits.idleTimeoutMs;
|
|
4110
|
+
if (initialTimeoutMs === void 0 && idleLimitMs === void 0) {
|
|
4111
|
+
return runner(task, ctx);
|
|
4112
|
+
}
|
|
3996
4113
|
const start = Date.now();
|
|
3997
4114
|
let timer = null;
|
|
3998
4115
|
const timeoutPromise = new Promise((_, reject) => {
|
|
3999
4116
|
const armFor = (ms) => {
|
|
4000
4117
|
if (timer) clearTimeout(timer);
|
|
4001
|
-
timer = setTimeout(
|
|
4002
|
-
|
|
4003
|
-
|
|
4004
|
-
|
|
4005
|
-
|
|
4006
|
-
|
|
4118
|
+
timer = setTimeout(onTick, Math.max(0, ms));
|
|
4119
|
+
};
|
|
4120
|
+
const scheduleNext = () => {
|
|
4121
|
+
const wallRemaining = initialTimeoutMs === void 0 ? Number.POSITIVE_INFINITY : (budget.limits.timeoutMs ?? initialTimeoutMs) - (Date.now() - start);
|
|
4122
|
+
const idleRemaining = idleLimitMs === void 0 ? Number.POSITIVE_INFINITY : (budget.limits.idleTimeoutMs ?? idleLimitMs) - budget.idleMs();
|
|
4123
|
+
armFor(Math.max(25, Math.min(wallRemaining, idleRemaining)));
|
|
4124
|
+
};
|
|
4125
|
+
const onTick = async () => {
|
|
4126
|
+
const elapsed = Date.now() - start;
|
|
4127
|
+
const wallLimit = initialTimeoutMs === void 0 ? void 0 : budget.limits.timeoutMs ?? initialTimeoutMs;
|
|
4128
|
+
const idleLimit = idleLimitMs === void 0 ? void 0 : budget.limits.idleTimeoutMs ?? idleLimitMs;
|
|
4129
|
+
const wallExceeded = wallLimit !== void 0 && elapsed >= wallLimit;
|
|
4130
|
+
const idleExceeded = idleLimit !== void 0 && budget.idleMs() >= idleLimit;
|
|
4131
|
+
if (idleExceeded && !wallExceeded) {
|
|
4132
|
+
this.subagents.get(ctx.subagentId)?.abortController.abort();
|
|
4133
|
+
reject(new BudgetExceededError("timeout", idleLimit, budget.idleMs()));
|
|
4134
|
+
return;
|
|
4135
|
+
}
|
|
4136
|
+
if (!wallExceeded) {
|
|
4137
|
+
scheduleNext();
|
|
4138
|
+
return;
|
|
4139
|
+
}
|
|
4140
|
+
const limit = wallLimit;
|
|
4141
|
+
if (!budget.onThreshold) {
|
|
4142
|
+
this.subagents.get(ctx.subagentId)?.abortController.abort();
|
|
4143
|
+
reject(new BudgetExceededError("timeout", limit, elapsed));
|
|
4144
|
+
return;
|
|
4145
|
+
}
|
|
4146
|
+
try {
|
|
4147
|
+
const result = budget.onThreshold({
|
|
4148
|
+
kind: "timeout",
|
|
4149
|
+
used: elapsed,
|
|
4150
|
+
limit,
|
|
4151
|
+
requestDecision: () => new Promise((resolveDecision) => {
|
|
4152
|
+
budget._events?.emit("budget.threshold_reached", {
|
|
4153
|
+
kind: "timeout",
|
|
4154
|
+
used: elapsed,
|
|
4155
|
+
limit,
|
|
4156
|
+
timeoutMs: 6e4,
|
|
4157
|
+
extend: (extra) => resolveDecision({ extend: extra }),
|
|
4158
|
+
deny: () => resolveDecision("stop")
|
|
4159
|
+
});
|
|
4160
|
+
})
|
|
4161
|
+
});
|
|
4162
|
+
const decision = typeof result === "string" ? result : await result;
|
|
4163
|
+
if (decision === "continue" || decision === "throw" || decision === "stop") {
|
|
4164
|
+
armFor(Math.max(1e3, limit));
|
|
4007
4165
|
return;
|
|
4008
4166
|
}
|
|
4009
|
-
|
|
4010
|
-
|
|
4011
|
-
|
|
4012
|
-
|
|
4013
|
-
limit,
|
|
4014
|
-
requestDecision: () => new Promise((resolveDecision) => {
|
|
4015
|
-
budget._events?.emit("budget.threshold_reached", {
|
|
4016
|
-
kind: "timeout",
|
|
4017
|
-
used: elapsed,
|
|
4018
|
-
limit,
|
|
4019
|
-
timeoutMs: 6e4,
|
|
4020
|
-
extend: (extra) => resolveDecision({ extend: extra }),
|
|
4021
|
-
deny: () => resolveDecision("stop")
|
|
4022
|
-
});
|
|
4023
|
-
})
|
|
4024
|
-
});
|
|
4025
|
-
const decision = typeof result === "string" ? result : await result;
|
|
4026
|
-
if (decision === "continue") {
|
|
4027
|
-
armFor(Math.max(1e3, limit));
|
|
4028
|
-
return;
|
|
4029
|
-
}
|
|
4030
|
-
if (decision === "throw" || decision === "stop") {
|
|
4031
|
-
armFor(Math.max(1e3, limit));
|
|
4032
|
-
return;
|
|
4033
|
-
}
|
|
4034
|
-
if (decision.extend.timeoutMs !== void 0) {
|
|
4035
|
-
budget.limits.timeoutMs = decision.extend.timeoutMs;
|
|
4036
|
-
const newLimit = decision.extend.timeoutMs;
|
|
4037
|
-
const remaining = Math.max(1e3, newLimit - elapsed);
|
|
4038
|
-
armFor(remaining);
|
|
4039
|
-
return;
|
|
4040
|
-
}
|
|
4041
|
-
this.subagents.get(ctx.subagentId)?.abortController.abort();
|
|
4042
|
-
reject(new BudgetExceededError("timeout", limit, elapsed));
|
|
4043
|
-
} catch (err) {
|
|
4044
|
-
this.subagents.get(ctx.subagentId)?.abortController.abort();
|
|
4045
|
-
reject(
|
|
4046
|
-
err instanceof BudgetExceededError ? err : new BudgetExceededError("timeout", limit, elapsed)
|
|
4047
|
-
);
|
|
4167
|
+
if (decision.extend.timeoutMs !== void 0) {
|
|
4168
|
+
budget.limits.timeoutMs = decision.extend.timeoutMs;
|
|
4169
|
+
scheduleNext();
|
|
4170
|
+
return;
|
|
4048
4171
|
}
|
|
4049
|
-
|
|
4172
|
+
this.subagents.get(ctx.subagentId)?.abortController.abort();
|
|
4173
|
+
reject(new BudgetExceededError("timeout", limit, elapsed));
|
|
4174
|
+
} catch (err) {
|
|
4175
|
+
this.subagents.get(ctx.subagentId)?.abortController.abort();
|
|
4176
|
+
reject(
|
|
4177
|
+
err instanceof BudgetExceededError ? err : new BudgetExceededError("timeout", limit, elapsed)
|
|
4178
|
+
);
|
|
4179
|
+
}
|
|
4050
4180
|
};
|
|
4051
|
-
|
|
4181
|
+
scheduleNext();
|
|
4052
4182
|
});
|
|
4053
4183
|
try {
|
|
4054
4184
|
return await Promise.race([runner(task, ctx), timeoutPromise]);
|
|
@@ -4510,7 +4640,7 @@ function makeRollUpTool(director) {
|
|
|
4510
4640
|
function makeTerminateTool(director) {
|
|
4511
4641
|
return {
|
|
4512
4642
|
name: "terminate_subagent",
|
|
4513
|
-
description:
|
|
4643
|
+
description: 'Forcibly abort a subagent. The subagent finishes its current iteration then exits with status "stopped".',
|
|
4514
4644
|
permission: "auto",
|
|
4515
4645
|
mutating: true,
|
|
4516
4646
|
inputSchema: { type: "object", properties: { subagentId: { type: "string", description: "Subagent to abort." } }, required: ["subagentId"] },
|
|
@@ -4521,6 +4651,19 @@ function makeTerminateTool(director) {
|
|
|
4521
4651
|
}
|
|
4522
4652
|
};
|
|
4523
4653
|
}
|
|
4654
|
+
function makeTerminateAllTool(director) {
|
|
4655
|
+
return {
|
|
4656
|
+
name: "terminate_all",
|
|
4657
|
+
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.',
|
|
4658
|
+
permission: "auto",
|
|
4659
|
+
mutating: true,
|
|
4660
|
+
inputSchema: { type: "object", properties: {}, required: [] },
|
|
4661
|
+
async execute() {
|
|
4662
|
+
await director.terminateAll();
|
|
4663
|
+
return { ok: true, message: `Fleet shutdown complete \u2014 all subagents stopped, pending tasks drained.` };
|
|
4664
|
+
}
|
|
4665
|
+
};
|
|
4666
|
+
}
|
|
4524
4667
|
function makeFleetStatusTool(director) {
|
|
4525
4668
|
return {
|
|
4526
4669
|
name: "fleet_status",
|
|
@@ -4663,7 +4806,7 @@ function makeCollabDebugTool(director) {
|
|
|
4663
4806
|
function makeFleetEmitTool(director) {
|
|
4664
4807
|
return {
|
|
4665
4808
|
name: "fleet_emit",
|
|
4666
|
-
description: "Emit a structured event on the FleetBus.
|
|
4809
|
+
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
4810
|
permission: "auto",
|
|
4668
4811
|
mutating: false,
|
|
4669
4812
|
inputSchema: {
|
|
@@ -4671,14 +4814,14 @@ function makeFleetEmitTool(director) {
|
|
|
4671
4814
|
properties: {
|
|
4672
4815
|
type: {
|
|
4673
4816
|
type: "string",
|
|
4674
|
-
description: "Event type
|
|
4817
|
+
description: "Event type string (e.g. bug.found, refactor.plan, critic.evaluation, progress, result)."
|
|
4675
4818
|
},
|
|
4676
4819
|
payload: {
|
|
4677
4820
|
type: "object",
|
|
4678
|
-
description: "Event payload
|
|
4821
|
+
description: "Event payload. Structure depends on event type. Use null if no payload."
|
|
4679
4822
|
}
|
|
4680
4823
|
},
|
|
4681
|
-
required: ["type"
|
|
4824
|
+
required: ["type"]
|
|
4682
4825
|
},
|
|
4683
4826
|
async execute(input) {
|
|
4684
4827
|
const i = input;
|
|
@@ -4686,12 +4829,31 @@ function makeFleetEmitTool(director) {
|
|
|
4686
4829
|
subagentId: director.id,
|
|
4687
4830
|
ts: Date.now(),
|
|
4688
4831
|
type: i.type,
|
|
4689
|
-
payload: i.payload
|
|
4832
|
+
payload: i.payload ?? {}
|
|
4690
4833
|
});
|
|
4691
4834
|
return { ok: true, event: i.type };
|
|
4692
4835
|
}
|
|
4693
4836
|
};
|
|
4694
4837
|
}
|
|
4838
|
+
function makeWorkCompleteTool(director) {
|
|
4839
|
+
return {
|
|
4840
|
+
name: "work_complete",
|
|
4841
|
+
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.",
|
|
4842
|
+
permission: "auto",
|
|
4843
|
+
mutating: false,
|
|
4844
|
+
inputSchema: { type: "object", properties: {}, required: [] },
|
|
4845
|
+
async execute() {
|
|
4846
|
+
director.workComplete();
|
|
4847
|
+
return { ok: true, message: "Fleet wind-down signaled. No new spawns or task dispatches." };
|
|
4848
|
+
}
|
|
4849
|
+
};
|
|
4850
|
+
}
|
|
4851
|
+
var DirectorAlertLevel = /* @__PURE__ */ ((DirectorAlertLevel2) => {
|
|
4852
|
+
DirectorAlertLevel2["WARNING"] = "warning";
|
|
4853
|
+
DirectorAlertLevel2["CRITICAL"] = "critical";
|
|
4854
|
+
DirectorAlertLevel2["CANCELLED"] = "cancelled";
|
|
4855
|
+
return DirectorAlertLevel2;
|
|
4856
|
+
})(DirectorAlertLevel || {});
|
|
4695
4857
|
var CollabSession = class extends EventEmitter {
|
|
4696
4858
|
sessionId;
|
|
4697
4859
|
options;
|
|
@@ -4706,6 +4868,14 @@ var CollabSession = class extends EventEmitter {
|
|
|
4706
4868
|
disposers = new Array();
|
|
4707
4869
|
settled = false;
|
|
4708
4870
|
timeoutMs;
|
|
4871
|
+
cancelled = false;
|
|
4872
|
+
alerts = [];
|
|
4873
|
+
/** Tracks tool call counts per subagent for progress-based timeout decisions. */
|
|
4874
|
+
progressBySubagent = /* @__PURE__ */ new Map();
|
|
4875
|
+
/** Last tool call count when a timeout warning was handled. */
|
|
4876
|
+
lastTimeoutProgress = /* @__PURE__ */ new Map();
|
|
4877
|
+
/** Session-level timeout timer handle (cleared on cancel or natural completion). */
|
|
4878
|
+
_timeoutTimer;
|
|
4709
4879
|
constructor(director, fleetBus, options) {
|
|
4710
4880
|
super();
|
|
4711
4881
|
this.sessionId = randomUUID();
|
|
@@ -4723,11 +4893,23 @@ var CollabSession = class extends EventEmitter {
|
|
|
4723
4893
|
};
|
|
4724
4894
|
}
|
|
4725
4895
|
}
|
|
4896
|
+
get id() {
|
|
4897
|
+
return this.sessionId;
|
|
4898
|
+
}
|
|
4899
|
+
getSessionAlerts() {
|
|
4900
|
+
return [...this.alerts];
|
|
4901
|
+
}
|
|
4902
|
+
isCancelled() {
|
|
4903
|
+
return this.cancelled;
|
|
4904
|
+
}
|
|
4726
4905
|
/**
|
|
4727
|
-
*
|
|
4728
|
-
*
|
|
4729
|
-
*
|
|
4906
|
+
* Snapshot of role → subagentId map. The Director calls coordinator.stop()
|
|
4907
|
+
* for each agent when cancelling the session, using this map to enumerate
|
|
4908
|
+
* all three collab agents.
|
|
4730
4909
|
*/
|
|
4910
|
+
getSubagentIds() {
|
|
4911
|
+
return new Map(this.subagentIds);
|
|
4912
|
+
}
|
|
4731
4913
|
async buildSnapshot() {
|
|
4732
4914
|
if (this.snapshot.files.length > 0) return this.snapshot;
|
|
4733
4915
|
for (const filePath of this.options.targetPaths) {
|
|
@@ -4743,9 +4925,30 @@ var CollabSession = class extends EventEmitter {
|
|
|
4743
4925
|
return this.snapshot;
|
|
4744
4926
|
}
|
|
4745
4927
|
/**
|
|
4746
|
-
*
|
|
4747
|
-
*
|
|
4928
|
+
* Cancel the session. Emits director.cancel_collab on the FleetBus so all
|
|
4929
|
+
* collab agents finish early. The session-level timeout timer is also cleared.
|
|
4930
|
+
* Safe to call multiple times (idempotent after first call).
|
|
4748
4931
|
*/
|
|
4932
|
+
cancel(reason = "Director cancelled collab session") {
|
|
4933
|
+
if (this.settled) return;
|
|
4934
|
+
this.cancelled = true;
|
|
4935
|
+
if (this._timeoutTimer) {
|
|
4936
|
+
clearTimeout(this._timeoutTimer);
|
|
4937
|
+
this._timeoutTimer = void 0;
|
|
4938
|
+
}
|
|
4939
|
+
this.fleetBus.emit({
|
|
4940
|
+
subagentId: this.director.id,
|
|
4941
|
+
ts: Date.now(),
|
|
4942
|
+
type: "director.cancel_collab",
|
|
4943
|
+
payload: { sessionId: this.sessionId, reason, cancelledAt: (/* @__PURE__ */ new Date()).toISOString() }
|
|
4944
|
+
});
|
|
4945
|
+
this.fleetBus.emit({
|
|
4946
|
+
subagentId: this.director.id,
|
|
4947
|
+
ts: Date.now(),
|
|
4948
|
+
type: "collab.cancelled",
|
|
4949
|
+
payload: { sessionId: this.sessionId, reason }
|
|
4950
|
+
});
|
|
4951
|
+
}
|
|
4749
4952
|
async start() {
|
|
4750
4953
|
if (this.settled) throw new Error("session already settled");
|
|
4751
4954
|
this.settled = true;
|
|
@@ -4760,10 +4963,10 @@ var CollabSession = class extends EventEmitter {
|
|
|
4760
4963
|
this.subagentIds.set("refactor-planner", refactorPlannerId);
|
|
4761
4964
|
this.subagentIds.set("critic", criticId);
|
|
4762
4965
|
const timeout = new Promise((_, reject) => {
|
|
4763
|
-
setTimeout(
|
|
4764
|
-
(
|
|
4765
|
-
this.timeoutMs
|
|
4766
|
-
);
|
|
4966
|
+
this._timeoutTimer = setTimeout(() => {
|
|
4967
|
+
this.cancel("Session-level timeout reached");
|
|
4968
|
+
reject(new Error(`CollabSession timed out after ${this.timeoutMs}ms`));
|
|
4969
|
+
}, this.timeoutMs);
|
|
4767
4970
|
});
|
|
4768
4971
|
let results;
|
|
4769
4972
|
try {
|
|
@@ -4776,6 +4979,10 @@ var CollabSession = class extends EventEmitter {
|
|
|
4776
4979
|
timeout
|
|
4777
4980
|
]);
|
|
4778
4981
|
} catch (err) {
|
|
4982
|
+
if (this._timeoutTimer) {
|
|
4983
|
+
clearTimeout(this._timeoutTimer);
|
|
4984
|
+
this._timeoutTimer = void 0;
|
|
4985
|
+
}
|
|
4779
4986
|
this.cleanup();
|
|
4780
4987
|
const error = err instanceof Error ? err : new Error(String(err));
|
|
4781
4988
|
this.emit("session.error", error);
|
|
@@ -4789,17 +4996,6 @@ var CollabSession = class extends EventEmitter {
|
|
|
4789
4996
|
this.emit("session.done", report);
|
|
4790
4997
|
return report;
|
|
4791
4998
|
}
|
|
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
4999
|
async parseAndEmit(result) {
|
|
4804
5000
|
if (result.status !== "success" || result.result == null) return;
|
|
4805
5001
|
const text = typeof result.result === "string" ? result.result : JSON.stringify(result.result);
|
|
@@ -4815,12 +5011,6 @@ var CollabSession = class extends EventEmitter {
|
|
|
4815
5011
|
});
|
|
4816
5012
|
}
|
|
4817
5013
|
}
|
|
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
5014
|
extractJsonObjects(text) {
|
|
4825
5015
|
const objects = [];
|
|
4826
5016
|
let depth = 0;
|
|
@@ -4857,25 +5047,33 @@ var CollabSession = class extends EventEmitter {
|
|
|
4857
5047
|
}
|
|
4858
5048
|
return objects;
|
|
4859
5049
|
}
|
|
5050
|
+
budgetForRole(role) {
|
|
5051
|
+
if (this.options.budgetOverrides?.[role]) {
|
|
5052
|
+
return this.options.budgetOverrides[role];
|
|
5053
|
+
}
|
|
5054
|
+
const defaults = {
|
|
5055
|
+
"bug-hunter": { maxIterations: 2e3, maxToolCalls: 5e3, timeoutMs: 10 * 60 * 1e3 },
|
|
5056
|
+
"refactor-planner": { maxIterations: 1500, maxToolCalls: 4e3, timeoutMs: 8 * 60 * 1e3 },
|
|
5057
|
+
"critic": { maxIterations: 1e3, maxToolCalls: 3e3, timeoutMs: 6 * 60 * 1e3 }
|
|
5058
|
+
};
|
|
5059
|
+
return defaults[role] ?? { maxIterations: 1500, maxToolCalls: 4e3, timeoutMs: 8 * 60 * 1e3 };
|
|
5060
|
+
}
|
|
4860
5061
|
async spawnAgent(role, taskBrief) {
|
|
5062
|
+
const budget = this.budgetForRole(role);
|
|
4861
5063
|
const cfg = {
|
|
4862
5064
|
id: `${role}-${this.sessionId}`,
|
|
4863
5065
|
name: role,
|
|
4864
|
-
role
|
|
4865
|
-
|
|
4866
|
-
|
|
4867
|
-
|
|
5066
|
+
role,
|
|
5067
|
+
tools: ["fleet_emit", "fleet_status", "read", "grep", "glob", "bash", "write"],
|
|
5068
|
+
maxIterations: budget.maxIterations,
|
|
5069
|
+
maxToolCalls: budget.maxToolCalls,
|
|
5070
|
+
timeoutMs: budget.timeoutMs
|
|
4868
5071
|
};
|
|
4869
5072
|
const subagentId = await this.director.spawn(cfg);
|
|
4870
|
-
await this.director.assign({
|
|
4871
|
-
id: randomUUID(),
|
|
4872
|
-
subagentId,
|
|
4873
|
-
description: taskBrief
|
|
4874
|
-
});
|
|
5073
|
+
await this.director.assign({ id: randomUUID(), subagentId, description: taskBrief });
|
|
4875
5074
|
return subagentId;
|
|
4876
5075
|
}
|
|
4877
5076
|
buildBugHunterTask() {
|
|
4878
|
-
this.options.targetPaths.join(", ");
|
|
4879
5077
|
const scratchpad = this.director.sharedScratchpadPath ?? "/tmp";
|
|
4880
5078
|
const fileContents = this.snapshot.files.map((f) => `=== ${f.path} ===
|
|
4881
5079
|
${f.content}`).join("\n\n");
|
|
@@ -4884,13 +5082,17 @@ ${f.content}`).join("\n\n");
|
|
|
4884
5082
|
Target files:
|
|
4885
5083
|
${fileContents}
|
|
4886
5084
|
|
|
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
|
-
|
|
5085
|
+
For each bug found, emit it using the fleet_emit tool immediately:
|
|
5086
|
+
{ "type": "bug.found", "payload": { "finding": { "id": "<uuid>", "type": "<pattern>", "severity": "<critical|high|medium|low>", "location": { "file": "<path>", "line": <n> }, "description": "<explain>", "suggestedFix": "<optional>" } } }
|
|
5087
|
+
|
|
5088
|
+
After scanning all files, write your full markdown bug report to:
|
|
5089
|
+
${scratchpad}/bug-hunter-report-${this.sessionId}.md
|
|
5090
|
+
|
|
5091
|
+
Important: emit each finding as soon as you find it. Do not batch or wait until the end.`;
|
|
4891
5092
|
}
|
|
4892
5093
|
buildRefactorPlannerTask() {
|
|
4893
5094
|
const scratchpad = this.director.sharedScratchpadPath ?? "/tmp";
|
|
5095
|
+
const bugHunterReportPath = `${scratchpad}/bug-hunter-report-${this.sessionId}.md`;
|
|
4894
5096
|
const fileContents = this.snapshot.files.map((f) => `=== ${f.path} ===
|
|
4895
5097
|
${f.content}`).join("\n\n");
|
|
4896
5098
|
return `You are RefactorPlanner. Plan refactorings for the following files.
|
|
@@ -4898,26 +5100,146 @@ ${f.content}`).join("\n\n");
|
|
|
4898
5100
|
Target files:
|
|
4899
5101
|
${fileContents}
|
|
4900
5102
|
|
|
4901
|
-
|
|
4902
|
-
|
|
4903
|
-
|
|
4904
|
-
|
|
5103
|
+
Read the BugHunter report at: ${bugHunterReportPath}
|
|
5104
|
+
|
|
5105
|
+
For each bug you can address, emit a refactor plan using fleet_emit:
|
|
5106
|
+
{ "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>" } } }
|
|
5107
|
+
|
|
5108
|
+
Also write your full markdown plan to:
|
|
5109
|
+
${scratchpad}/refactor-plan-${this.sessionId}.md
|
|
5110
|
+
|
|
5111
|
+
Emit each plan immediately. Do not wait until planning is complete.`;
|
|
4905
5112
|
}
|
|
4906
5113
|
buildCriticTask() {
|
|
4907
5114
|
const scratchpad = this.director.sharedScratchpadPath ?? "/tmp";
|
|
5115
|
+
const bugHunterReportPath = `${scratchpad}/bug-hunter-report-${this.sessionId}.md`;
|
|
5116
|
+
const refactorPlanPath = `${scratchpad}/refactor-plan-${this.sessionId}.md`;
|
|
4908
5117
|
const fileContents = this.snapshot.files.map((f) => `=== ${f.path} ===
|
|
4909
5118
|
${f.content}`).join("\n\n");
|
|
4910
|
-
return `You are Critic. Evaluate bug findings and refactor plans
|
|
5119
|
+
return `You are Critic. Evaluate bug findings and refactor plans.
|
|
4911
5120
|
|
|
4912
5121
|
Target files:
|
|
4913
5122
|
${fileContents}
|
|
4914
5123
|
|
|
4915
|
-
|
|
4916
|
-
|
|
4917
|
-
|
|
4918
|
-
|
|
5124
|
+
Read the BugHunter report at: ${bugHunterReportPath}
|
|
5125
|
+
Read the RefactorPlanner report at: ${refactorPlanPath}
|
|
5126
|
+
|
|
5127
|
+
For each bug and refactor plan, emit your evaluation using fleet_emit:
|
|
5128
|
+
{ "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>" }] } } }
|
|
5129
|
+
|
|
5130
|
+
After all evaluations, write your markdown report to:
|
|
5131
|
+
${scratchpad}/critic-report-${this.sessionId}.md
|
|
5132
|
+
|
|
5133
|
+
Emit each evaluation immediately. Do not wait until you have read all reports.`;
|
|
4919
5134
|
}
|
|
4920
5135
|
wireFleetBus() {
|
|
5136
|
+
const dTool = this.fleetBus.filter("tool.executed", (e) => {
|
|
5137
|
+
this.progressBySubagent.set(e.subagentId, (this.progressBySubagent.get(e.subagentId) ?? 0) + 1);
|
|
5138
|
+
});
|
|
5139
|
+
this.disposers.push(dTool);
|
|
5140
|
+
const dBudget = this.fleetBus.filter("budget.threshold_reached", (e) => {
|
|
5141
|
+
const payload = e.payload;
|
|
5142
|
+
const role = this.roleFromSubagentId(e.subagentId);
|
|
5143
|
+
if (!role) return;
|
|
5144
|
+
const btwNotes = this.director.getLeaderBtwNotes();
|
|
5145
|
+
const alert = {
|
|
5146
|
+
sessionId: this.sessionId,
|
|
5147
|
+
subagentId: e.subagentId,
|
|
5148
|
+
role,
|
|
5149
|
+
level: "warning" /* WARNING */,
|
|
5150
|
+
message: `${role} hit ${payload.kind} soft limit (${payload.used}/${payload.limit})`,
|
|
5151
|
+
budgetKind: payload.kind,
|
|
5152
|
+
elapsedMs: payload.timeoutMs,
|
|
5153
|
+
limit: payload.limit,
|
|
5154
|
+
btwNotes
|
|
5155
|
+
};
|
|
5156
|
+
this.alerts.push(alert);
|
|
5157
|
+
this.fleetBus.emit({
|
|
5158
|
+
subagentId: e.subagentId,
|
|
5159
|
+
ts: Date.now(),
|
|
5160
|
+
type: "collab.warning",
|
|
5161
|
+
payload: alert
|
|
5162
|
+
});
|
|
5163
|
+
const decision = this.options.onBudgetWarning?.(alert) ?? "ignore";
|
|
5164
|
+
if (decision === "cancel") {
|
|
5165
|
+
this.cancel(`Director cancelled: ${role} ${payload.kind} threshold`);
|
|
5166
|
+
return;
|
|
5167
|
+
}
|
|
5168
|
+
if (payload.kind === "timeout") {
|
|
5169
|
+
const progress = this.progressBySubagent.get(e.subagentId) ?? 0;
|
|
5170
|
+
const lastProgress = this.lastTimeoutProgress.get(e.subagentId) ?? -1;
|
|
5171
|
+
if (progress <= lastProgress) {
|
|
5172
|
+
payload.deny();
|
|
5173
|
+
return;
|
|
5174
|
+
}
|
|
5175
|
+
this.lastTimeoutProgress.set(e.subagentId, progress);
|
|
5176
|
+
const newLimit = Math.min(Math.ceil((payload.timeoutMs ?? payload.limit) * 2), 24 * 60 * 6e4);
|
|
5177
|
+
setImmediate(() => {
|
|
5178
|
+
payload.extend({ timeoutMs: newLimit });
|
|
5179
|
+
});
|
|
5180
|
+
return;
|
|
5181
|
+
}
|
|
5182
|
+
if (decision === "extend") {
|
|
5183
|
+
setImmediate(() => {
|
|
5184
|
+
const base = Math.max(payload.limit, payload.used);
|
|
5185
|
+
const extra = {};
|
|
5186
|
+
switch (payload.kind) {
|
|
5187
|
+
case "iterations":
|
|
5188
|
+
extra.maxIterations = Math.min(Math.ceil(base * 1.5), 5e4);
|
|
5189
|
+
break;
|
|
5190
|
+
case "tool_calls":
|
|
5191
|
+
extra.maxToolCalls = Math.min(Math.ceil(base * 1.5), 1e5);
|
|
5192
|
+
break;
|
|
5193
|
+
case "tokens":
|
|
5194
|
+
extra.maxTokens = Math.min(Math.ceil(base * 1.5), 5e6);
|
|
5195
|
+
break;
|
|
5196
|
+
case "cost":
|
|
5197
|
+
extra.maxCostUsd = Math.min(base * 1.5, 100);
|
|
5198
|
+
break;
|
|
5199
|
+
}
|
|
5200
|
+
payload.extend(extra);
|
|
5201
|
+
});
|
|
5202
|
+
return;
|
|
5203
|
+
}
|
|
5204
|
+
if (payload.kind !== "timeout") {
|
|
5205
|
+
setImmediate(() => {
|
|
5206
|
+
const base = Math.max(payload.limit, payload.used);
|
|
5207
|
+
const extra = {};
|
|
5208
|
+
switch (payload.kind) {
|
|
5209
|
+
case "iterations":
|
|
5210
|
+
extra.maxIterations = Math.min(Math.ceil(base * 1.25), 5e4);
|
|
5211
|
+
break;
|
|
5212
|
+
case "tool_calls":
|
|
5213
|
+
extra.maxToolCalls = Math.min(Math.ceil(base * 1.25), 1e5);
|
|
5214
|
+
break;
|
|
5215
|
+
case "tokens":
|
|
5216
|
+
extra.maxTokens = Math.min(Math.ceil(base * 1.25), 5e6);
|
|
5217
|
+
break;
|
|
5218
|
+
case "cost":
|
|
5219
|
+
extra.maxCostUsd = Math.min(base * 1.25, 100);
|
|
5220
|
+
break;
|
|
5221
|
+
}
|
|
5222
|
+
payload.extend(extra);
|
|
5223
|
+
});
|
|
5224
|
+
}
|
|
5225
|
+
});
|
|
5226
|
+
this.disposers.push(dBudget);
|
|
5227
|
+
const dCancel = this.fleetBus.filter("director.cancel_collab", (e) => {
|
|
5228
|
+
const payload = e.payload;
|
|
5229
|
+
if (payload.sessionId !== this.sessionId) return;
|
|
5230
|
+
this.cancelled = true;
|
|
5231
|
+
if (this._timeoutTimer) {
|
|
5232
|
+
clearTimeout(this._timeoutTimer);
|
|
5233
|
+
this._timeoutTimer = void 0;
|
|
5234
|
+
}
|
|
5235
|
+
this.fleetBus.emit({
|
|
5236
|
+
subagentId: this.director.id,
|
|
5237
|
+
ts: Date.now(),
|
|
5238
|
+
type: "collab.cancelled",
|
|
5239
|
+
payload: { sessionId: this.sessionId, reason: payload.reason }
|
|
5240
|
+
});
|
|
5241
|
+
});
|
|
5242
|
+
this.disposers.push(dCancel);
|
|
4921
5243
|
const d1 = this.fleetBus.filter("bug.found", (e) => {
|
|
4922
5244
|
const payload = e.payload;
|
|
4923
5245
|
if (payload?.finding) {
|
|
@@ -4943,10 +5265,19 @@ ${scratchpad}/critic-report-${this.sessionId}.md`;
|
|
|
4943
5265
|
});
|
|
4944
5266
|
this.disposers.push(d3);
|
|
4945
5267
|
}
|
|
5268
|
+
roleFromSubagentId(subagentId) {
|
|
5269
|
+
for (const [role, id] of this.subagentIds) {
|
|
5270
|
+
if (id === subagentId) return role;
|
|
5271
|
+
}
|
|
5272
|
+
const match = subagentId.match(/^(bug-hunter|refactor-planner|critic)/);
|
|
5273
|
+
return match?.[1] ?? null;
|
|
5274
|
+
}
|
|
4946
5275
|
assembleReport() {
|
|
4947
5276
|
const bugList = Array.from(this.bugs.values());
|
|
4948
5277
|
const planList = Array.from(this.plans.values());
|
|
4949
5278
|
const evalList = Array.from(this.evaluations.values());
|
|
5279
|
+
let disposition = "completed";
|
|
5280
|
+
if (this.cancelled) disposition = "cancelled";
|
|
4950
5281
|
const verdictOrder = {
|
|
4951
5282
|
approve: 0,
|
|
4952
5283
|
needs_revision: 1,
|
|
@@ -4960,33 +5291,41 @@ ${scratchpad}/critic-report-${this.sessionId}.md`;
|
|
|
4960
5291
|
},
|
|
4961
5292
|
"approve"
|
|
4962
5293
|
);
|
|
4963
|
-
const summary = this.buildMarkdownSummary(bugList, planList, evalList, overallVerdict);
|
|
5294
|
+
const summary = this.buildMarkdownSummary(bugList, planList, evalList, overallVerdict, disposition);
|
|
4964
5295
|
return {
|
|
4965
5296
|
sessionId: this.sessionId,
|
|
4966
5297
|
startedAt: this.snapshot.createdAt,
|
|
4967
5298
|
completedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
4968
5299
|
targetPaths: this.options.targetPaths,
|
|
5300
|
+
disposition,
|
|
4969
5301
|
bugs: bugList,
|
|
4970
5302
|
refactorPlans: planList,
|
|
4971
5303
|
evaluations: evalList,
|
|
5304
|
+
alerts: [...this.alerts],
|
|
4972
5305
|
overallVerdict,
|
|
4973
5306
|
summary
|
|
4974
5307
|
};
|
|
4975
5308
|
}
|
|
4976
|
-
buildMarkdownSummary(bugs, plans, evals, overallVerdict) {
|
|
5309
|
+
buildMarkdownSummary(bugs, plans, evals, overallVerdict, disposition) {
|
|
4977
5310
|
const lines = [
|
|
4978
5311
|
`## Collaborative Debugging Report \u2014 ${this.sessionId}`,
|
|
4979
5312
|
"",
|
|
4980
5313
|
`**Target:** ${this.options.targetPaths.join(", ")}`,
|
|
5314
|
+
`**Disposition:** ${disposition.toUpperCase()}`,
|
|
4981
5315
|
`**Overall Verdict:** **${overallVerdict.toUpperCase()}**`,
|
|
4982
5316
|
""
|
|
4983
5317
|
];
|
|
5318
|
+
if (this.alerts.length > 0) {
|
|
5319
|
+
lines.push("### Alerts", "");
|
|
5320
|
+
for (const alert of this.alerts) {
|
|
5321
|
+
lines.push(`- **[${alert.level.toUpperCase()}]** ${alert.role}: ${alert.message}`);
|
|
5322
|
+
}
|
|
5323
|
+
lines.push("");
|
|
5324
|
+
}
|
|
4984
5325
|
if (bugs.length > 0) {
|
|
4985
5326
|
lines.push("### Bugs Found", "");
|
|
4986
5327
|
for (const b of bugs) {
|
|
4987
|
-
lines.push(
|
|
4988
|
-
`- **[${b.severity.toUpperCase()}]** \`${b.location.file}:${b.location.line}\` \u2014 ${b.description}`
|
|
4989
|
-
);
|
|
5328
|
+
lines.push(`- **[${b.severity.toUpperCase()}]** \`${b.location.file}:${b.location.line}\` \u2014 ${b.description}`);
|
|
4990
5329
|
}
|
|
4991
5330
|
lines.push("");
|
|
4992
5331
|
}
|
|
@@ -5005,9 +5344,7 @@ ${scratchpad}/critic-report-${this.sessionId}.md`;
|
|
|
5005
5344
|
for (const e of evals) {
|
|
5006
5345
|
lines.push(`- [${e.subjectType}] score=${e.score}/10 \u2014 **${e.verdict.toUpperCase()}**`);
|
|
5007
5346
|
for (const c of e.concerns) {
|
|
5008
|
-
if (c.severity === "blocking") {
|
|
5009
|
-
lines.push(` - ${c.description}`);
|
|
5010
|
-
}
|
|
5347
|
+
if (c.severity === "blocking") lines.push(` - ${c.description}`);
|
|
5011
5348
|
}
|
|
5012
5349
|
}
|
|
5013
5350
|
lines.push("");
|
|
@@ -5025,10 +5362,9 @@ var FleetSpawnBudgetError = class extends Error {
|
|
|
5025
5362
|
kind;
|
|
5026
5363
|
limit;
|
|
5027
5364
|
observed;
|
|
5028
|
-
constructor(kind, limit, observed) {
|
|
5029
|
-
|
|
5030
|
-
|
|
5031
|
-
);
|
|
5365
|
+
constructor(kind, limit, observed, message) {
|
|
5366
|
+
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}`;
|
|
5367
|
+
super(message ?? defaultMsg);
|
|
5032
5368
|
this.name = "FleetSpawnBudgetError";
|
|
5033
5369
|
this.kind = kind;
|
|
5034
5370
|
this.limit = limit;
|
|
@@ -5049,6 +5385,20 @@ var FleetCostCapError = class extends Error {
|
|
|
5049
5385
|
this.observed = observed;
|
|
5050
5386
|
}
|
|
5051
5387
|
};
|
|
5388
|
+
var FleetContextOverflowError = class extends Error {
|
|
5389
|
+
kind;
|
|
5390
|
+
limit;
|
|
5391
|
+
observed;
|
|
5392
|
+
constructor(limit, observed) {
|
|
5393
|
+
super(
|
|
5394
|
+
`Leader context overflow: leader has ${observed} tokens in flight (limit: ${limit}). Compact the leader context before spawning more subagents.`
|
|
5395
|
+
);
|
|
5396
|
+
this.name = "FleetContextOverflowError";
|
|
5397
|
+
this.kind = "max_context_load";
|
|
5398
|
+
this.limit = limit;
|
|
5399
|
+
this.observed = observed;
|
|
5400
|
+
}
|
|
5401
|
+
};
|
|
5052
5402
|
var Director = class {
|
|
5053
5403
|
/** Alias for the ICoordinator contract. `id` is retained for backward compatibility. */
|
|
5054
5404
|
get coordinatorId() {
|
|
@@ -5065,6 +5415,25 @@ var Director = class {
|
|
|
5065
5415
|
* injected; otherwise own FleetUsageAggregator.
|
|
5066
5416
|
*/
|
|
5067
5417
|
usage;
|
|
5418
|
+
/**
|
|
5419
|
+
* Update the leader agent's current context pressure (full request tokens:
|
|
5420
|
+
* messages + systemPrompt + toolDefs). The director checks this before every
|
|
5421
|
+
* spawn — if the pressure exceeds `maxLeaderContextLoad * maxContext`,
|
|
5422
|
+
* spawning is refused with a `FleetContextOverflowError`.
|
|
5423
|
+
*
|
|
5424
|
+
* Call this after each leader agent iteration to keep the pressure current.
|
|
5425
|
+
* The compactor's `CompactReport.fullRequestTokensAfter` is a natural source.
|
|
5426
|
+
*/
|
|
5427
|
+
setLeaderContextPressure(tokens) {
|
|
5428
|
+
this.leaderContextPressure = tokens;
|
|
5429
|
+
this.fleetManager?.setLeaderContextPressure(tokens);
|
|
5430
|
+
}
|
|
5431
|
+
/**
|
|
5432
|
+
* Read the leader agent's current context pressure.
|
|
5433
|
+
*/
|
|
5434
|
+
getLeaderContextPressure() {
|
|
5435
|
+
return this.leaderContextPressure;
|
|
5436
|
+
}
|
|
5068
5437
|
/**
|
|
5069
5438
|
* Optional fleet-level policy container. When provided the Director
|
|
5070
5439
|
* delegates spawn budgeting, manifest entries, and checkpointing to it
|
|
@@ -5150,6 +5519,23 @@ var Director = class {
|
|
|
5150
5519
|
taskCompletedListener = null;
|
|
5151
5520
|
/** Optional LLM classifier for smart dispatch. Passed from options. */
|
|
5152
5521
|
dispatchClassifier;
|
|
5522
|
+
/** Leader agent's current context pressure (full request tokens). */
|
|
5523
|
+
leaderContextPressure = 0;
|
|
5524
|
+
/** Maximum context load fraction before spawn is refused. */
|
|
5525
|
+
maxLeaderContextLoad;
|
|
5526
|
+
/** Provider's max context window in tokens. */
|
|
5527
|
+
maxContext;
|
|
5528
|
+
/**
|
|
5529
|
+
* When set by `workComplete()`, the director stops dispatching new tasks
|
|
5530
|
+
* and terminates all running subagents. Used when the director's LLM decides
|
|
5531
|
+
* the goal is satisfied and no further spawns are needed — prevents the
|
|
5532
|
+
* coordinator from keeping workers alive for tasks that will never arrive.
|
|
5533
|
+
*/
|
|
5534
|
+
workCompleteFlag = false;
|
|
5535
|
+
/** Pending /btw notes stashed by the leader agent (see setLeaderBtwNote). */
|
|
5536
|
+
_leaderBtwNotes = [];
|
|
5537
|
+
/** Active collab sessions tracked by sessionId (see spawnCollab). */
|
|
5538
|
+
_activeCollabSessions = /* @__PURE__ */ new Map();
|
|
5153
5539
|
constructor(opts) {
|
|
5154
5540
|
this.id = opts.config.coordinatorId || randomUUID();
|
|
5155
5541
|
this.manifestPath = opts.manifestPath;
|
|
@@ -5165,6 +5551,8 @@ var Director = class {
|
|
|
5165
5551
|
this.dispatchClassifier = opts.dispatchClassifier;
|
|
5166
5552
|
this.maxFleetCostUsd = opts.directorBudget?.maxCostUsd ?? Number.POSITIVE_INFINITY;
|
|
5167
5553
|
this.maxBudgetExtensions = opts.maxBudgetExtensions ?? 5;
|
|
5554
|
+
this.maxLeaderContextLoad = opts.maxLeaderContextLoad ?? 0.85;
|
|
5555
|
+
this.maxContext = opts.maxContext ?? 128e3;
|
|
5168
5556
|
this.sessionsRoot = opts.sessionsRoot;
|
|
5169
5557
|
this.directorRunId = opts.directorRunId ?? this.id;
|
|
5170
5558
|
this.stateCheckpoint = opts.stateCheckpointPath ? new DirectorStateCheckpoint(opts.stateCheckpointPath, {
|
|
@@ -5192,7 +5580,7 @@ var Director = class {
|
|
|
5192
5580
|
this.fleet = new FleetBus();
|
|
5193
5581
|
this.usage = new FleetUsageAggregator(
|
|
5194
5582
|
this.fleet,
|
|
5195
|
-
(
|
|
5583
|
+
(_id, provider, model) => {
|
|
5196
5584
|
if (provider && model) return this.priceLookups.get(`${provider}/${model}`);
|
|
5197
5585
|
return void 0;
|
|
5198
5586
|
},
|
|
@@ -5254,6 +5642,9 @@ var Director = class {
|
|
|
5254
5642
|
});
|
|
5255
5643
|
this.fleet.filter("budget.threshold_reached", (e) => {
|
|
5256
5644
|
const payload = e.payload;
|
|
5645
|
+
if (e.subagentId.startsWith("bug-hunter-") || e.subagentId.startsWith("refactor-planner-") || e.subagentId.startsWith("critic-")) {
|
|
5646
|
+
return;
|
|
5647
|
+
}
|
|
5257
5648
|
if (payload.kind === "timeout") {
|
|
5258
5649
|
const progress = progressBySubagent.get(e.subagentId) ?? 0;
|
|
5259
5650
|
const lastProgress = lastTimeoutProgress.get(e.subagentId) ?? -1;
|
|
@@ -5332,6 +5723,107 @@ var Director = class {
|
|
|
5332
5723
|
extensionsFor(subagentId) {
|
|
5333
5724
|
return this.extendTotals.get(subagentId) ?? 0;
|
|
5334
5725
|
}
|
|
5726
|
+
/**
|
|
5727
|
+
* Signal that the director's work is done. Once called:
|
|
5728
|
+
* - `spawn()` throws `FleetSpawnBudgetError('max_spawns', …)` — no new
|
|
5729
|
+
* subagents can be created
|
|
5730
|
+
* - Running subagents are NOT forcibly stopped — they finish naturally,
|
|
5731
|
+
* but no new tasks are dispatched to them
|
|
5732
|
+
*
|
|
5733
|
+
* This lets the director LLM say "I'm satisfied with the results, stop
|
|
5734
|
+
* spawning and wind down" — without killing in-flight work mid-execution.
|
|
5735
|
+
* Call `terminateAll()` separately if you need immediate teardown.
|
|
5736
|
+
*
|
|
5737
|
+
* Idempotent — calling twice is a no-op.
|
|
5738
|
+
*/
|
|
5739
|
+
workComplete() {
|
|
5740
|
+
this.workCompleteFlag = true;
|
|
5741
|
+
this.fleet.emit({ subagentId: this.id, ts: Date.now(), type: "director.work_complete", payload: {} });
|
|
5742
|
+
}
|
|
5743
|
+
/** Returns true if `workComplete()` has been called on this director. */
|
|
5744
|
+
isWorkComplete() {
|
|
5745
|
+
return this.workCompleteFlag;
|
|
5746
|
+
}
|
|
5747
|
+
/**
|
|
5748
|
+
* Stashes a /btw note on the leader agent's context. The leader's agent loop
|
|
5749
|
+
* calls `consumeBtwNotes()` at each iteration boundary and folds pending notes
|
|
5750
|
+
* into the conversation as a visible block — no abort, no restart, just a
|
|
5751
|
+
* "by the way" nudge the model picks up on its next turn.
|
|
5752
|
+
*
|
|
5753
|
+
* This is the entry point for the host (CLI, TUI) to inject /btw notes
|
|
5754
|
+
* programmatically without going through the slash-command path.
|
|
5755
|
+
*/
|
|
5756
|
+
setLeaderBtwNote(note) {
|
|
5757
|
+
const trimmed = note.trim();
|
|
5758
|
+
if (!trimmed) return this._leaderBtwNotes.length;
|
|
5759
|
+
this._leaderBtwNotes = [...this._leaderBtwNotes, trimmed].slice(-20);
|
|
5760
|
+
return this._leaderBtwNotes.length;
|
|
5761
|
+
}
|
|
5762
|
+
/**
|
|
5763
|
+
* Read and clear all pending /btw notes the leader has stashed.
|
|
5764
|
+
* Returns them in FIFO order (empty array when none).
|
|
5765
|
+
*
|
|
5766
|
+
* Called by CollabSession when a budget threshold event fires so the
|
|
5767
|
+
* Director can inspect accumulated /btw notes before deciding whether
|
|
5768
|
+
* to cancel the collab session or let it continue.
|
|
5769
|
+
*/
|
|
5770
|
+
getLeaderBtwNotes() {
|
|
5771
|
+
const notes = this._leaderBtwNotes;
|
|
5772
|
+
this._leaderBtwNotes = [];
|
|
5773
|
+
return notes;
|
|
5774
|
+
}
|
|
5775
|
+
/**
|
|
5776
|
+
* Peek at pending /btw notes without consuming them.
|
|
5777
|
+
* Useful for UI to show "N pending notes" without clearing them.
|
|
5778
|
+
*/
|
|
5779
|
+
peekLeaderBtwNotes() {
|
|
5780
|
+
return [...this._leaderBtwNotes];
|
|
5781
|
+
}
|
|
5782
|
+
/**
|
|
5783
|
+
* Drain (read + clear) all /btw notes in one call.
|
|
5784
|
+
* Alias for getLeaderBtwNotes() — kept for symmetry with consumeBtwNotes()
|
|
5785
|
+
* in the agent's btw.ts. The Director calls this at the point where it
|
|
5786
|
+
* makes a cancellation decision, not on every budget event.
|
|
5787
|
+
*/
|
|
5788
|
+
drainLeaderBtwNotes() {
|
|
5789
|
+
return this.getLeaderBtwNotes();
|
|
5790
|
+
}
|
|
5791
|
+
/**
|
|
5792
|
+
* Cancel an active collab session by its id.
|
|
5793
|
+
* Emits `director.cancel_collab` on the FleetBus so the session's agents
|
|
5794
|
+
* finish early with a 'cancelled' disposition.
|
|
5795
|
+
*
|
|
5796
|
+
* Returns silently if the session id is not tracked or already settled.
|
|
5797
|
+
* The CollabDebugReport will reflect 'cancelled' disposition when awaited.
|
|
5798
|
+
*/
|
|
5799
|
+
cancelCollabSession(sessionId, reason = "Director cancelled") {
|
|
5800
|
+
const session = this._activeCollabSessions.get(sessionId);
|
|
5801
|
+
if (!session || session.isCancelled()) return;
|
|
5802
|
+
session.cancel(reason);
|
|
5803
|
+
for (const [_role, subagentId] of session.getSubagentIds()) {
|
|
5804
|
+
this.coordinator.stop(subagentId).catch(() => {
|
|
5805
|
+
});
|
|
5806
|
+
}
|
|
5807
|
+
}
|
|
5808
|
+
/**
|
|
5809
|
+
* Subscribe a callback to be notified whenever a collab session raises
|
|
5810
|
+
* an alert (warning level). The callback receives the full DirectorAlert
|
|
5811
|
+
* payload so the host (CLI, TUI) can display it to the user.
|
|
5812
|
+
* Returns an unsubscribe function.
|
|
5813
|
+
*/
|
|
5814
|
+
onCollabAlert(handler) {
|
|
5815
|
+
return this.fleet.filter("collab.warning", (e) => {
|
|
5816
|
+
handler(e.payload);
|
|
5817
|
+
});
|
|
5818
|
+
}
|
|
5819
|
+
/**
|
|
5820
|
+
* Returns all active (not yet settled) collab session ids.
|
|
5821
|
+
* Useful for the TUI to render a "N active sessions" badge and for
|
|
5822
|
+
* the host to know what can be cancelled.
|
|
5823
|
+
*/
|
|
5824
|
+
activeCollabSessions() {
|
|
5825
|
+
return Array.from(this._activeCollabSessions.keys());
|
|
5826
|
+
}
|
|
5335
5827
|
/** Best-effort session-writer append. Swallows failures — the director
|
|
5336
5828
|
* must not break a fleet run because the session JSONL handle closed. */
|
|
5337
5829
|
async appendSessionEvent(event) {
|
|
@@ -5370,12 +5862,21 @@ var Director = class {
|
|
|
5370
5862
|
* it the `cost` column in `usage.snapshot()` stays at 0.
|
|
5371
5863
|
*/
|
|
5372
5864
|
async spawn(config, priceLookup) {
|
|
5865
|
+
if (this.workCompleteFlag) {
|
|
5866
|
+
throw new FleetSpawnBudgetError(
|
|
5867
|
+
"max_spawns",
|
|
5868
|
+
this.maxSpawns,
|
|
5869
|
+
this.spawnCount + 1,
|
|
5870
|
+
"workComplete() has been called \u2014 director closed further spawning"
|
|
5871
|
+
);
|
|
5872
|
+
}
|
|
5373
5873
|
if (this.fleetManager) {
|
|
5374
5874
|
const rejection = this.fleetManager.canSpawn(config);
|
|
5375
5875
|
if (rejection) {
|
|
5376
5876
|
if (rejection.kind === "max_spawn_depth") throw new FleetSpawnBudgetError("max_spawn_depth", rejection.limit, rejection.observed);
|
|
5377
5877
|
if (rejection.kind === "max_spawns") throw new FleetSpawnBudgetError("max_spawns", rejection.limit, rejection.observed);
|
|
5378
5878
|
if (rejection.kind === "max_cost_usd") throw new FleetCostCapError(rejection.limit, rejection.observed);
|
|
5879
|
+
if (rejection.kind === "max_context_load") throw new FleetContextOverflowError(rejection.limit, rejection.observed);
|
|
5379
5880
|
}
|
|
5380
5881
|
} else {
|
|
5381
5882
|
if (this.spawnDepth >= this.maxSpawnDepth) {
|
|
@@ -5390,9 +5891,15 @@ var Director = class {
|
|
|
5390
5891
|
throw new FleetCostCapError(this.maxFleetCostUsd, totalCost);
|
|
5391
5892
|
}
|
|
5392
5893
|
}
|
|
5894
|
+
if (this.maxLeaderContextLoad < 1) {
|
|
5895
|
+
const threshold = this.maxContext * this.maxLeaderContextLoad;
|
|
5896
|
+
if (this.leaderContextPressure >= threshold) {
|
|
5897
|
+
throw new FleetContextOverflowError(threshold, this.leaderContextPressure);
|
|
5898
|
+
}
|
|
5899
|
+
}
|
|
5393
5900
|
}
|
|
5394
5901
|
let result;
|
|
5395
|
-
const needsNickname = config.name === config.role || !config.name || config.name === "subagent";
|
|
5902
|
+
const needsNickname = config.name === config.role || !config.name || config.name === "subagent" || config.name === "adhoc";
|
|
5396
5903
|
if (needsNickname) {
|
|
5397
5904
|
const role = config.role ?? "subagent";
|
|
5398
5905
|
if (this.fleetManager) {
|
|
@@ -5617,6 +6124,24 @@ var Director = class {
|
|
|
5617
6124
|
*/
|
|
5618
6125
|
async assign(task) {
|
|
5619
6126
|
const taskWithId = task.id ? task : { ...task, id: randomUUID() };
|
|
6127
|
+
if (this.workCompleteFlag) {
|
|
6128
|
+
const synthetic = {
|
|
6129
|
+
subagentId: taskWithId.subagentId ?? "unassigned",
|
|
6130
|
+
taskId: taskWithId.id,
|
|
6131
|
+
status: "stopped",
|
|
6132
|
+
error: { kind: "aborted_by_parent", message: "Director called workComplete() \u2014 no further tasks will run", retryable: false },
|
|
6133
|
+
iterations: 0,
|
|
6134
|
+
toolCalls: 0,
|
|
6135
|
+
durationMs: 0
|
|
6136
|
+
};
|
|
6137
|
+
this.completed.set(taskWithId.id, synthetic);
|
|
6138
|
+
const waiter = this.taskWaiters.get(taskWithId.id);
|
|
6139
|
+
if (waiter) {
|
|
6140
|
+
waiter.resolve(synthetic);
|
|
6141
|
+
this.taskWaiters.delete(taskWithId.id);
|
|
6142
|
+
}
|
|
6143
|
+
return taskWithId.id;
|
|
6144
|
+
}
|
|
5620
6145
|
if (task.subagentId) {
|
|
5621
6146
|
if (this.fleetManager) {
|
|
5622
6147
|
this.fleetManager.addTaskToSubagent(task.subagentId, taskWithId.id);
|
|
@@ -5839,12 +6364,14 @@ var Director = class {
|
|
|
5839
6364
|
makeAskTool(this),
|
|
5840
6365
|
makeRollUpTool(this),
|
|
5841
6366
|
makeTerminateTool(this),
|
|
6367
|
+
makeTerminateAllTool(this),
|
|
5842
6368
|
makeFleetStatusTool(this),
|
|
5843
6369
|
makeFleetUsageTool(this),
|
|
5844
6370
|
makeFleetSessionTool(this),
|
|
5845
6371
|
makeFleetHealthTool(this),
|
|
5846
6372
|
makeCollabDebugTool(this),
|
|
5847
|
-
makeFleetEmitTool(this)
|
|
6373
|
+
makeFleetEmitTool(this),
|
|
6374
|
+
makeWorkCompleteTool(this)
|
|
5848
6375
|
];
|
|
5849
6376
|
return t;
|
|
5850
6377
|
}
|
|
@@ -5864,7 +6391,15 @@ var Director = class {
|
|
|
5864
6391
|
* three agents complete or the session times out.
|
|
5865
6392
|
*/
|
|
5866
6393
|
async spawnCollab(options) {
|
|
5867
|
-
const session = new CollabSession(this, this.fleet,
|
|
6394
|
+
const session = new CollabSession(this, this.fleet, {
|
|
6395
|
+
...options,
|
|
6396
|
+
onBudgetWarning: (alert) => {
|
|
6397
|
+
return options.onBudgetWarning?.(alert) ?? "ignore";
|
|
6398
|
+
}
|
|
6399
|
+
});
|
|
6400
|
+
this._activeCollabSessions.set(session.sessionId, session);
|
|
6401
|
+
session.on("session.done", () => this._activeCollabSessions.delete(session.sessionId));
|
|
6402
|
+
session.on("session.error", () => this._activeCollabSessions.delete(session.sessionId));
|
|
5868
6403
|
return session.start();
|
|
5869
6404
|
}
|
|
5870
6405
|
/**
|
|
@@ -6282,6 +6817,7 @@ function makeAgentSubagentRunner(opts) {
|
|
|
6282
6817
|
// Cheap: O(1) per progress event, and the budget short-circuits
|
|
6283
6818
|
// when timeoutMs is unset (most subagents have one set anyway).
|
|
6284
6819
|
events.on("tool.progress", () => {
|
|
6820
|
+
ctx.budget.markActivity();
|
|
6285
6821
|
try {
|
|
6286
6822
|
ctx.budget.checkTimeout();
|
|
6287
6823
|
} catch (e) {
|
|
@@ -6298,6 +6834,7 @@ function makeAgentSubagentRunner(opts) {
|
|
|
6298
6834
|
currentToolName = e.name;
|
|
6299
6835
|
}),
|
|
6300
6836
|
events.on("provider.text_delta", (e) => {
|
|
6837
|
+
ctx.budget.markActivity();
|
|
6301
6838
|
streamingTextAcc = (streamingTextAcc + e.text).slice(-200);
|
|
6302
6839
|
})
|
|
6303
6840
|
);
|
|
@@ -6777,7 +7314,6 @@ var FileSessionWriter = class {
|
|
|
6777
7314
|
}
|
|
6778
7315
|
return event;
|
|
6779
7316
|
}
|
|
6780
|
-
promptIndex = 0;
|
|
6781
7317
|
pendingFileSnapshots = [];
|
|
6782
7318
|
/** Tracks open tool_use IDs during the current run to serialize on close for resume. */
|
|
6783
7319
|
openToolUses = /* @__PURE__ */ new Set();
|
|
@@ -6868,7 +7404,6 @@ var FileSessionWriter = class {
|
|
|
6868
7404
|
await this.writeFileSnapshot(promptIndex, [...this.pendingFileSnapshots]);
|
|
6869
7405
|
this.pendingFileSnapshots = [];
|
|
6870
7406
|
}
|
|
6871
|
-
this.promptIndex = promptIndex + 1;
|
|
6872
7407
|
await this.append({
|
|
6873
7408
|
type: "checkpoint",
|
|
6874
7409
|
ts: (/* @__PURE__ */ new Date()).toISOString(),
|
|
@@ -6964,6 +7499,38 @@ var FileSessionWriter = class {
|
|
|
6964
7499
|
`;
|
|
6965
7500
|
await fsp5.writeFile(this.filePath, record, "utf8");
|
|
6966
7501
|
}
|
|
7502
|
+
/**
|
|
7503
|
+
* Idea #1 — write an in-flight marker. The agent loop should call
|
|
7504
|
+
* this at the start of each long-running operation; a matching
|
|
7505
|
+
* `clearInFlightMarker` follows on clean exit. A stale marker
|
|
7506
|
+
* (no end) is what `SessionRecovery.detectStale` looks for.
|
|
7507
|
+
*/
|
|
7508
|
+
async writeInFlightMarker(context) {
|
|
7509
|
+
if (!context || context.length > 500) {
|
|
7510
|
+
throw new Error("In-flight context must be 1..500 chars");
|
|
7511
|
+
}
|
|
7512
|
+
await this.append({
|
|
7513
|
+
type: "in_flight_start",
|
|
7514
|
+
ts: (/* @__PURE__ */ new Date()).toISOString(),
|
|
7515
|
+
context
|
|
7516
|
+
});
|
|
7517
|
+
this.events?.emit("in_flight.started", { context, ts: (/* @__PURE__ */ new Date()).toISOString() });
|
|
7518
|
+
}
|
|
7519
|
+
/**
|
|
7520
|
+
* Idea #1 — close the in-flight marker. Idempotent in spirit
|
|
7521
|
+
* (you can call it after a successful iteration even if you
|
|
7522
|
+
* didn't open one this round) — but the session log records
|
|
7523
|
+
* every call so postmortem tooling can see "the agent finished
|
|
7524
|
+
* cleanly X times, then died without finishing Y".
|
|
7525
|
+
*/
|
|
7526
|
+
async clearInFlightMarker(reason) {
|
|
7527
|
+
await this.append({
|
|
7528
|
+
type: "in_flight_end",
|
|
7529
|
+
ts: (/* @__PURE__ */ new Date()).toISOString(),
|
|
7530
|
+
reason
|
|
7531
|
+
});
|
|
7532
|
+
this.events?.emit("in_flight.ended", { reason, ts: (/* @__PURE__ */ new Date()).toISOString() });
|
|
7533
|
+
}
|
|
6967
7534
|
};
|
|
6968
7535
|
function userInputTitle(content) {
|
|
6969
7536
|
const text = typeof content === "string" ? content : content.filter((b) => b.type === "text").map((b) => b.text).join(" ");
|
|
@@ -7064,7 +7631,6 @@ var FleetManager = class {
|
|
|
7064
7631
|
/** Usage rollup across all subagents. */
|
|
7065
7632
|
usage;
|
|
7066
7633
|
manifestPath;
|
|
7067
|
-
sessionsRoot;
|
|
7068
7634
|
directorRunId;
|
|
7069
7635
|
/** Spawn cap (lifetime total). Infinity means unlimited. */
|
|
7070
7636
|
maxSpawns;
|
|
@@ -7092,9 +7658,14 @@ var FleetManager = class {
|
|
|
7092
7658
|
_usedNicknames = /* @__PURE__ */ new Set();
|
|
7093
7659
|
/** The coordinator (wired via setCoordinator by Director after construction). */
|
|
7094
7660
|
coordinator = null;
|
|
7661
|
+
/** Leader agent's current context pressure (full request tokens). */
|
|
7662
|
+
leaderContextPressure = 0;
|
|
7663
|
+
/** Maximum context load fraction before spawn is refused. */
|
|
7664
|
+
maxLeaderContextLoad;
|
|
7665
|
+
/** Provider's max context window in tokens. */
|
|
7666
|
+
maxContext;
|
|
7095
7667
|
constructor(opts = {}) {
|
|
7096
7668
|
this.manifestPath = opts.manifestPath;
|
|
7097
|
-
this.sessionsRoot = opts.sessionsRoot;
|
|
7098
7669
|
this.directorRunId = opts.directorRunId ?? randomUUID();
|
|
7099
7670
|
this.maxSpawns = opts.maxSpawns ?? Number.POSITIVE_INFINITY;
|
|
7100
7671
|
this.maxSpawnDepth = opts.maxSpawnDepth ?? 2;
|
|
@@ -7102,6 +7673,8 @@ var FleetManager = class {
|
|
|
7102
7673
|
this.sessionWriter = opts.sessionWriter ?? null;
|
|
7103
7674
|
this.manifestDebounceMs = opts.manifestDebounceMs ?? 2e3;
|
|
7104
7675
|
this.maxFleetCostUsd = opts.directorBudget?.maxCostUsd ?? Number.POSITIVE_INFINITY;
|
|
7676
|
+
this.maxLeaderContextLoad = opts.maxLeaderContextLoad ?? 0.85;
|
|
7677
|
+
this.maxContext = opts.maxContext ?? 128e3;
|
|
7105
7678
|
this.stateCheckpoint = opts.stateCheckpointPath ? new DirectorStateCheckpoint(
|
|
7106
7679
|
opts.stateCheckpointPath,
|
|
7107
7680
|
{
|
|
@@ -7116,7 +7689,7 @@ var FleetManager = class {
|
|
|
7116
7689
|
this.fleet = new FleetBus();
|
|
7117
7690
|
this.usage = new FleetUsageAggregator(
|
|
7118
7691
|
this.fleet,
|
|
7119
|
-
(
|
|
7692
|
+
(_id, provider, model) => {
|
|
7120
7693
|
if (provider && model) return this.priceLookups.get(`${provider}/${model}`);
|
|
7121
7694
|
return void 0;
|
|
7122
7695
|
},
|
|
@@ -7155,7 +7728,7 @@ var FleetManager = class {
|
|
|
7155
7728
|
* which cap was exceeded. Does NOT throw — the caller decides
|
|
7156
7729
|
* how to surface the rejection.
|
|
7157
7730
|
*/
|
|
7158
|
-
canSpawn(
|
|
7731
|
+
canSpawn(_config) {
|
|
7159
7732
|
if (this.spawnDepth >= this.maxSpawnDepth) {
|
|
7160
7733
|
return { kind: "max_spawn_depth", limit: this.maxSpawnDepth, observed: this.spawnDepth };
|
|
7161
7734
|
}
|
|
@@ -7168,8 +7741,21 @@ var FleetManager = class {
|
|
|
7168
7741
|
return { kind: "max_cost_usd", limit: this.maxFleetCostUsd, observed: totalCost };
|
|
7169
7742
|
}
|
|
7170
7743
|
}
|
|
7744
|
+
if (this.maxLeaderContextLoad < 1) {
|
|
7745
|
+
const threshold = this.maxContext * this.maxLeaderContextLoad;
|
|
7746
|
+
if (this.leaderContextPressure >= threshold) {
|
|
7747
|
+
return {
|
|
7748
|
+
kind: "max_context_load",
|
|
7749
|
+
limit: threshold,
|
|
7750
|
+
observed: this.leaderContextPressure
|
|
7751
|
+
};
|
|
7752
|
+
}
|
|
7753
|
+
}
|
|
7171
7754
|
return null;
|
|
7172
7755
|
}
|
|
7756
|
+
setLeaderContextPressure(tokens) {
|
|
7757
|
+
this.leaderContextPressure = tokens;
|
|
7758
|
+
}
|
|
7173
7759
|
/**
|
|
7174
7760
|
* Assign a memorable nickname (e.g. "Einstein (Bug Hunter)") to the config,
|
|
7175
7761
|
* record it so the same name is never reused, then record the spawn.
|
|
@@ -7364,6 +7950,6 @@ var FleetManager = class {
|
|
|
7364
7950
|
}
|
|
7365
7951
|
};
|
|
7366
7952
|
|
|
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 };
|
|
7953
|
+
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
7954
|
//# sourceMappingURL=index.js.map
|
|
7369
7955
|
//# sourceMappingURL=index.js.map
|