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

@@ -849,7 +849,7 @@ describe("AgentConfigForm environment selector", () => {
it.each([
["Codex", "codex", undefined, DEFAULT_CODEX_LOCAL_MODEL],
["ACP agents", "acpx", "claude", "claude-sonnet-5"],
["ACP agents", "acpx", "claude", "claude-opus-5-5"],
["Claude Managed", "claude_managed", undefined, "claude-sonnet-5"],
])("saves the %s harness default without the previous OpenCode model prefix", async (label, provider, acpxAgent, model) => {
const accountList = vi.spyOn(aiConnectionsApi, "list").mockResolvedValue({
@@ -996,8 +996,8 @@ describe("AgentConfigForm environment selector", () => {
roots.push(existing.root);
const created = await renderCreateForm(environments, { adapterType: "claude_local", model: "" });
roots.push(created.root);
expect(existing.container.textContent).toContain("Default (claude-opus-5)");
expect(created.container.textContent).toContain("Default (claude-opus-5)");
expect(existing.container.textContent).toContain("Default (claude-opus-5-5)");
expect(created.container.textContent).toContain("Default (claude-opus-5-5)");
expect(existing.onSave).not.toHaveBeenCalled();
});
+32 -29
View File
@@ -1,7 +1,7 @@
import { useConnectionModels } from "./ai-connections/useConnectionModels";
import { agentHarnessType, agentRunner, type AgentRunnerChoice } from "@paperclipai/shared";
import { adaptersApi } from "../api/adapters";
import { CodexRunnerSelect } from "./CodexRunnerSelect";
import { AgentRunnerSelect } from "./AgentRunnerSelect";
import { aiRoutingHarness } from "@paperclipai/shared";
import { AiConnectionField } from "./ai-connections/AiConnectionField";
import { aiConnectionBindingSchema, aiRuntimeConnectionBindingSchema } from "@paperclipai/shared";
@@ -609,6 +609,7 @@ export function AgentConfigForm(props: AgentConfigFormProps) {
const effectiveConfig = isCreate ? props.values.adapterSchemaValues ?? {} : { ...config, ...overlay.adapterConfig };
const harnessType = agentHarnessType(adapterType, effectiveConfig);
const isCodexHarness = harnessType === "codex_local";
const isRunnerHarness = isCodexHarness || harnessType === "claude_local";
const rawCurrentDefaultEnvironmentId = isCreate
? props.values.defaultEnvironmentId ?? ""
: eff("identity", "defaultEnvironmentId", props.agent.defaultEnvironmentId ?? "");
@@ -622,13 +623,13 @@ export function AgentConfigForm(props: AgentConfigFormProps) {
queryFn: () => adaptersApi.list({ companyId: selectedCompanyId!, environmentId: rawCurrentDefaultEnvironmentId || null }),
enabled: Boolean(selectedCompanyId),
});
const codexRunnerAvailability = runnerDiscovery.data?.find(item => item.type === "codex_local");
const runnerAvailability = runnerDiscovery.data?.find(item => item.type === harnessType);
const runner: AgentRunnerChoice = isCreate ? props.values.runner ?? "auto" : overlay.runner ?? agentRunner(adapterType);
const fieldsAdapterType = isCodexHarness
? (runner === "paperclip" || (runner === "auto" && codexRunnerAvailability?.defaultRunner === "paperclip") ? "paperclip_runner" : "codex_local")
const fieldsAdapterType = isRunnerHarness
? (runner === "paperclip" || (runner === "auto" && runnerAvailability?.defaultRunner === "paperclip") ? "paperclip_runner" : harnessType)
: adapterType;
const getCapabilities = useAdapterCapabilities();
const adapterCaps = getCapabilities(isCodexHarness ? harnessType : adapterType);
const adapterCaps = getCapabilities(isRunnerHarness ? harnessType : adapterType);
const isDotRunner = adapterType === "paperclip_runner" && (isCreate ? props.values.adapterSchemaValues?.provider : eff("adapterConfig", "provider", config.provider)) === "openai_dot";
const isLocal = !isDotRunner && (adapterCaps.supportsInstructionsBundle || adapterCaps.supportsSkills || adapterCaps.supportsLocalAgentJwt);
@@ -918,7 +919,7 @@ export function AgentConfigForm(props: AgentConfigFormProps) {
).data?.provider === "openrouter" ? "openrouter" : runnerProvider;
const connectionModels = useConnectionModels(selectedCompanyId, isCreate ? undefined : aiConnectionBindingSchema.safeParse((overlay.runtime.runtimeConfig as Record<string, unknown> | undefined)?.aiConnection ?? runtimeConfig.aiConnection).data, aiRoutingHarness(adapterType, runnerProvider, eff("adapterConfig", "acpxAgent", config.acpxAgent)));
// Fetch adapter models for the effective provider, including unsaved changes.
const modelAdapterType = isCodexHarness ? harnessType : adapterType;
const modelAdapterType = isRunnerHarness ? harnessType : adapterType;
const modelQueryKey = selectedCompanyId
? queryKeys.agents.adapterModels(selectedCompanyId, modelAdapterType, currentDefaultEnvironmentId || null, modelProvider)
: ["agents", "none", "adapter-models", adapterType];
@@ -970,10 +971,10 @@ export function AgentConfigForm(props: AgentConfigFormProps) {
mode,
isCreate,
adapterType: fieldsAdapterType,
hideRunnerHarness: isCodexHarness,
values: isCreate ? props.values : null,
hideRunnerHarness: isRunnerHarness,
values: isCreate ? { ...props.values, ...(harnessType === "claude_local" && fieldsAdapterType === "paperclip_runner" ? { adapterSchemaValues: { ...props.values.adapterSchemaValues, provider: "acpx", acpxAgent: "claude" } } : {}) } : null,
set: isCreate ? (patch: Partial<CreateConfigValues>) => props.onChange(patch) : null,
config,
config: harnessType === "claude_local" && fieldsAdapterType === "paperclip_runner" ? { ...config, provider: "acpx", acpxAgent: "claude" } : config,
eff: eff as <T>(group: "adapterConfig", field: string, original: T) => T,
// Harness transitions supply the new harness's default model. Resolve
// user-selected model IDs in ModelDropdown, not through the previous
@@ -1104,9 +1105,9 @@ export function AgentConfigForm(props: AgentConfigFormProps) {
: adapterConfig.provider === "acpx" && adapterConfig.acpxAgent === "claude" ? "claude_local"
: adapterType
: adapterType;
return testAgentSetup({ companyId: selectedCompanyId, adapterType, providerAdapter, adapterConfig, agentId, aiConnection, environmentId, ...(isCodexHarness && (isCreate ? runner !== "auto" : overlay.runner !== undefined) ? { runner } : {}) });
return testAgentSetup({ companyId: selectedCompanyId, adapterType, providerAdapter, adapterConfig, agentId, aiConnection, environmentId, ...(isRunnerHarness && (isCreate ? runner !== "auto" : overlay.runner !== undefined) ? { runner } : {}) });
}
return agentsApi.testEnvironment(selectedCompanyId, adapterType, { adapterConfig, agentId, aiConnection, environmentId, ...(isCodexHarness && (isCreate ? runner !== "auto" : overlay.runner !== undefined) ? { runner } : {}) });
return agentsApi.testEnvironment(selectedCompanyId, adapterType, { adapterConfig, agentId, aiConnection, environmentId, ...(isRunnerHarness && (isCreate ? runner !== "auto" : overlay.runner !== undefined) ? { runner } : {}) });
},
});
const [testActionPending, setTestActionPending] = useState(false);
@@ -1153,7 +1154,7 @@ export function AgentConfigForm(props: AgentConfigFormProps) {
resetTestEnvironmentRef.current();
setTestActionError(null);
clearClaudeLoginClaimRef.current();
}, [adapterType, runner, effectiveLoginEnvironmentId, runner === "auto" ? codexRunnerAvailability?.defaultRunner : undefined]);
}, [adapterType, runner, effectiveLoginEnvironmentId, runner === "auto" ? runnerAvailability?.defaultRunner : undefined]);
// Show the login affordance only for a current sandbox adapter that declares a
// login capability, and whose most recent Test result carries the canonical
@@ -1164,7 +1165,7 @@ export function AgentConfigForm(props: AgentConfigFormProps) {
const adapterSupportsSandboxLogin = adapterCaps.login != null;
const testResult = testEnvironment.data;
const testResultSupportsSandboxLogin =
testResult != null && getCapabilities(isCodexHarness ? harnessType : testResult.adapterType).login != null;
testResult != null && getCapabilities(isRunnerHarness ? harnessType : testResult.adapterType).login != null;
const authMissingCheck =
testResult && testResultSupportsSandboxLogin
? testResult.checks.find((check) => check.code === ADAPTER_AUTH_MISSING_CHECK_CODE) ?? null
@@ -1259,7 +1260,7 @@ export function AgentConfigForm(props: AgentConfigFormProps) {
// environment id, so both are present here.
login:
showAdapterLogin && selectedCompanyId && effectiveLoginEnvironmentId
? { companyId: selectedCompanyId, adapterType: isCodexHarness ? harnessType : adapterType, environmentId: effectiveLoginEnvironmentId }
? { companyId: selectedCompanyId, adapterType: isRunnerHarness ? harnessType : adapterType, environmentId: effectiveLoginEnvironmentId }
: null,
});
return () => {
@@ -1297,7 +1298,7 @@ export function AgentConfigForm(props: AgentConfigFormProps) {
}
const thinkingEffortKey =
isCodexHarness
isCodexHarness || (adapterType === "paperclip_runner" && harnessType === "claude_local")
? "modelReasoningEffort"
: adapterType === "cursor"
? "mode"
@@ -1319,19 +1320,19 @@ export function AgentConfigForm(props: AgentConfigFormProps) {
? kimiThinkingEffortOptions
: adapterType === "pi_local"
? [{ id: "", label: "Auto" }, ...["off", "minimal", "low", "medium", "high", "xhigh"].map(id => ({ id, label: id }))]
: adapterType === "claude_local" || adapterType === "grok_local"
? [{ id: "", label: "Auto" }, ...setupEfforts(adapterType, currentModelId).map((id) => ({
: harnessType === "claude_local" || adapterType === "grok_local"
? [{ id: "", label: "Auto" }, ...setupEfforts(harnessType, currentModelId).map((id) => ({
id,
label: id === "xhigh" ? "X-High" : id[0].toUpperCase() + id.slice(1),
}))]
: claudeThinkingEffortOptions;
const currentThinkingEffort = isCreate
? val!.thinkingEffort
: isCodexHarness
: isCodexHarness || (adapterType === "paperclip_runner" && harnessType === "claude_local")
? eff(
"adapterConfig",
thinkingEffortKey,
String(config[thinkingEffortKey] ?? config.reasoningEffort ?? config.modelReasoningEffort ?? ""),
String(config[thinkingEffortKey] ?? config.reasoningEffort ?? config.modelReasoningEffort ?? config.effort ?? ""),
)
: adapterType === "cursor"
? eff("adapterConfig", "mode", String(config.mode ?? ""))
@@ -1340,7 +1341,7 @@ export function AgentConfigForm(props: AgentConfigFormProps) {
: eff("adapterConfig", thinkingEffortKey, String(config[thinkingEffortKey] ?? ""));
const showThinkingEffort = adapterType !== "gemini_local"
&& adapterType !== "cursor_cloud"
&& (adapterType !== "paperclip_runner" || isCodexHarness);
&& (adapterType !== "paperclip_runner" || isRunnerHarness);
const codexSearchEnabled = adapterType === "codex_local"
? (isCreate ? Boolean(val!.search) : eff("adapterConfig", "search", Boolean(config.search)))
: false;
@@ -1628,7 +1629,7 @@ export function AgentConfigForm(props: AgentConfigFormProps) {
{showAdapterTypeField && (
<Field label="Harness" hint={help.adapterType}>
<AdapterTypeDropdown
value={isDotRunner ? "openai_dot" : isCodexHarness ? harnessType : adapterType}
value={isDotRunner ? "openai_dot" : isRunnerHarness ? harnessType : adapterType}
disabledTypes={adapterPickerDisabledTypes}
openAiDotEnabled={experimentalSettings?.enableOpenAiDot === true}
nativeRunnerEnabled={experimentalSettings?.enableNativeRunner === true}
@@ -1664,7 +1665,7 @@ export function AgentConfigForm(props: AgentConfigFormProps) {
setOverlay((prev) => ({
...prev,
adapterType: t,
runner: t === "codex_local" ? "auto" : undefined,
runner: ["codex_local", "claude_local"].includes(t) ? "auto" : undefined,
adapterConfig: {
model:
t === "gemini_local"
@@ -1720,7 +1721,7 @@ export function AgentConfigForm(props: AgentConfigFormProps) {
<AdapterLoginPanel
key={`${adapterType}:${effectiveLoginEnvironmentId}`}
companyId={selectedCompanyId!}
adapterType={isCodexHarness ? harnessType : adapterType}
adapterType={isRunnerHarness ? harnessType : adapterType}
environmentId={effectiveLoginEnvironmentId!}
onStored={isCreate ? handleClaudeLoginStored : handleClaudeLoginStoredEdit}
onApplyStored={
@@ -1762,8 +1763,8 @@ export function AgentConfigForm(props: AgentConfigFormProps) {
loadingModels={connectionModels?.isLoading ?? fetchingModels}
value={currentModelId}
onChange={(v) => {
const supportedEfforts = setupEfforts(isCodexHarness ? harnessType : adapterType, v);
const clearUnsupportedEffort = ["codex_local", "claude_local", "grok_local"].includes(isCodexHarness ? harnessType : adapterType)
const supportedEfforts = setupEfforts(isRunnerHarness ? harnessType : adapterType, v);
const clearUnsupportedEffort = ["codex_local", "claude_local", "grok_local"].includes(isRunnerHarness ? harnessType : adapterType)
&& Boolean(currentThinkingEffort)
&& !supportedEfforts.includes(String(currentThinkingEffort));
if (isCreate) {
@@ -1831,7 +1832,9 @@ export function AgentConfigForm(props: AgentConfigFormProps) {
? set!({ thinkingEffort: v })
: (() => {
mark("adapterConfig", thinkingEffortKey, v || undefined);
if (isCodexHarness) mark("adapterConfig", thinkingEffortKey === "reasoningEffort" ? "modelReasoningEffort" : "reasoningEffort", undefined);
if (isRunnerHarness) {
for (const key of ["modelReasoningEffort", "reasoningEffort", "effort"]) if (key !== thinkingEffortKey) mark("adapterConfig", key, undefined);
}
})()
}
open={thinkingEffortOpen}
@@ -1893,10 +1896,10 @@ export function AgentConfigForm(props: AgentConfigFormProps) {
onToggle={() => setConfigurationAdvancedOpen(!configurationAdvancedOpen)}
>
<div className="space-y-3">
{isCodexHarness && <CodexRunnerSelect value={runner}
{isRunnerHarness && <AgentRunnerSelect value={runner}
pending={runnerDiscovery.isFetching} error={runnerDiscovery.error?.message} onRetry={() => { void runnerDiscovery.refetch(); }}
defaultRunner={codexRunnerAvailability?.defaultRunner}
supportedRunners={codexRunnerAvailability?.supportedRunners}
defaultRunner={runnerAvailability?.defaultRunner}
supportedRunners={runnerAvailability?.supportedRunners}
onChange={next => isCreate ? set!({ runner: next }) : setOverlay(prev => ({ ...prev, runner: next }))} />}
{isLocal && (<> {/*
The command names a binary on the execution host, so the
@@ -4,7 +4,7 @@ import { SelectPopover } from "./ui/select";
import { Button } from "./ui/button";
/** The override is request-level. Automatic selection is resolved by the server. */
export function CodexRunnerSelect({
export function AgentRunnerSelect({
value,
onChange,
defaultRunner,
@@ -1,7 +1,7 @@
import { useMemo, useState } from "react";
import { agentHarnessType, agentRunner, type AgentRunnerChoice } from "@paperclipai/shared";
import { adaptersApi } from "@/api/adapters";
import { CodexRunnerSelect } from "./CodexRunnerSelect";
import { AgentRunnerSelect } from "./AgentRunnerSelect";
import { useMutation, useQuery, useQueryClient } from "@tanstack/react-query";
import {
@@ -68,12 +68,12 @@ export function ConfigureBuiltInAgentModal({
const { definition } = state;
const [adapterType, setAdapterType] = useState<string>(
() => state.agent && agentHarnessType(state.agent.adapterType, state.agent.adapterConfig) === "codex_local" ? "codex_local" : state.agent?.adapterType ?? defaultAdapterType(state),
() => state.agent && ["codex_local", "claude_local"].includes(agentHarnessType(state.agent.adapterType, state.agent.adapterConfig)) ? agentHarnessType(state.agent.adapterType, state.agent.adapterConfig) : state.agent?.adapterType ?? defaultAdapterType(state),
);
const [runner, setRunner] = useState<AgentRunnerChoice | undefined>();
const environmentId = state.agent?.defaultEnvironmentId ?? null;
const adapters = useQuery({ queryKey: queryKeys.adapters.availability(companyId, environmentId), queryFn: () => adaptersApi.list({ companyId, environmentId }), enabled: open });
const codexAvailability = adapters.data?.find(item => item.type === "codex_local");
const runnerAvailability = adapters.data?.find(item => item.type === adapterType);
const [model, setModel] = useState<string>(() => {
const config = state.agent?.adapterConfig;
const configuredModel = typeof config === "object" && config !== null
@@ -145,7 +145,7 @@ export function ConfigureBuiltInAgentModal({
: { adapterType, adapterConfig };
const result = await builtInAgentsApi.provision(companyId, definition.key, {
...adapterInput,
...(adapterType === "codex_local" && runner !== undefined ? { runner } : {}),
...(["codex_local", "claude_local"].includes(adapterType) && runner !== undefined ? { runner } : {}),
...(budgetMonthlyCents !== undefined ? { budgetMonthlyCents } : {}),
});
return result;
@@ -191,11 +191,11 @@ export function ConfigureBuiltInAgentModal({
/>
</Field>
{adapterType === "codex_local" && <details className="space-y-3">
{["codex_local", "claude_local"].includes(adapterType) && <details className="space-y-3">
<summary className="cursor-pointer text-sm text-muted-foreground">Advanced</summary>
<CodexRunnerSelect value={runner ?? (state.agent ? agentRunner(state.agent.adapterType) : "auto")}
<AgentRunnerSelect value={runner ?? (state.agent ? agentRunner(state.agent.adapterType) : "auto")}
pending={adapters.isFetching} error={adapters.error?.message} onRetry={() => { void adapters.refetch(); }}
defaultRunner={codexAvailability?.defaultRunner} supportedRunners={codexAvailability?.supportedRunners}
defaultRunner={runnerAvailability?.defaultRunner} supportedRunners={runnerAvailability?.supportedRunners}
onChange={setRunner} />
</details>}
+10 -10
View File
@@ -1,7 +1,7 @@
import type { AgentRunnerChoice } from "@paperclipai/shared";
import { agentHarnessType } from "@paperclipai/shared";
import { adaptersApi } from "../api/adapters";
import { CodexRunnerSelect } from "./CodexRunnerSelect";
import { AgentRunnerSelect } from "./AgentRunnerSelect";
import { healthApi } from "@/api/health";
import { LocalProviderLoginInstructions } from "./AdapterLoginChrome";
import { useLocalAiLogin } from "./ai-connections/useLocalAiLogin";
@@ -600,7 +600,7 @@ function OnboardingWizardInner({
restoreOnboardingAdapterType(saved?.adapterType, saved?.runnerProvider),
);
const [runner, setRunner] = useState<AgentRunnerChoice>(() => saved?.runner === "legacy" || saved?.runner === "paperclip"
? saved.runner : saved?.adapterType === "paperclip_runner" && (saved?.runnerProvider === undefined || saved?.runnerProvider === "codex") ? "paperclip" : "auto");
? saved.runner : saved?.adapterType === "paperclip_runner" && (saved?.runnerProvider === undefined || ["codex", "claude", "acpx"].includes(String(saved?.runnerProvider))) ? "paperclip" : "auto");
/**
* Whether a model source has been chosen, as opposed to which one
* `adapterType` happens to hold.
@@ -629,7 +629,7 @@ function OnboardingWizardInner({
// Recover the harness and runner separately. Host command overrides from an
// old native draft cannot describe the provider; portable model/cwd survive.
const [model, setModel] = useState(
savedNativeRunnerDraft && saved?.runnerProvider !== undefined && saved.runnerProvider !== "codex" ? "" : (saved?.model as string) ?? "",
savedNativeRunnerDraft && saved?.runnerProvider !== undefined && !["codex", "claude", "acpx"].includes(String(saved.runnerProvider)) ? "" : (saved?.model as string) ?? "",
);
const [command, setCommand] = useState(
savedNativeRunnerDraft ? "" : (saved?.command as string) ?? "",
@@ -1550,7 +1550,7 @@ function OnboardingWizardInner({
setAdapterEnvResult(null);
adapterEnvResultAppliedStoredLoginRef.current = false;
setAdapterEnvError(null);
}, [step, adapterType, runner, model, command, args, url, credentialMode, apiKey, selectedSavedKey, selectedApiKey?.id, savedSubscription?.id, createdCompanyId, resolvedLoginEnvironmentId, runner === "auto" ? runnerDiscovery.data?.find(item => item.type === "codex_local")?.defaultRunner : undefined]);
}, [step, adapterType, runner, model, command, args, url, credentialMode, apiKey, selectedSavedKey, selectedApiKey?.id, savedSubscription?.id, createdCompanyId, resolvedLoginEnvironmentId, runner === "auto" ? runnerDiscovery.data?.find(item => item.type === adapterType)?.defaultRunner : undefined]);
/**
* Leaving the step puts the row back to a question.
@@ -1963,7 +1963,7 @@ function OnboardingWizardInner({
adapterType,
{
adapterConfig: adapterConfigOverride ?? buildAdapterConfig(),
...(adapterType === "codex_local" && runner !== "auto" ? { runner } : {}),
...(["codex_local", "claude_local"].includes(adapterType) && runner !== "auto" ? { runner } : {}),
...(managedBindingForStep() ? { aiConnection: managedBindingForStep() } : {}),
environmentId,
}
@@ -2212,7 +2212,7 @@ function OnboardingWizardInner({
role: agentRole,
adapterType,
adapterConfig: hireAdapterConfig,
...(adapterType === "codex_local" && runner !== "auto" ? { runner } : {}),
...(["codex_local", "claude_local"].includes(adapterType) && runner !== "auto" ? { runner } : {}),
...(shouldApplyStoredClaudeLogin ? { applyStoredClaudeLogin: true } : {}),
// The server owns what the first agent is told now: this marker seeds
// the chief-of-staff persona over the agent's entry instruction file.
@@ -2908,12 +2908,12 @@ function OnboardingWizardInner({
) : null}
</motion.div>
{sourceSelected && adapterType === "codex_local" && <details className="space-y-3">
{sourceSelected && ["codex_local", "claude_local"].includes(adapterType) && <details className="space-y-3">
<summary className="cursor-pointer text-sm text-muted-foreground">Advanced</summary>
<CodexRunnerSelect value={runner} disabled={loading || adapterEnvLoading}
<AgentRunnerSelect value={runner} disabled={loading || adapterEnvLoading}
pending={runnerDiscovery.isFetching} error={runnerDiscovery.error?.message} onRetry={() => { void runnerDiscovery.refetch(); }}
defaultRunner={runnerDiscovery.data?.find(item => item.type === "codex_local")?.defaultRunner}
supportedRunners={runnerDiscovery.data?.find(item => item.type === "codex_local")?.supportedRunners}
defaultRunner={runnerDiscovery.data?.find(item => item.type === adapterType)?.defaultRunner}
supportedRunners={runnerDiscovery.data?.find(item => item.type === adapterType)?.supportedRunners}
onChange={setRunner} />
</details>}
@@ -225,8 +225,6 @@ export function AgentBasicsDialog({
placeholder="Choose a native harness…"
onValueChange={provider => { setRunnerProvider(provider); setAdapterType("paperclip_runner"); }}
options={[
{ value: "codex", label: "Codex (Paperclip Runner)" },
{ value: "claude", label: "Claude Code (Paperclip Runner)" },
{ value: "grok", label: "Grok Build (Paperclip Runner)" },
{ value: "opencode", label: "OpenCode (Paperclip Runner)" },
]} />
+22 -22
View File
@@ -4,7 +4,7 @@ import { useAgentAppearanceDraft } from "../../hooks/useAgentAppearanceDraft";
import { AiConnectionField, aiProviderForAdapter } from "../ai-connections/AiConnectionField";
import type { AiConnectionBinding } from "@paperclipai/shared";
import type { AgentRunnerChoice } from "@paperclipai/shared";
import { CodexRunnerSelect } from "../CodexRunnerSelect";
import { AgentRunnerSelect } from "../AgentRunnerSelect";
import { SelectPopover } from "../ui/select";
import { DEFAULT_CODEX_LOCAL_MODEL } from "@paperclipai/adapter-codex-local";
import {
@@ -90,9 +90,9 @@ export function NewAgentSetup() {
key={`${selectedCompanyId}:${params.get("name")}:${params.get("adapterType")}:${params.get("runnerProvider")}:${params.get("runner")}`}
companyId={selectedCompanyId}
name={params.get("name") ?? ""}
adapterType={params.get("adapterType") === "paperclip_runner" && (params.get("runnerProvider") ?? "codex") === "codex" ? "codex_local" : params.get("adapterType") ?? ""}
initialRunner={params.get("runner") ?? (params.get("adapterType") === "paperclip_runner" && (params.get("runnerProvider") ?? "codex") === "codex" ? "paperclip" : "auto")}
preserveNativeCodex={params.get("adapterType") === "paperclip_runner" && (params.get("runnerProvider") ?? "codex") === "codex"}
adapterType={params.get("adapterType") === "paperclip_runner" && ["codex", "claude"].includes(params.get("runnerProvider") ?? "codex") ? params.get("runnerProvider") === "claude" ? "claude_local" : "codex_local" : params.get("adapterType") ?? ""}
initialRunner={params.get("runner") ?? (params.get("adapterType") === "paperclip_runner" && ["codex", "claude"].includes(params.get("runnerProvider") ?? "codex") ? "paperclip" : "auto")}
preserveNativeHarness={params.get("adapterType") === "paperclip_runner" && ["codex", "claude"].includes(params.get("runnerProvider") ?? "codex")}
runnerProvider={params.get("runnerProvider") ?? "codex"}
createdAgentId={params.get("createdAgentId")}
/>
@@ -105,7 +105,7 @@ function Setup({
adapterType,
runnerProvider,
initialRunner,
preserveNativeCodex,
preserveNativeHarness,
createdAgentId,
}: {
companyId: string;
@@ -113,7 +113,7 @@ function Setup({
adapterType: string;
runnerProvider: string;
initialRunner: string;
preserveNativeCodex: boolean;
preserveNativeHarness: boolean;
createdAgentId: string | null;
}) {
const navigate = useNavigate();
@@ -121,7 +121,7 @@ function Setup({
const { openNewIssue } = useDialogActions();
const appearanceDraft = useAgentAppearanceDraft(`${companyId}:new-agent`);
const [runner, setRunner] = useState<AgentRunnerChoice>(initialRunner === "legacy" || initialRunner === "paperclip" ? initialRunner : "auto");
const requestAdapterType = preserveNativeCodex && runner === "paperclip" ? "paperclip_runner" : adapterType;
const requestAdapterType = preserveNativeHarness && runner === "paperclip" ? "paperclip_runner" : adapterType;
const isRunner = adapterType === "paperclip_runner";
const isDot = isRunner && runnerProvider === "openai_dot";
const brandType = isDot ? "openai_dot" : isRunner
@@ -287,7 +287,7 @@ function Setup({
queryKey: queryKeys.adapters.availability(companyId, environmentId),
queryFn: () => adaptersApi.list({ companyId, environmentId }),
});
const defaultRunner = adapters.data?.find(adapter => adapter.type === "codex_local")?.defaultRunner;
const defaultRunner = adapters.data?.find(adapter => adapter.type === adapterType)?.defaultRunner;
useEffect(() => {
resetTest();
}, [companyId, environmentId, runner === "auto" ? defaultRunner : undefined]);
@@ -342,7 +342,7 @@ function Setup({
) && (requestAdapterType !== "paperclip_runner" || adapterType === "paperclip_runner" || adapterInventory.data?.some(adapter => adapter.type === "paperclip_runner" && adapter.loaded && !adapter.disabled));
const ready = Boolean(
available &&
(adapterType !== "codex_local" || runner !== "auto" || Boolean(adapters.data)) &&
(!["codex_local", "claude_local"].includes(adapterType) || runner !== "auto" || Boolean(adapters.data)) &&
!environmentError &&
!envs.isPending &&
!settings.isPending &&
@@ -383,9 +383,9 @@ function Setup({
}
: {}),
};
const config = getUIAdapter(adapterType).buildAdapterConfig(values);
if (preserveNativeCodex && runner === "paperclip") {
Object.assign(config, { provider: "codex", codexPermissionMode: "never", lifecycleMode: "per_turn" });
const config = getUIAdapter(preserveNativeHarness && runner === "paperclip" ? "paperclip_runner" : adapterType).buildAdapterConfig(preserveNativeHarness && runner === "paperclip" ? { ...values, adapterSchemaValues: runnerProvider === "claude" ? { provider: "acpx", acpxAgent: "claude", acpxPermissionMode: "approve-all" } : { provider: "codex" } } : values);
if (preserveNativeHarness && runner === "paperclip") {
Object.assign(config, runnerProvider === "claude" ? { provider: "acpx", acpxAgent: "claude", acpxPermissionMode: "approve-all", lifecycleMode: "per_turn" } : { provider: "codex", codexPermissionMode: "never", lifecycleMode: "per_turn" });
}
if (isRunner)
Object.assign(config, {
@@ -473,7 +473,7 @@ function Setup({
companyId,
adapterType: requestAdapterType,
providerAdapter: brandType,
...(adapterType === "codex_local" && runner !== "auto" ? { runner } : {}),
...(["codex_local", "claude_local"].includes(adapterType) && runner !== "auto" ? { runner } : {}),
adapterConfig: config,
testCredentials: pendingCredentials(nextConnection),
aiConnection: runtimeAiBinding ?? nextConnection?.aiConnection,
@@ -546,7 +546,7 @@ function Setup({
role: existing.length ? "general" : "ceo",
...(leader ? { reportsTo: leader.id } : {}),
adapterType: requestAdapterType,
...(adapterType === "codex_local" && runner !== "auto" ? { runner } : {}),
...(["codex_local", "claude_local"].includes(adapterType) && runner !== "auto" ? { runner } : {}),
adapterConfig: config,
defaultEnvironmentId:
environmentOverride ||
@@ -814,12 +814,12 @@ function Setup({
setScreen("runtime");
}}
/> : <p role="status" className="text-sm text-muted-foreground">Loading connection settings…</p>}
{adapterType === "codex_local" && <details className="mt-5 space-y-3">
{["codex_local", "claude_local"].includes(adapterType) && <details className="mt-5 space-y-3">
<summary className="cursor-pointer text-sm text-muted-foreground">Advanced</summary>
<CodexRunnerSelect value={runner} disabled={busy}
<AgentRunnerSelect value={runner} disabled={busy}
pending={adapters.isFetching} error={adapters.error?.message} onRetry={() => { void adapters.refetch(); }}
defaultRunner={adapters.data?.find(item => item.type === "codex_local")?.defaultRunner}
supportedRunners={adapters.data?.find(item => item.type === "codex_local")?.supportedRunners}
defaultRunner={adapters.data?.find(item => item.type === adapterType)?.defaultRunner}
supportedRunners={adapters.data?.find(item => item.type === adapterType)?.supportedRunners}
onChange={value => { setRunner(value); resetTest(); }} />
</details>}
</OnboardingCard>
@@ -1165,12 +1165,12 @@ function Setup({
</Field>
</div>
)}
{adapterType === "codex_local" && <details className="space-y-3">
{["codex_local", "claude_local"].includes(adapterType) && <details className="space-y-3">
<summary className="cursor-pointer text-sm text-muted-foreground">Advanced</summary>
<CodexRunnerSelect value={runner} disabled={busy}
<AgentRunnerSelect value={runner} disabled={busy}
pending={adapters.isFetching} error={adapters.error?.message} onRetry={() => { void adapters.refetch(); }}
defaultRunner={adapters.data?.find(item => item.type === "codex_local")?.defaultRunner}
supportedRunners={adapters.data?.find(item => item.type === "codex_local")?.supportedRunners}
defaultRunner={adapters.data?.find(item => item.type === adapterType)?.defaultRunner}
supportedRunners={adapters.data?.find(item => item.type === adapterType)?.supportedRunners}
onChange={value => { setRunner(value); resetTest(); }} />
</details>}
</section>
@@ -1,7 +1,7 @@
import { useEffect, useLayoutEffect, useRef, useState, type CSSProperties, type ReactNode, type Ref } from "react";
import { useQuery } from "@tanstack/react-query";
import { ArrowLeft, Check, ChevronDown, Plus, RotateCcw, Search, X, Zap } from "lucide-react";
import { aiRuntimeConnectionBindingSchema, type Agent, type IssueAssigneeAdapterOverrides } from "@paperclipai/shared";
import { aiRuntimeConnectionBindingSchema, agentHarnessType, type Agent, type IssueAssigneeAdapterOverrides } from "@paperclipai/shared";
import { agentsApi, type AdapterModel } from "@/api/agents";
import { queryKeys } from "@/lib/queryKeys";
import { cn } from "@/lib/utils";
@@ -51,7 +51,7 @@ const HARNESS_LABELS: Record<string, string> = {
function harnessLabel(agent: Agent | undefined): string {
if (!agent) return "Choose an agent";
const harness = HARNESS_LABELS[agent.adapterType] ?? agent.adapterType;
const harness = HARNESS_LABELS[agentHarnessType(agent.adapterType, agent.adapterConfig)] ?? agent.adapterType;
const provider = composerCatalogProvider(agent);
const binding = aiRuntimeConnectionBindingSchema.safeParse(agent?.runtimeConfig?.aiConnection).data;
const poolId = binding?.mode === "router" ? binding.connectionId : undefined;
@@ -3,7 +3,7 @@ import { claudeLocalReasoningEffortsForModel } from "@paperclipai/adapter-claude
import { resolvePaperclipRunnerModel } from "@paperclipai/adapter-utils";
import { DEFAULT_GROK_LOCAL_MODEL, grokLocalReasoningEffortsForModel } from "@paperclipai/adapter-grok-local";
import { DEFAULT_KIMI_LOCAL_MODEL, modelSupportsEffort, KIMI_SUPPORTED_EFFORTS } from "@paperclipai/adapter-kimi-local";
import { aiConnectionBindingSchema, aiRuntimeConnectionBindingSchema, type Agent, type IssueAssigneeAdapterOverrides } from "@paperclipai/shared";
import { aiConnectionBindingSchema, aiRuntimeConnectionBindingSchema, agentHarnessType, type Agent, type IssueAssigneeAdapterOverrides } from "@paperclipai/shared";
export interface ComposerRunSettings {
model: string | null;
@@ -65,12 +65,12 @@ export function composerEfforts(agent: Agent | undefined, model: string, catalog
: agent.adapterType === "kimi_local" ? DEFAULT_KIMI_LOCAL_MODEL : "");
if (!effectiveModel) return [];
const pool = aiRuntimeConnectionBindingSchema.safeParse(agent.runtimeConfig?.aiConnection).data?.mode === "router";
if (pool && agent.adapterType === "paperclip_runner") return isCodexLocalKnownModel(effectiveModel) ? codexLocalReasoningEffortsForModel(effectiveModel) : [];
if (pool && agent.adapterType === "paperclip_runner") return isCodexLocalKnownModel(effectiveModel) ? codexLocalReasoningEffortsForModel(effectiveModel) : /^claude-/.test(effectiveModel) ? claudeLocalReasoningEffortsForModel(effectiveModel) : [];
if (agent.adapterType === "codex_local" || (agent.adapterType === "paperclip_runner" && composerCatalogProvider(agent) === "codex")) {
return isCodexLocalKnownModel(effectiveModel) ? codexLocalReasoningEffortsForModel(effectiveModel) : [];
}
if (!catalogIds.includes(effectiveModel)) return [];
if (agent.adapterType === "claude_local") return claudeLocalReasoningEffortsForModel(effectiveModel);
if (agentHarnessType(agent.adapterType, agent.adapterConfig) === "claude_local") return claudeLocalReasoningEffortsForModel(effectiveModel);
if (agent.adapterType === "grok_local") return grokLocalReasoningEffortsForModel(effectiveModel);
if (agent.adapterType === "pi_local") return ["off", "minimal", "low", "medium", "high", "xhigh"];
if (agent.adapterType === "kimi_local" && typeof agent.adapterConfig.engine === "string"