show ACP login methods for all desktop agents
This commit is contained in:
1 parent
46dfbeb98d
commit
7a4dce8f86
8 files changed
+49
-15
No files matched your search
@@ -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) {
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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 ? (
|
||||
<p className="text-xs leading-relaxed text-slate-600">{t(adapterId === "workbuddyCn" ? "aiAssistant.agentSource.workbuddyCnLoginHint" : "aiAssistant.agentSource.workbuddyIntlLoginHint")}</p>
|
||||
) : null}
|
||||
{(shown?.state === "needs_login" || (isWorkBuddy && shown?.state === "available")) && authMethods.length ? (
|
||||
{shown?.state === "available" && !isWorkBuddy && authMethods.length ? (
|
||||
<p className="text-xs leading-relaxed text-slate-600">{t("aiAssistant.agentSource.authAvailableHint")}</p>
|
||||
) : null}
|
||||
{(shown?.state === "needs_login" || shown?.state === "available") && authMethods.length ? (
|
||||
<div className="flex flex-wrap gap-2 pt-0.5">
|
||||
{authMethods.map((method) => (
|
||||
<Button key={method.id} type="button" variant="outline" size="sm" className="h-8 bg-card text-xs font-normal" disabled={authenticating} onClick={() => void authenticate(method.id)}>
|
||||
|
||||
@@ -15,3 +15,9 @@ test("a newly installed managed connector takes priority over an older probe", (
|
||||
const previous = { id: "antigravity", label: "Antigravity", state: "failed", version: "1.2.1", managed: true };
|
||||
expect(displayedDesktopAcpAdapter({ id: "antigravity", path: "", listed: [current], probed: previous })).toBe(current);
|
||||
});
|
||||
|
||||
test("a prompt-time login failure takes priority over an earlier successful session probe", () => {
|
||||
const current = { id: "grokBuild", label: "Grok Build", state: "needs_login", authMethods: [{ id: "browser", name: "Browser" }] };
|
||||
const earlierProbe = { id: "grokBuild", label: "Grok Build", state: "available" };
|
||||
expect(displayedDesktopAcpAdapter({ id: "grokBuild", path: "", listed: [current], probed: earlierProbe })).toBe(current);
|
||||
});
|
||||
@@ -33,6 +33,7 @@ export const displayedDesktopAcpAdapter = ({
|
||||
const checked = probed?.id === id ? probed : undefined;
|
||||
if (id === "antigravity" && path.trim()) return checked;
|
||||
if (current?.state === "installing" || (current?.managed && (!checked?.managed || current.version !== checked.version))) return current;
|
||||
if (current?.state === "needs_login" && checked?.state === "available") return current;
|
||||
return checked ?? current;
|
||||
};
|
||||
|
||||
|
||||
@@ -1439,11 +1439,12 @@ export const enUS = {
|
||||
updateFailed: "Automatic update failed. The previous version remains in use.",
|
||||
authenticating: "Signing in…",
|
||||
authenticateWith: "Sign in with {{method}}",
|
||||
authAvailableHint: "This agent offers ACP sign-in. If sending a message requires login, complete it here.",
|
||||
loginUnavailable: "This ACP connector requires authentication but provides no ACP sign-in method. Authenticate in the agent, then check the connection again.",
|
||||
autoUpdated: "Auto-updated",
|
||||
probe: "Check connection",
|
||||
probing: "Checking…",
|
||||
probeHint: "Opens a temporary session and closes it. Your notes are not sent.",
|
||||
probeHint: "Opens and closes a temporary session without sending your notes. This check does not verify model sign-in.",
|
||||
notProbed: "Not checked yet",
|
||||
invalidPath: "Enter an absolute path to an executable file.",
|
||||
states: {
|
||||
|
||||
@@ -1439,11 +1439,12 @@ export const ja = {
|
||||
updateFailed: "自動更新に失敗しました。以前のバージョンを継続して使用します。",
|
||||
authenticating: "ログイン中…",
|
||||
authenticateWith: "{{method}} でログイン",
|
||||
authAvailableHint: "この Agent は ACP ログインに対応しています。メッセージ送信時にログインを求められた場合は、ここで認証してください。",
|
||||
loginUnavailable: "この ACP 接続コンポーネントは認証が必要ですが、ACP のログイン方法を提供していません。エージェント側で認証してから再確認してください。",
|
||||
autoUpdated: "自動更新",
|
||||
probe: "接続を確認",
|
||||
probing: "確認中…",
|
||||
probeHint: "一時的なセッションを開いてすぐ閉じます。ノートは送信しません。",
|
||||
probeHint: "一時的なセッションを開いてすぐ閉じます。ノートは送信されず、モデルへのログイン状態も確認しません。",
|
||||
notProbed: "未確認",
|
||||
invalidPath: "実行ファイルの絶対パスを入力してください。",
|
||||
states: {
|
||||
|
||||
@@ -1439,11 +1439,12 @@ export const zhCN = {
|
||||
updateFailed: "自动更新失败,正在继续使用原版本。",
|
||||
authenticating: "正在登录…",
|
||||
authenticateWith: "使用 {{method}} 登录",
|
||||
authAvailableHint: "此 Agent 提供 ACP 登录方式。若发消息时提示需要登录,可在这里完成认证。",
|
||||
loginUnavailable: "这个 ACP 连接组件要求认证,但没有提供 ACP 登录入口。请在对应代理中完成认证后重试连接。",
|
||||
autoUpdated: "自动更新",
|
||||
probe: "检查连接",
|
||||
probing: "正在检查…",
|
||||
probeHint: "会临时建立会话并立即关闭,不会发送你的笔记。",
|
||||
probeHint: "会临时建立会话并立即关闭,不会发送你的笔记;此检查不验证模型请求是否已登录。",
|
||||
notProbed: "尚未检查",
|
||||
invalidPath: "请填写可执行文件的绝对路径。",
|
||||
states: {
|
||||
|
||||
Reference in new issue
Block a user