oh-my-codex 0.18.6 → 0.18.8
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 +59 -10
- package/crates/omx-sparkshell/tests/execution.rs +1 -1
- package/dist/agents/__tests__/definitions.test.js +11 -0
- package/dist/agents/__tests__/definitions.test.js.map +1 -1
- package/dist/agents/__tests__/native-config.test.js +56 -6
- package/dist/agents/__tests__/native-config.test.js.map +1 -1
- package/dist/agents/definitions.d.ts +10 -0
- package/dist/agents/definitions.d.ts.map +1 -1
- package/dist/agents/definitions.js +5 -1
- package/dist/agents/definitions.js.map +1 -1
- package/dist/agents/native-config.d.ts +5 -1
- package/dist/agents/native-config.d.ts.map +1 -1
- package/dist/agents/native-config.js +19 -4
- package/dist/agents/native-config.js.map +1 -1
- package/dist/autopilot/__tests__/fsm.test.d.ts +2 -0
- package/dist/autopilot/__tests__/fsm.test.d.ts.map +1 -0
- package/dist/autopilot/__tests__/fsm.test.js +75 -0
- package/dist/autopilot/__tests__/fsm.test.js.map +1 -0
- package/dist/autopilot/__tests__/ralplan-gate.test.d.ts +2 -0
- package/dist/autopilot/__tests__/ralplan-gate.test.d.ts.map +1 -0
- package/dist/autopilot/__tests__/ralplan-gate.test.js +79 -0
- package/dist/autopilot/__tests__/ralplan-gate.test.js.map +1 -0
- package/dist/autopilot/deep-interview-gate.d.ts +18 -0
- package/dist/autopilot/deep-interview-gate.d.ts.map +1 -0
- package/dist/autopilot/deep-interview-gate.js +256 -0
- package/dist/autopilot/deep-interview-gate.js.map +1 -0
- package/dist/autopilot/fsm.d.ts +13 -0
- package/dist/autopilot/fsm.d.ts.map +1 -0
- package/dist/autopilot/fsm.js +70 -0
- package/dist/autopilot/fsm.js.map +1 -0
- package/dist/autopilot/ralplan-gate.d.ts +17 -0
- package/dist/autopilot/ralplan-gate.d.ts.map +1 -0
- package/dist/autopilot/ralplan-gate.js +61 -0
- package/dist/autopilot/ralplan-gate.js.map +1 -0
- package/dist/cli/__tests__/codex-plugin-layout.test.js +512 -1
- package/dist/cli/__tests__/codex-plugin-layout.test.js.map +1 -1
- package/dist/cli/__tests__/doctor-warning-copy.test.js +39 -0
- package/dist/cli/__tests__/doctor-warning-copy.test.js.map +1 -1
- package/dist/cli/__tests__/index.test.js +83 -7
- package/dist/cli/__tests__/index.test.js.map +1 -1
- package/dist/cli/__tests__/launch-fallback.test.js +175 -6
- package/dist/cli/__tests__/launch-fallback.test.js.map +1 -1
- package/dist/cli/__tests__/package-bin-contract.test.js +8 -4
- package/dist/cli/__tests__/package-bin-contract.test.js.map +1 -1
- package/dist/cli/__tests__/question.test.js +100 -0
- package/dist/cli/__tests__/question.test.js.map +1 -1
- package/dist/cli/__tests__/ralph-goal-mode-contract.test.js +13 -0
- package/dist/cli/__tests__/ralph-goal-mode-contract.test.js.map +1 -1
- package/dist/cli/__tests__/ralph.test.js +14 -0
- package/dist/cli/__tests__/ralph.test.js.map +1 -1
- package/dist/cli/__tests__/setup-install-mode.test.js +89 -0
- package/dist/cli/__tests__/setup-install-mode.test.js.map +1 -1
- package/dist/cli/__tests__/setup-refresh.test.js +83 -0
- package/dist/cli/__tests__/setup-refresh.test.js.map +1 -1
- package/dist/cli/__tests__/state.test.js +21 -0
- package/dist/cli/__tests__/state.test.js.map +1 -1
- package/dist/cli/__tests__/team.test.js +2 -2
- package/dist/cli/__tests__/team.test.js.map +1 -1
- package/dist/cli/__tests__/update.test.js +110 -2
- package/dist/cli/__tests__/update.test.js.map +1 -1
- package/dist/cli/doctor.d.ts.map +1 -1
- package/dist/cli/doctor.js +8 -1
- package/dist/cli/doctor.js.map +1 -1
- package/dist/cli/index.d.ts +14 -3
- package/dist/cli/index.d.ts.map +1 -1
- package/dist/cli/index.js +298 -50
- package/dist/cli/index.js.map +1 -1
- package/dist/cli/plugin-marketplace.d.ts +14 -2
- package/dist/cli/plugin-marketplace.d.ts.map +1 -1
- package/dist/cli/plugin-marketplace.js +62 -15
- package/dist/cli/plugin-marketplace.js.map +1 -1
- package/dist/cli/question.d.ts.map +1 -1
- package/dist/cli/question.js +36 -5
- package/dist/cli/question.js.map +1 -1
- package/dist/cli/ralph.d.ts.map +1 -1
- package/dist/cli/ralph.js +3 -1
- package/dist/cli/ralph.js.map +1 -1
- package/dist/cli/setup-preferences.d.ts +2 -0
- package/dist/cli/setup-preferences.d.ts.map +1 -1
- package/dist/cli/setup-preferences.js +4 -0
- package/dist/cli/setup-preferences.js.map +1 -1
- package/dist/cli/setup.d.ts +3 -0
- package/dist/cli/setup.d.ts.map +1 -1
- package/dist/cli/setup.js +166 -27
- package/dist/cli/setup.js.map +1 -1
- package/dist/cli/state.d.ts.map +1 -1
- package/dist/cli/state.js +8 -1
- package/dist/cli/state.js.map +1 -1
- package/dist/cli/tmux-hook.d.ts.map +1 -1
- package/dist/cli/tmux-hook.js +16 -0
- package/dist/cli/tmux-hook.js.map +1 -1
- package/dist/cli/update.d.ts +2 -0
- package/dist/cli/update.d.ts.map +1 -1
- package/dist/cli/update.js +47 -3
- package/dist/cli/update.js.map +1 -1
- package/dist/config/__tests__/deep-interview.test.js +7 -6
- package/dist/config/__tests__/deep-interview.test.js.map +1 -1
- package/dist/config/__tests__/generator-notify.test.js +1 -0
- package/dist/config/__tests__/generator-notify.test.js.map +1 -1
- package/dist/config/deep-interview.d.ts.map +1 -1
- package/dist/config/deep-interview.js +14 -4
- package/dist/config/deep-interview.js.map +1 -1
- package/dist/config/generator.d.ts +2 -2
- package/dist/config/generator.d.ts.map +1 -1
- package/dist/config/generator.js +2 -2
- package/dist/config/generator.js.map +1 -1
- package/dist/config/team-mode.d.ts +12 -0
- package/dist/config/team-mode.d.ts.map +1 -0
- package/dist/config/team-mode.js +91 -0
- package/dist/config/team-mode.js.map +1 -0
- package/dist/hooks/__tests__/agents-overlay.test.js +88 -0
- package/dist/hooks/__tests__/agents-overlay.test.js.map +1 -1
- package/dist/hooks/__tests__/autopilot-skill-contract.test.js +8 -0
- package/dist/hooks/__tests__/autopilot-skill-contract.test.js.map +1 -1
- package/dist/hooks/__tests__/code-review-skill-contract.test.js +8 -0
- package/dist/hooks/__tests__/code-review-skill-contract.test.js.map +1 -1
- package/dist/hooks/__tests__/deep-interview-contract.test.js +10 -0
- package/dist/hooks/__tests__/deep-interview-contract.test.js.map +1 -1
- package/dist/hooks/__tests__/keyword-detector.test.js +1072 -14
- package/dist/hooks/__tests__/keyword-detector.test.js.map +1 -1
- package/dist/hooks/__tests__/notify-fallback-watcher.test.js +64 -1
- package/dist/hooks/__tests__/notify-fallback-watcher.test.js.map +1 -1
- package/dist/hooks/__tests__/notify-hook-auto-nudge.test.js +189 -0
- package/dist/hooks/__tests__/notify-hook-auto-nudge.test.js.map +1 -1
- package/dist/hooks/__tests__/notify-hook-team-leader-nudge.test.js +35 -2
- package/dist/hooks/__tests__/notify-hook-team-leader-nudge.test.js.map +1 -1
- package/dist/hooks/__tests__/notify-hook-tmux-heal.test.js +3 -3
- package/dist/hooks/__tests__/notify-hook-tmux-heal.test.js.map +1 -1
- package/dist/hooks/__tests__/session.test.js +25 -0
- package/dist/hooks/__tests__/session.test.js.map +1 -1
- package/dist/hooks/__tests__/skill-guidance-contract.test.js +21 -0
- package/dist/hooks/__tests__/skill-guidance-contract.test.js.map +1 -1
- package/dist/hooks/agents-overlay.d.ts.map +1 -1
- package/dist/hooks/agents-overlay.js +36 -50
- package/dist/hooks/agents-overlay.js.map +1 -1
- package/dist/hooks/deep-interview-config-instruction.js +1 -1
- package/dist/hooks/deep-interview-config-instruction.js.map +1 -1
- package/dist/hooks/extensibility/__tests__/plugin-runner.test.js +31 -0
- package/dist/hooks/extensibility/__tests__/plugin-runner.test.js.map +1 -1
- package/dist/hooks/extensibility/plugin-runner.js +17 -21
- package/dist/hooks/extensibility/plugin-runner.js.map +1 -1
- package/dist/hooks/keyword-detector.d.ts +1 -0
- package/dist/hooks/keyword-detector.d.ts.map +1 -1
- package/dist/hooks/keyword-detector.js +428 -32
- package/dist/hooks/keyword-detector.js.map +1 -1
- package/dist/hooks/keyword-registry.d.ts.map +1 -1
- package/dist/hooks/keyword-registry.js +1 -0
- package/dist/hooks/keyword-registry.js.map +1 -1
- package/dist/hooks/prompt-guidance-contract.d.ts.map +1 -1
- package/dist/hooks/prompt-guidance-contract.js +6 -0
- package/dist/hooks/prompt-guidance-contract.js.map +1 -1
- package/dist/hooks/session.d.ts +3 -0
- package/dist/hooks/session.d.ts.map +1 -1
- package/dist/hooks/session.js +13 -5
- package/dist/hooks/session.js.map +1 -1
- package/dist/hud/__tests__/authority.test.js +469 -31
- package/dist/hud/__tests__/authority.test.js.map +1 -1
- package/dist/hud/__tests__/hud-tmux-injection.test.js +2 -1
- package/dist/hud/__tests__/hud-tmux-injection.test.js.map +1 -1
- package/dist/hud/__tests__/index.test.js +210 -2
- package/dist/hud/__tests__/index.test.js.map +1 -1
- package/dist/hud/__tests__/reconcile.test.js +588 -28
- package/dist/hud/__tests__/reconcile.test.js.map +1 -1
- package/dist/hud/__tests__/render.test.js +61 -0
- package/dist/hud/__tests__/render.test.js.map +1 -1
- package/dist/hud/__tests__/state.test.js +208 -0
- package/dist/hud/__tests__/state.test.js.map +1 -1
- package/dist/hud/__tests__/tmux.test.js +314 -22
- package/dist/hud/__tests__/tmux.test.js.map +1 -1
- package/dist/hud/authority.d.ts +5 -0
- package/dist/hud/authority.d.ts.map +1 -1
- package/dist/hud/authority.js +337 -30
- package/dist/hud/authority.js.map +1 -1
- package/dist/hud/index.d.ts +20 -2
- package/dist/hud/index.d.ts.map +1 -1
- package/dist/hud/index.js +103 -26
- package/dist/hud/index.js.map +1 -1
- package/dist/hud/reconcile.d.ts +3 -3
- package/dist/hud/reconcile.d.ts.map +1 -1
- package/dist/hud/reconcile.js +129 -20
- package/dist/hud/reconcile.js.map +1 -1
- package/dist/hud/render.d.ts.map +1 -1
- package/dist/hud/render.js +35 -0
- package/dist/hud/render.js.map +1 -1
- package/dist/hud/state.d.ts.map +1 -1
- package/dist/hud/state.js +64 -50
- package/dist/hud/state.js.map +1 -1
- package/dist/hud/tmux.d.ts +26 -6
- package/dist/hud/tmux.d.ts.map +1 -1
- package/dist/hud/tmux.js +173 -38
- package/dist/hud/tmux.js.map +1 -1
- package/dist/hud/types.d.ts +11 -0
- package/dist/hud/types.d.ts.map +1 -1
- package/dist/hud/types.js.map +1 -1
- package/dist/mcp/__tests__/hermes-bridge.test.js +203 -7
- package/dist/mcp/__tests__/hermes-bridge.test.js.map +1 -1
- package/dist/mcp/__tests__/state-paths.test.js +71 -1
- package/dist/mcp/__tests__/state-paths.test.js.map +1 -1
- package/dist/mcp/__tests__/state-server.test.js +13 -1
- package/dist/mcp/__tests__/state-server.test.js.map +1 -1
- package/dist/mcp/hermes-bridge.d.ts +12 -2
- package/dist/mcp/hermes-bridge.d.ts.map +1 -1
- package/dist/mcp/hermes-bridge.js +83 -9
- package/dist/mcp/hermes-bridge.js.map +1 -1
- package/dist/mcp/state-paths.d.ts +32 -0
- package/dist/mcp/state-paths.d.ts.map +1 -1
- package/dist/mcp/state-paths.js +113 -17
- package/dist/mcp/state-paths.js.map +1 -1
- package/dist/mcp/state-server.d.ts +4 -4
- package/dist/modes/__tests__/base-autoresearch-contract.test.js +7 -1
- package/dist/modes/__tests__/base-autoresearch-contract.test.js.map +1 -1
- package/dist/pipeline/__tests__/stages.test.js +130 -0
- package/dist/pipeline/__tests__/stages.test.js.map +1 -1
- package/dist/pipeline/orchestrator.js +1 -1
- package/dist/pipeline/orchestrator.js.map +1 -1
- package/dist/pipeline/stages/ralplan.d.ts +1 -0
- package/dist/pipeline/stages/ralplan.d.ts.map +1 -1
- package/dist/pipeline/stages/ralplan.js +14 -5
- package/dist/pipeline/stages/ralplan.js.map +1 -1
- package/dist/question/__tests__/deep-interview.test.js +160 -2
- package/dist/question/__tests__/deep-interview.test.js.map +1 -1
- package/dist/question/__tests__/policy.test.js +63 -3
- package/dist/question/__tests__/policy.test.js.map +1 -1
- package/dist/question/__tests__/renderer.test.js +191 -2
- package/dist/question/__tests__/renderer.test.js.map +1 -1
- package/dist/question/__tests__/state.test.js +94 -3
- package/dist/question/__tests__/state.test.js.map +1 -1
- package/dist/question/__tests__/ui.test.js +4 -0
- package/dist/question/__tests__/ui.test.js.map +1 -1
- package/dist/question/autopilot-wait.d.ts +12 -2
- package/dist/question/autopilot-wait.d.ts.map +1 -1
- package/dist/question/autopilot-wait.js +158 -47
- package/dist/question/autopilot-wait.js.map +1 -1
- package/dist/question/deep-interview.d.ts.map +1 -1
- package/dist/question/deep-interview.js +22 -6
- package/dist/question/deep-interview.js.map +1 -1
- package/dist/question/policy.d.ts.map +1 -1
- package/dist/question/policy.js +2 -5
- package/dist/question/policy.js.map +1 -1
- package/dist/question/renderer.d.ts +12 -0
- package/dist/question/renderer.d.ts.map +1 -1
- package/dist/question/renderer.js +87 -3
- package/dist/question/renderer.js.map +1 -1
- package/dist/question/state.d.ts +8 -1
- package/dist/question/state.d.ts.map +1 -1
- package/dist/question/state.js +54 -14
- package/dist/question/state.js.map +1 -1
- package/dist/question/types.d.ts +1 -1
- package/dist/question/types.d.ts.map +1 -1
- package/dist/question/ui.d.ts +1 -0
- package/dist/question/ui.d.ts.map +1 -1
- package/dist/question/ui.js +1 -0
- package/dist/question/ui.js.map +1 -1
- package/dist/ralplan/__tests__/runtime.test.js +191 -0
- package/dist/ralplan/__tests__/runtime.test.js.map +1 -1
- package/dist/ralplan/consensus-gate.d.ts +9 -1
- package/dist/ralplan/consensus-gate.d.ts.map +1 -1
- package/dist/ralplan/consensus-gate.js +84 -2
- package/dist/ralplan/consensus-gate.js.map +1 -1
- package/dist/ralplan/runtime.d.ts +9 -0
- package/dist/ralplan/runtime.d.ts.map +1 -1
- package/dist/ralplan/runtime.js +32 -11
- package/dist/ralplan/runtime.js.map +1 -1
- package/dist/scripts/__tests__/codex-native-hook.test.js +2315 -280
- package/dist/scripts/__tests__/codex-native-hook.test.js.map +1 -1
- package/dist/scripts/__tests__/notify-state-io.test.js +72 -1
- package/dist/scripts/__tests__/notify-state-io.test.js.map +1 -1
- package/dist/scripts/__tests__/notify-tmux-injection.test.d.ts +2 -0
- package/dist/scripts/__tests__/notify-tmux-injection.test.d.ts.map +1 -0
- package/dist/scripts/__tests__/notify-tmux-injection.test.js +57 -0
- package/dist/scripts/__tests__/notify-tmux-injection.test.js.map +1 -0
- package/dist/scripts/__tests__/run-test-files.test.js +74 -0
- package/dist/scripts/__tests__/run-test-files.test.js.map +1 -1
- package/dist/scripts/__tests__/verify-native-agents.test.js +65 -0
- package/dist/scripts/__tests__/verify-native-agents.test.js.map +1 -1
- package/dist/scripts/codex-native-hook.d.ts.map +1 -1
- package/dist/scripts/codex-native-hook.js +431 -56
- package/dist/scripts/codex-native-hook.js.map +1 -1
- package/dist/scripts/codex-native-pre-post.d.ts.map +1 -1
- package/dist/scripts/codex-native-pre-post.js +79 -1
- package/dist/scripts/codex-native-pre-post.js.map +1 -1
- package/dist/scripts/eval/eval-parity-smoke.js +1 -1
- package/dist/scripts/eval/eval-parity-smoke.js.map +1 -1
- package/dist/scripts/hook-payload-guard.d.ts +9 -0
- package/dist/scripts/hook-payload-guard.d.ts.map +1 -0
- package/dist/scripts/hook-payload-guard.js +111 -0
- package/dist/scripts/hook-payload-guard.js.map +1 -0
- package/dist/scripts/notify-fallback-watcher.js +8 -1
- package/dist/scripts/notify-fallback-watcher.js.map +1 -1
- package/dist/scripts/notify-hook/__tests__/payload-guard.test.d.ts +2 -0
- package/dist/scripts/notify-hook/__tests__/payload-guard.test.d.ts.map +1 -0
- package/dist/scripts/notify-hook/__tests__/payload-guard.test.js +39 -0
- package/dist/scripts/notify-hook/__tests__/payload-guard.test.js.map +1 -0
- package/dist/scripts/notify-hook/auto-nudge.d.ts.map +1 -1
- package/dist/scripts/notify-hook/auto-nudge.js +3 -1
- package/dist/scripts/notify-hook/auto-nudge.js.map +1 -1
- package/dist/scripts/notify-hook/ralph-session-resume.d.ts.map +1 -1
- package/dist/scripts/notify-hook/ralph-session-resume.js +3 -10
- package/dist/scripts/notify-hook/ralph-session-resume.js.map +1 -1
- package/dist/scripts/notify-hook/state-io.d.ts.map +1 -1
- package/dist/scripts/notify-hook/state-io.js +62 -38
- package/dist/scripts/notify-hook/state-io.js.map +1 -1
- package/dist/scripts/notify-hook/team-leader-nudge.d.ts.map +1 -1
- package/dist/scripts/notify-hook/team-leader-nudge.js +7 -0
- package/dist/scripts/notify-hook/team-leader-nudge.js.map +1 -1
- package/dist/scripts/notify-hook/team-worker-stop.d.ts.map +1 -1
- package/dist/scripts/notify-hook/team-worker-stop.js +234 -86
- package/dist/scripts/notify-hook/team-worker-stop.js.map +1 -1
- package/dist/scripts/notify-hook/tmux-injection.d.ts +7 -0
- package/dist/scripts/notify-hook/tmux-injection.d.ts.map +1 -1
- package/dist/scripts/notify-hook/tmux-injection.js +24 -18
- package/dist/scripts/notify-hook/tmux-injection.js.map +1 -1
- package/dist/scripts/notify-hook.js +86 -13
- package/dist/scripts/notify-hook.js.map +1 -1
- package/dist/scripts/run-test-files.js +193 -22
- package/dist/scripts/run-test-files.js.map +1 -1
- package/dist/scripts/sync-plugin-mirror.d.ts.map +1 -1
- package/dist/scripts/sync-plugin-mirror.js +61 -3
- package/dist/scripts/sync-plugin-mirror.js.map +1 -1
- package/dist/scripts/verify-native-agents.d.ts.map +1 -1
- package/dist/scripts/verify-native-agents.js +58 -1
- package/dist/scripts/verify-native-agents.js.map +1 -1
- package/dist/state/__tests__/operations.test.js +1125 -1
- package/dist/state/__tests__/operations.test.js.map +1 -1
- package/dist/state/__tests__/skill-active.test.js +46 -1
- package/dist/state/__tests__/skill-active.test.js.map +1 -1
- package/dist/state/__tests__/workflow-transition.test.js +98 -7
- package/dist/state/__tests__/workflow-transition.test.js.map +1 -1
- package/dist/state/operations.d.ts.map +1 -1
- package/dist/state/operations.js +159 -2
- package/dist/state/operations.js.map +1 -1
- package/dist/state/skill-active.js +6 -8
- package/dist/state/skill-active.js.map +1 -1
- package/dist/state/workflow-transition-reconcile.d.ts +6 -0
- package/dist/state/workflow-transition-reconcile.d.ts.map +1 -1
- package/dist/state/workflow-transition-reconcile.js +38 -15
- package/dist/state/workflow-transition-reconcile.js.map +1 -1
- package/dist/state/workflow-transition.d.ts.map +1 -1
- package/dist/state/workflow-transition.js +10 -3
- package/dist/state/workflow-transition.js.map +1 -1
- package/dist/subagents/__tests__/tracker.test.js +139 -0
- package/dist/subagents/__tests__/tracker.test.js.map +1 -1
- package/dist/subagents/tracker.d.ts +3 -0
- package/dist/subagents/tracker.d.ts.map +1 -1
- package/dist/subagents/tracker.js +41 -4
- package/dist/subagents/tracker.js.map +1 -1
- package/dist/team/__tests__/coordination-protocol.test.d.ts +2 -0
- package/dist/team/__tests__/coordination-protocol.test.d.ts.map +1 -0
- package/dist/team/__tests__/coordination-protocol.test.js +173 -0
- package/dist/team/__tests__/coordination-protocol.test.js.map +1 -0
- package/dist/team/__tests__/runtime.test.js +52 -3
- package/dist/team/__tests__/runtime.test.js.map +1 -1
- package/dist/team/__tests__/scaling.test.js +9 -4
- package/dist/team/__tests__/scaling.test.js.map +1 -1
- package/dist/team/__tests__/state.test.js +83 -0
- package/dist/team/__tests__/state.test.js.map +1 -1
- package/dist/team/__tests__/tmux-session.test.js +240 -2
- package/dist/team/__tests__/tmux-session.test.js.map +1 -1
- package/dist/team/__tests__/worker-bootstrap.test.js +84 -0
- package/dist/team/__tests__/worker-bootstrap.test.js.map +1 -1
- package/dist/team/__tests__/worker-runtime-identity.test.js +4 -2
- package/dist/team/__tests__/worker-runtime-identity.test.js.map +1 -1
- package/dist/team/coordination-protocol.d.ts +14 -0
- package/dist/team/coordination-protocol.d.ts.map +1 -0
- package/dist/team/coordination-protocol.js +244 -0
- package/dist/team/coordination-protocol.js.map +1 -0
- package/dist/team/runtime.d.ts +1 -0
- package/dist/team/runtime.d.ts.map +1 -1
- package/dist/team/runtime.js +19 -3
- package/dist/team/runtime.js.map +1 -1
- package/dist/team/scaling.d.ts.map +1 -1
- package/dist/team/scaling.js +3 -2
- package/dist/team/scaling.js.map +1 -1
- package/dist/team/state/tasks.d.ts.map +1 -1
- package/dist/team/state/tasks.js +24 -0
- package/dist/team/state/tasks.js.map +1 -1
- package/dist/team/state/types.d.ts +21 -1
- 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 +17 -1
- package/dist/team/state.d.ts.map +1 -1
- package/dist/team/state.js +12 -5
- package/dist/team/state.js.map +1 -1
- package/dist/team/team-ops.d.ts +1 -1
- package/dist/team/team-ops.d.ts.map +1 -1
- package/dist/team/team-ops.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 +161 -13
- 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 +63 -0
- package/dist/team/worker-bootstrap.js.map +1 -1
- package/dist/utils/__tests__/agents-model-table.test.js +4 -2
- package/dist/utils/__tests__/agents-model-table.test.js.map +1 -1
- package/dist/utils/agents-model-table.d.ts.map +1 -1
- package/dist/utils/agents-model-table.js +3 -0
- package/dist/utils/agents-model-table.js.map +1 -1
- package/dist/verification/__tests__/ci-rust-gates.test.js +81 -1
- package/dist/verification/__tests__/ci-rust-gates.test.js.map +1 -1
- package/package.json +8 -8
- package/plugins/oh-my-codex/.codex-plugin/plugin.json +1 -1
- package/plugins/oh-my-codex/hooks/codex-native-hook.mjs +334 -21
- package/plugins/oh-my-codex/hooks/hooks.json +1 -2
- package/plugins/oh-my-codex/skills/autopilot/SKILL.md +13 -6
- package/plugins/oh-my-codex/skills/code-review/SKILL.md +7 -7
- package/plugins/oh-my-codex/skills/deep-interview/SKILL.md +9 -4
- package/plugins/oh-my-codex/skills/ralph/SKILL.md +22 -22
- package/plugins/oh-my-codex/skills/ralplan/SKILL.md +12 -0
- package/plugins/oh-my-codex/skills/team/SKILL.md +16 -0
- package/plugins/oh-my-codex/skills/ultraqa/SKILL.md +9 -0
- package/plugins/oh-my-codex/skills/worker/SKILL.md +14 -0
- package/skills/autopilot/SKILL.md +13 -6
- package/skills/code-review/SKILL.md +7 -7
- package/skills/deep-interview/SKILL.md +9 -4
- package/skills/ralph/SKILL.md +22 -22
- package/skills/ralplan/SKILL.md +12 -0
- package/skills/team/SKILL.md +16 -0
- package/skills/ultraqa/SKILL.md +9 -0
- package/skills/worker/SKILL.md +14 -0
- package/src/scripts/__tests__/codex-native-hook.test.ts +4435 -2083
- package/src/scripts/__tests__/notify-state-io.test.ts +95 -0
- package/src/scripts/__tests__/notify-tmux-injection.test.ts +82 -0
- package/src/scripts/__tests__/run-test-files.test.ts +102 -0
- package/src/scripts/__tests__/verify-native-agents.test.ts +75 -0
- package/src/scripts/codex-native-hook.ts +536 -51
- package/src/scripts/codex-native-pre-post.ts +80 -0
- package/src/scripts/demo-team-e2e.sh +10 -7
- package/src/scripts/eval/eval-parity-smoke.ts +1 -1
- package/src/scripts/hook-payload-guard.ts +113 -0
- package/src/scripts/notify-fallback-watcher.ts +8 -1
- package/src/scripts/notify-hook/__tests__/payload-guard.test.ts +41 -0
- package/src/scripts/notify-hook/auto-nudge.ts +3 -1
- package/src/scripts/notify-hook/ralph-session-resume.ts +2 -8
- package/src/scripts/notify-hook/state-io.ts +75 -37
- package/src/scripts/notify-hook/team-leader-nudge.ts +7 -0
- package/src/scripts/notify-hook/team-worker-stop.ts +193 -52
- package/src/scripts/notify-hook/tmux-injection.ts +35 -19
- package/src/scripts/notify-hook.ts +105 -6
- package/src/scripts/run-test-files.ts +192 -22
- package/src/scripts/sync-plugin-mirror.ts +98 -9
- package/src/scripts/verify-native-agents.ts +65 -1
|
@@ -4,6 +4,7 @@ import { appendFile, mkdir, readFile, readdir, stat, writeFile } from "fs/promis
|
|
|
4
4
|
import { extname, join, relative, resolve } from "path";
|
|
5
5
|
import { pathToFileURL } from "url";
|
|
6
6
|
import { readModeStateForActiveDecision, readModeStateForSession, updateModeState } from "../modes/base.js";
|
|
7
|
+
import { redactAuthSecrets } from "../auth/redact.js";
|
|
7
8
|
import {
|
|
8
9
|
SKILL_ACTIVE_STATE_FILE,
|
|
9
10
|
extractSessionIdFromInitializedStatePath,
|
|
@@ -14,6 +15,7 @@ import {
|
|
|
14
15
|
type SkillActiveStateLike,
|
|
15
16
|
} from "../state/skill-active.js";
|
|
16
17
|
import {
|
|
18
|
+
isTrustedSubagentThread,
|
|
17
19
|
readSubagentSessionSummary,
|
|
18
20
|
readSubagentTrackingState,
|
|
19
21
|
recordSubagentTurnForSession,
|
|
@@ -46,6 +48,7 @@ import {
|
|
|
46
48
|
type SkillActiveState,
|
|
47
49
|
} from "../hooks/keyword-detector.js";
|
|
48
50
|
import { buildDeepInterviewConfigInstruction } from "../hooks/deep-interview-config-instruction.js";
|
|
51
|
+
import { readTeamModeConfig } from "../config/team-mode.js";
|
|
49
52
|
import {
|
|
50
53
|
detectNativeStopStallPattern,
|
|
51
54
|
loadAutoNudgeConfig,
|
|
@@ -108,6 +111,10 @@ import {
|
|
|
108
111
|
isFinalHandoffDocumentRefreshCandidate,
|
|
109
112
|
} from "../document-refresh/enforcer.js";
|
|
110
113
|
import { buildExecFollowupStopOutput } from "../exec/followup.js";
|
|
114
|
+
import {
|
|
115
|
+
MAX_NATIVE_STDIN_JSON_BYTES,
|
|
116
|
+
extractRawCodexHookEventName,
|
|
117
|
+
} from "./hook-payload-guard.js";
|
|
111
118
|
|
|
112
119
|
type CodexHookEventName =
|
|
113
120
|
| "SessionStart"
|
|
@@ -142,6 +149,7 @@ const ORDINARY_STOP_NO_PROGRESS_DEFAULT_MAX_REPEATS = 8;
|
|
|
142
149
|
const RALPH_ORPHANED_STARTING_STALE_MS = 15 * 60_000;
|
|
143
150
|
const ORDINARY_STOP_NO_PROGRESS_DEFAULT_IDLE_MS = 10 * 60_000;
|
|
144
151
|
const ORDINARY_STOP_NO_PROGRESS_MAX_MESSAGE_LENGTH = 240;
|
|
152
|
+
const OMX_OWNER_SESSION_ID_PATTERN = /^omx-[A-Za-z0-9_-]{1,60}$/;
|
|
145
153
|
const STABLE_FINAL_RECOMMENDATION_PATTERNS = [
|
|
146
154
|
/^\s*(?:launch|release|ship)-?ready\s*:\s*(?:yes|no)\b[^\n\r]*/im,
|
|
147
155
|
/^\s*ready to release\s*:\s*(?:yes|no)\b[^\n\r]*/im,
|
|
@@ -172,6 +180,35 @@ function safeObject(value: unknown): Record<string, unknown> {
|
|
|
172
180
|
return value && typeof value === "object" ? value as Record<string, unknown> : {};
|
|
173
181
|
}
|
|
174
182
|
|
|
183
|
+
function resolveHudReconcileSessionId(
|
|
184
|
+
currentSessionState: SessionState | null,
|
|
185
|
+
canonicalSessionId: string | null,
|
|
186
|
+
sessionIdForState: string | null,
|
|
187
|
+
): string | undefined {
|
|
188
|
+
const ownerOmxSessionId = safeString(currentSessionState?.owner_omx_session_id).trim();
|
|
189
|
+
if (OMX_OWNER_SESSION_ID_PATTERN.test(ownerOmxSessionId)) return ownerOmxSessionId;
|
|
190
|
+
return canonicalSessionId || sessionIdForState || undefined;
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
function resolveHudReconcileSessionIds(
|
|
194
|
+
currentSessionState: SessionState | null,
|
|
195
|
+
canonicalSessionId: string | null,
|
|
196
|
+
sessionIdForState: string | null,
|
|
197
|
+
nativeSessionId: string | null,
|
|
198
|
+
): string[] {
|
|
199
|
+
const ownerOmxSessionId = safeString(currentSessionState?.owner_omx_session_id).trim();
|
|
200
|
+
return uniqueNonEmpty([
|
|
201
|
+
resolveHudReconcileSessionId(currentSessionState, canonicalSessionId, sessionIdForState),
|
|
202
|
+
canonicalSessionId ?? undefined,
|
|
203
|
+
sessionIdForState ?? undefined,
|
|
204
|
+
nativeSessionId ?? undefined,
|
|
205
|
+
safeString(currentSessionState?.session_id),
|
|
206
|
+
safeString(currentSessionState?.native_session_id),
|
|
207
|
+
OMX_OWNER_SESSION_ID_PATTERN.test(ownerOmxSessionId) ? ownerOmxSessionId : undefined,
|
|
208
|
+
safeString(currentSessionState?.owner_codex_session_id),
|
|
209
|
+
]);
|
|
210
|
+
}
|
|
211
|
+
|
|
175
212
|
function safeContextSnippet(value: unknown, maxLength = 300): string {
|
|
176
213
|
const text = safeString(value).replace(/\s+/g, " ").trim();
|
|
177
214
|
if (text.length <= maxLength) return text;
|
|
@@ -248,18 +285,25 @@ async function recordNativeSubagentSessionStart(
|
|
|
248
285
|
metadata: NativeSubagentSessionStartMetadata,
|
|
249
286
|
transcriptPath: string,
|
|
250
287
|
): Promise<void> {
|
|
288
|
+
const parentThreadId = metadata.parentThreadId.trim();
|
|
289
|
+
const childThreadId = childSessionId.trim();
|
|
251
290
|
const trackingSessionIds = [...new Set([
|
|
252
291
|
canonicalSessionId.trim(),
|
|
253
|
-
|
|
292
|
+
parentThreadId,
|
|
254
293
|
].filter(Boolean))];
|
|
255
294
|
for (const sessionId of trackingSessionIds) {
|
|
295
|
+
if (parentThreadId && parentThreadId !== childThreadId) {
|
|
296
|
+
await recordSubagentTurnForSession(cwd, {
|
|
297
|
+
sessionId,
|
|
298
|
+
threadId: parentThreadId,
|
|
299
|
+
kind: 'leader',
|
|
300
|
+
}).catch(() => {});
|
|
301
|
+
}
|
|
256
302
|
await recordSubagentTurnForSession(cwd, {
|
|
257
303
|
sessionId,
|
|
258
|
-
threadId:
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
sessionId,
|
|
262
|
-
threadId: childSessionId,
|
|
304
|
+
threadId: childThreadId,
|
|
305
|
+
kind: 'subagent',
|
|
306
|
+
...(parentThreadId && parentThreadId !== childThreadId ? { leaderThreadId: parentThreadId } : {}),
|
|
263
307
|
mode: metadata.agentRole,
|
|
264
308
|
}).catch(() => {});
|
|
265
309
|
}
|
|
@@ -301,17 +345,54 @@ async function isNativeSubagentHook(
|
|
|
301
345
|
canonicalSessionId: string,
|
|
302
346
|
nativeSessionId: string,
|
|
303
347
|
threadId: string,
|
|
348
|
+
canonicalLeaderNativeSessionId = "",
|
|
304
349
|
): Promise<boolean> {
|
|
305
|
-
const
|
|
350
|
+
const nativeId = nativeSessionId.trim();
|
|
351
|
+
const promptThreadId = threadId.trim();
|
|
352
|
+
const candidateIds = [nativeId, promptThreadId]
|
|
306
353
|
.map((value) => value.trim())
|
|
307
354
|
.filter(Boolean);
|
|
308
355
|
if (candidateIds.length === 0) return false;
|
|
309
356
|
|
|
310
357
|
const sessionId = canonicalSessionId.trim();
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
358
|
+
const currentLeaderNativeSessionId = canonicalLeaderNativeSessionId.trim();
|
|
359
|
+
const summary = sessionId
|
|
360
|
+
? await readSubagentSessionSummary(cwd, sessionId).catch(() => null)
|
|
361
|
+
: null;
|
|
362
|
+
const currentLeaderIds = new Set([
|
|
363
|
+
currentLeaderNativeSessionId,
|
|
364
|
+
summary?.leaderThreadId?.trim(),
|
|
365
|
+
].filter(Boolean));
|
|
366
|
+
if (
|
|
367
|
+
summary
|
|
368
|
+
&& candidateIds.some((id) => !currentLeaderIds.has(id) && summary.allSubagentThreadIds.includes(id))
|
|
369
|
+
) {
|
|
370
|
+
return true;
|
|
371
|
+
}
|
|
372
|
+
// Native UserPromptSubmit can carry a per-turn thread_id that differs from
|
|
373
|
+
// the long-lived native session id. Treat the current canonical native
|
|
374
|
+
// session as the leader before consulting stale/global tracker state.
|
|
375
|
+
if (
|
|
376
|
+
sessionId
|
|
377
|
+
&& currentLeaderNativeSessionId
|
|
378
|
+
&& (
|
|
379
|
+
nativeId === currentLeaderNativeSessionId
|
|
380
|
+
|| (!nativeId && promptThreadId === currentLeaderNativeSessionId)
|
|
381
|
+
)
|
|
382
|
+
) {
|
|
383
|
+
return false;
|
|
384
|
+
}
|
|
385
|
+
|
|
386
|
+
if (summary) {
|
|
387
|
+
const leaderThreadId = summary.leaderThreadId?.trim();
|
|
388
|
+
if (
|
|
389
|
+
leaderThreadId
|
|
390
|
+
&& (
|
|
391
|
+
nativeId === leaderThreadId
|
|
392
|
+
|| (!nativeId && promptThreadId === leaderThreadId)
|
|
393
|
+
)
|
|
394
|
+
) {
|
|
395
|
+
return false;
|
|
315
396
|
}
|
|
316
397
|
}
|
|
317
398
|
|
|
@@ -326,7 +407,7 @@ async function isNativeSubagentHook(
|
|
|
326
407
|
if (!trackingState) return false;
|
|
327
408
|
|
|
328
409
|
return Object.values(trackingState.sessions).some((session) => (
|
|
329
|
-
candidateIds.some((id) => session
|
|
410
|
+
candidateIds.some((id) => isTrustedSubagentThread(session, id))
|
|
330
411
|
));
|
|
331
412
|
}
|
|
332
413
|
|
|
@@ -1669,6 +1750,7 @@ function buildNativeOutsideTmuxTeamPromptBlockState(
|
|
|
1669
1750
|
threadId?: string,
|
|
1670
1751
|
turnId?: string,
|
|
1671
1752
|
): SkillActiveState | null {
|
|
1753
|
+
if (!readTeamModeConfig(cwd).enabled) return null;
|
|
1672
1754
|
const match = detectPrimaryKeyword(prompt);
|
|
1673
1755
|
if (match?.skill !== "team") return null;
|
|
1674
1756
|
|
|
@@ -1702,15 +1784,32 @@ function buildSkillStateCliInstruction(mode: string, statePath: string): string
|
|
|
1702
1784
|
return `skill: ${mode} activated and initial state initialized at ${statePath}; use CLI-first state updates via \`omx state write/read/clear --input '<json>' --json\`; use omx_state MCP only when explicit MCP compatibility is enabled.`;
|
|
1703
1785
|
}
|
|
1704
1786
|
|
|
1705
|
-
function buildAutopilotPromptActivationNote(
|
|
1787
|
+
function buildAutopilotPromptActivationNote(
|
|
1788
|
+
skillState?: SkillActiveState | null,
|
|
1789
|
+
options: { markedQuestionAnswer?: boolean; cwd?: string } = {},
|
|
1790
|
+
): string | null {
|
|
1706
1791
|
if (skillState?.initialized_mode !== "autopilot") return null;
|
|
1792
|
+
const teamHandoff = readTeamModeConfig(options.cwd).enabled
|
|
1793
|
+
? " (+ $team if needed)"
|
|
1794
|
+
: "";
|
|
1707
1795
|
return [
|
|
1708
|
-
|
|
1796
|
+
`Autopilot protocol: the durable default chain is $deep-interview -> $ralplan -> $ultragoal${teamHandoff} -> $code-review -> $ultraqa (deep-interview -> ralplan -> ultragoal -> code-review -> ultraqa).`,
|
|
1709
1797
|
"Start/resume at current_phase=deep-interview unless the task is clear and bounded; if deep-interview is intentionally skipped, persist and state an explicit deep_interview_gate.skip_reason before moving to ralplan.",
|
|
1798
|
+
"Deep-interview is a structured question chain, not a one-question gate: after an omx question answer, re-score ambiguity against the active threshold, treat max_rounds as a cap, and crystallize once ambiguity is at or below threshold and readiness gates pass.",
|
|
1799
|
+
options.markedQuestionAnswer
|
|
1800
|
+
? "This turn is a marked omx question answer. Treat ordinary selected option/freeform answer text as interview input, then re-score. Do not close merely because the first question was answered; if ambiguity is at or below threshold and readiness gates pass, write interview_complete evidence and hand off. Ask another deep-interview follow-up only when a readiness gate remains unresolved and the answer would materially change execution."
|
|
1801
|
+
: null,
|
|
1802
|
+
"Do not advance from deep-interview to ralplan merely because the first question was answered; persist explicit interview_complete evidence before setting current_phase=ralplan, and do advance when threshold plus readiness gates are satisfied.",
|
|
1710
1803
|
"The ralplan phase is not complete until Planner output has been reviewed sequentially by Architect and then Critic; do not hand off to Ultragoal or implementation until the ralplan state/artifact records both ralplan_architect_review and ralplan_critic_review with approval or an explicit blocker.",
|
|
1711
1804
|
"Do not silently fall back to ordinary $plan/ralplan-only handling; keep autopilot-state.json, skill-active-state.json, HUD/statusline, and Codex goal-mode handoff guidance visible while the workflow is active.",
|
|
1712
1805
|
"When Codex goal tools are available, call get_goal/create_goal only from the active thread handoff and treat the active goal as the completion contract until code-review and ultraqa are clean.",
|
|
1713
|
-
].join(" ");
|
|
1806
|
+
].filter(Boolean).join(" ");
|
|
1807
|
+
}
|
|
1808
|
+
|
|
1809
|
+
function formatExecutionHandoffList(cwd: string): string {
|
|
1810
|
+
return readTeamModeConfig(cwd).enabled
|
|
1811
|
+
? "`$ultragoal`, `$team`, or `$ralph`"
|
|
1812
|
+
: "`$ultragoal` or `$ralph`";
|
|
1714
1813
|
}
|
|
1715
1814
|
|
|
1716
1815
|
function buildAdditionalContextMessage(
|
|
@@ -1721,8 +1820,9 @@ function buildAdditionalContextMessage(
|
|
|
1721
1820
|
): string | null {
|
|
1722
1821
|
if (!prompt) return null;
|
|
1723
1822
|
const promptPriorityMessage = buildPromptPriorityMessage(prompt);
|
|
1724
|
-
const
|
|
1725
|
-
const
|
|
1823
|
+
const teamMode = readTeamModeConfig(cwd);
|
|
1824
|
+
const matches = detectKeywords(prompt).filter((entry) => teamMode.enabled || entry.skill !== "team");
|
|
1825
|
+
const match = matches[0] ?? null;
|
|
1726
1826
|
if (!match) {
|
|
1727
1827
|
const continuedSkill = safeString(skillState?.skill).trim();
|
|
1728
1828
|
if (!continuedSkill) return promptPriorityMessage;
|
|
@@ -1730,6 +1830,8 @@ function buildAdditionalContextMessage(
|
|
|
1730
1830
|
? buildDeepInterviewQuestionBridgeInstruction(cwd, payload)
|
|
1731
1831
|
: null;
|
|
1732
1832
|
const deepInterviewConfigPromptActivationNote = buildDeepInterviewConfigInstruction(cwd, skillState);
|
|
1833
|
+
const markedQuestionAnswer = /^\s*\[omx question answered\]/i.test(prompt);
|
|
1834
|
+
const autopilotPromptActivationNote = buildAutopilotPromptActivationNote(skillState, { markedQuestionAnswer, cwd });
|
|
1733
1835
|
return [
|
|
1734
1836
|
`OMX native UserPromptSubmit continued active workflow skill "${continuedSkill}".`,
|
|
1735
1837
|
promptPriorityMessage,
|
|
@@ -1738,12 +1840,36 @@ function buildAdditionalContextMessage(
|
|
|
1738
1840
|
: null,
|
|
1739
1841
|
deepInterviewPromptActivationNote,
|
|
1740
1842
|
deepInterviewConfigPromptActivationNote,
|
|
1843
|
+
autopilotPromptActivationNote,
|
|
1741
1844
|
"Follow AGENTS.md routing and preserve workflow transition and planning-safety rules.",
|
|
1742
1845
|
].filter(Boolean).join(" ");
|
|
1743
1846
|
}
|
|
1744
1847
|
const detectedKeywordMessage = matches.length > 1
|
|
1745
1848
|
? `OMX native UserPromptSubmit detected workflow keywords ${matches.map((entry) => `"${entry.keyword}" -> ${entry.skill}`).join(", ")}.`
|
|
1746
1849
|
: `OMX native UserPromptSubmit detected workflow keyword "${match.keyword}" -> ${match.skill}.`;
|
|
1850
|
+
const continuedSkill = safeString(skillState?.skill).trim();
|
|
1851
|
+
if (
|
|
1852
|
+
continuedSkill
|
|
1853
|
+
&& continuedSkill !== match.skill
|
|
1854
|
+
&& /^\s*\[omx question answered\]/i.test(prompt)
|
|
1855
|
+
) {
|
|
1856
|
+
const deepInterviewPromptActivationNote = skillState?.initialized_mode === "deep-interview"
|
|
1857
|
+
? buildDeepInterviewQuestionBridgeInstruction(cwd, payload)
|
|
1858
|
+
: null;
|
|
1859
|
+
const deepInterviewConfigPromptActivationNote = buildDeepInterviewConfigInstruction(cwd, skillState);
|
|
1860
|
+
const autopilotPromptActivationNote = buildAutopilotPromptActivationNote(skillState, { markedQuestionAnswer: true, cwd });
|
|
1861
|
+
return [
|
|
1862
|
+
`OMX native UserPromptSubmit continued active workflow skill "${continuedSkill}"; workflow-like tokens inside the marked omx question answer are treated as answer text, not a new workflow activation.`,
|
|
1863
|
+
promptPriorityMessage,
|
|
1864
|
+
skillState?.initialized_mode && skillState.initialized_state_path
|
|
1865
|
+
? buildSkillStateCliInstruction(skillState.initialized_mode, skillState.initialized_state_path)
|
|
1866
|
+
: null,
|
|
1867
|
+
deepInterviewPromptActivationNote,
|
|
1868
|
+
deepInterviewConfigPromptActivationNote,
|
|
1869
|
+
autopilotPromptActivationNote,
|
|
1870
|
+
"Follow AGENTS.md routing and preserve workflow transition and planning-safety rules.",
|
|
1871
|
+
].filter(Boolean).join(" ");
|
|
1872
|
+
}
|
|
1747
1873
|
const activeSkills = Array.isArray(skillState?.active_skills)
|
|
1748
1874
|
? skillState.active_skills.map((entry) => entry.skill)
|
|
1749
1875
|
: [];
|
|
@@ -1764,7 +1890,7 @@ function buildAdditionalContextMessage(
|
|
|
1764
1890
|
const ultragoalPromptActivationNote = match.skill === "ultragoal"
|
|
1765
1891
|
? "Ultragoal protocol: use `omx ultragoal create-goals` / `complete-goals` / `checkpoint` for `.omx/ultragoal` artifacts, then use Codex goal model tools only from the active agent handoff (`get_goal`, `create_goal`, `update_goal`) and never overwrite a different active Codex goal. Ultragoal does not call `/goal clear`; for multiple sequential ultragoal runs in one Codex session/thread, manually clear the completed Codex goal in the UI before creating the next aggregate goal."
|
|
1766
1892
|
: null;
|
|
1767
|
-
const autopilotPromptActivationNote = buildAutopilotPromptActivationNote(skillState);
|
|
1893
|
+
const autopilotPromptActivationNote = buildAutopilotPromptActivationNote(skillState, { cwd });
|
|
1768
1894
|
const combinedTransitionMessage = (() => {
|
|
1769
1895
|
if (!skillState?.transition_message) return null;
|
|
1770
1896
|
if (matches.length <= 1 || activeSkills.length <= 1) return skillState.transition_message;
|
|
@@ -2369,8 +2495,11 @@ function readPayloadTurnId(payload: CodexHookPayload): string {
|
|
|
2369
2495
|
async function resolveInternalSessionIdForPayload(
|
|
2370
2496
|
cwd: string,
|
|
2371
2497
|
payloadSessionId: string,
|
|
2498
|
+
stateDir?: string,
|
|
2372
2499
|
): Promise<string> {
|
|
2373
|
-
const currentSession =
|
|
2500
|
+
const currentSession = stateDir
|
|
2501
|
+
? await readUsableSessionStateFromStateDir(cwd, stateDir)
|
|
2502
|
+
: await readUsableSessionState(cwd);
|
|
2374
2503
|
const canonicalSessionId = safeString(currentSession?.session_id).trim();
|
|
2375
2504
|
if (!canonicalSessionId) return payloadSessionId;
|
|
2376
2505
|
|
|
@@ -2381,6 +2510,22 @@ async function resolveInternalSessionIdForPayload(
|
|
|
2381
2510
|
return payloadSessionId;
|
|
2382
2511
|
}
|
|
2383
2512
|
|
|
2513
|
+
async function readUsableSessionStateFromStateDir(
|
|
2514
|
+
cwd: string,
|
|
2515
|
+
stateDir: string,
|
|
2516
|
+
): Promise<SessionState | null> {
|
|
2517
|
+
const sessionPath = join(stateDir, "session.json");
|
|
2518
|
+
if (!existsSync(sessionPath)) return null;
|
|
2519
|
+
|
|
2520
|
+
try {
|
|
2521
|
+
const content = await readFile(sessionPath, "utf-8");
|
|
2522
|
+
const state = JSON.parse(content) as SessionState;
|
|
2523
|
+
return isSessionStateUsable(state, cwd) ? state : null;
|
|
2524
|
+
} catch {
|
|
2525
|
+
return null;
|
|
2526
|
+
}
|
|
2527
|
+
}
|
|
2528
|
+
|
|
2384
2529
|
async function readStopSessionPinnedState(
|
|
2385
2530
|
fileName: string,
|
|
2386
2531
|
cwd: string,
|
|
@@ -2400,14 +2545,35 @@ const DEEP_INTERVIEW_ALLOWED_WRITE_PREFIXES = [
|
|
|
2400
2545
|
".omx/state",
|
|
2401
2546
|
] as const;
|
|
2402
2547
|
|
|
2403
|
-
const
|
|
2548
|
+
const RALPLAN_ALLOWED_WRITE_PREFIXES = [
|
|
2549
|
+
".omx/context",
|
|
2550
|
+
".omx/plans",
|
|
2551
|
+
".omx/specs",
|
|
2552
|
+
".omx/state",
|
|
2553
|
+
] as const;
|
|
2554
|
+
|
|
2555
|
+
const PLANNING_MODE_IMPLEMENTATION_TOOL_NAMES = new Set([
|
|
2404
2556
|
"Write",
|
|
2405
2557
|
"Edit",
|
|
2406
2558
|
"MultiEdit",
|
|
2559
|
+
"NotebookEdit",
|
|
2407
2560
|
"apply_patch",
|
|
2408
2561
|
"ApplyPatch",
|
|
2409
2562
|
]);
|
|
2410
2563
|
|
|
2564
|
+
const DEEP_INTERVIEW_IMPLEMENTATION_TOOL_NAMES = PLANNING_MODE_IMPLEMENTATION_TOOL_NAMES;
|
|
2565
|
+
|
|
2566
|
+
const RALPLAN_EXECUTION_HANDOFF_SKILLS = new Set([
|
|
2567
|
+
// Autopilot is intentionally excluded: it supervises planning phases such as
|
|
2568
|
+
// ralplan/replan and is not by itself an execution authorization.
|
|
2569
|
+
"autoresearch",
|
|
2570
|
+
"ralph",
|
|
2571
|
+
"team",
|
|
2572
|
+
"ultragoal",
|
|
2573
|
+
"ultrawork",
|
|
2574
|
+
"ultraqa",
|
|
2575
|
+
]);
|
|
2576
|
+
|
|
2411
2577
|
function isActiveDeepInterviewPhase(state: Record<string, unknown> | null): boolean {
|
|
2412
2578
|
if (!state || state.active !== true) return false;
|
|
2413
2579
|
const mode = safeString(state.mode).trim();
|
|
@@ -2417,7 +2583,39 @@ function isActiveDeepInterviewPhase(state: Record<string, unknown> | null): bool
|
|
|
2417
2583
|
return true;
|
|
2418
2584
|
}
|
|
2419
2585
|
|
|
2420
|
-
function
|
|
2586
|
+
function isActiveRalplanPhase(state: Record<string, unknown> | null): boolean {
|
|
2587
|
+
if (!state || state.active !== true) return false;
|
|
2588
|
+
const mode = safeString(state.mode).trim();
|
|
2589
|
+
if (mode && mode !== "ralplan") return false;
|
|
2590
|
+
const phase = safeString(state.current_phase ?? state.currentPhase).trim().toLowerCase();
|
|
2591
|
+
if (phase && (TERMINAL_MODE_PHASES.has(phase) || phase === "completing")) return false;
|
|
2592
|
+
return true;
|
|
2593
|
+
}
|
|
2594
|
+
|
|
2595
|
+
function isActiveAutopilotRalplanPhase(state: Record<string, unknown> | null): boolean {
|
|
2596
|
+
if (!state || state.active !== true) return false;
|
|
2597
|
+
const mode = safeString(state.mode).trim();
|
|
2598
|
+
if (mode && mode !== "autopilot") return false;
|
|
2599
|
+
const phase = safeString(state.current_phase ?? state.currentPhase).trim().toLowerCase();
|
|
2600
|
+
return phase === "ralplan" || phase === "replan" || phase === "autopilot:replan";
|
|
2601
|
+
}
|
|
2602
|
+
|
|
2603
|
+
function hasExplicitExecutionHandoffSkill(
|
|
2604
|
+
state: SkillActiveStateLike | null,
|
|
2605
|
+
sessionId: string,
|
|
2606
|
+
threadId: string,
|
|
2607
|
+
): boolean {
|
|
2608
|
+
return listActiveSkills(state ?? {}).some((entry) => (
|
|
2609
|
+
RALPLAN_EXECUTION_HANDOFF_SKILLS.has(entry.skill)
|
|
2610
|
+
&& matchesSkillStopContext(entry, state ?? {}, sessionId, threadId)
|
|
2611
|
+
));
|
|
2612
|
+
}
|
|
2613
|
+
|
|
2614
|
+
function isAllowedPlanningArtifactPath(
|
|
2615
|
+
cwd: string,
|
|
2616
|
+
rawPath: string,
|
|
2617
|
+
allowedPrefixes: readonly string[],
|
|
2618
|
+
): boolean {
|
|
2421
2619
|
const trimmed = rawPath.trim().replace(/^['"]|['"]$/g, "");
|
|
2422
2620
|
if (!trimmed || trimmed.includes("\0")) return false;
|
|
2423
2621
|
let relativePath: string;
|
|
@@ -2428,11 +2626,19 @@ function isAllowedDeepInterviewArtifactPath(cwd: string, rawPath: string): boole
|
|
|
2428
2626
|
return false;
|
|
2429
2627
|
}
|
|
2430
2628
|
if (!relativePath || relativePath.startsWith("..") || relativePath.startsWith("/")) return false;
|
|
2431
|
-
return
|
|
2629
|
+
return allowedPrefixes.some((prefix) => (
|
|
2432
2630
|
relativePath === prefix || relativePath.startsWith(`${prefix}/`)
|
|
2433
2631
|
));
|
|
2434
2632
|
}
|
|
2435
2633
|
|
|
2634
|
+
function isAllowedDeepInterviewArtifactPath(cwd: string, rawPath: string): boolean {
|
|
2635
|
+
return isAllowedPlanningArtifactPath(cwd, rawPath, DEEP_INTERVIEW_ALLOWED_WRITE_PREFIXES);
|
|
2636
|
+
}
|
|
2637
|
+
|
|
2638
|
+
function isAllowedRalplanArtifactPath(cwd: string, rawPath: string): boolean {
|
|
2639
|
+
return isAllowedPlanningArtifactPath(cwd, rawPath, RALPLAN_ALLOWED_WRITE_PREFIXES);
|
|
2640
|
+
}
|
|
2641
|
+
|
|
2436
2642
|
function readPreToolUseCommand(payload: CodexHookPayload): string {
|
|
2437
2643
|
const toolInput = safeObject(payload.tool_input);
|
|
2438
2644
|
return safeString(toolInput.command).trim();
|
|
@@ -2494,7 +2700,7 @@ async function readActiveDeepInterviewStateForPreToolUse(
|
|
|
2494
2700
|
const canonicalState = sessionId
|
|
2495
2701
|
? await readVisibleSkillActiveStateForStateDir(stateDir, sessionId)
|
|
2496
2702
|
: await readSkillActiveState(join(stateDir, SKILL_ACTIVE_STATE_FILE));
|
|
2497
|
-
if (!canonicalState) return
|
|
2703
|
+
if (!canonicalState) return null;
|
|
2498
2704
|
const hasActiveDeepInterviewSkill = listActiveSkills(canonicalState).some((entry) => (
|
|
2499
2705
|
entry.skill === "deep-interview"
|
|
2500
2706
|
&& matchesSkillStopContext(entry, canonicalState, sessionId, threadId)
|
|
@@ -2502,12 +2708,102 @@ async function readActiveDeepInterviewStateForPreToolUse(
|
|
|
2502
2708
|
return hasActiveDeepInterviewSkill ? modeState : null;
|
|
2503
2709
|
}
|
|
2504
2710
|
|
|
2711
|
+
async function readActiveRalplanStateForPreToolUse(
|
|
2712
|
+
cwd: string,
|
|
2713
|
+
stateDir: string,
|
|
2714
|
+
sessionId: string,
|
|
2715
|
+
threadId: string,
|
|
2716
|
+
): Promise<Record<string, unknown> | null> {
|
|
2717
|
+
const modeState = sessionId
|
|
2718
|
+
? await readStopSessionPinnedState("ralplan-state.json", cwd, sessionId, stateDir)
|
|
2719
|
+
: await readJsonIfExists(join(stateDir, "ralplan-state.json"));
|
|
2720
|
+
const canonicalState = sessionId
|
|
2721
|
+
? await readVisibleSkillActiveStateForStateDir(stateDir, sessionId)
|
|
2722
|
+
: await readSkillActiveState(join(stateDir, SKILL_ACTIVE_STATE_FILE));
|
|
2723
|
+
if (isActiveRalplanPhase(modeState) && modeState && modeStateMatchesSkillStopContext(modeState, cwd, sessionId)) {
|
|
2724
|
+
if (hasExplicitExecutionHandoffSkill(canonicalState, sessionId, threadId)) return null;
|
|
2725
|
+
if (!canonicalState) return null;
|
|
2726
|
+
const hasActiveRalplanSkill = listActiveSkills(canonicalState).some((entry) => (
|
|
2727
|
+
entry.skill === "ralplan"
|
|
2728
|
+
&& matchesSkillStopContext(entry, canonicalState, sessionId, threadId)
|
|
2729
|
+
));
|
|
2730
|
+
if (hasActiveRalplanSkill) return modeState;
|
|
2731
|
+
}
|
|
2732
|
+
|
|
2733
|
+
const autopilotState = sessionId
|
|
2734
|
+
? await readStopSessionPinnedState("autopilot-state.json", cwd, sessionId, stateDir)
|
|
2735
|
+
: await readJsonIfExists(join(stateDir, "autopilot-state.json"));
|
|
2736
|
+
if (!isActiveAutopilotRalplanPhase(autopilotState) || !autopilotState) return null;
|
|
2737
|
+
if (!modeStateMatchesSkillStopContext(autopilotState, cwd, sessionId)) return null;
|
|
2738
|
+
const terminalAutopilotRunState = await readCanonicalTerminalRunStateForStop(cwd, sessionId, "autopilot");
|
|
2739
|
+
if (terminalAutopilotRunState) return null;
|
|
2740
|
+
if (!canonicalState) return null;
|
|
2741
|
+
const hasActiveAutopilotSkill = listActiveSkills(canonicalState).some((entry) => (
|
|
2742
|
+
entry.skill === "autopilot"
|
|
2743
|
+
&& matchesSkillStopContext(entry, canonicalState, sessionId, threadId)
|
|
2744
|
+
));
|
|
2745
|
+
return hasActiveAutopilotSkill ? autopilotState : null;
|
|
2746
|
+
}
|
|
2747
|
+
|
|
2748
|
+
function isAllowedRalplanBashWrite(cwd: string, command: string): boolean {
|
|
2749
|
+
if (!commandHasDeepInterviewWriteIntent(command)) return true;
|
|
2750
|
+
if (/\bomx\s+(?:state\s+(?:write|read|clear)|question)\b/.test(command)) return true;
|
|
2751
|
+
const targets = extractDeepInterviewCommandWriteTargets(command);
|
|
2752
|
+
return targets.length > 0 && targets.every((target) => isAllowedRalplanArtifactPath(cwd, target));
|
|
2753
|
+
}
|
|
2754
|
+
|
|
2755
|
+
async function buildRalplanPreToolUseBoundaryOutput(
|
|
2756
|
+
payload: CodexHookPayload,
|
|
2757
|
+
cwd: string,
|
|
2758
|
+
stateDir: string,
|
|
2759
|
+
resolvedSessionId?: string,
|
|
2760
|
+
): Promise<Record<string, unknown> | null> {
|
|
2761
|
+
const sessionId = safeString(resolvedSessionId ?? readPayloadSessionId(payload)).trim();
|
|
2762
|
+
const threadId = readPayloadThreadId(payload);
|
|
2763
|
+
const activeState = await readActiveRalplanStateForPreToolUse(cwd, stateDir, sessionId, threadId);
|
|
2764
|
+
if (!activeState) return null;
|
|
2765
|
+
|
|
2766
|
+
const toolName = safeString(payload.tool_name).trim();
|
|
2767
|
+
const command = readPreToolUseCommand(payload);
|
|
2768
|
+
const pathCandidates = readPreToolUsePathCandidates(payload);
|
|
2769
|
+
let blocked = false;
|
|
2770
|
+
|
|
2771
|
+
if (toolName === "Bash") {
|
|
2772
|
+
blocked = !isAllowedRalplanBashWrite(cwd, command);
|
|
2773
|
+
} else if (PLANNING_MODE_IMPLEMENTATION_TOOL_NAMES.has(toolName)) {
|
|
2774
|
+
blocked = pathCandidates.length === 0
|
|
2775
|
+
|| !pathCandidates.every((candidate) => isAllowedRalplanArtifactPath(cwd, candidate));
|
|
2776
|
+
}
|
|
2777
|
+
|
|
2778
|
+
if (!blocked) return null;
|
|
2779
|
+
|
|
2780
|
+
const phase = formatPhase(activeState.current_phase ?? activeState.currentPhase, "planning");
|
|
2781
|
+
const activeMode = safeString(activeState.mode).trim().toLowerCase();
|
|
2782
|
+
const planningModeLabel = activeMode === "autopilot" ? "Autopilot planning" : "Ralplan";
|
|
2783
|
+
const planningModeDescription = activeMode === "autopilot"
|
|
2784
|
+
? "Autopilot is supervising a planning phase"
|
|
2785
|
+
: "Ralplan is consensus-planning mode";
|
|
2786
|
+
return {
|
|
2787
|
+
decision: "block",
|
|
2788
|
+
reason: `${planningModeLabel} is active (phase: ${phase}); implementation/write tools are blocked until an explicit execution handoff workflow is activated.`,
|
|
2789
|
+
hookSpecificOutput: {
|
|
2790
|
+
hookEventName: "PreToolUse",
|
|
2791
|
+
additionalContext:
|
|
2792
|
+
`${planningModeDescription}. `
|
|
2793
|
+
+ "Write only planning artifacts under `.omx/context/`, `.omx/plans/`, `.omx/specs/`, or required `.omx/state/` files. "
|
|
2794
|
+
+ "Do not edit implementation files or run implementation-focused writes from planning phases. "
|
|
2795
|
+
+ `To execute, first process an explicit handoff such as ${formatExecutionHandoffList(cwd)}, which must emit terminal planning state before implementation begins.`,
|
|
2796
|
+
},
|
|
2797
|
+
};
|
|
2798
|
+
}
|
|
2799
|
+
|
|
2505
2800
|
async function buildDeepInterviewPreToolUseBoundaryOutput(
|
|
2506
2801
|
payload: CodexHookPayload,
|
|
2507
2802
|
cwd: string,
|
|
2508
2803
|
stateDir: string,
|
|
2804
|
+
resolvedSessionId?: string,
|
|
2509
2805
|
): Promise<Record<string, unknown> | null> {
|
|
2510
|
-
const sessionId = readPayloadSessionId(payload);
|
|
2806
|
+
const sessionId = safeString(resolvedSessionId ?? readPayloadSessionId(payload)).trim();
|
|
2511
2807
|
const threadId = readPayloadThreadId(payload);
|
|
2512
2808
|
const activeState = await readActiveDeepInterviewStateForPreToolUse(cwd, stateDir, sessionId, threadId);
|
|
2513
2809
|
if (!activeState) return null;
|
|
@@ -2533,7 +2829,7 @@ async function buildDeepInterviewPreToolUseBoundaryOutput(
|
|
|
2533
2829
|
hookSpecificOutput: {
|
|
2534
2830
|
hookEventName: "PreToolUse",
|
|
2535
2831
|
additionalContext:
|
|
2536
|
-
|
|
2832
|
+
`Deep-interview is requirements/spec mode. Treat detailed user answers as interview/spec material, not implicit implementation authorization. You may write only deep-interview artifacts under \`.omx/context/\`, \`.omx/interviews/\`, \`.omx/specs/\`, or required \`.omx/state/\` files. To implement, first ask for or process an explicit transition such as \`$ralplan\`, \`$autopilot\`, ${formatExecutionHandoffList(cwd)}.`,
|
|
2537
2833
|
},
|
|
2538
2834
|
};
|
|
2539
2835
|
}
|
|
@@ -2792,6 +3088,7 @@ async function reconcileStaleRootSkillActiveStateForStop(
|
|
|
2792
3088
|
function buildRalplanContinuationStatus(
|
|
2793
3089
|
blocker: { phase: string; latestPlanPath?: string; planningComplete?: boolean; runOutcome?: string },
|
|
2794
3090
|
activeSubagentCount: number,
|
|
3091
|
+
cwd: string,
|
|
2795
3092
|
): { reason: string; systemMessage: string; stopReasonSuffix: string } {
|
|
2796
3093
|
const phase = blocker.phase || "planning";
|
|
2797
3094
|
const artifact = blocker.latestPlanPath
|
|
@@ -2827,15 +3124,15 @@ function buildRalplanContinuationStatus(
|
|
|
2827
3124
|
}
|
|
2828
3125
|
|
|
2829
3126
|
const completeHint = blocker.planningComplete
|
|
2830
|
-
?
|
|
3127
|
+
? ` The planning artifacts are present; if consensus is approved, emit terminal ralplan complete/approved handoff state and stop planning. Implementation must wait for an explicit ${formatExecutionHandoffList(cwd).replaceAll("`", "")} handoff.`
|
|
2831
3128
|
: "";
|
|
2832
3129
|
|
|
2833
3130
|
return {
|
|
2834
3131
|
reason:
|
|
2835
|
-
`Status: continue_from_artifact — ralplan is still active (phase: ${phase}) and has not emitted a terminal complete/paused/waiting status. Continue from the current ralplan artifact, resolve any review ambiguity conservatively or ask the user if needed, and proceed to the next planning/review step before stopping.${artifact}${completeHint}`,
|
|
3132
|
+
`Status: continue_from_artifact — ralplan is still active (phase: ${phase}) and has not emitted a terminal complete/paused/waiting status. Continue from the current ralplan artifact, resolve any review ambiguity conservatively or ask the user if needed, and proceed to the next planning/review step before stopping; do not begin implementation from ralplan.${artifact}${completeHint}`,
|
|
2836
3133
|
stopReasonSuffix: "continue_artifact",
|
|
2837
3134
|
systemMessage:
|
|
2838
|
-
`OMX ralplan status: continue_from_artifact at phase ${phase}; continue from the current ralplan artifact and finish by stating whether ralplan is complete, paused for review, waiting for input, or still continuing.`,
|
|
3135
|
+
`OMX ralplan status: continue_from_artifact at phase ${phase}; continue from the current ralplan artifact and finish by stating whether ralplan is complete, paused for review, waiting for input, or still continuing; do not begin implementation from ralplan.`,
|
|
2839
3136
|
};
|
|
2840
3137
|
}
|
|
2841
3138
|
|
|
@@ -3047,6 +3344,11 @@ async function maybeBuildOrdinaryStopNoProgressOutput(
|
|
|
3047
3344
|
stateDir: string,
|
|
3048
3345
|
canonicalSessionId?: string,
|
|
3049
3346
|
): Promise<Record<string, unknown> | null> {
|
|
3347
|
+
const lastAssistantMessage = safeString(
|
|
3348
|
+
payload.last_assistant_message ?? payload.lastAssistantMessage,
|
|
3349
|
+
).trim();
|
|
3350
|
+
if (!lastAssistantMessage) return null;
|
|
3351
|
+
|
|
3050
3352
|
const statePath = join(stateDir, NATIVE_STOP_STATE_FILE);
|
|
3051
3353
|
const state = await readJsonIfExists(statePath) ?? {};
|
|
3052
3354
|
const sessions = safeObject(state.sessions);
|
|
@@ -3078,9 +3380,6 @@ async function maybeBuildOrdinaryStopNoProgressOutput(
|
|
|
3078
3380
|
await mkdir(stateDir, { recursive: true });
|
|
3079
3381
|
await writeFile(statePath, JSON.stringify({ ...state, sessions }, null, 2));
|
|
3080
3382
|
|
|
3081
|
-
const stopHookActive = payload.stop_hook_active === true || payload.stopHookActive === true;
|
|
3082
|
-
if (!stopHookActive) return null;
|
|
3083
|
-
|
|
3084
3383
|
const maxRepeats = parseBoundedPositiveInteger(
|
|
3085
3384
|
process.env.OMX_NATIVE_STOP_NO_PROGRESS_MAX_REPEATS,
|
|
3086
3385
|
ORDINARY_STOP_NO_PROGRESS_DEFAULT_MAX_REPEATS,
|
|
@@ -3285,7 +3584,7 @@ async function buildSkillStopOutput(
|
|
|
3285
3584
|
const activeSubagentCount = subagentSummary?.activeSubagentThreadIds.length ?? 0;
|
|
3286
3585
|
|
|
3287
3586
|
if (blocker.skill === "ralplan") {
|
|
3288
|
-
const status = buildRalplanContinuationStatus(blocker, activeSubagentCount);
|
|
3587
|
+
const status = buildRalplanContinuationStatus(blocker, activeSubagentCount, cwd);
|
|
3289
3588
|
return {
|
|
3290
3589
|
decision: "block",
|
|
3291
3590
|
reason: status.reason,
|
|
@@ -3732,10 +4031,20 @@ export async function dispatchCodexNativeHook(
|
|
|
3732
4031
|
): Promise<NativeHookDispatchResult> {
|
|
3733
4032
|
const hookEventName = readHookEventName(payload);
|
|
3734
4033
|
const cwd = options.cwd ?? (safeString(payload.cwd).trim() || process.cwd());
|
|
4034
|
+
if (hookEventName === "Stop" && !hasNativeStopRuntimeSurface(cwd)) {
|
|
4035
|
+
return {
|
|
4036
|
+
hookEventName,
|
|
4037
|
+
omxEventName: mapCodexHookEventToOmxEvent(hookEventName),
|
|
4038
|
+
skillState: null,
|
|
4039
|
+
outputJson: null,
|
|
4040
|
+
};
|
|
4041
|
+
}
|
|
3735
4042
|
// Native hooks must use the same authoritative runtime state root as HUD/MCP
|
|
3736
4043
|
// when boxed/team roots are active; do not bypass it with cwd/.omx/state.
|
|
3737
4044
|
const stateDir = getBaseStateDir(cwd);
|
|
3738
|
-
|
|
4045
|
+
if (hookEventName !== "Stop") {
|
|
4046
|
+
await mkdir(stateDir, { recursive: true });
|
|
4047
|
+
}
|
|
3739
4048
|
|
|
3740
4049
|
const omxEventName = mapCodexHookEventToOmxEvent(hookEventName);
|
|
3741
4050
|
let skillState: SkillActiveState | null = null;
|
|
@@ -3814,7 +4123,13 @@ export async function dispatchCodexNativeHook(
|
|
|
3814
4123
|
const sessionIdForState = canonicalSessionId || nativeSessionId;
|
|
3815
4124
|
let outputJson: Record<string, unknown> | null = null;
|
|
3816
4125
|
const isSubagentPromptSubmit = hookEventName === "UserPromptSubmit"
|
|
3817
|
-
? await isNativeSubagentHook(
|
|
4126
|
+
? await isNativeSubagentHook(
|
|
4127
|
+
cwd,
|
|
4128
|
+
canonicalSessionId,
|
|
4129
|
+
nativeSessionId,
|
|
4130
|
+
threadId,
|
|
4131
|
+
safeString(currentSessionState?.native_session_id).trim(),
|
|
4132
|
+
)
|
|
3818
4133
|
: false;
|
|
3819
4134
|
const isSubagentStop = hookEventName === "Stop"
|
|
3820
4135
|
? (await Promise.all(
|
|
@@ -3822,7 +4137,15 @@ export async function dispatchCodexNativeHook(
|
|
|
3822
4137
|
canonicalSessionId,
|
|
3823
4138
|
safeString(currentSessionState?.session_id).trim(),
|
|
3824
4139
|
].filter(Boolean))]
|
|
3825
|
-
.map((candidateSessionId) => isNativeSubagentHook(
|
|
4140
|
+
.map((candidateSessionId) => isNativeSubagentHook(
|
|
4141
|
+
cwd,
|
|
4142
|
+
candidateSessionId,
|
|
4143
|
+
nativeSessionId,
|
|
4144
|
+
threadId,
|
|
4145
|
+
candidateSessionId === safeString(currentSessionState?.session_id).trim()
|
|
4146
|
+
? safeString(currentSessionState?.native_session_id).trim()
|
|
4147
|
+
: "",
|
|
4148
|
+
)),
|
|
3826
4149
|
)).some(Boolean)
|
|
3827
4150
|
: false;
|
|
3828
4151
|
const suppressNoisySubagentLifecycleDispatch =
|
|
@@ -3922,11 +4245,23 @@ export async function dispatchCodexNativeHook(
|
|
|
3922
4245
|
triageAdditionalContext = null;
|
|
3923
4246
|
}
|
|
3924
4247
|
}
|
|
4248
|
+
const skipHudReconcileForDoctorSmoke = process.env.OMX_NATIVE_HOOK_DOCTOR_SMOKE === "1";
|
|
3925
4249
|
const skipHudReconcileForTeamWorkerPane = !isSubagentPromptSubmit
|
|
3926
4250
|
&& await isConfirmedTeamWorkerPromptSubmitPane(cwd).catch(() => false);
|
|
3927
|
-
if (!skipHudReconcileForTeamWorkerPane) {
|
|
4251
|
+
if (!skipHudReconcileForDoctorSmoke && !skipHudReconcileForTeamWorkerPane) {
|
|
3928
4252
|
const reconcileHudForPromptSubmitFn = options.reconcileHudForPromptSubmitFn ?? reconcileHudForPromptSubmit;
|
|
3929
|
-
|
|
4253
|
+
const hudSessionId = resolveHudReconcileSessionId(
|
|
4254
|
+
currentSessionState,
|
|
4255
|
+
canonicalSessionId,
|
|
4256
|
+
sessionIdForState,
|
|
4257
|
+
);
|
|
4258
|
+
const hudSessionIds = resolveHudReconcileSessionIds(
|
|
4259
|
+
currentSessionState,
|
|
4260
|
+
canonicalSessionId,
|
|
4261
|
+
sessionIdForState,
|
|
4262
|
+
nativeSessionId,
|
|
4263
|
+
);
|
|
4264
|
+
await reconcileHudForPromptSubmitFn(cwd, { sessionId: hudSessionId, sessionIds: hudSessionIds }).catch(() => {});
|
|
3930
4265
|
}
|
|
3931
4266
|
}
|
|
3932
4267
|
|
|
@@ -3986,7 +4321,12 @@ export async function dispatchCodexNativeHook(
|
|
|
3986
4321
|
};
|
|
3987
4322
|
}
|
|
3988
4323
|
} else if (hookEventName === "PreToolUse") {
|
|
3989
|
-
|
|
4324
|
+
const payloadSessionId = readPayloadSessionId(payload);
|
|
4325
|
+
const preToolUseSessionId = payloadSessionId
|
|
4326
|
+
? await resolveInternalSessionIdForPayload(cwd, payloadSessionId, stateDir)
|
|
4327
|
+
: "";
|
|
4328
|
+
outputJson = await buildDeepInterviewPreToolUseBoundaryOutput(payload, cwd, stateDir, preToolUseSessionId)
|
|
4329
|
+
?? await buildRalplanPreToolUseBoundaryOutput(payload, cwd, stateDir, preToolUseSessionId)
|
|
3990
4330
|
?? buildNativePreToolUseOutput(payload);
|
|
3991
4331
|
} else if (hookEventName === "PostToolUse") {
|
|
3992
4332
|
if (detectMcpTransportFailure(payload)) {
|
|
@@ -4008,9 +4348,31 @@ export async function dispatchCodexNativeHook(
|
|
|
4008
4348
|
};
|
|
4009
4349
|
}
|
|
4010
4350
|
|
|
4351
|
+
function hasNativeStopRuntimeSurface(cwd: string): boolean {
|
|
4352
|
+
if (existsSync(join(cwd, ".omx"))) return true;
|
|
4353
|
+
if (findGitLayout(cwd)) return true;
|
|
4354
|
+
const omxRoot = safeString(process.env.OMX_ROOT).trim();
|
|
4355
|
+
if (omxRoot && existsSync(join(omxRoot, ".omx"))) return true;
|
|
4356
|
+
const stateRoot = safeString(process.env.OMX_STATE_ROOT).trim();
|
|
4357
|
+
if (stateRoot && existsSync(stateRoot)) return true;
|
|
4358
|
+
return [
|
|
4359
|
+
process.env.OMX_SESSION_ID,
|
|
4360
|
+
process.env.OMX_TEAM_INTERNAL_WORKER,
|
|
4361
|
+
process.env.OMX_TEAM_WORKER,
|
|
4362
|
+
process.env.OMX_TEAM_STATE_ROOT,
|
|
4363
|
+
process.env.OMX_TEAM_LEADER_CWD,
|
|
4364
|
+
process.env.OMX_NOTIFY_HOOK_TRUSTED_MANAGED_CWD,
|
|
4365
|
+
process.env.OMX_TMUX_HUD_OWNER,
|
|
4366
|
+
process.env.OMX_TMUX_HUD_LEADER_PANE,
|
|
4367
|
+
].some((value) => safeString(value).trim() !== "");
|
|
4368
|
+
}
|
|
4369
|
+
|
|
4011
4370
|
interface NativeHookCliReadResult {
|
|
4012
4371
|
payload: CodexHookPayload;
|
|
4013
4372
|
parseError: Error | null;
|
|
4373
|
+
rawInput: string;
|
|
4374
|
+
oversized: boolean;
|
|
4375
|
+
rawHookEventName: CodexHookEventName | null;
|
|
4014
4376
|
}
|
|
4015
4377
|
|
|
4016
4378
|
export function isCodexNativeHookMainModule(
|
|
@@ -4023,36 +4385,156 @@ export function isCodexNativeHookMainModule(
|
|
|
4023
4385
|
|
|
4024
4386
|
async function readStdinJson(): Promise<NativeHookCliReadResult> {
|
|
4025
4387
|
const chunks: Buffer[] = [];
|
|
4388
|
+
let totalBytes = 0;
|
|
4389
|
+
let oversized = false;
|
|
4026
4390
|
for await (const chunk of process.stdin) {
|
|
4027
|
-
|
|
4391
|
+
const buffer = Buffer.isBuffer(chunk) ? chunk : Buffer.from(String(chunk));
|
|
4392
|
+
totalBytes += buffer.byteLength;
|
|
4393
|
+
if (totalBytes > MAX_NATIVE_STDIN_JSON_BYTES) {
|
|
4394
|
+
const remaining = Math.max(0, MAX_NATIVE_STDIN_JSON_BYTES - (totalBytes - buffer.byteLength));
|
|
4395
|
+
if (remaining > 0) chunks.push(Buffer.from(buffer.subarray(0, remaining)));
|
|
4396
|
+
oversized = true;
|
|
4397
|
+
process.stdin.destroy();
|
|
4398
|
+
break;
|
|
4399
|
+
}
|
|
4400
|
+
chunks.push(buffer);
|
|
4028
4401
|
}
|
|
4029
4402
|
const raw = Buffer.concat(chunks).toString("utf-8").trim();
|
|
4403
|
+
const rawHookEventName = extractRawCodexHookEventName(raw);
|
|
4404
|
+
if (oversized) {
|
|
4405
|
+
return {
|
|
4406
|
+
payload: {},
|
|
4407
|
+
parseError: null,
|
|
4408
|
+
rawInput: raw,
|
|
4409
|
+
oversized: true,
|
|
4410
|
+
rawHookEventName,
|
|
4411
|
+
};
|
|
4412
|
+
}
|
|
4030
4413
|
if (!raw) {
|
|
4031
|
-
return { payload: {}, parseError: null };
|
|
4414
|
+
return { payload: {}, parseError: null, rawInput: raw, oversized: false, rawHookEventName };
|
|
4032
4415
|
}
|
|
4033
4416
|
|
|
4034
4417
|
try {
|
|
4035
4418
|
return {
|
|
4036
4419
|
payload: safeObject(JSON.parse(raw)),
|
|
4037
4420
|
parseError: null,
|
|
4421
|
+
rawInput: raw,
|
|
4422
|
+
oversized: false,
|
|
4423
|
+
rawHookEventName,
|
|
4038
4424
|
};
|
|
4039
4425
|
} catch (error) {
|
|
4040
4426
|
return {
|
|
4041
4427
|
payload: {},
|
|
4042
4428
|
parseError: error instanceof Error ? error : new Error(String(error)),
|
|
4429
|
+
rawInput: raw,
|
|
4430
|
+
oversized: false,
|
|
4431
|
+
rawHookEventName,
|
|
4043
4432
|
};
|
|
4044
4433
|
}
|
|
4045
4434
|
}
|
|
4046
4435
|
|
|
4436
|
+
function inferHookEventNameFromMalformedInput(raw: string): CodexHookEventName | null {
|
|
4437
|
+
const match = raw.match(/(?:\"|['"])?hook[_-]?event[_-]?name(?:\"|['"])?\s*:\s*(?:\"|['"])?(SessionStart|PreToolUse|PostToolUse|UserPromptSubmit|PreCompact|PostCompact|Stop)\b/i);
|
|
4438
|
+
const value = match?.[1];
|
|
4439
|
+
if (!value) return null;
|
|
4440
|
+
return readHookEventName({ hook_event_name: value });
|
|
4441
|
+
}
|
|
4442
|
+
|
|
4443
|
+
function buildMalformedStdinHookOutput(parseError: Error, rawInput: string): Record<string, unknown> {
|
|
4444
|
+
const reason =
|
|
4445
|
+
"OMX native hook received malformed JSON input. Preserve runtime state, inspect the emitting hook payload yourself, and retry with valid JSON.";
|
|
4446
|
+
const systemMessage =
|
|
4447
|
+
`${reason} stdin JSON parsing failed inside codex-native-hook: ${parseError.message}.`;
|
|
4448
|
+
if (inferHookEventNameFromMalformedInput(rawInput) === "Stop") {
|
|
4449
|
+
return {
|
|
4450
|
+
decision: "block",
|
|
4451
|
+
reason,
|
|
4452
|
+
stopReason: "native_hook_stdin_parse_error",
|
|
4453
|
+
systemMessage,
|
|
4454
|
+
};
|
|
4455
|
+
}
|
|
4456
|
+
return {
|
|
4457
|
+
continue: false,
|
|
4458
|
+
stopReason: "native_hook_stdin_parse_error",
|
|
4459
|
+
systemMessage,
|
|
4460
|
+
};
|
|
4461
|
+
}
|
|
4462
|
+
|
|
4463
|
+
async function buildOversizedStopActiveWorkflowOutput(cwd: string): Promise<Record<string, unknown> | null> {
|
|
4464
|
+
const currentSession = await readUsableSessionState(cwd);
|
|
4465
|
+
const currentSessionId = safeString(currentSession?.session_id).trim()
|
|
4466
|
+
|| safeString(process.env.OMX_SESSION_ID || process.env.CODEX_SESSION_ID).trim();
|
|
4467
|
+
if (!currentSessionId) return null;
|
|
4468
|
+
|
|
4469
|
+
if (await readCanonicalTerminalRunStateForStop(cwd, currentSessionId, "autopilot")) return null;
|
|
4470
|
+
|
|
4471
|
+
const autopilotState = await readModeStateForActiveDecision("autopilot", currentSessionId, cwd);
|
|
4472
|
+
if (!autopilotState || !shouldContinueRun(autopilotState)) return null;
|
|
4473
|
+
|
|
4474
|
+
const phase = formatPhase(autopilotState.current_phase);
|
|
4475
|
+
const reason =
|
|
4476
|
+
`OMX native Stop received oversized stdin before parsing while the current session has active OMX autopilot state (phase: ${phase}); continue once with a compact response or reduce hook payload size so normal Stop gates can run.`;
|
|
4477
|
+
return {
|
|
4478
|
+
decision: "block",
|
|
4479
|
+
reason,
|
|
4480
|
+
stopReason: "native_stop_stdin_oversized_active_workflow",
|
|
4481
|
+
systemMessage:
|
|
4482
|
+
"OMX native Stop rejected oversized stdin before parsing; active current-session workflow state is present, so Stop is blocked instead of silently allowing termination.",
|
|
4483
|
+
};
|
|
4484
|
+
}
|
|
4485
|
+
|
|
4486
|
+
async function buildOversizedStdinHookOutput(
|
|
4487
|
+
rawHookEventName: CodexHookEventName | null,
|
|
4488
|
+
cwd: string,
|
|
4489
|
+
): Promise<Record<string, unknown>> {
|
|
4490
|
+
if (rawHookEventName === "Stop") {
|
|
4491
|
+
return await buildOversizedStopActiveWorkflowOutput(cwd) ?? {};
|
|
4492
|
+
}
|
|
4493
|
+
const systemMessage =
|
|
4494
|
+
`OMX native hook rejected oversized stdin JSON before parsing; maxBytes=${MAX_NATIVE_STDIN_JSON_BYTES}.`;
|
|
4495
|
+
return {
|
|
4496
|
+
continue: false,
|
|
4497
|
+
stopReason: "native_hook_stdin_oversized",
|
|
4498
|
+
systemMessage,
|
|
4499
|
+
};
|
|
4500
|
+
}
|
|
4501
|
+
|
|
4047
4502
|
function writeNativeHookJsonStdout(output: Record<string, unknown>): void {
|
|
4048
4503
|
process.stdout.write(`${JSON.stringify(output)}\n`);
|
|
4049
4504
|
}
|
|
4050
4505
|
|
|
4506
|
+
function redactMalformedHookPreview(rawInput: string): string {
|
|
4507
|
+
const withoutControls = rawInput.replace(/[\u0000-\u001f\u007f-\u009f]/g, "");
|
|
4508
|
+
const withoutAuthSecrets = redactAuthSecrets(withoutControls);
|
|
4509
|
+
return withoutAuthSecrets
|
|
4510
|
+
.replace(
|
|
4511
|
+
/(["']?(?:prompt|user_prompt|input|text)["']?\s*:\s*)(["'])(?:\\.|(?!\2)[^\\])*\2/gi,
|
|
4512
|
+
"$1$2[REDACTED]$2",
|
|
4513
|
+
)
|
|
4514
|
+
.replace(
|
|
4515
|
+
/(["']?(?:prompt|user_prompt|input|text)["']?\s*:\s*)(["'])(?:\\.|[^\\])*$/gi,
|
|
4516
|
+
"$1$2[REDACTED]$2",
|
|
4517
|
+
)
|
|
4518
|
+
.replace(
|
|
4519
|
+
/(["']?(?:prompt|user_prompt|input|text)["']?\s*:\s*)(?!["'])[^,}]*/gi,
|
|
4520
|
+
"$1[REDACTED]",
|
|
4521
|
+
);
|
|
4522
|
+
}
|
|
4523
|
+
|
|
4524
|
+
function buildRawInputLogFields(rawInput: string): Record<string, unknown> {
|
|
4525
|
+
if (!rawInput) return {};
|
|
4526
|
+
return {
|
|
4527
|
+
raw_input_length: Buffer.byteLength(rawInput, "utf-8"),
|
|
4528
|
+
raw_input_prefix: redactMalformedHookPreview(rawInput).slice(0, 240),
|
|
4529
|
+
};
|
|
4530
|
+
}
|
|
4531
|
+
|
|
4051
4532
|
async function logNativeHookCliError(
|
|
4052
4533
|
cwd: string,
|
|
4053
4534
|
type: string,
|
|
4054
4535
|
error: unknown,
|
|
4055
4536
|
payload: CodexHookPayload = {},
|
|
4537
|
+
details: Record<string, unknown> = {},
|
|
4056
4538
|
): Promise<void> {
|
|
4057
4539
|
const logsDir = join(cwd || process.cwd(), ".omx", "logs");
|
|
4058
4540
|
await mkdir(logsDir, { recursive: true }).catch(() => {});
|
|
@@ -4067,6 +4549,7 @@ async function logNativeHookCliError(
|
|
|
4067
4549
|
thread_id: readPayloadThreadId(payload) || undefined,
|
|
4068
4550
|
turn_id: readPayloadTurnId(payload) || undefined,
|
|
4069
4551
|
error: error instanceof Error ? error.message : String(error),
|
|
4552
|
+
...details,
|
|
4070
4553
|
}) + "\n",
|
|
4071
4554
|
).catch(() => {});
|
|
4072
4555
|
}
|
|
@@ -4095,18 +4578,20 @@ function buildStopDispatchFailureOutput(error: unknown): Record<string, unknown>
|
|
|
4095
4578
|
}
|
|
4096
4579
|
|
|
4097
4580
|
export async function runCodexNativeHookCli(): Promise<void> {
|
|
4098
|
-
const { payload, parseError } = await readStdinJson();
|
|
4581
|
+
const { payload, parseError, rawInput, oversized, rawHookEventName } = await readStdinJson();
|
|
4582
|
+
if (oversized) {
|
|
4583
|
+
writeNativeHookJsonStdout(await buildOversizedStdinHookOutput(rawHookEventName, process.cwd()));
|
|
4584
|
+
return;
|
|
4585
|
+
}
|
|
4099
4586
|
if (parseError) {
|
|
4100
|
-
await logNativeHookCliError(
|
|
4101
|
-
|
|
4102
|
-
|
|
4103
|
-
|
|
4104
|
-
|
|
4105
|
-
|
|
4106
|
-
|
|
4107
|
-
|
|
4108
|
-
},
|
|
4109
|
-
});
|
|
4587
|
+
await logNativeHookCliError(
|
|
4588
|
+
process.cwd(),
|
|
4589
|
+
"native_hook_stdin_parse_error",
|
|
4590
|
+
parseError,
|
|
4591
|
+
{},
|
|
4592
|
+
buildRawInputLogFields(rawInput),
|
|
4593
|
+
);
|
|
4594
|
+
writeNativeHookJsonStdout(buildMalformedStdinHookOutput(parseError, rawInput));
|
|
4110
4595
|
return;
|
|
4111
4596
|
}
|
|
4112
4597
|
|