Files
openclaw/extensions/codex/src/app-server/native-hook-relay.test.ts
Kaspre 69a0c925b8 fix(codex): cover side-question native hooks (#82559)
* fix(codex): cover side-question native hooks

* fix(codex): enforce native approvals for app-server requests

* fix(codex): preserve approval fallback after native relay noop

* fix(codex): satisfy approval relay json typing

* fix(codex): run approval relay in report mode

* fix(codex): keep relay pre-tool decisions deny-only

* fix(codex): remove dead relay approval branch

* fix(codex): dedupe app-server relay approvals

* fix(codex): fail closed on native relay rewrites

* fix(codex): preserve side-question provider context

* fix(codex): route side-question replies to origin

* fix(codex): preserve native hook channel context

* test(codex): align native relay rewrite assertion

* fix(codex): align side-question hook config

* fix(codex): route side-question approvals safely

* test(codex): fix side-question hook typing

* fix(codex): preserve side-question hook policy context

* fix(codex): close native hook relay review gaps

* fix(codex): keep dynamic tool hook channel context

* fix(codex): preserve native finalize hook channel context

* fix(codex): scope dynamic tool result hooks by channel

* fix(codex): drop stale deadcode allowlist entry

---------

Co-authored-by: Peter Steinberger <steipete@gmail.com>
2026-05-17 12:02:17 +01:00

275 lines
8.9 KiB
TypeScript

import type { NativeHookRelayRegistrationHandle } from "openclaw/plugin-sdk/agent-harness-runtime";
import { describe, expect, it } from "vitest";
import {
buildCodexNativeHookRelayConfig,
buildCodexNativeHookRelayDisabledConfig,
} from "./native-hook-relay.js";
describe("Codex native hook relay config", () => {
it("builds deterministic Codex config overrides with command hooks", () => {
const config = buildCodexNativeHookRelayConfig({
relay: createRelay(),
hookTimeoutSec: 7,
});
expect(config).toEqual({
"features.hooks": true,
"hooks.PreToolUse": [
{
hooks: [
{
type: "command",
command:
"openclaw hooks relay --provider codex --relay-id relay-1 --event pre_tool_use",
timeout: 7,
async: false,
statusMessage: "OpenClaw native hook relay",
},
],
},
],
"hooks.PostToolUse": [
{
hooks: [
{
type: "command",
command:
"openclaw hooks relay --provider codex --relay-id relay-1 --event post_tool_use",
timeout: 7,
async: false,
statusMessage: "OpenClaw native hook relay",
},
],
},
],
"hooks.PermissionRequest": [
{
hooks: [
{
type: "command",
command:
"openclaw hooks relay --provider codex --relay-id relay-1 --event permission_request",
timeout: 7,
async: false,
statusMessage: "OpenClaw native hook relay",
},
],
},
],
"hooks.Stop": [
{
hooks: [
{
type: "command",
command:
"openclaw hooks relay --provider codex --relay-id relay-1 --event before_agent_finalize",
timeout: 7,
async: false,
statusMessage: "OpenClaw native hook relay",
},
],
},
],
"hooks.state": {
"/<session-flags>/config.toml:pre_tool_use:0:0": {
enabled: true,
trusted_hash: expect.stringMatching(/^sha256:[a-f0-9]{64}$/),
},
"<session-flags>/config.toml:pre_tool_use:0:0": {
enabled: true,
trusted_hash: expect.stringMatching(/^sha256:[a-f0-9]{64}$/),
},
"/<session-flags>/config.toml:post_tool_use:0:0": {
enabled: true,
trusted_hash: expect.stringMatching(/^sha256:[a-f0-9]{64}$/),
},
"<session-flags>/config.toml:post_tool_use:0:0": {
enabled: true,
trusted_hash: expect.stringMatching(/^sha256:[a-f0-9]{64}$/),
},
"/<session-flags>/config.toml:permission_request:0:0": {
enabled: true,
trusted_hash: expect.stringMatching(/^sha256:[a-f0-9]{64}$/),
},
"<session-flags>/config.toml:permission_request:0:0": {
enabled: true,
trusted_hash: expect.stringMatching(/^sha256:[a-f0-9]{64}$/),
},
"/<session-flags>/config.toml:stop:0:0": {
enabled: true,
trusted_hash: expect.stringMatching(/^sha256:[a-f0-9]{64}$/),
},
"<session-flags>/config.toml:stop:0:0": {
enabled: true,
trusted_hash: expect.stringMatching(/^sha256:[a-f0-9]{64}$/),
},
},
});
expect(JSON.stringify(config)).not.toContain("timeoutSec");
expect(JSON.stringify(config)).not.toContain('"matcher":null');
expect(config).not.toHaveProperty("hooks.SessionStart");
expect(config).not.toHaveProperty("hooks.UserPromptSubmit");
});
it("includes only requested hook events", () => {
expect(
buildCodexNativeHookRelayConfig({
relay: createRelay(),
events: ["permission_request"],
}),
).toEqual({
"features.hooks": true,
"hooks.PermissionRequest": [
{
hooks: [
{
type: "command",
command:
"openclaw hooks relay --provider codex --relay-id relay-1 --event permission_request",
timeout: 5,
async: false,
statusMessage: "OpenClaw native hook relay",
},
],
},
],
"hooks.state": {
"/<session-flags>/config.toml:permission_request:0:0": {
enabled: true,
trusted_hash: expect.stringMatching(/^sha256:[a-f0-9]{64}$/),
},
"<session-flags>/config.toml:permission_request:0:0": {
enabled: true,
trusted_hash: expect.stringMatching(/^sha256:[a-f0-9]{64}$/),
},
},
});
});
it("clears requested hook events when the relay reports no local work", () => {
expect(
buildCodexNativeHookRelayConfig({
relay: createRelay({ inactiveEvents: ["post_tool_use", "before_agent_finalize"] }),
events: ["pre_tool_use", "post_tool_use", "before_agent_finalize"],
}),
).toEqual({
"features.hooks": true,
"hooks.PreToolUse": [
{
hooks: [
{
type: "command",
command:
"openclaw hooks relay --provider codex --relay-id relay-1 --event pre_tool_use",
timeout: 5,
async: false,
statusMessage: "OpenClaw native hook relay",
},
],
},
],
"hooks.PostToolUse": [],
"hooks.Stop": [],
"hooks.state": {
"/<session-flags>/config.toml:pre_tool_use:0:0": {
enabled: true,
trusted_hash: expect.stringMatching(/^sha256:[a-f0-9]{64}$/),
},
"<session-flags>/config.toml:pre_tool_use:0:0": {
enabled: true,
trusted_hash: expect.stringMatching(/^sha256:[a-f0-9]{64}$/),
},
},
});
});
it("clears omitted hook events when requested", () => {
expect(
buildCodexNativeHookRelayConfig({
relay: createRelay(),
events: ["permission_request"],
clearOmittedEvents: true,
}),
).toEqual({
"features.hooks": true,
"hooks.PreToolUse": [],
"hooks.PostToolUse": [],
"hooks.PermissionRequest": [
{
hooks: [
{
type: "command",
command:
"openclaw hooks relay --provider codex --relay-id relay-1 --event permission_request",
timeout: 5,
async: false,
statusMessage: "OpenClaw native hook relay",
},
],
},
],
"hooks.Stop": [],
"hooks.state": {
"/<session-flags>/config.toml:pre_tool_use:0:0": { enabled: false },
"<session-flags>/config.toml:pre_tool_use:0:0": { enabled: false },
"/<session-flags>/config.toml:post_tool_use:0:0": { enabled: false },
"<session-flags>/config.toml:post_tool_use:0:0": { enabled: false },
"/<session-flags>/config.toml:permission_request:0:0": {
enabled: true,
trusted_hash: expect.stringMatching(/^sha256:[a-f0-9]{64}$/),
},
"<session-flags>/config.toml:permission_request:0:0": {
enabled: true,
trusted_hash: expect.stringMatching(/^sha256:[a-f0-9]{64}$/),
},
"/<session-flags>/config.toml:stop:0:0": { enabled: false },
"<session-flags>/config.toml:stop:0:0": { enabled: false },
},
});
});
it("omits matchers so Codex MCP tool names reach the relay with a stable trust hash", () => {
const config = buildCodexNativeHookRelayConfig({
relay: createRelay(),
events: ["pre_tool_use", "post_tool_use"],
});
expect((config["hooks.PreToolUse"] as Array<{ matcher?: unknown }>)[0]).not.toHaveProperty(
"matcher",
);
expect((config["hooks.PostToolUse"] as Array<{ matcher?: unknown }>)[0]).not.toHaveProperty(
"matcher",
);
});
it("builds deterministic clearing config when the relay is disabled", () => {
expect(buildCodexNativeHookRelayDisabledConfig()).toEqual({
"features.hooks": false,
"hooks.PreToolUse": [],
"hooks.PostToolUse": [],
"hooks.PermissionRequest": [],
"hooks.Stop": [],
});
});
});
function createRelay(options?: {
inactiveEvents?: readonly NativeHookRelayRegistrationHandle["allowedEvents"][number][];
}): NativeHookRelayRegistrationHandle {
const inactiveEvents = new Set(options?.inactiveEvents ?? []);
return {
relayId: "relay-1",
provider: "codex",
sessionId: "session-1",
sessionKey: "agent:main:session-1",
runId: "run-1",
allowedEvents: ["pre_tool_use", "post_tool_use", "permission_request", "before_agent_finalize"],
expiresAtMs: Date.now() + 1000,
shouldRelayEvent: (event) => !inactiveEvents.has(event),
commandForEvent: (event) =>
`openclaw hooks relay --provider codex --relay-id relay-1 --event ${event}`,
renew: () => undefined,
unregister: () => undefined,
};
}