mirror of
https://github.com/openclaw/openclaw.git
synced 2026-05-25 13:13:04 +00:00
* 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>
275 lines
8.9 KiB
TypeScript
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,
|
|
};
|
|
}
|