oh-my-codex 0.16.3 → 0.17.0

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 (339) 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__/generator.test.js +2 -0
  5. package/dist/catalog/__tests__/generator.test.js.map +1 -1
  6. package/dist/catalog/__tests__/plugin-bundle-ssot.test.js +9 -0
  7. package/dist/catalog/__tests__/plugin-bundle-ssot.test.js.map +1 -1
  8. package/dist/cli/__tests__/cleanup.test.js +27 -0
  9. package/dist/cli/__tests__/cleanup.test.js.map +1 -1
  10. package/dist/cli/__tests__/codex-plugin-layout.test.js +7 -5
  11. package/dist/cli/__tests__/codex-plugin-layout.test.js.map +1 -1
  12. package/dist/cli/__tests__/doctor-warning-copy.test.js +175 -7
  13. package/dist/cli/__tests__/doctor-warning-copy.test.js.map +1 -1
  14. package/dist/cli/__tests__/index.test.js +147 -12
  15. package/dist/cli/__tests__/index.test.js.map +1 -1
  16. package/dist/cli/__tests__/mcp-serve.test.js +4 -0
  17. package/dist/cli/__tests__/mcp-serve.test.js.map +1 -1
  18. package/dist/cli/__tests__/ralph-goal-mode-contract.test.js +2 -0
  19. package/dist/cli/__tests__/ralph-goal-mode-contract.test.js.map +1 -1
  20. package/dist/cli/__tests__/ralph.test.js +47 -0
  21. package/dist/cli/__tests__/ralph.test.js.map +1 -1
  22. package/dist/cli/__tests__/setup-hooks-shared-ownership.test.js +10 -5
  23. package/dist/cli/__tests__/setup-hooks-shared-ownership.test.js.map +1 -1
  24. package/dist/cli/__tests__/setup-install-mode.test.js +299 -27
  25. package/dist/cli/__tests__/setup-install-mode.test.js.map +1 -1
  26. package/dist/cli/__tests__/setup-refresh.test.js +85 -3
  27. package/dist/cli/__tests__/setup-refresh.test.js.map +1 -1
  28. package/dist/cli/__tests__/setup-scope.test.js +1 -1
  29. package/dist/cli/__tests__/setup-scope.test.js.map +1 -1
  30. package/dist/cli/__tests__/setup-skills-overwrite.test.js +2 -1
  31. package/dist/cli/__tests__/setup-skills-overwrite.test.js.map +1 -1
  32. package/dist/cli/__tests__/team.test.js +108 -0
  33. package/dist/cli/__tests__/team.test.js.map +1 -1
  34. package/dist/cli/__tests__/ultragoal.test.js +91 -0
  35. package/dist/cli/__tests__/ultragoal.test.js.map +1 -1
  36. package/dist/cli/__tests__/uninstall.test.js +54 -8
  37. package/dist/cli/__tests__/uninstall.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 +214 -23
  48. package/dist/cli/doctor.js.map +1 -1
  49. package/dist/cli/index.d.ts +17 -4
  50. package/dist/cli/index.d.ts.map +1 -1
  51. package/dist/cli/index.js +152 -24
  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/mcp-serve.d.ts.map +1 -1
  56. package/dist/cli/mcp-serve.js +4 -0
  57. package/dist/cli/mcp-serve.js.map +1 -1
  58. package/dist/cli/plugin-marketplace.d.ts +23 -0
  59. package/dist/cli/plugin-marketplace.d.ts.map +1 -1
  60. package/dist/cli/plugin-marketplace.js +203 -1
  61. package/dist/cli/plugin-marketplace.js.map +1 -1
  62. package/dist/cli/ralph.d.ts.map +1 -1
  63. package/dist/cli/ralph.js +21 -0
  64. package/dist/cli/ralph.js.map +1 -1
  65. package/dist/cli/setup-preferences.d.ts +4 -0
  66. package/dist/cli/setup-preferences.d.ts.map +1 -1
  67. package/dist/cli/setup-preferences.js +7 -0
  68. package/dist/cli/setup-preferences.js.map +1 -1
  69. package/dist/cli/setup.d.ts +5 -3
  70. package/dist/cli/setup.d.ts.map +1 -1
  71. package/dist/cli/setup.js +140 -51
  72. package/dist/cli/setup.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 +70 -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 +12 -3
  80. package/dist/cli/uninstall.js.map +1 -1
  81. package/dist/config/__tests__/codex-feature-flags.test.d.ts +2 -0
  82. package/dist/config/__tests__/codex-feature-flags.test.d.ts.map +1 -0
  83. package/dist/config/__tests__/codex-feature-flags.test.js +35 -0
  84. package/dist/config/__tests__/codex-feature-flags.test.js.map +1 -0
  85. package/dist/config/__tests__/codex-hooks.test.js +143 -9
  86. package/dist/config/__tests__/codex-hooks.test.js.map +1 -1
  87. package/dist/config/__tests__/generator-idempotent.test.js +85 -9
  88. package/dist/config/__tests__/generator-idempotent.test.js.map +1 -1
  89. package/dist/config/__tests__/generator-notify.test.js +116 -11
  90. package/dist/config/__tests__/generator-notify.test.js.map +1 -1
  91. package/dist/config/__tests__/wiki-config-contract.test.js +6 -3
  92. package/dist/config/__tests__/wiki-config-contract.test.js.map +1 -1
  93. package/dist/config/codex-feature-flags.d.ts +21 -0
  94. package/dist/config/codex-feature-flags.d.ts.map +1 -0
  95. package/dist/config/codex-feature-flags.js +56 -0
  96. package/dist/config/codex-feature-flags.js.map +1 -0
  97. package/dist/config/codex-hooks.d.ts +14 -13
  98. package/dist/config/codex-hooks.d.ts.map +1 -1
  99. package/dist/config/codex-hooks.js +108 -8
  100. package/dist/config/codex-hooks.js.map +1 -1
  101. package/dist/config/generator.d.ts +15 -3
  102. package/dist/config/generator.d.ts.map +1 -1
  103. package/dist/config/generator.js +233 -129
  104. package/dist/config/generator.js.map +1 -1
  105. package/dist/config/omx-first-party-mcp.d.ts +3 -1
  106. package/dist/config/omx-first-party-mcp.d.ts.map +1 -1
  107. package/dist/config/omx-first-party-mcp.js +9 -2
  108. package/dist/config/omx-first-party-mcp.js.map +1 -1
  109. package/dist/hooks/__tests__/design-skill.test.d.ts +2 -0
  110. package/dist/hooks/__tests__/design-skill.test.d.ts.map +1 -0
  111. package/dist/hooks/__tests__/design-skill.test.js +55 -0
  112. package/dist/hooks/__tests__/design-skill.test.js.map +1 -0
  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-hook-non-omx-guard.test.js +125 -1
  116. package/dist/hooks/__tests__/notify-hook-non-omx-guard.test.js.map +1 -1
  117. package/dist/hooks/__tests__/notify-hook-tmux-heal.test.js +265 -0
  118. package/dist/hooks/__tests__/notify-hook-tmux-heal.test.js.map +1 -1
  119. package/dist/hooks/__tests__/skill-catalog-hygiene.test.d.ts +2 -0
  120. package/dist/hooks/__tests__/skill-catalog-hygiene.test.d.ts.map +1 -0
  121. package/dist/hooks/__tests__/skill-catalog-hygiene.test.js +84 -0
  122. package/dist/hooks/__tests__/skill-catalog-hygiene.test.js.map +1 -0
  123. package/dist/hooks/__tests__/skill-guidance-contract.test.js +41 -0
  124. package/dist/hooks/__tests__/skill-guidance-contract.test.js.map +1 -1
  125. package/dist/hooks/agents-overlay.js +2 -2
  126. package/dist/hooks/agents-overlay.js.map +1 -1
  127. package/dist/hooks/keyword-detector.d.ts +1 -0
  128. package/dist/hooks/keyword-detector.d.ts.map +1 -1
  129. package/dist/hooks/keyword-detector.js +12 -6
  130. package/dist/hooks/keyword-detector.js.map +1 -1
  131. package/dist/hooks/keyword-registry.d.ts.map +1 -1
  132. package/dist/hooks/keyword-registry.js +2 -0
  133. package/dist/hooks/keyword-registry.js.map +1 -1
  134. package/dist/hooks/prompt-guidance-contract.d.ts.map +1 -1
  135. package/dist/hooks/prompt-guidance-contract.js +47 -2
  136. package/dist/hooks/prompt-guidance-contract.js.map +1 -1
  137. package/dist/hud/__tests__/state.test.js +164 -0
  138. package/dist/hud/__tests__/state.test.js.map +1 -1
  139. package/dist/hud/state.d.ts.map +1 -1
  140. package/dist/hud/state.js +4 -5
  141. package/dist/hud/state.js.map +1 -1
  142. package/dist/mcp/__tests__/bootstrap.test.js +3 -0
  143. package/dist/mcp/__tests__/bootstrap.test.js.map +1 -1
  144. package/dist/mcp/__tests__/hermes-bridge.test.d.ts +2 -0
  145. package/dist/mcp/__tests__/hermes-bridge.test.d.ts.map +1 -0
  146. package/dist/mcp/__tests__/hermes-bridge.test.js +374 -0
  147. package/dist/mcp/__tests__/hermes-bridge.test.js.map +1 -0
  148. package/dist/mcp/__tests__/state-paths.test.js +155 -11
  149. package/dist/mcp/__tests__/state-paths.test.js.map +1 -1
  150. package/dist/mcp/__tests__/state-server.test.js +166 -0
  151. package/dist/mcp/__tests__/state-server.test.js.map +1 -1
  152. package/dist/mcp/bootstrap.d.ts +1 -1
  153. package/dist/mcp/bootstrap.d.ts.map +1 -1
  154. package/dist/mcp/bootstrap.js +2 -0
  155. package/dist/mcp/bootstrap.js.map +1 -1
  156. package/dist/mcp/hermes-bridge.d.ts +81 -0
  157. package/dist/mcp/hermes-bridge.d.ts.map +1 -0
  158. package/dist/mcp/hermes-bridge.js +400 -0
  159. package/dist/mcp/hermes-bridge.js.map +1 -0
  160. package/dist/mcp/hermes-server.d.ts +269 -0
  161. package/dist/mcp/hermes-server.d.ts.map +1 -0
  162. package/dist/mcp/hermes-server.js +121 -0
  163. package/dist/mcp/hermes-server.js.map +1 -0
  164. package/dist/mcp/state-paths.d.ts.map +1 -1
  165. package/dist/mcp/state-paths.js +64 -11
  166. package/dist/mcp/state-paths.js.map +1 -1
  167. package/dist/modes/__tests__/base-session-scope.test.js +22 -0
  168. package/dist/modes/__tests__/base-session-scope.test.js.map +1 -1
  169. package/dist/modes/__tests__/base-tmux-pane.test.js +88 -27
  170. package/dist/modes/__tests__/base-tmux-pane.test.js.map +1 -1
  171. package/dist/modes/base.d.ts.map +1 -1
  172. package/dist/modes/base.js +5 -0
  173. package/dist/modes/base.js.map +1 -1
  174. package/dist/planning/__tests__/approved-execution-lifecycle-matrix.test.d.ts +2 -0
  175. package/dist/planning/__tests__/approved-execution-lifecycle-matrix.test.d.ts.map +1 -0
  176. package/dist/planning/__tests__/approved-execution-lifecycle-matrix.test.js +316 -0
  177. package/dist/planning/__tests__/approved-execution-lifecycle-matrix.test.js.map +1 -0
  178. package/dist/planning/__tests__/approved-launch-hint-lineage-matrix.test.d.ts +2 -0
  179. package/dist/planning/__tests__/approved-launch-hint-lineage-matrix.test.d.ts.map +1 -0
  180. package/dist/planning/__tests__/approved-launch-hint-lineage-matrix.test.js +481 -0
  181. package/dist/planning/__tests__/approved-launch-hint-lineage-matrix.test.js.map +1 -0
  182. package/dist/planning/__tests__/artifacts.test.js +533 -4
  183. package/dist/planning/__tests__/artifacts.test.js.map +1 -1
  184. package/dist/planning/__tests__/context-pack-status.test.js +524 -0
  185. package/dist/planning/__tests__/context-pack-status.test.js.map +1 -1
  186. package/dist/planning/__tests__/markdown-structure.test.d.ts +2 -0
  187. package/dist/planning/__tests__/markdown-structure.test.d.ts.map +1 -0
  188. package/dist/planning/__tests__/markdown-structure.test.js +459 -0
  189. package/dist/planning/__tests__/markdown-structure.test.js.map +1 -0
  190. package/dist/planning/__tests__/ready-context-pack-role-refs.test.js +523 -1
  191. package/dist/planning/__tests__/ready-context-pack-role-refs.test.js.map +1 -1
  192. package/dist/planning/artifacts.d.ts +1 -1
  193. package/dist/planning/artifacts.d.ts.map +1 -1
  194. package/dist/planning/artifacts.js +227 -28
  195. package/dist/planning/artifacts.js.map +1 -1
  196. package/dist/planning/context-pack-status.d.ts +25 -0
  197. package/dist/planning/context-pack-status.d.ts.map +1 -1
  198. package/dist/planning/context-pack-status.js +272 -31
  199. package/dist/planning/context-pack-status.js.map +1 -1
  200. package/dist/planning/markdown-structure.d.ts +20 -0
  201. package/dist/planning/markdown-structure.d.ts.map +1 -0
  202. package/dist/planning/markdown-structure.js +137 -0
  203. package/dist/planning/markdown-structure.js.map +1 -0
  204. package/dist/ralph/__tests__/completion-audit.test.d.ts +2 -0
  205. package/dist/ralph/__tests__/completion-audit.test.d.ts.map +1 -0
  206. package/dist/ralph/__tests__/completion-audit.test.js +121 -0
  207. package/dist/ralph/__tests__/completion-audit.test.js.map +1 -0
  208. package/dist/ralph/completion-audit.d.ts +8 -0
  209. package/dist/ralph/completion-audit.d.ts.map +1 -0
  210. package/dist/ralph/completion-audit.js +99 -0
  211. package/dist/ralph/completion-audit.js.map +1 -0
  212. package/dist/scripts/__tests__/codex-native-hook.test.js +407 -15
  213. package/dist/scripts/__tests__/codex-native-hook.test.js.map +1 -1
  214. package/dist/scripts/__tests__/notify-dispatcher.test.d.ts +2 -0
  215. package/dist/scripts/__tests__/notify-dispatcher.test.d.ts.map +1 -0
  216. package/dist/scripts/__tests__/notify-dispatcher.test.js +126 -0
  217. package/dist/scripts/__tests__/notify-dispatcher.test.js.map +1 -0
  218. package/dist/scripts/codex-native-hook.d.ts +1 -0
  219. package/dist/scripts/codex-native-hook.d.ts.map +1 -1
  220. package/dist/scripts/codex-native-hook.js +177 -71
  221. package/dist/scripts/codex-native-hook.js.map +1 -1
  222. package/dist/scripts/codex-native-pre-post.d.ts.map +1 -1
  223. package/dist/scripts/codex-native-pre-post.js +4 -2
  224. package/dist/scripts/codex-native-pre-post.js.map +1 -1
  225. package/dist/scripts/notify-dispatcher.js +30 -1
  226. package/dist/scripts/notify-dispatcher.js.map +1 -1
  227. package/dist/scripts/notify-hook/tmux-injection.d.ts.map +1 -1
  228. package/dist/scripts/notify-hook/tmux-injection.js +91 -2
  229. package/dist/scripts/notify-hook/tmux-injection.js.map +1 -1
  230. package/dist/scripts/notify-hook.js +3 -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/mode-state-context.d.ts +2 -0
  235. package/dist/state/mode-state-context.d.ts.map +1 -1
  236. package/dist/state/mode-state-context.js +21 -0
  237. package/dist/state/mode-state-context.js.map +1 -1
  238. package/dist/state/operations.d.ts.map +1 -1
  239. package/dist/state/operations.js +9 -3
  240. package/dist/state/operations.js.map +1 -1
  241. package/dist/state/skill-active.d.ts +7 -0
  242. package/dist/state/skill-active.d.ts.map +1 -1
  243. package/dist/state/skill-active.js +25 -8
  244. package/dist/state/skill-active.js.map +1 -1
  245. package/dist/state/workflow-transition-reconcile.d.ts +1 -0
  246. package/dist/state/workflow-transition-reconcile.d.ts.map +1 -1
  247. package/dist/state/workflow-transition-reconcile.js +22 -15
  248. package/dist/state/workflow-transition-reconcile.js.map +1 -1
  249. package/dist/state/workflow-transition.js +3 -3
  250. package/dist/state/workflow-transition.js.map +1 -1
  251. package/dist/team/__tests__/approved-execution.test.js +39 -0
  252. package/dist/team/__tests__/approved-execution.test.js.map +1 -1
  253. package/dist/team/__tests__/runtime.test.js +5 -0
  254. package/dist/team/__tests__/runtime.test.js.map +1 -1
  255. package/dist/team/__tests__/scaling.test.js +497 -2
  256. package/dist/team/__tests__/scaling.test.js.map +1 -1
  257. package/dist/team/__tests__/state-root.test.js +1 -1
  258. package/dist/team/__tests__/state-root.test.js.map +1 -1
  259. package/dist/team/__tests__/worker-bootstrap.test.js +8 -0
  260. package/dist/team/__tests__/worker-bootstrap.test.js.map +1 -1
  261. package/dist/team/approved-execution.d.ts.map +1 -1
  262. package/dist/team/approved-execution.js +3 -0
  263. package/dist/team/approved-execution.js.map +1 -1
  264. package/dist/team/scaling.d.ts.map +1 -1
  265. package/dist/team/scaling.js +43 -0
  266. package/dist/team/scaling.js.map +1 -1
  267. package/dist/team/state-root.d.ts.map +1 -1
  268. package/dist/team/state-root.js +4 -0
  269. package/dist/team/state-root.js.map +1 -1
  270. package/dist/team/state.d.ts.map +1 -1
  271. package/dist/team/state.js +2 -6
  272. package/dist/team/state.js.map +1 -1
  273. package/dist/ultragoal/__tests__/artifacts.test.js +245 -1
  274. package/dist/ultragoal/__tests__/artifacts.test.js.map +1 -1
  275. package/dist/ultragoal/__tests__/docs-contract.test.js +21 -0
  276. package/dist/ultragoal/__tests__/docs-contract.test.js.map +1 -1
  277. package/dist/ultragoal/artifacts.d.ts +52 -2
  278. package/dist/ultragoal/artifacts.d.ts.map +1 -1
  279. package/dist/ultragoal/artifacts.js +301 -15
  280. package/dist/ultragoal/artifacts.js.map +1 -1
  281. package/dist/utils/__tests__/paths.test.js +31 -1
  282. package/dist/utils/__tests__/paths.test.js.map +1 -1
  283. package/dist/utils/paths.d.ts +6 -0
  284. package/dist/utils/paths.d.ts.map +1 -1
  285. package/dist/utils/paths.js +18 -0
  286. package/dist/utils/paths.js.map +1 -1
  287. package/dist/wiki/lifecycle.js +4 -4
  288. package/dist/wiki/lifecycle.js.map +1 -1
  289. package/package.json +1 -1
  290. package/plugins/oh-my-codex/.codex-plugin/plugin.json +1 -1
  291. package/plugins/oh-my-codex/.mcp.json +13 -5
  292. package/plugins/oh-my-codex/skills/analyze/SKILL.md +0 -2
  293. package/plugins/oh-my-codex/skills/autopilot/SKILL.md +2 -2
  294. package/plugins/oh-my-codex/skills/code-review/SKILL.md +1 -3
  295. package/plugins/oh-my-codex/skills/deep-interview/SKILL.md +5 -7
  296. package/plugins/oh-my-codex/skills/design/SKILL.md +180 -0
  297. package/plugins/oh-my-codex/skills/doctor/SKILL.md +2 -2
  298. package/plugins/oh-my-codex/skills/omx-setup/SKILL.md +3 -3
  299. package/plugins/oh-my-codex/skills/pipeline/SKILL.md +3 -3
  300. package/plugins/oh-my-codex/skills/plan/SKILL.md +3 -6
  301. package/plugins/oh-my-codex/skills/ralph/SKILL.md +9 -10
  302. package/plugins/oh-my-codex/skills/skill/SKILL.md +2 -1
  303. package/plugins/oh-my-codex/skills/ultragoal/SKILL.md +36 -3
  304. package/plugins/oh-my-codex/skills/ultraqa/SKILL.md +175 -64
  305. package/plugins/oh-my-codex/skills/ultrawork/SKILL.md +8 -8
  306. package/plugins/oh-my-codex/skills/visual-ralph/SKILL.md +2 -2
  307. package/plugins/oh-my-codex/skills/wiki/SKILL.md +13 -13
  308. package/skills/analyze/SKILL.md +0 -2
  309. package/skills/ask-claude/SKILL.md +5 -3
  310. package/skills/ask-gemini/SKILL.md +5 -3
  311. package/skills/autopilot/SKILL.md +2 -2
  312. package/skills/code-review/SKILL.md +1 -3
  313. package/skills/deep-interview/SKILL.md +5 -7
  314. package/skills/design/SKILL.md +180 -0
  315. package/skills/doctor/SKILL.md +2 -2
  316. package/skills/ecomode/SKILL.md +105 -1
  317. package/skills/frontend-ui-ux/SKILL.md +6 -24
  318. package/skills/git-master/SKILL.md +2 -4
  319. package/skills/omx-setup/SKILL.md +3 -3
  320. package/skills/pipeline/SKILL.md +3 -3
  321. package/skills/plan/SKILL.md +3 -6
  322. package/skills/ralph/SKILL.md +9 -10
  323. package/skills/skill/SKILL.md +2 -1
  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 +175 -64
  328. package/skills/ultrawork/SKILL.md +8 -8
  329. package/skills/visual-ralph/SKILL.md +2 -2
  330. package/skills/web-clone/SKILL.md +348 -1
  331. package/skills/wiki/SKILL.md +13 -13
  332. package/src/scripts/__tests__/codex-native-hook.test.ts +437 -14
  333. package/src/scripts/__tests__/notify-dispatcher.test.ts +153 -0
  334. package/src/scripts/codex-native-hook.ts +205 -61
  335. package/src/scripts/codex-native-pre-post.ts +4 -1
  336. package/src/scripts/notify-dispatcher.ts +40 -1
  337. package/src/scripts/notify-hook/tmux-injection.ts +110 -3
  338. package/src/scripts/notify-hook.ts +3 -1
  339. package/templates/catalog-manifest.json +9 -2
@@ -3,7 +3,7 @@ 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
9
  import { uninstall } from "../uninstall.js";
@@ -128,14 +128,71 @@ describe("notify setup scope", () => {
128
128
  await rm(wd, { recursive: true, force: true });
129
129
  }
130
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
+ });
131
188
  });
132
189
  async function assertProjectPluginModeArtifacts(wd) {
133
190
  const hooks = await readFile(join(wd, ".codex", "hooks.json"), "utf-8");
134
191
  assert.match(hooks, /codex-native-hook\.js/);
135
192
  const config = await readFile(join(wd, ".codex", "config.toml"), "utf-8");
136
- assert.match(config, /^codex_hooks = true$/m);
193
+ assert.match(config, /^hooks = true$/m);
137
194
  assert.match(config, /^goals = true$/m);
138
- assert.doesNotMatch(config, /developer_instructions|notify-hook|mcp_servers/);
195
+ assert.doesNotMatch(config, /developer_instructions|notify-hook/g);
139
196
  assert.equal(existsSync(join(wd, ".codex", "skills", "ask", "SKILL.md")), false);
140
197
  assert.equal(existsSync(join(wd, ".codex", "agents", "planner.toml")), false);
141
198
  assert.equal(existsSync(join(wd, ".codex", "prompts", "executor.md")), false);
@@ -144,6 +201,7 @@ async function assertProjectPluginModeArtifacts(wd) {
144
201
  assert.deepEqual(persisted, {
145
202
  scope: "project",
146
203
  installMode: "plugin",
204
+ mcpMode: "none",
147
205
  });
148
206
  }
149
207
  async function captureConsoleOutput(fn) {
@@ -189,6 +247,10 @@ async function seedStalePluginDiscoveryCache(codexHomeDir) {
189
247
  await writeFile(join(artifactPath, "skills", "old-only", "SKILL.md"), "# old\n");
190
248
  return artifactPath;
191
249
  }
250
+ async function packagedPluginCacheDir(codexHomeDir) {
251
+ const manifest = JSON.parse(await readFile(join(packageRoot, "plugins", "oh-my-codex", ".codex-plugin", "plugin.json"), "utf-8"));
252
+ return join(codexHomeDir, "plugins", "cache", "oh-my-codex-local", "oh-my-codex", manifest.version);
253
+ }
192
254
  describe("omx setup install mode behavior", () => {
193
255
  it("summarizes and keeps persisted setup preferences when review chooses keep", async () => {
194
256
  const wd = await mkdtemp(join(tmpdir(), "omx-setup-install-mode-"));
@@ -208,7 +270,7 @@ describe("omx setup install mode behavior", () => {
208
270
  },
209
271
  });
210
272
  });
211
- assert.match(output, /Setup preference review: keep \(scope=user, installMode=legacy\)/);
273
+ assert.match(output, /Setup preference review: keep \(scope=user, installMode=legacy, mcpMode=not recorded\)/);
212
274
  assert.match(output, /Using setup scope: user \(from \.omx\/setup-scope\.json\)/);
213
275
  assert.match(output, /Using setup install mode: legacy \(from \.omx\/setup-scope\.json\)/);
214
276
  });
@@ -237,7 +299,11 @@ describe("omx setup install mode behavior", () => {
237
299
  },
238
300
  });
239
301
  const persisted = JSON.parse(await readFile(join(wd, ".omx", "setup-scope.json"), "utf-8"));
240
- assert.deepEqual(persisted, { scope: "user", installMode: "plugin" });
302
+ assert.deepEqual(persisted, {
303
+ scope: "user",
304
+ installMode: "plugin",
305
+ mcpMode: "none",
306
+ });
241
307
  });
242
308
  });
243
309
  }
@@ -260,7 +326,7 @@ describe("omx setup install mode behavior", () => {
260
326
  },
261
327
  });
262
328
  const persisted = JSON.parse(await readFile(join(wd, ".omx", "setup-scope.json"), "utf-8"));
263
- assert.deepEqual(persisted, { scope: "project" });
329
+ assert.deepEqual(persisted, { scope: "project", mcpMode: "none" });
264
330
  });
265
331
  });
266
332
  }
@@ -289,7 +355,11 @@ describe("omx setup install mode behavior", () => {
289
355
  });
290
356
  assert.equal(reviewed, true);
291
357
  const persisted = JSON.parse(await readFile(join(wd, ".omx", "setup-scope.json"), "utf-8"));
292
- assert.deepEqual(persisted, { scope: "user", installMode: "plugin" });
358
+ assert.deepEqual(persisted, {
359
+ scope: "user",
360
+ installMode: "plugin",
361
+ mcpMode: "none",
362
+ });
293
363
  });
294
364
  });
295
365
  }
@@ -318,7 +388,11 @@ describe("omx setup install mode behavior", () => {
318
388
  });
319
389
  assert.equal(reviewed, true);
320
390
  const persisted = JSON.parse(await readFile(join(wd, ".omx", "setup-scope.json"), "utf-8"));
321
- assert.deepEqual(persisted, { scope: "user", installMode: "plugin" });
391
+ assert.deepEqual(persisted, {
392
+ scope: "user",
393
+ installMode: "plugin",
394
+ mcpMode: "none",
395
+ });
322
396
  });
323
397
  });
324
398
  }
@@ -345,7 +419,11 @@ describe("omx setup install mode behavior", () => {
345
419
  },
346
420
  });
347
421
  const persisted = JSON.parse(await readFile(join(wd, ".omx", "setup-scope.json"), "utf-8"));
348
- assert.deepEqual(persisted, { scope: "user", installMode: "legacy" });
422
+ assert.deepEqual(persisted, {
423
+ scope: "user",
424
+ installMode: "legacy",
425
+ mcpMode: "none",
426
+ });
349
427
  });
350
428
  });
351
429
  }
@@ -395,7 +473,43 @@ describe("omx setup install mode behavior", () => {
395
473
  });
396
474
  });
397
475
  const persisted = JSON.parse(await readFile(join(wd, ".omx", "setup-scope.json"), "utf-8"));
398
- assert.deepEqual(persisted, { scope: "user", installMode: "plugin" });
476
+ assert.deepEqual(persisted, {
477
+ scope: "user",
478
+ installMode: "plugin",
479
+ mcpMode: "none",
480
+ });
481
+ }
482
+ finally {
483
+ await rm(wd, { recursive: true, force: true });
484
+ }
485
+ });
486
+ it("defaults setup to no first-party MCP blocks", async () => {
487
+ const wd = await mkdtemp(join(tmpdir(), "omx-setup-mcp-mode-"));
488
+ try {
489
+ await withIsolatedUserHome(wd, async (codexHomeDir) => {
490
+ await withTempCwd(wd, async () => {
491
+ await setup({ scope: "user", installMode: "legacy" });
492
+ });
493
+ const config = await readFile(join(codexHomeDir, "config.toml"), "utf-8");
494
+ assert.doesNotMatch(config, /^\[mcp_servers\.omx_state\]$/m);
495
+ });
496
+ }
497
+ finally {
498
+ await rm(wd, { recursive: true, force: true });
499
+ }
500
+ });
501
+ it("emits first-party MCP blocks when compat MCP mode is requested", async () => {
502
+ const wd = await mkdtemp(join(tmpdir(), "omx-setup-mcp-mode-"));
503
+ try {
504
+ await withIsolatedUserHome(wd, async (codexHomeDir) => {
505
+ await withTempCwd(wd, async () => {
506
+ await setup({ scope: "user", installMode: "legacy", mcpMode: "compat" });
507
+ });
508
+ const config = await readFile(join(codexHomeDir, "config.toml"), "utf-8");
509
+ assert.match(config, /^\[mcp_servers\.omx_state\]$/m);
510
+ const persisted = JSON.parse(await readFile(join(wd, ".omx", "setup-scope.json"), "utf-8"));
511
+ assert.equal(persisted.mcpMode, "compat");
512
+ });
399
513
  }
400
514
  finally {
401
515
  await rm(wd, { recursive: true, force: true });
@@ -411,7 +525,11 @@ describe("omx setup install mode behavior", () => {
411
525
  await writeFile(join(pluginDir, ".codex-plugin", "plugin.json"), JSON.stringify({ name: "oh-my-codex", version: "local" }));
412
526
  await setup({ scope: "user" });
413
527
  const persisted = JSON.parse(await readFile(join(wd, ".omx", "setup-scope.json"), "utf-8"));
414
- assert.deepEqual(persisted, { scope: "user", installMode: "plugin" });
528
+ assert.deepEqual(persisted, {
529
+ scope: "user",
530
+ installMode: "plugin",
531
+ mcpMode: "none",
532
+ });
415
533
  assert.equal(existsSync(join(codexHomeDir, "skills", "ask", "SKILL.md")), false);
416
534
  const hooks = await readFile(join(codexHomeDir, "hooks.json"), "utf-8");
417
535
  assert.match(hooks, /codex-native-hook\.js/);
@@ -431,7 +549,8 @@ describe("omx setup install mode behavior", () => {
431
549
  const output = await captureConsoleOutput(async () => {
432
550
  await setup({ scope: "user", installMode: "plugin" });
433
551
  });
434
- assert.equal(existsSync(cacheDir), false);
552
+ assert.equal(existsSync(join(cacheDir, "skills", "old-only", "SKILL.md")), false);
553
+ assert.equal(existsSync(join(await packagedPluginCacheDir(codexHomeDir), "skills", "ask", "SKILL.md")), true);
435
554
  assert.match(output, /Invalidated 1 stale Codex plugin discovery cache entry/);
436
555
  assert.equal(existsSync(join(codexHomeDir, "skills", "old-only", "SKILL.md")), false);
437
556
  });
@@ -459,6 +578,24 @@ describe("omx setup install mode behavior", () => {
459
578
  await rm(wd, { recursive: true, force: true });
460
579
  }
461
580
  });
581
+ it("reports plugin cache materialization during dry-run without writing cache", async () => {
582
+ const wd = await mkdtemp(join(tmpdir(), "omx-setup-install-mode-"));
583
+ try {
584
+ await withIsolatedUserHome(wd, async (codexHomeDir) => {
585
+ await withTempCwd(wd, async () => {
586
+ const output = await captureConsoleOutput(async () => {
587
+ await setup({ scope: "user", installMode: "plugin", dryRun: true });
588
+ });
589
+ const cacheDir = await packagedPluginCacheDir(codexHomeDir);
590
+ assert.equal(existsSync(cacheDir), false);
591
+ assert.match(output, /Would install local Codex plugin cache/);
592
+ });
593
+ });
594
+ }
595
+ finally {
596
+ await rm(wd, { recursive: true, force: true });
597
+ }
598
+ });
462
599
  it("does not prompt for install mode during project-scoped setup", async () => {
463
600
  const wd = await mkdtemp(join(tmpdir(), "omx-setup-install-mode-"));
464
601
  let promptCalls = 0;
@@ -474,7 +611,7 @@ describe("omx setup install mode behavior", () => {
474
611
  });
475
612
  assert.equal(promptCalls, 0);
476
613
  const persisted = JSON.parse(await readFile(join(wd, ".omx", "setup-scope.json"), "utf-8"));
477
- assert.deepEqual(persisted, { scope: "project" });
614
+ assert.deepEqual(persisted, { scope: "project", mcpMode: "none" });
478
615
  }
479
616
  finally {
480
617
  await rm(wd, { recursive: true, force: true });
@@ -488,11 +625,11 @@ describe("omx setup install mode behavior", () => {
488
625
  await setup({ scope: "user", installMode: "plugin" });
489
626
  await setup({ scope: "project" });
490
627
  const persisted = JSON.parse(await readFile(join(wd, ".omx", "setup-scope.json"), "utf-8"));
491
- assert.deepEqual(persisted, { scope: "project" });
628
+ assert.deepEqual(persisted, { scope: "project", mcpMode: "none" });
492
629
  assert.equal(existsSync(join(wd, ".codex", "skills", "ask", "SKILL.md")), true);
493
630
  await setup({ scope: "project" });
494
631
  const repeatedPersisted = JSON.parse(await readFile(join(wd, ".omx", "setup-scope.json"), "utf-8"));
495
- assert.deepEqual(repeatedPersisted, { scope: "project" });
632
+ assert.deepEqual(repeatedPersisted, { scope: "project", mcpMode: "none" });
496
633
  assert.equal(existsSync(join(wd, ".codex", "agents", "planner.toml")), true);
497
634
  assert.equal(existsSync(join(wd, ".codex", "prompts", "executor.md")), true);
498
635
  });
@@ -510,7 +647,11 @@ describe("omx setup install mode behavior", () => {
510
647
  await setup({ scope: "project", installMode: "plugin" });
511
648
  await setup({ scope: "user" });
512
649
  const persisted = JSON.parse(await readFile(join(wd, ".omx", "setup-scope.json"), "utf-8"));
513
- assert.deepEqual(persisted, { scope: "user", installMode: "legacy" });
650
+ assert.deepEqual(persisted, {
651
+ scope: "user",
652
+ installMode: "legacy",
653
+ mcpMode: "none",
654
+ });
514
655
  assert.equal(existsSync(join(codexHomeDir, "skills", "ask", "SKILL.md")), true);
515
656
  assert.equal(existsSync(join(codexHomeDir, "agents", "planner.toml")), true);
516
657
  assert.equal(existsSync(join(codexHomeDir, "prompts", "executor.md")), true);
@@ -549,7 +690,14 @@ describe("omx setup install mode behavior", () => {
549
690
  assert.equal(parsed.marketplaces?.other?.source, "/tmp/other");
550
691
  assert.equal((config.match(/^\[marketplaces\.oh-my-codex-local\]$/gm) ?? [])
551
692
  .length, 1);
552
- assert.match(config, /^codex_hooks = true$/m);
693
+ assert.equal((config.match(/^\[plugins\."oh-my-codex@oh-my-codex-local"\]$/gm) ?? [])
694
+ .length, 1);
695
+ assert.equal(parsed.plugins?.["oh-my-codex@oh-my-codex-local"]?.enabled, true);
696
+ const cacheDir = await packagedPluginCacheDir(codexHomeDir);
697
+ assert.equal(existsSync(join(cacheDir, ".codex-plugin", "plugin.json")), true);
698
+ assert.equal(existsSync(join(cacheDir, "skills", "ask", "SKILL.md")), true);
699
+ assert.match(config, /^hooks = true$/m);
700
+ assert.doesNotMatch(config, /^codex_hooks = true$/m);
553
701
  assert.doesNotMatch(config, /\[mcp_servers\./);
554
702
  assert.equal(existsSync(join(codexHomeDir, "skills", "ask", "SKILL.md")), false);
555
703
  assert.equal(existsSync(join(codexHomeDir, "agents", "planner.toml")), false);
@@ -561,6 +709,67 @@ describe("omx setup install mode behavior", () => {
561
709
  await rm(wd, { recursive: true, force: true });
562
710
  }
563
711
  });
712
+ it("enables the local Codex plugin while preserving plugin subtable policy", async () => {
713
+ const wd = await mkdtemp(join(tmpdir(), "omx-setup-install-mode-"));
714
+ try {
715
+ await withIsolatedUserHome(wd, async (codexHomeDir) => {
716
+ await withTempCwd(wd, async () => {
717
+ const configPath = join(codexHomeDir, "config.toml");
718
+ await writeFile(configPath, [
719
+ "[plugins.\"oh-my-codex@oh-my-codex-local\"]",
720
+ "enabled = false",
721
+ "",
722
+ "[plugins.\"oh-my-codex@oh-my-codex-local\".mcp_servers.omx_state]",
723
+ "enabled = false",
724
+ "",
725
+ ].join("\n"));
726
+ await setup({ scope: "user", installMode: "plugin", force: true });
727
+ await setup({ scope: "user", installMode: "plugin", force: true });
728
+ const config = await readFile(configPath, "utf-8");
729
+ const parsed = parseToml(config);
730
+ assert.equal((config.match(/^\[plugins\."oh-my-codex@oh-my-codex-local"\]$/gm) ?? [])
731
+ .length, 1);
732
+ assert.equal(parsed.plugins?.["oh-my-codex@oh-my-codex-local"]?.enabled, true);
733
+ assert.equal(parsed.plugins?.["oh-my-codex@oh-my-codex-local"]?.mcp_servers
734
+ ?.omx_state?.enabled, false);
735
+ });
736
+ });
737
+ }
738
+ finally {
739
+ await rm(wd, { recursive: true, force: true });
740
+ }
741
+ });
742
+ it("enables plugin MCP subtables only when compat MCP mode is requested", async () => {
743
+ const wd = await mkdtemp(join(tmpdir(), "omx-setup-install-mode-"));
744
+ try {
745
+ await withIsolatedUserHome(wd, async (codexHomeDir) => {
746
+ await withTempCwd(wd, async () => {
747
+ const configPath = join(codexHomeDir, "config.toml");
748
+ await setup({
749
+ scope: "user",
750
+ installMode: "plugin",
751
+ mcpMode: "compat",
752
+ force: true,
753
+ });
754
+ let parsed = parseToml(await readFile(configPath, "utf-8"));
755
+ assert.equal(parsed.plugins?.["oh-my-codex@oh-my-codex-local"]?.mcp_servers
756
+ ?.omx_state?.enabled, true);
757
+ await setup({
758
+ scope: "user",
759
+ installMode: "plugin",
760
+ mcpMode: "none",
761
+ force: true,
762
+ });
763
+ parsed = parseToml(await readFile(configPath, "utf-8"));
764
+ assert.equal(parsed.plugins?.["oh-my-codex@oh-my-codex-local"]?.mcp_servers
765
+ ?.omx_state?.enabled, false);
766
+ });
767
+ });
768
+ }
769
+ finally {
770
+ await rm(wd, { recursive: true, force: true });
771
+ }
772
+ });
564
773
  it("reports plugin marketplace registration during dry-run without mutating config", async () => {
565
774
  const wd = await mkdtemp(join(tmpdir(), "omx-setup-install-mode-"));
566
775
  try {
@@ -593,13 +802,13 @@ describe("omx setup install mode behavior", () => {
593
802
  const parsed = parseToml(config);
594
803
  const managedHookStateKeys = Object.keys(parsed.hooks?.state ?? {}).filter((key) => key.includes(`${codexHomeDir}/hooks.json:`));
595
804
  assert.equal(managedHookStateKeys.length, 7);
596
- assert.match(config, /^codex_hooks = true$/m);
597
- assert.doesNotMatch(config, /^hooks = true$/m);
805
+ assert.match(config, /^hooks = true$/m);
806
+ assert.doesNotMatch(config, /^codex_hooks = true$/m);
598
807
  assert.match(config, /^goals = true$/m);
599
808
  assert.match(config, /^\[hooks\.state\.".*hooks\.json:pre_tool_use:0:0"\]$/m);
600
809
  assert.equal((config.match(/^\[hooks\.state\.".*hooks\.json:pre_tool_use:0:0"\]$/gm) ?? []).length, 1);
601
810
  assert.match(config, /^trusted_hash = "sha256:[a-f0-9]{64}"$/m);
602
- assert.doesNotMatch(config, /developer_instructions|notify-hook|mcp_servers/);
811
+ assert.doesNotMatch(config, /developer_instructions|notify-hook/g);
603
812
  assert.equal(existsSync(join(codexHomeDir, "skills", "ask", "SKILL.md")), false);
604
813
  assert.equal(existsSync(join(codexHomeDir, "agents", "planner.toml")), false);
605
814
  assert.equal(existsSync(join(codexHomeDir, "prompts", "executor.md")), false);
@@ -634,8 +843,10 @@ describe("omx setup install mode behavior", () => {
634
843
  assert.match(config, /Setup-owned prompt files and native-agent TOML defaults are intentionally omitted unless explicitly installed/);
635
844
  assert.doesNotMatch(config, /Native subagents live in \.codex\/agents/);
636
845
  assert.doesNotMatch(config, /Treat installed prompts as narrower execution surfaces/);
637
- assert.match(config, /^codex_hooks = true$/m);
638
- assert.doesNotMatch(config, /notify-hook|mcp_servers/);
846
+ assert.match(config, /^hooks = true$/m);
847
+ assert.doesNotMatch(config, /notify-hook/);
848
+ assert.doesNotMatch(config, /^\s*\[mcp_servers[.\]]/m);
849
+ assert.match(config, /mcp_servers\.omx_state]\nenabled = false/);
639
850
  const agentsMd = await readFile(join(codexHomeDir, "AGENTS.md"), "utf-8");
640
851
  assert.match(agentsMd, /oh-my-codex - Intelligent Multi-Agent Orchestration/);
641
852
  assert.match(agentsMd, /<!-- omx:generated:agents-md -->/);
@@ -695,7 +906,7 @@ describe("omx setup install mode behavior", () => {
695
906
  });
696
907
  const config = await readFile(configPath, "utf-8");
697
908
  assert.match(config, /^developer_instructions = "custom"$/m);
698
- assert.match(config, /^codex_hooks = true$/m);
909
+ assert.match(config, /^hooks = true$/m);
699
910
  assert.equal((config.match(/^developer_instructions\s*=/gm) ?? []).length, 1);
700
911
  });
701
912
  });
@@ -721,7 +932,7 @@ describe("omx setup install mode behavior", () => {
721
932
  assert.match(config, /You have oh-my-codex installed/);
722
933
  assert.doesNotMatch(config, /^developer_instructions = "custom"$/m);
723
934
  assert.equal((config.match(/^developer_instructions\s*=/gm) ?? []).length, 1);
724
- assert.match(config, /^codex_hooks = true$/m);
935
+ assert.match(config, /^hooks = true$/m);
725
936
  assert.match(config, /^\[hooks\.state\.".*hooks\.json:pre_tool_use:0:0"\]$/m);
726
937
  });
727
938
  });
@@ -730,6 +941,27 @@ describe("omx setup install mode behavior", () => {
730
941
  await rm(wd, { recursive: true, force: true });
731
942
  }
732
943
  });
944
+ it("uses legacy codex_hooks only when the installed Codex reports that hook feature", async () => {
945
+ const wd = await mkdtemp(join(tmpdir(), "omx-setup-install-mode-"));
946
+ try {
947
+ await withIsolatedUserHome(wd, async (codexHomeDir) => {
948
+ await withTempCwd(wd, async () => {
949
+ await setup({
950
+ scope: "user",
951
+ installMode: "plugin",
952
+ codexFeaturesProbe: () => "codex_hooks experimental true\n",
953
+ codexVersionProbe: () => "codex-cli 0.129.0",
954
+ });
955
+ const config = await readFile(join(codexHomeDir, "config.toml"), "utf-8");
956
+ assert.match(config, /^codex_hooks = true$/m);
957
+ assert.doesNotMatch(config, /^hooks = true$/m);
958
+ });
959
+ });
960
+ }
961
+ finally {
962
+ await rm(wd, { recursive: true, force: true });
963
+ }
964
+ });
733
965
  it("preserves existing user hooks while installing plugin-mode native hooks", async () => {
734
966
  const wd = await mkdtemp(join(tmpdir(), "omx-setup-install-mode-"));
735
967
  try {
@@ -743,7 +975,7 @@ describe("omx setup install mode behavior", () => {
743
975
  assert.match(hooks, /"UserPromptSubmit"/);
744
976
  assert.match(hooks, /codex-native-hook\.js/);
745
977
  const config = await readFile(join(codexHomeDir, "config.toml"), "utf-8");
746
- assert.match(config, /^codex_hooks = true$/m);
978
+ assert.match(config, /^hooks = true$/m);
747
979
  });
748
980
  });
749
981
  }
@@ -760,6 +992,7 @@ describe("omx setup install mode behavior", () => {
760
992
  assert.deepEqual(persisted, {
761
993
  scope: "project",
762
994
  installMode: "plugin",
995
+ mcpMode: "none",
763
996
  });
764
997
  await setup({ scope: "project" });
765
998
  assert.equal(existsSync(join(wd, ".codex", "skills", "ask", "SKILL.md")), false);
@@ -781,7 +1014,7 @@ describe("omx setup install mode behavior", () => {
781
1014
  await writeFile(join(wd, ".omx", "setup-scope.json"), JSON.stringify({ scope: "project", installMode: "plugin" }));
782
1015
  await setup({ scope: "project", installMode: "legacy" });
783
1016
  const persisted = JSON.parse(await readFile(join(wd, ".omx", "setup-scope.json"), "utf-8"));
784
- assert.deepEqual(persisted, { scope: "project" });
1017
+ assert.deepEqual(persisted, { scope: "project", mcpMode: "none" });
785
1018
  assert.equal(existsSync(join(wd, ".codex", "skills", "ask", "SKILL.md")), true);
786
1019
  assert.equal(existsSync(join(wd, ".codex", "agents", "planner.toml")), true);
787
1020
  assert.equal(existsSync(join(wd, ".codex", "prompts", "executor.md")), true);
@@ -803,6 +1036,7 @@ describe("omx setup install mode behavior", () => {
803
1036
  assert.deepEqual(persisted, {
804
1037
  scope: "user",
805
1038
  installMode: "legacy",
1039
+ mcpMode: "none",
806
1040
  });
807
1041
  assert.equal(existsSync(join(codexHomeDir, "skills", "ask", "SKILL.md")), true);
808
1042
  assert.equal(existsSync(join(codexHomeDir, "agents", "planner.toml")), true);
@@ -814,6 +1048,44 @@ describe("omx setup install mode behavior", () => {
814
1048
  await rm(wd, { recursive: true, force: true });
815
1049
  }
816
1050
  });
1051
+ it("dedupes plugin-mode hook trust state when switching user setup back to legacy", async () => {
1052
+ const wd = await mkdtemp(join(tmpdir(), "omx-setup-install-mode-"));
1053
+ try {
1054
+ await withIsolatedUserHome(wd, async (codexHomeDir) => {
1055
+ await withTempCwd(wd, async () => {
1056
+ const configPath = join(codexHomeDir, "config.toml");
1057
+ await setup({ scope: "user", installMode: "plugin", force: true });
1058
+ const pluginConfig = await readFile(configPath, "utf-8");
1059
+ const staleUnfencedPluginConfig = pluginConfig
1060
+ .split(/\r?\n/)
1061
+ .filter((line) => line.trim() !== "# OMX-owned Codex hook trust state" &&
1062
+ line.trim() !==
1063
+ "# Trusts only setup-managed codex-native-hook.js wrappers." &&
1064
+ line.trim() !== "# End OMX-owned Codex hook trust state")
1065
+ .join("\n");
1066
+ await writeFile(configPath, staleUnfencedPluginConfig);
1067
+ assert.doesNotThrow(() => parseToml(staleUnfencedPluginConfig));
1068
+ await setup({ scope: "user", installMode: "legacy", force: true });
1069
+ const legacyConfig = await readFile(configPath, "utf-8");
1070
+ assert.doesNotThrow(() => parseToml(legacyConfig));
1071
+ assert.equal(legacyConfig
1072
+ .split(/\r?\n/)
1073
+ .filter((line) => line.trim() ===
1074
+ `[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");
1075
+ assert.match(legacyConfig, /# OMX-owned Codex hook trust state[\s\S]*# End OMX-owned Codex hook trust state/);
1076
+ const persisted = JSON.parse(await readFile(join(wd, ".omx", "setup-scope.json"), "utf-8"));
1077
+ assert.deepEqual(persisted, {
1078
+ scope: "user",
1079
+ installMode: "legacy",
1080
+ mcpMode: "none",
1081
+ });
1082
+ });
1083
+ });
1084
+ }
1085
+ finally {
1086
+ await rm(wd, { recursive: true, force: true });
1087
+ }
1088
+ });
817
1089
  it("installs project-scoped native hooks when plugin mode is explicitly requested", async () => {
818
1090
  const wd = await mkdtemp(join(tmpdir(), "omx-setup-install-mode-"));
819
1091
  try {
@@ -902,7 +1174,7 @@ describe("omx setup install mode behavior", () => {
902
1174
  const hooks = await readFile(hooksPath, "utf-8");
903
1175
  assert.match(hooks, /codex-native-hook\.js/);
904
1176
  const config = await readFile(configPath, "utf-8");
905
- assert.match(config, /^codex_hooks = true$/m);
1177
+ assert.match(config, /^hooks = true$/m);
906
1178
  assert.doesNotMatch(config, /^\s*(?:notify|developer_instructions)\s*=|^\s*\[mcp_servers[.\]]/m);
907
1179
  });
908
1180
  });