oh-my-codex 0.20.1 → 0.20.2

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 (654) hide show
  1. package/Cargo.lock +6 -6
  2. package/Cargo.toml +1 -1
  3. package/README.md +10 -1
  4. package/dist/adapt/__tests__/hermes.test.js.map +1 -1
  5. package/dist/adapt/contracts.d.ts.map +1 -1
  6. package/dist/agents/native-config.js.map +1 -1
  7. package/dist/auth/__tests__/hotswap.test.d.ts +2 -0
  8. package/dist/auth/__tests__/hotswap.test.d.ts.map +1 -0
  9. package/dist/auth/__tests__/hotswap.test.js +183 -0
  10. package/dist/auth/__tests__/hotswap.test.js.map +1 -0
  11. package/dist/auth/hotswap.d.ts.map +1 -1
  12. package/dist/auth/hotswap.js +17 -4
  13. package/dist/auth/hotswap.js.map +1 -1
  14. package/dist/auth/paths.js.map +1 -1
  15. package/dist/auth/storage.js.map +1 -1
  16. package/dist/autopilot/__tests__/ralplan-gate.test.js +4 -4
  17. package/dist/autopilot/__tests__/ralplan-gate.test.js.map +1 -1
  18. package/dist/autopilot/completion-gate.js.map +1 -1
  19. package/dist/autopilot/fsm.d.ts +1 -1
  20. package/dist/autopilot/fsm.d.ts.map +1 -1
  21. package/dist/autoresearch/goal.js.map +1 -1
  22. package/dist/autoresearch/runtime.js.map +1 -1
  23. package/dist/capabilities/lockfile.js.map +1 -1
  24. package/dist/catalog/reader.js.map +1 -1
  25. package/dist/catalog/skill-mirror.js.map +1 -1
  26. package/dist/cli/__tests__/adapt-help.test.js.map +1 -1
  27. package/dist/cli/__tests__/agents-init.test.js.map +1 -1
  28. package/dist/cli/__tests__/agents.test.js.map +1 -1
  29. package/dist/cli/__tests__/api.test.js.map +1 -1
  30. package/dist/cli/__tests__/ask.test.js.map +1 -1
  31. package/dist/cli/__tests__/auth.test.js.map +1 -1
  32. package/dist/cli/__tests__/autoresearch-guided.test.js.map +1 -1
  33. package/dist/cli/__tests__/autoresearch.test.js.map +1 -1
  34. package/dist/cli/__tests__/capabilities.test.js.map +1 -1
  35. package/dist/cli/__tests__/codex-plugin-layout.test.js.map +1 -1
  36. package/dist/cli/__tests__/doctor-context-window-warning.test.js.map +1 -1
  37. package/dist/cli/__tests__/doctor-invalid-config.test.js +47 -4
  38. package/dist/cli/__tests__/doctor-invalid-config.test.js.map +1 -1
  39. package/dist/cli/__tests__/doctor-team.test.js.map +1 -1
  40. package/dist/cli/__tests__/doctor-warning-copy.test.js +650 -15
  41. package/dist/cli/__tests__/doctor-warning-copy.test.js.map +1 -1
  42. package/dist/cli/__tests__/exec.test.js.map +1 -1
  43. package/dist/cli/__tests__/imagegen-continuation.test.js.map +1 -1
  44. package/dist/cli/__tests__/index.test.js +216 -11
  45. package/dist/cli/__tests__/index.test.js.map +1 -1
  46. package/dist/cli/__tests__/launch-fallback.test.js +49 -3
  47. package/dist/cli/__tests__/launch-fallback.test.js.map +1 -1
  48. package/dist/cli/__tests__/mcp-parity.test.js +84 -12
  49. package/dist/cli/__tests__/mcp-parity.test.js.map +1 -1
  50. package/dist/cli/__tests__/native-hook-claim-journal.test.d.ts +2 -0
  51. package/dist/cli/__tests__/native-hook-claim-journal.test.d.ts.map +1 -0
  52. package/dist/cli/__tests__/native-hook-claim-journal.test.js +128 -0
  53. package/dist/cli/__tests__/native-hook-claim-journal.test.js.map +1 -0
  54. package/dist/cli/__tests__/nested-help-routing.test.js.map +1 -1
  55. package/dist/cli/__tests__/question.test.js.map +1 -1
  56. package/dist/cli/__tests__/ralph-deslop-contract.test.js.map +1 -1
  57. package/dist/cli/__tests__/ralph-goal-mode-contract.test.js +6 -0
  58. package/dist/cli/__tests__/ralph-goal-mode-contract.test.js.map +1 -1
  59. package/dist/cli/__tests__/ralph-prd-deep-interview.test.js.map +1 -1
  60. package/dist/cli/__tests__/ralph-prd-smoke.test.js.map +1 -1
  61. package/dist/cli/__tests__/ralph.test.js +8 -3
  62. package/dist/cli/__tests__/ralph.test.js.map +1 -1
  63. package/dist/cli/__tests__/ralplan-bootstrap-3181.test.d.ts +2 -0
  64. package/dist/cli/__tests__/ralplan-bootstrap-3181.test.d.ts.map +1 -0
  65. package/dist/cli/__tests__/ralplan-bootstrap-3181.test.js +187 -0
  66. package/dist/cli/__tests__/ralplan-bootstrap-3181.test.js.map +1 -0
  67. package/dist/cli/__tests__/ralplan.test.d.ts +2 -0
  68. package/dist/cli/__tests__/ralplan.test.d.ts.map +1 -0
  69. package/dist/cli/__tests__/ralplan.test.js +129 -0
  70. package/dist/cli/__tests__/ralplan.test.js.map +1 -0
  71. package/dist/cli/__tests__/resume.test.js +6 -1
  72. package/dist/cli/__tests__/resume.test.js.map +1 -1
  73. package/dist/cli/__tests__/session-scoped-runtime.test.js +25 -0
  74. package/dist/cli/__tests__/session-scoped-runtime.test.js.map +1 -1
  75. package/dist/cli/__tests__/session-search-help.test.js.map +1 -1
  76. package/dist/cli/__tests__/session-search.test.js.map +1 -1
  77. package/dist/cli/__tests__/setup-agents-overwrite.test.js +79 -0
  78. package/dist/cli/__tests__/setup-agents-overwrite.test.js.map +1 -1
  79. package/dist/cli/__tests__/setup-gh-star.test.js.map +1 -1
  80. package/dist/cli/__tests__/setup-hooks-shared-ownership.test.js +77 -3
  81. package/dist/cli/__tests__/setup-hooks-shared-ownership.test.js.map +1 -1
  82. package/dist/cli/__tests__/setup-hooks-trust-e2e.test.d.ts +2 -0
  83. package/dist/cli/__tests__/setup-hooks-trust-e2e.test.d.ts.map +1 -0
  84. package/dist/cli/__tests__/setup-hooks-trust-e2e.test.js +285 -0
  85. package/dist/cli/__tests__/setup-hooks-trust-e2e.test.js.map +1 -0
  86. package/dist/cli/__tests__/setup-install-mode.test.js +2261 -14
  87. package/dist/cli/__tests__/setup-install-mode.test.js.map +1 -1
  88. package/dist/cli/__tests__/setup-preferences.test.d.ts +2 -0
  89. package/dist/cli/__tests__/setup-preferences.test.d.ts.map +1 -0
  90. package/dist/cli/__tests__/setup-preferences.test.js +79 -0
  91. package/dist/cli/__tests__/setup-preferences.test.js.map +1 -0
  92. package/dist/cli/__tests__/setup-refresh.test.js +33 -2
  93. package/dist/cli/__tests__/setup-refresh.test.js.map +1 -1
  94. package/dist/cli/__tests__/setup-scope.test.js +46 -3
  95. package/dist/cli/__tests__/setup-scope.test.js.map +1 -1
  96. package/dist/cli/__tests__/sparkshell-cli.test.js.map +1 -1
  97. package/dist/cli/__tests__/state.test.js +37 -0
  98. package/dist/cli/__tests__/state.test.js.map +1 -1
  99. package/dist/cli/__tests__/team.test.js.map +1 -1
  100. package/dist/cli/__tests__/ultragoal.test.js +61 -1
  101. package/dist/cli/__tests__/ultragoal.test.js.map +1 -1
  102. package/dist/cli/__tests__/uninstall.test.js +1771 -24
  103. package/dist/cli/__tests__/uninstall.test.js.map +1 -1
  104. package/dist/cli/__tests__/update.test.js +58 -0
  105. package/dist/cli/__tests__/update.test.js.map +1 -1
  106. package/dist/cli/__tests__/url.test.js.map +1 -1
  107. package/dist/cli/__tests__/windows-popup-loop-contract.test.js.map +1 -1
  108. package/dist/cli/adapt.js.map +1 -1
  109. package/dist/cli/agents-init.js.map +1 -1
  110. package/dist/cli/agents.js.map +1 -1
  111. package/dist/cli/api.js.map +1 -1
  112. package/dist/cli/ask.d.ts +1 -1
  113. package/dist/cli/ask.d.ts.map +1 -1
  114. package/dist/cli/ask.js.map +1 -1
  115. package/dist/cli/auth.js.map +1 -1
  116. package/dist/cli/autoresearch-guided.js.map +1 -1
  117. package/dist/cli/autoresearch-intake.js.map +1 -1
  118. package/dist/cli/capabilities.js.map +1 -1
  119. package/dist/cli/cleanup.js.map +1 -1
  120. package/dist/cli/codex-feature-probe.js.map +1 -1
  121. package/dist/cli/codex-home.js.map +1 -1
  122. package/dist/cli/doctor.d.ts +18 -0
  123. package/dist/cli/doctor.d.ts.map +1 -1
  124. package/dist/cli/doctor.js +594 -130
  125. package/dist/cli/doctor.js.map +1 -1
  126. package/dist/cli/explore.js.map +1 -1
  127. package/dist/cli/index.d.ts +11 -3
  128. package/dist/cli/index.d.ts.map +1 -1
  129. package/dist/cli/index.js +108 -20
  130. package/dist/cli/index.js.map +1 -1
  131. package/dist/cli/mcp-serve.js.map +1 -1
  132. package/dist/cli/mission.js.map +1 -1
  133. package/dist/cli/native-assets.js.map +1 -1
  134. package/dist/cli/native-hook-claim-journal.d.ts +18 -0
  135. package/dist/cli/native-hook-claim-journal.d.ts.map +1 -0
  136. package/dist/cli/native-hook-claim-journal.js +239 -0
  137. package/dist/cli/native-hook-claim-journal.js.map +1 -0
  138. package/dist/cli/omx.js.map +1 -1
  139. package/dist/cli/plugin-marketplace.js.map +1 -1
  140. package/dist/cli/project-runtime-codex-homes.js.map +1 -1
  141. package/dist/cli/question.js.map +1 -1
  142. package/dist/cli/ralph.d.ts.map +1 -1
  143. package/dist/cli/ralph.js +3 -2
  144. package/dist/cli/ralph.js.map +1 -1
  145. package/dist/cli/ralplan.d.ts +14 -0
  146. package/dist/cli/ralplan.d.ts.map +1 -0
  147. package/dist/cli/ralplan.js +203 -0
  148. package/dist/cli/ralplan.js.map +1 -0
  149. package/dist/cli/setup-preferences.d.ts +11 -0
  150. package/dist/cli/setup-preferences.d.ts.map +1 -1
  151. package/dist/cli/setup-preferences.js +32 -0
  152. package/dist/cli/setup-preferences.js.map +1 -1
  153. package/dist/cli/setup.d.ts +53 -0
  154. package/dist/cli/setup.d.ts.map +1 -1
  155. package/dist/cli/setup.js +1401 -398
  156. package/dist/cli/setup.js.map +1 -1
  157. package/dist/cli/sparkshell.js.map +1 -1
  158. package/dist/cli/star-prompt.js.map +1 -1
  159. package/dist/cli/state.d.ts.map +1 -1
  160. package/dist/cli/state.js +2 -1
  161. package/dist/cli/state.js.map +1 -1
  162. package/dist/cli/team.js.map +1 -1
  163. package/dist/cli/ultragoal.d.ts +5 -1
  164. package/dist/cli/ultragoal.d.ts.map +1 -1
  165. package/dist/cli/ultragoal.js +34 -2
  166. package/dist/cli/ultragoal.js.map +1 -1
  167. package/dist/cli/uninstall.d.ts +8 -0
  168. package/dist/cli/uninstall.d.ts.map +1 -1
  169. package/dist/cli/uninstall.js +944 -95
  170. package/dist/cli/uninstall.js.map +1 -1
  171. package/dist/cli/update.d.ts.map +1 -1
  172. package/dist/cli/update.js +8 -1
  173. package/dist/cli/update.js.map +1 -1
  174. package/dist/cli/url.js.map +1 -1
  175. package/dist/compat/__tests__/doctor-contract.test.js +5 -1
  176. package/dist/compat/__tests__/doctor-contract.test.js.map +1 -1
  177. package/dist/compat/__tests__/rust-runtime-compat.test.js.map +1 -1
  178. package/dist/config/__tests__/codex-hooks.test.js +1287 -11
  179. package/dist/config/__tests__/codex-hooks.test.js.map +1 -1
  180. package/dist/config/__tests__/generator-idempotent.test.js +850 -32
  181. package/dist/config/__tests__/generator-idempotent.test.js.map +1 -1
  182. package/dist/config/__tests__/generator-notify.test.js +6 -1
  183. package/dist/config/__tests__/generator-notify.test.js.map +1 -1
  184. package/dist/config/codex-feature-flags.d.ts.map +1 -1
  185. package/dist/config/codex-feature-flags.js.map +1 -1
  186. package/dist/config/codex-hooks.d.ts +124 -1
  187. package/dist/config/codex-hooks.d.ts.map +1 -1
  188. package/dist/config/codex-hooks.js +2142 -265
  189. package/dist/config/codex-hooks.js.map +1 -1
  190. package/dist/config/commit-lore-guard.js.map +1 -1
  191. package/dist/config/generator.d.ts +21 -16
  192. package/dist/config/generator.d.ts.map +1 -1
  193. package/dist/config/generator.js +991 -137
  194. package/dist/config/generator.js.map +1 -1
  195. package/dist/config/mcp-registry.js.map +1 -1
  196. package/dist/config/models.d.ts +2 -2
  197. package/dist/config/models.d.ts.map +1 -1
  198. package/dist/config/models.js.map +1 -1
  199. package/dist/config/omx-first-party-mcp.js.map +1 -1
  200. package/dist/config/team-mode.d.ts.map +1 -1
  201. package/dist/document-refresh/__tests__/enforcer.test.js.map +1 -1
  202. package/dist/document-refresh/enforcer.js.map +1 -1
  203. package/dist/exec/followup.js.map +1 -1
  204. package/dist/goal-workflows/artifacts.js.map +1 -1
  205. package/dist/hooks/__tests__/agents-overlay.test.js +7 -4
  206. package/dist/hooks/__tests__/agents-overlay.test.js.map +1 -1
  207. package/dist/hooks/__tests__/analyze-routing-contract.test.js.map +1 -1
  208. package/dist/hooks/__tests__/analyze-skill-contract.test.js.map +1 -1
  209. package/dist/hooks/__tests__/anti-slop-workflow.test.js.map +1 -1
  210. package/dist/hooks/__tests__/autopilot-skill-contract.test.js +9 -3
  211. package/dist/hooks/__tests__/autopilot-skill-contract.test.js.map +1 -1
  212. package/dist/hooks/__tests__/code-review-skill-contract.test.js.map +1 -1
  213. package/dist/hooks/__tests__/consensus-execution-handoff.test.js.map +1 -1
  214. package/dist/hooks/__tests__/deep-interview-contract.test.js.map +1 -1
  215. package/dist/hooks/__tests__/design-skill.test.js.map +1 -1
  216. package/dist/hooks/__tests__/keyword-detector.test.js +1372 -2
  217. package/dist/hooks/__tests__/keyword-detector.test.js.map +1 -1
  218. package/dist/hooks/__tests__/notify-fallback-watcher.test.js +14 -0
  219. package/dist/hooks/__tests__/notify-fallback-watcher.test.js.map +1 -1
  220. package/dist/hooks/__tests__/notify-hook-all-workers-idle.test.js.map +1 -1
  221. package/dist/hooks/__tests__/notify-hook-auto-nudge.test.js +669 -1
  222. package/dist/hooks/__tests__/notify-hook-auto-nudge.test.js.map +1 -1
  223. package/dist/hooks/__tests__/notify-hook-cross-worktree-heartbeat.test.js +2 -0
  224. package/dist/hooks/__tests__/notify-hook-cross-worktree-heartbeat.test.js.map +1 -1
  225. package/dist/hooks/__tests__/notify-hook-managed-tmux.test.js +142 -1
  226. package/dist/hooks/__tests__/notify-hook-managed-tmux.test.js.map +1 -1
  227. package/dist/hooks/__tests__/notify-hook-modules.test.js.map +1 -1
  228. package/dist/hooks/__tests__/notify-hook-non-omx-guard.test.js +48 -2
  229. package/dist/hooks/__tests__/notify-hook-non-omx-guard.test.js.map +1 -1
  230. package/dist/hooks/__tests__/notify-hook-ralph-resume.test.js +61 -44
  231. package/dist/hooks/__tests__/notify-hook-ralph-resume.test.js.map +1 -1
  232. package/dist/hooks/__tests__/notify-hook-regression-205.test.js.map +1 -1
  233. package/dist/hooks/__tests__/notify-hook-session-idle-dedupe.test.js +3 -2
  234. package/dist/hooks/__tests__/notify-hook-session-idle-dedupe.test.js.map +1 -1
  235. package/dist/hooks/__tests__/notify-hook-session-scope.test.js +243 -4
  236. package/dist/hooks/__tests__/notify-hook-session-scope.test.js.map +1 -1
  237. package/dist/hooks/__tests__/notify-hook-team-dispatch.test.js.map +1 -1
  238. package/dist/hooks/__tests__/notify-hook-team-leader-nudge.test.js +8 -1
  239. package/dist/hooks/__tests__/notify-hook-team-leader-nudge.test.js.map +1 -1
  240. package/dist/hooks/__tests__/notify-hook-team-tmux-guard.test.js.map +1 -1
  241. package/dist/hooks/__tests__/notify-hook-tmux-heal.test.js.map +1 -1
  242. package/dist/hooks/__tests__/notify-hook-tmux-scrollback.test.js.map +1 -1
  243. package/dist/hooks/__tests__/notify-hook-visual-verdict.test.js.map +1 -1
  244. package/dist/hooks/__tests__/notify-hook-worker-idle.test.js.map +1 -1
  245. package/dist/hooks/__tests__/openclaw-setup-contract.test.js.map +1 -1
  246. package/dist/hooks/__tests__/pre-context-gate-skills.test.js.map +1 -1
  247. package/dist/hooks/__tests__/prometheus-strict-contract.test.js.map +1 -1
  248. package/dist/hooks/__tests__/prompt-guidance-fragments.test.js.map +1 -1
  249. package/dist/hooks/__tests__/prompt-guidance-test-helpers.js.map +1 -1
  250. package/dist/hooks/__tests__/prompt-session-provenance.test.d.ts +2 -0
  251. package/dist/hooks/__tests__/prompt-session-provenance.test.d.ts.map +1 -0
  252. package/dist/hooks/__tests__/prompt-session-provenance.test.js +82 -0
  253. package/dist/hooks/__tests__/prompt-session-provenance.test.js.map +1 -0
  254. package/dist/hooks/__tests__/session.test.js +532 -11
  255. package/dist/hooks/__tests__/session.test.js.map +1 -1
  256. package/dist/hooks/__tests__/skill-catalog-hygiene.test.js.map +1 -1
  257. package/dist/hooks/__tests__/team-runtime-gating-docs-contract.test.js.map +1 -1
  258. package/dist/hooks/__tests__/visual-ralph-skill.test.js.map +1 -1
  259. package/dist/hooks/__tests__/visual-verdict-loop.test.js.map +1 -1
  260. package/dist/hooks/agents-overlay.js +5 -5
  261. package/dist/hooks/agents-overlay.js.map +1 -1
  262. package/dist/hooks/code-simplifier/index.js.map +1 -1
  263. package/dist/hooks/explore-routing.js.map +1 -1
  264. package/dist/hooks/extensibility/dispatcher.js.map +1 -1
  265. package/dist/hooks/extensibility/events.js.map +1 -1
  266. package/dist/hooks/extensibility/loader.js.map +1 -1
  267. package/dist/hooks/extensibility/plugin-runner-stdin.js.map +1 -1
  268. package/dist/hooks/extensibility/sdk/logging.js.map +1 -1
  269. package/dist/hooks/keyword-detector.d.ts +33 -7
  270. package/dist/hooks/keyword-detector.d.ts.map +1 -1
  271. package/dist/hooks/keyword-detector.js +2228 -119
  272. package/dist/hooks/keyword-detector.js.map +1 -1
  273. package/dist/hooks/keyword-registry.d.ts +12 -0
  274. package/dist/hooks/keyword-registry.d.ts.map +1 -1
  275. package/dist/hooks/keyword-registry.js +41 -0
  276. package/dist/hooks/keyword-registry.js.map +1 -1
  277. package/dist/hooks/prompt-session-provenance.d.ts +87 -0
  278. package/dist/hooks/prompt-session-provenance.d.ts.map +1 -0
  279. package/dist/hooks/prompt-session-provenance.js +180 -0
  280. package/dist/hooks/prompt-session-provenance.js.map +1 -0
  281. package/dist/hooks/session.d.ts +139 -34
  282. package/dist/hooks/session.d.ts.map +1 -1
  283. package/dist/hooks/session.js +1239 -317
  284. package/dist/hooks/session.js.map +1 -1
  285. package/dist/hooks/task-size-detector.js.map +1 -1
  286. package/dist/hud/__tests__/state.test.js.map +1 -1
  287. package/dist/hud/authority.js.map +1 -1
  288. package/dist/hud/constants.js.map +1 -1
  289. package/dist/hud/index.js.map +1 -1
  290. package/dist/hud/reconcile.js.map +1 -1
  291. package/dist/hud/render.js.map +1 -1
  292. package/dist/hud/state.js.map +1 -1
  293. package/dist/hud/tmux.js.map +1 -1
  294. package/dist/leader/__tests__/contract.test.js +103 -1
  295. package/dist/leader/__tests__/contract.test.js.map +1 -1
  296. package/dist/leader/contract.d.ts +26 -5
  297. package/dist/leader/contract.d.ts.map +1 -1
  298. package/dist/leader/contract.js +109 -3
  299. package/dist/leader/contract.js.map +1 -1
  300. package/dist/mcp/__tests__/bootstrap.test.js.map +1 -1
  301. package/dist/mcp/__tests__/server-lifecycle.test.js +15 -2
  302. package/dist/mcp/__tests__/server-lifecycle.test.js.map +1 -1
  303. package/dist/mcp/__tests__/state-paths.test.js +98 -1
  304. package/dist/mcp/__tests__/state-paths.test.js.map +1 -1
  305. package/dist/mcp/bootstrap.js.map +1 -1
  306. package/dist/mcp/code-intel-server.d.ts +58 -58
  307. package/dist/mcp/code-intel-server.d.ts.map +1 -1
  308. package/dist/mcp/code-intel-server.js.map +1 -1
  309. package/dist/mcp/hermes-bridge.js.map +1 -1
  310. package/dist/mcp/hermes-server.d.ts +70 -70
  311. package/dist/mcp/hermes-server.d.ts.map +1 -1
  312. package/dist/mcp/lifecycle-telemetry.js.map +1 -1
  313. package/dist/mcp/memory-server.d.ts +34 -34
  314. package/dist/mcp/memory-server.d.ts.map +1 -1
  315. package/dist/mcp/memory-server.js.map +1 -1
  316. package/dist/mcp/state-paths.d.ts +14 -0
  317. package/dist/mcp/state-paths.d.ts.map +1 -1
  318. package/dist/mcp/state-paths.js +96 -20
  319. package/dist/mcp/state-paths.js.map +1 -1
  320. package/dist/mcp/state-server.d.ts +47 -47
  321. package/dist/mcp/state-server.d.ts.map +1 -1
  322. package/dist/mcp/trace-server.d.ts +4 -4
  323. package/dist/mcp/trace-server.d.ts.map +1 -1
  324. package/dist/mcp/trace-server.js.map +1 -1
  325. package/dist/mcp/wiki-server.d.ts +23 -23
  326. package/dist/mcp/wiki-server.d.ts.map +1 -1
  327. package/dist/mcp/wiki-server.js.map +1 -1
  328. package/dist/modes/__tests__/base-session-scope.test.js +53 -1
  329. package/dist/modes/__tests__/base-session-scope.test.js.map +1 -1
  330. package/dist/modes/base.d.ts.map +1 -1
  331. package/dist/modes/base.js +5 -5
  332. package/dist/modes/base.js.map +1 -1
  333. package/dist/notifications/__tests__/lifecycle-dedupe.test.js.map +1 -1
  334. package/dist/notifications/__tests__/reply-listener.test.js.map +1 -1
  335. package/dist/notifications/__tests__/session-idle-tail-dedupe.test.js +177 -85
  336. package/dist/notifications/__tests__/session-idle-tail-dedupe.test.js.map +1 -1
  337. package/dist/notifications/__tests__/session-registry.test.js.map +1 -1
  338. package/dist/notifications/__tests__/template-engine.test.js.map +1 -1
  339. package/dist/notifications/__tests__/verbosity.test.js.map +1 -1
  340. package/dist/notifications/http-client.js.map +1 -1
  341. package/dist/notifications/index.d.ts +4 -1
  342. package/dist/notifications/index.d.ts.map +1 -1
  343. package/dist/notifications/index.js +3 -9
  344. package/dist/notifications/index.js.map +1 -1
  345. package/dist/notifications/lifecycle-dedupe.js.map +1 -1
  346. package/dist/notifications/reply-listener.d.ts.map +1 -1
  347. package/dist/notifications/reply-listener.js.map +1 -1
  348. package/dist/notifications/session-registry.js.map +1 -1
  349. package/dist/notifications/session-status.js.map +1 -1
  350. package/dist/notifications/temp-contract.js.map +1 -1
  351. package/dist/notifications/tmux-detector.js.map +1 -1
  352. package/dist/notifications/tmux.js.map +1 -1
  353. package/dist/openclaw/config.js.map +1 -1
  354. package/dist/openclaw/dispatcher.js.map +1 -1
  355. package/dist/pipeline/__tests__/orchestrator.test.js.map +1 -1
  356. package/dist/pipeline/__tests__/stages.test.js +8 -5
  357. package/dist/pipeline/__tests__/stages.test.js.map +1 -1
  358. package/dist/pipeline/stages/code-review.js.map +1 -1
  359. package/dist/pipeline/stages/ralph-verify.js.map +1 -1
  360. package/dist/pipeline/stages/ralplan.js.map +1 -1
  361. package/dist/pipeline/stages/team-exec.js.map +1 -1
  362. package/dist/pipeline/stages/ultragoal.js.map +1 -1
  363. package/dist/pipeline/stages/ultraqa.js.map +1 -1
  364. package/dist/planning/__tests__/approved-execution-lifecycle-matrix.test.js.map +1 -1
  365. package/dist/planning/artifact-names.js.map +1 -1
  366. package/dist/planning/artifacts.js.map +1 -1
  367. package/dist/question/__tests__/state.test.js.map +1 -1
  368. package/dist/question/__tests__/ui.test.js.map +1 -1
  369. package/dist/question/autopilot-wait.js.map +1 -1
  370. package/dist/question/client.d.ts.map +1 -1
  371. package/dist/question/client.js.map +1 -1
  372. package/dist/question/deep-interview.js.map +1 -1
  373. package/dist/question/events.js.map +1 -1
  374. package/dist/question/renderer.js.map +1 -1
  375. package/dist/question/state.d.ts.map +1 -1
  376. package/dist/question/state.js.map +1 -1
  377. package/dist/question/ui.js.map +1 -1
  378. package/dist/ralph/contract.d.ts +1 -1
  379. package/dist/ralph/contract.d.ts.map +1 -1
  380. package/dist/ralplan/__tests__/consensus-gate.test.js +403 -8
  381. package/dist/ralplan/__tests__/consensus-gate.test.js.map +1 -1
  382. package/dist/ralplan/__tests__/runtime.test.js +112 -14
  383. package/dist/ralplan/__tests__/runtime.test.js.map +1 -1
  384. package/dist/ralplan/consensus-gate.d.ts +3 -3
  385. package/dist/ralplan/consensus-gate.d.ts.map +1 -1
  386. package/dist/ralplan/consensus-gate.js +155 -60
  387. package/dist/ralplan/consensus-gate.js.map +1 -1
  388. package/dist/ralplan/runtime.d.ts +2 -2
  389. package/dist/ralplan/runtime.d.ts.map +1 -1
  390. package/dist/ralplan/runtime.js +1 -2
  391. package/dist/ralplan/runtime.js.map +1 -1
  392. package/dist/runtime/bridge.d.ts.map +1 -1
  393. package/dist/runtime/bridge.js.map +1 -1
  394. package/dist/runtime/process-tree.js.map +1 -1
  395. package/dist/runtime/run-loop.js.map +1 -1
  396. package/dist/runtime/run-outcome.d.ts +3 -3
  397. package/dist/runtime/run-outcome.d.ts.map +1 -1
  398. package/dist/runtime/run-outcome.js.map +1 -1
  399. package/dist/runtime/run-state.js.map +1 -1
  400. package/dist/scripts/__tests__/codex-native-hook.test.js +4121 -592
  401. package/dist/scripts/__tests__/codex-native-hook.test.js.map +1 -1
  402. package/dist/scripts/__tests__/fixtures/issue-3138-installed-smoke.d.ts +2 -0
  403. package/dist/scripts/__tests__/fixtures/issue-3138-installed-smoke.d.ts.map +1 -0
  404. package/dist/scripts/__tests__/fixtures/issue-3138-installed-smoke.js +634 -0
  405. package/dist/scripts/__tests__/fixtures/issue-3138-installed-smoke.js.map +1 -0
  406. package/dist/scripts/__tests__/generate-release-body.test.js.map +1 -1
  407. package/dist/scripts/__tests__/hook-derived-watcher.test.js.map +1 -1
  408. package/dist/scripts/__tests__/notify-dispatcher.test.js.map +1 -1
  409. package/dist/scripts/__tests__/notify-fallback-delivery.test.d.ts +2 -0
  410. package/dist/scripts/__tests__/notify-fallback-delivery.test.d.ts.map +1 -0
  411. package/dist/scripts/__tests__/notify-fallback-delivery.test.js +174 -0
  412. package/dist/scripts/__tests__/notify-fallback-delivery.test.js.map +1 -0
  413. package/dist/scripts/__tests__/notify-state-io.test.js +57 -1
  414. package/dist/scripts/__tests__/notify-state-io.test.js.map +1 -1
  415. package/dist/scripts/__tests__/role-intent-bootstrap-e2e-3181.test.d.ts +2 -0
  416. package/dist/scripts/__tests__/role-intent-bootstrap-e2e-3181.test.d.ts.map +1 -0
  417. package/dist/scripts/__tests__/role-intent-bootstrap-e2e-3181.test.js +205 -0
  418. package/dist/scripts/__tests__/role-intent-bootstrap-e2e-3181.test.js.map +1 -0
  419. package/dist/scripts/__tests__/role-intent-durable-recovery-3181.test.d.ts +2 -0
  420. package/dist/scripts/__tests__/role-intent-durable-recovery-3181.test.d.ts.map +1 -0
  421. package/dist/scripts/__tests__/role-intent-durable-recovery-3181.test.js +175 -0
  422. package/dist/scripts/__tests__/role-intent-durable-recovery-3181.test.js.map +1 -0
  423. package/dist/scripts/__tests__/run-test-files.test.js.map +1 -1
  424. package/dist/scripts/__tests__/smoke-packed-install.test.js +848 -4
  425. package/dist/scripts/__tests__/smoke-packed-install.test.js.map +1 -1
  426. package/dist/scripts/__tests__/test-reply-listener-live.test.js.map +1 -1
  427. package/dist/scripts/build-api.js.map +1 -1
  428. package/dist/scripts/build-explore-harness.js.map +1 -1
  429. package/dist/scripts/build-sparkshell.js.map +1 -1
  430. package/dist/scripts/cleanup-explore-harness.js.map +1 -1
  431. package/dist/scripts/codex-execution-surface.js.map +1 -1
  432. package/dist/scripts/codex-native-hook.d.ts.map +1 -1
  433. package/dist/scripts/codex-native-hook.js +689 -156
  434. package/dist/scripts/codex-native-hook.js.map +1 -1
  435. package/dist/scripts/generate-native-release-manifest.js.map +1 -1
  436. package/dist/scripts/generate-release-body.js.map +1 -1
  437. package/dist/scripts/hook-payload-guard.d.ts.map +1 -1
  438. package/dist/scripts/hook-payload-guard.js.map +1 -1
  439. package/dist/scripts/notify-fallback-delivery.d.ts +48 -0
  440. package/dist/scripts/notify-fallback-delivery.d.ts.map +1 -0
  441. package/dist/scripts/notify-fallback-delivery.js +419 -0
  442. package/dist/scripts/notify-fallback-delivery.js.map +1 -0
  443. package/dist/scripts/notify-fallback-watcher.js +136 -30
  444. package/dist/scripts/notify-fallback-watcher.js.map +1 -1
  445. package/dist/scripts/notify-hook/auto-nudge.d.ts +18 -9
  446. package/dist/scripts/notify-hook/auto-nudge.d.ts.map +1 -1
  447. package/dist/scripts/notify-hook/auto-nudge.js +39 -13
  448. package/dist/scripts/notify-hook/auto-nudge.js.map +1 -1
  449. package/dist/scripts/notify-hook/managed-tmux.d.ts +23 -0
  450. package/dist/scripts/notify-hook/managed-tmux.d.ts.map +1 -1
  451. package/dist/scripts/notify-hook/managed-tmux.js +122 -12
  452. package/dist/scripts/notify-hook/managed-tmux.js.map +1 -1
  453. package/dist/scripts/notify-hook/orchestration-intent.d.ts +7 -7
  454. package/dist/scripts/notify-hook/orchestration-intent.d.ts.map +1 -1
  455. package/dist/scripts/notify-hook/ralph-session-resume.d.ts +5 -2
  456. package/dist/scripts/notify-hook/ralph-session-resume.d.ts.map +1 -1
  457. package/dist/scripts/notify-hook/ralph-session-resume.js +33 -37
  458. package/dist/scripts/notify-hook/ralph-session-resume.js.map +1 -1
  459. package/dist/scripts/notify-hook/state-io.d.ts +17 -0
  460. package/dist/scripts/notify-hook/state-io.d.ts.map +1 -1
  461. package/dist/scripts/notify-hook/state-io.js +73 -11
  462. package/dist/scripts/notify-hook/state-io.js.map +1 -1
  463. package/dist/scripts/notify-hook/team-dispatch.d.ts +1 -1
  464. package/dist/scripts/notify-hook/team-dispatch.js.map +1 -1
  465. package/dist/scripts/notify-hook/team-leader-nudge.d.ts +2 -2
  466. package/dist/scripts/notify-hook/team-tmux-guard.d.ts +4 -4
  467. package/dist/scripts/notify-hook/team-worker-posttooluse.js.map +1 -1
  468. package/dist/scripts/notify-hook/team-worker-stop.d.ts +1 -1
  469. package/dist/scripts/notify-hook/team-worker.d.ts +2 -2
  470. package/dist/scripts/notify-hook/tmux-injection.d.ts +8 -1
  471. package/dist/scripts/notify-hook/tmux-injection.d.ts.map +1 -1
  472. package/dist/scripts/notify-hook/tmux-injection.js +25 -16
  473. package/dist/scripts/notify-hook/tmux-injection.js.map +1 -1
  474. package/dist/scripts/notify-hook/visual-verdict.d.ts +1 -1
  475. package/dist/scripts/notify-hook/visual-verdict.d.ts.map +1 -1
  476. package/dist/scripts/notify-hook/visual-verdict.js +15 -13
  477. package/dist/scripts/notify-hook/visual-verdict.js.map +1 -1
  478. package/dist/scripts/notify-hook.d.ts +24 -1
  479. package/dist/scripts/notify-hook.d.ts.map +1 -1
  480. package/dist/scripts/notify-hook.js +266 -90
  481. package/dist/scripts/notify-hook.js.map +1 -1
  482. package/dist/scripts/postinstall.js.map +1 -1
  483. package/dist/scripts/prompt-inventory.js.map +1 -1
  484. package/dist/scripts/smoke-packed-install.d.ts +879 -2
  485. package/dist/scripts/smoke-packed-install.d.ts.map +1 -1
  486. package/dist/scripts/smoke-packed-install.js +1528 -7
  487. package/dist/scripts/smoke-packed-install.js.map +1 -1
  488. package/dist/scripts/sync-plugin-mirror.js.map +1 -1
  489. package/dist/scripts/test-reply-listener-live.js.map +1 -1
  490. package/dist/scripts/test-sparkshell.js.map +1 -1
  491. package/dist/scripts/verify-native-agents.js.map +1 -1
  492. package/dist/session-history/friction.js.map +1 -1
  493. package/dist/session-history/search.js.map +1 -1
  494. package/dist/sidecar/__tests__/boundary.test.js.map +1 -1
  495. package/dist/sidecar/collector.js.map +1 -1
  496. package/dist/sidecar/index.js.map +1 -1
  497. package/dist/sidecar/render.js.map +1 -1
  498. package/dist/sidecar/tmux.js.map +1 -1
  499. package/dist/state/__tests__/operations.test.js +64 -15
  500. package/dist/state/__tests__/operations.test.js.map +1 -1
  501. package/dist/state/__tests__/skill-active.test.js +19 -1
  502. package/dist/state/__tests__/skill-active.test.js.map +1 -1
  503. package/dist/state/__tests__/workflow-transition.test.js +61 -0
  504. package/dist/state/__tests__/workflow-transition.test.js.map +1 -1
  505. package/dist/state/mode-state-context.js.map +1 -1
  506. package/dist/state/operations.d.ts +1 -1
  507. package/dist/state/operations.d.ts.map +1 -1
  508. package/dist/state/operations.js +49 -55
  509. package/dist/state/operations.js.map +1 -1
  510. package/dist/state/paths.d.ts +1 -1
  511. package/dist/state/paths.d.ts.map +1 -1
  512. package/dist/state/paths.js +1 -1
  513. package/dist/state/paths.js.map +1 -1
  514. package/dist/state/skill-active.d.ts +13 -1
  515. package/dist/state/skill-active.d.ts.map +1 -1
  516. package/dist/state/skill-active.js +43 -4
  517. package/dist/state/skill-active.js.map +1 -1
  518. package/dist/state/terminal-normalization.js.map +1 -1
  519. package/dist/state/workflow-transition-reconcile.d.ts.map +1 -1
  520. package/dist/state/workflow-transition-reconcile.js +20 -4
  521. package/dist/state/workflow-transition-reconcile.js.map +1 -1
  522. package/dist/state/workflow-transition.d.ts +1 -1
  523. package/dist/state/workflow-transition.d.ts.map +1 -1
  524. package/dist/state/workflow-transition.js.map +1 -1
  525. package/dist/subagents/__tests__/adapted-role-binding.test.d.ts +2 -0
  526. package/dist/subagents/__tests__/adapted-role-binding.test.d.ts.map +1 -0
  527. package/dist/subagents/__tests__/adapted-role-binding.test.js +988 -0
  528. package/dist/subagents/__tests__/adapted-role-binding.test.js.map +1 -0
  529. package/dist/subagents/__tests__/leader-bootstrap-3181.test.d.ts +2 -0
  530. package/dist/subagents/__tests__/leader-bootstrap-3181.test.d.ts.map +1 -0
  531. package/dist/subagents/__tests__/leader-bootstrap-3181.test.js +273 -0
  532. package/dist/subagents/__tests__/leader-bootstrap-3181.test.js.map +1 -0
  533. package/dist/subagents/__tests__/tracker.test.js +1550 -10
  534. package/dist/subagents/__tests__/tracker.test.js.map +1 -1
  535. package/dist/subagents/adapted-role-binding.d.ts +17 -0
  536. package/dist/subagents/adapted-role-binding.d.ts.map +1 -0
  537. package/dist/subagents/adapted-role-binding.js +75 -0
  538. package/dist/subagents/adapted-role-binding.js.map +1 -0
  539. package/dist/subagents/role-routing-marker.d.ts +10 -0
  540. package/dist/subagents/role-routing-marker.d.ts.map +1 -0
  541. package/dist/subagents/role-routing-marker.js +136 -0
  542. package/dist/subagents/role-routing-marker.js.map +1 -0
  543. package/dist/subagents/tracker.d.ts +172 -0
  544. package/dist/subagents/tracker.d.ts.map +1 -1
  545. package/dist/subagents/tracker.js +1077 -61
  546. package/dist/subagents/tracker.js.map +1 -1
  547. package/dist/team/__tests__/approved-execution.test.js.map +1 -1
  548. package/dist/team/__tests__/delivery-e2e-smoke.test.js.map +1 -1
  549. package/dist/team/__tests__/model-contract.test.js +231 -1
  550. package/dist/team/__tests__/model-contract.test.js.map +1 -1
  551. package/dist/team/__tests__/role-router.test.js.map +1 -1
  552. package/dist/team/__tests__/runtime.test.js +401 -1
  553. package/dist/team/__tests__/runtime.test.js.map +1 -1
  554. package/dist/team/__tests__/scaling.test.js +181 -0
  555. package/dist/team/__tests__/scaling.test.js.map +1 -1
  556. package/dist/team/__tests__/state-root.test.js.map +1 -1
  557. package/dist/team/__tests__/tmux-session.test.js +434 -4
  558. package/dist/team/__tests__/tmux-session.test.js.map +1 -1
  559. package/dist/team/__tests__/tmux-test-fixture.js.map +1 -1
  560. package/dist/team/api-interop.d.ts +2 -2
  561. package/dist/team/api-interop.d.ts.map +1 -1
  562. package/dist/team/api-interop.js.map +1 -1
  563. package/dist/team/commit-hygiene.js.map +1 -1
  564. package/dist/team/contracts.d.ts +5 -5
  565. package/dist/team/contracts.d.ts.map +1 -1
  566. package/dist/team/delivery-log.js.map +1 -1
  567. package/dist/team/followup-planner.js.map +1 -1
  568. package/dist/team/goal-workflow.js.map +1 -1
  569. package/dist/team/idle-nudge.d.ts.map +1 -1
  570. package/dist/team/model-contract.d.ts +20 -1
  571. package/dist/team/model-contract.d.ts.map +1 -1
  572. package/dist/team/model-contract.js +267 -30
  573. package/dist/team/model-contract.js.map +1 -1
  574. package/dist/team/orchestrator.js.map +1 -1
  575. package/dist/team/pane-status.js.map +1 -1
  576. package/dist/team/phase-controller.js.map +1 -1
  577. package/dist/team/reminder-intents.d.ts +1 -1
  578. package/dist/team/reminder-intents.d.ts.map +1 -1
  579. package/dist/team/reminder-intents.js.map +1 -1
  580. package/dist/team/runtime-cli.js.map +1 -1
  581. package/dist/team/runtime.d.ts.map +1 -1
  582. package/dist/team/runtime.js +83 -52
  583. package/dist/team/runtime.js.map +1 -1
  584. package/dist/team/scaling.d.ts +9 -7
  585. package/dist/team/scaling.d.ts.map +1 -1
  586. package/dist/team/scaling.js +69 -28
  587. package/dist/team/scaling.js.map +1 -1
  588. package/dist/team/state/dispatch-lock.js.map +1 -1
  589. package/dist/team/state/dispatch.js.map +1 -1
  590. package/dist/team/state/events.js.map +1 -1
  591. package/dist/team/state/tasks.js.map +1 -1
  592. package/dist/team/state-root.js.map +1 -1
  593. package/dist/team/state.js.map +1 -1
  594. package/dist/team/team-identity.d.ts.map +1 -1
  595. package/dist/team/team-identity.js.map +1 -1
  596. package/dist/team/tmux-session.d.ts +2 -0
  597. package/dist/team/tmux-session.d.ts.map +1 -1
  598. package/dist/team/tmux-session.js +90 -42
  599. package/dist/team/tmux-session.js.map +1 -1
  600. package/dist/team/worker-bootstrap.js.map +1 -1
  601. package/dist/team/worktree.js.map +1 -1
  602. package/dist/ultragoal/__tests__/artifacts.test.js.map +1 -1
  603. package/dist/ultragoal/__tests__/docs-contract.test.js.map +1 -1
  604. package/dist/ultragoal/artifacts.d.ts.map +1 -1
  605. package/dist/ultragoal/artifacts.js +4 -1
  606. package/dist/ultragoal/artifacts.js.map +1 -1
  607. package/dist/url-reader/index.js.map +1 -1
  608. package/dist/utils/agents-model-table.js.map +1 -1
  609. package/dist/utils/package.js.map +1 -1
  610. package/dist/utils/paths.js.map +1 -1
  611. package/dist/utils/platform-command.js.map +1 -1
  612. package/dist/utils/repo-deps.js.map +1 -1
  613. package/dist/utils/version.js.map +1 -1
  614. package/dist/verification/__tests__/ci-rust-gates.test.js +1 -1
  615. package/dist/verification/__tests__/dev-merge-issue-close-workflow.test.js.map +1 -1
  616. package/dist/verification/verifier.js.map +1 -1
  617. package/dist/visual/constants.d.ts +1 -1
  618. package/dist/visual/constants.d.ts.map +1 -1
  619. package/dist/vscode/__tests__/index.test.js.map +1 -1
  620. package/dist/vscode/index.js.map +1 -1
  621. package/dist/wiki/__tests__/cjk-tokenize.test.js.map +1 -1
  622. package/dist/wiki/__tests__/lint.test.js.map +1 -1
  623. package/dist/wiki/__tests__/query.test.js.map +1 -1
  624. package/dist/wiki/__tests__/storage.test.js.map +1 -1
  625. package/dist/wiki/__tests__/test-helpers.d.ts.map +1 -1
  626. package/dist/wiki/lint.js.map +1 -1
  627. package/dist/wiki/query.js.map +1 -1
  628. package/dist/wiki/storage.js.map +1 -1
  629. package/package.json +2 -2
  630. package/plugins/oh-my-codex/.codex-plugin/plugin.json +1 -1
  631. package/plugins/oh-my-codex/skills/omx-setup/SKILL.md +10 -5
  632. package/plugins/oh-my-codex/skills/ralph/SKILL.md +4 -2
  633. package/plugins/oh-my-codex/skills/ralplan/SKILL.md +5 -2
  634. package/skills/omx-setup/SKILL.md +10 -5
  635. package/skills/ralph/SKILL.md +4 -2
  636. package/skills/ralplan/SKILL.md +5 -2
  637. package/src/scripts/__tests__/codex-native-hook.test.ts +17522 -10630
  638. package/src/scripts/__tests__/fixtures/issue-3138-installed-smoke.ts +734 -0
  639. package/src/scripts/__tests__/notify-fallback-delivery.test.ts +177 -0
  640. package/src/scripts/__tests__/notify-state-io.test.ts +73 -0
  641. package/src/scripts/__tests__/role-intent-bootstrap-e2e-3181.test.ts +215 -0
  642. package/src/scripts/__tests__/role-intent-durable-recovery-3181.test.ts +193 -0
  643. package/src/scripts/__tests__/smoke-packed-install.test.ts +991 -4
  644. package/src/scripts/codex-native-hook.ts +779 -159
  645. package/src/scripts/notify-fallback-delivery.ts +416 -0
  646. package/src/scripts/notify-fallback-watcher.ts +132 -40
  647. package/src/scripts/notify-hook/auto-nudge.ts +43 -12
  648. package/src/scripts/notify-hook/managed-tmux.ts +149 -12
  649. package/src/scripts/notify-hook/ralph-session-resume.ts +37 -36
  650. package/src/scripts/notify-hook/state-io.ts +100 -13
  651. package/src/scripts/notify-hook/tmux-injection.ts +38 -21
  652. package/src/scripts/notify-hook/visual-verdict.ts +22 -12
  653. package/src/scripts/notify-hook.ts +335 -90
  654. package/src/scripts/smoke-packed-install.ts +1829 -6
package/dist/cli/setup.js CHANGED
@@ -2,17 +2,18 @@
2
2
  * omx setup - Automated installation of oh-my-codex
3
3
  * Installs skills, prompts, MCP servers config, and AGENTS.md
4
4
  */
5
- import { mkdir, cp, copyFile, readdir, readFile, rename, writeFile, stat, lstat, rm, } from "fs/promises";
6
- import { join, dirname, relative, basename } from "path";
7
- import { existsSync } from "fs";
5
+ import { mkdir, cp, copyFile, readdir, readFile, rename, writeFile, stat, lstat, rm, open, chmod, } from "fs/promises";
6
+ import { join, dirname, relative, basename, isAbsolute, sep, win32 } from "path";
7
+ import { constants, existsSync } from "fs";
8
8
  import { spawnSync } from "child_process";
9
9
  import { createInterface } from "readline/promises";
10
10
  import { homedir } from "os";
11
11
  import TOML from "@iarna/toml";
12
12
  import { createHash } from "crypto";
13
+ import { clearNativeHookClaimJournal, persistNativeHookClaimJournal, recoverNativeHookClaimJournal, syncNativeHookClaimParent, restoreNativeHookClaimNoClobber, } from "./native-hook-claim-journal.js";
13
14
  import { codexHome, codexConfigPath, codexPromptsDir, codexAgentsDir, userSkillsDir, omxStateDir, detectLegacySkillRootOverlap, omxPlansDir, omxLogsDir, } from "../utils/paths.js";
14
15
  import { buildMergedConfig, getRootModelName, getRootTomlArray, hasLegacyOmxTeamRunTable, isOmxManagedNotifyCommand, sanitizePreviousNotifyCommand, stripExistingOmxBlocks, stripExistingSharedMcpRegistryBlock, mergeSharedMcpRegistryBlock, stripOmxEnvSettings, stripOmxFeatureFlags, stripOmxSeededBehavioralDefaults, upsertPluginModeRuntimeFeatureFlags, upsertManagedCodexHookTrustState, stripManagedCodexHookTrustState, OMX_DEVELOPER_INSTRUCTIONS, OMX_PLUGIN_DEVELOPER_INSTRUCTIONS, hasFirstPartyOmxMcpRegistrations, extractFirstPartyOmxMcpSections, stripFirstPartyOmxMcpSections, } from "../config/generator.js";
15
- import { buildManagedCodexHookTrustState, buildManagedCodexNativeHookWindowsShimContent, buildManagedCodexNativeHookWindowsShimPath, mergeManagedCodexHooksConfig, extractCodexHooksJsonTrustState, removeManagedCodexHooks, } from "../config/codex-hooks.js";
16
+ import { buildManagedCodexNativeHookWindowsShimContent, buildManagedCodexNativeHookWindowsShimPath, planManagedCodexHooksMerge, planManagedCodexHooksRemoval, classifyManagedCodexNativeHookWindowsShimOwnership, ManagedCodexHooksPlanError, validateCodexHooksConfigStrict, } from "../config/codex-hooks.js";
16
17
  import { getLegacyUnifiedMcpRegistryCandidate, getUnifiedMcpRegistryCandidates, loadUnifiedMcpRegistry, planClaudeCodeMcpSettingsSync, } from "../config/mcp-registry.js";
17
18
  import { generateAgentToml } from "../agents/native-config.js";
18
19
  import { AGENT_DEFINITIONS } from "../agents/definitions.js";
@@ -25,7 +26,7 @@ import { DEFAULT_FRONTIER_MODEL } from "../config/models.js";
25
26
  import { teamModeEnabled, } from "../config/team-mode.js";
26
27
  import { addGeneratedAgentsMarker, hasOmxAgentsContract, hasOmxManagedAgentsSections, isOmxGeneratedAgentsMd, preserveUserOmxPolicyBlocks, upsertManagedAgentsBlock, } from "../utils/agents-md.js";
27
28
  import { DEFAULT_HUD_CONFIG } from "../hud/types.js";
28
- import { SETUP_INSTALL_MODES, SETUP_MCP_MODES, SETUP_SCOPES, getSetupScopeFilePath, readPersistedSetupPreferences, } from "./setup-preferences.js";
29
+ import { SETUP_INSTALL_MODES, SETUP_MCP_MODES, SETUP_SCOPES, getSetupScopeFilePath, readPersistedSetupPreferences, resolvePersistedSetupMergeAgents, writePersistedSetupPreferences, } from "./setup-preferences.js";
29
30
  import { OMX_LOCAL_MARKETPLACE_NAME, OMX_PLUGIN_NAME, materializePackagedOmxPluginCache, resolvePackagedOmxMarketplace, upsertLocalOmxMarketplaceRegistration, upsertLocalOmxPluginEnablement, upsertLocalOmxPluginMcpServerEnablement, hasLocalOmxPluginMcpServerRegistrations, pluginHookCacheMatchesPackaged, } from "./plugin-marketplace.js";
30
31
  import { resolveCodexHookFeatureSupportForCli } from "./codex-feature-probe.js";
31
32
  async function resolveStatusLinePresetForSetup(projectRoot, options) {
@@ -195,66 +196,969 @@ function getBackupContext(scope, projectRoot) {
195
196
  baseRoot: homedir(),
196
197
  };
197
198
  }
198
- function escapeTomlBasicString(value) {
199
- return value.replace(/\\/g, "\\\\").replace(/"/g, '\\"');
200
- }
201
- function renderHooksJsonTrustStateToml(content) {
202
- const trustState = extractCodexHooksJsonTrustState(content);
203
- return Object.entries(trustState)
204
- .sort(([left], [right]) => left.localeCompare(right))
205
- .flatMap(([key, state]) => [
206
- `[hooks.state."${escapeTomlBasicString(key)}"]`,
207
- `trusted_hash = "${escapeTomlBasicString(state.trusted_hash)}"`,
208
- ...(typeof state.enabled === "boolean" ? [`enabled = ${state.enabled}`] : []),
209
- "",
210
- ])
211
- .join("\n")
212
- .trimEnd();
199
+ function logManagedCodexHooksPlanDiagnostics(diagnostics, options) {
200
+ if (!options.verbose || !diagnostics)
201
+ return;
202
+ for (const diagnostic of diagnostics) {
203
+ console.log(` hook plan diagnostic: ${diagnostic.message}`);
204
+ }
205
+ }
206
+ let nativeHookTransactionFailureInjector;
207
+ let nativeHookTransactionSequence = 0;
208
+ let nativeHookTransactionPlatformOverride;
209
+ let nativeHookTransactionTemporaryPathOverride;
210
+ let setupLatePhaseFailureInjector;
211
+ /** @internal Test seam for deterministic atomic-write and rollback coverage. */
212
+ export function setNativeHookTransactionFailureInjectorForTest(injector) {
213
+ const previous = nativeHookTransactionFailureInjector;
214
+ nativeHookTransactionFailureInjector = injector;
215
+ return () => {
216
+ nativeHookTransactionFailureInjector = previous;
217
+ };
218
+ }
219
+ /** @internal Test seam proving native artifacts commit only after later setup phases. */
220
+ export function setSetupLatePhaseFailureInjectorForTest(injector) {
221
+ const previous = setupLatePhaseFailureInjector;
222
+ setupLatePhaseFailureInjector = injector;
223
+ return () => {
224
+ setupLatePhaseFailureInjector = previous;
225
+ };
226
+ }
227
+ /** @internal Test seam for deterministic Windows transaction coverage. */
228
+ export function setNativeHookTransactionPlatformForTest(platform) {
229
+ const previous = nativeHookTransactionPlatformOverride;
230
+ nativeHookTransactionPlatformOverride = platform;
231
+ return () => {
232
+ nativeHookTransactionPlatformOverride = previous;
233
+ };
234
+ }
235
+ /** @internal Test seam for deterministic native transaction temporary-path coverage. */
236
+ export function setNativeHookTransactionTemporaryPathForTest(resolver) {
237
+ const previous = nativeHookTransactionTemporaryPathOverride;
238
+ nativeHookTransactionTemporaryPathOverride = resolver;
239
+ return () => {
240
+ nativeHookTransactionTemporaryPathOverride = previous;
241
+ };
242
+ }
243
+ function nativeHookPlatform() {
244
+ return nativeHookTransactionPlatformOverride ?? process.platform;
245
+ }
246
+ function hookTransactionBytesEqual(left, right) {
247
+ return left === null || right === null ? left === right : left.equals(right);
248
+ }
249
+ function nativeHookTransactionTopologyEqual(left, right) {
250
+ return left.kind === "absent" || right.kind === "absent"
251
+ ? left.kind === right.kind
252
+ : left.mode === right.mode;
253
+ }
254
+ function isMissingPathError(error) {
255
+ return (typeof error === "object" &&
256
+ error !== null &&
257
+ "code" in error &&
258
+ error.code === "ENOENT");
259
+ }
260
+ function nativeHookTransactionArtifactAncestorPaths(controlledRoot, artifactPaths) {
261
+ const paths = new Set([controlledRoot]);
262
+ for (const artifactPath of artifactPaths) {
263
+ const parentPath = dirname(artifactPath);
264
+ const fromControlledRoot = relative(controlledRoot, parentPath);
265
+ if (fromControlledRoot === ".." ||
266
+ fromControlledRoot.startsWith(`..${sep}`) ||
267
+ isAbsolute(fromControlledRoot)) {
268
+ continue;
269
+ }
270
+ let ancestorPath = controlledRoot;
271
+ if (fromControlledRoot !== "") {
272
+ for (const component of fromControlledRoot.split(sep)) {
273
+ ancestorPath = join(ancestorPath, component);
274
+ paths.add(ancestorPath);
275
+ }
276
+ }
277
+ }
278
+ return [...paths];
279
+ }
280
+ async function captureNativeHookTransactionAncestorSnapshots(ancestorPaths) {
281
+ const snapshots = [];
282
+ for (const path of ancestorPaths) {
283
+ let status;
284
+ try {
285
+ status = await lstat(path);
286
+ }
287
+ catch (error) {
288
+ if (isMissingPathError(error)) {
289
+ snapshots.push({ path, topology: { kind: "absent" } });
290
+ continue;
291
+ }
292
+ throw error;
293
+ }
294
+ if (status.isSymbolicLink()) {
295
+ throw new Error(`Refusing native hook transaction: ancestor ${path} is a symbolic link.`);
296
+ }
297
+ if (!status.isDirectory()) {
298
+ throw new Error(`Refusing native hook transaction: ancestor ${path} is not a directory.`);
299
+ }
300
+ snapshots.push({
301
+ path,
302
+ topology: { kind: "directory", device: status.dev, inode: status.ino },
303
+ });
304
+ }
305
+ return snapshots;
306
+ }
307
+ function nativeHookTransactionAncestorTopologyEqual(left, right) {
308
+ return left.kind === "absent" || right.kind === "absent"
309
+ ? left.kind === right.kind
310
+ : left.device === right.device && left.inode === right.inode;
311
+ }
312
+ async function captureNativeHookTransactionAncestorPrecondition(controlledRoot, artifactPaths) {
313
+ const ancestorPaths = nativeHookTransactionArtifactAncestorPaths(controlledRoot, artifactPaths);
314
+ return {
315
+ controlledRoot,
316
+ ancestorPaths,
317
+ snapshots: await captureNativeHookTransactionAncestorSnapshots(ancestorPaths),
318
+ };
213
319
  }
214
- function existingHooksStateKeys(config) {
320
+ async function assertNativeHookTransactionAncestorPrecondition(precondition) {
321
+ const actual = await captureNativeHookTransactionAncestorSnapshots(precondition.ancestorPaths);
322
+ for (let index = 0; index < precondition.snapshots.length; index += 1) {
323
+ const expected = precondition.snapshots[index];
324
+ const current = actual[index];
325
+ if (!nativeHookTransactionAncestorTopologyEqual(current.topology, expected.topology)) {
326
+ throw new Error(`Native hook transaction precondition changed for ancestor ${expected.path}; refusing to mutate a stale topology.`);
327
+ }
328
+ }
329
+ }
330
+ async function refreshNativeHookTransactionAncestorPrecondition(precondition, artifactPath) {
331
+ const allowedCreatedPaths = new Set(nativeHookTransactionArtifactAncestorPaths(precondition.controlledRoot, [artifactPath]));
332
+ const actual = await captureNativeHookTransactionAncestorSnapshots(precondition.ancestorPaths);
333
+ for (let index = 0; index < precondition.snapshots.length; index += 1) {
334
+ const expected = precondition.snapshots[index];
335
+ const current = actual[index];
336
+ if (nativeHookTransactionAncestorTopologyEqual(current.topology, expected.topology)) {
337
+ continue;
338
+ }
339
+ if (expected.topology.kind === "absent" &&
340
+ current.topology.kind === "directory" &&
341
+ allowedCreatedPaths.has(expected.path)) {
342
+ continue;
343
+ }
344
+ throw new Error(`Native hook transaction precondition changed for ancestor ${expected.path}; refusing to mutate a stale topology.`);
345
+ }
346
+ precondition.snapshots = actual;
347
+ }
348
+ function decodeNativeHookTransactionUtf8(bytes, label) {
215
349
  try {
216
- const parsed = TOML.parse(config);
217
- return new Set(Object.keys(parsed.hooks?.state ?? {}));
350
+ const content = new TextDecoder("utf-8", {
351
+ fatal: true,
352
+ ignoreBOM: true,
353
+ }).decode(bytes);
354
+ if (!Buffer.from(content, "utf-8").equals(bytes)) {
355
+ throw new Error("decoded text did not round-trip to the original bytes");
356
+ }
357
+ return content;
218
358
  }
219
- catch {
220
- return new Set();
359
+ catch (error) {
360
+ throw new ManagedCodexHooksPlanError("invalid_document", `Refusing to read ${label}: invalid UTF-8 (${error instanceof Error ? error.message : String(error)}).`, { label });
361
+ }
362
+ }
363
+ async function captureNativeHookTransactionArtifact(path, label) {
364
+ let before;
365
+ try {
366
+ before = await lstat(path);
367
+ }
368
+ catch (error) {
369
+ if (isMissingPathError(error)) {
370
+ return { bytes: null, topology: { kind: "absent" } };
371
+ }
372
+ throw error;
373
+ }
374
+ if (!before.isFile() || before.nlink !== 1) {
375
+ throw new Error(`Refusing native hook transaction for ${label}: expected a single-link regular file or absence.`);
376
+ }
377
+ const bytes = await readFile(path);
378
+ const after = await lstat(path);
379
+ const beforeTopology = {
380
+ kind: "regular_file",
381
+ mode: before.mode & 0o7777,
382
+ };
383
+ const afterTopology = {
384
+ kind: "regular_file",
385
+ mode: after.mode & 0o7777,
386
+ };
387
+ if (!after.isFile() ||
388
+ after.nlink !== 1 ||
389
+ !nativeHookTransactionTopologyEqual(beforeTopology, afterTopology) ||
390
+ before.dev !== after.dev ||
391
+ before.ino !== after.ino ||
392
+ before.nlink !== after.nlink) {
393
+ throw new Error(`Refusing native hook transaction for ${label}: path changed while its snapshot was read.`);
221
394
  }
395
+ return {
396
+ bytes,
397
+ topology: beforeTopology,
398
+ device: before.dev,
399
+ inode: before.ino,
400
+ links: before.nlink,
401
+ };
402
+ }
403
+ function nativeHookTransactionSnapshotsEqual(left, right) {
404
+ return (hookTransactionBytesEqual(left.bytes, right.bytes) &&
405
+ nativeHookTransactionTopologyEqual(left.topology, right.topology) &&
406
+ left.device === right.device &&
407
+ left.inode === right.inode &&
408
+ left.links === right.links);
222
409
  }
223
- function appendHooksJsonTrustStateToConfig(config, hooksContent) {
224
- const existingKeys = existingHooksStateKeys(config);
225
- const trustState = extractCodexHooksJsonTrustState(hooksContent);
226
- const migratableContent = JSON.stringify({
227
- state: Object.fromEntries(Object.entries(trustState).filter(([key]) => !existingKeys.has(key))),
410
+ function nativeHookTransactionSnapshotMatchesExpected(snapshot, expected) {
411
+ return (hookTransactionBytesEqual(snapshot.bytes, expected.bytes) &&
412
+ nativeHookTransactionTopologyEqual(snapshot.topology, expected.topology));
413
+ }
414
+ function nativeHookTransactionOutputTopology(before, after) {
415
+ if (after === null)
416
+ return { kind: "absent" };
417
+ return {
418
+ kind: "regular_file",
419
+ mode: before.topology.kind === "regular_file" ? before.topology.mode : 0o600,
420
+ };
421
+ }
422
+ function nativeHookTransactionArtifact(kind, path, label, before, after, hookPlatform) {
423
+ return hookTransactionBytesEqual(before.bytes, after)
424
+ ? null
425
+ : {
426
+ kind,
427
+ path,
428
+ label,
429
+ before,
430
+ after,
431
+ afterTopology: nativeHookTransactionOutputTopology(before, after),
432
+ hookPlatform,
433
+ };
434
+ }
435
+ function nativeHookTransactionPrecondition(kind, path, label, before) {
436
+ return { kind, path, label, before };
437
+ }
438
+ function assertWindowsNativeHookShimOwnership(shimPath, before, expected) {
439
+ if (before === null)
440
+ return;
441
+ const ownership = classifyManagedCodexNativeHookWindowsShimOwnership(before, expected);
442
+ if (ownership === "current" || ownership === "historical")
443
+ return;
444
+ throw new Error(`Refusing to replace modified Windows native hook shim ${shimPath}. Restore the complete OMX-generated shim or remove it after verifying no foreign hook references it.`);
445
+ }
446
+ function isWindowsShimReferenceRecord(value) {
447
+ return typeof value === "object" && value !== null && !Array.isArray(value);
448
+ }
449
+ function normalizeWindowsShimReferencePath(path) {
450
+ const windowsPath = path.replace(/\//g, "\\");
451
+ if (windowsPath.length === 0 ||
452
+ /[\0-\x1f\x7f-\x9f`<>"|?*]/.test(windowsPath) ||
453
+ /^\\\\\.(?:\\|$)/.test(windowsPath)) {
454
+ return null;
455
+ }
456
+ const isDriveAbsolute = /^[A-Za-z]:\\(?:[^\\:]+(?:\\[^\\:]+)*)?$/.test(windowsPath);
457
+ const isUncAbsolute = /^\\\\[^\\:]+\\[^\\:]+(?:\\[^\\:]+)*$/.test(windowsPath);
458
+ if (!isDriveAbsolute && !isUncAbsolute)
459
+ return null;
460
+ return win32.normalize(windowsPath).toLowerCase();
461
+ }
462
+ function windowsShimPathBasename(path) {
463
+ const name = win32.basename(path.replace(/\//g, "\\")).replace(/[. ]+$/, "");
464
+ return name.length > 0 ? name.toLowerCase() : null;
465
+ }
466
+ function decodePowerShellSingleQuotedLiteral(value) {
467
+ return value.replace(/''/g, "'");
468
+ }
469
+ /**
470
+ * Parses the only foreign command form that proves its -File target is a
471
+ * static literal. All other PowerShell forms remain ambiguous because they can
472
+ * construct an executable target dynamically or through a nested evaluator.
473
+ */
474
+ function decodeWindowsShimReferencePath(command) {
475
+ if (/["`]/.test(command))
476
+ return null;
477
+ const match = command.match(/^\s*&\s+'((?:''|[^'\r\n])*)'\s+-noprofile\s+-executionpolicy\s+bypass\s+-file\s+'((?:''|[^'\r\n])*)'\s*$/i);
478
+ if (!match)
479
+ return null;
480
+ const powerShellPath = decodePowerShellSingleQuotedLiteral(match[1]);
481
+ if (windowsShimPathBasename(powerShellPath) !== "powershell.exe" ||
482
+ normalizeWindowsShimReferencePath(powerShellPath) === null ||
483
+ hasPotentialWindowsPathAlias(powerShellPath)) {
484
+ return null;
485
+ }
486
+ return decodePowerShellSingleQuotedLiteral(match[2]);
487
+ }
488
+ function hasPotentialWindowsPathAlias(path) {
489
+ const windowsPath = path.replace(/\//g, "\\");
490
+ return windowsPath.split("\\").some((component) => component === "." ||
491
+ component === ".." ||
492
+ /[. ]$/.test(component) ||
493
+ /~\d+(?:\.[^\\]*)?$/i.test(component));
494
+ }
495
+ function windowsShimCommandReferenceDecision(command, shimPath) {
496
+ const decodedShimPath = decodeWindowsShimReferencePath(command);
497
+ if (decodedShimPath === null)
498
+ return "ambiguous";
499
+ const normalizedDecodedPath = normalizeWindowsShimReferencePath(decodedShimPath);
500
+ const normalizedShimPath = normalizeWindowsShimReferencePath(shimPath);
501
+ if (normalizedDecodedPath === null || normalizedShimPath === null) {
502
+ return "ambiguous";
503
+ }
504
+ if (normalizedDecodedPath === normalizedShimPath)
505
+ return "referenced";
506
+ // A distinct fully qualified spelling is not proof that the foreign target
507
+ // cannot resolve to this shim: reparse points, SUBST, mapped drives, and UNC
508
+ // aliases can all refer to the same file. No race-safe file-identity proof is
509
+ // available here, so preserve the proof-owned shim.
510
+ return "ambiguous";
511
+ }
512
+ /**
513
+ * Decides whether a proof-owned Windows shim must remain after a hooks.json
514
+ * transition. JSON is strictly validated and decoded before inspection so
515
+ * escaped paths are handled semantically. Unknown future event members are
516
+ * scanned only through the established executable group/command shape; prompt
517
+ * and agent payloads remain inert metadata.
518
+ */
519
+ export function decideWindowsNativeHookShimReference(finalHooksContent, shimPath) {
520
+ if (finalHooksContent === null)
521
+ return "not_referenced";
522
+ const validation = validateCodexHooksConfigStrict(finalHooksContent, {
523
+ platform: "win32",
228
524
  });
229
- const trustToml = renderHooksJsonTrustStateToml(migratableContent);
230
- if (!trustToml)
231
- return config;
232
- const base = config.trimEnd();
233
- return [
234
- base,
235
- base ? "" : null,
236
- "# Migrated from legacy hooks.json state; kept in Codex config.toml because Codex 0.140 rejects top-level hooks.json state.",
237
- trustToml,
238
- "",
239
- ].filter((line) => line !== null).join("\n");
240
- }
241
- async function migrateLegacyHooksJsonTrustStateToConfig(configPath, hooksContent, backupContext, summary, options) {
242
- const existingConfig = existsSync(configPath)
243
- ? await readFile(configPath, "utf-8")
244
- : "";
245
- const nextConfig = appendHooksJsonTrustStateToConfig(existingConfig, hooksContent);
246
- if (nextConfig === existingConfig)
525
+ if (!validation.ok)
526
+ return "ambiguous";
527
+ const hooks = validation.root.hooks;
528
+ if (!isWindowsShimReferenceRecord(hooks))
529
+ return "not_referenced";
530
+ let ambiguous = false;
531
+ for (const eventGroups of Object.values(hooks)) {
532
+ if (!Array.isArray(eventGroups))
533
+ continue;
534
+ for (const group of eventGroups) {
535
+ if (!isWindowsShimReferenceRecord(group) ||
536
+ !Array.isArray(group.hooks)) {
537
+ continue;
538
+ }
539
+ for (const handler of group.hooks) {
540
+ if (!isWindowsShimReferenceRecord(handler) ||
541
+ handler.type !== "command") {
542
+ continue;
543
+ }
544
+ for (const command of [
545
+ handler.commandWindows,
546
+ handler.command_windows,
547
+ handler.command,
548
+ ]) {
549
+ if (typeof command !== "string")
550
+ continue;
551
+ const decision = windowsShimCommandReferenceDecision(command, shimPath);
552
+ if (decision === "referenced")
553
+ return decision;
554
+ if (decision === "ambiguous")
555
+ ambiguous = true;
556
+ }
557
+ }
558
+ }
559
+ }
560
+ return ambiguous ? "ambiguous" : "not_referenced";
561
+ }
562
+ function preflightManagedCodexHookTrustState(config, priorManagedHookTrustState, managedHookTrustState) {
563
+ stripManagedCodexHookTrustState(config, {
564
+ priorManagedHookTrustState,
565
+ managedTrustState: managedHookTrustState,
566
+ });
567
+ }
568
+ function injectNativeHookTransactionFailure(stage, target) {
569
+ nativeHookTransactionFailureInjector?.(stage, target);
570
+ }
571
+ function nativeHookTransactionTemporaryPath(path, purpose) {
572
+ if (nativeHookTransactionTemporaryPathOverride) {
573
+ return nativeHookTransactionTemporaryPathOverride(path, purpose);
574
+ }
575
+ nativeHookTransactionSequence += 1;
576
+ return join(dirname(path), `.${basename(path)}.omx-${purpose}-${process.pid}-${nativeHookTransactionSequence}.tmp`);
577
+ }
578
+ function nativeHookTransactionClaimPath(path) {
579
+ nativeHookTransactionSequence += 1;
580
+ return join(dirname(path), `.${basename(path)}.omx-claim-${process.pid}-${nativeHookTransactionSequence}.tmp`);
581
+ }
582
+ async function restoreNativeHookClaim(claimPath, destinationPath) {
583
+ await restoreNativeHookClaimNoClobber(claimPath, destinationPath);
584
+ }
585
+ async function atomicWriteNativeHookTransactionArtifact(artifact, content, stage, expectedCurrent, ancestorPrecondition, onWriteApplied, onWriteStabilized, assertApplied) {
586
+ const temporaryPath = nativeHookTransactionTemporaryPath(artifact.path, "write");
587
+ let temporaryCreated = false;
588
+ let temporarySnapshot;
589
+ let claimPath;
590
+ let claimCreated = false;
591
+ let journaledClaim = false;
592
+ try {
593
+ await assertNativeHookTransactionAncestorPrecondition(ancestorPrecondition);
594
+ await assertApplied?.();
595
+ await mkdir(dirname(artifact.path), { recursive: true });
596
+ await refreshNativeHookTransactionAncestorPrecondition(ancestorPrecondition, artifact.path);
597
+ if (stage === "write") {
598
+ injectNativeHookTransactionFailure("before_temp_write", artifact);
599
+ }
600
+ else {
601
+ injectNativeHookTransactionFailure("before_rollback", artifact);
602
+ }
603
+ await assertNativeHookTransactionArtifactSnapshot(artifact.path, artifact.label, expectedCurrent, stage === "write" ? "precondition" : "rollback");
604
+ await assertNativeHookTransactionAncestorPrecondition(ancestorPrecondition);
605
+ await assertApplied?.();
606
+ const handle = await open(temporaryPath, "wx", artifact.afterTopology.kind === "regular_file"
607
+ ? artifact.afterTopology.mode
608
+ : 0o600);
609
+ temporaryCreated = true;
610
+ try {
611
+ await handle.writeFile(content);
612
+ await handle.sync();
613
+ }
614
+ finally {
615
+ await handle.close();
616
+ }
617
+ if (artifact.afterTopology.kind === "regular_file") {
618
+ await chmod(temporaryPath, artifact.afterTopology.mode);
619
+ }
620
+ temporarySnapshot = await assertNativeHookTransactionArtifactState(temporaryPath, `${artifact.label} temporary`, { bytes: content, topology: artifact.afterTopology }, "read-back");
621
+ if (stage === "write") {
622
+ injectNativeHookTransactionFailure("before_rename", artifact);
623
+ }
624
+ else {
625
+ injectNativeHookTransactionFailure("before_rollback_rename", artifact);
626
+ }
627
+ await assertNativeHookTransactionArtifactSnapshot(artifact.path, artifact.label, expectedCurrent, stage === "write" ? "precondition" : "rollback");
628
+ await assertNativeHookTransactionAncestorPrecondition(ancestorPrecondition);
629
+ await assertNativeHookTransactionArtifactSnapshot(temporaryPath, `${artifact.label} temporary`, temporarySnapshot, "read-back");
630
+ await assertApplied?.();
631
+ if (!temporarySnapshot) {
632
+ throw new Error("temporary path was not fully captured after writing");
633
+ }
634
+ injectNativeHookTransactionFailure(stage === "write"
635
+ ? "after_final_rename_validation"
636
+ : "after_final_restore_validation", artifact);
637
+ claimPath = nativeHookTransactionClaimPath(artifact.path);
638
+ const claimRelativePath = relative(ancestorPrecondition.controlledRoot, artifact.path);
639
+ const canJournalClaim = !isAbsolute(claimRelativePath) &&
640
+ claimRelativePath !== ".." &&
641
+ !claimRelativePath.startsWith(`..${sep}`);
642
+ if (expectedCurrent.bytes !== null) {
643
+ if (canJournalClaim) {
644
+ await persistNativeHookClaimJournal(ancestorPrecondition.controlledRoot, {
645
+ canonicalPath: artifact.path,
646
+ claimPath,
647
+ before: expectedCurrent.bytes,
648
+ after: content,
649
+ });
650
+ await refreshNativeHookTransactionAncestorPrecondition(ancestorPrecondition, join(ancestorPrecondition.controlledRoot, ".omx", "native-hook-claim-journal.json"));
651
+ journaledClaim = true;
652
+ }
653
+ await rename(artifact.path, claimPath);
654
+ claimCreated = true;
655
+ if (journaledClaim)
656
+ await syncNativeHookClaimParent(claimPath);
657
+ await assertNativeHookTransactionArtifactSnapshot(claimPath, `${artifact.label} replacement claim`, expectedCurrent, stage === "write" ? "precondition" : "rollback");
658
+ }
659
+ await copyFile(temporaryPath, artifact.path, constants.COPYFILE_EXCL);
660
+ if (artifact.afterTopology.kind === "regular_file") {
661
+ await chmod(artifact.path, artifact.afterTopology.mode);
662
+ }
663
+ const installedHandle = await open(artifact.path, "r");
664
+ try {
665
+ await installedHandle.sync();
666
+ }
667
+ finally {
668
+ await installedHandle.close();
669
+ }
670
+ await syncNativeHookClaimParent(artifact.path);
671
+ const installedSnapshot = await assertNativeHookTransactionArtifactState(artifact.path, artifact.label, { bytes: content, topology: artifact.afterTopology }, stage === "write" ? "precondition" : "rollback");
672
+ if (claimCreated && claimPath) {
673
+ await rm(claimPath);
674
+ await syncNativeHookClaimParent(claimPath);
675
+ claimCreated = false;
676
+ }
677
+ if (journaledClaim) {
678
+ await clearNativeHookClaimJournal(ancestorPrecondition.controlledRoot);
679
+ journaledClaim = false;
680
+ }
681
+ await rm(temporaryPath);
682
+ temporaryCreated = false;
683
+ onWriteApplied?.(installedSnapshot);
684
+ onWriteStabilized?.(installedSnapshot);
685
+ if (stage === "write") {
686
+ injectNativeHookTransactionFailure("after_rename", artifact);
687
+ }
688
+ await assertNativeHookTransactionArtifactSnapshot(artifact.path, artifact.label, installedSnapshot, stage === "write" ? "precondition" : "rollback");
689
+ }
690
+ catch (error) {
691
+ if (claimCreated && claimPath) {
692
+ try {
693
+ await restoreNativeHookClaim(claimPath, artifact.path);
694
+ await syncNativeHookClaimParent(artifact.path);
695
+ claimCreated = false;
696
+ if (journaledClaim) {
697
+ await clearNativeHookClaimJournal(ancestorPrecondition.controlledRoot);
698
+ journaledClaim = false;
699
+ }
700
+ }
701
+ catch (recoveryError) {
702
+ throw new Error(`Native hook transaction ${stage} failed (${error instanceof Error ? error.message : String(error)}) and preserved ${claimPath} for manual recovery: ${recoveryError instanceof Error ? recoveryError.message : String(recoveryError)}`);
703
+ }
704
+ }
705
+ if (temporaryCreated) {
706
+ try {
707
+ if (!temporarySnapshot) {
708
+ throw new Error("temporary path was not fully captured after writing");
709
+ }
710
+ await assertNativeHookTransactionArtifactSnapshot(temporaryPath, `${artifact.label} temporary`, temporarySnapshot, "read-back");
711
+ await assertNativeHookTransactionAncestorPrecondition(ancestorPrecondition);
712
+ await rm(temporaryPath);
713
+ }
714
+ catch (cleanupError) {
715
+ const message = error instanceof Error ? error.message : String(error);
716
+ throw new Error(`Native hook transaction ${stage} failed (${message}) and preserved temporary ${temporaryPath} for manual recovery after cleanup verification failed: ${cleanupError instanceof Error ? cleanupError.message : String(cleanupError)}`);
717
+ }
718
+ }
719
+ throw error;
720
+ }
721
+ }
722
+ async function assertNativeHookTransactionArtifactState(path, label, expected, context) {
723
+ const actual = await captureNativeHookTransactionArtifact(path, label);
724
+ if (!nativeHookTransactionSnapshotMatchesExpected(actual, expected)) {
725
+ if (context === "rollback") {
726
+ throw new Error(`Native hook transaction rollback preserved ${path} for manual recovery because ${label} no longer matches the transaction-owned version.`);
727
+ }
728
+ throw new Error(`Native hook transaction ${context} changed for ${label}; refusing to overwrite concurrent content.`);
729
+ }
730
+ return actual;
731
+ }
732
+ async function assertNativeHookTransactionArtifactSnapshot(path, label, expected, context) {
733
+ const actual = await captureNativeHookTransactionArtifact(path, label);
734
+ if (!nativeHookTransactionSnapshotsEqual(actual, expected)) {
735
+ if (context === "rollback") {
736
+ throw new Error(`Native hook transaction rollback preserved ${path} for manual recovery because ${label} no longer matches the transaction-owned version.`);
737
+ }
738
+ throw new Error(`Native hook transaction ${context} changed for ${label}; refusing to overwrite concurrent content.`);
739
+ }
740
+ return actual;
741
+ }
742
+ async function assertNativeHookTransactionPrecondition(precondition) {
743
+ await assertNativeHookTransactionArtifactSnapshot(precondition.path, precondition.label, precondition.before, "precondition");
744
+ }
745
+ async function assertUnmutatedNativeHookTransactionPreconditions(preconditions, applied) {
746
+ const mutatedPaths = new Set(applied.map((entry) => entry.artifact.path));
747
+ for (const precondition of preconditions) {
748
+ if (mutatedPaths.has(precondition.path))
749
+ continue;
750
+ await assertNativeHookTransactionPrecondition(precondition);
751
+ }
752
+ }
753
+ async function assertAppliedNativeHookTransactionSnapshots(applied) {
754
+ for (const entry of applied) {
755
+ await assertNativeHookTransactionArtifactSnapshot(entry.artifact.path, entry.artifact.label, entry.appliedSnapshot, "precondition");
756
+ }
757
+ }
758
+ async function assertNativeHookTransactionStagedRecoveryCopies(applied) {
759
+ for (const entry of applied) {
760
+ if (!entry.stagedDeletionPath ||
761
+ !entry.stagedDeletionSnapshot ||
762
+ entry.stagedDeletionCleaned) {
763
+ continue;
764
+ }
765
+ await assertNativeHookTransactionArtifactSnapshot(entry.stagedDeletionPath, `${entry.artifact.label} staged deletion`, entry.stagedDeletionSnapshot, "rollback");
766
+ }
767
+ }
768
+ async function assertNativeHookTransactionRollbackState(applied) {
769
+ await assertNativeHookTransactionStagedRecoveryCopies(applied);
770
+ await assertAppliedNativeHookTransactionSnapshots(applied);
771
+ }
772
+ async function applyNativeHookTransactionArtifact(artifact, ancestorPrecondition, applied) {
773
+ await assertNativeHookTransactionArtifactSnapshot(artifact.path, artifact.label, artifact.before, "precondition");
774
+ await assertNativeHookTransactionAncestorPrecondition(ancestorPrecondition);
775
+ if (artifact.after !== null) {
776
+ let entry;
777
+ await atomicWriteNativeHookTransactionArtifact(artifact, artifact.after, "write", artifact.before, ancestorPrecondition, (appliedSnapshot) => {
778
+ entry = { artifact, appliedSnapshot };
779
+ applied.push(entry);
780
+ }, (stabilizedSnapshot) => {
781
+ if (!entry) {
782
+ throw new Error("native hook transaction write was not registered as applied");
783
+ }
784
+ entry.appliedSnapshot = stabilizedSnapshot;
785
+ }, () => assertAppliedNativeHookTransactionSnapshots(applied));
786
+ if (!entry) {
787
+ throw new Error("native hook transaction write was not registered as applied");
788
+ }
789
+ return entry;
790
+ }
791
+ const stagedDeletionPath = nativeHookTransactionTemporaryPath(artifact.path, "delete");
792
+ let stagedDeletionCreated = false;
793
+ let originalRemoved = false;
794
+ let stagedDeletionSnapshot;
795
+ try {
796
+ injectNativeHookTransactionFailure("before_temp_write", artifact);
797
+ await assertNativeHookTransactionArtifactSnapshot(artifact.path, artifact.label, artifact.before, "precondition");
798
+ await assertNativeHookTransactionAncestorPrecondition(ancestorPrecondition);
799
+ await assertAppliedNativeHookTransactionSnapshots(applied);
800
+ const handle = await open(stagedDeletionPath, "wx", artifact.before.topology.kind === "regular_file"
801
+ ? artifact.before.topology.mode
802
+ : 0o600);
803
+ stagedDeletionCreated = true;
804
+ try {
805
+ await handle.writeFile(artifact.before.bytes);
806
+ await handle.sync();
807
+ }
808
+ finally {
809
+ await handle.close();
810
+ }
811
+ if (artifact.before.topology.kind === "regular_file") {
812
+ await chmod(stagedDeletionPath, artifact.before.topology.mode);
813
+ }
814
+ stagedDeletionSnapshot = await assertNativeHookTransactionArtifactState(stagedDeletionPath, `${artifact.label} staged deletion`, { bytes: artifact.before.bytes, topology: artifact.before.topology }, "read-back");
815
+ injectNativeHookTransactionFailure("before_remove", artifact);
816
+ await assertNativeHookTransactionArtifactSnapshot(artifact.path, artifact.label, artifact.before, "precondition");
817
+ await assertNativeHookTransactionAncestorPrecondition(ancestorPrecondition);
818
+ await assertNativeHookTransactionArtifactSnapshot(stagedDeletionPath, `${artifact.label} staged deletion`, stagedDeletionSnapshot, "read-back");
819
+ await assertAppliedNativeHookTransactionSnapshots(applied);
820
+ injectNativeHookTransactionFailure("after_final_remove_validation", artifact);
821
+ await assertNativeHookTransactionArtifactSnapshot(stagedDeletionPath, `${artifact.label} staged deletion`, stagedDeletionSnapshot, "read-back");
822
+ const claimPath = nativeHookTransactionClaimPath(artifact.path);
823
+ const claimRelativePath = relative(ancestorPrecondition.controlledRoot, artifact.path);
824
+ const journaledClaim = !isAbsolute(claimRelativePath) &&
825
+ claimRelativePath !== ".." &&
826
+ !claimRelativePath.startsWith(`..${sep}`);
827
+ if (journaledClaim) {
828
+ await persistNativeHookClaimJournal(ancestorPrecondition.controlledRoot, {
829
+ canonicalPath: artifact.path,
830
+ claimPath,
831
+ before: artifact.before.bytes,
832
+ after: null,
833
+ });
834
+ }
835
+ await rename(artifact.path, claimPath);
836
+ if (journaledClaim)
837
+ await syncNativeHookClaimParent(claimPath);
838
+ const entry = {
839
+ artifact,
840
+ appliedSnapshot: { bytes: null, topology: { kind: "absent" } },
841
+ stagedDeletionPath,
842
+ stagedDeletionSnapshot,
843
+ };
844
+ applied.push(entry);
845
+ try {
846
+ await assertNativeHookTransactionAncestorPrecondition(ancestorPrecondition);
847
+ await assertNativeHookTransactionArtifactSnapshot(claimPath, `${artifact.label} removal claim`, artifact.before, "precondition");
848
+ }
849
+ catch (error) {
850
+ try {
851
+ const claimed = await captureNativeHookTransactionArtifact(claimPath, `${artifact.label} removal claim`);
852
+ await restoreNativeHookClaim(claimPath, artifact.path);
853
+ if (journaledClaim)
854
+ await syncNativeHookClaimParent(artifact.path);
855
+ if (journaledClaim) {
856
+ await clearNativeHookClaimJournal(ancestorPrecondition.controlledRoot);
857
+ }
858
+ await assertNativeHookTransactionArtifactSnapshot(artifact.path, artifact.label, claimed, "precondition");
859
+ const entryIndex = applied.indexOf(entry);
860
+ if (entryIndex >= 0)
861
+ applied.splice(entryIndex, 1);
862
+ }
863
+ catch (recoveryError) {
864
+ throw new Error(`Native hook transaction removal claim failed (${error instanceof Error ? error.message : String(error)}) and preserved ${claimPath} for manual recovery: ${recoveryError instanceof Error ? recoveryError.message : String(recoveryError)}`);
865
+ }
866
+ throw error;
867
+ }
868
+ await rm(claimPath);
869
+ if (journaledClaim)
870
+ await syncNativeHookClaimParent(claimPath);
871
+ if (journaledClaim) {
872
+ await clearNativeHookClaimJournal(ancestorPrecondition.controlledRoot);
873
+ }
874
+ originalRemoved = true;
875
+ injectNativeHookTransactionFailure("after_remove", artifact);
876
+ return entry;
877
+ }
878
+ catch (error) {
879
+ if (stagedDeletionCreated && !originalRemoved) {
880
+ try {
881
+ if (!stagedDeletionSnapshot) {
882
+ throw new Error("staged deletion path was not fully captured after writing");
883
+ }
884
+ await assertNativeHookTransactionArtifactSnapshot(stagedDeletionPath, `${artifact.label} staged deletion`, stagedDeletionSnapshot, "read-back");
885
+ await assertNativeHookTransactionAncestorPrecondition(ancestorPrecondition);
886
+ const claimPath = nativeHookTransactionClaimPath(artifact.path);
887
+ await rename(stagedDeletionPath, claimPath);
888
+ try {
889
+ await assertNativeHookTransactionArtifactSnapshot(claimPath, `${artifact.label} staged deletion cleanup claim`, stagedDeletionSnapshot, "read-back");
890
+ }
891
+ catch (claimError) {
892
+ try {
893
+ await restoreNativeHookClaim(claimPath, stagedDeletionPath);
894
+ }
895
+ catch (recoveryError) {
896
+ throw new Error(`Native hook transaction staged deletion cleanup claim failed (${claimError instanceof Error ? claimError.message : String(claimError)}) and preserved ${claimPath} for manual recovery: ${recoveryError instanceof Error ? recoveryError.message : String(recoveryError)}`);
897
+ }
898
+ throw claimError;
899
+ }
900
+ await rm(claimPath);
901
+ }
902
+ catch (cleanupError) {
903
+ const message = error instanceof Error ? error.message : String(error);
904
+ throw new Error(`Native hook transaction failed (${message}) and preserved staged deletion ${stagedDeletionPath} for manual recovery after cleanup verification failed: ${cleanupError instanceof Error ? cleanupError.message : String(cleanupError)}`);
905
+ }
906
+ }
907
+ throw error;
908
+ }
909
+ }
910
+ async function verifyNativeHookTransactionArtifact(applied) {
911
+ const { artifact } = applied;
912
+ injectNativeHookTransactionFailure("before_readback", artifact);
913
+ const actual = await captureNativeHookTransactionArtifact(artifact.path, artifact.label);
914
+ if (!nativeHookTransactionSnapshotsEqual(actual, applied.appliedSnapshot)) {
915
+ throw new Error(`Native hook transaction read-back changed for ${artifact.label}; refusing to overwrite concurrent content.`);
916
+ }
917
+ if (artifact.after === null)
247
918
  return;
248
- if (await ensureBackup(configPath, existsSync(configPath), backupContext, options)) {
249
- summary.backedUp += 1;
919
+ const content = decodeNativeHookTransactionUtf8(actual.bytes, artifact.label);
920
+ if (artifact.kind === "hooks") {
921
+ const validation = validateCodexHooksConfigStrict(content, {
922
+ platform: artifact.hookPlatform,
923
+ });
924
+ if (!validation.ok) {
925
+ throw new Error(`Native hook transaction wrote invalid hooks.json: ${validation.error.message}`);
926
+ }
927
+ }
928
+ if (artifact.kind === "config")
929
+ TOML.parse(content);
930
+ }
931
+ async function restoreNativeHookTransactionArtifact(applied, ancestorPrecondition, assertRollbackState) {
932
+ const { artifact } = applied;
933
+ const current = await captureNativeHookTransactionArtifact(artifact.path, artifact.label);
934
+ if (!nativeHookTransactionSnapshotsEqual(current, applied.appliedSnapshot)) {
935
+ throw new Error(`Native hook transaction rollback preserved ${artifact.path} for manual recovery because ${artifact.label} no longer matches the transaction-owned version.`);
936
+ }
937
+ if (artifact.before.bytes === null) {
938
+ injectNativeHookTransactionFailure("before_rollback", artifact);
939
+ injectNativeHookTransactionFailure("before_rollback_remove", artifact);
940
+ await assertNativeHookTransactionArtifactSnapshot(artifact.path, artifact.label, applied.appliedSnapshot, "rollback");
941
+ await assertNativeHookTransactionAncestorPrecondition(ancestorPrecondition);
942
+ await assertRollbackState();
943
+ injectNativeHookTransactionFailure("after_final_restore_validation", artifact);
944
+ const claimPath = nativeHookTransactionClaimPath(artifact.path);
945
+ await rename(artifact.path, claimPath);
946
+ applied.appliedSnapshot = { bytes: null, topology: { kind: "absent" } };
947
+ try {
948
+ await assertNativeHookTransactionArtifactSnapshot(claimPath, `${artifact.label} rollback removal claim`, current, "rollback");
949
+ }
950
+ catch (error) {
951
+ try {
952
+ const claimed = await captureNativeHookTransactionArtifact(claimPath, `${artifact.label} rollback removal claim`);
953
+ await restoreNativeHookClaim(claimPath, artifact.path);
954
+ await assertNativeHookTransactionArtifactSnapshot(artifact.path, artifact.label, claimed, "rollback");
955
+ }
956
+ catch (recoveryError) {
957
+ throw new Error(`Native hook transaction rollback removal claim failed (${error instanceof Error ? error.message : String(error)}) and preserved ${claimPath} for manual recovery: ${recoveryError instanceof Error ? recoveryError.message : String(recoveryError)}`);
958
+ }
959
+ throw error;
960
+ }
961
+ await rm(claimPath);
250
962
  }
963
+ else {
964
+ const restoreArtifact = {
965
+ ...artifact,
966
+ after: artifact.before.bytes,
967
+ afterTopology: artifact.before.topology,
968
+ };
969
+ await atomicWriteNativeHookTransactionArtifact(restoreArtifact, artifact.before.bytes, "rollback", applied.appliedSnapshot, ancestorPrecondition, (restoredSnapshot) => {
970
+ applied.appliedSnapshot = restoredSnapshot;
971
+ }, (stabilizedSnapshot) => {
972
+ applied.appliedSnapshot = stabilizedSnapshot;
973
+ }, assertRollbackState);
974
+ }
975
+ const restored = await assertNativeHookTransactionArtifactSnapshot(artifact.path, artifact.label, applied.appliedSnapshot, "rollback");
976
+ if (!nativeHookTransactionSnapshotMatchesExpected(restored, {
977
+ bytes: artifact.before.bytes,
978
+ topology: artifact.before.topology,
979
+ })) {
980
+ throw new Error(`Native hook transaction rollback preserved ${artifact.path} for manual recovery because ${artifact.label} no longer matches the expected restored version.`);
981
+ }
982
+ }
983
+ async function cleanupNativeHookTransactionStagedDeletions(applied, ancestorPrecondition, preconditions, rollbackApplied) {
984
+ for (const entry of applied) {
985
+ if (!entry.stagedDeletionPath ||
986
+ !entry.stagedDeletionSnapshot ||
987
+ entry.stagedDeletionCleaned) {
988
+ continue;
989
+ }
990
+ injectNativeHookTransactionFailure("before_staged_cleanup", entry.artifact);
991
+ if (preconditions) {
992
+ await assertUnmutatedNativeHookTransactionPreconditions(preconditions, applied);
993
+ await assertAppliedNativeHookTransactionSnapshots(applied);
994
+ }
995
+ if (rollbackApplied) {
996
+ await assertNativeHookTransactionRollbackState(rollbackApplied);
997
+ }
998
+ const stagedDeletionPath = entry.stagedDeletionPath;
999
+ const stagedDeletionSnapshot = entry.stagedDeletionSnapshot;
1000
+ await assertNativeHookTransactionArtifactSnapshot(stagedDeletionPath, `${entry.artifact.label} staged deletion`, stagedDeletionSnapshot, "read-back");
1001
+ await assertNativeHookTransactionAncestorPrecondition(ancestorPrecondition);
1002
+ const claimPath = nativeHookTransactionClaimPath(entry.artifact.path);
1003
+ await rename(stagedDeletionPath, claimPath);
1004
+ try {
1005
+ await assertNativeHookTransactionArtifactSnapshot(claimPath, `${entry.artifact.label} staged deletion cleanup claim`, stagedDeletionSnapshot, "read-back");
1006
+ }
1007
+ catch (error) {
1008
+ try {
1009
+ await restoreNativeHookClaim(claimPath, stagedDeletionPath);
1010
+ }
1011
+ catch (recoveryError) {
1012
+ throw new Error(`Native hook transaction staged deletion cleanup claim failed (${error instanceof Error ? error.message : String(error)}) and preserved ${claimPath} for manual recovery: ${recoveryError instanceof Error ? recoveryError.message : String(recoveryError)}`);
1013
+ }
1014
+ throw error;
1015
+ }
1016
+ await rm(claimPath);
1017
+ entry.stagedDeletionCleaned = true;
1018
+ if (rollbackApplied) {
1019
+ await assertNativeHookTransactionRollbackState(rollbackApplied);
1020
+ }
1021
+ }
1022
+ }
1023
+ function nativeHookTransactionBackupPath(artifactPath, backupContext) {
1024
+ const relativePath = relative(backupContext.baseRoot, artifactPath);
1025
+ const safeRelativePath = relativePath.startsWith("..") || relativePath === ""
1026
+ ? artifactPath.replace(/^[/]+/, "")
1027
+ : relativePath;
1028
+ return join(backupContext.backupRoot, safeRelativePath);
1029
+ }
1030
+ async function ensureSnapshotBackup(artifact, backupContext, options) {
1031
+ const bytes = artifact.before.bytes;
1032
+ if (bytes === null)
1033
+ return false;
1034
+ const backupPath = nativeHookTransactionBackupPath(artifact.path, backupContext);
251
1035
  if (!options.dryRun) {
252
- await mkdir(dirname(configPath), { recursive: true });
253
- await writeFile(configPath, nextConfig);
1036
+ const relativeParent = relative(backupContext.baseRoot, dirname(backupPath));
1037
+ if (isAbsolute(relativeParent) ||
1038
+ relativeParent === ".." ||
1039
+ relativeParent.startsWith(`..${sep}`)) {
1040
+ throw new Error(`Refusing to back up ${artifact.path} outside controlled backup root.`);
1041
+ }
1042
+ let currentPath = backupContext.baseRoot;
1043
+ for (const component of relativeParent.split(sep).filter(Boolean)) {
1044
+ currentPath = join(currentPath, component);
1045
+ try {
1046
+ const currentStat = await lstat(currentPath);
1047
+ if (currentStat.isSymbolicLink() || !currentStat.isDirectory()) {
1048
+ throw new Error(`Refusing to use unsafe backup ancestor ${currentPath}.`);
1049
+ }
1050
+ }
1051
+ catch (error) {
1052
+ if (!isMissingPathError(error))
1053
+ throw error;
1054
+ await mkdir(currentPath);
1055
+ const createdStat = await lstat(currentPath);
1056
+ if (createdStat.isSymbolicLink() || !createdStat.isDirectory()) {
1057
+ throw new Error(`Refusing to use unsafe created backup ancestor ${currentPath}.`);
1058
+ }
1059
+ }
1060
+ }
1061
+ const handle = await open(backupPath, "wx", 0o600);
1062
+ try {
1063
+ await handle.writeFile(bytes);
1064
+ await handle.sync();
1065
+ }
1066
+ finally {
1067
+ await handle.close();
1068
+ }
1069
+ const backupStat = await lstat(backupPath);
1070
+ if (backupStat.isSymbolicLink() || !backupStat.isFile() || backupStat.nlink !== 1) {
1071
+ throw new Error(`Refusing unsafe native hook transaction backup ${backupPath}.`);
1072
+ }
1073
+ const writtenBytes = await readFile(backupPath);
1074
+ if (!writtenBytes.equals(bytes)) {
1075
+ throw new Error(`Native hook transaction backup verification failed for ${backupPath}.`);
1076
+ }
254
1077
  }
255
- summary.updated += 1;
256
1078
  if (options.verbose) {
257
- console.log(` ${options.dryRun ? "would migrate" : "migrated"} legacy hooks.json trust state to ${configPath}`);
1079
+ console.log(` backup ${artifact.path} -> ${backupPath}`);
1080
+ }
1081
+ return true;
1082
+ }
1083
+ async function commitNativeHookTransaction(artifacts, preconditions, ancestorPrecondition, backupContext, summary, options) {
1084
+ if (options.dryRun)
1085
+ return;
1086
+ await assertNativeHookTransactionAncestorPrecondition(ancestorPrecondition);
1087
+ for (const precondition of preconditions) {
1088
+ injectNativeHookTransactionFailure("before_precondition", precondition);
1089
+ await assertNativeHookTransactionPrecondition(precondition);
1090
+ }
1091
+ if (artifacts.length === 0)
1092
+ return;
1093
+ for (const artifact of artifacts) {
1094
+ injectNativeHookTransactionFailure("before_backup", artifact);
1095
+ if (await ensureSnapshotBackup(artifact, backupContext, options)) {
1096
+ summary.backedUp += 1;
1097
+ }
1098
+ }
1099
+ await assertUnmutatedNativeHookTransactionPreconditions(preconditions, []);
1100
+ await assertNativeHookTransactionAncestorPrecondition(ancestorPrecondition);
1101
+ const applied = [];
1102
+ let stagedCleanupStarted = false;
1103
+ try {
1104
+ for (const artifact of artifacts) {
1105
+ await assertUnmutatedNativeHookTransactionPreconditions(preconditions, applied);
1106
+ await assertAppliedNativeHookTransactionSnapshots(applied);
1107
+ const entry = await applyNativeHookTransactionArtifact(artifact, ancestorPrecondition, applied);
1108
+ await verifyNativeHookTransactionArtifact(entry);
1109
+ await assertUnmutatedNativeHookTransactionPreconditions(preconditions, applied);
1110
+ await assertAppliedNativeHookTransactionSnapshots(applied);
1111
+ }
1112
+ await assertUnmutatedNativeHookTransactionPreconditions(preconditions, applied);
1113
+ await assertAppliedNativeHookTransactionSnapshots(applied);
1114
+ stagedCleanupStarted = true;
1115
+ await cleanupNativeHookTransactionStagedDeletions(applied, ancestorPrecondition, preconditions);
1116
+ injectNativeHookTransactionFailure("after_staged_cleanup", artifacts[artifacts.length - 1]);
1117
+ await assertUnmutatedNativeHookTransactionPreconditions(preconditions, applied);
1118
+ await assertNativeHookTransactionAncestorPrecondition(ancestorPrecondition);
1119
+ await assertAppliedNativeHookTransactionSnapshots(applied);
1120
+ }
1121
+ catch (error) {
1122
+ if (stagedCleanupStarted &&
1123
+ applied.some((entry) => entry.stagedDeletionCleaned)) {
1124
+ throw new Error(`Native hook transaction committed but staged deletion cleanup failed during finalization: ${error instanceof Error ? error.message : String(error)}`);
1125
+ }
1126
+ const rollbackFailures = [];
1127
+ const restored = [];
1128
+ try {
1129
+ await assertNativeHookTransactionRollbackState(applied);
1130
+ }
1131
+ catch (rollbackError) {
1132
+ rollbackFailures.push(`recovery preflight: ${rollbackError instanceof Error ? rollbackError.message : String(rollbackError)}`);
1133
+ }
1134
+ if (rollbackFailures.length === 0) {
1135
+ for (const entry of [...applied].reverse()) {
1136
+ try {
1137
+ await assertNativeHookTransactionRollbackState(applied);
1138
+ await restoreNativeHookTransactionArtifact(entry, ancestorPrecondition, () => assertNativeHookTransactionRollbackState(applied));
1139
+ restored.push(entry);
1140
+ await assertNativeHookTransactionRollbackState(applied);
1141
+ }
1142
+ catch (rollbackError) {
1143
+ rollbackFailures.push(`${entry.artifact.label}: ${rollbackError instanceof Error ? rollbackError.message : String(rollbackError)}`);
1144
+ }
1145
+ }
1146
+ }
1147
+ if (rollbackFailures.length === 0) {
1148
+ try {
1149
+ await assertNativeHookTransactionRollbackState(applied);
1150
+ await cleanupNativeHookTransactionStagedDeletions(restored, ancestorPrecondition, undefined, applied);
1151
+ await assertNativeHookTransactionRollbackState(applied);
1152
+ }
1153
+ catch (cleanupError) {
1154
+ rollbackFailures.push(`staged deletion cleanup: ${cleanupError instanceof Error ? cleanupError.message : String(cleanupError)}`);
1155
+ }
1156
+ }
1157
+ const message = error instanceof Error ? error.message : String(error);
1158
+ if (rollbackFailures.length > 0) {
1159
+ throw new Error(`Native hook transaction failed (${message}) and rollback failed; manual recovery is required (${rollbackFailures.join("; ")}).`);
1160
+ }
1161
+ throw new Error(`Native hook transaction failed and was rolled back: ${message}`);
258
1162
  }
259
1163
  }
260
1164
  async function ensureBackup(destinationPath, contentChanged, backupContext, options) {
@@ -503,7 +1407,10 @@ async function promptForFirstPartyMcpRemoval(configPath, registrationKinds) {
503
1407
  }
504
1408
  }
505
1409
  function hasPersistedSetupPreferences(preferences) {
506
- return Boolean(preferences?.scope || preferences?.installMode || preferences?.teamMode);
1410
+ return Boolean(preferences?.scope ||
1411
+ preferences?.installMode ||
1412
+ preferences?.teamMode ||
1413
+ typeof preferences?.mergeAgents === "boolean");
507
1414
  }
508
1415
  function formatPersistedSetupPreferenceSummary(preferences) {
509
1416
  const summary = [
@@ -513,6 +1420,9 @@ function formatPersistedSetupPreferenceSummary(preferences) {
513
1420
  ];
514
1421
  if (preferences.teamMode)
515
1422
  summary.push(`teamMode=${preferences.teamMode}`);
1423
+ if (typeof preferences.mergeAgents === "boolean") {
1424
+ summary.push(`mergeAgents=${preferences.mergeAgents}`);
1425
+ }
516
1426
  return summary.join(", ");
517
1427
  }
518
1428
  async function promptForPersistedSetupReview(preferences) {
@@ -612,7 +1522,7 @@ function classifyPluginDeveloperInstructions(value) {
612
1522
  }
613
1523
  if (normalized ===
614
1524
  normalizeDeveloperInstructionsText(LEGACY_PLUGIN_DEVELOPER_INSTRUCTIONS)) {
615
- return "current";
1525
+ return "historical";
616
1526
  }
617
1527
  if (normalized === normalizeDeveloperInstructionsText(OMX_DEVELOPER_INSTRUCTIONS)) {
618
1528
  return "historical";
@@ -669,11 +1579,8 @@ function legacyPluginDeveloperInstructionsDecision(choice, state = "missing") {
669
1579
  reason: "legacy explicit skip policy",
670
1580
  };
671
1581
  }
672
- async function resolvePluginDeveloperInstructionsDecision(configPath, options) {
673
- const existing = existsSync(configPath)
674
- ? await readFile(configPath, "utf-8")
675
- : "";
676
- const value = readRootDeveloperInstructions(existing);
1582
+ async function resolvePluginDeveloperInstructionsDecision(existingConfig, configPath, options) {
1583
+ const value = readRootDeveloperInstructions(existingConfig);
677
1584
  if (value === undefined) {
678
1585
  if (options.pluginDeveloperInstructionsPrompt) {
679
1586
  return legacyPluginDeveloperInstructionsDecision(await options.pluginDeveloperInstructionsPrompt(configPath), "missing");
@@ -1016,8 +1923,7 @@ async function persistSetupPreferences(projectRoot, preferences, options) {
1016
1923
  console.log(` dry-run: skip persisting ${scopePath}`);
1017
1924
  return;
1018
1925
  }
1019
- await mkdir(dirname(scopePath), { recursive: true });
1020
- await writeFile(scopePath, JSON.stringify(preferences, null, 2) + "\n");
1926
+ await writePersistedSetupPreferences(projectRoot, preferences);
1021
1927
  if (options.verbose)
1022
1928
  console.log(` Wrote ${scopePath}`);
1023
1929
  }
@@ -1181,176 +2087,246 @@ function insertRootTomlKey(config, line) {
1181
2087
  const after = lines.slice(firstTableIndex);
1182
2088
  return [...before, line, "", ...after].join("\n") + "\n";
1183
2089
  }
1184
- async function ensurePluginMarketplaceRegistration(configPath, pkgRoot, mcpMode, removeFirstPartyMcp, backupContext, summary, options) {
1185
- const packagedMarketplace = await resolvePackagedOmxMarketplace(pkgRoot);
1186
- if (!packagedMarketplace) {
1187
- summary.skipped += 1;
1188
- return "unavailable";
1189
- }
1190
- const existingConfig = existsSync(configPath)
1191
- ? await readFile(configPath, "utf-8")
1192
- : "";
1193
- const nextConfig = upsertLocalOmxMarketplaceRegistration(upsertLocalOmxPluginMcpServerEnablement(upsertLocalOmxPluginEnablement(existingConfig), mcpMode === "compat", { removeWhenDisabled: removeFirstPartyMcp }), pkgRoot);
1194
- const destinationExists = existsSync(configPath);
1195
- if (nextConfig === existingConfig) {
1196
- summary.unchanged += 1;
1197
- return "unchanged";
1198
- }
1199
- if (await ensureBackup(configPath, destinationExists, backupContext, options)) {
1200
- summary.backedUp += 1;
1201
- }
1202
- if (!options.dryRun) {
1203
- await mkdir(dirname(configPath), { recursive: true });
1204
- await writeFile(configPath, nextConfig);
1205
- }
1206
- summary.updated += 1;
1207
- if (options.verbose) {
1208
- console.log(` ${options.dryRun ? "would register" : "registered"} local Codex plugin marketplace ${OMX_LOCAL_MARKETPLACE_NAME} from ${pkgRoot}`);
1209
- }
1210
- return "updated";
1211
- }
1212
- async function cleanupPluginModeManagedHooksJson(existingHooksContent, hooksPath, backupContext, summary, options) {
1213
- if (existingHooksContent === null) {
1214
- summary.unchanged += 1;
1215
- return;
1216
- }
1217
- const removed = removeManagedCodexHooks(existingHooksContent);
1218
- if (removed.removedCount === 0) {
1219
- summary.unchanged += 1;
1220
- return;
1221
- }
1222
- if (await ensureBackup(hooksPath, true, backupContext, options)) {
1223
- summary.backedUp += 1;
1224
- }
1225
- if (!options.dryRun) {
1226
- if (removed.nextContent === null) {
1227
- await rm(hooksPath, { force: true });
1228
- }
1229
- else {
1230
- await writeFile(hooksPath, removed.nextContent);
1231
- }
1232
- }
1233
- summary.removed += removed.removedCount;
1234
- if (options.verbose) {
1235
- console.log(` ${options.dryRun ? "would remove" : "removed"} ${removed.removedCount} legacy setup-managed hook wrapper(s) from ${hooksPath}`);
1236
- }
1237
- }
1238
- async function applyPluginModeHooksConfig(configPath, hooksPath, pkgRoot, codexHomeDir, backupContext, summary, options) {
1239
- const existingConfig = existsSync(configPath)
1240
- ? await readFile(configPath, "utf-8")
1241
- : "";
1242
- const managedTrustState = buildManagedCodexHookTrustState(hooksPath, pkgRoot, { platform: process.platform, codexHomeDir });
1243
- const nextConfigBase = upsertPluginModeRuntimeFeatureFlags(stripManagedCodexHookTrustState(existingConfig, { managedTrustState }), options.codexHookFeatureFlag, { pluginScopedHooks: options.pluginScopedHooks });
1244
- const nextConfig = options.pluginScopedHooks
1245
- ? nextConfigBase
1246
- : upsertManagedCodexHookTrustState(nextConfigBase, pkgRoot, hooksPath, { platform: process.platform, codexHomeDir });
1247
- if (nextConfig !== existingConfig) {
1248
- if (await ensureBackup(configPath, existsSync(configPath), backupContext, options)) {
1249
- summary.backedUp += 1;
1250
- }
1251
- if (!options.dryRun) {
1252
- await mkdir(dirname(configPath), { recursive: true });
1253
- await writeFile(configPath, nextConfig);
1254
- }
1255
- summary.updated += 1;
1256
- }
1257
- else {
1258
- summary.unchanged += 1;
1259
- }
1260
- const existingHooksContent = existsSync(hooksPath)
1261
- ? await readFile(hooksPath, "utf-8")
1262
- : null;
1263
- await migrateLegacyHooksJsonTrustStateToConfig(configPath, existingHooksContent, backupContext, summary, options);
1264
- if (options.pluginScopedHooks) {
1265
- await cleanupPluginModeManagedHooksJson(existingHooksContent, hooksPath, backupContext, summary, options);
1266
- }
1267
- else {
1268
- const hooksConfig = mergeManagedCodexHooksConfig(existingHooksContent, pkgRoot, hooksPath, { platform: process.platform, codexHomeDir });
1269
- await syncManagedContent(hooksConfig, hooksPath, summary, backupContext, options, `native hooks ${hooksPath}`);
1270
- await syncManagedWindowsNativeHookShim(codexHomeDir, pkgRoot, summary, backupContext, options);
1271
- }
1272
- if (options.verbose) {
1273
- const surface = options.pluginScopedHooks
1274
- ? "official plugin-scoped hooks"
1275
- : `legacy native hooks at ${hooksPath}`;
1276
- console.log(` ${options.dryRun ? "would configure" : "configured"} plugin-mode ${surface} and runtime feature flags`);
1277
- }
2090
+ function buildPluginModeHooksConfigPlan(existingConfig, existingHooksContent, pkgRoot, hooksPath, codexHomeDir, options) {
2091
+ const managedHookOptions = {
2092
+ platform: options.platform,
2093
+ codexHomeDir,
2094
+ };
2095
+ const managedHooksPlan = options.pluginScopedHooks
2096
+ ? existingHooksContent === null
2097
+ ? null
2098
+ : planManagedCodexHooksRemoval(existingHooksContent, hooksPath, managedHookOptions)
2099
+ : planManagedCodexHooksMerge(existingHooksContent, pkgRoot, hooksPath, managedHookOptions);
2100
+ if (managedHooksPlan && !managedHooksPlan.ok)
2101
+ throw managedHooksPlan.error;
2102
+ const managedHookTrustState = managedHooksPlan?.finalTrustState ?? {};
2103
+ const priorManagedHookTrustState = managedHooksPlan?.priorTrustState ?? {};
2104
+ const legacyHookTrustState = managedHooksPlan?.legacyTrustState ?? {};
2105
+ preflightManagedCodexHookTrustState(existingConfig, priorManagedHookTrustState, managedHookTrustState);
2106
+ const configAfterLegacyCleanup = buildPluginModeLegacyConfig(existingConfig, {
2107
+ ...options,
2108
+ managedHookTrustState,
2109
+ priorManagedHookTrustState,
2110
+ });
2111
+ const configWithRuntimeFeatures = upsertPluginModeRuntimeFeatureFlags(configAfterLegacyCleanup, options.codexHookFeatureFlag, {
2112
+ pluginScopedHooks: options.pluginScopedHooks,
2113
+ preserveNativeHooks: options.pluginScopedHooks && managedHooksPlan?.hasForeignHooks === true,
2114
+ });
2115
+ return {
2116
+ finalConfig: upsertManagedCodexHookTrustState(configWithRuntimeFeatures, pkgRoot, hooksPath, {
2117
+ ...managedHookOptions,
2118
+ managedTrustState: managedHookTrustState,
2119
+ priorManagedHookTrustState,
2120
+ legacyHookTrustState,
2121
+ }),
2122
+ hooksFinalContent: managedHooksPlan ? managedHooksPlan.finalContent : existingHooksContent,
2123
+ hooksRemovedCount: managedHooksPlan?.removedCount ?? 0,
2124
+ cleanedLegacyConfig: configAfterLegacyCleanup !== existingConfig,
2125
+ diagnostics: managedHooksPlan?.diagnostics ?? [],
2126
+ };
1278
2127
  }
1279
- async function applyPluginDeveloperInstructionsDefault(configPath, backupContext, summary, options) {
1280
- const existing = existsSync(configPath)
1281
- ? await readFile(configPath, "utf-8")
1282
- : "";
1283
- if (options.decision.action === "preserve") {
1284
- summary.skipped += 1;
1285
- if (options.verbose) {
1286
- console.log(` preserved plugin developer_instructions default: ${options.decision.reason}`);
1287
- }
1288
- return options.decision.state === "missing" ? "skipped" : "exists";
2128
+ function buildPluginDeveloperInstructionsConfigPlan(existingConfig, decision) {
2129
+ if (decision.action === "preserve") {
2130
+ return {
2131
+ finalConfig: existingConfig,
2132
+ result: decision.state === "missing" ? "skipped" : "exists",
2133
+ };
1289
2134
  }
1290
2135
  const line = `developer_instructions = ${JSON.stringify(OMX_PLUGIN_DEVELOPER_INSTRUCTIONS)}`;
1291
- const hasExistingDeveloperInstructions = rootHasTomlKey(existing, "developer_instructions");
1292
- if (hasExistingDeveloperInstructions && options.decision.action === "add") {
1293
- summary.skipped += 1;
1294
- if (options.verbose) {
1295
- console.log(" skipped plugin developer_instructions default: root developer_instructions already exists");
1296
- }
1297
- return "exists";
1298
- }
1299
- const nextConfig = hasExistingDeveloperInstructions
1300
- ? replaceRootTomlKey(existing, "developer_instructions", line)
1301
- : insertRootTomlKey(existing, line);
1302
- const destinationExists = existsSync(configPath);
1303
- if (await ensureBackup(configPath, destinationExists, backupContext, options)) {
1304
- summary.backedUp += 1;
1305
- }
1306
- if (!options.dryRun) {
1307
- await mkdir(dirname(configPath), { recursive: true });
1308
- await writeFile(configPath, nextConfig);
2136
+ const hasExistingDeveloperInstructions = rootHasTomlKey(existingConfig, "developer_instructions");
2137
+ if (hasExistingDeveloperInstructions && decision.action === "add") {
2138
+ return { finalConfig: existingConfig, result: "exists" };
1309
2139
  }
1310
- summary.updated += 1;
1311
- if (options.verbose) {
1312
- console.log(` ${options.dryRun ? "would add" : "added"} plugin developer_instructions default to ${configPath}`);
1313
- }
1314
- return "updated";
2140
+ return {
2141
+ finalConfig: hasExistingDeveloperInstructions
2142
+ ? replaceRootTomlKey(existingConfig, "developer_instructions", line)
2143
+ : insertRootTomlKey(existingConfig, line),
2144
+ result: "updated",
2145
+ };
1315
2146
  }
1316
- async function cleanupPluginModeLegacyConfig(configPath, backupContext, options) {
1317
- if (!existsSync(configPath))
1318
- return false;
1319
- const original = await readFile(configPath, "utf-8");
2147
+ function buildPluginModeLegacyConfig(original, options) {
1320
2148
  const preservedFirstPartyMcp = options.preserveFirstPartyMcp
1321
2149
  ? extractFirstPartyOmxMcpSections(original)
1322
2150
  : "";
1323
2151
  let config = original;
1324
2152
  config = stripFirstPartyOmxMcpSections(config);
1325
- config = stripExistingOmxBlocks(config).cleaned;
2153
+ config = stripExistingOmxBlocks(config, {
2154
+ managedTrustState: options.managedHookTrustState,
2155
+ priorManagedHookTrustState: options.priorManagedHookTrustState,
2156
+ }).cleaned;
1326
2157
  config = stripExistingSharedMcpRegistryBlock(config).cleaned;
1327
2158
  config = stripPluginModeLegacyRootDefaults(config, options.developerInstructionsDecision);
1328
2159
  config = stripOmxSeededBehavioralDefaults(config);
1329
2160
  config = stripOmxFeatureFlags(config, { preserveMultiAgent: true });
1330
- config = stripManagedCodexHookTrustState(config);
2161
+ config = stripManagedCodexHookTrustState(config, {
2162
+ managedTrustState: options.managedHookTrustState,
2163
+ priorManagedHookTrustState: options.priorManagedHookTrustState,
2164
+ });
1331
2165
  config = stripOmxEnvSettings(config);
1332
2166
  if (preservedFirstPartyMcp) {
1333
2167
  config = `${config.trimEnd()}\n\n${preservedFirstPartyMcp}\n`;
1334
2168
  }
1335
2169
  config = config.trim();
1336
- const nextConfig = config.length > 0 ? `${config}\n` : "";
1337
- if (nextConfig === original)
1338
- return false;
1339
- if (await ensureBackup(configPath, true, backupContext, options)) {
1340
- // backup created for pre-existing config
1341
- }
1342
- if (!options.dryRun) {
1343
- if (nextConfig.length === 0) {
1344
- await rm(configPath, { force: true });
2170
+ return config.length > 0 ? `${config}\n` : "";
2171
+ }
2172
+ async function planNativeHookSetupTransaction(options) {
2173
+ const existingConfigSnapshot = options.configSnapshot;
2174
+ const existingHooksSnapshot = await captureNativeHookTransactionArtifact(options.hooksPath, `native hooks ${options.hooksPath}`);
2175
+ const existingConfig = existingConfigSnapshot.bytes
2176
+ ? decodeNativeHookTransactionUtf8(existingConfigSnapshot.bytes, `config ${options.configPath}`)
2177
+ : "";
2178
+ const existingHooksContent = existingHooksSnapshot.bytes
2179
+ ? decodeNativeHookTransactionUtf8(existingHooksSnapshot.bytes, `native hooks ${options.hooksPath}`)
2180
+ : null;
2181
+ let finalConfig = existingConfig;
2182
+ let finalHooksContent = existingHooksContent;
2183
+ let hooksRemovedCount = 0;
2184
+ let cleanedLegacyConfig = false;
2185
+ let pluginMarketplaceResult = "unchanged";
2186
+ let pluginDeveloperInstructionsResult = "skipped";
2187
+ let diagnostics = [];
2188
+ let notifyMetadataArtifact = null;
2189
+ let notifyMetadataPrecondition = null;
2190
+ let modelUpgrade;
2191
+ let repairedLegacyTeamRunTable = false;
2192
+ if (options.isPluginInstallMode) {
2193
+ const pluginPlan = buildPluginModeHooksConfigPlan(existingConfig, existingHooksContent, options.pkgRoot, options.hooksPath, options.codexHomeDir, {
2194
+ codexHookFeatureFlag: options.codexHookFeatureFlag,
2195
+ pluginScopedHooks: options.pluginScopedHooks,
2196
+ preserveFirstPartyMcp: options.preserveFirstPartyMcp,
2197
+ developerInstructionsDecision: options.pluginDeveloperInstructionsDecision,
2198
+ platform: options.platform,
2199
+ });
2200
+ finalConfig = pluginPlan.finalConfig;
2201
+ finalHooksContent = pluginPlan.hooksFinalContent;
2202
+ hooksRemovedCount = pluginPlan.hooksRemovedCount;
2203
+ cleanedLegacyConfig = pluginPlan.cleanedLegacyConfig;
2204
+ diagnostics = pluginPlan.diagnostics;
2205
+ if (options.pluginMarketplaceAvailable) {
2206
+ const withMarketplace = upsertLocalOmxMarketplaceRegistration(upsertLocalOmxPluginMcpServerEnablement(upsertLocalOmxPluginEnablement(finalConfig), options.mcpMode === "compat", { removeWhenDisabled: options.removeFirstPartyMcp }), options.pkgRoot);
2207
+ pluginMarketplaceResult =
2208
+ withMarketplace === finalConfig ? "unchanged" : "updated";
2209
+ finalConfig = withMarketplace;
1345
2210
  }
1346
2211
  else {
1347
- await writeFile(configPath, nextConfig);
2212
+ pluginMarketplaceResult = "unavailable";
1348
2213
  }
2214
+ const developerInstructionsPlan = buildPluginDeveloperInstructionsConfigPlan(finalConfig, options.pluginDeveloperInstructionsDecision);
2215
+ finalConfig = developerInstructionsPlan.finalConfig;
2216
+ pluginDeveloperInstructionsResult = developerInstructionsPlan.result;
1349
2217
  }
1350
- if (options.verbose) {
1351
- console.log(` ${options.dryRun ? "would clean" : nextConfig.length === 0 ? "removed" : "cleaned"} legacy OMX config ${basename(configPath)}`);
2218
+ else {
2219
+ const managedHooksPlan = planManagedCodexHooksMerge(existingHooksContent, options.pkgRoot, options.hooksPath, {
2220
+ platform: options.platform,
2221
+ codexHomeDir: options.codexHomeDir,
2222
+ });
2223
+ if (!managedHooksPlan.ok)
2224
+ throw managedHooksPlan.error;
2225
+ if (managedHooksPlan.finalContent === null) {
2226
+ throw new Error("Native hook merge unexpectedly produced no hooks.json content.");
2227
+ }
2228
+ finalHooksContent = managedHooksPlan.finalContent;
2229
+ hooksRemovedCount = managedHooksPlan.removedCount;
2230
+ diagnostics = managedHooksPlan.diagnostics;
2231
+ const managedConfigPlan = await planManagedConfig(options.hooksPath, options.pkgRoot, options.sharedMcpRegistry, options.mcpMode, options.preserveFirstPartyMcp, options.resolvedScope, options.codexHomeDir, {
2232
+ modelUpgradePrompt: options.modelUpgradePrompt,
2233
+ existingConfig,
2234
+ verbose: false,
2235
+ statusLinePreset: options.statusLinePreset,
2236
+ forceStatusLinePreset: options.forceStatusLinePreset,
2237
+ codexHookFeatureFlag: options.codexHookFeatureFlag,
2238
+ managedHookTrustState: managedHooksPlan.finalTrustState,
2239
+ priorManagedHookTrustState: managedHooksPlan.priorTrustState,
2240
+ legacyHookTrustState: managedHooksPlan.legacyTrustState,
2241
+ hookCommandPlatform: options.platform,
2242
+ notifyMetadataSnapshot: options.notifyMetadataSnapshot,
2243
+ });
2244
+ finalConfig = managedConfigPlan.finalConfig;
2245
+ if (managedConfigPlan.currentModel &&
2246
+ managedConfigPlan.modelOverride &&
2247
+ managedConfigPlan.currentModel !== managedConfigPlan.modelOverride) {
2248
+ modelUpgrade = {
2249
+ currentModel: managedConfigPlan.currentModel,
2250
+ modelOverride: managedConfigPlan.modelOverride,
2251
+ };
2252
+ }
2253
+ repairedLegacyTeamRunTable = managedConfigPlan.repairedLegacyTeamRunTable;
2254
+ const notifyPlan = managedConfigPlan.notifyPlan;
2255
+ if (notifyPlan.metadataSnapshot && notifyPlan.metadataPath) {
2256
+ notifyMetadataPrecondition = nativeHookTransactionPrecondition("metadata", notifyPlan.metadataPath, `notification metadata ${notifyPlan.metadataPath}`, notifyPlan.metadataSnapshot);
2257
+ }
2258
+ if (notifyPlan.metadataPath && notifyPlan.metadata) {
2259
+ const metadataBefore = options.notifyMetadataSnapshot;
2260
+ if (!metadataBefore) {
2261
+ throw new Error(`Missing notification metadata snapshot for ${notifyPlan.metadataPath}; refusing to plan from a newly captured baseline.`);
2262
+ }
2263
+ notifyMetadataArtifact = nativeHookTransactionArtifact("metadata", notifyPlan.metadataPath, `notification metadata ${notifyPlan.metadataPath}`, metadataBefore, Buffer.from(JSON.stringify(notifyPlan.metadata, null, 2) + "\n", "utf-8"));
2264
+ if (!notifyMetadataPrecondition) {
2265
+ notifyMetadataPrecondition = nativeHookTransactionPrecondition("metadata", notifyPlan.metadataPath, `notification metadata ${notifyPlan.metadataPath}`, metadataBefore);
2266
+ }
2267
+ }
1352
2268
  }
1353
- return true;
2269
+ if (options.isPluginInstallMode && options.sharedMcpRegistry.servers.length > 0) {
2270
+ finalConfig = mergeSharedMcpRegistryBlock(finalConfig, options.sharedMcpRegistry.servers, options.sharedMcpRegistry.sourcePath);
2271
+ }
2272
+ try {
2273
+ TOML.parse(finalConfig);
2274
+ }
2275
+ catch (error) {
2276
+ throw new Error(`Refusing to write invalid planned config.toml: ${error instanceof Error ? error.message : String(error)}`);
2277
+ }
2278
+ const hookArtifact = nativeHookTransactionArtifact("hooks", options.hooksPath, `native hooks ${options.hooksPath}`, existingHooksSnapshot, finalHooksContent === null ? null : Buffer.from(finalHooksContent, "utf-8"), options.platform);
2279
+ const configArtifact = nativeHookTransactionArtifact("config", options.configPath, `config ${options.configPath}`, existingConfigSnapshot, Buffer.from(finalConfig, "utf-8"));
2280
+ let shimArtifact = null;
2281
+ let shimPrecondition = null;
2282
+ if (options.platform === "win32") {
2283
+ const shimPath = buildManagedCodexNativeHookWindowsShimPath(options.codexHomeDir);
2284
+ const shimSnapshot = await captureNativeHookTransactionArtifact(shimPath, `native hook Windows shim ${shimPath}`);
2285
+ const shimAfter = Buffer.from(buildManagedCodexNativeHookWindowsShimContent(options.pkgRoot), "utf-8");
2286
+ assertWindowsNativeHookShimOwnership(shimPath, shimSnapshot.bytes, shimAfter);
2287
+ const shimReference = decideWindowsNativeHookShimReference(finalHooksContent, shimPath);
2288
+ shimArtifact = nativeHookTransactionArtifact("shim", shimPath, `native hook Windows shim ${shimPath}`, shimSnapshot, options.isPluginInstallMode && options.pluginScopedHooks &&
2289
+ shimReference === "not_referenced"
2290
+ ? null
2291
+ : shimAfter);
2292
+ shimPrecondition = nativeHookTransactionPrecondition("shim", shimPath, `native hook Windows shim ${shimPath}`, shimSnapshot);
2293
+ }
2294
+ const windowsShimCreateOrUpdateArtifact = shimArtifact && shimArtifact.after !== null ? shimArtifact : null;
2295
+ const windowsShimDeletionArtifact = shimArtifact && shimArtifact.after === null ? shimArtifact : null;
2296
+ const artifacts = options.platform === "win32"
2297
+ ? windowsShimDeletionArtifact
2298
+ ? [
2299
+ hookArtifact,
2300
+ windowsShimDeletionArtifact,
2301
+ notifyMetadataArtifact,
2302
+ configArtifact,
2303
+ ]
2304
+ : [
2305
+ windowsShimCreateOrUpdateArtifact,
2306
+ hookArtifact,
2307
+ notifyMetadataArtifact,
2308
+ configArtifact,
2309
+ ]
2310
+ : [hookArtifact, notifyMetadataArtifact, configArtifact];
2311
+ const preconditions = [
2312
+ shimPrecondition,
2313
+ nativeHookTransactionPrecondition("hooks", options.hooksPath, `native hooks ${options.hooksPath}`, existingHooksSnapshot),
2314
+ nativeHookTransactionPrecondition("config", options.configPath, `config ${options.configPath}`, existingConfigSnapshot),
2315
+ notifyMetadataPrecondition,
2316
+ ];
2317
+ return {
2318
+ artifacts: artifacts.filter((artifact) => artifact !== null),
2319
+ preconditions: preconditions.filter((precondition) => precondition !== null),
2320
+ finalConfig,
2321
+ hooksRemovedCount,
2322
+ pluginScopedHooks: options.pluginScopedHooks,
2323
+ cleanedLegacyConfig,
2324
+ pluginMarketplaceResult,
2325
+ pluginDeveloperInstructionsResult,
2326
+ diagnostics,
2327
+ modelUpgrade,
2328
+ repairedLegacyTeamRunTable,
2329
+ };
1354
2330
  }
1355
2331
  export async function setup(options = {}) {
1356
2332
  const { force = false, dryRun = false, installMode: requestedInstallMode, mcpMode: requestedMcpMode, teamMode: requestedTeamMode, scope: requestedScope, verbose = false, skipNativeAgentRefresh: requestedSkipNativeAgentRefresh = false, setupScopePrompt, persistedSetupReviewPrompt, installModePrompt, modelUpgradePrompt, pluginAgentsMdPrompt, pluginDeveloperInstructionsPrompt, firstPartyMcpRemovalPrompt, } = options;
@@ -1372,8 +2348,15 @@ export async function setup(options = {}) {
1372
2348
  Boolean(persistedPreferences?.teamMode) &&
1373
2349
  (!persistedPreferences?.scope ||
1374
2350
  persistedPreferences.scope === effectiveScopeForInstallMode);
2351
+ const wouldUsePersistedMergeAgents = !options.mergeAgentsPolicy &&
2352
+ typeof options.mergeAgents !== "boolean" &&
2353
+ resolvePersistedSetupMergeAgents(persistedPreferences, effectiveScopeForInstallMode) !== undefined;
1375
2354
  const shouldReviewPersistedSetup = hasPersistedSetupPreferences(persistedPreferences) &&
1376
- (wouldUsePersistedScope || wouldUsePersistedInstallMode || wouldUsePersistedMcpMode || wouldUsePersistedTeamMode) &&
2355
+ (wouldUsePersistedScope ||
2356
+ wouldUsePersistedInstallMode ||
2357
+ wouldUsePersistedMcpMode ||
2358
+ wouldUsePersistedTeamMode ||
2359
+ wouldUsePersistedMergeAgents) &&
1377
2360
  (typeof persistedSetupReviewPrompt === "function" ||
1378
2361
  (process.stdin.isTTY && process.stdout.isTTY));
1379
2362
  if (shouldReviewPersistedSetup) {
@@ -1383,6 +2366,18 @@ export async function setup(options = {}) {
1383
2366
  console.log(`Setup preference review: ${persistedReviewDecision} (${formatPersistedSetupPreferenceSummary(persistedPreferences)})\n`);
1384
2367
  }
1385
2368
  const resolvedScope = await resolveSetupScope(projectRoot, requestedScope, persistedReviewDecision, persistedPreferences, setupScopePrompt);
2369
+ const requestedMergeAgentsPolicy = options.mergeAgentsPolicy ??
2370
+ (typeof options.mergeAgents === "boolean"
2371
+ ? { kind: "set", value: options.mergeAgents }
2372
+ : undefined);
2373
+ const inheritedMergeAgents = persistedReviewDecision === "reset"
2374
+ ? undefined
2375
+ : resolvePersistedSetupMergeAgents(persistedPreferences, resolvedScope.scope);
2376
+ const effectiveMergeAgents = requestedMergeAgentsPolicy?.kind === "set"
2377
+ ? requestedMergeAgentsPolicy.value
2378
+ : requestedMergeAgentsPolicy?.kind === "clear"
2379
+ ? undefined
2380
+ : inheritedMergeAgents;
1386
2381
  const resolvedInstallMode = await resolveSetupInstallMode(projectRoot, resolvedScope.scope, requestedInstallMode, installModePrompt, persistedReviewDecision, persistedPreferences);
1387
2382
  const resolvedMcpMode = resolveSetupMcpMode(resolvedScope.scope, requestedMcpMode, persistedReviewDecision, persistedPreferences);
1388
2383
  const resolvedTeamMode = requestedTeamMode
@@ -1395,9 +2390,27 @@ export async function setup(options = {}) {
1395
2390
  const skipNativeAgentRefresh = requestedSkipNativeAgentRefresh ||
1396
2391
  process.env[SKIP_NATIVE_AGENT_REFRESH_ENV] === "1";
1397
2392
  const scopeDirs = resolveScopeDirectories(resolvedScope.scope, projectRoot);
1398
- const existingConfigForMcpMigration = existsSync(scopeDirs.codexConfigFile)
1399
- ? await readFile(scopeDirs.codexConfigFile, "utf-8")
2393
+ if (!dryRun) {
2394
+ await recoverNativeHookClaimJournal(scopeDirs.codexHomeDir);
2395
+ }
2396
+ const nativeHookTransactionPlatform = nativeHookPlatform();
2397
+ let nativeHookTransactionAncestorPrecondition = await captureNativeHookTransactionAncestorPrecondition(scopeDirs.codexHomeDir, [
2398
+ scopeDirs.codexConfigFile,
2399
+ scopeDirs.codexHooksFile,
2400
+ getNotifyMetadataPath(scopeDirs.codexHomeDir),
2401
+ ...(nativeHookTransactionPlatform === "win32"
2402
+ ? [
2403
+ buildManagedCodexNativeHookWindowsShimPath(scopeDirs.codexHomeDir),
2404
+ ]
2405
+ : []),
2406
+ ]);
2407
+ const existingConfigForMcpMigrationSnapshot = await captureNativeHookTransactionArtifact(scopeDirs.codexConfigFile, `config ${scopeDirs.codexConfigFile}`);
2408
+ const existingConfigForMcpMigration = existingConfigForMcpMigrationSnapshot.bytes
2409
+ ? decodeNativeHookTransactionUtf8(existingConfigForMcpMigrationSnapshot.bytes, `config ${scopeDirs.codexConfigFile}`)
1400
2410
  : "";
2411
+ const notifyMetadataSnapshot = configRequiresNotificationMetadataSnapshot(existingConfigForMcpMigration, pkgRoot, resolvedScope.scope)
2412
+ ? await captureNativeHookTransactionArtifact(getNotifyMetadataPath(scopeDirs.codexHomeDir), `notification metadata ${getNotifyMetadataPath(scopeDirs.codexHomeDir)}`)
2413
+ : undefined;
1401
2414
  const firstPartyMcpRegistrationKinds = [
1402
2415
  hasFirstPartyOmxMcpRegistrations(existingConfigForMcpMigration)
1403
2416
  ? "config.toml [mcp_servers.omx_*]"
@@ -1425,7 +2438,7 @@ export async function setup(options = {}) {
1425
2438
  ? join(projectRoot, "AGENTS.md")
1426
2439
  : join(scopeDirs.codexHomeDir, "AGENTS.md");
1427
2440
  const pluginDeveloperInstructionsDecision = isPluginInstallMode
1428
- ? await resolvePluginDeveloperInstructionsDecision(scopeDirs.codexConfigFile, { pluginDeveloperInstructionsPrompt })
2441
+ ? await resolvePluginDeveloperInstructionsDecision(existingConfigForMcpMigration, scopeDirs.codexConfigFile, { pluginDeveloperInstructionsPrompt })
1429
2442
  : {
1430
2443
  action: "preserve",
1431
2444
  state: "custom",
@@ -1443,7 +2456,7 @@ export async function setup(options = {}) {
1443
2456
  pluginAgentsMdIsSymlink = false;
1444
2457
  }
1445
2458
  const usePluginAgentsMdDefault = isPluginInstallMode
1446
- ? options.mergeAgents || pluginAgentsMdIsSymlink
2459
+ ? effectiveMergeAgents || pluginAgentsMdIsSymlink
1447
2460
  ? false
1448
2461
  : force
1449
2462
  ? true
@@ -1451,6 +2464,55 @@ export async function setup(options = {}) {
1451
2464
  ? await pluginAgentsMdPrompt(pluginAgentsMdDst)
1452
2465
  : await promptForPluginAgentsMdDefault(pluginAgentsMdDst)
1453
2466
  : false;
2467
+ const codexHookFeatureSupport = resolveCodexHookFeatureSupportForCli({
2468
+ codexFeaturesProbe: options.codexFeaturesProbe,
2469
+ codexVersionProbe: options.codexVersionProbe,
2470
+ });
2471
+ const codexHookFeatureFlag = codexHookFeatureSupport.hookFeatureFlag;
2472
+ const pluginScopedHooksSupported = codexHookFeatureSupport.pluginScopedHooks;
2473
+ const shouldSyncSharedMcpRegistry = resolvedMcpMode.mcpMode === "compat";
2474
+ const registryCandidates = getUnifiedMcpRegistryCandidates();
2475
+ const defaultRegistryCandidates = registryCandidates.slice(0, 1);
2476
+ const sharedMcpRegistry = shouldSyncSharedMcpRegistry
2477
+ ? await loadUnifiedMcpRegistry({
2478
+ candidates: options.mcpRegistryCandidates ?? defaultRegistryCandidates,
2479
+ })
2480
+ : { servers: [], warnings: [] };
2481
+ const legacyRegistryCandidate = getLegacyUnifiedMcpRegistryCandidate();
2482
+ const preflightMarketplace = isPluginInstallMode
2483
+ ? await resolvePackagedOmxMarketplace(pkgRoot)
2484
+ : null;
2485
+ const statusLinePreset = isPluginInstallMode
2486
+ ? undefined
2487
+ : await resolveStatusLinePresetForSetup(projectRoot, { force });
2488
+ const nativeHookSetupTransaction = await planNativeHookSetupTransaction({
2489
+ configPath: scopeDirs.codexConfigFile,
2490
+ hooksPath: scopeDirs.codexHooksFile,
2491
+ codexHomeDir: scopeDirs.codexHomeDir,
2492
+ pkgRoot,
2493
+ platform: nativeHookTransactionPlatform,
2494
+ isPluginInstallMode,
2495
+ pluginScopedHooks: pluginScopedHooksSupported,
2496
+ codexHookFeatureFlag,
2497
+ preserveFirstPartyMcp: shouldOfferFirstPartyMcpRemoval && !removeFirstPartyMcpRegistrations,
2498
+ removeFirstPartyMcp: removeFirstPartyMcpRegistrations,
2499
+ pluginDeveloperInstructionsDecision,
2500
+ pluginMarketplaceAvailable: preflightMarketplace !== null,
2501
+ sharedMcpRegistry,
2502
+ mcpMode: resolvedMcpMode.mcpMode,
2503
+ resolvedScope: resolvedScope.scope,
2504
+ modelUpgradePrompt,
2505
+ statusLinePreset,
2506
+ forceStatusLinePreset: force,
2507
+ configSnapshot: existingConfigForMcpMigrationSnapshot,
2508
+ notifyMetadataSnapshot,
2509
+ });
2510
+ const summary = createEmptyRunSummary();
2511
+ for (const precondition of nativeHookSetupTransaction.preconditions) {
2512
+ injectNativeHookTransactionFailure("before_precondition", precondition);
2513
+ await assertNativeHookTransactionPrecondition(precondition);
2514
+ }
2515
+ await assertNativeHookTransactionAncestorPrecondition(nativeHookTransactionAncestorPrecondition);
1454
2516
  console.log("oh-my-codex setup");
1455
2517
  console.log("=================\n");
1456
2518
  console.log(`Using setup scope: ${resolvedScope.scope}${scopeSourceMessage}\n`);
@@ -1510,11 +2572,8 @@ export async function setup(options = {}) {
1510
2572
  resolvedInstallMode.installMode === "plugin")
1511
2573
  ? { installMode: resolvedInstallMode.installMode }
1512
2574
  : {}),
2575
+ ...(effectiveMergeAgents !== undefined ? { mergeAgents: effectiveMergeAgents } : {}),
1513
2576
  };
1514
- await persistSetupPreferences(projectRoot, setupPreferencesToPersist, {
1515
- dryRun,
1516
- verbose,
1517
- });
1518
2577
  console.log(" Done.\n");
1519
2578
  if (resolvedScope.scope === "project") {
1520
2579
  const gitignoreResult = await ensureProjectGitignore(projectRoot, backupContext, { dryRun, verbose });
@@ -1526,7 +2585,6 @@ export async function setup(options = {}) {
1526
2585
  }
1527
2586
  }
1528
2587
  const catalogCounts = getCatalogHeadlineCounts();
1529
- const summary = createEmptyRunSummary();
1530
2588
  // Step 2: Install agent prompts
1531
2589
  console.log("[2/8] Installing agent prompts...");
1532
2590
  {
@@ -1624,27 +2682,12 @@ export async function setup(options = {}) {
1624
2682
  }
1625
2683
  // Step 5: Update config.toml
1626
2684
  console.log("[5/8] Updating config.toml...");
1627
- let resolvedConfig = "";
1628
- let omxManagesTui = false;
1629
- const codexHookFeatureSupport = resolveCodexHookFeatureSupportForCli({
1630
- codexFeaturesProbe: options.codexFeaturesProbe,
1631
- codexVersionProbe: options.codexVersionProbe,
1632
- });
1633
- const codexHookFeatureFlag = codexHookFeatureSupport.hookFeatureFlag;
1634
- const pluginScopedHooksSupported = codexHookFeatureSupport.pluginScopedHooks;
2685
+ const resolvedConfig = nativeHookSetupTransaction.finalConfig;
2686
+ const omxManagesTui = !isPluginInstallMode;
1635
2687
  if (verbose) {
1636
2688
  console.log(` Native Codex hook feature flag: [features].${codexHookFeatureFlag}`);
1637
2689
  console.log(` Plugin-scoped Codex hooks: ${pluginScopedHooksSupported ? "supported" : "not reported; using legacy setup fallback"}`);
1638
2690
  }
1639
- const shouldSyncSharedMcpRegistry = resolvedMcpMode.mcpMode === "compat";
1640
- const registryCandidates = getUnifiedMcpRegistryCandidates();
1641
- const defaultRegistryCandidates = registryCandidates.slice(0, 1);
1642
- const sharedMcpRegistry = shouldSyncSharedMcpRegistry
1643
- ? await loadUnifiedMcpRegistry({
1644
- candidates: options.mcpRegistryCandidates ?? defaultRegistryCandidates,
1645
- })
1646
- : { servers: [], warnings: [] };
1647
- const legacyRegistryCandidate = getLegacyUnifiedMcpRegistryCandidate();
1648
2691
  if (shouldSyncSharedMcpRegistry &&
1649
2692
  !options.mcpRegistryCandidates &&
1650
2693
  !sharedMcpRegistry.sourcePath &&
@@ -1658,36 +2701,51 @@ export async function setup(options = {}) {
1658
2701
  for (const warning of sharedMcpRegistry.warnings) {
1659
2702
  console.log(` warning: ${warning}`);
1660
2703
  }
2704
+ logManagedCodexHooksPlanDiagnostics(nativeHookSetupTransaction.diagnostics, {
2705
+ verbose,
2706
+ });
2707
+ const changedHookArtifact = nativeHookSetupTransaction.artifacts.some((artifact) => artifact.kind === "hooks");
2708
+ const changedShimArtifact = nativeHookSetupTransaction.artifacts.some((artifact) => artifact.kind === "shim");
2709
+ const changedConfigArtifact = nativeHookSetupTransaction.artifacts.some((artifact) => artifact.kind === "config");
2710
+ if (changedHookArtifact) {
2711
+ if (isPluginInstallMode &&
2712
+ nativeHookSetupTransaction.pluginScopedHooks &&
2713
+ nativeHookSetupTransaction.hooksRemovedCount > 0) {
2714
+ summary.config.removed += nativeHookSetupTransaction.hooksRemovedCount;
2715
+ }
2716
+ else {
2717
+ summary.config.updated += 1;
2718
+ }
2719
+ }
2720
+ else if (!isPluginInstallMode || !nativeHookSetupTransaction.pluginScopedHooks) {
2721
+ summary.config.unchanged += 1;
2722
+ }
2723
+ if (changedShimArtifact)
2724
+ summary.config.updated += 1;
2725
+ if (changedConfigArtifact)
2726
+ summary.config.updated += 1;
2727
+ else
2728
+ summary.config.unchanged += 1;
2729
+ if (verbose && nativeHookSetupTransaction.modelUpgrade) {
2730
+ console.log(` ${dryRun ? "would update" : "updated"} root model from ${nativeHookSetupTransaction.modelUpgrade.currentModel} to ${nativeHookSetupTransaction.modelUpgrade.modelOverride}`);
2731
+ }
1661
2732
  if (isPluginInstallMode) {
1662
- const configCleaned = await cleanupPluginModeLegacyConfig(scopeDirs.codexConfigFile, backupContext, {
1663
- dryRun,
1664
- verbose,
1665
- preserveFirstPartyMcp: shouldOfferFirstPartyMcpRemoval &&
1666
- !removeFirstPartyMcpRegistrations,
1667
- developerInstructionsDecision: pluginDeveloperInstructionsDecision,
1668
- });
1669
- if (configCleaned)
2733
+ if (nativeHookSetupTransaction.cleanedLegacyConfig) {
1670
2734
  summary.config.removed += 1;
1671
- console.log(configCleaned
1672
- ? ` ${dryRun ? "Would clean" : "Cleaned"} legacy OMX config entries for plugin mode.\n`
1673
- : " Config refresh skipped; no legacy OMX config entries found.\n");
1674
- await applyPluginModeHooksConfig(scopeDirs.codexConfigFile, scopeDirs.codexHooksFile, pkgRoot, scopeDirs.codexHomeDir, backupContext, summary.config, {
1675
- dryRun,
1676
- verbose,
1677
- codexHookFeatureFlag,
1678
- pluginScopedHooks: pluginScopedHooksSupported,
1679
- });
1680
- const pluginMarketplaceResult = await ensurePluginMarketplaceRegistration(scopeDirs.codexConfigFile, pkgRoot, resolvedMcpMode.mcpMode, removeFirstPartyMcpRegistrations, backupContext, summary.config, { dryRun, verbose });
1681
- if (pluginMarketplaceResult === "unavailable") {
2735
+ console.log(` ${dryRun ? "Would clean" : "Cleaned"} legacy OMX config entries for plugin mode.\n`);
2736
+ }
2737
+ else {
2738
+ console.log(" Config refresh skipped; no legacy OMX config entries found.\n");
2739
+ }
2740
+ if (nativeHookSetupTransaction.pluginMarketplaceResult === "unavailable") {
1682
2741
  console.log(` warning: packaged ${OMX_LOCAL_MARKETPLACE_NAME} Codex plugin marketplace metadata not found; /skills plugin discovery was not registered.`);
1683
2742
  }
1684
- else if (pluginMarketplaceResult === "updated") {
2743
+ else if (nativeHookSetupTransaction.pluginMarketplaceResult === "updated") {
1685
2744
  console.log(` ${dryRun ? "Would register" : "Registered"} local Codex plugin marketplace ${OMX_LOCAL_MARKETPLACE_NAME} (${pkgRoot}).`);
1686
2745
  }
1687
2746
  else {
1688
2747
  console.log(` Local Codex plugin marketplace ${OMX_LOCAL_MARKETPLACE_NAME} already registered (${pkgRoot}).`);
1689
2748
  }
1690
- const packagedMarketplace = await resolvePackagedOmxMarketplace(pkgRoot);
1691
2749
  const pluginCacheRefresh = await refreshOmxPluginDiscoveryCache(pkgRoot, {
1692
2750
  dryRun,
1693
2751
  verbose,
@@ -1700,38 +2758,25 @@ export async function setup(options = {}) {
1700
2758
  else if (pluginCacheRefresh.status === "unchanged") {
1701
2759
  console.log(" Codex plugin discovery cache already matches packaged plugin metadata.");
1702
2760
  }
1703
- const pluginCacheMaterialize = await materializePackagedOmxPluginCache(scopeDirs.codexHomeDir, packagedMarketplace, { dryRun, teamMode: resolvedTeamMode });
2761
+ const pluginCacheMaterialize = await materializePackagedOmxPluginCache(scopeDirs.codexHomeDir, preflightMarketplace, { dryRun, teamMode: resolvedTeamMode });
1704
2762
  if (pluginCacheMaterialize.status === "materialized") {
1705
2763
  console.log(` ${dryRun ? "Would install" : "Installed"} local Codex plugin cache for ${OMX_LOCAL_MARKETPLACE_NAME}/${OMX_PLUGIN_NAME} at ${pluginCacheMaterialize.cacheDir}.`);
1706
2764
  }
1707
2765
  else if (pluginCacheMaterialize.status === "unchanged") {
1708
2766
  console.log(" Local Codex plugin cache already exposes packaged OMX skills.");
1709
2767
  }
1710
- if (pluginCacheMaterialize.status === "materialized" || pluginCacheMaterialize.status === "unchanged") {
2768
+ if (pluginCacheMaterialize.status === "materialized" ||
2769
+ pluginCacheMaterialize.status === "unchanged") {
1711
2770
  console.log(" Start a new Codex session if /skills still shows stale OMX plugin skill metadata; the current session may keep its in-memory plugin registry until restart.");
1712
2771
  }
1713
- if (shouldSyncSharedMcpRegistry) {
1714
- resolvedConfig = await syncSharedMcpRegistryIntoConfig(scopeDirs.codexConfigFile, sharedMcpRegistry, summary.config, backupContext, { dryRun, verbose });
1715
- if (resolvedScope.scope === "user") {
1716
- await syncClaudeCodeMcpSettings(sharedMcpRegistry, summary.config, backupContext, { dryRun, verbose });
1717
- }
2772
+ if (shouldSyncSharedMcpRegistry && resolvedScope.scope === "user") {
2773
+ await syncClaudeCodeMcpSettings(sharedMcpRegistry, summary.config, backupContext, { dryRun, verbose });
1718
2774
  }
1719
- resolvedConfig = existsSync(scopeDirs.codexConfigFile)
1720
- ? await readFile(scopeDirs.codexConfigFile, "utf-8")
1721
- : "";
1722
2775
  console.log(pluginScopedHooksSupported
1723
2776
  ? " Plugin-scoped Codex hooks and runtime feature flags refresh complete (plugin_hooks, goals).\n"
1724
2777
  : ` Native Codex hooks fallback and runtime feature flags refresh complete (${scopeDirs.codexHooksFile}; hooks, goals).\n`);
1725
2778
  if (pluginDeveloperInstructionsDecision.action !== "preserve") {
1726
- const developerInstructionsResult = await applyPluginDeveloperInstructionsDefault(scopeDirs.codexConfigFile, backupContext, summary.config, {
1727
- dryRun,
1728
- verbose,
1729
- decision: pluginDeveloperInstructionsDecision,
1730
- });
1731
- if (developerInstructionsResult === "updated") {
1732
- resolvedConfig = existsSync(scopeDirs.codexConfigFile)
1733
- ? await readFile(scopeDirs.codexConfigFile, "utf-8")
1734
- : "";
2779
+ if (nativeHookSetupTransaction.pluginDeveloperInstructionsResult === "updated") {
1735
2780
  console.log(` ${dryRun ? "Would add" : "Added"} plugin-mode developer_instructions default (${scopeDirs.codexConfigFile}).\n`);
1736
2781
  }
1737
2782
  else {
@@ -1743,31 +2788,13 @@ export async function setup(options = {}) {
1743
2788
  }
1744
2789
  }
1745
2790
  else {
1746
- const statusLinePreset = await resolveStatusLinePresetForSetup(projectRoot, { force });
1747
- const managedConfig = await updateManagedConfig(scopeDirs.codexConfigFile, scopeDirs.codexHooksFile, pkgRoot, sharedMcpRegistry, resolvedMcpMode.mcpMode, shouldOfferFirstPartyMcpRemoval && !removeFirstPartyMcpRegistrations, resolvedScope.scope, scopeDirs.codexHomeDir, summary.config, backupContext, {
1748
- dryRun,
1749
- modelUpgradePrompt,
1750
- verbose,
1751
- statusLinePreset,
1752
- forceStatusLinePreset: force,
1753
- codexHookFeatureFlag,
1754
- });
1755
- resolvedConfig = managedConfig.finalConfig;
1756
- omxManagesTui = managedConfig.omxManagesTui;
1757
- if (managedConfig.repairedLegacyTeamRunTable) {
1758
- console.log(" Removed retired [mcp_servers.omx_team_run] config during refresh.");
1759
- }
1760
2791
  if (shouldSyncSharedMcpRegistry && resolvedScope.scope === "user") {
1761
2792
  await syncClaudeCodeMcpSettings(sharedMcpRegistry, summary.config, backupContext, { dryRun, verbose });
1762
2793
  }
2794
+ if (nativeHookSetupTransaction.repairedLegacyTeamRunTable) {
2795
+ console.log(" Removed retired [mcp_servers.omx_team_run] config during refresh.");
2796
+ }
1763
2797
  console.log(` Config refresh complete (${scopeDirs.codexConfigFile}).\n`);
1764
- const existingHooksContent = existsSync(scopeDirs.codexHooksFile)
1765
- ? await readFile(scopeDirs.codexHooksFile, "utf-8")
1766
- : null;
1767
- await migrateLegacyHooksJsonTrustStateToConfig(scopeDirs.codexConfigFile, existingHooksContent, backupContext, summary.config, { dryRun, verbose });
1768
- const hooksConfig = mergeManagedCodexHooksConfig(existingHooksContent, pkgRoot, scopeDirs.codexHooksFile, { platform: process.platform, codexHomeDir: scopeDirs.codexHomeDir });
1769
- await syncManagedContent(hooksConfig, scopeDirs.codexHooksFile, summary.config, backupContext, { dryRun, verbose }, `native hooks ${scopeDirs.codexHooksFile}`);
1770
- await syncManagedWindowsNativeHookShim(scopeDirs.codexHomeDir, pkgRoot, summary.config, backupContext, { dryRun, verbose });
1771
2798
  console.log(` Native Codex hooks refresh complete (${scopeDirs.codexHooksFile}).\n`);
1772
2799
  }
1773
2800
  // Step 5.5: Verify team CLI interop surface is available when Team is enabled.
@@ -1802,7 +2829,7 @@ export async function setup(options = {}) {
1802
2829
  });
1803
2830
  const modelTableDefinitions = getAgentsModelTableDefinitionsForTeamMode(resolvedTeamMode);
1804
2831
  const rewritten = upsertAgentsModelTable(addGeneratedAgentsMarker(applyTeamModeToAgentsTemplate(applyPluginModeWordingToAgentsTemplate(content, resolvedScope.scope), resolvedTeamMode)), modelTableContext, modelTableDefinitions, { codexHomeOverride: scopeDirs.codexHomeDir });
1805
- if (options.mergeAgents && pluginAgentsMdExists) {
2832
+ if (effectiveMergeAgents && pluginAgentsMdExists) {
1806
2833
  if (pluginAgentsMdIsSymlink) {
1807
2834
  summary.agentsMd.skipped += 1;
1808
2835
  console.log(` Skipped plugin-mode AGENTS.md merge for symlinked ${pluginAgentsMdDst}; existing AGENTS.md left untouched.`);
@@ -1917,7 +2944,7 @@ export async function setup(options = {}) {
1917
2944
  console.log(` WARNING: Existing AGENTS.md at ${agentsMdDst} lacks OMX contract markers; it may have been overwritten by another tool.`);
1918
2945
  console.log(` Repair safely with "omx setup ${scopeFlag} --merge-agents" to preserve local guidance, or "omx setup ${scopeFlag} --force" to replace it after backup.`);
1919
2946
  }
1920
- if (options.mergeAgents) {
2947
+ if (effectiveMergeAgents) {
1921
2948
  mergedAgentsContent = upsertManagedAgentsBlock(existing, rewritten);
1922
2949
  canApplyManagedAgentsMerge = mergedAgentsContent !== existing;
1923
2950
  }
@@ -1949,7 +2976,7 @@ export async function setup(options = {}) {
1949
2976
  console.log(" Skipping AGENTS.md overwrite to avoid corrupting runtime overlay.");
1950
2977
  console.log(" Stop the active session first, then re-run setup.");
1951
2978
  }
1952
- else if (options.mergeAgents &&
2979
+ else if (effectiveMergeAgents &&
1953
2980
  agentsMdExists &&
1954
2981
  !canApplyManagedAgentsMerge) {
1955
2982
  summary.agentsMd.unchanged += 1;
@@ -2044,6 +3071,14 @@ export async function setup(options = {}) {
2044
3071
  console.log("Force mode: enabled additional destructive maintenance (for example stale deprecated skill cleanup).");
2045
3072
  console.log();
2046
3073
  }
3074
+ await persistSetupPreferences(projectRoot, setupPreferencesToPersist, {
3075
+ dryRun,
3076
+ verbose,
3077
+ });
3078
+ setupLatePhaseFailureInjector?.();
3079
+ nativeHookTransactionAncestorPrecondition =
3080
+ await captureNativeHookTransactionAncestorPrecondition(scopeDirs.codexHomeDir, nativeHookSetupTransaction.artifacts.map((artifact) => artifact.path));
3081
+ await commitNativeHookTransaction(nativeHookSetupTransaction.artifacts, nativeHookSetupTransaction.preconditions, nativeHookTransactionAncestorPrecondition, backupContext, summary.config, { dryRun, verbose });
2047
3082
  console.log('Setup complete! Run "omx doctor" to verify installation.');
2048
3083
  console.log("\nNext steps:");
2049
3084
  console.log(" 1. Start Codex CLI in your project directory");
@@ -2240,13 +3275,6 @@ async function syncNativeAgentToml(content, dstPath, summary, backupContext, opt
2240
3275
  console.log(` ${options.dryRun ? "would update" : "updated"} ${verboseLabel}`);
2241
3276
  }
2242
3277
  }
2243
- async function syncManagedWindowsNativeHookShim(codexHomeDir, pkgRoot, summary, backupContext, options) {
2244
- if (process.platform !== "win32")
2245
- return;
2246
- const shimPath = buildManagedCodexNativeHookWindowsShimPath(codexHomeDir);
2247
- const shimContent = buildManagedCodexNativeHookWindowsShimContent(pkgRoot);
2248
- await syncManagedContent(shimContent, shimPath, summary, backupContext, options, `native hook Windows shim ${shimPath}`);
2249
- }
2250
3278
  async function syncManagedAgentsContent(content, dstPath, summary, backupContext, options) {
2251
3279
  const destinationExists = existsSync(dstPath);
2252
3280
  let existing = "";
@@ -2710,7 +3738,34 @@ async function cleanupLegacyManagedSkills(srcDir, dstDir, backupContext, options
2710
3738
  function getNotifyMetadataPath(codexHomeDir) {
2711
3739
  return join(codexHomeDir, ".omx", "notify-dispatch.json");
2712
3740
  }
2713
- async function buildNotifyMergePlan(existingConfig, pkgRoot, codexHomeDir, scope) {
3741
+ function isOmxDispatcherNotifyCommand(notifyCommand, pkgRoot) {
3742
+ return Boolean(isOmxManagedNotifyCommand(notifyCommand, pkgRoot) &&
3743
+ notifyCommand?.some((part) => /(?:^|[\\/])notify-dispatcher\.js$/.test(part)));
3744
+ }
3745
+ function configRequiresNotificationMetadataSnapshot(existingConfig, pkgRoot, scope) {
3746
+ if (scope === "project")
3747
+ return false;
3748
+ const existingNotify = getRootTomlArray(existingConfig, "notify");
3749
+ return Boolean(existingNotify &&
3750
+ (!isOmxManagedNotifyCommand(existingNotify, pkgRoot) ||
3751
+ isOmxDispatcherNotifyCommand(existingNotify, pkgRoot)));
3752
+ }
3753
+ function parseNotifyMetadataSnapshot(snapshot, label) {
3754
+ if (snapshot.bytes === null)
3755
+ return null;
3756
+ const content = decodeNativeHookTransactionUtf8(snapshot.bytes, label);
3757
+ try {
3758
+ const parsed = JSON.parse(content);
3759
+ if (!parsed || typeof parsed !== "object" || Array.isArray(parsed)) {
3760
+ throw new Error("expected a JSON object");
3761
+ }
3762
+ return parsed;
3763
+ }
3764
+ catch (error) {
3765
+ throw new ManagedCodexHooksPlanError("invalid_document", `Refusing to read ${label}: invalid JSON (${error instanceof Error ? error.message : String(error)}).`, { label });
3766
+ }
3767
+ }
3768
+ async function buildNotifyMergePlan(existingConfig, pkgRoot, codexHomeDir, scope, metadataSnapshot) {
2714
3769
  if (scope === "project") {
2715
3770
  return { notifyCommand: false };
2716
3771
  }
@@ -2727,35 +3782,34 @@ async function buildNotifyMergePlan(existingConfig, pkgRoot, codexHomeDir, scope
2727
3782
  return { notifyCommand: omxNotify };
2728
3783
  }
2729
3784
  if (isOmxManagedNotifyCommand(existingNotify, pkgRoot)) {
2730
- if (!existingNotify.some((part) => /(?:^|[\\/])notify-dispatcher\.js$/.test(part))) {
3785
+ if (!isOmxDispatcherNotifyCommand(existingNotify, pkgRoot)) {
2731
3786
  return { notifyCommand: omxNotify };
2732
3787
  }
2733
- try {
2734
- const metadata = JSON.parse(await readFile(metadataPath, "utf-8"));
2735
- const previousNotify = metadata.previousNotify;
2736
- if (Array.isArray(previousNotify) &&
2737
- previousNotify.every((item) => typeof item === "string")) {
2738
- const sanitizedPreviousNotify = sanitizePreviousNotifyCommand(previousNotify, pkgRoot);
2739
- if (!sanitizedPreviousNotify) {
2740
- return { notifyCommand: omxNotify };
2741
- }
2742
- return {
2743
- notifyCommand: dispatcherNotify,
2744
- metadataPath,
2745
- metadata: {
2746
- managedBy: "oh-my-codex",
2747
- version: 1,
2748
- previousNotify: sanitizedPreviousNotify,
2749
- omxNotify,
2750
- dispatcherNotify,
2751
- },
2752
- };
2753
- }
3788
+ if (!metadataSnapshot) {
3789
+ throw new Error(`Missing notification metadata snapshot for ${metadataPath}; refusing to plan from a newly captured baseline.`);
2754
3790
  }
2755
- catch {
2756
- // Missing dispatcher metadata: fall back to plain OMX notify instead of nesting.
3791
+ const metadata = parseNotifyMetadataSnapshot(metadataSnapshot, `notification metadata ${metadataPath}`);
3792
+ const previousNotify = metadata?.previousNotify;
3793
+ if (Array.isArray(previousNotify) &&
3794
+ previousNotify.every((item) => typeof item === "string")) {
3795
+ const sanitizedPreviousNotify = sanitizePreviousNotifyCommand(previousNotify, pkgRoot);
3796
+ if (!sanitizedPreviousNotify) {
3797
+ return { notifyCommand: omxNotify, metadataPath, metadataSnapshot };
3798
+ }
3799
+ return {
3800
+ notifyCommand: dispatcherNotify,
3801
+ metadataPath,
3802
+ metadataSnapshot,
3803
+ metadata: {
3804
+ managedBy: "oh-my-codex",
3805
+ version: 1,
3806
+ previousNotify: sanitizedPreviousNotify,
3807
+ omxNotify,
3808
+ dispatcherNotify,
3809
+ },
3810
+ };
2757
3811
  }
2758
- return { notifyCommand: omxNotify };
3812
+ return { notifyCommand: omxNotify, metadataPath, metadataSnapshot };
2759
3813
  }
2760
3814
  return {
2761
3815
  notifyCommand: dispatcherNotify,
@@ -2769,14 +3823,11 @@ async function buildNotifyMergePlan(existingConfig, pkgRoot, codexHomeDir, scope
2769
3823
  },
2770
3824
  };
2771
3825
  }
2772
- async function updateManagedConfig(configPath, hooksPath, pkgRoot, sharedMcpRegistry, mcpMode, preserveExistingFirstPartyMcp, scope, codexHomeDir, summary, backupContext, options) {
2773
- const existing = existsSync(configPath)
2774
- ? await readFile(configPath, "utf-8")
2775
- : "";
2776
- const hadLegacyTeamRunTable = hasLegacyOmxTeamRunTable(existing);
2777
- const currentModel = getRootModelName(existing);
3826
+ async function planManagedConfig(hooksPath, pkgRoot, sharedMcpRegistry, mcpMode, preserveExistingFirstPartyMcp, scope, codexHomeDir, options) {
3827
+ const existingConfig = options.existingConfig ?? "";
3828
+ const hadLegacyTeamRunTable = hasLegacyOmxTeamRunTable(existingConfig);
3829
+ const currentModel = getRootModelName(existingConfig);
2778
3830
  let modelOverride;
2779
- const omxManagesTui = true;
2780
3831
  if (currentModel && LEGACY_SETUP_MODELS.has(currentModel)) {
2781
3832
  const shouldPrompt = typeof options.modelUpgradePrompt === "function" ||
2782
3833
  (process.stdin.isTTY && process.stdout.isTTY);
@@ -2789,12 +3840,15 @@ async function updateManagedConfig(configPath, hooksPath, pkgRoot, sharedMcpRegi
2789
3840
  }
2790
3841
  }
2791
3842
  }
2792
- const notifyPlan = await buildNotifyMergePlan(existing, pkgRoot, codexHomeDir, scope);
2793
- const finalConfig = buildMergedConfig(existing, pkgRoot, {
2794
- includeTui: omxManagesTui,
3843
+ const notifyPlan = await buildNotifyMergePlan(existingConfig, pkgRoot, codexHomeDir, scope, options.notifyMetadataSnapshot);
3844
+ const finalConfig = buildMergedConfig(existingConfig, pkgRoot, {
3845
+ includeTui: true,
2795
3846
  codexHooksFile: hooksPath,
2796
3847
  codexHomeDir,
2797
- hookCommandPlatform: process.platform,
3848
+ hookCommandPlatform: options.hookCommandPlatform,
3849
+ managedHookTrustState: options.managedHookTrustState,
3850
+ priorManagedHookTrustState: options.priorManagedHookTrustState,
3851
+ legacyHookTrustState: options.legacyHookTrustState,
2798
3852
  codexHookFeatureFlag: options.codexHookFeatureFlag,
2799
3853
  modelOverride,
2800
3854
  sharedMcpServers: sharedMcpRegistry.servers,
@@ -2806,65 +3860,14 @@ async function updateManagedConfig(configPath, hooksPath, pkgRoot, sharedMcpRegi
2806
3860
  includeFirstPartyMcp: mcpMode === "compat",
2807
3861
  preserveExistingFirstPartyMcp,
2808
3862
  });
2809
- const changed = existing !== finalConfig;
2810
- if (!changed) {
2811
- summary.unchanged += 1;
2812
- return {
2813
- finalConfig,
2814
- omxManagesTui,
2815
- repairedLegacyTeamRunTable: false,
2816
- };
2817
- }
2818
- if (await ensureBackup(configPath, existsSync(configPath), backupContext, options)) {
2819
- summary.backedUp += 1;
2820
- }
2821
- if (!options.dryRun) {
2822
- await writeFile(configPath, finalConfig);
2823
- if (notifyPlan.metadataPath && notifyPlan.metadata) {
2824
- await mkdir(dirname(notifyPlan.metadataPath), { recursive: true });
2825
- await writeFile(notifyPlan.metadataPath, JSON.stringify(notifyPlan.metadata, null, 2) + "\n");
2826
- }
2827
- }
2828
- if (options.verbose &&
2829
- modelOverride &&
2830
- currentModel &&
2831
- currentModel !== modelOverride) {
2832
- console.log(` ${options.dryRun ? "would update" : "updated"} root model from ${currentModel} to ${modelOverride}`);
2833
- }
2834
- summary.updated += 1;
2835
- if (options.verbose) {
2836
- console.log(` ${options.dryRun ? "would update" : "updated"} config ${configPath}`);
2837
- }
2838
3863
  return {
2839
3864
  finalConfig,
2840
- omxManagesTui,
3865
+ currentModel,
3866
+ modelOverride,
3867
+ notifyPlan,
2841
3868
  repairedLegacyTeamRunTable: hadLegacyTeamRunTable && !hasLegacyOmxTeamRunTable(finalConfig),
2842
3869
  };
2843
3870
  }
2844
- async function syncSharedMcpRegistryIntoConfig(configPath, sharedMcpRegistry, summary, backupContext, options) {
2845
- if (sharedMcpRegistry.servers.length === 0) {
2846
- return existsSync(configPath) ? await readFile(configPath, "utf-8") : "";
2847
- }
2848
- const existing = existsSync(configPath)
2849
- ? await readFile(configPath, "utf-8")
2850
- : "";
2851
- const finalConfig = mergeSharedMcpRegistryBlock(existing, sharedMcpRegistry.servers, sharedMcpRegistry.sourcePath);
2852
- if (existing === finalConfig) {
2853
- summary.unchanged += 1;
2854
- return finalConfig;
2855
- }
2856
- if (await ensureBackup(configPath, existsSync(configPath), backupContext, options)) {
2857
- summary.backedUp += 1;
2858
- }
2859
- if (!options.dryRun) {
2860
- await writeFile(configPath, finalConfig);
2861
- }
2862
- summary.updated += 1;
2863
- if (options.verbose) {
2864
- console.log(` ${options.dryRun ? "would sync" : "synced"} shared MCP registry servers into ${configPath}`);
2865
- }
2866
- return finalConfig;
2867
- }
2868
3871
  function getClaudeCodeSettingsPath(homeDir = homedir()) {
2869
3872
  return join(homeDir, ".claude", "settings.json");
2870
3873
  }