oh-my-codex 0.20.0 → 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 (661) 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 +27 -0
  36. package/dist/cli/__tests__/codex-plugin-layout.test.js.map +1 -1
  37. package/dist/cli/__tests__/doctor-context-window-warning.test.js +83 -78
  38. package/dist/cli/__tests__/doctor-context-window-warning.test.js.map +1 -1
  39. package/dist/cli/__tests__/doctor-invalid-config.test.js +56 -4
  40. package/dist/cli/__tests__/doctor-invalid-config.test.js.map +1 -1
  41. package/dist/cli/__tests__/doctor-team.test.js.map +1 -1
  42. package/dist/cli/__tests__/doctor-warning-copy.test.js +735 -14
  43. package/dist/cli/__tests__/doctor-warning-copy.test.js.map +1 -1
  44. package/dist/cli/__tests__/exec.test.js.map +1 -1
  45. package/dist/cli/__tests__/imagegen-continuation.test.js.map +1 -1
  46. package/dist/cli/__tests__/index.test.js +216 -11
  47. package/dist/cli/__tests__/index.test.js.map +1 -1
  48. package/dist/cli/__tests__/launch-fallback.test.js +49 -3
  49. package/dist/cli/__tests__/launch-fallback.test.js.map +1 -1
  50. package/dist/cli/__tests__/mcp-parity.test.js +84 -12
  51. package/dist/cli/__tests__/mcp-parity.test.js.map +1 -1
  52. package/dist/cli/__tests__/native-hook-claim-journal.test.d.ts +2 -0
  53. package/dist/cli/__tests__/native-hook-claim-journal.test.d.ts.map +1 -0
  54. package/dist/cli/__tests__/native-hook-claim-journal.test.js +128 -0
  55. package/dist/cli/__tests__/native-hook-claim-journal.test.js.map +1 -0
  56. package/dist/cli/__tests__/nested-help-routing.test.js.map +1 -1
  57. package/dist/cli/__tests__/question.test.js.map +1 -1
  58. package/dist/cli/__tests__/ralph-deslop-contract.test.js.map +1 -1
  59. package/dist/cli/__tests__/ralph-goal-mode-contract.test.js +6 -0
  60. package/dist/cli/__tests__/ralph-goal-mode-contract.test.js.map +1 -1
  61. package/dist/cli/__tests__/ralph-prd-deep-interview.test.js.map +1 -1
  62. package/dist/cli/__tests__/ralph-prd-smoke.test.js.map +1 -1
  63. package/dist/cli/__tests__/ralph.test.js +8 -3
  64. package/dist/cli/__tests__/ralph.test.js.map +1 -1
  65. package/dist/cli/__tests__/ralplan-bootstrap-3181.test.d.ts +2 -0
  66. package/dist/cli/__tests__/ralplan-bootstrap-3181.test.d.ts.map +1 -0
  67. package/dist/cli/__tests__/ralplan-bootstrap-3181.test.js +187 -0
  68. package/dist/cli/__tests__/ralplan-bootstrap-3181.test.js.map +1 -0
  69. package/dist/cli/__tests__/ralplan.test.d.ts +2 -0
  70. package/dist/cli/__tests__/ralplan.test.d.ts.map +1 -0
  71. package/dist/cli/__tests__/ralplan.test.js +129 -0
  72. package/dist/cli/__tests__/ralplan.test.js.map +1 -0
  73. package/dist/cli/__tests__/resume.test.js +6 -1
  74. package/dist/cli/__tests__/resume.test.js.map +1 -1
  75. package/dist/cli/__tests__/session-scoped-runtime.test.js +25 -0
  76. package/dist/cli/__tests__/session-scoped-runtime.test.js.map +1 -1
  77. package/dist/cli/__tests__/session-search-help.test.js.map +1 -1
  78. package/dist/cli/__tests__/session-search.test.js.map +1 -1
  79. package/dist/cli/__tests__/setup-agents-overwrite.test.js +79 -0
  80. package/dist/cli/__tests__/setup-agents-overwrite.test.js.map +1 -1
  81. package/dist/cli/__tests__/setup-gh-star.test.js.map +1 -1
  82. package/dist/cli/__tests__/setup-hooks-shared-ownership.test.js +131 -5
  83. package/dist/cli/__tests__/setup-hooks-shared-ownership.test.js.map +1 -1
  84. package/dist/cli/__tests__/setup-hooks-trust-e2e.test.d.ts +2 -0
  85. package/dist/cli/__tests__/setup-hooks-trust-e2e.test.d.ts.map +1 -0
  86. package/dist/cli/__tests__/setup-hooks-trust-e2e.test.js +285 -0
  87. package/dist/cli/__tests__/setup-hooks-trust-e2e.test.js.map +1 -0
  88. package/dist/cli/__tests__/setup-install-mode.test.js +2264 -17
  89. package/dist/cli/__tests__/setup-install-mode.test.js.map +1 -1
  90. package/dist/cli/__tests__/setup-preferences.test.d.ts +2 -0
  91. package/dist/cli/__tests__/setup-preferences.test.d.ts.map +1 -0
  92. package/dist/cli/__tests__/setup-preferences.test.js +79 -0
  93. package/dist/cli/__tests__/setup-preferences.test.js.map +1 -0
  94. package/dist/cli/__tests__/setup-refresh.test.js +168 -5
  95. package/dist/cli/__tests__/setup-refresh.test.js.map +1 -1
  96. package/dist/cli/__tests__/setup-scope.test.js +73 -6
  97. package/dist/cli/__tests__/setup-scope.test.js.map +1 -1
  98. package/dist/cli/__tests__/sparkshell-cli.test.js.map +1 -1
  99. package/dist/cli/__tests__/state.test.js +37 -0
  100. package/dist/cli/__tests__/state.test.js.map +1 -1
  101. package/dist/cli/__tests__/team.test.js.map +1 -1
  102. package/dist/cli/__tests__/ultragoal.test.js +61 -1
  103. package/dist/cli/__tests__/ultragoal.test.js.map +1 -1
  104. package/dist/cli/__tests__/uninstall.test.js +2067 -26
  105. package/dist/cli/__tests__/uninstall.test.js.map +1 -1
  106. package/dist/cli/__tests__/update.test.js +58 -0
  107. package/dist/cli/__tests__/update.test.js.map +1 -1
  108. package/dist/cli/__tests__/url.test.js.map +1 -1
  109. package/dist/cli/__tests__/windows-popup-loop-contract.test.js.map +1 -1
  110. package/dist/cli/adapt.js.map +1 -1
  111. package/dist/cli/agents-init.js.map +1 -1
  112. package/dist/cli/agents.js.map +1 -1
  113. package/dist/cli/api.js.map +1 -1
  114. package/dist/cli/ask.d.ts +1 -1
  115. package/dist/cli/ask.d.ts.map +1 -1
  116. package/dist/cli/ask.js.map +1 -1
  117. package/dist/cli/auth.js.map +1 -1
  118. package/dist/cli/autoresearch-guided.js.map +1 -1
  119. package/dist/cli/autoresearch-intake.js.map +1 -1
  120. package/dist/cli/capabilities.js.map +1 -1
  121. package/dist/cli/cleanup.js.map +1 -1
  122. package/dist/cli/codex-feature-probe.js.map +1 -1
  123. package/dist/cli/codex-home.js.map +1 -1
  124. package/dist/cli/doctor.d.ts +20 -0
  125. package/dist/cli/doctor.d.ts.map +1 -1
  126. package/dist/cli/doctor.js +636 -161
  127. package/dist/cli/doctor.js.map +1 -1
  128. package/dist/cli/explore.js.map +1 -1
  129. package/dist/cli/index.d.ts +11 -3
  130. package/dist/cli/index.d.ts.map +1 -1
  131. package/dist/cli/index.js +108 -20
  132. package/dist/cli/index.js.map +1 -1
  133. package/dist/cli/mcp-serve.js.map +1 -1
  134. package/dist/cli/mission.js.map +1 -1
  135. package/dist/cli/native-assets.js.map +1 -1
  136. package/dist/cli/native-hook-claim-journal.d.ts +18 -0
  137. package/dist/cli/native-hook-claim-journal.d.ts.map +1 -0
  138. package/dist/cli/native-hook-claim-journal.js +239 -0
  139. package/dist/cli/native-hook-claim-journal.js.map +1 -0
  140. package/dist/cli/omx.js.map +1 -1
  141. package/dist/cli/plugin-marketplace.js.map +1 -1
  142. package/dist/cli/project-runtime-codex-homes.js.map +1 -1
  143. package/dist/cli/question.js.map +1 -1
  144. package/dist/cli/ralph.d.ts.map +1 -1
  145. package/dist/cli/ralph.js +3 -2
  146. package/dist/cli/ralph.js.map +1 -1
  147. package/dist/cli/ralplan.d.ts +14 -0
  148. package/dist/cli/ralplan.d.ts.map +1 -0
  149. package/dist/cli/ralplan.js +203 -0
  150. package/dist/cli/ralplan.js.map +1 -0
  151. package/dist/cli/setup-preferences.d.ts +11 -0
  152. package/dist/cli/setup-preferences.d.ts.map +1 -1
  153. package/dist/cli/setup-preferences.js +32 -0
  154. package/dist/cli/setup-preferences.js.map +1 -1
  155. package/dist/cli/setup.d.ts +53 -0
  156. package/dist/cli/setup.d.ts.map +1 -1
  157. package/dist/cli/setup.js +1403 -400
  158. package/dist/cli/setup.js.map +1 -1
  159. package/dist/cli/sparkshell.js.map +1 -1
  160. package/dist/cli/star-prompt.js.map +1 -1
  161. package/dist/cli/state.d.ts.map +1 -1
  162. package/dist/cli/state.js +2 -1
  163. package/dist/cli/state.js.map +1 -1
  164. package/dist/cli/team.js.map +1 -1
  165. package/dist/cli/ultragoal.d.ts +5 -1
  166. package/dist/cli/ultragoal.d.ts.map +1 -1
  167. package/dist/cli/ultragoal.js +34 -2
  168. package/dist/cli/ultragoal.js.map +1 -1
  169. package/dist/cli/uninstall.d.ts +8 -0
  170. package/dist/cli/uninstall.d.ts.map +1 -1
  171. package/dist/cli/uninstall.js +947 -99
  172. package/dist/cli/uninstall.js.map +1 -1
  173. package/dist/cli/update.d.ts.map +1 -1
  174. package/dist/cli/update.js +8 -1
  175. package/dist/cli/update.js.map +1 -1
  176. package/dist/cli/url.js.map +1 -1
  177. package/dist/compat/__tests__/doctor-contract.test.js +5 -1
  178. package/dist/compat/__tests__/doctor-contract.test.js.map +1 -1
  179. package/dist/compat/__tests__/rust-runtime-compat.test.js.map +1 -1
  180. package/dist/config/__tests__/codex-hooks.test.js +1287 -11
  181. package/dist/config/__tests__/codex-hooks.test.js.map +1 -1
  182. package/dist/config/__tests__/generator-idempotent.test.js +1049 -135
  183. package/dist/config/__tests__/generator-idempotent.test.js.map +1 -1
  184. package/dist/config/__tests__/generator-notify.test.js +28 -49
  185. package/dist/config/__tests__/generator-notify.test.js.map +1 -1
  186. package/dist/config/codex-feature-flags.d.ts.map +1 -1
  187. package/dist/config/codex-feature-flags.js.map +1 -1
  188. package/dist/config/codex-hooks.d.ts +124 -1
  189. package/dist/config/codex-hooks.d.ts.map +1 -1
  190. package/dist/config/codex-hooks.js +2142 -265
  191. package/dist/config/codex-hooks.js.map +1 -1
  192. package/dist/config/commit-lore-guard.js.map +1 -1
  193. package/dist/config/generator.d.ts +40 -26
  194. package/dist/config/generator.d.ts.map +1 -1
  195. package/dist/config/generator.js +1159 -288
  196. package/dist/config/generator.js.map +1 -1
  197. package/dist/config/mcp-registry.js.map +1 -1
  198. package/dist/config/models.d.ts +2 -2
  199. package/dist/config/models.d.ts.map +1 -1
  200. package/dist/config/models.js.map +1 -1
  201. package/dist/config/omx-first-party-mcp.js.map +1 -1
  202. package/dist/config/team-mode.d.ts.map +1 -1
  203. package/dist/document-refresh/__tests__/enforcer.test.js.map +1 -1
  204. package/dist/document-refresh/enforcer.js.map +1 -1
  205. package/dist/exec/followup.js.map +1 -1
  206. package/dist/goal-workflows/artifacts.js.map +1 -1
  207. package/dist/hooks/__tests__/agents-overlay.test.js +7 -4
  208. package/dist/hooks/__tests__/agents-overlay.test.js.map +1 -1
  209. package/dist/hooks/__tests__/analyze-routing-contract.test.js.map +1 -1
  210. package/dist/hooks/__tests__/analyze-skill-contract.test.js.map +1 -1
  211. package/dist/hooks/__tests__/anti-slop-workflow.test.js.map +1 -1
  212. package/dist/hooks/__tests__/autopilot-skill-contract.test.js +9 -3
  213. package/dist/hooks/__tests__/autopilot-skill-contract.test.js.map +1 -1
  214. package/dist/hooks/__tests__/code-review-skill-contract.test.js.map +1 -1
  215. package/dist/hooks/__tests__/consensus-execution-handoff.test.js.map +1 -1
  216. package/dist/hooks/__tests__/deep-interview-contract.test.js.map +1 -1
  217. package/dist/hooks/__tests__/design-skill.test.js.map +1 -1
  218. package/dist/hooks/__tests__/keyword-detector.test.js +1372 -2
  219. package/dist/hooks/__tests__/keyword-detector.test.js.map +1 -1
  220. package/dist/hooks/__tests__/notify-fallback-watcher.test.js +14 -0
  221. package/dist/hooks/__tests__/notify-fallback-watcher.test.js.map +1 -1
  222. package/dist/hooks/__tests__/notify-hook-all-workers-idle.test.js.map +1 -1
  223. package/dist/hooks/__tests__/notify-hook-auto-nudge.test.js +669 -1
  224. package/dist/hooks/__tests__/notify-hook-auto-nudge.test.js.map +1 -1
  225. package/dist/hooks/__tests__/notify-hook-cross-worktree-heartbeat.test.js +2 -0
  226. package/dist/hooks/__tests__/notify-hook-cross-worktree-heartbeat.test.js.map +1 -1
  227. package/dist/hooks/__tests__/notify-hook-managed-tmux.test.js +142 -1
  228. package/dist/hooks/__tests__/notify-hook-managed-tmux.test.js.map +1 -1
  229. package/dist/hooks/__tests__/notify-hook-modules.test.js.map +1 -1
  230. package/dist/hooks/__tests__/notify-hook-non-omx-guard.test.js +48 -2
  231. package/dist/hooks/__tests__/notify-hook-non-omx-guard.test.js.map +1 -1
  232. package/dist/hooks/__tests__/notify-hook-ralph-resume.test.js +61 -44
  233. package/dist/hooks/__tests__/notify-hook-ralph-resume.test.js.map +1 -1
  234. package/dist/hooks/__tests__/notify-hook-regression-205.test.js.map +1 -1
  235. package/dist/hooks/__tests__/notify-hook-session-idle-dedupe.test.js +3 -2
  236. package/dist/hooks/__tests__/notify-hook-session-idle-dedupe.test.js.map +1 -1
  237. package/dist/hooks/__tests__/notify-hook-session-scope.test.js +243 -4
  238. package/dist/hooks/__tests__/notify-hook-session-scope.test.js.map +1 -1
  239. package/dist/hooks/__tests__/notify-hook-team-dispatch.test.js.map +1 -1
  240. package/dist/hooks/__tests__/notify-hook-team-leader-nudge.test.js +8 -1
  241. package/dist/hooks/__tests__/notify-hook-team-leader-nudge.test.js.map +1 -1
  242. package/dist/hooks/__tests__/notify-hook-team-tmux-guard.test.js.map +1 -1
  243. package/dist/hooks/__tests__/notify-hook-tmux-heal.test.js.map +1 -1
  244. package/dist/hooks/__tests__/notify-hook-tmux-scrollback.test.js.map +1 -1
  245. package/dist/hooks/__tests__/notify-hook-visual-verdict.test.js.map +1 -1
  246. package/dist/hooks/__tests__/notify-hook-worker-idle.test.js.map +1 -1
  247. package/dist/hooks/__tests__/openclaw-setup-contract.test.js.map +1 -1
  248. package/dist/hooks/__tests__/pre-context-gate-skills.test.js.map +1 -1
  249. package/dist/hooks/__tests__/prometheus-strict-contract.test.js.map +1 -1
  250. package/dist/hooks/__tests__/prompt-guidance-fragments.test.js.map +1 -1
  251. package/dist/hooks/__tests__/prompt-guidance-test-helpers.js.map +1 -1
  252. package/dist/hooks/__tests__/prompt-session-provenance.test.d.ts +2 -0
  253. package/dist/hooks/__tests__/prompt-session-provenance.test.d.ts.map +1 -0
  254. package/dist/hooks/__tests__/prompt-session-provenance.test.js +82 -0
  255. package/dist/hooks/__tests__/prompt-session-provenance.test.js.map +1 -0
  256. package/dist/hooks/__tests__/session.test.js +532 -11
  257. package/dist/hooks/__tests__/session.test.js.map +1 -1
  258. package/dist/hooks/__tests__/skill-catalog-hygiene.test.js.map +1 -1
  259. package/dist/hooks/__tests__/team-runtime-gating-docs-contract.test.js.map +1 -1
  260. package/dist/hooks/__tests__/visual-ralph-skill.test.js.map +1 -1
  261. package/dist/hooks/__tests__/visual-verdict-loop.test.js.map +1 -1
  262. package/dist/hooks/agents-overlay.js +5 -5
  263. package/dist/hooks/agents-overlay.js.map +1 -1
  264. package/dist/hooks/code-simplifier/index.js.map +1 -1
  265. package/dist/hooks/explore-routing.js.map +1 -1
  266. package/dist/hooks/extensibility/dispatcher.js.map +1 -1
  267. package/dist/hooks/extensibility/events.js.map +1 -1
  268. package/dist/hooks/extensibility/loader.js.map +1 -1
  269. package/dist/hooks/extensibility/plugin-runner-stdin.js.map +1 -1
  270. package/dist/hooks/extensibility/sdk/logging.js.map +1 -1
  271. package/dist/hooks/keyword-detector.d.ts +33 -7
  272. package/dist/hooks/keyword-detector.d.ts.map +1 -1
  273. package/dist/hooks/keyword-detector.js +2228 -119
  274. package/dist/hooks/keyword-detector.js.map +1 -1
  275. package/dist/hooks/keyword-registry.d.ts +12 -0
  276. package/dist/hooks/keyword-registry.d.ts.map +1 -1
  277. package/dist/hooks/keyword-registry.js +41 -0
  278. package/dist/hooks/keyword-registry.js.map +1 -1
  279. package/dist/hooks/prompt-session-provenance.d.ts +87 -0
  280. package/dist/hooks/prompt-session-provenance.d.ts.map +1 -0
  281. package/dist/hooks/prompt-session-provenance.js +180 -0
  282. package/dist/hooks/prompt-session-provenance.js.map +1 -0
  283. package/dist/hooks/session.d.ts +139 -34
  284. package/dist/hooks/session.d.ts.map +1 -1
  285. package/dist/hooks/session.js +1239 -317
  286. package/dist/hooks/session.js.map +1 -1
  287. package/dist/hooks/task-size-detector.js.map +1 -1
  288. package/dist/hud/__tests__/state.test.js.map +1 -1
  289. package/dist/hud/authority.js.map +1 -1
  290. package/dist/hud/constants.js.map +1 -1
  291. package/dist/hud/index.js.map +1 -1
  292. package/dist/hud/reconcile.js.map +1 -1
  293. package/dist/hud/render.js.map +1 -1
  294. package/dist/hud/state.js.map +1 -1
  295. package/dist/hud/tmux.js.map +1 -1
  296. package/dist/leader/__tests__/contract.test.js +146 -2
  297. package/dist/leader/__tests__/contract.test.js.map +1 -1
  298. package/dist/leader/contract.d.ts +26 -5
  299. package/dist/leader/contract.d.ts.map +1 -1
  300. package/dist/leader/contract.js +131 -7
  301. package/dist/leader/contract.js.map +1 -1
  302. package/dist/mcp/__tests__/bootstrap.test.js.map +1 -1
  303. package/dist/mcp/__tests__/server-lifecycle.test.js +15 -2
  304. package/dist/mcp/__tests__/server-lifecycle.test.js.map +1 -1
  305. package/dist/mcp/__tests__/state-paths.test.js +98 -1
  306. package/dist/mcp/__tests__/state-paths.test.js.map +1 -1
  307. package/dist/mcp/bootstrap.js.map +1 -1
  308. package/dist/mcp/code-intel-server.d.ts +58 -58
  309. package/dist/mcp/code-intel-server.d.ts.map +1 -1
  310. package/dist/mcp/code-intel-server.js.map +1 -1
  311. package/dist/mcp/hermes-bridge.js.map +1 -1
  312. package/dist/mcp/hermes-server.d.ts +70 -70
  313. package/dist/mcp/hermes-server.d.ts.map +1 -1
  314. package/dist/mcp/lifecycle-telemetry.js.map +1 -1
  315. package/dist/mcp/memory-server.d.ts +34 -34
  316. package/dist/mcp/memory-server.d.ts.map +1 -1
  317. package/dist/mcp/memory-server.js.map +1 -1
  318. package/dist/mcp/state-paths.d.ts +14 -0
  319. package/dist/mcp/state-paths.d.ts.map +1 -1
  320. package/dist/mcp/state-paths.js +96 -20
  321. package/dist/mcp/state-paths.js.map +1 -1
  322. package/dist/mcp/state-server.d.ts +47 -47
  323. package/dist/mcp/state-server.d.ts.map +1 -1
  324. package/dist/mcp/trace-server.d.ts +4 -4
  325. package/dist/mcp/trace-server.d.ts.map +1 -1
  326. package/dist/mcp/trace-server.js.map +1 -1
  327. package/dist/mcp/wiki-server.d.ts +23 -23
  328. package/dist/mcp/wiki-server.d.ts.map +1 -1
  329. package/dist/mcp/wiki-server.js.map +1 -1
  330. package/dist/modes/__tests__/base-session-scope.test.js +53 -1
  331. package/dist/modes/__tests__/base-session-scope.test.js.map +1 -1
  332. package/dist/modes/base.d.ts.map +1 -1
  333. package/dist/modes/base.js +5 -5
  334. package/dist/modes/base.js.map +1 -1
  335. package/dist/notifications/__tests__/lifecycle-dedupe.test.js.map +1 -1
  336. package/dist/notifications/__tests__/reply-listener.test.js.map +1 -1
  337. package/dist/notifications/__tests__/session-idle-tail-dedupe.test.js +177 -85
  338. package/dist/notifications/__tests__/session-idle-tail-dedupe.test.js.map +1 -1
  339. package/dist/notifications/__tests__/session-registry.test.js.map +1 -1
  340. package/dist/notifications/__tests__/template-engine.test.js.map +1 -1
  341. package/dist/notifications/__tests__/verbosity.test.js.map +1 -1
  342. package/dist/notifications/http-client.js.map +1 -1
  343. package/dist/notifications/index.d.ts +4 -1
  344. package/dist/notifications/index.d.ts.map +1 -1
  345. package/dist/notifications/index.js +3 -9
  346. package/dist/notifications/index.js.map +1 -1
  347. package/dist/notifications/lifecycle-dedupe.js.map +1 -1
  348. package/dist/notifications/reply-listener.d.ts.map +1 -1
  349. package/dist/notifications/reply-listener.js.map +1 -1
  350. package/dist/notifications/session-registry.js.map +1 -1
  351. package/dist/notifications/session-status.js.map +1 -1
  352. package/dist/notifications/temp-contract.js.map +1 -1
  353. package/dist/notifications/tmux-detector.js.map +1 -1
  354. package/dist/notifications/tmux.js.map +1 -1
  355. package/dist/openclaw/config.js.map +1 -1
  356. package/dist/openclaw/dispatcher.js.map +1 -1
  357. package/dist/pipeline/__tests__/orchestrator.test.js.map +1 -1
  358. package/dist/pipeline/__tests__/stages.test.js +8 -5
  359. package/dist/pipeline/__tests__/stages.test.js.map +1 -1
  360. package/dist/pipeline/stages/code-review.js.map +1 -1
  361. package/dist/pipeline/stages/ralph-verify.js.map +1 -1
  362. package/dist/pipeline/stages/ralplan.js.map +1 -1
  363. package/dist/pipeline/stages/team-exec.js.map +1 -1
  364. package/dist/pipeline/stages/ultragoal.js.map +1 -1
  365. package/dist/pipeline/stages/ultraqa.js.map +1 -1
  366. package/dist/planning/__tests__/approved-execution-lifecycle-matrix.test.js.map +1 -1
  367. package/dist/planning/artifact-names.js.map +1 -1
  368. package/dist/planning/artifacts.js.map +1 -1
  369. package/dist/question/__tests__/state.test.js.map +1 -1
  370. package/dist/question/__tests__/ui.test.js.map +1 -1
  371. package/dist/question/autopilot-wait.js.map +1 -1
  372. package/dist/question/client.d.ts.map +1 -1
  373. package/dist/question/client.js.map +1 -1
  374. package/dist/question/deep-interview.js.map +1 -1
  375. package/dist/question/events.js.map +1 -1
  376. package/dist/question/renderer.js.map +1 -1
  377. package/dist/question/state.d.ts.map +1 -1
  378. package/dist/question/state.js.map +1 -1
  379. package/dist/question/ui.js.map +1 -1
  380. package/dist/ralph/contract.d.ts +1 -1
  381. package/dist/ralph/contract.d.ts.map +1 -1
  382. package/dist/ralplan/__tests__/consensus-gate.test.js +403 -8
  383. package/dist/ralplan/__tests__/consensus-gate.test.js.map +1 -1
  384. package/dist/ralplan/__tests__/runtime.test.js +112 -14
  385. package/dist/ralplan/__tests__/runtime.test.js.map +1 -1
  386. package/dist/ralplan/consensus-gate.d.ts +3 -3
  387. package/dist/ralplan/consensus-gate.d.ts.map +1 -1
  388. package/dist/ralplan/consensus-gate.js +155 -60
  389. package/dist/ralplan/consensus-gate.js.map +1 -1
  390. package/dist/ralplan/runtime.d.ts +2 -2
  391. package/dist/ralplan/runtime.d.ts.map +1 -1
  392. package/dist/ralplan/runtime.js +1 -2
  393. package/dist/ralplan/runtime.js.map +1 -1
  394. package/dist/runtime/bridge.d.ts.map +1 -1
  395. package/dist/runtime/bridge.js.map +1 -1
  396. package/dist/runtime/process-tree.js.map +1 -1
  397. package/dist/runtime/run-loop.js.map +1 -1
  398. package/dist/runtime/run-outcome.d.ts +3 -3
  399. package/dist/runtime/run-outcome.d.ts.map +1 -1
  400. package/dist/runtime/run-outcome.js.map +1 -1
  401. package/dist/runtime/run-state.js.map +1 -1
  402. package/dist/scripts/__tests__/codex-native-hook.test.js +5414 -801
  403. package/dist/scripts/__tests__/codex-native-hook.test.js.map +1 -1
  404. package/dist/scripts/__tests__/fixtures/issue-3138-installed-smoke.d.ts +2 -0
  405. package/dist/scripts/__tests__/fixtures/issue-3138-installed-smoke.d.ts.map +1 -0
  406. package/dist/scripts/__tests__/fixtures/issue-3138-installed-smoke.js +634 -0
  407. package/dist/scripts/__tests__/fixtures/issue-3138-installed-smoke.js.map +1 -0
  408. package/dist/scripts/__tests__/generate-release-body.test.js.map +1 -1
  409. package/dist/scripts/__tests__/hook-derived-watcher.test.js.map +1 -1
  410. package/dist/scripts/__tests__/notify-dispatcher.test.js.map +1 -1
  411. package/dist/scripts/__tests__/notify-fallback-delivery.test.d.ts +2 -0
  412. package/dist/scripts/__tests__/notify-fallback-delivery.test.d.ts.map +1 -0
  413. package/dist/scripts/__tests__/notify-fallback-delivery.test.js +174 -0
  414. package/dist/scripts/__tests__/notify-fallback-delivery.test.js.map +1 -0
  415. package/dist/scripts/__tests__/notify-state-io.test.js +57 -1
  416. package/dist/scripts/__tests__/notify-state-io.test.js.map +1 -1
  417. package/dist/scripts/__tests__/role-intent-bootstrap-e2e-3181.test.d.ts +2 -0
  418. package/dist/scripts/__tests__/role-intent-bootstrap-e2e-3181.test.d.ts.map +1 -0
  419. package/dist/scripts/__tests__/role-intent-bootstrap-e2e-3181.test.js +205 -0
  420. package/dist/scripts/__tests__/role-intent-bootstrap-e2e-3181.test.js.map +1 -0
  421. package/dist/scripts/__tests__/role-intent-durable-recovery-3181.test.d.ts +2 -0
  422. package/dist/scripts/__tests__/role-intent-durable-recovery-3181.test.d.ts.map +1 -0
  423. package/dist/scripts/__tests__/role-intent-durable-recovery-3181.test.js +175 -0
  424. package/dist/scripts/__tests__/role-intent-durable-recovery-3181.test.js.map +1 -0
  425. package/dist/scripts/__tests__/run-test-files.test.js.map +1 -1
  426. package/dist/scripts/__tests__/smoke-packed-install.test.js +848 -4
  427. package/dist/scripts/__tests__/smoke-packed-install.test.js.map +1 -1
  428. package/dist/scripts/__tests__/test-reply-listener-live.test.js.map +1 -1
  429. package/dist/scripts/build-api.js.map +1 -1
  430. package/dist/scripts/build-explore-harness.js.map +1 -1
  431. package/dist/scripts/build-sparkshell.js.map +1 -1
  432. package/dist/scripts/cleanup-explore-harness.js.map +1 -1
  433. package/dist/scripts/codex-execution-surface.js.map +1 -1
  434. package/dist/scripts/codex-native-hook.d.ts.map +1 -1
  435. package/dist/scripts/codex-native-hook.js +825 -172
  436. package/dist/scripts/codex-native-hook.js.map +1 -1
  437. package/dist/scripts/generate-native-release-manifest.js.map +1 -1
  438. package/dist/scripts/generate-release-body.js.map +1 -1
  439. package/dist/scripts/hook-payload-guard.d.ts.map +1 -1
  440. package/dist/scripts/hook-payload-guard.js.map +1 -1
  441. package/dist/scripts/notify-fallback-delivery.d.ts +48 -0
  442. package/dist/scripts/notify-fallback-delivery.d.ts.map +1 -0
  443. package/dist/scripts/notify-fallback-delivery.js +419 -0
  444. package/dist/scripts/notify-fallback-delivery.js.map +1 -0
  445. package/dist/scripts/notify-fallback-watcher.js +136 -30
  446. package/dist/scripts/notify-fallback-watcher.js.map +1 -1
  447. package/dist/scripts/notify-hook/auto-nudge.d.ts +18 -9
  448. package/dist/scripts/notify-hook/auto-nudge.d.ts.map +1 -1
  449. package/dist/scripts/notify-hook/auto-nudge.js +39 -13
  450. package/dist/scripts/notify-hook/auto-nudge.js.map +1 -1
  451. package/dist/scripts/notify-hook/managed-tmux.d.ts +23 -0
  452. package/dist/scripts/notify-hook/managed-tmux.d.ts.map +1 -1
  453. package/dist/scripts/notify-hook/managed-tmux.js +122 -12
  454. package/dist/scripts/notify-hook/managed-tmux.js.map +1 -1
  455. package/dist/scripts/notify-hook/orchestration-intent.d.ts +7 -7
  456. package/dist/scripts/notify-hook/orchestration-intent.d.ts.map +1 -1
  457. package/dist/scripts/notify-hook/ralph-session-resume.d.ts +5 -2
  458. package/dist/scripts/notify-hook/ralph-session-resume.d.ts.map +1 -1
  459. package/dist/scripts/notify-hook/ralph-session-resume.js +33 -37
  460. package/dist/scripts/notify-hook/ralph-session-resume.js.map +1 -1
  461. package/dist/scripts/notify-hook/state-io.d.ts +17 -0
  462. package/dist/scripts/notify-hook/state-io.d.ts.map +1 -1
  463. package/dist/scripts/notify-hook/state-io.js +73 -11
  464. package/dist/scripts/notify-hook/state-io.js.map +1 -1
  465. package/dist/scripts/notify-hook/team-dispatch.d.ts +1 -1
  466. package/dist/scripts/notify-hook/team-dispatch.js.map +1 -1
  467. package/dist/scripts/notify-hook/team-leader-nudge.d.ts +2 -2
  468. package/dist/scripts/notify-hook/team-tmux-guard.d.ts +4 -4
  469. package/dist/scripts/notify-hook/team-worker-posttooluse.js.map +1 -1
  470. package/dist/scripts/notify-hook/team-worker-stop.d.ts +1 -1
  471. package/dist/scripts/notify-hook/team-worker.d.ts +2 -2
  472. package/dist/scripts/notify-hook/tmux-injection.d.ts +8 -1
  473. package/dist/scripts/notify-hook/tmux-injection.d.ts.map +1 -1
  474. package/dist/scripts/notify-hook/tmux-injection.js +25 -16
  475. package/dist/scripts/notify-hook/tmux-injection.js.map +1 -1
  476. package/dist/scripts/notify-hook/visual-verdict.d.ts +1 -1
  477. package/dist/scripts/notify-hook/visual-verdict.d.ts.map +1 -1
  478. package/dist/scripts/notify-hook/visual-verdict.js +15 -13
  479. package/dist/scripts/notify-hook/visual-verdict.js.map +1 -1
  480. package/dist/scripts/notify-hook.d.ts +24 -1
  481. package/dist/scripts/notify-hook.d.ts.map +1 -1
  482. package/dist/scripts/notify-hook.js +266 -90
  483. package/dist/scripts/notify-hook.js.map +1 -1
  484. package/dist/scripts/postinstall.js.map +1 -1
  485. package/dist/scripts/prompt-inventory.js.map +1 -1
  486. package/dist/scripts/smoke-packed-install.d.ts +879 -2
  487. package/dist/scripts/smoke-packed-install.d.ts.map +1 -1
  488. package/dist/scripts/smoke-packed-install.js +1528 -7
  489. package/dist/scripts/smoke-packed-install.js.map +1 -1
  490. package/dist/scripts/sync-plugin-mirror.js.map +1 -1
  491. package/dist/scripts/test-reply-listener-live.js.map +1 -1
  492. package/dist/scripts/test-sparkshell.js.map +1 -1
  493. package/dist/scripts/verify-native-agents.js.map +1 -1
  494. package/dist/session-history/friction.js.map +1 -1
  495. package/dist/session-history/search.js.map +1 -1
  496. package/dist/sidecar/__tests__/boundary.test.js.map +1 -1
  497. package/dist/sidecar/collector.js.map +1 -1
  498. package/dist/sidecar/index.js.map +1 -1
  499. package/dist/sidecar/render.js.map +1 -1
  500. package/dist/sidecar/tmux.js.map +1 -1
  501. package/dist/state/__tests__/operations.test.js +64 -15
  502. package/dist/state/__tests__/operations.test.js.map +1 -1
  503. package/dist/state/__tests__/skill-active.test.js +19 -1
  504. package/dist/state/__tests__/skill-active.test.js.map +1 -1
  505. package/dist/state/__tests__/workflow-transition.test.js +61 -0
  506. package/dist/state/__tests__/workflow-transition.test.js.map +1 -1
  507. package/dist/state/mode-state-context.js.map +1 -1
  508. package/dist/state/operations.d.ts +1 -1
  509. package/dist/state/operations.d.ts.map +1 -1
  510. package/dist/state/operations.js +49 -55
  511. package/dist/state/operations.js.map +1 -1
  512. package/dist/state/paths.d.ts +1 -1
  513. package/dist/state/paths.d.ts.map +1 -1
  514. package/dist/state/paths.js +1 -1
  515. package/dist/state/paths.js.map +1 -1
  516. package/dist/state/skill-active.d.ts +13 -1
  517. package/dist/state/skill-active.d.ts.map +1 -1
  518. package/dist/state/skill-active.js +43 -4
  519. package/dist/state/skill-active.js.map +1 -1
  520. package/dist/state/terminal-normalization.js.map +1 -1
  521. package/dist/state/workflow-transition-reconcile.d.ts.map +1 -1
  522. package/dist/state/workflow-transition-reconcile.js +20 -4
  523. package/dist/state/workflow-transition-reconcile.js.map +1 -1
  524. package/dist/state/workflow-transition.d.ts +1 -1
  525. package/dist/state/workflow-transition.d.ts.map +1 -1
  526. package/dist/state/workflow-transition.js.map +1 -1
  527. package/dist/subagents/__tests__/adapted-role-binding.test.d.ts +2 -0
  528. package/dist/subagents/__tests__/adapted-role-binding.test.d.ts.map +1 -0
  529. package/dist/subagents/__tests__/adapted-role-binding.test.js +988 -0
  530. package/dist/subagents/__tests__/adapted-role-binding.test.js.map +1 -0
  531. package/dist/subagents/__tests__/leader-bootstrap-3181.test.d.ts +2 -0
  532. package/dist/subagents/__tests__/leader-bootstrap-3181.test.d.ts.map +1 -0
  533. package/dist/subagents/__tests__/leader-bootstrap-3181.test.js +273 -0
  534. package/dist/subagents/__tests__/leader-bootstrap-3181.test.js.map +1 -0
  535. package/dist/subagents/__tests__/tracker.test.js +1550 -10
  536. package/dist/subagents/__tests__/tracker.test.js.map +1 -1
  537. package/dist/subagents/adapted-role-binding.d.ts +17 -0
  538. package/dist/subagents/adapted-role-binding.d.ts.map +1 -0
  539. package/dist/subagents/adapted-role-binding.js +75 -0
  540. package/dist/subagents/adapted-role-binding.js.map +1 -0
  541. package/dist/subagents/role-routing-marker.d.ts +10 -0
  542. package/dist/subagents/role-routing-marker.d.ts.map +1 -0
  543. package/dist/subagents/role-routing-marker.js +136 -0
  544. package/dist/subagents/role-routing-marker.js.map +1 -0
  545. package/dist/subagents/tracker.d.ts +172 -0
  546. package/dist/subagents/tracker.d.ts.map +1 -1
  547. package/dist/subagents/tracker.js +1077 -61
  548. package/dist/subagents/tracker.js.map +1 -1
  549. package/dist/team/__tests__/approved-execution.test.js.map +1 -1
  550. package/dist/team/__tests__/delivery-e2e-smoke.test.js.map +1 -1
  551. package/dist/team/__tests__/model-contract.test.js +231 -1
  552. package/dist/team/__tests__/model-contract.test.js.map +1 -1
  553. package/dist/team/__tests__/role-router.test.js.map +1 -1
  554. package/dist/team/__tests__/runtime.test.js +401 -1
  555. package/dist/team/__tests__/runtime.test.js.map +1 -1
  556. package/dist/team/__tests__/scaling.test.js +181 -0
  557. package/dist/team/__tests__/scaling.test.js.map +1 -1
  558. package/dist/team/__tests__/state-root.test.js.map +1 -1
  559. package/dist/team/__tests__/tmux-session.test.js +434 -4
  560. package/dist/team/__tests__/tmux-session.test.js.map +1 -1
  561. package/dist/team/__tests__/tmux-test-fixture.js.map +1 -1
  562. package/dist/team/api-interop.d.ts +2 -2
  563. package/dist/team/api-interop.d.ts.map +1 -1
  564. package/dist/team/api-interop.js.map +1 -1
  565. package/dist/team/commit-hygiene.js.map +1 -1
  566. package/dist/team/contracts.d.ts +5 -5
  567. package/dist/team/contracts.d.ts.map +1 -1
  568. package/dist/team/delivery-log.js.map +1 -1
  569. package/dist/team/followup-planner.js.map +1 -1
  570. package/dist/team/goal-workflow.js.map +1 -1
  571. package/dist/team/idle-nudge.d.ts.map +1 -1
  572. package/dist/team/model-contract.d.ts +20 -1
  573. package/dist/team/model-contract.d.ts.map +1 -1
  574. package/dist/team/model-contract.js +267 -30
  575. package/dist/team/model-contract.js.map +1 -1
  576. package/dist/team/orchestrator.js.map +1 -1
  577. package/dist/team/pane-status.js.map +1 -1
  578. package/dist/team/phase-controller.js.map +1 -1
  579. package/dist/team/reminder-intents.d.ts +1 -1
  580. package/dist/team/reminder-intents.d.ts.map +1 -1
  581. package/dist/team/reminder-intents.js.map +1 -1
  582. package/dist/team/runtime-cli.js.map +1 -1
  583. package/dist/team/runtime.d.ts.map +1 -1
  584. package/dist/team/runtime.js +83 -52
  585. package/dist/team/runtime.js.map +1 -1
  586. package/dist/team/scaling.d.ts +9 -7
  587. package/dist/team/scaling.d.ts.map +1 -1
  588. package/dist/team/scaling.js +69 -28
  589. package/dist/team/scaling.js.map +1 -1
  590. package/dist/team/state/dispatch-lock.js.map +1 -1
  591. package/dist/team/state/dispatch.js.map +1 -1
  592. package/dist/team/state/events.js.map +1 -1
  593. package/dist/team/state/tasks.js.map +1 -1
  594. package/dist/team/state-root.js.map +1 -1
  595. package/dist/team/state.js.map +1 -1
  596. package/dist/team/team-identity.d.ts.map +1 -1
  597. package/dist/team/team-identity.js.map +1 -1
  598. package/dist/team/tmux-session.d.ts +2 -0
  599. package/dist/team/tmux-session.d.ts.map +1 -1
  600. package/dist/team/tmux-session.js +90 -42
  601. package/dist/team/tmux-session.js.map +1 -1
  602. package/dist/team/worker-bootstrap.js.map +1 -1
  603. package/dist/team/worktree.js.map +1 -1
  604. package/dist/ultragoal/__tests__/artifacts.test.js.map +1 -1
  605. package/dist/ultragoal/__tests__/docs-contract.test.js.map +1 -1
  606. package/dist/ultragoal/artifacts.d.ts.map +1 -1
  607. package/dist/ultragoal/artifacts.js +4 -1
  608. package/dist/ultragoal/artifacts.js.map +1 -1
  609. package/dist/url-reader/index.js.map +1 -1
  610. package/dist/utils/__tests__/agents-md.test.js +16 -0
  611. package/dist/utils/__tests__/agents-md.test.js.map +1 -1
  612. package/dist/utils/agents-md.d.ts.map +1 -1
  613. package/dist/utils/agents-md.js +4 -3
  614. package/dist/utils/agents-md.js.map +1 -1
  615. package/dist/utils/agents-model-table.js.map +1 -1
  616. package/dist/utils/package.js.map +1 -1
  617. package/dist/utils/paths.js.map +1 -1
  618. package/dist/utils/platform-command.js.map +1 -1
  619. package/dist/utils/repo-deps.js.map +1 -1
  620. package/dist/utils/version.js.map +1 -1
  621. package/dist/verification/__tests__/ci-rust-gates.test.js +1 -1
  622. package/dist/verification/__tests__/dev-merge-issue-close-workflow.test.js.map +1 -1
  623. package/dist/verification/verifier.js.map +1 -1
  624. package/dist/visual/constants.d.ts +1 -1
  625. package/dist/visual/constants.d.ts.map +1 -1
  626. package/dist/vscode/__tests__/index.test.js.map +1 -1
  627. package/dist/vscode/index.js.map +1 -1
  628. package/dist/wiki/__tests__/cjk-tokenize.test.js.map +1 -1
  629. package/dist/wiki/__tests__/lint.test.js.map +1 -1
  630. package/dist/wiki/__tests__/query.test.js.map +1 -1
  631. package/dist/wiki/__tests__/storage.test.js.map +1 -1
  632. package/dist/wiki/__tests__/test-helpers.d.ts.map +1 -1
  633. package/dist/wiki/lint.js.map +1 -1
  634. package/dist/wiki/query.js.map +1 -1
  635. package/dist/wiki/storage.js.map +1 -1
  636. package/package.json +2 -2
  637. package/plugins/oh-my-codex/.codex-plugin/plugin.json +1 -1
  638. package/plugins/oh-my-codex/skills/omx-setup/SKILL.md +10 -5
  639. package/plugins/oh-my-codex/skills/ralph/SKILL.md +4 -2
  640. package/plugins/oh-my-codex/skills/ralplan/SKILL.md +5 -2
  641. package/skills/omx-setup/SKILL.md +10 -5
  642. package/skills/ralph/SKILL.md +4 -2
  643. package/skills/ralplan/SKILL.md +5 -2
  644. package/src/scripts/__tests__/codex-native-hook.test.ts +20497 -12377
  645. package/src/scripts/__tests__/fixtures/issue-3138-installed-smoke.ts +734 -0
  646. package/src/scripts/__tests__/notify-fallback-delivery.test.ts +177 -0
  647. package/src/scripts/__tests__/notify-state-io.test.ts +73 -0
  648. package/src/scripts/__tests__/role-intent-bootstrap-e2e-3181.test.ts +215 -0
  649. package/src/scripts/__tests__/role-intent-durable-recovery-3181.test.ts +193 -0
  650. package/src/scripts/__tests__/smoke-packed-install.test.ts +991 -4
  651. package/src/scripts/codex-native-hook.ts +918 -172
  652. package/src/scripts/notify-fallback-delivery.ts +416 -0
  653. package/src/scripts/notify-fallback-watcher.ts +132 -40
  654. package/src/scripts/notify-hook/auto-nudge.ts +43 -12
  655. package/src/scripts/notify-hook/managed-tmux.ts +149 -12
  656. package/src/scripts/notify-hook/ralph-session-resume.ts +37 -36
  657. package/src/scripts/notify-hook/state-io.ts +100 -13
  658. package/src/scripts/notify-hook/tmux-injection.ts +38 -21
  659. package/src/scripts/notify-hook/visual-verdict.ts +22 -12
  660. package/src/scripts/notify-hook.ts +335 -90
  661. 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 { buildMergedConfig, cleanCodexModelAvailabilityNuxIfNeeded, mergeConfig, repairConfigIfNeeded, stripManagedCodexHookTrustState, upsertManagedCodexHookTrustState, } 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");
@@ -133,15 +155,75 @@ describe("config generator idempotency (#384)", () => {
133
155
  await mergeConfig(configPath, wd);
134
156
  const toml = await readFile(configPath, "utf-8");
135
157
  assertSingleOmxBlock(toml);
136
- assert.match(toml, /^multi_agent = true$/m);
158
+ assert.doesNotMatch(toml, /^multi_agent\s*=/m);
137
159
  assert.match(toml, /^child_agents_md = true$/m);
138
160
  assert.match(toml, /^hooks = true$/m);
139
161
  assert.match(toml, /^goals = true$/m);
162
+ assert.doesNotMatch(toml, /^\[agents\]$/m);
163
+ assert.doesNotMatch(toml, /^max_threads\s*=/m);
164
+ assert.doesNotMatch(toml, /^max_depth\s*=/m);
140
165
  }
141
166
  finally {
142
167
  await rm(wd, { recursive: true, force: true });
143
168
  }
144
169
  });
170
+ it("classifies legacy multi-agent keys without mutating config", () => {
171
+ const absent = analyzeLegacyMultiAgentConfig("");
172
+ assert.equal(absent.assessments["features.multi_agent"].state, "absent");
173
+ const legacy = analyzeLegacyMultiAgentConfig([
174
+ "[features]",
175
+ "multi_agent = true",
176
+ "",
177
+ "[agents]",
178
+ "max_threads = 6",
179
+ "max_depth = 2",
180
+ ].join("\n"));
181
+ assert.equal(legacy.assessments["features.multi_agent"].state, "retained-legacy");
182
+ assert.equal(legacy.assessments["agents.max_threads"].state, "retained-legacy");
183
+ assert.equal(legacy.assessments["agents.max_depth"].state, "retained-legacy");
184
+ const custom = analyzeLegacyMultiAgentConfig("[features]\nmulti_agent = false\n\n[agents]\nmax_threads = 8\nmax_depth = 3\n");
185
+ assert.equal(custom.assessments["features.multi_agent"].state, "custom");
186
+ assert.equal(custom.assessments["agents.max_threads"].state, "custom");
187
+ assert.equal(custom.assessments["agents.max_depth"].state, "custom");
188
+ const invalid = analyzeLegacyMultiAgentConfig("[features\nmulti_agent = true");
189
+ assert.equal(invalid.assessments["features.multi_agent"].state, "invalid/duplicate");
190
+ const duplicate = analyzeLegacyMultiAgentConfig("[features]\nmulti_agent = true\nmulti_agent = false\n");
191
+ assert.equal(duplicate.assessments["features.multi_agent"].state, "invalid/duplicate");
192
+ assert.equal(duplicate.assessments["features.multi_agent"].reasonCode, "toml-duplicate-key");
193
+ });
194
+ it("preserves legacy multi-agent values and all role tables across fixed-point merges", () => {
195
+ const existing = [
196
+ "[features]",
197
+ "multi_agent = false",
198
+ "",
199
+ "[agents]",
200
+ "max_threads = 8",
201
+ "max_depth = 3",
202
+ "",
203
+ "[agents.executor]",
204
+ 'config_file = "/custom/executor.toml"',
205
+ "",
206
+ '[agents."review bot"]',
207
+ 'config_file = "/custom/review.toml"',
208
+ "",
209
+ ].join("\n");
210
+ const merged = buildMergedConfig(existing, "/tmp/omx");
211
+ assert.match(merged, /^multi_agent = false$/m);
212
+ assert.match(merged, /^max_threads = 8$/m);
213
+ assert.match(merged, /^max_depth = 3$/m);
214
+ assert.match(merged, /^\[agents\.executor\]$/m);
215
+ assert.match(merged, /^\[agents\."review bot"\]$/m);
216
+ assert.match(merged, /^config_file = "\/custom\/executor\.toml"$/m);
217
+ assert.match(merged, /^config_file = "\/custom\/review\.toml"$/m);
218
+ const parsed = TOML.parse(merged);
219
+ assert.equal(parsed.agents?.executor?.config_file, "/custom/executor.toml");
220
+ assert.equal(parsed.agents?.["review bot"]?.config_file, "/custom/review.toml");
221
+ assert.equal(buildMergedConfig(merged, "/tmp/omx"), merged);
222
+ });
223
+ it("can preserve multi_agent while stripping other OMX feature flags", () => {
224
+ const stripped = stripOmxFeatureFlags("[features]\nmulti_agent = false\nchild_agents_md = true\nhooks = true\ngoals = true\n", { preserveMultiAgent: true });
225
+ assert.equal(stripped, "[features]\nmulti_agent = false\n");
226
+ });
145
227
  it("emits first-party MCP blocks only when explicitly enabled", () => {
146
228
  const toml = buildMergedConfig("", "/tmp/omx", {
147
229
  includeFirstPartyMcp: true,
@@ -354,11 +436,10 @@ describe("config generator idempotency (#384)", () => {
354
436
  assertSingleOmxBlock(toml);
355
437
  assert.match(toml, /^\[user\.custom\]$/m, "user section preserved");
356
438
  assert.match(toml, /^name = "kept"$/m, "user key preserved");
357
- assert.match(toml, /^\[agents\]$/m, "global agents settings added");
358
- assert.match(toml, /^max_threads = 6$/m, "global agents max_threads seeded");
359
- assert.match(toml, /^max_depth = 2$/m, "global agents max_depth seeded");
360
- assert.doesNotMatch(toml, /^\[agents\.executor\]$/m, "legacy OMX agent entry removed");
361
- assert.doesNotMatch(toml, /^\[agents\.explore\]$/m, "legacy OMX agent entry removed");
439
+ assert.match(toml, /^\[agents\.executor\]$/m, "known agent table preserved");
440
+ assert.match(toml, /^\[agents\.explore\]$/m, "known agent table preserved");
441
+ assert.match(toml, /^config_file = "\/old\/path\/executor\.toml"$/m);
442
+ assert.match(toml, /^config_file = "\/old\/path\/explore\.toml"$/m);
362
443
  }
363
444
  finally {
364
445
  await rm(wd, { recursive: true, force: true });
@@ -518,128 +599,165 @@ describe("config generator idempotency (#384)", () => {
518
599
  await rm(wd, { recursive: true, force: true });
519
600
  }
520
601
  });
521
- it("seeds context keys when root model is missing and both context keys are absent", async () => {
602
+ it("does not seed context defaults and preserves explicit context settings", async () => {
522
603
  const wd = await mkdtemp(join(tmpdir(), "omx-idem-"));
523
604
  try {
524
605
  const configPath = join(wd, "config.toml");
525
- await writeFile(configPath, 'approval_policy = "on-failure"\n');
606
+ await writeFile(configPath, ['model = "gpt-5.6-sol"', "model_context_window = 640000", ""].join("\n"));
526
607
  await mergeConfig(configPath, wd);
527
608
  const toml = await readFile(configPath, "utf-8");
528
- assert.match(toml, /^model = "gpt-5.6-sol"$/m);
529
- assert.match(toml, /^# oh-my-codex seeded behavioral defaults \(uninstall removes unchanged defaults\)$/m);
530
- assert.match(toml, /^model_context_window = 250000$/m);
531
- assert.match(toml, /^model_auto_compact_token_limit = 200000$/m);
532
- assert.match(toml, /^# End oh-my-codex seeded behavioral defaults$/m);
609
+ assert.match(toml, /^model_context_window = 640000$/m);
610
+ assert.doesNotMatch(toml, /^model_auto_compact_token_limit\s*=/m);
611
+ assert.doesNotMatch(toml, /seeded behavioral defaults/);
612
+ await mergeConfig(configPath, wd);
613
+ const repeated = await readFile(configPath, "utf-8");
614
+ assert.equal(repeated, toml);
615
+ assert.doesNotMatch(repeated, /^model_auto_compact_token_limit\s*=/m);
616
+ assert.doesNotMatch(repeated, /seeded behavioral defaults/);
533
617
  }
534
618
  finally {
535
619
  await rm(wd, { recursive: true, force: true });
536
620
  }
537
621
  });
538
- it("can override gpt-5.6-terra to gpt-5.6-sol and seed 250k context defaults", async () => {
539
- const wd = await mkdtemp(join(tmpdir(), "omx-idem-"));
540
- try {
541
- const toml = buildMergedConfig('model = \"gpt-5.6-terra\"\n', wd, {
542
- modelOverride: "gpt-5.6-sol",
543
- });
544
- assert.match(toml, /^model = "gpt-5\.6-sol"$/m);
545
- assert.doesNotMatch(toml, /^model = "gpt-5\.6-terra"$/m);
546
- assert.match(toml, /^# oh-my-codex seeded behavioral defaults \(uninstall removes unchanged defaults\)$/m);
547
- assert.match(toml, /^model_context_window = 250000$/m);
548
- assert.match(toml, /^model_auto_compact_token_limit = 200000$/m);
549
- assert.match(toml, /^# End oh-my-codex seeded behavioral defaults$/m);
550
- }
551
- finally {
552
- await rm(wd, { recursive: true, force: true });
622
+ it("removes exact legacy source spans with LF, CRLF, and EOF variants", () => {
623
+ const start = "# oh-my-codex seeded behavioral defaults (uninstall removes unchanged defaults)";
624
+ const end = "# End oh-my-codex seeded behavioral defaults";
625
+ const fixtures = [
626
+ {
627
+ input: `before\n${start}\nmodel_context_window = 250000\nmodel_auto_compact_token_limit = 200000\n${end}\nafter`,
628
+ expected: "before\nafter",
629
+ },
630
+ {
631
+ input: `model_auto_compact_token_limit=123\r\n${start}\r\nmodel_context_window = 250000\r\n${end}\r\n[features]\r\nweb_search = true`,
632
+ expected: "model_auto_compact_token_limit=123\r\n[features]\r\nweb_search = true",
633
+ },
634
+ {
635
+ input: `model_context_window = [\n 1,\n]\n${start}\nmodel_auto_compact_token_limit = 200000\n${end}`,
636
+ expected: "model_context_window = [\n 1,\n]\n",
637
+ },
638
+ ];
639
+ for (const { input, expected } of fixtures) {
640
+ const stripped = stripOmxSeededBehavioralDefaults(input);
641
+ assert.equal(stripped, expected);
642
+ assert.equal(stripOmxSeededBehavioralDefaults(stripped), stripped);
553
643
  }
554
644
  });
555
- it("does not seed 250k context defaults for non-gpt-5.6-sol models", async () => {
556
- const wd = await mkdtemp(join(tmpdir(), "omx-idem-"));
557
- try {
558
- const configPath = join(wd, "config.toml");
559
- await writeFile(configPath, 'model = "o3"\n');
560
- await mergeConfig(configPath, wd);
561
- const toml = await readFile(configPath, "utf-8");
562
- assert.match(toml, /^model = "o3"$/m, "user model preserved");
563
- assert.doesNotMatch(toml, /^model_context_window = 250000$/m);
564
- assert.doesNotMatch(toml, /^model_auto_compact_token_limit = 200000$/m);
565
- }
566
- finally {
567
- await rm(wd, { recursive: true, force: true });
568
- }
645
+ it("strips bounded customized or siblingless singleton markers and preserves ambiguity", () => {
646
+ const start = "# oh-my-codex seeded behavioral defaults (uninstall removes unchanged defaults)";
647
+ const end = "# End oh-my-codex seeded behavioral defaults";
648
+ const customized = `before\r\n${start}\r\nmodel_context_window = 123\r\n${end}\r\nafter`;
649
+ assert.equal(stripOmxSeededBehavioralDefaults(customized), "before\r\nmodel_context_window = 123\r\nafter");
650
+ const siblingless = `${start}\nmodel_context_window = 250000\n${end}\n[features]\nmodel_auto_compact_token_limit = 999`;
651
+ assert.equal(stripOmxSeededBehavioralDefaults(siblingless), "model_context_window = 250000\n[features]\nmodel_auto_compact_token_limit = 999");
652
+ const ambiguous = `${start}\nmodel_context_window = 250000\n${end}\nmodel_auto_compact_token_limit = 1\nmodel_auto_compact_token_limit = 2`;
653
+ assert.equal(stripOmxSeededBehavioralDefaults(ambiguous), ambiguous);
654
+ const sameKeyOutside = `model_context_window = 1\n${start}\nmodel_context_window = 250000\n${end}\nmodel_auto_compact_token_limit = 2`;
655
+ assert.equal(stripOmxSeededBehavioralDefaults(sameKeyOutside), sameKeyOutside);
656
+ const malformed = `${start}\nmodel_context_window = 250000`;
657
+ assert.equal(stripOmxSeededBehavioralDefaults(malformed), malformed);
569
658
  });
570
- it("seeds missing auto compact limit without overwriting an existing context window", async () => {
571
- const wd = await mkdtemp(join(tmpdir(), "omx-idem-"));
572
- try {
573
- const configPath = join(wd, "config.toml");
574
- await writeFile(configPath, ['model = "gpt-5.6-sol"', "model_context_window = 640000", ""].join("\n"));
575
- await mergeConfig(configPath, wd);
576
- const toml = await readFile(configPath, "utf-8");
577
- assert.match(toml, /^model = "gpt-5\.6-sol"$/m);
578
- assert.match(toml, /^model_context_window = 640000$/m);
579
- assert.match(toml, /^model_auto_compact_token_limit = 200000$/m);
580
- }
581
- finally {
582
- await rm(wd, { recursive: true, force: true });
659
+ it("preserves every byte inside noncanonical bounded blocks", () => {
660
+ const start = "# oh-my-codex seeded behavioral defaults (uninstall removes unchanged defaults)";
661
+ const end = "# End oh-my-codex seeded behavioral defaults";
662
+ const bodies = [
663
+ ["model_context_window = 250000", "# user comment", "model_auto_compact_token_limit = 200000"],
664
+ ["model_context_window = 250000", "", "model_auto_compact_token_limit = 200000"],
665
+ [" model_context_window = 250000", "model_auto_compact_token_limit = 200000"],
666
+ ["model_context_window = 250000", "model_auto_compact_token_limit = 200000 "],
667
+ ["model_auto_compact_token_limit = 200000", "model_context_window = 250000"],
668
+ ["model_context_window = 250000", "model_context_window = 250000", "model_auto_compact_token_limit = 200000"],
669
+ ["model_context_window = 250000", "unexpected = true", "model_auto_compact_token_limit = 200000"],
670
+ ["model_context_window = 250_000", "model_auto_compact_token_limit = 200000"],
671
+ ["", "model_context_window = 250000", "model_auto_compact_token_limit = 200000", ""],
672
+ ];
673
+ for (const body of bodies) {
674
+ const input = ["before", start, ...body, end, "after"].join("\n");
675
+ const expected = ["before", ...body, "after"].join("\n");
676
+ const stripped = stripOmxSeededBehavioralDefaults(input);
677
+ assert.equal(stripped, expected);
678
+ assert.equal(stripOmxSeededBehavioralDefaults(stripped), stripped);
583
679
  }
584
680
  });
585
- it("seeds missing context window without overwriting an existing auto compact limit", async () => {
586
- const wd = await mkdtemp(join(tmpdir(), "omx-idem-"));
587
- try {
588
- const configPath = join(wd, "config.toml");
589
- await writeFile(configPath, ['model = "gpt-5.6-sol"', "model_auto_compact_token_limit = 150000", ""].join("\n"));
590
- await mergeConfig(configPath, wd);
591
- const toml = await readFile(configPath, "utf-8");
592
- assert.match(toml, /^model = "gpt-5\.6-sol"$/m);
593
- assert.match(toml, /^model_context_window = 250000$/m);
594
- assert.match(toml, /^model_auto_compact_token_limit = 150000$/m);
595
- }
596
- finally {
597
- await rm(wd, { recursive: true, force: true });
681
+ it("fails closed for duplicate assignments and malformed marker topologies", () => {
682
+ const start = "# oh-my-codex seeded behavioral defaults (uninstall removes unchanged defaults)";
683
+ const end = "# End oh-my-codex seeded behavioral defaults";
684
+ const pair = [start, "model_context_window = 250000", "model_auto_compact_token_limit = 200000", end];
685
+ const unchanged = [
686
+ ["model_context_window = 999", ...pair, "after"].join("\n"),
687
+ [...pair, "model_auto_compact_token_limit = 999", "after"].join("\n"),
688
+ [start, start, "model_context_window = 250000", "model_auto_compact_token_limit = 200000", end, end].join("\n"),
689
+ [...pair, ...pair].join("\n"),
690
+ ];
691
+ for (const input of unchanged) {
692
+ assert.equal(stripOmxSeededBehavioralDefaults(input), input);
693
+ assert.equal(hasExactOmxSeededBehavioralDefaultsPair(input), false);
598
694
  }
695
+ const invalidSuffix = ["before", ...pair, "invalid = [", ""].join("\n");
696
+ const expected = "before\ninvalid = [\n";
697
+ assert.equal(stripOmxSeededBehavioralDefaults(invalidSuffix), expected);
698
+ assert.equal(stripOmxSeededBehavioralDefaults(expected), expected);
599
699
  });
600
- it("does not duplicate independently seeded defaults across reruns", async () => {
601
- const wd = await mkdtemp(join(tmpdir(), "omx-idem-"));
602
- try {
603
- const configPath = join(wd, "config.toml");
604
- await writeFile(configPath, ['model = "gpt-5.6-sol"', "model_context_window = 640000", ""].join("\n"));
605
- await mergeConfig(configPath, wd);
606
- await mergeConfig(configPath, wd);
607
- const toml = await readFile(configPath, "utf-8");
608
- assert.equal(count(toml, /^model_context_window = 640000$/gm), 1);
609
- assert.equal(count(toml, /^model_auto_compact_token_limit = 200000$/gm), 1);
610
- assert.doesNotMatch(toml, /^model_context_window = 250000$/m);
611
- }
612
- finally {
613
- await rm(wd, { recursive: true, force: true });
614
- }
700
+ it("ignores marker-shaped text inside TOML values and fails closed on stray markers", () => {
701
+ const start = "# oh-my-codex seeded behavioral defaults (uninstall removes unchanged defaults)";
702
+ const end = "# End oh-my-codex seeded behavioral defaults";
703
+ const stringContent = [
704
+ 'developer_instructions = """',
705
+ start,
706
+ "model_context_window = 250000",
707
+ "model_auto_compact_token_limit = 200000",
708
+ end,
709
+ '"""',
710
+ "",
711
+ ].join("\n");
712
+ assert.equal(stripOmxSeededBehavioralDefaults(stringContent), stringContent);
713
+ assert.equal(hasExactOmxSeededBehavioralDefaultsPair(stringContent), false);
714
+ const duplicateAfterTable = [
715
+ start,
716
+ "model_context_window = 250000",
717
+ "model_auto_compact_token_limit = 200000",
718
+ end,
719
+ "[tui]",
720
+ start,
721
+ "",
722
+ ].join("\n");
723
+ assert.equal(stripOmxSeededBehavioralDefaults(duplicateAfterTable), duplicateAfterTable);
724
+ assert.equal(hasExactOmxSeededBehavioralDefaultsPair(duplicateAfterTable), false);
615
725
  });
616
- it("does not duplicate seeded model defaults across reruns", async () => {
617
- const wd = await mkdtemp(join(tmpdir(), "omx-idem-"));
618
- try {
619
- const configPath = join(wd, "config.toml");
620
- await mergeConfig(configPath, wd);
621
- await mergeConfig(configPath, wd);
622
- const toml = await readFile(configPath, "utf-8");
623
- assert.equal(count(toml, /^model = "gpt-5\.6-sol"$/gm), 1, "seeded model should appear once");
624
- assert.equal(count(toml, /^model_context_window = 250000$/gm), 1, "seeded context window should appear once");
625
- assert.equal(count(toml, /^model_auto_compact_token_limit = 200000$/gm), 1, "seeded auto compact limit should appear once");
626
- assert.equal(count(toml, /^# oh-my-codex seeded behavioral defaults \(uninstall removes unchanged defaults\)$/gm), 1, "seeded defaults start marker should appear once");
627
- assert.equal(count(toml, /^# End oh-my-codex seeded behavioral defaults$/gm), 1, "seeded defaults end marker should appear once");
628
- }
629
- finally {
630
- await rm(wd, { recursive: true, force: true });
726
+ it("migrates before reconstruction without incremental normalization", () => {
727
+ const start = "# oh-my-codex seeded behavioral defaults (uninstall removes unchanged defaults)";
728
+ const end = "# End oh-my-codex seeded behavioral defaults";
729
+ const fixtures = [
730
+ {
731
+ baseline: 'approval_policy = "on-failure"\n[features]\nweb_search = true\n',
732
+ migrated: `approval_policy = "on-failure"\n${start}\nmodel_context_window = 250000\nmodel_auto_compact_token_limit = 200000\n${end}\n[features]\nweb_search = true\n`,
733
+ },
734
+ {
735
+ baseline: "model_auto_compact_token_limit = [\n 999,\n]\n[features]\nweb_search = true\n",
736
+ migrated: `model_auto_compact_token_limit = [\n 999,\n]\n${start}\nmodel_context_window = 250000\n${end}\n[features]\nweb_search = true\n`,
737
+ },
738
+ {
739
+ baseline: "# explicit sibling\nmodel_context_window = 640000\n[features]\nweb_search = true\n",
740
+ migrated: `# explicit sibling\nmodel_context_window = 640000\n${start}\nmodel_auto_compact_token_limit = 200000\n${end}\n[features]\nweb_search = true\n`,
741
+ },
742
+ ];
743
+ for (const fixture of fixtures) {
744
+ const baseline = buildMergedConfig(fixture.baseline, "/tmp/omx");
745
+ const migrated = buildMergedConfig(fixture.migrated, "/tmp/omx");
746
+ assert.equal(migrated, baseline);
747
+ assert.equal(buildMergedConfig(migrated, "/tmp/omx"), migrated);
748
+ assert.doesNotMatch(migrated, /seeded behavioral defaults/);
749
+ assert.doesNotThrow(() => TOML.parse(migrated));
631
750
  }
632
751
  });
633
- it("writes only the global [agents] defaults into config", async () => {
752
+ it("does not write retired global [agents] defaults", async () => {
634
753
  const wd = await mkdtemp(join(tmpdir(), "omx-idem-"));
635
754
  try {
636
755
  const configPath = join(wd, "config.toml");
637
756
  await mergeConfig(configPath, wd);
638
757
  const toml = await readFile(configPath, "utf-8");
639
- assert.match(toml, /^\[agents\]$/m, "global [agents] section present");
640
- assert.match(toml, /^max_threads = 6$/m, "max_threads default written");
641
- assert.match(toml, /^max_depth = 2$/m, "max_depth default written");
642
- assert.doesNotMatch(toml, /^\[agents\.[^\]]+\]$/m, "legacy per-agent config_file entries omitted");
758
+ assert.doesNotMatch(toml, /^\[agents\]$/m);
759
+ assert.doesNotMatch(toml, /^max_threads\s*=/m);
760
+ assert.doesNotMatch(toml, /^max_depth\s*=/m);
643
761
  }
644
762
  finally {
645
763
  await rm(wd, { recursive: true, force: true });
@@ -692,6 +810,106 @@ describe("config generator idempotency (#384)", () => {
692
810
  await rm(wd, { recursive: true, force: true });
693
811
  }
694
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
+ });
695
913
  it("mergeConfig removes legacy omx_team_run tables during setup upgrade", async () => {
696
914
  const wd = await mkdtemp(join(tmpdir(), "omx-idem-"));
697
915
  try {
@@ -791,6 +1009,74 @@ describe("config generator idempotency (#384)", () => {
791
1009
  await rm(wd, { recursive: true, force: true });
792
1010
  }
793
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
+ });
794
1080
  it("preserves trailing user config after an unmatched managed hook trust-state start marker", () => {
795
1081
  const malformed = [
796
1082
  'model = "gpt-5.6-sol"',
@@ -856,7 +1142,7 @@ describe("config generator idempotency (#384)", () => {
856
1142
  assert.match(stripped, /^\[desktop\]$/m);
857
1143
  assert.doesNotThrow(() => TOML.parse(stripped));
858
1144
  });
859
- it("preserves same-key user hook trust state and suppresses generated duplicates", () => {
1145
+ it("fails closed on conflicting same-key hook trust state", () => {
860
1146
  const hooksPath = "/tmp/codex/hooks.json";
861
1147
  const config = [
862
1148
  'model = "gpt-5.6-sol"',
@@ -866,13 +1152,10 @@ describe("config generator idempotency (#384)", () => {
866
1152
  "enabled = false",
867
1153
  "",
868
1154
  ].join("\n");
869
- const refreshed = upsertManagedCodexHookTrustState(config, "/tmp/omx", hooksPath);
870
- assert.match(refreshed, /^trusted_hash = "sha256:user"$/m);
871
- assert.match(refreshed, /^enabled = false$/m);
872
- 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");
873
- assert.doesNotThrow(() => TOML.parse(refreshed));
1155
+ assert.throws(() => upsertManagedCodexHookTrustState(config, "/tmp/omx", hooksPath), (error) => error instanceof ManagedCodexHooksPlanError &&
1156
+ error.code === "managed_trust_key_conflict");
874
1157
  });
875
- it("preserves unproven same-key hook trust-state tables", () => {
1158
+ it("fails closed on unproven same-key hook trust-state tables", () => {
876
1159
  const hooksPath = "/tmp/codex/hooks.json";
877
1160
  const managedTrustState = buildManagedCodexHookTrustState(hooksPath, "/tmp/omx");
878
1161
  const fixtures = [
@@ -922,10 +1205,534 @@ describe("config generator idempotency (#384)", () => {
922
1205
  ],
923
1206
  ];
924
1207
  for (const [name, table] of fixtures) {
925
- const stripped = stripManagedCodexHookTrustState(table.join("\n"), {
1208
+ assert.throws(() => stripManagedCodexHookTrustState(table.join("\n"), {
926
1209
  managedTrustState,
927
- });
928
- 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");
929
1736
  }
930
1737
  });
931
1738
  it("does not remove unfenced hook trust-state tables without a proof map", () => {
@@ -955,31 +1762,138 @@ describe("config generator idempotency (#384)", () => {
955
1762
  assert.match(repaired, /^enabled = false$/m);
956
1763
  assertSingleManagedHookTrustState(repaired);
957
1764
  });
958
- it("dedupes stale unfenced managed hook trust-state tables during legacy setup refresh", () => {
1765
+ it("preserves managed-looking trust blocks inside multiline TOML values", () => {
959
1766
  const hooksPath = "/tmp/codex/hooks.json";
960
- const stalePluginModeConfig = [
961
- "[features]",
962
- "codex_hooks = true",
963
- "goals = true",
964
- "",
965
- '[hooks.state."custom:/hooks.json:stop:0:0"]',
966
- 'trusted_hash = "sha256:user"',
967
- "enabled = false",
968
- "",
969
- // Regression fixture for #2225: plugin-mode state could be left outside
970
- // the managed fence, then legacy setup appended the same table again.
971
- '[hooks.state."/tmp/codex/hooks.json:post_compact:0:0"]',
972
- 'trusted_hash = "sha256:stale-plugin-mode"',
973
- "",
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",
974
1779
  ].join("\n");
975
- 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", {
976
1821
  codexHooksFile: hooksPath,
1822
+ managedHookTrustState: plan.finalTrustState,
1823
+ priorManagedHookTrustState: plan.priorTrustState,
1824
+ legacyHookTrustState: plan.legacyTrustState,
977
1825
  });
978
- assert.doesNotThrow(() => TOML.parse(refreshed));
979
- 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");
980
- assert.match(refreshed, /^\[hooks\.state\."custom:\/hooks\.json:stop:0:0"\]$/m, "unrelated user hook state should be preserved");
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", {
1852
+ codexHooksFile: hooksPath,
1853
+ managedHookTrustState: plan.finalTrustState,
1854
+ priorManagedHookTrustState: plan.priorTrustState,
1855
+ legacyHookTrustState: plan.legacyTrustState,
1856
+ });
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);
981
1860
  assert.match(refreshed, /^enabled = false$/m);
982
- 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
+ }
983
1897
  });
984
1898
  it("syncs shared MCP registry entries in a dedicated managed block", async () => {
985
1899
  const wd = await mkdtemp(join(tmpdir(), "omx-idem-"));