oh-my-codex 0.18.13 → 0.18.15
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 +41 -18
- package/dist/agents/__tests__/native-config.test.js +62 -0
- package/dist/agents/__tests__/native-config.test.js.map +1 -1
- package/dist/agents/native-config.d.ts.map +1 -1
- package/dist/agents/native-config.js +15 -2
- package/dist/agents/native-config.js.map +1 -1
- package/dist/autopilot/__tests__/fsm.test.js +7 -0
- package/dist/autopilot/__tests__/fsm.test.js.map +1 -1
- package/dist/autopilot/__tests__/planner-routing.test.d.ts +2 -0
- package/dist/autopilot/__tests__/planner-routing.test.d.ts.map +1 -0
- package/dist/autopilot/__tests__/planner-routing.test.js +74 -0
- package/dist/autopilot/__tests__/planner-routing.test.js.map +1 -0
- package/dist/autopilot/__tests__/ralplan-gate.test.js +315 -0
- package/dist/autopilot/__tests__/ralplan-gate.test.js.map +1 -1
- package/dist/autopilot/completion-gate.js +1 -1
- package/dist/autopilot/completion-gate.js.map +1 -1
- package/dist/autopilot/fsm.d.ts +2 -2
- package/dist/autopilot/fsm.d.ts.map +1 -1
- package/dist/autopilot/fsm.js +3 -0
- package/dist/autopilot/fsm.js.map +1 -1
- package/dist/autopilot/planner-routing.d.ts +21 -0
- package/dist/autopilot/planner-routing.d.ts.map +1 -0
- package/dist/autopilot/planner-routing.js +59 -0
- package/dist/autopilot/planner-routing.js.map +1 -0
- package/dist/autopilot/ralplan-gate.d.ts.map +1 -1
- package/dist/autopilot/ralplan-gate.js +30 -0
- package/dist/autopilot/ralplan-gate.js.map +1 -1
- package/dist/autoresearch/goal.d.ts.map +1 -1
- package/dist/autoresearch/goal.js +10 -1
- package/dist/autoresearch/goal.js.map +1 -1
- package/dist/cli/__tests__/autoresearch-goal.test.js +33 -0
- package/dist/cli/__tests__/autoresearch-goal.test.js.map +1 -1
- package/dist/cli/__tests__/autoresearch-guided.test.js +3 -0
- package/dist/cli/__tests__/autoresearch-guided.test.js.map +1 -1
- package/dist/cli/__tests__/codex-plugin-layout.test.js +30 -0
- package/dist/cli/__tests__/codex-plugin-layout.test.js.map +1 -1
- package/dist/cli/__tests__/doctor-artifact-ownership.test.d.ts +2 -0
- package/dist/cli/__tests__/doctor-artifact-ownership.test.d.ts.map +1 -0
- package/dist/cli/__tests__/doctor-artifact-ownership.test.js +158 -0
- package/dist/cli/__tests__/doctor-artifact-ownership.test.js.map +1 -0
- package/dist/cli/__tests__/doctor-explore-deprecated.test.d.ts +2 -0
- package/dist/cli/__tests__/doctor-explore-deprecated.test.d.ts.map +1 -0
- package/dist/cli/__tests__/doctor-explore-deprecated.test.js +33 -0
- package/dist/cli/__tests__/doctor-explore-deprecated.test.js.map +1 -0
- package/dist/cli/__tests__/doctor-spark-routing.test.js +13 -0
- package/dist/cli/__tests__/doctor-spark-routing.test.js.map +1 -1
- package/dist/cli/__tests__/doctor-warning-copy.test.js +132 -2
- package/dist/cli/__tests__/doctor-warning-copy.test.js.map +1 -1
- package/dist/cli/__tests__/index.test.js +5 -0
- package/dist/cli/__tests__/index.test.js.map +1 -1
- package/dist/cli/__tests__/mcp-parity.test.js +40 -0
- package/dist/cli/__tests__/mcp-parity.test.js.map +1 -1
- package/dist/cli/__tests__/nested-help-routing.test.js +4 -0
- package/dist/cli/__tests__/nested-help-routing.test.js.map +1 -1
- package/dist/cli/__tests__/package-bin-contract.test.js +7 -1
- package/dist/cli/__tests__/package-bin-contract.test.js.map +1 -1
- package/dist/cli/__tests__/performance-goal.test.js +22 -0
- package/dist/cli/__tests__/performance-goal.test.js.map +1 -1
- package/dist/cli/__tests__/plugin-marketplace-idempotent.test.d.ts +2 -0
- package/dist/cli/__tests__/plugin-marketplace-idempotent.test.d.ts.map +1 -0
- package/dist/cli/__tests__/plugin-marketplace-idempotent.test.js +64 -0
- package/dist/cli/__tests__/plugin-marketplace-idempotent.test.js.map +1 -0
- package/dist/cli/__tests__/resume.test.js +96 -0
- package/dist/cli/__tests__/resume.test.js.map +1 -1
- package/dist/cli/__tests__/session-scoped-runtime.test.js +162 -0
- package/dist/cli/__tests__/session-scoped-runtime.test.js.map +1 -1
- package/dist/cli/__tests__/session-search.test.js +36 -0
- package/dist/cli/__tests__/session-search.test.js.map +1 -1
- package/dist/cli/__tests__/setup-agents-overwrite.test.js +70 -1
- package/dist/cli/__tests__/setup-agents-overwrite.test.js.map +1 -1
- package/dist/cli/__tests__/setup-install-mode.test.js +33 -0
- package/dist/cli/__tests__/setup-install-mode.test.js.map +1 -1
- package/dist/cli/__tests__/team.test.js +17 -3
- package/dist/cli/__tests__/team.test.js.map +1 -1
- package/dist/cli/__tests__/ultragoal.test.js +37 -0
- package/dist/cli/__tests__/ultragoal.test.js.map +1 -1
- package/dist/cli/__tests__/update.test.js +81 -2
- package/dist/cli/__tests__/update.test.js.map +1 -1
- package/dist/cli/__tests__/url.test.d.ts +2 -0
- package/dist/cli/__tests__/url.test.d.ts.map +1 -0
- package/dist/cli/__tests__/url.test.js +82 -0
- package/dist/cli/__tests__/url.test.js.map +1 -0
- package/dist/cli/autoresearch-goal.d.ts.map +1 -1
- package/dist/cli/autoresearch-goal.js +2 -1
- package/dist/cli/autoresearch-goal.js.map +1 -1
- package/dist/cli/autoresearch-intake.d.ts.map +1 -1
- package/dist/cli/autoresearch-intake.js +2 -0
- package/dist/cli/autoresearch-intake.js.map +1 -1
- package/dist/cli/doctor.d.ts +39 -1
- package/dist/cli/doctor.d.ts.map +1 -1
- package/dist/cli/doctor.js +460 -40
- package/dist/cli/doctor.js.map +1 -1
- package/dist/cli/index.d.ts +2 -2
- package/dist/cli/index.d.ts.map +1 -1
- package/dist/cli/index.js +71 -4
- package/dist/cli/index.js.map +1 -1
- package/dist/cli/performance-goal.d.ts.map +1 -1
- package/dist/cli/performance-goal.js +4 -2
- package/dist/cli/performance-goal.js.map +1 -1
- package/dist/cli/plugin-marketplace.d.ts +1 -0
- package/dist/cli/plugin-marketplace.d.ts.map +1 -1
- package/dist/cli/plugin-marketplace.js +88 -67
- package/dist/cli/plugin-marketplace.js.map +1 -1
- package/dist/cli/project-runtime-codex-homes.d.ts +2 -0
- package/dist/cli/project-runtime-codex-homes.d.ts.map +1 -1
- package/dist/cli/project-runtime-codex-homes.js +112 -5
- package/dist/cli/project-runtime-codex-homes.js.map +1 -1
- package/dist/cli/setup.d.ts.map +1 -1
- package/dist/cli/setup.js +15 -6
- package/dist/cli/setup.js.map +1 -1
- package/dist/cli/ultragoal.d.ts.map +1 -1
- package/dist/cli/ultragoal.js +5 -1
- package/dist/cli/ultragoal.js.map +1 -1
- package/dist/cli/update.d.ts.map +1 -1
- package/dist/cli/update.js +23 -4
- package/dist/cli/update.js.map +1 -1
- package/dist/cli/url.d.ts +8 -0
- package/dist/cli/url.d.ts.map +1 -0
- package/dist/cli/url.js +69 -0
- package/dist/cli/url.js.map +1 -0
- package/dist/config/__tests__/codex-hooks.test.js +20 -4
- package/dist/config/__tests__/codex-hooks.test.js.map +1 -1
- package/dist/config/__tests__/models.test.js +21 -1
- package/dist/config/__tests__/models.test.js.map +1 -1
- package/dist/config/codex-hooks.d.ts.map +1 -1
- package/dist/config/codex-hooks.js +1 -4
- package/dist/config/codex-hooks.js.map +1 -1
- package/dist/config/models.d.ts +5 -0
- package/dist/config/models.d.ts.map +1 -1
- package/dist/config/models.js +23 -0
- package/dist/config/models.js.map +1 -1
- package/dist/goal-workflows/codex-goal-snapshot.d.ts +2 -0
- package/dist/goal-workflows/codex-goal-snapshot.d.ts.map +1 -1
- package/dist/goal-workflows/codex-goal-snapshot.js +10 -0
- package/dist/goal-workflows/codex-goal-snapshot.js.map +1 -1
- package/dist/hooks/__tests__/autopilot-skill-contract.test.js +7 -0
- package/dist/hooks/__tests__/autopilot-skill-contract.test.js.map +1 -1
- package/dist/hooks/__tests__/keyword-detector.test.js +46 -0
- package/dist/hooks/__tests__/keyword-detector.test.js.map +1 -1
- package/dist/hooks/__tests__/notify-fallback-watcher.test.js +100 -0
- package/dist/hooks/__tests__/notify-fallback-watcher.test.js.map +1 -1
- package/dist/hooks/__tests__/notify-hook-all-workers-idle.test.js +75 -0
- package/dist/hooks/__tests__/notify-hook-all-workers-idle.test.js.map +1 -1
- package/dist/hooks/__tests__/notify-hook-auto-nudge.test.js +225 -0
- package/dist/hooks/__tests__/notify-hook-auto-nudge.test.js.map +1 -1
- package/dist/hooks/__tests__/notify-hook-team-dispatch.test.js +75 -0
- package/dist/hooks/__tests__/notify-hook-team-dispatch.test.js.map +1 -1
- package/dist/hooks/__tests__/notify-hook-team-leader-nudge.test.js +563 -0
- 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 +187 -12
- package/dist/hooks/__tests__/notify-hook-team-tmux-guard.test.js.map +1 -1
- package/dist/hooks/__tests__/notify-hook-tmux-heal.test.js +225 -0
- package/dist/hooks/__tests__/notify-hook-tmux-heal.test.js.map +1 -1
- package/dist/hooks/__tests__/notify-hook-tmux-scrollback.test.js +0 -0
- package/dist/hooks/__tests__/notify-hook-tmux-scrollback.test.js.map +1 -1
- package/dist/hooks/__tests__/notify-hook-worker-idle.test.js +75 -0
- package/dist/hooks/__tests__/notify-hook-worker-idle.test.js.map +1 -1
- package/dist/hooks/keyword-detector.d.ts.map +1 -1
- package/dist/hooks/keyword-detector.js +4 -0
- package/dist/hooks/keyword-detector.js.map +1 -1
- package/dist/hud/__tests__/reconcile.test.js +137 -2
- package/dist/hud/__tests__/reconcile.test.js.map +1 -1
- package/dist/hud/__tests__/state.test.js +107 -0
- package/dist/hud/__tests__/state.test.js.map +1 -1
- package/dist/hud/__tests__/tmux.test.js +17 -0
- package/dist/hud/__tests__/tmux.test.js.map +1 -1
- package/dist/hud/reconcile.d.ts.map +1 -1
- package/dist/hud/reconcile.js +21 -7
- package/dist/hud/reconcile.js.map +1 -1
- package/dist/hud/render.d.ts.map +1 -1
- package/dist/hud/render.js +9 -0
- package/dist/hud/render.js.map +1 -1
- package/dist/hud/state.d.ts.map +1 -1
- package/dist/hud/state.js +43 -5
- package/dist/hud/state.js.map +1 -1
- package/dist/hud/types.d.ts +6 -0
- package/dist/hud/types.d.ts.map +1 -1
- package/dist/hud/types.js.map +1 -1
- package/dist/index.d.ts +1 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1 -0
- package/dist/index.js.map +1 -1
- package/dist/mcp/__tests__/bootstrap.test.js +24 -1
- package/dist/mcp/__tests__/bootstrap.test.js.map +1 -1
- package/dist/mcp/__tests__/state-paths.test.js +53 -0
- package/dist/mcp/__tests__/state-paths.test.js.map +1 -1
- package/dist/mcp/bootstrap.d.ts +2 -0
- package/dist/mcp/bootstrap.d.ts.map +1 -1
- package/dist/mcp/bootstrap.js +32 -6
- package/dist/mcp/bootstrap.js.map +1 -1
- package/dist/mcp/state-paths.d.ts.map +1 -1
- package/dist/mcp/state-paths.js +11 -5
- package/dist/mcp/state-paths.js.map +1 -1
- package/dist/modes/__tests__/base-autopilot-gates.test.js +48 -0
- package/dist/modes/__tests__/base-autopilot-gates.test.js.map +1 -1
- package/dist/modes/base.d.ts.map +1 -1
- package/dist/modes/base.js +1 -0
- package/dist/modes/base.js.map +1 -1
- package/dist/notifications/__tests__/http-client.test.js +9 -0
- package/dist/notifications/__tests__/http-client.test.js.map +1 -1
- package/dist/notifications/http-client.js +1 -1
- package/dist/notifications/http-client.js.map +1 -1
- package/dist/performance-goal/artifacts.d.ts.map +1 -1
- package/dist/performance-goal/artifacts.js +10 -1
- package/dist/performance-goal/artifacts.js.map +1 -1
- package/dist/ralplan/__tests__/consensus-gate.test.js +112 -0
- package/dist/ralplan/__tests__/consensus-gate.test.js.map +1 -1
- package/dist/ralplan/consensus-gate.d.ts +23 -0
- package/dist/ralplan/consensus-gate.d.ts.map +1 -1
- package/dist/ralplan/consensus-gate.js +133 -21
- package/dist/ralplan/consensus-gate.js.map +1 -1
- package/dist/scripts/__tests__/codex-native-hook.test.js +1251 -13
- package/dist/scripts/__tests__/codex-native-hook.test.js.map +1 -1
- package/dist/scripts/codex-native-hook.d.ts.map +1 -1
- package/dist/scripts/codex-native-hook.js +601 -24
- package/dist/scripts/codex-native-hook.js.map +1 -1
- package/dist/scripts/notify-hook/team-leader-nudge.d.ts +1 -0
- package/dist/scripts/notify-hook/team-leader-nudge.d.ts.map +1 -1
- package/dist/scripts/notify-hook/team-leader-nudge.js +279 -86
- package/dist/scripts/notify-hook/team-leader-nudge.js.map +1 -1
- package/dist/scripts/notify-hook/team-tmux-guard.d.ts.map +1 -1
- package/dist/scripts/notify-hook/team-tmux-guard.js +87 -8
- package/dist/scripts/notify-hook/team-tmux-guard.js.map +1 -1
- package/dist/scripts/notify-hook/tmux-injection.js +1 -1
- package/dist/scripts/notify-hook/tmux-injection.js.map +1 -1
- package/dist/scripts/run-compiled-ci.d.ts +2 -0
- package/dist/scripts/run-compiled-ci.d.ts.map +1 -0
- package/dist/scripts/run-compiled-ci.js +77 -0
- package/dist/scripts/run-compiled-ci.js.map +1 -0
- package/dist/scripts/sync-plugin-mirror.d.ts.map +1 -1
- package/dist/scripts/sync-plugin-mirror.js +1 -0
- package/dist/scripts/sync-plugin-mirror.js.map +1 -1
- package/dist/session-history/search.d.ts.map +1 -1
- package/dist/session-history/search.js +24 -9
- package/dist/session-history/search.js.map +1 -1
- package/dist/sidecar/__tests__/args.test.d.ts +2 -0
- package/dist/sidecar/__tests__/args.test.d.ts.map +1 -0
- package/dist/sidecar/__tests__/args.test.js +18 -0
- package/dist/sidecar/__tests__/args.test.js.map +1 -0
- package/dist/sidecar/index.d.ts.map +1 -1
- package/dist/sidecar/index.js +12 -4
- package/dist/sidecar/index.js.map +1 -1
- package/dist/state/__tests__/operations.test.js +107 -3
- package/dist/state/__tests__/operations.test.js.map +1 -1
- package/dist/state/operations.d.ts.map +1 -1
- package/dist/state/operations.js +57 -2
- package/dist/state/operations.js.map +1 -1
- package/dist/team/__tests__/delivery-e2e-smoke.test.js +22 -1
- package/dist/team/__tests__/delivery-e2e-smoke.test.js.map +1 -1
- package/dist/team/__tests__/model-contract.test.js +57 -1
- package/dist/team/__tests__/model-contract.test.js.map +1 -1
- package/dist/team/__tests__/runtime.test.js +1468 -110
- package/dist/team/__tests__/runtime.test.js.map +1 -1
- package/dist/team/__tests__/scaling.test.js +63 -0
- package/dist/team/__tests__/scaling.test.js.map +1 -1
- package/dist/team/__tests__/state.test.js +24 -1
- package/dist/team/__tests__/state.test.js.map +1 -1
- package/dist/team/__tests__/tmux-session.test.js +512 -0
- package/dist/team/__tests__/tmux-session.test.js.map +1 -1
- package/dist/team/__tests__/worker-bootstrap.test.js +42 -9
- package/dist/team/__tests__/worker-bootstrap.test.js.map +1 -1
- package/dist/team/model-contract.d.ts +16 -0
- package/dist/team/model-contract.d.ts.map +1 -1
- package/dist/team/model-contract.js +41 -1
- package/dist/team/model-contract.js.map +1 -1
- package/dist/team/runtime.d.ts.map +1 -1
- package/dist/team/runtime.js +116 -30
- package/dist/team/runtime.js.map +1 -1
- package/dist/team/scaling.d.ts.map +1 -1
- package/dist/team/scaling.js +9 -1
- package/dist/team/scaling.js.map +1 -1
- package/dist/team/state/types.d.ts +2 -0
- package/dist/team/state/types.d.ts.map +1 -1
- package/dist/team/state/types.js.map +1 -1
- package/dist/team/state.d.ts +3 -0
- package/dist/team/state.d.ts.map +1 -1
- package/dist/team/state.js +20 -0
- package/dist/team/state.js.map +1 -1
- package/dist/team/tmux-session.d.ts +38 -3
- package/dist/team/tmux-session.d.ts.map +1 -1
- package/dist/team/tmux-session.js +275 -36
- package/dist/team/tmux-session.js.map +1 -1
- package/dist/team/worker-bootstrap.d.ts.map +1 -1
- package/dist/team/worker-bootstrap.js +26 -1
- package/dist/team/worker-bootstrap.js.map +1 -1
- package/dist/ultragoal/__tests__/artifacts.test.js +388 -0
- package/dist/ultragoal/__tests__/artifacts.test.js.map +1 -1
- package/dist/ultragoal/__tests__/docs-contract.test.js +3 -0
- package/dist/ultragoal/__tests__/docs-contract.test.js.map +1 -1
- package/dist/ultragoal/artifacts.d.ts +23 -0
- package/dist/ultragoal/artifacts.d.ts.map +1 -1
- package/dist/ultragoal/artifacts.js +275 -12
- package/dist/ultragoal/artifacts.js.map +1 -1
- package/dist/url-reader/__tests__/url-reader.test.d.ts +2 -0
- package/dist/url-reader/__tests__/url-reader.test.d.ts.map +1 -0
- package/dist/url-reader/__tests__/url-reader.test.js +433 -0
- package/dist/url-reader/__tests__/url-reader.test.js.map +1 -0
- package/dist/url-reader/index.d.ts +4 -0
- package/dist/url-reader/index.d.ts.map +1 -0
- package/dist/url-reader/index.js +522 -0
- package/dist/url-reader/index.js.map +1 -0
- package/dist/url-reader/types.d.ts +41 -0
- package/dist/url-reader/types.d.ts.map +1 -0
- package/dist/url-reader/types.js +2 -0
- package/dist/url-reader/types.js.map +1 -0
- package/dist/utils/__tests__/agents-md.test.js +39 -1
- package/dist/utils/__tests__/agents-md.test.js.map +1 -1
- package/dist/utils/__tests__/agents-model-table.test.js +28 -0
- package/dist/utils/__tests__/agents-model-table.test.js.map +1 -1
- package/dist/utils/agents-md.d.ts +4 -0
- package/dist/utils/agents-md.d.ts.map +1 -1
- package/dist/utils/agents-md.js +28 -0
- package/dist/utils/agents-md.js.map +1 -1
- package/dist/utils/agents-model-table.d.ts +7 -3
- package/dist/utils/agents-model-table.d.ts.map +1 -1
- package/dist/utils/agents-model-table.js +16 -8
- package/dist/utils/agents-model-table.js.map +1 -1
- package/dist/utils/platform-command.d.ts +7 -1
- package/dist/utils/platform-command.d.ts.map +1 -1
- package/dist/utils/platform-command.js +12 -1
- package/dist/utils/platform-command.js.map +1 -1
- package/dist/verification/__tests__/ci-rust-gates.test.js +4 -0
- package/dist/verification/__tests__/ci-rust-gates.test.js.map +1 -1
- package/dist/vscode/__tests__/index.test.d.ts +2 -0
- package/dist/vscode/__tests__/index.test.d.ts.map +1 -0
- package/dist/vscode/__tests__/index.test.js +236 -0
- package/dist/vscode/__tests__/index.test.js.map +1 -0
- package/dist/vscode/index.d.ts +76 -0
- package/dist/vscode/index.d.ts.map +1 -0
- package/dist/vscode/index.js +196 -0
- package/dist/vscode/index.js.map +1 -0
- package/package.json +3 -3
- package/plugins/oh-my-codex/.codex-plugin/plugin.json +1 -1
- package/plugins/oh-my-codex/hooks/codex-native-hook.mjs +59 -8
- package/plugins/oh-my-codex/skills/autopilot/SKILL.md +9 -5
- package/plugins/oh-my-codex/skills/autoresearch-goal/SKILL.md +3 -0
- package/plugins/oh-my-codex/skills/performance-goal/SKILL.md +3 -0
- package/plugins/oh-my-codex/skills/ralplan/SKILL.md +1 -1
- package/plugins/oh-my-codex/skills/ultragoal/SKILL.md +22 -4
- package/plugins/oh-my-codex/skills/ultrawork/SKILL.md +2 -2
- package/plugins/oh-my-codex/skills/ultrawork/references/agent-tiers.md +61 -0
- package/skills/autopilot/SKILL.md +9 -5
- package/skills/autoresearch-goal/SKILL.md +3 -0
- package/skills/ecomode/SKILL.md +1 -1
- package/skills/ecomode/references/agent-tiers.md +61 -0
- package/skills/performance-goal/SKILL.md +3 -0
- package/skills/ralplan/SKILL.md +1 -1
- package/skills/ultragoal/SKILL.md +22 -4
- package/skills/ultrawork/SKILL.md +2 -2
- package/skills/ultrawork/references/agent-tiers.md +61 -0
- package/src/scripts/__tests__/codex-native-hook.test.ts +1560 -181
- package/src/scripts/codex-native-hook.ts +663 -24
- package/src/scripts/notify-hook/team-leader-nudge.ts +298 -91
- package/src/scripts/notify-hook/team-tmux-guard.ts +92 -8
- package/src/scripts/notify-hook/tmux-injection.ts +1 -1
- package/src/scripts/run-compiled-ci.ts +85 -0
- package/src/scripts/sync-plugin-mirror.ts +1 -0
- package/templates/AGENTS.md +6 -0
|
@@ -160,6 +160,31 @@ if [[ "$cmd" == "capture-pane" ]]; then
|
|
|
160
160
|
printf '%s\\n' ${captureText}
|
|
161
161
|
exit 0
|
|
162
162
|
fi
|
|
163
|
+
if [[ "$cmd" == "set-buffer" ]]; then
|
|
164
|
+
printf '%s' "\${@: -1}" > "${tmuxLogPath}.buffer"
|
|
165
|
+
exit 0
|
|
166
|
+
fi
|
|
167
|
+
if [[ "$cmd" == "show-buffer" ]]; then
|
|
168
|
+
if [[ -f "${tmuxLogPath}.buffer" ]]; then cat "${tmuxLogPath}.buffer"; fi
|
|
169
|
+
exit 0
|
|
170
|
+
fi
|
|
171
|
+
if [[ "$cmd" == "paste-buffer" ]]; then
|
|
172
|
+
target=""
|
|
173
|
+
while [[ "$#" -gt 0 ]]; do
|
|
174
|
+
case "$1" in
|
|
175
|
+
-t) target="$2"; shift 2 ;;
|
|
176
|
+
*) shift ;;
|
|
177
|
+
esac
|
|
178
|
+
done
|
|
179
|
+
if [[ -f "${tmuxLogPath}.buffer" ]]; then
|
|
180
|
+
echo "send-keys -t \${target} -l $(cat "${tmuxLogPath}.buffer")" >> "${tmuxLogPath}"
|
|
181
|
+
fi
|
|
182
|
+
exit 0
|
|
183
|
+
fi
|
|
184
|
+
if [[ "$cmd" == "delete-buffer" ]]; then
|
|
185
|
+
rm -f "${tmuxLogPath}.buffer"
|
|
186
|
+
exit 0
|
|
187
|
+
fi
|
|
163
188
|
if [[ "$cmd" == "send-keys" ]]; then
|
|
164
189
|
${sendDelaySeconds > 0 ? `sleep ${sendDelaySeconds}` : ""}
|
|
165
190
|
${removePathOnSend ? `rm -rf ${removePathOnSend}` : ""}
|
|
@@ -346,9 +371,9 @@ describe("codex native hook dispatch", () => {
|
|
|
346
371
|
assert.equal(result.status, 0, result.stderr || result.stdout);
|
|
347
372
|
assert.equal(result.stderr, "");
|
|
348
373
|
const output = parseSingleJsonStdout(result.stdout);
|
|
349
|
-
assert.equal(output.continue,
|
|
374
|
+
assert.equal(output.continue, undefined);
|
|
350
375
|
assert.equal(output.decision, undefined);
|
|
351
|
-
assert.equal(output.stopReason,
|
|
376
|
+
assert.equal(output.stopReason, undefined);
|
|
352
377
|
assert.equal(output.hookSpecificOutput, undefined);
|
|
353
378
|
assert.match(String(output.systemMessage ?? ""), /stdin JSON parsing failed inside codex-native-hook:/);
|
|
354
379
|
}
|
|
@@ -421,6 +446,151 @@ describe("codex native hook dispatch", () => {
|
|
|
421
446
|
assert.equal(output.hookSpecificOutput, undefined);
|
|
422
447
|
assert.match(String(output.systemMessage ?? ""), /stdin JSON parsing failed inside codex-native-hook:/);
|
|
423
448
|
});
|
|
449
|
+
it("emits no-op JSON stdout for PreToolUse non-Bash tools with null output", async () => {
|
|
450
|
+
const cwd = await mkdtemp(join(tmpdir(), "omx-native-hook-cli-pretool-nonbash-noop-"));
|
|
451
|
+
try {
|
|
452
|
+
const result = spawnSync(process.execPath, [nativeHookScriptPath()], {
|
|
453
|
+
cwd,
|
|
454
|
+
input: JSON.stringify({
|
|
455
|
+
hook_event_name: "PreToolUse",
|
|
456
|
+
cwd,
|
|
457
|
+
session_id: "sess-cli-pretool-nonbash-noop",
|
|
458
|
+
thread_id: "thread-cli-pretool-nonbash-noop",
|
|
459
|
+
turn_id: "turn-cli-pretool-nonbash-noop",
|
|
460
|
+
tool_name: "Read",
|
|
461
|
+
tool_input: { file_path: "package.json" },
|
|
462
|
+
}),
|
|
463
|
+
encoding: "utf-8",
|
|
464
|
+
stdio: ["pipe", "pipe", "pipe"],
|
|
465
|
+
});
|
|
466
|
+
assert.equal(result.status, 0, result.stderr || result.stdout);
|
|
467
|
+
assert.equal(result.stderr, "");
|
|
468
|
+
assert.deepEqual(parseSingleJsonStdout(result.stdout), {});
|
|
469
|
+
}
|
|
470
|
+
finally {
|
|
471
|
+
await rm(cwd, { recursive: true, force: true });
|
|
472
|
+
}
|
|
473
|
+
});
|
|
474
|
+
it("emits PreToolUse CLI advisory JSON with only systemMessage", async () => {
|
|
475
|
+
const cwd = await mkdtemp(join(tmpdir(), "omx-native-hook-cli-pretool-schema-safe-"));
|
|
476
|
+
try {
|
|
477
|
+
const output = parseSingleJsonStdout(runNativeHookCli({
|
|
478
|
+
hook_event_name: "PreToolUse",
|
|
479
|
+
cwd,
|
|
480
|
+
session_id: "sess-cli-pretool-schema-safe",
|
|
481
|
+
thread_id: "thread-cli-pretool-schema-safe",
|
|
482
|
+
turn_id: "turn-cli-pretool-schema-safe",
|
|
483
|
+
tool_name: "Bash",
|
|
484
|
+
tool_input: { command: "rm -rf dist" },
|
|
485
|
+
}, { cwd }));
|
|
486
|
+
assert.deepEqual(output, {
|
|
487
|
+
systemMessage: "Destructive Bash command detected (`rm -rf dist`). Confirm the target and expected side effects before running it.",
|
|
488
|
+
});
|
|
489
|
+
}
|
|
490
|
+
finally {
|
|
491
|
+
await rm(cwd, { recursive: true, force: true });
|
|
492
|
+
}
|
|
493
|
+
});
|
|
494
|
+
it("emits PreToolUse CLI block JSON with only systemMessage", async () => {
|
|
495
|
+
const cwd = await mkdtemp(join(tmpdir(), "omx-native-hook-cli-pretool-block-schema-safe-"));
|
|
496
|
+
try {
|
|
497
|
+
const output = parseSingleJsonStdout(runNativeHookCli({
|
|
498
|
+
hook_event_name: "PreToolUse",
|
|
499
|
+
cwd,
|
|
500
|
+
session_id: "sess-cli-pretool-block-schema-safe",
|
|
501
|
+
thread_id: "thread-cli-pretool-block-schema-safe",
|
|
502
|
+
turn_id: "turn-cli-pretool-block-schema-safe",
|
|
503
|
+
tool_name: "Bash",
|
|
504
|
+
tool_input: { command: 'OMX_LORE_COMMIT_GUARD=1 git commit -m "fix tests"' },
|
|
505
|
+
}, { cwd }));
|
|
506
|
+
assert.deepEqual(Object.keys(output).sort(), ["systemMessage"]);
|
|
507
|
+
assert.match(String(output.systemMessage ?? ""), /Lore protocol/);
|
|
508
|
+
assert.equal(output.decision, undefined);
|
|
509
|
+
assert.equal(output.stopReason, undefined);
|
|
510
|
+
assert.equal(output.hookSpecificOutput, undefined);
|
|
511
|
+
}
|
|
512
|
+
finally {
|
|
513
|
+
await rm(cwd, { recursive: true, force: true });
|
|
514
|
+
}
|
|
515
|
+
});
|
|
516
|
+
it("preserves ralplan PreToolUse planning guard as schema-safe CLI systemMessage", async () => {
|
|
517
|
+
const cwd = await mkdtemp(join(tmpdir(), "omx-native-hook-cli-ralplan-pretool-boundary-"));
|
|
518
|
+
const sessionId = "sess-cli-ralplan-pretool-boundary";
|
|
519
|
+
const stateDir = join(cwd, ".omx", "state");
|
|
520
|
+
try {
|
|
521
|
+
await writeJson(join(stateDir, "session.json"), { session_id: sessionId });
|
|
522
|
+
await writeJson(join(stateDir, "sessions", sessionId, "skill-active-state.json"), {
|
|
523
|
+
active: true,
|
|
524
|
+
skill: "ralplan",
|
|
525
|
+
phase: "planning",
|
|
526
|
+
session_id: sessionId,
|
|
527
|
+
active_skills: [{ skill: "ralplan", phase: "planning", active: true, session_id: sessionId }],
|
|
528
|
+
});
|
|
529
|
+
await writeJson(join(stateDir, "sessions", sessionId, "ralplan-state.json"), {
|
|
530
|
+
active: true,
|
|
531
|
+
mode: "ralplan",
|
|
532
|
+
current_phase: "critic-review",
|
|
533
|
+
session_id: sessionId,
|
|
534
|
+
});
|
|
535
|
+
const output = parseSingleJsonStdout(runNativeHookCli({
|
|
536
|
+
hook_event_name: "PreToolUse",
|
|
537
|
+
cwd,
|
|
538
|
+
session_id: sessionId,
|
|
539
|
+
thread_id: "thread-cli-ralplan-pretool-boundary",
|
|
540
|
+
tool_name: "Edit",
|
|
541
|
+
tool_input: { file_path: "src/runtime.ts", old_string: "a", new_string: "b" },
|
|
542
|
+
}, { cwd }));
|
|
543
|
+
assert.deepEqual(Object.keys(output).sort(), ["systemMessage"]);
|
|
544
|
+
assert.match(String(output.systemMessage ?? ""), /Ralplan is active \(phase: critic-review\)/);
|
|
545
|
+
assert.match(String(output.systemMessage ?? ""), /implementation\/write tools are blocked/);
|
|
546
|
+
assert.match(String(output.systemMessage ?? ""), /Write only planning artifacts/);
|
|
547
|
+
assert.equal(output.decision, undefined);
|
|
548
|
+
assert.equal(output.reason, undefined);
|
|
549
|
+
assert.equal(output.hookSpecificOutput, undefined);
|
|
550
|
+
}
|
|
551
|
+
finally {
|
|
552
|
+
await rm(cwd, { recursive: true, force: true });
|
|
553
|
+
}
|
|
554
|
+
});
|
|
555
|
+
it("preserves deep-interview PreToolUse planning guard as schema-safe CLI systemMessage", async () => {
|
|
556
|
+
const cwd = await mkdtemp(join(tmpdir(), "omx-native-hook-cli-deep-interview-pretool-boundary-"));
|
|
557
|
+
const sessionId = "sess-cli-deep-interview-pretool-boundary";
|
|
558
|
+
const stateDir = join(cwd, ".omx", "state");
|
|
559
|
+
try {
|
|
560
|
+
await writeJson(join(stateDir, "session.json"), { session_id: sessionId });
|
|
561
|
+
await writeJson(join(stateDir, "sessions", sessionId, "skill-active-state.json"), {
|
|
562
|
+
active: true,
|
|
563
|
+
skill: "deep-interview",
|
|
564
|
+
phase: "planning",
|
|
565
|
+
session_id: sessionId,
|
|
566
|
+
active_skills: [{ skill: "deep-interview", phase: "planning", active: true, session_id: sessionId }],
|
|
567
|
+
});
|
|
568
|
+
await writeJson(join(stateDir, "sessions", sessionId, "deep-interview-state.json"), {
|
|
569
|
+
active: true,
|
|
570
|
+
mode: "deep-interview",
|
|
571
|
+
current_phase: "intent-first",
|
|
572
|
+
session_id: sessionId,
|
|
573
|
+
});
|
|
574
|
+
const output = parseSingleJsonStdout(runNativeHookCli({
|
|
575
|
+
hook_event_name: "PreToolUse",
|
|
576
|
+
cwd,
|
|
577
|
+
session_id: sessionId,
|
|
578
|
+
thread_id: "thread-cli-deep-interview-pretool-boundary",
|
|
579
|
+
tool_name: "Write",
|
|
580
|
+
tool_input: { file_path: "src/runtime.ts", content: "export const changed = true;\n" },
|
|
581
|
+
}, { cwd }));
|
|
582
|
+
assert.deepEqual(Object.keys(output).sort(), ["systemMessage"]);
|
|
583
|
+
assert.match(String(output.systemMessage ?? ""), /Deep-interview is active \(phase: intent-first\)/);
|
|
584
|
+
assert.match(String(output.systemMessage ?? ""), /implementation\/write tools are blocked/);
|
|
585
|
+
assert.match(String(output.systemMessage ?? ""), /requirements\/spec mode/);
|
|
586
|
+
assert.equal(output.decision, undefined);
|
|
587
|
+
assert.equal(output.reason, undefined);
|
|
588
|
+
assert.equal(output.hookSpecificOutput, undefined);
|
|
589
|
+
}
|
|
590
|
+
finally {
|
|
591
|
+
await rm(cwd, { recursive: true, force: true });
|
|
592
|
+
}
|
|
593
|
+
});
|
|
424
594
|
it("emits parseable no-op JSON stdout for inactive Stop CLI runs", async () => {
|
|
425
595
|
const cwd = await mkdtemp(join(tmpdir(), "omx-native-hook-cli-stop-noop-json-"));
|
|
426
596
|
try {
|
|
@@ -439,6 +609,31 @@ describe("codex native hook dispatch", () => {
|
|
|
439
609
|
await rm(cwd, { recursive: true, force: true });
|
|
440
610
|
}
|
|
441
611
|
});
|
|
612
|
+
it("emits no-op JSON stdout for Stop payloads with no runtime output", async () => {
|
|
613
|
+
const cwd = await mkdtemp(join(tmpdir(), "omx-native-hook-cli-stop-null-output-"));
|
|
614
|
+
try {
|
|
615
|
+
const result = spawnSync(process.execPath, [nativeHookScriptPath()], {
|
|
616
|
+
cwd,
|
|
617
|
+
input: JSON.stringify({
|
|
618
|
+
hook_event_name: "Stop",
|
|
619
|
+
cwd,
|
|
620
|
+
session_id: "sess-cli-stop-null-output",
|
|
621
|
+
thread_id: "thread-cli-stop-null-output",
|
|
622
|
+
turn_id: "turn-cli-stop-null-output",
|
|
623
|
+
stop_hook_active: true,
|
|
624
|
+
}),
|
|
625
|
+
encoding: "utf-8",
|
|
626
|
+
stdio: ["pipe", "pipe", "pipe"],
|
|
627
|
+
});
|
|
628
|
+
assert.equal(result.status, 0, result.stderr || result.stdout);
|
|
629
|
+
assert.equal(result.stderr, "");
|
|
630
|
+
assert.deepEqual(parseSingleJsonStdout(result.stdout), {});
|
|
631
|
+
assert.equal(existsSync(join(cwd, ".omx", "state")), false);
|
|
632
|
+
}
|
|
633
|
+
finally {
|
|
634
|
+
await rm(cwd, { recursive: true, force: true });
|
|
635
|
+
}
|
|
636
|
+
});
|
|
442
637
|
it("returns empty JSON for oversized Stop stdin without parsing or creating inactive state", async () => {
|
|
443
638
|
const cwd = await mkdtemp(join(tmpdir(), "omx-native-hook-cli-stop-oversized-"));
|
|
444
639
|
try {
|
|
@@ -567,6 +762,79 @@ describe("codex native hook dispatch", () => {
|
|
|
567
762
|
await rm(cwd, { recursive: true, force: true });
|
|
568
763
|
}
|
|
569
764
|
});
|
|
765
|
+
it("does not block Stop on stale session autopilot mirror when canonical skill state is inactive", async () => {
|
|
766
|
+
const cwd = await mkdtemp(join(tmpdir(), "omx-native-hook-stale-autopilot-mirror-"));
|
|
767
|
+
try {
|
|
768
|
+
const sessionId = "sess-stale-autopilot-mirror";
|
|
769
|
+
await writeJson(join(cwd, ".omx", "state", "session.json"), { session_id: sessionId, cwd });
|
|
770
|
+
await writeJson(join(cwd, ".omx", "state", "skill-active-state.json"), {
|
|
771
|
+
version: 1,
|
|
772
|
+
active: false,
|
|
773
|
+
skill: "",
|
|
774
|
+
phase: "cancelled",
|
|
775
|
+
active_skills: [],
|
|
776
|
+
});
|
|
777
|
+
await writeJson(join(cwd, ".omx", "state", "sessions", sessionId, "autopilot-state.json"), {
|
|
778
|
+
active: true,
|
|
779
|
+
mode: "autopilot",
|
|
780
|
+
current_phase: "ultragoal",
|
|
781
|
+
session_id: sessionId,
|
|
782
|
+
});
|
|
783
|
+
await writeJson(join(cwd, ".omx", "state", "sessions", sessionId, "skill-active-state.json"), {
|
|
784
|
+
version: 1,
|
|
785
|
+
active: true,
|
|
786
|
+
skill: "autopilot",
|
|
787
|
+
phase: "ultragoal",
|
|
788
|
+
session_id: sessionId,
|
|
789
|
+
active_skills: [{ skill: "autopilot", phase: "ultragoal", active: true, session_id: sessionId }],
|
|
790
|
+
});
|
|
791
|
+
const stdout = runNativeHookCli({
|
|
792
|
+
hook_event_name: "Stop",
|
|
793
|
+
cwd,
|
|
794
|
+
session_id: sessionId,
|
|
795
|
+
thread_id: "thread-stale-autopilot-mirror",
|
|
796
|
+
}, { cwd });
|
|
797
|
+
assert.deepEqual(parseSingleJsonStdout(stdout), {});
|
|
798
|
+
}
|
|
799
|
+
finally {
|
|
800
|
+
await rm(cwd, { recursive: true, force: true });
|
|
801
|
+
}
|
|
802
|
+
});
|
|
803
|
+
it("includes blocking state source and canonical agreement in Stop diagnostics", async () => {
|
|
804
|
+
const cwd = await mkdtemp(join(tmpdir(), "omx-native-hook-autopilot-diagnostic-"));
|
|
805
|
+
try {
|
|
806
|
+
const sessionId = "sess-autopilot-diagnostic";
|
|
807
|
+
await writeJson(join(cwd, ".omx", "state", "session.json"), { session_id: sessionId, cwd });
|
|
808
|
+
await writeJson(join(cwd, ".omx", "state", "skill-active-state.json"), {
|
|
809
|
+
version: 1,
|
|
810
|
+
active: true,
|
|
811
|
+
skill: "autopilot",
|
|
812
|
+
phase: "ultragoal",
|
|
813
|
+
session_id: sessionId,
|
|
814
|
+
active_skills: [{ skill: "autopilot", phase: "ultragoal", active: true, session_id: sessionId }],
|
|
815
|
+
});
|
|
816
|
+
await writeJson(join(cwd, ".omx", "state", "sessions", sessionId, "autopilot-state.json"), {
|
|
817
|
+
active: true,
|
|
818
|
+
mode: "autopilot",
|
|
819
|
+
current_phase: "ultragoal",
|
|
820
|
+
session_id: sessionId,
|
|
821
|
+
});
|
|
822
|
+
const output = parseSingleJsonStdout(runNativeHookCli({
|
|
823
|
+
hook_event_name: "Stop",
|
|
824
|
+
cwd,
|
|
825
|
+
session_id: sessionId,
|
|
826
|
+
thread_id: "thread-autopilot-diagnostic",
|
|
827
|
+
}, { cwd }));
|
|
828
|
+
assert.equal(output.decision, "block");
|
|
829
|
+
assert.match(String(output.reason ?? ""), /state: \.omx\/state\/sessions\/sess-autopilot-diagnostic\/autopilot-state\.json/);
|
|
830
|
+
assert.match(String(output.reason ?? ""), /canonical: canonical_agrees/);
|
|
831
|
+
assert.equal(output.statePath, ".omx/state/sessions/sess-autopilot-diagnostic/autopilot-state.json");
|
|
832
|
+
assert.equal(output.canonicalDisagreement, "canonical_agrees");
|
|
833
|
+
}
|
|
834
|
+
finally {
|
|
835
|
+
await rm(cwd, { recursive: true, force: true });
|
|
836
|
+
}
|
|
837
|
+
});
|
|
570
838
|
it("emits exactly one parseable JSON object for active Stop CLI continuation", async () => {
|
|
571
839
|
const cwd = await mkdtemp(join(tmpdir(), "omx-native-hook-cli-stop-json-"));
|
|
572
840
|
try {
|
|
@@ -2380,6 +2648,140 @@ standardMaxRounds = 15
|
|
|
2380
2648
|
await rm(cwd, { recursive: true, force: true });
|
|
2381
2649
|
}
|
|
2382
2650
|
});
|
|
2651
|
+
it("nudges next-goal prompts when a completed Codex goal cleanup step remains", async () => {
|
|
2652
|
+
const cwd = await mkdtemp(join(tmpdir(), "omx-native-hook-completed-goal-prompt-"));
|
|
2653
|
+
try {
|
|
2654
|
+
await writeJson(join(cwd, ".omx", "goals", "performance", "latency", "state.json"), {
|
|
2655
|
+
version: 1,
|
|
2656
|
+
workflow: "performance-goal",
|
|
2657
|
+
slug: "latency",
|
|
2658
|
+
objective: "Reduce latency",
|
|
2659
|
+
status: "complete",
|
|
2660
|
+
completedAt: "2026-05-20T00:00:00.000Z",
|
|
2661
|
+
});
|
|
2662
|
+
const result = await dispatchCodexNativeHook({
|
|
2663
|
+
hook_event_name: "UserPromptSubmit",
|
|
2664
|
+
cwd,
|
|
2665
|
+
session_id: "sess-completed-goal-prompt",
|
|
2666
|
+
thread_id: "thread-completed-goal-prompt",
|
|
2667
|
+
prompt: "Start the next performance goal now",
|
|
2668
|
+
}, { cwd });
|
|
2669
|
+
const output = JSON.stringify(result.outputJson);
|
|
2670
|
+
assert.match(output, /run \/goal clear/);
|
|
2671
|
+
assert.match(output, /before calling create_goal/);
|
|
2672
|
+
assert.match(output, /hooks only nudge and must not mutate Codex goal state/);
|
|
2673
|
+
assert.doesNotMatch(output, /cleared Codex goal state/i);
|
|
2674
|
+
}
|
|
2675
|
+
finally {
|
|
2676
|
+
await rm(cwd, { recursive: true, force: true });
|
|
2677
|
+
}
|
|
2678
|
+
});
|
|
2679
|
+
it("does not block ordinary Stop after completed Ultragoal cleanup remains", async () => {
|
|
2680
|
+
const cwd = await mkdtemp(join(tmpdir(), "omx-native-hook-completed-ultragoal-ordinary-stop-"));
|
|
2681
|
+
try {
|
|
2682
|
+
await writeJson(join(cwd, ".omx", "ultragoal", "goals.json"), {
|
|
2683
|
+
version: 1,
|
|
2684
|
+
aggregateCompletion: { status: "complete", completedAt: "2026-05-20T00:00:00.000Z" },
|
|
2685
|
+
goals: [{ id: "G001-done", status: "complete", objective: "Done" }],
|
|
2686
|
+
});
|
|
2687
|
+
const result = await dispatchCodexNativeHook({
|
|
2688
|
+
hook_event_name: "Stop",
|
|
2689
|
+
cwd,
|
|
2690
|
+
session_id: "sess-completed-ultragoal-ordinary-stop",
|
|
2691
|
+
thread_id: "thread-completed-ultragoal-ordinary-stop",
|
|
2692
|
+
last_assistant_message: "Implemented the requested fix and verified the focused tests.",
|
|
2693
|
+
}, { cwd });
|
|
2694
|
+
const output = JSON.stringify(result.outputJson);
|
|
2695
|
+
assert.notEqual(result.outputJson?.decision, "block");
|
|
2696
|
+
assert.notEqual(result.outputJson?.stopReason, "completed_codex_goal_cleanup_required");
|
|
2697
|
+
assert.doesNotMatch(output, /run \/goal clear/);
|
|
2698
|
+
}
|
|
2699
|
+
finally {
|
|
2700
|
+
await rm(cwd, { recursive: true, force: true });
|
|
2701
|
+
}
|
|
2702
|
+
});
|
|
2703
|
+
it("does not treat explanatory create_goal warnings as completed-goal cleanup attempts", async () => {
|
|
2704
|
+
const cwd = await mkdtemp(join(tmpdir(), "omx-native-hook-completed-goal-explainer-stop-"));
|
|
2705
|
+
try {
|
|
2706
|
+
await writeJson(join(cwd, ".omx", "ultragoal", "goals.json"), {
|
|
2707
|
+
version: 1,
|
|
2708
|
+
aggregateCompletion: { status: "complete", completedAt: "2026-05-20T00:00:00.000Z" },
|
|
2709
|
+
goals: [{ id: "G001-done", status: "complete", objective: "Done" }],
|
|
2710
|
+
});
|
|
2711
|
+
for (const [index, last_assistant_message] of [
|
|
2712
|
+
"Do not call create_goal until the user has explicitly cleared the old goal state.",
|
|
2713
|
+
"No create_goal attempt was made; this is only the final summary.",
|
|
2714
|
+
"I am not calling create_goal; the completed work is summarized above.",
|
|
2715
|
+
"I am not starting another goal; cleanup is already documented.",
|
|
2716
|
+
"Do not start another goal until cleanup is explicit.",
|
|
2717
|
+
"Do not create a new ultragoal; this is only a final summary.",
|
|
2718
|
+
].entries()) {
|
|
2719
|
+
const result = await dispatchCodexNativeHook({
|
|
2720
|
+
hook_event_name: "Stop",
|
|
2721
|
+
cwd,
|
|
2722
|
+
session_id: `sess-completed-goal-explainer-stop-${index}`,
|
|
2723
|
+
thread_id: `thread-completed-goal-explainer-stop-${index}`,
|
|
2724
|
+
last_assistant_message,
|
|
2725
|
+
}, { cwd });
|
|
2726
|
+
const output = JSON.stringify(result.outputJson);
|
|
2727
|
+
assert.notEqual(result.outputJson?.decision, "block");
|
|
2728
|
+
assert.notEqual(result.outputJson?.stopReason, "completed_codex_goal_cleanup_required");
|
|
2729
|
+
assert.doesNotMatch(output, /run \/goal clear/);
|
|
2730
|
+
}
|
|
2731
|
+
}
|
|
2732
|
+
finally {
|
|
2733
|
+
await rm(cwd, { recursive: true, force: true });
|
|
2734
|
+
}
|
|
2735
|
+
});
|
|
2736
|
+
it("blocks explicit create_goal attempts when fresh native-goal cleanup evidence remains", async () => {
|
|
2737
|
+
const cwd = await mkdtemp(join(tmpdir(), "omx-native-hook-completed-goal-mixed-stop-"));
|
|
2738
|
+
try {
|
|
2739
|
+
await writeJson(join(cwd, ".omx", "ultragoal", "goals.json"), {
|
|
2740
|
+
version: 1,
|
|
2741
|
+
aggregateCompletion: { status: "complete", completedAt: "2026-05-20T00:00:00.000Z" },
|
|
2742
|
+
goals: [{ id: "G001-done", status: "complete", objective: "Done" }],
|
|
2743
|
+
});
|
|
2744
|
+
const result = await dispatchCodexNativeHook({
|
|
2745
|
+
hook_event_name: "Stop",
|
|
2746
|
+
cwd,
|
|
2747
|
+
session_id: "sess-completed-goal-mixed-stop",
|
|
2748
|
+
thread_id: "thread-completed-goal-mixed-stop",
|
|
2749
|
+
last_user_message: "get_goal reports a completed Codex goal still attached to this thread; do not call create_goal until cleanup is explicit.",
|
|
2750
|
+
last_assistant_message: "I am starting another run now; create_goal payload follows.",
|
|
2751
|
+
}, { cwd });
|
|
2752
|
+
const output = JSON.stringify(result.outputJson);
|
|
2753
|
+
assert.equal(result.outputJson?.decision, "block");
|
|
2754
|
+
assert.equal(result.outputJson?.stopReason, "completed_codex_goal_cleanup_required");
|
|
2755
|
+
assert.match(output, /run \/goal clear/);
|
|
2756
|
+
}
|
|
2757
|
+
finally {
|
|
2758
|
+
await rm(cwd, { recursive: true, force: true });
|
|
2759
|
+
}
|
|
2760
|
+
});
|
|
2761
|
+
it("does not block Stop when completed durable history alone precedes another goal", async () => {
|
|
2762
|
+
const cwd = await mkdtemp(join(tmpdir(), "omx-native-hook-completed-goal-stop-"));
|
|
2763
|
+
try {
|
|
2764
|
+
await writeJson(join(cwd, ".omx", "ultragoal", "goals.json"), {
|
|
2765
|
+
version: 1,
|
|
2766
|
+
aggregateCompletion: { status: "complete", completedAt: "2026-05-20T00:00:00.000Z" },
|
|
2767
|
+
goals: [{ id: "G001-done", status: "complete", objective: "Done" }],
|
|
2768
|
+
});
|
|
2769
|
+
const result = await dispatchCodexNativeHook({
|
|
2770
|
+
hook_event_name: "Stop",
|
|
2771
|
+
cwd,
|
|
2772
|
+
session_id: "sess-completed-goal-stop",
|
|
2773
|
+
thread_id: "thread-completed-goal-stop",
|
|
2774
|
+
last_assistant_message: "Starting another ultragoal now; create_goal payload follows after /goal clear already cleared native state.",
|
|
2775
|
+
}, { cwd });
|
|
2776
|
+
const output = JSON.stringify(result.outputJson);
|
|
2777
|
+
assert.notEqual(result.outputJson?.decision, "block");
|
|
2778
|
+
assert.notEqual(result.outputJson?.stopReason, "completed_codex_goal_cleanup_required");
|
|
2779
|
+
assert.doesNotMatch(output, /run \/goal clear/);
|
|
2780
|
+
}
|
|
2781
|
+
finally {
|
|
2782
|
+
await rm(cwd, { recursive: true, force: true });
|
|
2783
|
+
}
|
|
2784
|
+
});
|
|
2383
2785
|
it("blocks ultragoal Stop for concise generic goal completion claims", async () => {
|
|
2384
2786
|
const cwd = await mkdtemp(join(tmpdir(), "omx-native-hook-ultragoal-generic-complete-stop-"));
|
|
2385
2787
|
try {
|
|
@@ -5279,18 +5681,139 @@ exit 0
|
|
|
5279
5681
|
await rm(cwd, { recursive: true, force: true });
|
|
5280
5682
|
}
|
|
5281
5683
|
});
|
|
5282
|
-
it("allows
|
|
5283
|
-
const cwd = await mkdtemp(join(tmpdir(), "omx-native-hook-pretool-
|
|
5684
|
+
it("allows Ralplan read-only inspection and planning artifact writes while blocking implementation targets", async () => {
|
|
5685
|
+
const cwd = await mkdtemp(join(tmpdir(), "omx-native-hook-pretool-ralplan-guard-"));
|
|
5284
5686
|
try {
|
|
5285
5687
|
const stateDir = join(cwd, ".omx", "state");
|
|
5286
|
-
const
|
|
5688
|
+
const sessionId = "sess-ralplan-guard";
|
|
5689
|
+
const sessionDir = join(stateDir, "sessions", sessionId);
|
|
5287
5690
|
await mkdir(sessionDir, { recursive: true });
|
|
5288
|
-
await writeJson(join(stateDir, "session.json"), { session_id:
|
|
5691
|
+
await writeJson(join(stateDir, "session.json"), { session_id: sessionId, cwd });
|
|
5289
5692
|
await writeJson(join(sessionDir, "skill-active-state.json"), {
|
|
5290
5693
|
version: 1,
|
|
5291
5694
|
active: true,
|
|
5292
|
-
skill: "
|
|
5293
|
-
phase: "
|
|
5695
|
+
skill: "ralplan",
|
|
5696
|
+
phase: "planning",
|
|
5697
|
+
session_id: sessionId,
|
|
5698
|
+
active_skills: [{ skill: "ralplan", phase: "planning", active: true, session_id: sessionId }],
|
|
5699
|
+
});
|
|
5700
|
+
await writeJson(join(sessionDir, "ralplan-state.json"), {
|
|
5701
|
+
active: true,
|
|
5702
|
+
mode: "ralplan",
|
|
5703
|
+
current_phase: "planning",
|
|
5704
|
+
session_id: sessionId,
|
|
5705
|
+
});
|
|
5706
|
+
const preToolUse = async (tool_name, tool_use_id, tool_input) => dispatchCodexNativeHook({
|
|
5707
|
+
hook_event_name: "PreToolUse",
|
|
5708
|
+
cwd,
|
|
5709
|
+
session_id: sessionId,
|
|
5710
|
+
tool_name,
|
|
5711
|
+
tool_use_id,
|
|
5712
|
+
tool_input,
|
|
5713
|
+
}, { cwd });
|
|
5714
|
+
const readOnlyCommands = [
|
|
5715
|
+
"git status --short",
|
|
5716
|
+
"ls -1 .omx/plans",
|
|
5717
|
+
"find .omx/plans -type f -name '*.md'",
|
|
5718
|
+
"grep -RIn \"Scholastic\" doc tests .omx/plans .omx/context",
|
|
5719
|
+
"rg \"Scholastic\" doc tests .omx/plans .omx/context",
|
|
5720
|
+
"sed -n '1,80p' .omx/plans/demo.md",
|
|
5721
|
+
"cat .omx/plans/demo.md",
|
|
5722
|
+
"git status --short; ls -1 .omx/plans; grep -RIn \"Scholastic\" doc tests .omx/plans .omx/context",
|
|
5723
|
+
];
|
|
5724
|
+
for (const [index, command] of readOnlyCommands.entries()) {
|
|
5725
|
+
const result = await preToolUse("Bash", `tool-ralplan-read-${index}`, { command });
|
|
5726
|
+
assert.equal(result.outputJson, null, `read-only command should be allowed: ${command}`);
|
|
5727
|
+
}
|
|
5728
|
+
for (const path of [
|
|
5729
|
+
".omx/context/findings.md",
|
|
5730
|
+
".omx/plans/issue-2863.md",
|
|
5731
|
+
".omx/specs/issue-2863.md",
|
|
5732
|
+
".omx/state/required-planning-state.json",
|
|
5733
|
+
]) {
|
|
5734
|
+
const writeResult = await preToolUse("Write", `tool-ralplan-write-${path}`, { file_path: path, content: "ok" });
|
|
5735
|
+
assert.equal(writeResult.outputJson, null, `Write should be allowed for ${path}`);
|
|
5736
|
+
}
|
|
5737
|
+
const allowedPatchAdd = await preToolUse("apply_patch", "tool-ralplan-patch-add", {
|
|
5738
|
+
input: "*** Begin Patch\n*** Add File: .omx/plans/issue-2863.md\n+# Plan\n*** End Patch\n",
|
|
5739
|
+
});
|
|
5740
|
+
assert.equal(allowedPatchAdd.outputJson, null);
|
|
5741
|
+
const allowedPatchUpdate = await preToolUse("ApplyPatch", "tool-ralplan-patch-update", {
|
|
5742
|
+
input: "*** Begin Patch\n*** Update File: .omx/plans/issue-2863.md\n@@\n-old\n+new\n*** End Patch\n",
|
|
5743
|
+
});
|
|
5744
|
+
assert.equal(allowedPatchUpdate.outputJson, null);
|
|
5745
|
+
const allowedRedirect = await preToolUse("Bash", "tool-ralplan-redirect-allow", {
|
|
5746
|
+
command: "printf '\\nmore\\n' >> .omx/plans/issue-2863.md",
|
|
5747
|
+
});
|
|
5748
|
+
assert.equal(allowedRedirect.outputJson, null);
|
|
5749
|
+
const allowedTee = await preToolUse("Bash", "tool-ralplan-tee-allow", {
|
|
5750
|
+
command: "printf '\\nmore\\n' | tee -a .omx/specs/issue-2863.md",
|
|
5751
|
+
});
|
|
5752
|
+
assert.equal(allowedTee.outputJson, null);
|
|
5753
|
+
const blockedRedirect = await preToolUse("Bash", "tool-ralplan-redirect-block", {
|
|
5754
|
+
command: "printf 'bad' > src/implementation.ts",
|
|
5755
|
+
});
|
|
5756
|
+
assert.equal(blockedRedirect.outputJson?.decision, "block");
|
|
5757
|
+
const redirectReason = String(blockedRedirect.outputJson?.reason ?? "");
|
|
5758
|
+
assert.match(redirectReason, /Bash redirect write/);
|
|
5759
|
+
assert.match(redirectReason, /src\/implementation\.ts/);
|
|
5760
|
+
const blockedEdit = await preToolUse("Edit", "tool-ralplan-edit-block", {
|
|
5761
|
+
file_path: "src/implementation.ts",
|
|
5762
|
+
old_string: "a",
|
|
5763
|
+
new_string: "b",
|
|
5764
|
+
});
|
|
5765
|
+
assert.equal(blockedEdit.outputJson?.decision, "block");
|
|
5766
|
+
const editReason = String(blockedEdit.outputJson?.reason ?? "");
|
|
5767
|
+
assert.match(editReason, /Edit path/);
|
|
5768
|
+
assert.match(editReason, /src\/implementation\.ts/);
|
|
5769
|
+
const blockedMixedPatch = await preToolUse("apply_patch", "tool-ralplan-patch-mixed", {
|
|
5770
|
+
input: "*** Begin Patch\n*** Add File: .omx/plans/ok.md\n+ok\n*** Add File: src/leak.ts\n+leak\n*** End Patch\n",
|
|
5771
|
+
});
|
|
5772
|
+
assert.equal(blockedMixedPatch.outputJson?.decision, "block");
|
|
5773
|
+
const mixedReason = String(blockedMixedPatch.outputJson?.reason ?? "");
|
|
5774
|
+
assert.match(mixedReason, /apply_patch target/);
|
|
5775
|
+
assert.match(mixedReason, /src\/leak\.ts/);
|
|
5776
|
+
const blockedUnparseablePatch = await preToolUse("apply_patch", "tool-ralplan-patch-unparseable", {
|
|
5777
|
+
input: "not a recognizable patch",
|
|
5778
|
+
});
|
|
5779
|
+
assert.equal(blockedUnparseablePatch.outputJson?.decision, "block");
|
|
5780
|
+
assert.match(String(blockedUnparseablePatch.outputJson?.reason ?? ""), /apply_patch target extraction failed/);
|
|
5781
|
+
const blockedUnresolvedRedirect = await preToolUse("Bash", "tool-ralplan-redirect-unresolved", {
|
|
5782
|
+
command: "cat > \"$PLAN_PATH\" <<'EOF'\ncontent\nEOF",
|
|
5783
|
+
});
|
|
5784
|
+
assert.equal(blockedUnresolvedRedirect.outputJson?.decision, "block");
|
|
5785
|
+
const unresolvedReason = String(blockedUnresolvedRedirect.outputJson?.reason ?? "");
|
|
5786
|
+
assert.match(unresolvedReason, /unresolved Bash write target/);
|
|
5787
|
+
assert.match(unresolvedReason, /\$PLAN_PATH/);
|
|
5788
|
+
const blockedTraversal = await preToolUse("Write", "tool-ralplan-traversal-block", {
|
|
5789
|
+
file_path: ".omx/plans/../../src/leak.ts",
|
|
5790
|
+
content: "bad",
|
|
5791
|
+
});
|
|
5792
|
+
assert.equal(blockedTraversal.outputJson?.decision, "block");
|
|
5793
|
+
assert.match(String(blockedTraversal.outputJson?.reason ?? ""), /\.omx\/plans\/\.\.\/\.\.\/src\/leak\.ts/);
|
|
5794
|
+
const blockedAbsolute = await preToolUse("Write", "tool-ralplan-absolute-block", {
|
|
5795
|
+
file_path: "/tmp/leak.ts",
|
|
5796
|
+
content: "bad",
|
|
5797
|
+
});
|
|
5798
|
+
assert.equal(blockedAbsolute.outputJson?.decision, "block");
|
|
5799
|
+
assert.match(String(blockedAbsolute.outputJson?.reason ?? ""), /\/tmp\/leak\.ts/);
|
|
5800
|
+
}
|
|
5801
|
+
finally {
|
|
5802
|
+
await rm(cwd, { recursive: true, force: true });
|
|
5803
|
+
}
|
|
5804
|
+
});
|
|
5805
|
+
it("allows Autopilot ralplan planning artifacts while blocking implementation writes", async () => {
|
|
5806
|
+
const cwd = await mkdtemp(join(tmpdir(), "omx-native-hook-pretool-autopilot-ralplan-artifact-"));
|
|
5807
|
+
try {
|
|
5808
|
+
const stateDir = join(cwd, ".omx", "state");
|
|
5809
|
+
const sessionDir = join(stateDir, "sessions", "sess-autopilot-ralplan-artifact");
|
|
5810
|
+
await mkdir(sessionDir, { recursive: true });
|
|
5811
|
+
await writeJson(join(stateDir, "session.json"), { session_id: "sess-autopilot-ralplan-artifact", cwd });
|
|
5812
|
+
await writeJson(join(sessionDir, "skill-active-state.json"), {
|
|
5813
|
+
version: 1,
|
|
5814
|
+
active: true,
|
|
5815
|
+
skill: "autopilot",
|
|
5816
|
+
phase: "ralplan",
|
|
5294
5817
|
session_id: "sess-autopilot-ralplan-artifact",
|
|
5295
5818
|
thread_id: "thread-autopilot-ralplan-artifact",
|
|
5296
5819
|
active_skills: [
|
|
@@ -5347,6 +5870,225 @@ exit 0
|
|
|
5347
5870
|
await rm(cwd, { recursive: true, force: true });
|
|
5348
5871
|
}
|
|
5349
5872
|
});
|
|
5873
|
+
it("allows Ralplan Beads tracker metadata writes during planning", async () => {
|
|
5874
|
+
const cwd = await mkdtemp(join(tmpdir(), "omx-native-hook-pretool-ralplan-beads-"));
|
|
5875
|
+
try {
|
|
5876
|
+
const stateDir = join(cwd, ".omx", "state");
|
|
5877
|
+
const sessionDir = join(stateDir, "sessions", "sess-ralplan-beads");
|
|
5878
|
+
await mkdir(sessionDir, { recursive: true });
|
|
5879
|
+
await writeJson(join(stateDir, "session.json"), { session_id: "sess-ralplan-beads", cwd });
|
|
5880
|
+
await writeJson(join(sessionDir, "skill-active-state.json"), {
|
|
5881
|
+
version: 1,
|
|
5882
|
+
active: true,
|
|
5883
|
+
skill: "ralplan",
|
|
5884
|
+
phase: "planning",
|
|
5885
|
+
session_id: "sess-ralplan-beads",
|
|
5886
|
+
thread_id: "thread-ralplan-beads",
|
|
5887
|
+
active_skills: [
|
|
5888
|
+
{
|
|
5889
|
+
skill: "ralplan",
|
|
5890
|
+
phase: "planning",
|
|
5891
|
+
active: true,
|
|
5892
|
+
session_id: "sess-ralplan-beads",
|
|
5893
|
+
thread_id: "thread-ralplan-beads",
|
|
5894
|
+
},
|
|
5895
|
+
],
|
|
5896
|
+
});
|
|
5897
|
+
await writeJson(join(sessionDir, "ralplan-state.json"), {
|
|
5898
|
+
active: true,
|
|
5899
|
+
mode: "ralplan",
|
|
5900
|
+
current_phase: "planning",
|
|
5901
|
+
session_id: "sess-ralplan-beads",
|
|
5902
|
+
thread_id: "thread-ralplan-beads",
|
|
5903
|
+
});
|
|
5904
|
+
const allowedWrite = await dispatchCodexNativeHook({
|
|
5905
|
+
hook_event_name: "PreToolUse",
|
|
5906
|
+
cwd,
|
|
5907
|
+
session_id: "sess-ralplan-beads",
|
|
5908
|
+
thread_id: "thread-ralplan-beads",
|
|
5909
|
+
tool_name: "Write",
|
|
5910
|
+
tool_use_id: "tool-ralplan-beads-write",
|
|
5911
|
+
tool_input: { file_path: ".beads/issues.db", content: "metadata" },
|
|
5912
|
+
}, { cwd });
|
|
5913
|
+
assert.equal(allowedWrite.outputJson, null);
|
|
5914
|
+
const allowedEdit = await dispatchCodexNativeHook({
|
|
5915
|
+
hook_event_name: "PreToolUse",
|
|
5916
|
+
cwd,
|
|
5917
|
+
session_id: "sess-ralplan-beads",
|
|
5918
|
+
thread_id: "thread-ralplan-beads",
|
|
5919
|
+
tool_name: "Edit",
|
|
5920
|
+
tool_use_id: "tool-ralplan-beads-edit",
|
|
5921
|
+
tool_input: { file_path: ".beads/tasks/issue.json", old_string: "old", new_string: "new" },
|
|
5922
|
+
}, { cwd });
|
|
5923
|
+
assert.equal(allowedEdit.outputJson, null);
|
|
5924
|
+
const allowedPatch = await dispatchCodexNativeHook({
|
|
5925
|
+
hook_event_name: "PreToolUse",
|
|
5926
|
+
cwd,
|
|
5927
|
+
session_id: "sess-ralplan-beads",
|
|
5928
|
+
thread_id: "thread-ralplan-beads",
|
|
5929
|
+
tool_name: "apply_patch",
|
|
5930
|
+
tool_use_id: "tool-ralplan-beads-apply-patch",
|
|
5931
|
+
tool_input: {
|
|
5932
|
+
input: "*** Begin Patch\n*** Add File: .beads/tasks/issue.json\n+{}\n*** End Patch\n",
|
|
5933
|
+
},
|
|
5934
|
+
}, { cwd });
|
|
5935
|
+
assert.equal(allowedPatch.outputJson, null);
|
|
5936
|
+
const allowedCommands = [
|
|
5937
|
+
"bd --db .beads/issues.db create 'Issue title'",
|
|
5938
|
+
"bd --db .beads/issues.db update OMX-1 --title 'Issue title'",
|
|
5939
|
+
"bd --db .beads/issues.db edit OMX-1 --title 'Issue title'",
|
|
5940
|
+
"bd --db .beads/issues.db comments add OMX-1 'evidence'",
|
|
5941
|
+
"bd --db .beads/issues.db close OMX-1",
|
|
5942
|
+
"bd --db .beads/issues.db reopen OMX-1",
|
|
5943
|
+
"bd --db .beads/issues.db status OMX-1",
|
|
5944
|
+
"bd --db .beads/issues.db dep add OMX-1 OMX-2",
|
|
5945
|
+
];
|
|
5946
|
+
for (const [index, command] of allowedCommands.entries()) {
|
|
5947
|
+
const result = await dispatchCodexNativeHook({
|
|
5948
|
+
hook_event_name: "PreToolUse",
|
|
5949
|
+
cwd,
|
|
5950
|
+
session_id: "sess-ralplan-beads",
|
|
5951
|
+
thread_id: "thread-ralplan-beads",
|
|
5952
|
+
tool_name: "Bash",
|
|
5953
|
+
tool_use_id: `tool-ralplan-beads-bd-${index}`,
|
|
5954
|
+
tool_input: { command },
|
|
5955
|
+
}, { cwd });
|
|
5956
|
+
assert.equal(result.outputJson, null, `expected Beads metadata command to be allowed: ${command}`);
|
|
5957
|
+
}
|
|
5958
|
+
}
|
|
5959
|
+
finally {
|
|
5960
|
+
await rm(cwd, { recursive: true, force: true });
|
|
5961
|
+
}
|
|
5962
|
+
});
|
|
5963
|
+
it("blocks unsafe Beads tracker targets and mixed implementation writes during Autopilot ralplan planning", async () => {
|
|
5964
|
+
const cwd = await mkdtemp(join(tmpdir(), "omx-native-hook-pretool-autopilot-beads-block-"));
|
|
5965
|
+
try {
|
|
5966
|
+
const stateDir = join(cwd, ".omx", "state");
|
|
5967
|
+
const sessionDir = join(stateDir, "sessions", "sess-autopilot-beads-block");
|
|
5968
|
+
await mkdir(sessionDir, { recursive: true });
|
|
5969
|
+
await writeJson(join(stateDir, "session.json"), { session_id: "sess-autopilot-beads-block", cwd });
|
|
5970
|
+
await writeJson(join(sessionDir, "skill-active-state.json"), {
|
|
5971
|
+
version: 1,
|
|
5972
|
+
active: true,
|
|
5973
|
+
skill: "autopilot",
|
|
5974
|
+
phase: "ralplan",
|
|
5975
|
+
session_id: "sess-autopilot-beads-block",
|
|
5976
|
+
thread_id: "thread-autopilot-beads-block",
|
|
5977
|
+
active_skills: [
|
|
5978
|
+
{
|
|
5979
|
+
skill: "autopilot",
|
|
5980
|
+
phase: "ralplan",
|
|
5981
|
+
active: true,
|
|
5982
|
+
session_id: "sess-autopilot-beads-block",
|
|
5983
|
+
thread_id: "thread-autopilot-beads-block",
|
|
5984
|
+
},
|
|
5985
|
+
],
|
|
5986
|
+
});
|
|
5987
|
+
await writeJson(join(sessionDir, "autopilot-state.json"), {
|
|
5988
|
+
active: true,
|
|
5989
|
+
mode: "autopilot",
|
|
5990
|
+
current_phase: "ralplan",
|
|
5991
|
+
session_id: "sess-autopilot-beads-block",
|
|
5992
|
+
thread_id: "thread-autopilot-beads-block",
|
|
5993
|
+
});
|
|
5994
|
+
const blockedCommands = [
|
|
5995
|
+
"bd --db /tmp/outside.db create 'Issue title'",
|
|
5996
|
+
"bd --db ../outside.db create 'Issue title'",
|
|
5997
|
+
"bd --db .beads create 'Issue title'",
|
|
5998
|
+
"bd --db $DB create 'Issue title'",
|
|
5999
|
+
"DB=.beads/issues.db bd --db $DB create 'Issue title'",
|
|
6000
|
+
"env bd --db /tmp/outside.db create 'Issue title'",
|
|
6001
|
+
"command bd --db /tmp/outside.db create 'Issue title'",
|
|
6002
|
+
"bd --db .beads/issues.db export",
|
|
6003
|
+
"bd --db .beads/issues.db create 'Issue title' > src/leak.ts",
|
|
6004
|
+
"bd --db .beads/issues.db create 'Issue title'; cat > src/leak.ts",
|
|
6005
|
+
];
|
|
6006
|
+
for (const [index, command] of blockedCommands.entries()) {
|
|
6007
|
+
const result = await dispatchCodexNativeHook({
|
|
6008
|
+
hook_event_name: "PreToolUse",
|
|
6009
|
+
cwd,
|
|
6010
|
+
session_id: "sess-autopilot-beads-block",
|
|
6011
|
+
thread_id: "thread-autopilot-beads-block",
|
|
6012
|
+
tool_name: "Bash",
|
|
6013
|
+
tool_use_id: `tool-autopilot-beads-block-${index}`,
|
|
6014
|
+
tool_input: { command },
|
|
6015
|
+
}, { cwd });
|
|
6016
|
+
assert.equal(result.outputJson?.decision, "block", `expected unsafe Beads command to be blocked: ${command}`);
|
|
6017
|
+
}
|
|
6018
|
+
const mentionWithImplementationWrite = await dispatchCodexNativeHook({
|
|
6019
|
+
hook_event_name: "PreToolUse",
|
|
6020
|
+
cwd,
|
|
6021
|
+
session_id: "sess-autopilot-beads-block",
|
|
6022
|
+
thread_id: "thread-autopilot-beads-block",
|
|
6023
|
+
tool_name: "Bash",
|
|
6024
|
+
tool_use_id: "tool-autopilot-beads-mention-write",
|
|
6025
|
+
tool_input: { command: "echo bd --db .beads/issues.db create > src/leak.ts" },
|
|
6026
|
+
}, { cwd });
|
|
6027
|
+
assert.equal(mentionWithImplementationWrite.outputJson?.decision, "block");
|
|
6028
|
+
assert.match(String(mentionWithImplementationWrite.outputJson?.reason ?? ""), /src\/leak\.ts/);
|
|
6029
|
+
const blockedImplementationEdit = await dispatchCodexNativeHook({
|
|
6030
|
+
hook_event_name: "PreToolUse",
|
|
6031
|
+
cwd,
|
|
6032
|
+
session_id: "sess-autopilot-beads-block",
|
|
6033
|
+
thread_id: "thread-autopilot-beads-block",
|
|
6034
|
+
tool_name: "Edit",
|
|
6035
|
+
tool_use_id: "tool-autopilot-beads-src-edit",
|
|
6036
|
+
tool_input: { file_path: "src/implementation.ts", old_string: "a", new_string: "b" },
|
|
6037
|
+
}, { cwd });
|
|
6038
|
+
assert.equal(blockedImplementationEdit.outputJson?.decision, "block");
|
|
6039
|
+
}
|
|
6040
|
+
finally {
|
|
6041
|
+
await rm(cwd, { recursive: true, force: true });
|
|
6042
|
+
}
|
|
6043
|
+
});
|
|
6044
|
+
it("allows Autopilot rework implementation writes while ralplan remains guarded", async () => {
|
|
6045
|
+
const cwd = await mkdtemp(join(tmpdir(), "omx-native-hook-pretool-autopilot-rework-"));
|
|
6046
|
+
try {
|
|
6047
|
+
const stateDir = join(cwd, ".omx", "state");
|
|
6048
|
+
const sessionDir = join(stateDir, "sessions", "sess-autopilot-rework");
|
|
6049
|
+
await mkdir(sessionDir, { recursive: true });
|
|
6050
|
+
await writeJson(join(stateDir, "session.json"), { session_id: "sess-autopilot-rework", cwd });
|
|
6051
|
+
await writeJson(join(sessionDir, "skill-active-state.json"), {
|
|
6052
|
+
version: 1,
|
|
6053
|
+
active: true,
|
|
6054
|
+
skill: "autopilot",
|
|
6055
|
+
phase: "rework",
|
|
6056
|
+
session_id: "sess-autopilot-rework",
|
|
6057
|
+
thread_id: "thread-autopilot-rework",
|
|
6058
|
+
active_skills: [
|
|
6059
|
+
{
|
|
6060
|
+
skill: "autopilot",
|
|
6061
|
+
phase: "rework",
|
|
6062
|
+
active: true,
|
|
6063
|
+
session_id: "sess-autopilot-rework",
|
|
6064
|
+
thread_id: "thread-autopilot-rework",
|
|
6065
|
+
},
|
|
6066
|
+
],
|
|
6067
|
+
});
|
|
6068
|
+
await writeJson(join(sessionDir, "autopilot-state.json"), {
|
|
6069
|
+
active: true,
|
|
6070
|
+
mode: "autopilot",
|
|
6071
|
+
current_phase: "rework",
|
|
6072
|
+
review_cycle: 2,
|
|
6073
|
+
review_verdict: { clean: false, recommendation: "REQUEST_CHANGES", findings: ["src/implementation.ts"] },
|
|
6074
|
+
session_id: "sess-autopilot-rework",
|
|
6075
|
+
thread_id: "thread-autopilot-rework",
|
|
6076
|
+
});
|
|
6077
|
+
const allowedImplementationEdit = await dispatchCodexNativeHook({
|
|
6078
|
+
hook_event_name: "PreToolUse",
|
|
6079
|
+
cwd,
|
|
6080
|
+
session_id: "sess-autopilot-rework",
|
|
6081
|
+
thread_id: "thread-autopilot-rework",
|
|
6082
|
+
tool_name: "Edit",
|
|
6083
|
+
tool_use_id: "tool-autopilot-rework-src-edit",
|
|
6084
|
+
tool_input: { file_path: "src/implementation.ts", old_string: "a", new_string: "b" },
|
|
6085
|
+
}, { cwd });
|
|
6086
|
+
assert.equal(allowedImplementationEdit.outputJson, null);
|
|
6087
|
+
}
|
|
6088
|
+
finally {
|
|
6089
|
+
await rm(cwd, { recursive: true, force: true });
|
|
6090
|
+
}
|
|
6091
|
+
});
|
|
5350
6092
|
it("allows null-device fd redirects while deep-interview blocks real Bash writes", async () => {
|
|
5351
6093
|
const cwd = await mkdtemp(join(tmpdir(), "omx-native-hook-pretool-deep-interview-null-redirect-"));
|
|
5352
6094
|
try {
|
|
@@ -5488,6 +6230,173 @@ exit 0
|
|
|
5488
6230
|
await rm(cwd, { recursive: true, force: true });
|
|
5489
6231
|
}
|
|
5490
6232
|
});
|
|
6233
|
+
it("records native subagent capacity exhaustion from spawn_agent PostToolUse output", async () => {
|
|
6234
|
+
const cwd = await mkdtemp(join(tmpdir(), "omx-native-hook-subagent-capacity-record-"));
|
|
6235
|
+
try {
|
|
6236
|
+
const result = await dispatchCodexNativeHook({
|
|
6237
|
+
hook_event_name: "PostToolUse",
|
|
6238
|
+
cwd,
|
|
6239
|
+
session_id: "sess-subagent-capacity-record",
|
|
6240
|
+
thread_id: "thread-subagent-capacity-record",
|
|
6241
|
+
turn_id: "turn-subagent-capacity-record",
|
|
6242
|
+
tool_name: "multi_agent_v1.spawn_agent",
|
|
6243
|
+
tool_response: {
|
|
6244
|
+
error: "collab spawn failed: agent thread limit reached",
|
|
6245
|
+
},
|
|
6246
|
+
}, { cwd });
|
|
6247
|
+
assert.equal(result.omxEventName, "post-tool-use");
|
|
6248
|
+
assert.equal(result.outputJson, null);
|
|
6249
|
+
const blocker = JSON.parse(await readFile(join(cwd, ".omx", "state", "native-subagent-capacity-blocker.json"), "utf-8"));
|
|
6250
|
+
assert.equal(blocker.reason, "agent_thread_limit_reached");
|
|
6251
|
+
assert.equal(blocker.session_id, "sess-subagent-capacity-record");
|
|
6252
|
+
assert.equal(blocker.thread_id, "thread-subagent-capacity-record");
|
|
6253
|
+
assert.equal(blocker.tool_name, "multi_agent_v1.spawn_agent");
|
|
6254
|
+
assert.match(String(blocker.error_summary), /agent thread limit reached/);
|
|
6255
|
+
assert.ok(Date.parse(String(blocker.expires_at)) > Date.parse(String(blocker.observed_at)));
|
|
6256
|
+
}
|
|
6257
|
+
finally {
|
|
6258
|
+
await rm(cwd, { recursive: true, force: true });
|
|
6259
|
+
}
|
|
6260
|
+
});
|
|
6261
|
+
it("blocks close_agent cleanup after recent native subagent capacity exhaustion", async () => {
|
|
6262
|
+
const cwd = await mkdtemp(join(tmpdir(), "omx-native-hook-subagent-capacity-close-block-"));
|
|
6263
|
+
try {
|
|
6264
|
+
await dispatchCodexNativeHook({
|
|
6265
|
+
hook_event_name: "PostToolUse",
|
|
6266
|
+
cwd,
|
|
6267
|
+
session_id: "sess-subagent-capacity-close-block",
|
|
6268
|
+
thread_id: "thread-subagent-capacity-close-block",
|
|
6269
|
+
turn_id: "turn-subagent-capacity-close-block",
|
|
6270
|
+
tool_name: "multi_agent_v1.spawn_agent",
|
|
6271
|
+
tool_response: "collab spawn failed: agent thread limit reached",
|
|
6272
|
+
}, { cwd });
|
|
6273
|
+
const result = await dispatchCodexNativeHook({
|
|
6274
|
+
hook_event_name: "PreToolUse",
|
|
6275
|
+
cwd,
|
|
6276
|
+
session_id: "sess-subagent-capacity-close-block",
|
|
6277
|
+
thread_id: "thread-subagent-capacity-close-block",
|
|
6278
|
+
tool_name: "multi_agent_v1.close_agent",
|
|
6279
|
+
tool_input: { target: "019ecc36-stale" },
|
|
6280
|
+
}, { cwd });
|
|
6281
|
+
assert.equal(result.omxEventName, "pre-tool-use");
|
|
6282
|
+
assert.equal(result.outputJson?.decision, "block");
|
|
6283
|
+
assert.match(JSON.stringify(result.outputJson), /agent thread limit reached/);
|
|
6284
|
+
assert.match(JSON.stringify(result.outputJson), /multi_agent_v1\.close_agent/);
|
|
6285
|
+
assert.match(JSON.stringify(result.outputJson), /multi_tool_use\.parallel/);
|
|
6286
|
+
assert.match(JSON.stringify(result.outputJson), /bounded capacity blocker/);
|
|
6287
|
+
}
|
|
6288
|
+
finally {
|
|
6289
|
+
await rm(cwd, { recursive: true, force: true });
|
|
6290
|
+
}
|
|
6291
|
+
});
|
|
6292
|
+
it("emits schema-safe PreToolUse CLI stdout for close_agent capacity blocks", async () => {
|
|
6293
|
+
const cwd = await mkdtemp(join(tmpdir(), "omx-native-hook-cli-subagent-capacity-close-block-"));
|
|
6294
|
+
try {
|
|
6295
|
+
parseSingleJsonStdout(runNativeHookCli({
|
|
6296
|
+
hook_event_name: "PostToolUse",
|
|
6297
|
+
cwd,
|
|
6298
|
+
session_id: "sess-cli-subagent-capacity-close-block",
|
|
6299
|
+
thread_id: "thread-cli-subagent-capacity-close-block",
|
|
6300
|
+
turn_id: "turn-cli-subagent-capacity-close-block",
|
|
6301
|
+
tool_name: "multi_agent_v1.spawn_agent",
|
|
6302
|
+
tool_response: "collab spawn failed: agent thread limit reached",
|
|
6303
|
+
}, { cwd }));
|
|
6304
|
+
const output = parseSingleJsonStdout(runNativeHookCli({
|
|
6305
|
+
hook_event_name: "PreToolUse",
|
|
6306
|
+
cwd,
|
|
6307
|
+
session_id: "sess-cli-subagent-capacity-close-block",
|
|
6308
|
+
thread_id: "thread-cli-subagent-capacity-close-block",
|
|
6309
|
+
tool_name: "multi_agent_v1.close_agent",
|
|
6310
|
+
tool_input: { target: "019ecc36-stale" },
|
|
6311
|
+
}, { cwd }));
|
|
6312
|
+
assert.deepEqual(Object.keys(output).sort(), ["systemMessage"]);
|
|
6313
|
+
assert.match(String(output.systemMessage ?? ""), /agent thread limit reached/);
|
|
6314
|
+
assert.match(String(output.systemMessage ?? ""), /Do not call multi_agent_v1\.close_agent/);
|
|
6315
|
+
assert.equal(output.decision, undefined);
|
|
6316
|
+
assert.equal(output.hookSpecificOutput, undefined);
|
|
6317
|
+
}
|
|
6318
|
+
finally {
|
|
6319
|
+
await rm(cwd, { recursive: true, force: true });
|
|
6320
|
+
}
|
|
6321
|
+
});
|
|
6322
|
+
it("blocks parallel close_agent cleanup after recent native subagent capacity exhaustion", async () => {
|
|
6323
|
+
const cwd = await mkdtemp(join(tmpdir(), "omx-native-hook-subagent-capacity-parallel-close-block-"));
|
|
6324
|
+
try {
|
|
6325
|
+
await dispatchCodexNativeHook({
|
|
6326
|
+
hook_event_name: "PostToolUse",
|
|
6327
|
+
cwd,
|
|
6328
|
+
session_id: "sess-subagent-capacity-parallel-close-block",
|
|
6329
|
+
thread_id: "thread-subagent-capacity-parallel-close-block",
|
|
6330
|
+
tool_name: "multi_agent_v1.spawn_agent",
|
|
6331
|
+
tool_response: "agent thread limit reached",
|
|
6332
|
+
}, { cwd });
|
|
6333
|
+
const result = await dispatchCodexNativeHook({
|
|
6334
|
+
hook_event_name: "PreToolUse",
|
|
6335
|
+
cwd,
|
|
6336
|
+
session_id: "sess-subagent-capacity-parallel-close-block",
|
|
6337
|
+
thread_id: "thread-subagent-capacity-parallel-close-block",
|
|
6338
|
+
tool_name: "multi_tool_use.parallel",
|
|
6339
|
+
tool_input: {
|
|
6340
|
+
tool_uses: [
|
|
6341
|
+
{ recipient_name: "multi_agent_v1.close_agent", parameters: { target: "stale-1" } },
|
|
6342
|
+
{ recipient_name: "multi_agent_v1.close_agent", parameters: { target: "stale-2" } },
|
|
6343
|
+
],
|
|
6344
|
+
},
|
|
6345
|
+
}, { cwd });
|
|
6346
|
+
assert.equal(result.outputJson?.decision, "block");
|
|
6347
|
+
assert.match(JSON.stringify(result.outputJson), /Do not call multi_agent_v1\.close_agent/);
|
|
6348
|
+
assert.match(JSON.stringify(result.outputJson), /do not batch close_agent through multi_tool_use\.parallel/);
|
|
6349
|
+
}
|
|
6350
|
+
finally {
|
|
6351
|
+
await rm(cwd, { recursive: true, force: true });
|
|
6352
|
+
}
|
|
6353
|
+
});
|
|
6354
|
+
it("does not block close_agent without a recent native capacity blocker", async () => {
|
|
6355
|
+
const cwd = await mkdtemp(join(tmpdir(), "omx-native-hook-subagent-capacity-close-no-blocker-"));
|
|
6356
|
+
try {
|
|
6357
|
+
const result = await dispatchCodexNativeHook({
|
|
6358
|
+
hook_event_name: "PreToolUse",
|
|
6359
|
+
cwd,
|
|
6360
|
+
session_id: "sess-subagent-capacity-close-no-blocker",
|
|
6361
|
+
thread_id: "thread-subagent-capacity-close-no-blocker",
|
|
6362
|
+
tool_name: "multi_agent_v1.close_agent",
|
|
6363
|
+
tool_input: { target: "completed-agent" },
|
|
6364
|
+
}, { cwd });
|
|
6365
|
+
assert.equal(result.omxEventName, "pre-tool-use");
|
|
6366
|
+
assert.equal(result.outputJson, null);
|
|
6367
|
+
}
|
|
6368
|
+
finally {
|
|
6369
|
+
await rm(cwd, { recursive: true, force: true });
|
|
6370
|
+
}
|
|
6371
|
+
});
|
|
6372
|
+
it("does not block close_agent when the native capacity blocker is expired", async () => {
|
|
6373
|
+
const cwd = await mkdtemp(join(tmpdir(), "omx-native-hook-subagent-capacity-close-expired-"));
|
|
6374
|
+
try {
|
|
6375
|
+
const stateDir = join(cwd, ".omx", "state");
|
|
6376
|
+
await mkdir(stateDir, { recursive: true });
|
|
6377
|
+
await writeJson(join(stateDir, "native-subagent-capacity-blocker.json"), {
|
|
6378
|
+
schema_version: 1,
|
|
6379
|
+
reason: "agent_thread_limit_reached",
|
|
6380
|
+
session_id: "sess-subagent-capacity-close-expired",
|
|
6381
|
+
error_summary: "agent thread limit reached",
|
|
6382
|
+
observed_at: "2026-06-20T00:00:00.000Z",
|
|
6383
|
+
expires_at: "2026-06-20T00:30:00.000Z",
|
|
6384
|
+
cwd,
|
|
6385
|
+
});
|
|
6386
|
+
const result = await dispatchCodexNativeHook({
|
|
6387
|
+
hook_event_name: "PreToolUse",
|
|
6388
|
+
cwd,
|
|
6389
|
+
session_id: "sess-subagent-capacity-close-expired",
|
|
6390
|
+
thread_id: "thread-subagent-capacity-close-expired",
|
|
6391
|
+
tool_name: "multi_agent_v1.close_agent",
|
|
6392
|
+
tool_input: { target: "completed-agent" },
|
|
6393
|
+
}, { cwd });
|
|
6394
|
+
assert.equal(result.outputJson, null);
|
|
6395
|
+
}
|
|
6396
|
+
finally {
|
|
6397
|
+
await rm(cwd, { recursive: true, force: true });
|
|
6398
|
+
}
|
|
6399
|
+
});
|
|
5491
6400
|
it("warns on PreToolUse for vague sloppy fallback implementation framing", async () => {
|
|
5492
6401
|
const cwd = await mkdtemp(join(tmpdir(), "omx-native-hook-pretool-slop-warn-"));
|
|
5493
6402
|
try {
|
|
@@ -9261,13 +10170,67 @@ exit 0
|
|
|
9261
10170
|
await rm(cwd, { recursive: true, force: true });
|
|
9262
10171
|
}
|
|
9263
10172
|
});
|
|
9264
|
-
it("does not block
|
|
9265
|
-
const cwd = await mkdtemp(join(tmpdir(), "omx-native-hook-stop-
|
|
10173
|
+
it("does not block Stop after owner-session ralplan CLI completion writes to native canonical state", async () => {
|
|
10174
|
+
const cwd = await mkdtemp(join(tmpdir(), "omx-native-hook-stop-ralplan-owner-alias-complete-"));
|
|
10175
|
+
const previousSessionId = process.env.OMX_SESSION_ID;
|
|
9266
10176
|
try {
|
|
9267
10177
|
const stateDir = join(cwd, ".omx", "state");
|
|
9268
|
-
|
|
9269
|
-
|
|
9270
|
-
await
|
|
10178
|
+
const nativeSessionId = "native-id";
|
|
10179
|
+
const ownerSessionId = "omx-owner-id";
|
|
10180
|
+
await mkdir(join(stateDir, "sessions", nativeSessionId), { recursive: true });
|
|
10181
|
+
await writeJson(join(stateDir, "session.json"), {
|
|
10182
|
+
session_id: nativeSessionId,
|
|
10183
|
+
native_session_id: nativeSessionId,
|
|
10184
|
+
owner_omx_session_id: ownerSessionId,
|
|
10185
|
+
cwd,
|
|
10186
|
+
});
|
|
10187
|
+
await writeJson(join(stateDir, "sessions", nativeSessionId, "skill-active-state.json"), {
|
|
10188
|
+
active: true,
|
|
10189
|
+
skill: "ralplan",
|
|
10190
|
+
phase: "planning",
|
|
10191
|
+
session_id: nativeSessionId,
|
|
10192
|
+
active_skills: [{ skill: "ralplan", phase: "planning", active: true, session_id: nativeSessionId }],
|
|
10193
|
+
});
|
|
10194
|
+
await writeJson(join(stateDir, "sessions", nativeSessionId, "ralplan-state.json"), {
|
|
10195
|
+
active: true,
|
|
10196
|
+
mode: "ralplan",
|
|
10197
|
+
current_phase: "planning",
|
|
10198
|
+
session_id: nativeSessionId,
|
|
10199
|
+
});
|
|
10200
|
+
process.env.OMX_SESSION_ID = ownerSessionId;
|
|
10201
|
+
const writeResult = await executeStateOperation("state_write", {
|
|
10202
|
+
mode: "ralplan",
|
|
10203
|
+
active: false,
|
|
10204
|
+
current_phase: "complete",
|
|
10205
|
+
status: "complete",
|
|
10206
|
+
terminal_state: "complete",
|
|
10207
|
+
workingDirectory: cwd,
|
|
10208
|
+
});
|
|
10209
|
+
assert.notEqual(writeResult.isError, true);
|
|
10210
|
+
assert.equal(existsSync(join(stateDir, "sessions", ownerSessionId, "ralplan-state.json")), false);
|
|
10211
|
+
const result = await dispatchCodexNativeHook({
|
|
10212
|
+
hook_event_name: "Stop",
|
|
10213
|
+
cwd,
|
|
10214
|
+
session_id: nativeSessionId,
|
|
10215
|
+
}, { cwd });
|
|
10216
|
+
assert.equal(result.omxEventName, "stop");
|
|
10217
|
+
assert.equal(result.outputJson, null);
|
|
10218
|
+
}
|
|
10219
|
+
finally {
|
|
10220
|
+
if (typeof previousSessionId === "string")
|
|
10221
|
+
process.env.OMX_SESSION_ID = previousSessionId;
|
|
10222
|
+
else
|
|
10223
|
+
delete process.env.OMX_SESSION_ID;
|
|
10224
|
+
await rm(cwd, { recursive: true, force: true });
|
|
10225
|
+
}
|
|
10226
|
+
});
|
|
10227
|
+
it("does not block on stale ralplan skill-active state when the matching mode state is absent", async () => {
|
|
10228
|
+
const cwd = await mkdtemp(join(tmpdir(), "omx-native-hook-stop-stale-skill-"));
|
|
10229
|
+
try {
|
|
10230
|
+
const stateDir = join(cwd, ".omx", "state");
|
|
10231
|
+
await mkdir(join(stateDir, "sessions", "sess-stop-stale-skill"), { recursive: true });
|
|
10232
|
+
await writeJson(join(stateDir, "session.json"), { session_id: "sess-stop-stale-skill" });
|
|
10233
|
+
await writeJson(join(stateDir, "sessions", "sess-stop-stale-skill", "skill-active-state.json"), {
|
|
9271
10234
|
active: true,
|
|
9272
10235
|
skill: "ralplan",
|
|
9273
10236
|
phase: "planning",
|
|
@@ -9309,6 +10272,7 @@ exit 0
|
|
|
9309
10272
|
mode: "ralplan",
|
|
9310
10273
|
current_phase: "complete",
|
|
9311
10274
|
session_id: sessionId,
|
|
10275
|
+
cwd,
|
|
9312
10276
|
});
|
|
9313
10277
|
await writeJson(join(stateDir, "sessions", sessionId, "skill-active-state.json"), {
|
|
9314
10278
|
active: true,
|
|
@@ -9340,6 +10304,58 @@ exit 0
|
|
|
9340
10304
|
await rm(cwd, { recursive: true, force: true });
|
|
9341
10305
|
}
|
|
9342
10306
|
});
|
|
10307
|
+
it("keeps blocking current session ralplan when canonical root inactive ralplan state lacks project context", async () => {
|
|
10308
|
+
const cwd = await mkdtemp(join(tmpdir(), "omx-native-hook-stop-ralplan-canonical-root-no-cwd-"));
|
|
10309
|
+
try {
|
|
10310
|
+
const stateDir = join(cwd, ".omx", "state");
|
|
10311
|
+
const sessionId = "sess-stop-ralplan-canonical-root-no-cwd";
|
|
10312
|
+
await mkdir(join(stateDir, "sessions", sessionId), { recursive: true });
|
|
10313
|
+
await writeJson(join(stateDir, "session.json"), { session_id: sessionId });
|
|
10314
|
+
await writeJson(join(stateDir, "skill-active-state.json"), {
|
|
10315
|
+
active: false,
|
|
10316
|
+
skill: "ralplan",
|
|
10317
|
+
phase: "complete",
|
|
10318
|
+
session_id: sessionId,
|
|
10319
|
+
active_skills: [],
|
|
10320
|
+
});
|
|
10321
|
+
await writeJson(join(stateDir, "ralplan-state.json"), {
|
|
10322
|
+
active: false,
|
|
10323
|
+
mode: "ralplan",
|
|
10324
|
+
current_phase: "complete",
|
|
10325
|
+
session_id: sessionId,
|
|
10326
|
+
});
|
|
10327
|
+
await writeJson(join(stateDir, "sessions", sessionId, "skill-active-state.json"), {
|
|
10328
|
+
active: true,
|
|
10329
|
+
skill: "ralplan",
|
|
10330
|
+
phase: "planning",
|
|
10331
|
+
session_id: sessionId,
|
|
10332
|
+
active_skills: [{
|
|
10333
|
+
skill: "ralplan",
|
|
10334
|
+
phase: "planning",
|
|
10335
|
+
active: true,
|
|
10336
|
+
session_id: sessionId,
|
|
10337
|
+
}],
|
|
10338
|
+
});
|
|
10339
|
+
await writeJson(join(stateDir, "sessions", sessionId, "ralplan-state.json"), {
|
|
10340
|
+
active: true,
|
|
10341
|
+
mode: "ralplan",
|
|
10342
|
+
current_phase: "planning",
|
|
10343
|
+
session_id: sessionId,
|
|
10344
|
+
cwd,
|
|
10345
|
+
});
|
|
10346
|
+
const result = await dispatchCodexNativeHook({
|
|
10347
|
+
hook_event_name: "Stop",
|
|
10348
|
+
cwd,
|
|
10349
|
+
session_id: sessionId,
|
|
10350
|
+
}, { cwd });
|
|
10351
|
+
assert.equal(result.omxEventName, "stop");
|
|
10352
|
+
assert.equal(result.outputJson?.decision, "block");
|
|
10353
|
+
assert.equal(result.outputJson?.stopReason, "skill_ralplan_planning_continue_artifact");
|
|
10354
|
+
}
|
|
10355
|
+
finally {
|
|
10356
|
+
await rm(cwd, { recursive: true, force: true });
|
|
10357
|
+
}
|
|
10358
|
+
});
|
|
9343
10359
|
it("keeps blocking current session ralplan when root inactive ralplan state belongs to another session", async () => {
|
|
9344
10360
|
const cwd = await mkdtemp(join(tmpdir(), "omx-native-hook-stop-session-ralplan-root-other-session-"));
|
|
9345
10361
|
try {
|
|
@@ -9440,6 +10456,227 @@ exit 0
|
|
|
9440
10456
|
await rm(cwd, { recursive: true, force: true });
|
|
9441
10457
|
}
|
|
9442
10458
|
});
|
|
10459
|
+
it("keeps blocking current session ralplan when unscoped root completion lacks a plan artifact", async () => {
|
|
10460
|
+
const cwd = await mkdtemp(join(tmpdir(), "omx-native-hook-stop-session-ralplan-root-no-plan-artifact-"));
|
|
10461
|
+
try {
|
|
10462
|
+
const stateDir = join(cwd, ".omx", "state");
|
|
10463
|
+
const sessionId = "sess-stop-unscoped-root-no-plan-artifact";
|
|
10464
|
+
await mkdir(join(stateDir, "sessions", sessionId), { recursive: true });
|
|
10465
|
+
await writeJson(join(stateDir, "session.json"), { session_id: sessionId });
|
|
10466
|
+
await writeJson(join(stateDir, "skill-active-state.json"), {
|
|
10467
|
+
active: false,
|
|
10468
|
+
skill: "ralplan",
|
|
10469
|
+
phase: "complete",
|
|
10470
|
+
active_skills: [],
|
|
10471
|
+
});
|
|
10472
|
+
await writeJson(join(stateDir, "ralplan-state.json"), {
|
|
10473
|
+
active: false,
|
|
10474
|
+
mode: "ralplan",
|
|
10475
|
+
current_phase: "complete",
|
|
10476
|
+
planning_complete: true,
|
|
10477
|
+
updated_at: "2026-06-21T08:05:00.000Z",
|
|
10478
|
+
});
|
|
10479
|
+
await writeJson(join(stateDir, "sessions", sessionId, "skill-active-state.json"), {
|
|
10480
|
+
active: true,
|
|
10481
|
+
skill: "ralplan",
|
|
10482
|
+
phase: "planning",
|
|
10483
|
+
session_id: sessionId,
|
|
10484
|
+
active_skills: [{
|
|
10485
|
+
skill: "ralplan",
|
|
10486
|
+
phase: "planning",
|
|
10487
|
+
active: true,
|
|
10488
|
+
session_id: sessionId,
|
|
10489
|
+
}],
|
|
10490
|
+
});
|
|
10491
|
+
await writeJson(join(stateDir, "sessions", sessionId, "ralplan-state.json"), {
|
|
10492
|
+
active: true,
|
|
10493
|
+
mode: "ralplan",
|
|
10494
|
+
current_phase: "planning",
|
|
10495
|
+
session_id: sessionId,
|
|
10496
|
+
updated_at: "2026-06-21T08:00:00.000Z",
|
|
10497
|
+
});
|
|
10498
|
+
const result = await dispatchCodexNativeHook({
|
|
10499
|
+
hook_event_name: "Stop",
|
|
10500
|
+
cwd,
|
|
10501
|
+
session_id: sessionId,
|
|
10502
|
+
}, { cwd });
|
|
10503
|
+
assert.equal(result.omxEventName, "stop");
|
|
10504
|
+
assert.equal(result.outputJson?.decision, "block");
|
|
10505
|
+
assert.equal(result.outputJson?.stopReason, "skill_ralplan_planning_continue_artifact");
|
|
10506
|
+
}
|
|
10507
|
+
finally {
|
|
10508
|
+
await rm(cwd, { recursive: true, force: true });
|
|
10509
|
+
}
|
|
10510
|
+
});
|
|
10511
|
+
it("does not loop when newer unscoped root ralplan complete state shadows stale session planning", async () => {
|
|
10512
|
+
const cwd = await mkdtemp(join(tmpdir(), "omx-native-hook-stop-stale-session-ralplan-newer-root-complete-"));
|
|
10513
|
+
try {
|
|
10514
|
+
const stateDir = join(cwd, ".omx", "state");
|
|
10515
|
+
const sessionId = "sess-stop-stale-session-ralplan-newer-root";
|
|
10516
|
+
await mkdir(join(stateDir, "sessions", sessionId), { recursive: true });
|
|
10517
|
+
await writeJson(join(stateDir, "session.json"), { session_id: sessionId });
|
|
10518
|
+
await writeJson(join(stateDir, "sessions", sessionId, "skill-active-state.json"), {
|
|
10519
|
+
active: true,
|
|
10520
|
+
skill: "ralplan",
|
|
10521
|
+
phase: "planning",
|
|
10522
|
+
session_id: sessionId,
|
|
10523
|
+
active_skills: [{
|
|
10524
|
+
skill: "ralplan",
|
|
10525
|
+
phase: "planning",
|
|
10526
|
+
active: true,
|
|
10527
|
+
session_id: sessionId,
|
|
10528
|
+
}],
|
|
10529
|
+
});
|
|
10530
|
+
await writeJson(join(stateDir, "sessions", sessionId, "ralplan-state.json"), {
|
|
10531
|
+
active: true,
|
|
10532
|
+
mode: "ralplan",
|
|
10533
|
+
current_phase: "planning",
|
|
10534
|
+
session_id: sessionId,
|
|
10535
|
+
updated_at: "2026-06-21T08:00:00.000Z",
|
|
10536
|
+
});
|
|
10537
|
+
await mkdir(join(cwd, ".omx", "plans"), { recursive: true });
|
|
10538
|
+
await writeFile(join(cwd, ".omx", "plans", "prd-issue-2923.md"), "# PRD\n");
|
|
10539
|
+
await writeFile(join(cwd, ".omx", "plans", "test-spec-issue-2923.md"), "# Test spec\n");
|
|
10540
|
+
await writeJson(join(stateDir, "skill-active-state.json"), {
|
|
10541
|
+
active: false,
|
|
10542
|
+
skill: "ralplan",
|
|
10543
|
+
phase: "complete",
|
|
10544
|
+
active_skills: [],
|
|
10545
|
+
});
|
|
10546
|
+
await writeJson(join(stateDir, "ralplan-state.json"), {
|
|
10547
|
+
active: false,
|
|
10548
|
+
mode: "ralplan",
|
|
10549
|
+
current_phase: "complete",
|
|
10550
|
+
cwd,
|
|
10551
|
+
planning_complete: true,
|
|
10552
|
+
latest_plan_path: ".omx/plans/prd-issue-2923.md",
|
|
10553
|
+
updated_at: "2026-06-21T08:05:00.000Z",
|
|
10554
|
+
});
|
|
10555
|
+
const result = await dispatchCodexNativeHook({
|
|
10556
|
+
hook_event_name: "Stop",
|
|
10557
|
+
cwd,
|
|
10558
|
+
session_id: sessionId,
|
|
10559
|
+
tool_name: "apply_patch",
|
|
10560
|
+
}, { cwd });
|
|
10561
|
+
assert.equal(result.omxEventName, "stop");
|
|
10562
|
+
assert.equal(result.outputJson, null);
|
|
10563
|
+
}
|
|
10564
|
+
finally {
|
|
10565
|
+
await rm(cwd, { recursive: true, force: true });
|
|
10566
|
+
}
|
|
10567
|
+
});
|
|
10568
|
+
it("keeps blocking current session ralplan when newer unscoped root completion belongs to another worktree", async () => {
|
|
10569
|
+
const cwd = await mkdtemp(join(tmpdir(), "omx-native-hook-stop-ralplan-cross-worktree-root-complete-"));
|
|
10570
|
+
try {
|
|
10571
|
+
const otherWorktree = join(tmpdir(), "omx-native-hook-other-worktree-2925");
|
|
10572
|
+
const stateDir = join(cwd, ".omx", "state");
|
|
10573
|
+
const sessionId = "sess-stop-ralplan-cross-worktree-root";
|
|
10574
|
+
await mkdir(join(stateDir, "sessions", sessionId), { recursive: true });
|
|
10575
|
+
await writeJson(join(stateDir, "session.json"), { session_id: sessionId });
|
|
10576
|
+
await writeJson(join(stateDir, "sessions", sessionId, "skill-active-state.json"), {
|
|
10577
|
+
active: true,
|
|
10578
|
+
skill: "ralplan",
|
|
10579
|
+
phase: "planning",
|
|
10580
|
+
session_id: sessionId,
|
|
10581
|
+
active_skills: [{
|
|
10582
|
+
skill: "ralplan",
|
|
10583
|
+
phase: "planning",
|
|
10584
|
+
active: true,
|
|
10585
|
+
session_id: sessionId,
|
|
10586
|
+
}],
|
|
10587
|
+
});
|
|
10588
|
+
await writeJson(join(stateDir, "sessions", sessionId, "ralplan-state.json"), {
|
|
10589
|
+
active: true,
|
|
10590
|
+
mode: "ralplan",
|
|
10591
|
+
current_phase: "planning",
|
|
10592
|
+
session_id: sessionId,
|
|
10593
|
+
cwd,
|
|
10594
|
+
updated_at: "2026-06-21T08:00:00.000Z",
|
|
10595
|
+
});
|
|
10596
|
+
await writeJson(join(stateDir, "skill-active-state.json"), {
|
|
10597
|
+
active: false,
|
|
10598
|
+
skill: "ralplan",
|
|
10599
|
+
phase: "complete",
|
|
10600
|
+
active_skills: [],
|
|
10601
|
+
});
|
|
10602
|
+
await writeJson(join(stateDir, "ralplan-state.json"), {
|
|
10603
|
+
active: false,
|
|
10604
|
+
mode: "ralplan",
|
|
10605
|
+
current_phase: "complete",
|
|
10606
|
+
cwd: otherWorktree,
|
|
10607
|
+
planning_complete: true,
|
|
10608
|
+
latest_plan_path: ".omx/plans/prd-issue-2925.md",
|
|
10609
|
+
updated_at: "2026-06-21T08:05:00.000Z",
|
|
10610
|
+
});
|
|
10611
|
+
const result = await dispatchCodexNativeHook({
|
|
10612
|
+
hook_event_name: "Stop",
|
|
10613
|
+
cwd,
|
|
10614
|
+
session_id: sessionId,
|
|
10615
|
+
tool_name: "apply_patch",
|
|
10616
|
+
}, { cwd });
|
|
10617
|
+
assert.equal(result.omxEventName, "stop");
|
|
10618
|
+
assert.equal(result.outputJson?.decision, "block");
|
|
10619
|
+
assert.equal(result.outputJson?.stopReason, "skill_ralplan_planning_continue_artifact");
|
|
10620
|
+
}
|
|
10621
|
+
finally {
|
|
10622
|
+
await rm(cwd, { recursive: true, force: true });
|
|
10623
|
+
}
|
|
10624
|
+
});
|
|
10625
|
+
it("keeps blocking current session ralplan when newer unscoped root completion lacks project context", async () => {
|
|
10626
|
+
const cwd = await mkdtemp(join(tmpdir(), "omx-native-hook-stop-ralplan-root-complete-no-cwd-"));
|
|
10627
|
+
try {
|
|
10628
|
+
const stateDir = join(cwd, ".omx", "state");
|
|
10629
|
+
const sessionId = "sess-stop-ralplan-root-complete-no-cwd";
|
|
10630
|
+
await mkdir(join(stateDir, "sessions", sessionId), { recursive: true });
|
|
10631
|
+
await writeJson(join(stateDir, "session.json"), { session_id: sessionId });
|
|
10632
|
+
await writeJson(join(stateDir, "sessions", sessionId, "skill-active-state.json"), {
|
|
10633
|
+
active: true,
|
|
10634
|
+
skill: "ralplan",
|
|
10635
|
+
phase: "planning",
|
|
10636
|
+
session_id: sessionId,
|
|
10637
|
+
active_skills: [{
|
|
10638
|
+
skill: "ralplan",
|
|
10639
|
+
phase: "planning",
|
|
10640
|
+
active: true,
|
|
10641
|
+
session_id: sessionId,
|
|
10642
|
+
}],
|
|
10643
|
+
});
|
|
10644
|
+
await writeJson(join(stateDir, "sessions", sessionId, "ralplan-state.json"), {
|
|
10645
|
+
active: true,
|
|
10646
|
+
mode: "ralplan",
|
|
10647
|
+
current_phase: "planning",
|
|
10648
|
+
session_id: sessionId,
|
|
10649
|
+
cwd,
|
|
10650
|
+
updated_at: "2026-06-21T08:00:00.000Z",
|
|
10651
|
+
});
|
|
10652
|
+
await writeJson(join(stateDir, "skill-active-state.json"), {
|
|
10653
|
+
active: false,
|
|
10654
|
+
skill: "ralplan",
|
|
10655
|
+
phase: "complete",
|
|
10656
|
+
active_skills: [],
|
|
10657
|
+
});
|
|
10658
|
+
await writeJson(join(stateDir, "ralplan-state.json"), {
|
|
10659
|
+
active: false,
|
|
10660
|
+
mode: "ralplan",
|
|
10661
|
+
current_phase: "complete",
|
|
10662
|
+
planning_complete: true,
|
|
10663
|
+
latest_plan_path: ".omx/plans/prd-issue-2925.md",
|
|
10664
|
+
updated_at: "2026-06-21T08:05:00.000Z",
|
|
10665
|
+
});
|
|
10666
|
+
const result = await dispatchCodexNativeHook({
|
|
10667
|
+
hook_event_name: "Stop",
|
|
10668
|
+
cwd,
|
|
10669
|
+
session_id: sessionId,
|
|
10670
|
+
tool_name: "apply_patch",
|
|
10671
|
+
}, { cwd });
|
|
10672
|
+
assert.equal(result.omxEventName, "stop");
|
|
10673
|
+
assert.equal(result.outputJson?.decision, "block");
|
|
10674
|
+
assert.equal(result.outputJson?.stopReason, "skill_ralplan_planning_continue_artifact");
|
|
10675
|
+
}
|
|
10676
|
+
finally {
|
|
10677
|
+
await rm(cwd, { recursive: true, force: true });
|
|
10678
|
+
}
|
|
10679
|
+
});
|
|
9443
10680
|
it("does not block stale session ralplan when root ralplan is terminal and another root skill is active", async () => {
|
|
9444
10681
|
const cwd = await mkdtemp(join(tmpdir(), "omx-native-hook-stop-stale-ralplan-other-root-skill-"));
|
|
9445
10682
|
try {
|
|
@@ -9464,6 +10701,7 @@ exit 0
|
|
|
9464
10701
|
mode: "ralplan",
|
|
9465
10702
|
current_phase: "complete",
|
|
9466
10703
|
session_id: sessionId,
|
|
10704
|
+
cwd,
|
|
9467
10705
|
});
|
|
9468
10706
|
await writeJson(join(stateDir, "sessions", sessionId, "skill-active-state.json"), {
|
|
9469
10707
|
active: true,
|