feat: default Claude to the native runner on Apple Silicon

Carry Claude effort through versioned native admission and recovery. Preserve existing connections and saved runner choices, and price Opus 5.5 API receipts.

Co-Authored-By: Paperclip <noreply@paperclip.ing>
This commit is contained in:
DottaandPaperclip committed 2026-10-12 10:48:21 -05:00
1 parent 058af8f1b6
commit 6fd56c445d
77 files changed
+909 -296

No files matched your search

+46 -8
View File
@@ -1,9 +1,10 @@
import type { Db } from "@paperclipai/db";
import { agentHarnessType, isEnvironmentDriverSupportedForAdapter, paperclipRunnerSupportsPlatform, type AgentRunnerChoice, type AgentRunnerAvailability } from "@paperclipai/shared";
import { normalizeLegacyRunnerProvider, PAPERCLIP_RUNNER_DEFAULT_MODELS } from "@paperclipai/adapter-utils";
import { resolveClaudeModel } from "@paperclipai/adapter-claude-local";
import { findActiveServerAdapter, hasActiveAdapterOverride, waitForExternalAdapters } from "../adapters/registry.js";
import { getDisabledAdapterTypes } from "./adapter-plugin-store.js";
import { PaperclipRunnerProviderProfileError, resolvePaperclipRunnerNativeProviderInput } from "./native-runtime/provider-profile.js";
import { claudeRunnerUnsupportedSettings, PaperclipRunnerProviderProfileError, resolvePaperclipRunnerNativeProviderInput } from "./native-runtime/provider-profile.js";
import { forbidden, unprocessable } from "../errors.js";
import { runSshCommand } from "@paperclipai/adapter-utils/ssh";
import { resolveEnvironmentDriverConfigForRuntime } from "./environment-config.js";
@@ -16,14 +17,20 @@ export interface AgentRunnerSelection {
target?: RunnerTarget;
}
export function agentRunnerAvailability(harness: string, target: RunnerTarget = { driver: "local", platform: process.platform, architecture: process.arch }): AgentRunnerAvailability {
function runnerAdaptersAvailable(harness: string, driver: string): boolean {
const disabled = new Set(getDisabledAdapterTypes());
const supported = harness === "codex_local"
return ["codex_local", "claude_local"].includes(harness)
&& !disabled.has(harness) && !disabled.has("paperclip_runner")
&& !!findActiveServerAdapter(harness) && !!findActiveServerAdapter("paperclip_runner")
&& !hasActiveAdapterOverride(harness) && !hasActiveAdapterOverride("paperclip_runner")
&& isEnvironmentDriverSupportedForAdapter("paperclip_runner", target.driver)
&& (!target.platform || paperclipRunnerSupportsPlatform(harness, target.platform, target.architecture ?? ""));
&& isEnvironmentDriverSupportedForAdapter("paperclip_runner", driver);
}
export function agentRunnerAvailability(harness: string, target: RunnerTarget = { driver: "local", platform: process.platform, architecture: process.arch }): AgentRunnerAvailability {
const supported = runnerAdaptersAvailable(harness, target.driver)
&& (harness === "claude_local"
? !!target.platform && paperclipRunnerSupportsPlatform(harness, target.platform, target.architecture ?? "")
: !target.platform || paperclipRunnerSupportsPlatform(harness, target.platform, target.architecture ?? ""));
return { supportedRunners: supported ? ["paperclip", "legacy"] : ["legacy"], defaultRunner: supported ? "paperclip" : "legacy" };
}
@@ -31,19 +38,41 @@ function incompatible(fields: string[]): never {
throw unprocessable(`Paperclip Runner cannot honor these settings: ${fields.join(", ")}. Remove them or select Legacy runner in Advanced.`, { code: "agent_runner_config_incompatible", fields });
}
function resolveClaudeRunnerConfig(config: Record<string, unknown>): Record<string, unknown> {
const unsupported = claudeRunnerUnsupportedSettings(config);
const effort = config.modelReasoningEffort ?? config.reasoningEffort ?? config.effort;
if (config.model !== undefined && typeof config.model !== "string") unsupported.push("model");
if (unsupported.length) incompatible(unsupported);
const translated = { ...config };
for (const key of ["engine", "command", "mode", "nonInteractivePermissions", "dangerouslySkipPermissions", "chrome", "maxTurnsPerRun", "effort", "reasoningEffort"]) delete translated[key];
const env = config.env && typeof config.env === "object" ? config.env as Record<string, unknown> : {};
const model = resolveClaudeModel(config.model, env);
if (!model) incompatible(["model"]);
return { ...translated, ...(effort === undefined || effort === "" ? {} : { modelReasoningEffort: effort }), provider: "acpx", acpxAgent: "claude", model, acpxPermissionMode: config.acpxPermissionMode ?? "approve-all", lifecycleMode: config.lifecycleMode ?? "per_turn" };
}
/** Idempotent translation. Missing dependencies or credentials never select another runner. */
export function resolveNewAgentRunner(input: AgentRunnerSelection): { adapterType: string; adapterConfig: Record<string, unknown> } {
const adapterType = input.adapterType ?? "process";
let config = { ...input.adapterConfig };
const harness = agentHarnessType(adapterType, config);
const native = adapterType === "paperclip_runner";
if (adapterType === "codex_local" && getDisabledAdapterTypes().includes(adapterType)) throw unprocessable("Codex is disabled on this instance. Enable its adapter or choose another harness.", { code: "agent_runner_unavailable" });
if (["codex_local", "claude_local"].includes(adapterType) && getDisabledAdapterTypes().includes(adapterType)) throw unprocessable(`${adapterType === "claude_local" ? "Claude" : "Codex"} is disabled on this instance. Enable its adapter or choose another harness.`, { code: "agent_runner_unavailable" });
// Explicit saved native profiles (including historical provider omission) retain execution.
if (native && (input.runner === undefined || input.runner === "paperclip" || (harness !== "codex_local" && input.runner !== "legacy"))) return { adapterType, adapterConfig: normalizeLegacyRunnerProvider(config) };
const availability = agentRunnerAvailability(harness, input.target);
const choice = input.runner ?? "auto";
const useNative = choice === "paperclip" || (choice === "auto" && availability.defaultRunner === "paperclip");
if (!useNative) {
if (harness === "claude_local") {
if (!native) return { adapterType, adapterConfig: config };
if (config.acpxPermissionMode !== undefined && config.acpxPermissionMode !== "approve-all") incompatible(["acpxPermissionMode"]);
if (config.lifecycleMode !== undefined && config.lifecycleMode !== "per_turn") incompatible(["lifecycleMode"]);
const effort = config.modelReasoningEffort ?? config.reasoningEffort ?? config.effort;
for (const key of ["provider", "acpxAgent", "acpxPermissionMode", "lifecycleMode", "idleTimeoutMs", "modelReasoningEffort", "reasoningEffort"]) delete config[key];
if (effort !== undefined) config.effort = effort;
return { adapterType: "claude_local", adapterConfig: { ...config, dangerouslySkipPermissions: config.dangerouslySkipPermissions ?? true } };
}
if (!native && harness !== "codex_local") return { adapterType, adapterConfig: config };
if (!native && !["codexPermissionMode", "lifecycleMode", "idleTimeoutMs"].some(key => config[key] !== undefined)) return { adapterType, adapterConfig: config };
if (harness !== "codex_local") throw unprocessable("This native harness does not have a runner override in this release.", { code: "agent_runner_unavailable" });
@@ -55,6 +84,15 @@ export function resolveNewAgentRunner(input: AgentRunnerSelection): { adapterTyp
if (native && harness !== "codex_local" && choice === "paperclip") return { adapterType, adapterConfig: normalizeLegacyRunnerProvider(config) };
if (availability.defaultRunner !== "paperclip") throw unprocessable("Paperclip Runner is unavailable for this harness and environment. Select Legacy runner in Advanced.", { code: "agent_runner_unavailable" });
if (native) return { adapterType, adapterConfig: normalizeLegacyRunnerProvider(config) };
if (harness === "claude_local") {
config = resolveClaudeRunnerConfig(config);
try { resolvePaperclipRunnerNativeProviderInput({ backend: "acpx_runtime", adapterConfig: config }); }
catch (error) {
if (error instanceof PaperclipRunnerProviderProfileError) throw unprocessable(error.message, { code: error.code });
throw error;
}
return { adapterType: "paperclip_runner", adapterConfig: config };
}
const unsupported = ["agentCommand", "stateDir", "warmHandleIdleMs", "extraArgs", "args", "search", "fastMode", "variant", "filesystemScope", "filesystemExtraPaths", "filesystemSandboxCommand", "networkScope", "networkAllowlist", "permissionMode", "maxTurns", "disableWebSearch", "outputInactivityTimeoutMs", "terminalResultCleanupGraceMs"]
.filter(key => config[key] !== undefined && config[key] !== false && config[key] !== "" && config[key] !== 0 && !(Array.isArray(config[key]) && config[key].length === 0));
if (config.command && config.command !== "codex") unsupported.push("command");
@@ -78,7 +116,7 @@ export function resolveNewAgentRunner(input: AgentRunnerSelection): { adapterTyp
/** Use the execution environment selected by dispatch, without starting a provider. */
export async function resolveNewAgentRunnerForCompany(db: Db, companyId: string, input: AgentRunnerSelection & { defaultEnvironmentId?: string | null }) {
if (agentHarnessType(input.adapterType ?? "process", input.adapterConfig) !== "codex_local"
if (!["codex_local", "claude_local"].includes(agentHarnessType(input.adapterType ?? "process", input.adapterConfig))
|| input.runner === "legacy" || (input.adapterType === "paperclip_runner" && (input.runner === undefined || input.runner === "paperclip"))) return resolveNewAgentRunner(input);
const target = await resolveAgentRunnerTargetForCompany(db, companyId, input.defaultEnvironmentId);
return resolveNewAgentRunner({ ...input, target });
@@ -111,7 +149,7 @@ export async function resolveAgentRunnerTargetForCompany(db: Db, companyId: stri
const driver = environment?.driver ?? "local";
let target: RunnerTarget = driver === "local" ? { driver, platform: process.platform, architecture: process.arch }
: { driver }; // Sandbox platform/artifact identity is verified by selected-runtime setup; do not guess an image's architecture.
if (driver === "ssh" && environment && agentRunnerAvailability("codex_local", target).defaultRunner === "paperclip") {
if (driver === "ssh" && environment && ["codex_local", "claude_local"].some(harness => runnerAdaptersAvailable(harness, driver))) {
const parsed = await resolveEnvironmentDriverConfigForRuntime(db, companyId, environment);
if (parsed.driver !== "ssh") throw unprocessable("The selected SSH environment is unavailable.");
let output: string;