Files
fusion/plugins/fusion-plugin-acp-runtime/src/__tests__/setup.test.ts
gsxdsm a9815fb1ff FN-6457: add ACP ask runner and bundled Claude bridge setup
Route ACP-backed planning and validation through a read-only ask-once runner with a pinned Claude bridge foundation.

- Add askAcpOnce for single-turn ACP sessions with timeout handling, JSON recovery, clean stop validation, and disposal.
- Refactor validation seams to use ACP runtime prompts and require structured pass verdicts.
- Resolve the Claude ACP bridge from the plugin bundle and add setup checks for identity, environment, probing, and auth readiness.
- Document the ACP Route B plan and update tests for validator, session, runtime, and plugin setup behavior.

Files changed:
 CONCEPTS.md                                        |   6 +
 docs/acp-contract.md                               |  36 ++
 .../2026-06-14-001-feat-claude-acp-runtime-plan.md | 465 +++++++++++++++++++++
 .../engine/src/__tests__/cli-agent-ask.test.ts     | 104 +++++
 .../src/__tests__/cli-agent-validator.test.ts      | 137 +++---
 .../src/__tests__/interactive-ai-session.test.ts   |  96 +++--
 packages/engine/src/agent-runtime.ts               |   6 +-
 packages/engine/src/cli-agent-ask.ts               | 120 ++++++
 packages/engine/src/cli-agent-validator.ts         |  65 ++-
 .../cli-agent/__tests__/one-shot-session.test.ts   |  16 +-
 packages/engine/src/cli-agent/one-shot-session.ts  |  17 +-
 packages/engine/src/index.ts                       |   8 +-
 packages/engine/src/interactive-ai-session.ts      |  33 +-
 plugins/fusion-plugin-acp-runtime/AGENTS.md        |  14 +
 plugins/fusion-plugin-acp-runtime/CHANGELOG.md     |   6 +
 plugins/fusion-plugin-acp-runtime/README.md        |  13 +-
 plugins/fusion-plugin-acp-runtime/package.json     |   3 +-
 .../src/__tests__/index.test.ts                    |  51 ++-
 .../src/__tests__/process-manager.test.ts          |  32 +-
 .../src/__tests__/runtime-adapter.test.ts          |   4 +-
 .../src/__tests__/setup.test.ts                    |  71 ++++
 plugins/fusion-plugin-acp-runtime/src/cli-spawn.ts |  95 ++++-
 plugins/fusion-plugin-acp-runtime/src/index.ts     |  16 +-
 .../src/process-manager.ts                         |  26 +-
 .../src/runtime-adapter.ts                         |  11 +-
 plugins/fusion-plugin-acp-runtime/src/setup.ts     | 104 +++++
 plugins/fusion-plugin-acp-runtime/src/types.ts     |   6 +-
 pnpm-lock.yaml                                     | 139 ++++--
 28 files changed, 1502 insertions(+), 198 deletions(-)

Fusion-Task-Id: FN-6457
Fusion-Task-Lineage: a3364ed7-cb28-4a2b-b898-6ccd0d95fb92
2026-06-15 02:30:41 -07:00

72 lines
3.1 KiB
TypeScript

import { describe, expect, it } from "vitest";
import { join } from "node:path";
import { checkSetup, setupManifest, validateBundledBridgeIdentity } from "../setup.js";
import { CLAUDE_CODE_CLI_ACP_BINARY, bundledClaudeBridgeBinPath } from "../cli-spawn.js";
import type { AcpProbeStatus, ProbeOptions } from "../probe.js";
function ctx(settings: Record<string, unknown> = {}) {
return { settings } as never;
}
function probe(status: AcpProbeStatus) {
return async (_opts: ProbeOptions) => status;
}
describe("ACP setup manifest", () => {
it("describes the bundled Claude bridge", () => {
expect(setupManifest.binaryName).toBe(CLAUDE_CODE_CLI_ACP_BINARY);
expect(setupManifest.channel).toBe("beta");
});
});
describe("validateBundledBridgeIdentity", () => {
it("accepts the plugin-owned node_modules bin shim", () => {
expect(validateBundledBridgeIdentity(bundledClaudeBridgeBinPath())).toBeUndefined();
});
it("rejects a PATH-resolved substitute outside plugin node_modules", () => {
const pluginRoot = "/repo/plugins/fusion-plugin-acp-runtime";
const err = validateBundledBridgeIdentity("/usr/local/bin/claude-code-cli-acp", pluginRoot);
expect(err).toContain("must come from this plugin's node_modules");
});
it("accepts nested package files inside plugin node_modules", () => {
const pluginRoot = "/repo/plugins/fusion-plugin-acp-runtime";
const packageBin = join(pluginRoot, "node_modules", "claude-code-cli-acp", "bin", "claude-code-cli-acp.js");
expect(validateBundledBridgeIdentity(packageBin, pluginRoot)).toBeUndefined();
});
});
describe("checkSetup", () => {
it("reports installed when the bridge handshakes", async () => {
const result = await checkSetup(ctx(), { probe: probe({ ok: true, reason: "ok", authRequired: false }) });
expect(result.status).toBe("installed");
expect(result.binaryPath).toContain("claude-code-cli-acp");
});
it("maps missing_binary to a not-installed install hint", async () => {
const result = await checkSetup(ctx(), {
probe: probe({ ok: false, reason: "missing_binary", detail: "ENOENT" }),
});
expect(result.status).toBe("not-installed");
expect(result.error).toContain("Install bundled dependency");
});
it("maps authRequired ok status to the claude auth hint", async () => {
const result = await checkSetup(ctx(), { probe: probe({ ok: true, reason: "ok", authRequired: true }) });
expect(result.status).toBe("error");
expect(result.error).toContain("run `claude` once to authenticate");
});
it("maps handshake_timeout and incompatible_protocol to distinct errors", async () => {
const timeout = await checkSetup(ctx(), {
probe: probe({ ok: false, reason: "handshake_timeout", detail: "initialize timed out" }),
});
const incompatible = await checkSetup(ctx(), {
probe: probe({ ok: false, reason: "incompatible_protocol", detail: "protocol 999", protocolVersion: 999 }),
});
expect(timeout).toMatchObject({ status: "error", error: "initialize timed out" });
expect(incompatible).toMatchObject({ status: "error", error: "protocol 999" });
});
});