oh-my-codex 0.20.1 → 0.20.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/Cargo.lock +6 -6
- package/Cargo.toml +1 -1
- package/README.md +10 -1
- package/dist/adapt/__tests__/hermes.test.js.map +1 -1
- package/dist/adapt/contracts.d.ts.map +1 -1
- package/dist/agents/native-config.js.map +1 -1
- package/dist/auth/__tests__/hotswap.test.d.ts +2 -0
- package/dist/auth/__tests__/hotswap.test.d.ts.map +1 -0
- package/dist/auth/__tests__/hotswap.test.js +183 -0
- package/dist/auth/__tests__/hotswap.test.js.map +1 -0
- package/dist/auth/hotswap.d.ts.map +1 -1
- package/dist/auth/hotswap.js +17 -4
- package/dist/auth/hotswap.js.map +1 -1
- package/dist/auth/paths.js.map +1 -1
- package/dist/auth/storage.js.map +1 -1
- package/dist/autopilot/__tests__/ralplan-gate.test.js +4 -4
- package/dist/autopilot/__tests__/ralplan-gate.test.js.map +1 -1
- package/dist/autopilot/completion-gate.js.map +1 -1
- package/dist/autopilot/fsm.d.ts +1 -1
- package/dist/autopilot/fsm.d.ts.map +1 -1
- package/dist/autoresearch/goal.js.map +1 -1
- package/dist/autoresearch/runtime.js.map +1 -1
- package/dist/capabilities/lockfile.js.map +1 -1
- package/dist/catalog/reader.js.map +1 -1
- package/dist/catalog/skill-mirror.js.map +1 -1
- package/dist/cli/__tests__/adapt-help.test.js.map +1 -1
- package/dist/cli/__tests__/agents-init.test.js.map +1 -1
- package/dist/cli/__tests__/agents.test.js.map +1 -1
- package/dist/cli/__tests__/api.test.js.map +1 -1
- package/dist/cli/__tests__/ask.test.js.map +1 -1
- package/dist/cli/__tests__/auth.test.js.map +1 -1
- package/dist/cli/__tests__/autoresearch-guided.test.js.map +1 -1
- package/dist/cli/__tests__/autoresearch.test.js.map +1 -1
- package/dist/cli/__tests__/capabilities.test.js.map +1 -1
- package/dist/cli/__tests__/codex-plugin-layout.test.js.map +1 -1
- package/dist/cli/__tests__/doctor-context-window-warning.test.js.map +1 -1
- package/dist/cli/__tests__/doctor-invalid-config.test.js +47 -4
- package/dist/cli/__tests__/doctor-invalid-config.test.js.map +1 -1
- package/dist/cli/__tests__/doctor-team.test.js.map +1 -1
- package/dist/cli/__tests__/doctor-warning-copy.test.js +650 -15
- package/dist/cli/__tests__/doctor-warning-copy.test.js.map +1 -1
- package/dist/cli/__tests__/exec.test.js.map +1 -1
- package/dist/cli/__tests__/imagegen-continuation.test.js.map +1 -1
- package/dist/cli/__tests__/index.test.js +216 -11
- package/dist/cli/__tests__/index.test.js.map +1 -1
- package/dist/cli/__tests__/launch-fallback.test.js +49 -3
- package/dist/cli/__tests__/launch-fallback.test.js.map +1 -1
- package/dist/cli/__tests__/mcp-parity.test.js +84 -12
- package/dist/cli/__tests__/mcp-parity.test.js.map +1 -1
- package/dist/cli/__tests__/native-hook-claim-journal.test.d.ts +2 -0
- package/dist/cli/__tests__/native-hook-claim-journal.test.d.ts.map +1 -0
- package/dist/cli/__tests__/native-hook-claim-journal.test.js +128 -0
- package/dist/cli/__tests__/native-hook-claim-journal.test.js.map +1 -0
- package/dist/cli/__tests__/nested-help-routing.test.js.map +1 -1
- package/dist/cli/__tests__/question.test.js.map +1 -1
- package/dist/cli/__tests__/ralph-deslop-contract.test.js.map +1 -1
- package/dist/cli/__tests__/ralph-goal-mode-contract.test.js +6 -0
- package/dist/cli/__tests__/ralph-goal-mode-contract.test.js.map +1 -1
- package/dist/cli/__tests__/ralph-prd-deep-interview.test.js.map +1 -1
- package/dist/cli/__tests__/ralph-prd-smoke.test.js.map +1 -1
- package/dist/cli/__tests__/ralph.test.js +8 -3
- package/dist/cli/__tests__/ralph.test.js.map +1 -1
- package/dist/cli/__tests__/ralplan-bootstrap-3181.test.d.ts +2 -0
- package/dist/cli/__tests__/ralplan-bootstrap-3181.test.d.ts.map +1 -0
- package/dist/cli/__tests__/ralplan-bootstrap-3181.test.js +187 -0
- package/dist/cli/__tests__/ralplan-bootstrap-3181.test.js.map +1 -0
- package/dist/cli/__tests__/ralplan.test.d.ts +2 -0
- package/dist/cli/__tests__/ralplan.test.d.ts.map +1 -0
- package/dist/cli/__tests__/ralplan.test.js +129 -0
- package/dist/cli/__tests__/ralplan.test.js.map +1 -0
- package/dist/cli/__tests__/resume.test.js +6 -1
- package/dist/cli/__tests__/resume.test.js.map +1 -1
- package/dist/cli/__tests__/session-scoped-runtime.test.js +25 -0
- package/dist/cli/__tests__/session-scoped-runtime.test.js.map +1 -1
- package/dist/cli/__tests__/session-search-help.test.js.map +1 -1
- package/dist/cli/__tests__/session-search.test.js.map +1 -1
- package/dist/cli/__tests__/setup-agents-overwrite.test.js +79 -0
- package/dist/cli/__tests__/setup-agents-overwrite.test.js.map +1 -1
- package/dist/cli/__tests__/setup-gh-star.test.js.map +1 -1
- package/dist/cli/__tests__/setup-hooks-shared-ownership.test.js +77 -3
- package/dist/cli/__tests__/setup-hooks-shared-ownership.test.js.map +1 -1
- package/dist/cli/__tests__/setup-hooks-trust-e2e.test.d.ts +2 -0
- package/dist/cli/__tests__/setup-hooks-trust-e2e.test.d.ts.map +1 -0
- package/dist/cli/__tests__/setup-hooks-trust-e2e.test.js +285 -0
- package/dist/cli/__tests__/setup-hooks-trust-e2e.test.js.map +1 -0
- package/dist/cli/__tests__/setup-install-mode.test.js +2261 -14
- package/dist/cli/__tests__/setup-install-mode.test.js.map +1 -1
- package/dist/cli/__tests__/setup-preferences.test.d.ts +2 -0
- package/dist/cli/__tests__/setup-preferences.test.d.ts.map +1 -0
- package/dist/cli/__tests__/setup-preferences.test.js +79 -0
- package/dist/cli/__tests__/setup-preferences.test.js.map +1 -0
- package/dist/cli/__tests__/setup-refresh.test.js +33 -2
- package/dist/cli/__tests__/setup-refresh.test.js.map +1 -1
- package/dist/cli/__tests__/setup-scope.test.js +46 -3
- package/dist/cli/__tests__/setup-scope.test.js.map +1 -1
- package/dist/cli/__tests__/sparkshell-cli.test.js.map +1 -1
- package/dist/cli/__tests__/state.test.js +37 -0
- package/dist/cli/__tests__/state.test.js.map +1 -1
- package/dist/cli/__tests__/team.test.js.map +1 -1
- package/dist/cli/__tests__/ultragoal.test.js +61 -1
- package/dist/cli/__tests__/ultragoal.test.js.map +1 -1
- package/dist/cli/__tests__/uninstall.test.js +1771 -24
- package/dist/cli/__tests__/uninstall.test.js.map +1 -1
- package/dist/cli/__tests__/update.test.js +58 -0
- package/dist/cli/__tests__/update.test.js.map +1 -1
- package/dist/cli/__tests__/url.test.js.map +1 -1
- package/dist/cli/__tests__/windows-popup-loop-contract.test.js.map +1 -1
- package/dist/cli/adapt.js.map +1 -1
- package/dist/cli/agents-init.js.map +1 -1
- package/dist/cli/agents.js.map +1 -1
- package/dist/cli/api.js.map +1 -1
- package/dist/cli/ask.d.ts +1 -1
- package/dist/cli/ask.d.ts.map +1 -1
- package/dist/cli/ask.js.map +1 -1
- package/dist/cli/auth.js.map +1 -1
- package/dist/cli/autoresearch-guided.js.map +1 -1
- package/dist/cli/autoresearch-intake.js.map +1 -1
- package/dist/cli/capabilities.js.map +1 -1
- package/dist/cli/cleanup.js.map +1 -1
- package/dist/cli/codex-feature-probe.js.map +1 -1
- package/dist/cli/codex-home.js.map +1 -1
- package/dist/cli/doctor.d.ts +18 -0
- package/dist/cli/doctor.d.ts.map +1 -1
- package/dist/cli/doctor.js +594 -130
- package/dist/cli/doctor.js.map +1 -1
- package/dist/cli/explore.js.map +1 -1
- package/dist/cli/index.d.ts +11 -3
- package/dist/cli/index.d.ts.map +1 -1
- package/dist/cli/index.js +108 -20
- package/dist/cli/index.js.map +1 -1
- package/dist/cli/mcp-serve.js.map +1 -1
- package/dist/cli/mission.js.map +1 -1
- package/dist/cli/native-assets.js.map +1 -1
- package/dist/cli/native-hook-claim-journal.d.ts +18 -0
- package/dist/cli/native-hook-claim-journal.d.ts.map +1 -0
- package/dist/cli/native-hook-claim-journal.js +239 -0
- package/dist/cli/native-hook-claim-journal.js.map +1 -0
- package/dist/cli/omx.js.map +1 -1
- package/dist/cli/plugin-marketplace.js.map +1 -1
- package/dist/cli/project-runtime-codex-homes.js.map +1 -1
- package/dist/cli/question.js.map +1 -1
- package/dist/cli/ralph.d.ts.map +1 -1
- package/dist/cli/ralph.js +3 -2
- package/dist/cli/ralph.js.map +1 -1
- package/dist/cli/ralplan.d.ts +14 -0
- package/dist/cli/ralplan.d.ts.map +1 -0
- package/dist/cli/ralplan.js +203 -0
- package/dist/cli/ralplan.js.map +1 -0
- package/dist/cli/setup-preferences.d.ts +11 -0
- package/dist/cli/setup-preferences.d.ts.map +1 -1
- package/dist/cli/setup-preferences.js +32 -0
- package/dist/cli/setup-preferences.js.map +1 -1
- package/dist/cli/setup.d.ts +53 -0
- package/dist/cli/setup.d.ts.map +1 -1
- package/dist/cli/setup.js +1401 -398
- package/dist/cli/setup.js.map +1 -1
- package/dist/cli/sparkshell.js.map +1 -1
- package/dist/cli/star-prompt.js.map +1 -1
- package/dist/cli/state.d.ts.map +1 -1
- package/dist/cli/state.js +2 -1
- package/dist/cli/state.js.map +1 -1
- package/dist/cli/team.js.map +1 -1
- package/dist/cli/ultragoal.d.ts +5 -1
- package/dist/cli/ultragoal.d.ts.map +1 -1
- package/dist/cli/ultragoal.js +34 -2
- package/dist/cli/ultragoal.js.map +1 -1
- package/dist/cli/uninstall.d.ts +8 -0
- package/dist/cli/uninstall.d.ts.map +1 -1
- package/dist/cli/uninstall.js +944 -95
- package/dist/cli/uninstall.js.map +1 -1
- package/dist/cli/update.d.ts.map +1 -1
- package/dist/cli/update.js +8 -1
- package/dist/cli/update.js.map +1 -1
- package/dist/cli/url.js.map +1 -1
- package/dist/compat/__tests__/doctor-contract.test.js +5 -1
- package/dist/compat/__tests__/doctor-contract.test.js.map +1 -1
- package/dist/compat/__tests__/rust-runtime-compat.test.js.map +1 -1
- package/dist/config/__tests__/codex-hooks.test.js +1287 -11
- package/dist/config/__tests__/codex-hooks.test.js.map +1 -1
- package/dist/config/__tests__/generator-idempotent.test.js +850 -32
- package/dist/config/__tests__/generator-idempotent.test.js.map +1 -1
- package/dist/config/__tests__/generator-notify.test.js +6 -1
- package/dist/config/__tests__/generator-notify.test.js.map +1 -1
- package/dist/config/codex-feature-flags.d.ts.map +1 -1
- package/dist/config/codex-feature-flags.js.map +1 -1
- package/dist/config/codex-hooks.d.ts +124 -1
- package/dist/config/codex-hooks.d.ts.map +1 -1
- package/dist/config/codex-hooks.js +2142 -265
- package/dist/config/codex-hooks.js.map +1 -1
- package/dist/config/commit-lore-guard.js.map +1 -1
- package/dist/config/generator.d.ts +21 -16
- package/dist/config/generator.d.ts.map +1 -1
- package/dist/config/generator.js +991 -137
- package/dist/config/generator.js.map +1 -1
- package/dist/config/mcp-registry.js.map +1 -1
- package/dist/config/models.d.ts +2 -2
- package/dist/config/models.d.ts.map +1 -1
- package/dist/config/models.js.map +1 -1
- package/dist/config/omx-first-party-mcp.js.map +1 -1
- package/dist/config/team-mode.d.ts.map +1 -1
- package/dist/document-refresh/__tests__/enforcer.test.js.map +1 -1
- package/dist/document-refresh/enforcer.js.map +1 -1
- package/dist/exec/followup.js.map +1 -1
- package/dist/goal-workflows/artifacts.js.map +1 -1
- package/dist/hooks/__tests__/agents-overlay.test.js +7 -4
- package/dist/hooks/__tests__/agents-overlay.test.js.map +1 -1
- package/dist/hooks/__tests__/analyze-routing-contract.test.js.map +1 -1
- package/dist/hooks/__tests__/analyze-skill-contract.test.js.map +1 -1
- package/dist/hooks/__tests__/anti-slop-workflow.test.js.map +1 -1
- package/dist/hooks/__tests__/autopilot-skill-contract.test.js +9 -3
- package/dist/hooks/__tests__/autopilot-skill-contract.test.js.map +1 -1
- package/dist/hooks/__tests__/code-review-skill-contract.test.js.map +1 -1
- package/dist/hooks/__tests__/consensus-execution-handoff.test.js.map +1 -1
- package/dist/hooks/__tests__/deep-interview-contract.test.js.map +1 -1
- package/dist/hooks/__tests__/design-skill.test.js.map +1 -1
- package/dist/hooks/__tests__/keyword-detector.test.js +1372 -2
- package/dist/hooks/__tests__/keyword-detector.test.js.map +1 -1
- package/dist/hooks/__tests__/notify-fallback-watcher.test.js +14 -0
- package/dist/hooks/__tests__/notify-fallback-watcher.test.js.map +1 -1
- package/dist/hooks/__tests__/notify-hook-all-workers-idle.test.js.map +1 -1
- package/dist/hooks/__tests__/notify-hook-auto-nudge.test.js +669 -1
- package/dist/hooks/__tests__/notify-hook-auto-nudge.test.js.map +1 -1
- package/dist/hooks/__tests__/notify-hook-cross-worktree-heartbeat.test.js +2 -0
- package/dist/hooks/__tests__/notify-hook-cross-worktree-heartbeat.test.js.map +1 -1
- package/dist/hooks/__tests__/notify-hook-managed-tmux.test.js +142 -1
- package/dist/hooks/__tests__/notify-hook-managed-tmux.test.js.map +1 -1
- package/dist/hooks/__tests__/notify-hook-modules.test.js.map +1 -1
- package/dist/hooks/__tests__/notify-hook-non-omx-guard.test.js +48 -2
- package/dist/hooks/__tests__/notify-hook-non-omx-guard.test.js.map +1 -1
- package/dist/hooks/__tests__/notify-hook-ralph-resume.test.js +61 -44
- package/dist/hooks/__tests__/notify-hook-ralph-resume.test.js.map +1 -1
- package/dist/hooks/__tests__/notify-hook-regression-205.test.js.map +1 -1
- package/dist/hooks/__tests__/notify-hook-session-idle-dedupe.test.js +3 -2
- package/dist/hooks/__tests__/notify-hook-session-idle-dedupe.test.js.map +1 -1
- package/dist/hooks/__tests__/notify-hook-session-scope.test.js +243 -4
- package/dist/hooks/__tests__/notify-hook-session-scope.test.js.map +1 -1
- package/dist/hooks/__tests__/notify-hook-team-dispatch.test.js.map +1 -1
- package/dist/hooks/__tests__/notify-hook-team-leader-nudge.test.js +8 -1
- package/dist/hooks/__tests__/notify-hook-team-leader-nudge.test.js.map +1 -1
- package/dist/hooks/__tests__/notify-hook-team-tmux-guard.test.js.map +1 -1
- package/dist/hooks/__tests__/notify-hook-tmux-heal.test.js.map +1 -1
- package/dist/hooks/__tests__/notify-hook-tmux-scrollback.test.js.map +1 -1
- package/dist/hooks/__tests__/notify-hook-visual-verdict.test.js.map +1 -1
- package/dist/hooks/__tests__/notify-hook-worker-idle.test.js.map +1 -1
- package/dist/hooks/__tests__/openclaw-setup-contract.test.js.map +1 -1
- package/dist/hooks/__tests__/pre-context-gate-skills.test.js.map +1 -1
- package/dist/hooks/__tests__/prometheus-strict-contract.test.js.map +1 -1
- package/dist/hooks/__tests__/prompt-guidance-fragments.test.js.map +1 -1
- package/dist/hooks/__tests__/prompt-guidance-test-helpers.js.map +1 -1
- package/dist/hooks/__tests__/prompt-session-provenance.test.d.ts +2 -0
- package/dist/hooks/__tests__/prompt-session-provenance.test.d.ts.map +1 -0
- package/dist/hooks/__tests__/prompt-session-provenance.test.js +82 -0
- package/dist/hooks/__tests__/prompt-session-provenance.test.js.map +1 -0
- package/dist/hooks/__tests__/session.test.js +532 -11
- package/dist/hooks/__tests__/session.test.js.map +1 -1
- package/dist/hooks/__tests__/skill-catalog-hygiene.test.js.map +1 -1
- package/dist/hooks/__tests__/team-runtime-gating-docs-contract.test.js.map +1 -1
- package/dist/hooks/__tests__/visual-ralph-skill.test.js.map +1 -1
- package/dist/hooks/__tests__/visual-verdict-loop.test.js.map +1 -1
- package/dist/hooks/agents-overlay.js +5 -5
- package/dist/hooks/agents-overlay.js.map +1 -1
- package/dist/hooks/code-simplifier/index.js.map +1 -1
- package/dist/hooks/explore-routing.js.map +1 -1
- package/dist/hooks/extensibility/dispatcher.js.map +1 -1
- package/dist/hooks/extensibility/events.js.map +1 -1
- package/dist/hooks/extensibility/loader.js.map +1 -1
- package/dist/hooks/extensibility/plugin-runner-stdin.js.map +1 -1
- package/dist/hooks/extensibility/sdk/logging.js.map +1 -1
- package/dist/hooks/keyword-detector.d.ts +33 -7
- package/dist/hooks/keyword-detector.d.ts.map +1 -1
- package/dist/hooks/keyword-detector.js +2228 -119
- package/dist/hooks/keyword-detector.js.map +1 -1
- package/dist/hooks/keyword-registry.d.ts +12 -0
- package/dist/hooks/keyword-registry.d.ts.map +1 -1
- package/dist/hooks/keyword-registry.js +41 -0
- package/dist/hooks/keyword-registry.js.map +1 -1
- package/dist/hooks/prompt-session-provenance.d.ts +87 -0
- package/dist/hooks/prompt-session-provenance.d.ts.map +1 -0
- package/dist/hooks/prompt-session-provenance.js +180 -0
- package/dist/hooks/prompt-session-provenance.js.map +1 -0
- package/dist/hooks/session.d.ts +139 -34
- package/dist/hooks/session.d.ts.map +1 -1
- package/dist/hooks/session.js +1239 -317
- package/dist/hooks/session.js.map +1 -1
- package/dist/hooks/task-size-detector.js.map +1 -1
- package/dist/hud/__tests__/state.test.js.map +1 -1
- package/dist/hud/authority.js.map +1 -1
- package/dist/hud/constants.js.map +1 -1
- package/dist/hud/index.js.map +1 -1
- package/dist/hud/reconcile.js.map +1 -1
- package/dist/hud/render.js.map +1 -1
- package/dist/hud/state.js.map +1 -1
- package/dist/hud/tmux.js.map +1 -1
- package/dist/leader/__tests__/contract.test.js +103 -1
- package/dist/leader/__tests__/contract.test.js.map +1 -1
- package/dist/leader/contract.d.ts +26 -5
- package/dist/leader/contract.d.ts.map +1 -1
- package/dist/leader/contract.js +109 -3
- package/dist/leader/contract.js.map +1 -1
- package/dist/mcp/__tests__/bootstrap.test.js.map +1 -1
- package/dist/mcp/__tests__/server-lifecycle.test.js +15 -2
- package/dist/mcp/__tests__/server-lifecycle.test.js.map +1 -1
- package/dist/mcp/__tests__/state-paths.test.js +98 -1
- package/dist/mcp/__tests__/state-paths.test.js.map +1 -1
- package/dist/mcp/bootstrap.js.map +1 -1
- package/dist/mcp/code-intel-server.d.ts +58 -58
- package/dist/mcp/code-intel-server.d.ts.map +1 -1
- package/dist/mcp/code-intel-server.js.map +1 -1
- package/dist/mcp/hermes-bridge.js.map +1 -1
- package/dist/mcp/hermes-server.d.ts +70 -70
- package/dist/mcp/hermes-server.d.ts.map +1 -1
- package/dist/mcp/lifecycle-telemetry.js.map +1 -1
- package/dist/mcp/memory-server.d.ts +34 -34
- package/dist/mcp/memory-server.d.ts.map +1 -1
- package/dist/mcp/memory-server.js.map +1 -1
- package/dist/mcp/state-paths.d.ts +14 -0
- package/dist/mcp/state-paths.d.ts.map +1 -1
- package/dist/mcp/state-paths.js +96 -20
- package/dist/mcp/state-paths.js.map +1 -1
- package/dist/mcp/state-server.d.ts +47 -47
- package/dist/mcp/state-server.d.ts.map +1 -1
- package/dist/mcp/trace-server.d.ts +4 -4
- package/dist/mcp/trace-server.d.ts.map +1 -1
- package/dist/mcp/trace-server.js.map +1 -1
- package/dist/mcp/wiki-server.d.ts +23 -23
- package/dist/mcp/wiki-server.d.ts.map +1 -1
- package/dist/mcp/wiki-server.js.map +1 -1
- package/dist/modes/__tests__/base-session-scope.test.js +53 -1
- package/dist/modes/__tests__/base-session-scope.test.js.map +1 -1
- package/dist/modes/base.d.ts.map +1 -1
- package/dist/modes/base.js +5 -5
- package/dist/modes/base.js.map +1 -1
- package/dist/notifications/__tests__/lifecycle-dedupe.test.js.map +1 -1
- package/dist/notifications/__tests__/reply-listener.test.js.map +1 -1
- package/dist/notifications/__tests__/session-idle-tail-dedupe.test.js +177 -85
- package/dist/notifications/__tests__/session-idle-tail-dedupe.test.js.map +1 -1
- package/dist/notifications/__tests__/session-registry.test.js.map +1 -1
- package/dist/notifications/__tests__/template-engine.test.js.map +1 -1
- package/dist/notifications/__tests__/verbosity.test.js.map +1 -1
- package/dist/notifications/http-client.js.map +1 -1
- package/dist/notifications/index.d.ts +4 -1
- package/dist/notifications/index.d.ts.map +1 -1
- package/dist/notifications/index.js +3 -9
- package/dist/notifications/index.js.map +1 -1
- package/dist/notifications/lifecycle-dedupe.js.map +1 -1
- package/dist/notifications/reply-listener.d.ts.map +1 -1
- package/dist/notifications/reply-listener.js.map +1 -1
- package/dist/notifications/session-registry.js.map +1 -1
- package/dist/notifications/session-status.js.map +1 -1
- package/dist/notifications/temp-contract.js.map +1 -1
- package/dist/notifications/tmux-detector.js.map +1 -1
- package/dist/notifications/tmux.js.map +1 -1
- package/dist/openclaw/config.js.map +1 -1
- package/dist/openclaw/dispatcher.js.map +1 -1
- package/dist/pipeline/__tests__/orchestrator.test.js.map +1 -1
- package/dist/pipeline/__tests__/stages.test.js +8 -5
- package/dist/pipeline/__tests__/stages.test.js.map +1 -1
- package/dist/pipeline/stages/code-review.js.map +1 -1
- package/dist/pipeline/stages/ralph-verify.js.map +1 -1
- package/dist/pipeline/stages/ralplan.js.map +1 -1
- package/dist/pipeline/stages/team-exec.js.map +1 -1
- package/dist/pipeline/stages/ultragoal.js.map +1 -1
- package/dist/pipeline/stages/ultraqa.js.map +1 -1
- package/dist/planning/__tests__/approved-execution-lifecycle-matrix.test.js.map +1 -1
- package/dist/planning/artifact-names.js.map +1 -1
- package/dist/planning/artifacts.js.map +1 -1
- package/dist/question/__tests__/state.test.js.map +1 -1
- package/dist/question/__tests__/ui.test.js.map +1 -1
- package/dist/question/autopilot-wait.js.map +1 -1
- package/dist/question/client.d.ts.map +1 -1
- package/dist/question/client.js.map +1 -1
- package/dist/question/deep-interview.js.map +1 -1
- package/dist/question/events.js.map +1 -1
- package/dist/question/renderer.js.map +1 -1
- package/dist/question/state.d.ts.map +1 -1
- package/dist/question/state.js.map +1 -1
- package/dist/question/ui.js.map +1 -1
- package/dist/ralph/contract.d.ts +1 -1
- package/dist/ralph/contract.d.ts.map +1 -1
- package/dist/ralplan/__tests__/consensus-gate.test.js +403 -8
- package/dist/ralplan/__tests__/consensus-gate.test.js.map +1 -1
- package/dist/ralplan/__tests__/runtime.test.js +112 -14
- package/dist/ralplan/__tests__/runtime.test.js.map +1 -1
- package/dist/ralplan/consensus-gate.d.ts +3 -3
- package/dist/ralplan/consensus-gate.d.ts.map +1 -1
- package/dist/ralplan/consensus-gate.js +155 -60
- package/dist/ralplan/consensus-gate.js.map +1 -1
- package/dist/ralplan/runtime.d.ts +2 -2
- package/dist/ralplan/runtime.d.ts.map +1 -1
- package/dist/ralplan/runtime.js +1 -2
- package/dist/ralplan/runtime.js.map +1 -1
- package/dist/runtime/bridge.d.ts.map +1 -1
- package/dist/runtime/bridge.js.map +1 -1
- package/dist/runtime/process-tree.js.map +1 -1
- package/dist/runtime/run-loop.js.map +1 -1
- package/dist/runtime/run-outcome.d.ts +3 -3
- package/dist/runtime/run-outcome.d.ts.map +1 -1
- package/dist/runtime/run-outcome.js.map +1 -1
- package/dist/runtime/run-state.js.map +1 -1
- package/dist/scripts/__tests__/codex-native-hook.test.js +4121 -592
- package/dist/scripts/__tests__/codex-native-hook.test.js.map +1 -1
- package/dist/scripts/__tests__/fixtures/issue-3138-installed-smoke.d.ts +2 -0
- package/dist/scripts/__tests__/fixtures/issue-3138-installed-smoke.d.ts.map +1 -0
- package/dist/scripts/__tests__/fixtures/issue-3138-installed-smoke.js +634 -0
- package/dist/scripts/__tests__/fixtures/issue-3138-installed-smoke.js.map +1 -0
- package/dist/scripts/__tests__/generate-release-body.test.js.map +1 -1
- package/dist/scripts/__tests__/hook-derived-watcher.test.js.map +1 -1
- package/dist/scripts/__tests__/notify-dispatcher.test.js.map +1 -1
- package/dist/scripts/__tests__/notify-fallback-delivery.test.d.ts +2 -0
- package/dist/scripts/__tests__/notify-fallback-delivery.test.d.ts.map +1 -0
- package/dist/scripts/__tests__/notify-fallback-delivery.test.js +174 -0
- package/dist/scripts/__tests__/notify-fallback-delivery.test.js.map +1 -0
- package/dist/scripts/__tests__/notify-state-io.test.js +57 -1
- package/dist/scripts/__tests__/notify-state-io.test.js.map +1 -1
- package/dist/scripts/__tests__/role-intent-bootstrap-e2e-3181.test.d.ts +2 -0
- package/dist/scripts/__tests__/role-intent-bootstrap-e2e-3181.test.d.ts.map +1 -0
- package/dist/scripts/__tests__/role-intent-bootstrap-e2e-3181.test.js +205 -0
- package/dist/scripts/__tests__/role-intent-bootstrap-e2e-3181.test.js.map +1 -0
- package/dist/scripts/__tests__/role-intent-durable-recovery-3181.test.d.ts +2 -0
- package/dist/scripts/__tests__/role-intent-durable-recovery-3181.test.d.ts.map +1 -0
- package/dist/scripts/__tests__/role-intent-durable-recovery-3181.test.js +175 -0
- package/dist/scripts/__tests__/role-intent-durable-recovery-3181.test.js.map +1 -0
- package/dist/scripts/__tests__/run-test-files.test.js.map +1 -1
- package/dist/scripts/__tests__/smoke-packed-install.test.js +848 -4
- package/dist/scripts/__tests__/smoke-packed-install.test.js.map +1 -1
- package/dist/scripts/__tests__/test-reply-listener-live.test.js.map +1 -1
- package/dist/scripts/build-api.js.map +1 -1
- package/dist/scripts/build-explore-harness.js.map +1 -1
- package/dist/scripts/build-sparkshell.js.map +1 -1
- package/dist/scripts/cleanup-explore-harness.js.map +1 -1
- package/dist/scripts/codex-execution-surface.js.map +1 -1
- package/dist/scripts/codex-native-hook.d.ts.map +1 -1
- package/dist/scripts/codex-native-hook.js +689 -156
- package/dist/scripts/codex-native-hook.js.map +1 -1
- package/dist/scripts/generate-native-release-manifest.js.map +1 -1
- package/dist/scripts/generate-release-body.js.map +1 -1
- package/dist/scripts/hook-payload-guard.d.ts.map +1 -1
- package/dist/scripts/hook-payload-guard.js.map +1 -1
- package/dist/scripts/notify-fallback-delivery.d.ts +48 -0
- package/dist/scripts/notify-fallback-delivery.d.ts.map +1 -0
- package/dist/scripts/notify-fallback-delivery.js +419 -0
- package/dist/scripts/notify-fallback-delivery.js.map +1 -0
- package/dist/scripts/notify-fallback-watcher.js +136 -30
- package/dist/scripts/notify-fallback-watcher.js.map +1 -1
- package/dist/scripts/notify-hook/auto-nudge.d.ts +18 -9
- package/dist/scripts/notify-hook/auto-nudge.d.ts.map +1 -1
- package/dist/scripts/notify-hook/auto-nudge.js +39 -13
- package/dist/scripts/notify-hook/auto-nudge.js.map +1 -1
- package/dist/scripts/notify-hook/managed-tmux.d.ts +23 -0
- package/dist/scripts/notify-hook/managed-tmux.d.ts.map +1 -1
- package/dist/scripts/notify-hook/managed-tmux.js +122 -12
- package/dist/scripts/notify-hook/managed-tmux.js.map +1 -1
- package/dist/scripts/notify-hook/orchestration-intent.d.ts +7 -7
- package/dist/scripts/notify-hook/orchestration-intent.d.ts.map +1 -1
- package/dist/scripts/notify-hook/ralph-session-resume.d.ts +5 -2
- package/dist/scripts/notify-hook/ralph-session-resume.d.ts.map +1 -1
- package/dist/scripts/notify-hook/ralph-session-resume.js +33 -37
- package/dist/scripts/notify-hook/ralph-session-resume.js.map +1 -1
- package/dist/scripts/notify-hook/state-io.d.ts +17 -0
- package/dist/scripts/notify-hook/state-io.d.ts.map +1 -1
- package/dist/scripts/notify-hook/state-io.js +73 -11
- package/dist/scripts/notify-hook/state-io.js.map +1 -1
- package/dist/scripts/notify-hook/team-dispatch.d.ts +1 -1
- package/dist/scripts/notify-hook/team-dispatch.js.map +1 -1
- package/dist/scripts/notify-hook/team-leader-nudge.d.ts +2 -2
- package/dist/scripts/notify-hook/team-tmux-guard.d.ts +4 -4
- package/dist/scripts/notify-hook/team-worker-posttooluse.js.map +1 -1
- package/dist/scripts/notify-hook/team-worker-stop.d.ts +1 -1
- package/dist/scripts/notify-hook/team-worker.d.ts +2 -2
- package/dist/scripts/notify-hook/tmux-injection.d.ts +8 -1
- package/dist/scripts/notify-hook/tmux-injection.d.ts.map +1 -1
- package/dist/scripts/notify-hook/tmux-injection.js +25 -16
- package/dist/scripts/notify-hook/tmux-injection.js.map +1 -1
- package/dist/scripts/notify-hook/visual-verdict.d.ts +1 -1
- package/dist/scripts/notify-hook/visual-verdict.d.ts.map +1 -1
- package/dist/scripts/notify-hook/visual-verdict.js +15 -13
- package/dist/scripts/notify-hook/visual-verdict.js.map +1 -1
- package/dist/scripts/notify-hook.d.ts +24 -1
- package/dist/scripts/notify-hook.d.ts.map +1 -1
- package/dist/scripts/notify-hook.js +266 -90
- package/dist/scripts/notify-hook.js.map +1 -1
- package/dist/scripts/postinstall.js.map +1 -1
- package/dist/scripts/prompt-inventory.js.map +1 -1
- package/dist/scripts/smoke-packed-install.d.ts +879 -2
- package/dist/scripts/smoke-packed-install.d.ts.map +1 -1
- package/dist/scripts/smoke-packed-install.js +1528 -7
- package/dist/scripts/smoke-packed-install.js.map +1 -1
- package/dist/scripts/sync-plugin-mirror.js.map +1 -1
- package/dist/scripts/test-reply-listener-live.js.map +1 -1
- package/dist/scripts/test-sparkshell.js.map +1 -1
- package/dist/scripts/verify-native-agents.js.map +1 -1
- package/dist/session-history/friction.js.map +1 -1
- package/dist/session-history/search.js.map +1 -1
- package/dist/sidecar/__tests__/boundary.test.js.map +1 -1
- package/dist/sidecar/collector.js.map +1 -1
- package/dist/sidecar/index.js.map +1 -1
- package/dist/sidecar/render.js.map +1 -1
- package/dist/sidecar/tmux.js.map +1 -1
- package/dist/state/__tests__/operations.test.js +64 -15
- package/dist/state/__tests__/operations.test.js.map +1 -1
- package/dist/state/__tests__/skill-active.test.js +19 -1
- package/dist/state/__tests__/skill-active.test.js.map +1 -1
- package/dist/state/__tests__/workflow-transition.test.js +61 -0
- package/dist/state/__tests__/workflow-transition.test.js.map +1 -1
- package/dist/state/mode-state-context.js.map +1 -1
- package/dist/state/operations.d.ts +1 -1
- package/dist/state/operations.d.ts.map +1 -1
- package/dist/state/operations.js +49 -55
- package/dist/state/operations.js.map +1 -1
- package/dist/state/paths.d.ts +1 -1
- package/dist/state/paths.d.ts.map +1 -1
- package/dist/state/paths.js +1 -1
- package/dist/state/paths.js.map +1 -1
- package/dist/state/skill-active.d.ts +13 -1
- package/dist/state/skill-active.d.ts.map +1 -1
- package/dist/state/skill-active.js +43 -4
- package/dist/state/skill-active.js.map +1 -1
- package/dist/state/terminal-normalization.js.map +1 -1
- package/dist/state/workflow-transition-reconcile.d.ts.map +1 -1
- package/dist/state/workflow-transition-reconcile.js +20 -4
- package/dist/state/workflow-transition-reconcile.js.map +1 -1
- package/dist/state/workflow-transition.d.ts +1 -1
- package/dist/state/workflow-transition.d.ts.map +1 -1
- package/dist/state/workflow-transition.js.map +1 -1
- package/dist/subagents/__tests__/adapted-role-binding.test.d.ts +2 -0
- package/dist/subagents/__tests__/adapted-role-binding.test.d.ts.map +1 -0
- package/dist/subagents/__tests__/adapted-role-binding.test.js +988 -0
- package/dist/subagents/__tests__/adapted-role-binding.test.js.map +1 -0
- package/dist/subagents/__tests__/leader-bootstrap-3181.test.d.ts +2 -0
- package/dist/subagents/__tests__/leader-bootstrap-3181.test.d.ts.map +1 -0
- package/dist/subagents/__tests__/leader-bootstrap-3181.test.js +273 -0
- package/dist/subagents/__tests__/leader-bootstrap-3181.test.js.map +1 -0
- package/dist/subagents/__tests__/tracker.test.js +1550 -10
- package/dist/subagents/__tests__/tracker.test.js.map +1 -1
- package/dist/subagents/adapted-role-binding.d.ts +17 -0
- package/dist/subagents/adapted-role-binding.d.ts.map +1 -0
- package/dist/subagents/adapted-role-binding.js +75 -0
- package/dist/subagents/adapted-role-binding.js.map +1 -0
- package/dist/subagents/role-routing-marker.d.ts +10 -0
- package/dist/subagents/role-routing-marker.d.ts.map +1 -0
- package/dist/subagents/role-routing-marker.js +136 -0
- package/dist/subagents/role-routing-marker.js.map +1 -0
- package/dist/subagents/tracker.d.ts +172 -0
- package/dist/subagents/tracker.d.ts.map +1 -1
- package/dist/subagents/tracker.js +1077 -61
- package/dist/subagents/tracker.js.map +1 -1
- package/dist/team/__tests__/approved-execution.test.js.map +1 -1
- package/dist/team/__tests__/delivery-e2e-smoke.test.js.map +1 -1
- package/dist/team/__tests__/model-contract.test.js +231 -1
- package/dist/team/__tests__/model-contract.test.js.map +1 -1
- package/dist/team/__tests__/role-router.test.js.map +1 -1
- package/dist/team/__tests__/runtime.test.js +401 -1
- package/dist/team/__tests__/runtime.test.js.map +1 -1
- package/dist/team/__tests__/scaling.test.js +181 -0
- package/dist/team/__tests__/scaling.test.js.map +1 -1
- package/dist/team/__tests__/state-root.test.js.map +1 -1
- package/dist/team/__tests__/tmux-session.test.js +434 -4
- package/dist/team/__tests__/tmux-session.test.js.map +1 -1
- package/dist/team/__tests__/tmux-test-fixture.js.map +1 -1
- package/dist/team/api-interop.d.ts +2 -2
- package/dist/team/api-interop.d.ts.map +1 -1
- package/dist/team/api-interop.js.map +1 -1
- package/dist/team/commit-hygiene.js.map +1 -1
- package/dist/team/contracts.d.ts +5 -5
- package/dist/team/contracts.d.ts.map +1 -1
- package/dist/team/delivery-log.js.map +1 -1
- package/dist/team/followup-planner.js.map +1 -1
- package/dist/team/goal-workflow.js.map +1 -1
- package/dist/team/idle-nudge.d.ts.map +1 -1
- package/dist/team/model-contract.d.ts +20 -1
- package/dist/team/model-contract.d.ts.map +1 -1
- package/dist/team/model-contract.js +267 -30
- package/dist/team/model-contract.js.map +1 -1
- package/dist/team/orchestrator.js.map +1 -1
- package/dist/team/pane-status.js.map +1 -1
- package/dist/team/phase-controller.js.map +1 -1
- package/dist/team/reminder-intents.d.ts +1 -1
- package/dist/team/reminder-intents.d.ts.map +1 -1
- package/dist/team/reminder-intents.js.map +1 -1
- package/dist/team/runtime-cli.js.map +1 -1
- package/dist/team/runtime.d.ts.map +1 -1
- package/dist/team/runtime.js +83 -52
- package/dist/team/runtime.js.map +1 -1
- package/dist/team/scaling.d.ts +9 -7
- package/dist/team/scaling.d.ts.map +1 -1
- package/dist/team/scaling.js +69 -28
- package/dist/team/scaling.js.map +1 -1
- package/dist/team/state/dispatch-lock.js.map +1 -1
- package/dist/team/state/dispatch.js.map +1 -1
- package/dist/team/state/events.js.map +1 -1
- package/dist/team/state/tasks.js.map +1 -1
- package/dist/team/state-root.js.map +1 -1
- package/dist/team/state.js.map +1 -1
- package/dist/team/team-identity.d.ts.map +1 -1
- package/dist/team/team-identity.js.map +1 -1
- package/dist/team/tmux-session.d.ts +2 -0
- package/dist/team/tmux-session.d.ts.map +1 -1
- package/dist/team/tmux-session.js +90 -42
- package/dist/team/tmux-session.js.map +1 -1
- package/dist/team/worker-bootstrap.js.map +1 -1
- package/dist/team/worktree.js.map +1 -1
- package/dist/ultragoal/__tests__/artifacts.test.js.map +1 -1
- package/dist/ultragoal/__tests__/docs-contract.test.js.map +1 -1
- package/dist/ultragoal/artifacts.d.ts.map +1 -1
- package/dist/ultragoal/artifacts.js +4 -1
- package/dist/ultragoal/artifacts.js.map +1 -1
- package/dist/url-reader/index.js.map +1 -1
- package/dist/utils/agents-model-table.js.map +1 -1
- package/dist/utils/package.js.map +1 -1
- package/dist/utils/paths.js.map +1 -1
- package/dist/utils/platform-command.js.map +1 -1
- package/dist/utils/repo-deps.js.map +1 -1
- package/dist/utils/version.js.map +1 -1
- package/dist/verification/__tests__/ci-rust-gates.test.js +1 -1
- package/dist/verification/__tests__/dev-merge-issue-close-workflow.test.js.map +1 -1
- package/dist/verification/verifier.js.map +1 -1
- package/dist/visual/constants.d.ts +1 -1
- package/dist/visual/constants.d.ts.map +1 -1
- package/dist/vscode/__tests__/index.test.js.map +1 -1
- package/dist/vscode/index.js.map +1 -1
- package/dist/wiki/__tests__/cjk-tokenize.test.js.map +1 -1
- package/dist/wiki/__tests__/lint.test.js.map +1 -1
- package/dist/wiki/__tests__/query.test.js.map +1 -1
- package/dist/wiki/__tests__/storage.test.js.map +1 -1
- package/dist/wiki/__tests__/test-helpers.d.ts.map +1 -1
- package/dist/wiki/lint.js.map +1 -1
- package/dist/wiki/query.js.map +1 -1
- package/dist/wiki/storage.js.map +1 -1
- package/package.json +2 -2
- package/plugins/oh-my-codex/.codex-plugin/plugin.json +1 -1
- package/plugins/oh-my-codex/skills/omx-setup/SKILL.md +10 -5
- package/plugins/oh-my-codex/skills/ralph/SKILL.md +4 -2
- package/plugins/oh-my-codex/skills/ralplan/SKILL.md +5 -2
- package/skills/omx-setup/SKILL.md +10 -5
- package/skills/ralph/SKILL.md +4 -2
- package/skills/ralplan/SKILL.md +5 -2
- package/src/scripts/__tests__/codex-native-hook.test.ts +17522 -10630
- package/src/scripts/__tests__/fixtures/issue-3138-installed-smoke.ts +734 -0
- package/src/scripts/__tests__/notify-fallback-delivery.test.ts +177 -0
- package/src/scripts/__tests__/notify-state-io.test.ts +73 -0
- package/src/scripts/__tests__/role-intent-bootstrap-e2e-3181.test.ts +215 -0
- package/src/scripts/__tests__/role-intent-durable-recovery-3181.test.ts +193 -0
- package/src/scripts/__tests__/smoke-packed-install.test.ts +991 -4
- package/src/scripts/codex-native-hook.ts +779 -159
- package/src/scripts/notify-fallback-delivery.ts +416 -0
- package/src/scripts/notify-fallback-watcher.ts +132 -40
- package/src/scripts/notify-hook/auto-nudge.ts +43 -12
- package/src/scripts/notify-hook/managed-tmux.ts +149 -12
- package/src/scripts/notify-hook/ralph-session-resume.ts +37 -36
- package/src/scripts/notify-hook/state-io.ts +100 -13
- package/src/scripts/notify-hook/tmux-injection.ts +38 -21
- package/src/scripts/notify-hook/visual-verdict.ts +22 -12
- package/src/scripts/notify-hook.ts +335 -90
- package/src/scripts/smoke-packed-install.ts +1829 -6
|
@@ -4,11 +4,11 @@ import { mkdir, mkdtemp, rm, symlink, writeFile } from "node:fs/promises";
|
|
|
4
4
|
import { tmpdir } from "node:os";
|
|
5
5
|
import { join } from "node:path";
|
|
6
6
|
import { spawnSync } from "node:child_process";
|
|
7
|
-
import { buildManagedCodexNativeHookCommand, buildManagedCodexNativeHookWindowsShimContent, buildManagedCodexNativeHookWindowsShimPath, buildManagedCodexHookTrustState, buildManagedCodexHookTrustToml, buildManagedCodexHooksConfig, extractCodexHooksJsonTrustState, hasCodexHooksJsonTopLevelState, discoverCodexHookConfigPaths, dedupeCodexHookConfigPaths, getMissingManagedCodexHookEvents, hasUserCodexHooksAfterManagedRemoval, isRuntimeCodexHomeMirrorPath, mergeManagedCodexHooksConfig, removeManagedCodexHooks, resolveWindowsPowerShellPath, } from "../codex-hooks.js";
|
|
7
|
+
import { buildManagedCodexNativeHookCommand, buildManagedCodexNativeHookWindowsShimContent, buildManagedCodexNativeHookWindowsShimPath, classifyManagedCodexNativeHookWindowsShimOwnership, buildManagedCodexHookTrustState, buildManagedCodexHookTrustToml, buildManagedCodexHooksConfig, extractCodexHooksJsonTrustState, hasCodexHooksJsonTopLevelState, discoverCodexHookConfigPaths, dedupeCodexHookConfigPaths, getMissingManagedCodexHookEvents, hasUserCodexHooksAfterManagedRemoval, isRuntimeCodexHomeMirrorPath, isManagedCodexHookCommand, mergeManagedCodexHooksConfig, removeManagedCodexHooks, resolveWindowsPowerShellPath, parseManagedCodexNativeHookWindowsShimCommand, ManagedCodexHooksPlanError, planManagedCodexHooksMerge, planManagedCodexHooksRemoval, scanManagedCodexHookTrustStateFromContent, validateCodexHooksConfigStrict, } from "../codex-hooks.js";
|
|
8
8
|
describe("codex hooks helpers", () => {
|
|
9
9
|
it("uses the current JavaScript runtime for managed hook commands", () => {
|
|
10
10
|
const config = buildManagedCodexHooksConfig("/repo");
|
|
11
|
-
const command = config.hooks.SessionStart[0]?.hooks
|
|
11
|
+
const command = config.hooks.SessionStart[0]?.hooks[0]?.command;
|
|
12
12
|
assert.equal(command, `"${process.execPath.replace(/\\/g, "\\\\").replace(/"/g, '\\"')}" "/repo/dist/scripts/codex-native-hook.js"`);
|
|
13
13
|
});
|
|
14
14
|
it("registers SessionStart for startup, resume, and clear reset sources", () => {
|
|
@@ -24,12 +24,579 @@ describe("codex hooks helpers", () => {
|
|
|
24
24
|
codexHomeDir: "C:\\Users\\Ada Lovelace\\.codex",
|
|
25
25
|
env: { SystemRoot: "C:\\Windows" },
|
|
26
26
|
});
|
|
27
|
-
const command = config.hooks.SessionStart[0]?.hooks
|
|
27
|
+
const command = config.hooks.SessionStart[0]?.hooks[0]?.command;
|
|
28
28
|
assert.equal(command, "& 'C:\\Windows\\System32\\WindowsPowerShell\\v1.0\\powershell.exe' -NoProfile -ExecutionPolicy Bypass -File 'C:\\Users\\Ada Lovelace\\.codex\\hooks\\omx-native-hook-windows-shim.ps1'");
|
|
29
29
|
assert.doesNotMatch(command ?? "", /codex-native-hook\.js/);
|
|
30
30
|
assert.doesNotMatch(command ?? "", /^"[A-Z]:\\/i);
|
|
31
31
|
assert.doesNotMatch(command ?? "", /\\"/);
|
|
32
32
|
});
|
|
33
|
+
it("parses only managed Windows shim commands and returns their validated final path", () => {
|
|
34
|
+
const shimPath = "C:\\Users\\Ada Lovelace\\.codex\\hooks\\omx-native-hook-windows-shim.ps1";
|
|
35
|
+
const managed = `& 'C:\\Windows\\System32\\WindowsPowerShell\\v1.0\\powershell.exe' -NoProfile -ExecutionPolicy Bypass -File '${shimPath}'`;
|
|
36
|
+
assert.equal(parseManagedCodexNativeHookWindowsShimCommand(managed), shimPath);
|
|
37
|
+
assert.equal(parseManagedCodexNativeHookWindowsShimCommand(`powershell.exe -NoProfile -ExecutionPolicy Bypass -File "${shimPath}"`), shimPath);
|
|
38
|
+
for (const command of [
|
|
39
|
+
`${managed} -NoExit`,
|
|
40
|
+
managed.replace("-ExecutionPolicy Bypass", "-ExecutionPolicy RemoteSigned"),
|
|
41
|
+
managed.replace("omx-native-hook-windows-shim.ps1", "foreign-hook.ps1"),
|
|
42
|
+
`${managed}; Write-Host modified`,
|
|
43
|
+
managed.replace("& ", "'&' "),
|
|
44
|
+
]) {
|
|
45
|
+
assert.equal(parseManagedCodexNativeHookWindowsShimCommand(command), null);
|
|
46
|
+
}
|
|
47
|
+
});
|
|
48
|
+
it("accepts quoted POSIX and PowerShell path operators while rejecting unquoted operators", () => {
|
|
49
|
+
const directCommand = buildManagedCodexNativeHookCommand("/repo with spaces;|<> (fixture) $literal/O'Brien", { platform: "linux" });
|
|
50
|
+
const packageRoot = "D:\\Program Files\\O'Brien\\hook (fixture) $literal\\oh-my-codex";
|
|
51
|
+
const codexHome = "C:\\Users\\O'Brien\\.codex (fixture) $literal";
|
|
52
|
+
const shimPath = "C:\\Users\\O'Brien\\.codex (fixture) $literal\\hooks\\omx-native-hook-windows-shim.ps1";
|
|
53
|
+
const windowsCommand = buildManagedCodexNativeHookCommand(packageRoot, {
|
|
54
|
+
platform: "win32",
|
|
55
|
+
codexHomeDir: codexHome,
|
|
56
|
+
env: { SystemRoot: "C:\\Windows" },
|
|
57
|
+
});
|
|
58
|
+
assert.match(directCommand, /\\\$literal/);
|
|
59
|
+
assert.equal(isManagedCodexHookCommand(directCommand), true);
|
|
60
|
+
assert.equal(isManagedCodexHookCommand(windowsCommand), true);
|
|
61
|
+
assert.equal(parseManagedCodexNativeHookWindowsShimCommand(windowsCommand), shimPath);
|
|
62
|
+
assert.match(windowsCommand, /O''Brien/);
|
|
63
|
+
assert.equal(isManagedCodexHookCommand('node /repo/dist/scripts/codex-native-hook.js; Write-Host injected'), false);
|
|
64
|
+
});
|
|
65
|
+
it("owns only shell-static command spellings and keeps platform ownership exact", () => {
|
|
66
|
+
const posixScript = "/repo/dist/scripts/codex-native-hook.js";
|
|
67
|
+
const windowsScript = "C:\\repo\\DIST\\SCRIPTS\\CODEX-NATIVE-HOOK.JS";
|
|
68
|
+
const shimPath = "C:\\Users\\Ada\\.codex\\hooks\\omx-native-hook-windows-shim.ps1";
|
|
69
|
+
const managedWindowsShim = `& 'C:\\Windows\\System32\\WindowsPowerShell\\v1.0\\powershell.exe' -NoProfile -ExecutionPolicy Bypass -File '${shimPath}'`;
|
|
70
|
+
assert.equal(isManagedCodexHookCommand(`node '${posixScript}'`), true);
|
|
71
|
+
assert.equal(isManagedCodexHookCommand(`C:\\Node\\NODE.EXE ${windowsScript}`), true);
|
|
72
|
+
for (const command of [
|
|
73
|
+
`node "$HOME${posixScript}"`,
|
|
74
|
+
`node "$(printf /repo)/dist/scripts/codex-native-hook.js"`,
|
|
75
|
+
"node /repo/*/dist/scripts/codex-native-hook.js",
|
|
76
|
+
"node ~/repo/dist/scripts/codex-native-hook.js",
|
|
77
|
+
"node /repo/{old,new}/dist/scripts/codex-native-hook.js",
|
|
78
|
+
`node "${posixScript}"; :`,
|
|
79
|
+
]) {
|
|
80
|
+
assert.equal(isManagedCodexHookCommand(command), false, command);
|
|
81
|
+
}
|
|
82
|
+
for (const command of [
|
|
83
|
+
`& "$env:SystemRoot\\System32\\WindowsPowerShell\\v1.0\\powershell.exe" -NoProfile -ExecutionPolicy Bypass -File '${shimPath}'`,
|
|
84
|
+
`& "$([Environment]::GetFolderPath('Windows'))\\System32\\WindowsPowerShell\\v1.0\\powershell.exe" -NoProfile -ExecutionPolicy Bypass -File '${shimPath}'`,
|
|
85
|
+
`${managedWindowsShim}; Write-Host injected`,
|
|
86
|
+
]) {
|
|
87
|
+
assert.equal(parseManagedCodexNativeHookWindowsShimCommand(command), null, command);
|
|
88
|
+
assert.equal(isManagedCodexHookCommand(command), false, command);
|
|
89
|
+
}
|
|
90
|
+
const caseVariant = JSON.stringify({
|
|
91
|
+
hooks: {
|
|
92
|
+
SessionStart: [{ hooks: [{ type: "command", command: `NODE ${posixScript}` }] }],
|
|
93
|
+
},
|
|
94
|
+
});
|
|
95
|
+
const posix = planManagedCodexHooksRemoval(caseVariant, "/hooks.json", { platform: "linux" });
|
|
96
|
+
const windows = planManagedCodexHooksRemoval(caseVariant, "C:\\Users\\Ada\\.codex\\hooks.json", { platform: "win32" });
|
|
97
|
+
assert.equal(posix.ok, false);
|
|
98
|
+
assert.equal(windows.ok, false);
|
|
99
|
+
if (!windows.ok)
|
|
100
|
+
assert.equal(windows.error.code, "ambiguous_managed_handler");
|
|
101
|
+
});
|
|
102
|
+
it("requires absolute, control-free POSIX OMX paths before ownership proof", () => {
|
|
103
|
+
const historicalCommand = `node "/historical install/O'Brien/dist/scripts/codex-native-hook.js"`;
|
|
104
|
+
const ownedSource = JSON.stringify({
|
|
105
|
+
hooks: {
|
|
106
|
+
PreToolUse: [{ hooks: [{ type: "command", command: historicalCommand }] }],
|
|
107
|
+
},
|
|
108
|
+
});
|
|
109
|
+
const ownedMerge = planManagedCodexHooksMerge(ownedSource, "/repo", "/hooks.json", { platform: "linux" });
|
|
110
|
+
const ownedRemoval = planManagedCodexHooksRemoval(ownedSource, "/hooks.json", { platform: "linux" });
|
|
111
|
+
assert.equal(isManagedCodexHookCommand(historicalCommand), true);
|
|
112
|
+
assert.equal(ownedMerge.ok, true);
|
|
113
|
+
assert.equal(ownedRemoval.ok, true);
|
|
114
|
+
if (ownedRemoval.ok) {
|
|
115
|
+
assert.equal(ownedRemoval.removedCount, 1);
|
|
116
|
+
assert.equal(ownedRemoval.finalContent, null);
|
|
117
|
+
}
|
|
118
|
+
const foreignCommand = "node ./foreign-hook.js";
|
|
119
|
+
const foreignSource = JSON.stringify({
|
|
120
|
+
hooks: {
|
|
121
|
+
PreToolUse: [{ hooks: [{ type: "command", command: foreignCommand }] }],
|
|
122
|
+
},
|
|
123
|
+
});
|
|
124
|
+
const foreignMerge = planManagedCodexHooksMerge(foreignSource, "/repo", "/hooks.json", { platform: "linux" });
|
|
125
|
+
const foreignRemoval = planManagedCodexHooksRemoval(foreignSource, "/hooks.json", { platform: "linux" });
|
|
126
|
+
assert.equal(foreignMerge.ok, true);
|
|
127
|
+
assert.equal(foreignRemoval.ok, true);
|
|
128
|
+
if (foreignMerge.ok) {
|
|
129
|
+
assert.equal(foreignMerge.hasForeignHooks, true);
|
|
130
|
+
assert.ok(foreignMerge.finalContent?.includes(foreignCommand));
|
|
131
|
+
}
|
|
132
|
+
if (foreignRemoval.ok)
|
|
133
|
+
assert.equal(foreignRemoval.finalContent, foreignSource);
|
|
134
|
+
const controlCodePoints = [
|
|
135
|
+
...Array.from({ length: 0x20 }, (_, codePoint) => codePoint),
|
|
136
|
+
...Array.from({ length: 0x21 }, (_, offset) => 0x7f + offset),
|
|
137
|
+
];
|
|
138
|
+
const ambiguousCommands = [
|
|
139
|
+
"node ./dist/scripts/codex-native-hook.js",
|
|
140
|
+
"node dist/scripts/codex-native-hook.js",
|
|
141
|
+
"node ../historical/dist/scripts/codex-native-hook.js",
|
|
142
|
+
...controlCodePoints.map((codePoint) => `node "/historical/dist/scripts/codex-native-hook.js${String.fromCodePoint(codePoint)}"`),
|
|
143
|
+
];
|
|
144
|
+
for (const command of ambiguousCommands) {
|
|
145
|
+
const source = JSON.stringify({
|
|
146
|
+
hooks: {
|
|
147
|
+
PreToolUse: [{ hooks: [{ type: "command", command }] }],
|
|
148
|
+
},
|
|
149
|
+
});
|
|
150
|
+
const merge = planManagedCodexHooksMerge(source, "/repo", "/hooks.json", { platform: "linux" });
|
|
151
|
+
const removal = planManagedCodexHooksRemoval(source, "/hooks.json", { platform: "linux" });
|
|
152
|
+
const context = JSON.stringify(command);
|
|
153
|
+
assert.equal(isManagedCodexHookCommand(command), false, context);
|
|
154
|
+
assert.equal(merge.ok, false, context);
|
|
155
|
+
assert.equal(removal.ok, false, context);
|
|
156
|
+
if (!merge.ok) {
|
|
157
|
+
assert.equal(merge.error.code, "ambiguous_managed_handler", context);
|
|
158
|
+
assert.equal("finalContent" in merge, false, context);
|
|
159
|
+
}
|
|
160
|
+
if (!removal.ok) {
|
|
161
|
+
assert.equal(removal.error.code, "ambiguous_managed_handler", context);
|
|
162
|
+
assert.equal("finalContent" in removal, false, context);
|
|
163
|
+
}
|
|
164
|
+
}
|
|
165
|
+
});
|
|
166
|
+
it("fails closed for decoded POSIX OMX spellings across command fields", () => {
|
|
167
|
+
const exactEscapedCommand = String.raw `node /repo/dist/scripts/codex-native-ho\ok.js`;
|
|
168
|
+
assert.equal(isManagedCodexHookCommand(exactEscapedCommand), true);
|
|
169
|
+
const decodedNonExactCommands = [
|
|
170
|
+
["escaped filename with extra argv", String.raw `node /repo/dist/scripts/codex-native-ho\ok.js --extra`],
|
|
171
|
+
["escaped filename with comment", String.raw `node /repo/dist/scripts/codex-native-ho\ok.js # retained comment`],
|
|
172
|
+
["backslash-newline filename split", "node /repo/dist/scripts/codex-native-ho\\\nok.js --extra"],
|
|
173
|
+
];
|
|
174
|
+
const commandFields = [
|
|
175
|
+
["command", (command) => ({ command })],
|
|
176
|
+
["commandWindows", (command) => ({
|
|
177
|
+
command: 'node "/repo/dist/scripts/codex-native-hook.js"',
|
|
178
|
+
commandWindows: command,
|
|
179
|
+
})],
|
|
180
|
+
["command_windows", (command) => ({
|
|
181
|
+
command: 'node "/repo/dist/scripts/codex-native-hook.js"',
|
|
182
|
+
command_windows: command,
|
|
183
|
+
})],
|
|
184
|
+
];
|
|
185
|
+
for (const [description, command] of decodedNonExactCommands) {
|
|
186
|
+
for (const [field, commandFieldsForHandler] of commandFields) {
|
|
187
|
+
const source = JSON.stringify({
|
|
188
|
+
hooks: {
|
|
189
|
+
PreToolUse: [{
|
|
190
|
+
hooks: [{ type: "command", ...commandFieldsForHandler(command) }],
|
|
191
|
+
}],
|
|
192
|
+
},
|
|
193
|
+
});
|
|
194
|
+
const context = `${field}: ${description}`;
|
|
195
|
+
const merge = planManagedCodexHooksMerge(source, "/repo", "/hooks.json", { platform: "linux" });
|
|
196
|
+
const removal = planManagedCodexHooksRemoval(source, "/hooks.json", { platform: "linux" });
|
|
197
|
+
assert.equal(isManagedCodexHookCommand(command), false, context);
|
|
198
|
+
assert.equal(merge.ok, false, context);
|
|
199
|
+
assert.equal(removal.ok, false, context);
|
|
200
|
+
if (!merge.ok) {
|
|
201
|
+
assert.equal(merge.error.code, "ambiguous_managed_handler", context);
|
|
202
|
+
assert.equal("finalContent" in merge, false, context);
|
|
203
|
+
}
|
|
204
|
+
if (!removal.ok) {
|
|
205
|
+
assert.equal(removal.error.code, "ambiguous_managed_handler", context);
|
|
206
|
+
assert.equal("finalContent" in removal, false, context);
|
|
207
|
+
}
|
|
208
|
+
}
|
|
209
|
+
}
|
|
210
|
+
});
|
|
211
|
+
it("owns only the exact current drive-less Windows shim command from supplied options", () => {
|
|
212
|
+
const options = {
|
|
213
|
+
platform: "win32",
|
|
214
|
+
codexHomeDir: "/fixture codex home",
|
|
215
|
+
env: { SystemRoot: "C:\\Windows" },
|
|
216
|
+
};
|
|
217
|
+
const command = buildManagedCodexNativeHookCommand("/repo", options);
|
|
218
|
+
const source = JSON.stringify({
|
|
219
|
+
hooks: {
|
|
220
|
+
PreToolUse: [{ hooks: [{ type: "command", command }] }],
|
|
221
|
+
},
|
|
222
|
+
});
|
|
223
|
+
const merge = planManagedCodexHooksMerge(source, "/repo", "/fixture codex home/hooks.json", options);
|
|
224
|
+
const removal = planManagedCodexHooksRemoval(source, "/fixture codex home/hooks.json", options);
|
|
225
|
+
assert.match(command, /-File '\\fixture codex home\\hooks\\omx-native-hook-windows-shim\.ps1'$/);
|
|
226
|
+
assert.equal(isManagedCodexHookCommand(command), false);
|
|
227
|
+
assert.equal(merge.ok, true);
|
|
228
|
+
assert.equal(removal.ok, true);
|
|
229
|
+
if (removal.ok)
|
|
230
|
+
assert.equal(removal.finalContent, null);
|
|
231
|
+
const altered = command.replace("\\fixture codex home\\", "\\foreign codex home\\");
|
|
232
|
+
const alteredSource = JSON.stringify({
|
|
233
|
+
hooks: {
|
|
234
|
+
PreToolUse: [{ hooks: [{ type: "command", command: altered }] }],
|
|
235
|
+
},
|
|
236
|
+
});
|
|
237
|
+
const alteredMerge = planManagedCodexHooksMerge(alteredSource, "/repo", "/fixture codex home/hooks.json", options);
|
|
238
|
+
const alteredRemoval = planManagedCodexHooksRemoval(alteredSource, "/fixture codex home/hooks.json", options);
|
|
239
|
+
assert.equal(alteredMerge.ok, false);
|
|
240
|
+
assert.equal(alteredRemoval.ok, false);
|
|
241
|
+
if (!alteredMerge.ok)
|
|
242
|
+
assert.equal(alteredMerge.error.code, "ambiguous_managed_handler");
|
|
243
|
+
if (!alteredRemoval.ok)
|
|
244
|
+
assert.equal(alteredRemoval.error.code, "ambiguous_managed_handler");
|
|
245
|
+
});
|
|
246
|
+
it("owns only exact current host-joined Windows shim commands on non-Windows seams", () => {
|
|
247
|
+
if (process.platform === "win32")
|
|
248
|
+
return;
|
|
249
|
+
const env = { SystemRoot: "C:\\Windows" };
|
|
250
|
+
const fixtures = [
|
|
251
|
+
{
|
|
252
|
+
label: "supplied codex home",
|
|
253
|
+
options: { platform: "win32", codexHomeDir: "/fixture codex home", env },
|
|
254
|
+
hooksPath: "/fixture codex home/hooks.json",
|
|
255
|
+
codexHomeDir: "/fixture codex home",
|
|
256
|
+
},
|
|
257
|
+
{
|
|
258
|
+
label: "derived codex home",
|
|
259
|
+
options: { platform: "win32", env },
|
|
260
|
+
hooksPath: "/derived codex home/hooks.json",
|
|
261
|
+
codexHomeDir: "/derived codex home",
|
|
262
|
+
},
|
|
263
|
+
];
|
|
264
|
+
for (const { label, options, hooksPath, codexHomeDir } of fixtures) {
|
|
265
|
+
const hostShimPath = join(codexHomeDir, "hooks", "omx-native-hook-windows-shim.ps1");
|
|
266
|
+
const powerShellPath = resolveWindowsPowerShellPath(env);
|
|
267
|
+
const command = `& '${powerShellPath}' -NoProfile -ExecutionPolicy Bypass -File '${hostShimPath}'`;
|
|
268
|
+
const source = JSON.stringify({
|
|
269
|
+
hooks: {
|
|
270
|
+
PreToolUse: [{ hooks: [{ type: "command", command }] }],
|
|
271
|
+
},
|
|
272
|
+
});
|
|
273
|
+
const merge = planManagedCodexHooksMerge(source, "/repo", hooksPath, options);
|
|
274
|
+
const removal = planManagedCodexHooksRemoval(source, hooksPath, options);
|
|
275
|
+
assert.match(command, /\/hooks\/omx-native-hook-windows-shim\.ps1'$/);
|
|
276
|
+
assert.equal(isManagedCodexHookCommand(command), false, label);
|
|
277
|
+
assert.equal(merge.ok, true, label);
|
|
278
|
+
assert.equal(removal.ok, true, label);
|
|
279
|
+
if (removal.ok)
|
|
280
|
+
assert.equal(removal.finalContent, null, label);
|
|
281
|
+
const alteredShimPath = join("/foreign codex home", "hooks", "omx-native-hook-windows-shim.ps1");
|
|
282
|
+
const altered = command.replace(hostShimPath, alteredShimPath);
|
|
283
|
+
const alteredSource = JSON.stringify({
|
|
284
|
+
hooks: {
|
|
285
|
+
PreToolUse: [{ hooks: [{ type: "command", command: altered }] }],
|
|
286
|
+
},
|
|
287
|
+
});
|
|
288
|
+
const alteredMerge = planManagedCodexHooksMerge(alteredSource, "/repo", hooksPath, options);
|
|
289
|
+
const alteredRemoval = planManagedCodexHooksRemoval(alteredSource, hooksPath, options);
|
|
290
|
+
assert.equal(alteredMerge.ok, false, label);
|
|
291
|
+
assert.equal(alteredRemoval.ok, false, label);
|
|
292
|
+
if (!alteredMerge.ok)
|
|
293
|
+
assert.equal(alteredMerge.error.code, "ambiguous_managed_handler", label);
|
|
294
|
+
if (!alteredRemoval.ok)
|
|
295
|
+
assert.equal(alteredRemoval.error.code, "ambiguous_managed_handler", label);
|
|
296
|
+
}
|
|
297
|
+
});
|
|
298
|
+
it("fails closed for Windows direct command expansion, quoting, wrappers, and invalid cross-fields", () => {
|
|
299
|
+
const script = "C:\\repo\\dist\\scripts\\codex-native-hook.js";
|
|
300
|
+
const direct = `node "${script}"`;
|
|
301
|
+
const invalidDirectCommands = [
|
|
302
|
+
`node "%OMX_ROOT%\\dist\\scripts\\codex-native-hook.js"`,
|
|
303
|
+
`node "!OMX_ROOT!\\dist\\scripts\\codex-native-hook.js"`,
|
|
304
|
+
`node "$(Get-Location)\\dist\\scripts\\codex-native-hook.js"`,
|
|
305
|
+
`node '${script}'`,
|
|
306
|
+
`node "${script}`,
|
|
307
|
+
`node "${script}\\"`,
|
|
308
|
+
`${direct} & echo injected`,
|
|
309
|
+
`${direct} ^& echo injected`,
|
|
310
|
+
`${direct} --extra`,
|
|
311
|
+
`cmd.exe /d /c ${direct}`,
|
|
312
|
+
`${direct}\r\necho injected`,
|
|
313
|
+
`node ".\\dist\\scripts\\codex-native-hook.js"`,
|
|
314
|
+
`node "C:historical\\dist\\scripts\\codex-native-hook.js"`,
|
|
315
|
+
];
|
|
316
|
+
const crossFieldHandlers = [
|
|
317
|
+
{ command: `node '${script}'`, commandWindows: direct },
|
|
318
|
+
{ command: direct, command_windows: `node '${script}'` },
|
|
319
|
+
{
|
|
320
|
+
command: 'node "/repo/dist/scripts/codex-native-hook.js"',
|
|
321
|
+
commandWindows: "node '/repo/dist/scripts/codex-native-hook.js'",
|
|
322
|
+
},
|
|
323
|
+
];
|
|
324
|
+
const cases = [
|
|
325
|
+
...invalidDirectCommands.map((command) => ({
|
|
326
|
+
handler: { command },
|
|
327
|
+
options: { platform: "win32" },
|
|
328
|
+
})),
|
|
329
|
+
{
|
|
330
|
+
handler: crossFieldHandlers[0],
|
|
331
|
+
options: { platform: "win32" },
|
|
332
|
+
},
|
|
333
|
+
{
|
|
334
|
+
handler: crossFieldHandlers[1],
|
|
335
|
+
options: { platform: "win32" },
|
|
336
|
+
},
|
|
337
|
+
{
|
|
338
|
+
handler: crossFieldHandlers[2],
|
|
339
|
+
options: { platform: "linux" },
|
|
340
|
+
},
|
|
341
|
+
];
|
|
342
|
+
for (const { handler, options } of cases) {
|
|
343
|
+
const source = JSON.stringify({
|
|
344
|
+
hooks: {
|
|
345
|
+
PreToolUse: [{ hooks: [{ type: "command", ...handler }] }],
|
|
346
|
+
},
|
|
347
|
+
});
|
|
348
|
+
const merge = planManagedCodexHooksMerge(source, "C:\\repo", "C:\\Users\\Ada\\.codex\\hooks.json", options);
|
|
349
|
+
const removal = planManagedCodexHooksRemoval(source, "C:\\Users\\Ada\\.codex\\hooks.json", options);
|
|
350
|
+
assert.equal(merge.ok, false, source);
|
|
351
|
+
assert.equal(removal.ok, false, source);
|
|
352
|
+
if (!merge.ok) {
|
|
353
|
+
assert.equal(merge.error.code, "ambiguous_managed_handler", source);
|
|
354
|
+
assert.equal("finalContent" in merge, false);
|
|
355
|
+
}
|
|
356
|
+
if (!removal.ok) {
|
|
357
|
+
assert.equal(removal.error.code, "ambiguous_managed_handler", source);
|
|
358
|
+
assert.equal("finalContent" in removal, false);
|
|
359
|
+
}
|
|
360
|
+
}
|
|
361
|
+
const staticSpecialPath = `"C:\\Program Files (x86)\\node.exe" "C:\\Users\\O'Brien\\@scope & safe\\dist\\scripts\\codex-native-hook.js"`;
|
|
362
|
+
assert.equal(isManagedCodexHookCommand(staticSpecialPath), true);
|
|
363
|
+
const validCrossField = JSON.stringify({
|
|
364
|
+
hooks: {
|
|
365
|
+
PreToolUse: [{
|
|
366
|
+
hooks: [{
|
|
367
|
+
type: "command",
|
|
368
|
+
command: 'node "/repo/dist/scripts/codex-native-hook.js"',
|
|
369
|
+
commandWindows: direct,
|
|
370
|
+
}],
|
|
371
|
+
}],
|
|
372
|
+
},
|
|
373
|
+
});
|
|
374
|
+
for (const platform of ["linux", "win32"]) {
|
|
375
|
+
const pkgRoot = platform === "win32" ? "C:\\repo" : "/repo";
|
|
376
|
+
const hooksPath = platform === "win32" ? "C:\\Users\\Ada\\.codex\\hooks.json" : "/hooks.json";
|
|
377
|
+
const options = { platform };
|
|
378
|
+
const merge = planManagedCodexHooksMerge(validCrossField, pkgRoot, hooksPath, options);
|
|
379
|
+
const removal = planManagedCodexHooksRemoval(validCrossField, hooksPath, options);
|
|
380
|
+
assert.equal(merge.ok, true, `${platform} must accept platform-specific managed command fields`);
|
|
381
|
+
assert.equal(removal.ok, true, `${platform} must remove platform-specific managed command fields`);
|
|
382
|
+
}
|
|
383
|
+
});
|
|
384
|
+
it("fails closed for unqualified direct executables and quoted PowerShell executable paths across command fields", () => {
|
|
385
|
+
const posixScript = "/historical/dist/scripts/codex-native-hook.js";
|
|
386
|
+
const windowsScript = "C:\\historical\\dist\\scripts\\codex-native-hook.js";
|
|
387
|
+
const powerShellExecutable = "C:\\Windows\\System32\\WindowsPowerShell\\v1.0\\powershell.exe";
|
|
388
|
+
const shimPath = "C:\\Users\\Ada\\.codex\\hooks\\omx-native-hook-windows-shim.ps1";
|
|
389
|
+
const fallbackPosixCommand = `node "${posixScript}"`;
|
|
390
|
+
const unsafeCommands = [
|
|
391
|
+
["POSIX dot-relative executable", `./node "${posixScript}"`, "linux"],
|
|
392
|
+
["POSIX parent-relative executable", `../node "${posixScript}"`, "linux"],
|
|
393
|
+
["POSIX relative executable", `tools/node "${posixScript}"`, "linux"],
|
|
394
|
+
["Windows dot-relative executable", `.\\node.exe "${windowsScript}"`, "win32"],
|
|
395
|
+
["Windows relative executable", `tools\\node.exe "${windowsScript}"`, "win32"],
|
|
396
|
+
["Windows drive-relative executable", `C:node.exe "${windowsScript}"`, "win32"],
|
|
397
|
+
[
|
|
398
|
+
"PowerShell dot-relative executable",
|
|
399
|
+
`.\\powershell.exe -NoProfile -ExecutionPolicy Bypass -File '${shimPath}'`,
|
|
400
|
+
"win32",
|
|
401
|
+
],
|
|
402
|
+
[
|
|
403
|
+
"PowerShell relative executable",
|
|
404
|
+
`tools\\powershell.exe -NoProfile -ExecutionPolicy Bypass -File '${shimPath}'`,
|
|
405
|
+
"win32",
|
|
406
|
+
],
|
|
407
|
+
[
|
|
408
|
+
"PowerShell drive-relative executable",
|
|
409
|
+
`C:powershell.exe -NoProfile -ExecutionPolicy Bypass -File '${shimPath}'`,
|
|
410
|
+
"win32",
|
|
411
|
+
],
|
|
412
|
+
[
|
|
413
|
+
"quoted PowerShell executable without call operator",
|
|
414
|
+
`'${powerShellExecutable}' -NoProfile -ExecutionPolicy Bypass -File '${shimPath}'`,
|
|
415
|
+
"win32",
|
|
416
|
+
],
|
|
417
|
+
];
|
|
418
|
+
const assertAmbiguous = (source, platform, context) => {
|
|
419
|
+
const pkgRoot = platform === "win32" ? "C:\\repo" : "/repo";
|
|
420
|
+
const hooksPath = platform === "win32" ? "C:\\Users\\Ada\\.codex\\hooks.json" : "/hooks.json";
|
|
421
|
+
const merge = planManagedCodexHooksMerge(source, pkgRoot, hooksPath, { platform });
|
|
422
|
+
const removal = planManagedCodexHooksRemoval(source, hooksPath, { platform });
|
|
423
|
+
assert.equal(merge.ok, false, context);
|
|
424
|
+
assert.equal(removal.ok, false, context);
|
|
425
|
+
if (!merge.ok) {
|
|
426
|
+
assert.equal(merge.error.code, "ambiguous_managed_handler", context);
|
|
427
|
+
assert.equal("finalContent" in merge, false, context);
|
|
428
|
+
}
|
|
429
|
+
if (!removal.ok) {
|
|
430
|
+
assert.equal(removal.error.code, "ambiguous_managed_handler", context);
|
|
431
|
+
assert.equal("finalContent" in removal, false, context);
|
|
432
|
+
}
|
|
433
|
+
};
|
|
434
|
+
for (const [spelling, unsafeCommand, primaryPlatform] of unsafeCommands) {
|
|
435
|
+
assert.equal(isManagedCodexHookCommand(unsafeCommand), false, spelling);
|
|
436
|
+
assertAmbiguous(JSON.stringify({
|
|
437
|
+
hooks: {
|
|
438
|
+
PreToolUse: [{ hooks: [{ type: "command", command: unsafeCommand }] }],
|
|
439
|
+
},
|
|
440
|
+
}), primaryPlatform, `${spelling} in command`);
|
|
441
|
+
for (const [field, handler] of [
|
|
442
|
+
["commandWindows", { command: fallbackPosixCommand, commandWindows: unsafeCommand }],
|
|
443
|
+
["command_windows", { command: fallbackPosixCommand, command_windows: unsafeCommand }],
|
|
444
|
+
]) {
|
|
445
|
+
assertAmbiguous(JSON.stringify({
|
|
446
|
+
hooks: {
|
|
447
|
+
PreToolUse: [{ hooks: [{ type: "command", ...handler }] }],
|
|
448
|
+
},
|
|
449
|
+
}), "win32", `${spelling} in ${field}`);
|
|
450
|
+
}
|
|
451
|
+
}
|
|
452
|
+
});
|
|
453
|
+
it("fails closed for Windows direct-node and PowerShell shim quote concatenation across command fields", () => {
|
|
454
|
+
const posixScript = "/repo/dist/scripts/codex-native-hook.js";
|
|
455
|
+
const windowsScript = "C:\\repo\\dist\\scripts\\codex-native-hook.js";
|
|
456
|
+
const shimPath = "C:\\Users\\Ada\\.codex\\hooks\\omx-native-hook-windows-shim.ps1";
|
|
457
|
+
const shimPrefix = "powershell.exe -NoProfile -ExecutionPolicy Bypass -File";
|
|
458
|
+
const malformedCommands = [
|
|
459
|
+
["direct doubled", `node "C:\\repo\\dist\\scripts\\codex""-native-hook.js"`],
|
|
460
|
+
["direct adjacent", `node ""${windowsScript}""`],
|
|
461
|
+
["direct triple", `node """${windowsScript}"""`],
|
|
462
|
+
["shim doubled", `${shimPrefix} "C:\\Users\\Ada\\.codex\\hooks\\omx""-native-hook-windows-shim.ps1"`],
|
|
463
|
+
["shim adjacent", `${shimPrefix} ""${shimPath}""`],
|
|
464
|
+
["shim triple", `${shimPrefix} """${shimPath}"""`],
|
|
465
|
+
];
|
|
466
|
+
for (const [spelling, malformed] of malformedCommands) {
|
|
467
|
+
for (const [field, handler] of [
|
|
468
|
+
["command", { command: malformed }],
|
|
469
|
+
["commandWindows", { command: `node "${posixScript}"`, commandWindows: malformed }],
|
|
470
|
+
["command_windows", { command: `node "${posixScript}"`, command_windows: malformed }],
|
|
471
|
+
]) {
|
|
472
|
+
const source = JSON.stringify({
|
|
473
|
+
hooks: {
|
|
474
|
+
PreToolUse: [{ hooks: [{ type: "command", ...handler }] }],
|
|
475
|
+
},
|
|
476
|
+
});
|
|
477
|
+
const context = `${spelling} in ${field}: ${source}`;
|
|
478
|
+
const merge = planManagedCodexHooksMerge(source, "C:\\repo", "C:\\Users\\Ada\\.codex\\hooks.json", { platform: "win32" });
|
|
479
|
+
const removal = planManagedCodexHooksRemoval(source, "C:\\Users\\Ada\\.codex\\hooks.json", { platform: "win32" });
|
|
480
|
+
assert.equal(merge.ok, false, context);
|
|
481
|
+
assert.equal(removal.ok, false, context);
|
|
482
|
+
if (!merge.ok) {
|
|
483
|
+
assert.equal(merge.error.code, "ambiguous_managed_handler", context);
|
|
484
|
+
assert.equal("finalContent" in merge, false, context);
|
|
485
|
+
}
|
|
486
|
+
if (!removal.ok) {
|
|
487
|
+
assert.equal(removal.error.code, "ambiguous_managed_handler", context);
|
|
488
|
+
assert.equal("finalContent" in removal, false, context);
|
|
489
|
+
}
|
|
490
|
+
}
|
|
491
|
+
}
|
|
492
|
+
});
|
|
493
|
+
it("fails closed for invalid Windows path syntax in direct and PowerShell commands", () => {
|
|
494
|
+
const posixCommand = 'node "/repo/dist/scripts/codex-native-hook.js"';
|
|
495
|
+
const directExecutable = "C:\\base\\node.exe";
|
|
496
|
+
const directScript = "C:\\base\\dist\\scripts\\codex-native-hook.js";
|
|
497
|
+
const powerShellExecutable = "C:\\base\\System32\\WindowsPowerShell\\v1.0\\powershell.exe";
|
|
498
|
+
const shimPath = "C:\\base\\hooks\\omx-native-hook-windows-shim.ps1";
|
|
499
|
+
const invalidWindowsPaths = [
|
|
500
|
+
["pipe", (path) => path.replace("\\base\\", "\\ba|se\\")],
|
|
501
|
+
["angle brackets", (path) => path.replace("\\base\\", "\\ba<>se\\")],
|
|
502
|
+
["wildcards", (path) => path.replace("\\base\\", "\\ba?*se\\")],
|
|
503
|
+
["embedded quote", (path) => path.replace("\\base\\", '\\ba\\"se\\')],
|
|
504
|
+
["control character", (path) => path.replace("\\base\\", "\\ba\u0000se\\")],
|
|
505
|
+
["alternate data stream", (path) => path.replace("\\base\\", "\\base:stream\\")],
|
|
506
|
+
["extended namespace", (path) => `\\\\?\\${path}`],
|
|
507
|
+
["device namespace", (path) => `\\\\.\\${path}`],
|
|
508
|
+
["NT device namespace", (path) => `\\Device\\HarddiskVolume2${path.slice(2)}`],
|
|
509
|
+
["DOS device name", (path) => path.replace("\\base\\", "\\NUL\\")],
|
|
510
|
+
["DOS COM device stem before extension", (path) => path.replace("\\base\\", "\\COM1 .cache\\")],
|
|
511
|
+
["DOS NUL device stem before extension", (path) => path.replace("\\base\\", "\\NUL .dir\\")],
|
|
512
|
+
["superscript COM device name", (path) => path.replace("\\base\\", "\\COM¹\\")],
|
|
513
|
+
["superscript LPT device name", (path) => path.replace("\\base\\", "\\LPT²\\")],
|
|
514
|
+
["trailing component period", (path) => path.replace("\\base\\", "\\base.\\")],
|
|
515
|
+
["trailing component space", (path) => path.replace("\\base\\", "\\base " + String.fromCharCode(92))],
|
|
516
|
+
];
|
|
517
|
+
const invalidCommands = invalidWindowsPaths.flatMap(([syntax, invalidPath]) => [
|
|
518
|
+
[`direct executable ${syntax}`, `"${invalidPath(directExecutable)}" "${directScript}"`],
|
|
519
|
+
[`direct script ${syntax}`, `node "${invalidPath(directScript)}"`],
|
|
520
|
+
[
|
|
521
|
+
`PowerShell executable ${syntax}`,
|
|
522
|
+
`& '${invalidPath(powerShellExecutable)}' -NoProfile -ExecutionPolicy Bypass -File '${shimPath}'`,
|
|
523
|
+
],
|
|
524
|
+
[
|
|
525
|
+
`PowerShell shim ${syntax}`,
|
|
526
|
+
`& '${powerShellExecutable}' -NoProfile -ExecutionPolicy Bypass -File '${invalidPath(shimPath)}'`,
|
|
527
|
+
],
|
|
528
|
+
]);
|
|
529
|
+
const validDirect = '"C:\\Program Files (x86)\\node.exe" "C:\\Users\\O\'Brien\\@scope & safe\\(fixture)\\dist\\scripts\\codex-native-hook.js"';
|
|
530
|
+
const validUncDirect = '"\\\\server\\share\\node.exe" "\\\\server\\share\\repo\\dist\\scripts\\codex-native-hook.js"';
|
|
531
|
+
const validShimPath = "C:\\Users\\O'Brien\\@scope & safe\\(fixture)\\.codex\\hooks\\omx-native-hook-windows-shim.ps1";
|
|
532
|
+
const validPowerShell = "& 'C:\\Program Files (x86)\\WindowsPowerShell\\v1.0\\powershell.exe' -NoProfile -ExecutionPolicy Bypass -File 'C:\\Users\\O''Brien\\@scope & safe\\(fixture)\\.codex\\hooks\\omx-native-hook-windows-shim.ps1'";
|
|
533
|
+
const rootedPowerShell = "& '\\Windows\\System32\\WindowsPowerShell\\v1.0\\powershell.exe' -NoProfile -ExecutionPolicy Bypass -File '\\Users\\Ada\\.codex\\hooks\\omx-native-hook-windows-shim.ps1'";
|
|
534
|
+
assert.equal(isManagedCodexHookCommand(validDirect), true);
|
|
535
|
+
assert.equal(isManagedCodexHookCommand(validUncDirect), true);
|
|
536
|
+
assert.equal(parseManagedCodexNativeHookWindowsShimCommand(validPowerShell), validShimPath);
|
|
537
|
+
assert.equal(parseManagedCodexNativeHookWindowsShimCommand(rootedPowerShell), null);
|
|
538
|
+
assert.equal(isManagedCodexHookCommand(rootedPowerShell), false);
|
|
539
|
+
for (const [spelling, invalid] of invalidCommands) {
|
|
540
|
+
assert.equal(isManagedCodexHookCommand(invalid), false, spelling);
|
|
541
|
+
for (const [field, handler] of [
|
|
542
|
+
["command", { command: invalid }],
|
|
543
|
+
["commandWindows", { command: posixCommand, commandWindows: invalid }],
|
|
544
|
+
["command_windows", { command: posixCommand, command_windows: invalid }],
|
|
545
|
+
]) {
|
|
546
|
+
const source = JSON.stringify({
|
|
547
|
+
hooks: {
|
|
548
|
+
PreToolUse: [{ hooks: [{ type: "command", ...handler }] }],
|
|
549
|
+
},
|
|
550
|
+
});
|
|
551
|
+
const context = `${spelling} in ${field}: ${source}`;
|
|
552
|
+
const merge = planManagedCodexHooksMerge(source, "C:\\repo", "C:\\Users\\Ada\\.codex\\hooks.json", { platform: "win32" });
|
|
553
|
+
const removal = planManagedCodexHooksRemoval(source, "C:\\Users\\Ada\\.codex\\hooks.json", { platform: "win32" });
|
|
554
|
+
assert.equal(merge.ok, false, context);
|
|
555
|
+
assert.equal(removal.ok, false, context);
|
|
556
|
+
if (!merge.ok) {
|
|
557
|
+
assert.equal(merge.error.code, "ambiguous_managed_handler", context);
|
|
558
|
+
assert.equal("finalContent" in merge, false, context);
|
|
559
|
+
}
|
|
560
|
+
if (!removal.ok) {
|
|
561
|
+
assert.equal(removal.error.code, "ambiguous_managed_handler", context);
|
|
562
|
+
assert.equal("finalContent" in removal, false, context);
|
|
563
|
+
}
|
|
564
|
+
}
|
|
565
|
+
}
|
|
566
|
+
});
|
|
567
|
+
it("preserves compatible mixed matcher source and managed coordinates", () => {
|
|
568
|
+
const matcherCases = [
|
|
569
|
+
{
|
|
570
|
+
eventName: "PreToolUse",
|
|
571
|
+
eventLabel: "pre_tool_use",
|
|
572
|
+
matcherSlice: '"matcher" : null',
|
|
573
|
+
},
|
|
574
|
+
{
|
|
575
|
+
eventName: "SessionStart",
|
|
576
|
+
eventLabel: "session_start",
|
|
577
|
+
matcherSlice: String.raw `"matcher" : "\u0073tartup\u007cresume\u007cclear"`,
|
|
578
|
+
},
|
|
579
|
+
];
|
|
580
|
+
for (const { eventName, eventLabel, matcherSlice } of matcherCases) {
|
|
581
|
+
const source = String.raw `{"hooks":{"${eventName}":[{${matcherSlice},"hooks":[{"type":"command","command":"echo foreign"},{"type":"command","command":"node \"/old/dist/scripts/codex-native-hook.js\""}]}]}}`;
|
|
582
|
+
const merged = planManagedCodexHooksMerge(source, "/repo", "/hooks.json");
|
|
583
|
+
assert.equal(merged.ok, true, source);
|
|
584
|
+
if (!merged.ok)
|
|
585
|
+
continue;
|
|
586
|
+
assert.equal(merged.coordinateProof.safe, true);
|
|
587
|
+
assert.equal(merged.hasForeignHooks, true);
|
|
588
|
+
assert.ok(merged.finalContent?.includes(matcherSlice));
|
|
589
|
+
assert.ok(merged.finalTrustState[`/hooks.json:${eventLabel}:0:1`]);
|
|
590
|
+
const removal = planManagedCodexHooksRemoval(merged.finalContent, "/hooks.json");
|
|
591
|
+
assert.equal(removal.ok, true, merged.finalContent ?? source);
|
|
592
|
+
if (!removal.ok)
|
|
593
|
+
continue;
|
|
594
|
+
assert.equal(removal.coordinateProof.safe, true);
|
|
595
|
+
assert.ok(removal.finalContent?.includes(matcherSlice));
|
|
596
|
+
assert.match(removal.finalContent ?? "", /echo foreign/);
|
|
597
|
+
assert.deepEqual(removal.finalTrustState, {});
|
|
598
|
+
}
|
|
599
|
+
});
|
|
33
600
|
it("emits Windows hooks.json entries with only the cmd-compatible command field", () => {
|
|
34
601
|
const config = buildManagedCodexHooksConfig("D:\\Program Files\\nvm\\v24.12.0\\node_modules\\oh-my-codex", {
|
|
35
602
|
platform: "win32",
|
|
@@ -105,6 +672,86 @@ describe("codex hooks helpers", () => {
|
|
|
105
672
|
const utf8 = Buffer.from(content, "utf-8");
|
|
106
673
|
assert.deepEqual([...utf8.subarray(0, 3)], [0xef, 0xbb, 0xbf]);
|
|
107
674
|
});
|
|
675
|
+
it("classifies only byte-identical Windows shims as current and complete generated variants as historical", () => {
|
|
676
|
+
const expected = Buffer.from(buildManagedCodexNativeHookWindowsShimContent("C:\\Current Install\\oh-my-codex", {
|
|
677
|
+
nodePath: "C:\\Current Node\\node.exe",
|
|
678
|
+
hookScriptPath: "C:\\Current Install\\oh-my-codex\\dist\\scripts\\codex-native-hook.js",
|
|
679
|
+
}), "utf-8");
|
|
680
|
+
const historical = Buffer.from(buildManagedCodexNativeHookWindowsShimContent("C:\\Historical Install\\oh-my-codex", {
|
|
681
|
+
nodePath: "D:\\Historical Node\\O'Malley\\node",
|
|
682
|
+
hookScriptPath: "D:\\Historical Install\\O'Malley\\oh-my-codex\\dist\\scripts\\codex-native-hook.js",
|
|
683
|
+
}), "utf-8");
|
|
684
|
+
assert.equal(classifyManagedCodexNativeHookWindowsShimOwnership(expected, expected), "current");
|
|
685
|
+
assert.equal(classifyManagedCodexNativeHookWindowsShimOwnership(historical, expected), "historical");
|
|
686
|
+
});
|
|
687
|
+
it("requires qualified valid paths before recognizing historical Windows shim ownership", () => {
|
|
688
|
+
const expected = Buffer.from(buildManagedCodexNativeHookWindowsShimContent("C:\\Current Install\\oh-my-codex", {
|
|
689
|
+
nodePath: "C:\\Current Node\\node.exe",
|
|
690
|
+
hookScriptPath: "C:\\Current Install\\oh-my-codex\\dist\\scripts\\codex-native-hook.js",
|
|
691
|
+
}), "utf-8");
|
|
692
|
+
const validNodePath = "D:\\Historical Node\\node.exe";
|
|
693
|
+
const validHookScriptPath = "D:\\Historical Install\\oh-my-codex\\dist\\scripts\\codex-native-hook.js";
|
|
694
|
+
const invalidHistoricalPaths = [
|
|
695
|
+
{ nodePath: "node.exe", hookScriptPath: validHookScriptPath },
|
|
696
|
+
{ nodePath: "D:node.exe", hookScriptPath: validHookScriptPath },
|
|
697
|
+
{ nodePath: ".\\node.exe", hookScriptPath: validHookScriptPath },
|
|
698
|
+
{ nodePath: "D:\\bad|node\\node.exe", hookScriptPath: validHookScriptPath },
|
|
699
|
+
{ nodePath: "D:\\bad\u0000node\\node.exe", hookScriptPath: validHookScriptPath },
|
|
700
|
+
{ nodePath: String.raw `\\?\D:\\Historical Node\\node.exe`, hookScriptPath: validHookScriptPath },
|
|
701
|
+
{ nodePath: String.raw `\Device\HarddiskVolume2\node.exe`, hookScriptPath: validHookScriptPath },
|
|
702
|
+
{ nodePath: "D:\\COM¹\\node.exe", hookScriptPath: validHookScriptPath },
|
|
703
|
+
{ nodePath: "D:\\LPT²\\node.exe", hookScriptPath: validHookScriptPath },
|
|
704
|
+
{ nodePath: "D:\\COM1 .cache\\node.exe", hookScriptPath: validHookScriptPath },
|
|
705
|
+
{ nodePath: "D:\\NUL .dir\\node.exe", hookScriptPath: validHookScriptPath },
|
|
706
|
+
{ nodePath: "D:\\Historical Node.\\node.exe", hookScriptPath: validHookScriptPath },
|
|
707
|
+
{ nodePath: "D:\\Historical Node \\node.exe", hookScriptPath: validHookScriptPath },
|
|
708
|
+
{ nodePath: validNodePath, hookScriptPath: ".\\dist\\scripts\\codex-native-hook.js" },
|
|
709
|
+
{ nodePath: validNodePath, hookScriptPath: "D:historical\\dist\\scripts\\codex-native-hook.js" },
|
|
710
|
+
{ nodePath: validNodePath, hookScriptPath: "D:\\bad?script\\dist\\scripts\\codex-native-hook.js" },
|
|
711
|
+
{ nodePath: validNodePath, hookScriptPath: "D:\\bad\u0000script\\dist\\scripts\\codex-native-hook.js" },
|
|
712
|
+
{ nodePath: validNodePath, hookScriptPath: String.raw `\\?\D:\\Historical Install\\dist\\scripts\\codex-native-hook.js` },
|
|
713
|
+
{ nodePath: validNodePath, hookScriptPath: String.raw `\Device\HarddiskVolume2\dist\scripts\codex-native-hook.js` },
|
|
714
|
+
{ nodePath: validNodePath, hookScriptPath: "D:\\Historical Install.\\dist\\scripts\\codex-native-hook.js" },
|
|
715
|
+
{ nodePath: validNodePath, hookScriptPath: "D:\\Historical Install \\dist\\scripts\\codex-native-hook.js" },
|
|
716
|
+
{ nodePath: validNodePath, hookScriptPath: "D:\\COM1 .cache\\dist\\scripts\\codex-native-hook.js" },
|
|
717
|
+
{ nodePath: validNodePath, hookScriptPath: "D:\\NUL .dir\\dist\\scripts\\codex-native-hook.js" },
|
|
718
|
+
];
|
|
719
|
+
const uncHistorical = Buffer.from(buildManagedCodexNativeHookWindowsShimContent("\\\\server\\share\\Historical Install\\oh-my-codex", {
|
|
720
|
+
nodePath: "\\\\server\\share\\Node O'Brien\\node.exe",
|
|
721
|
+
hookScriptPath: "\\\\server\\share\\Historical Install\\O'Brien\\oh-my-codex\\dist\\scripts\\codex-native-hook.js",
|
|
722
|
+
}), "utf-8");
|
|
723
|
+
assert.equal(classifyManagedCodexNativeHookWindowsShimOwnership(uncHistorical, expected), "historical");
|
|
724
|
+
for (const paths of invalidHistoricalPaths) {
|
|
725
|
+
const content = Buffer.from(buildManagedCodexNativeHookWindowsShimContent("D:\\Historical Install\\oh-my-codex", paths), "utf-8");
|
|
726
|
+
assert.equal(classifyManagedCodexNativeHookWindowsShimOwnership(content, expected), "modified", JSON.stringify(paths));
|
|
727
|
+
}
|
|
728
|
+
});
|
|
729
|
+
it("rejects incomplete, altered, encoding-ambiguous, and unverifiable Windows shim ownership", () => {
|
|
730
|
+
const expected = Buffer.from(buildManagedCodexNativeHookWindowsShimContent("C:\\Current Install\\oh-my-codex", {
|
|
731
|
+
nodePath: "C:\\Current Node\\node.exe",
|
|
732
|
+
hookScriptPath: "C:\\Current Install\\oh-my-codex\\dist\\scripts\\codex-native-hook.js",
|
|
733
|
+
}), "utf-8");
|
|
734
|
+
const historical = Buffer.from(buildManagedCodexNativeHookWindowsShimContent("C:\\Historical Install\\oh-my-codex", {
|
|
735
|
+
nodePath: "D:\\Historical Node\\node.exe",
|
|
736
|
+
hookScriptPath: "D:\\Historical Install\\oh-my-codex\\dist\\scripts\\codex-native-hook.js",
|
|
737
|
+
}), "utf-8");
|
|
738
|
+
const modifiedFixedStatement = Buffer.from(historical.toString("utf-8").replace("$process.WaitForExit()", "$process.WaitForExit(1)"), "utf-8");
|
|
739
|
+
const unverifiableNode = Buffer.from(historical.toString("utf-8").replace("D:\\Historical Node\\node.exe", "D:\\Historical Node\\foreign.exe"), "utf-8");
|
|
740
|
+
const unverifiableHookScript = Buffer.from(historical.toString("utf-8").replace("\\dist\\scripts\\codex-native-hook.js", "\\dist\\scripts\\foreign-hook.js"), "utf-8");
|
|
741
|
+
const invalidUtf8 = Buffer.from(historical);
|
|
742
|
+
invalidUtf8[10] = 0xff;
|
|
743
|
+
for (const candidate of [
|
|
744
|
+
historical.subarray(3),
|
|
745
|
+
historical.subarray(0, historical.length - 1),
|
|
746
|
+
Buffer.concat([historical, Buffer.from("# extra\\n", "utf-8")]),
|
|
747
|
+
modifiedFixedStatement,
|
|
748
|
+
unverifiableNode,
|
|
749
|
+
unverifiableHookScript,
|
|
750
|
+
invalidUtf8,
|
|
751
|
+
]) {
|
|
752
|
+
assert.equal(classifyManagedCodexNativeHookWindowsShimOwnership(candidate, expected), "modified");
|
|
753
|
+
}
|
|
754
|
+
});
|
|
108
755
|
it("forwards payload, stdout, stderr, and non-zero exit through the Windows shim when PowerShell is available", async () => {
|
|
109
756
|
const shell = ["pwsh", "powershell.exe", "powershell"].find((candidate) => {
|
|
110
757
|
const probe = spawnSync(candidate, ["-NoProfile", "-Command", "$PSVersionTable.PSVersion.ToString()"], {
|
|
@@ -158,6 +805,7 @@ describe("codex hooks helpers", () => {
|
|
|
158
805
|
hooks: {
|
|
159
806
|
SessionStart: [
|
|
160
807
|
{
|
|
808
|
+
matcher: "startup|resume|clear",
|
|
161
809
|
hooks: [
|
|
162
810
|
{ type: "command", command: 'node "/old/dist/scripts/codex-native-hook.js"' },
|
|
163
811
|
{ type: "command", command: "echo keep-me" },
|
|
@@ -175,6 +823,103 @@ describe("codex hooks helpers", () => {
|
|
|
175
823
|
assert.match(JSON.stringify(sessionStart), /echo standalone-user/);
|
|
176
824
|
assert.doesNotMatch(JSON.stringify(sessionStart), /Loading OMX session context/);
|
|
177
825
|
});
|
|
826
|
+
it("replaces existing managed groups in place without moving foreign groups", () => {
|
|
827
|
+
const managed = buildManagedCodexHooksConfig("/repo");
|
|
828
|
+
const original = JSON.stringify({
|
|
829
|
+
hooks: {
|
|
830
|
+
SessionStart: [
|
|
831
|
+
{ hooks: [{ type: "command", command: "echo before" }] },
|
|
832
|
+
{
|
|
833
|
+
matcher: "startup|resume|clear",
|
|
834
|
+
hooks: [
|
|
835
|
+
{ type: "command", command: 'node "/old/dist/scripts/codex-native-hook.js"' },
|
|
836
|
+
],
|
|
837
|
+
},
|
|
838
|
+
{ hooks: [{ type: "command", command: "echo after" }] },
|
|
839
|
+
],
|
|
840
|
+
PreToolUse: [
|
|
841
|
+
{ hooks: [{ type: "command", command: "echo pre-before" }] },
|
|
842
|
+
managed.hooks.PreToolUse[0],
|
|
843
|
+
{ hooks: [{ type: "command", command: "echo pre-after" }] },
|
|
844
|
+
],
|
|
845
|
+
},
|
|
846
|
+
});
|
|
847
|
+
const first = mergeManagedCodexHooksConfig(original, "/repo", "/hooks.json");
|
|
848
|
+
const second = mergeManagedCodexHooksConfig(first, "/repo", "/hooks.json");
|
|
849
|
+
const merged = JSON.parse(first);
|
|
850
|
+
assert.equal(second, first);
|
|
851
|
+
assert.deepEqual(merged.hooks.SessionStart.map((entry) => entry.hooks?.[0]?.command), [
|
|
852
|
+
"echo before",
|
|
853
|
+
managed.hooks.SessionStart[0]?.hooks[0]?.command,
|
|
854
|
+
"echo after",
|
|
855
|
+
]);
|
|
856
|
+
assert.deepEqual(merged.hooks.PreToolUse.map((entry) => entry.hooks?.[0]?.command), [
|
|
857
|
+
"echo pre-before",
|
|
858
|
+
managed.hooks.PreToolUse[0]?.hooks[0]?.command,
|
|
859
|
+
"echo pre-after",
|
|
860
|
+
]);
|
|
861
|
+
});
|
|
862
|
+
it("appends missing managed groups without moving unrelated foreign groups", () => {
|
|
863
|
+
const merged = JSON.parse(mergeManagedCodexHooksConfig(JSON.stringify({
|
|
864
|
+
hooks: {
|
|
865
|
+
Stop: [
|
|
866
|
+
{ hooks: [{ type: "command", command: "echo first" }] },
|
|
867
|
+
{ hooks: [{ type: "command", command: "echo second" }] },
|
|
868
|
+
],
|
|
869
|
+
},
|
|
870
|
+
}), "/repo", "/hooks.json"));
|
|
871
|
+
assert.deepEqual(merged.hooks.Stop.map((entry) => entry.hooks?.[0]?.command), [
|
|
872
|
+
"echo first",
|
|
873
|
+
"echo second",
|
|
874
|
+
buildManagedCodexHooksConfig("/repo").hooks.Stop[0]?.hooks[0]?.command,
|
|
875
|
+
]);
|
|
876
|
+
});
|
|
877
|
+
it("derives managed trust keys from the final merged hook group layout", () => {
|
|
878
|
+
const hooksPath = "/home/me/.codex/hooks.json";
|
|
879
|
+
const hooksContent = mergeManagedCodexHooksConfig(JSON.stringify({
|
|
880
|
+
hooks: {
|
|
881
|
+
PreToolUse: [
|
|
882
|
+
{ hooks: [{ type: "command", command: "echo first" }] },
|
|
883
|
+
{ hooks: [{ type: "command", command: "echo second" }] },
|
|
884
|
+
],
|
|
885
|
+
Stop: [
|
|
886
|
+
{ hooks: [{ type: "command", command: "echo keep" }] },
|
|
887
|
+
],
|
|
888
|
+
},
|
|
889
|
+
}), "/repo", hooksPath);
|
|
890
|
+
const state = buildManagedCodexHookTrustState(hooksPath, "/repo", {
|
|
891
|
+
hooksContent,
|
|
892
|
+
});
|
|
893
|
+
assert.ok(state[`${hooksPath}:pre_tool_use:2:0`]);
|
|
894
|
+
assert.ok(state[`${hooksPath}:stop:1:0`]);
|
|
895
|
+
assert.equal(state[`${hooksPath}:pre_tool_use:0:0`], undefined);
|
|
896
|
+
assert.equal(state[`${hooksPath}:stop:0:0`], undefined);
|
|
897
|
+
});
|
|
898
|
+
it("distinguishes an absent final hooks artifact from fallback and scanned content", () => {
|
|
899
|
+
const hooksPath = "/home/me/.codex/hooks.json";
|
|
900
|
+
const fallbackState = buildManagedCodexHookTrustState(hooksPath, "/repo");
|
|
901
|
+
const fallbackToml = buildManagedCodexHookTrustToml(hooksPath, "/repo");
|
|
902
|
+
const normalFinalContent = mergeManagedCodexHooksConfig(JSON.stringify({
|
|
903
|
+
hooks: {
|
|
904
|
+
PreToolUse: [{ hooks: [{ type: "command", command: "echo foreign" }] }],
|
|
905
|
+
},
|
|
906
|
+
}), "/repo", hooksPath);
|
|
907
|
+
const normalState = buildManagedCodexHookTrustState(hooksPath, "/repo", {
|
|
908
|
+
hooksContent: normalFinalContent,
|
|
909
|
+
});
|
|
910
|
+
const normalToml = buildManagedCodexHookTrustToml(hooksPath, "/repo", {
|
|
911
|
+
hooksContent: normalFinalContent,
|
|
912
|
+
});
|
|
913
|
+
assert.deepEqual(buildManagedCodexHookTrustState(hooksPath, "/repo", { hooksContent: null }), {});
|
|
914
|
+
assert.equal(buildManagedCodexHookTrustToml(hooksPath, "/repo", { hooksContent: null }), "");
|
|
915
|
+
assert.deepEqual(buildManagedCodexHookTrustState(hooksPath, "/repo", { hooksContent: undefined }), fallbackState);
|
|
916
|
+
assert.equal(buildManagedCodexHookTrustToml(hooksPath, "/repo", { hooksContent: undefined }), fallbackToml);
|
|
917
|
+
assert.deepEqual(buildManagedCodexHookTrustState(hooksPath, "/repo", { hooksContent: "{}" }), {});
|
|
918
|
+
assert.equal(buildManagedCodexHookTrustToml(hooksPath, "/repo", { hooksContent: "{}" }), "");
|
|
919
|
+
assert.ok(normalState[`${hooksPath}:pre_tool_use:1:0`]);
|
|
920
|
+
assert.equal(normalState[`${hooksPath}:pre_tool_use:0:0`], undefined);
|
|
921
|
+
assert.ok(normalToml.includes(`[hooks.state."${hooksPath}:pre_tool_use:1:0"]`));
|
|
922
|
+
});
|
|
178
923
|
it("builds trust state only for generated OMX hook handlers", () => {
|
|
179
924
|
const state = buildManagedCodexHookTrustState("/home/me/.codex/hooks.json", "/repo");
|
|
180
925
|
const keys = Object.keys(state).sort();
|
|
@@ -257,6 +1002,25 @@ describe("codex hooks helpers", () => {
|
|
|
257
1002
|
const expectedHash = `sha256:${createHash("sha256").update(canonical).digest("hex")}`;
|
|
258
1003
|
assert.equal(state[`${hooksPath}:pre_tool_use:0:0`]?.trusted_hash, expectedHash);
|
|
259
1004
|
});
|
|
1005
|
+
it("hashes u64 timeout literals without losing values above Number.MAX_SAFE_INTEGER", async () => {
|
|
1006
|
+
const command = buildManagedCodexNativeHookCommand("/repo");
|
|
1007
|
+
const lower = "9007199254740992";
|
|
1008
|
+
const higher = "9007199254740993";
|
|
1009
|
+
const scanTimeout = (timeout) => scanManagedCodexHookTrustStateFromContent(`{"hooks":{"PreToolUse":[{"hooks":[{"type":"command","command":${JSON.stringify(command)},"timeout":${timeout}}]}]}}`, "/hooks.json");
|
|
1010
|
+
const lowerScan = scanTimeout(lower);
|
|
1011
|
+
const higherScan = scanTimeout(higher);
|
|
1012
|
+
assert.equal(lowerScan.ok, true);
|
|
1013
|
+
assert.equal(higherScan.ok, true);
|
|
1014
|
+
if (!lowerScan.ok || !higherScan.ok)
|
|
1015
|
+
return;
|
|
1016
|
+
const key = "/hooks.json:pre_tool_use:0:0";
|
|
1017
|
+
const lowerHash = lowerScan.trustState[key]?.trusted_hash;
|
|
1018
|
+
const higherHash = higherScan.trustState[key]?.trusted_hash;
|
|
1019
|
+
assert.notEqual(lowerHash, higherHash);
|
|
1020
|
+
const canonical = `{"event_name":"pre_tool_use","hooks":[{"async":false,"command":${JSON.stringify(command)},"timeout":${higher},"type":"command"}]}`;
|
|
1021
|
+
const { createHash } = await import("node:crypto");
|
|
1022
|
+
assert.equal(higherHash, `sha256:${createHash("sha256").update(canonical).digest("hex")}`);
|
|
1023
|
+
});
|
|
260
1024
|
it("serializes managed hook trust state as TOML tables for config.toml", () => {
|
|
261
1025
|
const toml = buildManagedCodexHookTrustToml("/hooks.json", "/repo");
|
|
262
1026
|
assert.ok(toml.includes('[hooks.state."/hooks.json:pre_tool_use:0:0"]'));
|
|
@@ -351,6 +1115,28 @@ describe("codex hooks helpers", () => {
|
|
|
351
1115
|
}), "/repo", "/hooks.json"));
|
|
352
1116
|
assert.equal(Object.hasOwn(merged, "state"), false);
|
|
353
1117
|
assert.equal(Object.hasOwn(merged.hooks, "state"), false);
|
|
1118
|
+
const exactNestedPlan = planManagedCodexHooksMerge(JSON.stringify({
|
|
1119
|
+
hooks: {
|
|
1120
|
+
state: {
|
|
1121
|
+
"custom:/hooks.json:stop:0:0": {
|
|
1122
|
+
trusted_hash: "sha256:legacy",
|
|
1123
|
+
enabled: false,
|
|
1124
|
+
},
|
|
1125
|
+
},
|
|
1126
|
+
},
|
|
1127
|
+
}), "/repo", "/hooks.json");
|
|
1128
|
+
assert.equal(exactNestedPlan.ok, true);
|
|
1129
|
+
if (exactNestedPlan.ok) {
|
|
1130
|
+
assert.deepEqual(exactNestedPlan.legacyTrustState, {
|
|
1131
|
+
"custom:/hooks.json:stop:0:0": {
|
|
1132
|
+
trusted_hash: "sha256:legacy",
|
|
1133
|
+
enabled: false,
|
|
1134
|
+
},
|
|
1135
|
+
});
|
|
1136
|
+
const finalContent = exactNestedPlan.finalContent;
|
|
1137
|
+
assert.ok(finalContent);
|
|
1138
|
+
assert.equal(Object.hasOwn(JSON.parse(finalContent), "hooks"), true);
|
|
1139
|
+
}
|
|
354
1140
|
});
|
|
355
1141
|
it("keeps managed hook merge idempotent", () => {
|
|
356
1142
|
const first = mergeManagedCodexHooksConfig(null, "/repo", "/hooks.json");
|
|
@@ -362,6 +1148,7 @@ describe("codex hooks helpers", () => {
|
|
|
362
1148
|
hooks: {
|
|
363
1149
|
SessionStart: [
|
|
364
1150
|
{
|
|
1151
|
+
matcher: "startup|resume|clear",
|
|
365
1152
|
hooks: [
|
|
366
1153
|
{ type: "command", command: 'node "D:\\old\\dist\\scripts\\codex-native-hook.js"' },
|
|
367
1154
|
{ type: "command", command: "echo keep-me" },
|
|
@@ -403,14 +1190,14 @@ describe("codex hooks helpers", () => {
|
|
|
403
1190
|
},
|
|
404
1191
|
SessionStart: [
|
|
405
1192
|
{
|
|
1193
|
+
matcher: "startup|resume|clear",
|
|
406
1194
|
hooks: [
|
|
407
|
-
{ type: "command", command: 'node "/repo/dist/scripts/codex-native-hook.js"' },
|
|
408
1195
|
{ type: "command", command: "echo keep-me" },
|
|
1196
|
+
{ type: "command", command: 'node "/repo/dist/scripts/codex-native-hook.js"' },
|
|
409
1197
|
],
|
|
410
1198
|
},
|
|
411
1199
|
],
|
|
412
1200
|
},
|
|
413
|
-
version: 1,
|
|
414
1201
|
});
|
|
415
1202
|
const removedManagedOnly = removeManagedCodexHooks(managedOnly);
|
|
416
1203
|
assert.equal(removedManagedOnly.removedCount > 0, true);
|
|
@@ -424,11 +1211,35 @@ describe("codex hooks helpers", () => {
|
|
|
424
1211
|
assert.ok(removedMixed.nextContent);
|
|
425
1212
|
assert.match(removedMixed.nextContent, /echo keep-me/);
|
|
426
1213
|
assert.doesNotMatch(removedMixed.nextContent, /codex-native-hook\.js/);
|
|
427
|
-
assert.match(removedMixed.nextContent, /"version": 1/);
|
|
428
1214
|
const cleaned = JSON.parse(removedMixed.nextContent);
|
|
429
1215
|
assert.equal(Object.hasOwn(cleaned, "state"), false);
|
|
430
1216
|
assert.equal(Object.hasOwn(cleaned.hooks ?? {}, "state"), false);
|
|
431
1217
|
});
|
|
1218
|
+
for (const handler of [
|
|
1219
|
+
{ type: "prompt", prompt: "Review the future event." },
|
|
1220
|
+
{ type: "agent", prompt: "Handle the future event." },
|
|
1221
|
+
]) {
|
|
1222
|
+
it(`preserves executable foreign future-event ${handler.type} handlers after managed removal`, () => {
|
|
1223
|
+
const futureEvent = [{
|
|
1224
|
+
matcher: "future-source",
|
|
1225
|
+
hooks: [handler],
|
|
1226
|
+
}];
|
|
1227
|
+
const source = JSON.stringify({
|
|
1228
|
+
hooks: {
|
|
1229
|
+
...buildManagedCodexHooksConfig("/repo").hooks,
|
|
1230
|
+
FutureEvent: futureEvent,
|
|
1231
|
+
},
|
|
1232
|
+
});
|
|
1233
|
+
const plan = planManagedCodexHooksRemoval(source, "/hooks.json");
|
|
1234
|
+
assert.equal(plan.ok, true);
|
|
1235
|
+
if (!plan.ok)
|
|
1236
|
+
return;
|
|
1237
|
+
assert.equal(plan.hasForeignHooks, true);
|
|
1238
|
+
assert.ok(plan.finalContent);
|
|
1239
|
+
assert.deepEqual(JSON.parse(plan.finalContent).hooks?.FutureEvent, futureEvent);
|
|
1240
|
+
assert.equal(hasUserCodexHooksAfterManagedRemoval(source), true);
|
|
1241
|
+
});
|
|
1242
|
+
}
|
|
432
1243
|
it("detects user hooks that remain after managed wrapper removal", () => {
|
|
433
1244
|
const managedOnly = JSON.stringify(buildManagedCodexHooksConfig("/repo"));
|
|
434
1245
|
const mixed = JSON.stringify({
|
|
@@ -440,9 +1251,10 @@ describe("codex hooks helpers", () => {
|
|
|
440
1251
|
},
|
|
441
1252
|
SessionStart: [
|
|
442
1253
|
{
|
|
1254
|
+
matcher: "startup|resume|clear",
|
|
443
1255
|
hooks: [
|
|
444
|
-
{ type: "command", command: 'node "/repo/dist/scripts/codex-native-hook.js"' },
|
|
445
1256
|
{ type: "command", command: "echo keep-me" },
|
|
1257
|
+
{ type: "command", command: 'node "/repo/dist/scripts/codex-native-hook.js"' },
|
|
446
1258
|
],
|
|
447
1259
|
},
|
|
448
1260
|
],
|
|
@@ -465,10 +1277,8 @@ describe("codex hooks helpers", () => {
|
|
|
465
1277
|
const config = buildManagedCodexHooksConfig("/repo");
|
|
466
1278
|
assert.ok(config.hooks.PreCompact?.length);
|
|
467
1279
|
assert.ok(config.hooks.PostCompact?.length);
|
|
468
|
-
const
|
|
469
|
-
const
|
|
470
|
-
const preCommand = preCompact[0]?.hooks?.[0]?.command;
|
|
471
|
-
const postCommand = postCompact[0]?.hooks?.[0]?.command;
|
|
1280
|
+
const preCommand = config.hooks.PreCompact[0]?.hooks[0]?.command;
|
|
1281
|
+
const postCommand = config.hooks.PostCompact[0]?.hooks[0]?.command;
|
|
472
1282
|
assert.match(String(preCommand), /codex-native-hook\.js/);
|
|
473
1283
|
assert.match(String(postCommand), /codex-native-hook\.js/);
|
|
474
1284
|
assert.equal(postCommand, preCommand);
|
|
@@ -550,5 +1360,471 @@ describe("codex hooks helpers", () => {
|
|
|
550
1360
|
await rm(cwd, { recursive: true, force: true });
|
|
551
1361
|
}
|
|
552
1362
|
});
|
|
1363
|
+
it("models Codex empty/default/null load behavior without accepting async null", () => {
|
|
1364
|
+
const valid = validateCodexHooksConfigStrict(JSON.stringify({
|
|
1365
|
+
hooks: {
|
|
1366
|
+
PreToolUse: [{
|
|
1367
|
+
matcher: null,
|
|
1368
|
+
hooks: [{
|
|
1369
|
+
type: "command",
|
|
1370
|
+
command: "echo foreign",
|
|
1371
|
+
commandWindows: null,
|
|
1372
|
+
timeout: 0,
|
|
1373
|
+
statusMessage: null,
|
|
1374
|
+
}],
|
|
1375
|
+
}],
|
|
1376
|
+
},
|
|
1377
|
+
}));
|
|
1378
|
+
assert.equal(validateCodexHooksConfigStrict("{}").ok, true);
|
|
1379
|
+
assert.equal(valid.ok, true);
|
|
1380
|
+
if (valid.ok) {
|
|
1381
|
+
assert.equal(valid.groupOccurrences[0]?.groupIndex, 0);
|
|
1382
|
+
assert.equal(valid.handlerOccurrences[0]?.handlerIndex, 0);
|
|
1383
|
+
}
|
|
1384
|
+
const invalid = validateCodexHooksConfigStrict(JSON.stringify({
|
|
1385
|
+
hooks: {
|
|
1386
|
+
PreToolUse: [{ hooks: [{ type: "command", command: "echo", async: null }] }],
|
|
1387
|
+
},
|
|
1388
|
+
}));
|
|
1389
|
+
assert.equal(invalid.ok, false);
|
|
1390
|
+
});
|
|
1391
|
+
it("matches pinned serde_json strict intake for whitespace, Unicode, numbers, and nesting", () => {
|
|
1392
|
+
const validTimeoutPrefix = '{"hooks":{"PreToolUse":[{"hooks":[{"type":"command","command":"echo","timeout":';
|
|
1393
|
+
for (const content of [
|
|
1394
|
+
"\uFEFF{}",
|
|
1395
|
+
"\v{}",
|
|
1396
|
+
"\f{}",
|
|
1397
|
+
"\u00a0{}",
|
|
1398
|
+
`${validTimeoutPrefix}1e400}]}]}}`,
|
|
1399
|
+
'{"hooks":{"PreToolUse":[{"matcher":"\\uD800","hooks":[{"type":"command","command":"echo"}]}]}}',
|
|
1400
|
+
]) {
|
|
1401
|
+
assert.equal(validateCodexHooksConfigStrict(content).ok, false, JSON.stringify(content));
|
|
1402
|
+
}
|
|
1403
|
+
const withinSerdeDepth = `{"x":${"[".repeat(127)}0${"]".repeat(127)}}`;
|
|
1404
|
+
const beyondSerdeDepth = `{"x":${"[".repeat(128)}0${"]".repeat(128)}}`;
|
|
1405
|
+
const within = validateCodexHooksConfigStrict(withinSerdeDepth);
|
|
1406
|
+
const beyond = validateCodexHooksConfigStrict(beyondSerdeDepth);
|
|
1407
|
+
assert.equal(within.ok, false);
|
|
1408
|
+
assert.equal(beyond.ok, false);
|
|
1409
|
+
if (!within.ok)
|
|
1410
|
+
assert.match(within.error.message, /unknown root field x/);
|
|
1411
|
+
if (!beyond.ok)
|
|
1412
|
+
assert.match(beyond.error.message, /must contain a JSON object/);
|
|
1413
|
+
});
|
|
1414
|
+
it("uses the platform-effective Windows command before discovery and skipped-command diagnostics", () => {
|
|
1415
|
+
const withWindowsCommand = JSON.stringify({
|
|
1416
|
+
hooks: {
|
|
1417
|
+
PreToolUse: [{ hooks: [{ type: "command", command: " \t", commandWindows: "echo windows" }] }],
|
|
1418
|
+
},
|
|
1419
|
+
});
|
|
1420
|
+
const windows = validateCodexHooksConfigStrict(withWindowsCommand, { platform: "win32" });
|
|
1421
|
+
assert.equal(windows.ok, true);
|
|
1422
|
+
if (windows.ok) {
|
|
1423
|
+
assert.deepEqual(windows.discoveredCommands.map((entry) => entry.command), ["echo windows"]);
|
|
1424
|
+
assert.equal(windows.diagnostics.some((entry) => entry.code === "empty_command"), false);
|
|
1425
|
+
}
|
|
1426
|
+
const withWindowsAlias = JSON.stringify({
|
|
1427
|
+
hooks: {
|
|
1428
|
+
PreToolUse: [{ hooks: [{ type: "command", command: "echo posix", command_windows: " \n" }] }],
|
|
1429
|
+
},
|
|
1430
|
+
});
|
|
1431
|
+
const windowsAlias = validateCodexHooksConfigStrict(withWindowsAlias, { platform: "win32" });
|
|
1432
|
+
const posix = validateCodexHooksConfigStrict(withWindowsAlias, { platform: "linux" });
|
|
1433
|
+
assert.equal(windowsAlias.ok, true);
|
|
1434
|
+
assert.equal(posix.ok, true);
|
|
1435
|
+
if (windowsAlias.ok) {
|
|
1436
|
+
assert.deepEqual(windowsAlias.discoveredCommands, []);
|
|
1437
|
+
assert.equal(windowsAlias.diagnostics.filter((entry) => entry.code === "empty_command").length, 1);
|
|
1438
|
+
}
|
|
1439
|
+
if (posix.ok)
|
|
1440
|
+
assert.deepEqual(posix.discoveredCommands.map((entry) => entry.command), ["echo posix"]);
|
|
1441
|
+
});
|
|
1442
|
+
it("accepts Codex's match-all matcher and treats whitespace-only commands as skipped", () => {
|
|
1443
|
+
const strict = validateCodexHooksConfigStrict(JSON.stringify({
|
|
1444
|
+
hooks: {
|
|
1445
|
+
PreToolUse: [{
|
|
1446
|
+
matcher: "*",
|
|
1447
|
+
hooks: [
|
|
1448
|
+
{ type: "command", command: " \t" },
|
|
1449
|
+
{ type: "command", command: "echo discovered" },
|
|
1450
|
+
],
|
|
1451
|
+
}],
|
|
1452
|
+
},
|
|
1453
|
+
}));
|
|
1454
|
+
assert.equal(strict.ok, true);
|
|
1455
|
+
if (!strict.ok)
|
|
1456
|
+
return;
|
|
1457
|
+
assert.equal(strict.diagnostics.some((entry) => entry.code === "invalid_matcher"), false);
|
|
1458
|
+
assert.equal(strict.diagnostics.filter((entry) => entry.code === "empty_command").length, 1);
|
|
1459
|
+
assert.deepEqual(strict.discoveredCommands.map((entry) => entry.command), ["echo discovered"]);
|
|
1460
|
+
const managed = buildManagedCodexNativeHookCommand("/repo");
|
|
1461
|
+
const plan = planManagedCodexHooksMerge(JSON.stringify({
|
|
1462
|
+
hooks: {
|
|
1463
|
+
PreToolUse: [{
|
|
1464
|
+
hooks: [
|
|
1465
|
+
{ type: "command", command: managed },
|
|
1466
|
+
{ type: "command", command: "\n\t " },
|
|
1467
|
+
],
|
|
1468
|
+
}],
|
|
1469
|
+
},
|
|
1470
|
+
}), "/repo", "/hooks.json");
|
|
1471
|
+
assert.equal(plan.ok, false);
|
|
1472
|
+
if (!plan.ok)
|
|
1473
|
+
assert.equal(plan.error.code, "ambiguous_managed_handler");
|
|
1474
|
+
});
|
|
1475
|
+
it("uses Rust-regex matcher syntax rather than accepting JavaScript-only assertions", () => {
|
|
1476
|
+
const validateMatcher = (matcher) => validateCodexHooksConfigStrict(JSON.stringify({
|
|
1477
|
+
hooks: {
|
|
1478
|
+
PreToolUse: [{
|
|
1479
|
+
matcher,
|
|
1480
|
+
hooks: [{ type: "command", command: "echo matcher" }],
|
|
1481
|
+
}],
|
|
1482
|
+
},
|
|
1483
|
+
}));
|
|
1484
|
+
for (const matcher of [
|
|
1485
|
+
"(?i)startup",
|
|
1486
|
+
"(?i:startup)",
|
|
1487
|
+
"(?-i)startup",
|
|
1488
|
+
"(?im-s:startup)",
|
|
1489
|
+
"(?x: start up)",
|
|
1490
|
+
"\\Astartup\\z",
|
|
1491
|
+
"\\a\\x41\\u{1F600}",
|
|
1492
|
+
"\\p{Greek}",
|
|
1493
|
+
]) {
|
|
1494
|
+
const result = validateMatcher(matcher);
|
|
1495
|
+
assert.equal(result.ok, true, matcher);
|
|
1496
|
+
if (result.ok) {
|
|
1497
|
+
assert.equal(result.diagnostics.some((entry) => entry.code === "invalid_matcher"), false, matcher);
|
|
1498
|
+
}
|
|
1499
|
+
}
|
|
1500
|
+
for (const matcher of [
|
|
1501
|
+
"(?=startup)startup",
|
|
1502
|
+
"(?<=start)up",
|
|
1503
|
+
"(startup)\\1",
|
|
1504
|
+
"\\q",
|
|
1505
|
+
"\\u0041",
|
|
1506
|
+
"\\x4",
|
|
1507
|
+
"\\p{}",
|
|
1508
|
+
"\\p{NotAUnicodeProperty}",
|
|
1509
|
+
"\\u{D800}",
|
|
1510
|
+
"\\u{DFFF}",
|
|
1511
|
+
]) {
|
|
1512
|
+
const result = validateMatcher(matcher);
|
|
1513
|
+
assert.equal(result.ok, true, matcher);
|
|
1514
|
+
if (result.ok) {
|
|
1515
|
+
assert.equal(result.diagnostics.some((entry) => entry.code === "invalid_matcher"), true, matcher);
|
|
1516
|
+
}
|
|
1517
|
+
}
|
|
1518
|
+
});
|
|
1519
|
+
it("preserves ignored nested events, nonmatching hooks.state, and unknown raw members", () => {
|
|
1520
|
+
const existing = `{"hooks":{"state":{"not":"legacy"},"Future":{"raw":[1,{"keep":true}]},"PreToolUse":[{"opaque":{"source" : [1, 2]},"hooks":[{"type":"command","command":"echo foreign","unknown":{"keep":true}}]}]}}`;
|
|
1521
|
+
const plan = planManagedCodexHooksMerge(existing, "/repo", "/hooks.json");
|
|
1522
|
+
assert.equal(plan.ok, true);
|
|
1523
|
+
if (!plan.ok)
|
|
1524
|
+
return;
|
|
1525
|
+
assert.ok(plan.finalContent?.includes(`"state":{"not":"legacy"}`));
|
|
1526
|
+
assert.ok(plan.finalContent?.includes(`"Future":{"raw":[1,{"keep":true}]}`));
|
|
1527
|
+
assert.ok(plan.finalContent?.includes(`"opaque":{"source" : [1, 2]}`));
|
|
1528
|
+
assert.ok(plan.finalContent?.includes(`"unknown":{"keep":true}`));
|
|
1529
|
+
});
|
|
1530
|
+
it("migrates only exact historical root state and fails closed for nonmatching root state", () => {
|
|
1531
|
+
const exact = planManagedCodexHooksMerge(JSON.stringify({
|
|
1532
|
+
state: {
|
|
1533
|
+
"/hooks.json:stop:0:0": { trusted_hash: "sha256:legacy", enabled: true },
|
|
1534
|
+
},
|
|
1535
|
+
}), "/repo", "/hooks.json");
|
|
1536
|
+
assert.equal(exact.ok, true);
|
|
1537
|
+
if (exact.ok) {
|
|
1538
|
+
assert.deepEqual(exact.legacyTrustState, {
|
|
1539
|
+
"/hooks.json:stop:0:0": { trusted_hash: "sha256:legacy", enabled: true },
|
|
1540
|
+
});
|
|
1541
|
+
assert.doesNotMatch(exact.finalContent ?? "", /"state"/);
|
|
1542
|
+
}
|
|
1543
|
+
const nonmatching = planManagedCodexHooksMerge(JSON.stringify({
|
|
1544
|
+
state: { "/hooks.json:stop:0:0": { trusted_hash: "sha256:legacy", extra: true } },
|
|
1545
|
+
}), "/repo", "/hooks.json");
|
|
1546
|
+
assert.equal(nonmatching.ok, false);
|
|
1547
|
+
if (!nonmatching.ok)
|
|
1548
|
+
assert.equal(nonmatching.error.code, "invalid_document");
|
|
1549
|
+
});
|
|
1550
|
+
it("preserves a legacy __proto__ trust key as an own migration entry", () => {
|
|
1551
|
+
const plan = planManagedCodexHooksMerge('{"state":{"__proto__":{"trusted_hash":"sha256:legacy","enabled":true}}}', "/repo", "/hooks.json");
|
|
1552
|
+
assert.equal(plan.ok, true);
|
|
1553
|
+
if (!plan.ok)
|
|
1554
|
+
return;
|
|
1555
|
+
assert.equal(Object.hasOwn(plan.legacyTrustState, "__proto__"), true);
|
|
1556
|
+
assert.deepEqual(plan.legacyTrustState["__proto__"], {
|
|
1557
|
+
trusted_hash: "sha256:legacy",
|
|
1558
|
+
enabled: true,
|
|
1559
|
+
});
|
|
1560
|
+
});
|
|
1561
|
+
it("coalesces identical root and nested legacy trust state but rejects conflicts", () => {
|
|
1562
|
+
const key = "/hooks.json:stop:0:0";
|
|
1563
|
+
const matching = planManagedCodexHooksMerge(JSON.stringify({
|
|
1564
|
+
state: { [key]: { trusted_hash: "sha256:legacy", enabled: false } },
|
|
1565
|
+
hooks: {
|
|
1566
|
+
state: { [key]: { trusted_hash: "sha256:legacy", enabled: false } },
|
|
1567
|
+
},
|
|
1568
|
+
}), "/repo", "/hooks.json");
|
|
1569
|
+
assert.equal(matching.ok, true);
|
|
1570
|
+
if (matching.ok) {
|
|
1571
|
+
assert.deepEqual(matching.legacyTrustState, {
|
|
1572
|
+
[key]: { trusted_hash: "sha256:legacy", enabled: false },
|
|
1573
|
+
});
|
|
1574
|
+
}
|
|
1575
|
+
const conflicting = planManagedCodexHooksMerge(JSON.stringify({
|
|
1576
|
+
state: { [key]: { trusted_hash: "sha256:root", enabled: false } },
|
|
1577
|
+
hooks: {
|
|
1578
|
+
state: { [key]: { trusted_hash: "sha256:nested", enabled: false } },
|
|
1579
|
+
},
|
|
1580
|
+
}), "/repo", "/hooks.json");
|
|
1581
|
+
assert.equal(conflicting.ok, false);
|
|
1582
|
+
if (!conflicting.ok) {
|
|
1583
|
+
assert.equal(conflicting.error.code, "managed_trust_key_conflict");
|
|
1584
|
+
assert.equal("finalContent" in conflicting, false);
|
|
1585
|
+
}
|
|
1586
|
+
});
|
|
1587
|
+
it("coalesces every exact duplicate nested legacy state and rejects conflicting duplicates", () => {
|
|
1588
|
+
const key = "/hooks.json:stop:0:0";
|
|
1589
|
+
const identical = planManagedCodexHooksMerge(`{"hooks":{"state":{"${key}":{"trusted_hash":"sha256:legacy","enabled":true}},"state":{"${key}":{"trusted_hash":"sha256:legacy","enabled":true}}}}`, "/repo", "/hooks.json");
|
|
1590
|
+
assert.equal(identical.ok, true);
|
|
1591
|
+
if (identical.ok) {
|
|
1592
|
+
assert.deepEqual(identical.legacyTrustState, {
|
|
1593
|
+
[key]: { trusted_hash: "sha256:legacy", enabled: true },
|
|
1594
|
+
});
|
|
1595
|
+
assert.doesNotMatch(identical.finalContent ?? "", /"state"/);
|
|
1596
|
+
}
|
|
1597
|
+
const conflicting = planManagedCodexHooksMerge(`{"hooks":{"state":{"${key}":{"trusted_hash":"sha256:first"}},"state":{"${key}":{"trusted_hash":"sha256:second"}}}}`, "/repo", "/hooks.json");
|
|
1598
|
+
assert.equal(conflicting.ok, false);
|
|
1599
|
+
if (!conflicting.ok)
|
|
1600
|
+
assert.equal(conflicting.error.code, "managed_trust_key_conflict");
|
|
1601
|
+
const nonLegacyDuplicate = planManagedCodexHooksMerge('{"hooks":{"state":{"not":"legacy"},"state":{"not":"legacy"}}}', "/repo", "/hooks.json");
|
|
1602
|
+
assert.equal(nonLegacyDuplicate.ok, false);
|
|
1603
|
+
if (!nonLegacyDuplicate.ok)
|
|
1604
|
+
assert.equal(nonLegacyDuplicate.error.code, "invalid_document");
|
|
1605
|
+
});
|
|
1606
|
+
it("records every raw group and handler occurrence across all ten Codex events", () => {
|
|
1607
|
+
const events = [
|
|
1608
|
+
"PreToolUse",
|
|
1609
|
+
"PermissionRequest",
|
|
1610
|
+
"PostToolUse",
|
|
1611
|
+
"PreCompact",
|
|
1612
|
+
"PostCompact",
|
|
1613
|
+
"SessionStart",
|
|
1614
|
+
"UserPromptSubmit",
|
|
1615
|
+
"SubagentStart",
|
|
1616
|
+
"SubagentStop",
|
|
1617
|
+
"Stop",
|
|
1618
|
+
];
|
|
1619
|
+
const hooks = Object.fromEntries(events.map((eventName) => [eventName, [
|
|
1620
|
+
eventName === "PreToolUse" ? {} : { hooks: [] },
|
|
1621
|
+
{
|
|
1622
|
+
...(eventName === "PreToolUse" ? { matcher: "(" } : {}),
|
|
1623
|
+
hooks: [
|
|
1624
|
+
{ type: "prompt", prompt: "keep" },
|
|
1625
|
+
{ type: "agent", prompt: "keep" },
|
|
1626
|
+
{ type: "command", command: "", async: true },
|
|
1627
|
+
],
|
|
1628
|
+
},
|
|
1629
|
+
]]));
|
|
1630
|
+
const strict = validateCodexHooksConfigStrict(JSON.stringify({ hooks }));
|
|
1631
|
+
assert.equal(strict.ok, true);
|
|
1632
|
+
if (!strict.ok)
|
|
1633
|
+
return;
|
|
1634
|
+
assert.equal(strict.groupOccurrences.length, 20);
|
|
1635
|
+
assert.equal(strict.handlerOccurrences.length, 30);
|
|
1636
|
+
assert.equal(strict.discoveredCommands.length, 0);
|
|
1637
|
+
assert.equal(strict.diagnostics.filter((entry) => entry.code === "invalid_matcher").length, 1);
|
|
1638
|
+
});
|
|
1639
|
+
it("ignores UserPromptSubmit and Stop matchers for discovery while keeping their coordinates", () => {
|
|
1640
|
+
const existing = JSON.stringify({
|
|
1641
|
+
hooks: {
|
|
1642
|
+
UserPromptSubmit: [{ matcher: "(", hooks: [{ type: "command", command: "echo prompt" }] }],
|
|
1643
|
+
Stop: [{ matcher: "(", hooks: [{ type: "command", command: "echo stop" }] }],
|
|
1644
|
+
},
|
|
1645
|
+
});
|
|
1646
|
+
const strict = validateCodexHooksConfigStrict(existing);
|
|
1647
|
+
assert.equal(strict.ok, true);
|
|
1648
|
+
if (strict.ok)
|
|
1649
|
+
assert.equal(strict.diagnostics.some((entry) => entry.code === "invalid_matcher"), false);
|
|
1650
|
+
const plan = planManagedCodexHooksMerge(existing, "/repo", "/hooks.json");
|
|
1651
|
+
assert.equal(plan.ok, true);
|
|
1652
|
+
if (!plan.ok)
|
|
1653
|
+
return;
|
|
1654
|
+
assert.ok(plan.finalTrustState["/hooks.json:user_prompt_submit:1:0"]);
|
|
1655
|
+
assert.ok(plan.finalTrustState["/hooks.json:stop:1:0"]);
|
|
1656
|
+
});
|
|
1657
|
+
it("appends each missing OMX event once and makes the third setup byte-identical", () => {
|
|
1658
|
+
const first = planManagedCodexHooksMerge("{}", "/repo", "/hooks.json");
|
|
1659
|
+
assert.equal(first.ok, true);
|
|
1660
|
+
if (!first.ok)
|
|
1661
|
+
return;
|
|
1662
|
+
assert.deepEqual(first.diagnostics, []);
|
|
1663
|
+
const second = planManagedCodexHooksMerge(first.finalContent, "/repo", "/hooks.json");
|
|
1664
|
+
assert.equal(second.ok, true);
|
|
1665
|
+
if (!second.ok)
|
|
1666
|
+
return;
|
|
1667
|
+
assert.equal(second.changed, false);
|
|
1668
|
+
const finalContent = second.finalContent;
|
|
1669
|
+
assert.ok(finalContent);
|
|
1670
|
+
const merged = JSON.parse(finalContent);
|
|
1671
|
+
assert.deepEqual(Object.keys(merged.hooks ?? {}).sort(), [
|
|
1672
|
+
"PostCompact",
|
|
1673
|
+
"PostToolUse",
|
|
1674
|
+
"PreCompact",
|
|
1675
|
+
"PreToolUse",
|
|
1676
|
+
"SessionStart",
|
|
1677
|
+
"Stop",
|
|
1678
|
+
"UserPromptSubmit",
|
|
1679
|
+
]);
|
|
1680
|
+
});
|
|
1681
|
+
it("replaces a compatible managed handler in place and scans trust from its actual coordinate", () => {
|
|
1682
|
+
const existing = String.raw `{"hooks":{"PreToolUse":[{"hooks":[{"type":"command","command":"echo before"}]},{"opaque":{"keep":[1,2]},"hooks":[{"type":"command","command":"node \"/old/dist/scripts/codex-native-hook.js\"","unknown":{"keep":true}}]},{"hooks":[{"type":"command","command":"echo after"}]}]}}`;
|
|
1683
|
+
const plan = planManagedCodexHooksMerge(existing, "/repo", "/hooks.json");
|
|
1684
|
+
assert.equal(plan.ok, true);
|
|
1685
|
+
if (!plan.ok)
|
|
1686
|
+
return;
|
|
1687
|
+
assert.ok(plan.finalContent?.includes(`"opaque":{"keep":[1,2]}`));
|
|
1688
|
+
assert.ok(plan.finalContent?.includes(`"unknown":{"keep":true}`));
|
|
1689
|
+
assert.ok(plan.finalTrustState["/hooks.json:pre_tool_use:1:0"]);
|
|
1690
|
+
const finalContent = plan.finalContent;
|
|
1691
|
+
assert.ok(finalContent);
|
|
1692
|
+
const scan = scanManagedCodexHookTrustStateFromContent(finalContent, "/hooks.json");
|
|
1693
|
+
assert.equal(scan.ok, true);
|
|
1694
|
+
if (scan.ok)
|
|
1695
|
+
assert.deepEqual(scan.trustState, plan.finalTrustState);
|
|
1696
|
+
});
|
|
1697
|
+
it("fails closed instead of leaving an opaque managed-only group tombstone during removal", () => {
|
|
1698
|
+
const command = buildManagedCodexHooksConfig("/repo").hooks.PreToolUse[0].hooks[0].command;
|
|
1699
|
+
const source = JSON.stringify({
|
|
1700
|
+
hooks: {
|
|
1701
|
+
PreToolUse: [{
|
|
1702
|
+
opaque: { preserve: "exactly" },
|
|
1703
|
+
hooks: [{ type: "command", command }],
|
|
1704
|
+
}],
|
|
1705
|
+
},
|
|
1706
|
+
});
|
|
1707
|
+
const original = source;
|
|
1708
|
+
const plan = planManagedCodexHooksRemoval(source, "/hooks.json");
|
|
1709
|
+
assert.equal(plan.ok, false);
|
|
1710
|
+
if (plan.ok)
|
|
1711
|
+
return;
|
|
1712
|
+
assert.equal(plan.error.code, "unsafe_managed_removal");
|
|
1713
|
+
assert.deepEqual(plan.error.details, {
|
|
1714
|
+
shifted: {
|
|
1715
|
+
kind: "group",
|
|
1716
|
+
eventName: "PreToolUse",
|
|
1717
|
+
oldCoordinate: [0],
|
|
1718
|
+
},
|
|
1719
|
+
});
|
|
1720
|
+
assert.equal("finalContent" in plan, false);
|
|
1721
|
+
assert.equal(source, original);
|
|
1722
|
+
});
|
|
1723
|
+
it("fails closed instead of leaving an opaque duplicate group tombstone during cleanup", () => {
|
|
1724
|
+
const command = buildManagedCodexHooksConfig("/repo").hooks.PreToolUse[0].hooks[0].command;
|
|
1725
|
+
const source = JSON.stringify({
|
|
1726
|
+
hooks: {
|
|
1727
|
+
PreToolUse: [
|
|
1728
|
+
{ hooks: [{ type: "command", command }] },
|
|
1729
|
+
{
|
|
1730
|
+
opaque: { preserve: "exactly" },
|
|
1731
|
+
hooks: [{ type: "command", command }],
|
|
1732
|
+
},
|
|
1733
|
+
],
|
|
1734
|
+
},
|
|
1735
|
+
});
|
|
1736
|
+
const original = source;
|
|
1737
|
+
const plan = planManagedCodexHooksMerge(source, "/repo", "/hooks.json");
|
|
1738
|
+
assert.equal(plan.ok, false);
|
|
1739
|
+
if (plan.ok)
|
|
1740
|
+
return;
|
|
1741
|
+
assert.equal(plan.error.code, "unsafe_managed_removal");
|
|
1742
|
+
assert.deepEqual(plan.error.details, {
|
|
1743
|
+
shifted: {
|
|
1744
|
+
kind: "group",
|
|
1745
|
+
eventName: "PreToolUse",
|
|
1746
|
+
oldCoordinate: [1],
|
|
1747
|
+
},
|
|
1748
|
+
});
|
|
1749
|
+
assert.equal("finalContent" in plan, false);
|
|
1750
|
+
assert.equal(source, original);
|
|
1751
|
+
});
|
|
1752
|
+
it("removes a suffix duplicate only when all surviving foreign coordinates stay fixed", () => {
|
|
1753
|
+
const command = buildManagedCodexHooksConfig("/repo").hooks.SessionStart[0]?.hooks[0]?.command;
|
|
1754
|
+
const safe = JSON.stringify({
|
|
1755
|
+
hooks: {
|
|
1756
|
+
SessionStart: [
|
|
1757
|
+
{ hooks: [{ type: "command", command: "echo foreign" }] },
|
|
1758
|
+
{ matcher: "startup|resume|clear", hooks: [{ type: "command", command }] },
|
|
1759
|
+
{ matcher: "startup|resume|clear", hooks: [{ type: "command", command }] },
|
|
1760
|
+
],
|
|
1761
|
+
},
|
|
1762
|
+
});
|
|
1763
|
+
const plan = planManagedCodexHooksMerge(safe, "/repo", "/hooks.json");
|
|
1764
|
+
assert.equal(plan.ok, true);
|
|
1765
|
+
if (!plan.ok)
|
|
1766
|
+
return;
|
|
1767
|
+
const finalContent = plan.finalContent;
|
|
1768
|
+
assert.ok(finalContent);
|
|
1769
|
+
const merged = JSON.parse(finalContent);
|
|
1770
|
+
assert.equal(merged.hooks.SessionStart.length, 2);
|
|
1771
|
+
assert.ok(plan.finalTrustState["/hooks.json:session_start:1:0"]);
|
|
1772
|
+
const unsafe = JSON.stringify({
|
|
1773
|
+
hooks: {
|
|
1774
|
+
SessionStart: [
|
|
1775
|
+
{ matcher: "startup|resume|clear", hooks: [{ type: "command", command }] },
|
|
1776
|
+
{ matcher: "startup|resume|clear", hooks: [{ type: "command", command }] },
|
|
1777
|
+
{ hooks: [{ type: "command", command: "echo shifted" }] },
|
|
1778
|
+
],
|
|
1779
|
+
},
|
|
1780
|
+
});
|
|
1781
|
+
const unsafePlan = planManagedCodexHooksMerge(unsafe, "/repo", "/hooks.json");
|
|
1782
|
+
assert.equal(unsafePlan.ok, false);
|
|
1783
|
+
if (!unsafePlan.ok)
|
|
1784
|
+
assert.equal(unsafePlan.error.code, "unsafe_managed_removal");
|
|
1785
|
+
});
|
|
1786
|
+
it("fails closed for unsafe mixed removal and partial-corrupt OMX commands", () => {
|
|
1787
|
+
const command = buildManagedCodexHooksConfig("/repo").hooks.SessionStart[0]?.hooks[0]?.command;
|
|
1788
|
+
const unsafeMixed = planManagedCodexHooksRemoval(JSON.stringify({
|
|
1789
|
+
hooks: {
|
|
1790
|
+
SessionStart: [{
|
|
1791
|
+
matcher: "startup|resume|clear",
|
|
1792
|
+
hooks: [
|
|
1793
|
+
{ type: "command", command },
|
|
1794
|
+
{ type: "command", command: "echo moved" },
|
|
1795
|
+
],
|
|
1796
|
+
}],
|
|
1797
|
+
},
|
|
1798
|
+
}), "/hooks.json");
|
|
1799
|
+
assert.equal(unsafeMixed.ok, false);
|
|
1800
|
+
if (!unsafeMixed.ok)
|
|
1801
|
+
assert.equal(unsafeMixed.error.code, "unsafe_managed_removal");
|
|
1802
|
+
const partial = planManagedCodexHooksMerge(JSON.stringify({
|
|
1803
|
+
hooks: {
|
|
1804
|
+
PreToolUse: [{
|
|
1805
|
+
hooks: [{ type: "command", command: 'node "/repo/dist/scripts/codex-native-hook.js" --extra' }],
|
|
1806
|
+
}],
|
|
1807
|
+
},
|
|
1808
|
+
}), "/repo", "/hooks.json");
|
|
1809
|
+
assert.equal(partial.ok, false);
|
|
1810
|
+
if (!partial.ok) {
|
|
1811
|
+
assert.ok(partial.error instanceof ManagedCodexHooksPlanError);
|
|
1812
|
+
assert.equal(partial.error.code, "ambiguous_managed_handler");
|
|
1813
|
+
assert.ok(Array.isArray(partial.diagnostics));
|
|
1814
|
+
}
|
|
1815
|
+
const sharedSkipped = planManagedCodexHooksMerge(JSON.stringify({
|
|
1816
|
+
hooks: {
|
|
1817
|
+
PreToolUse: [{
|
|
1818
|
+
hooks: [
|
|
1819
|
+
{ type: "command", command: buildManagedCodexHooksConfig("/repo").hooks.PreToolUse[0]?.hooks[0]?.command },
|
|
1820
|
+
{ type: "command", command: "echo skipped", async: true },
|
|
1821
|
+
],
|
|
1822
|
+
}],
|
|
1823
|
+
},
|
|
1824
|
+
}), "/repo", "/hooks.json");
|
|
1825
|
+
assert.equal(sharedSkipped.ok, false);
|
|
1826
|
+
if (!sharedSkipped.ok)
|
|
1827
|
+
assert.equal(sharedSkipped.error.code, "ambiguous_managed_handler");
|
|
1828
|
+
});
|
|
553
1829
|
});
|
|
554
1830
|
//# sourceMappingURL=codex-hooks.test.js.map
|