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
@@ -2,6 +2,7 @@ import { execFileSync } from "child_process";
2
2
  import { existsSync, readFileSync } from "fs";
3
3
  import { mkdir, readFile, readdir, writeFile } from "fs/promises";
4
4
  import { join, resolve } from "path";
5
+ import { pathToFileURL } from "url";
5
6
  import { readModeState, readModeStateForSession, updateModeState } from "../modes/base.js";
6
7
  import {
7
8
  listActiveSkills,
@@ -9,7 +10,12 @@ import {
9
10
  } from "../state/skill-active.js";
10
11
  import { readSubagentSessionSummary } from "../subagents/tracker.js";
11
12
  import { resolveCanonicalTeamStateRoot } from "../team/state-root.js";
12
- import { readUsableSessionState, reconcileNativeSessionStart } from "../hooks/session.js";
13
+ import {
14
+ isSessionStateUsable,
15
+ readSessionState,
16
+ readUsableSessionState,
17
+ reconcileNativeSessionStart,
18
+ } from "../hooks/session.js";
13
19
  import {
14
20
  appendTeamEvent,
15
21
  readTeamLeaderAttention,
@@ -43,7 +49,21 @@ import {
43
49
  import type { HookEventEnvelope } from "../hooks/extensibility/types.js";
44
50
  import { dispatchHookEvent } from "../hooks/extensibility/dispatcher.js";
45
51
  import { reconcileHudForPromptSubmit } from "../hud/reconcile.js";
52
+ import { shellEscapeSingle } from "../hud/tmux.js";
46
53
  import { onSessionStart as buildWikiSessionStartContext } from "../wiki/lifecycle.js";
54
+ import { readAutoresearchCompletionStatus, readAutoresearchModeState } from "../autoresearch/skill-validation.js";
55
+ import { shouldContinueRun } from "../runtime/run-loop.js";
56
+ import { triagePrompt } from "../hooks/triage-heuristic.js";
57
+ import { readTriageConfig } from "../hooks/triage-config.js";
58
+ import {
59
+ readTriageState,
60
+ writeTriageState,
61
+ shouldSuppressFollowup,
62
+ promptSignature,
63
+ type TriageStateFile,
64
+ } from "../hooks/triage-state.js";
65
+ import { isPendingDeepInterviewQuestionEnforcement } from "../question/deep-interview.js";
66
+ import { resolveOmxCliEntryPath } from "../utils/paths.js";
47
67
 
48
68
  type CodexHookEventName =
49
69
  | "SessionStart"
@@ -57,6 +77,7 @@ type CodexHookPayload = Record<string, unknown>;
57
77
  interface NativeHookDispatchOptions {
58
78
  cwd?: string;
59
79
  sessionOwnerPid?: number;
80
+ reconcileHudForPromptSubmitFn?: typeof reconcileHudForPromptSubmit;
60
81
  }
61
82
 
62
83
  export interface NativeHookDispatchResult {
@@ -66,7 +87,6 @@ export interface NativeHookDispatchResult {
66
87
  outputJson: Record<string, unknown> | null;
67
88
  }
68
89
 
69
- const TERMINAL_RALPH_PHASES = new Set(["complete", "failed", "cancelled"]);
70
90
  const TERMINAL_MODE_PHASES = new Set(["complete", "failed", "cancelled"]);
71
91
  const SKILL_STOP_BLOCKERS = new Set(["ralplan"]);
72
92
  const TEAM_TERMINAL_TASK_STATUSES = new Set(["completed", "failed"]);
@@ -79,6 +99,18 @@ const STABLE_FINAL_RECOMMENDATION_PATTERNS = [
79
99
  ] as const;
80
100
  const RELEASE_READINESS_FINALIZE_SYSTEM_MESSAGE =
81
101
  "OMX release-readiness detected a stable final recommendation with no active worker tasks; emit one concise final decision summary and finalize.";
102
+ const EXECUTION_HANDOFF_PATTERNS = [
103
+ /^(?:好|好的|行|可以|那就|那现在)?[,,\s]*(?:开始|继续|直接)\s*(?:执行|优化|实现|修改|修复)(?=$|\s|[,,。.!!??])/u,
104
+ /(?:按照|按|基于)(?:这个|上述|当前)?\s*(?:plan|计划|方案).{0,16}(?:开始|继续|直接)?\s*(?:执行|优化|实现|修改|修复)/u,
105
+ /(?:不用|别|不要).{0,6}讨论/u,
106
+ /\b(?:start|begin|go ahead(?: and)?|proceed(?: now)?)\s+(?:to\s+)?(?:implement|execute|apply|fix)\b/i,
107
+ /\b(?:according to|based on)\s+(?:the|this|that)\s+plan\b.{0,20}\b(?:start|begin|proceed(?: now)?|go ahead(?: and)?)\b/i,
108
+ ] as const;
109
+ const SHORT_FOLLOWUP_PRIORITY_PATTERNS = [
110
+ /^(?:继续|接着|然后|那就|那现在|还有(?:一个)?问题|这些优化都做了么|这些都做了么|现在呢|本轮|当前轮|这一轮)/u,
111
+ /(?:按照|按|基于)(?:这个|上述|当前)?(?:plan|计划|方案)/u,
112
+ /\b(?:follow up|latest request|this turn|current turn|newest request)\b/i,
113
+ ] as const;
82
114
 
83
115
  function safeString(value: unknown): string {
84
116
  return typeof value === "string" ? value : "";
@@ -97,6 +129,34 @@ function safePositiveInteger(value: unknown): number | null {
97
129
  return null;
98
130
  }
99
131
 
132
+ function normalizePromptSignalText(text: string): string {
133
+ return text.trim().replace(/\s+/g, " ");
134
+ }
135
+
136
+ function looksLikeExecutionHandoffPrompt(prompt: string): boolean {
137
+ const normalized = normalizePromptSignalText(prompt);
138
+ if (!normalized) return false;
139
+ return EXECUTION_HANDOFF_PATTERNS.some((pattern) => pattern.test(normalized));
140
+ }
141
+
142
+ function looksLikeShortFollowupPrompt(prompt: string): boolean {
143
+ const normalized = normalizePromptSignalText(prompt);
144
+ if (!normalized) return false;
145
+ if (looksLikeExecutionHandoffPrompt(normalized)) return true;
146
+ if (normalized.length > 240) return false;
147
+ return SHORT_FOLLOWUP_PRIORITY_PATTERNS.some((pattern) => pattern.test(normalized));
148
+ }
149
+
150
+ function buildPromptPriorityMessage(prompt: string): string | null {
151
+ if (looksLikeExecutionHandoffPrompt(prompt)) {
152
+ return "Newest user input is an execution handoff for the current task. Treat it as authorization to act now against the latest approved plan/request. Do not restate the prior plan unless the user explicitly asks for a recap or status update.";
153
+ }
154
+ if (looksLikeShortFollowupPrompt(prompt)) {
155
+ return "Newest user input is a same-thread follow-up. Answer that latest follow-up directly and prefer it over older unresolved prompts when choosing what to do next.";
156
+ }
157
+ return null;
158
+ }
159
+
100
160
  function readHookEventName(payload: CodexHookPayload): CodexHookEventName | null {
101
161
  const raw = safeString(
102
162
  payload.hook_event_name
@@ -151,15 +211,26 @@ function readPromptText(payload: CodexHookPayload): string {
151
211
  function sanitizePayloadForHookContext(
152
212
  payload: CodexHookPayload,
153
213
  hookEventName: CodexHookEventName,
214
+ canonicalSessionId = "",
154
215
  ): CodexHookPayload {
155
- if (hookEventName !== "UserPromptSubmit") return payload;
156
-
157
216
  const sanitized = { ...payload };
158
- delete sanitized.prompt;
159
- delete sanitized.input;
160
- delete sanitized.user_prompt;
161
- delete sanitized.userPrompt;
162
- delete sanitized.text;
217
+
218
+ if (hookEventName === "UserPromptSubmit") {
219
+ delete sanitized.prompt;
220
+ delete sanitized.input;
221
+ delete sanitized.user_prompt;
222
+ delete sanitized.userPrompt;
223
+ delete sanitized.text;
224
+ return sanitized;
225
+ }
226
+
227
+ if (hookEventName === "Stop") {
228
+ delete sanitized.stop_hook_active;
229
+ delete sanitized.stopHookActive;
230
+ delete sanitized.sessionId;
231
+ sanitized.session_id = canonicalSessionId.trim() || safeString(payload.session_id ?? payload.sessionId).trim();
232
+ }
233
+
163
234
  return sanitized;
164
235
  }
165
236
 
@@ -167,13 +238,14 @@ function buildBaseContext(
167
238
  cwd: string,
168
239
  payload: CodexHookPayload,
169
240
  hookEventName: CodexHookEventName,
241
+ canonicalSessionId = "",
170
242
  ): Record<string, unknown> {
171
243
  return {
172
244
  cwd,
173
245
  project_path: cwd,
174
246
  transcript_path: safeString(payload.transcript_path ?? payload.transcriptPath) || null,
175
247
  source: safeString(payload.source),
176
- payload: sanitizePayloadForHookContext(payload, hookEventName),
248
+ payload: sanitizePayloadForHookContext(payload, hookEventName, canonicalSessionId),
177
249
  };
178
250
  }
179
251
 
@@ -196,27 +268,45 @@ function formatPhase(value: unknown, fallback = "active"): string {
196
268
  return phase || fallback;
197
269
  }
198
270
 
271
+ async function readActiveAutoresearchState(
272
+ cwd: string,
273
+ sessionId?: string,
274
+ ): Promise<Record<string, unknown> | null> {
275
+ const normalizedSessionId = sessionId?.trim() || undefined;
276
+ if (!normalizedSessionId) return null;
277
+ const state = await readAutoresearchModeState(cwd, normalizedSessionId);
278
+ if (state?.active !== true) return null;
279
+ if (!isNonTerminalPhase(state.current_phase ?? state.currentPhase ?? 'executing')) return null;
280
+ return state;
281
+ }
282
+
199
283
  async function readActiveRalphState(
200
284
  stateDir: string,
201
285
  preferredSessionId?: string,
202
286
  ): Promise<Record<string, unknown> | null> {
203
- const sessionInfo = await readUsableSessionState(resolve(stateDir, "..", ".."));
204
- const currentOmxSessionId = safeString(sessionInfo?.session_id).trim();
287
+ const cwd = resolve(stateDir, "..", "..");
288
+ const [rawSessionInfo, usableSessionInfo] = await Promise.all([
289
+ readSessionState(cwd),
290
+ readUsableSessionState(cwd),
291
+ ]);
292
+ const currentOmxSessionId = safeString(usableSessionInfo?.session_id).trim();
293
+ const staleCurrentSessionId = rawSessionInfo && !isSessionStateUsable(rawSessionInfo, cwd)
294
+ ? safeString(rawSessionInfo.session_id).trim()
295
+ : "";
205
296
  const sessionCandidates = [...new Set([
206
297
  safeString(preferredSessionId).trim(),
207
298
  currentOmxSessionId,
208
299
  ].filter(Boolean))];
209
300
 
301
+ // Ralph Stop stays authoritative-scope-only once the Stop payload is session-bound.
302
+ // That is intentionally stricter than generic state MCP reads: do not scan sibling
303
+ // session scopes or fall back to root when a current/explicit session is in play.
210
304
  for (const sessionId of sessionCandidates) {
211
- const sessionScoped = await readJsonIfExists(
212
- join(stateDir, "sessions", sessionId, "ralph-state.json"),
213
- );
214
- if (
215
- sessionScoped?.active === true
216
- && !TERMINAL_RALPH_PHASES.has(
217
- safeString(sessionScoped.current_phase).trim().toLowerCase(),
218
- )
219
- ) {
305
+ if (staleCurrentSessionId && sessionId === staleCurrentSessionId) {
306
+ continue;
307
+ }
308
+ const sessionScoped = await readStopSessionPinnedState("ralph-state.json", cwd, sessionId);
309
+ if (sessionScoped?.active === true && shouldContinueRun(sessionScoped)) {
220
310
  return sessionScoped;
221
311
  }
222
312
  }
@@ -224,26 +314,10 @@ async function readActiveRalphState(
224
314
  if (sessionCandidates.length > 0) return null;
225
315
 
226
316
  const direct = await readJsonIfExists(join(stateDir, "ralph-state.json"));
227
- if (direct?.active === true && !TERMINAL_RALPH_PHASES.has(safeString(direct.current_phase).trim().toLowerCase())) {
317
+ if (direct?.active === true && shouldContinueRun(direct)) {
228
318
  return direct;
229
319
  }
230
320
 
231
- const sessionsRoot = join(stateDir, "sessions");
232
- if (!existsSync(sessionsRoot)) return null;
233
- const entries = await readdir(sessionsRoot, { withFileTypes: true }).catch(() => []);
234
- for (const entry of entries) {
235
- if (!entry.isDirectory()) continue;
236
- const candidate = await readJsonIfExists(join(sessionsRoot, entry.name, "ralph-state.json"));
237
- if (
238
- candidate?.active === true
239
- && !TERMINAL_RALPH_PHASES.has(
240
- safeString(candidate.current_phase).trim().toLowerCase(),
241
- )
242
- ) {
243
- return candidate;
244
- }
245
- }
246
-
247
321
  return null;
248
322
  }
249
323
 
@@ -345,32 +419,55 @@ function resolveSessionOwnerPid(payload: CodexHookPayload): number {
345
419
  return process.pid;
346
420
  }
347
421
 
348
- async function ensureOmxGitignoreEntry(cwd: string): Promise<{ changed: boolean; gitignorePath?: string }> {
349
- let repoRoot = "";
422
+ function tryReadGitValue(cwd: string, args: string[]): string | null {
350
423
  try {
351
- repoRoot = execFileSync("git", ["rev-parse", "--show-toplevel"], {
424
+ const value = execFileSync("git", args, {
352
425
  cwd,
353
426
  encoding: "utf-8",
354
427
  stdio: ["ignore", "pipe", "ignore"],
355
428
  windowsHide: true,
356
429
  }).trim();
430
+ return value || null;
357
431
  } catch {
358
- return { changed: false };
432
+ return null;
433
+ }
434
+ }
435
+
436
+ function isPathIgnoredByGit(cwd: string, path: string): boolean {
437
+ try {
438
+ execFileSync("git", ["check-ignore", "-q", path], {
439
+ cwd,
440
+ stdio: ["ignore", "ignore", "ignore"],
441
+ windowsHide: true,
442
+ });
443
+ return true;
444
+ } catch {
445
+ return false;
359
446
  }
447
+ }
448
+
449
+ async function ensureOmxLocalIgnoreEntry(cwd: string): Promise<{ changed: boolean; excludePath?: string }> {
450
+ const repoRoot = tryReadGitValue(cwd, ["rev-parse", "--show-toplevel"]);
360
451
  if (!repoRoot) return { changed: false };
452
+ if (isPathIgnoredByGit(repoRoot, ".omx/")) {
453
+ return { changed: false };
454
+ }
455
+
456
+ const excludePathValue = tryReadGitValue(repoRoot, ["rev-parse", "--git-path", "info/exclude"]);
457
+ if (!excludePathValue) return { changed: false };
458
+ const excludePath = resolve(repoRoot, excludePathValue);
361
459
 
362
- const gitignorePath = join(repoRoot, ".gitignore");
363
- const existing = existsSync(gitignorePath)
364
- ? await readFile(gitignorePath, "utf-8")
460
+ const existing = existsSync(excludePath)
461
+ ? await readFile(excludePath, "utf-8")
365
462
  : "";
366
463
  const lines = existing.split(/\r?\n/).map((line) => line.trim());
367
464
  if (lines.includes(".omx/")) {
368
- return { changed: false, gitignorePath };
465
+ return { changed: false, excludePath };
369
466
  }
370
467
 
371
468
  const next = `${existing}${existing.endsWith("\n") || existing.length === 0 ? "" : "\n"}.omx/\n`;
372
- await writeFile(gitignorePath, next);
373
- return { changed: true, gitignorePath };
469
+ await writeFile(excludePath, next);
470
+ return { changed: true, excludePath };
374
471
  }
375
472
 
376
473
  async function buildSessionStartContext(
@@ -379,9 +476,9 @@ async function buildSessionStartContext(
379
476
  ): Promise<string | null> {
380
477
  const sections: string[] = [];
381
478
 
382
- const gitignoreResult = await ensureOmxGitignoreEntry(cwd);
383
- if (gitignoreResult.changed) {
384
- sections.push(`Added .omx/ to ${gitignoreResult.gitignorePath} to keep local OMX state out of source control.`);
479
+ const localIgnoreResult = await ensureOmxLocalIgnoreEntry(cwd);
480
+ if (localIgnoreResult.changed) {
481
+ sections.push(`Added .omx/ to ${localIgnoreResult.excludePath} to keep local OMX state out of source control without mutating tracked repo ignores.`);
385
482
  }
386
483
 
387
484
  const modeSummaries: string[] = [];
@@ -469,11 +566,22 @@ async function buildSessionStartContext(
469
566
  return sections.length > 0 ? sections.join("\n\n") : null;
470
567
  }
471
568
 
472
- function buildAdditionalContextMessage(prompt: string, skillState?: SkillActiveState | null): string | null {
569
+ function buildDeepInterviewQuestionBridgeInstruction(cwd: string): string {
570
+ const omxBin = resolveOmxCliEntryPath({ cwd }) || process.argv[1] || "omx";
571
+ const bridgeCommand = `${shellEscapeSingle(process.execPath)} ${shellEscapeSingle(omxBin)} question`;
572
+ return `Deep-interview must ask each interview round via \`omx question\`; do not fall back to \`request_user_input\` or plain-text questioning. If bare \`omx question\` is unavailable in this reused session, use the current-session CLI bridge command: \`${bridgeCommand}\`. Stop remains blocked while a deep-interview question obligation is pending.`;
573
+ }
574
+
575
+ function buildAdditionalContextMessage(
576
+ prompt: string,
577
+ skillState?: SkillActiveState | null,
578
+ cwd: string = process.cwd(),
579
+ ): string | null {
473
580
  if (!prompt) return null;
581
+ const promptPriorityMessage = buildPromptPriorityMessage(prompt);
474
582
  const matches = detectKeywords(prompt);
475
583
  const match = detectPrimaryKeyword(prompt);
476
- if (!match) return null;
584
+ if (!match) return promptPriorityMessage;
477
585
  const detectedKeywordMessage = matches.length > 1
478
586
  ? `OMX native UserPromptSubmit detected workflow keywords ${matches.map((entry) => `"${entry.keyword}" -> ${entry.skill}`).join(", ")}.`
479
587
  : `OMX native UserPromptSubmit detected workflow keyword "${match.keyword}" -> ${match.skill}.`;
@@ -487,6 +595,9 @@ function buildAdditionalContextMessage(prompt: string, skillState?: SkillActiveS
487
595
  const ralphPromptActivationNote = skillState?.initialized_mode === "ralph"
488
596
  ? "Prompt-side `$ralph` activation seeds Ralph workflow state only; it does not invoke `omx ralph`. Use `omx ralph --prd ...` only when you explicitly want the PRD-gated CLI startup path."
489
597
  : null;
598
+ const deepInterviewPromptActivationNote = skillState?.initialized_mode === "deep-interview"
599
+ ? buildDeepInterviewQuestionBridgeInstruction(cwd)
600
+ : null;
490
601
  const combinedTransitionMessage = (() => {
491
602
  if (!skillState?.transition_message) return null;
492
603
  if (matches.length <= 1 || activeSkills.length <= 1) return skillState.transition_message;
@@ -499,6 +610,7 @@ function buildAdditionalContextMessage(prompt: string, skillState?: SkillActiveS
499
610
  return [
500
611
  `OMX native UserPromptSubmit denied workflow keyword "${match.keyword}" -> ${match.skill}.`,
501
612
  skillState.transition_error,
613
+ promptPriorityMessage,
502
614
  'Follow AGENTS.md routing and preserve workflow transition and planning-safety rules.',
503
615
  ].join(' ');
504
616
  }
@@ -511,6 +623,7 @@ function buildAdditionalContextMessage(prompt: string, skillState?: SkillActiveS
511
623
  deferredSkills.length > 0
512
624
  ? `planning preserved over simultaneous execution follow-up; deferred skills: ${deferredSkills.join(", ")}.`
513
625
  : null,
626
+ promptPriorityMessage,
514
627
  skillState.initialized_mode && skillState.initialized_state_path
515
628
  ? `skill: ${skillState.initialized_mode} activated and initial state initialized at ${skillState.initialized_state_path}; write subsequent updates via omx_state MCP.`
516
629
  : null,
@@ -532,7 +645,9 @@ function buildAdditionalContextMessage(prompt: string, skillState?: SkillActiveS
532
645
  deferredSkills.length > 0
533
646
  ? `planning preserved over simultaneous execution follow-up; deferred skills: ${deferredSkills.join(", ")}.`
534
647
  : null,
648
+ promptPriorityMessage,
535
649
  initializedStateMessage,
650
+ deepInterviewPromptActivationNote,
536
651
  "Use the durable OMX team runtime via `omx team ...` for coordinated execution; do not replace it with in-process fanout.",
537
652
  "If you need runtime syntax, run `omx team --help` yourself.",
538
653
  "Follow AGENTS.md routing and preserve workflow transition and planning-safety rules.",
@@ -546,13 +661,15 @@ function buildAdditionalContextMessage(prompt: string, skillState?: SkillActiveS
546
661
  deferredSkills.length > 0
547
662
  ? `planning preserved over simultaneous execution follow-up; deferred skills: ${deferredSkills.join(", ")}.`
548
663
  : null,
664
+ promptPriorityMessage,
549
665
  `skill: ${skillState.initialized_mode} activated and initial state initialized at ${skillState.initialized_state_path}; write subsequent updates via omx_state MCP.`,
666
+ deepInterviewPromptActivationNote,
550
667
  ralphPromptActivationNote,
551
668
  "Follow AGENTS.md routing and preserve workflow transition and planning-safety rules.",
552
669
  ].join(" ");
553
670
  }
554
671
 
555
- return `${detectedKeywordMessage} Follow AGENTS.md routing and preserve workflow transition and planning-safety rules.`;
672
+ return [detectedKeywordMessage, promptPriorityMessage, "Follow AGENTS.md routing and preserve workflow transition and planning-safety rules."].filter(Boolean).join(" ");
556
673
  }
557
674
 
558
675
  function parseTeamWorkerEnv(rawValue: string): { teamName: string; workerName: string } | null {
@@ -678,7 +795,7 @@ async function buildModeBasedStopOutput(
678
795
  const state = sessionId
679
796
  ? await readModeStateForSession(mode, sessionId, cwd)
680
797
  : await readModeState(mode, cwd);
681
- if (state?.active !== true || !isNonTerminalPhase(state.current_phase)) return null;
798
+ if (!state || !shouldContinueRun(state)) return null;
682
799
  const phase = formatPhase(state.current_phase);
683
800
  return {
684
801
  decision: "block",
@@ -927,6 +1044,54 @@ async function readStopAutoNudgePhase(
927
1044
  return modePhase === "intent-first" ? "planning" : "";
928
1045
  }
929
1046
 
1047
+ async function buildDeepInterviewQuestionStopOutput(
1048
+ cwd: string,
1049
+ sessionId: string,
1050
+ threadId: string,
1051
+ ): Promise<{ output: Record<string, unknown>; obligationId: string } | null> {
1052
+ const modeState = await readStopSessionPinnedState("deep-interview-state.json", cwd, sessionId);
1053
+ if (!modeState) return null;
1054
+
1055
+ const questionEnforcement = safeObject(modeState.question_enforcement);
1056
+ const hasPendingQuestionObligation = isPendingDeepInterviewQuestionEnforcement(questionEnforcement);
1057
+ if (modeState.active !== true && !hasPendingQuestionObligation) return null;
1058
+
1059
+ const phase = formatPhase(modeState.current_phase, "planning");
1060
+ if (TERMINAL_MODE_PHASES.has(phase.toLowerCase()) || phase === "completing") {
1061
+ return null;
1062
+ }
1063
+
1064
+ const canonicalState = await readVisibleSkillActiveState(cwd, sessionId);
1065
+ if (canonicalState) {
1066
+ const blocker = listActiveSkills(canonicalState).find((entry) => (
1067
+ entry.skill === "deep-interview"
1068
+ && matchesSkillStopContext(entry, canonicalState, sessionId, threadId)
1069
+ ));
1070
+ if (!blocker) return null;
1071
+ }
1072
+
1073
+ if (!hasPendingQuestionObligation) {
1074
+ return null;
1075
+ }
1076
+
1077
+ const obligationId = safeString(questionEnforcement.obligation_id).trim();
1078
+ if (!obligationId) return null;
1079
+
1080
+ const systemMessage =
1081
+ `OMX deep-interview is still active (phase: ${phase}) and requires a structured question via omx question before stopping.`;
1082
+
1083
+ return {
1084
+ obligationId,
1085
+ output: {
1086
+ decision: "block",
1087
+ reason:
1088
+ `Deep interview is still active (phase: ${phase}) and has a pending structured question obligation; use \`omx question\` before stopping.`,
1089
+ stopReason: "deep_interview_question_required",
1090
+ systemMessage,
1091
+ },
1092
+ };
1093
+ }
1094
+
930
1095
  function resolveRepeatableStopSessionId(
931
1096
  payload: CodexHookPayload,
932
1097
  canonicalSessionId?: string,
@@ -1039,6 +1204,7 @@ async function returnPersistentStopBlock(
1039
1204
  signatureValue: string,
1040
1205
  output: Record<string, unknown> | null,
1041
1206
  canonicalSessionId?: string,
1207
+ options: { allowRepeatDuringStopHook?: boolean } = { allowRepeatDuringStopHook: true },
1042
1208
  ): Promise<Record<string, unknown> | null> {
1043
1209
  return await maybeReturnRepeatableStopOutput(
1044
1210
  payload,
@@ -1046,7 +1212,7 @@ async function returnPersistentStopBlock(
1046
1212
  buildRepeatableStopSignature(payload, signatureKind, signatureValue, canonicalSessionId),
1047
1213
  output,
1048
1214
  canonicalSessionId,
1049
- { allowRepeatDuringStopHook: true },
1215
+ options,
1050
1216
  );
1051
1217
  }
1052
1218
 
@@ -1283,6 +1449,28 @@ async function buildStopHookOutput(
1283
1449
  const threadId = readPayloadThreadId(payload);
1284
1450
  const ralphState = await readActiveRalphState(stateDir, canonicalSessionId);
1285
1451
  if (!ralphState) {
1452
+ const autoresearchState = await readActiveAutoresearchState(cwd, canonicalSessionId);
1453
+ if (autoresearchState) {
1454
+ const completion = await readAutoresearchCompletionStatus(cwd, canonicalSessionId!.trim());
1455
+ if (!completion.complete) {
1456
+ const currentPhase = safeString(autoresearchState.current_phase ?? autoresearchState.currentPhase).trim() || 'executing';
1457
+ const systemMessage = `OMX autoresearch is still active (phase: ${currentPhase}); continue until validator evidence is complete before stopping.`;
1458
+ return await maybeReturnRepeatableStopOutput(
1459
+ payload,
1460
+ stateDir,
1461
+ buildRepeatableStopSignature(payload, 'autoresearch-stop', `${currentPhase}|${completion.reason}`, canonicalSessionId),
1462
+ {
1463
+ decision: 'block',
1464
+ reason: systemMessage,
1465
+ stopReason: `autoresearch_${currentPhase}`,
1466
+ systemMessage,
1467
+ },
1468
+ canonicalSessionId,
1469
+ { allowRepeatDuringStopHook: true },
1470
+ );
1471
+ }
1472
+ }
1473
+
1286
1474
  const teamWorkerOutput = await buildTeamWorkerStopOutput(cwd);
1287
1475
  if (hasTeamWorkerContext() && teamWorkerOutput) return teamWorkerOutput;
1288
1476
 
@@ -1307,6 +1495,7 @@ async function buildStopHookOutput(
1307
1495
  safeString(ultraworkOutput.stopReason),
1308
1496
  ultraworkOutput,
1309
1497
  canonicalSessionId,
1498
+ { allowRepeatDuringStopHook: false },
1310
1499
  );
1311
1500
  }
1312
1501
 
@@ -1343,6 +1532,22 @@ async function buildStopHookOutput(
1343
1532
  }
1344
1533
 
1345
1534
  if (canonicalSessionId) {
1535
+ const deepInterviewQuestionOutput = await buildDeepInterviewQuestionStopOutput(
1536
+ cwd,
1537
+ canonicalSessionId,
1538
+ threadId,
1539
+ );
1540
+ if (deepInterviewQuestionOutput) {
1541
+ return await returnPersistentStopBlock(
1542
+ payload,
1543
+ stateDir,
1544
+ "deep-interview-question-stop",
1545
+ deepInterviewQuestionOutput.obligationId,
1546
+ deepInterviewQuestionOutput.output,
1547
+ canonicalSessionId,
1548
+ );
1549
+ }
1550
+
1346
1551
  const canonicalTeam = await findCanonicalActiveTeamForSession(cwd, canonicalSessionId);
1347
1552
  if (canonicalTeam) {
1348
1553
  const canonicalTeamOutput = buildTeamStopOutputForPhase(
@@ -1435,23 +1640,42 @@ export async function dispatchCodexNativeHook(
1435
1640
 
1436
1641
  const omxEventName = mapCodexHookEventToOmxEvent(hookEventName);
1437
1642
  let skillState: SkillActiveState | null = null;
1643
+ let triageAdditionalContext: string | null = null;
1438
1644
 
1439
1645
  const nativeSessionId = safeString(payload.session_id ?? payload.sessionId).trim();
1440
1646
  const threadId = safeString(payload.thread_id ?? payload.threadId).trim();
1441
1647
  const turnId = safeString(payload.turn_id ?? payload.turnId).trim();
1442
- let canonicalSessionId = safeString((await readUsableSessionState(cwd))?.session_id).trim();
1648
+ const currentSessionState = await readUsableSessionState(cwd);
1649
+ let canonicalSessionId = safeString(currentSessionState?.session_id).trim();
1650
+ let resolvedNativeSessionId = nativeSessionId;
1443
1651
 
1444
1652
  if (hookEventName === "SessionStart" && nativeSessionId) {
1445
1653
  const sessionState = await reconcileNativeSessionStart(cwd, nativeSessionId, {
1446
1654
  pid: options.sessionOwnerPid ?? resolveSessionOwnerPid(payload),
1447
1655
  });
1448
1656
  canonicalSessionId = safeString(sessionState.session_id).trim();
1657
+ resolvedNativeSessionId = safeString(sessionState.native_session_id).trim() || nativeSessionId;
1449
1658
  } else if (!canonicalSessionId) {
1450
- canonicalSessionId = safeString((await readUsableSessionState(cwd))?.session_id).trim();
1659
+ canonicalSessionId = safeString(currentSessionState?.session_id).trim();
1660
+ }
1661
+
1662
+ if (hookEventName === "Stop") {
1663
+ const stopCanonicalSessionId = await resolveInternalSessionIdForPayload(
1664
+ cwd,
1665
+ readPayloadSessionId(payload),
1666
+ );
1667
+ if (stopCanonicalSessionId) {
1668
+ canonicalSessionId = stopCanonicalSessionId;
1669
+ }
1670
+ if (canonicalSessionId && safeString(currentSessionState?.session_id).trim() === canonicalSessionId) {
1671
+ resolvedNativeSessionId =
1672
+ safeString(currentSessionState?.native_session_id).trim() || resolvedNativeSessionId;
1673
+ }
1451
1674
  }
1452
1675
 
1453
1676
  const eventSessionId = canonicalSessionId || nativeSessionId || undefined;
1454
1677
  const sessionIdForState = canonicalSessionId || nativeSessionId;
1678
+ let outputJson: Record<string, unknown> | null = null;
1455
1679
 
1456
1680
  if (hookEventName === "UserPromptSubmit") {
1457
1681
  const prompt = readPromptText(payload);
@@ -1464,14 +1688,82 @@ export async function dispatchCodexNativeHook(
1464
1688
  turnId,
1465
1689
  });
1466
1690
  }
1467
- await reconcileHudForPromptSubmit(cwd).catch(() => {});
1691
+ // --- Triage classifier (advisory-only, non-keyword prompts) ---
1692
+ if (prompt && skillState === null) {
1693
+ try {
1694
+ if (readTriageConfig().enabled) {
1695
+ const normalized = prompt.trim().toLowerCase();
1696
+ const previous = readTriageState({ cwd, sessionId: sessionIdForState || null });
1697
+ const suppress = shouldSuppressFollowup({
1698
+ previous,
1699
+ currentPrompt: normalized,
1700
+ currentHasKeyword: false,
1701
+ });
1702
+ if (!suppress) {
1703
+ const decision = triagePrompt(prompt);
1704
+ const nowIso = new Date().toISOString();
1705
+ const effectiveTurnId = turnId || nowIso;
1706
+ if (decision.lane === "HEAVY") {
1707
+ triageAdditionalContext =
1708
+ "OMX native UserPromptSubmit triage detected a multi-step goal with no workflow keyword. This is advisory prompt-routing context only; it did not activate autopilot or initialize workflow state. Prefer the existing autopilot-style workflow if AGENTS.md/runtime conditions allow it, unless newer user context narrows or opts out.";
1709
+ const newState: TriageStateFile = {
1710
+ version: 1,
1711
+ last_triage: {
1712
+ lane: "HEAVY",
1713
+ destination: "autopilot",
1714
+ reason: decision.reason,
1715
+ prompt_signature: promptSignature(normalized),
1716
+ turn_id: effectiveTurnId,
1717
+ created_at: nowIso,
1718
+ },
1719
+ suppress_followup: true,
1720
+ };
1721
+ writeTriageState({ cwd, sessionId: sessionIdForState || null, state: newState });
1722
+ } else if (decision.lane === "LIGHT") {
1723
+ if (decision.destination === "explore") {
1724
+ triageAdditionalContext =
1725
+ "OMX native UserPromptSubmit triage detected a read-only/question-shaped request with no workflow keyword. This is advisory prompt-routing context only. Prefer the explore role surface rather than escalating to autopilot.";
1726
+ } else if (decision.destination === "executor") {
1727
+ triageAdditionalContext =
1728
+ "OMX native UserPromptSubmit triage detected a narrow edit-shaped request with no workflow keyword. This is advisory prompt-routing context only. Prefer the executor role surface rather than autopilot.";
1729
+ } else if (decision.destination === "designer") {
1730
+ triageAdditionalContext =
1731
+ "OMX native UserPromptSubmit triage detected a visual/style request with no workflow keyword. This is advisory prompt-routing context only. Prefer the designer role surface.";
1732
+ }
1733
+ if (triageAdditionalContext !== null) {
1734
+ const dest = decision.destination as "explore" | "executor" | "designer";
1735
+ const newState: TriageStateFile = {
1736
+ version: 1,
1737
+ last_triage: {
1738
+ lane: "LIGHT",
1739
+ destination: dest,
1740
+ reason: decision.reason,
1741
+ prompt_signature: promptSignature(normalized),
1742
+ turn_id: effectiveTurnId,
1743
+ created_at: nowIso,
1744
+ },
1745
+ suppress_followup: true,
1746
+ };
1747
+ writeTriageState({ cwd, sessionId: sessionIdForState || null, state: newState });
1748
+ }
1749
+ }
1750
+ // lane === "PASS": no context, no state write
1751
+ }
1752
+ }
1753
+ } catch {
1754
+ // Swallow all triage errors; never break the hook
1755
+ triageAdditionalContext = null;
1756
+ }
1757
+ }
1758
+ const reconcileHudForPromptSubmitFn = options.reconcileHudForPromptSubmitFn ?? reconcileHudForPromptSubmit;
1759
+ await reconcileHudForPromptSubmitFn(cwd, { sessionId: canonicalSessionId || sessionIdForState || undefined }).catch(() => {});
1468
1760
  }
1469
1761
 
1470
1762
  if (omxEventName) {
1471
- const baseContext = buildBaseContext(cwd, payload, hookEventName!);
1472
- if (nativeSessionId) {
1473
- baseContext.native_session_id = nativeSessionId;
1474
- baseContext.codex_session_id = nativeSessionId;
1763
+ const baseContext = buildBaseContext(cwd, payload, hookEventName!, canonicalSessionId);
1764
+ if (resolvedNativeSessionId) {
1765
+ baseContext.native_session_id = resolvedNativeSessionId;
1766
+ baseContext.codex_session_id = resolvedNativeSessionId;
1475
1767
  }
1476
1768
  if (canonicalSessionId) {
1477
1769
  baseContext.omx_session_id = canonicalSessionId;
@@ -1489,11 +1781,10 @@ export async function dispatchCodexNativeHook(
1489
1781
  await dispatchHookEvent(event, { cwd });
1490
1782
  }
1491
1783
 
1492
- let outputJson: Record<string, unknown> | null = null;
1493
1784
  if (hookEventName === "SessionStart" || hookEventName === "UserPromptSubmit") {
1494
1785
  const additionalContext = hookEventName === "SessionStart"
1495
1786
  ? await buildSessionStartContext(cwd, canonicalSessionId || nativeSessionId)
1496
- : buildAdditionalContextMessage(readPromptText(payload), skillState);
1787
+ : (buildAdditionalContextMessage(readPromptText(payload), skillState, cwd) ?? triageAdditionalContext);
1497
1788
  if (additionalContext) {
1498
1789
  outputJson = {
1499
1790
  hookSpecificOutput: {
@@ -1526,6 +1817,14 @@ interface NativeHookCliReadResult {
1526
1817
  parseError: Error | null;
1527
1818
  }
1528
1819
 
1820
+ export function isCodexNativeHookMainModule(
1821
+ moduleUrl: string,
1822
+ argv1: string | undefined,
1823
+ ): boolean {
1824
+ if (!argv1) return false;
1825
+ return moduleUrl === pathToFileURL(argv1).href;
1826
+ }
1827
+
1529
1828
  async function readStdinJson(): Promise<NativeHookCliReadResult> {
1530
1829
  const chunks: Buffer[] = [];
1531
1830
  for await (const chunk of process.stdin) {
@@ -1570,7 +1869,7 @@ export async function runCodexNativeHookCli(): Promise<void> {
1570
1869
  }
1571
1870
  }
1572
1871
 
1573
- if (import.meta.url === `file://${process.argv[1]}`) {
1872
+ if (isCodexNativeHookMainModule(import.meta.url, process.argv[1])) {
1574
1873
  runCodexNativeHookCli().catch((error) => {
1575
1874
  process.stderr.write(
1576
1875
  `[omx] codex-native-hook failed: ${