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
@@ -22,11 +22,50 @@ const CODEX_HOOK_EVENT_LABELS = {
22
22
  function isPlainObject(value) {
23
23
  return typeof value === "object" && value !== null && !Array.isArray(value);
24
24
  }
25
+ function setOwnRecordValue(record, key, value) {
26
+ Object.defineProperty(record, key, {
27
+ value,
28
+ enumerable: true,
29
+ writable: true,
30
+ configurable: true,
31
+ });
32
+ }
33
+ function isJsonArray(value) {
34
+ return Array.isArray(value);
35
+ }
25
36
  function cloneJson(value) {
26
37
  return structuredClone(value);
27
38
  }
39
+ function jsonValueFromUnknown(value) {
40
+ if (value === null || typeof value === "string" || typeof value === "boolean")
41
+ return value;
42
+ if (typeof value === "number") {
43
+ if (!Number.isFinite(value))
44
+ throw new TypeError("Managed Codex hook configuration numbers must be finite JSON values.");
45
+ return value;
46
+ }
47
+ if (Array.isArray(value))
48
+ return value.map((item) => jsonValueFromUnknown(item));
49
+ if (typeof value === "object") {
50
+ const object = {};
51
+ for (const [key, item] of Object.entries(value))
52
+ setOwnRecordValue(object, key, jsonValueFromUnknown(item));
53
+ return object;
54
+ }
55
+ throw new TypeError("Managed Codex hook configuration must be JSON-serializable.");
56
+ }
57
+ function jsonObjectFromUnknown(value) {
58
+ const jsonValue = jsonValueFromUnknown(value);
59
+ if (typeof jsonValue !== "object" || jsonValue === null || Array.isArray(jsonValue)) {
60
+ throw new TypeError("Managed Codex hook configuration root must be a JSON object.");
61
+ }
62
+ return jsonValue;
63
+ }
28
64
  function quoteCommandPart(value) {
29
- return `"${value.replace(/\\/g, "\\\\").replace(/"/g, '\\"')}"`;
65
+ return `"${value
66
+ .replace(/\\/g, "\\\\")
67
+ .replace(/"/g, '\\"')
68
+ .replace(/[$`]/g, "\\$&")}"`;
30
69
  }
31
70
  export function escapeTomlBasicString(value) {
32
71
  return value.replace(/\\/g, "\\\\").replace(/"/g, '\\"');
@@ -60,6 +99,15 @@ export const WINDOWS_NATIVE_HOOK_SHIM_RELATIVE_PATH = [
60
99
  "hooks",
61
100
  "omx-native-hook-windows-shim.ps1",
62
101
  ];
102
+ function resolveManagedCodexHookOptions(options, hooksPath) {
103
+ const platform = options.platform ?? process.platform;
104
+ return {
105
+ ...options,
106
+ ...(platform === "win32" && !options.codexHomeDir
107
+ ? { codexHomeDir: dirname(hooksPath) }
108
+ : {}),
109
+ };
110
+ }
63
111
  const DEFAULT_WINDOWS_SYSTEM_ROOT = "C:\\Windows";
64
112
  /**
65
113
  * Resolve an absolute path to Windows PowerShell. When PATH has been shortened
@@ -76,6 +124,102 @@ export function resolveWindowsPowerShellPath(env = process.env) {
76
124
  export function buildManagedCodexNativeHookWindowsShimPath(codexHomeDir) {
77
125
  return win32.join(codexHomeDir, ...WINDOWS_NATIVE_HOOK_SHIM_RELATIVE_PATH);
78
126
  }
127
+ function decodePowerShellSingleQuotedLiteral(value) {
128
+ if (value.length < 2 || value[0] !== "'" || value[value.length - 1] !== "'")
129
+ return null;
130
+ let decoded = "";
131
+ for (let index = 1; index < value.length - 1; index += 1) {
132
+ const character = value[index];
133
+ if (character !== "'") {
134
+ decoded += character;
135
+ continue;
136
+ }
137
+ if (index + 1 >= value.length - 1 || value[index + 1] !== "'")
138
+ return null;
139
+ decoded += "'";
140
+ index += 1;
141
+ }
142
+ return decoded;
143
+ }
144
+ function decodeGeneratedWindowsProcessArgument(value) {
145
+ if (value.length < 2 || value[0] !== '"' || value[value.length - 1] !== '"')
146
+ return null;
147
+ let decoded = "";
148
+ let index = 1;
149
+ while (index < value.length - 1) {
150
+ let backslashes = 0;
151
+ while (value[index] === "\\") {
152
+ backslashes += 1;
153
+ index += 1;
154
+ }
155
+ if (index === value.length - 1) {
156
+ if (backslashes % 2 !== 0)
157
+ return null;
158
+ decoded += "\\".repeat(backslashes / 2);
159
+ break;
160
+ }
161
+ const character = value[index];
162
+ if (character === '"') {
163
+ if (backslashes % 2 === 0)
164
+ return null;
165
+ decoded += "\\".repeat((backslashes - 1) / 2) + '"';
166
+ }
167
+ else {
168
+ decoded += "\\".repeat(backslashes) + character;
169
+ }
170
+ index += 1;
171
+ }
172
+ return quoteWindowsProcessArgument(decoded) === value ? decoded : null;
173
+ }
174
+ function isProofValidNodeExecutable(nodePath) {
175
+ const executable = nodePath.split(/[\\/]/).pop()?.toLowerCase();
176
+ return isQualifiedWindowsFilesystemPath(nodePath) &&
177
+ (executable === "node" || executable === "node.exe");
178
+ }
179
+ function isProofValidNativeHookScriptPath(hookScriptPath) {
180
+ return isQualifiedWindowsFilesystemPath(hookScriptPath) &&
181
+ /(?:^|\\)dist\\scripts\\codex-native-hook\.js$/i.test(hookScriptPath);
182
+ }
183
+ /**
184
+ * Classify a Windows native-hook shim from its raw on-disk bytes. Current
185
+ * shims must be byte-identical to `expected`; historical shims must be the
186
+ * complete, BOM-prefixed generated forwarding template with independently
187
+ * proof-valid variable paths. Every other input is modified.
188
+ */
189
+ export function classifyManagedCodexNativeHookWindowsShimOwnership(content, expected) {
190
+ if (content.equals(expected))
191
+ return "current";
192
+ if (content[0] !== 0xef || content[1] !== 0xbb || content[2] !== 0xbf)
193
+ return "modified";
194
+ const decoded = content.toString("utf-8");
195
+ if (!Buffer.from(decoded, "utf-8").equals(content) || !decoded.startsWith("\uFEFF")) {
196
+ return "modified";
197
+ }
198
+ const lines = decoded.slice(1).split("\n");
199
+ if (lines.length !== 21 || lines[20] !== "")
200
+ return "modified";
201
+ const nodePath = lines[2]?.startsWith("$startInfo.FileName = ")
202
+ ? decodePowerShellSingleQuotedLiteral(lines[2].slice("$startInfo.FileName = ".length))
203
+ : null;
204
+ const encodedHookScript = lines[7]?.startsWith("$startInfo.Arguments = ")
205
+ ? decodePowerShellSingleQuotedLiteral(lines[7].slice("$startInfo.Arguments = ".length))
206
+ : null;
207
+ const hookScriptPath = encodedHookScript === null
208
+ ? null
209
+ : decodeGeneratedWindowsProcessArgument(encodedHookScript);
210
+ if (nodePath === null ||
211
+ hookScriptPath === null ||
212
+ !isProofValidNodeExecutable(nodePath) ||
213
+ !isProofValidNativeHookScriptPath(hookScriptPath)) {
214
+ return "modified";
215
+ }
216
+ return Buffer.from(buildManagedCodexNativeHookWindowsShimContent("", {
217
+ nodePath,
218
+ hookScriptPath,
219
+ }), "utf-8").equals(content)
220
+ ? "historical"
221
+ : "modified";
222
+ }
79
223
  export function buildManagedCodexNativeHookWindowsShimContent(pkgRoot, options = {}) {
80
224
  const hookScript = options.hookScriptPath ??
81
225
  win32.join(pkgRoot, "dist", "scripts", "codex-native-hook.js");
@@ -108,214 +252,2022 @@ export function buildManagedCodexNativeHookWindowsShimContent(pkgRoot, options =
108
252
  "",
109
253
  ].join("\n");
110
254
  }
111
- export function buildManagedCodexNativeHookCommand(pkgRoot, optionsOrPlatform = process.platform) {
112
- const options = typeof optionsOrPlatform === "string"
113
- ? { platform: optionsOrPlatform }
114
- : optionsOrPlatform;
115
- const platform = options.platform ?? process.platform;
116
- const hookScript = platform === "win32"
117
- ? win32.join(pkgRoot, "dist", "scripts", "codex-native-hook.js")
118
- : join(pkgRoot, "dist", "scripts", "codex-native-hook.js");
119
- if (platform === "win32") {
120
- const codexHomeDir = options.codexHomeDir ?? dirname(pkgRoot);
121
- const shimPath = buildManagedCodexNativeHookWindowsShimPath(codexHomeDir);
122
- const powerShellPath = resolveWindowsPowerShellPath(options.env);
123
- return `& ${quotePowerShellLiteral(powerShellPath)} -NoProfile -ExecutionPolicy Bypass -File ${quotePowerShellLiteral(shimPath)}`;
255
+ export function buildManagedCodexNativeHookCommand(pkgRoot, optionsOrPlatform = process.platform) {
256
+ const options = typeof optionsOrPlatform === "string"
257
+ ? { platform: optionsOrPlatform }
258
+ : optionsOrPlatform;
259
+ const platform = options.platform ?? process.platform;
260
+ if (platform === "win32") {
261
+ const codexHomeDir = options.codexHomeDir ?? dirname(pkgRoot);
262
+ const shimPath = buildManagedCodexNativeHookWindowsShimPath(codexHomeDir);
263
+ const powerShellPath = resolveWindowsPowerShellPath(options.env);
264
+ return `& ${quotePowerShellLiteral(powerShellPath)} -NoProfile -ExecutionPolicy Bypass -File ${quotePowerShellLiteral(shimPath)}`;
265
+ }
266
+ const hookScript = join(pkgRoot, "dist", "scripts", "codex-native-hook.js");
267
+ return `${quoteCommandPart(process.execPath)} ${quoteCommandPart(hookScript)}`;
268
+ }
269
+ function buildCommandHook(command, options = {}) {
270
+ const hook = {
271
+ type: "command",
272
+ command,
273
+ ...(options.statusMessage ? { statusMessage: options.statusMessage } : {}),
274
+ ...(typeof options.timeout === "number" ? { timeout: options.timeout } : {}),
275
+ };
276
+ return {
277
+ ...(options.matcher ? { matcher: options.matcher } : {}),
278
+ hooks: [hook],
279
+ };
280
+ }
281
+ export function buildManagedCodexHooksConfig(pkgRoot, options = {}) {
282
+ const command = buildManagedCodexNativeHookCommand(pkgRoot, options);
283
+ return {
284
+ hooks: {
285
+ SessionStart: [
286
+ buildCommandHook(command, {
287
+ matcher: "startup|resume|clear",
288
+ }),
289
+ ],
290
+ PreToolUse: [
291
+ buildCommandHook(command),
292
+ ],
293
+ PostToolUse: [
294
+ buildCommandHook(command),
295
+ ],
296
+ UserPromptSubmit: [
297
+ buildCommandHook(command),
298
+ ],
299
+ PreCompact: [
300
+ buildCommandHook(command),
301
+ ],
302
+ PostCompact: [
303
+ buildCommandHook(command),
304
+ ],
305
+ Stop: [
306
+ buildCommandHook(command, {
307
+ timeout: 30,
308
+ }),
309
+ ],
310
+ },
311
+ };
312
+ }
313
+ const CODEX_HOOK_EVENTS = [
314
+ "PreToolUse",
315
+ "PermissionRequest",
316
+ "PostToolUse",
317
+ "PreCompact",
318
+ "PostCompact",
319
+ "SessionStart",
320
+ "UserPromptSubmit",
321
+ "SubagentStart",
322
+ "SubagentStop",
323
+ "Stop",
324
+ ];
325
+ const MATCHER_AWARE_HOOK_EVENTS = new Set([
326
+ "PreToolUse",
327
+ "PermissionRequest",
328
+ "PostToolUse",
329
+ "PreCompact",
330
+ "PostCompact",
331
+ "SessionStart",
332
+ "SubagentStart",
333
+ "SubagentStop",
334
+ ]);
335
+ const KNOWN_HOOK_EVENT_FIELDS = new Set(CODEX_HOOK_EVENTS);
336
+ const KNOWN_GROUP_FIELDS = new Set(["matcher", "hooks"]);
337
+ const KNOWN_COMMAND_FIELDS = new Set([
338
+ "type",
339
+ "command",
340
+ "commandWindows",
341
+ "command_windows",
342
+ "timeout",
343
+ "async",
344
+ "statusMessage",
345
+ ]);
346
+ const KNOWN_ROOT_FIELDS = new Set(["hooks"]);
347
+ const KNOWN_HANDLER_TYPE_FIELD = new Set(["type"]);
348
+ const KNOWN_LEGACY_TRUST_STATE_ENTRY_FIELDS = new Set(["trusted_hash", "enabled"]);
349
+ const KNOWN_MATCHER_FIELD = new Set(["matcher"]);
350
+ export class ManagedCodexHooksPlanError extends Error {
351
+ code;
352
+ details;
353
+ constructor(code, message, details = {}) {
354
+ super(message);
355
+ this.name = "ManagedCodexHooksPlanError";
356
+ this.code = code;
357
+ this.details = details;
358
+ }
359
+ }
360
+ function planError(code, message, details = {}) {
361
+ return new ManagedCodexHooksPlanError(code, message, details);
362
+ }
363
+ function planFailure(error, diagnostics = []) {
364
+ return { ok: false, error, diagnostics };
365
+ }
366
+ function isManagedHookEventName(value) {
367
+ return MANAGED_HOOK_EVENTS.includes(value);
368
+ }
369
+ function matcherIsAware(eventName) {
370
+ return MATCHER_AWARE_HOOK_EVENTS.has(eventName);
371
+ }
372
+ function containsRustUnsupportedJavaScriptMatcherSyntax(matcher) {
373
+ for (let index = 0; index < matcher.length; index += 1) {
374
+ if (matcher[index] === "\\") {
375
+ const escaped = matcher[index + 1];
376
+ if (!escaped || !isValidRustRegexEscape(matcher, index))
377
+ return true;
378
+ index = skipRustRegexEscape(matcher, index) - 1;
379
+ continue;
380
+ }
381
+ if (matcher[index] !== "(" || matcher[index + 1] !== "?")
382
+ continue;
383
+ const marker = matcher[index + 2];
384
+ if (marker === "=" || marker === "!")
385
+ return true;
386
+ if (marker === "<" && (matcher[index + 3] === "=" || matcher[index + 3] === "!")) {
387
+ return true;
388
+ }
389
+ }
390
+ return false;
391
+ }
392
+ const RUST_SIMPLE_REGEX_ESCAPES = new Set([
393
+ "A", "B", "D", "S", "W", "a", "b", "d", "f", "n", "r", "s", "t", "v", "w", "z",
394
+ ]);
395
+ const RUST_ESCAPABLE_REGEX_PUNCTUATION = new Set([
396
+ "\\", ".", "*", "+", "?", "(", ")", "|", "[", "]", "{", "}", "^", "$", "-", "/",
397
+ ]);
398
+ function isHexadecimal(value) {
399
+ return /^[0-9A-Fa-f]+$/.test(value);
400
+ }
401
+ function skipRustRegexEscape(matcher, index) {
402
+ const kind = matcher[index + 1];
403
+ if ((kind === "p" || kind === "P" || kind === "u") && matcher[index + 2] === "{") {
404
+ const end = matcher.indexOf("}", index + 3);
405
+ return end >= 0 ? end + 1 : matcher.length;
406
+ }
407
+ return index + 2 + (kind === "x" ? 2 : 0);
408
+ }
409
+ function isValidRustUnicodeProperty(property) {
410
+ const candidates = [property];
411
+ const equals = property.indexOf("=");
412
+ if (equals >= 0) {
413
+ const name = property.slice(0, equals).toLowerCase();
414
+ const value = property.slice(equals + 1);
415
+ const normalizedName = name === "gc" || name === "general_category"
416
+ ? "General_Category"
417
+ : name === "sc" || name === "script"
418
+ ? "Script"
419
+ : name === "scx" || name === "script_extensions"
420
+ ? "Script_Extensions"
421
+ : property.slice(0, equals);
422
+ candidates.push(`${normalizedName}=${value}`);
423
+ }
424
+ else {
425
+ candidates.push(`Script=${property}`);
426
+ }
427
+ return candidates.some((candidate) => {
428
+ try {
429
+ new RegExp(`\\p{${candidate}}`, "u");
430
+ return true;
431
+ }
432
+ catch {
433
+ return false;
434
+ }
435
+ });
436
+ }
437
+ function isValidRustRegexEscape(matcher, index) {
438
+ const kind = matcher[index + 1];
439
+ if (!kind)
440
+ return false;
441
+ if (RUST_SIMPLE_REGEX_ESCAPES.has(kind) || RUST_ESCAPABLE_REGEX_PUNCTUATION.has(kind))
442
+ return true;
443
+ if (kind === "x")
444
+ return matcher[index + 3] !== undefined && isHexadecimal(matcher.slice(index + 2, index + 4));
445
+ if (kind === "u") {
446
+ if (matcher[index + 2] !== "{")
447
+ return false;
448
+ const end = matcher.indexOf("}", index + 3);
449
+ const codePoint = end >= 0 ? matcher.slice(index + 3, end) : "";
450
+ if (codePoint.length === 0 || codePoint.length > 6 || !isHexadecimal(codePoint))
451
+ return false;
452
+ const value = Number.parseInt(codePoint, 16);
453
+ return value <= 0x10ffff && (value < 0xd800 || value > 0xdfff);
454
+ }
455
+ if (kind === "p" || kind === "P") {
456
+ if (matcher[index + 2] !== "{")
457
+ return false;
458
+ const end = matcher.indexOf("}", index + 3);
459
+ return end > index + 3 && isValidRustUnicodeProperty(matcher.slice(index + 3, end));
460
+ }
461
+ return false;
462
+ }
463
+ function normalizeRustMatcherForJavaScriptValidation(matcher) {
464
+ return matcher
465
+ .replace(/\\[pP]\{[^}]+\}/g, ".")
466
+ .replace(/\\u\{([0-9A-Fa-f]{1,6})\}/g, (_match, codePoint) => String.fromCodePoint(Number.parseInt(codePoint, 16)))
467
+ .replace(/\\A/g, "^")
468
+ .replace(/\\z/g, "$")
469
+ .replace(/\\a/g, "\\x07")
470
+ .replace(/\(\?P<([A-Za-z_][A-Za-z0-9_]*)>/g, "(?<$1>")
471
+ .replace(/\(\?([imsuUxR]*)(?:-([imsuUxR]+))?([:)])/g, (_match, enabled, disabled, terminator) => enabled || disabled ? terminator === ":" ? "(?:" : "" : _match);
472
+ }
473
+ function isValidCodexMatcher(matcher) {
474
+ if (matcher === "*")
475
+ return true;
476
+ if (containsRustUnsupportedJavaScriptMatcherSyntax(matcher))
477
+ return false;
478
+ try {
479
+ new RegExp(normalizeRustMatcherForJavaScriptValidation(matcher));
480
+ return true;
481
+ }
482
+ catch {
483
+ return false;
484
+ }
485
+ }
486
+ function commandIsSkipped(command) {
487
+ return command.trim().length === 0;
488
+ }
489
+ function containsLoneSurrogate(value) {
490
+ for (let index = 0; index < value.length; index += 1) {
491
+ const codeUnit = value.charCodeAt(index);
492
+ if (codeUnit >= 0xd800 && codeUnit <= 0xdbff) {
493
+ if (index + 1 >= value.length)
494
+ return true;
495
+ const next = value.charCodeAt(index + 1);
496
+ if (next < 0xdc00 || next > 0xdfff)
497
+ return true;
498
+ index += 1;
499
+ }
500
+ else if (codeUnit >= 0xdc00 && codeUnit <= 0xdfff) {
501
+ return true;
502
+ }
503
+ }
504
+ return false;
505
+ }
506
+ function parseJsonSource(content) {
507
+ let cursor = 0;
508
+ let nesting = 0;
509
+ const skipWhitespace = () => {
510
+ while ([" ", "\n", "\r", "\t"].includes(content[cursor] ?? ""))
511
+ cursor += 1;
512
+ };
513
+ const parseString = () => {
514
+ const start = cursor;
515
+ if (content[cursor] !== '"')
516
+ return null;
517
+ cursor += 1;
518
+ let escaped = false;
519
+ while (cursor < content.length) {
520
+ const char = content[cursor++];
521
+ if (escaped) {
522
+ escaped = false;
523
+ }
524
+ else if (char === "\\") {
525
+ escaped = true;
526
+ }
527
+ else if (char === '"') {
528
+ const raw = content.slice(start, cursor);
529
+ try {
530
+ const value = JSON.parse(raw);
531
+ if (typeof value !== "string" || containsLoneSurrogate(value))
532
+ return null;
533
+ return { kind: "string", start, end: cursor, raw, value };
534
+ }
535
+ catch {
536
+ return null;
537
+ }
538
+ }
539
+ }
540
+ return null;
541
+ };
542
+ const parseValue = () => {
543
+ skipWhitespace();
544
+ const start = cursor;
545
+ const current = content[cursor];
546
+ if (current === "{") {
547
+ if (nesting >= 128)
548
+ return null;
549
+ nesting += 1;
550
+ try {
551
+ cursor += 1;
552
+ skipWhitespace();
553
+ const properties = [];
554
+ const value = {};
555
+ if (content[cursor] === "}") {
556
+ cursor += 1;
557
+ return { kind: "object", start, end: cursor, value, properties };
558
+ }
559
+ while (cursor < content.length) {
560
+ skipWhitespace();
561
+ const keyNode = parseString();
562
+ if (!keyNode)
563
+ return null;
564
+ skipWhitespace();
565
+ if (content[cursor] !== ":")
566
+ return null;
567
+ cursor += 1;
568
+ const child = parseValue();
569
+ if (!child)
570
+ return null;
571
+ properties.push({ key: keyNode.value, keyNode, value: child });
572
+ setOwnRecordValue(value, keyNode.value, child.value);
573
+ skipWhitespace();
574
+ if (content[cursor] === "}") {
575
+ cursor += 1;
576
+ return { kind: "object", start, end: cursor, value, properties };
577
+ }
578
+ if (content[cursor] !== ",")
579
+ return null;
580
+ cursor += 1;
581
+ }
582
+ return null;
583
+ }
584
+ finally {
585
+ nesting -= 1;
586
+ }
587
+ }
588
+ if (current === "[") {
589
+ if (nesting >= 128)
590
+ return null;
591
+ nesting += 1;
592
+ try {
593
+ cursor += 1;
594
+ skipWhitespace();
595
+ const elements = [];
596
+ const value = [];
597
+ if (content[cursor] === "]") {
598
+ cursor += 1;
599
+ return { kind: "array", start, end: cursor, value, elements };
600
+ }
601
+ while (cursor < content.length) {
602
+ const child = parseValue();
603
+ if (!child)
604
+ return null;
605
+ elements.push(child);
606
+ value.push(child.value);
607
+ skipWhitespace();
608
+ if (content[cursor] === "]") {
609
+ cursor += 1;
610
+ return { kind: "array", start, end: cursor, value, elements };
611
+ }
612
+ if (content[cursor] !== ",")
613
+ return null;
614
+ cursor += 1;
615
+ }
616
+ return null;
617
+ }
618
+ finally {
619
+ nesting -= 1;
620
+ }
621
+ }
622
+ if (current === '"')
623
+ return parseString();
624
+ if (content.startsWith("true", cursor)) {
625
+ cursor += 4;
626
+ return { kind: "boolean", start, end: cursor, raw: "true", value: true };
627
+ }
628
+ if (content.startsWith("false", cursor)) {
629
+ cursor += 5;
630
+ return { kind: "boolean", start, end: cursor, raw: "false", value: false };
631
+ }
632
+ if (content.startsWith("null", cursor)) {
633
+ cursor += 4;
634
+ return { kind: "null", start, end: cursor, raw: "null", value: null };
635
+ }
636
+ const number = /-?(?:0|[1-9]\d*)(?:\.\d+)?(?:[eE][+-]?\d+)?/.exec(content.slice(cursor));
637
+ if (!number || number.index !== 0)
638
+ return null;
639
+ const value = Number(number[0]);
640
+ if (!Number.isFinite(value))
641
+ return null;
642
+ cursor += number[0].length;
643
+ return {
644
+ kind: "number",
645
+ start,
646
+ end: cursor,
647
+ raw: number[0],
648
+ value,
649
+ };
650
+ };
651
+ const root = parseValue();
652
+ skipWhitespace();
653
+ return root && cursor === content.length ? root : null;
654
+ }
655
+ function objectProperty(node, key) {
656
+ return node.properties.find((property) => property.key === key);
657
+ }
658
+ function duplicateKnownProperty(node, known) {
659
+ const seen = new Set();
660
+ for (const property of node.properties) {
661
+ if (!known.has(property.key))
662
+ continue;
663
+ if (seen.has(property.key))
664
+ return property.key;
665
+ seen.add(property.key);
666
+ }
667
+ return undefined;
668
+ }
669
+ function stringProperty(node, key) {
670
+ const property = objectProperty(node, key);
671
+ return property?.value.kind === "string" ? property.value.value : undefined;
672
+ }
673
+ function nullableStringProperty(node, key) {
674
+ const property = objectProperty(node, key);
675
+ if (!property)
676
+ return undefined;
677
+ if (property.value.kind === "null")
678
+ return null;
679
+ return property.value.kind === "string" ? property.value.value : undefined;
680
+ }
681
+ function effectiveCommandForPlatform(handler, platform) {
682
+ const command = stringProperty(handler, "command");
683
+ if (platform !== "win32")
684
+ return command;
685
+ const windows = nullableStringProperty(handler, "commandWindows") ??
686
+ nullableStringProperty(handler, "command_windows");
687
+ return windows === undefined || windows === null ? command : windows;
688
+ }
689
+ function isU64Node(node) {
690
+ if (node.kind !== "number" || !/^(?:0|[1-9]\d*)$/.test(node.raw))
691
+ return false;
692
+ const maximum = "18446744073709551615";
693
+ return node.raw.length < maximum.length ||
694
+ (node.raw.length === maximum.length && node.raw <= maximum);
695
+ }
696
+ function validateCommandHandler(node, eventName, groupIndex, handlerIndex) {
697
+ const duplicate = duplicateKnownProperty(node, KNOWN_COMMAND_FIELDS);
698
+ if (duplicate) {
699
+ return planError("invalid_document", `Duplicate command hook field ${duplicate}.`, {
700
+ eventName,
701
+ groupIndex,
702
+ handlerIndex,
703
+ });
704
+ }
705
+ if (stringProperty(node, "command") === undefined) {
706
+ return planError("invalid_document", "Command hooks require a string command.", {
707
+ eventName,
708
+ groupIndex,
709
+ handlerIndex,
710
+ });
711
+ }
712
+ const windows = objectProperty(node, "commandWindows");
713
+ const windowsAlias = objectProperty(node, "command_windows");
714
+ if (windows && windowsAlias) {
715
+ return planError("invalid_document", "Command hook Windows aliases cannot both be present.", {
716
+ eventName,
717
+ groupIndex,
718
+ handlerIndex,
719
+ });
720
+ }
721
+ for (const property of [windows, windowsAlias]) {
722
+ if (property && property.value.kind !== "string" && property.value.kind !== "null") {
723
+ return planError("invalid_document", "Command hook Windows command must be a string or null.", {
724
+ eventName,
725
+ groupIndex,
726
+ handlerIndex,
727
+ });
728
+ }
729
+ }
730
+ const timeout = objectProperty(node, "timeout");
731
+ if (timeout && timeout.value.kind !== "null" && !isU64Node(timeout.value)) {
732
+ return planError("invalid_document", "Command hook timeout must be an unsigned 64-bit integer or null.", {
733
+ eventName,
734
+ groupIndex,
735
+ handlerIndex,
736
+ });
737
+ }
738
+ const asynchronous = objectProperty(node, "async");
739
+ if (asynchronous && asynchronous.value.kind !== "boolean") {
740
+ return planError("invalid_document", "Command hook async must be a boolean.", {
741
+ eventName,
742
+ groupIndex,
743
+ handlerIndex,
744
+ });
745
+ }
746
+ const status = objectProperty(node, "statusMessage");
747
+ if (status && status.value.kind !== "string" && status.value.kind !== "null") {
748
+ return planError("invalid_document", "Command hook statusMessage must be a string or null.", {
749
+ eventName,
750
+ groupIndex,
751
+ handlerIndex,
752
+ });
753
+ }
754
+ return null;
755
+ }
756
+ function validateCodexHooksDocument(content, options = {}) {
757
+ const root = parseJsonSource(content);
758
+ if (!root || root.kind !== "object") {
759
+ return { ok: false, error: planError("invalid_document", "hooks.json must contain a JSON object.") };
760
+ }
761
+ const duplicateRoot = duplicateKnownProperty(root, KNOWN_ROOT_FIELDS);
762
+ if (duplicateRoot) {
763
+ return { ok: false, error: planError("invalid_document", `Duplicate root field ${duplicateRoot}.`) };
764
+ }
765
+ const rootUnknown = root.properties.find((property) => !KNOWN_ROOT_FIELDS.has(property.key));
766
+ if (rootUnknown) {
767
+ return {
768
+ ok: false,
769
+ error: planError("invalid_document", `Codex does not accept unknown root field ${rootUnknown.key}.`),
770
+ };
771
+ }
772
+ const hooksProperty = objectProperty(root, "hooks");
773
+ if (hooksProperty && hooksProperty.value.kind !== "object") {
774
+ return { ok: false, error: planError("invalid_document", "Root hooks must be an object when present.") };
775
+ }
776
+ const hooksNode = hooksProperty?.value.kind === "object" ? hooksProperty.value : undefined;
777
+ const duplicateEvent = hooksNode && duplicateKnownProperty(hooksNode, KNOWN_HOOK_EVENT_FIELDS);
778
+ if (duplicateEvent) {
779
+ return { ok: false, error: planError("invalid_document", `Duplicate hooks event ${duplicateEvent}.`) };
780
+ }
781
+ const diagnostics = [];
782
+ const groupOccurrences = [];
783
+ const handlerOccurrences = [];
784
+ const discoveredCommands = [];
785
+ const platform = options.platform ?? process.platform;
786
+ for (const eventName of CODEX_HOOK_EVENTS) {
787
+ const eventProperty = hooksNode ? objectProperty(hooksNode, eventName) : undefined;
788
+ if (!eventProperty)
789
+ continue;
790
+ if (eventProperty.value.kind !== "array") {
791
+ return {
792
+ ok: false,
793
+ error: planError("invalid_document", `${eventName} must be an array of matcher groups.`, { eventName }),
794
+ };
795
+ }
796
+ for (const [groupIndex, groupNode] of eventProperty.value.elements.entries()) {
797
+ if (groupNode.kind !== "object") {
798
+ return {
799
+ ok: false,
800
+ error: planError("invalid_document", "Hook matcher groups must be objects.", { eventName, groupIndex }),
801
+ };
802
+ }
803
+ groupOccurrences.push({ eventName, groupIndex, span: groupNode });
804
+ const duplicate = duplicateKnownProperty(groupNode, KNOWN_GROUP_FIELDS);
805
+ if (duplicate) {
806
+ return {
807
+ ok: false,
808
+ error: planError("invalid_document", `Duplicate matcher group field ${duplicate}.`, { eventName, groupIndex }),
809
+ };
810
+ }
811
+ const matcher = objectProperty(groupNode, "matcher");
812
+ if (matcher && matcher.value.kind !== "string" && matcher.value.kind !== "null") {
813
+ return {
814
+ ok: false,
815
+ error: planError("invalid_document", "Hook matcher must be a string or null.", { eventName, groupIndex }),
816
+ };
817
+ }
818
+ const hooks = objectProperty(groupNode, "hooks");
819
+ if (hooks && hooks.value.kind !== "array") {
820
+ return {
821
+ ok: false,
822
+ error: planError("invalid_document", "Matcher group hooks must be an array when present.", { eventName, groupIndex }),
823
+ };
824
+ }
825
+ const invalidMatcher = matcherIsAware(eventName) && matcher?.value.kind === "string" &&
826
+ !isValidCodexMatcher(matcher.value.value);
827
+ if (invalidMatcher) {
828
+ diagnostics.push({
829
+ code: "invalid_matcher",
830
+ eventName,
831
+ groupIndex,
832
+ message: "Codex skips groups whose matcher is not a valid regular expression.",
833
+ });
834
+ }
835
+ const handlers = hooks?.value.kind === "array" ? hooks.value.elements : [];
836
+ for (const [handlerIndex, handlerNode] of handlers.entries()) {
837
+ handlerOccurrences.push({ eventName, groupIndex, handlerIndex, span: handlerNode });
838
+ if (handlerNode.kind !== "object") {
839
+ return {
840
+ ok: false,
841
+ error: planError("invalid_document", "Hook handlers must be objects.", { eventName, groupIndex, handlerIndex }),
842
+ };
843
+ }
844
+ const duplicateType = duplicateKnownProperty(handlerNode, KNOWN_HANDLER_TYPE_FIELD);
845
+ if (duplicateType) {
846
+ return {
847
+ ok: false,
848
+ error: planError("invalid_document", "Duplicate hook handler type field.", { eventName, groupIndex, handlerIndex }),
849
+ };
850
+ }
851
+ const type = stringProperty(handlerNode, "type");
852
+ if (type !== "command" && type !== "prompt" && type !== "agent") {
853
+ return {
854
+ ok: false,
855
+ error: planError("invalid_document", "Hook handler type must be command, prompt, or agent.", {
856
+ eventName,
857
+ groupIndex,
858
+ handlerIndex,
859
+ }),
860
+ };
861
+ }
862
+ if (type !== "command") {
863
+ diagnostics.push({
864
+ code: "unsupported_handler",
865
+ eventName,
866
+ groupIndex,
867
+ handlerIndex,
868
+ message: `Codex preserves but does not discover ${type} hook handlers.`,
869
+ });
870
+ continue;
871
+ }
872
+ const commandError = validateCommandHandler(handlerNode, eventName, groupIndex, handlerIndex);
873
+ if (commandError)
874
+ return { ok: false, error: commandError };
875
+ const command = effectiveCommandForPlatform(handlerNode, platform);
876
+ const asyncProperty = objectProperty(handlerNode, "async");
877
+ const asynchronous = asyncProperty?.value.kind === "boolean" &&
878
+ asyncProperty.value.value === true;
879
+ if (asynchronous) {
880
+ diagnostics.push({
881
+ code: "async_command",
882
+ eventName,
883
+ groupIndex,
884
+ handlerIndex,
885
+ message: "Codex skips async command hook handlers.",
886
+ });
887
+ }
888
+ else if (commandIsSkipped(command)) {
889
+ diagnostics.push({
890
+ code: "empty_command",
891
+ eventName,
892
+ groupIndex,
893
+ handlerIndex,
894
+ message: "Codex skips empty command hook handlers.",
895
+ });
896
+ }
897
+ else if (!invalidMatcher) {
898
+ discoveredCommands.push({ eventName, groupIndex, handlerIndex, command });
899
+ }
900
+ }
901
+ }
902
+ }
903
+ return {
904
+ content,
905
+ rootNode: root,
906
+ hooksNode,
907
+ result: {
908
+ ok: true,
909
+ root: cloneJson(root.value),
910
+ diagnostics,
911
+ groupOccurrences,
912
+ handlerOccurrences,
913
+ discoveredCommands,
914
+ },
915
+ };
916
+ }
917
+ export function validateCodexHooksConfigStrict(content, options = {}) {
918
+ const result = validateCodexHooksDocument(content, options);
919
+ return "result" in result ? result.result : result;
920
+ }
921
+ /** Compatibility-only permissive JSON intake; strict plans never use this parser. */
922
+ export function parseCodexHooksConfig(content) {
923
+ try {
924
+ const parsed = JSON.parse(content);
925
+ if (!isPlainObject(parsed))
926
+ return null;
927
+ return {
928
+ root: cloneJson(parsed),
929
+ hooks: isPlainObject(parsed.hooks) ? cloneJson(parsed.hooks) : {},
930
+ };
931
+ }
932
+ catch {
933
+ return null;
934
+ }
935
+ }
936
+ const UNQUOTED_POSIX_SHELL_ACTIVE_CHARACTERS = new Set([
937
+ "\r", "\n", ";", "|", "&", "<", ">", "`", "$", "(", ")", "*", "?", "[", "]", "{", "}", "~", "!", "#",
938
+ ]);
939
+ const UNQUOTED_POWERSHELL_ACTIVE_CHARACTERS = new Set([
940
+ "\r", "\n", ";", "|", "<", ">", "`", "$", "(", ")", "*", "?", "[", "]", "{", "}", "~", "!", "#", "@", "%", "^",
941
+ ]);
942
+ function isShellWhitespace(character) {
943
+ return character === " " || character === "\t" || character === "\r" || character === "\n";
944
+ }
945
+ /**
946
+ * Decode only shell-static POSIX words. The decoder preserves enough quote and
947
+ * escape provenance to reject interpolation and shell-active syntax instead of
948
+ * merely comparing the decoded argument values.
949
+ */
950
+ function tokenizePosixCommand(command, preserveWindowsBackslashes = false) {
951
+ if (containsControlCharacter(command))
952
+ return null;
953
+ const words = [];
954
+ let word = "";
955
+ let tokenStarted = false;
956
+ let quote;
957
+ for (let index = 0; index < command.length; index += 1) {
958
+ const character = command[index];
959
+ if (quote === "'") {
960
+ if (character === "'")
961
+ quote = undefined;
962
+ else
963
+ word += character;
964
+ continue;
965
+ }
966
+ if (quote === '"') {
967
+ if (character === '"') {
968
+ quote = undefined;
969
+ }
970
+ else if (character === "$" || character === "`") {
971
+ return null;
972
+ }
973
+ else if (character === "\\" && !preserveWindowsBackslashes) {
974
+ const escaped = command[index + 1];
975
+ if (!escaped || escaped === "\r" || escaped === "\n")
976
+ return null;
977
+ if (["$", "`", '"', "\\"].includes(escaped)) {
978
+ word += escaped;
979
+ index += 1;
980
+ }
981
+ else {
982
+ // POSIX preserves a backslash in double quotes unless it quotes a
983
+ // shell-active character. Keep it so suffix comparisons are exact.
984
+ word += "\\";
985
+ }
986
+ }
987
+ else {
988
+ word += character;
989
+ }
990
+ continue;
991
+ }
992
+ if (character === "'" || character === '"') {
993
+ quote = character;
994
+ tokenStarted = true;
995
+ continue;
996
+ }
997
+ if (UNQUOTED_POSIX_SHELL_ACTIVE_CHARACTERS.has(character))
998
+ return null;
999
+ if (character === "\\" && !preserveWindowsBackslashes) {
1000
+ const escaped = command[index + 1];
1001
+ if (!escaped || escaped === "\r" || escaped === "\n")
1002
+ return null;
1003
+ word += escaped;
1004
+ tokenStarted = true;
1005
+ index += 1;
1006
+ continue;
1007
+ }
1008
+ if (isShellWhitespace(character)) {
1009
+ if (tokenStarted) {
1010
+ words.push(word);
1011
+ word = "";
1012
+ tokenStarted = false;
1013
+ }
1014
+ continue;
1015
+ }
1016
+ word += character;
1017
+ tokenStarted = true;
1018
+ }
1019
+ if (quote)
1020
+ return null;
1021
+ if (tokenStarted)
1022
+ words.push(word);
1023
+ return words.length > 0 ? words : null;
1024
+ }
1025
+ const ALWAYS_UNSAFE_WINDOWS_DIRECT_COMMAND_CHARACTERS = new Set(["%", "!", "$", "`"]);
1026
+ const UNQUOTED_UNSAFE_WINDOWS_DIRECT_COMMAND_CHARACTERS = new Set([
1027
+ "&", "|", "<", ">", "(", ")", "^", ";", "@", "*", "?", "[", "]", "{", "}", "~",
1028
+ ]);
1029
+ /**
1030
+ * Empty/concatenated double-quote runs have cmd/UCRT and PowerShell parsing
1031
+ * rules that this ownership grammar intentionally does not model. Reject them
1032
+ * before tokenization so they cannot disappear into an owned proof path.
1033
+ */
1034
+ function containsAdjacentDoubleQuotes(command) {
1035
+ return command.includes('""');
1036
+ }
1037
+ /**
1038
+ * Decode only cmd.exe-static argv. This intentionally permits only double-quote
1039
+ * grouping and the CommandLineToArgvW backslash/quote convention; all cmd
1040
+ * expansion and unquoted operator syntax is rejected during tokenization.
1041
+ */
1042
+ function tokenizeWindowsDirectCommand(command) {
1043
+ if (containsControlCharacter(command) || containsAdjacentDoubleQuotes(command))
1044
+ return null;
1045
+ const words = [];
1046
+ let word = "";
1047
+ let tokenStarted = false;
1048
+ let quoted = false;
1049
+ for (let index = 0; index < command.length;) {
1050
+ const character = command[index];
1051
+ if (ALWAYS_UNSAFE_WINDOWS_DIRECT_COMMAND_CHARACTERS.has(character) ||
1052
+ (!quoted && UNQUOTED_UNSAFE_WINDOWS_DIRECT_COMMAND_CHARACTERS.has(character)))
1053
+ return null;
1054
+ if (character === "\\") {
1055
+ const start = index;
1056
+ while (command[index] === "\\")
1057
+ index += 1;
1058
+ const count = index - start;
1059
+ if (command[index] === '"') {
1060
+ word += "\\".repeat(Math.floor(count / 2));
1061
+ if (count % 2 === 0)
1062
+ quoted = !quoted;
1063
+ else
1064
+ word += '"';
1065
+ tokenStarted = true;
1066
+ index += 1;
1067
+ }
1068
+ else {
1069
+ word += "\\".repeat(count);
1070
+ tokenStarted = true;
1071
+ }
1072
+ continue;
1073
+ }
1074
+ if (character === '"') {
1075
+ quoted = !quoted;
1076
+ tokenStarted = true;
1077
+ index += 1;
1078
+ continue;
1079
+ }
1080
+ if (isShellWhitespace(character)) {
1081
+ if (quoted) {
1082
+ word += character;
1083
+ }
1084
+ else if (tokenStarted) {
1085
+ words.push(word);
1086
+ word = "";
1087
+ tokenStarted = false;
1088
+ }
1089
+ index += 1;
1090
+ continue;
1091
+ }
1092
+ word += character;
1093
+ tokenStarted = true;
1094
+ index += 1;
1095
+ }
1096
+ if (quoted)
1097
+ return null;
1098
+ if (tokenStarted)
1099
+ words.push(word);
1100
+ return words.length > 0 && words.every((value) => !value.includes('"')) ? words : null;
1101
+ }
1102
+ function containsControlCharacter(value) {
1103
+ for (const character of value) {
1104
+ const codePoint = character.codePointAt(0);
1105
+ if (codePoint <= 0x1f || (codePoint >= 0x7f && codePoint <= 0x9f))
1106
+ return true;
1107
+ }
1108
+ return false;
1109
+ }
1110
+ const WINDOWS_RESERVED_DEVICE_STEM = /^(?:con|prn|aux|nul|com[1-9¹²³]|lpt[1-9¹²³])$/i;
1111
+ function isReservedWindowsDeviceComponent(component) {
1112
+ const dot = component.indexOf(".");
1113
+ const deviceStem = (dot === -1 ? component : component.slice(0, dot)).replace(/ +$/, "");
1114
+ return WINDOWS_RESERVED_DEVICE_STEM.test(deviceStem);
1115
+ }
1116
+ /**
1117
+ * Managed command ownership accepts only ordinary Win32 path syntax. Namespace,
1118
+ * device, stream, and wildcard forms can be lexically static yet resolve outside
1119
+ * the filesystem path the command appears to name, so do not own them.
1120
+ */
1121
+ function isValidWindowsFilesystemPath(path) {
1122
+ if (!path || containsControlCharacter(path) || /["<>|?*]/.test(path))
1123
+ return false;
1124
+ if (/^(?:[\\/]{3,}|[\\/]{1,2}[?.][\\/]|[\\/]{1,2}\?\?[\\/]|[\\/](?:device|globalroot)[\\/])/i.test(path))
1125
+ return false;
1126
+ const firstColon = path.indexOf(":");
1127
+ if (firstColon !== -1 &&
1128
+ (firstColon !== 1 || !/[A-Za-z]/.test(path[0]) || path.indexOf(":", firstColon + 1) !== -1))
1129
+ return false;
1130
+ return path.split(/[\\/]+/).every((segment) => !segment || /^[A-Za-z]:$/.test(segment) ||
1131
+ (!segment.endsWith(".") && !segment.endsWith(" ") && !isReservedWindowsDeviceComponent(segment)));
1132
+ }
1133
+ /** Accept only drive-absolute or complete UNC paths as ownership proof. */
1134
+ function isQualifiedWindowsFilesystemPath(path) {
1135
+ if (!isValidWindowsFilesystemPath(path))
1136
+ return false;
1137
+ if (/^[A-Za-z]:[\\/]/.test(path))
1138
+ return true;
1139
+ if (!/^[\\/]{2}/.test(path))
1140
+ return false;
1141
+ const uncSegments = path.slice(2).split(/[\\/]+/);
1142
+ return uncSegments.length >= 2 && uncSegments[0].length > 0 && uncSegments[1].length > 0;
1143
+ }
1144
+ function isAbsolutePosixFilesystemPath(path) {
1145
+ return path.startsWith("/") && !containsControlCharacter(path);
1146
+ }
1147
+ /** Decode only literal PowerShell command words; interpolation is never owned. */
1148
+ function tokenizePowerShellCommand(command) {
1149
+ const tokens = [];
1150
+ let word = "";
1151
+ let tokenStarted = false;
1152
+ let tokenQuoted = false;
1153
+ let quote;
1154
+ let hasCallOperator = false;
1155
+ if (containsControlCharacter(command) || containsAdjacentDoubleQuotes(command))
1156
+ return null;
1157
+ for (let index = 0; index < command.length; index += 1) {
1158
+ const character = command[index];
1159
+ if (quote === "'") {
1160
+ if (character !== "'") {
1161
+ word += character;
1162
+ continue;
1163
+ }
1164
+ if (command[index + 1] === "'") {
1165
+ word += "'";
1166
+ index += 1;
1167
+ continue;
1168
+ }
1169
+ quote = undefined;
1170
+ continue;
1171
+ }
1172
+ if (quote === '"') {
1173
+ if (character === '"') {
1174
+ quote = undefined;
1175
+ }
1176
+ else if (character === "$" || character === "`") {
1177
+ return null;
1178
+ }
1179
+ else {
1180
+ word += character;
1181
+ }
1182
+ continue;
1183
+ }
1184
+ if (character === "'" || character === '"') {
1185
+ quote = character;
1186
+ tokenStarted = true;
1187
+ tokenQuoted = true;
1188
+ continue;
1189
+ }
1190
+ if (character === "&") {
1191
+ if (hasCallOperator ||
1192
+ tokenStarted ||
1193
+ tokens.length > 0 ||
1194
+ !isShellWhitespace(command[index + 1] ?? ""))
1195
+ return null;
1196
+ hasCallOperator = true;
1197
+ continue;
1198
+ }
1199
+ if (UNQUOTED_POWERSHELL_ACTIVE_CHARACTERS.has(character))
1200
+ return null;
1201
+ if (isShellWhitespace(character)) {
1202
+ if (tokenStarted) {
1203
+ tokens.push({ value: word, quoted: tokenQuoted });
1204
+ word = "";
1205
+ tokenStarted = false;
1206
+ tokenQuoted = false;
1207
+ }
1208
+ continue;
1209
+ }
1210
+ word += character;
1211
+ tokenStarted = true;
1212
+ }
1213
+ if (quote)
1214
+ return null;
1215
+ if (tokenStarted)
1216
+ tokens.push({ value: word, quoted: tokenQuoted });
1217
+ return tokens.length > 0 ? { tokens, hasCallOperator } : null;
1218
+ }
1219
+ function commandBasename(path, platform) {
1220
+ const executable = platform === "win32"
1221
+ ? path.split(/[\\/]/).pop()
1222
+ : path.split("/").pop();
1223
+ return platform === "win32" ? executable?.toLowerCase() ?? "" : executable ?? "";
1224
+ }
1225
+ function isNativeHookScriptPath(path, platform) {
1226
+ return platform === "win32"
1227
+ ? isQualifiedWindowsFilesystemPath(path) &&
1228
+ /(?:^|[\\/])dist[\\/]scripts[\\/]codex-native-hook\.js$/i.test(path)
1229
+ : isAbsolutePosixFilesystemPath(path) &&
1230
+ /(?:^|\/)dist\/scripts\/codex-native-hook\.js$/.test(path);
1231
+ }
1232
+ function hasNativeHookShimSuffix(path) {
1233
+ return /(?:^|[\\/])hooks[\\/]omx-native-hook-windows-shim\.ps1$/i.test(path);
1234
+ }
1235
+ function isNativeHookShimPath(path) {
1236
+ return isQualifiedWindowsFilesystemPath(path) && hasNativeHookShimSuffix(path);
1237
+ }
1238
+ function isSupportedDirectNodeExecutablePath(executablePath, platform) {
1239
+ const bareExecutable = platform === "win32" ? executablePath.toLowerCase() : executablePath;
1240
+ const supportedBareExecutable = platform === "win32"
1241
+ ? bareExecutable === "node" || bareExecutable === "node.exe"
1242
+ : bareExecutable === "node";
1243
+ return supportedBareExecutable ||
1244
+ (platform === "win32"
1245
+ ? isQualifiedWindowsFilesystemPath(executablePath)
1246
+ : isAbsolutePosixFilesystemPath(executablePath)) &&
1247
+ (platform === "win32"
1248
+ ? commandBasename(executablePath, "win32") === "node" ||
1249
+ commandBasename(executablePath, "win32") === "node.exe"
1250
+ : commandBasename(executablePath, "linux") === "node");
1251
+ }
1252
+ function isSupportedPowerShellExecutablePath(executablePath) {
1253
+ return executablePath.toLowerCase() === "powershell.exe" ||
1254
+ (isQualifiedWindowsFilesystemPath(executablePath) &&
1255
+ commandBasename(executablePath, "win32") === "powershell.exe");
1256
+ }
1257
+ function isValidDirectNodeCommand(command, platform) {
1258
+ const words = platform === "win32"
1259
+ ? tokenizeWindowsDirectCommand(command)
1260
+ : tokenizePosixCommand(command);
1261
+ const executablePath = words?.[0];
1262
+ const scriptPath = words?.[1];
1263
+ return words?.length === 2 &&
1264
+ executablePath !== undefined &&
1265
+ scriptPath !== undefined &&
1266
+ isSupportedDirectNodeExecutablePath(executablePath, platform) &&
1267
+ isNativeHookScriptPath(scriptPath, platform);
1268
+ }
1269
+ function hasStaticWindowsShimCommandGrammar(command) {
1270
+ const parsed = tokenizePowerShellCommand(command);
1271
+ if (!parsed)
1272
+ return false;
1273
+ const { tokens, hasCallOperator } = parsed;
1274
+ const powerShellToken = tokens[0];
1275
+ const powerShellPath = powerShellToken?.value;
1276
+ const shimPath = tokens[5]?.value;
1277
+ return tokens.length === 6 &&
1278
+ powerShellToken !== undefined &&
1279
+ powerShellPath !== undefined &&
1280
+ shimPath !== undefined &&
1281
+ (!powerShellToken.quoted || hasCallOperator) &&
1282
+ isSupportedPowerShellExecutablePath(powerShellPath) &&
1283
+ isValidWindowsFilesystemPath(shimPath) &&
1284
+ tokens[1]?.value.toLowerCase() === "-noprofile" &&
1285
+ tokens[2]?.value.toLowerCase() === "-executionpolicy" &&
1286
+ tokens[3]?.value.toLowerCase() === "bypass" &&
1287
+ tokens[4]?.value.toLowerCase() === "-file" &&
1288
+ hasNativeHookShimSuffix(shimPath);
1289
+ }
1290
+ /**
1291
+ * Parse an OMX Windows shim invocation using the managed-command ownership
1292
+ * grammar. Returns the validated shim path; returns null for every other
1293
+ * command. Supplying current install options additionally recognizes the exact
1294
+ * non-Windows host-path spelling used by platform-seam validation.
1295
+ */
1296
+ export function parseManagedCodexNativeHookWindowsShimCommand(command, options) {
1297
+ const parsed = tokenizePowerShellCommand(command);
1298
+ const shimPath = parsed?.tokens[5]?.value;
1299
+ return shimPath !== undefined &&
1300
+ hasStaticWindowsShimCommandGrammar(command) &&
1301
+ (isNativeHookShimPath(shimPath) ||
1302
+ (options !== undefined &&
1303
+ isExactCurrentWindowsShimCommand(command, "win32", options)))
1304
+ ? shimPath
1305
+ : null;
1306
+ }
1307
+ function isValidWindowsShimCommand(command) {
1308
+ return parseManagedCodexNativeHookWindowsShimCommand(command) !== null;
1309
+ }
1310
+ const OMX_COMMAND_PATTERN = /(?:codex-native-hook\.js|omx-native-hook-windows-shim\.ps1)/i;
1311
+ /**
1312
+ * Lex only static POSIX word fragments for ambiguity detection. This is
1313
+ * deliberately more permissive than the ownership grammar: backslash-newline
1314
+ * continuations and escaped characters must identify OMX-looking commands as
1315
+ * ambiguous, never make them owned.
1316
+ */
1317
+ function decodedPosixCommandWords(command) {
1318
+ const words = [];
1319
+ let word = "";
1320
+ let quote;
1321
+ const flushWord = () => {
1322
+ if (word)
1323
+ words.push(word);
1324
+ word = "";
1325
+ };
1326
+ for (let index = 0; index < command.length; index += 1) {
1327
+ const character = command[index];
1328
+ if (quote === "'") {
1329
+ if (character === "'")
1330
+ quote = undefined;
1331
+ else
1332
+ word += character;
1333
+ continue;
1334
+ }
1335
+ if (quote === '"') {
1336
+ if (character === '"') {
1337
+ quote = undefined;
1338
+ }
1339
+ else if (character === "\\") {
1340
+ const escaped = command[index + 1];
1341
+ if (escaped === "\n") {
1342
+ index += 1;
1343
+ }
1344
+ else if (escaped === "\r" && command[index + 2] === "\n") {
1345
+ index += 2;
1346
+ }
1347
+ else if (escaped && ["$", "`", '"', "\\"].includes(escaped)) {
1348
+ word += escaped;
1349
+ index += 1;
1350
+ }
1351
+ else {
1352
+ word += "\\";
1353
+ }
1354
+ }
1355
+ else {
1356
+ word += character;
1357
+ }
1358
+ continue;
1359
+ }
1360
+ if (character === "'" || character === '"') {
1361
+ quote = character;
1362
+ continue;
1363
+ }
1364
+ if (character === "\\") {
1365
+ const escaped = command[index + 1];
1366
+ if (escaped === "\n") {
1367
+ index += 1;
1368
+ }
1369
+ else if (escaped === "\r" && command[index + 2] === "\n") {
1370
+ index += 2;
1371
+ }
1372
+ else if (escaped) {
1373
+ word += escaped;
1374
+ index += 1;
1375
+ }
1376
+ else {
1377
+ word += character;
1378
+ }
1379
+ continue;
1380
+ }
1381
+ if (isShellWhitespace(character) || UNQUOTED_POSIX_SHELL_ACTIVE_CHARACTERS.has(character)) {
1382
+ flushWord();
1383
+ continue;
1384
+ }
1385
+ word += character;
1386
+ }
1387
+ flushWord();
1388
+ return words;
1389
+ }
1390
+ function commandMentionsDecodedPosixOmx(command) {
1391
+ return decodedPosixCommandWords(command).some((word) => OMX_COMMAND_PATTERN.test(word));
1392
+ }
1393
+ function commandMentionsOmx(command) {
1394
+ return OMX_COMMAND_PATTERN.test(command) || OMX_COMMAND_PATTERN.test(command.replace(/["']/g, ""));
1395
+ }
1396
+ function isValidManagedCommand(command, platform) {
1397
+ return isValidDirectNodeCommand(command, platform) ||
1398
+ (platform === "win32" && isValidWindowsShimCommand(command));
1399
+ }
1400
+ function buildExactCurrentWindowsHostShimCommand(options) {
1401
+ if (process.platform === "win32" ||
1402
+ (options.platform ?? process.platform) !== "win32" ||
1403
+ options.codexHomeDir === undefined)
1404
+ return null;
1405
+ const shimPath = join(options.codexHomeDir, ...WINDOWS_NATIVE_HOOK_SHIM_RELATIVE_PATH);
1406
+ const powerShellPath = resolveWindowsPowerShellPath(options.env);
1407
+ return `& ${quotePowerShellLiteral(powerShellPath)} -NoProfile -ExecutionPolicy Bypass -File ${quotePowerShellLiteral(shimPath)}`;
1408
+ }
1409
+ /**
1410
+ * A current Windows command may have a drive-less shim path in non-Windows
1411
+ * platform seams. Accept only byte-identical canonical or host-joined command
1412
+ * spellings produced from resolved current options; generic historical
1413
+ * classification remains provenance-qualified.
1414
+ */
1415
+ function isExactCurrentWindowsShimCommand(command, platform, options) {
1416
+ if (platform !== "win32" ||
1417
+ (options.platform ?? process.platform) !== "win32" ||
1418
+ options.codexHomeDir === undefined ||
1419
+ !hasStaticWindowsShimCommandGrammar(command))
1420
+ return false;
1421
+ return command === buildManagedCodexNativeHookCommand("", options) ||
1422
+ command === buildExactCurrentWindowsHostShimCommand(options);
1423
+ }
1424
+ function isValidHistoricalManagedCommand(command) {
1425
+ return isValidDirectNodeCommand(command, "linux") ||
1426
+ isValidDirectNodeCommand(command, "win32") ||
1427
+ isValidWindowsShimCommand(command);
1428
+ }
1429
+ /**
1430
+ * Returns whether a command has the exact approved token grammar of an OMX
1431
+ * native hook from this or a historical installation. Ownership requires a
1432
+ * shell-static executable and provenance-qualified terminal script path, but
1433
+ * never a current package-root path. The union is deliberately narrow and
1434
+ * accepts only shell-static POSIX, Windows, or PowerShell command spellings.
1435
+ */
1436
+ export function isManagedCodexHookCommand(command) {
1437
+ return isValidHistoricalManagedCommand(command);
1438
+ }
1439
+ function classifyManagedCommand(handler, options) {
1440
+ const command = stringProperty(handler, "command");
1441
+ const windows = nullableStringProperty(handler, "commandWindows") ??
1442
+ nullableStringProperty(handler, "command_windows");
1443
+ const platform = options.platform ?? process.platform;
1444
+ const commandPlatform = typeof windows === "string" ? "linux" : platform;
1445
+ const supplied = [
1446
+ { command, platform: commandPlatform },
1447
+ ...(typeof windows === "string" ? [{ command: windows, platform: "win32" }] : []),
1448
+ ];
1449
+ let hasOwnedCommand = false;
1450
+ let hasForeignAlternative = false;
1451
+ for (const suppliedCommand of supplied) {
1452
+ // Examine static POSIX word decoding before exact grammar validation. A
1453
+ // non-exact invocation can hide an OMX filename with escapes or a line
1454
+ // continuation, and must fail closed rather than become a foreign hook.
1455
+ const mentionsDecodedPosixOmx = commandMentionsDecodedPosixOmx(suppliedCommand.command);
1456
+ if (isValidManagedCommand(suppliedCommand.command, suppliedCommand.platform) ||
1457
+ isExactCurrentWindowsShimCommand(suppliedCommand.command, suppliedCommand.platform, options)) {
1458
+ hasOwnedCommand = true;
1459
+ continue;
1460
+ }
1461
+ if (mentionsDecodedPosixOmx || commandMentionsOmx(suppliedCommand.command))
1462
+ return "ambiguous";
1463
+ hasForeignAlternative = true;
1464
+ }
1465
+ return hasOwnedCommand
1466
+ ? hasForeignAlternative ? "ambiguous" : "owned"
1467
+ : "foreign";
1468
+ }
1469
+ function hasOpaqueProperties(node, known) {
1470
+ return node.properties.some((property) => !known.has(property.key));
1471
+ }
1472
+ function groupMatcher(node) {
1473
+ return nullableStringProperty(node, "matcher");
1474
+ }
1475
+ function matcherOwnershipError(eventName, group) {
1476
+ const matcher = groupMatcher(group.node);
1477
+ if (matcherIsAware(eventName) && typeof matcher === "string" && !isValidCodexMatcher(matcher)) {
1478
+ return planError("ambiguous_managed_group", "Cannot mutate an OMX group with an invalid matcher.", {
1479
+ eventName,
1480
+ groupIndex: group.groupIndex,
1481
+ });
1482
+ }
1483
+ const foreignSiblings = group.handlers.some((handler) => !handler.owner);
1484
+ if (eventName === "SessionStart") {
1485
+ const allowed = foreignSiblings
1486
+ ? matcher === "startup|resume|clear"
1487
+ : matcher === undefined || matcher === null || matcher === "startup" || matcher === "startup|resume" || matcher === "startup|resume|clear";
1488
+ if (!allowed) {
1489
+ return planError("ambiguous_managed_group", "SessionStart matcher is not compatible with OMX ownership.", {
1490
+ eventName,
1491
+ groupIndex: group.groupIndex,
1492
+ });
1493
+ }
1494
+ }
1495
+ else if (matcher !== undefined && matcher !== null) {
1496
+ return planError("ambiguous_managed_group", "OMX managed groups must not carry a matcher for this event.", {
1497
+ eventName,
1498
+ groupIndex: group.groupIndex,
1499
+ });
1500
+ }
1501
+ return null;
1502
+ }
1503
+ function inspectOwnership(document, options) {
1504
+ const groups = [];
1505
+ const owners = [];
1506
+ for (const eventName of CODEX_HOOK_EVENTS) {
1507
+ const event = document.hooksNode ? objectProperty(document.hooksNode, eventName) : undefined;
1508
+ if (!event || event.value.kind !== "array")
1509
+ continue;
1510
+ for (const [groupIndex, groupNode] of event.value.elements.entries()) {
1511
+ if (groupNode.kind !== "object")
1512
+ continue;
1513
+ const hooks = objectProperty(groupNode, "hooks");
1514
+ const handlerNodes = hooks?.value.kind === "array" ? hooks.value.elements : [];
1515
+ const handlers = [];
1516
+ for (const [handlerIndex, handlerNode] of handlerNodes.entries()) {
1517
+ if (handlerNode.kind !== "object")
1518
+ continue;
1519
+ const type = stringProperty(handlerNode, "type");
1520
+ let classification = "foreign";
1521
+ if (type === "command")
1522
+ classification = classifyManagedCommand(handlerNode, options);
1523
+ if (classification === "ambiguous") {
1524
+ return planError("ambiguous_managed_handler", "A command mentions OMX but does not match the managed command grammar.", {
1525
+ eventName,
1526
+ groupIndex,
1527
+ handlerIndex,
1528
+ });
1529
+ }
1530
+ const asyncProperty = type === "command"
1531
+ ? objectProperty(handlerNode, "async")
1532
+ : undefined;
1533
+ const asynchronous = asyncProperty?.value.kind === "boolean" &&
1534
+ asyncProperty.value.value === true;
1535
+ if (classification !== "owned" || !isManagedHookEventName(eventName)) {
1536
+ if (classification === "owned") {
1537
+ return planError("ambiguous_managed_handler", "An OMX command is attached to an unmanaged Codex event.", {
1538
+ eventName,
1539
+ groupIndex,
1540
+ handlerIndex,
1541
+ });
1542
+ }
1543
+ const foreignCommandIsSkipped = type === "command" && (commandIsSkipped(effectiveCommandForPlatform(handlerNode, options.platform ?? process.platform)) || asynchronous);
1544
+ handlers.push({ node: handlerNode, foreign: true, discoverySkipped: foreignCommandIsSkipped });
1545
+ continue;
1546
+ }
1547
+ const command = effectiveCommandForPlatform(handlerNode, options.platform ?? process.platform);
1548
+ if (asynchronous || commandIsSkipped(command)) {
1549
+ return planError("ambiguous_managed_handler", "Cannot mutate skipped OMX command handlers.", {
1550
+ eventName,
1551
+ groupIndex,
1552
+ handlerIndex,
1553
+ });
1554
+ }
1555
+ const owner = {
1556
+ eventName,
1557
+ groupIndex,
1558
+ handlerIndex,
1559
+ groupNode,
1560
+ handlerNode,
1561
+ opaqueHandler: hasOpaqueProperties(handlerNode, KNOWN_COMMAND_FIELDS),
1562
+ };
1563
+ owners.push(owner);
1564
+ handlers.push({ node: handlerNode, owner, foreign: false });
1565
+ }
1566
+ const group = {
1567
+ eventName,
1568
+ groupIndex,
1569
+ node: groupNode,
1570
+ handlers,
1571
+ opaque: hasOpaqueProperties(groupNode, KNOWN_GROUP_FIELDS),
1572
+ foreign: false,
1573
+ };
1574
+ group.foreign = handlers.length === 0 || handlers.some((handler) => handler.foreign);
1575
+ groups.push(group);
1576
+ const groupOwners = handlers.flatMap((handler) => handler.owner ? [handler.owner] : []);
1577
+ if (groupOwners.length > 0) {
1578
+ if (handlers.some((handler) => handler.discoverySkipped)) {
1579
+ return planError("ambiguous_managed_handler", "Cannot mutate an OMX group that also contains a skipped command handler.", {
1580
+ eventName,
1581
+ groupIndex,
1582
+ });
1583
+ }
1584
+ const error = matcherOwnershipError(eventName, group);
1585
+ if (error)
1586
+ return error;
1587
+ }
1588
+ }
1589
+ }
1590
+ return { owners, groups };
1591
+ }
1592
+ function applySourcePatches(content, patches) {
1593
+ const ordered = [...patches].sort((left, right) => left.start - right.start || left.end - right.end);
1594
+ for (let index = 1; index < ordered.length; index += 1) {
1595
+ if (ordered[index - 1].end > ordered[index].start) {
1596
+ throw new Error("Overlapping source patches are not permitted.");
1597
+ }
1598
+ }
1599
+ let result = content;
1600
+ for (const patch of ordered.reverse()) {
1601
+ result = result.slice(0, patch.start) + patch.text + result.slice(patch.end);
1602
+ }
1603
+ return result;
1604
+ }
1605
+ function propertyRemovalPatch(object, property) {
1606
+ if (object.properties.length === 1) {
1607
+ return { start: object.start + 1, end: object.end - 1, text: "" };
1608
+ }
1609
+ const index = object.properties.indexOf(property);
1610
+ if (index < object.properties.length - 1) {
1611
+ return { start: property.keyNode.start, end: object.properties[index + 1].keyNode.start, text: "" };
1612
+ }
1613
+ return { start: object.properties[index - 1].value.end, end: property.value.end, text: "" };
1614
+ }
1615
+ function appendObjectPropertyPatch(object, key, value) {
1616
+ const prefix = object.properties.length === 0 ? "" : ",";
1617
+ return { start: object.end - 1, end: object.end - 1, text: `${prefix}${JSON.stringify(key)}:${value}` };
1618
+ }
1619
+ function appendObjectPropertiesPatch(object, entries) {
1620
+ const prefix = object.properties.length === 0 ? "" : ",";
1621
+ const text = entries
1622
+ .map(([key, value]) => `${JSON.stringify(key)}:${value}`)
1623
+ .join(",");
1624
+ return { start: object.end - 1, end: object.end - 1, text: `${prefix}${text}` };
1625
+ }
1626
+ function appendArrayElementPatch(array, value) {
1627
+ const prefix = array.elements.length === 0 ? "" : ",";
1628
+ return { start: array.end - 1, end: array.end - 1, text: `${prefix}${value}` };
1629
+ }
1630
+ function knownFieldPatches(content, node, desired, known) {
1631
+ const removedProperties = node.properties.filter((property) => known.has(property.key) && !Object.hasOwn(desired, property.key));
1632
+ if (removedProperties.length > 1) {
1633
+ const members = node.properties.flatMap((property) => {
1634
+ if (!known.has(property.key))
1635
+ return [content.slice(property.keyNode.start, property.value.end)];
1636
+ if (!Object.hasOwn(desired, property.key))
1637
+ return [];
1638
+ return [`${JSON.stringify(property.key)}:${JSON.stringify(desired[property.key])}`];
1639
+ });
1640
+ for (const [key, value] of Object.entries(desired)) {
1641
+ if (!objectProperty(node, key))
1642
+ members.push(`${JSON.stringify(key)}:${JSON.stringify(value)}`);
1643
+ }
1644
+ return [{ start: node.start, end: node.end, text: `{${members.join(",")}}` }];
124
1645
  }
125
- return `${quoteCommandPart(process.execPath)} ${quoteCommandPart(hookScript)}`;
1646
+ const patches = [];
1647
+ for (const property of node.properties) {
1648
+ if (!known.has(property.key))
1649
+ continue;
1650
+ if (Object.hasOwn(desired, property.key)) {
1651
+ patches.push({
1652
+ start: property.value.start,
1653
+ end: property.value.end,
1654
+ text: JSON.stringify(desired[property.key]),
1655
+ });
1656
+ }
1657
+ else {
1658
+ patches.push(propertyRemovalPatch(node, property));
1659
+ }
1660
+ }
1661
+ for (const [key, value] of Object.entries(desired)) {
1662
+ if (!objectProperty(node, key))
1663
+ patches.push(appendObjectPropertyPatch(node, key, JSON.stringify(value)));
1664
+ }
1665
+ return patches;
126
1666
  }
127
- function buildCommandHook(command, options = {}) {
128
- const hook = {
129
- type: "command",
130
- command,
131
- ...(options.statusMessage ? { statusMessage: options.statusMessage } : {}),
132
- ...(typeof options.timeout === "number" ? { timeout: options.timeout } : {}),
133
- };
1667
+ function serializeCodexHooksConfig(root) {
1668
+ return JSON.stringify(root, null, 2) + "\n";
1669
+ }
1670
+ class CanonicalJsonNumber {
1671
+ raw;
1672
+ constructor(raw) {
1673
+ this.raw = raw;
1674
+ }
1675
+ }
1676
+ function canonicalJsonString(value) {
1677
+ if (value instanceof CanonicalJsonNumber)
1678
+ return value.raw;
1679
+ if (Array.isArray(value))
1680
+ return `[${value.map((item) => canonicalJsonString(item)).join(",")}]`;
1681
+ if (isPlainObject(value)) {
1682
+ return `{${Object.keys(value)
1683
+ .sort()
1684
+ .map((key) => `${JSON.stringify(key)}:${canonicalJsonString(value[key])}`)
1685
+ .join(",")}}`;
1686
+ }
1687
+ const serialized = JSON.stringify(value);
1688
+ if (serialized === undefined)
1689
+ throw new TypeError("Codex hook trust identity must be JSON-serializable.");
1690
+ return serialized;
1691
+ }
1692
+ function versionForCodexTomlIdentity(value) {
1693
+ return `sha256:${createHash("sha256").update(canonicalJsonString(value)).digest("hex")}`;
1694
+ }
1695
+ function managedHookStateKey(hooksPath, eventName, groupIndex, handlerIndex) {
1696
+ return `${hooksPath}:${CODEX_HOOK_EVENT_LABELS[eventName]}:${groupIndex}:${handlerIndex}`;
1697
+ }
1698
+ function canonicalHookForEvent(entry) {
1699
+ const hook = entry.hooks[0];
134
1700
  return {
135
- ...(options.matcher ? { matcher: options.matcher } : {}),
136
- hooks: [hook],
1701
+ type: "command",
1702
+ command: hook.command,
1703
+ ...(typeof hook.timeout === "number" ? { timeout: hook.timeout } : {}),
137
1704
  };
138
1705
  }
139
- export function buildManagedCodexHooksConfig(pkgRoot, options = {}) {
140
- const command = buildManagedCodexNativeHookCommand(pkgRoot, options);
1706
+ function canonicalGroupForEvent(entry) {
141
1707
  return {
142
- hooks: {
143
- SessionStart: [
144
- buildCommandHook(command, {
145
- matcher: "startup|resume|clear",
146
- }),
147
- ],
148
- PreToolUse: [
149
- buildCommandHook(command),
150
- ],
151
- PostToolUse: [
152
- buildCommandHook(command),
153
- ],
154
- UserPromptSubmit: [
155
- buildCommandHook(command),
156
- ],
157
- PreCompact: [
158
- buildCommandHook(command),
159
- ],
160
- PostCompact: [
161
- buildCommandHook(command),
162
- ],
163
- Stop: [
164
- buildCommandHook(command, {
165
- timeout: 30,
166
- }),
167
- ],
168
- },
1708
+ ...(entry.matcher ? { matcher: entry.matcher } : {}),
1709
+ hooks: [canonicalHookForEvent(entry)],
169
1710
  };
170
1711
  }
171
- export function parseCodexHooksConfig(content) {
172
- try {
173
- const parsed = JSON.parse(content);
174
- if (!isPlainObject(parsed))
175
- return null;
176
- return {
177
- root: cloneJson(parsed),
178
- hooks: isPlainObject(parsed.hooks) ? cloneJson(parsed.hooks) : {},
179
- };
1712
+ function canonicalMatcherForEvent(entry) {
1713
+ return entry.matcher ? { matcher: entry.matcher } : {};
1714
+ }
1715
+ function removesAllGroupHandlers(group, ownersToRemove) {
1716
+ return group.handlers.length > 0 &&
1717
+ group.handlers.every((handler) => handler.owner && ownersToRemove.has(handler.owner));
1718
+ }
1719
+ function removalProof(model, ownersToRemove) {
1720
+ for (const group of model.groups) {
1721
+ if (group.opaque && removesAllGroupHandlers(group, ownersToRemove)) {
1722
+ return {
1723
+ safe: false,
1724
+ shifted: {
1725
+ kind: "group",
1726
+ eventName: group.eventName,
1727
+ oldCoordinate: [group.groupIndex],
1728
+ },
1729
+ };
1730
+ }
180
1731
  }
181
- catch {
182
- return null;
1732
+ for (const owner of ownersToRemove) {
1733
+ if (owner.opaqueHandler) {
1734
+ return {
1735
+ safe: false,
1736
+ shifted: {
1737
+ kind: "handler",
1738
+ eventName: owner.eventName,
1739
+ oldCoordinate: [owner.groupIndex, owner.handlerIndex],
1740
+ },
1741
+ };
1742
+ }
1743
+ }
1744
+ const byEvent = new Map();
1745
+ for (const group of model.groups) {
1746
+ const eventGroups = byEvent.get(group.eventName) ?? [];
1747
+ eventGroups.push(group);
1748
+ byEvent.set(group.eventName, eventGroups);
1749
+ }
1750
+ for (const [eventName, groups] of byEvent) {
1751
+ const removedGroups = new Set();
1752
+ for (const group of groups) {
1753
+ if (!group.opaque && removesAllGroupHandlers(group, ownersToRemove)) {
1754
+ removedGroups.add(group);
1755
+ }
1756
+ }
1757
+ const survivors = groups.filter((group) => !removedGroups.has(group));
1758
+ for (const group of groups) {
1759
+ const newGroupIndex = survivors.indexOf(group);
1760
+ if (group.foreign && newGroupIndex !== group.groupIndex) {
1761
+ return {
1762
+ safe: false,
1763
+ shifted: {
1764
+ kind: "group",
1765
+ eventName,
1766
+ oldCoordinate: [group.groupIndex],
1767
+ ...(newGroupIndex >= 0 ? { newCoordinate: [newGroupIndex] } : {}),
1768
+ },
1769
+ };
1770
+ }
1771
+ if (newGroupIndex < 0)
1772
+ continue;
1773
+ const remainingHandlers = group.handlers.filter((handler) => !handler.owner || !ownersToRemove.has(handler.owner));
1774
+ for (const [handlerIndex, handler] of group.handlers.entries()) {
1775
+ if (!handler.foreign || ownersToRemove.has(handler.owner))
1776
+ continue;
1777
+ const newHandlerIndex = remainingHandlers.indexOf(handler);
1778
+ if (newHandlerIndex !== handlerIndex) {
1779
+ return {
1780
+ safe: false,
1781
+ shifted: {
1782
+ kind: "handler",
1783
+ eventName,
1784
+ oldCoordinate: [group.groupIndex, handlerIndex],
1785
+ newCoordinate: [newGroupIndex, newHandlerIndex],
1786
+ },
1787
+ };
1788
+ }
1789
+ }
1790
+ }
183
1791
  }
1792
+ return { safe: true };
184
1793
  }
185
- function isOmxManagedHookCommand(command) {
186
- return /(?:^|[\\/])codex-native-hook\.js(?:["'\s]|$)/.test(command)
187
- || /(?:^|[\\/])omx-native-hook-windows-shim\.ps1(?:["'\s]|$)/i.test(command);
1794
+ function fullRemovalGroupKeys(model, ownersToRemove) {
1795
+ const keys = new Map();
1796
+ for (const group of model.groups) {
1797
+ const removable = !group.opaque && removesAllGroupHandlers(group, ownersToRemove);
1798
+ if (!removable)
1799
+ continue;
1800
+ const eventKeys = keys.get(group.eventName) ?? new Set();
1801
+ eventKeys.add(group.groupIndex);
1802
+ keys.set(group.eventName, eventKeys);
1803
+ }
1804
+ return keys;
188
1805
  }
189
- function countManagedHooksInEntry(entry) {
190
- if (!isPlainObject(entry) || !Array.isArray(entry.hooks)) {
191
- return 0;
1806
+ function arrayWithoutElements(content, array, removed) {
1807
+ const values = [];
1808
+ for (const element of array.elements) {
1809
+ if (!removed.has(element))
1810
+ values.push(content.slice(element.start, element.end));
192
1811
  }
193
- return entry.hooks.filter((hook) => {
194
- return isPlainObject(hook)
195
- && hook.type === "command"
196
- && typeof hook.command === "string"
197
- && isOmxManagedHookCommand(hook.command);
198
- }).length;
1812
+ return { start: array.start, end: array.end, text: `[${values.join(",")}]` };
199
1813
  }
200
- export function getMissingManagedCodexHookEvents(content) {
201
- const parsed = parseCodexHooksConfig(content);
202
- if (!parsed)
1814
+ function eventArrayForGroup(document, eventName) {
1815
+ const event = document.hooksNode ? objectProperty(document.hooksNode, eventName) : undefined;
1816
+ return event?.value.kind === "array" ? event.value : undefined;
1817
+ }
1818
+ function pruneRemovedManagedEventProperties(content, deletedEvents, options) {
1819
+ let current = content;
1820
+ for (;;) {
1821
+ const validation = validateCodexHooksDocument(current, options);
1822
+ if (!("result" in validation))
1823
+ return validation.error;
1824
+ let emptyEvent;
1825
+ for (const eventName of deletedEvents) {
1826
+ const event = validation.hooksNode ? objectProperty(validation.hooksNode, eventName) : undefined;
1827
+ if (event?.value.kind === "array" && event.value.elements.length === 0) {
1828
+ emptyEvent = eventName;
1829
+ break;
1830
+ }
1831
+ }
1832
+ if (emptyEvent === undefined) {
1833
+ if (!validation.hooksNode || validation.hooksNode.properties.length > 0)
1834
+ return current;
1835
+ const hooks = objectProperty(validation.rootNode, "hooks");
1836
+ if (!hooks)
1837
+ return validation.rootNode.properties.length === 0 ? null : current;
1838
+ current = applySourcePatches(current, [propertyRemovalPatch(validation.rootNode, hooks)]);
1839
+ const afterRoot = validateCodexHooksDocument(current, options);
1840
+ if (!("result" in afterRoot))
1841
+ return afterRoot.error;
1842
+ return afterRoot.rootNode.properties.length === 0 ? null : current;
1843
+ }
1844
+ const property = objectProperty(validation.hooksNode, emptyEvent);
1845
+ current = applySourcePatches(current, [propertyRemovalPatch(validation.hooksNode, property)]);
1846
+ }
1847
+ }
1848
+ function applyCoordinateSafeRemovals(content, model, ownersToRemove, options) {
1849
+ const fullGroups = fullRemovalGroupKeys(model, ownersToRemove);
1850
+ const partialPatches = [];
1851
+ for (const group of model.groups) {
1852
+ if (fullGroups.get(group.eventName)?.has(group.groupIndex))
1853
+ continue;
1854
+ const hooks = objectProperty(group.node, "hooks")?.value;
1855
+ if (hooks?.kind !== "array")
1856
+ continue;
1857
+ const removedHandlers = new Set(group.handlers
1858
+ .filter((handler) => handler.owner && ownersToRemove.has(handler.owner))
1859
+ .map((handler) => handler.node));
1860
+ if (removedHandlers.size > 0)
1861
+ partialPatches.push(arrayWithoutElements(content, hooks, removedHandlers));
1862
+ }
1863
+ let current = partialPatches.length > 0 ? applySourcePatches(content, partialPatches) : content;
1864
+ if (fullGroups.size === 0)
1865
+ return current;
1866
+ const afterPartial = validateCodexHooksDocument(current, options);
1867
+ if (!("result" in afterPartial))
1868
+ return afterPartial.error;
1869
+ const afterOwnership = inspectOwnership(afterPartial, options);
1870
+ if (afterOwnership instanceof ManagedCodexHooksPlanError)
1871
+ return afterOwnership;
1872
+ const groupPatches = [];
1873
+ const deletedEvents = new Set();
1874
+ for (const [eventName, groupIndices] of fullGroups) {
1875
+ const array = eventArrayForGroup(afterPartial, eventName);
1876
+ if (!array)
1877
+ continue;
1878
+ const removedGroups = new Set(afterOwnership.groups
1879
+ .filter((group) => group.eventName === eventName && groupIndices.has(group.groupIndex))
1880
+ .map((group) => group.node));
1881
+ if (removedGroups.size === 0)
1882
+ continue;
1883
+ groupPatches.push(arrayWithoutElements(current, array, removedGroups));
1884
+ if (removedGroups.size === array.elements.length)
1885
+ deletedEvents.add(eventName);
1886
+ }
1887
+ current = groupPatches.length > 0 ? applySourcePatches(current, groupPatches) : current;
1888
+ return pruneRemovedManagedEventProperties(current, deletedEvents, options);
1889
+ }
1890
+ function exactLegacyState(node) {
1891
+ if (node.kind !== "object")
203
1892
  return null;
204
- return MANAGED_HOOK_EVENTS.filter((eventName) => {
205
- const entries = Array.isArray(parsed.hooks[eventName])
206
- ? parsed.hooks[eventName]
207
- : [];
208
- return !entries.some((entry) => countManagedHooksInEntry(entry) > 0);
209
- });
1893
+ const entries = {};
1894
+ const seenKeys = new Set();
1895
+ for (const property of node.properties) {
1896
+ if (seenKeys.has(property.key))
1897
+ return null;
1898
+ seenKeys.add(property.key);
1899
+ if (property.value.kind !== "object")
1900
+ return null;
1901
+ const entry = property.value;
1902
+ if (entry.properties.some((member) => member.key !== "trusted_hash" && member.key !== "enabled"))
1903
+ return null;
1904
+ if (duplicateKnownProperty(entry, KNOWN_LEGACY_TRUST_STATE_ENTRY_FIELDS))
1905
+ return null;
1906
+ const hash = stringProperty(entry, "trusted_hash");
1907
+ const enabled = objectProperty(entry, "enabled");
1908
+ if (!hash || (enabled && enabled.value.kind !== "boolean"))
1909
+ return null;
1910
+ setOwnRecordValue(entries, property.key, {
1911
+ trusted_hash: hash,
1912
+ ...(enabled?.value.kind === "boolean" ? { enabled: enabled.value.value } : {}),
1913
+ });
1914
+ }
1915
+ return entries;
210
1916
  }
211
- export function getManagedCodexHookCommandsForEvent(content, eventName) {
212
- const parsed = parseCodexHooksConfig(content);
213
- if (!parsed)
1917
+ function identicalLegacyTrustStateEntry(left, right) {
1918
+ return left.trusted_hash === right.trusted_hash &&
1919
+ Object.hasOwn(left, "enabled") === Object.hasOwn(right, "enabled") &&
1920
+ left.enabled === right.enabled;
1921
+ }
1922
+ function mergeLegacyTrustState(target, incoming, source) {
1923
+ const conflictingKeys = [];
1924
+ for (const [key, entry] of Object.entries(incoming)) {
1925
+ const existing = Object.hasOwn(target, key) ? target[key] : undefined;
1926
+ if (existing === undefined) {
1927
+ setOwnRecordValue(target, key, entry);
1928
+ continue;
1929
+ }
1930
+ if (!identicalLegacyTrustStateEntry(existing, entry))
1931
+ conflictingKeys.push(key);
1932
+ }
1933
+ if (conflictingKeys.length === 0)
214
1934
  return null;
215
- const entries = Array.isArray(parsed.hooks[eventName])
216
- ? parsed.hooks[eventName]
1935
+ const keys = conflictingKeys.sort((left, right) => left.localeCompare(right));
1936
+ return planError("managed_trust_key_conflict", `Conflicting legacy hook trust state appears in root state and ${source}.`, { keys, source });
1937
+ }
1938
+ function prepareLegacyState(content) {
1939
+ const root = parseJsonSource(content);
1940
+ if (!root || root.kind !== "object") {
1941
+ return { ok: false, error: planError("invalid_document", "hooks.json must contain a JSON object.") };
1942
+ }
1943
+ const legacyTrustState = {};
1944
+ const rootStates = root.properties.filter((property) => property.key === "state");
1945
+ if (rootStates.length > 1) {
1946
+ return { ok: false, error: planError("invalid_document", "Duplicate top-level state is not supported.") };
1947
+ }
1948
+ if (rootStates.length === 1) {
1949
+ const exact = exactLegacyState(rootStates[0].value);
1950
+ if (!exact) {
1951
+ return { ok: false, error: planError("invalid_document", "Top-level state is not an exact historical OMX trust map.") };
1952
+ }
1953
+ const conflict = mergeLegacyTrustState(legacyTrustState, exact, "root");
1954
+ if (conflict)
1955
+ return { ok: false, error: conflict };
1956
+ }
1957
+ const hooks = objectProperty(root, "hooks");
1958
+ const nestedStates = hooks?.value.kind === "object"
1959
+ ? hooks.value.properties.filter((property) => property.key === "state")
217
1960
  : [];
218
- const commands = [];
219
- for (const entry of entries) {
220
- if (!isPlainObject(entry) || !Array.isArray(entry.hooks))
221
- continue;
222
- for (const hook of entry.hooks) {
223
- if (isPlainObject(hook) &&
224
- hook.type === "command" &&
225
- typeof hook.command === "string" &&
226
- isOmxManagedHookCommand(hook.command)) {
227
- commands.push(hook.command);
228
- }
1961
+ const exactNestedStates = nestedStates.map((state) => exactLegacyState(state.value));
1962
+ let nestedStatesToRemove = 0;
1963
+ if (nestedStates.length > 1 && exactNestedStates.some((state) => state === null)) {
1964
+ return {
1965
+ ok: false,
1966
+ error: planError("invalid_document", "Duplicate hooks.state entries must be exact historical OMX trust maps."),
1967
+ };
1968
+ }
1969
+ if (nestedStates.length === 0 || exactNestedStates[0] !== null) {
1970
+ for (const exact of exactNestedStates) {
1971
+ if (!exact)
1972
+ continue;
1973
+ const conflict = mergeLegacyTrustState(legacyTrustState, exact, "hooks.state");
1974
+ if (conflict)
1975
+ return { ok: false, error: conflict };
1976
+ }
1977
+ nestedStatesToRemove = exactNestedStates.length;
1978
+ }
1979
+ let preparedContent = content;
1980
+ if (rootStates.length === 1) {
1981
+ const currentRoot = parseJsonSource(preparedContent);
1982
+ preparedContent = applySourcePatches(preparedContent, [
1983
+ propertyRemovalPatch(currentRoot, objectProperty(currentRoot, "state")),
1984
+ ]);
1985
+ }
1986
+ for (let index = 0; index < nestedStatesToRemove; index += 1) {
1987
+ const currentRoot = parseJsonSource(preparedContent);
1988
+ const currentHooks = objectProperty(currentRoot, "hooks")?.value;
1989
+ if (currentHooks?.kind !== "object") {
1990
+ return { ok: false, error: planError("invalid_document", "hooks.json changed while preparing legacy state.") };
1991
+ }
1992
+ const currentState = objectProperty(currentHooks, "state");
1993
+ if (!currentState) {
1994
+ return { ok: false, error: planError("invalid_document", "hooks.state changed while preparing legacy state.") };
229
1995
  }
1996
+ preparedContent = applySourcePatches(preparedContent, [propertyRemovalPatch(currentHooks, currentState)]);
230
1997
  }
231
- return commands;
1998
+ return { content: preparedContent, legacyTrustState };
232
1999
  }
233
- function stripManagedHooksFromEntry(entry) {
234
- if (!isPlainObject(entry) || !Array.isArray(entry.hooks)) {
235
- return { entry: cloneJson(entry), removedCount: 0 };
2000
+ function trustFromDocument(document, hooksPath, options) {
2001
+ const ownership = inspectOwnership(document, options);
2002
+ if (ownership instanceof ManagedCodexHooksPlanError)
2003
+ return { ok: false, error: ownership };
2004
+ const trustState = {};
2005
+ for (const owner of ownership.owners) {
2006
+ const group = owner.groupNode;
2007
+ const handler = owner.handlerNode;
2008
+ const command = effectiveCommandForPlatform(handler, options.platform ?? process.platform);
2009
+ const timeout = objectProperty(handler, "timeout");
2010
+ const timeoutValue = timeout?.value.kind === "number"
2011
+ ? timeout.value.raw === "0"
2012
+ ? 1
2013
+ : new CanonicalJsonNumber(timeout.value.raw)
2014
+ : 600;
2015
+ const status = nullableStringProperty(handler, "statusMessage");
2016
+ const matcher = matcherIsAware(owner.eventName) ? groupMatcher(group) : undefined;
2017
+ setOwnRecordValue(trustState, managedHookStateKey(hooksPath, owner.eventName, owner.groupIndex, owner.handlerIndex), {
2018
+ trusted_hash: versionForCodexTomlIdentity({
2019
+ event_name: CODEX_HOOK_EVENT_LABELS[owner.eventName],
2020
+ ...(matcher ? { matcher } : {}),
2021
+ hooks: [{
2022
+ type: "command",
2023
+ command,
2024
+ timeout: timeoutValue,
2025
+ async: false,
2026
+ ...(status !== undefined && status !== null ? { statusMessage: status } : {}),
2027
+ }],
2028
+ }),
2029
+ });
236
2030
  }
237
- const nextHooks = entry.hooks.filter((hook) => {
238
- if (!isPlainObject(hook))
239
- return true;
240
- return !(hook.type === "command" &&
241
- typeof hook.command === "string" &&
242
- isOmxManagedHookCommand(hook.command));
243
- });
244
- const removedCount = entry.hooks.length - nextHooks.length;
245
- if (removedCount === 0) {
246
- return { entry: cloneJson(entry), removedCount: 0 };
2031
+ return {
2032
+ ok: true,
2033
+ trustState,
2034
+ groupOccurrences: document.result.groupOccurrences,
2035
+ handlerOccurrences: document.result.handlerOccurrences,
2036
+ };
2037
+ }
2038
+ export function scanManagedCodexHookTrustStateFromContent(content, hooksPath, options = {}) {
2039
+ const validation = validateCodexHooksDocument(content, options);
2040
+ if (!("result" in validation))
2041
+ return validation;
2042
+ return trustFromDocument(validation, hooksPath, options);
2043
+ }
2044
+ function hasExecutableForeignHandlerInUnknownEvent(event, platform) {
2045
+ if (KNOWN_HOOK_EVENT_FIELDS.has(event.key) || event.value.kind !== "array")
2046
+ return false;
2047
+ for (const group of event.value.elements) {
2048
+ if (group.kind !== "object")
2049
+ continue;
2050
+ const hooks = objectProperty(group, "hooks")?.value;
2051
+ if (hooks?.kind !== "array")
2052
+ continue;
2053
+ for (const handler of hooks.elements) {
2054
+ if (handler.kind !== "object")
2055
+ continue;
2056
+ const type = stringProperty(handler, "type");
2057
+ if (type === "prompt" || type === "agent")
2058
+ return true;
2059
+ if (type !== "command")
2060
+ continue;
2061
+ const asynchronous = objectProperty(handler, "async");
2062
+ if (asynchronous?.value.kind === "boolean" && asynchronous.value.value)
2063
+ continue;
2064
+ const command = platform === "win32"
2065
+ ? nullableStringProperty(handler, "commandWindows") ??
2066
+ nullableStringProperty(handler, "command_windows") ??
2067
+ stringProperty(handler, "command")
2068
+ : stringProperty(handler, "command");
2069
+ if (typeof command === "string" && !commandIsSkipped(command))
2070
+ return true;
2071
+ }
247
2072
  }
248
- if (nextHooks.length === 0) {
249
- return { entry: null, removedCount };
2073
+ return false;
2074
+ }
2075
+ function foreignHooksInDocument(document, options) {
2076
+ const ownership = inspectOwnership(document, options);
2077
+ return ownership instanceof ManagedCodexHooksPlanError ||
2078
+ ownership.groups.some((group) => group.foreign) ||
2079
+ document.hooksNode?.properties.some((event) => hasExecutableForeignHandlerInUnknownEvent(event, options.platform ?? process.platform)) === true;
2080
+ }
2081
+ function finalizePlan(originalContent, finalContent, removedCount, coordinateProof, legacyTrustState, hooksPath, options, priorTrustState) {
2082
+ if (finalContent === null) {
2083
+ return {
2084
+ ok: true,
2085
+ finalContent: null,
2086
+ changed: originalContent !== null && originalContent !== undefined,
2087
+ removedCount,
2088
+ finalTrustState: {},
2089
+ priorTrustState,
2090
+ hasForeignHooks: false,
2091
+ coordinateProof,
2092
+ diagnostics: [],
2093
+ legacyTrustState,
2094
+ };
250
2095
  }
2096
+ const validation = validateCodexHooksDocument(finalContent, options);
2097
+ if (!("result" in validation))
2098
+ return planFailure(validation.error);
2099
+ const scan = trustFromDocument(validation, hooksPath, options);
2100
+ if (!scan.ok)
2101
+ return planFailure(scan.error, validation.result.diagnostics);
251
2102
  return {
252
- entry: {
253
- ...cloneJson(entry),
254
- hooks: nextHooks,
255
- },
2103
+ ok: true,
2104
+ finalContent,
2105
+ changed: finalContent !== originalContent,
256
2106
  removedCount,
2107
+ finalTrustState: scan.trustState,
2108
+ priorTrustState,
2109
+ hasForeignHooks: foreignHooksInDocument(validation, options),
2110
+ coordinateProof,
2111
+ diagnostics: validation.result.diagnostics,
2112
+ legacyTrustState,
257
2113
  };
258
2114
  }
259
- function serializeCodexHooksConfig(root) {
260
- return JSON.stringify(root, null, 2) + "\n";
2115
+ export function planManagedCodexHooksMerge(existingContent, pkgRoot, hooksPath, options = {}) {
2116
+ const resolvedOptions = resolveManagedCodexHookOptions(options, hooksPath);
2117
+ if (typeof existingContent !== "string") {
2118
+ const content = serializeCodexHooksConfig(jsonObjectFromUnknown(buildManagedCodexHooksConfig(pkgRoot, resolvedOptions)));
2119
+ return finalizePlan(existingContent, content, 0, { safe: true }, {}, hooksPath, resolvedOptions, {});
2120
+ }
2121
+ const prepared = prepareLegacyState(existingContent);
2122
+ if (!("content" in prepared))
2123
+ return planFailure(prepared.error);
2124
+ const validation = validateCodexHooksDocument(prepared.content, resolvedOptions);
2125
+ if (!("result" in validation))
2126
+ return planFailure(validation.error);
2127
+ const priorScan = trustFromDocument(validation, hooksPath, resolvedOptions);
2128
+ if (!priorScan.ok)
2129
+ return planFailure(priorScan.error, validation.result.diagnostics);
2130
+ const ownership = inspectOwnership(validation, resolvedOptions);
2131
+ if (ownership instanceof ManagedCodexHooksPlanError)
2132
+ return planFailure(ownership, validation.result.diagnostics);
2133
+ const managed = buildManagedCodexHooksConfig(pkgRoot, resolvedOptions);
2134
+ const patches = [];
2135
+ const missingEventProperties = [];
2136
+ for (const eventName of MANAGED_HOOK_EVENTS) {
2137
+ const eventOwners = ownership.owners.filter((owner) => owner.eventName === eventName)
2138
+ .sort((left, right) => left.groupIndex - right.groupIndex || left.handlerIndex - right.handlerIndex);
2139
+ const canonicalEntry = managed.hooks[eventName][0];
2140
+ if (eventOwners.length === 0) {
2141
+ const eventArray = eventArrayForGroup(validation, eventName);
2142
+ if (eventArray) {
2143
+ patches.push(appendArrayElementPatch(eventArray, JSON.stringify(canonicalGroupForEvent(canonicalEntry))));
2144
+ }
2145
+ else if (validation.hooksNode) {
2146
+ missingEventProperties.push([
2147
+ eventName,
2148
+ JSON.stringify([canonicalGroupForEvent(canonicalEntry)]),
2149
+ ]);
2150
+ }
2151
+ continue;
2152
+ }
2153
+ const primary = eventOwners[0];
2154
+ patches.push(...knownFieldPatches(prepared.content, primary.handlerNode, canonicalHookForEvent(canonicalEntry), KNOWN_COMMAND_FIELDS));
2155
+ const primaryGroup = ownership.groups.find((group) => group.node === primary.groupNode);
2156
+ if (!primaryGroup?.foreign) {
2157
+ patches.push(...knownFieldPatches(prepared.content, primary.groupNode, canonicalMatcherForEvent(canonicalEntry), KNOWN_MATCHER_FIELD));
2158
+ }
2159
+ }
2160
+ if (validation.hooksNode && missingEventProperties.length > 0) {
2161
+ patches.push(appendObjectPropertiesPatch(validation.hooksNode, missingEventProperties));
2162
+ }
2163
+ if (!validation.hooksNode)
2164
+ patches.push(appendObjectPropertyPatch(validation.rootNode, "hooks", JSON.stringify(managed.hooks)));
2165
+ let intermediate;
2166
+ try {
2167
+ intermediate = applySourcePatches(prepared.content, patches);
2168
+ }
2169
+ catch (error) {
2170
+ return planFailure(planError("invalid_document", "Overlapping source edits prevented a safe merge.", { cause: String(error) }), validation.result.diagnostics);
2171
+ }
2172
+ const afterMerge = validateCodexHooksDocument(intermediate, resolvedOptions);
2173
+ if (!("result" in afterMerge))
2174
+ return planFailure(afterMerge.error);
2175
+ const afterOwnership = inspectOwnership(afterMerge, resolvedOptions);
2176
+ if (afterOwnership instanceof ManagedCodexHooksPlanError)
2177
+ return planFailure(afterOwnership, afterMerge.result.diagnostics);
2178
+ const removeOwners = new Set();
2179
+ for (const eventName of MANAGED_HOOK_EVENTS) {
2180
+ const eventOwners = afterOwnership.owners.filter((owner) => owner.eventName === eventName)
2181
+ .sort((left, right) => left.groupIndex - right.groupIndex || left.handlerIndex - right.handlerIndex);
2182
+ for (const duplicate of eventOwners.slice(1))
2183
+ removeOwners.add(duplicate);
2184
+ }
2185
+ const proof = removalProof(afterOwnership, removeOwners);
2186
+ if (!proof.safe) {
2187
+ return {
2188
+ ok: false,
2189
+ error: planError("unsafe_managed_removal", "Removing duplicate OMX hooks would shift a foreign coordinate or discard opaque metadata.", proof.shifted ? { shifted: proof.shifted } : {}),
2190
+ diagnostics: afterMerge.result.diagnostics,
2191
+ };
2192
+ }
2193
+ let finalContent;
2194
+ try {
2195
+ finalContent = applyCoordinateSafeRemovals(intermediate, afterOwnership, removeOwners, resolvedOptions);
2196
+ }
2197
+ catch (error) {
2198
+ return planFailure(planError("invalid_document", "Source edits prevented a safe duplicate cleanup.", { cause: String(error) }), afterMerge.result.diagnostics);
2199
+ }
2200
+ if (finalContent instanceof ManagedCodexHooksPlanError)
2201
+ return planFailure(finalContent, afterMerge.result.diagnostics);
2202
+ return finalizePlan(existingContent, finalContent, removeOwners.size, proof, prepared.legacyTrustState, hooksPath, resolvedOptions, priorScan.trustState);
261
2203
  }
262
- function canonicalJson(value) {
263
- if (Array.isArray(value)) {
264
- return value.map((item) => canonicalJson(item));
2204
+ export function planManagedCodexHooksRemoval(existingContent, hooksPath, options = {}) {
2205
+ const resolvedOptions = resolveManagedCodexHookOptions(options, hooksPath);
2206
+ const prepared = prepareLegacyState(existingContent);
2207
+ if (!("content" in prepared))
2208
+ return planFailure(prepared.error);
2209
+ const validation = validateCodexHooksDocument(prepared.content, resolvedOptions);
2210
+ if (!("result" in validation))
2211
+ return planFailure(validation.error);
2212
+ const priorScan = trustFromDocument(validation, hooksPath, resolvedOptions);
2213
+ if (!priorScan.ok)
2214
+ return planFailure(priorScan.error, validation.result.diagnostics);
2215
+ const ownership = inspectOwnership(validation, resolvedOptions);
2216
+ if (ownership instanceof ManagedCodexHooksPlanError)
2217
+ return planFailure(ownership, validation.result.diagnostics);
2218
+ const removeOwners = new Set(ownership.owners);
2219
+ const proof = removalProof(ownership, removeOwners);
2220
+ if (!proof.safe) {
2221
+ return {
2222
+ ok: false,
2223
+ error: planError("unsafe_managed_removal", "Removing OMX hooks would shift a foreign coordinate or discard opaque metadata.", proof.shifted ? { shifted: proof.shifted } : {}),
2224
+ diagnostics: validation.result.diagnostics,
2225
+ };
265
2226
  }
266
- if (isPlainObject(value)) {
267
- return Object.fromEntries(Object.keys(value)
268
- .sort()
269
- .map((key) => [key, canonicalJson(value[key])]));
2227
+ let finalContent;
2228
+ try {
2229
+ finalContent = applyCoordinateSafeRemovals(prepared.content, ownership, removeOwners, resolvedOptions);
270
2230
  }
271
- return value;
2231
+ catch (error) {
2232
+ return planFailure(planError("invalid_document", "Source edits prevented safe removal.", { cause: String(error) }), validation.result.diagnostics);
2233
+ }
2234
+ if (finalContent instanceof ManagedCodexHooksPlanError)
2235
+ return planFailure(finalContent, validation.result.diagnostics);
2236
+ return finalizePlan(existingContent, finalContent, removeOwners.size, proof, prepared.legacyTrustState, hooksPath, resolvedOptions, priorScan.trustState);
272
2237
  }
273
- function versionForCodexTomlIdentity(value) {
274
- const canonical = canonicalJson(value);
275
- const serialized = JSON.stringify(canonical);
276
- return `sha256:${createHash("sha256").update(serialized).digest("hex")}`;
2238
+ function codexHookEntries(hooks, eventName) {
2239
+ const entries = hooks[eventName];
2240
+ return isJsonArray(entries) ? entries : [];
277
2241
  }
278
- function normalizedCommandHookIdentity(eventName, entry, hook) {
279
- return {
280
- event_name: CODEX_HOOK_EVENT_LABELS[eventName],
281
- ...(entry.matcher ? { matcher: entry.matcher } : {}),
282
- hooks: [
283
- {
284
- type: "command",
285
- command: hook.command,
286
- timeout: Math.max(1, hook.timeout ?? 600),
287
- async: false,
288
- ...(hook.statusMessage ? { statusMessage: hook.statusMessage } : {}),
289
- },
290
- ],
291
- };
2242
+ export function getMissingManagedCodexHookEvents(content) {
2243
+ const parsed = parseCodexHooksConfig(content);
2244
+ if (!parsed)
2245
+ return null;
2246
+ return MANAGED_HOOK_EVENTS.filter((eventName) => !codexHookEntries(parsed.hooks, eventName).some((entry) => isPlainObject(entry) && isJsonArray(entry.hooks) && entry.hooks.some((hook) => isPlainObject(hook) && hook.type === "command" && typeof hook.command === "string" &&
2247
+ isValidManagedCommand(hook.command, process.platform))));
292
2248
  }
293
- function managedHookStateKey(hooksPath, eventName, groupIndex, handlerIndex) {
294
- return `${hooksPath}:${CODEX_HOOK_EVENT_LABELS[eventName]}:${groupIndex}:${handlerIndex}`;
2249
+ export function getManagedCodexHookCommandsForEvent(content, eventName) {
2250
+ const parsed = parseCodexHooksConfig(content);
2251
+ if (!parsed)
2252
+ return null;
2253
+ return codexHookEntries(parsed.hooks, eventName).flatMap((entry) => isPlainObject(entry) && isJsonArray(entry.hooks)
2254
+ ? entry.hooks.flatMap((hook) => isPlainObject(hook) && hook.type === "command" && typeof hook.command === "string" &&
2255
+ isValidManagedCommand(hook.command, process.platform)
2256
+ ? [hook.command]
2257
+ : [])
2258
+ : []);
295
2259
  }
296
2260
  export function buildManagedCodexHookTrustState(hooksPath, pkgRoot, options = {}) {
297
- const managedConfig = buildManagedCodexHooksConfig(pkgRoot, {
298
- ...options,
299
- ...(options.platform === "win32" && !options.codexHomeDir
300
- ? { codexHomeDir: dirname(hooksPath) }
301
- : {}),
302
- });
303
- const state = {};
304
- for (const eventName of MANAGED_HOOK_EVENTS) {
305
- const entries = managedConfig.hooks[eventName];
306
- entries.forEach((entry, groupIndex) => {
307
- entry.hooks.forEach((hook, handlerIndex) => {
308
- if (hook.type !== "command" || !isOmxManagedHookCommand(hook.command)) {
309
- return;
310
- }
311
- const key = managedHookStateKey(hooksPath, eventName, groupIndex, handlerIndex);
312
- state[key] = {
313
- trusted_hash: versionForCodexTomlIdentity(normalizedCommandHookIdentity(eventName, entry, hook)),
314
- };
315
- });
316
- });
317
- }
318
- return state;
2261
+ const resolvedOptions = resolveManagedCodexHookOptions(options, hooksPath);
2262
+ if (options.hooksContent === null)
2263
+ return {};
2264
+ const content = typeof options.hooksContent === "string"
2265
+ ? options.hooksContent
2266
+ : serializeCodexHooksConfig(jsonObjectFromUnknown(buildManagedCodexHooksConfig(pkgRoot, resolvedOptions)));
2267
+ const scan = scanManagedCodexHookTrustStateFromContent(content, hooksPath, resolvedOptions);
2268
+ if (!scan.ok)
2269
+ throw scan.error;
2270
+ return scan.trustState;
319
2271
  }
320
2272
  export function buildManagedCodexHookTrustToml(hooksPath, pkgRoot, options = {}) {
321
2273
  if (!hooksPath)
@@ -426,10 +2378,10 @@ function collectTrustStateEntries(value) {
426
2378
  if (!isPlainObject(rawEntry) || typeof rawEntry.trusted_hash !== "string") {
427
2379
  continue;
428
2380
  }
429
- entries[key] = {
2381
+ setOwnRecordValue(entries, key, {
430
2382
  trusted_hash: rawEntry.trusted_hash,
431
2383
  ...(typeof rawEntry.enabled === "boolean" ? { enabled: rawEntry.enabled } : {}),
432
- };
2384
+ });
433
2385
  }
434
2386
  return entries;
435
2387
  }
@@ -451,110 +2403,35 @@ export function hasCodexHooksJsonTopLevelState(content) {
451
2403
  return Object.hasOwn(parsed.root, "state");
452
2404
  }
453
2405
  export function mergeManagedCodexHooksConfig(existingContent, pkgRoot, hooksPathOrOptions, options = {}) {
454
- const hooksPath = typeof hooksPathOrOptions === "string" ? hooksPathOrOptions : undefined;
2406
+ const hooksPath = typeof hooksPathOrOptions === "string" ? hooksPathOrOptions : "";
455
2407
  const providedOptions = typeof hooksPathOrOptions === "object" && hooksPathOrOptions !== null
456
2408
  ? hooksPathOrOptions
457
2409
  : options;
458
- const resolvedOptions = {
459
- ...providedOptions,
460
- ...(hooksPath && providedOptions.platform === "win32" && !providedOptions.codexHomeDir
461
- ? { codexHomeDir: dirname(hooksPath) }
462
- : {}),
463
- };
464
- const managedConfig = buildManagedCodexHooksConfig(pkgRoot, resolvedOptions);
465
- const parsed = typeof existingContent === "string"
466
- ? parseCodexHooksConfig(existingContent)
467
- : null;
468
- const nextRoot = parsed ? cloneJson(parsed.root) : {};
469
- const nextHooks = parsed ? cloneJson(parsed.hooks) : {};
470
- delete nextRoot.state;
471
- delete nextHooks.state;
472
- for (const eventName of MANAGED_HOOK_EVENTS) {
473
- const existingEntries = Array.isArray(nextHooks[eventName])
474
- ? nextHooks[eventName]
475
- : [];
476
- const preservedEntries = [];
477
- for (const entry of existingEntries) {
478
- const stripped = stripManagedHooksFromEntry(entry);
479
- if (stripped.entry !== null) {
480
- preservedEntries.push(stripped.entry);
481
- }
482
- }
483
- nextHooks[eventName] = [
484
- ...preservedEntries,
485
- ...managedConfig.hooks[eventName].map((entry) => cloneJson(entry)),
486
- ];
487
- }
488
- if (Object.keys(nextHooks).length > 0) {
489
- nextRoot.hooks = nextHooks;
490
- }
491
- else {
492
- delete nextRoot.hooks;
493
- }
494
- return serializeCodexHooksConfig(nextRoot);
2410
+ const plan = planManagedCodexHooksMerge(existingContent, pkgRoot, hooksPath, providedOptions);
2411
+ if (!plan.ok)
2412
+ throw plan.error;
2413
+ return plan.finalContent ?? "";
495
2414
  }
496
2415
  export function removeManagedCodexHooks(existingContent) {
497
- const parsed = parseCodexHooksConfig(existingContent);
498
- if (!parsed) {
499
- return { nextContent: existingContent, removedCount: 0 };
500
- }
501
- const nextRoot = cloneJson(parsed.root);
502
- const nextHooks = cloneJson(parsed.hooks);
503
- delete nextRoot.state;
504
- delete nextHooks.state;
505
- let removedCount = 0;
506
- for (const [eventName, rawEntries] of Object.entries(nextHooks)) {
507
- if (!Array.isArray(rawEntries))
508
- continue;
509
- const preservedEntries = [];
510
- for (const entry of rawEntries) {
511
- const stripped = stripManagedHooksFromEntry(entry);
512
- removedCount += stripped.removedCount;
513
- if (stripped.entry !== null) {
514
- preservedEntries.push(stripped.entry);
515
- }
516
- }
517
- if (preservedEntries.length > 0) {
518
- nextHooks[eventName] = preservedEntries;
519
- }
520
- else {
521
- delete nextHooks[eventName];
522
- }
523
- }
524
- if (removedCount === 0) {
525
- return { nextContent: existingContent, removedCount: 0 };
526
- }
527
- const hasRemainingHookEntries = Object.keys(nextHooks).length > 0;
528
- if (hasRemainingHookEntries) {
529
- nextRoot.hooks = nextHooks;
530
- }
531
- else {
532
- delete nextRoot.hooks;
533
- }
534
- if (Object.keys(nextRoot).length === 0) {
535
- return { nextContent: null, removedCount };
536
- }
537
- return {
538
- nextContent: serializeCodexHooksConfig(nextRoot),
539
- removedCount,
540
- };
2416
+ const plan = planManagedCodexHooksRemoval(existingContent, "");
2417
+ if (!plan.ok)
2418
+ throw plan.error;
2419
+ return { nextContent: plan.finalContent, removedCount: plan.removedCount };
541
2420
  }
542
2421
  export function hasCodexHookEntries(content) {
543
2422
  const parsed = parseCodexHooksConfig(content);
544
2423
  if (!parsed)
545
2424
  return false;
546
2425
  return Object.entries(parsed.hooks).some(([eventName, rawEntries]) => {
547
- if (eventName === "state" || !Array.isArray(rawEntries))
2426
+ if (eventName === "state" || !isJsonArray(rawEntries))
548
2427
  return false;
549
- return rawEntries.some((entry) => {
550
- return isPlainObject(entry) &&
551
- Array.isArray(entry.hooks) &&
552
- entry.hooks.length > 0;
553
- });
2428
+ return rawEntries.some((entry) => isPlainObject(entry) && isJsonArray(entry.hooks) && entry.hooks.length > 0);
554
2429
  });
555
2430
  }
556
2431
  export function hasUserCodexHooksAfterManagedRemoval(existingContent) {
557
- const { nextContent } = removeManagedCodexHooks(existingContent);
558
- return nextContent !== null && hasCodexHookEntries(nextContent);
2432
+ const plan = planManagedCodexHooksRemoval(existingContent, "");
2433
+ if (!plan.ok)
2434
+ throw plan.error;
2435
+ return plan.finalContent !== null && hasCodexHookEntries(plan.finalContent);
559
2436
  }
560
2437
  //# sourceMappingURL=codex-hooks.js.map