feat: add native Codex hook integration

This commit is contained in:
ZacharyZhang-NY committed 2026-07-19 05:34:47 -04:00
1 parent ea550154b5
commit 104efbc720
5 files changed
+840

No files matched your search

+411
View File
@@ -0,0 +1,411 @@
from __future__ import annotations
from dataclasses import dataclass
import json
import os
from pathlib import Path
import subprocess
from typing import Any
import pytest
from elydora.plugins import codex
from elydora.plugins.base import InstallConfig
from elydora.plugins.registry import SUPPORTED_AGENTS
AGENT_ID = "agent-1"
MISSING = object()
GUARD_STATUS = "Checking Elydora agent state"
AUDIT_STATUS = "Recording Elydora tool use"
@dataclass(frozen=True)
class CodexFixture:
plugin: codex.CodexPlugin
config: InstallConfig
home_dir: Path
agent_dir: Path
config_path: Path
guard_path: Path
hook_path: Path
runtime_config_path: Path
private_key_path: Path
def write_json_or_text(path: Path, value: Any) -> None:
path.parent.mkdir(parents=True, exist_ok=True)
content = value if isinstance(value, str) else json.dumps(value, indent=2)
path.write_text(content, encoding="utf-8")
def prepare_fixture(
monkeypatch: pytest.MonkeyPatch,
tmp_path: Path,
*,
existing_settings: Any = MISSING,
create_guard: bool = True,
) -> CodexFixture:
home_dir = tmp_path / "home with spaces"
elydora_dir = home_dir / ".elydora"
agent_dir = elydora_dir / AGENT_ID
config_path = home_dir / ".codex" / "hooks.json"
agent_dir.mkdir(parents=True)
guard_path = agent_dir / "guard.py"
if create_guard:
guard_path.write_text(
"import sys\nsys.stderr.write('Agent is frozen by Elydora.')\n"
"raise SystemExit(2)\n",
encoding="utf-8",
)
if existing_settings is not MISSING:
write_json_or_text(config_path, existing_settings)
monkeypatch.setattr(codex, "ELYDORA_DIR", str(elydora_dir))
monkeypatch.setattr(codex, "CONFIG_PATH", str(config_path))
config: InstallConfig = {
"agent_id": AGENT_ID,
"agent_name": "codex",
"org_id": "org-1",
"private_key": "test-key",
"kid": "kid-1",
"base_url": "https://api.elydora.test",
"guard_script_path": str(guard_path),
}
return CodexFixture(
plugin=codex.CodexPlugin(),
config=config,
home_dir=home_dir,
agent_dir=agent_dir,
config_path=config_path,
guard_path=guard_path,
hook_path=agent_dir / "hook.py",
runtime_config_path=agent_dir / "config.json",
private_key_path=agent_dir / "private.key",
)
def find_handler(
settings: dict[str, Any],
event: str,
status_message: str,
) -> dict[str, Any]:
for group in settings["hooks"][event]:
for handler in group["hooks"]:
if handler.get("statusMessage") == status_message:
return handler
raise AssertionError(f"handler {status_message!r} not found")
def run_command(
command: str,
home_dir: Path,
payload: dict[str, Any],
) -> subprocess.CompletedProcess[str]:
env = {
**os.environ,
"HOME": str(home_dir),
"USERPROFILE": str(home_dir),
}
return subprocess.run(
command,
shell=True,
capture_output=True,
check=False,
env=env,
input=json.dumps(payload),
text=True,
)
def test_codex_registry_points_at_global_hooks_contract() -> None:
assert SUPPORTED_AGENTS["codex"] == {
"name": "OpenAI Codex",
"hook_event": "PreToolUse/PostToolUse",
"config_path": "~/.codex/hooks.json",
}
def test_install_preserves_existing_hooks_and_is_idempotent(
monkeypatch: pytest.MonkeyPatch,
tmp_path: Path,
capsys: pytest.CaptureFixture[str],
) -> None:
fixture = prepare_fixture(
monkeypatch,
tmp_path,
existing_settings={
"description": "Workspace hooks",
"hooks": {
"SessionStart": [{
"hooks": [{"type": "command", "command": "existing-command"}],
}],
},
},
)
fixture.plugin.install(fixture.config)
fixture.plugin.install(fixture.config)
assert "run /hooks to review and trust" in capsys.readouterr().out
settings = json.loads(fixture.config_path.read_text(encoding="utf-8"))
assert settings["description"] == "Workspace hooks"
assert settings["hooks"]["SessionStart"][0]["hooks"][0]["command"] == "existing-command"
assert len(settings["hooks"]["PreToolUse"]) == 1
assert len(settings["hooks"]["PostToolUse"]) == 1
assert settings["hooks"]["PreToolUse"][0]["matcher"] == "*"
guard = find_handler(settings, "PreToolUse", GUARD_STATUS)
assert guard["type"] == "command"
assert guard["timeout"] == 10
assert isinstance(guard["command"], str)
assert isinstance(guard["commandWindows"], str)
def test_commands_block_frozen_agents_and_forward_official_payload(
monkeypatch: pytest.MonkeyPatch,
tmp_path: Path,
) -> None:
fixture = prepare_fixture(monkeypatch, tmp_path)
fixture.plugin.install(fixture.config)
capture_path = tmp_path / "captured-event.json"
fixture.hook_path.write_text(
"import pathlib, sys\n"
f"pathlib.Path({str(capture_path)!r}).write_text(sys.stdin.read(), encoding='utf-8')\n",
encoding="utf-8",
)
settings = json.loads(fixture.config_path.read_text(encoding="utf-8"))
command_key = "commandWindows" if os.name == "nt" else "command"
payload = {
"hook_event_name": "PreToolUse",
"session_id": "session-1",
"turn_id": "turn-1",
"transcript_path": None,
"cwd": str(fixture.home_dir),
"model": "gpt-5",
"permission_mode": "default",
"tool_name": "Bash",
"tool_use_id": "call-1",
"tool_input": {"command": "echo test"},
}
guard = find_handler(settings, "PreToolUse", GUARD_STATUS)
guard_result = run_command(guard[command_key], fixture.home_dir, payload)
assert guard_result.returncode == 2
assert "Agent is frozen by Elydora" in guard_result.stderr
payload["hook_event_name"] = "PostToolUse"
payload["tool_response"] = {"output": "test"}
audit = find_handler(settings, "PostToolUse", AUDIT_STATUS)
audit_result = run_command(audit[command_key], fixture.home_dir, payload)
assert audit_result.returncode == 0
assert json.loads(capture_path.read_text(encoding="utf-8")) == payload
def test_status_requires_both_runtimes_and_uninstall_preserves_other_hooks(
monkeypatch: pytest.MonkeyPatch,
tmp_path: Path,
) -> None:
existing_handler = {"type": "command", "command": "existing-command"}
fixture = prepare_fixture(
monkeypatch,
tmp_path,
existing_settings={
"hooks": {"PreToolUse": [{"hooks": [existing_handler]}]},
},
)
fixture.plugin.install(fixture.config)
assert fixture.plugin.status() == {
"installed": True,
"agent": "codex",
"details": f"Config: {fixture.config_path}",
}
fixture.guard_path.unlink()
assert fixture.plugin.status() == {
"installed": False,
"agent": "codex",
"details": f"Configured at {fixture.config_path}; runtime scripts missing",
}
fixture.plugin.uninstall(AGENT_ID)
settings = json.loads(fixture.config_path.read_text(encoding="utf-8"))
assert settings["hooks"]["PreToolUse"] == [{"hooks": [existing_handler]}]
assert settings["hooks"]["PostToolUse"] == []
def test_install_preserves_user_handlers_with_matching_status_text(
monkeypatch: pytest.MonkeyPatch,
tmp_path: Path,
) -> None:
user_guard = {
"type": "command",
"command": "user-guard",
"statusMessage": GUARD_STATUS,
}
user_audit = {
"type": "command",
"command": "user-audit",
"statusMessage": AUDIT_STATUS,
}
fixture = prepare_fixture(
monkeypatch,
tmp_path,
existing_settings={
"hooks": {
"PreToolUse": [{"matcher": "Bash", "hooks": [user_guard]}],
"PostToolUse": [{"matcher": "Bash", "hooks": [user_audit]}],
},
},
)
fixture.plugin.install(fixture.config)
settings = json.loads(fixture.config_path.read_text(encoding="utf-8"))
assert settings["hooks"]["PreToolUse"][0]["hooks"][0] == user_guard
assert settings["hooks"]["PostToolUse"][0]["hooks"][0] == user_audit
assert len(settings["hooks"]["PreToolUse"]) == 2
assert len(settings["hooks"]["PostToolUse"]) == 2
def test_install_rejects_missing_guard_before_runtime_or_config_writes(
monkeypatch: pytest.MonkeyPatch,
tmp_path: Path,
) -> None:
fixture = prepare_fixture(monkeypatch, tmp_path, create_guard=False)
with pytest.raises(FileNotFoundError, match="guard runtime is missing"):
fixture.plugin.install(fixture.config)
assert fixture.config_path.exists() is False
assert fixture.hook_path.exists() is False
assert fixture.runtime_config_path.exists() is False
assert fixture.private_key_path.exists() is False
def test_status_surfaces_malformed_referenced_runtime_metadata(
monkeypatch: pytest.MonkeyPatch,
tmp_path: Path,
) -> None:
fixture = prepare_fixture(monkeypatch, tmp_path)
fixture.plugin.install(fixture.config)
fixture.runtime_config_path.write_text("{ malformed", encoding="utf-8")
with pytest.raises(ValueError, match="parse Elydora runtime config"):
fixture.plugin.status()
def test_status_surfaces_malformed_matcher_groups(
monkeypatch: pytest.MonkeyPatch,
tmp_path: Path,
) -> None:
fixture = prepare_fixture(monkeypatch, tmp_path)
fixture.plugin.install(fixture.config)
settings = json.loads(fixture.config_path.read_text(encoding="utf-8"))
settings["hooks"]["PreToolUse"][0]["hooks"] = None
fixture.config_path.write_text(json.dumps(settings), encoding="utf-8")
with pytest.raises(ValueError, match="matcher group must contain a hooks array"):
fixture.plugin.status()
def test_uninstall_removes_config_owned_entirely_by_elydora(
monkeypatch: pytest.MonkeyPatch,
tmp_path: Path,
) -> None:
fixture = prepare_fixture(monkeypatch, tmp_path)
fixture.plugin.install(fixture.config)
fixture.plugin.uninstall(AGENT_ID)
assert fixture.config_path.exists() is False
def test_uninstall_matches_the_exact_agent_runtime_directory(
monkeypatch: pytest.MonkeyPatch,
tmp_path: Path,
) -> None:
fixture = prepare_fixture(monkeypatch, tmp_path)
fixture.plugin.install(fixture.config)
settings = json.loads(fixture.config_path.read_text(encoding="utf-8"))
other_agent_dir = fixture.agent_dir.parent / "agent-10"
for event, status in (
("PreToolUse", GUARD_STATUS),
("PostToolUse", AUDIT_STATUS),
):
handler = find_handler(settings, event, status)
other_handler = {
**handler,
"command": handler["command"].replace(
str(fixture.agent_dir), str(other_agent_dir)
),
"commandWindows": handler["commandWindows"].replace(
str(fixture.agent_dir), str(other_agent_dir)
),
}
settings["hooks"][event].append(
{"matcher": "*", "hooks": [other_handler]}
)
fixture.config_path.write_text(json.dumps(settings), encoding="utf-8")
fixture.plugin.uninstall(AGENT_ID)
remaining = json.loads(fixture.config_path.read_text(encoding="utf-8"))
assert len(remaining["hooks"]["PreToolUse"]) == 1
assert len(remaining["hooks"]["PostToolUse"]) == 1
assert str(other_agent_dir) in find_handler(
remaining, "PreToolUse", GUARD_STATUS
)["command"]
@pytest.mark.parametrize(
("existing_settings", "error_pattern"),
[
({"hooks": None}, 'field "hooks" must be an object'),
({"hooks": {"PreToolUse": None}}, 'field "hooks.PreToolUse" must be an array'),
(
{"hooks": {"PreToolUse": [{"hooks": None}]}},
"matcher group must contain a hooks array",
),
],
)
def test_install_preserves_invalid_shapes_before_runtime_writes(
monkeypatch: pytest.MonkeyPatch,
tmp_path: Path,
existing_settings: dict[str, Any],
error_pattern: str,
) -> None:
fixture = prepare_fixture(
monkeypatch,
tmp_path,
existing_settings=existing_settings,
)
original = fixture.config_path.read_text(encoding="utf-8")
with pytest.raises(ValueError, match=error_pattern):
fixture.plugin.install(fixture.config)
assert fixture.config_path.read_text(encoding="utf-8") == original
assert fixture.hook_path.exists() is False
assert fixture.runtime_config_path.exists() is False
assert fixture.private_key_path.exists() is False
def test_install_preserves_malformed_config_before_runtime_writes(
monkeypatch: pytest.MonkeyPatch,
tmp_path: Path,
) -> None:
fixture = prepare_fixture(
monkeypatch,
tmp_path,
existing_settings="{ malformed",
)
with pytest.raises(ValueError, match="parse Codex hooks config"):
fixture.plugin.install(fixture.config)
assert fixture.config_path.read_text(encoding="utf-8") == "{ malformed"
assert fixture.hook_path.exists() is False
assert fixture.runtime_config_path.exists() is False
assert fixture.private_key_path.exists() is False