@wrongstack/cli 0.1.7 → 0.1.8
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/index.js +309 -34
- package/dist/index.js.map +1 -1
- package/package.json +8 -8
package/dist/index.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
import { color, DefaultLogger, DefaultModelsRegistry, Container, DefaultConfigStore, TOKENS, DefaultSecretScrubber, DefaultRetryPolicy, DefaultErrorHandler, DefaultTokenCounter, DefaultModeStore, DefaultSessionStore, DefaultMemoryStore, DefaultSkillLoader, DefaultSystemPromptBuilder, DefaultPermissionPolicy, HybridCompactor, ProviderRegistry, ToolRegistry, createContextManagerTool, EventBus, InMemoryMetricsSink, wireMetricsToEvents, DefaultHealthRegistry, startMetricsServer, RecoveryLock, DefaultAttachmentStore, QueueStore, Context, createDefaultPipelines, AutoCompactionMiddleware, Agent, SlashCommandRegistry, loadPlugins, DefaultPathResolver, resolveWstackPaths, DefaultSecretVault, migratePlaintextSecrets, DefaultConfigLoader, InputBuilder, DefaultPluginAPI, atomicWrite, DefaultSessionReader, makeAgentSubagentRunner, DefaultMultiAgentCoordinator, decryptConfigSecrets, encryptConfigSecrets } from '@wrongstack/core';
|
|
2
|
+
import { color, DefaultLogger, DefaultModelsRegistry, Container, DefaultConfigStore, TOKENS, DefaultSecretScrubber, DefaultRetryPolicy, DefaultErrorHandler, DefaultTokenCounter, DefaultModeStore, DefaultSessionStore, DefaultMemoryStore, DefaultSkillLoader, DefaultSystemPromptBuilder, DefaultPermissionPolicy, HybridCompactor, ProviderRegistry, ToolRegistry, createContextManagerTool, EventBus, InMemoryMetricsSink, wireMetricsToEvents, DefaultHealthRegistry, startMetricsServer, RecoveryLock, DefaultAttachmentStore, QueueStore, Context, createDefaultPipelines, AutoCompactionMiddleware, Agent, SlashCommandRegistry, loadPlugins, DefaultPathResolver, resolveWstackPaths, DefaultSecretVault, migratePlaintextSecrets, DefaultConfigLoader, InputBuilder, DefaultPluginAPI, atomicWrite, DefaultSessionReader, makeAgentSubagentRunner, Director, DefaultMultiAgentCoordinator, decryptConfigSecrets, encryptConfigSecrets } from '@wrongstack/core';
|
|
3
3
|
import { WebSocketServer, WebSocket } from 'ws';
|
|
4
4
|
import * as fs6 from 'fs/promises';
|
|
5
5
|
import { writeFileSync } from 'fs';
|
|
@@ -1046,6 +1046,7 @@ function buildBuiltinSlashCommands(opts) {
|
|
|
1046
1046
|
statsCommand(opts),
|
|
1047
1047
|
spawnCommand(opts),
|
|
1048
1048
|
agentsCommand(opts),
|
|
1049
|
+
fleetCommand(opts),
|
|
1049
1050
|
metricsCommand(opts),
|
|
1050
1051
|
healthCommand(opts),
|
|
1051
1052
|
memoryCommand(opts),
|
|
@@ -1673,18 +1674,46 @@ function statusIcon(status) {
|
|
|
1673
1674
|
if (status === "degraded") return color.yellow("\u25CF");
|
|
1674
1675
|
return color.red("\u25CF");
|
|
1675
1676
|
}
|
|
1677
|
+
function parseSpawnFlags(input) {
|
|
1678
|
+
const opts = {};
|
|
1679
|
+
let rest = input;
|
|
1680
|
+
const consume = (re) => {
|
|
1681
|
+
const m = rest.match(re);
|
|
1682
|
+
if (m) {
|
|
1683
|
+
rest = rest.slice(m[0].length).replace(/^\s+/, "");
|
|
1684
|
+
return m;
|
|
1685
|
+
}
|
|
1686
|
+
return null;
|
|
1687
|
+
};
|
|
1688
|
+
while (rest.length > 0) {
|
|
1689
|
+
let m;
|
|
1690
|
+
if (m = consume(/^--provider=(\S+)\s*/)) opts.provider = m[1];
|
|
1691
|
+
else if (m = consume(/^--model=(\S+)\s*/)) opts.model = m[1];
|
|
1692
|
+
else if (m = consume(/^--name=("([^"]+)"|(\S+))\s*/)) opts.name = m[2] ?? m[3];
|
|
1693
|
+
else if (m = consume(/^--tools=(\S+)\s*/)) opts.tools = m[1].split(",").map((t) => t.trim()).filter(Boolean);
|
|
1694
|
+
else if (m = consume(/^-p\s+(\S+)\s*/)) opts.provider = m[1];
|
|
1695
|
+
else if (m = consume(/^-m\s+(\S+)\s*/)) opts.model = m[1];
|
|
1696
|
+
else if (m = consume(/^-n\s+("([^"]+)"|(\S+))\s*/)) opts.name = m[2] ?? m[3];
|
|
1697
|
+
else break;
|
|
1698
|
+
}
|
|
1699
|
+
return { description: rest.trim(), opts };
|
|
1700
|
+
}
|
|
1676
1701
|
function spawnCommand(opts) {
|
|
1677
1702
|
return {
|
|
1678
1703
|
name: "spawn",
|
|
1679
|
-
description: "Spawn an isolated subagent to handle a task. Usage: /spawn <task description>",
|
|
1704
|
+
description: "Spawn an isolated subagent to handle a task. Usage: /spawn [--provider=<id>] [--model=<id>] [--name=<label>] [--tools=a,b,c] <task description>",
|
|
1680
1705
|
async run(args) {
|
|
1681
|
-
const description = args.trim();
|
|
1682
|
-
if (!description)
|
|
1706
|
+
const { description, opts: parsed } = parseSpawnFlags(args.trim());
|
|
1707
|
+
if (!description) {
|
|
1708
|
+
return {
|
|
1709
|
+
message: "Usage: /spawn [--provider=<id>] [--model=<id>] [--name=<label>] [--tools=a,b,c] <task description>"
|
|
1710
|
+
};
|
|
1711
|
+
}
|
|
1683
1712
|
if (!opts.onSpawn) {
|
|
1684
1713
|
return { message: "Multi-agent is not enabled in this session." };
|
|
1685
1714
|
}
|
|
1686
1715
|
try {
|
|
1687
|
-
const summary = await opts.onSpawn(description);
|
|
1716
|
+
const summary = Object.keys(parsed).length > 0 ? await opts.onSpawn(description, parsed) : await opts.onSpawn(description);
|
|
1688
1717
|
return { message: summary };
|
|
1689
1718
|
} catch (err) {
|
|
1690
1719
|
return {
|
|
@@ -1706,6 +1735,63 @@ function agentsCommand(opts) {
|
|
|
1706
1735
|
}
|
|
1707
1736
|
};
|
|
1708
1737
|
}
|
|
1738
|
+
function fleetCommand(opts) {
|
|
1739
|
+
return {
|
|
1740
|
+
name: "fleet",
|
|
1741
|
+
description: "Inspect or control the subagent fleet: /fleet [status|usage|kill <id>|manifest|help]",
|
|
1742
|
+
help: [
|
|
1743
|
+
"Usage:",
|
|
1744
|
+
" /fleet Show fleet status (alias for /fleet status).",
|
|
1745
|
+
" /fleet status Pending + completed subagent task table.",
|
|
1746
|
+
" /fleet usage Per-subagent runtime cost \u2014 iterations, tool calls, duration.",
|
|
1747
|
+
" /fleet kill <id> Terminate a running subagent by id (or prefix).",
|
|
1748
|
+
" /fleet manifest Print the director manifest (only with --director).",
|
|
1749
|
+
" /fleet help Show this help.",
|
|
1750
|
+
"",
|
|
1751
|
+
"Subagent ids are returned by /spawn and listed in /fleet status."
|
|
1752
|
+
].join("\n"),
|
|
1753
|
+
async run(args) {
|
|
1754
|
+
if (!opts.onFleet) {
|
|
1755
|
+
return { message: "Multi-agent is not enabled in this session." };
|
|
1756
|
+
}
|
|
1757
|
+
const trimmed = args.trim();
|
|
1758
|
+
const [verb, ...rest] = trimmed.length === 0 ? ["status"] : trimmed.split(/\s+/);
|
|
1759
|
+
const target = rest.join(" ").trim() || void 0;
|
|
1760
|
+
switch (verb) {
|
|
1761
|
+
case "status":
|
|
1762
|
+
case "usage":
|
|
1763
|
+
case "manifest": {
|
|
1764
|
+
const out = await opts.onFleet(verb, void 0);
|
|
1765
|
+
return { message: out };
|
|
1766
|
+
}
|
|
1767
|
+
case "kill": {
|
|
1768
|
+
if (!target) {
|
|
1769
|
+
return { message: "Usage: /fleet kill <subagent-id>" };
|
|
1770
|
+
}
|
|
1771
|
+
const out = await opts.onFleet("kill", target);
|
|
1772
|
+
return { message: out };
|
|
1773
|
+
}
|
|
1774
|
+
case "help":
|
|
1775
|
+
case "?":
|
|
1776
|
+
return {
|
|
1777
|
+
message: [
|
|
1778
|
+
"/fleet \u2014 inspect or control the subagent fleet",
|
|
1779
|
+
"",
|
|
1780
|
+
" /fleet \u2192 status (default)",
|
|
1781
|
+
" /fleet status pending + completed tasks per subagent",
|
|
1782
|
+
" /fleet usage iterations, tool calls, duration roll-up",
|
|
1783
|
+
" /fleet kill <id> terminate a subagent",
|
|
1784
|
+
" /fleet manifest director manifest (requires --director)"
|
|
1785
|
+
].join("\n")
|
|
1786
|
+
};
|
|
1787
|
+
default:
|
|
1788
|
+
return {
|
|
1789
|
+
message: `Unknown subcommand "${verb}". Try: status | usage | kill <id> | manifest | help`
|
|
1790
|
+
};
|
|
1791
|
+
}
|
|
1792
|
+
}
|
|
1793
|
+
};
|
|
1794
|
+
}
|
|
1709
1795
|
|
|
1710
1796
|
// src/pre-launch.ts
|
|
1711
1797
|
var MANIFESTS = [
|
|
@@ -2474,25 +2560,31 @@ async function ensureProjectMeta(paths, projectRoot) {
|
|
|
2474
2560
|
}
|
|
2475
2561
|
}
|
|
2476
2562
|
var MultiAgentHost = class {
|
|
2477
|
-
constructor(deps) {
|
|
2563
|
+
constructor(deps, opts = {}) {
|
|
2478
2564
|
this.deps = deps;
|
|
2565
|
+
this.opts = opts;
|
|
2479
2566
|
}
|
|
2480
2567
|
deps;
|
|
2481
2568
|
coordinator;
|
|
2569
|
+
/** Lazily built when `opts.directorMode` is set. Owns its own internal
|
|
2570
|
+
* coordinator; the host's `coordinator` field still points at it so
|
|
2571
|
+
* the rest of the methods don't need to branch. */
|
|
2572
|
+
director;
|
|
2482
2573
|
pending = /* @__PURE__ */ new Map();
|
|
2483
2574
|
results = [];
|
|
2575
|
+
opts;
|
|
2484
2576
|
async ensureCoordinator() {
|
|
2485
2577
|
if (this.coordinator) return this.coordinator;
|
|
2486
2578
|
const config = this.deps.configStore.get();
|
|
2487
2579
|
const factory = async (subCfg) => {
|
|
2488
2580
|
const events = new EventBus();
|
|
2489
|
-
const provider = await this.buildSubagentProvider(config);
|
|
2581
|
+
const provider = await this.buildSubagentProvider(config, subCfg.provider);
|
|
2490
2582
|
const baseSystem = await this.deps.systemPromptBuilder.build({
|
|
2491
2583
|
cwd: this.deps.cwd,
|
|
2492
2584
|
projectRoot: this.deps.projectRoot,
|
|
2493
2585
|
tools: this.filterTools(subCfg.tools),
|
|
2494
2586
|
model: subCfg.model ?? config.model,
|
|
2495
|
-
provider: config.provider
|
|
2587
|
+
provider: subCfg.provider ?? config.provider
|
|
2496
2588
|
});
|
|
2497
2589
|
const parentSession = this.deps.session;
|
|
2498
2590
|
const subSession = {
|
|
@@ -2526,15 +2618,26 @@ var MultiAgentHost = class {
|
|
|
2526
2618
|
return { agent, events };
|
|
2527
2619
|
};
|
|
2528
2620
|
const runner = makeAgentSubagentRunner({ factory });
|
|
2529
|
-
|
|
2530
|
-
|
|
2531
|
-
|
|
2532
|
-
|
|
2533
|
-
|
|
2534
|
-
|
|
2535
|
-
|
|
2536
|
-
|
|
2537
|
-
|
|
2621
|
+
const coordinatorConfig = {
|
|
2622
|
+
coordinatorId: randomUUID(),
|
|
2623
|
+
doneCondition: { type: "all_tasks_done" },
|
|
2624
|
+
maxConcurrent: 2,
|
|
2625
|
+
defaultBudget: { maxToolCalls: 20, maxIterations: 20, timeoutMs: 12e4 }
|
|
2626
|
+
};
|
|
2627
|
+
if (this.opts.directorMode) {
|
|
2628
|
+
this.director = new Director({
|
|
2629
|
+
config: coordinatorConfig,
|
|
2630
|
+
runner,
|
|
2631
|
+
manifestPath: this.opts.manifestPath
|
|
2632
|
+
});
|
|
2633
|
+
this.director.on("task.completed", ({ task, result }) => {
|
|
2634
|
+
this.results.push(result);
|
|
2635
|
+
this.pending.delete(task.id);
|
|
2636
|
+
});
|
|
2637
|
+
this.coordinator = this.director.coordinator;
|
|
2638
|
+
return this.coordinator;
|
|
2639
|
+
}
|
|
2640
|
+
this.coordinator = new DefaultMultiAgentCoordinator(coordinatorConfig, { runner });
|
|
2538
2641
|
this.coordinator.on(
|
|
2539
2642
|
"task.completed",
|
|
2540
2643
|
({ task, result }) => {
|
|
@@ -2544,15 +2647,24 @@ var MultiAgentHost = class {
|
|
|
2544
2647
|
);
|
|
2545
2648
|
return this.coordinator;
|
|
2546
2649
|
}
|
|
2547
|
-
|
|
2548
|
-
|
|
2549
|
-
|
|
2650
|
+
/**
|
|
2651
|
+
* Build a Provider for a subagent. When `overrideId` is supplied (from
|
|
2652
|
+
* `SubagentConfig.provider`), looks that provider up in
|
|
2653
|
+
* `config.providers` and constructs it with its own apiKey/baseUrl.
|
|
2654
|
+
* Falls back to the leader's provider when `overrideId` is absent or
|
|
2655
|
+
* not configured (so a typo doesn't crash the whole run — we just
|
|
2656
|
+
* use the leader and the calling code can decide to error later).
|
|
2657
|
+
*/
|
|
2658
|
+
async buildSubagentProvider(config, overrideId) {
|
|
2659
|
+
const providerId = overrideId && config.providers?.[overrideId] ? overrideId : config.provider;
|
|
2660
|
+
const newCfg = config.providers?.[providerId] ?? {
|
|
2661
|
+
type: providerId,
|
|
2550
2662
|
apiKey: config.apiKey,
|
|
2551
2663
|
baseUrl: config.baseUrl
|
|
2552
2664
|
};
|
|
2553
|
-
return makeProviderFromConfig(
|
|
2665
|
+
return makeProviderFromConfig(providerId, {
|
|
2554
2666
|
...newCfg,
|
|
2555
|
-
type:
|
|
2667
|
+
type: providerId
|
|
2556
2668
|
});
|
|
2557
2669
|
}
|
|
2558
2670
|
/** Returns a tool slice for the subagent — full set unless restricted. */
|
|
@@ -2569,15 +2681,40 @@ var MultiAgentHost = class {
|
|
|
2569
2681
|
for (const t of this.filterTools(allow)) sub.register(t);
|
|
2570
2682
|
return sub;
|
|
2571
2683
|
}
|
|
2572
|
-
/**
|
|
2573
|
-
|
|
2574
|
-
|
|
2575
|
-
|
|
2576
|
-
|
|
2684
|
+
/**
|
|
2685
|
+
* Spawn a fresh subagent and assign a single task. Returns task id.
|
|
2686
|
+
*
|
|
2687
|
+
* Optional `opts` lets the caller (a `/spawn` slash command or the
|
|
2688
|
+
* future director surface) override the subagent's provider, model,
|
|
2689
|
+
* and tool slice on a per-spawn basis. Without options, the legacy
|
|
2690
|
+
* behavior holds: the subagent uses the leader's provider/model and
|
|
2691
|
+
* the full tool registry.
|
|
2692
|
+
*/
|
|
2693
|
+
async spawn(description, opts) {
|
|
2694
|
+
await this.ensureCoordinator();
|
|
2695
|
+
const subagentConfig = {
|
|
2696
|
+
name: opts?.name ?? "adhoc",
|
|
2577
2697
|
role: "general",
|
|
2578
2698
|
maxToolCalls: 20,
|
|
2579
|
-
maxIterations: 20
|
|
2580
|
-
|
|
2699
|
+
maxIterations: 20,
|
|
2700
|
+
provider: opts?.provider,
|
|
2701
|
+
model: opts?.model,
|
|
2702
|
+
tools: opts?.tools
|
|
2703
|
+
};
|
|
2704
|
+
if (this.director) {
|
|
2705
|
+
const subagentId = await this.director.spawn(subagentConfig);
|
|
2706
|
+
const taskId2 = randomUUID();
|
|
2707
|
+
this.pending.set(taskId2, { description, subagentId });
|
|
2708
|
+
await this.director.assign({
|
|
2709
|
+
id: taskId2,
|
|
2710
|
+
description,
|
|
2711
|
+
subagentId,
|
|
2712
|
+
maxToolCalls: 20
|
|
2713
|
+
});
|
|
2714
|
+
return { subagentId, taskId: taskId2 };
|
|
2715
|
+
}
|
|
2716
|
+
const coord = this.coordinator;
|
|
2717
|
+
const spawned = await coord.spawn(subagentConfig);
|
|
2581
2718
|
const taskId = randomUUID();
|
|
2582
2719
|
this.pending.set(taskId, { description, subagentId: spawned.subagentId });
|
|
2583
2720
|
await coord.assign({
|
|
@@ -2597,6 +2734,79 @@ var MultiAgentHost = class {
|
|
|
2597
2734
|
const summary = !this.coordinator ? "No subagents have been spawned." : `${pending.length} pending, ${this.results.length} completed.`;
|
|
2598
2735
|
return { pending, completed: this.results, summary };
|
|
2599
2736
|
}
|
|
2737
|
+
/**
|
|
2738
|
+
* Roll up per-subagent runtime cost from completed TaskResults. We don't
|
|
2739
|
+
* yet have FleetUsageAggregator wired into the simple MultiAgentHost
|
|
2740
|
+
* path (that lives on `Director`), so this aggregates iterations / tool
|
|
2741
|
+
* calls / duration which we *do* have — enough to spot a thrashing
|
|
2742
|
+
* worker without paying for a heavier orchestrator on every /spawn.
|
|
2743
|
+
*
|
|
2744
|
+
* Returns rows sorted by total duration descending (slowest first) so
|
|
2745
|
+
* the table renders the most interesting subagent at the top.
|
|
2746
|
+
*/
|
|
2747
|
+
usage() {
|
|
2748
|
+
const bySubagent = /* @__PURE__ */ new Map();
|
|
2749
|
+
for (const r of this.results) {
|
|
2750
|
+
const cur = bySubagent.get(r.subagentId) ?? { tasks: 0, iterations: 0, toolCalls: 0, durationMs: 0, lastStatus: "unknown" };
|
|
2751
|
+
cur.tasks += 1;
|
|
2752
|
+
cur.iterations += r.iterations;
|
|
2753
|
+
cur.toolCalls += r.toolCalls;
|
|
2754
|
+
cur.durationMs += r.durationMs;
|
|
2755
|
+
cur.lastStatus = r.status;
|
|
2756
|
+
bySubagent.set(r.subagentId, cur);
|
|
2757
|
+
}
|
|
2758
|
+
const rows = Array.from(bySubagent.entries()).map(([subagentId, v]) => ({
|
|
2759
|
+
subagentId,
|
|
2760
|
+
tasks: v.tasks,
|
|
2761
|
+
iterations: v.iterations,
|
|
2762
|
+
toolCalls: v.toolCalls,
|
|
2763
|
+
durationMs: v.durationMs,
|
|
2764
|
+
status: v.lastStatus
|
|
2765
|
+
})).sort((a, b) => b.durationMs - a.durationMs);
|
|
2766
|
+
const totals = rows.reduce(
|
|
2767
|
+
(acc, r) => ({
|
|
2768
|
+
tasks: acc.tasks + r.tasks,
|
|
2769
|
+
iterations: acc.iterations + r.iterations,
|
|
2770
|
+
toolCalls: acc.toolCalls + r.toolCalls,
|
|
2771
|
+
durationMs: acc.durationMs + r.durationMs
|
|
2772
|
+
}),
|
|
2773
|
+
{ tasks: 0, iterations: 0, toolCalls: 0, durationMs: 0 }
|
|
2774
|
+
);
|
|
2775
|
+
return { rows, totals };
|
|
2776
|
+
}
|
|
2777
|
+
/**
|
|
2778
|
+
* Force the director to write its manifest to disk and return the path,
|
|
2779
|
+
* or `null` when director mode is off (the simple coordinator path has
|
|
2780
|
+
* no manifest). Callers should fall back to a friendly user message
|
|
2781
|
+
* when `null` is returned — e.g. `/fleet manifest` does this already.
|
|
2782
|
+
*
|
|
2783
|
+
* The returned string is the absolute path of the manifest file. The
|
|
2784
|
+
* file contents are JSON; readers can `JSON.parse(fs.readFileSync(...))`
|
|
2785
|
+
* to consume.
|
|
2786
|
+
*/
|
|
2787
|
+
async manifest() {
|
|
2788
|
+
if (!this.director) return null;
|
|
2789
|
+
return this.director.writeManifest();
|
|
2790
|
+
}
|
|
2791
|
+
/**
|
|
2792
|
+
* True when this host is running in director mode. Surfaces the mode
|
|
2793
|
+
* to slash commands and tests without exposing the underlying Director
|
|
2794
|
+
* (which would let callers bypass the host's coordination layer).
|
|
2795
|
+
*/
|
|
2796
|
+
isDirectorMode() {
|
|
2797
|
+
return !!this.director;
|
|
2798
|
+
}
|
|
2799
|
+
/**
|
|
2800
|
+
* Terminate a single subagent. Returns true when the subagent existed
|
|
2801
|
+
* (regardless of whether stop() succeeded or it was already idle),
|
|
2802
|
+
* false when no coordinator has been created yet — meaning the user
|
|
2803
|
+
* called /fleet kill before any /spawn, and there's nothing to do.
|
|
2804
|
+
*/
|
|
2805
|
+
async kill(subagentId) {
|
|
2806
|
+
if (!this.coordinator) return false;
|
|
2807
|
+
await this.coordinator.stop(subagentId);
|
|
2808
|
+
return true;
|
|
2809
|
+
}
|
|
2600
2810
|
async stopAll() {
|
|
2601
2811
|
if (this.coordinator) {
|
|
2602
2812
|
await this.coordinator.stopAll();
|
|
@@ -3933,7 +4143,8 @@ async function helpCmd(_args, deps) {
|
|
|
3933
4143
|
" wstack version Print version",
|
|
3934
4144
|
"",
|
|
3935
4145
|
"Global flags:",
|
|
3936
|
-
" --provider, --model, --cwd, --log-level, --yolo, --verbose, --trace, --config"
|
|
4146
|
+
" --provider, --model, --cwd, --log-level, --yolo, --verbose, --trace, --config",
|
|
4147
|
+
" --director Run with Director-backed orchestration (writes fleet manifest)"
|
|
3937
4148
|
];
|
|
3938
4149
|
deps.renderer.write(lines.join("\n") + "\n");
|
|
3939
4150
|
return 0;
|
|
@@ -4677,6 +4888,8 @@ Try \`wstack models refresh\` once you have network access, or run with --no-fea
|
|
|
4677
4888
|
return err instanceof Error ? err.message : String(err);
|
|
4678
4889
|
}
|
|
4679
4890
|
};
|
|
4891
|
+
const directorMode = flags["director"] === true;
|
|
4892
|
+
const manifestPath = directorMode ? typeof process.env["WRONGSTACK_FLEET_MANIFEST"] === "string" ? process.env["WRONGSTACK_FLEET_MANIFEST"] : path5.join(wpaths.projectSessions, session.id, "fleet.json") : void 0;
|
|
4680
4893
|
const multiAgentHost = new MultiAgentHost({
|
|
4681
4894
|
container,
|
|
4682
4895
|
toolRegistry,
|
|
@@ -4688,7 +4901,10 @@ Try \`wstack models refresh\` once you have network access, or run with --no-fea
|
|
|
4688
4901
|
tokenCounter,
|
|
4689
4902
|
projectRoot,
|
|
4690
4903
|
cwd
|
|
4691
|
-
});
|
|
4904
|
+
}, { directorMode, manifestPath });
|
|
4905
|
+
if (directorMode) {
|
|
4906
|
+
renderer.writeInfo(`Director mode enabled. Fleet manifest \u2192 ${manifestPath}`);
|
|
4907
|
+
}
|
|
4692
4908
|
const slashCmds = buildBuiltinSlashCommands({
|
|
4693
4909
|
registry: slashRegistry,
|
|
4694
4910
|
toolRegistry,
|
|
@@ -4701,9 +4917,14 @@ Try \`wstack models refresh\` once you have network access, or run with --no-fea
|
|
|
4701
4917
|
context,
|
|
4702
4918
|
metricsSink,
|
|
4703
4919
|
healthRegistry,
|
|
4704
|
-
onSpawn: async (description) => {
|
|
4705
|
-
const { subagentId, taskId } = await multiAgentHost.spawn(description);
|
|
4706
|
-
|
|
4920
|
+
onSpawn: async (description, spawnOpts) => {
|
|
4921
|
+
const { subagentId, taskId } = await multiAgentHost.spawn(description, spawnOpts);
|
|
4922
|
+
const tags = [];
|
|
4923
|
+
if (spawnOpts?.provider) tags.push(spawnOpts.provider);
|
|
4924
|
+
if (spawnOpts?.model) tags.push(spawnOpts.model);
|
|
4925
|
+
if (spawnOpts?.name) tags.push(`"${spawnOpts.name}"`);
|
|
4926
|
+
const tag = tags.length > 0 ? ` (${tags.join(" / ")})` : "";
|
|
4927
|
+
return `Spawned subagent ${subagentId}${tag} for task ${taskId}. Use /agents to track progress.`;
|
|
4707
4928
|
},
|
|
4708
4929
|
onAgents: () => {
|
|
4709
4930
|
const s = multiAgentHost.status();
|
|
@@ -4718,6 +4939,60 @@ Try \`wstack models refresh\` once you have network access, or run with --no-fea
|
|
|
4718
4939
|
}
|
|
4719
4940
|
return lines.join("\n");
|
|
4720
4941
|
},
|
|
4942
|
+
onFleet: async (action, target) => {
|
|
4943
|
+
if (action === "status") {
|
|
4944
|
+
const s = multiAgentHost.status();
|
|
4945
|
+
const lines = [color.bold("Fleet status"), ` ${s.summary}`];
|
|
4946
|
+
if (s.pending.length > 0) {
|
|
4947
|
+
lines.push("", color.dim(" Pending"));
|
|
4948
|
+
for (const p of s.pending) {
|
|
4949
|
+
lines.push(` ${p.taskId.slice(0, 8)} \u2192 ${p.subagentId.slice(0, 8)} \xB7 ${p.description.slice(0, 60)}`);
|
|
4950
|
+
}
|
|
4951
|
+
}
|
|
4952
|
+
if (s.completed.length > 0) {
|
|
4953
|
+
lines.push("", color.dim(" Completed"));
|
|
4954
|
+
for (const r of s.completed) {
|
|
4955
|
+
const mark = r.status === "success" ? color.green("\u2713") : color.red("\u2717");
|
|
4956
|
+
lines.push(` ${mark} ${r.taskId.slice(0, 8)} \u2192 ${r.subagentId.slice(0, 8)} \xB7 ${r.iterations}it ${r.toolCalls}tc ${r.durationMs}ms`);
|
|
4957
|
+
}
|
|
4958
|
+
}
|
|
4959
|
+
return lines.join("\n");
|
|
4960
|
+
}
|
|
4961
|
+
if (action === "usage") {
|
|
4962
|
+
const u = multiAgentHost.usage();
|
|
4963
|
+
if (u.rows.length === 0) return "No completed subagent tasks yet.";
|
|
4964
|
+
const lines = [
|
|
4965
|
+
color.bold("Fleet usage"),
|
|
4966
|
+
color.dim(" subagent tasks iter tools ms status")
|
|
4967
|
+
];
|
|
4968
|
+
for (const r of u.rows) {
|
|
4969
|
+
lines.push(
|
|
4970
|
+
` ${r.subagentId.slice(0, 14).padEnd(14)} ${String(r.tasks).padStart(5)} ${String(r.iterations).padStart(4)} ${String(r.toolCalls).padStart(5)} ${String(r.durationMs).padStart(5)} ${r.status}`
|
|
4971
|
+
);
|
|
4972
|
+
}
|
|
4973
|
+
lines.push(
|
|
4974
|
+
color.dim(" \u2500".repeat(28)),
|
|
4975
|
+
` ${"TOTAL".padEnd(14)} ${String(u.totals.tasks).padStart(5)} ${String(u.totals.iterations).padStart(4)} ${String(u.totals.toolCalls).padStart(5)} ${String(u.totals.durationMs).padStart(5)}`
|
|
4976
|
+
);
|
|
4977
|
+
return lines.join("\n");
|
|
4978
|
+
}
|
|
4979
|
+
if (action === "kill") {
|
|
4980
|
+
if (!target) return "Usage: /fleet kill <subagent-id>";
|
|
4981
|
+
const ok = await multiAgentHost.kill(target);
|
|
4982
|
+
return ok ? `Sent stop signal to ${target}.` : "No coordinator is running yet \u2014 nothing to kill.";
|
|
4983
|
+
}
|
|
4984
|
+
if (action === "manifest") {
|
|
4985
|
+
if (!multiAgentHost.isDirectorMode()) {
|
|
4986
|
+
return "Manifest is only available when the run was started with --director.";
|
|
4987
|
+
}
|
|
4988
|
+
const p = await multiAgentHost.manifest();
|
|
4989
|
+
if (!p) {
|
|
4990
|
+
return "Director is active but no subagents have been spawned \u2014 nothing to record yet.";
|
|
4991
|
+
}
|
|
4992
|
+
return `Manifest written \u2192 ${p}`;
|
|
4993
|
+
}
|
|
4994
|
+
return `Unknown fleet action: ${action}`;
|
|
4995
|
+
},
|
|
4721
4996
|
onExit: () => {
|
|
4722
4997
|
void mcpRegistry.stopAll();
|
|
4723
4998
|
},
|