@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
package/dist/index.js
CHANGED
|
@@ -586,6 +586,16 @@ var EventBus = class {
|
|
|
586
586
|
this.off(event, wrapper);
|
|
587
587
|
};
|
|
588
588
|
}
|
|
589
|
+
/**
|
|
590
|
+
* Subscribe to all events, regardless of name. Short-hand for
|
|
591
|
+
* `onPattern('*')`. Use for logging, debugging, or forwarding every
|
|
592
|
+
* event to another bus (as FleetBus does).
|
|
593
|
+
*
|
|
594
|
+
* Returns an unsubscribe function.
|
|
595
|
+
*/
|
|
596
|
+
onAny(fn) {
|
|
597
|
+
return this.onPattern("*", fn);
|
|
598
|
+
}
|
|
589
599
|
/**
|
|
590
600
|
* Subscribe to all events whose name matches a glob-style prefix.
|
|
591
601
|
* `'tool.*'` matches `tool.started`, `tool.executed`, `tool.progress`, etc.
|
|
@@ -720,6 +730,19 @@ var ScopedEventBus = class extends EventBus {
|
|
|
720
730
|
this.registrations.set(key, unsub);
|
|
721
731
|
return unsub;
|
|
722
732
|
}
|
|
733
|
+
/**
|
|
734
|
+
* Subscribe to all events. Alias for `onPattern('*')` — the listener is
|
|
735
|
+
* tracked so that `teardown()` will remove it automatically.
|
|
736
|
+
*/
|
|
737
|
+
onAny(fn) {
|
|
738
|
+
const key = this.nextKey++;
|
|
739
|
+
const unsub = EventBus.prototype.onPattern.call(this, "*", fn);
|
|
740
|
+
this.registrations.set(key, unsub);
|
|
741
|
+
return () => {
|
|
742
|
+
this.registrations.delete(key);
|
|
743
|
+
unsub();
|
|
744
|
+
};
|
|
745
|
+
}
|
|
723
746
|
/**
|
|
724
747
|
* Identical to `EventBus.onPattern` but the listener is tracked so that
|
|
725
748
|
* `teardown()` will remove it automatically.
|
|
@@ -8239,29 +8262,35 @@ var SubagentBudget = class _SubagentBudget {
|
|
|
8239
8262
|
* same kind are no-ops — without this dedup, every `recordIteration`
|
|
8240
8263
|
* after the limit is reached spawns a fresh decision Promise (until
|
|
8241
8264
|
* the first one lands and patches limits), flooding the FleetBus
|
|
8242
|
-
* with redundant threshold events. Cleared in `
|
|
8265
|
+
* with redundant threshold events. Cleared in `negotiateExtension`'s
|
|
8243
8266
|
* `finally`.
|
|
8244
8267
|
*/
|
|
8245
8268
|
pendingExtensions = /* @__PURE__ */ new Set();
|
|
8246
8269
|
/**
|
|
8247
|
-
* Hard cap on how long `
|
|
8270
|
+
* Hard cap on how long `negotiateExtension` waits for the coordinator to
|
|
8248
8271
|
* respond before defaulting to 'stop'. Without this fallback an absent
|
|
8249
8272
|
* or hung listener (Director not built / event filter detached mid-run)
|
|
8250
|
-
* leaves the budget over-limit and never enforces anything
|
|
8251
|
-
* `checkLimit` returns synchronously via `void this.checkLimitAsync`.
|
|
8252
|
-
* Raised from 30s to 60s to give subagents more headroom before
|
|
8253
|
-
* the threshold negotiation times out and triggers a hard stop.
|
|
8273
|
+
* leaves the budget over-limit and never enforces anything.
|
|
8254
8274
|
*/
|
|
8255
8275
|
static DECISION_TIMEOUT_MS = 6e4;
|
|
8256
8276
|
/**
|
|
8257
8277
|
* Injected by the runner when wiring the budget to its EventBus.
|
|
8258
|
-
* Used
|
|
8278
|
+
* Used to emit `budget.threshold_reached` events in `'auto'` mode.
|
|
8259
8279
|
*/
|
|
8260
8280
|
_events;
|
|
8281
|
+
/**
|
|
8282
|
+
* Negotiation mode — controls whether a threshold hit tries to emit
|
|
8283
|
+
* `budget.threshold_reached` and wait for a coordinator decision, or
|
|
8284
|
+
* falls straight through to a synchronous hard stop.
|
|
8285
|
+
*
|
|
8286
|
+
* `'auto'` (default) — emit on the EventBus and wait; times out to 'stop'.
|
|
8287
|
+
* `'sync'` — throw `BudgetExceededError` immediately regardless of listeners.
|
|
8288
|
+
*/
|
|
8289
|
+
_mode;
|
|
8261
8290
|
/**
|
|
8262
8291
|
* Optional callback for soft-limit handling. When set, the budget will
|
|
8263
|
-
*
|
|
8264
|
-
*
|
|
8292
|
+
* invoke it rather than throw immediately. The handler decides whether to
|
|
8293
|
+
* throw synchronously, continue, or ask the coordinator for an extension.
|
|
8265
8294
|
*/
|
|
8266
8295
|
get onThreshold() {
|
|
8267
8296
|
return this._onThreshold;
|
|
@@ -8269,7 +8298,12 @@ var SubagentBudget = class _SubagentBudget {
|
|
|
8269
8298
|
set onThreshold(fn) {
|
|
8270
8299
|
this._onThreshold = fn;
|
|
8271
8300
|
}
|
|
8272
|
-
|
|
8301
|
+
/** Returns the current negotiation mode. */
|
|
8302
|
+
get mode() {
|
|
8303
|
+
return this._mode;
|
|
8304
|
+
}
|
|
8305
|
+
constructor(limits = {}, mode = "auto") {
|
|
8306
|
+
this._mode = mode;
|
|
8273
8307
|
this.limits = { ...limits };
|
|
8274
8308
|
}
|
|
8275
8309
|
start() {
|
|
@@ -8285,16 +8319,23 @@ var SubagentBudget = class _SubagentBudget {
|
|
|
8285
8319
|
return false;
|
|
8286
8320
|
}
|
|
8287
8321
|
/**
|
|
8288
|
-
* Synchronous budget check
|
|
8289
|
-
*
|
|
8290
|
-
*
|
|
8291
|
-
*
|
|
8292
|
-
*
|
|
8322
|
+
* Synchronous budget check. Always throws synchronously so callers (especially
|
|
8323
|
+
* test event handlers using `expect().toThrow()`) get an unhandled rejection
|
|
8324
|
+
* when the budget is exceeded without a handler.
|
|
8325
|
+
*
|
|
8326
|
+
* Decision table:
|
|
8327
|
+
* - no `onThreshold` handler → throw `BudgetExceededError` (hard stop, always)
|
|
8328
|
+
* - `mode === 'sync'` → throw `BudgetExceededError` (hard stop, always)
|
|
8329
|
+
* - `mode === 'auto'` + no listener → throw `BudgetExceededError` (hard stop; no one to ask)
|
|
8330
|
+
* - `mode === 'auto'` + listener → throw `BudgetThresholdSignal` with async decision promise
|
|
8293
8331
|
*/
|
|
8294
8332
|
checkLimit(kind, used, limit) {
|
|
8295
8333
|
if (!this._onThreshold) {
|
|
8296
8334
|
throw new BudgetExceededError(kind, limit, used);
|
|
8297
8335
|
}
|
|
8336
|
+
if (this._mode === "sync") {
|
|
8337
|
+
throw new BudgetExceededError(kind, limit, used);
|
|
8338
|
+
}
|
|
8298
8339
|
const bus = this._events;
|
|
8299
8340
|
if (!bus || !bus.hasListenerFor("budget.threshold_reached")) {
|
|
8300
8341
|
throw new BudgetExceededError(kind, limit, used);
|
|
@@ -8311,8 +8352,8 @@ var SubagentBudget = class _SubagentBudget {
|
|
|
8311
8352
|
* `pendingExtensions` slot in `finally`.
|
|
8312
8353
|
*
|
|
8313
8354
|
* The 'continue' return from a sync handler is treated as
|
|
8314
|
-
* `{ extend: {} }` — keep going without patching
|
|
8315
|
-
*
|
|
8355
|
+
* `{ extend: {} }` — keep going without patching; next overrun fires
|
|
8356
|
+
* a fresh signal.
|
|
8316
8357
|
*/
|
|
8317
8358
|
async negotiateExtension(kind, used, limit) {
|
|
8318
8359
|
try {
|
|
@@ -8320,11 +8361,6 @@ var SubagentBudget = class _SubagentBudget {
|
|
|
8320
8361
|
kind,
|
|
8321
8362
|
used,
|
|
8322
8363
|
limit,
|
|
8323
|
-
// Inject a requestDecision helper the handler can call to emit the
|
|
8324
|
-
// budget.threshold_reached event and wait for the coordinator's verdict.
|
|
8325
|
-
// A hard fallback timer guarantees the promise eventually resolves
|
|
8326
|
-
// even if no listener responds — without it, an absent/detached
|
|
8327
|
-
// Director would leave the budget permanently in "asking" state.
|
|
8328
8364
|
requestDecision: () => {
|
|
8329
8365
|
const bus = this._events;
|
|
8330
8366
|
if (!bus || !bus.hasListenerFor("budget.threshold_reached")) {
|
|
@@ -8408,12 +8444,12 @@ var SubagentBudget = class _SubagentBudget {
|
|
|
8408
8444
|
}
|
|
8409
8445
|
}
|
|
8410
8446
|
/**
|
|
8411
|
-
* Wall-clock budget check. Unlike other limits, timeout
|
|
8412
|
-
*
|
|
8413
|
-
*
|
|
8414
|
-
*
|
|
8415
|
-
*
|
|
8416
|
-
*
|
|
8447
|
+
* Wall-clock budget check. Unlike other limits, timeout check passes through
|
|
8448
|
+
* `checkLimit` and is subject to the same negotiation-mode decision table.
|
|
8449
|
+
* In practice, `'sync'` mode (the usual test configuration) means a timeout
|
|
8450
|
+
* immediately throws `BudgetExceededError`. In production with a coordinator,
|
|
8451
|
+
* a timeout emits `budget.threshold_reached` so the Director can decide whether
|
|
8452
|
+
* to extend or abort.
|
|
8417
8453
|
*/
|
|
8418
8454
|
checkTimeout() {
|
|
8419
8455
|
if (this.startTime === null || this.limits.timeoutMs === void 0) return;
|
|
@@ -10794,8 +10830,7 @@ function scoreAgents(task, catalog = AGENT_CATALOG) {
|
|
|
10794
10830
|
const hay = normalize2(task);
|
|
10795
10831
|
const out = [];
|
|
10796
10832
|
for (const def of Object.values(catalog)) {
|
|
10797
|
-
|
|
10798
|
-
if (!role) continue;
|
|
10833
|
+
if (!def?.config?.role) continue;
|
|
10799
10834
|
let score = 0;
|
|
10800
10835
|
const matched = [];
|
|
10801
10836
|
for (const kw of def.capability.keywords) {
|
|
@@ -10807,7 +10842,7 @@ function scoreAgents(task, catalog = AGENT_CATALOG) {
|
|
|
10807
10842
|
}
|
|
10808
10843
|
}
|
|
10809
10844
|
if (score > 0) {
|
|
10810
|
-
out.push({ role, name: def.config.name, score, matched });
|
|
10845
|
+
out.push({ role: def.config.role, name: def.config.name, score, matched });
|
|
10811
10846
|
}
|
|
10812
10847
|
}
|
|
10813
10848
|
out.sort((a, b) => b.score - a.score);
|
|
@@ -11178,6 +11213,7 @@ var DefaultMultiAgentCoordinator = class extends EventEmitter {
|
|
|
11178
11213
|
coordinatorId;
|
|
11179
11214
|
config;
|
|
11180
11215
|
runner;
|
|
11216
|
+
fleetBus;
|
|
11181
11217
|
subagents = /* @__PURE__ */ new Map();
|
|
11182
11218
|
pendingTasks = [];
|
|
11183
11219
|
completedResults = [];
|
|
@@ -11206,6 +11242,14 @@ var DefaultMultiAgentCoordinator = class extends EventEmitter {
|
|
|
11206
11242
|
setRunner(runner) {
|
|
11207
11243
|
this.runner = runner;
|
|
11208
11244
|
}
|
|
11245
|
+
/**
|
|
11246
|
+
* Wire a FleetBus for director-mode event emission. Call after the
|
|
11247
|
+
* FleetManager is constructed so the coordinator can emit lifecycle
|
|
11248
|
+
* events the TUI and monitoring tools subscribe to.
|
|
11249
|
+
*/
|
|
11250
|
+
setFleetBus(fleet) {
|
|
11251
|
+
this.fleetBus = fleet;
|
|
11252
|
+
}
|
|
11209
11253
|
/**
|
|
11210
11254
|
* Change the in-flight dispatch ceiling at runtime. Lowering does NOT
|
|
11211
11255
|
* preempt running tasks — already-dispatched subagents finish their
|
|
@@ -11241,6 +11285,18 @@ var DefaultMultiAgentCoordinator = class extends EventEmitter {
|
|
|
11241
11285
|
abortController: new AbortController()
|
|
11242
11286
|
});
|
|
11243
11287
|
this.emit("subagent.started", { subagent: { ...subagent, id } });
|
|
11288
|
+
this.fleetBus?.emit({
|
|
11289
|
+
subagentId: id,
|
|
11290
|
+
ts: Date.now(),
|
|
11291
|
+
type: "subagent.assigned",
|
|
11292
|
+
payload: {
|
|
11293
|
+
subagentId: id,
|
|
11294
|
+
name: subagent.name,
|
|
11295
|
+
provider: subagent.provider,
|
|
11296
|
+
model: subagent.model
|
|
11297
|
+
}
|
|
11298
|
+
});
|
|
11299
|
+
this.emitCoordinatorStats();
|
|
11244
11300
|
return { subagentId: id, agentId: id };
|
|
11245
11301
|
}
|
|
11246
11302
|
async assign(task) {
|
|
@@ -11273,6 +11329,13 @@ var DefaultMultiAgentCoordinator = class extends EventEmitter {
|
|
|
11273
11329
|
subagent.currentTask = void 0;
|
|
11274
11330
|
subagent.context.parentBridge = null;
|
|
11275
11331
|
this.emit("subagent.stopped", { subagentId, reason: "stopped by coordinator" });
|
|
11332
|
+
this.fleetBus?.emit({
|
|
11333
|
+
subagentId,
|
|
11334
|
+
ts: Date.now(),
|
|
11335
|
+
type: "subagent.stopped",
|
|
11336
|
+
payload: { subagentId, reason: "stopped by coordinator" }
|
|
11337
|
+
});
|
|
11338
|
+
this.emitCoordinatorStats();
|
|
11276
11339
|
}
|
|
11277
11340
|
async stopAll() {
|
|
11278
11341
|
this.drainPendingAsAborted("Coordinator stopAll() drained the pending queue");
|
|
@@ -11304,6 +11367,22 @@ var DefaultMultiAgentCoordinator = class extends EventEmitter {
|
|
|
11304
11367
|
completed: this.completedResults.length
|
|
11305
11368
|
};
|
|
11306
11369
|
}
|
|
11370
|
+
/** Emit a reactive coordinator.stats event on FleetBus so the TUI can subscribe. */
|
|
11371
|
+
emitCoordinatorStats() {
|
|
11372
|
+
const stats = this.getStats();
|
|
11373
|
+
const subagentStatuses = Array.from(this.subagents.entries()).map(([id, s]) => ({
|
|
11374
|
+
subagentId: id,
|
|
11375
|
+
taskId: s.currentTask ?? "",
|
|
11376
|
+
status: s.status,
|
|
11377
|
+
assigned: s.context.parentBridge !== null
|
|
11378
|
+
}));
|
|
11379
|
+
this.fleetBus?.emit({
|
|
11380
|
+
subagentId: this.coordinatorId,
|
|
11381
|
+
ts: Date.now(),
|
|
11382
|
+
type: "coordinator.stats",
|
|
11383
|
+
payload: { ...stats, subagentStatuses }
|
|
11384
|
+
});
|
|
11385
|
+
}
|
|
11307
11386
|
getStatus() {
|
|
11308
11387
|
return {
|
|
11309
11388
|
coordinatorId: this.coordinatorId,
|
|
@@ -11478,7 +11557,15 @@ var DefaultMultiAgentCoordinator = class extends EventEmitter {
|
|
|
11478
11557
|
subagent.currentTask = task.id;
|
|
11479
11558
|
task.subagentId = subagentId;
|
|
11480
11559
|
subagent.context.tasks.push(task);
|
|
11560
|
+
this.fleetBus?.emit({
|
|
11561
|
+
subagentId,
|
|
11562
|
+
taskId: task.id,
|
|
11563
|
+
ts: Date.now(),
|
|
11564
|
+
type: "subagent.running",
|
|
11565
|
+
payload: { subagentId, taskId: task.id }
|
|
11566
|
+
});
|
|
11481
11567
|
this.emit("task.assigned", { task, subagentId });
|
|
11568
|
+
this.emitCoordinatorStats();
|
|
11482
11569
|
const rawMaxIterations = subagent.config.maxIterations;
|
|
11483
11570
|
const rawMaxToolCalls = subagent.config.maxToolCalls;
|
|
11484
11571
|
const rawMaxTokens = subagent.config.maxTokens;
|
|
@@ -11622,13 +11709,34 @@ var DefaultMultiAgentCoordinator = class extends EventEmitter {
|
|
|
11622
11709
|
if (subagent.abortController.signal.aborted) {
|
|
11623
11710
|
subagent.abortController = new AbortController();
|
|
11624
11711
|
}
|
|
11712
|
+
this.fleetBus?.emit({
|
|
11713
|
+
subagentId: result.subagentId,
|
|
11714
|
+
ts: Date.now(),
|
|
11715
|
+
type: "subagent.idle",
|
|
11716
|
+
payload: { subagentId: result.subagentId }
|
|
11717
|
+
});
|
|
11625
11718
|
}
|
|
11626
11719
|
this.terminating.delete(result.subagentId);
|
|
11627
11720
|
this.emit("task.completed", {
|
|
11628
11721
|
task: subagent?.context.tasks.find((t2) => t2.id === result.taskId) ?? { id: result.taskId },
|
|
11629
11722
|
result
|
|
11630
11723
|
});
|
|
11724
|
+
this.fleetBus?.emit({
|
|
11725
|
+
subagentId: result.subagentId,
|
|
11726
|
+
taskId: result.taskId,
|
|
11727
|
+
ts: Date.now(),
|
|
11728
|
+
type: "subagent.completed",
|
|
11729
|
+
payload: {
|
|
11730
|
+
subagentId: result.subagentId,
|
|
11731
|
+
taskId: result.taskId,
|
|
11732
|
+
status: result.status,
|
|
11733
|
+
iterations: result.iterations,
|
|
11734
|
+
toolCalls: result.toolCalls,
|
|
11735
|
+
durationMs: result.durationMs
|
|
11736
|
+
}
|
|
11737
|
+
});
|
|
11631
11738
|
this.tryDispatchNext();
|
|
11739
|
+
this.emitCoordinatorStats();
|
|
11632
11740
|
if (this.isDone()) {
|
|
11633
11741
|
this.emit("done", {
|
|
11634
11742
|
results: this.completedResults,
|
|
@@ -12335,18 +12443,17 @@ var FleetBus = class {
|
|
|
12335
12443
|
byType = /* @__PURE__ */ new Map();
|
|
12336
12444
|
any = /* @__PURE__ */ new Set();
|
|
12337
12445
|
/**
|
|
12338
|
-
* Hook a subagent's EventBus into the fleet.
|
|
12339
|
-
*
|
|
12340
|
-
*
|
|
12341
|
-
*
|
|
12342
|
-
*
|
|
12343
|
-
* the wire format clear.
|
|
12446
|
+
* Hook a subagent's EventBus into the fleet. Uses `onAny()` (an alias for
|
|
12447
|
+
* `onPattern('*')`) to forward all events with subagent attribution, so
|
|
12448
|
+
* new kernel event types are automatically forwarded without any manual
|
|
12449
|
+
* registration. `subagent.*` events are excluded because they originate
|
|
12450
|
+
* from MultiAgentHost on the parent bus, not the subagent's own bus.
|
|
12344
12451
|
*
|
|
12345
12452
|
* Returns a disposer that detaches every subscription; call on
|
|
12346
12453
|
* subagent teardown so the listeners don't outlive the run.
|
|
12347
12454
|
*/
|
|
12348
12455
|
attach(subagentId, bus, taskId) {
|
|
12349
|
-
const off = bus.
|
|
12456
|
+
const off = bus.onAny((type, payload) => {
|
|
12350
12457
|
if (type.startsWith("subagent.")) return;
|
|
12351
12458
|
this.emit({ subagentId, taskId, ts: Date.now(), type, payload });
|
|
12352
12459
|
});
|
|
@@ -12468,7 +12575,7 @@ var FleetUsageAggregator = class {
|
|
|
12468
12575
|
this.total.output += usage.output ?? 0;
|
|
12469
12576
|
this.total.cacheRead += usage.cacheRead ?? 0;
|
|
12470
12577
|
this.total.cacheWrite += usage.cacheWrite ?? 0;
|
|
12471
|
-
const price = this.priceLookup?.(e.subagentId);
|
|
12578
|
+
const price = this.priceLookup?.(e.subagentId, snap.provider, snap.model);
|
|
12472
12579
|
if (price) {
|
|
12473
12580
|
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);
|
|
12474
12581
|
snap.cost += delta;
|
|
@@ -12491,8 +12598,9 @@ function makeSpawnTool(director, roster) {
|
|
|
12491
12598
|
const inputSchema = {
|
|
12492
12599
|
type: "object",
|
|
12493
12600
|
properties: {
|
|
12494
|
-
role: { type: "string", description: "Roster role id
|
|
12495
|
-
|
|
12601
|
+
role: { type: "string", description: "Roster role id. When set, the spawn uses the matching config from the roster and ignores other fields." },
|
|
12602
|
+
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." },
|
|
12603
|
+
name: { type: "string", description: "Display name for the subagent. Used as a fallback when description-based dispatch does not resolve a role." },
|
|
12496
12604
|
provider: { type: "string", description: 'Provider id (e.g. "anthropic", "openai"). Defaults to the leader provider when omitted.' },
|
|
12497
12605
|
model: { type: "string", description: "Model id within the provider. Defaults to the leader model when omitted." },
|
|
12498
12606
|
systemPromptOverride: { type: "string", description: "Extra prompt text appended after the role-base prompt." },
|
|
@@ -12505,18 +12613,38 @@ function makeSpawnTool(director, roster) {
|
|
|
12505
12613
|
return {
|
|
12506
12614
|
name: "spawn_subagent",
|
|
12507
12615
|
description: "Create a new subagent under this director. Returns the subagent id.",
|
|
12508
|
-
usageHint: "
|
|
12616
|
+
usageHint: "Pass `role` (matches the roster), `description` (smart dispatch to best agent), or `name` + `provider`/`model`. Returns `{ subagentId }`.",
|
|
12509
12617
|
permission: "auto",
|
|
12510
12618
|
mutating: false,
|
|
12511
12619
|
inputSchema,
|
|
12512
12620
|
async execute(input) {
|
|
12513
12621
|
const i = input ?? {};
|
|
12514
12622
|
const role = typeof i.role === "string" ? i.role : void 0;
|
|
12515
|
-
const
|
|
12516
|
-
|
|
12517
|
-
|
|
12623
|
+
const description = typeof i.description === "string" ? i.description : void 0;
|
|
12624
|
+
let cfg;
|
|
12625
|
+
if (role && roster) {
|
|
12626
|
+
const base = roster[role];
|
|
12627
|
+
if (!base) return { error: `unknown role "${role}". roster has: ${Object.keys(roster).join(", ")}` };
|
|
12628
|
+
cfg = instantiateRosterConfig(role, base);
|
|
12629
|
+
} else if (description && !role) {
|
|
12630
|
+
const dispatchResult = await dispatchAgent(description, {
|
|
12631
|
+
classifier: director.dispatchClassifier,
|
|
12632
|
+
catalog: roster
|
|
12633
|
+
});
|
|
12634
|
+
const dispatchRole = dispatchResult.role;
|
|
12635
|
+
if (roster && roster[dispatchRole]) {
|
|
12636
|
+
cfg = instantiateRosterConfig(dispatchRole, roster[dispatchRole]);
|
|
12637
|
+
} else {
|
|
12638
|
+
const def = dispatchResult.definition;
|
|
12639
|
+
cfg = {
|
|
12640
|
+
name: def.config.name ?? dispatchRole,
|
|
12641
|
+
role: dispatchRole,
|
|
12642
|
+
provider: def.config.provider,
|
|
12643
|
+
model: def.config.model
|
|
12644
|
+
};
|
|
12645
|
+
}
|
|
12518
12646
|
}
|
|
12519
|
-
|
|
12647
|
+
cfg ??= { name: i.name ?? "subagent" };
|
|
12520
12648
|
if (typeof i.name === "string") cfg.name = i.name;
|
|
12521
12649
|
if (typeof i.provider === "string") cfg.provider = i.provider;
|
|
12522
12650
|
if (typeof i.model === "string") cfg.model = i.model;
|
|
@@ -12526,7 +12654,7 @@ function makeSpawnTool(director, roster) {
|
|
|
12526
12654
|
if (typeof i.maxCostUsd === "number") cfg.maxCostUsd = i.maxCostUsd;
|
|
12527
12655
|
try {
|
|
12528
12656
|
const subagentId = await director.spawn(cfg);
|
|
12529
|
-
return { subagentId, provider: cfg.provider, model: cfg.model, name: cfg.name };
|
|
12657
|
+
return { subagentId, provider: cfg.provider, model: cfg.model, name: cfg.name, role: cfg.role };
|
|
12530
12658
|
} catch (err) {
|
|
12531
12659
|
if (err instanceof FleetSpawnBudgetError) {
|
|
12532
12660
|
return { error: err.message, kind: err.kind, limit: err.limit, observed: err.observed };
|
|
@@ -12650,12 +12778,21 @@ function makeTerminateTool(director) {
|
|
|
12650
12778
|
function makeFleetStatusTool(director) {
|
|
12651
12779
|
return {
|
|
12652
12780
|
name: "fleet_status",
|
|
12653
|
-
description: "Snapshot of the fleet \u2014 every subagent's current status,
|
|
12781
|
+
description: "Snapshot of the fleet \u2014 every subagent's current status, coordinator counts (total/running/idle/stopped), pending task descriptions, and usage rollup.",
|
|
12654
12782
|
permission: "auto",
|
|
12655
12783
|
mutating: false,
|
|
12656
12784
|
inputSchema: { type: "object", properties: {}, required: [] },
|
|
12657
12785
|
async execute() {
|
|
12658
|
-
|
|
12786
|
+
const base = director.status();
|
|
12787
|
+
const fm = director.fleetManager;
|
|
12788
|
+
const stats = fm?.getFleetStats();
|
|
12789
|
+
const fleetStatus = fm?.getFleetStatus();
|
|
12790
|
+
return {
|
|
12791
|
+
subagents: base.subagents,
|
|
12792
|
+
coordinatorStats: stats ? { total: stats.total, running: stats.running, idle: stats.idle, stopped: stats.stopped } : void 0,
|
|
12793
|
+
pending: fleetStatus?.pending ?? [],
|
|
12794
|
+
usage: fm?.snapshot()
|
|
12795
|
+
};
|
|
12659
12796
|
}
|
|
12660
12797
|
};
|
|
12661
12798
|
}
|
|
@@ -12857,6 +12994,8 @@ var Director = class {
|
|
|
12857
12994
|
* default cap.
|
|
12858
12995
|
*/
|
|
12859
12996
|
taskCompletedListener = null;
|
|
12997
|
+
/** Optional LLM classifier for smart dispatch. Passed from options. */
|
|
12998
|
+
dispatchClassifier;
|
|
12860
12999
|
constructor(opts) {
|
|
12861
13000
|
this.id = opts.config.coordinatorId || randomUUID();
|
|
12862
13001
|
this.manifestPath = opts.manifestPath;
|
|
@@ -12869,6 +13008,7 @@ var Director = class {
|
|
|
12869
13008
|
this.spawnDepth = opts.spawnDepth ?? 0;
|
|
12870
13009
|
this.sessionWriter = opts.sessionWriter ?? null;
|
|
12871
13010
|
this.manifestDebounceMs = opts.manifestDebounceMs ?? 2e3;
|
|
13011
|
+
this.dispatchClassifier = opts.dispatchClassifier;
|
|
12872
13012
|
this.maxFleetCostUsd = opts.directorBudget?.maxCostUsd ?? Number.POSITIVE_INFINITY;
|
|
12873
13013
|
this.maxBudgetExtensions = opts.maxBudgetExtensions ?? 5;
|
|
12874
13014
|
this.sessionsRoot = opts.sessionsRoot;
|
|
@@ -12898,7 +13038,10 @@ var Director = class {
|
|
|
12898
13038
|
this.fleet = new FleetBus();
|
|
12899
13039
|
this.usage = new FleetUsageAggregator(
|
|
12900
13040
|
this.fleet,
|
|
12901
|
-
(id) =>
|
|
13041
|
+
(id, provider, model) => {
|
|
13042
|
+
if (provider && model) return this.priceLookups.get(`${provider}/${model}`);
|
|
13043
|
+
return void 0;
|
|
13044
|
+
},
|
|
12902
13045
|
(id) => this.subagentMeta.get(id)
|
|
12903
13046
|
);
|
|
12904
13047
|
}
|
|
@@ -12906,6 +13049,8 @@ var Director = class {
|
|
|
12906
13049
|
{ ...opts.config, coordinatorId: this.id },
|
|
12907
13050
|
{ runner: opts.runner }
|
|
12908
13051
|
);
|
|
13052
|
+
this.coordinator.setFleetBus(this.fleet);
|
|
13053
|
+
this.fleetManager?.setCoordinator(this.coordinator);
|
|
12909
13054
|
this.taskCompletedListener = (payload) => {
|
|
12910
13055
|
const r = payload.result;
|
|
12911
13056
|
this.completed.set(r.taskId, r);
|
|
@@ -12940,7 +13085,11 @@ var Director = class {
|
|
|
12940
13085
|
title
|
|
12941
13086
|
}
|
|
12942
13087
|
);
|
|
12943
|
-
this.
|
|
13088
|
+
if (this.fleetManager) {
|
|
13089
|
+
this.fleetManager.flushManifest();
|
|
13090
|
+
} else {
|
|
13091
|
+
this.scheduleManifest();
|
|
13092
|
+
}
|
|
12944
13093
|
};
|
|
12945
13094
|
this.coordinator.on("task.completed", this.taskCompletedListener);
|
|
12946
13095
|
const extendCounts = /* @__PURE__ */ new Map();
|
|
@@ -13032,10 +13181,17 @@ var Director = class {
|
|
|
13032
13181
|
}
|
|
13033
13182
|
}
|
|
13034
13183
|
/** Debounced manifest writer. A burst of spawn/assign/complete events
|
|
13035
|
-
* collapses into one write. Set `manifestDebounceMs` to 0 to
|
|
13184
|
+
* collapses into one write. Set `manifestDebounceMs` to 0 to write
|
|
13185
|
+
* synchronously (no debounce); set to negative to disable entirely. */
|
|
13036
13186
|
scheduleManifest() {
|
|
13037
|
-
if (!this.manifestPath
|
|
13038
|
-
if (this.
|
|
13187
|
+
if (!this.manifestPath) return;
|
|
13188
|
+
if (this.manifestDebounceMs === 0) {
|
|
13189
|
+
void this.writeManifest().catch(
|
|
13190
|
+
(err) => this.logShutdownError("manifest_write_debounced", err)
|
|
13191
|
+
);
|
|
13192
|
+
return;
|
|
13193
|
+
}
|
|
13194
|
+
if (this.manifestDebounceMs < 0) return;
|
|
13039
13195
|
this.manifestTimer = setTimeout(() => {
|
|
13040
13196
|
this.manifestTimer = null;
|
|
13041
13197
|
void this.writeManifest().catch(
|
|
@@ -13084,7 +13240,9 @@ var Director = class {
|
|
|
13084
13240
|
provider: config.provider,
|
|
13085
13241
|
model: config.model
|
|
13086
13242
|
});
|
|
13087
|
-
if (priceLookup
|
|
13243
|
+
if (priceLookup && config.provider && config.model) {
|
|
13244
|
+
this.priceLookups.set(`${config.provider}/${config.model}`, priceLookup);
|
|
13245
|
+
}
|
|
13088
13246
|
}
|
|
13089
13247
|
const subagentBridge = new InMemoryAgentBridge(
|
|
13090
13248
|
{ agentId: result.subagentId, coordinatorId: this.id },
|
|
@@ -20198,6 +20356,8 @@ var FleetManager = class {
|
|
|
20198
20356
|
pendingTasks = /* @__PURE__ */ new Map();
|
|
20199
20357
|
subagentMeta = /* @__PURE__ */ new Map();
|
|
20200
20358
|
priceLookups = /* @__PURE__ */ new Map();
|
|
20359
|
+
/** The coordinator (wired via setCoordinator by Director after construction). */
|
|
20360
|
+
coordinator = null;
|
|
20201
20361
|
constructor(opts = {}) {
|
|
20202
20362
|
this.manifestPath = opts.manifestPath;
|
|
20203
20363
|
this.sessionsRoot = opts.sessionsRoot;
|
|
@@ -20222,7 +20382,10 @@ var FleetManager = class {
|
|
|
20222
20382
|
this.fleet = new FleetBus();
|
|
20223
20383
|
this.usage = new FleetUsageAggregator(
|
|
20224
20384
|
this.fleet,
|
|
20225
|
-
(id) =>
|
|
20385
|
+
(id, provider, model) => {
|
|
20386
|
+
if (provider && model) return this.priceLookups.get(`${provider}/${model}`);
|
|
20387
|
+
return void 0;
|
|
20388
|
+
},
|
|
20226
20389
|
(id) => this.subagentMeta.get(id)
|
|
20227
20390
|
);
|
|
20228
20391
|
}
|
|
@@ -20232,11 +20395,26 @@ var FleetManager = class {
|
|
|
20232
20395
|
get fleetBus() {
|
|
20233
20396
|
return this.fleet;
|
|
20234
20397
|
}
|
|
20398
|
+
/**
|
|
20399
|
+
* Wire the coordinator after Director construction. The coordinator
|
|
20400
|
+
* is not available when FleetManager is constructed standalone.
|
|
20401
|
+
* Once set, `getFleetStats()` delegates to `coordinator.getStats()`.
|
|
20402
|
+
*/
|
|
20403
|
+
setCoordinator(coordinator) {
|
|
20404
|
+
this.coordinator = coordinator;
|
|
20405
|
+
}
|
|
20235
20406
|
snapshot() {
|
|
20236
20407
|
return this.usage.snapshot();
|
|
20237
20408
|
}
|
|
20238
20409
|
getSubagentMeta(id) {
|
|
20239
|
-
|
|
20410
|
+
const meta = this.subagentMeta.get(id);
|
|
20411
|
+
const manifest = this.manifestEntries.get(id);
|
|
20412
|
+
if (!meta && !manifest) return void 0;
|
|
20413
|
+
return {
|
|
20414
|
+
provider: meta?.provider ?? manifest?.provider,
|
|
20415
|
+
model: meta?.model ?? manifest?.model,
|
|
20416
|
+
name: manifest?.name
|
|
20417
|
+
};
|
|
20240
20418
|
}
|
|
20241
20419
|
/**
|
|
20242
20420
|
* Returns null if the spawn is allowed, or an object describing
|
|
@@ -20273,7 +20451,9 @@ var FleetManager = class {
|
|
|
20273
20451
|
provider: config.provider,
|
|
20274
20452
|
model: config.model
|
|
20275
20453
|
});
|
|
20276
|
-
if (priceLookup
|
|
20454
|
+
if (priceLookup && config.provider && config.model) {
|
|
20455
|
+
this.priceLookups.set(`${config.provider}/${config.model}`, priceLookup);
|
|
20456
|
+
}
|
|
20277
20457
|
this.manifestEntries.set(subagentId, {
|
|
20278
20458
|
subagentId,
|
|
20279
20459
|
name: config.name,
|
|
@@ -20329,9 +20509,21 @@ var FleetManager = class {
|
|
|
20329
20509
|
/**
|
|
20330
20510
|
* Debounced manifest write. Call after any state mutation
|
|
20331
20511
|
* (spawn, assign, complete) so a burst collapses into one write.
|
|
20512
|
+
* When `manifestDebounceMs` is 0, writes are synchronous (no debounce).
|
|
20332
20513
|
*/
|
|
20333
20514
|
scheduleManifest() {
|
|
20334
|
-
if (!this.manifestPath
|
|
20515
|
+
if (!this.manifestPath) return;
|
|
20516
|
+
if (this.manifestDebounceMs === 0) {
|
|
20517
|
+
void this.writeManifest().catch((err) => {
|
|
20518
|
+
const detail = err instanceof Error ? err.message : String(err);
|
|
20519
|
+
process.emitWarning(
|
|
20520
|
+
`FleetManager manifest write failed: ${detail}`,
|
|
20521
|
+
"FleetManagerWarning"
|
|
20522
|
+
);
|
|
20523
|
+
});
|
|
20524
|
+
return;
|
|
20525
|
+
}
|
|
20526
|
+
if (this.manifestDebounceMs < 0) return;
|
|
20335
20527
|
if (this.manifestTimer) return;
|
|
20336
20528
|
this.manifestTimer = setTimeout(() => {
|
|
20337
20529
|
this.manifestTimer = null;
|
|
@@ -20344,6 +20536,24 @@ var FleetManager = class {
|
|
|
20344
20536
|
});
|
|
20345
20537
|
}, this.manifestDebounceMs);
|
|
20346
20538
|
}
|
|
20539
|
+
/**
|
|
20540
|
+
* Bypass the debounce timer and write the manifest immediately.
|
|
20541
|
+
* Clears any pending debounce timer before writing.
|
|
20542
|
+
*/
|
|
20543
|
+
async flushManifest() {
|
|
20544
|
+
if (!this.manifestPath) return;
|
|
20545
|
+
if (this.manifestTimer) {
|
|
20546
|
+
clearTimeout(this.manifestTimer);
|
|
20547
|
+
this.manifestTimer = null;
|
|
20548
|
+
}
|
|
20549
|
+
await this.writeManifest().catch((err) => {
|
|
20550
|
+
const detail = err instanceof Error ? err.message : String(err);
|
|
20551
|
+
process.emitWarning(
|
|
20552
|
+
`FleetManager manifest write failed: ${detail}`,
|
|
20553
|
+
"FleetManagerWarning"
|
|
20554
|
+
);
|
|
20555
|
+
});
|
|
20556
|
+
}
|
|
20347
20557
|
/** Best-effort session event writer. Swallows failures. */
|
|
20348
20558
|
async appendSessionEvent(event) {
|
|
20349
20559
|
if (!this.sessionWriter) return;
|
|
@@ -20361,6 +20571,31 @@ var FleetManager = class {
|
|
|
20361
20571
|
removePendingTask(taskId) {
|
|
20362
20572
|
this.pendingTasks.delete(taskId);
|
|
20363
20573
|
}
|
|
20574
|
+
getFleetStats() {
|
|
20575
|
+
if (!this.coordinator) {
|
|
20576
|
+
return {
|
|
20577
|
+
total: 0,
|
|
20578
|
+
running: 0,
|
|
20579
|
+
idle: 0,
|
|
20580
|
+
stopped: 0,
|
|
20581
|
+
inFlight: 0,
|
|
20582
|
+
pending: 0,
|
|
20583
|
+
completed: 0,
|
|
20584
|
+
subagentStatuses: []
|
|
20585
|
+
};
|
|
20586
|
+
}
|
|
20587
|
+
const stats = this.coordinator.getStats();
|
|
20588
|
+
const subagentStatuses = [];
|
|
20589
|
+
for (const [subagentId, s] of this.coordinator["subagents"]) {
|
|
20590
|
+
subagentStatuses.push({
|
|
20591
|
+
subagentId,
|
|
20592
|
+
taskId: s.currentTask ?? "",
|
|
20593
|
+
status: s.status,
|
|
20594
|
+
assigned: s.context.parentBridge !== null
|
|
20595
|
+
});
|
|
20596
|
+
}
|
|
20597
|
+
return { ...stats, subagentStatuses };
|
|
20598
|
+
}
|
|
20364
20599
|
getFleetStatus() {
|
|
20365
20600
|
const pending = Array.from(this.pendingTasks.entries()).map(([taskId, v]) => ({
|
|
20366
20601
|
taskId,
|