oh-my-codex 0.20.1 → 0.20.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (654) hide show
  1. package/Cargo.lock +6 -6
  2. package/Cargo.toml +1 -1
  3. package/README.md +10 -1
  4. package/dist/adapt/__tests__/hermes.test.js.map +1 -1
  5. package/dist/adapt/contracts.d.ts.map +1 -1
  6. package/dist/agents/native-config.js.map +1 -1
  7. package/dist/auth/__tests__/hotswap.test.d.ts +2 -0
  8. package/dist/auth/__tests__/hotswap.test.d.ts.map +1 -0
  9. package/dist/auth/__tests__/hotswap.test.js +183 -0
  10. package/dist/auth/__tests__/hotswap.test.js.map +1 -0
  11. package/dist/auth/hotswap.d.ts.map +1 -1
  12. package/dist/auth/hotswap.js +17 -4
  13. package/dist/auth/hotswap.js.map +1 -1
  14. package/dist/auth/paths.js.map +1 -1
  15. package/dist/auth/storage.js.map +1 -1
  16. package/dist/autopilot/__tests__/ralplan-gate.test.js +4 -4
  17. package/dist/autopilot/__tests__/ralplan-gate.test.js.map +1 -1
  18. package/dist/autopilot/completion-gate.js.map +1 -1
  19. package/dist/autopilot/fsm.d.ts +1 -1
  20. package/dist/autopilot/fsm.d.ts.map +1 -1
  21. package/dist/autoresearch/goal.js.map +1 -1
  22. package/dist/autoresearch/runtime.js.map +1 -1
  23. package/dist/capabilities/lockfile.js.map +1 -1
  24. package/dist/catalog/reader.js.map +1 -1
  25. package/dist/catalog/skill-mirror.js.map +1 -1
  26. package/dist/cli/__tests__/adapt-help.test.js.map +1 -1
  27. package/dist/cli/__tests__/agents-init.test.js.map +1 -1
  28. package/dist/cli/__tests__/agents.test.js.map +1 -1
  29. package/dist/cli/__tests__/api.test.js.map +1 -1
  30. package/dist/cli/__tests__/ask.test.js.map +1 -1
  31. package/dist/cli/__tests__/auth.test.js.map +1 -1
  32. package/dist/cli/__tests__/autoresearch-guided.test.js.map +1 -1
  33. package/dist/cli/__tests__/autoresearch.test.js.map +1 -1
  34. package/dist/cli/__tests__/capabilities.test.js.map +1 -1
  35. package/dist/cli/__tests__/codex-plugin-layout.test.js.map +1 -1
  36. package/dist/cli/__tests__/doctor-context-window-warning.test.js.map +1 -1
  37. package/dist/cli/__tests__/doctor-invalid-config.test.js +47 -4
  38. package/dist/cli/__tests__/doctor-invalid-config.test.js.map +1 -1
  39. package/dist/cli/__tests__/doctor-team.test.js.map +1 -1
  40. package/dist/cli/__tests__/doctor-warning-copy.test.js +650 -15
  41. package/dist/cli/__tests__/doctor-warning-copy.test.js.map +1 -1
  42. package/dist/cli/__tests__/exec.test.js.map +1 -1
  43. package/dist/cli/__tests__/imagegen-continuation.test.js.map +1 -1
  44. package/dist/cli/__tests__/index.test.js +216 -11
  45. package/dist/cli/__tests__/index.test.js.map +1 -1
  46. package/dist/cli/__tests__/launch-fallback.test.js +49 -3
  47. package/dist/cli/__tests__/launch-fallback.test.js.map +1 -1
  48. package/dist/cli/__tests__/mcp-parity.test.js +84 -12
  49. package/dist/cli/__tests__/mcp-parity.test.js.map +1 -1
  50. package/dist/cli/__tests__/native-hook-claim-journal.test.d.ts +2 -0
  51. package/dist/cli/__tests__/native-hook-claim-journal.test.d.ts.map +1 -0
  52. package/dist/cli/__tests__/native-hook-claim-journal.test.js +128 -0
  53. package/dist/cli/__tests__/native-hook-claim-journal.test.js.map +1 -0
  54. package/dist/cli/__tests__/nested-help-routing.test.js.map +1 -1
  55. package/dist/cli/__tests__/question.test.js.map +1 -1
  56. package/dist/cli/__tests__/ralph-deslop-contract.test.js.map +1 -1
  57. package/dist/cli/__tests__/ralph-goal-mode-contract.test.js +6 -0
  58. package/dist/cli/__tests__/ralph-goal-mode-contract.test.js.map +1 -1
  59. package/dist/cli/__tests__/ralph-prd-deep-interview.test.js.map +1 -1
  60. package/dist/cli/__tests__/ralph-prd-smoke.test.js.map +1 -1
  61. package/dist/cli/__tests__/ralph.test.js +8 -3
  62. package/dist/cli/__tests__/ralph.test.js.map +1 -1
  63. package/dist/cli/__tests__/ralplan-bootstrap-3181.test.d.ts +2 -0
  64. package/dist/cli/__tests__/ralplan-bootstrap-3181.test.d.ts.map +1 -0
  65. package/dist/cli/__tests__/ralplan-bootstrap-3181.test.js +187 -0
  66. package/dist/cli/__tests__/ralplan-bootstrap-3181.test.js.map +1 -0
  67. package/dist/cli/__tests__/ralplan.test.d.ts +2 -0
  68. package/dist/cli/__tests__/ralplan.test.d.ts.map +1 -0
  69. package/dist/cli/__tests__/ralplan.test.js +129 -0
  70. package/dist/cli/__tests__/ralplan.test.js.map +1 -0
  71. package/dist/cli/__tests__/resume.test.js +6 -1
  72. package/dist/cli/__tests__/resume.test.js.map +1 -1
  73. package/dist/cli/__tests__/session-scoped-runtime.test.js +25 -0
  74. package/dist/cli/__tests__/session-scoped-runtime.test.js.map +1 -1
  75. package/dist/cli/__tests__/session-search-help.test.js.map +1 -1
  76. package/dist/cli/__tests__/session-search.test.js.map +1 -1
  77. package/dist/cli/__tests__/setup-agents-overwrite.test.js +79 -0
  78. package/dist/cli/__tests__/setup-agents-overwrite.test.js.map +1 -1
  79. package/dist/cli/__tests__/setup-gh-star.test.js.map +1 -1
  80. package/dist/cli/__tests__/setup-hooks-shared-ownership.test.js +77 -3
  81. package/dist/cli/__tests__/setup-hooks-shared-ownership.test.js.map +1 -1
  82. package/dist/cli/__tests__/setup-hooks-trust-e2e.test.d.ts +2 -0
  83. package/dist/cli/__tests__/setup-hooks-trust-e2e.test.d.ts.map +1 -0
  84. package/dist/cli/__tests__/setup-hooks-trust-e2e.test.js +285 -0
  85. package/dist/cli/__tests__/setup-hooks-trust-e2e.test.js.map +1 -0
  86. package/dist/cli/__tests__/setup-install-mode.test.js +2261 -14
  87. package/dist/cli/__tests__/setup-install-mode.test.js.map +1 -1
  88. package/dist/cli/__tests__/setup-preferences.test.d.ts +2 -0
  89. package/dist/cli/__tests__/setup-preferences.test.d.ts.map +1 -0
  90. package/dist/cli/__tests__/setup-preferences.test.js +79 -0
  91. package/dist/cli/__tests__/setup-preferences.test.js.map +1 -0
  92. package/dist/cli/__tests__/setup-refresh.test.js +33 -2
  93. package/dist/cli/__tests__/setup-refresh.test.js.map +1 -1
  94. package/dist/cli/__tests__/setup-scope.test.js +46 -3
  95. package/dist/cli/__tests__/setup-scope.test.js.map +1 -1
  96. package/dist/cli/__tests__/sparkshell-cli.test.js.map +1 -1
  97. package/dist/cli/__tests__/state.test.js +37 -0
  98. package/dist/cli/__tests__/state.test.js.map +1 -1
  99. package/dist/cli/__tests__/team.test.js.map +1 -1
  100. package/dist/cli/__tests__/ultragoal.test.js +61 -1
  101. package/dist/cli/__tests__/ultragoal.test.js.map +1 -1
  102. package/dist/cli/__tests__/uninstall.test.js +1771 -24
  103. package/dist/cli/__tests__/uninstall.test.js.map +1 -1
  104. package/dist/cli/__tests__/update.test.js +58 -0
  105. package/dist/cli/__tests__/update.test.js.map +1 -1
  106. package/dist/cli/__tests__/url.test.js.map +1 -1
  107. package/dist/cli/__tests__/windows-popup-loop-contract.test.js.map +1 -1
  108. package/dist/cli/adapt.js.map +1 -1
  109. package/dist/cli/agents-init.js.map +1 -1
  110. package/dist/cli/agents.js.map +1 -1
  111. package/dist/cli/api.js.map +1 -1
  112. package/dist/cli/ask.d.ts +1 -1
  113. package/dist/cli/ask.d.ts.map +1 -1
  114. package/dist/cli/ask.js.map +1 -1
  115. package/dist/cli/auth.js.map +1 -1
  116. package/dist/cli/autoresearch-guided.js.map +1 -1
  117. package/dist/cli/autoresearch-intake.js.map +1 -1
  118. package/dist/cli/capabilities.js.map +1 -1
  119. package/dist/cli/cleanup.js.map +1 -1
  120. package/dist/cli/codex-feature-probe.js.map +1 -1
  121. package/dist/cli/codex-home.js.map +1 -1
  122. package/dist/cli/doctor.d.ts +18 -0
  123. package/dist/cli/doctor.d.ts.map +1 -1
  124. package/dist/cli/doctor.js +594 -130
  125. package/dist/cli/doctor.js.map +1 -1
  126. package/dist/cli/explore.js.map +1 -1
  127. package/dist/cli/index.d.ts +11 -3
  128. package/dist/cli/index.d.ts.map +1 -1
  129. package/dist/cli/index.js +108 -20
  130. package/dist/cli/index.js.map +1 -1
  131. package/dist/cli/mcp-serve.js.map +1 -1
  132. package/dist/cli/mission.js.map +1 -1
  133. package/dist/cli/native-assets.js.map +1 -1
  134. package/dist/cli/native-hook-claim-journal.d.ts +18 -0
  135. package/dist/cli/native-hook-claim-journal.d.ts.map +1 -0
  136. package/dist/cli/native-hook-claim-journal.js +239 -0
  137. package/dist/cli/native-hook-claim-journal.js.map +1 -0
  138. package/dist/cli/omx.js.map +1 -1
  139. package/dist/cli/plugin-marketplace.js.map +1 -1
  140. package/dist/cli/project-runtime-codex-homes.js.map +1 -1
  141. package/dist/cli/question.js.map +1 -1
  142. package/dist/cli/ralph.d.ts.map +1 -1
  143. package/dist/cli/ralph.js +3 -2
  144. package/dist/cli/ralph.js.map +1 -1
  145. package/dist/cli/ralplan.d.ts +14 -0
  146. package/dist/cli/ralplan.d.ts.map +1 -0
  147. package/dist/cli/ralplan.js +203 -0
  148. package/dist/cli/ralplan.js.map +1 -0
  149. package/dist/cli/setup-preferences.d.ts +11 -0
  150. package/dist/cli/setup-preferences.d.ts.map +1 -1
  151. package/dist/cli/setup-preferences.js +32 -0
  152. package/dist/cli/setup-preferences.js.map +1 -1
  153. package/dist/cli/setup.d.ts +53 -0
  154. package/dist/cli/setup.d.ts.map +1 -1
  155. package/dist/cli/setup.js +1401 -398
  156. package/dist/cli/setup.js.map +1 -1
  157. package/dist/cli/sparkshell.js.map +1 -1
  158. package/dist/cli/star-prompt.js.map +1 -1
  159. package/dist/cli/state.d.ts.map +1 -1
  160. package/dist/cli/state.js +2 -1
  161. package/dist/cli/state.js.map +1 -1
  162. package/dist/cli/team.js.map +1 -1
  163. package/dist/cli/ultragoal.d.ts +5 -1
  164. package/dist/cli/ultragoal.d.ts.map +1 -1
  165. package/dist/cli/ultragoal.js +34 -2
  166. package/dist/cli/ultragoal.js.map +1 -1
  167. package/dist/cli/uninstall.d.ts +8 -0
  168. package/dist/cli/uninstall.d.ts.map +1 -1
  169. package/dist/cli/uninstall.js +944 -95
  170. package/dist/cli/uninstall.js.map +1 -1
  171. package/dist/cli/update.d.ts.map +1 -1
  172. package/dist/cli/update.js +8 -1
  173. package/dist/cli/update.js.map +1 -1
  174. package/dist/cli/url.js.map +1 -1
  175. package/dist/compat/__tests__/doctor-contract.test.js +5 -1
  176. package/dist/compat/__tests__/doctor-contract.test.js.map +1 -1
  177. package/dist/compat/__tests__/rust-runtime-compat.test.js.map +1 -1
  178. package/dist/config/__tests__/codex-hooks.test.js +1287 -11
  179. package/dist/config/__tests__/codex-hooks.test.js.map +1 -1
  180. package/dist/config/__tests__/generator-idempotent.test.js +850 -32
  181. package/dist/config/__tests__/generator-idempotent.test.js.map +1 -1
  182. package/dist/config/__tests__/generator-notify.test.js +6 -1
  183. package/dist/config/__tests__/generator-notify.test.js.map +1 -1
  184. package/dist/config/codex-feature-flags.d.ts.map +1 -1
  185. package/dist/config/codex-feature-flags.js.map +1 -1
  186. package/dist/config/codex-hooks.d.ts +124 -1
  187. package/dist/config/codex-hooks.d.ts.map +1 -1
  188. package/dist/config/codex-hooks.js +2142 -265
  189. package/dist/config/codex-hooks.js.map +1 -1
  190. package/dist/config/commit-lore-guard.js.map +1 -1
  191. package/dist/config/generator.d.ts +21 -16
  192. package/dist/config/generator.d.ts.map +1 -1
  193. package/dist/config/generator.js +991 -137
  194. package/dist/config/generator.js.map +1 -1
  195. package/dist/config/mcp-registry.js.map +1 -1
  196. package/dist/config/models.d.ts +2 -2
  197. package/dist/config/models.d.ts.map +1 -1
  198. package/dist/config/models.js.map +1 -1
  199. package/dist/config/omx-first-party-mcp.js.map +1 -1
  200. package/dist/config/team-mode.d.ts.map +1 -1
  201. package/dist/document-refresh/__tests__/enforcer.test.js.map +1 -1
  202. package/dist/document-refresh/enforcer.js.map +1 -1
  203. package/dist/exec/followup.js.map +1 -1
  204. package/dist/goal-workflows/artifacts.js.map +1 -1
  205. package/dist/hooks/__tests__/agents-overlay.test.js +7 -4
  206. package/dist/hooks/__tests__/agents-overlay.test.js.map +1 -1
  207. package/dist/hooks/__tests__/analyze-routing-contract.test.js.map +1 -1
  208. package/dist/hooks/__tests__/analyze-skill-contract.test.js.map +1 -1
  209. package/dist/hooks/__tests__/anti-slop-workflow.test.js.map +1 -1
  210. package/dist/hooks/__tests__/autopilot-skill-contract.test.js +9 -3
  211. package/dist/hooks/__tests__/autopilot-skill-contract.test.js.map +1 -1
  212. package/dist/hooks/__tests__/code-review-skill-contract.test.js.map +1 -1
  213. package/dist/hooks/__tests__/consensus-execution-handoff.test.js.map +1 -1
  214. package/dist/hooks/__tests__/deep-interview-contract.test.js.map +1 -1
  215. package/dist/hooks/__tests__/design-skill.test.js.map +1 -1
  216. package/dist/hooks/__tests__/keyword-detector.test.js +1372 -2
  217. package/dist/hooks/__tests__/keyword-detector.test.js.map +1 -1
  218. package/dist/hooks/__tests__/notify-fallback-watcher.test.js +14 -0
  219. package/dist/hooks/__tests__/notify-fallback-watcher.test.js.map +1 -1
  220. package/dist/hooks/__tests__/notify-hook-all-workers-idle.test.js.map +1 -1
  221. package/dist/hooks/__tests__/notify-hook-auto-nudge.test.js +669 -1
  222. package/dist/hooks/__tests__/notify-hook-auto-nudge.test.js.map +1 -1
  223. package/dist/hooks/__tests__/notify-hook-cross-worktree-heartbeat.test.js +2 -0
  224. package/dist/hooks/__tests__/notify-hook-cross-worktree-heartbeat.test.js.map +1 -1
  225. package/dist/hooks/__tests__/notify-hook-managed-tmux.test.js +142 -1
  226. package/dist/hooks/__tests__/notify-hook-managed-tmux.test.js.map +1 -1
  227. package/dist/hooks/__tests__/notify-hook-modules.test.js.map +1 -1
  228. package/dist/hooks/__tests__/notify-hook-non-omx-guard.test.js +48 -2
  229. package/dist/hooks/__tests__/notify-hook-non-omx-guard.test.js.map +1 -1
  230. package/dist/hooks/__tests__/notify-hook-ralph-resume.test.js +61 -44
  231. package/dist/hooks/__tests__/notify-hook-ralph-resume.test.js.map +1 -1
  232. package/dist/hooks/__tests__/notify-hook-regression-205.test.js.map +1 -1
  233. package/dist/hooks/__tests__/notify-hook-session-idle-dedupe.test.js +3 -2
  234. package/dist/hooks/__tests__/notify-hook-session-idle-dedupe.test.js.map +1 -1
  235. package/dist/hooks/__tests__/notify-hook-session-scope.test.js +243 -4
  236. package/dist/hooks/__tests__/notify-hook-session-scope.test.js.map +1 -1
  237. package/dist/hooks/__tests__/notify-hook-team-dispatch.test.js.map +1 -1
  238. package/dist/hooks/__tests__/notify-hook-team-leader-nudge.test.js +8 -1
  239. package/dist/hooks/__tests__/notify-hook-team-leader-nudge.test.js.map +1 -1
  240. package/dist/hooks/__tests__/notify-hook-team-tmux-guard.test.js.map +1 -1
  241. package/dist/hooks/__tests__/notify-hook-tmux-heal.test.js.map +1 -1
  242. package/dist/hooks/__tests__/notify-hook-tmux-scrollback.test.js.map +1 -1
  243. package/dist/hooks/__tests__/notify-hook-visual-verdict.test.js.map +1 -1
  244. package/dist/hooks/__tests__/notify-hook-worker-idle.test.js.map +1 -1
  245. package/dist/hooks/__tests__/openclaw-setup-contract.test.js.map +1 -1
  246. package/dist/hooks/__tests__/pre-context-gate-skills.test.js.map +1 -1
  247. package/dist/hooks/__tests__/prometheus-strict-contract.test.js.map +1 -1
  248. package/dist/hooks/__tests__/prompt-guidance-fragments.test.js.map +1 -1
  249. package/dist/hooks/__tests__/prompt-guidance-test-helpers.js.map +1 -1
  250. package/dist/hooks/__tests__/prompt-session-provenance.test.d.ts +2 -0
  251. package/dist/hooks/__tests__/prompt-session-provenance.test.d.ts.map +1 -0
  252. package/dist/hooks/__tests__/prompt-session-provenance.test.js +82 -0
  253. package/dist/hooks/__tests__/prompt-session-provenance.test.js.map +1 -0
  254. package/dist/hooks/__tests__/session.test.js +532 -11
  255. package/dist/hooks/__tests__/session.test.js.map +1 -1
  256. package/dist/hooks/__tests__/skill-catalog-hygiene.test.js.map +1 -1
  257. package/dist/hooks/__tests__/team-runtime-gating-docs-contract.test.js.map +1 -1
  258. package/dist/hooks/__tests__/visual-ralph-skill.test.js.map +1 -1
  259. package/dist/hooks/__tests__/visual-verdict-loop.test.js.map +1 -1
  260. package/dist/hooks/agents-overlay.js +5 -5
  261. package/dist/hooks/agents-overlay.js.map +1 -1
  262. package/dist/hooks/code-simplifier/index.js.map +1 -1
  263. package/dist/hooks/explore-routing.js.map +1 -1
  264. package/dist/hooks/extensibility/dispatcher.js.map +1 -1
  265. package/dist/hooks/extensibility/events.js.map +1 -1
  266. package/dist/hooks/extensibility/loader.js.map +1 -1
  267. package/dist/hooks/extensibility/plugin-runner-stdin.js.map +1 -1
  268. package/dist/hooks/extensibility/sdk/logging.js.map +1 -1
  269. package/dist/hooks/keyword-detector.d.ts +33 -7
  270. package/dist/hooks/keyword-detector.d.ts.map +1 -1
  271. package/dist/hooks/keyword-detector.js +2228 -119
  272. package/dist/hooks/keyword-detector.js.map +1 -1
  273. package/dist/hooks/keyword-registry.d.ts +12 -0
  274. package/dist/hooks/keyword-registry.d.ts.map +1 -1
  275. package/dist/hooks/keyword-registry.js +41 -0
  276. package/dist/hooks/keyword-registry.js.map +1 -1
  277. package/dist/hooks/prompt-session-provenance.d.ts +87 -0
  278. package/dist/hooks/prompt-session-provenance.d.ts.map +1 -0
  279. package/dist/hooks/prompt-session-provenance.js +180 -0
  280. package/dist/hooks/prompt-session-provenance.js.map +1 -0
  281. package/dist/hooks/session.d.ts +139 -34
  282. package/dist/hooks/session.d.ts.map +1 -1
  283. package/dist/hooks/session.js +1239 -317
  284. package/dist/hooks/session.js.map +1 -1
  285. package/dist/hooks/task-size-detector.js.map +1 -1
  286. package/dist/hud/__tests__/state.test.js.map +1 -1
  287. package/dist/hud/authority.js.map +1 -1
  288. package/dist/hud/constants.js.map +1 -1
  289. package/dist/hud/index.js.map +1 -1
  290. package/dist/hud/reconcile.js.map +1 -1
  291. package/dist/hud/render.js.map +1 -1
  292. package/dist/hud/state.js.map +1 -1
  293. package/dist/hud/tmux.js.map +1 -1
  294. package/dist/leader/__tests__/contract.test.js +103 -1
  295. package/dist/leader/__tests__/contract.test.js.map +1 -1
  296. package/dist/leader/contract.d.ts +26 -5
  297. package/dist/leader/contract.d.ts.map +1 -1
  298. package/dist/leader/contract.js +109 -3
  299. package/dist/leader/contract.js.map +1 -1
  300. package/dist/mcp/__tests__/bootstrap.test.js.map +1 -1
  301. package/dist/mcp/__tests__/server-lifecycle.test.js +15 -2
  302. package/dist/mcp/__tests__/server-lifecycle.test.js.map +1 -1
  303. package/dist/mcp/__tests__/state-paths.test.js +98 -1
  304. package/dist/mcp/__tests__/state-paths.test.js.map +1 -1
  305. package/dist/mcp/bootstrap.js.map +1 -1
  306. package/dist/mcp/code-intel-server.d.ts +58 -58
  307. package/dist/mcp/code-intel-server.d.ts.map +1 -1
  308. package/dist/mcp/code-intel-server.js.map +1 -1
  309. package/dist/mcp/hermes-bridge.js.map +1 -1
  310. package/dist/mcp/hermes-server.d.ts +70 -70
  311. package/dist/mcp/hermes-server.d.ts.map +1 -1
  312. package/dist/mcp/lifecycle-telemetry.js.map +1 -1
  313. package/dist/mcp/memory-server.d.ts +34 -34
  314. package/dist/mcp/memory-server.d.ts.map +1 -1
  315. package/dist/mcp/memory-server.js.map +1 -1
  316. package/dist/mcp/state-paths.d.ts +14 -0
  317. package/dist/mcp/state-paths.d.ts.map +1 -1
  318. package/dist/mcp/state-paths.js +96 -20
  319. package/dist/mcp/state-paths.js.map +1 -1
  320. package/dist/mcp/state-server.d.ts +47 -47
  321. package/dist/mcp/state-server.d.ts.map +1 -1
  322. package/dist/mcp/trace-server.d.ts +4 -4
  323. package/dist/mcp/trace-server.d.ts.map +1 -1
  324. package/dist/mcp/trace-server.js.map +1 -1
  325. package/dist/mcp/wiki-server.d.ts +23 -23
  326. package/dist/mcp/wiki-server.d.ts.map +1 -1
  327. package/dist/mcp/wiki-server.js.map +1 -1
  328. package/dist/modes/__tests__/base-session-scope.test.js +53 -1
  329. package/dist/modes/__tests__/base-session-scope.test.js.map +1 -1
  330. package/dist/modes/base.d.ts.map +1 -1
  331. package/dist/modes/base.js +5 -5
  332. package/dist/modes/base.js.map +1 -1
  333. package/dist/notifications/__tests__/lifecycle-dedupe.test.js.map +1 -1
  334. package/dist/notifications/__tests__/reply-listener.test.js.map +1 -1
  335. package/dist/notifications/__tests__/session-idle-tail-dedupe.test.js +177 -85
  336. package/dist/notifications/__tests__/session-idle-tail-dedupe.test.js.map +1 -1
  337. package/dist/notifications/__tests__/session-registry.test.js.map +1 -1
  338. package/dist/notifications/__tests__/template-engine.test.js.map +1 -1
  339. package/dist/notifications/__tests__/verbosity.test.js.map +1 -1
  340. package/dist/notifications/http-client.js.map +1 -1
  341. package/dist/notifications/index.d.ts +4 -1
  342. package/dist/notifications/index.d.ts.map +1 -1
  343. package/dist/notifications/index.js +3 -9
  344. package/dist/notifications/index.js.map +1 -1
  345. package/dist/notifications/lifecycle-dedupe.js.map +1 -1
  346. package/dist/notifications/reply-listener.d.ts.map +1 -1
  347. package/dist/notifications/reply-listener.js.map +1 -1
  348. package/dist/notifications/session-registry.js.map +1 -1
  349. package/dist/notifications/session-status.js.map +1 -1
  350. package/dist/notifications/temp-contract.js.map +1 -1
  351. package/dist/notifications/tmux-detector.js.map +1 -1
  352. package/dist/notifications/tmux.js.map +1 -1
  353. package/dist/openclaw/config.js.map +1 -1
  354. package/dist/openclaw/dispatcher.js.map +1 -1
  355. package/dist/pipeline/__tests__/orchestrator.test.js.map +1 -1
  356. package/dist/pipeline/__tests__/stages.test.js +8 -5
  357. package/dist/pipeline/__tests__/stages.test.js.map +1 -1
  358. package/dist/pipeline/stages/code-review.js.map +1 -1
  359. package/dist/pipeline/stages/ralph-verify.js.map +1 -1
  360. package/dist/pipeline/stages/ralplan.js.map +1 -1
  361. package/dist/pipeline/stages/team-exec.js.map +1 -1
  362. package/dist/pipeline/stages/ultragoal.js.map +1 -1
  363. package/dist/pipeline/stages/ultraqa.js.map +1 -1
  364. package/dist/planning/__tests__/approved-execution-lifecycle-matrix.test.js.map +1 -1
  365. package/dist/planning/artifact-names.js.map +1 -1
  366. package/dist/planning/artifacts.js.map +1 -1
  367. package/dist/question/__tests__/state.test.js.map +1 -1
  368. package/dist/question/__tests__/ui.test.js.map +1 -1
  369. package/dist/question/autopilot-wait.js.map +1 -1
  370. package/dist/question/client.d.ts.map +1 -1
  371. package/dist/question/client.js.map +1 -1
  372. package/dist/question/deep-interview.js.map +1 -1
  373. package/dist/question/events.js.map +1 -1
  374. package/dist/question/renderer.js.map +1 -1
  375. package/dist/question/state.d.ts.map +1 -1
  376. package/dist/question/state.js.map +1 -1
  377. package/dist/question/ui.js.map +1 -1
  378. package/dist/ralph/contract.d.ts +1 -1
  379. package/dist/ralph/contract.d.ts.map +1 -1
  380. package/dist/ralplan/__tests__/consensus-gate.test.js +403 -8
  381. package/dist/ralplan/__tests__/consensus-gate.test.js.map +1 -1
  382. package/dist/ralplan/__tests__/runtime.test.js +112 -14
  383. package/dist/ralplan/__tests__/runtime.test.js.map +1 -1
  384. package/dist/ralplan/consensus-gate.d.ts +3 -3
  385. package/dist/ralplan/consensus-gate.d.ts.map +1 -1
  386. package/dist/ralplan/consensus-gate.js +155 -60
  387. package/dist/ralplan/consensus-gate.js.map +1 -1
  388. package/dist/ralplan/runtime.d.ts +2 -2
  389. package/dist/ralplan/runtime.d.ts.map +1 -1
  390. package/dist/ralplan/runtime.js +1 -2
  391. package/dist/ralplan/runtime.js.map +1 -1
  392. package/dist/runtime/bridge.d.ts.map +1 -1
  393. package/dist/runtime/bridge.js.map +1 -1
  394. package/dist/runtime/process-tree.js.map +1 -1
  395. package/dist/runtime/run-loop.js.map +1 -1
  396. package/dist/runtime/run-outcome.d.ts +3 -3
  397. package/dist/runtime/run-outcome.d.ts.map +1 -1
  398. package/dist/runtime/run-outcome.js.map +1 -1
  399. package/dist/runtime/run-state.js.map +1 -1
  400. package/dist/scripts/__tests__/codex-native-hook.test.js +4121 -592
  401. package/dist/scripts/__tests__/codex-native-hook.test.js.map +1 -1
  402. package/dist/scripts/__tests__/fixtures/issue-3138-installed-smoke.d.ts +2 -0
  403. package/dist/scripts/__tests__/fixtures/issue-3138-installed-smoke.d.ts.map +1 -0
  404. package/dist/scripts/__tests__/fixtures/issue-3138-installed-smoke.js +634 -0
  405. package/dist/scripts/__tests__/fixtures/issue-3138-installed-smoke.js.map +1 -0
  406. package/dist/scripts/__tests__/generate-release-body.test.js.map +1 -1
  407. package/dist/scripts/__tests__/hook-derived-watcher.test.js.map +1 -1
  408. package/dist/scripts/__tests__/notify-dispatcher.test.js.map +1 -1
  409. package/dist/scripts/__tests__/notify-fallback-delivery.test.d.ts +2 -0
  410. package/dist/scripts/__tests__/notify-fallback-delivery.test.d.ts.map +1 -0
  411. package/dist/scripts/__tests__/notify-fallback-delivery.test.js +174 -0
  412. package/dist/scripts/__tests__/notify-fallback-delivery.test.js.map +1 -0
  413. package/dist/scripts/__tests__/notify-state-io.test.js +57 -1
  414. package/dist/scripts/__tests__/notify-state-io.test.js.map +1 -1
  415. package/dist/scripts/__tests__/role-intent-bootstrap-e2e-3181.test.d.ts +2 -0
  416. package/dist/scripts/__tests__/role-intent-bootstrap-e2e-3181.test.d.ts.map +1 -0
  417. package/dist/scripts/__tests__/role-intent-bootstrap-e2e-3181.test.js +205 -0
  418. package/dist/scripts/__tests__/role-intent-bootstrap-e2e-3181.test.js.map +1 -0
  419. package/dist/scripts/__tests__/role-intent-durable-recovery-3181.test.d.ts +2 -0
  420. package/dist/scripts/__tests__/role-intent-durable-recovery-3181.test.d.ts.map +1 -0
  421. package/dist/scripts/__tests__/role-intent-durable-recovery-3181.test.js +175 -0
  422. package/dist/scripts/__tests__/role-intent-durable-recovery-3181.test.js.map +1 -0
  423. package/dist/scripts/__tests__/run-test-files.test.js.map +1 -1
  424. package/dist/scripts/__tests__/smoke-packed-install.test.js +848 -4
  425. package/dist/scripts/__tests__/smoke-packed-install.test.js.map +1 -1
  426. package/dist/scripts/__tests__/test-reply-listener-live.test.js.map +1 -1
  427. package/dist/scripts/build-api.js.map +1 -1
  428. package/dist/scripts/build-explore-harness.js.map +1 -1
  429. package/dist/scripts/build-sparkshell.js.map +1 -1
  430. package/dist/scripts/cleanup-explore-harness.js.map +1 -1
  431. package/dist/scripts/codex-execution-surface.js.map +1 -1
  432. package/dist/scripts/codex-native-hook.d.ts.map +1 -1
  433. package/dist/scripts/codex-native-hook.js +689 -156
  434. package/dist/scripts/codex-native-hook.js.map +1 -1
  435. package/dist/scripts/generate-native-release-manifest.js.map +1 -1
  436. package/dist/scripts/generate-release-body.js.map +1 -1
  437. package/dist/scripts/hook-payload-guard.d.ts.map +1 -1
  438. package/dist/scripts/hook-payload-guard.js.map +1 -1
  439. package/dist/scripts/notify-fallback-delivery.d.ts +48 -0
  440. package/dist/scripts/notify-fallback-delivery.d.ts.map +1 -0
  441. package/dist/scripts/notify-fallback-delivery.js +419 -0
  442. package/dist/scripts/notify-fallback-delivery.js.map +1 -0
  443. package/dist/scripts/notify-fallback-watcher.js +136 -30
  444. package/dist/scripts/notify-fallback-watcher.js.map +1 -1
  445. package/dist/scripts/notify-hook/auto-nudge.d.ts +18 -9
  446. package/dist/scripts/notify-hook/auto-nudge.d.ts.map +1 -1
  447. package/dist/scripts/notify-hook/auto-nudge.js +39 -13
  448. package/dist/scripts/notify-hook/auto-nudge.js.map +1 -1
  449. package/dist/scripts/notify-hook/managed-tmux.d.ts +23 -0
  450. package/dist/scripts/notify-hook/managed-tmux.d.ts.map +1 -1
  451. package/dist/scripts/notify-hook/managed-tmux.js +122 -12
  452. package/dist/scripts/notify-hook/managed-tmux.js.map +1 -1
  453. package/dist/scripts/notify-hook/orchestration-intent.d.ts +7 -7
  454. package/dist/scripts/notify-hook/orchestration-intent.d.ts.map +1 -1
  455. package/dist/scripts/notify-hook/ralph-session-resume.d.ts +5 -2
  456. package/dist/scripts/notify-hook/ralph-session-resume.d.ts.map +1 -1
  457. package/dist/scripts/notify-hook/ralph-session-resume.js +33 -37
  458. package/dist/scripts/notify-hook/ralph-session-resume.js.map +1 -1
  459. package/dist/scripts/notify-hook/state-io.d.ts +17 -0
  460. package/dist/scripts/notify-hook/state-io.d.ts.map +1 -1
  461. package/dist/scripts/notify-hook/state-io.js +73 -11
  462. package/dist/scripts/notify-hook/state-io.js.map +1 -1
  463. package/dist/scripts/notify-hook/team-dispatch.d.ts +1 -1
  464. package/dist/scripts/notify-hook/team-dispatch.js.map +1 -1
  465. package/dist/scripts/notify-hook/team-leader-nudge.d.ts +2 -2
  466. package/dist/scripts/notify-hook/team-tmux-guard.d.ts +4 -4
  467. package/dist/scripts/notify-hook/team-worker-posttooluse.js.map +1 -1
  468. package/dist/scripts/notify-hook/team-worker-stop.d.ts +1 -1
  469. package/dist/scripts/notify-hook/team-worker.d.ts +2 -2
  470. package/dist/scripts/notify-hook/tmux-injection.d.ts +8 -1
  471. package/dist/scripts/notify-hook/tmux-injection.d.ts.map +1 -1
  472. package/dist/scripts/notify-hook/tmux-injection.js +25 -16
  473. package/dist/scripts/notify-hook/tmux-injection.js.map +1 -1
  474. package/dist/scripts/notify-hook/visual-verdict.d.ts +1 -1
  475. package/dist/scripts/notify-hook/visual-verdict.d.ts.map +1 -1
  476. package/dist/scripts/notify-hook/visual-verdict.js +15 -13
  477. package/dist/scripts/notify-hook/visual-verdict.js.map +1 -1
  478. package/dist/scripts/notify-hook.d.ts +24 -1
  479. package/dist/scripts/notify-hook.d.ts.map +1 -1
  480. package/dist/scripts/notify-hook.js +266 -90
  481. package/dist/scripts/notify-hook.js.map +1 -1
  482. package/dist/scripts/postinstall.js.map +1 -1
  483. package/dist/scripts/prompt-inventory.js.map +1 -1
  484. package/dist/scripts/smoke-packed-install.d.ts +879 -2
  485. package/dist/scripts/smoke-packed-install.d.ts.map +1 -1
  486. package/dist/scripts/smoke-packed-install.js +1528 -7
  487. package/dist/scripts/smoke-packed-install.js.map +1 -1
  488. package/dist/scripts/sync-plugin-mirror.js.map +1 -1
  489. package/dist/scripts/test-reply-listener-live.js.map +1 -1
  490. package/dist/scripts/test-sparkshell.js.map +1 -1
  491. package/dist/scripts/verify-native-agents.js.map +1 -1
  492. package/dist/session-history/friction.js.map +1 -1
  493. package/dist/session-history/search.js.map +1 -1
  494. package/dist/sidecar/__tests__/boundary.test.js.map +1 -1
  495. package/dist/sidecar/collector.js.map +1 -1
  496. package/dist/sidecar/index.js.map +1 -1
  497. package/dist/sidecar/render.js.map +1 -1
  498. package/dist/sidecar/tmux.js.map +1 -1
  499. package/dist/state/__tests__/operations.test.js +64 -15
  500. package/dist/state/__tests__/operations.test.js.map +1 -1
  501. package/dist/state/__tests__/skill-active.test.js +19 -1
  502. package/dist/state/__tests__/skill-active.test.js.map +1 -1
  503. package/dist/state/__tests__/workflow-transition.test.js +61 -0
  504. package/dist/state/__tests__/workflow-transition.test.js.map +1 -1
  505. package/dist/state/mode-state-context.js.map +1 -1
  506. package/dist/state/operations.d.ts +1 -1
  507. package/dist/state/operations.d.ts.map +1 -1
  508. package/dist/state/operations.js +49 -55
  509. package/dist/state/operations.js.map +1 -1
  510. package/dist/state/paths.d.ts +1 -1
  511. package/dist/state/paths.d.ts.map +1 -1
  512. package/dist/state/paths.js +1 -1
  513. package/dist/state/paths.js.map +1 -1
  514. package/dist/state/skill-active.d.ts +13 -1
  515. package/dist/state/skill-active.d.ts.map +1 -1
  516. package/dist/state/skill-active.js +43 -4
  517. package/dist/state/skill-active.js.map +1 -1
  518. package/dist/state/terminal-normalization.js.map +1 -1
  519. package/dist/state/workflow-transition-reconcile.d.ts.map +1 -1
  520. package/dist/state/workflow-transition-reconcile.js +20 -4
  521. package/dist/state/workflow-transition-reconcile.js.map +1 -1
  522. package/dist/state/workflow-transition.d.ts +1 -1
  523. package/dist/state/workflow-transition.d.ts.map +1 -1
  524. package/dist/state/workflow-transition.js.map +1 -1
  525. package/dist/subagents/__tests__/adapted-role-binding.test.d.ts +2 -0
  526. package/dist/subagents/__tests__/adapted-role-binding.test.d.ts.map +1 -0
  527. package/dist/subagents/__tests__/adapted-role-binding.test.js +988 -0
  528. package/dist/subagents/__tests__/adapted-role-binding.test.js.map +1 -0
  529. package/dist/subagents/__tests__/leader-bootstrap-3181.test.d.ts +2 -0
  530. package/dist/subagents/__tests__/leader-bootstrap-3181.test.d.ts.map +1 -0
  531. package/dist/subagents/__tests__/leader-bootstrap-3181.test.js +273 -0
  532. package/dist/subagents/__tests__/leader-bootstrap-3181.test.js.map +1 -0
  533. package/dist/subagents/__tests__/tracker.test.js +1550 -10
  534. package/dist/subagents/__tests__/tracker.test.js.map +1 -1
  535. package/dist/subagents/adapted-role-binding.d.ts +17 -0
  536. package/dist/subagents/adapted-role-binding.d.ts.map +1 -0
  537. package/dist/subagents/adapted-role-binding.js +75 -0
  538. package/dist/subagents/adapted-role-binding.js.map +1 -0
  539. package/dist/subagents/role-routing-marker.d.ts +10 -0
  540. package/dist/subagents/role-routing-marker.d.ts.map +1 -0
  541. package/dist/subagents/role-routing-marker.js +136 -0
  542. package/dist/subagents/role-routing-marker.js.map +1 -0
  543. package/dist/subagents/tracker.d.ts +172 -0
  544. package/dist/subagents/tracker.d.ts.map +1 -1
  545. package/dist/subagents/tracker.js +1077 -61
  546. package/dist/subagents/tracker.js.map +1 -1
  547. package/dist/team/__tests__/approved-execution.test.js.map +1 -1
  548. package/dist/team/__tests__/delivery-e2e-smoke.test.js.map +1 -1
  549. package/dist/team/__tests__/model-contract.test.js +231 -1
  550. package/dist/team/__tests__/model-contract.test.js.map +1 -1
  551. package/dist/team/__tests__/role-router.test.js.map +1 -1
  552. package/dist/team/__tests__/runtime.test.js +401 -1
  553. package/dist/team/__tests__/runtime.test.js.map +1 -1
  554. package/dist/team/__tests__/scaling.test.js +181 -0
  555. package/dist/team/__tests__/scaling.test.js.map +1 -1
  556. package/dist/team/__tests__/state-root.test.js.map +1 -1
  557. package/dist/team/__tests__/tmux-session.test.js +434 -4
  558. package/dist/team/__tests__/tmux-session.test.js.map +1 -1
  559. package/dist/team/__tests__/tmux-test-fixture.js.map +1 -1
  560. package/dist/team/api-interop.d.ts +2 -2
  561. package/dist/team/api-interop.d.ts.map +1 -1
  562. package/dist/team/api-interop.js.map +1 -1
  563. package/dist/team/commit-hygiene.js.map +1 -1
  564. package/dist/team/contracts.d.ts +5 -5
  565. package/dist/team/contracts.d.ts.map +1 -1
  566. package/dist/team/delivery-log.js.map +1 -1
  567. package/dist/team/followup-planner.js.map +1 -1
  568. package/dist/team/goal-workflow.js.map +1 -1
  569. package/dist/team/idle-nudge.d.ts.map +1 -1
  570. package/dist/team/model-contract.d.ts +20 -1
  571. package/dist/team/model-contract.d.ts.map +1 -1
  572. package/dist/team/model-contract.js +267 -30
  573. package/dist/team/model-contract.js.map +1 -1
  574. package/dist/team/orchestrator.js.map +1 -1
  575. package/dist/team/pane-status.js.map +1 -1
  576. package/dist/team/phase-controller.js.map +1 -1
  577. package/dist/team/reminder-intents.d.ts +1 -1
  578. package/dist/team/reminder-intents.d.ts.map +1 -1
  579. package/dist/team/reminder-intents.js.map +1 -1
  580. package/dist/team/runtime-cli.js.map +1 -1
  581. package/dist/team/runtime.d.ts.map +1 -1
  582. package/dist/team/runtime.js +83 -52
  583. package/dist/team/runtime.js.map +1 -1
  584. package/dist/team/scaling.d.ts +9 -7
  585. package/dist/team/scaling.d.ts.map +1 -1
  586. package/dist/team/scaling.js +69 -28
  587. package/dist/team/scaling.js.map +1 -1
  588. package/dist/team/state/dispatch-lock.js.map +1 -1
  589. package/dist/team/state/dispatch.js.map +1 -1
  590. package/dist/team/state/events.js.map +1 -1
  591. package/dist/team/state/tasks.js.map +1 -1
  592. package/dist/team/state-root.js.map +1 -1
  593. package/dist/team/state.js.map +1 -1
  594. package/dist/team/team-identity.d.ts.map +1 -1
  595. package/dist/team/team-identity.js.map +1 -1
  596. package/dist/team/tmux-session.d.ts +2 -0
  597. package/dist/team/tmux-session.d.ts.map +1 -1
  598. package/dist/team/tmux-session.js +90 -42
  599. package/dist/team/tmux-session.js.map +1 -1
  600. package/dist/team/worker-bootstrap.js.map +1 -1
  601. package/dist/team/worktree.js.map +1 -1
  602. package/dist/ultragoal/__tests__/artifacts.test.js.map +1 -1
  603. package/dist/ultragoal/__tests__/docs-contract.test.js.map +1 -1
  604. package/dist/ultragoal/artifacts.d.ts.map +1 -1
  605. package/dist/ultragoal/artifacts.js +4 -1
  606. package/dist/ultragoal/artifacts.js.map +1 -1
  607. package/dist/url-reader/index.js.map +1 -1
  608. package/dist/utils/agents-model-table.js.map +1 -1
  609. package/dist/utils/package.js.map +1 -1
  610. package/dist/utils/paths.js.map +1 -1
  611. package/dist/utils/platform-command.js.map +1 -1
  612. package/dist/utils/repo-deps.js.map +1 -1
  613. package/dist/utils/version.js.map +1 -1
  614. package/dist/verification/__tests__/ci-rust-gates.test.js +1 -1
  615. package/dist/verification/__tests__/dev-merge-issue-close-workflow.test.js.map +1 -1
  616. package/dist/verification/verifier.js.map +1 -1
  617. package/dist/visual/constants.d.ts +1 -1
  618. package/dist/visual/constants.d.ts.map +1 -1
  619. package/dist/vscode/__tests__/index.test.js.map +1 -1
  620. package/dist/vscode/index.js.map +1 -1
  621. package/dist/wiki/__tests__/cjk-tokenize.test.js.map +1 -1
  622. package/dist/wiki/__tests__/lint.test.js.map +1 -1
  623. package/dist/wiki/__tests__/query.test.js.map +1 -1
  624. package/dist/wiki/__tests__/storage.test.js.map +1 -1
  625. package/dist/wiki/__tests__/test-helpers.d.ts.map +1 -1
  626. package/dist/wiki/lint.js.map +1 -1
  627. package/dist/wiki/query.js.map +1 -1
  628. package/dist/wiki/storage.js.map +1 -1
  629. package/package.json +2 -2
  630. package/plugins/oh-my-codex/.codex-plugin/plugin.json +1 -1
  631. package/plugins/oh-my-codex/skills/omx-setup/SKILL.md +10 -5
  632. package/plugins/oh-my-codex/skills/ralph/SKILL.md +4 -2
  633. package/plugins/oh-my-codex/skills/ralplan/SKILL.md +5 -2
  634. package/skills/omx-setup/SKILL.md +10 -5
  635. package/skills/ralph/SKILL.md +4 -2
  636. package/skills/ralplan/SKILL.md +5 -2
  637. package/src/scripts/__tests__/codex-native-hook.test.ts +17522 -10630
  638. package/src/scripts/__tests__/fixtures/issue-3138-installed-smoke.ts +734 -0
  639. package/src/scripts/__tests__/notify-fallback-delivery.test.ts +177 -0
  640. package/src/scripts/__tests__/notify-state-io.test.ts +73 -0
  641. package/src/scripts/__tests__/role-intent-bootstrap-e2e-3181.test.ts +215 -0
  642. package/src/scripts/__tests__/role-intent-durable-recovery-3181.test.ts +193 -0
  643. package/src/scripts/__tests__/smoke-packed-install.test.ts +991 -4
  644. package/src/scripts/codex-native-hook.ts +779 -159
  645. package/src/scripts/notify-fallback-delivery.ts +416 -0
  646. package/src/scripts/notify-fallback-watcher.ts +132 -40
  647. package/src/scripts/notify-hook/auto-nudge.ts +43 -12
  648. package/src/scripts/notify-hook/managed-tmux.ts +149 -12
  649. package/src/scripts/notify-hook/ralph-session-resume.ts +37 -36
  650. package/src/scripts/notify-hook/state-io.ts +100 -13
  651. package/src/scripts/notify-hook/tmux-injection.ts +38 -21
  652. package/src/scripts/notify-hook/visual-verdict.ts +22 -12
  653. package/src/scripts/notify-hook.ts +335 -90
  654. package/src/scripts/smoke-packed-install.ts +1829 -6
@@ -8,13 +8,35 @@ import { mkdtemp, readFile, rm, writeFile } from "node:fs/promises";
8
8
  import { tmpdir } from "node:os";
9
9
  import { isAbsolute, join } from "node:path";
10
10
  import TOML from "@iarna/toml";
11
- import { analyzeLegacyMultiAgentConfig, buildMergedConfig, cleanCodexModelAvailabilityNuxIfNeeded, hasExactOmxSeededBehavioralDefaultsPair, mergeConfig, repairConfigIfNeeded, stripManagedCodexHookTrustState, stripOmxFeatureFlags, upsertManagedCodexHookTrustState, stripOmxSeededBehavioralDefaults, } from "../generator.js";
12
- import { buildManagedCodexHookTrustState } from "../codex-hooks.js";
11
+ import { analyzeLegacyMultiAgentConfig, buildMergedConfig, cleanCodexModelAvailabilityNuxIfNeeded, hasExactOmxSeededBehavioralDefaultsPair, mergeConfig, repairConfigIfNeeded, stripExistingOmxBlocks, stripManagedCodexHookTrustState, stripOmxFeatureFlags, upsertManagedCodexHookTrustState, stripOmxSeededBehavioralDefaults, } from "../generator.js";
12
+ import { MANAGED_HOOK_EVENTS, ManagedCodexHooksPlanError, buildManagedCodexHookTrustState, planManagedCodexHooksMerge, } from "../codex-hooks.js";
13
13
  import { OMX_FIRST_PARTY_MCP_SERVER_NAMES } from "../omx-first-party-mcp.js";
14
14
  /** Count occurrences of a pattern in text */
15
15
  function count(text, pattern) {
16
16
  return (text.match(pattern) ?? []).length;
17
17
  }
18
+ async function writeSetupGeneratedHookTrustFixture(wd) {
19
+ const configPath = join(wd, "config.toml");
20
+ const hooksPath = join(wd, "hooks.json");
21
+ const hooksPlan = planManagedCodexHooksMerge(null, wd, hooksPath);
22
+ if (!hooksPlan.ok || hooksPlan.finalContent === null) {
23
+ throw new Error("Expected setup hook plan to produce a hooks artifact.");
24
+ }
25
+ await writeFile(hooksPath, hooksPlan.finalContent);
26
+ const config = buildMergedConfig("", wd, {
27
+ codexHooksFile: hooksPath,
28
+ codexHooksContent: hooksPlan.finalContent,
29
+ managedHookTrustState: hooksPlan.finalTrustState,
30
+ priorManagedHookTrustState: hooksPlan.priorTrustState,
31
+ });
32
+ await writeFile(configPath, config);
33
+ return {
34
+ configPath,
35
+ hooksPath,
36
+ config,
37
+ trustState: hooksPlan.finalTrustState,
38
+ };
39
+ }
18
40
  /** Assert the current OMX block appears exactly once */
19
41
  function assertSingleOmxBlock(toml, options = {}) {
20
42
  assert.equal(count(toml, /# oh-my-codex \(OMX\) Configuration/g), 1, "OMX marker should appear once");
@@ -788,6 +810,106 @@ describe("config generator idempotency (#384)", () => {
788
810
  await rm(wd, { recursive: true, force: true });
789
811
  }
790
812
  });
813
+ it("strips only complete exact OMX marker blocks outside TOML values", () => {
814
+ const start = "# oh-my-codex (OMX) Configuration";
815
+ const end = "# End oh-my-codex";
816
+ const decoys = [
817
+ `basic = "${start}"`,
818
+ `literal = '${end}'`,
819
+ 'multiline_basic = """',
820
+ start,
821
+ end,
822
+ '"""',
823
+ "multiline_literal = '''",
824
+ start,
825
+ end,
826
+ "'''",
827
+ `# Marker-shaped text: ${start}`,
828
+ `# Marker-shaped text: ${end}`,
829
+ ];
830
+ const decoyOnly = [...decoys, ""].join("\n");
831
+ assert.deepEqual(stripExistingOmxBlocks(decoyOnly), {
832
+ cleaned: decoyOnly,
833
+ removed: 0,
834
+ });
835
+ const incomplete = [...decoys, "", start, "[user.incomplete]", "enabled = true", ""].join("\n");
836
+ assert.deepEqual(stripExistingOmxBlocks(incomplete), {
837
+ cleaned: incomplete,
838
+ removed: 0,
839
+ });
840
+ const managed = [
841
+ ...decoys,
842
+ "",
843
+ "# ============================================================",
844
+ start,
845
+ "[mcp_servers.omx_state]",
846
+ 'command = "node"',
847
+ end,
848
+ "",
849
+ "[user.after]",
850
+ 'name = "kept"',
851
+ "",
852
+ ].join("\n");
853
+ const expected = [...decoys, "", "[user.after]", 'name = "kept"', ""].join("\n");
854
+ assert.deepEqual(stripExistingOmxBlocks(managed), {
855
+ cleaned: expected,
856
+ removed: 1,
857
+ });
858
+ assert.doesNotThrow(() => TOML.parse(expected));
859
+ });
860
+ it("keeps OMX delimiter comments inert inside complete TOML assignments", () => {
861
+ const start = "# oh-my-codex (OMX) Configuration";
862
+ const end = "# End oh-my-codex";
863
+ const fixtures = [
864
+ ["root multiline array", ["root = [", start, end, "]"].join("\n")],
865
+ ["dotted multiline array", ["outer.value = [", start, end, "]"].join("\n")],
866
+ ["quoted multiline array", ['"quoted" = [', start, end, "]"].join("\n")],
867
+ ["empty-quoted multiline array", ['"" = [', start, end, "]"].join("\n")],
868
+ ["root multiline basic value", ['root_value = """', start, end, '"""'].join("\n")],
869
+ ["dotted multiline literal value", ["outer.literal = '''", start, end, "'''"].join("\n")],
870
+ ];
871
+ for (const [name, assignment] of fixtures) {
872
+ const config = `${assignment}\n`;
873
+ assert.doesNotThrow(() => TOML.parse(config), `${name} fixture must be valid TOML`);
874
+ assert.deepEqual(stripExistingOmxBlocks(config), { cleaned: config, removed: 0 }, name);
875
+ const merged = buildMergedConfig(config, "/tmp/omx");
876
+ assert.ok(merged.includes(assignment), `${name} bytes must survive merging`);
877
+ assert.doesNotThrow(() => TOML.parse(merged), `${name} merged config must be valid TOML`);
878
+ }
879
+ });
880
+ it("rejects marker-contained hook trust state without ownership proof", () => {
881
+ const start = "# oh-my-codex (OMX) Configuration";
882
+ const end = "# End oh-my-codex";
883
+ const fixtures = [
884
+ ["table", ['[hooks.state."foreign"]', 'trusted_hash = "sha256:foreign"'].join("\n")],
885
+ ["dotted", 'hooks.state."foreign" = { trusted_hash = "sha256:foreign" }'],
886
+ ["inline", 'hooks = { state = { foreign = { trusted_hash = "sha256:foreign" } } }'],
887
+ ["scalar", 'hooks.state = "foreign"'],
888
+ ];
889
+ const isManagedTrustConflict = (error) => error instanceof ManagedCodexHooksPlanError &&
890
+ error.code === "managed_trust_key_conflict";
891
+ for (const [name, content] of fixtures) {
892
+ const config = [start, content, end, ""].join("\n");
893
+ assert.doesNotThrow(() => TOML.parse(config), `${name} fixture must be valid TOML`);
894
+ assert.throws(() => stripExistingOmxBlocks(config), isManagedTrustConflict, name);
895
+ assert.throws(() => buildMergedConfig(config, "/tmp/omx"), isManagedTrustConflict, name);
896
+ }
897
+ });
898
+ it("matches managed hooks.state keys case-sensitively", () => {
899
+ const start = "# oh-my-codex (OMX) Configuration";
900
+ const end = "# End oh-my-codex";
901
+ const variants = [
902
+ 'Hooks.state = { foreign = { trusted_hash = "sha256:foreign" } }',
903
+ 'hooks.State = { foreign = { trusted_hash = "sha256:foreign" } }',
904
+ '"Hooks"."State" = { foreign = { trusted_hash = "sha256:foreign" } }',
905
+ ];
906
+ for (const variant of variants) {
907
+ const config = [start, variant, end, ""].join("\n");
908
+ assert.doesNotThrow(() => TOML.parse(config));
909
+ assert.equal(stripManagedCodexHookTrustState(config, { managedTrustState: {} }), config, `${variant} must remain foreign`);
910
+ assert.doesNotThrow(() => buildMergedConfig(config, "/tmp/omx"));
911
+ }
912
+ });
791
913
  it("mergeConfig removes legacy omx_team_run tables during setup upgrade", async () => {
792
914
  const wd = await mkdtemp(join(tmpdir(), "omx-idem-"));
793
915
  try {
@@ -887,6 +1009,74 @@ describe("config generator idempotency (#384)", () => {
887
1009
  await rm(wd, { recursive: true, force: true });
888
1010
  }
889
1011
  });
1012
+ it("repairs duplicate TUI config with setup-generated hook trust from the current hooks artifact", async () => {
1013
+ const wd = await mkdtemp(join(tmpdir(), "omx-idem-"));
1014
+ try {
1015
+ const { configPath, config: setupConfig } = await writeSetupGeneratedHookTrustFixture(wd);
1016
+ const trustBlock = setupConfig.match(/# OMX-owned Codex hook trust state\n[\s\S]*?# End OMX-owned Codex hook trust state/)?.[0];
1017
+ assert.ok(trustBlock, "setup fixture must contain fenced managed hook trust");
1018
+ const setupTrustState = TOML.parse(setupConfig).hooks?.state;
1019
+ await writeFile(configPath, `${setupConfig}\n[tui]\nstatus_line = ["git-branch"]\n`);
1020
+ assert.equal(await repairConfigIfNeeded(configPath, wd), true);
1021
+ const repaired = await readFile(configPath, "utf-8");
1022
+ const repairedTrustState = TOML.parse(repaired).hooks?.state;
1023
+ assert.equal(count(repaired, /^\[tui\]$/gm), 1);
1024
+ assert.ok(repaired.includes(trustBlock), "repair must preserve setup-generated trust bytes");
1025
+ assert.deepEqual(repairedTrustState, setupTrustState);
1026
+ }
1027
+ finally {
1028
+ await rm(wd, { recursive: true, force: true });
1029
+ }
1030
+ });
1031
+ it("fails closed without rewriting managed hook trust when launch repair cannot prove it", async () => {
1032
+ const wd = await mkdtemp(join(tmpdir(), "omx-idem-"));
1033
+ try {
1034
+ const { configPath, hooksPath, config: setupConfig, trustState } = await writeSetupGeneratedHookTrustFixture(wd);
1035
+ const [firstTrustState] = Object.values(trustState);
1036
+ assert.ok(firstTrustState);
1037
+ const repairTarget = `${setupConfig}\n[tui]\nstatus_line = ["git-branch"]\n`;
1038
+ const conflicting = repairTarget.replace(firstTrustState.trusted_hash, "sha256:conflicting-user-trust");
1039
+ await writeFile(configPath, conflicting);
1040
+ await assert.rejects(repairConfigIfNeeded(configPath, wd), (error) => error instanceof ManagedCodexHooksPlanError &&
1041
+ error.code === "managed_trust_key_conflict");
1042
+ assert.equal(await readFile(configPath, "utf-8"), conflicting);
1043
+ const unprovenHooks = [
1044
+ ["invalid", "{ not valid JSON"],
1045
+ [
1046
+ "ambiguous",
1047
+ JSON.stringify({
1048
+ hooks: {
1049
+ SessionStart: [
1050
+ {
1051
+ hooks: [
1052
+ {
1053
+ type: "command",
1054
+ command: `NODE ${join(wd, "dist", "scripts", "codex-native-hook.js")}`,
1055
+ },
1056
+ ],
1057
+ },
1058
+ ],
1059
+ },
1060
+ }),
1061
+ ],
1062
+ ["missing", null],
1063
+ ];
1064
+ for (const [name, hooksContent] of unprovenHooks) {
1065
+ await writeFile(configPath, repairTarget);
1066
+ if (hooksContent === null) {
1067
+ await rm(hooksPath);
1068
+ }
1069
+ else {
1070
+ await writeFile(hooksPath, hooksContent);
1071
+ }
1072
+ await assert.rejects(repairConfigIfNeeded(configPath, wd), (error) => error instanceof ManagedCodexHooksPlanError, name);
1073
+ assert.equal(await readFile(configPath, "utf-8"), repairTarget, name);
1074
+ }
1075
+ }
1076
+ finally {
1077
+ await rm(wd, { recursive: true, force: true });
1078
+ }
1079
+ });
890
1080
  it("preserves trailing user config after an unmatched managed hook trust-state start marker", () => {
891
1081
  const malformed = [
892
1082
  'model = "gpt-5.6-sol"',
@@ -952,7 +1142,7 @@ describe("config generator idempotency (#384)", () => {
952
1142
  assert.match(stripped, /^\[desktop\]$/m);
953
1143
  assert.doesNotThrow(() => TOML.parse(stripped));
954
1144
  });
955
- it("preserves same-key user hook trust state and suppresses generated duplicates", () => {
1145
+ it("fails closed on conflicting same-key hook trust state", () => {
956
1146
  const hooksPath = "/tmp/codex/hooks.json";
957
1147
  const config = [
958
1148
  'model = "gpt-5.6-sol"',
@@ -962,13 +1152,10 @@ describe("config generator idempotency (#384)", () => {
962
1152
  "enabled = false",
963
1153
  "",
964
1154
  ].join("\n");
965
- const refreshed = upsertManagedCodexHookTrustState(config, "/tmp/omx", hooksPath);
966
- assert.match(refreshed, /^trusted_hash = "sha256:user"$/m);
967
- assert.match(refreshed, /^enabled = false$/m);
968
- assert.equal(count(refreshed, /^\[hooks\.state\."\/tmp\/codex\/hooks\.json:post_compact:0:0"\]$/gm), 1, "preserved same-key user trust state must not be duplicated");
969
- assert.doesNotThrow(() => TOML.parse(refreshed));
1155
+ assert.throws(() => upsertManagedCodexHookTrustState(config, "/tmp/omx", hooksPath), (error) => error instanceof ManagedCodexHooksPlanError &&
1156
+ error.code === "managed_trust_key_conflict");
970
1157
  });
971
- it("preserves unproven same-key hook trust-state tables", () => {
1158
+ it("fails closed on unproven same-key hook trust-state tables", () => {
972
1159
  const hooksPath = "/tmp/codex/hooks.json";
973
1160
  const managedTrustState = buildManagedCodexHookTrustState(hooksPath, "/tmp/omx");
974
1161
  const fixtures = [
@@ -1018,10 +1205,534 @@ describe("config generator idempotency (#384)", () => {
1018
1205
  ],
1019
1206
  ];
1020
1207
  for (const [name, table] of fixtures) {
1021
- const stripped = stripManagedCodexHookTrustState(table.join("\n"), {
1208
+ assert.throws(() => stripManagedCodexHookTrustState(table.join("\n"), {
1022
1209
  managedTrustState,
1023
- });
1024
- assert.match(stripped, /^\[hooks\.state\."\/tmp\/codex\/hooks\.json:post_compact:0:0"\]/m, `${name} should be preserved`);
1210
+ }), (error) => error instanceof ManagedCodexHooksPlanError &&
1211
+ error.code === "managed_trust_key_conflict", name);
1212
+ }
1213
+ });
1214
+ it("handles semantically equivalent TOML trust-key variants before writing a replacement", () => {
1215
+ const hooksPath = "/tmp/codex/hooks.json";
1216
+ const managedTrustState = buildManagedCodexHookTrustState(hooksPath, "/tmp/omx");
1217
+ const key = `${hooksPath}:post_compact:0:0`;
1218
+ const hash = managedTrustState[key]?.trusted_hash;
1219
+ assert.ok(hash);
1220
+ const matchingHeaders = [
1221
+ `[hooks.state.'${key}']`,
1222
+ `[hooks.state."${key.replace(/\//g, "\\u002f")}"]`,
1223
+ ];
1224
+ for (const header of matchingHeaders) {
1225
+ const matching = [header, `trusted_hash = "${hash}"`, ""].join("\n");
1226
+ const refreshed = upsertManagedCodexHookTrustState(matching, "/tmp/omx", hooksPath, { managedTrustState });
1227
+ assert.equal(count(refreshed, /^\[hooks\.state\./gm), Object.keys(managedTrustState).length);
1228
+ assert.equal(TOML.parse(refreshed)
1229
+ .hooks?.state?.[key] !== undefined, true);
1230
+ }
1231
+ const conflicting = [
1232
+ matchingHeaders[0],
1233
+ 'trusted_hash = "sha256:foreign"',
1234
+ "",
1235
+ ].join("\n");
1236
+ assert.throws(() => upsertManagedCodexHookTrustState(conflicting, "/tmp/omx", hooksPath, { managedTrustState }), (error) => error instanceof ManagedCodexHooksPlanError &&
1237
+ error.code === "managed_trust_key_conflict");
1238
+ });
1239
+ it("removes only a single exact proven managed dotted hook trust assignment", () => {
1240
+ const hooksPath = "/tmp/codex/hooks.json";
1241
+ const managedTrustState = buildManagedCodexHookTrustState(hooksPath, "/tmp/omx");
1242
+ const key = `${hooksPath}:post_compact:0:0`;
1243
+ const hash = managedTrustState[key]?.trusted_hash;
1244
+ assert.ok(hash);
1245
+ const exactAssignments = [
1246
+ `hooks.state."${key}".trusted_hash = "${hash}"`,
1247
+ `hooks . state . '${key}' . trusted_hash = "${hash}"`,
1248
+ ];
1249
+ for (const exact of exactAssignments) {
1250
+ assert.equal(stripManagedCodexHookTrustState(exact, { managedTrustState }), "");
1251
+ }
1252
+ const exact = exactAssignments[1];
1253
+ const refreshed = upsertManagedCodexHookTrustState(exact, "/tmp/omx", hooksPath, { managedTrustState });
1254
+ assert.doesNotMatch(refreshed, /^hooks\s*\.\s*state\s*\./m);
1255
+ assert.equal(count(refreshed, /^\[hooks\.state\./gm), Object.keys(managedTrustState).length);
1256
+ });
1257
+ it("fails closed for conflicting, commented, or multi-field managed dotted hook trust assignments", () => {
1258
+ const hooksPath = "/tmp/codex/hooks.json";
1259
+ const managedTrustState = buildManagedCodexHookTrustState(hooksPath, "/tmp/omx");
1260
+ const key = `${hooksPath}:post_compact:0:0`;
1261
+ const hash = managedTrustState[key]?.trusted_hash;
1262
+ assert.ok(hash);
1263
+ const assignment = `hooks.state.'${key}'.trusted_hash = "${hash}"`;
1264
+ const fixtures = [
1265
+ assignment.replace(hash, "sha256:foreign"),
1266
+ `${assignment}\nhooks.state.'${key}'.enabled = true`,
1267
+ `${assignment} # user comment`,
1268
+ ];
1269
+ for (const fixture of fixtures) {
1270
+ assert.throws(() => stripManagedCodexHookTrustState(fixture, { managedTrustState }), (error) => error instanceof ManagedCodexHooksPlanError &&
1271
+ error.code === "managed_trust_key_conflict");
1272
+ }
1273
+ });
1274
+ it("removes exact inline hook trust state without changing adjacent TOML", () => {
1275
+ const hooksPath = "/tmp/codex/hooks.json";
1276
+ const managedTrustState = buildManagedCodexHookTrustState(hooksPath, "/tmp/omx");
1277
+ const key = `${hooksPath}:post_compact:0:0`;
1278
+ const hash = managedTrustState[key]?.trusted_hash;
1279
+ assert.ok(hash);
1280
+ const exactWholeStatement = `hooks = { state = { "${key}" = { trusted_hash = "${hash}" } } }`;
1281
+ assert.equal(stripManagedCodexHookTrustState(exactWholeStatement, { managedTrustState }), "", "an exact single-field inline hooks state remains removable");
1282
+ const inline = [
1283
+ 'model = "gpt-5.6-sol"',
1284
+ `hooks.state.'${key}' = { trusted_hash = '${hash}' }`,
1285
+ "",
1286
+ "[desktop]",
1287
+ "git-create-pull-request-as-draft = true",
1288
+ "",
1289
+ ].join("\n");
1290
+ const stripped = stripManagedCodexHookTrustState(inline, { managedTrustState });
1291
+ assert.doesNotMatch(stripped, /hooks\.state/);
1292
+ assert.match(stripped, /^model = "gpt-5\.6-sol"$/m);
1293
+ assert.match(stripped, /^\[desktop\]$/m);
1294
+ assert.match(stripped, /^git-create-pull-request-as-draft = true$/m);
1295
+ assert.doesNotThrow(() => TOML.parse(stripped));
1296
+ const refreshed = upsertManagedCodexHookTrustState(inline, "/tmp/omx", hooksPath, { managedTrustState });
1297
+ assert.equal(Object.keys(TOML.parse(refreshed)
1298
+ .hooks?.state ?? {}).filter((stateKey) => stateKey === key).length, 1);
1299
+ });
1300
+ it("fails closed for inline, dotted, and table hook trust representations with foreign siblings", () => {
1301
+ const hooksPath = "/tmp/codex/hooks.json";
1302
+ const managedTrustState = buildManagedCodexHookTrustState(hooksPath, "/tmp/omx");
1303
+ const key = `${hooksPath}:post_compact:0:0`;
1304
+ const hash = managedTrustState[key]?.trusted_hash;
1305
+ assert.ok(hash);
1306
+ const fixtures = [
1307
+ [
1308
+ "inline",
1309
+ `hooks = { state = { "${key}" = { trusted_hash = "${hash}" } }, foreign = "keep" }`,
1310
+ ],
1311
+ [
1312
+ "dotted",
1313
+ `hooks.state = { "${key}" = { trusted_hash = "${hash}" }, foreign = "keep" }`,
1314
+ ],
1315
+ [
1316
+ "table",
1317
+ [
1318
+ "[hooks]",
1319
+ `state = { "${key}" = { trusted_hash = "${hash}" } }`,
1320
+ 'foreign = "keep"',
1321
+ ].join("\n"),
1322
+ ],
1323
+ ];
1324
+ for (const [name, fixture] of fixtures) {
1325
+ assert.doesNotThrow(() => TOML.parse(fixture), `${name} fixture must be valid TOML`);
1326
+ assert.throws(() => stripManagedCodexHookTrustState(fixture, { managedTrustState }), (error) => error instanceof ManagedCodexHooksPlanError &&
1327
+ error.code === "managed_trust_key_conflict", `${name} cleanup must preserve the entire foreign-containing representation`);
1328
+ assert.throws(() => upsertManagedCodexHookTrustState(fixture, "/tmp/omx", hooksPath, { managedTrustState }), (error) => error instanceof ManagedCodexHooksPlanError &&
1329
+ error.code === "managed_trust_key_conflict", `${name} refresh must preserve the original bytes`);
1330
+ }
1331
+ });
1332
+ it("fails closed for commented trust assignments inside a managed config marker", () => {
1333
+ const fixture = [
1334
+ "# oh-my-codex (OMX) Configuration",
1335
+ 'hooks.state."foreign-key".trusted_hash = "sha256:foreign" # preserve me',
1336
+ "# End oh-my-codex",
1337
+ "",
1338
+ ].join("\n");
1339
+ assert.throws(() => stripManagedCodexHookTrustState(fixture, { managedTrustState: {} }), (error) => error instanceof ManagedCodexHooksPlanError &&
1340
+ error.code === "managed_trust_key_conflict");
1341
+ });
1342
+ it("fails closed on every non-record hooks.state form inside managed markers", () => {
1343
+ const hooksPath = "/tmp/codex/hooks.json";
1344
+ const managedTrustState = buildManagedCodexHookTrustState(hooksPath, "/tmp/omx");
1345
+ const fixtures = [
1346
+ ["scalar", 'hooks.state = "foreign"'],
1347
+ ["array", "hooks.state = []"],
1348
+ ["quoted state key", 'hooks."state" = "foreign"'],
1349
+ ["inline non-record", 'hooks = { state = "foreign" }'],
1350
+ [
1351
+ "array of tables",
1352
+ ["[[hooks.state]]", 'trusted_hash = "sha256:foreign"'].join("\n"),
1353
+ ],
1354
+ ];
1355
+ for (const [name, value] of fixtures) {
1356
+ const fixture = [
1357
+ "# oh-my-codex (OMX) Configuration",
1358
+ value,
1359
+ "# End oh-my-codex",
1360
+ "",
1361
+ ].join("\n");
1362
+ assert.doesNotThrow(() => TOML.parse(fixture), `${name} fixture must be valid TOML`);
1363
+ assert.throws(() => stripManagedCodexHookTrustState(fixture, { managedTrustState }), (error) => error instanceof ManagedCodexHooksPlanError &&
1364
+ error.code === "managed_trust_key_conflict", name);
1365
+ }
1366
+ });
1367
+ it("fails closed before stripping malformed hooks.state spellings", () => {
1368
+ const hooksPath = "/tmp/codex/hooks.json";
1369
+ const managedTrustState = buildManagedCodexHookTrustState(hooksPath, "/tmp/omx");
1370
+ const key = `${hooksPath}:post_compact:0:0`;
1371
+ const hash = managedTrustState[key]?.trusted_hash;
1372
+ assert.ok(hash);
1373
+ const spellings = [
1374
+ ["quoted state key", 'hooks."state" ='],
1375
+ ["quoted hooks key", '"hooks".state ='],
1376
+ ["fully quoted dotted key", '"hooks"."state" ='],
1377
+ ["whitespace-delimited malformed array", 'hooks \t. "state" \t= ['],
1378
+ ["malformed inline table", 'hooks = { "state" = {'],
1379
+ ["incomplete table header", '[ "hooks" . "state"'],
1380
+ ["incomplete array table header", '[[ "hooks" . "state" ]'],
1381
+ ["incomplete hooks table header prefix", "[hooks"],
1382
+ ["incomplete hooks array table header prefix", "[[hooks"],
1383
+ ["partial hooks array-table closer", "[[hooks] # continuation"],
1384
+ ["whitespace-split hooks array-table closer", "[[hooks] ]"],
1385
+ [
1386
+ "split hooks array-table closer",
1387
+ ["[[hooks]", "]"].join("\n"),
1388
+ ],
1389
+ [
1390
+ "comment-separated hooks assignment continuation",
1391
+ [
1392
+ "hooks # continuation",
1393
+ '= { "state" = { trusted_hash = "sha256:foreign" } }',
1394
+ ].join("\n"),
1395
+ ],
1396
+ [
1397
+ "bare hooks assignment continuation",
1398
+ [
1399
+ "hooks",
1400
+ '= { "state" = { trusted_hash = "sha256:foreign" } }',
1401
+ ].join("\n"),
1402
+ ],
1403
+ [
1404
+ "comment-separated hooks dotted-key continuation",
1405
+ [
1406
+ "hooks # continuation",
1407
+ '. "state" = { trusted_hash = "sha256:foreign" }',
1408
+ ].join("\n"),
1409
+ ],
1410
+ [
1411
+ "comment-separated hooks table-header continuation",
1412
+ ["hooks # continuation", '[ "state" ]'].join("\n"),
1413
+ ],
1414
+ [
1415
+ "comment-separated hooks array-table-header continuation",
1416
+ ["hooks # continuation", '[[ "state" ]]'].join("\n"),
1417
+ ],
1418
+ [
1419
+ "split dotted key",
1420
+ ["hooks .", '"state" = { trusted_hash = "sha256:foreign" }'].join("\n"),
1421
+ ],
1422
+ [
1423
+ "unclosed multiline inline table",
1424
+ ["hooks = {", '"state" = { trusted_hash = "sha256:foreign" }'].join("\n"),
1425
+ ],
1426
+ [
1427
+ "comment-ended inline table continuation",
1428
+ [
1429
+ "hooks = { # continuation",
1430
+ '"state" = { trusted_hash = "sha256:foreign" }',
1431
+ ].join("\n"),
1432
+ ],
1433
+ [
1434
+ "bare hooks assignment with inline-table continuation",
1435
+ [
1436
+ "hooks =",
1437
+ '{ "state" = { trusted_hash = "sha256:foreign" } }',
1438
+ ].join("\n"),
1439
+ ],
1440
+ ];
1441
+ const isManagedTrustConflict = (error) => error instanceof ManagedCodexHooksPlanError &&
1442
+ error.code === "managed_trust_key_conflict";
1443
+ for (const [name, spelling] of spellings) {
1444
+ const fixture = [
1445
+ "# oh-my-codex (OMX) Configuration",
1446
+ spelling,
1447
+ "# End oh-my-codex",
1448
+ "",
1449
+ "[user.after]",
1450
+ 'value = "preserved"',
1451
+ "",
1452
+ ].join("\n");
1453
+ const original = fixture;
1454
+ assert.throws(() => buildMergedConfig(fixture, "/tmp/omx", { managedHookTrustState: managedTrustState }), isManagedTrustConflict, `${name} must fail before OMX block stripping`);
1455
+ assert.equal(fixture, original, `${name} planning must not mutate input`);
1456
+ assert.throws(() => stripExistingOmxBlocks(fixture), isManagedTrustConflict, `${name} must not be erased by direct OMX block stripping`);
1457
+ assert.equal(fixture, original, `${name} stripping must not mutate input`);
1458
+ }
1459
+ const exactQuoted = [
1460
+ "# oh-my-codex (OMX) Configuration",
1461
+ `["hooks"."state"."${key}"]`,
1462
+ `trusted_hash = "${hash}"`,
1463
+ "# End oh-my-codex",
1464
+ "",
1465
+ ].join("\n");
1466
+ assert.throws(() => stripExistingOmxBlocks(exactQuoted), isManagedTrustConflict, "direct stripping must not infer ownership without trust expectations");
1467
+ assert.deepEqual(stripExistingOmxBlocks(exactQuoted, { managedTrustState }), {
1468
+ cleaned: "",
1469
+ removed: 1,
1470
+ });
1471
+ assert.doesNotThrow(() => buildMergedConfig(exactQuoted, "/tmp/omx", { managedHookTrustState: managedTrustState }));
1472
+ });
1473
+ it("fails closed when hooks scope crosses parsed assignments or malformed headers", () => {
1474
+ const hooksPath = "/tmp/codex/hooks.json";
1475
+ const managedTrustState = buildManagedCodexHookTrustState(hooksPath, "/tmp/omx");
1476
+ const validNestedArrayAssignment = [
1477
+ "values = [",
1478
+ ' ["unrelated"]',
1479
+ "]",
1480
+ ];
1481
+ assert.doesNotThrow(() => TOML.parse(["[hooks]", ...validNestedArrayAssignment].join("\n")));
1482
+ const fixtures = [
1483
+ [
1484
+ "parsed nested assignment",
1485
+ ["[hooks]", ...validNestedArrayAssignment, "state ="].join("\n"),
1486
+ ],
1487
+ [
1488
+ "malformed bracket-prefixed line",
1489
+ ["[hooks]", "[broken", "state ="].join("\n"),
1490
+ ],
1491
+ ];
1492
+ const isManagedTrustConflict = (error) => error instanceof ManagedCodexHooksPlanError &&
1493
+ error.code === "managed_trust_key_conflict";
1494
+ for (const [name, content] of fixtures) {
1495
+ const fixture = [
1496
+ "# oh-my-codex (OMX) Configuration",
1497
+ content,
1498
+ "# End oh-my-codex",
1499
+ "",
1500
+ "[user.after]",
1501
+ 'value = "preserved"',
1502
+ "",
1503
+ ].join("\n");
1504
+ const original = fixture;
1505
+ assert.throws(() => stripExistingOmxBlocks(fixture), isManagedTrustConflict, `${name} must not be erased by direct OMX block stripping`);
1506
+ assert.equal(fixture, original, `${name} stripping must not mutate input`);
1507
+ assert.throws(() => buildMergedConfig(fixture, "/tmp/omx", { managedHookTrustState: managedTrustState }), isManagedTrustConflict, `${name} must fail before OMX block stripping`);
1508
+ assert.equal(fixture, original, `${name} planning must not mutate input`);
1509
+ }
1510
+ const validNonHooksHeaderExit = [
1511
+ "# oh-my-codex (OMX) Configuration",
1512
+ "[hooks]",
1513
+ "[unrelated]",
1514
+ "state =",
1515
+ "# End oh-my-codex",
1516
+ "",
1517
+ "[user.after]",
1518
+ 'value = "preserved"',
1519
+ "",
1520
+ ].join("\n");
1521
+ const expectedExit = ["[user.after]", 'value = "preserved"', ""].join("\n");
1522
+ assert.deepEqual(stripExistingOmxBlocks(validNonHooksHeaderExit), {
1523
+ cleaned: expectedExit,
1524
+ removed: 1,
1525
+ });
1526
+ assert.doesNotThrow(() => buildMergedConfig(validNonHooksHeaderExit, "/tmp/omx", {
1527
+ managedHookTrustState: managedTrustState,
1528
+ }));
1529
+ });
1530
+ it("fails closed for hooks array-table openers and preserves unrelated arrays", () => {
1531
+ const hooksPath = "/tmp/codex/hooks.json";
1532
+ const managedTrustState = buildManagedCodexHookTrustState(hooksPath, "/tmp/omx");
1533
+ const arrayTableOpeners = [
1534
+ ["adjacent", "[[hooks]]"],
1535
+ ["space-split", "[ [hooks]]"],
1536
+ ["tab-split", "[\t[hooks]]"],
1537
+ ["newline-split", ["[[", "hooks]]"].join("\n")],
1538
+ [
1539
+ "two standalone opening brackets followed by independently parsed state",
1540
+ [
1541
+ "[",
1542
+ "[",
1543
+ "hooks]]",
1544
+ 'state = { trusted_hash = "sha256:foreign" }',
1545
+ ].join("\n"),
1546
+ ],
1547
+ [
1548
+ "three standalone opening brackets followed by independently parsed state",
1549
+ [
1550
+ "[",
1551
+ "[",
1552
+ "[",
1553
+ "hooks]]]",
1554
+ 'state = { trusted_hash = "sha256:foreign" }',
1555
+ ].join("\n"),
1556
+ ],
1557
+ [
1558
+ "comment- and blank-separated standalone opening brackets",
1559
+ [
1560
+ "[",
1561
+ "# opening bracket continuation",
1562
+ "",
1563
+ "[",
1564
+ "",
1565
+ "# another opening bracket continuation",
1566
+ "[",
1567
+ "hooks]]]",
1568
+ 'state = { trusted_hash = "sha256:foreign" }',
1569
+ ].join("\n"),
1570
+ ],
1571
+ ["triple", "[[[hooks]]]"],
1572
+ ["extra", "[[[[hooks]]]]"],
1573
+ ["space-newline-split", ["[ [", "hooks]]"].join("\n")],
1574
+ ["tab-newline-split", ["[\t[", "hooks]]"].join("\n")],
1575
+ ["triple-newline-split", ["[[[", "hooks]]]"].join("\n")],
1576
+ ["comment-ended", ["[[ # continuation", "hooks]]"].join("\n")],
1577
+ ];
1578
+ const isManagedTrustConflict = (error) => error instanceof ManagedCodexHooksPlanError &&
1579
+ error.code === "managed_trust_key_conflict";
1580
+ for (const [name, opener] of arrayTableOpeners) {
1581
+ const fixture = [
1582
+ "# oh-my-codex (OMX) Configuration",
1583
+ opener,
1584
+ "# End oh-my-codex",
1585
+ "",
1586
+ "[user.after]",
1587
+ 'value = "preserved"',
1588
+ "",
1589
+ ].join("\n");
1590
+ const original = fixture;
1591
+ assert.throws(() => buildMergedConfig(fixture, "/tmp/omx", {
1592
+ managedHookTrustState: managedTrustState,
1593
+ }), isManagedTrustConflict, `${name} opener must fail before OMX block stripping`);
1594
+ assert.equal(fixture, original, `${name} planning must not mutate input`);
1595
+ assert.throws(() => stripExistingOmxBlocks(fixture), isManagedTrustConflict, `${name} opener must not be erased by direct OMX block stripping`);
1596
+ assert.equal(fixture, original, `${name} stripping must not mutate input`);
1597
+ }
1598
+ const unrelatedArrays = [
1599
+ "[[user.before]]",
1600
+ 'name = "before"',
1601
+ "",
1602
+ "# oh-my-codex (OMX) Configuration",
1603
+ "[[unrelated]]",
1604
+ 'name = "inside managed marker"',
1605
+ "# End oh-my-codex",
1606
+ "",
1607
+ "[[user.after]]",
1608
+ 'name = "after"',
1609
+ "",
1610
+ ].join("\n");
1611
+ const preservedArrays = [
1612
+ "[[user.before]]",
1613
+ 'name = "before"',
1614
+ "",
1615
+ "[[user.after]]",
1616
+ 'name = "after"',
1617
+ "",
1618
+ ].join("\n");
1619
+ assert.deepEqual(stripExistingOmxBlocks(unrelatedArrays), {
1620
+ cleaned: preservedArrays,
1621
+ removed: 1,
1622
+ });
1623
+ const merged = buildMergedConfig(unrelatedArrays, "/tmp/omx");
1624
+ assert.match(merged, /^\[\[user\.before\]\]$/m);
1625
+ assert.match(merged, /^\[\[user\.after\]\]$/m);
1626
+ assert.doesNotMatch(merged, /^\[\[unrelated\]\]$/m);
1627
+ assert.doesNotThrow(() => TOML.parse(merged));
1628
+ });
1629
+ it("removes valid nested hooks arrays inside exact managed marker ranges", () => {
1630
+ const fixture = [
1631
+ "# oh-my-codex (OMX) Configuration",
1632
+ "values = [",
1633
+ " [",
1634
+ "",
1635
+ " # a value named hooks is not a table prefix",
1636
+ ' "hooks",',
1637
+ " ],",
1638
+ " # nor is this independently nested array",
1639
+ " [",
1640
+ ' "unrelated",',
1641
+ " ],",
1642
+ " [",
1643
+ ' [["hooks"]]',
1644
+ " ],",
1645
+ " [",
1646
+ ' [ [ "hooks" ] ]',
1647
+ " ],",
1648
+ "]",
1649
+ "outer.values = [",
1650
+ ' [["hooks"]]',
1651
+ "]",
1652
+ '"" = [',
1653
+ ' [ [ "hooks" ] ]',
1654
+ "]",
1655
+ "# End oh-my-codex",
1656
+ "",
1657
+ "[user.after]",
1658
+ 'value = "preserved"',
1659
+ "",
1660
+ ].join("\n");
1661
+ const expected = ["[user.after]", 'value = "preserved"', ""].join("\n");
1662
+ assert.doesNotThrow(() => TOML.parse(fixture));
1663
+ assert.deepEqual(stripExistingOmxBlocks(fixture), { cleaned: expected, removed: 1 });
1664
+ const merged = buildMergedConfig(fixture, "/tmp/omx");
1665
+ assert.doesNotMatch(merged, /^values = \[/m);
1666
+ assert.match(merged, /^\[user\.after\]$/m);
1667
+ assert.doesNotThrow(() => TOML.parse(merged));
1668
+ });
1669
+ it("ignores inert hooks.state text inside exact managed marker ranges", () => {
1670
+ const fixture = [
1671
+ "# oh-my-codex (OMX) Configuration",
1672
+ '# hooks."state" =',
1673
+ 'basic = "hooks.\'state\' ="',
1674
+ "literal = 'hooks = { state = {'",
1675
+ 'multiline = """',
1676
+ '[[ "hooks" . "state" ]',
1677
+ 'hooks = { "state" = {',
1678
+ '"""',
1679
+ "# End oh-my-codex",
1680
+ "",
1681
+ "[user.after]",
1682
+ 'value = "preserved"',
1683
+ "",
1684
+ ].join("\n");
1685
+ const expected = ["[user.after]", 'value = "preserved"', ""].join("\n");
1686
+ assert.deepEqual(stripExistingOmxBlocks(fixture), { cleaned: expected, removed: 1 });
1687
+ assert.doesNotThrow(() => buildMergedConfig(fixture, "/tmp/omx"));
1688
+ });
1689
+ it("removes an exact inline state entry within a hooks.state table", () => {
1690
+ const hooksPath = "/tmp/codex/hooks.json";
1691
+ const managedTrustState = buildManagedCodexHookTrustState(hooksPath, "/tmp/omx");
1692
+ const key = `${hooksPath}:post_compact:0:0`;
1693
+ const hash = managedTrustState[key]?.trusted_hash;
1694
+ assert.ok(hash);
1695
+ const inline = [
1696
+ "[hooks.state]",
1697
+ `"${key}" = { trusted_hash = "${hash}" }`,
1698
+ "",
1699
+ "[desktop]",
1700
+ "git-create-pull-request-as-draft = true",
1701
+ "",
1702
+ ].join("\n");
1703
+ const stripped = stripManagedCodexHookTrustState(inline, { managedTrustState });
1704
+ assert.doesNotMatch(stripped, new RegExp(key.replace(/[.*+?^${}()|[\]\\]/g, "\\$&")));
1705
+ assert.match(stripped, /^\[hooks\.state\]$/m);
1706
+ assert.match(stripped, /^\[desktop\]$/m);
1707
+ assert.doesNotThrow(() => TOML.parse(stripped));
1708
+ });
1709
+ it("fails closed on multi-field, array-like, and mixed managed trust representations", () => {
1710
+ const hooksPath = "/tmp/codex/hooks.json";
1711
+ const managedTrustState = buildManagedCodexHookTrustState(hooksPath, "/tmp/omx");
1712
+ const key = `${hooksPath}:post_compact:0:0`;
1713
+ const hash = managedTrustState[key]?.trusted_hash;
1714
+ assert.ok(hash);
1715
+ const fixtures = [
1716
+ [
1717
+ `hooks.state."${key}" = { trusted_hash = "${hash}", enabled = false }`,
1718
+ "",
1719
+ ].join("\n"),
1720
+ [
1721
+ `hooks.state."${key}" = [{ trusted_hash = "${hash}" }]`,
1722
+ "",
1723
+ ].join("\n"),
1724
+ [
1725
+ `hooks.state."${key}" = { trusted_hash = "${hash}" }`,
1726
+ `[hooks.state."${key}"]`,
1727
+ `trusted_hash = "${hash}"`,
1728
+ "",
1729
+ ].join("\n"),
1730
+ ];
1731
+ for (const fixture of fixtures) {
1732
+ assert.throws(() => stripManagedCodexHookTrustState(fixture, { managedTrustState }), (error) => error instanceof ManagedCodexHooksPlanError &&
1733
+ error.code === "managed_trust_key_conflict");
1734
+ assert.throws(() => buildMergedConfig(fixture, "/tmp/omx", { managedHookTrustState: managedTrustState }), (error) => error instanceof ManagedCodexHooksPlanError &&
1735
+ error.code === "managed_trust_key_conflict");
1025
1736
  }
1026
1737
  });
1027
1738
  it("does not remove unfenced hook trust-state tables without a proof map", () => {
@@ -1051,31 +1762,138 @@ describe("config generator idempotency (#384)", () => {
1051
1762
  assert.match(repaired, /^enabled = false$/m);
1052
1763
  assertSingleManagedHookTrustState(repaired);
1053
1764
  });
1054
- it("dedupes stale unfenced managed hook trust-state tables during legacy setup refresh", () => {
1765
+ it("preserves managed-looking trust blocks inside multiline TOML values", () => {
1055
1766
  const hooksPath = "/tmp/codex/hooks.json";
1056
- const stalePluginModeConfig = [
1057
- "[features]",
1058
- "codex_hooks = true",
1059
- "goals = true",
1060
- "",
1061
- '[hooks.state."custom:/hooks.json:stop:0:0"]',
1062
- 'trusted_hash = "sha256:user"',
1063
- "enabled = false",
1064
- "",
1065
- // Regression fixture for #2225: plugin-mode state could be left outside
1066
- // the managed fence, then legacy setup appended the same table again.
1067
- '[hooks.state."/tmp/codex/hooks.json:post_compact:0:0"]',
1068
- 'trusted_hash = "sha256:stale-plugin-mode"',
1069
- "",
1767
+ const managedTrustState = buildManagedCodexHookTrustState(hooksPath, "/tmp/omx");
1768
+ const embeddedTrustBlock = [
1769
+ "# OMX-owned Codex hook trust state",
1770
+ "# Trusts only setup-managed native hook wrappers.",
1771
+ ...Object.entries(managedTrustState)
1772
+ .sort(([left], [right]) => left.localeCompare(right))
1773
+ .flatMap(([key, state]) => [
1774
+ `[hooks.state."${key}"]`,
1775
+ `trusted_hash = "${state.trusted_hash}"`,
1776
+ "",
1777
+ ]),
1778
+ "# End OMX-owned Codex hook trust state",
1070
1779
  ].join("\n");
1071
- const refreshed = buildMergedConfig(stalePluginModeConfig, "/tmp/omx", {
1780
+ const multilineBasicValue = `basic = """\n${embeddedTrustBlock}\n"""`;
1781
+ const multilineLiteralValue = `literal = '''\n${embeddedTrustBlock}\n'''`;
1782
+ const config = [multilineBasicValue, "", multilineLiteralValue, ""].join("\n");
1783
+ const first = upsertManagedCodexHookTrustState(config, "/tmp/omx", hooksPath, {
1784
+ managedTrustState,
1785
+ });
1786
+ const refreshed = upsertManagedCodexHookTrustState(first, "/tmp/omx", hooksPath, {
1787
+ managedTrustState,
1788
+ });
1789
+ const stripped = stripManagedCodexHookTrustState(refreshed, { managedTrustState });
1790
+ assert.equal(refreshed, first, "the real managed block should still be replaced idempotently");
1791
+ for (const preserved of [first, stripped]) {
1792
+ assert.ok(preserved.includes(multilineBasicValue));
1793
+ assert.ok(preserved.includes(multilineLiteralValue));
1794
+ }
1795
+ const parsed = TOML.parse(stripped);
1796
+ assert.equal(parsed.basic, `${embeddedTrustBlock}\n`);
1797
+ assert.equal(parsed.literal, `${embeddedTrustBlock}\n`);
1798
+ assert.equal(parsed.hooks, undefined, "only the real managed trust tables should be stripped");
1799
+ const unterminatedMultilineValue = multilineBasicValue.slice(0, -3);
1800
+ assert.equal(stripManagedCodexHookTrustState(unterminatedMultilineValue, { managedTrustState }), unterminatedMultilineValue, "an ambiguous unterminated multiline value must not lose managed-looking content");
1801
+ });
1802
+ it("uses strict final hook coordinates for precomputed managed trust", () => {
1803
+ const hooksPath = "/tmp/codex/hooks.json";
1804
+ const foreignGroup = {
1805
+ hooks: [
1806
+ {
1807
+ type: "command",
1808
+ command: "/usr/bin/python3 /tmp/user-hook.py",
1809
+ timeout: 5,
1810
+ },
1811
+ ],
1812
+ };
1813
+ const existingHooks = JSON.stringify({
1814
+ hooks: Object.fromEntries(MANAGED_HOOK_EVENTS.map((eventName) => [eventName, [foreignGroup]])),
1815
+ });
1816
+ const plan = planManagedCodexHooksMerge(existingHooks, "/tmp/omx", hooksPath);
1817
+ assert.equal(plan.ok, true);
1818
+ if (!plan.ok)
1819
+ return;
1820
+ const refreshed = buildMergedConfig("", "/tmp/omx", {
1821
+ codexHooksFile: hooksPath,
1822
+ managedHookTrustState: plan.finalTrustState,
1823
+ priorManagedHookTrustState: plan.priorTrustState,
1824
+ legacyHookTrustState: plan.legacyTrustState,
1825
+ });
1826
+ const parsed = TOML.parse(refreshed);
1827
+ const trustKeys = Object.keys(parsed.hooks?.state ?? {}).sort();
1828
+ assert.deepEqual(trustKeys, Object.keys(plan.finalTrustState).sort());
1829
+ for (const eventName of MANAGED_HOOK_EVENTS) {
1830
+ const eventLabel = eventName
1831
+ .replace(/([A-Z])/g, "_$1")
1832
+ .toLowerCase()
1833
+ .replace(/^_/, "");
1834
+ assert.ok(trustKeys.some((key) => key.includes(`:${eventLabel}:1:0`)), `${eventName} should retain the actual final group index after foreign hooks`);
1835
+ }
1836
+ });
1837
+ it("migrates exact legacy hooks.json state in the same precomputed config plan", () => {
1838
+ const hooksPath = "/tmp/codex/hooks.json";
1839
+ const plan = planManagedCodexHooksMerge(JSON.stringify({
1840
+ state: {
1841
+ "legacy:/hooks.json:stop:0:0": {
1842
+ trusted_hash: "sha256:legacy",
1843
+ enabled: false,
1844
+ },
1845
+ },
1846
+ hooks: {},
1847
+ }), "/tmp/omx", hooksPath);
1848
+ assert.equal(plan.ok, true);
1849
+ if (!plan.ok)
1850
+ return;
1851
+ const refreshed = buildMergedConfig("", "/tmp/omx", {
1072
1852
  codexHooksFile: hooksPath,
1853
+ managedHookTrustState: plan.finalTrustState,
1854
+ priorManagedHookTrustState: plan.priorTrustState,
1855
+ legacyHookTrustState: plan.legacyTrustState,
1073
1856
  });
1074
- assert.doesNotThrow(() => TOML.parse(refreshed));
1075
- assert.equal(count(refreshed, /^\[hooks\.state\."\/tmp\/codex\/hooks\.json:post_compact:0:0"\]$/gm), 1, "legacy refresh should replace the stale plugin-mode managed hook state");
1076
- assert.match(refreshed, /^\[hooks\.state\."custom:\/hooks\.json:stop:0:0"\]$/m, "unrelated user hook state should be preserved");
1857
+ assert.doesNotMatch(plan.finalContent ?? "", /"state"/);
1858
+ assert.match(refreshed, /^\[hooks\.state\."legacy:\/hooks\.json:stop:0:0"\]$/m);
1859
+ assert.match(refreshed, /^trusted_hash = "sha256:legacy"$/m);
1077
1860
  assert.match(refreshed, /^enabled = false$/m);
1078
- assertSingleManagedHookTrustState(refreshed);
1861
+ });
1862
+ it("migrates a legacy __proto__ trust key into config.toml", () => {
1863
+ const plan = planManagedCodexHooksMerge('{"state":{"__proto__":{"trusted_hash":"sha256:legacy","enabled":true}}}', "/tmp/omx", "/tmp/codex/hooks.json");
1864
+ assert.equal(plan.ok, true);
1865
+ if (!plan.ok)
1866
+ return;
1867
+ const config = buildMergedConfig("", "/tmp/omx", {
1868
+ legacyHookTrustState: plan.legacyTrustState,
1869
+ });
1870
+ assert.match(config, /^\[hooks\.state\."__proto__"\]$/m);
1871
+ assert.match(config, /^trusted_hash = "sha256:legacy"$/m);
1872
+ assert.match(config, /^enabled = true$/m);
1873
+ });
1874
+ it("coalesces matching legacy hook trust config tables and rejects hash or enabled collisions", () => {
1875
+ const key = "legacy:/hooks.json:stop:0:0";
1876
+ const legacyHookTrustState = {
1877
+ [key]: { trusted_hash: "sha256:legacy", enabled: false },
1878
+ };
1879
+ const matchingConfig = [
1880
+ `[hooks.state."${key}"]`,
1881
+ 'trusted_hash = "sha256:legacy"',
1882
+ "enabled = false",
1883
+ "",
1884
+ ].join("\n");
1885
+ const matching = buildMergedConfig(matchingConfig, "/tmp/omx", {
1886
+ legacyHookTrustState,
1887
+ });
1888
+ assert.equal(count(matching, /^\[hooks\.state\."legacy:\/hooks\.json:stop:0:0"\]$/gm), 1);
1889
+ for (const conflictingConfig of [
1890
+ matchingConfig.replace("sha256:legacy", "sha256:other"),
1891
+ matchingConfig.replace("enabled = false", "enabled = true"),
1892
+ matchingConfig.replace("\nenabled = false", ""),
1893
+ ]) {
1894
+ assert.throws(() => buildMergedConfig(conflictingConfig, "/tmp/omx", { legacyHookTrustState }), (error) => error instanceof ManagedCodexHooksPlanError &&
1895
+ error.code === "managed_trust_key_conflict");
1896
+ }
1079
1897
  });
1080
1898
  it("syncs shared MCP registry entries in a dedicated managed block", async () => {
1081
1899
  const wd = await mkdtemp(join(tmpdir(), "omx-idem-"));