oh-my-codex 0.12.4 → 0.12.6
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 +5 -5
- package/Cargo.toml +1 -1
- package/README.md +27 -3
- package/dist/cli/__tests__/ask.test.js +26 -0
- package/dist/cli/__tests__/ask.test.js.map +1 -1
- package/dist/cli/__tests__/doctor-warning-copy.test.js +28 -0
- package/dist/cli/__tests__/doctor-warning-copy.test.js.map +1 -1
- package/dist/cli/__tests__/explore.test.js +95 -8
- package/dist/cli/__tests__/explore.test.js.map +1 -1
- package/dist/cli/__tests__/index.test.js +102 -4
- package/dist/cli/__tests__/index.test.js.map +1 -1
- package/dist/cli/__tests__/launch-fallback.test.js +169 -0
- package/dist/cli/__tests__/launch-fallback.test.js.map +1 -1
- package/dist/cli/__tests__/mcp-parity.test.js +31 -0
- package/dist/cli/__tests__/mcp-parity.test.js.map +1 -1
- package/dist/cli/__tests__/setup-agents-overwrite.test.js +66 -2
- package/dist/cli/__tests__/setup-agents-overwrite.test.js.map +1 -1
- package/dist/cli/__tests__/setup-refresh.test.js +51 -1
- package/dist/cli/__tests__/setup-refresh.test.js.map +1 -1
- package/dist/cli/__tests__/team.test.js +148 -3
- package/dist/cli/__tests__/team.test.js.map +1 -1
- package/dist/cli/__tests__/uninstall.test.js +14 -1
- package/dist/cli/__tests__/uninstall.test.js.map +1 -1
- package/dist/cli/cleanup.js +1 -1
- package/dist/cli/cleanup.js.map +1 -1
- package/dist/cli/constants.d.ts +1 -0
- package/dist/cli/constants.d.ts.map +1 -1
- package/dist/cli/constants.js +1 -0
- package/dist/cli/constants.js.map +1 -1
- package/dist/cli/doctor.d.ts.map +1 -1
- package/dist/cli/doctor.js +15 -0
- package/dist/cli/doctor.js.map +1 -1
- package/dist/cli/explore.d.ts +1 -0
- package/dist/cli/explore.d.ts.map +1 -1
- package/dist/cli/explore.js +49 -1
- package/dist/cli/explore.js.map +1 -1
- package/dist/cli/index.d.ts +2 -1
- package/dist/cli/index.d.ts.map +1 -1
- package/dist/cli/index.js +127 -14
- package/dist/cli/index.js.map +1 -1
- package/dist/cli/mcp-parity.d.ts +1 -1
- package/dist/cli/mcp-parity.d.ts.map +1 -1
- package/dist/cli/mcp-parity.js +24 -0
- package/dist/cli/mcp-parity.js.map +1 -1
- package/dist/cli/setup.d.ts.map +1 -1
- package/dist/cli/setup.js +17 -5
- package/dist/cli/setup.js.map +1 -1
- package/dist/cli/team.d.ts.map +1 -1
- package/dist/cli/team.js +80 -6
- package/dist/cli/team.js.map +1 -1
- package/dist/cli/uninstall.d.ts.map +1 -1
- package/dist/cli/uninstall.js +1 -0
- package/dist/cli/uninstall.js.map +1 -1
- package/dist/config/__tests__/generator-idempotent.test.js +60 -0
- package/dist/config/__tests__/generator-idempotent.test.js.map +1 -1
- package/dist/config/__tests__/mcp-registry.test.js +61 -0
- package/dist/config/__tests__/mcp-registry.test.js.map +1 -1
- package/dist/config/__tests__/wiki-config-contract.test.d.ts +2 -0
- package/dist/config/__tests__/wiki-config-contract.test.d.ts.map +1 -0
- package/dist/config/__tests__/wiki-config-contract.test.js +19 -0
- package/dist/config/__tests__/wiki-config-contract.test.js.map +1 -0
- package/dist/config/generator.d.ts +1 -0
- package/dist/config/generator.d.ts.map +1 -1
- package/dist/config/generator.js +88 -3
- package/dist/config/generator.js.map +1 -1
- package/dist/config/mcp-registry.d.ts +2 -0
- package/dist/config/mcp-registry.d.ts.map +1 -1
- package/dist/config/mcp-registry.js +12 -0
- package/dist/config/mcp-registry.js.map +1 -1
- package/dist/hooks/__tests__/agents-overlay.test.js +39 -0
- package/dist/hooks/__tests__/agents-overlay.test.js.map +1 -1
- package/dist/hooks/__tests__/keyword-detector.test.js +297 -4
- package/dist/hooks/__tests__/keyword-detector.test.js.map +1 -1
- package/dist/hooks/__tests__/notify-fallback-watcher.test.js +392 -22
- package/dist/hooks/__tests__/notify-fallback-watcher.test.js.map +1 -1
- package/dist/hooks/__tests__/notify-hook-auto-nudge.test.js +166 -67
- package/dist/hooks/__tests__/notify-hook-auto-nudge.test.js.map +1 -1
- package/dist/hooks/__tests__/notify-hook-managed-tmux.test.js +112 -2
- package/dist/hooks/__tests__/notify-hook-managed-tmux.test.js.map +1 -1
- package/dist/hooks/__tests__/notify-hook-modules.test.js +52 -12
- package/dist/hooks/__tests__/notify-hook-modules.test.js.map +1 -1
- package/dist/hooks/__tests__/notify-hook-regression-205.test.d.ts +2 -3
- package/dist/hooks/__tests__/notify-hook-regression-205.test.d.ts.map +1 -1
- package/dist/hooks/__tests__/notify-hook-regression-205.test.js +18 -23
- package/dist/hooks/__tests__/notify-hook-regression-205.test.js.map +1 -1
- package/dist/hooks/__tests__/notify-hook-session-scope.test.js +33 -0
- package/dist/hooks/__tests__/notify-hook-session-scope.test.js.map +1 -1
- package/dist/hooks/__tests__/notify-hook-team-dispatch.test.js +176 -1
- package/dist/hooks/__tests__/notify-hook-team-dispatch.test.js.map +1 -1
- package/dist/hooks/__tests__/notify-hook-team-leader-nudge.test.js +355 -7
- package/dist/hooks/__tests__/notify-hook-team-leader-nudge.test.js.map +1 -1
- package/dist/hooks/__tests__/notify-hook-tmux-heal.test.js +90 -2
- package/dist/hooks/__tests__/notify-hook-tmux-heal.test.js.map +1 -1
- package/dist/hooks/__tests__/session.test.js +142 -2
- package/dist/hooks/__tests__/session.test.js.map +1 -1
- package/dist/hooks/__tests__/wiki-docs-contract.test.d.ts +2 -0
- package/dist/hooks/__tests__/wiki-docs-contract.test.d.ts.map +1 -0
- package/dist/hooks/__tests__/wiki-docs-contract.test.js +34 -0
- package/dist/hooks/__tests__/wiki-docs-contract.test.js.map +1 -0
- package/dist/hooks/agents-overlay.d.ts.map +1 -1
- package/dist/hooks/agents-overlay.js +0 -1
- package/dist/hooks/agents-overlay.js.map +1 -1
- package/dist/hooks/extensibility/__tests__/dispatcher.test.js +32 -0
- package/dist/hooks/extensibility/__tests__/dispatcher.test.js.map +1 -1
- package/dist/hooks/extensibility/__tests__/runtime.test.js +31 -0
- package/dist/hooks/extensibility/__tests__/runtime.test.js.map +1 -1
- package/dist/hooks/extensibility/__tests__/sdk.test.js +33 -3
- package/dist/hooks/extensibility/__tests__/sdk.test.js.map +1 -1
- package/dist/hooks/extensibility/dispatcher.d.ts.map +1 -1
- package/dist/hooks/extensibility/dispatcher.js +41 -0
- package/dist/hooks/extensibility/dispatcher.js.map +1 -1
- package/dist/hooks/extensibility/sdk/runtime-state.d.ts.map +1 -1
- package/dist/hooks/extensibility/sdk/runtime-state.js +7 -1
- package/dist/hooks/extensibility/sdk/runtime-state.js.map +1 -1
- package/dist/hooks/extensibility/types.d.ts +1 -0
- package/dist/hooks/extensibility/types.d.ts.map +1 -1
- package/dist/hooks/keyword-detector.d.ts +6 -1
- package/dist/hooks/keyword-detector.d.ts.map +1 -1
- package/dist/hooks/keyword-detector.js +207 -10
- 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 +3 -0
- package/dist/hooks/keyword-registry.js.map +1 -1
- package/dist/hooks/session.d.ts +14 -2
- package/dist/hooks/session.d.ts.map +1 -1
- package/dist/hooks/session.js +120 -16
- package/dist/hooks/session.js.map +1 -1
- package/dist/hud/__tests__/state.test.js +111 -2
- package/dist/hud/__tests__/state.test.js.map +1 -1
- package/dist/hud/state.d.ts.map +1 -1
- package/dist/hud/state.js +18 -21
- package/dist/hud/state.js.map +1 -1
- package/dist/mcp/__tests__/bootstrap.test.js +88 -1
- package/dist/mcp/__tests__/bootstrap.test.js.map +1 -1
- package/dist/mcp/__tests__/server-lifecycle.test.js +3 -0
- package/dist/mcp/__tests__/server-lifecycle.test.js.map +1 -1
- package/dist/mcp/__tests__/state-paths.test.js +30 -2
- package/dist/mcp/__tests__/state-paths.test.js.map +1 -1
- package/dist/mcp/__tests__/state-server.test.js +415 -0
- package/dist/mcp/__tests__/state-server.test.js.map +1 -1
- package/dist/mcp/__tests__/wiki-server.test.d.ts +2 -0
- package/dist/mcp/__tests__/wiki-server.test.d.ts.map +1 -0
- package/dist/mcp/__tests__/wiki-server.test.js +30 -0
- package/dist/mcp/__tests__/wiki-server.test.js.map +1 -0
- package/dist/mcp/bootstrap.d.ts +19 -1
- package/dist/mcp/bootstrap.d.ts.map +1 -1
- package/dist/mcp/bootstrap.js +185 -0
- package/dist/mcp/bootstrap.js.map +1 -1
- package/dist/mcp/state-paths.d.ts +5 -0
- package/dist/mcp/state-paths.d.ts.map +1 -1
- package/dist/mcp/state-paths.js +41 -11
- package/dist/mcp/state-paths.js.map +1 -1
- package/dist/mcp/state-server.d.ts +4 -4
- package/dist/mcp/state-server.d.ts.map +1 -1
- package/dist/mcp/state-server.js +49 -2
- package/dist/mcp/state-server.js.map +1 -1
- package/dist/mcp/wiki-server.d.ts +181 -0
- package/dist/mcp/wiki-server.d.ts.map +1 -0
- package/dist/mcp/wiki-server.js +235 -0
- package/dist/mcp/wiki-server.js.map +1 -0
- package/dist/modes/__tests__/base-autoresearch-contract.test.js +74 -2
- package/dist/modes/__tests__/base-autoresearch-contract.test.js.map +1 -1
- package/dist/modes/__tests__/base-multi-state-compat.test.d.ts +2 -0
- package/dist/modes/__tests__/base-multi-state-compat.test.d.ts.map +1 -0
- package/dist/modes/__tests__/base-multi-state-compat.test.js +38 -0
- package/dist/modes/__tests__/base-multi-state-compat.test.js.map +1 -0
- package/dist/modes/__tests__/base-tmux-pane.test.js +1 -1
- package/dist/modes/__tests__/base-tmux-pane.test.js.map +1 -1
- package/dist/modes/base.d.ts +2 -1
- package/dist/modes/base.d.ts.map +1 -1
- package/dist/modes/base.js +55 -31
- package/dist/modes/base.js.map +1 -1
- package/dist/notifications/__tests__/formatter.test.js +11 -0
- package/dist/notifications/__tests__/formatter.test.js.map +1 -1
- package/dist/notifications/__tests__/idle-cooldown.test.js +32 -1
- package/dist/notifications/__tests__/idle-cooldown.test.js.map +1 -1
- package/dist/notifications/__tests__/index.test.d.ts +2 -0
- package/dist/notifications/__tests__/index.test.d.ts.map +1 -0
- package/dist/notifications/__tests__/index.test.js +113 -0
- package/dist/notifications/__tests__/index.test.js.map +1 -0
- package/dist/notifications/__tests__/lifecycle-dedupe.test.d.ts +2 -0
- package/dist/notifications/__tests__/lifecycle-dedupe.test.d.ts.map +1 -0
- package/dist/notifications/__tests__/lifecycle-dedupe.test.js +86 -0
- package/dist/notifications/__tests__/lifecycle-dedupe.test.js.map +1 -0
- package/dist/notifications/__tests__/reply-listener.test.js +174 -0
- package/dist/notifications/__tests__/reply-listener.test.js.map +1 -1
- package/dist/notifications/__tests__/session-idle-tail-dedupe.test.d.ts +2 -0
- package/dist/notifications/__tests__/session-idle-tail-dedupe.test.d.ts.map +1 -0
- package/dist/notifications/__tests__/session-idle-tail-dedupe.test.js +93 -0
- package/dist/notifications/__tests__/session-idle-tail-dedupe.test.js.map +1 -0
- package/dist/notifications/__tests__/session-registry.test.js +48 -1
- package/dist/notifications/__tests__/session-registry.test.js.map +1 -1
- package/dist/notifications/__tests__/session-status.test.d.ts +2 -0
- package/dist/notifications/__tests__/session-status.test.d.ts.map +1 -0
- package/dist/notifications/__tests__/session-status.test.js +159 -0
- package/dist/notifications/__tests__/session-status.test.js.map +1 -0
- package/dist/notifications/__tests__/tmux.test.js +58 -1
- package/dist/notifications/__tests__/tmux.test.js.map +1 -1
- package/dist/notifications/idle-cooldown.d.ts +11 -0
- package/dist/notifications/idle-cooldown.d.ts.map +1 -1
- package/dist/notifications/idle-cooldown.js +42 -8
- package/dist/notifications/idle-cooldown.js.map +1 -1
- package/dist/notifications/index.d.ts +1 -1
- package/dist/notifications/index.d.ts.map +1 -1
- package/dist/notifications/index.js +41 -8
- package/dist/notifications/index.js.map +1 -1
- package/dist/notifications/lifecycle-dedupe.d.ts +8 -0
- package/dist/notifications/lifecycle-dedupe.d.ts.map +1 -0
- package/dist/notifications/lifecycle-dedupe.js +112 -0
- package/dist/notifications/lifecycle-dedupe.js.map +1 -0
- package/dist/notifications/reply-listener.d.ts +10 -1
- package/dist/notifications/reply-listener.d.ts.map +1 -1
- package/dist/notifications/reply-listener.js +49 -11
- package/dist/notifications/reply-listener.js.map +1 -1
- package/dist/notifications/session-registry.d.ts.map +1 -1
- package/dist/notifications/session-registry.js +7 -1
- package/dist/notifications/session-registry.js.map +1 -1
- package/dist/notifications/session-status.d.ts +23 -0
- package/dist/notifications/session-status.d.ts.map +1 -0
- package/dist/notifications/session-status.js +187 -0
- package/dist/notifications/session-status.js.map +1 -0
- package/dist/notifications/tmux.d.ts +10 -0
- package/dist/notifications/tmux.d.ts.map +1 -1
- package/dist/notifications/tmux.js +59 -5
- package/dist/notifications/tmux.js.map +1 -1
- package/dist/notifications/types.d.ts +2 -0
- package/dist/notifications/types.d.ts.map +1 -1
- package/dist/openclaw/__tests__/index.test.js +84 -0
- package/dist/openclaw/__tests__/index.test.js.map +1 -1
- package/dist/openclaw/index.d.ts.map +1 -1
- package/dist/openclaw/index.js +7 -14
- package/dist/openclaw/index.js.map +1 -1
- package/dist/openclaw/types.d.ts +2 -2
- package/dist/openclaw/types.d.ts.map +1 -1
- package/dist/scripts/__tests__/codex-native-hook.test.js +692 -40
- package/dist/scripts/__tests__/codex-native-hook.test.js.map +1 -1
- package/dist/scripts/__tests__/hook-derived-watcher.test.d.ts +2 -0
- package/dist/scripts/__tests__/hook-derived-watcher.test.d.ts.map +1 -0
- package/dist/scripts/__tests__/hook-derived-watcher.test.js +87 -0
- package/dist/scripts/__tests__/hook-derived-watcher.test.js.map +1 -0
- package/dist/scripts/codex-native-hook.d.ts.map +1 -1
- package/dist/scripts/codex-native-hook.js +309 -77
- package/dist/scripts/codex-native-hook.js.map +1 -1
- package/dist/scripts/hook-derived-watcher.js +43 -1
- package/dist/scripts/hook-derived-watcher.js.map +1 -1
- package/dist/scripts/notify-fallback-watcher.js +95 -21
- package/dist/scripts/notify-fallback-watcher.js.map +1 -1
- package/dist/scripts/notify-hook/active-team.d.ts +9 -0
- package/dist/scripts/notify-hook/active-team.d.ts.map +1 -0
- package/dist/scripts/notify-hook/active-team.js +44 -0
- package/dist/scripts/notify-hook/active-team.js.map +1 -0
- package/dist/scripts/notify-hook/auto-nudge.d.ts +5 -3
- package/dist/scripts/notify-hook/auto-nudge.d.ts.map +1 -1
- package/dist/scripts/notify-hook/auto-nudge.js +121 -78
- package/dist/scripts/notify-hook/auto-nudge.js.map +1 -1
- package/dist/scripts/notify-hook/managed-tmux.d.ts.map +1 -1
- package/dist/scripts/notify-hook/managed-tmux.js +18 -4
- package/dist/scripts/notify-hook/managed-tmux.js.map +1 -1
- package/dist/scripts/notify-hook/operational-events.d.ts.map +1 -1
- package/dist/scripts/notify-hook/operational-events.js +21 -0
- package/dist/scripts/notify-hook/operational-events.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 -2
- package/dist/scripts/notify-hook/ralph-session-resume.js.map +1 -1
- package/dist/scripts/notify-hook/state-io.d.ts +10 -1
- package/dist/scripts/notify-hook/state-io.d.ts.map +1 -1
- package/dist/scripts/notify-hook/state-io.js +56 -12
- package/dist/scripts/notify-hook/state-io.js.map +1 -1
- package/dist/scripts/notify-hook/team-dispatch.d.ts.map +1 -1
- package/dist/scripts/notify-hook/team-dispatch.js +305 -167
- package/dist/scripts/notify-hook/team-dispatch.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 +87 -15
- package/dist/scripts/notify-hook/team-leader-nudge.js.map +1 -1
- package/dist/scripts/notify-hook/tmux-injection.d.ts.map +1 -1
- package/dist/scripts/notify-hook/tmux-injection.js +11 -2
- package/dist/scripts/notify-hook/tmux-injection.js.map +1 -1
- package/dist/scripts/notify-hook.js +26 -16
- package/dist/scripts/notify-hook.js.map +1 -1
- package/dist/scripts/run-provider-advisor.js +20 -2
- package/dist/scripts/run-provider-advisor.js.map +1 -1
- package/dist/scripts/smoke-packed-install.d.ts +1 -8
- package/dist/scripts/smoke-packed-install.d.ts.map +1 -1
- package/dist/scripts/smoke-packed-install.js +12 -68
- package/dist/scripts/smoke-packed-install.js.map +1 -1
- package/dist/state/__tests__/operations.test.js +113 -0
- package/dist/state/__tests__/operations.test.js.map +1 -1
- package/dist/state/__tests__/skill-active.test.js +35 -0
- package/dist/state/__tests__/skill-active.test.js.map +1 -1
- package/dist/state/__tests__/workflow-transition.test.d.ts +2 -0
- package/dist/state/__tests__/workflow-transition.test.d.ts.map +1 -0
- package/dist/state/__tests__/workflow-transition.test.js +56 -0
- package/dist/state/__tests__/workflow-transition.test.js.map +1 -0
- package/dist/state/operations.d.ts +1 -1
- package/dist/state/operations.d.ts.map +1 -1
- package/dist/state/operations.js +88 -2
- package/dist/state/operations.js.map +1 -1
- package/dist/state/skill-active.d.ts +2 -2
- package/dist/state/skill-active.d.ts.map +1 -1
- package/dist/state/skill-active.js +119 -33
- package/dist/state/skill-active.js.map +1 -1
- package/dist/state/workflow-transition-reconcile.d.ts +15 -0
- package/dist/state/workflow-transition-reconcile.d.ts.map +1 -0
- package/dist/state/workflow-transition-reconcile.js +100 -0
- package/dist/state/workflow-transition-reconcile.js.map +1 -0
- package/dist/state/workflow-transition.d.ts +22 -0
- package/dist/state/workflow-transition.d.ts.map +1 -0
- package/dist/state/workflow-transition.js +188 -0
- package/dist/state/workflow-transition.js.map +1 -0
- package/dist/team/__tests__/api-interop.test.js +90 -0
- package/dist/team/__tests__/api-interop.test.js.map +1 -1
- package/dist/team/__tests__/current-task-baseline.test.d.ts +2 -0
- package/dist/team/__tests__/current-task-baseline.test.d.ts.map +1 -0
- package/dist/team/__tests__/current-task-baseline.test.js +87 -0
- package/dist/team/__tests__/current-task-baseline.test.js.map +1 -0
- package/dist/team/__tests__/hardening-e2e.test.js +17 -0
- package/dist/team/__tests__/hardening-e2e.test.js.map +1 -1
- package/dist/team/__tests__/runtime.test.js +673 -65
- package/dist/team/__tests__/runtime.test.js.map +1 -1
- package/dist/team/__tests__/shutdown-fallback.test.js +11 -1
- package/dist/team/__tests__/shutdown-fallback.test.js.map +1 -1
- package/dist/team/__tests__/tmux-session.test.js +447 -4
- package/dist/team/__tests__/tmux-session.test.js.map +1 -1
- package/dist/team/api-interop.d.ts.map +1 -1
- package/dist/team/api-interop.js +10 -1
- package/dist/team/api-interop.js.map +1 -1
- package/dist/team/current-task-baseline.d.ts +32 -0
- package/dist/team/current-task-baseline.d.ts.map +1 -0
- package/dist/team/current-task-baseline.js +85 -0
- package/dist/team/current-task-baseline.js.map +1 -0
- package/dist/team/delivery-log.d.ts +1 -1
- package/dist/team/delivery-log.d.ts.map +1 -1
- package/dist/team/delivery-log.js.map +1 -1
- package/dist/team/leader-activity.d.ts +1 -0
- package/dist/team/leader-activity.d.ts.map +1 -1
- package/dist/team/leader-activity.js +4 -2
- package/dist/team/leader-activity.js.map +1 -1
- package/dist/team/progress-evidence.d.ts +2 -0
- package/dist/team/progress-evidence.d.ts.map +1 -0
- package/dist/team/progress-evidence.js +77 -0
- package/dist/team/progress-evidence.js.map +1 -0
- package/dist/team/runtime.d.ts.map +1 -1
- package/dist/team/runtime.js +269 -64
- package/dist/team/runtime.js.map +1 -1
- package/dist/team/scaling.d.ts.map +1 -1
- package/dist/team/scaling.js +1 -1
- package/dist/team/scaling.js.map +1 -1
- package/dist/team/state.d.ts.map +1 -1
- package/dist/team/state.js +2 -13
- package/dist/team/state.js.map +1 -1
- package/dist/team/tmux-session.d.ts +12 -3
- package/dist/team/tmux-session.d.ts.map +1 -1
- package/dist/team/tmux-session.js +174 -20
- package/dist/team/tmux-session.js.map +1 -1
- package/dist/team/worktree.d.ts +6 -1
- package/dist/team/worktree.d.ts.map +1 -1
- package/dist/team/worktree.js +28 -4
- package/dist/team/worktree.js.map +1 -1
- package/dist/utils/__tests__/agents-md.test.js +21 -1
- package/dist/utils/__tests__/agents-md.test.js.map +1 -1
- package/dist/utils/__tests__/repo-deps.test.d.ts +2 -0
- package/dist/utils/__tests__/repo-deps.test.d.ts.map +1 -0
- package/dist/utils/__tests__/repo-deps.test.js +71 -0
- package/dist/utils/__tests__/repo-deps.test.js.map +1 -0
- package/dist/utils/agents-md.d.ts +1 -0
- package/dist/utils/agents-md.d.ts.map +1 -1
- package/dist/utils/agents-md.js +7 -3
- package/dist/utils/agents-md.js.map +1 -1
- package/dist/utils/paths.d.ts +4 -0
- package/dist/utils/paths.d.ts.map +1 -1
- package/dist/utils/paths.js +20 -0
- package/dist/utils/paths.js.map +1 -1
- package/dist/utils/repo-deps.d.ts +20 -0
- package/dist/utils/repo-deps.d.ts.map +1 -0
- package/dist/utils/repo-deps.js +78 -0
- package/dist/utils/repo-deps.js.map +1 -0
- package/dist/verification/__tests__/dev-merge-issue-close-workflow.test.d.ts +2 -0
- package/dist/verification/__tests__/dev-merge-issue-close-workflow.test.d.ts.map +1 -0
- package/dist/verification/__tests__/dev-merge-issue-close-workflow.test.js +54 -0
- package/dist/verification/__tests__/dev-merge-issue-close-workflow.test.js.map +1 -0
- package/dist/wiki/__tests__/cjk-tokenize.test.d.ts +12 -0
- package/dist/wiki/__tests__/cjk-tokenize.test.d.ts.map +1 -0
- package/dist/wiki/__tests__/cjk-tokenize.test.js +139 -0
- package/dist/wiki/__tests__/cjk-tokenize.test.js.map +1 -0
- package/dist/wiki/__tests__/crlf-parse.test.d.ts +2 -0
- package/dist/wiki/__tests__/crlf-parse.test.d.ts.map +1 -0
- package/dist/wiki/__tests__/crlf-parse.test.js +24 -0
- package/dist/wiki/__tests__/crlf-parse.test.js.map +1 -0
- package/dist/wiki/__tests__/escape-newline.test.d.ts +2 -0
- package/dist/wiki/__tests__/escape-newline.test.d.ts.map +1 -0
- package/dist/wiki/__tests__/escape-newline.test.js +45 -0
- package/dist/wiki/__tests__/escape-newline.test.js.map +1 -0
- package/dist/wiki/__tests__/ingest.test.d.ts +5 -0
- package/dist/wiki/__tests__/ingest.test.d.ts.map +1 -0
- package/dist/wiki/__tests__/ingest.test.js +181 -0
- package/dist/wiki/__tests__/ingest.test.js.map +1 -0
- package/dist/wiki/__tests__/lint.test.d.ts +5 -0
- package/dist/wiki/__tests__/lint.test.d.ts.map +1 -0
- package/dist/wiki/__tests__/lint.test.js +163 -0
- package/dist/wiki/__tests__/lint.test.js.map +1 -0
- package/dist/wiki/__tests__/query.test.d.ts +5 -0
- package/dist/wiki/__tests__/query.test.d.ts.map +1 -0
- package/dist/wiki/__tests__/query.test.js +141 -0
- package/dist/wiki/__tests__/query.test.js.map +1 -0
- package/dist/wiki/__tests__/reserved-file-guard.test.d.ts +2 -0
- package/dist/wiki/__tests__/reserved-file-guard.test.d.ts.map +1 -0
- package/dist/wiki/__tests__/reserved-file-guard.test.js +44 -0
- package/dist/wiki/__tests__/reserved-file-guard.test.js.map +1 -0
- package/dist/wiki/__tests__/session-hooks.test.d.ts +5 -0
- package/dist/wiki/__tests__/session-hooks.test.d.ts.map +1 -0
- package/dist/wiki/__tests__/session-hooks.test.js +36 -0
- package/dist/wiki/__tests__/session-hooks.test.js.map +1 -0
- package/dist/wiki/__tests__/slug-nonascii.test.d.ts +2 -0
- package/dist/wiki/__tests__/slug-nonascii.test.d.ts.map +1 -0
- package/dist/wiki/__tests__/slug-nonascii.test.js +24 -0
- package/dist/wiki/__tests__/slug-nonascii.test.js.map +1 -0
- package/dist/wiki/__tests__/storage.test.d.ts +5 -0
- package/dist/wiki/__tests__/storage.test.d.ts.map +1 -0
- package/dist/wiki/__tests__/storage.test.js +278 -0
- package/dist/wiki/__tests__/storage.test.js.map +1 -0
- package/dist/wiki/__tests__/test-helpers.d.ts +31 -0
- package/dist/wiki/__tests__/test-helpers.d.ts.map +1 -0
- package/dist/wiki/__tests__/test-helpers.js +108 -0
- package/dist/wiki/__tests__/test-helpers.js.map +1 -0
- package/dist/wiki/index.d.ts +14 -0
- package/dist/wiki/index.d.ts.map +1 -0
- package/dist/wiki/index.js +17 -0
- package/dist/wiki/index.js.map +1 -0
- package/dist/wiki/ingest.d.ts +20 -0
- package/dist/wiki/ingest.d.ts.map +1 -0
- package/dist/wiki/ingest.js +115 -0
- package/dist/wiki/ingest.js.map +1 -0
- package/dist/wiki/lifecycle.d.ts +20 -0
- package/dist/wiki/lifecycle.d.ts.map +1 -0
- package/dist/wiki/lifecycle.js +212 -0
- package/dist/wiki/lifecycle.js.map +1 -0
- package/dist/wiki/lint.d.ts +25 -0
- package/dist/wiki/lint.d.ts.map +1 -0
- package/dist/wiki/lint.js +166 -0
- package/dist/wiki/lint.js.map +1 -0
- package/dist/wiki/query.d.ts +36 -0
- package/dist/wiki/query.d.ts.map +1 -0
- package/dist/wiki/query.js +138 -0
- package/dist/wiki/query.js.map +1 -0
- package/dist/wiki/storage.d.ts +33 -0
- package/dist/wiki/storage.d.ts.map +1 -0
- package/dist/wiki/storage.js +321 -0
- package/dist/wiki/storage.js.map +1 -0
- package/dist/wiki/types.d.ts +83 -0
- package/dist/wiki/types.d.ts.map +1 -0
- package/dist/wiki/types.js +15 -0
- package/dist/wiki/types.js.map +1 -0
- package/package.json +3 -1
- package/skills/configure-notifications/SKILL.md +1 -0
- package/skills/doctor/SKILL.md +11 -0
- package/skills/omx-setup/SKILL.md +1 -1
- package/skills/wiki/SKILL.md +57 -0
- package/src/scripts/__tests__/codex-native-hook.test.ts +920 -56
- package/src/scripts/__tests__/hook-derived-watcher.test.ts +111 -0
- package/src/scripts/codex-native-hook.ts +377 -83
- package/src/scripts/hook-derived-watcher.ts +43 -1
- package/src/scripts/notify-fallback-watcher.ts +99 -20
- package/src/scripts/notify-hook/active-team.ts +54 -0
- package/src/scripts/notify-hook/auto-nudge.ts +132 -79
- package/src/scripts/notify-hook/managed-tmux.ts +22 -4
- package/src/scripts/notify-hook/operational-events.ts +21 -0
- package/src/scripts/notify-hook/ralph-session-resume.ts +3 -2
- package/src/scripts/notify-hook/state-io.ts +89 -12
- package/src/scripts/notify-hook/team-dispatch.ts +326 -168
- package/src/scripts/notify-hook/team-leader-nudge.ts +91 -14
- package/src/scripts/notify-hook/tmux-injection.ts +11 -2
- package/src/scripts/notify-hook.ts +36 -22
- package/src/scripts/run-provider-advisor.ts +20 -2
- package/src/scripts/smoke-packed-install.ts +16 -83
- package/templates/AGENTS.md +3 -4
|
@@ -10,20 +10,64 @@ import {
|
|
|
10
10
|
initTeamState,
|
|
11
11
|
readTeamLeaderAttention,
|
|
12
12
|
readTeamPhase,
|
|
13
|
+
writeTeamLeaderAttention,
|
|
13
14
|
} from "../../team/state.js";
|
|
14
15
|
import {
|
|
15
16
|
dispatchCodexNativeHook,
|
|
16
17
|
mapCodexHookEventToOmxEvent,
|
|
17
18
|
resolveSessionOwnerPidFromAncestry,
|
|
18
19
|
} from "../codex-native-hook.js";
|
|
20
|
+
import { writeSessionStart } from "../../hooks/session.js";
|
|
19
21
|
|
|
20
22
|
async function writeJson(path: string, value: unknown): Promise<void> {
|
|
21
23
|
await mkdir(dirname(path), { recursive: true }).catch(() => {});
|
|
22
24
|
await writeFile(path, JSON.stringify(value, null, 2));
|
|
23
25
|
}
|
|
24
26
|
|
|
27
|
+
async function writeReleaseReadinessLeaderAttention(
|
|
28
|
+
teamName: string,
|
|
29
|
+
sessionId: string,
|
|
30
|
+
cwd: string,
|
|
31
|
+
options: { workRemaining: boolean },
|
|
32
|
+
): Promise<void> {
|
|
33
|
+
await writeTeamLeaderAttention(teamName, {
|
|
34
|
+
team_name: teamName,
|
|
35
|
+
updated_at: "2026-04-12T17:20:00.000Z",
|
|
36
|
+
source: "notify_hook",
|
|
37
|
+
leader_decision_state: "done_waiting_on_leader",
|
|
38
|
+
leader_attention_pending: true,
|
|
39
|
+
leader_attention_reason: "leader_session_stopped",
|
|
40
|
+
attention_reasons: ["leader_session_stopped"],
|
|
41
|
+
leader_stale: true,
|
|
42
|
+
leader_session_active: false,
|
|
43
|
+
leader_session_id: sessionId,
|
|
44
|
+
leader_session_stopped_at: "2026-04-12T17:20:00.000Z",
|
|
45
|
+
unread_leader_message_count: 0,
|
|
46
|
+
work_remaining: options.workRemaining,
|
|
47
|
+
stalled_for_ms: null,
|
|
48
|
+
}, cwd);
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
async function writeReleaseReadinessStateMarker(
|
|
52
|
+
sessionId: string,
|
|
53
|
+
teamName: string,
|
|
54
|
+
cwd: string,
|
|
55
|
+
): Promise<void> {
|
|
56
|
+
await writeJson(
|
|
57
|
+
join(cwd, ".omx", "state", "sessions", sessionId, "release-readiness-state.json"),
|
|
58
|
+
{
|
|
59
|
+
active: true,
|
|
60
|
+
session_id: sessionId,
|
|
61
|
+
team_name: teamName,
|
|
62
|
+
stable_final_recommendation_emitted: true,
|
|
63
|
+
},
|
|
64
|
+
);
|
|
65
|
+
}
|
|
66
|
+
|
|
25
67
|
const TEAM_STOP_COMMIT_GUIDANCE =
|
|
26
68
|
" If system-generated worker auto-checkpoint commits exist, rewrite them into Lore-format final commits before merge/finalization.";
|
|
69
|
+
const DEFAULT_AUTO_NUDGE_RESPONSE =
|
|
70
|
+
"continue with the current task only if it is already authorized";
|
|
27
71
|
|
|
28
72
|
const TEAM_ENV_KEYS = [
|
|
29
73
|
"OMX_TEAM_WORKER",
|
|
@@ -90,6 +134,36 @@ describe("codex native hook config", () => {
|
|
|
90
134
|
});
|
|
91
135
|
|
|
92
136
|
describe("codex native hook dispatch", () => {
|
|
137
|
+
it("emits deterministic JSON stdout when CLI stdin is malformed", () => {
|
|
138
|
+
const stdout = execFileSync(
|
|
139
|
+
process.execPath,
|
|
140
|
+
[join(process.cwd(), "dist", "scripts", "codex-native-hook.js")],
|
|
141
|
+
{
|
|
142
|
+
cwd: process.cwd(),
|
|
143
|
+
input: "{",
|
|
144
|
+
encoding: "utf-8",
|
|
145
|
+
stdio: ["pipe", "pipe", "pipe"],
|
|
146
|
+
},
|
|
147
|
+
);
|
|
148
|
+
|
|
149
|
+
const output = JSON.parse(stdout.trim()) as {
|
|
150
|
+
decision?: string;
|
|
151
|
+
reason?: string;
|
|
152
|
+
hookSpecificOutput?: { hookEventName?: string; additionalContext?: string };
|
|
153
|
+
};
|
|
154
|
+
|
|
155
|
+
assert.equal(output.decision, "block");
|
|
156
|
+
assert.equal(
|
|
157
|
+
output.reason,
|
|
158
|
+
"OMX native hook received malformed JSON input. Preserve runtime state and inspect the emitting hook payload before retrying.",
|
|
159
|
+
);
|
|
160
|
+
assert.equal(output.hookSpecificOutput?.hookEventName, "Unknown");
|
|
161
|
+
assert.match(
|
|
162
|
+
String(output.hookSpecificOutput?.additionalContext ?? ""),
|
|
163
|
+
/stdin JSON parsing failed inside codex-native-hook:/,
|
|
164
|
+
);
|
|
165
|
+
});
|
|
166
|
+
|
|
93
167
|
it("maps Codex events onto OMX logical surfaces", () => {
|
|
94
168
|
assert.equal(mapCodexHookEventToOmxEvent("SessionStart"), "session-start");
|
|
95
169
|
assert.equal(mapCodexHookEventToOmxEvent("UserPromptSubmit"), "keyword-detector");
|
|
@@ -123,14 +197,69 @@ describe("codex native hook dispatch", () => {
|
|
|
123
197
|
});
|
|
124
198
|
const sessionState = JSON.parse(
|
|
125
199
|
await readFile(join(cwd, ".omx", "state", "session.json"), "utf-8"),
|
|
126
|
-
) as { session_id?: string; pid?: number };
|
|
200
|
+
) as { session_id?: string; native_session_id?: string; pid?: number };
|
|
127
201
|
assert.equal(sessionState.session_id, "sess-start-1");
|
|
202
|
+
assert.equal(sessionState.native_session_id, "sess-start-1");
|
|
128
203
|
assert.equal(sessionState.pid, 43210);
|
|
129
204
|
} finally {
|
|
130
205
|
await rm(cwd, { recursive: true, force: true });
|
|
131
206
|
}
|
|
132
207
|
});
|
|
133
208
|
|
|
209
|
+
it("preserves canonical OMX session scope when native SessionStart arrives with a different id", async () => {
|
|
210
|
+
const cwd = await mkdtemp(join(tmpdir(), "omx-native-hook-session-reconcile-"));
|
|
211
|
+
try {
|
|
212
|
+
const stateDir = join(cwd, ".omx", "state");
|
|
213
|
+
const canonicalSessionId = "omx-launch-1";
|
|
214
|
+
const nativeSessionId = "codex-native-1";
|
|
215
|
+
await mkdir(join(stateDir, "sessions", canonicalSessionId), { recursive: true });
|
|
216
|
+
await writeSessionStart(cwd, canonicalSessionId);
|
|
217
|
+
await writeJson(join(stateDir, "sessions", canonicalSessionId, "hud-state.json"), {
|
|
218
|
+
last_turn_at: "2026-04-10T00:00:00.000Z",
|
|
219
|
+
turn_count: 1,
|
|
220
|
+
});
|
|
221
|
+
|
|
222
|
+
await dispatchCodexNativeHook(
|
|
223
|
+
{
|
|
224
|
+
hook_event_name: "SessionStart",
|
|
225
|
+
cwd,
|
|
226
|
+
session_id: nativeSessionId,
|
|
227
|
+
},
|
|
228
|
+
{
|
|
229
|
+
cwd,
|
|
230
|
+
sessionOwnerPid: process.pid,
|
|
231
|
+
},
|
|
232
|
+
);
|
|
233
|
+
|
|
234
|
+
const sessionState = JSON.parse(
|
|
235
|
+
await readFile(join(stateDir, "session.json"), "utf-8"),
|
|
236
|
+
) as { session_id?: string; native_session_id?: string; pid?: number };
|
|
237
|
+
assert.equal(sessionState.session_id, canonicalSessionId);
|
|
238
|
+
assert.equal(sessionState.native_session_id, nativeSessionId);
|
|
239
|
+
assert.equal(sessionState.pid, process.pid);
|
|
240
|
+
|
|
241
|
+
const promptResult = await dispatchCodexNativeHook(
|
|
242
|
+
{
|
|
243
|
+
hook_event_name: "UserPromptSubmit",
|
|
244
|
+
cwd,
|
|
245
|
+
session_id: nativeSessionId,
|
|
246
|
+
thread_id: "thread-1",
|
|
247
|
+
turn_id: "turn-1",
|
|
248
|
+
prompt: "$ralplan fix hud scope drift",
|
|
249
|
+
},
|
|
250
|
+
{ cwd },
|
|
251
|
+
);
|
|
252
|
+
|
|
253
|
+
assert.equal(promptResult.omxEventName, "keyword-detector");
|
|
254
|
+
assert.equal(existsSync(join(stateDir, "sessions", canonicalSessionId, "skill-active-state.json")), true);
|
|
255
|
+
assert.equal(existsSync(join(stateDir, "sessions", canonicalSessionId, "ralplan-state.json")), true);
|
|
256
|
+
assert.equal(existsSync(join(stateDir, "sessions", nativeSessionId, "skill-active-state.json")), false);
|
|
257
|
+
assert.equal(existsSync(join(stateDir, "sessions", nativeSessionId, "ralplan-state.json")), false);
|
|
258
|
+
} finally {
|
|
259
|
+
await rm(cwd, { recursive: true, force: true });
|
|
260
|
+
}
|
|
261
|
+
});
|
|
262
|
+
|
|
134
263
|
it("appends .omx/ to repo-root .gitignore during SessionStart when missing", async () => {
|
|
135
264
|
const cwd = await mkdtemp(join(tmpdir(), "omx-native-hook-session-gitignore-"));
|
|
136
265
|
try {
|
|
@@ -250,6 +379,52 @@ describe("codex native hook dispatch", () => {
|
|
|
250
379
|
}
|
|
251
380
|
});
|
|
252
381
|
|
|
382
|
+
it("does not expose submitted prompt text to keyword-detector hook plugins", async () => {
|
|
383
|
+
const cwd = await mkdtemp(join(tmpdir(), "omx-native-hook-prompt-sanitized-"));
|
|
384
|
+
try {
|
|
385
|
+
await mkdir(join(cwd, ".omx", "hooks"), { recursive: true });
|
|
386
|
+
await writeFile(
|
|
387
|
+
join(cwd, ".omx", "hooks", "capture-keyword-context.mjs"),
|
|
388
|
+
`import { mkdir, writeFile } from "node:fs/promises";
|
|
389
|
+
import { dirname, join } from "node:path";
|
|
390
|
+
|
|
391
|
+
export async function onHookEvent(event) {
|
|
392
|
+
if (event.event !== "keyword-detector") return;
|
|
393
|
+
const outPath = join(process.cwd(), ".omx", "captured-keyword-context.json");
|
|
394
|
+
await mkdir(dirname(outPath), { recursive: true });
|
|
395
|
+
await writeFile(outPath, JSON.stringify(event.context, null, 2));
|
|
396
|
+
}
|
|
397
|
+
`,
|
|
398
|
+
"utf-8",
|
|
399
|
+
);
|
|
400
|
+
|
|
401
|
+
await dispatchCodexNativeHook(
|
|
402
|
+
{
|
|
403
|
+
hook_event_name: "UserPromptSubmit",
|
|
404
|
+
cwd,
|
|
405
|
+
session_id: "sess-sanitized-1",
|
|
406
|
+
thread_id: "thread-sanitized-1",
|
|
407
|
+
turn_id: "turn-sanitized-1",
|
|
408
|
+
prompt: "$ralplan approve this blocker-sensitive request",
|
|
409
|
+
},
|
|
410
|
+
{ cwd },
|
|
411
|
+
);
|
|
412
|
+
|
|
413
|
+
const captured = JSON.parse(
|
|
414
|
+
await readFile(join(cwd, ".omx", "captured-keyword-context.json"), "utf-8"),
|
|
415
|
+
) as { prompt?: string; payload?: Record<string, unknown> };
|
|
416
|
+
|
|
417
|
+
assert.equal(captured.prompt, undefined);
|
|
418
|
+
assert.equal(captured.payload?.prompt, undefined);
|
|
419
|
+
assert.equal(captured.payload?.input, undefined);
|
|
420
|
+
assert.equal(captured.payload?.user_prompt, undefined);
|
|
421
|
+
assert.equal(captured.payload?.userPrompt, undefined);
|
|
422
|
+
assert.equal(captured.payload?.text, undefined);
|
|
423
|
+
} finally {
|
|
424
|
+
await rm(cwd, { recursive: true, force: true });
|
|
425
|
+
}
|
|
426
|
+
});
|
|
427
|
+
|
|
253
428
|
it("does not emit UserPromptSubmit routing context for unknown $tokens", async () => {
|
|
254
429
|
const cwd = await mkdtemp(join(tmpdir(), "omx-native-hook-unknown-token-"));
|
|
255
430
|
try {
|
|
@@ -311,6 +486,121 @@ describe("codex native hook dispatch", () => {
|
|
|
311
486
|
}
|
|
312
487
|
});
|
|
313
488
|
|
|
489
|
+
it("returns actionable denial guidance for unsupported workflow overlaps on prompt submit", async () => {
|
|
490
|
+
const cwd = await mkdtemp(join(tmpdir(), "omx-native-hook-transition-deny-"));
|
|
491
|
+
try {
|
|
492
|
+
await mkdir(join(cwd, ".omx", "state"), { recursive: true });
|
|
493
|
+
await dispatchCodexNativeHook(
|
|
494
|
+
{
|
|
495
|
+
hook_event_name: "UserPromptSubmit",
|
|
496
|
+
cwd,
|
|
497
|
+
session_id: "sess-deny-1",
|
|
498
|
+
thread_id: "thread-deny-1",
|
|
499
|
+
turn_id: "turn-deny-1",
|
|
500
|
+
prompt: "$team ship this fix",
|
|
501
|
+
},
|
|
502
|
+
{ cwd },
|
|
503
|
+
);
|
|
504
|
+
|
|
505
|
+
const denied = await dispatchCodexNativeHook(
|
|
506
|
+
{
|
|
507
|
+
hook_event_name: "UserPromptSubmit",
|
|
508
|
+
cwd,
|
|
509
|
+
session_id: "sess-deny-1",
|
|
510
|
+
thread_id: "thread-deny-1",
|
|
511
|
+
turn_id: "turn-deny-2",
|
|
512
|
+
prompt: "$autopilot also run this",
|
|
513
|
+
},
|
|
514
|
+
{ cwd },
|
|
515
|
+
);
|
|
516
|
+
|
|
517
|
+
assert.match(JSON.stringify(denied.outputJson), /denied workflow keyword/i);
|
|
518
|
+
assert.match(JSON.stringify(denied.outputJson), /Unsupported workflow overlap: team \+ autopilot\./);
|
|
519
|
+
assert.match(JSON.stringify(denied.outputJson), /`omx state clear --mode <mode>`/);
|
|
520
|
+
assert.match(JSON.stringify(denied.outputJson), /`omx_state\.\*` MCP tools/);
|
|
521
|
+
assert.equal(
|
|
522
|
+
existsSync(join(cwd, ".omx", "state", "sessions", "sess-deny-1", "autopilot-state.json")),
|
|
523
|
+
false,
|
|
524
|
+
);
|
|
525
|
+
} finally {
|
|
526
|
+
await rm(cwd, { recursive: true, force: true });
|
|
527
|
+
}
|
|
528
|
+
});
|
|
529
|
+
|
|
530
|
+
it("surfaces transition success output for allowlisted prompt-submit handoffs", async () => {
|
|
531
|
+
const cwd = await mkdtemp(join(tmpdir(), "omx-native-hook-transition-success-"));
|
|
532
|
+
try {
|
|
533
|
+
const sessionDir = join(cwd, ".omx", "state", "sessions", "sess-handoff-1");
|
|
534
|
+
await mkdir(sessionDir, { recursive: true });
|
|
535
|
+
await writeJson(join(sessionDir, "deep-interview-state.json"), {
|
|
536
|
+
active: true,
|
|
537
|
+
mode: "deep-interview",
|
|
538
|
+
current_phase: "intent-first",
|
|
539
|
+
});
|
|
540
|
+
await writeJson(join(sessionDir, "skill-active-state.json"), {
|
|
541
|
+
active: true,
|
|
542
|
+
skill: "deep-interview",
|
|
543
|
+
phase: "planning",
|
|
544
|
+
session_id: "sess-handoff-1",
|
|
545
|
+
active_skills: [{ skill: "deep-interview", phase: "planning", active: true, session_id: "sess-handoff-1" }],
|
|
546
|
+
});
|
|
547
|
+
|
|
548
|
+
const result = await dispatchCodexNativeHook(
|
|
549
|
+
{
|
|
550
|
+
hook_event_name: "UserPromptSubmit",
|
|
551
|
+
cwd,
|
|
552
|
+
session_id: "sess-handoff-1",
|
|
553
|
+
thread_id: "thread-handoff-1",
|
|
554
|
+
turn_id: "turn-handoff-1",
|
|
555
|
+
prompt: "$ralplan implement the approved contract",
|
|
556
|
+
},
|
|
557
|
+
{ cwd },
|
|
558
|
+
);
|
|
559
|
+
|
|
560
|
+
assert.match(JSON.stringify(result.outputJson), /mode transiting: deep-interview -> ralplan/);
|
|
561
|
+
const completed = JSON.parse(await readFile(join(sessionDir, "deep-interview-state.json"), "utf-8")) as {
|
|
562
|
+
active?: boolean;
|
|
563
|
+
current_phase?: string;
|
|
564
|
+
};
|
|
565
|
+
assert.equal(completed.active, false);
|
|
566
|
+
assert.equal(completed.current_phase, "completed");
|
|
567
|
+
} finally {
|
|
568
|
+
await rm(cwd, { recursive: true, force: true });
|
|
569
|
+
}
|
|
570
|
+
});
|
|
571
|
+
|
|
572
|
+
it("keeps the planning skill active when planning and execution workflows are invoked together", async () => {
|
|
573
|
+
const cwd = await mkdtemp(join(tmpdir(), "omx-native-hook-planning-precedence-"));
|
|
574
|
+
try {
|
|
575
|
+
await mkdir(join(cwd, ".omx", "state"), { recursive: true });
|
|
576
|
+
|
|
577
|
+
const result = await dispatchCodexNativeHook(
|
|
578
|
+
{
|
|
579
|
+
hook_event_name: "UserPromptSubmit",
|
|
580
|
+
cwd,
|
|
581
|
+
session_id: "sess-multi-1",
|
|
582
|
+
thread_id: "thread-multi-1",
|
|
583
|
+
turn_id: "turn-multi-1",
|
|
584
|
+
prompt: "$ralplan $team $ralph ship this fix",
|
|
585
|
+
},
|
|
586
|
+
{ cwd },
|
|
587
|
+
);
|
|
588
|
+
|
|
589
|
+
const message = String(
|
|
590
|
+
(result.outputJson as { hookSpecificOutput?: { additionalContext?: string } })?.hookSpecificOutput?.additionalContext || '',
|
|
591
|
+
);
|
|
592
|
+
assert.match(message, /\$ralplan" -> ralplan/);
|
|
593
|
+
assert.match(message, /\$team" -> team/);
|
|
594
|
+
assert.match(message, /\$ralph" -> ralph/);
|
|
595
|
+
assert.doesNotMatch(message, /mode transiting:/);
|
|
596
|
+
assert.match(message, /planning preserved over simultaneous execution follow-up; deferred skills: team, ralph\./);
|
|
597
|
+
assert.match(message, /skill: ralplan activated and initial state initialized at \.omx\/state\/sessions\/sess-multi-1\/ralplan-state\.json; write subsequent updates via omx_state MCP\./);
|
|
598
|
+
assert.doesNotMatch(message, /Use the durable OMX team runtime via `omx team \.\.\.`/);
|
|
599
|
+
} finally {
|
|
600
|
+
await rm(cwd, { recursive: true, force: true });
|
|
601
|
+
}
|
|
602
|
+
});
|
|
603
|
+
|
|
314
604
|
it("runs prompt-submit HUD reconciliation as a best-effort tmux-only side effect", async () => {
|
|
315
605
|
const cwd = await mkdtemp(join(tmpdir(), "omx-native-hook-hud-reconcile-"));
|
|
316
606
|
const originalTmux = process.env.TMUX;
|
|
@@ -581,6 +871,70 @@ esac
|
|
|
581
871
|
}
|
|
582
872
|
});
|
|
583
873
|
|
|
874
|
+
it("marks canonical team state failed when native payload session ids differ during MCP transport death", async () => {
|
|
875
|
+
const cwd = await mkdtemp(join(tmpdir(), "omx-native-hook-team-native-transport-"));
|
|
876
|
+
const previousCwd = process.cwd();
|
|
877
|
+
const canonicalSessionId = "omx-canonical-session";
|
|
878
|
+
const nativeSessionId = "codex-native-session";
|
|
879
|
+
try {
|
|
880
|
+
process.chdir(cwd);
|
|
881
|
+
await writeSessionStart(cwd, canonicalSessionId);
|
|
882
|
+
const sessionPath = join(cwd, ".omx", "state", "session.json");
|
|
883
|
+
const sessionState = JSON.parse(
|
|
884
|
+
await readFile(sessionPath, "utf-8"),
|
|
885
|
+
) as { session_id?: string; native_session_id?: string };
|
|
886
|
+
await writeFile(
|
|
887
|
+
sessionPath,
|
|
888
|
+
JSON.stringify(
|
|
889
|
+
{
|
|
890
|
+
...sessionState,
|
|
891
|
+
native_session_id: nativeSessionId,
|
|
892
|
+
},
|
|
893
|
+
null,
|
|
894
|
+
2,
|
|
895
|
+
),
|
|
896
|
+
);
|
|
897
|
+
|
|
898
|
+
await initTeamState(
|
|
899
|
+
"transport-team",
|
|
900
|
+
"task",
|
|
901
|
+
"executor",
|
|
902
|
+
1,
|
|
903
|
+
cwd,
|
|
904
|
+
undefined,
|
|
905
|
+
{ ...process.env, OMX_SESSION_ID: canonicalSessionId },
|
|
906
|
+
);
|
|
907
|
+
await writeJson(join(cwd, ".omx", "state", "team-state.json"), {
|
|
908
|
+
active: true,
|
|
909
|
+
team_name: "transport-team",
|
|
910
|
+
current_phase: "team-exec",
|
|
911
|
+
});
|
|
912
|
+
|
|
913
|
+
await dispatchCodexNativeHook(
|
|
914
|
+
{
|
|
915
|
+
hook_event_name: "PostToolUse",
|
|
916
|
+
cwd,
|
|
917
|
+
session_id: nativeSessionId,
|
|
918
|
+
tool_name: "mcp__omx_state__state_write",
|
|
919
|
+
tool_use_id: "tool-mcp-transport-team-native",
|
|
920
|
+
tool_input: { mode: "team", active: true },
|
|
921
|
+
tool_response: "{\"error\":\"MCP transport closed\",\"details\":\"stdio pipe closed before response\"}",
|
|
922
|
+
},
|
|
923
|
+
{ cwd },
|
|
924
|
+
);
|
|
925
|
+
|
|
926
|
+
const phase = await readTeamPhase("transport-team", cwd);
|
|
927
|
+
const attention = await readTeamLeaderAttention("transport-team", cwd);
|
|
928
|
+
assert.equal(phase?.current_phase, "failed");
|
|
929
|
+
assert.equal(attention?.leader_attention_reason, "mcp_transport_dead");
|
|
930
|
+
assert.equal(attention?.leader_attention_pending, true);
|
|
931
|
+
assert.equal(attention?.leader_session_id, canonicalSessionId);
|
|
932
|
+
} finally {
|
|
933
|
+
process.chdir(previousCwd);
|
|
934
|
+
await rm(cwd, { recursive: true, force: true });
|
|
935
|
+
}
|
|
936
|
+
});
|
|
937
|
+
|
|
584
938
|
it("treats stderr-only informative non-zero output as reviewable instead of a generic failure", async () => {
|
|
585
939
|
const cwd = await mkdtemp(join(tmpdir(), "omx-native-hook-posttool-informative-stderr-"));
|
|
586
940
|
try {
|
|
@@ -1051,6 +1405,101 @@ esac
|
|
|
1051
1405
|
}
|
|
1052
1406
|
});
|
|
1053
1407
|
|
|
1408
|
+
it("emits one concise final decision summary and auto-finalize guidance when release-readiness already has a stable final recommendation and no active worker tasks", async () => {
|
|
1409
|
+
const cwd = await mkdtemp(join(tmpdir(), "omx-native-hook-stop-release-readiness-finalize-"));
|
|
1410
|
+
try {
|
|
1411
|
+
await initTeamState(
|
|
1412
|
+
"release-ready-team",
|
|
1413
|
+
"release readiness finalize",
|
|
1414
|
+
"executor",
|
|
1415
|
+
1,
|
|
1416
|
+
cwd,
|
|
1417
|
+
undefined,
|
|
1418
|
+
{ ...process.env, OMX_SESSION_ID: "sess-stop-release-ready" },
|
|
1419
|
+
);
|
|
1420
|
+
await writeReleaseReadinessLeaderAttention(
|
|
1421
|
+
"release-ready-team",
|
|
1422
|
+
"sess-stop-release-ready",
|
|
1423
|
+
cwd,
|
|
1424
|
+
{ workRemaining: false },
|
|
1425
|
+
);
|
|
1426
|
+
await writeReleaseReadinessStateMarker(
|
|
1427
|
+
"sess-stop-release-ready",
|
|
1428
|
+
"release-ready-team",
|
|
1429
|
+
cwd,
|
|
1430
|
+
);
|
|
1431
|
+
|
|
1432
|
+
const result = await dispatchCodexNativeHook(
|
|
1433
|
+
{
|
|
1434
|
+
hook_event_name: "Stop",
|
|
1435
|
+
cwd,
|
|
1436
|
+
session_id: "sess-stop-release-ready",
|
|
1437
|
+
thread_id: "thread-stop-release-ready",
|
|
1438
|
+
turn_id: "turn-stop-release-ready-1",
|
|
1439
|
+
mode: "release-readiness",
|
|
1440
|
+
last_assistant_message: "Launch-ready: yes",
|
|
1441
|
+
},
|
|
1442
|
+
{ cwd },
|
|
1443
|
+
);
|
|
1444
|
+
|
|
1445
|
+
assert.equal(result.omxEventName, "stop");
|
|
1446
|
+
assert.deepEqual(result.outputJson, {
|
|
1447
|
+
decision: "block",
|
|
1448
|
+
reason:
|
|
1449
|
+
'Stable final recommendation already reached with no active worker tasks. Emit exactly one concise final decision summary aligned to "Launch-ready: yes." with no filler or residual acknowledgements (for example "yes"), then stop.',
|
|
1450
|
+
stopReason: "release_readiness_auto_finalize",
|
|
1451
|
+
systemMessage:
|
|
1452
|
+
"OMX release-readiness detected a stable final recommendation with no active worker tasks; emit one concise final decision summary and finalize.",
|
|
1453
|
+
});
|
|
1454
|
+
} finally {
|
|
1455
|
+
await rm(cwd, { recursive: true, force: true });
|
|
1456
|
+
}
|
|
1457
|
+
});
|
|
1458
|
+
|
|
1459
|
+
it("does not auto-finalize non-release team stops that happen to contain a stable recommendation summary", async () => {
|
|
1460
|
+
const cwd = await mkdtemp(join(tmpdir(), "omx-native-hook-stop-non-release-readiness-control-"));
|
|
1461
|
+
try {
|
|
1462
|
+
await initTeamState(
|
|
1463
|
+
"general-review-team",
|
|
1464
|
+
"general team stop control",
|
|
1465
|
+
"executor",
|
|
1466
|
+
1,
|
|
1467
|
+
cwd,
|
|
1468
|
+
undefined,
|
|
1469
|
+
{ ...process.env, OMX_SESSION_ID: "sess-stop-general-review" },
|
|
1470
|
+
);
|
|
1471
|
+
await writeReleaseReadinessLeaderAttention(
|
|
1472
|
+
"general-review-team",
|
|
1473
|
+
"sess-stop-general-review",
|
|
1474
|
+
cwd,
|
|
1475
|
+
{ workRemaining: false },
|
|
1476
|
+
);
|
|
1477
|
+
|
|
1478
|
+
const result = await dispatchCodexNativeHook(
|
|
1479
|
+
{
|
|
1480
|
+
hook_event_name: "Stop",
|
|
1481
|
+
cwd,
|
|
1482
|
+
session_id: "sess-stop-general-review",
|
|
1483
|
+
thread_id: "thread-stop-general-review",
|
|
1484
|
+
turn_id: "turn-stop-general-review-1",
|
|
1485
|
+
last_assistant_message: "Launch-ready: yes",
|
|
1486
|
+
},
|
|
1487
|
+
{ cwd },
|
|
1488
|
+
);
|
|
1489
|
+
|
|
1490
|
+
assert.equal(result.omxEventName, "stop");
|
|
1491
|
+
assert.deepEqual(result.outputJson, {
|
|
1492
|
+
decision: "block",
|
|
1493
|
+
reason:
|
|
1494
|
+
`OMX team pipeline is still active (general-review-team) at phase team-exec; continue coordinating until the team reaches a terminal phase.${TEAM_STOP_COMMIT_GUIDANCE}`,
|
|
1495
|
+
stopReason: "team_team-exec",
|
|
1496
|
+
systemMessage: "OMX team pipeline is still active at phase team-exec.",
|
|
1497
|
+
});
|
|
1498
|
+
} finally {
|
|
1499
|
+
await rm(cwd, { recursive: true, force: true });
|
|
1500
|
+
}
|
|
1501
|
+
});
|
|
1502
|
+
|
|
1054
1503
|
it("re-fires canonical-team Stop output for a later fresh Stop reply when coarse mode state is missing", async () => {
|
|
1055
1504
|
const cwd = await mkdtemp(join(tmpdir(), "omx-native-hook-stop-team-canonical-refire-"));
|
|
1056
1505
|
try {
|
|
@@ -1265,27 +1714,31 @@ esac
|
|
|
1265
1714
|
}
|
|
1266
1715
|
});
|
|
1267
1716
|
|
|
1268
|
-
it("
|
|
1269
|
-
const cwd = await mkdtemp(join(tmpdir(), "omx-native-hook-stop-
|
|
1717
|
+
it("blocks Stop from session-scoped team mode when session.json points to another session", async () => {
|
|
1718
|
+
const cwd = await mkdtemp(join(tmpdir(), "omx-native-hook-stop-team-session-mismatch-"));
|
|
1270
1719
|
try {
|
|
1271
1720
|
const stateDir = join(cwd, ".omx", "state");
|
|
1272
|
-
await mkdir(join(stateDir, "sessions", "sess-
|
|
1273
|
-
await writeJson(join(stateDir, "session.json"), { session_id: "sess-
|
|
1274
|
-
await writeJson(join(stateDir, "sessions", "sess-
|
|
1721
|
+
await mkdir(join(stateDir, "sessions", "sess-live-team"), { recursive: true });
|
|
1722
|
+
await writeJson(join(stateDir, "session.json"), { session_id: "sess-other-team" });
|
|
1723
|
+
await writeJson(join(stateDir, "sessions", "sess-live-team", "team-state.json"), {
|
|
1275
1724
|
active: true,
|
|
1276
|
-
|
|
1277
|
-
|
|
1725
|
+
mode: "team",
|
|
1726
|
+
current_phase: "team-exec",
|
|
1727
|
+
team_name: "session-live-team",
|
|
1278
1728
|
});
|
|
1279
|
-
await writeJson(join(stateDir, "
|
|
1280
|
-
|
|
1281
|
-
|
|
1729
|
+
await writeJson(join(stateDir, "team", "session-live-team", "phase.json"), {
|
|
1730
|
+
current_phase: "team-exec",
|
|
1731
|
+
max_fix_attempts: 3,
|
|
1732
|
+
current_fix_attempt: 0,
|
|
1733
|
+
transitions: [],
|
|
1734
|
+
updated_at: new Date().toISOString(),
|
|
1282
1735
|
});
|
|
1283
1736
|
|
|
1284
1737
|
const result = await dispatchCodexNativeHook(
|
|
1285
1738
|
{
|
|
1286
1739
|
hook_event_name: "Stop",
|
|
1287
1740
|
cwd,
|
|
1288
|
-
session_id: "sess-
|
|
1741
|
+
session_id: "sess-live-team",
|
|
1289
1742
|
},
|
|
1290
1743
|
{ cwd },
|
|
1291
1744
|
);
|
|
@@ -1294,9 +1747,47 @@ esac
|
|
|
1294
1747
|
assert.deepEqual(result.outputJson, {
|
|
1295
1748
|
decision: "block",
|
|
1296
1749
|
reason:
|
|
1297
|
-
|
|
1298
|
-
stopReason: "
|
|
1299
|
-
systemMessage: "OMX
|
|
1750
|
+
`OMX team pipeline is still active (session-live-team) at phase team-exec; continue coordinating until the team reaches a terminal phase.${TEAM_STOP_COMMIT_GUIDANCE}`,
|
|
1751
|
+
stopReason: "team_team-exec",
|
|
1752
|
+
systemMessage: "OMX team pipeline is still active at phase team-exec.",
|
|
1753
|
+
});
|
|
1754
|
+
} finally {
|
|
1755
|
+
await rm(cwd, { recursive: true, force: true });
|
|
1756
|
+
}
|
|
1757
|
+
});
|
|
1758
|
+
|
|
1759
|
+
it("returns Stop continuation output for active ralplan skill with matching active mode state and without active subagents", async () => {
|
|
1760
|
+
const cwd = await mkdtemp(join(tmpdir(), "omx-native-hook-stop-skill-"));
|
|
1761
|
+
try {
|
|
1762
|
+
const stateDir = join(cwd, ".omx", "state");
|
|
1763
|
+
await mkdir(join(stateDir, "sessions", "sess-stop-skill"), { recursive: true });
|
|
1764
|
+
await writeJson(join(stateDir, "session.json"), { session_id: "sess-stop-skill" });
|
|
1765
|
+
await writeJson(join(stateDir, "sessions", "sess-stop-skill", "skill-active-state.json"), {
|
|
1766
|
+
active: true,
|
|
1767
|
+
skill: "ralplan",
|
|
1768
|
+
phase: "planning",
|
|
1769
|
+
});
|
|
1770
|
+
await writeJson(join(stateDir, "sessions", "sess-stop-skill", "ralplan-state.json"), {
|
|
1771
|
+
active: true,
|
|
1772
|
+
current_phase: "planning",
|
|
1773
|
+
});
|
|
1774
|
+
|
|
1775
|
+
const result = await dispatchCodexNativeHook(
|
|
1776
|
+
{
|
|
1777
|
+
hook_event_name: "Stop",
|
|
1778
|
+
cwd,
|
|
1779
|
+
session_id: "sess-stop-skill",
|
|
1780
|
+
},
|
|
1781
|
+
{ cwd },
|
|
1782
|
+
);
|
|
1783
|
+
|
|
1784
|
+
assert.equal(result.omxEventName, "stop");
|
|
1785
|
+
assert.deepEqual(result.outputJson, {
|
|
1786
|
+
decision: "block",
|
|
1787
|
+
reason:
|
|
1788
|
+
"OMX skill ralplan is still active (phase: planning); continue until the current ralplan workflow reaches a terminal state.",
|
|
1789
|
+
stopReason: "skill_ralplan_planning",
|
|
1790
|
+
systemMessage: "OMX skill ralplan is still active (phase: planning).",
|
|
1300
1791
|
});
|
|
1301
1792
|
} finally {
|
|
1302
1793
|
await rm(cwd, { recursive: true, force: true });
|
|
@@ -1424,7 +1915,7 @@ esac
|
|
|
1424
1915
|
}
|
|
1425
1916
|
});
|
|
1426
1917
|
|
|
1427
|
-
it("
|
|
1918
|
+
it("does not block Stop solely because deep-interview is active", async () => {
|
|
1428
1919
|
const cwd = await mkdtemp(join(tmpdir(), "omx-native-hook-stop-deep-interview-"));
|
|
1429
1920
|
try {
|
|
1430
1921
|
const stateDir = join(cwd, ".omx", "state");
|
|
@@ -1449,13 +1940,7 @@ esac
|
|
|
1449
1940
|
{ cwd },
|
|
1450
1941
|
);
|
|
1451
1942
|
|
|
1452
|
-
assert.
|
|
1453
|
-
decision: "block",
|
|
1454
|
-
reason:
|
|
1455
|
-
"OMX skill deep-interview is still active (phase: planning); continue until the current deep-interview workflow reaches a terminal state.",
|
|
1456
|
-
stopReason: "skill_deep-interview_planning",
|
|
1457
|
-
systemMessage: "OMX skill deep-interview is still active (phase: planning).",
|
|
1458
|
-
});
|
|
1943
|
+
assert.equal(result.outputJson, null);
|
|
1459
1944
|
} finally {
|
|
1460
1945
|
await rm(cwd, { recursive: true, force: true });
|
|
1461
1946
|
}
|
|
@@ -1490,7 +1975,7 @@ esac
|
|
|
1490
1975
|
}
|
|
1491
1976
|
});
|
|
1492
1977
|
|
|
1493
|
-
it("returns Stop continuation output while Ralph is active", async () => {
|
|
1978
|
+
it("returns Stop continuation output while Ralph is active without an explicit session pin", async () => {
|
|
1494
1979
|
const cwd = await mkdtemp(join(tmpdir(), "omx-native-hook-stop-"));
|
|
1495
1980
|
try {
|
|
1496
1981
|
const stateDir = join(cwd, ".omx", "state");
|
|
@@ -1507,7 +1992,41 @@ esac
|
|
|
1507
1992
|
{
|
|
1508
1993
|
hook_event_name: "Stop",
|
|
1509
1994
|
cwd,
|
|
1510
|
-
|
|
1995
|
+
},
|
|
1996
|
+
{ cwd },
|
|
1997
|
+
);
|
|
1998
|
+
|
|
1999
|
+
assert.equal(result.omxEventName, "stop");
|
|
2000
|
+
assert.deepEqual(result.outputJson, {
|
|
2001
|
+
decision: "block",
|
|
2002
|
+
reason:
|
|
2003
|
+
"OMX Ralph is still active (phase: executing); continue the task and gather fresh verification evidence before stopping.",
|
|
2004
|
+
stopReason: "ralph_executing",
|
|
2005
|
+
systemMessage:
|
|
2006
|
+
"OMX Ralph is still active (phase: executing); continue the task and gather fresh verification evidence before stopping.",
|
|
2007
|
+
});
|
|
2008
|
+
} finally {
|
|
2009
|
+
await rm(cwd, { recursive: true, force: true });
|
|
2010
|
+
}
|
|
2011
|
+
});
|
|
2012
|
+
|
|
2013
|
+
it("blocks Stop from session-scoped Ralph state when session.json points to another session", async () => {
|
|
2014
|
+
const cwd = await mkdtemp(join(tmpdir(), "omx-native-hook-stop-ralph-session-mismatch-"));
|
|
2015
|
+
try {
|
|
2016
|
+
const stateDir = join(cwd, ".omx", "state");
|
|
2017
|
+
await mkdir(join(stateDir, "sessions", "sess-live-ralph"), { recursive: true });
|
|
2018
|
+
await writeJson(join(stateDir, "session.json"), { session_id: "sess-other-ralph" });
|
|
2019
|
+
await writeJson(join(stateDir, "sessions", "sess-live-ralph", "ralph-state.json"), {
|
|
2020
|
+
active: true,
|
|
2021
|
+
current_phase: "executing",
|
|
2022
|
+
session_id: "sess-live-ralph",
|
|
2023
|
+
});
|
|
2024
|
+
|
|
2025
|
+
const result = await dispatchCodexNativeHook(
|
|
2026
|
+
{
|
|
2027
|
+
hook_event_name: "Stop",
|
|
2028
|
+
cwd,
|
|
2029
|
+
session_id: "sess-live-ralph",
|
|
1511
2030
|
},
|
|
1512
2031
|
{ cwd },
|
|
1513
2032
|
);
|
|
@@ -1555,6 +2074,123 @@ esac
|
|
|
1555
2074
|
}
|
|
1556
2075
|
});
|
|
1557
2076
|
|
|
2077
|
+
it("does not block Stop from another session-scoped Ralph state when an explicit session_id has no active Ralph state", async () => {
|
|
2078
|
+
const cwd = await mkdtemp(join(tmpdir(), "omx-native-hook-stop-explicit-session-ralph-"));
|
|
2079
|
+
try {
|
|
2080
|
+
const stateDir = join(cwd, ".omx", "state");
|
|
2081
|
+
await mkdir(join(stateDir, "sessions", "sess-other"), { recursive: true });
|
|
2082
|
+
await writeJson(join(stateDir, "sessions", "sess-other", "ralph-state.json"), {
|
|
2083
|
+
active: true,
|
|
2084
|
+
current_phase: "starting",
|
|
2085
|
+
session_id: "sess-other",
|
|
2086
|
+
});
|
|
2087
|
+
|
|
2088
|
+
const result = await dispatchCodexNativeHook(
|
|
2089
|
+
{
|
|
2090
|
+
hook_event_name: "Stop",
|
|
2091
|
+
cwd,
|
|
2092
|
+
session_id: "sess-current",
|
|
2093
|
+
},
|
|
2094
|
+
{ cwd },
|
|
2095
|
+
);
|
|
2096
|
+
|
|
2097
|
+
assert.equal(result.omxEventName, "stop");
|
|
2098
|
+
assert.equal(result.outputJson, null);
|
|
2099
|
+
} finally {
|
|
2100
|
+
await rm(cwd, { recursive: true, force: true });
|
|
2101
|
+
}
|
|
2102
|
+
});
|
|
2103
|
+
|
|
2104
|
+
it("does not block Stop from root Ralph fallback when the current session has no scoped Ralph state", async () => {
|
|
2105
|
+
const cwd = await mkdtemp(join(tmpdir(), "omx-native-hook-stop-root-fallback-ralph-"));
|
|
2106
|
+
try {
|
|
2107
|
+
const stateDir = join(cwd, ".omx", "state");
|
|
2108
|
+
await mkdir(join(stateDir, "sessions", "sess-current"), { recursive: true });
|
|
2109
|
+
await writeJson(join(stateDir, "session.json"), { session_id: "sess-current", cwd });
|
|
2110
|
+
await writeJson(join(stateDir, "ralph-state.json"), {
|
|
2111
|
+
active: true,
|
|
2112
|
+
current_phase: "executing",
|
|
2113
|
+
});
|
|
2114
|
+
|
|
2115
|
+
const result = await dispatchCodexNativeHook(
|
|
2116
|
+
{
|
|
2117
|
+
hook_event_name: "Stop",
|
|
2118
|
+
cwd,
|
|
2119
|
+
session_id: "sess-current",
|
|
2120
|
+
},
|
|
2121
|
+
{ cwd },
|
|
2122
|
+
);
|
|
2123
|
+
|
|
2124
|
+
assert.equal(result.omxEventName, "stop");
|
|
2125
|
+
assert.equal(result.outputJson, null);
|
|
2126
|
+
} finally {
|
|
2127
|
+
await rm(cwd, { recursive: true, force: true });
|
|
2128
|
+
}
|
|
2129
|
+
});
|
|
2130
|
+
|
|
2131
|
+
it("does not block Stop when the current session Ralph state is cancelled even if stale root fallback remains", async () => {
|
|
2132
|
+
const cwd = await mkdtemp(join(tmpdir(), "omx-native-hook-stop-cancelled-session-ralph-"));
|
|
2133
|
+
try {
|
|
2134
|
+
const stateDir = join(cwd, ".omx", "state");
|
|
2135
|
+
await mkdir(join(stateDir, "sessions", "sess-current"), { recursive: true });
|
|
2136
|
+
await writeJson(join(stateDir, "session.json"), { session_id: "sess-current", cwd });
|
|
2137
|
+
await writeJson(join(stateDir, "sessions", "sess-current", "ralph-state.json"), {
|
|
2138
|
+
active: false,
|
|
2139
|
+
current_phase: "cancelled",
|
|
2140
|
+
completed_at: "2026-04-10T23:30:38.000Z",
|
|
2141
|
+
session_id: "sess-current",
|
|
2142
|
+
});
|
|
2143
|
+
await writeJson(join(stateDir, "ralph-state.json"), {
|
|
2144
|
+
active: true,
|
|
2145
|
+
current_phase: "starting",
|
|
2146
|
+
});
|
|
2147
|
+
|
|
2148
|
+
const result = await dispatchCodexNativeHook(
|
|
2149
|
+
{
|
|
2150
|
+
hook_event_name: "Stop",
|
|
2151
|
+
cwd,
|
|
2152
|
+
session_id: "sess-current",
|
|
2153
|
+
},
|
|
2154
|
+
{ cwd },
|
|
2155
|
+
);
|
|
2156
|
+
|
|
2157
|
+
assert.equal(result.omxEventName, "stop");
|
|
2158
|
+
assert.equal(result.outputJson, null);
|
|
2159
|
+
} finally {
|
|
2160
|
+
await rm(cwd, { recursive: true, force: true });
|
|
2161
|
+
}
|
|
2162
|
+
});
|
|
2163
|
+
|
|
2164
|
+
it("does not block Stop from root Ralph fallback when an explicit session_id is present and session.json points to another worktree", async () => {
|
|
2165
|
+
const cwd = await mkdtemp(join(tmpdir(), "omx-native-hook-stop-root-fallback-cwd-mismatch-"));
|
|
2166
|
+
try {
|
|
2167
|
+
const stateDir = join(cwd, ".omx", "state");
|
|
2168
|
+
await mkdir(stateDir, { recursive: true });
|
|
2169
|
+
await writeJson(join(stateDir, "session.json"), {
|
|
2170
|
+
session_id: "sess-elsewhere",
|
|
2171
|
+
cwd: join(cwd, "..", "different-worktree"),
|
|
2172
|
+
});
|
|
2173
|
+
await writeJson(join(stateDir, "ralph-state.json"), {
|
|
2174
|
+
active: true,
|
|
2175
|
+
current_phase: "executing",
|
|
2176
|
+
});
|
|
2177
|
+
|
|
2178
|
+
const result = await dispatchCodexNativeHook(
|
|
2179
|
+
{
|
|
2180
|
+
hook_event_name: "Stop",
|
|
2181
|
+
cwd,
|
|
2182
|
+
session_id: "sess-current",
|
|
2183
|
+
},
|
|
2184
|
+
{ cwd },
|
|
2185
|
+
);
|
|
2186
|
+
|
|
2187
|
+
assert.equal(result.omxEventName, "stop");
|
|
2188
|
+
assert.equal(result.outputJson, null);
|
|
2189
|
+
} finally {
|
|
2190
|
+
await rm(cwd, { recursive: true, force: true });
|
|
2191
|
+
}
|
|
2192
|
+
});
|
|
2193
|
+
|
|
1558
2194
|
it("does not re-block Ralph when Stop already continued once", async () => {
|
|
1559
2195
|
const cwd = await mkdtemp(join(tmpdir(), "omx-native-hook-stop-ralph-once-"));
|
|
1560
2196
|
try {
|
|
@@ -1596,7 +2232,7 @@ esac
|
|
|
1596
2232
|
hook_event_name: "Stop",
|
|
1597
2233
|
cwd,
|
|
1598
2234
|
session_id: "sess-stop-auto",
|
|
1599
|
-
last_assistant_message: "
|
|
2235
|
+
last_assistant_message: "Keep going and finish the cleanup.",
|
|
1600
2236
|
},
|
|
1601
2237
|
{ cwd },
|
|
1602
2238
|
);
|
|
@@ -1604,7 +2240,7 @@ esac
|
|
|
1604
2240
|
assert.equal(result.omxEventName, "stop");
|
|
1605
2241
|
assert.deepEqual(result.outputJson, {
|
|
1606
2242
|
decision: "block",
|
|
1607
|
-
reason:
|
|
2243
|
+
reason: DEFAULT_AUTO_NUDGE_RESPONSE,
|
|
1608
2244
|
stopReason: "auto_nudge",
|
|
1609
2245
|
systemMessage:
|
|
1610
2246
|
"OMX native Stop detected a stall/permission-style handoff and continued the turn automatically.",
|
|
@@ -1627,7 +2263,7 @@ esac
|
|
|
1627
2263
|
session_id: "sess-stop-auto-once",
|
|
1628
2264
|
thread_id: "thread-stop-auto",
|
|
1629
2265
|
turn_id: "turn-stop-auto-1",
|
|
1630
|
-
last_assistant_message: "
|
|
2266
|
+
last_assistant_message: "Keep going and finish the cleanup.",
|
|
1631
2267
|
},
|
|
1632
2268
|
{ cwd },
|
|
1633
2269
|
);
|
|
@@ -1640,7 +2276,7 @@ esac
|
|
|
1640
2276
|
thread_id: "thread-stop-auto",
|
|
1641
2277
|
turn_id: "turn-stop-auto-1",
|
|
1642
2278
|
stop_hook_active: true,
|
|
1643
|
-
last_assistant_message: "
|
|
2279
|
+
last_assistant_message: "Keep going and finish the cleanup.",
|
|
1644
2280
|
},
|
|
1645
2281
|
{ cwd },
|
|
1646
2282
|
);
|
|
@@ -1665,7 +2301,7 @@ esac
|
|
|
1665
2301
|
session_id: "sess-stop-auto-refire",
|
|
1666
2302
|
thread_id: "thread-stop-auto-refire",
|
|
1667
2303
|
turn_id: "turn-stop-auto-refire-1",
|
|
1668
|
-
last_assistant_message: "
|
|
2304
|
+
last_assistant_message: "Keep going and finish the cleanup.",
|
|
1669
2305
|
},
|
|
1670
2306
|
{ cwd },
|
|
1671
2307
|
);
|
|
@@ -1678,7 +2314,7 @@ esac
|
|
|
1678
2314
|
thread_id: "thread-stop-auto-refire",
|
|
1679
2315
|
turn_id: "turn-stop-auto-refire-2",
|
|
1680
2316
|
stop_hook_active: true,
|
|
1681
|
-
last_assistant_message: "
|
|
2317
|
+
last_assistant_message: "Continue with the cleanup from here.",
|
|
1682
2318
|
},
|
|
1683
2319
|
{ cwd },
|
|
1684
2320
|
);
|
|
@@ -1686,7 +2322,7 @@ esac
|
|
|
1686
2322
|
assert.equal(result.omxEventName, "stop");
|
|
1687
2323
|
assert.deepEqual(result.outputJson, {
|
|
1688
2324
|
decision: "block",
|
|
1689
|
-
reason:
|
|
2325
|
+
reason: DEFAULT_AUTO_NUDGE_RESPONSE,
|
|
1690
2326
|
stopReason: "auto_nudge",
|
|
1691
2327
|
systemMessage:
|
|
1692
2328
|
"OMX native Stop detected a stall/permission-style handoff and continued the turn automatically.",
|
|
@@ -1696,17 +2332,41 @@ esac
|
|
|
1696
2332
|
}
|
|
1697
2333
|
});
|
|
1698
2334
|
|
|
1699
|
-
it("
|
|
1700
|
-
const cwd = await mkdtemp(join(tmpdir(), "omx-native-hook-auto-nudge-
|
|
2335
|
+
it("does not auto-continue native Stop on permission-seeking prompts", async () => {
|
|
2336
|
+
const cwd = await mkdtemp(join(tmpdir(), "omx-native-hook-auto-nudge-permission-"));
|
|
2337
|
+
try {
|
|
2338
|
+
await mkdir(join(cwd, ".omx", "state"), { recursive: true });
|
|
2339
|
+
|
|
2340
|
+
const result = await dispatchCodexNativeHook(
|
|
2341
|
+
{
|
|
2342
|
+
hook_event_name: "Stop",
|
|
2343
|
+
cwd,
|
|
2344
|
+
session_id: "sess-stop-auto-permission",
|
|
2345
|
+
last_assistant_message: "Would you like me to continue with the cleanup?",
|
|
2346
|
+
},
|
|
2347
|
+
{ cwd },
|
|
2348
|
+
);
|
|
2349
|
+
|
|
2350
|
+
assert.equal(result.omxEventName, "stop");
|
|
2351
|
+
assert.equal(result.outputJson, null);
|
|
2352
|
+
} finally {
|
|
2353
|
+
await rm(cwd, { recursive: true, force: true });
|
|
2354
|
+
}
|
|
2355
|
+
});
|
|
2356
|
+
|
|
2357
|
+
it("does not auto-continue native Stop while deep-interview is waiting on an intent-first question", async () => {
|
|
2358
|
+
const cwd = await mkdtemp(join(tmpdir(), "omx-native-hook-auto-nudge-deep-interview-question-"));
|
|
1701
2359
|
try {
|
|
1702
2360
|
const stateDir = join(cwd, ".omx", "state");
|
|
1703
|
-
await mkdir(join(stateDir, "sessions", "sess-stop-auto-
|
|
1704
|
-
await writeJson(join(stateDir, "session.json"), { session_id: "sess-stop-auto-
|
|
1705
|
-
await writeJson(join(stateDir, "sessions", "sess-stop-auto-
|
|
2361
|
+
await mkdir(join(stateDir, "sessions", "sess-stop-auto-question"), { recursive: true });
|
|
2362
|
+
await writeJson(join(stateDir, "session.json"), { session_id: "sess-stop-auto-question" });
|
|
2363
|
+
await writeJson(join(stateDir, "sessions", "sess-stop-auto-question", "skill-active-state.json"), {
|
|
1706
2364
|
version: 1,
|
|
1707
2365
|
active: true,
|
|
1708
2366
|
skill: "deep-interview",
|
|
1709
2367
|
phase: "planning",
|
|
2368
|
+
session_id: "sess-stop-auto-question",
|
|
2369
|
+
thread_id: "thread-stop-auto-question",
|
|
1710
2370
|
input_lock: {
|
|
1711
2371
|
active: true,
|
|
1712
2372
|
scope: "deep-interview-auto-approval",
|
|
@@ -1714,31 +2374,31 @@ esac
|
|
|
1714
2374
|
message: "Deep interview is active; auto-approval shortcuts are blocked until the interview finishes.",
|
|
1715
2375
|
},
|
|
1716
2376
|
});
|
|
1717
|
-
await writeJson(join(stateDir, "sessions", "sess-stop-auto-
|
|
2377
|
+
await writeJson(join(stateDir, "sessions", "sess-stop-auto-question", "deep-interview-state.json"), {
|
|
1718
2378
|
active: true,
|
|
1719
|
-
|
|
2379
|
+
mode: "deep-interview",
|
|
2380
|
+
current_phase: "intent-first",
|
|
1720
2381
|
});
|
|
1721
2382
|
|
|
1722
2383
|
const result = await dispatchCodexNativeHook(
|
|
1723
2384
|
{
|
|
1724
2385
|
hook_event_name: "Stop",
|
|
1725
2386
|
cwd,
|
|
1726
|
-
session_id: "sess-stop-auto-
|
|
1727
|
-
thread_id: "thread-stop-auto-
|
|
1728
|
-
turn_id: "turn-stop-auto-
|
|
1729
|
-
last_assistant_message:
|
|
2387
|
+
session_id: "sess-stop-auto-question",
|
|
2388
|
+
thread_id: "thread-stop-auto-question",
|
|
2389
|
+
turn_id: "turn-stop-auto-question-1",
|
|
2390
|
+
last_assistant_message: [
|
|
2391
|
+
"Round 2 | Target: Decision boundary | Ambiguity: 24%",
|
|
2392
|
+
"",
|
|
2393
|
+
"If an existing project spider still declares session_mode = \"owned\", should ZenX fail loudly so the stale attribute is removed, or should it ignore the attribute and initialize the session pool anyway?",
|
|
2394
|
+
"Keep going once I have your answer.",
|
|
2395
|
+
].join("\n"),
|
|
1730
2396
|
},
|
|
1731
2397
|
{ cwd },
|
|
1732
2398
|
);
|
|
1733
2399
|
|
|
1734
2400
|
assert.equal(result.omxEventName, "stop");
|
|
1735
|
-
assert.
|
|
1736
|
-
decision: "block",
|
|
1737
|
-
reason:
|
|
1738
|
-
"OMX skill deep-interview is still active (phase: planning); continue until the current deep-interview workflow reaches a terminal state.",
|
|
1739
|
-
stopReason: "skill_deep-interview_planning",
|
|
1740
|
-
systemMessage: "OMX skill deep-interview is still active (phase: planning).",
|
|
1741
|
-
});
|
|
2401
|
+
assert.equal(result.outputJson, null);
|
|
1742
2402
|
} finally {
|
|
1743
2403
|
await rm(cwd, { recursive: true, force: true });
|
|
1744
2404
|
}
|
|
@@ -1822,7 +2482,7 @@ esac
|
|
|
1822
2482
|
session_id: "sess-stop-auto-stale-root-mode",
|
|
1823
2483
|
thread_id: "thread-stop-auto-stale-root-mode",
|
|
1824
2484
|
turn_id: "turn-stop-auto-stale-root-mode-1",
|
|
1825
|
-
last_assistant_message: "
|
|
2485
|
+
last_assistant_message: "Keep going and finish the cleanup.",
|
|
1826
2486
|
},
|
|
1827
2487
|
{ cwd },
|
|
1828
2488
|
);
|
|
@@ -1830,7 +2490,7 @@ esac
|
|
|
1830
2490
|
assert.equal(result.omxEventName, "stop");
|
|
1831
2491
|
assert.deepEqual(result.outputJson, {
|
|
1832
2492
|
decision: "block",
|
|
1833
|
-
reason:
|
|
2493
|
+
reason: DEFAULT_AUTO_NUDGE_RESPONSE,
|
|
1834
2494
|
stopReason: "auto_nudge",
|
|
1835
2495
|
systemMessage:
|
|
1836
2496
|
"OMX native Stop detected a stall/permission-style handoff and continued the turn automatically.",
|
|
@@ -1858,7 +2518,7 @@ esac
|
|
|
1858
2518
|
session_id: "sess-stop-auto-stale-root-skill",
|
|
1859
2519
|
thread_id: "thread-stop-auto-stale-root-skill",
|
|
1860
2520
|
turn_id: "turn-stop-auto-stale-root-skill-1",
|
|
1861
|
-
last_assistant_message: "
|
|
2521
|
+
last_assistant_message: "Keep going and finish the cleanup.",
|
|
1862
2522
|
},
|
|
1863
2523
|
{ cwd },
|
|
1864
2524
|
);
|
|
@@ -1866,7 +2526,7 @@ esac
|
|
|
1866
2526
|
assert.equal(result.omxEventName, "stop");
|
|
1867
2527
|
assert.deepEqual(result.outputJson, {
|
|
1868
2528
|
decision: "block",
|
|
1869
|
-
reason:
|
|
2529
|
+
reason: DEFAULT_AUTO_NUDGE_RESPONSE,
|
|
1870
2530
|
stopReason: "auto_nudge",
|
|
1871
2531
|
systemMessage:
|
|
1872
2532
|
"OMX native Stop detected a stall/permission-style handoff and continued the turn automatically.",
|
|
@@ -1900,7 +2560,49 @@ esac
|
|
|
1900
2560
|
session_id: "sess-stop-auto-stale-root-lock",
|
|
1901
2561
|
thread_id: "thread-stop-auto-stale-root-lock",
|
|
1902
2562
|
turn_id: "turn-stop-auto-stale-root-lock-1",
|
|
1903
|
-
last_assistant_message: "
|
|
2563
|
+
last_assistant_message: "Keep going and finish the cleanup.",
|
|
2564
|
+
},
|
|
2565
|
+
{ cwd },
|
|
2566
|
+
);
|
|
2567
|
+
|
|
2568
|
+
assert.equal(result.omxEventName, "stop");
|
|
2569
|
+
assert.deepEqual(result.outputJson, {
|
|
2570
|
+
decision: "block",
|
|
2571
|
+
reason: DEFAULT_AUTO_NUDGE_RESPONSE,
|
|
2572
|
+
stopReason: "auto_nudge",
|
|
2573
|
+
systemMessage:
|
|
2574
|
+
"OMX native Stop detected a stall/permission-style handoff and continued the turn automatically.",
|
|
2575
|
+
});
|
|
2576
|
+
} finally {
|
|
2577
|
+
await rm(cwd, { recursive: true, force: true });
|
|
2578
|
+
}
|
|
2579
|
+
});
|
|
2580
|
+
|
|
2581
|
+
it("does not suppress native auto-nudge from active root deep-interview state when the current scoped mode state is explicitly inactive", async () => {
|
|
2582
|
+
const cwd = await mkdtemp(join(tmpdir(), "omx-native-hook-auto-nudge-inactive-scoped-mode-"));
|
|
2583
|
+
try {
|
|
2584
|
+
const stateDir = join(cwd, ".omx", "state");
|
|
2585
|
+
await mkdir(join(stateDir, "sessions", "sess-stop-auto-inactive-mode"), { recursive: true });
|
|
2586
|
+
await writeJson(join(stateDir, "session.json"), { session_id: "sess-stop-auto-inactive-mode" });
|
|
2587
|
+
await writeJson(join(stateDir, "sessions", "sess-stop-auto-inactive-mode", "deep-interview-state.json"), {
|
|
2588
|
+
active: false,
|
|
2589
|
+
mode: "deep-interview",
|
|
2590
|
+
current_phase: "completed",
|
|
2591
|
+
});
|
|
2592
|
+
await writeJson(join(stateDir, "deep-interview-state.json"), {
|
|
2593
|
+
active: true,
|
|
2594
|
+
mode: "deep-interview",
|
|
2595
|
+
current_phase: "intent-first",
|
|
2596
|
+
});
|
|
2597
|
+
|
|
2598
|
+
const result = await dispatchCodexNativeHook(
|
|
2599
|
+
{
|
|
2600
|
+
hook_event_name: "Stop",
|
|
2601
|
+
cwd,
|
|
2602
|
+
session_id: "sess-stop-auto-inactive-mode",
|
|
2603
|
+
thread_id: "thread-stop-auto-inactive-mode",
|
|
2604
|
+
turn_id: "turn-stop-auto-inactive-mode-1",
|
|
2605
|
+
last_assistant_message: "Keep going and finish the cleanup.",
|
|
1904
2606
|
},
|
|
1905
2607
|
{ cwd },
|
|
1906
2608
|
);
|
|
@@ -1908,7 +2610,7 @@ esac
|
|
|
1908
2610
|
assert.equal(result.omxEventName, "stop");
|
|
1909
2611
|
assert.deepEqual(result.outputJson, {
|
|
1910
2612
|
decision: "block",
|
|
1911
|
-
reason:
|
|
2613
|
+
reason: DEFAULT_AUTO_NUDGE_RESPONSE,
|
|
1912
2614
|
stopReason: "auto_nudge",
|
|
1913
2615
|
systemMessage:
|
|
1914
2616
|
"OMX native Stop detected a stall/permission-style handoff and continued the turn automatically.",
|
|
@@ -1971,4 +2673,166 @@ esac
|
|
|
1971
2673
|
await rm(cwd, { recursive: true, force: true });
|
|
1972
2674
|
}
|
|
1973
2675
|
});
|
|
2676
|
+
|
|
2677
|
+
it("does not block Stop from another session's stale root team state when no scoped team state exists", async () => {
|
|
2678
|
+
const cwd = await mkdtemp(join(tmpdir(), "omx-native-hook-stop-stale-root-team-"));
|
|
2679
|
+
try {
|
|
2680
|
+
const stateDir = join(cwd, ".omx", "state");
|
|
2681
|
+
await mkdir(join(stateDir, "sessions", "sess-current"), { recursive: true });
|
|
2682
|
+
await writeJson(join(stateDir, "session.json"), { session_id: "sess-current" });
|
|
2683
|
+
await writeJson(join(stateDir, "team-state.json"), {
|
|
2684
|
+
active: true,
|
|
2685
|
+
current_phase: "starting",
|
|
2686
|
+
team_name: "stale-root-team",
|
|
2687
|
+
session_id: "sess-other",
|
|
2688
|
+
});
|
|
2689
|
+
await writeJson(join(stateDir, "team", "stale-root-team", "phase.json"), {
|
|
2690
|
+
current_phase: "team-exec",
|
|
2691
|
+
max_fix_attempts: 3,
|
|
2692
|
+
current_fix_attempt: 0,
|
|
2693
|
+
transitions: [],
|
|
2694
|
+
updated_at: new Date().toISOString(),
|
|
2695
|
+
});
|
|
2696
|
+
|
|
2697
|
+
const result = await dispatchCodexNativeHook(
|
|
2698
|
+
{
|
|
2699
|
+
hook_event_name: "Stop",
|
|
2700
|
+
cwd,
|
|
2701
|
+
session_id: "sess-current",
|
|
2702
|
+
},
|
|
2703
|
+
{ cwd },
|
|
2704
|
+
);
|
|
2705
|
+
|
|
2706
|
+
assert.equal(result.omxEventName, "stop");
|
|
2707
|
+
assert.equal(result.outputJson, null);
|
|
2708
|
+
} finally {
|
|
2709
|
+
await rm(cwd, { recursive: true, force: true });
|
|
2710
|
+
}
|
|
2711
|
+
});
|
|
2712
|
+
|
|
2713
|
+
it("does not block Stop from orphaned team mode state after cleanup removed canonical team artifacts", async () => {
|
|
2714
|
+
const cwd = await mkdtemp(join(tmpdir(), "omx-native-hook-stop-orphaned-team-state-"));
|
|
2715
|
+
try {
|
|
2716
|
+
const stateDir = join(cwd, ".omx", "state");
|
|
2717
|
+
await mkdir(join(stateDir, "sessions", "sess-current"), { recursive: true });
|
|
2718
|
+
await writeJson(join(stateDir, "session.json"), { session_id: "sess-current" });
|
|
2719
|
+
await writeJson(join(stateDir, "team-state.json"), {
|
|
2720
|
+
active: true,
|
|
2721
|
+
current_phase: "starting",
|
|
2722
|
+
team_name: "cleaned-team",
|
|
2723
|
+
session_id: "sess-current",
|
|
2724
|
+
});
|
|
2725
|
+
|
|
2726
|
+
const result = await dispatchCodexNativeHook(
|
|
2727
|
+
{
|
|
2728
|
+
hook_event_name: "Stop",
|
|
2729
|
+
cwd,
|
|
2730
|
+
session_id: "sess-current",
|
|
2731
|
+
},
|
|
2732
|
+
{ cwd },
|
|
2733
|
+
);
|
|
2734
|
+
|
|
2735
|
+
assert.equal(result.omxEventName, "stop");
|
|
2736
|
+
assert.equal(result.outputJson, null);
|
|
2737
|
+
} finally {
|
|
2738
|
+
await rm(cwd, { recursive: true, force: true });
|
|
2739
|
+
}
|
|
2740
|
+
});
|
|
2741
|
+
|
|
2742
|
+
it("prefers the current session team state over a stale root team fallback during Stop", async () => {
|
|
2743
|
+
const cwd = await mkdtemp(join(tmpdir(), "omx-native-hook-stop-current-session-team-preferred-"));
|
|
2744
|
+
try {
|
|
2745
|
+
const stateDir = join(cwd, ".omx", "state");
|
|
2746
|
+
await mkdir(join(stateDir, "sessions", "sess-current"), { recursive: true });
|
|
2747
|
+
await writeJson(join(stateDir, "session.json"), { session_id: "sess-current" });
|
|
2748
|
+
await writeJson(join(stateDir, "sessions", "sess-current", "team-state.json"), {
|
|
2749
|
+
active: true,
|
|
2750
|
+
current_phase: "starting",
|
|
2751
|
+
team_name: "current-team",
|
|
2752
|
+
session_id: "sess-current",
|
|
2753
|
+
});
|
|
2754
|
+
await writeJson(join(stateDir, "team", "current-team", "phase.json"), {
|
|
2755
|
+
current_phase: "team-verify",
|
|
2756
|
+
max_fix_attempts: 3,
|
|
2757
|
+
current_fix_attempt: 1,
|
|
2758
|
+
transitions: [],
|
|
2759
|
+
updated_at: new Date().toISOString(),
|
|
2760
|
+
});
|
|
2761
|
+
await writeJson(join(stateDir, "team-state.json"), {
|
|
2762
|
+
active: true,
|
|
2763
|
+
current_phase: "starting",
|
|
2764
|
+
team_name: "stale-root-team",
|
|
2765
|
+
session_id: "sess-other",
|
|
2766
|
+
});
|
|
2767
|
+
await writeJson(join(stateDir, "team", "stale-root-team", "phase.json"), {
|
|
2768
|
+
current_phase: "team-exec",
|
|
2769
|
+
max_fix_attempts: 3,
|
|
2770
|
+
current_fix_attempt: 0,
|
|
2771
|
+
transitions: [],
|
|
2772
|
+
updated_at: new Date().toISOString(),
|
|
2773
|
+
});
|
|
2774
|
+
|
|
2775
|
+
const result = await dispatchCodexNativeHook(
|
|
2776
|
+
{
|
|
2777
|
+
hook_event_name: "Stop",
|
|
2778
|
+
cwd,
|
|
2779
|
+
session_id: "sess-current",
|
|
2780
|
+
},
|
|
2781
|
+
{ cwd },
|
|
2782
|
+
);
|
|
2783
|
+
|
|
2784
|
+
assert.equal(result.omxEventName, "stop");
|
|
2785
|
+
assert.deepEqual(result.outputJson, {
|
|
2786
|
+
decision: "block",
|
|
2787
|
+
reason:
|
|
2788
|
+
`OMX team pipeline is still active (current-team) at phase team-verify; continue coordinating until the team reaches a terminal phase.${TEAM_STOP_COMMIT_GUIDANCE}`,
|
|
2789
|
+
stopReason: "team_team-verify",
|
|
2790
|
+
systemMessage: "OMX team pipeline is still active at phase team-verify.",
|
|
2791
|
+
});
|
|
2792
|
+
} finally {
|
|
2793
|
+
await rm(cwd, { recursive: true, force: true });
|
|
2794
|
+
}
|
|
2795
|
+
});
|
|
2796
|
+
|
|
2797
|
+
it("does not fall back to active root team state when the current scoped team state is inactive", async () => {
|
|
2798
|
+
const cwd = await mkdtemp(join(tmpdir(), "omx-native-hook-stop-inactive-scoped-team-"));
|
|
2799
|
+
try {
|
|
2800
|
+
const stateDir = join(cwd, ".omx", "state");
|
|
2801
|
+
await mkdir(join(stateDir, "sessions", "sess-current"), { recursive: true });
|
|
2802
|
+
await writeJson(join(stateDir, "session.json"), { session_id: "sess-current" });
|
|
2803
|
+
await writeJson(join(stateDir, "sessions", "sess-current", "team-state.json"), {
|
|
2804
|
+
active: false,
|
|
2805
|
+
current_phase: "complete",
|
|
2806
|
+
team_name: "scoped-finished-team",
|
|
2807
|
+
session_id: "sess-current",
|
|
2808
|
+
});
|
|
2809
|
+
await writeJson(join(stateDir, "team-state.json"), {
|
|
2810
|
+
active: true,
|
|
2811
|
+
current_phase: "starting",
|
|
2812
|
+
team_name: "root-fallback-team",
|
|
2813
|
+
session_id: "sess-current",
|
|
2814
|
+
});
|
|
2815
|
+
await writeJson(join(stateDir, "team", "root-fallback-team", "phase.json"), {
|
|
2816
|
+
current_phase: "team-exec",
|
|
2817
|
+
max_fix_attempts: 3,
|
|
2818
|
+
current_fix_attempt: 0,
|
|
2819
|
+
transitions: [],
|
|
2820
|
+
updated_at: new Date().toISOString(),
|
|
2821
|
+
});
|
|
2822
|
+
|
|
2823
|
+
const result = await dispatchCodexNativeHook(
|
|
2824
|
+
{
|
|
2825
|
+
hook_event_name: "Stop",
|
|
2826
|
+
cwd,
|
|
2827
|
+
session_id: "sess-current",
|
|
2828
|
+
},
|
|
2829
|
+
{ cwd },
|
|
2830
|
+
);
|
|
2831
|
+
|
|
2832
|
+
assert.equal(result.omxEventName, "stop");
|
|
2833
|
+
assert.equal(result.outputJson, null);
|
|
2834
|
+
} finally {
|
|
2835
|
+
await rm(cwd, { recursive: true, force: true });
|
|
2836
|
+
}
|
|
2837
|
+
});
|
|
1974
2838
|
});
|