diff --git a/apps/desktop/src/main/acp-host.mjs b/apps/desktop/src/main/acp-host.mjs index 46b15622..c5c2c818 100644 --- a/apps/desktop/src/main/acp-host.mjs +++ b/apps/desktop/src/main/acp-host.mjs @@ -789,7 +789,7 @@ export function createAcpHostRuntime(options = {}) { const prepared = id === "piAgent" ? withPiPath(command, resolutionDeps(commandDeps)) : id === "antigravity" ? withAntigravityMacProxy(command, commandDeps) : command; connected = await withHandshakeTimeout((signal) => connect(prepared, `install-${id}`, () => {}, signal), 90_000); - return { ...adapterShell(id), state: "available", promptCapabilities: connected.promptCapabilities }; + return { ...adapterShell(id), state: "available", promptCapabilities: connected.promptCapabilities, authMethods: connected.authMethods }; } catch (error) { return { ...adapterShell(id), ...failureFields(classifyAcpFailure(error)), ...(isAuthRequiredError(error) ? { authMethods: error.authMethods ?? [] } : {}) }; } finally { @@ -860,7 +860,7 @@ export function createAcpHostRuntime(options = {}) { ...adapterShell(id), state: "available", promptCapabilities: connected.promptCapabilities, - ...((id === "workbuddyCn" || id === "workbuddyIntl") ? { authMethods: connected.authMethods } : {}), + authMethods: connected.authMethods, ...(resolved.version ? { version: resolved.version, managed: true } : {}), }; latestStatus.set(id, adapter); @@ -878,7 +878,7 @@ export function createAcpHostRuntime(options = {}) { let connected; try { connected = await withHandshakeTimeout((signal) => connect(resolved.command, `auth-${id}`, () => {}, signal, input.methodId), 5 * 60_000); - const adapter = { ...adapterShell(id), state: "available", promptCapabilities: connected.promptCapabilities, ...((id === "workbuddyCn" || id === "workbuddyIntl") ? { authMethods: connected.authMethods } : {}), ...(resolved.version ? { version: resolved.version, managed: true } : {}) }; + const adapter = { ...adapterShell(id), state: "available", promptCapabilities: connected.promptCapabilities, authMethods: connected.authMethods, ...(resolved.version ? { version: resolved.version, managed: true } : {}) }; latestStatus.set(id, adapter); return adapter; } catch (error) { diff --git a/apps/desktop/src/main/acp-host.test.mjs b/apps/desktop/src/main/acp-host.test.mjs index a328ac26..acef62d9 100644 --- a/apps/desktop/src/main/acp-host.test.mjs +++ b/apps/desktop/src/main/acp-host.test.mjs @@ -68,7 +68,7 @@ const collector = () => { }; }; -const fakeAgentSource = ({ reportPath, secretPath, allowImage, allowEmbedded, hold, requireAuth, promptRefusal }) => `#!/usr/bin/env bun +const fakeAgentSource = ({ reportPath, secretPath, allowImage, allowEmbedded, hold, requireAuth, advertiseAuth, promptRefusal }) => `#!/usr/bin/env bun import * as acp from ${JSON.stringify(sdkHref)}; import { writeFileSync } from "node:fs"; @@ -78,6 +78,7 @@ const allowImage = ${allowImage ? "true" : "false"}; const allowEmbedded = ${allowEmbedded ? "true" : "false"}; const hold = ${hold ? "true" : "false"}; const requireAuth = ${requireAuth ? "true" : "false"}; +const advertiseAuth = ${advertiseAuth ? "true" : "false"}; const promptRefusal = ${promptRefusal ? JSON.stringify(promptRefusal) : "null"}; let authenticated = false; const report = { initialize: null, newSession: null, permission: null, readError: null, readResult: null, prompt: null, authMethod: null }; @@ -119,7 +120,7 @@ acp.agent({ name: "edgeever-fake-agent" }) return { protocolVersion: ctx.params.protocolVersion, agentCapabilities: { promptCapabilities: { image: allowImage, embeddedContext: allowEmbedded } }, - authMethods: requireAuth ? [{ id: "browser", name: "Browser" }] : [], + authMethods: requireAuth || advertiseAuth ? [{ id: "browser", name: "Browser" }] : [], }; }) .onRequest("authenticate", (ctx) => { @@ -708,6 +709,26 @@ describe("ACP stdio session", () => { } }, 15_000); + sessionTest("keeps ACP login methods visible when another agent can already create a session", async () => { + const directory = await mkdtemp(path.join(tmpdir(), "edgeever-acp-optional-auth-")); + try { + const scriptPath = await writeFakeAgent(directory, { + reportPath: path.join(directory, "report.json"), + secretPath: path.join(directory, "secret.txt"), + allowImage: false, + allowEmbedded: false, + hold: false, + advertiseAuth: true, + }); + const runtime = createAcpHostRuntime(); + const probed = await runtime.probeAdapter({ id: "antigravity", path: scriptPath }); + expect(probed.state).toBe("available"); + expect(probed.authMethods).toEqual([{ id: "browser", name: "Browser" }]); + } finally { + await rm(directory, { recursive: true, force: true }); + } + }, 15_000); + sessionTest("reports a prompt-time authentication refusal instead of an empty reply", async () => { const directory = await mkdtemp(path.join(tmpdir(), "edgeever-acp-refusal-")); try { diff --git a/apps/web/src/components/settings/DesktopAcpAgentCard.tsx b/apps/web/src/components/settings/DesktopAcpAgentCard.tsx index 07c1490b..beea8f6b 100644 --- a/apps/web/src/components/settings/DesktopAcpAgentCard.tsx +++ b/apps/web/src/components/settings/DesktopAcpAgentCard.tsx @@ -126,15 +126,15 @@ const DesktopAcpAgentCardBody = ({ bridge }: { bridge: boolean }) => { installError, state: shown?.state, }); - const hasDetails = Boolean( - shown?.state === "not_installed" || - shown?.state === "needs_login" || - (isWorkBuddy && shown?.state === "available" && Boolean(shown.authMethods?.length)) || - shown?.updateError - ); const authMethods = shown?.authMethods?.filter((method) => ( adapterId === "workbuddyCn" ? method.id !== "external" : adapterId === "workbuddyIntl" ? method.id !== "internal" : true )) ?? []; + const hasDetails = Boolean( + shown?.state === "not_installed" || + shown?.state === "needs_login" || + (shown?.state === "available" && authMethods.length > 0) || + shown?.updateError + ); const selectAdapter = (id: DesktopAcpAdapterId) => { setAdapterId(id); @@ -369,7 +369,10 @@ const DesktopAcpAgentCardBody = ({ bridge }: { bridge: boolean }) => { {(shown?.state === "needs_login" || shown?.state === "available") && isWorkBuddy ? (

{t(adapterId === "workbuddyCn" ? "aiAssistant.agentSource.workbuddyCnLoginHint" : "aiAssistant.agentSource.workbuddyIntlLoginHint")}

) : null} - {(shown?.state === "needs_login" || (isWorkBuddy && shown?.state === "available")) && authMethods.length ? ( + {shown?.state === "available" && !isWorkBuddy && authMethods.length ? ( +

{t("aiAssistant.agentSource.authAvailableHint")}

+ ) : null} + {(shown?.state === "needs_login" || shown?.state === "available") && authMethods.length ? (
{authMethods.map((method) => (