Merge branch 'codex/work-folders-runtime-hardening-refresh' into codex/work-folders-staging-hardening-refresh

* codex/work-folders-runtime-hardening-refresh:
  fix: normalize omitted Pi models to the qualified default
This commit is contained in:
Dotta 2026-09-11 15:50:40 -05:00
commit 17687da70e
5 changed files with 27 additions and 7 deletions

View File

@ -116,6 +116,7 @@ export {
PAPERCLIP_RUNNER_IDLE_TIMEOUT_DEFAULT_MS,
PAPERCLIP_RUNNER_IDLE_TIMEOUT_MAX_MS,
PAPERCLIP_RUNNER_DEFAULT_MODELS,
PAPERCLIP_RUNNER_ACPX_DEFAULT_MODELS,
PAPERCLIP_RUNNER_PERMISSION_CAPABILITIES,
isPaperclipRunnerProvider,
resolvePaperclipRunnerIdleTimeoutMs,

View File

@ -16,6 +16,12 @@ export const PAPERCLIP_RUNNER_DEFAULT_MODELS = {
opencode: "openrouter/deepseek/deepseek-v4-flash-0731",
} as const;
export const PAPERCLIP_RUNNER_ACPX_DEFAULT_MODELS = {
claude: PAPERCLIP_RUNNER_DEFAULT_MODELS.acpx,
codex: PAPERCLIP_RUNNER_DEFAULT_MODELS.codex,
pi: "openrouter/deepseek/deepseek-v4-flash-0731",
} as const;
export interface PaperclipRunnerPermissionOption<
TMode extends string = string,
> {

View File

@ -15,6 +15,7 @@ import { redactCommandText } from "./command-redaction.js";
import { paperclipChatFilePreparationDelivery } from "./chat-file-delivery.js";
import {
PAPERCLIP_RUNNER_PERMISSION_CAPABILITIES,
PAPERCLIP_RUNNER_ACPX_DEFAULT_MODELS,
resolvePaperclipRunnerModel,
normalizeLegacyRunnerProvider,
} from "./paperclip-runner-permissions.js";
@ -4113,7 +4114,11 @@ export function normalizePaperclipRunnerAdapterConfig(
}
if (next.provider === "acpx") {
next.acpxAgent ??= "claude";
next.model = resolvePaperclipRunnerModel("acpx", config.model);
const defaultModel = next.acpxAgent === "pi"
? PAPERCLIP_RUNNER_ACPX_DEFAULT_MODELS.pi
: PAPERCLIP_RUNNER_ACPX_DEFAULT_MODELS.claude;
next.model = typeof config.model === "string" && config.model.trim().length > 0
? config.model.trim() : defaultModel;
}
return normalizePaperclipOperationalSkillPreference(adapterType, next);
}

View File

@ -5,6 +5,7 @@ import {
PAPERCLIP_OPERATIONAL_SKILL_KEY,
resolveLegacyPaperclipDesiredSkillNames,
} from "@paperclipai/adapter-utils/server-utils";
import { resolvePaperclipRunnerProviderProfile } from "../services/native-runtime/provider-profile.js";
const legacyConfig = {
paperclipSkillSync: {
@ -13,6 +14,16 @@ const legacyConfig = {
};
describe("paperclip_runner operational skill normalization", () => {
it.each([undefined, "", " "])("qualifies Pi's default model at persistence and execution (%s)", (model) => {
const input = { provider: "acpx", acpxAgent: "pi", model };
const normalized = normalizePaperclipRunnerAdapterConfig("paperclip_runner", input);
expect(normalized.model).toBe("openrouter/deepseek/deepseek-v4-flash-0731");
expect(resolvePaperclipRunnerProviderProfile(normalized)).toMatchObject({ provider: "acpx", acpxAgent: "pi", model: normalized.model });
expect(resolvePaperclipRunnerProviderProfile(input)).toMatchObject({ provider: "acpx", acpxAgent: "pi", model: normalized.model });
const explicit = normalizePaperclipRunnerAdapterConfig("paperclip_runner", { ...input, model: "unqualified-model" });
expect(explicit.model).toBe("unqualified-model");
expect(() => resolvePaperclipRunnerProviderProfile(explicit)).toThrow("requires exact model");
});
it("applies full-auto native runner defaults at persistence boundaries", () => {
expect(normalizePaperclipRunnerAdapterConfig("paperclip_runner", {})).toEqual({
provider: "codex",

View File

@ -1,6 +1,7 @@
import {
isPaperclipRunnerProvider,
PAPERCLIP_RUNNER_PERMISSION_CAPABILITIES,
PAPERCLIP_RUNNER_ACPX_DEFAULT_MODELS,
resolvePaperclipRunnerPermissionMode,
type PaperclipRunnerProvider,
} from "@paperclipai/adapter-utils";
@ -14,11 +15,7 @@ export const DEFAULT_OPENCODE_RUNNER_MODEL =
"openrouter/deepseek/deepseek-v4-flash-0731" as const;
export const CLAUDE_MANAGED_BETA_VERSION = "managed-agents-2026-04-01" as const;
export const QUALIFIED_ACPX_RUNNER_MODELS = {
claude: "claude-sonnet-5",
codex: "gpt-5.6-sol",
pi: "openrouter/deepseek/deepseek-v4-flash-0731",
} as const;
export const QUALIFIED_ACPX_RUNNER_MODELS = PAPERCLIP_RUNNER_ACPX_DEFAULT_MODELS;
export type QualifiedPaperclipRunnerAcpxAgent =
keyof typeof QUALIFIED_ACPX_RUNNER_MODELS;
@ -412,7 +409,7 @@ export function resolvePaperclipRunnerProviderProfile(
);
}
const qualifiedModel = QUALIFIED_ACPX_RUNNER_MODELS[acpxAgent];
if (acpxAgent !== "claude" && model !== qualifiedModel) {
if (acpxAgent !== "claude" && model !== null && model !== qualifiedModel) {
throw new PaperclipRunnerProviderProfileError(
"paperclip_runner_acpx_model_unqualified",
`Paperclip Runner ACPX ${acpxAgent} requires exact model ${qualifiedModel}.`,