Merge Claude Auto effort and implicit harness fixes

Co-Authored-By: Paperclip <noreply@paperclip.ing>

* codex/claude-runner-default:
  fix(claude): honor Auto effort and implicit native harness settings
This commit is contained in:
DottaandPaperclip committed 2026-10-12 13:13:05 -05:00
commit 3fc2af5511
5 files changed
+21 -8

No files matched your search

@@ -220,6 +220,10 @@ describe("buildPaperclipRunnerConfig", () => {
});
});
it.each(["modelReasoningEffort", "reasoningEffort", "effort"])("keeps explicit Auto Claude effort cleared over saved %s", key => {
const config = buildPaperclipRunnerConfig(makeValues({ thinkingEffort: "", adapterSchemaValues: { provider: "acpx", acpxAgent: "claude", [key]: "high" } }));
for (const effortKey of ["modelReasoningEffort", "reasoningEffort", "effort"]) expect(config).not.toHaveProperty(effortKey);
});
it.each(["claude-opus-5", "my-custom-model"])("preserves the selected ACPX Claude model %s", (model) => {
expect(buildPaperclipRunnerConfig(makeValues({ model, adapterSchemaValues: { provider: "acpx" } })))
.toMatchObject({ provider: "acpx", acpxAgent: "claude", model });
@@ -76,7 +76,9 @@ export function buildCodexLocalConfig(v: CreateConfigValues): Record<string, unk
export function buildPaperclipRunnerConfig(v: CreateConfigValues): Record<string, unknown> {
const config = buildCodexLocalConfig(v);
const schemaValues = normalizeLegacyRunnerProvider({ ...(v.adapterSchemaValues ?? {}) });
const configuredEffort = v.thinkingEffort || schemaValues.modelReasoningEffort || schemaValues.reasoningEffort || schemaValues.effort;
const configuredEffort = typeof v.thinkingEffort === "string"
? v.thinkingEffort.trim()
: schemaValues.modelReasoningEffort || schemaValues.reasoningEffort || schemaValues.effort;
for (const unsupportedKey of [
"engine",
"agentCommand",
@@ -228,6 +230,12 @@ export function buildPaperclipRunnerConfig(v: CreateConfigValues): Record<string
]) {
delete schemaValues[normalizedKey];
}
if (provider === "acpx" && acpxAgent === "claude") {
delete config.reasoningEffort;
delete config.effort;
delete schemaValues.reasoningEffort;
delete schemaValues.effort;
}
return {
...config,
...schemaValues,
@@ -91,8 +91,8 @@ describe("Paperclip Runner native provider configuration", () => {
.toMatchObject({ provider: "codex", model: "gpt-6-astra", modelReasoningEffort: "high" });
});
it("carries Claude task effort while retaining provider and permissions", () => {
const base = { provider: "acpx", acpxAgent: "claude", model: "claude-opus-5-5", modelReasoningEffort: "medium", acpxPermissionMode: "deny-all" };
it.each([true, false])("carries Claude task effort while retaining provider and permissions (explicit agent: %s)", explicitAgent => {
const base = { provider: "acpx", ...(explicitAgent ? { acpxAgent: "claude" } : {}), model: "claude-opus-5-5", modelReasoningEffort: "medium", acpxPermissionMode: "deny-all" };
const projected = projectPaperclipRunnerTaskConfig("acpx_runtime", base, { effort: "xhigh", provider: "codex", acpxAgent: "pi", acpxPermissionMode: "approve-all" });
expect(projected).toEqual({ ...base, modelReasoningEffort: "xhigh" });
expect(resolvePaperclipRunnerNativeProviderInput({ backend: "acpx_runtime", adapterConfig: projected })).toMatchObject({ provider: "acpx", acpxAgent: "claude", claudeEffort: "xhigh", acpxPermissionMode: "deny-all" });
@@ -18,7 +18,7 @@ import type { agentInstructionWorkingCopyService } from "../agent-instruction-wo
import { createHash, randomUUID } from "node:crypto";
import { and, eq, inArray, sql } from "drizzle-orm";
import type { Db } from "@paperclipai/db";
import { CHAT_PROVIDERS } from "@paperclipai/shared";
import { agentHarnessType, CHAT_PROVIDERS } from "@paperclipai/shared";
import {
type agents,
completionContracts,
@@ -696,7 +696,7 @@ export async function selectHeartbeatRuntime(db: Db, input: HeartbeatRuntimeSele
});
getNativeFreshSessionHandoff = nativeReviewRequest ? undefined : getFreshSessionHandoff;
const nativeProviderConfig = nativeRuntimeResolution.profile.backend === "codex_app_server"
|| (nativeRuntimeResolution.profile.backend === "acpx_runtime" && parseObject(agent.adapterConfig).provider === "acpx" && parseObject(agent.adapterConfig).acpxAgent === "claude")
|| (nativeRuntimeResolution.profile.backend === "acpx_runtime" && agentHarnessType(agent.adapterType, parseObject(agent.adapterConfig)) === "claude_local")
|| nativeRuntimeResolution.profile.backend === "opencode_server"
? projectPaperclipRunnerTaskConfig(
nativeRuntimeResolution.profile.backend,
@@ -1,3 +1,4 @@
import { agentHarnessType } from "@paperclipai/shared";
import { resolveClaudeEffort, resolveQualifiedAcpxProfile } from "../../vendor/paperclip-runner/index.js";
import {
isPaperclipRunnerProvider,
@@ -179,7 +180,7 @@ export function projectPaperclipRunnerTaskConfig(
// The server resolves a connection's model namespace after task overrides.
// Carry that model into durable input without allowing routing to change the harness.
const model = optionalString(managedModel) ?? optionalString(task.model);
const supportsEffort = backend === "codex_app_server" || (backend === "acpx_runtime" && base.acpxAgent === "claude");
const supportsEffort = backend === "codex_app_server" || (backend === "acpx_runtime" && agentHarnessType("paperclip_runner", base) === "claude_local");
const effortKey = supportsEffort
? ["modelReasoningEffort", "reasoningEffort", "effort"].find((key) => key in task)
: undefined;
@@ -389,7 +390,7 @@ export function resolvePaperclipRunnerProviderProfile(
const bindingId = validatePaperclipRunnerDotConfig(config)!;
return { provider: "openai_dot", backend: "openai_dot_mcp", model: null, dotBindingId: bindingId };
}
if (candidate === "acpx" && config.acpxAgent === "claude") {
if (candidate === "acpx" && agentHarnessType("paperclip_runner", config) === "claude_local") {
const unsupported = claudeRunnerUnsupportedSettings(config);
if (unsupported.length) throw new PaperclipRunnerProviderProfileError("agent_runner_config_incompatible",
`Paperclip Runner cannot honor these settings: ${unsupported.join(", ")}. Remove them or select Legacy runner in Advanced.`);
@@ -403,7 +404,7 @@ export function resolvePaperclipRunnerProviderProfile(
error instanceof Error ? error.message : "Invalid Cursor session mode",
);
}
try { if (candidate === "acpx" && config.acpxAgent === "claude") resolveClaudeEffort("claude", config.modelReasoningEffort ?? config.reasoningEffort ?? config.effort); } catch (error) {
try { if (candidate === "acpx" && agentHarnessType("paperclip_runner", config) === "claude_local") resolveClaudeEffort("claude", config.modelReasoningEffort ?? config.reasoningEffort ?? config.effort); } catch (error) {
throw new PaperclipRunnerProviderProfileError("paperclip_runner_claude_effort_invalid", error instanceof Error ? error.message : "Invalid Claude effort");
}
try { resolvePaperclipRunnerPiThinkingLevel(candidate, config.acpxAgent, config.piThinkingLevel); } catch (error) {