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
|
@@ -2,9 +2,10 @@ import { describe, it } from 'node:test';
|
|
|
2
2
|
import assert from 'node:assert/strict';
|
|
3
3
|
import { spawnSync } from 'node:child_process';
|
|
4
4
|
import { chmod, mkdtemp, mkdir, readFile, rm, writeFile } from 'node:fs/promises';
|
|
5
|
-
import { existsSync } from 'node:fs';
|
|
5
|
+
import { existsSync, readFileSync, writeFileSync } from 'node:fs';
|
|
6
6
|
import { tmpdir } from 'node:os';
|
|
7
7
|
import { join } from 'node:path';
|
|
8
|
+
import { buildTmuxSessionName } from '../../cli/index.js';
|
|
8
9
|
const NOTIFY_HOOK_SCRIPT = new URL('../../../dist/scripts/notify-hook.js', import.meta.url);
|
|
9
10
|
const DEEP_INTERVIEW_BLOCKED_APPROVAL_INPUTS = ['yes', 'y', 'proceed', 'continue', 'ok', 'sure', 'go ahead', 'next i should'];
|
|
10
11
|
const NEXT_I_SHOULD_RESPONSE = 'Next I should update the focused tests.';
|
|
@@ -20,6 +21,44 @@ async function withTempWorkingDir(run) {
|
|
|
20
21
|
async function writeJson(path, value) {
|
|
21
22
|
await writeFile(path, JSON.stringify(value, null, 2));
|
|
22
23
|
}
|
|
24
|
+
function readLinuxStartTicks(pid) {
|
|
25
|
+
try {
|
|
26
|
+
const stat = readFileSync(`/proc/${pid}/stat`, 'utf-8');
|
|
27
|
+
const commandEnd = stat.lastIndexOf(')');
|
|
28
|
+
if (commandEnd === -1)
|
|
29
|
+
return null;
|
|
30
|
+
const remainder = stat.slice(commandEnd + 1).trim();
|
|
31
|
+
const fields = remainder.split(/\s+/);
|
|
32
|
+
if (fields.length <= 19)
|
|
33
|
+
return null;
|
|
34
|
+
const startTicks = Number(fields[19]);
|
|
35
|
+
return Number.isFinite(startTicks) ? startTicks : null;
|
|
36
|
+
}
|
|
37
|
+
catch {
|
|
38
|
+
return null;
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
function readLinuxCmdline(pid) {
|
|
42
|
+
try {
|
|
43
|
+
const raw = readFileSync(`/proc/${pid}/cmdline`);
|
|
44
|
+
const text = raw.toString('utf-8').replace(/\0+/g, ' ').trim();
|
|
45
|
+
return text.length > 0 ? text : null;
|
|
46
|
+
}
|
|
47
|
+
catch {
|
|
48
|
+
return null;
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
async function writeManagedSessionState(stateDir, cwd) {
|
|
52
|
+
await writeJson(join(stateDir, 'session.json'), {
|
|
53
|
+
session_id: 'sess-managed',
|
|
54
|
+
started_at: new Date().toISOString(),
|
|
55
|
+
cwd,
|
|
56
|
+
pid: process.pid,
|
|
57
|
+
platform: process.platform,
|
|
58
|
+
pid_start_ticks: readLinuxStartTicks(process.pid),
|
|
59
|
+
pid_cmdline: readLinuxCmdline(process.pid),
|
|
60
|
+
});
|
|
61
|
+
}
|
|
23
62
|
function escapeRegex(value) {
|
|
24
63
|
return value.replace(/[-/\\^$*+?.()|[\]{}]/g, '\\$&');
|
|
25
64
|
}
|
|
@@ -65,12 +104,23 @@ if [[ "\$cmd" == "display-message" ]]; then
|
|
|
65
104
|
exit 0
|
|
66
105
|
fi
|
|
67
106
|
if [[ "\$format" == "#S" ]]; then
|
|
68
|
-
echo "devsess"
|
|
107
|
+
echo "${'${OMX_TEST_TMUX_SESSION_NAME:-devsess}'}"
|
|
69
108
|
exit 0
|
|
70
109
|
fi
|
|
71
110
|
exit 0
|
|
72
111
|
fi
|
|
73
112
|
if [[ "\$cmd" == "list-panes" ]]; then
|
|
113
|
+
target=""
|
|
114
|
+
while [[ "\$#" -gt 0 ]]; do
|
|
115
|
+
case "\$1" in
|
|
116
|
+
-t) target="\$2"; shift 2 ;;
|
|
117
|
+
*) shift ;;
|
|
118
|
+
esac
|
|
119
|
+
done
|
|
120
|
+
if [[ -n "\$target" && "\$target" == "${'${OMX_TEST_TMUX_SESSION_NAME:-devsess}'}" ]]; then
|
|
121
|
+
printf '%%99\tnode\tcodex --model gpt-5\n'
|
|
122
|
+
exit 0
|
|
123
|
+
fi
|
|
74
124
|
echo "%1 12345"
|
|
75
125
|
exit 0
|
|
76
126
|
fi
|
|
@@ -78,11 +128,25 @@ exit 0
|
|
|
78
128
|
`;
|
|
79
129
|
}
|
|
80
130
|
function runNotifyHook(cwd, fakeBinDir, codexHome, payloadOverrides = {}, extraEnv = {}) {
|
|
131
|
+
if (extraEnv.OMX_TEST_UNMANAGED_SESSION !== '1' && !extraEnv.OMX_TEAM_WORKER) {
|
|
132
|
+
const sessionPath = join(cwd, '.omx', 'state', 'session.json');
|
|
133
|
+
const sessionState = {
|
|
134
|
+
session_id: 'sess-managed',
|
|
135
|
+
started_at: new Date().toISOString(),
|
|
136
|
+
cwd,
|
|
137
|
+
pid: process.pid,
|
|
138
|
+
platform: process.platform,
|
|
139
|
+
pid_start_ticks: readLinuxStartTicks(process.pid),
|
|
140
|
+
pid_cmdline: readLinuxCmdline(process.pid),
|
|
141
|
+
};
|
|
142
|
+
writeFileSync(sessionPath, JSON.stringify(sessionState, null, 2));
|
|
143
|
+
}
|
|
81
144
|
const payload = {
|
|
82
145
|
cwd,
|
|
83
146
|
type: 'agent-turn-complete',
|
|
84
147
|
'thread-id': 'thread-test',
|
|
85
148
|
'turn-id': `turn-${Date.now()}-${Math.random().toString(16).slice(2, 8)}`,
|
|
149
|
+
...(extraEnv.OMX_TEST_UNMANAGED_SESSION !== '1' && !extraEnv.OMX_TEAM_WORKER ? { 'session-id': 'sess-managed' } : {}),
|
|
86
150
|
'input-messages': ['test'],
|
|
87
151
|
'last-assistant-message': 'done',
|
|
88
152
|
...payloadOverrides,
|
|
@@ -94,6 +158,8 @@ function runNotifyHook(cwd, fakeBinDir, codexHome, payloadOverrides = {}, extraE
|
|
|
94
158
|
...process.env,
|
|
95
159
|
PATH: `${fakeBinDir}:${process.env.PATH || ''}`,
|
|
96
160
|
CODEX_HOME: codexHome,
|
|
161
|
+
...(extraEnv.OMX_TEST_UNMANAGED_SESSION !== '1' && !extraEnv.OMX_TEAM_WORKER ? { OMX_SESSION_ID: 'sess-managed' } : {}),
|
|
162
|
+
...(extraEnv.OMX_TEST_UNMANAGED_SESSION !== '1' && !extraEnv.OMX_TEAM_WORKER ? { OMX_TEST_TMUX_SESSION_NAME: buildTmuxSessionName(cwd, 'sess-managed') } : {}),
|
|
97
163
|
TMUX_PANE: '%99',
|
|
98
164
|
TMUX: '1',
|
|
99
165
|
OMX_TEAM_WORKER: '',
|
|
@@ -121,6 +187,7 @@ describe('notify-hook auto-nudge', () => {
|
|
|
121
187
|
});
|
|
122
188
|
await writeFile(join(fakeBinDir, 'tmux'), buildFakeTmux(tmuxLogPath));
|
|
123
189
|
await chmod(join(fakeBinDir, 'tmux'), 0o755);
|
|
190
|
+
await writeManagedSessionState(stateDir, cwd);
|
|
124
191
|
const result = runNotifyHook(cwd, fakeBinDir, codexHome, {
|
|
125
192
|
'last-assistant-message': 'I analyzed the code. If you want me to make these changes, let me know.',
|
|
126
193
|
});
|
|
@@ -149,6 +216,7 @@ describe('notify-hook auto-nudge', () => {
|
|
|
149
216
|
await writeJson(join(codexHome, '.omx-config.json'), {
|
|
150
217
|
autoNudge: { enabled: true, delaySec: 0, stallMs: 0 },
|
|
151
218
|
});
|
|
219
|
+
await writeManagedSessionState(stateDir, cwd);
|
|
152
220
|
await writeFile(join(fakeBinDir, 'tmux'), buildFakeTmux(tmuxLogPath));
|
|
153
221
|
await chmod(join(fakeBinDir, 'tmux'), 0o755);
|
|
154
222
|
const result = runNotifyHook(cwd, fakeBinDir, codexHome, {
|
|
@@ -163,6 +231,169 @@ describe('notify-hook auto-nudge', () => {
|
|
|
163
231
|
assert.ok(cmMatches && cmMatches.length >= 2, `should send C-m twice, got ${cmMatches?.length ?? 0}`);
|
|
164
232
|
});
|
|
165
233
|
});
|
|
234
|
+
it('respects `.omx/tmux-hook.json` enabled:false and skips auto-nudge injection', async () => {
|
|
235
|
+
await withTempWorkingDir(async (cwd) => {
|
|
236
|
+
const omxDir = join(cwd, '.omx');
|
|
237
|
+
const stateDir = join(omxDir, 'state');
|
|
238
|
+
const logsDir = join(omxDir, 'logs');
|
|
239
|
+
const codexHome = join(cwd, 'codex-home');
|
|
240
|
+
const fakeBinDir = join(cwd, 'fake-bin');
|
|
241
|
+
const tmuxLogPath = join(cwd, 'tmux.log');
|
|
242
|
+
await mkdir(logsDir, { recursive: true });
|
|
243
|
+
await mkdir(stateDir, { recursive: true });
|
|
244
|
+
await mkdir(codexHome, { recursive: true });
|
|
245
|
+
await mkdir(fakeBinDir, { recursive: true });
|
|
246
|
+
await writeJson(join(codexHome, '.omx-config.json'), {
|
|
247
|
+
autoNudge: { enabled: true, delaySec: 0, stallMs: 0 },
|
|
248
|
+
});
|
|
249
|
+
await writeJson(join(omxDir, 'tmux-hook.json'), {
|
|
250
|
+
enabled: false,
|
|
251
|
+
target: { type: 'pane', value: '%99' },
|
|
252
|
+
});
|
|
253
|
+
await writeManagedSessionState(stateDir, cwd);
|
|
254
|
+
await writeFile(join(fakeBinDir, 'tmux'), buildFakeTmux(tmuxLogPath));
|
|
255
|
+
await chmod(join(fakeBinDir, 'tmux'), 0o755);
|
|
256
|
+
const result = runNotifyHook(cwd, fakeBinDir, codexHome, {
|
|
257
|
+
'last-assistant-message': 'I analyzed the code. If you want me to make these changes, let me know.',
|
|
258
|
+
});
|
|
259
|
+
assert.equal(result.status, 0, `hook failed: ${result.stderr || result.stdout}`);
|
|
260
|
+
const tmuxLog = await readFile(tmuxLogPath, 'utf-8').catch(() => '');
|
|
261
|
+
assert.doesNotMatch(tmuxLog, /send-keys -t %99 -l yes, proceed \[OMX_TMUX_INJECT\]/);
|
|
262
|
+
});
|
|
263
|
+
});
|
|
264
|
+
it('does not auto-nudge plain tmux Codex sessions that only inherit OMX session env', async () => {
|
|
265
|
+
await withTempWorkingDir(async (cwd) => {
|
|
266
|
+
const omxDir = join(cwd, '.omx');
|
|
267
|
+
const stateDir = join(omxDir, 'state');
|
|
268
|
+
const logsDir = join(omxDir, 'logs');
|
|
269
|
+
const codexHome = join(cwd, 'codex-home');
|
|
270
|
+
const fakeBinDir = join(cwd, 'fake-bin');
|
|
271
|
+
const tmuxLogPath = join(cwd, 'tmux.log');
|
|
272
|
+
await mkdir(logsDir, { recursive: true });
|
|
273
|
+
await mkdir(stateDir, { recursive: true });
|
|
274
|
+
await mkdir(codexHome, { recursive: true });
|
|
275
|
+
await mkdir(fakeBinDir, { recursive: true });
|
|
276
|
+
await writeJson(join(codexHome, '.omx-config.json'), {
|
|
277
|
+
autoNudge: { enabled: true, delaySec: 0, stallMs: 0 },
|
|
278
|
+
});
|
|
279
|
+
const sleeper = spawnSync('bash', ['-lc', 'sleep 5 >/dev/null 2>&1 & echo $!'], { encoding: 'utf8' });
|
|
280
|
+
assert.equal(sleeper.status, 0, sleeper.stderr || sleeper.stdout);
|
|
281
|
+
const sleeperPid = Number((sleeper.stdout || '').trim());
|
|
282
|
+
assert.ok(Number.isFinite(sleeperPid) && sleeperPid > 1, 'expected helper pid');
|
|
283
|
+
await writeJson(join(stateDir, 'session.json'), {
|
|
284
|
+
session_id: 'sess-managed',
|
|
285
|
+
started_at: new Date().toISOString(),
|
|
286
|
+
cwd,
|
|
287
|
+
pid: sleeperPid,
|
|
288
|
+
platform: process.platform,
|
|
289
|
+
pid_start_ticks: readLinuxStartTicks(sleeperPid),
|
|
290
|
+
pid_cmdline: readLinuxCmdline(sleeperPid),
|
|
291
|
+
});
|
|
292
|
+
await writeFile(join(fakeBinDir, 'tmux'), buildFakeTmux(tmuxLogPath));
|
|
293
|
+
await chmod(join(fakeBinDir, 'tmux'), 0o755);
|
|
294
|
+
const result = runNotifyHook(cwd, fakeBinDir, codexHome, {
|
|
295
|
+
'session-id': 'sess-managed',
|
|
296
|
+
'last-assistant-message': 'I analyzed the code. If you want me to make these changes, let me know.',
|
|
297
|
+
}, {
|
|
298
|
+
OMX_SESSION_ID: 'sess-managed',
|
|
299
|
+
OMX_TEST_UNMANAGED_SESSION: '1',
|
|
300
|
+
});
|
|
301
|
+
assert.equal(result.status, 0, `hook failed: ${result.stderr || result.stdout}`);
|
|
302
|
+
const tmuxLog = await readFile(tmuxLogPath, 'utf-8').catch(() => '');
|
|
303
|
+
assert.doesNotMatch(tmuxLog, /send-keys -t %99 -l yes, proceed \[OMX_TMUX_INJECT\]/);
|
|
304
|
+
});
|
|
305
|
+
});
|
|
306
|
+
it('does not auto-nudge plain tmux Codex sessions that are not OMX-managed', async () => {
|
|
307
|
+
await withTempWorkingDir(async (cwd) => {
|
|
308
|
+
const omxDir = join(cwd, '.omx');
|
|
309
|
+
const stateDir = join(omxDir, 'state');
|
|
310
|
+
const logsDir = join(omxDir, 'logs');
|
|
311
|
+
const codexHome = join(cwd, 'codex-home');
|
|
312
|
+
const fakeBinDir = join(cwd, 'fake-bin');
|
|
313
|
+
const tmuxLogPath = join(cwd, 'tmux.log');
|
|
314
|
+
await mkdir(logsDir, { recursive: true });
|
|
315
|
+
await mkdir(stateDir, { recursive: true });
|
|
316
|
+
await mkdir(codexHome, { recursive: true });
|
|
317
|
+
await mkdir(fakeBinDir, { recursive: true });
|
|
318
|
+
await writeJson(join(codexHome, '.omx-config.json'), {
|
|
319
|
+
autoNudge: { enabled: true, delaySec: 0, stallMs: 0 },
|
|
320
|
+
});
|
|
321
|
+
await writeFile(join(fakeBinDir, 'tmux'), buildFakeTmux(tmuxLogPath));
|
|
322
|
+
await chmod(join(fakeBinDir, 'tmux'), 0o755);
|
|
323
|
+
const result = runNotifyHook(cwd, fakeBinDir, codexHome, {
|
|
324
|
+
'last-assistant-message': 'I analyzed the code. If you want me to make these changes, let me know.',
|
|
325
|
+
}, {
|
|
326
|
+
OMX_TEST_UNMANAGED_SESSION: '1',
|
|
327
|
+
});
|
|
328
|
+
assert.equal(result.status, 0, `hook failed: ${result.stderr || result.stdout}`);
|
|
329
|
+
const tmuxLog = await readFile(tmuxLogPath, 'utf-8').catch(() => '');
|
|
330
|
+
assert.doesNotMatch(tmuxLog, /send-keys -t %99 -l yes, proceed \[OMX_TMUX_INJECT\]/);
|
|
331
|
+
});
|
|
332
|
+
});
|
|
333
|
+
it('does not auto-nudge when payload session-id disagrees with the managed tmux session identity', async () => {
|
|
334
|
+
await withTempWorkingDir(async (cwd) => {
|
|
335
|
+
const omxDir = join(cwd, '.omx');
|
|
336
|
+
const stateDir = join(omxDir, 'state');
|
|
337
|
+
const logsDir = join(omxDir, 'logs');
|
|
338
|
+
const codexHome = join(cwd, 'codex-home');
|
|
339
|
+
const fakeBinDir = join(cwd, 'fake-bin');
|
|
340
|
+
const tmuxLogPath = join(cwd, 'tmux.log');
|
|
341
|
+
const managedSessionName = buildTmuxSessionName(cwd, 'sess-managed');
|
|
342
|
+
await mkdir(logsDir, { recursive: true });
|
|
343
|
+
await mkdir(stateDir, { recursive: true });
|
|
344
|
+
await mkdir(codexHome, { recursive: true });
|
|
345
|
+
await mkdir(fakeBinDir, { recursive: true });
|
|
346
|
+
await writeJson(join(codexHome, '.omx-config.json'), {
|
|
347
|
+
autoNudge: { enabled: true, delaySec: 0, stallMs: 0 },
|
|
348
|
+
});
|
|
349
|
+
await writeManagedSessionState(stateDir, cwd);
|
|
350
|
+
await writeFile(join(fakeBinDir, 'tmux'), buildFakeTmux(tmuxLogPath));
|
|
351
|
+
await chmod(join(fakeBinDir, 'tmux'), 0o755);
|
|
352
|
+
const result = runNotifyHook(cwd, fakeBinDir, codexHome, {
|
|
353
|
+
'session-id': 'sess-other',
|
|
354
|
+
'last-assistant-message': 'I analyzed the code. If you want me to make these changes, let me know.',
|
|
355
|
+
}, {
|
|
356
|
+
OMX_SESSION_ID: 'sess-managed',
|
|
357
|
+
OMX_TEST_TMUX_SESSION_NAME: managedSessionName,
|
|
358
|
+
});
|
|
359
|
+
assert.equal(result.status, 0, `hook failed: ${result.stderr || result.stdout}`);
|
|
360
|
+
const tmuxLog = await readFile(tmuxLogPath, 'utf-8');
|
|
361
|
+
assert.doesNotMatch(tmuxLog, /send-keys -t %99 -l yes, proceed \[OMX_TMUX_INJECT\]/);
|
|
362
|
+
});
|
|
363
|
+
});
|
|
364
|
+
it('does not auto-nudge when tmux session naming drifts from the current OMX session id', async () => {
|
|
365
|
+
await withTempWorkingDir(async (cwd) => {
|
|
366
|
+
const omxDir = join(cwd, '.omx');
|
|
367
|
+
const stateDir = join(omxDir, 'state');
|
|
368
|
+
const logsDir = join(omxDir, 'logs');
|
|
369
|
+
const codexHome = join(cwd, 'codex-home');
|
|
370
|
+
const fakeBinDir = join(cwd, 'fake-bin');
|
|
371
|
+
const tmuxLogPath = join(cwd, 'tmux.log');
|
|
372
|
+
const expectedManagedSessionName = buildTmuxSessionName(cwd, 'sess-managed');
|
|
373
|
+
const mismatchedDetachedSessionName = buildTmuxSessionName(cwd, 'sess-legacy-detached');
|
|
374
|
+
await mkdir(logsDir, { recursive: true });
|
|
375
|
+
await mkdir(stateDir, { recursive: true });
|
|
376
|
+
await mkdir(codexHome, { recursive: true });
|
|
377
|
+
await mkdir(fakeBinDir, { recursive: true });
|
|
378
|
+
await writeJson(join(codexHome, '.omx-config.json'), {
|
|
379
|
+
autoNudge: { enabled: true, delaySec: 0, stallMs: 0 },
|
|
380
|
+
});
|
|
381
|
+
await writeManagedSessionState(stateDir, cwd);
|
|
382
|
+
await writeFile(join(fakeBinDir, 'tmux'), buildFakeTmux(tmuxLogPath));
|
|
383
|
+
await chmod(join(fakeBinDir, 'tmux'), 0o755);
|
|
384
|
+
const result = runNotifyHook(cwd, fakeBinDir, codexHome, {
|
|
385
|
+
'session-id': 'sess-managed',
|
|
386
|
+
'last-assistant-message': 'I analyzed the code. If you want me to make these changes, let me know.',
|
|
387
|
+
}, {
|
|
388
|
+
OMX_SESSION_ID: 'sess-managed',
|
|
389
|
+
OMX_TEST_TMUX_SESSION_NAME: mismatchedDetachedSessionName,
|
|
390
|
+
});
|
|
391
|
+
assert.equal(result.status, 0, `hook failed: ${result.stderr || result.stdout}`);
|
|
392
|
+
const tmuxLog = await readFile(tmuxLogPath, 'utf-8');
|
|
393
|
+
assert.match(tmuxLog, new RegExp(`list-panes -s -t ${escapeRegex(expectedManagedSessionName)}`), 'should resolve panes against the current OMX session identity, not the drifted tmux session name');
|
|
394
|
+
assert.doesNotMatch(tmuxLog, /send-keys -t %99 -l yes, proceed \[OMX_TMUX_INJECT\]/);
|
|
395
|
+
});
|
|
396
|
+
});
|
|
166
397
|
it('sends nudge via capture-pane fallback when payload has no stall pattern', async () => {
|
|
167
398
|
await withTempWorkingDir(async (cwd) => {
|
|
168
399
|
const omxDir = join(cwd, '.omx');
|
|
@@ -179,6 +410,7 @@ describe('notify-hook auto-nudge', () => {
|
|
|
179
410
|
await writeJson(join(codexHome, '.omx-config.json'), {
|
|
180
411
|
autoNudge: { enabled: true, delaySec: 0, stallMs: 0 },
|
|
181
412
|
});
|
|
413
|
+
await writeManagedSessionState(stateDir, cwd);
|
|
182
414
|
// capture-pane will return content with a stall pattern
|
|
183
415
|
await writeFile(captureFile, 'Here are the results.\nWould you like me to continue with the implementation?\n› ');
|
|
184
416
|
await writeFile(join(fakeBinDir, 'tmux'), buildFakeTmux(tmuxLogPath));
|
|
@@ -202,6 +434,7 @@ describe('notify-hook auto-nudge', () => {
|
|
|
202
434
|
const codexHome = join(cwd, 'codex-home');
|
|
203
435
|
const fakeBinDir = join(cwd, 'fake-bin');
|
|
204
436
|
const tmuxLogPath = join(cwd, 'tmux.log');
|
|
437
|
+
const managedSessionName = buildTmuxSessionName(cwd, 'sess-managed');
|
|
205
438
|
await mkdir(logsDir, { recursive: true });
|
|
206
439
|
await mkdir(stateDir, { recursive: true });
|
|
207
440
|
await mkdir(codexHome, { recursive: true });
|
|
@@ -209,6 +442,7 @@ describe('notify-hook auto-nudge', () => {
|
|
|
209
442
|
await writeJson(join(codexHome, '.omx-config.json'), {
|
|
210
443
|
autoNudge: { enabled: true, delaySec: 0, stallMs: 0 },
|
|
211
444
|
});
|
|
445
|
+
await writeManagedSessionState(stateDir, cwd);
|
|
212
446
|
await writeJson(join(stateDir, 'ralph-state.json'), {
|
|
213
447
|
active: true,
|
|
214
448
|
tmux_pane_id: '%99',
|
|
@@ -245,7 +479,7 @@ if [[ "$cmd" == "display-message" ]]; then
|
|
|
245
479
|
exit 0
|
|
246
480
|
fi
|
|
247
481
|
if [[ "$format" == "#S" && "$target" == "%99" ]]; then
|
|
248
|
-
echo "
|
|
482
|
+
echo "${managedSessionName}"
|
|
249
483
|
exit 0
|
|
250
484
|
fi
|
|
251
485
|
exit 0
|
|
@@ -258,7 +492,7 @@ if [[ "$cmd" == "list-panes" ]]; then
|
|
|
258
492
|
*) shift ;;
|
|
259
493
|
esac
|
|
260
494
|
done
|
|
261
|
-
if [[ "$target" == "
|
|
495
|
+
if [[ "$target" == "${managedSessionName}" ]]; then
|
|
262
496
|
printf "%%99\tsh\tbash\n%%100\tnode\tcodex --model gpt-5\n"
|
|
263
497
|
exit 0
|
|
264
498
|
fi
|
|
@@ -283,7 +517,6 @@ exit 0
|
|
|
283
517
|
});
|
|
284
518
|
assert.equal(result.status, 0, `hook failed: ${result.stderr || result.stdout}`);
|
|
285
519
|
const tmuxLog = await readFile(tmuxLogPath, 'utf-8');
|
|
286
|
-
assert.match(tmuxLog, /display-message -t %99 -p #S/, 'should anchor off the active mode pane');
|
|
287
520
|
assert.match(tmuxLog, /send-keys -t %100 -l yes, proceed \[OMX_TMUX_INJECT\]/, 'should upgrade anchored shell pane to sibling codex pane');
|
|
288
521
|
});
|
|
289
522
|
});
|
|
@@ -294,6 +527,7 @@ exit 0
|
|
|
294
527
|
const codexHome = join(cwd, 'codex-home');
|
|
295
528
|
const fakeBinDir = join(cwd, 'fake-bin');
|
|
296
529
|
const tmuxLogPath = join(cwd, 'tmux.log');
|
|
530
|
+
const managedSessionName = buildTmuxSessionName(cwd, 'sess-managed');
|
|
297
531
|
await mkdir(logsDir, { recursive: true });
|
|
298
532
|
await mkdir(workerStateRoot, { recursive: true });
|
|
299
533
|
await mkdir(codexHome, { recursive: true });
|
|
@@ -351,6 +585,7 @@ exit 0
|
|
|
351
585
|
const codexHome = join(cwd, 'codex-home');
|
|
352
586
|
const fakeBinDir = join(cwd, 'fake-bin');
|
|
353
587
|
const tmuxLogPath = join(cwd, 'tmux.log');
|
|
588
|
+
const managedSessionName = buildTmuxSessionName(cwd, 'sess-managed');
|
|
354
589
|
await mkdir(logsDir, { recursive: true });
|
|
355
590
|
await mkdir(stateDir, { recursive: true });
|
|
356
591
|
await mkdir(codexHome, { recursive: true });
|
|
@@ -399,11 +634,11 @@ exit 0
|
|
|
399
634
|
});
|
|
400
635
|
assert.equal(result.status, 0, `hook failed: ${result.stderr || result.stdout}`);
|
|
401
636
|
const tmuxLog = await readFile(tmuxLogPath, 'utf-8');
|
|
402
|
-
assert.
|
|
637
|
+
assert.ok(tmuxLog.includes('display-message -p -t %99 #S'), 'should inspect the managed anchor pane before deciding');
|
|
403
638
|
assert.doesNotMatch(tmuxLog, /send-keys -t %99 -l yes, proceed \[OMX_TMUX_INJECT\]/, 'shell pane should not receive auto-nudge injection');
|
|
404
639
|
});
|
|
405
640
|
});
|
|
406
|
-
it('
|
|
641
|
+
it('falls back to the sibling codex pane when TMUX_PANE is a managed non-agent shell pane', async () => {
|
|
407
642
|
await withTempWorkingDir(async (cwd) => {
|
|
408
643
|
const omxDir = join(cwd, '.omx');
|
|
409
644
|
const stateDir = join(omxDir, 'state');
|
|
@@ -411,6 +646,7 @@ exit 0
|
|
|
411
646
|
const codexHome = join(cwd, 'codex-home');
|
|
412
647
|
const fakeBinDir = join(cwd, 'fake-bin');
|
|
413
648
|
const tmuxLogPath = join(cwd, 'tmux.log');
|
|
649
|
+
const managedSessionName = buildTmuxSessionName(cwd, 'sess-managed');
|
|
414
650
|
await mkdir(logsDir, { recursive: true });
|
|
415
651
|
await mkdir(stateDir, { recursive: true });
|
|
416
652
|
await mkdir(codexHome, { recursive: true });
|
|
@@ -442,11 +678,11 @@ shift || true
|
|
|
442
678
|
exit 0
|
|
443
679
|
fi
|
|
444
680
|
if [[ "$format" == "#S" && "$target" == "%99" ]]; then
|
|
445
|
-
echo "
|
|
681
|
+
echo "${managedSessionName}"
|
|
446
682
|
exit 0
|
|
447
683
|
fi
|
|
448
684
|
if [[ "$format" == "#S" && "$target" == "%100" ]]; then
|
|
449
|
-
echo "
|
|
685
|
+
echo "${managedSessionName}"
|
|
450
686
|
exit 0
|
|
451
687
|
fi
|
|
452
688
|
if [[ "$format" == "#{pane_current_path}" && "$target" == "%99" ]]; then
|
|
@@ -474,7 +710,7 @@ if [[ "$cmd" == "list-panes" ]]; then
|
|
|
474
710
|
*) shift ;;
|
|
475
711
|
esac
|
|
476
712
|
done
|
|
477
|
-
if [[ "$target" == "
|
|
713
|
+
if [[ "$target" == "${managedSessionName}" ]]; then
|
|
478
714
|
printf "%%99\t1\tsh\\n%%100\t0\tcodex --model gpt-5\\n"
|
|
479
715
|
exit 0
|
|
480
716
|
fi
|
|
@@ -491,7 +727,7 @@ exit 0
|
|
|
491
727
|
assert.equal(result.status, 0, `hook failed: ${result.stderr || result.stdout}`);
|
|
492
728
|
const tmuxLog = await readFile(tmuxLogPath, 'utf-8');
|
|
493
729
|
assert.match(tmuxLog, /display-message -p #S/);
|
|
494
|
-
assert.
|
|
730
|
+
assert.ok(tmuxLog.includes('display-message -p -t %99 #S'), 'should inspect the anchored shell pane before upgrading');
|
|
495
731
|
assert.match(tmuxLog, /send-keys -t %100 -l yes, proceed \[OMX_TMUX_INJECT\]/);
|
|
496
732
|
});
|
|
497
733
|
});
|
|
@@ -503,6 +739,7 @@ exit 0
|
|
|
503
739
|
const codexHome = join(cwd, 'codex-home');
|
|
504
740
|
const fakeBinDir = join(cwd, 'fake-bin');
|
|
505
741
|
const tmuxLogPath = join(cwd, 'tmux.log');
|
|
742
|
+
const managedSessionName = buildTmuxSessionName(cwd, 'sess-managed');
|
|
506
743
|
await mkdir(logsDir, { recursive: true });
|
|
507
744
|
await mkdir(stateDir, { recursive: true });
|
|
508
745
|
await mkdir(codexHome, { recursive: true });
|
|
@@ -518,7 +755,7 @@ exit 0
|
|
|
518
755
|
assert.equal(result.status, 0, `hook failed: ${result.stderr || result.stdout}`);
|
|
519
756
|
const tmuxLog = await readFile(tmuxLogPath, 'utf-8');
|
|
520
757
|
assert.match(tmuxLog, /display-message -p #S/);
|
|
521
|
-
assert.
|
|
758
|
+
assert.doesNotMatch(tmuxLog, /send-keys -t %99 -l yes, proceed \[OMX_TMUX_INJECT\]/, 'copy-mode pane should not receive auto-nudge injection');
|
|
522
759
|
});
|
|
523
760
|
});
|
|
524
761
|
it('does not nudge when pane capture shows an active task despite stall-like assistant text', async () => {
|
|
@@ -552,8 +789,8 @@ exit 0
|
|
|
552
789
|
assert.equal(result.status, 0, `hook failed: ${result.stderr || result.stdout}`);
|
|
553
790
|
const tmuxLog = await readFile(tmuxLogPath, 'utf-8');
|
|
554
791
|
assert.match(tmuxLog, /display-message -p #S/);
|
|
555
|
-
assert.
|
|
556
|
-
assert.
|
|
792
|
+
assert.match(tmuxLog, /capture-pane -t %99/, 'busy pane detection should inspect capture output');
|
|
793
|
+
assert.doesNotMatch(tmuxLog, /send-keys -t %99 -l yes, proceed \[OMX_TMUX_INJECT\]/, 'busy pane should not receive auto-nudge injection');
|
|
557
794
|
});
|
|
558
795
|
});
|
|
559
796
|
it('respects enabled=false configuration', async () => {
|
|
@@ -666,6 +903,53 @@ exit 0
|
|
|
666
903
|
assert.equal(nudgeState.lastSemanticSignature, 'stall:proceed_intent');
|
|
667
904
|
});
|
|
668
905
|
});
|
|
906
|
+
it('does not resend the exact same stalled turn after TTL expiry', async () => {
|
|
907
|
+
await withTempWorkingDir(async (cwd) => {
|
|
908
|
+
const omxDir = join(cwd, '.omx');
|
|
909
|
+
const stateDir = join(omxDir, 'state');
|
|
910
|
+
const logsDir = join(omxDir, 'logs');
|
|
911
|
+
const codexHome = join(cwd, 'codex-home');
|
|
912
|
+
const fakeBinDir = join(cwd, 'fake-bin');
|
|
913
|
+
const tmuxLogPath = join(cwd, 'tmux.log');
|
|
914
|
+
const lastTurnAt = '2026-03-01T00:00:00.000Z';
|
|
915
|
+
const lastMessage = 'If you want, I can keep going from here.';
|
|
916
|
+
await mkdir(logsDir, { recursive: true });
|
|
917
|
+
await mkdir(stateDir, { recursive: true });
|
|
918
|
+
await mkdir(codexHome, { recursive: true });
|
|
919
|
+
await mkdir(fakeBinDir, { recursive: true });
|
|
920
|
+
await writeJson(join(codexHome, '.omx-config.json'), {
|
|
921
|
+
autoNudge: { enabled: true, delaySec: 0, stallMs: 0, ttlMs: 5000 },
|
|
922
|
+
});
|
|
923
|
+
await writeJson(join(stateDir, 'hud-state.json'), {
|
|
924
|
+
last_turn_at: lastTurnAt,
|
|
925
|
+
turn_count: 1,
|
|
926
|
+
last_agent_output: lastMessage,
|
|
927
|
+
});
|
|
928
|
+
await writeFile(join(fakeBinDir, 'tmux'), buildFakeTmux(tmuxLogPath));
|
|
929
|
+
await chmod(join(fakeBinDir, 'tmux'), 0o755);
|
|
930
|
+
const first = runNotifyHook(cwd, fakeBinDir, codexHome, {
|
|
931
|
+
'turn-id': 'stalled-turn-1',
|
|
932
|
+
'last-assistant-message': lastMessage,
|
|
933
|
+
});
|
|
934
|
+
assert.equal(first.status, 0, `first hook failed: ${first.stderr || first.stdout}`);
|
|
935
|
+
const nudgeStatePath = join(stateDir, 'auto-nudge-state.json');
|
|
936
|
+
const firstState = JSON.parse(await readFile(nudgeStatePath, 'utf-8'));
|
|
937
|
+
await writeJson(nudgeStatePath, {
|
|
938
|
+
...firstState,
|
|
939
|
+
lastNudgeAt: '2026-03-01T00:00:10.000Z',
|
|
940
|
+
});
|
|
941
|
+
const result = runNotifyHook(cwd, fakeBinDir, codexHome, {
|
|
942
|
+
'turn-id': 'stalled-turn-1',
|
|
943
|
+
'last-assistant-message': lastMessage,
|
|
944
|
+
});
|
|
945
|
+
assert.equal(result.status, 0, `second hook failed: ${result.stderr || result.stdout}`);
|
|
946
|
+
const tmuxLog = await readFile(tmuxLogPath, 'utf-8').catch(() => '');
|
|
947
|
+
assert.equal((tmuxLog.match(/send-keys -t %99 -l yes, proceed \[OMX_TMUX_INJECT\]/g) || []).length, 1);
|
|
948
|
+
const nudgeState = JSON.parse(await readFile(nudgeStatePath, 'utf-8'));
|
|
949
|
+
assert.equal(nudgeState.nudgeCount, 1);
|
|
950
|
+
assert.equal(nudgeState.lastSignature, firstState.lastSignature);
|
|
951
|
+
});
|
|
952
|
+
});
|
|
669
953
|
it('uses custom response from config', async () => {
|
|
670
954
|
await withTempWorkingDir(async (cwd) => {
|
|
671
955
|
const omxDir = join(cwd, '.omx');
|
|
@@ -778,6 +1062,48 @@ exit 0
|
|
|
778
1062
|
assert.doesNotMatch(tmuxLog, /send-keys -t %99 -l yes, proceed \[OMX_TMUX_INJECT\]/);
|
|
779
1063
|
});
|
|
780
1064
|
});
|
|
1065
|
+
it('disables auto-nudge when only skill-active-state carries the deep-interview input lock', async () => {
|
|
1066
|
+
await withTempWorkingDir(async (cwd) => {
|
|
1067
|
+
const omxDir = join(cwd, '.omx');
|
|
1068
|
+
const stateDir = join(omxDir, 'state');
|
|
1069
|
+
const logsDir = join(omxDir, 'logs');
|
|
1070
|
+
const codexHome = join(cwd, 'codex-home');
|
|
1071
|
+
const fakeBinDir = join(cwd, 'fake-bin');
|
|
1072
|
+
const tmuxLogPath = join(cwd, 'tmux.log');
|
|
1073
|
+
await mkdir(logsDir, { recursive: true });
|
|
1074
|
+
await mkdir(stateDir, { recursive: true });
|
|
1075
|
+
await mkdir(codexHome, { recursive: true });
|
|
1076
|
+
await mkdir(fakeBinDir, { recursive: true });
|
|
1077
|
+
await writeJson(join(codexHome, '.omx-config.json'), {
|
|
1078
|
+
autoNudge: { enabled: true, delaySec: 0, stallMs: 0 },
|
|
1079
|
+
});
|
|
1080
|
+
await writeJson(join(stateDir, 'skill-active-state.json'), {
|
|
1081
|
+
version: 1,
|
|
1082
|
+
active: true,
|
|
1083
|
+
skill: 'deep-interview',
|
|
1084
|
+
keyword: 'deep interview',
|
|
1085
|
+
phase: 'planning',
|
|
1086
|
+
activated_at: '2026-02-25T00:00:00.000Z',
|
|
1087
|
+
updated_at: '2026-02-25T00:00:00.000Z',
|
|
1088
|
+
source: 'keyword-detector',
|
|
1089
|
+
input_lock: {
|
|
1090
|
+
active: true,
|
|
1091
|
+
scope: 'deep-interview-auto-approval',
|
|
1092
|
+
acquired_at: '2026-02-25T00:00:00.000Z',
|
|
1093
|
+
blocked_inputs: DEEP_INTERVIEW_BLOCKED_APPROVAL_INPUTS,
|
|
1094
|
+
message: 'Deep interview is active; auto-approval shortcuts are blocked until the interview finishes.',
|
|
1095
|
+
},
|
|
1096
|
+
});
|
|
1097
|
+
await writeFile(join(fakeBinDir, 'tmux'), buildFakeTmux(tmuxLogPath));
|
|
1098
|
+
await chmod(join(fakeBinDir, 'tmux'), 0o755);
|
|
1099
|
+
const result = runNotifyHook(cwd, fakeBinDir, codexHome, {
|
|
1100
|
+
'last-assistant-message': 'Would you like me to continue?',
|
|
1101
|
+
});
|
|
1102
|
+
assert.equal(result.status, 0, `hook failed: ${result.stderr || result.stdout}`);
|
|
1103
|
+
const tmuxLog = await readFile(tmuxLogPath, 'utf-8').catch(() => '');
|
|
1104
|
+
assert.doesNotMatch(tmuxLog, /send-keys -t %99 -l yes, proceed \[OMX_TMUX_INJECT\]/);
|
|
1105
|
+
});
|
|
1106
|
+
});
|
|
781
1107
|
it('acquires the deep-interview input lock when deep-interview activates', async () => {
|
|
782
1108
|
await withTempWorkingDir(async (cwd) => {
|
|
783
1109
|
const omxDir = join(cwd, '.omx');
|
|
@@ -804,6 +1130,11 @@ exit 0
|
|
|
804
1130
|
assert.equal(skillState.input_lock?.active, true);
|
|
805
1131
|
assert.deepEqual(skillState.input_lock?.blocked_inputs, DEEP_INTERVIEW_BLOCKED_APPROVAL_INPUTS);
|
|
806
1132
|
assert.match(skillState.input_lock?.message || '', /Deep interview is active/i);
|
|
1133
|
+
const modeState = JSON.parse(await readFile(join(stateDir, 'deep-interview-state.json'), 'utf-8'));
|
|
1134
|
+
assert.equal(modeState.active, true);
|
|
1135
|
+
assert.equal(modeState.mode, 'deep-interview');
|
|
1136
|
+
assert.equal(modeState.current_phase, 'intent-first');
|
|
1137
|
+
assert.equal(modeState.input_lock?.active, true);
|
|
807
1138
|
});
|
|
808
1139
|
});
|
|
809
1140
|
for (const blockedResponse of ['yes', 'y', 'proceed', 'continue', 'ok', 'sure', 'go ahead']) {
|
|
@@ -1149,7 +1480,7 @@ exit 0
|
|
|
1149
1480
|
assert.match(tmuxLog, /send-keys -t %99 -l yes, proceed \[OMX_TMUX_INJECT\]/, 'should nudge with default config and marker');
|
|
1150
1481
|
});
|
|
1151
1482
|
});
|
|
1152
|
-
it('
|
|
1483
|
+
it('can still resolve the managed session pane when TMUX_PANE is not set', async () => {
|
|
1153
1484
|
await withTempWorkingDir(async (cwd) => {
|
|
1154
1485
|
const omxDir = join(cwd, '.omx');
|
|
1155
1486
|
const stateDir = join(omxDir, 'state');
|
|
@@ -1175,7 +1506,7 @@ exit 0
|
|
|
1175
1506
|
assert.equal(result.status, 0, `hook failed: ${result.stderr || result.stdout}`);
|
|
1176
1507
|
if (existsSync(tmuxLogPath)) {
|
|
1177
1508
|
const tmuxLog = await readFile(tmuxLogPath, 'utf-8');
|
|
1178
|
-
assert.
|
|
1509
|
+
assert.match(tmuxLog, /send-keys -t %99 -l yes, proceed \[OMX_TMUX_INJECT\]/, 'should fall back to the managed session pane when TMUX_PANE is absent');
|
|
1179
1510
|
}
|
|
1180
1511
|
});
|
|
1181
1512
|
});
|