feat(acp): enable Route A via experimental flag (claudeCliAcp), default ON
Replace the manual FUSION_CLAUDE_ACP env enable with an experimental feature switch. `experimentalFeatures.claudeCliAcp` is ON by default (off only when explicitly set false); the engine translates it into the FUSION_CLAUDE_ACP dispatch the pi-claude-cli provider reads, at registerExtensionProviders time. - Still fail-closed: with no bridge path published (acp-runtime plugin absent), the provider falls back to `claude -p`. - Explicit FUSION_CLAUDE_ACP env always wins (operator / test override). - New testable helper claude-acp-enable.ts (6/6 tests); flag documented in the core experimentalFeatures doc. So with the acp-runtime plugin installed, Claude CLI now routes through the ACP bridge by default; set experimentalFeatures.claudeCliAcp=false to force `-p`. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
@@ -3170,9 +3170,14 @@ export interface GlobalSettings {
|
||||
* "another-experiment": false
|
||||
* }
|
||||
*
|
||||
* Default: workflow columns, graph executor, dual-observe, and authoritative
|
||||
* interpreter flags enabled; operators may explicitly set individual flags
|
||||
* false while rollout controls remain available. */
|
||||
* Default: workflow columns, graph executor, dual-observe, authoritative
|
||||
* interpreter, and `claudeCliAcp` flags enabled; operators may explicitly set
|
||||
* individual flags false while rollout controls remain available.
|
||||
*
|
||||
* `claudeCliAcp` (default ON): routes the Claude CLI provider through the
|
||||
* `claude-code-cli-acp` ACP bridge instead of `claude -p`. Effective only when
|
||||
* the acp-runtime plugin is installed (it publishes the bundled bridge path);
|
||||
* otherwise the provider fails closed to `-p`. Set false to force `-p`. */
|
||||
experimentalFeatures?: Record<string, boolean>;
|
||||
/** Per-adapter CLI-agent launch configuration (CLI Agent Executor, U15).
|
||||
* Keyed by adapter id (e.g. `"claude-code"`, `"codex"`, `"generic"`). Each
|
||||
|
||||
38
packages/engine/src/__tests__/claude-acp-enable.test.ts
Normal file
38
packages/engine/src/__tests__/claude-acp-enable.test.ts
Normal file
@@ -0,0 +1,38 @@
|
||||
import { describe, it, expect } from "vitest";
|
||||
import { claudeAcpExperimentalEnabled, applyClaudeAcpEnable } from "../claude-acp-enable.js";
|
||||
|
||||
describe("claudeAcpExperimentalEnabled — default ON", () => {
|
||||
it("is ON when no settings / no experimentalFeatures", () => {
|
||||
expect(claudeAcpExperimentalEnabled(undefined)).toBe(true);
|
||||
expect(claudeAcpExperimentalEnabled({})).toBe(true);
|
||||
expect(claudeAcpExperimentalEnabled({ experimentalFeatures: {} })).toBe(true);
|
||||
});
|
||||
it("is ON when explicitly true", () => {
|
||||
expect(claudeAcpExperimentalEnabled({ experimentalFeatures: { claudeCliAcp: true } })).toBe(true);
|
||||
});
|
||||
it("is OFF only when explicitly false", () => {
|
||||
expect(claudeAcpExperimentalEnabled({ experimentalFeatures: { claudeCliAcp: false } })).toBe(false);
|
||||
});
|
||||
});
|
||||
|
||||
describe("applyClaudeAcpEnable — translates the flag to FUSION_CLAUDE_ACP", () => {
|
||||
it("sets FUSION_CLAUDE_ACP=1 when enabled (default ON) and env unset", () => {
|
||||
const env: NodeJS.ProcessEnv = {};
|
||||
expect(applyClaudeAcpEnable({}, env)).toBe(true);
|
||||
expect(env.FUSION_CLAUDE_ACP).toBe("1");
|
||||
});
|
||||
it("does NOT set the env when the flag is explicitly false", () => {
|
||||
const env: NodeJS.ProcessEnv = {};
|
||||
expect(applyClaudeAcpEnable({ experimentalFeatures: { claudeCliAcp: false } }, env)).toBe(false);
|
||||
expect(env.FUSION_CLAUDE_ACP).toBeUndefined();
|
||||
});
|
||||
it("honors an explicit env override (operator/test wins over the flag)", () => {
|
||||
const off: NodeJS.ProcessEnv = { FUSION_CLAUDE_ACP: "0" };
|
||||
expect(applyClaudeAcpEnable({}, off)).toBe(false); // flag default-on, but env says off
|
||||
expect(off.FUSION_CLAUDE_ACP).toBe("0");
|
||||
|
||||
const on: NodeJS.ProcessEnv = { FUSION_CLAUDE_ACP: "1" };
|
||||
expect(applyClaudeAcpEnable({ experimentalFeatures: { claudeCliAcp: false } }, on)).toBe(true);
|
||||
expect(on.FUSION_CLAUDE_ACP).toBe("1");
|
||||
});
|
||||
});
|
||||
36
packages/engine/src/claude-acp-enable.ts
Normal file
36
packages/engine/src/claude-acp-enable.ts
Normal file
@@ -0,0 +1,36 @@
|
||||
/**
|
||||
* Route A enable resolution (experimental, DEFAULT ON).
|
||||
*
|
||||
* The `pi-claude-cli` provider drives Claude through the `claude-code-cli-acp`
|
||||
* ACP bridge instead of `claude -p` when BOTH hold at dispatch time:
|
||||
* 1. `FUSION_CLAUDE_ACP=1` (this module sets it from the experimental flag), and
|
||||
* 2. a bridge path is resolvable (the acp-runtime plugin publishes
|
||||
* `FUSION_CLAUDE_ACP_BRIDGE` on load — KTD10; absent → fail-closed to `-p`).
|
||||
*
|
||||
* The user-facing switch is `experimentalFeatures.claudeCliAcp`: ON unless the
|
||||
* user explicitly sets it to `false`. An explicit `FUSION_CLAUDE_ACP` env value
|
||||
* always wins (operator / test override) — see {@link applyClaudeAcpEnable}.
|
||||
*/
|
||||
|
||||
/** True unless `experimentalFeatures.claudeCliAcp === false` (default ON). */
|
||||
export function claudeAcpExperimentalEnabled(
|
||||
globalSettings: Record<string, unknown> | undefined,
|
||||
): boolean {
|
||||
const exp = ((globalSettings ?? {}).experimentalFeatures ?? {}) as Record<string, unknown>;
|
||||
return exp.claudeCliAcp !== false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Translate the experimental flag into the `FUSION_CLAUDE_ACP` dispatch the
|
||||
* provider reads. No-op when the env var is already set (explicit override wins),
|
||||
* so operators/tests keep full control. Returns the resolved enabled state.
|
||||
*/
|
||||
export function applyClaudeAcpEnable(
|
||||
globalSettings: Record<string, unknown> | undefined,
|
||||
env: NodeJS.ProcessEnv = process.env,
|
||||
): boolean {
|
||||
if (typeof env.FUSION_CLAUDE_ACP === "string") return env.FUSION_CLAUDE_ACP === "1";
|
||||
const enabled = claudeAcpExperimentalEnabled(globalSettings);
|
||||
if (enabled) env.FUSION_CLAUDE_ACP = "1";
|
||||
return enabled;
|
||||
}
|
||||
@@ -57,6 +57,7 @@ import {
|
||||
type SkillSelectionContext,
|
||||
} from "./skill-resolver.js";
|
||||
import { isContextLimitError } from "./context-limit-detector.js";
|
||||
import { applyClaudeAcpEnable } from "./claude-acp-enable.js";
|
||||
import { createFusionAuthStorage, getModelRegistryModelsPath } from "./auth-storage.js";
|
||||
import { piLog, extensionsLog } from "./logger.js";
|
||||
import { readCustomProviders } from "./custom-providers.js";
|
||||
@@ -1368,10 +1369,18 @@ async function registerExtensionProviders(cwd: string, modelRegistry: ModelRegis
|
||||
|
||||
try {
|
||||
const agentDir = getPackageManagerAgentDir();
|
||||
const settingsView = createReadOnlyPiSettingsView(cwd, agentDir);
|
||||
|
||||
// Route A enable (experimental, DEFAULT ON): translate
|
||||
// experimentalFeatures.claudeCliAcp into the FUSION_CLAUDE_ACP dispatch the
|
||||
// pi-claude-cli provider reads. Still fail-closed — with no bridge path
|
||||
// published (acp-runtime plugin absent), the provider falls back to `-p`.
|
||||
applyClaudeAcpEnable(settingsView.getGlobalSettings() as Record<string, unknown>);
|
||||
|
||||
const packageManager = new DefaultPackageManager({
|
||||
cwd,
|
||||
agentDir,
|
||||
settingsManager: createReadOnlyPiSettingsView(cwd, agentDir) as any,
|
||||
settingsManager: settingsView as any,
|
||||
});
|
||||
const resolvedPaths = await packageManager.resolve();
|
||||
const packageExtensionPaths = resolvedPaths.extensions
|
||||
|
||||
Reference in New Issue
Block a user