oh-my-codex 0.11.11 → 0.11.13
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.de.md +12 -6
- package/README.el.md +223 -0
- package/README.es.md +12 -6
- package/README.fr.md +11 -5
- package/README.it.md +12 -6
- package/README.ja.md +12 -6
- package/README.ko.md +12 -6
- package/README.md +56 -28
- package/README.pl.md +216 -0
- package/README.pt.md +12 -6
- package/README.ru.md +12 -6
- package/README.tr.md +12 -6
- package/README.vi.md +148 -183
- package/README.zh-TW.md +14 -17
- package/README.zh.md +12 -6
- package/crates/omx-runtime-core/src/engine.rs +122 -4
- package/crates/omx-runtime-core/src/lib.rs +17 -0
- package/dist/autoresearch/contracts.d.ts.map +1 -1
- package/dist/autoresearch/contracts.js +1 -0
- package/dist/autoresearch/contracts.js.map +1 -1
- package/dist/autoresearch/runtime.d.ts.map +1 -1
- package/dist/autoresearch/runtime.js +7 -1
- package/dist/autoresearch/runtime.js.map +1 -1
- package/dist/cli/__tests__/agents.test.js +24 -1
- package/dist/cli/__tests__/agents.test.js.map +1 -1
- package/dist/cli/__tests__/autoresearch.test.js +11 -0
- package/dist/cli/__tests__/autoresearch.test.js.map +1 -1
- package/dist/cli/__tests__/cleanup.test.js +117 -4
- package/dist/cli/__tests__/cleanup.test.js.map +1 -1
- package/dist/cli/__tests__/doctor-warning-copy.test.js +33 -3
- package/dist/cli/__tests__/doctor-warning-copy.test.js.map +1 -1
- package/dist/cli/__tests__/error-handling-warnings.test.js +13 -0
- package/dist/cli/__tests__/error-handling-warnings.test.js.map +1 -1
- package/dist/cli/__tests__/exec.test.js +6 -0
- package/dist/cli/__tests__/exec.test.js.map +1 -1
- package/dist/cli/__tests__/index.test.js +101 -1
- package/dist/cli/__tests__/index.test.js.map +1 -1
- package/dist/cli/__tests__/launch-fallback.test.js +3 -0
- package/dist/cli/__tests__/launch-fallback.test.js.map +1 -1
- package/dist/cli/__tests__/package-bin-contract.test.js +10 -0
- package/dist/cli/__tests__/package-bin-contract.test.js.map +1 -1
- package/dist/cli/__tests__/packaged-script-resolution.test.js +4 -3
- package/dist/cli/__tests__/packaged-script-resolution.test.js.map +1 -1
- package/dist/cli/__tests__/resume.test.js +6 -0
- package/dist/cli/__tests__/resume.test.js.map +1 -1
- package/dist/cli/__tests__/setup-refresh.test.js +29 -12
- package/dist/cli/__tests__/setup-refresh.test.js.map +1 -1
- package/dist/cli/__tests__/setup-scope.test.js +1 -1
- package/dist/cli/__tests__/setup-scope.test.js.map +1 -1
- package/dist/cli/__tests__/star-prompt.test.js +16 -0
- package/dist/cli/__tests__/star-prompt.test.js.map +1 -1
- package/dist/cli/__tests__/uninstall.test.js +112 -1
- package/dist/cli/__tests__/uninstall.test.js.map +1 -1
- package/dist/cli/__tests__/windows-popup-loop-contract.test.d.ts +2 -0
- package/dist/cli/__tests__/windows-popup-loop-contract.test.d.ts.map +1 -0
- package/dist/cli/__tests__/windows-popup-loop-contract.test.js +30 -0
- package/dist/cli/__tests__/windows-popup-loop-contract.test.js.map +1 -0
- package/dist/cli/agents.d.ts.map +1 -1
- package/dist/cli/agents.js +9 -3
- package/dist/cli/agents.js.map +1 -1
- package/dist/cli/autoresearch-guided.d.ts.map +1 -1
- package/dist/cli/autoresearch-guided.js +9 -3
- package/dist/cli/autoresearch-guided.js.map +1 -1
- package/dist/cli/autoresearch.d.ts.map +1 -1
- package/dist/cli/autoresearch.js +8 -2
- package/dist/cli/autoresearch.js.map +1 -1
- package/dist/cli/cleanup.d.ts +2 -0
- package/dist/cli/cleanup.d.ts.map +1 -1
- package/dist/cli/cleanup.js +27 -1
- package/dist/cli/cleanup.js.map +1 -1
- package/dist/cli/doctor.js +7 -0
- package/dist/cli/doctor.js.map +1 -1
- package/dist/cli/index.d.ts +9 -1
- package/dist/cli/index.d.ts.map +1 -1
- package/dist/cli/index.js +171 -55
- package/dist/cli/index.js.map +1 -1
- package/dist/cli/setup.d.ts.map +1 -1
- package/dist/cli/setup.js +18 -15
- package/dist/cli/setup.js.map +1 -1
- package/dist/cli/star-prompt.d.ts.map +1 -1
- package/dist/cli/star-prompt.js +2 -0
- package/dist/cli/star-prompt.js.map +1 -1
- package/dist/cli/team.d.ts.map +1 -1
- package/dist/cli/team.js +5 -1
- package/dist/cli/team.js.map +1 -1
- package/dist/cli/tmux-hook.d.ts.map +1 -1
- package/dist/cli/tmux-hook.js +4 -1
- package/dist/cli/tmux-hook.js.map +1 -1
- package/dist/cli/uninstall.d.ts.map +1 -1
- package/dist/cli/uninstall.js +26 -0
- package/dist/cli/uninstall.js.map +1 -1
- package/dist/cli/update.d.ts.map +1 -1
- package/dist/cli/update.js +1 -0
- package/dist/cli/update.js.map +1 -1
- package/dist/compat/__tests__/rust-runtime-compat.test.js +84 -1
- package/dist/compat/__tests__/rust-runtime-compat.test.js.map +1 -1
- package/dist/config/__tests__/generator-idempotent.test.js +4 -4
- package/dist/config/__tests__/generator-idempotent.test.js.map +1 -1
- package/dist/config/__tests__/mcp-registry.test.js +13 -16
- package/dist/config/__tests__/mcp-registry.test.js.map +1 -1
- package/dist/config/mcp-registry.d.ts +1 -0
- package/dist/config/mcp-registry.d.ts.map +1 -1
- package/dist/config/mcp-registry.js +4 -4
- package/dist/config/mcp-registry.js.map +1 -1
- package/dist/config/models.d.ts +1 -0
- package/dist/config/models.d.ts.map +1 -1
- package/dist/config/models.js +39 -1
- package/dist/config/models.js.map +1 -1
- package/dist/hooks/__tests__/keyword-detector.test.js +12 -1
- package/dist/hooks/__tests__/keyword-detector.test.js.map +1 -1
- package/dist/hooks/__tests__/notify-fallback-watcher.test.js +554 -18
- package/dist/hooks/__tests__/notify-fallback-watcher.test.js.map +1 -1
- package/dist/hooks/__tests__/notify-hook-auto-nudge.test.js +347 -16
- package/dist/hooks/__tests__/notify-hook-auto-nudge.test.js.map +1 -1
- package/dist/hooks/__tests__/notify-hook-modules.test.js +5 -0
- package/dist/hooks/__tests__/notify-hook-modules.test.js.map +1 -1
- package/dist/hooks/__tests__/notify-hook-ralph-resume.test.d.ts +2 -0
- package/dist/hooks/__tests__/notify-hook-ralph-resume.test.d.ts.map +1 -0
- package/dist/hooks/__tests__/notify-hook-ralph-resume.test.js +597 -0
- package/dist/hooks/__tests__/notify-hook-ralph-resume.test.js.map +1 -0
- package/dist/hooks/__tests__/notify-hook-regression-205.test.js +19 -1
- package/dist/hooks/__tests__/notify-hook-regression-205.test.js.map +1 -1
- package/dist/hooks/__tests__/notify-hook-session-scope.test.js +73 -53
- package/dist/hooks/__tests__/notify-hook-session-scope.test.js.map +1 -1
- package/dist/hooks/__tests__/notify-hook-team-dispatch.test.js +193 -2
- package/dist/hooks/__tests__/notify-hook-team-dispatch.test.js.map +1 -1
- package/dist/hooks/__tests__/notify-hook-team-leader-nudge.test.js +183 -0
- package/dist/hooks/__tests__/notify-hook-team-leader-nudge.test.js.map +1 -1
- package/dist/hooks/__tests__/notify-hook-tmux-heal.test.js +255 -97
- package/dist/hooks/__tests__/notify-hook-tmux-heal.test.js.map +1 -1
- package/dist/hooks/__tests__/notify-hook-tmux-scrollback.test.js +0 -0
- package/dist/hooks/__tests__/notify-hook-tmux-scrollback.test.js.map +1 -1
- package/dist/hooks/__tests__/notify-hook-worker-idle.test.js +46 -0
- package/dist/hooks/__tests__/notify-hook-worker-idle.test.js.map +1 -1
- package/dist/hooks/__tests__/prompt-team-routing.test.js +34 -0
- package/dist/hooks/__tests__/prompt-team-routing.test.js.map +1 -1
- package/dist/hooks/__tests__/tmux-hook-engine.test.js +32 -1
- package/dist/hooks/__tests__/tmux-hook-engine.test.js.map +1 -1
- package/dist/hooks/code-simplifier/index.d.ts.map +1 -1
- package/dist/hooks/code-simplifier/index.js +1 -0
- package/dist/hooks/code-simplifier/index.js.map +1 -1
- package/dist/hooks/codebase-map.d.ts.map +1 -1
- package/dist/hooks/codebase-map.js +1 -0
- package/dist/hooks/codebase-map.js.map +1 -1
- package/dist/hooks/extensibility/sdk/tmux.d.ts.map +1 -1
- package/dist/hooks/extensibility/sdk/tmux.js +3 -1
- package/dist/hooks/extensibility/sdk/tmux.js.map +1 -1
- package/dist/hooks/keyword-detector.d.ts +1 -0
- package/dist/hooks/keyword-detector.d.ts.map +1 -1
- package/dist/hooks/keyword-detector.js +48 -0
- package/dist/hooks/keyword-detector.js.map +1 -1
- package/dist/hooks/prompt-guidance-contract.d.ts.map +1 -1
- package/dist/hooks/prompt-guidance-contract.js +6 -0
- package/dist/hooks/prompt-guidance-contract.js.map +1 -1
- package/dist/hooks/session.d.ts.map +1 -1
- package/dist/hooks/session.js +1 -0
- package/dist/hooks/session.js.map +1 -1
- package/dist/hud/__tests__/state.test.js +70 -1
- package/dist/hud/__tests__/state.test.js.map +1 -1
- package/dist/hud/authority.d.ts.map +1 -1
- package/dist/hud/authority.js +1 -0
- package/dist/hud/authority.js.map +1 -1
- package/dist/hud/state.d.ts.map +1 -1
- package/dist/hud/state.js +52 -0
- package/dist/hud/state.js.map +1 -1
- package/dist/mcp/state-server.d.ts.map +1 -1
- package/dist/mcp/state-server.js +5 -0
- package/dist/mcp/state-server.js.map +1 -1
- package/dist/modes/__tests__/base-session-scope.test.js +46 -0
- package/dist/modes/__tests__/base-session-scope.test.js.map +1 -1
- package/dist/modes/base.d.ts.map +1 -1
- package/dist/modes/base.js +4 -0
- package/dist/modes/base.js.map +1 -1
- package/dist/notifications/__tests__/custom-alias-enablement.test.d.ts +2 -0
- package/dist/notifications/__tests__/custom-alias-enablement.test.d.ts.map +1 -0
- package/dist/notifications/__tests__/custom-alias-enablement.test.js +84 -0
- package/dist/notifications/__tests__/custom-alias-enablement.test.js.map +1 -0
- package/dist/notifications/__tests__/idle-cooldown.test.js +55 -0
- package/dist/notifications/__tests__/idle-cooldown.test.js.map +1 -1
- package/dist/notifications/idle-cooldown.d.ts +8 -6
- package/dist/notifications/idle-cooldown.d.ts.map +1 -1
- package/dist/notifications/idle-cooldown.js +53 -22
- package/dist/notifications/idle-cooldown.js.map +1 -1
- package/dist/notifications/notifier.js +1 -1
- package/dist/notifications/notifier.js.map +1 -1
- package/dist/notifications/reply-listener.d.ts.map +1 -1
- package/dist/notifications/reply-listener.js +1 -0
- package/dist/notifications/reply-listener.js.map +1 -1
- package/dist/notifications/tmux.d.ts.map +1 -1
- package/dist/notifications/tmux.js +4 -0
- package/dist/notifications/tmux.js.map +1 -1
- package/dist/openclaw/config.js +2 -2
- package/dist/openclaw/config.js.map +1 -1
- package/dist/runtime/bridge.d.ts +2 -0
- package/dist/runtime/bridge.d.ts.map +1 -1
- package/dist/runtime/bridge.js +8 -0
- package/dist/runtime/bridge.js.map +1 -1
- package/dist/scripts/notify-fallback-watcher.js +103 -53
- package/dist/scripts/notify-fallback-watcher.js.map +1 -1
- package/dist/scripts/notify-hook/auto-nudge.d.ts +2 -1
- package/dist/scripts/notify-hook/auto-nudge.d.ts.map +1 -1
- package/dist/scripts/notify-hook/auto-nudge.js +90 -104
- package/dist/scripts/notify-hook/auto-nudge.js.map +1 -1
- package/dist/scripts/notify-hook/managed-tmux.d.ts +19 -0
- package/dist/scripts/notify-hook/managed-tmux.d.ts.map +1 -0
- package/dist/scripts/notify-hook/managed-tmux.js +320 -0
- package/dist/scripts/notify-hook/managed-tmux.js.map +1 -0
- package/dist/scripts/notify-hook/operational-events.d.ts.map +1 -1
- package/dist/scripts/notify-hook/operational-events.js +2 -0
- package/dist/scripts/notify-hook/operational-events.js.map +1 -1
- package/dist/scripts/notify-hook/ralph-session-resume.d.ts +22 -0
- package/dist/scripts/notify-hook/ralph-session-resume.d.ts.map +1 -0
- package/dist/scripts/notify-hook/ralph-session-resume.js +277 -0
- package/dist/scripts/notify-hook/ralph-session-resume.js.map +1 -0
- package/dist/scripts/notify-hook/state-io.d.ts +1 -1
- package/dist/scripts/notify-hook/state-io.d.ts.map +1 -1
- package/dist/scripts/notify-hook/state-io.js +2 -10
- package/dist/scripts/notify-hook/state-io.js.map +1 -1
- package/dist/scripts/notify-hook/team-dispatch.d.ts +1 -1
- package/dist/scripts/notify-hook/team-dispatch.d.ts.map +1 -1
- package/dist/scripts/notify-hook/team-dispatch.js +123 -72
- package/dist/scripts/notify-hook/team-dispatch.js.map +1 -1
- package/dist/scripts/notify-hook/team-leader-nudge.d.ts +2 -1
- package/dist/scripts/notify-hook/team-leader-nudge.d.ts.map +1 -1
- package/dist/scripts/notify-hook/team-leader-nudge.js +13 -5
- package/dist/scripts/notify-hook/team-leader-nudge.js.map +1 -1
- package/dist/scripts/notify-hook/team-tmux-guard.d.ts.map +1 -1
- package/dist/scripts/notify-hook/team-tmux-guard.js +1 -19
- package/dist/scripts/notify-hook/team-tmux-guard.js.map +1 -1
- package/dist/scripts/notify-hook/team-worker.js +4 -4
- package/dist/scripts/notify-hook/team-worker.js.map +1 -1
- package/dist/scripts/notify-hook/tmux-injection.d.ts +1 -1
- package/dist/scripts/notify-hook/tmux-injection.d.ts.map +1 -1
- package/dist/scripts/notify-hook/tmux-injection.js +102 -35
- package/dist/scripts/notify-hook/tmux-injection.js.map +1 -1
- package/dist/scripts/notify-hook.js +144 -20
- package/dist/scripts/notify-hook.js.map +1 -1
- package/dist/scripts/run-provider-advisor.js +2 -0
- package/dist/scripts/run-provider-advisor.js.map +1 -1
- package/dist/scripts/run-test-files.d.ts +2 -0
- package/dist/scripts/run-test-files.d.ts.map +1 -0
- package/dist/scripts/run-test-files.js +41 -0
- package/dist/scripts/run-test-files.js.map +1 -0
- package/dist/scripts/tmux-hook-engine.d.ts +2 -0
- package/dist/scripts/tmux-hook-engine.d.ts.map +1 -1
- package/dist/scripts/tmux-hook-engine.js +15 -0
- package/dist/scripts/tmux-hook-engine.js.map +1 -1
- package/dist/team/__tests__/api-interop.test.js +136 -4
- package/dist/team/__tests__/api-interop.test.js.map +1 -1
- package/dist/team/__tests__/leader-activity.test.js +107 -2
- package/dist/team/__tests__/leader-activity.test.js.map +1 -1
- package/dist/team/__tests__/runtime-cli.test.js +32 -0
- package/dist/team/__tests__/runtime-cli.test.js.map +1 -1
- package/dist/team/__tests__/runtime.test.js +148 -0
- package/dist/team/__tests__/runtime.test.js.map +1 -1
- package/dist/team/__tests__/shutdown-fallback.test.js +13 -0
- package/dist/team/__tests__/shutdown-fallback.test.js.map +1 -1
- package/dist/team/__tests__/state-root.test.js +11 -1
- package/dist/team/__tests__/state-root.test.js.map +1 -1
- package/dist/team/__tests__/state.test.js +237 -0
- package/dist/team/__tests__/state.test.js.map +1 -1
- package/dist/team/__tests__/tmux-session.test.js +521 -2
- 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 +41 -31
- package/dist/team/api-interop.js.map +1 -1
- package/dist/team/commit-hygiene.d.ts +60 -0
- package/dist/team/commit-hygiene.d.ts.map +1 -0
- package/dist/team/commit-hygiene.js +232 -0
- package/dist/team/commit-hygiene.js.map +1 -0
- package/dist/team/leader-activity.d.ts.map +1 -1
- package/dist/team/leader-activity.js +56 -4
- package/dist/team/leader-activity.js.map +1 -1
- package/dist/team/runtime-cli.d.ts +9 -1
- package/dist/team/runtime-cli.d.ts.map +1 -1
- package/dist/team/runtime-cli.js +15 -6
- package/dist/team/runtime-cli.js.map +1 -1
- package/dist/team/runtime.d.ts +7 -2
- package/dist/team/runtime.d.ts.map +1 -1
- package/dist/team/runtime.js +392 -171
- package/dist/team/runtime.js.map +1 -1
- package/dist/team/scaling.d.ts.map +1 -1
- package/dist/team/scaling.js +6 -2
- package/dist/team/scaling.js.map +1 -1
- package/dist/team/state/dispatch.d.ts +2 -0
- package/dist/team/state/dispatch.d.ts.map +1 -1
- package/dist/team/state/dispatch.js +86 -40
- package/dist/team/state/dispatch.js.map +1 -1
- package/dist/team/state/mailbox.d.ts +3 -0
- package/dist/team/state/mailbox.d.ts.map +1 -1
- package/dist/team/state/mailbox.js +93 -19
- package/dist/team/state/mailbox.js.map +1 -1
- package/dist/team/state-root.d.ts +1 -1
- package/dist/team/state-root.d.ts.map +1 -1
- package/dist/team/state-root.js +8 -3
- package/dist/team/state-root.js.map +1 -1
- package/dist/team/state.d.ts.map +1 -1
- package/dist/team/state.js +96 -2
- package/dist/team/state.js.map +1 -1
- package/dist/team/tmux-session.d.ts.map +1 -1
- package/dist/team/tmux-session.js +81 -29
- package/dist/team/tmux-session.js.map +1 -1
- package/dist/team/worker-bootstrap.d.ts.map +1 -1
- package/dist/team/worker-bootstrap.js +4 -0
- package/dist/team/worker-bootstrap.js.map +1 -1
- package/dist/team/worktree.d.ts.map +1 -1
- package/dist/team/worktree.js +9 -0
- package/dist/team/worktree.js.map +1 -1
- package/dist/utils/__tests__/paths.test.js +98 -11
- package/dist/utils/__tests__/paths.test.js.map +1 -1
- package/dist/utils/__tests__/platform-command.test.js +101 -2
- package/dist/utils/__tests__/platform-command.test.js.map +1 -1
- package/dist/utils/git-layout.d.ts +8 -0
- package/dist/utils/git-layout.d.ts.map +1 -0
- package/dist/utils/git-layout.js +58 -0
- package/dist/utils/git-layout.js.map +1 -0
- package/dist/utils/paths.d.ts +3 -0
- package/dist/utils/paths.d.ts.map +1 -1
- package/dist/utils/paths.js +14 -4
- package/dist/utils/paths.js.map +1 -1
- package/dist/utils/platform-command.d.ts.map +1 -1
- package/dist/utils/platform-command.js +35 -3
- package/dist/utils/platform-command.js.map +1 -1
- package/package.json +9 -5
- package/src/scripts/notify-fallback-watcher.ts +103 -53
- package/src/scripts/notify-hook/auto-nudge.ts +97 -103
- package/src/scripts/notify-hook/managed-tmux.ts +324 -0
- package/src/scripts/notify-hook/operational-events.ts +2 -0
- package/src/scripts/notify-hook/ralph-session-resume.ts +337 -0
- package/src/scripts/notify-hook/state-io.ts +2 -10
- package/src/scripts/notify-hook/team-dispatch.ts +131 -66
- package/src/scripts/notify-hook/team-leader-nudge.ts +19 -5
- package/src/scripts/notify-hook/team-tmux-guard.ts +0 -20
- package/src/scripts/notify-hook/team-worker.ts +4 -4
- package/src/scripts/notify-hook/tmux-injection.ts +103 -33
- package/src/scripts/notify-hook.ts +150 -21
- package/src/scripts/run-provider-advisor.ts +4 -2
- package/src/scripts/run-test-files.ts +48 -0
- package/src/scripts/tmux-hook-engine.ts +16 -0
- package/templates/AGENTS.md +51 -43
|
@@ -7,6 +7,7 @@ import { join } from 'node:path';
|
|
|
7
7
|
import { spawn, spawnSync } from 'node:child_process';
|
|
8
8
|
import { randomUUID } from 'node:crypto';
|
|
9
9
|
import { initTeamState, enqueueDispatchRequest, readDispatchRequest } from '../../team/state.js';
|
|
10
|
+
import { buildWindowsMsysBackgroundHelperBootstrapScript } from '../../cli/index.js';
|
|
10
11
|
import { writeSessionStart } from '../session.js';
|
|
11
12
|
async function appendLine(path, line) {
|
|
12
13
|
const prev = await readFile(path, 'utf-8');
|
|
@@ -111,7 +112,7 @@ if [[ "$cmd" == "display-message" ]]; then
|
|
|
111
112
|
exit 0
|
|
112
113
|
fi
|
|
113
114
|
if [[ "$fmt" == "#S" ]]; then
|
|
114
|
-
echo "session-test"
|
|
115
|
+
echo "\${OMX_TEST_TMUX_SESSION_NAME:-session-test}"
|
|
115
116
|
exit 0
|
|
116
117
|
fi
|
|
117
118
|
exit 0
|
|
@@ -129,6 +130,20 @@ if [[ "$cmd" == "send-keys" ]]; then
|
|
|
129
130
|
exit 0
|
|
130
131
|
fi
|
|
131
132
|
if [[ "$cmd" == "list-panes" ]]; then
|
|
133
|
+
target=""
|
|
134
|
+
while [[ "$#" -gt 0 ]]; do
|
|
135
|
+
case "$1" in
|
|
136
|
+
-t)
|
|
137
|
+
shift
|
|
138
|
+
target="$1"
|
|
139
|
+
;;
|
|
140
|
+
esac
|
|
141
|
+
shift || true
|
|
142
|
+
done
|
|
143
|
+
if [[ -n "$target" ]]; then
|
|
144
|
+
printf "%%42\tcodex\tcodex\n"
|
|
145
|
+
exit 0
|
|
146
|
+
fi
|
|
132
147
|
echo "%42 1"
|
|
133
148
|
exit 0
|
|
134
149
|
fi
|
|
@@ -372,6 +387,71 @@ describe('notify-fallback watcher', () => {
|
|
|
372
387
|
await rm(wd, { recursive: true, force: true });
|
|
373
388
|
}
|
|
374
389
|
});
|
|
390
|
+
it('suppresses idle no-op lifecycle and control-plane logs during authority-only one-shot ticks', async () => {
|
|
391
|
+
const wd = await mkdtemp(join(tmpdir(), 'omx-fallback-authority-noop-'));
|
|
392
|
+
try {
|
|
393
|
+
await mkdir(join(wd, '.omx', 'logs'), { recursive: true });
|
|
394
|
+
await mkdir(join(wd, '.omx', 'state'), { recursive: true });
|
|
395
|
+
const watcherScript = new URL('../../../dist/scripts/notify-fallback-watcher.js', import.meta.url).pathname;
|
|
396
|
+
const notifyHook = new URL('../../../dist/scripts/notify-hook.js', import.meta.url).pathname;
|
|
397
|
+
const result = spawnSync(process.execPath, [watcherScript, '--once', '--authority-only', '--cwd', wd, '--notify-script', notifyHook, '--poll-ms', '50'], { encoding: 'utf-8', env: buildCleanNotifyEnv() });
|
|
398
|
+
assert.equal(result.status, 0, result.stderr || result.stdout);
|
|
399
|
+
const watcherStatePath = join(wd, '.omx', 'state', 'notify-fallback-state.json');
|
|
400
|
+
const watcherState = JSON.parse(await readFile(watcherStatePath, 'utf-8'));
|
|
401
|
+
assert.equal(watcherState.authority_only, true);
|
|
402
|
+
assert.equal(watcherState.dispatch_drain?.run_count, 1);
|
|
403
|
+
assert.equal(watcherState.dispatch_drain?.last_result?.processed ?? 0, 0);
|
|
404
|
+
assert.equal(watcherState.leader_nudge?.run_count, 1);
|
|
405
|
+
assert.equal(watcherState.leader_nudge?.precomputed_leader_stale, false);
|
|
406
|
+
assert.equal(watcherState.fallback_auto_nudge?.last_reason, 'hud_state_missing');
|
|
407
|
+
const logPath = join(wd, '.omx', 'logs', `notify-fallback-${new Date().toISOString().split('T')[0]}.jsonl`);
|
|
408
|
+
const logContent = await readFile(logPath, 'utf-8').catch(() => '');
|
|
409
|
+
assert.equal(logContent.trim(), '');
|
|
410
|
+
}
|
|
411
|
+
finally {
|
|
412
|
+
await rm(wd, { recursive: true, force: true });
|
|
413
|
+
}
|
|
414
|
+
});
|
|
415
|
+
it('suppresses authority-only control-plane ticks when only skill-active-state carries the deep-interview input lock', async () => {
|
|
416
|
+
const wd = await mkdtemp(join(tmpdir(), 'omx-fallback-authority-skill-lock-'));
|
|
417
|
+
try {
|
|
418
|
+
await mkdir(join(wd, '.omx', 'logs'), { recursive: true });
|
|
419
|
+
await mkdir(join(wd, '.omx', 'state'), { recursive: true });
|
|
420
|
+
await writeFile(join(wd, '.omx', 'state', 'skill-active-state.json'), JSON.stringify({
|
|
421
|
+
version: 1,
|
|
422
|
+
active: true,
|
|
423
|
+
skill: 'deep-interview',
|
|
424
|
+
keyword: 'deep interview',
|
|
425
|
+
phase: 'planning',
|
|
426
|
+
activated_at: '2026-02-25T00:00:00.000Z',
|
|
427
|
+
updated_at: '2026-02-25T00:00:00.000Z',
|
|
428
|
+
source: 'keyword-detector',
|
|
429
|
+
input_lock: {
|
|
430
|
+
active: true,
|
|
431
|
+
scope: 'deep-interview-auto-approval',
|
|
432
|
+
acquired_at: '2026-02-25T00:00:00.000Z',
|
|
433
|
+
blocked_inputs: ['yes', 'y', 'proceed', 'continue', 'ok', 'sure', 'go ahead', 'next i should'],
|
|
434
|
+
message: 'Deep interview is active; auto-approval shortcuts are blocked until the interview finishes.',
|
|
435
|
+
},
|
|
436
|
+
}, null, 2));
|
|
437
|
+
const watcherScript = new URL('../../../dist/scripts/notify-fallback-watcher.js', import.meta.url).pathname;
|
|
438
|
+
const notifyHook = new URL('../../../dist/scripts/notify-hook.js', import.meta.url).pathname;
|
|
439
|
+
const result = spawnSync(process.execPath, [watcherScript, '--once', '--authority-only', '--cwd', wd, '--notify-script', notifyHook, '--poll-ms', '50'], { encoding: 'utf-8', env: buildCleanNotifyEnv() });
|
|
440
|
+
assert.equal(result.status, 0, result.stderr || result.stdout);
|
|
441
|
+
const watcherStatePath = join(wd, '.omx', 'state', 'notify-fallback-state.json');
|
|
442
|
+
const watcherState = JSON.parse(await readFile(watcherStatePath, 'utf-8'));
|
|
443
|
+
assert.equal(watcherState.authority_only, true);
|
|
444
|
+
assert.equal(watcherState.dispatch_drain?.run_count, 1);
|
|
445
|
+
assert.equal(watcherState.leader_nudge?.run_count, 0);
|
|
446
|
+
assert.equal(watcherState.fallback_auto_nudge?.last_reason, 'init');
|
|
447
|
+
const logPath = join(wd, '.omx', 'logs', `notify-fallback-${new Date().toISOString().split('T')[0]}.jsonl`);
|
|
448
|
+
const logContent = await readFile(logPath, 'utf-8').catch(() => '');
|
|
449
|
+
assert.equal(logContent.trim(), '');
|
|
450
|
+
}
|
|
451
|
+
finally {
|
|
452
|
+
await rm(wd, { recursive: true, force: true });
|
|
453
|
+
}
|
|
454
|
+
});
|
|
375
455
|
it('disables fallback watcher nudges when deep-interview state is active', async () => {
|
|
376
456
|
const wd = await mkdtemp(join(tmpdir(), 'omx-fallback-deep-interview-suppressed-'));
|
|
377
457
|
const fakeBinDir = join(wd, 'fake-bin');
|
|
@@ -423,6 +503,69 @@ describe('notify-fallback watcher', () => {
|
|
|
423
503
|
await rm(wd, { recursive: true, force: true });
|
|
424
504
|
}
|
|
425
505
|
});
|
|
506
|
+
it('disables fallback watcher nudges when only skill-active-state carries the deep-interview input lock', async () => {
|
|
507
|
+
const wd = await mkdtemp(join(tmpdir(), 'omx-fallback-deep-interview-skill-lock-'));
|
|
508
|
+
const fakeBinDir = join(wd, 'fake-bin');
|
|
509
|
+
const tmuxLogPath = join(wd, 'tmux.log');
|
|
510
|
+
try {
|
|
511
|
+
await mkdir(join(wd, '.omx', 'logs'), { recursive: true });
|
|
512
|
+
await mkdir(join(wd, '.omx', 'state', 'team', 'dispatch-team'), { recursive: true });
|
|
513
|
+
await mkdir(fakeBinDir, { recursive: true });
|
|
514
|
+
await writeFile(join(fakeBinDir, 'tmux'), buildFakeTmux(tmuxLogPath));
|
|
515
|
+
await chmod(join(fakeBinDir, 'tmux'), 0o755);
|
|
516
|
+
await writeFile(join(wd, '.omx', 'state', 'skill-active-state.json'), JSON.stringify({
|
|
517
|
+
version: 1,
|
|
518
|
+
active: true,
|
|
519
|
+
skill: 'deep-interview',
|
|
520
|
+
keyword: 'deep interview',
|
|
521
|
+
phase: 'planning',
|
|
522
|
+
activated_at: '2026-02-25T00:00:00.000Z',
|
|
523
|
+
updated_at: '2026-02-25T00:00:00.000Z',
|
|
524
|
+
source: 'keyword-detector',
|
|
525
|
+
input_lock: {
|
|
526
|
+
active: true,
|
|
527
|
+
scope: 'deep-interview-auto-approval',
|
|
528
|
+
acquired_at: '2026-02-25T00:00:00.000Z',
|
|
529
|
+
blocked_inputs: ['yes', 'y', 'proceed', 'continue', 'ok', 'sure', 'go ahead', 'next i should'],
|
|
530
|
+
message: 'Deep interview is active; auto-approval shortcuts are blocked until the interview finishes.',
|
|
531
|
+
},
|
|
532
|
+
}, null, 2));
|
|
533
|
+
await writeFile(join(wd, '.omx', 'state', 'ralph-state.json'), JSON.stringify({
|
|
534
|
+
active: true,
|
|
535
|
+
current_phase: 'executing',
|
|
536
|
+
tmux_pane_id: '%42',
|
|
537
|
+
}, null, 2));
|
|
538
|
+
await writeFile(join(wd, '.omx', 'state', 'team-state.json'), JSON.stringify({
|
|
539
|
+
active: true,
|
|
540
|
+
team_name: 'dispatch-team',
|
|
541
|
+
current_phase: 'team-exec',
|
|
542
|
+
}, null, 2));
|
|
543
|
+
await writeFile(join(wd, '.omx', 'state', 'hud-state.json'), JSON.stringify({
|
|
544
|
+
last_turn_at: new Date(Date.now() - 300_000).toISOString(),
|
|
545
|
+
turn_count: 3,
|
|
546
|
+
last_agent_output: 'Would you like me to continue?',
|
|
547
|
+
}, null, 2));
|
|
548
|
+
await writeFile(join(wd, '.omx', 'state', 'team', 'dispatch-team', 'config.json'), JSON.stringify({
|
|
549
|
+
name: 'dispatch-team',
|
|
550
|
+
tmux_session: 'omx-team-dispatch-team',
|
|
551
|
+
leader_pane_id: '%42',
|
|
552
|
+
}, null, 2));
|
|
553
|
+
const watcherScript = new URL('../../../dist/scripts/notify-fallback-watcher.js', import.meta.url).pathname;
|
|
554
|
+
const notifyHook = new URL('../../../dist/scripts/notify-hook.js', import.meta.url).pathname;
|
|
555
|
+
const result = spawnSync(process.execPath, [watcherScript, '--once', '--cwd', wd, '--notify-script', notifyHook], {
|
|
556
|
+
encoding: 'utf-8',
|
|
557
|
+
env: buildCleanNotifyEnv({ PATH: `${fakeBinDir}:${process.env.PATH || ''}` }),
|
|
558
|
+
});
|
|
559
|
+
assert.equal(result.status, 0, result.stderr || result.stdout);
|
|
560
|
+
const tmuxLog = await readFile(tmuxLogPath, 'utf8').catch(() => '');
|
|
561
|
+
assert.doesNotMatch(tmuxLog, /Ralph loop active continue/);
|
|
562
|
+
assert.doesNotMatch(tmuxLog, /Team dispatch-team:/);
|
|
563
|
+
assert.doesNotMatch(tmuxLog, /yes, proceed \[OMX_TMUX_INJECT\]/);
|
|
564
|
+
}
|
|
565
|
+
finally {
|
|
566
|
+
await rm(wd, { recursive: true, force: true });
|
|
567
|
+
}
|
|
568
|
+
});
|
|
426
569
|
it('runs leader nudge checks from the fallback watcher so stale alerts do not wait for a leader turn', async () => {
|
|
427
570
|
const wd = await mkdtemp(join(tmpdir(), 'omx-fallback-leader-nudge-'));
|
|
428
571
|
const fakeBinDir = join(wd, 'fake-bin');
|
|
@@ -532,9 +675,181 @@ describe('notify-fallback watcher', () => {
|
|
|
532
675
|
const logPath = join(wd, '.omx', 'logs', `notify-fallback-${new Date().toISOString().split('T')[0]}.jsonl`);
|
|
533
676
|
const logEntries = (await readFile(logPath, 'utf-8')).trim().split('\n').filter(Boolean).map((line) => JSON.parse(line));
|
|
534
677
|
const nudgeEvent = logEntries.find((entry) => entry.type === 'leader_nudge_tick');
|
|
535
|
-
assert.
|
|
536
|
-
|
|
537
|
-
|
|
678
|
+
assert.equal(nudgeEvent, undefined);
|
|
679
|
+
}
|
|
680
|
+
finally {
|
|
681
|
+
await rm(wd, { recursive: true, force: true });
|
|
682
|
+
}
|
|
683
|
+
});
|
|
684
|
+
it('runs stalled-worker leader nudges from the fallback watcher even when the leader is not stale', async () => {
|
|
685
|
+
const wd = await mkdtemp(join(tmpdir(), 'omx-fallback-worker-stall-nudge-'));
|
|
686
|
+
const fakeBinDir = join(wd, 'fake-bin');
|
|
687
|
+
const tmuxLogPath = join(wd, 'tmux.log');
|
|
688
|
+
try {
|
|
689
|
+
await mkdir(join(wd, '.omx', 'logs'), { recursive: true });
|
|
690
|
+
await mkdir(join(wd, '.omx', 'state', 'team', 'dispatch-team', 'workers', 'worker-1'), { recursive: true });
|
|
691
|
+
await mkdir(join(wd, '.omx', 'state', 'team', 'dispatch-team', 'tasks'), { recursive: true });
|
|
692
|
+
await mkdir(fakeBinDir, { recursive: true });
|
|
693
|
+
const tmuxScript = `#!/usr/bin/env bash
|
|
694
|
+
set -eu
|
|
695
|
+
echo "$@" >> "${tmuxLogPath}"
|
|
696
|
+
cmd="$1"
|
|
697
|
+
shift || true
|
|
698
|
+
if [[ "$cmd" == "display-message" ]]; then
|
|
699
|
+
target=""
|
|
700
|
+
fmt=""
|
|
701
|
+
while [[ "$#" -gt 0 ]]; do
|
|
702
|
+
case "$1" in
|
|
703
|
+
-t)
|
|
704
|
+
shift
|
|
705
|
+
target="$1"
|
|
706
|
+
;;
|
|
707
|
+
*)
|
|
708
|
+
fmt="$1"
|
|
709
|
+
;;
|
|
710
|
+
esac
|
|
711
|
+
shift || true
|
|
712
|
+
done
|
|
713
|
+
if [[ "$fmt" == "#{pane_in_mode}" ]]; then
|
|
714
|
+
echo "0"
|
|
715
|
+
exit 0
|
|
716
|
+
fi
|
|
717
|
+
if [[ "$fmt" == "#{pane_id}" ]]; then
|
|
718
|
+
echo "\${target:-%42}"
|
|
719
|
+
exit 0
|
|
720
|
+
fi
|
|
721
|
+
if [[ "$fmt" == "#{pane_current_path}" ]]; then
|
|
722
|
+
dirname "${tmuxLogPath}"
|
|
723
|
+
exit 0
|
|
724
|
+
fi
|
|
725
|
+
if [[ "$fmt" == "#{pane_current_command}" ]]; then
|
|
726
|
+
echo "codex"
|
|
727
|
+
exit 0
|
|
728
|
+
fi
|
|
729
|
+
if [[ "$fmt" == "#S" ]]; then
|
|
730
|
+
echo "omx-team-dispatch-team"
|
|
731
|
+
exit 0
|
|
732
|
+
fi
|
|
733
|
+
exit 0
|
|
734
|
+
fi
|
|
735
|
+
if [[ "$cmd" == "send-keys" ]]; then
|
|
736
|
+
exit 0
|
|
737
|
+
fi
|
|
738
|
+
if [[ "$cmd" == "list-panes" ]]; then
|
|
739
|
+
target=""
|
|
740
|
+
while [[ "$#" -gt 0 ]]; do
|
|
741
|
+
case "$1" in
|
|
742
|
+
-t)
|
|
743
|
+
shift
|
|
744
|
+
target="$1"
|
|
745
|
+
;;
|
|
746
|
+
esac
|
|
747
|
+
shift || true
|
|
748
|
+
done
|
|
749
|
+
if [[ -n "$target" ]]; then
|
|
750
|
+
printf "%%42 12345\n%%10 12346\n%%11 12347\n"
|
|
751
|
+
exit 0
|
|
752
|
+
fi
|
|
753
|
+
echo "%42 1"
|
|
754
|
+
exit 0
|
|
755
|
+
fi
|
|
756
|
+
exit 0
|
|
757
|
+
`;
|
|
758
|
+
await writeFile(join(fakeBinDir, 'tmux'), tmuxScript);
|
|
759
|
+
await chmod(join(fakeBinDir, 'tmux'), 0o755);
|
|
760
|
+
const now = Date.now();
|
|
761
|
+
await writeFile(join(wd, '.omx', 'state', 'team-state.json'), JSON.stringify({
|
|
762
|
+
active: true,
|
|
763
|
+
team_name: 'dispatch-team',
|
|
764
|
+
current_phase: 'team-exec',
|
|
765
|
+
}, null, 2));
|
|
766
|
+
await writeFile(join(wd, '.omx', 'state', 'hud-state.json'), JSON.stringify({
|
|
767
|
+
last_turn_at: new Date().toISOString(),
|
|
768
|
+
turn_count: 3,
|
|
769
|
+
}, null, 2));
|
|
770
|
+
await writeFile(join(wd, '.omx', 'state', 'team', 'dispatch-team', 'config.json'), JSON.stringify({
|
|
771
|
+
name: 'dispatch-team',
|
|
772
|
+
tmux_session: 'omx-team-dispatch-team',
|
|
773
|
+
leader_pane_id: '%42',
|
|
774
|
+
workers: [
|
|
775
|
+
{ name: 'worker-1', index: 1, pane_id: '%10' },
|
|
776
|
+
{ name: 'worker-2', index: 2, pane_id: '%11' },
|
|
777
|
+
],
|
|
778
|
+
}, null, 2));
|
|
779
|
+
await writeFile(join(wd, '.omx', 'state', 'team', 'dispatch-team', 'tasks', 'task-1.json'), JSON.stringify({
|
|
780
|
+
id: '1',
|
|
781
|
+
subject: 'Pending work',
|
|
782
|
+
description: 'Needs attention',
|
|
783
|
+
status: 'pending',
|
|
784
|
+
created_at: new Date().toISOString(),
|
|
785
|
+
}, null, 2));
|
|
786
|
+
await writeFile(join(wd, '.omx', 'state', 'team', 'dispatch-team', 'workers', 'worker-1', 'status.json'), JSON.stringify({
|
|
787
|
+
state: 'working',
|
|
788
|
+
current_task_id: '1',
|
|
789
|
+
updated_at: new Date(now - 180_000).toISOString(),
|
|
790
|
+
}, null, 2));
|
|
791
|
+
await writeFile(join(wd, '.omx', 'state', 'team', 'dispatch-team', 'workers', 'worker-1', 'heartbeat.json'), JSON.stringify({
|
|
792
|
+
alive: true,
|
|
793
|
+
pid: 101,
|
|
794
|
+
turn_count: 2,
|
|
795
|
+
last_turn_at: new Date(now - 180_000).toISOString(),
|
|
796
|
+
}, null, 2));
|
|
797
|
+
await writeFile(join(wd, '.omx', 'state', 'team-leader-nudge.json'), JSON.stringify({
|
|
798
|
+
last_nudged_by_team: {
|
|
799
|
+
'dispatch-team': {
|
|
800
|
+
at: new Date(now - 5_000).toISOString(),
|
|
801
|
+
last_message_id: '',
|
|
802
|
+
reason: 'new_mailbox_message',
|
|
803
|
+
},
|
|
804
|
+
},
|
|
805
|
+
progress_by_team: {
|
|
806
|
+
'dispatch-team': {
|
|
807
|
+
signature: JSON.stringify({
|
|
808
|
+
tasks: [{ id: '1', owner: '', status: 'pending' }],
|
|
809
|
+
workers: [
|
|
810
|
+
{
|
|
811
|
+
worker: 'worker-1',
|
|
812
|
+
state: 'working',
|
|
813
|
+
current_task_id: '1',
|
|
814
|
+
status_missing: false,
|
|
815
|
+
turn_count: 2,
|
|
816
|
+
heartbeat_missing: false,
|
|
817
|
+
},
|
|
818
|
+
{
|
|
819
|
+
worker: 'worker-2',
|
|
820
|
+
state: 'unknown',
|
|
821
|
+
current_task_id: '',
|
|
822
|
+
status_missing: true,
|
|
823
|
+
turn_count: null,
|
|
824
|
+
heartbeat_missing: true,
|
|
825
|
+
},
|
|
826
|
+
],
|
|
827
|
+
}),
|
|
828
|
+
last_progress_at: new Date(now - 180_000).toISOString(),
|
|
829
|
+
},
|
|
830
|
+
},
|
|
831
|
+
}, null, 2));
|
|
832
|
+
const watcherScript = new URL('../../../dist/scripts/notify-fallback-watcher.js', import.meta.url).pathname;
|
|
833
|
+
const notifyHook = new URL('../../../dist/scripts/notify-hook.js', import.meta.url).pathname;
|
|
834
|
+
const result = spawnSync(process.execPath, [watcherScript, '--once', '--cwd', wd, '--notify-script', notifyHook], {
|
|
835
|
+
encoding: 'utf-8',
|
|
836
|
+
env: buildCleanNotifyEnv({
|
|
837
|
+
PATH: `${fakeBinDir}:${process.env.PATH || ''}`,
|
|
838
|
+
OMX_TEAM_PROGRESS_STALL_MS: '60000',
|
|
839
|
+
OMX_TEAM_LEADER_NUDGE_MS: '30000',
|
|
840
|
+
OMX_TEAM_LEADER_STALE_MS: '60000',
|
|
841
|
+
}),
|
|
842
|
+
});
|
|
843
|
+
assert.equal(result.status, 0, result.stderr || result.stdout);
|
|
844
|
+
const tmuxLog = await readFile(tmuxLogPath, 'utf8');
|
|
845
|
+
assert.match(tmuxLog, /send-keys -t %42 -l Team dispatch-team: worker panes stalled, no progress 3m/);
|
|
846
|
+
assert.doesNotMatch(tmuxLog, /leader stale/);
|
|
847
|
+
const watcherStatePath = join(wd, '.omx', 'state', 'notify-fallback-state.json');
|
|
848
|
+
const watcherState = JSON.parse(await readFile(watcherStatePath, 'utf-8'));
|
|
849
|
+
assert.equal(watcherState.leader_nudge?.enabled, true);
|
|
850
|
+
assert.equal(watcherState.leader_nudge?.leader_only, true);
|
|
851
|
+
assert.equal(watcherState.leader_nudge?.run_count, 1);
|
|
852
|
+
assert.equal(watcherState.leader_nudge?.precomputed_leader_stale, false);
|
|
538
853
|
}
|
|
539
854
|
finally {
|
|
540
855
|
await rm(wd, { recursive: true, force: true });
|
|
@@ -555,6 +870,7 @@ describe('notify-fallback watcher', () => {
|
|
|
555
870
|
await writeFile(join(codexHome, '.omx-config.json'), JSON.stringify({
|
|
556
871
|
autoNudge: { enabled: true, delaySec: 0, ttlMs: 30_000 },
|
|
557
872
|
}, null, 2));
|
|
873
|
+
await writeSessionStart(wd, 'sess-managed-fallback');
|
|
558
874
|
await writeFile(join(wd, '.omx', 'state', 'hud-state.json'), JSON.stringify({
|
|
559
875
|
last_turn_at: new Date(Date.now() - 6_000).toISOString(),
|
|
560
876
|
turn_count: 7,
|
|
@@ -567,6 +883,8 @@ describe('notify-fallback watcher', () => {
|
|
|
567
883
|
env: buildCleanNotifyEnv({
|
|
568
884
|
PATH: `${fakeBinDir}:${process.env.PATH || ''}`,
|
|
569
885
|
CODEX_HOME: codexHome,
|
|
886
|
+
OMX_SESSION_ID: 'sess-managed-fallback',
|
|
887
|
+
OMX_TEST_TMUX_SESSION_NAME: 'omx-fallback-auto-nudge-stalled-managed',
|
|
570
888
|
TMUX: '1',
|
|
571
889
|
TMUX_PANE: '%42',
|
|
572
890
|
OMX_NOTIFY_FALLBACK_AUTO_NUDGE_STALL_MS: '5000',
|
|
@@ -585,6 +903,98 @@ describe('notify-fallback watcher', () => {
|
|
|
585
903
|
await rm(wd, { recursive: true, force: true });
|
|
586
904
|
}
|
|
587
905
|
});
|
|
906
|
+
it('respects `.omx/tmux-hook.json` enabled:false for fallback auto-nudge', async () => {
|
|
907
|
+
const wd = await mkdtemp(join(tmpdir(), 'omx-fallback-auto-nudge-disabled-'));
|
|
908
|
+
const fakeBinDir = join(wd, 'fake-bin');
|
|
909
|
+
const tmuxLogPath = join(wd, 'tmux.log');
|
|
910
|
+
const codexHome = join(wd, 'codex-home');
|
|
911
|
+
try {
|
|
912
|
+
await mkdir(join(wd, '.omx', 'logs'), { recursive: true });
|
|
913
|
+
await mkdir(join(wd, '.omx', 'state'), { recursive: true });
|
|
914
|
+
await mkdir(fakeBinDir, { recursive: true });
|
|
915
|
+
await mkdir(codexHome, { recursive: true });
|
|
916
|
+
await writeFile(join(fakeBinDir, 'tmux'), buildFakeTmux(tmuxLogPath));
|
|
917
|
+
await chmod(join(fakeBinDir, 'tmux'), 0o755);
|
|
918
|
+
await writeFile(join(codexHome, '.omx-config.json'), JSON.stringify({
|
|
919
|
+
autoNudge: { enabled: true, delaySec: 0, ttlMs: 30_000 },
|
|
920
|
+
}, null, 2));
|
|
921
|
+
await writeFile(join(wd, '.omx', 'tmux-hook.json'), JSON.stringify({
|
|
922
|
+
enabled: false,
|
|
923
|
+
target: { type: 'pane', value: '%42' },
|
|
924
|
+
}, null, 2));
|
|
925
|
+
await writeSessionStart(wd, 'sess-managed-fallback');
|
|
926
|
+
await writeFile(join(wd, '.omx', 'state', 'hud-state.json'), JSON.stringify({
|
|
927
|
+
last_turn_at: new Date(Date.now() - 6_000).toISOString(),
|
|
928
|
+
turn_count: 7,
|
|
929
|
+
last_agent_output: 'If you want, I can keep going from here.',
|
|
930
|
+
}, null, 2));
|
|
931
|
+
const watcherScript = new URL('../../../dist/scripts/notify-fallback-watcher.js', import.meta.url).pathname;
|
|
932
|
+
const notifyHook = new URL('../../../dist/scripts/notify-hook.js', import.meta.url).pathname;
|
|
933
|
+
const result = spawnSync(process.execPath, [watcherScript, '--once', '--cwd', wd, '--notify-script', notifyHook, '--poll-ms', '50'], {
|
|
934
|
+
encoding: 'utf-8',
|
|
935
|
+
env: buildCleanNotifyEnv({
|
|
936
|
+
PATH: `${fakeBinDir}:${process.env.PATH || ''}`,
|
|
937
|
+
CODEX_HOME: codexHome,
|
|
938
|
+
OMX_SESSION_ID: 'sess-managed-fallback',
|
|
939
|
+
TMUX: '1',
|
|
940
|
+
TMUX_PANE: '%42',
|
|
941
|
+
OMX_NOTIFY_FALLBACK_AUTO_NUDGE_STALL_MS: '5000',
|
|
942
|
+
}),
|
|
943
|
+
});
|
|
944
|
+
assert.equal(result.status, 0, result.stderr || result.stdout);
|
|
945
|
+
const tmuxLog = await readFile(tmuxLogPath, 'utf8').catch(() => '');
|
|
946
|
+
assert.doesNotMatch(tmuxLog, /send-keys -t %42 -l yes, proceed \[OMX_TMUX_INJECT\]/);
|
|
947
|
+
}
|
|
948
|
+
finally {
|
|
949
|
+
await rm(wd, { recursive: true, force: true });
|
|
950
|
+
}
|
|
951
|
+
});
|
|
952
|
+
it('suppresses fallback unmanaged-session auto-nudge skip logs while idle', async () => {
|
|
953
|
+
const wd = await mkdtemp(join(tmpdir(), 'omx-fallback-auto-nudge-unmanaged-'));
|
|
954
|
+
const fakeBinDir = join(wd, 'fake-bin');
|
|
955
|
+
const tmuxLogPath = join(wd, 'tmux.log');
|
|
956
|
+
const codexHome = join(wd, 'codex-home');
|
|
957
|
+
try {
|
|
958
|
+
await mkdir(join(wd, '.omx', 'logs'), { recursive: true });
|
|
959
|
+
await mkdir(join(wd, '.omx', 'state'), { recursive: true });
|
|
960
|
+
await mkdir(fakeBinDir, { recursive: true });
|
|
961
|
+
await mkdir(codexHome, { recursive: true });
|
|
962
|
+
await writeFile(join(fakeBinDir, 'tmux'), buildFakeTmux(tmuxLogPath));
|
|
963
|
+
await chmod(join(fakeBinDir, 'tmux'), 0o755);
|
|
964
|
+
await writeFile(join(codexHome, '.omx-config.json'), JSON.stringify({
|
|
965
|
+
autoNudge: { enabled: true, delaySec: 0, ttlMs: 30_000 },
|
|
966
|
+
}, null, 2));
|
|
967
|
+
await writeFile(join(wd, '.omx', 'state', 'hud-state.json'), JSON.stringify({
|
|
968
|
+
last_turn_at: new Date(Date.now() - 6_000).toISOString(),
|
|
969
|
+
turn_count: 9,
|
|
970
|
+
last_agent_output: 'If you want, I can keep going from here.',
|
|
971
|
+
}, null, 2));
|
|
972
|
+
const watcherScript = new URL('../../../dist/scripts/notify-fallback-watcher.js', import.meta.url).pathname;
|
|
973
|
+
const notifyHook = new URL('../../../dist/scripts/notify-hook.js', import.meta.url).pathname;
|
|
974
|
+
const result = spawnSync(process.execPath, [watcherScript, '--once', '--cwd', wd, '--notify-script', notifyHook, '--poll-ms', '50'], {
|
|
975
|
+
encoding: 'utf-8',
|
|
976
|
+
env: buildCleanNotifyEnv({
|
|
977
|
+
PATH: `${fakeBinDir}:${process.env.PATH || ''}`,
|
|
978
|
+
CODEX_HOME: codexHome,
|
|
979
|
+
TMUX: '1',
|
|
980
|
+
TMUX_PANE: '%42',
|
|
981
|
+
OMX_NOTIFY_FALLBACK_AUTO_NUDGE_STALL_MS: '5000',
|
|
982
|
+
}),
|
|
983
|
+
});
|
|
984
|
+
assert.equal(result.status, 0, result.stderr || result.stdout);
|
|
985
|
+
const tmuxLog = await readFile(tmuxLogPath, 'utf8').catch(() => '');
|
|
986
|
+
assert.doesNotMatch(tmuxLog, /send-keys -t %42 -l yes, proceed \[OMX_TMUX_INJECT\]/);
|
|
987
|
+
const watcherStatePath = join(wd, '.omx', 'state', 'notify-fallback-state.json');
|
|
988
|
+
const watcherState = JSON.parse(await readFile(watcherStatePath, 'utf-8'));
|
|
989
|
+
assert.equal(watcherState.fallback_auto_nudge?.last_reason, 'eligible_but_not_sent');
|
|
990
|
+
const tmuxHookLogPath = join(wd, '.omx', 'logs', `tmux-hook-${new Date().toISOString().split('T')[0]}.jsonl`);
|
|
991
|
+
const tmuxHookLog = await readFile(tmuxHookLogPath, 'utf-8').catch(() => '');
|
|
992
|
+
assert.equal(tmuxHookLog.trim(), '');
|
|
993
|
+
}
|
|
994
|
+
finally {
|
|
995
|
+
await rm(wd, { recursive: true, force: true });
|
|
996
|
+
}
|
|
997
|
+
});
|
|
588
998
|
it('does not auto-nudge stalled-like output when the latest turn is still fresh', async () => {
|
|
589
999
|
const wd = await mkdtemp(join(tmpdir(), 'omx-fallback-auto-nudge-fresh-'));
|
|
590
1000
|
const fakeBinDir = join(wd, 'fake-bin');
|
|
@@ -674,7 +1084,59 @@ describe('notify-fallback watcher', () => {
|
|
|
674
1084
|
assert.doesNotMatch(tmuxLog, /send-keys -t %42 -l yes, proceed \[OMX_TMUX_INJECT\]/);
|
|
675
1085
|
const watcherStatePath = join(wd, '.omx', 'state', 'notify-fallback-state.json');
|
|
676
1086
|
const watcherState = JSON.parse(await readFile(watcherStatePath, 'utf-8'));
|
|
677
|
-
assert.equal(watcherState.fallback_auto_nudge?.last_reason, '
|
|
1087
|
+
assert.equal(watcherState.fallback_auto_nudge?.last_reason, 'already_nudged_for_signature');
|
|
1088
|
+
assert.equal(watcherState.fallback_auto_nudge?.last_turn_count, 7);
|
|
1089
|
+
}
|
|
1090
|
+
finally {
|
|
1091
|
+
await rm(wd, { recursive: true, force: true });
|
|
1092
|
+
}
|
|
1093
|
+
});
|
|
1094
|
+
it('does not fallback auto-nudge the same stalled hud turn again after TTL expiry', async () => {
|
|
1095
|
+
const wd = await mkdtemp(join(tmpdir(), 'omx-fallback-auto-nudge-exact-dedup-'));
|
|
1096
|
+
const fakeBinDir = join(wd, 'fake-bin');
|
|
1097
|
+
const tmuxLogPath = join(wd, 'tmux.log');
|
|
1098
|
+
const codexHome = join(wd, 'codex-home');
|
|
1099
|
+
const lastTurnAt = '2026-03-01T00:00:00.000Z';
|
|
1100
|
+
const lastMessage = 'If you want, I can keep going from here.';
|
|
1101
|
+
try {
|
|
1102
|
+
await mkdir(join(wd, '.omx', 'logs'), { recursive: true });
|
|
1103
|
+
await mkdir(join(wd, '.omx', 'state'), { recursive: true });
|
|
1104
|
+
await mkdir(fakeBinDir, { recursive: true });
|
|
1105
|
+
await mkdir(codexHome, { recursive: true });
|
|
1106
|
+
await writeFile(join(fakeBinDir, 'tmux'), buildFakeTmux(tmuxLogPath));
|
|
1107
|
+
await chmod(join(fakeBinDir, 'tmux'), 0o755);
|
|
1108
|
+
await writeFile(join(codexHome, '.omx-config.json'), JSON.stringify({
|
|
1109
|
+
autoNudge: { enabled: true, delaySec: 0, ttlMs: 5000 },
|
|
1110
|
+
}, null, 2));
|
|
1111
|
+
await writeFile(join(wd, '.omx', 'state', 'hud-state.json'), JSON.stringify({
|
|
1112
|
+
last_turn_at: lastTurnAt,
|
|
1113
|
+
turn_count: 7,
|
|
1114
|
+
last_agent_output: lastMessage,
|
|
1115
|
+
}, null, 2));
|
|
1116
|
+
await writeFile(join(wd, '.omx', 'state', 'auto-nudge-state.json'), JSON.stringify({
|
|
1117
|
+
nudgeCount: 1,
|
|
1118
|
+
lastNudgeAt: '2026-03-01T00:00:10.000Z',
|
|
1119
|
+
lastSignature: `hud:7|${lastTurnAt}|stall:proceed_intent`,
|
|
1120
|
+
lastSemanticSignature: 'stall:proceed_intent',
|
|
1121
|
+
}, null, 2));
|
|
1122
|
+
const watcherScript = new URL('../../../dist/scripts/notify-fallback-watcher.js', import.meta.url).pathname;
|
|
1123
|
+
const notifyHook = new URL('../../../dist/scripts/notify-hook.js', import.meta.url).pathname;
|
|
1124
|
+
const result = spawnSync(process.execPath, [watcherScript, '--once', '--cwd', wd, '--notify-script', notifyHook, '--poll-ms', '50'], {
|
|
1125
|
+
encoding: 'utf-8',
|
|
1126
|
+
env: buildCleanNotifyEnv({
|
|
1127
|
+
PATH: `${fakeBinDir}:${process.env.PATH || ''}`,
|
|
1128
|
+
CODEX_HOME: codexHome,
|
|
1129
|
+
TMUX: '1',
|
|
1130
|
+
TMUX_PANE: '%42',
|
|
1131
|
+
OMX_NOTIFY_FALLBACK_AUTO_NUDGE_STALL_MS: '5000',
|
|
1132
|
+
}),
|
|
1133
|
+
});
|
|
1134
|
+
assert.equal(result.status, 0, result.stderr || result.stdout);
|
|
1135
|
+
const tmuxLog = await readFile(tmuxLogPath, 'utf8').catch(() => '');
|
|
1136
|
+
assert.doesNotMatch(tmuxLog, /send-keys -t %42 -l yes, proceed \[OMX_TMUX_INJECT\]/);
|
|
1137
|
+
const watcherStatePath = join(wd, '.omx', 'state', 'notify-fallback-state.json');
|
|
1138
|
+
const watcherState = JSON.parse(await readFile(watcherStatePath, 'utf-8'));
|
|
1139
|
+
assert.equal(watcherState.fallback_auto_nudge?.last_reason, 'already_nudged_for_signature');
|
|
678
1140
|
assert.equal(watcherState.fallback_auto_nudge?.last_turn_count, 7);
|
|
679
1141
|
}
|
|
680
1142
|
finally {
|
|
@@ -683,7 +1145,9 @@ describe('notify-fallback watcher', () => {
|
|
|
683
1145
|
});
|
|
684
1146
|
it('runs bounded non-turn team dispatch drain tick in leader context', async () => {
|
|
685
1147
|
const wd = await mkdtemp(join(tmpdir(), 'omx-fallback-dispatch-'));
|
|
1148
|
+
const previousRuntimeBridge = process.env.OMX_RUNTIME_BRIDGE;
|
|
686
1149
|
try {
|
|
1150
|
+
process.env.OMX_RUNTIME_BRIDGE = '0';
|
|
687
1151
|
await initTeamState('dispatch-team', 'task', 'executor', 1, wd);
|
|
688
1152
|
const queued = await enqueueDispatchRequest('dispatch-team', {
|
|
689
1153
|
kind: 'inbox',
|
|
@@ -700,12 +1164,18 @@ describe('notify-fallback watcher', () => {
|
|
|
700
1164
|
assert.notEqual(request?.status, 'pending');
|
|
701
1165
|
}
|
|
702
1166
|
finally {
|
|
1167
|
+
if (typeof previousRuntimeBridge === 'string')
|
|
1168
|
+
process.env.OMX_RUNTIME_BRIDGE = previousRuntimeBridge;
|
|
1169
|
+
else
|
|
1170
|
+
delete process.env.OMX_RUNTIME_BRIDGE;
|
|
703
1171
|
await rm(wd, { recursive: true, force: true });
|
|
704
1172
|
}
|
|
705
1173
|
});
|
|
706
1174
|
it('skips dispatch drain in worker context (leader-only guard)', async () => {
|
|
707
1175
|
const wd = await mkdtemp(join(tmpdir(), 'omx-fallback-dispatch-worker-'));
|
|
1176
|
+
const previousRuntimeBridge = process.env.OMX_RUNTIME_BRIDGE;
|
|
708
1177
|
try {
|
|
1178
|
+
process.env.OMX_RUNTIME_BRIDGE = '0';
|
|
709
1179
|
await initTeamState('dispatch-team', 'task', 'executor', 1, wd);
|
|
710
1180
|
const queued = await enqueueDispatchRequest('dispatch-team', {
|
|
711
1181
|
kind: 'inbox',
|
|
@@ -727,11 +1197,13 @@ describe('notify-fallback watcher', () => {
|
|
|
727
1197
|
const logPath = join(wd, '.omx', 'logs', `notify-fallback-${new Date().toISOString().split('T')[0]}.jsonl`);
|
|
728
1198
|
const logEntries = (await readFile(logPath, 'utf-8')).trim().split('\n').filter(Boolean).map((line) => JSON.parse(line));
|
|
729
1199
|
const drainEvent = logEntries.find((entry) => entry.type === 'dispatch_drain_tick');
|
|
730
|
-
assert.
|
|
731
|
-
assert.equal(drainEvent.leader_only, false);
|
|
732
|
-
assert.equal(drainEvent.reason, 'worker_context');
|
|
1200
|
+
assert.equal(drainEvent, undefined);
|
|
733
1201
|
}
|
|
734
1202
|
finally {
|
|
1203
|
+
if (typeof previousRuntimeBridge === 'string')
|
|
1204
|
+
process.env.OMX_RUNTIME_BRIDGE = previousRuntimeBridge;
|
|
1205
|
+
else
|
|
1206
|
+
delete process.env.OMX_RUNTIME_BRIDGE;
|
|
735
1207
|
await rm(wd, { recursive: true, force: true });
|
|
736
1208
|
}
|
|
737
1209
|
});
|
|
@@ -740,18 +1212,20 @@ describe('notify-fallback watcher', () => {
|
|
|
740
1212
|
const fakeBinDir = join(wd, 'fake-bin');
|
|
741
1213
|
const tmuxLogPath = join(wd, 'tmux.log');
|
|
742
1214
|
const captureFile = join(wd, 'capture.txt');
|
|
1215
|
+
const previousRuntimeBridge = process.env.OMX_RUNTIME_BRIDGE;
|
|
743
1216
|
try {
|
|
1217
|
+
process.env.OMX_RUNTIME_BRIDGE = '0';
|
|
744
1218
|
await mkdir(fakeBinDir, { recursive: true });
|
|
745
1219
|
await writeFile(join(fakeBinDir, 'tmux'), buildFakeTmux(tmuxLogPath));
|
|
746
1220
|
await chmod(join(fakeBinDir, 'tmux'), 0o755);
|
|
747
|
-
await writeFile(captureFile, '
|
|
1221
|
+
await writeFile(captureFile, '... ping ...');
|
|
748
1222
|
await initTeamState('dispatch-team', 'task', 'executor', 1, wd);
|
|
749
1223
|
const queued = await enqueueDispatchRequest('dispatch-team', {
|
|
750
1224
|
kind: 'inbox',
|
|
751
1225
|
to_worker: 'worker-1',
|
|
752
1226
|
worker_index: 1,
|
|
753
1227
|
pane_id: '%42',
|
|
754
|
-
trigger_message: '
|
|
1228
|
+
trigger_message: 'ping',
|
|
755
1229
|
}, wd);
|
|
756
1230
|
const watcherScript = new URL('../../../dist/scripts/notify-fallback-watcher.js', import.meta.url).pathname;
|
|
757
1231
|
const notifyHook = new URL('../../../dist/scripts/notify-hook.js', import.meta.url).pathname;
|
|
@@ -765,14 +1239,21 @@ describe('notify-fallback watcher', () => {
|
|
|
765
1239
|
const second = spawnSync(process.execPath, [watcherScript, '--once', '--cwd', wd, '--notify-script', notifyHook, '--poll-ms', '50', '--dispatch-max-per-tick', '1'], { encoding: 'utf-8', env });
|
|
766
1240
|
assert.equal(second.status, 0, second.stderr || second.stdout);
|
|
767
1241
|
const tmuxLog = await readFile(tmuxLogPath, 'utf8');
|
|
768
|
-
const typeMatches = tmuxLog.match(/send-keys -t %42 -l
|
|
769
|
-
assert.equal(typeMatches.length, 1, '
|
|
1242
|
+
const typeMatches = tmuxLog.match(/send-keys -t %42 -l ping/g) || [];
|
|
1243
|
+
assert.equal(typeMatches.length, 1, 'fresh attempt should type once; retries with draft should be submit-only');
|
|
1244
|
+
const cmMatches = tmuxLog.match(/send-keys -t %42 C-m/g) || [];
|
|
1245
|
+
assert.ok(cmMatches.length > 0, 'submit should use C-m');
|
|
770
1246
|
assert.ok(!/send-keys[^\n]*-l[^\n]*C-m/.test(tmuxLog), 'must keep -l payload and C-m submits isolated');
|
|
771
1247
|
const request = await readDispatchRequest('dispatch-team', queued.request.request_id, wd);
|
|
772
1248
|
assert.equal(request?.status, 'pending');
|
|
1249
|
+
assert.equal(request?.attempt_count, 2);
|
|
773
1250
|
assert.equal(request?.last_reason, 'tmux_send_keys_unconfirmed');
|
|
774
1251
|
}
|
|
775
1252
|
finally {
|
|
1253
|
+
if (typeof previousRuntimeBridge === 'string')
|
|
1254
|
+
process.env.OMX_RUNTIME_BRIDGE = previousRuntimeBridge;
|
|
1255
|
+
else
|
|
1256
|
+
delete process.env.OMX_RUNTIME_BRIDGE;
|
|
776
1257
|
await rm(wd, { recursive: true, force: true });
|
|
777
1258
|
}
|
|
778
1259
|
});
|
|
@@ -935,6 +1416,7 @@ describe('notify-fallback watcher', () => {
|
|
|
935
1416
|
}, null, 2));
|
|
936
1417
|
await writeFile(statePath, JSON.stringify({
|
|
937
1418
|
ralph_continue_steer: {
|
|
1419
|
+
pane_id: '%7',
|
|
938
1420
|
last_sent_at: new Date(Date.now() - 61_000).toISOString(),
|
|
939
1421
|
},
|
|
940
1422
|
}, null, 2));
|
|
@@ -948,6 +1430,7 @@ describe('notify-fallback watcher', () => {
|
|
|
948
1430
|
assert.equal(missingRun.status, 0, missingRun.stderr || missingRun.stdout);
|
|
949
1431
|
let watcherState = JSON.parse(await readFile(statePath, 'utf-8'));
|
|
950
1432
|
assert.equal(watcherState.ralph_continue_steer?.last_reason, 'progress_missing');
|
|
1433
|
+
assert.equal(watcherState.ralph_continue_steer?.pane_id, '%42');
|
|
951
1434
|
await writeFile(join(stateDir, 'hud-state.json'), JSON.stringify({
|
|
952
1435
|
last_progress_at: 'not-a-date',
|
|
953
1436
|
}, null, 2));
|
|
@@ -955,6 +1438,7 @@ describe('notify-fallback watcher', () => {
|
|
|
955
1438
|
assert.equal(invalidRun.status, 0, invalidRun.stderr || invalidRun.stdout);
|
|
956
1439
|
watcherState = JSON.parse(await readFile(statePath, 'utf-8'));
|
|
957
1440
|
assert.equal(watcherState.ralph_continue_steer?.last_reason, 'progress_invalid');
|
|
1441
|
+
assert.equal(watcherState.ralph_continue_steer?.pane_id, '%42');
|
|
958
1442
|
const tmuxLog = await readFile(tmuxLogPath, 'utf8').catch(() => '');
|
|
959
1443
|
const sends = tmuxLog.match(/send-keys -t %42 -l Ralph loop active continue \[OMX_TMUX_INJECT\]/g) || [];
|
|
960
1444
|
assert.equal(sends.length, 0, 'missing or invalid progress should fail closed without sending steer');
|
|
@@ -1206,7 +1690,9 @@ describe('notify-fallback watcher', () => {
|
|
|
1206
1690
|
const wd = await mkdtemp(join(tmpdir(), 'omx-fallback-control-plane-split-'));
|
|
1207
1691
|
const fakeBinDir = join(wd, 'fake-bin');
|
|
1208
1692
|
const tmuxLogPath = join(wd, 'tmux.log');
|
|
1693
|
+
const previousRuntimeBridge = process.env.OMX_RUNTIME_BRIDGE;
|
|
1209
1694
|
try {
|
|
1695
|
+
process.env.OMX_RUNTIME_BRIDGE = '0';
|
|
1210
1696
|
await mkdir(join(wd, '.omx', 'state'), { recursive: true });
|
|
1211
1697
|
await mkdir(fakeBinDir, { recursive: true });
|
|
1212
1698
|
await writeFile(join(fakeBinDir, 'tmux'), buildFakeTmux(tmuxLogPath, {
|
|
@@ -1259,6 +1745,10 @@ describe('notify-fallback watcher', () => {
|
|
|
1259
1745
|
assert.ok(ralphFailureEvent, 'expected Ralph failure to be logged without aborting team control-plane pumping');
|
|
1260
1746
|
}
|
|
1261
1747
|
finally {
|
|
1748
|
+
if (typeof previousRuntimeBridge === 'string')
|
|
1749
|
+
process.env.OMX_RUNTIME_BRIDGE = previousRuntimeBridge;
|
|
1750
|
+
else
|
|
1751
|
+
delete process.env.OMX_RUNTIME_BRIDGE;
|
|
1262
1752
|
await rm(wd, { recursive: true, force: true });
|
|
1263
1753
|
}
|
|
1264
1754
|
});
|
|
@@ -1268,7 +1758,9 @@ describe('notify-fallback watcher', () => {
|
|
|
1268
1758
|
const tmuxLogPath = join(wd, 'tmux.log');
|
|
1269
1759
|
const captureSeqFile = join(wd, 'capture-seq.txt');
|
|
1270
1760
|
const captureCounterFile = join(wd, 'capture-seq.idx');
|
|
1761
|
+
const previousRuntimeBridge = process.env.OMX_RUNTIME_BRIDGE;
|
|
1271
1762
|
try {
|
|
1763
|
+
process.env.OMX_RUNTIME_BRIDGE = '0';
|
|
1272
1764
|
await mkdir(fakeBinDir, { recursive: true });
|
|
1273
1765
|
await writeFile(join(fakeBinDir, 'tmux'), buildFakeTmux(tmuxLogPath));
|
|
1274
1766
|
await chmod(join(fakeBinDir, 'tmux'), 0o755);
|
|
@@ -1277,11 +1769,11 @@ describe('notify-fallback watcher', () => {
|
|
|
1277
1769
|
// (no trigger) so the request is retyped on every retry.
|
|
1278
1770
|
await writeFile(captureSeqFile, [
|
|
1279
1771
|
// Run 1 (attempt 0): 1 shared preflight + 3 verify rounds × 2 captures = 7
|
|
1280
|
-
'ready', '
|
|
1772
|
+
'ready', 'ping', 'ping', 'ping', 'ping', 'ping', 'ping',
|
|
1281
1773
|
// Run 2 (attempt 1): 1 shared preflight + 1 pre-capture + 3 verify rounds × 2 captures = 8
|
|
1282
|
-
'ready', 'ready', '
|
|
1774
|
+
'ready', 'ready', 'ping', 'ping', 'ping', 'ping', 'ping', 'ping',
|
|
1283
1775
|
// Run 3 (attempt 2): 1 shared preflight + 1 pre-capture + 3 verify rounds × 2 captures = 8
|
|
1284
|
-
'ready', 'ready', '
|
|
1776
|
+
'ready', 'ready', 'ping', 'ping', 'ping', 'ping', 'ping', 'ping',
|
|
1285
1777
|
].join('\n'));
|
|
1286
1778
|
await initTeamState('dispatch-team', 'task', 'executor', 1, wd);
|
|
1287
1779
|
const queued = await enqueueDispatchRequest('dispatch-team', {
|
|
@@ -1289,7 +1781,7 @@ describe('notify-fallback watcher', () => {
|
|
|
1289
1781
|
to_worker: 'worker-1',
|
|
1290
1782
|
worker_index: 1,
|
|
1291
1783
|
pane_id: '%42',
|
|
1292
|
-
trigger_message: '
|
|
1784
|
+
trigger_message: 'ping',
|
|
1293
1785
|
}, wd);
|
|
1294
1786
|
const watcherScript = new URL('../../../dist/scripts/notify-fallback-watcher.js', import.meta.url).pathname;
|
|
1295
1787
|
const notifyHook = new URL('../../../dist/scripts/notify-hook.js', import.meta.url).pathname;
|
|
@@ -1304,13 +1796,17 @@ describe('notify-fallback watcher', () => {
|
|
|
1304
1796
|
assert.equal(run.status, 0, run.stderr || run.stdout);
|
|
1305
1797
|
}
|
|
1306
1798
|
const tmuxLog = await readFile(tmuxLogPath, 'utf8');
|
|
1307
|
-
const typeMatches = tmuxLog.match(/send-keys -t %42 -l
|
|
1308
|
-
assert.equal(typeMatches.length, 3, '
|
|
1799
|
+
const typeMatches = tmuxLog.match(/send-keys -t %42 -l ping/g) || [];
|
|
1800
|
+
assert.equal(typeMatches.length, 3, 'should retype on every retry when trigger not in narrow capture (fresh + 2 retries)');
|
|
1309
1801
|
const request = await readDispatchRequest('dispatch-team', queued.request.request_id, wd);
|
|
1310
1802
|
assert.equal(request?.status, 'failed');
|
|
1311
1803
|
assert.equal(request?.last_reason, 'unconfirmed_after_max_retries');
|
|
1312
1804
|
}
|
|
1313
1805
|
finally {
|
|
1806
|
+
if (typeof previousRuntimeBridge === 'string')
|
|
1807
|
+
process.env.OMX_RUNTIME_BRIDGE = previousRuntimeBridge;
|
|
1808
|
+
else
|
|
1809
|
+
delete process.env.OMX_RUNTIME_BRIDGE;
|
|
1314
1810
|
await rm(wd, { recursive: true, force: true });
|
|
1315
1811
|
}
|
|
1316
1812
|
});
|
|
@@ -1806,5 +2302,45 @@ describe('notify-fallback watcher', () => {
|
|
|
1806
2302
|
await rm(tempHome, { recursive: true, force: true });
|
|
1807
2303
|
}
|
|
1808
2304
|
});
|
|
2305
|
+
it('keeps the detached helper alive after the hidden bootstrap exits', async () => {
|
|
2306
|
+
const wd = await mkdtemp(join(tmpdir(), 'omx-fallback-bootstrap-survival-'));
|
|
2307
|
+
const readyPath = join(wd, 'helper-ready.json');
|
|
2308
|
+
const helperScriptPath = join(wd, 'helper-survival.js');
|
|
2309
|
+
try {
|
|
2310
|
+
await writeFile(helperScriptPath, `
|
|
2311
|
+
const fs = require('node:fs');
|
|
2312
|
+
const readyPath = process.argv[2];
|
|
2313
|
+
fs.writeFileSync(readyPath, JSON.stringify({ pid: process.pid, started_at: new Date().toISOString() }));
|
|
2314
|
+
setInterval(() => {}, 1000);
|
|
2315
|
+
`);
|
|
2316
|
+
const bootstrap = spawnSync(process.execPath, [
|
|
2317
|
+
'-e',
|
|
2318
|
+
buildWindowsMsysBackgroundHelperBootstrapScript([helperScriptPath, readyPath], wd),
|
|
2319
|
+
], {
|
|
2320
|
+
cwd: wd,
|
|
2321
|
+
encoding: 'utf-8',
|
|
2322
|
+
stdio: ['ignore', 'pipe', 'pipe'],
|
|
2323
|
+
windowsHide: true,
|
|
2324
|
+
});
|
|
2325
|
+
assert.equal(bootstrap.status, 0, bootstrap.stderr || bootstrap.stdout);
|
|
2326
|
+
const helperPid = Number.parseInt((bootstrap.stdout || '').trim(), 10);
|
|
2327
|
+
assert.ok(Number.isFinite(helperPid) && helperPid > 0, 'expected detached helper pid from bootstrap');
|
|
2328
|
+
await waitFor(async () => {
|
|
2329
|
+
try {
|
|
2330
|
+
const ready = JSON.parse(await readFile(readyPath, 'utf-8'));
|
|
2331
|
+
return ready.pid === helperPid;
|
|
2332
|
+
}
|
|
2333
|
+
catch {
|
|
2334
|
+
return false;
|
|
2335
|
+
}
|
|
2336
|
+
}, 4000, 50);
|
|
2337
|
+
assert.ok(isPidAlive(helperPid), 'expected detached helper to survive after bootstrap exit');
|
|
2338
|
+
process.kill(helperPid, 'SIGTERM');
|
|
2339
|
+
await waitFor(async () => !isPidAlive(helperPid), 4000, 50);
|
|
2340
|
+
}
|
|
2341
|
+
finally {
|
|
2342
|
+
await rm(wd, { recursive: true, force: true });
|
|
2343
|
+
}
|
|
2344
|
+
});
|
|
1809
2345
|
});
|
|
1810
2346
|
//# sourceMappingURL=notify-fallback-watcher.test.js.map
|