@wrongstack/cli 0.7.0 → 0.7.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.js +619 -55
- package/dist/index.js.map +1 -1
- package/package.json +11 -10
package/dist/index.js
CHANGED
|
@@ -3,11 +3,11 @@ 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, 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,
|
|
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, AutoApprovePermissionPolicy, makeLLMClassifier, resolveWstackPaths, DefaultSecretVault, migratePlaintextSecrets, DefaultConfigLoader, DefaultSessionReader, DefaultSessionRewinder, DefaultSessionStore, atomicWrite, DefaultPluginAPI, makeAgentSubagentRunner, NULL_FLEET_BUS, formatContextWindowModeList, repairToolUseAdjacency, getContextWindowMode, resolveContextWindowPolicy, AGENTS_BY_PHASE, dispatchAgent, formatTodosList, emptyPlan, clearPlan, savePlan, formatPlanTemplates, getPlanTemplate, addPlanItem, formatPlan, deriveTodosFromPlanItem, removePlanItem, setPlanItemStatus, SpecStore, TaskGraphStore, analyzeCriticalPath, getTemplate, listTemplates, templateToMarkdown, SpecParser, renderSpecAnalysis, AISpecBuilder, DefaultTaskStore, TaskTracker, renderProgress, renderTaskGraph, loadGoal, goalFilePath, summarizeUsage, saveGoal, emptyGoal, buildGoalPreamble, formatGoal, InputBuilder, FsError, ERROR_CODES, projectHash, WrongStackError, defaultOrchestrator, decryptConfigSecrets, encryptConfigSecrets as encryptConfigSecrets$1, SpecVersioning, ParallelEternalEngine, 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';
|
|
10
|
-
import * as
|
|
10
|
+
import * as crypto2 from 'crypto';
|
|
11
11
|
import { randomUUID } from 'crypto';
|
|
12
12
|
import { DefaultSecretVault as DefaultSecretVault$1, encryptConfigSecrets, decryptConfigSecrets as decryptConfigSecrets$1 } from '@wrongstack/core/security';
|
|
13
13
|
import { WebSocketServer, WebSocket } from 'ws';
|
|
@@ -18,6 +18,9 @@ import { builtinToolsPack, rememberTool, forgetTool } from '@wrongstack/tools';
|
|
|
18
18
|
import * as readline from 'readline';
|
|
19
19
|
import { spawn } from 'child_process';
|
|
20
20
|
import { SkillInstaller } from '@wrongstack/core/skills';
|
|
21
|
+
import { WrongStackACPServer } from '@wrongstack/acp/agent';
|
|
22
|
+
import { ACP_AGENT_COMMANDS, makeACPSubagentRunner, makeACPSubagentRunnerWithStop } from '@wrongstack/acp';
|
|
23
|
+
import { ACP_AGENTS, SubagentBudget } from '@wrongstack/core/coordination';
|
|
21
24
|
import { allServers } from '@wrongstack/core/infrastructure';
|
|
22
25
|
import { createToolVisionAdapters } from '@wrongstack/runtime/vision';
|
|
23
26
|
import { ToolExecutor } from '@wrongstack/core/execution';
|
|
@@ -1244,12 +1247,12 @@ ${lines.join("\n")}`
|
|
|
1244
1247
|
if (analysis.bottlenecks.length > 0) {
|
|
1245
1248
|
lines.push("");
|
|
1246
1249
|
lines.push(` \u{1F6AB} Bottlenecks (blocking most downstream):`);
|
|
1247
|
-
analysis.bottlenecks
|
|
1250
|
+
for (const bt of analysis.bottlenecks) {
|
|
1248
1251
|
const node = graph.nodes.get(bt.taskId);
|
|
1249
1252
|
if (node) {
|
|
1250
1253
|
lines.push(` \u2022 ${node.title} (blocks ${bt.blockedCount} task(s))`);
|
|
1251
1254
|
}
|
|
1252
|
-
}
|
|
1255
|
+
}
|
|
1253
1256
|
}
|
|
1254
1257
|
if (analysis.parallelGroups.length > 0) {
|
|
1255
1258
|
lines.push("");
|
|
@@ -1262,12 +1265,12 @@ ${lines.join("\n")}`
|
|
|
1262
1265
|
if (analysis.readyTasks.length > 0) {
|
|
1263
1266
|
lines.push("");
|
|
1264
1267
|
lines.push(` \u2705 Ready to start now:`);
|
|
1265
|
-
analysis.readyTasks
|
|
1268
|
+
for (const taskId of analysis.readyTasks) {
|
|
1266
1269
|
const node = graph.nodes.get(taskId);
|
|
1267
1270
|
if (node) {
|
|
1268
1271
|
lines.push(` \u2022 ${node.title}`);
|
|
1269
1272
|
}
|
|
1270
|
-
}
|
|
1273
|
+
}
|
|
1271
1274
|
}
|
|
1272
1275
|
lines.push(`\u2570${"\u2500".repeat(55)}\u256F`);
|
|
1273
1276
|
return { message: lines.join("\n") };
|
|
@@ -1468,7 +1471,10 @@ var init_sdd = __esm({
|
|
|
1468
1471
|
return Date.now() - this.phaseStartTime;
|
|
1469
1472
|
}
|
|
1470
1473
|
getVersioning() {
|
|
1471
|
-
|
|
1474
|
+
if (this.versioning === null) {
|
|
1475
|
+
this.versioning = new SpecVersioning();
|
|
1476
|
+
}
|
|
1477
|
+
return this.versioning;
|
|
1472
1478
|
}
|
|
1473
1479
|
clearTaskState() {
|
|
1474
1480
|
this.taskStore = null;
|
|
@@ -1554,7 +1560,7 @@ function currentVersion() {
|
|
|
1554
1560
|
return "dev";
|
|
1555
1561
|
}
|
|
1556
1562
|
function isNewer(a, b) {
|
|
1557
|
-
const parse = (v) => v.replace(/^v/i, "").split(".").map((p) => parseInt(p, 10) || 0);
|
|
1563
|
+
const parse = (v) => v.replace(/^v/i, "").split(".").map((p) => Number.parseInt(p, 10) || 0);
|
|
1558
1564
|
const [ap, bp] = [parse(a), parse(b)];
|
|
1559
1565
|
for (let i = 0; i < Math.max(ap.length, bp.length); i++) {
|
|
1560
1566
|
const ai = ap[i] ?? 0;
|
|
@@ -1663,7 +1669,7 @@ async function runWebUI(opts) {
|
|
|
1663
1669
|
const port = opts.port ?? 3457;
|
|
1664
1670
|
const clients = /* @__PURE__ */ new Map();
|
|
1665
1671
|
let abortController = null;
|
|
1666
|
-
const authToken =
|
|
1672
|
+
const authToken = crypto2.randomBytes(16).toString("hex");
|
|
1667
1673
|
const wss = new WebSocketServer({ port, host: "127.0.0.1", maxPayload: 1 * 1024 * 1024 });
|
|
1668
1674
|
console.log(`[WebUI] WebSocket server starting on ws://127.0.0.1:${port}`);
|
|
1669
1675
|
const eventUnsubscribers = [];
|
|
@@ -2244,6 +2250,35 @@ ${diff}`;
|
|
|
2244
2250
|
}
|
|
2245
2251
|
return "chore: update";
|
|
2246
2252
|
}
|
|
2253
|
+
function makeProviderClassifier(provider, model) {
|
|
2254
|
+
return makeLLMClassifier(async (prompt) => {
|
|
2255
|
+
const ctrl = new AbortController();
|
|
2256
|
+
const timeout = setTimeout(() => ctrl.abort(), 15e3);
|
|
2257
|
+
try {
|
|
2258
|
+
const resp = await provider.complete(
|
|
2259
|
+
{
|
|
2260
|
+
model,
|
|
2261
|
+
system: [
|
|
2262
|
+
{
|
|
2263
|
+
type: "text",
|
|
2264
|
+
text: 'You are an agent router. Choose the single best agent for the task. Reply with ONLY a compact JSON object {"role":"...","reason":"..."}.'
|
|
2265
|
+
}
|
|
2266
|
+
],
|
|
2267
|
+
messages: [{ role: "user", content: [{ type: "text", text: prompt }] }],
|
|
2268
|
+
maxTokens: 120,
|
|
2269
|
+
temperature: 0
|
|
2270
|
+
},
|
|
2271
|
+
{ signal: ctrl.signal }
|
|
2272
|
+
);
|
|
2273
|
+
const content = resp.content;
|
|
2274
|
+
return Array.isArray(content) ? content[0]?.text ?? "" : "";
|
|
2275
|
+
} catch {
|
|
2276
|
+
return "";
|
|
2277
|
+
} finally {
|
|
2278
|
+
clearTimeout(timeout);
|
|
2279
|
+
}
|
|
2280
|
+
});
|
|
2281
|
+
}
|
|
2247
2282
|
|
|
2248
2283
|
// src/arg-parser.ts
|
|
2249
2284
|
var BOOLEAN_FLAGS = /* @__PURE__ */ new Set([
|
|
@@ -3511,8 +3546,12 @@ async function runGit(args, cwd) {
|
|
|
3511
3546
|
});
|
|
3512
3547
|
let stdout = "";
|
|
3513
3548
|
let stderr = "";
|
|
3514
|
-
child.stdout?.on("data", (d) =>
|
|
3515
|
-
|
|
3549
|
+
child.stdout?.on("data", (d) => {
|
|
3550
|
+
stdout += d;
|
|
3551
|
+
});
|
|
3552
|
+
child.stderr?.on("data", (d) => {
|
|
3553
|
+
stderr += d;
|
|
3554
|
+
});
|
|
3516
3555
|
child.on("error", (err) => {
|
|
3517
3556
|
reject(new WrongStackError({
|
|
3518
3557
|
message: `Failed to run git: ${err.message}`,
|
|
@@ -3846,6 +3885,17 @@ function buildStatsCommand(opts) {
|
|
|
3846
3885
|
}
|
|
3847
3886
|
};
|
|
3848
3887
|
}
|
|
3888
|
+
var PHASE_ORDER = [
|
|
3889
|
+
{ phase: "discovery", label: "1 \xB7 Discovery" },
|
|
3890
|
+
{ phase: "planning", label: "2 \xB7 Planning" },
|
|
3891
|
+
{ phase: "build", label: "3 \xB7 Build" },
|
|
3892
|
+
{ phase: "verify", label: "4 \xB7 Verify" },
|
|
3893
|
+
{ phase: "review", label: "5 \xB7 Review" },
|
|
3894
|
+
{ phase: "domain", label: "6 \xB7 Domain" },
|
|
3895
|
+
{ phase: "knowledge", label: "7 \xB7 Knowledge" },
|
|
3896
|
+
{ phase: "delivery", label: "8 \xB7 Delivery & Ops" },
|
|
3897
|
+
{ phase: "meta", label: "9 \xB7 Meta" }
|
|
3898
|
+
];
|
|
3849
3899
|
function buildFleetCommand(opts) {
|
|
3850
3900
|
return {
|
|
3851
3901
|
name: "fleet",
|
|
@@ -3854,12 +3904,15 @@ function buildFleetCommand(opts) {
|
|
|
3854
3904
|
"Usage:",
|
|
3855
3905
|
" /fleet Show fleet status (default)",
|
|
3856
3906
|
" /fleet status Same as /fleet (verbose status)",
|
|
3907
|
+
" /fleet list List the agent roster grouped by phase",
|
|
3908
|
+
" /fleet dispatch <task> Route a task to the best agent and spawn it",
|
|
3857
3909
|
" /fleet spawn <role> [count] Spawn N subagents of a role (default 1)",
|
|
3858
3910
|
" /fleet terminate <subagentId> Stop a specific subagent by id",
|
|
3859
3911
|
" /fleet kill Stop all running subagents",
|
|
3860
3912
|
" /fleet usage Token and cost breakdown across the fleet",
|
|
3861
3913
|
" /fleet journal Show recent journal entries from /goal journal",
|
|
3862
3914
|
"",
|
|
3915
|
+
"In the TUI, press Ctrl+F to open the graphical fleet monitor.",
|
|
3863
3916
|
"Works during /autonomy parallel mode and standalone director sessions."
|
|
3864
3917
|
].join("\n"),
|
|
3865
3918
|
async run(args) {
|
|
@@ -3893,8 +3946,9 @@ function buildFleetCommand(opts) {
|
|
|
3893
3946
|
const id = sa.id?.padEnd(36) ?? "".padEnd(36);
|
|
3894
3947
|
const name = (sa.name ?? "worker").padEnd(16);
|
|
3895
3948
|
const statusColor = sa.status === "running" ? color.green(sa.status.padEnd(10)) : sa.status === "idle" ? color.dim(sa.status.padEnd(10)) : color.dim(sa.status.padEnd(10));
|
|
3949
|
+
const ext = sa.extensions && sa.extensions > 0 ? `${color.yellow(`\u26A1\xD7${sa.extensions}`)} ` : "";
|
|
3896
3950
|
const task = sa.currentTask ?? color.dim("\u2014");
|
|
3897
|
-
lines.push(` ${id} ${name} ${statusColor} ${task}`);
|
|
3951
|
+
lines.push(` ${id} ${name} ${statusColor} ${ext}${task}`);
|
|
3898
3952
|
}
|
|
3899
3953
|
}
|
|
3900
3954
|
const msg3 = lines.join("\n");
|
|
@@ -4000,29 +4054,28 @@ function buildFleetCommand(opts) {
|
|
|
4000
4054
|
if (cmd === "terminate" || cmd === "stop") {
|
|
4001
4055
|
const targetId = subargs[0];
|
|
4002
4056
|
if (!targetId) {
|
|
4003
|
-
const
|
|
4004
|
-
opts.renderer.writeWarning(
|
|
4005
|
-
return { message:
|
|
4057
|
+
const msg3 = `${color.amber("\u26A0 /fleet terminate requires a subagentId.")} Use /fleet to see active ids.`;
|
|
4058
|
+
opts.renderer.writeWarning(msg3);
|
|
4059
|
+
return { message: msg3 };
|
|
4006
4060
|
}
|
|
4007
4061
|
if (!opts.onFleetTerminate) {
|
|
4008
|
-
const
|
|
4009
|
-
opts.renderer.writeWarning(
|
|
4010
|
-
return { message:
|
|
4062
|
+
const msg3 = `${color.amber("\u26A0 /fleet terminate is not wired in this session.")}`;
|
|
4063
|
+
opts.renderer.writeWarning(msg3);
|
|
4064
|
+
return { message: msg3 };
|
|
4011
4065
|
}
|
|
4012
4066
|
const ok = opts.onFleetTerminate(targetId);
|
|
4013
4067
|
if (ok) {
|
|
4014
|
-
const
|
|
4015
|
-
opts.renderer.write(
|
|
4016
|
-
return { message:
|
|
4017
|
-
} else {
|
|
4018
|
-
const msg2 = `${color.red("\u2717 Failed")} to terminate ${color.bold(targetId)}. Subagent may already be stopped.`;
|
|
4019
|
-
opts.renderer.writeWarning(msg2);
|
|
4020
|
-
return { message: msg2 };
|
|
4068
|
+
const msg3 = `${color.green("\u2713 Terminated")} subagent ${color.bold(targetId)}.`;
|
|
4069
|
+
opts.renderer.write(msg3);
|
|
4070
|
+
return { message: msg3 };
|
|
4021
4071
|
}
|
|
4072
|
+
const msg2 = `${color.red("\u2717 Failed")} to terminate ${color.bold(targetId)}. Subagent may already be stopped.`;
|
|
4073
|
+
opts.renderer.writeWarning(msg2);
|
|
4074
|
+
return { message: msg2 };
|
|
4022
4075
|
}
|
|
4023
4076
|
if (cmd === "spawn" || cmd === "add") {
|
|
4024
4077
|
const role = subargs[0] ?? "worker";
|
|
4025
|
-
const count = Math.min(16, Math.max(1, parseInt(subargs[1] ?? "1", 10) || 1));
|
|
4078
|
+
const count = Math.min(16, Math.max(1, Number.parseInt(subargs[1] ?? "1", 10) || 1));
|
|
4026
4079
|
if (!opts.onFleetSpawn) {
|
|
4027
4080
|
const msg3 = `${color.amber("\u26A0 /fleet spawn is not wired in this session.")}`;
|
|
4028
4081
|
opts.renderer.writeWarning(msg3);
|
|
@@ -4048,11 +4101,64 @@ function buildFleetCommand(opts) {
|
|
|
4048
4101
|
}
|
|
4049
4102
|
return { message: msg2 };
|
|
4050
4103
|
}
|
|
4104
|
+
if (cmd === "list" || cmd === "roster" || cmd === "agents") {
|
|
4105
|
+
const lines = [`${color.bold("Agent Roster")} ${color.dim("(spawn with /fleet spawn <role>)")}`];
|
|
4106
|
+
for (const { phase, label } of PHASE_ORDER) {
|
|
4107
|
+
const defs = AGENTS_BY_PHASE[phase];
|
|
4108
|
+
if (!defs || defs.length === 0) continue;
|
|
4109
|
+
lines.push("");
|
|
4110
|
+
lines.push(color.cyan(` Phase ${label}`));
|
|
4111
|
+
for (const def of defs) {
|
|
4112
|
+
const role = (def.config.role ?? "").padEnd(18);
|
|
4113
|
+
lines.push(` ${color.bold(role)} ${color.dim(def.capability.summary)}`);
|
|
4114
|
+
}
|
|
4115
|
+
}
|
|
4116
|
+
const msg2 = lines.join("\n");
|
|
4117
|
+
opts.renderer.write(msg2);
|
|
4118
|
+
return { message: msg2 };
|
|
4119
|
+
}
|
|
4120
|
+
if (cmd === "dispatch" || cmd === "route") {
|
|
4121
|
+
const task = subargs.join(" ").trim();
|
|
4122
|
+
if (!task) {
|
|
4123
|
+
const msg3 = `Usage: /fleet dispatch <task description> \u2014 routes the task to the best agent.`;
|
|
4124
|
+
opts.renderer.writeWarning(msg3);
|
|
4125
|
+
return { message: msg3 };
|
|
4126
|
+
}
|
|
4127
|
+
const decision = await dispatchAgent(task, { classifier: opts.onDispatchClassify });
|
|
4128
|
+
const pct2 = Math.round(decision.confidence * 100);
|
|
4129
|
+
const lines = [];
|
|
4130
|
+
lines.push(
|
|
4131
|
+
`${color.bold("\u2192 " + decision.role)} ${color.dim(`(${decision.method}, ${pct2}% confidence)`)}`
|
|
4132
|
+
);
|
|
4133
|
+
lines.push(` ${color.dim(decision.definition.capability.summary)}`);
|
|
4134
|
+
lines.push(` ${color.dim("why:")} ${decision.reason}`);
|
|
4135
|
+
if (decision.alternatives.length > 0) {
|
|
4136
|
+
const alts = decision.alternatives.slice(0, 3).map((a) => a.role).join(", ");
|
|
4137
|
+
lines.push(` ${color.dim("alternatives:")} ${alts}`);
|
|
4138
|
+
}
|
|
4139
|
+
if (opts.onFleetSpawn) {
|
|
4140
|
+
try {
|
|
4141
|
+
const id = await opts.onFleetSpawn(decision.role);
|
|
4142
|
+
lines.push(` ${color.green("\u2713 spawned")} ${color.bold(decision.role)} as ${color.dim(id)}`);
|
|
4143
|
+
} catch (err) {
|
|
4144
|
+
lines.push(
|
|
4145
|
+
` ${color.amber("\u26A0 spawn failed:")} ${err instanceof Error ? err.message : String(err)}`
|
|
4146
|
+
);
|
|
4147
|
+
}
|
|
4148
|
+
} else {
|
|
4149
|
+
lines.push(` ${color.dim("(no fleet active \u2014 run /autonomy parallel or --director to spawn)")}`);
|
|
4150
|
+
}
|
|
4151
|
+
const msg2 = lines.join("\n");
|
|
4152
|
+
opts.renderer.write(msg2);
|
|
4153
|
+
return { message: msg2 };
|
|
4154
|
+
}
|
|
4051
4155
|
if (cmd === "help" || cmd === "?") {
|
|
4052
4156
|
const msg2 = [
|
|
4053
4157
|
`${color.bold("Fleet Commands")}`,
|
|
4054
4158
|
` ${color.dim("/fleet")} Show fleet status (default)`,
|
|
4055
4159
|
` ${color.dim("/fleet status")} Same as /fleet (verbose status)`,
|
|
4160
|
+
` ${color.dim("/fleet list")} List the agent roster grouped by phase`,
|
|
4161
|
+
` ${color.dim("/fleet dispatch <task>")} Route a task to the best agent and spawn it`,
|
|
4056
4162
|
` ${color.dim("/fleet spawn <role> [count]")} Spawn N subagents of a role (default 1)`,
|
|
4057
4163
|
` ${color.dim("/fleet terminate <subagentId>")} Stop a specific subagent by id`,
|
|
4058
4164
|
` ${color.dim("/fleet kill")} Stop all running subagents`,
|
|
@@ -4062,7 +4168,7 @@ function buildFleetCommand(opts) {
|
|
|
4062
4168
|
opts.renderer.write(msg2);
|
|
4063
4169
|
return { message: msg2 };
|
|
4064
4170
|
}
|
|
4065
|
-
const valid = ["status", "usage", "spawn", "terminate", "kill", "retry", "journal"];
|
|
4171
|
+
const valid = ["status", "list", "dispatch", "usage", "spawn", "terminate", "kill", "retry", "journal"];
|
|
4066
4172
|
const msg = `Unknown subcommand "${cmd}". Valid subcommands: ${valid.join(", ")}. Run /fleet with no args to see status, or /fleet help for usage.`;
|
|
4067
4173
|
opts.renderer.writeWarning(msg);
|
|
4068
4174
|
return { message: msg };
|
|
@@ -5471,8 +5577,8 @@ ${list}
|
|
|
5471
5577
|
|
|
5472
5578
|
Use \`/security report <number>\` to view a specific report.` };
|
|
5473
5579
|
}
|
|
5474
|
-
const index = parseInt(reportId, 10) - 1;
|
|
5475
|
-
if (!isNaN(index) && reports[index]) {
|
|
5580
|
+
const index = Number.parseInt(reportId, 10) - 1;
|
|
5581
|
+
if (!Number.isNaN(index) && reports[index]) {
|
|
5476
5582
|
const content = await readFile(join(reportsDir, reports[index]), "utf-8");
|
|
5477
5583
|
return { message: `# Security Report
|
|
5478
5584
|
|
|
@@ -7147,6 +7253,162 @@ function summarize(value, name) {
|
|
|
7147
7253
|
}
|
|
7148
7254
|
return "";
|
|
7149
7255
|
}
|
|
7256
|
+
var acpCmd = async (args, deps) => {
|
|
7257
|
+
const sub = args[0];
|
|
7258
|
+
if (!sub || sub === "server" || sub === "serve") {
|
|
7259
|
+
return runACPServer(deps);
|
|
7260
|
+
}
|
|
7261
|
+
if (sub === "help") {
|
|
7262
|
+
deps.renderer.write(`wstack acp \u2014 ACP (Agent Client Protocol) integration
|
|
7263
|
+
|
|
7264
|
+
Usage:
|
|
7265
|
+
wstack acp Start WrongStack as an ACP server (blocks)
|
|
7266
|
+
wstack acp server Same as above
|
|
7267
|
+
wstack acp list List available ACP agents
|
|
7268
|
+
wstack acp spawn <id> <task>
|
|
7269
|
+
Spawn an ACP agent as a subagent and wait for result
|
|
7270
|
+
wstack acp help Show this help
|
|
7271
|
+
|
|
7272
|
+
ACP Mode:
|
|
7273
|
+
When run as \`wstack acp\`. WrongStack acts as an ACP-compatible agent.
|
|
7274
|
+
ACP clients (Zed, JetBrains, VS Code) spawn it as a subprocess and
|
|
7275
|
+
communicate over stdio JSON-RPC.
|
|
7276
|
+
Press Ctrl+C to stop.
|
|
7277
|
+
|
|
7278
|
+
spawn:
|
|
7279
|
+
Spawns a named ACP agent (cline, gemini-cli, copilot, openhands, goose)
|
|
7280
|
+
with the given task and waits for its result.
|
|
7281
|
+
Example: wstack acp spawn cline "fix the login bug"
|
|
7282
|
+
`);
|
|
7283
|
+
return 0;
|
|
7284
|
+
}
|
|
7285
|
+
if (sub === "list") {
|
|
7286
|
+
return listACPAgents(deps);
|
|
7287
|
+
}
|
|
7288
|
+
if (sub === "spawn") {
|
|
7289
|
+
return spawnACPAgent(args.slice(1), deps);
|
|
7290
|
+
}
|
|
7291
|
+
deps.renderer.writeError(`Unknown acp subcommand: ${sub}
|
|
7292
|
+
`);
|
|
7293
|
+
deps.renderer.write("Run `wstack acp help` for usage.\n");
|
|
7294
|
+
return 1;
|
|
7295
|
+
};
|
|
7296
|
+
async function runACPServer(deps) {
|
|
7297
|
+
const toolRegistry = deps.toolRegistry;
|
|
7298
|
+
const tools = toolRegistry?.list() ?? [];
|
|
7299
|
+
deps.renderer.writeInfo("Starting WrongStack ACP server...\n");
|
|
7300
|
+
deps.renderer.writeInfo(`Exposing ${tools.length} tool(s) via ACP protocol.
|
|
7301
|
+
`);
|
|
7302
|
+
deps.renderer.writeInfo("Waiting for ACP client connection on stdin/stdout...\n");
|
|
7303
|
+
deps.renderer.writeInfo("Press Ctrl+C to stop.\n");
|
|
7304
|
+
const server = new WrongStackACPServer({ tools });
|
|
7305
|
+
const shutdown = () => {
|
|
7306
|
+
deps.renderer.writeWarning("\nShutting down ACP server...");
|
|
7307
|
+
server.stop();
|
|
7308
|
+
process.exit(0);
|
|
7309
|
+
};
|
|
7310
|
+
process.on("SIGINT", shutdown);
|
|
7311
|
+
process.on("SIGTERM", shutdown);
|
|
7312
|
+
try {
|
|
7313
|
+
await server.start();
|
|
7314
|
+
} catch (err) {
|
|
7315
|
+
deps.renderer.writeError(`ACP server error: ${err instanceof Error ? err.message : String(err)}
|
|
7316
|
+
`);
|
|
7317
|
+
return 1;
|
|
7318
|
+
}
|
|
7319
|
+
return 0;
|
|
7320
|
+
}
|
|
7321
|
+
function listACPAgents(deps) {
|
|
7322
|
+
deps.renderer.write("Available ACP agents:\n\n");
|
|
7323
|
+
for (const a of ACP_AGENTS) {
|
|
7324
|
+
const id = a.id ?? a.role;
|
|
7325
|
+
const name = a.name ?? a.role ?? "";
|
|
7326
|
+
const desc = a.prompt?.slice(0, 50) ?? "";
|
|
7327
|
+
deps.renderer.write(` ${id.padEnd(16)} ${name.padEnd(20)} ${desc}\u2026
|
|
7328
|
+
`);
|
|
7329
|
+
}
|
|
7330
|
+
deps.renderer.write("\nUse `wstack acp spawn <agent> <task>` to delegate a task.\n");
|
|
7331
|
+
return 0;
|
|
7332
|
+
}
|
|
7333
|
+
async function spawnACPAgent(args, deps) {
|
|
7334
|
+
const [subagentId, ...taskParts] = args;
|
|
7335
|
+
if (!subagentId) {
|
|
7336
|
+
deps.renderer.writeError("Usage: wstack acp spawn <agent-id> <task>\n");
|
|
7337
|
+
deps.renderer.write("Run `wstack acp list` to see available agents.\n");
|
|
7338
|
+
return 1;
|
|
7339
|
+
}
|
|
7340
|
+
const task = taskParts.join(" ");
|
|
7341
|
+
if (!task) {
|
|
7342
|
+
deps.renderer.writeError("Usage: wstack acp spawn <agent-id> <task>\n");
|
|
7343
|
+
deps.renderer.write("Task description is required.\n");
|
|
7344
|
+
return 1;
|
|
7345
|
+
}
|
|
7346
|
+
const cmd = ACP_AGENT_COMMANDS[subagentId];
|
|
7347
|
+
if (!cmd) {
|
|
7348
|
+
deps.renderer.writeError(`Unknown ACP agent: ${subagentId}
|
|
7349
|
+
`);
|
|
7350
|
+
deps.renderer.write("Run `wstack acp list` to see available agents.\n");
|
|
7351
|
+
return 1;
|
|
7352
|
+
}
|
|
7353
|
+
deps.renderer.writeInfo(`Spawning ACP agent '${subagentId}'\u2026
|
|
7354
|
+
`);
|
|
7355
|
+
const cleanup = () => {
|
|
7356
|
+
if (stop) {
|
|
7357
|
+
try {
|
|
7358
|
+
stop();
|
|
7359
|
+
} catch {
|
|
7360
|
+
}
|
|
7361
|
+
}
|
|
7362
|
+
};
|
|
7363
|
+
process.on("SIGINT", cleanup);
|
|
7364
|
+
process.on("SIGTERM", cleanup);
|
|
7365
|
+
let stop = null;
|
|
7366
|
+
try {
|
|
7367
|
+
const { runner, stop: runStop } = await makeACPSubagentRunnerWithStop(cmd);
|
|
7368
|
+
stop = runStop;
|
|
7369
|
+
const taskId = `acp-${crypto.randomUUID()}`;
|
|
7370
|
+
const budget = new SubagentBudget({
|
|
7371
|
+
timeoutMs: 5 * 60 * 1e3,
|
|
7372
|
+
maxIterations: 2e3,
|
|
7373
|
+
maxToolCalls: 5e3
|
|
7374
|
+
});
|
|
7375
|
+
const ctx = {
|
|
7376
|
+
subagentId,
|
|
7377
|
+
config: {
|
|
7378
|
+
id: subagentId,
|
|
7379
|
+
name: cmd.role ?? subagentId,
|
|
7380
|
+
role: subagentId,
|
|
7381
|
+
provider: "acp",
|
|
7382
|
+
prompt: ""
|
|
7383
|
+
},
|
|
7384
|
+
budget,
|
|
7385
|
+
signal: new AbortController().signal,
|
|
7386
|
+
bridge: null
|
|
7387
|
+
};
|
|
7388
|
+
budget.start();
|
|
7389
|
+
deps.renderer.writeInfo("Running task\u2026\n");
|
|
7390
|
+
const result = await runner(
|
|
7391
|
+
{ id: taskId, description: task },
|
|
7392
|
+
ctx
|
|
7393
|
+
);
|
|
7394
|
+
deps.renderer.write("\n--- Result ---\n");
|
|
7395
|
+
deps.renderer.write(String(result.result ?? "no result"));
|
|
7396
|
+
deps.renderer.write("\n---------------\n");
|
|
7397
|
+
deps.renderer.writeInfo(
|
|
7398
|
+
`Done. iterations=${result.iterations} toolCalls=${result.toolCalls}
|
|
7399
|
+
`
|
|
7400
|
+
);
|
|
7401
|
+
return 0;
|
|
7402
|
+
} catch (err) {
|
|
7403
|
+
deps.renderer.writeError(`ACP agent error: ${err instanceof Error ? err.message : String(err)}
|
|
7404
|
+
`);
|
|
7405
|
+
return 1;
|
|
7406
|
+
} finally {
|
|
7407
|
+
cleanup();
|
|
7408
|
+
process.off("SIGINT", cleanup);
|
|
7409
|
+
process.off("SIGTERM", cleanup);
|
|
7410
|
+
}
|
|
7411
|
+
}
|
|
7150
7412
|
|
|
7151
7413
|
// src/auth-menu.ts
|
|
7152
7414
|
init_provider_config_utils();
|
|
@@ -9015,8 +9277,8 @@ var rewindCmd = async (args, deps) => {
|
|
|
9015
9277
|
deps.renderer.write("Rewinding to session start...\n");
|
|
9016
9278
|
result = await rewind.rewindToStart(sessionId);
|
|
9017
9279
|
} else if (flags.last) {
|
|
9018
|
-
const n = parseInt(flags.last, 10);
|
|
9019
|
-
if (isNaN(n) || n < 1) {
|
|
9280
|
+
const n = Number.parseInt(flags.last, 10);
|
|
9281
|
+
if (Number.isNaN(n) || n < 1) {
|
|
9020
9282
|
deps.renderer.writeError("--last requires a positive number");
|
|
9021
9283
|
return 1;
|
|
9022
9284
|
}
|
|
@@ -9024,8 +9286,8 @@ var rewindCmd = async (args, deps) => {
|
|
|
9024
9286
|
`);
|
|
9025
9287
|
result = await rewind.rewindLastN(sessionId, n);
|
|
9026
9288
|
} else if (flags.to) {
|
|
9027
|
-
const idx = parseInt(flags.to, 10);
|
|
9028
|
-
if (isNaN(idx) || idx < 0) {
|
|
9289
|
+
const idx = Number.parseInt(flags.to, 10);
|
|
9290
|
+
if (Number.isNaN(idx) || idx < 0) {
|
|
9029
9291
|
deps.renderer.writeError("--to requires a non-negative number");
|
|
9030
9292
|
return 1;
|
|
9031
9293
|
}
|
|
@@ -9146,6 +9408,7 @@ var helpCmd = async (_args, deps) => {
|
|
|
9146
9408
|
|
|
9147
9409
|
// src/subcommands/index.ts
|
|
9148
9410
|
var subcommands = {
|
|
9411
|
+
acp: acpCmd,
|
|
9149
9412
|
init: initCmd,
|
|
9150
9413
|
auth: authCmd,
|
|
9151
9414
|
update: updateCmd,
|
|
@@ -9188,22 +9451,22 @@ function fmtDuration(ms) {
|
|
|
9188
9451
|
const remMin = m - h * 60;
|
|
9189
9452
|
return `${h}h${remMin}m`;
|
|
9190
9453
|
}
|
|
9191
|
-
function fmtTaskResultLine(r,
|
|
9454
|
+
function fmtTaskResultLine(r, color39) {
|
|
9192
9455
|
const stats = `${r.iterations}it ${r.toolCalls}tc ${fmtDuration(r.durationMs)}`;
|
|
9193
9456
|
const errMsg = typeof r.error === "string" ? r.error : r.error?.message;
|
|
9194
9457
|
const errKind = typeof r.error === "object" ? r.error?.kind : void 0;
|
|
9195
9458
|
const errTail = errMsg ? ` \u2014 ${errMsg.replace(/\s+/g, " ").slice(0, 80)}${errMsg.length > 80 ? "\u2026" : ""}` : "";
|
|
9196
|
-
const errKindChip = errKind ?
|
|
9197
|
-
const errSnip = errMsg || errKind ? `${errKindChip}${
|
|
9459
|
+
const errKindChip = errKind ? color39.dim(` [${errKind}]`) : "";
|
|
9460
|
+
const errSnip = errMsg || errKind ? `${errKindChip}${color39.dim(errTail)}` : "";
|
|
9198
9461
|
switch (r.status) {
|
|
9199
9462
|
case "success":
|
|
9200
|
-
return { mark:
|
|
9463
|
+
return { mark: color39.green("\u2713"), stats, tail: "" };
|
|
9201
9464
|
case "timeout":
|
|
9202
|
-
return { mark:
|
|
9465
|
+
return { mark: color39.yellow("\u23F1"), stats: `${color39.yellow("timeout")} ${stats}`, tail: errSnip };
|
|
9203
9466
|
case "stopped":
|
|
9204
|
-
return { mark:
|
|
9467
|
+
return { mark: color39.dim("\u2298"), stats: `${color39.dim("stopped")} ${stats}`, tail: errSnip };
|
|
9205
9468
|
case "failed":
|
|
9206
|
-
return { mark:
|
|
9469
|
+
return { mark: color39.red("\u2717"), stats: `${color39.red("failed")} ${stats}`, tail: errSnip };
|
|
9207
9470
|
}
|
|
9208
9471
|
}
|
|
9209
9472
|
|
|
@@ -9362,6 +9625,214 @@ async function boot(argv) {
|
|
|
9362
9625
|
updateInfo
|
|
9363
9626
|
};
|
|
9364
9627
|
}
|
|
9628
|
+
function fmtElapsed(ms) {
|
|
9629
|
+
const s = Math.floor(ms / 1e3);
|
|
9630
|
+
if (s < 60) return `${s}s`;
|
|
9631
|
+
const m = Math.floor(s / 60);
|
|
9632
|
+
const rem = s % 60;
|
|
9633
|
+
if (m < 60) return `${m}m${rem.toString().padStart(2, "0")}s`;
|
|
9634
|
+
const h = Math.floor(m / 60);
|
|
9635
|
+
return `${h}h${(m % 60).toString().padStart(2, "0")}m`;
|
|
9636
|
+
}
|
|
9637
|
+
function renderFleetLine(states, now, columns, version) {
|
|
9638
|
+
const all = [...states.values()];
|
|
9639
|
+
if (all.length === 0) return "";
|
|
9640
|
+
const running = all.filter((a) => a.status === "running");
|
|
9641
|
+
const done = all.filter((a) => a.status === "done").length;
|
|
9642
|
+
const failed = all.filter((a) => a.status === "failed").length;
|
|
9643
|
+
const versionChip = version ? `${color.bold("WS")}${color.dim(` v${version}`)} ${color.dim("\u2502")} ` : "";
|
|
9644
|
+
const counts = versionChip + `${color.cyan("\u27F3 fleet")} ${color.yellow(`\u25B6${running.length}`)} ${color.green(`\u2713${done}`)}` + (failed > 0 ? ` ${color.red(`\u2717${failed}`)}` : "");
|
|
9645
|
+
const shown = running.sort((a, b) => a.startedAt - b.startedAt).slice(0, 4).map((a) => {
|
|
9646
|
+
const elapsed = fmtElapsed(Math.max(0, now - a.startedAt));
|
|
9647
|
+
const tool = a.lastTool ? ` ${color.dim(a.lastTool)}` : "";
|
|
9648
|
+
const ext = a.extensions && a.extensions > 0 ? ` ${color.yellow(`\u26A1${a.extensions}`)}` : "";
|
|
9649
|
+
return `${color.bold(a.name)} ${color.yellow("\u25B6")} ${color.dim(elapsed)} ${color.dim(`L${a.iterations}`)} ${color.dim(`${a.toolCalls}t`)}${tool}${ext}`;
|
|
9650
|
+
});
|
|
9651
|
+
let line = shown.length > 0 ? `${counts} ${color.dim("\u2502")} ${shown.join(color.dim(" \xB7 "))}` : counts;
|
|
9652
|
+
const max = Math.max(20, columns - 1);
|
|
9653
|
+
const visible = line.replace(/\x1b\[[0-9;]*m/g, "");
|
|
9654
|
+
if (visible.length > max) {
|
|
9655
|
+
let count = 0;
|
|
9656
|
+
let out = "";
|
|
9657
|
+
let i = 0;
|
|
9658
|
+
while (i < line.length && count < max - 1) {
|
|
9659
|
+
if (line[i] === "\x1B") {
|
|
9660
|
+
const end = line.indexOf("m", i);
|
|
9661
|
+
if (end !== -1) {
|
|
9662
|
+
out += line.slice(i, end + 1);
|
|
9663
|
+
i = end + 1;
|
|
9664
|
+
continue;
|
|
9665
|
+
}
|
|
9666
|
+
}
|
|
9667
|
+
out += line[i];
|
|
9668
|
+
count++;
|
|
9669
|
+
i++;
|
|
9670
|
+
}
|
|
9671
|
+
line = out + "\u2026";
|
|
9672
|
+
}
|
|
9673
|
+
return line;
|
|
9674
|
+
}
|
|
9675
|
+
var FleetStatusLine = class {
|
|
9676
|
+
events;
|
|
9677
|
+
out;
|
|
9678
|
+
throttleMs;
|
|
9679
|
+
now;
|
|
9680
|
+
version;
|
|
9681
|
+
states = /* @__PURE__ */ new Map();
|
|
9682
|
+
unsubs = [];
|
|
9683
|
+
active = false;
|
|
9684
|
+
rows = 0;
|
|
9685
|
+
repaintTimer = null;
|
|
9686
|
+
tickTimer = null;
|
|
9687
|
+
lastPaint = 0;
|
|
9688
|
+
onResize = () => {
|
|
9689
|
+
if (this.active) {
|
|
9690
|
+
this.rows = this.out.rows ?? 24;
|
|
9691
|
+
this.out.write(`\x1B[1;${this.rows - 1}r`);
|
|
9692
|
+
this.paint(true);
|
|
9693
|
+
}
|
|
9694
|
+
};
|
|
9695
|
+
constructor(opts) {
|
|
9696
|
+
this.events = opts.events;
|
|
9697
|
+
this.out = opts.out ?? process.stdout;
|
|
9698
|
+
this.throttleMs = opts.throttleMs ?? 150;
|
|
9699
|
+
this.now = opts.now ?? Date.now;
|
|
9700
|
+
this.version = opts.version;
|
|
9701
|
+
}
|
|
9702
|
+
/** Subscribe to host fleet events. No terminal output until a subagent spawns. */
|
|
9703
|
+
start() {
|
|
9704
|
+
const ensure = (id, name) => {
|
|
9705
|
+
let s = this.states.get(id);
|
|
9706
|
+
if (!s) {
|
|
9707
|
+
s = {
|
|
9708
|
+
id,
|
|
9709
|
+
name: name ?? id,
|
|
9710
|
+
status: "running",
|
|
9711
|
+
iterations: 0,
|
|
9712
|
+
toolCalls: 0,
|
|
9713
|
+
startedAt: this.now()
|
|
9714
|
+
};
|
|
9715
|
+
this.states.set(id, s);
|
|
9716
|
+
} else if (name && s.name === s.id) {
|
|
9717
|
+
s.name = name;
|
|
9718
|
+
}
|
|
9719
|
+
return s;
|
|
9720
|
+
};
|
|
9721
|
+
this.unsubs.push(
|
|
9722
|
+
this.events.on("subagent.spawned", (e) => {
|
|
9723
|
+
ensure(e.subagentId, e.name);
|
|
9724
|
+
this.activate();
|
|
9725
|
+
this.schedulePaint();
|
|
9726
|
+
}),
|
|
9727
|
+
this.events.on("subagent.task_started", (e) => {
|
|
9728
|
+
const s = ensure(e.subagentId);
|
|
9729
|
+
s.status = "running";
|
|
9730
|
+
this.activate();
|
|
9731
|
+
this.schedulePaint();
|
|
9732
|
+
}),
|
|
9733
|
+
this.events.on("subagent.tool_executed", (e) => {
|
|
9734
|
+
const s = ensure(e.subagentId);
|
|
9735
|
+
s.toolCalls++;
|
|
9736
|
+
s.lastTool = e.name;
|
|
9737
|
+
this.activate();
|
|
9738
|
+
this.schedulePaint();
|
|
9739
|
+
}),
|
|
9740
|
+
this.events.on("subagent.iteration_summary", (e) => {
|
|
9741
|
+
const s = ensure(e.subagentId);
|
|
9742
|
+
s.iterations = e.iteration;
|
|
9743
|
+
if (typeof e.toolCalls === "number") s.toolCalls = e.toolCalls;
|
|
9744
|
+
if (e.currentTool) s.lastTool = e.currentTool;
|
|
9745
|
+
this.schedulePaint();
|
|
9746
|
+
}),
|
|
9747
|
+
this.events.on("subagent.budget_extended", (e) => {
|
|
9748
|
+
const s = ensure(e.subagentId);
|
|
9749
|
+
s.extensions = e.totalExtensions;
|
|
9750
|
+
this.activate();
|
|
9751
|
+
this.schedulePaint();
|
|
9752
|
+
}),
|
|
9753
|
+
this.events.on("subagent.task_completed", (e) => {
|
|
9754
|
+
const s = ensure(e.subagentId);
|
|
9755
|
+
s.status = e.status === "success" ? "done" : "failed";
|
|
9756
|
+
s.iterations = e.iterations;
|
|
9757
|
+
s.toolCalls = e.toolCalls;
|
|
9758
|
+
s.endedAt = this.now();
|
|
9759
|
+
this.schedulePaint();
|
|
9760
|
+
if (![...this.states.values()].some((a) => a.status === "running")) {
|
|
9761
|
+
setTimeout(() => {
|
|
9762
|
+
if (![...this.states.values()].some((a) => a.status === "running")) {
|
|
9763
|
+
this.deactivate();
|
|
9764
|
+
}
|
|
9765
|
+
}, 800);
|
|
9766
|
+
}
|
|
9767
|
+
})
|
|
9768
|
+
);
|
|
9769
|
+
}
|
|
9770
|
+
/** Unsubscribe and restore the terminal. Idempotent. */
|
|
9771
|
+
stop() {
|
|
9772
|
+
for (const u of this.unsubs.splice(0)) u();
|
|
9773
|
+
this.deactivate();
|
|
9774
|
+
}
|
|
9775
|
+
isTty() {
|
|
9776
|
+
return !!this.out.isTTY;
|
|
9777
|
+
}
|
|
9778
|
+
activate() {
|
|
9779
|
+
if (this.active || !this.isTty()) return;
|
|
9780
|
+
this.active = true;
|
|
9781
|
+
this.rows = this.out.rows ?? 24;
|
|
9782
|
+
this.out.write("\n");
|
|
9783
|
+
this.out.write(`\x1B[1;${this.rows - 1}r`);
|
|
9784
|
+
this.out.write(`\x1B[${this.rows - 1};1H`);
|
|
9785
|
+
this.out.on("resize", this.onResize);
|
|
9786
|
+
this.tickTimer = setInterval(() => this.paint(true), 1e3);
|
|
9787
|
+
if (this.tickTimer.unref) this.tickTimer.unref();
|
|
9788
|
+
this.paint(true);
|
|
9789
|
+
}
|
|
9790
|
+
deactivate() {
|
|
9791
|
+
if (!this.active) return;
|
|
9792
|
+
this.active = false;
|
|
9793
|
+
if (this.tickTimer) {
|
|
9794
|
+
clearInterval(this.tickTimer);
|
|
9795
|
+
this.tickTimer = null;
|
|
9796
|
+
}
|
|
9797
|
+
if (this.repaintTimer) {
|
|
9798
|
+
clearTimeout(this.repaintTimer);
|
|
9799
|
+
this.repaintTimer = null;
|
|
9800
|
+
}
|
|
9801
|
+
this.out.off("resize", this.onResize);
|
|
9802
|
+
if (this.isTty()) {
|
|
9803
|
+
this.out.write("\x1B7");
|
|
9804
|
+
this.out.write(`\x1B[${this.rows};1H`);
|
|
9805
|
+
this.out.write("\x1B[2K");
|
|
9806
|
+
this.out.write("\x1B8");
|
|
9807
|
+
this.out.write("\x1B[r");
|
|
9808
|
+
}
|
|
9809
|
+
}
|
|
9810
|
+
schedulePaint() {
|
|
9811
|
+
if (!this.active) return;
|
|
9812
|
+
const since = this.now() - this.lastPaint;
|
|
9813
|
+
if (since >= this.throttleMs) {
|
|
9814
|
+
this.paint(false);
|
|
9815
|
+
return;
|
|
9816
|
+
}
|
|
9817
|
+
if (this.repaintTimer) return;
|
|
9818
|
+
this.repaintTimer = setTimeout(() => {
|
|
9819
|
+
this.repaintTimer = null;
|
|
9820
|
+
this.paint(false);
|
|
9821
|
+
}, this.throttleMs - since);
|
|
9822
|
+
if (this.repaintTimer.unref) this.repaintTimer.unref();
|
|
9823
|
+
}
|
|
9824
|
+
paint(force) {
|
|
9825
|
+
if (!this.active || !this.isTty()) return;
|
|
9826
|
+
if (!force) this.lastPaint = this.now();
|
|
9827
|
+
else this.lastPaint = this.now();
|
|
9828
|
+
const line = renderFleetLine(this.states, this.now(), this.out.columns ?? 80, this.version);
|
|
9829
|
+
this.out.write("\x1B7");
|
|
9830
|
+
this.out.write(`\x1B[${this.rows};1H`);
|
|
9831
|
+
this.out.write("\x1B[2K");
|
|
9832
|
+
this.out.write(line);
|
|
9833
|
+
this.out.write("\x1B8");
|
|
9834
|
+
}
|
|
9835
|
+
};
|
|
9365
9836
|
|
|
9366
9837
|
// src/repl.ts
|
|
9367
9838
|
init_sdd();
|
|
@@ -9959,6 +10430,7 @@ async function execute(deps) {
|
|
|
9959
10430
|
skillLoader
|
|
9960
10431
|
} = deps;
|
|
9961
10432
|
let code = 0;
|
|
10433
|
+
let fleetStatusLine = null;
|
|
9962
10434
|
try {
|
|
9963
10435
|
const visionAdapters = () => createToolVisionAdapters(agent.tools);
|
|
9964
10436
|
const supportsVision = async () => {
|
|
@@ -9978,6 +10450,11 @@ async function execute(deps) {
|
|
|
9978
10450
|
if ((goalFlag || askFlag) && positional.length === 0 && !promptFlag) {
|
|
9979
10451
|
flags.tui = true;
|
|
9980
10452
|
}
|
|
10453
|
+
const enteringTui = !(positional.length > 0 || promptFlag) && !!flags.tui && flags["no-tui"] !== true;
|
|
10454
|
+
if (!enteringTui) {
|
|
10455
|
+
fleetStatusLine = new FleetStatusLine({ events, version: CLI_VERSION });
|
|
10456
|
+
fleetStatusLine.start();
|
|
10457
|
+
}
|
|
9981
10458
|
if (positional.length > 0 || promptFlag) {
|
|
9982
10459
|
const query = positional.join(" ");
|
|
9983
10460
|
const ctrl = new AbortController();
|
|
@@ -10189,6 +10666,7 @@ async function execute(deps) {
|
|
|
10189
10666
|
});
|
|
10190
10667
|
}
|
|
10191
10668
|
} finally {
|
|
10669
|
+
fleetStatusLine?.stop();
|
|
10192
10670
|
try {
|
|
10193
10671
|
stats.render(renderer);
|
|
10194
10672
|
} catch (err) {
|
|
@@ -10248,6 +10726,13 @@ var MultiAgentHost = class {
|
|
|
10248
10726
|
getCoordinator() {
|
|
10249
10727
|
return this.director.coordinator;
|
|
10250
10728
|
}
|
|
10729
|
+
/** Public accessor for the Director — used by buildRoutingRunner. */
|
|
10730
|
+
getDirector() {
|
|
10731
|
+
return this.director;
|
|
10732
|
+
}
|
|
10733
|
+
async ensureCoordinator(config) {
|
|
10734
|
+
await this.buildDirector();
|
|
10735
|
+
}
|
|
10251
10736
|
async buildDirector() {
|
|
10252
10737
|
if (this.director) return;
|
|
10253
10738
|
const config = this.deps.configStore.get();
|
|
@@ -10303,22 +10788,41 @@ var MultiAgentHost = class {
|
|
|
10303
10788
|
limit: payload.limit
|
|
10304
10789
|
});
|
|
10305
10790
|
});
|
|
10306
|
-
this.
|
|
10307
|
-
|
|
10308
|
-
|
|
10309
|
-
|
|
10310
|
-
|
|
10791
|
+
this.director.fleet.filter("budget.extended", (e) => {
|
|
10792
|
+
const payload = e.payload;
|
|
10793
|
+
this.deps.events.emit("subagent.budget_extended", {
|
|
10794
|
+
subagentId: e.subagentId,
|
|
10795
|
+
kind: payload.kind,
|
|
10796
|
+
newLimit: payload.newLimit,
|
|
10797
|
+
totalExtensions: payload.totalExtensions
|
|
10311
10798
|
});
|
|
10312
10799
|
});
|
|
10313
|
-
|
|
10800
|
+
this.getCoordinator().on(
|
|
10801
|
+
"task.assigned",
|
|
10802
|
+
({
|
|
10803
|
+
task,
|
|
10804
|
+
subagentId
|
|
10805
|
+
}) => {
|
|
10806
|
+
this.deps.events.emit("subagent.task_started", {
|
|
10807
|
+
subagentId,
|
|
10808
|
+
taskId: task.id,
|
|
10809
|
+
description: task.description
|
|
10810
|
+
});
|
|
10811
|
+
}
|
|
10812
|
+
);
|
|
10813
|
+
const runner = await this.buildSubagentRunner(config);
|
|
10314
10814
|
this.getCoordinator().setRunner(runner);
|
|
10315
10815
|
}
|
|
10316
10816
|
/**
|
|
10317
|
-
* Build
|
|
10318
|
-
*
|
|
10817
|
+
* Build a per-role subagent factory: given a SubagentConfig, construct a
|
|
10818
|
+
* fresh, isolated Agent with the role's filtered tools and (when the config
|
|
10819
|
+
* carries one) the role's persona as an appended system-prompt block. Public
|
|
10820
|
+
* so the autonomy-parallel engine can reuse the exact same agent-construction
|
|
10821
|
+
* path the director/spawn flow uses — each parallel slot then runs as a real,
|
|
10822
|
+
* specialized, concurrency-safe agent instead of sharing the leader's Context.
|
|
10319
10823
|
*/
|
|
10320
|
-
|
|
10321
|
-
|
|
10824
|
+
makeSubagentFactory(config) {
|
|
10825
|
+
return async (subCfg) => {
|
|
10322
10826
|
const events = new EventBus();
|
|
10323
10827
|
const provider = await this.buildSubagentProvider(config, subCfg.provider);
|
|
10324
10828
|
const baseSystem = await this.deps.systemPromptBuilder.build({
|
|
@@ -10333,6 +10837,9 @@ var MultiAgentHost = class {
|
|
|
10333
10837
|
// meaningless to a single delegated subtask.
|
|
10334
10838
|
subagent: true
|
|
10335
10839
|
});
|
|
10840
|
+
if (subCfg.systemPromptOverride) {
|
|
10841
|
+
baseSystem.push({ type: "text", text: subCfg.systemPromptOverride });
|
|
10842
|
+
}
|
|
10336
10843
|
let subSession;
|
|
10337
10844
|
if (this.sessionFactory) {
|
|
10338
10845
|
const subagentName = subCfg.id ?? subCfg.name ?? `sub_${randomUUID().slice(0, 8)}`;
|
|
@@ -10411,7 +10918,22 @@ var MultiAgentHost = class {
|
|
|
10411
10918
|
};
|
|
10412
10919
|
return { agent, events, dispose };
|
|
10413
10920
|
};
|
|
10414
|
-
|
|
10921
|
+
}
|
|
10922
|
+
/**
|
|
10923
|
+
* Build the per-subagent runner.
|
|
10924
|
+
*
|
|
10925
|
+
* ACP agents (provider: 'acp') get their own runner via
|
|
10926
|
+
* makeACPSubagentRunner — they run external processes and don't go
|
|
10927
|
+
* through the Agent factory. Regular agents use the standard
|
|
10928
|
+
* makeAgentSubagentRunner path.
|
|
10929
|
+
*/
|
|
10930
|
+
async buildSubagentRunner(config) {
|
|
10931
|
+
return buildRoutingRunner(config, this);
|
|
10932
|
+
}
|
|
10933
|
+
async buildACPRunner(subagentId) {
|
|
10934
|
+
const cmd = ACP_AGENT_COMMANDS[subagentId];
|
|
10935
|
+
if (!cmd) throw new Error(`Unknown ACP agent: ${subagentId}`);
|
|
10936
|
+
return makeACPSubagentRunner(cmd);
|
|
10415
10937
|
}
|
|
10416
10938
|
/**
|
|
10417
10939
|
* Build a Provider for a subagent. When `overrideId` is supplied (from
|
|
@@ -10433,6 +10955,24 @@ var MultiAgentHost = class {
|
|
|
10433
10955
|
type: providerId
|
|
10434
10956
|
});
|
|
10435
10957
|
}
|
|
10958
|
+
async spawnACP(subagentId, task, config) {
|
|
10959
|
+
const taskId = randomUUID();
|
|
10960
|
+
await this.ensureCoordinator(config);
|
|
10961
|
+
const coordinator = this.getCoordinator();
|
|
10962
|
+
const acpRunner = await this.buildACPRunner(subagentId);
|
|
10963
|
+
coordinator.setRunner(acpRunner);
|
|
10964
|
+
await coordinator.spawn({
|
|
10965
|
+
id: subagentId,
|
|
10966
|
+
name: subagentId,
|
|
10967
|
+
role: subagentId,
|
|
10968
|
+
provider: "acp"
|
|
10969
|
+
});
|
|
10970
|
+
await coordinator.assign({
|
|
10971
|
+
id: taskId,
|
|
10972
|
+
description: task
|
|
10973
|
+
});
|
|
10974
|
+
return taskId;
|
|
10975
|
+
}
|
|
10436
10976
|
/** Returns a tool slice for the subagent — full set unless restricted. */
|
|
10437
10977
|
filterTools(allow) {
|
|
10438
10978
|
const all = this.deps.toolRegistry.list();
|
|
@@ -10442,7 +10982,7 @@ var MultiAgentHost = class {
|
|
|
10442
10982
|
}
|
|
10443
10983
|
subagentToolRegistry(allow) {
|
|
10444
10984
|
if (!allow || allow.length === 0) return this.deps.toolRegistry;
|
|
10445
|
-
const sub =
|
|
10985
|
+
const sub = new ToolRegistry();
|
|
10446
10986
|
for (const t of this.filterTools(allow)) sub.register(t);
|
|
10447
10987
|
return sub;
|
|
10448
10988
|
}
|
|
@@ -10687,6 +11227,20 @@ var MultiAgentHost = class {
|
|
|
10687
11227
|
}
|
|
10688
11228
|
}
|
|
10689
11229
|
};
|
|
11230
|
+
function buildRoutingRunner(config, host) {
|
|
11231
|
+
const standardRunner = makeAgentSubagentRunner({
|
|
11232
|
+
factory: host.makeSubagentFactory(config),
|
|
11233
|
+
fleetBus: host.getDirector()?.fleet ?? NULL_FLEET_BUS
|
|
11234
|
+
});
|
|
11235
|
+
return async (task, ctx) => {
|
|
11236
|
+
const subCfg = ctx.config;
|
|
11237
|
+
if (subCfg.provider === "acp") {
|
|
11238
|
+
const cacheKey = subCfg.role ?? subCfg.name ?? subCfg.id;
|
|
11239
|
+
return host.buildACPRunner(cacheKey).then((r) => r(task, ctx));
|
|
11240
|
+
}
|
|
11241
|
+
return standardRunner(task, ctx);
|
|
11242
|
+
};
|
|
11243
|
+
}
|
|
10690
11244
|
function makePromptDelegate(reader) {
|
|
10691
11245
|
return async (tool, input, suggestedPattern) => {
|
|
10692
11246
|
process.stdout.write("\x07");
|
|
@@ -12160,7 +12714,11 @@ Restart WrongStack to load or unload plugin code in this session.`;
|
|
|
12160
12714
|
projectRoot,
|
|
12161
12715
|
compactor: container.resolve(TOKENS.Compactor),
|
|
12162
12716
|
maxContextTokens: effectiveMaxContext > 0 ? effectiveMaxContext : void 0,
|
|
12163
|
-
onIteration: broadcastEternalIteration
|
|
12717
|
+
onIteration: broadcastEternalIteration,
|
|
12718
|
+
// Real per-role factory: each dispatched slot runs as a fresh,
|
|
12719
|
+
// isolated agent with the role's filtered tools + persona prompt
|
|
12720
|
+
// (instead of sharing the leader agent's Context).
|
|
12721
|
+
subagentFactory: multiAgentHost.makeSubagentFactory(config)
|
|
12164
12722
|
});
|
|
12165
12723
|
}
|
|
12166
12724
|
void parallelEngine.prime?.();
|
|
@@ -12190,7 +12748,9 @@ Restart WrongStack to load or unload plugin code in this session.`;
|
|
|
12190
12748
|
const statusResult = await new Promise((resolve4, reject) => {
|
|
12191
12749
|
const child = spawn3("git", ["status", "--porcelain"], { cwd: cwd2, stdio: ["ignore", "pipe", "pipe"] });
|
|
12192
12750
|
let stdout = "";
|
|
12193
|
-
child.stdout?.on("data", (d) =>
|
|
12751
|
+
child.stdout?.on("data", (d) => {
|
|
12752
|
+
stdout += d;
|
|
12753
|
+
});
|
|
12194
12754
|
child.on("error", reject);
|
|
12195
12755
|
child.on("close", (code) => resolve4({ stdout, code: code ?? 0 }));
|
|
12196
12756
|
});
|
|
@@ -12236,7 +12796,11 @@ Restart WrongStack to load or unload plugin code in this session.`;
|
|
|
12236
12796
|
provider: context.provider,
|
|
12237
12797
|
model: context.model
|
|
12238
12798
|
});
|
|
12239
|
-
}
|
|
12799
|
+
},
|
|
12800
|
+
onDispatchClassify: makeProviderClassifier(
|
|
12801
|
+
context.provider,
|
|
12802
|
+
context.model
|
|
12803
|
+
)
|
|
12240
12804
|
});
|
|
12241
12805
|
for (const cmd of slashCmds) slashRegistry.register(cmd);
|
|
12242
12806
|
const eternalFlag = typeof flags["eternal"] === "string" ? flags["eternal"].trim() : "";
|