@wrongstack/cli 0.6.0 → 0.6.3
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 +509 -60
- package/dist/index.js.map +1 -1
- package/package.json +10 -10
package/dist/index.js
CHANGED
|
@@ -3,7 +3,7 @@ import * as path23 from 'path';
|
|
|
3
3
|
import { join } from 'path';
|
|
4
4
|
import * as fsp2 from 'fs/promises';
|
|
5
5
|
import { readdir, readFile } from 'fs/promises';
|
|
6
|
-
import { color,
|
|
6
|
+
import { color, DefaultPathResolver, TOKENS, DefaultSystemPromptBuilder, makeAutonomyPromptContributor, ToolRegistry, createContextManagerTool, EventBus, SlashCommandRegistry, createDelegateTool, FLEET_ROSTER, createMcpControlTool, EternalAutonomyEngine, DefaultLogger, DefaultModelsRegistry, ProviderRegistry, InMemoryMetricsSink, wireMetricsToEvents, DefaultHealthRegistry, startMetricsServer, RecoveryLock, DefaultAttachmentStore, QueueStore, Context, loadTodosCheckpoint, attachTodosCheckpoint, loadDirectorState, loadPlan, createDefaultPipelines, AutoCompactionMiddleware, estimateRequestTokens, Agent, loadPlugins, FleetManager, makeDirectorSessionFactory, Director, makeAgentSubagentRunner, NULL_FLEET_BUS, resolveWstackPaths, DefaultSecretVault, migratePlaintextSecrets, DefaultConfigLoader, DefaultSessionReader, DefaultSessionRewinder, DefaultSessionStore, atomicWrite, DefaultPluginAPI, AutoApprovePermissionPolicy, formatContextWindowModeList, repairToolUseAdjacency, getContextWindowMode, resolveContextWindowPolicy, formatTodosList, emptyPlan, clearPlan, savePlan, formatPlanTemplates, getPlanTemplate, addPlanItem, formatPlan, deriveTodosFromPlanItem, removePlanItem, setPlanItemStatus, SpecStore, TaskGraphStore, SpecVersioning, getTemplate, listTemplates, templateToMarkdown, SpecParser, renderSpecAnalysis, AISpecBuilder, DefaultTaskStore, TaskTracker, loadGoal, goalFilePath, summarizeUsage, emptyGoal, saveGoal, buildGoalPreamble, formatGoal, InputBuilder, projectHash, defaultOrchestrator, decryptConfigSecrets, encryptConfigSecrets as encryptConfigSecrets$1, allServers as allServers$1 } from '@wrongstack/core';
|
|
7
7
|
import { createRequire } from 'module';
|
|
8
8
|
import * as os6 from 'os';
|
|
9
9
|
import os6__default from 'os';
|
|
@@ -17,8 +17,8 @@ import { createDefaultContainer, routeImagesForModel, readClipboardImage } from
|
|
|
17
17
|
import { builtinToolsPack, rememberTool, forgetTool } from '@wrongstack/tools';
|
|
18
18
|
import * as readline from 'readline';
|
|
19
19
|
import { spawn } from 'child_process';
|
|
20
|
-
import { buildGoalPreamble } from '@wrongstack/tui';
|
|
21
20
|
import { SkillInstaller } from '@wrongstack/core/skills';
|
|
21
|
+
import { allServers } from '@wrongstack/core/infrastructure';
|
|
22
22
|
import { createToolVisionAdapters } from '@wrongstack/runtime/vision';
|
|
23
23
|
import { ToolExecutor } from '@wrongstack/core/execution';
|
|
24
24
|
import { writeFileSync } from 'fs';
|
|
@@ -1104,7 +1104,7 @@ async function runWebUI(opts) {
|
|
|
1104
1104
|
let abortController = null;
|
|
1105
1105
|
const authToken = crypto.randomBytes(16).toString("hex");
|
|
1106
1106
|
const wss = new WebSocketServer({ port, host: "127.0.0.1", maxPayload: 1 * 1024 * 1024 });
|
|
1107
|
-
console.log(`[WebUI] WebSocket server starting on ws://
|
|
1107
|
+
console.log(`[WebUI] WebSocket server starting on ws://127.0.0.1:${port}`);
|
|
1108
1108
|
const eventUnsubscribers = [];
|
|
1109
1109
|
function setupEvents() {
|
|
1110
1110
|
for (const unsub of eventUnsubscribers) unsub();
|
|
@@ -1220,7 +1220,7 @@ async function runWebUI(opts) {
|
|
|
1220
1220
|
}
|
|
1221
1221
|
return new Promise((resolve4) => {
|
|
1222
1222
|
wss.on("listening", () => {
|
|
1223
|
-
console.log(`[WebUI] WebSocket server running on ws://
|
|
1223
|
+
console.log(`[WebUI] WebSocket server running on ws://127.0.0.1:${port}`);
|
|
1224
1224
|
setupEvents();
|
|
1225
1225
|
});
|
|
1226
1226
|
wss.on("connection", (ws, req2) => {
|
|
@@ -1704,7 +1704,9 @@ var BOOLEAN_FLAGS = /* @__PURE__ */ new Set([
|
|
|
1704
1704
|
"metrics",
|
|
1705
1705
|
"webui",
|
|
1706
1706
|
"no-check",
|
|
1707
|
-
"director"
|
|
1707
|
+
"director",
|
|
1708
|
+
"no-hints",
|
|
1709
|
+
"hints"
|
|
1708
1710
|
]);
|
|
1709
1711
|
function parseArgs(argv) {
|
|
1710
1712
|
const flags = {};
|
|
@@ -2322,10 +2324,10 @@ var theme = { primary: color.amber };
|
|
|
2322
2324
|
async function saveToGlobalConfig(configPath2, provider, model, homeFn = () => process.env.HOME ?? __require("os").homedir()) {
|
|
2323
2325
|
try {
|
|
2324
2326
|
const { atomicWrite: atomicWrite8 } = await import('@wrongstack/core');
|
|
2325
|
-
const
|
|
2327
|
+
const fs20 = await import('fs/promises');
|
|
2326
2328
|
let existing = {};
|
|
2327
2329
|
try {
|
|
2328
|
-
const raw = await
|
|
2330
|
+
const raw = await fs20.readFile(configPath2, "utf8");
|
|
2329
2331
|
existing = JSON.parse(raw);
|
|
2330
2332
|
} catch {
|
|
2331
2333
|
}
|
|
@@ -2590,6 +2592,84 @@ async function resolveModelSelection(answer, models, provider, _registry, render
|
|
|
2590
2592
|
`);
|
|
2591
2593
|
return { provider: provider.id, model: modelId };
|
|
2592
2594
|
}
|
|
2595
|
+
var GROUPS = [
|
|
2596
|
+
{
|
|
2597
|
+
title: "Autonomy",
|
|
2598
|
+
items: [
|
|
2599
|
+
{ key: "/goal <text>", blurb: "lock in a verifiable mission \u2014 only Esc / Ctrl+C interrupt" },
|
|
2600
|
+
{ key: "/autonomy eternal", blurb: "sense \u2192 decide \u2192 execute \u2192 reflect loop until you stop it" },
|
|
2601
|
+
{ key: "--eternal", blurb: "boot directly into the eternal-autonomy engine" },
|
|
2602
|
+
{ key: "/autonomy on|suggest", blurb: "self-driving: auto-pick next step or just suggest it" }
|
|
2603
|
+
]
|
|
2604
|
+
},
|
|
2605
|
+
{
|
|
2606
|
+
title: "Multi-agent / fleet",
|
|
2607
|
+
items: [
|
|
2608
|
+
{ key: '--director "<task>"', blurb: "one-line LLM-driven fleet kickoff with 8 orchestration tools" },
|
|
2609
|
+
{ key: "/director", blurb: "promote the current session to director mode at runtime" },
|
|
2610
|
+
{ key: "/spawn -p <prov> -m <model> -n <name> <task>", blurb: "launch a single subagent (any provider/model)" },
|
|
2611
|
+
{ key: "/fleet status|usage|kill|log|manifest", blurb: "inspect and control the running subagent fleet" }
|
|
2612
|
+
]
|
|
2613
|
+
},
|
|
2614
|
+
{
|
|
2615
|
+
title: "Steering",
|
|
2616
|
+
items: [
|
|
2617
|
+
{ key: "Esc (while busy)", blurb: "soft interrupt \u2014 next message carries a STEERING preamble" },
|
|
2618
|
+
{ key: "/steer <text>", blurb: "mid-flight redirect, works when Esc is eaten by tmux" },
|
|
2619
|
+
{ key: "Ctrl+C \xD7 1 / \xD7 2 / \xD7 3", blurb: "cancel iteration \xB7 force-exit Ink \xB7 hard exit(130)" }
|
|
2620
|
+
]
|
|
2621
|
+
},
|
|
2622
|
+
{
|
|
2623
|
+
title: "Modes & context",
|
|
2624
|
+
items: [
|
|
2625
|
+
{ key: "/mode", blurb: "switch persona: code-reviewer, debugger, architect, tester, devops, \u2026" },
|
|
2626
|
+
{ key: "/model", blurb: "two-step provider \u2192 model picker, hot-swap at runtime" },
|
|
2627
|
+
{ key: "/yolo on|off|toggle", blurb: "auto-approve every tool call without restart" },
|
|
2628
|
+
{ key: "/context mode frugal|balanced|deep|archival", blurb: "pick how aggressively history is trimmed" },
|
|
2629
|
+
{ key: "/compact", blurb: "manually compact the in-flight context window" },
|
|
2630
|
+
{ key: "/plan show|add|start|done", blurb: "strategic roadmap, survives /resume across sessions" }
|
|
2631
|
+
]
|
|
2632
|
+
},
|
|
2633
|
+
{
|
|
2634
|
+
title: "Daily ops",
|
|
2635
|
+
items: [
|
|
2636
|
+
{ key: "@<query> / Alt+V / /image", blurb: "fuzzy file picker \xB7 paste clipboard image (TUI)" },
|
|
2637
|
+
{ key: "/mcp \xB7 wstack mcp add <name>", blurb: "connect MCP servers (stdio / SSE / streamable-http)" },
|
|
2638
|
+
{ key: "/plugin install|enable|disable <name>", blurb: "manage plugins (telegram, lsp, \u2026)" },
|
|
2639
|
+
{ key: "/skill \xB7 /init \xB7 /commit", blurb: "list skills \xB7 scaffold AGENTS.md \xB7 LLM-drafted git commit" },
|
|
2640
|
+
{ key: "/diag \xB7 /usage \xB7 wstack resume <id>", blurb: "diagnostics \xB7 token & cost totals \xB7 continue any session" }
|
|
2641
|
+
]
|
|
2642
|
+
}
|
|
2643
|
+
];
|
|
2644
|
+
var HINT_COUNT = GROUPS.reduce((n, g) => n + g.items.length, 0);
|
|
2645
|
+
function shouldSuppress(flags) {
|
|
2646
|
+
if (flags["no-hints"] === true) return true;
|
|
2647
|
+
if (flags["hints"] === false) return true;
|
|
2648
|
+
const env = process.env.WRONGSTACK_NO_HINTS;
|
|
2649
|
+
if (env && env !== "0" && env.toLowerCase() !== "false") return true;
|
|
2650
|
+
return false;
|
|
2651
|
+
}
|
|
2652
|
+
function printLaunchHints(renderer, flags) {
|
|
2653
|
+
if (shouldSuppress(flags)) return;
|
|
2654
|
+
const lines = [];
|
|
2655
|
+
lines.push("");
|
|
2656
|
+
lines.push(
|
|
2657
|
+
` ${color.cyan("\u25C6")} ${color.bold(`WrongStack \u2014 ${HINT_COUNT} things you can do here`)}`
|
|
2658
|
+
);
|
|
2659
|
+
for (const group of GROUPS) {
|
|
2660
|
+
lines.push(` ${color.dim("\u2500")} ${color.cyan(group.title)}`);
|
|
2661
|
+
for (const item of group.items) {
|
|
2662
|
+
lines.push(` ${color.bold(item.key)} ${color.dim("\u2014")} ${color.dim(item.blurb)}`);
|
|
2663
|
+
}
|
|
2664
|
+
}
|
|
2665
|
+
lines.push("");
|
|
2666
|
+
lines.push(
|
|
2667
|
+
` ${color.dim(`tip: hide this with ${color.bold("--no-hints")} or ${color.bold("WRONGSTACK_NO_HINTS=1")}`)}`
|
|
2668
|
+
);
|
|
2669
|
+
lines.push("");
|
|
2670
|
+
renderer.write(`${lines.join("\n")}
|
|
2671
|
+
`);
|
|
2672
|
+
}
|
|
2593
2673
|
async function pathExists(file) {
|
|
2594
2674
|
try {
|
|
2595
2675
|
await fsp2.access(file);
|
|
@@ -2679,52 +2759,79 @@ async function detectProjectFacts(root) {
|
|
|
2679
2759
|
}
|
|
2680
2760
|
function renderAgentsTemplate(f) {
|
|
2681
2761
|
const cmd = (s) => s ? `\`${s}\`` : "_TODO_";
|
|
2762
|
+
const hints = f.hints.length > 0 ? `
|
|
2763
|
+
|
|
2764
|
+
> Auto-detected: ${f.hints.join(", ")}` : "";
|
|
2682
2765
|
return `# AGENTS.md
|
|
2683
2766
|
|
|
2684
|
-
|
|
2685
|
-
|
|
2686
|
-
|
|
2767
|
+
> **DO NOT DELETE THIS FILE.** It is loaded into WrongStack's system prompt as
|
|
2768
|
+
> persistent project context. Previous content here may contain decisions,
|
|
2769
|
+
> architecture notes, domain knowledge, or verification history that should be
|
|
2770
|
+
> preserved. Merge additions rather than replacing.
|
|
2687
2771
|
|
|
2688
2772
|
## Project brief
|
|
2689
2773
|
|
|
2690
|
-
- **Purpose:** _What does this project do
|
|
2774
|
+
- **Purpose:** _What does this project do and why does it exist?_
|
|
2691
2775
|
- **Primary users:** _Who uses it: developers, operators, customers, internal systems?_
|
|
2692
|
-
- **Runtime/deployment:**
|
|
2693
|
-
- **Main entry points:** _Which files or commands should an agent inspect first?_
|
|
2776
|
+
- **Runtime / deployment:** _CLI, server, browser, worker, library, package?_${hints}
|
|
2694
2777
|
|
|
2695
2778
|
## How to work safely
|
|
2696
2779
|
|
|
2697
2780
|
- _Project-specific rules the agent should always follow._
|
|
2698
2781
|
- _Files, generated artifacts, migrations, or config the agent should not edit without asking._
|
|
2699
|
-
- _Preferred style or architecture choices
|
|
2782
|
+
- _Preferred style or architecture choices not obvious from the code._
|
|
2783
|
+
- _Known fragile areas or historical bugs that deserve extra caution._
|
|
2700
2784
|
|
|
2701
2785
|
## Commands
|
|
2702
2786
|
|
|
2703
|
-
|
|
2704
|
-
|
|
2705
|
-
|
|
2706
|
-
|
|
2787
|
+
| Command | Script |
|
|
2788
|
+
|---------|--------|
|
|
2789
|
+
| Build | ${cmd(f.build)} |
|
|
2790
|
+
| Test | ${cmd(f.test)} |
|
|
2791
|
+
| Lint | ${cmd(f.lint)} |
|
|
2792
|
+
| Run locally | ${cmd(f.run)} |
|
|
2793
|
+
|
|
2794
|
+
## Key files and entry points
|
|
2795
|
+
|
|
2796
|
+
| File / directory | Role |
|
|
2797
|
+
|---|---|
|
|
2798
|
+
| _src/_ | _Main source entry point(s)_ |
|
|
2799
|
+
| _tests/_ | _Test root or convention_ |
|
|
2800
|
+
| _docs/_ | _Architecture, runbooks, design notes_ |
|
|
2801
|
+
| _scripts/_ | _Automation scripts (CI, release, install, etc.)_ |
|
|
2707
2802
|
|
|
2708
2803
|
## Architecture notes
|
|
2709
2804
|
|
|
2710
2805
|
_Summarize the important modules, data flow, boundaries, and ownership rules.
|
|
2711
|
-
Mention anything a newcomer might misread._
|
|
2806
|
+
Mention anything a newcomer might misread or that looks unusual but is intentional._
|
|
2807
|
+
|
|
2808
|
+
### Dependency layers
|
|
2809
|
+
|
|
2810
|
+
_Describe the key dependency direction or layered structure, e.g.: "core has no
|
|
2811
|
+
runtime deps; cli assembles everything above it."_
|
|
2812
|
+
|
|
2813
|
+
### Extension points
|
|
2814
|
+
|
|
2815
|
+
_Plugin, MCP, extension hooks, custom tools \u2014 what's wired up and how._
|
|
2712
2816
|
|
|
2713
2817
|
## Domain knowledge
|
|
2714
2818
|
|
|
2715
2819
|
_Business rules, acronyms, invariants, external services, and notes where the
|
|
2716
|
-
code looks unusual but is intentional.
|
|
2820
|
+
code looks unusual but is intentional. E.g.: "IDs are ULIDs, not UUIDs", "the
|
|
2821
|
+
\`draft\` flag means uncommitted billing metadata", "MCP servers are restarted
|
|
2822
|
+
on disconnect with exponential backoff, up to 3 attempts"._
|
|
2717
2823
|
|
|
2718
2824
|
## Verification checklist
|
|
2719
2825
|
|
|
2720
2826
|
- _What should be run after code changes?_
|
|
2721
2827
|
- _What manual smoke test proves the common path still works?_
|
|
2722
2828
|
- _What failure modes deserve extra attention?_
|
|
2829
|
+
- _Any known flaky tests or environment-dependent behavior?_
|
|
2723
2830
|
|
|
2724
2831
|
## Useful pointers
|
|
2725
2832
|
|
|
2726
|
-
- _Docs, dashboards, runbooks, issue trackers, design notes,
|
|
2727
|
-
`;
|
|
2833
|
+
- _Docs, dashboards, runbooks, issue trackers, design notes, owner contacts._
|
|
2834
|
+
- _Related projects or repositories._`;
|
|
2728
2835
|
}
|
|
2729
2836
|
function countTurnPairs(messages) {
|
|
2730
2837
|
let count = 0;
|
|
@@ -3128,7 +3235,7 @@ function buildStatsCommand(opts) {
|
|
|
3128
3235
|
function buildFleetCommand(opts) {
|
|
3129
3236
|
return {
|
|
3130
3237
|
name: "fleet",
|
|
3131
|
-
description: "Inspect or control the subagent fleet: /fleet [status|usage|kill <id>|manifest|retry [taskId]|log <id>|stream on|off|help]",
|
|
3238
|
+
description: "Inspect or control the subagent fleet: /fleet [status|usage|kill <id>|manifest|concurrency [N]|retry [taskId]|log <id>|stream on|off|help]",
|
|
3132
3239
|
help: [
|
|
3133
3240
|
"Usage:",
|
|
3134
3241
|
" /fleet Show fleet status (alias for /fleet status).",
|
|
@@ -3136,6 +3243,8 @@ function buildFleetCommand(opts) {
|
|
|
3136
3243
|
" /fleet usage Per-subagent runtime cost.",
|
|
3137
3244
|
" /fleet kill <id> Terminate a running subagent.",
|
|
3138
3245
|
" /fleet manifest Print the director manifest.",
|
|
3246
|
+
" /fleet concurrency Show the current concurrent-subagent ceiling.",
|
|
3247
|
+
" /fleet concurrency N Set the ceiling to N (>= 1). Lowering does not preempt running tasks.",
|
|
3139
3248
|
" /fleet retry List interrupted tasks from the last run.",
|
|
3140
3249
|
" /fleet retry <taskId> Re-spawn the matching subagent and re-assign the task.",
|
|
3141
3250
|
" /fleet retry all Re-assign every interrupted task at once.",
|
|
@@ -3161,6 +3270,9 @@ function buildFleetCommand(opts) {
|
|
|
3161
3270
|
if (!target) return { message: "Usage: /fleet kill <subagent-id>" };
|
|
3162
3271
|
return { message: await opts.onFleet("kill", target) };
|
|
3163
3272
|
}
|
|
3273
|
+
case "concurrency": {
|
|
3274
|
+
return { message: await opts.onFleet("concurrency", target) };
|
|
3275
|
+
}
|
|
3164
3276
|
case "retry": {
|
|
3165
3277
|
if (!opts.onFleetRetry) {
|
|
3166
3278
|
return { message: "Retry is only available when director mode is active." };
|
|
@@ -3298,20 +3410,10 @@ function buildHelpCommand(opts) {
|
|
|
3298
3410
|
function buildInitCommand(opts) {
|
|
3299
3411
|
return {
|
|
3300
3412
|
name: "init",
|
|
3301
|
-
description: "Create .wrongstack/AGENTS.md project context for the system prompt.",
|
|
3302
|
-
async run(
|
|
3303
|
-
const force = args.trim() === "--force";
|
|
3413
|
+
description: "Create or update .wrongstack/AGENTS.md project context for the system prompt.",
|
|
3414
|
+
async run(_args, ctx) {
|
|
3304
3415
|
const dir = path23.join(ctx.projectRoot, ".wrongstack");
|
|
3305
3416
|
const file = path23.join(dir, "AGENTS.md");
|
|
3306
|
-
try {
|
|
3307
|
-
await fsp2.access(file);
|
|
3308
|
-
if (!force) {
|
|
3309
|
-
const msg2 = `AGENTS.md already exists at ${file}. Use "/init --force" to overwrite.`;
|
|
3310
|
-
opts.renderer.writeWarning(msg2);
|
|
3311
|
-
return { message: msg2 };
|
|
3312
|
-
}
|
|
3313
|
-
} catch {
|
|
3314
|
-
}
|
|
3315
3417
|
const detected = await detectProjectFacts(ctx.projectRoot);
|
|
3316
3418
|
const body = renderAgentsTemplate(detected);
|
|
3317
3419
|
await fsp2.mkdir(dir, { recursive: true });
|
|
@@ -3332,6 +3434,223 @@ No project type auto-detected. Edit the file with project context and instructio
|
|
|
3332
3434
|
}
|
|
3333
3435
|
};
|
|
3334
3436
|
}
|
|
3437
|
+
function parseMcpArgs(args) {
|
|
3438
|
+
const trimmed = args.trim();
|
|
3439
|
+
if (!trimmed || trimmed === "list") return { action: "list", name: "" };
|
|
3440
|
+
const parts = trimmed.split(/\s+/);
|
|
3441
|
+
const action = parts[0];
|
|
3442
|
+
const name = parts[1] ?? "";
|
|
3443
|
+
const enable = parts.includes("--enable") || parts.includes("-e");
|
|
3444
|
+
switch (action) {
|
|
3445
|
+
case "add":
|
|
3446
|
+
return name ? { action: "add", name, enable } : null;
|
|
3447
|
+
case "remove":
|
|
3448
|
+
return name ? { action: "remove", name } : null;
|
|
3449
|
+
case "enable":
|
|
3450
|
+
return name ? { action: "enable", name } : null;
|
|
3451
|
+
case "disable":
|
|
3452
|
+
return name ? { action: "disable", name } : null;
|
|
3453
|
+
case "restart":
|
|
3454
|
+
return name ? { action: "restart", name } : null;
|
|
3455
|
+
default:
|
|
3456
|
+
return null;
|
|
3457
|
+
}
|
|
3458
|
+
}
|
|
3459
|
+
async function runMcpManagementCommand(parsed, deps) {
|
|
3460
|
+
const { config, configPath: configPath2, mcpRegistry, allServerPresets } = deps;
|
|
3461
|
+
const configured = config.mcpServers ?? {};
|
|
3462
|
+
switch (parsed.action) {
|
|
3463
|
+
case "list":
|
|
3464
|
+
return renderList(configured, mcpRegistry, allServerPresets);
|
|
3465
|
+
case "add":
|
|
3466
|
+
return runAdd(parsed.name, parsed.enable ?? false, configured, configPath2, allServerPresets);
|
|
3467
|
+
case "remove":
|
|
3468
|
+
return runRemove(parsed.name, configured, configPath2, mcpRegistry);
|
|
3469
|
+
case "enable":
|
|
3470
|
+
return runEnable(parsed.name, configured, configPath2, mcpRegistry);
|
|
3471
|
+
case "disable":
|
|
3472
|
+
return runDisable(parsed.name, configured, configPath2, mcpRegistry);
|
|
3473
|
+
case "restart":
|
|
3474
|
+
return runRestart(parsed.name, mcpRegistry);
|
|
3475
|
+
}
|
|
3476
|
+
}
|
|
3477
|
+
function renderList(configured, mcpRegistry, all) {
|
|
3478
|
+
const lines = [];
|
|
3479
|
+
const liveStatus = mcpRegistry.list();
|
|
3480
|
+
const liveMap = new Map(liveStatus.map((s) => [s.name, s]));
|
|
3481
|
+
const configuredNames = new Set(Object.keys(configured));
|
|
3482
|
+
if (configuredNames.size > 0) {
|
|
3483
|
+
lines.push(color.bold("Configured servers:"));
|
|
3484
|
+
for (const [name, cfg] of Object.entries(configured)) {
|
|
3485
|
+
const live = liveMap.get(name);
|
|
3486
|
+
const toolCount = live ? color.dim(` (${live.toolCount} tools)`) : "";
|
|
3487
|
+
const enabled = cfg.enabled === false ? `${color.dim("disabled")} ` : `${color.green("\u25CF enabled")} `;
|
|
3488
|
+
const stateStr = live ? stateBadge(live.state) : color.dim("\u25CB not running");
|
|
3489
|
+
lines.push(` ${color.bold(name)} ${enabled}${stateStr}${toolCount}`);
|
|
3490
|
+
if (cfg.description) lines.push(` ${color.dim(cfg.description)}`);
|
|
3491
|
+
}
|
|
3492
|
+
lines.push("");
|
|
3493
|
+
}
|
|
3494
|
+
const unconfigured = Object.entries(all).filter(([n]) => !configuredNames.has(n));
|
|
3495
|
+
lines.push(color.bold("Available presets (run `/mcp add <name> --enable` to enable):"));
|
|
3496
|
+
if (unconfigured.length === 0) {
|
|
3497
|
+
lines.push(` ${color.dim("All presets are already configured.")}`);
|
|
3498
|
+
} else {
|
|
3499
|
+
for (const [name, cfg] of unconfigured) {
|
|
3500
|
+
const warn = cfg.permission === "deny" ? color.red(" \u26A0") : "";
|
|
3501
|
+
lines.push(` ${color.bold(name)} ${cfg.description ?? cfg.transport}${warn}`);
|
|
3502
|
+
}
|
|
3503
|
+
}
|
|
3504
|
+
lines.push("");
|
|
3505
|
+
lines.push(color.dim(" /mcp add <name> [--enable] /mcp remove <name>"));
|
|
3506
|
+
lines.push(color.dim(" /mcp enable <name> /mcp disable <name>"));
|
|
3507
|
+
lines.push(color.dim(" /mcp restart <name> (runtime restart)"));
|
|
3508
|
+
return lines.join("\n");
|
|
3509
|
+
}
|
|
3510
|
+
async function runAdd(name, enable, configured, configPath2, all) {
|
|
3511
|
+
const preset = all[name];
|
|
3512
|
+
if (!preset) {
|
|
3513
|
+
const known = Object.keys(all).join(", ");
|
|
3514
|
+
return `Unknown server "${name}". Available: ${known}`;
|
|
3515
|
+
}
|
|
3516
|
+
if (configured[name]) {
|
|
3517
|
+
const full2 = await readConfig(configPath2);
|
|
3518
|
+
full2.mcpServers = {
|
|
3519
|
+
...full2.mcpServers ?? {},
|
|
3520
|
+
[name]: { ...preset, ...configured[name], enabled: enable }
|
|
3521
|
+
};
|
|
3522
|
+
await writeConfig(configPath2, full2);
|
|
3523
|
+
return `${color.green("Updated")} "${name}" (${enable ? "enabled" : "disabled"}). Config written.`;
|
|
3524
|
+
}
|
|
3525
|
+
const full = await readConfig(configPath2);
|
|
3526
|
+
const mcpServers = { ...full.mcpServers ?? {}, [name]: { ...preset, enabled: enable } };
|
|
3527
|
+
full.mcpServers = mcpServers;
|
|
3528
|
+
await writeConfig(configPath2, full);
|
|
3529
|
+
const verb = enable ? "Enabled" : "Added (disabled \u2014 /mcp enable to start)";
|
|
3530
|
+
return `${color.green(verb)} "${name}" (${preset.transport}). Config written to ${configPath2}.`;
|
|
3531
|
+
}
|
|
3532
|
+
async function runRemove(name, configured, configPath2, mcpRegistry) {
|
|
3533
|
+
if (!configured[name]) return `Server "${name}" is not in config.`;
|
|
3534
|
+
await mcpRegistry.stop(name).catch(() => {
|
|
3535
|
+
});
|
|
3536
|
+
const full = await readConfig(configPath2);
|
|
3537
|
+
const mcpServers = { ...full.mcpServers ?? {} };
|
|
3538
|
+
delete mcpServers[name];
|
|
3539
|
+
full.mcpServers = mcpServers;
|
|
3540
|
+
await writeConfig(configPath2, full);
|
|
3541
|
+
return `${color.yellow("Removed")} "${name}" from config.`;
|
|
3542
|
+
}
|
|
3543
|
+
async function runEnable(name, configured, configPath2, mcpRegistry) {
|
|
3544
|
+
const cfg = configured[name];
|
|
3545
|
+
if (!cfg) return `Server "${name}" is not in config. Run \`/mcp add ${name} --enable\` first.`;
|
|
3546
|
+
if (cfg.enabled !== false) {
|
|
3547
|
+
try {
|
|
3548
|
+
await mcpRegistry.restart(name);
|
|
3549
|
+
return `${color.green("\u25CF")} "${name}" is already enabled and running.`;
|
|
3550
|
+
} catch {
|
|
3551
|
+
await mcpRegistry.start({ ...cfg, enabled: true });
|
|
3552
|
+
return `${color.green("Enabled")} "${name}" and started.`;
|
|
3553
|
+
}
|
|
3554
|
+
}
|
|
3555
|
+
const full = await readConfig(configPath2);
|
|
3556
|
+
const mcpServers = { ...full.mcpServers ?? {} };
|
|
3557
|
+
mcpServers[name] = { ...mcpServers[name], enabled: true };
|
|
3558
|
+
full.mcpServers = mcpServers;
|
|
3559
|
+
await writeConfig(configPath2, full);
|
|
3560
|
+
try {
|
|
3561
|
+
await mcpRegistry.restart(name);
|
|
3562
|
+
} catch {
|
|
3563
|
+
await mcpRegistry.start({ ...cfg, enabled: true });
|
|
3564
|
+
}
|
|
3565
|
+
return `${color.green("Enabled")} "${name}" and started.`;
|
|
3566
|
+
}
|
|
3567
|
+
async function runDisable(name, configured, configPath2, mcpRegistry) {
|
|
3568
|
+
const cfg = configured[name];
|
|
3569
|
+
if (!cfg) return `Server "${name}" is not in config.`;
|
|
3570
|
+
await mcpRegistry.stop(name).catch(() => {
|
|
3571
|
+
});
|
|
3572
|
+
const full = await readConfig(configPath2);
|
|
3573
|
+
const mcpServers = { ...full.mcpServers ?? {} };
|
|
3574
|
+
mcpServers[name] = { ...mcpServers[name], enabled: false };
|
|
3575
|
+
full.mcpServers = mcpServers;
|
|
3576
|
+
await writeConfig(configPath2, full);
|
|
3577
|
+
return `${color.yellow("Disabled")} "${name}" and stopped.`;
|
|
3578
|
+
}
|
|
3579
|
+
async function runRestart(name, mcpRegistry) {
|
|
3580
|
+
const live = mcpRegistry.list();
|
|
3581
|
+
if (!live.find((s) => s.name === name)) {
|
|
3582
|
+
return `Server "${name}" is not currently running. Add it with \`/mcp add ${name} --enable\`.`;
|
|
3583
|
+
}
|
|
3584
|
+
try {
|
|
3585
|
+
await mcpRegistry.restart(name);
|
|
3586
|
+
return `${color.green("\u2713")} Restarted "${name}".`;
|
|
3587
|
+
} catch (err) {
|
|
3588
|
+
return `${color.red("\u2717")} Failed to restart "${name}": ${err instanceof Error ? err.message : String(err)}`;
|
|
3589
|
+
}
|
|
3590
|
+
}
|
|
3591
|
+
function stateBadge(state) {
|
|
3592
|
+
switch (state) {
|
|
3593
|
+
case "connected":
|
|
3594
|
+
return color.green("\u25CF connected");
|
|
3595
|
+
case "connecting":
|
|
3596
|
+
return color.cyan("\u25D0 connecting");
|
|
3597
|
+
case "reconnecting":
|
|
3598
|
+
return color.cyan("\u25D1 reconnecting");
|
|
3599
|
+
case "disconnected":
|
|
3600
|
+
return color.dim("\u25CB disconnected");
|
|
3601
|
+
case "failed":
|
|
3602
|
+
return color.red("\u2717 failed");
|
|
3603
|
+
default:
|
|
3604
|
+
return color.dim(state);
|
|
3605
|
+
}
|
|
3606
|
+
}
|
|
3607
|
+
async function readConfig(path24) {
|
|
3608
|
+
try {
|
|
3609
|
+
return JSON.parse(await fsp2.readFile(path24, "utf8"));
|
|
3610
|
+
} catch {
|
|
3611
|
+
return {};
|
|
3612
|
+
}
|
|
3613
|
+
}
|
|
3614
|
+
async function writeConfig(path24, cfg) {
|
|
3615
|
+
const raw = JSON.stringify(cfg, null, 2);
|
|
3616
|
+
const tmp = path24 + ".tmp";
|
|
3617
|
+
await fsp2.writeFile(tmp, raw, "utf8");
|
|
3618
|
+
await fsp2.rename(tmp, path24);
|
|
3619
|
+
}
|
|
3620
|
+
|
|
3621
|
+
// src/slash-commands/mcp.ts
|
|
3622
|
+
function buildMcpSlashCommand(opts) {
|
|
3623
|
+
return {
|
|
3624
|
+
name: "mcp",
|
|
3625
|
+
description: "Manage MCP servers: /mcp [list|add <name>|remove <name>|enable <name>|disable <name>|restart <name>]",
|
|
3626
|
+
aliases: ["mcp-servers"],
|
|
3627
|
+
argsHint: "[list|add <name>|remove <name>|enable <name>|disable <name>|restart <name>]",
|
|
3628
|
+
help: [
|
|
3629
|
+
"Usage:",
|
|
3630
|
+
" /mcp List available and configured servers.",
|
|
3631
|
+
" /mcp list Same.",
|
|
3632
|
+
" /mcp add <name> Add server preset to config (disabled).",
|
|
3633
|
+
" /mcp add <name> --enable Add and immediately enable.",
|
|
3634
|
+
" /mcp remove <name> Remove server from config.",
|
|
3635
|
+
" /mcp enable <name> Enable server in config + start it.",
|
|
3636
|
+
" /mcp disable <name> Disable server in config + stop it.",
|
|
3637
|
+
" /mcp restart <name> Stop and restart a running server (REPL only).",
|
|
3638
|
+
"",
|
|
3639
|
+
"Examples:",
|
|
3640
|
+
" /mcp",
|
|
3641
|
+
" /mcp add filesystem --enable",
|
|
3642
|
+
" /mcp enable github",
|
|
3643
|
+
" /mcp restart brave-search"
|
|
3644
|
+
].join("\n"),
|
|
3645
|
+
async run(args) {
|
|
3646
|
+
if (!opts.onMcp) {
|
|
3647
|
+
return { message: "MCP management is not available in this session." };
|
|
3648
|
+
}
|
|
3649
|
+
const result = await opts.onMcp(args.trim());
|
|
3650
|
+
return { message: result };
|
|
3651
|
+
}
|
|
3652
|
+
};
|
|
3653
|
+
}
|
|
3335
3654
|
|
|
3336
3655
|
// src/slash-commands/memory.ts
|
|
3337
3656
|
function buildMemoryCommand(opts) {
|
|
@@ -3657,10 +3976,11 @@ function buildSpawnCommand(opts) {
|
|
|
3657
3976
|
function buildAgentsCommand(opts) {
|
|
3658
3977
|
return {
|
|
3659
3978
|
name: "agents",
|
|
3660
|
-
description: "Show status of spawned subagents.",
|
|
3661
|
-
async run() {
|
|
3979
|
+
description: "Show status of spawned subagents. With an id, show live monitor view.",
|
|
3980
|
+
async run(args) {
|
|
3662
3981
|
if (!opts.onAgents) return { message: "Multi-agent is not enabled in this session." };
|
|
3663
|
-
|
|
3982
|
+
const subagentId = args.trim() || void 0;
|
|
3983
|
+
return { message: await opts.onAgents(subagentId) };
|
|
3664
3984
|
}
|
|
3665
3985
|
};
|
|
3666
3986
|
}
|
|
@@ -4676,6 +4996,7 @@ function buildBuiltinSlashCommands(opts) {
|
|
|
4676
4996
|
buildSkillUpdateCommand(opts),
|
|
4677
4997
|
buildSkillUninstallCommand(opts),
|
|
4678
4998
|
buildPluginCommand(opts),
|
|
4999
|
+
buildMcpSlashCommand(opts),
|
|
4679
5000
|
buildDiagCommand(opts),
|
|
4680
5001
|
buildStatsCommand(opts),
|
|
4681
5002
|
buildSpawnCommand(opts),
|
|
@@ -6138,12 +6459,8 @@ var initCmd = async (_args, deps) => {
|
|
|
6138
6459
|
await atomicWrite(deps.paths.globalConfig, JSON.stringify(encrypted, null, 2));
|
|
6139
6460
|
await fsp2.mkdir(path23.join(deps.projectRoot, ".wrongstack"), { recursive: true });
|
|
6140
6461
|
const agentsFile = path23.join(deps.projectRoot, ".wrongstack", "AGENTS.md");
|
|
6141
|
-
|
|
6142
|
-
|
|
6143
|
-
} catch {
|
|
6144
|
-
const detected2 = await detectProjectFacts(deps.projectRoot);
|
|
6145
|
-
await atomicWrite(agentsFile, renderAgentsTemplate(detected2));
|
|
6146
|
-
}
|
|
6462
|
+
const projectFacts = await detectProjectFacts(deps.projectRoot);
|
|
6463
|
+
await atomicWrite(agentsFile, renderAgentsTemplate(projectFacts));
|
|
6147
6464
|
deps.renderer.writeInfo(`Wrote ${deps.paths.globalConfig}`);
|
|
6148
6465
|
deps.renderer.writeInfo(`Project state lives in ${deps.paths.projectDir}`);
|
|
6149
6466
|
deps.renderer.writeInfo('Try: wstack "<task>" or wstack');
|
|
@@ -6179,7 +6496,7 @@ var mcpCmd = async (args, deps) => {
|
|
|
6179
6496
|
return removeMcpServer(name, deps);
|
|
6180
6497
|
}
|
|
6181
6498
|
if (sub === "restart") {
|
|
6182
|
-
deps.renderer.writeWarning("mcp restart is only available in REPL mode.");
|
|
6499
|
+
deps.renderer.writeWarning("mcp restart is only available in REPL mode. Use /mcp restart instead.");
|
|
6183
6500
|
return 0;
|
|
6184
6501
|
}
|
|
6185
6502
|
deps.renderer.writeError(`Unknown mcp subcommand: ${sub}`);
|
|
@@ -6354,7 +6671,7 @@ function renderConfiguredPlugins(config) {
|
|
|
6354
6671
|
return ` ${`${name}${suffix}`.padEnd(44)} ${enabled}`;
|
|
6355
6672
|
}).join("\n");
|
|
6356
6673
|
}
|
|
6357
|
-
async function
|
|
6674
|
+
async function readConfig2(file) {
|
|
6358
6675
|
try {
|
|
6359
6676
|
return JSON.parse(await fsp2.readFile(file, "utf8"));
|
|
6360
6677
|
} catch {
|
|
@@ -6373,7 +6690,7 @@ function officialPluginState(config, spec) {
|
|
|
6373
6690
|
return typeof match === "object" && match.enabled === false ? "disabled" : "enabled";
|
|
6374
6691
|
}
|
|
6375
6692
|
async function upsertPlugin(spec, opts, deps, verb) {
|
|
6376
|
-
const existing = await
|
|
6693
|
+
const existing = await readConfig2(deps.configPath);
|
|
6377
6694
|
const plugins = Array.isArray(existing.plugins) ? existing.plugins : [];
|
|
6378
6695
|
const idx = plugins.findIndex((p) => pluginName(p) === spec);
|
|
6379
6696
|
const nextEntry = pluginEntry(spec, opts.enabled);
|
|
@@ -6396,7 +6713,7 @@ async function upsertPlugin(spec, opts, deps, verb) {
|
|
|
6396
6713
|
};
|
|
6397
6714
|
}
|
|
6398
6715
|
async function removePlugin(spec, deps) {
|
|
6399
|
-
const existing = await
|
|
6716
|
+
const existing = await readConfig2(deps.configPath);
|
|
6400
6717
|
const plugins = Array.isArray(existing.plugins) ? existing.plugins : [];
|
|
6401
6718
|
const next = plugins.filter((p) => pluginName(p) !== spec);
|
|
6402
6719
|
if (next.length === plugins.length) {
|
|
@@ -6932,12 +7249,23 @@ function parseRewindFlags(args) {
|
|
|
6932
7249
|
}
|
|
6933
7250
|
return flags;
|
|
6934
7251
|
}
|
|
7252
|
+
function findSessionId(args) {
|
|
7253
|
+
for (let i = 0; i < args.length; i++) {
|
|
7254
|
+
const a = args[i];
|
|
7255
|
+
if (a === "--last" || a === "--to") {
|
|
7256
|
+
i++;
|
|
7257
|
+
continue;
|
|
7258
|
+
}
|
|
7259
|
+
if (!a.startsWith("--")) return a;
|
|
7260
|
+
}
|
|
7261
|
+
return void 0;
|
|
7262
|
+
}
|
|
6935
7263
|
var rewindCmd = async (args, deps) => {
|
|
6936
7264
|
const flags = parseRewindFlags(args);
|
|
6937
7265
|
const wpaths = resolveWstackPaths({ projectRoot: deps.projectRoot });
|
|
6938
7266
|
const sessionsDir = path23.join(wpaths.globalRoot, "sessions");
|
|
6939
7267
|
const rewind = new DefaultSessionRewinder(sessionsDir);
|
|
6940
|
-
let sessionId = args
|
|
7268
|
+
let sessionId = findSessionId(args);
|
|
6941
7269
|
if (!sessionId) {
|
|
6942
7270
|
if (!deps.sessionStore) {
|
|
6943
7271
|
deps.renderer.writeError("No session store available.");
|
|
@@ -7146,22 +7474,22 @@ function fmtDuration(ms) {
|
|
|
7146
7474
|
const remMin = m - h * 60;
|
|
7147
7475
|
return `${h}h${remMin}m`;
|
|
7148
7476
|
}
|
|
7149
|
-
function fmtTaskResultLine(r,
|
|
7477
|
+
function fmtTaskResultLine(r, color36) {
|
|
7150
7478
|
const stats = `${r.iterations}it ${r.toolCalls}tc ${fmtDuration(r.durationMs)}`;
|
|
7151
7479
|
const errMsg = typeof r.error === "string" ? r.error : r.error?.message;
|
|
7152
7480
|
const errKind = typeof r.error === "object" ? r.error?.kind : void 0;
|
|
7153
7481
|
const errTail = errMsg ? ` \u2014 ${errMsg.replace(/\s+/g, " ").slice(0, 80)}${errMsg.length > 80 ? "\u2026" : ""}` : "";
|
|
7154
|
-
const errKindChip = errKind ?
|
|
7155
|
-
const errSnip = errMsg || errKind ? `${errKindChip}${
|
|
7482
|
+
const errKindChip = errKind ? color36.dim(` [${errKind}]`) : "";
|
|
7483
|
+
const errSnip = errMsg || errKind ? `${errKindChip}${color36.dim(errTail)}` : "";
|
|
7156
7484
|
switch (r.status) {
|
|
7157
7485
|
case "success":
|
|
7158
|
-
return { mark:
|
|
7486
|
+
return { mark: color36.green("\u2713"), stats, tail: "" };
|
|
7159
7487
|
case "timeout":
|
|
7160
|
-
return { mark:
|
|
7488
|
+
return { mark: color36.yellow("\u23F1"), stats: `${color36.yellow("timeout")} ${stats}`, tail: errSnip };
|
|
7161
7489
|
case "stopped":
|
|
7162
|
-
return { mark:
|
|
7490
|
+
return { mark: color36.dim("\u2298"), stats: `${color36.dim("stopped")} ${stats}`, tail: errSnip };
|
|
7163
7491
|
case "failed":
|
|
7164
|
-
return { mark:
|
|
7492
|
+
return { mark: color36.red("\u2717"), stats: `${color36.red("failed")} ${stats}`, tail: errSnip };
|
|
7165
7493
|
}
|
|
7166
7494
|
}
|
|
7167
7495
|
|
|
@@ -7302,6 +7630,7 @@ async function boot(argv) {
|
|
|
7302
7630
|
flags["no-tui"] = true;
|
|
7303
7631
|
}
|
|
7304
7632
|
if (choices.yolo !== config.yolo) config = patchConfig(config, { yolo: choices.yolo });
|
|
7633
|
+
printLaunchHints(renderer, flags);
|
|
7305
7634
|
}
|
|
7306
7635
|
return {
|
|
7307
7636
|
config,
|
|
@@ -8107,7 +8436,7 @@ var MultiAgentHost = class {
|
|
|
8107
8436
|
const coordinatorConfig = {
|
|
8108
8437
|
coordinatorId: randomUUID(),
|
|
8109
8438
|
doneCondition: { type: "all_tasks_done" },
|
|
8110
|
-
maxConcurrent:
|
|
8439
|
+
maxConcurrent: this.opts.maxConcurrent ?? 4
|
|
8111
8440
|
};
|
|
8112
8441
|
const defaultScratchpad = this.opts.sharedScratchpadPath || (this.opts.sessionsRoot && this.opts.directorRunId ? path23.join(this.opts.sessionsRoot, this.opts.directorRunId, "shared") : void 0);
|
|
8113
8442
|
this.director = new Director({
|
|
@@ -8484,6 +8813,37 @@ var MultiAgentHost = class {
|
|
|
8484
8813
|
await this.getCoordinator().stopAll();
|
|
8485
8814
|
}
|
|
8486
8815
|
}
|
|
8816
|
+
/**
|
|
8817
|
+
* Current effective concurrent-subagent ceiling. Reads the live
|
|
8818
|
+
* coordinator config when the director is built; otherwise falls back
|
|
8819
|
+
* to the constructor option (or the default of 4 that buildDirector
|
|
8820
|
+
* will apply on first /spawn).
|
|
8821
|
+
*/
|
|
8822
|
+
getMaxConcurrent() {
|
|
8823
|
+
if (this.director) {
|
|
8824
|
+
return this.getCoordinator().config.maxConcurrent ?? 4;
|
|
8825
|
+
}
|
|
8826
|
+
return this.opts.maxConcurrent ?? 4;
|
|
8827
|
+
}
|
|
8828
|
+
/**
|
|
8829
|
+
* Change the concurrent-subagent ceiling at runtime. Updates the
|
|
8830
|
+
* constructor option (so lazy-built director picks it up) and, if the
|
|
8831
|
+
* coordinator already exists, mutates its live config + triggers a
|
|
8832
|
+
* dispatch pass so newly-allowed slots fill immediately.
|
|
8833
|
+
*
|
|
8834
|
+
* Throws on non-positive values; the caller is expected to validate
|
|
8835
|
+
* user input first.
|
|
8836
|
+
*/
|
|
8837
|
+
setMaxConcurrent(n) {
|
|
8838
|
+
if (!Number.isFinite(n) || n < 1) {
|
|
8839
|
+
throw new Error(`maxConcurrent must be a finite integer >= 1, got ${n}`);
|
|
8840
|
+
}
|
|
8841
|
+
const v = Math.floor(n);
|
|
8842
|
+
this.opts.maxConcurrent = v;
|
|
8843
|
+
if (this.director) {
|
|
8844
|
+
this.getCoordinator().setMaxConcurrent(v);
|
|
8845
|
+
}
|
|
8846
|
+
}
|
|
8487
8847
|
};
|
|
8488
8848
|
function makePromptDelegate(reader) {
|
|
8489
8849
|
return async (tool, input, suggestedPattern) => {
|
|
@@ -9186,6 +9546,8 @@ async function main(argv) {
|
|
|
9186
9546
|
const memoryStore = container.resolve(TOKENS.MemoryStore);
|
|
9187
9547
|
const skillLoader = container.resolve(TOKENS.SkillLoader);
|
|
9188
9548
|
const sessionRef = {};
|
|
9549
|
+
const autonomyModeRef = { current: "off" };
|
|
9550
|
+
const goalPathForPrompt = path23.join(projectRoot, ".wrongstack", "goal.json");
|
|
9189
9551
|
container.bind(
|
|
9190
9552
|
TOKENS.SystemPromptBuilder,
|
|
9191
9553
|
() => new DefaultSystemPromptBuilder({
|
|
@@ -9195,7 +9557,17 @@ async function main(argv) {
|
|
|
9195
9557
|
modeId,
|
|
9196
9558
|
modePrompt,
|
|
9197
9559
|
modelCapabilities,
|
|
9198
|
-
planPath: () => sessionRef.current ? path23.join(wpaths.projectSessions, `${sessionRef.current.id}.plan.json`) : void 0
|
|
9560
|
+
planPath: () => sessionRef.current ? path23.join(wpaths.projectSessions, `${sessionRef.current.id}.plan.json`) : void 0,
|
|
9561
|
+
contributors: [
|
|
9562
|
+
// Injects the ETERNAL AUTONOMY block when the user has activated
|
|
9563
|
+
// `/autonomy eternal`. Without this, the per-iteration directive
|
|
9564
|
+
// is the only place the model sees the rules — compaction can
|
|
9565
|
+
// drop it and the model forgets it's in autonomy mode.
|
|
9566
|
+
makeAutonomyPromptContributor({
|
|
9567
|
+
goalPath: goalPathForPrompt,
|
|
9568
|
+
enabled: () => autonomyModeRef.current === "eternal"
|
|
9569
|
+
})
|
|
9570
|
+
]
|
|
9199
9571
|
})
|
|
9200
9572
|
);
|
|
9201
9573
|
const toolRegistry = new ToolRegistry();
|
|
@@ -9368,6 +9740,9 @@ async function main(argv) {
|
|
|
9368
9740
|
}
|
|
9369
9741
|
};
|
|
9370
9742
|
const directorMode = flags["director"] === true || typeof flags["resume"] === "string";
|
|
9743
|
+
const maxConcurrentFromFlag = typeof flags["max-concurrent"] === "string" ? Number.parseInt(flags["max-concurrent"], 10) : void 0;
|
|
9744
|
+
const maxConcurrentFromEnv = typeof process.env["WRONGSTACK_MAX_CONCURRENT"] === "string" ? Number.parseInt(process.env["WRONGSTACK_MAX_CONCURRENT"], 10) : void 0;
|
|
9745
|
+
const maxConcurrent = Number.isFinite(maxConcurrentFromFlag) && maxConcurrentFromFlag > 0 ? maxConcurrentFromFlag : Number.isFinite(maxConcurrentFromEnv) && maxConcurrentFromEnv > 0 ? maxConcurrentFromEnv : void 0;
|
|
9371
9746
|
let director = null;
|
|
9372
9747
|
let autonomyMode = "off";
|
|
9373
9748
|
let eternalEngine = null;
|
|
@@ -9408,7 +9783,8 @@ async function main(argv) {
|
|
|
9408
9783
|
directorRunId: session.id,
|
|
9409
9784
|
fleetRoot: fleetRootForPromotion,
|
|
9410
9785
|
stateCheckpointPath,
|
|
9411
|
-
sessionWriter: session
|
|
9786
|
+
sessionWriter: session,
|
|
9787
|
+
maxConcurrent
|
|
9412
9788
|
}
|
|
9413
9789
|
);
|
|
9414
9790
|
toolRegistry.register(
|
|
@@ -9423,6 +9799,13 @@ async function main(argv) {
|
|
|
9423
9799
|
directorRunId: session.id
|
|
9424
9800
|
})
|
|
9425
9801
|
);
|
|
9802
|
+
toolRegistry.register(
|
|
9803
|
+
createMcpControlTool({
|
|
9804
|
+
getConfig: () => configStore.get(),
|
|
9805
|
+
configPath: wpaths.globalConfig,
|
|
9806
|
+
registry: mcpRegistry
|
|
9807
|
+
})
|
|
9808
|
+
);
|
|
9426
9809
|
if (directorMode) {
|
|
9427
9810
|
director = await multiAgentHost.ensureDirector();
|
|
9428
9811
|
if (director) {
|
|
@@ -9488,8 +9871,41 @@ async function main(argv) {
|
|
|
9488
9871
|
const tag = tags.length > 0 ? ` (${tags.join(" / ")})` : "";
|
|
9489
9872
|
return `Spawned subagent ${subagentId}${tag} for task ${taskId}. Use /agents to track progress.`;
|
|
9490
9873
|
},
|
|
9491
|
-
onAgents: () => {
|
|
9874
|
+
onAgents: (subagentId) => {
|
|
9492
9875
|
const s = multiAgentHost.status();
|
|
9876
|
+
if (subagentId) {
|
|
9877
|
+
const live = s.live.find((a) => a.subagentId === subagentId);
|
|
9878
|
+
const completed = s.completed.filter((r) => r.subagentId === subagentId);
|
|
9879
|
+
const pending = s.pending.filter((p) => p.subagentId === subagentId);
|
|
9880
|
+
if (!live && completed.length === 0 && pending.length === 0) {
|
|
9881
|
+
return `No subagent found with id "${subagentId}".`;
|
|
9882
|
+
}
|
|
9883
|
+
const STATUS_ICON2 = {
|
|
9884
|
+
running: "\u25CF",
|
|
9885
|
+
idle: "\u25CB",
|
|
9886
|
+
stopped: "\u2298"
|
|
9887
|
+
};
|
|
9888
|
+
const lines2 = [color.bold(`Agent ${subagentId.slice(0, 8)}`)];
|
|
9889
|
+
if (live) {
|
|
9890
|
+
lines2.push(` ${STATUS_ICON2[live.status] ?? "?"} status: ${live.status}`);
|
|
9891
|
+
if (live.task) lines2.push(` task: ${live.task}`);
|
|
9892
|
+
}
|
|
9893
|
+
for (const p of pending) {
|
|
9894
|
+
lines2.push(` \xB7 pending: ${p.taskId.slice(0, 8)} \u2192 ${p.description.slice(0, 60)}`);
|
|
9895
|
+
}
|
|
9896
|
+
for (const r of completed) {
|
|
9897
|
+
const fmt = fmtTaskResultLine(r, color);
|
|
9898
|
+
lines2.push(` ${fmt.mark} ${r.taskId.slice(0, 8)} ${fmt.stats}${fmt.tail}`);
|
|
9899
|
+
}
|
|
9900
|
+
if (director) {
|
|
9901
|
+
const snap = director.snapshot();
|
|
9902
|
+
const per = snap.perSubagent?.[subagentId];
|
|
9903
|
+
if (per?.cost) lines2.push(` cost: ${per.cost.toFixed(4)}`);
|
|
9904
|
+
if (per?.iterations) lines2.push(` iterations: ${per.iterations}`);
|
|
9905
|
+
if (per?.toolCalls) lines2.push(` toolCalls: ${per.toolCalls}`);
|
|
9906
|
+
}
|
|
9907
|
+
return lines2.join("\n");
|
|
9908
|
+
}
|
|
9493
9909
|
const lines = [s.summary];
|
|
9494
9910
|
const STATUS_ICON = {
|
|
9495
9911
|
running: "\u25CF",
|
|
@@ -9582,6 +9998,22 @@ async function main(argv) {
|
|
|
9582
9998
|
}
|
|
9583
9999
|
return `Manifest written \u2192 ${p}`;
|
|
9584
10000
|
}
|
|
10001
|
+
if (action === "concurrency") {
|
|
10002
|
+
const current = multiAgentHost.getMaxConcurrent();
|
|
10003
|
+
if (!target) {
|
|
10004
|
+
return `Concurrent-subagent ceiling: ${current}`;
|
|
10005
|
+
}
|
|
10006
|
+
const n = Number.parseInt(target, 10);
|
|
10007
|
+
if (!Number.isFinite(n) || n < 1) {
|
|
10008
|
+
return `Invalid value "${target}". Concurrency must be an integer >= 1.`;
|
|
10009
|
+
}
|
|
10010
|
+
try {
|
|
10011
|
+
multiAgentHost.setMaxConcurrent(n);
|
|
10012
|
+
} catch (err) {
|
|
10013
|
+
return err instanceof Error ? err.message : String(err);
|
|
10014
|
+
}
|
|
10015
|
+
return `Concurrent-subagent ceiling: ${current} \u2192 ${n}`;
|
|
10016
|
+
}
|
|
9585
10017
|
return `Unknown fleet action: ${action}`;
|
|
9586
10018
|
},
|
|
9587
10019
|
onFleetLog: async (subagentId, mode) => {
|
|
@@ -9800,6 +10232,21 @@ Restart WrongStack to load or unload plugin code in this session.`;
|
|
|
9800
10232
|
}
|
|
9801
10233
|
return result.message;
|
|
9802
10234
|
},
|
|
10235
|
+
onMcp: async (args) => {
|
|
10236
|
+
const parsed = parseMcpArgs(args);
|
|
10237
|
+
if (!parsed) {
|
|
10238
|
+
return [
|
|
10239
|
+
"Usage: /mcp [list|add <name>|remove <name>|enable <name>|disable <name>|restart <name>]",
|
|
10240
|
+
"Run `/mcp` without args to see available servers."
|
|
10241
|
+
].join("\n");
|
|
10242
|
+
}
|
|
10243
|
+
return runMcpManagementCommand(parsed, {
|
|
10244
|
+
config,
|
|
10245
|
+
configPath: wpaths.globalConfig,
|
|
10246
|
+
mcpRegistry,
|
|
10247
|
+
allServerPresets: allServers$1()
|
|
10248
|
+
});
|
|
10249
|
+
},
|
|
9803
10250
|
onYolo: (setTo) => {
|
|
9804
10251
|
const policy = container.resolve(TOKENS.PermissionPolicy);
|
|
9805
10252
|
if (setTo !== void 0) {
|
|
@@ -9812,6 +10259,7 @@ Restart WrongStack to load or unload plugin code in this session.`;
|
|
|
9812
10259
|
onAutonomy: (setTo) => {
|
|
9813
10260
|
if (setTo !== void 0) {
|
|
9814
10261
|
autonomyMode = setTo;
|
|
10262
|
+
autonomyModeRef.current = setTo;
|
|
9815
10263
|
return setTo;
|
|
9816
10264
|
}
|
|
9817
10265
|
return autonomyMode;
|
|
@@ -9912,6 +10360,7 @@ Restart WrongStack to load or unload plugin code in this session.`;
|
|
|
9912
10360
|
});
|
|
9913
10361
|
await eternalEngine.prime();
|
|
9914
10362
|
autonomyMode = "eternal";
|
|
10363
|
+
autonomyModeRef.current = "eternal";
|
|
9915
10364
|
renderer.write(
|
|
9916
10365
|
color.red("Eternal mode launching from --eternal flag.") + color.dim(` Goal: ${eternalFlag.slice(0, 80)}${eternalFlag.length > 80 ? "\u2026" : ""}`) + "\n"
|
|
9917
10366
|
);
|