@wrongstack/acp 1.0.2 → 1.0.4
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/agent/index.d.ts +1 -1
- package/dist/agent/protocol-contract.d.ts +22 -2
- package/dist/agent/protocol-handler.d.ts +1 -1
- package/dist/agent/protocol-session-ops.d.ts +12 -1
- package/dist/agent/server-agent-turn.d.ts +6 -2
- package/dist/agent.js +121 -8
- package/dist/client/acp-session-errors.d.ts +6 -0
- package/dist/client/acp-session.d.ts +12 -0
- package/dist/client.js +97 -8
- package/dist/index.js +227 -31
- package/dist/integration/acp-subagent-runner.d.ts +8 -2
- package/dist/registry/acp-registry-fetch.d.ts +2 -0
- package/dist/types/acp-v1.d.ts +9 -1
- package/dist/wrongstack-acp-agent.js +115 -7
- package/package.json +2 -2
package/dist/index.js
CHANGED
|
@@ -49,6 +49,64 @@ var DEFAULT_MODES = [
|
|
|
49
49
|
description: "Default agent mode for code-generation tasks."
|
|
50
50
|
}
|
|
51
51
|
];
|
|
52
|
+
function parseMcpServers(raw, onSkipped) {
|
|
53
|
+
if (!Array.isArray(raw)) return [];
|
|
54
|
+
const out = [];
|
|
55
|
+
for (const entry of raw) {
|
|
56
|
+
if (typeof entry !== "object" || entry === null) {
|
|
57
|
+
onSkipped?.("entry is not an object");
|
|
58
|
+
continue;
|
|
59
|
+
}
|
|
60
|
+
const e = entry;
|
|
61
|
+
const name = typeof e.name === "string" ? e.name.trim() : "";
|
|
62
|
+
if (name === "") {
|
|
63
|
+
onSkipped?.("entry has no name");
|
|
64
|
+
continue;
|
|
65
|
+
}
|
|
66
|
+
const type = typeof e.type === "string" ? e.type : "stdio";
|
|
67
|
+
if (type === "http" || type === "sse") {
|
|
68
|
+
if (typeof e.url !== "string" || e.url === "") {
|
|
69
|
+
onSkipped?.(`"${name}": ${type} server has no url`);
|
|
70
|
+
continue;
|
|
71
|
+
}
|
|
72
|
+
const headers = parseNameValuePairs(e.headers);
|
|
73
|
+
const url = e.url;
|
|
74
|
+
out.push(
|
|
75
|
+
type === "http" ? { type: "http", name, url, ...headers ? { headers } : {} } : { type: "sse", name, url, ...headers ? { headers } : {} }
|
|
76
|
+
);
|
|
77
|
+
continue;
|
|
78
|
+
}
|
|
79
|
+
if (type !== "stdio") {
|
|
80
|
+
onSkipped?.(`"${name}": unknown transport "${type}"`);
|
|
81
|
+
continue;
|
|
82
|
+
}
|
|
83
|
+
if (typeof e.command !== "string" || e.command === "") {
|
|
84
|
+
onSkipped?.(`"${name}": stdio server has no command`);
|
|
85
|
+
continue;
|
|
86
|
+
}
|
|
87
|
+
const args = Array.isArray(e.args) ? e.args.filter((a) => typeof a === "string") : void 0;
|
|
88
|
+
const env = parseNameValuePairs(e.env);
|
|
89
|
+
out.push({
|
|
90
|
+
name,
|
|
91
|
+
command: e.command,
|
|
92
|
+
...args && args.length > 0 ? { args } : {},
|
|
93
|
+
...env ? { env } : {}
|
|
94
|
+
});
|
|
95
|
+
}
|
|
96
|
+
return out;
|
|
97
|
+
}
|
|
98
|
+
function parseNameValuePairs(raw) {
|
|
99
|
+
if (!Array.isArray(raw)) return void 0;
|
|
100
|
+
const out = [];
|
|
101
|
+
for (const pair of raw) {
|
|
102
|
+
if (typeof pair !== "object" || pair === null) continue;
|
|
103
|
+
const p = pair;
|
|
104
|
+
if (typeof p.name === "string" && p.name !== "" && typeof p.value === "string") {
|
|
105
|
+
out.push({ name: p.name, value: p.value });
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
return out.length > 0 ? out : void 0;
|
|
109
|
+
}
|
|
52
110
|
async function resolveSessionCwd(requested) {
|
|
53
111
|
if (!path.isAbsolute(requested)) return null;
|
|
54
112
|
const resolved = path.resolve(requested);
|
|
@@ -131,9 +189,16 @@ function buildInitializeResult(agentName, modes, configOptions) {
|
|
|
131
189
|
audio: false,
|
|
132
190
|
embeddedContext: true
|
|
133
191
|
},
|
|
192
|
+
// All three ACP transports are supported. stdio is mandatory per spec
|
|
193
|
+
// and cannot be declined; http and sse are declared here because the
|
|
194
|
+
// agent now actually connects them (see `parseMcpServers` above and the
|
|
195
|
+
// per-session MCP registry in `buildAcpServerAgentFactory`). Before that
|
|
196
|
+
// wiring existed the array was destructured and thrown away at every
|
|
197
|
+
// entry point, so a client got a successful `session/new` and no tools —
|
|
198
|
+
// flip these back to false if that connection path is ever removed.
|
|
134
199
|
mcpCapabilities: {
|
|
135
|
-
http:
|
|
136
|
-
sse:
|
|
200
|
+
http: true,
|
|
201
|
+
sse: true
|
|
137
202
|
},
|
|
138
203
|
sessionCapabilities: {
|
|
139
204
|
close: {},
|
|
@@ -173,6 +238,8 @@ async function handleSessionNewOp(ctx, id, params) {
|
|
|
173
238
|
}
|
|
174
239
|
cwd = resolved;
|
|
175
240
|
}
|
|
241
|
+
const skipped = [];
|
|
242
|
+
const mcpServers = parseMcpServers(p.mcpServers, (reason) => skipped.push(reason));
|
|
176
243
|
const sessionId = `sess_${ctx.allocId()}`;
|
|
177
244
|
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
178
245
|
const state = {
|
|
@@ -181,7 +248,8 @@ async function handleSessionNewOp(ctx, id, params) {
|
|
|
181
248
|
abort: new AbortController(),
|
|
182
249
|
modeId: DEFAULT_MODE_ID,
|
|
183
250
|
createdAt: now,
|
|
184
|
-
updatedAt: now
|
|
251
|
+
updatedAt: now,
|
|
252
|
+
...mcpServers.length > 0 ? { mcpServers } : {}
|
|
185
253
|
};
|
|
186
254
|
ctx.sessions.set(sessionId, state);
|
|
187
255
|
ctx.onSessionNew(state);
|
|
@@ -202,6 +270,7 @@ async function handleSessionNewOp(ctx, id, params) {
|
|
|
202
270
|
}
|
|
203
271
|
});
|
|
204
272
|
}
|
|
273
|
+
await reportSkippedMcpServers(ctx, sessionId, skipped);
|
|
205
274
|
await ctx.sendResult(id, {
|
|
206
275
|
sessionId,
|
|
207
276
|
modes: ctx.modes,
|
|
@@ -213,6 +282,8 @@ async function handleSessionLoadOp(ctx, id, params) {
|
|
|
213
282
|
const p = params ?? {};
|
|
214
283
|
const sessionId = typeof p.sessionId === "string" ? p.sessionId : null;
|
|
215
284
|
const loadCwd = typeof p.cwd === "string" ? p.cwd : void 0;
|
|
285
|
+
const loadSkipped = [];
|
|
286
|
+
const loadMcpServers = parseMcpServers(p.mcpServers, (reason) => loadSkipped.push(reason));
|
|
216
287
|
const existing = sessionId ? ctx.sessions.get(sessionId) : void 0;
|
|
217
288
|
if (!existing && sessionId && ctx.store) {
|
|
218
289
|
const persisted = await ctx.store.load(sessionId);
|
|
@@ -234,7 +305,8 @@ async function handleSessionLoadOp(ctx, id, params) {
|
|
|
234
305
|
modeId: persisted.modeId ?? DEFAULT_MODE_ID,
|
|
235
306
|
createdAt: persisted.createdAt ?? (/* @__PURE__ */ new Date()).toISOString(),
|
|
236
307
|
updatedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
237
|
-
...persisted.title !== void 0 ? { title: persisted.title } : {}
|
|
308
|
+
...persisted.title !== void 0 ? { title: persisted.title } : {},
|
|
309
|
+
...loadMcpServers.length > 0 ? { mcpServers: loadMcpServers } : {}
|
|
238
310
|
};
|
|
239
311
|
ctx.sessions.set(sessionId, restored);
|
|
240
312
|
ctx.seedFor?.(sessionId, persisted.history ?? []);
|
|
@@ -245,6 +317,7 @@ async function handleSessionLoadOp(ctx, id, params) {
|
|
|
245
317
|
sessionId,
|
|
246
318
|
update: { sessionUpdate: "current_mode_update", modeId: restored.modeId }
|
|
247
319
|
});
|
|
320
|
+
await reportSkippedMcpServers(ctx, sessionId, loadSkipped);
|
|
248
321
|
await ctx.sendResult(id, {
|
|
249
322
|
initialMode: { currentModeId: restored.modeId, availableModes: ctx.modes }
|
|
250
323
|
});
|
|
@@ -253,6 +326,9 @@ async function handleSessionLoadOp(ctx, id, params) {
|
|
|
253
326
|
}
|
|
254
327
|
if (existing) {
|
|
255
328
|
existing.updatedAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
329
|
+
if (loadMcpServers.length > 0) {
|
|
330
|
+
existing.mcpServers = loadMcpServers;
|
|
331
|
+
}
|
|
256
332
|
const replay = ctx.replayFor?.(sessionId);
|
|
257
333
|
if (replay) {
|
|
258
334
|
for (const update of replay) {
|
|
@@ -273,6 +349,7 @@ async function handleSessionLoadOp(ctx, id, params) {
|
|
|
273
349
|
modeId: existing.modeId
|
|
274
350
|
}
|
|
275
351
|
});
|
|
352
|
+
await reportSkippedMcpServers(ctx, sessionId, loadSkipped);
|
|
276
353
|
await ctx.sendResult(id, {
|
|
277
354
|
initialMode: {
|
|
278
355
|
currentModeId: existing.modeId,
|
|
@@ -305,6 +382,9 @@ async function handleSessionForkOp(ctx, id, params) {
|
|
|
305
382
|
}
|
|
306
383
|
forkCwd = resolved;
|
|
307
384
|
}
|
|
385
|
+
const forkSkipped = [];
|
|
386
|
+
const forkRequested = parseMcpServers(p.mcpServers, (reason) => forkSkipped.push(reason));
|
|
387
|
+
const forkMcpServers = forkRequested.length > 0 ? forkRequested : source.mcpServers;
|
|
308
388
|
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
309
389
|
const sessionId = `sess_${ctx.allocId()}`;
|
|
310
390
|
const forked = {
|
|
@@ -314,7 +394,8 @@ async function handleSessionForkOp(ctx, id, params) {
|
|
|
314
394
|
modeId: source.modeId,
|
|
315
395
|
createdAt: now,
|
|
316
396
|
updatedAt: now,
|
|
317
|
-
...source.title !== void 0 ? { title: source.title } : {}
|
|
397
|
+
...source.title !== void 0 ? { title: source.title } : {},
|
|
398
|
+
...forkMcpServers && forkMcpServers.length > 0 ? { mcpServers: forkMcpServers } : {}
|
|
318
399
|
};
|
|
319
400
|
const history = (ctx.replayFor?.(sourceId) ?? []).map((update) => ({
|
|
320
401
|
sessionUpdate: update.sessionUpdate,
|
|
@@ -328,6 +409,7 @@ async function handleSessionForkOp(ctx, id, params) {
|
|
|
328
409
|
sessionId,
|
|
329
410
|
update: { sessionUpdate: "current_mode_update", modeId: forked.modeId }
|
|
330
411
|
});
|
|
412
|
+
await reportSkippedMcpServers(ctx, sessionId, forkSkipped);
|
|
331
413
|
await ctx.sendResult(id, {
|
|
332
414
|
sessionId,
|
|
333
415
|
modes: ctx.modes,
|
|
@@ -367,7 +449,13 @@ async function handleSessionPromptOp(ctx, id, params) {
|
|
|
367
449
|
};
|
|
368
450
|
try {
|
|
369
451
|
result = await ctx.runTurn(
|
|
370
|
-
{
|
|
452
|
+
{
|
|
453
|
+
sessionId,
|
|
454
|
+
prompt: p.prompt,
|
|
455
|
+
signal: turnSignal.signal,
|
|
456
|
+
cwd: session.cwd,
|
|
457
|
+
...session.mcpServers ? { mcpServers: session.mcpServers } : {}
|
|
458
|
+
},
|
|
371
459
|
emit,
|
|
372
460
|
api
|
|
373
461
|
);
|
|
@@ -425,6 +513,22 @@ async function handleSetConfigOptionOp(ctx, id, params) {
|
|
|
425
513
|
await ctx.sendResult(id, { configOptions: [...ctx.configOptions] });
|
|
426
514
|
return false;
|
|
427
515
|
}
|
|
516
|
+
async function reportSkippedMcpServers(ctx, sessionId, skipped) {
|
|
517
|
+
if (skipped.length === 0) return;
|
|
518
|
+
try {
|
|
519
|
+
await ctx.sendNotification({
|
|
520
|
+
sessionId,
|
|
521
|
+
update: {
|
|
522
|
+
sessionUpdate: "agent_message_chunk",
|
|
523
|
+
content: {
|
|
524
|
+
type: "text",
|
|
525
|
+
text: `Ignored ${skipped.length} malformed mcpServers entr${skipped.length === 1 ? "y" : "ies"}: ${skipped.join("; ")}`
|
|
526
|
+
}
|
|
527
|
+
}
|
|
528
|
+
});
|
|
529
|
+
} catch {
|
|
530
|
+
}
|
|
531
|
+
}
|
|
428
532
|
|
|
429
533
|
// src/agent/protocol-handler.ts
|
|
430
534
|
var ACPProtocolHandler = class {
|
|
@@ -623,7 +727,7 @@ var ACPProtocolHandler = class {
|
|
|
623
727
|
return false;
|
|
624
728
|
}
|
|
625
729
|
async handleAuthenticate(id, _params) {
|
|
626
|
-
await this.sendResult(id, {
|
|
730
|
+
await this.sendResult(id, {});
|
|
627
731
|
return false;
|
|
628
732
|
}
|
|
629
733
|
async handleLogout(id, _params) {
|
|
@@ -636,6 +740,10 @@ var ACPProtocolHandler = class {
|
|
|
636
740
|
const existing = sessionId ? this.sessions.get(sessionId) : void 0;
|
|
637
741
|
if (existing) {
|
|
638
742
|
existing.updatedAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
743
|
+
const resumeServers = parseMcpServers(p.mcpServers);
|
|
744
|
+
if (resumeServers.length > 0) {
|
|
745
|
+
existing.mcpServers = resumeServers;
|
|
746
|
+
}
|
|
639
747
|
await this.sendResult(id, {
|
|
640
748
|
initialMode: {
|
|
641
749
|
currentModeId: existing.modeId,
|
|
@@ -1937,7 +2045,7 @@ async function authorizeAcpCallback(permissionPolicy, partial, callbackOptions)
|
|
|
1937
2045
|
title: partial.title,
|
|
1938
2046
|
kind: partial.kind,
|
|
1939
2047
|
status: "pending",
|
|
1940
|
-
|
|
2048
|
+
rawInput: partial.rawInput
|
|
1941
2049
|
},
|
|
1942
2050
|
options: [
|
|
1943
2051
|
{ optionId: "allow", name: "Allow", kind: "allow_once" },
|
|
@@ -2045,6 +2153,20 @@ var ACPSessionError = class extends Error {
|
|
|
2045
2153
|
function isJsonRpcError(v) {
|
|
2046
2154
|
return typeof v === "object" && v !== null && typeof v.code === "number" && typeof v.message === "string";
|
|
2047
2155
|
}
|
|
2156
|
+
function isAuthRequiredError(err) {
|
|
2157
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
2158
|
+
if (/auth(_|-)?required|authentication required/i.test(message)) return true;
|
|
2159
|
+
const cause = err instanceof ACPSessionError ? err.cause : err && typeof err === "object" && "cause" in err ? err.cause : err;
|
|
2160
|
+
if (!cause || typeof cause !== "object") return false;
|
|
2161
|
+
const data = cause.data;
|
|
2162
|
+
if (data === "auth_required" || data === "AUTH_REQUIRED") return true;
|
|
2163
|
+
if (data && typeof data === "object") {
|
|
2164
|
+
const d = data;
|
|
2165
|
+
if (d.authRequired === true) return true;
|
|
2166
|
+
if (d.code === "auth_required" || d.code === "AUTH_REQUIRED") return true;
|
|
2167
|
+
}
|
|
2168
|
+
return false;
|
|
2169
|
+
}
|
|
2048
2170
|
|
|
2049
2171
|
// src/client/acp-session-ops.ts
|
|
2050
2172
|
function filterMcpServers(agentCapabilities, servers) {
|
|
@@ -2806,7 +2928,7 @@ var WebSocketClientTransport = class {
|
|
|
2806
2928
|
if (pending === null) return;
|
|
2807
2929
|
this.pendingStart = null;
|
|
2808
2930
|
this.closed = true;
|
|
2809
|
-
|
|
2931
|
+
this.ws = null;
|
|
2810
2932
|
this.handlers.clear();
|
|
2811
2933
|
try {
|
|
2812
2934
|
ws.close();
|
|
@@ -2830,7 +2952,7 @@ var WebSocketClientTransport = class {
|
|
|
2830
2952
|
}
|
|
2831
2953
|
this.pendingStart = null;
|
|
2832
2954
|
this.closed = true;
|
|
2833
|
-
|
|
2955
|
+
this.ws = null;
|
|
2834
2956
|
this.handlers.clear();
|
|
2835
2957
|
clearTimeout(pending.timer);
|
|
2836
2958
|
const message = ev && typeof ev === "object" && "message" in ev ? String(ev.message) : "WebSocket error";
|
|
@@ -3089,7 +3211,7 @@ var ACPSession = class _ACPSession {
|
|
|
3089
3211
|
fs: { readTextFile: true, writeTextFile: true },
|
|
3090
3212
|
terminal: true
|
|
3091
3213
|
},
|
|
3092
|
-
clientInfo: { name: "wrongstack", title: "WrongStack", version:
|
|
3214
|
+
clientInfo: { name: "wrongstack", title: "WrongStack", version: ACP_PACKAGE_VERSION }
|
|
3093
3215
|
});
|
|
3094
3216
|
if (isJsonRpcError(result)) {
|
|
3095
3217
|
throw new ACPSessionError("init_failed", `initialize failed: ${result.message}`, result);
|
|
@@ -3121,12 +3243,13 @@ var ACPSession = class _ACPSession {
|
|
|
3121
3243
|
if (this.state === "closed") {
|
|
3122
3244
|
throw new ACPSessionError("closed", "session is closed");
|
|
3123
3245
|
}
|
|
3124
|
-
if (this.state !== "ready") {
|
|
3246
|
+
if (this.state !== "ready" && this.state !== "authenticated") {
|
|
3125
3247
|
throw new ACPSessionError(
|
|
3126
3248
|
"protocol_error",
|
|
3127
3249
|
`authenticate called in state=${this.state} (expected 'ready')`
|
|
3128
3250
|
);
|
|
3129
3251
|
}
|
|
3252
|
+
if (this.state === "authenticated") return;
|
|
3130
3253
|
if (!this.authMethods.some((m) => m.id === methodId)) {
|
|
3131
3254
|
throw new ACPSessionError(
|
|
3132
3255
|
"auth_failed",
|
|
@@ -3165,9 +3288,12 @@ var ACPSession = class _ACPSession {
|
|
|
3165
3288
|
// Session management delegation
|
|
3166
3289
|
// ──────────────────────────────────────────────────────────────────────
|
|
3167
3290
|
opContext() {
|
|
3291
|
+
const self = this;
|
|
3168
3292
|
return {
|
|
3169
3293
|
closed: this.closed,
|
|
3170
|
-
sessionId
|
|
3294
|
+
get sessionId() {
|
|
3295
|
+
return self.sessionId;
|
|
3296
|
+
},
|
|
3171
3297
|
agentCapabilities: this.agentCapabilities,
|
|
3172
3298
|
opts: this.opts,
|
|
3173
3299
|
allocId: () => this.allocId(),
|
|
@@ -3226,7 +3352,7 @@ var ACPSession = class _ACPSession {
|
|
|
3226
3352
|
return emptyRunResult("cancelled");
|
|
3227
3353
|
}
|
|
3228
3354
|
if (!this.sessionId) {
|
|
3229
|
-
this.sessionId = await
|
|
3355
|
+
this.sessionId = await this.createSessionWithAuth();
|
|
3230
3356
|
}
|
|
3231
3357
|
if (signal.aborted) {
|
|
3232
3358
|
return emptyRunResult("cancelled");
|
|
@@ -3362,6 +3488,54 @@ var ACPSession = class _ACPSession {
|
|
|
3362
3488
|
});
|
|
3363
3489
|
});
|
|
3364
3490
|
}
|
|
3491
|
+
/**
|
|
3492
|
+
* `session/new`, then one authenticate+retry if the agent demands login.
|
|
3493
|
+
* Logged-in CLIs succeed on the first call even when they advertise
|
|
3494
|
+
* `authMethods`; we do not pop OAuth on every spawn.
|
|
3495
|
+
*/
|
|
3496
|
+
async createSessionWithAuth() {
|
|
3497
|
+
try {
|
|
3498
|
+
return await executeCreateSession(this.opContext());
|
|
3499
|
+
} catch (err) {
|
|
3500
|
+
if (this.state === "authenticated" || !isAuthRequiredError(err)) {
|
|
3501
|
+
throw err instanceof ACPSessionError ? err : new ACPSessionError(
|
|
3502
|
+
"session_create_failed",
|
|
3503
|
+
err instanceof Error ? err.message : String(err),
|
|
3504
|
+
err
|
|
3505
|
+
);
|
|
3506
|
+
}
|
|
3507
|
+
await this.ensureAuthenticated();
|
|
3508
|
+
return executeCreateSession(this.opContext());
|
|
3509
|
+
}
|
|
3510
|
+
}
|
|
3511
|
+
/**
|
|
3512
|
+
* Pick a non-terminal auth method and run `authenticate`. Terminal-only
|
|
3513
|
+
* agents need an out-of-band login CLI (registry AUTHENTICATION.md) —
|
|
3514
|
+
* we refuse rather than hang a TUI inside the JSON-RPC child.
|
|
3515
|
+
*/
|
|
3516
|
+
async ensureAuthenticated() {
|
|
3517
|
+
if (this.state === "authenticated") return;
|
|
3518
|
+
if (this.authMethods.length === 0) {
|
|
3519
|
+
throw new ACPSessionError(
|
|
3520
|
+
"auth_failed",
|
|
3521
|
+
"This agent requires authentication before a session can start, but advertised no authMethods. Log into the CLI, then retry."
|
|
3522
|
+
);
|
|
3523
|
+
}
|
|
3524
|
+
const inProcess = this.authMethods.find(
|
|
3525
|
+
(m) => m.type === void 0 || m.type === "agent" || m.type === "oauth" || m.type === "http"
|
|
3526
|
+
);
|
|
3527
|
+
if (inProcess) {
|
|
3528
|
+
await this.authenticate(inProcess.id);
|
|
3529
|
+
return;
|
|
3530
|
+
}
|
|
3531
|
+
const terminal = this.authMethods.find((m) => m.type === "terminal");
|
|
3532
|
+
const setupArgs = terminal?.args?.length ? terminal.args.join(" ") : void 0;
|
|
3533
|
+
const setup = setupArgs !== void 0 ? `${this.opts.command} ${setupArgs}` : `${this.opts.command}${this.opts.args?.length ? ` ${this.opts.args.join(" ")}` : ""}`;
|
|
3534
|
+
throw new ACPSessionError(
|
|
3535
|
+
"auth_failed",
|
|
3536
|
+
`This agent requires a terminal login before ACP can start. Run \`${setup}\` (or the CLI's /login), then retry.`
|
|
3537
|
+
);
|
|
3538
|
+
}
|
|
3365
3539
|
sendResult(id, result) {
|
|
3366
3540
|
return this.transport.send({ jsonrpc: "2.0", id, result });
|
|
3367
3541
|
}
|
|
@@ -3390,7 +3564,11 @@ var ACPSession = class _ACPSession {
|
|
|
3390
3564
|
clearTimeout(pending.timeoutHandle);
|
|
3391
3565
|
this.pending.delete(msg.id);
|
|
3392
3566
|
if (msg.error !== void 0) {
|
|
3393
|
-
|
|
3567
|
+
const method = pending.method;
|
|
3568
|
+
const kind = method === "session/new" ? "session_create_failed" : method === "authenticate" ? "auth_failed" : "protocol_error";
|
|
3569
|
+
pending.reject(
|
|
3570
|
+
new ACPSessionError(kind, msg.error.message ?? "unknown JSON-RPC error", msg.error)
|
|
3571
|
+
);
|
|
3394
3572
|
} else {
|
|
3395
3573
|
pending.resolve(msg.result);
|
|
3396
3574
|
}
|
|
@@ -3830,7 +4008,7 @@ var AGENTS_CATALOG = [
|
|
|
3830
4008
|
id: "cline",
|
|
3831
4009
|
displayName: "Cline",
|
|
3832
4010
|
vendor: "community",
|
|
3833
|
-
probe: { command: "
|
|
4011
|
+
probe: { command: "cline", args: ["--version"] },
|
|
3834
4012
|
// Registry id `cline`: the `cline` npm package speaks ACP behind `--acp`.
|
|
3835
4013
|
acp: {
|
|
3836
4014
|
command: "npx",
|
|
@@ -3873,7 +4051,8 @@ var AGENTS_CATALOG = [
|
|
|
3873
4051
|
fs: true
|
|
3874
4052
|
},
|
|
3875
4053
|
integration: "experimental",
|
|
3876
|
-
//
|
|
4054
|
+
// Not in the official agentclientprotocol/registry (2026-09). Keep as a
|
|
4055
|
+
// local-PATH fallback; probe/spawn may hang if the binary has no ACP entry.
|
|
3877
4056
|
docs: "https://github.com/OpenHands/OpenHands"
|
|
3878
4057
|
},
|
|
3879
4058
|
// ── Vendor CLIs (native binaries) ───────────────────────────────────
|
|
@@ -3906,6 +4085,7 @@ var AGENTS_CATALOG = [
|
|
|
3906
4085
|
fs: true
|
|
3907
4086
|
},
|
|
3908
4087
|
integration: "experimental",
|
|
4088
|
+
// Not in the official agentclientprotocol/registry (2026-09).
|
|
3909
4089
|
docs: "https://kiro.dev"
|
|
3910
4090
|
},
|
|
3911
4091
|
{
|
|
@@ -3928,8 +4108,9 @@ var AGENTS_CATALOG = [
|
|
|
3928
4108
|
id: "mistral-vibe",
|
|
3929
4109
|
displayName: "Mistral Vibe",
|
|
3930
4110
|
vendor: "community",
|
|
3931
|
-
probe: { command: "vibe", args: ["--version"] },
|
|
3932
|
-
|
|
4111
|
+
probe: { command: "vibe-acp", args: ["--version"] },
|
|
4112
|
+
// Official registry ships a dedicated `vibe-acp` binary, not bare `vibe`.
|
|
4113
|
+
acp: { command: "vibe-acp", args: [] },
|
|
3933
4114
|
supports: {
|
|
3934
4115
|
loadSession: false,
|
|
3935
4116
|
promptImages: false,
|
|
@@ -4051,6 +4232,10 @@ async function makeACPSubagentRunnerWithStop(options) {
|
|
|
4051
4232
|
} catch {
|
|
4052
4233
|
}
|
|
4053
4234
|
options.onProgress?.(event);
|
|
4235
|
+
try {
|
|
4236
|
+
options.publishLive?.(ctx, task, event);
|
|
4237
|
+
} catch {
|
|
4238
|
+
}
|
|
4054
4239
|
};
|
|
4055
4240
|
try {
|
|
4056
4241
|
const result = await session.prompt([textContent(task.description)], ctx.signal, onProgress);
|
|
@@ -4137,10 +4322,13 @@ var REGISTRY_ID_ALIASES = {
|
|
|
4137
4322
|
"gemini-cli": "gemini",
|
|
4138
4323
|
"codex-cli": "codex-acp",
|
|
4139
4324
|
copilot: "github-copilot-cli",
|
|
4140
|
-
|
|
4141
|
-
|
|
4142
|
-
|
|
4143
|
-
|
|
4325
|
+
kimi: "kimi",
|
|
4326
|
+
cline: "cline",
|
|
4327
|
+
goose: "goose",
|
|
4328
|
+
opencode: "opencode",
|
|
4329
|
+
cursor: "cursor",
|
|
4330
|
+
"qwen-code": "qwen-code",
|
|
4331
|
+
"mistral-vibe": "mistral-vibe"
|
|
4144
4332
|
};
|
|
4145
4333
|
function resolveAcpAgentCommand(id, overrides, live) {
|
|
4146
4334
|
const ov = overrides?.[id];
|
|
@@ -4563,20 +4751,20 @@ async function runEnsemble(opts) {
|
|
|
4563
4751
|
const detectedById = new Map(detected.map((a) => [a.id, a]));
|
|
4564
4752
|
const runnable = [];
|
|
4565
4753
|
for (const id of requested) {
|
|
4754
|
+
const cmd = resolveCmd(id);
|
|
4566
4755
|
const det = detectedById.get(id);
|
|
4567
|
-
|
|
4756
|
+
const pkgLauncher = cmd?.command === "npx" || cmd?.command === "uvx";
|
|
4757
|
+
if (det && !det.installed && !pkgLauncher) {
|
|
4568
4758
|
setResult(results, id, {
|
|
4569
4759
|
status: "skipped",
|
|
4570
|
-
reason: det
|
|
4760
|
+
reason: det.reason ?? "binary not found"
|
|
4571
4761
|
});
|
|
4572
4762
|
continue;
|
|
4573
4763
|
}
|
|
4574
|
-
const cmd = resolveCmd(id);
|
|
4575
4764
|
if (!cmd) {
|
|
4576
4765
|
setResult(results, id, {
|
|
4577
|
-
status: "
|
|
4578
|
-
|
|
4579
|
-
durationMs: 0
|
|
4766
|
+
status: "skipped",
|
|
4767
|
+
reason: det?.reason ?? "not in catalog"
|
|
4580
4768
|
});
|
|
4581
4769
|
continue;
|
|
4582
4770
|
}
|
|
@@ -4712,9 +4900,17 @@ function mapRegistryEntry(entry, platformKey = currentPlatformKey()) {
|
|
|
4712
4900
|
const dist = entry.distribution;
|
|
4713
4901
|
let acp = null;
|
|
4714
4902
|
if (dist?.npx?.package) {
|
|
4715
|
-
acp = {
|
|
4903
|
+
acp = {
|
|
4904
|
+
command: "npx",
|
|
4905
|
+
args: ["-y", dist.npx.package, ...dist.npx.args ?? []],
|
|
4906
|
+
...dist.npx.env ? { env: dist.npx.env } : {}
|
|
4907
|
+
};
|
|
4716
4908
|
} else if (dist?.uvx?.package) {
|
|
4717
|
-
acp = {
|
|
4909
|
+
acp = {
|
|
4910
|
+
command: "uvx",
|
|
4911
|
+
args: [dist.uvx.package, ...dist.uvx.args ?? []],
|
|
4912
|
+
...dist.uvx.env ? { env: dist.uvx.env } : {}
|
|
4913
|
+
};
|
|
4718
4914
|
} else if (dist?.binary) {
|
|
4719
4915
|
const target = dist.binary[platformKey];
|
|
4720
4916
|
if (target?.cmd) {
|
|
@@ -11,8 +11,8 @@
|
|
|
11
11
|
* Connected to the Director / MultiAgentCoordinator via the
|
|
12
12
|
* `SubagentRunner` interface (same shape as `AgentSubagentRunner`).
|
|
13
13
|
*/
|
|
14
|
-
import type { SubagentRunner } from '@wrongstack/core/types';
|
|
15
|
-
import { type ACPProgressHandler, ACPSession } from '../client/acp-session.js';
|
|
14
|
+
import type { SubagentRunContext, SubagentRunner, TaskSpec } from '@wrongstack/core/types';
|
|
15
|
+
import { type ACPProgressEvent, type ACPProgressHandler, ACPSession } from '../client/acp-session.js';
|
|
16
16
|
import type { PermissionPolicy } from '../client/permission.js';
|
|
17
17
|
import type { McpServer } from '../types/acp-v1.js';
|
|
18
18
|
export interface ACPSubagentRunnerOptions {
|
|
@@ -40,6 +40,12 @@ export interface ACPSubagentRunnerOptions {
|
|
|
40
40
|
* stream, instead of waiting for the buffered final result.
|
|
41
41
|
*/
|
|
42
42
|
onProgress?: ACPProgressHandler | undefined;
|
|
43
|
+
/**
|
|
44
|
+
* Host live-view hook. Called with the run context so a fleet/TUI/WebUI
|
|
45
|
+
* publisher can attribute tool calls and text to the right subagent.
|
|
46
|
+
* Invoked in addition to `onProgress`.
|
|
47
|
+
*/
|
|
48
|
+
publishLive?: ((ctx: SubagentRunContext, task: TaskSpec, event: ACPProgressEvent) => void) | undefined;
|
|
43
49
|
/**
|
|
44
50
|
* Permission policy for the external agent's `session/request_permission`
|
|
45
51
|
* calls. Defaults to the session's own default. Inject the host's
|
|
@@ -39,10 +39,12 @@ export interface RegistryAgentEntry {
|
|
|
39
39
|
npx?: {
|
|
40
40
|
package: string;
|
|
41
41
|
args?: string[];
|
|
42
|
+
env?: Record<string, string>;
|
|
42
43
|
};
|
|
43
44
|
uvx?: {
|
|
44
45
|
package: string;
|
|
45
46
|
args?: string[];
|
|
47
|
+
env?: Record<string, string>;
|
|
46
48
|
};
|
|
47
49
|
binary?: Record<string, {
|
|
48
50
|
archive?: string;
|
package/dist/types/acp-v1.d.ts
CHANGED
|
@@ -95,7 +95,15 @@ export interface AuthMethod {
|
|
|
95
95
|
id: string;
|
|
96
96
|
name: string;
|
|
97
97
|
description?: string | undefined;
|
|
98
|
-
|
|
98
|
+
/**
|
|
99
|
+
* Registry agents advertise `agent` (OAuth in-process) or `terminal`
|
|
100
|
+
* (separate login CLI). `oauth`/`http` are spec aliases; `env_var` is
|
|
101
|
+
* used by some agents but is not a registry-supported setup path.
|
|
102
|
+
*/
|
|
103
|
+
type?: 'agent' | 'oauth' | 'http' | 'terminal' | 'env_var' | undefined;
|
|
104
|
+
/** Extra argv for `type: 'terminal'` setup (replaces the ACP entry args). */
|
|
105
|
+
args?: string[] | undefined;
|
|
106
|
+
env?: Record<string, string> | undefined;
|
|
99
107
|
}
|
|
100
108
|
export interface AuthenticateRequest {
|
|
101
109
|
methodId: string;
|