fix(claude): preserve Legacy limits after automatic runner resolution

Co-Authored-By: Paperclip <noreply@paperclip.ing>
This commit is contained in:
DottaandPaperclip committed 2026-10-12 18:13:12 -05:00
1 parent 1285cb77f6
commit 75239d8cd7
8 files changed
+101 -7

No files matched your search

@@ -18,6 +18,7 @@ import { ApiError } from "../api/client";
import { aiConnectionsApi } from "../api/ai-connections";
import { CodexLocalConfigFields } from "../adapters/codex-local/config-fields";
import type { AdapterConfigFieldsProps } from "../adapters/types";
import { buildClaudeLocalConfig } from "@paperclipai/adapter-claude-local/ui";
import { DEFAULT_CODEX_LOCAL_MODEL } from "@paperclipai/adapter-codex-local";
const mockAgentsApi = vi.hoisted(() => ({
@@ -73,9 +74,8 @@ vi.mock("../api/agents", () => ({
agentsApi: mockAgentsApi,
}));
vi.mock("../api/adapters", () => ({
adaptersApi: { list: async () => [{ type: "codex_local", supportedRunners: ["legacy", "paperclip"], defaultRunner: "legacy" }] },
}));
const mockAdaptersApi = vi.hoisted(() => ({ list: vi.fn(async () => [{ type: "codex_local", supportedRunners: ["legacy", "paperclip"], defaultRunner: "legacy" }]) }));
vi.mock("../api/adapters", () => ({ adaptersApi: mockAdaptersApi }));
vi.mock("../api/environments", () => ({
environmentsApi: mockEnvironmentsApi,
@@ -660,6 +660,37 @@ async function flushUntil(check: () => boolean, timeoutMs = 4000) {
describe("AgentConfigForm environment selector", () => {
let roots: Root[] = [];
it.each(["paperclip", "legacy"] as const)("uses the resolved %s Claude form to preserve the correct turn limit", async defaultRunner => {
mockAdaptersApi.list.mockResolvedValue([{ type: "claude_local", supportedRunners: defaultRunner === "paperclip" ? ["paperclip", "legacy"] : ["legacy"], defaultRunner }]);
try {
const values = { ...defaultCreateValues, adapterType: "claude_local", runner: "auto" as const };
const result = await renderCreateForm([], values);
roots.push(result.root);
const resolved = result.onChange.mock.calls.reduce((current, [patch]) => ({ ...current, ...patch }), values);
expect(resolved.resolvedRunner).toBe(defaultRunner);
const config = buildClaudeLocalConfig(resolved);
if (defaultRunner === "legacy") expect(config.maxTurnsPerRun).toBe(1000);
else expect(config).not.toHaveProperty("maxTurnsPerRun");
expect(config).not.toHaveProperty("resolvedRunner");
} finally { mockAdaptersApi.list.mockResolvedValue([{ type: "codex_local", supportedRunners: ["legacy", "paperclip"], defaultRunner: "legacy" }]); }
});
it("carries a selected native Claude permission through the ordinary creation builder", async () => {
mockAdaptersApi.list.mockResolvedValue([{ type: "claude_local", supportedRunners: ["paperclip", "legacy"], defaultRunner: "paperclip" }]);
try {
const values = { ...defaultCreateValues, adapterType: "claude_local", runner: "auto" as const };
const result = await renderCreateForm([], values);
roots.push(result.root);
await clickElement(result.container.querySelector('[aria-label="Permission mode"]'));
const deny = [...document.querySelectorAll<HTMLElement>('[role="option"]')].find(option => option.textContent === "Deny all");
expect(deny).toBeTruthy();
await clickElement(deny);
const resolved = result.onChange.mock.calls.reduce((current, [patch]) => ({ ...current, ...patch }), values);
expect(buildClaudeLocalConfig(resolved)).toMatchObject({ acpxPermissionMode: "deny-all" });
} finally { mockAdaptersApi.list.mockResolvedValue([{ type: "codex_local", supportedRunners: ["legacy", "paperclip"], defaultRunner: "legacy" }]); }
});
it("keeps an explicit Codex draft policy until a harness reset clears it", async () => {
mockEnvironmentsApi.list.mockResolvedValue([]);
const container = document.createElement("div");
+6
View File
@@ -628,6 +628,12 @@ export function AgentConfigForm(props: AgentConfigFormProps) {
const fieldsAdapterType = isRunnerHarness
? (runner === "paperclip" || (runner === "auto" && runnerAvailability?.defaultRunner === "paperclip") ? "paperclip_runner" : harnessType)
: adapterType;
useEffect(() => {
if (props.mode !== "create" || harnessType !== "claude_local") return;
const resolvedRunner = runner === "auto" ? runnerAvailability?.defaultRunner : runner;
if (props.values.resolvedRunner !== resolvedRunner) props.onChange({ resolvedRunner });
}, [props.mode, harnessType, runner, runnerAvailability?.defaultRunner,
isCreate ? props.values.resolvedRunner : undefined, isCreate ? props.onChange : undefined]);
const getCapabilities = useAdapterCapabilities();
const adapterCaps = getCapabilities(isRunnerHarness ? harnessType : adapterType);
const isDotRunner = adapterType === "paperclip_runner" && (isCreate ? props.values.adapterSchemaValues?.provider : eff("adapterConfig", "provider", config.provider)) === "openai_dot";
+1
View File
@@ -1832,6 +1832,7 @@ function OnboardingWizardInner({
...defaultCreateValues,
adapterType,
runner,
resolvedRunner: runner === "auto" ? runnerDiscovery.data?.find(item => item.type === adapterType)?.defaultRunner : runner,
model:
adapterType === "gemini_local"
? model || DEFAULT_GEMINI_LOCAL_MODEL