@wrongstack/cli 0.6.0 → 0.6.1
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 +389 -55
- 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) => {
|
|
@@ -2322,10 +2322,10 @@ var theme = { primary: color.amber };
|
|
|
2322
2322
|
async function saveToGlobalConfig(configPath2, provider, model, homeFn = () => process.env.HOME ?? __require("os").homedir()) {
|
|
2323
2323
|
try {
|
|
2324
2324
|
const { atomicWrite: atomicWrite8 } = await import('@wrongstack/core');
|
|
2325
|
-
const
|
|
2325
|
+
const fs20 = await import('fs/promises');
|
|
2326
2326
|
let existing = {};
|
|
2327
2327
|
try {
|
|
2328
|
-
const raw = await
|
|
2328
|
+
const raw = await fs20.readFile(configPath2, "utf8");
|
|
2329
2329
|
existing = JSON.parse(raw);
|
|
2330
2330
|
} catch {
|
|
2331
2331
|
}
|
|
@@ -2679,52 +2679,79 @@ async function detectProjectFacts(root) {
|
|
|
2679
2679
|
}
|
|
2680
2680
|
function renderAgentsTemplate(f) {
|
|
2681
2681
|
const cmd = (s) => s ? `\`${s}\`` : "_TODO_";
|
|
2682
|
+
const hints = f.hints.length > 0 ? `
|
|
2683
|
+
|
|
2684
|
+
> Auto-detected: ${f.hints.join(", ")}` : "";
|
|
2682
2685
|
return `# AGENTS.md
|
|
2683
2686
|
|
|
2684
|
-
|
|
2685
|
-
|
|
2686
|
-
|
|
2687
|
+
> **DO NOT DELETE THIS FILE.** It is loaded into WrongStack's system prompt as
|
|
2688
|
+
> persistent project context. Previous content here may contain decisions,
|
|
2689
|
+
> architecture notes, domain knowledge, or verification history that should be
|
|
2690
|
+
> preserved. Merge additions rather than replacing.
|
|
2687
2691
|
|
|
2688
2692
|
## Project brief
|
|
2689
2693
|
|
|
2690
|
-
- **Purpose:** _What does this project do
|
|
2694
|
+
- **Purpose:** _What does this project do and why does it exist?_
|
|
2691
2695
|
- **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?_
|
|
2696
|
+
- **Runtime / deployment:** _CLI, server, browser, worker, library, package?_${hints}
|
|
2694
2697
|
|
|
2695
2698
|
## How to work safely
|
|
2696
2699
|
|
|
2697
2700
|
- _Project-specific rules the agent should always follow._
|
|
2698
2701
|
- _Files, generated artifacts, migrations, or config the agent should not edit without asking._
|
|
2699
|
-
- _Preferred style or architecture choices
|
|
2702
|
+
- _Preferred style or architecture choices not obvious from the code._
|
|
2703
|
+
- _Known fragile areas or historical bugs that deserve extra caution._
|
|
2700
2704
|
|
|
2701
2705
|
## Commands
|
|
2702
2706
|
|
|
2703
|
-
|
|
2704
|
-
|
|
2705
|
-
|
|
2706
|
-
|
|
2707
|
+
| Command | Script |
|
|
2708
|
+
|---------|--------|
|
|
2709
|
+
| Build | ${cmd(f.build)} |
|
|
2710
|
+
| Test | ${cmd(f.test)} |
|
|
2711
|
+
| Lint | ${cmd(f.lint)} |
|
|
2712
|
+
| Run locally | ${cmd(f.run)} |
|
|
2713
|
+
|
|
2714
|
+
## Key files and entry points
|
|
2715
|
+
|
|
2716
|
+
| File / directory | Role |
|
|
2717
|
+
|---|---|
|
|
2718
|
+
| _src/_ | _Main source entry point(s)_ |
|
|
2719
|
+
| _tests/_ | _Test root or convention_ |
|
|
2720
|
+
| _docs/_ | _Architecture, runbooks, design notes_ |
|
|
2721
|
+
| _scripts/_ | _Automation scripts (CI, release, install, etc.)_ |
|
|
2707
2722
|
|
|
2708
2723
|
## Architecture notes
|
|
2709
2724
|
|
|
2710
2725
|
_Summarize the important modules, data flow, boundaries, and ownership rules.
|
|
2711
|
-
Mention anything a newcomer might misread._
|
|
2726
|
+
Mention anything a newcomer might misread or that looks unusual but is intentional._
|
|
2727
|
+
|
|
2728
|
+
### Dependency layers
|
|
2729
|
+
|
|
2730
|
+
_Describe the key dependency direction or layered structure, e.g.: "core has no
|
|
2731
|
+
runtime deps; cli assembles everything above it."_
|
|
2732
|
+
|
|
2733
|
+
### Extension points
|
|
2734
|
+
|
|
2735
|
+
_Plugin, MCP, extension hooks, custom tools \u2014 what's wired up and how._
|
|
2712
2736
|
|
|
2713
2737
|
## Domain knowledge
|
|
2714
2738
|
|
|
2715
2739
|
_Business rules, acronyms, invariants, external services, and notes where the
|
|
2716
|
-
code looks unusual but is intentional.
|
|
2740
|
+
code looks unusual but is intentional. E.g.: "IDs are ULIDs, not UUIDs", "the
|
|
2741
|
+
\`draft\` flag means uncommitted billing metadata", "MCP servers are restarted
|
|
2742
|
+
on disconnect with exponential backoff, up to 3 attempts"._
|
|
2717
2743
|
|
|
2718
2744
|
## Verification checklist
|
|
2719
2745
|
|
|
2720
2746
|
- _What should be run after code changes?_
|
|
2721
2747
|
- _What manual smoke test proves the common path still works?_
|
|
2722
2748
|
- _What failure modes deserve extra attention?_
|
|
2749
|
+
- _Any known flaky tests or environment-dependent behavior?_
|
|
2723
2750
|
|
|
2724
2751
|
## Useful pointers
|
|
2725
2752
|
|
|
2726
|
-
- _Docs, dashboards, runbooks, issue trackers, design notes,
|
|
2727
|
-
`;
|
|
2753
|
+
- _Docs, dashboards, runbooks, issue trackers, design notes, owner contacts._
|
|
2754
|
+
- _Related projects or repositories._`;
|
|
2728
2755
|
}
|
|
2729
2756
|
function countTurnPairs(messages) {
|
|
2730
2757
|
let count = 0;
|
|
@@ -3128,7 +3155,7 @@ function buildStatsCommand(opts) {
|
|
|
3128
3155
|
function buildFleetCommand(opts) {
|
|
3129
3156
|
return {
|
|
3130
3157
|
name: "fleet",
|
|
3131
|
-
description: "Inspect or control the subagent fleet: /fleet [status|usage|kill <id>|manifest|retry [taskId]|log <id>|stream on|off|help]",
|
|
3158
|
+
description: "Inspect or control the subagent fleet: /fleet [status|usage|kill <id>|manifest|concurrency [N]|retry [taskId]|log <id>|stream on|off|help]",
|
|
3132
3159
|
help: [
|
|
3133
3160
|
"Usage:",
|
|
3134
3161
|
" /fleet Show fleet status (alias for /fleet status).",
|
|
@@ -3136,6 +3163,8 @@ function buildFleetCommand(opts) {
|
|
|
3136
3163
|
" /fleet usage Per-subagent runtime cost.",
|
|
3137
3164
|
" /fleet kill <id> Terminate a running subagent.",
|
|
3138
3165
|
" /fleet manifest Print the director manifest.",
|
|
3166
|
+
" /fleet concurrency Show the current concurrent-subagent ceiling.",
|
|
3167
|
+
" /fleet concurrency N Set the ceiling to N (>= 1). Lowering does not preempt running tasks.",
|
|
3139
3168
|
" /fleet retry List interrupted tasks from the last run.",
|
|
3140
3169
|
" /fleet retry <taskId> Re-spawn the matching subagent and re-assign the task.",
|
|
3141
3170
|
" /fleet retry all Re-assign every interrupted task at once.",
|
|
@@ -3161,6 +3190,9 @@ function buildFleetCommand(opts) {
|
|
|
3161
3190
|
if (!target) return { message: "Usage: /fleet kill <subagent-id>" };
|
|
3162
3191
|
return { message: await opts.onFleet("kill", target) };
|
|
3163
3192
|
}
|
|
3193
|
+
case "concurrency": {
|
|
3194
|
+
return { message: await opts.onFleet("concurrency", target) };
|
|
3195
|
+
}
|
|
3164
3196
|
case "retry": {
|
|
3165
3197
|
if (!opts.onFleetRetry) {
|
|
3166
3198
|
return { message: "Retry is only available when director mode is active." };
|
|
@@ -3298,20 +3330,10 @@ function buildHelpCommand(opts) {
|
|
|
3298
3330
|
function buildInitCommand(opts) {
|
|
3299
3331
|
return {
|
|
3300
3332
|
name: "init",
|
|
3301
|
-
description: "Create .wrongstack/AGENTS.md project context for the system prompt.",
|
|
3302
|
-
async run(
|
|
3303
|
-
const force = args.trim() === "--force";
|
|
3333
|
+
description: "Create or update .wrongstack/AGENTS.md project context for the system prompt.",
|
|
3334
|
+
async run(_args, ctx) {
|
|
3304
3335
|
const dir = path23.join(ctx.projectRoot, ".wrongstack");
|
|
3305
3336
|
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
3337
|
const detected = await detectProjectFacts(ctx.projectRoot);
|
|
3316
3338
|
const body = renderAgentsTemplate(detected);
|
|
3317
3339
|
await fsp2.mkdir(dir, { recursive: true });
|
|
@@ -3332,6 +3354,223 @@ No project type auto-detected. Edit the file with project context and instructio
|
|
|
3332
3354
|
}
|
|
3333
3355
|
};
|
|
3334
3356
|
}
|
|
3357
|
+
function parseMcpArgs(args) {
|
|
3358
|
+
const trimmed = args.trim();
|
|
3359
|
+
if (!trimmed || trimmed === "list") return { action: "list", name: "" };
|
|
3360
|
+
const parts = trimmed.split(/\s+/);
|
|
3361
|
+
const action = parts[0];
|
|
3362
|
+
const name = parts[1] ?? "";
|
|
3363
|
+
const enable = parts.includes("--enable") || parts.includes("-e");
|
|
3364
|
+
switch (action) {
|
|
3365
|
+
case "add":
|
|
3366
|
+
return name ? { action: "add", name, enable } : null;
|
|
3367
|
+
case "remove":
|
|
3368
|
+
return name ? { action: "remove", name } : null;
|
|
3369
|
+
case "enable":
|
|
3370
|
+
return name ? { action: "enable", name } : null;
|
|
3371
|
+
case "disable":
|
|
3372
|
+
return name ? { action: "disable", name } : null;
|
|
3373
|
+
case "restart":
|
|
3374
|
+
return name ? { action: "restart", name } : null;
|
|
3375
|
+
default:
|
|
3376
|
+
return null;
|
|
3377
|
+
}
|
|
3378
|
+
}
|
|
3379
|
+
async function runMcpManagementCommand(parsed, deps) {
|
|
3380
|
+
const { config, configPath: configPath2, mcpRegistry, allServerPresets } = deps;
|
|
3381
|
+
const configured = config.mcpServers ?? {};
|
|
3382
|
+
switch (parsed.action) {
|
|
3383
|
+
case "list":
|
|
3384
|
+
return renderList(configured, mcpRegistry, allServerPresets);
|
|
3385
|
+
case "add":
|
|
3386
|
+
return runAdd(parsed.name, parsed.enable ?? false, configured, configPath2, allServerPresets);
|
|
3387
|
+
case "remove":
|
|
3388
|
+
return runRemove(parsed.name, configured, configPath2, mcpRegistry);
|
|
3389
|
+
case "enable":
|
|
3390
|
+
return runEnable(parsed.name, configured, configPath2, mcpRegistry);
|
|
3391
|
+
case "disable":
|
|
3392
|
+
return runDisable(parsed.name, configured, configPath2, mcpRegistry);
|
|
3393
|
+
case "restart":
|
|
3394
|
+
return runRestart(parsed.name, mcpRegistry);
|
|
3395
|
+
}
|
|
3396
|
+
}
|
|
3397
|
+
function renderList(configured, mcpRegistry, all) {
|
|
3398
|
+
const lines = [];
|
|
3399
|
+
const liveStatus = mcpRegistry.list();
|
|
3400
|
+
const liveMap = new Map(liveStatus.map((s) => [s.name, s]));
|
|
3401
|
+
const configuredNames = new Set(Object.keys(configured));
|
|
3402
|
+
if (configuredNames.size > 0) {
|
|
3403
|
+
lines.push(color.bold("Configured servers:"));
|
|
3404
|
+
for (const [name, cfg] of Object.entries(configured)) {
|
|
3405
|
+
const live = liveMap.get(name);
|
|
3406
|
+
const toolCount = live ? color.dim(` (${live.toolCount} tools)`) : "";
|
|
3407
|
+
const enabled = cfg.enabled === false ? `${color.dim("disabled")} ` : `${color.green("\u25CF enabled")} `;
|
|
3408
|
+
const stateStr = live ? stateBadge(live.state) : color.dim("\u25CB not running");
|
|
3409
|
+
lines.push(` ${color.bold(name)} ${enabled}${stateStr}${toolCount}`);
|
|
3410
|
+
if (cfg.description) lines.push(` ${color.dim(cfg.description)}`);
|
|
3411
|
+
}
|
|
3412
|
+
lines.push("");
|
|
3413
|
+
}
|
|
3414
|
+
const unconfigured = Object.entries(all).filter(([n]) => !configuredNames.has(n));
|
|
3415
|
+
lines.push(color.bold("Available presets (run `/mcp add <name> --enable` to enable):"));
|
|
3416
|
+
if (unconfigured.length === 0) {
|
|
3417
|
+
lines.push(` ${color.dim("All presets are already configured.")}`);
|
|
3418
|
+
} else {
|
|
3419
|
+
for (const [name, cfg] of unconfigured) {
|
|
3420
|
+
const warn = cfg.permission === "deny" ? color.red(" \u26A0") : "";
|
|
3421
|
+
lines.push(` ${color.bold(name)} ${cfg.description ?? cfg.transport}${warn}`);
|
|
3422
|
+
}
|
|
3423
|
+
}
|
|
3424
|
+
lines.push("");
|
|
3425
|
+
lines.push(color.dim(" /mcp add <name> [--enable] /mcp remove <name>"));
|
|
3426
|
+
lines.push(color.dim(" /mcp enable <name> /mcp disable <name>"));
|
|
3427
|
+
lines.push(color.dim(" /mcp restart <name> (runtime restart)"));
|
|
3428
|
+
return lines.join("\n");
|
|
3429
|
+
}
|
|
3430
|
+
async function runAdd(name, enable, configured, configPath2, all) {
|
|
3431
|
+
const preset = all[name];
|
|
3432
|
+
if (!preset) {
|
|
3433
|
+
const known = Object.keys(all).join(", ");
|
|
3434
|
+
return `Unknown server "${name}". Available: ${known}`;
|
|
3435
|
+
}
|
|
3436
|
+
if (configured[name]) {
|
|
3437
|
+
const full2 = await readConfig(configPath2);
|
|
3438
|
+
full2.mcpServers = {
|
|
3439
|
+
...full2.mcpServers ?? {},
|
|
3440
|
+
[name]: { ...preset, ...configured[name], enabled: enable }
|
|
3441
|
+
};
|
|
3442
|
+
await writeConfig(configPath2, full2);
|
|
3443
|
+
return `${color.green("Updated")} "${name}" (${enable ? "enabled" : "disabled"}). Config written.`;
|
|
3444
|
+
}
|
|
3445
|
+
const full = await readConfig(configPath2);
|
|
3446
|
+
const mcpServers = { ...full.mcpServers ?? {}, [name]: { ...preset, enabled: enable } };
|
|
3447
|
+
full.mcpServers = mcpServers;
|
|
3448
|
+
await writeConfig(configPath2, full);
|
|
3449
|
+
const verb = enable ? "Enabled" : "Added (disabled \u2014 /mcp enable to start)";
|
|
3450
|
+
return `${color.green(verb)} "${name}" (${preset.transport}). Config written to ${configPath2}.`;
|
|
3451
|
+
}
|
|
3452
|
+
async function runRemove(name, configured, configPath2, mcpRegistry) {
|
|
3453
|
+
if (!configured[name]) return `Server "${name}" is not in config.`;
|
|
3454
|
+
await mcpRegistry.stop(name).catch(() => {
|
|
3455
|
+
});
|
|
3456
|
+
const full = await readConfig(configPath2);
|
|
3457
|
+
const mcpServers = { ...full.mcpServers ?? {} };
|
|
3458
|
+
delete mcpServers[name];
|
|
3459
|
+
full.mcpServers = mcpServers;
|
|
3460
|
+
await writeConfig(configPath2, full);
|
|
3461
|
+
return `${color.yellow("Removed")} "${name}" from config.`;
|
|
3462
|
+
}
|
|
3463
|
+
async function runEnable(name, configured, configPath2, mcpRegistry) {
|
|
3464
|
+
const cfg = configured[name];
|
|
3465
|
+
if (!cfg) return `Server "${name}" is not in config. Run \`/mcp add ${name} --enable\` first.`;
|
|
3466
|
+
if (cfg.enabled !== false) {
|
|
3467
|
+
try {
|
|
3468
|
+
await mcpRegistry.restart(name);
|
|
3469
|
+
return `${color.green("\u25CF")} "${name}" is already enabled and running.`;
|
|
3470
|
+
} catch {
|
|
3471
|
+
await mcpRegistry.start({ ...cfg, enabled: true });
|
|
3472
|
+
return `${color.green("Enabled")} "${name}" and started.`;
|
|
3473
|
+
}
|
|
3474
|
+
}
|
|
3475
|
+
const full = await readConfig(configPath2);
|
|
3476
|
+
const mcpServers = { ...full.mcpServers ?? {} };
|
|
3477
|
+
mcpServers[name] = { ...mcpServers[name], enabled: true };
|
|
3478
|
+
full.mcpServers = mcpServers;
|
|
3479
|
+
await writeConfig(configPath2, full);
|
|
3480
|
+
try {
|
|
3481
|
+
await mcpRegistry.restart(name);
|
|
3482
|
+
} catch {
|
|
3483
|
+
await mcpRegistry.start({ ...cfg, enabled: true });
|
|
3484
|
+
}
|
|
3485
|
+
return `${color.green("Enabled")} "${name}" and started.`;
|
|
3486
|
+
}
|
|
3487
|
+
async function runDisable(name, configured, configPath2, mcpRegistry) {
|
|
3488
|
+
const cfg = configured[name];
|
|
3489
|
+
if (!cfg) return `Server "${name}" is not in config.`;
|
|
3490
|
+
await mcpRegistry.stop(name).catch(() => {
|
|
3491
|
+
});
|
|
3492
|
+
const full = await readConfig(configPath2);
|
|
3493
|
+
const mcpServers = { ...full.mcpServers ?? {} };
|
|
3494
|
+
mcpServers[name] = { ...mcpServers[name], enabled: false };
|
|
3495
|
+
full.mcpServers = mcpServers;
|
|
3496
|
+
await writeConfig(configPath2, full);
|
|
3497
|
+
return `${color.yellow("Disabled")} "${name}" and stopped.`;
|
|
3498
|
+
}
|
|
3499
|
+
async function runRestart(name, mcpRegistry) {
|
|
3500
|
+
const live = mcpRegistry.list();
|
|
3501
|
+
if (!live.find((s) => s.name === name)) {
|
|
3502
|
+
return `Server "${name}" is not currently running. Add it with \`/mcp add ${name} --enable\`.`;
|
|
3503
|
+
}
|
|
3504
|
+
try {
|
|
3505
|
+
await mcpRegistry.restart(name);
|
|
3506
|
+
return `${color.green("\u2713")} Restarted "${name}".`;
|
|
3507
|
+
} catch (err) {
|
|
3508
|
+
return `${color.red("\u2717")} Failed to restart "${name}": ${err instanceof Error ? err.message : String(err)}`;
|
|
3509
|
+
}
|
|
3510
|
+
}
|
|
3511
|
+
function stateBadge(state) {
|
|
3512
|
+
switch (state) {
|
|
3513
|
+
case "connected":
|
|
3514
|
+
return color.green("\u25CF connected");
|
|
3515
|
+
case "connecting":
|
|
3516
|
+
return color.cyan("\u25D0 connecting");
|
|
3517
|
+
case "reconnecting":
|
|
3518
|
+
return color.cyan("\u25D1 reconnecting");
|
|
3519
|
+
case "disconnected":
|
|
3520
|
+
return color.dim("\u25CB disconnected");
|
|
3521
|
+
case "failed":
|
|
3522
|
+
return color.red("\u2717 failed");
|
|
3523
|
+
default:
|
|
3524
|
+
return color.dim(state);
|
|
3525
|
+
}
|
|
3526
|
+
}
|
|
3527
|
+
async function readConfig(path24) {
|
|
3528
|
+
try {
|
|
3529
|
+
return JSON.parse(await fsp2.readFile(path24, "utf8"));
|
|
3530
|
+
} catch {
|
|
3531
|
+
return {};
|
|
3532
|
+
}
|
|
3533
|
+
}
|
|
3534
|
+
async function writeConfig(path24, cfg) {
|
|
3535
|
+
const raw = JSON.stringify(cfg, null, 2);
|
|
3536
|
+
const tmp = path24 + ".tmp";
|
|
3537
|
+
await fsp2.writeFile(tmp, raw, "utf8");
|
|
3538
|
+
await fsp2.rename(tmp, path24);
|
|
3539
|
+
}
|
|
3540
|
+
|
|
3541
|
+
// src/slash-commands/mcp.ts
|
|
3542
|
+
function buildMcpSlashCommand(opts) {
|
|
3543
|
+
return {
|
|
3544
|
+
name: "mcp",
|
|
3545
|
+
description: "Manage MCP servers: /mcp [list|add <name>|remove <name>|enable <name>|disable <name>|restart <name>]",
|
|
3546
|
+
aliases: ["mcp-servers"],
|
|
3547
|
+
argsHint: "[list|add <name>|remove <name>|enable <name>|disable <name>|restart <name>]",
|
|
3548
|
+
help: [
|
|
3549
|
+
"Usage:",
|
|
3550
|
+
" /mcp List available and configured servers.",
|
|
3551
|
+
" /mcp list Same.",
|
|
3552
|
+
" /mcp add <name> Add server preset to config (disabled).",
|
|
3553
|
+
" /mcp add <name> --enable Add and immediately enable.",
|
|
3554
|
+
" /mcp remove <name> Remove server from config.",
|
|
3555
|
+
" /mcp enable <name> Enable server in config + start it.",
|
|
3556
|
+
" /mcp disable <name> Disable server in config + stop it.",
|
|
3557
|
+
" /mcp restart <name> Stop and restart a running server (REPL only).",
|
|
3558
|
+
"",
|
|
3559
|
+
"Examples:",
|
|
3560
|
+
" /mcp",
|
|
3561
|
+
" /mcp add filesystem --enable",
|
|
3562
|
+
" /mcp enable github",
|
|
3563
|
+
" /mcp restart brave-search"
|
|
3564
|
+
].join("\n"),
|
|
3565
|
+
async run(args) {
|
|
3566
|
+
if (!opts.onMcp) {
|
|
3567
|
+
return { message: "MCP management is not available in this session." };
|
|
3568
|
+
}
|
|
3569
|
+
const result = await opts.onMcp(args.trim());
|
|
3570
|
+
return { message: result };
|
|
3571
|
+
}
|
|
3572
|
+
};
|
|
3573
|
+
}
|
|
3335
3574
|
|
|
3336
3575
|
// src/slash-commands/memory.ts
|
|
3337
3576
|
function buildMemoryCommand(opts) {
|
|
@@ -4676,6 +4915,7 @@ function buildBuiltinSlashCommands(opts) {
|
|
|
4676
4915
|
buildSkillUpdateCommand(opts),
|
|
4677
4916
|
buildSkillUninstallCommand(opts),
|
|
4678
4917
|
buildPluginCommand(opts),
|
|
4918
|
+
buildMcpSlashCommand(opts),
|
|
4679
4919
|
buildDiagCommand(opts),
|
|
4680
4920
|
buildStatsCommand(opts),
|
|
4681
4921
|
buildSpawnCommand(opts),
|
|
@@ -6138,12 +6378,8 @@ var initCmd = async (_args, deps) => {
|
|
|
6138
6378
|
await atomicWrite(deps.paths.globalConfig, JSON.stringify(encrypted, null, 2));
|
|
6139
6379
|
await fsp2.mkdir(path23.join(deps.projectRoot, ".wrongstack"), { recursive: true });
|
|
6140
6380
|
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
|
-
}
|
|
6381
|
+
const projectFacts = await detectProjectFacts(deps.projectRoot);
|
|
6382
|
+
await atomicWrite(agentsFile, renderAgentsTemplate(projectFacts));
|
|
6147
6383
|
deps.renderer.writeInfo(`Wrote ${deps.paths.globalConfig}`);
|
|
6148
6384
|
deps.renderer.writeInfo(`Project state lives in ${deps.paths.projectDir}`);
|
|
6149
6385
|
deps.renderer.writeInfo('Try: wstack "<task>" or wstack');
|
|
@@ -6179,7 +6415,7 @@ var mcpCmd = async (args, deps) => {
|
|
|
6179
6415
|
return removeMcpServer(name, deps);
|
|
6180
6416
|
}
|
|
6181
6417
|
if (sub === "restart") {
|
|
6182
|
-
deps.renderer.writeWarning("mcp restart is only available in REPL mode.");
|
|
6418
|
+
deps.renderer.writeWarning("mcp restart is only available in REPL mode. Use /mcp restart instead.");
|
|
6183
6419
|
return 0;
|
|
6184
6420
|
}
|
|
6185
6421
|
deps.renderer.writeError(`Unknown mcp subcommand: ${sub}`);
|
|
@@ -6354,7 +6590,7 @@ function renderConfiguredPlugins(config) {
|
|
|
6354
6590
|
return ` ${`${name}${suffix}`.padEnd(44)} ${enabled}`;
|
|
6355
6591
|
}).join("\n");
|
|
6356
6592
|
}
|
|
6357
|
-
async function
|
|
6593
|
+
async function readConfig2(file) {
|
|
6358
6594
|
try {
|
|
6359
6595
|
return JSON.parse(await fsp2.readFile(file, "utf8"));
|
|
6360
6596
|
} catch {
|
|
@@ -6373,7 +6609,7 @@ function officialPluginState(config, spec) {
|
|
|
6373
6609
|
return typeof match === "object" && match.enabled === false ? "disabled" : "enabled";
|
|
6374
6610
|
}
|
|
6375
6611
|
async function upsertPlugin(spec, opts, deps, verb) {
|
|
6376
|
-
const existing = await
|
|
6612
|
+
const existing = await readConfig2(deps.configPath);
|
|
6377
6613
|
const plugins = Array.isArray(existing.plugins) ? existing.plugins : [];
|
|
6378
6614
|
const idx = plugins.findIndex((p) => pluginName(p) === spec);
|
|
6379
6615
|
const nextEntry = pluginEntry(spec, opts.enabled);
|
|
@@ -6396,7 +6632,7 @@ async function upsertPlugin(spec, opts, deps, verb) {
|
|
|
6396
6632
|
};
|
|
6397
6633
|
}
|
|
6398
6634
|
async function removePlugin(spec, deps) {
|
|
6399
|
-
const existing = await
|
|
6635
|
+
const existing = await readConfig2(deps.configPath);
|
|
6400
6636
|
const plugins = Array.isArray(existing.plugins) ? existing.plugins : [];
|
|
6401
6637
|
const next = plugins.filter((p) => pluginName(p) !== spec);
|
|
6402
6638
|
if (next.length === plugins.length) {
|
|
@@ -6932,12 +7168,23 @@ function parseRewindFlags(args) {
|
|
|
6932
7168
|
}
|
|
6933
7169
|
return flags;
|
|
6934
7170
|
}
|
|
7171
|
+
function findSessionId(args) {
|
|
7172
|
+
for (let i = 0; i < args.length; i++) {
|
|
7173
|
+
const a = args[i];
|
|
7174
|
+
if (a === "--last" || a === "--to") {
|
|
7175
|
+
i++;
|
|
7176
|
+
continue;
|
|
7177
|
+
}
|
|
7178
|
+
if (!a.startsWith("--")) return a;
|
|
7179
|
+
}
|
|
7180
|
+
return void 0;
|
|
7181
|
+
}
|
|
6935
7182
|
var rewindCmd = async (args, deps) => {
|
|
6936
7183
|
const flags = parseRewindFlags(args);
|
|
6937
7184
|
const wpaths = resolveWstackPaths({ projectRoot: deps.projectRoot });
|
|
6938
7185
|
const sessionsDir = path23.join(wpaths.globalRoot, "sessions");
|
|
6939
7186
|
const rewind = new DefaultSessionRewinder(sessionsDir);
|
|
6940
|
-
let sessionId = args
|
|
7187
|
+
let sessionId = findSessionId(args);
|
|
6941
7188
|
if (!sessionId) {
|
|
6942
7189
|
if (!deps.sessionStore) {
|
|
6943
7190
|
deps.renderer.writeError("No session store available.");
|
|
@@ -7146,22 +7393,22 @@ function fmtDuration(ms) {
|
|
|
7146
7393
|
const remMin = m - h * 60;
|
|
7147
7394
|
return `${h}h${remMin}m`;
|
|
7148
7395
|
}
|
|
7149
|
-
function fmtTaskResultLine(r,
|
|
7396
|
+
function fmtTaskResultLine(r, color35) {
|
|
7150
7397
|
const stats = `${r.iterations}it ${r.toolCalls}tc ${fmtDuration(r.durationMs)}`;
|
|
7151
7398
|
const errMsg = typeof r.error === "string" ? r.error : r.error?.message;
|
|
7152
7399
|
const errKind = typeof r.error === "object" ? r.error?.kind : void 0;
|
|
7153
7400
|
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}${
|
|
7401
|
+
const errKindChip = errKind ? color35.dim(` [${errKind}]`) : "";
|
|
7402
|
+
const errSnip = errMsg || errKind ? `${errKindChip}${color35.dim(errTail)}` : "";
|
|
7156
7403
|
switch (r.status) {
|
|
7157
7404
|
case "success":
|
|
7158
|
-
return { mark:
|
|
7405
|
+
return { mark: color35.green("\u2713"), stats, tail: "" };
|
|
7159
7406
|
case "timeout":
|
|
7160
|
-
return { mark:
|
|
7407
|
+
return { mark: color35.yellow("\u23F1"), stats: `${color35.yellow("timeout")} ${stats}`, tail: errSnip };
|
|
7161
7408
|
case "stopped":
|
|
7162
|
-
return { mark:
|
|
7409
|
+
return { mark: color35.dim("\u2298"), stats: `${color35.dim("stopped")} ${stats}`, tail: errSnip };
|
|
7163
7410
|
case "failed":
|
|
7164
|
-
return { mark:
|
|
7411
|
+
return { mark: color35.red("\u2717"), stats: `${color35.red("failed")} ${stats}`, tail: errSnip };
|
|
7165
7412
|
}
|
|
7166
7413
|
}
|
|
7167
7414
|
|
|
@@ -8107,7 +8354,7 @@ var MultiAgentHost = class {
|
|
|
8107
8354
|
const coordinatorConfig = {
|
|
8108
8355
|
coordinatorId: randomUUID(),
|
|
8109
8356
|
doneCondition: { type: "all_tasks_done" },
|
|
8110
|
-
maxConcurrent:
|
|
8357
|
+
maxConcurrent: this.opts.maxConcurrent ?? 4
|
|
8111
8358
|
};
|
|
8112
8359
|
const defaultScratchpad = this.opts.sharedScratchpadPath || (this.opts.sessionsRoot && this.opts.directorRunId ? path23.join(this.opts.sessionsRoot, this.opts.directorRunId, "shared") : void 0);
|
|
8113
8360
|
this.director = new Director({
|
|
@@ -8484,6 +8731,37 @@ var MultiAgentHost = class {
|
|
|
8484
8731
|
await this.getCoordinator().stopAll();
|
|
8485
8732
|
}
|
|
8486
8733
|
}
|
|
8734
|
+
/**
|
|
8735
|
+
* Current effective concurrent-subagent ceiling. Reads the live
|
|
8736
|
+
* coordinator config when the director is built; otherwise falls back
|
|
8737
|
+
* to the constructor option (or the default of 4 that buildDirector
|
|
8738
|
+
* will apply on first /spawn).
|
|
8739
|
+
*/
|
|
8740
|
+
getMaxConcurrent() {
|
|
8741
|
+
if (this.director) {
|
|
8742
|
+
return this.getCoordinator().config.maxConcurrent ?? 4;
|
|
8743
|
+
}
|
|
8744
|
+
return this.opts.maxConcurrent ?? 4;
|
|
8745
|
+
}
|
|
8746
|
+
/**
|
|
8747
|
+
* Change the concurrent-subagent ceiling at runtime. Updates the
|
|
8748
|
+
* constructor option (so lazy-built director picks it up) and, if the
|
|
8749
|
+
* coordinator already exists, mutates its live config + triggers a
|
|
8750
|
+
* dispatch pass so newly-allowed slots fill immediately.
|
|
8751
|
+
*
|
|
8752
|
+
* Throws on non-positive values; the caller is expected to validate
|
|
8753
|
+
* user input first.
|
|
8754
|
+
*/
|
|
8755
|
+
setMaxConcurrent(n) {
|
|
8756
|
+
if (!Number.isFinite(n) || n < 1) {
|
|
8757
|
+
throw new Error(`maxConcurrent must be a finite integer >= 1, got ${n}`);
|
|
8758
|
+
}
|
|
8759
|
+
const v = Math.floor(n);
|
|
8760
|
+
this.opts.maxConcurrent = v;
|
|
8761
|
+
if (this.director) {
|
|
8762
|
+
this.getCoordinator().setMaxConcurrent(v);
|
|
8763
|
+
}
|
|
8764
|
+
}
|
|
8487
8765
|
};
|
|
8488
8766
|
function makePromptDelegate(reader) {
|
|
8489
8767
|
return async (tool, input, suggestedPattern) => {
|
|
@@ -9186,6 +9464,8 @@ async function main(argv) {
|
|
|
9186
9464
|
const memoryStore = container.resolve(TOKENS.MemoryStore);
|
|
9187
9465
|
const skillLoader = container.resolve(TOKENS.SkillLoader);
|
|
9188
9466
|
const sessionRef = {};
|
|
9467
|
+
const autonomyModeRef = { current: "off" };
|
|
9468
|
+
const goalPathForPrompt = path23.join(projectRoot, ".wrongstack", "goal.json");
|
|
9189
9469
|
container.bind(
|
|
9190
9470
|
TOKENS.SystemPromptBuilder,
|
|
9191
9471
|
() => new DefaultSystemPromptBuilder({
|
|
@@ -9195,7 +9475,17 @@ async function main(argv) {
|
|
|
9195
9475
|
modeId,
|
|
9196
9476
|
modePrompt,
|
|
9197
9477
|
modelCapabilities,
|
|
9198
|
-
planPath: () => sessionRef.current ? path23.join(wpaths.projectSessions, `${sessionRef.current.id}.plan.json`) : void 0
|
|
9478
|
+
planPath: () => sessionRef.current ? path23.join(wpaths.projectSessions, `${sessionRef.current.id}.plan.json`) : void 0,
|
|
9479
|
+
contributors: [
|
|
9480
|
+
// Injects the ETERNAL AUTONOMY block when the user has activated
|
|
9481
|
+
// `/autonomy eternal`. Without this, the per-iteration directive
|
|
9482
|
+
// is the only place the model sees the rules — compaction can
|
|
9483
|
+
// drop it and the model forgets it's in autonomy mode.
|
|
9484
|
+
makeAutonomyPromptContributor({
|
|
9485
|
+
goalPath: goalPathForPrompt,
|
|
9486
|
+
enabled: () => autonomyModeRef.current === "eternal"
|
|
9487
|
+
})
|
|
9488
|
+
]
|
|
9199
9489
|
})
|
|
9200
9490
|
);
|
|
9201
9491
|
const toolRegistry = new ToolRegistry();
|
|
@@ -9368,6 +9658,9 @@ async function main(argv) {
|
|
|
9368
9658
|
}
|
|
9369
9659
|
};
|
|
9370
9660
|
const directorMode = flags["director"] === true || typeof flags["resume"] === "string";
|
|
9661
|
+
const maxConcurrentFromFlag = typeof flags["max-concurrent"] === "string" ? Number.parseInt(flags["max-concurrent"], 10) : void 0;
|
|
9662
|
+
const maxConcurrentFromEnv = typeof process.env["WRONGSTACK_MAX_CONCURRENT"] === "string" ? Number.parseInt(process.env["WRONGSTACK_MAX_CONCURRENT"], 10) : void 0;
|
|
9663
|
+
const maxConcurrent = Number.isFinite(maxConcurrentFromFlag) && maxConcurrentFromFlag > 0 ? maxConcurrentFromFlag : Number.isFinite(maxConcurrentFromEnv) && maxConcurrentFromEnv > 0 ? maxConcurrentFromEnv : void 0;
|
|
9371
9664
|
let director = null;
|
|
9372
9665
|
let autonomyMode = "off";
|
|
9373
9666
|
let eternalEngine = null;
|
|
@@ -9408,7 +9701,8 @@ async function main(argv) {
|
|
|
9408
9701
|
directorRunId: session.id,
|
|
9409
9702
|
fleetRoot: fleetRootForPromotion,
|
|
9410
9703
|
stateCheckpointPath,
|
|
9411
|
-
sessionWriter: session
|
|
9704
|
+
sessionWriter: session,
|
|
9705
|
+
maxConcurrent
|
|
9412
9706
|
}
|
|
9413
9707
|
);
|
|
9414
9708
|
toolRegistry.register(
|
|
@@ -9423,6 +9717,13 @@ async function main(argv) {
|
|
|
9423
9717
|
directorRunId: session.id
|
|
9424
9718
|
})
|
|
9425
9719
|
);
|
|
9720
|
+
toolRegistry.register(
|
|
9721
|
+
createMcpControlTool({
|
|
9722
|
+
getConfig: () => configStore.get(),
|
|
9723
|
+
configPath: wpaths.globalConfig,
|
|
9724
|
+
registry: mcpRegistry
|
|
9725
|
+
})
|
|
9726
|
+
);
|
|
9426
9727
|
if (directorMode) {
|
|
9427
9728
|
director = await multiAgentHost.ensureDirector();
|
|
9428
9729
|
if (director) {
|
|
@@ -9582,6 +9883,22 @@ async function main(argv) {
|
|
|
9582
9883
|
}
|
|
9583
9884
|
return `Manifest written \u2192 ${p}`;
|
|
9584
9885
|
}
|
|
9886
|
+
if (action === "concurrency") {
|
|
9887
|
+
const current = multiAgentHost.getMaxConcurrent();
|
|
9888
|
+
if (!target) {
|
|
9889
|
+
return `Concurrent-subagent ceiling: ${current}`;
|
|
9890
|
+
}
|
|
9891
|
+
const n = Number.parseInt(target, 10);
|
|
9892
|
+
if (!Number.isFinite(n) || n < 1) {
|
|
9893
|
+
return `Invalid value "${target}". Concurrency must be an integer >= 1.`;
|
|
9894
|
+
}
|
|
9895
|
+
try {
|
|
9896
|
+
multiAgentHost.setMaxConcurrent(n);
|
|
9897
|
+
} catch (err) {
|
|
9898
|
+
return err instanceof Error ? err.message : String(err);
|
|
9899
|
+
}
|
|
9900
|
+
return `Concurrent-subagent ceiling: ${current} \u2192 ${n}`;
|
|
9901
|
+
}
|
|
9585
9902
|
return `Unknown fleet action: ${action}`;
|
|
9586
9903
|
},
|
|
9587
9904
|
onFleetLog: async (subagentId, mode) => {
|
|
@@ -9800,6 +10117,21 @@ Restart WrongStack to load or unload plugin code in this session.`;
|
|
|
9800
10117
|
}
|
|
9801
10118
|
return result.message;
|
|
9802
10119
|
},
|
|
10120
|
+
onMcp: async (args) => {
|
|
10121
|
+
const parsed = parseMcpArgs(args);
|
|
10122
|
+
if (!parsed) {
|
|
10123
|
+
return [
|
|
10124
|
+
"Usage: /mcp [list|add <name>|remove <name>|enable <name>|disable <name>|restart <name>]",
|
|
10125
|
+
"Run `/mcp` without args to see available servers."
|
|
10126
|
+
].join("\n");
|
|
10127
|
+
}
|
|
10128
|
+
return runMcpManagementCommand(parsed, {
|
|
10129
|
+
config,
|
|
10130
|
+
configPath: wpaths.globalConfig,
|
|
10131
|
+
mcpRegistry,
|
|
10132
|
+
allServerPresets: allServers$1()
|
|
10133
|
+
});
|
|
10134
|
+
},
|
|
9803
10135
|
onYolo: (setTo) => {
|
|
9804
10136
|
const policy = container.resolve(TOKENS.PermissionPolicy);
|
|
9805
10137
|
if (setTo !== void 0) {
|
|
@@ -9812,6 +10144,7 @@ Restart WrongStack to load or unload plugin code in this session.`;
|
|
|
9812
10144
|
onAutonomy: (setTo) => {
|
|
9813
10145
|
if (setTo !== void 0) {
|
|
9814
10146
|
autonomyMode = setTo;
|
|
10147
|
+
autonomyModeRef.current = setTo;
|
|
9815
10148
|
return setTo;
|
|
9816
10149
|
}
|
|
9817
10150
|
return autonomyMode;
|
|
@@ -9912,6 +10245,7 @@ Restart WrongStack to load or unload plugin code in this session.`;
|
|
|
9912
10245
|
});
|
|
9913
10246
|
await eternalEngine.prime();
|
|
9914
10247
|
autonomyMode = "eternal";
|
|
10248
|
+
autonomyModeRef.current = "eternal";
|
|
9915
10249
|
renderer.write(
|
|
9916
10250
|
color.red("Eternal mode launching from --eternal flag.") + color.dim(` Goal: ${eternalFlag.slice(0, 80)}${eternalFlag.length > 80 ? "\u2026" : ""}`) + "\n"
|
|
9917
10251
|
);
|