oh-my-codex 0.20.1 → 0.20.3

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 (791) hide show
  1. package/Cargo.lock +7 -6
  2. package/Cargo.toml +1 -1
  3. package/README.md +36 -1
  4. package/crates/omx-runtime/Cargo.toml +1 -0
  5. package/crates/omx-runtime/src/main.rs +25 -2
  6. package/crates/omx-runtime/tests/execution.rs +219 -0
  7. package/crates/omx-runtime-core/src/dispatch.rs +96 -10
  8. package/crates/omx-runtime-core/src/engine.rs +839 -29
  9. package/crates/omx-runtime-core/src/lib.rs +8 -0
  10. package/dist/adapt/__tests__/hermes.test.js.map +1 -1
  11. package/dist/adapt/contracts.d.ts.map +1 -1
  12. package/dist/agents/__tests__/definitions.test.js +81 -0
  13. package/dist/agents/__tests__/definitions.test.js.map +1 -1
  14. package/dist/agents/__tests__/native-config.test.js +25 -0
  15. package/dist/agents/__tests__/native-config.test.js.map +1 -1
  16. package/dist/agents/native-config.d.ts +2 -1
  17. package/dist/agents/native-config.d.ts.map +1 -1
  18. package/dist/agents/native-config.js.map +1 -1
  19. package/dist/auth/__tests__/hotswap.test.d.ts +2 -0
  20. package/dist/auth/__tests__/hotswap.test.d.ts.map +1 -0
  21. package/dist/auth/__tests__/hotswap.test.js +183 -0
  22. package/dist/auth/__tests__/hotswap.test.js.map +1 -0
  23. package/dist/auth/hotswap.d.ts +6 -0
  24. package/dist/auth/hotswap.d.ts.map +1 -1
  25. package/dist/auth/hotswap.js +102 -19
  26. package/dist/auth/hotswap.js.map +1 -1
  27. package/dist/auth/paths.js.map +1 -1
  28. package/dist/auth/storage.js.map +1 -1
  29. package/dist/autopilot/__tests__/ralplan-gate.test.js +5 -5
  30. package/dist/autopilot/__tests__/ralplan-gate.test.js.map +1 -1
  31. package/dist/autopilot/completion-gate.js.map +1 -1
  32. package/dist/autopilot/fsm.d.ts +1 -1
  33. package/dist/autopilot/fsm.d.ts.map +1 -1
  34. package/dist/autoresearch/goal.js.map +1 -1
  35. package/dist/autoresearch/runtime.js.map +1 -1
  36. package/dist/capabilities/lockfile.js.map +1 -1
  37. package/dist/catalog/reader.js.map +1 -1
  38. package/dist/catalog/skill-mirror.js.map +1 -1
  39. package/dist/cli/__tests__/adapt-help.test.js.map +1 -1
  40. package/dist/cli/__tests__/agents-init.test.js.map +1 -1
  41. package/dist/cli/__tests__/agents.test.js.map +1 -1
  42. package/dist/cli/__tests__/api.test.js.map +1 -1
  43. package/dist/cli/__tests__/ask.test.js.map +1 -1
  44. package/dist/cli/__tests__/auth.test.js +60 -6
  45. package/dist/cli/__tests__/auth.test.js.map +1 -1
  46. package/dist/cli/__tests__/autoresearch-guided.test.js.map +1 -1
  47. package/dist/cli/__tests__/autoresearch.test.js.map +1 -1
  48. package/dist/cli/__tests__/capabilities.test.js.map +1 -1
  49. package/dist/cli/__tests__/codex-plugin-layout.test.js +80 -0
  50. package/dist/cli/__tests__/codex-plugin-layout.test.js.map +1 -1
  51. package/dist/cli/__tests__/doctor-context-window-warning.test.js.map +1 -1
  52. package/dist/cli/__tests__/doctor-invalid-config.test.js +47 -4
  53. package/dist/cli/__tests__/doctor-invalid-config.test.js.map +1 -1
  54. package/dist/cli/__tests__/doctor-team.test.js.map +1 -1
  55. package/dist/cli/__tests__/doctor-warning-copy.test.js +752 -15
  56. package/dist/cli/__tests__/doctor-warning-copy.test.js.map +1 -1
  57. package/dist/cli/__tests__/exec.test.js +136 -0
  58. package/dist/cli/__tests__/exec.test.js.map +1 -1
  59. package/dist/cli/__tests__/imagegen-continuation.test.js.map +1 -1
  60. package/dist/cli/__tests__/index.test.js +473 -14
  61. package/dist/cli/__tests__/index.test.js.map +1 -1
  62. package/dist/cli/__tests__/launch-fallback.test.js +336 -4
  63. package/dist/cli/__tests__/launch-fallback.test.js.map +1 -1
  64. package/dist/cli/__tests__/mcp-parity.test.js +87 -12
  65. package/dist/cli/__tests__/mcp-parity.test.js.map +1 -1
  66. package/dist/cli/__tests__/native-hook-claim-journal.test.d.ts +2 -0
  67. package/dist/cli/__tests__/native-hook-claim-journal.test.d.ts.map +1 -0
  68. package/dist/cli/__tests__/native-hook-claim-journal.test.js +206 -0
  69. package/dist/cli/__tests__/native-hook-claim-journal.test.js.map +1 -0
  70. package/dist/cli/__tests__/nested-help-routing.test.js.map +1 -1
  71. package/dist/cli/__tests__/question.test.js.map +1 -1
  72. package/dist/cli/__tests__/ralph-deslop-contract.test.js.map +1 -1
  73. package/dist/cli/__tests__/ralph-goal-mode-contract.test.js +6 -0
  74. package/dist/cli/__tests__/ralph-goal-mode-contract.test.js.map +1 -1
  75. package/dist/cli/__tests__/ralph-prd-deep-interview.test.js.map +1 -1
  76. package/dist/cli/__tests__/ralph-prd-smoke.test.js.map +1 -1
  77. package/dist/cli/__tests__/ralph.test.js +8 -3
  78. package/dist/cli/__tests__/ralph.test.js.map +1 -1
  79. package/dist/cli/__tests__/ralplan-bootstrap-3181.test.d.ts +2 -0
  80. package/dist/cli/__tests__/ralplan-bootstrap-3181.test.d.ts.map +1 -0
  81. package/dist/cli/__tests__/ralplan-bootstrap-3181.test.js +72 -0
  82. package/dist/cli/__tests__/ralplan-bootstrap-3181.test.js.map +1 -0
  83. package/dist/cli/__tests__/ralplan.test.d.ts +2 -0
  84. package/dist/cli/__tests__/ralplan.test.d.ts.map +1 -0
  85. package/dist/cli/__tests__/ralplan.test.js +55 -0
  86. package/dist/cli/__tests__/ralplan.test.js.map +1 -0
  87. package/dist/cli/__tests__/resume.test.js +17 -11
  88. package/dist/cli/__tests__/resume.test.js.map +1 -1
  89. package/dist/cli/__tests__/session-scoped-runtime.test.js +25 -0
  90. package/dist/cli/__tests__/session-scoped-runtime.test.js.map +1 -1
  91. package/dist/cli/__tests__/session-search-help.test.js +25 -1
  92. package/dist/cli/__tests__/session-search-help.test.js.map +1 -1
  93. package/dist/cli/__tests__/session-search.test.js.map +1 -1
  94. package/dist/cli/__tests__/setup-agents-overwrite.test.js +79 -0
  95. package/dist/cli/__tests__/setup-agents-overwrite.test.js.map +1 -1
  96. package/dist/cli/__tests__/setup-gh-star.test.js.map +1 -1
  97. package/dist/cli/__tests__/setup-hooks-shared-ownership.test.js +77 -3
  98. package/dist/cli/__tests__/setup-hooks-shared-ownership.test.js.map +1 -1
  99. package/dist/cli/__tests__/setup-hooks-trust-e2e.test.d.ts +2 -0
  100. package/dist/cli/__tests__/setup-hooks-trust-e2e.test.d.ts.map +1 -0
  101. package/dist/cli/__tests__/setup-hooks-trust-e2e.test.js +285 -0
  102. package/dist/cli/__tests__/setup-hooks-trust-e2e.test.js.map +1 -0
  103. package/dist/cli/__tests__/setup-install-mode.test.js +2579 -294
  104. package/dist/cli/__tests__/setup-install-mode.test.js.map +1 -1
  105. package/dist/cli/__tests__/setup-preferences.test.d.ts +2 -0
  106. package/dist/cli/__tests__/setup-preferences.test.d.ts.map +1 -0
  107. package/dist/cli/__tests__/setup-preferences.test.js +79 -0
  108. package/dist/cli/__tests__/setup-preferences.test.js.map +1 -0
  109. package/dist/cli/__tests__/setup-refresh.test.js +33 -2
  110. package/dist/cli/__tests__/setup-refresh.test.js.map +1 -1
  111. package/dist/cli/__tests__/setup-scope.test.js +46 -3
  112. package/dist/cli/__tests__/setup-scope.test.js.map +1 -1
  113. package/dist/cli/__tests__/sparkshell-cli.test.js.map +1 -1
  114. package/dist/cli/__tests__/state.test.js +37 -0
  115. package/dist/cli/__tests__/state.test.js.map +1 -1
  116. package/dist/cli/__tests__/team.test.js +105 -7
  117. package/dist/cli/__tests__/team.test.js.map +1 -1
  118. package/dist/cli/__tests__/ultragoal.test.js +61 -1
  119. package/dist/cli/__tests__/ultragoal.test.js.map +1 -1
  120. package/dist/cli/__tests__/uninstall.test.js +1817 -24
  121. package/dist/cli/__tests__/uninstall.test.js.map +1 -1
  122. package/dist/cli/__tests__/update.test.js +58 -0
  123. package/dist/cli/__tests__/update.test.js.map +1 -1
  124. package/dist/cli/__tests__/url.test.js.map +1 -1
  125. package/dist/cli/__tests__/windows-popup-loop-contract.test.js +2 -1
  126. package/dist/cli/__tests__/windows-popup-loop-contract.test.js.map +1 -1
  127. package/dist/cli/adapt.js.map +1 -1
  128. package/dist/cli/agents-init.js.map +1 -1
  129. package/dist/cli/agents.js.map +1 -1
  130. package/dist/cli/api.js.map +1 -1
  131. package/dist/cli/ask.d.ts +1 -1
  132. package/dist/cli/ask.d.ts.map +1 -1
  133. package/dist/cli/ask.js.map +1 -1
  134. package/dist/cli/auth.js.map +1 -1
  135. package/dist/cli/autoresearch-guided.js.map +1 -1
  136. package/dist/cli/autoresearch-intake.js.map +1 -1
  137. package/dist/cli/capabilities.js.map +1 -1
  138. package/dist/cli/cleanup.js.map +1 -1
  139. package/dist/cli/codex-feature-probe.js.map +1 -1
  140. package/dist/cli/codex-home.js.map +1 -1
  141. package/dist/cli/doctor.d.ts +21 -0
  142. package/dist/cli/doctor.d.ts.map +1 -1
  143. package/dist/cli/doctor.js +607 -130
  144. package/dist/cli/doctor.js.map +1 -1
  145. package/dist/cli/explore.js.map +1 -1
  146. package/dist/cli/index.d.ts +14 -3
  147. package/dist/cli/index.d.ts.map +1 -1
  148. package/dist/cli/index.js +245 -57
  149. package/dist/cli/index.js.map +1 -1
  150. package/dist/cli/mcp-parity.js +2 -2
  151. package/dist/cli/mcp-parity.js.map +1 -1
  152. package/dist/cli/mcp-serve.js.map +1 -1
  153. package/dist/cli/mission.js.map +1 -1
  154. package/dist/cli/native-assets.js.map +1 -1
  155. package/dist/cli/native-hook-claim-journal.d.ts +29 -0
  156. package/dist/cli/native-hook-claim-journal.d.ts.map +1 -0
  157. package/dist/cli/native-hook-claim-journal.js +252 -0
  158. package/dist/cli/native-hook-claim-journal.js.map +1 -0
  159. package/dist/cli/omx.js.map +1 -1
  160. package/dist/cli/plugin-marketplace.js.map +1 -1
  161. package/dist/cli/project-runtime-codex-homes.js.map +1 -1
  162. package/dist/cli/question.js.map +1 -1
  163. package/dist/cli/ralph.d.ts.map +1 -1
  164. package/dist/cli/ralph.js +3 -2
  165. package/dist/cli/ralph.js.map +1 -1
  166. package/dist/cli/ralplan.d.ts +10 -0
  167. package/dist/cli/ralplan.d.ts.map +1 -0
  168. package/dist/cli/ralplan.js +110 -0
  169. package/dist/cli/ralplan.js.map +1 -0
  170. package/dist/cli/session-search.d.ts +5 -0
  171. package/dist/cli/session-search.d.ts.map +1 -1
  172. package/dist/cli/session-search.js +87 -0
  173. package/dist/cli/session-search.js.map +1 -1
  174. package/dist/cli/setup-preferences.d.ts +11 -0
  175. package/dist/cli/setup-preferences.d.ts.map +1 -1
  176. package/dist/cli/setup-preferences.js +32 -0
  177. package/dist/cli/setup-preferences.js.map +1 -1
  178. package/dist/cli/setup.d.ts +58 -0
  179. package/dist/cli/setup.d.ts.map +1 -1
  180. package/dist/cli/setup.js +1450 -398
  181. package/dist/cli/setup.js.map +1 -1
  182. package/dist/cli/sparkshell.js.map +1 -1
  183. package/dist/cli/star-prompt.js.map +1 -1
  184. package/dist/cli/state.d.ts.map +1 -1
  185. package/dist/cli/state.js +2 -1
  186. package/dist/cli/state.js.map +1 -1
  187. package/dist/cli/team.js.map +1 -1
  188. package/dist/cli/ultragoal.d.ts +5 -1
  189. package/dist/cli/ultragoal.d.ts.map +1 -1
  190. package/dist/cli/ultragoal.js +34 -2
  191. package/dist/cli/ultragoal.js.map +1 -1
  192. package/dist/cli/uninstall.d.ts +17 -0
  193. package/dist/cli/uninstall.d.ts.map +1 -1
  194. package/dist/cli/uninstall.js +986 -95
  195. package/dist/cli/uninstall.js.map +1 -1
  196. package/dist/cli/update.d.ts.map +1 -1
  197. package/dist/cli/update.js +8 -1
  198. package/dist/cli/update.js.map +1 -1
  199. package/dist/cli/url.js.map +1 -1
  200. package/dist/compat/__tests__/doctor-contract.test.js +5 -1
  201. package/dist/compat/__tests__/doctor-contract.test.js.map +1 -1
  202. package/dist/compat/__tests__/rust-runtime-compat.test.js +1 -1
  203. package/dist/compat/__tests__/rust-runtime-compat.test.js.map +1 -1
  204. package/dist/config/__tests__/codex-hooks.test.js +1287 -11
  205. package/dist/config/__tests__/codex-hooks.test.js.map +1 -1
  206. package/dist/config/__tests__/generator-idempotent.test.js +1179 -32
  207. package/dist/config/__tests__/generator-idempotent.test.js.map +1 -1
  208. package/dist/config/__tests__/generator-notify.test.js +6 -1
  209. package/dist/config/__tests__/generator-notify.test.js.map +1 -1
  210. package/dist/config/__tests__/models.test.js +67 -3
  211. package/dist/config/__tests__/models.test.js.map +1 -1
  212. package/dist/config/codex-feature-flags.d.ts.map +1 -1
  213. package/dist/config/codex-feature-flags.js.map +1 -1
  214. package/dist/config/codex-hooks.d.ts +124 -1
  215. package/dist/config/codex-hooks.d.ts.map +1 -1
  216. package/dist/config/codex-hooks.js +2142 -265
  217. package/dist/config/codex-hooks.js.map +1 -1
  218. package/dist/config/commit-lore-guard.js.map +1 -1
  219. package/dist/config/generator.d.ts +23 -34
  220. package/dist/config/generator.d.ts.map +1 -1
  221. package/dist/config/generator.js +1480 -246
  222. package/dist/config/generator.js.map +1 -1
  223. package/dist/config/mcp-registry.js.map +1 -1
  224. package/dist/config/models.d.ts +14 -4
  225. package/dist/config/models.d.ts.map +1 -1
  226. package/dist/config/models.js +15 -1
  227. package/dist/config/models.js.map +1 -1
  228. package/dist/config/omx-first-party-mcp.js.map +1 -1
  229. package/dist/config/team-mode.d.ts.map +1 -1
  230. package/dist/document-refresh/__tests__/enforcer.test.js.map +1 -1
  231. package/dist/document-refresh/enforcer.js.map +1 -1
  232. package/dist/exec/followup.js.map +1 -1
  233. package/dist/goal-workflows/artifacts.js.map +1 -1
  234. package/dist/hooks/__tests__/agents-overlay.test.js +7 -4
  235. package/dist/hooks/__tests__/agents-overlay.test.js.map +1 -1
  236. package/dist/hooks/__tests__/analyze-routing-contract.test.js.map +1 -1
  237. package/dist/hooks/__tests__/analyze-skill-contract.test.js.map +1 -1
  238. package/dist/hooks/__tests__/anti-slop-workflow.test.js.map +1 -1
  239. package/dist/hooks/__tests__/autopilot-skill-contract.test.js +9 -3
  240. package/dist/hooks/__tests__/autopilot-skill-contract.test.js.map +1 -1
  241. package/dist/hooks/__tests__/code-review-skill-contract.test.js.map +1 -1
  242. package/dist/hooks/__tests__/consensus-execution-handoff.test.js.map +1 -1
  243. package/dist/hooks/__tests__/deep-interview-contract.test.js.map +1 -1
  244. package/dist/hooks/__tests__/design-skill.test.js.map +1 -1
  245. package/dist/hooks/__tests__/keyword-detector.test.js +1376 -6
  246. package/dist/hooks/__tests__/keyword-detector.test.js.map +1 -1
  247. package/dist/hooks/__tests__/notify-fallback-watcher.test.js +252 -543
  248. package/dist/hooks/__tests__/notify-fallback-watcher.test.js.map +1 -1
  249. package/dist/hooks/__tests__/notify-hook-all-workers-idle.test.js +38 -4
  250. package/dist/hooks/__tests__/notify-hook-all-workers-idle.test.js.map +1 -1
  251. package/dist/hooks/__tests__/notify-hook-auto-nudge.test.js +765 -10
  252. package/dist/hooks/__tests__/notify-hook-auto-nudge.test.js.map +1 -1
  253. package/dist/hooks/__tests__/notify-hook-cross-worktree-heartbeat.test.js +2 -0
  254. package/dist/hooks/__tests__/notify-hook-cross-worktree-heartbeat.test.js.map +1 -1
  255. package/dist/hooks/__tests__/notify-hook-managed-tmux.test.js +142 -1
  256. package/dist/hooks/__tests__/notify-hook-managed-tmux.test.js.map +1 -1
  257. package/dist/hooks/__tests__/notify-hook-modules.test.js.map +1 -1
  258. package/dist/hooks/__tests__/notify-hook-non-omx-guard.test.js +48 -2
  259. package/dist/hooks/__tests__/notify-hook-non-omx-guard.test.js.map +1 -1
  260. package/dist/hooks/__tests__/notify-hook-ralph-resume.test.js +61 -44
  261. package/dist/hooks/__tests__/notify-hook-ralph-resume.test.js.map +1 -1
  262. package/dist/hooks/__tests__/notify-hook-regression-205.test.js.map +1 -1
  263. package/dist/hooks/__tests__/notify-hook-session-idle-dedupe.test.js +3 -2
  264. package/dist/hooks/__tests__/notify-hook-session-idle-dedupe.test.js.map +1 -1
  265. package/dist/hooks/__tests__/notify-hook-session-scope.test.js +243 -4
  266. package/dist/hooks/__tests__/notify-hook-session-scope.test.js.map +1 -1
  267. package/dist/hooks/__tests__/notify-hook-team-dispatch.test.js +531 -12
  268. package/dist/hooks/__tests__/notify-hook-team-dispatch.test.js.map +1 -1
  269. package/dist/hooks/__tests__/notify-hook-team-leader-nudge.test.js +163 -24
  270. package/dist/hooks/__tests__/notify-hook-team-leader-nudge.test.js.map +1 -1
  271. package/dist/hooks/__tests__/notify-hook-team-tmux-guard.test.js +482 -32
  272. package/dist/hooks/__tests__/notify-hook-team-tmux-guard.test.js.map +1 -1
  273. package/dist/hooks/__tests__/notify-hook-tmux-heal.test.js +68 -1
  274. package/dist/hooks/__tests__/notify-hook-tmux-heal.test.js.map +1 -1
  275. package/dist/hooks/__tests__/notify-hook-tmux-scrollback.test.js +7 -1
  276. package/dist/hooks/__tests__/notify-hook-tmux-scrollback.test.js.map +1 -1
  277. package/dist/hooks/__tests__/notify-hook-visual-verdict.test.js.map +1 -1
  278. package/dist/hooks/__tests__/notify-hook-worker-idle.test.js +43 -4
  279. package/dist/hooks/__tests__/notify-hook-worker-idle.test.js.map +1 -1
  280. package/dist/hooks/__tests__/openclaw-setup-contract.test.js.map +1 -1
  281. package/dist/hooks/__tests__/pre-context-gate-skills.test.js.map +1 -1
  282. package/dist/hooks/__tests__/prometheus-strict-contract.test.js.map +1 -1
  283. package/dist/hooks/__tests__/prompt-guidance-fragments.test.js.map +1 -1
  284. package/dist/hooks/__tests__/prompt-guidance-test-helpers.js.map +1 -1
  285. package/dist/hooks/__tests__/prompt-session-provenance.test.d.ts +2 -0
  286. package/dist/hooks/__tests__/prompt-session-provenance.test.d.ts.map +1 -0
  287. package/dist/hooks/__tests__/prompt-session-provenance.test.js +82 -0
  288. package/dist/hooks/__tests__/prompt-session-provenance.test.js.map +1 -0
  289. package/dist/hooks/__tests__/session.test.js +878 -11
  290. package/dist/hooks/__tests__/session.test.js.map +1 -1
  291. package/dist/hooks/__tests__/skill-catalog-hygiene.test.js.map +1 -1
  292. package/dist/hooks/__tests__/team-runtime-gating-docs-contract.test.js.map +1 -1
  293. package/dist/hooks/__tests__/visual-ralph-skill.test.js.map +1 -1
  294. package/dist/hooks/__tests__/visual-verdict-loop.test.js.map +1 -1
  295. package/dist/hooks/agents-overlay.js +5 -5
  296. package/dist/hooks/agents-overlay.js.map +1 -1
  297. package/dist/hooks/code-simplifier/index.js.map +1 -1
  298. package/dist/hooks/explore-routing.js.map +1 -1
  299. package/dist/hooks/extensibility/dispatcher.js.map +1 -1
  300. package/dist/hooks/extensibility/events.js.map +1 -1
  301. package/dist/hooks/extensibility/loader.js.map +1 -1
  302. package/dist/hooks/extensibility/plugin-runner-stdin.js.map +1 -1
  303. package/dist/hooks/extensibility/sdk/logging.js.map +1 -1
  304. package/dist/hooks/keyword-detector.d.ts +33 -7
  305. package/dist/hooks/keyword-detector.d.ts.map +1 -1
  306. package/dist/hooks/keyword-detector.js +2228 -119
  307. package/dist/hooks/keyword-detector.js.map +1 -1
  308. package/dist/hooks/keyword-registry.d.ts +12 -0
  309. package/dist/hooks/keyword-registry.d.ts.map +1 -1
  310. package/dist/hooks/keyword-registry.js +41 -0
  311. package/dist/hooks/keyword-registry.js.map +1 -1
  312. package/dist/hooks/prompt-session-provenance.d.ts +87 -0
  313. package/dist/hooks/prompt-session-provenance.d.ts.map +1 -0
  314. package/dist/hooks/prompt-session-provenance.js +180 -0
  315. package/dist/hooks/prompt-session-provenance.js.map +1 -0
  316. package/dist/hooks/session.d.ts +163 -34
  317. package/dist/hooks/session.d.ts.map +1 -1
  318. package/dist/hooks/session.js +1395 -317
  319. package/dist/hooks/session.js.map +1 -1
  320. package/dist/hooks/task-size-detector.js.map +1 -1
  321. package/dist/hud/__tests__/state.test.js.map +1 -1
  322. package/dist/hud/authority.js.map +1 -1
  323. package/dist/hud/constants.js.map +1 -1
  324. package/dist/hud/index.js.map +1 -1
  325. package/dist/hud/reconcile.js.map +1 -1
  326. package/dist/hud/render.js.map +1 -1
  327. package/dist/hud/state.js.map +1 -1
  328. package/dist/hud/tmux.js.map +1 -1
  329. package/dist/leader/__tests__/contract.test.js +153 -1
  330. package/dist/leader/__tests__/contract.test.js.map +1 -1
  331. package/dist/leader/contract.d.ts +42 -5
  332. package/dist/leader/contract.d.ts.map +1 -1
  333. package/dist/leader/contract.js +184 -3
  334. package/dist/leader/contract.js.map +1 -1
  335. package/dist/mcp/__tests__/bootstrap.test.js.map +1 -1
  336. package/dist/mcp/__tests__/code-intel-server.test.js +31 -3
  337. package/dist/mcp/__tests__/code-intel-server.test.js.map +1 -1
  338. package/dist/mcp/__tests__/server-lifecycle.test.js +15 -2
  339. package/dist/mcp/__tests__/server-lifecycle.test.js.map +1 -1
  340. package/dist/mcp/__tests__/state-paths.test.js +98 -1
  341. package/dist/mcp/__tests__/state-paths.test.js.map +1 -1
  342. package/dist/mcp/__tests__/wiki-server.test.js +18 -0
  343. package/dist/mcp/__tests__/wiki-server.test.js.map +1 -1
  344. package/dist/mcp/bootstrap.js.map +1 -1
  345. package/dist/mcp/code-intel-server.d.ts +58 -58
  346. package/dist/mcp/code-intel-server.d.ts.map +1 -1
  347. package/dist/mcp/code-intel-server.js +13 -3
  348. package/dist/mcp/code-intel-server.js.map +1 -1
  349. package/dist/mcp/hermes-bridge.js.map +1 -1
  350. package/dist/mcp/hermes-server.d.ts +70 -70
  351. package/dist/mcp/hermes-server.d.ts.map +1 -1
  352. package/dist/mcp/lifecycle-telemetry.js.map +1 -1
  353. package/dist/mcp/memory-server.d.ts +34 -34
  354. package/dist/mcp/memory-server.d.ts.map +1 -1
  355. package/dist/mcp/memory-server.js.map +1 -1
  356. package/dist/mcp/state-paths.d.ts +14 -0
  357. package/dist/mcp/state-paths.d.ts.map +1 -1
  358. package/dist/mcp/state-paths.js +96 -20
  359. package/dist/mcp/state-paths.js.map +1 -1
  360. package/dist/mcp/state-server.d.ts +47 -47
  361. package/dist/mcp/state-server.d.ts.map +1 -1
  362. package/dist/mcp/trace-server.d.ts +4 -4
  363. package/dist/mcp/trace-server.d.ts.map +1 -1
  364. package/dist/mcp/trace-server.js.map +1 -1
  365. package/dist/mcp/wiki-server.d.ts +34 -23
  366. package/dist/mcp/wiki-server.d.ts.map +1 -1
  367. package/dist/mcp/wiki-server.js +9 -2
  368. package/dist/mcp/wiki-server.js.map +1 -1
  369. package/dist/modes/__tests__/base-session-scope.test.js +53 -1
  370. package/dist/modes/__tests__/base-session-scope.test.js.map +1 -1
  371. package/dist/modes/__tests__/base-tmux-pane.test.js +4 -4
  372. package/dist/modes/__tests__/base-tmux-pane.test.js.map +1 -1
  373. package/dist/modes/base.d.ts.map +1 -1
  374. package/dist/modes/base.js +5 -5
  375. package/dist/modes/base.js.map +1 -1
  376. package/dist/notifications/__tests__/lifecycle-dedupe.test.js.map +1 -1
  377. package/dist/notifications/__tests__/reply-listener.test.js.map +1 -1
  378. package/dist/notifications/__tests__/session-idle-tail-dedupe.test.js +177 -85
  379. package/dist/notifications/__tests__/session-idle-tail-dedupe.test.js.map +1 -1
  380. package/dist/notifications/__tests__/session-registry.test.js.map +1 -1
  381. package/dist/notifications/__tests__/template-engine.test.js.map +1 -1
  382. package/dist/notifications/__tests__/verbosity.test.js.map +1 -1
  383. package/dist/notifications/http-client.js.map +1 -1
  384. package/dist/notifications/index.d.ts +4 -1
  385. package/dist/notifications/index.d.ts.map +1 -1
  386. package/dist/notifications/index.js +3 -9
  387. package/dist/notifications/index.js.map +1 -1
  388. package/dist/notifications/lifecycle-dedupe.js.map +1 -1
  389. package/dist/notifications/reply-listener.d.ts.map +1 -1
  390. package/dist/notifications/reply-listener.js.map +1 -1
  391. package/dist/notifications/session-registry.js.map +1 -1
  392. package/dist/notifications/session-status.js.map +1 -1
  393. package/dist/notifications/temp-contract.d.ts.map +1 -1
  394. package/dist/notifications/temp-contract.js +4 -0
  395. package/dist/notifications/temp-contract.js.map +1 -1
  396. package/dist/notifications/tmux-detector.js.map +1 -1
  397. package/dist/notifications/tmux.js.map +1 -1
  398. package/dist/openclaw/config.js.map +1 -1
  399. package/dist/openclaw/dispatcher.js.map +1 -1
  400. package/dist/pipeline/__tests__/orchestrator.test.js +4 -4
  401. package/dist/pipeline/__tests__/orchestrator.test.js.map +1 -1
  402. package/dist/pipeline/__tests__/stages.test.js +16 -13
  403. package/dist/pipeline/__tests__/stages.test.js.map +1 -1
  404. package/dist/pipeline/stages/code-review.js.map +1 -1
  405. package/dist/pipeline/stages/ralph-verify.js.map +1 -1
  406. package/dist/pipeline/stages/ralplan.js.map +1 -1
  407. package/dist/pipeline/stages/team-exec.js.map +1 -1
  408. package/dist/pipeline/stages/ultragoal.js.map +1 -1
  409. package/dist/pipeline/stages/ultraqa.js.map +1 -1
  410. package/dist/planning/__tests__/approved-execution-lifecycle-matrix.test.js.map +1 -1
  411. package/dist/planning/artifact-names.js.map +1 -1
  412. package/dist/planning/artifacts.js.map +1 -1
  413. package/dist/question/__tests__/state.test.js.map +1 -1
  414. package/dist/question/__tests__/ui.test.js.map +1 -1
  415. package/dist/question/autopilot-wait.js.map +1 -1
  416. package/dist/question/client.d.ts.map +1 -1
  417. package/dist/question/client.js.map +1 -1
  418. package/dist/question/deep-interview.js.map +1 -1
  419. package/dist/question/events.js.map +1 -1
  420. package/dist/question/renderer.js.map +1 -1
  421. package/dist/question/state.d.ts.map +1 -1
  422. package/dist/question/state.js.map +1 -1
  423. package/dist/question/ui.js.map +1 -1
  424. package/dist/ralph/contract.d.ts +1 -1
  425. package/dist/ralph/contract.d.ts.map +1 -1
  426. package/dist/ralplan/__tests__/consensus-gate.test.js +527 -8
  427. package/dist/ralplan/__tests__/consensus-gate.test.js.map +1 -1
  428. package/dist/ralplan/__tests__/documented-leader-preflight.test.d.ts +2 -0
  429. package/dist/ralplan/__tests__/documented-leader-preflight.test.d.ts.map +1 -0
  430. package/dist/ralplan/__tests__/documented-leader-preflight.test.js +46 -0
  431. package/dist/ralplan/__tests__/documented-leader-preflight.test.js.map +1 -0
  432. package/dist/ralplan/__tests__/runtime.test.js +116 -14
  433. package/dist/ralplan/__tests__/runtime.test.js.map +1 -1
  434. package/dist/ralplan/consensus-gate.d.ts +3 -3
  435. package/dist/ralplan/consensus-gate.d.ts.map +1 -1
  436. package/dist/ralplan/consensus-gate.js +173 -70
  437. package/dist/ralplan/consensus-gate.js.map +1 -1
  438. package/dist/ralplan/documented-leader-preflight.d.ts +34 -0
  439. package/dist/ralplan/documented-leader-preflight.d.ts.map +1 -0
  440. package/dist/ralplan/documented-leader-preflight.js +58 -0
  441. package/dist/ralplan/documented-leader-preflight.js.map +1 -0
  442. package/dist/ralplan/runtime.d.ts +3 -2
  443. package/dist/ralplan/runtime.d.ts.map +1 -1
  444. package/dist/ralplan/runtime.js +12 -9
  445. package/dist/ralplan/runtime.js.map +1 -1
  446. package/dist/runtime/__tests__/bridge.test.js +42 -1
  447. package/dist/runtime/__tests__/bridge.test.js.map +1 -1
  448. package/dist/runtime/bridge.d.ts +12 -0
  449. package/dist/runtime/bridge.d.ts.map +1 -1
  450. package/dist/runtime/bridge.js +52 -1
  451. package/dist/runtime/bridge.js.map +1 -1
  452. package/dist/runtime/process-tree.js.map +1 -1
  453. package/dist/runtime/run-loop.js.map +1 -1
  454. package/dist/runtime/run-outcome.d.ts +3 -3
  455. package/dist/runtime/run-outcome.d.ts.map +1 -1
  456. package/dist/runtime/run-outcome.js.map +1 -1
  457. package/dist/runtime/run-state.js.map +1 -1
  458. package/dist/scripts/__tests__/codex-native-hook.test.js +6657 -995
  459. package/dist/scripts/__tests__/codex-native-hook.test.js.map +1 -1
  460. package/dist/scripts/__tests__/fixtures/issue-3138-installed-smoke.d.ts +2 -0
  461. package/dist/scripts/__tests__/fixtures/issue-3138-installed-smoke.d.ts.map +1 -0
  462. package/dist/scripts/__tests__/fixtures/issue-3138-installed-smoke.js +634 -0
  463. package/dist/scripts/__tests__/fixtures/issue-3138-installed-smoke.js.map +1 -0
  464. package/dist/scripts/__tests__/generate-release-body.test.js.map +1 -1
  465. package/dist/scripts/__tests__/hook-derived-watcher.test.js.map +1 -1
  466. package/dist/scripts/__tests__/notify-dispatcher.test.js.map +1 -1
  467. package/dist/scripts/__tests__/notify-fallback-delivery.test.d.ts +2 -0
  468. package/dist/scripts/__tests__/notify-fallback-delivery.test.d.ts.map +1 -0
  469. package/dist/scripts/__tests__/notify-fallback-delivery.test.js +174 -0
  470. package/dist/scripts/__tests__/notify-fallback-delivery.test.js.map +1 -0
  471. package/dist/scripts/__tests__/notify-state-io.test.js +57 -1
  472. package/dist/scripts/__tests__/notify-state-io.test.js.map +1 -1
  473. package/dist/scripts/__tests__/reasoning-artifact-contract.test.d.ts +2 -0
  474. package/dist/scripts/__tests__/reasoning-artifact-contract.test.d.ts.map +1 -0
  475. package/dist/scripts/__tests__/reasoning-artifact-contract.test.js +108 -0
  476. package/dist/scripts/__tests__/reasoning-artifact-contract.test.js.map +1 -0
  477. package/dist/scripts/__tests__/role-intent-bootstrap-e2e-3181.test.d.ts +2 -0
  478. package/dist/scripts/__tests__/role-intent-bootstrap-e2e-3181.test.d.ts.map +1 -0
  479. package/dist/scripts/__tests__/role-intent-bootstrap-e2e-3181.test.js +78 -0
  480. package/dist/scripts/__tests__/role-intent-bootstrap-e2e-3181.test.js.map +1 -0
  481. package/dist/scripts/__tests__/role-intent-durable-recovery-3181.test.d.ts +2 -0
  482. package/dist/scripts/__tests__/role-intent-durable-recovery-3181.test.d.ts.map +1 -0
  483. package/dist/scripts/__tests__/role-intent-durable-recovery-3181.test.js +55 -0
  484. package/dist/scripts/__tests__/role-intent-durable-recovery-3181.test.js.map +1 -0
  485. package/dist/scripts/__tests__/run-test-files.test.js.map +1 -1
  486. package/dist/scripts/__tests__/smoke-packed-install.test.js +1100 -4
  487. package/dist/scripts/__tests__/smoke-packed-install.test.js.map +1 -1
  488. package/dist/scripts/__tests__/test-reply-listener-live.test.js.map +1 -1
  489. package/dist/scripts/build-api.js.map +1 -1
  490. package/dist/scripts/build-explore-harness.js.map +1 -1
  491. package/dist/scripts/build-sparkshell.js.map +1 -1
  492. package/dist/scripts/cleanup-explore-harness.js.map +1 -1
  493. package/dist/scripts/codex-execution-surface.js.map +1 -1
  494. package/dist/scripts/codex-native-hook.d.ts +4 -1
  495. package/dist/scripts/codex-native-hook.d.ts.map +1 -1
  496. package/dist/scripts/codex-native-hook.js +11577 -1184
  497. package/dist/scripts/codex-native-hook.js.map +1 -1
  498. package/dist/scripts/generate-native-release-manifest.js.map +1 -1
  499. package/dist/scripts/generate-release-body.js.map +1 -1
  500. package/dist/scripts/hook-payload-guard.d.ts.map +1 -1
  501. package/dist/scripts/hook-payload-guard.js.map +1 -1
  502. package/dist/scripts/notify-fallback-delivery.d.ts +48 -0
  503. package/dist/scripts/notify-fallback-delivery.d.ts.map +1 -0
  504. package/dist/scripts/notify-fallback-delivery.js +419 -0
  505. package/dist/scripts/notify-fallback-delivery.js.map +1 -0
  506. package/dist/scripts/notify-fallback-watcher.js +218 -138
  507. package/dist/scripts/notify-fallback-watcher.js.map +1 -1
  508. package/dist/scripts/notify-hook/auto-nudge.d.ts +18 -9
  509. package/dist/scripts/notify-hook/auto-nudge.d.ts.map +1 -1
  510. package/dist/scripts/notify-hook/auto-nudge.js +112 -15
  511. package/dist/scripts/notify-hook/auto-nudge.js.map +1 -1
  512. package/dist/scripts/notify-hook/managed-tmux.d.ts +23 -0
  513. package/dist/scripts/notify-hook/managed-tmux.d.ts.map +1 -1
  514. package/dist/scripts/notify-hook/managed-tmux.js +122 -12
  515. package/dist/scripts/notify-hook/managed-tmux.js.map +1 -1
  516. package/dist/scripts/notify-hook/orchestration-intent.d.ts +7 -7
  517. package/dist/scripts/notify-hook/orchestration-intent.d.ts.map +1 -1
  518. package/dist/scripts/notify-hook/ralph-session-resume.d.ts +5 -2
  519. package/dist/scripts/notify-hook/ralph-session-resume.d.ts.map +1 -1
  520. package/dist/scripts/notify-hook/ralph-session-resume.js +33 -37
  521. package/dist/scripts/notify-hook/ralph-session-resume.js.map +1 -1
  522. package/dist/scripts/notify-hook/state-io.d.ts +17 -0
  523. package/dist/scripts/notify-hook/state-io.d.ts.map +1 -1
  524. package/dist/scripts/notify-hook/state-io.js +73 -11
  525. package/dist/scripts/notify-hook/state-io.js.map +1 -1
  526. package/dist/scripts/notify-hook/team-dispatch.d.ts +10 -1
  527. package/dist/scripts/notify-hook/team-dispatch.d.ts.map +1 -1
  528. package/dist/scripts/notify-hook/team-dispatch.js +234 -79
  529. package/dist/scripts/notify-hook/team-dispatch.js.map +1 -1
  530. package/dist/scripts/notify-hook/team-leader-nudge.d.ts +2 -2
  531. package/dist/scripts/notify-hook/team-leader-nudge.d.ts.map +1 -1
  532. package/dist/scripts/notify-hook/team-leader-nudge.js +39 -13
  533. package/dist/scripts/notify-hook/team-leader-nudge.js.map +1 -1
  534. package/dist/scripts/notify-hook/team-tmux-guard.d.ts +16 -8
  535. package/dist/scripts/notify-hook/team-tmux-guard.d.ts.map +1 -1
  536. package/dist/scripts/notify-hook/team-tmux-guard.js +365 -44
  537. package/dist/scripts/notify-hook/team-tmux-guard.js.map +1 -1
  538. package/dist/scripts/notify-hook/team-worker-posttooluse.js.map +1 -1
  539. package/dist/scripts/notify-hook/team-worker-stop.d.ts +1 -1
  540. package/dist/scripts/notify-hook/team-worker-stop.d.ts.map +1 -1
  541. package/dist/scripts/notify-hook/team-worker-stop.js +19 -18
  542. package/dist/scripts/notify-hook/team-worker-stop.js.map +1 -1
  543. package/dist/scripts/notify-hook/team-worker.d.ts +5 -2
  544. package/dist/scripts/notify-hook/team-worker.d.ts.map +1 -1
  545. package/dist/scripts/notify-hook/team-worker.js +37 -28
  546. package/dist/scripts/notify-hook/team-worker.js.map +1 -1
  547. package/dist/scripts/notify-hook/tmux-injection.d.ts +8 -1
  548. package/dist/scripts/notify-hook/tmux-injection.d.ts.map +1 -1
  549. package/dist/scripts/notify-hook/tmux-injection.js +53 -18
  550. package/dist/scripts/notify-hook/tmux-injection.js.map +1 -1
  551. package/dist/scripts/notify-hook/visual-verdict.d.ts +1 -1
  552. package/dist/scripts/notify-hook/visual-verdict.d.ts.map +1 -1
  553. package/dist/scripts/notify-hook/visual-verdict.js +15 -13
  554. package/dist/scripts/notify-hook/visual-verdict.js.map +1 -1
  555. package/dist/scripts/notify-hook.d.ts +24 -1
  556. package/dist/scripts/notify-hook.d.ts.map +1 -1
  557. package/dist/scripts/notify-hook.js +267 -90
  558. package/dist/scripts/notify-hook.js.map +1 -1
  559. package/dist/scripts/postinstall.js.map +1 -1
  560. package/dist/scripts/prompt-inventory.js.map +1 -1
  561. package/dist/scripts/smoke-packed-install.d.ts +927 -5
  562. package/dist/scripts/smoke-packed-install.d.ts.map +1 -1
  563. package/dist/scripts/smoke-packed-install.js +4317 -13
  564. package/dist/scripts/smoke-packed-install.js.map +1 -1
  565. package/dist/scripts/sync-plugin-mirror.js.map +1 -1
  566. package/dist/scripts/test-reply-listener-live.js.map +1 -1
  567. package/dist/scripts/test-sparkshell.js.map +1 -1
  568. package/dist/scripts/verify-native-agents.js.map +1 -1
  569. package/dist/session-history/friction.js.map +1 -1
  570. package/dist/session-history/search.js.map +1 -1
  571. package/dist/sidecar/__tests__/boundary.test.js.map +1 -1
  572. package/dist/sidecar/collector.js.map +1 -1
  573. package/dist/sidecar/index.js.map +1 -1
  574. package/dist/sidecar/render.js.map +1 -1
  575. package/dist/sidecar/tmux.js.map +1 -1
  576. package/dist/state/__tests__/mode-state-context.test.js +54 -0
  577. package/dist/state/__tests__/mode-state-context.test.js.map +1 -1
  578. package/dist/state/__tests__/operations.test.js +66 -15
  579. package/dist/state/__tests__/operations.test.js.map +1 -1
  580. package/dist/state/__tests__/skill-active.test.js +19 -1
  581. package/dist/state/__tests__/skill-active.test.js.map +1 -1
  582. package/dist/state/__tests__/workflow-transition.test.js +61 -0
  583. package/dist/state/__tests__/workflow-transition.test.js.map +1 -1
  584. package/dist/state/mode-state-context.d.ts +5 -0
  585. package/dist/state/mode-state-context.d.ts.map +1 -1
  586. package/dist/state/mode-state-context.js +59 -0
  587. package/dist/state/mode-state-context.js.map +1 -1
  588. package/dist/state/operations.d.ts +1 -1
  589. package/dist/state/operations.d.ts.map +1 -1
  590. package/dist/state/operations.js +49 -55
  591. package/dist/state/operations.js.map +1 -1
  592. package/dist/state/paths.d.ts +1 -1
  593. package/dist/state/paths.d.ts.map +1 -1
  594. package/dist/state/paths.js +1 -1
  595. package/dist/state/paths.js.map +1 -1
  596. package/dist/state/skill-active.d.ts +13 -1
  597. package/dist/state/skill-active.d.ts.map +1 -1
  598. package/dist/state/skill-active.js +43 -4
  599. package/dist/state/skill-active.js.map +1 -1
  600. package/dist/state/terminal-normalization.js.map +1 -1
  601. package/dist/state/workflow-transition-reconcile.d.ts.map +1 -1
  602. package/dist/state/workflow-transition-reconcile.js +20 -4
  603. package/dist/state/workflow-transition-reconcile.js.map +1 -1
  604. package/dist/state/workflow-transition.d.ts +1 -1
  605. package/dist/state/workflow-transition.d.ts.map +1 -1
  606. package/dist/state/workflow-transition.js.map +1 -1
  607. package/dist/subagents/__tests__/leader-bootstrap-3181.test.d.ts +2 -0
  608. package/dist/subagents/__tests__/leader-bootstrap-3181.test.d.ts.map +1 -0
  609. package/dist/subagents/__tests__/leader-bootstrap-3181.test.js +52 -0
  610. package/dist/subagents/__tests__/leader-bootstrap-3181.test.js.map +1 -0
  611. package/dist/subagents/__tests__/tracker.test.js +1550 -10
  612. package/dist/subagents/__tests__/tracker.test.js.map +1 -1
  613. package/dist/subagents/role-routing-marker.d.ts +10 -0
  614. package/dist/subagents/role-routing-marker.d.ts.map +1 -0
  615. package/dist/subagents/role-routing-marker.js +136 -0
  616. package/dist/subagents/role-routing-marker.js.map +1 -0
  617. package/dist/subagents/tracker.d.ts +91 -0
  618. package/dist/subagents/tracker.d.ts.map +1 -1
  619. package/dist/subagents/tracker.js +806 -62
  620. package/dist/subagents/tracker.js.map +1 -1
  621. package/dist/team/__tests__/api-interop.test.js +91 -1
  622. package/dist/team/__tests__/api-interop.test.js.map +1 -1
  623. package/dist/team/__tests__/approved-execution.test.js.map +1 -1
  624. package/dist/team/__tests__/delivery-e2e-smoke.test.js +52 -10
  625. package/dist/team/__tests__/delivery-e2e-smoke.test.js.map +1 -1
  626. package/dist/team/__tests__/idle-nudge.test.js +45 -15
  627. package/dist/team/__tests__/idle-nudge.test.js.map +1 -1
  628. package/dist/team/__tests__/mcp-comm.test.js +93 -27
  629. package/dist/team/__tests__/mcp-comm.test.js.map +1 -1
  630. package/dist/team/__tests__/model-contract.test.js +341 -4
  631. package/dist/team/__tests__/model-contract.test.js.map +1 -1
  632. package/dist/team/__tests__/role-router.test.js.map +1 -1
  633. package/dist/team/__tests__/runtime-cli.test.js +9 -0
  634. package/dist/team/__tests__/runtime-cli.test.js.map +1 -1
  635. package/dist/team/__tests__/runtime.test.js +3624 -355
  636. package/dist/team/__tests__/runtime.test.js.map +1 -1
  637. package/dist/team/__tests__/scaling.test.js +2516 -118
  638. package/dist/team/__tests__/scaling.test.js.map +1 -1
  639. package/dist/team/__tests__/state-root.test.js.map +1 -1
  640. package/dist/team/__tests__/state.test.js +804 -36
  641. package/dist/team/__tests__/state.test.js.map +1 -1
  642. package/dist/team/__tests__/team-ops-contract.test.js +6 -0
  643. package/dist/team/__tests__/team-ops-contract.test.js.map +1 -1
  644. package/dist/team/__tests__/tmux-session.test.js +3664 -969
  645. package/dist/team/__tests__/tmux-session.test.js.map +1 -1
  646. package/dist/team/__tests__/tmux-test-fixture.js.map +1 -1
  647. package/dist/team/__tests__/worker-runtime-identity.test.js +38 -2
  648. package/dist/team/__tests__/worker-runtime-identity.test.js.map +1 -1
  649. package/dist/team/__tests__/worktree.test.js +6 -0
  650. package/dist/team/__tests__/worktree.test.js.map +1 -1
  651. package/dist/team/api-interop.d.ts +2 -2
  652. package/dist/team/api-interop.d.ts.map +1 -1
  653. package/dist/team/api-interop.js +15 -9
  654. package/dist/team/api-interop.js.map +1 -1
  655. package/dist/team/commit-hygiene.js.map +1 -1
  656. package/dist/team/contracts.d.ts +5 -5
  657. package/dist/team/contracts.d.ts.map +1 -1
  658. package/dist/team/delivery-log.js.map +1 -1
  659. package/dist/team/exact-pane.d.ts +23 -0
  660. package/dist/team/exact-pane.d.ts.map +1 -0
  661. package/dist/team/exact-pane.js +133 -0
  662. package/dist/team/exact-pane.js.map +1 -0
  663. package/dist/team/followup-planner.js.map +1 -1
  664. package/dist/team/goal-workflow.js.map +1 -1
  665. package/dist/team/idle-nudge.d.ts +12 -1
  666. package/dist/team/idle-nudge.d.ts.map +1 -1
  667. package/dist/team/idle-nudge.js +41 -14
  668. package/dist/team/idle-nudge.js.map +1 -1
  669. package/dist/team/model-contract.d.ts +18 -2
  670. package/dist/team/model-contract.d.ts.map +1 -1
  671. package/dist/team/model-contract.js +314 -35
  672. package/dist/team/model-contract.js.map +1 -1
  673. package/dist/team/orchestrator.js.map +1 -1
  674. package/dist/team/pane-status.js.map +1 -1
  675. package/dist/team/phase-controller.js.map +1 -1
  676. package/dist/team/reminder-intents.d.ts +1 -1
  677. package/dist/team/reminder-intents.d.ts.map +1 -1
  678. package/dist/team/reminder-intents.js.map +1 -1
  679. package/dist/team/runtime-cli.js.map +1 -1
  680. package/dist/team/runtime.d.ts +1 -0
  681. package/dist/team/runtime.d.ts.map +1 -1
  682. package/dist/team/runtime.js +1326 -231
  683. package/dist/team/runtime.js.map +1 -1
  684. package/dist/team/scaling.d.ts +22 -8
  685. package/dist/team/scaling.d.ts.map +1 -1
  686. package/dist/team/scaling.js +1463 -376
  687. package/dist/team/scaling.js.map +1 -1
  688. package/dist/team/state/dispatch-lock.js.map +1 -1
  689. package/dist/team/state/dispatch.d.ts.map +1 -1
  690. package/dist/team/state/dispatch.js +26 -8
  691. package/dist/team/state/dispatch.js.map +1 -1
  692. package/dist/team/state/events.js.map +1 -1
  693. package/dist/team/state/locks.d.ts.map +1 -1
  694. package/dist/team/state/locks.js +61 -13
  695. package/dist/team/state/locks.js.map +1 -1
  696. package/dist/team/state/tasks.d.ts.map +1 -1
  697. package/dist/team/state/tasks.js +4 -3
  698. package/dist/team/state/tasks.js.map +1 -1
  699. package/dist/team/state-root.js.map +1 -1
  700. package/dist/team/state.d.ts +67 -1
  701. package/dist/team/state.d.ts.map +1 -1
  702. package/dist/team/state.js +636 -129
  703. package/dist/team/state.js.map +1 -1
  704. package/dist/team/team-identity.d.ts.map +1 -1
  705. package/dist/team/team-identity.js.map +1 -1
  706. package/dist/team/team-ops.d.ts +5 -0
  707. package/dist/team/team-ops.d.ts.map +1 -1
  708. package/dist/team/team-ops.js +4 -0
  709. package/dist/team/team-ops.js.map +1 -1
  710. package/dist/team/tmux-session.d.ts +125 -23
  711. package/dist/team/tmux-session.d.ts.map +1 -1
  712. package/dist/team/tmux-session.js +1340 -333
  713. package/dist/team/tmux-session.js.map +1 -1
  714. package/dist/team/worker-bootstrap.js.map +1 -1
  715. package/dist/team/worktree.d.ts.map +1 -1
  716. package/dist/team/worktree.js +4 -0
  717. package/dist/team/worktree.js.map +1 -1
  718. package/dist/ultragoal/__tests__/artifacts.test.js.map +1 -1
  719. package/dist/ultragoal/__tests__/docs-contract.test.js.map +1 -1
  720. package/dist/ultragoal/artifacts.d.ts.map +1 -1
  721. package/dist/ultragoal/artifacts.js +4 -1
  722. package/dist/ultragoal/artifacts.js.map +1 -1
  723. package/dist/url-reader/index.js.map +1 -1
  724. package/dist/utils/__tests__/file-durability.test.d.ts +2 -0
  725. package/dist/utils/__tests__/file-durability.test.d.ts.map +1 -0
  726. package/dist/utils/__tests__/file-durability.test.js +57 -0
  727. package/dist/utils/__tests__/file-durability.test.js.map +1 -0
  728. package/dist/utils/agents-model-table.js.map +1 -1
  729. package/dist/utils/file-durability.d.ts +15 -0
  730. package/dist/utils/file-durability.d.ts.map +1 -0
  731. package/dist/utils/file-durability.js +37 -0
  732. package/dist/utils/file-durability.js.map +1 -0
  733. package/dist/utils/package.js.map +1 -1
  734. package/dist/utils/paths.js.map +1 -1
  735. package/dist/utils/platform-command.js.map +1 -1
  736. package/dist/utils/repo-deps.js.map +1 -1
  737. package/dist/utils/version.js.map +1 -1
  738. package/dist/verification/__tests__/ci-rust-gates.test.js +1 -1
  739. package/dist/verification/__tests__/dev-merge-issue-close-workflow.test.js +3 -0
  740. package/dist/verification/__tests__/dev-merge-issue-close-workflow.test.js.map +1 -1
  741. package/dist/verification/verifier.js.map +1 -1
  742. package/dist/visual/constants.d.ts +1 -1
  743. package/dist/visual/constants.d.ts.map +1 -1
  744. package/dist/vscode/__tests__/index.test.js.map +1 -1
  745. package/dist/vscode/index.js.map +1 -1
  746. package/dist/wiki/__tests__/cjk-tokenize.test.js.map +1 -1
  747. package/dist/wiki/__tests__/lint.test.js.map +1 -1
  748. package/dist/wiki/__tests__/query.test.js.map +1 -1
  749. package/dist/wiki/__tests__/storage.test.js.map +1 -1
  750. package/dist/wiki/__tests__/test-helpers.d.ts.map +1 -1
  751. package/dist/wiki/lint.d.ts +4 -1
  752. package/dist/wiki/lint.d.ts.map +1 -1
  753. package/dist/wiki/lint.js +3 -2
  754. package/dist/wiki/lint.js.map +1 -1
  755. package/dist/wiki/query.js.map +1 -1
  756. package/dist/wiki/storage.js.map +1 -1
  757. package/package.json +2 -2
  758. package/plugins/oh-my-codex/.codex-plugin/plugin.json +1 -1
  759. package/plugins/oh-my-codex/hooks/codex-native-hook.mjs +24 -0
  760. package/plugins/oh-my-codex/skills/omx-setup/SKILL.md +10 -5
  761. package/plugins/oh-my-codex/skills/ralph/SKILL.md +6 -2
  762. package/plugins/oh-my-codex/skills/ralplan/SKILL.md +6 -2
  763. package/plugins/oh-my-codex/skills/team/SKILL.md +13 -9
  764. package/skills/omx-setup/SKILL.md +10 -5
  765. package/skills/ralph/SKILL.md +6 -2
  766. package/skills/ralplan/SKILL.md +6 -2
  767. package/skills/team/SKILL.md +13 -9
  768. package/src/scripts/__tests__/codex-native-hook.test.ts +20048 -10827
  769. package/src/scripts/__tests__/fixtures/issue-3138-installed-smoke.ts +734 -0
  770. package/src/scripts/__tests__/notify-fallback-delivery.test.ts +177 -0
  771. package/src/scripts/__tests__/notify-state-io.test.ts +73 -0
  772. package/src/scripts/__tests__/reasoning-artifact-contract.test.ts +129 -0
  773. package/src/scripts/__tests__/role-intent-bootstrap-e2e-3181.test.ts +78 -0
  774. package/src/scripts/__tests__/role-intent-durable-recovery-3181.test.ts +60 -0
  775. package/src/scripts/__tests__/smoke-packed-install.test.ts +1309 -4
  776. package/src/scripts/codex-native-hook.ts +11429 -1163
  777. package/src/scripts/notify-fallback-delivery.ts +416 -0
  778. package/src/scripts/notify-fallback-watcher.ts +216 -155
  779. package/src/scripts/notify-hook/auto-nudge.ts +113 -14
  780. package/src/scripts/notify-hook/managed-tmux.ts +149 -12
  781. package/src/scripts/notify-hook/ralph-session-resume.ts +37 -36
  782. package/src/scripts/notify-hook/state-io.ts +100 -13
  783. package/src/scripts/notify-hook/team-dispatch.ts +242 -75
  784. package/src/scripts/notify-hook/team-leader-nudge.ts +40 -18
  785. package/src/scripts/notify-hook/team-tmux-guard.ts +366 -39
  786. package/src/scripts/notify-hook/team-worker-stop.ts +20 -16
  787. package/src/scripts/notify-hook/team-worker.ts +39 -26
  788. package/src/scripts/notify-hook/tmux-injection.ts +66 -23
  789. package/src/scripts/notify-hook/visual-verdict.ts +22 -12
  790. package/src/scripts/notify-hook.ts +336 -90
  791. package/src/scripts/smoke-packed-install.ts +5154 -149
package/dist/cli/setup.js CHANGED
@@ -2,17 +2,19 @@
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 as clearNativeHookClaimJournalWithDurability, createNativeHookClaimJournalDurability, persistNativeHookClaimJournal as persistNativeHookClaimJournalWithDurability, recoverNativeHookClaimJournal, syncNativeHookClaimParent as syncNativeHookClaimParentWithDurability, restoreNativeHookClaimNoClobber as restoreNativeHookClaimNoClobberWithDurability, } from "./native-hook-claim-journal.js";
13
14
  import { codexHome, codexConfigPath, codexPromptsDir, codexAgentsDir, userSkillsDir, omxStateDir, detectLegacySkillRootOverlap, omxPlansDir, omxLogsDir, } from "../utils/paths.js";
15
+ import { emitDegradedDurabilityWarning, recordRegularFileSyncOutcome, syncRegularFile, } from "../utils/file-durability.js";
14
16
  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";
17
+ import { buildManagedCodexNativeHookWindowsShimContent, buildManagedCodexNativeHookWindowsShimPath, planManagedCodexHooksMerge, planManagedCodexHooksRemoval, classifyManagedCodexNativeHookWindowsShimOwnership, ManagedCodexHooksPlanError, validateCodexHooksConfigStrict, } from "../config/codex-hooks.js";
16
18
  import { getLegacyUnifiedMcpRegistryCandidate, getUnifiedMcpRegistryCandidates, loadUnifiedMcpRegistry, planClaudeCodeMcpSettingsSync, } from "../config/mcp-registry.js";
17
19
  import { generateAgentToml } from "../agents/native-config.js";
18
20
  import { AGENT_DEFINITIONS } from "../agents/definitions.js";
@@ -25,7 +27,7 @@ import { DEFAULT_FRONTIER_MODEL } from "../config/models.js";
25
27
  import { teamModeEnabled, } from "../config/team-mode.js";
26
28
  import { addGeneratedAgentsMarker, hasOmxAgentsContract, hasOmxManagedAgentsSections, isOmxGeneratedAgentsMd, preserveUserOmxPolicyBlocks, upsertManagedAgentsBlock, } from "../utils/agents-md.js";
27
29
  import { DEFAULT_HUD_CONFIG } from "../hud/types.js";
28
- import { SETUP_INSTALL_MODES, SETUP_MCP_MODES, SETUP_SCOPES, getSetupScopeFilePath, readPersistedSetupPreferences, } from "./setup-preferences.js";
30
+ import { SETUP_INSTALL_MODES, SETUP_MCP_MODES, SETUP_SCOPES, getSetupScopeFilePath, readPersistedSetupPreferences, resolvePersistedSetupMergeAgents, writePersistedSetupPreferences, } from "./setup-preferences.js";
29
31
  import { OMX_LOCAL_MARKETPLACE_NAME, OMX_PLUGIN_NAME, materializePackagedOmxPluginCache, resolvePackagedOmxMarketplace, upsertLocalOmxMarketplaceRegistration, upsertLocalOmxPluginEnablement, upsertLocalOmxPluginMcpServerEnablement, hasLocalOmxPluginMcpServerRegistrations, pluginHookCacheMatchesPackaged, } from "./plugin-marketplace.js";
30
32
  import { resolveCodexHookFeatureSupportForCli } from "./codex-feature-probe.js";
31
33
  async function resolveStatusLinePresetForSetup(projectRoot, options) {
@@ -195,66 +197,1010 @@ function getBackupContext(scope, projectRoot) {
195
197
  baseRoot: homedir(),
196
198
  };
197
199
  }
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();
200
+ function logManagedCodexHooksPlanDiagnostics(diagnostics, options) {
201
+ if (!options.verbose || !diagnostics)
202
+ return;
203
+ for (const diagnostic of diagnostics) {
204
+ console.log(` hook plan diagnostic: ${diagnostic.message}`);
205
+ }
206
+ }
207
+ let nativeHookTransactionFailureInjector;
208
+ let nativeHookTransactionSequence = 0;
209
+ let nativeHookTransactionPlatformOverride;
210
+ let nativeHookTransactionTemporaryPathOverride;
211
+ let setupLatePhaseFailureInjector;
212
+ let nativeHookTransactionRegularFileSyncOverride;
213
+ let nativeHookClaimJournalDurabilityOverride;
214
+ /** @internal Test seam for deterministic atomic-write and rollback coverage. */
215
+ export function setNativeHookTransactionFailureInjectorForTest(injector) {
216
+ const previous = nativeHookTransactionFailureInjector;
217
+ nativeHookTransactionFailureInjector = injector;
218
+ return () => {
219
+ nativeHookTransactionFailureInjector = previous;
220
+ };
221
+ }
222
+ /** @internal Test seam proving native artifacts commit only after later setup phases. */
223
+ export function setSetupLatePhaseFailureInjectorForTest(injector) {
224
+ const previous = setupLatePhaseFailureInjector;
225
+ setupLatePhaseFailureInjector = injector;
226
+ return () => {
227
+ setupLatePhaseFailureInjector = previous;
228
+ };
229
+ }
230
+ /** @internal Test seam for deterministic Windows transaction coverage. */
231
+ export function setNativeHookTransactionPlatformForTest(platform) {
232
+ const previous = nativeHookTransactionPlatformOverride;
233
+ nativeHookTransactionPlatformOverride = platform;
234
+ return () => {
235
+ nativeHookTransactionPlatformOverride = previous;
236
+ };
237
+ }
238
+ /** @internal Test seam for deterministic native transaction temporary-path coverage. */
239
+ export function setNativeHookTransactionTemporaryPathForTest(resolver) {
240
+ const previous = nativeHookTransactionTemporaryPathOverride;
241
+ nativeHookTransactionTemporaryPathOverride = resolver;
242
+ return () => {
243
+ nativeHookTransactionTemporaryPathOverride = previous;
244
+ };
245
+ }
246
+ /** @internal Test seam for deterministic regular-file fsync coverage. */
247
+ export function setNativeHookTransactionRegularFileSyncForTest(sync) {
248
+ const previous = nativeHookTransactionRegularFileSyncOverride;
249
+ nativeHookTransactionRegularFileSyncOverride = sync;
250
+ return () => {
251
+ nativeHookTransactionRegularFileSyncOverride = previous;
252
+ };
253
+ }
254
+ /** @internal Test seam for deterministic claim-journal durability coverage. */
255
+ export function setNativeHookClaimJournalDurabilityForTest(durability) {
256
+ const previous = nativeHookClaimJournalDurabilityOverride;
257
+ nativeHookClaimJournalDurabilityOverride = durability;
258
+ return () => {
259
+ nativeHookClaimJournalDurabilityOverride = previous;
260
+ };
261
+ }
262
+ function nativeHookPlatform() {
263
+ return nativeHookTransactionPlatformOverride ?? process.platform;
264
+ }
265
+ function nativeHookClaimJournalDurability() {
266
+ return nativeHookClaimJournalDurabilityOverride
267
+ ?? createNativeHookClaimJournalDurability(nativeHookPlatform());
268
+ }
269
+ async function clearNativeHookClaimJournal(root) {
270
+ return clearNativeHookClaimJournalWithDurability(root, nativeHookClaimJournalDurability());
271
+ }
272
+ async function persistNativeHookClaimJournal(root, entry) {
273
+ return persistNativeHookClaimJournalWithDurability(root, entry, nativeHookClaimJournalDurability());
274
+ }
275
+ async function restoreNativeHookClaimNoClobber(claimPath, destinationPath) {
276
+ return restoreNativeHookClaimNoClobberWithDurability(claimPath, destinationPath, nativeHookClaimJournalDurability());
277
+ }
278
+ async function syncNativeHookClaimParent(path) {
279
+ return syncNativeHookClaimParentWithDurability(path, nativeHookClaimJournalDurability());
280
+ }
281
+ async function syncNativeHookRegularFile(handle) {
282
+ const platform = nativeHookPlatform();
283
+ if (nativeHookTransactionRegularFileSyncOverride) {
284
+ return syncRegularFile({ sync: () => nativeHookTransactionRegularFileSyncOverride(platform) }, platform);
285
+ }
286
+ return syncRegularFile(handle, platform);
287
+ }
288
+ function hookTransactionBytesEqual(left, right) {
289
+ return left === null || right === null ? left === right : left.equals(right);
290
+ }
291
+ function nativeHookTransactionTopologyEqual(left, right) {
292
+ return left.kind === "absent" || right.kind === "absent"
293
+ ? left.kind === right.kind
294
+ : left.mode === right.mode;
295
+ }
296
+ function isMissingPathError(error) {
297
+ return (typeof error === "object" &&
298
+ error !== null &&
299
+ "code" in error &&
300
+ error.code === "ENOENT");
301
+ }
302
+ function nativeHookTransactionArtifactAncestorPaths(controlledRoot, artifactPaths) {
303
+ const paths = new Set([controlledRoot]);
304
+ for (const artifactPath of artifactPaths) {
305
+ const parentPath = dirname(artifactPath);
306
+ const fromControlledRoot = relative(controlledRoot, parentPath);
307
+ if (fromControlledRoot === ".." ||
308
+ fromControlledRoot.startsWith(`..${sep}`) ||
309
+ isAbsolute(fromControlledRoot)) {
310
+ continue;
311
+ }
312
+ let ancestorPath = controlledRoot;
313
+ if (fromControlledRoot !== "") {
314
+ for (const component of fromControlledRoot.split(sep)) {
315
+ ancestorPath = join(ancestorPath, component);
316
+ paths.add(ancestorPath);
317
+ }
318
+ }
319
+ }
320
+ return [...paths];
321
+ }
322
+ async function captureNativeHookTransactionAncestorSnapshots(ancestorPaths) {
323
+ const snapshots = [];
324
+ for (const path of ancestorPaths) {
325
+ let status;
326
+ try {
327
+ status = await lstat(path);
328
+ }
329
+ catch (error) {
330
+ if (isMissingPathError(error)) {
331
+ snapshots.push({ path, topology: { kind: "absent" } });
332
+ continue;
333
+ }
334
+ throw error;
335
+ }
336
+ if (status.isSymbolicLink()) {
337
+ throw new Error(`Refusing native hook transaction: ancestor ${path} is a symbolic link.`);
338
+ }
339
+ if (!status.isDirectory()) {
340
+ throw new Error(`Refusing native hook transaction: ancestor ${path} is not a directory.`);
341
+ }
342
+ snapshots.push({
343
+ path,
344
+ topology: { kind: "directory", device: status.dev, inode: status.ino },
345
+ });
346
+ }
347
+ return snapshots;
348
+ }
349
+ function nativeHookTransactionAncestorTopologyEqual(left, right) {
350
+ return left.kind === "absent" || right.kind === "absent"
351
+ ? left.kind === right.kind
352
+ : left.device === right.device && left.inode === right.inode;
353
+ }
354
+ async function captureNativeHookTransactionAncestorPrecondition(controlledRoot, artifactPaths) {
355
+ const ancestorPaths = nativeHookTransactionArtifactAncestorPaths(controlledRoot, artifactPaths);
356
+ return {
357
+ controlledRoot,
358
+ ancestorPaths,
359
+ snapshots: await captureNativeHookTransactionAncestorSnapshots(ancestorPaths),
360
+ };
213
361
  }
214
- function existingHooksStateKeys(config) {
362
+ async function assertNativeHookTransactionAncestorPrecondition(precondition) {
363
+ const actual = await captureNativeHookTransactionAncestorSnapshots(precondition.ancestorPaths);
364
+ for (let index = 0; index < precondition.snapshots.length; index += 1) {
365
+ const expected = precondition.snapshots[index];
366
+ const current = actual[index];
367
+ if (!nativeHookTransactionAncestorTopologyEqual(current.topology, expected.topology)) {
368
+ throw new Error(`Native hook transaction precondition changed for ancestor ${expected.path}; refusing to mutate a stale topology.`);
369
+ }
370
+ }
371
+ }
372
+ async function refreshNativeHookTransactionAncestorPrecondition(precondition, artifactPath) {
373
+ const allowedCreatedPaths = new Set(nativeHookTransactionArtifactAncestorPaths(precondition.controlledRoot, [artifactPath]));
374
+ const actual = await captureNativeHookTransactionAncestorSnapshots(precondition.ancestorPaths);
375
+ for (let index = 0; index < precondition.snapshots.length; index += 1) {
376
+ const expected = precondition.snapshots[index];
377
+ const current = actual[index];
378
+ if (nativeHookTransactionAncestorTopologyEqual(current.topology, expected.topology)) {
379
+ continue;
380
+ }
381
+ if (expected.topology.kind === "absent" &&
382
+ current.topology.kind === "directory" &&
383
+ allowedCreatedPaths.has(expected.path)) {
384
+ continue;
385
+ }
386
+ throw new Error(`Native hook transaction precondition changed for ancestor ${expected.path}; refusing to mutate a stale topology.`);
387
+ }
388
+ precondition.snapshots = actual;
389
+ }
390
+ function decodeNativeHookTransactionUtf8(bytes, label) {
215
391
  try {
216
- const parsed = TOML.parse(config);
217
- return new Set(Object.keys(parsed.hooks?.state ?? {}));
392
+ const content = new TextDecoder("utf-8", {
393
+ fatal: true,
394
+ ignoreBOM: true,
395
+ }).decode(bytes);
396
+ if (!Buffer.from(content, "utf-8").equals(bytes)) {
397
+ throw new Error("decoded text did not round-trip to the original bytes");
398
+ }
399
+ return content;
218
400
  }
219
- catch {
220
- return new Set();
401
+ catch (error) {
402
+ throw new ManagedCodexHooksPlanError("invalid_document", `Refusing to read ${label}: invalid UTF-8 (${error instanceof Error ? error.message : String(error)}).`, { label });
403
+ }
404
+ }
405
+ async function captureNativeHookTransactionArtifact(path, label) {
406
+ let before;
407
+ try {
408
+ before = await lstat(path);
409
+ }
410
+ catch (error) {
411
+ if (isMissingPathError(error)) {
412
+ return { bytes: null, topology: { kind: "absent" } };
413
+ }
414
+ throw error;
415
+ }
416
+ if (!before.isFile() || before.nlink !== 1) {
417
+ throw new Error(`Refusing native hook transaction for ${label}: expected a single-link regular file or absence.`);
418
+ }
419
+ const bytes = await readFile(path);
420
+ const after = await lstat(path);
421
+ const beforeTopology = {
422
+ kind: "regular_file",
423
+ mode: before.mode & 0o7777,
424
+ };
425
+ const afterTopology = {
426
+ kind: "regular_file",
427
+ mode: after.mode & 0o7777,
428
+ };
429
+ if (!after.isFile() ||
430
+ after.nlink !== 1 ||
431
+ !nativeHookTransactionTopologyEqual(beforeTopology, afterTopology) ||
432
+ before.dev !== after.dev ||
433
+ before.ino !== after.ino ||
434
+ before.nlink !== after.nlink) {
435
+ throw new Error(`Refusing native hook transaction for ${label}: path changed while its snapshot was read.`);
436
+ }
437
+ return {
438
+ bytes,
439
+ topology: beforeTopology,
440
+ device: before.dev,
441
+ inode: before.ino,
442
+ links: before.nlink,
443
+ };
444
+ }
445
+ function nativeHookTransactionSnapshotsEqual(left, right) {
446
+ return (hookTransactionBytesEqual(left.bytes, right.bytes) &&
447
+ nativeHookTransactionTopologyEqual(left.topology, right.topology) &&
448
+ left.device === right.device &&
449
+ left.inode === right.inode &&
450
+ left.links === right.links);
451
+ }
452
+ function nativeHookTransactionSnapshotMatchesExpected(snapshot, expected) {
453
+ return (hookTransactionBytesEqual(snapshot.bytes, expected.bytes) &&
454
+ nativeHookTransactionTopologyEqual(snapshot.topology, expected.topology));
455
+ }
456
+ function nativeHookTransactionOutputTopology(before, after) {
457
+ if (after === null)
458
+ return { kind: "absent" };
459
+ return {
460
+ kind: "regular_file",
461
+ mode: before.topology.kind === "regular_file" ? before.topology.mode : 0o600,
462
+ };
463
+ }
464
+ function nativeHookTransactionArtifact(kind, path, label, before, after, hookPlatform) {
465
+ return hookTransactionBytesEqual(before.bytes, after)
466
+ ? null
467
+ : {
468
+ kind,
469
+ path,
470
+ label,
471
+ before,
472
+ after,
473
+ afterTopology: nativeHookTransactionOutputTopology(before, after),
474
+ hookPlatform,
475
+ };
476
+ }
477
+ function nativeHookTransactionPrecondition(kind, path, label, before) {
478
+ return { kind, path, label, before };
479
+ }
480
+ function assertWindowsNativeHookShimOwnership(shimPath, before, expected) {
481
+ if (before === null)
482
+ return;
483
+ const ownership = classifyManagedCodexNativeHookWindowsShimOwnership(before, expected);
484
+ if (ownership === "current" || ownership === "historical")
485
+ return;
486
+ 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.`);
487
+ }
488
+ function isWindowsShimReferenceRecord(value) {
489
+ return typeof value === "object" && value !== null && !Array.isArray(value);
490
+ }
491
+ function normalizeWindowsShimReferencePath(path) {
492
+ const windowsPath = path.replace(/\//g, "\\");
493
+ if (windowsPath.length === 0 ||
494
+ /[\0-\x1f\x7f-\x9f`<>"|?*]/.test(windowsPath) ||
495
+ /^\\\\\.(?:\\|$)/.test(windowsPath)) {
496
+ return null;
497
+ }
498
+ const isDriveAbsolute = /^[A-Za-z]:\\(?:[^\\:]+(?:\\[^\\:]+)*)?$/.test(windowsPath);
499
+ const isUncAbsolute = /^\\\\[^\\:]+\\[^\\:]+(?:\\[^\\:]+)*$/.test(windowsPath);
500
+ if (!isDriveAbsolute && !isUncAbsolute)
501
+ return null;
502
+ return win32.normalize(windowsPath).toLowerCase();
503
+ }
504
+ function windowsShimPathBasename(path) {
505
+ const name = win32.basename(path.replace(/\//g, "\\")).replace(/[. ]+$/, "");
506
+ return name.length > 0 ? name.toLowerCase() : null;
507
+ }
508
+ function decodePowerShellSingleQuotedLiteral(value) {
509
+ return value.replace(/''/g, "'");
510
+ }
511
+ /**
512
+ * Parses the only foreign command form that proves its -File target is a
513
+ * static literal. All other PowerShell forms remain ambiguous because they can
514
+ * construct an executable target dynamically or through a nested evaluator.
515
+ */
516
+ function decodeWindowsShimReferencePath(command) {
517
+ if (/["`]/.test(command))
518
+ return null;
519
+ const match = command.match(/^\s*&\s+'((?:''|[^'\r\n])*)'\s+-noprofile\s+-executionpolicy\s+bypass\s+-file\s+'((?:''|[^'\r\n])*)'\s*$/i);
520
+ if (!match)
521
+ return null;
522
+ const powerShellPath = decodePowerShellSingleQuotedLiteral(match[1]);
523
+ if (windowsShimPathBasename(powerShellPath) !== "powershell.exe" ||
524
+ normalizeWindowsShimReferencePath(powerShellPath) === null ||
525
+ hasPotentialWindowsPathAlias(powerShellPath)) {
526
+ return null;
221
527
  }
528
+ return decodePowerShellSingleQuotedLiteral(match[2]);
529
+ }
530
+ function hasPotentialWindowsPathAlias(path) {
531
+ const windowsPath = path.replace(/\//g, "\\");
532
+ return windowsPath.split("\\").some((component) => component === "." ||
533
+ component === ".." ||
534
+ /[. ]$/.test(component) ||
535
+ /~\d+(?:\.[^\\]*)?$/i.test(component));
536
+ }
537
+ function windowsShimCommandReferenceDecision(command, shimPath) {
538
+ const decodedShimPath = decodeWindowsShimReferencePath(command);
539
+ if (decodedShimPath === null)
540
+ return "ambiguous";
541
+ const normalizedDecodedPath = normalizeWindowsShimReferencePath(decodedShimPath);
542
+ const normalizedShimPath = normalizeWindowsShimReferencePath(shimPath);
543
+ if (normalizedDecodedPath === null || normalizedShimPath === null) {
544
+ return "ambiguous";
545
+ }
546
+ if (normalizedDecodedPath === normalizedShimPath)
547
+ return "referenced";
548
+ // A distinct fully qualified spelling is not proof that the foreign target
549
+ // cannot resolve to this shim: reparse points, SUBST, mapped drives, and UNC
550
+ // aliases can all refer to the same file. No race-safe file-identity proof is
551
+ // available here, so preserve the proof-owned shim.
552
+ return "ambiguous";
222
553
  }
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))),
554
+ /**
555
+ * Decides whether a proof-owned Windows shim must remain after a hooks.json
556
+ * transition. JSON is strictly validated and decoded before inspection so
557
+ * escaped paths are handled semantically. Unknown future event members are
558
+ * scanned only through the established executable group/command shape; prompt
559
+ * and agent payloads remain inert metadata.
560
+ */
561
+ export function decideWindowsNativeHookShimReference(finalHooksContent, shimPath) {
562
+ if (finalHooksContent === null)
563
+ return "not_referenced";
564
+ const validation = validateCodexHooksConfigStrict(finalHooksContent, {
565
+ platform: "win32",
228
566
  });
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)
567
+ if (!validation.ok)
568
+ return "ambiguous";
569
+ const hooks = validation.root.hooks;
570
+ if (!isWindowsShimReferenceRecord(hooks))
571
+ return "not_referenced";
572
+ let ambiguous = false;
573
+ for (const eventGroups of Object.values(hooks)) {
574
+ if (!Array.isArray(eventGroups))
575
+ continue;
576
+ for (const group of eventGroups) {
577
+ if (!isWindowsShimReferenceRecord(group) ||
578
+ !Array.isArray(group.hooks)) {
579
+ continue;
580
+ }
581
+ for (const handler of group.hooks) {
582
+ if (!isWindowsShimReferenceRecord(handler) ||
583
+ handler.type !== "command") {
584
+ continue;
585
+ }
586
+ for (const command of [
587
+ handler.commandWindows,
588
+ handler.command_windows,
589
+ handler.command,
590
+ ]) {
591
+ if (typeof command !== "string")
592
+ continue;
593
+ const decision = windowsShimCommandReferenceDecision(command, shimPath);
594
+ if (decision === "referenced")
595
+ return decision;
596
+ if (decision === "ambiguous")
597
+ ambiguous = true;
598
+ }
599
+ }
600
+ }
601
+ }
602
+ return ambiguous ? "ambiguous" : "not_referenced";
603
+ }
604
+ function preflightManagedCodexHookTrustState(config, priorManagedHookTrustState, managedHookTrustState) {
605
+ stripManagedCodexHookTrustState(config, {
606
+ priorManagedHookTrustState,
607
+ managedTrustState: managedHookTrustState,
608
+ });
609
+ }
610
+ function injectNativeHookTransactionFailure(stage, target) {
611
+ nativeHookTransactionFailureInjector?.(stage, target);
612
+ }
613
+ function nativeHookTransactionTemporaryPath(path, purpose) {
614
+ if (nativeHookTransactionTemporaryPathOverride) {
615
+ return nativeHookTransactionTemporaryPathOverride(path, purpose);
616
+ }
617
+ nativeHookTransactionSequence += 1;
618
+ return join(dirname(path), `.${basename(path)}.omx-${purpose}-${process.pid}-${nativeHookTransactionSequence}.tmp`);
619
+ }
620
+ function nativeHookTransactionClaimPath(path) {
621
+ nativeHookTransactionSequence += 1;
622
+ return join(dirname(path), `.${basename(path)}.omx-claim-${process.pid}-${nativeHookTransactionSequence}.tmp`);
623
+ }
624
+ async function restoreNativeHookClaim(claimPath, destinationPath, tracker) {
625
+ recordRegularFileSyncOutcome(tracker, await restoreNativeHookClaimNoClobber(claimPath, destinationPath));
626
+ }
627
+ async function atomicWriteNativeHookTransactionArtifact(artifact, content, stage, expectedCurrent, ancestorPrecondition, tracker, onWriteApplied, onWriteStabilized, assertApplied) {
628
+ const temporaryPath = nativeHookTransactionTemporaryPath(artifact.path, "write");
629
+ let temporaryCreated = false;
630
+ let temporarySnapshot;
631
+ let claimPath;
632
+ let claimCreated = false;
633
+ let journaledClaim = false;
634
+ try {
635
+ await assertNativeHookTransactionAncestorPrecondition(ancestorPrecondition);
636
+ await assertApplied?.();
637
+ await mkdir(dirname(artifact.path), { recursive: true });
638
+ await refreshNativeHookTransactionAncestorPrecondition(ancestorPrecondition, artifact.path);
639
+ if (stage === "write") {
640
+ injectNativeHookTransactionFailure("before_temp_write", artifact);
641
+ }
642
+ else {
643
+ injectNativeHookTransactionFailure("before_rollback", artifact);
644
+ }
645
+ await assertNativeHookTransactionArtifactSnapshot(artifact.path, artifact.label, expectedCurrent, stage === "write" ? "precondition" : "rollback");
646
+ await assertNativeHookTransactionAncestorPrecondition(ancestorPrecondition);
647
+ await assertApplied?.();
648
+ const handle = await open(temporaryPath, "wx", artifact.afterTopology.kind === "regular_file"
649
+ ? artifact.afterTopology.mode
650
+ : 0o600);
651
+ temporaryCreated = true;
652
+ try {
653
+ await handle.writeFile(content);
654
+ recordRegularFileSyncOutcome(tracker, await syncNativeHookRegularFile(handle));
655
+ }
656
+ finally {
657
+ await handle.close();
658
+ }
659
+ if (artifact.afterTopology.kind === "regular_file") {
660
+ await chmod(temporaryPath, artifact.afterTopology.mode);
661
+ }
662
+ temporarySnapshot = await assertNativeHookTransactionArtifactState(temporaryPath, `${artifact.label} temporary`, { bytes: content, topology: artifact.afterTopology }, "read-back");
663
+ if (stage === "write") {
664
+ injectNativeHookTransactionFailure("before_rename", artifact);
665
+ }
666
+ else {
667
+ injectNativeHookTransactionFailure("before_rollback_rename", artifact);
668
+ }
669
+ await assertNativeHookTransactionArtifactSnapshot(artifact.path, artifact.label, expectedCurrent, stage === "write" ? "precondition" : "rollback");
670
+ await assertNativeHookTransactionAncestorPrecondition(ancestorPrecondition);
671
+ await assertNativeHookTransactionArtifactSnapshot(temporaryPath, `${artifact.label} temporary`, temporarySnapshot, "read-back");
672
+ await assertApplied?.();
673
+ if (!temporarySnapshot) {
674
+ throw new Error("temporary path was not fully captured after writing");
675
+ }
676
+ injectNativeHookTransactionFailure(stage === "write"
677
+ ? "after_final_rename_validation"
678
+ : "after_final_restore_validation", artifact);
679
+ claimPath = nativeHookTransactionClaimPath(artifact.path);
680
+ const claimRelativePath = relative(ancestorPrecondition.controlledRoot, artifact.path);
681
+ const canJournalClaim = !isAbsolute(claimRelativePath) &&
682
+ claimRelativePath !== ".." &&
683
+ !claimRelativePath.startsWith(`..${sep}`);
684
+ if (expectedCurrent.bytes !== null) {
685
+ if (canJournalClaim) {
686
+ recordRegularFileSyncOutcome(tracker, await persistNativeHookClaimJournal(ancestorPrecondition.controlledRoot, {
687
+ canonicalPath: artifact.path,
688
+ claimPath,
689
+ before: expectedCurrent.bytes,
690
+ after: content,
691
+ }));
692
+ await refreshNativeHookTransactionAncestorPrecondition(ancestorPrecondition, join(ancestorPrecondition.controlledRoot, ".omx", "native-hook-claim-journal.json"));
693
+ journaledClaim = true;
694
+ }
695
+ await rename(artifact.path, claimPath);
696
+ claimCreated = true;
697
+ if (journaledClaim)
698
+ await syncNativeHookClaimParent(claimPath);
699
+ await assertNativeHookTransactionArtifactSnapshot(claimPath, `${artifact.label} replacement claim`, expectedCurrent, stage === "write" ? "precondition" : "rollback");
700
+ }
701
+ await copyFile(temporaryPath, artifact.path, constants.COPYFILE_EXCL);
702
+ if (artifact.afterTopology.kind === "regular_file") {
703
+ await chmod(artifact.path, artifact.afterTopology.mode);
704
+ }
705
+ const installedHandle = await open(artifact.path, "r");
706
+ try {
707
+ recordRegularFileSyncOutcome(tracker, await syncNativeHookRegularFile(installedHandle));
708
+ }
709
+ finally {
710
+ await installedHandle.close();
711
+ }
712
+ await syncNativeHookClaimParent(artifact.path);
713
+ const installedSnapshot = await assertNativeHookTransactionArtifactState(artifact.path, artifact.label, { bytes: content, topology: artifact.afterTopology }, stage === "write" ? "precondition" : "rollback");
714
+ if (claimCreated && claimPath) {
715
+ await rm(claimPath);
716
+ await syncNativeHookClaimParent(claimPath);
717
+ claimCreated = false;
718
+ }
719
+ if (journaledClaim) {
720
+ await clearNativeHookClaimJournal(ancestorPrecondition.controlledRoot);
721
+ journaledClaim = false;
722
+ }
723
+ await rm(temporaryPath);
724
+ temporaryCreated = false;
725
+ onWriteApplied?.(installedSnapshot);
726
+ onWriteStabilized?.(installedSnapshot);
727
+ if (stage === "write") {
728
+ injectNativeHookTransactionFailure("after_rename", artifact);
729
+ }
730
+ await assertNativeHookTransactionArtifactSnapshot(artifact.path, artifact.label, installedSnapshot, stage === "write" ? "precondition" : "rollback");
731
+ }
732
+ catch (error) {
733
+ if (claimCreated && claimPath) {
734
+ try {
735
+ await restoreNativeHookClaim(claimPath, artifact.path, tracker);
736
+ await syncNativeHookClaimParent(artifact.path);
737
+ claimCreated = false;
738
+ if (journaledClaim) {
739
+ await clearNativeHookClaimJournal(ancestorPrecondition.controlledRoot);
740
+ journaledClaim = false;
741
+ }
742
+ }
743
+ catch (recoveryError) {
744
+ 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)}`);
745
+ }
746
+ }
747
+ if (temporaryCreated) {
748
+ try {
749
+ if (!temporarySnapshot) {
750
+ throw new Error("temporary path was not fully captured after writing");
751
+ }
752
+ await assertNativeHookTransactionArtifactSnapshot(temporaryPath, `${artifact.label} temporary`, temporarySnapshot, "read-back");
753
+ await assertNativeHookTransactionAncestorPrecondition(ancestorPrecondition);
754
+ await rm(temporaryPath);
755
+ }
756
+ catch (cleanupError) {
757
+ const message = error instanceof Error ? error.message : String(error);
758
+ 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)}`);
759
+ }
760
+ }
761
+ throw error;
762
+ }
763
+ }
764
+ async function assertNativeHookTransactionArtifactState(path, label, expected, context) {
765
+ const actual = await captureNativeHookTransactionArtifact(path, label);
766
+ if (!nativeHookTransactionSnapshotMatchesExpected(actual, expected)) {
767
+ if (context === "rollback") {
768
+ throw new Error(`Native hook transaction rollback preserved ${path} for manual recovery because ${label} no longer matches the transaction-owned version.`);
769
+ }
770
+ throw new Error(`Native hook transaction ${context} changed for ${label}; refusing to overwrite concurrent content.`);
771
+ }
772
+ return actual;
773
+ }
774
+ async function assertNativeHookTransactionArtifactSnapshot(path, label, expected, context) {
775
+ const actual = await captureNativeHookTransactionArtifact(path, label);
776
+ if (!nativeHookTransactionSnapshotsEqual(actual, expected)) {
777
+ if (context === "rollback") {
778
+ throw new Error(`Native hook transaction rollback preserved ${path} for manual recovery because ${label} no longer matches the transaction-owned version.`);
779
+ }
780
+ throw new Error(`Native hook transaction ${context} changed for ${label}; refusing to overwrite concurrent content.`);
781
+ }
782
+ return actual;
783
+ }
784
+ async function assertNativeHookTransactionPrecondition(precondition) {
785
+ await assertNativeHookTransactionArtifactSnapshot(precondition.path, precondition.label, precondition.before, "precondition");
786
+ }
787
+ async function assertUnmutatedNativeHookTransactionPreconditions(preconditions, applied) {
788
+ const mutatedPaths = new Set(applied.map((entry) => entry.artifact.path));
789
+ for (const precondition of preconditions) {
790
+ if (mutatedPaths.has(precondition.path))
791
+ continue;
792
+ await assertNativeHookTransactionPrecondition(precondition);
793
+ }
794
+ }
795
+ async function assertAppliedNativeHookTransactionSnapshots(applied) {
796
+ for (const entry of applied) {
797
+ await assertNativeHookTransactionArtifactSnapshot(entry.artifact.path, entry.artifact.label, entry.appliedSnapshot, "precondition");
798
+ }
799
+ }
800
+ async function assertNativeHookTransactionStagedRecoveryCopies(applied) {
801
+ for (const entry of applied) {
802
+ if (!entry.stagedDeletionPath ||
803
+ !entry.stagedDeletionSnapshot ||
804
+ entry.stagedDeletionCleaned) {
805
+ continue;
806
+ }
807
+ await assertNativeHookTransactionArtifactSnapshot(entry.stagedDeletionPath, `${entry.artifact.label} staged deletion`, entry.stagedDeletionSnapshot, "rollback");
808
+ }
809
+ }
810
+ async function assertNativeHookTransactionRollbackState(applied) {
811
+ await assertNativeHookTransactionStagedRecoveryCopies(applied);
812
+ await assertAppliedNativeHookTransactionSnapshots(applied);
813
+ }
814
+ async function applyNativeHookTransactionArtifact(artifact, ancestorPrecondition, tracker, applied) {
815
+ await assertNativeHookTransactionArtifactSnapshot(artifact.path, artifact.label, artifact.before, "precondition");
816
+ await assertNativeHookTransactionAncestorPrecondition(ancestorPrecondition);
817
+ if (artifact.after !== null) {
818
+ let entry;
819
+ await atomicWriteNativeHookTransactionArtifact(artifact, artifact.after, "write", artifact.before, ancestorPrecondition, tracker, (appliedSnapshot) => {
820
+ entry = { artifact, appliedSnapshot };
821
+ applied.push(entry);
822
+ }, (stabilizedSnapshot) => {
823
+ if (!entry) {
824
+ throw new Error("native hook transaction write was not registered as applied");
825
+ }
826
+ entry.appliedSnapshot = stabilizedSnapshot;
827
+ }, () => assertAppliedNativeHookTransactionSnapshots(applied));
828
+ if (!entry) {
829
+ throw new Error("native hook transaction write was not registered as applied");
830
+ }
831
+ return entry;
832
+ }
833
+ const stagedDeletionPath = nativeHookTransactionTemporaryPath(artifact.path, "delete");
834
+ let stagedDeletionCreated = false;
835
+ let originalRemoved = false;
836
+ let stagedDeletionSnapshot;
837
+ try {
838
+ injectNativeHookTransactionFailure("before_temp_write", artifact);
839
+ await assertNativeHookTransactionArtifactSnapshot(artifact.path, artifact.label, artifact.before, "precondition");
840
+ await assertNativeHookTransactionAncestorPrecondition(ancestorPrecondition);
841
+ await assertAppliedNativeHookTransactionSnapshots(applied);
842
+ const handle = await open(stagedDeletionPath, "wx", artifact.before.topology.kind === "regular_file"
843
+ ? artifact.before.topology.mode
844
+ : 0o600);
845
+ stagedDeletionCreated = true;
846
+ try {
847
+ await handle.writeFile(artifact.before.bytes);
848
+ recordRegularFileSyncOutcome(tracker, await syncNativeHookRegularFile(handle));
849
+ }
850
+ finally {
851
+ await handle.close();
852
+ }
853
+ if (artifact.before.topology.kind === "regular_file") {
854
+ await chmod(stagedDeletionPath, artifact.before.topology.mode);
855
+ }
856
+ stagedDeletionSnapshot = await assertNativeHookTransactionArtifactState(stagedDeletionPath, `${artifact.label} staged deletion`, { bytes: artifact.before.bytes, topology: artifact.before.topology }, "read-back");
857
+ injectNativeHookTransactionFailure("before_remove", artifact);
858
+ await assertNativeHookTransactionArtifactSnapshot(artifact.path, artifact.label, artifact.before, "precondition");
859
+ await assertNativeHookTransactionAncestorPrecondition(ancestorPrecondition);
860
+ await assertNativeHookTransactionArtifactSnapshot(stagedDeletionPath, `${artifact.label} staged deletion`, stagedDeletionSnapshot, "read-back");
861
+ await assertAppliedNativeHookTransactionSnapshots(applied);
862
+ injectNativeHookTransactionFailure("after_final_remove_validation", artifact);
863
+ await assertNativeHookTransactionArtifactSnapshot(stagedDeletionPath, `${artifact.label} staged deletion`, stagedDeletionSnapshot, "read-back");
864
+ const claimPath = nativeHookTransactionClaimPath(artifact.path);
865
+ const claimRelativePath = relative(ancestorPrecondition.controlledRoot, artifact.path);
866
+ const journaledClaim = !isAbsolute(claimRelativePath) &&
867
+ claimRelativePath !== ".." &&
868
+ !claimRelativePath.startsWith(`..${sep}`);
869
+ if (journaledClaim) {
870
+ recordRegularFileSyncOutcome(tracker, await persistNativeHookClaimJournal(ancestorPrecondition.controlledRoot, {
871
+ canonicalPath: artifact.path,
872
+ claimPath,
873
+ before: artifact.before.bytes,
874
+ after: null,
875
+ }));
876
+ }
877
+ await rename(artifact.path, claimPath);
878
+ if (journaledClaim)
879
+ await syncNativeHookClaimParent(claimPath);
880
+ const entry = {
881
+ artifact,
882
+ appliedSnapshot: { bytes: null, topology: { kind: "absent" } },
883
+ stagedDeletionPath,
884
+ stagedDeletionSnapshot,
885
+ };
886
+ applied.push(entry);
887
+ try {
888
+ await assertNativeHookTransactionAncestorPrecondition(ancestorPrecondition);
889
+ await assertNativeHookTransactionArtifactSnapshot(claimPath, `${artifact.label} removal claim`, artifact.before, "precondition");
890
+ }
891
+ catch (error) {
892
+ try {
893
+ const claimed = await captureNativeHookTransactionArtifact(claimPath, `${artifact.label} removal claim`);
894
+ await restoreNativeHookClaim(claimPath, artifact.path, tracker);
895
+ if (journaledClaim)
896
+ await syncNativeHookClaimParent(artifact.path);
897
+ if (journaledClaim) {
898
+ await clearNativeHookClaimJournal(ancestorPrecondition.controlledRoot);
899
+ }
900
+ await assertNativeHookTransactionArtifactSnapshot(artifact.path, artifact.label, claimed, "precondition");
901
+ const entryIndex = applied.indexOf(entry);
902
+ if (entryIndex >= 0)
903
+ applied.splice(entryIndex, 1);
904
+ }
905
+ catch (recoveryError) {
906
+ 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)}`);
907
+ }
908
+ throw error;
909
+ }
910
+ await rm(claimPath);
911
+ if (journaledClaim)
912
+ await syncNativeHookClaimParent(claimPath);
913
+ if (journaledClaim) {
914
+ await clearNativeHookClaimJournal(ancestorPrecondition.controlledRoot);
915
+ }
916
+ originalRemoved = true;
917
+ injectNativeHookTransactionFailure("after_remove", artifact);
918
+ return entry;
919
+ }
920
+ catch (error) {
921
+ if (stagedDeletionCreated && !originalRemoved) {
922
+ try {
923
+ if (!stagedDeletionSnapshot) {
924
+ throw new Error("staged deletion path was not fully captured after writing");
925
+ }
926
+ await assertNativeHookTransactionArtifactSnapshot(stagedDeletionPath, `${artifact.label} staged deletion`, stagedDeletionSnapshot, "read-back");
927
+ await assertNativeHookTransactionAncestorPrecondition(ancestorPrecondition);
928
+ const claimPath = nativeHookTransactionClaimPath(artifact.path);
929
+ await rename(stagedDeletionPath, claimPath);
930
+ try {
931
+ await assertNativeHookTransactionArtifactSnapshot(claimPath, `${artifact.label} staged deletion cleanup claim`, stagedDeletionSnapshot, "read-back");
932
+ }
933
+ catch (claimError) {
934
+ try {
935
+ await restoreNativeHookClaim(claimPath, stagedDeletionPath, tracker);
936
+ }
937
+ catch (recoveryError) {
938
+ 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)}`);
939
+ }
940
+ throw claimError;
941
+ }
942
+ await rm(claimPath);
943
+ }
944
+ catch (cleanupError) {
945
+ const message = error instanceof Error ? error.message : String(error);
946
+ 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)}`);
947
+ }
948
+ }
949
+ throw error;
950
+ }
951
+ }
952
+ async function verifyNativeHookTransactionArtifact(applied) {
953
+ const { artifact } = applied;
954
+ injectNativeHookTransactionFailure("before_readback", artifact);
955
+ const actual = await captureNativeHookTransactionArtifact(artifact.path, artifact.label);
956
+ if (!nativeHookTransactionSnapshotsEqual(actual, applied.appliedSnapshot)) {
957
+ throw new Error(`Native hook transaction read-back changed for ${artifact.label}; refusing to overwrite concurrent content.`);
958
+ }
959
+ if (artifact.after === null)
247
960
  return;
248
- if (await ensureBackup(configPath, existsSync(configPath), backupContext, options)) {
249
- summary.backedUp += 1;
961
+ const content = decodeNativeHookTransactionUtf8(actual.bytes, artifact.label);
962
+ if (artifact.kind === "hooks") {
963
+ const validation = validateCodexHooksConfigStrict(content, {
964
+ platform: artifact.hookPlatform,
965
+ });
966
+ if (!validation.ok) {
967
+ throw new Error(`Native hook transaction wrote invalid hooks.json: ${validation.error.message}`);
968
+ }
969
+ }
970
+ if (artifact.kind === "config")
971
+ TOML.parse(content);
972
+ }
973
+ async function restoreNativeHookTransactionArtifact(applied, ancestorPrecondition, tracker, assertRollbackState) {
974
+ const { artifact } = applied;
975
+ const current = await captureNativeHookTransactionArtifact(artifact.path, artifact.label);
976
+ if (!nativeHookTransactionSnapshotsEqual(current, applied.appliedSnapshot)) {
977
+ throw new Error(`Native hook transaction rollback preserved ${artifact.path} for manual recovery because ${artifact.label} no longer matches the transaction-owned version.`);
978
+ }
979
+ if (artifact.before.bytes === null) {
980
+ injectNativeHookTransactionFailure("before_rollback", artifact);
981
+ injectNativeHookTransactionFailure("before_rollback_remove", artifact);
982
+ await assertNativeHookTransactionArtifactSnapshot(artifact.path, artifact.label, applied.appliedSnapshot, "rollback");
983
+ await assertNativeHookTransactionAncestorPrecondition(ancestorPrecondition);
984
+ await assertRollbackState();
985
+ injectNativeHookTransactionFailure("after_final_restore_validation", artifact);
986
+ const claimPath = nativeHookTransactionClaimPath(artifact.path);
987
+ await rename(artifact.path, claimPath);
988
+ applied.appliedSnapshot = { bytes: null, topology: { kind: "absent" } };
989
+ try {
990
+ await assertNativeHookTransactionArtifactSnapshot(claimPath, `${artifact.label} rollback removal claim`, current, "rollback");
991
+ }
992
+ catch (error) {
993
+ try {
994
+ const claimed = await captureNativeHookTransactionArtifact(claimPath, `${artifact.label} rollback removal claim`);
995
+ await restoreNativeHookClaim(claimPath, artifact.path, tracker);
996
+ await assertNativeHookTransactionArtifactSnapshot(artifact.path, artifact.label, claimed, "rollback");
997
+ }
998
+ catch (recoveryError) {
999
+ 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)}`);
1000
+ }
1001
+ throw error;
1002
+ }
1003
+ await rm(claimPath);
250
1004
  }
1005
+ else {
1006
+ const restoreArtifact = {
1007
+ ...artifact,
1008
+ after: artifact.before.bytes,
1009
+ afterTopology: artifact.before.topology,
1010
+ };
1011
+ await atomicWriteNativeHookTransactionArtifact(restoreArtifact, artifact.before.bytes, "rollback", applied.appliedSnapshot, ancestorPrecondition, tracker, (restoredSnapshot) => {
1012
+ applied.appliedSnapshot = restoredSnapshot;
1013
+ }, (stabilizedSnapshot) => {
1014
+ applied.appliedSnapshot = stabilizedSnapshot;
1015
+ }, assertRollbackState);
1016
+ }
1017
+ const restored = await assertNativeHookTransactionArtifactSnapshot(artifact.path, artifact.label, applied.appliedSnapshot, "rollback");
1018
+ if (!nativeHookTransactionSnapshotMatchesExpected(restored, {
1019
+ bytes: artifact.before.bytes,
1020
+ topology: artifact.before.topology,
1021
+ })) {
1022
+ throw new Error(`Native hook transaction rollback preserved ${artifact.path} for manual recovery because ${artifact.label} no longer matches the expected restored version.`);
1023
+ }
1024
+ }
1025
+ async function cleanupNativeHookTransactionStagedDeletions(applied, ancestorPrecondition, tracker, preconditions, rollbackApplied) {
1026
+ for (const entry of applied) {
1027
+ if (!entry.stagedDeletionPath ||
1028
+ !entry.stagedDeletionSnapshot ||
1029
+ entry.stagedDeletionCleaned) {
1030
+ continue;
1031
+ }
1032
+ injectNativeHookTransactionFailure("before_staged_cleanup", entry.artifact);
1033
+ if (preconditions) {
1034
+ await assertUnmutatedNativeHookTransactionPreconditions(preconditions, applied);
1035
+ await assertAppliedNativeHookTransactionSnapshots(applied);
1036
+ }
1037
+ if (rollbackApplied) {
1038
+ await assertNativeHookTransactionRollbackState(rollbackApplied);
1039
+ }
1040
+ const stagedDeletionPath = entry.stagedDeletionPath;
1041
+ const stagedDeletionSnapshot = entry.stagedDeletionSnapshot;
1042
+ await assertNativeHookTransactionArtifactSnapshot(stagedDeletionPath, `${entry.artifact.label} staged deletion`, stagedDeletionSnapshot, "read-back");
1043
+ await assertNativeHookTransactionAncestorPrecondition(ancestorPrecondition);
1044
+ const claimPath = nativeHookTransactionClaimPath(entry.artifact.path);
1045
+ await rename(stagedDeletionPath, claimPath);
1046
+ try {
1047
+ await assertNativeHookTransactionArtifactSnapshot(claimPath, `${entry.artifact.label} staged deletion cleanup claim`, stagedDeletionSnapshot, "read-back");
1048
+ }
1049
+ catch (error) {
1050
+ try {
1051
+ await restoreNativeHookClaim(claimPath, stagedDeletionPath, tracker);
1052
+ }
1053
+ catch (recoveryError) {
1054
+ 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)}`);
1055
+ }
1056
+ throw error;
1057
+ }
1058
+ await rm(claimPath);
1059
+ entry.stagedDeletionCleaned = true;
1060
+ if (rollbackApplied) {
1061
+ await assertNativeHookTransactionRollbackState(rollbackApplied);
1062
+ }
1063
+ }
1064
+ }
1065
+ function nativeHookTransactionBackupPath(artifactPath, backupContext) {
1066
+ const relativePath = relative(backupContext.baseRoot, artifactPath);
1067
+ const safeRelativePath = relativePath.startsWith("..") || relativePath === ""
1068
+ ? artifactPath.replace(/^[/]+/, "")
1069
+ : relativePath;
1070
+ return join(backupContext.backupRoot, safeRelativePath);
1071
+ }
1072
+ async function ensureSnapshotBackup(artifact, backupContext, options, tracker) {
1073
+ const bytes = artifact.before.bytes;
1074
+ if (bytes === null)
1075
+ return false;
1076
+ const backupPath = nativeHookTransactionBackupPath(artifact.path, backupContext);
251
1077
  if (!options.dryRun) {
252
- await mkdir(dirname(configPath), { recursive: true });
253
- await writeFile(configPath, nextConfig);
1078
+ const relativeParent = relative(backupContext.baseRoot, dirname(backupPath));
1079
+ if (isAbsolute(relativeParent) ||
1080
+ relativeParent === ".." ||
1081
+ relativeParent.startsWith(`..${sep}`)) {
1082
+ throw new Error(`Refusing to back up ${artifact.path} outside controlled backup root.`);
1083
+ }
1084
+ let currentPath = backupContext.baseRoot;
1085
+ for (const component of relativeParent.split(sep).filter(Boolean)) {
1086
+ currentPath = join(currentPath, component);
1087
+ try {
1088
+ const currentStat = await lstat(currentPath);
1089
+ if (currentStat.isSymbolicLink() || !currentStat.isDirectory()) {
1090
+ throw new Error(`Refusing to use unsafe backup ancestor ${currentPath}.`);
1091
+ }
1092
+ }
1093
+ catch (error) {
1094
+ if (!isMissingPathError(error))
1095
+ throw error;
1096
+ await mkdir(currentPath);
1097
+ const createdStat = await lstat(currentPath);
1098
+ if (createdStat.isSymbolicLink() || !createdStat.isDirectory()) {
1099
+ throw new Error(`Refusing to use unsafe created backup ancestor ${currentPath}.`);
1100
+ }
1101
+ }
1102
+ }
1103
+ const handle = await open(backupPath, "wx", 0o600);
1104
+ try {
1105
+ await handle.writeFile(bytes);
1106
+ recordRegularFileSyncOutcome(tracker, await syncNativeHookRegularFile(handle));
1107
+ }
1108
+ finally {
1109
+ await handle.close();
1110
+ }
1111
+ const backupStat = await lstat(backupPath);
1112
+ if (backupStat.isSymbolicLink() || !backupStat.isFile() || backupStat.nlink !== 1) {
1113
+ throw new Error(`Refusing unsafe native hook transaction backup ${backupPath}.`);
1114
+ }
1115
+ const writtenBytes = await readFile(backupPath);
1116
+ if (!writtenBytes.equals(bytes)) {
1117
+ throw new Error(`Native hook transaction backup verification failed for ${backupPath}.`);
1118
+ }
254
1119
  }
255
- summary.updated += 1;
256
1120
  if (options.verbose) {
257
- console.log(` ${options.dryRun ? "would migrate" : "migrated"} legacy hooks.json trust state to ${configPath}`);
1121
+ console.log(` backup ${artifact.path} -> ${backupPath}`);
1122
+ }
1123
+ return true;
1124
+ }
1125
+ async function commitNativeHookTransaction(artifacts, preconditions, ancestorPrecondition, backupContext, tracker, summary, options) {
1126
+ if (options.dryRun)
1127
+ return;
1128
+ await assertNativeHookTransactionAncestorPrecondition(ancestorPrecondition);
1129
+ for (const precondition of preconditions) {
1130
+ injectNativeHookTransactionFailure("before_precondition", precondition);
1131
+ await assertNativeHookTransactionPrecondition(precondition);
1132
+ }
1133
+ if (artifacts.length === 0)
1134
+ return;
1135
+ for (const artifact of artifacts) {
1136
+ injectNativeHookTransactionFailure("before_backup", artifact);
1137
+ if (await ensureSnapshotBackup(artifact, backupContext, options, tracker)) {
1138
+ summary.backedUp += 1;
1139
+ }
1140
+ }
1141
+ await assertUnmutatedNativeHookTransactionPreconditions(preconditions, []);
1142
+ await assertNativeHookTransactionAncestorPrecondition(ancestorPrecondition);
1143
+ const applied = [];
1144
+ let stagedCleanupStarted = false;
1145
+ try {
1146
+ for (const artifact of artifacts) {
1147
+ await assertUnmutatedNativeHookTransactionPreconditions(preconditions, applied);
1148
+ await assertAppliedNativeHookTransactionSnapshots(applied);
1149
+ const entry = await applyNativeHookTransactionArtifact(artifact, ancestorPrecondition, tracker, applied);
1150
+ await verifyNativeHookTransactionArtifact(entry);
1151
+ await assertUnmutatedNativeHookTransactionPreconditions(preconditions, applied);
1152
+ await assertAppliedNativeHookTransactionSnapshots(applied);
1153
+ }
1154
+ await assertUnmutatedNativeHookTransactionPreconditions(preconditions, applied);
1155
+ await assertAppliedNativeHookTransactionSnapshots(applied);
1156
+ stagedCleanupStarted = true;
1157
+ await cleanupNativeHookTransactionStagedDeletions(applied, ancestorPrecondition, tracker, preconditions);
1158
+ injectNativeHookTransactionFailure("after_staged_cleanup", artifacts[artifacts.length - 1]);
1159
+ await assertUnmutatedNativeHookTransactionPreconditions(preconditions, applied);
1160
+ await assertNativeHookTransactionAncestorPrecondition(ancestorPrecondition);
1161
+ await assertAppliedNativeHookTransactionSnapshots(applied);
1162
+ }
1163
+ catch (error) {
1164
+ if (stagedCleanupStarted &&
1165
+ applied.some((entry) => entry.stagedDeletionCleaned)) {
1166
+ throw new Error(`Native hook transaction committed but staged deletion cleanup failed during finalization: ${error instanceof Error ? error.message : String(error)}`);
1167
+ }
1168
+ const rollbackFailures = [];
1169
+ const restored = [];
1170
+ try {
1171
+ await assertNativeHookTransactionRollbackState(applied);
1172
+ }
1173
+ catch (rollbackError) {
1174
+ rollbackFailures.push(`recovery preflight: ${rollbackError instanceof Error ? rollbackError.message : String(rollbackError)}`);
1175
+ }
1176
+ if (rollbackFailures.length === 0) {
1177
+ for (const entry of [...applied].reverse()) {
1178
+ try {
1179
+ await assertNativeHookTransactionRollbackState(applied);
1180
+ await restoreNativeHookTransactionArtifact(entry, ancestorPrecondition, tracker, () => assertNativeHookTransactionRollbackState(applied));
1181
+ restored.push(entry);
1182
+ await assertNativeHookTransactionRollbackState(applied);
1183
+ }
1184
+ catch (rollbackError) {
1185
+ rollbackFailures.push(`${entry.artifact.label}: ${rollbackError instanceof Error ? rollbackError.message : String(rollbackError)}`);
1186
+ }
1187
+ }
1188
+ }
1189
+ if (rollbackFailures.length === 0) {
1190
+ try {
1191
+ await assertNativeHookTransactionRollbackState(applied);
1192
+ await cleanupNativeHookTransactionStagedDeletions(restored, ancestorPrecondition, tracker, undefined, applied);
1193
+ await assertNativeHookTransactionRollbackState(applied);
1194
+ }
1195
+ catch (cleanupError) {
1196
+ rollbackFailures.push(`staged deletion cleanup: ${cleanupError instanceof Error ? cleanupError.message : String(cleanupError)}`);
1197
+ }
1198
+ }
1199
+ const message = error instanceof Error ? error.message : String(error);
1200
+ if (rollbackFailures.length > 0) {
1201
+ throw new Error(`Native hook transaction failed (${message}) and rollback failed; manual recovery is required (${rollbackFailures.join("; ")}).`);
1202
+ }
1203
+ throw new Error(`Native hook transaction failed and was rolled back: ${message}`);
258
1204
  }
259
1205
  }
260
1206
  async function ensureBackup(destinationPath, contentChanged, backupContext, options) {
@@ -503,7 +1449,10 @@ async function promptForFirstPartyMcpRemoval(configPath, registrationKinds) {
503
1449
  }
504
1450
  }
505
1451
  function hasPersistedSetupPreferences(preferences) {
506
- return Boolean(preferences?.scope || preferences?.installMode || preferences?.teamMode);
1452
+ return Boolean(preferences?.scope ||
1453
+ preferences?.installMode ||
1454
+ preferences?.teamMode ||
1455
+ typeof preferences?.mergeAgents === "boolean");
507
1456
  }
508
1457
  function formatPersistedSetupPreferenceSummary(preferences) {
509
1458
  const summary = [
@@ -513,6 +1462,9 @@ function formatPersistedSetupPreferenceSummary(preferences) {
513
1462
  ];
514
1463
  if (preferences.teamMode)
515
1464
  summary.push(`teamMode=${preferences.teamMode}`);
1465
+ if (typeof preferences.mergeAgents === "boolean") {
1466
+ summary.push(`mergeAgents=${preferences.mergeAgents}`);
1467
+ }
516
1468
  return summary.join(", ");
517
1469
  }
518
1470
  async function promptForPersistedSetupReview(preferences) {
@@ -612,7 +1564,7 @@ function classifyPluginDeveloperInstructions(value) {
612
1564
  }
613
1565
  if (normalized ===
614
1566
  normalizeDeveloperInstructionsText(LEGACY_PLUGIN_DEVELOPER_INSTRUCTIONS)) {
615
- return "current";
1567
+ return "historical";
616
1568
  }
617
1569
  if (normalized === normalizeDeveloperInstructionsText(OMX_DEVELOPER_INSTRUCTIONS)) {
618
1570
  return "historical";
@@ -669,11 +1621,8 @@ function legacyPluginDeveloperInstructionsDecision(choice, state = "missing") {
669
1621
  reason: "legacy explicit skip policy",
670
1622
  };
671
1623
  }
672
- async function resolvePluginDeveloperInstructionsDecision(configPath, options) {
673
- const existing = existsSync(configPath)
674
- ? await readFile(configPath, "utf-8")
675
- : "";
676
- const value = readRootDeveloperInstructions(existing);
1624
+ async function resolvePluginDeveloperInstructionsDecision(existingConfig, configPath, options) {
1625
+ const value = readRootDeveloperInstructions(existingConfig);
677
1626
  if (value === undefined) {
678
1627
  if (options.pluginDeveloperInstructionsPrompt) {
679
1628
  return legacyPluginDeveloperInstructionsDecision(await options.pluginDeveloperInstructionsPrompt(configPath), "missing");
@@ -1016,8 +1965,7 @@ async function persistSetupPreferences(projectRoot, preferences, options) {
1016
1965
  console.log(` dry-run: skip persisting ${scopePath}`);
1017
1966
  return;
1018
1967
  }
1019
- await mkdir(dirname(scopePath), { recursive: true });
1020
- await writeFile(scopePath, JSON.stringify(preferences, null, 2) + "\n");
1968
+ await writePersistedSetupPreferences(projectRoot, preferences);
1021
1969
  if (options.verbose)
1022
1970
  console.log(` Wrote ${scopePath}`);
1023
1971
  }
@@ -1181,176 +2129,246 @@ function insertRootTomlKey(config, line) {
1181
2129
  const after = lines.slice(firstTableIndex);
1182
2130
  return [...before, line, "", ...after].join("\n") + "\n";
1183
2131
  }
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
- }
2132
+ function buildPluginModeHooksConfigPlan(existingConfig, existingHooksContent, pkgRoot, hooksPath, codexHomeDir, options) {
2133
+ const managedHookOptions = {
2134
+ platform: options.platform,
2135
+ codexHomeDir,
2136
+ };
2137
+ const managedHooksPlan = options.pluginScopedHooks
2138
+ ? existingHooksContent === null
2139
+ ? null
2140
+ : planManagedCodexHooksRemoval(existingHooksContent, hooksPath, managedHookOptions)
2141
+ : planManagedCodexHooksMerge(existingHooksContent, pkgRoot, hooksPath, managedHookOptions);
2142
+ if (managedHooksPlan && !managedHooksPlan.ok)
2143
+ throw managedHooksPlan.error;
2144
+ const managedHookTrustState = managedHooksPlan?.finalTrustState ?? {};
2145
+ const priorManagedHookTrustState = managedHooksPlan?.priorTrustState ?? {};
2146
+ const legacyHookTrustState = managedHooksPlan?.legacyTrustState ?? {};
2147
+ preflightManagedCodexHookTrustState(existingConfig, priorManagedHookTrustState, managedHookTrustState);
2148
+ const configAfterLegacyCleanup = buildPluginModeLegacyConfig(existingConfig, {
2149
+ ...options,
2150
+ managedHookTrustState,
2151
+ priorManagedHookTrustState,
2152
+ });
2153
+ const configWithRuntimeFeatures = upsertPluginModeRuntimeFeatureFlags(configAfterLegacyCleanup, options.codexHookFeatureFlag, {
2154
+ pluginScopedHooks: options.pluginScopedHooks,
2155
+ preserveNativeHooks: options.pluginScopedHooks && managedHooksPlan?.hasForeignHooks === true,
2156
+ });
2157
+ return {
2158
+ finalConfig: upsertManagedCodexHookTrustState(configWithRuntimeFeatures, pkgRoot, hooksPath, {
2159
+ ...managedHookOptions,
2160
+ managedTrustState: managedHookTrustState,
2161
+ priorManagedHookTrustState,
2162
+ legacyHookTrustState,
2163
+ }),
2164
+ hooksFinalContent: managedHooksPlan ? managedHooksPlan.finalContent : existingHooksContent,
2165
+ hooksRemovedCount: managedHooksPlan?.removedCount ?? 0,
2166
+ cleanedLegacyConfig: configAfterLegacyCleanup !== existingConfig,
2167
+ diagnostics: managedHooksPlan?.diagnostics ?? [],
2168
+ };
1278
2169
  }
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";
2170
+ function buildPluginDeveloperInstructionsConfigPlan(existingConfig, decision) {
2171
+ if (decision.action === "preserve") {
2172
+ return {
2173
+ finalConfig: existingConfig,
2174
+ result: decision.state === "missing" ? "skipped" : "exists",
2175
+ };
1289
2176
  }
1290
2177
  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);
1309
- }
1310
- summary.updated += 1;
1311
- if (options.verbose) {
1312
- console.log(` ${options.dryRun ? "would add" : "added"} plugin developer_instructions default to ${configPath}`);
2178
+ const hasExistingDeveloperInstructions = rootHasTomlKey(existingConfig, "developer_instructions");
2179
+ if (hasExistingDeveloperInstructions && decision.action === "add") {
2180
+ return { finalConfig: existingConfig, result: "exists" };
1313
2181
  }
1314
- return "updated";
2182
+ return {
2183
+ finalConfig: hasExistingDeveloperInstructions
2184
+ ? replaceRootTomlKey(existingConfig, "developer_instructions", line)
2185
+ : insertRootTomlKey(existingConfig, line),
2186
+ result: "updated",
2187
+ };
1315
2188
  }
1316
- async function cleanupPluginModeLegacyConfig(configPath, backupContext, options) {
1317
- if (!existsSync(configPath))
1318
- return false;
1319
- const original = await readFile(configPath, "utf-8");
2189
+ function buildPluginModeLegacyConfig(original, options) {
1320
2190
  const preservedFirstPartyMcp = options.preserveFirstPartyMcp
1321
2191
  ? extractFirstPartyOmxMcpSections(original)
1322
2192
  : "";
1323
2193
  let config = original;
1324
2194
  config = stripFirstPartyOmxMcpSections(config);
1325
- config = stripExistingOmxBlocks(config).cleaned;
2195
+ config = stripExistingOmxBlocks(config, {
2196
+ managedTrustState: options.managedHookTrustState,
2197
+ priorManagedHookTrustState: options.priorManagedHookTrustState,
2198
+ }).cleaned;
1326
2199
  config = stripExistingSharedMcpRegistryBlock(config).cleaned;
1327
2200
  config = stripPluginModeLegacyRootDefaults(config, options.developerInstructionsDecision);
1328
2201
  config = stripOmxSeededBehavioralDefaults(config);
1329
2202
  config = stripOmxFeatureFlags(config, { preserveMultiAgent: true });
1330
- config = stripManagedCodexHookTrustState(config);
2203
+ config = stripManagedCodexHookTrustState(config, {
2204
+ managedTrustState: options.managedHookTrustState,
2205
+ priorManagedHookTrustState: options.priorManagedHookTrustState,
2206
+ });
1331
2207
  config = stripOmxEnvSettings(config);
1332
2208
  if (preservedFirstPartyMcp) {
1333
2209
  config = `${config.trimEnd()}\n\n${preservedFirstPartyMcp}\n`;
1334
2210
  }
1335
2211
  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 });
2212
+ return config.length > 0 ? `${config}\n` : "";
2213
+ }
2214
+ async function planNativeHookSetupTransaction(options) {
2215
+ const existingConfigSnapshot = options.configSnapshot;
2216
+ const existingHooksSnapshot = await captureNativeHookTransactionArtifact(options.hooksPath, `native hooks ${options.hooksPath}`);
2217
+ const existingConfig = existingConfigSnapshot.bytes
2218
+ ? decodeNativeHookTransactionUtf8(existingConfigSnapshot.bytes, `config ${options.configPath}`)
2219
+ : "";
2220
+ const existingHooksContent = existingHooksSnapshot.bytes
2221
+ ? decodeNativeHookTransactionUtf8(existingHooksSnapshot.bytes, `native hooks ${options.hooksPath}`)
2222
+ : null;
2223
+ let finalConfig = existingConfig;
2224
+ let finalHooksContent = existingHooksContent;
2225
+ let hooksRemovedCount = 0;
2226
+ let cleanedLegacyConfig = false;
2227
+ let pluginMarketplaceResult = "unchanged";
2228
+ let pluginDeveloperInstructionsResult = "skipped";
2229
+ let diagnostics = [];
2230
+ let notifyMetadataArtifact = null;
2231
+ let notifyMetadataPrecondition = null;
2232
+ let modelUpgrade;
2233
+ let repairedLegacyTeamRunTable = false;
2234
+ if (options.isPluginInstallMode) {
2235
+ const pluginPlan = buildPluginModeHooksConfigPlan(existingConfig, existingHooksContent, options.pkgRoot, options.hooksPath, options.codexHomeDir, {
2236
+ codexHookFeatureFlag: options.codexHookFeatureFlag,
2237
+ pluginScopedHooks: options.pluginScopedHooks,
2238
+ preserveFirstPartyMcp: options.preserveFirstPartyMcp,
2239
+ developerInstructionsDecision: options.pluginDeveloperInstructionsDecision,
2240
+ platform: options.platform,
2241
+ });
2242
+ finalConfig = pluginPlan.finalConfig;
2243
+ finalHooksContent = pluginPlan.hooksFinalContent;
2244
+ hooksRemovedCount = pluginPlan.hooksRemovedCount;
2245
+ cleanedLegacyConfig = pluginPlan.cleanedLegacyConfig;
2246
+ diagnostics = pluginPlan.diagnostics;
2247
+ if (options.pluginMarketplaceAvailable) {
2248
+ const withMarketplace = upsertLocalOmxMarketplaceRegistration(upsertLocalOmxPluginMcpServerEnablement(upsertLocalOmxPluginEnablement(finalConfig), options.mcpMode === "compat", { removeWhenDisabled: options.removeFirstPartyMcp }), options.pkgRoot);
2249
+ pluginMarketplaceResult =
2250
+ withMarketplace === finalConfig ? "unchanged" : "updated";
2251
+ finalConfig = withMarketplace;
1345
2252
  }
1346
2253
  else {
1347
- await writeFile(configPath, nextConfig);
2254
+ pluginMarketplaceResult = "unavailable";
1348
2255
  }
2256
+ const developerInstructionsPlan = buildPluginDeveloperInstructionsConfigPlan(finalConfig, options.pluginDeveloperInstructionsDecision);
2257
+ finalConfig = developerInstructionsPlan.finalConfig;
2258
+ pluginDeveloperInstructionsResult = developerInstructionsPlan.result;
1349
2259
  }
1350
- if (options.verbose) {
1351
- console.log(` ${options.dryRun ? "would clean" : nextConfig.length === 0 ? "removed" : "cleaned"} legacy OMX config ${basename(configPath)}`);
2260
+ else {
2261
+ const managedHooksPlan = planManagedCodexHooksMerge(existingHooksContent, options.pkgRoot, options.hooksPath, {
2262
+ platform: options.platform,
2263
+ codexHomeDir: options.codexHomeDir,
2264
+ });
2265
+ if (!managedHooksPlan.ok)
2266
+ throw managedHooksPlan.error;
2267
+ if (managedHooksPlan.finalContent === null) {
2268
+ throw new Error("Native hook merge unexpectedly produced no hooks.json content.");
2269
+ }
2270
+ finalHooksContent = managedHooksPlan.finalContent;
2271
+ hooksRemovedCount = managedHooksPlan.removedCount;
2272
+ diagnostics = managedHooksPlan.diagnostics;
2273
+ const managedConfigPlan = await planManagedConfig(options.hooksPath, options.pkgRoot, options.sharedMcpRegistry, options.mcpMode, options.preserveFirstPartyMcp, options.resolvedScope, options.codexHomeDir, {
2274
+ modelUpgradePrompt: options.modelUpgradePrompt,
2275
+ existingConfig,
2276
+ verbose: false,
2277
+ statusLinePreset: options.statusLinePreset,
2278
+ forceStatusLinePreset: options.forceStatusLinePreset,
2279
+ codexHookFeatureFlag: options.codexHookFeatureFlag,
2280
+ managedHookTrustState: managedHooksPlan.finalTrustState,
2281
+ priorManagedHookTrustState: managedHooksPlan.priorTrustState,
2282
+ legacyHookTrustState: managedHooksPlan.legacyTrustState,
2283
+ hookCommandPlatform: options.platform,
2284
+ notifyMetadataSnapshot: options.notifyMetadataSnapshot,
2285
+ });
2286
+ finalConfig = managedConfigPlan.finalConfig;
2287
+ if (managedConfigPlan.currentModel &&
2288
+ managedConfigPlan.modelOverride &&
2289
+ managedConfigPlan.currentModel !== managedConfigPlan.modelOverride) {
2290
+ modelUpgrade = {
2291
+ currentModel: managedConfigPlan.currentModel,
2292
+ modelOverride: managedConfigPlan.modelOverride,
2293
+ };
2294
+ }
2295
+ repairedLegacyTeamRunTable = managedConfigPlan.repairedLegacyTeamRunTable;
2296
+ const notifyPlan = managedConfigPlan.notifyPlan;
2297
+ if (notifyPlan.metadataSnapshot && notifyPlan.metadataPath) {
2298
+ notifyMetadataPrecondition = nativeHookTransactionPrecondition("metadata", notifyPlan.metadataPath, `notification metadata ${notifyPlan.metadataPath}`, notifyPlan.metadataSnapshot);
2299
+ }
2300
+ if (notifyPlan.metadataPath && notifyPlan.metadata) {
2301
+ const metadataBefore = options.notifyMetadataSnapshot;
2302
+ if (!metadataBefore) {
2303
+ throw new Error(`Missing notification metadata snapshot for ${notifyPlan.metadataPath}; refusing to plan from a newly captured baseline.`);
2304
+ }
2305
+ notifyMetadataArtifact = nativeHookTransactionArtifact("metadata", notifyPlan.metadataPath, `notification metadata ${notifyPlan.metadataPath}`, metadataBefore, Buffer.from(JSON.stringify(notifyPlan.metadata, null, 2) + "\n", "utf-8"));
2306
+ if (!notifyMetadataPrecondition) {
2307
+ notifyMetadataPrecondition = nativeHookTransactionPrecondition("metadata", notifyPlan.metadataPath, `notification metadata ${notifyPlan.metadataPath}`, metadataBefore);
2308
+ }
2309
+ }
1352
2310
  }
1353
- return true;
2311
+ if (options.isPluginInstallMode && options.sharedMcpRegistry.servers.length > 0) {
2312
+ finalConfig = mergeSharedMcpRegistryBlock(finalConfig, options.sharedMcpRegistry.servers, options.sharedMcpRegistry.sourcePath);
2313
+ }
2314
+ try {
2315
+ TOML.parse(finalConfig);
2316
+ }
2317
+ catch (error) {
2318
+ throw new Error(`Refusing to write invalid planned config.toml: ${error instanceof Error ? error.message : String(error)}`);
2319
+ }
2320
+ const hookArtifact = nativeHookTransactionArtifact("hooks", options.hooksPath, `native hooks ${options.hooksPath}`, existingHooksSnapshot, finalHooksContent === null ? null : Buffer.from(finalHooksContent, "utf-8"), options.platform);
2321
+ const configArtifact = nativeHookTransactionArtifact("config", options.configPath, `config ${options.configPath}`, existingConfigSnapshot, Buffer.from(finalConfig, "utf-8"));
2322
+ let shimArtifact = null;
2323
+ let shimPrecondition = null;
2324
+ if (options.platform === "win32") {
2325
+ const shimPath = buildManagedCodexNativeHookWindowsShimPath(options.codexHomeDir);
2326
+ const shimSnapshot = await captureNativeHookTransactionArtifact(shimPath, `native hook Windows shim ${shimPath}`);
2327
+ const shimAfter = Buffer.from(buildManagedCodexNativeHookWindowsShimContent(options.pkgRoot), "utf-8");
2328
+ assertWindowsNativeHookShimOwnership(shimPath, shimSnapshot.bytes, shimAfter);
2329
+ const shimReference = decideWindowsNativeHookShimReference(finalHooksContent, shimPath);
2330
+ shimArtifact = nativeHookTransactionArtifact("shim", shimPath, `native hook Windows shim ${shimPath}`, shimSnapshot, options.isPluginInstallMode && options.pluginScopedHooks &&
2331
+ shimReference === "not_referenced"
2332
+ ? null
2333
+ : shimAfter);
2334
+ shimPrecondition = nativeHookTransactionPrecondition("shim", shimPath, `native hook Windows shim ${shimPath}`, shimSnapshot);
2335
+ }
2336
+ const windowsShimCreateOrUpdateArtifact = shimArtifact && shimArtifact.after !== null ? shimArtifact : null;
2337
+ const windowsShimDeletionArtifact = shimArtifact && shimArtifact.after === null ? shimArtifact : null;
2338
+ const artifacts = options.platform === "win32"
2339
+ ? windowsShimDeletionArtifact
2340
+ ? [
2341
+ hookArtifact,
2342
+ windowsShimDeletionArtifact,
2343
+ notifyMetadataArtifact,
2344
+ configArtifact,
2345
+ ]
2346
+ : [
2347
+ windowsShimCreateOrUpdateArtifact,
2348
+ hookArtifact,
2349
+ notifyMetadataArtifact,
2350
+ configArtifact,
2351
+ ]
2352
+ : [hookArtifact, notifyMetadataArtifact, configArtifact];
2353
+ const preconditions = [
2354
+ shimPrecondition,
2355
+ nativeHookTransactionPrecondition("hooks", options.hooksPath, `native hooks ${options.hooksPath}`, existingHooksSnapshot),
2356
+ nativeHookTransactionPrecondition("config", options.configPath, `config ${options.configPath}`, existingConfigSnapshot),
2357
+ notifyMetadataPrecondition,
2358
+ ];
2359
+ return {
2360
+ artifacts: artifacts.filter((artifact) => artifact !== null),
2361
+ preconditions: preconditions.filter((precondition) => precondition !== null),
2362
+ finalConfig,
2363
+ hooksRemovedCount,
2364
+ pluginScopedHooks: options.pluginScopedHooks,
2365
+ cleanedLegacyConfig,
2366
+ pluginMarketplaceResult,
2367
+ pluginDeveloperInstructionsResult,
2368
+ diagnostics,
2369
+ modelUpgrade,
2370
+ repairedLegacyTeamRunTable,
2371
+ };
1354
2372
  }
1355
2373
  export async function setup(options = {}) {
1356
2374
  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 +2390,15 @@ export async function setup(options = {}) {
1372
2390
  Boolean(persistedPreferences?.teamMode) &&
1373
2391
  (!persistedPreferences?.scope ||
1374
2392
  persistedPreferences.scope === effectiveScopeForInstallMode);
2393
+ const wouldUsePersistedMergeAgents = !options.mergeAgentsPolicy &&
2394
+ typeof options.mergeAgents !== "boolean" &&
2395
+ resolvePersistedSetupMergeAgents(persistedPreferences, effectiveScopeForInstallMode) !== undefined;
1375
2396
  const shouldReviewPersistedSetup = hasPersistedSetupPreferences(persistedPreferences) &&
1376
- (wouldUsePersistedScope || wouldUsePersistedInstallMode || wouldUsePersistedMcpMode || wouldUsePersistedTeamMode) &&
2397
+ (wouldUsePersistedScope ||
2398
+ wouldUsePersistedInstallMode ||
2399
+ wouldUsePersistedMcpMode ||
2400
+ wouldUsePersistedTeamMode ||
2401
+ wouldUsePersistedMergeAgents) &&
1377
2402
  (typeof persistedSetupReviewPrompt === "function" ||
1378
2403
  (process.stdin.isTTY && process.stdout.isTTY));
1379
2404
  if (shouldReviewPersistedSetup) {
@@ -1383,6 +2408,18 @@ export async function setup(options = {}) {
1383
2408
  console.log(`Setup preference review: ${persistedReviewDecision} (${formatPersistedSetupPreferenceSummary(persistedPreferences)})\n`);
1384
2409
  }
1385
2410
  const resolvedScope = await resolveSetupScope(projectRoot, requestedScope, persistedReviewDecision, persistedPreferences, setupScopePrompt);
2411
+ const requestedMergeAgentsPolicy = options.mergeAgentsPolicy ??
2412
+ (typeof options.mergeAgents === "boolean"
2413
+ ? { kind: "set", value: options.mergeAgents }
2414
+ : undefined);
2415
+ const inheritedMergeAgents = persistedReviewDecision === "reset"
2416
+ ? undefined
2417
+ : resolvePersistedSetupMergeAgents(persistedPreferences, resolvedScope.scope);
2418
+ const effectiveMergeAgents = requestedMergeAgentsPolicy?.kind === "set"
2419
+ ? requestedMergeAgentsPolicy.value
2420
+ : requestedMergeAgentsPolicy?.kind === "clear"
2421
+ ? undefined
2422
+ : inheritedMergeAgents;
1386
2423
  const resolvedInstallMode = await resolveSetupInstallMode(projectRoot, resolvedScope.scope, requestedInstallMode, installModePrompt, persistedReviewDecision, persistedPreferences);
1387
2424
  const resolvedMcpMode = resolveSetupMcpMode(resolvedScope.scope, requestedMcpMode, persistedReviewDecision, persistedPreferences);
1388
2425
  const resolvedTeamMode = requestedTeamMode
@@ -1395,9 +2432,32 @@ export async function setup(options = {}) {
1395
2432
  const skipNativeAgentRefresh = requestedSkipNativeAgentRefresh ||
1396
2433
  process.env[SKIP_NATIVE_AGENT_REFRESH_ENV] === "1";
1397
2434
  const scopeDirs = resolveScopeDirectories(resolvedScope.scope, projectRoot);
1398
- const existingConfigForMcpMigration = existsSync(scopeDirs.codexConfigFile)
1399
- ? await readFile(scopeDirs.codexConfigFile, "utf-8")
2435
+ if (!dryRun) {
2436
+ const recoveryTracker = { degraded: false };
2437
+ const recovery = await recoverNativeHookClaimJournal(scopeDirs.codexHomeDir, nativeHookClaimJournalDurability());
2438
+ recordRegularFileSyncOutcome(recoveryTracker, recovery.outcome);
2439
+ if (recovery.recovered) {
2440
+ emitDegradedDurabilityWarning("native-hook claim-journal recovery", recoveryTracker);
2441
+ }
2442
+ }
2443
+ const nativeHookTransactionPlatform = nativeHookPlatform();
2444
+ let nativeHookTransactionAncestorPrecondition = await captureNativeHookTransactionAncestorPrecondition(scopeDirs.codexHomeDir, [
2445
+ scopeDirs.codexConfigFile,
2446
+ scopeDirs.codexHooksFile,
2447
+ getNotifyMetadataPath(scopeDirs.codexHomeDir),
2448
+ ...(nativeHookTransactionPlatform === "win32"
2449
+ ? [
2450
+ buildManagedCodexNativeHookWindowsShimPath(scopeDirs.codexHomeDir),
2451
+ ]
2452
+ : []),
2453
+ ]);
2454
+ const existingConfigForMcpMigrationSnapshot = await captureNativeHookTransactionArtifact(scopeDirs.codexConfigFile, `config ${scopeDirs.codexConfigFile}`);
2455
+ const existingConfigForMcpMigration = existingConfigForMcpMigrationSnapshot.bytes
2456
+ ? decodeNativeHookTransactionUtf8(existingConfigForMcpMigrationSnapshot.bytes, `config ${scopeDirs.codexConfigFile}`)
1400
2457
  : "";
2458
+ const notifyMetadataSnapshot = configRequiresNotificationMetadataSnapshot(existingConfigForMcpMigration, pkgRoot, resolvedScope.scope)
2459
+ ? await captureNativeHookTransactionArtifact(getNotifyMetadataPath(scopeDirs.codexHomeDir), `notification metadata ${getNotifyMetadataPath(scopeDirs.codexHomeDir)}`)
2460
+ : undefined;
1401
2461
  const firstPartyMcpRegistrationKinds = [
1402
2462
  hasFirstPartyOmxMcpRegistrations(existingConfigForMcpMigration)
1403
2463
  ? "config.toml [mcp_servers.omx_*]"
@@ -1425,7 +2485,7 @@ export async function setup(options = {}) {
1425
2485
  ? join(projectRoot, "AGENTS.md")
1426
2486
  : join(scopeDirs.codexHomeDir, "AGENTS.md");
1427
2487
  const pluginDeveloperInstructionsDecision = isPluginInstallMode
1428
- ? await resolvePluginDeveloperInstructionsDecision(scopeDirs.codexConfigFile, { pluginDeveloperInstructionsPrompt })
2488
+ ? await resolvePluginDeveloperInstructionsDecision(existingConfigForMcpMigration, scopeDirs.codexConfigFile, { pluginDeveloperInstructionsPrompt })
1429
2489
  : {
1430
2490
  action: "preserve",
1431
2491
  state: "custom",
@@ -1443,7 +2503,7 @@ export async function setup(options = {}) {
1443
2503
  pluginAgentsMdIsSymlink = false;
1444
2504
  }
1445
2505
  const usePluginAgentsMdDefault = isPluginInstallMode
1446
- ? options.mergeAgents || pluginAgentsMdIsSymlink
2506
+ ? effectiveMergeAgents || pluginAgentsMdIsSymlink
1447
2507
  ? false
1448
2508
  : force
1449
2509
  ? true
@@ -1451,6 +2511,55 @@ export async function setup(options = {}) {
1451
2511
  ? await pluginAgentsMdPrompt(pluginAgentsMdDst)
1452
2512
  : await promptForPluginAgentsMdDefault(pluginAgentsMdDst)
1453
2513
  : false;
2514
+ const codexHookFeatureSupport = resolveCodexHookFeatureSupportForCli({
2515
+ codexFeaturesProbe: options.codexFeaturesProbe,
2516
+ codexVersionProbe: options.codexVersionProbe,
2517
+ });
2518
+ const codexHookFeatureFlag = codexHookFeatureSupport.hookFeatureFlag;
2519
+ const pluginScopedHooksSupported = codexHookFeatureSupport.pluginScopedHooks;
2520
+ const shouldSyncSharedMcpRegistry = resolvedMcpMode.mcpMode === "compat";
2521
+ const registryCandidates = getUnifiedMcpRegistryCandidates();
2522
+ const defaultRegistryCandidates = registryCandidates.slice(0, 1);
2523
+ const sharedMcpRegistry = shouldSyncSharedMcpRegistry
2524
+ ? await loadUnifiedMcpRegistry({
2525
+ candidates: options.mcpRegistryCandidates ?? defaultRegistryCandidates,
2526
+ })
2527
+ : { servers: [], warnings: [] };
2528
+ const legacyRegistryCandidate = getLegacyUnifiedMcpRegistryCandidate();
2529
+ const preflightMarketplace = isPluginInstallMode
2530
+ ? await resolvePackagedOmxMarketplace(pkgRoot)
2531
+ : null;
2532
+ const statusLinePreset = isPluginInstallMode
2533
+ ? undefined
2534
+ : await resolveStatusLinePresetForSetup(projectRoot, { force });
2535
+ const nativeHookSetupTransaction = await planNativeHookSetupTransaction({
2536
+ configPath: scopeDirs.codexConfigFile,
2537
+ hooksPath: scopeDirs.codexHooksFile,
2538
+ codexHomeDir: scopeDirs.codexHomeDir,
2539
+ pkgRoot,
2540
+ platform: nativeHookTransactionPlatform,
2541
+ isPluginInstallMode,
2542
+ pluginScopedHooks: pluginScopedHooksSupported,
2543
+ codexHookFeatureFlag,
2544
+ preserveFirstPartyMcp: shouldOfferFirstPartyMcpRemoval && !removeFirstPartyMcpRegistrations,
2545
+ removeFirstPartyMcp: removeFirstPartyMcpRegistrations,
2546
+ pluginDeveloperInstructionsDecision,
2547
+ pluginMarketplaceAvailable: preflightMarketplace !== null,
2548
+ sharedMcpRegistry,
2549
+ mcpMode: resolvedMcpMode.mcpMode,
2550
+ resolvedScope: resolvedScope.scope,
2551
+ modelUpgradePrompt,
2552
+ statusLinePreset,
2553
+ forceStatusLinePreset: force,
2554
+ configSnapshot: existingConfigForMcpMigrationSnapshot,
2555
+ notifyMetadataSnapshot,
2556
+ });
2557
+ const summary = createEmptyRunSummary();
2558
+ for (const precondition of nativeHookSetupTransaction.preconditions) {
2559
+ injectNativeHookTransactionFailure("before_precondition", precondition);
2560
+ await assertNativeHookTransactionPrecondition(precondition);
2561
+ }
2562
+ await assertNativeHookTransactionAncestorPrecondition(nativeHookTransactionAncestorPrecondition);
1454
2563
  console.log("oh-my-codex setup");
1455
2564
  console.log("=================\n");
1456
2565
  console.log(`Using setup scope: ${resolvedScope.scope}${scopeSourceMessage}\n`);
@@ -1510,11 +2619,8 @@ export async function setup(options = {}) {
1510
2619
  resolvedInstallMode.installMode === "plugin")
1511
2620
  ? { installMode: resolvedInstallMode.installMode }
1512
2621
  : {}),
2622
+ ...(effectiveMergeAgents !== undefined ? { mergeAgents: effectiveMergeAgents } : {}),
1513
2623
  };
1514
- await persistSetupPreferences(projectRoot, setupPreferencesToPersist, {
1515
- dryRun,
1516
- verbose,
1517
- });
1518
2624
  console.log(" Done.\n");
1519
2625
  if (resolvedScope.scope === "project") {
1520
2626
  const gitignoreResult = await ensureProjectGitignore(projectRoot, backupContext, { dryRun, verbose });
@@ -1526,7 +2632,6 @@ export async function setup(options = {}) {
1526
2632
  }
1527
2633
  }
1528
2634
  const catalogCounts = getCatalogHeadlineCounts();
1529
- const summary = createEmptyRunSummary();
1530
2635
  // Step 2: Install agent prompts
1531
2636
  console.log("[2/8] Installing agent prompts...");
1532
2637
  {
@@ -1624,27 +2729,12 @@ export async function setup(options = {}) {
1624
2729
  }
1625
2730
  // Step 5: Update config.toml
1626
2731
  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;
2732
+ const resolvedConfig = nativeHookSetupTransaction.finalConfig;
2733
+ const omxManagesTui = !isPluginInstallMode;
1635
2734
  if (verbose) {
1636
2735
  console.log(` Native Codex hook feature flag: [features].${codexHookFeatureFlag}`);
1637
2736
  console.log(` Plugin-scoped Codex hooks: ${pluginScopedHooksSupported ? "supported" : "not reported; using legacy setup fallback"}`);
1638
2737
  }
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
2738
  if (shouldSyncSharedMcpRegistry &&
1649
2739
  !options.mcpRegistryCandidates &&
1650
2740
  !sharedMcpRegistry.sourcePath &&
@@ -1658,36 +2748,51 @@ export async function setup(options = {}) {
1658
2748
  for (const warning of sharedMcpRegistry.warnings) {
1659
2749
  console.log(` warning: ${warning}`);
1660
2750
  }
2751
+ logManagedCodexHooksPlanDiagnostics(nativeHookSetupTransaction.diagnostics, {
2752
+ verbose,
2753
+ });
2754
+ const changedHookArtifact = nativeHookSetupTransaction.artifacts.some((artifact) => artifact.kind === "hooks");
2755
+ const changedShimArtifact = nativeHookSetupTransaction.artifacts.some((artifact) => artifact.kind === "shim");
2756
+ const changedConfigArtifact = nativeHookSetupTransaction.artifacts.some((artifact) => artifact.kind === "config");
2757
+ if (changedHookArtifact) {
2758
+ if (isPluginInstallMode &&
2759
+ nativeHookSetupTransaction.pluginScopedHooks &&
2760
+ nativeHookSetupTransaction.hooksRemovedCount > 0) {
2761
+ summary.config.removed += nativeHookSetupTransaction.hooksRemovedCount;
2762
+ }
2763
+ else {
2764
+ summary.config.updated += 1;
2765
+ }
2766
+ }
2767
+ else if (!isPluginInstallMode || !nativeHookSetupTransaction.pluginScopedHooks) {
2768
+ summary.config.unchanged += 1;
2769
+ }
2770
+ if (changedShimArtifact)
2771
+ summary.config.updated += 1;
2772
+ if (changedConfigArtifact)
2773
+ summary.config.updated += 1;
2774
+ else
2775
+ summary.config.unchanged += 1;
2776
+ if (verbose && nativeHookSetupTransaction.modelUpgrade) {
2777
+ console.log(` ${dryRun ? "would update" : "updated"} root model from ${nativeHookSetupTransaction.modelUpgrade.currentModel} to ${nativeHookSetupTransaction.modelUpgrade.modelOverride}`);
2778
+ }
1661
2779
  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)
2780
+ if (nativeHookSetupTransaction.cleanedLegacyConfig) {
1670
2781
  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") {
2782
+ console.log(` ${dryRun ? "Would clean" : "Cleaned"} legacy OMX config entries for plugin mode.\n`);
2783
+ }
2784
+ else {
2785
+ console.log(" Config refresh skipped; no legacy OMX config entries found.\n");
2786
+ }
2787
+ if (nativeHookSetupTransaction.pluginMarketplaceResult === "unavailable") {
1682
2788
  console.log(` warning: packaged ${OMX_LOCAL_MARKETPLACE_NAME} Codex plugin marketplace metadata not found; /skills plugin discovery was not registered.`);
1683
2789
  }
1684
- else if (pluginMarketplaceResult === "updated") {
2790
+ else if (nativeHookSetupTransaction.pluginMarketplaceResult === "updated") {
1685
2791
  console.log(` ${dryRun ? "Would register" : "Registered"} local Codex plugin marketplace ${OMX_LOCAL_MARKETPLACE_NAME} (${pkgRoot}).`);
1686
2792
  }
1687
2793
  else {
1688
2794
  console.log(` Local Codex plugin marketplace ${OMX_LOCAL_MARKETPLACE_NAME} already registered (${pkgRoot}).`);
1689
2795
  }
1690
- const packagedMarketplace = await resolvePackagedOmxMarketplace(pkgRoot);
1691
2796
  const pluginCacheRefresh = await refreshOmxPluginDiscoveryCache(pkgRoot, {
1692
2797
  dryRun,
1693
2798
  verbose,
@@ -1700,38 +2805,25 @@ export async function setup(options = {}) {
1700
2805
  else if (pluginCacheRefresh.status === "unchanged") {
1701
2806
  console.log(" Codex plugin discovery cache already matches packaged plugin metadata.");
1702
2807
  }
1703
- const pluginCacheMaterialize = await materializePackagedOmxPluginCache(scopeDirs.codexHomeDir, packagedMarketplace, { dryRun, teamMode: resolvedTeamMode });
2808
+ const pluginCacheMaterialize = await materializePackagedOmxPluginCache(scopeDirs.codexHomeDir, preflightMarketplace, { dryRun, teamMode: resolvedTeamMode });
1704
2809
  if (pluginCacheMaterialize.status === "materialized") {
1705
2810
  console.log(` ${dryRun ? "Would install" : "Installed"} local Codex plugin cache for ${OMX_LOCAL_MARKETPLACE_NAME}/${OMX_PLUGIN_NAME} at ${pluginCacheMaterialize.cacheDir}.`);
1706
2811
  }
1707
2812
  else if (pluginCacheMaterialize.status === "unchanged") {
1708
2813
  console.log(" Local Codex plugin cache already exposes packaged OMX skills.");
1709
2814
  }
1710
- if (pluginCacheMaterialize.status === "materialized" || pluginCacheMaterialize.status === "unchanged") {
2815
+ if (pluginCacheMaterialize.status === "materialized" ||
2816
+ pluginCacheMaterialize.status === "unchanged") {
1711
2817
  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
2818
  }
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
- }
2819
+ if (shouldSyncSharedMcpRegistry && resolvedScope.scope === "user") {
2820
+ await syncClaudeCodeMcpSettings(sharedMcpRegistry, summary.config, backupContext, { dryRun, verbose });
1718
2821
  }
1719
- resolvedConfig = existsSync(scopeDirs.codexConfigFile)
1720
- ? await readFile(scopeDirs.codexConfigFile, "utf-8")
1721
- : "";
1722
2822
  console.log(pluginScopedHooksSupported
1723
2823
  ? " Plugin-scoped Codex hooks and runtime feature flags refresh complete (plugin_hooks, goals).\n"
1724
2824
  : ` Native Codex hooks fallback and runtime feature flags refresh complete (${scopeDirs.codexHooksFile}; hooks, goals).\n`);
1725
2825
  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
- : "";
2826
+ if (nativeHookSetupTransaction.pluginDeveloperInstructionsResult === "updated") {
1735
2827
  console.log(` ${dryRun ? "Would add" : "Added"} plugin-mode developer_instructions default (${scopeDirs.codexConfigFile}).\n`);
1736
2828
  }
1737
2829
  else {
@@ -1743,31 +2835,13 @@ export async function setup(options = {}) {
1743
2835
  }
1744
2836
  }
1745
2837
  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
2838
  if (shouldSyncSharedMcpRegistry && resolvedScope.scope === "user") {
1761
2839
  await syncClaudeCodeMcpSettings(sharedMcpRegistry, summary.config, backupContext, { dryRun, verbose });
1762
2840
  }
2841
+ if (nativeHookSetupTransaction.repairedLegacyTeamRunTable) {
2842
+ console.log(" Removed retired [mcp_servers.omx_team_run] config during refresh.");
2843
+ }
1763
2844
  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
2845
  console.log(` Native Codex hooks refresh complete (${scopeDirs.codexHooksFile}).\n`);
1772
2846
  }
1773
2847
  // Step 5.5: Verify team CLI interop surface is available when Team is enabled.
@@ -1802,7 +2876,7 @@ export async function setup(options = {}) {
1802
2876
  });
1803
2877
  const modelTableDefinitions = getAgentsModelTableDefinitionsForTeamMode(resolvedTeamMode);
1804
2878
  const rewritten = upsertAgentsModelTable(addGeneratedAgentsMarker(applyTeamModeToAgentsTemplate(applyPluginModeWordingToAgentsTemplate(content, resolvedScope.scope), resolvedTeamMode)), modelTableContext, modelTableDefinitions, { codexHomeOverride: scopeDirs.codexHomeDir });
1805
- if (options.mergeAgents && pluginAgentsMdExists) {
2879
+ if (effectiveMergeAgents && pluginAgentsMdExists) {
1806
2880
  if (pluginAgentsMdIsSymlink) {
1807
2881
  summary.agentsMd.skipped += 1;
1808
2882
  console.log(` Skipped plugin-mode AGENTS.md merge for symlinked ${pluginAgentsMdDst}; existing AGENTS.md left untouched.`);
@@ -1917,7 +2991,7 @@ export async function setup(options = {}) {
1917
2991
  console.log(` WARNING: Existing AGENTS.md at ${agentsMdDst} lacks OMX contract markers; it may have been overwritten by another tool.`);
1918
2992
  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
2993
  }
1920
- if (options.mergeAgents) {
2994
+ if (effectiveMergeAgents) {
1921
2995
  mergedAgentsContent = upsertManagedAgentsBlock(existing, rewritten);
1922
2996
  canApplyManagedAgentsMerge = mergedAgentsContent !== existing;
1923
2997
  }
@@ -1949,7 +3023,7 @@ export async function setup(options = {}) {
1949
3023
  console.log(" Skipping AGENTS.md overwrite to avoid corrupting runtime overlay.");
1950
3024
  console.log(" Stop the active session first, then re-run setup.");
1951
3025
  }
1952
- else if (options.mergeAgents &&
3026
+ else if (effectiveMergeAgents &&
1953
3027
  agentsMdExists &&
1954
3028
  !canApplyManagedAgentsMerge) {
1955
3029
  summary.agentsMd.unchanged += 1;
@@ -2044,6 +3118,16 @@ export async function setup(options = {}) {
2044
3118
  console.log("Force mode: enabled additional destructive maintenance (for example stale deprecated skill cleanup).");
2045
3119
  console.log();
2046
3120
  }
3121
+ await persistSetupPreferences(projectRoot, setupPreferencesToPersist, {
3122
+ dryRun,
3123
+ verbose,
3124
+ });
3125
+ setupLatePhaseFailureInjector?.();
3126
+ nativeHookTransactionAncestorPrecondition =
3127
+ await captureNativeHookTransactionAncestorPrecondition(scopeDirs.codexHomeDir, nativeHookSetupTransaction.artifacts.map((artifact) => artifact.path));
3128
+ const nativeHookSetupDurabilityTracker = { degraded: false };
3129
+ await commitNativeHookTransaction(nativeHookSetupTransaction.artifacts, nativeHookSetupTransaction.preconditions, nativeHookTransactionAncestorPrecondition, backupContext, nativeHookSetupDurabilityTracker, summary.config, { dryRun, verbose });
3130
+ emitDegradedDurabilityWarning("native-hook setup", nativeHookSetupDurabilityTracker);
2047
3131
  console.log('Setup complete! Run "omx doctor" to verify installation.');
2048
3132
  console.log("\nNext steps:");
2049
3133
  console.log(" 1. Start Codex CLI in your project directory");
@@ -2240,13 +3324,6 @@ async function syncNativeAgentToml(content, dstPath, summary, backupContext, opt
2240
3324
  console.log(` ${options.dryRun ? "would update" : "updated"} ${verboseLabel}`);
2241
3325
  }
2242
3326
  }
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
3327
  async function syncManagedAgentsContent(content, dstPath, summary, backupContext, options) {
2251
3328
  const destinationExists = existsSync(dstPath);
2252
3329
  let existing = "";
@@ -2710,7 +3787,34 @@ async function cleanupLegacyManagedSkills(srcDir, dstDir, backupContext, options
2710
3787
  function getNotifyMetadataPath(codexHomeDir) {
2711
3788
  return join(codexHomeDir, ".omx", "notify-dispatch.json");
2712
3789
  }
2713
- async function buildNotifyMergePlan(existingConfig, pkgRoot, codexHomeDir, scope) {
3790
+ function isOmxDispatcherNotifyCommand(notifyCommand, pkgRoot) {
3791
+ return Boolean(isOmxManagedNotifyCommand(notifyCommand, pkgRoot) &&
3792
+ notifyCommand?.some((part) => /(?:^|[\\/])notify-dispatcher\.js$/.test(part)));
3793
+ }
3794
+ function configRequiresNotificationMetadataSnapshot(existingConfig, pkgRoot, scope) {
3795
+ if (scope === "project")
3796
+ return false;
3797
+ const existingNotify = getRootTomlArray(existingConfig, "notify");
3798
+ return Boolean(existingNotify &&
3799
+ (!isOmxManagedNotifyCommand(existingNotify, pkgRoot) ||
3800
+ isOmxDispatcherNotifyCommand(existingNotify, pkgRoot)));
3801
+ }
3802
+ function parseNotifyMetadataSnapshot(snapshot, label) {
3803
+ if (snapshot.bytes === null)
3804
+ return null;
3805
+ const content = decodeNativeHookTransactionUtf8(snapshot.bytes, label);
3806
+ try {
3807
+ const parsed = JSON.parse(content);
3808
+ if (!parsed || typeof parsed !== "object" || Array.isArray(parsed)) {
3809
+ throw new Error("expected a JSON object");
3810
+ }
3811
+ return parsed;
3812
+ }
3813
+ catch (error) {
3814
+ throw new ManagedCodexHooksPlanError("invalid_document", `Refusing to read ${label}: invalid JSON (${error instanceof Error ? error.message : String(error)}).`, { label });
3815
+ }
3816
+ }
3817
+ async function buildNotifyMergePlan(existingConfig, pkgRoot, codexHomeDir, scope, metadataSnapshot) {
2714
3818
  if (scope === "project") {
2715
3819
  return { notifyCommand: false };
2716
3820
  }
@@ -2727,35 +3831,34 @@ async function buildNotifyMergePlan(existingConfig, pkgRoot, codexHomeDir, scope
2727
3831
  return { notifyCommand: omxNotify };
2728
3832
  }
2729
3833
  if (isOmxManagedNotifyCommand(existingNotify, pkgRoot)) {
2730
- if (!existingNotify.some((part) => /(?:^|[\\/])notify-dispatcher\.js$/.test(part))) {
3834
+ if (!isOmxDispatcherNotifyCommand(existingNotify, pkgRoot)) {
2731
3835
  return { notifyCommand: omxNotify };
2732
3836
  }
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
- }
3837
+ if (!metadataSnapshot) {
3838
+ throw new Error(`Missing notification metadata snapshot for ${metadataPath}; refusing to plan from a newly captured baseline.`);
2754
3839
  }
2755
- catch {
2756
- // Missing dispatcher metadata: fall back to plain OMX notify instead of nesting.
3840
+ const metadata = parseNotifyMetadataSnapshot(metadataSnapshot, `notification metadata ${metadataPath}`);
3841
+ const previousNotify = metadata?.previousNotify;
3842
+ if (Array.isArray(previousNotify) &&
3843
+ previousNotify.every((item) => typeof item === "string")) {
3844
+ const sanitizedPreviousNotify = sanitizePreviousNotifyCommand(previousNotify, pkgRoot);
3845
+ if (!sanitizedPreviousNotify) {
3846
+ return { notifyCommand: omxNotify, metadataPath, metadataSnapshot };
3847
+ }
3848
+ return {
3849
+ notifyCommand: dispatcherNotify,
3850
+ metadataPath,
3851
+ metadataSnapshot,
3852
+ metadata: {
3853
+ managedBy: "oh-my-codex",
3854
+ version: 1,
3855
+ previousNotify: sanitizedPreviousNotify,
3856
+ omxNotify,
3857
+ dispatcherNotify,
3858
+ },
3859
+ };
2757
3860
  }
2758
- return { notifyCommand: omxNotify };
3861
+ return { notifyCommand: omxNotify, metadataPath, metadataSnapshot };
2759
3862
  }
2760
3863
  return {
2761
3864
  notifyCommand: dispatcherNotify,
@@ -2769,14 +3872,11 @@ async function buildNotifyMergePlan(existingConfig, pkgRoot, codexHomeDir, scope
2769
3872
  },
2770
3873
  };
2771
3874
  }
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);
3875
+ async function planManagedConfig(hooksPath, pkgRoot, sharedMcpRegistry, mcpMode, preserveExistingFirstPartyMcp, scope, codexHomeDir, options) {
3876
+ const existingConfig = options.existingConfig ?? "";
3877
+ const hadLegacyTeamRunTable = hasLegacyOmxTeamRunTable(existingConfig);
3878
+ const currentModel = getRootModelName(existingConfig);
2778
3879
  let modelOverride;
2779
- const omxManagesTui = true;
2780
3880
  if (currentModel && LEGACY_SETUP_MODELS.has(currentModel)) {
2781
3881
  const shouldPrompt = typeof options.modelUpgradePrompt === "function" ||
2782
3882
  (process.stdin.isTTY && process.stdout.isTTY);
@@ -2789,12 +3889,15 @@ async function updateManagedConfig(configPath, hooksPath, pkgRoot, sharedMcpRegi
2789
3889
  }
2790
3890
  }
2791
3891
  }
2792
- const notifyPlan = await buildNotifyMergePlan(existing, pkgRoot, codexHomeDir, scope);
2793
- const finalConfig = buildMergedConfig(existing, pkgRoot, {
2794
- includeTui: omxManagesTui,
3892
+ const notifyPlan = await buildNotifyMergePlan(existingConfig, pkgRoot, codexHomeDir, scope, options.notifyMetadataSnapshot);
3893
+ const finalConfig = buildMergedConfig(existingConfig, pkgRoot, {
3894
+ includeTui: true,
2795
3895
  codexHooksFile: hooksPath,
2796
3896
  codexHomeDir,
2797
- hookCommandPlatform: process.platform,
3897
+ hookCommandPlatform: options.hookCommandPlatform,
3898
+ managedHookTrustState: options.managedHookTrustState,
3899
+ priorManagedHookTrustState: options.priorManagedHookTrustState,
3900
+ legacyHookTrustState: options.legacyHookTrustState,
2798
3901
  codexHookFeatureFlag: options.codexHookFeatureFlag,
2799
3902
  modelOverride,
2800
3903
  sharedMcpServers: sharedMcpRegistry.servers,
@@ -2806,65 +3909,14 @@ async function updateManagedConfig(configPath, hooksPath, pkgRoot, sharedMcpRegi
2806
3909
  includeFirstPartyMcp: mcpMode === "compat",
2807
3910
  preserveExistingFirstPartyMcp,
2808
3911
  });
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
3912
  return {
2839
3913
  finalConfig,
2840
- omxManagesTui,
3914
+ currentModel,
3915
+ modelOverride,
3916
+ notifyPlan,
2841
3917
  repairedLegacyTeamRunTable: hadLegacyTeamRunTable && !hasLegacyOmxTeamRunTable(finalConfig),
2842
3918
  };
2843
3919
  }
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
3920
  function getClaudeCodeSettingsPath(homeDir = homedir()) {
2869
3921
  return join(homeDir, ".claude", "settings.json");
2870
3922
  }