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 的登录分开。国内版请使用微信或组织指定的登录方式。",