@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/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 if (this.handlers.size === 0) {
227
- if (this.messageQueue.length >= this.maxQueuedMessages || this.queuedChars + chars > this.maxQueuedChars) {
228
- writeErr(
229
- `[acp-child message queue exceeds ${this.maxQueuedMessages} entries or ${this.maxQueuedChars} characters]
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
- this.stop();
233
- return;
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 verbatimOptions(invocation) {
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
- const packageJson = load();
263
- if (typeof packageJson.version === "string" && packageJson.version.length > 0) {
264
- return packageJson.version;
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
- } catch {
628
+ await host.ensureAuthenticated();
629
+ return executeCreateSession(host.opContext());
267
630
  }
268
- return "dev";
269
631
  }
270
- var ACP_PACKAGE_VERSION = readPackageVersion();
271
-
272
- // src/client/acp-message-routing.ts
273
- function isBestEffortAckMethod(method) {
274
- return method === "mcp/connect" || method === "mcp/message" || method === "mcp/disconnect" || method === "elicitation/create" || method === "elicitation/complete";
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
- if (normReal === normRealRoot || normReal.startsWith(normRealRoot + path.sep)) return;
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
- const code = isAbortLikeError(err) ? -32800 : -32603;
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
- const id = ctx.allocId();
849
- const result = await ctx.sendRequest(id, "session/delete", { sessionId });
850
- if (isJsonRpcError(result)) {
851
- throw new ACPSessionError("prompt_failed", `session/delete failed: ${result.message}`, result);
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
- if (ctx.sessionId === sessionId) {
854
- ctx.setSessionId(null);
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
- async function executeForkSession(ctx, sourceSessionId, cwd, mcpServers) {
858
- if (ctx.closed) throw new ACPSessionError("closed", "session is closed");
859
- const servers = filterMcpServers(ctx.agentCapabilities, mcpServers ?? ctx.opts.mcpServers);
860
- const id = ctx.allocId();
861
- const result = await ctx.sendRequest(id, "session/fork", {
862
- sessionId: sourceSessionId,
863
- cwd: cwd ?? ctx.opts.cwd ?? ctx.opts.projectRoot,
864
- ...servers.length > 0 ? { mcpServers: servers } : {}
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
- async function executeSetMode(ctx, sessionId, modeId) {
876
- if (ctx.closed) throw new ACPSessionError("closed", "session is closed");
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
- async function executeSetConfigOption(ctx, sessionId, configId, value) {
888
- if (ctx.closed) throw new ACPSessionError("closed", "session is closed");
889
- const id = ctx.allocId();
890
- const result = await ctx.sendRequest(id, "session/set_config_option", {
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
- async function executeListProviders(ctx) {
904
- if (ctx.closed) throw new ACPSessionError("closed", "session is closed");
905
- const id = ctx.allocId();
906
- const result = await ctx.sendRequest(id, "providers/list", {});
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
- async function executeSetProvider(ctx, providerId, config) {
914
- if (ctx.closed) throw new ACPSessionError("closed", "session is closed");
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
- async function executeDisableProvider(ctx) {
922
- if (ctx.closed) throw new ACPSessionError("closed", "session is closed");
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
- async function executeMcpMessage(ctx, connectionId, message) {
934
- if (ctx.closed) throw new ACPSessionError("closed", "session is closed");
935
- const id = ctx.allocId();
936
- const result = await ctx.sendRequest(id, "mcp/message", { connectionId, message });
937
- if (isJsonRpcError(result)) {
938
- throw new ACPSessionError("prompt_failed", `mcp/message failed: ${result.message}`, result);
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 result;
1113
+ return "";
941
1114
  }
942
- async function executeCreateSession(ctx) {
943
- const servers = filterMcpServers(ctx.agentCapabilities, ctx.opts.mcpServers);
944
- const id = ctx.allocId();
945
- const result = await ctx.sendRequest(id, "session/new", {
946
- cwd: ctx.opts.cwd ?? ctx.opts.projectRoot,
947
- mcpServers: servers
948
- });
949
- if (isJsonRpcError(result)) {
950
- throw new ACPSessionError(
951
- "session_create_failed",
952
- `session/new failed: ${result.message}`,
953
- result
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 > ACP_PROTOCOL_VERSION) {
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
- if (this.state === "closed") {
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
- if (this.state === "closed") {
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
- createPromise.then(async (lateId) => {
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
- return this.nextId++;
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
- try {
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
- if (this.state === "authenticated") return;
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.sendResult(msg.id, {}).catch(() => {
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