oh-my-codex 0.18.6 → 0.18.8

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 (444) hide show
  1. package/Cargo.lock +6 -6
  2. package/Cargo.toml +1 -1
  3. package/README.md +59 -10
  4. package/crates/omx-sparkshell/tests/execution.rs +1 -1
  5. package/dist/agents/__tests__/definitions.test.js +11 -0
  6. package/dist/agents/__tests__/definitions.test.js.map +1 -1
  7. package/dist/agents/__tests__/native-config.test.js +56 -6
  8. package/dist/agents/__tests__/native-config.test.js.map +1 -1
  9. package/dist/agents/definitions.d.ts +10 -0
  10. package/dist/agents/definitions.d.ts.map +1 -1
  11. package/dist/agents/definitions.js +5 -1
  12. package/dist/agents/definitions.js.map +1 -1
  13. package/dist/agents/native-config.d.ts +5 -1
  14. package/dist/agents/native-config.d.ts.map +1 -1
  15. package/dist/agents/native-config.js +19 -4
  16. package/dist/agents/native-config.js.map +1 -1
  17. package/dist/autopilot/__tests__/fsm.test.d.ts +2 -0
  18. package/dist/autopilot/__tests__/fsm.test.d.ts.map +1 -0
  19. package/dist/autopilot/__tests__/fsm.test.js +75 -0
  20. package/dist/autopilot/__tests__/fsm.test.js.map +1 -0
  21. package/dist/autopilot/__tests__/ralplan-gate.test.d.ts +2 -0
  22. package/dist/autopilot/__tests__/ralplan-gate.test.d.ts.map +1 -0
  23. package/dist/autopilot/__tests__/ralplan-gate.test.js +79 -0
  24. package/dist/autopilot/__tests__/ralplan-gate.test.js.map +1 -0
  25. package/dist/autopilot/deep-interview-gate.d.ts +18 -0
  26. package/dist/autopilot/deep-interview-gate.d.ts.map +1 -0
  27. package/dist/autopilot/deep-interview-gate.js +256 -0
  28. package/dist/autopilot/deep-interview-gate.js.map +1 -0
  29. package/dist/autopilot/fsm.d.ts +13 -0
  30. package/dist/autopilot/fsm.d.ts.map +1 -0
  31. package/dist/autopilot/fsm.js +70 -0
  32. package/dist/autopilot/fsm.js.map +1 -0
  33. package/dist/autopilot/ralplan-gate.d.ts +17 -0
  34. package/dist/autopilot/ralplan-gate.d.ts.map +1 -0
  35. package/dist/autopilot/ralplan-gate.js +61 -0
  36. package/dist/autopilot/ralplan-gate.js.map +1 -0
  37. package/dist/cli/__tests__/codex-plugin-layout.test.js +512 -1
  38. package/dist/cli/__tests__/codex-plugin-layout.test.js.map +1 -1
  39. package/dist/cli/__tests__/doctor-warning-copy.test.js +39 -0
  40. package/dist/cli/__tests__/doctor-warning-copy.test.js.map +1 -1
  41. package/dist/cli/__tests__/index.test.js +83 -7
  42. package/dist/cli/__tests__/index.test.js.map +1 -1
  43. package/dist/cli/__tests__/launch-fallback.test.js +175 -6
  44. package/dist/cli/__tests__/launch-fallback.test.js.map +1 -1
  45. package/dist/cli/__tests__/package-bin-contract.test.js +8 -4
  46. package/dist/cli/__tests__/package-bin-contract.test.js.map +1 -1
  47. package/dist/cli/__tests__/question.test.js +100 -0
  48. package/dist/cli/__tests__/question.test.js.map +1 -1
  49. package/dist/cli/__tests__/ralph-goal-mode-contract.test.js +13 -0
  50. package/dist/cli/__tests__/ralph-goal-mode-contract.test.js.map +1 -1
  51. package/dist/cli/__tests__/ralph.test.js +14 -0
  52. package/dist/cli/__tests__/ralph.test.js.map +1 -1
  53. package/dist/cli/__tests__/setup-install-mode.test.js +89 -0
  54. package/dist/cli/__tests__/setup-install-mode.test.js.map +1 -1
  55. package/dist/cli/__tests__/setup-refresh.test.js +83 -0
  56. package/dist/cli/__tests__/setup-refresh.test.js.map +1 -1
  57. package/dist/cli/__tests__/state.test.js +21 -0
  58. package/dist/cli/__tests__/state.test.js.map +1 -1
  59. package/dist/cli/__tests__/team.test.js +2 -2
  60. package/dist/cli/__tests__/team.test.js.map +1 -1
  61. package/dist/cli/__tests__/update.test.js +110 -2
  62. package/dist/cli/__tests__/update.test.js.map +1 -1
  63. package/dist/cli/doctor.d.ts.map +1 -1
  64. package/dist/cli/doctor.js +8 -1
  65. package/dist/cli/doctor.js.map +1 -1
  66. package/dist/cli/index.d.ts +14 -3
  67. package/dist/cli/index.d.ts.map +1 -1
  68. package/dist/cli/index.js +298 -50
  69. package/dist/cli/index.js.map +1 -1
  70. package/dist/cli/plugin-marketplace.d.ts +14 -2
  71. package/dist/cli/plugin-marketplace.d.ts.map +1 -1
  72. package/dist/cli/plugin-marketplace.js +62 -15
  73. package/dist/cli/plugin-marketplace.js.map +1 -1
  74. package/dist/cli/question.d.ts.map +1 -1
  75. package/dist/cli/question.js +36 -5
  76. package/dist/cli/question.js.map +1 -1
  77. package/dist/cli/ralph.d.ts.map +1 -1
  78. package/dist/cli/ralph.js +3 -1
  79. package/dist/cli/ralph.js.map +1 -1
  80. package/dist/cli/setup-preferences.d.ts +2 -0
  81. package/dist/cli/setup-preferences.d.ts.map +1 -1
  82. package/dist/cli/setup-preferences.js +4 -0
  83. package/dist/cli/setup-preferences.js.map +1 -1
  84. package/dist/cli/setup.d.ts +3 -0
  85. package/dist/cli/setup.d.ts.map +1 -1
  86. package/dist/cli/setup.js +166 -27
  87. package/dist/cli/setup.js.map +1 -1
  88. package/dist/cli/state.d.ts.map +1 -1
  89. package/dist/cli/state.js +8 -1
  90. package/dist/cli/state.js.map +1 -1
  91. package/dist/cli/tmux-hook.d.ts.map +1 -1
  92. package/dist/cli/tmux-hook.js +16 -0
  93. package/dist/cli/tmux-hook.js.map +1 -1
  94. package/dist/cli/update.d.ts +2 -0
  95. package/dist/cli/update.d.ts.map +1 -1
  96. package/dist/cli/update.js +47 -3
  97. package/dist/cli/update.js.map +1 -1
  98. package/dist/config/__tests__/deep-interview.test.js +7 -6
  99. package/dist/config/__tests__/deep-interview.test.js.map +1 -1
  100. package/dist/config/__tests__/generator-notify.test.js +1 -0
  101. package/dist/config/__tests__/generator-notify.test.js.map +1 -1
  102. package/dist/config/deep-interview.d.ts.map +1 -1
  103. package/dist/config/deep-interview.js +14 -4
  104. package/dist/config/deep-interview.js.map +1 -1
  105. package/dist/config/generator.d.ts +2 -2
  106. package/dist/config/generator.d.ts.map +1 -1
  107. package/dist/config/generator.js +2 -2
  108. package/dist/config/generator.js.map +1 -1
  109. package/dist/config/team-mode.d.ts +12 -0
  110. package/dist/config/team-mode.d.ts.map +1 -0
  111. package/dist/config/team-mode.js +91 -0
  112. package/dist/config/team-mode.js.map +1 -0
  113. package/dist/hooks/__tests__/agents-overlay.test.js +88 -0
  114. package/dist/hooks/__tests__/agents-overlay.test.js.map +1 -1
  115. package/dist/hooks/__tests__/autopilot-skill-contract.test.js +8 -0
  116. package/dist/hooks/__tests__/autopilot-skill-contract.test.js.map +1 -1
  117. package/dist/hooks/__tests__/code-review-skill-contract.test.js +8 -0
  118. package/dist/hooks/__tests__/code-review-skill-contract.test.js.map +1 -1
  119. package/dist/hooks/__tests__/deep-interview-contract.test.js +10 -0
  120. package/dist/hooks/__tests__/deep-interview-contract.test.js.map +1 -1
  121. package/dist/hooks/__tests__/keyword-detector.test.js +1072 -14
  122. package/dist/hooks/__tests__/keyword-detector.test.js.map +1 -1
  123. package/dist/hooks/__tests__/notify-fallback-watcher.test.js +64 -1
  124. package/dist/hooks/__tests__/notify-fallback-watcher.test.js.map +1 -1
  125. package/dist/hooks/__tests__/notify-hook-auto-nudge.test.js +189 -0
  126. package/dist/hooks/__tests__/notify-hook-auto-nudge.test.js.map +1 -1
  127. package/dist/hooks/__tests__/notify-hook-team-leader-nudge.test.js +35 -2
  128. package/dist/hooks/__tests__/notify-hook-team-leader-nudge.test.js.map +1 -1
  129. package/dist/hooks/__tests__/notify-hook-tmux-heal.test.js +3 -3
  130. package/dist/hooks/__tests__/notify-hook-tmux-heal.test.js.map +1 -1
  131. package/dist/hooks/__tests__/session.test.js +25 -0
  132. package/dist/hooks/__tests__/session.test.js.map +1 -1
  133. package/dist/hooks/__tests__/skill-guidance-contract.test.js +21 -0
  134. package/dist/hooks/__tests__/skill-guidance-contract.test.js.map +1 -1
  135. package/dist/hooks/agents-overlay.d.ts.map +1 -1
  136. package/dist/hooks/agents-overlay.js +36 -50
  137. package/dist/hooks/agents-overlay.js.map +1 -1
  138. package/dist/hooks/deep-interview-config-instruction.js +1 -1
  139. package/dist/hooks/deep-interview-config-instruction.js.map +1 -1
  140. package/dist/hooks/extensibility/__tests__/plugin-runner.test.js +31 -0
  141. package/dist/hooks/extensibility/__tests__/plugin-runner.test.js.map +1 -1
  142. package/dist/hooks/extensibility/plugin-runner.js +17 -21
  143. package/dist/hooks/extensibility/plugin-runner.js.map +1 -1
  144. package/dist/hooks/keyword-detector.d.ts +1 -0
  145. package/dist/hooks/keyword-detector.d.ts.map +1 -1
  146. package/dist/hooks/keyword-detector.js +428 -32
  147. package/dist/hooks/keyword-detector.js.map +1 -1
  148. package/dist/hooks/keyword-registry.d.ts.map +1 -1
  149. package/dist/hooks/keyword-registry.js +1 -0
  150. package/dist/hooks/keyword-registry.js.map +1 -1
  151. package/dist/hooks/prompt-guidance-contract.d.ts.map +1 -1
  152. package/dist/hooks/prompt-guidance-contract.js +6 -0
  153. package/dist/hooks/prompt-guidance-contract.js.map +1 -1
  154. package/dist/hooks/session.d.ts +3 -0
  155. package/dist/hooks/session.d.ts.map +1 -1
  156. package/dist/hooks/session.js +13 -5
  157. package/dist/hooks/session.js.map +1 -1
  158. package/dist/hud/__tests__/authority.test.js +469 -31
  159. package/dist/hud/__tests__/authority.test.js.map +1 -1
  160. package/dist/hud/__tests__/hud-tmux-injection.test.js +2 -1
  161. package/dist/hud/__tests__/hud-tmux-injection.test.js.map +1 -1
  162. package/dist/hud/__tests__/index.test.js +210 -2
  163. package/dist/hud/__tests__/index.test.js.map +1 -1
  164. package/dist/hud/__tests__/reconcile.test.js +588 -28
  165. package/dist/hud/__tests__/reconcile.test.js.map +1 -1
  166. package/dist/hud/__tests__/render.test.js +61 -0
  167. package/dist/hud/__tests__/render.test.js.map +1 -1
  168. package/dist/hud/__tests__/state.test.js +208 -0
  169. package/dist/hud/__tests__/state.test.js.map +1 -1
  170. package/dist/hud/__tests__/tmux.test.js +314 -22
  171. package/dist/hud/__tests__/tmux.test.js.map +1 -1
  172. package/dist/hud/authority.d.ts +5 -0
  173. package/dist/hud/authority.d.ts.map +1 -1
  174. package/dist/hud/authority.js +337 -30
  175. package/dist/hud/authority.js.map +1 -1
  176. package/dist/hud/index.d.ts +20 -2
  177. package/dist/hud/index.d.ts.map +1 -1
  178. package/dist/hud/index.js +103 -26
  179. package/dist/hud/index.js.map +1 -1
  180. package/dist/hud/reconcile.d.ts +3 -3
  181. package/dist/hud/reconcile.d.ts.map +1 -1
  182. package/dist/hud/reconcile.js +129 -20
  183. package/dist/hud/reconcile.js.map +1 -1
  184. package/dist/hud/render.d.ts.map +1 -1
  185. package/dist/hud/render.js +35 -0
  186. package/dist/hud/render.js.map +1 -1
  187. package/dist/hud/state.d.ts.map +1 -1
  188. package/dist/hud/state.js +64 -50
  189. package/dist/hud/state.js.map +1 -1
  190. package/dist/hud/tmux.d.ts +26 -6
  191. package/dist/hud/tmux.d.ts.map +1 -1
  192. package/dist/hud/tmux.js +173 -38
  193. package/dist/hud/tmux.js.map +1 -1
  194. package/dist/hud/types.d.ts +11 -0
  195. package/dist/hud/types.d.ts.map +1 -1
  196. package/dist/hud/types.js.map +1 -1
  197. package/dist/mcp/__tests__/hermes-bridge.test.js +203 -7
  198. package/dist/mcp/__tests__/hermes-bridge.test.js.map +1 -1
  199. package/dist/mcp/__tests__/state-paths.test.js +71 -1
  200. package/dist/mcp/__tests__/state-paths.test.js.map +1 -1
  201. package/dist/mcp/__tests__/state-server.test.js +13 -1
  202. package/dist/mcp/__tests__/state-server.test.js.map +1 -1
  203. package/dist/mcp/hermes-bridge.d.ts +12 -2
  204. package/dist/mcp/hermes-bridge.d.ts.map +1 -1
  205. package/dist/mcp/hermes-bridge.js +83 -9
  206. package/dist/mcp/hermes-bridge.js.map +1 -1
  207. package/dist/mcp/state-paths.d.ts +32 -0
  208. package/dist/mcp/state-paths.d.ts.map +1 -1
  209. package/dist/mcp/state-paths.js +113 -17
  210. package/dist/mcp/state-paths.js.map +1 -1
  211. package/dist/mcp/state-server.d.ts +4 -4
  212. package/dist/modes/__tests__/base-autoresearch-contract.test.js +7 -1
  213. package/dist/modes/__tests__/base-autoresearch-contract.test.js.map +1 -1
  214. package/dist/pipeline/__tests__/stages.test.js +130 -0
  215. package/dist/pipeline/__tests__/stages.test.js.map +1 -1
  216. package/dist/pipeline/orchestrator.js +1 -1
  217. package/dist/pipeline/orchestrator.js.map +1 -1
  218. package/dist/pipeline/stages/ralplan.d.ts +1 -0
  219. package/dist/pipeline/stages/ralplan.d.ts.map +1 -1
  220. package/dist/pipeline/stages/ralplan.js +14 -5
  221. package/dist/pipeline/stages/ralplan.js.map +1 -1
  222. package/dist/question/__tests__/deep-interview.test.js +160 -2
  223. package/dist/question/__tests__/deep-interview.test.js.map +1 -1
  224. package/dist/question/__tests__/policy.test.js +63 -3
  225. package/dist/question/__tests__/policy.test.js.map +1 -1
  226. package/dist/question/__tests__/renderer.test.js +191 -2
  227. package/dist/question/__tests__/renderer.test.js.map +1 -1
  228. package/dist/question/__tests__/state.test.js +94 -3
  229. package/dist/question/__tests__/state.test.js.map +1 -1
  230. package/dist/question/__tests__/ui.test.js +4 -0
  231. package/dist/question/__tests__/ui.test.js.map +1 -1
  232. package/dist/question/autopilot-wait.d.ts +12 -2
  233. package/dist/question/autopilot-wait.d.ts.map +1 -1
  234. package/dist/question/autopilot-wait.js +158 -47
  235. package/dist/question/autopilot-wait.js.map +1 -1
  236. package/dist/question/deep-interview.d.ts.map +1 -1
  237. package/dist/question/deep-interview.js +22 -6
  238. package/dist/question/deep-interview.js.map +1 -1
  239. package/dist/question/policy.d.ts.map +1 -1
  240. package/dist/question/policy.js +2 -5
  241. package/dist/question/policy.js.map +1 -1
  242. package/dist/question/renderer.d.ts +12 -0
  243. package/dist/question/renderer.d.ts.map +1 -1
  244. package/dist/question/renderer.js +87 -3
  245. package/dist/question/renderer.js.map +1 -1
  246. package/dist/question/state.d.ts +8 -1
  247. package/dist/question/state.d.ts.map +1 -1
  248. package/dist/question/state.js +54 -14
  249. package/dist/question/state.js.map +1 -1
  250. package/dist/question/types.d.ts +1 -1
  251. package/dist/question/types.d.ts.map +1 -1
  252. package/dist/question/ui.d.ts +1 -0
  253. package/dist/question/ui.d.ts.map +1 -1
  254. package/dist/question/ui.js +1 -0
  255. package/dist/question/ui.js.map +1 -1
  256. package/dist/ralplan/__tests__/runtime.test.js +191 -0
  257. package/dist/ralplan/__tests__/runtime.test.js.map +1 -1
  258. package/dist/ralplan/consensus-gate.d.ts +9 -1
  259. package/dist/ralplan/consensus-gate.d.ts.map +1 -1
  260. package/dist/ralplan/consensus-gate.js +84 -2
  261. package/dist/ralplan/consensus-gate.js.map +1 -1
  262. package/dist/ralplan/runtime.d.ts +9 -0
  263. package/dist/ralplan/runtime.d.ts.map +1 -1
  264. package/dist/ralplan/runtime.js +32 -11
  265. package/dist/ralplan/runtime.js.map +1 -1
  266. package/dist/scripts/__tests__/codex-native-hook.test.js +2315 -280
  267. package/dist/scripts/__tests__/codex-native-hook.test.js.map +1 -1
  268. package/dist/scripts/__tests__/notify-state-io.test.js +72 -1
  269. package/dist/scripts/__tests__/notify-state-io.test.js.map +1 -1
  270. package/dist/scripts/__tests__/notify-tmux-injection.test.d.ts +2 -0
  271. package/dist/scripts/__tests__/notify-tmux-injection.test.d.ts.map +1 -0
  272. package/dist/scripts/__tests__/notify-tmux-injection.test.js +57 -0
  273. package/dist/scripts/__tests__/notify-tmux-injection.test.js.map +1 -0
  274. package/dist/scripts/__tests__/run-test-files.test.js +74 -0
  275. package/dist/scripts/__tests__/run-test-files.test.js.map +1 -1
  276. package/dist/scripts/__tests__/verify-native-agents.test.js +65 -0
  277. package/dist/scripts/__tests__/verify-native-agents.test.js.map +1 -1
  278. package/dist/scripts/codex-native-hook.d.ts.map +1 -1
  279. package/dist/scripts/codex-native-hook.js +431 -56
  280. package/dist/scripts/codex-native-hook.js.map +1 -1
  281. package/dist/scripts/codex-native-pre-post.d.ts.map +1 -1
  282. package/dist/scripts/codex-native-pre-post.js +79 -1
  283. package/dist/scripts/codex-native-pre-post.js.map +1 -1
  284. package/dist/scripts/eval/eval-parity-smoke.js +1 -1
  285. package/dist/scripts/eval/eval-parity-smoke.js.map +1 -1
  286. package/dist/scripts/hook-payload-guard.d.ts +9 -0
  287. package/dist/scripts/hook-payload-guard.d.ts.map +1 -0
  288. package/dist/scripts/hook-payload-guard.js +111 -0
  289. package/dist/scripts/hook-payload-guard.js.map +1 -0
  290. package/dist/scripts/notify-fallback-watcher.js +8 -1
  291. package/dist/scripts/notify-fallback-watcher.js.map +1 -1
  292. package/dist/scripts/notify-hook/__tests__/payload-guard.test.d.ts +2 -0
  293. package/dist/scripts/notify-hook/__tests__/payload-guard.test.d.ts.map +1 -0
  294. package/dist/scripts/notify-hook/__tests__/payload-guard.test.js +39 -0
  295. package/dist/scripts/notify-hook/__tests__/payload-guard.test.js.map +1 -0
  296. package/dist/scripts/notify-hook/auto-nudge.d.ts.map +1 -1
  297. package/dist/scripts/notify-hook/auto-nudge.js +3 -1
  298. package/dist/scripts/notify-hook/auto-nudge.js.map +1 -1
  299. package/dist/scripts/notify-hook/ralph-session-resume.d.ts.map +1 -1
  300. package/dist/scripts/notify-hook/ralph-session-resume.js +3 -10
  301. package/dist/scripts/notify-hook/ralph-session-resume.js.map +1 -1
  302. package/dist/scripts/notify-hook/state-io.d.ts.map +1 -1
  303. package/dist/scripts/notify-hook/state-io.js +62 -38
  304. package/dist/scripts/notify-hook/state-io.js.map +1 -1
  305. package/dist/scripts/notify-hook/team-leader-nudge.d.ts.map +1 -1
  306. package/dist/scripts/notify-hook/team-leader-nudge.js +7 -0
  307. package/dist/scripts/notify-hook/team-leader-nudge.js.map +1 -1
  308. package/dist/scripts/notify-hook/team-worker-stop.d.ts.map +1 -1
  309. package/dist/scripts/notify-hook/team-worker-stop.js +234 -86
  310. package/dist/scripts/notify-hook/team-worker-stop.js.map +1 -1
  311. package/dist/scripts/notify-hook/tmux-injection.d.ts +7 -0
  312. package/dist/scripts/notify-hook/tmux-injection.d.ts.map +1 -1
  313. package/dist/scripts/notify-hook/tmux-injection.js +24 -18
  314. package/dist/scripts/notify-hook/tmux-injection.js.map +1 -1
  315. package/dist/scripts/notify-hook.js +86 -13
  316. package/dist/scripts/notify-hook.js.map +1 -1
  317. package/dist/scripts/run-test-files.js +193 -22
  318. package/dist/scripts/run-test-files.js.map +1 -1
  319. package/dist/scripts/sync-plugin-mirror.d.ts.map +1 -1
  320. package/dist/scripts/sync-plugin-mirror.js +61 -3
  321. package/dist/scripts/sync-plugin-mirror.js.map +1 -1
  322. package/dist/scripts/verify-native-agents.d.ts.map +1 -1
  323. package/dist/scripts/verify-native-agents.js +58 -1
  324. package/dist/scripts/verify-native-agents.js.map +1 -1
  325. package/dist/state/__tests__/operations.test.js +1125 -1
  326. package/dist/state/__tests__/operations.test.js.map +1 -1
  327. package/dist/state/__tests__/skill-active.test.js +46 -1
  328. package/dist/state/__tests__/skill-active.test.js.map +1 -1
  329. package/dist/state/__tests__/workflow-transition.test.js +98 -7
  330. package/dist/state/__tests__/workflow-transition.test.js.map +1 -1
  331. package/dist/state/operations.d.ts.map +1 -1
  332. package/dist/state/operations.js +159 -2
  333. package/dist/state/operations.js.map +1 -1
  334. package/dist/state/skill-active.js +6 -8
  335. package/dist/state/skill-active.js.map +1 -1
  336. package/dist/state/workflow-transition-reconcile.d.ts +6 -0
  337. package/dist/state/workflow-transition-reconcile.d.ts.map +1 -1
  338. package/dist/state/workflow-transition-reconcile.js +38 -15
  339. package/dist/state/workflow-transition-reconcile.js.map +1 -1
  340. package/dist/state/workflow-transition.d.ts.map +1 -1
  341. package/dist/state/workflow-transition.js +10 -3
  342. package/dist/state/workflow-transition.js.map +1 -1
  343. package/dist/subagents/__tests__/tracker.test.js +139 -0
  344. package/dist/subagents/__tests__/tracker.test.js.map +1 -1
  345. package/dist/subagents/tracker.d.ts +3 -0
  346. package/dist/subagents/tracker.d.ts.map +1 -1
  347. package/dist/subagents/tracker.js +41 -4
  348. package/dist/subagents/tracker.js.map +1 -1
  349. package/dist/team/__tests__/coordination-protocol.test.d.ts +2 -0
  350. package/dist/team/__tests__/coordination-protocol.test.d.ts.map +1 -0
  351. package/dist/team/__tests__/coordination-protocol.test.js +173 -0
  352. package/dist/team/__tests__/coordination-protocol.test.js.map +1 -0
  353. package/dist/team/__tests__/runtime.test.js +52 -3
  354. package/dist/team/__tests__/runtime.test.js.map +1 -1
  355. package/dist/team/__tests__/scaling.test.js +9 -4
  356. package/dist/team/__tests__/scaling.test.js.map +1 -1
  357. package/dist/team/__tests__/state.test.js +83 -0
  358. package/dist/team/__tests__/state.test.js.map +1 -1
  359. package/dist/team/__tests__/tmux-session.test.js +240 -2
  360. package/dist/team/__tests__/tmux-session.test.js.map +1 -1
  361. package/dist/team/__tests__/worker-bootstrap.test.js +84 -0
  362. package/dist/team/__tests__/worker-bootstrap.test.js.map +1 -1
  363. package/dist/team/__tests__/worker-runtime-identity.test.js +4 -2
  364. package/dist/team/__tests__/worker-runtime-identity.test.js.map +1 -1
  365. package/dist/team/coordination-protocol.d.ts +14 -0
  366. package/dist/team/coordination-protocol.d.ts.map +1 -0
  367. package/dist/team/coordination-protocol.js +244 -0
  368. package/dist/team/coordination-protocol.js.map +1 -0
  369. package/dist/team/runtime.d.ts +1 -0
  370. package/dist/team/runtime.d.ts.map +1 -1
  371. package/dist/team/runtime.js +19 -3
  372. package/dist/team/runtime.js.map +1 -1
  373. package/dist/team/scaling.d.ts.map +1 -1
  374. package/dist/team/scaling.js +3 -2
  375. package/dist/team/scaling.js.map +1 -1
  376. package/dist/team/state/tasks.d.ts.map +1 -1
  377. package/dist/team/state/tasks.js +24 -0
  378. package/dist/team/state/tasks.js.map +1 -1
  379. package/dist/team/state/types.d.ts +21 -1
  380. package/dist/team/state/types.d.ts.map +1 -1
  381. package/dist/team/state/types.js.map +1 -1
  382. package/dist/team/state.d.ts +17 -1
  383. package/dist/team/state.d.ts.map +1 -1
  384. package/dist/team/state.js +12 -5
  385. package/dist/team/state.js.map +1 -1
  386. package/dist/team/team-ops.d.ts +1 -1
  387. package/dist/team/team-ops.d.ts.map +1 -1
  388. package/dist/team/team-ops.js.map +1 -1
  389. package/dist/team/tmux-session.d.ts +2 -0
  390. package/dist/team/tmux-session.d.ts.map +1 -1
  391. package/dist/team/tmux-session.js +161 -13
  392. package/dist/team/tmux-session.js.map +1 -1
  393. package/dist/team/worker-bootstrap.d.ts.map +1 -1
  394. package/dist/team/worker-bootstrap.js +63 -0
  395. package/dist/team/worker-bootstrap.js.map +1 -1
  396. package/dist/utils/__tests__/agents-model-table.test.js +4 -2
  397. package/dist/utils/__tests__/agents-model-table.test.js.map +1 -1
  398. package/dist/utils/agents-model-table.d.ts.map +1 -1
  399. package/dist/utils/agents-model-table.js +3 -0
  400. package/dist/utils/agents-model-table.js.map +1 -1
  401. package/dist/verification/__tests__/ci-rust-gates.test.js +81 -1
  402. package/dist/verification/__tests__/ci-rust-gates.test.js.map +1 -1
  403. package/package.json +8 -8
  404. package/plugins/oh-my-codex/.codex-plugin/plugin.json +1 -1
  405. package/plugins/oh-my-codex/hooks/codex-native-hook.mjs +334 -21
  406. package/plugins/oh-my-codex/hooks/hooks.json +1 -2
  407. package/plugins/oh-my-codex/skills/autopilot/SKILL.md +13 -6
  408. package/plugins/oh-my-codex/skills/code-review/SKILL.md +7 -7
  409. package/plugins/oh-my-codex/skills/deep-interview/SKILL.md +9 -4
  410. package/plugins/oh-my-codex/skills/ralph/SKILL.md +22 -22
  411. package/plugins/oh-my-codex/skills/ralplan/SKILL.md +12 -0
  412. package/plugins/oh-my-codex/skills/team/SKILL.md +16 -0
  413. package/plugins/oh-my-codex/skills/ultraqa/SKILL.md +9 -0
  414. package/plugins/oh-my-codex/skills/worker/SKILL.md +14 -0
  415. package/skills/autopilot/SKILL.md +13 -6
  416. package/skills/code-review/SKILL.md +7 -7
  417. package/skills/deep-interview/SKILL.md +9 -4
  418. package/skills/ralph/SKILL.md +22 -22
  419. package/skills/ralplan/SKILL.md +12 -0
  420. package/skills/team/SKILL.md +16 -0
  421. package/skills/ultraqa/SKILL.md +9 -0
  422. package/skills/worker/SKILL.md +14 -0
  423. package/src/scripts/__tests__/codex-native-hook.test.ts +4435 -2083
  424. package/src/scripts/__tests__/notify-state-io.test.ts +95 -0
  425. package/src/scripts/__tests__/notify-tmux-injection.test.ts +82 -0
  426. package/src/scripts/__tests__/run-test-files.test.ts +102 -0
  427. package/src/scripts/__tests__/verify-native-agents.test.ts +75 -0
  428. package/src/scripts/codex-native-hook.ts +536 -51
  429. package/src/scripts/codex-native-pre-post.ts +80 -0
  430. package/src/scripts/demo-team-e2e.sh +10 -7
  431. package/src/scripts/eval/eval-parity-smoke.ts +1 -1
  432. package/src/scripts/hook-payload-guard.ts +113 -0
  433. package/src/scripts/notify-fallback-watcher.ts +8 -1
  434. package/src/scripts/notify-hook/__tests__/payload-guard.test.ts +41 -0
  435. package/src/scripts/notify-hook/auto-nudge.ts +3 -1
  436. package/src/scripts/notify-hook/ralph-session-resume.ts +2 -8
  437. package/src/scripts/notify-hook/state-io.ts +75 -37
  438. package/src/scripts/notify-hook/team-leader-nudge.ts +7 -0
  439. package/src/scripts/notify-hook/team-worker-stop.ts +193 -52
  440. package/src/scripts/notify-hook/tmux-injection.ts +35 -19
  441. package/src/scripts/notify-hook.ts +105 -6
  442. package/src/scripts/run-test-files.ts +192 -22
  443. package/src/scripts/sync-plugin-mirror.ts +98 -9
  444. package/src/scripts/verify-native-agents.ts +65 -1
@@ -1,9 +1,10 @@
1
- import { spawnSync } from 'node:child_process';
1
+ import { spawn, spawnSync, type ChildProcess } from 'node:child_process';
2
2
  import { readdirSync, statSync } from 'node:fs';
3
3
  import { join, resolve } from 'node:path';
4
4
 
5
5
  const DEFAULT_TEST_TIMEOUT_MS = 0;
6
6
  const DEFAULT_RUNNER_TIMEOUT_MS = 30 * 60 * 1_000;
7
+ const DEFAULT_FORCE_EXIT_GRACE_MS = 30_000;
7
8
  const DEFAULT_CI_TEST_CONCURRENCY = 1;
8
9
  const RUNTIME_STATE_ENV_KEYS = [
9
10
  'OMX_ROOT',
@@ -47,6 +48,10 @@ function parseTimeoutMs(value: string | undefined, defaultTimeoutMs: number): nu
47
48
  return Math.floor(parsed);
48
49
  }
49
50
 
51
+ function isWindows(): boolean {
52
+ return process.platform === 'win32';
53
+ }
54
+
50
55
  function parseTestConcurrency(env: NodeJS.ProcessEnv): number | undefined {
51
56
  const rawValue = env.OMX_NODE_TEST_CONCURRENCY;
52
57
  if (rawValue) {
@@ -74,6 +79,7 @@ if (files.length === 0) {
74
79
 
75
80
  const testTimeoutMs = parseTimeoutMs(process.env.OMX_NODE_TEST_TIMEOUT_MS, DEFAULT_TEST_TIMEOUT_MS);
76
81
  const runnerTimeoutMs = parseTimeoutMs(process.env.OMX_NODE_TEST_RUNNER_TIMEOUT_MS, DEFAULT_RUNNER_TIMEOUT_MS);
82
+ const forceExitGraceMs = parseTimeoutMs(process.env.OMX_NODE_TEST_FORCE_EXIT_GRACE_MS, DEFAULT_FORCE_EXIT_GRACE_MS);
77
83
  const testConcurrency = parseTestConcurrency(process.env);
78
84
  const forceExit = parseBooleanEnv(process.env.OMX_NODE_TEST_FORCE_EXIT);
79
85
  const testArgs = ['--test'];
@@ -84,7 +90,7 @@ if (testConcurrency) {
84
90
  testArgs.push(`--test-concurrency=${testConcurrency}`);
85
91
  }
86
92
  if (forceExit) {
87
- testArgs.push('--test-force-exit');
93
+ testArgs.push('--test-force-exit', '--test-reporter=tap');
88
94
  }
89
95
  testArgs.push(...files);
90
96
 
@@ -92,7 +98,7 @@ console.error(
92
98
  `[run-test-files] running ${files.length} test file(s) from ${targets.join(', ')}${
93
99
  testTimeoutMs > 0 ? ` with per-test timeout ${testTimeoutMs}ms` : ' with per-test timeout disabled'
94
100
  }${testConcurrency ? `, test concurrency ${testConcurrency}` : ', default test concurrency'}${
95
- forceExit ? ', force exit enabled' : ', force exit disabled'
101
+ forceExit ? `, force exit enabled with ${forceExitGraceMs}ms completion grace` : ', force exit disabled'
96
102
  }${runnerTimeoutMs > 0 ? `, and runner timeout ${runnerTimeoutMs}ms` : ', and runner timeout disabled'}`,
97
103
  );
98
104
 
@@ -105,26 +111,190 @@ if (!parseBooleanEnv(process.env.OMX_NODE_TEST_PRESERVE_RUNTIME_ENV)) {
105
111
  }
106
112
  }
107
113
 
108
- const result = spawnSync(process.execPath, testArgs, {
109
- stdio: 'inherit',
110
- env: childEnv,
111
- timeout: runnerTimeoutMs > 0 ? runnerTimeoutMs : undefined,
112
- killSignal: 'SIGTERM',
113
- });
114
+ function reportAbnormalExit(signal: NodeJS.Signals | null, errorMessage?: string): void {
115
+ if (errorMessage) {
116
+ console.error(`[run-test-files] node --test error: ${errorMessage}`);
117
+ }
118
+ console.error(
119
+ `[run-test-files] node --test did not exit normally${signal ? ` (signal: ${signal})` : ''}. `
120
+ + `Roots: ${targets.join(', ')}. Test files: ${files.length}. `
121
+ + `Per-test timeout: ${testTimeoutMs > 0 ? `${testTimeoutMs}ms` : 'disabled'}. `
122
+ + `Test concurrency: ${testConcurrency ?? 'default'}. `
123
+ + `Force exit: ${forceExit ? 'enabled' : 'disabled'}. `
124
+ + `Runner timeout: ${runnerTimeoutMs > 0 ? `${runnerTimeoutMs}ms` : 'disabled'}.`,
125
+ );
126
+ }
114
127
 
115
- if (typeof result.status === 'number') {
116
- process.exit(result.status);
128
+ function signalChild(child: ChildProcess, signal: NodeJS.Signals): void {
129
+ try {
130
+ if (!isWindows() && child.pid) {
131
+ process.kill(-child.pid, signal);
132
+ } else {
133
+ child.kill(signal);
134
+ }
135
+ } catch {
136
+ try {
137
+ child.kill(signal);
138
+ } catch {
139
+ // Ignore kill races. The child might have exited between detection and termination.
140
+ }
141
+ }
117
142
  }
118
143
 
119
- if (result.error) {
120
- console.error(`[run-test-files] node --test error: ${result.error.message}`);
144
+ function terminateChild(child: ChildProcess): void {
145
+ signalChild(child, 'SIGTERM');
146
+ signalChild(child, 'SIGKILL');
147
+ }
148
+
149
+ function runWithCompletionForceExit(): void {
150
+ let finished = false;
151
+ let sawFailure = false;
152
+ let lastTapOk = 0;
153
+ let tapTests: number | undefined;
154
+ let tapPass: number | undefined;
155
+ let tapFail = 0;
156
+ let tapCancelled = 0;
157
+ let completedFromSummary = false;
158
+ let completionTimer: NodeJS.Timeout | undefined;
159
+ let runnerTimer: NodeJS.Timeout | undefined;
160
+ let stdoutRemainder = '';
161
+ let stderrRemainder = '';
162
+
163
+ const child = spawn(process.execPath, testArgs, {
164
+ stdio: ['ignore', 'pipe', 'pipe'],
165
+ env: childEnv,
166
+ detached: !isWindows(),
167
+ });
168
+
169
+ function finish(exitCode: number, reason: string): void {
170
+ if (finished) return;
171
+ finished = true;
172
+ if (completionTimer) clearTimeout(completionTimer);
173
+ if (runnerTimer) clearTimeout(runnerTimer);
174
+ console.error(`[run-test-files] ${reason}; exiting with status ${exitCode}`);
175
+ terminateChild(child);
176
+ child.stdout?.destroy();
177
+ child.stderr?.destroy();
178
+ child.unref();
179
+ process.exit(exitCode);
180
+ }
181
+
182
+ function markFailure(): void {
183
+ sawFailure = true;
184
+ if (completionTimer) {
185
+ clearTimeout(completionTimer);
186
+ completionTimer = undefined;
187
+ }
188
+ }
189
+
190
+ function armCompletionTimer(reason: string): void {
191
+ if (sawFailure) return;
192
+ if (completionTimer) clearTimeout(completionTimer);
193
+ completionTimer = setTimeout(() => {
194
+ if (sawFailure) return;
195
+ finish(0, reason);
196
+ }, forceExitGraceMs);
197
+ }
198
+
199
+ function sawCleanTapSummary(): boolean {
200
+ if (tapTests === undefined || tapPass === undefined) return false;
201
+ return tapTests === tapPass && tapFail === 0 && tapCancelled === 0;
202
+ }
203
+
204
+ function parseTapLine(line: string): void {
205
+ if (/^(?:not ok|Bail out!)/.test(line)) {
206
+ markFailure();
207
+ return;
208
+ }
209
+
210
+ const summary = line.match(/^# (tests|pass|fail|cancelled) (\d+)$/);
211
+ if (summary) {
212
+ const count = Number(summary[2]);
213
+ if (summary[1] === 'tests') tapTests = count;
214
+ if (summary[1] === 'pass') tapPass = count;
215
+ if (summary[1] === 'fail') tapFail = count;
216
+ if (summary[1] === 'cancelled') tapCancelled = count;
217
+ if ((summary[1] === 'fail' || summary[1] === 'cancelled') && count > 0) markFailure();
218
+ return;
219
+ }
220
+
221
+ const ok = line.match(/^ok (\d+)\b/);
222
+ if (ok) {
223
+ lastTapOk = Number(ok[1]);
224
+ if (lastTapOk >= files.length) {
225
+ armCompletionTimer(`force-exit completion grace elapsed after TAP ok ${lastTapOk} with no later failures`);
226
+ }
227
+ return;
228
+ }
229
+
230
+ const plan = line.match(/^1\.\.(\d+)$/);
231
+ if (plan && Number(plan[1]) === lastTapOk && !sawFailure) {
232
+ armCompletionTimer(`force-exit completion grace elapsed after TAP plan ${line}`);
233
+ return;
234
+ }
235
+
236
+ if (/^# duration_ms /.test(line) && sawCleanTapSummary()) {
237
+ completedFromSummary = true;
238
+ armCompletionTimer('force-exit completion grace elapsed after clean TAP summary');
239
+ }
240
+ }
241
+
242
+ function handleOutput(chunk: Buffer, stream: NodeJS.WriteStream, isStdout: boolean): void {
243
+ stream.write(chunk);
244
+ const text = chunk.toString('utf8');
245
+ let combined = (isStdout ? stdoutRemainder : stderrRemainder) + text;
246
+ const lines = combined.split(/\r?\n/);
247
+ combined = lines.pop() ?? '';
248
+ if (isStdout) {
249
+ stdoutRemainder = combined;
250
+ } else {
251
+ stderrRemainder = combined;
252
+ }
253
+ for (const line of lines) parseTapLine(line);
254
+ }
255
+
256
+ child.stdout?.on('data', (chunk: Buffer) => handleOutput(chunk, process.stdout, true));
257
+ child.stderr?.on('data', (chunk: Buffer) => handleOutput(chunk, process.stderr, false));
258
+
259
+ child.on('error', (error) => {
260
+ reportAbnormalExit(null, error.message);
261
+ finish(1, 'node --test failed to spawn');
262
+ });
263
+
264
+ child.on('exit', (status, signal) => {
265
+ if (finished) return;
266
+ if (stdoutRemainder) parseTapLine(stdoutRemainder);
267
+ if (stderrRemainder) parseTapLine(stderrRemainder);
268
+ if (typeof status === 'number') {
269
+ finish(status, `node --test exited normally${completedFromSummary ? ' after clean TAP summary' : ''}`);
270
+ return;
271
+ }
272
+ reportAbnormalExit(signal);
273
+ finish(1, 'node --test exited without a numeric status');
274
+ });
275
+
276
+ if (runnerTimeoutMs > 0) {
277
+ runnerTimer = setTimeout(() => {
278
+ reportAbnormalExit(null);
279
+ finish(1, `runner timeout ${runnerTimeoutMs}ms elapsed`);
280
+ }, runnerTimeoutMs);
281
+ }
282
+ }
283
+
284
+ if (forceExit) {
285
+ runWithCompletionForceExit();
286
+ } else {
287
+ const result = spawnSync(process.execPath, testArgs, {
288
+ stdio: 'inherit',
289
+ env: childEnv,
290
+ timeout: runnerTimeoutMs > 0 ? runnerTimeoutMs : undefined,
291
+ killSignal: 'SIGTERM',
292
+ });
293
+
294
+ if (typeof result.status === 'number') {
295
+ process.exit(result.status);
296
+ }
297
+
298
+ reportAbnormalExit(result.signal, result.error?.message);
299
+ process.exit(1);
121
300
  }
122
- console.error(
123
- `[run-test-files] node --test did not exit normally${result.signal ? ` (signal: ${result.signal})` : ''}. `
124
- + `Roots: ${targets.join(', ')}. Test files: ${files.length}. `
125
- + `Per-test timeout: ${testTimeoutMs > 0 ? `${testTimeoutMs}ms` : 'disabled'}. `
126
- + `Test concurrency: ${testConcurrency ?? 'default'}. `
127
- + `Force exit: ${forceExit ? 'enabled' : 'disabled'}. `
128
- + `Runner timeout: ${runnerTimeoutMs > 0 ? `${runnerTimeoutMs}ms` : 'disabled'}.`,
129
- );
130
- process.exit(1);
@@ -13,6 +13,7 @@ import {
13
13
  assertSkillMirror,
14
14
  compareSkillMirror,
15
15
  } from "../catalog/skill-mirror.js";
16
+ import { MANAGED_HOOK_EVENTS } from "../config/codex-hooks.js";
16
17
  import { buildOmxPluginMcpManifest } from "../config/omx-first-party-mcp.js";
17
18
 
18
19
  export interface SyncPluginMirrorOptions {
@@ -56,6 +57,15 @@ const SETUP_OWNED_PLUGIN_MANIFEST_FIELDS = [
56
57
  "agents",
57
58
  "prompts",
58
59
  ] as const;
60
+ const OMX_PLUGIN_HOOK_COMMAND =
61
+ 'node "${PLUGIN_ROOT}/hooks/codex-native-hook.mjs"';
62
+ const OMX_PLUGIN_HOOK_LAUNCHER_CONTRACT_MARKER =
63
+ "omx-plugin-hook-launcher:v1";
64
+ // Plugin-scoped Codex hooks intentionally mirror the setup-managed lifecycle
65
+ // roster today while using PLUGIN_ROOT-local launch commands. If plugin and
66
+ // setup hook coverage diverge, split this alias into a plugin-owned roster.
67
+ const PLUGIN_HOOK_EVENTS = MANAGED_HOOK_EVENTS;
68
+ type PluginHookEventName = (typeof PLUGIN_HOOK_EVENTS)[number];
59
69
 
60
70
  async function readJsonFile<T>(path: string): Promise<T> {
61
71
  return JSON.parse(await readFile(path, "utf-8")) as T;
@@ -65,6 +75,35 @@ function stringifyJson(value: unknown): string {
65
75
  return `${JSON.stringify(value, null, 2)}\n`;
66
76
  }
67
77
 
78
+ function commandHook(timeout?: number): JsonValue {
79
+ return {
80
+ type: "command",
81
+ command: OMX_PLUGIN_HOOK_COMMAND,
82
+ ...(typeof timeout === "number" ? { timeout } : {}),
83
+ };
84
+ }
85
+
86
+ function pluginHookEntry(eventName: PluginHookEventName): JsonValue {
87
+ const base = {
88
+ hooks: [commandHook(eventName === "Stop" ? 30 : undefined)],
89
+ };
90
+ if (eventName === "SessionStart") {
91
+ return { ...base, matcher: "startup|resume|clear" };
92
+ }
93
+ return base;
94
+ }
95
+
96
+ function buildOmxPluginHooksManifest(): JsonValue {
97
+ return {
98
+ hooks: Object.fromEntries(
99
+ PLUGIN_HOOK_EVENTS.map((eventName) => [
100
+ eventName,
101
+ [pluginHookEntry(eventName)],
102
+ ]),
103
+ ),
104
+ };
105
+ }
106
+
68
107
  function assertDeepJsonEqual(
69
108
  actual: unknown,
70
109
  expected: unknown,
@@ -84,12 +123,36 @@ function assertDeepJsonEqual(
84
123
  }
85
124
  }
86
125
 
126
+ function assertPluginHookLauncherContractMarkerPresent(
127
+ path: string,
128
+ content: string,
129
+ ): void {
130
+ const requiredMarkers = [
131
+ OMX_PLUGIN_HOOK_LAUNCHER_CONTRACT_MARKER,
132
+ ];
133
+ const missingMarkers = requiredMarkers.filter(
134
+ (marker) => !content.includes(marker),
135
+ );
136
+ if (missingMarkers.length > 0) {
137
+ throw new Error(
138
+ [
139
+ "plugin_bundle_metadata_out_of_sync",
140
+ "kind=hook-launcher",
141
+ `path=${path}`,
142
+ `missingMarkers=${JSON.stringify(missingMarkers)}`,
143
+ ].join("\n"),
144
+ );
145
+ }
146
+ }
147
+
87
148
  function getPluginPaths(root: string): {
88
149
  pluginRoot: string;
89
150
  pluginSkillsDir: string;
90
151
  pluginMcpPath: string;
91
152
  pluginAppsPath: string;
92
153
  pluginManifestPath: string;
154
+ pluginHooksPath: string;
155
+ pluginHookLauncherPath: string;
93
156
  } {
94
157
  const pluginRoot = join(root, "plugins", PLUGIN_NAME);
95
158
  return {
@@ -98,6 +161,8 @@ function getPluginPaths(root: string): {
98
161
  pluginMcpPath: join(pluginRoot, ".mcp.json"),
99
162
  pluginAppsPath: join(pluginRoot, ".app.json"),
100
163
  pluginManifestPath: join(pluginRoot, ".codex-plugin", "plugin.json"),
164
+ pluginHooksPath: join(pluginRoot, "hooks", "hooks.json"),
165
+ pluginHookLauncherPath: join(pluginRoot, "hooks", "codex-native-hook.mjs"),
101
166
  };
102
167
  }
103
168
 
@@ -188,6 +253,7 @@ async function buildExpectedPluginManifest(
188
253
  skills: "./skills/",
189
254
  mcpServers: "./.mcp.json",
190
255
  apps: "./.app.json",
256
+ hooks: "./hooks/hooks.json",
191
257
  };
192
258
  }
193
259
 
@@ -237,16 +303,29 @@ async function assertPluginManifestPolicy(
237
303
  }
238
304
 
239
305
  async function assertPluginMetadata(root: string): Promise<void> {
240
- const { pluginMcpPath, pluginAppsPath, pluginManifestPath } =
241
- getPluginPaths(root);
242
- const [actualMcp, actualApps, actualManifest] = await Promise.all([
243
- readJsonFile<unknown>(pluginMcpPath),
244
- readJsonFile<unknown>(pluginAppsPath),
245
- readJsonFile<PluginManifest>(pluginManifestPath),
246
- ]);
306
+ const {
307
+ pluginMcpPath,
308
+ pluginAppsPath,
309
+ pluginManifestPath,
310
+ pluginHooksPath,
311
+ pluginHookLauncherPath,
312
+ } = getPluginPaths(root);
313
+ const [actualMcp, actualApps, actualManifest, actualHooks, actualHookLauncher] =
314
+ await Promise.all([
315
+ readJsonFile<unknown>(pluginMcpPath),
316
+ readJsonFile<unknown>(pluginAppsPath),
317
+ readJsonFile<PluginManifest>(pluginManifestPath),
318
+ readJsonFile<unknown>(pluginHooksPath),
319
+ readFile(pluginHookLauncherPath, "utf-8"),
320
+ ]);
247
321
 
248
322
  assertDeepJsonEqual(actualMcp, buildOmxPluginMcpManifest(), "mcp-manifest");
249
323
  assertDeepJsonEqual(actualApps, { apps: {} }, "apps-manifest");
324
+ assertDeepJsonEqual(actualHooks, buildOmxPluginHooksManifest(), "hooks-manifest");
325
+ assertPluginHookLauncherContractMarkerPresent(
326
+ pluginHookLauncherPath,
327
+ actualHookLauncher,
328
+ );
250
329
  await assertPluginManifestPolicy(root, actualManifest);
251
330
  }
252
331
 
@@ -254,11 +333,16 @@ async function writePluginMetadata(
254
333
  root: string,
255
334
  verbose = false,
256
335
  ): Promise<boolean> {
257
- const { pluginMcpPath, pluginAppsPath, pluginManifestPath } =
258
- getPluginPaths(root);
336
+ const {
337
+ pluginMcpPath,
338
+ pluginAppsPath,
339
+ pluginManifestPath,
340
+ pluginHooksPath,
341
+ } = getPluginPaths(root);
259
342
  const expectedMcp = buildOmxPluginMcpManifest();
260
343
  const expectedApps = { apps: {} };
261
344
  const expectedManifest = await buildExpectedPluginManifest(root);
345
+ const expectedHooks = buildOmxPluginHooksManifest();
262
346
  const writes = [
263
347
  {
264
348
  path: pluginMcpPath,
@@ -275,6 +359,11 @@ async function writePluginMetadata(
275
359
  content: stringifyJson(expectedManifest),
276
360
  label: "plugin manifest",
277
361
  },
362
+ {
363
+ path: pluginHooksPath,
364
+ content: stringifyJson(expectedHooks),
365
+ label: "plugin hooks manifest",
366
+ },
278
367
  ];
279
368
  let changed = false;
280
369
 
@@ -116,13 +116,30 @@ function assertTomlStructure(
116
116
  });
117
117
  }
118
118
 
119
+ const metadataHeading = "## OMX Agent Metadata";
120
+ const metadataIndex = instructions.lastIndexOf(metadataHeading);
121
+ if (metadataIndex < 0) {
122
+ throw errorBlock("native_agent_toml_invalid", {
123
+ agent: agentName,
124
+ field: "developer_instructions",
125
+ missing: metadataHeading,
126
+ });
127
+ }
128
+ const metadataLines = new Set(
129
+ instructions
130
+ .slice(metadataIndex)
131
+ .split(/\r?\n/)
132
+ .map((line) => line.trim())
133
+ .filter(Boolean),
134
+ );
135
+
119
136
  for (const expected of [
120
137
  `- role: ${agentName}`,
121
138
  `- posture: ${agent.posture}`,
122
139
  `- model_class: ${agent.modelClass}`,
123
140
  `- routing_role: ${agent.routingRole}`,
124
141
  ]) {
125
- if (!instructions.includes(expected)) {
142
+ if (!metadataLines.has(expected)) {
126
143
  throw errorBlock("native_agent_toml_invalid", {
127
144
  agent: agentName,
128
145
  field: "developer_instructions",
@@ -130,6 +147,53 @@ function assertTomlStructure(
130
147
  });
131
148
  }
132
149
  }
150
+
151
+ const leafGuard = "<native_subagent_leaf_guard>";
152
+ const leafGuardEnd = "</native_subagent_leaf_guard>";
153
+ const allowedDelegation = "- native_subagent_delegation: allowed";
154
+ const latestGeneratedPolicyIndex = Math.max(
155
+ instructions.lastIndexOf("</posture_overlay>", metadataIndex),
156
+ instructions.lastIndexOf("</model_class_guidance>", metadataIndex),
157
+ instructions.lastIndexOf("</exact_model_guidance>", metadataIndex),
158
+ );
159
+ const leafGuardIndex = instructions.lastIndexOf(leafGuard);
160
+ const leafGuardEndIndex = instructions.lastIndexOf(leafGuardEnd);
161
+ const hasGeneratedLeafGuard = leafGuardIndex > latestGeneratedPolicyIndex
162
+ && leafGuardEndIndex > leafGuardIndex
163
+ && leafGuardEndIndex < metadataIndex;
164
+ if (agent.nativeSubagentDelegation === "allowed") {
165
+ if (!metadataLines.has(allowedDelegation)) {
166
+ throw errorBlock("native_agent_toml_invalid", {
167
+ agent: agentName,
168
+ field: "developer_instructions",
169
+ missing: allowedDelegation,
170
+ });
171
+ }
172
+ if (hasGeneratedLeafGuard) {
173
+ throw errorBlock("native_agent_toml_invalid", {
174
+ agent: agentName,
175
+ field: "developer_instructions",
176
+ unexpected: leafGuard,
177
+ message: "delegation-allowed agents must not receive the leaf-only guard",
178
+ });
179
+ }
180
+ } else {
181
+ if (!hasGeneratedLeafGuard) {
182
+ throw errorBlock("native_agent_toml_invalid", {
183
+ agent: agentName,
184
+ field: "developer_instructions",
185
+ missing: leafGuard,
186
+ message: "leaf native agents must receive the anti-recursion guard",
187
+ });
188
+ }
189
+ if (metadataLines.has(allowedDelegation)) {
190
+ throw errorBlock("native_agent_toml_invalid", {
191
+ agent: agentName,
192
+ field: "developer_instructions",
193
+ unexpected: allowedDelegation,
194
+ });
195
+ }
196
+ }
133
197
  }
134
198
 
135
199
  export async function verifyNativeAgents(