mirror of
https://github.com/paperclipai/paperclip.git
synced 2026-10-25 17:45:53 +01:00
fix native Codex runner E2E authentication
This commit is contained in:
1 parent
b9b92fccea
commit
fda4d5927d
4 files changed
+39
-5
No files matched your search
@@ -106,6 +106,25 @@ describe("runtime context materialization", () => {
|
||||
}
|
||||
});
|
||||
|
||||
it("materializes a configured API key into the isolated Codex home", async () => {
|
||||
const root = await mkdtemp(join(tmpdir(), "paperclip-runtime-context-api-key-"));
|
||||
roots.push(root);
|
||||
const sourceCodexHome = join(root, "source-codex-home");
|
||||
const codexHome = join(root, "codex-home");
|
||||
await mkdir(sourceCodexHome, { recursive: true });
|
||||
await writeFile(join(sourceCodexHome, "auth.json"), JSON.stringify({ OPENAI_API_KEY: "host-key-must-not-win" }), { mode: 0o600 });
|
||||
|
||||
await prepareIsolatedCodexHome({
|
||||
context: null,
|
||||
codexHome,
|
||||
sourceCodexHome,
|
||||
apiKey: "configured-agent-key",
|
||||
});
|
||||
|
||||
await expect(readFile(join(codexHome, "auth.json"), "utf8")).resolves.toBe(JSON.stringify({ OPENAI_API_KEY: "configured-agent-key" }));
|
||||
expect((await stat(join(codexHome, "auth.json"))).mode & 0o777).toBe(0o600);
|
||||
});
|
||||
|
||||
it("reconciles changed and empty assignments without following unexpected symlinks", async () => {
|
||||
const root = await mkdtemp(join(tmpdir(), "paperclip-runtime-context-reconcile-"));
|
||||
roots.push(root);
|
||||
|
||||
@@ -51,7 +51,7 @@ export async function materializeNativeRuntimeSkills(context: NativeRuntimeConte
|
||||
}
|
||||
}
|
||||
|
||||
export async function prepareIsolatedCodexHome(input: { context: NativeRuntimeContextSnapshot | null; codexHome: string; sourceCodexHome?: string | null; nativeMcp?: NativeMcpLaunchBinding | null }): Promise<void> {
|
||||
export async function prepareIsolatedCodexHome(input: { context: NativeRuntimeContextSnapshot | null; codexHome: string; sourceCodexHome?: string | null; nativeMcp?: NativeMcpLaunchBinding | null; apiKey?: string | null }): Promise<void> {
|
||||
await mkdir(input.codexHome, { recursive: true, mode: 0o700 });
|
||||
await chmod(input.codexHome, 0o700);
|
||||
await materializeNativeRuntimeSkills(input.context, join(input.codexHome, "skills"));
|
||||
@@ -64,6 +64,19 @@ export async function prepareIsolatedCodexHome(input: { context: NativeRuntimeCo
|
||||
"",
|
||||
].join("\n"), { mode: 0o600 });
|
||||
}
|
||||
const apiKey = input.apiKey;
|
||||
if (apiKey?.trim()) {
|
||||
// Codex app-server intentionally receives a small allowlisted process
|
||||
// environment and does not inherit OPENAI_API_KEY. Materialize an
|
||||
// API-key-only credential in the per-session Codex home instead. That
|
||||
// home is excluded from durable backup credentials and removed with the
|
||||
// isolated runner state.
|
||||
const target = join(input.codexHome, "auth.json");
|
||||
await rm(target, { force: true });
|
||||
await writeFile(target, JSON.stringify({ OPENAI_API_KEY: apiKey }), { mode: 0o600 });
|
||||
await chmod(target, 0o600);
|
||||
return;
|
||||
}
|
||||
const sourceHome = input.sourceCodexHome?.trim();
|
||||
if (!sourceHome) return;
|
||||
const sourceAuth = join(sourceHome, "auth.json");
|
||||
|
||||
@@ -1376,6 +1376,7 @@ class DurablePrpCodexTransport implements CodexAppServerTransport {
|
||||
sourceCodexHome:
|
||||
this.options.sourceCodexHome ??
|
||||
resolveSourceCodexHome(this.options.environment),
|
||||
apiKey: this.options.environment?.OPENAI_API_KEY,
|
||||
nativeMcp: nativeMcpLaunchBinding(this.options.environment),
|
||||
});
|
||||
}
|
||||
@@ -1698,6 +1699,7 @@ class DurablePrpCodexTransport implements CodexAppServerTransport {
|
||||
sourceCodexHome:
|
||||
this.options.sourceCodexHome ??
|
||||
resolveSourceCodexHome(this.options.environment),
|
||||
apiKey: this.options.environment?.OPENAI_API_KEY,
|
||||
nativeMcp: nativeMcpLaunchBinding(this.options.environment),
|
||||
});
|
||||
}
|
||||
|
||||
@@ -253,7 +253,7 @@ function normalizePlanMarkdown(body: string | null | undefined) {
|
||||
return (body ?? "").replaceAll("\\_", "_");
|
||||
}
|
||||
|
||||
function renderedPlanMarkerPattern(marker: string) {
|
||||
function renderedMarkerPattern(marker: string) {
|
||||
return new RegExp(
|
||||
marker
|
||||
.split("_")
|
||||
@@ -496,7 +496,7 @@ for (const execution of executions) {
|
||||
await expect(
|
||||
page
|
||||
.getByTestId("task-chat-plan-preview")
|
||||
.filter({ hasText: renderedPlanMarkerPattern(planMarkers.draft) })
|
||||
.filter({ hasText: renderedMarkerPattern(planMarkers.draft) })
|
||||
.last(),
|
||||
).toBeVisible({ timeout: 30_000 });
|
||||
await captureScreenshot(
|
||||
@@ -580,7 +580,7 @@ for (const execution of executions) {
|
||||
page
|
||||
.getByTestId("task-chat-plan-preview")
|
||||
.filter({
|
||||
hasText: renderedPlanMarkerPattern(planMarkers.revised),
|
||||
hasText: renderedMarkerPattern(planMarkers.revised),
|
||||
})
|
||||
.last(),
|
||||
).toBeVisible({ timeout: 30_000 });
|
||||
@@ -854,7 +854,7 @@ for (const execution of executions) {
|
||||
page
|
||||
.getByTestId("task-chat-thread")
|
||||
.getByTestId("task-chat-agent-bubble")
|
||||
.filter({ hasText: marker })
|
||||
.filter({ hasText: renderedMarkerPattern(marker) })
|
||||
.last(),
|
||||
).toBeVisible({ timeout: 30_000 });
|
||||
await expect(
|
||||
|
||||
Reference in new issue
Block a user