diff --git a/apps/desktop/src/main/acp-adapter-manager.mjs b/apps/desktop/src/main/acp-adapter-manager.mjs index 21addc13..987d0dd3 100644 --- a/apps/desktop/src/main/acp-adapter-manager.mjs +++ b/apps/desktop/src/main/acp-adapter-manager.mjs @@ -12,6 +12,7 @@ import { Unzip, UnzipInflate } from "fflate"; const require = createRequire(import.meta.url); const npmCli = path.join(path.dirname(require.resolve("npm")), "bin", "npm-cli.js"); const REGISTRY_URL = "https://cdn.agentclientprotocol.com/registry/v1/latest/registry.json"; +const PI_PACKAGE_URL = "https://registry.npmjs.org/pi-acp/latest"; const MAX_ARCHIVE_BYTES = 300 * 1024 * 1024; const MAX_EXTRACTED_BYTES = 1024 * 1024 * 1024; const VERSION_PATTERN = /^\d+\.\d+\.\d+$/; @@ -30,17 +31,17 @@ const compareVersions = (left, right) => { return 0; }; -const commandFile = (root, id, version, platform) => id === "codex" - ? path.join(root, id, version, "node_modules", "@agentclientprotocol", "codex-acp", "dist", "index.js") - : path.join(root, id, version, platform === "win32" ? "agy_acp_server.exe" : "agy_acp_server.par"); +const commandFile = (root, id, version, platform) => id === "antigravity" + ? path.join(root, id, version, platform === "win32" ? "agy_acp_server.exe" : "agy_acp_server.par") + : path.join(root, id, version, "node_modules", ...(id === "codex" ? ["@agentclientprotocol", "codex-acp"] : ["pi-acp"]), "dist", "index.js"); const antigravityArgs = (platform) => platform === "linux" ? ["--uid="] : []; export const managedAdapterCommand = (root, id, version, { platform = process.platform, executablePath = process.execPath } = {}) => { - if (!VERSION_PATTERN.test(version) || (id !== "codex" && id !== "antigravity")) return null; + if (!VERSION_PATTERN.test(version) || !["codex", "antigravity", "piAgent"].includes(id)) return null; const file = commandFile(root, id, version, platform); if (!existsSync(file)) return null; - return id === "codex" + return id !== "antigravity" ? { command: executablePath, args: [file], env: { ELECTRON_RUN_AS_NODE: "1" } } : { command: file, args: antigravityArgs(platform) }; }; @@ -57,6 +58,21 @@ const readRegistry = async (fetchImpl) => { return agents; }; +export const selectPiAdapterRelease = (entry) => { + if (entry?.name !== "pi-acp" || !/^0\.0\.\d+$/.test(entry.version ?? "") || entry.bin?.["pi-acp"] !== "dist/index.js") { + throw new Error("unsupported_release"); + } + return { id: "piAgent", version: entry.version, kind: "npm", packageName: `pi-acp@${entry.version}` }; +}; + +const readPiRelease = async (fetchImpl) => { + const response = await fetchImpl(PI_PACKAGE_URL, { signal: AbortSignal.timeout(15_000), redirect: "error" }); + if (!response.ok || Number(response.headers.get("content-length") || 0) > 1024 * 1024) throw new Error("registry_unavailable"); + const raw = await response.text(); + if (raw.length > 1024 * 1024) throw new Error("registry_too_large"); + return selectPiAdapterRelease(JSON.parse(raw)); +}; + export const selectAdapterRelease = (agents, id, { platform = process.platform, arch = process.arch } = {}) => { const entry = agents.find((agent) => agent?.id === (id === "codex" ? "codex-acp" : "antigravity-acp")); if (!entry || !VERSION_PATTERN.test(entry.version)) throw new Error("unsupported_release"); @@ -200,12 +216,14 @@ export function createAcpAdapterManager({ root, fetchImpl = fetch, platform = pr }; const install = (id, validate, { updateOnly = false } = {}) => { - if (id !== "codex" && id !== "antigravity") return Promise.reject(new Error("unknown_adapter")); + if (!["codex", "antigravity", "piAgent"].includes(id)) return Promise.reject(new Error("unknown_adapter")); if (inFlight.has(id)) return inFlight.get(id); const task = (async () => { const previous = get(id); if (updateOnly && !previous) return { updated: false, reason: "not_managed" }; - const release = selectAdapterRelease(await readRegistry(fetchImpl), id, { platform, arch }); + const release = id === "piAgent" + ? await readPiRelease(fetchImpl) + : selectAdapterRelease(await readRegistry(fetchImpl), id, { platform, arch }); if (previous && compareVersions(release.version, previous.version) <= 0) return { updated: false, version: previous.version }; const versionRoot = path.join(root, id); const target = path.join(versionRoot, release.version); @@ -223,7 +241,7 @@ export function createAcpAdapterManager({ root, fetchImpl = fetch, platform = pr } // Inspect the staged command before making it visible to future sessions. const stagedFile = release.kind === "npm" - ? path.join(stage, "node_modules", "@agentclientprotocol", "codex-acp", "dist", "index.js") + ? path.join(stage, "node_modules", ...(id === "codex" ? ["@agentclientprotocol", "codex-acp"] : ["pi-acp"]), "dist", "index.js") : path.join(stage, platform === "win32" ? "agy_acp_server.exe" : "agy_acp_server.par"); if (!existsSync(stagedFile)) throw new Error("adapter_install_failed"); const stagedCommand = release.kind === "npm" @@ -251,7 +269,7 @@ export function createAcpAdapterManager({ root, fetchImpl = fetch, platform = pr }; const prune = async () => { - for (const id of ["codex", "antigravity"]) { + for (const id of ["codex", "antigravity", "piAgent"]) { const current = get(id)?.version; if (!current) continue; const folder = path.join(root, id); @@ -266,5 +284,5 @@ export function createAcpAdapterManager({ root, fetchImpl = fetch, platform = pr } }; - return { get, install, prune, installedIds: () => ["codex", "antigravity"].filter((id) => get(id)) }; + return { get, install, prune, installedIds: () => ["codex", "antigravity", "piAgent"].filter((id) => get(id)) }; } diff --git a/apps/desktop/src/main/acp-adapter-manager.test.mjs b/apps/desktop/src/main/acp-adapter-manager.test.mjs index 73fdb17f..3682c122 100644 --- a/apps/desktop/src/main/acp-adapter-manager.test.mjs +++ b/apps/desktop/src/main/acp-adapter-manager.test.mjs @@ -4,7 +4,7 @@ import { mkdtemp, mkdir, readFile, rm, writeFile } from "node:fs/promises"; import { tmpdir } from "node:os"; import path from "node:path"; import { zipSync } from "fflate"; -import { createAcpAdapterManager, managedAdapterCommand, selectAdapterRelease } from "./acp-adapter-manager.mjs"; +import { createAcpAdapterManager, managedAdapterCommand, selectAdapterRelease, selectPiAdapterRelease } from "./acp-adapter-manager.mjs"; const codexEntry = (version) => ({ id: "codex-acp", @@ -13,6 +13,35 @@ const codexEntry = (version) => ({ }); describe("managed ACP adapters", () => { + test("accepts only the expected pi ACP npm package and retains the old adapter on a failed update", async () => { + const root = await mkdtemp(path.join(tmpdir(), "edgeever-managed-pi-")); + let version = "0.0.34"; + const metadata = () => ({ name: "pi-acp", version, bin: { "pi-acp": "dist/index.js" } }); + expect(selectPiAdapterRelease(metadata()).packageName).toBe("pi-acp@0.0.34"); + expect(() => selectPiAdapterRelease({ ...metadata(), name: "other-package" })).toThrow("unsupported_release"); + expect(() => selectPiAdapterRelease({ ...metadata(), version: "0.1.0" })).toThrow("unsupported_release"); + const manager = createAcpAdapterManager({ + root, + executablePath: "/usr/bin/node", + fetchImpl: async () => new Response(JSON.stringify(metadata()), { status: 200 }), + installNpmImpl: async (args, stage) => { + expect(args).toContain(`pi-acp@${version}`); + const directory = path.join(stage, "node_modules", "pi-acp", "dist"); + await mkdir(directory, { recursive: true }); + await writeFile(path.join(directory, "index.js"), "// fake ACP adapter"); + }, + }); + try { + expect((await manager.install("piAgent", async () => ({ state: "available" }))).updated).toBe(true); + expect(manager.get("piAgent")?.command.args[0].endsWith(path.join("pi-acp", "dist", "index.js"))).toBe(true); + version = "0.0.35"; + await expect(manager.install("piAgent", async () => ({ state: "failed", detail: "handshake_failed" }), { updateOnly: true })).rejects.toThrow("handshake_failed"); + expect(manager.get("piAgent")?.version).toBe("0.0.34"); + expect((await manager.install("piAgent", async () => ({ state: "available" }), { updateOnly: true })).version).toBe("0.0.35"); + } finally { + await rm(root, { recursive: true, force: true }); + } + }); test("accepts only the supported official distribution and platform command", () => { expect(selectAdapterRelease([codexEntry("2.0.1")], "codex").version).toBe("2.0.1"); expect(() => selectAdapterRelease([{ ...codexEntry("2.0.1"), distribution: { npx: { package: "evil@2.0.1" } } }], "codex")).toThrow("unsupported_release"); diff --git a/apps/desktop/src/main/acp-host.mjs b/apps/desktop/src/main/acp-host.mjs index 4c5a5f65..633e565a 100644 --- a/apps/desktop/src/main/acp-host.mjs +++ b/apps/desktop/src/main/acp-host.mjs @@ -13,6 +13,8 @@ const ADAPTERS = { codex: { id: "codex", label: "Codex" }, antigravity: { id: "antigravity", label: "Antigravity" }, grokBuild: { id: "grokBuild", label: "Grok Build" }, + deepseekHarness: { id: "deepseekHarness", label: "DeepSeek Harness" }, + piAgent: { id: "piAgent", label: "pi agent" }, workbuddyCn: { id: "workbuddyCn", label: "WorkBuddy 中国版" }, workbuddyIntl: { id: "workbuddyIntl", label: "WorkBuddy International" }, }; @@ -95,7 +97,9 @@ export function detectInstalledAgentApps({ : [env.LOCALAPPDATA && path.join(env.LOCALAPPDATA, "Programs", "Antigravity", "Antigravity.exe"), env.PROGRAMFILES && path.join(env.PROGRAMFILES, "Antigravity", "Antigravity.exe")]; return []; }; - return ["codex", "antigravity"].filter((id) => { + return ["codex", "antigravity", "piAgent"].filter((id) => { + if (id === "piAgent") return lookup(platform === "win32" ? ["pi.exe"] : ["pi"]) + || (platform !== "win32" && [path.join(home, ".local", "bin", "pi"), ...(platform === "darwin" ? ["/opt/homebrew/bin/pi", "/usr/local/bin/pi"] : [])].some((candidate) => exists(candidate))); const names = platform === "win32" ? [`${id}.exe`, id] : [id]; return lookup(names) || candidates(id).some((candidate) => candidate && exists(candidate)); }); @@ -121,9 +125,52 @@ export function resolveAcpCommand(input, deps = {}) { if (id === "codex") return resolveCodex(input?.path, resolved); if (id === "antigravity") return resolveAntigravity(input?.path, resolved); if (id === "grokBuild") return resolveGrok(input?.path, resolved); + if (id === "deepseekHarness") return resolveLocalAcpCli("dsh", ["--profile", "acp"], resolved); + if (id === "piAgent") { + if (!localExecutable("pi", resolved)) return { ok: false, state: "not_installed" }; + const adapter = resolveLocalAcpCli("pi-acp", [], resolved); + return adapter.ok ? { ...adapter, command: withPiPath(adapter.command, resolved) } : { ...adapter, detail: "adapter_missing" }; + } return resolveWorkBuddy(id, resolved); } +function localExecutable(name, deps) { + const executable = deps.platform === "win32" ? `${name}.exe` : name; + const candidates = [findOnPath([executable], deps)]; + if (deps.platform !== "win32") candidates.push(path.join(deps.home, ".local", "bin", name)); + if (deps.platform === "darwin") candidates.push(`/opt/homebrew/bin/${name}`, `/usr/local/bin/${name}`); + for (const candidate of candidates) { + if (!candidate) continue; + try { + const actual = deps.realpath(candidate); + if (deps.stat(actual).isFile()) { deps.access(actual, fsConstants.X_OK); return actual; } + } catch { /* Try another installation. */ } + } + return null; +} + +function resolveLocalAcpCli(name, args, deps) { + const executable = localExecutable(name, deps); + if (!executable) return { ok: false, state: "not_installed" }; + return { ok: true, command: /\.[cm]?js$/i.test(executable) + ? { command: deps.executablePath, args: [executable, ...args], env: { ELECTRON_RUN_AS_NODE: "1" } } + : { command: executable, args } }; +} + +function withPiPath(command, deps) { + if (deps.platform === "win32" || findOnPath(["pi"], deps)) return command; + const candidates = [path.join(deps.home, ".local", "bin", "pi"), ...(deps.platform === "darwin" ? ["/opt/homebrew/bin/pi", "/usr/local/bin/pi"] : [])]; + for (const candidate of candidates) { + try { + const actual = deps.realpath(candidate); + if (!deps.stat(actual).isFile()) continue; + deps.access(actual, fsConstants.X_OK); + return { ...command, env: { ...command.env, PATH: `${path.dirname(candidate)}:${deps.pathEnv}` } }; + } catch { /* Try another installation. */ } + } + return command; +} + function resolveWorkBuddy(id, deps) { const homeApplications = path.join(deps.home, "Applications"); const macApps = id === "workbuddyCn" ? ["WorkBuddy.app", "WorkBuddy AI.app"] : ["WorkBuddy AI.app", "WorkBuddy.app"]; @@ -254,7 +301,7 @@ const adapterFromResolution = (id, resolution) => { if (!resolution.ok && resolution.state === "failed") { return { ...adapter, state: "failed", detail: resolution.detail || "connection_failed" }; } - if (!resolution.ok) return { ...adapter, state: "not_installed" }; + if (!resolution.ok) return { ...adapter, state: "not_installed", ...(resolution.detail ? { detail: resolution.detail } : {}) }; return adapter; }; @@ -574,8 +621,9 @@ export function createAcpHostRuntime(options = {}) { const resolveCommand = (input) => { if (input?.id === "antigravity" && typeof input.path === "string" && input.path.trim()) return resolveAcpCommand(input, commandDeps); + if (input?.id === "piAgent" && !localExecutable("pi", resolutionDeps(commandDeps))) return { ok: false, state: "not_installed" }; const managed = manager?.get(input?.id); - if (managed) return { ok: true, command: managed.command, version: managed.version, managed: true }; + if (managed) return { ok: true, command: input?.id === "piAgent" ? withPiPath(managed.command, resolutionDeps(commandDeps)) : managed.command, version: managed.version, managed: true }; return resolveAcpCommand(input, commandDeps); }; @@ -636,7 +684,7 @@ export function createAcpHostRuntime(options = {}) { return { listAdapters() { - return ["codex", "antigravity", "grokBuild", "workbuddyCn", "workbuddyIntl"].map((id) => { + return ["codex", "antigravity", "grokBuild", "deepseekHarness", "piAgent", "workbuddyCn", "workbuddyIntl"].map((id) => { if (installingIds.has(id)) return { ...adapterShell(id), state: "installing" }; const resolved = resolveCommand({ id }); return resolved.ok @@ -647,12 +695,13 @@ export function createAcpHostRuntime(options = {}) { async installAdapter(id, { updateOnly = false } = {}) { if (!manager) return { updated: false, reason: "adapter_store_unavailable" }; + if (id === "piAgent" && !localExecutable("pi", resolutionDeps(commandDeps))) return { updated: false, reason: "agent_not_installed" }; installingIds.add(id); try { const result = await manager.install(id, async (command) => { let connected; try { - connected = await withHandshakeTimeout((signal) => connect(command, `install-${id}`, () => {}, signal), 90_000); + connected = await withHandshakeTimeout((signal) => connect(id === "piAgent" ? withPiPath(command, resolutionDeps(commandDeps)) : command, `install-${id}`, () => {}, signal), 90_000); return { ...adapterShell(id), state: "available", promptCapabilities: connected.promptCapabilities }; } catch (error) { return { ...adapterShell(id), ...failureFields(classifyAcpFailure(error)), ...(isAuthRequiredError(error) ? { authMethods: error.authMethods ?? [] } : {}) }; @@ -689,6 +738,7 @@ export function createAcpHostRuntime(options = {}) { if (!manager) return []; const results = []; for (const id of manager.installedIds()) { + if (id === "piAgent" && !localExecutable("pi", resolutionDeps(commandDeps))) continue; try { const result = await this.installAdapter(id, { updateOnly: true }); updateFailures.delete(id); diff --git a/apps/desktop/src/main/acp-host.test.mjs b/apps/desktop/src/main/acp-host.test.mjs index d59cde37..fa055216 100644 --- a/apps/desktop/src/main/acp-host.test.mjs +++ b/apps/desktop/src/main/acp-host.test.mjs @@ -234,6 +234,38 @@ describe("ACP command allow-list", () => { expect(detected).toEqual(["codex", "antigravity"]); }); + test("starts the official DeepSeek ACP profile and uses a separate adapter for pi", async () => { + const directory = await mkdtemp(path.join(tmpdir(), "edgeever-new-agents-")); + try { + const bin = path.join(directory, "bin"); + await mkdir(bin); + const pi = path.join(bin, "pi"); + const piAcp = path.join(bin, "pi-acp"); + const dsh = path.join(bin, "dsh"); + await writeFile(pi, "#!/bin/sh\nexit 0\n", { mode: 0o755 }); + await writeFile(piAcp, "#!/bin/sh\nexit 0\n", { mode: 0o755 }); + await writeFile(dsh, "#!/bin/sh\nexit 0\n", { mode: 0o755 }); + const deps = { platform: "darwin", pathEnv: bin, home: directory, executablePath: "/usr/bin/node" }; + expect(resolveAcpCommand({ id: "deepseekHarness" }, deps).command).toEqual({ command: realpathSync(dsh), args: ["--profile", "acp"] }); + expect(resolveAcpCommand({ id: "piAgent" }, deps).command).toEqual({ command: realpathSync(piAcp), args: [] }); + expect(detectInstalledAgentApps({ platform: "darwin", pathEnv: bin, home: directory })).toContain("piAgent"); + const localBin = path.join(directory, ".local", "bin"); + await mkdir(localBin, { recursive: true }); + await writeFile(path.join(localBin, "pi"), "#!/bin/sh\nexit 0\n", { mode: 0o755 }); + await writeFile(path.join(localBin, "pi-acp"), "#!/bin/sh\nexit 0\n", { mode: 0o755 }); + const fallback = resolveAcpCommand({ id: "piAgent" }, { ...deps, pathEnv: "" }); + expect(fallback.command.env.PATH.startsWith(`${localBin}:`)).toBe(true); + await rm(piAcp); + await rm(path.join(localBin, "pi-acp")); + expect(resolveAcpCommand({ id: "piAgent" }, deps)).toMatchObject({ ok: false, detail: "adapter_missing" }); + await rm(pi); + await rm(path.join(localBin, "pi")); + expect(resolveAcpCommand({ id: "piAgent" }, deps)).toMatchObject({ ok: false, state: "not_installed" }); + } finally { + await rm(directory, { recursive: true, force: true }); + } + }); + test("automatically installs only a detected agent missing its ACP connector", async () => { let installed = false; let installs = 0; diff --git a/apps/web/src/components/ai-sidebar/AiSidebar.tsx b/apps/web/src/components/ai-sidebar/AiSidebar.tsx index 816e2b6c..c85260b3 100644 --- a/apps/web/src/components/ai-sidebar/AiSidebar.tsx +++ b/apps/web/src/components/ai-sidebar/AiSidebar.tsx @@ -347,7 +347,7 @@ function AiSidebarThreadMenu({ const readLocalAdapter = (): { id: DesktopAcpAdapterId; path?: string } | null => { const id = readStorage(AI_SIDEBAR_ADAPTER_KEY); - if (id !== "codex" && id !== "antigravity" && id !== "grokBuild" && id !== "workbuddyCn" && id !== "workbuddyIntl") return null; + if (id !== "codex" && id !== "antigravity" && id !== "grokBuild" && id !== "deepseekHarness" && id !== "piAgent" && id !== "workbuddyCn" && id !== "workbuddyIntl") return null; const path = readStorage(AI_SIDEBAR_ADAPTER_PATH_KEY)?.trim(); return id === "antigravity" && path ? { id, path } : { id }; }; diff --git a/apps/web/src/components/settings/DesktopAcpAgentCard.tsx b/apps/web/src/components/settings/DesktopAcpAgentCard.tsx index 41babd34..4e84b3e9 100644 --- a/apps/web/src/components/settings/DesktopAcpAgentCard.tsx +++ b/apps/web/src/components/settings/DesktopAcpAgentCard.tsx @@ -27,7 +27,7 @@ import { SETTINGS_ITEM_TITLE_CLASSNAME, } from "./settings-ui"; -const adapterIds: DesktopAcpAdapterId[] = ["codex", "antigravity", "grokBuild", "workbuddyCn", "workbuddyIntl"]; +const adapterIds: DesktopAcpAdapterId[] = ["codex", "antigravity", "grokBuild", "deepseekHarness", "piAgent", "workbuddyCn", "workbuddyIntl"]; const agentCatalog = [ { id: "codex", connectable: true }, @@ -36,6 +36,8 @@ const agentCatalog = [ { id: "workbuddyCn", connectable: true }, { id: "workbuddyIntl", connectable: true }, { id: "grokBuild", connectable: true }, + { id: "deepseekHarness", connectable: true }, + { id: "piAgent", connectable: true }, ] as const; const readSource = (): AiSidebarSource => ( @@ -53,6 +55,8 @@ const statusKey = (adapter: DesktopAcpAdapter | undefined, probing: boolean) => if (adapter.detail === "invalid_path") return "aiAssistant.agentSource.invalidPath"; if (adapter.detail === "desktop_unavailable") return "aiAssistant.agentSource.localDisabled"; if (adapter.id === "grokBuild" && adapter.state === "not_installed") return "aiAssistant.agentSource.grokBuildNotFound"; + if (adapter.id === "deepseekHarness" && adapter.state === "not_installed") return "aiAssistant.agentSource.deepseekHarnessNotFound"; + if (adapter.id === "piAgent" && adapter.state === "not_installed") return adapter.detail === "adapter_missing" ? "aiAssistant.agentSource.piAgentAdapterMissing" : "aiAssistant.agentSource.piAgentNotFound"; if ((adapter.id === "workbuddyCn" || adapter.id === "workbuddyIntl") && adapter.state === "not_installed") return "aiAssistant.agentSource.workbuddyNotFound"; return `aiAssistant.agentSource.states.${adapter.state}`; }; @@ -118,7 +122,7 @@ const DesktopAcpAgentCardBody = ({ bridge }: { bridge: boolean }) => { }; const install = async () => { - if (adapterId !== "codex" && adapterId !== "antigravity") return; + if (adapterId !== "codex" && adapterId !== "antigravity" && adapterId !== "piAgent") return; setInstalling(true); setInstallError(false); try { @@ -290,7 +294,7 @@ const DesktopAcpAgentCardBody = ({ bridge }: { bridge: boolean }) => { {shown?.managed && shown.version ? ` · v${shown.version} · ${t("aiAssistant.agentSource.autoUpdated")}` : ""}
{t(adapterId === "grokBuild" ? "aiAssistant.agentSource.grokBuildMissingHint" : adapterId === "workbuddyCn" || adapterId === "workbuddyIntl" ? "aiAssistant.agentSource.workbuddyMissingHint" : "aiAssistant.agentSource.installHint")}
: null} + {shown?.state === "not_installed" ?{t(adapterId === "grokBuild" ? "aiAssistant.agentSource.grokBuildMissingHint" : adapterId === "deepseekHarness" ? "aiAssistant.agentSource.deepseekHarnessMissingHint" : adapterId === "piAgent" ? shown.detail === "adapter_missing" ? "aiAssistant.agentSource.piAgentAdapterMissingHint" : "aiAssistant.agentSource.piAgentMissingHint" : adapterId === "workbuddyCn" || adapterId === "workbuddyIntl" ? "aiAssistant.agentSource.workbuddyMissingHint" : "aiAssistant.agentSource.installHint")}
: null} + {shown?.state === "needs_login" && adapterId === "piAgent" ?{t("aiAssistant.agentSource.piAgentLoginHint")}
: null} {shown?.state === "needs_login" && (adapterId === "workbuddyCn" || adapterId === "workbuddyIntl") ? ({t(adapterId === "workbuddyCn" ? "aiAssistant.agentSource.workbuddyCnLoginHint" : "aiAssistant.agentSource.workbuddyIntlLoginHint")}
) : null} @@ -320,7 +325,7 @@ const DesktopAcpAgentCardBody = ({ bridge }: { bridge: boolean }) => { ))} ) : null} - {shown?.state === "needs_login" && !authMethods.length ? ( + {shown?.state === "needs_login" && !authMethods.length && adapterId !== "piAgent" ? ({t("aiAssistant.agentSource.loginUnavailable")}
) : null} {shown?.updateError ?{t("aiAssistant.agentSource.updateFailed")}
: null} diff --git a/apps/web/src/lib/desktop-acp.ts b/apps/web/src/lib/desktop-acp.ts index 077540cc..473905ca 100644 --- a/apps/web/src/lib/desktop-acp.ts +++ b/apps/web/src/lib/desktop-acp.ts @@ -1,4 +1,4 @@ -export type DesktopAcpAdapterId = "codex" | "antigravity" | "grokBuild" | "workbuddyCn" | "workbuddyIntl"; +export type DesktopAcpAdapterId = "codex" | "antigravity" | "grokBuild" | "deepseekHarness" | "piAgent" | "workbuddyCn" | "workbuddyIntl"; export type DesktopAcpAdapterState = "not_installed" | "installing" | "needs_login" | "available" | "failed"; export type DesktopAcpPromptCapabilities = { @@ -71,7 +71,7 @@ export const probeDesktopAcpAdapter = async (input: { id: DesktopAcpAdapterId; p return desktop.probeAcpAdapter(input); }; -export const installDesktopAcpAdapter = async (id: Extract