oh-my-codex 0.10.2 → 0.10.4

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 (293) hide show
  1. package/Cargo.lock +2 -2
  2. package/Cargo.toml +1 -1
  3. package/README.de.md +4 -4
  4. package/README.es.md +4 -4
  5. package/README.fr.md +4 -4
  6. package/README.it.md +4 -4
  7. package/README.ja.md +4 -4
  8. package/README.ko.md +4 -4
  9. package/README.md +13 -7
  10. package/README.pt.md +4 -4
  11. package/README.ru.md +4 -4
  12. package/README.tr.md +4 -4
  13. package/README.vi.md +4 -4
  14. package/README.zh-TW.md +4 -4
  15. package/README.zh.md +4 -4
  16. package/dist/agents/__tests__/native-config.test.js +37 -33
  17. package/dist/agents/__tests__/native-config.test.js.map +1 -1
  18. package/dist/agents/native-config.d.ts +18 -6
  19. package/dist/agents/native-config.d.ts.map +1 -1
  20. package/dist/agents/native-config.js +109 -92
  21. package/dist/agents/native-config.js.map +1 -1
  22. package/dist/autoresearch/__tests__/contracts.test.js +37 -1
  23. package/dist/autoresearch/__tests__/contracts.test.js.map +1 -1
  24. package/dist/autoresearch/__tests__/runtime-parity-extra.test.js +10 -10
  25. package/dist/autoresearch/__tests__/runtime-parity-extra.test.js.map +1 -1
  26. package/dist/autoresearch/__tests__/runtime.test.js +2 -2
  27. package/dist/autoresearch/__tests__/runtime.test.js.map +1 -1
  28. package/dist/autoresearch/contracts.d.ts.map +1 -1
  29. package/dist/autoresearch/contracts.js +17 -10
  30. package/dist/autoresearch/contracts.js.map +1 -1
  31. package/dist/autoresearch/runtime.d.ts.map +1 -1
  32. package/dist/autoresearch/runtime.js +84 -96
  33. package/dist/autoresearch/runtime.js.map +1 -1
  34. package/dist/cli/__tests__/agents-init.test.js +2 -0
  35. package/dist/cli/__tests__/agents-init.test.js.map +1 -1
  36. package/dist/cli/__tests__/agents.test.d.ts +2 -0
  37. package/dist/cli/__tests__/agents.test.d.ts.map +1 -0
  38. package/dist/cli/__tests__/agents.test.js +114 -0
  39. package/dist/cli/__tests__/agents.test.js.map +1 -0
  40. package/dist/cli/__tests__/autoresearch-guided.test.js +156 -1
  41. package/dist/cli/__tests__/autoresearch-guided.test.js.map +1 -1
  42. package/dist/cli/__tests__/autoresearch.test.js +483 -25
  43. package/dist/cli/__tests__/autoresearch.test.js.map +1 -1
  44. package/dist/cli/__tests__/cleanup.test.d.ts +2 -0
  45. package/dist/cli/__tests__/cleanup.test.d.ts.map +1 -0
  46. package/dist/cli/__tests__/cleanup.test.js +213 -0
  47. package/dist/cli/__tests__/cleanup.test.js.map +1 -0
  48. package/dist/cli/__tests__/error-handling-warnings.test.js +1 -1
  49. package/dist/cli/__tests__/error-handling-warnings.test.js.map +1 -1
  50. package/dist/cli/__tests__/explore.test.js +3 -3
  51. package/dist/cli/__tests__/explore.test.js.map +1 -1
  52. package/dist/cli/__tests__/index.test.js +530 -401
  53. package/dist/cli/__tests__/index.test.js.map +1 -1
  54. package/dist/cli/__tests__/native-assets.test.js +72 -9
  55. package/dist/cli/__tests__/native-assets.test.js.map +1 -1
  56. package/dist/cli/__tests__/ralph-deslop-contract.test.d.ts +2 -0
  57. package/dist/cli/__tests__/ralph-deslop-contract.test.d.ts.map +1 -0
  58. package/dist/cli/__tests__/ralph-deslop-contract.test.js +28 -0
  59. package/dist/cli/__tests__/ralph-deslop-contract.test.js.map +1 -0
  60. package/dist/cli/__tests__/ralph-prd-deep-interview.test.js +4 -0
  61. package/dist/cli/__tests__/ralph-prd-deep-interview.test.js.map +1 -1
  62. package/dist/cli/__tests__/ralphthon.test.d.ts +2 -0
  63. package/dist/cli/__tests__/ralphthon.test.d.ts.map +1 -0
  64. package/dist/cli/__tests__/ralphthon.test.js +28 -0
  65. package/dist/cli/__tests__/ralphthon.test.js.map +1 -0
  66. package/dist/cli/__tests__/setup-agents-overwrite.test.js +36 -1
  67. package/dist/cli/__tests__/setup-agents-overwrite.test.js.map +1 -1
  68. package/dist/cli/__tests__/setup-prompts-overwrite.test.js +35 -5
  69. package/dist/cli/__tests__/setup-prompts-overwrite.test.js.map +1 -1
  70. package/dist/cli/__tests__/setup-refresh.test.js +2 -2
  71. package/dist/cli/__tests__/setup-refresh.test.js.map +1 -1
  72. package/dist/cli/__tests__/setup-scope.test.js +131 -161
  73. package/dist/cli/__tests__/setup-scope.test.js.map +1 -1
  74. package/dist/cli/__tests__/setup-skills-overwrite.test.js +10 -10
  75. package/dist/cli/__tests__/setup-skills-overwrite.test.js.map +1 -1
  76. package/dist/cli/__tests__/sparkshell-cli.test.js +28 -2
  77. package/dist/cli/__tests__/sparkshell-cli.test.js.map +1 -1
  78. package/dist/cli/__tests__/team.test.js +1 -112
  79. package/dist/cli/__tests__/team.test.js.map +1 -1
  80. package/dist/cli/__tests__/uninstall.test.js +7 -20
  81. package/dist/cli/__tests__/uninstall.test.js.map +1 -1
  82. package/dist/cli/agents-init.d.ts.map +1 -1
  83. package/dist/cli/agents-init.js +99 -95
  84. package/dist/cli/agents-init.js.map +1 -1
  85. package/dist/cli/agents.d.ts +14 -0
  86. package/dist/cli/agents.d.ts.map +1 -0
  87. package/dist/cli/agents.js +261 -0
  88. package/dist/cli/agents.js.map +1 -0
  89. package/dist/cli/autoresearch-guided.d.ts +8 -0
  90. package/dist/cli/autoresearch-guided.d.ts.map +1 -1
  91. package/dist/cli/autoresearch-guided.js +104 -37
  92. package/dist/cli/autoresearch-guided.js.map +1 -1
  93. package/dist/cli/autoresearch-intake.d.ts +62 -0
  94. package/dist/cli/autoresearch-intake.d.ts.map +1 -0
  95. package/dist/cli/autoresearch-intake.js +336 -0
  96. package/dist/cli/autoresearch-intake.js.map +1 -0
  97. package/dist/cli/autoresearch.d.ts +4 -1
  98. package/dist/cli/autoresearch.d.ts.map +1 -1
  99. package/dist/cli/autoresearch.js +181 -32
  100. package/dist/cli/autoresearch.js.map +1 -1
  101. package/dist/cli/cleanup.d.ts +52 -0
  102. package/dist/cli/cleanup.d.ts.map +1 -0
  103. package/dist/cli/cleanup.js +302 -0
  104. package/dist/cli/cleanup.js.map +1 -0
  105. package/dist/cli/doctor.d.ts.map +1 -1
  106. package/dist/cli/doctor.js +9 -37
  107. package/dist/cli/doctor.js.map +1 -1
  108. package/dist/cli/explore.d.ts.map +1 -1
  109. package/dist/cli/explore.js +5 -4
  110. package/dist/cli/explore.js.map +1 -1
  111. package/dist/cli/index.d.ts +5 -7
  112. package/dist/cli/index.d.ts.map +1 -1
  113. package/dist/cli/index.js +623 -451
  114. package/dist/cli/index.js.map +1 -1
  115. package/dist/cli/native-assets.d.ts +15 -1
  116. package/dist/cli/native-assets.d.ts.map +1 -1
  117. package/dist/cli/native-assets.js +134 -32
  118. package/dist/cli/native-assets.js.map +1 -1
  119. package/dist/cli/ralph.d.ts.map +1 -1
  120. package/dist/cli/ralph.js +38 -1
  121. package/dist/cli/ralph.js.map +1 -1
  122. package/dist/cli/ralphthon.d.ts +14 -0
  123. package/dist/cli/ralphthon.d.ts.map +1 -0
  124. package/dist/cli/ralphthon.js +234 -0
  125. package/dist/cli/ralphthon.js.map +1 -0
  126. package/dist/cli/setup.d.ts +1 -4
  127. package/dist/cli/setup.d.ts.map +1 -1
  128. package/dist/cli/setup.js +111 -76
  129. package/dist/cli/setup.js.map +1 -1
  130. package/dist/cli/sparkshell.d.ts +3 -1
  131. package/dist/cli/sparkshell.d.ts.map +1 -1
  132. package/dist/cli/sparkshell.js +35 -16
  133. package/dist/cli/sparkshell.js.map +1 -1
  134. package/dist/cli/team.d.ts.map +1 -1
  135. package/dist/cli/team.js +1 -0
  136. package/dist/cli/team.js.map +1 -1
  137. package/dist/cli/uninstall.d.ts +1 -1
  138. package/dist/cli/uninstall.d.ts.map +1 -1
  139. package/dist/cli/uninstall.js +82 -64
  140. package/dist/cli/uninstall.js.map +1 -1
  141. package/dist/config/__tests__/generator-idempotent.test.js +10 -10
  142. package/dist/config/__tests__/generator-idempotent.test.js.map +1 -1
  143. package/dist/config/__tests__/generator-notify.test.js +15 -0
  144. package/dist/config/__tests__/generator-notify.test.js.map +1 -1
  145. package/dist/config/generator.d.ts +0 -1
  146. package/dist/config/generator.d.ts.map +1 -1
  147. package/dist/config/generator.js +53 -42
  148. package/dist/config/generator.js.map +1 -1
  149. package/dist/hooks/__tests__/agents-overlay.test.js +295 -230
  150. package/dist/hooks/__tests__/agents-overlay.test.js.map +1 -1
  151. package/dist/hooks/__tests__/anti-slop-workflow.test.js +3 -0
  152. package/dist/hooks/__tests__/anti-slop-workflow.test.js.map +1 -1
  153. package/dist/hooks/__tests__/deep-interview-contract.test.js +49 -24
  154. package/dist/hooks/__tests__/deep-interview-contract.test.js.map +1 -1
  155. package/dist/hooks/__tests__/notify-fallback-watcher-ralphthon.test.d.ts +2 -0
  156. package/dist/hooks/__tests__/notify-fallback-watcher-ralphthon.test.d.ts.map +1 -0
  157. package/dist/hooks/__tests__/notify-fallback-watcher-ralphthon.test.js +193 -0
  158. package/dist/hooks/__tests__/notify-fallback-watcher-ralphthon.test.js.map +1 -0
  159. package/dist/hooks/agents-overlay.d.ts +1 -1
  160. package/dist/hooks/agents-overlay.d.ts.map +1 -1
  161. package/dist/hooks/agents-overlay.js +109 -106
  162. package/dist/hooks/agents-overlay.js.map +1 -1
  163. package/dist/hud/constants.d.ts +1 -1
  164. package/dist/hud/constants.js +1 -1
  165. package/dist/modes/base.d.ts +1 -1
  166. package/dist/modes/base.d.ts.map +1 -1
  167. package/dist/modes/base.js +1 -1
  168. package/dist/modes/base.js.map +1 -1
  169. package/dist/notifications/__tests__/formatter.test.js +36 -2
  170. package/dist/notifications/__tests__/formatter.test.js.map +1 -1
  171. package/dist/notifications/formatter.d.ts +3 -2
  172. package/dist/notifications/formatter.d.ts.map +1 -1
  173. package/dist/notifications/formatter.js +33 -9
  174. package/dist/notifications/formatter.js.map +1 -1
  175. package/dist/ralphthon/__tests__/bootstrap.test.d.ts +2 -0
  176. package/dist/ralphthon/__tests__/bootstrap.test.d.ts.map +1 -0
  177. package/dist/ralphthon/__tests__/bootstrap.test.js +23 -0
  178. package/dist/ralphthon/__tests__/bootstrap.test.js.map +1 -0
  179. package/dist/ralphthon/__tests__/orchestrator.test.d.ts +2 -0
  180. package/dist/ralphthon/__tests__/orchestrator.test.d.ts.map +1 -0
  181. package/dist/ralphthon/__tests__/orchestrator.test.js +309 -0
  182. package/dist/ralphthon/__tests__/orchestrator.test.js.map +1 -0
  183. package/dist/ralphthon/__tests__/prd.test.d.ts +2 -0
  184. package/dist/ralphthon/__tests__/prd.test.d.ts.map +1 -0
  185. package/dist/ralphthon/__tests__/prd.test.js +133 -0
  186. package/dist/ralphthon/__tests__/prd.test.js.map +1 -0
  187. package/dist/ralphthon/bootstrap.d.ts +3 -0
  188. package/dist/ralphthon/bootstrap.d.ts.map +1 -0
  189. package/dist/ralphthon/bootstrap.js +84 -0
  190. package/dist/ralphthon/bootstrap.js.map +1 -0
  191. package/dist/ralphthon/orchestrator.d.ts +50 -0
  192. package/dist/ralphthon/orchestrator.d.ts.map +1 -0
  193. package/dist/ralphthon/orchestrator.js +362 -0
  194. package/dist/ralphthon/orchestrator.js.map +1 -0
  195. package/dist/ralphthon/prd.d.ts +191 -0
  196. package/dist/ralphthon/prd.d.ts.map +1 -0
  197. package/dist/ralphthon/prd.js +359 -0
  198. package/dist/ralphthon/prd.js.map +1 -0
  199. package/dist/ralphthon/runtime.d.ts +31 -0
  200. package/dist/ralphthon/runtime.d.ts.map +1 -0
  201. package/dist/ralphthon/runtime.js +108 -0
  202. package/dist/ralphthon/runtime.js.map +1 -0
  203. package/dist/ralphthon/tmux.d.ts +3 -0
  204. package/dist/ralphthon/tmux.d.ts.map +1 -0
  205. package/dist/ralphthon/tmux.js +39 -0
  206. package/dist/ralphthon/tmux.js.map +1 -0
  207. package/dist/subagents/__tests__/tracker.test.d.ts +2 -0
  208. package/dist/subagents/__tests__/tracker.test.d.ts.map +1 -0
  209. package/dist/subagents/__tests__/tracker.test.js +47 -0
  210. package/dist/subagents/__tests__/tracker.test.js.map +1 -0
  211. package/dist/subagents/tracker.d.ts +52 -0
  212. package/dist/subagents/tracker.d.ts.map +1 -0
  213. package/dist/subagents/tracker.js +175 -0
  214. package/dist/subagents/tracker.js.map +1 -0
  215. package/dist/team/__tests__/runtime.test.js +2 -2
  216. package/dist/team/__tests__/runtime.test.js.map +1 -1
  217. package/dist/team/__tests__/tmux-session.test.js +34 -0
  218. package/dist/team/__tests__/tmux-session.test.js.map +1 -1
  219. package/dist/team/__tests__/worker-bootstrap.test.js +189 -163
  220. package/dist/team/__tests__/worker-bootstrap.test.js.map +1 -1
  221. package/dist/team/__tests__/worktree.test.js +1 -1
  222. package/dist/team/__tests__/worktree.test.js.map +1 -1
  223. package/dist/team/tmux-session.d.ts +4 -4
  224. package/dist/team/tmux-session.d.ts.map +1 -1
  225. package/dist/team/tmux-session.js +48 -15
  226. package/dist/team/tmux-session.js.map +1 -1
  227. package/dist/team/worker-bootstrap.d.ts +1 -1
  228. package/dist/team/worker-bootstrap.d.ts.map +1 -1
  229. package/dist/team/worker-bootstrap.js +58 -63
  230. package/dist/team/worker-bootstrap.js.map +1 -1
  231. package/dist/team/worktree.js +1 -1
  232. package/dist/team/worktree.js.map +1 -1
  233. package/dist/utils/__tests__/agents-md.test.d.ts +2 -0
  234. package/dist/utils/__tests__/agents-md.test.d.ts.map +1 -0
  235. package/dist/utils/__tests__/agents-md.test.js +32 -0
  236. package/dist/utils/__tests__/agents-md.test.js.map +1 -0
  237. package/dist/utils/__tests__/agents-model-table.test.d.ts +2 -0
  238. package/dist/utils/__tests__/agents-model-table.test.d.ts.map +1 -0
  239. package/dist/utils/__tests__/agents-model-table.test.js +84 -0
  240. package/dist/utils/__tests__/agents-model-table.test.js.map +1 -0
  241. package/dist/utils/__tests__/paths.test.js +78 -83
  242. package/dist/utils/__tests__/paths.test.js.map +1 -1
  243. package/dist/utils/agents-md.d.ts.map +1 -1
  244. package/dist/utils/agents-md.js +10 -0
  245. package/dist/utils/agents-md.js.map +1 -1
  246. package/dist/utils/agents-model-table.d.ts +16 -0
  247. package/dist/utils/agents-model-table.d.ts.map +1 -0
  248. package/dist/utils/agents-model-table.js +83 -0
  249. package/dist/utils/agents-model-table.js.map +1 -0
  250. package/dist/utils/paths.d.ts +6 -6
  251. package/dist/utils/paths.d.ts.map +1 -1
  252. package/dist/utils/paths.js +31 -31
  253. package/dist/utils/paths.js.map +1 -1
  254. package/dist/verification/__tests__/explore-harness-release-workflow.test.js +21 -3
  255. package/dist/verification/__tests__/explore-harness-release-workflow.test.js.map +1 -1
  256. package/dist/verification/__tests__/native-release-manifest.test.d.ts +2 -0
  257. package/dist/verification/__tests__/native-release-manifest.test.d.ts.map +1 -0
  258. package/dist/verification/__tests__/native-release-manifest.test.js +80 -0
  259. package/dist/verification/__tests__/native-release-manifest.test.js.map +1 -0
  260. package/package.json +1 -1
  261. package/prompts/executor.md +15 -0
  262. package/scripts/__tests__/smoke-packed-install.test.mjs +137 -8
  263. package/scripts/eval-adaptive-sort-optimization.py +24 -0
  264. package/scripts/eval-in-action-cat-shellout-demo.js +31 -0
  265. package/scripts/eval-ml-kaggle-model-optimization.py +29 -0
  266. package/scripts/eval-noisy-bayesopt-highdim.py +44 -0
  267. package/scripts/eval-noisy-latent-subspace-discovery.py +44 -0
  268. package/scripts/generate-native-release-manifest.mjs +14 -3
  269. package/scripts/notify-fallback-watcher.js +308 -6
  270. package/scripts/notify-hook.js +20 -0
  271. package/scripts/run-autoresearch-showcase.sh +75 -0
  272. package/scripts/smoke-packed-install.mjs +142 -10
  273. package/skills/ai-slop-cleaner/SKILL.md +7 -0
  274. package/skills/deep-interview/SKILL.md +30 -1
  275. package/skills/omx-setup/SKILL.md +2 -2
  276. package/skills/ralph/SKILL.md +15 -0
  277. package/skills/skill/SKILL.md +32 -32
  278. package/skills/team/SKILL.md +6 -0
  279. package/skills/worker/SKILL.md +2 -2
  280. package/templates/AGENTS.md +97 -16
  281. package/dist/cli/__tests__/runtime-native-resolution.test.d.ts +0 -2
  282. package/dist/cli/__tests__/runtime-native-resolution.test.d.ts.map +0 -1
  283. package/dist/cli/__tests__/runtime-native-resolution.test.js.map +0 -1
  284. package/dist/cli/__tests__/runtime-native.test.d.ts +0 -2
  285. package/dist/cli/__tests__/runtime-native.test.d.ts.map +0 -1
  286. package/dist/cli/__tests__/runtime-native.test.js.map +0 -1
  287. package/dist/cli/runtime-native.d.ts +0 -23
  288. package/dist/cli/runtime-native.d.ts.map +0 -1
  289. package/dist/cli/runtime-native.js +0 -86
  290. package/dist/cli/runtime-native.js.map +0 -1
  291. package/dist/mcp/__tests__/runtime-run-native-cutover.test.d.ts +0 -2
  292. package/dist/mcp/__tests__/runtime-run-native-cutover.test.d.ts.map +0 -1
  293. package/dist/mcp/__tests__/runtime-run-native-cutover.test.js.map +0 -1
package/dist/cli/index.js CHANGED
@@ -2,41 +2,45 @@
2
2
  * oh-my-codex CLI
3
3
  * Multi-agent orchestration for OpenAI Codex CLI
4
4
  */
5
- import { execFileSync, spawn } from 'child_process';
6
- import { basename, dirname, join } from 'path';
7
- import { existsSync, readFileSync } from 'fs';
8
- import { constants as osConstants } from 'os';
9
- import { setup, SETUP_SCOPES, SETUP_SKILL_TARGETS } from './setup.js';
10
- import { uninstall } from './uninstall.js';
11
- import { version } from './version.js';
12
- import { tmuxHookCommand } from './tmux-hook.js';
13
- import { hooksCommand } from './hooks.js';
14
- import { hudCommand } from '../hud/index.js';
15
- import { teamCommand } from './team.js';
16
- import { ralphCommand } from './ralph.js';
17
- import { askCommand } from './ask.js';
18
- import { exploreCommand } from './explore.js';
19
- import { sparkshellCommand } from './sparkshell.js';
20
- import { agentsInitCommand } from './agents-init.js';
21
- import { sessionCommand } from './session-search.js';
22
- import { autoresearchCommand } from './autoresearch.js';
23
- import { MADMAX_FLAG, CODEX_BYPASS_FLAG, HIGH_REASONING_FLAG, XHIGH_REASONING_FLAG, SPARK_FLAG, MADMAX_SPARK_FLAG, CONFIG_FLAG, LONG_CONFIG_FLAG, } from './constants.js';
24
- import { getBaseStateDir, getStateDir, listModeStateFilesWithScopePreference, } from '../mcp/state-paths.js';
25
- import { maybeCheckAndPromptUpdate } from './update.js';
26
- import { maybePromptGithubStar } from './star-prompt.js';
27
- import { generateOverlay, removeSessionModelInstructionsFile, resolveSessionOrchestrationMode, sessionModelInstructionsPath, writeSessionModelInstructionsFile, } from '../hooks/agents-overlay.js';
28
- import { readSessionState, isSessionStale, writeSessionStart, writeSessionEnd, resetSessionMetrics, } from '../hooks/session.js';
29
- import { buildClientAttachedReconcileHookName, buildReconcileHudResizeArgs, buildRegisterClientAttachedReconcileArgs, buildRegisterResizeHookArgs, buildResizeHookName, buildResizeHookTarget, buildScheduleDelayedHudResizeArgs, buildUnregisterClientAttachedReconcileArgs, buildUnregisterResizeHookArgs, enableMouseScrolling, isNativeWindows, isTmuxAvailable, } from '../team/tmux-session.js';
30
- import { getPackageRoot } from '../utils/package.js';
31
- import { codexConfigPath } from '../utils/paths.js';
32
- import { repairConfigIfNeeded } from '../config/generator.js';
33
- import { HUD_TMUX_HEIGHT_LINES } from '../hud/constants.js';
34
- import { classifySpawnError, spawnPlatformCommandSync } from '../utils/platform-command.js';
35
- import { buildHookEvent } from '../hooks/extensibility/events.js';
36
- import { dispatchHookEvent } from '../hooks/extensibility/dispatcher.js';
37
- import { collectInheritableTeamWorkerArgs as collectInheritableTeamWorkerArgsShared, resolveTeamWorkerLaunchArgs, resolveTeamLowComplexityDefaultModel, } from '../team/model-contract.js';
38
- import { parseWorktreeMode, planWorktreeTarget, ensureWorktree, } from '../team/worktree.js';
39
- import { OMX_NOTIFY_TEMP_CONTRACT_ENV, parseNotifyTempContractFromArgs, serializeNotifyTempContract, } from '../notifications/temp-contract.js';
5
+ import { execFileSync, spawn } from "child_process";
6
+ import { basename, dirname, join } from "path";
7
+ import { existsSync, readFileSync } from "fs";
8
+ import { constants as osConstants } from "os";
9
+ import { setup, SETUP_SCOPES } from "./setup.js";
10
+ import { uninstall } from "./uninstall.js";
11
+ import { version } from "./version.js";
12
+ import { tmuxHookCommand } from "./tmux-hook.js";
13
+ import { hooksCommand } from "./hooks.js";
14
+ import { hudCommand } from "../hud/index.js";
15
+ import { teamCommand } from "./team.js";
16
+ import { ralphCommand } from "./ralph.js";
17
+ import { ralphthonCommand } from "./ralphthon.js";
18
+ import { askCommand } from "./ask.js";
19
+ import { cleanupCommand } from "./cleanup.js";
20
+ import { exploreCommand } from "./explore.js";
21
+ import { sparkshellCommand } from "./sparkshell.js";
22
+ import { agentsInitCommand } from "./agents-init.js";
23
+ import { agentsCommand } from "./agents.js";
24
+ import { sessionCommand } from "./session-search.js";
25
+ import { autoresearchCommand } from "./autoresearch.js";
26
+ import { MADMAX_FLAG, CODEX_BYPASS_FLAG, HIGH_REASONING_FLAG, XHIGH_REASONING_FLAG, SPARK_FLAG, MADMAX_SPARK_FLAG, CONFIG_FLAG, LONG_CONFIG_FLAG, } from "./constants.js";
27
+ import { getBaseStateDir, getStateDir, listModeStateFilesWithScopePreference, } from "../mcp/state-paths.js";
28
+ import { maybeCheckAndPromptUpdate } from "./update.js";
29
+ import { maybePromptGithubStar } from "./star-prompt.js";
30
+ import { generateOverlay, removeSessionModelInstructionsFile, resolveSessionOrchestrationMode, sessionModelInstructionsPath, writeSessionModelInstructionsFile, } from "../hooks/agents-overlay.js";
31
+ import { readModeState, updateModeState } from "../modes/base.js";
32
+ import { readSessionState, writeSessionStart, writeSessionEnd, resetSessionMetrics, } from "../hooks/session.js";
33
+ import { buildClientAttachedReconcileHookName, buildReconcileHudResizeArgs, buildRegisterClientAttachedReconcileArgs, buildRegisterResizeHookArgs, buildResizeHookName, buildResizeHookTarget, buildScheduleDelayedHudResizeArgs, buildUnregisterClientAttachedReconcileArgs, buildUnregisterResizeHookArgs, enableMouseScrolling, isNativeWindows, isTmuxAvailable, } from "../team/tmux-session.js";
34
+ import { getPackageRoot } from "../utils/package.js";
35
+ import { codexConfigPath } from "../utils/paths.js";
36
+ import { repairConfigIfNeeded } from "../config/generator.js";
37
+ import { HUD_TMUX_HEIGHT_LINES } from "../hud/constants.js";
38
+ import { classifySpawnError, spawnPlatformCommandSync, } from "../utils/platform-command.js";
39
+ import { buildHookEvent } from "../hooks/extensibility/events.js";
40
+ import { dispatchHookEvent } from "../hooks/extensibility/dispatcher.js";
41
+ import { collectInheritableTeamWorkerArgs as collectInheritableTeamWorkerArgsShared, resolveTeamWorkerLaunchArgs, resolveTeamLowComplexityDefaultModel, } from "../team/model-contract.js";
42
+ import { parseWorktreeMode, planWorktreeTarget, ensureWorktree, } from "../team/worktree.js";
43
+ import { OMX_NOTIFY_TEMP_CONTRACT_ENV, parseNotifyTempContractFromArgs, serializeNotifyTempContract, } from "../notifications/temp-contract.js";
40
44
  const HELP = `
41
45
  oh-my-codex (omx) - Multi-agent orchestration for Codex CLI
42
46
 
@@ -46,6 +50,7 @@ Usage:
46
50
  omx setup Install skills, prompts, MCP servers, and scope-specific AGENTS.md
47
51
  omx uninstall Remove OMX configuration and clean up installed artifacts
48
52
  omx doctor Check installation health
53
+ omx cleanup Kill orphaned OMX MCP server processes and remove stale OMX /tmp directories
49
54
  omx doctor --team Check team/swarm runtime health diagnostics
50
55
  omx ask Ask local provider CLI (claude|gemini) and write artifact output
51
56
  omx resume Resume a previous interactive Codex session
@@ -53,10 +58,12 @@ Usage:
53
58
  omx session Search prior local session transcripts and history artifacts
54
59
  omx agents-init [path]
55
60
  Bootstrap lightweight AGENTS.md files for a repo/subtree
61
+ omx agents Manage Codex native agent TOML files
56
62
  omx deepinit [path]
57
63
  Alias for agents-init (lightweight AGENTS bootstrap only)
58
64
  omx team Spawn parallel worker panes in tmux and bootstrap inbox/task state
59
65
  omx ralph Launch Codex with ralph persistence mode active
66
+ omx ralphthon Launch Codex with autonomous hackathon lifecycle mode active
60
67
  omx autoresearch Launch thin-supervisor autoresearch with keep/discard/reset parity
61
68
  omx version Show version information
62
69
  omx tmux-hook Manage tmux prompt injection workaround (init|status|validate|test)
@@ -100,39 +107,55 @@ Options:
100
107
  user | project
101
108
  --skill-target
102
109
  User-scope skills target for "omx setup" only:
103
- codex-home | agents
110
+ codex-home
104
111
  `;
105
- const REASONING_KEY = 'model_reasoning_effort';
106
- const MODEL_INSTRUCTIONS_FILE_KEY = 'model_instructions_file';
107
- const TEAM_WORKER_LAUNCH_ARGS_ENV = 'OMX_TEAM_WORKER_LAUNCH_ARGS';
108
- const TEAM_INHERIT_LEADER_FLAGS_ENV = 'OMX_TEAM_INHERIT_LEADER_FLAGS';
109
- const OMX_BYPASS_DEFAULT_SYSTEM_PROMPT_ENV = 'OMX_BYPASS_DEFAULT_SYSTEM_PROMPT';
110
- const OMX_MODEL_INSTRUCTIONS_FILE_ENV = 'OMX_MODEL_INSTRUCTIONS_FILE';
111
- const OMX_AUTORESEARCH_APPEND_INSTRUCTIONS_FILE_ENV = 'OMX_AUTORESEARCH_APPEND_INSTRUCTIONS_FILE';
112
- const REASONING_MODES = ['low', 'medium', 'high', 'xhigh'];
112
+ const REASONING_KEY = "model_reasoning_effort";
113
+ const MODEL_INSTRUCTIONS_FILE_KEY = "model_instructions_file";
114
+ const TEAM_WORKER_LAUNCH_ARGS_ENV = "OMX_TEAM_WORKER_LAUNCH_ARGS";
115
+ const TEAM_INHERIT_LEADER_FLAGS_ENV = "OMX_TEAM_INHERIT_LEADER_FLAGS";
116
+ const OMX_BYPASS_DEFAULT_SYSTEM_PROMPT_ENV = "OMX_BYPASS_DEFAULT_SYSTEM_PROMPT";
117
+ const OMX_MODEL_INSTRUCTIONS_FILE_ENV = "OMX_MODEL_INSTRUCTIONS_FILE";
118
+ const OMX_RALPH_APPEND_INSTRUCTIONS_FILE_ENV = "OMX_RALPH_APPEND_INSTRUCTIONS_FILE";
119
+ const OMX_AUTORESEARCH_APPEND_INSTRUCTIONS_FILE_ENV = "OMX_AUTORESEARCH_APPEND_INSTRUCTIONS_FILE";
120
+ const OMX_RALPHTHON_APPEND_INSTRUCTIONS_FILE_ENV = "OMX_RALPHTHON_APPEND_INSTRUCTIONS_FILE";
121
+ const REASONING_MODES = ["low", "medium", "high", "xhigh"];
113
122
  const REASONING_MODE_SET = new Set(REASONING_MODES);
114
- const REASONING_USAGE = 'Usage: omx reasoning <low|medium|high|xhigh>';
123
+ const REASONING_USAGE = "Usage: omx reasoning <low|medium|high|xhigh>";
115
124
  const ALLOWED_SHELLS = new Set([
116
- '/bin/sh', '/bin/bash', '/bin/zsh', '/bin/dash', '/bin/fish',
117
- '/usr/bin/sh', '/usr/bin/bash', '/usr/bin/zsh', '/usr/bin/dash', '/usr/bin/fish',
118
- '/usr/local/bin/bash', '/usr/local/bin/zsh', '/usr/local/bin/fish',
125
+ "/bin/sh",
126
+ "/bin/bash",
127
+ "/bin/zsh",
128
+ "/bin/dash",
129
+ "/bin/fish",
130
+ "/usr/bin/sh",
131
+ "/usr/bin/bash",
132
+ "/usr/bin/zsh",
133
+ "/usr/bin/dash",
134
+ "/usr/bin/fish",
135
+ "/usr/local/bin/bash",
136
+ "/usr/local/bin/zsh",
137
+ "/usr/local/bin/fish",
119
138
  ]);
120
139
  const WINDOWS_DETACHED_BOOTSTRAP_DELAY_MS = 2500;
121
- const CODEX_VERSION_FLAGS = new Set(['--version', '-V']);
140
+ const CODEX_VERSION_FLAGS = new Set(["--version", "-V"]);
122
141
  const NESTED_HELP_COMMANDS = new Set([
123
- 'ask',
124
- 'autoresearch',
125
- 'agents-init',
126
- 'deepinit',
127
- 'exec',
128
- 'hooks',
129
- 'hud',
130
- 'ralph',
131
- 'resume',
132
- 'session',
133
- 'sparkshell',
134
- 'team',
135
- 'tmux-hook',
142
+ "ask",
143
+ "cleanup",
144
+ "autoresearch",
145
+ "agents",
146
+ "agents-init",
147
+ "deepinit",
148
+ "exec",
149
+ "hooks",
150
+ "hud",
151
+ "ralph",
152
+ "ralphthon",
153
+ "ralphthon",
154
+ "resume",
155
+ "session",
156
+ "sparkshell",
157
+ "team",
158
+ "tmux-hook",
136
159
  ]);
137
160
  /**
138
161
  * Legacy scope values that may appear in persisted setup-scope.json files.
@@ -140,19 +163,19 @@ const NESTED_HELP_COMMANDS = new Set([
140
163
  * migrated to the current 'project' scope on read.
141
164
  */
142
165
  const LEGACY_SCOPE_MIGRATION_SYNC = {
143
- 'project-local': 'project',
166
+ "project-local": "project",
144
167
  };
145
168
  export function readPersistedSetupScope(cwd) {
146
169
  return readPersistedSetupPreferences(cwd)?.scope;
147
170
  }
148
171
  export function readPersistedSetupPreferences(cwd) {
149
- const scopePath = join(cwd, '.omx', 'setup-scope.json');
172
+ const scopePath = join(cwd, ".omx", "setup-scope.json");
150
173
  if (!existsSync(scopePath))
151
174
  return undefined;
152
175
  try {
153
- const parsed = JSON.parse(readFileSync(scopePath, 'utf-8'));
176
+ const parsed = JSON.parse(readFileSync(scopePath, "utf-8"));
154
177
  const persisted = {};
155
- if (typeof parsed.scope === 'string') {
178
+ if (typeof parsed.scope === "string") {
156
179
  if (SETUP_SCOPES.includes(parsed.scope)) {
157
180
  persisted.scope = parsed.scope;
158
181
  }
@@ -160,10 +183,6 @@ export function readPersistedSetupPreferences(cwd) {
160
183
  if (migrated)
161
184
  persisted.scope = migrated;
162
185
  }
163
- if (typeof parsed.skillTarget === 'string' &&
164
- SETUP_SKILL_TARGETS.includes(parsed.skillTarget)) {
165
- persisted.skillTarget = parsed.skillTarget;
166
- }
167
186
  return Object.keys(persisted).length > 0 ? persisted : undefined;
168
187
  }
169
188
  catch (err) {
@@ -173,11 +192,11 @@ export function readPersistedSetupPreferences(cwd) {
173
192
  return undefined;
174
193
  }
175
194
  export function resolveCodexHomeForLaunch(cwd, env = process.env) {
176
- if (env.CODEX_HOME && env.CODEX_HOME.trim() !== '')
195
+ if (env.CODEX_HOME && env.CODEX_HOME.trim() !== "")
177
196
  return env.CODEX_HOME;
178
197
  const persistedScope = readPersistedSetupScope(cwd);
179
- if (persistedScope === 'project') {
180
- return join(cwd, '.codex');
198
+ if (persistedScope === "project") {
199
+ return join(cwd, ".codex");
181
200
  }
182
201
  return undefined;
183
202
  }
@@ -185,17 +204,17 @@ export function resolveSetupScopeArg(args) {
185
204
  let value;
186
205
  for (let index = 0; index < args.length; index += 1) {
187
206
  const arg = args[index];
188
- if (arg === '--scope') {
207
+ if (arg === "--scope") {
189
208
  const next = args[index + 1];
190
- if (!next || next.startsWith('-')) {
191
- throw new Error(`Missing setup scope value after --scope. Expected one of: ${SETUP_SCOPES.join(', ')}`);
209
+ if (!next || next.startsWith("-")) {
210
+ throw new Error(`Missing setup scope value after --scope. Expected one of: ${SETUP_SCOPES.join(", ")}`);
192
211
  }
193
212
  value = next;
194
213
  index += 1;
195
214
  continue;
196
215
  }
197
- if (arg.startsWith('--scope=')) {
198
- value = arg.slice('--scope='.length);
216
+ if (arg.startsWith("--scope=")) {
217
+ value = arg.slice("--scope=".length);
199
218
  }
200
219
  }
201
220
  if (!value)
@@ -203,51 +222,27 @@ export function resolveSetupScopeArg(args) {
203
222
  if (SETUP_SCOPES.includes(value)) {
204
223
  return value;
205
224
  }
206
- throw new Error(`Invalid setup scope: ${value}. Expected one of: ${SETUP_SCOPES.join(', ')}`);
207
- }
208
- export function resolveSetupSkillTargetArg(args) {
209
- let value;
210
- for (let index = 0; index < args.length; index += 1) {
211
- const arg = args[index];
212
- if (arg === '--skill-target') {
213
- const next = args[index + 1];
214
- if (!next || next.startsWith('-')) {
215
- throw new Error(`Missing setup skill target value after --skill-target. Expected one of: ${SETUP_SKILL_TARGETS.join(', ')}`);
216
- }
217
- value = next;
218
- index += 1;
219
- continue;
220
- }
221
- if (arg.startsWith('--skill-target=')) {
222
- value = arg.slice('--skill-target='.length);
223
- }
224
- }
225
- if (!value)
226
- return undefined;
227
- if (SETUP_SKILL_TARGETS.includes(value)) {
228
- return value;
229
- }
230
- throw new Error(`Invalid setup skill target: ${value}. Expected one of: ${SETUP_SKILL_TARGETS.join(', ')}`);
225
+ throw new Error(`Invalid setup scope: ${value}. Expected one of: ${SETUP_SCOPES.join(", ")}`);
231
226
  }
232
227
  export function resolveCliInvocation(args) {
233
228
  const firstArg = args[0];
234
- if (firstArg === '--help' || firstArg === '-h') {
235
- return { command: 'help', launchArgs: [] };
229
+ if (firstArg === "--help" || firstArg === "-h") {
230
+ return { command: "help", launchArgs: [] };
236
231
  }
237
- if (firstArg === '--version' || firstArg === '-v') {
238
- return { command: 'version', launchArgs: [] };
232
+ if (firstArg === "--version" || firstArg === "-v") {
233
+ return { command: "version", launchArgs: [] };
239
234
  }
240
- if (!firstArg || firstArg.startsWith('--')) {
241
- return { command: 'launch', launchArgs: firstArg ? args : [] };
235
+ if (!firstArg || firstArg.startsWith("--")) {
236
+ return { command: "launch", launchArgs: firstArg ? args : [] };
242
237
  }
243
- if (firstArg === 'launch') {
244
- return { command: 'launch', launchArgs: args.slice(1) };
238
+ if (firstArg === "launch") {
239
+ return { command: "launch", launchArgs: args.slice(1) };
245
240
  }
246
- if (firstArg === 'exec') {
247
- return { command: 'exec', launchArgs: args.slice(1) };
241
+ if (firstArg === "exec") {
242
+ return { command: "exec", launchArgs: args.slice(1) };
248
243
  }
249
- if (firstArg === 'resume') {
250
- return { command: 'resume', launchArgs: args.slice(1) };
244
+ if (firstArg === "resume") {
245
+ return { command: "resume", launchArgs: args.slice(1) };
251
246
  }
252
247
  return { command: firstArg, launchArgs: [] };
253
248
  }
@@ -259,65 +254,70 @@ export function commandOwnsLocalHelp(command) {
259
254
  }
260
255
  export function resolveCodexLaunchPolicy(env = process.env, _platform = process.platform, tmuxAvailable = isTmuxAvailable()) {
261
256
  if (env.TMUX)
262
- return 'inside-tmux';
263
- return tmuxAvailable ? 'detached-tmux' : 'direct';
257
+ return "inside-tmux";
258
+ return tmuxAvailable ? "detached-tmux" : "direct";
264
259
  }
265
260
  function hasErrnoCode(error, code) {
266
- return Boolean(error && typeof error === 'object' && 'code' in error && error.code === code);
261
+ return Boolean(error &&
262
+ typeof error === "object" &&
263
+ "code" in error &&
264
+ error.code === code);
267
265
  }
268
266
  export function resolveSignalExitCode(signal) {
269
267
  if (!signal)
270
268
  return 1;
271
269
  const signalNumber = osConstants.signals[signal];
272
- if (typeof signalNumber === 'number' && Number.isFinite(signalNumber)) {
270
+ if (typeof signalNumber === "number" && Number.isFinite(signalNumber)) {
273
271
  return 128 + signalNumber;
274
272
  }
275
273
  return 1;
276
274
  }
277
275
  export function classifyCodexExecFailure(error) {
278
- if (!error || typeof error !== 'object') {
276
+ if (!error || typeof error !== "object") {
279
277
  return {
280
- kind: 'launch-error',
278
+ kind: "launch-error",
281
279
  message: String(error),
282
280
  };
283
281
  }
284
282
  const err = error;
285
- const code = typeof err.code === 'string' ? err.code : undefined;
286
- const message = typeof err.message === 'string' && err.message.length > 0
283
+ const code = typeof err.code === "string" ? err.code : undefined;
284
+ const message = typeof err.message === "string" && err.message.length > 0
287
285
  ? err.message
288
- : 'unknown codex launch failure';
289
- const hasExitStatus = typeof err.status === 'number';
290
- const hasSignal = typeof err.signal === 'string' && err.signal.length > 0;
286
+ : "unknown codex launch failure";
287
+ const hasExitStatus = typeof err.status === "number";
288
+ const hasSignal = typeof err.signal === "string" && err.signal.length > 0;
291
289
  if (hasExitStatus || hasSignal) {
292
290
  return {
293
- kind: 'exit',
291
+ kind: "exit",
294
292
  code,
295
293
  message,
296
- exitCode: hasExitStatus ? err.status : resolveSignalExitCode(err.signal),
294
+ exitCode: hasExitStatus
295
+ ? err.status
296
+ : resolveSignalExitCode(err.signal),
297
297
  signal: hasSignal ? err.signal : undefined,
298
298
  };
299
299
  }
300
300
  return {
301
- kind: 'launch-error',
301
+ kind: "launch-error",
302
302
  code,
303
303
  message,
304
304
  };
305
305
  }
306
306
  function runCodexBlocking(cwd, launchArgs, codexEnv) {
307
- const { result } = spawnPlatformCommandSync('codex', launchArgs, {
307
+ const { result } = spawnPlatformCommandSync("codex", launchArgs, {
308
308
  cwd,
309
- stdio: 'inherit',
309
+ stdio: "inherit",
310
310
  env: codexEnv,
311
- encoding: 'utf-8',
311
+ encoding: "utf-8",
312
312
  });
313
313
  if (result.error) {
314
314
  const errno = result.error;
315
315
  const kind = classifySpawnError(errno);
316
- if (kind === 'missing') {
317
- console.error('[omx] failed to launch codex: executable not found in PATH');
316
+ if (kind === "missing") {
317
+ console.error("[omx] failed to launch codex: executable not found in PATH");
318
318
  }
319
- else if (kind === 'blocked') {
320
- console.error(`[omx] failed to launch codex: executable is present but blocked in the current environment (${errno.code || 'blocked'})`);
319
+ else if (kind === "blocked") {
320
+ console.error(`[omx] failed to launch codex: executable is present but blocked in the current environment (${errno.code || "blocked"})`);
321
321
  }
322
322
  else {
323
323
  console.error(`[omx] failed to launch codex: ${errno.message}`);
@@ -325,9 +325,10 @@ function runCodexBlocking(cwd, launchArgs, codexEnv) {
325
325
  throw result.error;
326
326
  }
327
327
  if (result.status !== 0) {
328
- process.exitCode = typeof result.status === 'number'
329
- ? result.status
330
- : resolveSignalExitCode(result.signal);
328
+ process.exitCode =
329
+ typeof result.status === "number"
330
+ ? result.status
331
+ : resolveSignalExitCode(result.signal);
331
332
  if (result.signal) {
332
333
  console.error(`[omx] codex exited due to signal ${result.signal}`);
333
334
  }
@@ -335,24 +336,24 @@ function runCodexBlocking(cwd, launchArgs, codexEnv) {
335
336
  }
336
337
  export function parseTmuxPaneSnapshot(output) {
337
338
  return output
338
- .split('\n')
339
+ .split("\n")
339
340
  .map((line) => line.trim())
340
341
  .filter(Boolean)
341
342
  .map((line) => {
342
- const [paneId = '', currentCommand = '', ...startCommandParts] = line.split('\t');
343
+ const [paneId = "", currentCommand = "", ...startCommandParts] = line.split("\t");
343
344
  return {
344
345
  paneId: paneId.trim(),
345
346
  currentCommand: currentCommand.trim(),
346
- startCommand: startCommandParts.join('\t').trim(),
347
+ startCommand: startCommandParts.join("\t").trim(),
347
348
  };
348
349
  })
349
- .filter((pane) => pane.paneId.startsWith('%'));
350
+ .filter((pane) => pane.paneId.startsWith("%"));
350
351
  }
351
352
  export function isHudWatchPane(pane) {
352
353
  const command = `${pane.startCommand} ${pane.currentCommand}`.toLowerCase();
353
- return /\bhud\b/.test(command)
354
- && /--watch\b/.test(command)
355
- && (/\bomx(?:\.js)?\b/.test(command) || /\bnode\b/.test(command));
354
+ return (/\bhud\b/.test(command) &&
355
+ /--watch\b/.test(command) &&
356
+ (/\bomx(?:\.js)?\b/.test(command) || /\bnode\b/.test(command)));
356
357
  }
357
358
  export function findHudWatchPaneIds(panes, currentPaneId) {
358
359
  return panes
@@ -361,122 +362,158 @@ export function findHudWatchPaneIds(panes, currentPaneId) {
361
362
  .map((pane) => pane.paneId);
362
363
  }
363
364
  export function buildHudPaneCleanupTargets(existingPaneIds, createdPaneId, leaderPaneId) {
364
- const targets = new Set(existingPaneIds.filter((id) => id.startsWith('%')));
365
- if (createdPaneId && createdPaneId.startsWith('%')) {
365
+ const targets = new Set(existingPaneIds.filter((id) => id.startsWith("%")));
366
+ if (createdPaneId && createdPaneId.startsWith("%")) {
366
367
  targets.add(createdPaneId);
367
368
  }
368
369
  // Guard: never kill the leader's own pane under any circumstances.
369
- if (leaderPaneId && leaderPaneId.startsWith('%')) {
370
+ if (leaderPaneId && leaderPaneId.startsWith("%")) {
370
371
  targets.delete(leaderPaneId);
371
372
  }
372
373
  return [...targets];
373
374
  }
374
375
  export async function main(args) {
375
376
  const knownCommands = new Set([
376
- 'launch', 'exec', 'setup', 'agents-init', 'deepinit', 'uninstall', 'doctor', 'ask', 'autoresearch', 'explore', 'sparkshell', 'team', 'ralph', 'session', 'resume', 'version', 'tmux-hook', 'hooks', 'hud', 'status', 'cancel', 'help', '--help', '-h',
377
+ "launch",
378
+ "exec",
379
+ "setup",
380
+ "agents",
381
+ "agents-init",
382
+ "deepinit",
383
+ "uninstall",
384
+ "doctor",
385
+ "cleanup",
386
+ "ask",
387
+ "autoresearch",
388
+ "explore",
389
+ "sparkshell",
390
+ "team",
391
+ "ralph",
392
+ "ralphthon",
393
+ "session",
394
+ "resume",
395
+ "version",
396
+ "tmux-hook",
397
+ "hooks",
398
+ "hud",
399
+ "status",
400
+ "cancel",
401
+ "help",
402
+ "--help",
403
+ "-h",
377
404
  ]);
378
405
  const firstArg = args[0];
379
406
  const { command, launchArgs } = resolveCliInvocation(args);
380
- const flags = new Set(args.filter(a => a.startsWith('--')));
407
+ const flags = new Set(args.filter((a) => a.startsWith("--")));
381
408
  const options = {
382
- force: flags.has('--force'),
383
- dryRun: flags.has('--dry-run'),
384
- verbose: flags.has('--verbose'),
385
- team: flags.has('--team'),
409
+ force: flags.has("--force"),
410
+ dryRun: flags.has("--dry-run"),
411
+ verbose: flags.has("--verbose"),
412
+ team: flags.has("--team"),
386
413
  };
387
- if (flags.has('--help') && !commandOwnsLocalHelp(command)) {
414
+ if (flags.has("--help") && !commandOwnsLocalHelp(command)) {
388
415
  console.log(HELP);
389
416
  return;
390
417
  }
391
418
  try {
392
419
  switch (command) {
393
- case 'launch':
420
+ case "launch":
394
421
  await launchWithHud(launchArgs);
395
422
  break;
396
- case 'resume':
397
- await launchWithHud(['resume', ...launchArgs]);
423
+ case "resume":
424
+ await launchWithHud(["resume", ...launchArgs]);
398
425
  break;
399
- case 'setup':
426
+ case "setup":
400
427
  await setup({
401
428
  force: options.force,
402
429
  dryRun: options.dryRun,
403
430
  verbose: options.verbose,
404
431
  scope: resolveSetupScopeArg(args.slice(1)),
405
- skillTarget: resolveSetupSkillTargetArg(args.slice(1)),
406
432
  });
407
433
  break;
408
- case 'agents-init':
434
+ case "agents":
435
+ await agentsCommand(args.slice(1));
436
+ break;
437
+ case "agents-init":
409
438
  await agentsInitCommand(args.slice(1));
410
439
  break;
411
- case 'deepinit':
440
+ case "deepinit":
412
441
  await agentsInitCommand(args.slice(1));
413
442
  break;
414
- case 'uninstall':
443
+ case "uninstall":
415
444
  await uninstall({
416
445
  dryRun: options.dryRun,
417
- keepConfig: flags.has('--keep-config'),
446
+ keepConfig: flags.has("--keep-config"),
418
447
  verbose: options.verbose,
419
- purge: flags.has('--purge'),
448
+ purge: flags.has("--purge"),
420
449
  scope: resolveSetupScopeArg(args.slice(1)),
421
450
  });
422
451
  break;
423
- case 'doctor': {
424
- const { doctor } = await import('./doctor.js');
452
+ case "doctor": {
453
+ const { doctor } = await import("./doctor.js");
425
454
  await doctor(options);
426
455
  break;
427
456
  }
428
- case 'ask':
457
+ case "ask":
429
458
  await askCommand(args.slice(1));
430
459
  break;
431
- case 'autoresearch':
460
+ case "cleanup":
461
+ await cleanupCommand(args.slice(1));
462
+ break;
463
+ case "autoresearch":
432
464
  await autoresearchCommand(args.slice(1));
433
465
  break;
434
- case 'explore':
466
+ case "explore":
435
467
  await exploreCommand(args.slice(1));
436
468
  break;
437
- case 'exec':
469
+ case "exec":
438
470
  await execWithOverlay(launchArgs);
439
471
  break;
440
- case 'sparkshell':
472
+ case "sparkshell":
441
473
  await sparkshellCommand(args.slice(1));
442
474
  break;
443
- case 'team':
475
+ case "team":
444
476
  await teamCommand(args.slice(1), options);
445
477
  break;
446
- case 'session':
478
+ case "session":
447
479
  await sessionCommand(args.slice(1));
448
480
  break;
449
- case 'ralph':
481
+ case "ralph":
450
482
  await ralphCommand(args.slice(1));
451
483
  break;
452
- case 'version':
484
+ case "ralphthon":
485
+ await ralphthonCommand(args.slice(1));
486
+ break;
487
+ case "version":
453
488
  version();
454
489
  break;
455
- case 'hud':
490
+ case "hud":
456
491
  await hudCommand(args.slice(1));
457
492
  break;
458
- case 'tmux-hook':
493
+ case "tmux-hook":
459
494
  await tmuxHookCommand(args.slice(1));
460
495
  break;
461
- case 'hooks':
496
+ case "hooks":
462
497
  await hooksCommand(args.slice(1));
463
498
  break;
464
- case 'status':
499
+ case "status":
465
500
  await showStatus();
466
501
  break;
467
- case 'cancel':
502
+ case "cancel":
468
503
  await cancelModes();
469
504
  break;
470
- case 'reasoning':
505
+ case "reasoning":
471
506
  await reasoningCommand(args.slice(1));
472
507
  break;
473
- case 'help':
474
- case '--help':
475
- case '-h':
508
+ case "help":
509
+ case "--help":
510
+ case "-h":
476
511
  console.log(HELP);
477
512
  break;
478
513
  default:
479
- if (firstArg && firstArg.startsWith('-') && !knownCommands.has(firstArg)) {
514
+ if (firstArg &&
515
+ firstArg.startsWith("-") &&
516
+ !knownCommands.has(firstArg)) {
480
517
  await launchWithHud(args);
481
518
  break;
482
519
  }
@@ -491,17 +528,17 @@ export async function main(args) {
491
528
  }
492
529
  }
493
530
  async function showStatus() {
494
- const { readFile } = await import('fs/promises');
531
+ const { readFile } = await import("fs/promises");
495
532
  const cwd = process.cwd();
496
533
  try {
497
534
  const refs = await listModeStateFilesWithScopePreference(cwd);
498
535
  const states = refs.map((ref) => ref.path);
499
536
  if (states.length === 0) {
500
- console.log('No active modes.');
537
+ console.log("No active modes.");
501
538
  return;
502
539
  }
503
540
  for (const path of states) {
504
- const content = await readFile(path, 'utf-8');
541
+ const content = await readFile(path, "utf-8");
505
542
  let state;
506
543
  try {
507
544
  state = JSON.parse(content);
@@ -511,13 +548,13 @@ async function showStatus() {
511
548
  continue;
512
549
  }
513
550
  const file = basename(path);
514
- const mode = file.replace('-state.json', '');
515
- console.log(`${mode}: ${state.active === true ? 'ACTIVE' : 'inactive'} (phase: ${String(state.current_phase || 'n/a')})`);
551
+ const mode = file.replace("-state.json", "");
552
+ console.log(`${mode}: ${state.active === true ? "ACTIVE" : "inactive"} (phase: ${String(state.current_phase || "n/a")})`);
516
553
  }
517
554
  }
518
555
  catch (err) {
519
556
  process.stderr.write(`[cli/index] operation failed: ${err}\n`);
520
- console.log('No active modes.');
557
+ console.log("No active modes.");
521
558
  }
522
559
  }
523
560
  async function reasoningCommand(args) {
@@ -529,8 +566,8 @@ async function reasoningCommand(args) {
529
566
  console.log(REASONING_USAGE);
530
567
  return;
531
568
  }
532
- const { readFile } = await import('fs/promises');
533
- const content = await readFile(configPath, 'utf-8');
569
+ const { readFile } = await import("fs/promises");
570
+ const content = await readFile(configPath, "utf-8");
534
571
  const current = readTopLevelTomlString(content, REASONING_KEY);
535
572
  if (current) {
536
573
  console.log(`Current ${REASONING_KEY}: ${current}`);
@@ -541,37 +578,39 @@ async function reasoningCommand(args) {
541
578
  return;
542
579
  }
543
580
  if (!REASONING_MODE_SET.has(mode)) {
544
- throw new Error(`Invalid reasoning mode "${mode}". Expected one of: ${REASONING_MODES.join(', ')}.\n${REASONING_USAGE}`);
581
+ throw new Error(`Invalid reasoning mode "${mode}". Expected one of: ${REASONING_MODES.join(", ")}.\n${REASONING_USAGE}`);
545
582
  }
546
- const { mkdir, readFile, writeFile } = await import('fs/promises');
583
+ const { mkdir, readFile, writeFile } = await import("fs/promises");
547
584
  await mkdir(dirname(configPath), { recursive: true });
548
- const existing = existsSync(configPath) ? await readFile(configPath, 'utf-8') : '';
585
+ const existing = existsSync(configPath)
586
+ ? await readFile(configPath, "utf-8")
587
+ : "";
549
588
  const updated = upsertTopLevelTomlString(existing, REASONING_KEY, mode);
550
589
  await writeFile(configPath, updated);
551
590
  console.log(`Set ${REASONING_KEY}="${mode}" in ${configPath}`);
552
591
  }
553
592
  export async function launchWithHud(args) {
554
593
  if (isNativeWindows()) {
555
- const { result } = spawnPlatformCommandSync('tmux', ['-V'], {
556
- encoding: 'utf-8',
557
- stdio: ['pipe', 'pipe', 'pipe'],
594
+ const { result } = spawnPlatformCommandSync("tmux", ["-V"], {
595
+ encoding: "utf-8",
596
+ stdio: ["pipe", "pipe", "pipe"],
558
597
  });
559
598
  if (result.error) {
560
599
  const errno = result.error;
561
600
  const kind = classifySpawnError(errno);
562
- if (kind === 'missing') {
563
- console.warn('[omx] warning: tmux was not found on native Windows. Continuing without tmux/HUD.\n' +
564
- '[omx] To enable tmux-backed features, install psmux:\n' +
565
- '[omx] winget install psmux\n' +
566
- '[omx] See: https://github.com/marlocarlo/psmux');
601
+ if (kind === "missing") {
602
+ console.warn("[omx] warning: tmux was not found on native Windows. Continuing without tmux/HUD.\n" +
603
+ "[omx] To enable tmux-backed features, install psmux:\n" +
604
+ "[omx] winget install psmux\n" +
605
+ "[omx] See: https://github.com/marlocarlo/psmux");
567
606
  }
568
607
  else {
569
608
  console.warn(`[omx] warning: tmux probe failed on native Windows (${errno.code || errno.message}). Continuing without tmux/HUD.`);
570
609
  }
571
610
  }
572
611
  else if (result.status !== 0 && !isTmuxAvailable()) {
573
- const stderr = (result.stderr || '').trim();
574
- console.warn(`[omx] warning: tmux reported an error on native Windows${stderr ? ` (${stderr})` : ''}. Continuing without tmux/HUD.`);
612
+ const stderr = (result.stderr || "").trim();
613
+ console.warn(`[omx] warning: tmux reported an error on native Windows${stderr ? ` (${stderr})` : ""}. Continuing without tmux/HUD.`);
575
614
  }
576
615
  }
577
616
  const launchCwd = process.cwd();
@@ -584,7 +623,7 @@ export async function launchWithHud(args) {
584
623
  if (parsedWorktree.mode.enabled) {
585
624
  const planned = planWorktreeTarget({
586
625
  cwd: launchCwd,
587
- scope: 'launch',
626
+ scope: "launch",
588
627
  mode: parsedWorktree.mode,
589
628
  });
590
629
  const ensured = ensureWorktree(planned);
@@ -614,7 +653,7 @@ export async function launchWithHud(args) {
614
653
  try {
615
654
  const repaired = await repairConfigIfNeeded(codexConfigPath(), getPackageRoot());
616
655
  if (repaired) {
617
- console.log('[omx] Repaired duplicate [tui] section in config.toml.');
656
+ console.log("[omx] Repaired duplicate [tui] section in config.toml.");
618
657
  }
619
658
  }
620
659
  catch {
@@ -650,7 +689,7 @@ export async function execWithOverlay(args) {
650
689
  if (parsedWorktree.mode.enabled) {
651
690
  const planned = planWorktreeTarget({
652
691
  cwd: launchCwd,
653
- scope: 'launch',
692
+ scope: "launch",
654
693
  mode: parsedWorktree.mode,
655
694
  });
656
695
  const ensured = ensureWorktree(planned);
@@ -674,7 +713,7 @@ export async function execWithOverlay(args) {
674
713
  try {
675
714
  const repaired = await repairConfigIfNeeded(codexConfigPath(), getPackageRoot());
676
715
  if (repaired) {
677
- console.log('[omx] Repaired duplicate [tui] section in config.toml.');
716
+ console.log("[omx] Repaired duplicate [tui] section in config.toml.");
678
717
  }
679
718
  }
680
719
  catch {
@@ -690,12 +729,15 @@ export async function execWithOverlay(args) {
690
729
  const notifyTempContractRaw = notifyTempResult.contract.active
691
730
  ? serializeNotifyTempContract(notifyTempResult.contract)
692
731
  : null;
693
- const codexArgs = injectModelInstructionsBypassArgs(cwd, ['exec', ...normalizedArgs], process.env, sessionModelInstructionsPath(cwd, sessionId));
732
+ const codexArgs = injectModelInstructionsBypassArgs(cwd, ["exec", ...normalizedArgs], process.env, sessionModelInstructionsPath(cwd, sessionId));
694
733
  const codexEnvBase = codexHomeOverride
695
734
  ? { ...process.env, CODEX_HOME: codexHomeOverride }
696
735
  : process.env;
697
736
  const codexEnv = notifyTempContractRaw
698
- ? { ...codexEnvBase, [OMX_NOTIFY_TEMP_CONTRACT_ENV]: notifyTempContractRaw }
737
+ ? {
738
+ ...codexEnvBase,
739
+ [OMX_NOTIFY_TEMP_CONTRACT_ENV]: notifyTempContractRaw,
740
+ }
699
741
  : codexEnvBase;
700
742
  runCodexBlocking(cwd, codexArgs, codexEnv);
701
743
  }
@@ -723,11 +765,11 @@ export function normalizeCodexLaunchArgs(args) {
723
765
  continue;
724
766
  }
725
767
  if (arg === HIGH_REASONING_FLAG) {
726
- reasoningMode = 'high';
768
+ reasoningMode = "high";
727
769
  continue;
728
770
  }
729
771
  if (arg === XHIGH_REASONING_FLAG) {
730
- reasoningMode = 'xhigh';
772
+ reasoningMode = "xhigh";
731
773
  continue;
732
774
  }
733
775
  if (arg === SPARK_FLAG) {
@@ -770,7 +812,8 @@ function hasModelInstructionsOverride(args) {
770
812
  const arg = args[i];
771
813
  if (arg === CONFIG_FLAG || arg === LONG_CONFIG_FLAG) {
772
814
  const maybeValue = args[i + 1];
773
- if (typeof maybeValue === 'string' && isModelInstructionsOverride(maybeValue)) {
815
+ if (typeof maybeValue === "string" &&
816
+ isModelInstructionsOverride(maybeValue)) {
774
817
  return true;
775
818
  }
776
819
  continue;
@@ -784,18 +827,20 @@ function hasModelInstructionsOverride(args) {
784
827
  return false;
785
828
  }
786
829
  function shouldBypassDefaultSystemPrompt(env) {
787
- return env[OMX_BYPASS_DEFAULT_SYSTEM_PROMPT_ENV] !== '0';
830
+ return env[OMX_BYPASS_DEFAULT_SYSTEM_PROMPT_ENV] !== "0";
788
831
  }
789
832
  function buildModelInstructionsOverride(cwd, env, defaultFilePath) {
790
- const filePath = env[OMX_MODEL_INSTRUCTIONS_FILE_ENV] || defaultFilePath || join(cwd, 'AGENTS.md');
833
+ const filePath = env[OMX_MODEL_INSTRUCTIONS_FILE_ENV] ||
834
+ defaultFilePath ||
835
+ join(cwd, "AGENTS.md");
791
836
  return `${MODEL_INSTRUCTIONS_FILE_KEY}="${escapeTomlString(filePath)}"`;
792
837
  }
793
838
  function tryReadGitValue(cwd, args) {
794
839
  try {
795
- const value = execFileSync('git', args, {
840
+ const value = execFileSync("git", args, {
796
841
  cwd,
797
- encoding: 'utf-8',
798
- stdio: ['ignore', 'pipe', 'ignore'],
842
+ encoding: "utf-8",
843
+ stdio: ["ignore", "pipe", "ignore"],
799
844
  timeout: 2000,
800
845
  }).trim();
801
846
  return value || undefined;
@@ -814,9 +859,9 @@ function extractIssueNumber(text) {
814
859
  function resolveNativeSessionName(cwd, sessionId) {
815
860
  if (process.env.TMUX) {
816
861
  try {
817
- const tmuxSession = execFileSync('tmux', ['display-message', '-p', '#S'], {
818
- encoding: 'utf-8',
819
- stdio: ['ignore', 'pipe', 'ignore'],
862
+ const tmuxSession = execFileSync("tmux", ["display-message", "-p", "#S"], {
863
+ encoding: "utf-8",
864
+ stdio: ["ignore", "pipe", "ignore"],
820
865
  timeout: 2000,
821
866
  }).trim();
822
867
  if (tmuxSession)
@@ -829,9 +874,9 @@ function resolveNativeSessionName(cwd, sessionId) {
829
874
  return buildTmuxSessionName(cwd, sessionId);
830
875
  }
831
876
  function buildNativeHookBaseContext(cwd, sessionId, normalizedEvent, extra = {}) {
832
- const repoPath = tryReadGitValue(cwd, ['rev-parse', '--show-toplevel']) || cwd;
833
- const branch = tryReadGitValue(cwd, ['rev-parse', '--abbrev-ref', 'HEAD']);
834
- const issueNumber = extractIssueNumber([branch, basename(cwd)].filter(Boolean).join(' '));
877
+ const repoPath = tryReadGitValue(cwd, ["rev-parse", "--show-toplevel"]) || cwd;
878
+ const branch = tryReadGitValue(cwd, ["rev-parse", "--abbrev-ref", "HEAD"]);
879
+ const issueNumber = extractIssueNumber([branch, basename(cwd)].filter(Boolean).join(" "));
835
880
  return {
836
881
  normalized_event: normalizedEvent,
837
882
  session_name: resolveNativeSessionName(cwd, sessionId),
@@ -848,13 +893,19 @@ export function injectModelInstructionsBypassArgs(cwd, args, env = process.env,
848
893
  return [...args];
849
894
  if (hasModelInstructionsOverride(args))
850
895
  return [...args];
851
- return [...args, CONFIG_FLAG, buildModelInstructionsOverride(cwd, env, defaultFilePath)];
896
+ return [
897
+ ...args,
898
+ CONFIG_FLAG,
899
+ buildModelInstructionsOverride(cwd, env, defaultFilePath),
900
+ ];
852
901
  }
853
902
  export function collectInheritableTeamWorkerArgs(codexArgs) {
854
903
  return collectInheritableTeamWorkerArgsShared(codexArgs);
855
904
  }
856
905
  export function resolveTeamWorkerLaunchArgsEnv(existingRaw, codexArgs, inheritLeaderFlags = true, defaultModel) {
857
- const inheritedArgs = inheritLeaderFlags ? collectInheritableTeamWorkerArgs(codexArgs) : [];
906
+ const inheritedArgs = inheritLeaderFlags
907
+ ? collectInheritableTeamWorkerArgs(codexArgs)
908
+ : [];
858
909
  const normalized = resolveTeamWorkerLaunchArgs({
859
910
  existingRaw,
860
911
  inheritedArgs,
@@ -862,14 +913,14 @@ export function resolveTeamWorkerLaunchArgsEnv(existingRaw, codexArgs, inheritLe
862
913
  });
863
914
  if (normalized.length === 0)
864
915
  return null;
865
- return normalized.join(' ');
916
+ return normalized.join(" ");
866
917
  }
867
918
  export function readTopLevelTomlString(content, key) {
868
919
  let inTopLevel = true;
869
920
  const lines = content.split(/\r?\n/);
870
921
  for (const line of lines) {
871
922
  const trimmed = line.trim();
872
- if (!trimmed || trimmed.startsWith('#'))
923
+ if (!trimmed || trimmed.startsWith("#"))
873
924
  continue;
874
925
  if (/^\[[^[\]]+\]\s*(#.*)?$/.test(trimmed)) {
875
926
  inTopLevel = false;
@@ -885,7 +936,7 @@ export function readTopLevelTomlString(content, key) {
885
936
  return null;
886
937
  }
887
938
  export function upsertTopLevelTomlString(content, key, value) {
888
- const eol = content.includes('\r\n') ? '\r\n' : '\n';
939
+ const eol = content.includes("\r\n") ? "\r\n" : "\n";
889
940
  const assignment = `${key} = "${escapeTomlString(value)}"`;
890
941
  if (!content.trim()) {
891
942
  return assignment + eol;
@@ -896,7 +947,7 @@ export function upsertTopLevelTomlString(content, key, value) {
896
947
  for (let i = 0; i < lines.length; i++) {
897
948
  const line = lines[i];
898
949
  const trimmed = line.trim();
899
- if (!trimmed || trimmed.startsWith('#'))
950
+ if (!trimmed || trimmed.startsWith("#"))
900
951
  continue;
901
952
  if (/^\[[^[\]]+\]\s*(#.*)?$/.test(trimmed)) {
902
953
  inTopLevel = false;
@@ -912,7 +963,7 @@ export function upsertTopLevelTomlString(content, key, value) {
912
963
  }
913
964
  }
914
965
  if (!replaced) {
915
- const firstTableIndex = lines.findIndex(line => /^\s*\[[^[\]]+\]\s*(#.*)?$/.test(line.trim()));
966
+ const firstTableIndex = lines.findIndex((line) => /^\s*\[[^[\]]+\]\s*(#.*)?$/.test(line.trim()));
916
967
  if (firstTableIndex >= 0) {
917
968
  lines.splice(firstTableIndex, 0, assignment);
918
969
  }
@@ -930,43 +981,54 @@ function parseTomlStringValue(value) {
930
981
  if (trimmed.startsWith('"') && trimmed.endsWith('"') && trimmed.length >= 2) {
931
982
  return trimmed.slice(1, -1);
932
983
  }
933
- if (trimmed.startsWith('\'') && trimmed.endsWith('\'') && trimmed.length >= 2) {
984
+ if (trimmed.startsWith("'") && trimmed.endsWith("'") && trimmed.length >= 2) {
934
985
  return trimmed.slice(1, -1);
935
986
  }
936
987
  return trimmed;
937
988
  }
938
989
  function escapeTomlString(value) {
939
- return value.replace(/\\/g, '\\\\').replace(/"/g, '\\"');
990
+ return value.replace(/\\/g, "\\\\").replace(/"/g, '\\"');
940
991
  }
941
992
  function sanitizeTmuxToken(value) {
942
- const cleaned = value.toLowerCase().replace(/[^a-z0-9]+/g, '-').replace(/^-+|-+$/g, '');
943
- return cleaned || 'unknown';
993
+ const cleaned = value
994
+ .toLowerCase()
995
+ .replace(/[^a-z0-9]+/g, "-")
996
+ .replace(/^-+|-+$/g, "");
997
+ return cleaned || "unknown";
944
998
  }
945
999
  export function buildTmuxSessionName(cwd, sessionId) {
946
- const parentDir = basename(dirname(cwd));
1000
+ const parentPath = dirname(cwd);
1001
+ const parentDir = basename(parentPath);
947
1002
  const dirName = basename(cwd);
948
- const dirToken = parentDir.endsWith('.omx-worktrees')
949
- ? sanitizeTmuxToken(`${parentDir.slice(0, -'.omx-worktrees'.length)}-${dirName}`)
1003
+ const grandparentPath = dirname(parentPath);
1004
+ const grandparentDir = basename(grandparentPath);
1005
+ const repoDir = parentDir.endsWith(".omx-worktrees")
1006
+ ? parentDir.slice(0, -".omx-worktrees".length)
1007
+ : parentDir === "worktrees" && grandparentDir === ".omx"
1008
+ ? basename(dirname(grandparentPath))
1009
+ : null;
1010
+ const dirToken = repoDir
1011
+ ? sanitizeTmuxToken(`${repoDir}-${dirName}`)
950
1012
  : sanitizeTmuxToken(dirName);
951
- let branchToken = 'detached';
952
- const branch = tryReadGitValue(cwd, ['rev-parse', '--abbrev-ref', 'HEAD']);
1013
+ let branchToken = "detached";
1014
+ const branch = tryReadGitValue(cwd, ["rev-parse", "--abbrev-ref", "HEAD"]);
953
1015
  if (branch)
954
1016
  branchToken = sanitizeTmuxToken(branch);
955
- const sessionToken = sanitizeTmuxToken(sessionId.replace(/^omx-/, ''));
1017
+ const sessionToken = sanitizeTmuxToken(sessionId.replace(/^omx-/, ""));
956
1018
  const name = `omx-${dirToken}-${branchToken}-${sessionToken}`;
957
1019
  return name.length > 120 ? name.slice(0, 120) : name;
958
1020
  }
959
1021
  function parsePaneIdFromTmuxOutput(rawOutput) {
960
- const paneId = rawOutput.split('\n')[0]?.trim() || '';
961
- return paneId.startsWith('%') ? paneId : null;
1022
+ const paneId = rawOutput.split("\n")[0]?.trim() || "";
1023
+ return paneId.startsWith("%") ? paneId : null;
962
1024
  }
963
1025
  function parseWindowIndexFromTmuxOutput(rawOutput) {
964
- const windowIndex = rawOutput.split('\n')[0]?.trim() || '';
1026
+ const windowIndex = rawOutput.split("\n")[0]?.trim() || "";
965
1027
  return /^[0-9]+$/.test(windowIndex) ? windowIndex : null;
966
1028
  }
967
1029
  function detectDetachedSessionWindowIndex(sessionName) {
968
1030
  try {
969
- const output = execFileSync('tmux', ['display-message', '-p', '-t', sessionName, '#{window_index}'], { encoding: 'utf-8' });
1031
+ const output = execFileSync("tmux", ["display-message", "-p", "-t", sessionName, "#{window_index}"], { encoding: "utf-8" });
970
1032
  return parseWindowIndexFromTmuxOutput(output);
971
1033
  }
972
1034
  catch (err) {
@@ -975,129 +1037,177 @@ function detectDetachedSessionWindowIndex(sessionName) {
975
1037
  }
976
1038
  }
977
1039
  export function buildDetachedSessionBootstrapSteps(sessionName, cwd, codexCmd, hudCmd, workerLaunchArgs, codexHomeOverride, notifyTempContractRaw, nativeWindows = false) {
1040
+ const detachedLeaderCmd = nativeWindows
1041
+ ? "powershell.exe"
1042
+ : `/bin/sh -lc ${quoteShellArg(`${codexCmd}; status=$?; tmux kill-session -t ${quoteShellArg(sessionName)} >/dev/null 2>&1 || true; exit $status`)}`;
978
1043
  const newSessionArgs = [
979
- 'new-session', '-d', '-s', sessionName, '-c', cwd,
980
- ...(workerLaunchArgs ? ['-e', `${TEAM_WORKER_LAUNCH_ARGS_ENV}=${workerLaunchArgs}`] : []),
981
- ...(codexHomeOverride ? ['-e', `CODEX_HOME=${codexHomeOverride}`] : []),
982
- ...(notifyTempContractRaw ? ['-e', `${OMX_NOTIFY_TEMP_CONTRACT_ENV}=${notifyTempContractRaw}`] : []),
983
- nativeWindows ? 'powershell.exe' : codexCmd,
1044
+ "new-session",
1045
+ "-d",
1046
+ "-P",
1047
+ "-F",
1048
+ "#{pane_id}",
1049
+ "-s",
1050
+ sessionName,
1051
+ "-c",
1052
+ cwd,
1053
+ ...(workerLaunchArgs
1054
+ ? ["-e", `${TEAM_WORKER_LAUNCH_ARGS_ENV}=${workerLaunchArgs}`]
1055
+ : []),
1056
+ ...(codexHomeOverride ? ["-e", `CODEX_HOME=${codexHomeOverride}`] : []),
1057
+ ...(notifyTempContractRaw
1058
+ ? ["-e", `${OMX_NOTIFY_TEMP_CONTRACT_ENV}=${notifyTempContractRaw}`]
1059
+ : []),
1060
+ detachedLeaderCmd,
984
1061
  ];
985
1062
  const splitCaptureArgs = [
986
- 'split-window', '-v', '-l', String(HUD_TMUX_HEIGHT_LINES), '-d', '-t', sessionName,
987
- '-c', cwd, '-P', '-F', '#{pane_id}', hudCmd,
1063
+ "split-window",
1064
+ "-v",
1065
+ "-l",
1066
+ String(HUD_TMUX_HEIGHT_LINES),
1067
+ "-d",
1068
+ "-t",
1069
+ sessionName,
1070
+ "-c",
1071
+ cwd,
1072
+ "-P",
1073
+ "-F",
1074
+ "#{pane_id}",
1075
+ hudCmd,
988
1076
  ];
989
1077
  return [
990
- { name: 'new-session', args: newSessionArgs },
991
- { name: 'split-and-capture-hud-pane', args: splitCaptureArgs },
1078
+ { name: "new-session", args: newSessionArgs },
1079
+ { name: "split-and-capture-hud-pane", args: splitCaptureArgs },
992
1080
  ];
993
1081
  }
1082
+ async function updateActiveRalphthonLaunchTarget(cwd, patch) {
1083
+ const state = await readModeState("ralphthon", cwd).catch(() => null);
1084
+ if (!state || state.active !== true)
1085
+ return;
1086
+ const next = {};
1087
+ if (typeof patch.leader_pane_id === "string" &&
1088
+ patch.leader_pane_id.trim().startsWith("%")) {
1089
+ next.leader_pane_id = patch.leader_pane_id.trim();
1090
+ next.tmux_pane_id = patch.leader_pane_id.trim();
1091
+ }
1092
+ else if (typeof patch.tmux_pane_id === "string" &&
1093
+ patch.tmux_pane_id.trim().startsWith("%")) {
1094
+ next.tmux_pane_id = patch.tmux_pane_id.trim();
1095
+ }
1096
+ if (typeof patch.tmux_session === "string" &&
1097
+ patch.tmux_session.trim() !== "") {
1098
+ next.tmux_session = patch.tmux_session.trim();
1099
+ }
1100
+ if (Object.keys(next).length === 0)
1101
+ return;
1102
+ await updateModeState("ralphthon", next, cwd).catch(() => { });
1103
+ }
1104
+ async function readLaunchAppendInstructions() {
1105
+ const appendixCandidates = [
1106
+ process.env[OMX_RALPH_APPEND_INSTRUCTIONS_FILE_ENV]?.trim(),
1107
+ process.env[OMX_RALPHTHON_APPEND_INSTRUCTIONS_FILE_ENV]?.trim(),
1108
+ process.env[OMX_AUTORESEARCH_APPEND_INSTRUCTIONS_FILE_ENV]?.trim(),
1109
+ ].filter((value) => typeof value === "string" && value.length > 0);
1110
+ if (appendixCandidates.length === 0)
1111
+ return "";
1112
+ const appendixPath = appendixCandidates[0];
1113
+ if (!existsSync(appendixPath)) {
1114
+ throw new Error(`launch instructions file not found: ${appendixPath}`);
1115
+ }
1116
+ const { readFile } = await import("fs/promises");
1117
+ return (await readFile(appendixPath, "utf-8")).trim();
1118
+ }
994
1119
  export function buildDetachedSessionFinalizeSteps(sessionName, hudPaneId, hookWindowIndex, enableMouse, nativeWindows = false) {
995
1120
  const steps = [];
996
1121
  if (!nativeWindows && hudPaneId && hookWindowIndex) {
997
1122
  const hookTarget = buildResizeHookTarget(sessionName, hookWindowIndex);
998
- const hookName = buildResizeHookName('launch', sessionName, hookWindowIndex, hudPaneId);
999
- const clientAttachedHookName = buildClientAttachedReconcileHookName('launch', sessionName, hookWindowIndex, hudPaneId);
1123
+ const hookName = buildResizeHookName("launch", sessionName, hookWindowIndex, hudPaneId);
1124
+ const clientAttachedHookName = buildClientAttachedReconcileHookName("launch", sessionName, hookWindowIndex, hudPaneId);
1000
1125
  steps.push({
1001
- name: 'register-resize-hook',
1126
+ name: "register-resize-hook",
1002
1127
  args: buildRegisterResizeHookArgs(hookTarget, hookName, hudPaneId, HUD_TMUX_HEIGHT_LINES),
1003
1128
  });
1004
1129
  steps.push({
1005
- name: 'register-client-attached-reconcile',
1130
+ name: "register-client-attached-reconcile",
1006
1131
  args: buildRegisterClientAttachedReconcileArgs(hookTarget, clientAttachedHookName, hudPaneId, HUD_TMUX_HEIGHT_LINES),
1007
1132
  });
1008
1133
  steps.push({
1009
- name: 'schedule-delayed-resize',
1134
+ name: "schedule-delayed-resize",
1010
1135
  args: buildScheduleDelayedHudResizeArgs(hudPaneId, undefined, HUD_TMUX_HEIGHT_LINES),
1011
1136
  });
1012
1137
  steps.push({
1013
- name: 'reconcile-hud-resize',
1138
+ name: "reconcile-hud-resize",
1014
1139
  args: buildReconcileHudResizeArgs(hudPaneId, HUD_TMUX_HEIGHT_LINES),
1015
1140
  });
1016
1141
  }
1017
1142
  if (enableMouse) {
1018
- steps.push({ name: 'set-mouse', args: ['set-option', '-t', sessionName, 'mouse', 'on'] });
1143
+ steps.push({
1144
+ name: "set-mouse",
1145
+ args: ["set-option", "-t", sessionName, "mouse", "on"],
1146
+ });
1019
1147
  }
1020
- steps.push({ name: 'attach-session', args: ['attach-session', '-t', sessionName] });
1148
+ steps.push({
1149
+ name: "attach-session",
1150
+ args: ["attach-session", "-t", sessionName],
1151
+ });
1021
1152
  return steps;
1022
1153
  }
1023
1154
  export function buildDetachedSessionRollbackSteps(sessionName, hookTarget, hookName, clientAttachedHookName) {
1024
1155
  const steps = [];
1025
1156
  if (hookTarget && clientAttachedHookName) {
1026
1157
  steps.push({
1027
- name: 'unregister-client-attached-reconcile',
1158
+ name: "unregister-client-attached-reconcile",
1028
1159
  args: buildUnregisterClientAttachedReconcileArgs(hookTarget, clientAttachedHookName),
1029
1160
  });
1030
1161
  }
1031
1162
  if (hookTarget && hookName) {
1032
1163
  steps.push({
1033
- name: 'unregister-resize-hook',
1164
+ name: "unregister-resize-hook",
1034
1165
  args: buildUnregisterResizeHookArgs(hookTarget, hookName),
1035
1166
  });
1036
1167
  }
1037
- steps.push({ name: 'kill-session', args: ['kill-session', '-t', sessionName] });
1168
+ steps.push({
1169
+ name: "kill-session",
1170
+ args: ["kill-session", "-t", sessionName],
1171
+ });
1038
1172
  return steps;
1039
1173
  }
1040
- async function readAutoresearchAppendInstructions() {
1041
- const appendixPath = process.env[OMX_AUTORESEARCH_APPEND_INSTRUCTIONS_FILE_ENV]?.trim();
1042
- if (!appendixPath)
1043
- return '';
1044
- if (!existsSync(appendixPath)) {
1045
- throw new Error(`autoresearch instructions file not found: ${appendixPath}`);
1046
- }
1047
- const { readFile } = await import('fs/promises');
1048
- return (await readFile(appendixPath, 'utf-8')).trim();
1049
- }
1050
1174
  export function buildNotifyTempStartupMessages(contract, hasValidProviders) {
1051
1175
  const providers = contract.canonicalSelectors.length > 0
1052
- ? contract.canonicalSelectors.join(',')
1053
- : 'none';
1176
+ ? contract.canonicalSelectors.join(",")
1177
+ : "none";
1054
1178
  const infoLines = [
1055
1179
  `notify temp: active | providers=${providers} | persistent-routing=bypassed`,
1056
1180
  ];
1057
1181
  const warningLines = [...contract.warnings];
1058
1182
  if (!hasValidProviders) {
1059
- warningLines.push('notify temp: no valid providers resolved; notifications skipped');
1183
+ warningLines.push("notify temp: no valid providers resolved; notifications skipped");
1060
1184
  }
1061
1185
  return { infoLines, warningLines };
1062
1186
  }
1063
1187
  /**
1064
1188
  * preLaunch: Prepare environment before Codex starts.
1065
- * 1. Orphan cleanup (stale session from a crashed launch)
1066
- * 2. Generate runtime overlay + write session-scoped model instructions file
1067
- * 3. Write session.json
1189
+ * 1. Generate runtime overlay + write session-scoped model instructions file
1190
+ * 2. Write session.json
1191
+ *
1192
+ * Automatic stale-session cleanup is intentionally disabled here. Destructive
1193
+ * cleanup must be explicit via `omx cleanup` so normal launches never reap
1194
+ * files or processes from other OMX sessions.
1068
1195
  */
1069
1196
  async function preLaunch(cwd, sessionId, notifyTempContract) {
1070
- // 1. Orphan cleanup
1071
- const existingSession = await readSessionState(cwd);
1072
- if (existingSession && isSessionStale(existingSession)) {
1073
- try {
1074
- await removeSessionModelInstructionsFile(cwd, existingSession.session_id);
1075
- }
1076
- catch (err) {
1077
- process.stderr.write(`[cli/index] operation failed: ${err}\n`);
1078
- }
1079
- const { unlink } = await import('fs/promises');
1080
- try {
1081
- await unlink(join(cwd, '.omx', 'state', 'session.json'));
1082
- }
1083
- catch (err) {
1084
- process.stderr.write(`[cli/index] operation failed: ${err}\n`);
1085
- }
1086
- }
1087
- // 2. Generate runtime overlay + write session-scoped model instructions file
1197
+ // 1. Generate runtime overlay + write session-scoped model instructions file
1088
1198
  const orchestrationMode = await resolveSessionOrchestrationMode(cwd, sessionId);
1089
1199
  const overlay = await generateOverlay(cwd, sessionId, { orchestrationMode });
1090
- const autoresearchAppendix = await readAutoresearchAppendInstructions();
1091
- const sessionInstructions = autoresearchAppendix
1200
+ const launchAppendix = await readLaunchAppendInstructions();
1201
+ const sessionInstructions = launchAppendix.trim().length > 0
1092
1202
  ? `${overlay}
1093
1203
 
1094
- ${autoresearchAppendix}`
1204
+ ${launchAppendix}`
1095
1205
  : overlay;
1096
1206
  await writeSessionModelInstructionsFile(cwd, sessionId, sessionInstructions);
1097
- // 3. Write session state
1207
+ // 2. Write session state
1098
1208
  await resetSessionMetrics(cwd);
1099
1209
  await writeSessionStart(cwd, sessionId);
1100
- // 4. Start notify fallback watcher (best effort)
1210
+ // 3. Start notify fallback watcher (best effort)
1101
1211
  try {
1102
1212
  await startNotifyFallbackWatcher(cwd);
1103
1213
  }
@@ -1105,7 +1215,7 @@ ${autoresearchAppendix}`
1105
1215
  process.stderr.write(`[cli/index] operation failed: ${err}\n`);
1106
1216
  // Non-fatal
1107
1217
  }
1108
- // 5. Start derived watcher (best effort, opt-in)
1218
+ // 4. Start derived watcher (best effort, opt-in)
1109
1219
  try {
1110
1220
  await startHookDerivedWatcher(cwd);
1111
1221
  }
@@ -1113,11 +1223,12 @@ ${autoresearchAppendix}`
1113
1223
  process.stderr.write(`[cli/index] operation failed: ${err}\n`);
1114
1224
  // Non-fatal
1115
1225
  }
1116
- // 6. Emit temp notification startup summary + warnings, then send session-start lifecycle notification (best effort)
1226
+ // 5. Emit temp notification startup summary + warnings, then send session-start lifecycle notification (best effort)
1117
1227
  try {
1118
1228
  if (notifyTempContract?.active) {
1119
- process.env[OMX_NOTIFY_TEMP_CONTRACT_ENV] = serializeNotifyTempContract(notifyTempContract);
1120
- const { getNotificationConfig } = await import('../notifications/config.js');
1229
+ process.env[OMX_NOTIFY_TEMP_CONTRACT_ENV] =
1230
+ serializeNotifyTempContract(notifyTempContract);
1231
+ const { getNotificationConfig } = await import("../notifications/config.js");
1121
1232
  const resolved = getNotificationConfig();
1122
1233
  const startup = buildNotifyTempStartupMessages(notifyTempContract, Boolean(resolved?.enabled));
1123
1234
  for (const info of startup.infoLines) {
@@ -1130,8 +1241,8 @@ ${autoresearchAppendix}`
1130
1241
  else {
1131
1242
  delete process.env[OMX_NOTIFY_TEMP_CONTRACT_ENV];
1132
1243
  }
1133
- const { notifyLifecycle } = await import('../notifications/index.js');
1134
- await notifyLifecycle('session-start', {
1244
+ const { notifyLifecycle } = await import("../notifications/index.js");
1245
+ await notifyLifecycle("session-start", {
1135
1246
  sessionId,
1136
1247
  projectPath: cwd,
1137
1248
  projectName: basename(cwd),
@@ -1141,14 +1252,14 @@ ${autoresearchAppendix}`
1141
1252
  process.stderr.write(`[cli/index] operation failed: ${err}\n`);
1142
1253
  // Non-fatal: notification failures must never block launch
1143
1254
  }
1144
- // 7. Dispatch native hook event (best effort)
1255
+ // 6. Dispatch native hook event (best effort)
1145
1256
  try {
1146
- await emitNativeHookEvent(cwd, 'session-start', {
1257
+ await emitNativeHookEvent(cwd, "session-start", {
1147
1258
  session_id: sessionId,
1148
- context: buildNativeHookBaseContext(cwd, sessionId, 'started', {
1259
+ context: buildNativeHookBaseContext(cwd, sessionId, "started", {
1149
1260
  project_path: cwd,
1150
1261
  project_name: basename(cwd),
1151
- status: 'started',
1262
+ status: "started",
1152
1263
  }),
1153
1264
  });
1154
1265
  }
@@ -1166,9 +1277,9 @@ function runCodex(cwd, args, sessionId, workerDefaultModel, codexHomeOverride, n
1166
1277
  const nativeWindows = isNativeWindows();
1167
1278
  const omxBin = process.argv[1];
1168
1279
  const hudCmd = nativeWindows
1169
- ? buildWindowsPromptCommand('node', [omxBin, 'hud', '--watch'])
1170
- : buildTmuxPaneCommand('node', [omxBin, 'hud', '--watch']);
1171
- const inheritLeaderFlags = process.env[TEAM_INHERIT_LEADER_FLAGS_ENV] !== '0';
1280
+ ? buildWindowsPromptCommand("node", [omxBin, "hud", "--watch"])
1281
+ : buildTmuxPaneCommand("node", [omxBin, "hud", "--watch"]);
1282
+ const inheritLeaderFlags = process.env[TEAM_INHERIT_LEADER_FLAGS_ENV] !== "0";
1172
1283
  const workerLaunchArgs = resolveTeamWorkerLaunchArgsEnv(process.env[TEAM_WORKER_LAUNCH_ARGS_ENV], launchArgs, inheritLeaderFlags, workerDefaultModel);
1173
1284
  const codexBaseEnv = codexHomeOverride
1174
1285
  ? { ...process.env, CODEX_HOME: codexHomeOverride }
@@ -1184,7 +1295,7 @@ function runCodex(cwd, args, sessionId, workerDefaultModel, codexHomeOverride, n
1184
1295
  runCodexBlocking(cwd, launchArgs, codexEnvWithNotify);
1185
1296
  return;
1186
1297
  }
1187
- if (launchPolicy === 'inside-tmux') {
1298
+ if (launchPolicy === "inside-tmux") {
1188
1299
  // Already in tmux: launch codex in current pane, HUD in bottom split
1189
1300
  const currentPaneId = process.env.TMUX_PANE;
1190
1301
  const staleHudPaneIds = listHudWatchPaneIdsInCurrentWindow(currentPaneId);
@@ -1202,13 +1313,15 @@ function runCodex(cwd, args, sessionId, workerDefaultModel, codexHomeOverride, n
1202
1313
  // Enable mouse scrolling at session start so scroll works before team
1203
1314
  // expansion. Previously this was only called from createTeamSession().
1204
1315
  // Opt-out: set OMX_MOUSE=0. (closes #128)
1205
- if (process.env.OMX_MOUSE !== '0') {
1316
+ if (process.env.OMX_MOUSE !== "0") {
1206
1317
  try {
1207
1318
  const tmuxPaneTarget = process.env.TMUX_PANE;
1208
1319
  const displayArgs = tmuxPaneTarget
1209
- ? ['display-message', '-p', '-t', tmuxPaneTarget, '#S']
1210
- : ['display-message', '-p', '#S'];
1211
- const tmuxSession = execFileSync('tmux', displayArgs, { encoding: 'utf-8' }).trim();
1320
+ ? ["display-message", "-p", "-t", tmuxPaneTarget, "#S"]
1321
+ : ["display-message", "-p", "#S"];
1322
+ const tmuxSession = execFileSync("tmux", displayArgs, {
1323
+ encoding: "utf-8",
1324
+ }).trim();
1212
1325
  if (tmuxSession)
1213
1326
  enableMouseScrolling(tmuxSession);
1214
1327
  }
@@ -1217,6 +1330,22 @@ function runCodex(cwd, args, sessionId, workerDefaultModel, codexHomeOverride, n
1217
1330
  // Non-fatal: mouse scrolling is a convenience feature
1218
1331
  }
1219
1332
  }
1333
+ const activePaneId = process.env.TMUX_PANE?.trim();
1334
+ if (activePaneId) {
1335
+ let tmuxSessionName;
1336
+ try {
1337
+ const displayArgs = ["display-message", "-p", "-t", activePaneId, "#S"];
1338
+ tmuxSessionName =
1339
+ execFileSync("tmux", displayArgs, { encoding: "utf-8" }).trim() ||
1340
+ undefined;
1341
+ }
1342
+ catch { }
1343
+ void updateActiveRalphthonLaunchTarget(cwd, {
1344
+ leader_pane_id: activePaneId,
1345
+ tmux_pane_id: activePaneId,
1346
+ tmux_session: tmuxSessionName ?? null,
1347
+ });
1348
+ }
1220
1349
  try {
1221
1350
  runCodexBlocking(cwd, launchArgs, codexEnvWithNotify);
1222
1351
  }
@@ -1227,62 +1356,77 @@ function runCodex(cwd, args, sessionId, workerDefaultModel, codexHomeOverride, n
1227
1356
  }
1228
1357
  }
1229
1358
  }
1230
- else if (launchPolicy === 'direct') {
1359
+ else if (launchPolicy === "direct") {
1231
1360
  // Detached HUD sessions require tmux. Skip the bootstrap entirely when the
1232
1361
  // binary is unavailable so direct launches do not emit noisy ENOENT logs.
1233
1362
  runCodexBlocking(cwd, launchArgs, codexEnvWithNotify);
1234
1363
  }
1235
1364
  else {
1236
1365
  // Not in tmux: create a new tmux session with codex + HUD pane
1237
- const codexCmd = buildTmuxPaneCommand('codex', launchArgs);
1366
+ const codexCmd = buildTmuxPaneCommand("codex", launchArgs);
1238
1367
  const detachedWindowsCodexCmd = nativeWindows
1239
- ? buildWindowsPromptCommand('codex', launchArgs)
1368
+ ? buildWindowsPromptCommand("codex", launchArgs)
1240
1369
  : null;
1241
1370
  const tmuxSessionId = `omx-${Date.now()}-${Math.random().toString(36).slice(2, 8)}`;
1242
1371
  const sessionName = buildTmuxSessionName(cwd, tmuxSessionId);
1243
1372
  let createdDetachedSession = false;
1373
+ let detachedLeaderPaneId = null;
1244
1374
  let registeredHookTarget = null;
1245
1375
  let registeredHookName = null;
1246
1376
  let registeredClientAttachedHookName = null;
1247
1377
  try {
1248
1378
  const bootstrapSteps = buildDetachedSessionBootstrapSteps(sessionName, cwd, codexCmd, hudCmd, workerLaunchArgs, codexHomeOverride, notifyTempContractRaw, nativeWindows);
1249
1379
  for (const step of bootstrapSteps) {
1250
- const output = execFileSync('tmux', step.args, { stdio: step.name === 'new-session' ? 'ignore' : 'pipe', encoding: 'utf-8' });
1251
- if (step.name === 'new-session') {
1380
+ const output = execFileSync("tmux", step.args, {
1381
+ stdio: "pipe",
1382
+ encoding: "utf-8",
1383
+ });
1384
+ if (step.name === "new-session") {
1252
1385
  createdDetachedSession = true;
1386
+ detachedLeaderPaneId = parsePaneIdFromTmuxOutput(output || "");
1387
+ void updateActiveRalphthonLaunchTarget(cwd, {
1388
+ leader_pane_id: detachedLeaderPaneId,
1389
+ tmux_pane_id: detachedLeaderPaneId,
1390
+ tmux_session: sessionName,
1391
+ });
1253
1392
  }
1254
- if (step.name === 'split-and-capture-hud-pane') {
1255
- const hudPaneId = parsePaneIdFromTmuxOutput(output || '');
1256
- const hookWindowIndex = hudPaneId ? detectDetachedSessionWindowIndex(sessionName) : null;
1393
+ if (step.name === "split-and-capture-hud-pane") {
1394
+ const hudPaneId = parsePaneIdFromTmuxOutput(output || "");
1395
+ const hookWindowIndex = hudPaneId
1396
+ ? detectDetachedSessionWindowIndex(sessionName)
1397
+ : null;
1257
1398
  const hookTarget = hudPaneId && hookWindowIndex
1258
1399
  ? buildResizeHookTarget(sessionName, hookWindowIndex)
1259
1400
  : null;
1260
1401
  const hookName = hudPaneId && hookWindowIndex
1261
- ? buildResizeHookName('launch', sessionName, hookWindowIndex, hudPaneId)
1402
+ ? buildResizeHookName("launch", sessionName, hookWindowIndex, hudPaneId)
1262
1403
  : null;
1263
1404
  const clientAttachedHookName = hudPaneId && hookWindowIndex
1264
- ? buildClientAttachedReconcileHookName('launch', sessionName, hookWindowIndex, hudPaneId)
1405
+ ? buildClientAttachedReconcileHookName("launch", sessionName, hookWindowIndex, hudPaneId)
1265
1406
  : null;
1266
- const finalizeSteps = buildDetachedSessionFinalizeSteps(sessionName, hudPaneId, hookWindowIndex, process.env.OMX_MOUSE !== '0', nativeWindows);
1407
+ const finalizeSteps = buildDetachedSessionFinalizeSteps(sessionName, hudPaneId, hookWindowIndex, process.env.OMX_MOUSE !== "0", nativeWindows);
1267
1408
  if (nativeWindows && detachedWindowsCodexCmd) {
1268
1409
  scheduleDetachedWindowsCodexLaunch(sessionName, detachedWindowsCodexCmd);
1269
1410
  }
1270
1411
  for (const finalizeStep of finalizeSteps) {
1271
- const stdio = finalizeStep.name === 'attach-session' ? 'inherit' : 'ignore';
1412
+ const stdio = finalizeStep.name === "attach-session" ? "inherit" : "ignore";
1272
1413
  try {
1273
- execFileSync('tmux', finalizeStep.args, { stdio });
1414
+ execFileSync("tmux", finalizeStep.args, { stdio });
1274
1415
  }
1275
1416
  catch (err) {
1276
1417
  process.stderr.write(`[cli/index] operation failed: ${err}\n`);
1277
- if (finalizeStep.name === 'attach-session')
1278
- throw new Error('failed to attach detached tmux session');
1418
+ if (finalizeStep.name === "attach-session")
1419
+ throw new Error("failed to attach detached tmux session");
1279
1420
  continue;
1280
1421
  }
1281
- if (finalizeStep.name === 'register-resize-hook' && hookTarget && hookName) {
1422
+ if (finalizeStep.name === "register-resize-hook" &&
1423
+ hookTarget &&
1424
+ hookName) {
1282
1425
  registeredHookTarget = hookTarget;
1283
1426
  registeredHookName = hookName;
1284
1427
  }
1285
- if (finalizeStep.name === 'register-client-attached-reconcile' && clientAttachedHookName) {
1428
+ if (finalizeStep.name === "register-client-attached-reconcile" &&
1429
+ clientAttachedHookName) {
1286
1430
  registeredClientAttachedHookName = clientAttachedHookName;
1287
1431
  }
1288
1432
  }
@@ -1295,7 +1439,7 @@ function runCodex(cwd, args, sessionId, workerDefaultModel, codexHomeOverride, n
1295
1439
  const rollbackSteps = buildDetachedSessionRollbackSteps(sessionName, registeredHookTarget, registeredHookName, registeredClientAttachedHookName);
1296
1440
  for (const rollbackStep of rollbackSteps) {
1297
1441
  try {
1298
- execFileSync('tmux', rollbackStep.args, { stdio: 'ignore' });
1442
+ execFileSync("tmux", rollbackStep.args, { stdio: "ignore" });
1299
1443
  }
1300
1444
  catch (err) {
1301
1445
  process.stderr.write(`[cli/index] operation failed: ${err}\n`);
@@ -1310,7 +1454,11 @@ function runCodex(cwd, args, sessionId, workerDefaultModel, codexHomeOverride, n
1310
1454
  }
1311
1455
  function listHudWatchPaneIdsInCurrentWindow(currentPaneId) {
1312
1456
  try {
1313
- const output = execFileSync('tmux', ['list-panes', '-F', '#{pane_id}\t#{pane_current_command}\t#{pane_start_command}'], { encoding: 'utf-8' });
1457
+ const output = execFileSync("tmux", [
1458
+ "list-panes",
1459
+ "-F",
1460
+ "#{pane_id}\t#{pane_current_command}\t#{pane_start_command}",
1461
+ ], { encoding: "utf-8" });
1314
1462
  return findHudWatchPaneIds(parseTmuxPaneSnapshot(output), currentPaneId);
1315
1463
  }
1316
1464
  catch (err) {
@@ -1319,14 +1467,26 @@ function listHudWatchPaneIdsInCurrentWindow(currentPaneId) {
1319
1467
  }
1320
1468
  }
1321
1469
  function createHudWatchPane(cwd, hudCmd) {
1322
- const output = execFileSync('tmux', ['split-window', '-v', '-l', String(HUD_TMUX_HEIGHT_LINES), '-d', '-c', cwd, '-P', '-F', '#{pane_id}', hudCmd], { encoding: 'utf-8' });
1470
+ const output = execFileSync("tmux", [
1471
+ "split-window",
1472
+ "-v",
1473
+ "-l",
1474
+ String(HUD_TMUX_HEIGHT_LINES),
1475
+ "-d",
1476
+ "-c",
1477
+ cwd,
1478
+ "-P",
1479
+ "-F",
1480
+ "#{pane_id}",
1481
+ hudCmd,
1482
+ ], { encoding: "utf-8" });
1323
1483
  return parsePaneIdFromTmuxOutput(output);
1324
1484
  }
1325
1485
  function killTmuxPane(paneId) {
1326
- if (!paneId.startsWith('%'))
1486
+ if (!paneId.startsWith("%"))
1327
1487
  return;
1328
1488
  try {
1329
- execFileSync('tmux', ['kill-pane', '-t', paneId], { stdio: 'ignore' });
1489
+ execFileSync("tmux", ["kill-pane", "-t", paneId], { stdio: "ignore" });
1330
1490
  }
1331
1491
  catch (err) {
1332
1492
  process.stderr.write(`[cli/index] operation failed: ${err}\n`);
@@ -1334,20 +1494,24 @@ function killTmuxPane(paneId) {
1334
1494
  }
1335
1495
  }
1336
1496
  export function buildTmuxShellCommand(command, args) {
1337
- return [quoteShellArg(command), ...args.map(quoteShellArg)].join(' ');
1497
+ return [quoteShellArg(command), ...args.map(quoteShellArg)].join(" ");
1338
1498
  }
1339
1499
  function encodePowerShellCommand(commandText) {
1340
- return Buffer.from(commandText, 'utf16le').toString('base64');
1500
+ return Buffer.from(commandText, "utf16le").toString("base64");
1341
1501
  }
1342
1502
  function isCodexVersionRequest(args) {
1343
1503
  return args.some((arg) => CODEX_VERSION_FLAGS.has(arg));
1344
1504
  }
1345
1505
  export function buildWindowsPromptCommand(command, args) {
1346
- const invocation = ['&', quotePowerShellArg(command), ...args.map(quotePowerShellArg)].join(' ');
1506
+ const invocation = [
1507
+ "&",
1508
+ quotePowerShellArg(command),
1509
+ ...args.map(quotePowerShellArg),
1510
+ ].join(" ");
1347
1511
  const wrappedCommand = [
1348
1512
  "$ErrorActionPreference = 'Stop'",
1349
1513
  `& { ${invocation} }`,
1350
- ].join('; ');
1514
+ ].join("; ");
1351
1515
  return `powershell.exe -NoLogo -NoExit -EncodedCommand ${encodePowerShellCommand(wrappedCommand)}`;
1352
1516
  }
1353
1517
  /**
@@ -1357,15 +1521,15 @@ export function buildWindowsPromptCommand(command, args) {
1357
1521
  */
1358
1522
  export function buildTmuxPaneCommand(command, args, shellPath = process.env.SHELL) {
1359
1523
  const bareCmd = buildTmuxShellCommand(command, args);
1360
- let rcSource = '';
1524
+ let rcSource = "";
1361
1525
  if (shellPath && /\/zsh$/i.test(shellPath)) {
1362
- rcSource = 'if [ -f ~/.zshrc ]; then source ~/.zshrc; fi; ';
1526
+ rcSource = "if [ -f ~/.zshrc ]; then source ~/.zshrc; fi; ";
1363
1527
  }
1364
1528
  else if (shellPath && /\/bash$/i.test(shellPath)) {
1365
- rcSource = 'if [ -f ~/.bashrc ]; then source ~/.bashrc; fi; ';
1529
+ rcSource = "if [ -f ~/.bashrc ]; then source ~/.bashrc; fi; ";
1366
1530
  }
1367
- const rawShell = shellPath && shellPath.trim() !== '' ? shellPath.trim() : '/bin/sh';
1368
- const shellBin = ALLOWED_SHELLS.has(rawShell) ? rawShell : '/bin/sh';
1531
+ const rawShell = shellPath && shellPath.trim() !== "" ? shellPath.trim() : "/bin/sh";
1532
+ const shellBin = ALLOWED_SHELLS.has(rawShell) ? rawShell : "/bin/sh";
1369
1533
  const inner = `${rcSource}exec ${bareCmd}`;
1370
1534
  return `${quoteShellArg(shellBin)} -lc ${quoteShellArg(inner)}`;
1371
1535
  }
@@ -1387,12 +1551,12 @@ function buildDetachedWindowsBootstrapScript(sessionName, commandText, delayMs =
1387
1551
  `try { execFileSync('tmux', ['send-keys', '-t', ${targetLiteral}, '-l', '--', ${commandLiteral}], { stdio: 'ignore' }); } catch {}`,
1388
1552
  `try { execFileSync('tmux', ['send-keys', '-t', ${targetLiteral}, 'C-m'], { stdio: 'ignore' }); } catch {}`,
1389
1553
  `}, ${delay});`,
1390
- ].join('');
1554
+ ].join("");
1391
1555
  }
1392
1556
  function scheduleDetachedWindowsCodexLaunch(sessionName, commandText) {
1393
- const child = spawn(process.execPath, ['-e', buildDetachedWindowsBootstrapScript(sessionName, commandText)], {
1557
+ const child = spawn(process.execPath, ["-e", buildDetachedWindowsBootstrapScript(sessionName, commandText)], {
1394
1558
  detached: true,
1395
- stdio: 'ignore',
1559
+ stdio: "ignore",
1396
1560
  windowsHide: true,
1397
1561
  });
1398
1562
  child.unref();
@@ -1460,15 +1624,15 @@ async function postLaunch(cwd, sessionId) {
1460
1624
  }
1461
1625
  // 3. Cancel any still-active modes
1462
1626
  try {
1463
- const { readdir, writeFile, readFile } = await import('fs/promises');
1627
+ const { readdir, writeFile, readFile } = await import("fs/promises");
1464
1628
  const scopedDirs = [getBaseStateDir(cwd), getStateDir(cwd, sessionId)];
1465
1629
  for (const stateDir of scopedDirs) {
1466
1630
  const files = await readdir(stateDir).catch(() => []);
1467
1631
  for (const file of files) {
1468
- if (!file.endsWith('-state.json') || file === 'session.json')
1632
+ if (!file.endsWith("-state.json") || file === "session.json")
1469
1633
  continue;
1470
1634
  const path = join(stateDir, file);
1471
- const content = await readFile(path, 'utf-8');
1635
+ const content = await readFile(path, "utf-8");
1472
1636
  const state = JSON.parse(content);
1473
1637
  if (state.active) {
1474
1638
  state.active = false;
@@ -1483,16 +1647,16 @@ async function postLaunch(cwd, sessionId) {
1483
1647
  }
1484
1648
  // 4. Send session-end lifecycle notification (best effort)
1485
1649
  try {
1486
- const { notifyLifecycle } = await import('../notifications/index.js');
1650
+ const { notifyLifecycle } = await import("../notifications/index.js");
1487
1651
  const durationMs = sessionStartedAt
1488
1652
  ? Date.now() - new Date(sessionStartedAt).getTime()
1489
1653
  : undefined;
1490
- await notifyLifecycle('session-end', {
1654
+ await notifyLifecycle("session-end", {
1491
1655
  sessionId,
1492
1656
  projectPath: cwd,
1493
1657
  projectName: basename(cwd),
1494
1658
  durationMs,
1495
- reason: 'session_exit',
1659
+ reason: "session_exit",
1496
1660
  });
1497
1661
  }
1498
1662
  catch (err) {
@@ -1504,19 +1668,21 @@ async function postLaunch(cwd, sessionId) {
1504
1668
  const durationMs = sessionStartedAt
1505
1669
  ? Date.now() - new Date(sessionStartedAt).getTime()
1506
1670
  : undefined;
1507
- const normalizedEvent = process.exitCode && process.exitCode !== 0 ? 'failed' : 'finished';
1508
- const errorSummary = normalizedEvent === 'failed'
1671
+ const normalizedEvent = process.exitCode && process.exitCode !== 0 ? "failed" : "finished";
1672
+ const errorSummary = normalizedEvent === "failed"
1509
1673
  ? `codex exited with code ${process.exitCode}`
1510
1674
  : undefined;
1511
- await emitNativeHookEvent(cwd, 'session-end', {
1675
+ await emitNativeHookEvent(cwd, "session-end", {
1512
1676
  session_id: sessionId,
1513
1677
  context: buildNativeHookBaseContext(cwd, sessionId, normalizedEvent, {
1514
1678
  project_path: cwd,
1515
1679
  project_name: basename(cwd),
1516
1680
  duration_ms: durationMs,
1517
- reason: 'session_exit',
1518
- status: normalizedEvent === 'failed' ? 'failed' : 'finished',
1519
- ...(process.exitCode !== undefined ? { exit_code: process.exitCode } : {}),
1681
+ reason: "session_exit",
1682
+ status: normalizedEvent === "failed" ? "failed" : "finished",
1683
+ ...(process.exitCode !== undefined
1684
+ ? { exit_code: process.exitCode }
1685
+ : {}),
1520
1686
  ...(errorSummary ? { error_summary: errorSummary } : {}),
1521
1687
  }),
1522
1688
  });
@@ -1528,7 +1694,7 @@ async function postLaunch(cwd, sessionId) {
1528
1694
  }
1529
1695
  async function emitNativeHookEvent(cwd, event, opts = {}) {
1530
1696
  const payload = buildHookEvent(event, {
1531
- source: 'native',
1697
+ source: "native",
1532
1698
  context: opts.context || {},
1533
1699
  session_id: opts.session_id,
1534
1700
  thread_id: opts.thread_id,
@@ -1540,10 +1706,10 @@ async function emitNativeHookEvent(cwd, event, opts = {}) {
1540
1706
  });
1541
1707
  }
1542
1708
  function notifyFallbackPidPath(cwd) {
1543
- return join(cwd, '.omx', 'state', 'notify-fallback.pid');
1709
+ return join(cwd, ".omx", "state", "notify-fallback.pid");
1544
1710
  }
1545
1711
  function hookDerivedWatcherPidPath(cwd) {
1546
- return join(cwd, '.omx', 'state', 'hook-derived-watcher.pid');
1712
+ return join(cwd, ".omx", "state", "hook-derived-watcher.pid");
1547
1713
  }
1548
1714
  function parseWatcherPidFile(content) {
1549
1715
  const trimmed = content.trim();
@@ -1551,217 +1717,221 @@ function parseWatcherPidFile(content) {
1551
1717
  return null;
1552
1718
  try {
1553
1719
  const parsed = JSON.parse(trimmed);
1554
- return typeof parsed.pid === 'number' && Number.isFinite(parsed.pid) && parsed.pid > 0 ? parsed.pid : null;
1720
+ return typeof parsed.pid === "number" &&
1721
+ Number.isFinite(parsed.pid) &&
1722
+ parsed.pid > 0
1723
+ ? parsed.pid
1724
+ : null;
1555
1725
  }
1556
1726
  catch {
1557
1727
  const pid = Number.parseInt(trimmed, 10);
1558
1728
  return Number.isFinite(pid) && pid > 0 ? pid : null;
1559
1729
  }
1560
1730
  }
1561
- function tryKillPid(pid, signal = 'SIGTERM') {
1731
+ function tryKillPid(pid, signal = "SIGTERM") {
1562
1732
  try {
1563
1733
  process.kill(pid, signal);
1564
1734
  return true;
1565
1735
  }
1566
1736
  catch (error) {
1567
1737
  const code = error.code;
1568
- if (code === 'ESRCH')
1738
+ if (code === "ESRCH")
1569
1739
  return false;
1570
1740
  throw error;
1571
1741
  }
1572
1742
  }
1573
1743
  async function startNotifyFallbackWatcher(cwd) {
1574
- if (process.env.OMX_NOTIFY_FALLBACK === '0')
1744
+ if (process.env.OMX_NOTIFY_FALLBACK === "0")
1575
1745
  return;
1576
- const { mkdir, writeFile, readFile } = await import('fs/promises');
1746
+ const { mkdir, writeFile, readFile } = await import("fs/promises");
1577
1747
  const pidPath = notifyFallbackPidPath(cwd);
1578
1748
  const pkgRoot = getPackageRoot();
1579
- const watcherScript = join(pkgRoot, 'scripts', 'notify-fallback-watcher.js');
1580
- const notifyScript = join(pkgRoot, 'scripts', 'notify-hook.js');
1749
+ const watcherScript = join(pkgRoot, "scripts", "notify-fallback-watcher.js");
1750
+ const notifyScript = join(pkgRoot, "scripts", "notify-hook.js");
1581
1751
  if (!existsSync(watcherScript) || !existsSync(notifyScript))
1582
1752
  return;
1583
1753
  // Stop stale watcher from a previous run.
1584
1754
  if (existsSync(pidPath)) {
1585
1755
  try {
1586
- const prevPid = parseWatcherPidFile(await readFile(pidPath, 'utf-8'));
1756
+ const prevPid = parseWatcherPidFile(await readFile(pidPath, "utf-8"));
1587
1757
  if (prevPid) {
1588
- tryKillPid(prevPid, 'SIGTERM');
1758
+ tryKillPid(prevPid, "SIGTERM");
1589
1759
  }
1590
1760
  }
1591
1761
  catch (error) {
1592
- if (!hasErrnoCode(error, 'ESRCH')) {
1593
- console.warn('[omx] warning: failed to stop stale notify fallback watcher', {
1762
+ if (!hasErrnoCode(error, "ESRCH")) {
1763
+ console.warn("[omx] warning: failed to stop stale notify fallback watcher", {
1594
1764
  path: pidPath,
1595
1765
  error: error instanceof Error ? error.message : String(error),
1596
1766
  });
1597
1767
  }
1598
1768
  }
1599
1769
  }
1600
- await mkdir(join(cwd, '.omx', 'state'), { recursive: true }).catch((error) => {
1601
- console.warn('[omx] warning: failed to create notify fallback watcher state directory', {
1770
+ await mkdir(join(cwd, ".omx", "state"), { recursive: true }).catch((error) => {
1771
+ console.warn("[omx] warning: failed to create notify fallback watcher state directory", {
1602
1772
  cwd,
1603
1773
  error: error instanceof Error ? error.message : String(error),
1604
1774
  });
1605
1775
  });
1606
1776
  const child = spawn(process.execPath, [
1607
1777
  watcherScript,
1608
- '--cwd',
1778
+ "--cwd",
1609
1779
  cwd,
1610
- '--notify-script',
1780
+ "--notify-script",
1611
1781
  notifyScript,
1612
- '--pid-file',
1782
+ "--pid-file",
1613
1783
  pidPath,
1614
- '--parent-pid',
1784
+ "--parent-pid",
1615
1785
  String(process.pid),
1616
1786
  ...(process.env.OMX_NOTIFY_FALLBACK_MAX_LIFETIME_MS
1617
- ? ['--max-lifetime-ms', process.env.OMX_NOTIFY_FALLBACK_MAX_LIFETIME_MS]
1787
+ ? ["--max-lifetime-ms", process.env.OMX_NOTIFY_FALLBACK_MAX_LIFETIME_MS]
1618
1788
  : []),
1619
1789
  ], {
1620
1790
  cwd,
1621
1791
  detached: true,
1622
- stdio: 'ignore',
1792
+ stdio: "ignore",
1623
1793
  });
1624
1794
  child.unref();
1625
1795
  await writeFile(pidPath, JSON.stringify({ pid: child.pid, started_at: new Date().toISOString() }, null, 2)).catch((error) => {
1626
- console.warn('[omx] warning: failed to write notify fallback watcher pid file', {
1796
+ console.warn("[omx] warning: failed to write notify fallback watcher pid file", {
1627
1797
  path: pidPath,
1628
1798
  error: error instanceof Error ? error.message : String(error),
1629
1799
  });
1630
1800
  });
1631
1801
  }
1632
1802
  async function startHookDerivedWatcher(cwd) {
1633
- if (process.env.OMX_HOOK_DERIVED_SIGNALS !== '1')
1803
+ if (process.env.OMX_HOOK_DERIVED_SIGNALS !== "1")
1634
1804
  return;
1635
- const { mkdir, writeFile, readFile } = await import('fs/promises');
1805
+ const { mkdir, writeFile, readFile } = await import("fs/promises");
1636
1806
  const pidPath = hookDerivedWatcherPidPath(cwd);
1637
1807
  const pkgRoot = getPackageRoot();
1638
- const watcherScript = join(pkgRoot, 'scripts', 'hook-derived-watcher.js');
1808
+ const watcherScript = join(pkgRoot, "scripts", "hook-derived-watcher.js");
1639
1809
  if (!existsSync(watcherScript))
1640
1810
  return;
1641
1811
  if (existsSync(pidPath)) {
1642
1812
  try {
1643
- const prev = JSON.parse(await readFile(pidPath, 'utf-8'));
1644
- if (prev && typeof prev.pid === 'number') {
1645
- process.kill(prev.pid, 'SIGTERM');
1813
+ const prev = JSON.parse(await readFile(pidPath, "utf-8"));
1814
+ if (prev && typeof prev.pid === "number") {
1815
+ process.kill(prev.pid, "SIGTERM");
1646
1816
  }
1647
1817
  }
1648
1818
  catch (error) {
1649
- console.warn('[omx] warning: failed to stop stale hook-derived watcher', {
1819
+ console.warn("[omx] warning: failed to stop stale hook-derived watcher", {
1650
1820
  path: pidPath,
1651
1821
  error: error instanceof Error ? error.message : String(error),
1652
1822
  });
1653
1823
  }
1654
1824
  }
1655
- await mkdir(join(cwd, '.omx', 'state'), { recursive: true }).catch((error) => {
1656
- console.warn('[omx] warning: failed to create hook-derived watcher state directory', {
1825
+ await mkdir(join(cwd, ".omx", "state"), { recursive: true }).catch((error) => {
1826
+ console.warn("[omx] warning: failed to create hook-derived watcher state directory", {
1657
1827
  cwd,
1658
1828
  error: error instanceof Error ? error.message : String(error),
1659
1829
  });
1660
1830
  });
1661
- const child = spawn(process.execPath, [watcherScript, '--cwd', cwd], {
1831
+ const child = spawn(process.execPath, [watcherScript, "--cwd", cwd], {
1662
1832
  cwd,
1663
1833
  detached: true,
1664
- stdio: 'ignore',
1834
+ stdio: "ignore",
1665
1835
  env: process.env,
1666
1836
  });
1667
1837
  child.unref();
1668
1838
  await writeFile(pidPath, JSON.stringify({ pid: child.pid, started_at: new Date().toISOString() }, null, 2)).catch((error) => {
1669
- console.warn('[omx] warning: failed to write hook-derived watcher pid file', {
1839
+ console.warn("[omx] warning: failed to write hook-derived watcher pid file", {
1670
1840
  path: pidPath,
1671
1841
  error: error instanceof Error ? error.message : String(error),
1672
1842
  });
1673
1843
  });
1674
1844
  }
1675
1845
  async function stopNotifyFallbackWatcher(cwd) {
1676
- const { readFile, unlink } = await import('fs/promises');
1846
+ const { readFile, unlink } = await import("fs/promises");
1677
1847
  const pidPath = notifyFallbackPidPath(cwd);
1678
1848
  if (!existsSync(pidPath))
1679
1849
  return;
1680
1850
  try {
1681
- const pid = parseWatcherPidFile(await readFile(pidPath, 'utf-8'));
1851
+ const pid = parseWatcherPidFile(await readFile(pidPath, "utf-8"));
1682
1852
  if (pid) {
1683
- tryKillPid(pid, 'SIGTERM');
1853
+ tryKillPid(pid, "SIGTERM");
1684
1854
  }
1685
1855
  }
1686
1856
  catch (error) {
1687
- if (!hasErrnoCode(error, 'ESRCH')) {
1688
- console.warn('[omx] warning: failed to stop notify fallback watcher process', {
1857
+ if (!hasErrnoCode(error, "ESRCH")) {
1858
+ console.warn("[omx] warning: failed to stop notify fallback watcher process", {
1689
1859
  path: pidPath,
1690
1860
  error: error instanceof Error ? error.message : String(error),
1691
1861
  });
1692
1862
  }
1693
1863
  }
1694
1864
  await unlink(pidPath).catch((error) => {
1695
- console.warn('[omx] warning: failed to remove notify fallback watcher pid file', {
1865
+ console.warn("[omx] warning: failed to remove notify fallback watcher pid file", {
1696
1866
  path: pidPath,
1697
1867
  error: error instanceof Error ? error.message : String(error),
1698
1868
  });
1699
1869
  });
1700
1870
  }
1701
1871
  async function stopHookDerivedWatcher(cwd) {
1702
- const { readFile, unlink } = await import('fs/promises');
1872
+ const { readFile, unlink } = await import("fs/promises");
1703
1873
  const pidPath = hookDerivedWatcherPidPath(cwd);
1704
1874
  if (!existsSync(pidPath))
1705
1875
  return;
1706
1876
  try {
1707
- const parsed = JSON.parse(await readFile(pidPath, 'utf-8'));
1708
- if (parsed && typeof parsed.pid === 'number') {
1709
- process.kill(parsed.pid, 'SIGTERM');
1877
+ const parsed = JSON.parse(await readFile(pidPath, "utf-8"));
1878
+ if (parsed && typeof parsed.pid === "number") {
1879
+ process.kill(parsed.pid, "SIGTERM");
1710
1880
  }
1711
1881
  }
1712
1882
  catch (error) {
1713
- console.warn('[omx] warning: failed to stop hook-derived watcher process', {
1883
+ console.warn("[omx] warning: failed to stop hook-derived watcher process", {
1714
1884
  path: pidPath,
1715
1885
  error: error instanceof Error ? error.message : String(error),
1716
1886
  });
1717
1887
  }
1718
1888
  await unlink(pidPath).catch((error) => {
1719
- console.warn('[omx] warning: failed to remove hook-derived watcher pid file', {
1889
+ console.warn("[omx] warning: failed to remove hook-derived watcher pid file", {
1720
1890
  path: pidPath,
1721
1891
  error: error instanceof Error ? error.message : String(error),
1722
1892
  });
1723
1893
  });
1724
1894
  }
1725
1895
  async function flushNotifyFallbackOnce(cwd) {
1726
- const { spawnSync } = await import('child_process');
1896
+ const { spawnSync } = await import("child_process");
1727
1897
  const pkgRoot = getPackageRoot();
1728
- const watcherScript = join(pkgRoot, 'scripts', 'notify-fallback-watcher.js');
1729
- const notifyScript = join(pkgRoot, 'scripts', 'notify-hook.js');
1898
+ const watcherScript = join(pkgRoot, "scripts", "notify-fallback-watcher.js");
1899
+ const notifyScript = join(pkgRoot, "scripts", "notify-hook.js");
1730
1900
  if (!existsSync(watcherScript) || !existsSync(notifyScript))
1731
1901
  return;
1732
- spawnSync(process.execPath, [watcherScript, '--once', '--cwd', cwd, '--notify-script', notifyScript], {
1902
+ spawnSync(process.execPath, [watcherScript, "--once", "--cwd", cwd, "--notify-script", notifyScript], {
1733
1903
  cwd,
1734
- stdio: 'ignore',
1904
+ stdio: "ignore",
1735
1905
  timeout: 3000,
1736
1906
  });
1737
1907
  }
1738
1908
  async function flushHookDerivedWatcherOnce(cwd) {
1739
- if (process.env.OMX_HOOK_DERIVED_SIGNALS !== '1')
1909
+ if (process.env.OMX_HOOK_DERIVED_SIGNALS !== "1")
1740
1910
  return;
1741
- const { spawnSync } = await import('child_process');
1911
+ const { spawnSync } = await import("child_process");
1742
1912
  const pkgRoot = getPackageRoot();
1743
- const watcherScript = join(pkgRoot, 'scripts', 'hook-derived-watcher.js');
1913
+ const watcherScript = join(pkgRoot, "scripts", "hook-derived-watcher.js");
1744
1914
  if (!existsSync(watcherScript))
1745
1915
  return;
1746
- spawnSync(process.execPath, [watcherScript, '--once', '--cwd', cwd], {
1916
+ spawnSync(process.execPath, [watcherScript, "--once", "--cwd", cwd], {
1747
1917
  cwd,
1748
- stdio: 'ignore',
1918
+ stdio: "ignore",
1749
1919
  timeout: 3000,
1750
1920
  env: {
1751
1921
  ...process.env,
1752
- OMX_HOOK_DERIVED_SIGNALS: '1',
1922
+ OMX_HOOK_DERIVED_SIGNALS: "1",
1753
1923
  },
1754
1924
  });
1755
1925
  }
1756
1926
  async function cancelModes() {
1757
- const { writeFile, readFile } = await import('fs/promises');
1927
+ const { writeFile, readFile } = await import("fs/promises");
1758
1928
  const cwd = process.cwd();
1759
1929
  const nowIso = new Date().toISOString();
1760
1930
  try {
1761
1931
  const refs = await listModeStateFilesWithScopePreference(cwd);
1762
1932
  const states = new Map();
1763
1933
  for (const ref of refs) {
1764
- const content = await readFile(ref.path, 'utf-8');
1934
+ const content = await readFile(ref.path, "utf-8");
1765
1935
  let parsedState;
1766
1936
  try {
1767
1937
  parsedState = JSON.parse(content);
@@ -1778,15 +1948,15 @@ async function cancelModes() {
1778
1948
  }
1779
1949
  const changed = new Set();
1780
1950
  const reported = new Set();
1781
- const cancelMode = (mode, phase = 'cancelled', reportIfWasActive = true) => {
1951
+ const cancelMode = (mode, phase = "cancelled", reportIfWasActive = true) => {
1782
1952
  const entry = states.get(mode);
1783
1953
  if (!entry)
1784
1954
  return;
1785
1955
  const wasActive = entry.state.active === true;
1786
- const needsChange = entry.state.active !== false
1787
- || entry.state.current_phase !== phase
1788
- || typeof entry.state.completed_at !== 'string'
1789
- || String(entry.state.completed_at).trim() === '';
1956
+ const needsChange = entry.state.active !== false ||
1957
+ entry.state.current_phase !== phase ||
1958
+ typeof entry.state.completed_at !== "string" ||
1959
+ String(entry.state.completed_at).trim() === "";
1790
1960
  if (!needsChange)
1791
1961
  return;
1792
1962
  entry.state.active = false;
@@ -1797,30 +1967,32 @@ async function cancelModes() {
1797
1967
  if (reportIfWasActive && wasActive)
1798
1968
  reported.add(mode);
1799
1969
  };
1800
- const ralphLinksUltrawork = (state) => state.linked_ultrawork === true || state.linked_mode === 'ultrawork';
1801
- const team = states.get('team');
1802
- const ralph = states.get('ralph');
1970
+ const ralphLinksUltrawork = (state) => state.linked_ultrawork === true || state.linked_mode === "ultrawork";
1971
+ const team = states.get("team");
1972
+ const ralph = states.get("ralph");
1803
1973
  const hadActiveRalph = !!(ralph && ralph.state.active === true);
1804
- if (team && team.state.active === true && team.state.linked_ralph === true) {
1805
- cancelMode('team', 'cancelled', true);
1974
+ if (team &&
1975
+ team.state.active === true &&
1976
+ team.state.linked_ralph === true) {
1977
+ cancelMode("team", "cancelled", true);
1806
1978
  if (ralph && ralph.state.linked_team === true) {
1807
- cancelMode('ralph', 'cancelled', true);
1808
- ralph.state.linked_team_terminal_phase = 'cancelled';
1979
+ cancelMode("ralph", "cancelled", true);
1980
+ ralph.state.linked_team_terminal_phase = "cancelled";
1809
1981
  ralph.state.linked_team_terminal_at = nowIso;
1810
- changed.add('ralph');
1982
+ changed.add("ralph");
1811
1983
  if (ralphLinksUltrawork(ralph.state))
1812
- cancelMode('ultrawork', 'cancelled', true);
1984
+ cancelMode("ultrawork", "cancelled", true);
1813
1985
  }
1814
1986
  }
1815
1987
  if (ralph && ralph.state.active === true) {
1816
- cancelMode('ralph', 'cancelled', true);
1988
+ cancelMode("ralph", "cancelled", true);
1817
1989
  if (ralphLinksUltrawork(ralph.state))
1818
- cancelMode('ultrawork', 'cancelled', true);
1990
+ cancelMode("ultrawork", "cancelled", true);
1819
1991
  }
1820
1992
  if (!hadActiveRalph) {
1821
1993
  for (const [mode, entry] of states.entries()) {
1822
1994
  if (entry.state.active === true)
1823
- cancelMode(mode, 'cancelled', true);
1995
+ cancelMode(mode, "cancelled", true);
1824
1996
  }
1825
1997
  }
1826
1998
  for (const [mode, entry] of states.entries()) {
@@ -1832,12 +2004,12 @@ async function cancelModes() {
1832
2004
  console.log(`Cancelled: ${mode}`);
1833
2005
  }
1834
2006
  if (reported.size === 0) {
1835
- console.log('No active modes to cancel.');
2007
+ console.log("No active modes to cancel.");
1836
2008
  }
1837
2009
  }
1838
2010
  catch (err) {
1839
2011
  process.stderr.write(`[cli/index] operation failed: ${err}\n`);
1840
- console.log('No active modes to cancel.');
2012
+ console.log("No active modes to cancel.");
1841
2013
  }
1842
2014
  }
1843
2015
  //# sourceMappingURL=index.js.map