Keep Codex Computer Use hook relays live across turns (#74107)

* Fix Codex native hook relay across processes

* fix: harden native hook relay bridge

* test: stabilize pairing store cache assertion

---------

Co-authored-by: pashpashpash <nik@vault77.ai>
This commit is contained in:
pash-openai
2026-04-29 16:57:12 -04:00
committed by GitHub
parent 9ccd015898
commit 3b5dab372a
7 changed files with 843 additions and 68 deletions

View File

@@ -735,6 +735,71 @@ describe("runCodexAppServerAttempt", () => {
expect(nativeHookRelayTesting.getNativeHookRelayRegistrationForTests(relayId)).toBeUndefined();
});
it("reuses the Codex native hook relay id across runs for the same session", async () => {
const sessionFile = path.join(tempDir, "session.jsonl");
const workspaceDir = path.join(tempDir, "workspace");
const firstHarness = createStartedThreadHarness();
const firstRun = runCodexAppServerAttempt(createParams(sessionFile, workspaceDir), {
nativeHookRelay: {
enabled: true,
events: ["pre_tool_use"],
},
});
await firstHarness.waitForMethod("turn/start");
await firstHarness.completeTurn({ threadId: "thread-1", turnId: "turn-1" });
await firstRun;
const firstStartRequest = firstHarness.requests.find(
(request) => request.method === "thread/start",
);
const firstRelayId = extractRelayIdFromThreadRequest(firstStartRequest?.params);
expect(
nativeHookRelayTesting.getNativeHookRelayRegistrationForTests(firstRelayId),
).toBeUndefined();
const secondHarness = createResumeHarness();
const secondParams = createParams(sessionFile, workspaceDir);
secondParams.runId = "run-2";
const secondRun = runCodexAppServerAttempt(secondParams, {
nativeHookRelay: {
enabled: true,
events: ["pre_tool_use"],
},
});
await secondHarness.waitForMethod("turn/start");
const resumeRequest = secondHarness.requests.find(
(request) => request.method === "thread/resume",
);
const secondRelayId = extractRelayIdFromThreadRequest(resumeRequest?.params);
expect(secondRelayId).toBe(firstRelayId);
expect(
nativeHookRelayTesting.getNativeHookRelayRegistrationForTests(firstRelayId),
).toMatchObject({
runId: "run-2",
allowedEvents: ["pre_tool_use"],
});
await secondHarness.completeTurn({ threadId: "thread-existing", turnId: "turn-1" });
await secondRun;
expect(
nativeHookRelayTesting.getNativeHookRelayRegistrationForTests(firstRelayId),
).toBeUndefined();
});
it("builds deterministic opaque Codex native hook relay ids", () => {
const relayId = __testing.buildCodexNativeHookRelayId({
agentId: "dev-codex",
sessionId: "cu-pr-relay-smoke",
sessionKey: "agent:dev-codex:cu-pr-relay-smoke",
});
expect(relayId).toBe("codex-8810b5252975550c887ff0def512b25e944bac39");
expect(relayId).not.toContain("dev-codex");
expect(relayId).not.toContain("cu-pr-relay-smoke");
});
it("sends clearing Codex native hook config when the relay is disabled", async () => {
const sessionFile = path.join(tempDir, "session.jsonl");
const workspaceDir = path.join(tempDir, "workspace");

View File

@@ -1,3 +1,4 @@
import { createHash } from "node:crypto";
import fs from "node:fs/promises";
import { SessionManager } from "@mariozechner/pi-coding-agent";
import {
@@ -1009,6 +1010,11 @@ function createCodexNativeHookRelay(params: {
}
return registerNativeHookRelay({
provider: "codex",
relayId: buildCodexNativeHookRelayId({
agentId: params.agentId,
sessionId: params.sessionId,
sessionKey: params.sessionKey,
}),
...(params.agentId ? { agentId: params.agentId } : {}),
sessionId: params.sessionId,
...(params.sessionKey ? { sessionKey: params.sessionKey } : {}),
@@ -1022,6 +1028,20 @@ function createCodexNativeHookRelay(params: {
});
}
function buildCodexNativeHookRelayId(params: {
agentId: string | undefined;
sessionId: string;
sessionKey: string | undefined;
}): string {
const hash = createHash("sha256");
hash.update("openclaw:codex:native-hook-relay:v1");
hash.update("\0");
hash.update(params.agentId?.trim() || "");
hash.update("\0");
hash.update(params.sessionKey?.trim() || params.sessionId);
return `codex-${hash.digest("hex").slice(0, 40)}`;
}
function interruptCodexTurnBestEffort(
client: CodexAppServerClient,
params: {
@@ -1297,6 +1317,7 @@ function handleApprovalRequest(params: {
export const __testing = {
CODEX_DYNAMIC_TOOL_TIMEOUT_MS,
CODEX_TURN_COMPLETION_IDLE_TIMEOUT_MS,
buildCodexNativeHookRelayId,
filterToolsForVisionInputs,
handleDynamicToolCallWithTimeout,
...createCodexAppServerClientFactoryTestHooks((factory) => {