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
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { describe, it } from 'node:test';
|
|
2
2
|
import { once } from 'node:events';
|
|
3
3
|
import assert from 'node:assert/strict';
|
|
4
|
-
import { chmod, mkdtemp, mkdir, readFile, rm, writeFile } from 'node:fs/promises';
|
|
4
|
+
import { chmod, mkdtemp, mkdir, readFile, rm, symlink, writeFile } from 'node:fs/promises';
|
|
5
5
|
import { tmpdir } from 'node:os';
|
|
6
6
|
import { join } from 'node:path';
|
|
7
7
|
import { spawn, spawnSync } from 'node:child_process';
|
|
@@ -9,6 +9,7 @@ import { randomUUID } from 'node:crypto';
|
|
|
9
9
|
import { initTeamState, enqueueDispatchRequest, readDispatchRequest } from '../../team/state.js';
|
|
10
10
|
import { buildWindowsMsysBackgroundHelperBootstrapScript } from '../../cli/index.js';
|
|
11
11
|
import { writeSessionStart } from '../session.js';
|
|
12
|
+
const DEFAULT_AUTO_NUDGE_RESPONSE = 'continue with the current task only if it is already authorized';
|
|
12
13
|
async function appendLine(path, line) {
|
|
13
14
|
const prev = await readFile(path, 'utf-8');
|
|
14
15
|
const content = prev + `${JSON.stringify(line)}\n`;
|
|
@@ -30,6 +31,80 @@ async function readJsonLines(path) {
|
|
|
30
31
|
.filter(Boolean)
|
|
31
32
|
.map((line) => JSON.parse(line));
|
|
32
33
|
}
|
|
34
|
+
async function writeCanonicalWatcherTeamFixture(wd, { teamName = 'dispatch-team', sessionId = 'sess-current', ownerSessionId = sessionId, coarseState = 'missing', terminal = false, } = {}) {
|
|
35
|
+
const stateDir = join(wd, '.omx', 'state');
|
|
36
|
+
const teamDir = join(stateDir, 'team', teamName);
|
|
37
|
+
const nowIso = new Date().toISOString();
|
|
38
|
+
await mkdir(join(wd, '.omx', 'logs'), { recursive: true });
|
|
39
|
+
await mkdir(join(teamDir, 'workers'), { recursive: true });
|
|
40
|
+
await writeFile(join(stateDir, 'session.json'), JSON.stringify({ session_id: sessionId }, null, 2));
|
|
41
|
+
if (coarseState !== 'missing') {
|
|
42
|
+
await writeFile(join(stateDir, 'team-state.json'), JSON.stringify({
|
|
43
|
+
active: coarseState === 'active',
|
|
44
|
+
team_name: teamName,
|
|
45
|
+
current_phase: terminal ? 'complete' : 'team-exec',
|
|
46
|
+
...(terminal ? { completed_at: nowIso } : {}),
|
|
47
|
+
}, null, 2));
|
|
48
|
+
}
|
|
49
|
+
await writeFile(join(stateDir, 'hud-state.json'), JSON.stringify({
|
|
50
|
+
last_turn_at: new Date(Date.now() - 300_000).toISOString(),
|
|
51
|
+
turn_count: 3,
|
|
52
|
+
}, null, 2));
|
|
53
|
+
const manifest = {
|
|
54
|
+
schema_version: 2,
|
|
55
|
+
name: teamName,
|
|
56
|
+
task: 'canonical watcher fallback repro',
|
|
57
|
+
leader: {
|
|
58
|
+
session_id: ownerSessionId,
|
|
59
|
+
worker_id: 'leader-fixed',
|
|
60
|
+
role: 'coordinator',
|
|
61
|
+
},
|
|
62
|
+
policy: {
|
|
63
|
+
worker_launch_mode: 'interactive',
|
|
64
|
+
display_mode: 'split_pane',
|
|
65
|
+
dispatch_mode: 'hook_preferred_with_fallback',
|
|
66
|
+
dispatch_ack_timeout_ms: 2000,
|
|
67
|
+
},
|
|
68
|
+
governance: {
|
|
69
|
+
delegation_only: false,
|
|
70
|
+
plan_approval_required: false,
|
|
71
|
+
nested_teams_allowed: false,
|
|
72
|
+
one_team_per_leader_session: true,
|
|
73
|
+
cleanup_requires_all_workers_inactive: true,
|
|
74
|
+
},
|
|
75
|
+
lifecycle_profile: 'default',
|
|
76
|
+
permissions_snapshot: {
|
|
77
|
+
approval_mode: 'never',
|
|
78
|
+
sandbox_mode: 'danger-full-access',
|
|
79
|
+
network_access: true,
|
|
80
|
+
},
|
|
81
|
+
tmux_session: `${teamName}:0`,
|
|
82
|
+
leader_pane_id: '%42',
|
|
83
|
+
hud_pane_id: null,
|
|
84
|
+
resize_hook_name: null,
|
|
85
|
+
resize_hook_target: null,
|
|
86
|
+
worker_count: 1,
|
|
87
|
+
next_task_id: 1,
|
|
88
|
+
workers: [
|
|
89
|
+
{ name: 'worker-1', index: 1, pane_id: '%42', role: 'executor' },
|
|
90
|
+
],
|
|
91
|
+
created_at: nowIso,
|
|
92
|
+
};
|
|
93
|
+
await writeFile(join(teamDir, 'manifest.v2.json'), JSON.stringify(manifest, null, 2));
|
|
94
|
+
await writeFile(join(teamDir, 'config.json'), JSON.stringify({
|
|
95
|
+
name: teamName,
|
|
96
|
+
tmux_session: `${teamName}:0`,
|
|
97
|
+
leader_pane_id: '%42',
|
|
98
|
+
workers: [
|
|
99
|
+
{ name: 'worker-1', pane_id: '%42' },
|
|
100
|
+
],
|
|
101
|
+
}, null, 2));
|
|
102
|
+
await writeFile(join(teamDir, 'phase.json'), JSON.stringify({
|
|
103
|
+
current_phase: terminal ? 'complete' : 'team-exec',
|
|
104
|
+
updated_at: nowIso,
|
|
105
|
+
transitions: terminal ? [{ from: 'team-exec', to: 'complete', at: nowIso }] : [],
|
|
106
|
+
}, null, 2));
|
|
107
|
+
}
|
|
33
108
|
async function sleep(ms) {
|
|
34
109
|
await new Promise(resolve => setTimeout(resolve, ms));
|
|
35
110
|
}
|
|
@@ -63,6 +138,9 @@ async function waitForExit(child, timeoutMs = 4000) {
|
|
|
63
138
|
}),
|
|
64
139
|
]);
|
|
65
140
|
}
|
|
141
|
+
function defaultAutoNudgePattern(targetPane) {
|
|
142
|
+
return new RegExp(`send-keys -t ${targetPane} -l ${DEFAULT_AUTO_NUDGE_RESPONSE.replace(/[-/\\^$*+?.()|[\]{}]/g, '\\$&')} \\[OMX_TMUX_INJECT\\]`);
|
|
143
|
+
}
|
|
66
144
|
function buildFakeTmux(tmuxLogPath, options = {}) {
|
|
67
145
|
return `#!/usr/bin/env bash
|
|
68
146
|
set -eu
|
|
@@ -530,10 +608,11 @@ describe('notify-fallback watcher', () => {
|
|
|
530
608
|
autoNudge: { enabled: true, delaySec: 0, ttlMs: 30_000 },
|
|
531
609
|
}, null, 2));
|
|
532
610
|
await writeSessionStart(wd, 'sess-managed-fallback');
|
|
533
|
-
await
|
|
611
|
+
await mkdir(join(wd, '.omx', 'state', 'sessions', 'sess-managed-fallback'), { recursive: true });
|
|
612
|
+
await writeFile(join(wd, '.omx', 'state', 'sessions', 'sess-managed-fallback', 'hud-state.json'), JSON.stringify({
|
|
534
613
|
last_turn_at: new Date(Date.now() - 6_000).toISOString(),
|
|
535
614
|
turn_count: 7,
|
|
536
|
-
last_agent_output: '
|
|
615
|
+
last_agent_output: 'Keep going and finish the cleanup from here.',
|
|
537
616
|
}, null, 2));
|
|
538
617
|
await writeFile(join(wd, '.omx', 'state', 'notify-fallback.pid'), JSON.stringify({
|
|
539
618
|
pid: process.pid,
|
|
@@ -562,7 +641,7 @@ describe('notify-fallback watcher', () => {
|
|
|
562
641
|
});
|
|
563
642
|
assert.equal(result.status, 0, result.stderr || result.stdout);
|
|
564
643
|
const tmuxLog = await readFile(tmuxLogPath, 'utf8').catch(() => '');
|
|
565
|
-
assert.doesNotMatch(tmuxLog,
|
|
644
|
+
assert.doesNotMatch(tmuxLog, defaultAutoNudgePattern('%42'));
|
|
566
645
|
const watcherState = JSON.parse(await readFile(join(wd, '.omx', 'state', 'notify-fallback-state.json'), 'utf-8'));
|
|
567
646
|
assert.equal(watcherState.pid, process.pid, 'authority backoff should preserve the primary watcher state owner');
|
|
568
647
|
assert.equal(watcherState.authority_only, false, 'authority backoff should not overwrite primary watcher ownership');
|
|
@@ -578,6 +657,52 @@ describe('notify-fallback watcher', () => {
|
|
|
578
657
|
await rm(wd, { recursive: true, force: true });
|
|
579
658
|
}
|
|
580
659
|
});
|
|
660
|
+
it('treats symlinked cwd aliases as the same primary watcher during authority handoff', async () => {
|
|
661
|
+
const wd = await mkdtemp(join(tmpdir(), 'omx-fallback-cwd-alias-'));
|
|
662
|
+
const aliasWd = `${wd}-alias`;
|
|
663
|
+
const fakeBinDir = join(wd, 'fake-bin');
|
|
664
|
+
const tmuxLogPath = join(wd, 'tmux.log');
|
|
665
|
+
try {
|
|
666
|
+
await symlink(wd, aliasWd, process.platform === 'win32' ? 'junction' : 'dir');
|
|
667
|
+
await mkdir(join(wd, '.omx', 'logs'), { recursive: true });
|
|
668
|
+
await mkdir(fakeBinDir, { recursive: true });
|
|
669
|
+
await writeFile(join(fakeBinDir, 'tmux'), buildFakeTmux(tmuxLogPath));
|
|
670
|
+
await chmod(join(fakeBinDir, 'tmux'), 0o755);
|
|
671
|
+
await writeSessionStart(wd, 'sess-cwd-alias');
|
|
672
|
+
await writeFile(join(wd, '.omx', 'state', 'notify-fallback.pid'), JSON.stringify({
|
|
673
|
+
pid: process.pid,
|
|
674
|
+
cwd: wd,
|
|
675
|
+
started_at: new Date().toISOString(),
|
|
676
|
+
}, null, 2));
|
|
677
|
+
await writeFile(join(wd, '.omx', 'state', 'notify-fallback-state.json'), JSON.stringify({
|
|
678
|
+
pid: process.pid,
|
|
679
|
+
cwd: wd,
|
|
680
|
+
authority_only: false,
|
|
681
|
+
poll_ms: 250,
|
|
682
|
+
dispatch_drain: { last_tick_at: new Date().toISOString() },
|
|
683
|
+
}, null, 2));
|
|
684
|
+
const watcherScript = new URL('../../../dist/scripts/notify-fallback-watcher.js', import.meta.url).pathname;
|
|
685
|
+
const notifyHook = new URL('../../../dist/scripts/notify-hook.js', import.meta.url).pathname;
|
|
686
|
+
const result = spawnSync(process.execPath, [watcherScript, '--once', '--authority-only', '--cwd', aliasWd, '--notify-script', notifyHook, '--poll-ms', '50'], {
|
|
687
|
+
encoding: 'utf-8',
|
|
688
|
+
env: buildCleanNotifyEnv({
|
|
689
|
+
PATH: `${fakeBinDir}:${process.env.PATH || ''}`,
|
|
690
|
+
OMX_SESSION_ID: 'sess-cwd-alias',
|
|
691
|
+
TMUX: '1',
|
|
692
|
+
TMUX_PANE: '%42',
|
|
693
|
+
}),
|
|
694
|
+
});
|
|
695
|
+
assert.equal(result.status, 0, result.stderr || result.stdout);
|
|
696
|
+
const watcherState = JSON.parse(await readFile(join(wd, '.omx', 'state', 'notify-fallback-state.json'), 'utf-8'));
|
|
697
|
+
assert.equal(watcherState.authority_backoff?.active, true);
|
|
698
|
+
assert.equal(watcherState.authority_backoff?.reason, 'primary_watcher_healthy');
|
|
699
|
+
assert.equal(watcherState.authority_backoff?.primary_pid, process.pid);
|
|
700
|
+
}
|
|
701
|
+
finally {
|
|
702
|
+
await rm(aliasWd, { recursive: true, force: true });
|
|
703
|
+
await rm(wd, { recursive: true, force: true });
|
|
704
|
+
}
|
|
705
|
+
});
|
|
581
706
|
it('disables fallback watcher nudges when deep-interview state is active', async () => {
|
|
582
707
|
const wd = await mkdtemp(join(tmpdir(), 'omx-fallback-deep-interview-suppressed-'));
|
|
583
708
|
const fakeBinDir = join(wd, 'fake-bin');
|
|
@@ -623,7 +748,7 @@ describe('notify-fallback watcher', () => {
|
|
|
623
748
|
const tmuxLog = await readFile(tmuxLogPath, 'utf8').catch(() => '');
|
|
624
749
|
assert.doesNotMatch(tmuxLog, /Ralph loop active continue/);
|
|
625
750
|
assert.doesNotMatch(tmuxLog, /Team dispatch-team:/);
|
|
626
|
-
assert.doesNotMatch(tmuxLog, /
|
|
751
|
+
assert.doesNotMatch(tmuxLog, new RegExp(`${DEFAULT_AUTO_NUDGE_RESPONSE.replace(/[-/\\^$*+?.()|[\]{}]/g, '\\$&')} \\[OMX_TMUX_INJECT\\]`));
|
|
627
752
|
}
|
|
628
753
|
finally {
|
|
629
754
|
await rm(wd, { recursive: true, force: true });
|
|
@@ -686,7 +811,7 @@ describe('notify-fallback watcher', () => {
|
|
|
686
811
|
const tmuxLog = await readFile(tmuxLogPath, 'utf8').catch(() => '');
|
|
687
812
|
assert.doesNotMatch(tmuxLog, /Ralph loop active continue/);
|
|
688
813
|
assert.doesNotMatch(tmuxLog, /Team dispatch-team:/);
|
|
689
|
-
assert.doesNotMatch(tmuxLog, /
|
|
814
|
+
assert.doesNotMatch(tmuxLog, new RegExp(`${DEFAULT_AUTO_NUDGE_RESPONSE.replace(/[-/\\^$*+?.()|[\]{}]/g, '\\$&')} \\[OMX_TMUX_INJECT\\]`));
|
|
690
815
|
}
|
|
691
816
|
finally {
|
|
692
817
|
await rm(wd, { recursive: true, force: true });
|
|
@@ -722,6 +847,7 @@ describe('notify-fallback watcher', () => {
|
|
|
722
847
|
encoding: 'utf-8',
|
|
723
848
|
env: buildCleanNotifyEnv({
|
|
724
849
|
PATH: `${fakeBinDir}:${process.env.PATH || ''}`,
|
|
850
|
+
OMX_SESSION_ID: 'sess-canonical-inactive',
|
|
725
851
|
}),
|
|
726
852
|
});
|
|
727
853
|
assert.equal(result.status, 0, result.stderr || result.stdout);
|
|
@@ -751,6 +877,37 @@ describe('notify-fallback watcher', () => {
|
|
|
751
877
|
await rm(wd, { recursive: true, force: true });
|
|
752
878
|
}
|
|
753
879
|
});
|
|
880
|
+
it('runs leader nudge checks from canonical fallback when coarse team-state is inactive', async () => {
|
|
881
|
+
const wd = await mkdtemp(join(tmpdir(), 'omx-fallback-leader-nudge-canonical-inactive-'));
|
|
882
|
+
const fakeBinDir = join(wd, 'fake-bin');
|
|
883
|
+
const tmuxLogPath = join(wd, 'tmux.log');
|
|
884
|
+
try {
|
|
885
|
+
await mkdir(fakeBinDir, { recursive: true });
|
|
886
|
+
await writeCanonicalWatcherTeamFixture(wd, {
|
|
887
|
+
teamName: 'dispatch-team',
|
|
888
|
+
sessionId: 'sess-canonical-inactive',
|
|
889
|
+
ownerSessionId: 'sess-canonical-inactive',
|
|
890
|
+
coarseState: 'inactive',
|
|
891
|
+
});
|
|
892
|
+
await writeFile(join(fakeBinDir, 'tmux'), buildFakeTmux(tmuxLogPath));
|
|
893
|
+
await chmod(join(fakeBinDir, 'tmux'), 0o755);
|
|
894
|
+
const watcherScript = new URL('../../../dist/scripts/notify-fallback-watcher.js', import.meta.url).pathname;
|
|
895
|
+
const notifyHook = new URL('../../../dist/scripts/notify-hook.js', import.meta.url).pathname;
|
|
896
|
+
const result = spawnSync(process.execPath, [watcherScript, '--once', '--cwd', wd, '--notify-script', notifyHook], {
|
|
897
|
+
encoding: 'utf-8',
|
|
898
|
+
env: buildCleanNotifyEnv({
|
|
899
|
+
PATH: `${fakeBinDir}:${process.env.PATH || ''}`,
|
|
900
|
+
OMX_SESSION_ID: 'sess-canonical-inactive',
|
|
901
|
+
}),
|
|
902
|
+
});
|
|
903
|
+
assert.equal(result.status, 0, result.stderr || result.stdout);
|
|
904
|
+
const tmuxLog = await readFile(tmuxLogPath, 'utf8');
|
|
905
|
+
assert.match(tmuxLog, /send-keys -t %42 -l Team dispatch-team: leader stale, \d+ worker pane\(s\) still active\./);
|
|
906
|
+
}
|
|
907
|
+
finally {
|
|
908
|
+
await rm(wd, { recursive: true, force: true });
|
|
909
|
+
}
|
|
910
|
+
});
|
|
754
911
|
it('skips fallback watcher leader nudges when the leader is not stale even if mailbox messages exist', async () => {
|
|
755
912
|
const wd = await mkdtemp(join(tmpdir(), 'omx-fallback-leader-nudge-fresh-'));
|
|
756
913
|
const fakeBinDir = join(wd, 'fake-bin');
|
|
@@ -793,6 +950,7 @@ describe('notify-fallback watcher', () => {
|
|
|
793
950
|
encoding: 'utf-8',
|
|
794
951
|
env: buildCleanNotifyEnv({
|
|
795
952
|
PATH: `${fakeBinDir}:${process.env.PATH || ''}`,
|
|
953
|
+
OMX_SESSION_ID: 'sess-canonical-inactive',
|
|
796
954
|
}),
|
|
797
955
|
});
|
|
798
956
|
assert.equal(result.status, 0, result.stderr || result.stdout);
|
|
@@ -1003,10 +1161,11 @@ exit 0
|
|
|
1003
1161
|
autoNudge: { enabled: true, delaySec: 0, ttlMs: 30_000 },
|
|
1004
1162
|
}, null, 2));
|
|
1005
1163
|
await writeSessionStart(wd, 'sess-managed-fallback');
|
|
1006
|
-
await
|
|
1164
|
+
await mkdir(join(wd, '.omx', 'state', 'sessions', 'sess-managed-fallback'), { recursive: true });
|
|
1165
|
+
await writeFile(join(wd, '.omx', 'state', 'sessions', 'sess-managed-fallback', 'hud-state.json'), JSON.stringify({
|
|
1007
1166
|
last_turn_at: new Date(Date.now() - 6_000).toISOString(),
|
|
1008
1167
|
turn_count: 7,
|
|
1009
|
-
last_agent_output: '
|
|
1168
|
+
last_agent_output: 'Keep going and finish the cleanup from here.',
|
|
1010
1169
|
}, null, 2));
|
|
1011
1170
|
const watcherScript = new URL('../../../dist/scripts/notify-fallback-watcher.js', import.meta.url).pathname;
|
|
1012
1171
|
const notifyHook = new URL('../../../dist/scripts/notify-hook.js', import.meta.url).pathname;
|
|
@@ -1024,7 +1183,7 @@ exit 0
|
|
|
1024
1183
|
});
|
|
1025
1184
|
assert.equal(result.status, 0, result.stderr || result.stdout);
|
|
1026
1185
|
const tmuxLog = await readFile(tmuxLogPath, 'utf8');
|
|
1027
|
-
assert.match(tmuxLog,
|
|
1186
|
+
assert.match(tmuxLog, defaultAutoNudgePattern('%42'));
|
|
1028
1187
|
const watcherStatePath = join(wd, '.omx', 'state', 'notify-fallback-state.json');
|
|
1029
1188
|
const watcherState = JSON.parse(await readFile(watcherStatePath, 'utf-8'));
|
|
1030
1189
|
assert.equal(watcherState.fallback_auto_nudge?.last_reason, 'sent');
|
|
@@ -1055,10 +1214,11 @@ exit 0
|
|
|
1055
1214
|
target: { type: 'pane', value: '%42' },
|
|
1056
1215
|
}, null, 2));
|
|
1057
1216
|
await writeSessionStart(wd, 'sess-managed-fallback');
|
|
1058
|
-
await
|
|
1217
|
+
await mkdir(join(wd, '.omx', 'state', 'sessions', 'sess-managed-fallback'), { recursive: true });
|
|
1218
|
+
await writeFile(join(wd, '.omx', 'state', 'sessions', 'sess-managed-fallback', 'hud-state.json'), JSON.stringify({
|
|
1059
1219
|
last_turn_at: new Date(Date.now() - 6_000).toISOString(),
|
|
1060
1220
|
turn_count: 7,
|
|
1061
|
-
last_agent_output: '
|
|
1221
|
+
last_agent_output: 'Keep going and finish the cleanup from here.',
|
|
1062
1222
|
}, null, 2));
|
|
1063
1223
|
const watcherScript = new URL('../../../dist/scripts/notify-fallback-watcher.js', import.meta.url).pathname;
|
|
1064
1224
|
const notifyHook = new URL('../../../dist/scripts/notify-hook.js', import.meta.url).pathname;
|
|
@@ -1075,7 +1235,7 @@ exit 0
|
|
|
1075
1235
|
});
|
|
1076
1236
|
assert.equal(result.status, 0, result.stderr || result.stdout);
|
|
1077
1237
|
const tmuxLog = await readFile(tmuxLogPath, 'utf8').catch(() => '');
|
|
1078
|
-
assert.doesNotMatch(tmuxLog,
|
|
1238
|
+
assert.doesNotMatch(tmuxLog, defaultAutoNudgePattern('%42'));
|
|
1079
1239
|
}
|
|
1080
1240
|
finally {
|
|
1081
1241
|
await rm(wd, { recursive: true, force: true });
|
|
@@ -1099,7 +1259,7 @@ exit 0
|
|
|
1099
1259
|
await writeFile(join(wd, '.omx', 'state', 'hud-state.json'), JSON.stringify({
|
|
1100
1260
|
last_turn_at: new Date(Date.now() - 6_000).toISOString(),
|
|
1101
1261
|
turn_count: 9,
|
|
1102
|
-
last_agent_output: '
|
|
1262
|
+
last_agent_output: 'Keep going and finish the cleanup from here.',
|
|
1103
1263
|
}, null, 2));
|
|
1104
1264
|
const watcherScript = new URL('../../../dist/scripts/notify-fallback-watcher.js', import.meta.url).pathname;
|
|
1105
1265
|
const notifyHook = new URL('../../../dist/scripts/notify-hook.js', import.meta.url).pathname;
|
|
@@ -1115,7 +1275,7 @@ exit 0
|
|
|
1115
1275
|
});
|
|
1116
1276
|
assert.equal(result.status, 0, result.stderr || result.stdout);
|
|
1117
1277
|
const tmuxLog = await readFile(tmuxLogPath, 'utf8').catch(() => '');
|
|
1118
|
-
assert.doesNotMatch(tmuxLog,
|
|
1278
|
+
assert.doesNotMatch(tmuxLog, defaultAutoNudgePattern('%42'));
|
|
1119
1279
|
const watcherStatePath = join(wd, '.omx', 'state', 'notify-fallback-state.json');
|
|
1120
1280
|
const watcherState = JSON.parse(await readFile(watcherStatePath, 'utf-8'));
|
|
1121
1281
|
assert.equal(watcherState.fallback_auto_nudge?.last_reason, 'eligible_but_not_sent');
|
|
@@ -1145,7 +1305,7 @@ exit 0
|
|
|
1145
1305
|
await writeFile(join(wd, '.omx', 'state', 'hud-state.json'), JSON.stringify({
|
|
1146
1306
|
last_turn_at: new Date(Date.now() - 1_000).toISOString(),
|
|
1147
1307
|
turn_count: 8,
|
|
1148
|
-
last_agent_output: '
|
|
1308
|
+
last_agent_output: 'Keep going and finish the cleanup from here.',
|
|
1149
1309
|
}, null, 2));
|
|
1150
1310
|
const watcherScript = new URL('../../../dist/scripts/notify-fallback-watcher.js', import.meta.url).pathname;
|
|
1151
1311
|
const notifyHook = new URL('../../../dist/scripts/notify-hook.js', import.meta.url).pathname;
|
|
@@ -1161,7 +1321,7 @@ exit 0
|
|
|
1161
1321
|
});
|
|
1162
1322
|
assert.equal(result.status, 0, result.stderr || result.stdout);
|
|
1163
1323
|
const tmuxLog = await readFile(tmuxLogPath, 'utf8').catch(() => '');
|
|
1164
|
-
assert.doesNotMatch(tmuxLog,
|
|
1324
|
+
assert.doesNotMatch(tmuxLog, defaultAutoNudgePattern('%42'));
|
|
1165
1325
|
const watcherStatePath = join(wd, '.omx', 'state', 'notify-fallback-state.json');
|
|
1166
1326
|
const watcherState = JSON.parse(await readFile(watcherStatePath, 'utf-8'));
|
|
1167
1327
|
assert.equal(watcherState.fallback_auto_nudge?.last_reason, 'recent_turn_activity');
|
|
@@ -1177,7 +1337,7 @@ exit 0
|
|
|
1177
1337
|
const tmuxLogPath = join(wd, 'tmux.log');
|
|
1178
1338
|
const codexHome = join(wd, 'codex-home');
|
|
1179
1339
|
const lastTurnAt = new Date(Date.now() - 6_000).toISOString();
|
|
1180
|
-
const lastMessage = '
|
|
1340
|
+
const lastMessage = 'Keep going and finish the cleanup from here.';
|
|
1181
1341
|
try {
|
|
1182
1342
|
await mkdir(join(wd, '.omx', 'logs'), { recursive: true });
|
|
1183
1343
|
await mkdir(join(wd, '.omx', 'state'), { recursive: true });
|
|
@@ -1213,7 +1373,7 @@ exit 0
|
|
|
1213
1373
|
});
|
|
1214
1374
|
assert.equal(result.status, 0, result.stderr || result.stdout);
|
|
1215
1375
|
const tmuxLog = await readFile(tmuxLogPath, 'utf8').catch(() => '');
|
|
1216
|
-
assert.doesNotMatch(tmuxLog,
|
|
1376
|
+
assert.doesNotMatch(tmuxLog, defaultAutoNudgePattern('%42'));
|
|
1217
1377
|
const watcherStatePath = join(wd, '.omx', 'state', 'notify-fallback-state.json');
|
|
1218
1378
|
const watcherState = JSON.parse(await readFile(watcherStatePath, 'utf-8'));
|
|
1219
1379
|
assert.equal(watcherState.fallback_auto_nudge?.last_reason, 'already_nudged_for_signature');
|
|
@@ -1229,7 +1389,7 @@ exit 0
|
|
|
1229
1389
|
const tmuxLogPath = join(wd, 'tmux.log');
|
|
1230
1390
|
const codexHome = join(wd, 'codex-home');
|
|
1231
1391
|
const lastTurnAt = '2026-03-01T00:00:00.000Z';
|
|
1232
|
-
const lastMessage = '
|
|
1392
|
+
const lastMessage = 'Keep going and finish the cleanup from here.';
|
|
1233
1393
|
try {
|
|
1234
1394
|
await mkdir(join(wd, '.omx', 'logs'), { recursive: true });
|
|
1235
1395
|
await mkdir(join(wd, '.omx', 'state'), { recursive: true });
|
|
@@ -1265,7 +1425,7 @@ exit 0
|
|
|
1265
1425
|
});
|
|
1266
1426
|
assert.equal(result.status, 0, result.stderr || result.stdout);
|
|
1267
1427
|
const tmuxLog = await readFile(tmuxLogPath, 'utf8').catch(() => '');
|
|
1268
|
-
assert.doesNotMatch(tmuxLog,
|
|
1428
|
+
assert.doesNotMatch(tmuxLog, defaultAutoNudgePattern('%42'));
|
|
1269
1429
|
const watcherStatePath = join(wd, '.omx', 'state', 'notify-fallback-state.json');
|
|
1270
1430
|
const watcherState = JSON.parse(await readFile(watcherStatePath, 'utf-8'));
|
|
1271
1431
|
assert.equal(watcherState.fallback_auto_nudge?.last_reason, 'already_nudged_for_signature');
|
|
@@ -1530,6 +1690,53 @@ exit 0
|
|
|
1530
1690
|
await rm(wd, { recursive: true, force: true });
|
|
1531
1691
|
}
|
|
1532
1692
|
});
|
|
1693
|
+
it('suppresses Ralph continue steer when session-scoped Ralph is stuck in stale starting phase', async () => {
|
|
1694
|
+
const wd = await mkdtemp(join(tmpdir(), 'omx-fallback-ralph-starting-stale-'));
|
|
1695
|
+
const fakeBinDir = join(wd, 'fake-bin');
|
|
1696
|
+
const tmuxLogPath = join(wd, 'tmux.log');
|
|
1697
|
+
const stateDir = join(wd, '.omx', 'state');
|
|
1698
|
+
const sessionId = 'sess-starting-stale';
|
|
1699
|
+
const sessionStateDir = join(stateDir, 'sessions', sessionId);
|
|
1700
|
+
const watcherStatePath = join(stateDir, 'notify-fallback-state.json');
|
|
1701
|
+
try {
|
|
1702
|
+
await mkdir(sessionStateDir, { recursive: true });
|
|
1703
|
+
await mkdir(fakeBinDir, { recursive: true });
|
|
1704
|
+
await writeFile(join(fakeBinDir, 'tmux'), buildFakeTmux(tmuxLogPath));
|
|
1705
|
+
await chmod(join(fakeBinDir, 'tmux'), 0o755);
|
|
1706
|
+
await writeSessionStart(wd, sessionId);
|
|
1707
|
+
await writeFile(join(sessionStateDir, 'ralph-state.json'), JSON.stringify({
|
|
1708
|
+
active: true,
|
|
1709
|
+
current_phase: 'starting',
|
|
1710
|
+
started_at: new Date(Date.now() - 180_000).toISOString(),
|
|
1711
|
+
tmux_pane_id: '%42',
|
|
1712
|
+
}, null, 2));
|
|
1713
|
+
await writeFile(join(sessionStateDir, 'hud-state.json'), JSON.stringify({
|
|
1714
|
+
last_progress_at: new Date(Date.now() - 180_000).toISOString(),
|
|
1715
|
+
}, null, 2));
|
|
1716
|
+
await writeFile(watcherStatePath, JSON.stringify({
|
|
1717
|
+
ralph_continue_steer: {
|
|
1718
|
+
last_sent_at: new Date(Date.now() - 61_000).toISOString(),
|
|
1719
|
+
},
|
|
1720
|
+
}, null, 2));
|
|
1721
|
+
const watcherScript = new URL('../../../dist/scripts/notify-fallback-watcher.js', import.meta.url).pathname;
|
|
1722
|
+
const notifyHook = new URL('../../../dist/scripts/notify-hook.js', import.meta.url).pathname;
|
|
1723
|
+
const run = spawnSync(process.execPath, [watcherScript, '--once', '--cwd', wd, '--notify-script', notifyHook, '--poll-ms', '50'], {
|
|
1724
|
+
encoding: 'utf-8',
|
|
1725
|
+
env: buildCleanNotifyEnv({
|
|
1726
|
+
PATH: `${fakeBinDir}:${process.env.PATH || ''}`,
|
|
1727
|
+
}),
|
|
1728
|
+
});
|
|
1729
|
+
assert.equal(run.status, 0, run.stderr || run.stdout);
|
|
1730
|
+
const tmuxLog = await readFile(tmuxLogPath, 'utf8').catch(() => '');
|
|
1731
|
+
const sends = tmuxLog.match(/send-keys -t %42 -l Ralph loop active continue \[OMX_TMUX_INJECT\]/g) || [];
|
|
1732
|
+
assert.equal(sends.length, 0, 'stale starting phase should suppress continue steer');
|
|
1733
|
+
const watcherState = JSON.parse(await readFile(watcherStatePath, 'utf-8'));
|
|
1734
|
+
assert.equal(watcherState.ralph_continue_steer?.last_reason, 'starting_stale');
|
|
1735
|
+
}
|
|
1736
|
+
finally {
|
|
1737
|
+
await rm(wd, { recursive: true, force: true });
|
|
1738
|
+
}
|
|
1739
|
+
});
|
|
1533
1740
|
it('suppresses Ralph continue steer while tracked native subagents are still active', async () => {
|
|
1534
1741
|
const wd = await mkdtemp(join(tmpdir(), 'omx-fallback-ralph-subagents-active-'));
|
|
1535
1742
|
const fakeBinDir = join(wd, 'fake-bin');
|
|
@@ -1551,7 +1758,7 @@ exit 0
|
|
|
1551
1758
|
owner_omx_session_id: omxSessionId,
|
|
1552
1759
|
owner_codex_session_id: codexSessionId,
|
|
1553
1760
|
}, null, 2));
|
|
1554
|
-
await writeFile(join(stateDir, 'hud-state.json'), JSON.stringify({
|
|
1761
|
+
await writeFile(join(stateDir, 'sessions', omxSessionId, 'hud-state.json'), JSON.stringify({
|
|
1555
1762
|
last_progress_at: new Date(Date.now() - 61_000).toISOString(),
|
|
1556
1763
|
}, null, 2));
|
|
1557
1764
|
await writeFile(statePath, JSON.stringify({
|
|
@@ -1846,6 +2053,52 @@ exit 0
|
|
|
1846
2053
|
await rm(wd, { recursive: true, force: true });
|
|
1847
2054
|
}
|
|
1848
2055
|
});
|
|
2056
|
+
it('treats a long-running starting phase as terminal so Ralph steer stops', async () => {
|
|
2057
|
+
const wd = await mkdtemp(join(tmpdir(), 'omx-fallback-starting-phase-stale-'));
|
|
2058
|
+
const fakeBinDir = join(wd, 'fake-bin');
|
|
2059
|
+
const tmuxLogPath = join(wd, 'tmux.log');
|
|
2060
|
+
const stateDir = join(wd, '.omx', 'state');
|
|
2061
|
+
const watcherStatePath = join(stateDir, 'notify-fallback-state.json');
|
|
2062
|
+
const staleStartedAt = new Date(Date.now() - 3 * 60_000).toISOString();
|
|
2063
|
+
try {
|
|
2064
|
+
await mkdir(stateDir, { recursive: true });
|
|
2065
|
+
await mkdir(fakeBinDir, { recursive: true });
|
|
2066
|
+
await writeFile(join(fakeBinDir, 'tmux'), buildFakeTmux(tmuxLogPath));
|
|
2067
|
+
await chmod(join(fakeBinDir, 'tmux'), 0o755);
|
|
2068
|
+
await writeFile(join(stateDir, 'ralph-state.json'), JSON.stringify({
|
|
2069
|
+
active: true,
|
|
2070
|
+
current_phase: 'starting',
|
|
2071
|
+
started_at: staleStartedAt,
|
|
2072
|
+
tmux_pane_id: '%42',
|
|
2073
|
+
}, null, 2));
|
|
2074
|
+
await writeFile(join(stateDir, 'hud-state.json'), JSON.stringify({
|
|
2075
|
+
last_progress_at: new Date(Date.now() - 5 * 60_000).toISOString(),
|
|
2076
|
+
}, null, 2));
|
|
2077
|
+
await writeFile(watcherStatePath, JSON.stringify({
|
|
2078
|
+
ralph_continue_steer: {
|
|
2079
|
+
last_sent_at: new Date(Date.now() - 61_000).toISOString(),
|
|
2080
|
+
},
|
|
2081
|
+
}, null, 2));
|
|
2082
|
+
const watcherScript = new URL('../../../dist/scripts/notify-fallback-watcher.js', import.meta.url).pathname;
|
|
2083
|
+
const notifyHook = new URL('../../../dist/scripts/notify-hook.js', import.meta.url).pathname;
|
|
2084
|
+
const run = spawnSync(process.execPath, [watcherScript, '--once', '--cwd', wd, '--notify-script', notifyHook, '--poll-ms', '50'], {
|
|
2085
|
+
encoding: 'utf-8',
|
|
2086
|
+
env: buildCleanNotifyEnv({
|
|
2087
|
+
PATH: `${fakeBinDir}:${process.env.PATH || ''}`,
|
|
2088
|
+
}),
|
|
2089
|
+
});
|
|
2090
|
+
assert.equal(run.status, 0, run.stderr || run.stdout);
|
|
2091
|
+
const tmuxLog = await readFile(tmuxLogPath, 'utf8').catch(() => '');
|
|
2092
|
+
const sends = tmuxLog.match(/send-keys -t %42 -l Ralph loop active continue \[OMX_TMUX_INJECT\]/g) || [];
|
|
2093
|
+
assert.equal(sends.length, 0, 'stale starting phase should block Ralph continue steer');
|
|
2094
|
+
const watcherState = JSON.parse(await readFile(watcherStatePath, 'utf-8'));
|
|
2095
|
+
assert.equal(watcherState.ralph_continue_steer?.active, false);
|
|
2096
|
+
assert.equal(watcherState.ralph_continue_steer?.last_reason, 'terminal');
|
|
2097
|
+
}
|
|
2098
|
+
finally {
|
|
2099
|
+
await rm(wd, { recursive: true, force: true });
|
|
2100
|
+
}
|
|
2101
|
+
});
|
|
1849
2102
|
it('globally debounces Ralph continue steer across concurrent watcher instances', async () => {
|
|
1850
2103
|
const wd = await mkdtemp(join(tmpdir(), 'omx-fallback-ralph-global-debounce-'));
|
|
1851
2104
|
const fakeBinDir = join(wd, 'fake-bin');
|
|
@@ -2178,7 +2431,7 @@ exit 0
|
|
|
2178
2431
|
current_phase: 'executing',
|
|
2179
2432
|
tmux_pane_id: '%42',
|
|
2180
2433
|
}, null, 2));
|
|
2181
|
-
await writeFile(join(
|
|
2434
|
+
await writeFile(join(sessionStateDir, 'hud-state.json'), JSON.stringify({
|
|
2182
2435
|
last_progress_at: new Date(Date.now() - 61_000).toISOString(),
|
|
2183
2436
|
}, null, 2));
|
|
2184
2437
|
await writeFile(join(stateDir, 'notify-fallback-state.json'), JSON.stringify({
|
|
@@ -2320,6 +2573,123 @@ exit 0
|
|
|
2320
2573
|
await rm(tempHome, { recursive: true, force: true });
|
|
2321
2574
|
}
|
|
2322
2575
|
});
|
|
2576
|
+
it('stays alive after parent exit when coarse team-state is missing but canonical team is active for the current session', async () => {
|
|
2577
|
+
const wd = await mkdtemp(join(tmpdir(), 'omx-fallback-parent-gone-canonical-team-'));
|
|
2578
|
+
const tempHome = await mkdtemp(join(tmpdir(), 'omx-fallback-parent-gone-canonical-home-'));
|
|
2579
|
+
const fakeBinDir = join(wd, 'fake-bin');
|
|
2580
|
+
const tmuxLogPath = join(wd, 'tmux.log');
|
|
2581
|
+
let child;
|
|
2582
|
+
try {
|
|
2583
|
+
await mkdir(fakeBinDir, { recursive: true });
|
|
2584
|
+
await writeCanonicalWatcherTeamFixture(wd, {
|
|
2585
|
+
teamName: 'dispatch-team',
|
|
2586
|
+
sessionId: 'sess-parent-canonical',
|
|
2587
|
+
ownerSessionId: 'sess-parent-canonical',
|
|
2588
|
+
});
|
|
2589
|
+
await writeFile(join(fakeBinDir, 'tmux'), buildFakeTmux(tmuxLogPath));
|
|
2590
|
+
await chmod(join(fakeBinDir, 'tmux'), 0o755);
|
|
2591
|
+
const watcherScript = new URL('../../../dist/scripts/notify-fallback-watcher.js', import.meta.url).pathname;
|
|
2592
|
+
const notifyHook = new URL('../../../dist/scripts/notify-hook.js', import.meta.url).pathname;
|
|
2593
|
+
const logPath = join(wd, '.omx', 'logs', `notify-fallback-${new Date().toISOString().split('T')[0]}.jsonl`);
|
|
2594
|
+
const shortLivedParent = spawn(process.execPath, ['-e', 'setTimeout(() => process.exit(0), 10)'], {
|
|
2595
|
+
stdio: 'ignore',
|
|
2596
|
+
});
|
|
2597
|
+
assert.ok(shortLivedParent.pid, 'expected short-lived parent pid');
|
|
2598
|
+
const parentPid = shortLivedParent.pid;
|
|
2599
|
+
await once(shortLivedParent, 'exit');
|
|
2600
|
+
child = spawn(process.execPath, [
|
|
2601
|
+
watcherScript,
|
|
2602
|
+
'--cwd',
|
|
2603
|
+
wd,
|
|
2604
|
+
'--notify-script',
|
|
2605
|
+
notifyHook,
|
|
2606
|
+
'--poll-ms',
|
|
2607
|
+
'50',
|
|
2608
|
+
'--parent-pid',
|
|
2609
|
+
String(parentPid),
|
|
2610
|
+
'--max-lifetime-ms',
|
|
2611
|
+
'5000',
|
|
2612
|
+
], {
|
|
2613
|
+
cwd: wd,
|
|
2614
|
+
stdio: 'ignore',
|
|
2615
|
+
env: buildCleanNotifyEnv({ HOME: tempHome, PATH: `${fakeBinDir}:${process.env.PATH || ''}` }),
|
|
2616
|
+
});
|
|
2617
|
+
await waitFor(async () => isPidAlive(child?.pid), 4000, 50);
|
|
2618
|
+
await waitFor(async () => {
|
|
2619
|
+
const logEntries = (await readFile(logPath, 'utf-8').catch(() => ''))
|
|
2620
|
+
.trim()
|
|
2621
|
+
.split('\n')
|
|
2622
|
+
.filter(Boolean)
|
|
2623
|
+
.map((line) => JSON.parse(line));
|
|
2624
|
+
return logEntries.some((entry) => (entry.type === 'watcher_parent_guard' && entry.reason === 'parent_gone_deferred_for_active_team'));
|
|
2625
|
+
}, 4000, 50);
|
|
2626
|
+
assert.ok(isPidAlive(child.pid), 'expected watcher to stay alive while canonical team panes remain active');
|
|
2627
|
+
}
|
|
2628
|
+
finally {
|
|
2629
|
+
if (child && isPidAlive(child.pid)) {
|
|
2630
|
+
child.kill('SIGTERM');
|
|
2631
|
+
await waitForExit(child, 4000).catch(() => { });
|
|
2632
|
+
}
|
|
2633
|
+
await rm(wd, { recursive: true, force: true });
|
|
2634
|
+
await rm(tempHome, { recursive: true, force: true });
|
|
2635
|
+
}
|
|
2636
|
+
});
|
|
2637
|
+
it('does not defer parent-loss shutdown when canonical owner session is blank and coarse team-state is missing', async () => {
|
|
2638
|
+
const wd = await mkdtemp(join(tmpdir(), 'omx-fallback-parent-gone-ownerless-team-'));
|
|
2639
|
+
const tempHome = await mkdtemp(join(tmpdir(), 'omx-fallback-parent-gone-ownerless-home-'));
|
|
2640
|
+
const fakeBinDir = join(wd, 'fake-bin');
|
|
2641
|
+
const tmuxLogPath = join(wd, 'tmux.log');
|
|
2642
|
+
let child;
|
|
2643
|
+
try {
|
|
2644
|
+
await mkdir(fakeBinDir, { recursive: true });
|
|
2645
|
+
await writeCanonicalWatcherTeamFixture(wd, {
|
|
2646
|
+
teamName: 'dispatch-team',
|
|
2647
|
+
sessionId: 'sess-parent-ownerless',
|
|
2648
|
+
ownerSessionId: '',
|
|
2649
|
+
});
|
|
2650
|
+
await writeFile(join(fakeBinDir, 'tmux'), buildFakeTmux(tmuxLogPath));
|
|
2651
|
+
await chmod(join(fakeBinDir, 'tmux'), 0o755);
|
|
2652
|
+
const watcherScript = new URL('../../../dist/scripts/notify-fallback-watcher.js', import.meta.url).pathname;
|
|
2653
|
+
const notifyHook = new URL('../../../dist/scripts/notify-hook.js', import.meta.url).pathname;
|
|
2654
|
+
const logPath = join(wd, '.omx', 'logs', `notify-fallback-${new Date().toISOString().split('T')[0]}.jsonl`);
|
|
2655
|
+
const shortLivedParent = spawn(process.execPath, ['-e', 'setTimeout(() => process.exit(0), 10)'], {
|
|
2656
|
+
stdio: 'ignore',
|
|
2657
|
+
});
|
|
2658
|
+
assert.ok(shortLivedParent.pid, 'expected short-lived parent pid');
|
|
2659
|
+
const parentPid = shortLivedParent.pid;
|
|
2660
|
+
await once(shortLivedParent, 'exit');
|
|
2661
|
+
child = spawn(process.execPath, [
|
|
2662
|
+
watcherScript,
|
|
2663
|
+
'--cwd',
|
|
2664
|
+
wd,
|
|
2665
|
+
'--notify-script',
|
|
2666
|
+
notifyHook,
|
|
2667
|
+
'--poll-ms',
|
|
2668
|
+
'50',
|
|
2669
|
+
'--parent-pid',
|
|
2670
|
+
String(parentPid),
|
|
2671
|
+
'--max-lifetime-ms',
|
|
2672
|
+
'5000',
|
|
2673
|
+
], {
|
|
2674
|
+
cwd: wd,
|
|
2675
|
+
stdio: 'ignore',
|
|
2676
|
+
env: buildCleanNotifyEnv({ HOME: tempHome, PATH: `${fakeBinDir}:${process.env.PATH || ''}` }),
|
|
2677
|
+
});
|
|
2678
|
+
await waitForExit(child, 4000);
|
|
2679
|
+
assert.equal(child.exitCode, 0);
|
|
2680
|
+
const logEntries = (await readFile(logPath, 'utf-8')).trim().split('\n').filter(Boolean).map((line) => JSON.parse(line));
|
|
2681
|
+
assert.ok(logEntries.some((entry) => (entry.type === 'watcher_stop' && entry.reason === 'parent_gone')));
|
|
2682
|
+
assert.ok(!logEntries.some((entry) => (entry.type === 'watcher_parent_guard' && entry.reason === 'parent_gone_deferred_for_active_team')), 'ownerless canonical team must not defer parent-loss shutdown');
|
|
2683
|
+
}
|
|
2684
|
+
finally {
|
|
2685
|
+
if (child && isPidAlive(child.pid)) {
|
|
2686
|
+
child.kill('SIGTERM');
|
|
2687
|
+
await waitForExit(child, 4000).catch(() => { });
|
|
2688
|
+
}
|
|
2689
|
+
await rm(wd, { recursive: true, force: true });
|
|
2690
|
+
await rm(tempHome, { recursive: true, force: true });
|
|
2691
|
+
}
|
|
2692
|
+
});
|
|
2323
2693
|
it('does not defer parent-loss shutdown for a team that is already terminal in phase.json', async () => {
|
|
2324
2694
|
const wd = await mkdtemp(join(tmpdir(), 'omx-fallback-parent-gone-terminal-team-'));
|
|
2325
2695
|
const tempHome = await mkdtemp(join(tmpdir(), 'omx-fallback-parent-gone-terminal-team-home-'));
|