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
@@ -0,0 +1 @@
1
+ {"version":3,"file":"ralplan-gate.d.ts","sourceRoot":"","sources":["../../src/autopilot/ralplan-gate.ts"],"names":[],"mappings":"AAAA,OAAO,EAAwC,KAAK,4BAA4B,EAAE,MAAM,8BAA8B,CAAC;AAEvH,KAAK,UAAU,GAAG,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;AAE1C,MAAM,WAAW,kCAAkC;IACjD,GAAG,EAAE,MAAM,CAAC;IACZ,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,YAAY,CAAC,EAAE,UAAU,GAAG,IAAI,CAAC;IACjC,SAAS,CAAC,EAAE,UAAU,GAAG,IAAI,CAAC;CAC/B;AAED,MAAM,WAAW,qCAAqC;IACpD,OAAO,EAAE,OAAO,CAAC;IACjB,MAAM,EAAE,MAAM,CAAC;IACf,QAAQ,CAAC,EAAE,4BAA4B,CAAC;CACzC;AAoCD,wBAAgB,qCAAqC,CACnD,KAAK,EAAE,kCAAkC,GACxC,qCAAqC,CAoBvC;AAED,wBAAgB,uCAAuC,CACrD,QAAQ,EAAE,qCAAqC,GAC9C,MAAM,CAKR"}
@@ -0,0 +1,61 @@
1
+ import { buildRalplanConsensusGateFromSources } from '../ralplan/consensus-gate.js';
2
+ function safeObject(value) {
3
+ return value && typeof value === 'object' && !Array.isArray(value)
4
+ ? value
5
+ : null;
6
+ }
7
+ function nestedState(state) {
8
+ return safeObject(state?.state);
9
+ }
10
+ function handoffArtifacts(state) {
11
+ return safeObject(state?.handoff_artifacts) ?? safeObject(nestedState(state)?.handoff_artifacts);
12
+ }
13
+ function ralplanHandoff(state) {
14
+ return safeObject(handoffArtifacts(state)?.ralplan);
15
+ }
16
+ function gateSources(input) {
17
+ const sources = [];
18
+ for (const [label, state] of [
19
+ ['next-autopilot-state', input.nextState],
20
+ ['current-autopilot-state', input.currentState],
21
+ ]) {
22
+ if (!state)
23
+ continue;
24
+ sources.push({ source: label, value: state });
25
+ const handoffs = handoffArtifacts(state);
26
+ if (handoffs)
27
+ sources.push({ source: `${label}:handoff_artifacts`, value: handoffs });
28
+ const ralplan = ralplanHandoff(state);
29
+ if (ralplan)
30
+ sources.push({ source: `${label}:handoff_artifacts.ralplan`, value: ralplan });
31
+ }
32
+ return sources;
33
+ }
34
+ export function canAdvanceAutopilotRalplanToUltragoal(input) {
35
+ const evidence = buildRalplanConsensusGateFromSources(gateSources(input), {
36
+ cwd: input.cwd,
37
+ sessionId: input.sessionId,
38
+ requireNativeSubagents: true,
39
+ });
40
+ if (evidence.complete) {
41
+ return {
42
+ allowed: true,
43
+ reason: 'tracker-backed native ralplan architect and critic consensus evidence',
44
+ evidence,
45
+ };
46
+ }
47
+ return {
48
+ allowed: false,
49
+ reason: evidence.blockedReason === 'native_subagent_consensus_evidence_missing'
50
+ ? 'ralplan consensus lacks tracker-backed native architect and critic lanes'
51
+ : 'missing ralplan consensus gate with tracker-backed native architect and critic lanes',
52
+ evidence,
53
+ };
54
+ }
55
+ export function buildAutopilotRalplanUltragoalGateError(decision) {
56
+ const details = decision.evidence?.blockedDetails?.length
57
+ ? ` Details: ${decision.evidence.blockedDetails.join('; ')}.`
58
+ : '';
59
+ return `Cannot transition ralplan -> ultragoal: ${decision.reason}.${details}`;
60
+ }
61
+ //# sourceMappingURL=ralplan-gate.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"ralplan-gate.js","sourceRoot":"","sources":["../../src/autopilot/ralplan-gate.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,oCAAoC,EAAqC,MAAM,8BAA8B,CAAC;AAiBvH,SAAS,UAAU,CAAC,KAAc;IAChC,OAAO,KAAK,IAAI,OAAO,KAAK,KAAK,QAAQ,IAAI,CAAC,KAAK,CAAC,OAAO,CAAC,KAAK,CAAC;QAChE,CAAC,CAAC,KAAmB;QACrB,CAAC,CAAC,IAAI,CAAC;AACX,CAAC;AAED,SAAS,WAAW,CAAC,KAAoC;IACvD,OAAO,UAAU,CAAC,KAAK,EAAE,KAAK,CAAC,CAAC;AAClC,CAAC;AAED,SAAS,gBAAgB,CAAC,KAAoC;IAC5D,OAAO,UAAU,CAAC,KAAK,EAAE,iBAAiB,CAAC,IAAI,UAAU,CAAC,WAAW,CAAC,KAAK,CAAC,EAAE,iBAAiB,CAAC,CAAC;AACnG,CAAC;AAED,SAAS,cAAc,CAAC,KAAoC;IAC1D,OAAO,UAAU,CAAC,gBAAgB,CAAC,KAAK,CAAC,EAAE,OAAO,CAAC,CAAC;AACtD,CAAC;AAED,SAAS,WAAW,CAAC,KAAyC;IAC5D,MAAM,OAAO,GAA8C,EAAE,CAAC;IAC9D,KAAK,MAAM,CAAC,KAAK,EAAE,KAAK,CAAC,IAAI;QAC3B,CAAC,sBAAsB,EAAE,KAAK,CAAC,SAAS,CAAC;QACzC,CAAC,yBAAyB,EAAE,KAAK,CAAC,YAAY,CAAC;KACvC,EAAE,CAAC;QACX,IAAI,CAAC,KAAK;YAAE,SAAS;QACrB,OAAO,CAAC,IAAI,CAAC,EAAE,MAAM,EAAE,KAAK,EAAE,KAAK,EAAE,KAAK,EAAE,CAAC,CAAC;QAC9C,MAAM,QAAQ,GAAG,gBAAgB,CAAC,KAAK,CAAC,CAAC;QACzC,IAAI,QAAQ;YAAE,OAAO,CAAC,IAAI,CAAC,EAAE,MAAM,EAAE,GAAG,KAAK,oBAAoB,EAAE,KAAK,EAAE,QAAQ,EAAE,CAAC,CAAC;QACtF,MAAM,OAAO,GAAG,cAAc,CAAC,KAAK,CAAC,CAAC;QACtC,IAAI,OAAO;YAAE,OAAO,CAAC,IAAI,CAAC,EAAE,MAAM,EAAE,GAAG,KAAK,4BAA4B,EAAE,KAAK,EAAE,OAAO,EAAE,CAAC,CAAC;IAC9F,CAAC;IACD,OAAO,OAAO,CAAC;AACjB,CAAC;AAED,MAAM,UAAU,qCAAqC,CACnD,KAAyC;IAEzC,MAAM,QAAQ,GAAG,oCAAoC,CAAC,WAAW,CAAC,KAAK,CAAC,EAAE;QACxE,GAAG,EAAE,KAAK,CAAC,GAAG;QACd,SAAS,EAAE,KAAK,CAAC,SAAS;QAC1B,sBAAsB,EAAE,IAAI;KAC7B,CAAC,CAAC;IACH,IAAI,QAAQ,CAAC,QAAQ,EAAE,CAAC;QACtB,OAAO;YACL,OAAO,EAAE,IAAI;YACb,MAAM,EAAE,uEAAuE;YAC/E,QAAQ;SACT,CAAC;IACJ,CAAC;IACD,OAAO;QACL,OAAO,EAAE,KAAK;QACd,MAAM,EAAE,QAAQ,CAAC,aAAa,KAAK,4CAA4C;YAC7E,CAAC,CAAC,0EAA0E;YAC5E,CAAC,CAAC,sFAAsF;QAC1F,QAAQ;KACT,CAAC;AACJ,CAAC;AAED,MAAM,UAAU,uCAAuC,CACrD,QAA+C;IAE/C,MAAM,OAAO,GAAG,QAAQ,CAAC,QAAQ,EAAE,cAAc,EAAE,MAAM;QACvD,CAAC,CAAC,aAAa,QAAQ,CAAC,QAAQ,CAAC,cAAc,CAAC,IAAI,CAAC,IAAI,CAAC,GAAG;QAC7D,CAAC,CAAC,EAAE,CAAC;IACP,OAAO,2CAA2C,QAAQ,CAAC,MAAM,IAAI,OAAO,EAAE,CAAC;AACjF,CAAC"}
@@ -3,7 +3,7 @@ import assert from 'node:assert/strict';
3
3
  import { spawnSync } from 'node:child_process';
4
4
  import { chmod, cp, mkdir, mkdtemp, readdir, readFile, rm, stat, writeFile } from 'node:fs/promises';
5
5
  import { tmpdir } from 'node:os';
6
- import { delimiter, join, relative, sep } from 'node:path';
6
+ import { delimiter, dirname, join, relative, sep } from 'node:path';
7
7
  import { buildMergedConfig } from '../../config/generator.js';
8
8
  import { getSetupInstallableSkillNames } from '../../catalog/installable.js';
9
9
  import { buildOmxPluginMcpManifest, OMX_FIRST_PARTY_MCP_ENTRYPOINTS, OMX_FIRST_PARTY_MCP_PLUGIN_TARGETS, OMX_FIRST_PARTY_MCP_SERVER_NAMES, OMX_PLUGIN_MCP_COMMAND, OMX_PLUGIN_MCP_SERVE_SUBCOMMAND, } from '../../config/omx-first-party-mcp.js';
@@ -14,11 +14,16 @@ const pluginManifestPath = join(pluginRoot, '.codex-plugin', 'plugin.json');
14
14
  const pluginMcpPath = join(pluginRoot, '.mcp.json');
15
15
  const pluginAppsPath = join(pluginRoot, '.app.json');
16
16
  const pluginHooksPath = join(pluginRoot, 'hooks', 'hooks.json');
17
+ const pluginHookLauncherPath = join(pluginRoot, 'hooks', 'codex-native-hook.mjs');
17
18
  const marketplacePath = join(root, '.agents', 'plugins', 'marketplace.json');
18
19
  const omxBin = join(root, 'dist', 'cli', 'omx.js');
19
20
  async function readJson(path) {
20
21
  return JSON.parse(await readFile(path, 'utf-8'));
21
22
  }
23
+ async function writeJson(path, value) {
24
+ await mkdir(dirname(path), { recursive: true });
25
+ await writeFile(path, JSON.stringify(value, null, 2), 'utf-8');
26
+ }
22
27
  function escapeRegex(value) {
23
28
  return value.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
24
29
  }
@@ -44,6 +49,166 @@ async function writeOmxShim(binDir) {
44
49
  await writeFile(shimPath, `#!/bin/sh\nexec "${process.execPath}" "${omxBin}" "$@"\n`, 'utf-8');
45
50
  await chmod(shimPath, 0o755);
46
51
  }
52
+ async function createPluginMirrorFixtureRoot() {
53
+ const fixtureRoot = await mkdtemp(join(tmpdir(), 'omx-plugin-mirror-fixture-'));
54
+ await Promise.all([
55
+ mkdir(join(fixtureRoot, 'plugins'), { recursive: true }),
56
+ mkdir(join(fixtureRoot, 'src', 'catalog'), { recursive: true }),
57
+ ]);
58
+ await Promise.all([
59
+ cp(join(root, 'package.json'), join(fixtureRoot, 'package.json')),
60
+ cp(join(root, 'plugins', pluginName), join(fixtureRoot, 'plugins', pluginName), { recursive: true }),
61
+ cp(join(root, 'skills'), join(fixtureRoot, 'skills'), { recursive: true }),
62
+ cp(join(root, 'src', 'catalog', 'manifest.json'), join(fixtureRoot, 'src', 'catalog', 'manifest.json')),
63
+ ]);
64
+ return fixtureRoot;
65
+ }
66
+ async function assertSyncPluginRepairsMissingHooksPointer() {
67
+ const fixtureRoot = await createPluginMirrorFixtureRoot();
68
+ try {
69
+ const fixtureManifestPath = join(fixtureRoot, 'plugins', pluginName, '.codex-plugin', 'plugin.json');
70
+ const fixtureHooksPath = join(fixtureRoot, 'plugins', pluginName, 'hooks', 'hooks.json');
71
+ const originalManifest = await readFile(fixtureManifestPath, 'utf-8');
72
+ const manifest = JSON.parse(originalManifest);
73
+ delete manifest.hooks;
74
+ await writeFile(fixtureManifestPath, `${JSON.stringify(manifest, null, 2)}\n`, 'utf-8');
75
+ const result = spawnSync(process.execPath, [join(root, 'dist', 'scripts', 'sync-plugin-mirror.js')], {
76
+ cwd: fixtureRoot,
77
+ encoding: 'utf-8',
78
+ env: {
79
+ ...process.env,
80
+ OMX_AUTO_UPDATE: '0',
81
+ },
82
+ });
83
+ assert.equal(result.status, 0, result.stderr || result.stdout);
84
+ const repairedManifest = await readJson(fixtureManifestPath);
85
+ assert.equal(repairedManifest.hooks, './hooks/hooks.json');
86
+ const hooksManifest = await readJson(fixtureHooksPath);
87
+ const preToolUseEntries = hooksManifest.hooks?.PreToolUse ?? [];
88
+ assert.notEqual(preToolUseEntries.length, 0, 'fixture sync should keep PreToolUse hook entries');
89
+ assert.deepEqual(preToolUseEntries.map((entry) => entry.matcher).filter((matcher) => typeof matcher === 'string'), [], 'fixture sync must not reintroduce a Bash-only PreToolUse matcher');
90
+ }
91
+ finally {
92
+ await rm(fixtureRoot, { recursive: true, force: true });
93
+ }
94
+ }
95
+ async function assertSyncPluginCheckRejectsLauncherWithoutContract() {
96
+ const fixtureRoot = await createPluginMirrorFixtureRoot();
97
+ try {
98
+ const fixtureHookLauncherPath = join(fixtureRoot, 'plugins', pluginName, 'hooks', 'codex-native-hook.mjs');
99
+ const launcher = await readFile(fixtureHookLauncherPath, 'utf-8');
100
+ await writeFile(fixtureHookLauncherPath, launcher.replace('omx-plugin-hook-launcher:v1', 'omx-plugin-hook-launcher:missing'), 'utf-8');
101
+ const result = spawnSync(process.execPath, [join(root, 'dist', 'scripts', 'sync-plugin-mirror.js'), '--check'], {
102
+ cwd: fixtureRoot,
103
+ encoding: 'utf-8',
104
+ env: {
105
+ ...process.env,
106
+ OMX_AUTO_UPDATE: '0',
107
+ },
108
+ });
109
+ assert.notEqual(result.status, 0, 'sync-plugin-mirror --check should reject launcher contract drift');
110
+ assert.match(result.stderr, /plugin_bundle_metadata_out_of_sync/);
111
+ assert.match(result.stderr, /kind=hook-launcher/);
112
+ }
113
+ finally {
114
+ await rm(fixtureRoot, { recursive: true, force: true });
115
+ }
116
+ }
117
+ async function assertPluginHookEventsAlignWithLauncher() {
118
+ const hooksManifest = await readJson(pluginHooksPath);
119
+ const launcher = await readFile(pluginHookLauncherPath, 'utf-8');
120
+ const eventSetMatch = launcher.match(/const CODEX_HOOK_EVENT_NAMES = new Set\(\[([\s\S]*?)\]\);/);
121
+ assert.ok(eventSetMatch, 'plugin hook launcher should declare a CODEX_HOOK_EVENT_NAMES set');
122
+ const launcherEvents = Array.from(eventSetMatch[1].matchAll(/'([^']+)'/g), (match) => match[1]).sort();
123
+ assert.deepEqual(launcherEvents, Object.keys(hooksManifest.hooks ?? {}).sort(), 'plugin hook launcher event allowlist must stay aligned with generated plugin hooks manifest');
124
+ }
125
+ async function assertPluginHookLaunchesPostCompactFromCache() {
126
+ const cacheRoot = await mkdtemp(join(tmpdir(), 'omx-plugin-hook-cache-'));
127
+ const cachePluginRoot = join(cacheRoot, pluginName, 'local');
128
+ const shimDir = join(cacheRoot, 'bin');
129
+ await cp(pluginRoot, cachePluginRoot, { recursive: true });
130
+ await writeOmxShim(shimDir);
131
+ try {
132
+ const payload = JSON.stringify({
133
+ hook_event_name: 'PostCompact',
134
+ session_id: 'omx-plugin-hook-postcompact-smoke',
135
+ transcript_path: join(cacheRoot, 'missing-transcript.jsonl'),
136
+ cwd: cacheRoot,
137
+ });
138
+ const result = spawnSync(process.execPath, [join(cachePluginRoot, 'hooks', 'codex-native-hook.mjs')], {
139
+ cwd: cachePluginRoot,
140
+ encoding: 'utf-8',
141
+ input: payload,
142
+ env: {
143
+ ...process.env,
144
+ PATH: `${shimDir}${delimiter}${process.env.PATH || ''}`,
145
+ OMX_AUTO_UPDATE: '0',
146
+ OMX_NOTIFY_FALLBACK: '0',
147
+ OMX_HOOK_DERIVED_SIGNALS: '0',
148
+ OMX_ROOT: join(cacheRoot, '.omx-root'),
149
+ OMX_SESSION_ID: 'omx-plugin-hook-postcompact-smoke',
150
+ OMX_SOURCE_CWD: cacheRoot,
151
+ OMX_STARTUP_CWD: cacheRoot,
152
+ },
153
+ });
154
+ assert.equal(result.status, 0, result.stderr || result.stdout);
155
+ assert.equal(result.stdout, '', 'PostCompact plugin hook launcher should emit no stdout');
156
+ assert.doesNotMatch(result.stderr, /MODULE_NOT_FOUND|Cannot find module/);
157
+ }
158
+ finally {
159
+ await rm(cacheRoot, { recursive: true, force: true });
160
+ }
161
+ }
162
+ async function assertPluginHookDelegatesPostCompactToPinnedCommand() {
163
+ const cacheRoot = await mkdtemp(join(tmpdir(), 'omx-plugin-hook-delegate-'));
164
+ const cachePluginRoot = join(cacheRoot, pluginName, 'local');
165
+ const recorderPath = join(cacheRoot, 'record-hook.mjs');
166
+ const argsPath = join(cacheRoot, 'recorded-args.json');
167
+ const stdinPath = join(cacheRoot, 'recorded-stdin.json');
168
+ await cp(pluginRoot, cachePluginRoot, { recursive: true });
169
+ await writeFile(recorderPath, [
170
+ "import { writeFileSync } from 'node:fs';",
171
+ "const chunks = [];",
172
+ "for await (const chunk of process.stdin) chunks.push(Buffer.from(chunk));",
173
+ `writeFileSync(${JSON.stringify(argsPath)}, JSON.stringify(process.argv.slice(2)));`,
174
+ `writeFileSync(${JSON.stringify(stdinPath)}, Buffer.concat(chunks));`,
175
+ "",
176
+ ].join('\n'), 'utf-8');
177
+ await writeJson(join(cachePluginRoot, 'hooks', 'omx-command.json'), {
178
+ command: process.execPath,
179
+ argsPrefix: [recorderPath],
180
+ });
181
+ try {
182
+ const payload = JSON.stringify({
183
+ hook_event_name: 'PostCompact',
184
+ session_id: 'omx-plugin-hook-postcompact-delegate',
185
+ transcript_path: join(cacheRoot, 'missing-transcript.jsonl'),
186
+ cwd: cacheRoot,
187
+ });
188
+ const result = spawnSync(process.execPath, [join(cachePluginRoot, 'hooks', 'codex-native-hook.mjs')], {
189
+ cwd: cachePluginRoot,
190
+ encoding: 'utf-8',
191
+ input: payload,
192
+ env: {
193
+ ...process.env,
194
+ OMX_AUTO_UPDATE: '0',
195
+ OMX_NOTIFY_FALLBACK: '0',
196
+ OMX_HOOK_DERIVED_SIGNALS: '0',
197
+ OMX_ROOT: join(cacheRoot, '.omx-root'),
198
+ OMX_SESSION_ID: 'omx-plugin-hook-postcompact-delegate',
199
+ OMX_SOURCE_CWD: cacheRoot,
200
+ OMX_STARTUP_CWD: cacheRoot,
201
+ },
202
+ });
203
+ assert.equal(result.status, 0, result.stderr || result.stdout);
204
+ assert.equal(result.stdout, '', 'PostCompact plugin hook launcher should emit no stdout when delegate is quiet');
205
+ assert.deepEqual(JSON.parse(await readFile(argsPath, 'utf-8')), ['codex-native-hook']);
206
+ assert.deepEqual(JSON.parse(await readFile(stdinPath, 'utf-8')), JSON.parse(payload));
207
+ }
208
+ finally {
209
+ await rm(cacheRoot, { recursive: true, force: true });
210
+ }
211
+ }
47
212
  async function assertPluginCacheLaunchable(entrypoint) {
48
213
  const cacheRoot = await mkdtemp(join(tmpdir(), 'omx-plugin-cache-'));
49
214
  const cachePluginRoot = join(cacheRoot, pluginName, 'local');
@@ -70,6 +235,39 @@ async function assertPluginCacheLaunchable(entrypoint) {
70
235
  await rm(cacheRoot, { recursive: true, force: true });
71
236
  }
72
237
  }
238
+ function parseSingleJsonStdout(stdout) {
239
+ const trimmed = stdout.trim();
240
+ assert.notEqual(trimmed, '');
241
+ assert.equal(trimmed.split('\n').length, 1);
242
+ return JSON.parse(trimmed);
243
+ }
244
+ async function withPluginCacheCopy(run) {
245
+ const cacheRoot = await mkdtemp(join(tmpdir(), 'omx-plugin-hook-cache-'));
246
+ const cachePluginRoot = join(cacheRoot, pluginName, 'local');
247
+ await cp(pluginRoot, cachePluginRoot, { recursive: true });
248
+ try {
249
+ return await run(cachePluginRoot, cacheRoot);
250
+ }
251
+ finally {
252
+ await rm(cacheRoot, { recursive: true, force: true });
253
+ }
254
+ }
255
+ function pluginHookEnv(overrides = {}) {
256
+ const env = { ...process.env };
257
+ for (const key of ['OMX_TEAM_STATE_ROOT', 'OMX_ROOT', 'OMX_STATE_ROOT', 'OMX_SESSION_ID', 'CODEX_SESSION_ID']) {
258
+ delete env[key];
259
+ }
260
+ return { ...env, ...overrides };
261
+ }
262
+ function runPluginNativeHook(cachePluginRoot, input, env = {}) {
263
+ return spawnSync(process.execPath, [join(cachePluginRoot, 'hooks', 'codex-native-hook.mjs')], {
264
+ cwd: cachePluginRoot,
265
+ input,
266
+ encoding: 'utf-8',
267
+ stdio: ['pipe', 'pipe', 'pipe'],
268
+ env: pluginHookEnv(env),
269
+ });
270
+ }
73
271
  describe('official Codex plugin layout', () => {
74
272
  it('defines a plugin manifest under a plugin root and keeps .codex-plugin limited to plugin.json', async () => {
75
273
  const pkg = await readJson(join(root, 'package.json'));
@@ -88,6 +286,15 @@ describe('official Codex plugin layout', () => {
88
286
  assert.ok(manifest.interface?.longDescription, 'expected long interface description');
89
287
  assert.ok(manifest.interface?.developerName, 'expected developerName');
90
288
  });
289
+ it('repairs a missing plugin hooks manifest pointer during plugin sync', async () => {
290
+ await assertSyncPluginRepairsMissingHooksPointer();
291
+ });
292
+ it('rejects plugin hook launcher drift during plugin sync check', async () => {
293
+ await assertSyncPluginCheckRejectsLauncherWithoutContract();
294
+ });
295
+ it('keeps generated plugin hook events aligned with the launcher allowlist', async () => {
296
+ await assertPluginHookEventsAlignWithLauncher();
297
+ });
91
298
  it('ships plugin-scoped hooks and disabled-by-default MCP compatibility metadata', async () => {
92
299
  const [mcpManifest, appsManifest, hooksManifest] = await Promise.all([
93
300
  readJson(pluginMcpPath),
@@ -105,6 +312,7 @@ describe('official Codex plugin layout', () => {
105
312
  .flatMap((entry) => entry.hooks ?? [])
106
313
  .map((hook) => hook.command);
107
314
  assert.ok(hookCommands.every((command) => command === 'node "${PLUGIN_ROOT}/hooks/codex-native-hook.mjs"'), 'plugin hooks should use Codex PLUGIN_ROOT instead of setup-owned .codex/hooks.json');
315
+ assert.equal(hooksManifest.hooks?.PreToolUse?.some((entry) => typeof entry.matcher === 'string'), false, 'plugin PreToolUse hooks must cover non-Bash tools just like setup-owned native hooks');
108
316
  assert.deepEqual(mcpManifest, expectedPluginMcpManifest);
109
317
  for (const [serverName, server] of Object.entries(mcpManifest.mcpServers ?? {})) {
110
318
  assert.equal(server.command, OMX_PLUGIN_MCP_COMMAND, `${serverName} should run via omx`);
@@ -119,6 +327,302 @@ describe('official Codex plugin layout', () => {
119
327
  assert.equal(target?.endsWith('-server.js'), false, `${serverName} should not expose internal dist filenames in plugin metadata`);
120
328
  }
121
329
  });
330
+ it('emits Stop JSON when the plugin hook pinned launcher is invalid', async () => {
331
+ await withPluginCacheCopy(async (cachePluginRoot) => {
332
+ await writeFile(join(cachePluginRoot, 'hooks', 'omx-command.json'), '{"command":', 'utf-8');
333
+ const result = runPluginNativeHook(cachePluginRoot, JSON.stringify({
334
+ hook_event_name: 'Stop',
335
+ session_id: 'sess-plugin-invalid-launcher-stop',
336
+ }));
337
+ assert.equal(result.status, 0, result.stderr || result.stdout);
338
+ assert.match(result.stderr, /invalid plugin hook launcher/);
339
+ const output = parseSingleJsonStdout(result.stdout);
340
+ assert.equal(output.decision, 'block');
341
+ assert.equal(output.stopReason, 'plugin_stop_hook_launcher_failure');
342
+ });
343
+ });
344
+ it('emits Stop JSON when the plugin hook command cannot spawn', async () => {
345
+ await withPluginCacheCopy(async (cachePluginRoot, cacheRoot) => {
346
+ const result = runPluginNativeHook(cachePluginRoot, JSON.stringify({ hook_event_name: 'Stop', session_id: 'sess-plugin-missing-command-stop' }), {
347
+ OMX_NATIVE_HOOK_COMMAND: join(cacheRoot, 'bin', 'missing-omx-command'),
348
+ });
349
+ assert.equal(result.status, 0, result.stderr || result.stdout);
350
+ const output = parseSingleJsonStdout(result.stdout);
351
+ assert.equal(output.decision, 'block');
352
+ assert.equal(output.stopReason, 'plugin_stop_hook_launcher_spawn_error');
353
+ });
354
+ });
355
+ it('emits Stop JSON when the launched plugin hook command exits before producing stdout', async () => {
356
+ await withPluginCacheCopy(async (cachePluginRoot) => {
357
+ const result = runPluginNativeHook(cachePluginRoot, JSON.stringify({ hook_event_name: 'Stop', session_id: 'sess-plugin-false-command-stop' }), {
358
+ OMX_NATIVE_HOOK_COMMAND: process.platform === 'win32' ? 'cmd.exe /c exit 1' : '/bin/false',
359
+ });
360
+ assert.equal(result.status, 0, result.stderr || result.stdout);
361
+ const output = parseSingleJsonStdout(result.stdout);
362
+ assert.equal(output.decision, 'block');
363
+ assert.match(String(output.stopReason ?? ''), /plugin_stop_hook_launcher_(?:exit|stdin_error)/);
364
+ });
365
+ });
366
+ it('emits Stop JSON when the launched plugin hook command exits successfully without stdout', async () => {
367
+ await withPluginCacheCopy(async (cachePluginRoot, cacheRoot) => {
368
+ const commandPath = join(cacheRoot, process.platform === 'win32' ? 'empty-ok.cmd' : 'empty-ok.sh');
369
+ if (process.platform === 'win32') {
370
+ await writeFile(commandPath, '@echo off\r\nexit /b 0\r\n', 'utf-8');
371
+ }
372
+ else {
373
+ await writeFile(commandPath, '#!/bin/sh\nexit 0\n', 'utf-8');
374
+ await chmod(commandPath, 0o755);
375
+ }
376
+ const result = runPluginNativeHook(cachePluginRoot, JSON.stringify({ hook_event_name: 'Stop', session_id: 'sess-plugin-empty-ok-stop' }), { OMX_NATIVE_HOOK_COMMAND: commandPath });
377
+ assert.equal(result.status, 0, result.stderr || result.stdout);
378
+ const output = parseSingleJsonStdout(result.stdout);
379
+ assert.equal(output.decision, 'block');
380
+ assert.equal(output.stopReason, 'plugin_stop_hook_launcher_empty_stdout');
381
+ });
382
+ });
383
+ it('does not append fallback Stop JSON after partial child stdout', async () => {
384
+ await withPluginCacheCopy(async (cachePluginRoot, cacheRoot) => {
385
+ const commandPath = join(cacheRoot, process.platform === 'win32' ? 'partial.cmd' : 'partial.sh');
386
+ if (process.platform === 'win32') {
387
+ await writeFile(commandPath, '@echo off\r\n<nul set /p=PARTIAL\r\nexit /b 2\r\n', 'utf-8');
388
+ }
389
+ else {
390
+ await writeFile(commandPath, '#!/bin/sh\nprintf PARTIAL\nexit 2\n', 'utf-8');
391
+ await chmod(commandPath, 0o755);
392
+ }
393
+ const result = runPluginNativeHook(cachePluginRoot, JSON.stringify({ hook_event_name: 'Stop', session_id: 'sess-plugin-partial-stop' }), { OMX_NATIVE_HOOK_COMMAND: commandPath });
394
+ assert.equal(result.status, 2, result.stderr || result.stdout);
395
+ assert.equal(result.stdout, 'PARTIAL');
396
+ assert.doesNotMatch(result.stdout, /plugin_stop_hook_launcher/);
397
+ });
398
+ });
399
+ it('emits Stop JSON for malformed Stop-looking stdin before invalid launcher failure', async () => {
400
+ await withPluginCacheCopy(async (cachePluginRoot) => {
401
+ await writeFile(join(cachePluginRoot, 'hooks', 'omx-command.json'), '{"command":', 'utf-8');
402
+ const result = runPluginNativeHook(cachePluginRoot, '{"hook_event_name":"Stop",');
403
+ assert.equal(result.status, 0, result.stderr || result.stdout);
404
+ const output = parseSingleJsonStdout(result.stdout);
405
+ assert.equal(output.decision, 'block');
406
+ assert.equal(output.stopReason, 'plugin_stop_hook_launcher_failure');
407
+ });
408
+ });
409
+ it('emits Stop JSON for the core-supported name alias before invalid launcher failure', async () => {
410
+ await withPluginCacheCopy(async (cachePluginRoot) => {
411
+ await writeFile(join(cachePluginRoot, 'hooks', 'omx-command.json'), '{"command":', 'utf-8');
412
+ const result = runPluginNativeHook(cachePluginRoot, '{"name":"Stop",');
413
+ assert.equal(result.status, 0, result.stderr || result.stdout);
414
+ const output = parseSingleJsonStdout(result.stdout);
415
+ assert.equal(output.decision, 'block');
416
+ assert.equal(output.stopReason, 'plugin_stop_hook_launcher_failure');
417
+ });
418
+ });
419
+ it('keeps non-Stop plugin hook launcher failures fail-closed without Stop JSON', async () => {
420
+ await withPluginCacheCopy(async (cachePluginRoot) => {
421
+ await writeFile(join(cachePluginRoot, 'hooks', 'omx-command.json'), '{"command":', 'utf-8');
422
+ const result = runPluginNativeHook(cachePluginRoot, JSON.stringify({
423
+ hook_event_name: 'UserPromptSubmit',
424
+ prompt: 'hello',
425
+ }));
426
+ assert.equal(result.status, 1);
427
+ assert.equal(result.stdout, '');
428
+ assert.match(result.stderr, /invalid plugin hook launcher/);
429
+ });
430
+ });
431
+ it('does not classify valid non-Stop plugin JSON with nested Stop text as Stop', async () => {
432
+ await withPluginCacheCopy(async (cachePluginRoot) => {
433
+ await writeFile(join(cachePluginRoot, 'hooks', 'omx-command.json'), '{"command":', 'utf-8');
434
+ const result = runPluginNativeHook(cachePluginRoot, JSON.stringify({
435
+ hook_event_name: 'PreToolUse',
436
+ tool_input: { name: 'Stop' },
437
+ }));
438
+ assert.equal(result.status, 1);
439
+ assert.equal(result.stdout, '');
440
+ assert.match(result.stderr, /invalid plugin hook launcher/);
441
+ });
442
+ });
443
+ it('does not classify malformed non-Stop plugin JSON with nested Stop text as Stop', async () => {
444
+ await withPluginCacheCopy(async (cachePluginRoot) => {
445
+ await writeFile(join(cachePluginRoot, 'hooks', 'omx-command.json'), '{"command":', 'utf-8');
446
+ const result = runPluginNativeHook(cachePluginRoot, '{"hook_event_name":"PreToolUse","tool_input":{"name":"Stop"},');
447
+ assert.equal(result.status, 1);
448
+ assert.equal(result.stdout, '');
449
+ assert.match(result.stderr, /invalid plugin hook launcher/);
450
+ });
451
+ });
452
+ it('allows oversized plugin Stop stdin when no active workflow state is present', async () => {
453
+ await withPluginCacheCopy(async (cachePluginRoot) => {
454
+ const oversizedStop = `{"hook_event_name":"Stop","padding":"${'x'.repeat(1024 * 1024 + 1)}`;
455
+ const result = runPluginNativeHook(cachePluginRoot, oversizedStop);
456
+ assert.equal(result.status, 0, result.stderr || result.stdout);
457
+ assert.deepEqual(parseSingleJsonStdout(result.stdout), {});
458
+ });
459
+ });
460
+ it('blocks oversized plugin Stop stdin when current session autopilot state is active', async () => {
461
+ await withPluginCacheCopy(async (cachePluginRoot) => {
462
+ const sessionId = 'sess-plugin-oversized-active';
463
+ await writeJson(join(cachePluginRoot, '.omx', 'state', 'session.json'), { session_id: sessionId });
464
+ await writeJson(join(cachePluginRoot, '.omx', 'state', 'sessions', sessionId, 'autopilot-state.json'), {
465
+ active: true,
466
+ current_phase: 'execution',
467
+ });
468
+ const oversizedStop = `{"hook_event_name":"Stop","cwd":"${cachePluginRoot}","session_id":"${sessionId}","padding":"${'x'.repeat(1024 * 1024 + 1)}`;
469
+ const result = runPluginNativeHook(cachePluginRoot, oversizedStop);
470
+ assert.equal(result.status, 0, result.stderr || result.stdout);
471
+ const output = parseSingleJsonStdout(result.stdout);
472
+ assert.equal(output.decision, 'block');
473
+ assert.equal(output.stopReason, 'plugin_stop_hook_stdin_oversized_active_workflow');
474
+ });
475
+ });
476
+ it('does not let unrelated terminal run-state suppress active plugin Autopilot oversized Stop blocking', async () => {
477
+ await withPluginCacheCopy(async (cachePluginRoot) => {
478
+ const sessionId = 'sess-plugin-oversized-unrelated-terminal';
479
+ await writeJson(join(cachePluginRoot, '.omx', 'state', 'session.json'), { session_id: sessionId });
480
+ await writeJson(join(cachePluginRoot, '.omx', 'state', 'sessions', sessionId, 'autopilot-state.json'), {
481
+ active: true,
482
+ current_phase: 'execution',
483
+ });
484
+ await writeJson(join(cachePluginRoot, '.omx', 'state', 'sessions', sessionId, 'run-state.json'), {
485
+ mode: 'ralph',
486
+ active: false,
487
+ outcome: 'finish',
488
+ });
489
+ const oversizedStop = `{"hook_event_name":"Stop","cwd":"${cachePluginRoot}","session_id":"${sessionId}","padding":"${'x'.repeat(1024 * 1024 + 1)}`;
490
+ const result = runPluginNativeHook(cachePluginRoot, oversizedStop);
491
+ assert.equal(result.status, 0, result.stderr || result.stdout);
492
+ const output = parseSingleJsonStdout(result.stdout);
493
+ assert.equal(output.decision, 'block');
494
+ assert.equal(output.stopReason, 'plugin_stop_hook_stdin_oversized_active_workflow');
495
+ });
496
+ });
497
+ it('allows oversized plugin Stop stdin when terminal Autopilot run-state shadows stale active state', async () => {
498
+ await withPluginCacheCopy(async (cachePluginRoot) => {
499
+ const sessionId = 'sess-plugin-oversized-terminal-autopilot';
500
+ await writeJson(join(cachePluginRoot, '.omx', 'state', 'session.json'), { session_id: sessionId });
501
+ await writeJson(join(cachePluginRoot, '.omx', 'state', 'sessions', sessionId, 'autopilot-state.json'), {
502
+ active: true,
503
+ current_phase: 'execution',
504
+ });
505
+ await writeJson(join(cachePluginRoot, '.omx', 'state', 'sessions', sessionId, 'run-state.json'), {
506
+ mode: 'autopilot',
507
+ active: false,
508
+ outcome: 'blocked_on_user',
509
+ });
510
+ const oversizedStop = `{"hook_event_name":"Stop","cwd":"${cachePluginRoot}","session_id":"${sessionId}","padding":"${'x'.repeat(1024 * 1024 + 1)}`;
511
+ const result = runPluginNativeHook(cachePluginRoot, oversizedStop);
512
+ assert.equal(result.status, 0, result.stderr || result.stdout);
513
+ assert.deepEqual(parseSingleJsonStdout(result.stdout), {});
514
+ });
515
+ });
516
+ it('detects active plugin Autopilot state for oversized Stop under OMX_ROOT', async () => {
517
+ await withPluginCacheCopy(async (cachePluginRoot, cacheRoot) => {
518
+ const sessionId = 'sess-plugin-oversized-omx-root';
519
+ const omxRoot = join(cacheRoot, 'boxed-root');
520
+ await writeJson(join(omxRoot, '.omx', 'state', 'session.json'), { session_id: sessionId });
521
+ await writeJson(join(omxRoot, '.omx', 'state', 'sessions', sessionId, 'autopilot-state.json'), {
522
+ active: true,
523
+ current_phase: 'execution',
524
+ });
525
+ const oversizedStop = `{"hook_event_name":"Stop","cwd":"${cachePluginRoot}","session_id":"${sessionId}","padding":"${'x'.repeat(1024 * 1024 + 1)}`;
526
+ const result = runPluginNativeHook(cachePluginRoot, oversizedStop, { OMX_ROOT: omxRoot });
527
+ assert.equal(result.status, 0, result.stderr || result.stdout);
528
+ const output = parseSingleJsonStdout(result.stdout);
529
+ assert.equal(output.decision, 'block');
530
+ assert.equal(output.stopReason, 'plugin_stop_hook_stdin_oversized_active_workflow');
531
+ });
532
+ });
533
+ it('lets terminal OMX_ROOT Autopilot state override stale cwd active state for oversized Stop', async () => {
534
+ await withPluginCacheCopy(async (cachePluginRoot, cacheRoot) => {
535
+ const sessionId = 'sess-plugin-oversized-omx-root-terminal';
536
+ const omxRoot = join(cacheRoot, 'boxed-root-terminal');
537
+ await writeJson(join(omxRoot, '.omx', 'state', 'session.json'), { session_id: sessionId });
538
+ await writeJson(join(omxRoot, '.omx', 'state', 'sessions', sessionId, 'run-state.json'), {
539
+ mode: 'autopilot',
540
+ outcome: 'blocked_on_user',
541
+ });
542
+ await writeJson(join(cachePluginRoot, '.omx', 'state', 'session.json'), { session_id: sessionId });
543
+ await writeJson(join(cachePluginRoot, '.omx', 'state', 'sessions', sessionId, 'autopilot-state.json'), {
544
+ active: true,
545
+ current_phase: 'execution',
546
+ });
547
+ const oversizedStop = `{"hook_event_name":"Stop","cwd":"${cachePluginRoot}","session_id":"${sessionId}","padding":"${'x'.repeat(1024 * 1024 + 1)}`;
548
+ const result = runPluginNativeHook(cachePluginRoot, oversizedStop, { OMX_ROOT: omxRoot });
549
+ assert.equal(result.status, 0, result.stderr || result.stdout);
550
+ assert.deepEqual(parseSingleJsonStdout(result.stdout), {});
551
+ });
552
+ });
553
+ it('allows oversized plugin Stop when Autopilot state is active but terminal by phase', async () => {
554
+ await withPluginCacheCopy(async (cachePluginRoot) => {
555
+ const sessionId = 'sess-plugin-oversized-terminal-phase';
556
+ await writeJson(join(cachePluginRoot, '.omx', 'state', 'session.json'), { session_id: sessionId });
557
+ await writeJson(join(cachePluginRoot, '.omx', 'state', 'sessions', sessionId, 'autopilot-state.json'), {
558
+ active: true,
559
+ current_phase: 'complete',
560
+ });
561
+ const oversizedStop = `{"hook_event_name":"Stop","cwd":"${cachePluginRoot}","session_id":"${sessionId}","padding":"${'x'.repeat(1024 * 1024 + 1)}`;
562
+ const result = runPluginNativeHook(cachePluginRoot, oversizedStop);
563
+ assert.equal(result.status, 0, result.stderr || result.stdout);
564
+ assert.deepEqual(parseSingleJsonStdout(result.stdout), {});
565
+ });
566
+ });
567
+ it('ignores stale plugin session state whose cwd does not match oversized Stop cwd', async () => {
568
+ await withPluginCacheCopy(async (cachePluginRoot, cacheRoot) => {
569
+ const sessionId = 'sess-plugin-oversized-stale-cwd';
570
+ await writeJson(join(cachePluginRoot, '.omx', 'state', 'session.json'), {
571
+ session_id: sessionId,
572
+ cwd: join(cacheRoot, 'different-cwd'),
573
+ });
574
+ await writeJson(join(cachePluginRoot, '.omx', 'state', 'sessions', sessionId, 'autopilot-state.json'), {
575
+ active: true,
576
+ current_phase: 'execution',
577
+ });
578
+ const oversizedStop = `{"hook_event_name":"Stop","cwd":"${cachePluginRoot}","session_id":"${sessionId}","padding":"${'x'.repeat(1024 * 1024 + 1)}`;
579
+ const result = runPluginNativeHook(cachePluginRoot, oversizedStop);
580
+ assert.equal(result.status, 0, result.stderr || result.stdout);
581
+ assert.deepEqual(parseSingleJsonStdout(result.stdout), {});
582
+ });
583
+ });
584
+ it('fails oversized non-Stop plugin stdin without Stop JSON', async () => {
585
+ await withPluginCacheCopy(async (cachePluginRoot) => {
586
+ const result = runPluginNativeHook(cachePluginRoot, 'x'.repeat(1024 * 1024 + 1));
587
+ assert.equal(result.status, 1);
588
+ assert.equal(result.stdout, '');
589
+ assert.match(result.stderr, /plugin hook stdin exceeded/);
590
+ });
591
+ });
592
+ it('forwards under-cap plugin hook stdin bytes unchanged to the delegated command', async () => {
593
+ await withPluginCacheCopy(async (cachePluginRoot, cacheRoot) => {
594
+ const capturePath = join(cacheRoot, 'captured-stdin.json');
595
+ const commandPath = join(cacheRoot, 'capture-hook.mjs');
596
+ const launcherPath = join(cacheRoot, process.platform === 'win32' ? 'capture-hook.cmd' : 'capture-hook.sh');
597
+ await writeFile(commandPath, `import { writeFileSync } from 'node:fs';
598
+ const chunks = [];
599
+ process.stdin.on('data', (chunk) => chunks.push(chunk));
600
+ process.stdin.on('end', () => {
601
+ writeFileSync(process.env.CAPTURE_PATH, Buffer.concat(chunks));
602
+ process.stdout.write('{}\\n');
603
+ });
604
+ `, 'utf-8');
605
+ if (process.platform === 'win32') {
606
+ await writeFile(launcherPath, `@echo off\r\n"${process.execPath}" "${commandPath}" %*\r\n`, 'utf-8');
607
+ }
608
+ else {
609
+ await writeFile(launcherPath, `#!/bin/sh\nexec "${process.execPath}" "${commandPath}" "$@"\n`, 'utf-8');
610
+ await chmod(launcherPath, 0o755);
611
+ }
612
+ const input = JSON.stringify({
613
+ hook_event_name: 'Stop',
614
+ session_id: 'sess-plugin-forward-stdin',
615
+ payload: 'keep these bytes unchanged',
616
+ });
617
+ const result = runPluginNativeHook(cachePluginRoot, input, {
618
+ OMX_NATIVE_HOOK_COMMAND: launcherPath,
619
+ CAPTURE_PATH: capturePath,
620
+ });
621
+ assert.equal(result.status, 0, result.stderr || result.stdout);
622
+ assert.deepEqual(parseSingleJsonStdout(result.stdout), {});
623
+ assert.equal(await readFile(capturePath, 'utf-8'), input);
624
+ });
625
+ });
122
626
  it('keeps plugin MCP metadata aligned with the explicit compat setup-managed MCP roster', async () => {
123
627
  const mcpManifest = await readJson(pluginMcpPath);
124
628
  const defaultConfig = buildMergedConfig('', root, { includeTui: false });
@@ -142,12 +646,19 @@ describe('official Codex plugin layout', () => {
142
646
  await assertPluginCacheLaunchable(target);
143
647
  }
144
648
  });
649
+ it('launches the plugin-scoped native hook for PostCompact from a cache-style plugin root', async () => {
650
+ await assertPluginHookLaunchesPostCompactFromCache();
651
+ });
652
+ it('delegates PostCompact plugin hook payloads to the pinned launcher command', async () => {
653
+ await assertPluginHookDelegatesPostCompactToPinnedCommand();
654
+ });
145
655
  it('does not stage setup-owned hook or runtime directories inside the plugin', async () => {
146
656
  const pluginEntries = await readdir(pluginRoot);
147
657
  assert.equal(pluginEntries.includes('.codex'), false, 'official plugin should not ship setup-owned .codex hook assets');
148
658
  assert.equal(pluginEntries.includes('.omx'), false, 'official plugin should not ship runtime hook directories');
149
659
  assert.equal(pluginEntries.includes('hooks.json'), false, 'official plugin hook metadata should stay under hooks/');
150
660
  assert.equal(pluginEntries.includes('hooks'), true, 'official plugin should ship plugin-scoped lifecycle hooks');
661
+ await stat(pluginHookLauncherPath);
151
662
  });
152
663
  it('registers the plugin in the repo marketplace with explicit source, policy, and category', async () => {
153
664
  const marketplace = await readJson(marketplacePath);