feat(desktop): support DeepSeek Harness and pi agent ACP adapters and polish agent mode copy
This commit is contained in:
1 parent
4ed13eb0ad
commit
cb16fa21a5
11 files changed
+215
-54
No files matched your search
@@ -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");
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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}
|
||||
|
||||
@@ -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);
|
||||
|
||||
Vendored
+8
-8
@@ -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;
|
||||
|
||||
@@ -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.",
|
||||
|
||||
@@ -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 または組織指定の方法を使用してください。",
|
||||
|
||||
@@ -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 的登录分开。国内版请使用微信或组织指定的登录方式。",
|
||||
|
||||
Reference in new issue
Block a user