oh-my-codex 0.18.9 → 0.18.10

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.
Files changed (163) hide show
  1. package/Cargo.lock +6 -6
  2. package/Cargo.toml +1 -1
  3. package/README.md +4 -0
  4. package/dist/autopilot/__tests__/deep-interview-gate.test.d.ts +2 -0
  5. package/dist/autopilot/__tests__/deep-interview-gate.test.d.ts.map +1 -0
  6. package/dist/autopilot/__tests__/deep-interview-gate.test.js +215 -0
  7. package/dist/autopilot/__tests__/deep-interview-gate.test.js.map +1 -0
  8. package/dist/autopilot/__tests__/ralplan-gate.test.js +148 -0
  9. package/dist/autopilot/__tests__/ralplan-gate.test.js.map +1 -1
  10. package/dist/autopilot/deep-interview-gate.d.ts.map +1 -1
  11. package/dist/autopilot/deep-interview-gate.js +140 -0
  12. package/dist/autopilot/deep-interview-gate.js.map +1 -1
  13. package/dist/cli/__tests__/auth.test.js +36 -3
  14. package/dist/cli/__tests__/auth.test.js.map +1 -1
  15. package/dist/cli/__tests__/codex-feature-probe.test.d.ts +2 -0
  16. package/dist/cli/__tests__/codex-feature-probe.test.d.ts.map +1 -0
  17. package/dist/cli/__tests__/codex-feature-probe.test.js +46 -0
  18. package/dist/cli/__tests__/codex-feature-probe.test.js.map +1 -0
  19. package/dist/cli/__tests__/doctor-warning-copy.test.js +2 -0
  20. package/dist/cli/__tests__/doctor-warning-copy.test.js.map +1 -1
  21. package/dist/cli/__tests__/index.test.js +251 -5
  22. package/dist/cli/__tests__/index.test.js.map +1 -1
  23. package/dist/cli/__tests__/launch-fallback.test.js +19 -5
  24. package/dist/cli/__tests__/launch-fallback.test.js.map +1 -1
  25. package/dist/cli/__tests__/package-bin-contract.test.js +19 -6
  26. package/dist/cli/__tests__/package-bin-contract.test.js.map +1 -1
  27. package/dist/cli/__tests__/setup-refresh.test.js +6 -2
  28. package/dist/cli/__tests__/setup-refresh.test.js.map +1 -1
  29. package/dist/cli/__tests__/sparkshell-packaging.test.js +45 -2
  30. package/dist/cli/__tests__/sparkshell-packaging.test.js.map +1 -1
  31. package/dist/cli/__tests__/team-decompose.test.js +10 -5
  32. package/dist/cli/__tests__/team-decompose.test.js.map +1 -1
  33. package/dist/cli/__tests__/team.test.js +45 -1
  34. package/dist/cli/__tests__/team.test.js.map +1 -1
  35. package/dist/cli/__tests__/ultragoal.test.js +75 -0
  36. package/dist/cli/__tests__/ultragoal.test.js.map +1 -1
  37. package/dist/cli/auth.d.ts.map +1 -1
  38. package/dist/cli/auth.js +25 -1
  39. package/dist/cli/auth.js.map +1 -1
  40. package/dist/cli/codex-feature-probe.d.ts +5 -2
  41. package/dist/cli/codex-feature-probe.d.ts.map +1 -1
  42. package/dist/cli/codex-feature-probe.js +25 -9
  43. package/dist/cli/codex-feature-probe.js.map +1 -1
  44. package/dist/cli/index.d.ts +28 -2
  45. package/dist/cli/index.d.ts.map +1 -1
  46. package/dist/cli/index.js +149 -88
  47. package/dist/cli/index.js.map +1 -1
  48. package/dist/cli/setup.d.ts.map +1 -1
  49. package/dist/cli/setup.js +9 -1
  50. package/dist/cli/setup.js.map +1 -1
  51. package/dist/cli/team.d.ts +4 -0
  52. package/dist/cli/team.d.ts.map +1 -1
  53. package/dist/cli/team.js +43 -4
  54. package/dist/cli/team.js.map +1 -1
  55. package/dist/cli/ultragoal.d.ts.map +1 -1
  56. package/dist/cli/ultragoal.js +29 -0
  57. package/dist/cli/ultragoal.js.map +1 -1
  58. package/dist/hooks/__tests__/agents-overlay.test.js +1 -0
  59. package/dist/hooks/__tests__/agents-overlay.test.js.map +1 -1
  60. package/dist/hooks/__tests__/autopilot-skill-contract.test.js +15 -0
  61. package/dist/hooks/__tests__/autopilot-skill-contract.test.js.map +1 -1
  62. package/dist/hooks/__tests__/deep-interview-contract.test.js +16 -0
  63. package/dist/hooks/__tests__/deep-interview-contract.test.js.map +1 -1
  64. package/dist/hooks/__tests__/skill-guidance-contract.test.js +14 -5
  65. package/dist/hooks/__tests__/skill-guidance-contract.test.js.map +1 -1
  66. package/dist/hooks/agents-overlay.d.ts.map +1 -1
  67. package/dist/hooks/agents-overlay.js +2 -1
  68. package/dist/hooks/agents-overlay.js.map +1 -1
  69. package/dist/hooks/extensibility/__tests__/plugin-runner.test.js +112 -1
  70. package/dist/hooks/extensibility/__tests__/plugin-runner.test.js.map +1 -1
  71. package/dist/hooks/extensibility/plugin-runner-stdin.d.ts +2 -0
  72. package/dist/hooks/extensibility/plugin-runner-stdin.d.ts.map +1 -0
  73. package/dist/hooks/extensibility/plugin-runner-stdin.js +16 -0
  74. package/dist/hooks/extensibility/plugin-runner-stdin.js.map +1 -0
  75. package/dist/hooks/extensibility/plugin-runner.js +2 -4
  76. package/dist/hooks/extensibility/plugin-runner.js.map +1 -1
  77. package/dist/hud/__tests__/index.test.js +23 -2
  78. package/dist/hud/__tests__/index.test.js.map +1 -1
  79. package/dist/hud/__tests__/reconcile.test.js +266 -0
  80. package/dist/hud/__tests__/reconcile.test.js.map +1 -1
  81. package/dist/hud/__tests__/tmux.test.js +118 -7
  82. package/dist/hud/__tests__/tmux.test.js.map +1 -1
  83. package/dist/hud/index.d.ts +6 -1
  84. package/dist/hud/index.d.ts.map +1 -1
  85. package/dist/hud/index.js +12 -3
  86. package/dist/hud/index.js.map +1 -1
  87. package/dist/hud/reconcile.d.ts +6 -2
  88. package/dist/hud/reconcile.d.ts.map +1 -1
  89. package/dist/hud/reconcile.js +58 -28
  90. package/dist/hud/reconcile.js.map +1 -1
  91. package/dist/hud/tmux.d.ts +14 -1
  92. package/dist/hud/tmux.d.ts.map +1 -1
  93. package/dist/hud/tmux.js +129 -15
  94. package/dist/hud/tmux.js.map +1 -1
  95. package/dist/ralplan/consensus-gate.js +9 -1
  96. package/dist/ralplan/consensus-gate.js.map +1 -1
  97. package/dist/scripts/__tests__/codex-native-hook.test.js +168 -15
  98. package/dist/scripts/__tests__/codex-native-hook.test.js.map +1 -1
  99. package/dist/scripts/__tests__/run-test-files.test.js +115 -1
  100. package/dist/scripts/__tests__/run-test-files.test.js.map +1 -1
  101. package/dist/scripts/codex-native-hook.d.ts.map +1 -1
  102. package/dist/scripts/codex-native-hook.js +74 -11
  103. package/dist/scripts/codex-native-hook.js.map +1 -1
  104. package/dist/scripts/notify-hook/team-worker-stop.d.ts.map +1 -1
  105. package/dist/scripts/notify-hook/team-worker-stop.js +54 -21
  106. package/dist/scripts/notify-hook/team-worker-stop.js.map +1 -1
  107. package/dist/scripts/run-test-files.js +218 -160
  108. package/dist/scripts/run-test-files.js.map +1 -1
  109. package/dist/state/__tests__/operations.test.js +463 -0
  110. package/dist/state/__tests__/operations.test.js.map +1 -1
  111. package/dist/team/__tests__/delivery-log.test.js +18 -0
  112. package/dist/team/__tests__/delivery-log.test.js.map +1 -1
  113. package/dist/team/__tests__/runtime.test.js +48 -0
  114. package/dist/team/__tests__/runtime.test.js.map +1 -1
  115. package/dist/team/__tests__/tmux-session.test.js +107 -0
  116. package/dist/team/__tests__/tmux-session.test.js.map +1 -1
  117. package/dist/team/__tests__/tmux-test-fixture.d.ts.map +1 -1
  118. package/dist/team/__tests__/tmux-test-fixture.js +14 -2
  119. package/dist/team/__tests__/tmux-test-fixture.js.map +1 -1
  120. package/dist/team/__tests__/tmux-test-fixture.test.js +1 -0
  121. package/dist/team/__tests__/tmux-test-fixture.test.js.map +1 -1
  122. package/dist/team/__tests__/worker-bootstrap.test.js +54 -1
  123. package/dist/team/__tests__/worker-bootstrap.test.js.map +1 -1
  124. package/dist/team/delivery-log.d.ts +1 -1
  125. package/dist/team/delivery-log.d.ts.map +1 -1
  126. package/dist/team/delivery-log.js.map +1 -1
  127. package/dist/team/repo-aware-decomposition.d.ts +4 -0
  128. package/dist/team/repo-aware-decomposition.d.ts.map +1 -1
  129. package/dist/team/repo-aware-decomposition.js.map +1 -1
  130. package/dist/team/runtime.d.ts.map +1 -1
  131. package/dist/team/runtime.js +78 -9
  132. package/dist/team/runtime.js.map +1 -1
  133. package/dist/team/tmux-session.d.ts +1 -0
  134. package/dist/team/tmux-session.d.ts.map +1 -1
  135. package/dist/team/tmux-session.js +16 -5
  136. package/dist/team/tmux-session.js.map +1 -1
  137. package/dist/team/ultragoal-context.d.ts +12 -0
  138. package/dist/team/ultragoal-context.d.ts.map +1 -1
  139. package/dist/team/ultragoal-context.js +32 -8
  140. package/dist/team/ultragoal-context.js.map +1 -1
  141. package/dist/utils/__tests__/paths.test.js +23 -0
  142. package/dist/utils/__tests__/paths.test.js.map +1 -1
  143. package/dist/utils/paths.d.ts.map +1 -1
  144. package/dist/utils/paths.js +4 -2
  145. package/dist/utils/paths.js.map +1 -1
  146. package/dist/utils/toml.d.ts +4 -0
  147. package/dist/utils/toml.d.ts.map +1 -0
  148. package/dist/utils/toml.js +75 -0
  149. package/dist/utils/toml.js.map +1 -0
  150. package/package.json +1 -1
  151. package/plugins/oh-my-codex/.codex-plugin/plugin.json +1 -1
  152. package/plugins/oh-my-codex/skills/autopilot/SKILL.md +3 -0
  153. package/plugins/oh-my-codex/skills/deep-interview/SKILL.md +34 -0
  154. package/plugins/oh-my-codex/skills/ultrawork/SKILL.md +32 -17
  155. package/skills/autopilot/SKILL.md +3 -0
  156. package/skills/deep-interview/SKILL.md +34 -0
  157. package/skills/ultrawork/SKILL.md +32 -17
  158. package/src/scripts/__tests__/codex-native-hook.test.ts +216 -26
  159. package/src/scripts/__tests__/run-test-files.test.ts +138 -2
  160. package/src/scripts/codex-native-hook.ts +80 -10
  161. package/src/scripts/notify-hook/team-worker-stop.ts +58 -18
  162. package/src/scripts/run-test-files.ts +229 -150
  163. package/templates/AGENTS.md +40 -199
@@ -1,13 +1,13 @@
1
1
  import { afterEach, describe, it, mock } from "node:test";
2
2
  import assert from "node:assert/strict";
3
3
  import { existsSync, mkdirSync, readFileSync, utimesSync } from "node:fs";
4
- import { chmod, mkdir, mkdtemp, readFile, rm, stat, writeFile } from "node:fs/promises";
5
- import { dirname, join } from "node:path";
4
+ import { chmod, lstat, mkdir, mkdtemp, readFile, rm, stat, symlink, writeFile } from "node:fs/promises";
5
+ import { delimiter, dirname, join } from "node:path";
6
6
  import { tmpdir } from "node:os";
7
7
  import { fileURLToPath } from "node:url";
8
8
  import { once } from "node:events";
9
9
  import TOML from "@iarna/toml";
10
- import { HELP, normalizeCodexLaunchArgs, buildTmuxShellCommand, buildTmuxPaneCommand, shouldSourceTmuxPaneShellRc, buildWindowsPromptCommand, buildTmuxSessionName, resolveCliInvocation, resolveUpdateChannelArg, commandOwnsLocalHelp, resolveCodexLaunchPolicy, resolveEffectiveLeaderLaunchPolicyOverride, resolveEnvLaunchPolicyOverride, resolveLeaderLaunchPolicyOverride, classifyCodexExecFailure, resolveSignalExitCode, parseTmuxPaneSnapshot, findHudWatchPaneIds, buildHudPaneCleanupTargets, readTopLevelTomlString, upsertTopLevelTomlString, collectInheritableTeamWorkerArgs, resolveTeamWorkerLaunchArgsEnv, injectModelInstructionsBypassArgs, resolveWorkerSparkModel, resolveSetupInstallModeArg, resolveSetupMcpModeArg, resolveSetupScopeArg, resolveSetupTeamModeArg, resolveLaunchConfigRepairOptions, readPersistedSetupPreferences, readPersistedSetupScope, resolveCodexConfigPathForLaunch, resolveCodexHomeForLaunch, resolveProjectLocalCodexHomeForLaunch, shouldAutoIsolateMadmaxLaunch, createMadmaxIsolatedRoot, buildMadmaxDetachedLaunchContextKey, withMadmaxDetachedContextLock, resolveOmxRootForLaunch, resolveDisposableWorktreeOmxRootForLaunch, prepareCodexHomeForLaunch, persistProjectLaunchRuntimeAuthState, persistProjectLaunchRuntimeProjectTrustState, runtimeCodexHomePath, buildDetachedSessionBootstrapSteps, buildDetachedTmuxSessionName, buildDetachedSessionFinalizeSteps, shouldAttachDetachedTmuxSession, buildDetachedSessionRollbackSteps, detectDetachedSessionWindowIndex, resolveNotifyTempContract, buildNotifyTempStartupMessages, buildNotifyFallbackWatcherEnv, shouldEnableNotifyFallbackWatcher, reapStaleNotifyFallbackWatcher, cleanupLaunchOrphanedMcpProcesses, reapPostLaunchOrphanedMcpProcesses, cleanupPostLaunchModeStateFiles, resolveBackgroundHelperLaunchMode, shouldDetachBackgroundHelper, resolveNotifyFallbackWatcherScript, resolveHookDerivedWatcherScript, resolveNotifyHookScript, buildDetachedWindowsBootstrapScript, acquireTmuxExtendedKeysLease, resolveNativeSessionName, releaseTmuxExtendedKeysLease, withTmuxExtendedKeys, serializeDetachedSessionParentEnv, CODEX_SQLITE_HOME_ENV, DETACHED_TMUX_HISTORY_LIMIT, } from "../index.js";
10
+ import { HELP, normalizeCodexLaunchArgs, buildTmuxShellCommand, buildTmuxPaneCommand, shouldSourceTmuxPaneShellRc, buildWindowsPromptCommand, buildTmuxSessionName, resolveCliInvocation, resolveUpdateChannelArg, commandOwnsLocalHelp, resolveCodexLaunchPolicy, resolveEffectiveLeaderLaunchPolicyOverride, resolveEnvLaunchPolicyOverride, resolveLeaderLaunchPolicyOverride, classifyCodexExecFailure, resolveSignalExitCode, parseTmuxPaneSnapshot, findHudWatchPaneIds, buildHudPaneCleanupTargets, readTopLevelTomlString, upsertTopLevelTomlString, collectInheritableTeamWorkerArgs, resolveTeamWorkerLaunchArgsEnv, injectModelInstructionsBypassArgs, resolveWorkerSparkModel, resolveSetupInstallModeArg, resolveSetupMcpModeArg, resolveSetupScopeArg, resolveSetupTeamModeArg, resolveLaunchConfigRepairOptions, readPersistedSetupPreferences, readPersistedSetupScope, resolveCodexConfigPathForLaunch, resolveCodexHomeForLaunch, resolveProjectLocalCodexHomeForLaunch, shouldAutoIsolateMadmaxLaunch, createMadmaxIsolatedRoot, buildMadmaxDetachedLaunchContextKey, withMadmaxDetachedContextLock, resolveOmxRootForLaunch, resolveDisposableWorktreeOmxRootForLaunch, prepareCodexHomeForLaunch, persistProjectLaunchRuntimeAuthState, persistProjectLaunchRuntimeProjectTrustState, runtimeCodexHomePath, buildDetachedSessionBootstrapSteps, buildDetachedTmuxSessionName, buildDetachedSessionFinalizeSteps, shouldAttachDetachedTmuxSession, buildDetachedSessionRollbackSteps, detectDetachedSessionWindowIndex, resolveNotifyTempContract, buildNotifyTempStartupMessages, buildNotifyFallbackWatcherEnv, shouldEnableNotifyFallbackWatcher, reapStaleNotifyFallbackWatcher, cleanupLaunchOrphanedMcpProcesses, reapPostLaunchOrphanedMcpProcesses, cleanupPostLaunchModeStateFiles, resolveBackgroundHelperLaunchMode, shouldDetachBackgroundHelper, resolveNotifyFallbackWatcherScript, resolveHookDerivedWatcherScript, resolveNotifyHookScript, buildDetachedWindowsBootstrapScript, acquireTmuxExtendedKeysLease, resolveNativeSessionName, releaseTmuxExtendedKeysLease, withTmuxExtendedKeys, serializeDetachedSessionParentEnv, buildInsideTmuxHudHookEnv, registerInsideTmuxHudResizeHook, buildDetachedHudHookEnv, registerDetachedHudLayoutReconcileHook, ensureOmxRuntimeCommandShim, omxRuntimeCommandShimPath, prependOmxRuntimeCommandShimToEnv, CODEX_SQLITE_HOME_ENV, DETACHED_TMUX_HISTORY_LIMIT, } from "../index.js";
11
11
  import { mergeConfig, repairConfigIfNeeded } from "../../config/generator.js";
12
12
  import { ensureReusableNodeModules } from "../../utils/repo-deps.js";
13
13
  import { readAllState } from "../../hud/state.js";
@@ -1937,7 +1937,19 @@ describe("tmux HUD pane helpers", () => {
1937
1937
  "-t",
1938
1938
  "%leader",
1939
1939
  "-F",
1940
- "#{pane_id}\x1f#{pane_current_command}\x1f#{pane_start_command}\x1f#{pane_current_path}",
1940
+ [
1941
+ "#{pane_id}",
1942
+ "#{pane_current_command}",
1943
+ "#{pane_left}",
1944
+ "#{pane_top}",
1945
+ "#{pane_width}",
1946
+ "#{pane_height}",
1947
+ "#{pane_bottom}",
1948
+ "#{window_width}",
1949
+ "#{window_height}",
1950
+ "#{pane_start_command}",
1951
+ "#{pane_current_path}",
1952
+ ].join("\x1f"),
1941
1953
  ]);
1942
1954
  });
1943
1955
  it("createHudWatchPane splits from the emitting pane target when provided", () => {
@@ -2076,6 +2088,134 @@ describe("detached tmux new-session sequencing", () => {
2076
2088
  assert.match(envScript, /export IS_GAJAE_SLOP_GENERATOR='1'/);
2077
2089
  assert.doesNotMatch(envScript, /not-a-shell-name/);
2078
2090
  });
2091
+ it("creates a repo-local omx command shim for launched Codex sessions", async () => {
2092
+ const cwd = await mkdtemp(join(tmpdir(), "omx-runtime-command-shim-"));
2093
+ try {
2094
+ const shimDir = ensureOmxRuntimeCommandShim(cwd, "/repo/dist/cli/omx.js", "/usr/local/bin/node");
2095
+ const shimPath = omxRuntimeCommandShimPath(cwd);
2096
+ assert.equal(shimDir, dirname(shimPath));
2097
+ assert.equal(existsSync(shimPath), true);
2098
+ assert.equal(await readFile(shimPath, "utf-8"), [
2099
+ "#!/bin/sh",
2100
+ `exec '/usr/local/bin/node' '/repo/dist/cli/omx.js' "$@"`,
2101
+ "",
2102
+ ].join("\n"));
2103
+ assert.equal((await stat(shimPath)).mode & 0o700, 0o700);
2104
+ }
2105
+ finally {
2106
+ await rm(cwd, { recursive: true, force: true });
2107
+ }
2108
+ });
2109
+ it("prepends the repo-local omx shim before global PATH entries", async () => {
2110
+ const cwd = await mkdtemp(join(tmpdir(), "omx-runtime-command-shim-env-"));
2111
+ try {
2112
+ const env = prependOmxRuntimeCommandShimToEnv(cwd, {
2113
+ PATH: "/opt/homebrew/bin:/usr/bin",
2114
+ OMX_ENTRY_PATH: "/opt/homebrew/lib/node_modules/oh-my-codex/dist/cli/omx.js",
2115
+ }, "/repo/dist/cli/omx.js", "/usr/local/bin/node");
2116
+ const shimDir = dirname(omxRuntimeCommandShimPath(cwd));
2117
+ assert.equal(env.PATH, `${shimDir}${delimiter}/opt/homebrew/bin:/usr/bin`);
2118
+ assert.equal(env.OMX_ENTRY_PATH, "/repo/dist/cli/omx.js");
2119
+ assert.equal(env.OMX_STARTUP_CWD, cwd);
2120
+ }
2121
+ finally {
2122
+ await rm(cwd, { recursive: true, force: true });
2123
+ }
2124
+ });
2125
+ it("executes the repo-local omx shim before a stale global omx with misleading success output", async () => {
2126
+ const cwd = await mkdtemp(join(tmpdir(), "omx-runtime-command-shim-exec-"));
2127
+ try {
2128
+ const fakeGlobalBin = join(cwd, "fake-global-bin");
2129
+ const fakeLocalBin = join(cwd, "fake local's $() ; bin");
2130
+ await mkdir(fakeGlobalBin);
2131
+ await mkdir(fakeLocalBin);
2132
+ const globalMarker = join(cwd, "GLOBAL_CALLED");
2133
+ const localMarker = join(cwd, "LOCAL_CALLED");
2134
+ const fakeGlobalOmx = join(fakeGlobalBin, "omx");
2135
+ const fakeNode = join(fakeLocalBin, "node runner");
2136
+ const localOmxEntry = join(fakeLocalBin, "omx entry's $() ;.js");
2137
+ await writeFile(fakeGlobalOmx, `#!/bin/sh
2138
+ printf 'global-called\\n' > "${globalMarker}"
2139
+ printf '{"success":true,"source":"global"}\\n'
2140
+ exit 0
2141
+ `);
2142
+ await chmod(fakeGlobalOmx, 0o755);
2143
+ await writeFile(fakeNode, `#!/bin/sh
2144
+ printf '%s\\n' "$@" > "${localMarker}"
2145
+ printf '{"success":true,"source":"local"}\\n'
2146
+ exit 0
2147
+ `);
2148
+ await chmod(fakeNode, 0o755);
2149
+ const env = prependOmxRuntimeCommandShimToEnv(cwd, { PATH: `${fakeGlobalBin}${delimiter}/usr/bin:/bin` }, localOmxEntry, fakeNode);
2150
+ const { execFileSync } = await import("node:child_process");
2151
+ const output = execFileSync("omx", ["team", "status", "hud-check"], {
2152
+ cwd,
2153
+ env,
2154
+ encoding: "utf-8",
2155
+ });
2156
+ assert.match(output, /"source":"local"/);
2157
+ assert.equal(existsSync(globalMarker), false);
2158
+ assert.deepEqual((await readFile(localMarker, "utf-8")).trim().split("\n"), [
2159
+ localOmxEntry,
2160
+ "team",
2161
+ "status",
2162
+ "hud-check",
2163
+ ]);
2164
+ }
2165
+ finally {
2166
+ await rm(cwd, { recursive: true, force: true });
2167
+ }
2168
+ });
2169
+ it("overwrites stale runtime shim contents and permissions", async () => {
2170
+ const cwd = await mkdtemp(join(tmpdir(), "omx-runtime-command-shim-stale-"));
2171
+ try {
2172
+ const shimPath = omxRuntimeCommandShimPath(cwd);
2173
+ await mkdir(dirname(shimPath), { recursive: true });
2174
+ await writeFile(shimPath, "#!/bin/sh\necho stale-global\n");
2175
+ await chmod(shimPath, 0o600);
2176
+ ensureOmxRuntimeCommandShim(cwd, "/repo/dist/cli/omx.js", "/usr/local/bin/node");
2177
+ assert.equal(await readFile(shimPath, "utf-8"), [
2178
+ "#!/bin/sh",
2179
+ `exec '/usr/local/bin/node' '/repo/dist/cli/omx.js' "$@"`,
2180
+ "",
2181
+ ].join("\n"));
2182
+ assert.equal((await stat(shimPath)).mode & 0o777, 0o700);
2183
+ }
2184
+ finally {
2185
+ await rm(cwd, { recursive: true, force: true });
2186
+ }
2187
+ });
2188
+ it("replaces a stale runtime shim symlink without following it", async () => {
2189
+ const cwd = await mkdtemp(join(tmpdir(), "omx-runtime-command-shim-symlink-"));
2190
+ try {
2191
+ if (process.platform === "win32")
2192
+ return;
2193
+ const shimPath = omxRuntimeCommandShimPath(cwd);
2194
+ const externalTarget = join(cwd, "outside-target");
2195
+ await mkdir(dirname(shimPath), { recursive: true });
2196
+ await writeFile(externalTarget, "do not overwrite\n");
2197
+ await symlink(externalTarget, shimPath);
2198
+ ensureOmxRuntimeCommandShim(cwd, "/repo/dist/cli/omx.js", "/usr/local/bin/node");
2199
+ assert.equal(await readFile(externalTarget, "utf-8"), "do not overwrite\n");
2200
+ assert.equal((await lstat(shimPath)).isSymbolicLink(), false);
2201
+ assert.match(await readFile(shimPath, "utf-8"), /\/repo\/dist\/cli\/omx\.js/);
2202
+ }
2203
+ finally {
2204
+ await rm(cwd, { recursive: true, force: true });
2205
+ }
2206
+ });
2207
+ it("throws when the runtime shim bin path is not a directory", async () => {
2208
+ const cwd = await mkdtemp(join(tmpdir(), "omx-runtime-command-shim-file-"));
2209
+ try {
2210
+ const shimPath = omxRuntimeCommandShimPath(cwd);
2211
+ await mkdir(dirname(dirname(shimPath)), { recursive: true });
2212
+ await writeFile(dirname(shimPath), "not a directory\n");
2213
+ assert.throws(() => ensureOmxRuntimeCommandShim(cwd, "/repo/dist/cli/omx.js", "/usr/local/bin/node"), /not a directory/);
2214
+ }
2215
+ finally {
2216
+ await rm(cwd, { recursive: true, force: true });
2217
+ }
2218
+ });
2079
2219
  it("keeps detached tmux bootstrap bounded when no interactive parent env file is requested", () => {
2080
2220
  const steps = buildDetachedSessionBootstrapSteps("omx-demo", "/tmp/project", "'codex' '--model' 'gpt-5'", "'node' '/tmp/omx.js' 'hud' '--watch'", null, undefined, null, false, "sess-detached-managed", undefined, undefined, undefined, { CUSTOM_LLM_API_KEY: "fake-provider-key" });
2081
2221
  const newSession = steps.find((step) => step.name === "new-session");
@@ -2105,7 +2245,113 @@ describe("detached tmux new-session sequencing", () => {
2105
2245
  });
2106
2246
  it("runCodex registers a HUD resize hook immediately for inside-tmux launches", async () => {
2107
2247
  const source = await readFile(join(repoRoot, 'src', 'cli', 'index.ts'), 'utf-8');
2108
- assert.match(source, /registerHudResizeHook\(hudPaneId,\s*currentPaneId,\s*HUD_TMUX_HEIGHT_LINES\)/);
2248
+ assert.match(source, /registerInsideTmuxHudResizeHook\(\{\s*hudPaneId,\s*currentPaneId,\s*cwd,\s*sessionId,\s*omxRootOverride,\s*\}\)/);
2249
+ assert.match(source, /if \(currentPaneId\) \{\s*unregisterHudResizeHook\(currentPaneId\);\s*\}/);
2250
+ });
2251
+ it("buildInsideTmuxHudHookEnv tags hook commands with session, owner, leader, and local root", () => {
2252
+ const env = buildInsideTmuxHudHookEnv({ PATH: "/bin" }, "sess-a", "%leader", "/repo");
2253
+ assert.equal(env.PATH, "/bin");
2254
+ assert.equal(env.OMX_SESSION_ID, "sess-a");
2255
+ assert.equal(env.OMX_TMUX_HUD_OWNER, "1");
2256
+ assert.equal(env.OMX_TMUX_HUD_LEADER_PANE, "%leader");
2257
+ assert.equal(env.OMX_ROOT, "/repo");
2258
+ });
2259
+ it("registerInsideTmuxHudResizeHook forwards cwd and env to hook registration", () => {
2260
+ const calls = [];
2261
+ const result = registerInsideTmuxHudResizeHook({
2262
+ hudPaneId: "%hud",
2263
+ currentPaneId: "%leader",
2264
+ cwd: "/repo",
2265
+ sessionId: "sess-a",
2266
+ omxRootOverride: "/repo",
2267
+ baseEnv: { PATH: "/bin" },
2268
+ register: (hudPaneId, leaderPaneId, heightLines, options) => {
2269
+ calls.push({ hudPaneId, leaderPaneId, heightLines, cwd: options?.cwd, env: options?.env });
2270
+ return true;
2271
+ },
2272
+ });
2273
+ assert.equal(result, true);
2274
+ assert.deepEqual(calls, [{
2275
+ hudPaneId: "%hud",
2276
+ leaderPaneId: "%leader",
2277
+ heightLines: HUD_TMUX_HEIGHT_LINES,
2278
+ cwd: "/repo",
2279
+ env: {
2280
+ PATH: "/bin",
2281
+ OMX_SESSION_ID: "sess-a",
2282
+ OMX_TMUX_HUD_OWNER: "1",
2283
+ OMX_TMUX_HUD_LEADER_PANE: "%leader",
2284
+ OMX_ROOT: "/repo",
2285
+ },
2286
+ }]);
2287
+ assert.equal(registerInsideTmuxHudResizeHook({
2288
+ hudPaneId: null,
2289
+ currentPaneId: "%leader",
2290
+ cwd: "/repo",
2291
+ sessionId: "sess-a",
2292
+ register: () => {
2293
+ throw new Error("should not register without a HUD pane");
2294
+ },
2295
+ }), false);
2296
+ });
2297
+ it("buildDetachedHudHookEnv preserves tmux targeting and local launcher identity", () => {
2298
+ const env = buildDetachedHudHookEnv({ PATH: "/bin" }, "sess-a", "%leader", "/tmp/tmux.sock,123,7", "/repo/dist/cli/omx.js", "/repo");
2299
+ assert.equal(env.PATH, "/bin");
2300
+ assert.equal(env.TMUX, "/tmp/tmux.sock,123,7");
2301
+ assert.equal(env.TMUX_PANE, "%leader");
2302
+ assert.equal(env.OMX_SESSION_ID, "sess-a");
2303
+ assert.equal(env.OMX_TMUX_HUD_OWNER, "1");
2304
+ assert.equal(env.OMX_ROOT, "/repo");
2305
+ assert.equal(env.OMX_ENTRY_PATH, "/repo/dist/cli/omx.js");
2306
+ });
2307
+ it("registerDetachedHudLayoutReconcileHook reads TMUX from the detached leader pane before registering", () => {
2308
+ const calls = [];
2309
+ const readTargets = [];
2310
+ const result = registerDetachedHudLayoutReconcileHook({
2311
+ hudPaneId: "%hud",
2312
+ detachedLeaderPaneId: "%leader",
2313
+ cwd: "/repo",
2314
+ sessionId: "sess-a",
2315
+ omxBin: "/repo/dist/cli/omx.js",
2316
+ omxRootOverride: "/repo",
2317
+ baseEnv: { PATH: "/bin" },
2318
+ readTmuxEnvValue: (targetPaneId) => {
2319
+ readTargets.push(targetPaneId);
2320
+ return "/tmp/tmux.sock,123,7";
2321
+ },
2322
+ register: (hudPaneId, leaderPaneId, heightLines, options) => {
2323
+ calls.push({ hudPaneId, leaderPaneId, heightLines, cwd: options?.cwd, env: options?.env });
2324
+ return true;
2325
+ },
2326
+ });
2327
+ assert.equal(result, true);
2328
+ assert.deepEqual(readTargets, ["%leader"]);
2329
+ assert.deepEqual(calls, [{
2330
+ hudPaneId: "%hud",
2331
+ leaderPaneId: "%leader",
2332
+ heightLines: HUD_TMUX_HEIGHT_LINES,
2333
+ cwd: "/repo",
2334
+ env: {
2335
+ PATH: "/bin",
2336
+ TMUX: "/tmp/tmux.sock,123,7",
2337
+ TMUX_PANE: "%leader",
2338
+ OMX_SESSION_ID: "sess-a",
2339
+ OMX_TMUX_HUD_OWNER: "1",
2340
+ OMX_ROOT: "/repo",
2341
+ OMX_ENTRY_PATH: "/repo/dist/cli/omx.js",
2342
+ },
2343
+ }]);
2344
+ assert.equal(registerDetachedHudLayoutReconcileHook({
2345
+ hudPaneId: "%hud",
2346
+ detachedLeaderPaneId: "%leader",
2347
+ cwd: "/repo",
2348
+ sessionId: "sess-a",
2349
+ omxBin: "/repo/dist/cli/omx.js",
2350
+ readTmuxEnvValue: () => undefined,
2351
+ register: () => {
2352
+ throw new Error("should not register without TMUX");
2353
+ },
2354
+ }), false);
2109
2355
  });
2110
2356
  it("buildDetachedSessionBootstrapSteps starts native Windows detached sessions with powershell", () => {
2111
2357
  const hudCmd = buildWindowsPromptCommand("node", [