fix(opencode): refresh isolated model cache
This commit is contained in:
parent
24917f8293
commit
556785beab
|
|
@ -931,6 +931,7 @@ jobs:
|
|||
PAPERCLIP_E2E_CAMPAIGN_ID: gha-${{ github.run_id }}-${{ github.run_attempt }}-${{ matrix.executionId }}
|
||||
PAPERCLIP_RUNNER_E2E_SOURCE_SHA: ${{ needs.authorize.outputs.target_sha }}
|
||||
PAPERCLIP_RUNNER_E2E_SOURCE_REF: ${{ needs.authorize.outputs.target_ref }}
|
||||
XDG_CACHE_HOME: ${{ runner.temp }}/paperclip-runner-e2e-xdg-cache
|
||||
run: pnpm test:e2e:runner -- --id "${{ matrix.executionId }}"
|
||||
|
||||
- name: Upload access-controlled packaged cell evidence
|
||||
|
|
|
|||
|
|
@ -18,7 +18,8 @@ describe("openCode models", () => {
|
|||
});
|
||||
|
||||
it("returns an empty list when discovery command is unavailable", async () => {
|
||||
process.env.PAPERCLIP_OPENCODE_COMMAND = "__paperclip_missing_opencode_command__";
|
||||
process.env.PAPERCLIP_OPENCODE_COMMAND =
|
||||
"__paperclip_missing_opencode_command__";
|
||||
await expect(listOpenCodeModels()).resolves.toEqual([]);
|
||||
});
|
||||
|
||||
|
|
@ -29,7 +30,9 @@ describe("openCode models", () => {
|
|||
});
|
||||
|
||||
it("accepts a provider/model id without running discovery", () => {
|
||||
expect(requireOpenCodeModelId("openai/gpt-5.2-codex")).toBe("openai/gpt-5.2-codex");
|
||||
expect(requireOpenCodeModelId("openai/gpt-5.2-codex")).toBe(
|
||||
"openai/gpt-5.2-codex",
|
||||
);
|
||||
});
|
||||
|
||||
it("rejects malformed provider/model ids before discovery", () => {
|
||||
|
|
@ -42,7 +45,8 @@ describe("openCode models", () => {
|
|||
});
|
||||
|
||||
it("proceeds with the configured model when discovery cannot run (probe is best-effort, never fatal)", async () => {
|
||||
process.env.PAPERCLIP_OPENCODE_COMMAND = "__paperclip_missing_opencode_command__";
|
||||
process.env.PAPERCLIP_OPENCODE_COMMAND =
|
||||
"__paperclip_missing_opencode_command__";
|
||||
await expect(
|
||||
ensureOpenCodeModelConfiguredAndAvailable({
|
||||
model: "openai/gpt-5",
|
||||
|
|
@ -51,23 +55,35 @@ describe("openCode models", () => {
|
|||
});
|
||||
|
||||
it("skips the availability check when OPENCODE_ALLOW_ALL_MODELS is set in the run env", async () => {
|
||||
process.env.PAPERCLIP_OPENCODE_COMMAND = "__paperclip_missing_opencode_command__";
|
||||
process.env.PAPERCLIP_OPENCODE_COMMAND =
|
||||
"__paperclip_missing_opencode_command__";
|
||||
await expect(
|
||||
ensureOpenCodeModelConfiguredAndAvailable({
|
||||
model: "anthropic/tensorix/deepseek/deepseek-chat-v3.1",
|
||||
env: { OPENCODE_ALLOW_ALL_MODELS: "true" },
|
||||
}),
|
||||
).resolves.toEqual([
|
||||
{ id: "anthropic/tensorix/deepseek/deepseek-chat-v3.1", label: "anthropic/tensorix/deepseek/deepseek-chat-v3.1" },
|
||||
{
|
||||
id: "anthropic/tensorix/deepseek/deepseek-chat-v3.1",
|
||||
label: "anthropic/tensorix/deepseek/deepseek-chat-v3.1",
|
||||
},
|
||||
]);
|
||||
});
|
||||
|
||||
it("honours OPENCODE_ALLOW_ALL_MODELS from the process env", async () => {
|
||||
process.env.PAPERCLIP_OPENCODE_COMMAND = "__paperclip_missing_opencode_command__";
|
||||
process.env.PAPERCLIP_OPENCODE_COMMAND =
|
||||
"__paperclip_missing_opencode_command__";
|
||||
process.env.OPENCODE_ALLOW_ALL_MODELS = "1";
|
||||
await expect(
|
||||
ensureOpenCodeModelConfiguredAndAvailable({ model: "anthropic/gateway/some-model" }),
|
||||
).resolves.toEqual([{ id: "anthropic/gateway/some-model", label: "anthropic/gateway/some-model" }]);
|
||||
ensureOpenCodeModelConfiguredAndAvailable({
|
||||
model: "anthropic/gateway/some-model",
|
||||
}),
|
||||
).resolves.toEqual([
|
||||
{
|
||||
id: "anthropic/gateway/some-model",
|
||||
label: "anthropic/gateway/some-model",
|
||||
},
|
||||
]);
|
||||
});
|
||||
|
||||
it("still enforces provider/model format when OPENCODE_ALLOW_ALL_MODELS is set", async () => {
|
||||
|
|
@ -120,20 +136,147 @@ describe("openCode models", () => {
|
|||
expect(spy).toHaveBeenCalledTimes(3);
|
||||
});
|
||||
|
||||
it("surfaces the last error once retries are exhausted", async () => {
|
||||
vi.useFakeTimers();
|
||||
it("refreshes a stale non-empty catalog before rejecting the configured model", async () => {
|
||||
const spy = vi
|
||||
.spyOn(serverUtils, "runChildProcess")
|
||||
.mockResolvedValue({
|
||||
exitCode: 1,
|
||||
.mockResolvedValueOnce({
|
||||
exitCode: 0,
|
||||
signal: null,
|
||||
timedOut: false,
|
||||
stdout: "",
|
||||
stderr: "queued behind another opencode run",
|
||||
stdout: "openrouter/example/stale-model\n",
|
||||
stderr: "",
|
||||
pid: 1,
|
||||
startedAt: new Date().toISOString(),
|
||||
})
|
||||
.mockResolvedValueOnce({
|
||||
exitCode: 0,
|
||||
signal: null,
|
||||
timedOut: false,
|
||||
stdout:
|
||||
"openrouter/example/stale-model\nopenrouter/deepseek/deepseek-v4-flash-0731\n",
|
||||
stderr: "",
|
||||
pid: 1,
|
||||
startedAt: new Date().toISOString(),
|
||||
});
|
||||
|
||||
await expect(
|
||||
ensureOpenCodeModelConfiguredAndAvailable({
|
||||
model: "openrouter/deepseek/deepseek-v4-flash-0731",
|
||||
}),
|
||||
).resolves.toContainEqual({
|
||||
id: "openrouter/deepseek/deepseek-v4-flash-0731",
|
||||
label: "openrouter/deepseek/deepseek-v4-flash-0731",
|
||||
});
|
||||
expect(spy).toHaveBeenCalledTimes(2);
|
||||
expect(spy.mock.calls[0]?.[2]).toEqual(["models"]);
|
||||
expect(spy.mock.calls[1]?.[2]).toEqual(["models", "--refresh"]);
|
||||
});
|
||||
|
||||
it("still rejects when a refreshed non-empty catalog omits the configured model", async () => {
|
||||
const spy = vi
|
||||
.spyOn(serverUtils, "runChildProcess")
|
||||
.mockResolvedValueOnce({
|
||||
exitCode: 0,
|
||||
signal: null,
|
||||
timedOut: false,
|
||||
stdout: "openrouter/example/stale-model\n",
|
||||
stderr: "",
|
||||
pid: 1,
|
||||
startedAt: new Date().toISOString(),
|
||||
})
|
||||
.mockResolvedValueOnce({
|
||||
exitCode: 0,
|
||||
signal: null,
|
||||
timedOut: false,
|
||||
stdout: "openrouter/example/current-model\n",
|
||||
stderr: "",
|
||||
pid: 1,
|
||||
startedAt: new Date().toISOString(),
|
||||
});
|
||||
|
||||
await expect(
|
||||
ensureOpenCodeModelConfiguredAndAvailable({
|
||||
model: "openrouter/deepseek/deepseek-v4-flash-0731",
|
||||
}),
|
||||
).rejects.toThrow(
|
||||
"Configured OpenCode model is unavailable: openrouter/deepseek/deepseek-v4-flash-0731",
|
||||
);
|
||||
expect(spy).toHaveBeenCalledTimes(2);
|
||||
expect(spy.mock.calls[1]?.[2]).toEqual(["models", "--refresh"]);
|
||||
});
|
||||
|
||||
it("still rejects from the original catalog when refresh returns no models", async () => {
|
||||
const spy = vi
|
||||
.spyOn(serverUtils, "runChildProcess")
|
||||
.mockResolvedValueOnce({
|
||||
exitCode: 0,
|
||||
signal: null,
|
||||
timedOut: false,
|
||||
stdout: "openrouter/example/stale-model\n",
|
||||
stderr: "",
|
||||
pid: 1,
|
||||
startedAt: new Date().toISOString(),
|
||||
})
|
||||
.mockResolvedValueOnce({
|
||||
exitCode: 0,
|
||||
signal: null,
|
||||
timedOut: false,
|
||||
stdout: "",
|
||||
stderr: "",
|
||||
pid: 1,
|
||||
startedAt: new Date().toISOString(),
|
||||
});
|
||||
|
||||
await expect(
|
||||
ensureOpenCodeModelConfiguredAndAvailable({
|
||||
model: "openrouter/deepseek/deepseek-v4-flash-0731",
|
||||
}),
|
||||
).rejects.toThrow("Available models: openrouter/example/stale-model");
|
||||
expect(spy).toHaveBeenCalledTimes(2);
|
||||
expect(spy.mock.calls[1]?.[2]).toEqual(["models", "--refresh"]);
|
||||
});
|
||||
|
||||
it("still rejects from the original catalog when refresh fails", async () => {
|
||||
const warning = vi.spyOn(console, "warn").mockImplementation(() => {});
|
||||
const spy = vi
|
||||
.spyOn(serverUtils, "runChildProcess")
|
||||
.mockResolvedValueOnce({
|
||||
exitCode: 0,
|
||||
signal: null,
|
||||
timedOut: false,
|
||||
stdout: "openrouter/example/stale-model\n",
|
||||
stderr: "",
|
||||
pid: 1,
|
||||
startedAt: new Date().toISOString(),
|
||||
})
|
||||
.mockRejectedValueOnce(new Error("refresh unavailable"));
|
||||
|
||||
await expect(
|
||||
ensureOpenCodeModelConfiguredAndAvailable({
|
||||
model: "openrouter/deepseek/deepseek-v4-flash-0731",
|
||||
}),
|
||||
).rejects.toThrow("Available models: openrouter/example/stale-model");
|
||||
expect(spy).toHaveBeenCalledTimes(2);
|
||||
expect(spy.mock.calls[1]?.[2]).toEqual(["models", "--refresh"]);
|
||||
expect(warning).toHaveBeenCalledWith(
|
||||
expect.stringContaining(
|
||||
'refresh failed for "openrouter/deepseek/deepseek-v4-flash-0731"',
|
||||
),
|
||||
);
|
||||
});
|
||||
|
||||
it("surfaces the last error once retries are exhausted", async () => {
|
||||
vi.useFakeTimers();
|
||||
const spy = vi.spyOn(serverUtils, "runChildProcess").mockResolvedValue({
|
||||
exitCode: 1,
|
||||
signal: null,
|
||||
timedOut: false,
|
||||
stdout: "",
|
||||
stderr: "queued behind another opencode run",
|
||||
pid: 1,
|
||||
startedAt: new Date().toISOString(),
|
||||
});
|
||||
|
||||
const promise = discoverOpenCodeModels();
|
||||
const assertion = expect(promise).rejects.toThrow(
|
||||
"`opencode models` failed: queued behind another opencode run",
|
||||
|
|
|
|||
|
|
@ -29,14 +29,26 @@ function resolveOpenCodeCommand(input: unknown): string {
|
|||
return asString(input, envOverride);
|
||||
}
|
||||
|
||||
const discoveryCache = new Map<string, { expiresAt: number; models: AdapterModel[] }>();
|
||||
const discoveryCache = new Map<
|
||||
string,
|
||||
{ expiresAt: number; models: AdapterModel[] }
|
||||
>();
|
||||
const VOLATILE_ENV_KEY_PREFIXES = ["PAPERCLIP_", "npm_", "NPM_"] as const;
|
||||
const VOLATILE_ENV_KEY_EXACT = new Set(["PWD", "OLDPWD", "SHLVL", "_", "TERM_SESSION_ID", "HOME"]);
|
||||
const VOLATILE_ENV_KEY_EXACT = new Set([
|
||||
"PWD",
|
||||
"OLDPWD",
|
||||
"SHLVL",
|
||||
"_",
|
||||
"TERM_SESSION_ID",
|
||||
"HOME",
|
||||
]);
|
||||
|
||||
export function requireOpenCodeModelId(input: unknown): string {
|
||||
const model = asString(input, "").trim();
|
||||
if (!isValidOpenCodeModelId(model)) {
|
||||
throw new Error("OpenCode requires `adapterConfig.model` in provider/model format.");
|
||||
throw new Error(
|
||||
"OpenCode requires `adapterConfig.model` in provider/model format.",
|
||||
);
|
||||
}
|
||||
return model;
|
||||
}
|
||||
|
|
@ -84,9 +96,10 @@ export function parseOpenCodeModelsOutput(stdout: string): AdapterModel[] {
|
|||
}
|
||||
|
||||
function normalizeEnv(input: unknown): Record<string, string> {
|
||||
const envInput = typeof input === "object" && input !== null && !Array.isArray(input)
|
||||
? (input as Record<string, unknown>)
|
||||
: {};
|
||||
const envInput =
|
||||
typeof input === "object" && input !== null && !Array.isArray(input)
|
||||
? (input as Record<string, unknown>)
|
||||
: {};
|
||||
const env: Record<string, string> = {};
|
||||
for (const [key, value] of Object.entries(envInput)) {
|
||||
if (typeof value === "string") env[key] = value;
|
||||
|
|
@ -103,7 +116,11 @@ function hashValue(value: string): string {
|
|||
return createHash("sha256").update(value).digest("hex");
|
||||
}
|
||||
|
||||
function discoveryCacheKey(command: string, cwd: string, env: Record<string, string>) {
|
||||
function discoveryCacheKey(
|
||||
command: string,
|
||||
cwd: string,
|
||||
env: Record<string, string>,
|
||||
) {
|
||||
const envKey = Object.entries(env)
|
||||
.filter(([key]) => !isVolatileEnvKey(key))
|
||||
.sort(([a], [b]) => a.localeCompare(b))
|
||||
|
|
@ -118,11 +135,14 @@ function pruneExpiredDiscoveryCache(now: number) {
|
|||
}
|
||||
}
|
||||
|
||||
export async function discoverOpenCodeModels(input: {
|
||||
command?: unknown;
|
||||
cwd?: unknown;
|
||||
env?: unknown;
|
||||
} = {}): Promise<AdapterModel[]> {
|
||||
export async function discoverOpenCodeModels(
|
||||
input: {
|
||||
command?: unknown;
|
||||
cwd?: unknown;
|
||||
env?: unknown;
|
||||
refresh?: boolean;
|
||||
} = {},
|
||||
): Promise<AdapterModel[]> {
|
||||
const command = resolveOpenCodeCommand(input.command);
|
||||
const cwd = asString(input.cwd, process.cwd());
|
||||
const env = normalizeEnv(input.env);
|
||||
|
|
@ -139,7 +159,14 @@ export async function discoverOpenCodeModels(input: {
|
|||
// image). Fall back to process.env.HOME.
|
||||
}
|
||||
// Prevent OpenCode from writing an opencode.json into the working directory.
|
||||
const runtimeEnv = normalizeEnv(ensurePathInEnv({ ...process.env, ...env, ...(resolvedHome ? { HOME: resolvedHome } : {}), OPENCODE_DISABLE_PROJECT_CONFIG: "true" }));
|
||||
const runtimeEnv = normalizeEnv(
|
||||
ensurePathInEnv({
|
||||
...process.env,
|
||||
...env,
|
||||
...(resolvedHome ? { HOME: resolvedHome } : {}),
|
||||
OPENCODE_DISABLE_PROJECT_CONFIG: "true",
|
||||
}),
|
||||
);
|
||||
|
||||
const maxAttempts = MODELS_DISCOVERY_RETRY_DELAYS_MS.length + 1;
|
||||
let lastError: Error | undefined;
|
||||
|
|
@ -148,7 +175,7 @@ export async function discoverOpenCodeModels(input: {
|
|||
const result = await runChildProcess(
|
||||
`opencode-models-${Date.now()}-${Math.random().toString(16).slice(2)}`,
|
||||
command,
|
||||
["models"],
|
||||
["models", ...(input.refresh ? ["--refresh"] : [])],
|
||||
{
|
||||
cwd,
|
||||
env: runtimeEnv,
|
||||
|
|
@ -159,10 +186,17 @@ export async function discoverOpenCodeModels(input: {
|
|||
);
|
||||
|
||||
if (result.timedOut) {
|
||||
lastError = new Error(`\`opencode models\` timed out after ${MODELS_DISCOVERY_TIMEOUT_MS / 1000}s.`);
|
||||
lastError = new Error(
|
||||
`\`opencode models\` timed out after ${MODELS_DISCOVERY_TIMEOUT_MS / 1000}s.`,
|
||||
);
|
||||
} else if ((result.exitCode ?? 1) !== 0) {
|
||||
const detail = firstNonEmptyLine(result.stderr) || firstNonEmptyLine(result.stdout);
|
||||
lastError = new Error(detail ? `\`opencode models\` failed: ${detail}` : "`opencode models` failed.");
|
||||
const detail =
|
||||
firstNonEmptyLine(result.stderr) || firstNonEmptyLine(result.stdout);
|
||||
lastError = new Error(
|
||||
detail
|
||||
? `\`opencode models\` failed: ${detail}`
|
||||
: "`opencode models` failed.",
|
||||
);
|
||||
} else {
|
||||
return sortModels(parseOpenCodeModelsOutput(result.stdout));
|
||||
}
|
||||
|
|
@ -175,11 +209,13 @@ export async function discoverOpenCodeModels(input: {
|
|||
throw lastError ?? new Error("`opencode models` failed.");
|
||||
}
|
||||
|
||||
export async function discoverOpenCodeModelsCached(input: {
|
||||
command?: unknown;
|
||||
cwd?: unknown;
|
||||
env?: unknown;
|
||||
} = {}): Promise<AdapterModel[]> {
|
||||
export async function discoverOpenCodeModelsCached(
|
||||
input: {
|
||||
command?: unknown;
|
||||
cwd?: unknown;
|
||||
env?: unknown;
|
||||
} = {},
|
||||
): Promise<AdapterModel[]> {
|
||||
const command = resolveOpenCodeCommand(input.command);
|
||||
const cwd = asString(input.cwd, process.cwd());
|
||||
const env = normalizeEnv(input.env);
|
||||
|
|
@ -194,6 +230,29 @@ export async function discoverOpenCodeModelsCached(input: {
|
|||
return models;
|
||||
}
|
||||
|
||||
async function refreshOpenCodeModelsCached(input: {
|
||||
command?: unknown;
|
||||
cwd?: unknown;
|
||||
env?: unknown;
|
||||
}): Promise<AdapterModel[]> {
|
||||
const command = resolveOpenCodeCommand(input.command);
|
||||
const cwd = asString(input.cwd, process.cwd());
|
||||
const env = normalizeEnv(input.env);
|
||||
const models = await discoverOpenCodeModels({
|
||||
command,
|
||||
cwd,
|
||||
env,
|
||||
refresh: true,
|
||||
});
|
||||
if (models.length > 0) {
|
||||
discoveryCache.set(discoveryCacheKey(command, cwd, env), {
|
||||
expiresAt: Date.now() + MODELS_CACHE_TTL_MS,
|
||||
models,
|
||||
});
|
||||
}
|
||||
return models;
|
||||
}
|
||||
|
||||
export function isTruthyEnvFlag(value: string | undefined): boolean {
|
||||
if (value === undefined) return false;
|
||||
const v = value.trim().toLowerCase();
|
||||
|
|
@ -214,7 +273,11 @@ export async function ensureOpenCodeModelConfiguredAndAvailable(input: {
|
|||
// we still enforce the provider/model format above and do not second-guess
|
||||
// the configured model. Prefer the explicit run env, then the process env.
|
||||
const env = normalizeEnv(input.env);
|
||||
if (isTruthyEnvFlag(env.OPENCODE_ALLOW_ALL_MODELS ?? process.env.OPENCODE_ALLOW_ALL_MODELS)) {
|
||||
if (
|
||||
isTruthyEnvFlag(
|
||||
env.OPENCODE_ALLOW_ALL_MODELS ?? process.env.OPENCODE_ALLOW_ALL_MODELS,
|
||||
)
|
||||
) {
|
||||
return [{ id: model, label: model }];
|
||||
}
|
||||
|
||||
|
|
@ -250,7 +313,33 @@ export async function ensureOpenCodeModelConfiguredAndAvailable(input: {
|
|||
}
|
||||
|
||||
if (!models.some((entry) => entry.id === model)) {
|
||||
const sample = models.slice(0, 12).map((entry) => entry.id).join(", ");
|
||||
// `opencode models` reads a persistent models.dev cache. Long-lived runner
|
||||
// hosts can therefore report a stale non-empty catalog even while the
|
||||
// configured provider serves the model. Refresh once before treating a
|
||||
// cached miss as authoritative; a successful refresh that still omits the
|
||||
// model retains the strict availability rejection below.
|
||||
try {
|
||||
const refreshedModels = await refreshOpenCodeModelsCached({
|
||||
command: input.command,
|
||||
cwd: input.cwd,
|
||||
env: input.env,
|
||||
});
|
||||
if (refreshedModels.some((entry) => entry.id === model)) {
|
||||
return refreshedModels;
|
||||
}
|
||||
if (refreshedModels.length > 0) models = refreshedModels;
|
||||
} catch (err) {
|
||||
console.warn(
|
||||
`[opencode-local] Model availability refresh failed for "${model}" (${
|
||||
err instanceof Error ? err.message : String(err)
|
||||
}); preserving the cached availability rejection.`,
|
||||
);
|
||||
}
|
||||
|
||||
const sample = models
|
||||
.slice(0, 12)
|
||||
.map((entry) => entry.id)
|
||||
.join(", ");
|
||||
throw new Error(
|
||||
`Configured OpenCode model is unavailable: ${model}. Available models: ${sample}${models.length > 12 ? ", ..." : ""}`,
|
||||
);
|
||||
|
|
|
|||
|
|
@ -136,6 +136,11 @@ export function assertIsolatedServerEnvironment(
|
|||
"Paperclip server paths escape the isolated temporary root",
|
||||
);
|
||||
}
|
||||
if (env.XDG_CACHE_HOME !== path.join(expected.temporaryRoot, "xdg-cache")) {
|
||||
throw new Error(
|
||||
"Paperclip server cache does not use the allocated temporary root",
|
||||
);
|
||||
}
|
||||
for (const key of [
|
||||
...CREDENTIAL_NAMES,
|
||||
...DATABASE_KEYS,
|
||||
|
|
|
|||
|
|
@ -397,6 +397,7 @@ async function runAttempt(input: {
|
|||
const childEnv: NodeJS.ProcessEnv = {
|
||||
...process.env,
|
||||
PATH: providerPath,
|
||||
XDG_CACHE_HOME: path.join(temporaryRoot, "xdg-cache"),
|
||||
PAPERCLIP_RUNNER_E2E_EXECUTION_IDS: JSON.stringify(
|
||||
executions.map((candidate) => candidate.id),
|
||||
),
|
||||
|
|
|
|||
|
|
@ -596,6 +596,7 @@ describe("runner E2E server isolation", () => {
|
|||
{
|
||||
PAPERCLIP_HOME: "/tmp/cell/paperclip-home",
|
||||
PAPERCLIP_CONFIG: "/tmp/cell/paperclip-home/instances/e2e/config.json",
|
||||
XDG_CACHE_HOME: "/tmp/cell/xdg-cache",
|
||||
PAPERCLIP_AGENT_JWT_SECRET: "generated-agent-jwt",
|
||||
PAPERCLIP_DECISION_SIGNING_SECRET: "generated-decision-key",
|
||||
PAPERCLIP_TOOL_ACTION_SIGNING_SECRET: "generated-tool-key",
|
||||
|
|
@ -608,6 +609,7 @@ describe("runner E2E server isolation", () => {
|
|||
expect(env.OPENAI_ORG_ID).toBeUndefined();
|
||||
expect(env.PAPERCLIP_API_KEY).toBeUndefined();
|
||||
expect(env.PAPERCLIP_AGENT_API_KEY).toBeUndefined();
|
||||
expect(env.XDG_CACHE_HOME).toBe("/tmp/cell/xdg-cache");
|
||||
expect(env.PAPERCLIP_AGENT_JWT_SECRET).toBe("generated-agent-jwt");
|
||||
expect(env.PAPERCLIP_TASK_BRIDGE_TOKEN).toBeUndefined();
|
||||
expect(env.PAPERCLIP_SETUP_TOKEN).toBeUndefined();
|
||||
|
|
|
|||
|
|
@ -398,6 +398,9 @@ describe("public repository paid workflow security", () => {
|
|||
"PAPERCLIP_RUNNER_E2E_SOURCE_REF: ${{ needs.authorize.outputs.target_ref }}",
|
||||
);
|
||||
}
|
||||
expect(paidJob).toContain(
|
||||
"XDG_CACHE_HOME: ${{ runner.temp }}/paperclip-runner-e2e-xdg-cache",
|
||||
);
|
||||
for (const [secret, condition] of Object.entries({
|
||||
OPENAI_API_KEY: "matrix.credentialName == 'OPENAI_API_KEY'",
|
||||
ANTHROPIC_API_KEY: "matrix.credentialName == 'ANTHROPIC_API_KEY'",
|
||||
|
|
|
|||
Loading…
Reference in New Issue