oh-my-codex 0.11.11 → 0.11.13

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (342) hide show
  1. package/Cargo.lock +5 -5
  2. package/Cargo.toml +1 -1
  3. package/README.de.md +12 -6
  4. package/README.el.md +223 -0
  5. package/README.es.md +12 -6
  6. package/README.fr.md +11 -5
  7. package/README.it.md +12 -6
  8. package/README.ja.md +12 -6
  9. package/README.ko.md +12 -6
  10. package/README.md +56 -28
  11. package/README.pl.md +216 -0
  12. package/README.pt.md +12 -6
  13. package/README.ru.md +12 -6
  14. package/README.tr.md +12 -6
  15. package/README.vi.md +148 -183
  16. package/README.zh-TW.md +14 -17
  17. package/README.zh.md +12 -6
  18. package/crates/omx-runtime-core/src/engine.rs +122 -4
  19. package/crates/omx-runtime-core/src/lib.rs +17 -0
  20. package/dist/autoresearch/contracts.d.ts.map +1 -1
  21. package/dist/autoresearch/contracts.js +1 -0
  22. package/dist/autoresearch/contracts.js.map +1 -1
  23. package/dist/autoresearch/runtime.d.ts.map +1 -1
  24. package/dist/autoresearch/runtime.js +7 -1
  25. package/dist/autoresearch/runtime.js.map +1 -1
  26. package/dist/cli/__tests__/agents.test.js +24 -1
  27. package/dist/cli/__tests__/agents.test.js.map +1 -1
  28. package/dist/cli/__tests__/autoresearch.test.js +11 -0
  29. package/dist/cli/__tests__/autoresearch.test.js.map +1 -1
  30. package/dist/cli/__tests__/cleanup.test.js +117 -4
  31. package/dist/cli/__tests__/cleanup.test.js.map +1 -1
  32. package/dist/cli/__tests__/doctor-warning-copy.test.js +33 -3
  33. package/dist/cli/__tests__/doctor-warning-copy.test.js.map +1 -1
  34. package/dist/cli/__tests__/error-handling-warnings.test.js +13 -0
  35. package/dist/cli/__tests__/error-handling-warnings.test.js.map +1 -1
  36. package/dist/cli/__tests__/exec.test.js +6 -0
  37. package/dist/cli/__tests__/exec.test.js.map +1 -1
  38. package/dist/cli/__tests__/index.test.js +101 -1
  39. package/dist/cli/__tests__/index.test.js.map +1 -1
  40. package/dist/cli/__tests__/launch-fallback.test.js +3 -0
  41. package/dist/cli/__tests__/launch-fallback.test.js.map +1 -1
  42. package/dist/cli/__tests__/package-bin-contract.test.js +10 -0
  43. package/dist/cli/__tests__/package-bin-contract.test.js.map +1 -1
  44. package/dist/cli/__tests__/packaged-script-resolution.test.js +4 -3
  45. package/dist/cli/__tests__/packaged-script-resolution.test.js.map +1 -1
  46. package/dist/cli/__tests__/resume.test.js +6 -0
  47. package/dist/cli/__tests__/resume.test.js.map +1 -1
  48. package/dist/cli/__tests__/setup-refresh.test.js +29 -12
  49. package/dist/cli/__tests__/setup-refresh.test.js.map +1 -1
  50. package/dist/cli/__tests__/setup-scope.test.js +1 -1
  51. package/dist/cli/__tests__/setup-scope.test.js.map +1 -1
  52. package/dist/cli/__tests__/star-prompt.test.js +16 -0
  53. package/dist/cli/__tests__/star-prompt.test.js.map +1 -1
  54. package/dist/cli/__tests__/uninstall.test.js +112 -1
  55. package/dist/cli/__tests__/uninstall.test.js.map +1 -1
  56. package/dist/cli/__tests__/windows-popup-loop-contract.test.d.ts +2 -0
  57. package/dist/cli/__tests__/windows-popup-loop-contract.test.d.ts.map +1 -0
  58. package/dist/cli/__tests__/windows-popup-loop-contract.test.js +30 -0
  59. package/dist/cli/__tests__/windows-popup-loop-contract.test.js.map +1 -0
  60. package/dist/cli/agents.d.ts.map +1 -1
  61. package/dist/cli/agents.js +9 -3
  62. package/dist/cli/agents.js.map +1 -1
  63. package/dist/cli/autoresearch-guided.d.ts.map +1 -1
  64. package/dist/cli/autoresearch-guided.js +9 -3
  65. package/dist/cli/autoresearch-guided.js.map +1 -1
  66. package/dist/cli/autoresearch.d.ts.map +1 -1
  67. package/dist/cli/autoresearch.js +8 -2
  68. package/dist/cli/autoresearch.js.map +1 -1
  69. package/dist/cli/cleanup.d.ts +2 -0
  70. package/dist/cli/cleanup.d.ts.map +1 -1
  71. package/dist/cli/cleanup.js +27 -1
  72. package/dist/cli/cleanup.js.map +1 -1
  73. package/dist/cli/doctor.js +7 -0
  74. package/dist/cli/doctor.js.map +1 -1
  75. package/dist/cli/index.d.ts +9 -1
  76. package/dist/cli/index.d.ts.map +1 -1
  77. package/dist/cli/index.js +171 -55
  78. package/dist/cli/index.js.map +1 -1
  79. package/dist/cli/setup.d.ts.map +1 -1
  80. package/dist/cli/setup.js +18 -15
  81. package/dist/cli/setup.js.map +1 -1
  82. package/dist/cli/star-prompt.d.ts.map +1 -1
  83. package/dist/cli/star-prompt.js +2 -0
  84. package/dist/cli/star-prompt.js.map +1 -1
  85. package/dist/cli/team.d.ts.map +1 -1
  86. package/dist/cli/team.js +5 -1
  87. package/dist/cli/team.js.map +1 -1
  88. package/dist/cli/tmux-hook.d.ts.map +1 -1
  89. package/dist/cli/tmux-hook.js +4 -1
  90. package/dist/cli/tmux-hook.js.map +1 -1
  91. package/dist/cli/uninstall.d.ts.map +1 -1
  92. package/dist/cli/uninstall.js +26 -0
  93. package/dist/cli/uninstall.js.map +1 -1
  94. package/dist/cli/update.d.ts.map +1 -1
  95. package/dist/cli/update.js +1 -0
  96. package/dist/cli/update.js.map +1 -1
  97. package/dist/compat/__tests__/rust-runtime-compat.test.js +84 -1
  98. package/dist/compat/__tests__/rust-runtime-compat.test.js.map +1 -1
  99. package/dist/config/__tests__/generator-idempotent.test.js +4 -4
  100. package/dist/config/__tests__/generator-idempotent.test.js.map +1 -1
  101. package/dist/config/__tests__/mcp-registry.test.js +13 -16
  102. package/dist/config/__tests__/mcp-registry.test.js.map +1 -1
  103. package/dist/config/mcp-registry.d.ts +1 -0
  104. package/dist/config/mcp-registry.d.ts.map +1 -1
  105. package/dist/config/mcp-registry.js +4 -4
  106. package/dist/config/mcp-registry.js.map +1 -1
  107. package/dist/config/models.d.ts +1 -0
  108. package/dist/config/models.d.ts.map +1 -1
  109. package/dist/config/models.js +39 -1
  110. package/dist/config/models.js.map +1 -1
  111. package/dist/hooks/__tests__/keyword-detector.test.js +12 -1
  112. package/dist/hooks/__tests__/keyword-detector.test.js.map +1 -1
  113. package/dist/hooks/__tests__/notify-fallback-watcher.test.js +554 -18
  114. package/dist/hooks/__tests__/notify-fallback-watcher.test.js.map +1 -1
  115. package/dist/hooks/__tests__/notify-hook-auto-nudge.test.js +347 -16
  116. package/dist/hooks/__tests__/notify-hook-auto-nudge.test.js.map +1 -1
  117. package/dist/hooks/__tests__/notify-hook-modules.test.js +5 -0
  118. package/dist/hooks/__tests__/notify-hook-modules.test.js.map +1 -1
  119. package/dist/hooks/__tests__/notify-hook-ralph-resume.test.d.ts +2 -0
  120. package/dist/hooks/__tests__/notify-hook-ralph-resume.test.d.ts.map +1 -0
  121. package/dist/hooks/__tests__/notify-hook-ralph-resume.test.js +597 -0
  122. package/dist/hooks/__tests__/notify-hook-ralph-resume.test.js.map +1 -0
  123. package/dist/hooks/__tests__/notify-hook-regression-205.test.js +19 -1
  124. package/dist/hooks/__tests__/notify-hook-regression-205.test.js.map +1 -1
  125. package/dist/hooks/__tests__/notify-hook-session-scope.test.js +73 -53
  126. package/dist/hooks/__tests__/notify-hook-session-scope.test.js.map +1 -1
  127. package/dist/hooks/__tests__/notify-hook-team-dispatch.test.js +193 -2
  128. package/dist/hooks/__tests__/notify-hook-team-dispatch.test.js.map +1 -1
  129. package/dist/hooks/__tests__/notify-hook-team-leader-nudge.test.js +183 -0
  130. package/dist/hooks/__tests__/notify-hook-team-leader-nudge.test.js.map +1 -1
  131. package/dist/hooks/__tests__/notify-hook-tmux-heal.test.js +255 -97
  132. package/dist/hooks/__tests__/notify-hook-tmux-heal.test.js.map +1 -1
  133. package/dist/hooks/__tests__/notify-hook-tmux-scrollback.test.js +0 -0
  134. package/dist/hooks/__tests__/notify-hook-tmux-scrollback.test.js.map +1 -1
  135. package/dist/hooks/__tests__/notify-hook-worker-idle.test.js +46 -0
  136. package/dist/hooks/__tests__/notify-hook-worker-idle.test.js.map +1 -1
  137. package/dist/hooks/__tests__/prompt-team-routing.test.js +34 -0
  138. package/dist/hooks/__tests__/prompt-team-routing.test.js.map +1 -1
  139. package/dist/hooks/__tests__/tmux-hook-engine.test.js +32 -1
  140. package/dist/hooks/__tests__/tmux-hook-engine.test.js.map +1 -1
  141. package/dist/hooks/code-simplifier/index.d.ts.map +1 -1
  142. package/dist/hooks/code-simplifier/index.js +1 -0
  143. package/dist/hooks/code-simplifier/index.js.map +1 -1
  144. package/dist/hooks/codebase-map.d.ts.map +1 -1
  145. package/dist/hooks/codebase-map.js +1 -0
  146. package/dist/hooks/codebase-map.js.map +1 -1
  147. package/dist/hooks/extensibility/sdk/tmux.d.ts.map +1 -1
  148. package/dist/hooks/extensibility/sdk/tmux.js +3 -1
  149. package/dist/hooks/extensibility/sdk/tmux.js.map +1 -1
  150. package/dist/hooks/keyword-detector.d.ts +1 -0
  151. package/dist/hooks/keyword-detector.d.ts.map +1 -1
  152. package/dist/hooks/keyword-detector.js +48 -0
  153. package/dist/hooks/keyword-detector.js.map +1 -1
  154. package/dist/hooks/prompt-guidance-contract.d.ts.map +1 -1
  155. package/dist/hooks/prompt-guidance-contract.js +6 -0
  156. package/dist/hooks/prompt-guidance-contract.js.map +1 -1
  157. package/dist/hooks/session.d.ts.map +1 -1
  158. package/dist/hooks/session.js +1 -0
  159. package/dist/hooks/session.js.map +1 -1
  160. package/dist/hud/__tests__/state.test.js +70 -1
  161. package/dist/hud/__tests__/state.test.js.map +1 -1
  162. package/dist/hud/authority.d.ts.map +1 -1
  163. package/dist/hud/authority.js +1 -0
  164. package/dist/hud/authority.js.map +1 -1
  165. package/dist/hud/state.d.ts.map +1 -1
  166. package/dist/hud/state.js +52 -0
  167. package/dist/hud/state.js.map +1 -1
  168. package/dist/mcp/state-server.d.ts.map +1 -1
  169. package/dist/mcp/state-server.js +5 -0
  170. package/dist/mcp/state-server.js.map +1 -1
  171. package/dist/modes/__tests__/base-session-scope.test.js +46 -0
  172. package/dist/modes/__tests__/base-session-scope.test.js.map +1 -1
  173. package/dist/modes/base.d.ts.map +1 -1
  174. package/dist/modes/base.js +4 -0
  175. package/dist/modes/base.js.map +1 -1
  176. package/dist/notifications/__tests__/custom-alias-enablement.test.d.ts +2 -0
  177. package/dist/notifications/__tests__/custom-alias-enablement.test.d.ts.map +1 -0
  178. package/dist/notifications/__tests__/custom-alias-enablement.test.js +84 -0
  179. package/dist/notifications/__tests__/custom-alias-enablement.test.js.map +1 -0
  180. package/dist/notifications/__tests__/idle-cooldown.test.js +55 -0
  181. package/dist/notifications/__tests__/idle-cooldown.test.js.map +1 -1
  182. package/dist/notifications/idle-cooldown.d.ts +8 -6
  183. package/dist/notifications/idle-cooldown.d.ts.map +1 -1
  184. package/dist/notifications/idle-cooldown.js +53 -22
  185. package/dist/notifications/idle-cooldown.js.map +1 -1
  186. package/dist/notifications/notifier.js +1 -1
  187. package/dist/notifications/notifier.js.map +1 -1
  188. package/dist/notifications/reply-listener.d.ts.map +1 -1
  189. package/dist/notifications/reply-listener.js +1 -0
  190. package/dist/notifications/reply-listener.js.map +1 -1
  191. package/dist/notifications/tmux.d.ts.map +1 -1
  192. package/dist/notifications/tmux.js +4 -0
  193. package/dist/notifications/tmux.js.map +1 -1
  194. package/dist/openclaw/config.js +2 -2
  195. package/dist/openclaw/config.js.map +1 -1
  196. package/dist/runtime/bridge.d.ts +2 -0
  197. package/dist/runtime/bridge.d.ts.map +1 -1
  198. package/dist/runtime/bridge.js +8 -0
  199. package/dist/runtime/bridge.js.map +1 -1
  200. package/dist/scripts/notify-fallback-watcher.js +103 -53
  201. package/dist/scripts/notify-fallback-watcher.js.map +1 -1
  202. package/dist/scripts/notify-hook/auto-nudge.d.ts +2 -1
  203. package/dist/scripts/notify-hook/auto-nudge.d.ts.map +1 -1
  204. package/dist/scripts/notify-hook/auto-nudge.js +90 -104
  205. package/dist/scripts/notify-hook/auto-nudge.js.map +1 -1
  206. package/dist/scripts/notify-hook/managed-tmux.d.ts +19 -0
  207. package/dist/scripts/notify-hook/managed-tmux.d.ts.map +1 -0
  208. package/dist/scripts/notify-hook/managed-tmux.js +320 -0
  209. package/dist/scripts/notify-hook/managed-tmux.js.map +1 -0
  210. package/dist/scripts/notify-hook/operational-events.d.ts.map +1 -1
  211. package/dist/scripts/notify-hook/operational-events.js +2 -0
  212. package/dist/scripts/notify-hook/operational-events.js.map +1 -1
  213. package/dist/scripts/notify-hook/ralph-session-resume.d.ts +22 -0
  214. package/dist/scripts/notify-hook/ralph-session-resume.d.ts.map +1 -0
  215. package/dist/scripts/notify-hook/ralph-session-resume.js +277 -0
  216. package/dist/scripts/notify-hook/ralph-session-resume.js.map +1 -0
  217. package/dist/scripts/notify-hook/state-io.d.ts +1 -1
  218. package/dist/scripts/notify-hook/state-io.d.ts.map +1 -1
  219. package/dist/scripts/notify-hook/state-io.js +2 -10
  220. package/dist/scripts/notify-hook/state-io.js.map +1 -1
  221. package/dist/scripts/notify-hook/team-dispatch.d.ts +1 -1
  222. package/dist/scripts/notify-hook/team-dispatch.d.ts.map +1 -1
  223. package/dist/scripts/notify-hook/team-dispatch.js +123 -72
  224. package/dist/scripts/notify-hook/team-dispatch.js.map +1 -1
  225. package/dist/scripts/notify-hook/team-leader-nudge.d.ts +2 -1
  226. package/dist/scripts/notify-hook/team-leader-nudge.d.ts.map +1 -1
  227. package/dist/scripts/notify-hook/team-leader-nudge.js +13 -5
  228. package/dist/scripts/notify-hook/team-leader-nudge.js.map +1 -1
  229. package/dist/scripts/notify-hook/team-tmux-guard.d.ts.map +1 -1
  230. package/dist/scripts/notify-hook/team-tmux-guard.js +1 -19
  231. package/dist/scripts/notify-hook/team-tmux-guard.js.map +1 -1
  232. package/dist/scripts/notify-hook/team-worker.js +4 -4
  233. package/dist/scripts/notify-hook/team-worker.js.map +1 -1
  234. package/dist/scripts/notify-hook/tmux-injection.d.ts +1 -1
  235. package/dist/scripts/notify-hook/tmux-injection.d.ts.map +1 -1
  236. package/dist/scripts/notify-hook/tmux-injection.js +102 -35
  237. package/dist/scripts/notify-hook/tmux-injection.js.map +1 -1
  238. package/dist/scripts/notify-hook.js +144 -20
  239. package/dist/scripts/notify-hook.js.map +1 -1
  240. package/dist/scripts/run-provider-advisor.js +2 -0
  241. package/dist/scripts/run-provider-advisor.js.map +1 -1
  242. package/dist/scripts/run-test-files.d.ts +2 -0
  243. package/dist/scripts/run-test-files.d.ts.map +1 -0
  244. package/dist/scripts/run-test-files.js +41 -0
  245. package/dist/scripts/run-test-files.js.map +1 -0
  246. package/dist/scripts/tmux-hook-engine.d.ts +2 -0
  247. package/dist/scripts/tmux-hook-engine.d.ts.map +1 -1
  248. package/dist/scripts/tmux-hook-engine.js +15 -0
  249. package/dist/scripts/tmux-hook-engine.js.map +1 -1
  250. package/dist/team/__tests__/api-interop.test.js +136 -4
  251. package/dist/team/__tests__/api-interop.test.js.map +1 -1
  252. package/dist/team/__tests__/leader-activity.test.js +107 -2
  253. package/dist/team/__tests__/leader-activity.test.js.map +1 -1
  254. package/dist/team/__tests__/runtime-cli.test.js +32 -0
  255. package/dist/team/__tests__/runtime-cli.test.js.map +1 -1
  256. package/dist/team/__tests__/runtime.test.js +148 -0
  257. package/dist/team/__tests__/runtime.test.js.map +1 -1
  258. package/dist/team/__tests__/shutdown-fallback.test.js +13 -0
  259. package/dist/team/__tests__/shutdown-fallback.test.js.map +1 -1
  260. package/dist/team/__tests__/state-root.test.js +11 -1
  261. package/dist/team/__tests__/state-root.test.js.map +1 -1
  262. package/dist/team/__tests__/state.test.js +237 -0
  263. package/dist/team/__tests__/state.test.js.map +1 -1
  264. package/dist/team/__tests__/tmux-session.test.js +521 -2
  265. package/dist/team/__tests__/tmux-session.test.js.map +1 -1
  266. package/dist/team/api-interop.d.ts.map +1 -1
  267. package/dist/team/api-interop.js +41 -31
  268. package/dist/team/api-interop.js.map +1 -1
  269. package/dist/team/commit-hygiene.d.ts +60 -0
  270. package/dist/team/commit-hygiene.d.ts.map +1 -0
  271. package/dist/team/commit-hygiene.js +232 -0
  272. package/dist/team/commit-hygiene.js.map +1 -0
  273. package/dist/team/leader-activity.d.ts.map +1 -1
  274. package/dist/team/leader-activity.js +56 -4
  275. package/dist/team/leader-activity.js.map +1 -1
  276. package/dist/team/runtime-cli.d.ts +9 -1
  277. package/dist/team/runtime-cli.d.ts.map +1 -1
  278. package/dist/team/runtime-cli.js +15 -6
  279. package/dist/team/runtime-cli.js.map +1 -1
  280. package/dist/team/runtime.d.ts +7 -2
  281. package/dist/team/runtime.d.ts.map +1 -1
  282. package/dist/team/runtime.js +392 -171
  283. package/dist/team/runtime.js.map +1 -1
  284. package/dist/team/scaling.d.ts.map +1 -1
  285. package/dist/team/scaling.js +6 -2
  286. package/dist/team/scaling.js.map +1 -1
  287. package/dist/team/state/dispatch.d.ts +2 -0
  288. package/dist/team/state/dispatch.d.ts.map +1 -1
  289. package/dist/team/state/dispatch.js +86 -40
  290. package/dist/team/state/dispatch.js.map +1 -1
  291. package/dist/team/state/mailbox.d.ts +3 -0
  292. package/dist/team/state/mailbox.d.ts.map +1 -1
  293. package/dist/team/state/mailbox.js +93 -19
  294. package/dist/team/state/mailbox.js.map +1 -1
  295. package/dist/team/state-root.d.ts +1 -1
  296. package/dist/team/state-root.d.ts.map +1 -1
  297. package/dist/team/state-root.js +8 -3
  298. package/dist/team/state-root.js.map +1 -1
  299. package/dist/team/state.d.ts.map +1 -1
  300. package/dist/team/state.js +96 -2
  301. package/dist/team/state.js.map +1 -1
  302. package/dist/team/tmux-session.d.ts.map +1 -1
  303. package/dist/team/tmux-session.js +81 -29
  304. package/dist/team/tmux-session.js.map +1 -1
  305. package/dist/team/worker-bootstrap.d.ts.map +1 -1
  306. package/dist/team/worker-bootstrap.js +4 -0
  307. package/dist/team/worker-bootstrap.js.map +1 -1
  308. package/dist/team/worktree.d.ts.map +1 -1
  309. package/dist/team/worktree.js +9 -0
  310. package/dist/team/worktree.js.map +1 -1
  311. package/dist/utils/__tests__/paths.test.js +98 -11
  312. package/dist/utils/__tests__/paths.test.js.map +1 -1
  313. package/dist/utils/__tests__/platform-command.test.js +101 -2
  314. package/dist/utils/__tests__/platform-command.test.js.map +1 -1
  315. package/dist/utils/git-layout.d.ts +8 -0
  316. package/dist/utils/git-layout.d.ts.map +1 -0
  317. package/dist/utils/git-layout.js +58 -0
  318. package/dist/utils/git-layout.js.map +1 -0
  319. package/dist/utils/paths.d.ts +3 -0
  320. package/dist/utils/paths.d.ts.map +1 -1
  321. package/dist/utils/paths.js +14 -4
  322. package/dist/utils/paths.js.map +1 -1
  323. package/dist/utils/platform-command.d.ts.map +1 -1
  324. package/dist/utils/platform-command.js +35 -3
  325. package/dist/utils/platform-command.js.map +1 -1
  326. package/package.json +9 -5
  327. package/src/scripts/notify-fallback-watcher.ts +103 -53
  328. package/src/scripts/notify-hook/auto-nudge.ts +97 -103
  329. package/src/scripts/notify-hook/managed-tmux.ts +324 -0
  330. package/src/scripts/notify-hook/operational-events.ts +2 -0
  331. package/src/scripts/notify-hook/ralph-session-resume.ts +337 -0
  332. package/src/scripts/notify-hook/state-io.ts +2 -10
  333. package/src/scripts/notify-hook/team-dispatch.ts +131 -66
  334. package/src/scripts/notify-hook/team-leader-nudge.ts +19 -5
  335. package/src/scripts/notify-hook/team-tmux-guard.ts +0 -20
  336. package/src/scripts/notify-hook/team-worker.ts +4 -4
  337. package/src/scripts/notify-hook/tmux-injection.ts +103 -33
  338. package/src/scripts/notify-hook.ts +150 -21
  339. package/src/scripts/run-provider-advisor.ts +4 -2
  340. package/src/scripts/run-test-files.ts +48 -0
  341. package/src/scripts/tmux-hook-engine.ts +16 -0
  342. package/templates/AGENTS.md +51 -43
@@ -0,0 +1,337 @@
1
+ import { existsSync } from 'fs';
2
+ import { mkdir, readFile, readdir, rename, rm, stat, writeFile } from 'fs/promises';
3
+ import { dirname, join } from 'path';
4
+ import { captureTmuxPaneFromEnv } from '../../state/mode-state-context.js';
5
+ import { resolveCodexPane } from '../tmux-hook-engine.js';
6
+ import { safeString } from './utils.js';
7
+
8
+ const SESSION_ID_PATTERN = /^[A-Za-z0-9_-]{1,64}$/;
9
+ const RALPH_TERMINAL_PHASES = new Set(['complete', 'failed', 'cancelled']);
10
+ const RALPH_RESUME_LOCK_STALE_MS = 10_000;
11
+ const RALPH_RESUME_LOCK_TIMEOUT_MS = 5_000;
12
+ const RALPH_RESUME_LOCK_RETRY_MS = 25;
13
+
14
+ interface RalphSessionResumeHooks {
15
+ afterLockAcquired?: () => Promise<void> | void;
16
+ afterTargetWrite?: () => Promise<void> | void;
17
+ }
18
+
19
+ interface RalphSessionResumeParams {
20
+ stateDir: string;
21
+ payloadSessionId: string;
22
+ payloadThreadId?: string;
23
+ env?: NodeJS.ProcessEnv;
24
+ hooks?: RalphSessionResumeHooks;
25
+ }
26
+
27
+ export interface RalphSessionResumeResult {
28
+ currentOmxSessionId: string;
29
+ resumed: boolean;
30
+ updatedCurrentOwner: boolean;
31
+ reason: string;
32
+ sourcePath?: string;
33
+ targetPath?: string;
34
+ }
35
+
36
+ interface RalphStateCandidate {
37
+ sessionId: string;
38
+ path: string;
39
+ state: Record<string, unknown>;
40
+ }
41
+
42
+ function lockOwnerToken(): string {
43
+ return `${process.pid}.${Date.now()}.${Math.random().toString(16).slice(2)}`;
44
+ }
45
+
46
+ async function sleep(ms: number): Promise<void> {
47
+ await new Promise((resolve) => setTimeout(resolve, ms));
48
+ }
49
+
50
+ async function maybeRecoverStaleLock(lockDir: string): Promise<boolean> {
51
+ try {
52
+ const info = await stat(lockDir);
53
+ if (Date.now() - info.mtimeMs <= RALPH_RESUME_LOCK_STALE_MS) {
54
+ return false;
55
+ }
56
+ await rm(lockDir, { recursive: true, force: true });
57
+ return true;
58
+ } catch {
59
+ return false;
60
+ }
61
+ }
62
+
63
+ async function withRalphResumeLock<T>(
64
+ stateDir: string,
65
+ fn: () => Promise<T>,
66
+ ): Promise<T | null> {
67
+ const lockDir = join(stateDir, '.lock.ralph-session-resume');
68
+ const ownerPath = join(lockDir, 'owner');
69
+ const ownerToken = lockOwnerToken();
70
+ const deadline = Date.now() + RALPH_RESUME_LOCK_TIMEOUT_MS;
71
+ await mkdir(dirname(lockDir), { recursive: true }).catch(() => {});
72
+
73
+ while (true) {
74
+ try {
75
+ await mkdir(lockDir, { recursive: false });
76
+ try {
77
+ await writeFile(ownerPath, ownerToken, 'utf8');
78
+ } catch (error) {
79
+ await rm(lockDir, { recursive: true, force: true }).catch(() => {});
80
+ throw error;
81
+ }
82
+ break;
83
+ } catch (error) {
84
+ const err = error as NodeJS.ErrnoException;
85
+ if (err.code !== 'EEXIST') throw error;
86
+ if (await maybeRecoverStaleLock(lockDir)) continue;
87
+ if (Date.now() > deadline) return null;
88
+ await sleep(RALPH_RESUME_LOCK_RETRY_MS);
89
+ }
90
+ }
91
+
92
+ try {
93
+ return await fn();
94
+ } finally {
95
+ try {
96
+ const currentOwner = await readFile(ownerPath, 'utf8');
97
+ if (currentOwner.trim() === ownerToken) {
98
+ await rm(lockDir, { recursive: true, force: true });
99
+ }
100
+ } catch {
101
+ // Lock may already be gone after stale recovery or process interruption.
102
+ }
103
+ }
104
+ }
105
+
106
+ async function readJson(path: string): Promise<Record<string, unknown> | null> {
107
+ try {
108
+ return JSON.parse(await readFile(path, 'utf-8')) as Record<string, unknown>;
109
+ } catch {
110
+ return null;
111
+ }
112
+ }
113
+
114
+ async function writeJsonAtomic(path: string, value: unknown): Promise<void> {
115
+ await mkdir(dirname(path), { recursive: true }).catch(() => {});
116
+ const tempPath = `${path}.tmp-${process.pid}-${Date.now()}-${Math.random().toString(16).slice(2)}`;
117
+ await writeFile(tempPath, JSON.stringify(value, null, 2));
118
+ await rename(tempPath, path);
119
+ }
120
+
121
+ function isTerminalRalphPhase(value: unknown): boolean {
122
+ return RALPH_TERMINAL_PHASES.has(safeString(value).trim().toLowerCase());
123
+ }
124
+
125
+ function isActiveRalphCandidate(state: Record<string, unknown> | null): state is Record<string, unknown> {
126
+ if (!state || typeof state !== 'object') return false;
127
+ return state.active === true && !isTerminalRalphPhase(state.current_phase);
128
+ }
129
+
130
+ async function readCurrentOmxSessionId(stateDir: string): Promise<string> {
131
+ const session = await readJson(join(stateDir, 'session.json'));
132
+ const sessionId = safeString(session?.session_id).trim();
133
+ return SESSION_ID_PATTERN.test(sessionId) ? sessionId : '';
134
+ }
135
+
136
+ function resolveResumePane(env: NodeJS.ProcessEnv = process.env): string {
137
+ const injectedPane = captureTmuxPaneFromEnv(env);
138
+ if (env !== process.env && injectedPane) return injectedPane;
139
+ return resolveCodexPane() || injectedPane || '';
140
+ }
141
+
142
+ function bindCurrentPane(state: Record<string, unknown>, nowIso: string, env: NodeJS.ProcessEnv = process.env): Record<string, unknown> {
143
+ const paneId = resolveResumePane(env);
144
+ if (!paneId) return state;
145
+
146
+ return {
147
+ ...state,
148
+ tmux_pane_id: paneId,
149
+ tmux_pane_set_at: nowIso,
150
+ };
151
+ }
152
+
153
+ async function scanMatchingRalphCandidates(
154
+ stateDir: string,
155
+ currentOmxSessionId: string,
156
+ payloadSessionId: string,
157
+ payloadThreadId: string,
158
+ ): Promise<RalphStateCandidate[]> {
159
+ const sessionsRoot = join(stateDir, 'sessions');
160
+ if (!existsSync(sessionsRoot)) return [];
161
+
162
+ const entries = await readdir(sessionsRoot, { withFileTypes: true }).catch(() => []);
163
+ const matches: RalphStateCandidate[] = [];
164
+ for (const entry of entries) {
165
+ if (!entry.isDirectory() || !SESSION_ID_PATTERN.test(entry.name) || entry.name === currentOmxSessionId) continue;
166
+ const path = join(sessionsRoot, entry.name, 'ralph-state.json');
167
+ if (!existsSync(path)) continue;
168
+ const state = await readJson(path);
169
+ if (!isActiveRalphCandidate(state)) continue;
170
+ const ownerSessionId = safeString(state.owner_codex_session_id).trim();
171
+ const ownerThreadId = safeString(state.owner_codex_thread_id).trim();
172
+ if (ownerSessionId) {
173
+ if (!payloadSessionId || ownerSessionId !== payloadSessionId) continue;
174
+ } else if (!payloadThreadId || !ownerThreadId || ownerThreadId !== payloadThreadId) {
175
+ continue;
176
+ }
177
+ matches.push({
178
+ sessionId: entry.name,
179
+ path,
180
+ state,
181
+ });
182
+ }
183
+ return matches;
184
+ }
185
+
186
+ export async function reconcileRalphSessionResume({
187
+ stateDir,
188
+ payloadSessionId,
189
+ payloadThreadId = '',
190
+ env = process.env,
191
+ hooks,
192
+ }: RalphSessionResumeParams): Promise<RalphSessionResumeResult> {
193
+ const lockedResult = await withRalphResumeLock(stateDir, async () => {
194
+ await hooks?.afterLockAcquired?.();
195
+
196
+ const currentOmxSessionId = await readCurrentOmxSessionId(stateDir);
197
+ if (!currentOmxSessionId) {
198
+ return {
199
+ currentOmxSessionId: '',
200
+ resumed: false,
201
+ updatedCurrentOwner: false,
202
+ reason: 'current_omx_session_missing',
203
+ };
204
+ }
205
+
206
+ const currentSessionDir = join(stateDir, 'sessions', currentOmxSessionId);
207
+ const currentRalphPath = join(currentSessionDir, 'ralph-state.json');
208
+ const currentRalphExists = existsSync(currentRalphPath);
209
+ const currentRalphState = currentRalphExists
210
+ ? await readJson(currentRalphPath)
211
+ : null;
212
+ const nowIso = new Date().toISOString();
213
+
214
+ if (currentRalphState && currentRalphState.active === true) {
215
+ let changed = false;
216
+ const updated: Record<string, unknown> = { ...currentRalphState };
217
+ if (safeString(updated.owner_omx_session_id).trim() !== currentOmxSessionId) {
218
+ updated.owner_omx_session_id = currentOmxSessionId;
219
+ changed = true;
220
+ }
221
+ if (payloadSessionId && !safeString(updated.owner_codex_session_id).trim()) {
222
+ updated.owner_codex_session_id = payloadSessionId;
223
+ changed = true;
224
+ }
225
+ if (
226
+ typeof updated.owner_codex_thread_id === 'string'
227
+ && safeString(updated.owner_codex_session_id).trim()
228
+ ) {
229
+ delete updated.owner_codex_thread_id;
230
+ changed = true;
231
+ }
232
+ const currentPaneId = resolveResumePane(env);
233
+ const currentStatePaneId = safeString(updated.tmux_pane_id).trim();
234
+ if (currentPaneId && currentPaneId !== currentStatePaneId) {
235
+ Object.assign(updated, bindCurrentPane(updated, nowIso, env));
236
+ changed = true;
237
+ }
238
+ if (changed) {
239
+ await writeJsonAtomic(currentRalphPath, updated);
240
+ }
241
+ return {
242
+ currentOmxSessionId,
243
+ resumed: false,
244
+ updatedCurrentOwner: changed,
245
+ reason: 'current_ralph_active',
246
+ targetPath: currentRalphPath,
247
+ };
248
+ }
249
+
250
+ if (currentRalphExists) {
251
+ return {
252
+ currentOmxSessionId,
253
+ resumed: false,
254
+ updatedCurrentOwner: false,
255
+ reason: currentRalphState ? 'current_ralph_present' : 'current_ralph_unreadable',
256
+ targetPath: currentRalphPath,
257
+ };
258
+ }
259
+
260
+ const normalizedPayloadSessionId = safeString(payloadSessionId).trim();
261
+ const normalizedPayloadThreadId = safeString(payloadThreadId).trim();
262
+ if (!normalizedPayloadSessionId && !normalizedPayloadThreadId) {
263
+ return {
264
+ currentOmxSessionId,
265
+ resumed: false,
266
+ updatedCurrentOwner: false,
267
+ reason: 'payload_codex_identity_missing',
268
+ };
269
+ }
270
+
271
+ const candidates = await scanMatchingRalphCandidates(
272
+ stateDir,
273
+ currentOmxSessionId,
274
+ normalizedPayloadSessionId,
275
+ normalizedPayloadThreadId,
276
+ );
277
+ if (candidates.length !== 1) {
278
+ return {
279
+ currentOmxSessionId,
280
+ resumed: false,
281
+ updatedCurrentOwner: false,
282
+ reason: candidates.length === 0 ? 'no_matching_prior_ralph' : 'multiple_matching_prior_ralphs',
283
+ };
284
+ }
285
+
286
+ const source = candidates[0];
287
+ await mkdir(currentSessionDir, { recursive: true });
288
+
289
+ const nextState = bindCurrentPane({
290
+ ...source.state,
291
+ owner_omx_session_id: currentOmxSessionId,
292
+ ...(normalizedPayloadSessionId ? { owner_codex_session_id: normalizedPayloadSessionId } : {}),
293
+ }, nowIso, env);
294
+ if (safeString(nextState.owner_codex_session_id).trim()) {
295
+ delete nextState.owner_codex_thread_id;
296
+ }
297
+ delete nextState.completed_at;
298
+ delete nextState.stop_reason;
299
+
300
+ const previousState: Record<string, unknown> = {
301
+ ...source.state,
302
+ active: false,
303
+ current_phase: 'cancelled',
304
+ completed_at: nowIso,
305
+ stop_reason: 'ownership_transferred',
306
+ };
307
+
308
+ await writeJsonAtomic(currentRalphPath, nextState);
309
+ try {
310
+ await hooks?.afterTargetWrite?.();
311
+ await writeJsonAtomic(source.path, previousState);
312
+ } catch (error) {
313
+ await rm(currentRalphPath, { force: true }).catch(() => {});
314
+ throw error;
315
+ }
316
+
317
+ return {
318
+ currentOmxSessionId,
319
+ resumed: true,
320
+ updatedCurrentOwner: false,
321
+ reason: 'resumed_same_codex_session',
322
+ sourcePath: source.path,
323
+ targetPath: currentRalphPath,
324
+ };
325
+ });
326
+
327
+ if (lockedResult) {
328
+ return lockedResult;
329
+ }
330
+
331
+ return {
332
+ currentOmxSessionId: '',
333
+ resumed: false,
334
+ updatedCurrentOwner: false,
335
+ reason: 'resume_lock_timeout',
336
+ };
337
+ }
@@ -3,7 +3,6 @@
3
3
  */
4
4
 
5
5
  import { readFile, readdir } from 'fs/promises';
6
- import { existsSync } from 'fs';
7
6
  import { join } from 'path';
8
7
  import { asNumber, safeString } from './utils.js';
9
8
 
@@ -17,20 +16,13 @@ export function readJsonIfExists(path: string, fallback: any): Promise<any> {
17
16
  .catch(() => fallback);
18
17
  }
19
18
 
20
- export async function getScopedStateDirsForCurrentSession(baseStateDir: string, payloadSessionId?: any): Promise<string[]> {
21
- const explicitSessionId = safeString(payloadSessionId || '');
22
- if (SESSION_ID_PATTERN.test(explicitSessionId)) {
23
- const sessionDir = join(baseStateDir, 'sessions', explicitSessionId);
24
- return [sessionDir];
25
- }
26
-
19
+ export async function getScopedStateDirsForCurrentSession(baseStateDir: string): Promise<string[]> {
27
20
  const sessionPath = join(baseStateDir, 'session.json');
28
21
  try {
29
22
  const session = JSON.parse(await readFile(sessionPath, 'utf-8'));
30
23
  const sessionId = safeString(session && session.session_id ? session.session_id : '');
31
24
  if (SESSION_ID_PATTERN.test(sessionId)) {
32
- const sessionDir = join(baseStateDir, 'sessions', sessionId);
33
- if (existsSync(sessionDir)) return [sessionDir];
25
+ return [join(baseStateDir, 'sessions', sessionId)];
34
26
  }
35
27
  } catch {
36
28
  // No session file or malformed - fall back to global only