oh-my-codex 0.13.2 → 0.14.1

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 (406) hide show
  1. package/Cargo.lock +5 -5
  2. package/Cargo.toml +1 -1
  3. package/README.md +14 -8
  4. package/crates/omx-explore/src/main.rs +94 -1
  5. package/crates/omx-sparkshell/src/codex_bridge.rs +59 -12
  6. package/crates/omx-sparkshell/tests/execution.rs +48 -0
  7. package/dist/autoresearch/__tests__/skill-validation.test.d.ts +2 -0
  8. package/dist/autoresearch/__tests__/skill-validation.test.d.ts.map +1 -0
  9. package/dist/autoresearch/__tests__/skill-validation.test.js +91 -0
  10. package/dist/autoresearch/__tests__/skill-validation.test.js.map +1 -0
  11. package/dist/autoresearch/skill-validation.d.ts +13 -0
  12. package/dist/autoresearch/skill-validation.d.ts.map +1 -0
  13. package/dist/autoresearch/skill-validation.js +165 -0
  14. package/dist/autoresearch/skill-validation.js.map +1 -0
  15. package/dist/catalog/__tests__/schema.test.js +6 -0
  16. package/dist/catalog/__tests__/schema.test.js.map +1 -1
  17. package/dist/cli/__tests__/autoresearch-guided.test.js +236 -273
  18. package/dist/cli/__tests__/autoresearch-guided.test.js.map +1 -1
  19. package/dist/cli/__tests__/autoresearch.test.js +64 -653
  20. package/dist/cli/__tests__/autoresearch.test.js.map +1 -1
  21. package/dist/cli/__tests__/explore.test.js +33 -1
  22. package/dist/cli/__tests__/explore.test.js.map +1 -1
  23. package/dist/cli/__tests__/index.test.js +18 -2
  24. package/dist/cli/__tests__/index.test.js.map +1 -1
  25. package/dist/cli/__tests__/nested-help-routing.test.js +2 -1
  26. package/dist/cli/__tests__/nested-help-routing.test.js.map +1 -1
  27. package/dist/cli/__tests__/package-bin-contract.test.js +5 -0
  28. package/dist/cli/__tests__/package-bin-contract.test.js.map +1 -1
  29. package/dist/cli/__tests__/question.test.d.ts +2 -0
  30. package/dist/cli/__tests__/question.test.d.ts.map +1 -0
  31. package/dist/cli/__tests__/question.test.js +166 -0
  32. package/dist/cli/__tests__/question.test.js.map +1 -0
  33. package/dist/cli/__tests__/session-search-help.test.js +1 -1
  34. package/dist/cli/__tests__/session-search-help.test.js.map +1 -1
  35. package/dist/cli/__tests__/setup-agents-overwrite.test.js +32 -7
  36. package/dist/cli/__tests__/setup-agents-overwrite.test.js.map +1 -1
  37. package/dist/cli/__tests__/setup-refresh.test.js +8 -6
  38. package/dist/cli/__tests__/setup-refresh.test.js.map +1 -1
  39. package/dist/cli/__tests__/setup-skills-overwrite.test.js +2 -0
  40. package/dist/cli/__tests__/setup-skills-overwrite.test.js.map +1 -1
  41. package/dist/cli/__tests__/sparkshell-cli.test.js +23 -0
  42. package/dist/cli/__tests__/sparkshell-cli.test.js.map +1 -1
  43. package/dist/cli/__tests__/uninstall.test.js +65 -5
  44. package/dist/cli/__tests__/uninstall.test.js.map +1 -1
  45. package/dist/cli/__tests__/update.test.js +360 -26
  46. package/dist/cli/__tests__/update.test.js.map +1 -1
  47. package/dist/cli/autoresearch-guided.d.ts +24 -7
  48. package/dist/cli/autoresearch-guided.d.ts.map +1 -1
  49. package/dist/cli/autoresearch-guided.js +189 -130
  50. package/dist/cli/autoresearch-guided.js.map +1 -1
  51. package/dist/cli/autoresearch.d.ts +3 -2
  52. package/dist/cli/autoresearch.d.ts.map +1 -1
  53. package/dist/cli/autoresearch.js +29 -305
  54. package/dist/cli/autoresearch.js.map +1 -1
  55. package/dist/cli/doctor.d.ts.map +1 -1
  56. package/dist/cli/doctor.js +43 -0
  57. package/dist/cli/doctor.js.map +1 -1
  58. package/dist/cli/explore.d.ts.map +1 -1
  59. package/dist/cli/explore.js +18 -3
  60. package/dist/cli/explore.js.map +1 -1
  61. package/dist/cli/index.d.ts +2 -1
  62. package/dist/cli/index.d.ts.map +1 -1
  63. package/dist/cli/index.js +15 -3
  64. package/dist/cli/index.js.map +1 -1
  65. package/dist/cli/question.d.ts +3 -0
  66. package/dist/cli/question.d.ts.map +1 -0
  67. package/dist/cli/question.js +182 -0
  68. package/dist/cli/question.js.map +1 -0
  69. package/dist/cli/setup.d.ts.map +1 -1
  70. package/dist/cli/setup.js +25 -3
  71. package/dist/cli/setup.js.map +1 -1
  72. package/dist/cli/sparkshell.d.ts.map +1 -1
  73. package/dist/cli/sparkshell.js +11 -1
  74. package/dist/cli/sparkshell.js.map +1 -1
  75. package/dist/cli/team.d.ts.map +1 -1
  76. package/dist/cli/team.js +159 -394
  77. package/dist/cli/team.js.map +1 -1
  78. package/dist/cli/uninstall.d.ts.map +1 -1
  79. package/dist/cli/uninstall.js +3 -1
  80. package/dist/cli/uninstall.js.map +1 -1
  81. package/dist/cli/update.d.ts +37 -9
  82. package/dist/cli/update.d.ts.map +1 -1
  83. package/dist/cli/update.js +204 -26
  84. package/dist/cli/update.js.map +1 -1
  85. package/dist/config/__tests__/generator-idempotent.test.js +51 -14
  86. package/dist/config/__tests__/generator-idempotent.test.js.map +1 -1
  87. package/dist/config/__tests__/generator-notify.test.js +35 -10
  88. package/dist/config/__tests__/generator-notify.test.js.map +1 -1
  89. package/dist/config/generator.d.ts +1 -0
  90. package/dist/config/generator.d.ts.map +1 -1
  91. package/dist/config/generator.js +61 -7
  92. package/dist/config/generator.js.map +1 -1
  93. package/dist/hooks/__tests__/analyze-routing-contract.test.js +22 -13
  94. package/dist/hooks/__tests__/analyze-routing-contract.test.js.map +1 -1
  95. package/dist/hooks/__tests__/anti-slop-workflow.test.js +3 -3
  96. package/dist/hooks/__tests__/anti-slop-workflow.test.js.map +1 -1
  97. package/dist/hooks/__tests__/code-review-skill-contract.test.d.ts +2 -0
  98. package/dist/hooks/__tests__/code-review-skill-contract.test.d.ts.map +1 -0
  99. package/dist/hooks/__tests__/code-review-skill-contract.test.js +56 -0
  100. package/dist/hooks/__tests__/code-review-skill-contract.test.js.map +1 -0
  101. package/dist/hooks/__tests__/debugger-log-recency-contract.test.js +2 -2
  102. package/dist/hooks/__tests__/debugger-log-recency-contract.test.js.map +1 -1
  103. package/dist/hooks/__tests__/deep-interview-contract.test.js +51 -5
  104. package/dist/hooks/__tests__/deep-interview-contract.test.js.map +1 -1
  105. package/dist/hooks/__tests__/explicit-terminal-stop-docs-contract.test.d.ts +2 -0
  106. package/dist/hooks/__tests__/explicit-terminal-stop-docs-contract.test.d.ts.map +1 -0
  107. package/dist/hooks/__tests__/explicit-terminal-stop-docs-contract.test.js +43 -0
  108. package/dist/hooks/__tests__/explicit-terminal-stop-docs-contract.test.js.map +1 -0
  109. package/dist/hooks/__tests__/explicit-terminal-stop-model-docs-contract.test.d.ts +2 -0
  110. package/dist/hooks/__tests__/explicit-terminal-stop-model-docs-contract.test.d.ts.map +1 -0
  111. package/dist/hooks/__tests__/explicit-terminal-stop-model-docs-contract.test.js +38 -0
  112. package/dist/hooks/__tests__/explicit-terminal-stop-model-docs-contract.test.js.map +1 -0
  113. package/dist/hooks/__tests__/explore-sparkshell-guidance-contract.test.js +2 -2
  114. package/dist/hooks/__tests__/explore-sparkshell-guidance-contract.test.js.map +1 -1
  115. package/dist/hooks/__tests__/keyword-detector.test.js +308 -17
  116. package/dist/hooks/__tests__/keyword-detector.test.js.map +1 -1
  117. package/dist/hooks/__tests__/notify-fallback-watcher.test.js +570 -2
  118. package/dist/hooks/__tests__/notify-fallback-watcher.test.js.map +1 -1
  119. package/dist/hooks/__tests__/notify-hook-auto-nudge.test.js +717 -16
  120. package/dist/hooks/__tests__/notify-hook-auto-nudge.test.js.map +1 -1
  121. package/dist/hooks/__tests__/notify-hook-cross-worktree-heartbeat.test.js +25 -0
  122. package/dist/hooks/__tests__/notify-hook-cross-worktree-heartbeat.test.js.map +1 -1
  123. package/dist/hooks/__tests__/notify-hook-managed-tmux.test.js +894 -1
  124. package/dist/hooks/__tests__/notify-hook-managed-tmux.test.js.map +1 -1
  125. package/dist/hooks/__tests__/notify-hook-ralph-resume.test.js +34 -0
  126. package/dist/hooks/__tests__/notify-hook-ralph-resume.test.js.map +1 -1
  127. package/dist/hooks/__tests__/notify-hook-tmux-heal.test.js +132 -0
  128. package/dist/hooks/__tests__/notify-hook-tmux-heal.test.js.map +1 -1
  129. package/dist/hooks/__tests__/prompt-guidance-contract.test.js +22 -4
  130. package/dist/hooks/__tests__/prompt-guidance-contract.test.js.map +1 -1
  131. package/dist/hooks/__tests__/prompt-guidance-fragments.test.js +4 -2
  132. package/dist/hooks/__tests__/prompt-guidance-fragments.test.js.map +1 -1
  133. package/dist/hooks/__tests__/prompt-guidance-test-helpers.d.ts +1 -0
  134. package/dist/hooks/__tests__/prompt-guidance-test-helpers.d.ts.map +1 -1
  135. package/dist/hooks/__tests__/prompt-guidance-test-helpers.js +19 -1
  136. package/dist/hooks/__tests__/prompt-guidance-test-helpers.js.map +1 -1
  137. package/dist/hooks/__tests__/prompt-guidance-wave-two.test.js +28 -0
  138. package/dist/hooks/__tests__/prompt-guidance-wave-two.test.js.map +1 -1
  139. package/dist/hooks/__tests__/prompt-orchestration-boundary.test.js +5 -4
  140. package/dist/hooks/__tests__/prompt-orchestration-boundary.test.js.map +1 -1
  141. package/dist/hooks/__tests__/prompt-team-routing.test.js +2 -2
  142. package/dist/hooks/__tests__/prompt-team-routing.test.js.map +1 -1
  143. package/dist/hooks/__tests__/triage-config.test.d.ts +2 -0
  144. package/dist/hooks/__tests__/triage-config.test.d.ts.map +1 -0
  145. package/dist/hooks/__tests__/triage-config.test.js +211 -0
  146. package/dist/hooks/__tests__/triage-config.test.js.map +1 -0
  147. package/dist/hooks/__tests__/triage-heuristic.test.d.ts +2 -0
  148. package/dist/hooks/__tests__/triage-heuristic.test.d.ts.map +1 -0
  149. package/dist/hooks/__tests__/triage-heuristic.test.js +230 -0
  150. package/dist/hooks/__tests__/triage-heuristic.test.js.map +1 -0
  151. package/dist/hooks/__tests__/triage-state.test.d.ts +2 -0
  152. package/dist/hooks/__tests__/triage-state.test.d.ts.map +1 -0
  153. package/dist/hooks/__tests__/triage-state.test.js +426 -0
  154. package/dist/hooks/__tests__/triage-state.test.js.map +1 -0
  155. package/dist/hooks/keyword-detector.d.ts +26 -7
  156. package/dist/hooks/keyword-detector.d.ts.map +1 -1
  157. package/dist/hooks/keyword-detector.js +97 -26
  158. package/dist/hooks/keyword-detector.js.map +1 -1
  159. package/dist/hooks/keyword-registry.d.ts.map +1 -1
  160. package/dist/hooks/keyword-registry.js +16 -9
  161. package/dist/hooks/keyword-registry.js.map +1 -1
  162. package/dist/hooks/prompt-guidance-contract.d.ts.map +1 -1
  163. package/dist/hooks/prompt-guidance-contract.js +28 -1
  164. package/dist/hooks/prompt-guidance-contract.js.map +1 -1
  165. package/dist/hooks/triage-config.d.ts +33 -0
  166. package/dist/hooks/triage-config.d.ts.map +1 -0
  167. package/dist/hooks/triage-config.js +87 -0
  168. package/dist/hooks/triage-config.js.map +1 -0
  169. package/dist/hooks/triage-heuristic.d.ts +20 -0
  170. package/dist/hooks/triage-heuristic.d.ts.map +1 -0
  171. package/dist/hooks/triage-heuristic.js +210 -0
  172. package/dist/hooks/triage-heuristic.js.map +1 -0
  173. package/dist/hooks/triage-state.d.ts +63 -0
  174. package/dist/hooks/triage-state.d.ts.map +1 -0
  175. package/dist/hooks/triage-state.js +138 -0
  176. package/dist/hooks/triage-state.js.map +1 -0
  177. package/dist/hud/__tests__/reconcile.test.js +20 -0
  178. package/dist/hud/__tests__/reconcile.test.js.map +1 -1
  179. package/dist/hud/reconcile.d.ts +1 -0
  180. package/dist/hud/reconcile.d.ts.map +1 -1
  181. package/dist/hud/reconcile.js +2 -1
  182. package/dist/hud/reconcile.js.map +1 -1
  183. package/dist/mcp/__tests__/bootstrap.test.js +5 -24
  184. package/dist/mcp/__tests__/bootstrap.test.js.map +1 -1
  185. package/dist/mcp/__tests__/state-server.test.js +127 -0
  186. package/dist/mcp/__tests__/state-server.test.js.map +1 -1
  187. package/dist/mcp/bootstrap.d.ts +1 -1
  188. package/dist/mcp/bootstrap.d.ts.map +1 -1
  189. package/dist/mcp/bootstrap.js +3 -11
  190. package/dist/mcp/bootstrap.js.map +1 -1
  191. package/dist/mcp/state-server.d.ts +25 -0
  192. package/dist/mcp/state-server.d.ts.map +1 -1
  193. package/dist/mcp/state-server.js +41 -0
  194. package/dist/mcp/state-server.js.map +1 -1
  195. package/dist/modes/__tests__/base-ralph-contract.test.js +15 -0
  196. package/dist/modes/__tests__/base-ralph-contract.test.js.map +1 -1
  197. package/dist/modes/base.d.ts +1 -0
  198. package/dist/modes/base.d.ts.map +1 -1
  199. package/dist/modes/base.js +22 -6
  200. package/dist/modes/base.js.map +1 -1
  201. package/dist/notifications/__tests__/index.test.js +75 -0
  202. package/dist/notifications/__tests__/index.test.js.map +1 -1
  203. package/dist/notifications/__tests__/session-status.test.js +90 -0
  204. package/dist/notifications/__tests__/session-status.test.js.map +1 -1
  205. package/dist/notifications/index.d.ts.map +1 -1
  206. package/dist/notifications/index.js +39 -22
  207. package/dist/notifications/index.js.map +1 -1
  208. package/dist/notifications/session-status.d.ts +2 -0
  209. package/dist/notifications/session-status.d.ts.map +1 -1
  210. package/dist/notifications/session-status.js +19 -4
  211. package/dist/notifications/session-status.js.map +1 -1
  212. package/dist/openclaw/index.d.ts +5 -3
  213. package/dist/openclaw/index.d.ts.map +1 -1
  214. package/dist/openclaw/index.js +5 -3
  215. package/dist/openclaw/index.js.map +1 -1
  216. package/dist/question/__tests__/client.test.d.ts +2 -0
  217. package/dist/question/__tests__/client.test.d.ts.map +1 -0
  218. package/dist/question/__tests__/client.test.js +70 -0
  219. package/dist/question/__tests__/client.test.js.map +1 -0
  220. package/dist/question/__tests__/deep-interview.test.d.ts +2 -0
  221. package/dist/question/__tests__/deep-interview.test.d.ts.map +1 -0
  222. package/dist/question/__tests__/deep-interview.test.js +118 -0
  223. package/dist/question/__tests__/deep-interview.test.js.map +1 -0
  224. package/dist/question/__tests__/policy.test.d.ts +2 -0
  225. package/dist/question/__tests__/policy.test.d.ts.map +1 -0
  226. package/dist/question/__tests__/policy.test.js +107 -0
  227. package/dist/question/__tests__/policy.test.js.map +1 -0
  228. package/dist/question/__tests__/renderer.test.d.ts +2 -0
  229. package/dist/question/__tests__/renderer.test.d.ts.map +1 -0
  230. package/dist/question/__tests__/renderer.test.js +238 -0
  231. package/dist/question/__tests__/renderer.test.js.map +1 -0
  232. package/dist/question/__tests__/state.test.d.ts +2 -0
  233. package/dist/question/__tests__/state.test.d.ts.map +1 -0
  234. package/dist/question/__tests__/state.test.js +75 -0
  235. package/dist/question/__tests__/state.test.js.map +1 -0
  236. package/dist/question/__tests__/types.test.d.ts +2 -0
  237. package/dist/question/__tests__/types.test.d.ts.map +1 -0
  238. package/dist/question/__tests__/types.test.js +44 -0
  239. package/dist/question/__tests__/types.test.js.map +1 -0
  240. package/dist/question/__tests__/ui.test.d.ts +2 -0
  241. package/dist/question/__tests__/ui.test.d.ts.map +1 -0
  242. package/dist/question/__tests__/ui.test.js +169 -0
  243. package/dist/question/__tests__/ui.test.js.map +1 -0
  244. package/dist/question/client.d.ts +54 -0
  245. package/dist/question/client.d.ts.map +1 -0
  246. package/dist/question/client.js +77 -0
  247. package/dist/question/client.js.map +1 -0
  248. package/dist/question/deep-interview.d.ts +30 -0
  249. package/dist/question/deep-interview.d.ts.map +1 -0
  250. package/dist/question/deep-interview.js +118 -0
  251. package/dist/question/deep-interview.js.map +1 -0
  252. package/dist/question/policy.d.ts +18 -0
  253. package/dist/question/policy.d.ts.map +1 -0
  254. package/dist/question/policy.js +77 -0
  255. package/dist/question/policy.js.map +1 -0
  256. package/dist/question/renderer.d.ts +20 -0
  257. package/dist/question/renderer.d.ts.map +1 -0
  258. package/dist/question/renderer.js +190 -0
  259. package/dist/question/renderer.js.map +1 -0
  260. package/dist/question/state.d.ts +19 -0
  261. package/dist/question/state.d.ts.map +1 -0
  262. package/dist/question/state.js +108 -0
  263. package/dist/question/state.js.map +1 -0
  264. package/dist/question/types.d.ts +66 -0
  265. package/dist/question/types.d.ts.map +1 -0
  266. package/dist/question/types.js +82 -0
  267. package/dist/question/types.js.map +1 -0
  268. package/dist/question/ui.d.ts +38 -0
  269. package/dist/question/ui.d.ts.map +1 -0
  270. package/dist/question/ui.js +321 -0
  271. package/dist/question/ui.js.map +1 -0
  272. package/dist/ralph/contract.d.ts +1 -1
  273. package/dist/ralph/contract.d.ts.map +1 -1
  274. package/dist/ralph/contract.js +4 -1
  275. package/dist/ralph/contract.js.map +1 -1
  276. package/dist/ralplan/runtime.js +1 -1
  277. package/dist/ralplan/runtime.js.map +1 -1
  278. package/dist/runtime/__tests__/run-loop.test.d.ts +2 -0
  279. package/dist/runtime/__tests__/run-loop.test.d.ts.map +1 -0
  280. package/dist/runtime/__tests__/run-loop.test.js +35 -0
  281. package/dist/runtime/__tests__/run-loop.test.js.map +1 -0
  282. package/dist/runtime/__tests__/run-outcome.test.d.ts +2 -0
  283. package/dist/runtime/__tests__/run-outcome.test.d.ts.map +1 -0
  284. package/dist/runtime/__tests__/run-outcome.test.js +102 -0
  285. package/dist/runtime/__tests__/run-outcome.test.js.map +1 -0
  286. package/dist/runtime/__tests__/run-state.test.d.ts +2 -0
  287. package/dist/runtime/__tests__/run-state.test.d.ts.map +1 -0
  288. package/dist/runtime/__tests__/run-state.test.js +37 -0
  289. package/dist/runtime/__tests__/run-state.test.js.map +1 -0
  290. package/dist/runtime/run-loop.d.ts +45 -0
  291. package/dist/runtime/run-loop.d.ts.map +1 -0
  292. package/dist/runtime/run-loop.js +51 -0
  293. package/dist/runtime/run-loop.js.map +1 -0
  294. package/dist/runtime/run-outcome.d.ts +46 -0
  295. package/dist/runtime/run-outcome.d.ts.map +1 -0
  296. package/dist/runtime/run-outcome.js +285 -0
  297. package/dist/runtime/run-outcome.js.map +1 -0
  298. package/dist/runtime/run-state.d.ts +40 -0
  299. package/dist/runtime/run-state.d.ts.map +1 -0
  300. package/dist/runtime/run-state.js +120 -0
  301. package/dist/runtime/run-state.js.map +1 -0
  302. package/dist/runtime/terminal-lifecycle.d.ts +11 -0
  303. package/dist/runtime/terminal-lifecycle.d.ts.map +1 -0
  304. package/dist/runtime/terminal-lifecycle.js +52 -0
  305. package/dist/runtime/terminal-lifecycle.js.map +1 -0
  306. package/dist/scripts/__tests__/codex-native-hook.test.js +1459 -126
  307. package/dist/scripts/__tests__/codex-native-hook.test.js.map +1 -1
  308. package/dist/scripts/__tests__/postinstall.test.d.ts +2 -0
  309. package/dist/scripts/__tests__/postinstall.test.d.ts.map +1 -0
  310. package/dist/scripts/__tests__/postinstall.test.js +178 -0
  311. package/dist/scripts/__tests__/postinstall.test.js.map +1 -0
  312. package/dist/scripts/codex-native-hook.d.ts +3 -0
  313. package/dist/scripts/codex-native-hook.d.ts.map +1 -1
  314. package/dist/scripts/codex-native-hook.js +308 -61
  315. package/dist/scripts/codex-native-hook.js.map +1 -1
  316. package/dist/scripts/notify-fallback-watcher.js +81 -2
  317. package/dist/scripts/notify-fallback-watcher.js.map +1 -1
  318. package/dist/scripts/notify-hook/auto-nudge.d.ts +27 -0
  319. package/dist/scripts/notify-hook/auto-nudge.d.ts.map +1 -1
  320. package/dist/scripts/notify-hook/auto-nudge.js +83 -20
  321. package/dist/scripts/notify-hook/auto-nudge.js.map +1 -1
  322. package/dist/scripts/notify-hook/managed-tmux.d.ts.map +1 -1
  323. package/dist/scripts/notify-hook/managed-tmux.js +64 -38
  324. package/dist/scripts/notify-hook/managed-tmux.js.map +1 -1
  325. package/dist/scripts/notify-hook/ralph-session-resume.js +1 -1
  326. package/dist/scripts/notify-hook/ralph-session-resume.js.map +1 -1
  327. package/dist/scripts/notify-hook.js +15 -5
  328. package/dist/scripts/notify-hook.js.map +1 -1
  329. package/dist/scripts/postinstall.d.ts +22 -0
  330. package/dist/scripts/postinstall.d.ts.map +1 -0
  331. package/dist/scripts/postinstall.js +105 -0
  332. package/dist/scripts/postinstall.js.map +1 -0
  333. package/dist/scripts/sync-prompt-guidance-fragments.js +5 -0
  334. package/dist/scripts/sync-prompt-guidance-fragments.js.map +1 -1
  335. package/dist/state/__tests__/operations-ralph-phase.test.js +21 -0
  336. package/dist/state/__tests__/operations-ralph-phase.test.js.map +1 -1
  337. package/dist/state/__tests__/operations.test.js +18 -0
  338. package/dist/state/__tests__/operations.test.js.map +1 -1
  339. package/dist/state/__tests__/workflow-transition.test.js +11 -0
  340. package/dist/state/__tests__/workflow-transition.test.js.map +1 -1
  341. package/dist/state/operations.d.ts.map +1 -1
  342. package/dist/state/operations.js +15 -0
  343. package/dist/state/operations.js.map +1 -1
  344. package/dist/state/workflow-transition-reconcile.d.ts.map +1 -1
  345. package/dist/state/workflow-transition-reconcile.js +14 -1
  346. package/dist/state/workflow-transition-reconcile.js.map +1 -1
  347. package/dist/state/workflow-transition.d.ts.map +1 -1
  348. package/dist/state/workflow-transition.js +3 -1
  349. package/dist/state/workflow-transition.js.map +1 -1
  350. package/dist/team/__tests__/followup-planner.test.js +15 -0
  351. package/dist/team/__tests__/followup-planner.test.js.map +1 -1
  352. package/dist/team/__tests__/role-router.test.js +47 -0
  353. package/dist/team/__tests__/role-router.test.js.map +1 -1
  354. package/dist/team/__tests__/runtime.test.js +108 -2
  355. package/dist/team/__tests__/runtime.test.js.map +1 -1
  356. package/dist/team/followup-planner.d.ts.map +1 -1
  357. package/dist/team/followup-planner.js +31 -9
  358. package/dist/team/followup-planner.js.map +1 -1
  359. package/dist/team/role-router.d.ts.map +1 -1
  360. package/dist/team/role-router.js +73 -0
  361. package/dist/team/role-router.js.map +1 -1
  362. package/dist/team/runtime.d.ts.map +1 -1
  363. package/dist/team/runtime.js +18 -4
  364. package/dist/team/runtime.js.map +1 -1
  365. package/dist/utils/__tests__/dep-versions.test.js +25 -8
  366. package/dist/utils/__tests__/dep-versions.test.js.map +1 -1
  367. package/dist/utils/__tests__/paths.test.js +45 -0
  368. package/dist/utils/__tests__/paths.test.js.map +1 -1
  369. package/dist/utils/paths.d.ts +2 -0
  370. package/dist/utils/paths.d.ts.map +1 -1
  371. package/dist/utils/paths.js +22 -7
  372. package/dist/utils/paths.js.map +1 -1
  373. package/dist/verification/__tests__/ci-rust-gates.test.js +1 -1
  374. package/dist/verification/__tests__/ci-rust-gates.test.js.map +1 -1
  375. package/package.json +4 -2
  376. package/prompts/architect.md +4 -0
  377. package/prompts/code-reviewer.md +3 -0
  378. package/prompts/dependency-expert.md +3 -0
  379. package/prompts/executor.md +5 -0
  380. package/prompts/explore.md +2 -0
  381. package/prompts/planner.md +5 -0
  382. package/prompts/product-analyst.md +8 -8
  383. package/prompts/researcher.md +78 -30
  384. package/prompts/verifier.md +4 -0
  385. package/skills/autoresearch/SKILL.md +68 -0
  386. package/skills/code-review/SKILL.md +94 -28
  387. package/skills/deep-interview/SKILL.md +100 -9
  388. package/skills/help/SKILL.md +3 -1
  389. package/skills/ralplan/SKILL.md +1 -0
  390. package/skills/team/SKILL.md +1 -0
  391. package/skills/ultrawork/SKILL.md +1 -0
  392. package/src/scripts/__tests__/codex-native-hook.test.ts +2373 -692
  393. package/src/scripts/__tests__/postinstall.test.ts +210 -0
  394. package/src/scripts/codex-native-hook.ts +365 -66
  395. package/src/scripts/notify-fallback-watcher.ts +92 -2
  396. package/src/scripts/notify-hook/auto-nudge.ts +89 -20
  397. package/src/scripts/notify-hook/managed-tmux.ts +70 -31
  398. package/src/scripts/notify-hook/ralph-session-resume.ts +1 -1
  399. package/src/scripts/notify-hook.ts +23 -5
  400. package/src/scripts/postinstall-bootstrap.js +23 -0
  401. package/src/scripts/postinstall.ts +161 -0
  402. package/src/scripts/sync-prompt-guidance-fragments.ts +4 -0
  403. package/templates/AGENTS.md +48 -37
  404. package/templates/catalog-manifest.json +7 -0
  405. package/templates/model-instructions/explore-lightweight-AGENTS.md +11 -0
  406. package/templates/model-instructions/sparkshell-lightweight-AGENTS.md +10 -0
@@ -122,7 +122,7 @@ if [[ "\$cmd" == "list-panes" ]]; then
122
122
  esac
123
123
  done
124
124
  if [[ -n "\$target" && "\$target" == "${'${OMX_TEST_TMUX_SESSION_NAME:-devsess}'}" ]]; then
125
- printf '%%99\tnode\tcodex --model gpt-5\n'
125
+ printf '%%99\t1\tnode\tcodex --model gpt-5\n'
126
126
  exit 0
127
127
  fi
128
128
  echo "%1 12345"
@@ -375,6 +375,7 @@ describe('notify-hook auto-nudge', () => {
375
375
  await withTempWorkingDir(async (cwd) => {
376
376
  const omxDir = join(cwd, '.omx');
377
377
  const stateDir = join(omxDir, 'state');
378
+ const sessionStateDir = join(stateDir, 'sessions', 'sess-managed');
378
379
  const logsDir = join(omxDir, 'logs');
379
380
  const codexHome = join(cwd, 'codex-home');
380
381
  const fakeBinDir = join(cwd, 'fake-bin');
@@ -382,6 +383,7 @@ describe('notify-hook auto-nudge', () => {
382
383
  const managedSessionName = buildTmuxSessionName(cwd, 'sess-managed');
383
384
  await mkdir(logsDir, { recursive: true });
384
385
  await mkdir(stateDir, { recursive: true });
386
+ await mkdir(sessionStateDir, { recursive: true });
385
387
  await mkdir(codexHome, { recursive: true });
386
388
  await mkdir(fakeBinDir, { recursive: true });
387
389
  await writeJson(join(codexHome, '.omx-config.json'), {
@@ -506,6 +508,7 @@ describe('notify-hook auto-nudge', () => {
506
508
  await withTempWorkingDir(async (cwd) => {
507
509
  const omxDir = join(cwd, '.omx');
508
510
  const stateDir = join(omxDir, 'state');
511
+ const sessionStateDir = join(stateDir, 'sessions', 'sess-managed');
509
512
  const logsDir = join(omxDir, 'logs');
510
513
  const codexHome = join(cwd, 'codex-home');
511
514
  const fakeBinDir = join(cwd, 'fake-bin');
@@ -513,13 +516,14 @@ describe('notify-hook auto-nudge', () => {
513
516
  const managedSessionName = buildTmuxSessionName(cwd, 'sess-managed');
514
517
  await mkdir(logsDir, { recursive: true });
515
518
  await mkdir(stateDir, { recursive: true });
519
+ await mkdir(sessionStateDir, { recursive: true });
516
520
  await mkdir(codexHome, { recursive: true });
517
521
  await mkdir(fakeBinDir, { recursive: true });
518
522
  await writeJson(join(codexHome, '.omx-config.json'), {
519
523
  autoNudge: { enabled: true, delaySec: 0, stallMs: 0 },
520
524
  });
521
525
  await writeManagedSessionState(stateDir, cwd);
522
- await writeJson(join(stateDir, 'ralph-state.json'), {
526
+ await writeJson(join(sessionStateDir, 'ralph-state.json'), {
523
527
  active: true,
524
528
  tmux_pane_id: '%99',
525
529
  });
@@ -569,7 +573,7 @@ if [[ "$cmd" == "list-panes" ]]; then
569
573
  esac
570
574
  done
571
575
  if [[ "$target" == "${managedSessionName}" ]]; then
572
- printf "%%99\tsh\tbash\n%%100\tnode\tcodex --model gpt-5\n"
576
+ printf "%%99\t0\tsh\tbash\n%%100\t1\tnode\tcodex --model gpt-5\n"
573
577
  exit 0
574
578
  fi
575
579
  echo "%1 12345"
@@ -587,23 +591,420 @@ exit 0
587
591
  await writeFile(join(fakeBinDir, 'tmux'), fakeTmux);
588
592
  await chmod(join(fakeBinDir, 'tmux'), 0o755);
589
593
  const result = runNotifyHook(cwd, fakeBinDir, codexHome, {
590
- 'last-assistant-message': 'Keep going and finish the cleanup from here.',
594
+ 'last-assistant-message': 'Keep going and finish the cleanup from here.',
595
+ }, {
596
+ TMUX_PANE: '',
597
+ });
598
+ assert.equal(result.status, 0, `hook failed: ${result.stderr || result.stdout}`);
599
+ const tmuxLog = await readFile(tmuxLogPath, 'utf-8');
600
+ assert.match(tmuxLog, defaultAutoNudgePattern('%100'), 'should upgrade anchored shell pane to sibling codex pane');
601
+ });
602
+ });
603
+ it('keeps a verified codex anchor from active mode state even when a sibling codex pane is focused', async () => {
604
+ await withTempWorkingDir(async (cwd) => {
605
+ const omxDir = join(cwd, '.omx');
606
+ const stateDir = join(omxDir, 'state');
607
+ const sessionStateDir = join(stateDir, 'sessions', 'sess-managed');
608
+ const logsDir = join(omxDir, 'logs');
609
+ const codexHome = join(cwd, 'codex-home');
610
+ const fakeBinDir = join(cwd, 'fake-bin');
611
+ const tmuxLogPath = join(cwd, 'tmux.log');
612
+ const managedSessionName = buildTmuxSessionName(cwd, 'sess-managed');
613
+ await mkdir(logsDir, { recursive: true });
614
+ await mkdir(stateDir, { recursive: true });
615
+ await mkdir(sessionStateDir, { recursive: true });
616
+ await mkdir(codexHome, { recursive: true });
617
+ await mkdir(fakeBinDir, { recursive: true });
618
+ await writeJson(join(codexHome, '.omx-config.json'), {
619
+ autoNudge: { enabled: true, delaySec: 0, stallMs: 0 },
620
+ });
621
+ await writeManagedSessionState(stateDir, cwd);
622
+ await writeJson(join(sessionStateDir, 'ralph-state.json'), {
623
+ active: true,
624
+ tmux_pane_id: '%99',
625
+ });
626
+ const fakeTmux = `#!/usr/bin/env bash
627
+ set -eu
628
+ echo "$@" >> "${tmuxLogPath}"
629
+ cmd="$1"
630
+ shift || true
631
+ if [[ "$cmd" == "display-message" ]]; then
632
+ target=""
633
+ format=""
634
+ while [[ "$#" -gt 0 ]]; do
635
+ case "$1" in
636
+ -p) shift ;;
637
+ -t) target="$2"; shift 2 ;;
638
+ *) format="$1"; shift ;;
639
+ esac
640
+ done
641
+ if [[ "$format" == "#{pane_current_command}" && "$target" == "%99" ]]; then
642
+ echo "codex"
643
+ exit 0
644
+ fi
645
+ if [[ "$format" == "#{pane_start_command}" && "$target" == "%99" ]]; then
646
+ echo "codex"
647
+ exit 0
648
+ fi
649
+ if [[ "$format" == "#{pane_in_mode}" && "$target" == "%99" ]]; then
650
+ echo "0"
651
+ exit 0
652
+ fi
653
+ if [[ "$format" == "#S" && "$target" == "%99" ]]; then
654
+ echo "${managedSessionName}"
655
+ exit 0
656
+ fi
657
+ exit 0
658
+ fi
659
+ if [[ "$cmd" == "list-panes" ]]; then
660
+ target=""
661
+ while [[ "$#" -gt 0 ]]; do
662
+ case "$1" in
663
+ -t) target="$2"; shift 2 ;;
664
+ *) shift ;;
665
+ esac
666
+ done
667
+ if [[ "$target" == "${managedSessionName}" ]]; then
668
+ printf "%%99\t0\tcodex\tcodex\\n%%100\t1\tcodex\tcodex\\n"
669
+ exit 0
670
+ fi
671
+ echo "%1 12345"
672
+ exit 0
673
+ fi
674
+ if [[ "$cmd" == "capture-pane" ]]; then
675
+ printf "How can I help?\\n› "
676
+ exit 0
677
+ fi
678
+ if [[ "$cmd" == "send-keys" ]]; then
679
+ exit 0
680
+ fi
681
+ exit 0
682
+ `;
683
+ await writeFile(join(fakeBinDir, 'tmux'), fakeTmux);
684
+ await chmod(join(fakeBinDir, 'tmux'), 0o755);
685
+ const result = runNotifyHook(cwd, fakeBinDir, codexHome, {
686
+ 'last-assistant-message': 'Keep going and finish the cleanup from here.',
687
+ }, {
688
+ TMUX_PANE: '',
689
+ });
690
+ assert.equal(result.status, 0, `hook failed: ${result.stderr || result.stdout}`);
691
+ const tmuxLog = await readFile(tmuxLogPath, 'utf-8');
692
+ assert.match(tmuxLog, defaultAutoNudgePattern('%99'), 'should keep the verified codex anchor');
693
+ assert.doesNotMatch(tmuxLog, defaultAutoNudgePattern('%100'), 'should not jump to the focused sibling codex pane');
694
+ });
695
+ });
696
+ it('upgrades a node shell anchor from active mode state to the sibling codex pane', async () => {
697
+ await withTempWorkingDir(async (cwd) => {
698
+ const omxDir = join(cwd, '.omx');
699
+ const stateDir = join(omxDir, 'state');
700
+ const sessionStateDir = join(stateDir, 'sessions', 'sess-managed');
701
+ const logsDir = join(omxDir, 'logs');
702
+ const codexHome = join(cwd, 'codex-home');
703
+ const fakeBinDir = join(cwd, 'fake-bin');
704
+ const tmuxLogPath = join(cwd, 'tmux.log');
705
+ const managedSessionName = buildTmuxSessionName(cwd, 'sess-managed');
706
+ await mkdir(logsDir, { recursive: true });
707
+ await mkdir(stateDir, { recursive: true });
708
+ await mkdir(sessionStateDir, { recursive: true });
709
+ await mkdir(codexHome, { recursive: true });
710
+ await mkdir(fakeBinDir, { recursive: true });
711
+ await writeJson(join(codexHome, '.omx-config.json'), {
712
+ autoNudge: { enabled: true, delaySec: 0, stallMs: 0 },
713
+ });
714
+ await writeManagedSessionState(stateDir, cwd);
715
+ await writeJson(join(sessionStateDir, 'ralph-state.json'), {
716
+ active: true,
717
+ tmux_pane_id: '%99',
718
+ });
719
+ const fakeTmux = `#!/usr/bin/env bash
720
+ set -eu
721
+ echo "$@" >> "${tmuxLogPath}"
722
+ cmd="$1"
723
+ shift || true
724
+ if [[ "$cmd" == "display-message" ]]; then
725
+ target=""
726
+ format=""
727
+ while [[ "$#" -gt 0 ]]; do
728
+ case "$1" in
729
+ -p) shift ;;
730
+ -t) target="$2"; shift 2 ;;
731
+ *) format="$1"; shift ;;
732
+ esac
733
+ done
734
+ if [[ "$format" == "#{pane_current_command}" && "$target" == "%99" ]]; then
735
+ echo "node"
736
+ exit 0
737
+ fi
738
+ if [[ "$format" == "#{pane_start_command}" && "$target" == "%99" ]]; then
739
+ echo "bash"
740
+ exit 0
741
+ fi
742
+ if [[ "$format" == "#{pane_in_mode}" && "$target" == "%100" ]]; then
743
+ echo "0"
744
+ exit 0
745
+ fi
746
+ if [[ "$format" == "#S" && "$target" == "%99" ]]; then
747
+ echo "${managedSessionName}"
748
+ exit 0
749
+ fi
750
+ exit 0
751
+ fi
752
+ if [[ "$cmd" == "list-panes" ]]; then
753
+ target=""
754
+ while [[ "$#" -gt 0 ]]; do
755
+ case "$1" in
756
+ -t) target="$2"; shift 2 ;;
757
+ *) shift ;;
758
+ esac
759
+ done
760
+ if [[ "$target" == "${managedSessionName}" ]]; then
761
+ printf "%%99\t0\tnode\tbash\\n%%100\t1\tnode\tcodex --model gpt-5\\n"
762
+ exit 0
763
+ fi
764
+ echo "%1 12345"
765
+ exit 0
766
+ fi
767
+ if [[ "$cmd" == "capture-pane" ]]; then
768
+ printf "How can I help?\\n› "
769
+ exit 0
770
+ fi
771
+ if [[ "$cmd" == "send-keys" ]]; then
772
+ exit 0
773
+ fi
774
+ exit 0
775
+ `;
776
+ await writeFile(join(fakeBinDir, 'tmux'), fakeTmux);
777
+ await chmod(join(fakeBinDir, 'tmux'), 0o755);
778
+ const result = runNotifyHook(cwd, fakeBinDir, codexHome, {
779
+ 'last-assistant-message': 'Keep going and finish the cleanup from here.',
780
+ }, {
781
+ TMUX_PANE: '',
782
+ });
783
+ assert.equal(result.status, 0, `hook failed: ${result.stderr || result.stdout}`);
784
+ const tmuxLog = await readFile(tmuxLogPath, 'utf-8');
785
+ assert.match(tmuxLog, defaultAutoNudgePattern('%100'), 'should upgrade the node shell anchor to the sibling codex pane');
786
+ assert.doesNotMatch(tmuxLog, defaultAutoNudgePattern('%99'), 'node shell anchor should not be retained');
787
+ });
788
+ });
789
+ it('upgrades a shell-degraded codex anchor from active mode state to the sibling codex pane', async () => {
790
+ await withTempWorkingDir(async (cwd) => {
791
+ const omxDir = join(cwd, '.omx');
792
+ const stateDir = join(omxDir, 'state');
793
+ const sessionStateDir = join(stateDir, 'sessions', 'sess-managed');
794
+ const logsDir = join(omxDir, 'logs');
795
+ const codexHome = join(cwd, 'codex-home');
796
+ const fakeBinDir = join(cwd, 'fake-bin');
797
+ const tmuxLogPath = join(cwd, 'tmux.log');
798
+ const managedSessionName = buildTmuxSessionName(cwd, 'sess-managed');
799
+ await mkdir(logsDir, { recursive: true });
800
+ await mkdir(stateDir, { recursive: true });
801
+ await mkdir(sessionStateDir, { recursive: true });
802
+ await mkdir(codexHome, { recursive: true });
803
+ await mkdir(fakeBinDir, { recursive: true });
804
+ await writeJson(join(codexHome, '.omx-config.json'), {
805
+ autoNudge: { enabled: true, delaySec: 0, stallMs: 0 },
806
+ });
807
+ await writeManagedSessionState(stateDir, cwd);
808
+ await writeJson(join(sessionStateDir, 'ralph-state.json'), {
809
+ active: true,
810
+ tmux_pane_id: '%99',
811
+ });
812
+ const fakeTmux = `#!/usr/bin/env bash
813
+ set -eu
814
+ echo "$@" >> "${tmuxLogPath}"
815
+ cmd="$1"
816
+ shift || true
817
+ if [[ "$cmd" == "display-message" ]]; then
818
+ target=""
819
+ format=""
820
+ while [[ "$#" -gt 0 ]]; do
821
+ case "$1" in
822
+ -p) shift ;;
823
+ -t) target="$2"; shift 2 ;;
824
+ *) format="$1"; shift ;;
825
+ esac
826
+ done
827
+ if [[ "$format" == "#{pane_current_command}" && "$target" == "%99" ]]; then
828
+ echo "bash"
829
+ exit 0
830
+ fi
831
+ if [[ "$format" == "#{pane_start_command}" && "$target" == "%99" ]]; then
832
+ echo "codex --model gpt-5"
833
+ exit 0
834
+ fi
835
+ if [[ "$format" == "#{pane_in_mode}" && "$target" == "%100" ]]; then
836
+ echo "0"
837
+ exit 0
838
+ fi
839
+ if [[ "$format" == "#S" && "$target" == "%99" ]]; then
840
+ echo "${managedSessionName}"
841
+ exit 0
842
+ fi
843
+ exit 0
844
+ fi
845
+ if [[ "$cmd" == "list-panes" ]]; then
846
+ target=""
847
+ while [[ "$#" -gt 0 ]]; do
848
+ case "$1" in
849
+ -t) target="$2"; shift 2 ;;
850
+ *) shift ;;
851
+ esac
852
+ done
853
+ if [[ "$target" == "${managedSessionName}" ]]; then
854
+ printf "%%99\t1\tbash\tcodex --model gpt-5\\n%%100\t0\tnode\tcodex --model gpt-5\\n"
855
+ exit 0
856
+ fi
857
+ echo "%1 12345"
858
+ exit 0
859
+ fi
860
+ if [[ "$cmd" == "capture-pane" ]]; then
861
+ printf "How can I help?\\n› "
862
+ exit 0
863
+ fi
864
+ if [[ "$cmd" == "send-keys" ]]; then
865
+ exit 0
866
+ fi
867
+ exit 0
868
+ `;
869
+ await writeFile(join(fakeBinDir, 'tmux'), fakeTmux);
870
+ await chmod(join(fakeBinDir, 'tmux'), 0o755);
871
+ const result = runNotifyHook(cwd, fakeBinDir, codexHome, {
872
+ 'last-assistant-message': 'Keep going and finish the cleanup from here.',
873
+ }, {
874
+ TMUX_PANE: '',
875
+ });
876
+ assert.equal(result.status, 0, `hook failed: ${result.stderr || result.stdout}`);
877
+ const tmuxLog = await readFile(tmuxLogPath, 'utf-8');
878
+ assert.match(tmuxLog, defaultAutoNudgePattern('%100'), 'should upgrade the shell-degraded codex anchor to the sibling codex pane');
879
+ assert.doesNotMatch(tmuxLog, defaultAutoNudgePattern('%99'), 'shell-degraded codex anchor should not be retained');
880
+ });
881
+ });
882
+ it('fails closed when a shell-degraded codex anchor has no live sibling pane', async () => {
883
+ await withTempWorkingDir(async (cwd) => {
884
+ const omxDir = join(cwd, '.omx');
885
+ const stateDir = join(omxDir, 'state');
886
+ const sessionStateDir = join(stateDir, 'sessions', 'sess-managed');
887
+ const logsDir = join(omxDir, 'logs');
888
+ const codexHome = join(cwd, 'codex-home');
889
+ const fakeBinDir = join(cwd, 'fake-bin');
890
+ const tmuxLogPath = join(cwd, 'tmux.log');
891
+ const managedSessionName = buildTmuxSessionName(cwd, 'sess-managed');
892
+ await mkdir(logsDir, { recursive: true });
893
+ await mkdir(stateDir, { recursive: true });
894
+ await mkdir(sessionStateDir, { recursive: true });
895
+ await mkdir(codexHome, { recursive: true });
896
+ await mkdir(fakeBinDir, { recursive: true });
897
+ await writeJson(join(codexHome, '.omx-config.json'), {
898
+ autoNudge: { enabled: true, delaySec: 0, stallMs: 0 },
899
+ });
900
+ await writeManagedSessionState(stateDir, cwd);
901
+ await writeJson(join(sessionStateDir, 'ralph-state.json'), {
902
+ active: true,
903
+ tmux_pane_id: '%99',
904
+ });
905
+ const fakeTmux = `#!/usr/bin/env bash
906
+ set -eu
907
+ echo "$@" >> "${tmuxLogPath}"
908
+ cmd="$1"
909
+ shift || true
910
+ if [[ "$cmd" == "display-message" ]]; then
911
+ target=""
912
+ format=""
913
+ while [[ "$#" -gt 0 ]]; do
914
+ case "$1" in
915
+ -p) shift ;;
916
+ -t) target="$2"; shift 2 ;;
917
+ *) format="$1"; shift ;;
918
+ esac
919
+ done
920
+ if [[ "$format" == "#{pane_current_command}" && "$target" == "%99" ]]; then
921
+ echo "bash"
922
+ exit 0
923
+ fi
924
+ if [[ "$format" == "#{pane_start_command}" && "$target" == "%99" ]]; then
925
+ echo "codex --model gpt-5"
926
+ exit 0
927
+ fi
928
+ if [[ "$format" == "#S" && "$target" == "%99" ]]; then
929
+ echo "${managedSessionName}"
930
+ exit 0
931
+ fi
932
+ exit 0
933
+ fi
934
+ if [[ "$cmd" == "list-panes" ]]; then
935
+ target=""
936
+ while [[ "$#" -gt 0 ]]; do
937
+ case "$1" in
938
+ -t) target="$2"; shift 2 ;;
939
+ *) shift ;;
940
+ esac
941
+ done
942
+ if [[ "$target" == "${managedSessionName}" ]]; then
943
+ printf "%%99\t1\tbash\tcodex --model gpt-5\\n%%100\t0\tbash\tbash\\n"
944
+ exit 0
945
+ fi
946
+ echo "%1 12345"
947
+ exit 0
948
+ fi
949
+ if [[ "$cmd" == "capture-pane" ]]; then
950
+ printf "How can I help?\\n› "
951
+ exit 0
952
+ fi
953
+ if [[ "$cmd" == "send-keys" ]]; then
954
+ exit 0
955
+ fi
956
+ exit 0
957
+ `;
958
+ await writeFile(join(fakeBinDir, 'tmux'), fakeTmux);
959
+ await chmod(join(fakeBinDir, 'tmux'), 0o755);
960
+ const result = runNotifyHook(cwd, fakeBinDir, codexHome, {
961
+ 'last-assistant-message': 'Keep going and finish the cleanup from here.',
962
+ }, {
963
+ TMUX_PANE: '',
964
+ });
965
+ assert.equal(result.status, 0, `hook failed: ${result.stderr || result.stdout}`);
966
+ const tmuxLog = await readFile(tmuxLogPath, 'utf-8');
967
+ assert.doesNotMatch(tmuxLog, defaultAutoNudgePattern('%99'), 'shell-degraded codex anchor should not be retained without a live sibling');
968
+ assert.doesNotMatch(tmuxLog, defaultAutoNudgePattern('%100'), 'no live sibling should keep auto-nudge from sending input');
969
+ });
970
+ });
971
+ it('still auto-nudges in team-worker context using the worker state root', async () => {
972
+ await withTempWorkingDir(async (cwd) => {
973
+ const workerStateRoot = join(cwd, 'leader-state-root');
974
+ const logsDir = join(cwd, '.omx', 'logs');
975
+ const codexHome = join(cwd, 'codex-home');
976
+ const fakeBinDir = join(cwd, 'fake-bin');
977
+ const tmuxLogPath = join(cwd, 'tmux.log');
978
+ const managedSessionName = buildTmuxSessionName(cwd, 'sess-managed');
979
+ await mkdir(logsDir, { recursive: true });
980
+ await mkdir(workerStateRoot, { recursive: true });
981
+ await mkdir(codexHome, { recursive: true });
982
+ await mkdir(fakeBinDir, { recursive: true });
983
+ await writeJson(join(codexHome, '.omx-config.json'), {
984
+ autoNudge: { enabled: true, delaySec: 0, stallMs: 0 },
985
+ });
986
+ await writeFile(join(fakeBinDir, 'tmux'), buildFakeTmux(tmuxLogPath));
987
+ await chmod(join(fakeBinDir, 'tmux'), 0o755);
988
+ const result = runNotifyHook(cwd, fakeBinDir, codexHome, {
989
+ 'last-assistant-message': 'I can continue with the worker follow-up from here.',
591
990
  }, {
592
- TMUX_PANE: '',
991
+ OMX_TEAM_WORKER: 'auto-nudge/worker-1',
992
+ OMX_TEAM_STATE_ROOT: workerStateRoot,
593
993
  });
594
994
  assert.equal(result.status, 0, `hook failed: ${result.stderr || result.stdout}`);
595
995
  const tmuxLog = await readFile(tmuxLogPath, 'utf-8');
596
- assert.match(tmuxLog, defaultAutoNudgePattern('%100'), 'should upgrade anchored shell pane to sibling codex pane');
996
+ assert.match(tmuxLog, defaultAutoNudgePattern('%99'), 'team-worker context should still send auto-nudge');
997
+ const nudgeStatePath = join(workerStateRoot, 'auto-nudge-state.json');
998
+ assert.ok(existsSync(nudgeStatePath), 'worker state root should receive auto-nudge state');
597
999
  });
598
1000
  });
599
- it('still auto-nudges in team-worker context using the worker state root', async () => {
1001
+ it('still auto-nudges from the stored worker pane when TMUX_PANE is missing and the worker pane looks shell-degraded', async () => {
600
1002
  await withTempWorkingDir(async (cwd) => {
601
1003
  const workerStateRoot = join(cwd, 'leader-state-root');
602
1004
  const logsDir = join(cwd, '.omx', 'logs');
603
1005
  const codexHome = join(cwd, 'codex-home');
604
1006
  const fakeBinDir = join(cwd, 'fake-bin');
605
1007
  const tmuxLogPath = join(cwd, 'tmux.log');
606
- const managedSessionName = buildTmuxSessionName(cwd, 'sess-managed');
607
1008
  await mkdir(logsDir, { recursive: true });
608
1009
  await mkdir(workerStateRoot, { recursive: true });
609
1010
  await mkdir(codexHome, { recursive: true });
@@ -611,19 +1012,64 @@ exit 0
611
1012
  await writeJson(join(codexHome, '.omx-config.json'), {
612
1013
  autoNudge: { enabled: true, delaySec: 0, stallMs: 0 },
613
1014
  });
614
- await writeFile(join(fakeBinDir, 'tmux'), buildFakeTmux(tmuxLogPath));
1015
+ await writeJson(join(workerStateRoot, 'ralph-state.json'), {
1016
+ active: true,
1017
+ tmux_pane_id: '%99',
1018
+ });
1019
+ const fakeTmux = `#!/usr/bin/env bash
1020
+ set -eu
1021
+ echo "$@" >> "${tmuxLogPath}"
1022
+ cmd="$1"
1023
+ shift || true
1024
+ if [[ "$cmd" == "display-message" ]]; then
1025
+ target=""
1026
+ format=""
1027
+ while [[ "$#" -gt 0 ]]; do
1028
+ case "$1" in
1029
+ -p) shift ;;
1030
+ -t) target="$2"; shift 2 ;;
1031
+ *) format="$1"; shift ;;
1032
+ esac
1033
+ done
1034
+ if [[ "$format" == "#{pane_current_command}" && "$target" == "%99" ]]; then
1035
+ echo "bash"
1036
+ exit 0
1037
+ fi
1038
+ if [[ "$format" == "#{pane_start_command}" && "$target" == "%99" ]]; then
1039
+ echo "codex --model gpt-5"
1040
+ exit 0
1041
+ fi
1042
+ if [[ "$format" == "#{pane_in_mode}" && "$target" == "%99" ]]; then
1043
+ echo "0"
1044
+ exit 0
1045
+ fi
1046
+ exit 0
1047
+ fi
1048
+ if [[ "$cmd" == "capture-pane" ]]; then
1049
+ printf "How can I help?\\n› "
1050
+ exit 0
1051
+ fi
1052
+ if [[ "$cmd" == "send-keys" ]]; then
1053
+ exit 0
1054
+ fi
1055
+ if [[ "$cmd" == "list-panes" ]]; then
1056
+ echo "%1 12345"
1057
+ exit 0
1058
+ fi
1059
+ exit 0
1060
+ `;
1061
+ await writeFile(join(fakeBinDir, 'tmux'), fakeTmux);
615
1062
  await chmod(join(fakeBinDir, 'tmux'), 0o755);
616
1063
  const result = runNotifyHook(cwd, fakeBinDir, codexHome, {
617
1064
  'last-assistant-message': 'I can continue with the worker follow-up from here.',
618
1065
  }, {
619
1066
  OMX_TEAM_WORKER: 'auto-nudge/worker-1',
620
1067
  OMX_TEAM_STATE_ROOT: workerStateRoot,
1068
+ TMUX_PANE: '',
621
1069
  });
622
1070
  assert.equal(result.status, 0, `hook failed: ${result.stderr || result.stdout}`);
623
1071
  const tmuxLog = await readFile(tmuxLogPath, 'utf-8');
624
- assert.match(tmuxLog, defaultAutoNudgePattern('%99'), 'team-worker context should still send auto-nudge');
625
- const nudgeStatePath = join(workerStateRoot, 'auto-nudge-state.json');
626
- assert.ok(existsSync(nudgeStatePath), 'worker state root should receive auto-nudge state');
1072
+ assert.match(tmuxLog, defaultAutoNudgePattern('%99'), 'worker fallback should keep using the stored pane when TMUX_PANE is absent');
627
1073
  });
628
1074
  });
629
1075
  it('does not nudge when no stall pattern is present', async () => {
@@ -790,7 +1236,7 @@ if [[ "$cmd" == "list-panes" ]]; then
790
1236
  esac
791
1237
  done
792
1238
  if [[ "$target" == "${managedSessionName}" ]]; then
793
- printf "%%99\t1\tsh\\n%%100\t0\tcodex --model gpt-5\\n"
1239
+ printf "%%99\t1\tsh\tbash\\n%%100\t0\tnode\tcodex --model gpt-5\\n"
794
1240
  exit 0
795
1241
  fi
796
1242
  echo "%1 12345"
@@ -1152,7 +1598,7 @@ exit 0
1152
1598
  await writeFile(join(fakeBinDir, 'tmux'), buildFakeTmux(join(cwd, 'tmux.log')));
1153
1599
  await chmod(join(fakeBinDir, 'tmux'), 0o755);
1154
1600
  const result = runNotifyHook(cwd, fakeBinDir, codexHome, {
1155
- 'input-messages': ['please use autopilot for this task'],
1601
+ 'input-messages': ['please use $autopilot for this task'],
1156
1602
  'last-assistant-message': 'Here is the plan I will follow.',
1157
1603
  });
1158
1604
  assert.equal(result.status, 0, `hook failed: ${result.stderr || result.stdout}`);
@@ -1246,6 +1692,7 @@ exit 0
1246
1692
  const logsDir = join(omxDir, 'logs');
1247
1693
  const codexHome = join(cwd, 'codex-home');
1248
1694
  const fakeBinDir = join(cwd, 'fake-bin');
1695
+ const sessionStateDir = join(stateDir, 'sessions', 'sess-managed');
1249
1696
  await mkdir(logsDir, { recursive: true });
1250
1697
  await mkdir(stateDir, { recursive: true });
1251
1698
  await mkdir(codexHome, { recursive: true });
@@ -1265,13 +1712,55 @@ exit 0
1265
1712
  assert.equal(skillState.input_lock?.active, true);
1266
1713
  assert.deepEqual(skillState.input_lock?.blocked_inputs, DEEP_INTERVIEW_BLOCKED_APPROVAL_INPUTS);
1267
1714
  assert.match(skillState.input_lock?.message || '', /Deep interview is active/i);
1268
- const modeState = JSON.parse(await readFile(join(stateDir, 'deep-interview-state.json'), 'utf-8'));
1715
+ const modeState = JSON.parse(await readFile(join(sessionStateDir, 'deep-interview-state.json'), 'utf-8'));
1269
1716
  assert.equal(modeState.active, true);
1270
1717
  assert.equal(modeState.mode, 'deep-interview');
1271
1718
  assert.equal(modeState.current_phase, 'intent-first');
1272
1719
  assert.equal(modeState.input_lock?.active, true);
1273
1720
  });
1274
1721
  });
1722
+ it('releases deep-interview mode state on normal completion without waiting for later keyword input', async () => {
1723
+ await withTempWorkingDir(async (cwd) => {
1724
+ const omxDir = join(cwd, '.omx');
1725
+ const stateDir = join(omxDir, 'state');
1726
+ const logsDir = join(omxDir, 'logs');
1727
+ const codexHome = join(cwd, 'codex-home');
1728
+ const fakeBinDir = join(cwd, 'fake-bin');
1729
+ const sessionStateDir = join(stateDir, 'sessions', 'sess-managed');
1730
+ await mkdir(logsDir, { recursive: true });
1731
+ await mkdir(stateDir, { recursive: true });
1732
+ await mkdir(codexHome, { recursive: true });
1733
+ await mkdir(fakeBinDir, { recursive: true });
1734
+ await writeJson(join(codexHome, '.omx-config.json'), {
1735
+ autoNudge: { enabled: true, delaySec: 0, stallMs: 0 },
1736
+ });
1737
+ await writeFile(join(fakeBinDir, 'tmux'), buildFakeTmux(join(cwd, 'tmux.log')));
1738
+ await chmod(join(fakeBinDir, 'tmux'), 0o755);
1739
+ const activated = runNotifyHook(cwd, fakeBinDir, codexHome, {
1740
+ 'input-messages': ['please run a deep interview first'],
1741
+ 'last-assistant-message': 'Round 1 | Target: Goal Clarity',
1742
+ });
1743
+ assert.equal(activated.status, 0, `activation hook failed: ${activated.stderr || activated.stdout}`);
1744
+ const completed = runNotifyHook(cwd, fakeBinDir, codexHome, {
1745
+ 'input-messages': ['continue'],
1746
+ 'last-assistant-message': 'Interview completed. Final summary ready.',
1747
+ });
1748
+ assert.equal(completed.status, 0, `completion hook failed: ${completed.stderr || completed.stdout}`);
1749
+ const skillState = JSON.parse(await readFile(join(sessionStateDir, 'skill-active-state.json'), 'utf-8'));
1750
+ assert.equal(skillState.active, false);
1751
+ assert.equal(skillState.phase, 'completing');
1752
+ assert.equal(skillState.input_lock?.active, false);
1753
+ assert.equal(skillState.input_lock?.exit_reason, 'success');
1754
+ assert.ok(skillState.input_lock?.released_at);
1755
+ const modeState = JSON.parse(await readFile(join(sessionStateDir, 'deep-interview-state.json'), 'utf-8'));
1756
+ assert.equal(modeState.active, false);
1757
+ assert.equal(modeState.current_phase, 'completing');
1758
+ assert.ok(modeState.completed_at);
1759
+ assert.equal(modeState.input_lock?.active, false);
1760
+ assert.equal(modeState.input_lock?.exit_reason, 'success');
1761
+ assert.ok(modeState.input_lock?.released_at);
1762
+ });
1763
+ });
1275
1764
  for (const blockedResponse of ['yes', 'y', 'proceed', 'continue', 'ok', 'sure', 'go ahead']) {
1276
1765
  it(`blocks deep-interview auto-approval injection for "${blockedResponse}"`, async () => {
1277
1766
  await withTempWorkingDir(async (cwd) => {
@@ -1402,6 +1891,156 @@ exit 0
1402
1891
  assert.equal(tmuxLog.includes(`send-keys -t %99 -l ${NEXT_I_SHOULD_RESPONSE} [OMX_TMUX_INJECT]`), false);
1403
1892
  });
1404
1893
  });
1894
+ it('allows non-blocked custom deep-interview auto-nudge responses to continue', async () => {
1895
+ await withTempWorkingDir(async (cwd) => {
1896
+ const omxDir = join(cwd, '.omx');
1897
+ const stateDir = join(omxDir, 'state');
1898
+ const logsDir = join(omxDir, 'logs');
1899
+ const codexHome = join(cwd, 'codex-home');
1900
+ const fakeBinDir = join(cwd, 'fake-bin');
1901
+ const tmuxLogPath = join(cwd, 'tmux.log');
1902
+ const capturePath = join(cwd, 'capture.txt');
1903
+ const customResponse = 'advance with the next interview question';
1904
+ const sessionStateDir = join(stateDir, 'sessions', 'sess-managed');
1905
+ await mkdir(logsDir, { recursive: true });
1906
+ await mkdir(stateDir, { recursive: true });
1907
+ await mkdir(codexHome, { recursive: true });
1908
+ await mkdir(fakeBinDir, { recursive: true });
1909
+ await writeJson(join(codexHome, '.omx-config.json'), {
1910
+ autoNudge: { enabled: true, delaySec: 0, stallMs: 0, response: customResponse },
1911
+ });
1912
+ await writeManagedSessionState(stateDir, cwd);
1913
+ await mkdir(sessionStateDir, { recursive: true });
1914
+ await writeJson(join(sessionStateDir, 'skill-active-state.json'), {
1915
+ version: 1,
1916
+ active: true,
1917
+ skill: 'deep-interview',
1918
+ keyword: 'deep interview',
1919
+ phase: 'executing',
1920
+ activated_at: '2026-02-25T00:00:00.000Z',
1921
+ updated_at: '2026-02-25T00:00:00.000Z',
1922
+ source: 'keyword-detector',
1923
+ input_lock: {
1924
+ active: true,
1925
+ scope: 'deep-interview-auto-approval',
1926
+ acquired_at: '2026-02-25T00:00:00.000Z',
1927
+ blocked_inputs: DEEP_INTERVIEW_BLOCKED_APPROVAL_INPUTS,
1928
+ message: 'Deep interview is active; auto-approval shortcuts are blocked until the interview finishes.',
1929
+ },
1930
+ });
1931
+ await writeFile(capturePath, 'OpenAI Codex\n› ');
1932
+ await writeFile(join(fakeBinDir, 'tmux'), buildFakeTmux(tmuxLogPath));
1933
+ await chmod(join(fakeBinDir, 'tmux'), 0o755);
1934
+ const result = runNotifyHook(cwd, fakeBinDir, codexHome, {
1935
+ 'last-assistant-message': 'Keep going and finish the cleanup.',
1936
+ }, {
1937
+ OMX_TEST_CAPTURE_FILE: capturePath,
1938
+ });
1939
+ assert.equal(result.status, 0, `hook failed: ${result.stderr || result.stdout}`);
1940
+ const tmuxLog = await readFile(tmuxLogPath, 'utf-8');
1941
+ assert.match(tmuxLog, new RegExp(`send-keys -t %99 -l ${customResponse} \\[OMX_TMUX_INJECT\\]`), 'should allow a non-blocked continuation response during deep interview');
1942
+ });
1943
+ });
1944
+ it('keeps autoresearch active when assistant claims completion without validator evidence', async () => {
1945
+ await withTempWorkingDir(async (cwd) => {
1946
+ const omxDir = join(cwd, '.omx');
1947
+ const stateDir = join(omxDir, 'state');
1948
+ const logsDir = join(omxDir, 'logs');
1949
+ const codexHome = join(cwd, 'codex-home');
1950
+ const fakeBinDir = join(cwd, 'fake-bin');
1951
+ const tmuxLogPath = join(cwd, 'tmux.log');
1952
+ await mkdir(logsDir, { recursive: true });
1953
+ await mkdir(stateDir, { recursive: true });
1954
+ await mkdir(codexHome, { recursive: true });
1955
+ await mkdir(fakeBinDir, { recursive: true });
1956
+ await writeJson(join(codexHome, '.omx-config.json'), {
1957
+ autoNudge: { enabled: true, delaySec: 0, stallMs: 0 },
1958
+ });
1959
+ await writeManagedSessionState(stateDir, cwd);
1960
+ const sessionStateDir = join(stateDir, 'sessions', 'sess-managed');
1961
+ await mkdir(sessionStateDir, { recursive: true });
1962
+ await writeJson(join(sessionStateDir, 'skill-active-state.json'), {
1963
+ active: true,
1964
+ skill: 'autoresearch',
1965
+ keyword: '$autoresearch',
1966
+ phase: 'executing',
1967
+ source: 'keyword-detector',
1968
+ session_id: 'sess-managed',
1969
+ });
1970
+ await writeJson(join(sessionStateDir, 'autoresearch-state.json'), {
1971
+ active: true,
1972
+ mode: 'autoresearch',
1973
+ current_phase: 'executing',
1974
+ session_id: 'sess-managed',
1975
+ validation_mode: 'mission-validator-script',
1976
+ mission_validator_command: 'node scripts/validate.js',
1977
+ completion_artifact_path: '.omx/specs/autoresearch-demo/completion.json',
1978
+ });
1979
+ await writeFile(join(fakeBinDir, 'tmux'), buildFakeTmux(tmuxLogPath));
1980
+ await chmod(join(fakeBinDir, 'tmux'), 0o755);
1981
+ const result = runNotifyHook(cwd, fakeBinDir, codexHome, {
1982
+ 'last-assistant-message': 'All tests pass. Completed with summary.',
1983
+ });
1984
+ assert.equal(result.status, 0, `hook failed: ${result.stderr || result.stdout}`);
1985
+ const skillState = JSON.parse(await readFile(join(sessionStateDir, 'skill-active-state.json'), 'utf-8'));
1986
+ assert.equal(skillState.active, true);
1987
+ assert.equal(skillState.phase, 'executing');
1988
+ assert.equal(skillState.autoresearch_completion_reason, 'missing_or_invalid_completion_artifact');
1989
+ });
1990
+ });
1991
+ it('completes autoresearch when validator artifact passes', async () => {
1992
+ await withTempWorkingDir(async (cwd) => {
1993
+ const omxDir = join(cwd, '.omx');
1994
+ const stateDir = join(omxDir, 'state');
1995
+ const logsDir = join(omxDir, 'logs');
1996
+ const codexHome = join(cwd, 'codex-home');
1997
+ const fakeBinDir = join(cwd, 'fake-bin');
1998
+ const tmuxLogPath = join(cwd, 'tmux.log');
1999
+ const specDir = join(cwd, '.omx', 'specs', 'autoresearch-demo');
2000
+ await mkdir(logsDir, { recursive: true });
2001
+ await mkdir(stateDir, { recursive: true });
2002
+ await mkdir(codexHome, { recursive: true });
2003
+ await mkdir(fakeBinDir, { recursive: true });
2004
+ await mkdir(specDir, { recursive: true });
2005
+ await writeJson(join(codexHome, '.omx-config.json'), {
2006
+ autoNudge: { enabled: true, delaySec: 0, stallMs: 0 },
2007
+ });
2008
+ await writeManagedSessionState(stateDir, cwd);
2009
+ const sessionStateDir = join(stateDir, 'sessions', 'sess-managed');
2010
+ await mkdir(sessionStateDir, { recursive: true });
2011
+ await writeJson(join(sessionStateDir, 'skill-active-state.json'), {
2012
+ active: true,
2013
+ skill: 'autoresearch',
2014
+ keyword: '$autoresearch',
2015
+ phase: 'reviewing',
2016
+ source: 'keyword-detector',
2017
+ session_id: 'sess-managed',
2018
+ });
2019
+ await writeJson(join(sessionStateDir, 'autoresearch-state.json'), {
2020
+ active: true,
2021
+ mode: 'autoresearch',
2022
+ current_phase: 'reviewing',
2023
+ session_id: 'sess-managed',
2024
+ validation_mode: 'mission-validator-script',
2025
+ mission_validator_command: 'node scripts/validate.js',
2026
+ completion_artifact_path: '.omx/specs/autoresearch-demo/completion.json',
2027
+ });
2028
+ await writeJson(join(specDir, 'completion.json'), {
2029
+ status: 'passed',
2030
+ passed: true,
2031
+ });
2032
+ await writeFile(join(fakeBinDir, 'tmux'), buildFakeTmux(tmuxLogPath));
2033
+ await chmod(join(fakeBinDir, 'tmux'), 0o755);
2034
+ const result = runNotifyHook(cwd, fakeBinDir, codexHome, {
2035
+ 'last-assistant-message': 'Completed with final summary after validator pass.',
2036
+ });
2037
+ assert.equal(result.status, 0, `hook failed: ${result.stderr || result.stdout}`);
2038
+ const skillState = JSON.parse(await readFile(join(sessionStateDir, 'skill-active-state.json'), 'utf-8'));
2039
+ assert.equal(skillState.active, false);
2040
+ assert.equal(skillState.phase, 'completing');
2041
+ assert.equal(skillState.autoresearch_completion_reason, 'validator_passed');
2042
+ });
2043
+ });
1405
2044
  it('releases the deep-interview input lock on success', async () => {
1406
2045
  await withTempWorkingDir(async (cwd) => {
1407
2046
  const omxDir = join(cwd, '.omx');
@@ -1450,6 +2089,65 @@ exit 0
1450
2089
  assert.equal(skillState.input_lock?.exit_reason, 'success');
1451
2090
  });
1452
2091
  });
2092
+ it('does not release deep-interview state from generic progress prose', async () => {
2093
+ await withTempWorkingDir(async (cwd) => {
2094
+ const omxDir = join(cwd, '.omx');
2095
+ const stateDir = join(omxDir, 'state');
2096
+ const logsDir = join(omxDir, 'logs');
2097
+ const codexHome = join(cwd, 'codex-home');
2098
+ const fakeBinDir = join(cwd, 'fake-bin');
2099
+ await mkdir(logsDir, { recursive: true });
2100
+ await mkdir(stateDir, { recursive: true });
2101
+ await mkdir(codexHome, { recursive: true });
2102
+ await mkdir(fakeBinDir, { recursive: true });
2103
+ await writeJson(join(codexHome, '.omx-config.json'), {
2104
+ autoNudge: { enabled: true, delaySec: 0, stallMs: 0 },
2105
+ });
2106
+ await writeManagedSessionState(stateDir, cwd);
2107
+ const sessionStateDir = join(stateDir, 'sessions', 'sess-managed');
2108
+ await mkdir(sessionStateDir, { recursive: true });
2109
+ await writeJson(join(sessionStateDir, 'skill-active-state.json'), {
2110
+ version: 1,
2111
+ active: true,
2112
+ skill: 'deep-interview',
2113
+ keyword: 'deep interview',
2114
+ phase: 'executing',
2115
+ activated_at: '2026-02-25T00:00:00.000Z',
2116
+ updated_at: '2026-02-25T00:00:00.000Z',
2117
+ source: 'keyword-detector',
2118
+ input_lock: {
2119
+ active: true,
2120
+ scope: 'deep-interview-auto-approval',
2121
+ acquired_at: '2026-02-25T00:00:00.000Z',
2122
+ blocked_inputs: DEEP_INTERVIEW_BLOCKED_APPROVAL_INPUTS,
2123
+ message: 'Deep interview is active; auto-approval shortcuts are blocked until the interview finishes.',
2124
+ },
2125
+ });
2126
+ await writeJson(join(sessionStateDir, 'deep-interview-state.json'), {
2127
+ active: true,
2128
+ mode: 'deep-interview',
2129
+ current_phase: 'intent-first',
2130
+ started_at: '2026-02-25T00:00:00.000Z',
2131
+ updated_at: '2026-02-25T00:00:00.000Z',
2132
+ });
2133
+ await writeFile(join(fakeBinDir, 'tmux'), buildFakeTmux(join(cwd, 'tmux.log')));
2134
+ await chmod(join(fakeBinDir, 'tmux'), 0o755);
2135
+ const result = runNotifyHook(cwd, fakeBinDir, codexHome, {
2136
+ 'last-assistant-message': 'Summary so far: done with the first round of questions.',
2137
+ });
2138
+ assert.equal(result.status, 0, `hook failed: ${result.stderr || result.stdout}`);
2139
+ const skillState = JSON.parse(await readFile(join(sessionStateDir, 'skill-active-state.json'), 'utf-8'));
2140
+ assert.equal(skillState.active, true);
2141
+ assert.notEqual(skillState.phase, 'completing');
2142
+ assert.equal(skillState.input_lock?.active, true);
2143
+ assert.equal(skillState.input_lock?.released_at || '', '');
2144
+ assert.equal(skillState.input_lock?.exit_reason || '', '');
2145
+ const modeState = JSON.parse(await readFile(join(sessionStateDir, 'deep-interview-state.json'), 'utf-8'));
2146
+ assert.equal(modeState.active, true);
2147
+ assert.equal(modeState.current_phase, 'intent-first');
2148
+ assert.equal(modeState.completed_at || '', '');
2149
+ });
2150
+ });
1453
2151
  it('releases the deep-interview input lock on error', async () => {
1454
2152
  await withTempWorkingDir(async (cwd) => {
1455
2153
  const omxDir = join(cwd, '.omx');
@@ -1627,6 +2325,7 @@ exit 0
1627
2325
  const codexHome = join(cwd, 'codex-home');
1628
2326
  const fakeBinDir = join(cwd, 'fake-bin');
1629
2327
  const tmuxLogPath = join(cwd, 'tmux.log');
2328
+ const captureFile = join(cwd, 'capture-output.txt');
1630
2329
  await mkdir(logsDir, { recursive: true });
1631
2330
  await mkdir(stateDir, { recursive: true });
1632
2331
  await mkdir(codexHome, { recursive: true });
@@ -1634,13 +2333,15 @@ exit 0
1634
2333
  await writeJson(join(codexHome, '.omx-config.json'), {
1635
2334
  autoNudge: { enabled: true, delaySec: 0, stallMs: 0 },
1636
2335
  });
2336
+ await writeFile(captureFile, 'Here are the results.\nKeep going and finish the implementation.\n› ');
1637
2337
  await writeFile(join(fakeBinDir, 'tmux'), buildFakeTmux(tmuxLogPath));
1638
2338
  await chmod(join(fakeBinDir, 'tmux'), 0o755);
1639
2339
  const result = runNotifyHook(cwd, fakeBinDir, codexHome, {
1640
- 'last-assistant-message': 'Keep going and finish the focused cleanup.',
2340
+ 'last-assistant-message': 'clean output with no stall',
1641
2341
  }, {
1642
2342
  TMUX_PANE: '', // No pane available
1643
2343
  TMUX: '',
2344
+ OMX_TEST_CAPTURE_FILE: captureFile,
1644
2345
  });
1645
2346
  assert.equal(result.status, 0, `hook failed: ${result.stderr || result.stdout}`);
1646
2347
  if (existsSync(tmuxLogPath)) {