diff --git a/src/gateway/gateway-acp-bind.live.test.ts b/src/gateway/gateway-acp-bind.live.test.ts index a09aec5c9f4..af33b192430 100644 --- a/src/gateway/gateway-acp-bind.live.test.ts +++ b/src/gateway/gateway-acp-bind.live.test.ts @@ -43,6 +43,10 @@ const DEFAULT_LIVE_CODEX_MODEL = "gpt-5.5"; const DEFAULT_LIVE_PARENT_MODEL = "openai/gpt-5.4"; type LiveAcpAgent = "claude" | "codex" | "droid" | "gemini" | "opencode"; +class AcpBindSkipError extends Error { + readonly name = "AcpBindSkipError"; +} + function createSlackCurrentConversationBindingRegistry() { return createTestRegistry([ { @@ -262,6 +266,16 @@ function isRetryableAcpBindWarmupText(texts: string[]): boolean { ); } +function isSkippableAcpBindText(params: { liveAgent: LiveAcpAgent; texts: string[] }): boolean { + if (params.liveAgent !== "codex") { + return false; + } + const combined = params.texts.join("\n\n").toLowerCase(); + return ( + combined.includes("acp_session_init_failed") && combined.includes("authentication required") + ); +} + describe("isRetryableAcpBindWarmupText", () => { it.each([ { @@ -280,6 +294,23 @@ describe("isRetryableAcpBindWarmupText", () => { }); }); +describe("isSkippableAcpBindText", () => { + it.each([ + { + liveAgent: "codex" as const, + texts: ["ACP error (ACP_SESSION_INIT_FAILED): Authentication required"], + expected: true, + }, + { + liveAgent: "gemini" as const, + texts: ["ACP error (ACP_SESSION_INIT_FAILED): Authentication required"], + expected: false, + }, + ])("returns $expected for $liveAgent", ({ liveAgent, texts, expected }) => { + expect(isSkippableAcpBindText({ liveAgent, texts })).toBe(expected); + }); +}); + function formatAssistantTextPreview(texts: string[], maxChars = 600): string { const combined = texts.join("\n\n").trim(); if (!combined) { @@ -362,6 +393,13 @@ async function bindConversationAndWait(params: { return { mainAssistantTexts, spawnedSessionKey }; } if (!isRetryableAcpBindWarmupText(mainAssistantTexts)) { + if (isSkippableAcpBindText({ liveAgent: params.liveAgent, texts: mainAssistantTexts })) { + throw new AcpBindSkipError( + `SKIP: ${params.liveAgent} ACP bind unavailable: ${formatAssistantTextPreview( + mainAssistantTexts, + )}`, + ); + } throw new Error( `bind command did not produce an ACP session: ${formatAssistantTextPreview(mainAssistantTexts)}`, ); @@ -662,14 +700,24 @@ describeLive("gateway live (ACP bind)", () => { pinActivePluginChannelRegistry(channelRegistry); try { - const { mainAssistantTexts, spawnedSessionKey } = await bindConversationAndWait({ - client, - sessionKey: originalSessionKey, - liveAgent, - originatingChannel: "slack", - originatingTo: conversationId, - originatingAccountId: accountId, - }); + let bindResult: Awaited>; + try { + bindResult = await bindConversationAndWait({ + client, + sessionKey: originalSessionKey, + liveAgent, + originatingChannel: "slack", + originatingTo: conversationId, + originatingAccountId: accountId, + }); + } catch (error) { + if (error instanceof AcpBindSkipError) { + console.error(error.message); + return; + } + throw error; + } + const { mainAssistantTexts, spawnedSessionKey } = bindResult; logLiveStep("bind command completed"); expect(mainAssistantTexts.join("\n\n")).toContain("Bound this conversation to"); expect(spawnedSessionKey).toMatch(new RegExp(`^agent:${liveAgent}:acp:`));