@wrongstack/core 0.7.8 → 0.8.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/{agent-bridge-hXRN-GO_.d.ts → agent-bridge-DPxcUVkn.d.ts} +1 -1
- package/dist/{agent-subagent-runner-B2zguWzh.d.ts → agent-subagent-runner-Bzeueq2J.d.ts} +9 -10
- package/dist/coordination/index.d.ts +8 -8
- package/dist/coordination/index.js +393 -179
- package/dist/coordination/index.js.map +1 -1
- package/dist/defaults/index.d.ts +10 -10
- package/dist/defaults/index.js +190 -55
- package/dist/defaults/index.js.map +1 -1
- package/dist/{events-D4pGukpI.d.ts → events-DyhxkstG.d.ts} +33 -0
- package/dist/execution/index.d.ts +6 -6
- package/dist/execution/index.js +116 -31
- package/dist/execution/index.js.map +1 -1
- package/dist/extension/index.d.ts +2 -2
- package/dist/{index-BtNRyJft.d.ts → index-BciJeH8g.d.ts} +1 -1
- package/dist/index.d.ts +13 -13
- package/dist/index.js +294 -59
- package/dist/index.js.map +1 -1
- package/dist/infrastructure/index.d.ts +2 -2
- package/dist/kernel/index.d.ts +2 -2
- package/dist/kernel/index.js +23 -0
- package/dist/kernel/index.js.map +1 -1
- package/dist/{multi-agent-7OK4pEKW.d.ts → multi-agent-CRMznZmf.d.ts} +64 -30
- package/dist/{multi-agent-coordinator-3Ypfg-hr.d.ts → multi-agent-coordinator-tlSWD4cE.d.ts} +11 -1
- package/dist/{null-fleet-bus-BZUrXVcd.d.ts → null-fleet-bus-DXEvZKHW.d.ts} +92 -6
- package/dist/observability/index.d.ts +1 -1
- package/dist/{path-resolver-DPUjF10O.d.ts → path-resolver-1CIYbH2Q.d.ts} +1 -1
- package/dist/{plan-templates-C-IOLJ8Q.d.ts → plan-templates-BnlpEkX8.d.ts} +1 -1
- package/dist/{provider-runner-iST8U3ni.d.ts → provider-runner-BrA0XR-l.d.ts} +1 -1
- package/dist/sdd/index.d.ts +4 -4
- package/dist/sdd/index.js +114 -28
- package/dist/sdd/index.js.map +1 -1
- package/dist/storage/index.d.ts +2 -2
- package/dist/{tool-executor-CSwXjifK.d.ts → tool-executor-Cx2ndr0L.d.ts} +1 -1
- package/dist/types/index.d.ts +7 -7
- package/package.json +1 -1
|
@@ -479,18 +479,17 @@ var FleetBus = class {
|
|
|
479
479
|
byType = /* @__PURE__ */ new Map();
|
|
480
480
|
any = /* @__PURE__ */ new Set();
|
|
481
481
|
/**
|
|
482
|
-
* Hook a subagent's EventBus into the fleet.
|
|
483
|
-
*
|
|
484
|
-
*
|
|
485
|
-
*
|
|
486
|
-
*
|
|
487
|
-
* the wire format clear.
|
|
482
|
+
* Hook a subagent's EventBus into the fleet. Uses `onAny()` (an alias for
|
|
483
|
+
* `onPattern('*')`) to forward all events with subagent attribution, so
|
|
484
|
+
* new kernel event types are automatically forwarded without any manual
|
|
485
|
+
* registration. `subagent.*` events are excluded because they originate
|
|
486
|
+
* from MultiAgentHost on the parent bus, not the subagent's own bus.
|
|
488
487
|
*
|
|
489
488
|
* Returns a disposer that detaches every subscription; call on
|
|
490
489
|
* subagent teardown so the listeners don't outlive the run.
|
|
491
490
|
*/
|
|
492
491
|
attach(subagentId, bus, taskId) {
|
|
493
|
-
const off = bus.
|
|
492
|
+
const off = bus.onAny((type, payload) => {
|
|
494
493
|
if (type.startsWith("subagent.")) return;
|
|
495
494
|
this.emit({ subagentId, taskId, ts: Date.now(), type, payload });
|
|
496
495
|
});
|
|
@@ -612,7 +611,7 @@ var FleetUsageAggregator = class {
|
|
|
612
611
|
this.total.output += usage.output ?? 0;
|
|
613
612
|
this.total.cacheRead += usage.cacheRead ?? 0;
|
|
614
613
|
this.total.cacheWrite += usage.cacheWrite ?? 0;
|
|
615
|
-
const price = this.priceLookup?.(e.subagentId);
|
|
614
|
+
const price = this.priceLookup?.(e.subagentId, snap.provider, snap.model);
|
|
616
615
|
if (price) {
|
|
617
616
|
const delta = (usage.input ?? 0) / 1e6 * (price.input ?? 0) + (usage.output ?? 0) / 1e6 * (price.output ?? 0) + (usage.cacheRead ?? 0) / 1e6 * (price.cacheRead ?? 0) + (usage.cacheWrite ?? 0) / 1e6 * (price.cacheWrite ?? 0);
|
|
618
617
|
snap.cost += delta;
|
|
@@ -785,29 +784,35 @@ var SubagentBudget = class _SubagentBudget {
|
|
|
785
784
|
* same kind are no-ops — without this dedup, every `recordIteration`
|
|
786
785
|
* after the limit is reached spawns a fresh decision Promise (until
|
|
787
786
|
* the first one lands and patches limits), flooding the FleetBus
|
|
788
|
-
* with redundant threshold events. Cleared in `
|
|
787
|
+
* with redundant threshold events. Cleared in `negotiateExtension`'s
|
|
789
788
|
* `finally`.
|
|
790
789
|
*/
|
|
791
790
|
pendingExtensions = /* @__PURE__ */ new Set();
|
|
792
791
|
/**
|
|
793
|
-
* Hard cap on how long `
|
|
792
|
+
* Hard cap on how long `negotiateExtension` waits for the coordinator to
|
|
794
793
|
* respond before defaulting to 'stop'. Without this fallback an absent
|
|
795
794
|
* or hung listener (Director not built / event filter detached mid-run)
|
|
796
|
-
* leaves the budget over-limit and never enforces anything
|
|
797
|
-
* `checkLimit` returns synchronously via `void this.checkLimitAsync`.
|
|
798
|
-
* Raised from 30s to 60s to give subagents more headroom before
|
|
799
|
-
* the threshold negotiation times out and triggers a hard stop.
|
|
795
|
+
* leaves the budget over-limit and never enforces anything.
|
|
800
796
|
*/
|
|
801
797
|
static DECISION_TIMEOUT_MS = 6e4;
|
|
802
798
|
/**
|
|
803
799
|
* Injected by the runner when wiring the budget to its EventBus.
|
|
804
|
-
* Used
|
|
800
|
+
* Used to emit `budget.threshold_reached` events in `'auto'` mode.
|
|
805
801
|
*/
|
|
806
802
|
_events;
|
|
803
|
+
/**
|
|
804
|
+
* Negotiation mode — controls whether a threshold hit tries to emit
|
|
805
|
+
* `budget.threshold_reached` and wait for a coordinator decision, or
|
|
806
|
+
* falls straight through to a synchronous hard stop.
|
|
807
|
+
*
|
|
808
|
+
* `'auto'` (default) — emit on the EventBus and wait; times out to 'stop'.
|
|
809
|
+
* `'sync'` — throw `BudgetExceededError` immediately regardless of listeners.
|
|
810
|
+
*/
|
|
811
|
+
_mode;
|
|
807
812
|
/**
|
|
808
813
|
* Optional callback for soft-limit handling. When set, the budget will
|
|
809
|
-
*
|
|
810
|
-
*
|
|
814
|
+
* invoke it rather than throw immediately. The handler decides whether to
|
|
815
|
+
* throw synchronously, continue, or ask the coordinator for an extension.
|
|
811
816
|
*/
|
|
812
817
|
get onThreshold() {
|
|
813
818
|
return this._onThreshold;
|
|
@@ -815,7 +820,12 @@ var SubagentBudget = class _SubagentBudget {
|
|
|
815
820
|
set onThreshold(fn) {
|
|
816
821
|
this._onThreshold = fn;
|
|
817
822
|
}
|
|
818
|
-
|
|
823
|
+
/** Returns the current negotiation mode. */
|
|
824
|
+
get mode() {
|
|
825
|
+
return this._mode;
|
|
826
|
+
}
|
|
827
|
+
constructor(limits = {}, mode = "auto") {
|
|
828
|
+
this._mode = mode;
|
|
819
829
|
this.limits = { ...limits };
|
|
820
830
|
}
|
|
821
831
|
start() {
|
|
@@ -831,16 +841,23 @@ var SubagentBudget = class _SubagentBudget {
|
|
|
831
841
|
return false;
|
|
832
842
|
}
|
|
833
843
|
/**
|
|
834
|
-
* Synchronous budget check
|
|
835
|
-
*
|
|
836
|
-
*
|
|
837
|
-
*
|
|
838
|
-
*
|
|
844
|
+
* Synchronous budget check. Always throws synchronously so callers (especially
|
|
845
|
+
* test event handlers using `expect().toThrow()`) get an unhandled rejection
|
|
846
|
+
* when the budget is exceeded without a handler.
|
|
847
|
+
*
|
|
848
|
+
* Decision table:
|
|
849
|
+
* - no `onThreshold` handler → throw `BudgetExceededError` (hard stop, always)
|
|
850
|
+
* - `mode === 'sync'` → throw `BudgetExceededError` (hard stop, always)
|
|
851
|
+
* - `mode === 'auto'` + no listener → throw `BudgetExceededError` (hard stop; no one to ask)
|
|
852
|
+
* - `mode === 'auto'` + listener → throw `BudgetThresholdSignal` with async decision promise
|
|
839
853
|
*/
|
|
840
854
|
checkLimit(kind, used, limit) {
|
|
841
855
|
if (!this._onThreshold) {
|
|
842
856
|
throw new BudgetExceededError(kind, limit, used);
|
|
843
857
|
}
|
|
858
|
+
if (this._mode === "sync") {
|
|
859
|
+
throw new BudgetExceededError(kind, limit, used);
|
|
860
|
+
}
|
|
844
861
|
const bus = this._events;
|
|
845
862
|
if (!bus || !bus.hasListenerFor("budget.threshold_reached")) {
|
|
846
863
|
throw new BudgetExceededError(kind, limit, used);
|
|
@@ -857,8 +874,8 @@ var SubagentBudget = class _SubagentBudget {
|
|
|
857
874
|
* `pendingExtensions` slot in `finally`.
|
|
858
875
|
*
|
|
859
876
|
* The 'continue' return from a sync handler is treated as
|
|
860
|
-
* `{ extend: {} }` — keep going without patching
|
|
861
|
-
*
|
|
877
|
+
* `{ extend: {} }` — keep going without patching; next overrun fires
|
|
878
|
+
* a fresh signal.
|
|
862
879
|
*/
|
|
863
880
|
async negotiateExtension(kind, used, limit) {
|
|
864
881
|
try {
|
|
@@ -866,11 +883,6 @@ var SubagentBudget = class _SubagentBudget {
|
|
|
866
883
|
kind,
|
|
867
884
|
used,
|
|
868
885
|
limit,
|
|
869
|
-
// Inject a requestDecision helper the handler can call to emit the
|
|
870
|
-
// budget.threshold_reached event and wait for the coordinator's verdict.
|
|
871
|
-
// A hard fallback timer guarantees the promise eventually resolves
|
|
872
|
-
// even if no listener responds — without it, an absent/detached
|
|
873
|
-
// Director would leave the budget permanently in "asking" state.
|
|
874
886
|
requestDecision: () => {
|
|
875
887
|
const bus = this._events;
|
|
876
888
|
if (!bus || !bus.hasListenerFor("budget.threshold_reached")) {
|
|
@@ -954,12 +966,12 @@ var SubagentBudget = class _SubagentBudget {
|
|
|
954
966
|
}
|
|
955
967
|
}
|
|
956
968
|
/**
|
|
957
|
-
* Wall-clock budget check. Unlike other limits, timeout
|
|
958
|
-
*
|
|
959
|
-
*
|
|
960
|
-
*
|
|
961
|
-
*
|
|
962
|
-
*
|
|
969
|
+
* Wall-clock budget check. Unlike other limits, timeout check passes through
|
|
970
|
+
* `checkLimit` and is subject to the same negotiation-mode decision table.
|
|
971
|
+
* In practice, `'sync'` mode (the usual test configuration) means a timeout
|
|
972
|
+
* immediately throws `BudgetExceededError`. In production with a coordinator,
|
|
973
|
+
* a timeout emits `budget.threshold_reached` so the Director can decide whether
|
|
974
|
+
* to extend or abort.
|
|
963
975
|
*/
|
|
964
976
|
checkTimeout() {
|
|
965
977
|
if (this.startTime === null || this.limits.timeoutMs === void 0) return;
|
|
@@ -3433,6 +3445,7 @@ var DefaultMultiAgentCoordinator = class extends EventEmitter {
|
|
|
3433
3445
|
coordinatorId;
|
|
3434
3446
|
config;
|
|
3435
3447
|
runner;
|
|
3448
|
+
fleetBus;
|
|
3436
3449
|
subagents = /* @__PURE__ */ new Map();
|
|
3437
3450
|
pendingTasks = [];
|
|
3438
3451
|
completedResults = [];
|
|
@@ -3461,6 +3474,14 @@ var DefaultMultiAgentCoordinator = class extends EventEmitter {
|
|
|
3461
3474
|
setRunner(runner) {
|
|
3462
3475
|
this.runner = runner;
|
|
3463
3476
|
}
|
|
3477
|
+
/**
|
|
3478
|
+
* Wire a FleetBus for director-mode event emission. Call after the
|
|
3479
|
+
* FleetManager is constructed so the coordinator can emit lifecycle
|
|
3480
|
+
* events the TUI and monitoring tools subscribe to.
|
|
3481
|
+
*/
|
|
3482
|
+
setFleetBus(fleet) {
|
|
3483
|
+
this.fleetBus = fleet;
|
|
3484
|
+
}
|
|
3464
3485
|
/**
|
|
3465
3486
|
* Change the in-flight dispatch ceiling at runtime. Lowering does NOT
|
|
3466
3487
|
* preempt running tasks — already-dispatched subagents finish their
|
|
@@ -3496,6 +3517,18 @@ var DefaultMultiAgentCoordinator = class extends EventEmitter {
|
|
|
3496
3517
|
abortController: new AbortController()
|
|
3497
3518
|
});
|
|
3498
3519
|
this.emit("subagent.started", { subagent: { ...subagent, id } });
|
|
3520
|
+
this.fleetBus?.emit({
|
|
3521
|
+
subagentId: id,
|
|
3522
|
+
ts: Date.now(),
|
|
3523
|
+
type: "subagent.assigned",
|
|
3524
|
+
payload: {
|
|
3525
|
+
subagentId: id,
|
|
3526
|
+
name: subagent.name,
|
|
3527
|
+
provider: subagent.provider,
|
|
3528
|
+
model: subagent.model
|
|
3529
|
+
}
|
|
3530
|
+
});
|
|
3531
|
+
this.emitCoordinatorStats();
|
|
3499
3532
|
return { subagentId: id, agentId: id };
|
|
3500
3533
|
}
|
|
3501
3534
|
async assign(task) {
|
|
@@ -3528,6 +3561,13 @@ var DefaultMultiAgentCoordinator = class extends EventEmitter {
|
|
|
3528
3561
|
subagent.currentTask = void 0;
|
|
3529
3562
|
subagent.context.parentBridge = null;
|
|
3530
3563
|
this.emit("subagent.stopped", { subagentId, reason: "stopped by coordinator" });
|
|
3564
|
+
this.fleetBus?.emit({
|
|
3565
|
+
subagentId,
|
|
3566
|
+
ts: Date.now(),
|
|
3567
|
+
type: "subagent.stopped",
|
|
3568
|
+
payload: { subagentId, reason: "stopped by coordinator" }
|
|
3569
|
+
});
|
|
3570
|
+
this.emitCoordinatorStats();
|
|
3531
3571
|
}
|
|
3532
3572
|
async stopAll() {
|
|
3533
3573
|
this.drainPendingAsAborted("Coordinator stopAll() drained the pending queue");
|
|
@@ -3559,6 +3599,22 @@ var DefaultMultiAgentCoordinator = class extends EventEmitter {
|
|
|
3559
3599
|
completed: this.completedResults.length
|
|
3560
3600
|
};
|
|
3561
3601
|
}
|
|
3602
|
+
/** Emit a reactive coordinator.stats event on FleetBus so the TUI can subscribe. */
|
|
3603
|
+
emitCoordinatorStats() {
|
|
3604
|
+
const stats = this.getStats();
|
|
3605
|
+
const subagentStatuses = Array.from(this.subagents.entries()).map(([id, s]) => ({
|
|
3606
|
+
subagentId: id,
|
|
3607
|
+
taskId: s.currentTask ?? "",
|
|
3608
|
+
status: s.status,
|
|
3609
|
+
assigned: s.context.parentBridge !== null
|
|
3610
|
+
}));
|
|
3611
|
+
this.fleetBus?.emit({
|
|
3612
|
+
subagentId: this.coordinatorId,
|
|
3613
|
+
ts: Date.now(),
|
|
3614
|
+
type: "coordinator.stats",
|
|
3615
|
+
payload: { ...stats, subagentStatuses }
|
|
3616
|
+
});
|
|
3617
|
+
}
|
|
3562
3618
|
getStatus() {
|
|
3563
3619
|
return {
|
|
3564
3620
|
coordinatorId: this.coordinatorId,
|
|
@@ -3733,7 +3789,15 @@ var DefaultMultiAgentCoordinator = class extends EventEmitter {
|
|
|
3733
3789
|
subagent.currentTask = task.id;
|
|
3734
3790
|
task.subagentId = subagentId;
|
|
3735
3791
|
subagent.context.tasks.push(task);
|
|
3792
|
+
this.fleetBus?.emit({
|
|
3793
|
+
subagentId,
|
|
3794
|
+
taskId: task.id,
|
|
3795
|
+
ts: Date.now(),
|
|
3796
|
+
type: "subagent.running",
|
|
3797
|
+
payload: { subagentId, taskId: task.id }
|
|
3798
|
+
});
|
|
3736
3799
|
this.emit("task.assigned", { task, subagentId });
|
|
3800
|
+
this.emitCoordinatorStats();
|
|
3737
3801
|
const rawMaxIterations = subagent.config.maxIterations;
|
|
3738
3802
|
const rawMaxToolCalls = subagent.config.maxToolCalls;
|
|
3739
3803
|
const rawMaxTokens = subagent.config.maxTokens;
|
|
@@ -3877,13 +3941,34 @@ var DefaultMultiAgentCoordinator = class extends EventEmitter {
|
|
|
3877
3941
|
if (subagent.abortController.signal.aborted) {
|
|
3878
3942
|
subagent.abortController = new AbortController();
|
|
3879
3943
|
}
|
|
3944
|
+
this.fleetBus?.emit({
|
|
3945
|
+
subagentId: result.subagentId,
|
|
3946
|
+
ts: Date.now(),
|
|
3947
|
+
type: "subagent.idle",
|
|
3948
|
+
payload: { subagentId: result.subagentId }
|
|
3949
|
+
});
|
|
3880
3950
|
}
|
|
3881
3951
|
this.terminating.delete(result.subagentId);
|
|
3882
3952
|
this.emit("task.completed", {
|
|
3883
3953
|
task: subagent?.context.tasks.find((t) => t.id === result.taskId) ?? { id: result.taskId },
|
|
3884
3954
|
result
|
|
3885
3955
|
});
|
|
3956
|
+
this.fleetBus?.emit({
|
|
3957
|
+
subagentId: result.subagentId,
|
|
3958
|
+
taskId: result.taskId,
|
|
3959
|
+
ts: Date.now(),
|
|
3960
|
+
type: "subagent.completed",
|
|
3961
|
+
payload: {
|
|
3962
|
+
subagentId: result.subagentId,
|
|
3963
|
+
taskId: result.taskId,
|
|
3964
|
+
status: result.status,
|
|
3965
|
+
iterations: result.iterations,
|
|
3966
|
+
toolCalls: result.toolCalls,
|
|
3967
|
+
durationMs: result.durationMs
|
|
3968
|
+
}
|
|
3969
|
+
});
|
|
3886
3970
|
this.tryDispatchNext();
|
|
3971
|
+
this.emitCoordinatorStats();
|
|
3887
3972
|
if (this.isDone()) {
|
|
3888
3973
|
this.emit("done", {
|
|
3889
3974
|
results: this.completedResults,
|
|
@@ -3995,12 +4080,137 @@ function providerErrorToSubagentError(err, message, cause) {
|
|
|
3995
4080
|
}
|
|
3996
4081
|
return { kind: "unknown", message, retryable: err.retryable, cause };
|
|
3997
4082
|
}
|
|
4083
|
+
|
|
4084
|
+
// src/coordination/dispatcher.ts
|
|
4085
|
+
var DEFAULT_DISPATCH_ROLE = "executor";
|
|
4086
|
+
function normalize(text) {
|
|
4087
|
+
return ` ${text.toLowerCase().replace(/[^a-z0-9]+/g, " ").trim()} `;
|
|
4088
|
+
}
|
|
4089
|
+
function scoreAgents(task, catalog = AGENT_CATALOG) {
|
|
4090
|
+
const hay = normalize(task);
|
|
4091
|
+
const out = [];
|
|
4092
|
+
for (const def of Object.values(catalog)) {
|
|
4093
|
+
if (!def?.config?.role) continue;
|
|
4094
|
+
let score = 0;
|
|
4095
|
+
const matched = [];
|
|
4096
|
+
for (const kw of def.capability.keywords) {
|
|
4097
|
+
const needle = normalize(kw);
|
|
4098
|
+
if (hay.includes(needle.trimEnd() + " ") || hay.includes(" " + needle.trimStart())) {
|
|
4099
|
+
const words = kw.trim().split(/\s+/).length;
|
|
4100
|
+
score += words;
|
|
4101
|
+
matched.push(kw);
|
|
4102
|
+
}
|
|
4103
|
+
}
|
|
4104
|
+
if (score > 0) {
|
|
4105
|
+
out.push({ role: def.config.role, name: def.config.name, score, matched });
|
|
4106
|
+
}
|
|
4107
|
+
}
|
|
4108
|
+
out.sort((a, b) => b.score - a.score);
|
|
4109
|
+
return out;
|
|
4110
|
+
}
|
|
4111
|
+
function heuristicConfidence(candidates) {
|
|
4112
|
+
if (candidates.length === 0) return 0;
|
|
4113
|
+
const top = candidates[0].score;
|
|
4114
|
+
const second = candidates[1]?.score ?? 0;
|
|
4115
|
+
const strength = Math.min(1, top / 3);
|
|
4116
|
+
const margin = (top - second + 1) / (top + 1);
|
|
4117
|
+
return Math.min(1, strength * margin);
|
|
4118
|
+
}
|
|
4119
|
+
async function dispatchAgent(task, opts = {}) {
|
|
4120
|
+
const catalog = opts.catalog ?? AGENT_CATALOG;
|
|
4121
|
+
const threshold = opts.confidenceThreshold ?? 0.4;
|
|
4122
|
+
const maxCandidates = opts.maxCandidates ?? 6;
|
|
4123
|
+
const candidates = scoreAgents(task, catalog);
|
|
4124
|
+
const confidence = heuristicConfidence(candidates);
|
|
4125
|
+
const top = candidates[0];
|
|
4126
|
+
if (top && confidence >= threshold) {
|
|
4127
|
+
return {
|
|
4128
|
+
role: top.role,
|
|
4129
|
+
definition: catalog[top.role],
|
|
4130
|
+
confidence,
|
|
4131
|
+
method: "heuristic",
|
|
4132
|
+
reason: `Matched keywords: ${top.matched.slice(0, 4).join(", ")}`,
|
|
4133
|
+
alternatives: candidates.slice(1, maxCandidates)
|
|
4134
|
+
};
|
|
4135
|
+
}
|
|
4136
|
+
if (opts.classifier) {
|
|
4137
|
+
const pool = (candidates.length > 0 ? candidates.slice(0, maxCandidates).map((c) => catalog[c.role]) : ALL_AGENT_DEFINITIONS).map((d) => ({
|
|
4138
|
+
role: d.config.role,
|
|
4139
|
+
name: d.config.name,
|
|
4140
|
+
summary: d.capability.summary
|
|
4141
|
+
}));
|
|
4142
|
+
try {
|
|
4143
|
+
const choice = await opts.classifier(task, pool);
|
|
4144
|
+
if (choice && catalog[choice.role]) {
|
|
4145
|
+
return {
|
|
4146
|
+
role: choice.role,
|
|
4147
|
+
definition: catalog[choice.role],
|
|
4148
|
+
confidence: 1,
|
|
4149
|
+
method: "llm",
|
|
4150
|
+
reason: choice.reason ?? "Selected by LLM classifier",
|
|
4151
|
+
alternatives: candidates.slice(0, maxCandidates).filter((c) => c.role !== choice.role)
|
|
4152
|
+
};
|
|
4153
|
+
}
|
|
4154
|
+
} catch {
|
|
4155
|
+
}
|
|
4156
|
+
}
|
|
4157
|
+
if (top) {
|
|
4158
|
+
return {
|
|
4159
|
+
role: top.role,
|
|
4160
|
+
definition: catalog[top.role],
|
|
4161
|
+
confidence,
|
|
4162
|
+
method: "heuristic",
|
|
4163
|
+
reason: `Weak match (${top.matched.slice(0, 3).join(", ") || "low signal"})`,
|
|
4164
|
+
alternatives: candidates.slice(1, maxCandidates)
|
|
4165
|
+
};
|
|
4166
|
+
}
|
|
4167
|
+
const fallbackRole = catalog[DEFAULT_DISPATCH_ROLE] ? DEFAULT_DISPATCH_ROLE : Object.keys(catalog)[0];
|
|
4168
|
+
return {
|
|
4169
|
+
role: fallbackRole,
|
|
4170
|
+
definition: catalog[fallbackRole],
|
|
4171
|
+
confidence: 0,
|
|
4172
|
+
method: "fallback",
|
|
4173
|
+
reason: "No keyword signal; defaulting to the generalist Executor",
|
|
4174
|
+
alternatives: []
|
|
4175
|
+
};
|
|
4176
|
+
}
|
|
4177
|
+
function makeLLMClassifier(complete) {
|
|
4178
|
+
return async (task, candidates) => {
|
|
4179
|
+
const list = candidates.map((c, i) => `${i + 1}. ${c.role} \u2014 ${c.summary}`).join("\n");
|
|
4180
|
+
const prompt = `You are an agent router. Pick the single best agent for the task.
|
|
4181
|
+
|
|
4182
|
+
Task:
|
|
4183
|
+
${task}
|
|
4184
|
+
|
|
4185
|
+
Agents:
|
|
4186
|
+
${list}
|
|
4187
|
+
|
|
4188
|
+
Reply with ONLY a compact JSON object: {"role":"<one role id from the list>","reason":"<short why>"}.
|
|
4189
|
+
Do not add prose, markdown, or code fences.`;
|
|
4190
|
+
const raw = (await complete(prompt)).trim();
|
|
4191
|
+
const match = raw.match(/\{[\s\S]*\}/);
|
|
4192
|
+
if (!match) return null;
|
|
4193
|
+
try {
|
|
4194
|
+
const parsed = JSON.parse(match[0]);
|
|
4195
|
+
if (typeof parsed.role !== "string") return null;
|
|
4196
|
+
const role = parsed.role.trim();
|
|
4197
|
+
const valid = candidates.some((c) => c.role === role);
|
|
4198
|
+
if (!valid) return null;
|
|
4199
|
+
return { role, reason: typeof parsed.reason === "string" ? parsed.reason : void 0 };
|
|
4200
|
+
} catch {
|
|
4201
|
+
return null;
|
|
4202
|
+
}
|
|
4203
|
+
};
|
|
4204
|
+
}
|
|
4205
|
+
|
|
4206
|
+
// src/coordination/director-tools.ts
|
|
3998
4207
|
function makeSpawnTool(director, roster) {
|
|
3999
4208
|
const inputSchema = {
|
|
4000
4209
|
type: "object",
|
|
4001
4210
|
properties: {
|
|
4002
|
-
role: { type: "string", description: "Roster role id
|
|
4003
|
-
|
|
4211
|
+
role: { type: "string", description: "Roster role id. When set, the spawn uses the matching config from the roster and ignores other fields." },
|
|
4212
|
+
description: { type: "string", description: "Free-form task description. When `role` is not set, the director uses the smart dispatcher to route this to the best-matching catalog agent. Use this when you don't know the exact role name." },
|
|
4213
|
+
name: { type: "string", description: "Display name for the subagent. Used as a fallback when description-based dispatch does not resolve a role." },
|
|
4004
4214
|
provider: { type: "string", description: 'Provider id (e.g. "anthropic", "openai"). Defaults to the leader provider when omitted.' },
|
|
4005
4215
|
model: { type: "string", description: "Model id within the provider. Defaults to the leader model when omitted." },
|
|
4006
4216
|
systemPromptOverride: { type: "string", description: "Extra prompt text appended after the role-base prompt." },
|
|
@@ -4013,18 +4223,38 @@ function makeSpawnTool(director, roster) {
|
|
|
4013
4223
|
return {
|
|
4014
4224
|
name: "spawn_subagent",
|
|
4015
4225
|
description: "Create a new subagent under this director. Returns the subagent id.",
|
|
4016
|
-
usageHint: "
|
|
4226
|
+
usageHint: "Pass `role` (matches the roster), `description` (smart dispatch to best agent), or `name` + `provider`/`model`. Returns `{ subagentId }`.",
|
|
4017
4227
|
permission: "auto",
|
|
4018
4228
|
mutating: false,
|
|
4019
4229
|
inputSchema,
|
|
4020
4230
|
async execute(input) {
|
|
4021
4231
|
const i = input ?? {};
|
|
4022
4232
|
const role = typeof i.role === "string" ? i.role : void 0;
|
|
4023
|
-
const
|
|
4024
|
-
|
|
4025
|
-
|
|
4233
|
+
const description = typeof i.description === "string" ? i.description : void 0;
|
|
4234
|
+
let cfg;
|
|
4235
|
+
if (role && roster) {
|
|
4236
|
+
const base = roster[role];
|
|
4237
|
+
if (!base) return { error: `unknown role "${role}". roster has: ${Object.keys(roster).join(", ")}` };
|
|
4238
|
+
cfg = instantiateRosterConfig(role, base);
|
|
4239
|
+
} else if (description && !role) {
|
|
4240
|
+
const dispatchResult = await dispatchAgent(description, {
|
|
4241
|
+
classifier: director.dispatchClassifier,
|
|
4242
|
+
catalog: roster
|
|
4243
|
+
});
|
|
4244
|
+
const dispatchRole = dispatchResult.role;
|
|
4245
|
+
if (roster && roster[dispatchRole]) {
|
|
4246
|
+
cfg = instantiateRosterConfig(dispatchRole, roster[dispatchRole]);
|
|
4247
|
+
} else {
|
|
4248
|
+
const def = dispatchResult.definition;
|
|
4249
|
+
cfg = {
|
|
4250
|
+
name: def.config.name ?? dispatchRole,
|
|
4251
|
+
role: dispatchRole,
|
|
4252
|
+
provider: def.config.provider,
|
|
4253
|
+
model: def.config.model
|
|
4254
|
+
};
|
|
4255
|
+
}
|
|
4026
4256
|
}
|
|
4027
|
-
|
|
4257
|
+
cfg ??= { name: i.name ?? "subagent" };
|
|
4028
4258
|
if (typeof i.name === "string") cfg.name = i.name;
|
|
4029
4259
|
if (typeof i.provider === "string") cfg.provider = i.provider;
|
|
4030
4260
|
if (typeof i.model === "string") cfg.model = i.model;
|
|
@@ -4034,7 +4264,7 @@ function makeSpawnTool(director, roster) {
|
|
|
4034
4264
|
if (typeof i.maxCostUsd === "number") cfg.maxCostUsd = i.maxCostUsd;
|
|
4035
4265
|
try {
|
|
4036
4266
|
const subagentId = await director.spawn(cfg);
|
|
4037
|
-
return { subagentId, provider: cfg.provider, model: cfg.model, name: cfg.name };
|
|
4267
|
+
return { subagentId, provider: cfg.provider, model: cfg.model, name: cfg.name, role: cfg.role };
|
|
4038
4268
|
} catch (err) {
|
|
4039
4269
|
if (err instanceof FleetSpawnBudgetError) {
|
|
4040
4270
|
return { error: err.message, kind: err.kind, limit: err.limit, observed: err.observed };
|
|
@@ -4158,12 +4388,21 @@ function makeTerminateTool(director) {
|
|
|
4158
4388
|
function makeFleetStatusTool(director) {
|
|
4159
4389
|
return {
|
|
4160
4390
|
name: "fleet_status",
|
|
4161
|
-
description: "Snapshot of the fleet \u2014 every subagent's current status,
|
|
4391
|
+
description: "Snapshot of the fleet \u2014 every subagent's current status, coordinator counts (total/running/idle/stopped), pending task descriptions, and usage rollup.",
|
|
4162
4392
|
permission: "auto",
|
|
4163
4393
|
mutating: false,
|
|
4164
4394
|
inputSchema: { type: "object", properties: {}, required: [] },
|
|
4165
4395
|
async execute() {
|
|
4166
|
-
|
|
4396
|
+
const base = director.status();
|
|
4397
|
+
const fm = director.fleetManager;
|
|
4398
|
+
const stats = fm?.getFleetStats();
|
|
4399
|
+
const fleetStatus = fm?.getFleetStatus();
|
|
4400
|
+
return {
|
|
4401
|
+
subagents: base.subagents,
|
|
4402
|
+
coordinatorStats: stats ? { total: stats.total, running: stats.running, idle: stats.idle, stopped: stats.stopped } : void 0,
|
|
4403
|
+
pending: fleetStatus?.pending ?? [],
|
|
4404
|
+
usage: fm?.snapshot()
|
|
4405
|
+
};
|
|
4167
4406
|
}
|
|
4168
4407
|
};
|
|
4169
4408
|
}
|
|
@@ -4365,6 +4604,8 @@ var Director = class {
|
|
|
4365
4604
|
* default cap.
|
|
4366
4605
|
*/
|
|
4367
4606
|
taskCompletedListener = null;
|
|
4607
|
+
/** Optional LLM classifier for smart dispatch. Passed from options. */
|
|
4608
|
+
dispatchClassifier;
|
|
4368
4609
|
constructor(opts) {
|
|
4369
4610
|
this.id = opts.config.coordinatorId || randomUUID();
|
|
4370
4611
|
this.manifestPath = opts.manifestPath;
|
|
@@ -4377,6 +4618,7 @@ var Director = class {
|
|
|
4377
4618
|
this.spawnDepth = opts.spawnDepth ?? 0;
|
|
4378
4619
|
this.sessionWriter = opts.sessionWriter ?? null;
|
|
4379
4620
|
this.manifestDebounceMs = opts.manifestDebounceMs ?? 2e3;
|
|
4621
|
+
this.dispatchClassifier = opts.dispatchClassifier;
|
|
4380
4622
|
this.maxFleetCostUsd = opts.directorBudget?.maxCostUsd ?? Number.POSITIVE_INFINITY;
|
|
4381
4623
|
this.maxBudgetExtensions = opts.maxBudgetExtensions ?? 5;
|
|
4382
4624
|
this.sessionsRoot = opts.sessionsRoot;
|
|
@@ -4406,7 +4648,10 @@ var Director = class {
|
|
|
4406
4648
|
this.fleet = new FleetBus();
|
|
4407
4649
|
this.usage = new FleetUsageAggregator(
|
|
4408
4650
|
this.fleet,
|
|
4409
|
-
(id) =>
|
|
4651
|
+
(id, provider, model) => {
|
|
4652
|
+
if (provider && model) return this.priceLookups.get(`${provider}/${model}`);
|
|
4653
|
+
return void 0;
|
|
4654
|
+
},
|
|
4410
4655
|
(id) => this.subagentMeta.get(id)
|
|
4411
4656
|
);
|
|
4412
4657
|
}
|
|
@@ -4414,6 +4659,8 @@ var Director = class {
|
|
|
4414
4659
|
{ ...opts.config, coordinatorId: this.id },
|
|
4415
4660
|
{ runner: opts.runner }
|
|
4416
4661
|
);
|
|
4662
|
+
this.coordinator.setFleetBus(this.fleet);
|
|
4663
|
+
this.fleetManager?.setCoordinator(this.coordinator);
|
|
4417
4664
|
this.taskCompletedListener = (payload) => {
|
|
4418
4665
|
const r = payload.result;
|
|
4419
4666
|
this.completed.set(r.taskId, r);
|
|
@@ -4448,7 +4695,11 @@ var Director = class {
|
|
|
4448
4695
|
title
|
|
4449
4696
|
}
|
|
4450
4697
|
);
|
|
4451
|
-
this.
|
|
4698
|
+
if (this.fleetManager) {
|
|
4699
|
+
this.fleetManager.flushManifest();
|
|
4700
|
+
} else {
|
|
4701
|
+
this.scheduleManifest();
|
|
4702
|
+
}
|
|
4452
4703
|
};
|
|
4453
4704
|
this.coordinator.on("task.completed", this.taskCompletedListener);
|
|
4454
4705
|
const extendCounts = /* @__PURE__ */ new Map();
|
|
@@ -4540,10 +4791,17 @@ var Director = class {
|
|
|
4540
4791
|
}
|
|
4541
4792
|
}
|
|
4542
4793
|
/** Debounced manifest writer. A burst of spawn/assign/complete events
|
|
4543
|
-
* collapses into one write. Set `manifestDebounceMs` to 0 to
|
|
4794
|
+
* collapses into one write. Set `manifestDebounceMs` to 0 to write
|
|
4795
|
+
* synchronously (no debounce); set to negative to disable entirely. */
|
|
4544
4796
|
scheduleManifest() {
|
|
4545
|
-
if (!this.manifestPath
|
|
4546
|
-
if (this.
|
|
4797
|
+
if (!this.manifestPath) return;
|
|
4798
|
+
if (this.manifestDebounceMs === 0) {
|
|
4799
|
+
void this.writeManifest().catch(
|
|
4800
|
+
(err) => this.logShutdownError("manifest_write_debounced", err)
|
|
4801
|
+
);
|
|
4802
|
+
return;
|
|
4803
|
+
}
|
|
4804
|
+
if (this.manifestDebounceMs < 0) return;
|
|
4547
4805
|
this.manifestTimer = setTimeout(() => {
|
|
4548
4806
|
this.manifestTimer = null;
|
|
4549
4807
|
void this.writeManifest().catch(
|
|
@@ -4592,7 +4850,9 @@ var Director = class {
|
|
|
4592
4850
|
provider: config.provider,
|
|
4593
4851
|
model: config.model
|
|
4594
4852
|
});
|
|
4595
|
-
if (priceLookup
|
|
4853
|
+
if (priceLookup && config.provider && config.model) {
|
|
4854
|
+
this.priceLookups.set(`${config.provider}/${config.model}`, priceLookup);
|
|
4855
|
+
}
|
|
4596
4856
|
}
|
|
4597
4857
|
const subagentBridge = new InMemoryAgentBridge(
|
|
4598
4858
|
{ agentId: result.subagentId, coordinatorId: this.id },
|
|
@@ -6110,129 +6370,6 @@ function makeDirectorSessionFactory(opts) {
|
|
|
6110
6370
|
};
|
|
6111
6371
|
}
|
|
6112
6372
|
|
|
6113
|
-
// src/coordination/dispatcher.ts
|
|
6114
|
-
var DEFAULT_DISPATCH_ROLE = "executor";
|
|
6115
|
-
function normalize(text) {
|
|
6116
|
-
return ` ${text.toLowerCase().replace(/[^a-z0-9]+/g, " ").trim()} `;
|
|
6117
|
-
}
|
|
6118
|
-
function scoreAgents(task, catalog = AGENT_CATALOG) {
|
|
6119
|
-
const hay = normalize(task);
|
|
6120
|
-
const out = [];
|
|
6121
|
-
for (const def of Object.values(catalog)) {
|
|
6122
|
-
const role = def.config.role;
|
|
6123
|
-
if (!role) continue;
|
|
6124
|
-
let score = 0;
|
|
6125
|
-
const matched = [];
|
|
6126
|
-
for (const kw of def.capability.keywords) {
|
|
6127
|
-
const needle = normalize(kw);
|
|
6128
|
-
if (hay.includes(needle.trimEnd() + " ") || hay.includes(" " + needle.trimStart())) {
|
|
6129
|
-
const words = kw.trim().split(/\s+/).length;
|
|
6130
|
-
score += words;
|
|
6131
|
-
matched.push(kw);
|
|
6132
|
-
}
|
|
6133
|
-
}
|
|
6134
|
-
if (score > 0) {
|
|
6135
|
-
out.push({ role, name: def.config.name, score, matched });
|
|
6136
|
-
}
|
|
6137
|
-
}
|
|
6138
|
-
out.sort((a, b) => b.score - a.score);
|
|
6139
|
-
return out;
|
|
6140
|
-
}
|
|
6141
|
-
function heuristicConfidence(candidates) {
|
|
6142
|
-
if (candidates.length === 0) return 0;
|
|
6143
|
-
const top = candidates[0].score;
|
|
6144
|
-
const second = candidates[1]?.score ?? 0;
|
|
6145
|
-
const strength = Math.min(1, top / 3);
|
|
6146
|
-
const margin = (top - second + 1) / (top + 1);
|
|
6147
|
-
return Math.min(1, strength * margin);
|
|
6148
|
-
}
|
|
6149
|
-
async function dispatchAgent(task, opts = {}) {
|
|
6150
|
-
const catalog = opts.catalog ?? AGENT_CATALOG;
|
|
6151
|
-
const threshold = opts.confidenceThreshold ?? 0.4;
|
|
6152
|
-
const maxCandidates = opts.maxCandidates ?? 6;
|
|
6153
|
-
const candidates = scoreAgents(task, catalog);
|
|
6154
|
-
const confidence = heuristicConfidence(candidates);
|
|
6155
|
-
const top = candidates[0];
|
|
6156
|
-
if (top && confidence >= threshold) {
|
|
6157
|
-
return {
|
|
6158
|
-
role: top.role,
|
|
6159
|
-
definition: catalog[top.role],
|
|
6160
|
-
confidence,
|
|
6161
|
-
method: "heuristic",
|
|
6162
|
-
reason: `Matched keywords: ${top.matched.slice(0, 4).join(", ")}`,
|
|
6163
|
-
alternatives: candidates.slice(1, maxCandidates)
|
|
6164
|
-
};
|
|
6165
|
-
}
|
|
6166
|
-
if (opts.classifier) {
|
|
6167
|
-
const pool = (candidates.length > 0 ? candidates.slice(0, maxCandidates).map((c) => catalog[c.role]) : ALL_AGENT_DEFINITIONS).map((d) => ({
|
|
6168
|
-
role: d.config.role,
|
|
6169
|
-
name: d.config.name,
|
|
6170
|
-
summary: d.capability.summary
|
|
6171
|
-
}));
|
|
6172
|
-
try {
|
|
6173
|
-
const choice = await opts.classifier(task, pool);
|
|
6174
|
-
if (choice && catalog[choice.role]) {
|
|
6175
|
-
return {
|
|
6176
|
-
role: choice.role,
|
|
6177
|
-
definition: catalog[choice.role],
|
|
6178
|
-
confidence: 1,
|
|
6179
|
-
method: "llm",
|
|
6180
|
-
reason: choice.reason ?? "Selected by LLM classifier",
|
|
6181
|
-
alternatives: candidates.slice(0, maxCandidates).filter((c) => c.role !== choice.role)
|
|
6182
|
-
};
|
|
6183
|
-
}
|
|
6184
|
-
} catch {
|
|
6185
|
-
}
|
|
6186
|
-
}
|
|
6187
|
-
if (top) {
|
|
6188
|
-
return {
|
|
6189
|
-
role: top.role,
|
|
6190
|
-
definition: catalog[top.role],
|
|
6191
|
-
confidence,
|
|
6192
|
-
method: "heuristic",
|
|
6193
|
-
reason: `Weak match (${top.matched.slice(0, 3).join(", ") || "low signal"})`,
|
|
6194
|
-
alternatives: candidates.slice(1, maxCandidates)
|
|
6195
|
-
};
|
|
6196
|
-
}
|
|
6197
|
-
const fallbackRole = catalog[DEFAULT_DISPATCH_ROLE] ? DEFAULT_DISPATCH_ROLE : Object.keys(catalog)[0];
|
|
6198
|
-
return {
|
|
6199
|
-
role: fallbackRole,
|
|
6200
|
-
definition: catalog[fallbackRole],
|
|
6201
|
-
confidence: 0,
|
|
6202
|
-
method: "fallback",
|
|
6203
|
-
reason: "No keyword signal; defaulting to the generalist Executor",
|
|
6204
|
-
alternatives: []
|
|
6205
|
-
};
|
|
6206
|
-
}
|
|
6207
|
-
function makeLLMClassifier(complete) {
|
|
6208
|
-
return async (task, candidates) => {
|
|
6209
|
-
const list = candidates.map((c, i) => `${i + 1}. ${c.role} \u2014 ${c.summary}`).join("\n");
|
|
6210
|
-
const prompt = `You are an agent router. Pick the single best agent for the task.
|
|
6211
|
-
|
|
6212
|
-
Task:
|
|
6213
|
-
${task}
|
|
6214
|
-
|
|
6215
|
-
Agents:
|
|
6216
|
-
${list}
|
|
6217
|
-
|
|
6218
|
-
Reply with ONLY a compact JSON object: {"role":"<one role id from the list>","reason":"<short why>"}.
|
|
6219
|
-
Do not add prose, markdown, or code fences.`;
|
|
6220
|
-
const raw = (await complete(prompt)).trim();
|
|
6221
|
-
const match = raw.match(/\{[\s\S]*\}/);
|
|
6222
|
-
if (!match) return null;
|
|
6223
|
-
try {
|
|
6224
|
-
const parsed = JSON.parse(match[0]);
|
|
6225
|
-
if (typeof parsed.role !== "string") return null;
|
|
6226
|
-
const role = parsed.role.trim();
|
|
6227
|
-
const valid = candidates.some((c) => c.role === role);
|
|
6228
|
-
if (!valid) return null;
|
|
6229
|
-
return { role, reason: typeof parsed.reason === "string" ? parsed.reason : void 0 };
|
|
6230
|
-
} catch {
|
|
6231
|
-
return null;
|
|
6232
|
-
}
|
|
6233
|
-
};
|
|
6234
|
-
}
|
|
6235
|
-
|
|
6236
6373
|
// src/coordination/auto-extend.ts
|
|
6237
6374
|
var DEFAULT_CEILING = {
|
|
6238
6375
|
maxIterations: 5e4,
|
|
@@ -6323,6 +6460,8 @@ var FleetManager = class {
|
|
|
6323
6460
|
pendingTasks = /* @__PURE__ */ new Map();
|
|
6324
6461
|
subagentMeta = /* @__PURE__ */ new Map();
|
|
6325
6462
|
priceLookups = /* @__PURE__ */ new Map();
|
|
6463
|
+
/** The coordinator (wired via setCoordinator by Director after construction). */
|
|
6464
|
+
coordinator = null;
|
|
6326
6465
|
constructor(opts = {}) {
|
|
6327
6466
|
this.manifestPath = opts.manifestPath;
|
|
6328
6467
|
this.sessionsRoot = opts.sessionsRoot;
|
|
@@ -6347,7 +6486,10 @@ var FleetManager = class {
|
|
|
6347
6486
|
this.fleet = new FleetBus();
|
|
6348
6487
|
this.usage = new FleetUsageAggregator(
|
|
6349
6488
|
this.fleet,
|
|
6350
|
-
(id) =>
|
|
6489
|
+
(id, provider, model) => {
|
|
6490
|
+
if (provider && model) return this.priceLookups.get(`${provider}/${model}`);
|
|
6491
|
+
return void 0;
|
|
6492
|
+
},
|
|
6351
6493
|
(id) => this.subagentMeta.get(id)
|
|
6352
6494
|
);
|
|
6353
6495
|
}
|
|
@@ -6357,11 +6499,26 @@ var FleetManager = class {
|
|
|
6357
6499
|
get fleetBus() {
|
|
6358
6500
|
return this.fleet;
|
|
6359
6501
|
}
|
|
6502
|
+
/**
|
|
6503
|
+
* Wire the coordinator after Director construction. The coordinator
|
|
6504
|
+
* is not available when FleetManager is constructed standalone.
|
|
6505
|
+
* Once set, `getFleetStats()` delegates to `coordinator.getStats()`.
|
|
6506
|
+
*/
|
|
6507
|
+
setCoordinator(coordinator) {
|
|
6508
|
+
this.coordinator = coordinator;
|
|
6509
|
+
}
|
|
6360
6510
|
snapshot() {
|
|
6361
6511
|
return this.usage.snapshot();
|
|
6362
6512
|
}
|
|
6363
6513
|
getSubagentMeta(id) {
|
|
6364
|
-
|
|
6514
|
+
const meta = this.subagentMeta.get(id);
|
|
6515
|
+
const manifest = this.manifestEntries.get(id);
|
|
6516
|
+
if (!meta && !manifest) return void 0;
|
|
6517
|
+
return {
|
|
6518
|
+
provider: meta?.provider ?? manifest?.provider,
|
|
6519
|
+
model: meta?.model ?? manifest?.model,
|
|
6520
|
+
name: manifest?.name
|
|
6521
|
+
};
|
|
6365
6522
|
}
|
|
6366
6523
|
/**
|
|
6367
6524
|
* Returns null if the spawn is allowed, or an object describing
|
|
@@ -6398,7 +6555,9 @@ var FleetManager = class {
|
|
|
6398
6555
|
provider: config.provider,
|
|
6399
6556
|
model: config.model
|
|
6400
6557
|
});
|
|
6401
|
-
if (priceLookup
|
|
6558
|
+
if (priceLookup && config.provider && config.model) {
|
|
6559
|
+
this.priceLookups.set(`${config.provider}/${config.model}`, priceLookup);
|
|
6560
|
+
}
|
|
6402
6561
|
this.manifestEntries.set(subagentId, {
|
|
6403
6562
|
subagentId,
|
|
6404
6563
|
name: config.name,
|
|
@@ -6454,9 +6613,21 @@ var FleetManager = class {
|
|
|
6454
6613
|
/**
|
|
6455
6614
|
* Debounced manifest write. Call after any state mutation
|
|
6456
6615
|
* (spawn, assign, complete) so a burst collapses into one write.
|
|
6616
|
+
* When `manifestDebounceMs` is 0, writes are synchronous (no debounce).
|
|
6457
6617
|
*/
|
|
6458
6618
|
scheduleManifest() {
|
|
6459
|
-
if (!this.manifestPath
|
|
6619
|
+
if (!this.manifestPath) return;
|
|
6620
|
+
if (this.manifestDebounceMs === 0) {
|
|
6621
|
+
void this.writeManifest().catch((err) => {
|
|
6622
|
+
const detail = err instanceof Error ? err.message : String(err);
|
|
6623
|
+
process.emitWarning(
|
|
6624
|
+
`FleetManager manifest write failed: ${detail}`,
|
|
6625
|
+
"FleetManagerWarning"
|
|
6626
|
+
);
|
|
6627
|
+
});
|
|
6628
|
+
return;
|
|
6629
|
+
}
|
|
6630
|
+
if (this.manifestDebounceMs < 0) return;
|
|
6460
6631
|
if (this.manifestTimer) return;
|
|
6461
6632
|
this.manifestTimer = setTimeout(() => {
|
|
6462
6633
|
this.manifestTimer = null;
|
|
@@ -6469,6 +6640,24 @@ var FleetManager = class {
|
|
|
6469
6640
|
});
|
|
6470
6641
|
}, this.manifestDebounceMs);
|
|
6471
6642
|
}
|
|
6643
|
+
/**
|
|
6644
|
+
* Bypass the debounce timer and write the manifest immediately.
|
|
6645
|
+
* Clears any pending debounce timer before writing.
|
|
6646
|
+
*/
|
|
6647
|
+
async flushManifest() {
|
|
6648
|
+
if (!this.manifestPath) return;
|
|
6649
|
+
if (this.manifestTimer) {
|
|
6650
|
+
clearTimeout(this.manifestTimer);
|
|
6651
|
+
this.manifestTimer = null;
|
|
6652
|
+
}
|
|
6653
|
+
await this.writeManifest().catch((err) => {
|
|
6654
|
+
const detail = err instanceof Error ? err.message : String(err);
|
|
6655
|
+
process.emitWarning(
|
|
6656
|
+
`FleetManager manifest write failed: ${detail}`,
|
|
6657
|
+
"FleetManagerWarning"
|
|
6658
|
+
);
|
|
6659
|
+
});
|
|
6660
|
+
}
|
|
6472
6661
|
/** Best-effort session event writer. Swallows failures. */
|
|
6473
6662
|
async appendSessionEvent(event) {
|
|
6474
6663
|
if (!this.sessionWriter) return;
|
|
@@ -6486,6 +6675,31 @@ var FleetManager = class {
|
|
|
6486
6675
|
removePendingTask(taskId) {
|
|
6487
6676
|
this.pendingTasks.delete(taskId);
|
|
6488
6677
|
}
|
|
6678
|
+
getFleetStats() {
|
|
6679
|
+
if (!this.coordinator) {
|
|
6680
|
+
return {
|
|
6681
|
+
total: 0,
|
|
6682
|
+
running: 0,
|
|
6683
|
+
idle: 0,
|
|
6684
|
+
stopped: 0,
|
|
6685
|
+
inFlight: 0,
|
|
6686
|
+
pending: 0,
|
|
6687
|
+
completed: 0,
|
|
6688
|
+
subagentStatuses: []
|
|
6689
|
+
};
|
|
6690
|
+
}
|
|
6691
|
+
const stats = this.coordinator.getStats();
|
|
6692
|
+
const subagentStatuses = [];
|
|
6693
|
+
for (const [subagentId, s] of this.coordinator["subagents"]) {
|
|
6694
|
+
subagentStatuses.push({
|
|
6695
|
+
subagentId,
|
|
6696
|
+
taskId: s.currentTask ?? "",
|
|
6697
|
+
status: s.status,
|
|
6698
|
+
assigned: s.context.parentBridge !== null
|
|
6699
|
+
});
|
|
6700
|
+
}
|
|
6701
|
+
return { ...stats, subagentStatuses };
|
|
6702
|
+
}
|
|
6489
6703
|
getFleetStatus() {
|
|
6490
6704
|
const pending = Array.from(this.pendingTasks.entries()).map(([taskId, v]) => ({
|
|
6491
6705
|
taskId,
|