From cb16fa21a503d1c2d7da036c13f67f910ab2f045 Mon Sep 17 00:00:00 2001 From: tianma-if Date: Wed, 30 Sep 2026 12:29:54 +0800 Subject: [PATCH] feat(desktop): support DeepSeek Harness and pi agent ACP adapters and polish agent mode copy --- apps/desktop/src/main/acp-adapter-manager.mjs | 38 ++++++++---- .../src/main/acp-adapter-manager.test.mjs | 31 +++++++++- apps/desktop/src/main/acp-host.mjs | 60 +++++++++++++++++-- apps/desktop/src/main/acp-host.test.mjs | 32 ++++++++++ .../src/components/ai-sidebar/AiSidebar.tsx | 2 +- .../settings/DesktopAcpAgentCard.tsx | 15 +++-- apps/web/src/lib/desktop-acp.ts | 4 +- apps/web/src/vite-env.d.ts | 16 ++--- packages/shared/src/i18n/en-US.ts | 23 ++++--- packages/shared/src/i18n/ja.ts | 23 ++++--- packages/shared/src/i18n/zh-CN.ts | 25 +++++--- 11 files changed, 215 insertions(+), 54 deletions(-) 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")}` : ""}

- {shown?.state === "not_installed" && (adapterId === "codex" || adapterId === "antigravity") ? ( + {shown?.state === "not_installed" && (adapterId === "codex" || adapterId === "antigravity" || (adapterId === "piAgent" && shown?.detail === "adapter_missing")) ? ( @@ -307,7 +311,8 @@ const DesktopAcpAgentCardBody = ({ bridge }: { bridge: boolean }) => {
- {shown?.state === "not_installed" ?

{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) => { +export const installDesktopAcpAdapter = async (id: Extract) => { const desktop = bridge(); if (!desktop?.installAcpAdapter) throw new Error("desktop_acp_unavailable"); return desktop.installAcpAdapter(id); diff --git a/apps/web/src/vite-env.d.ts b/apps/web/src/vite-env.d.ts index bd1b62f0..1374debe 100644 --- a/apps/web/src/vite-env.d.ts +++ b/apps/web/src/vite-env.d.ts @@ -92,7 +92,7 @@ interface EdgeEverDesktopBridge { finishWeChatImport?(importId: string, success: boolean): Promise; retryWeChatImport?(importId: string): Promise; listAcpAdapters?(): Promise; }>>; - probeAcpAdapter?(input: { id: "codex" | "antigravity" | "grokBuild" | "workbuddyCn" | "workbuddyIntl"; path?: string }): Promise<{ - id: "codex" | "antigravity" | "grokBuild" | "workbuddyCn" | "workbuddyIntl"; + probeAcpAdapter?(input: { id: "codex" | "antigravity" | "grokBuild" | "deepseekHarness" | "piAgent" | "workbuddyCn" | "workbuddyIntl"; path?: string }): Promise<{ + id: "codex" | "antigravity" | "grokBuild" | "deepseekHarness" | "piAgent" | "workbuddyCn" | "workbuddyIntl"; label: string; state: "not_installed" | "needs_login" | "available" | "failed"; detail?: string; @@ -112,23 +112,23 @@ interface EdgeEverDesktopBridge { managed?: boolean; authMethods?: Array<{ id: string; name: string }>; }>; - installAcpAdapter?(id: "codex" | "antigravity"): Promise<{ updated: boolean; version?: string; adapter?: { - id: "codex" | "antigravity"; + installAcpAdapter?(id: "codex" | "antigravity" | "piAgent"): Promise<{ updated: boolean; version?: string; adapter?: { + id: "codex" | "antigravity" | "piAgent"; label: string; state: "not_installed" | "needs_login" | "available" | "failed"; detail?: string; promptCapabilities?: { image?: boolean; embeddedContext?: boolean }; authMethods?: Array<{ id: string; name: string }>; } }>; - authenticateAcpAdapter?(input: { id: "codex" | "antigravity" | "grokBuild" | "workbuddyCn" | "workbuddyIntl"; path?: string; methodId: string }): Promise<{ - id: "codex" | "antigravity" | "grokBuild" | "workbuddyCn" | "workbuddyIntl"; + authenticateAcpAdapter?(input: { id: "codex" | "antigravity" | "grokBuild" | "deepseekHarness" | "piAgent" | "workbuddyCn" | "workbuddyIntl"; path?: string; methodId: string }): Promise<{ + id: "codex" | "antigravity" | "grokBuild" | "deepseekHarness" | "piAgent" | "workbuddyCn" | "workbuddyIntl"; label: string; state: "not_installed" | "needs_login" | "available" | "failed"; detail?: string; authMethods?: Array<{ id: string; name: string }>; }>; promptAcp?(input: { - adapterId: "codex" | "antigravity" | "grokBuild" | "workbuddyCn" | "workbuddyIntl"; + adapterId: "codex" | "antigravity" | "grokBuild" | "deepseekHarness" | "piAgent" | "workbuddyCn" | "workbuddyIntl"; path?: string; prompt: string; contextText?: string; diff --git a/packages/shared/src/i18n/en-US.ts b/packages/shared/src/i18n/en-US.ts index 7370b91c..511a26df 100644 --- a/packages/shared/src/i18n/en-US.ts +++ b/packages/shared/src/i18n/en-US.ts @@ -1369,13 +1369,13 @@ export const enUS = { toolProgress: "{{name}} · {{status}}", }, agentSource: { - title: "Agent source", - description: "Use the built-in companion, or a local ACP agent on this computer.", - localDisabled: "Needs the desktop app. A browser cannot use a local agent.", - builtin: "Built-in companion", - builtinHint: "Uses the AI model configured below.", - local: "Local ACP agent", - localHint: "Codex, Antigravity, Grok Build, or WorkBuddy on this computer. EdgeEver does not provide your notes folder or a terminal through ACP; the agent's own tool permissions follow its configuration.", + title: "Agent mode", + description: "Use the built-in Agent, or connect to a local Agent (ACP) running on this machine.", + localDisabled: "Requires the desktop app.", + builtin: "Built-in Agent", + builtinHint: "Uses the model services and tools configured below.", + local: "Local Agent (ACP)", + localHint: "Connects to Codex, Antigravity, Grok Build, or WorkBuddy on this machine. EdgeEver does not expose files or terminal permissions via ACP; tool permissions follow the agent's own configuration.", adapter: "Agent", codex: "Codex", antigravity: "Antigravity", @@ -1384,6 +1384,8 @@ export const enUS = { workbuddyCn: "WorkBuddy China", workbuddyIntl: "WorkBuddy International", grokBuild: "Grok Build", + deepseekHarness: "DeepSeek Harness", + piAgent: "pi agent", pathLabel: "Antigravity executable", pathPlaceholder: "/absolute/path/to/antigravity-acp", pathHint: "Enter the absolute path of an existing Antigravity ACP executable, or let EdgeEver install one on demand.", @@ -1391,6 +1393,13 @@ export const enUS = { installHint: "When a local agent is detected, its connector downloads from the official source. This may exceed 100 MiB; updates are checked automatically.", grokBuildMissingHint: "Grok Build includes ACP. Install the Grok CLI first; EdgeEver uses the local grok command without a separate connector download.", grokBuildNotFound: "Grok CLI not found", + deepseekHarnessNotFound: "DeepSeek Harness CLI not found", + deepseekHarnessMissingHint: "Install the DeepSeek Harness CLI (dsh). EdgeEver uses the official dsh --profile acp entry point; the desktop app alone does not provide this CLI.", + piAgentNotFound: "pi agent not found", + piAgentAdapterMissing: "pi ACP connector not found", + piAgentMissingHint: "Install pi agent first. Once detected, EdgeEver installs and updates the pi-acp connector on demand.", + piAgentAdapterMissingHint: "pi agent is installed. EdgeEver installs pi-acp automatically, or you can select Install connector now.", + piAgentLoginHint: "Run pi in a terminal to sign in to your model provider or configure an API key, then check the connection again.", workbuddyNotFound: "WorkBuddy ACP program not found", workbuddyMissingHint: "Install the WorkBuddy desktop app or CodeBuddy CLI. EdgeEver uses its built-in ACP; China and International accounts sign in separately.", workbuddyCnLoginHint: "ACP sign-in is separate from the WorkBuddy desktop app. Use WeChat or your organization's sign-in method for China.", diff --git a/packages/shared/src/i18n/ja.ts b/packages/shared/src/i18n/ja.ts index b14b450a..e3bdf50f 100644 --- a/packages/shared/src/i18n/ja.ts +++ b/packages/shared/src/i18n/ja.ts @@ -1369,13 +1369,13 @@ export const ja = { toolProgress: "{{name}} · {{status}}", }, agentSource: { - title: "エージェントの接続先", - description: "内蔵コンパニオンか、このコンピュータ上のローカル ACP エージェントを使います。", - localDisabled: "デスクトップアプリが必要です。ブラウザではローカルエージェントを使えません。", - builtin: "内蔵コンパニオン", - builtinHint: "下で設定した AI モデルを使います。", - local: "ローカル ACP エージェント", - localHint: "このコンピュータ上の Codex、Antigravity、Grok Build、または WorkBuddy です。EdgeEver は ACP 経由でノートのフォルダやターミナルを提供しません。エージェント自身のツール権限はその設定に従います。", + title: "エージェントの動作モード", + description: "内蔵 Agent を使用するか、このマシンで動作するローカル Agent (ACP) に接続します。", + localDisabled: "デスクトップアプリが必要です。", + builtin: "内蔵 Agent", + builtinHint: "下で設定したモデルサービスとツールを使用します。", + local: "ローカル Agent (ACP)", + localHint: "このマシン上の Codex、Antigravity、Grok Build、または WorkBuddy に接続します。EdgeEver は ACP 経由でファイルやターミナル権限を公開しません。ツール権限はエージェント自身の設定に従います。", adapter: "エージェント", codex: "Codex", antigravity: "Antigravity", @@ -1384,6 +1384,8 @@ export const ja = { workbuddyCn: "WorkBuddy 中国版", workbuddyIntl: "WorkBuddy 国際版", grokBuild: "Grok Build", + deepseekHarness: "DeepSeek Harness", + piAgent: "pi agent", pathLabel: "Antigravity の実行ファイル", pathPlaceholder: "/absolute/path/to/antigravity-acp", pathHint: "既存の Antigravity ACP 実行ファイルの絶対パスを入力するか、EdgeEver で必要時にインストールします。", @@ -1391,6 +1393,13 @@ export const ja = { installHint: "ローカルのエージェントを検出すると、公式配布元からダウンロードします。100 MiB を超える場合があり、インストール後は更新を自動確認します。", grokBuildMissingHint: "Grok Build には ACP が内蔵されています。先に Grok CLI をインストールしてください。EdgeEver はローカルの grok コマンドを使用し、別の接続コンポーネントはダウンロードしません。", grokBuildNotFound: "Grok CLI が見つかりません", + deepseekHarnessNotFound: "DeepSeek Harness CLI が見つかりません", + deepseekHarnessMissingHint: "DeepSeek Harness CLI(dsh)をインストールしてください。EdgeEver は公式の dsh --profile acp を使用します。デスクトップアプリだけではこの CLI は利用できません。", + piAgentNotFound: "pi agent が見つかりません", + piAgentAdapterMissing: "pi ACP 接続コンポーネントが見つかりません", + piAgentMissingHint: "先に pi agent をインストールしてください。検出後、EdgeEver が pi-acp 接続コンポーネントを必要時にインストールし、自動更新します。", + piAgentAdapterMissingHint: "pi agent が見つかりました。EdgeEver が pi-acp を自動インストールします。「接続コンポーネントをインストール」から今すぐ実行することもできます。", + piAgentLoginHint: "ターミナルで pi を実行し、モデルサービスへのログインまたは API キーの設定を済ませてから、接続を再確認してください。", workbuddyNotFound: "WorkBuddy の ACP プログラムが見つかりません", workbuddyMissingHint: "WorkBuddy デスクトップアプリまたは CodeBuddy CLI をインストールしてください。内蔵 ACP を使用し、中国版と国際版は別々にログインします。", workbuddyCnLoginHint: "ACP ログインは WorkBuddy デスクトップアプリとは別です。中国版は WeChat または組織指定の方法を使用してください。", diff --git a/packages/shared/src/i18n/zh-CN.ts b/packages/shared/src/i18n/zh-CN.ts index 1a68ab5c..1da5eee6 100644 --- a/packages/shared/src/i18n/zh-CN.ts +++ b/packages/shared/src/i18n/zh-CN.ts @@ -1369,14 +1369,14 @@ export const zhCN = { toolProgress: "{{name}} · {{status}}", }, agentSource: { - title: "代理来源", - description: "使用内置助手,或使用这台电脑上的本地 ACP 代理。", - localDisabled: "需要桌面客户端。网页里不能使用本机代理。", - builtin: "内置助手", - builtinHint: "使用下方配置的 AI 模型。", - local: "本地 ACP 代理", - localHint: "使用这台电脑上的 Codex、Antigravity、Grok Build 或 WorkBuddy。EdgeEver 不通过 ACP 提供笔记目录或终端;代理自身的工具权限由其配置决定。", - adapter: "代理", + title: "Agent 运行模式", + description: "使用应用内置 Agent,或连接本机运行的本地 Agent (ACP)。", + localDisabled: "仅支持桌面客户端。", + builtin: "内置 Agent", + builtinHint: "直接调用下方配置的模型服务与工具。", + local: "本地 Agent (ACP)", + localHint: "连接本机安装的 Codex、Antigravity、Grok Build 或 WorkBuddy 等 Agent。EdgeEver 不通过 ACP 暴露文件系统或终端权限,具体工具权限由 Agent 自身配置决定。", + adapter: "Agent", codex: "Codex", antigravity: "Antigravity", claudeCode: "Claude Code", @@ -1384,6 +1384,8 @@ export const zhCN = { workbuddyCn: "WorkBuddy 国内版", workbuddyIntl: "WorkBuddy 国际版", grokBuild: "Grok Build", + deepseekHarness: "DeepSeek Harness", + piAgent: "pi agent", pathLabel: "Antigravity 可执行文件", pathPlaceholder: "/absolute/path/to/antigravity-acp", pathHint: "如需使用已有的 Antigravity ACP 程序,可填写其绝对路径;也可以让 EdgeEver 按需安装。", @@ -1391,6 +1393,13 @@ export const zhCN = { installHint: "检测到本机 Agent 后会从官方来源下载,可能超过 100 MiB;安装后会自动检查更新。", grokBuildMissingHint: "Grok Build 已内置 ACP。请先安装 Grok CLI;EdgeEver 会使用本机的 grok 命令,无需下载单独适配器。", grokBuildNotFound: "未找到 Grok CLI", + deepseekHarnessNotFound: "未找到 DeepSeek Harness CLI", + deepseekHarnessMissingHint: "请安装 DeepSeek Harness CLI(dsh)。EdgeEver 使用官方的 dsh --profile acp 入口;仅安装桌面版不等于已安装此 CLI。", + piAgentNotFound: "未找到 pi agent", + piAgentAdapterMissing: "未找到 pi ACP 连接组件", + piAgentMissingHint: "请先安装 pi agent。检测到 pi 后,EdgeEver 会按需安装并自动更新 pi-acp 连接组件。", + piAgentAdapterMissingHint: "已找到 pi agent。EdgeEver 会自动安装 pi-acp;也可以点击“安装连接组件”立即安装。", + piAgentLoginHint: "请先在终端运行 pi 并完成模型服务登录或 API Key 配置,然后重新检查连接。", workbuddyNotFound: "未找到 WorkBuddy 的 ACP 程序", workbuddyMissingHint: "请安装 WorkBuddy 桌面版或 CodeBuddy CLI。EdgeEver 使用其内置 ACP,国内版和国际版分别登录。", workbuddyCnLoginHint: "ACP 登录与 WorkBuddy 桌面 App 的登录分开。国内版请使用微信或组织指定的登录方式。",