oh-my-codex 0.20.1 → 0.20.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (791) hide show
  1. package/Cargo.lock +7 -6
  2. package/Cargo.toml +1 -1
  3. package/README.md +36 -1
  4. package/crates/omx-runtime/Cargo.toml +1 -0
  5. package/crates/omx-runtime/src/main.rs +25 -2
  6. package/crates/omx-runtime/tests/execution.rs +219 -0
  7. package/crates/omx-runtime-core/src/dispatch.rs +96 -10
  8. package/crates/omx-runtime-core/src/engine.rs +839 -29
  9. package/crates/omx-runtime-core/src/lib.rs +8 -0
  10. package/dist/adapt/__tests__/hermes.test.js.map +1 -1
  11. package/dist/adapt/contracts.d.ts.map +1 -1
  12. package/dist/agents/__tests__/definitions.test.js +81 -0
  13. package/dist/agents/__tests__/definitions.test.js.map +1 -1
  14. package/dist/agents/__tests__/native-config.test.js +25 -0
  15. package/dist/agents/__tests__/native-config.test.js.map +1 -1
  16. package/dist/agents/native-config.d.ts +2 -1
  17. package/dist/agents/native-config.d.ts.map +1 -1
  18. package/dist/agents/native-config.js.map +1 -1
  19. package/dist/auth/__tests__/hotswap.test.d.ts +2 -0
  20. package/dist/auth/__tests__/hotswap.test.d.ts.map +1 -0
  21. package/dist/auth/__tests__/hotswap.test.js +183 -0
  22. package/dist/auth/__tests__/hotswap.test.js.map +1 -0
  23. package/dist/auth/hotswap.d.ts +6 -0
  24. package/dist/auth/hotswap.d.ts.map +1 -1
  25. package/dist/auth/hotswap.js +102 -19
  26. package/dist/auth/hotswap.js.map +1 -1
  27. package/dist/auth/paths.js.map +1 -1
  28. package/dist/auth/storage.js.map +1 -1
  29. package/dist/autopilot/__tests__/ralplan-gate.test.js +5 -5
  30. package/dist/autopilot/__tests__/ralplan-gate.test.js.map +1 -1
  31. package/dist/autopilot/completion-gate.js.map +1 -1
  32. package/dist/autopilot/fsm.d.ts +1 -1
  33. package/dist/autopilot/fsm.d.ts.map +1 -1
  34. package/dist/autoresearch/goal.js.map +1 -1
  35. package/dist/autoresearch/runtime.js.map +1 -1
  36. package/dist/capabilities/lockfile.js.map +1 -1
  37. package/dist/catalog/reader.js.map +1 -1
  38. package/dist/catalog/skill-mirror.js.map +1 -1
  39. package/dist/cli/__tests__/adapt-help.test.js.map +1 -1
  40. package/dist/cli/__tests__/agents-init.test.js.map +1 -1
  41. package/dist/cli/__tests__/agents.test.js.map +1 -1
  42. package/dist/cli/__tests__/api.test.js.map +1 -1
  43. package/dist/cli/__tests__/ask.test.js.map +1 -1
  44. package/dist/cli/__tests__/auth.test.js +60 -6
  45. package/dist/cli/__tests__/auth.test.js.map +1 -1
  46. package/dist/cli/__tests__/autoresearch-guided.test.js.map +1 -1
  47. package/dist/cli/__tests__/autoresearch.test.js.map +1 -1
  48. package/dist/cli/__tests__/capabilities.test.js.map +1 -1
  49. package/dist/cli/__tests__/codex-plugin-layout.test.js +80 -0
  50. package/dist/cli/__tests__/codex-plugin-layout.test.js.map +1 -1
  51. package/dist/cli/__tests__/doctor-context-window-warning.test.js.map +1 -1
  52. package/dist/cli/__tests__/doctor-invalid-config.test.js +47 -4
  53. package/dist/cli/__tests__/doctor-invalid-config.test.js.map +1 -1
  54. package/dist/cli/__tests__/doctor-team.test.js.map +1 -1
  55. package/dist/cli/__tests__/doctor-warning-copy.test.js +752 -15
  56. package/dist/cli/__tests__/doctor-warning-copy.test.js.map +1 -1
  57. package/dist/cli/__tests__/exec.test.js +136 -0
  58. package/dist/cli/__tests__/exec.test.js.map +1 -1
  59. package/dist/cli/__tests__/imagegen-continuation.test.js.map +1 -1
  60. package/dist/cli/__tests__/index.test.js +473 -14
  61. package/dist/cli/__tests__/index.test.js.map +1 -1
  62. package/dist/cli/__tests__/launch-fallback.test.js +336 -4
  63. package/dist/cli/__tests__/launch-fallback.test.js.map +1 -1
  64. package/dist/cli/__tests__/mcp-parity.test.js +87 -12
  65. package/dist/cli/__tests__/mcp-parity.test.js.map +1 -1
  66. package/dist/cli/__tests__/native-hook-claim-journal.test.d.ts +2 -0
  67. package/dist/cli/__tests__/native-hook-claim-journal.test.d.ts.map +1 -0
  68. package/dist/cli/__tests__/native-hook-claim-journal.test.js +206 -0
  69. package/dist/cli/__tests__/native-hook-claim-journal.test.js.map +1 -0
  70. package/dist/cli/__tests__/nested-help-routing.test.js.map +1 -1
  71. package/dist/cli/__tests__/question.test.js.map +1 -1
  72. package/dist/cli/__tests__/ralph-deslop-contract.test.js.map +1 -1
  73. package/dist/cli/__tests__/ralph-goal-mode-contract.test.js +6 -0
  74. package/dist/cli/__tests__/ralph-goal-mode-contract.test.js.map +1 -1
  75. package/dist/cli/__tests__/ralph-prd-deep-interview.test.js.map +1 -1
  76. package/dist/cli/__tests__/ralph-prd-smoke.test.js.map +1 -1
  77. package/dist/cli/__tests__/ralph.test.js +8 -3
  78. package/dist/cli/__tests__/ralph.test.js.map +1 -1
  79. package/dist/cli/__tests__/ralplan-bootstrap-3181.test.d.ts +2 -0
  80. package/dist/cli/__tests__/ralplan-bootstrap-3181.test.d.ts.map +1 -0
  81. package/dist/cli/__tests__/ralplan-bootstrap-3181.test.js +72 -0
  82. package/dist/cli/__tests__/ralplan-bootstrap-3181.test.js.map +1 -0
  83. package/dist/cli/__tests__/ralplan.test.d.ts +2 -0
  84. package/dist/cli/__tests__/ralplan.test.d.ts.map +1 -0
  85. package/dist/cli/__tests__/ralplan.test.js +55 -0
  86. package/dist/cli/__tests__/ralplan.test.js.map +1 -0
  87. package/dist/cli/__tests__/resume.test.js +17 -11
  88. package/dist/cli/__tests__/resume.test.js.map +1 -1
  89. package/dist/cli/__tests__/session-scoped-runtime.test.js +25 -0
  90. package/dist/cli/__tests__/session-scoped-runtime.test.js.map +1 -1
  91. package/dist/cli/__tests__/session-search-help.test.js +25 -1
  92. package/dist/cli/__tests__/session-search-help.test.js.map +1 -1
  93. package/dist/cli/__tests__/session-search.test.js.map +1 -1
  94. package/dist/cli/__tests__/setup-agents-overwrite.test.js +79 -0
  95. package/dist/cli/__tests__/setup-agents-overwrite.test.js.map +1 -1
  96. package/dist/cli/__tests__/setup-gh-star.test.js.map +1 -1
  97. package/dist/cli/__tests__/setup-hooks-shared-ownership.test.js +77 -3
  98. package/dist/cli/__tests__/setup-hooks-shared-ownership.test.js.map +1 -1
  99. package/dist/cli/__tests__/setup-hooks-trust-e2e.test.d.ts +2 -0
  100. package/dist/cli/__tests__/setup-hooks-trust-e2e.test.d.ts.map +1 -0
  101. package/dist/cli/__tests__/setup-hooks-trust-e2e.test.js +285 -0
  102. package/dist/cli/__tests__/setup-hooks-trust-e2e.test.js.map +1 -0
  103. package/dist/cli/__tests__/setup-install-mode.test.js +2579 -294
  104. package/dist/cli/__tests__/setup-install-mode.test.js.map +1 -1
  105. package/dist/cli/__tests__/setup-preferences.test.d.ts +2 -0
  106. package/dist/cli/__tests__/setup-preferences.test.d.ts.map +1 -0
  107. package/dist/cli/__tests__/setup-preferences.test.js +79 -0
  108. package/dist/cli/__tests__/setup-preferences.test.js.map +1 -0
  109. package/dist/cli/__tests__/setup-refresh.test.js +33 -2
  110. package/dist/cli/__tests__/setup-refresh.test.js.map +1 -1
  111. package/dist/cli/__tests__/setup-scope.test.js +46 -3
  112. package/dist/cli/__tests__/setup-scope.test.js.map +1 -1
  113. package/dist/cli/__tests__/sparkshell-cli.test.js.map +1 -1
  114. package/dist/cli/__tests__/state.test.js +37 -0
  115. package/dist/cli/__tests__/state.test.js.map +1 -1
  116. package/dist/cli/__tests__/team.test.js +105 -7
  117. package/dist/cli/__tests__/team.test.js.map +1 -1
  118. package/dist/cli/__tests__/ultragoal.test.js +61 -1
  119. package/dist/cli/__tests__/ultragoal.test.js.map +1 -1
  120. package/dist/cli/__tests__/uninstall.test.js +1817 -24
  121. package/dist/cli/__tests__/uninstall.test.js.map +1 -1
  122. package/dist/cli/__tests__/update.test.js +58 -0
  123. package/dist/cli/__tests__/update.test.js.map +1 -1
  124. package/dist/cli/__tests__/url.test.js.map +1 -1
  125. package/dist/cli/__tests__/windows-popup-loop-contract.test.js +2 -1
  126. package/dist/cli/__tests__/windows-popup-loop-contract.test.js.map +1 -1
  127. package/dist/cli/adapt.js.map +1 -1
  128. package/dist/cli/agents-init.js.map +1 -1
  129. package/dist/cli/agents.js.map +1 -1
  130. package/dist/cli/api.js.map +1 -1
  131. package/dist/cli/ask.d.ts +1 -1
  132. package/dist/cli/ask.d.ts.map +1 -1
  133. package/dist/cli/ask.js.map +1 -1
  134. package/dist/cli/auth.js.map +1 -1
  135. package/dist/cli/autoresearch-guided.js.map +1 -1
  136. package/dist/cli/autoresearch-intake.js.map +1 -1
  137. package/dist/cli/capabilities.js.map +1 -1
  138. package/dist/cli/cleanup.js.map +1 -1
  139. package/dist/cli/codex-feature-probe.js.map +1 -1
  140. package/dist/cli/codex-home.js.map +1 -1
  141. package/dist/cli/doctor.d.ts +21 -0
  142. package/dist/cli/doctor.d.ts.map +1 -1
  143. package/dist/cli/doctor.js +607 -130
  144. package/dist/cli/doctor.js.map +1 -1
  145. package/dist/cli/explore.js.map +1 -1
  146. package/dist/cli/index.d.ts +14 -3
  147. package/dist/cli/index.d.ts.map +1 -1
  148. package/dist/cli/index.js +245 -57
  149. package/dist/cli/index.js.map +1 -1
  150. package/dist/cli/mcp-parity.js +2 -2
  151. package/dist/cli/mcp-parity.js.map +1 -1
  152. package/dist/cli/mcp-serve.js.map +1 -1
  153. package/dist/cli/mission.js.map +1 -1
  154. package/dist/cli/native-assets.js.map +1 -1
  155. package/dist/cli/native-hook-claim-journal.d.ts +29 -0
  156. package/dist/cli/native-hook-claim-journal.d.ts.map +1 -0
  157. package/dist/cli/native-hook-claim-journal.js +252 -0
  158. package/dist/cli/native-hook-claim-journal.js.map +1 -0
  159. package/dist/cli/omx.js.map +1 -1
  160. package/dist/cli/plugin-marketplace.js.map +1 -1
  161. package/dist/cli/project-runtime-codex-homes.js.map +1 -1
  162. package/dist/cli/question.js.map +1 -1
  163. package/dist/cli/ralph.d.ts.map +1 -1
  164. package/dist/cli/ralph.js +3 -2
  165. package/dist/cli/ralph.js.map +1 -1
  166. package/dist/cli/ralplan.d.ts +10 -0
  167. package/dist/cli/ralplan.d.ts.map +1 -0
  168. package/dist/cli/ralplan.js +110 -0
  169. package/dist/cli/ralplan.js.map +1 -0
  170. package/dist/cli/session-search.d.ts +5 -0
  171. package/dist/cli/session-search.d.ts.map +1 -1
  172. package/dist/cli/session-search.js +87 -0
  173. package/dist/cli/session-search.js.map +1 -1
  174. package/dist/cli/setup-preferences.d.ts +11 -0
  175. package/dist/cli/setup-preferences.d.ts.map +1 -1
  176. package/dist/cli/setup-preferences.js +32 -0
  177. package/dist/cli/setup-preferences.js.map +1 -1
  178. package/dist/cli/setup.d.ts +58 -0
  179. package/dist/cli/setup.d.ts.map +1 -1
  180. package/dist/cli/setup.js +1450 -398
  181. package/dist/cli/setup.js.map +1 -1
  182. package/dist/cli/sparkshell.js.map +1 -1
  183. package/dist/cli/star-prompt.js.map +1 -1
  184. package/dist/cli/state.d.ts.map +1 -1
  185. package/dist/cli/state.js +2 -1
  186. package/dist/cli/state.js.map +1 -1
  187. package/dist/cli/team.js.map +1 -1
  188. package/dist/cli/ultragoal.d.ts +5 -1
  189. package/dist/cli/ultragoal.d.ts.map +1 -1
  190. package/dist/cli/ultragoal.js +34 -2
  191. package/dist/cli/ultragoal.js.map +1 -1
  192. package/dist/cli/uninstall.d.ts +17 -0
  193. package/dist/cli/uninstall.d.ts.map +1 -1
  194. package/dist/cli/uninstall.js +986 -95
  195. package/dist/cli/uninstall.js.map +1 -1
  196. package/dist/cli/update.d.ts.map +1 -1
  197. package/dist/cli/update.js +8 -1
  198. package/dist/cli/update.js.map +1 -1
  199. package/dist/cli/url.js.map +1 -1
  200. package/dist/compat/__tests__/doctor-contract.test.js +5 -1
  201. package/dist/compat/__tests__/doctor-contract.test.js.map +1 -1
  202. package/dist/compat/__tests__/rust-runtime-compat.test.js +1 -1
  203. package/dist/compat/__tests__/rust-runtime-compat.test.js.map +1 -1
  204. package/dist/config/__tests__/codex-hooks.test.js +1287 -11
  205. package/dist/config/__tests__/codex-hooks.test.js.map +1 -1
  206. package/dist/config/__tests__/generator-idempotent.test.js +1179 -32
  207. package/dist/config/__tests__/generator-idempotent.test.js.map +1 -1
  208. package/dist/config/__tests__/generator-notify.test.js +6 -1
  209. package/dist/config/__tests__/generator-notify.test.js.map +1 -1
  210. package/dist/config/__tests__/models.test.js +67 -3
  211. package/dist/config/__tests__/models.test.js.map +1 -1
  212. package/dist/config/codex-feature-flags.d.ts.map +1 -1
  213. package/dist/config/codex-feature-flags.js.map +1 -1
  214. package/dist/config/codex-hooks.d.ts +124 -1
  215. package/dist/config/codex-hooks.d.ts.map +1 -1
  216. package/dist/config/codex-hooks.js +2142 -265
  217. package/dist/config/codex-hooks.js.map +1 -1
  218. package/dist/config/commit-lore-guard.js.map +1 -1
  219. package/dist/config/generator.d.ts +23 -34
  220. package/dist/config/generator.d.ts.map +1 -1
  221. package/dist/config/generator.js +1480 -246
  222. package/dist/config/generator.js.map +1 -1
  223. package/dist/config/mcp-registry.js.map +1 -1
  224. package/dist/config/models.d.ts +14 -4
  225. package/dist/config/models.d.ts.map +1 -1
  226. package/dist/config/models.js +15 -1
  227. package/dist/config/models.js.map +1 -1
  228. package/dist/config/omx-first-party-mcp.js.map +1 -1
  229. package/dist/config/team-mode.d.ts.map +1 -1
  230. package/dist/document-refresh/__tests__/enforcer.test.js.map +1 -1
  231. package/dist/document-refresh/enforcer.js.map +1 -1
  232. package/dist/exec/followup.js.map +1 -1
  233. package/dist/goal-workflows/artifacts.js.map +1 -1
  234. package/dist/hooks/__tests__/agents-overlay.test.js +7 -4
  235. package/dist/hooks/__tests__/agents-overlay.test.js.map +1 -1
  236. package/dist/hooks/__tests__/analyze-routing-contract.test.js.map +1 -1
  237. package/dist/hooks/__tests__/analyze-skill-contract.test.js.map +1 -1
  238. package/dist/hooks/__tests__/anti-slop-workflow.test.js.map +1 -1
  239. package/dist/hooks/__tests__/autopilot-skill-contract.test.js +9 -3
  240. package/dist/hooks/__tests__/autopilot-skill-contract.test.js.map +1 -1
  241. package/dist/hooks/__tests__/code-review-skill-contract.test.js.map +1 -1
  242. package/dist/hooks/__tests__/consensus-execution-handoff.test.js.map +1 -1
  243. package/dist/hooks/__tests__/deep-interview-contract.test.js.map +1 -1
  244. package/dist/hooks/__tests__/design-skill.test.js.map +1 -1
  245. package/dist/hooks/__tests__/keyword-detector.test.js +1376 -6
  246. package/dist/hooks/__tests__/keyword-detector.test.js.map +1 -1
  247. package/dist/hooks/__tests__/notify-fallback-watcher.test.js +252 -543
  248. package/dist/hooks/__tests__/notify-fallback-watcher.test.js.map +1 -1
  249. package/dist/hooks/__tests__/notify-hook-all-workers-idle.test.js +38 -4
  250. package/dist/hooks/__tests__/notify-hook-all-workers-idle.test.js.map +1 -1
  251. package/dist/hooks/__tests__/notify-hook-auto-nudge.test.js +765 -10
  252. package/dist/hooks/__tests__/notify-hook-auto-nudge.test.js.map +1 -1
  253. package/dist/hooks/__tests__/notify-hook-cross-worktree-heartbeat.test.js +2 -0
  254. package/dist/hooks/__tests__/notify-hook-cross-worktree-heartbeat.test.js.map +1 -1
  255. package/dist/hooks/__tests__/notify-hook-managed-tmux.test.js +142 -1
  256. package/dist/hooks/__tests__/notify-hook-managed-tmux.test.js.map +1 -1
  257. package/dist/hooks/__tests__/notify-hook-modules.test.js.map +1 -1
  258. package/dist/hooks/__tests__/notify-hook-non-omx-guard.test.js +48 -2
  259. package/dist/hooks/__tests__/notify-hook-non-omx-guard.test.js.map +1 -1
  260. package/dist/hooks/__tests__/notify-hook-ralph-resume.test.js +61 -44
  261. package/dist/hooks/__tests__/notify-hook-ralph-resume.test.js.map +1 -1
  262. package/dist/hooks/__tests__/notify-hook-regression-205.test.js.map +1 -1
  263. package/dist/hooks/__tests__/notify-hook-session-idle-dedupe.test.js +3 -2
  264. package/dist/hooks/__tests__/notify-hook-session-idle-dedupe.test.js.map +1 -1
  265. package/dist/hooks/__tests__/notify-hook-session-scope.test.js +243 -4
  266. package/dist/hooks/__tests__/notify-hook-session-scope.test.js.map +1 -1
  267. package/dist/hooks/__tests__/notify-hook-team-dispatch.test.js +531 -12
  268. package/dist/hooks/__tests__/notify-hook-team-dispatch.test.js.map +1 -1
  269. package/dist/hooks/__tests__/notify-hook-team-leader-nudge.test.js +163 -24
  270. package/dist/hooks/__tests__/notify-hook-team-leader-nudge.test.js.map +1 -1
  271. package/dist/hooks/__tests__/notify-hook-team-tmux-guard.test.js +482 -32
  272. package/dist/hooks/__tests__/notify-hook-team-tmux-guard.test.js.map +1 -1
  273. package/dist/hooks/__tests__/notify-hook-tmux-heal.test.js +68 -1
  274. package/dist/hooks/__tests__/notify-hook-tmux-heal.test.js.map +1 -1
  275. package/dist/hooks/__tests__/notify-hook-tmux-scrollback.test.js +7 -1
  276. package/dist/hooks/__tests__/notify-hook-tmux-scrollback.test.js.map +1 -1
  277. package/dist/hooks/__tests__/notify-hook-visual-verdict.test.js.map +1 -1
  278. package/dist/hooks/__tests__/notify-hook-worker-idle.test.js +43 -4
  279. package/dist/hooks/__tests__/notify-hook-worker-idle.test.js.map +1 -1
  280. package/dist/hooks/__tests__/openclaw-setup-contract.test.js.map +1 -1
  281. package/dist/hooks/__tests__/pre-context-gate-skills.test.js.map +1 -1
  282. package/dist/hooks/__tests__/prometheus-strict-contract.test.js.map +1 -1
  283. package/dist/hooks/__tests__/prompt-guidance-fragments.test.js.map +1 -1
  284. package/dist/hooks/__tests__/prompt-guidance-test-helpers.js.map +1 -1
  285. package/dist/hooks/__tests__/prompt-session-provenance.test.d.ts +2 -0
  286. package/dist/hooks/__tests__/prompt-session-provenance.test.d.ts.map +1 -0
  287. package/dist/hooks/__tests__/prompt-session-provenance.test.js +82 -0
  288. package/dist/hooks/__tests__/prompt-session-provenance.test.js.map +1 -0
  289. package/dist/hooks/__tests__/session.test.js +878 -11
  290. package/dist/hooks/__tests__/session.test.js.map +1 -1
  291. package/dist/hooks/__tests__/skill-catalog-hygiene.test.js.map +1 -1
  292. package/dist/hooks/__tests__/team-runtime-gating-docs-contract.test.js.map +1 -1
  293. package/dist/hooks/__tests__/visual-ralph-skill.test.js.map +1 -1
  294. package/dist/hooks/__tests__/visual-verdict-loop.test.js.map +1 -1
  295. package/dist/hooks/agents-overlay.js +5 -5
  296. package/dist/hooks/agents-overlay.js.map +1 -1
  297. package/dist/hooks/code-simplifier/index.js.map +1 -1
  298. package/dist/hooks/explore-routing.js.map +1 -1
  299. package/dist/hooks/extensibility/dispatcher.js.map +1 -1
  300. package/dist/hooks/extensibility/events.js.map +1 -1
  301. package/dist/hooks/extensibility/loader.js.map +1 -1
  302. package/dist/hooks/extensibility/plugin-runner-stdin.js.map +1 -1
  303. package/dist/hooks/extensibility/sdk/logging.js.map +1 -1
  304. package/dist/hooks/keyword-detector.d.ts +33 -7
  305. package/dist/hooks/keyword-detector.d.ts.map +1 -1
  306. package/dist/hooks/keyword-detector.js +2228 -119
  307. package/dist/hooks/keyword-detector.js.map +1 -1
  308. package/dist/hooks/keyword-registry.d.ts +12 -0
  309. package/dist/hooks/keyword-registry.d.ts.map +1 -1
  310. package/dist/hooks/keyword-registry.js +41 -0
  311. package/dist/hooks/keyword-registry.js.map +1 -1
  312. package/dist/hooks/prompt-session-provenance.d.ts +87 -0
  313. package/dist/hooks/prompt-session-provenance.d.ts.map +1 -0
  314. package/dist/hooks/prompt-session-provenance.js +180 -0
  315. package/dist/hooks/prompt-session-provenance.js.map +1 -0
  316. package/dist/hooks/session.d.ts +163 -34
  317. package/dist/hooks/session.d.ts.map +1 -1
  318. package/dist/hooks/session.js +1395 -317
  319. package/dist/hooks/session.js.map +1 -1
  320. package/dist/hooks/task-size-detector.js.map +1 -1
  321. package/dist/hud/__tests__/state.test.js.map +1 -1
  322. package/dist/hud/authority.js.map +1 -1
  323. package/dist/hud/constants.js.map +1 -1
  324. package/dist/hud/index.js.map +1 -1
  325. package/dist/hud/reconcile.js.map +1 -1
  326. package/dist/hud/render.js.map +1 -1
  327. package/dist/hud/state.js.map +1 -1
  328. package/dist/hud/tmux.js.map +1 -1
  329. package/dist/leader/__tests__/contract.test.js +153 -1
  330. package/dist/leader/__tests__/contract.test.js.map +1 -1
  331. package/dist/leader/contract.d.ts +42 -5
  332. package/dist/leader/contract.d.ts.map +1 -1
  333. package/dist/leader/contract.js +184 -3
  334. package/dist/leader/contract.js.map +1 -1
  335. package/dist/mcp/__tests__/bootstrap.test.js.map +1 -1
  336. package/dist/mcp/__tests__/code-intel-server.test.js +31 -3
  337. package/dist/mcp/__tests__/code-intel-server.test.js.map +1 -1
  338. package/dist/mcp/__tests__/server-lifecycle.test.js +15 -2
  339. package/dist/mcp/__tests__/server-lifecycle.test.js.map +1 -1
  340. package/dist/mcp/__tests__/state-paths.test.js +98 -1
  341. package/dist/mcp/__tests__/state-paths.test.js.map +1 -1
  342. package/dist/mcp/__tests__/wiki-server.test.js +18 -0
  343. package/dist/mcp/__tests__/wiki-server.test.js.map +1 -1
  344. package/dist/mcp/bootstrap.js.map +1 -1
  345. package/dist/mcp/code-intel-server.d.ts +58 -58
  346. package/dist/mcp/code-intel-server.d.ts.map +1 -1
  347. package/dist/mcp/code-intel-server.js +13 -3
  348. package/dist/mcp/code-intel-server.js.map +1 -1
  349. package/dist/mcp/hermes-bridge.js.map +1 -1
  350. package/dist/mcp/hermes-server.d.ts +70 -70
  351. package/dist/mcp/hermes-server.d.ts.map +1 -1
  352. package/dist/mcp/lifecycle-telemetry.js.map +1 -1
  353. package/dist/mcp/memory-server.d.ts +34 -34
  354. package/dist/mcp/memory-server.d.ts.map +1 -1
  355. package/dist/mcp/memory-server.js.map +1 -1
  356. package/dist/mcp/state-paths.d.ts +14 -0
  357. package/dist/mcp/state-paths.d.ts.map +1 -1
  358. package/dist/mcp/state-paths.js +96 -20
  359. package/dist/mcp/state-paths.js.map +1 -1
  360. package/dist/mcp/state-server.d.ts +47 -47
  361. package/dist/mcp/state-server.d.ts.map +1 -1
  362. package/dist/mcp/trace-server.d.ts +4 -4
  363. package/dist/mcp/trace-server.d.ts.map +1 -1
  364. package/dist/mcp/trace-server.js.map +1 -1
  365. package/dist/mcp/wiki-server.d.ts +34 -23
  366. package/dist/mcp/wiki-server.d.ts.map +1 -1
  367. package/dist/mcp/wiki-server.js +9 -2
  368. package/dist/mcp/wiki-server.js.map +1 -1
  369. package/dist/modes/__tests__/base-session-scope.test.js +53 -1
  370. package/dist/modes/__tests__/base-session-scope.test.js.map +1 -1
  371. package/dist/modes/__tests__/base-tmux-pane.test.js +4 -4
  372. package/dist/modes/__tests__/base-tmux-pane.test.js.map +1 -1
  373. package/dist/modes/base.d.ts.map +1 -1
  374. package/dist/modes/base.js +5 -5
  375. package/dist/modes/base.js.map +1 -1
  376. package/dist/notifications/__tests__/lifecycle-dedupe.test.js.map +1 -1
  377. package/dist/notifications/__tests__/reply-listener.test.js.map +1 -1
  378. package/dist/notifications/__tests__/session-idle-tail-dedupe.test.js +177 -85
  379. package/dist/notifications/__tests__/session-idle-tail-dedupe.test.js.map +1 -1
  380. package/dist/notifications/__tests__/session-registry.test.js.map +1 -1
  381. package/dist/notifications/__tests__/template-engine.test.js.map +1 -1
  382. package/dist/notifications/__tests__/verbosity.test.js.map +1 -1
  383. package/dist/notifications/http-client.js.map +1 -1
  384. package/dist/notifications/index.d.ts +4 -1
  385. package/dist/notifications/index.d.ts.map +1 -1
  386. package/dist/notifications/index.js +3 -9
  387. package/dist/notifications/index.js.map +1 -1
  388. package/dist/notifications/lifecycle-dedupe.js.map +1 -1
  389. package/dist/notifications/reply-listener.d.ts.map +1 -1
  390. package/dist/notifications/reply-listener.js.map +1 -1
  391. package/dist/notifications/session-registry.js.map +1 -1
  392. package/dist/notifications/session-status.js.map +1 -1
  393. package/dist/notifications/temp-contract.d.ts.map +1 -1
  394. package/dist/notifications/temp-contract.js +4 -0
  395. package/dist/notifications/temp-contract.js.map +1 -1
  396. package/dist/notifications/tmux-detector.js.map +1 -1
  397. package/dist/notifications/tmux.js.map +1 -1
  398. package/dist/openclaw/config.js.map +1 -1
  399. package/dist/openclaw/dispatcher.js.map +1 -1
  400. package/dist/pipeline/__tests__/orchestrator.test.js +4 -4
  401. package/dist/pipeline/__tests__/orchestrator.test.js.map +1 -1
  402. package/dist/pipeline/__tests__/stages.test.js +16 -13
  403. package/dist/pipeline/__tests__/stages.test.js.map +1 -1
  404. package/dist/pipeline/stages/code-review.js.map +1 -1
  405. package/dist/pipeline/stages/ralph-verify.js.map +1 -1
  406. package/dist/pipeline/stages/ralplan.js.map +1 -1
  407. package/dist/pipeline/stages/team-exec.js.map +1 -1
  408. package/dist/pipeline/stages/ultragoal.js.map +1 -1
  409. package/dist/pipeline/stages/ultraqa.js.map +1 -1
  410. package/dist/planning/__tests__/approved-execution-lifecycle-matrix.test.js.map +1 -1
  411. package/dist/planning/artifact-names.js.map +1 -1
  412. package/dist/planning/artifacts.js.map +1 -1
  413. package/dist/question/__tests__/state.test.js.map +1 -1
  414. package/dist/question/__tests__/ui.test.js.map +1 -1
  415. package/dist/question/autopilot-wait.js.map +1 -1
  416. package/dist/question/client.d.ts.map +1 -1
  417. package/dist/question/client.js.map +1 -1
  418. package/dist/question/deep-interview.js.map +1 -1
  419. package/dist/question/events.js.map +1 -1
  420. package/dist/question/renderer.js.map +1 -1
  421. package/dist/question/state.d.ts.map +1 -1
  422. package/dist/question/state.js.map +1 -1
  423. package/dist/question/ui.js.map +1 -1
  424. package/dist/ralph/contract.d.ts +1 -1
  425. package/dist/ralph/contract.d.ts.map +1 -1
  426. package/dist/ralplan/__tests__/consensus-gate.test.js +527 -8
  427. package/dist/ralplan/__tests__/consensus-gate.test.js.map +1 -1
  428. package/dist/ralplan/__tests__/documented-leader-preflight.test.d.ts +2 -0
  429. package/dist/ralplan/__tests__/documented-leader-preflight.test.d.ts.map +1 -0
  430. package/dist/ralplan/__tests__/documented-leader-preflight.test.js +46 -0
  431. package/dist/ralplan/__tests__/documented-leader-preflight.test.js.map +1 -0
  432. package/dist/ralplan/__tests__/runtime.test.js +116 -14
  433. package/dist/ralplan/__tests__/runtime.test.js.map +1 -1
  434. package/dist/ralplan/consensus-gate.d.ts +3 -3
  435. package/dist/ralplan/consensus-gate.d.ts.map +1 -1
  436. package/dist/ralplan/consensus-gate.js +173 -70
  437. package/dist/ralplan/consensus-gate.js.map +1 -1
  438. package/dist/ralplan/documented-leader-preflight.d.ts +34 -0
  439. package/dist/ralplan/documented-leader-preflight.d.ts.map +1 -0
  440. package/dist/ralplan/documented-leader-preflight.js +58 -0
  441. package/dist/ralplan/documented-leader-preflight.js.map +1 -0
  442. package/dist/ralplan/runtime.d.ts +3 -2
  443. package/dist/ralplan/runtime.d.ts.map +1 -1
  444. package/dist/ralplan/runtime.js +12 -9
  445. package/dist/ralplan/runtime.js.map +1 -1
  446. package/dist/runtime/__tests__/bridge.test.js +42 -1
  447. package/dist/runtime/__tests__/bridge.test.js.map +1 -1
  448. package/dist/runtime/bridge.d.ts +12 -0
  449. package/dist/runtime/bridge.d.ts.map +1 -1
  450. package/dist/runtime/bridge.js +52 -1
  451. package/dist/runtime/bridge.js.map +1 -1
  452. package/dist/runtime/process-tree.js.map +1 -1
  453. package/dist/runtime/run-loop.js.map +1 -1
  454. package/dist/runtime/run-outcome.d.ts +3 -3
  455. package/dist/runtime/run-outcome.d.ts.map +1 -1
  456. package/dist/runtime/run-outcome.js.map +1 -1
  457. package/dist/runtime/run-state.js.map +1 -1
  458. package/dist/scripts/__tests__/codex-native-hook.test.js +6657 -995
  459. package/dist/scripts/__tests__/codex-native-hook.test.js.map +1 -1
  460. package/dist/scripts/__tests__/fixtures/issue-3138-installed-smoke.d.ts +2 -0
  461. package/dist/scripts/__tests__/fixtures/issue-3138-installed-smoke.d.ts.map +1 -0
  462. package/dist/scripts/__tests__/fixtures/issue-3138-installed-smoke.js +634 -0
  463. package/dist/scripts/__tests__/fixtures/issue-3138-installed-smoke.js.map +1 -0
  464. package/dist/scripts/__tests__/generate-release-body.test.js.map +1 -1
  465. package/dist/scripts/__tests__/hook-derived-watcher.test.js.map +1 -1
  466. package/dist/scripts/__tests__/notify-dispatcher.test.js.map +1 -1
  467. package/dist/scripts/__tests__/notify-fallback-delivery.test.d.ts +2 -0
  468. package/dist/scripts/__tests__/notify-fallback-delivery.test.d.ts.map +1 -0
  469. package/dist/scripts/__tests__/notify-fallback-delivery.test.js +174 -0
  470. package/dist/scripts/__tests__/notify-fallback-delivery.test.js.map +1 -0
  471. package/dist/scripts/__tests__/notify-state-io.test.js +57 -1
  472. package/dist/scripts/__tests__/notify-state-io.test.js.map +1 -1
  473. package/dist/scripts/__tests__/reasoning-artifact-contract.test.d.ts +2 -0
  474. package/dist/scripts/__tests__/reasoning-artifact-contract.test.d.ts.map +1 -0
  475. package/dist/scripts/__tests__/reasoning-artifact-contract.test.js +108 -0
  476. package/dist/scripts/__tests__/reasoning-artifact-contract.test.js.map +1 -0
  477. package/dist/scripts/__tests__/role-intent-bootstrap-e2e-3181.test.d.ts +2 -0
  478. package/dist/scripts/__tests__/role-intent-bootstrap-e2e-3181.test.d.ts.map +1 -0
  479. package/dist/scripts/__tests__/role-intent-bootstrap-e2e-3181.test.js +78 -0
  480. package/dist/scripts/__tests__/role-intent-bootstrap-e2e-3181.test.js.map +1 -0
  481. package/dist/scripts/__tests__/role-intent-durable-recovery-3181.test.d.ts +2 -0
  482. package/dist/scripts/__tests__/role-intent-durable-recovery-3181.test.d.ts.map +1 -0
  483. package/dist/scripts/__tests__/role-intent-durable-recovery-3181.test.js +55 -0
  484. package/dist/scripts/__tests__/role-intent-durable-recovery-3181.test.js.map +1 -0
  485. package/dist/scripts/__tests__/run-test-files.test.js.map +1 -1
  486. package/dist/scripts/__tests__/smoke-packed-install.test.js +1100 -4
  487. package/dist/scripts/__tests__/smoke-packed-install.test.js.map +1 -1
  488. package/dist/scripts/__tests__/test-reply-listener-live.test.js.map +1 -1
  489. package/dist/scripts/build-api.js.map +1 -1
  490. package/dist/scripts/build-explore-harness.js.map +1 -1
  491. package/dist/scripts/build-sparkshell.js.map +1 -1
  492. package/dist/scripts/cleanup-explore-harness.js.map +1 -1
  493. package/dist/scripts/codex-execution-surface.js.map +1 -1
  494. package/dist/scripts/codex-native-hook.d.ts +4 -1
  495. package/dist/scripts/codex-native-hook.d.ts.map +1 -1
  496. package/dist/scripts/codex-native-hook.js +11577 -1184
  497. package/dist/scripts/codex-native-hook.js.map +1 -1
  498. package/dist/scripts/generate-native-release-manifest.js.map +1 -1
  499. package/dist/scripts/generate-release-body.js.map +1 -1
  500. package/dist/scripts/hook-payload-guard.d.ts.map +1 -1
  501. package/dist/scripts/hook-payload-guard.js.map +1 -1
  502. package/dist/scripts/notify-fallback-delivery.d.ts +48 -0
  503. package/dist/scripts/notify-fallback-delivery.d.ts.map +1 -0
  504. package/dist/scripts/notify-fallback-delivery.js +419 -0
  505. package/dist/scripts/notify-fallback-delivery.js.map +1 -0
  506. package/dist/scripts/notify-fallback-watcher.js +218 -138
  507. package/dist/scripts/notify-fallback-watcher.js.map +1 -1
  508. package/dist/scripts/notify-hook/auto-nudge.d.ts +18 -9
  509. package/dist/scripts/notify-hook/auto-nudge.d.ts.map +1 -1
  510. package/dist/scripts/notify-hook/auto-nudge.js +112 -15
  511. package/dist/scripts/notify-hook/auto-nudge.js.map +1 -1
  512. package/dist/scripts/notify-hook/managed-tmux.d.ts +23 -0
  513. package/dist/scripts/notify-hook/managed-tmux.d.ts.map +1 -1
  514. package/dist/scripts/notify-hook/managed-tmux.js +122 -12
  515. package/dist/scripts/notify-hook/managed-tmux.js.map +1 -1
  516. package/dist/scripts/notify-hook/orchestration-intent.d.ts +7 -7
  517. package/dist/scripts/notify-hook/orchestration-intent.d.ts.map +1 -1
  518. package/dist/scripts/notify-hook/ralph-session-resume.d.ts +5 -2
  519. package/dist/scripts/notify-hook/ralph-session-resume.d.ts.map +1 -1
  520. package/dist/scripts/notify-hook/ralph-session-resume.js +33 -37
  521. package/dist/scripts/notify-hook/ralph-session-resume.js.map +1 -1
  522. package/dist/scripts/notify-hook/state-io.d.ts +17 -0
  523. package/dist/scripts/notify-hook/state-io.d.ts.map +1 -1
  524. package/dist/scripts/notify-hook/state-io.js +73 -11
  525. package/dist/scripts/notify-hook/state-io.js.map +1 -1
  526. package/dist/scripts/notify-hook/team-dispatch.d.ts +10 -1
  527. package/dist/scripts/notify-hook/team-dispatch.d.ts.map +1 -1
  528. package/dist/scripts/notify-hook/team-dispatch.js +234 -79
  529. package/dist/scripts/notify-hook/team-dispatch.js.map +1 -1
  530. package/dist/scripts/notify-hook/team-leader-nudge.d.ts +2 -2
  531. package/dist/scripts/notify-hook/team-leader-nudge.d.ts.map +1 -1
  532. package/dist/scripts/notify-hook/team-leader-nudge.js +39 -13
  533. package/dist/scripts/notify-hook/team-leader-nudge.js.map +1 -1
  534. package/dist/scripts/notify-hook/team-tmux-guard.d.ts +16 -8
  535. package/dist/scripts/notify-hook/team-tmux-guard.d.ts.map +1 -1
  536. package/dist/scripts/notify-hook/team-tmux-guard.js +365 -44
  537. package/dist/scripts/notify-hook/team-tmux-guard.js.map +1 -1
  538. package/dist/scripts/notify-hook/team-worker-posttooluse.js.map +1 -1
  539. package/dist/scripts/notify-hook/team-worker-stop.d.ts +1 -1
  540. package/dist/scripts/notify-hook/team-worker-stop.d.ts.map +1 -1
  541. package/dist/scripts/notify-hook/team-worker-stop.js +19 -18
  542. package/dist/scripts/notify-hook/team-worker-stop.js.map +1 -1
  543. package/dist/scripts/notify-hook/team-worker.d.ts +5 -2
  544. package/dist/scripts/notify-hook/team-worker.d.ts.map +1 -1
  545. package/dist/scripts/notify-hook/team-worker.js +37 -28
  546. package/dist/scripts/notify-hook/team-worker.js.map +1 -1
  547. package/dist/scripts/notify-hook/tmux-injection.d.ts +8 -1
  548. package/dist/scripts/notify-hook/tmux-injection.d.ts.map +1 -1
  549. package/dist/scripts/notify-hook/tmux-injection.js +53 -18
  550. package/dist/scripts/notify-hook/tmux-injection.js.map +1 -1
  551. package/dist/scripts/notify-hook/visual-verdict.d.ts +1 -1
  552. package/dist/scripts/notify-hook/visual-verdict.d.ts.map +1 -1
  553. package/dist/scripts/notify-hook/visual-verdict.js +15 -13
  554. package/dist/scripts/notify-hook/visual-verdict.js.map +1 -1
  555. package/dist/scripts/notify-hook.d.ts +24 -1
  556. package/dist/scripts/notify-hook.d.ts.map +1 -1
  557. package/dist/scripts/notify-hook.js +267 -90
  558. package/dist/scripts/notify-hook.js.map +1 -1
  559. package/dist/scripts/postinstall.js.map +1 -1
  560. package/dist/scripts/prompt-inventory.js.map +1 -1
  561. package/dist/scripts/smoke-packed-install.d.ts +927 -5
  562. package/dist/scripts/smoke-packed-install.d.ts.map +1 -1
  563. package/dist/scripts/smoke-packed-install.js +4317 -13
  564. package/dist/scripts/smoke-packed-install.js.map +1 -1
  565. package/dist/scripts/sync-plugin-mirror.js.map +1 -1
  566. package/dist/scripts/test-reply-listener-live.js.map +1 -1
  567. package/dist/scripts/test-sparkshell.js.map +1 -1
  568. package/dist/scripts/verify-native-agents.js.map +1 -1
  569. package/dist/session-history/friction.js.map +1 -1
  570. package/dist/session-history/search.js.map +1 -1
  571. package/dist/sidecar/__tests__/boundary.test.js.map +1 -1
  572. package/dist/sidecar/collector.js.map +1 -1
  573. package/dist/sidecar/index.js.map +1 -1
  574. package/dist/sidecar/render.js.map +1 -1
  575. package/dist/sidecar/tmux.js.map +1 -1
  576. package/dist/state/__tests__/mode-state-context.test.js +54 -0
  577. package/dist/state/__tests__/mode-state-context.test.js.map +1 -1
  578. package/dist/state/__tests__/operations.test.js +66 -15
  579. package/dist/state/__tests__/operations.test.js.map +1 -1
  580. package/dist/state/__tests__/skill-active.test.js +19 -1
  581. package/dist/state/__tests__/skill-active.test.js.map +1 -1
  582. package/dist/state/__tests__/workflow-transition.test.js +61 -0
  583. package/dist/state/__tests__/workflow-transition.test.js.map +1 -1
  584. package/dist/state/mode-state-context.d.ts +5 -0
  585. package/dist/state/mode-state-context.d.ts.map +1 -1
  586. package/dist/state/mode-state-context.js +59 -0
  587. package/dist/state/mode-state-context.js.map +1 -1
  588. package/dist/state/operations.d.ts +1 -1
  589. package/dist/state/operations.d.ts.map +1 -1
  590. package/dist/state/operations.js +49 -55
  591. package/dist/state/operations.js.map +1 -1
  592. package/dist/state/paths.d.ts +1 -1
  593. package/dist/state/paths.d.ts.map +1 -1
  594. package/dist/state/paths.js +1 -1
  595. package/dist/state/paths.js.map +1 -1
  596. package/dist/state/skill-active.d.ts +13 -1
  597. package/dist/state/skill-active.d.ts.map +1 -1
  598. package/dist/state/skill-active.js +43 -4
  599. package/dist/state/skill-active.js.map +1 -1
  600. package/dist/state/terminal-normalization.js.map +1 -1
  601. package/dist/state/workflow-transition-reconcile.d.ts.map +1 -1
  602. package/dist/state/workflow-transition-reconcile.js +20 -4
  603. package/dist/state/workflow-transition-reconcile.js.map +1 -1
  604. package/dist/state/workflow-transition.d.ts +1 -1
  605. package/dist/state/workflow-transition.d.ts.map +1 -1
  606. package/dist/state/workflow-transition.js.map +1 -1
  607. package/dist/subagents/__tests__/leader-bootstrap-3181.test.d.ts +2 -0
  608. package/dist/subagents/__tests__/leader-bootstrap-3181.test.d.ts.map +1 -0
  609. package/dist/subagents/__tests__/leader-bootstrap-3181.test.js +52 -0
  610. package/dist/subagents/__tests__/leader-bootstrap-3181.test.js.map +1 -0
  611. package/dist/subagents/__tests__/tracker.test.js +1550 -10
  612. package/dist/subagents/__tests__/tracker.test.js.map +1 -1
  613. package/dist/subagents/role-routing-marker.d.ts +10 -0
  614. package/dist/subagents/role-routing-marker.d.ts.map +1 -0
  615. package/dist/subagents/role-routing-marker.js +136 -0
  616. package/dist/subagents/role-routing-marker.js.map +1 -0
  617. package/dist/subagents/tracker.d.ts +91 -0
  618. package/dist/subagents/tracker.d.ts.map +1 -1
  619. package/dist/subagents/tracker.js +806 -62
  620. package/dist/subagents/tracker.js.map +1 -1
  621. package/dist/team/__tests__/api-interop.test.js +91 -1
  622. package/dist/team/__tests__/api-interop.test.js.map +1 -1
  623. package/dist/team/__tests__/approved-execution.test.js.map +1 -1
  624. package/dist/team/__tests__/delivery-e2e-smoke.test.js +52 -10
  625. package/dist/team/__tests__/delivery-e2e-smoke.test.js.map +1 -1
  626. package/dist/team/__tests__/idle-nudge.test.js +45 -15
  627. package/dist/team/__tests__/idle-nudge.test.js.map +1 -1
  628. package/dist/team/__tests__/mcp-comm.test.js +93 -27
  629. package/dist/team/__tests__/mcp-comm.test.js.map +1 -1
  630. package/dist/team/__tests__/model-contract.test.js +341 -4
  631. package/dist/team/__tests__/model-contract.test.js.map +1 -1
  632. package/dist/team/__tests__/role-router.test.js.map +1 -1
  633. package/dist/team/__tests__/runtime-cli.test.js +9 -0
  634. package/dist/team/__tests__/runtime-cli.test.js.map +1 -1
  635. package/dist/team/__tests__/runtime.test.js +3624 -355
  636. package/dist/team/__tests__/runtime.test.js.map +1 -1
  637. package/dist/team/__tests__/scaling.test.js +2516 -118
  638. package/dist/team/__tests__/scaling.test.js.map +1 -1
  639. package/dist/team/__tests__/state-root.test.js.map +1 -1
  640. package/dist/team/__tests__/state.test.js +804 -36
  641. package/dist/team/__tests__/state.test.js.map +1 -1
  642. package/dist/team/__tests__/team-ops-contract.test.js +6 -0
  643. package/dist/team/__tests__/team-ops-contract.test.js.map +1 -1
  644. package/dist/team/__tests__/tmux-session.test.js +3664 -969
  645. package/dist/team/__tests__/tmux-session.test.js.map +1 -1
  646. package/dist/team/__tests__/tmux-test-fixture.js.map +1 -1
  647. package/dist/team/__tests__/worker-runtime-identity.test.js +38 -2
  648. package/dist/team/__tests__/worker-runtime-identity.test.js.map +1 -1
  649. package/dist/team/__tests__/worktree.test.js +6 -0
  650. package/dist/team/__tests__/worktree.test.js.map +1 -1
  651. package/dist/team/api-interop.d.ts +2 -2
  652. package/dist/team/api-interop.d.ts.map +1 -1
  653. package/dist/team/api-interop.js +15 -9
  654. package/dist/team/api-interop.js.map +1 -1
  655. package/dist/team/commit-hygiene.js.map +1 -1
  656. package/dist/team/contracts.d.ts +5 -5
  657. package/dist/team/contracts.d.ts.map +1 -1
  658. package/dist/team/delivery-log.js.map +1 -1
  659. package/dist/team/exact-pane.d.ts +23 -0
  660. package/dist/team/exact-pane.d.ts.map +1 -0
  661. package/dist/team/exact-pane.js +133 -0
  662. package/dist/team/exact-pane.js.map +1 -0
  663. package/dist/team/followup-planner.js.map +1 -1
  664. package/dist/team/goal-workflow.js.map +1 -1
  665. package/dist/team/idle-nudge.d.ts +12 -1
  666. package/dist/team/idle-nudge.d.ts.map +1 -1
  667. package/dist/team/idle-nudge.js +41 -14
  668. package/dist/team/idle-nudge.js.map +1 -1
  669. package/dist/team/model-contract.d.ts +18 -2
  670. package/dist/team/model-contract.d.ts.map +1 -1
  671. package/dist/team/model-contract.js +314 -35
  672. package/dist/team/model-contract.js.map +1 -1
  673. package/dist/team/orchestrator.js.map +1 -1
  674. package/dist/team/pane-status.js.map +1 -1
  675. package/dist/team/phase-controller.js.map +1 -1
  676. package/dist/team/reminder-intents.d.ts +1 -1
  677. package/dist/team/reminder-intents.d.ts.map +1 -1
  678. package/dist/team/reminder-intents.js.map +1 -1
  679. package/dist/team/runtime-cli.js.map +1 -1
  680. package/dist/team/runtime.d.ts +1 -0
  681. package/dist/team/runtime.d.ts.map +1 -1
  682. package/dist/team/runtime.js +1326 -231
  683. package/dist/team/runtime.js.map +1 -1
  684. package/dist/team/scaling.d.ts +22 -8
  685. package/dist/team/scaling.d.ts.map +1 -1
  686. package/dist/team/scaling.js +1463 -376
  687. package/dist/team/scaling.js.map +1 -1
  688. package/dist/team/state/dispatch-lock.js.map +1 -1
  689. package/dist/team/state/dispatch.d.ts.map +1 -1
  690. package/dist/team/state/dispatch.js +26 -8
  691. package/dist/team/state/dispatch.js.map +1 -1
  692. package/dist/team/state/events.js.map +1 -1
  693. package/dist/team/state/locks.d.ts.map +1 -1
  694. package/dist/team/state/locks.js +61 -13
  695. package/dist/team/state/locks.js.map +1 -1
  696. package/dist/team/state/tasks.d.ts.map +1 -1
  697. package/dist/team/state/tasks.js +4 -3
  698. package/dist/team/state/tasks.js.map +1 -1
  699. package/dist/team/state-root.js.map +1 -1
  700. package/dist/team/state.d.ts +67 -1
  701. package/dist/team/state.d.ts.map +1 -1
  702. package/dist/team/state.js +636 -129
  703. package/dist/team/state.js.map +1 -1
  704. package/dist/team/team-identity.d.ts.map +1 -1
  705. package/dist/team/team-identity.js.map +1 -1
  706. package/dist/team/team-ops.d.ts +5 -0
  707. package/dist/team/team-ops.d.ts.map +1 -1
  708. package/dist/team/team-ops.js +4 -0
  709. package/dist/team/team-ops.js.map +1 -1
  710. package/dist/team/tmux-session.d.ts +125 -23
  711. package/dist/team/tmux-session.d.ts.map +1 -1
  712. package/dist/team/tmux-session.js +1340 -333
  713. package/dist/team/tmux-session.js.map +1 -1
  714. package/dist/team/worker-bootstrap.js.map +1 -1
  715. package/dist/team/worktree.d.ts.map +1 -1
  716. package/dist/team/worktree.js +4 -0
  717. package/dist/team/worktree.js.map +1 -1
  718. package/dist/ultragoal/__tests__/artifacts.test.js.map +1 -1
  719. package/dist/ultragoal/__tests__/docs-contract.test.js.map +1 -1
  720. package/dist/ultragoal/artifacts.d.ts.map +1 -1
  721. package/dist/ultragoal/artifacts.js +4 -1
  722. package/dist/ultragoal/artifacts.js.map +1 -1
  723. package/dist/url-reader/index.js.map +1 -1
  724. package/dist/utils/__tests__/file-durability.test.d.ts +2 -0
  725. package/dist/utils/__tests__/file-durability.test.d.ts.map +1 -0
  726. package/dist/utils/__tests__/file-durability.test.js +57 -0
  727. package/dist/utils/__tests__/file-durability.test.js.map +1 -0
  728. package/dist/utils/agents-model-table.js.map +1 -1
  729. package/dist/utils/file-durability.d.ts +15 -0
  730. package/dist/utils/file-durability.d.ts.map +1 -0
  731. package/dist/utils/file-durability.js +37 -0
  732. package/dist/utils/file-durability.js.map +1 -0
  733. package/dist/utils/package.js.map +1 -1
  734. package/dist/utils/paths.js.map +1 -1
  735. package/dist/utils/platform-command.js.map +1 -1
  736. package/dist/utils/repo-deps.js.map +1 -1
  737. package/dist/utils/version.js.map +1 -1
  738. package/dist/verification/__tests__/ci-rust-gates.test.js +1 -1
  739. package/dist/verification/__tests__/dev-merge-issue-close-workflow.test.js +3 -0
  740. package/dist/verification/__tests__/dev-merge-issue-close-workflow.test.js.map +1 -1
  741. package/dist/verification/verifier.js.map +1 -1
  742. package/dist/visual/constants.d.ts +1 -1
  743. package/dist/visual/constants.d.ts.map +1 -1
  744. package/dist/vscode/__tests__/index.test.js.map +1 -1
  745. package/dist/vscode/index.js.map +1 -1
  746. package/dist/wiki/__tests__/cjk-tokenize.test.js.map +1 -1
  747. package/dist/wiki/__tests__/lint.test.js.map +1 -1
  748. package/dist/wiki/__tests__/query.test.js.map +1 -1
  749. package/dist/wiki/__tests__/storage.test.js.map +1 -1
  750. package/dist/wiki/__tests__/test-helpers.d.ts.map +1 -1
  751. package/dist/wiki/lint.d.ts +4 -1
  752. package/dist/wiki/lint.d.ts.map +1 -1
  753. package/dist/wiki/lint.js +3 -2
  754. package/dist/wiki/lint.js.map +1 -1
  755. package/dist/wiki/query.js.map +1 -1
  756. package/dist/wiki/storage.js.map +1 -1
  757. package/package.json +2 -2
  758. package/plugins/oh-my-codex/.codex-plugin/plugin.json +1 -1
  759. package/plugins/oh-my-codex/hooks/codex-native-hook.mjs +24 -0
  760. package/plugins/oh-my-codex/skills/omx-setup/SKILL.md +10 -5
  761. package/plugins/oh-my-codex/skills/ralph/SKILL.md +6 -2
  762. package/plugins/oh-my-codex/skills/ralplan/SKILL.md +6 -2
  763. package/plugins/oh-my-codex/skills/team/SKILL.md +13 -9
  764. package/skills/omx-setup/SKILL.md +10 -5
  765. package/skills/ralph/SKILL.md +6 -2
  766. package/skills/ralplan/SKILL.md +6 -2
  767. package/skills/team/SKILL.md +13 -9
  768. package/src/scripts/__tests__/codex-native-hook.test.ts +20048 -10827
  769. package/src/scripts/__tests__/fixtures/issue-3138-installed-smoke.ts +734 -0
  770. package/src/scripts/__tests__/notify-fallback-delivery.test.ts +177 -0
  771. package/src/scripts/__tests__/notify-state-io.test.ts +73 -0
  772. package/src/scripts/__tests__/reasoning-artifact-contract.test.ts +129 -0
  773. package/src/scripts/__tests__/role-intent-bootstrap-e2e-3181.test.ts +78 -0
  774. package/src/scripts/__tests__/role-intent-durable-recovery-3181.test.ts +60 -0
  775. package/src/scripts/__tests__/smoke-packed-install.test.ts +1309 -4
  776. package/src/scripts/codex-native-hook.ts +11429 -1163
  777. package/src/scripts/notify-fallback-delivery.ts +416 -0
  778. package/src/scripts/notify-fallback-watcher.ts +216 -155
  779. package/src/scripts/notify-hook/auto-nudge.ts +113 -14
  780. package/src/scripts/notify-hook/managed-tmux.ts +149 -12
  781. package/src/scripts/notify-hook/ralph-session-resume.ts +37 -36
  782. package/src/scripts/notify-hook/state-io.ts +100 -13
  783. package/src/scripts/notify-hook/team-dispatch.ts +242 -75
  784. package/src/scripts/notify-hook/team-leader-nudge.ts +40 -18
  785. package/src/scripts/notify-hook/team-tmux-guard.ts +366 -39
  786. package/src/scripts/notify-hook/team-worker-stop.ts +20 -16
  787. package/src/scripts/notify-hook/team-worker.ts +39 -26
  788. package/src/scripts/notify-hook/tmux-injection.ts +66 -23
  789. package/src/scripts/notify-hook/visual-verdict.ts +22 -12
  790. package/src/scripts/notify-hook.ts +336 -90
  791. package/src/scripts/smoke-packed-install.ts +5154 -149
@@ -16,7 +16,7 @@ import TOML from "@iarna/toml";
16
16
  import { DEFAULT_FRONTIER_MODEL } from "./models.js";
17
17
  import { DEFAULT_CODEX_HOOK_FEATURE_FLAG, CODEX_HOOK_FEATURE_FLAGS, CODEX_PLUGIN_SCOPED_HOOKS_FEATURE_FLAG, formatCodexHookFeatureFlagLine, normalizeCodexHookFeatureFlag, } from "./codex-feature-flags.js";
18
18
  import { OMX_FIRST_PARTY_MCP_SERVER_NAMES, getOmxFirstPartySetupMcpServers, } from "./omx-first-party-mcp.js";
19
- import { buildManagedCodexHookTrustState, escapeTomlBasicString, } from "./codex-hooks.js";
19
+ import { buildManagedCodexHookTrustState, escapeTomlBasicString, ManagedCodexHooksPlanError, scanManagedCodexHookTrustStateFromContent, } from "./codex-hooks.js";
20
20
  function escapeTomlString(value) {
21
21
  return value.replace(/\\/g, "\\\\").replace(/"/g, '\\"');
22
22
  }
@@ -37,8 +37,8 @@ const LEGACY_SEEDED_MODEL_CONTEXT_WINDOW = 250000;
37
37
  const LEGACY_SEEDED_MODEL_AUTO_COMPACT_TOKEN_LIMIT = 200000;
38
38
  const OMX_SEEDED_BEHAVIORAL_DEFAULTS_START_MARKER = "# oh-my-codex seeded behavioral defaults (uninstall removes unchanged defaults)";
39
39
  const OMX_SEEDED_BEHAVIORAL_DEFAULTS_END_MARKER = "# End oh-my-codex seeded behavioral defaults";
40
- export const OMX_DEVELOPER_INSTRUCTIONS = "You have oh-my-codex installed. AGENTS.md is the orchestration brain and main control surface. Follow AGENTS.md for skill/keyword routing, $name workflow invocation, and role-specialized subagents; when spawning native subagents, set `agent_type` to an installed role and never omit it for OMX work. Use outcome-first, concise progress updates: state the target result, constraints, validation evidence, and stop condition before adding process detail. Native subagents live in .codex/agents and may handle independent parallel subtasks within one Codex session or team pane. Skills load from .codex/skills, not native-agent TOMLs. Treat installed prompts as narrower execution surfaces under AGENTS.md authority.";
41
- export const OMX_PLUGIN_DEVELOPER_INSTRUCTIONS = '<omx version="1">You have oh-my-codex installed through Codex plugin mode. AGENTS.md is the orchestration brain and main control surface. Follow AGENTS.md for skill/keyword routing and $name workflow invocation. When spawning native subagents, set `agent_type` to an installed role and never omit it for OMX work. Registered Codex plugin marketplace surfaces supply OMX workflows and plugin-scoped companion resources when the plugin is installed; native agent roles are installed as setup-owned Codex agent TOML files in plugin mode so agent_type routing works. User-installed skills may still live under ~/.codex/skills. Use outcome-first, concise progress updates: state the target result, constraints, validation evidence, and stop condition before adding process detail.</omx>';
40
+ export const OMX_DEVELOPER_INSTRUCTIONS = "You have oh-my-codex installed. AGENTS.md is the orchestration brain and main control surface. Follow AGENTS.md for skill/keyword routing, $name workflow invocation, and role-specialized subagents; when the native surface exposes `agent_type` role routing, set `agent_type` to an installed role and never omit it for OMX work. When it reports `role_routing_unavailable`, do not fabricate `agent_type`; before Ralplan planning, state, HUD, runtime, or delegation work, run `omx ralplan preflight --json` and stop on `unsupported_documented_leader_proof`. Never fake the role via a prompt label or infer authority from session/thread/pointer/transcript/cwd state. Use outcome-first, concise progress updates: state the target result, constraints, validation evidence, and stop condition before adding process detail. Native subagents live in .codex/agents and may handle independent parallel subtasks within one Codex session or team pane. Skills load from .codex/skills, not native-agent TOMLs. Treat installed prompts as narrower execution surfaces under AGENTS.md authority.";
41
+ export const OMX_PLUGIN_DEVELOPER_INSTRUCTIONS = '<omx version="1">You have oh-my-codex installed through Codex plugin mode. AGENTS.md is the orchestration brain and main control surface. Follow AGENTS.md for skill/keyword routing and $name workflow invocation. When the native surface exposes `agent_type` role routing, set `agent_type` to an installed role and never omit it for OMX work. When it reports `role_routing_unavailable`, do not fabricate `agent_type`; before Ralplan planning, state, HUD, runtime, or delegation work, run `omx ralplan preflight --json` and stop on `unsupported_documented_leader_proof`. Never fake the role via a prompt label or infer authority from session/thread/pointer/transcript/cwd state. Registered Codex plugin marketplace surfaces supply OMX workflows and plugin-scoped companion resources when the plugin is installed; native agent roles are installed as setup-owned Codex agent TOML files in plugin mode so agent_type routing works. User-installed skills may still live under ~/.codex/skills. Use outcome-first, concise progress updates: state the target result, constraints, validation evidence, and stop condition before adding process detail.</omx>';
42
42
  const SHARED_MCP_REGISTRY_MARKER = "oh-my-codex (OMX) Shared MCP Registry Sync";
43
43
  const SHARED_MCP_REGISTRY_END_MARKER = "# End oh-my-codex shared MCP registry sync";
44
44
  const LEGACY_MULTI_AGENT_KEYS = [
@@ -136,6 +136,7 @@ const LEGACY_OMX_STATUS_LINE = statusLineForPreset(DEFAULT_STATUS_LINE_PRESET);
136
136
  const OMX_PRESET_STATUS_LINE_VALUES = new Set(Object.keys(STATUS_LINE_PRESETS).map((preset) => statusLineForPreset(preset)));
137
137
  const LEGACY_OMX_TEAM_RUN_TABLE_PATTERN = /^\s*\[mcp_servers\.(?:"omx_team_run"|omx_team_run)\]\s*$/m;
138
138
  const OMX_CONFIG_MARKER = "oh-my-codex (OMX) Configuration";
139
+ const OMX_CONFIG_START_MARKER = `# ${OMX_CONFIG_MARKER}`;
139
140
  const OMX_CONFIG_END_MARKER = "# End oh-my-codex";
140
141
  const CODEX_MODEL_AVAILABILITY_NUX_TABLE_PATTERN = /^\s*\[tui\.model_availability_nux\]\s*(?:#.*)?$/;
141
142
  const TOML_TABLE_HEADER_PATTERN = /^\s*\[\[?[^\]]+\]?\]\s*(?:#.*)?$/;
@@ -652,246 +653,1366 @@ const OMX_HOOK_TRUST_START_MARKER = "# OMX-owned Codex hook trust state";
652
653
  const OMX_HOOK_TRUST_END_MARKER = "# End OMX-owned Codex hook trust state";
653
654
  const OMX_PROJECT_TRUST_START_MARKER = "# OMX-synced Codex project trust state (from runtime CODEX_HOME)";
654
655
  const OMX_PROJECT_TRUST_END_MARKER = "# End OMX-synced Codex project trust state";
655
- function extractMarkerBlockContent(config, startMarker, endMarker) {
656
- const lines = config.split(/\r?\n/);
657
- for (let i = 0; i < lines.length; i++) {
658
- if (lines[i].trim() !== startMarker)
659
- continue;
660
- const nextEndIdx = lines.findIndex((line, index) => index > i && line.trim() === endMarker);
661
- const nextStartIdx = lines.findIndex((line, index) => index > i && line.trim() === startMarker);
662
- if (nextEndIdx === -1 || (nextStartIdx !== -1 && nextStartIdx < nextEndIdx)) {
656
+ function isPlainTomlRecord(value) {
657
+ return typeof value === "object" && value !== null && !Array.isArray(value) && Object.getPrototypeOf(value) === Object.prototype;
658
+ }
659
+ function safeParseToml(content) {
660
+ try {
661
+ const parsed = TOML.parse(content);
662
+ return isPlainTomlRecord(parsed) ? parsed : undefined;
663
+ }
664
+ catch {
665
+ return undefined;
666
+ }
667
+ }
668
+ function projectHookTrustState(config) {
669
+ const hooks = safeParseToml(config)?.hooks;
670
+ const state = isPlainTomlRecord(hooks) ? hooks.state : undefined;
671
+ return isPlainTomlRecord(state) ? state : undefined;
672
+ }
673
+ function projectTrustSemanticView(config) {
674
+ const hasLeadingBom = config.startsWith("\uFEFF");
675
+ const source = hasLeadingBom ? config.slice(1) : config;
676
+ if (source.includes("\uFEFF"))
677
+ return undefined;
678
+ return { source, prefix: hasLeadingBom ? "\uFEFF" : "" };
679
+ }
680
+ function projectTrustMarkerRange(config) {
681
+ const lexical = analyzeTomlSource(config);
682
+ if (!lexical.isUnambiguous)
683
+ return undefined;
684
+ const lines = sourceLines(config);
685
+ const starts = lines.filter((line, index) => lexical.lineStartsOutsideMultiline[index] && line.content.trim() === OMX_PROJECT_TRUST_START_MARKER);
686
+ const ends = lines.filter((line, index) => lexical.lineStartsOutsideMultiline[index] && line.content.trim() === OMX_PROJECT_TRUST_END_MARKER);
687
+ if (starts.length === 0 && ends.length === 0)
688
+ return { start: config.length, end: config.length, payloadStart: config.length, payloadEnd: config.length };
689
+ if (starts.length !== 1 || ends.length !== 1 || starts[0].start >= ends[0].start)
690
+ return undefined;
691
+ const start = starts[0];
692
+ const end = ends[0];
693
+ return { start: start.start, end: end.end, payloadStart: start.end, payloadEnd: end.start };
694
+ }
695
+ function hasInlineProjectTrustMarkerComment(config) {
696
+ const lexical = analyzeTomlSource(config);
697
+ return sourceLines(config).some((line, index) => {
698
+ if (!lexical.lineStartsOutsideMultiline[index])
699
+ return false;
700
+ const trimmed = line.content.trim();
701
+ return (trimmed.startsWith(OMX_PROJECT_TRUST_START_MARKER) && trimmed !== OMX_PROJECT_TRUST_START_MARKER) ||
702
+ (trimmed.startsWith(OMX_PROJECT_TRUST_END_MARKER) && trimmed !== OMX_PROJECT_TRUST_END_MARKER);
703
+ });
704
+ }
705
+ function projectTrustHeaderPath(line) {
706
+ if (/^\s*\[\[/.test(line))
707
+ return undefined;
708
+ const header = parseTomlSourceTableHeader(line);
709
+ if (!header?.parsed)
710
+ return undefined;
711
+ const path = [];
712
+ let cursor = header.parsed;
713
+ while (isPlainTomlRecord(cursor)) {
714
+ const keys = Object.keys(cursor);
715
+ if (keys.length === 0)
716
+ return path;
717
+ if (keys.length !== 1)
663
718
  return undefined;
664
- }
665
- return lines.slice(i + 1, nextEndIdx).join("\n").trim();
719
+ const key = keys[0];
720
+ path.push(key);
721
+ cursor = cursor[key];
666
722
  }
667
- return undefined;
723
+ return path;
668
724
  }
669
- function stripMarkerBlock(config, startMarker, endMarker) {
670
- const lines = config.split(/\r?\n/);
671
- const kept = [];
672
- for (let i = 0; i < lines.length;) {
673
- if (lines[i].trim() !== startMarker) {
674
- kept.push(lines[i]);
675
- i += 1;
725
+ function dominantProjectTrustEol(source) {
726
+ const crlfCount = (source.match(/\r\n/g) ?? []).length;
727
+ const lfCount = (source.match(/(?<!\r)\n/g) ?? []).length;
728
+ return crlfCount > lfCount ? "\r\n" : "\n";
729
+ }
730
+ function projectTrustPayloadIsSemanticallyEmpty(payload) {
731
+ const parsed = safeParseToml(payload);
732
+ return parsed !== undefined && Object.keys(parsed).length === 0;
733
+ }
734
+ function projectTrustValueAtPath(source, path) {
735
+ let value = source;
736
+ for (const key of path) {
737
+ if (!isPlainTomlRecord(value))
738
+ return undefined;
739
+ value = value[key];
740
+ }
741
+ return value;
742
+ }
743
+ /**
744
+ * Marker contents are an ownership boundary, not general TOML. Reject every
745
+ * source form that OMX did not emit rather than rebuilding it and losing data.
746
+ */
747
+ function inventoryProjectTrustPayload(payload, projectHooksPath) {
748
+ const parsed = safeParseToml(payload);
749
+ const lexical = analyzeTomlSource(payload);
750
+ const headers = projectTrustHeaders(payload, { start: 0, end: payload.length });
751
+ if (!parsed || !lexical.isUnambiguous || !headers)
752
+ return undefined;
753
+ if (Object.keys(parsed).some((key) => key !== "projects" && key !== "hooks"))
754
+ return undefined;
755
+ const lines = sourceLines(payload);
756
+ const headerStarts = new Set(headers.map((header) => header.start));
757
+ for (const [index, line] of lines.entries()) {
758
+ if (!lexical.lineStartsOutsideMultiline[index])
759
+ return undefined;
760
+ const isHeader = headerStarts.has(line.start);
761
+ const isBlankOrComment = line.content.trim() === "" || /^\s*#/.test(line.content);
762
+ if (isHeader || isBlankOrComment) {
763
+ if (isHeader && lexical.lineHasTomlComment[index])
764
+ return undefined;
676
765
  continue;
677
766
  }
678
- const nextEndIdx = lines.findIndex((line, index) => index > i && line.trim() === endMarker);
679
- const nextStartIdx = lines.findIndex((line, index) => index > i && line.trim() === startMarker);
680
- if (nextEndIdx === -1 || (nextStartIdx !== -1 && nextStartIdx < nextEndIdx)) {
681
- kept.push(lines[i]);
682
- i += 1;
767
+ if (lexical.lineHasTomlComment[index])
768
+ return undefined;
769
+ const priorHeader = headers.some((header) => header.start < line.start);
770
+ if (!priorHeader)
771
+ return undefined;
772
+ }
773
+ const managedHeaders = [];
774
+ for (const header of headers) {
775
+ if (header.path[0] === "projects" && header.path.length >= 2) {
776
+ if (!isPlainTomlRecord(projectTrustValueAtPath(parsed, header.path)))
777
+ return undefined;
778
+ managedHeaders.push({ ...header, kind: "project" });
683
779
  continue;
684
780
  }
685
- i = nextEndIdx + 1;
781
+ if (header.path[0] === "hooks" &&
782
+ header.path[1] === "state" &&
783
+ header.path.length === 3 &&
784
+ header.path[2].startsWith(`${projectHooksPath}:`)) {
785
+ const value = projectTrustValueAtPath(parsed, header.path);
786
+ if (!isPlainTomlRecord(value) || typeof value.trusted_hash !== "string" || value.trusted_hash.length === 0 || Object.keys(value).some((key) => key !== "trusted_hash"))
787
+ return undefined;
788
+ managedHeaders.push({ ...header, kind: "hook" });
789
+ continue;
790
+ }
791
+ return undefined;
686
792
  }
687
- return kept.join("\n").replace(/\n{3,}/g, "\n\n").trimEnd();
793
+ if (!projectTrustPayloadProjectFamiliesAreValid(managedHeaders))
794
+ return undefined;
795
+ if (!projectTrustPayloadNestedStructuresAreExplicit(parsed, managedHeaders))
796
+ return undefined;
797
+ return { parsed, headers: managedHeaders };
688
798
  }
689
- function isPlainTomlRecord(value) {
690
- return (typeof value === "object" &&
691
- value !== null &&
692
- !Array.isArray(value) &&
693
- Object.getPrototypeOf(value) === Object.prototype);
799
+ function projectTrustSourceNestedStructuresAreExplicit(parsed, headers) {
800
+ const projects = parsed.projects;
801
+ if (projects === undefined)
802
+ return true;
803
+ if (!isPlainTomlRecord(projects))
804
+ return false;
805
+ const hasHeader = (path) => headers.some((header) => projectTrustEqual(header.path, path));
806
+ const visit = (value, path) => {
807
+ for (const [key, child] of Object.entries(value)) {
808
+ if (!isPlainTomlRecord(child))
809
+ continue;
810
+ const childPath = [...path, key];
811
+ if (!hasHeader(childPath) || !visit(child, childPath))
812
+ return false;
813
+ }
814
+ return true;
815
+ };
816
+ return Object.entries(projects).every(([key, value]) => isPlainTomlRecord(value) && hasHeader(["projects", key]) && visit(value, ["projects", key]));
694
817
  }
695
- function safeParseToml(content) {
696
- try {
697
- const parsed = TOML.parse(content);
698
- return isPlainTomlRecord(parsed) ? parsed : undefined;
818
+ function projectTrustPayloadNestedStructuresAreExplicit(parsed, headers) {
819
+ const hasHeader = (path) => headers.some((header) => header.kind === "project" && projectTrustEqual(header.path, path));
820
+ const visit = (value, path) => {
821
+ for (const [key, child] of Object.entries(value)) {
822
+ if (!isPlainTomlRecord(child))
823
+ continue;
824
+ const childPath = [...path, key];
825
+ if (!hasHeader(childPath) || !visit(child, childPath))
826
+ return false;
827
+ }
828
+ return true;
829
+ };
830
+ return headers
831
+ .filter((header) => header.kind === "project" && header.path.length === 2)
832
+ .every((header) => {
833
+ const value = projectTrustValueAtPath(parsed, header.path);
834
+ return isPlainTomlRecord(value) && visit(value, header.path);
835
+ });
836
+ }
837
+ function projectTrustPayloadTableSpan(source) {
838
+ const lines = sourceLines(source);
839
+ const lastBodyLine = lines
840
+ .slice(1)
841
+ .reverse()
842
+ .find((line) => line.content.trim() !== "" && !/^\s*#/.test(line.content));
843
+ if (!lastBodyLine)
844
+ return { source, gap: "" };
845
+ const bodyEnd = lastBodyLine.end;
846
+ return { source: source.slice(0, bodyEnd), gap: source.slice(bodyEnd) };
847
+ }
848
+ function projectTrustPayloadTableBodyHasCommentsOrBlanks(source) {
849
+ return sourceLines(source).some((line) => line.content.trim() === "" || /^\s*#/.test(line.content));
850
+ }
851
+ function reconcileProjectTrustPayload(payload, payloadInventory, externalProjects, runtimeProjects, runtimeHooksState, externalHookTrustState, projectHooksPath, eol) {
852
+ const { headers } = payloadInventory;
853
+ const payloadProjectKeys = new Set(headers.filter((header) => header.kind === "project").map((header) => header.path[1]));
854
+ const replacementByHeader = new Map();
855
+ for (const key of payloadProjectKeys) {
856
+ const projectHeaders = headers.filter((header) => header.kind === "project" && header.path[1] === key);
857
+ const parent = projectHeaders.find((header) => header.path.length === 2);
858
+ const payloadValue = projectTrustValueAtPath(payloadInventory.parsed, ["projects", key]);
859
+ const externalValue = externalProjects[key];
860
+ const runtimeValue = runtimeProjects[key];
861
+ if (externalValue !== undefined) {
862
+ if (!projectTrustEqual(payloadValue, externalValue) || (runtimeValue !== undefined && !projectTrustEqual(runtimeValue, externalValue)))
863
+ return undefined;
864
+ for (const header of projectHeaders)
865
+ replacementByHeader.set(header.start, undefined);
866
+ continue;
867
+ }
868
+ if (runtimeValue === undefined) {
869
+ for (const header of projectHeaders)
870
+ replacementByHeader.set(header.start, undefined);
871
+ continue;
872
+ }
873
+ if (!parent || projectHeaders.length !== 1 || !isPlainTomlRecord(runtimeValue))
874
+ return undefined;
875
+ const rendered = renderProjectTrust(key, runtimeValue);
876
+ if (!rendered)
877
+ return undefined;
878
+ replacementByHeader.set(parent.start, rendered.replaceAll("\n", eol));
879
+ }
880
+ for (const header of headers.filter((candidate) => candidate.kind === "hook")) {
881
+ const key = header.path[2];
882
+ const payloadValue = projectTrustValueAtPath(payloadInventory.parsed, header.path);
883
+ const runtimeValue = runtimeHooksState?.[key];
884
+ const externalValue = externalHookTrustState?.[key];
885
+ if (externalValue !== undefined) {
886
+ if (!projectTrustEqual(payloadValue, externalValue))
887
+ return undefined;
888
+ replacementByHeader.set(header.start, undefined);
889
+ continue;
890
+ }
891
+ if (!isPlainTomlRecord(runtimeValue) || typeof runtimeValue.trusted_hash !== "string" || runtimeValue.trusted_hash.length === 0) {
892
+ replacementByHeader.set(header.start, undefined);
893
+ continue;
894
+ }
895
+ replacementByHeader.set(header.start, `[hooks.state."${escapeTomlBasicString(key)}"]${eol}trusted_hash = "${escapeTomlBasicString(runtimeValue.trusted_hash)}"`);
896
+ }
897
+ const leadingGap = payload.slice(0, headers[0]?.start ?? payload.length);
898
+ const parts = headers.length === 0 ? [] : [leadingGap];
899
+ let trailingGap = headers.length === 0 ? leadingGap : "";
900
+ const appendPart = (part) => {
901
+ if (!part)
902
+ return;
903
+ const previous = parts.at(-1);
904
+ if (previous && !/(?:\r\n|\n)$/.test(previous) && !/^(?:\r\n|\n)/.test(part))
905
+ parts.push(eol);
906
+ parts.push(part);
907
+ };
908
+ for (const [index, header] of headers.entries()) {
909
+ const next = headers[index + 1];
910
+ const segmentEnd = next?.start ?? payload.length;
911
+ const replacement = replacementByHeader.get(header.start);
912
+ const span = projectTrustPayloadTableSpan(payload.slice(header.start, segmentEnd));
913
+ const tableBody = span.source.slice(header.end - header.start);
914
+ if (replacement !== undefined &&
915
+ replacement !== span.source &&
916
+ projectTrustPayloadTableBodyHasCommentsOrBlanks(tableBody)) {
917
+ return undefined;
918
+ }
919
+ if (!next) {
920
+ trailingGap = span.gap;
921
+ if (replacement !== undefined)
922
+ appendPart(replacement);
923
+ continue;
924
+ }
925
+ if (replacement !== undefined)
926
+ appendPart(replacement);
927
+ if (span.gap)
928
+ appendPart(span.gap);
699
929
  }
700
- catch {
930
+ const additions = [];
931
+ for (const [key, value] of Object.entries(runtimeProjects).sort(([left], [right]) => left.localeCompare(right))) {
932
+ if (externalProjects[key] !== undefined || payloadProjectKeys.has(key))
933
+ continue;
934
+ if (!isPlainTomlRecord(value))
935
+ return undefined;
936
+ const rendered = renderProjectTrust(key, value);
937
+ if (!rendered)
938
+ return undefined;
939
+ additions.push(`${rendered.replaceAll("\n", eol)}${eol}`);
940
+ }
941
+ for (const [key, value] of Object.entries(runtimeHooksState ?? {}).sort(([left], [right]) => left.localeCompare(right))) {
942
+ if (!key.startsWith(`${projectHooksPath}:`) || externalHookTrustState?.[key] !== undefined || headers.some((header) => header.kind === "hook" && header.path[2] === key))
943
+ continue;
944
+ if (!isPlainTomlRecord(value) || typeof value.trusted_hash !== "string" || value.trusted_hash.length === 0)
945
+ continue;
946
+ additions.push(`[hooks.state."${escapeTomlBasicString(key)}"]${eol}trusted_hash = "${escapeTomlBasicString(value.trusted_hash)}"${eol}`);
947
+ }
948
+ let reconciled = parts.join("");
949
+ if (additions.length > 0 && reconciled && !/(?:\r\n|\n)$/.test(reconciled))
950
+ reconciled += eol;
951
+ reconciled += additions.join("");
952
+ if (trailingGap && reconciled && !/(?:\r\n|\n)$/.test(reconciled) && !/^(?:\r\n|\n)/.test(trailingGap))
953
+ reconciled += eol;
954
+ reconciled += trailingGap;
955
+ reconciled = reconciled.replace(/(?:\r\n|\n)$/, "");
956
+ return Object.keys(safeParseToml(reconciled) ?? {}).length === 0 ? "" : reconciled;
957
+ }
958
+ function projectTrustHeaders(config, region) {
959
+ const lexical = analyzeTomlSource(config);
960
+ if (!lexical.isUnambiguous)
701
961
  return undefined;
962
+ const lines = sourceLines(config);
963
+ const headers = [];
964
+ for (const [index, line] of lines.entries()) {
965
+ if (line.start < region.start || line.start >= region.end || !lexical.lineStartsOutsideMultiline[index])
966
+ continue;
967
+ if (!/^\s*\[/.test(line.content))
968
+ continue;
969
+ const path = projectTrustHeaderPath(line.content);
970
+ if (!path) {
971
+ if (/^\s*\[\[?\s*(?:projects|"projects"|'projects')/.test(line.content))
972
+ return undefined;
973
+ continue;
974
+ }
975
+ headers.push({ start: line.start, end: line.end, path });
702
976
  }
977
+ return headers;
703
978
  }
704
- function collectProjectHookTrustStateKeys(config) {
705
- const keys = new Set();
979
+ function inventoryProjectTrustSource(config) {
706
980
  const parsed = safeParseToml(config);
707
- const hooksTable = isPlainTomlRecord(parsed) ? parsed.hooks : undefined;
708
- const hooksState = isPlainTomlRecord(hooksTable) ? hooksTable.state : undefined;
709
- if (!isPlainTomlRecord(hooksState))
710
- return keys;
711
- for (const [key, entry] of Object.entries(hooksState)) {
712
- if (!isPlainTomlRecord(entry))
981
+ if (!parsed)
982
+ return undefined;
983
+ const projects = parsed.projects;
984
+ if (projects !== undefined && !isPlainTomlRecord(projects))
985
+ return undefined;
986
+ const headers = projectTrustHeaders(config, { start: 0, end: config.length });
987
+ if (!headers)
988
+ return undefined;
989
+ const projectHeaders = headers.filter((header) => header.path[0] === "projects");
990
+ if (projectHeaders.some((header) => header.path.length < 2))
991
+ return undefined;
992
+ for (const key of Object.keys(projects ?? {})) {
993
+ if (projectHeaders.filter((header) => header.path.length === 2 && header.path[1] === key).length !== 1) {
994
+ return undefined;
995
+ }
996
+ }
997
+ if (!projectTrustSourceNestedStructuresAreExplicit(parsed, projectHeaders))
998
+ return undefined;
999
+ return { projects: projects ?? {}, headers };
1000
+ }
1001
+ function projectTrustCandidateIsSafe(candidate, external, runtimeProjects) {
1002
+ const marker = projectTrustMarkerRange(candidate);
1003
+ const inventory = inventoryProjectTrustSource(candidate);
1004
+ const externalInventory = inventoryProjectTrustSource(external);
1005
+ if (!inventory || !externalInventory)
1006
+ return false;
1007
+ const hasMarker = marker !== undefined && marker.start !== marker.end;
1008
+ const hasEquivalentExternalBytes = (actual) => {
1009
+ if (actual === external)
1010
+ return true;
1011
+ return splitProjectTrustTrailingEols(actual).body === splitProjectTrustTrailingEols(external).body;
1012
+ };
1013
+ if (hasMarker) {
1014
+ const candidateExternal = `${candidate.slice(0, marker.start)}${candidate.slice(marker.end)}`;
1015
+ if (!hasEquivalentExternalBytes(candidateExternal) && !(candidate.startsWith(external) && candidate.slice(marker.end).length === 0 && /^(?:\r\n|\n)+$/.test(candidate.slice(external.length, marker.start))))
1016
+ return false;
1017
+ }
1018
+ else if (!hasEquivalentExternalBytes(candidate)) {
1019
+ return false;
1020
+ }
1021
+ const expectedProjects = { ...externalInventory.projects, ...runtimeProjects };
1022
+ if (!projectTrustEqual(inventory.projects, expectedProjects))
1023
+ return false;
1024
+ for (const [key, value] of Object.entries(expectedProjects)) {
1025
+ if (!projectTrustEqual(inventory.projects[key], value))
1026
+ return false;
1027
+ if (inventory.headers.filter((header) => header.path[0] === "projects" && header.path.length === 2 && header.path[1] === key).length !== 1)
1028
+ return false;
1029
+ }
1030
+ return true;
1031
+ }
1032
+ function projectTrustPayloadProjectFamiliesAreValid(headers) {
1033
+ const projectKeys = new Set(headers.filter((header) => header.kind === "project").map((header) => header.path[1]));
1034
+ for (const key of projectKeys) {
1035
+ const family = headers.filter((header) => header.kind === "project" && header.path[1] === key);
1036
+ const parents = family.filter((header) => header.path.length === 2);
1037
+ if (parents.length !== 1 || !projectTrustFamilyIsContiguous(headers, parents[0]))
1038
+ return false;
1039
+ }
1040
+ return true;
1041
+ }
1042
+ function projectTrustFamilyIsContiguous(headers, parent) {
1043
+ const first = headers.indexOf(parent);
1044
+ let ended = false;
1045
+ for (let index = first + 1; index < headers.length; index += 1) {
1046
+ const path = headers[index].path;
1047
+ const belongsToFamily = path[0] === "projects" && path[1] === parent.path[1];
1048
+ if (!belongsToFamily) {
1049
+ ended = true;
713
1050
  continue;
714
- keys.add(key);
1051
+ }
1052
+ if (ended || path.length < 3)
1053
+ return false;
715
1054
  }
716
- return keys;
1055
+ return true;
717
1056
  }
718
- /**
719
- * Repairs project configs from the 0.18.3 relaunch regression where a
720
- * project-synced trust block could duplicate setup-owned hook trust tables and
721
- * make the next runtime CODEX_HOME config.toml invalid before Codex started.
722
- */
1057
+ function projectTrustEqual(left, right) {
1058
+ if (Object.is(left, right))
1059
+ return true;
1060
+ if (left instanceof Date || right instanceof Date) {
1061
+ return left instanceof Date && right instanceof Date && left.constructor === right.constructor && String(left) === String(right);
1062
+ }
1063
+ if (Array.isArray(left) || Array.isArray(right)) {
1064
+ return Array.isArray(left) && Array.isArray(right) && left.length === right.length && left.every((value, index) => projectTrustEqual(value, right[index]));
1065
+ }
1066
+ if (!isPlainTomlRecord(left) || !isPlainTomlRecord(right))
1067
+ return false;
1068
+ const leftKeys = Object.keys(left).sort();
1069
+ const rightKeys = Object.keys(right).sort();
1070
+ return leftKeys.length === rightKeys.length && leftKeys.every((key, index) => key === rightKeys[index] && projectTrustEqual(left[key], right[key]));
1071
+ }
1072
+ function canRenderFlatProjectTrust(entry) {
1073
+ const isValue = (value) => {
1074
+ if (Array.isArray(value))
1075
+ return value.every(isValue);
1076
+ return !isPlainTomlRecord(value);
1077
+ };
1078
+ return Object.values(entry).every(isValue);
1079
+ }
1080
+ function renderProjectTrust(projectKey, entry) {
1081
+ if (!canRenderFlatProjectTrust(entry))
1082
+ return undefined;
1083
+ const serialized = TOML.stringify({ [projectKey]: entry });
1084
+ const body = serialized.split(/\r?\n/).filter((line) => !/^\s*\[/.test(line) && line.trim() !== "").join("\n");
1085
+ return body ? `[projects."${escapeTomlBasicString(projectKey)}"]\n${body}` : undefined;
1086
+ }
1087
+ function splitProjectTrustTrailingEols(source) {
1088
+ const match = source.match(/(?:(?:\r\n|\n))*$/);
1089
+ const trailingEols = match?.[0] ?? "";
1090
+ return { body: source.slice(0, source.length - trailingEols.length), trailingEols };
1091
+ }
1092
+ function appendProjectTrustMarker(base, payload, eol, trailingEols) {
1093
+ if (!payload)
1094
+ return `${splitProjectTrustTrailingEols(base).body}${trailingEols}`;
1095
+ const { body } = splitProjectTrustTrailingEols(base);
1096
+ const separator = body ? `${eol}${eol}` : "";
1097
+ const block = `${OMX_PROJECT_TRUST_START_MARKER}${eol}${payload}${eol}${OMX_PROJECT_TRUST_END_MARKER}`;
1098
+ return `${body}${separator}${block}${trailingEols}`;
1099
+ }
1100
+ function reconcileProjectScopeTrustState(projectConfig, runtimeConfig, projectHooksPath, requireExistingPayload) {
1101
+ const durable = projectTrustSemanticView(projectConfig);
1102
+ const runtime = projectTrustSemanticView(runtimeConfig);
1103
+ if (!durable || !runtime)
1104
+ return projectConfig;
1105
+ if (hasInlineProjectTrustMarkerComment(durable.source) || hasInlineProjectTrustMarkerComment(runtime.source))
1106
+ return projectConfig;
1107
+ const parsedRuntime = safeParseToml(runtime.source);
1108
+ const runtimeInventory = inventoryProjectTrustSource(runtime.source);
1109
+ if (!parsedRuntime || !runtimeInventory)
1110
+ return projectConfig;
1111
+ const marker = projectTrustMarkerRange(durable.source);
1112
+ if (!marker)
1113
+ return projectConfig;
1114
+ const hasMarker = marker.start !== marker.end;
1115
+ const payload = durable.source.slice(marker.payloadStart, marker.payloadEnd);
1116
+ const payloadIsEmpty = projectTrustPayloadIsSemanticallyEmpty(payload);
1117
+ const payloadInventory = hasMarker
1118
+ ? inventoryProjectTrustPayload(payload, projectHooksPath)
1119
+ : { parsed: {}, headers: [] };
1120
+ if (!payloadInventory)
1121
+ return projectConfig;
1122
+ if (requireExistingPayload && (!hasMarker || payloadIsEmpty))
1123
+ return projectConfig;
1124
+ const external = hasMarker
1125
+ ? `${durable.source.slice(0, marker.start)}${durable.source.slice(marker.end)}`
1126
+ : durable.source;
1127
+ const trailingEols = splitProjectTrustTrailingEols(durable.source).trailingEols;
1128
+ const externalInventory = inventoryProjectTrustSource(external);
1129
+ if (!externalInventory)
1130
+ return projectConfig;
1131
+ const projects = runtimeInventory.projects;
1132
+ const externalProjects = externalInventory.projects;
1133
+ const headersBeforeMarker = projectTrustHeaders(durable.source, { start: 0, end: marker.start });
1134
+ const headersAfterMarker = projectTrustHeaders(durable.source, { start: marker.end, end: durable.source.length });
1135
+ if (!headersBeforeMarker || !headersAfterMarker)
1136
+ return projectConfig;
1137
+ const headers = [...headersBeforeMarker, ...headersAfterMarker];
1138
+ for (const key of Object.keys(externalProjects)) {
1139
+ const family = headers.filter((header) => header.path[0] === "projects" && header.path[1] === key);
1140
+ const parents = family.filter((header) => header.path.length === 2);
1141
+ if (parents.length !== 1)
1142
+ return projectConfig;
1143
+ const parentIsBeforeMarker = parents[0].start < marker.start;
1144
+ if (family.some((header) => (header.start < marker.start) !== parentIsBeforeMarker))
1145
+ return projectConfig;
1146
+ if (!projectTrustFamilyIsContiguous(headers, parents[0]))
1147
+ return projectConfig;
1148
+ }
1149
+ const eol = dominantProjectTrustEol(durable.source);
1150
+ const hooks = parsedRuntime.hooks;
1151
+ const hooksState = isPlainTomlRecord(hooks) && isPlainTomlRecord(hooks.state) ? hooks.state : undefined;
1152
+ const nextPayload = reconcileProjectTrustPayload(payload, payloadInventory, externalProjects, projects, hooksState, projectHookTrustState(external), projectHooksPath, eol);
1153
+ if (nextPayload === undefined)
1154
+ return projectConfig;
1155
+ if (!inventoryProjectTrustPayload(nextPayload, projectHooksPath))
1156
+ return projectConfig;
1157
+ const candidate = appendProjectTrustMarker(external, nextPayload, eol, trailingEols);
1158
+ if (!projectTrustCandidateIsSafe(candidate, external, projects))
1159
+ return projectConfig;
1160
+ return `${durable.prefix}${candidate}`;
1161
+ }
1162
+ /** Repairs only a uniquely parseable, nonempty project-sync payload before mirror creation. */
723
1163
  export function repairProjectScopeTrustStateForLaunch(projectConfig, projectHooksPath) {
724
- const syncedTrustBlock = extractMarkerBlockContent(projectConfig, OMX_PROJECT_TRUST_START_MARKER, OMX_PROJECT_TRUST_END_MARKER);
725
- if (!syncedTrustBlock)
1164
+ const durable = projectTrustSemanticView(projectConfig);
1165
+ if (!durable)
1166
+ return projectConfig;
1167
+ const marker = projectTrustMarkerRange(durable.source);
1168
+ if (!marker || marker.start === marker.end)
726
1169
  return projectConfig;
727
- const stripped = stripMarkerBlock(projectConfig, OMX_PROJECT_TRUST_START_MARKER, OMX_PROJECT_TRUST_END_MARKER);
728
- const repaired = syncProjectScopeTrustStateFromRuntime(stripped, syncedTrustBlock, projectHooksPath);
729
- return repaired === stripped ? projectConfig : repaired;
1170
+ const payload = durable.source.slice(marker.payloadStart, marker.payloadEnd);
1171
+ return reconcileProjectScopeTrustState(projectConfig, payload, projectHooksPath, true);
1172
+ }
1173
+ /** Conservatively persists runtime project and hook trust without rewriting external TOML. */
1174
+ export function syncProjectScopeTrustStateFromRuntime(projectConfig, runtimeConfig, projectHooksPath) {
1175
+ return reconcileProjectScopeTrustState(projectConfig, runtimeConfig, projectHooksPath, false);
1176
+ }
1177
+ function isMultilineTomlString(mode) {
1178
+ return mode === "multiline-basic" || mode === "multiline-literal";
730
1179
  }
731
1180
  /**
732
- * Project-scope launches mirror the durable project config.toml into an
733
- * ephemeral runtime CODEX_HOME. Codex writes its workspace-trust ledger and
734
- * hook trust ledger into the runtime config.toml during the session. Without
735
- * persistence, those entries die with the runtime, so Codex prompts to trust
736
- * the workspace and hooks on every launch (issue #2470).
737
- *
738
- * This function extracts only trust-state tables (`[projects."<cwd>"]` and
739
- * `[hooks.state."<projectHooksPath>:..."]`) from the runtime config.toml and
740
- * upserts them into the durable project config.toml inside a marker-fenced
741
- * block, preserving any surrounding user-managed content and comments and
742
- * ignoring Codex's NUX counters or other ephemeral runtime-only writes.
1181
+ * Tracks only lexical boundaries needed to safely associate source lines with
1182
+ * TOML statements. TOML's parser gives us semantics; this prevents source
1183
+ * repair from treating table-like text in a multiline value as syntax.
743
1184
  */
744
- export function syncProjectScopeTrustStateFromRuntime(projectConfig, runtimeConfig, projectHooksPath) {
745
- const parsed = safeParseToml(runtimeConfig);
746
- if (!parsed)
747
- return projectConfig;
748
- const stripped = stripMarkerBlock(projectConfig, OMX_PROJECT_TRUST_START_MARKER, OMX_PROJECT_TRUST_END_MARKER);
749
- const existingHookTrustStateKeys = collectProjectHookTrustStateKeys(stripped);
750
- const trustBlockLines = [];
751
- const projectsTable = parsed.projects;
752
- if (isPlainTomlRecord(projectsTable)) {
753
- for (const [projectKey, entry] of Object.entries(projectsTable).sort(([a], [b]) => a.localeCompare(b))) {
754
- if (!isPlainTomlRecord(entry))
1185
+ function analyzeTomlSource(config) {
1186
+ const lines = config.split(/\r?\n/);
1187
+ const lineStartsOutsideMultiline = [];
1188
+ const lineHasTomlComment = [];
1189
+ let mode;
1190
+ let isUnambiguous = true;
1191
+ for (const line of lines) {
1192
+ lineStartsOutsideMultiline.push(!isMultilineTomlString(mode));
1193
+ let hasComment = false;
1194
+ for (let index = 0; index < line.length; index += 1) {
1195
+ const character = line[index];
1196
+ if (mode === "basic") {
1197
+ if (character === "\\") {
1198
+ index += 1;
1199
+ }
1200
+ else if (character === "\"") {
1201
+ mode = undefined;
1202
+ }
755
1203
  continue;
756
- const serialized = TOML.stringify({ [projectKey]: entry });
757
- const renderedHeader = `[projects."${escapeTomlBasicString(projectKey)}"]`;
758
- const body = serialized
759
- .split(/\r?\n/)
760
- .filter((line) => !/^\s*\[/.test(line) && line.trim() !== "")
761
- .join("\n");
762
- if (body.length === 0)
1204
+ }
1205
+ if (mode === "literal") {
1206
+ if (character === "'")
1207
+ mode = undefined;
763
1208
  continue;
764
- trustBlockLines.push(renderedHeader, body, "");
765
- }
766
- }
767
- const hooksTable = parsed.hooks;
768
- const hooksState = isPlainTomlRecord(hooksTable) ? hooksTable.state : undefined;
769
- if (isPlainTomlRecord(hooksState)) {
770
- for (const [stateKey, entry] of Object.entries(hooksState).sort(([a], [b]) => a.localeCompare(b))) {
771
- if (!isPlainTomlRecord(entry))
1209
+ }
1210
+ if (mode === "multiline-basic" || mode === "multiline-literal") {
1211
+ const delimiter = mode === "multiline-basic" ? "\"" : "'";
1212
+ if (mode === "multiline-basic" && character === "\\") {
1213
+ index += 1;
1214
+ continue;
1215
+ }
1216
+ if (character !== delimiter)
1217
+ continue;
1218
+ let runEnd = index + 1;
1219
+ while (line[runEnd] === delimiter)
1220
+ runEnd += 1;
1221
+ if (runEnd - index >= 3) {
1222
+ // TOML permits one or two delimiter characters immediately before
1223
+ // the closing triple delimiter as string content.
1224
+ mode = undefined;
1225
+ index = runEnd - 1;
1226
+ }
772
1227
  continue;
773
- if (!stateKey.startsWith(`${projectHooksPath}:`))
1228
+ }
1229
+ if (character === "#") {
1230
+ hasComment = true;
1231
+ break;
1232
+ }
1233
+ if (character !== "\"" && character !== "'")
774
1234
  continue;
775
- if (existingHookTrustStateKeys.has(stateKey))
1235
+ const isMultiline = line[index + 1] === character && line[index + 2] === character;
1236
+ if (isMultiline) {
1237
+ mode = character === "\"" ? "multiline-basic" : "multiline-literal";
1238
+ index += 2;
1239
+ }
1240
+ else {
1241
+ mode = character === "\"" ? "basic" : "literal";
1242
+ }
1243
+ }
1244
+ lineHasTomlComment.push(hasComment);
1245
+ if (mode === "basic" || mode === "literal")
1246
+ isUnambiguous = false;
1247
+ }
1248
+ if (isMultilineTomlString(mode))
1249
+ isUnambiguous = false;
1250
+ return { lines, lineStartsOutsideMultiline, lineHasTomlComment, isUnambiguous };
1251
+ }
1252
+ function skipTomlSourceWhitespace(line, index) {
1253
+ let cursor = index;
1254
+ while (line[cursor] === " " || line[cursor] === "\t")
1255
+ cursor += 1;
1256
+ return cursor;
1257
+ }
1258
+ function parseTomlSourceKeySegment(line, index) {
1259
+ const start = skipTomlSourceWhitespace(line, index);
1260
+ const first = line[start];
1261
+ if (!first)
1262
+ return undefined;
1263
+ if (first !== '"' && first !== "'") {
1264
+ const match = /^[A-Za-z0-9_-]+/.exec(line.slice(start));
1265
+ if (!match)
1266
+ return undefined;
1267
+ return { key: match[0], end: start + match[0].length, incomplete: false };
1268
+ }
1269
+ let cursor = start + 1;
1270
+ let escaped = false;
1271
+ for (; cursor < line.length; cursor += 1) {
1272
+ const character = line[cursor];
1273
+ if (first === '"' && escaped) {
1274
+ escaped = false;
1275
+ continue;
1276
+ }
1277
+ if (first === '"' && character === "\\") {
1278
+ escaped = true;
1279
+ continue;
1280
+ }
1281
+ if (character !== first)
1282
+ continue;
1283
+ const token = line.slice(start, cursor + 1);
1284
+ const parsed = safeParseToml(`${token} = true`);
1285
+ const keys = parsed ? Object.keys(parsed) : [];
1286
+ const hasKey = keys.length === 1;
1287
+ return {
1288
+ ...(hasKey ? { key: keys[0] } : {}),
1289
+ end: cursor + 1,
1290
+ incomplete: !hasKey,
1291
+ };
1292
+ }
1293
+ return { end: line.length, incomplete: true };
1294
+ }
1295
+ function isTomlSourceKey(segment, key) {
1296
+ return segment.key === key;
1297
+ }
1298
+ function findTomlSourceAssignmentSeparator(line) {
1299
+ let segment = parseTomlSourceKeySegment(line, 0);
1300
+ if (!segment || segment.incomplete)
1301
+ return undefined;
1302
+ let cursor = skipTomlSourceWhitespace(line, segment.end);
1303
+ while (line[cursor] === ".") {
1304
+ segment = parseTomlSourceKeySegment(line, cursor + 1);
1305
+ if (!segment || segment.incomplete)
1306
+ return undefined;
1307
+ cursor = skipTomlSourceWhitespace(line, segment.end);
1308
+ }
1309
+ return line[cursor] === "=" ? cursor : undefined;
1310
+ }
1311
+ function advancePastInlineTomlValue(line, index) {
1312
+ let braces = 0;
1313
+ let brackets = 0;
1314
+ let quote;
1315
+ let escaped = false;
1316
+ for (let cursor = index; cursor < line.length; cursor += 1) {
1317
+ const character = line[cursor];
1318
+ if (quote) {
1319
+ if (quote === '"' && escaped) {
1320
+ escaped = false;
1321
+ }
1322
+ else if (quote === '"' && character === "\\") {
1323
+ escaped = true;
1324
+ }
1325
+ else if (character === quote) {
1326
+ quote = undefined;
1327
+ }
1328
+ continue;
1329
+ }
1330
+ if (character === '"' || character === "'") {
1331
+ quote = character;
1332
+ continue;
1333
+ }
1334
+ if (character === "#")
1335
+ return line.length;
1336
+ if (character === "{") {
1337
+ braces += 1;
1338
+ continue;
1339
+ }
1340
+ if (character === "}") {
1341
+ if (braces === 0 && brackets === 0)
1342
+ return cursor;
1343
+ braces = Math.max(0, braces - 1);
1344
+ continue;
1345
+ }
1346
+ if (character === "[") {
1347
+ brackets += 1;
1348
+ continue;
1349
+ }
1350
+ if (character === "]") {
1351
+ brackets = Math.max(0, brackets - 1);
1352
+ continue;
1353
+ }
1354
+ if (character === "," && braces === 0 && brackets === 0)
1355
+ return cursor + 1;
1356
+ }
1357
+ return line.length;
1358
+ }
1359
+ function inlineTomlTableMaySpellStateKey(line, index) {
1360
+ let cursor = index;
1361
+ while (cursor < line.length) {
1362
+ cursor = skipTomlSourceWhitespace(line, cursor);
1363
+ // A comment cannot close the surrounding inline table.
1364
+ if (line[cursor] === "#")
1365
+ return true;
1366
+ if (line[cursor] === "}")
1367
+ return false;
1368
+ const segment = parseTomlSourceKeySegment(line, cursor);
1369
+ if (!segment)
1370
+ return true;
1371
+ if (segment.incomplete || isTomlSourceKey(segment, "state"))
1372
+ return true;
1373
+ cursor = skipTomlSourceWhitespace(line, segment.end);
1374
+ if (line[cursor] !== "=")
1375
+ return true;
1376
+ cursor = advancePastInlineTomlValue(line, cursor + 1);
1377
+ if (line[cursor] === "}")
1378
+ return false;
1379
+ }
1380
+ // The unclosed inline table can acquire a state member on a following
1381
+ // unparsed line, so it is not safe to strip the containing marker block.
1382
+ return true;
1383
+ }
1384
+ function lineMaySpellHooksState(line) {
1385
+ let cursor = skipTomlSourceWhitespace(line, 0);
1386
+ if (line[cursor] === "[") {
1387
+ if (line[cursor + 1] === "[") {
1388
+ cursor = skipTomlSourceWhitespace(line, cursor + 2);
1389
+ }
1390
+ else {
1391
+ cursor = skipTomlSourceWhitespace(line, cursor + 1);
1392
+ }
1393
+ }
1394
+ const hooks = parseTomlSourceKeySegment(line, cursor);
1395
+ if (!hooks || !isTomlSourceKey(hooks, "hooks"))
1396
+ return false;
1397
+ cursor = skipTomlSourceWhitespace(line, hooks.end);
1398
+ if (line[cursor] === ".") {
1399
+ const state = parseTomlSourceKeySegment(line, cursor + 1);
1400
+ // A dangling dot can be completed by a following unparsed state key.
1401
+ return state === undefined || state.incomplete || isTomlSourceKey(state, "state");
1402
+ }
1403
+ if (line[cursor] !== "=")
1404
+ return false;
1405
+ cursor = skipTomlSourceWhitespace(line, cursor + 1);
1406
+ // A bare assignment or comment cannot prove that a following unparsed line
1407
+ // does not continue an inline hooks table with a state member.
1408
+ if (cursor >= line.length || line[cursor] === "#")
1409
+ return true;
1410
+ return line[cursor] === "{" && inlineTomlTableMaySpellStateKey(line, cursor + 1);
1411
+ }
1412
+ /**
1413
+ * Detects a potential array-table opener without accepting malformed spacing
1414
+ * or extra opening brackets as TOML syntax. Array tables rooted at `hooks`
1415
+ * have no proof-owned representation, so their first key segment is enough
1416
+ * to retain a managed marker block rather than strip it.
1417
+ */
1418
+ function parseTomlSourceArrayTableOpener(line) {
1419
+ let cursor = skipTomlSourceWhitespace(line, 0);
1420
+ if (line[cursor] !== "[")
1421
+ return undefined;
1422
+ const hasExactAdjacentOpener = line[cursor + 1] === "[";
1423
+ if (hasExactAdjacentOpener) {
1424
+ cursor += 2;
1425
+ }
1426
+ else {
1427
+ cursor = skipTomlSourceWhitespace(line, cursor + 1);
1428
+ if (line[cursor] !== "[")
1429
+ return undefined;
1430
+ cursor += 1;
1431
+ }
1432
+ cursor = skipTomlSourceWhitespace(line, cursor);
1433
+ while (line[cursor] === "[") {
1434
+ cursor = skipTomlSourceWhitespace(line, cursor + 1);
1435
+ }
1436
+ const firstKey = parseTomlSourceKeySegment(line, cursor);
1437
+ return {
1438
+ firstKey,
1439
+ openerOnly: firstKey === undefined &&
1440
+ (cursor >= line.length || line[cursor] === "#"),
1441
+ };
1442
+ }
1443
+ function hasIncompleteTomlSourceHooksKeyPrefix(line) {
1444
+ let cursor = skipTomlSourceWhitespace(line, 0);
1445
+ let tableHeader = false;
1446
+ let arrayTableHeader = false;
1447
+ if (line[cursor] === "[") {
1448
+ tableHeader = true;
1449
+ if (line[cursor + 1] === "[") {
1450
+ arrayTableHeader = true;
1451
+ cursor = skipTomlSourceWhitespace(line, cursor + 2);
1452
+ }
1453
+ else {
1454
+ cursor = skipTomlSourceWhitespace(line, cursor + 1);
1455
+ }
1456
+ }
1457
+ const hooks = parseTomlSourceKeySegment(line, cursor);
1458
+ if (!hooks || !isTomlSourceKey(hooks, "hooks"))
1459
+ return false;
1460
+ cursor = skipTomlSourceWhitespace(line, hooks.end);
1461
+ if (!tableHeader)
1462
+ return cursor >= line.length || line[cursor] === "#";
1463
+ if (line[cursor] !== "]" || (arrayTableHeader && line[cursor + 1] !== "]")) {
1464
+ return true;
1465
+ }
1466
+ const suffix = line.slice(cursor + (arrayTableHeader ? 2 : 1)).trimStart();
1467
+ return suffix.length > 0 && !suffix.startsWith("#");
1468
+ }
1469
+ function isTomlSourceHooksKeyContinuationSeparator(line) {
1470
+ const cursor = skipTomlSourceWhitespace(line, 0);
1471
+ return line[cursor] === "." || line[cursor] === "=" || line[cursor] === "[";
1472
+ }
1473
+ function isTomlSourceBlankOrComment(line) {
1474
+ return /^\s*(?:#.*)?$/.test(line);
1475
+ }
1476
+ function isTomlSourceStandaloneOpeningBracket(line) {
1477
+ const cursor = skipTomlSourceWhitespace(line, 0);
1478
+ if (line[cursor] !== "[")
1479
+ return false;
1480
+ const next = skipTomlSourceWhitespace(line, cursor + 1);
1481
+ return next >= line.length || line[next] === "#";
1482
+ }
1483
+ function firstTomlSourceKeyFollowingOpeningBrackets(line) {
1484
+ let cursor = skipTomlSourceWhitespace(line, 0);
1485
+ while (line[cursor] === "[") {
1486
+ cursor = skipTomlSourceWhitespace(line, cursor + 1);
1487
+ }
1488
+ return parseTomlSourceKeySegment(line, cursor);
1489
+ }
1490
+ function lineMaySpellStateKey(line) {
1491
+ const state = parseTomlSourceKeySegment(line, 0);
1492
+ return state !== undefined && (state.incomplete || isTomlSourceKey(state, "state"));
1493
+ }
1494
+ function isTomlSourceHooksTableHeader(parsed) {
1495
+ const hooks = parsed?.hooks;
1496
+ return parsed !== undefined &&
1497
+ Object.keys(parsed).length === 1 &&
1498
+ isPlainTomlRecord(hooks) &&
1499
+ Object.keys(hooks).length === 0;
1500
+ }
1501
+ function hasUnprovenManagedMarkerHooksStateSyntax(source, managedMarkerRanges, parsedStatementSpans, parsedAssignmentSpans) {
1502
+ const parsedLines = new Set();
1503
+ const parsedStatementStartLines = new Set();
1504
+ for (const span of parsedStatementSpans) {
1505
+ parsedStatementStartLines.add(span.start);
1506
+ for (let index = span.start; index < span.end; index += 1)
1507
+ parsedLines.add(index);
1508
+ }
1509
+ const parsedAssignmentLines = new Set();
1510
+ for (const span of parsedAssignmentSpans) {
1511
+ for (let index = span.start; index < span.end; index += 1) {
1512
+ parsedAssignmentLines.add(index);
1513
+ }
1514
+ }
1515
+ const managedContentLines = new Set();
1516
+ for (const range of managedMarkerRanges) {
1517
+ for (let index = range.start + 1; index < range.end; index += 1) {
1518
+ managedContentLines.add(index);
1519
+ }
1520
+ }
1521
+ let insideHooksTable = false;
1522
+ let hasIncompleteHooksKeyPrefix = false;
1523
+ let hasIncompleteHooksArrayTableOpener = false;
1524
+ for (let index = 0; index < source.lines.length; index += 1) {
1525
+ if (!source.lineStartsOutsideMultiline[index]) {
1526
+ hasIncompleteHooksKeyPrefix = false;
1527
+ hasIncompleteHooksArrayTableOpener = false;
1528
+ continue;
1529
+ }
1530
+ const line = source.lines[index] ?? "";
1531
+ const isParsedAssignmentLine = parsedAssignmentLines.has(index);
1532
+ if (!isParsedAssignmentLine) {
1533
+ const header = parseTomlSourceTableHeader(line);
1534
+ if (header?.parsed) {
1535
+ insideHooksTable = isTomlSourceHooksTableHeader(header.parsed);
1536
+ }
1537
+ }
1538
+ if (!managedContentLines.has(index)) {
1539
+ hasIncompleteHooksKeyPrefix = false;
1540
+ hasIncompleteHooksArrayTableOpener = false;
1541
+ continue;
1542
+ }
1543
+ const isParsedStatementLine = parsedLines.has(index);
1544
+ const isParsedStatementStartLine = parsedStatementStartLines.has(index);
1545
+ if (hasIncompleteHooksKeyPrefix) {
1546
+ if (isTomlSourceBlankOrComment(line))
776
1547
  continue;
777
- const trusted = entry.trusted_hash;
778
- if (typeof trusted !== "string" || trusted.length === 0)
1548
+ if (isTomlSourceHooksKeyContinuationSeparator(line))
1549
+ return true;
1550
+ hasIncompleteHooksKeyPrefix = false;
1551
+ }
1552
+ // Parsed statements can contain nested arrays whose standalone brackets and
1553
+ // quoted "hooks" values must not contribute to malformed-header tracking.
1554
+ if (hasIncompleteHooksArrayTableOpener && !isParsedStatementLine) {
1555
+ if (isTomlSourceBlankOrComment(line))
779
1556
  continue;
780
- trustBlockLines.push(`[hooks.state."${escapeTomlBasicString(stateKey)}"]`, `trusted_hash = "${escapeTomlBasicString(trusted)}"`, "");
1557
+ const firstKey = firstTomlSourceKeyFollowingOpeningBrackets(line);
1558
+ if (firstKey) {
1559
+ if (isTomlSourceKey(firstKey, "hooks"))
1560
+ return true;
1561
+ hasIncompleteHooksArrayTableOpener = false;
1562
+ }
781
1563
  }
1564
+ const arrayTableOpener = !isParsedStatementLine || isParsedStatementStartLine
1565
+ ? parseTomlSourceArrayTableOpener(line)
1566
+ : undefined;
1567
+ if (arrayTableOpener?.firstKey && isTomlSourceKey(arrayTableOpener.firstKey, "hooks")) {
1568
+ return true;
1569
+ }
1570
+ if (arrayTableOpener?.openerOnly && !isParsedStatementLine) {
1571
+ hasIncompleteHooksArrayTableOpener = true;
1572
+ }
1573
+ if (!isParsedStatementLine && isTomlSourceStandaloneOpeningBracket(line)) {
1574
+ hasIncompleteHooksArrayTableOpener = true;
1575
+ }
1576
+ if (isParsedStatementLine)
1577
+ continue;
1578
+ if (lineMaySpellHooksState(line) || (insideHooksTable && lineMaySpellStateKey(line))) {
1579
+ return true;
1580
+ }
1581
+ const hasIncompleteHooksPrefix = hasIncompleteTomlSourceHooksKeyPrefix(line);
1582
+ if (hasIncompleteHooksPrefix && /^\s*\[\[?/.test(line))
1583
+ return true;
1584
+ hasIncompleteHooksKeyPrefix = hasIncompleteHooksPrefix;
782
1585
  }
783
- if (trustBlockLines.length === 0) {
784
- return stripped.length === 0 ? "" : `${stripped}\n`;
785
- }
786
- const block = [
787
- OMX_PROJECT_TRUST_START_MARKER,
788
- ...trustBlockLines,
789
- OMX_PROJECT_TRUST_END_MARKER,
790
- "",
791
- ].join("\n");
792
- if (stripped.length === 0)
793
- return block;
794
- return `${stripped}\n\n${block}`;
1586
+ return false;
795
1587
  }
796
- function decodeTomlBasicString(raw) {
797
- try {
798
- const parsed = TOML.parse(`value = "${raw}"`);
799
- return typeof parsed.value === "string" ? parsed.value : undefined;
1588
+ function managedHookTrustStateExpectations(...states) {
1589
+ const expectations = new Map();
1590
+ for (const state of states) {
1591
+ if (!state)
1592
+ continue;
1593
+ for (const [key, entry] of Object.entries(state)) {
1594
+ const hashes = expectations.get(key) ?? new Set();
1595
+ hashes.add(entry.trusted_hash);
1596
+ expectations.set(key, hashes);
1597
+ }
800
1598
  }
801
- catch {
1599
+ return expectations;
1600
+ }
1601
+ function managedHookTrustCoordinateFamily(key) {
1602
+ const match = /^(.*):([a-z_]+):\d+:\d+$/.exec(key);
1603
+ return match ? `${match[1]}:${match[2]}` : undefined;
1604
+ }
1605
+ function expectedManagedHookTrustHashes(expectations, key, allowCoordinateDrift) {
1606
+ const exact = expectations.get(key);
1607
+ if (exact || !allowCoordinateDrift)
1608
+ return exact;
1609
+ const family = managedHookTrustCoordinateFamily(key);
1610
+ if (!family)
802
1611
  return undefined;
1612
+ const hashes = new Set();
1613
+ for (const [expectedKey, expectedHashes] of expectations) {
1614
+ if (managedHookTrustCoordinateFamily(expectedKey) !== family)
1615
+ continue;
1616
+ for (const hash of expectedHashes)
1617
+ hashes.add(hash);
803
1618
  }
1619
+ return hashes.size > 0 ? hashes : undefined;
804
1620
  }
805
- function parseHooksStateHeader(line) {
806
- const match = line.match(/^\s*\[hooks\.state\."((?:\\.|[^"\\])*)"\]\s*(#.*)?$/);
807
- if (!match)
1621
+ function tomlHooksStateValue(parsed) {
1622
+ if (!parsed)
808
1623
  return undefined;
809
- const key = decodeTomlBasicString(match[1] ?? "");
810
- if (key === undefined)
1624
+ const hooks = parsed.hooks;
1625
+ if (!isPlainTomlRecord(hooks) || !Object.hasOwn(hooks, "state")) {
811
1626
  return undefined;
812
- return { key, hasInlineComment: match[2] !== undefined };
1627
+ }
1628
+ return hooks.state;
813
1629
  }
814
- function isTomlTableHeader(line) {
815
- return /^\s*\[\[?[^\]]+\]?\]\s*(?:#.*)?$/.test(line);
1630
+ function tomlHooksStateEntries(parsed) {
1631
+ const state = tomlHooksStateValue(parsed);
1632
+ return isPlainTomlRecord(state) ? state : undefined;
816
1633
  }
817
- function isExactlyManagedHookTrustBody(bodyLines, expectedHash) {
818
- const nonBlank = bodyLines.filter((line) => line.trim().length > 0);
819
- if (nonBlank.length !== 1)
1634
+ function hasOnlyManagedHookTrustStateField(parsed, key) {
1635
+ if (!parsed || Object.keys(parsed).length !== 1 || !Object.hasOwn(parsed, "hooks")) {
1636
+ return false;
1637
+ }
1638
+ const hooks = parsed.hooks;
1639
+ if (!isPlainTomlRecord(hooks) || Object.keys(hooks).length !== 1 || !Object.hasOwn(hooks, "state")) {
820
1640
  return false;
821
- const expected = expectedHash.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
822
- return new RegExp(`^\\s*trusted_hash\\s*=\\s*"${expected}"\\s*$`).test(nonBlank[0] ?? "");
1641
+ }
1642
+ const state = hooks.state;
1643
+ return isPlainTomlRecord(state) &&
1644
+ Object.keys(state).length === 1 &&
1645
+ Object.hasOwn(state, key);
823
1646
  }
824
- function stripProofManagedCodexHookTrustStateTables(config, managedTrustState) {
825
- if (Object.keys(managedTrustState).length === 0) {
826
- return { config, preservedConflictKeys: new Set() };
1647
+ function parseTomlSourceTableHeader(line) {
1648
+ const start = line.search(/\S/);
1649
+ if (start === -1 || line[start] !== "[")
1650
+ return undefined;
1651
+ const array = line[start + 1] === "[";
1652
+ let quote;
1653
+ let escaped = false;
1654
+ for (let index = start + (array ? 2 : 1); index < line.length; index += 1) {
1655
+ const character = line[index];
1656
+ if (quote === "\"") {
1657
+ if (escaped) {
1658
+ escaped = false;
1659
+ }
1660
+ else if (character === "\\") {
1661
+ escaped = true;
1662
+ }
1663
+ else if (character === quote) {
1664
+ quote = undefined;
1665
+ }
1666
+ continue;
1667
+ }
1668
+ if (quote === "'") {
1669
+ if (character === quote)
1670
+ quote = undefined;
1671
+ continue;
1672
+ }
1673
+ if (character === "\"" || character === "'") {
1674
+ quote = character;
1675
+ continue;
1676
+ }
1677
+ if (character !== "]")
1678
+ continue;
1679
+ const end = array ? index + 2 : index + 1;
1680
+ if (array && line[index + 1] !== "]")
1681
+ return undefined;
1682
+ const suffix = line.slice(end).trimStart();
1683
+ if (suffix.length > 0 && !suffix.startsWith("#"))
1684
+ return undefined;
1685
+ return { parsed: safeParseToml(line.slice(start, end)) };
827
1686
  }
828
- const lines = config.split(/\r?\n/);
829
- const kept = [];
830
- const preservedConflictKeys = new Set();
831
- for (let i = 0; i < lines.length;) {
832
- const header = parseHooksStateHeader(lines[i] ?? "");
833
- if (!header) {
834
- kept.push(lines[i]);
835
- i += 1;
1687
+ return undefined;
1688
+ }
1689
+ function sourceRangeHasTomlComment(source, start, end) {
1690
+ for (let index = start; index < end; index += 1) {
1691
+ if (source.lineHasTomlComment[index])
1692
+ return true;
1693
+ }
1694
+ return false;
1695
+ }
1696
+ function collectMarkerRanges(source, startMarker, endMarker, includeUnterminatedRange = false, inertAssignmentSpans = []) {
1697
+ const isInsideInertAssignment = (line) => inertAssignmentSpans.some((span) => span.start <= line && line < span.end);
1698
+ const ranges = [];
1699
+ for (let start = 0; start < source.lines.length; start += 1) {
1700
+ if (!source.lineStartsOutsideMultiline[start] ||
1701
+ isInsideInertAssignment(start) ||
1702
+ source.lines[start]?.trim() !== startMarker)
1703
+ continue;
1704
+ const end = source.lines.findIndex((line, index) => index > start &&
1705
+ source.lineStartsOutsideMultiline[index] &&
1706
+ !isInsideInertAssignment(index) &&
1707
+ line.trim() === endMarker);
1708
+ const nestedStart = source.lines.findIndex((line, index) => index > start &&
1709
+ source.lineStartsOutsideMultiline[index] &&
1710
+ !isInsideInertAssignment(index) &&
1711
+ line.trim() === startMarker);
1712
+ if (end === -1 || (nestedStart !== -1 && nestedStart < end)) {
1713
+ if (includeUnterminatedRange)
1714
+ ranges.push({ start, end: source.lines.length });
836
1715
  continue;
837
1716
  }
838
- let tableEnd = lines.length;
839
- for (let next = i + 1; next < lines.length; next += 1) {
840
- if (isTomlTableHeader(lines[next] ?? "")) {
841
- tableEnd = next;
842
- break;
1717
+ ranges.push({ start, end });
1718
+ start = end;
1719
+ }
1720
+ return ranges;
1721
+ }
1722
+ function recordManagedMarkerHooksStateSourceSpan(spans, invalidSpans, start, end, parsed, insideManagedMarkerBlock) {
1723
+ if (!insideManagedMarkerBlock)
1724
+ return;
1725
+ const state = tomlHooksStateValue(parsed);
1726
+ if (state === undefined)
1727
+ return;
1728
+ const span = { start, end };
1729
+ spans.push(span);
1730
+ if (!isPlainTomlRecord(state) || Object.keys(state).length === 0) {
1731
+ invalidSpans.push(span);
1732
+ }
1733
+ }
1734
+ function addManagedHookTrustStateSourceRepresentations(representations, markerContainedHooksStateSpans, invalidMarkerContainedHooksStateSpans, source, start, end, parsed, insideManagedMarkerBlock, containingTableScope, fallbackKeys = []) {
1735
+ recordManagedMarkerHooksStateSourceSpan(markerContainedHooksStateSpans, invalidMarkerContainedHooksStateSpans, start, end, parsed, insideManagedMarkerBlock);
1736
+ const state = tomlHooksStateEntries(parsed);
1737
+ const hasComment = sourceRangeHasTomlComment(source, start, end);
1738
+ if (state) {
1739
+ const stateEntryCount = Object.keys(state).length;
1740
+ for (const [key, value] of Object.entries(state)) {
1741
+ const hasExactSemanticScope = hasOnlyManagedHookTrustStateField(parsed, key) &&
1742
+ (!containingTableScope ||
1743
+ (!containingTableScope.hasComment &&
1744
+ hasOnlyManagedHookTrustStateField(containingTableScope.parsed, key)));
1745
+ const entries = representations.get(key) ?? [];
1746
+ entries.push({
1747
+ start,
1748
+ end,
1749
+ stateEntryCount,
1750
+ value,
1751
+ hasComment,
1752
+ hasExactSemanticScope,
1753
+ insideManagedMarkerBlock,
1754
+ });
1755
+ representations.set(key, entries);
1756
+ }
1757
+ return;
1758
+ }
1759
+ for (const key of fallbackKeys) {
1760
+ const entries = representations.get(key) ?? [];
1761
+ entries.push({
1762
+ start,
1763
+ end,
1764
+ stateEntryCount: 0,
1765
+ value: undefined,
1766
+ hasComment,
1767
+ hasExactSemanticScope: false,
1768
+ insideManagedMarkerBlock,
1769
+ });
1770
+ representations.set(key, entries);
1771
+ }
1772
+ }
1773
+ function collectManagedHookTrustStateSourceRepresentations(config) {
1774
+ const source = analyzeTomlSource(config);
1775
+ const { lines } = source;
1776
+ const parsedAssignmentSpans = [];
1777
+ for (let cursor = 0; cursor < lines.length; cursor += 1) {
1778
+ if (!source.lineStartsOutsideMultiline[cursor])
1779
+ continue;
1780
+ const line = lines[cursor] ?? "";
1781
+ if (findTomlSourceAssignmentSeparator(line) === undefined)
1782
+ continue;
1783
+ for (let statementEnd = cursor + 1; statementEnd <= lines.length; statementEnd += 1) {
1784
+ if (!safeParseToml(lines.slice(cursor, statementEnd).join("\n")))
1785
+ continue;
1786
+ parsedAssignmentSpans.push({ start: cursor, end: statementEnd });
1787
+ cursor = statementEnd - 1;
1788
+ break;
1789
+ }
1790
+ }
1791
+ const headers = [];
1792
+ for (const [index, line] of lines.entries()) {
1793
+ if (!source.lineStartsOutsideMultiline[index])
1794
+ continue;
1795
+ const header = parseTomlSourceTableHeader(line);
1796
+ if (parsedAssignmentSpans.some((span) => span.start < index && index < span.end))
1797
+ continue;
1798
+ if (header)
1799
+ headers.push({ index, header });
1800
+ }
1801
+ const boundaries = new Set(headers.map(({ index }) => index));
1802
+ for (const [index, line] of lines.entries()) {
1803
+ if (source.lineStartsOutsideMultiline[index] &&
1804
+ (line.trim() === OMX_HOOK_TRUST_END_MARKER ||
1805
+ line.trim() === OMX_CONFIG_END_MARKER)) {
1806
+ boundaries.add(index);
1807
+ }
1808
+ }
1809
+ const sortedBoundaries = [...boundaries].sort((left, right) => left - right);
1810
+ const representations = new Map();
1811
+ const markerContainedHooksStateSpans = [];
1812
+ const invalidMarkerContainedHooksStateSpans = [];
1813
+ const parsedStatementSpans = [];
1814
+ const managedMarkerRanges = [
1815
+ ...collectMarkerRanges(source, OMX_HOOK_TRUST_START_MARKER, OMX_HOOK_TRUST_END_MARKER, false, parsedAssignmentSpans),
1816
+ ...collectMarkerRanges(source, OMX_CONFIG_START_MARKER, OMX_CONFIG_END_MARKER, true, parsedAssignmentSpans),
1817
+ ];
1818
+ const isInsideManagedMarkerBlock = (start, end) => managedMarkerRanges.some((range) => start > range.start && end <= range.end);
1819
+ const collectAssignments = (start, end, prefix, containingTableScope) => {
1820
+ for (let cursor = start; cursor < end;) {
1821
+ const line = lines[cursor] ?? "";
1822
+ if (!source.lineStartsOutsideMultiline[cursor] ||
1823
+ line.trim().length === 0 ||
1824
+ line.trim() === OMX_HOOK_TRUST_START_MARKER ||
1825
+ line.trim() === OMX_CONFIG_START_MARKER) {
1826
+ cursor += 1;
1827
+ continue;
1828
+ }
1829
+ let parsed;
1830
+ let statementEnd = cursor + 1;
1831
+ for (; statementEnd <= end; statementEnd += 1) {
1832
+ const statement = lines.slice(cursor, statementEnd).join("\n");
1833
+ parsed = safeParseToml(prefix ? `${prefix}\n${statement}` : statement);
1834
+ if (parsed)
1835
+ break;
843
1836
  }
1837
+ if (!parsed) {
1838
+ cursor += 1;
1839
+ continue;
1840
+ }
1841
+ parsedStatementSpans.push({ start: cursor, end: statementEnd });
1842
+ addManagedHookTrustStateSourceRepresentations(representations, markerContainedHooksStateSpans, invalidMarkerContainedHooksStateSpans, source, cursor, statementEnd, parsed, isInsideManagedMarkerBlock(cursor, statementEnd), containingTableScope);
1843
+ cursor = statementEnd;
844
1844
  }
845
- const expectedState = managedTrustState[header.key];
846
- const removable = expectedState !== undefined &&
847
- !header.hasInlineComment &&
848
- isExactlyManagedHookTrustBody(lines.slice(i + 1, tableEnd), expectedState.trusted_hash);
849
- if (removable) {
850
- i = tableEnd;
1845
+ };
1846
+ const rootEnd = sortedBoundaries[0] ?? lines.length;
1847
+ collectAssignments(0, rootEnd, undefined);
1848
+ for (const { index, header } of headers) {
1849
+ const boundaryIndex = sortedBoundaries.findIndex((boundary) => boundary === index);
1850
+ const end = boundaryIndex === -1
1851
+ ? lines.length
1852
+ : (sortedBoundaries[boundaryIndex + 1] ?? lines.length);
1853
+ const tableScope = {
1854
+ parsed: safeParseToml(lines.slice(index, end).join("\n")),
1855
+ hasComment: sourceRangeHasTomlComment(source, index, end),
1856
+ };
1857
+ if (tableScope.parsed)
1858
+ parsedStatementSpans.push({ start: index, end });
1859
+ const insideManagedMarkerBlock = isInsideManagedMarkerBlock(index, end);
1860
+ recordManagedMarkerHooksStateSourceSpan(markerContainedHooksStateSpans, invalidMarkerContainedHooksStateSpans, index, end, tableScope.parsed, insideManagedMarkerBlock);
1861
+ const headerState = tomlHooksStateEntries(header.parsed);
1862
+ const directKeys = Object.keys(headerState ?? {});
1863
+ if (directKeys.length > 0) {
1864
+ addManagedHookTrustStateSourceRepresentations(representations, markerContainedHooksStateSpans, invalidMarkerContainedHooksStateSpans, source, index, end, tableScope.parsed, insideManagedMarkerBlock, undefined, directKeys);
851
1865
  continue;
852
1866
  }
853
- if (expectedState !== undefined) {
854
- preservedConflictKeys.add(header.key);
1867
+ collectAssignments(index + 1, end, lines[index], tableScope);
1868
+ }
1869
+ return {
1870
+ source,
1871
+ representations,
1872
+ managedMarkerRanges,
1873
+ markerContainedHooksStateSpans,
1874
+ invalidMarkerContainedHooksStateSpans,
1875
+ parsedStatementSpans,
1876
+ parsedAssignmentSpans,
1877
+ };
1878
+ }
1879
+ function isExactlyManagedHookTrustStateValue(value, expectedHashes) {
1880
+ return isPlainTomlRecord(value) &&
1881
+ Object.keys(value).length === 1 &&
1882
+ Object.hasOwn(value, "trusted_hash") &&
1883
+ typeof value.trusted_hash === "string" &&
1884
+ expectedHashes.has(value.trusted_hash);
1885
+ }
1886
+ function managedMarkerTrustStateConflicts(source, representations, managedMarkerRanges, markerContainedHooksStateSpans, invalidMarkerContainedHooksStateSpans, parsedStatementSpans, parsedAssignmentSpans, expectations) {
1887
+ const conflicts = new Set();
1888
+ const isInsideManagedMarkerRange = (span) => managedMarkerRanges.some((range) => span.start > range.start && span.end <= range.end);
1889
+ if (invalidMarkerContainedHooksStateSpans.some(isInsideManagedMarkerRange) ||
1890
+ hasUnprovenManagedMarkerHooksStateSyntax(source, managedMarkerRanges, [...markerContainedHooksStateSpans, ...parsedStatementSpans], parsedAssignmentSpans)) {
1891
+ conflicts.add("marker-contained hooks.state");
1892
+ }
1893
+ for (const [key, sourceRepresentations] of representations) {
1894
+ for (const representation of sourceRepresentations) {
1895
+ if (!isInsideManagedMarkerRange(representation))
1896
+ continue;
1897
+ const expectedHashes = expectedManagedHookTrustHashes(expectations, key, true);
1898
+ const isExactOwnedRepresentation = expectedHashes !== undefined &&
1899
+ representation.stateEntryCount === 1 &&
1900
+ !representation.hasComment &&
1901
+ representation.hasExactSemanticScope &&
1902
+ isExactlyManagedHookTrustStateValue(representation.value, expectedHashes);
1903
+ if (!source.isUnambiguous || !isExactOwnedRepresentation)
1904
+ conflicts.add(key);
855
1905
  }
856
- kept.push(...lines.slice(i, tableEnd));
857
- i = tableEnd;
858
1906
  }
859
- return { config: kept.join("\n"), preservedConflictKeys };
1907
+ return conflicts;
860
1908
  }
861
- function stripManagedCodexHookTrustStateWithResult(config, options = {}) {
862
- const lines = config.split(/\r?\n/);
863
- const kept = [];
864
- for (let i = 0; i < lines.length;) {
865
- const trimmed = lines[i].trim();
866
- if (trimmed !== OMX_HOOK_TRUST_START_MARKER) {
867
- kept.push(lines[i]);
868
- i += 1;
1909
+ function removeEmptyManagedHookTrustStateMarkerBlocks(source, removed) {
1910
+ const { lines } = source;
1911
+ for (let start = 0; start < lines.length; start += 1) {
1912
+ if (!source.lineStartsOutsideMultiline[start] ||
1913
+ lines[start]?.trim() !== OMX_HOOK_TRUST_START_MARKER)
1914
+ continue;
1915
+ const end = lines.findIndex((line, index) => index > start &&
1916
+ source.lineStartsOutsideMultiline[index] &&
1917
+ line.trim() === OMX_HOOK_TRUST_END_MARKER);
1918
+ const nestedStart = lines.findIndex((line, index) => index > start &&
1919
+ source.lineStartsOutsideMultiline[index] &&
1920
+ line.trim() === OMX_HOOK_TRUST_START_MARKER);
1921
+ if (end === -1 || (nestedStart !== -1 && nestedStart < end))
869
1922
  continue;
1923
+ let onlyOwnedMarkerContent = true;
1924
+ for (let index = start + 1; index < end; index += 1) {
1925
+ const trimmed = lines[index].trim();
1926
+ if (removed.has(index) ||
1927
+ trimmed.length === 0 ||
1928
+ trimmed === "# Trusts only setup-managed native hook wrappers.")
1929
+ continue;
1930
+ onlyOwnedMarkerContent = false;
1931
+ break;
870
1932
  }
871
- const nextEndIdx = lines.findIndex((line, index) => index > i && line.trim() === OMX_HOOK_TRUST_END_MARKER);
872
- const nextStartIdx = lines.findIndex((line, index) => index > i && line.trim() === OMX_HOOK_TRUST_START_MARKER);
873
- if (nextEndIdx === -1 || (nextStartIdx !== -1 && nextStartIdx < nextEndIdx)) {
874
- kept.push(lines[i]);
875
- i += 1;
1933
+ if (!onlyOwnedMarkerContent)
876
1934
  continue;
1935
+ removed.add(start);
1936
+ removed.add(end);
1937
+ for (let index = start + 1; index < end; index += 1) {
1938
+ if (lines[index]?.trim() === "# Trusts only setup-managed native hook wrappers.") {
1939
+ removed.add(index);
1940
+ }
1941
+ }
1942
+ start = end;
1943
+ }
1944
+ }
1945
+ function stripProofManagedCodexHookTrustStateTables(config, expectations) {
1946
+ const { source, representations, managedMarkerRanges, markerContainedHooksStateSpans, invalidMarkerContainedHooksStateSpans, parsedStatementSpans, parsedAssignmentSpans, } = collectManagedHookTrustStateSourceRepresentations(config);
1947
+ const preservedConflictKeys = managedMarkerTrustStateConflicts(source, representations, managedMarkerRanges, markerContainedHooksStateSpans, invalidMarkerContainedHooksStateSpans, parsedStatementSpans, parsedAssignmentSpans, expectations);
1948
+ if (!source.isUnambiguous) {
1949
+ return { config, preservedConflictKeys };
1950
+ }
1951
+ const { lines } = source;
1952
+ const removals = new Map();
1953
+ const parsedConfigState = tomlHooksStateEntries(safeParseToml(config));
1954
+ for (const key of Object.keys(parsedConfigState ?? {})) {
1955
+ if (expectations.has(key) && !representations.has(key)) {
1956
+ preservedConflictKeys.add(key);
1957
+ }
1958
+ }
1959
+ for (const [key, sourceRepresentations] of representations) {
1960
+ const isExactOwnedRepresentation = (representation) => {
1961
+ const expectedHashes = expectedManagedHookTrustHashes(expectations, key, representation.insideManagedMarkerBlock);
1962
+ return expectedHashes !== undefined &&
1963
+ representation.stateEntryCount === 1 &&
1964
+ !representation.hasComment &&
1965
+ representation.hasExactSemanticScope &&
1966
+ isExactlyManagedHookTrustStateValue(representation.value, expectedHashes);
1967
+ };
1968
+ if (!sourceRepresentations.some((representation) => expectedManagedHookTrustHashes(expectations, key, representation.insideManagedMarkerBlock) !== undefined))
1969
+ continue;
1970
+ const mayRemoveAll = sourceRepresentations.length === 1
1971
+ ? isExactOwnedRepresentation(sourceRepresentations[0])
1972
+ : sourceRepresentations.every((representation) => representation.insideManagedMarkerBlock &&
1973
+ isExactOwnedRepresentation(representation));
1974
+ if (!mayRemoveAll) {
1975
+ preservedConflictKeys.add(key);
1976
+ continue;
1977
+ }
1978
+ for (const representation of sourceRepresentations) {
1979
+ removals.set(`${representation.start}:${representation.end}`, {
1980
+ start: representation.start,
1981
+ end: representation.end,
1982
+ });
1983
+ }
1984
+ }
1985
+ if (removals.size === 0) {
1986
+ return { config, preservedConflictKeys };
1987
+ }
1988
+ const removed = new Set();
1989
+ for (const range of removals.values()) {
1990
+ for (let index = range.start; index < range.end; index += 1) {
1991
+ removed.add(index);
877
1992
  }
878
- i = nextEndIdx + 1;
879
1993
  }
880
- const withoutFenced = kept.join("\n");
881
- const proofStripped = options.managedTrustState
882
- ? stripProofManagedCodexHookTrustStateTables(withoutFenced, options.managedTrustState)
883
- : { config: withoutFenced, preservedConflictKeys: new Set() };
1994
+ removeEmptyManagedHookTrustStateMarkerBlocks(source, removed);
884
1995
  return {
885
- config: proofStripped.config.replace(/\n{3,}/g, "\n\n").trimEnd(),
886
- preservedConflictKeys: proofStripped.preservedConflictKeys,
1996
+ config: lines.filter((_, index) => !removed.has(index)).join("\n").trimEnd(),
1997
+ preservedConflictKeys,
887
1998
  };
888
1999
  }
2000
+ function stripManagedCodexHookTrustStateForRefresh(config, priorManagedTrustState, finalManagedTrustState) {
2001
+ return stripProofManagedCodexHookTrustStateTables(config, managedHookTrustStateExpectations(priorManagedTrustState, finalManagedTrustState));
2002
+ }
2003
+ function rejectManagedCodexHookTrustStateConflicts(conflicts) {
2004
+ if (conflicts.size === 0)
2005
+ return;
2006
+ const keys = [...conflicts].sort((left, right) => left.localeCompare(right));
2007
+ throw new ManagedCodexHooksPlanError("managed_trust_key_conflict", `Refusing to replace existing Codex hook trust state for ${keys.join(", ")}. Remove or reconcile the conflicting [hooks.state] entry before running setup.`, { keys });
2008
+ }
889
2009
  export function stripManagedCodexHookTrustState(config, options = {}) {
890
- return stripManagedCodexHookTrustStateWithResult(config, options).config;
2010
+ const stripped = stripManagedCodexHookTrustStateForRefresh(config, options.priorManagedHookTrustState, options.managedTrustState ?? {});
2011
+ rejectManagedCodexHookTrustStateConflicts(stripped.preservedConflictKeys);
2012
+ return stripped.config;
891
2013
  }
892
- function renderManagedCodexHookTrustToml(managedTrustState, excludedKeys = new Set()) {
2014
+ function renderManagedCodexHookTrustToml(managedTrustState) {
893
2015
  return Object.entries(managedTrustState)
894
- .filter(([key]) => !excludedKeys.has(key))
895
2016
  .sort(([left], [right]) => left.localeCompare(right))
896
2017
  .flatMap(([key, hookState]) => [
897
2018
  `[hooks.state."${escapeTomlBasicString(key)}"]`,
@@ -901,21 +2022,92 @@ function renderManagedCodexHookTrustToml(managedTrustState, excludedKeys = new S
901
2022
  .join("\n")
902
2023
  .trimEnd();
903
2024
  }
904
- function buildManagedCodexHookTrustStateForConfig(codexHooksFile, pkgRoot, options = {}) {
2025
+ function identicalLegacyHookTrustStateEntry(existing, expected) {
2026
+ if (!isPlainTomlRecord(existing))
2027
+ return false;
2028
+ if (!Object.hasOwn(existing, "trusted_hash") ||
2029
+ typeof existing.trusted_hash !== "string" ||
2030
+ existing.trusted_hash !== expected.trusted_hash ||
2031
+ Object.keys(existing).some((key) => key !== "trusted_hash" && key !== "enabled")) {
2032
+ return false;
2033
+ }
2034
+ return Object.hasOwn(existing, "enabled") === Object.hasOwn(expected, "enabled") &&
2035
+ existing.enabled === expected.enabled;
2036
+ }
2037
+ function legacyHookTrustStateConflict(keys, reason) {
2038
+ throw new ManagedCodexHooksPlanError("managed_trust_key_conflict", `Refusing to migrate legacy Codex hook trust state into conflicting config.toml entries for ${keys.join(", ")}.`, { keys: [...keys], ...(reason ? { reason } : {}) });
2039
+ }
2040
+ function appendLegacyHookTrustState(config, legacyTrustState) {
2041
+ if (!legacyTrustState || Object.keys(legacyTrustState).length === 0) {
2042
+ return config;
2043
+ }
2044
+ const parsed = safeParseToml(config);
2045
+ const keys = Object.keys(legacyTrustState).sort((left, right) => left.localeCompare(right));
2046
+ if (!parsed)
2047
+ legacyHookTrustStateConflict(keys, "config_parse_failure");
2048
+ const hooks = parsed.hooks;
2049
+ if (hooks !== undefined && !isPlainTomlRecord(hooks)) {
2050
+ legacyHookTrustStateConflict(keys, "hooks_table_invalid");
2051
+ }
2052
+ const hookState = isPlainTomlRecord(hooks) ? hooks.state : undefined;
2053
+ if (hookState !== undefined && !isPlainTomlRecord(hookState)) {
2054
+ legacyHookTrustStateConflict(keys, "hooks_state_table_invalid");
2055
+ }
2056
+ const existingTrustState = isPlainTomlRecord(hookState) ? hookState : {};
2057
+ const entriesToAppend = Object.entries(legacyTrustState)
2058
+ .filter(([key]) => !Object.hasOwn(existingTrustState, key));
2059
+ const conflictingKeys = Object.entries(legacyTrustState)
2060
+ .filter(([key, expected]) => Object.hasOwn(existingTrustState, key) &&
2061
+ !identicalLegacyHookTrustStateEntry(existingTrustState[key], expected))
2062
+ .map(([key]) => key)
2063
+ .sort((left, right) => left.localeCompare(right));
2064
+ if (conflictingKeys.length > 0)
2065
+ legacyHookTrustStateConflict(conflictingKeys);
2066
+ const trustToml = entriesToAppend
2067
+ .sort(([left], [right]) => left.localeCompare(right))
2068
+ .flatMap(([key, state]) => [
2069
+ `[hooks.state."${escapeTomlBasicString(key)}"]`,
2070
+ `trusted_hash = "${escapeTomlBasicString(state.trusted_hash)}"`,
2071
+ ...(typeof state.enabled === "boolean" ? [`enabled = ${state.enabled}`] : []),
2072
+ "",
2073
+ ])
2074
+ .join("\n")
2075
+ .trimEnd();
2076
+ if (!trustToml)
2077
+ return config;
2078
+ const base = config.trimEnd();
2079
+ return [
2080
+ base,
2081
+ base ? "" : null,
2082
+ "# Migrated from legacy hooks.json state; kept in Codex config.toml because Codex 0.140 rejects top-level hooks.json state.",
2083
+ trustToml,
2084
+ "",
2085
+ ].filter((line) => line !== null).join("\n");
2086
+ }
2087
+ function managedCodexHookOptionsFromMergeOptions(options) {
2088
+ return {
2089
+ codexHomeDir: options.codexHomeDir,
2090
+ platform: options.hookCommandPlatform,
2091
+ hooksContent: options.codexHooksContent,
2092
+ };
2093
+ }
2094
+ function buildManagedCodexHookTrustStateForConfig(codexHooksFile, pkgRoot, options = {}, precomputedTrustState) {
2095
+ if (precomputedTrustState)
2096
+ return precomputedTrustState;
905
2097
  if (!codexHooksFile)
906
2098
  return {};
907
2099
  return buildManagedCodexHookTrustState(codexHooksFile, pkgRoot, options);
908
2100
  }
909
2101
  export function upsertManagedCodexHookTrustState(config, pkgRoot, codexHooksFile, options = {}) {
910
- const managedTrustState = buildManagedCodexHookTrustStateForConfig(codexHooksFile, pkgRoot, options);
911
- const strippedResult = stripManagedCodexHookTrustStateWithResult(config, {
912
- managedTrustState,
913
- });
914
- const stripped = strippedResult.config;
915
- const hookTrustToml = renderManagedCodexHookTrustToml(managedTrustState, strippedResult.preservedConflictKeys);
916
- if (!hookTrustToml)
917
- return `${stripped}\n`;
918
- return [
2102
+ const managedTrustState = buildManagedCodexHookTrustStateForConfig(codexHooksFile, pkgRoot, options, options.managedTrustState);
2103
+ const strippedResult = stripManagedCodexHookTrustStateForRefresh(config, options.priorManagedHookTrustState, managedTrustState);
2104
+ rejectManagedCodexHookTrustStateConflicts(strippedResult.preservedConflictKeys);
2105
+ const stripped = strippedResult.config.trimEnd();
2106
+ const hookTrustToml = renderManagedCodexHookTrustToml(managedTrustState);
2107
+ if (!hookTrustToml) {
2108
+ return appendLegacyHookTrustState(`${stripped}\n`, options.legacyHookTrustState);
2109
+ }
2110
+ return appendLegacyHookTrustState([
919
2111
  stripped,
920
2112
  "",
921
2113
  OMX_HOOK_TRUST_START_MARKER,
@@ -923,7 +2115,7 @@ export function upsertManagedCodexHookTrustState(config, pkgRoot, codexHooksFile
923
2115
  hookTrustToml,
924
2116
  OMX_HOOK_TRUST_END_MARKER,
925
2117
  "",
926
- ].filter((line, index) => index !== 0 || line.length > 0).join("\n");
2118
+ ].filter((line, index) => index !== 0 || line.length > 0).join("\n"), options.legacyHookTrustState);
927
2119
  }
928
2120
  export function upsertPluginModeRuntimeFeatureFlags(config, codexHookFeatureFlag = DEFAULT_CODEX_HOOK_FEATURE_FLAG, options = {}) {
929
2121
  const lines = config.split(/\r?\n/);
@@ -936,6 +2128,9 @@ export function upsertPluginModeRuntimeFeatureFlags(config, codexHookFeatureFlag
936
2128
  const featureBlock = [
937
2129
  "[features]",
938
2130
  hookFeatureFlagLine,
2131
+ ...(options.pluginScopedHooks && options.preserveNativeHooks
2132
+ ? [formatCodexHookFeatureFlagLine(codexHookFeatureFlag)]
2133
+ : []),
939
2134
  "goals = true",
940
2135
  "",
941
2136
  ].join("\n");
@@ -959,9 +2154,15 @@ export function upsertPluginModeRuntimeFeatureFlags(config, codexHookFeatureFlag
959
2154
  sectionEnd -= 1;
960
2155
  }
961
2156
  }
962
- ({ sectionEnd } = options.pluginScopedHooks
963
- ? upsertPluginScopedHookFeatureFlagInSection(lines, featuresStart, sectionEnd)
964
- : upsertCodexHookFeatureFlagInSection(lines, featuresStart, sectionEnd, codexHookFeatureFlag));
2157
+ if (options.pluginScopedHooks) {
2158
+ ({ sectionEnd } = upsertPluginScopedHookFeatureFlagInSection(lines, featuresStart, sectionEnd));
2159
+ if (options.preserveNativeHooks) {
2160
+ ({ sectionEnd } = upsertCodexHookFeatureFlagInSection(lines, featuresStart, sectionEnd, codexHookFeatureFlag));
2161
+ }
2162
+ }
2163
+ else {
2164
+ ({ sectionEnd } = upsertCodexHookFeatureFlagInSection(lines, featuresStart, sectionEnd, codexHookFeatureFlag));
2165
+ }
965
2166
  let goalsIdx = -1;
966
2167
  for (let i = featuresStart + 1; i < sectionEnd; i++) {
967
2168
  if (/^\s*goals\s*=/.test(lines[i])) {
@@ -1425,35 +2626,45 @@ function upsertTuiStatusLine(config, preset = DEFAULT_STATUS_LINE_PRESET, option
1425
2626
  // ---------------------------------------------------------------------------
1426
2627
  // OMX [table] sections block (appended at end of file)
1427
2628
  // ---------------------------------------------------------------------------
1428
- export function stripExistingOmxBlocks(config) {
1429
- let cleaned = config;
1430
- let removed = 0;
1431
- while (true) {
1432
- const markerIdx = cleaned.indexOf(OMX_CONFIG_MARKER);
1433
- if (markerIdx < 0)
1434
- break;
1435
- let blockStart = cleaned.lastIndexOf("\n", markerIdx);
1436
- blockStart = blockStart >= 0 ? blockStart + 1 : 0;
1437
- const previousLineEnd = blockStart - 1;
1438
- if (previousLineEnd >= 0) {
1439
- const previousLineStart = cleaned.lastIndexOf("\n", previousLineEnd - 1);
1440
- const previousLine = cleaned.slice(previousLineStart + 1, previousLineEnd);
1441
- if (/^# =+$/.test(previousLine.trim())) {
1442
- blockStart = previousLineStart >= 0 ? previousLineStart + 1 : 0;
1443
- }
1444
- }
1445
- let blockEnd = cleaned.length;
1446
- const endIdx = cleaned.indexOf(OMX_CONFIG_END_MARKER, markerIdx);
1447
- if (endIdx >= 0) {
1448
- const endLineBreak = cleaned.indexOf("\n", endIdx);
1449
- blockEnd = endLineBreak >= 0 ? endLineBreak + 1 : cleaned.length;
1450
- }
1451
- const before = cleaned.slice(0, blockStart).trimEnd();
1452
- const after = cleaned.slice(blockEnd).trimStart();
1453
- cleaned = [before, after].filter(Boolean).join("\n\n");
1454
- removed += 1;
1455
- }
1456
- return { cleaned, removed };
2629
+ export function stripExistingOmxBlocks(config, options = {}) {
2630
+ const inventory = collectManagedHookTrustStateSourceRepresentations(config);
2631
+ const { source } = inventory;
2632
+ const markerRanges = collectMarkerRanges(source, OMX_CONFIG_START_MARKER, OMX_CONFIG_END_MARKER, false, inventory.parsedAssignmentSpans);
2633
+ if (markerRanges.length === 0)
2634
+ return { cleaned: config, removed: 0 };
2635
+ const conflicts = managedMarkerTrustStateConflicts(source, inventory.representations, markerRanges, inventory.markerContainedHooksStateSpans, inventory.invalidMarkerContainedHooksStateSpans, inventory.parsedStatementSpans, inventory.parsedAssignmentSpans, managedHookTrustStateExpectations(options.priorManagedHookTrustState, options.managedTrustState));
2636
+ rejectManagedCodexHookTrustStateConflicts(conflicts);
2637
+ if (!source.isUnambiguous)
2638
+ return { cleaned: config, removed: 0 };
2639
+ const lines = sourceLines(config);
2640
+ if (markerRanges.some((range) => !lines[range.start] || !lines[range.end])) {
2641
+ return { cleaned: config, removed: 0 };
2642
+ }
2643
+ const removalRanges = markerRanges.map((range) => {
2644
+ const startLine = lines[range.start];
2645
+ const endLine = lines[range.end];
2646
+ const previousLine = lines[range.start - 1];
2647
+ const start = previousLine && /^# =+$/.test(previousLine.content.trim())
2648
+ ? previousLine.start
2649
+ : startLine.start;
2650
+ return { start, end: endLine.end };
2651
+ });
2652
+ const segments = [];
2653
+ let cursor = 0;
2654
+ for (const range of removalRanges) {
2655
+ segments.push(config.slice(cursor, range.start));
2656
+ cursor = range.end;
2657
+ }
2658
+ segments.push(config.slice(cursor));
2659
+ const cleaned = segments
2660
+ .map((segment, index) => index === 0
2661
+ ? segment.trimEnd()
2662
+ : index === segments.length - 1
2663
+ ? segment.trimStart()
2664
+ : segment.trim())
2665
+ .filter(Boolean)
2666
+ .join("\n\n");
2667
+ return { cleaned, removed: markerRanges.length };
1457
2668
  }
1458
2669
  export function stripExistingSharedMcpRegistryBlock(config) {
1459
2670
  let cleaned = config;
@@ -1688,7 +2899,7 @@ export function mergeSharedMcpRegistryBlock(config, servers, sourcePath) {
1688
2899
  * OMX table-section block (MCP servers, TUI).
1689
2900
  * Contains ONLY [table] sections — no bare keys.
1690
2901
  */
1691
- function getOmxTablesBlock(pkgRoot, includeTui = true, statusLinePreset = DEFAULT_STATUS_LINE_PRESET, codexHooksFile, hookOptions = {}, includeFirstPartyMcp = false, excludedHookTrustStateKeys = new Set()) {
2902
+ function getOmxTablesBlock(pkgRoot, includeTui = true, statusLinePreset = DEFAULT_STATUS_LINE_PRESET, codexHooksFile, hookOptions = {}, managedHookTrustState, includeFirstPartyMcp = false) {
1692
2903
  const lines = [
1693
2904
  "",
1694
2905
  "# ============================================================",
@@ -1711,7 +2922,7 @@ function getOmxTablesBlock(pkgRoot, includeTui = true, statusLinePreset = DEFAUL
1711
2922
  }
1712
2923
  }
1713
2924
  }
1714
- const hookTrustToml = renderManagedCodexHookTrustToml(buildManagedCodexHookTrustStateForConfig(codexHooksFile, pkgRoot, hookOptions), excludedHookTrustStateKeys);
2925
+ const hookTrustToml = renderManagedCodexHookTrustToml(buildManagedCodexHookTrustStateForConfig(codexHooksFile, pkgRoot, hookOptions, managedHookTrustState));
1715
2926
  if (hookTrustToml) {
1716
2927
  lines.push("");
1717
2928
  lines.push("# OMX-owned Codex hook trust state");
@@ -1750,6 +2961,10 @@ function getOmxTablesBlock(pkgRoot, includeTui = true, statusLinePreset = DEFAUL
1750
2961
  */
1751
2962
  export function buildMergedConfig(existingConfig, pkgRoot, options = {}) {
1752
2963
  let existing = stripOmxSeededBehavioralDefaults(existingConfig);
2964
+ const hookOptions = managedCodexHookOptionsFromMergeOptions(options);
2965
+ const managedTrustState = buildManagedCodexHookTrustStateForConfig(options.codexHooksFile, pkgRoot, hookOptions, options.managedHookTrustState);
2966
+ const preflightHookTrustStrip = stripManagedCodexHookTrustStateForRefresh(existing, options.priorManagedHookTrustState, managedTrustState);
2967
+ rejectManagedCodexHookTrustStateConflicts(preflightHookTrustStrip.preservedConflictKeys);
1753
2968
  const preservedFirstPartyMcpSections = options.preserveExistingFirstPartyMcp === true &&
1754
2969
  options.includeFirstPartyMcp !== true
1755
2970
  ? extractFirstPartyOmxMcpSections(existing)
@@ -1758,7 +2973,10 @@ export function buildMergedConfig(existingConfig, pkgRoot, options = {}) {
1758
2973
  const statusLinePreset = options.statusLinePreset ?? DEFAULT_STATUS_LINE_PRESET;
1759
2974
  const customizedManagedTuiSections = extractCustomizedTuiSectionsFromOmxBlocks(existing);
1760
2975
  if (existing.includes(OMX_CONFIG_MARKER)) {
1761
- const stripped = stripExistingOmxBlocks(existing);
2976
+ const stripped = stripExistingOmxBlocks(existing, {
2977
+ managedTrustState,
2978
+ priorManagedHookTrustState: options.priorManagedHookTrustState,
2979
+ });
1762
2980
  existing = stripped.cleaned;
1763
2981
  if (customizedManagedTuiSections.length > 0) {
1764
2982
  existing = `${existing.trimEnd()}\n\n${customizedManagedTuiSections.join("\n\n")}\n`;
@@ -1777,13 +2995,8 @@ export function buildMergedConfig(existingConfig, pkgRoot, options = {}) {
1777
2995
  existing = `${`notify = ${formatTomlStringArray(userNotifyToPreserve)}`}\n${existing.trimStart()}`;
1778
2996
  }
1779
2997
  existing = stripOrphanedManagedNotify(existing, pkgRoot).trimStart();
1780
- const managedTrustState = buildManagedCodexHookTrustStateForConfig(options.codexHooksFile, pkgRoot, {
1781
- codexHomeDir: options.codexHomeDir,
1782
- platform: options.hookCommandPlatform,
1783
- });
1784
- const hookTrustStrip = stripManagedCodexHookTrustStateWithResult(existing, {
1785
- managedTrustState,
1786
- });
2998
+ const hookTrustStrip = stripManagedCodexHookTrustStateForRefresh(existing, options.priorManagedHookTrustState, managedTrustState);
2999
+ rejectManagedCodexHookTrustStateConflicts(hookTrustStrip.preservedConflictKeys);
1787
3000
  existing = hookTrustStrip.config;
1788
3001
  if (options.modelOverride) {
1789
3002
  existing = stripRootLevelKeys(existing, ["model"]);
@@ -1803,17 +3016,15 @@ export function buildMergedConfig(existingConfig, pkgRoot, options = {}) {
1803
3016
  const topLines = getOmxTopLevelLines(pkgRoot, existing, options.modelOverride, options.notifyCommand === undefined
1804
3017
  ? getDefaultNotifyCommand(pkgRoot)
1805
3018
  : options.notifyCommand);
1806
- const tablesBlock = getOmxTablesBlock(pkgRoot, includeTui && !tuiUpsert.hadExistingTui, statusLinePreset, options.codexHooksFile, {
1807
- codexHomeDir: options.codexHomeDir,
1808
- platform: options.hookCommandPlatform,
1809
- }, options.includeFirstPartyMcp === true, hookTrustStrip.preservedConflictKeys);
3019
+ const tablesBlock = getOmxTablesBlock(pkgRoot, includeTui && !tuiUpsert.hadExistingTui, statusLinePreset, options.codexHooksFile, hookOptions, managedTrustState, options.includeFirstPartyMcp === true);
1810
3020
  const sharedRegistryBlock = getSharedMcpRegistryBlock(options.sharedMcpServers ?? [], options.sharedMcpRegistrySource, existing);
1811
3021
  let body = existing.trim();
1812
3022
  if (sharedRegistryBlock) {
1813
3023
  body = body ? `${body}\n\n${sharedRegistryBlock}` : sharedRegistryBlock;
1814
3024
  }
1815
3025
  const bodySeparator = body.length > 0 && !/^\s*\[/.test(body) ? "\n" : "\n\n";
1816
- return addDefaultLauncherMcpStartupTimeouts(topLines.join("\n") + bodySeparator + body + "\n" + tablesBlock);
3026
+ const merged = addDefaultLauncherMcpStartupTimeouts(topLines.join("\n") + bodySeparator + body + "\n" + tablesBlock);
3027
+ return appendLegacyHookTrustState(merged, options.legacyHookTrustState);
1817
3028
  }
1818
3029
  /**
1819
3030
  * Detect and repair upgrade-era managed config incompatibilities in config.toml.
@@ -1826,6 +3037,33 @@ export function buildMergedConfig(existingConfig, pkgRoot, options = {}) {
1826
3037
  *
1827
3038
  * Returns `true` if a repair was performed.
1828
3039
  */
3040
+ function managedHookTrustProofRequiredForRepair(config) {
3041
+ const inventory = collectManagedHookTrustStateSourceRepresentations(config);
3042
+ return managedMarkerTrustStateConflicts(inventory.source, inventory.representations, inventory.managedMarkerRanges, inventory.markerContainedHooksStateSpans, inventory.invalidMarkerContainedHooksStateSpans, inventory.parsedStatementSpans, inventory.parsedAssignmentSpans, managedHookTrustStateExpectations()).size > 0;
3043
+ }
3044
+ async function launchRepairOptionsWithManagedHookTrustProof(configPath, config, options) {
3045
+ if (!managedHookTrustProofRequiredForRepair(config)) {
3046
+ return options;
3047
+ }
3048
+ const hooksPath = options.codexHooksFile ?? resolve(configPath, "..", "hooks.json");
3049
+ let hooksContent;
3050
+ try {
3051
+ hooksContent = await readFile(hooksPath, "utf-8");
3052
+ }
3053
+ catch (error) {
3054
+ throw new ManagedCodexHooksPlanError("invalid_document", `Cannot safely repair config.toml because the current hooks artifact at ${hooksPath} could not be read.`, { cause: String(error), hooksPath });
3055
+ }
3056
+ const trustScan = scanManagedCodexHookTrustStateFromContent(hooksContent, hooksPath, managedCodexHookOptionsFromMergeOptions(options));
3057
+ if (!trustScan.ok)
3058
+ throw trustScan.error;
3059
+ return {
3060
+ ...options,
3061
+ codexHooksFile: hooksPath,
3062
+ codexHooksContent: hooksContent,
3063
+ managedHookTrustState: trustScan.trustState,
3064
+ priorManagedHookTrustState: trustScan.trustState,
3065
+ };
3066
+ }
1829
3067
  export async function repairConfigIfNeeded(configPath, pkgRoot, options = {}) {
1830
3068
  if (!existsSync(configPath))
1831
3069
  return false;
@@ -1835,8 +3073,10 @@ export async function repairConfigIfNeeded(configPath, pkgRoot, options = {}) {
1835
3073
  const hasLauncherTimeoutGap = findLauncherTimeoutRepairTargets(content).length > 0;
1836
3074
  if (tuiCount <= 1 && !hasLegacyTeamRunTable && !hasLauncherTimeoutGap)
1837
3075
  return false;
1838
- // Managed config compatibility issue detected — run full merge to repair
1839
- const repaired = buildMergedConfig(content, pkgRoot, options);
3076
+ // Managed config compatibility issue detected — derive proof from the current
3077
+ // hooks artifact before any marker stripping can replace managed trust state.
3078
+ const repairOptions = await launchRepairOptionsWithManagedHookTrustProof(configPath, content, options);
3079
+ const repaired = buildMergedConfig(content, pkgRoot, repairOptions);
1840
3080
  if (repaired === content)
1841
3081
  return false;
1842
3082
  await writeFile(configPath, repaired);
@@ -1847,12 +3087,6 @@ export async function mergeConfig(configPath, pkgRoot, options = {}) {
1847
3087
  if (existsSync(configPath)) {
1848
3088
  existing = await readFile(configPath, "utf-8");
1849
3089
  }
1850
- if (existing.includes("oh-my-codex (OMX) Configuration")) {
1851
- const stripped = stripExistingOmxBlocks(existing);
1852
- if (options.verbose && stripped.removed > 0) {
1853
- console.log(" Updating existing OMX config block.");
1854
- }
1855
- }
1856
3090
  const finalConfig = buildMergedConfig(existing, pkgRoot, options);
1857
3091
  await writeFile(configPath, finalConfig);
1858
3092
  if (options.verbose) {