@wrongstack/cli 1.0.33 → 1.0.35
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/{acp-EDGUWU7N.js → acp-T77EAEDB.js} +469 -465
- package/dist/{auth-SUHKFFO4.js → auth-M6LXZTDA.js} +4 -4
- package/dist/auth-menu/openai-codex-oauth.d.ts +1 -1
- package/dist/auth-menu/panel-provider-edits.d.ts +15 -0
- package/dist/auth-menu/panel-service.d.ts +1 -1
- package/dist/boot/governance-evidence-candidate.d.ts +1 -2
- package/dist/boot/system-prompt-builder.d.ts +1 -1
- package/dist/boot/tui-project-spawn.d.ts +0 -1
- package/dist/boot/tui-session-resume-contracts.d.ts +95 -0
- package/dist/boot/tui-session-resume.d.ts +2 -88
- package/dist/boot/tui-settings-read.d.ts +3 -0
- package/dist/{boot-auto-thin-K2DHHYIW.js → boot-auto-thin-YMAZI3QL.js} +2 -2
- package/dist/boot-launch-choices.d.ts +22 -0
- package/dist/boot-provider-gate.d.ts +28 -0
- package/dist/boot-subcommand-dispatch.d.ts +24 -0
- package/dist/boot.d.ts +1 -1
- package/dist/chimera-cascade-evidence.d.ts +1 -1
- package/dist/{chunk-TI7KLHYX.js → chunk-2CMMWRQH.js} +81 -80
- package/dist/{chunk-3LEFXLAW.js → chunk-6SIUTX75.js} +36 -36
- package/dist/{chunk-RQ4UBKYH.js → chunk-6Z6C6ZHH.js} +3 -3
- package/dist/{chunk-5EA6OTLJ.js → chunk-AFOH6CHO.js} +2 -2
- package/dist/{chunk-WHEYHDIE.js → chunk-AKESWZH5.js} +2 -1
- package/dist/{chunk-JHUJUIOH.js → chunk-AT2A7RJZ.js} +4 -16
- package/dist/{chunk-U3SS4NRH.js → chunk-BHXAKTBE.js} +1 -1
- package/dist/{chunk-MPQCHBDJ.js → chunk-CJSHS2K7.js} +5 -1
- package/dist/{chunk-P3GRLV47.js → chunk-GZSKLXYG.js} +208 -141
- package/dist/{chunk-4MDRHMX4.js → chunk-KMU7GF7X.js} +3 -26
- package/dist/{chunk-SWOPJHXI.js → chunk-LDV4ERNV.js} +4 -4
- package/dist/{chunk-24C6VVLM.js → chunk-LDXXMPMS.js} +12 -12
- package/dist/{chunk-UQUYBDMG.js → chunk-M4G5FO7F.js} +1 -1
- package/dist/{chunk-5UZBUV2H.js → chunk-RKU66D2O.js} +6 -4
- package/dist/{chunk-PGNERAZL.js → chunk-RWNI5OWT.js} +2 -2
- package/dist/chunk-TGSPCFBT.js +1 -0
- package/dist/{cli-context-7HK67B2Z.js → cli-context-II57EHD2.js} +1554 -1450
- package/dist/cli-initial-tools.d.ts +54 -0
- package/dist/{cli-main-TQGLKB6D.js → cli-main-YR2WJCPL.js} +38062 -37035
- package/dist/cli-main-boot.d.ts +133 -0
- package/dist/cli-main-orchestration.d.ts +170 -0
- package/dist/cli-main-state.d.ts +19 -0
- package/dist/config-doctor.d.ts +1 -1
- package/dist/{config-transfer-UE37X4B5.js → config-transfer-VOIFY7U4.js} +2 -2
- package/dist/{execution-C2IECWF7.js → execution-IBAZ2EIU.js} +138 -124
- package/dist/fleet/host-director-setup.d.ts +41 -0
- package/dist/fleet/host-spawn-types.d.ts +17 -0
- package/dist/fleet/host.d.ts +2 -1
- package/dist/goal-host-prompts.d.ts +5 -0
- package/dist/goal-host-types.d.ts +94 -0
- package/dist/goal-host.d.ts +2 -91
- package/dist/{hq-KBFDKTDD.js → hq-5OIEKB4V.js} +5 -6
- package/dist/hq-server/handle-types.d.ts +1 -1
- package/dist/hq-server/routes/auth-handlers.d.ts +1 -1
- package/dist/hq-server/routes.d.ts +1 -13
- package/dist/hq-server/utils.d.ts +1 -1
- package/dist/{hq-server-AVNKZ2PV.js → hq-server-DU3ZH3X7.js} +3 -3
- package/dist/{hq-service-FGB7BY2D.js → hq-service-LYZV7EK7.js} +3 -3
- package/dist/index.js +4 -4
- package/dist/live-settings-input.d.ts +2 -0
- package/dist/mailbox-bridge-bootstrap.d.ts +1 -4
- package/dist/picker-effort.d.ts +32 -0
- package/dist/picker-model-picker.d.ts +13 -2
- package/dist/picker-provider-list.d.ts +66 -0
- package/dist/picker.d.ts +12 -12
- package/dist/{providers-models-O75MKLN2.js → providers-models-37DUV2GW.js} +247 -237
- package/dist/repl-suggestions.d.ts +0 -5
- package/dist/{sdd-runtime-PTOSPYI4.js → sdd-runtime-PF72WHLO.js} +3 -20
- package/dist/services/sdd/task-manager.d.ts +1 -12
- package/dist/services/sdd-runtime.d.ts +3 -4
- package/dist/services/statusline-config.d.ts +0 -2
- package/dist/settings-behavior-sections.d.ts +0 -1
- package/dist/short-circuit-flags-EXJBJ5GT.js +10 -0
- package/dist/{skill-suggest-L2EVWB6R.js → skill-suggest-THANK3LV.js} +1 -1
- package/dist/slash-commands/command-context.d.ts +2 -0
- package/dist/slash-commands/fallback-chain-view.d.ts +3 -0
- package/dist/slash-commands/fallback-ref-validation.d.ts +34 -0
- package/dist/slash-commands/goal-refiner.d.ts +10 -2
- package/dist/slash-commands/goals.d.ts +6 -0
- package/dist/slash-commands/kanban-task-completion.d.ts +17 -0
- package/dist/slash-commands/mcp.d.ts +0 -1
- package/dist/slash-commands/memory-purge-domain-terms.d.ts +5 -0
- package/dist/slash-commands/nextsteps-mode.d.ts +13 -0
- package/dist/slash-commands/session-live-status.d.ts +21 -0
- package/dist/slash-commands/session.d.ts +1 -1
- package/dist/slash-commands/settings-feature-ui-mutations.d.ts +8 -0
- package/dist/slash-commands/settings-hq-autonomy-mutations.d.ts +8 -0
- package/dist/slash-commands/settings-persist-deps.d.ts +10 -0
- package/dist/slash-commands/settings-runtime-mutations.d.ts +12 -0
- package/dist/slash-commands/shadow.d.ts +0 -1
- package/dist/slash-commands/tool-auto-thin-commands.d.ts +15 -0
- package/dist/subcommands/handlers/acp-agents.d.ts +9 -0
- package/dist/subcommands/handlers/acp-server.d.ts +7 -0
- package/dist/subcommands/handlers/hq.d.ts +0 -1
- package/dist/subcommands/handlers/providers-models-custom.d.ts +33 -0
- package/dist/subcommands/handlers/providers-models-visibility.d.ts +29 -0
- package/dist/subcommands/handlers/skill-suggest.d.ts +0 -2
- package/dist/{subcommands-CLI6QTLU.js → subcommands-DTKBXLPU.js} +2 -2
- package/dist/{update-J5WHAFPE.js → update-6OEPAEUV.js} +3 -3
- package/dist/utils/win32-cmd.d.ts +1 -1
- package/dist/{version-help-ATOKGMTK.js → version-help-JQGRPDJW.js} +2 -2
- package/dist/webui-server/host-settings.d.ts +17 -0
- package/dist/webui-server/http-bridge.d.ts +32 -0
- package/dist/webui-server/instance-registration.d.ts +19 -0
- package/dist/webui-server/kanban-services.d.ts +9 -0
- package/dist/webui-server/message-router.d.ts +14 -0
- package/dist/webui-server/prefs-seeding.d.ts +1 -2
- package/dist/webui-server/provider-config.d.ts +0 -1
- package/dist/webui-server/provider-plane.d.ts +14 -0
- package/dist/webui-server/session-agents.d.ts +18 -0
- package/dist/webui-server/session-start-live.d.ts +11 -0
- package/dist/webui-server/stream-coalescer.d.ts +1 -1
- package/dist/webui-server/transport.d.ts +12 -0
- package/dist/{webui-server-GQOVLQS3.js → webui-server-6IC3SLZG.js} +1600 -1435
- package/dist/webui-session-maintenance.d.ts +16 -0
- package/dist/wiring/brain-and-orchestration.d.ts +1 -1
- package/dist/wiring/cli-slash-commands-setup.d.ts +1 -0
- package/dist/wiring/command-host-state.d.ts +11 -2
- package/dist/wiring/dep-watcher-bridge.d.ts +8 -1
- package/dist/wiring/session-command-handlers.d.ts +2 -1
- package/dist/worktree-command-host.d.ts +31 -0
- package/package.json +30 -30
- package/dist/chunk-WHOIR577.js +0 -1
- package/dist/short-circuit-flags-D6VAG47Y.js +0 -10
|
@@ -14,9 +14,7 @@ import {
|
|
|
14
14
|
import "./chunk-SDJCLEK6.js";
|
|
15
15
|
import "./chunk-F5DCFAKI.js";
|
|
16
16
|
|
|
17
|
-
// src/subcommands/handlers/acp.ts
|
|
18
|
-
import { randomBytes } from "node:crypto";
|
|
19
|
-
import * as path2 from "node:path";
|
|
17
|
+
// src/subcommands/handlers/acp-agents.ts
|
|
20
18
|
import {
|
|
21
19
|
defaultPermissionPolicy,
|
|
22
20
|
EnsembleRegistry,
|
|
@@ -27,6 +25,304 @@ import {
|
|
|
27
25
|
runEnsemble,
|
|
28
26
|
runOneAcpTask
|
|
29
27
|
} from "@wrongstack/acp";
|
|
28
|
+
function acpOverrides(deps) {
|
|
29
|
+
return deps.config.acp?.agents;
|
|
30
|
+
}
|
|
31
|
+
async function loadLive(deps) {
|
|
32
|
+
return deps.paths ? loadCachedAcpRegistry(deps.paths) : null;
|
|
33
|
+
}
|
|
34
|
+
async function listACPAgents(deps) {
|
|
35
|
+
const detected = await new EnsembleRegistry().list();
|
|
36
|
+
const live = await loadLive(deps);
|
|
37
|
+
deps.renderer.write(`${formatAcpAgentList({ live, detected })}
|
|
38
|
+
`);
|
|
39
|
+
return 0;
|
|
40
|
+
}
|
|
41
|
+
async function syncACPRegistry(deps) {
|
|
42
|
+
if (!deps.paths) {
|
|
43
|
+
deps.renderer.writeError("Cannot sync: no cache directory available.\n");
|
|
44
|
+
return 1;
|
|
45
|
+
}
|
|
46
|
+
deps.renderer.writeInfo("Fetching the official ACP registry\u2026\n");
|
|
47
|
+
try {
|
|
48
|
+
const { count, location } = await refreshAcpRegistry(deps.paths);
|
|
49
|
+
deps.renderer.write(`Synced ${count} agents from the official ACP registry.
|
|
50
|
+
`);
|
|
51
|
+
deps.renderer.writeInfo(`Cached at ${location}
|
|
52
|
+
`);
|
|
53
|
+
return 0;
|
|
54
|
+
} catch (err) {
|
|
55
|
+
deps.renderer.writeError(
|
|
56
|
+
`ACP registry sync failed: ${err instanceof Error ? err.message : String(err)}
|
|
57
|
+
`
|
|
58
|
+
);
|
|
59
|
+
deps.renderer.write("The bundled offline catalog is still available via `wstack acp list`.\n");
|
|
60
|
+
return 1;
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
async function spawnACPAgent(args, deps) {
|
|
64
|
+
const [subagentId, ...taskParts] = args;
|
|
65
|
+
if (!subagentId) {
|
|
66
|
+
deps.renderer.writeError("Usage: wstack acp spawn <agent-id> <task>\n");
|
|
67
|
+
deps.renderer.write("Run `wstack acp list` to see available agents.\n");
|
|
68
|
+
return 1;
|
|
69
|
+
}
|
|
70
|
+
const task = taskParts.join(" ");
|
|
71
|
+
if (!task) {
|
|
72
|
+
deps.renderer.writeError("Usage: wstack acp spawn <agent-id> <task>\n");
|
|
73
|
+
deps.renderer.write("Task description is required.\n");
|
|
74
|
+
return 1;
|
|
75
|
+
}
|
|
76
|
+
const live = await loadLive(deps);
|
|
77
|
+
const cmd = resolveAcpAgentCommand(subagentId, acpOverrides(deps), live?.byId);
|
|
78
|
+
if (!cmd) {
|
|
79
|
+
deps.renderer.writeError(`Unknown ACP agent: ${subagentId}
|
|
80
|
+
`);
|
|
81
|
+
deps.renderer.write("Run `wstack acp list` (or `wstack acp sync`) to see available agents.\n");
|
|
82
|
+
return 1;
|
|
83
|
+
}
|
|
84
|
+
deps.renderer.writeInfo(`Spawning ACP agent '${subagentId}'\u2026
|
|
85
|
+
`);
|
|
86
|
+
const ac = new AbortController();
|
|
87
|
+
const cleanup = () => ac.abort();
|
|
88
|
+
process.on("SIGINT", cleanup);
|
|
89
|
+
process.on("SIGTERM", cleanup);
|
|
90
|
+
try {
|
|
91
|
+
deps.renderer.writeInfo("Running task\u2026\n");
|
|
92
|
+
const result = await runOneAcpTask({
|
|
93
|
+
command: cmd.command,
|
|
94
|
+
...cmd.args !== void 0 ? { args: cmd.args } : {},
|
|
95
|
+
...cmd.env !== void 0 ? { env: cmd.env } : {},
|
|
96
|
+
role: subagentId,
|
|
97
|
+
task,
|
|
98
|
+
signal: ac.signal,
|
|
99
|
+
permissionPolicy: defaultPermissionPolicy,
|
|
100
|
+
onProgress: (event) => {
|
|
101
|
+
const line = formatProgress(event);
|
|
102
|
+
if (line) deps.renderer.writeInfo(` ${line}
|
|
103
|
+
`);
|
|
104
|
+
}
|
|
105
|
+
});
|
|
106
|
+
deps.renderer.write("\n--- Result ---\n");
|
|
107
|
+
deps.renderer.write(result.result.length > 0 ? result.result : "no result");
|
|
108
|
+
deps.renderer.write("\n---------------\n");
|
|
109
|
+
deps.renderer.writeInfo(
|
|
110
|
+
`Done. iterations=${result.iterations} toolCalls=${result.toolCalls}
|
|
111
|
+
`
|
|
112
|
+
);
|
|
113
|
+
return 0;
|
|
114
|
+
} catch (err) {
|
|
115
|
+
const e = err;
|
|
116
|
+
const detail = e.kind ? `[${e.kind}] ` : "";
|
|
117
|
+
const message = e.message ?? (err instanceof Error ? err.message : String(err));
|
|
118
|
+
deps.renderer.writeError(`ACP agent error: ${detail}${message}
|
|
119
|
+
`);
|
|
120
|
+
return 1;
|
|
121
|
+
} finally {
|
|
122
|
+
process.off("SIGINT", cleanup);
|
|
123
|
+
process.off("SIGTERM", cleanup);
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
function formatProgress(event) {
|
|
127
|
+
switch (event.type) {
|
|
128
|
+
case "tool_call":
|
|
129
|
+
return `\u25B8 ${event.toolCall.title} (${event.toolCall.status})`;
|
|
130
|
+
case "tool_call_update":
|
|
131
|
+
return event.toolCall.status === "completed" || event.toolCall.status === "failed" ? ` \u21B3 ${event.toolCall.title}: ${event.toolCall.status}` : "";
|
|
132
|
+
case "diff":
|
|
133
|
+
return `\u270E ${event.diff.path}${event.diff.oldText === null ? " (new)" : ""}`;
|
|
134
|
+
case "plan":
|
|
135
|
+
return `\u2630 plan: ${event.entries.length} step(s)`;
|
|
136
|
+
default:
|
|
137
|
+
return "";
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
async function parallelACPAgents(args, deps) {
|
|
141
|
+
const [csv, ...taskParts] = args;
|
|
142
|
+
if (!csv) {
|
|
143
|
+
deps.renderer.writeError("Usage: wstack acp parallel <agent-id-csv> <task>\n");
|
|
144
|
+
deps.renderer.write('Example: wstack acp parallel claude-code,gemini-cli "review this diff"\n');
|
|
145
|
+
return 1;
|
|
146
|
+
}
|
|
147
|
+
const task = taskParts.join(" ");
|
|
148
|
+
if (!task) {
|
|
149
|
+
deps.renderer.writeError("Usage: wstack acp parallel <agent-id-csv> <task>\n");
|
|
150
|
+
deps.renderer.writeError("Task description is required.\n");
|
|
151
|
+
return 1;
|
|
152
|
+
}
|
|
153
|
+
const ac = new AbortController();
|
|
154
|
+
const onSignal = () => ac.abort();
|
|
155
|
+
process.on("SIGINT", onSignal);
|
|
156
|
+
process.on("SIGTERM", onSignal);
|
|
157
|
+
try {
|
|
158
|
+
const overrides = acpOverrides(deps);
|
|
159
|
+
const live = await loadLive(deps);
|
|
160
|
+
const result = await runEnsemble({
|
|
161
|
+
agentIds: csv,
|
|
162
|
+
task,
|
|
163
|
+
resolveCmd: (id) => resolveAcpAgentCommand(id, overrides, live?.byId),
|
|
164
|
+
signal: ac.signal,
|
|
165
|
+
onProgress: (agentId, event) => {
|
|
166
|
+
const line = formatProgress(event);
|
|
167
|
+
if (line) deps.renderer.writeInfo(` [${agentId}] ${line}
|
|
168
|
+
`);
|
|
169
|
+
}
|
|
170
|
+
});
|
|
171
|
+
const skipped = result.results.filter((r) => r.status === "skipped");
|
|
172
|
+
if (skipped.length > 0) {
|
|
173
|
+
deps.renderer.writeWarning(
|
|
174
|
+
`Skipping ${skipped.length} agent(s) not installed: ${skipped.map((s) => `${s.agentId} (${s.reason ?? "not installed"})`).join(", ")}
|
|
175
|
+
`
|
|
176
|
+
);
|
|
177
|
+
}
|
|
178
|
+
if (result.summary.succeeded + result.summary.failed + result.summary.cancelled === 0) {
|
|
179
|
+
deps.renderer.writeError("No installed agents to run.\n");
|
|
180
|
+
deps.renderer.write("Run `wstack acp list` to see what is available.\n");
|
|
181
|
+
return 1;
|
|
182
|
+
}
|
|
183
|
+
const fannedOut = result.results.filter((r) => r.status !== "skipped").map((r) => r.agentId).join(", ");
|
|
184
|
+
deps.renderer.writeInfo(
|
|
185
|
+
`Fanning out to ${result.summary.succeeded + result.summary.failed + result.summary.cancelled} agent(s): ${fannedOut}
|
|
186
|
+
`
|
|
187
|
+
);
|
|
188
|
+
deps.renderer.writeInfo(`Task: ${result.task}
|
|
189
|
+
|
|
190
|
+
`);
|
|
191
|
+
for (const r of result.results) {
|
|
192
|
+
if (r.status === "skipped") continue;
|
|
193
|
+
deps.renderer.write(`
|
|
194
|
+
=== ${r.agentId} ===
|
|
195
|
+
`);
|
|
196
|
+
if (r.status === "success") {
|
|
197
|
+
deps.renderer.write(r.result && r.result.length > 0 ? r.result : "(no result)");
|
|
198
|
+
deps.renderer.write(
|
|
199
|
+
`
|
|
200
|
+
[${r.agentId}] success ${r.durationMs}ms iterations=${r.iterations} toolCalls=${r.toolCalls}
|
|
201
|
+
`
|
|
202
|
+
);
|
|
203
|
+
} else if (r.status === "failed") {
|
|
204
|
+
deps.renderer.writeError(
|
|
205
|
+
`[${r.error?.kind ?? "unknown"}] ${r.error?.message ?? "failed"}
|
|
206
|
+
`
|
|
207
|
+
);
|
|
208
|
+
deps.renderer.write(`[${r.agentId}] failed ${r.durationMs}ms
|
|
209
|
+
`);
|
|
210
|
+
} else {
|
|
211
|
+
deps.renderer.writeError(
|
|
212
|
+
`[${r.error?.kind ?? "aborted"}] ${r.error?.message ?? "cancelled"}
|
|
213
|
+
`
|
|
214
|
+
);
|
|
215
|
+
deps.renderer.write(`[${r.agentId}] cancelled ${r.durationMs}ms
|
|
216
|
+
`);
|
|
217
|
+
}
|
|
218
|
+
}
|
|
219
|
+
const { succeeded, failed, cancelled, skipped: skip } = result.summary;
|
|
220
|
+
deps.renderer.write(
|
|
221
|
+
`
|
|
222
|
+
Parallel summary: ${succeeded} succeeded, ${failed} failed, ${cancelled} cancelled, ${skip} skipped.
|
|
223
|
+
`
|
|
224
|
+
);
|
|
225
|
+
return succeeded > 0 ? 0 : 1;
|
|
226
|
+
} finally {
|
|
227
|
+
process.off("SIGINT", onSignal);
|
|
228
|
+
process.off("SIGTERM", onSignal);
|
|
229
|
+
}
|
|
230
|
+
}
|
|
231
|
+
async function probeACPAgents(args, deps) {
|
|
232
|
+
const csv = args.join(" ");
|
|
233
|
+
let ids;
|
|
234
|
+
if (csv) {
|
|
235
|
+
ids = csv.split(",").flatMap((s) => s.split(/\s+/)).map((s) => s.trim()).filter(Boolean);
|
|
236
|
+
} else {
|
|
237
|
+
const detected = await new EnsembleRegistry().list();
|
|
238
|
+
ids = detected.filter((a) => a.installed).map((a) => a.id);
|
|
239
|
+
}
|
|
240
|
+
if (ids.length === 0) {
|
|
241
|
+
deps.renderer.writeError("No installed agents to probe.\n");
|
|
242
|
+
return 1;
|
|
243
|
+
}
|
|
244
|
+
const overrides = acpOverrides(deps);
|
|
245
|
+
const live = await loadLive(deps);
|
|
246
|
+
deps.renderer.writeInfo(
|
|
247
|
+
`Probing ${ids.length} agent(s)\u2026 (npx-based agents may download on first run)
|
|
248
|
+
`
|
|
249
|
+
);
|
|
250
|
+
const results = await probeAcpAgents({
|
|
251
|
+
agentIds: ids,
|
|
252
|
+
resolveCmd: (id) => resolveAcpAgentCommand(id, overrides, live?.byId),
|
|
253
|
+
projectRoot: deps.cwd ?? process.cwd(),
|
|
254
|
+
onProgress: (id, r) => deps.renderer.writeInfo(` ${r.ok ? "\u2713" : "\u2717"} ${id} (${r.ms}ms)
|
|
255
|
+
`)
|
|
256
|
+
});
|
|
257
|
+
deps.renderer.write("\nACP handshake probe:\n\n");
|
|
258
|
+
for (const r of results) {
|
|
259
|
+
if (r.ok) {
|
|
260
|
+
const info = r.agentInfo ? ` \u2014 ${r.agentInfo.name} ${r.agentInfo.version}` : "";
|
|
261
|
+
deps.renderer.write(` \u2713 ${r.id.padEnd(16)} ok ${r.ms}ms${info}
|
|
262
|
+
`);
|
|
263
|
+
} else {
|
|
264
|
+
deps.renderer.write(` \u2717 ${r.id.padEnd(16)} ${r.error ?? "failed"} (${r.ms}ms)
|
|
265
|
+
`);
|
|
266
|
+
}
|
|
267
|
+
}
|
|
268
|
+
const ok = results.filter((r) => r.ok).length;
|
|
269
|
+
deps.renderer.write(`
|
|
270
|
+
${ok} of ${results.length} agents completed the ACP handshake.
|
|
271
|
+
`);
|
|
272
|
+
return ok > 0 ? 0 : 1;
|
|
273
|
+
}
|
|
274
|
+
async function benchACPAgents(args, deps) {
|
|
275
|
+
const checkFs = deps.flags?.fs === true || deps.flags?.fs === "true" || args.includes("--fs");
|
|
276
|
+
const csv = args.filter((a) => a !== "--fs").join(" ");
|
|
277
|
+
let ids;
|
|
278
|
+
if (csv) {
|
|
279
|
+
ids = csv.split(",").flatMap((s) => s.split(/\s+/)).map((s) => s.trim()).filter(Boolean);
|
|
280
|
+
} else {
|
|
281
|
+
const detected = await new EnsembleRegistry().list();
|
|
282
|
+
ids = detected.filter((a) => a.installed).map((a) => a.id);
|
|
283
|
+
}
|
|
284
|
+
if (ids.length === 0) {
|
|
285
|
+
deps.renderer.writeError("No installed agents to bench.\n");
|
|
286
|
+
deps.renderer.write("Pass ids explicitly: `wstack acp bench gemini-cli,codex-cli`.\n");
|
|
287
|
+
return 1;
|
|
288
|
+
}
|
|
289
|
+
const ac = new AbortController();
|
|
290
|
+
const onSignal = () => ac.abort();
|
|
291
|
+
process.on("SIGINT", onSignal);
|
|
292
|
+
process.on("SIGTERM", onSignal);
|
|
293
|
+
try {
|
|
294
|
+
const overrides = acpOverrides(deps);
|
|
295
|
+
const live = await loadLive(deps);
|
|
296
|
+
deps.renderer.writeInfo(
|
|
297
|
+
`Benching ${ids.length} agent(s)${checkFs ? " (with fs check)" : ""}: ${ids.join(", ")}\u2026
|
|
298
|
+
`
|
|
299
|
+
);
|
|
300
|
+
const result = await runAcpBench({
|
|
301
|
+
agentIds: ids,
|
|
302
|
+
resolveCmd: (id) => resolveAcpAgentCommand(id, overrides, live?.byId),
|
|
303
|
+
projectRoot: deps.cwd ?? process.cwd(),
|
|
304
|
+
checkFs,
|
|
305
|
+
signal: ac.signal,
|
|
306
|
+
onProgress: (agentId, phase, r) => {
|
|
307
|
+
if (phase === "start") deps.renderer.writeInfo(` \u25B8 ${agentId}\u2026
|
|
308
|
+
`);
|
|
309
|
+
else if (r) deps.renderer.writeInfo(` \u21B3 ${agentId}: ${r.status}
|
|
310
|
+
`);
|
|
311
|
+
}
|
|
312
|
+
});
|
|
313
|
+
deps.renderer.write(`
|
|
314
|
+
${renderAcpBenchText(result)}
|
|
315
|
+
`);
|
|
316
|
+
return result.summary.pass > 0 ? 0 : 1;
|
|
317
|
+
} finally {
|
|
318
|
+
process.off("SIGINT", onSignal);
|
|
319
|
+
process.off("SIGTERM", onSignal);
|
|
320
|
+
}
|
|
321
|
+
}
|
|
322
|
+
|
|
323
|
+
// src/subcommands/handlers/acp-server.ts
|
|
324
|
+
import { randomBytes } from "node:crypto";
|
|
325
|
+
import * as path2 from "node:path";
|
|
30
326
|
import {
|
|
31
327
|
ACPProtocolHandler,
|
|
32
328
|
ACPSessionStore,
|
|
@@ -564,122 +860,34 @@ function createAcpConnectionGate(opts) {
|
|
|
564
860
|
};
|
|
565
861
|
const tokenOk = (supplied) => {
|
|
566
862
|
if (!supplied) return false;
|
|
567
|
-
const a = Buffer.from(supplied);
|
|
568
|
-
if (a.length !== expected.length) return false;
|
|
569
|
-
return timingSafeEqual(a, expected);
|
|
570
|
-
};
|
|
571
|
-
return {
|
|
572
|
-
check(req, liveConnections) {
|
|
573
|
-
const origin = req.headers.origin;
|
|
574
|
-
if (origin && origin !== `http://${host}:${port}` && origin !== `ws://${host}:${port}`) {
|
|
575
|
-
return { ok: false, code: 1008, reason: "cross-origin forbidden" };
|
|
576
|
-
}
|
|
577
|
-
if (!isLoopbackHostHeader(req.headers.host ?? "")) {
|
|
578
|
-
return { ok: false, code: 1008, reason: "untrusted host header" };
|
|
579
|
-
}
|
|
580
|
-
if (!tokenOk(tokenFromRequest(req))) {
|
|
581
|
-
return { ok: false, code: 1008, reason: "unauthorized" };
|
|
582
|
-
}
|
|
583
|
-
if (liveConnections >= maxConnections) {
|
|
584
|
-
return { ok: false, code: 1013, reason: "too many connections" };
|
|
585
|
-
}
|
|
586
|
-
return OK;
|
|
587
|
-
}
|
|
588
|
-
};
|
|
589
|
-
}
|
|
590
|
-
|
|
591
|
-
// src/subcommands/handlers/acp.ts
|
|
592
|
-
function pendingLeaderDeliveries(sessionId) {
|
|
593
|
-
return leaderDeliveryHub.pending(sessionId);
|
|
594
|
-
}
|
|
595
|
-
function acpOverrides(deps) {
|
|
596
|
-
return deps.config.acp?.agents;
|
|
597
|
-
}
|
|
598
|
-
async function loadLive(deps) {
|
|
599
|
-
return deps.paths ? loadCachedAcpRegistry(deps.paths) : null;
|
|
600
|
-
}
|
|
601
|
-
var acpCmd = async (args, deps) => {
|
|
602
|
-
const sub = args[0];
|
|
603
|
-
const loginIndex = sub === "auth" ? 0 : (sub === "server" || sub === "serve") && args[1] === "auth" ? 1 : -1;
|
|
604
|
-
if (loginIndex >= 0) {
|
|
605
|
-
const { authCmd } = await import("./auth-SUHKFFO4.js");
|
|
606
|
-
return authCmd(args.slice(loginIndex + 1), deps);
|
|
607
|
-
}
|
|
608
|
-
if (!sub || sub === "server" || sub === "serve") {
|
|
609
|
-
return runACPServer(deps);
|
|
610
|
-
}
|
|
611
|
-
if (sub === "help") {
|
|
612
|
-
deps.renderer.write(`wstack acp \u2014 ACP (Agent Client Protocol) integration
|
|
613
|
-
|
|
614
|
-
Usage:
|
|
615
|
-
wstack acp Start WrongStack as an ACP server (blocks)
|
|
616
|
-
wstack acp server Same as above
|
|
617
|
-
wstack acp list List available ACP agents
|
|
618
|
-
wstack acp sync Pull the official agentclientprotocol/registry into cache
|
|
619
|
-
wstack acp spawn <id> <task>
|
|
620
|
-
Spawn an ACP agent as a subagent and wait for result
|
|
621
|
-
wstack acp parallel <agent-id-csv> <task>
|
|
622
|
-
Fan a task out to multiple ACP agents in parallel
|
|
623
|
-
and aggregate the results
|
|
624
|
-
wstack acp probe [agent-id-csv]
|
|
625
|
-
Handshake-test agents (bounded concurrency). Defaults
|
|
626
|
-
to all installed agents.
|
|
627
|
-
wstack acp bench [agent-id-csv] [--fs]
|
|
628
|
-
End-to-end verify each agent (handshake \u2192 prompt \u2192
|
|
629
|
-
marker, optional fs check) and print a graded report.
|
|
630
|
-
Defaults to all installed agents.
|
|
631
|
-
wstack acp help Show this help
|
|
632
|
-
|
|
633
|
-
ACP Mode:
|
|
634
|
-
When run as \`wstack acp\`. WrongStack acts as an ACP-compatible agent driven
|
|
635
|
-
by your configured model provider. ACP clients (Zed, JetBrains, VS Code)
|
|
636
|
-
spawn it as a subprocess and communicate over stdio JSON-RPC. Run
|
|
637
|
-
\`wstack auth\` first to configure a provider, or pass \`--echo\` for a no-op
|
|
638
|
-
connectivity test that needs no provider. Press Ctrl+C to stop.
|
|
639
|
-
|
|
640
|
-
Transports:
|
|
641
|
-
(default) stdio JSON-RPC (the usual editor-spawned-subprocess mode)
|
|
642
|
-
--ws[=port] serve over WebSocket on 127.0.0.1:<port> (default 8889) \u2014
|
|
643
|
-
full-duplex, so updates/permission prompts stream live.
|
|
644
|
-
|
|
645
|
-
spawn:
|
|
646
|
-
Spawns a named ACP agent (claude-code, gemini-cli, codex-cli, copilot,
|
|
647
|
-
cline, goose, openhands, qwen-code, kiro-cli, opencode, mistral-vibe,
|
|
648
|
-
cursor) with the given task and waits for its result.
|
|
649
|
-
Example: wstack acp spawn cline "fix the login bug"
|
|
650
|
-
|
|
651
|
-
parallel:
|
|
652
|
-
Runs the same task on a comma-separated list of ACP agents concurrently.
|
|
653
|
-
Example: wstack acp parallel claude-code,gemini-cli,codex-cli "review this diff"
|
|
654
|
-
Each agent's result is rendered under a clearly-marked header. Returns 0
|
|
655
|
-
if at least one agent succeeds, 1 if all fail. Agents that aren't
|
|
656
|
-
installed are skipped with a warning.
|
|
657
|
-
`);
|
|
658
|
-
return 0;
|
|
659
|
-
}
|
|
660
|
-
if (sub === "list") {
|
|
661
|
-
return listACPAgents(deps);
|
|
662
|
-
}
|
|
663
|
-
if (sub === "sync") {
|
|
664
|
-
return syncACPRegistry(deps);
|
|
665
|
-
}
|
|
666
|
-
if (sub === "spawn") {
|
|
667
|
-
return spawnACPAgent(args.slice(1), deps);
|
|
668
|
-
}
|
|
669
|
-
if (sub === "parallel") {
|
|
670
|
-
return parallelACPAgents(args.slice(1), deps);
|
|
671
|
-
}
|
|
672
|
-
if (sub === "probe") {
|
|
673
|
-
return probeACPAgents(args.slice(1), deps);
|
|
674
|
-
}
|
|
675
|
-
if (sub === "bench") {
|
|
676
|
-
return benchACPAgents(args.slice(1), deps);
|
|
677
|
-
}
|
|
678
|
-
deps.renderer.writeError(`Unknown acp subcommand: ${sub}
|
|
679
|
-
`);
|
|
680
|
-
deps.renderer.write("Run `wstack acp help` for usage.\n");
|
|
681
|
-
return 1;
|
|
682
|
-
};
|
|
863
|
+
const a = Buffer.from(supplied);
|
|
864
|
+
if (a.length !== expected.length) return false;
|
|
865
|
+
return timingSafeEqual(a, expected);
|
|
866
|
+
};
|
|
867
|
+
return {
|
|
868
|
+
check(req, liveConnections) {
|
|
869
|
+
const origin = req.headers.origin;
|
|
870
|
+
if (origin && origin !== `http://${host}:${port}` && origin !== `ws://${host}:${port}`) {
|
|
871
|
+
return { ok: false, code: 1008, reason: "cross-origin forbidden" };
|
|
872
|
+
}
|
|
873
|
+
if (!isLoopbackHostHeader(req.headers.host ?? "")) {
|
|
874
|
+
return { ok: false, code: 1008, reason: "untrusted host header" };
|
|
875
|
+
}
|
|
876
|
+
if (!tokenOk(tokenFromRequest(req))) {
|
|
877
|
+
return { ok: false, code: 1008, reason: "unauthorized" };
|
|
878
|
+
}
|
|
879
|
+
if (liveConnections >= maxConnections) {
|
|
880
|
+
return { ok: false, code: 1013, reason: "too many connections" };
|
|
881
|
+
}
|
|
882
|
+
return OK;
|
|
883
|
+
}
|
|
884
|
+
};
|
|
885
|
+
}
|
|
886
|
+
|
|
887
|
+
// src/subcommands/handlers/acp-server.ts
|
|
888
|
+
function pendingLeaderDeliveries(sessionId) {
|
|
889
|
+
return leaderDeliveryHub.pending(sessionId);
|
|
890
|
+
}
|
|
683
891
|
function parseWsPort(flag) {
|
|
684
892
|
if (flag === void 0 || flag === false) return null;
|
|
685
893
|
if (flag === true || flag === "true") return 8889;
|
|
@@ -823,370 +1031,166 @@ async function runACPServer(deps) {
|
|
|
823
1031
|
deps.renderer.writeError(
|
|
824
1032
|
"No model provider configured. Run `wstack auth` before starting ACP, or use `--echo` for a connectivity test.\n"
|
|
825
1033
|
);
|
|
826
|
-
return 1;
|
|
827
|
-
}
|
|
828
|
-
let stdioHqTelemetry;
|
|
829
|
-
const server = new WrongStackACPServer(
|
|
830
|
-
echo ? {} : (() => {
|
|
831
|
-
let agentFor;
|
|
832
|
-
try {
|
|
833
|
-
agentFor = buildAcpServerAgentFactory(deps);
|
|
834
|
-
} catch (err) {
|
|
835
|
-
if (err instanceof AcpServerConfigError) {
|
|
836
|
-
deps.renderer.writeError(`${err.message}
|
|
837
|
-
`);
|
|
838
|
-
}
|
|
839
|
-
throw err;
|
|
840
|
-
}
|
|
841
|
-
stdioHqTelemetry = startAcpHqTelemetry({
|
|
842
|
-
projectRoot: deps.cwd ?? process.cwd(),
|
|
843
|
-
projectName: path2.basename(deps.cwd ?? process.cwd()),
|
|
844
|
-
appConfig: deps.config
|
|
845
|
-
});
|
|
846
|
-
const stdioAgentFactory = agentFor;
|
|
847
|
-
const turn = makeACPServerAgentTurn({
|
|
848
|
-
agentFor: stdioHqTelemetry.wrapAgentFactory(agentFor),
|
|
849
|
-
pendingDeliveries: pendingLeaderDeliveries
|
|
850
|
-
});
|
|
851
|
-
const store = deps.paths?.projectDir ? new ACPSessionStore({ dir: path2.join(deps.paths.projectDir, "acp-sessions") }) : void 0;
|
|
852
|
-
return {
|
|
853
|
-
runTurn: turn,
|
|
854
|
-
replayFor: turn.replay,
|
|
855
|
-
seedFor: turn.seed,
|
|
856
|
-
disposeFor: composeDispose(
|
|
857
|
-
stdioHqTelemetry.wrapDispose(turn.dispose),
|
|
858
|
-
stdioAgentFactory
|
|
859
|
-
),
|
|
860
|
-
...store ? { store } : {}
|
|
861
|
-
};
|
|
862
|
-
})()
|
|
863
|
-
);
|
|
864
|
-
if (echo) {
|
|
865
|
-
deps.renderer.writeInfo(
|
|
866
|
-
"ACP server starting in --echo mode (no-op turn; no provider needed).\n"
|
|
867
|
-
);
|
|
868
|
-
} else {
|
|
869
|
-
deps.renderer.writeInfo(
|
|
870
|
-
`Starting WrongStack ACP server (${deps.config.provider}/${deps.config.model})\u2026
|
|
871
|
-
`
|
|
872
|
-
);
|
|
873
|
-
deps.renderer.writeInfo(
|
|
874
|
-
"Waiting for an ACP client connection on stdin/stdout. Press Ctrl+C to stop.\n"
|
|
875
|
-
);
|
|
876
|
-
}
|
|
877
|
-
createGracefulShutdown({
|
|
878
|
-
run: async () => {
|
|
879
|
-
try {
|
|
880
|
-
await server.stop();
|
|
881
|
-
} catch (err) {
|
|
882
|
-
deps.renderer.writeError(
|
|
883
|
-
`ACP stop failed: ${err instanceof Error ? err.message : String(err)}
|
|
884
|
-
`
|
|
885
|
-
);
|
|
886
|
-
}
|
|
887
|
-
stdioHqTelemetry?.stop();
|
|
888
|
-
}
|
|
889
|
-
}).install();
|
|
890
|
-
try {
|
|
891
|
-
await server.start();
|
|
892
|
-
} catch (err) {
|
|
893
|
-
deps.renderer.writeError(
|
|
894
|
-
`ACP server error: ${err instanceof Error ? err.message : String(err)}
|
|
895
|
-
`
|
|
896
|
-
);
|
|
897
|
-
return 1;
|
|
898
|
-
}
|
|
899
|
-
return 0;
|
|
900
|
-
}
|
|
901
|
-
async function listACPAgents(deps) {
|
|
902
|
-
const detected = await new EnsembleRegistry().list();
|
|
903
|
-
const live = await loadLive(deps);
|
|
904
|
-
deps.renderer.write(`${formatAcpAgentList({ live, detected })}
|
|
905
|
-
`);
|
|
906
|
-
return 0;
|
|
907
|
-
}
|
|
908
|
-
async function syncACPRegistry(deps) {
|
|
909
|
-
if (!deps.paths) {
|
|
910
|
-
deps.renderer.writeError("Cannot sync: no cache directory available.\n");
|
|
911
|
-
return 1;
|
|
912
|
-
}
|
|
913
|
-
deps.renderer.writeInfo("Fetching the official ACP registry\u2026\n");
|
|
914
|
-
try {
|
|
915
|
-
const { count, location } = await refreshAcpRegistry(deps.paths);
|
|
916
|
-
deps.renderer.write(`Synced ${count} agents from the official ACP registry.
|
|
917
|
-
`);
|
|
918
|
-
deps.renderer.writeInfo(`Cached at ${location}
|
|
919
|
-
`);
|
|
920
|
-
return 0;
|
|
921
|
-
} catch (err) {
|
|
922
|
-
deps.renderer.writeError(
|
|
923
|
-
`ACP registry sync failed: ${err instanceof Error ? err.message : String(err)}
|
|
924
|
-
`
|
|
925
|
-
);
|
|
926
|
-
deps.renderer.write("The bundled offline catalog is still available via `wstack acp list`.\n");
|
|
927
|
-
return 1;
|
|
928
|
-
}
|
|
929
|
-
}
|
|
930
|
-
async function spawnACPAgent(args, deps) {
|
|
931
|
-
const [subagentId, ...taskParts] = args;
|
|
932
|
-
if (!subagentId) {
|
|
933
|
-
deps.renderer.writeError("Usage: wstack acp spawn <agent-id> <task>\n");
|
|
934
|
-
deps.renderer.write("Run `wstack acp list` to see available agents.\n");
|
|
935
|
-
return 1;
|
|
936
|
-
}
|
|
937
|
-
const task = taskParts.join(" ");
|
|
938
|
-
if (!task) {
|
|
939
|
-
deps.renderer.writeError("Usage: wstack acp spawn <agent-id> <task>\n");
|
|
940
|
-
deps.renderer.write("Task description is required.\n");
|
|
941
|
-
return 1;
|
|
942
|
-
}
|
|
943
|
-
const live = await loadLive(deps);
|
|
944
|
-
const cmd = resolveAcpAgentCommand(subagentId, acpOverrides(deps), live?.byId);
|
|
945
|
-
if (!cmd) {
|
|
946
|
-
deps.renderer.writeError(`Unknown ACP agent: ${subagentId}
|
|
947
|
-
`);
|
|
948
|
-
deps.renderer.write("Run `wstack acp list` (or `wstack acp sync`) to see available agents.\n");
|
|
949
|
-
return 1;
|
|
950
|
-
}
|
|
951
|
-
deps.renderer.writeInfo(`Spawning ACP agent '${subagentId}'\u2026
|
|
952
|
-
`);
|
|
953
|
-
const ac = new AbortController();
|
|
954
|
-
const cleanup = () => ac.abort();
|
|
955
|
-
process.on("SIGINT", cleanup);
|
|
956
|
-
process.on("SIGTERM", cleanup);
|
|
957
|
-
try {
|
|
958
|
-
deps.renderer.writeInfo("Running task\u2026\n");
|
|
959
|
-
const result = await runOneAcpTask({
|
|
960
|
-
command: cmd.command,
|
|
961
|
-
...cmd.args !== void 0 ? { args: cmd.args } : {},
|
|
962
|
-
...cmd.env !== void 0 ? { env: cmd.env } : {},
|
|
963
|
-
role: subagentId,
|
|
964
|
-
task,
|
|
965
|
-
signal: ac.signal,
|
|
966
|
-
permissionPolicy: defaultPermissionPolicy,
|
|
967
|
-
onProgress: (event) => {
|
|
968
|
-
const line = formatProgress(event);
|
|
969
|
-
if (line) deps.renderer.writeInfo(` ${line}
|
|
970
|
-
`);
|
|
971
|
-
}
|
|
972
|
-
});
|
|
973
|
-
deps.renderer.write("\n--- Result ---\n");
|
|
974
|
-
deps.renderer.write(result.result.length > 0 ? result.result : "no result");
|
|
975
|
-
deps.renderer.write("\n---------------\n");
|
|
976
|
-
deps.renderer.writeInfo(
|
|
977
|
-
`Done. iterations=${result.iterations} toolCalls=${result.toolCalls}
|
|
978
|
-
`
|
|
979
|
-
);
|
|
980
|
-
return 0;
|
|
981
|
-
} catch (err) {
|
|
982
|
-
const e = err;
|
|
983
|
-
const detail = e.kind ? `[${e.kind}] ` : "";
|
|
984
|
-
const message = e.message ?? (err instanceof Error ? err.message : String(err));
|
|
985
|
-
deps.renderer.writeError(`ACP agent error: ${detail}${message}
|
|
986
|
-
`);
|
|
987
|
-
return 1;
|
|
988
|
-
} finally {
|
|
989
|
-
process.off("SIGINT", cleanup);
|
|
990
|
-
process.off("SIGTERM", cleanup);
|
|
991
|
-
}
|
|
992
|
-
}
|
|
993
|
-
function formatProgress(event) {
|
|
994
|
-
switch (event.type) {
|
|
995
|
-
case "tool_call":
|
|
996
|
-
return `\u25B8 ${event.toolCall.title} (${event.toolCall.status})`;
|
|
997
|
-
case "tool_call_update":
|
|
998
|
-
return event.toolCall.status === "completed" || event.toolCall.status === "failed" ? ` \u21B3 ${event.toolCall.title}: ${event.toolCall.status}` : "";
|
|
999
|
-
case "diff":
|
|
1000
|
-
return `\u270E ${event.diff.path}${event.diff.oldText === null ? " (new)" : ""}`;
|
|
1001
|
-
case "plan":
|
|
1002
|
-
return `\u2630 plan: ${event.entries.length} step(s)`;
|
|
1003
|
-
default:
|
|
1004
|
-
return "";
|
|
1005
|
-
}
|
|
1006
|
-
}
|
|
1007
|
-
async function parallelACPAgents(args, deps) {
|
|
1008
|
-
const [csv, ...taskParts] = args;
|
|
1009
|
-
if (!csv) {
|
|
1010
|
-
deps.renderer.writeError("Usage: wstack acp parallel <agent-id-csv> <task>\n");
|
|
1011
|
-
deps.renderer.write('Example: wstack acp parallel claude-code,gemini-cli "review this diff"\n');
|
|
1012
|
-
return 1;
|
|
1013
|
-
}
|
|
1014
|
-
const task = taskParts.join(" ");
|
|
1015
|
-
if (!task) {
|
|
1016
|
-
deps.renderer.writeError("Usage: wstack acp parallel <agent-id-csv> <task>\n");
|
|
1017
|
-
deps.renderer.writeError("Task description is required.\n");
|
|
1018
|
-
return 1;
|
|
1019
|
-
}
|
|
1020
|
-
const ac = new AbortController();
|
|
1021
|
-
const onSignal = () => ac.abort();
|
|
1022
|
-
process.on("SIGINT", onSignal);
|
|
1023
|
-
process.on("SIGTERM", onSignal);
|
|
1024
|
-
try {
|
|
1025
|
-
const overrides = acpOverrides(deps);
|
|
1026
|
-
const live = await loadLive(deps);
|
|
1027
|
-
const result = await runEnsemble({
|
|
1028
|
-
agentIds: csv,
|
|
1029
|
-
task,
|
|
1030
|
-
resolveCmd: (id) => resolveAcpAgentCommand(id, overrides, live?.byId),
|
|
1031
|
-
signal: ac.signal,
|
|
1032
|
-
onProgress: (agentId, event) => {
|
|
1033
|
-
const line = formatProgress(event);
|
|
1034
|
-
if (line) deps.renderer.writeInfo(` [${agentId}] ${line}
|
|
1035
|
-
`);
|
|
1036
|
-
}
|
|
1037
|
-
});
|
|
1038
|
-
const skipped = result.results.filter((r) => r.status === "skipped");
|
|
1039
|
-
if (skipped.length > 0) {
|
|
1040
|
-
deps.renderer.writeWarning(
|
|
1041
|
-
`Skipping ${skipped.length} agent(s) not installed: ${skipped.map((s) => `${s.agentId} (${s.reason ?? "not installed"})`).join(", ")}
|
|
1042
|
-
`
|
|
1043
|
-
);
|
|
1044
|
-
}
|
|
1045
|
-
if (result.summary.succeeded + result.summary.failed + result.summary.cancelled === 0) {
|
|
1046
|
-
deps.renderer.writeError("No installed agents to run.\n");
|
|
1047
|
-
deps.renderer.write("Run `wstack acp list` to see what is available.\n");
|
|
1048
|
-
return 1;
|
|
1049
|
-
}
|
|
1050
|
-
const fannedOut = result.results.filter((r) => r.status !== "skipped").map((r) => r.agentId).join(", ");
|
|
1051
|
-
deps.renderer.writeInfo(
|
|
1052
|
-
`Fanning out to ${result.summary.succeeded + result.summary.failed + result.summary.cancelled} agent(s): ${fannedOut}
|
|
1053
|
-
`
|
|
1054
|
-
);
|
|
1055
|
-
deps.renderer.writeInfo(`Task: ${result.task}
|
|
1056
|
-
|
|
1057
|
-
`);
|
|
1058
|
-
for (const r of result.results) {
|
|
1059
|
-
if (r.status === "skipped") continue;
|
|
1060
|
-
deps.renderer.write(`
|
|
1061
|
-
=== ${r.agentId} ===
|
|
1034
|
+
return 1;
|
|
1035
|
+
}
|
|
1036
|
+
let stdioHqTelemetry;
|
|
1037
|
+
const server = new WrongStackACPServer(
|
|
1038
|
+
echo ? {} : (() => {
|
|
1039
|
+
let agentFor;
|
|
1040
|
+
try {
|
|
1041
|
+
agentFor = buildAcpServerAgentFactory(deps);
|
|
1042
|
+
} catch (err) {
|
|
1043
|
+
if (err instanceof AcpServerConfigError) {
|
|
1044
|
+
deps.renderer.writeError(`${err.message}
|
|
1062
1045
|
`);
|
|
1063
|
-
|
|
1064
|
-
|
|
1065
|
-
|
|
1066
|
-
|
|
1067
|
-
|
|
1068
|
-
|
|
1069
|
-
|
|
1070
|
-
}
|
|
1071
|
-
|
|
1072
|
-
|
|
1046
|
+
}
|
|
1047
|
+
throw err;
|
|
1048
|
+
}
|
|
1049
|
+
stdioHqTelemetry = startAcpHqTelemetry({
|
|
1050
|
+
projectRoot: deps.cwd ?? process.cwd(),
|
|
1051
|
+
projectName: path2.basename(deps.cwd ?? process.cwd()),
|
|
1052
|
+
appConfig: deps.config
|
|
1053
|
+
});
|
|
1054
|
+
const stdioAgentFactory = agentFor;
|
|
1055
|
+
const turn = makeACPServerAgentTurn({
|
|
1056
|
+
agentFor: stdioHqTelemetry.wrapAgentFactory(agentFor),
|
|
1057
|
+
pendingDeliveries: pendingLeaderDeliveries
|
|
1058
|
+
});
|
|
1059
|
+
const store = deps.paths?.projectDir ? new ACPSessionStore({ dir: path2.join(deps.paths.projectDir, "acp-sessions") }) : void 0;
|
|
1060
|
+
return {
|
|
1061
|
+
runTurn: turn,
|
|
1062
|
+
replayFor: turn.replay,
|
|
1063
|
+
seedFor: turn.seed,
|
|
1064
|
+
disposeFor: composeDispose(
|
|
1065
|
+
stdioHqTelemetry.wrapDispose(turn.dispose),
|
|
1066
|
+
stdioAgentFactory
|
|
1067
|
+
),
|
|
1068
|
+
...store ? { store } : {}
|
|
1069
|
+
};
|
|
1070
|
+
})()
|
|
1071
|
+
);
|
|
1072
|
+
if (echo) {
|
|
1073
|
+
deps.renderer.writeInfo(
|
|
1074
|
+
"ACP server starting in --echo mode (no-op turn; no provider needed).\n"
|
|
1075
|
+
);
|
|
1076
|
+
} else {
|
|
1077
|
+
deps.renderer.writeInfo(
|
|
1078
|
+
`Starting WrongStack ACP server (${deps.config.provider}/${deps.config.model})\u2026
|
|
1073
1079
|
`
|
|
1074
|
-
|
|
1075
|
-
|
|
1076
|
-
|
|
1077
|
-
|
|
1080
|
+
);
|
|
1081
|
+
deps.renderer.writeInfo(
|
|
1082
|
+
"Waiting for an ACP client connection on stdin/stdout. Press Ctrl+C to stop.\n"
|
|
1083
|
+
);
|
|
1084
|
+
}
|
|
1085
|
+
createGracefulShutdown({
|
|
1086
|
+
run: async () => {
|
|
1087
|
+
try {
|
|
1088
|
+
await server.stop();
|
|
1089
|
+
} catch (err) {
|
|
1078
1090
|
deps.renderer.writeError(
|
|
1079
|
-
`
|
|
1091
|
+
`ACP stop failed: ${err instanceof Error ? err.message : String(err)}
|
|
1080
1092
|
`
|
|
1081
1093
|
);
|
|
1082
|
-
deps.renderer.write(`[${r.agentId}] cancelled ${r.durationMs}ms
|
|
1083
|
-
`);
|
|
1084
1094
|
}
|
|
1095
|
+
stdioHqTelemetry?.stop();
|
|
1085
1096
|
}
|
|
1086
|
-
|
|
1087
|
-
|
|
1088
|
-
|
|
1089
|
-
|
|
1097
|
+
}).install();
|
|
1098
|
+
try {
|
|
1099
|
+
await server.start();
|
|
1100
|
+
} catch (err) {
|
|
1101
|
+
deps.renderer.writeError(
|
|
1102
|
+
`ACP server error: ${err instanceof Error ? err.message : String(err)}
|
|
1090
1103
|
`
|
|
1091
1104
|
);
|
|
1092
|
-
return
|
|
1093
|
-
} finally {
|
|
1094
|
-
process.off("SIGINT", onSignal);
|
|
1095
|
-
process.off("SIGTERM", onSignal);
|
|
1105
|
+
return 1;
|
|
1096
1106
|
}
|
|
1107
|
+
return 0;
|
|
1097
1108
|
}
|
|
1098
|
-
|
|
1099
|
-
|
|
1100
|
-
|
|
1101
|
-
|
|
1102
|
-
|
|
1103
|
-
|
|
1104
|
-
const
|
|
1105
|
-
|
|
1109
|
+
|
|
1110
|
+
// src/subcommands/handlers/acp.ts
|
|
1111
|
+
var acpCmd = async (args, deps) => {
|
|
1112
|
+
const sub = args[0];
|
|
1113
|
+
const loginIndex = sub === "auth" ? 0 : (sub === "server" || sub === "serve") && args[1] === "auth" ? 1 : -1;
|
|
1114
|
+
if (loginIndex >= 0) {
|
|
1115
|
+
const { authCmd } = await import("./auth-M6LXZTDA.js");
|
|
1116
|
+
return authCmd(args.slice(loginIndex + 1), deps);
|
|
1106
1117
|
}
|
|
1107
|
-
if (
|
|
1108
|
-
deps
|
|
1109
|
-
return 1;
|
|
1118
|
+
if (!sub || sub === "server" || sub === "serve") {
|
|
1119
|
+
return runACPServer(deps);
|
|
1110
1120
|
}
|
|
1111
|
-
|
|
1112
|
-
|
|
1113
|
-
|
|
1114
|
-
|
|
1115
|
-
|
|
1116
|
-
|
|
1117
|
-
|
|
1118
|
-
|
|
1119
|
-
|
|
1120
|
-
|
|
1121
|
-
|
|
1122
|
-
|
|
1123
|
-
|
|
1124
|
-
|
|
1125
|
-
|
|
1126
|
-
|
|
1127
|
-
|
|
1128
|
-
|
|
1129
|
-
|
|
1130
|
-
|
|
1131
|
-
|
|
1121
|
+
if (sub === "help") {
|
|
1122
|
+
deps.renderer.write(`wstack acp \u2014 ACP (Agent Client Protocol) integration
|
|
1123
|
+
|
|
1124
|
+
Usage:
|
|
1125
|
+
wstack acp Start WrongStack as an ACP server (blocks)
|
|
1126
|
+
wstack acp server Same as above
|
|
1127
|
+
wstack acp list List available ACP agents
|
|
1128
|
+
wstack acp sync Pull the official agentclientprotocol/registry into cache
|
|
1129
|
+
wstack acp spawn <id> <task>
|
|
1130
|
+
Spawn an ACP agent as a subagent and wait for result
|
|
1131
|
+
wstack acp parallel <agent-id-csv> <task>
|
|
1132
|
+
Fan a task out to multiple ACP agents in parallel
|
|
1133
|
+
and aggregate the results
|
|
1134
|
+
wstack acp probe [agent-id-csv]
|
|
1135
|
+
Handshake-test agents (bounded concurrency). Defaults
|
|
1136
|
+
to all installed agents.
|
|
1137
|
+
wstack acp bench [agent-id-csv] [--fs]
|
|
1138
|
+
End-to-end verify each agent (handshake \u2192 prompt \u2192
|
|
1139
|
+
marker, optional fs check) and print a graded report.
|
|
1140
|
+
Defaults to all installed agents.
|
|
1141
|
+
wstack acp help Show this help
|
|
1142
|
+
|
|
1143
|
+
ACP Mode:
|
|
1144
|
+
When run as \`wstack acp\`. WrongStack acts as an ACP-compatible agent driven
|
|
1145
|
+
by your configured model provider. ACP clients (Zed, JetBrains, VS Code)
|
|
1146
|
+
spawn it as a subprocess and communicate over stdio JSON-RPC. Run
|
|
1147
|
+
\`wstack auth\` first to configure a provider, or pass \`--echo\` for a no-op
|
|
1148
|
+
connectivity test that needs no provider. Press Ctrl+C to stop.
|
|
1149
|
+
|
|
1150
|
+
Transports:
|
|
1151
|
+
(default) stdio JSON-RPC (the usual editor-spawned-subprocess mode)
|
|
1152
|
+
--ws[=port] serve over WebSocket on 127.0.0.1:<port> (default 8889) \u2014
|
|
1153
|
+
full-duplex, so updates/permission prompts stream live.
|
|
1154
|
+
|
|
1155
|
+
spawn:
|
|
1156
|
+
Spawns a named ACP agent (claude-code, gemini-cli, codex-cli, copilot,
|
|
1157
|
+
cline, goose, openhands, qwen-code, kiro-cli, opencode, mistral-vibe,
|
|
1158
|
+
cursor) with the given task and waits for its result.
|
|
1159
|
+
Example: wstack acp spawn cline "fix the login bug"
|
|
1160
|
+
|
|
1161
|
+
parallel:
|
|
1162
|
+
Runs the same task on a comma-separated list of ACP agents concurrently.
|
|
1163
|
+
Example: wstack acp parallel claude-code,gemini-cli,codex-cli "review this diff"
|
|
1164
|
+
Each agent's result is rendered under a clearly-marked header. Returns 0
|
|
1165
|
+
if at least one agent succeeds, 1 if all fail. Agents that aren't
|
|
1166
|
+
installed are skipped with a warning.
|
|
1132
1167
|
`);
|
|
1133
|
-
|
|
1168
|
+
return 0;
|
|
1134
1169
|
}
|
|
1135
|
-
|
|
1136
|
-
|
|
1137
|
-
${ok} of ${results.length} agents completed the ACP handshake.
|
|
1138
|
-
`);
|
|
1139
|
-
return ok > 0 ? 0 : 1;
|
|
1140
|
-
}
|
|
1141
|
-
async function benchACPAgents(args, deps) {
|
|
1142
|
-
const checkFs = deps.flags?.fs === true || deps.flags?.fs === "true" || args.includes("--fs");
|
|
1143
|
-
const csv = args.filter((a) => a !== "--fs").join(" ");
|
|
1144
|
-
let ids;
|
|
1145
|
-
if (csv) {
|
|
1146
|
-
ids = csv.split(",").flatMap((s) => s.split(/\s+/)).map((s) => s.trim()).filter(Boolean);
|
|
1147
|
-
} else {
|
|
1148
|
-
const detected = await new EnsembleRegistry().list();
|
|
1149
|
-
ids = detected.filter((a) => a.installed).map((a) => a.id);
|
|
1170
|
+
if (sub === "list") {
|
|
1171
|
+
return listACPAgents(deps);
|
|
1150
1172
|
}
|
|
1151
|
-
if (
|
|
1152
|
-
deps
|
|
1153
|
-
deps.renderer.write("Pass ids explicitly: `wstack acp bench gemini-cli,codex-cli`.\n");
|
|
1154
|
-
return 1;
|
|
1173
|
+
if (sub === "sync") {
|
|
1174
|
+
return syncACPRegistry(deps);
|
|
1155
1175
|
}
|
|
1156
|
-
|
|
1157
|
-
|
|
1158
|
-
process.on("SIGINT", onSignal);
|
|
1159
|
-
process.on("SIGTERM", onSignal);
|
|
1160
|
-
try {
|
|
1161
|
-
const overrides = acpOverrides(deps);
|
|
1162
|
-
const live = await loadLive(deps);
|
|
1163
|
-
deps.renderer.writeInfo(
|
|
1164
|
-
`Benching ${ids.length} agent(s)${checkFs ? " (with fs check)" : ""}: ${ids.join(", ")}\u2026
|
|
1165
|
-
`
|
|
1166
|
-
);
|
|
1167
|
-
const result = await runAcpBench({
|
|
1168
|
-
agentIds: ids,
|
|
1169
|
-
resolveCmd: (id) => resolveAcpAgentCommand(id, overrides, live?.byId),
|
|
1170
|
-
projectRoot: deps.cwd ?? process.cwd(),
|
|
1171
|
-
checkFs,
|
|
1172
|
-
signal: ac.signal,
|
|
1173
|
-
onProgress: (agentId, phase, r) => {
|
|
1174
|
-
if (phase === "start") deps.renderer.writeInfo(` \u25B8 ${agentId}\u2026
|
|
1175
|
-
`);
|
|
1176
|
-
else if (r) deps.renderer.writeInfo(` \u21B3 ${agentId}: ${r.status}
|
|
1177
|
-
`);
|
|
1178
|
-
}
|
|
1179
|
-
});
|
|
1180
|
-
deps.renderer.write(`
|
|
1181
|
-
${renderAcpBenchText(result)}
|
|
1182
|
-
`);
|
|
1183
|
-
return result.summary.pass > 0 ? 0 : 1;
|
|
1184
|
-
} finally {
|
|
1185
|
-
process.off("SIGINT", onSignal);
|
|
1186
|
-
process.off("SIGTERM", onSignal);
|
|
1176
|
+
if (sub === "spawn") {
|
|
1177
|
+
return spawnACPAgent(args.slice(1), deps);
|
|
1187
1178
|
}
|
|
1188
|
-
|
|
1179
|
+
if (sub === "parallel") {
|
|
1180
|
+
return parallelACPAgents(args.slice(1), deps);
|
|
1181
|
+
}
|
|
1182
|
+
if (sub === "probe") {
|
|
1183
|
+
return probeACPAgents(args.slice(1), deps);
|
|
1184
|
+
}
|
|
1185
|
+
if (sub === "bench") {
|
|
1186
|
+
return benchACPAgents(args.slice(1), deps);
|
|
1187
|
+
}
|
|
1188
|
+
deps.renderer.writeError(`Unknown acp subcommand: ${sub}
|
|
1189
|
+
`);
|
|
1190
|
+
deps.renderer.write("Run `wstack acp help` for usage.\n");
|
|
1191
|
+
return 1;
|
|
1192
|
+
};
|
|
1189
1193
|
export {
|
|
1190
1194
|
acpCmd
|
|
1191
1195
|
};
|
|
1192
|
-
//# sourceMappingURL=acp-
|
|
1196
|
+
//# sourceMappingURL=acp-T77EAEDB.js.map
|