mirror of
https://github.com/paperclipai/paperclip.git
synced 2026-11-09 07:10:34 +01:00
Deliver native Kimi Code setup, exact models, scoped credentials, verified packages and runtime packs. Preserve current upstream release attestations and restore verified same-run remote retry dispatch without weakening process ownership. Co-Authored-By: Paperclip <noreply@paperclip.ing>
565 lines
24 KiB
TypeScript
565 lines
24 KiB
TypeScript
import { probeCopilotExecutionTarget } from "../services/copilot-connection-probe.js";
|
|
import { HttpError } from "../errors.js";
|
|
import { probeAcpxClaudeInstallation, probeAcpxCursorInstallation, probeAcpxPiInstallation } from "../vendor/paperclip-runner/live/index.js";
|
|
import { describe, expect, it, beforeEach, afterEach, vi } from "vitest";
|
|
import { buildSandboxNpmInstallCommand } from "@paperclipai/adapter-utils";
|
|
import type { ServerAdapterModule } from "../adapters/index.js";
|
|
|
|
import {
|
|
detectAdapterModel,
|
|
findActiveServerAdapter,
|
|
findServerAdapter,
|
|
listAdapterModels,
|
|
registerServerAdapter,
|
|
requireServerAdapter,
|
|
unregisterServerAdapter,
|
|
} from "../adapters/index.js";
|
|
import {
|
|
resolveExternalAdapterRegistration,
|
|
setOverridePaused,
|
|
hasActiveAdapterOverride,
|
|
} from "../adapters/registry.js";
|
|
|
|
const nativeSetup = vi.hoisted(() => ({ authenticate: vi.fn() }));
|
|
vi.mock("../services/native-runtime/setup-readiness.js", () => ({
|
|
testNativeRunnerAuthentication: nativeSetup.authenticate,
|
|
}));
|
|
vi.mock("../services/copilot-connection-probe.js",()=>({probeCopilotExecutionTarget:vi.fn(async()=>({status:"verified",models:[{id:"gpt-5.6-luna"}]}))}));
|
|
|
|
vi.mock("../vendor/paperclip-runner/live/index.js", () => ({
|
|
probeAcpxClaudeInstallation: vi.fn(async () => undefined),
|
|
probeAcpxGrokInstallation: vi.fn(async () => undefined),
|
|
probeAcpxPiInstallation: vi.fn(async () => undefined),
|
|
probeAcpxCursorInstallation: vi.fn(async () => undefined),
|
|
probeAcpxKimiInstallation: vi.fn(async () => undefined),
|
|
probeKimiModels: vi.fn(async () => undefined),
|
|
}));
|
|
|
|
it("advertises tool-refresh recovery for the selected legacy harness", () => {
|
|
for (const type of ["claude_local", "codex_local", "grok_local", "gemini_local", "kimi_local", "cursor", "opencode_local", "pi_local"]) {
|
|
expect(requireServerAdapter(type).supportsToolRefreshOnResume).toBe(true);
|
|
expect(requireServerAdapter(type).sessionManagement?.supportsSessionResume).toBe(true);
|
|
}
|
|
expect(requireServerAdapter("process").supportsToolRefreshOnResume).toBeUndefined();
|
|
});
|
|
|
|
const externalAdapter: ServerAdapterModule = {
|
|
type: "external_test",
|
|
execute: async () => ({
|
|
exitCode: 0,
|
|
signal: null,
|
|
timedOut: false,
|
|
}),
|
|
testEnvironment: async () => ({
|
|
adapterType: "external_test",
|
|
status: "pass",
|
|
checks: [],
|
|
testedAt: new Date(0).toISOString(),
|
|
}),
|
|
models: [{ id: "external-model", label: "External Model" }],
|
|
supportsLocalAgentJwt: false,
|
|
};
|
|
|
|
describe("server adapter registry", () => {
|
|
beforeEach(() => {
|
|
unregisterServerAdapter("external_test");
|
|
unregisterServerAdapter("hermes_local");
|
|
unregisterServerAdapter("hermes_gateway");
|
|
unregisterServerAdapter("claude_local");
|
|
setOverridePaused("claude_local", false);
|
|
});
|
|
|
|
afterEach(() => {
|
|
unregisterServerAdapter("external_test");
|
|
unregisterServerAdapter("hermes_local");
|
|
unregisterServerAdapter("hermes_gateway");
|
|
unregisterServerAdapter("claude_local");
|
|
setOverridePaused("claude_local", false);
|
|
});
|
|
|
|
it("registers external adapters and exposes them through lookup helpers", async () => {
|
|
expect(findServerAdapter("external_test")).toBeNull();
|
|
|
|
registerServerAdapter(externalAdapter);
|
|
|
|
expect(requireServerAdapter("external_test")).toBe(externalAdapter);
|
|
expect(await listAdapterModels("external_test")).toEqual([
|
|
{ id: "external-model", label: "External Model" },
|
|
]);
|
|
});
|
|
|
|
it("removes external adapters when unregistered", () => {
|
|
registerServerAdapter(externalAdapter);
|
|
|
|
unregisterServerAdapter("external_test");
|
|
|
|
expect(findServerAdapter("external_test")).toBeNull();
|
|
expect(() => requireServerAdapter("external_test")).toThrow(
|
|
"Unknown adapter type: external_test",
|
|
);
|
|
});
|
|
|
|
it("allows external plugin to override a built-in adapter type", () => {
|
|
// claude_local is always built-in
|
|
const builtIn = findServerAdapter("claude_local");
|
|
expect(builtIn).not.toBeNull();
|
|
|
|
const plugin: ServerAdapterModule = {
|
|
type: "claude_local",
|
|
execute: async () => ({
|
|
exitCode: 0,
|
|
signal: null,
|
|
timedOut: false,
|
|
}),
|
|
testEnvironment: async () => ({
|
|
adapterType: "claude_local",
|
|
status: "pass",
|
|
checks: [],
|
|
testedAt: new Date(0).toISOString(),
|
|
}),
|
|
models: [{ id: "plugin-model", label: "Plugin Override" }],
|
|
supportsLocalAgentJwt: false,
|
|
};
|
|
|
|
registerServerAdapter(plugin);
|
|
|
|
// Plugin wins
|
|
const resolved = requireServerAdapter("claude_local");
|
|
expect(resolved).toBe(plugin);
|
|
expect(resolved.models).toEqual([
|
|
{ id: "plugin-model", label: "Plugin Override" },
|
|
]);
|
|
});
|
|
|
|
it("ships Hermes adapters as built-ins and still accepts external overrides", () => {
|
|
const builtInLocal = findServerAdapter("hermes_local");
|
|
const builtInGateway = findServerAdapter("hermes_gateway");
|
|
|
|
expect(builtInLocal).not.toBeNull();
|
|
expect(builtInLocal?.supportsLocalAgentJwt).toBe(true);
|
|
expect(builtInLocal?.supportsInstructionsBundle).toBe(true);
|
|
expect(builtInLocal?.requiresMaterializedRuntimeSkills).toBe(false);
|
|
expect(builtInLocal?.detectModel).toBeTypeOf("function");
|
|
expect(builtInLocal?.getConfigSchema).toBeTypeOf("function");
|
|
|
|
expect(builtInGateway).not.toBeNull();
|
|
expect(builtInGateway?.supportsLocalAgentJwt).toBe(false);
|
|
expect(builtInGateway?.supportsInstructionsBundle).toBe(false);
|
|
expect(builtInGateway?.requiresMaterializedRuntimeSkills).toBe(false);
|
|
expect(builtInGateway?.getConfigSchema).toBeTypeOf("function");
|
|
|
|
const hermesLocalExternalAdapter: ServerAdapterModule = {
|
|
type: "hermes_local",
|
|
execute: async () => ({ exitCode: 0, signal: null, timedOut: false }),
|
|
testEnvironment: async () => ({
|
|
adapterType: "hermes_local",
|
|
status: "pass",
|
|
checks: [],
|
|
testedAt: new Date(0).toISOString(),
|
|
}),
|
|
supportsLocalAgentJwt: true,
|
|
supportsInstructionsBundle: true,
|
|
instructionsPathKey: "instructionsFilePath",
|
|
requiresMaterializedRuntimeSkills: false,
|
|
listSkills: async () => ({
|
|
adapterType: "hermes_local",
|
|
supported: true,
|
|
mode: "ephemeral",
|
|
desiredSkills: [],
|
|
entries: [],
|
|
warnings: [],
|
|
}),
|
|
getConfigSchema: () => ({ fields: [{ key: "provider", label: "Provider", type: "text" }] }),
|
|
detectModel: async () => ({
|
|
model: "hermes-model",
|
|
provider: "openrouter",
|
|
source: "test",
|
|
}),
|
|
};
|
|
|
|
const hermesGatewayExternalAdapter: ServerAdapterModule = {
|
|
type: "hermes_gateway",
|
|
execute: async () => ({ exitCode: 0, signal: null, timedOut: false }),
|
|
testEnvironment: async () => ({
|
|
adapterType: "hermes_gateway",
|
|
status: "pass",
|
|
checks: [],
|
|
testedAt: new Date(0).toISOString(),
|
|
}),
|
|
supportsLocalAgentJwt: false,
|
|
supportsInstructionsBundle: false,
|
|
requiresMaterializedRuntimeSkills: false,
|
|
getConfigSchema: () => ({
|
|
fields: [{ key: "apiBaseUrl", label: "API URL", type: "text" }],
|
|
}),
|
|
};
|
|
|
|
registerServerAdapter(hermesLocalExternalAdapter);
|
|
|
|
expect(requireServerAdapter("hermes_local")).toBe(hermesLocalExternalAdapter);
|
|
expect(findActiveServerAdapter("hermes_local")?.supportsLocalAgentJwt).toBe(true);
|
|
|
|
unregisterServerAdapter("hermes_local");
|
|
|
|
expect(requireServerAdapter("hermes_local")).toBe(builtInLocal);
|
|
|
|
registerServerAdapter(hermesGatewayExternalAdapter);
|
|
|
|
expect(requireServerAdapter("hermes_gateway")).toBe(hermesGatewayExternalAdapter);
|
|
expect(findActiveServerAdapter("hermes_gateway")?.supportsLocalAgentJwt).toBe(false);
|
|
|
|
unregisterServerAdapter("hermes_gateway");
|
|
|
|
expect(requireServerAdapter("hermes_gateway")).toBe(builtInGateway);
|
|
});
|
|
|
|
it("exposes capability flags from registered adapters", () => {
|
|
const adapterWithCaps: ServerAdapterModule = {
|
|
type: "external_test",
|
|
execute: async () => ({ exitCode: 0, signal: null, timedOut: false }),
|
|
testEnvironment: async () => ({
|
|
adapterType: "external_test",
|
|
status: "pass" as const,
|
|
checks: [],
|
|
testedAt: new Date(0).toISOString(),
|
|
}),
|
|
supportsLocalAgentJwt: true,
|
|
supportsInstructionsBundle: true,
|
|
instructionsPathKey: "customPathKey",
|
|
requiresMaterializedRuntimeSkills: true,
|
|
};
|
|
|
|
registerServerAdapter(adapterWithCaps);
|
|
|
|
const resolved = findActiveServerAdapter("external_test");
|
|
expect(resolved).not.toBeNull();
|
|
expect(resolved!.supportsInstructionsBundle).toBe(true);
|
|
expect(resolved!.instructionsPathKey).toBe("customPathKey");
|
|
expect(resolved!.requiresMaterializedRuntimeSkills).toBe(true);
|
|
expect(resolved!.supportsLocalAgentJwt).toBe(true);
|
|
});
|
|
|
|
it("returns undefined for capability flags on adapters that do not set them", () => {
|
|
registerServerAdapter(externalAdapter);
|
|
|
|
const resolved = findActiveServerAdapter("external_test");
|
|
expect(resolved).not.toBeNull();
|
|
expect(resolved!.supportsInstructionsBundle).toBeUndefined();
|
|
expect(resolved!.instructionsPathKey).toBeUndefined();
|
|
expect(resolved!.requiresMaterializedRuntimeSkills).toBeUndefined();
|
|
});
|
|
|
|
it("built-in claude_local adapter declares capability flags", () => {
|
|
const adapter = findActiveServerAdapter("claude_local");
|
|
expect(adapter).not.toBeNull();
|
|
expect(adapter!.supportsInstructionsBundle).toBe(true);
|
|
expect(adapter!.instructionsPathKey).toBe("instructionsFilePath");
|
|
expect(adapter!.requiresMaterializedRuntimeSkills).toBe(false);
|
|
expect(adapter!.supportsLocalAgentJwt).toBe(true);
|
|
});
|
|
|
|
it("rejects an incomplete managed runner provider before probing Codex", async () => {
|
|
const adapter = requireServerAdapter("paperclip_runner");
|
|
expect(adapter.supportsInstructionsBundle).toBe(true);
|
|
expect(adapter.instructionsPathKey).toBe("instructionsFilePath");
|
|
const result = await adapter.testEnvironment({
|
|
companyId: "company-1",
|
|
adapterType: "paperclip_runner",
|
|
config: { provider: "claude_managed" },
|
|
});
|
|
|
|
expect(result).toMatchObject({
|
|
adapterType: "paperclip_runner",
|
|
status: "fail",
|
|
checks: [{
|
|
code: "paperclip_runner_claude_managed_profile_required",
|
|
level: "error",
|
|
}],
|
|
});
|
|
});
|
|
|
|
it.each(["pass", "fail"] as const)("uses the selected native Codex runtime for setup (%s)", async status => {
|
|
nativeSetup.authenticate.mockReset().mockResolvedValue({ adapterType: "paperclip_runner", status, testedAt: "", checks: [{ code: `codex_hello_probe_${status === "pass" ? "passed" : "auth_required"}`, level: status === "pass" ? "info" : "error", message: "Selected native account" }] });
|
|
const context = { companyId: "company-1", adapterType: "paperclip_runner", config: { provider: "codex", model: "gpt-5.6-sol", modelReasoningEffort: "high", env: { OPENAI_API_KEY: "selected-key" } } };
|
|
const result = await requireServerAdapter("paperclip_runner").testEnvironment(context);
|
|
expect(result.status).toBe(status);
|
|
expect(nativeSetup.authenticate).toHaveBeenCalledWith(context, "codex", "gpt-5.6-sol");
|
|
});
|
|
|
|
it("identifies only active external overrides for automatic runner selection", () => {
|
|
try {
|
|
expect(hasActiveAdapterOverride("codex_local")).toBe(false);
|
|
registerServerAdapter({ ...externalAdapter, type: "codex_local" });
|
|
expect(hasActiveAdapterOverride("codex_local")).toBe(true);
|
|
setOverridePaused("codex_local", true);
|
|
expect(hasActiveAdapterOverride("codex_local")).toBe(false);
|
|
setOverridePaused("codex_local", false);
|
|
expect(hasActiveAdapterOverride("codex_local")).toBe(true);
|
|
} finally { unregisterServerAdapter("codex_local"); }
|
|
});
|
|
|
|
it.each([
|
|
["claude_managed", {
|
|
managedProfileId: "managed-primary",
|
|
managedAgentsRetentionAcknowledged: true,
|
|
}, "claude_managed_profile_selected"],
|
|
["aws_agentcore", {
|
|
agentCoreProfileId: "agentcore-primary",
|
|
agentCoreRetentionAcknowledged: true,
|
|
}, "aws_agentcore_profile_selected"],
|
|
] as const)("accepts a complete %s profile selection", async (provider, config, code) => {
|
|
const result = await requireServerAdapter("paperclip_runner").testEnvironment({
|
|
companyId: "company-1",
|
|
adapterType: "paperclip_runner",
|
|
config: { provider, ...config },
|
|
});
|
|
|
|
expect(result).toMatchObject({
|
|
adapterType: "paperclip_runner",
|
|
status: "warn",
|
|
checks: expect.arrayContaining([expect.objectContaining({ code, level: "info" })]),
|
|
});
|
|
});
|
|
|
|
it.each([
|
|
["claude", "claude-sonnet-5"],
|
|
] as const)("does not claim runtime readiness from the remote ACPX %s platform alone", async (acpxAgent, model) => {
|
|
const result = await requireServerAdapter("paperclip_runner").testEnvironment({
|
|
companyId: "company-1",
|
|
adapterType: "paperclip_runner",
|
|
config: { provider: "acpx", acpxAgent, model },
|
|
executionTarget: {
|
|
kind: "remote",
|
|
transport: "sandbox",
|
|
remoteCwd: "/workspace",
|
|
providerKey: "test-provider",
|
|
runner: { execute: vi.fn().mockResolvedValue({ exitCode: 0, timedOut: false, stdout: "Linux\nx86_64\n" }) },
|
|
},
|
|
});
|
|
|
|
expect(result).toMatchObject({
|
|
adapterType: "paperclip_runner",
|
|
status: "warn",
|
|
checks: [{ code: "acpx_remote_runtime_unverified", level: "warn" }],
|
|
});
|
|
});
|
|
|
|
it.each([true, false])("checks actual local ACPX installation readiness (%s)", async (ready) => {
|
|
const probe = vi.mocked(probeAcpxClaudeInstallation);
|
|
if (ready) probe.mockResolvedValueOnce(undefined);
|
|
else probe.mockRejectedValueOnce(new Error("Runtime package integrity verification failed"));
|
|
const result = await requireServerAdapter("paperclip_runner").testEnvironment({
|
|
companyId: "company-1", adapterType: "paperclip_runner",
|
|
config: { provider: "acpx", acpxAgent: "claude", model: "custom-claude-model" },
|
|
});
|
|
expect(probe).toHaveBeenLastCalledWith("custom-claude-model");
|
|
expect(result).toMatchObject({
|
|
status: ready ? "pass" : "fail",
|
|
checks: [expect.objectContaining({ code: ready ? "acpx_runtime_ready" : "acpx_runtime_unavailable" })],
|
|
});
|
|
});
|
|
|
|
it.each([true, false])("probes the exact Pi installation without qualification opt-in (ready=%s)", async (ready) => {
|
|
const probe = vi.mocked(probeAcpxPiInstallation);
|
|
if (ready) probe.mockResolvedValueOnce(undefined);
|
|
else probe.mockRejectedValueOnce(new Error("Pi closure unavailable"));
|
|
const result = await requireServerAdapter("paperclip_runner").testEnvironment({
|
|
companyId: "company-1",
|
|
adapterType: "paperclip_runner",
|
|
config: {
|
|
provider: "acpx",
|
|
acpxAgent: "pi",
|
|
model: "openrouter/deepseek/deepseek-v4-flash-0731",
|
|
},
|
|
});
|
|
|
|
expect(result).toMatchObject({
|
|
status: ready ? "pass" : "fail",
|
|
checks: [{ code: ready ? "acpx_runtime_ready" : "acpx_runtime_unavailable" }],
|
|
});
|
|
expect(probe).toHaveBeenLastCalledWith("openrouter/deepseek/deepseek-v4-flash-0731");
|
|
});
|
|
it("requires a saved Copilot token rather than ambient authentication",async()=>{
|
|
const prior=process.env.COPILOT_GITHUB_TOKEN;process.env.COPILOT_GITHUB_TOKEN="ambient-not-authority";
|
|
const probe=vi.mocked(probeCopilotExecutionTarget);probe.mockClear();
|
|
try{
|
|
const result=await requireServerAdapter("paperclip_runner").testEnvironment({companyId:"company-1",adapterType:"paperclip_runner",config:{provider:"acpx",acpxAgent:"copilot",model:"gpt-5.6-luna"}});
|
|
expect(result).toMatchObject({status:"fail",checks:[{code:"COPILOT_AUTH_REQUIRED"}]});expect(probe).not.toHaveBeenCalled();
|
|
}finally{if(prior===undefined)delete process.env.COPILOT_GITHUB_TOKEN;else process.env.COPILOT_GITHUB_TOKEN=prior;}
|
|
});
|
|
it.each([undefined,{kind:"remote",remoteCwd:"/workspace"}])("verifies the selected Copilot runtime and model without a prompt",async(executionTarget)=>{
|
|
const result=await requireServerAdapter("paperclip_runner").testEnvironment({companyId:"company-1",adapterType:"paperclip_runner",config:{provider:"acpx",acpxAgent:"copilot",model:"gpt-5.6-luna",env:{COPILOT_GITHUB_TOKEN:"fixture-token"}},executionTarget} as any);
|
|
expect(probeCopilotExecutionTarget).toHaveBeenLastCalledWith("fixture-token",executionTarget,"gpt-5.6-luna");
|
|
expect(result).toMatchObject({status:"pass",checks:[{code:"copilot_metadata_verified"}]});
|
|
});
|
|
it.each(["COPILOT_AUTH_REQUIRED","COPILOT_ENTITLEMENT_DENIED","COPILOT_MODEL_UNAVAILABLE","COPILOT_INSTALLATION_INVALID","COPILOT_REQUEST_FAILED"])("reports distinct Copilot failure %s",async code=>{
|
|
vi.mocked(probeCopilotExecutionTarget).mockRejectedValueOnce(new HttpError(422,"Fixture metadata failure",{code}));
|
|
const result=await requireServerAdapter("paperclip_runner").testEnvironment({companyId:"company-1",adapterType:"paperclip_runner",config:{provider:"acpx",acpxAgent:"copilot",model:"gpt-5.6-luna",env:{COPILOT_GITHUB_TOKEN:"fixture-token"}}});
|
|
expect(result).toMatchObject({status:"fail",checks:[{code}]});
|
|
});
|
|
it("sanitizes unexpected Copilot metadata failures",async()=>{
|
|
vi.mocked(probeCopilotExecutionTarget).mockRejectedValueOnce(new Error("fixture private implementation detail"));
|
|
const result=await requireServerAdapter("paperclip_runner").testEnvironment({companyId:"company-1",adapterType:"paperclip_runner",config:{provider:"acpx",acpxAgent:"copilot",model:"gpt-5.6-luna",env:{COPILOT_GITHUB_TOKEN:"fixture-token"}}});
|
|
expect(result).toMatchObject({status:"fail",checks:[{code:"COPILOT_REQUEST_FAILED",message:"Copilot metadata verification failed."}]});
|
|
expect(JSON.stringify(result)).not.toContain("private implementation detail");
|
|
});
|
|
it("wraps built-in npm runtime installs with the sandbox-aware install helper", () => {
|
|
const expectedClaudeInstall = `if ! command -v 'claude' >/dev/null 2>&1; then ${buildSandboxNpmInstallCommand("@anthropic-ai/claude-code")}; fi`;
|
|
const expectedCodexInstall = `if ! command -v 'codex' >/dev/null 2>&1; then ${buildSandboxNpmInstallCommand("@openai/codex")}; fi`;
|
|
const expectedGeminiInstall = `if ! command -v 'gemini' >/dev/null 2>&1; then ${buildSandboxNpmInstallCommand("@google/gemini-cli")}; fi`;
|
|
const expectedOpenCodeInstall = `if ! command -v 'opencode' >/dev/null 2>&1; then ${buildSandboxNpmInstallCommand("opencode-ai")}; fi`;
|
|
const expectedRunnerCodexInstall = `if ! command -v 'codex' >/dev/null 2>&1; then ${buildSandboxNpmInstallCommand("@openai/codex@0.160.0")}; fi`;
|
|
const expectedRunnerOpenCodeInstall = `if ! command -v 'opencode' >/dev/null 2>&1; then ${buildSandboxNpmInstallCommand("opencode-ai@1.18.34")}; fi`;
|
|
|
|
expect(findActiveServerAdapter("claude_local")?.getRuntimeCommandSpec?.({})).toEqual({
|
|
command: "claude",
|
|
detectCommand: "claude",
|
|
installCommand: expectedClaudeInstall,
|
|
});
|
|
expect(findActiveServerAdapter("codex_local")?.getRuntimeCommandSpec?.({})).toEqual({
|
|
command: "codex",
|
|
detectCommand: "codex",
|
|
installCommand: expectedCodexInstall,
|
|
});
|
|
expect(findActiveServerAdapter("gemini_local")?.getRuntimeCommandSpec?.({})).toEqual({
|
|
command: "gemini",
|
|
detectCommand: "gemini",
|
|
installCommand: expectedGeminiInstall,
|
|
});
|
|
expect(findActiveServerAdapter("opencode_local")?.getRuntimeCommandSpec?.({})).toEqual({
|
|
command: "opencode",
|
|
detectCommand: "opencode",
|
|
installCommand: expectedOpenCodeInstall,
|
|
});
|
|
expect(findActiveServerAdapter("paperclip_runner")?.getRuntimeCommandSpec?.({ provider: "codex" })).toEqual({
|
|
command: "codex",
|
|
detectCommand: "codex",
|
|
installCommand: expectedRunnerCodexInstall,
|
|
});
|
|
expect(findActiveServerAdapter("paperclip_runner")?.getRuntimeCommandSpec?.({ provider: "opencode" })).toEqual({
|
|
command: "opencode",
|
|
detectCommand: "opencode",
|
|
installCommand: expectedRunnerOpenCodeInstall,
|
|
});
|
|
expect(findActiveServerAdapter("paperclip_runner")?.getRuntimeCommandSpec?.({ provider: "acpx" })).toEqual({
|
|
command: "paperclip-runnerd",
|
|
detectCommand: null,
|
|
installCommand: null,
|
|
});
|
|
});
|
|
|
|
it("switches active adapter behavior back to the builtin when an override is paused", async () => {
|
|
const builtIn = findServerAdapter("claude_local");
|
|
expect(builtIn).not.toBeNull();
|
|
|
|
const detectModel = vi.fn(async () => ({
|
|
model: "plugin-model",
|
|
provider: "plugin-provider",
|
|
source: "plugin-source",
|
|
}));
|
|
const plugin: ServerAdapterModule = {
|
|
type: "claude_local",
|
|
execute: async () => ({
|
|
exitCode: 0,
|
|
signal: null,
|
|
timedOut: false,
|
|
}),
|
|
testEnvironment: async () => ({
|
|
adapterType: "claude_local",
|
|
status: "pass",
|
|
checks: [],
|
|
testedAt: new Date(0).toISOString(),
|
|
}),
|
|
models: [{ id: "plugin-model", label: "Plugin Override" }],
|
|
detectModel,
|
|
supportsLocalAgentJwt: false,
|
|
};
|
|
|
|
registerServerAdapter(plugin);
|
|
|
|
expect(findActiveServerAdapter("claude_local")).toBe(plugin);
|
|
expect(await listAdapterModels("claude_local")).toEqual([
|
|
{ id: "plugin-model", label: "Plugin Override" },
|
|
]);
|
|
expect(await detectAdapterModel("claude_local")).toMatchObject({
|
|
model: "plugin-model",
|
|
provider: "plugin-provider",
|
|
});
|
|
|
|
expect(setOverridePaused("claude_local", true)).toBe(true);
|
|
|
|
expect(findActiveServerAdapter("claude_local")).not.toBe(plugin);
|
|
expect(await listAdapterModels("claude_local")).toEqual(builtIn?.models ?? []);
|
|
expect(await detectAdapterModel("claude_local")).toBeNull();
|
|
expect(detectModel).toHaveBeenCalledTimes(1);
|
|
});
|
|
});
|
|
|
|
describe("resolveExternalAdapterRegistration", () => {
|
|
it("preserves module-provided sessionManagement", () => {
|
|
const sessionManagement = {
|
|
supportsSessionResume: true,
|
|
nativeContextManagement: "unknown" as const,
|
|
defaultSessionCompaction: {
|
|
enabled: true,
|
|
maxSessionRuns: 200,
|
|
maxRawInputTokens: 2_000_000,
|
|
maxSessionAgeHours: 72,
|
|
},
|
|
};
|
|
const adapter: ServerAdapterModule = {
|
|
type: "external_session_test",
|
|
execute: async () => ({ exitCode: 0, signal: null, timedOut: false }),
|
|
testEnvironment: async () => ({
|
|
adapterType: "external_session_test",
|
|
status: "pass",
|
|
checks: [],
|
|
testedAt: new Date(0).toISOString(),
|
|
}),
|
|
sessionManagement,
|
|
};
|
|
|
|
const resolved = resolveExternalAdapterRegistration(adapter);
|
|
|
|
expect(resolved.sessionManagement).toBe(sessionManagement);
|
|
});
|
|
|
|
it("falls back to the hardcoded registry when the module omits sessionManagement", () => {
|
|
// An external that overrides a built-in type should inherit the built-in's
|
|
// sessionManagement when it does not provide its own.
|
|
const adapter: ServerAdapterModule = {
|
|
type: "claude_local",
|
|
execute: async () => ({ exitCode: 0, signal: null, timedOut: false }),
|
|
testEnvironment: async () => ({
|
|
adapterType: "claude_local",
|
|
status: "pass",
|
|
checks: [],
|
|
testedAt: new Date(0).toISOString(),
|
|
}),
|
|
};
|
|
|
|
const resolved = resolveExternalAdapterRegistration(adapter);
|
|
|
|
expect(resolved.sessionManagement).toBeDefined();
|
|
expect(resolved.sessionManagement?.supportsSessionResume).toBe(true);
|
|
expect(resolved.sessionManagement?.nativeContextManagement).toBe("confirmed");
|
|
});
|
|
|
|
it("leaves sessionManagement undefined when neither module nor registry provides one", () => {
|
|
const adapter: ServerAdapterModule = {
|
|
type: "external_unknown_test",
|
|
execute: async () => ({ exitCode: 0, signal: null, timedOut: false }),
|
|
testEnvironment: async () => ({
|
|
adapterType: "external_unknown_test",
|
|
status: "pass",
|
|
checks: [],
|
|
testedAt: new Date(0).toISOString(),
|
|
}),
|
|
};
|
|
|
|
const resolved = resolveExternalAdapterRegistration(adapter);
|
|
|
|
expect(resolved.sessionManagement).toBeUndefined();
|
|
});
|
|
});
|