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
@@ -11,15 +11,19 @@
11
11
  */
12
12
  import { readFile, writeFile } from "fs/promises";
13
13
  import { existsSync } from "fs";
14
- import { join } from "path";
14
+ import { join, resolve } from "path";
15
15
  import TOML from "@iarna/toml";
16
16
  import { AGENT_DEFINITIONS } from "../agents/definitions.js";
17
17
  import { DEFAULT_FRONTIER_MODEL } from "./models.js";
18
- import { getOmxFirstPartySetupMcpServers } from "./omx-first-party-mcp.js";
18
+ import { DEFAULT_CODEX_HOOK_FEATURE_FLAG, CODEX_HOOK_FEATURE_FLAGS, formatCodexHookFeatureFlagLine, normalizeCodexHookFeatureFlag, } from "./codex-feature-flags.js";
19
+ import { OMX_FIRST_PARTY_MCP_SERVER_NAMES, getOmxFirstPartySetupMcpServers, } from "./omx-first-party-mcp.js";
19
20
  import { buildManagedCodexHookTrustToml } from "./codex-hooks.js";
20
21
  function escapeTomlString(value) {
21
22
  return value.replace(/\\/g, "\\\\").replace(/"/g, '\\"');
22
23
  }
24
+ function isRecord(value) {
25
+ return typeof value === "object" && value !== null && !Array.isArray(value);
26
+ }
23
27
  // ---------------------------------------------------------------------------
24
28
  // Top-level OMX keys (must live before any [table] header)
25
29
  // ---------------------------------------------------------------------------
@@ -183,13 +187,68 @@ function parseRootKeyValues(config) {
183
187
  }
184
188
  return values;
185
189
  }
186
- function getOmxTopLevelLines(pkgRoot, existingConfig = "", modelOverride) {
187
- const notifyHookPath = join(pkgRoot, "dist", "scripts", "notify-hook.js");
188
- const escapedPath = escapeTomlString(notifyHookPath);
190
+ function getDefaultNotifyCommand(pkgRoot) {
191
+ return ["node", join(pkgRoot, "dist", "scripts", "notify-hook.js")];
192
+ }
193
+ export function formatTomlStringArray(values) {
194
+ return `[${values.map((value) => `"${escapeTomlString(value)}"`).join(", ")}]`;
195
+ }
196
+ export function getRootTomlArray(config, key) {
197
+ const raw = parseRootKeyValues(config).get(key);
198
+ if (!raw)
199
+ return null;
200
+ try {
201
+ const parsed = TOML.parse(`${key} = ${raw}`);
202
+ const value = parsed[key];
203
+ if (!Array.isArray(value) ||
204
+ !value.every((item) => typeof item === "string")) {
205
+ return null;
206
+ }
207
+ return value;
208
+ }
209
+ catch {
210
+ return null;
211
+ }
212
+ }
213
+ function resolveNotifyEntrypoint(command) {
214
+ if (!/(?:^|[\\/])node(?:\.exe)?$/i.test(command[0] ?? "")) {
215
+ return command[0];
216
+ }
217
+ return command.slice(1).find((arg) => !arg.startsWith("-"));
218
+ }
219
+ export function isOmxManagedNotifyCommand(command, pkgRoot) {
220
+ if (!command)
221
+ return false;
222
+ const entrypoint = resolveNotifyEntrypoint(command);
223
+ if (!entrypoint)
224
+ return false;
225
+ if (!/(?:^|[\\/])notify-(?:hook|dispatcher)\.js$/.test(entrypoint)) {
226
+ return false;
227
+ }
228
+ const managedScripts = pkgRoot
229
+ ? new Set([
230
+ resolve(pkgRoot, "dist", "scripts", "notify-hook.js"),
231
+ resolve(pkgRoot, "dist", "scripts", "notify-dispatcher.js"),
232
+ ])
233
+ : new Set();
234
+ if (pkgRoot && managedScripts.has(resolve(entrypoint)))
235
+ return true;
236
+ return /(?:^|[\\/])oh-my-codex(?:[\\/]|$)/.test(entrypoint);
237
+ }
238
+ export function sanitizePreviousNotifyCommand(command, pkgRoot) {
239
+ if (!command || command.length === 0)
240
+ return null;
241
+ if (isOmxManagedNotifyCommand(command, pkgRoot))
242
+ return null;
243
+ return [...command];
244
+ }
245
+ function getOmxTopLevelLines(pkgRoot, existingConfig = "", modelOverride, notifyCommand = getDefaultNotifyCommand(pkgRoot)) {
189
246
  const rootValues = parseRootKeyValues(existingConfig);
190
247
  const lines = [
191
248
  "# oh-my-codex top-level settings (must be before any [table])",
192
- `notify = ["node", "${escapedPath}"]`,
249
+ ...(notifyCommand === false
250
+ ? []
251
+ : [`notify = ${formatTomlStringArray(notifyCommand)}`]),
193
252
  'model_reasoning_effort = "medium"',
194
253
  `developer_instructions = "${escapeTomlString(OMX_DEVELOPER_INSTRUCTIONS)}"`,
195
254
  ];
@@ -280,8 +339,18 @@ function stripRootLevelKeys(config, keys) {
280
339
  }
281
340
  return result.join("\n");
282
341
  }
283
- function stripOrphanedManagedNotify(config) {
342
+ function escapeRegExp(value) {
343
+ return value.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
344
+ }
345
+ function stripOrphanedManagedNotify(config, pkgRoot) {
346
+ const rootNotify = getRootTomlArray(config, "notify");
347
+ if (rootNotify &&
348
+ !isOmxManagedNotifyCommand(rootNotify, pkgRoot)) {
349
+ return config;
350
+ }
351
+ const managedHookPath = escapeRegExp(resolve(pkgRoot, "dist", "scripts", "notify-hook.js"));
284
352
  return config
353
+ .replace(new RegExp(`^\\s*notify\\s*=\\s*\\["node",\\s*"${managedHookPath}"\\]\\s*$(\\n)?`, "gm"), "")
285
354
  .replace(/^\s*notify\s*=\s*\["node",\s*".*notify-hook\.js"\]\s*$(\n)?/gm, "")
286
355
  .replace(/\n?\s*"node",\s*\n\s*".*notify-hook\.js",\s*\n\s*\]\s*(?=\n|$)/g, "");
287
356
  }
@@ -295,16 +364,56 @@ export function stripOmxTopLevelKeys(config) {
295
364
  // ---------------------------------------------------------------------------
296
365
  // [features] upsert
297
366
  // ---------------------------------------------------------------------------
298
- function upsertFeatureFlags(config) {
367
+ function isFeatureFlagLine(line, featureFlag) {
368
+ return new RegExp(`^\\s*${featureFlag}\\s*=`).test(line);
369
+ }
370
+ function isAnyCodexHookFeatureFlagLine(line) {
371
+ return CODEX_HOOK_FEATURE_FLAGS.some((flag) => isFeatureFlagLine(line, flag));
372
+ }
373
+ function upsertCodexHookFeatureFlagInSection(lines, featuresStart, sectionEnd, codexHookFeatureFlag) {
374
+ const featureFlag = normalizeCodexHookFeatureFlag(codexHookFeatureFlag);
375
+ let featureFlagIdx = -1;
376
+ let fallbackAliasIdx = -1;
377
+ for (let i = featuresStart + 1; i < sectionEnd; i++) {
378
+ if (isFeatureFlagLine(lines[i], featureFlag)) {
379
+ featureFlagIdx = i;
380
+ }
381
+ else if (isAnyCodexHookFeatureFlagLine(lines[i]) && fallbackAliasIdx < 0) {
382
+ fallbackAliasIdx = i;
383
+ }
384
+ }
385
+ if (featureFlagIdx < 0 && fallbackAliasIdx >= 0) {
386
+ featureFlagIdx = fallbackAliasIdx;
387
+ }
388
+ if (featureFlagIdx >= 0) {
389
+ lines[featureFlagIdx] = formatCodexHookFeatureFlagLine(featureFlag);
390
+ }
391
+ else {
392
+ lines.splice(sectionEnd, 0, formatCodexHookFeatureFlagLine(featureFlag));
393
+ featureFlagIdx = sectionEnd;
394
+ sectionEnd += 1;
395
+ }
396
+ for (let i = sectionEnd - 1; i > featuresStart; i--) {
397
+ if (i !== featureFlagIdx && isAnyCodexHookFeatureFlagLine(lines[i])) {
398
+ lines.splice(i, 1);
399
+ sectionEnd -= 1;
400
+ if (featureFlagIdx > i)
401
+ featureFlagIdx -= 1;
402
+ }
403
+ }
404
+ return { sectionEnd, featureFlagIndex: featureFlagIdx };
405
+ }
406
+ function upsertFeatureFlags(config, codexHookFeatureFlag = DEFAULT_CODEX_HOOK_FEATURE_FLAG) {
299
407
  const lines = config.split(/\r?\n/);
300
408
  const featuresStart = lines.findIndex((line) => /^\s*\[features\]\s*$/.test(line));
409
+ const hookFeatureFlagLine = formatCodexHookFeatureFlagLine(codexHookFeatureFlag);
301
410
  if (featuresStart < 0) {
302
411
  const base = config.trimEnd();
303
412
  const featureBlock = [
304
413
  "[features]",
305
414
  "multi_agent = true",
306
415
  "child_agents_md = true",
307
- "codex_hooks = true",
416
+ hookFeatureFlagLine,
308
417
  "goals = true",
309
418
  "",
310
419
  ].join("\n");
@@ -330,9 +439,6 @@ function upsertFeatureFlags(config) {
330
439
  }
331
440
  let multiAgentIdx = -1;
332
441
  let childAgentsIdx = -1;
333
- let hooksIdx = -1;
334
- let legacyCodexHooksIdx = -1;
335
- let goalsIdx = -1;
336
442
  for (let i = featuresStart + 1; i < sectionEnd; i++) {
337
443
  if (/^\s*multi_agent\s*=/.test(lines[i])) {
338
444
  multiAgentIdx = i;
@@ -340,15 +446,6 @@ function upsertFeatureFlags(config) {
340
446
  else if (/^\s*child_agents_md\s*=/.test(lines[i])) {
341
447
  childAgentsIdx = i;
342
448
  }
343
- else if (/^\s*hooks\s*=/.test(lines[i])) {
344
- hooksIdx = i;
345
- }
346
- else if (/^\s*codex_hooks\s*=/.test(lines[i])) {
347
- legacyCodexHooksIdx = i;
348
- }
349
- else if (/^\s*goals\s*=/.test(lines[i])) {
350
- goalsIdx = i;
351
- }
352
449
  }
353
450
  if (multiAgentIdx >= 0) {
354
451
  lines[multiAgentIdx] = "multi_agent = true";
@@ -364,22 +461,13 @@ function upsertFeatureFlags(config) {
364
461
  lines.splice(sectionEnd, 0, "child_agents_md = true");
365
462
  sectionEnd += 1;
366
463
  }
367
- if (hooksIdx >= 0) {
368
- lines[hooksIdx] = "codex_hooks = true";
369
- if (legacyCodexHooksIdx >= 0) {
370
- lines.splice(legacyCodexHooksIdx, 1);
371
- sectionEnd -= 1;
372
- if (goalsIdx > legacyCodexHooksIdx)
373
- goalsIdx -= 1;
464
+ ({ sectionEnd } = upsertCodexHookFeatureFlagInSection(lines, featuresStart, sectionEnd, codexHookFeatureFlag));
465
+ let goalsIdx = -1;
466
+ for (let i = featuresStart + 1; i < sectionEnd; i++) {
467
+ if (/^\s*goals\s*=/.test(lines[i])) {
468
+ goalsIdx = i;
374
469
  }
375
470
  }
376
- else if (legacyCodexHooksIdx >= 0) {
377
- lines[legacyCodexHooksIdx] = "codex_hooks = true";
378
- }
379
- else {
380
- lines.splice(sectionEnd, 0, "codex_hooks = true");
381
- sectionEnd += 1;
382
- }
383
471
  if (goalsIdx >= 0) {
384
472
  lines[goalsIdx] = "goals = true";
385
473
  }
@@ -391,12 +479,58 @@ function upsertFeatureFlags(config) {
391
479
  const OMX_HOOK_TRUST_START_MARKER = "# OMX-owned Codex hook trust state";
392
480
  const OMX_HOOK_TRUST_END_MARKER = "# End OMX-owned Codex hook trust state";
393
481
  export function stripManagedCodexHookTrustState(config) {
394
- const blockPattern = new RegExp(`\n?${OMX_HOOK_TRUST_START_MARKER.replace(/[.*+?^${}()|[\]\\]/g, "\\$&")}\n[\\s\\S]*?${OMX_HOOK_TRUST_END_MARKER.replace(/[.*+?^${}()|[\]\\]/g, "\\$&")}\n?`, "g");
395
- return config.replace(blockPattern, "\n").replace(/\n{3,}/g, "\n\n").trimEnd();
482
+ const lines = config.split(/\r?\n/);
483
+ const kept = [];
484
+ for (let i = 0; i < lines.length;) {
485
+ const trimmed = lines[i].trim();
486
+ if (trimmed !== OMX_HOOK_TRUST_START_MARKER) {
487
+ kept.push(lines[i]);
488
+ i += 1;
489
+ continue;
490
+ }
491
+ const nextEndIdx = lines.findIndex((line, index) => index > i && line.trim() === OMX_HOOK_TRUST_END_MARKER);
492
+ const nextStartIdx = lines.findIndex((line, index) => index > i && line.trim() === OMX_HOOK_TRUST_START_MARKER);
493
+ if (nextEndIdx === -1 || (nextStartIdx !== -1 && nextStartIdx < nextEndIdx)) {
494
+ kept.push(lines[i]);
495
+ i += 1;
496
+ continue;
497
+ }
498
+ i = nextEndIdx + 1;
499
+ }
500
+ return kept.join("\n").replace(/\n{3,}/g, "\n\n").trimEnd();
501
+ }
502
+ function managedCodexHookTrustStateHeaders(hookTrustToml) {
503
+ const headers = new Set();
504
+ for (const line of hookTrustToml.split(/\r?\n/)) {
505
+ const trimmed = line.trim();
506
+ if (/^\[hooks\.state\./.test(trimmed)) {
507
+ headers.add(trimmed);
508
+ }
509
+ }
510
+ return headers;
511
+ }
512
+ function stripUnfencedManagedCodexHookTrustState(config, hookTrustToml) {
513
+ const managedHeaders = managedCodexHookTrustStateHeaders(hookTrustToml);
514
+ if (managedHeaders.size === 0)
515
+ return config;
516
+ const lines = config.split(/\r?\n/);
517
+ const kept = [];
518
+ for (let i = 0; i < lines.length;) {
519
+ if (!managedHeaders.has(lines[i].trim())) {
520
+ kept.push(lines[i]);
521
+ i += 1;
522
+ continue;
523
+ }
524
+ i += 1;
525
+ while (i < lines.length && !TOML_TABLE_HEADER_PATTERN.test(lines[i])) {
526
+ i += 1;
527
+ }
528
+ }
529
+ return kept.join("\n").replace(/\n{3,}/g, "\n\n").trimEnd();
396
530
  }
397
531
  export function upsertManagedCodexHookTrustState(config, pkgRoot, codexHooksFile) {
398
- const stripped = stripManagedCodexHookTrustState(config);
399
532
  const hookTrustToml = buildManagedCodexHookTrustToml(codexHooksFile, pkgRoot);
533
+ const stripped = stripUnfencedManagedCodexHookTrustState(stripManagedCodexHookTrustState(config), hookTrustToml);
400
534
  if (!hookTrustToml)
401
535
  return `${stripped}\n`;
402
536
  return [
@@ -409,14 +543,15 @@ export function upsertManagedCodexHookTrustState(config, pkgRoot, codexHooksFile
409
543
  "",
410
544
  ].filter((line, index) => index !== 0 || line.length > 0).join("\n");
411
545
  }
412
- export function upsertPluginModeRuntimeFeatureFlags(config) {
546
+ export function upsertPluginModeRuntimeFeatureFlags(config, codexHookFeatureFlag = DEFAULT_CODEX_HOOK_FEATURE_FLAG) {
413
547
  const lines = config.split(/\r?\n/);
414
548
  const featuresStart = lines.findIndex((line) => /^\s*\[features\]\s*$/.test(line));
549
+ const hookFeatureFlagLine = formatCodexHookFeatureFlagLine(codexHookFeatureFlag);
415
550
  if (featuresStart < 0) {
416
551
  const base = config.trimEnd();
417
552
  const featureBlock = [
418
553
  "[features]",
419
- "codex_hooks = true",
554
+ hookFeatureFlagLine,
420
555
  "goals = true",
421
556
  "",
422
557
  ].join("\n");
@@ -440,36 +575,13 @@ export function upsertPluginModeRuntimeFeatureFlags(config) {
440
575
  sectionEnd -= 1;
441
576
  }
442
577
  }
443
- let hooksIdx = -1;
444
- let legacyCodexHooksIdx = -1;
578
+ ({ sectionEnd } = upsertCodexHookFeatureFlagInSection(lines, featuresStart, sectionEnd, codexHookFeatureFlag));
445
579
  let goalsIdx = -1;
446
580
  for (let i = featuresStart + 1; i < sectionEnd; i++) {
447
- if (/^\s*hooks\s*=/.test(lines[i])) {
448
- hooksIdx = i;
449
- }
450
- else if (/^\s*codex_hooks\s*=/.test(lines[i])) {
451
- legacyCodexHooksIdx = i;
452
- }
453
- else if (/^\s*goals\s*=/.test(lines[i])) {
581
+ if (/^\s*goals\s*=/.test(lines[i])) {
454
582
  goalsIdx = i;
455
583
  }
456
584
  }
457
- if (hooksIdx >= 0) {
458
- lines[hooksIdx] = "codex_hooks = true";
459
- if (legacyCodexHooksIdx >= 0) {
460
- lines.splice(legacyCodexHooksIdx, 1);
461
- sectionEnd--;
462
- if (goalsIdx > legacyCodexHooksIdx)
463
- goalsIdx--;
464
- }
465
- }
466
- else if (legacyCodexHooksIdx >= 0) {
467
- lines[legacyCodexHooksIdx] = "codex_hooks = true";
468
- }
469
- else {
470
- lines.splice(sectionEnd, 0, "codex_hooks = true");
471
- sectionEnd++;
472
- }
473
585
  if (goalsIdx >= 0) {
474
586
  lines[goalsIdx] = "goals = true";
475
587
  }
@@ -681,6 +793,29 @@ export function stripOmxFeatureFlags(config) {
681
793
  }
682
794
  return filtered.join("\n");
683
795
  }
796
+ /**
797
+ * Preserve native Codex hook enablement without re-adding other OMX feature
798
+ * flags. Used by uninstall when user-owned hooks remain in hooks.json.
799
+ */
800
+ export function upsertCodexHooksFeatureFlag(config, codexHookFeatureFlag = DEFAULT_CODEX_HOOK_FEATURE_FLAG) {
801
+ const lines = config.split(/\r?\n/);
802
+ const featuresStart = lines.findIndex((line) => /^\s*\[features\]\s*$/.test(line));
803
+ const hookFeatureFlagLine = formatCodexHookFeatureFlagLine(codexHookFeatureFlag);
804
+ if (featuresStart < 0) {
805
+ const base = config.trimEnd();
806
+ const featureBlock = ["[features]", hookFeatureFlagLine, ""].join("\n");
807
+ return base.length === 0 ? featureBlock : `${base}\n${featureBlock}`;
808
+ }
809
+ let sectionEnd = lines.length;
810
+ for (let i = featuresStart + 1; i < lines.length; i++) {
811
+ if (/^\s*\[\[?[^\]]+\]?\]\s*$/.test(lines[i])) {
812
+ sectionEnd = i;
813
+ break;
814
+ }
815
+ }
816
+ upsertCodexHookFeatureFlagInSection(lines, featuresStart, sectionEnd, codexHookFeatureFlag);
817
+ return lines.join("\n");
818
+ }
684
819
  export function stripOmxEnvSettings(config) {
685
820
  let lines = config.split(/\r?\n/);
686
821
  lines = stripTomlTableKey(lines, /^\s*\[env\]\s*$/, OMX_EXPLORE_CMD_ENV);
@@ -694,6 +829,13 @@ export function stripOmxEnvSettings(config) {
694
829
  * Check whether a TOML table name belongs to a legacy OMX-managed agent entry.
695
830
  * Handles both `agents.name` and `agents."name"` forms.
696
831
  */
832
+ function isOmxFirstPartyMcpSection(tableName) {
833
+ const match = tableName.match(/^mcp_servers\.(?:"([^"]+)"|([A-Za-z0-9_-]+))$/);
834
+ const name = match?.[1] ?? match?.[2];
835
+ return Boolean(name &&
836
+ (OMX_FIRST_PARTY_MCP_SERVER_NAMES.includes(name) ||
837
+ name === "omx_team_run"));
838
+ }
697
839
  function isLegacyOmxAgentSection(tableName) {
698
840
  const m = tableName.match(/^agents\.(?:"([^"]+)"|(\w[\w-]*))$/);
699
841
  if (!m)
@@ -706,7 +848,8 @@ function isLegacyOmxAgentSection(tableName) {
706
848
  * This covers legacy configs that were written before markers were added,
707
849
  * or configs where the marker was accidentally removed.
708
850
  *
709
- * Targets: [mcp_servers.omx_*], legacy [agents.<name>] entries, [tui]
851
+ * Targets: exact first-party [mcp_servers.<name>] entries, retired
852
+ * [mcp_servers.omx_team_run], and legacy [agents.<name>] entries.
710
853
  */
711
854
  function stripOrphanedOmxSections(config) {
712
855
  const lines = config.split(/\r?\n/);
@@ -720,7 +863,7 @@ function stripOrphanedOmxSections(config) {
720
863
  // Note: [tui] is NOT stripped here because it could be user-owned.
721
864
  // The marker-based stripExistingOmxBlocks already handles [tui]
722
865
  // when it lives inside the OMX marker block.
723
- const isOmxSection = /^mcp_servers\.omx_/.test(tableName) ||
866
+ const isOmxSection = isOmxFirstPartyMcpSection(tableName) ||
724
867
  isLegacyOmxAgentSection(tableName);
725
868
  if (isOmxSection) {
726
869
  // Remove preceding OMX comment lines and blank lines
@@ -960,6 +1103,72 @@ export function stripExistingSharedMcpRegistryBlock(config) {
960
1103
  }
961
1104
  return { cleaned, removed };
962
1105
  }
1106
+ function getExistingSharedMcpRegistryBlocks(config) {
1107
+ const blocks = [];
1108
+ let cursor = 0;
1109
+ while (cursor < config.length) {
1110
+ const markerIdx = config.indexOf(SHARED_MCP_REGISTRY_MARKER, cursor);
1111
+ if (markerIdx < 0)
1112
+ break;
1113
+ let blockStart = config.lastIndexOf("\n", markerIdx);
1114
+ blockStart = blockStart >= 0 ? blockStart + 1 : 0;
1115
+ const previousLineEnd = blockStart - 1;
1116
+ if (previousLineEnd >= 0) {
1117
+ const previousLineStart = config.lastIndexOf("\n", previousLineEnd - 1);
1118
+ const previousLine = config.slice(previousLineStart + 1, previousLineEnd);
1119
+ if (/^# =+$/.test(previousLine.trim())) {
1120
+ blockStart = previousLineStart >= 0 ? previousLineStart + 1 : 0;
1121
+ }
1122
+ }
1123
+ let blockEnd = config.length;
1124
+ const endIdx = config.indexOf(SHARED_MCP_REGISTRY_END_MARKER, markerIdx);
1125
+ if (endIdx >= 0) {
1126
+ const endLineBreak = config.indexOf("\n", endIdx);
1127
+ blockEnd = endLineBreak >= 0 ? endLineBreak + 1 : config.length;
1128
+ }
1129
+ blocks.push(config.slice(blockStart, blockEnd));
1130
+ cursor = blockEnd;
1131
+ }
1132
+ return blocks;
1133
+ }
1134
+ export function extractSharedMcpRegistryServersFromConfig(config) {
1135
+ const servers = [];
1136
+ let sourcePath;
1137
+ for (const block of getExistingSharedMcpRegistryBlocks(config)) {
1138
+ sourcePath ??= block.match(/^# Source:\s*(.+?)\s*$/m)?.[1];
1139
+ let parsed;
1140
+ try {
1141
+ parsed = TOML.parse(block);
1142
+ }
1143
+ catch {
1144
+ continue;
1145
+ }
1146
+ if (!isRecord(parsed) || !isRecord(parsed.mcp_servers))
1147
+ continue;
1148
+ for (const [name, value] of Object.entries(parsed.mcp_servers)) {
1149
+ if (!isRecord(value) || typeof value.command !== "string")
1150
+ continue;
1151
+ const args = Array.isArray(value.args)
1152
+ ? value.args.filter((arg) => typeof arg === "string")
1153
+ : [];
1154
+ const enabled = typeof value.enabled === "boolean" ? value.enabled : true;
1155
+ const timeoutCandidate = typeof value.startup_timeout_sec === "number"
1156
+ ? value.startup_timeout_sec
1157
+ : value.startupTimeoutSec;
1158
+ const startupTimeoutSec = typeof timeoutCandidate === "number" && Number.isFinite(timeoutCandidate)
1159
+ ? timeoutCandidate
1160
+ : undefined;
1161
+ servers.push({
1162
+ name,
1163
+ command: value.command,
1164
+ args,
1165
+ enabled,
1166
+ ...(startupTimeoutSec !== undefined ? { startupTimeoutSec } : {}),
1167
+ });
1168
+ }
1169
+ }
1170
+ return { servers, sourcePath };
1171
+ }
963
1172
  function toMcpServerTableKey(name) {
964
1173
  if (/^[A-Za-z0-9_-]+$/.test(name)) {
965
1174
  return `mcp_servers.${name}`;
@@ -1079,11 +1288,25 @@ function getSharedMcpRegistryBlock(servers, sourcePath, existingConfig) {
1079
1288
  lines.push(SHARED_MCP_REGISTRY_END_MARKER);
1080
1289
  return lines.join("\n");
1081
1290
  }
1291
+ export function mergeSharedMcpRegistryBlock(config, servers, sourcePath) {
1292
+ const stripped = stripExistingSharedMcpRegistryBlock(config);
1293
+ const existing = stripped.cleaned;
1294
+ const sharedRegistryBlock = getSharedMcpRegistryBlock(servers, sourcePath, existing);
1295
+ const body = existing.trimEnd();
1296
+ const merged = sharedRegistryBlock
1297
+ ? body
1298
+ ? `${body}\n\n${sharedRegistryBlock}\n`
1299
+ : `${sharedRegistryBlock}\n`
1300
+ : body
1301
+ ? `${body}\n`
1302
+ : "";
1303
+ return addDefaultLauncherMcpStartupTimeouts(merged);
1304
+ }
1082
1305
  /**
1083
1306
  * OMX table-section block (MCP servers, TUI).
1084
1307
  * Contains ONLY [table] sections — no bare keys.
1085
1308
  */
1086
- function getOmxTablesBlock(pkgRoot, includeTui = true, statusLinePreset = DEFAULT_STATUS_LINE_PRESET, codexHooksFile) {
1309
+ function getOmxTablesBlock(pkgRoot, includeTui = true, statusLinePreset = DEFAULT_STATUS_LINE_PRESET, codexHooksFile, includeFirstPartyMcp = false) {
1087
1310
  const lines = [
1088
1311
  "",
1089
1312
  "# ============================================================",
@@ -1091,17 +1314,19 @@ function getOmxTablesBlock(pkgRoot, includeTui = true, statusLinePreset = DEFAUL
1091
1314
  "# Managed by omx setup - manual edits preserved on next setup",
1092
1315
  "# ============================================================",
1093
1316
  ];
1094
- for (const server of getOmxFirstPartySetupMcpServers(pkgRoot)) {
1095
- lines.push("");
1096
- lines.push(server.title);
1097
- lines.push(`[mcp_servers.${server.name}]`);
1098
- lines.push(`command = "${escapeTomlString(server.command)}"`);
1099
- lines.push(`args = [${server.args
1100
- .map((arg) => `"${escapeTomlString(arg)}"`)
1101
- .join(", ")}]`);
1102
- lines.push(`enabled = ${server.enabled ? "true" : "false"}`);
1103
- if (typeof server.startupTimeoutSec === "number") {
1104
- lines.push(`startup_timeout_sec = ${server.startupTimeoutSec}`);
1317
+ if (includeFirstPartyMcp) {
1318
+ for (const server of getOmxFirstPartySetupMcpServers(pkgRoot)) {
1319
+ lines.push("");
1320
+ lines.push(server.title);
1321
+ lines.push(`[mcp_servers.${server.name}]`);
1322
+ lines.push(`command = "${escapeTomlString(server.command)}"`);
1323
+ lines.push(`args = [${server.args
1324
+ .map((arg) => `"${escapeTomlString(arg)}"`)
1325
+ .join(", ")}]`);
1326
+ lines.push(`enabled = ${server.enabled ? "true" : "false"}`);
1327
+ if (typeof server.startupTimeoutSec === "number") {
1328
+ lines.push(`startup_timeout_sec = ${server.startupTimeoutSec}`);
1329
+ }
1105
1330
  }
1106
1331
  }
1107
1332
  const hookTrustToml = buildManagedCodexHookTrustToml(codexHooksFile, pkgRoot);
@@ -1157,13 +1382,22 @@ export function buildMergedConfig(existingConfig, pkgRoot, options = {}) {
1157
1382
  const stripped = stripExistingSharedMcpRegistryBlock(existing);
1158
1383
  existing = stripped.cleaned;
1159
1384
  }
1385
+ const userNotifyToPreserve = options.notifyCommand === false &&
1386
+ !isOmxManagedNotifyCommand(getRootTomlArray(existing, "notify"), pkgRoot)
1387
+ ? getRootTomlArray(existing, "notify")
1388
+ : null;
1160
1389
  existing = stripOmxTopLevelKeys(existing);
1161
- existing = stripOrphanedManagedNotify(existing);
1390
+ if (userNotifyToPreserve) {
1391
+ existing = `${`notify = ${formatTomlStringArray(userNotifyToPreserve)}`}\n${existing.trimStart()}`;
1392
+ }
1393
+ existing = stripOrphanedManagedNotify(existing, pkgRoot);
1394
+ existing = stripManagedCodexHookTrustState(existing);
1395
+ existing = stripUnfencedManagedCodexHookTrustState(existing, buildManagedCodexHookTrustToml(options.codexHooksFile, pkgRoot));
1162
1396
  if (options.modelOverride) {
1163
1397
  existing = stripRootLevelKeys(existing, ["model"]);
1164
1398
  }
1165
1399
  existing = stripOrphanedOmxSections(existing);
1166
- existing = upsertFeatureFlags(existing);
1400
+ existing = upsertFeatureFlags(existing, options.codexHookFeatureFlag);
1167
1401
  existing = upsertEnvSettings(existing);
1168
1402
  existing = upsertAgentsSettings(existing);
1169
1403
  const tuiUpsert = includeTui
@@ -1172,8 +1406,10 @@ export function buildMergedConfig(existingConfig, pkgRoot, options = {}) {
1172
1406
  })
1173
1407
  : { cleaned: existing, hadExistingTui: false };
1174
1408
  existing = tuiUpsert.cleaned;
1175
- const topLines = getOmxTopLevelLines(pkgRoot, existing, options.modelOverride);
1176
- const tablesBlock = getOmxTablesBlock(pkgRoot, includeTui && !tuiUpsert.hadExistingTui, statusLinePreset, options.codexHooksFile);
1409
+ const topLines = getOmxTopLevelLines(pkgRoot, existing, options.modelOverride, options.notifyCommand === undefined
1410
+ ? getDefaultNotifyCommand(pkgRoot)
1411
+ : options.notifyCommand);
1412
+ const tablesBlock = getOmxTablesBlock(pkgRoot, includeTui && !tuiUpsert.hadExistingTui, statusLinePreset, options.codexHooksFile, options.includeFirstPartyMcp === true);
1177
1413
  const sharedRegistryBlock = getSharedMcpRegistryBlock(options.sharedMcpServers ?? [], options.sharedMcpRegistrySource, existing);
1178
1414
  let body = existing.trimEnd();
1179
1415
  if (sharedRegistryBlock) {