@wrongstack/core 0.7.8 → 0.8.2
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-Cav3yEJM.d.ts} +9 -10
- package/dist/coordination/index.d.ts +9 -9
- package/dist/coordination/index.js +393 -179
- package/dist/coordination/index.js.map +1 -1
- package/dist/defaults/index.d.ts +13 -13
- package/dist/defaults/index.js +261 -64
- 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 +24 -7
- package/dist/execution/index.js +158 -37
- package/dist/execution/index.js.map +1 -1
- package/dist/extension/index.d.ts +3 -3
- package/dist/{index-BtNRyJft.d.ts → index-oYZeWsuJ.d.ts} +2 -2
- package/dist/index.d.ts +16 -16
- package/dist/index.js +365 -68
- package/dist/index.js.map +1 -1
- package/dist/infrastructure/index.d.ts +2 -2
- package/dist/kernel/index.d.ts +3 -3
- 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-IQKrMfXz.d.ts} +11 -1
- package/dist/{null-fleet-bus-BZUrXVcd.d.ts → null-fleet-bus-sKnVwEd8.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/{permission-policy-D5Gj1o2K.d.ts → permission-policy-BBa1M1xc.d.ts} +10 -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 +5 -5
- package/dist/sdd/index.js +114 -28
- package/dist/sdd/index.js.map +1 -1
- package/dist/{secret-scrubber-DXkhwuka.d.ts → secret-scrubber-C0n1EqrC.d.ts} +1 -1
- package/dist/{secret-scrubber-nI8qjaqW.d.ts → secret-scrubber-CyE1-EMG.d.ts} +1 -1
- package/dist/security/index.d.ts +3 -3
- package/dist/security/index.js +29 -3
- package/dist/security/index.js.map +1 -1
- package/dist/storage/index.d.ts +2 -2
- package/dist/{tool-executor-CSwXjifK.d.ts → tool-executor-QwfWnQZ8.d.ts} +1 -1
- package/dist/types/index.d.ts +9 -9
- 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.
|
|
@@ -6159,10 +6182,36 @@ var DefaultPermissionPolicy = class {
|
|
|
6159
6182
|
return void 0;
|
|
6160
6183
|
}
|
|
6161
6184
|
};
|
|
6162
|
-
var AutoApprovePermissionPolicy = class {
|
|
6185
|
+
var AutoApprovePermissionPolicy = class _AutoApprovePermissionPolicy {
|
|
6186
|
+
/**
|
|
6187
|
+
* Tools that are too dangerous to auto-approve even in a delegated
|
|
6188
|
+
* subagent context. Subagents run non-interactively under a director
|
|
6189
|
+
* and cannot answer prompts, but inherited authorization does not
|
|
6190
|
+
* imply blanket permission for destructive or privilege-escalating
|
|
6191
|
+
* operations. These tools remain at their declared `permission`
|
|
6192
|
+
* level so the leader must explicitly allow them per-spawn.
|
|
6193
|
+
*/
|
|
6194
|
+
static DENY = /* @__PURE__ */ new Set([
|
|
6195
|
+
"bash",
|
|
6196
|
+
// arbitrary shell — use exec for constrained shell
|
|
6197
|
+
"write",
|
|
6198
|
+
// arbitrary file write
|
|
6199
|
+
"scaffold",
|
|
6200
|
+
// arbitrary file generation outside project root
|
|
6201
|
+
"patch",
|
|
6202
|
+
// arbitrary diff application
|
|
6203
|
+
"install",
|
|
6204
|
+
// installs from arbitrary package sources
|
|
6205
|
+
"exec"
|
|
6206
|
+
// restricted shell but with arbitrary command args
|
|
6207
|
+
]);
|
|
6163
6208
|
async evaluate(tool) {
|
|
6164
|
-
if (tool.permission === "deny") {
|
|
6165
|
-
return {
|
|
6209
|
+
if (tool.permission === "deny" || _AutoApprovePermissionPolicy.DENY.has(tool.name)) {
|
|
6210
|
+
return {
|
|
6211
|
+
permission: "deny",
|
|
6212
|
+
source: "subagent_guard",
|
|
6213
|
+
reason: _AutoApprovePermissionPolicy.DENY.has(tool.name) ? `tool ${tool.name} is not auto-approved for subagents \u2014 ask the leader to allow it explicitly` : "tool default deny"
|
|
6214
|
+
};
|
|
6166
6215
|
}
|
|
6167
6216
|
return { permission: "auto", source: "yolo" };
|
|
6168
6217
|
}
|
|
@@ -7239,6 +7288,7 @@ Summarize the following message range:`;
|
|
|
7239
7288
|
};
|
|
7240
7289
|
|
|
7241
7290
|
// src/execution/auto-compaction-middleware.ts
|
|
7291
|
+
var LEVEL_RANK2 = { warn: 0, soft: 1, hard: 2 };
|
|
7242
7292
|
var AutoCompactionMiddleware = class _AutoCompactionMiddleware {
|
|
7243
7293
|
name = "AutoCompaction";
|
|
7244
7294
|
compactor;
|
|
@@ -7259,6 +7309,16 @@ var AutoCompactionMiddleware = class _AutoCompactionMiddleware {
|
|
|
7259
7309
|
* short conversations, causing premature compaction triggers.
|
|
7260
7310
|
*/
|
|
7261
7311
|
static OVERHEAD_FACTOR = 1.3;
|
|
7312
|
+
/**
|
|
7313
|
+
* Once a compaction attempt reduces nothing (preserveK protects everything,
|
|
7314
|
+
* no oversized tool_results remain to elide), retrying on every iteration
|
|
7315
|
+
* just spams `compaction.fired` events without making progress. We remember
|
|
7316
|
+
* the no-op and skip until either the pressure level escalates or context
|
|
7317
|
+
* has grown by at least this many tokens since the failed attempt.
|
|
7318
|
+
*/
|
|
7319
|
+
static NOOP_RETRY_DELTA_TOKENS = 2e3;
|
|
7320
|
+
/** Tracks the most recent no-op attempt so we can avoid re-firing per turn. */
|
|
7321
|
+
lastNoopAttempt = null;
|
|
7262
7322
|
/**
|
|
7263
7323
|
* @param compactor Compactor to use for compaction.
|
|
7264
7324
|
* @param maxContext Provider's max context window in tokens.
|
|
@@ -7300,19 +7360,44 @@ var AutoCompactionMiddleware = class _AutoCompactionMiddleware {
|
|
|
7300
7360
|
hard: this.hardThreshold
|
|
7301
7361
|
};
|
|
7302
7362
|
const aggressiveOn = policy?.aggressiveOn ?? this.aggressiveOn;
|
|
7303
|
-
|
|
7304
|
-
|
|
7305
|
-
|
|
7306
|
-
|
|
7307
|
-
}
|
|
7308
|
-
|
|
7363
|
+
const level = load >= thresholds.hard ? "hard" : load >= thresholds.soft ? "soft" : load >= thresholds.warn ? "warn" : null;
|
|
7364
|
+
if (!level) {
|
|
7365
|
+
this.lastNoopAttempt = null;
|
|
7366
|
+
return next(ctx);
|
|
7367
|
+
}
|
|
7368
|
+
if (this.shouldSkipNoopRetry(level, tokens)) {
|
|
7369
|
+
return next(ctx);
|
|
7309
7370
|
}
|
|
7371
|
+
const aggressive = level === "hard" ? true : level === "soft" ? aggressiveOn !== "hard" : aggressiveOn === "warn";
|
|
7372
|
+
await this.compact(ctx, aggressive, { level, tokens, load });
|
|
7310
7373
|
return next(ctx);
|
|
7311
7374
|
};
|
|
7312
7375
|
}
|
|
7376
|
+
/**
|
|
7377
|
+
* Returns true when the previous compaction at the same or higher pressure
|
|
7378
|
+
* level reduced nothing and context has not grown materially since. Prevents
|
|
7379
|
+
* a stuck preserveK window from spamming compaction events every iteration.
|
|
7380
|
+
*/
|
|
7381
|
+
shouldSkipNoopRetry(level, tokens) {
|
|
7382
|
+
const stuck = this.lastNoopAttempt;
|
|
7383
|
+
if (!stuck) return false;
|
|
7384
|
+
if (LEVEL_RANK2[level] > LEVEL_RANK2[stuck.level]) return false;
|
|
7385
|
+
const delta = tokens - stuck.tokens;
|
|
7386
|
+
return delta < _AutoCompactionMiddleware.NOOP_RETRY_DELTA_TOKENS;
|
|
7387
|
+
}
|
|
7388
|
+
recordAttempt(level, tokens, report) {
|
|
7389
|
+
const reduced = report.before > report.after;
|
|
7390
|
+
const repaired = !!report.repaired;
|
|
7391
|
+
if (reduced || repaired) {
|
|
7392
|
+
this.lastNoopAttempt = null;
|
|
7393
|
+
} else {
|
|
7394
|
+
this.lastNoopAttempt = { level, tokens };
|
|
7395
|
+
}
|
|
7396
|
+
}
|
|
7313
7397
|
async compact(ctx, aggressive, pressure) {
|
|
7314
7398
|
try {
|
|
7315
7399
|
const report = await this.compactor.compact(ctx, { aggressive });
|
|
7400
|
+
this.recordAttempt(pressure.level, pressure.tokens, report);
|
|
7316
7401
|
this.events?.emit("compaction.fired", {
|
|
7317
7402
|
level: pressure.level,
|
|
7318
7403
|
tokens: pressure.tokens,
|
|
@@ -8239,29 +8324,35 @@ var SubagentBudget = class _SubagentBudget {
|
|
|
8239
8324
|
* same kind are no-ops — without this dedup, every `recordIteration`
|
|
8240
8325
|
* after the limit is reached spawns a fresh decision Promise (until
|
|
8241
8326
|
* the first one lands and patches limits), flooding the FleetBus
|
|
8242
|
-
* with redundant threshold events. Cleared in `
|
|
8327
|
+
* with redundant threshold events. Cleared in `negotiateExtension`'s
|
|
8243
8328
|
* `finally`.
|
|
8244
8329
|
*/
|
|
8245
8330
|
pendingExtensions = /* @__PURE__ */ new Set();
|
|
8246
8331
|
/**
|
|
8247
|
-
* Hard cap on how long `
|
|
8332
|
+
* Hard cap on how long `negotiateExtension` waits for the coordinator to
|
|
8248
8333
|
* respond before defaulting to 'stop'. Without this fallback an absent
|
|
8249
8334
|
* 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.
|
|
8335
|
+
* leaves the budget over-limit and never enforces anything.
|
|
8254
8336
|
*/
|
|
8255
8337
|
static DECISION_TIMEOUT_MS = 6e4;
|
|
8256
8338
|
/**
|
|
8257
8339
|
* Injected by the runner when wiring the budget to its EventBus.
|
|
8258
|
-
* Used
|
|
8340
|
+
* Used to emit `budget.threshold_reached` events in `'auto'` mode.
|
|
8259
8341
|
*/
|
|
8260
8342
|
_events;
|
|
8343
|
+
/**
|
|
8344
|
+
* Negotiation mode — controls whether a threshold hit tries to emit
|
|
8345
|
+
* `budget.threshold_reached` and wait for a coordinator decision, or
|
|
8346
|
+
* falls straight through to a synchronous hard stop.
|
|
8347
|
+
*
|
|
8348
|
+
* `'auto'` (default) — emit on the EventBus and wait; times out to 'stop'.
|
|
8349
|
+
* `'sync'` — throw `BudgetExceededError` immediately regardless of listeners.
|
|
8350
|
+
*/
|
|
8351
|
+
_mode;
|
|
8261
8352
|
/**
|
|
8262
8353
|
* Optional callback for soft-limit handling. When set, the budget will
|
|
8263
|
-
*
|
|
8264
|
-
*
|
|
8354
|
+
* invoke it rather than throw immediately. The handler decides whether to
|
|
8355
|
+
* throw synchronously, continue, or ask the coordinator for an extension.
|
|
8265
8356
|
*/
|
|
8266
8357
|
get onThreshold() {
|
|
8267
8358
|
return this._onThreshold;
|
|
@@ -8269,7 +8360,12 @@ var SubagentBudget = class _SubagentBudget {
|
|
|
8269
8360
|
set onThreshold(fn) {
|
|
8270
8361
|
this._onThreshold = fn;
|
|
8271
8362
|
}
|
|
8272
|
-
|
|
8363
|
+
/** Returns the current negotiation mode. */
|
|
8364
|
+
get mode() {
|
|
8365
|
+
return this._mode;
|
|
8366
|
+
}
|
|
8367
|
+
constructor(limits = {}, mode = "auto") {
|
|
8368
|
+
this._mode = mode;
|
|
8273
8369
|
this.limits = { ...limits };
|
|
8274
8370
|
}
|
|
8275
8371
|
start() {
|
|
@@ -8285,16 +8381,23 @@ var SubagentBudget = class _SubagentBudget {
|
|
|
8285
8381
|
return false;
|
|
8286
8382
|
}
|
|
8287
8383
|
/**
|
|
8288
|
-
* Synchronous budget check
|
|
8289
|
-
*
|
|
8290
|
-
*
|
|
8291
|
-
*
|
|
8292
|
-
*
|
|
8384
|
+
* Synchronous budget check. Always throws synchronously so callers (especially
|
|
8385
|
+
* test event handlers using `expect().toThrow()`) get an unhandled rejection
|
|
8386
|
+
* when the budget is exceeded without a handler.
|
|
8387
|
+
*
|
|
8388
|
+
* Decision table:
|
|
8389
|
+
* - no `onThreshold` handler → throw `BudgetExceededError` (hard stop, always)
|
|
8390
|
+
* - `mode === 'sync'` → throw `BudgetExceededError` (hard stop, always)
|
|
8391
|
+
* - `mode === 'auto'` + no listener → throw `BudgetExceededError` (hard stop; no one to ask)
|
|
8392
|
+
* - `mode === 'auto'` + listener → throw `BudgetThresholdSignal` with async decision promise
|
|
8293
8393
|
*/
|
|
8294
8394
|
checkLimit(kind, used, limit) {
|
|
8295
8395
|
if (!this._onThreshold) {
|
|
8296
8396
|
throw new BudgetExceededError(kind, limit, used);
|
|
8297
8397
|
}
|
|
8398
|
+
if (this._mode === "sync") {
|
|
8399
|
+
throw new BudgetExceededError(kind, limit, used);
|
|
8400
|
+
}
|
|
8298
8401
|
const bus = this._events;
|
|
8299
8402
|
if (!bus || !bus.hasListenerFor("budget.threshold_reached")) {
|
|
8300
8403
|
throw new BudgetExceededError(kind, limit, used);
|
|
@@ -8311,8 +8414,8 @@ var SubagentBudget = class _SubagentBudget {
|
|
|
8311
8414
|
* `pendingExtensions` slot in `finally`.
|
|
8312
8415
|
*
|
|
8313
8416
|
* The 'continue' return from a sync handler is treated as
|
|
8314
|
-
* `{ extend: {} }` — keep going without patching
|
|
8315
|
-
*
|
|
8417
|
+
* `{ extend: {} }` — keep going without patching; next overrun fires
|
|
8418
|
+
* a fresh signal.
|
|
8316
8419
|
*/
|
|
8317
8420
|
async negotiateExtension(kind, used, limit) {
|
|
8318
8421
|
try {
|
|
@@ -8320,11 +8423,6 @@ var SubagentBudget = class _SubagentBudget {
|
|
|
8320
8423
|
kind,
|
|
8321
8424
|
used,
|
|
8322
8425
|
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
8426
|
requestDecision: () => {
|
|
8329
8427
|
const bus = this._events;
|
|
8330
8428
|
if (!bus || !bus.hasListenerFor("budget.threshold_reached")) {
|
|
@@ -8408,12 +8506,12 @@ var SubagentBudget = class _SubagentBudget {
|
|
|
8408
8506
|
}
|
|
8409
8507
|
}
|
|
8410
8508
|
/**
|
|
8411
|
-
* Wall-clock budget check. Unlike other limits, timeout
|
|
8412
|
-
*
|
|
8413
|
-
*
|
|
8414
|
-
*
|
|
8415
|
-
*
|
|
8416
|
-
*
|
|
8509
|
+
* Wall-clock budget check. Unlike other limits, timeout check passes through
|
|
8510
|
+
* `checkLimit` and is subject to the same negotiation-mode decision table.
|
|
8511
|
+
* In practice, `'sync'` mode (the usual test configuration) means a timeout
|
|
8512
|
+
* immediately throws `BudgetExceededError`. In production with a coordinator,
|
|
8513
|
+
* a timeout emits `budget.threshold_reached` so the Director can decide whether
|
|
8514
|
+
* to extend or abort.
|
|
8417
8515
|
*/
|
|
8418
8516
|
checkTimeout() {
|
|
8419
8517
|
if (this.startTime === null || this.limits.timeoutMs === void 0) return;
|
|
@@ -10794,8 +10892,7 @@ function scoreAgents(task, catalog = AGENT_CATALOG) {
|
|
|
10794
10892
|
const hay = normalize2(task);
|
|
10795
10893
|
const out = [];
|
|
10796
10894
|
for (const def of Object.values(catalog)) {
|
|
10797
|
-
|
|
10798
|
-
if (!role) continue;
|
|
10895
|
+
if (!def?.config?.role) continue;
|
|
10799
10896
|
let score = 0;
|
|
10800
10897
|
const matched = [];
|
|
10801
10898
|
for (const kw of def.capability.keywords) {
|
|
@@ -10807,7 +10904,7 @@ function scoreAgents(task, catalog = AGENT_CATALOG) {
|
|
|
10807
10904
|
}
|
|
10808
10905
|
}
|
|
10809
10906
|
if (score > 0) {
|
|
10810
|
-
out.push({ role, name: def.config.name, score, matched });
|
|
10907
|
+
out.push({ role: def.config.role, name: def.config.name, score, matched });
|
|
10811
10908
|
}
|
|
10812
10909
|
}
|
|
10813
10910
|
out.sort((a, b) => b.score - a.score);
|
|
@@ -11178,6 +11275,7 @@ var DefaultMultiAgentCoordinator = class extends EventEmitter {
|
|
|
11178
11275
|
coordinatorId;
|
|
11179
11276
|
config;
|
|
11180
11277
|
runner;
|
|
11278
|
+
fleetBus;
|
|
11181
11279
|
subagents = /* @__PURE__ */ new Map();
|
|
11182
11280
|
pendingTasks = [];
|
|
11183
11281
|
completedResults = [];
|
|
@@ -11206,6 +11304,14 @@ var DefaultMultiAgentCoordinator = class extends EventEmitter {
|
|
|
11206
11304
|
setRunner(runner) {
|
|
11207
11305
|
this.runner = runner;
|
|
11208
11306
|
}
|
|
11307
|
+
/**
|
|
11308
|
+
* Wire a FleetBus for director-mode event emission. Call after the
|
|
11309
|
+
* FleetManager is constructed so the coordinator can emit lifecycle
|
|
11310
|
+
* events the TUI and monitoring tools subscribe to.
|
|
11311
|
+
*/
|
|
11312
|
+
setFleetBus(fleet) {
|
|
11313
|
+
this.fleetBus = fleet;
|
|
11314
|
+
}
|
|
11209
11315
|
/**
|
|
11210
11316
|
* Change the in-flight dispatch ceiling at runtime. Lowering does NOT
|
|
11211
11317
|
* preempt running tasks — already-dispatched subagents finish their
|
|
@@ -11241,6 +11347,18 @@ var DefaultMultiAgentCoordinator = class extends EventEmitter {
|
|
|
11241
11347
|
abortController: new AbortController()
|
|
11242
11348
|
});
|
|
11243
11349
|
this.emit("subagent.started", { subagent: { ...subagent, id } });
|
|
11350
|
+
this.fleetBus?.emit({
|
|
11351
|
+
subagentId: id,
|
|
11352
|
+
ts: Date.now(),
|
|
11353
|
+
type: "subagent.assigned",
|
|
11354
|
+
payload: {
|
|
11355
|
+
subagentId: id,
|
|
11356
|
+
name: subagent.name,
|
|
11357
|
+
provider: subagent.provider,
|
|
11358
|
+
model: subagent.model
|
|
11359
|
+
}
|
|
11360
|
+
});
|
|
11361
|
+
this.emitCoordinatorStats();
|
|
11244
11362
|
return { subagentId: id, agentId: id };
|
|
11245
11363
|
}
|
|
11246
11364
|
async assign(task) {
|
|
@@ -11273,6 +11391,13 @@ var DefaultMultiAgentCoordinator = class extends EventEmitter {
|
|
|
11273
11391
|
subagent.currentTask = void 0;
|
|
11274
11392
|
subagent.context.parentBridge = null;
|
|
11275
11393
|
this.emit("subagent.stopped", { subagentId, reason: "stopped by coordinator" });
|
|
11394
|
+
this.fleetBus?.emit({
|
|
11395
|
+
subagentId,
|
|
11396
|
+
ts: Date.now(),
|
|
11397
|
+
type: "subagent.stopped",
|
|
11398
|
+
payload: { subagentId, reason: "stopped by coordinator" }
|
|
11399
|
+
});
|
|
11400
|
+
this.emitCoordinatorStats();
|
|
11276
11401
|
}
|
|
11277
11402
|
async stopAll() {
|
|
11278
11403
|
this.drainPendingAsAborted("Coordinator stopAll() drained the pending queue");
|
|
@@ -11304,6 +11429,22 @@ var DefaultMultiAgentCoordinator = class extends EventEmitter {
|
|
|
11304
11429
|
completed: this.completedResults.length
|
|
11305
11430
|
};
|
|
11306
11431
|
}
|
|
11432
|
+
/** Emit a reactive coordinator.stats event on FleetBus so the TUI can subscribe. */
|
|
11433
|
+
emitCoordinatorStats() {
|
|
11434
|
+
const stats = this.getStats();
|
|
11435
|
+
const subagentStatuses = Array.from(this.subagents.entries()).map(([id, s]) => ({
|
|
11436
|
+
subagentId: id,
|
|
11437
|
+
taskId: s.currentTask ?? "",
|
|
11438
|
+
status: s.status,
|
|
11439
|
+
assigned: s.context.parentBridge !== null
|
|
11440
|
+
}));
|
|
11441
|
+
this.fleetBus?.emit({
|
|
11442
|
+
subagentId: this.coordinatorId,
|
|
11443
|
+
ts: Date.now(),
|
|
11444
|
+
type: "coordinator.stats",
|
|
11445
|
+
payload: { ...stats, subagentStatuses }
|
|
11446
|
+
});
|
|
11447
|
+
}
|
|
11307
11448
|
getStatus() {
|
|
11308
11449
|
return {
|
|
11309
11450
|
coordinatorId: this.coordinatorId,
|
|
@@ -11478,7 +11619,15 @@ var DefaultMultiAgentCoordinator = class extends EventEmitter {
|
|
|
11478
11619
|
subagent.currentTask = task.id;
|
|
11479
11620
|
task.subagentId = subagentId;
|
|
11480
11621
|
subagent.context.tasks.push(task);
|
|
11622
|
+
this.fleetBus?.emit({
|
|
11623
|
+
subagentId,
|
|
11624
|
+
taskId: task.id,
|
|
11625
|
+
ts: Date.now(),
|
|
11626
|
+
type: "subagent.running",
|
|
11627
|
+
payload: { subagentId, taskId: task.id }
|
|
11628
|
+
});
|
|
11481
11629
|
this.emit("task.assigned", { task, subagentId });
|
|
11630
|
+
this.emitCoordinatorStats();
|
|
11482
11631
|
const rawMaxIterations = subagent.config.maxIterations;
|
|
11483
11632
|
const rawMaxToolCalls = subagent.config.maxToolCalls;
|
|
11484
11633
|
const rawMaxTokens = subagent.config.maxTokens;
|
|
@@ -11622,13 +11771,34 @@ var DefaultMultiAgentCoordinator = class extends EventEmitter {
|
|
|
11622
11771
|
if (subagent.abortController.signal.aborted) {
|
|
11623
11772
|
subagent.abortController = new AbortController();
|
|
11624
11773
|
}
|
|
11774
|
+
this.fleetBus?.emit({
|
|
11775
|
+
subagentId: result.subagentId,
|
|
11776
|
+
ts: Date.now(),
|
|
11777
|
+
type: "subagent.idle",
|
|
11778
|
+
payload: { subagentId: result.subagentId }
|
|
11779
|
+
});
|
|
11625
11780
|
}
|
|
11626
11781
|
this.terminating.delete(result.subagentId);
|
|
11627
11782
|
this.emit("task.completed", {
|
|
11628
11783
|
task: subagent?.context.tasks.find((t2) => t2.id === result.taskId) ?? { id: result.taskId },
|
|
11629
11784
|
result
|
|
11630
11785
|
});
|
|
11786
|
+
this.fleetBus?.emit({
|
|
11787
|
+
subagentId: result.subagentId,
|
|
11788
|
+
taskId: result.taskId,
|
|
11789
|
+
ts: Date.now(),
|
|
11790
|
+
type: "subagent.completed",
|
|
11791
|
+
payload: {
|
|
11792
|
+
subagentId: result.subagentId,
|
|
11793
|
+
taskId: result.taskId,
|
|
11794
|
+
status: result.status,
|
|
11795
|
+
iterations: result.iterations,
|
|
11796
|
+
toolCalls: result.toolCalls,
|
|
11797
|
+
durationMs: result.durationMs
|
|
11798
|
+
}
|
|
11799
|
+
});
|
|
11631
11800
|
this.tryDispatchNext();
|
|
11801
|
+
this.emitCoordinatorStats();
|
|
11632
11802
|
if (this.isDone()) {
|
|
11633
11803
|
this.emit("done", {
|
|
11634
11804
|
results: this.completedResults,
|
|
@@ -12335,18 +12505,17 @@ var FleetBus = class {
|
|
|
12335
12505
|
byType = /* @__PURE__ */ new Map();
|
|
12336
12506
|
any = /* @__PURE__ */ new Set();
|
|
12337
12507
|
/**
|
|
12338
|
-
* Hook a subagent's EventBus into the fleet.
|
|
12339
|
-
*
|
|
12340
|
-
*
|
|
12341
|
-
*
|
|
12342
|
-
*
|
|
12343
|
-
* the wire format clear.
|
|
12508
|
+
* Hook a subagent's EventBus into the fleet. Uses `onAny()` (an alias for
|
|
12509
|
+
* `onPattern('*')`) to forward all events with subagent attribution, so
|
|
12510
|
+
* new kernel event types are automatically forwarded without any manual
|
|
12511
|
+
* registration. `subagent.*` events are excluded because they originate
|
|
12512
|
+
* from MultiAgentHost on the parent bus, not the subagent's own bus.
|
|
12344
12513
|
*
|
|
12345
12514
|
* Returns a disposer that detaches every subscription; call on
|
|
12346
12515
|
* subagent teardown so the listeners don't outlive the run.
|
|
12347
12516
|
*/
|
|
12348
12517
|
attach(subagentId, bus, taskId) {
|
|
12349
|
-
const off = bus.
|
|
12518
|
+
const off = bus.onAny((type, payload) => {
|
|
12350
12519
|
if (type.startsWith("subagent.")) return;
|
|
12351
12520
|
this.emit({ subagentId, taskId, ts: Date.now(), type, payload });
|
|
12352
12521
|
});
|
|
@@ -12468,7 +12637,7 @@ var FleetUsageAggregator = class {
|
|
|
12468
12637
|
this.total.output += usage.output ?? 0;
|
|
12469
12638
|
this.total.cacheRead += usage.cacheRead ?? 0;
|
|
12470
12639
|
this.total.cacheWrite += usage.cacheWrite ?? 0;
|
|
12471
|
-
const price = this.priceLookup?.(e.subagentId);
|
|
12640
|
+
const price = this.priceLookup?.(e.subagentId, snap.provider, snap.model);
|
|
12472
12641
|
if (price) {
|
|
12473
12642
|
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
12643
|
snap.cost += delta;
|
|
@@ -12491,8 +12660,9 @@ function makeSpawnTool(director, roster) {
|
|
|
12491
12660
|
const inputSchema = {
|
|
12492
12661
|
type: "object",
|
|
12493
12662
|
properties: {
|
|
12494
|
-
role: { type: "string", description: "Roster role id
|
|
12495
|
-
|
|
12663
|
+
role: { type: "string", description: "Roster role id. When set, the spawn uses the matching config from the roster and ignores other fields." },
|
|
12664
|
+
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." },
|
|
12665
|
+
name: { type: "string", description: "Display name for the subagent. Used as a fallback when description-based dispatch does not resolve a role." },
|
|
12496
12666
|
provider: { type: "string", description: 'Provider id (e.g. "anthropic", "openai"). Defaults to the leader provider when omitted.' },
|
|
12497
12667
|
model: { type: "string", description: "Model id within the provider. Defaults to the leader model when omitted." },
|
|
12498
12668
|
systemPromptOverride: { type: "string", description: "Extra prompt text appended after the role-base prompt." },
|
|
@@ -12505,18 +12675,38 @@ function makeSpawnTool(director, roster) {
|
|
|
12505
12675
|
return {
|
|
12506
12676
|
name: "spawn_subagent",
|
|
12507
12677
|
description: "Create a new subagent under this director. Returns the subagent id.",
|
|
12508
|
-
usageHint: "
|
|
12678
|
+
usageHint: "Pass `role` (matches the roster), `description` (smart dispatch to best agent), or `name` + `provider`/`model`. Returns `{ subagentId }`.",
|
|
12509
12679
|
permission: "auto",
|
|
12510
12680
|
mutating: false,
|
|
12511
12681
|
inputSchema,
|
|
12512
12682
|
async execute(input) {
|
|
12513
12683
|
const i = input ?? {};
|
|
12514
12684
|
const role = typeof i.role === "string" ? i.role : void 0;
|
|
12515
|
-
const
|
|
12516
|
-
|
|
12517
|
-
|
|
12685
|
+
const description = typeof i.description === "string" ? i.description : void 0;
|
|
12686
|
+
let cfg;
|
|
12687
|
+
if (role && roster) {
|
|
12688
|
+
const base = roster[role];
|
|
12689
|
+
if (!base) return { error: `unknown role "${role}". roster has: ${Object.keys(roster).join(", ")}` };
|
|
12690
|
+
cfg = instantiateRosterConfig(role, base);
|
|
12691
|
+
} else if (description && !role) {
|
|
12692
|
+
const dispatchResult = await dispatchAgent(description, {
|
|
12693
|
+
classifier: director.dispatchClassifier,
|
|
12694
|
+
catalog: roster
|
|
12695
|
+
});
|
|
12696
|
+
const dispatchRole = dispatchResult.role;
|
|
12697
|
+
if (roster && roster[dispatchRole]) {
|
|
12698
|
+
cfg = instantiateRosterConfig(dispatchRole, roster[dispatchRole]);
|
|
12699
|
+
} else {
|
|
12700
|
+
const def = dispatchResult.definition;
|
|
12701
|
+
cfg = {
|
|
12702
|
+
name: def.config.name ?? dispatchRole,
|
|
12703
|
+
role: dispatchRole,
|
|
12704
|
+
provider: def.config.provider,
|
|
12705
|
+
model: def.config.model
|
|
12706
|
+
};
|
|
12707
|
+
}
|
|
12518
12708
|
}
|
|
12519
|
-
|
|
12709
|
+
cfg ??= { name: i.name ?? "subagent" };
|
|
12520
12710
|
if (typeof i.name === "string") cfg.name = i.name;
|
|
12521
12711
|
if (typeof i.provider === "string") cfg.provider = i.provider;
|
|
12522
12712
|
if (typeof i.model === "string") cfg.model = i.model;
|
|
@@ -12526,7 +12716,7 @@ function makeSpawnTool(director, roster) {
|
|
|
12526
12716
|
if (typeof i.maxCostUsd === "number") cfg.maxCostUsd = i.maxCostUsd;
|
|
12527
12717
|
try {
|
|
12528
12718
|
const subagentId = await director.spawn(cfg);
|
|
12529
|
-
return { subagentId, provider: cfg.provider, model: cfg.model, name: cfg.name };
|
|
12719
|
+
return { subagentId, provider: cfg.provider, model: cfg.model, name: cfg.name, role: cfg.role };
|
|
12530
12720
|
} catch (err) {
|
|
12531
12721
|
if (err instanceof FleetSpawnBudgetError) {
|
|
12532
12722
|
return { error: err.message, kind: err.kind, limit: err.limit, observed: err.observed };
|
|
@@ -12650,12 +12840,21 @@ function makeTerminateTool(director) {
|
|
|
12650
12840
|
function makeFleetStatusTool(director) {
|
|
12651
12841
|
return {
|
|
12652
12842
|
name: "fleet_status",
|
|
12653
|
-
description: "Snapshot of the fleet \u2014 every subagent's current status,
|
|
12843
|
+
description: "Snapshot of the fleet \u2014 every subagent's current status, coordinator counts (total/running/idle/stopped), pending task descriptions, and usage rollup.",
|
|
12654
12844
|
permission: "auto",
|
|
12655
12845
|
mutating: false,
|
|
12656
12846
|
inputSchema: { type: "object", properties: {}, required: [] },
|
|
12657
12847
|
async execute() {
|
|
12658
|
-
|
|
12848
|
+
const base = director.status();
|
|
12849
|
+
const fm = director.fleetManager;
|
|
12850
|
+
const stats = fm?.getFleetStats();
|
|
12851
|
+
const fleetStatus = fm?.getFleetStatus();
|
|
12852
|
+
return {
|
|
12853
|
+
subagents: base.subagents,
|
|
12854
|
+
coordinatorStats: stats ? { total: stats.total, running: stats.running, idle: stats.idle, stopped: stats.stopped } : void 0,
|
|
12855
|
+
pending: fleetStatus?.pending ?? [],
|
|
12856
|
+
usage: fm?.snapshot()
|
|
12857
|
+
};
|
|
12659
12858
|
}
|
|
12660
12859
|
};
|
|
12661
12860
|
}
|
|
@@ -12857,6 +13056,8 @@ var Director = class {
|
|
|
12857
13056
|
* default cap.
|
|
12858
13057
|
*/
|
|
12859
13058
|
taskCompletedListener = null;
|
|
13059
|
+
/** Optional LLM classifier for smart dispatch. Passed from options. */
|
|
13060
|
+
dispatchClassifier;
|
|
12860
13061
|
constructor(opts) {
|
|
12861
13062
|
this.id = opts.config.coordinatorId || randomUUID();
|
|
12862
13063
|
this.manifestPath = opts.manifestPath;
|
|
@@ -12869,6 +13070,7 @@ var Director = class {
|
|
|
12869
13070
|
this.spawnDepth = opts.spawnDepth ?? 0;
|
|
12870
13071
|
this.sessionWriter = opts.sessionWriter ?? null;
|
|
12871
13072
|
this.manifestDebounceMs = opts.manifestDebounceMs ?? 2e3;
|
|
13073
|
+
this.dispatchClassifier = opts.dispatchClassifier;
|
|
12872
13074
|
this.maxFleetCostUsd = opts.directorBudget?.maxCostUsd ?? Number.POSITIVE_INFINITY;
|
|
12873
13075
|
this.maxBudgetExtensions = opts.maxBudgetExtensions ?? 5;
|
|
12874
13076
|
this.sessionsRoot = opts.sessionsRoot;
|
|
@@ -12898,7 +13100,10 @@ var Director = class {
|
|
|
12898
13100
|
this.fleet = new FleetBus();
|
|
12899
13101
|
this.usage = new FleetUsageAggregator(
|
|
12900
13102
|
this.fleet,
|
|
12901
|
-
(id) =>
|
|
13103
|
+
(id, provider, model) => {
|
|
13104
|
+
if (provider && model) return this.priceLookups.get(`${provider}/${model}`);
|
|
13105
|
+
return void 0;
|
|
13106
|
+
},
|
|
12902
13107
|
(id) => this.subagentMeta.get(id)
|
|
12903
13108
|
);
|
|
12904
13109
|
}
|
|
@@ -12906,6 +13111,8 @@ var Director = class {
|
|
|
12906
13111
|
{ ...opts.config, coordinatorId: this.id },
|
|
12907
13112
|
{ runner: opts.runner }
|
|
12908
13113
|
);
|
|
13114
|
+
this.coordinator.setFleetBus(this.fleet);
|
|
13115
|
+
this.fleetManager?.setCoordinator(this.coordinator);
|
|
12909
13116
|
this.taskCompletedListener = (payload) => {
|
|
12910
13117
|
const r = payload.result;
|
|
12911
13118
|
this.completed.set(r.taskId, r);
|
|
@@ -12940,7 +13147,11 @@ var Director = class {
|
|
|
12940
13147
|
title
|
|
12941
13148
|
}
|
|
12942
13149
|
);
|
|
12943
|
-
this.
|
|
13150
|
+
if (this.fleetManager) {
|
|
13151
|
+
this.fleetManager.flushManifest();
|
|
13152
|
+
} else {
|
|
13153
|
+
this.scheduleManifest();
|
|
13154
|
+
}
|
|
12944
13155
|
};
|
|
12945
13156
|
this.coordinator.on("task.completed", this.taskCompletedListener);
|
|
12946
13157
|
const extendCounts = /* @__PURE__ */ new Map();
|
|
@@ -13032,10 +13243,17 @@ var Director = class {
|
|
|
13032
13243
|
}
|
|
13033
13244
|
}
|
|
13034
13245
|
/** Debounced manifest writer. A burst of spawn/assign/complete events
|
|
13035
|
-
* collapses into one write. Set `manifestDebounceMs` to 0 to
|
|
13246
|
+
* collapses into one write. Set `manifestDebounceMs` to 0 to write
|
|
13247
|
+
* synchronously (no debounce); set to negative to disable entirely. */
|
|
13036
13248
|
scheduleManifest() {
|
|
13037
|
-
if (!this.manifestPath
|
|
13038
|
-
if (this.
|
|
13249
|
+
if (!this.manifestPath) return;
|
|
13250
|
+
if (this.manifestDebounceMs === 0) {
|
|
13251
|
+
void this.writeManifest().catch(
|
|
13252
|
+
(err) => this.logShutdownError("manifest_write_debounced", err)
|
|
13253
|
+
);
|
|
13254
|
+
return;
|
|
13255
|
+
}
|
|
13256
|
+
if (this.manifestDebounceMs < 0) return;
|
|
13039
13257
|
this.manifestTimer = setTimeout(() => {
|
|
13040
13258
|
this.manifestTimer = null;
|
|
13041
13259
|
void this.writeManifest().catch(
|
|
@@ -13084,7 +13302,9 @@ var Director = class {
|
|
|
13084
13302
|
provider: config.provider,
|
|
13085
13303
|
model: config.model
|
|
13086
13304
|
});
|
|
13087
|
-
if (priceLookup
|
|
13305
|
+
if (priceLookup && config.provider && config.model) {
|
|
13306
|
+
this.priceLookups.set(`${config.provider}/${config.model}`, priceLookup);
|
|
13307
|
+
}
|
|
13088
13308
|
}
|
|
13089
13309
|
const subagentBridge = new InMemoryAgentBridge(
|
|
13090
13310
|
{ agentId: result.subagentId, coordinatorId: this.id },
|
|
@@ -20198,6 +20418,8 @@ var FleetManager = class {
|
|
|
20198
20418
|
pendingTasks = /* @__PURE__ */ new Map();
|
|
20199
20419
|
subagentMeta = /* @__PURE__ */ new Map();
|
|
20200
20420
|
priceLookups = /* @__PURE__ */ new Map();
|
|
20421
|
+
/** The coordinator (wired via setCoordinator by Director after construction). */
|
|
20422
|
+
coordinator = null;
|
|
20201
20423
|
constructor(opts = {}) {
|
|
20202
20424
|
this.manifestPath = opts.manifestPath;
|
|
20203
20425
|
this.sessionsRoot = opts.sessionsRoot;
|
|
@@ -20222,7 +20444,10 @@ var FleetManager = class {
|
|
|
20222
20444
|
this.fleet = new FleetBus();
|
|
20223
20445
|
this.usage = new FleetUsageAggregator(
|
|
20224
20446
|
this.fleet,
|
|
20225
|
-
(id) =>
|
|
20447
|
+
(id, provider, model) => {
|
|
20448
|
+
if (provider && model) return this.priceLookups.get(`${provider}/${model}`);
|
|
20449
|
+
return void 0;
|
|
20450
|
+
},
|
|
20226
20451
|
(id) => this.subagentMeta.get(id)
|
|
20227
20452
|
);
|
|
20228
20453
|
}
|
|
@@ -20232,11 +20457,26 @@ var FleetManager = class {
|
|
|
20232
20457
|
get fleetBus() {
|
|
20233
20458
|
return this.fleet;
|
|
20234
20459
|
}
|
|
20460
|
+
/**
|
|
20461
|
+
* Wire the coordinator after Director construction. The coordinator
|
|
20462
|
+
* is not available when FleetManager is constructed standalone.
|
|
20463
|
+
* Once set, `getFleetStats()` delegates to `coordinator.getStats()`.
|
|
20464
|
+
*/
|
|
20465
|
+
setCoordinator(coordinator) {
|
|
20466
|
+
this.coordinator = coordinator;
|
|
20467
|
+
}
|
|
20235
20468
|
snapshot() {
|
|
20236
20469
|
return this.usage.snapshot();
|
|
20237
20470
|
}
|
|
20238
20471
|
getSubagentMeta(id) {
|
|
20239
|
-
|
|
20472
|
+
const meta = this.subagentMeta.get(id);
|
|
20473
|
+
const manifest = this.manifestEntries.get(id);
|
|
20474
|
+
if (!meta && !manifest) return void 0;
|
|
20475
|
+
return {
|
|
20476
|
+
provider: meta?.provider ?? manifest?.provider,
|
|
20477
|
+
model: meta?.model ?? manifest?.model,
|
|
20478
|
+
name: manifest?.name
|
|
20479
|
+
};
|
|
20240
20480
|
}
|
|
20241
20481
|
/**
|
|
20242
20482
|
* Returns null if the spawn is allowed, or an object describing
|
|
@@ -20273,7 +20513,9 @@ var FleetManager = class {
|
|
|
20273
20513
|
provider: config.provider,
|
|
20274
20514
|
model: config.model
|
|
20275
20515
|
});
|
|
20276
|
-
if (priceLookup
|
|
20516
|
+
if (priceLookup && config.provider && config.model) {
|
|
20517
|
+
this.priceLookups.set(`${config.provider}/${config.model}`, priceLookup);
|
|
20518
|
+
}
|
|
20277
20519
|
this.manifestEntries.set(subagentId, {
|
|
20278
20520
|
subagentId,
|
|
20279
20521
|
name: config.name,
|
|
@@ -20329,9 +20571,21 @@ var FleetManager = class {
|
|
|
20329
20571
|
/**
|
|
20330
20572
|
* Debounced manifest write. Call after any state mutation
|
|
20331
20573
|
* (spawn, assign, complete) so a burst collapses into one write.
|
|
20574
|
+
* When `manifestDebounceMs` is 0, writes are synchronous (no debounce).
|
|
20332
20575
|
*/
|
|
20333
20576
|
scheduleManifest() {
|
|
20334
|
-
if (!this.manifestPath
|
|
20577
|
+
if (!this.manifestPath) return;
|
|
20578
|
+
if (this.manifestDebounceMs === 0) {
|
|
20579
|
+
void this.writeManifest().catch((err) => {
|
|
20580
|
+
const detail = err instanceof Error ? err.message : String(err);
|
|
20581
|
+
process.emitWarning(
|
|
20582
|
+
`FleetManager manifest write failed: ${detail}`,
|
|
20583
|
+
"FleetManagerWarning"
|
|
20584
|
+
);
|
|
20585
|
+
});
|
|
20586
|
+
return;
|
|
20587
|
+
}
|
|
20588
|
+
if (this.manifestDebounceMs < 0) return;
|
|
20335
20589
|
if (this.manifestTimer) return;
|
|
20336
20590
|
this.manifestTimer = setTimeout(() => {
|
|
20337
20591
|
this.manifestTimer = null;
|
|
@@ -20344,6 +20598,24 @@ var FleetManager = class {
|
|
|
20344
20598
|
});
|
|
20345
20599
|
}, this.manifestDebounceMs);
|
|
20346
20600
|
}
|
|
20601
|
+
/**
|
|
20602
|
+
* Bypass the debounce timer and write the manifest immediately.
|
|
20603
|
+
* Clears any pending debounce timer before writing.
|
|
20604
|
+
*/
|
|
20605
|
+
async flushManifest() {
|
|
20606
|
+
if (!this.manifestPath) return;
|
|
20607
|
+
if (this.manifestTimer) {
|
|
20608
|
+
clearTimeout(this.manifestTimer);
|
|
20609
|
+
this.manifestTimer = null;
|
|
20610
|
+
}
|
|
20611
|
+
await this.writeManifest().catch((err) => {
|
|
20612
|
+
const detail = err instanceof Error ? err.message : String(err);
|
|
20613
|
+
process.emitWarning(
|
|
20614
|
+
`FleetManager manifest write failed: ${detail}`,
|
|
20615
|
+
"FleetManagerWarning"
|
|
20616
|
+
);
|
|
20617
|
+
});
|
|
20618
|
+
}
|
|
20347
20619
|
/** Best-effort session event writer. Swallows failures. */
|
|
20348
20620
|
async appendSessionEvent(event) {
|
|
20349
20621
|
if (!this.sessionWriter) return;
|
|
@@ -20361,6 +20633,31 @@ var FleetManager = class {
|
|
|
20361
20633
|
removePendingTask(taskId) {
|
|
20362
20634
|
this.pendingTasks.delete(taskId);
|
|
20363
20635
|
}
|
|
20636
|
+
getFleetStats() {
|
|
20637
|
+
if (!this.coordinator) {
|
|
20638
|
+
return {
|
|
20639
|
+
total: 0,
|
|
20640
|
+
running: 0,
|
|
20641
|
+
idle: 0,
|
|
20642
|
+
stopped: 0,
|
|
20643
|
+
inFlight: 0,
|
|
20644
|
+
pending: 0,
|
|
20645
|
+
completed: 0,
|
|
20646
|
+
subagentStatuses: []
|
|
20647
|
+
};
|
|
20648
|
+
}
|
|
20649
|
+
const stats = this.coordinator.getStats();
|
|
20650
|
+
const subagentStatuses = [];
|
|
20651
|
+
for (const [subagentId, s] of this.coordinator["subagents"]) {
|
|
20652
|
+
subagentStatuses.push({
|
|
20653
|
+
subagentId,
|
|
20654
|
+
taskId: s.currentTask ?? "",
|
|
20655
|
+
status: s.status,
|
|
20656
|
+
assigned: s.context.parentBridge !== null
|
|
20657
|
+
});
|
|
20658
|
+
}
|
|
20659
|
+
return { ...stats, subagentStatuses };
|
|
20660
|
+
}
|
|
20364
20661
|
getFleetStatus() {
|
|
20365
20662
|
const pending = Array.from(this.pendingTasks.entries()).map(([taskId, v]) => ({
|
|
20366
20663
|
taskId,
|