@wrongstack/acp 1.0.19 → 1.0.26
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/protocol-contract.d.ts +13 -0
- package/dist/agent/protocol-handler.d.ts +2 -0
- package/dist/agent/protocol-session-management.d.ts +2 -1
- package/dist/agent/protocol-session-ops.d.ts +1 -4
- package/dist/agent.js +223 -101
- package/dist/client/acp-late-session.d.ts +4 -0
- package/dist/client/acp-request-state.d.ts +9 -0
- package/dist/client/acp-session-auth.d.ts +20 -0
- package/dist/client/acp-session.d.ts +1 -0
- package/dist/client/file-server.d.ts +2 -0
- package/dist/client.js +578 -476
- package/dist/index.js +860 -644
- package/dist/types/acp-v1.d.ts +4 -3
- package/dist/wrongstack-acp-agent.js +213 -99
- package/package.json +3 -3
package/dist/client.js
CHANGED
|
@@ -213,6 +213,7 @@ var ClientTransport = class {
|
|
|
213
213
|
this.messageQueue.length = 0;
|
|
214
214
|
this.queuedChars = 0;
|
|
215
215
|
this.handlers.clear();
|
|
216
|
+
this.claimHandlers.clear();
|
|
216
217
|
if (code !== 0 && code !== null) {
|
|
217
218
|
writeErr(`[acp-child exited with code ${code}]
|
|
218
219
|
`);
|
|
@@ -223,17 +224,28 @@ var ClientTransport = class {
|
|
|
223
224
|
const resolve3 = this.resolveRead;
|
|
224
225
|
this.resolveRead = null;
|
|
225
226
|
resolve3(msg);
|
|
226
|
-
} else
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
227
|
+
} else {
|
|
228
|
+
let claimed = false;
|
|
229
|
+
for (const handler of this.claimHandlers) {
|
|
230
|
+
try {
|
|
231
|
+
if (handler(msg)) claimed = true;
|
|
232
|
+
} catch (err) {
|
|
233
|
+
writeErr(`[acp-child handler error] ${err}
|
|
234
|
+
`);
|
|
235
|
+
}
|
|
236
|
+
}
|
|
237
|
+
if (!claimed && this.handlers.size === 0) {
|
|
238
|
+
if (this.messageQueue.length >= this.maxQueuedMessages || this.queuedChars + chars > this.maxQueuedChars) {
|
|
239
|
+
writeErr(
|
|
240
|
+
`[acp-child message queue exceeds ${this.maxQueuedMessages} entries or ${this.maxQueuedChars} characters]
|
|
230
241
|
`
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
242
|
+
);
|
|
243
|
+
this.stop();
|
|
244
|
+
return;
|
|
245
|
+
}
|
|
246
|
+
this.messageQueue.push({ message: msg, chars });
|
|
247
|
+
this.queuedChars += chars;
|
|
234
248
|
}
|
|
235
|
-
this.messageQueue.push({ message: msg, chars });
|
|
236
|
-
this.queuedChars += chars;
|
|
237
249
|
}
|
|
238
250
|
for (const handler of this.handlers) {
|
|
239
251
|
try {
|
|
@@ -242,36 +254,403 @@ var ClientTransport = class {
|
|
|
242
254
|
}
|
|
243
255
|
}
|
|
244
256
|
}
|
|
245
|
-
};
|
|
246
|
-
function spawnInvocation(command, args, platform) {
|
|
247
|
-
if (platform !== "win32") return { command, args };
|
|
248
|
-
return buildWin32CmdShimInvocation(command, args);
|
|
257
|
+
};
|
|
258
|
+
function spawnInvocation(command, args, platform) {
|
|
259
|
+
if (platform !== "win32") return { command, args };
|
|
260
|
+
return buildWin32CmdShimInvocation(command, args);
|
|
261
|
+
}
|
|
262
|
+
function verbatimOptions(invocation) {
|
|
263
|
+
return invocation.windowsVerbatimArguments ? { windowsVerbatimArguments: invocation.windowsVerbatimArguments } : {};
|
|
264
|
+
}
|
|
265
|
+
|
|
266
|
+
// src/types/acp-v1.ts
|
|
267
|
+
var ACP_PROTOCOL_VERSION = 1;
|
|
268
|
+
|
|
269
|
+
// src/version.ts
|
|
270
|
+
import { createRequire } from "node:module";
|
|
271
|
+
var require2 = createRequire(import.meta.url);
|
|
272
|
+
function readPackageVersion(load = () => require2("../package.json")) {
|
|
273
|
+
try {
|
|
274
|
+
const packageJson = load();
|
|
275
|
+
if (typeof packageJson.version === "string" && packageJson.version.length > 0) {
|
|
276
|
+
return packageJson.version;
|
|
277
|
+
}
|
|
278
|
+
} catch {
|
|
279
|
+
}
|
|
280
|
+
return "dev";
|
|
281
|
+
}
|
|
282
|
+
var ACP_PACKAGE_VERSION = readPackageVersion();
|
|
283
|
+
|
|
284
|
+
// src/client/acp-late-session.ts
|
|
285
|
+
var LATE_CANCEL_SEND_TIMEOUT_MS = 1e4;
|
|
286
|
+
function cancelLateAcpSession(transport, createPromise) {
|
|
287
|
+
createPromise.then(async (lateId) => {
|
|
288
|
+
try {
|
|
289
|
+
await new Promise((resolve3, reject) => {
|
|
290
|
+
const timer = setTimeout(() => {
|
|
291
|
+
reject(new Error("late session/cancel send timed out"));
|
|
292
|
+
}, LATE_CANCEL_SEND_TIMEOUT_MS);
|
|
293
|
+
Promise.resolve(
|
|
294
|
+
transport.send({
|
|
295
|
+
jsonrpc: "2.0",
|
|
296
|
+
method: "session/cancel",
|
|
297
|
+
params: { sessionId: lateId }
|
|
298
|
+
})
|
|
299
|
+
).then(
|
|
300
|
+
() => {
|
|
301
|
+
clearTimeout(timer);
|
|
302
|
+
resolve3();
|
|
303
|
+
},
|
|
304
|
+
(sendErr) => {
|
|
305
|
+
clearTimeout(timer);
|
|
306
|
+
reject(sendErr instanceof Error ? sendErr : new Error(String(sendErr)));
|
|
307
|
+
}
|
|
308
|
+
);
|
|
309
|
+
});
|
|
310
|
+
} catch (err) {
|
|
311
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
312
|
+
console.warn(
|
|
313
|
+
JSON.stringify({
|
|
314
|
+
level: "warn",
|
|
315
|
+
event: "acp_session.late_cancel_failed",
|
|
316
|
+
sessionId: lateId,
|
|
317
|
+
message
|
|
318
|
+
})
|
|
319
|
+
);
|
|
320
|
+
}
|
|
321
|
+
}).catch((reason) => {
|
|
322
|
+
const message = reason instanceof Error ? reason.message : String(reason);
|
|
323
|
+
console.warn(
|
|
324
|
+
JSON.stringify({
|
|
325
|
+
level: "warn",
|
|
326
|
+
event: "acp_session.late_cancel_failed",
|
|
327
|
+
reason: message
|
|
328
|
+
})
|
|
329
|
+
);
|
|
330
|
+
});
|
|
331
|
+
}
|
|
332
|
+
|
|
333
|
+
// src/client/acp-message-routing.ts
|
|
334
|
+
function isBestEffortAckMethod(method) {
|
|
335
|
+
return method === "mcp/connect" || method === "mcp/message" || method === "mcp/disconnect" || method === "elicitation/create" || method === "elicitation/complete";
|
|
336
|
+
}
|
|
337
|
+
|
|
338
|
+
// src/client/acp-session-errors.ts
|
|
339
|
+
var ACPSessionError = class extends Error {
|
|
340
|
+
kind;
|
|
341
|
+
cause;
|
|
342
|
+
constructor(kind, message, cause) {
|
|
343
|
+
super(message);
|
|
344
|
+
this.name = "ACPSessionError";
|
|
345
|
+
this.kind = kind;
|
|
346
|
+
this.cause = cause;
|
|
347
|
+
}
|
|
348
|
+
};
|
|
349
|
+
function isJsonRpcError(v) {
|
|
350
|
+
return typeof v === "object" && v !== null && typeof v.code === "number" && typeof v.message === "string";
|
|
351
|
+
}
|
|
352
|
+
function isAuthRequiredError(err) {
|
|
353
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
354
|
+
if (/auth(_|-)?required|authentication required/i.test(message)) return true;
|
|
355
|
+
const cause = err instanceof ACPSessionError ? err.cause : err && typeof err === "object" && "cause" in err ? err.cause : err;
|
|
356
|
+
if (!cause || typeof cause !== "object") return false;
|
|
357
|
+
const data = cause.data;
|
|
358
|
+
if (data === "auth_required" || data === "AUTH_REQUIRED") return true;
|
|
359
|
+
if (data && typeof data === "object") {
|
|
360
|
+
const d = data;
|
|
361
|
+
if (d.authRequired === true) return true;
|
|
362
|
+
if (d.code === "auth_required" || d.code === "AUTH_REQUIRED") return true;
|
|
363
|
+
}
|
|
364
|
+
return false;
|
|
365
|
+
}
|
|
366
|
+
|
|
367
|
+
// src/client/acp-session-ops.ts
|
|
368
|
+
function filterMcpServers(agentCapabilities, servers) {
|
|
369
|
+
if (!servers || servers.length === 0) return [];
|
|
370
|
+
const mcpCaps = agentCapabilities.mcpCapabilities ?? {};
|
|
371
|
+
return servers.filter((s) => {
|
|
372
|
+
if ("type" in s && s.type === "http") return mcpCaps.http === true;
|
|
373
|
+
if ("type" in s && s.type === "sse") return mcpCaps.sse === true;
|
|
374
|
+
return true;
|
|
375
|
+
});
|
|
376
|
+
}
|
|
377
|
+
async function executeLoadSession(ctx, sessionId, mcpServers, cwd) {
|
|
378
|
+
if (ctx.closed) throw new ACPSessionError("closed", "session is closed");
|
|
379
|
+
if (!ctx.agentCapabilities.loadSession) {
|
|
380
|
+
throw new ACPSessionError(
|
|
381
|
+
"unsupported_capability",
|
|
382
|
+
"agent does not support session/load (loadSession capability not advertised)"
|
|
383
|
+
);
|
|
384
|
+
}
|
|
385
|
+
if (ctx.sessionId && ctx.sessionId !== sessionId) {
|
|
386
|
+
await ctx.closeSession();
|
|
387
|
+
}
|
|
388
|
+
ctx.resetScratch();
|
|
389
|
+
const servers = filterMcpServers(ctx.agentCapabilities, mcpServers ?? ctx.opts.mcpServers);
|
|
390
|
+
const id = ctx.allocId();
|
|
391
|
+
const result = await ctx.sendRequest(id, "session/load", {
|
|
392
|
+
sessionId,
|
|
393
|
+
cwd: cwd ?? ctx.opts.cwd ?? ctx.opts.projectRoot,
|
|
394
|
+
mcpServers: servers
|
|
395
|
+
});
|
|
396
|
+
if (isJsonRpcError(result)) {
|
|
397
|
+
throw new ACPSessionError("prompt_failed", `session/load failed: ${result.message}`, result);
|
|
398
|
+
}
|
|
399
|
+
ctx.setSessionId(sessionId);
|
|
400
|
+
}
|
|
401
|
+
async function executeResumeSession(ctx, sessionId, mcpServers, cwd) {
|
|
402
|
+
if (ctx.closed) throw new ACPSessionError("closed", "session is closed");
|
|
403
|
+
if (!ctx.agentCapabilities.sessionCapabilities?.resume) {
|
|
404
|
+
throw new ACPSessionError(
|
|
405
|
+
"unsupported_capability",
|
|
406
|
+
"agent does not support session/resume (sessionCapabilities.resume not advertised)"
|
|
407
|
+
);
|
|
408
|
+
}
|
|
409
|
+
if (ctx.sessionId && ctx.sessionId !== sessionId) {
|
|
410
|
+
await ctx.closeSession();
|
|
411
|
+
}
|
|
412
|
+
const servers = filterMcpServers(ctx.agentCapabilities, mcpServers ?? ctx.opts.mcpServers);
|
|
413
|
+
const id = ctx.allocId();
|
|
414
|
+
const result = await ctx.sendRequest(id, "session/resume", {
|
|
415
|
+
sessionId,
|
|
416
|
+
cwd: cwd ?? ctx.opts.cwd ?? ctx.opts.projectRoot,
|
|
417
|
+
mcpServers: servers
|
|
418
|
+
});
|
|
419
|
+
if (isJsonRpcError(result)) {
|
|
420
|
+
throw new ACPSessionError("prompt_failed", `session/resume failed: ${result.message}`, result);
|
|
421
|
+
}
|
|
422
|
+
ctx.setSessionId(sessionId);
|
|
423
|
+
}
|
|
424
|
+
async function executeListSessions(ctx, cursor, cwd) {
|
|
425
|
+
if (ctx.closed) throw new ACPSessionError("closed", "session is closed");
|
|
426
|
+
if (!ctx.agentCapabilities.sessionCapabilities?.list) {
|
|
427
|
+
throw new ACPSessionError(
|
|
428
|
+
"unsupported_capability",
|
|
429
|
+
"agent does not support session/list (sessionCapabilities.list not advertised)"
|
|
430
|
+
);
|
|
431
|
+
}
|
|
432
|
+
const id = ctx.allocId();
|
|
433
|
+
const params = {};
|
|
434
|
+
if (cursor !== void 0) params.cursor = cursor;
|
|
435
|
+
if (cwd !== void 0) params.cwd = cwd;
|
|
436
|
+
const result = await ctx.sendRequest(id, "session/list", params);
|
|
437
|
+
if (isJsonRpcError(result)) {
|
|
438
|
+
throw new ACPSessionError("prompt_failed", `session/list failed: ${result.message}`, result);
|
|
439
|
+
}
|
|
440
|
+
const r = result;
|
|
441
|
+
return {
|
|
442
|
+
sessions: r.sessions ?? [],
|
|
443
|
+
nextCursor: r.nextCursor
|
|
444
|
+
};
|
|
445
|
+
}
|
|
446
|
+
async function executeDeleteSession(ctx, sessionId) {
|
|
447
|
+
if (ctx.closed) throw new ACPSessionError("closed", "session is closed");
|
|
448
|
+
if (!ctx.agentCapabilities.sessionCapabilities?.delete) {
|
|
449
|
+
throw new ACPSessionError(
|
|
450
|
+
"unsupported_capability",
|
|
451
|
+
"agent does not support session/delete (sessionCapabilities.delete not advertised)"
|
|
452
|
+
);
|
|
453
|
+
}
|
|
454
|
+
const id = ctx.allocId();
|
|
455
|
+
const result = await ctx.sendRequest(id, "session/delete", { sessionId });
|
|
456
|
+
if (isJsonRpcError(result)) {
|
|
457
|
+
throw new ACPSessionError("prompt_failed", `session/delete failed: ${result.message}`, result);
|
|
458
|
+
}
|
|
459
|
+
if (ctx.sessionId === sessionId) {
|
|
460
|
+
ctx.setSessionId(null);
|
|
461
|
+
}
|
|
462
|
+
}
|
|
463
|
+
async function executeForkSession(ctx, sourceSessionId, cwd, mcpServers) {
|
|
464
|
+
if (ctx.closed) throw new ACPSessionError("closed", "session is closed");
|
|
465
|
+
const servers = filterMcpServers(ctx.agentCapabilities, mcpServers ?? ctx.opts.mcpServers);
|
|
466
|
+
const id = ctx.allocId();
|
|
467
|
+
const result = await ctx.sendRequest(id, "session/fork", {
|
|
468
|
+
sessionId: sourceSessionId,
|
|
469
|
+
cwd: cwd ?? ctx.opts.cwd ?? ctx.opts.projectRoot,
|
|
470
|
+
...servers.length > 0 ? { mcpServers: servers } : {}
|
|
471
|
+
});
|
|
472
|
+
if (isJsonRpcError(result)) {
|
|
473
|
+
throw new ACPSessionError("prompt_failed", `session/fork failed: ${result.message}`, result);
|
|
474
|
+
}
|
|
475
|
+
const newId = result.sessionId;
|
|
476
|
+
if (typeof newId !== "string" || !newId) {
|
|
477
|
+
throw new ACPSessionError("protocol_error", "session/fork returned no sessionId", result);
|
|
478
|
+
}
|
|
479
|
+
return newId;
|
|
480
|
+
}
|
|
481
|
+
async function executeSetMode(ctx, sessionId, modeId) {
|
|
482
|
+
if (ctx.closed) throw new ACPSessionError("closed", "session is closed");
|
|
483
|
+
const id = ctx.allocId();
|
|
484
|
+
const result = await ctx.sendRequest(id, "session/set_mode", { sessionId, modeId });
|
|
485
|
+
if (isJsonRpcError(result)) {
|
|
486
|
+
throw new ACPSessionError(
|
|
487
|
+
"prompt_failed",
|
|
488
|
+
`session/set_mode failed: ${result.message}`,
|
|
489
|
+
result
|
|
490
|
+
);
|
|
491
|
+
}
|
|
492
|
+
}
|
|
493
|
+
async function executeSetConfigOption(ctx, sessionId, configId, value) {
|
|
494
|
+
if (ctx.closed) throw new ACPSessionError("closed", "session is closed");
|
|
495
|
+
const id = ctx.allocId();
|
|
496
|
+
const result = await ctx.sendRequest(id, "session/set_config_option", {
|
|
497
|
+
sessionId,
|
|
498
|
+
configId,
|
|
499
|
+
value
|
|
500
|
+
});
|
|
501
|
+
if (isJsonRpcError(result)) {
|
|
502
|
+
throw new ACPSessionError(
|
|
503
|
+
"prompt_failed",
|
|
504
|
+
`session/set_config_option failed: ${result.message}`,
|
|
505
|
+
result
|
|
506
|
+
);
|
|
507
|
+
}
|
|
508
|
+
}
|
|
509
|
+
async function executeListProviders(ctx) {
|
|
510
|
+
if (ctx.closed) throw new ACPSessionError("closed", "session is closed");
|
|
511
|
+
const id = ctx.allocId();
|
|
512
|
+
const result = await ctx.sendRequest(id, "providers/list", {});
|
|
513
|
+
if (isJsonRpcError(result)) {
|
|
514
|
+
throw new ACPSessionError("prompt_failed", `providers/list failed: ${result.message}`, result);
|
|
515
|
+
}
|
|
516
|
+
const r = result;
|
|
517
|
+
return { providers: r.providers ?? [], currentProviderId: r.currentProviderId ?? null };
|
|
518
|
+
}
|
|
519
|
+
async function executeSetProvider(ctx, providerId, config) {
|
|
520
|
+
if (ctx.closed) throw new ACPSessionError("closed", "session is closed");
|
|
521
|
+
const id = ctx.allocId();
|
|
522
|
+
const result = await ctx.sendRequest(id, "providers/set", { providerId, ...config ?? {} });
|
|
523
|
+
if (isJsonRpcError(result)) {
|
|
524
|
+
throw new ACPSessionError("prompt_failed", `providers/set failed: ${result.message}`, result);
|
|
525
|
+
}
|
|
526
|
+
}
|
|
527
|
+
async function executeDisableProvider(ctx) {
|
|
528
|
+
if (ctx.closed) throw new ACPSessionError("closed", "session is closed");
|
|
529
|
+
const id = ctx.allocId();
|
|
530
|
+
const result = await ctx.sendRequest(id, "providers/disable", {});
|
|
531
|
+
if (isJsonRpcError(result)) {
|
|
532
|
+
throw new ACPSessionError(
|
|
533
|
+
"prompt_failed",
|
|
534
|
+
`providers/disable failed: ${result.message}`,
|
|
535
|
+
result
|
|
536
|
+
);
|
|
537
|
+
}
|
|
538
|
+
}
|
|
539
|
+
async function executeMcpMessage(ctx, connectionId, message) {
|
|
540
|
+
if (ctx.closed) throw new ACPSessionError("closed", "session is closed");
|
|
541
|
+
const id = ctx.allocId();
|
|
542
|
+
const result = await ctx.sendRequest(id, "mcp/message", { connectionId, message });
|
|
543
|
+
if (isJsonRpcError(result)) {
|
|
544
|
+
throw new ACPSessionError("prompt_failed", `mcp/message failed: ${result.message}`, result);
|
|
545
|
+
}
|
|
546
|
+
return result;
|
|
547
|
+
}
|
|
548
|
+
async function executeCreateSession(ctx) {
|
|
549
|
+
const servers = filterMcpServers(ctx.agentCapabilities, ctx.opts.mcpServers);
|
|
550
|
+
const id = ctx.allocId();
|
|
551
|
+
const result = await ctx.sendRequest(id, "session/new", {
|
|
552
|
+
cwd: ctx.opts.cwd ?? ctx.opts.projectRoot,
|
|
553
|
+
mcpServers: servers
|
|
554
|
+
});
|
|
555
|
+
if (isJsonRpcError(result)) {
|
|
556
|
+
throw new ACPSessionError(
|
|
557
|
+
"session_create_failed",
|
|
558
|
+
`session/new failed: ${result.message}`,
|
|
559
|
+
result
|
|
560
|
+
);
|
|
561
|
+
}
|
|
562
|
+
const sessionId = result.sessionId;
|
|
563
|
+
if (typeof sessionId !== "string" || sessionId.length === 0) {
|
|
564
|
+
throw new ACPSessionError("protocol_error", "session/new returned no sessionId", result);
|
|
565
|
+
}
|
|
566
|
+
return sessionId;
|
|
567
|
+
}
|
|
568
|
+
|
|
569
|
+
// src/client/acp-session-auth.ts
|
|
570
|
+
async function authenticate(host, methodId) {
|
|
571
|
+
if (host.state === "closed") {
|
|
572
|
+
throw new ACPSessionError("closed", "session is closed");
|
|
573
|
+
}
|
|
574
|
+
if (host.state !== "ready" && host.state !== "authenticated") {
|
|
575
|
+
throw new ACPSessionError(
|
|
576
|
+
"protocol_error",
|
|
577
|
+
`authenticate called in state=${host.state} (expected 'ready')`
|
|
578
|
+
);
|
|
579
|
+
}
|
|
580
|
+
if (host.state === "authenticated") return;
|
|
581
|
+
if (!host.authMethods.some((m) => m.id === methodId)) {
|
|
582
|
+
throw new ACPSessionError(
|
|
583
|
+
"auth_failed",
|
|
584
|
+
`auth method "${methodId}" not in advertised methods: ${host.authMethods.map((m) => m.id).join(", ")}`
|
|
585
|
+
);
|
|
586
|
+
}
|
|
587
|
+
if (host.authMethods.find((m) => m.id === methodId)?.type === "terminal") {
|
|
588
|
+
throw new ACPSessionError(
|
|
589
|
+
"auth_failed",
|
|
590
|
+
"Terminal authentication requires interactive login followed by reconnect; it cannot use authenticate"
|
|
591
|
+
);
|
|
592
|
+
}
|
|
593
|
+
const id = host.allocId();
|
|
594
|
+
const result = await host.sendRequest(id, "authenticate", { methodId });
|
|
595
|
+
if (isJsonRpcError(result)) {
|
|
596
|
+
throw new ACPSessionError("auth_failed", `authenticate failed: ${result.message}`, result);
|
|
597
|
+
}
|
|
598
|
+
host.state = "authenticated";
|
|
599
|
+
}
|
|
600
|
+
async function logout(host) {
|
|
601
|
+
if (host.state === "closed") {
|
|
602
|
+
throw new ACPSessionError("closed", "session is closed");
|
|
603
|
+
}
|
|
604
|
+
if (!host.agentCapabilities.auth?.logout) {
|
|
605
|
+
throw new ACPSessionError(
|
|
606
|
+
"unsupported_capability",
|
|
607
|
+
"agent does not support logout (auth.logout capability not advertised)"
|
|
608
|
+
);
|
|
609
|
+
}
|
|
610
|
+
const id = host.allocId();
|
|
611
|
+
const result = await host.sendRequest(id, "logout", {});
|
|
612
|
+
if (isJsonRpcError(result)) {
|
|
613
|
+
throw new ACPSessionError("logout_failed", `logout failed: ${result.message}`, result);
|
|
614
|
+
}
|
|
615
|
+
host.state = "ready";
|
|
249
616
|
}
|
|
250
|
-
function
|
|
251
|
-
return invocation.windowsVerbatimArguments ? { windowsVerbatimArguments: invocation.windowsVerbatimArguments } : {};
|
|
252
|
-
}
|
|
253
|
-
|
|
254
|
-
// src/types/acp-v1.ts
|
|
255
|
-
var ACP_PROTOCOL_VERSION = 1;
|
|
256
|
-
|
|
257
|
-
// src/version.ts
|
|
258
|
-
import { createRequire } from "node:module";
|
|
259
|
-
var require2 = createRequire(import.meta.url);
|
|
260
|
-
function readPackageVersion(load = () => require2("../package.json")) {
|
|
617
|
+
async function createSessionWithAuth(host) {
|
|
261
618
|
try {
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
619
|
+
return await executeCreateSession(host.opContext());
|
|
620
|
+
} catch (err) {
|
|
621
|
+
if (host.state === "authenticated" || !isAuthRequiredError(err)) {
|
|
622
|
+
throw err instanceof ACPSessionError ? err : new ACPSessionError(
|
|
623
|
+
"session_create_failed",
|
|
624
|
+
err instanceof Error ? err.message : String(err),
|
|
625
|
+
err
|
|
626
|
+
);
|
|
265
627
|
}
|
|
266
|
-
|
|
628
|
+
await host.ensureAuthenticated();
|
|
629
|
+
return executeCreateSession(host.opContext());
|
|
267
630
|
}
|
|
268
|
-
return "dev";
|
|
269
631
|
}
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
632
|
+
async function ensureAuthenticated(host) {
|
|
633
|
+
if (host.state === "authenticated") return;
|
|
634
|
+
if (host.authMethods.length === 0) {
|
|
635
|
+
throw new ACPSessionError(
|
|
636
|
+
"auth_failed",
|
|
637
|
+
"This agent requires authentication before a session can start, but advertised no authMethods. Log into the CLI, then retry."
|
|
638
|
+
);
|
|
639
|
+
}
|
|
640
|
+
const inProcess = host.authMethods.find(
|
|
641
|
+
(m) => m.type === void 0 || m.type === "agent" || m.type === "oauth" || m.type === "http"
|
|
642
|
+
);
|
|
643
|
+
if (inProcess) {
|
|
644
|
+
await host.authenticate(inProcess.id);
|
|
645
|
+
return;
|
|
646
|
+
}
|
|
647
|
+
const terminal = host.authMethods.find((m) => m.type === "terminal");
|
|
648
|
+
const setupArgs = terminal?.args?.length ? terminal.args.join(" ") : void 0;
|
|
649
|
+
const setup = setupArgs !== void 0 ? `${host.opts.command}${host.opts.args?.length ? ` ${host.opts.args.join(" ")}` : ""} ${setupArgs}` : `${host.opts.command}${host.opts.args?.length ? ` ${host.opts.args.join(" ")}` : ""}`;
|
|
650
|
+
throw new ACPSessionError(
|
|
651
|
+
"auth_failed",
|
|
652
|
+
`This agent requires a terminal login before ACP can start. Run \`${setup}\` (or the CLI's /login), then retry.`
|
|
653
|
+
);
|
|
275
654
|
}
|
|
276
655
|
|
|
277
656
|
// src/client/file-server.ts
|
|
@@ -316,6 +695,13 @@ var FileServer = class {
|
|
|
316
695
|
}
|
|
317
696
|
/** Read a text file. Returns the content as a string. */
|
|
318
697
|
async readTextFile(params) {
|
|
698
|
+
if (params.line != null && (!Number.isSafeInteger(params.line) || params.line < 1) || params.limit != null && (!Number.isSafeInteger(params.limit) || params.limit < 0)) {
|
|
699
|
+
throw new FsError(
|
|
700
|
+
"INVALID_PATH",
|
|
701
|
+
params.path,
|
|
702
|
+
"line must be a positive integer and limit a non-negative integer"
|
|
703
|
+
);
|
|
704
|
+
}
|
|
319
705
|
const safe = await this.resolveInside(params.path);
|
|
320
706
|
const controller = new AbortController();
|
|
321
707
|
const timer = setTimeout(() => controller.abort(), this.timeoutMs);
|
|
@@ -334,7 +720,12 @@ var FileServer = class {
|
|
|
334
720
|
encoding: "utf8",
|
|
335
721
|
signal: controller.signal
|
|
336
722
|
});
|
|
337
|
-
return { content };
|
|
723
|
+
if (params.line == null && params.limit == null) return { content };
|
|
724
|
+
const lines = content.match(/[^\n]*\n|[^\n]+$/g) ?? [];
|
|
725
|
+
const start = (params.line ?? 1) - 1;
|
|
726
|
+
return {
|
|
727
|
+
content: lines.slice(start, params.limit == null ? void 0 : start + params.limit).join("")
|
|
728
|
+
};
|
|
338
729
|
} catch (err) {
|
|
339
730
|
if (err instanceof FsError) throw err;
|
|
340
731
|
if (controller.signal.aborted) {
|
|
@@ -439,7 +830,8 @@ var FileServer = class {
|
|
|
439
830
|
throw mapFsError(err, resolvedPath);
|
|
440
831
|
}
|
|
441
832
|
const normReal = isWindows ? real.toLowerCase() : real;
|
|
442
|
-
|
|
833
|
+
const realRootWithSep = normRealRoot.endsWith(path.sep) ? normRealRoot : normRealRoot + path.sep;
|
|
834
|
+
if (normReal === normRealRoot || normReal.startsWith(realRootWithSep)) return;
|
|
443
835
|
throw new FsError(
|
|
444
836
|
"OUTSIDE_ROOT",
|
|
445
837
|
resolvedPath,
|
|
@@ -488,9 +880,12 @@ async function handleAcpPermissionRequest(msg, permissionPolicy, sender, callbac
|
|
|
488
880
|
);
|
|
489
881
|
await sender.sendResult(id, { outcome });
|
|
490
882
|
} catch (err) {
|
|
883
|
+
if (isAbortLikeError(err)) {
|
|
884
|
+
await sender.sendResult(id, { outcome: { outcome: "cancelled" } });
|
|
885
|
+
return;
|
|
886
|
+
}
|
|
491
887
|
const message = err instanceof Error ? err.message : String(err);
|
|
492
|
-
|
|
493
|
-
await sender.sendErrorResponse(id, code, `permission policy failed: ${message}`);
|
|
888
|
+
await sender.sendErrorResponse(id, -32603, `permission policy failed: ${message}`);
|
|
494
889
|
}
|
|
495
890
|
}
|
|
496
891
|
async function handleAcpFsRequest(msg, fileServer, permissionPolicy, sender, callbackOptions = {}) {
|
|
@@ -526,7 +921,9 @@ async function handleAcpFsRequest(msg, fileServer, permissionPolicy, sender, cal
|
|
|
526
921
|
if (msg.method === "fs/read_text_file") {
|
|
527
922
|
const result = await fileServer.readTextFile({
|
|
528
923
|
sessionId: params.sessionId ?? "",
|
|
529
|
-
path: params.path
|
|
924
|
+
path: params.path,
|
|
925
|
+
line: params.line,
|
|
926
|
+
limit: params.limit
|
|
530
927
|
});
|
|
531
928
|
await sender.sendResult(id, result);
|
|
532
929
|
} else {
|
|
@@ -626,338 +1023,107 @@ async function handleAcpTerminalRequest(msg, terminalServer, permissionPolicy, s
|
|
|
626
1023
|
}
|
|
627
1024
|
async function authorizeAcpCallback(permissionPolicy, partial, callbackOptions) {
|
|
628
1025
|
try {
|
|
629
|
-
const outcome = await runPermissionWithDeadline(
|
|
630
|
-
permissionPolicy,
|
|
631
|
-
{
|
|
632
|
-
toolCall: {
|
|
633
|
-
sessionUpdate: "tool_call_update",
|
|
634
|
-
toolCallId: partial.toolCallId,
|
|
635
|
-
title: partial.title,
|
|
636
|
-
kind: partial.kind,
|
|
637
|
-
status: "pending",
|
|
638
|
-
rawInput: partial.rawInput
|
|
639
|
-
},
|
|
640
|
-
options: [
|
|
641
|
-
{ optionId: "allow", name: "Allow", kind: "allow_once" },
|
|
642
|
-
{ optionId: "reject", name: "Reject", kind: "reject_once" }
|
|
643
|
-
]
|
|
644
|
-
},
|
|
645
|
-
callbackOptions
|
|
646
|
-
);
|
|
647
|
-
return outcome.outcome === "selected" && outcome.optionId !== "reject" && outcome.optionId !== "reject_once" && outcome.optionId !== "reject_always" ? "allowed" : "denied";
|
|
648
|
-
} catch (err) {
|
|
649
|
-
return isAbortLikeError(err) ? "cancelled" : "denied";
|
|
650
|
-
}
|
|
651
|
-
}
|
|
652
|
-
async function runPermissionWithDeadline(permissionPolicy, call, callbackOptions) {
|
|
653
|
-
const timeoutMs = resolvePermissionDeadline(callbackOptions.permissionTimeoutMs);
|
|
654
|
-
const controller = new AbortController();
|
|
655
|
-
const abort = () => controller.abort();
|
|
656
|
-
const timer = timeoutMs === null ? null : setTimeout(abort, timeoutMs);
|
|
657
|
-
let removeAbort;
|
|
658
|
-
if (callbackOptions.signal) {
|
|
659
|
-
if (callbackOptions.signal.aborted) {
|
|
660
|
-
abort();
|
|
661
|
-
} else {
|
|
662
|
-
callbackOptions.signal.addEventListener("abort", abort, { once: true });
|
|
663
|
-
removeAbort = () => callbackOptions.signal?.removeEventListener("abort", abort);
|
|
664
|
-
}
|
|
665
|
-
}
|
|
666
|
-
try {
|
|
667
|
-
return await Promise.race([
|
|
668
|
-
permissionPolicy({
|
|
669
|
-
toolCall: call.toolCall,
|
|
670
|
-
options: call.options,
|
|
671
|
-
signal: controller.signal
|
|
672
|
-
}),
|
|
673
|
-
rejectOnAbort(controller.signal)
|
|
674
|
-
]);
|
|
675
|
-
} finally {
|
|
676
|
-
if (timer !== null) clearTimeout(timer);
|
|
677
|
-
removeAbort?.();
|
|
678
|
-
}
|
|
679
|
-
}
|
|
680
|
-
function rejectOnAbort(signal) {
|
|
681
|
-
return new Promise((_, reject) => {
|
|
682
|
-
const rejectAbort = () => reject(new Error("permission request cancelled or timed out"));
|
|
683
|
-
if (signal.aborted) {
|
|
684
|
-
rejectAbort();
|
|
685
|
-
return;
|
|
686
|
-
}
|
|
687
|
-
signal.addEventListener("abort", rejectAbort, { once: true });
|
|
688
|
-
});
|
|
689
|
-
}
|
|
690
|
-
function isAbortLikeError(err) {
|
|
691
|
-
return err instanceof Error && /cancelled|canceled|timed out|aborted/i.test(err.message);
|
|
692
|
-
}
|
|
693
|
-
function resolvePermissionDeadline(value) {
|
|
694
|
-
if (value === Number.POSITIVE_INFINITY) return null;
|
|
695
|
-
if (value !== void 0 && Number.isFinite(value) && value > 0) return Math.trunc(value);
|
|
696
|
-
return DEFAULT_PERMISSION_TIMEOUT_MS;
|
|
697
|
-
}
|
|
698
|
-
|
|
699
|
-
// src/client/acp-session-content.ts
|
|
700
|
-
function textContent(text) {
|
|
701
|
-
return { type: "text", text };
|
|
702
|
-
}
|
|
703
|
-
function imageContent(mimeType, data) {
|
|
704
|
-
return { type: "image", mimeType, data };
|
|
705
|
-
}
|
|
706
|
-
function audioContent(mimeType, data) {
|
|
707
|
-
return { type: "audio", mimeType, data };
|
|
708
|
-
}
|
|
709
|
-
function extractText(block) {
|
|
710
|
-
if (typeof block !== "object" || block === null) return "";
|
|
711
|
-
const b = block;
|
|
712
|
-
if (b.type === "text" && typeof b.text === "string") return b.text;
|
|
713
|
-
if (b.type === "resource" && b.resource && typeof b.resource === "object" && typeof b.resource.text === "string") {
|
|
714
|
-
return b.resource.text;
|
|
715
|
-
}
|
|
716
|
-
return "";
|
|
717
|
-
}
|
|
718
|
-
function isRecord(v) {
|
|
719
|
-
return typeof v === "object" && v !== null && !Array.isArray(v);
|
|
720
|
-
}
|
|
721
|
-
function emptyRunResult(stopReason) {
|
|
722
|
-
return {
|
|
723
|
-
text: "",
|
|
724
|
-
stopReason,
|
|
725
|
-
hasText: false,
|
|
726
|
-
toolCalls: [],
|
|
727
|
-
diffs: [],
|
|
728
|
-
thoughts: ""
|
|
729
|
-
};
|
|
730
|
-
}
|
|
731
|
-
|
|
732
|
-
// src/client/acp-session-errors.ts
|
|
733
|
-
var ACPSessionError = class extends Error {
|
|
734
|
-
kind;
|
|
735
|
-
cause;
|
|
736
|
-
constructor(kind, message, cause) {
|
|
737
|
-
super(message);
|
|
738
|
-
this.name = "ACPSessionError";
|
|
739
|
-
this.kind = kind;
|
|
740
|
-
this.cause = cause;
|
|
741
|
-
}
|
|
742
|
-
};
|
|
743
|
-
function isJsonRpcError(v) {
|
|
744
|
-
return typeof v === "object" && v !== null && typeof v.code === "number" && typeof v.message === "string";
|
|
745
|
-
}
|
|
746
|
-
function isAuthRequiredError(err) {
|
|
747
|
-
const message = err instanceof Error ? err.message : String(err);
|
|
748
|
-
if (/auth(_|-)?required|authentication required/i.test(message)) return true;
|
|
749
|
-
const cause = err instanceof ACPSessionError ? err.cause : err && typeof err === "object" && "cause" in err ? err.cause : err;
|
|
750
|
-
if (!cause || typeof cause !== "object") return false;
|
|
751
|
-
const data = cause.data;
|
|
752
|
-
if (data === "auth_required" || data === "AUTH_REQUIRED") return true;
|
|
753
|
-
if (data && typeof data === "object") {
|
|
754
|
-
const d = data;
|
|
755
|
-
if (d.authRequired === true) return true;
|
|
756
|
-
if (d.code === "auth_required" || d.code === "AUTH_REQUIRED") return true;
|
|
757
|
-
}
|
|
758
|
-
return false;
|
|
759
|
-
}
|
|
760
|
-
|
|
761
|
-
// src/client/acp-session-ops.ts
|
|
762
|
-
function filterMcpServers(agentCapabilities, servers) {
|
|
763
|
-
if (!servers || servers.length === 0) return [];
|
|
764
|
-
const mcpCaps = agentCapabilities.mcpCapabilities ?? {};
|
|
765
|
-
return servers.filter((s) => {
|
|
766
|
-
if ("type" in s && s.type === "http") return mcpCaps.http === true;
|
|
767
|
-
if ("type" in s && s.type === "sse") return mcpCaps.sse === true;
|
|
768
|
-
return true;
|
|
769
|
-
});
|
|
770
|
-
}
|
|
771
|
-
async function executeLoadSession(ctx, sessionId, mcpServers, cwd) {
|
|
772
|
-
if (ctx.closed) throw new ACPSessionError("closed", "session is closed");
|
|
773
|
-
if (!ctx.agentCapabilities.loadSession) {
|
|
774
|
-
throw new ACPSessionError(
|
|
775
|
-
"unsupported_capability",
|
|
776
|
-
"agent does not support session/load (loadSession capability not advertised)"
|
|
777
|
-
);
|
|
778
|
-
}
|
|
779
|
-
if (ctx.sessionId) {
|
|
780
|
-
await ctx.closeSession();
|
|
781
|
-
}
|
|
782
|
-
ctx.resetScratch();
|
|
783
|
-
const servers = filterMcpServers(ctx.agentCapabilities, mcpServers ?? ctx.opts.mcpServers);
|
|
784
|
-
const id = ctx.allocId();
|
|
785
|
-
const result = await ctx.sendRequest(id, "session/load", {
|
|
786
|
-
sessionId,
|
|
787
|
-
cwd: cwd ?? ctx.opts.cwd ?? ctx.opts.projectRoot,
|
|
788
|
-
mcpServers: servers
|
|
789
|
-
});
|
|
790
|
-
if (isJsonRpcError(result)) {
|
|
791
|
-
throw new ACPSessionError("prompt_failed", `session/load failed: ${result.message}`, result);
|
|
792
|
-
}
|
|
793
|
-
ctx.setSessionId(sessionId);
|
|
794
|
-
}
|
|
795
|
-
async function executeResumeSession(ctx, sessionId, mcpServers, cwd) {
|
|
796
|
-
if (ctx.closed) throw new ACPSessionError("closed", "session is closed");
|
|
797
|
-
if (!ctx.agentCapabilities.sessionCapabilities?.resume) {
|
|
798
|
-
throw new ACPSessionError(
|
|
799
|
-
"unsupported_capability",
|
|
800
|
-
"agent does not support session/resume (sessionCapabilities.resume not advertised)"
|
|
801
|
-
);
|
|
802
|
-
}
|
|
803
|
-
if (ctx.sessionId) {
|
|
804
|
-
await ctx.closeSession();
|
|
805
|
-
}
|
|
806
|
-
const servers = filterMcpServers(ctx.agentCapabilities, mcpServers ?? ctx.opts.mcpServers);
|
|
807
|
-
const id = ctx.allocId();
|
|
808
|
-
const result = await ctx.sendRequest(id, "session/resume", {
|
|
809
|
-
sessionId,
|
|
810
|
-
cwd: cwd ?? ctx.opts.cwd ?? ctx.opts.projectRoot,
|
|
811
|
-
mcpServers: servers
|
|
812
|
-
});
|
|
813
|
-
if (isJsonRpcError(result)) {
|
|
814
|
-
throw new ACPSessionError("prompt_failed", `session/resume failed: ${result.message}`, result);
|
|
815
|
-
}
|
|
816
|
-
ctx.setSessionId(sessionId);
|
|
817
|
-
}
|
|
818
|
-
async function executeListSessions(ctx, cursor, cwd) {
|
|
819
|
-
if (ctx.closed) throw new ACPSessionError("closed", "session is closed");
|
|
820
|
-
if (!ctx.agentCapabilities.sessionCapabilities?.list) {
|
|
821
|
-
throw new ACPSessionError(
|
|
822
|
-
"unsupported_capability",
|
|
823
|
-
"agent does not support session/list (sessionCapabilities.list not advertised)"
|
|
824
|
-
);
|
|
825
|
-
}
|
|
826
|
-
const id = ctx.allocId();
|
|
827
|
-
const params = {};
|
|
828
|
-
if (cursor !== void 0) params.cursor = cursor;
|
|
829
|
-
if (cwd !== void 0) params.cwd = cwd;
|
|
830
|
-
const result = await ctx.sendRequest(id, "session/list", params);
|
|
831
|
-
if (isJsonRpcError(result)) {
|
|
832
|
-
throw new ACPSessionError("prompt_failed", `session/list failed: ${result.message}`, result);
|
|
833
|
-
}
|
|
834
|
-
const r = result;
|
|
835
|
-
return {
|
|
836
|
-
sessions: r.sessions ?? [],
|
|
837
|
-
nextCursor: r.nextCursor
|
|
838
|
-
};
|
|
839
|
-
}
|
|
840
|
-
async function executeDeleteSession(ctx, sessionId) {
|
|
841
|
-
if (ctx.closed) throw new ACPSessionError("closed", "session is closed");
|
|
842
|
-
if (!ctx.agentCapabilities.sessionCapabilities?.delete) {
|
|
843
|
-
throw new ACPSessionError(
|
|
844
|
-
"unsupported_capability",
|
|
845
|
-
"agent does not support session/delete (sessionCapabilities.delete not advertised)"
|
|
1026
|
+
const outcome = await runPermissionWithDeadline(
|
|
1027
|
+
permissionPolicy,
|
|
1028
|
+
{
|
|
1029
|
+
toolCall: {
|
|
1030
|
+
sessionUpdate: "tool_call_update",
|
|
1031
|
+
toolCallId: partial.toolCallId,
|
|
1032
|
+
title: partial.title,
|
|
1033
|
+
kind: partial.kind,
|
|
1034
|
+
status: "pending",
|
|
1035
|
+
rawInput: partial.rawInput
|
|
1036
|
+
},
|
|
1037
|
+
options: [
|
|
1038
|
+
{ optionId: "allow", name: "Allow", kind: "allow_once" },
|
|
1039
|
+
{ optionId: "reject", name: "Reject", kind: "reject_once" }
|
|
1040
|
+
]
|
|
1041
|
+
},
|
|
1042
|
+
callbackOptions
|
|
846
1043
|
);
|
|
1044
|
+
return outcome.outcome === "selected" && outcome.optionId !== "reject" && outcome.optionId !== "reject_once" && outcome.optionId !== "reject_always" ? "allowed" : "denied";
|
|
1045
|
+
} catch (err) {
|
|
1046
|
+
return isAbortLikeError(err) ? "cancelled" : "denied";
|
|
847
1047
|
}
|
|
848
|
-
|
|
849
|
-
|
|
850
|
-
|
|
851
|
-
|
|
1048
|
+
}
|
|
1049
|
+
async function runPermissionWithDeadline(permissionPolicy, call, callbackOptions) {
|
|
1050
|
+
const timeoutMs = resolvePermissionDeadline(callbackOptions.permissionTimeoutMs);
|
|
1051
|
+
const controller = new AbortController();
|
|
1052
|
+
const abort = () => controller.abort();
|
|
1053
|
+
const timer = timeoutMs === null ? null : setTimeout(abort, timeoutMs);
|
|
1054
|
+
let removeAbort;
|
|
1055
|
+
if (callbackOptions.signal) {
|
|
1056
|
+
if (callbackOptions.signal.aborted) {
|
|
1057
|
+
abort();
|
|
1058
|
+
} else {
|
|
1059
|
+
callbackOptions.signal.addEventListener("abort", abort, { once: true });
|
|
1060
|
+
removeAbort = () => callbackOptions.signal?.removeEventListener("abort", abort);
|
|
1061
|
+
}
|
|
852
1062
|
}
|
|
853
|
-
|
|
854
|
-
|
|
1063
|
+
try {
|
|
1064
|
+
return await Promise.race([
|
|
1065
|
+
permissionPolicy({
|
|
1066
|
+
toolCall: call.toolCall,
|
|
1067
|
+
options: call.options,
|
|
1068
|
+
signal: controller.signal
|
|
1069
|
+
}),
|
|
1070
|
+
rejectOnAbort(controller.signal)
|
|
1071
|
+
]);
|
|
1072
|
+
} finally {
|
|
1073
|
+
if (timer !== null) clearTimeout(timer);
|
|
1074
|
+
removeAbort?.();
|
|
855
1075
|
}
|
|
856
1076
|
}
|
|
857
|
-
|
|
858
|
-
|
|
859
|
-
|
|
860
|
-
|
|
861
|
-
|
|
862
|
-
|
|
863
|
-
|
|
864
|
-
|
|
1077
|
+
function rejectOnAbort(signal) {
|
|
1078
|
+
return new Promise((_, reject) => {
|
|
1079
|
+
const rejectAbort = () => reject(new Error("permission request cancelled or timed out"));
|
|
1080
|
+
if (signal.aborted) {
|
|
1081
|
+
rejectAbort();
|
|
1082
|
+
return;
|
|
1083
|
+
}
|
|
1084
|
+
signal.addEventListener("abort", rejectAbort, { once: true });
|
|
865
1085
|
});
|
|
866
|
-
if (isJsonRpcError(result)) {
|
|
867
|
-
throw new ACPSessionError("prompt_failed", `session/fork failed: ${result.message}`, result);
|
|
868
|
-
}
|
|
869
|
-
const newId = result.sessionId;
|
|
870
|
-
if (typeof newId !== "string" || !newId) {
|
|
871
|
-
throw new ACPSessionError("protocol_error", "session/fork returned no sessionId", result);
|
|
872
|
-
}
|
|
873
|
-
return newId;
|
|
874
1086
|
}
|
|
875
|
-
|
|
876
|
-
|
|
877
|
-
const id = ctx.allocId();
|
|
878
|
-
const result = await ctx.sendRequest(id, "session/set_mode", { sessionId, modeId });
|
|
879
|
-
if (isJsonRpcError(result)) {
|
|
880
|
-
throw new ACPSessionError(
|
|
881
|
-
"prompt_failed",
|
|
882
|
-
`session/set_mode failed: ${result.message}`,
|
|
883
|
-
result
|
|
884
|
-
);
|
|
885
|
-
}
|
|
1087
|
+
function isAbortLikeError(err) {
|
|
1088
|
+
return err instanceof Error && /cancelled|canceled|timed out|aborted/i.test(err.message);
|
|
886
1089
|
}
|
|
887
|
-
|
|
888
|
-
if (
|
|
889
|
-
|
|
890
|
-
|
|
891
|
-
sessionId,
|
|
892
|
-
configId,
|
|
893
|
-
value
|
|
894
|
-
});
|
|
895
|
-
if (isJsonRpcError(result)) {
|
|
896
|
-
throw new ACPSessionError(
|
|
897
|
-
"prompt_failed",
|
|
898
|
-
`session/set_config_option failed: ${result.message}`,
|
|
899
|
-
result
|
|
900
|
-
);
|
|
901
|
-
}
|
|
1090
|
+
function resolvePermissionDeadline(value) {
|
|
1091
|
+
if (value === Number.POSITIVE_INFINITY) return null;
|
|
1092
|
+
if (value !== void 0 && Number.isFinite(value) && value > 0) return Math.trunc(value);
|
|
1093
|
+
return DEFAULT_PERMISSION_TIMEOUT_MS;
|
|
902
1094
|
}
|
|
903
|
-
|
|
904
|
-
|
|
905
|
-
|
|
906
|
-
|
|
907
|
-
if (isJsonRpcError(result)) {
|
|
908
|
-
throw new ACPSessionError("prompt_failed", `providers/list failed: ${result.message}`, result);
|
|
909
|
-
}
|
|
910
|
-
const r = result;
|
|
911
|
-
return { providers: r.providers ?? [], currentProviderId: r.currentProviderId ?? null };
|
|
1095
|
+
|
|
1096
|
+
// src/client/acp-session-content.ts
|
|
1097
|
+
function textContent(text) {
|
|
1098
|
+
return { type: "text", text };
|
|
912
1099
|
}
|
|
913
|
-
|
|
914
|
-
|
|
915
|
-
const id = ctx.allocId();
|
|
916
|
-
const result = await ctx.sendRequest(id, "providers/set", { providerId, ...config ?? {} });
|
|
917
|
-
if (isJsonRpcError(result)) {
|
|
918
|
-
throw new ACPSessionError("prompt_failed", `providers/set failed: ${result.message}`, result);
|
|
919
|
-
}
|
|
1100
|
+
function imageContent(mimeType, data) {
|
|
1101
|
+
return { type: "image", mimeType, data };
|
|
920
1102
|
}
|
|
921
|
-
|
|
922
|
-
|
|
923
|
-
const id = ctx.allocId();
|
|
924
|
-
const result = await ctx.sendRequest(id, "providers/disable", {});
|
|
925
|
-
if (isJsonRpcError(result)) {
|
|
926
|
-
throw new ACPSessionError(
|
|
927
|
-
"prompt_failed",
|
|
928
|
-
`providers/disable failed: ${result.message}`,
|
|
929
|
-
result
|
|
930
|
-
);
|
|
931
|
-
}
|
|
1103
|
+
function audioContent(mimeType, data) {
|
|
1104
|
+
return { type: "audio", mimeType, data };
|
|
932
1105
|
}
|
|
933
|
-
|
|
934
|
-
if (
|
|
935
|
-
const
|
|
936
|
-
|
|
937
|
-
if (
|
|
938
|
-
|
|
1106
|
+
function extractText(block) {
|
|
1107
|
+
if (typeof block !== "object" || block === null) return "";
|
|
1108
|
+
const b = block;
|
|
1109
|
+
if (b.type === "text" && typeof b.text === "string") return b.text;
|
|
1110
|
+
if (b.type === "resource" && b.resource && typeof b.resource === "object" && typeof b.resource.text === "string") {
|
|
1111
|
+
return b.resource.text;
|
|
939
1112
|
}
|
|
940
|
-
return
|
|
1113
|
+
return "";
|
|
941
1114
|
}
|
|
942
|
-
|
|
943
|
-
|
|
944
|
-
|
|
945
|
-
|
|
946
|
-
|
|
947
|
-
|
|
948
|
-
|
|
949
|
-
|
|
950
|
-
|
|
951
|
-
|
|
952
|
-
|
|
953
|
-
|
|
954
|
-
);
|
|
955
|
-
}
|
|
956
|
-
const sessionId = result.sessionId;
|
|
957
|
-
if (typeof sessionId !== "string" || sessionId.length === 0) {
|
|
958
|
-
throw new ACPSessionError("protocol_error", "session/new returned no sessionId", result);
|
|
959
|
-
}
|
|
960
|
-
return sessionId;
|
|
1115
|
+
function isRecord(v) {
|
|
1116
|
+
return typeof v === "object" && v !== null && !Array.isArray(v);
|
|
1117
|
+
}
|
|
1118
|
+
function emptyRunResult(stopReason) {
|
|
1119
|
+
return {
|
|
1120
|
+
text: "",
|
|
1121
|
+
stopReason,
|
|
1122
|
+
hasText: false,
|
|
1123
|
+
toolCalls: [],
|
|
1124
|
+
diffs: [],
|
|
1125
|
+
thoughts: ""
|
|
1126
|
+
};
|
|
961
1127
|
}
|
|
962
1128
|
|
|
963
1129
|
// src/client/acp-session-updates.ts
|
|
@@ -978,6 +1144,7 @@ function handleAcpSessionUpdate(msg, scratch, emitProgress) {
|
|
|
978
1144
|
}
|
|
979
1145
|
return;
|
|
980
1146
|
}
|
|
1147
|
+
case "agent_thought_chunk":
|
|
981
1148
|
case "thought_chunk": {
|
|
982
1149
|
const text = extractText(u.content);
|
|
983
1150
|
if (text) {
|
|
@@ -1643,7 +1810,6 @@ function finitePositiveLimit(value, fallback) {
|
|
|
1643
1810
|
}
|
|
1644
1811
|
|
|
1645
1812
|
// src/client/acp-session.ts
|
|
1646
|
-
var LATE_CANCEL_SEND_TIMEOUT_MS = 1e4;
|
|
1647
1813
|
var ACPSession = class _ACPSession {
|
|
1648
1814
|
transport;
|
|
1649
1815
|
fileServer;
|
|
@@ -1813,7 +1979,7 @@ var ACPSession = class _ACPSession {
|
|
|
1813
1979
|
throw new ACPSessionError("protocol_error", "initialize returned no protocolVersion");
|
|
1814
1980
|
}
|
|
1815
1981
|
const r = result;
|
|
1816
|
-
if (r.protocolVersion
|
|
1982
|
+
if (r.protocolVersion !== ACP_PROTOCOL_VERSION) {
|
|
1817
1983
|
throw new ACPSessionError(
|
|
1818
1984
|
"unsupported_capability",
|
|
1819
1985
|
`agent requires protocolVersion=${r.protocolVersion}, client supports up to ${ACP_PROTOCOL_VERSION}`
|
|
@@ -1833,49 +1999,14 @@ var ACPSession = class _ACPSession {
|
|
|
1833
1999
|
* Call this AFTER start() and BEFORE any session/new call.
|
|
1834
2000
|
*/
|
|
1835
2001
|
async authenticate(methodId) {
|
|
1836
|
-
|
|
1837
|
-
throw new ACPSessionError("closed", "session is closed");
|
|
1838
|
-
}
|
|
1839
|
-
if (this.state !== "ready" && this.state !== "authenticated") {
|
|
1840
|
-
throw new ACPSessionError(
|
|
1841
|
-
"protocol_error",
|
|
1842
|
-
`authenticate called in state=${this.state} (expected 'ready')`
|
|
1843
|
-
);
|
|
1844
|
-
}
|
|
1845
|
-
if (this.state === "authenticated") return;
|
|
1846
|
-
if (!this.authMethods.some((m) => m.id === methodId)) {
|
|
1847
|
-
throw new ACPSessionError(
|
|
1848
|
-
"auth_failed",
|
|
1849
|
-
`auth method "${methodId}" not in advertised methods: ${this.authMethods.map((m) => m.id).join(", ")}`
|
|
1850
|
-
);
|
|
1851
|
-
}
|
|
1852
|
-
const id = this.allocId();
|
|
1853
|
-
const result = await this.sendRequest(id, "authenticate", { methodId });
|
|
1854
|
-
if (isJsonRpcError(result)) {
|
|
1855
|
-
throw new ACPSessionError("auth_failed", `authenticate failed: ${result.message}`, result);
|
|
1856
|
-
}
|
|
1857
|
-
this.state = "authenticated";
|
|
2002
|
+
return authenticate(this.acpSessionAuthHost(), methodId);
|
|
1858
2003
|
}
|
|
1859
2004
|
/**
|
|
1860
2005
|
* Log out from the current authenticated session.
|
|
1861
2006
|
* Only callable if the agent advertises `auth.logout` capability.
|
|
1862
2007
|
*/
|
|
1863
2008
|
async logout() {
|
|
1864
|
-
|
|
1865
|
-
throw new ACPSessionError("closed", "session is closed");
|
|
1866
|
-
}
|
|
1867
|
-
if (!this.agentCapabilities.auth?.logout) {
|
|
1868
|
-
throw new ACPSessionError(
|
|
1869
|
-
"unsupported_capability",
|
|
1870
|
-
"agent does not support logout (auth.logout capability not advertised)"
|
|
1871
|
-
);
|
|
1872
|
-
}
|
|
1873
|
-
const id = this.allocId();
|
|
1874
|
-
const result = await this.sendRequest(id, "logout", {});
|
|
1875
|
-
if (isJsonRpcError(result)) {
|
|
1876
|
-
throw new ACPSessionError("logout_failed", `logout failed: ${result.message}`, result);
|
|
1877
|
-
}
|
|
1878
|
-
this.state = "ready";
|
|
2009
|
+
return logout(this.acpSessionAuthHost());
|
|
1879
2010
|
}
|
|
1880
2011
|
// ──────────────────────────────────────────────────────────────────────
|
|
1881
2012
|
// Session management delegation
|
|
@@ -1944,6 +2075,16 @@ var ACPSession = class _ACPSession {
|
|
|
1944
2075
|
if (signal.aborted) {
|
|
1945
2076
|
return emptyRunResult("cancelled");
|
|
1946
2077
|
}
|
|
2078
|
+
const caps = this.agentCapabilities.promptCapabilities;
|
|
2079
|
+
for (const block of blocks) {
|
|
2080
|
+
const supported = block.type === "image" ? caps?.image : block.type === "audio" ? caps?.audio : block.type === "resource" ? caps?.embeddedContext : true;
|
|
2081
|
+
if (supported !== true) {
|
|
2082
|
+
throw new ACPSessionError(
|
|
2083
|
+
"unsupported_capability",
|
|
2084
|
+
`agent does not support ${block.type} prompt content`
|
|
2085
|
+
);
|
|
2086
|
+
}
|
|
2087
|
+
}
|
|
1947
2088
|
let cancelled = false;
|
|
1948
2089
|
this.promptCallbackAbort = new AbortController();
|
|
1949
2090
|
const onAbort = () => {
|
|
@@ -2078,50 +2219,7 @@ var ACPSession = class _ACPSession {
|
|
|
2078
2219
|
* being silently swallowed.
|
|
2079
2220
|
*/
|
|
2080
2221
|
cancelLateSession(createPromise) {
|
|
2081
|
-
|
|
2082
|
-
try {
|
|
2083
|
-
await new Promise((resolve3, reject) => {
|
|
2084
|
-
const timer = setTimeout(() => {
|
|
2085
|
-
reject(new Error("late session/cancel send timed out"));
|
|
2086
|
-
}, LATE_CANCEL_SEND_TIMEOUT_MS);
|
|
2087
|
-
Promise.resolve(
|
|
2088
|
-
this.transport.send({
|
|
2089
|
-
jsonrpc: "2.0",
|
|
2090
|
-
method: "session/cancel",
|
|
2091
|
-
params: { sessionId: lateId }
|
|
2092
|
-
})
|
|
2093
|
-
).then(
|
|
2094
|
-
() => {
|
|
2095
|
-
clearTimeout(timer);
|
|
2096
|
-
resolve3();
|
|
2097
|
-
},
|
|
2098
|
-
(sendErr) => {
|
|
2099
|
-
clearTimeout(timer);
|
|
2100
|
-
reject(sendErr instanceof Error ? sendErr : new Error(String(sendErr)));
|
|
2101
|
-
}
|
|
2102
|
-
);
|
|
2103
|
-
});
|
|
2104
|
-
} catch (err) {
|
|
2105
|
-
const message = err instanceof Error ? err.message : String(err);
|
|
2106
|
-
console.warn(
|
|
2107
|
-
JSON.stringify({
|
|
2108
|
-
level: "warn",
|
|
2109
|
-
event: "acp_session.late_cancel_failed",
|
|
2110
|
-
sessionId: lateId,
|
|
2111
|
-
message
|
|
2112
|
-
})
|
|
2113
|
-
);
|
|
2114
|
-
}
|
|
2115
|
-
}).catch((reason) => {
|
|
2116
|
-
const message = reason instanceof Error ? reason.message : String(reason);
|
|
2117
|
-
console.warn(
|
|
2118
|
-
JSON.stringify({
|
|
2119
|
-
level: "warn",
|
|
2120
|
-
event: "acp_session.late_cancel_failed",
|
|
2121
|
-
reason: message
|
|
2122
|
-
})
|
|
2123
|
-
);
|
|
2124
|
-
});
|
|
2222
|
+
cancelLateAcpSession(this.transport, createPromise);
|
|
2125
2223
|
}
|
|
2126
2224
|
async closeSession() {
|
|
2127
2225
|
if (!this.sessionId) return;
|
|
@@ -2168,7 +2266,13 @@ var ACPSession = class _ACPSession {
|
|
|
2168
2266
|
// Wire layer
|
|
2169
2267
|
// ────────────────────────────────────────────────────────────────────
|
|
2170
2268
|
allocId() {
|
|
2171
|
-
|
|
2269
|
+
let candidate = Number.isSafeInteger(this.nextId) && this.nextId >= 1 ? this.nextId : 1;
|
|
2270
|
+
for (let probe = 0; probe <= this.pending.size; probe++) {
|
|
2271
|
+
this.nextId = candidate >= Number.MAX_SAFE_INTEGER ? 1 : candidate + 1;
|
|
2272
|
+
if (!this.pending.has(candidate)) return candidate;
|
|
2273
|
+
candidate = this.nextId;
|
|
2274
|
+
}
|
|
2275
|
+
throw new ACPSessionError("protocol_error", "no JSON-RPC request id is available");
|
|
2172
2276
|
}
|
|
2173
2277
|
async sendRequest(id, method, params, timeoutMs) {
|
|
2174
2278
|
return new Promise((resolve3, reject) => {
|
|
@@ -2200,19 +2304,7 @@ var ACPSession = class _ACPSession {
|
|
|
2200
2304
|
* `authMethods`; we do not pop OAuth on every spawn.
|
|
2201
2305
|
*/
|
|
2202
2306
|
async createSessionWithAuth() {
|
|
2203
|
-
|
|
2204
|
-
return await executeCreateSession(this.opContext());
|
|
2205
|
-
} catch (err) {
|
|
2206
|
-
if (this.state === "authenticated" || !isAuthRequiredError(err)) {
|
|
2207
|
-
throw err instanceof ACPSessionError ? err : new ACPSessionError(
|
|
2208
|
-
"session_create_failed",
|
|
2209
|
-
err instanceof Error ? err.message : String(err),
|
|
2210
|
-
err
|
|
2211
|
-
);
|
|
2212
|
-
}
|
|
2213
|
-
await this.ensureAuthenticated();
|
|
2214
|
-
return executeCreateSession(this.opContext());
|
|
2215
|
-
}
|
|
2307
|
+
return createSessionWithAuth(this.acpSessionAuthHost());
|
|
2216
2308
|
}
|
|
2217
2309
|
/**
|
|
2218
2310
|
* Pick a non-terminal auth method and run `authenticate`. Terminal-only
|
|
@@ -2220,27 +2312,7 @@ var ACPSession = class _ACPSession {
|
|
|
2220
2312
|
* we refuse rather than hang a TUI inside the JSON-RPC child.
|
|
2221
2313
|
*/
|
|
2222
2314
|
async ensureAuthenticated() {
|
|
2223
|
-
|
|
2224
|
-
if (this.authMethods.length === 0) {
|
|
2225
|
-
throw new ACPSessionError(
|
|
2226
|
-
"auth_failed",
|
|
2227
|
-
"This agent requires authentication before a session can start, but advertised no authMethods. Log into the CLI, then retry."
|
|
2228
|
-
);
|
|
2229
|
-
}
|
|
2230
|
-
const inProcess = this.authMethods.find(
|
|
2231
|
-
(m) => m.type === void 0 || m.type === "agent" || m.type === "oauth" || m.type === "http"
|
|
2232
|
-
);
|
|
2233
|
-
if (inProcess) {
|
|
2234
|
-
await this.authenticate(inProcess.id);
|
|
2235
|
-
return;
|
|
2236
|
-
}
|
|
2237
|
-
const terminal = this.authMethods.find((m) => m.type === "terminal");
|
|
2238
|
-
const setupArgs = terminal?.args?.length ? terminal.args.join(" ") : void 0;
|
|
2239
|
-
const setup = setupArgs !== void 0 ? `${this.opts.command} ${setupArgs}` : `${this.opts.command}${this.opts.args?.length ? ` ${this.opts.args.join(" ")}` : ""}`;
|
|
2240
|
-
throw new ACPSessionError(
|
|
2241
|
-
"auth_failed",
|
|
2242
|
-
`This agent requires a terminal login before ACP can start. Run \`${setup}\` (or the CLI's /login), then retry.`
|
|
2243
|
-
);
|
|
2315
|
+
return ensureAuthenticated(this.acpSessionAuthHost());
|
|
2244
2316
|
}
|
|
2245
2317
|
sendResult(id, result) {
|
|
2246
2318
|
return this.transport.send({ jsonrpc: "2.0", id, result });
|
|
@@ -2318,7 +2390,7 @@ var ACPSession = class _ACPSession {
|
|
|
2318
2390
|
}
|
|
2319
2391
|
if (isBestEffortAckMethod(msg.method)) {
|
|
2320
2392
|
if (msg.id !== void 0) {
|
|
2321
|
-
this.
|
|
2393
|
+
this.sendErrorResponse(msg.id, -32601, `Unsupported method: ${msg.method}`).catch(() => {
|
|
2322
2394
|
});
|
|
2323
2395
|
}
|
|
2324
2396
|
return;
|
|
@@ -2327,6 +2399,13 @@ var ACPSession = class _ACPSession {
|
|
|
2327
2399
|
return;
|
|
2328
2400
|
}
|
|
2329
2401
|
if (msg.method) {
|
|
2402
|
+
if (msg.id !== void 0) {
|
|
2403
|
+
void this.sendErrorResponse(msg.id, -32601, `Unsupported method: ${msg.method}`).catch(
|
|
2404
|
+
() => {
|
|
2405
|
+
}
|
|
2406
|
+
);
|
|
2407
|
+
return;
|
|
2408
|
+
}
|
|
2330
2409
|
console.warn(
|
|
2331
2410
|
JSON.stringify({
|
|
2332
2411
|
level: "warn",
|
|
@@ -2349,6 +2428,29 @@ var ACPSession = class _ACPSession {
|
|
|
2349
2428
|
resetScratch() {
|
|
2350
2429
|
this.scratch = createSessionScratch();
|
|
2351
2430
|
}
|
|
2431
|
+
acpSessionAuthHost() {
|
|
2432
|
+
const self = this;
|
|
2433
|
+
return {
|
|
2434
|
+
get state() {
|
|
2435
|
+
return self.state;
|
|
2436
|
+
},
|
|
2437
|
+
set state(value) {
|
|
2438
|
+
self.state = value;
|
|
2439
|
+
},
|
|
2440
|
+
get authMethods() {
|
|
2441
|
+
return self.authMethods;
|
|
2442
|
+
},
|
|
2443
|
+
allocId: (...args) => this.allocId(...args),
|
|
2444
|
+
sendRequest: (...args) => this.sendRequest(...args),
|
|
2445
|
+
get agentCapabilities() {
|
|
2446
|
+
return self.agentCapabilities;
|
|
2447
|
+
},
|
|
2448
|
+
opContext: (...args) => this.opContext(...args),
|
|
2449
|
+
ensureAuthenticated: (...args) => this.ensureAuthenticated(...args),
|
|
2450
|
+
authenticate: (...args) => this.authenticate(...args),
|
|
2451
|
+
opts: this.opts
|
|
2452
|
+
};
|
|
2453
|
+
}
|
|
2352
2454
|
};
|
|
2353
2455
|
|
|
2354
2456
|
// src/integration/acp-subagent-runner.ts
|