Add a bundled Claude Code ACP runtime with model discovery and CLI distribution integration. - Add the Claude ACP runtime plugin, bridge, tool forwarding, and tests. - Register Claude model discovery and cached picker support in the dashboard. - Stage the plugin in CLI and desktop packaging with its pinned dependency. - Add workspace, lockfile, and release metadata. Files changed: .changeset/fn-8224-claude-acp-runtime.md | 7 + packages/cli/package.json | 1 + packages/cli/src/__tests__/bundle-output.test.ts | 32 +- .../cli/src/plugins/staged-bundled-plugin-ids.ts | 1 + packages/cli/tsup.config.ts | 40 +- .../core/src/plugins/bundled-plugin-install.ts | 2 + packages/dashboard/package.json | 3 +- packages/dashboard/src/claude-model-cache.ts | 185 +++++++ packages/dashboard/src/routes.ts | 1 + .../dashboard/src/routes/register-model-routes.ts | 11 + packages/dashboard/src/runtime-provider-probes.ts | 15 + packages/dashboard/vitest.config.ts | 4 + packages/desktop/scripts/workspace-tools.ts | 1 + plugins/fusion-plugin-claude-runtime/README.md | 7 + plugins/fusion-plugin-claude-runtime/manifest.json | 1 + plugins/fusion-plugin-claude-runtime/package.json | 1 + .../src/__tests__/cli-spawn.test.ts | 3 + .../src/__tests__/index.test.ts | 3 + .../src/__tests__/provider.test.ts | 8 + .../src/__tests__/runtime-adapter.test.ts | 7 + .../src/__tests__/tool-bridge.test.ts | 79 +++ .../src/acp-settings.ts | 21 + .../src/acp/VENDORED.md | 30 ++ .../src/acp/cli-spawn.ts | 188 +++++++ .../src/acp/control-handler.ts | 302 ++++++++++++ .../src/acp/event-bridge.ts | 308 ++++++++++++ .../src/acp/fs-capabilities.ts | 263 ++++++++++ .../fusion-plugin-claude-runtime/src/acp/index.ts | 16 + .../src/acp/path-jail.ts | 229 +++++++++ .../src/acp/process-manager.ts | 177 +++++++ .../src/acp/prompt-builder.ts | 87 ++++ .../src/acp/provider.ts | 542 +++++++++++++++++++++ .../src/acp/runtime-adapter.ts | 190 ++++++++ .../src/acp/sanitize.ts | 81 +++ .../src/acp/tool-mapping.ts | 47 ++ .../fusion-plugin-claude-runtime/src/acp/types.ts | 189 +++++++ .../fusion-plugin-claude-runtime/src/cli-spawn.ts | 31 ++ plugins/fusion-plugin-claude-runtime/src/index.ts | 95 ++++ .../src/mcp-forwarding.ts | 114 +++++ .../src/mcp-schema-server.cjs | 155 ++++++ plugins/fusion-plugin-claude-runtime/src/probe.ts | 8 + .../fusion-plugin-claude-runtime/src/provider.ts | 8 + .../src/runtime-adapter.ts | 424 ++++++++++++++++ .../src/skill-loader.ts | 290 +++++++++++ .../src/tool-bridge.ts | 264 ++++++++++ plugins/fusion-plugin-claude-runtime/src/types.ts | 142 ++++++ plugins/fusion-plugin-claude-runtime/tsconfig.json | 10 + .../fusion-plugin-claude-runtime/vitest.config.ts | 22 + pnpm-lock.yaml | 131 ++++- pnpm-workspace.yaml | 1 + 50 files changed, 4753 insertions(+), 24 deletions(-) Fusion-Task-Id: FN-8224 Fusion-Task-Lineage: 4e626913-32bb-4baa-a142-816c7f4ee7c4 Co-authored-by: Fusion (runfusion.ai) <noreply@runfusion.ai>
32 lines
2.6 KiB
TypeScript
32 lines
2.6 KiB
TypeScript
import { spawn } from "node:child_process";
|
|
import { existsSync } from "node:fs";
|
|
import { basename, dirname, isAbsolute, join, resolve } from "node:path";
|
|
import { fileURLToPath } from "node:url";
|
|
|
|
export const CLAUDE_CODE_CLI_ACP_BINARY = "claude-code-cli-acp";
|
|
export interface ClaudeBridgeResolution { kind: "resolved" | "not_resolved"; requested: string; path?: string; reason?: string }
|
|
/*
|
|
FNXC:ClaudeAcp 2026-07-18-11:55:
|
|
The plugin runs both from source (`src/`), a standalone build (`dist/`), and
|
|
Fusion's single-file `bundled.js`. Only the first two sit one directory below the
|
|
plugin root; bundled.js sits at the root. Resolve the staged bridge relative to
|
|
that layout so published CLI sessions do not look in `dist/plugins/bridge`.
|
|
*/
|
|
function pluginRootDir(): string {
|
|
const moduleDir = dirname(fileURLToPath(import.meta.url));
|
|
return ["src", "dist"].includes(basename(moduleDir)) ? resolve(moduleDir, "..") : moduleDir;
|
|
}
|
|
/** Resolve only the identity-pinned bridge staged beside this bundled plugin. */
|
|
export function bundledClaudeBridgeBinPath(pluginRoot = pluginRootDir()): string {
|
|
return join(pluginRoot, "bridge", `${CLAUDE_CODE_CLI_ACP_BINARY}${process.platform === "win32" ? ".cmd" : ""}`);
|
|
}
|
|
export function resolveBundledClaudeBridgeBinary(options: { pluginRoot?: string; exists?: (path: string) => boolean } = {}): ClaudeBridgeResolution {
|
|
const candidate = bundledClaudeBridgeBinPath(options.pluginRoot ?? pluginRootDir());
|
|
const exists = options.exists ?? existsSync;
|
|
if (!exists(candidate) || !isAbsolute(candidate)) return { kind: "not_resolved", requested: CLAUDE_CODE_CLI_ACP_BINARY, path: candidate, reason: `Staged ${CLAUDE_CODE_CLI_ACP_BINARY} bridge was not found at ${candidate}` };
|
|
return { kind: "resolved", requested: CLAUDE_CODE_CLI_ACP_BINARY, path: candidate };
|
|
}
|
|
export async function runClaudeCommand(binary: string, args: string[], timeoutMs: number): Promise<{ code: number | null; stdout: string; stderr: string }> {
|
|
return new Promise((done) => { let stdout="", stderr="", settled=false; const finish=(r:{code:number|null;stdout:string;stderr:string})=>{if(!settled){settled=true;clearTimeout(timer);done(r)}}; const child=spawn(binary,args,{stdio:["ignore","pipe","pipe"],shell:process.platform==="win32"}); const timer=setTimeout(()=>{try{child.kill("SIGKILL")}catch{ /* process already exited */ } finish({code:124,stdout,stderr})},timeoutMs); child.stdout?.on("data",c=>stdout+=String(c)); child.stderr?.on("data",c=>stderr+=String(c)); child.once("error",e=>finish({code:127,stdout,stderr:`${stderr}${e.message}`})); child.once("close",code=>finish({code,stdout,stderr})); });
|
|
}
|