oh-my-codex 0.16.2 → 0.16.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (340) hide show
  1. package/Cargo.lock +5 -5
  2. package/Cargo.toml +1 -1
  3. package/README.md +3 -3
  4. package/dist/catalog/__tests__/plugin-bundle-ssot.test.js +9 -0
  5. package/dist/catalog/__tests__/plugin-bundle-ssot.test.js.map +1 -1
  6. package/dist/cli/__tests__/cleanup.test.js +27 -0
  7. package/dist/cli/__tests__/cleanup.test.js.map +1 -1
  8. package/dist/cli/__tests__/codex-plugin-layout.test.js +7 -5
  9. package/dist/cli/__tests__/codex-plugin-layout.test.js.map +1 -1
  10. package/dist/cli/__tests__/doctor-warning-copy.test.js +137 -6
  11. package/dist/cli/__tests__/doctor-warning-copy.test.js.map +1 -1
  12. package/dist/cli/__tests__/index.test.js +303 -4
  13. package/dist/cli/__tests__/index.test.js.map +1 -1
  14. package/dist/cli/__tests__/launch-fallback.test.js +58 -0
  15. package/dist/cli/__tests__/launch-fallback.test.js.map +1 -1
  16. package/dist/cli/__tests__/ralph-goal-mode-contract.test.js +2 -0
  17. package/dist/cli/__tests__/ralph-goal-mode-contract.test.js.map +1 -1
  18. package/dist/cli/__tests__/ralph.test.js +48 -0
  19. package/dist/cli/__tests__/ralph.test.js.map +1 -1
  20. package/dist/cli/__tests__/setup-hooks-shared-ownership.test.js +8 -0
  21. package/dist/cli/__tests__/setup-hooks-shared-ownership.test.js.map +1 -1
  22. package/dist/cli/__tests__/setup-install-mode.test.js +350 -27
  23. package/dist/cli/__tests__/setup-install-mode.test.js.map +1 -1
  24. package/dist/cli/__tests__/setup-refresh.test.js +85 -3
  25. package/dist/cli/__tests__/setup-refresh.test.js.map +1 -1
  26. package/dist/cli/__tests__/setup-scope.test.js +1 -1
  27. package/dist/cli/__tests__/setup-scope.test.js.map +1 -1
  28. package/dist/cli/__tests__/setup-skills-overwrite.test.js +2 -1
  29. package/dist/cli/__tests__/setup-skills-overwrite.test.js.map +1 -1
  30. package/dist/cli/__tests__/team.test.js +269 -0
  31. package/dist/cli/__tests__/team.test.js.map +1 -1
  32. package/dist/cli/__tests__/ultragoal.test.js +69 -0
  33. package/dist/cli/__tests__/ultragoal.test.js.map +1 -1
  34. package/dist/cli/__tests__/uninstall.test.js +90 -6
  35. package/dist/cli/__tests__/uninstall.test.js.map +1 -1
  36. package/dist/cli/__tests__/update.test.js +109 -19
  37. package/dist/cli/__tests__/update.test.js.map +1 -1
  38. package/dist/cli/cleanup.d.ts.map +1 -1
  39. package/dist/cli/cleanup.js +8 -4
  40. package/dist/cli/cleanup.js.map +1 -1
  41. package/dist/cli/codex-feature-probe.d.ts +9 -0
  42. package/dist/cli/codex-feature-probe.d.ts.map +1 -0
  43. package/dist/cli/codex-feature-probe.js +28 -0
  44. package/dist/cli/codex-feature-probe.js.map +1 -0
  45. package/dist/cli/doctor.d.ts +1 -0
  46. package/dist/cli/doctor.d.ts.map +1 -1
  47. package/dist/cli/doctor.js +168 -16
  48. package/dist/cli/doctor.js.map +1 -1
  49. package/dist/cli/index.d.ts +9 -2
  50. package/dist/cli/index.d.ts.map +1 -1
  51. package/dist/cli/index.js +168 -20
  52. package/dist/cli/index.js.map +1 -1
  53. package/dist/cli/mcp-parity.js +8 -8
  54. package/dist/cli/mcp-parity.js.map +1 -1
  55. package/dist/cli/plugin-marketplace.d.ts +3 -0
  56. package/dist/cli/plugin-marketplace.d.ts.map +1 -1
  57. package/dist/cli/plugin-marketplace.js +88 -0
  58. package/dist/cli/plugin-marketplace.js.map +1 -1
  59. package/dist/cli/ralph.d.ts.map +1 -1
  60. package/dist/cli/ralph.js +21 -0
  61. package/dist/cli/ralph.js.map +1 -1
  62. package/dist/cli/setup-preferences.d.ts +4 -0
  63. package/dist/cli/setup-preferences.d.ts.map +1 -1
  64. package/dist/cli/setup-preferences.js +7 -0
  65. package/dist/cli/setup-preferences.js.map +1 -1
  66. package/dist/cli/setup.d.ts +5 -3
  67. package/dist/cli/setup.d.ts.map +1 -1
  68. package/dist/cli/setup.js +177 -43
  69. package/dist/cli/setup.js.map +1 -1
  70. package/dist/cli/team.d.ts.map +1 -1
  71. package/dist/cli/team.js +54 -15
  72. package/dist/cli/team.js.map +1 -1
  73. package/dist/cli/ultragoal.d.ts +1 -1
  74. package/dist/cli/ultragoal.d.ts.map +1 -1
  75. package/dist/cli/ultragoal.js +64 -5
  76. package/dist/cli/ultragoal.js.map +1 -1
  77. package/dist/cli/uninstall.d.ts +2 -0
  78. package/dist/cli/uninstall.d.ts.map +1 -1
  79. package/dist/cli/uninstall.js +76 -5
  80. package/dist/cli/uninstall.js.map +1 -1
  81. package/dist/cli/update.d.ts +10 -2
  82. package/dist/cli/update.d.ts.map +1 -1
  83. package/dist/cli/update.js +99 -5
  84. package/dist/cli/update.js.map +1 -1
  85. package/dist/config/__tests__/codex-feature-flags.test.d.ts +2 -0
  86. package/dist/config/__tests__/codex-feature-flags.test.d.ts.map +1 -0
  87. package/dist/config/__tests__/codex-feature-flags.test.js +35 -0
  88. package/dist/config/__tests__/codex-feature-flags.test.js.map +1 -0
  89. package/dist/config/__tests__/codex-hooks.test.js +188 -4
  90. package/dist/config/__tests__/codex-hooks.test.js.map +1 -1
  91. package/dist/config/__tests__/generator-idempotent.test.js +129 -10
  92. package/dist/config/__tests__/generator-idempotent.test.js.map +1 -1
  93. package/dist/config/__tests__/generator-notify.test.js +148 -7
  94. package/dist/config/__tests__/generator-notify.test.js.map +1 -1
  95. package/dist/config/__tests__/wiki-config-contract.test.js +6 -3
  96. package/dist/config/__tests__/wiki-config-contract.test.js.map +1 -1
  97. package/dist/config/codex-feature-flags.d.ts +21 -0
  98. package/dist/config/codex-feature-flags.d.ts.map +1 -0
  99. package/dist/config/codex-feature-flags.js +56 -0
  100. package/dist/config/codex-feature-flags.js.map +1 -0
  101. package/dist/config/codex-hooks.d.ts +40 -4
  102. package/dist/config/codex-hooks.d.ts.map +1 -1
  103. package/dist/config/codex-hooks.js +204 -18
  104. package/dist/config/codex-hooks.js.map +1 -1
  105. package/dist/config/generator.d.ts +19 -1
  106. package/dist/config/generator.d.ts.map +1 -1
  107. package/dist/config/generator.js +319 -83
  108. package/dist/config/generator.js.map +1 -1
  109. package/dist/config/omx-first-party-mcp.d.ts +3 -1
  110. package/dist/config/omx-first-party-mcp.d.ts.map +1 -1
  111. package/dist/config/omx-first-party-mcp.js +2 -2
  112. package/dist/config/omx-first-party-mcp.js.map +1 -1
  113. package/dist/hooks/__tests__/keyword-detector.test.js +92 -2
  114. package/dist/hooks/__tests__/keyword-detector.test.js.map +1 -1
  115. package/dist/hooks/__tests__/notify-fallback-watcher.test.js +29 -1
  116. package/dist/hooks/__tests__/notify-fallback-watcher.test.js.map +1 -1
  117. package/dist/hooks/__tests__/notify-hook-auto-nudge.test.js +10 -0
  118. package/dist/hooks/__tests__/notify-hook-auto-nudge.test.js.map +1 -1
  119. package/dist/hooks/__tests__/notify-hook-cross-worktree-heartbeat.test.js +1 -0
  120. package/dist/hooks/__tests__/notify-hook-cross-worktree-heartbeat.test.js.map +1 -1
  121. package/dist/hooks/__tests__/notify-hook-non-omx-guard.test.d.ts +2 -0
  122. package/dist/hooks/__tests__/notify-hook-non-omx-guard.test.d.ts.map +1 -0
  123. package/dist/hooks/__tests__/notify-hook-non-omx-guard.test.js +176 -0
  124. package/dist/hooks/__tests__/notify-hook-non-omx-guard.test.js.map +1 -0
  125. package/dist/hooks/__tests__/notify-hook-ralph-resume.test.js +148 -0
  126. package/dist/hooks/__tests__/notify-hook-ralph-resume.test.js.map +1 -1
  127. package/dist/hooks/__tests__/notify-hook-session-scope.test.js +3 -0
  128. package/dist/hooks/__tests__/notify-hook-session-scope.test.js.map +1 -1
  129. package/dist/hooks/__tests__/skill-catalog-hygiene.test.d.ts +2 -0
  130. package/dist/hooks/__tests__/skill-catalog-hygiene.test.d.ts.map +1 -0
  131. package/dist/hooks/__tests__/skill-catalog-hygiene.test.js +84 -0
  132. package/dist/hooks/__tests__/skill-catalog-hygiene.test.js.map +1 -0
  133. package/dist/hooks/__tests__/wiki-docs-contract.test.js +1 -2
  134. package/dist/hooks/__tests__/wiki-docs-contract.test.js.map +1 -1
  135. package/dist/hooks/agents-overlay.js +2 -2
  136. package/dist/hooks/agents-overlay.js.map +1 -1
  137. package/dist/hooks/keyword-detector.d.ts +1 -0
  138. package/dist/hooks/keyword-detector.d.ts.map +1 -1
  139. package/dist/hooks/keyword-detector.js +7 -5
  140. package/dist/hooks/keyword-detector.js.map +1 -1
  141. package/dist/hud/__tests__/state.test.js +164 -0
  142. package/dist/hud/__tests__/state.test.js.map +1 -1
  143. package/dist/hud/state.d.ts.map +1 -1
  144. package/dist/hud/state.js +4 -5
  145. package/dist/hud/state.js.map +1 -1
  146. package/dist/mcp/__tests__/state-paths.test.js +61 -0
  147. package/dist/mcp/__tests__/state-paths.test.js.map +1 -1
  148. package/dist/mcp/__tests__/state-server.test.js +166 -0
  149. package/dist/mcp/__tests__/state-server.test.js.map +1 -1
  150. package/dist/mcp/state-paths.d.ts.map +1 -1
  151. package/dist/mcp/state-paths.js +23 -2
  152. package/dist/mcp/state-paths.js.map +1 -1
  153. package/dist/modes/__tests__/base-session-scope.test.js +22 -0
  154. package/dist/modes/__tests__/base-session-scope.test.js.map +1 -1
  155. package/dist/modes/__tests__/base-tmux-pane.test.js +57 -26
  156. package/dist/modes/__tests__/base-tmux-pane.test.js.map +1 -1
  157. package/dist/modes/base.d.ts.map +1 -1
  158. package/dist/modes/base.js +5 -0
  159. package/dist/modes/base.js.map +1 -1
  160. package/dist/planning/__tests__/approved-execution-lifecycle-matrix.test.d.ts +2 -0
  161. package/dist/planning/__tests__/approved-execution-lifecycle-matrix.test.d.ts.map +1 -0
  162. package/dist/planning/__tests__/approved-execution-lifecycle-matrix.test.js +316 -0
  163. package/dist/planning/__tests__/approved-execution-lifecycle-matrix.test.js.map +1 -0
  164. package/dist/planning/__tests__/approved-launch-hint-lineage-matrix.test.d.ts +2 -0
  165. package/dist/planning/__tests__/approved-launch-hint-lineage-matrix.test.d.ts.map +1 -0
  166. package/dist/planning/__tests__/approved-launch-hint-lineage-matrix.test.js +481 -0
  167. package/dist/planning/__tests__/approved-launch-hint-lineage-matrix.test.js.map +1 -0
  168. package/dist/planning/__tests__/artifacts.test.js +597 -4
  169. package/dist/planning/__tests__/artifacts.test.js.map +1 -1
  170. package/dist/planning/__tests__/context-pack-status.test.js +524 -0
  171. package/dist/planning/__tests__/context-pack-status.test.js.map +1 -1
  172. package/dist/planning/__tests__/markdown-structure.test.d.ts +2 -0
  173. package/dist/planning/__tests__/markdown-structure.test.d.ts.map +1 -0
  174. package/dist/planning/__tests__/markdown-structure.test.js +459 -0
  175. package/dist/planning/__tests__/markdown-structure.test.js.map +1 -0
  176. package/dist/planning/__tests__/ready-context-pack-role-refs.test.d.ts +2 -0
  177. package/dist/planning/__tests__/ready-context-pack-role-refs.test.d.ts.map +1 -0
  178. package/dist/planning/__tests__/ready-context-pack-role-refs.test.js +612 -0
  179. package/dist/planning/__tests__/ready-context-pack-role-refs.test.js.map +1 -0
  180. package/dist/planning/artifacts.d.ts +7 -2
  181. package/dist/planning/artifacts.d.ts.map +1 -1
  182. package/dist/planning/artifacts.js +279 -26
  183. package/dist/planning/artifacts.js.map +1 -1
  184. package/dist/planning/context-pack-status.d.ts +31 -0
  185. package/dist/planning/context-pack-status.d.ts.map +1 -1
  186. package/dist/planning/context-pack-status.js +291 -25
  187. package/dist/planning/context-pack-status.js.map +1 -1
  188. package/dist/planning/markdown-structure.d.ts +20 -0
  189. package/dist/planning/markdown-structure.d.ts.map +1 -0
  190. package/dist/planning/markdown-structure.js +137 -0
  191. package/dist/planning/markdown-structure.js.map +1 -0
  192. package/dist/ralph/__tests__/completion-audit.test.d.ts +2 -0
  193. package/dist/ralph/__tests__/completion-audit.test.d.ts.map +1 -0
  194. package/dist/ralph/__tests__/completion-audit.test.js +121 -0
  195. package/dist/ralph/__tests__/completion-audit.test.js.map +1 -0
  196. package/dist/ralph/completion-audit.d.ts +8 -0
  197. package/dist/ralph/completion-audit.d.ts.map +1 -0
  198. package/dist/ralph/completion-audit.js +99 -0
  199. package/dist/ralph/completion-audit.js.map +1 -0
  200. package/dist/ralph/persistence.d.ts +1 -1
  201. package/dist/ralph/persistence.d.ts.map +1 -1
  202. package/dist/ralph/persistence.js +8 -2
  203. package/dist/ralph/persistence.js.map +1 -1
  204. package/dist/scripts/__tests__/codex-native-hook.test.js +359 -24
  205. package/dist/scripts/__tests__/codex-native-hook.test.js.map +1 -1
  206. package/dist/scripts/__tests__/notify-dispatcher.test.d.ts +2 -0
  207. package/dist/scripts/__tests__/notify-dispatcher.test.d.ts.map +1 -0
  208. package/dist/scripts/__tests__/notify-dispatcher.test.js +126 -0
  209. package/dist/scripts/__tests__/notify-dispatcher.test.js.map +1 -0
  210. package/dist/scripts/codex-native-hook.d.ts.map +1 -1
  211. package/dist/scripts/codex-native-hook.js +142 -76
  212. package/dist/scripts/codex-native-hook.js.map +1 -1
  213. package/dist/scripts/codex-native-pre-post.d.ts.map +1 -1
  214. package/dist/scripts/codex-native-pre-post.js +4 -2
  215. package/dist/scripts/codex-native-pre-post.js.map +1 -1
  216. package/dist/scripts/notify-dispatcher.d.ts +7 -0
  217. package/dist/scripts/notify-dispatcher.d.ts.map +1 -0
  218. package/dist/scripts/notify-dispatcher.js +87 -0
  219. package/dist/scripts/notify-dispatcher.js.map +1 -0
  220. package/dist/scripts/notify-fallback-watcher.js +4 -0
  221. package/dist/scripts/notify-fallback-watcher.js.map +1 -1
  222. package/dist/scripts/notify-hook/ralph-session-resume.d.ts.map +1 -1
  223. package/dist/scripts/notify-hook/ralph-session-resume.js +96 -8
  224. package/dist/scripts/notify-hook/ralph-session-resume.js.map +1 -1
  225. package/dist/scripts/notify-hook/state-io.d.ts.map +1 -1
  226. package/dist/scripts/notify-hook/state-io.js +6 -2
  227. package/dist/scripts/notify-hook/state-io.js.map +1 -1
  228. package/dist/scripts/notify-hook/visual-verdict.js +3 -3
  229. package/dist/scripts/notify-hook/visual-verdict.js.map +1 -1
  230. package/dist/scripts/notify-hook.js +127 -1
  231. package/dist/scripts/notify-hook.js.map +1 -1
  232. package/dist/state/__tests__/workflow-transition.test.js +102 -27
  233. package/dist/state/__tests__/workflow-transition.test.js.map +1 -1
  234. package/dist/state/operations.d.ts.map +1 -1
  235. package/dist/state/operations.js +9 -3
  236. package/dist/state/operations.js.map +1 -1
  237. package/dist/state/skill-active.d.ts +7 -0
  238. package/dist/state/skill-active.d.ts.map +1 -1
  239. package/dist/state/skill-active.js +25 -8
  240. package/dist/state/skill-active.js.map +1 -1
  241. package/dist/state/workflow-transition-reconcile.d.ts +1 -0
  242. package/dist/state/workflow-transition-reconcile.d.ts.map +1 -1
  243. package/dist/state/workflow-transition-reconcile.js +22 -15
  244. package/dist/state/workflow-transition-reconcile.js.map +1 -1
  245. package/dist/state/workflow-transition.js +3 -3
  246. package/dist/state/workflow-transition.js.map +1 -1
  247. package/dist/team/__tests__/approved-execution.test.js +84 -1
  248. package/dist/team/__tests__/approved-execution.test.js.map +1 -1
  249. package/dist/team/__tests__/runtime.test.js +178 -19
  250. package/dist/team/__tests__/runtime.test.js.map +1 -1
  251. package/dist/team/__tests__/scaling.test.js +497 -2
  252. package/dist/team/__tests__/scaling.test.js.map +1 -1
  253. package/dist/team/__tests__/state-root.test.js +1 -1
  254. package/dist/team/__tests__/state-root.test.js.map +1 -1
  255. package/dist/team/__tests__/worker-bootstrap.test.js +45 -0
  256. package/dist/team/__tests__/worker-bootstrap.test.js.map +1 -1
  257. package/dist/team/approved-execution.d.ts +1 -0
  258. package/dist/team/approved-execution.d.ts.map +1 -1
  259. package/dist/team/approved-execution.js +53 -0
  260. package/dist/team/approved-execution.js.map +1 -1
  261. package/dist/team/delivery-log.d.ts.map +1 -1
  262. package/dist/team/delivery-log.js +8 -1
  263. package/dist/team/delivery-log.js.map +1 -1
  264. package/dist/team/runtime.d.ts.map +1 -1
  265. package/dist/team/runtime.js +104 -18
  266. package/dist/team/runtime.js.map +1 -1
  267. package/dist/team/scaling.d.ts.map +1 -1
  268. package/dist/team/scaling.js +43 -0
  269. package/dist/team/scaling.js.map +1 -1
  270. package/dist/team/state/mailbox.d.ts +1 -0
  271. package/dist/team/state/mailbox.d.ts.map +1 -1
  272. package/dist/team/state/mailbox.js +10 -1
  273. package/dist/team/state/mailbox.js.map +1 -1
  274. package/dist/team/state-root.d.ts.map +1 -1
  275. package/dist/team/state-root.js +5 -1
  276. package/dist/team/state-root.js.map +1 -1
  277. package/dist/team/state.d.ts.map +1 -1
  278. package/dist/team/state.js +3 -7
  279. package/dist/team/state.js.map +1 -1
  280. package/dist/team/worker-bootstrap.d.ts +7 -2
  281. package/dist/team/worker-bootstrap.d.ts.map +1 -1
  282. package/dist/team/worker-bootstrap.js +17 -4
  283. package/dist/team/worker-bootstrap.js.map +1 -1
  284. package/dist/ultragoal/__tests__/artifacts.test.js +124 -1
  285. package/dist/ultragoal/__tests__/artifacts.test.js.map +1 -1
  286. package/dist/ultragoal/__tests__/docs-contract.test.js +21 -0
  287. package/dist/ultragoal/__tests__/docs-contract.test.js.map +1 -1
  288. package/dist/ultragoal/artifacts.d.ts +44 -2
  289. package/dist/ultragoal/artifacts.d.ts.map +1 -1
  290. package/dist/ultragoal/artifacts.js +197 -13
  291. package/dist/ultragoal/artifacts.js.map +1 -1
  292. package/dist/wiki/lifecycle.js +1 -1
  293. package/dist/wiki/lifecycle.js.map +1 -1
  294. package/package.json +1 -1
  295. package/plugins/oh-my-codex/.codex-plugin/plugin.json +1 -1
  296. package/plugins/oh-my-codex/.mcp.json +5 -5
  297. package/plugins/oh-my-codex/skills/analyze/SKILL.md +0 -2
  298. package/plugins/oh-my-codex/skills/autopilot/SKILL.md +2 -2
  299. package/plugins/oh-my-codex/skills/code-review/SKILL.md +1 -3
  300. package/plugins/oh-my-codex/skills/deep-interview/SKILL.md +5 -7
  301. package/plugins/oh-my-codex/skills/doctor/SKILL.md +2 -2
  302. package/plugins/oh-my-codex/skills/omx-setup/SKILL.md +3 -3
  303. package/plugins/oh-my-codex/skills/pipeline/SKILL.md +3 -3
  304. package/plugins/oh-my-codex/skills/plan/SKILL.md +3 -6
  305. package/plugins/oh-my-codex/skills/ralph/SKILL.md +9 -10
  306. package/plugins/oh-my-codex/skills/ultragoal/SKILL.md +36 -3
  307. package/plugins/oh-my-codex/skills/ultraqa/SKILL.md +21 -24
  308. package/plugins/oh-my-codex/skills/ultrawork/SKILL.md +8 -8
  309. package/plugins/oh-my-codex/skills/wiki/SKILL.md +13 -13
  310. package/skills/analyze/SKILL.md +0 -2
  311. package/skills/ask-claude/SKILL.md +5 -3
  312. package/skills/ask-gemini/SKILL.md +5 -3
  313. package/skills/autopilot/SKILL.md +2 -2
  314. package/skills/code-review/SKILL.md +1 -3
  315. package/skills/deep-interview/SKILL.md +5 -7
  316. package/skills/doctor/SKILL.md +2 -2
  317. package/skills/ecomode/SKILL.md +105 -1
  318. package/skills/frontend-ui-ux/SKILL.md +4 -26
  319. package/skills/git-master/SKILL.md +2 -4
  320. package/skills/omx-setup/SKILL.md +3 -3
  321. package/skills/pipeline/SKILL.md +3 -3
  322. package/skills/plan/SKILL.md +3 -6
  323. package/skills/ralph/SKILL.md +9 -10
  324. package/skills/swarm/SKILL.md +5 -3
  325. package/skills/tdd/SKILL.md +95 -1
  326. package/skills/ultragoal/SKILL.md +36 -3
  327. package/skills/ultraqa/SKILL.md +21 -24
  328. package/skills/ultrawork/SKILL.md +8 -8
  329. package/skills/web-clone/SKILL.md +348 -1
  330. package/skills/wiki/SKILL.md +13 -13
  331. package/src/scripts/__tests__/codex-native-hook.test.ts +389 -24
  332. package/src/scripts/__tests__/notify-dispatcher.test.ts +153 -0
  333. package/src/scripts/codex-native-hook.ts +168 -64
  334. package/src/scripts/codex-native-pre-post.ts +4 -1
  335. package/src/scripts/notify-dispatcher.ts +113 -0
  336. package/src/scripts/notify-fallback-watcher.ts +6 -2
  337. package/src/scripts/notify-hook/ralph-session-resume.ts +117 -8
  338. package/src/scripts/notify-hook/state-io.ts +4 -2
  339. package/src/scripts/notify-hook/visual-verdict.ts +3 -3
  340. package/src/scripts/notify-hook.ts +119 -1
@@ -3,9 +3,10 @@ import assert from "node:assert/strict";
3
3
  import { existsSync } from "node:fs";
4
4
  import { cp, mkdir, mkdtemp, readFile, readdir, rm, writeFile, } from "node:fs/promises";
5
5
  import { tmpdir } from "node:os";
6
- import { join } from "node:path";
6
+ import { dirname, join } from "node:path";
7
7
  import { parse as parseToml } from "@iarna/toml";
8
8
  import { setup } from "../setup.js";
9
+ import { uninstall } from "../uninstall.js";
9
10
  const packageRoot = process.cwd();
10
11
  async function withTempCwd(wd, fn) {
11
12
  const previousCwd = process.cwd();
@@ -58,13 +59,140 @@ async function withIsolatedUserHome(wd, fn) {
58
59
  }
59
60
  }
60
61
  }
62
+ describe("notify setup scope", () => {
63
+ it("does not write unsupported project-scope notify", async () => {
64
+ const wd = await mkdtemp(join(tmpdir(), "omx-project-no-notify-"));
65
+ try {
66
+ await withTempCwd(wd, async () => {
67
+ await setup({ scope: "project" });
68
+ });
69
+ const config = await readFile(join(wd, ".codex", "config.toml"), "utf-8");
70
+ assert.doesNotMatch(config, /^notify\s*=/m);
71
+ assert.doesNotMatch(config, /notify-hook\.js/);
72
+ }
73
+ finally {
74
+ await rm(wd, { recursive: true, force: true });
75
+ }
76
+ });
77
+ it("preserves existing user project-scope notify while suppressing OMX notify", async () => {
78
+ const wd = await mkdtemp(join(tmpdir(), "omx-project-user-notify-"));
79
+ try {
80
+ await mkdir(join(wd, ".codex"), { recursive: true });
81
+ await writeFile(join(wd, ".codex", "config.toml"), 'notify = ["node", "/tmp/notify-hook.js"]\napproval_policy = "never"\n');
82
+ await withTempCwd(wd, async () => {
83
+ await setup({ scope: "project" });
84
+ });
85
+ const config = await readFile(join(wd, ".codex", "config.toml"), "utf-8");
86
+ assert.match(config, /^notify = \["node", "\/tmp\/notify-hook\.js"\]$/m);
87
+ assert.doesNotMatch(config, /oh-my-codex.*notify-hook\.js/);
88
+ assert.match(config, /^approval_policy = "never"$/m);
89
+ }
90
+ finally {
91
+ await rm(wd, { recursive: true, force: true });
92
+ }
93
+ });
94
+ it("wraps and restores an existing user notify", async () => {
95
+ const wd = await mkdtemp(join(tmpdir(), "omx-user-notify-"));
96
+ try {
97
+ await withIsolatedUserHome(wd, async (codexHomeDir) => {
98
+ await mkdir(codexHomeDir, { recursive: true });
99
+ await writeFile(join(codexHomeDir, "config.toml"), 'notify = ["node", "/tmp/user-notify.js"]\napproval_policy = "on-failure"\n');
100
+ await withTempCwd(wd, async () => {
101
+ await setup({ scope: "user" });
102
+ });
103
+ const config = await readFile(join(codexHomeDir, "config.toml"), "utf-8");
104
+ assert.match(config, /^notify = \["node", ".*notify-dispatcher\.js", "--metadata", ".*notify-dispatch\.json"\]$/m);
105
+ const metadataPath = join(codexHomeDir, ".omx", "notify-dispatch.json");
106
+ const metadata = JSON.parse(await readFile(metadataPath, "utf-8"));
107
+ assert.deepEqual(metadata.previousNotify, ["node", "/tmp/user-notify.js"]);
108
+ assert.deepEqual(metadata.omxNotify?.slice(0, 1), ["node"]);
109
+ await withTempCwd(wd, async () => {
110
+ await setup({ scope: "user" });
111
+ });
112
+ const rerunConfig = await readFile(join(codexHomeDir, "config.toml"), "utf-8");
113
+ assert.match(rerunConfig, /notify-dispatcher\.js/);
114
+ const rerunMetadata = JSON.parse(await readFile(metadataPath, "utf-8"));
115
+ assert.deepEqual(rerunMetadata.previousNotify, [
116
+ "node",
117
+ "/tmp/user-notify.js",
118
+ ]);
119
+ await withTempCwd(wd, async () => {
120
+ await uninstall({ scope: "user" });
121
+ });
122
+ const restored = await readFile(join(codexHomeDir, "config.toml"), "utf-8");
123
+ assert.match(restored, new RegExp('^notify = \\["node", "/tmp/user-notify\\.js"\\]$', "m"));
124
+ assert.doesNotMatch(restored, /notify-dispatcher\.js/);
125
+ });
126
+ }
127
+ finally {
128
+ await rm(wd, { recursive: true, force: true });
129
+ }
130
+ });
131
+ it("does not preserve stale OMX dispatcher metadata as previous notify", async () => {
132
+ const wd = await mkdtemp(join(tmpdir(), "omx-stale-dispatcher-notify-"));
133
+ try {
134
+ await withIsolatedUserHome(wd, async (codexHomeDir) => {
135
+ await mkdir(codexHomeDir, { recursive: true });
136
+ const metadataPath = join(codexHomeDir, ".omx", "notify-dispatch.json");
137
+ const stalePkgRoot = join(wd, "old-global", "oh-my-codex");
138
+ const staleDispatcher = join(stalePkgRoot, "dist", "scripts", "notify-dispatcher.js");
139
+ await mkdir(dirname(metadataPath), { recursive: true });
140
+ await writeFile(join(codexHomeDir, "config.toml"), `notify = ["node", "${staleDispatcher}", "--metadata", "${metadataPath}"]\napproval_policy = "on-failure"\n`);
141
+ await writeFile(metadataPath, JSON.stringify({
142
+ managedBy: "oh-my-codex",
143
+ version: 1,
144
+ previousNotify: [
145
+ "node",
146
+ staleDispatcher,
147
+ "--metadata",
148
+ metadataPath,
149
+ ],
150
+ omxNotify: [
151
+ "node",
152
+ join(stalePkgRoot, "dist", "scripts", "notify-hook.js"),
153
+ ],
154
+ dispatcherNotify: ["node", staleDispatcher, "--metadata", metadataPath],
155
+ }));
156
+ await withTempCwd(wd, async () => {
157
+ await setup({ scope: "user" });
158
+ });
159
+ const config = await readFile(join(codexHomeDir, "config.toml"), "utf-8");
160
+ assert.match(config, /^notify = \["node", ".*notify-hook\.js"\]$/m);
161
+ assert.doesNotMatch(config, /notify-dispatcher\.js/);
162
+ });
163
+ }
164
+ finally {
165
+ await rm(wd, { recursive: true, force: true });
166
+ }
167
+ });
168
+ it("does not wrap stale global OMX notify hooks as user notify commands", async () => {
169
+ const wd = await mkdtemp(join(tmpdir(), "omx-stale-hook-notify-"));
170
+ try {
171
+ await withIsolatedUserHome(wd, async (codexHomeDir) => {
172
+ await mkdir(codexHomeDir, { recursive: true });
173
+ const staleHook = join("/opt", "homebrew", "lib", "node_modules", "oh-my-codex", "dist", "scripts", "notify-hook.js");
174
+ await writeFile(join(codexHomeDir, "config.toml"), `notify = ["node", "${staleHook}"]\napproval_policy = "on-failure"\n`);
175
+ await withTempCwd(wd, async () => {
176
+ await setup({ scope: "user" });
177
+ });
178
+ const config = await readFile(join(codexHomeDir, "config.toml"), "utf-8");
179
+ assert.match(config, /^notify = \["node", ".*notify-hook\.js"\]$/m);
180
+ assert.doesNotMatch(config, /notify-dispatcher\.js/);
181
+ assert.doesNotMatch(config, /lib\/node_modules\/oh-my-codex\/dist\/scripts\/notify-hook\.js/);
182
+ });
183
+ }
184
+ finally {
185
+ await rm(wd, { recursive: true, force: true });
186
+ }
187
+ });
188
+ });
61
189
  async function assertProjectPluginModeArtifacts(wd) {
62
190
  const hooks = await readFile(join(wd, ".codex", "hooks.json"), "utf-8");
63
191
  assert.match(hooks, /codex-native-hook\.js/);
64
192
  const config = await readFile(join(wd, ".codex", "config.toml"), "utf-8");
65
- assert.match(config, /^codex_hooks = true$/m);
193
+ assert.match(config, /^hooks = true$/m);
66
194
  assert.match(config, /^goals = true$/m);
67
- assert.doesNotMatch(config, /developer_instructions|notify-hook|mcp_servers/);
195
+ assert.doesNotMatch(config, /developer_instructions|notify-hook/g);
68
196
  assert.equal(existsSync(join(wd, ".codex", "skills", "ask", "SKILL.md")), false);
69
197
  assert.equal(existsSync(join(wd, ".codex", "agents", "planner.toml")), false);
70
198
  assert.equal(existsSync(join(wd, ".codex", "prompts", "executor.md")), false);
@@ -73,6 +201,7 @@ async function assertProjectPluginModeArtifacts(wd) {
73
201
  assert.deepEqual(persisted, {
74
202
  scope: "project",
75
203
  installMode: "plugin",
204
+ mcpMode: "none",
76
205
  });
77
206
  }
78
207
  async function captureConsoleOutput(fn) {
@@ -137,7 +266,7 @@ describe("omx setup install mode behavior", () => {
137
266
  },
138
267
  });
139
268
  });
140
- assert.match(output, /Setup preference review: keep \(scope=user, installMode=legacy\)/);
269
+ assert.match(output, /Setup preference review: keep \(scope=user, installMode=legacy, mcpMode=not recorded\)/);
141
270
  assert.match(output, /Using setup scope: user \(from \.omx\/setup-scope\.json\)/);
142
271
  assert.match(output, /Using setup install mode: legacy \(from \.omx\/setup-scope\.json\)/);
143
272
  });
@@ -166,7 +295,11 @@ describe("omx setup install mode behavior", () => {
166
295
  },
167
296
  });
168
297
  const persisted = JSON.parse(await readFile(join(wd, ".omx", "setup-scope.json"), "utf-8"));
169
- assert.deepEqual(persisted, { scope: "user", installMode: "plugin" });
298
+ assert.deepEqual(persisted, {
299
+ scope: "user",
300
+ installMode: "plugin",
301
+ mcpMode: "none",
302
+ });
170
303
  });
171
304
  });
172
305
  }
@@ -189,7 +322,7 @@ describe("omx setup install mode behavior", () => {
189
322
  },
190
323
  });
191
324
  const persisted = JSON.parse(await readFile(join(wd, ".omx", "setup-scope.json"), "utf-8"));
192
- assert.deepEqual(persisted, { scope: "project" });
325
+ assert.deepEqual(persisted, { scope: "project", mcpMode: "none" });
193
326
  });
194
327
  });
195
328
  }
@@ -218,7 +351,11 @@ describe("omx setup install mode behavior", () => {
218
351
  });
219
352
  assert.equal(reviewed, true);
220
353
  const persisted = JSON.parse(await readFile(join(wd, ".omx", "setup-scope.json"), "utf-8"));
221
- assert.deepEqual(persisted, { scope: "user", installMode: "plugin" });
354
+ assert.deepEqual(persisted, {
355
+ scope: "user",
356
+ installMode: "plugin",
357
+ mcpMode: "none",
358
+ });
222
359
  });
223
360
  });
224
361
  }
@@ -247,7 +384,11 @@ describe("omx setup install mode behavior", () => {
247
384
  });
248
385
  assert.equal(reviewed, true);
249
386
  const persisted = JSON.parse(await readFile(join(wd, ".omx", "setup-scope.json"), "utf-8"));
250
- assert.deepEqual(persisted, { scope: "user", installMode: "plugin" });
387
+ assert.deepEqual(persisted, {
388
+ scope: "user",
389
+ installMode: "plugin",
390
+ mcpMode: "none",
391
+ });
251
392
  });
252
393
  });
253
394
  }
@@ -274,7 +415,11 @@ describe("omx setup install mode behavior", () => {
274
415
  },
275
416
  });
276
417
  const persisted = JSON.parse(await readFile(join(wd, ".omx", "setup-scope.json"), "utf-8"));
277
- assert.deepEqual(persisted, { scope: "user", installMode: "legacy" });
418
+ assert.deepEqual(persisted, {
419
+ scope: "user",
420
+ installMode: "legacy",
421
+ mcpMode: "none",
422
+ });
278
423
  });
279
424
  });
280
425
  }
@@ -324,7 +469,43 @@ describe("omx setup install mode behavior", () => {
324
469
  });
325
470
  });
326
471
  const persisted = JSON.parse(await readFile(join(wd, ".omx", "setup-scope.json"), "utf-8"));
327
- assert.deepEqual(persisted, { scope: "user", installMode: "plugin" });
472
+ assert.deepEqual(persisted, {
473
+ scope: "user",
474
+ installMode: "plugin",
475
+ mcpMode: "none",
476
+ });
477
+ }
478
+ finally {
479
+ await rm(wd, { recursive: true, force: true });
480
+ }
481
+ });
482
+ it("defaults setup to no first-party MCP blocks", async () => {
483
+ const wd = await mkdtemp(join(tmpdir(), "omx-setup-mcp-mode-"));
484
+ try {
485
+ await withIsolatedUserHome(wd, async (codexHomeDir) => {
486
+ await withTempCwd(wd, async () => {
487
+ await setup({ scope: "user", installMode: "legacy" });
488
+ });
489
+ const config = await readFile(join(codexHomeDir, "config.toml"), "utf-8");
490
+ assert.doesNotMatch(config, /^\[mcp_servers\.omx_state\]$/m);
491
+ });
492
+ }
493
+ finally {
494
+ await rm(wd, { recursive: true, force: true });
495
+ }
496
+ });
497
+ it("emits first-party MCP blocks when compat MCP mode is requested", async () => {
498
+ const wd = await mkdtemp(join(tmpdir(), "omx-setup-mcp-mode-"));
499
+ try {
500
+ await withIsolatedUserHome(wd, async (codexHomeDir) => {
501
+ await withTempCwd(wd, async () => {
502
+ await setup({ scope: "user", installMode: "legacy", mcpMode: "compat" });
503
+ });
504
+ const config = await readFile(join(codexHomeDir, "config.toml"), "utf-8");
505
+ assert.match(config, /^\[mcp_servers\.omx_state\]$/m);
506
+ const persisted = JSON.parse(await readFile(join(wd, ".omx", "setup-scope.json"), "utf-8"));
507
+ assert.equal(persisted.mcpMode, "compat");
508
+ });
328
509
  }
329
510
  finally {
330
511
  await rm(wd, { recursive: true, force: true });
@@ -340,7 +521,11 @@ describe("omx setup install mode behavior", () => {
340
521
  await writeFile(join(pluginDir, ".codex-plugin", "plugin.json"), JSON.stringify({ name: "oh-my-codex", version: "local" }));
341
522
  await setup({ scope: "user" });
342
523
  const persisted = JSON.parse(await readFile(join(wd, ".omx", "setup-scope.json"), "utf-8"));
343
- assert.deepEqual(persisted, { scope: "user", installMode: "plugin" });
524
+ assert.deepEqual(persisted, {
525
+ scope: "user",
526
+ installMode: "plugin",
527
+ mcpMode: "none",
528
+ });
344
529
  assert.equal(existsSync(join(codexHomeDir, "skills", "ask", "SKILL.md")), false);
345
530
  const hooks = await readFile(join(codexHomeDir, "hooks.json"), "utf-8");
346
531
  assert.match(hooks, /codex-native-hook\.js/);
@@ -403,7 +588,7 @@ describe("omx setup install mode behavior", () => {
403
588
  });
404
589
  assert.equal(promptCalls, 0);
405
590
  const persisted = JSON.parse(await readFile(join(wd, ".omx", "setup-scope.json"), "utf-8"));
406
- assert.deepEqual(persisted, { scope: "project" });
591
+ assert.deepEqual(persisted, { scope: "project", mcpMode: "none" });
407
592
  }
408
593
  finally {
409
594
  await rm(wd, { recursive: true, force: true });
@@ -417,11 +602,11 @@ describe("omx setup install mode behavior", () => {
417
602
  await setup({ scope: "user", installMode: "plugin" });
418
603
  await setup({ scope: "project" });
419
604
  const persisted = JSON.parse(await readFile(join(wd, ".omx", "setup-scope.json"), "utf-8"));
420
- assert.deepEqual(persisted, { scope: "project" });
605
+ assert.deepEqual(persisted, { scope: "project", mcpMode: "none" });
421
606
  assert.equal(existsSync(join(wd, ".codex", "skills", "ask", "SKILL.md")), true);
422
607
  await setup({ scope: "project" });
423
608
  const repeatedPersisted = JSON.parse(await readFile(join(wd, ".omx", "setup-scope.json"), "utf-8"));
424
- assert.deepEqual(repeatedPersisted, { scope: "project" });
609
+ assert.deepEqual(repeatedPersisted, { scope: "project", mcpMode: "none" });
425
610
  assert.equal(existsSync(join(wd, ".codex", "agents", "planner.toml")), true);
426
611
  assert.equal(existsSync(join(wd, ".codex", "prompts", "executor.md")), true);
427
612
  });
@@ -439,7 +624,11 @@ describe("omx setup install mode behavior", () => {
439
624
  await setup({ scope: "project", installMode: "plugin" });
440
625
  await setup({ scope: "user" });
441
626
  const persisted = JSON.parse(await readFile(join(wd, ".omx", "setup-scope.json"), "utf-8"));
442
- assert.deepEqual(persisted, { scope: "user", installMode: "legacy" });
627
+ assert.deepEqual(persisted, {
628
+ scope: "user",
629
+ installMode: "legacy",
630
+ mcpMode: "none",
631
+ });
443
632
  assert.equal(existsSync(join(codexHomeDir, "skills", "ask", "SKILL.md")), true);
444
633
  assert.equal(existsSync(join(codexHomeDir, "agents", "planner.toml")), true);
445
634
  assert.equal(existsSync(join(codexHomeDir, "prompts", "executor.md")), true);
@@ -478,7 +667,11 @@ describe("omx setup install mode behavior", () => {
478
667
  assert.equal(parsed.marketplaces?.other?.source, "/tmp/other");
479
668
  assert.equal((config.match(/^\[marketplaces\.oh-my-codex-local\]$/gm) ?? [])
480
669
  .length, 1);
481
- assert.match(config, /^codex_hooks = true$/m);
670
+ assert.equal((config.match(/^\[plugins\."oh-my-codex@oh-my-codex-local"\]$/gm) ?? [])
671
+ .length, 1);
672
+ assert.equal(parsed.plugins?.["oh-my-codex@oh-my-codex-local"]?.enabled, true);
673
+ assert.match(config, /^hooks = true$/m);
674
+ assert.doesNotMatch(config, /^codex_hooks = true$/m);
482
675
  assert.doesNotMatch(config, /\[mcp_servers\./);
483
676
  assert.equal(existsSync(join(codexHomeDir, "skills", "ask", "SKILL.md")), false);
484
677
  assert.equal(existsSync(join(codexHomeDir, "agents", "planner.toml")), false);
@@ -490,6 +683,67 @@ describe("omx setup install mode behavior", () => {
490
683
  await rm(wd, { recursive: true, force: true });
491
684
  }
492
685
  });
686
+ it("enables the local Codex plugin while preserving plugin subtable policy", async () => {
687
+ const wd = await mkdtemp(join(tmpdir(), "omx-setup-install-mode-"));
688
+ try {
689
+ await withIsolatedUserHome(wd, async (codexHomeDir) => {
690
+ await withTempCwd(wd, async () => {
691
+ const configPath = join(codexHomeDir, "config.toml");
692
+ await writeFile(configPath, [
693
+ "[plugins.\"oh-my-codex@oh-my-codex-local\"]",
694
+ "enabled = false",
695
+ "",
696
+ "[plugins.\"oh-my-codex@oh-my-codex-local\".mcp_servers.omx_state]",
697
+ "enabled = false",
698
+ "",
699
+ ].join("\n"));
700
+ await setup({ scope: "user", installMode: "plugin", force: true });
701
+ await setup({ scope: "user", installMode: "plugin", force: true });
702
+ const config = await readFile(configPath, "utf-8");
703
+ const parsed = parseToml(config);
704
+ assert.equal((config.match(/^\[plugins\."oh-my-codex@oh-my-codex-local"\]$/gm) ?? [])
705
+ .length, 1);
706
+ assert.equal(parsed.plugins?.["oh-my-codex@oh-my-codex-local"]?.enabled, true);
707
+ assert.equal(parsed.plugins?.["oh-my-codex@oh-my-codex-local"]?.mcp_servers
708
+ ?.omx_state?.enabled, false);
709
+ });
710
+ });
711
+ }
712
+ finally {
713
+ await rm(wd, { recursive: true, force: true });
714
+ }
715
+ });
716
+ it("enables plugin MCP subtables only when compat MCP mode is requested", async () => {
717
+ const wd = await mkdtemp(join(tmpdir(), "omx-setup-install-mode-"));
718
+ try {
719
+ await withIsolatedUserHome(wd, async (codexHomeDir) => {
720
+ await withTempCwd(wd, async () => {
721
+ const configPath = join(codexHomeDir, "config.toml");
722
+ await setup({
723
+ scope: "user",
724
+ installMode: "plugin",
725
+ mcpMode: "compat",
726
+ force: true,
727
+ });
728
+ let parsed = parseToml(await readFile(configPath, "utf-8"));
729
+ assert.equal(parsed.plugins?.["oh-my-codex@oh-my-codex-local"]?.mcp_servers
730
+ ?.omx_state?.enabled, true);
731
+ await setup({
732
+ scope: "user",
733
+ installMode: "plugin",
734
+ mcpMode: "none",
735
+ force: true,
736
+ });
737
+ parsed = parseToml(await readFile(configPath, "utf-8"));
738
+ assert.equal(parsed.plugins?.["oh-my-codex@oh-my-codex-local"]?.mcp_servers
739
+ ?.omx_state?.enabled, false);
740
+ });
741
+ });
742
+ }
743
+ finally {
744
+ await rm(wd, { recursive: true, force: true });
745
+ }
746
+ });
493
747
  it("reports plugin marketplace registration during dry-run without mutating config", async () => {
494
748
  const wd = await mkdtemp(join(tmpdir(), "omx-setup-install-mode-"));
495
749
  try {
@@ -514,15 +768,21 @@ describe("omx setup install mode behavior", () => {
514
768
  try {
515
769
  await withIsolatedUserHome(wd, async (codexHomeDir) => {
516
770
  await withTempCwd(wd, async () => {
771
+ await setup({ scope: "user", installMode: "plugin" });
517
772
  await setup({ scope: "user", installMode: "plugin" });
518
773
  const hooks = await readFile(join(codexHomeDir, "hooks.json"), "utf-8");
519
774
  assert.match(hooks, /codex-native-hook\.js/);
520
775
  const config = await readFile(join(codexHomeDir, "config.toml"), "utf-8");
521
- assert.match(config, /^codex_hooks = true$/m);
776
+ const parsed = parseToml(config);
777
+ const managedHookStateKeys = Object.keys(parsed.hooks?.state ?? {}).filter((key) => key.includes(`${codexHomeDir}/hooks.json:`));
778
+ assert.equal(managedHookStateKeys.length, 7);
779
+ assert.match(config, /^hooks = true$/m);
780
+ assert.doesNotMatch(config, /^codex_hooks = true$/m);
522
781
  assert.match(config, /^goals = true$/m);
523
782
  assert.match(config, /^\[hooks\.state\.".*hooks\.json:pre_tool_use:0:0"\]$/m);
783
+ assert.equal((config.match(/^\[hooks\.state\.".*hooks\.json:pre_tool_use:0:0"\]$/gm) ?? []).length, 1);
524
784
  assert.match(config, /^trusted_hash = "sha256:[a-f0-9]{64}"$/m);
525
- assert.doesNotMatch(config, /developer_instructions|notify-hook|mcp_servers/);
785
+ assert.doesNotMatch(config, /developer_instructions|notify-hook/g);
526
786
  assert.equal(existsSync(join(codexHomeDir, "skills", "ask", "SKILL.md")), false);
527
787
  assert.equal(existsSync(join(codexHomeDir, "agents", "planner.toml")), false);
528
788
  assert.equal(existsSync(join(codexHomeDir, "prompts", "executor.md")), false);
@@ -557,8 +817,10 @@ describe("omx setup install mode behavior", () => {
557
817
  assert.match(config, /Setup-owned prompt files and native-agent TOML defaults are intentionally omitted unless explicitly installed/);
558
818
  assert.doesNotMatch(config, /Native subagents live in \.codex\/agents/);
559
819
  assert.doesNotMatch(config, /Treat installed prompts as narrower execution surfaces/);
560
- assert.match(config, /^codex_hooks = true$/m);
561
- assert.doesNotMatch(config, /notify-hook|mcp_servers/);
820
+ assert.match(config, /^hooks = true$/m);
821
+ assert.doesNotMatch(config, /notify-hook/);
822
+ assert.doesNotMatch(config, /^\s*\[mcp_servers[.\]]/m);
823
+ assert.match(config, /mcp_servers\.omx_state]\nenabled = false/);
562
824
  const agentsMd = await readFile(join(codexHomeDir, "AGENTS.md"), "utf-8");
563
825
  assert.match(agentsMd, /oh-my-codex - Intelligent Multi-Agent Orchestration/);
564
826
  assert.match(agentsMd, /<!-- omx:generated:agents-md -->/);
@@ -618,7 +880,7 @@ describe("omx setup install mode behavior", () => {
618
880
  });
619
881
  const config = await readFile(configPath, "utf-8");
620
882
  assert.match(config, /^developer_instructions = "custom"$/m);
621
- assert.match(config, /^codex_hooks = true$/m);
883
+ assert.match(config, /^hooks = true$/m);
622
884
  assert.equal((config.match(/^developer_instructions\s*=/gm) ?? []).length, 1);
623
885
  });
624
886
  });
@@ -644,7 +906,7 @@ describe("omx setup install mode behavior", () => {
644
906
  assert.match(config, /You have oh-my-codex installed/);
645
907
  assert.doesNotMatch(config, /^developer_instructions = "custom"$/m);
646
908
  assert.equal((config.match(/^developer_instructions\s*=/gm) ?? []).length, 1);
647
- assert.match(config, /^codex_hooks = true$/m);
909
+ assert.match(config, /^hooks = true$/m);
648
910
  assert.match(config, /^\[hooks\.state\.".*hooks\.json:pre_tool_use:0:0"\]$/m);
649
911
  });
650
912
  });
@@ -653,6 +915,27 @@ describe("omx setup install mode behavior", () => {
653
915
  await rm(wd, { recursive: true, force: true });
654
916
  }
655
917
  });
918
+ it("uses legacy codex_hooks only when the installed Codex reports that hook feature", async () => {
919
+ const wd = await mkdtemp(join(tmpdir(), "omx-setup-install-mode-"));
920
+ try {
921
+ await withIsolatedUserHome(wd, async (codexHomeDir) => {
922
+ await withTempCwd(wd, async () => {
923
+ await setup({
924
+ scope: "user",
925
+ installMode: "plugin",
926
+ codexFeaturesProbe: () => "codex_hooks experimental true\n",
927
+ codexVersionProbe: () => "codex-cli 0.129.0",
928
+ });
929
+ const config = await readFile(join(codexHomeDir, "config.toml"), "utf-8");
930
+ assert.match(config, /^codex_hooks = true$/m);
931
+ assert.doesNotMatch(config, /^hooks = true$/m);
932
+ });
933
+ });
934
+ }
935
+ finally {
936
+ await rm(wd, { recursive: true, force: true });
937
+ }
938
+ });
656
939
  it("preserves existing user hooks while installing plugin-mode native hooks", async () => {
657
940
  const wd = await mkdtemp(join(tmpdir(), "omx-setup-install-mode-"));
658
941
  try {
@@ -666,7 +949,7 @@ describe("omx setup install mode behavior", () => {
666
949
  assert.match(hooks, /"UserPromptSubmit"/);
667
950
  assert.match(hooks, /codex-native-hook\.js/);
668
951
  const config = await readFile(join(codexHomeDir, "config.toml"), "utf-8");
669
- assert.match(config, /^codex_hooks = true$/m);
952
+ assert.match(config, /^hooks = true$/m);
670
953
  });
671
954
  });
672
955
  }
@@ -683,6 +966,7 @@ describe("omx setup install mode behavior", () => {
683
966
  assert.deepEqual(persisted, {
684
967
  scope: "project",
685
968
  installMode: "plugin",
969
+ mcpMode: "none",
686
970
  });
687
971
  await setup({ scope: "project" });
688
972
  assert.equal(existsSync(join(wd, ".codex", "skills", "ask", "SKILL.md")), false);
@@ -704,7 +988,7 @@ describe("omx setup install mode behavior", () => {
704
988
  await writeFile(join(wd, ".omx", "setup-scope.json"), JSON.stringify({ scope: "project", installMode: "plugin" }));
705
989
  await setup({ scope: "project", installMode: "legacy" });
706
990
  const persisted = JSON.parse(await readFile(join(wd, ".omx", "setup-scope.json"), "utf-8"));
707
- assert.deepEqual(persisted, { scope: "project" });
991
+ assert.deepEqual(persisted, { scope: "project", mcpMode: "none" });
708
992
  assert.equal(existsSync(join(wd, ".codex", "skills", "ask", "SKILL.md")), true);
709
993
  assert.equal(existsSync(join(wd, ".codex", "agents", "planner.toml")), true);
710
994
  assert.equal(existsSync(join(wd, ".codex", "prompts", "executor.md")), true);
@@ -726,6 +1010,7 @@ describe("omx setup install mode behavior", () => {
726
1010
  assert.deepEqual(persisted, {
727
1011
  scope: "user",
728
1012
  installMode: "legacy",
1013
+ mcpMode: "none",
729
1014
  });
730
1015
  assert.equal(existsSync(join(codexHomeDir, "skills", "ask", "SKILL.md")), true);
731
1016
  assert.equal(existsSync(join(codexHomeDir, "agents", "planner.toml")), true);
@@ -737,6 +1022,44 @@ describe("omx setup install mode behavior", () => {
737
1022
  await rm(wd, { recursive: true, force: true });
738
1023
  }
739
1024
  });
1025
+ it("dedupes plugin-mode hook trust state when switching user setup back to legacy", async () => {
1026
+ const wd = await mkdtemp(join(tmpdir(), "omx-setup-install-mode-"));
1027
+ try {
1028
+ await withIsolatedUserHome(wd, async (codexHomeDir) => {
1029
+ await withTempCwd(wd, async () => {
1030
+ const configPath = join(codexHomeDir, "config.toml");
1031
+ await setup({ scope: "user", installMode: "plugin", force: true });
1032
+ const pluginConfig = await readFile(configPath, "utf-8");
1033
+ const staleUnfencedPluginConfig = pluginConfig
1034
+ .split(/\r?\n/)
1035
+ .filter((line) => line.trim() !== "# OMX-owned Codex hook trust state" &&
1036
+ line.trim() !==
1037
+ "# Trusts only setup-managed codex-native-hook.js wrappers." &&
1038
+ line.trim() !== "# End OMX-owned Codex hook trust state")
1039
+ .join("\n");
1040
+ await writeFile(configPath, staleUnfencedPluginConfig);
1041
+ assert.doesNotThrow(() => parseToml(staleUnfencedPluginConfig));
1042
+ await setup({ scope: "user", installMode: "legacy", force: true });
1043
+ const legacyConfig = await readFile(configPath, "utf-8");
1044
+ assert.doesNotThrow(() => parseToml(legacyConfig));
1045
+ assert.equal(legacyConfig
1046
+ .split(/\r?\n/)
1047
+ .filter((line) => line.trim() ===
1048
+ `[hooks.state."${join(codexHomeDir, "hooks.json")}:post_compact:0:0"]`).length, 1, "legacy setup should replace stale plugin-mode hook trust state instead of duplicating it");
1049
+ assert.match(legacyConfig, /# OMX-owned Codex hook trust state[\s\S]*# End OMX-owned Codex hook trust state/);
1050
+ const persisted = JSON.parse(await readFile(join(wd, ".omx", "setup-scope.json"), "utf-8"));
1051
+ assert.deepEqual(persisted, {
1052
+ scope: "user",
1053
+ installMode: "legacy",
1054
+ mcpMode: "none",
1055
+ });
1056
+ });
1057
+ });
1058
+ }
1059
+ finally {
1060
+ await rm(wd, { recursive: true, force: true });
1061
+ }
1062
+ });
740
1063
  it("installs project-scoped native hooks when plugin mode is explicitly requested", async () => {
741
1064
  const wd = await mkdtemp(join(tmpdir(), "omx-setup-install-mode-"));
742
1065
  try {
@@ -771,7 +1094,7 @@ describe("omx setup install mode behavior", () => {
771
1094
  await setup({ scope: "project", installMode: "plugin" });
772
1095
  });
773
1096
  assert.match(pluginOutput, /Using setup install mode: plugin/);
774
- assert.match(pluginOutput, /Native Codex hooks and runtime feature flags refresh complete .*codex_hooks, goals/);
1097
+ assert.match(pluginOutput, /Native Codex hooks and runtime feature flags refresh complete .*hooks, goals/);
775
1098
  assert.doesNotMatch(pluginOutput, /user-scope skill delivery mode/);
776
1099
  assert.doesNotMatch(pluginOutput, /Native agent defaults configured.*TOML files written to \.codex\/agents\//);
777
1100
  assert.doesNotMatch(pluginOutput, /Use role\/workflow keywords like \$architect, \$executor, and \$plan/);
@@ -825,8 +1148,8 @@ describe("omx setup install mode behavior", () => {
825
1148
  const hooks = await readFile(hooksPath, "utf-8");
826
1149
  assert.match(hooks, /codex-native-hook\.js/);
827
1150
  const config = await readFile(configPath, "utf-8");
828
- assert.match(config, /^codex_hooks = true$/m);
829
- assert.doesNotMatch(config, /mcp_servers|notify|developer_instructions/);
1151
+ assert.match(config, /^hooks = true$/m);
1152
+ assert.doesNotMatch(config, /^\s*(?:notify|developer_instructions)\s*=|^\s*\[mcp_servers[.\]]/m);
830
1153
  });
831
1154
  });
832
1155
  }