feat(desktop): support DeepSeek Harness and pi agent ACP adapters and polish agent mode copy

This commit is contained in:
tianma-if committed 2026-09-30 12:29:54 +08:00
1 parent 4ed13eb0ad
commit cb16fa21a5
11 files changed
+215 -54

No files matched your search

+28 -10
View File
@@ -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)) };
}
@@ -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");
+55 -5
View File
@@ -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);
+32
View File
@@ -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;
@@ -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 };
};
@@ -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")}` : ""}
</p>
<div className="flex shrink-0 gap-2">
{shown?.state === "not_installed" && (adapterId === "codex" || adapterId === "antigravity") ? (
{shown?.state === "not_installed" && (adapterId === "codex" || adapterId === "antigravity" || (adapterId === "piAgent" && shown?.detail === "adapter_missing")) ? (
<Button type="button" variant="outline" size="sm" className="h-8 bg-card text-xs font-normal" disabled={installing || probing} onClick={() => void install()}>
{installing ? t("aiAssistant.agentSource.installing") : t("aiAssistant.agentSource.install")}
</Button>
@@ -307,7 +311,8 @@ const DesktopAcpAgentCardBody = ({ bridge }: { bridge: boolean }) => {
</TooltipProvider>
</div>
</div>
{shown?.state === "not_installed" ? <p className="text-xs text-slate-500">{t(adapterId === "grokBuild" ? "aiAssistant.agentSource.grokBuildMissingHint" : adapterId === "workbuddyCn" || adapterId === "workbuddyIntl" ? "aiAssistant.agentSource.workbuddyMissingHint" : "aiAssistant.agentSource.installHint")}</p> : null}
{shown?.state === "not_installed" ? <p className="text-xs text-slate-500">{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")}</p> : null}
{shown?.state === "needs_login" && adapterId === "piAgent" ? <p className="text-xs text-slate-600">{t("aiAssistant.agentSource.piAgentLoginHint")}</p> : null}
{shown?.state === "needs_login" && (adapterId === "workbuddyCn" || adapterId === "workbuddyIntl") ? (
<p className="text-xs text-slate-600">{t(adapterId === "workbuddyCn" ? "aiAssistant.agentSource.workbuddyCnLoginHint" : "aiAssistant.agentSource.workbuddyIntlLoginHint")}</p>
) : null}
@@ -320,7 +325,7 @@ const DesktopAcpAgentCardBody = ({ bridge }: { bridge: boolean }) => {
))}
</div>
) : null}
{shown?.state === "needs_login" && !authMethods.length ? (
{shown?.state === "needs_login" && !authMethods.length && adapterId !== "piAgent" ? (
<p className="text-xs text-amber-700" role="status">{t("aiAssistant.agentSource.loginUnavailable")}</p>
) : null}
{shown?.updateError ? <p className="text-xs text-amber-700" role="status">{t("aiAssistant.agentSource.updateFailed")}</p> : null}
+2 -2
View File
@@ -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<DesktopAcpAdapterId, "codex" | "antigravity">) => {
export const installDesktopAcpAdapter = async (id: Extract<DesktopAcpAdapterId, "codex" | "antigravity" | "piAgent">) => {
const desktop = bridge();
if (!desktop?.installAcpAdapter) throw new Error("desktop_acp_unavailable");
return desktop.installAcpAdapter(id);
+8 -8
View File
@@ -92,7 +92,7 @@ interface EdgeEverDesktopBridge {
finishWeChatImport?(importId: string, success: boolean): Promise<void>;
retryWeChatImport?(importId: string): Promise<boolean>;
listAcpAdapters?(): Promise<Array<{
id: "codex" | "antigravity" | "grokBuild" | "workbuddyCn" | "workbuddyIntl";
id: "codex" | "antigravity" | "grokBuild" | "deepseekHarness" | "piAgent" | "workbuddyCn" | "workbuddyIntl";
label: string;
state: "not_installed" | "installing" | "needs_login" | "available" | "failed";
detail?: string;
@@ -102,8 +102,8 @@ interface EdgeEverDesktopBridge {
updateError?: string;
authMethods?: Array<{ id: string; name: string }>;
}>>;
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;
+16 -7
View File
@@ -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.",
+16 -7
View File
@@ -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 または組織指定の方法を使用してください。",
+17 -8
View File
@@ -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 的登录分开。国内版请使用微信或组织指定的登录方式。",