oh-my-codex 0.18.7 → 0.18.9

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 (307) hide show
  1. package/Cargo.lock +12 -12
  2. package/Cargo.toml +1 -1
  3. package/README.md +5 -5
  4. package/crates/omx-sparkshell/tests/execution.rs +1 -1
  5. package/dist/agents/__tests__/native-config.test.js +42 -1
  6. package/dist/agents/__tests__/native-config.test.js.map +1 -1
  7. package/dist/agents/definitions.d.ts +8 -0
  8. package/dist/agents/definitions.d.ts.map +1 -1
  9. package/dist/agents/definitions.js +1 -0
  10. package/dist/agents/definitions.js.map +1 -1
  11. package/dist/agents/native-config.d.ts +5 -1
  12. package/dist/agents/native-config.d.ts.map +1 -1
  13. package/dist/agents/native-config.js +17 -2
  14. package/dist/agents/native-config.js.map +1 -1
  15. package/dist/autopilot/__tests__/fsm.test.js +3 -0
  16. package/dist/autopilot/__tests__/fsm.test.js.map +1 -1
  17. package/dist/autopilot/fsm.js +2 -2
  18. package/dist/autopilot/fsm.js.map +1 -1
  19. package/dist/cli/__tests__/auth.test.js +4 -2
  20. package/dist/cli/__tests__/auth.test.js.map +1 -1
  21. package/dist/cli/__tests__/codex-plugin-layout.test.js +512 -1
  22. package/dist/cli/__tests__/codex-plugin-layout.test.js.map +1 -1
  23. package/dist/cli/__tests__/doctor-warning-copy.test.js +39 -0
  24. package/dist/cli/__tests__/doctor-warning-copy.test.js.map +1 -1
  25. package/dist/cli/__tests__/index.test.js +98 -6
  26. package/dist/cli/__tests__/index.test.js.map +1 -1
  27. package/dist/cli/__tests__/package-bin-contract.test.js +28 -8
  28. package/dist/cli/__tests__/package-bin-contract.test.js.map +1 -1
  29. package/dist/cli/__tests__/question.test.js +26 -9
  30. package/dist/cli/__tests__/question.test.js.map +1 -1
  31. package/dist/cli/__tests__/ralph-goal-mode-contract.test.js +13 -0
  32. package/dist/cli/__tests__/ralph-goal-mode-contract.test.js.map +1 -1
  33. package/dist/cli/__tests__/ralph.test.js +14 -0
  34. package/dist/cli/__tests__/ralph.test.js.map +1 -1
  35. package/dist/cli/__tests__/resume.test.js +50 -1
  36. package/dist/cli/__tests__/resume.test.js.map +1 -1
  37. package/dist/cli/__tests__/setup-install-mode.test.js +89 -0
  38. package/dist/cli/__tests__/setup-install-mode.test.js.map +1 -1
  39. package/dist/cli/__tests__/setup-refresh.test.js +65 -0
  40. package/dist/cli/__tests__/setup-refresh.test.js.map +1 -1
  41. package/dist/cli/__tests__/state.test.js +21 -0
  42. package/dist/cli/__tests__/state.test.js.map +1 -1
  43. package/dist/cli/__tests__/team.test.js +2 -2
  44. package/dist/cli/__tests__/update.test.js +323 -18
  45. package/dist/cli/__tests__/update.test.js.map +1 -1
  46. package/dist/cli/__tests__/windows-popup-loop-contract.test.js +1 -1
  47. package/dist/cli/doctor.d.ts.map +1 -1
  48. package/dist/cli/doctor.js +8 -1
  49. package/dist/cli/doctor.js.map +1 -1
  50. package/dist/cli/index.d.ts +21 -4
  51. package/dist/cli/index.d.ts.map +1 -1
  52. package/dist/cli/index.js +143 -28
  53. package/dist/cli/index.js.map +1 -1
  54. package/dist/cli/plugin-marketplace.d.ts +14 -2
  55. package/dist/cli/plugin-marketplace.d.ts.map +1 -1
  56. package/dist/cli/plugin-marketplace.js +62 -15
  57. package/dist/cli/plugin-marketplace.js.map +1 -1
  58. package/dist/cli/ralph.d.ts.map +1 -1
  59. package/dist/cli/ralph.js +3 -1
  60. package/dist/cli/ralph.js.map +1 -1
  61. package/dist/cli/setup-preferences.d.ts +2 -0
  62. package/dist/cli/setup-preferences.d.ts.map +1 -1
  63. package/dist/cli/setup-preferences.js +4 -0
  64. package/dist/cli/setup-preferences.js.map +1 -1
  65. package/dist/cli/setup.d.ts +3 -0
  66. package/dist/cli/setup.d.ts.map +1 -1
  67. package/dist/cli/setup.js +166 -27
  68. package/dist/cli/setup.js.map +1 -1
  69. package/dist/cli/state.d.ts.map +1 -1
  70. package/dist/cli/state.js +8 -1
  71. package/dist/cli/state.js.map +1 -1
  72. package/dist/cli/tmux-hook.d.ts.map +1 -1
  73. package/dist/cli/tmux-hook.js +16 -0
  74. package/dist/cli/tmux-hook.js.map +1 -1
  75. package/dist/cli/update.d.ts +22 -3
  76. package/dist/cli/update.d.ts.map +1 -1
  77. package/dist/cli/update.js +312 -26
  78. package/dist/cli/update.js.map +1 -1
  79. package/dist/cli/version.d.ts.map +1 -1
  80. package/dist/cli/version.js +5 -9
  81. package/dist/cli/version.js.map +1 -1
  82. package/dist/compat/__tests__/doctor-contract.test.js +12 -1
  83. package/dist/compat/__tests__/doctor-contract.test.js.map +1 -1
  84. package/dist/config/__tests__/generator-notify.test.js +1 -0
  85. package/dist/config/__tests__/generator-notify.test.js.map +1 -1
  86. package/dist/config/generator.d.ts +2 -2
  87. package/dist/config/generator.d.ts.map +1 -1
  88. package/dist/config/generator.js +2 -2
  89. package/dist/config/generator.js.map +1 -1
  90. package/dist/config/team-mode.d.ts +12 -0
  91. package/dist/config/team-mode.d.ts.map +1 -0
  92. package/dist/config/team-mode.js +91 -0
  93. package/dist/config/team-mode.js.map +1 -0
  94. package/dist/hooks/__tests__/agents-overlay.test.js +88 -0
  95. package/dist/hooks/__tests__/agents-overlay.test.js.map +1 -1
  96. package/dist/hooks/__tests__/code-review-skill-contract.test.js +12 -0
  97. package/dist/hooks/__tests__/code-review-skill-contract.test.js.map +1 -1
  98. package/dist/hooks/__tests__/deep-interview-contract.test.js +30 -1
  99. package/dist/hooks/__tests__/deep-interview-contract.test.js.map +1 -1
  100. package/dist/hooks/__tests__/keyword-detector.test.js +423 -3
  101. package/dist/hooks/__tests__/keyword-detector.test.js.map +1 -1
  102. package/dist/hooks/__tests__/notify-fallback-watcher.test.js +1 -1
  103. package/dist/hooks/__tests__/notify-fallback-watcher.test.js.map +1 -1
  104. package/dist/hooks/__tests__/notify-hook-auto-nudge.test.js +189 -0
  105. package/dist/hooks/__tests__/notify-hook-auto-nudge.test.js.map +1 -1
  106. package/dist/hooks/__tests__/notify-hook-team-leader-nudge.test.js +35 -2
  107. package/dist/hooks/__tests__/notify-hook-team-leader-nudge.test.js.map +1 -1
  108. package/dist/hooks/__tests__/notify-hook-tmux-heal.test.js +3 -3
  109. package/dist/hooks/__tests__/notify-hook-tmux-heal.test.js.map +1 -1
  110. package/dist/hooks/__tests__/skill-guidance-contract.test.js +21 -0
  111. package/dist/hooks/__tests__/skill-guidance-contract.test.js.map +1 -1
  112. package/dist/hooks/agents-overlay.d.ts.map +1 -1
  113. package/dist/hooks/agents-overlay.js +36 -50
  114. package/dist/hooks/agents-overlay.js.map +1 -1
  115. package/dist/hooks/extensibility/__tests__/plugin-runner.test.js +31 -0
  116. package/dist/hooks/extensibility/__tests__/plugin-runner.test.js.map +1 -1
  117. package/dist/hooks/extensibility/plugin-runner.js +17 -21
  118. package/dist/hooks/extensibility/plugin-runner.js.map +1 -1
  119. package/dist/hooks/keyword-detector.d.ts.map +1 -1
  120. package/dist/hooks/keyword-detector.js +258 -12
  121. package/dist/hooks/keyword-detector.js.map +1 -1
  122. package/dist/hooks/prompt-guidance-contract.d.ts.map +1 -1
  123. package/dist/hooks/prompt-guidance-contract.js +6 -0
  124. package/dist/hooks/prompt-guidance-contract.js.map +1 -1
  125. package/dist/hooks/session.d.ts +1 -0
  126. package/dist/hooks/session.d.ts.map +1 -1
  127. package/dist/hooks/session.js.map +1 -1
  128. package/dist/hud/__tests__/authority.test.js +435 -32
  129. package/dist/hud/__tests__/authority.test.js.map +1 -1
  130. package/dist/hud/__tests__/hud-tmux-injection.test.js +2 -1
  131. package/dist/hud/__tests__/hud-tmux-injection.test.js.map +1 -1
  132. package/dist/hud/__tests__/index.test.js +42 -0
  133. package/dist/hud/__tests__/index.test.js.map +1 -1
  134. package/dist/hud/__tests__/reconcile.test.js +642 -15
  135. package/dist/hud/__tests__/reconcile.test.js.map +1 -1
  136. package/dist/hud/__tests__/render.test.js +61 -0
  137. package/dist/hud/__tests__/render.test.js.map +1 -1
  138. package/dist/hud/__tests__/state.test.js +160 -4
  139. package/dist/hud/__tests__/state.test.js.map +1 -1
  140. package/dist/hud/__tests__/tmux.test.js +180 -21
  141. package/dist/hud/__tests__/tmux.test.js.map +1 -1
  142. package/dist/hud/authority.d.ts +5 -0
  143. package/dist/hud/authority.d.ts.map +1 -1
  144. package/dist/hud/authority.js +324 -28
  145. package/dist/hud/authority.js.map +1 -1
  146. package/dist/hud/index.d.ts +3 -2
  147. package/dist/hud/index.d.ts.map +1 -1
  148. package/dist/hud/index.js +42 -19
  149. package/dist/hud/index.js.map +1 -1
  150. package/dist/hud/reconcile.d.ts +3 -3
  151. package/dist/hud/reconcile.d.ts.map +1 -1
  152. package/dist/hud/reconcile.js +128 -19
  153. package/dist/hud/reconcile.js.map +1 -1
  154. package/dist/hud/render.d.ts.map +1 -1
  155. package/dist/hud/render.js +35 -0
  156. package/dist/hud/render.js.map +1 -1
  157. package/dist/hud/state.d.ts.map +1 -1
  158. package/dist/hud/state.js +65 -80
  159. package/dist/hud/state.js.map +1 -1
  160. package/dist/hud/tmux.d.ts +24 -6
  161. package/dist/hud/tmux.d.ts.map +1 -1
  162. package/dist/hud/tmux.js +136 -38
  163. package/dist/hud/tmux.js.map +1 -1
  164. package/dist/hud/types.d.ts +11 -0
  165. package/dist/hud/types.d.ts.map +1 -1
  166. package/dist/hud/types.js.map +1 -1
  167. package/dist/mcp/__tests__/state-paths.test.js +71 -1
  168. package/dist/mcp/__tests__/state-paths.test.js.map +1 -1
  169. package/dist/mcp/state-paths.d.ts +32 -0
  170. package/dist/mcp/state-paths.d.ts.map +1 -1
  171. package/dist/mcp/state-paths.js +113 -17
  172. package/dist/mcp/state-paths.js.map +1 -1
  173. package/dist/mcp/state-server.d.ts +4 -4
  174. package/dist/question/__tests__/renderer.test.js +566 -1
  175. package/dist/question/__tests__/renderer.test.js.map +1 -1
  176. package/dist/question/renderer.d.ts +9 -1
  177. package/dist/question/renderer.d.ts.map +1 -1
  178. package/dist/question/renderer.js +246 -70
  179. package/dist/question/renderer.js.map +1 -1
  180. package/dist/scripts/__tests__/codex-native-hook.test.js +837 -101
  181. package/dist/scripts/__tests__/codex-native-hook.test.js.map +1 -1
  182. package/dist/scripts/__tests__/notify-state-io.test.js +72 -1
  183. package/dist/scripts/__tests__/notify-state-io.test.js.map +1 -1
  184. package/dist/scripts/__tests__/notify-tmux-injection.test.d.ts +2 -0
  185. package/dist/scripts/__tests__/notify-tmux-injection.test.d.ts.map +1 -0
  186. package/dist/scripts/__tests__/notify-tmux-injection.test.js +57 -0
  187. package/dist/scripts/__tests__/notify-tmux-injection.test.js.map +1 -0
  188. package/dist/scripts/__tests__/run-test-files.test.js +74 -0
  189. package/dist/scripts/__tests__/run-test-files.test.js.map +1 -1
  190. package/dist/scripts/__tests__/verify-native-agents.test.js +65 -0
  191. package/dist/scripts/__tests__/verify-native-agents.test.js.map +1 -1
  192. package/dist/scripts/codex-native-hook.d.ts.map +1 -1
  193. package/dist/scripts/codex-native-hook.js +107 -39
  194. package/dist/scripts/codex-native-hook.js.map +1 -1
  195. package/dist/scripts/eval/eval-parity-smoke.js +1 -1
  196. package/dist/scripts/eval/eval-parity-smoke.js.map +1 -1
  197. package/dist/scripts/notify-hook/auto-nudge.d.ts.map +1 -1
  198. package/dist/scripts/notify-hook/auto-nudge.js +3 -1
  199. package/dist/scripts/notify-hook/auto-nudge.js.map +1 -1
  200. package/dist/scripts/notify-hook/ralph-session-resume.d.ts.map +1 -1
  201. package/dist/scripts/notify-hook/ralph-session-resume.js +3 -10
  202. package/dist/scripts/notify-hook/ralph-session-resume.js.map +1 -1
  203. package/dist/scripts/notify-hook/state-io.d.ts.map +1 -1
  204. package/dist/scripts/notify-hook/state-io.js +62 -38
  205. package/dist/scripts/notify-hook/state-io.js.map +1 -1
  206. package/dist/scripts/notify-hook/team-leader-nudge.d.ts.map +1 -1
  207. package/dist/scripts/notify-hook/team-leader-nudge.js +7 -0
  208. package/dist/scripts/notify-hook/team-leader-nudge.js.map +1 -1
  209. package/dist/scripts/notify-hook/tmux-injection.d.ts +7 -0
  210. package/dist/scripts/notify-hook/tmux-injection.d.ts.map +1 -1
  211. package/dist/scripts/notify-hook/tmux-injection.js +24 -18
  212. package/dist/scripts/notify-hook/tmux-injection.js.map +1 -1
  213. package/dist/scripts/notify-hook.js +75 -11
  214. package/dist/scripts/notify-hook.js.map +1 -1
  215. package/dist/scripts/run-test-files.js +193 -22
  216. package/dist/scripts/run-test-files.js.map +1 -1
  217. package/dist/scripts/sync-plugin-mirror.d.ts.map +1 -1
  218. package/dist/scripts/sync-plugin-mirror.js +61 -3
  219. package/dist/scripts/sync-plugin-mirror.js.map +1 -1
  220. package/dist/scripts/verify-native-agents.d.ts.map +1 -1
  221. package/dist/scripts/verify-native-agents.js +58 -1
  222. package/dist/scripts/verify-native-agents.js.map +1 -1
  223. package/dist/state/__tests__/operations.test.js +113 -0
  224. package/dist/state/__tests__/operations.test.js.map +1 -1
  225. package/dist/state/__tests__/skill-active.test.js +3 -16
  226. package/dist/state/__tests__/skill-active.test.js.map +1 -1
  227. package/dist/state/__tests__/workflow-transition.test.js +25 -0
  228. package/dist/state/__tests__/workflow-transition.test.js.map +1 -1
  229. package/dist/state/operations.d.ts.map +1 -1
  230. package/dist/state/operations.js +57 -2
  231. package/dist/state/operations.js.map +1 -1
  232. package/dist/state/skill-active.d.ts.map +1 -1
  233. package/dist/state/skill-active.js +7 -39
  234. package/dist/state/skill-active.js.map +1 -1
  235. package/dist/state/workflow-transition-reconcile.d.ts.map +1 -1
  236. package/dist/state/workflow-transition-reconcile.js +10 -14
  237. package/dist/state/workflow-transition-reconcile.js.map +1 -1
  238. package/dist/team/__tests__/runtime.test.js +1 -1
  239. package/dist/team/__tests__/runtime.test.js.map +1 -1
  240. package/dist/team/__tests__/scaling.test.js +9 -4
  241. package/dist/team/__tests__/scaling.test.js.map +1 -1
  242. package/dist/team/__tests__/tmux-session.test.js +195 -2
  243. package/dist/team/__tests__/tmux-session.test.js.map +1 -1
  244. package/dist/team/__tests__/worker-runtime-identity.test.js +4 -2
  245. package/dist/team/__tests__/worker-runtime-identity.test.js.map +1 -1
  246. package/dist/team/scaling.d.ts.map +1 -1
  247. package/dist/team/scaling.js +3 -2
  248. package/dist/team/scaling.js.map +1 -1
  249. package/dist/team/tmux-session.d.ts +2 -0
  250. package/dist/team/tmux-session.d.ts.map +1 -1
  251. package/dist/team/tmux-session.js +142 -12
  252. package/dist/team/tmux-session.js.map +1 -1
  253. package/dist/utils/__tests__/platform-command.test.js +16 -1
  254. package/dist/utils/__tests__/platform-command.test.js.map +1 -1
  255. package/dist/utils/__tests__/version.test.d.ts +2 -0
  256. package/dist/utils/__tests__/version.test.d.ts.map +1 -0
  257. package/dist/utils/__tests__/version.test.js +51 -0
  258. package/dist/utils/__tests__/version.test.js.map +1 -0
  259. package/dist/utils/paths.d.ts +8 -1
  260. package/dist/utils/paths.d.ts.map +1 -1
  261. package/dist/utils/paths.js +16 -4
  262. package/dist/utils/paths.js.map +1 -1
  263. package/dist/utils/platform-command.d.ts +9 -0
  264. package/dist/utils/platform-command.d.ts.map +1 -1
  265. package/dist/utils/platform-command.js +15 -0
  266. package/dist/utils/platform-command.js.map +1 -1
  267. package/dist/utils/version.d.ts +7 -0
  268. package/dist/utils/version.d.ts.map +1 -0
  269. package/dist/utils/version.js +67 -0
  270. package/dist/utils/version.js.map +1 -0
  271. package/dist/verification/__tests__/ci-rust-gates.test.js +89 -1
  272. package/dist/verification/__tests__/ci-rust-gates.test.js.map +1 -1
  273. package/dist/verification/__tests__/dev-merge-issue-close-workflow.test.js +16 -2
  274. package/dist/verification/__tests__/dev-merge-issue-close-workflow.test.js.map +1 -1
  275. package/package.json +11 -10
  276. package/plugins/oh-my-codex/.codex-plugin/plugin.json +1 -1
  277. package/plugins/oh-my-codex/hooks/codex-native-hook.mjs +334 -21
  278. package/plugins/oh-my-codex/hooks/hooks.json +1 -2
  279. package/plugins/oh-my-codex/skills/autopilot/SKILL.md +3 -1
  280. package/plugins/oh-my-codex/skills/code-review/SKILL.md +7 -7
  281. package/plugins/oh-my-codex/skills/deep-interview/SKILL.md +51 -11
  282. package/plugins/oh-my-codex/skills/ralph/SKILL.md +22 -22
  283. package/plugins/oh-my-codex/skills/ultraqa/SKILL.md +9 -0
  284. package/skills/autopilot/SKILL.md +3 -1
  285. package/skills/code-review/SKILL.md +7 -7
  286. package/skills/deep-interview/SKILL.md +51 -11
  287. package/skills/ralph/SKILL.md +22 -22
  288. package/skills/ultraqa/SKILL.md +9 -0
  289. package/src/scripts/__tests__/codex-native-hook.test.ts +946 -98
  290. package/src/scripts/__tests__/notify-state-io.test.ts +95 -0
  291. package/src/scripts/__tests__/notify-tmux-injection.test.ts +82 -0
  292. package/src/scripts/__tests__/run-test-files.test.ts +102 -0
  293. package/src/scripts/__tests__/verify-native-agents.test.ts +75 -0
  294. package/src/scripts/codex-native-hook.ts +123 -34
  295. package/src/scripts/demo-team-e2e.sh +10 -7
  296. package/src/scripts/eval/eval-parity-smoke.ts +1 -1
  297. package/src/scripts/notify-hook/auto-nudge.ts +3 -1
  298. package/src/scripts/notify-hook/ralph-session-resume.ts +2 -8
  299. package/src/scripts/notify-hook/state-io.ts +75 -37
  300. package/src/scripts/notify-hook/team-leader-nudge.ts +7 -0
  301. package/src/scripts/notify-hook/tmux-injection.ts +35 -19
  302. package/src/scripts/notify-hook.ts +91 -4
  303. package/src/scripts/prepare-build.js +83 -0
  304. package/src/scripts/run-test-files.ts +192 -22
  305. package/src/scripts/sync-plugin-mirror.ts +98 -9
  306. package/src/scripts/verify-native-agents.ts +65 -1
  307. package/src/scripts/postinstall-bootstrap.js +0 -23
@@ -69,6 +69,35 @@ async function writeJson(path: string, value: unknown): Promise<void> {
69
69
  await writeFile(path, JSON.stringify(value, null, 2));
70
70
  }
71
71
 
72
+ async function writeNativeMappedSessionState(
73
+ cwd: string,
74
+ stateDir: string,
75
+ sessionId: string,
76
+ nativeSessionId: string,
77
+ ): Promise<void> {
78
+ await mkdir(join(stateDir, "sessions", sessionId), { recursive: true });
79
+ await writeJson(join(stateDir, "session.json"), {
80
+ session_id: sessionId,
81
+ native_session_id: nativeSessionId,
82
+ cwd,
83
+ });
84
+ }
85
+
86
+ async function writeSessionSkillActiveState(
87
+ stateDir: string,
88
+ sessionId: string,
89
+ skill: string,
90
+ phase: string,
91
+ ): Promise<void> {
92
+ await writeJson(join(stateDir, "sessions", sessionId, "skill-active-state.json"), {
93
+ active: true,
94
+ skill,
95
+ phase,
96
+ session_id: sessionId,
97
+ active_skills: [{ skill, phase, active: true, session_id: sessionId }],
98
+ });
99
+ }
100
+
72
101
  async function setTeamPaneIds(
73
102
  cwd: string,
74
103
  teamName: string,
@@ -1604,7 +1633,7 @@ describe("codex native hook dispatch", () => {
1604
1633
  assert.equal(sessionState.previous_native_session_id, oldNativeSessionId);
1605
1634
  assert.equal(sessionState.owner_omx_session_id, ownerSessionId);
1606
1635
 
1607
- let reconcileCall: { cwd: string; sessionId?: string } | null = null;
1636
+ let reconcileCall: { cwd: string; sessionId?: string; sessionIds?: string[] } | null = null;
1608
1637
  const promptResult = await dispatchCodexNativeHook(
1609
1638
  {
1610
1639
  hook_event_name: "UserPromptSubmit",
@@ -1617,14 +1646,18 @@ describe("codex native hook dispatch", () => {
1617
1646
  {
1618
1647
  cwd,
1619
1648
  reconcileHudForPromptSubmitFn: async (hookCwd, deps = {}) => {
1620
- reconcileCall = { cwd: hookCwd, sessionId: deps.sessionId };
1649
+ reconcileCall = { cwd: hookCwd, sessionId: deps.sessionId, sessionIds: deps.sessionIds };
1621
1650
  return { status: "recreated", paneId: "%9", desiredHeight: 3, duplicateCount: 0 };
1622
1651
  },
1623
1652
  },
1624
1653
  );
1625
1654
 
1626
1655
  assert.equal(promptResult.omxEventName, "keyword-detector");
1627
- assert.deepEqual(reconcileCall, { cwd, sessionId: ownerSessionId });
1656
+ assert.deepEqual(reconcileCall, {
1657
+ cwd,
1658
+ sessionId: ownerSessionId,
1659
+ sessionIds: [ownerSessionId, nativeSessionId],
1660
+ });
1628
1661
  } finally {
1629
1662
  await rm(cwd, { recursive: true, force: true });
1630
1663
  }
@@ -1645,7 +1678,7 @@ describe("codex native hook dispatch", () => {
1645
1678
  sessionState.owner_omx_session_id = invalidOwnerSessionId;
1646
1679
  await writeJson(sessionStatePath, sessionState);
1647
1680
 
1648
- let reconcileCall: { cwd: string; sessionId?: string } | null = null;
1681
+ let reconcileCall: { cwd: string; sessionId?: string; sessionIds?: string[] } | null = null;
1649
1682
  const promptResult = await dispatchCodexNativeHook(
1650
1683
  {
1651
1684
  hook_event_name: "UserPromptSubmit",
@@ -1658,14 +1691,18 @@ describe("codex native hook dispatch", () => {
1658
1691
  {
1659
1692
  cwd,
1660
1693
  reconcileHudForPromptSubmitFn: async (hookCwd, deps = {}) => {
1661
- reconcileCall = { cwd: hookCwd, sessionId: deps.sessionId };
1694
+ reconcileCall = { cwd: hookCwd, sessionId: deps.sessionId, sessionIds: deps.sessionIds };
1662
1695
  return { status: "recreated", paneId: "%9", desiredHeight: 3, duplicateCount: 0 };
1663
1696
  },
1664
1697
  },
1665
1698
  );
1666
1699
 
1667
1700
  assert.equal(promptResult.omxEventName, "keyword-detector");
1668
- assert.deepEqual(reconcileCall, { cwd, sessionId: canonicalSessionId });
1701
+ assert.deepEqual(reconcileCall, {
1702
+ cwd,
1703
+ sessionId: canonicalSessionId,
1704
+ sessionIds: [canonicalSessionId, nativeSessionId],
1705
+ });
1669
1706
  } finally {
1670
1707
  await rm(cwd, { recursive: true, force: true });
1671
1708
  }
@@ -1830,6 +1867,51 @@ describe("codex native hook dispatch", () => {
1830
1867
  }
1831
1868
  });
1832
1869
 
1870
+ it("includes repo-local .omx project-memory during SessionStart when OMX_ROOT is boxed", async () => {
1871
+ const cwd = await mkdtemp(join(tmpdir(), "omx-native-hook-session-boxed-memory-"));
1872
+ const boxedRoot = await mkdtemp(join(tmpdir(), "omx-native-hook-boxed-root-"));
1873
+ const previousOmxRoot = process.env.OMX_ROOT;
1874
+ try {
1875
+ process.env.OMX_ROOT = boxedRoot;
1876
+ await writeJson(join(cwd, ".omx", "project-memory.json"), {
1877
+ techStack: "Repo-local CLI memory",
1878
+ conventions: "SessionStart should load CLI-written project memory",
1879
+ directives: [
1880
+ { directive: "Prefer repo-local .omx project memory over boxed runtime fallback.", priority: "high" },
1881
+ ],
1882
+ });
1883
+ await writeJson(join(boxedRoot, ".omx", "project-memory.json"), {
1884
+ techStack: "Boxed runtime memory should not win",
1885
+ notes: [{ category: "runtime", content: "stale boxed runtime note", timestamp: new Date().toISOString() }],
1886
+ });
1887
+
1888
+ const result = await dispatchCodexNativeHook(
1889
+ {
1890
+ hook_event_name: "SessionStart",
1891
+ cwd,
1892
+ session_id: "sess-boxed-memory-1",
1893
+ },
1894
+ { cwd, sessionOwnerPid: 43210 },
1895
+ );
1896
+
1897
+ const additionalContext = String(
1898
+ (result.outputJson as { hookSpecificOutput?: { additionalContext?: string } })?.hookSpecificOutput?.additionalContext ?? "",
1899
+ );
1900
+ assert.match(additionalContext, /\[Project memory\]/);
1901
+ assert.match(additionalContext, /source: \.omx\/project-memory\.json/);
1902
+ assert.match(additionalContext, /Repo-local CLI memory/);
1903
+ assert.match(additionalContext, /SessionStart should load CLI-written project memory/);
1904
+ assert.match(additionalContext, /Prefer repo-local \.omx project memory over boxed runtime fallback\./);
1905
+ assert.doesNotMatch(additionalContext, /Boxed runtime memory should not win/);
1906
+ assert.doesNotMatch(additionalContext, /stale boxed runtime note/);
1907
+ } finally {
1908
+ if (previousOmxRoot === undefined) delete process.env.OMX_ROOT;
1909
+ else process.env.OMX_ROOT = previousOmxRoot;
1910
+ await rm(cwd, { recursive: true, force: true });
1911
+ await rm(boxedRoot, { recursive: true, force: true });
1912
+ }
1913
+ });
1914
+
1833
1915
  it("prefers repository project-memory.json during SessionStart while preserving legacy wiki guidance", async () => {
1834
1916
  const cwd = await mkdtemp(join(tmpdir(), "omx-native-hook-session-root-memory-legacy-wiki-"));
1835
1917
  try {
@@ -3460,6 +3542,17 @@ standardMaxRounds = 15
3460
3542
  assert.match(message, /Do not advance from deep-interview to ralplan merely because the first question was answered/);
3461
3543
  assert.match(message, /Planner output has been reviewed sequentially by Architect and then Critic/);
3462
3544
  assert.match(message, /do not hand off to Ultragoal or implementation until .*ralplan_architect_review.*ralplan_critic_review/);
3545
+
3546
+ const autopilotState = JSON.parse(await readFile(
3547
+ join(cwd, ".omx", "state", "sessions", "sess-autopilot-ralplan-gate", "autopilot-state.json"),
3548
+ "utf-8",
3549
+ )) as { state?: { handoff_artifacts?: { context_snapshot_path?: string } } };
3550
+ const snapshotPath = autopilotState.state?.handoff_artifacts?.context_snapshot_path ?? "";
3551
+ assert.match(snapshotPath, /^\.omx\/context\/implement-issue-2430-\d{8}T\d{6}Z\.md$/);
3552
+ const snapshot = await readFile(join(cwd, snapshotPath), "utf-8");
3553
+ assert.match(snapshot, /activation prompt \/ task seed: \$autopilot implement issue #2430/);
3554
+ assert.match(snapshot, /scope note: this seed captures the Autopilot activation prompt/);
3555
+ assert.match(snapshot, /constraints: follow deep-interview -> ralplan -> ultragoal -> code-review -> ultraqa/);
3463
3556
  } finally {
3464
3557
  await rm(cwd, { recursive: true, force: true });
3465
3558
  }
@@ -5035,7 +5128,51 @@ esac
5035
5128
  }
5036
5129
  });
5037
5130
 
5038
- it("reuses an existing owner-tagged HUD pane when UserPromptSubmit revives with the canonical session id", async () => {
5131
+ it("skips prompt-submit HUD reconciliation during doctor smoke validation", async () => {
5132
+ const cwd = await mkdtemp(join(tmpdir(), "omx-native-hook-doctor-smoke-hud-"));
5133
+ const originalTmux = process.env.TMUX;
5134
+ const originalTmuxPane = process.env.TMUX_PANE;
5135
+ const originalHudOwner = process.env[OMX_TMUX_HUD_OWNER_ENV];
5136
+ const originalDoctorSmoke = process.env.OMX_NATIVE_HOOK_DOCTOR_SMOKE;
5137
+ try {
5138
+ process.env.TMUX = "1";
5139
+ process.env.TMUX_PANE = "%1";
5140
+ process.env[OMX_TMUX_HUD_OWNER_ENV] = "1";
5141
+ process.env.OMX_NATIVE_HOOK_DOCTOR_SMOKE = "1";
5142
+
5143
+ let reconcileCalled = false;
5144
+ const result = await dispatchCodexNativeHook(
5145
+ {
5146
+ hook_event_name: "UserPromptSubmit",
5147
+ cwd,
5148
+ session_id: "omx-doctor-plugin-hook-smoke",
5149
+ prompt: "$ralplan doctor plugin hook smoke test",
5150
+ },
5151
+ {
5152
+ cwd,
5153
+ reconcileHudForPromptSubmitFn: async () => {
5154
+ reconcileCalled = true;
5155
+ return { status: "recreated", paneId: "%9", desiredHeight: 3, duplicateCount: 0 };
5156
+ },
5157
+ },
5158
+ );
5159
+
5160
+ assert.equal(result.omxEventName, "keyword-detector");
5161
+ assert.equal(reconcileCalled, false);
5162
+ } finally {
5163
+ if (originalTmux === undefined) delete process.env.TMUX;
5164
+ else process.env.TMUX = originalTmux;
5165
+ if (originalTmuxPane === undefined) delete process.env.TMUX_PANE;
5166
+ else process.env.TMUX_PANE = originalTmuxPane;
5167
+ if (originalHudOwner === undefined) delete process.env[OMX_TMUX_HUD_OWNER_ENV];
5168
+ else process.env[OMX_TMUX_HUD_OWNER_ENV] = originalHudOwner;
5169
+ if (originalDoctorSmoke === undefined) delete process.env.OMX_NATIVE_HOOK_DOCTOR_SMOKE;
5170
+ else process.env.OMX_NATIVE_HOOK_DOCTOR_SMOKE = originalDoctorSmoke;
5171
+ await rm(cwd, { recursive: true, force: true });
5172
+ }
5173
+ });
5174
+
5175
+ it("recreates a leader-only HUD pane when UserPromptSubmit revives with the canonical session id", async () => {
5039
5176
  const cwd = await mkdtemp(join(tmpdir(), "omx-native-hook-hud-reuse-"));
5040
5177
  const originalTmux = process.env.TMUX;
5041
5178
  const originalTmuxPane = process.env.TMUX_PANE;
@@ -5091,8 +5228,8 @@ esac
5091
5228
  assert.equal(result.omxEventName, "keyword-detector");
5092
5229
  const tmuxCalls = await readFile(tmuxLog, "utf-8");
5093
5230
  assert.match(tmuxCalls, /list-panes -t %1 -F/);
5094
- assert.match(tmuxCalls, new RegExp(`resize-pane -t %2 -y ${HUD_TMUX_HEIGHT_LINES}`));
5095
- assert.doesNotMatch(tmuxCalls, /split-window/);
5231
+ assert.match(tmuxCalls, /split-window/);
5232
+ assert.match(tmuxCalls, new RegExp(`resize-pane -t %9 -y ${HUD_TMUX_HEIGHT_LINES}`));
5096
5233
  assert.equal(existsSync(join(cwd, ".omx", "state", "sessions", canonicalSessionId, "ralplan-state.json")), true);
5097
5234
  assert.equal(existsSync(join(cwd, ".omx", "state", "sessions", nativeSessionId, "ralplan-state.json")), false);
5098
5235
  } finally {
@@ -13784,7 +13921,7 @@ exit 0
13784
13921
 
13785
13922
  assert.equal(result.omxEventName, "pre-tool-use");
13786
13923
  assert.equal(result.outputJson?.decision, "block");
13787
- assert.match(String(result.outputJson?.reason ?? ""), /Ralplan is active .*implementation\/write tools are blocked/i);
13924
+ assert.match(String(result.outputJson?.reason ?? ""), /(?:Ralplan|Autopilot planning) is active .*implementation\/write tools are blocked/i);
13788
13925
  assert.match(
13789
13926
  String((result.outputJson?.hookSpecificOutput as { additionalContext?: string } | undefined)?.additionalContext ?? ""),
13790
13927
  /\$ultragoal.*\$team.*\$ralph/i,
@@ -13834,41 +13971,36 @@ exit 0
13834
13971
 
13835
13972
  assert.equal(result.omxEventName, "pre-tool-use");
13836
13973
  assert.equal(result.outputJson?.decision, "block");
13837
- assert.match(String(result.outputJson?.reason ?? ""), /Ralplan is active .*implementation\/write tools are blocked/i);
13974
+ assert.match(String(result.outputJson?.reason ?? ""), /(?:Ralplan|Autopilot planning) is active .*implementation\/write tools are blocked/i);
13838
13975
  } finally {
13839
13976
  await rm(cwd, { recursive: true, force: true });
13840
13977
  }
13841
13978
  });
13842
13979
 
13843
- it("allows implementation writes when terminal Autopilot run-state shadows stale supervised ralplan state", async () => {
13844
- const cwd = await mkdtemp(join(tmpdir(), "omx-native-hook-autopilot-ralplan-terminal-pretool-"));
13980
+ it("blocks implementation writes when Autopilot ralplan is visible only in skill-active phase", async () => {
13981
+ const cwd = await mkdtemp(join(tmpdir(), "omx-native-hook-autopilot-skill-ralplan-pretool-block-"));
13845
13982
  try {
13846
13983
  const stateDir = join(cwd, ".omx", "state");
13847
- const sessionId = "sess-autopilot-ralplan-terminal-pretool";
13984
+ const sessionId = "sess-autopilot-skill-ralplan-pretool-block";
13848
13985
  await mkdir(join(stateDir, "sessions", sessionId), { recursive: true });
13849
13986
  await writeJson(join(stateDir, "session.json"), { session_id: sessionId });
13850
13987
  await writeJson(join(stateDir, "sessions", sessionId, "skill-active-state.json"), {
13851
13988
  active: true,
13852
13989
  skill: "autopilot",
13853
- phase: "ralplan",
13990
+ phase: "autopilot:ralplan",
13854
13991
  session_id: sessionId,
13855
- active_skills: [{ skill: "autopilot", phase: "ralplan", active: true, session_id: sessionId }],
13992
+ active_skills: [{ skill: "autopilot", phase: "autopilot:ralplan", active: true, session_id: sessionId }],
13856
13993
  });
13857
13994
  await writeJson(join(stateDir, "sessions", sessionId, "autopilot-state.json"), {
13858
13995
  active: true,
13859
13996
  mode: "autopilot",
13860
- current_phase: "ralplan",
13997
+ current_phase: "planning",
13861
13998
  session_id: sessionId,
13862
- });
13863
- await writeJson(join(stateDir, "sessions", sessionId, "run-state.json"), {
13864
- version: 1,
13865
- active: false,
13866
- mode: "autopilot",
13867
- outcome: "finish",
13868
- lifecycle_outcome: "finished",
13869
- current_phase: "complete",
13870
- completed_at: "2026-05-30T00:00:00.000Z",
13871
- updated_at: "2026-05-30T00:00:00.000Z",
13999
+ state: {
14000
+ handoff_artifacts: {
14001
+ ralplan_consensus_gate: { required: true, complete: false },
14002
+ },
14003
+ },
13872
14004
  });
13873
14005
 
13874
14006
  const result = await dispatchCodexNativeHook(
@@ -13876,7 +14008,7 @@ exit 0
13876
14008
  hook_event_name: "PreToolUse",
13877
14009
  cwd,
13878
14010
  session_id: sessionId,
13879
- thread_id: "thread-autopilot-ralplan-terminal-pretool",
14011
+ thread_id: "thread-autopilot-skill-ralplan-pretool-block",
13880
14012
  tool_name: "Edit",
13881
14013
  tool_input: { file_path: "src/runtime.ts" },
13882
14014
  },
@@ -13884,30 +14016,74 @@ exit 0
13884
14016
  );
13885
14017
 
13886
14018
  assert.equal(result.omxEventName, "pre-tool-use");
13887
- assert.equal(result.outputJson, null);
14019
+ assert.equal(result.outputJson?.decision, "block");
14020
+ assert.match(String(result.outputJson?.reason ?? ""), /Autopilot planning is active .*implementation\/write tools are blocked/i);
13888
14021
  } finally {
13889
14022
  await rm(cwd, { recursive: true, force: true });
13890
14023
  }
13891
14024
  });
13892
14025
 
13893
- it("blocks bash implementation writes while Autopilot is supervising ralplan without handoff", async () => {
13894
- const cwd = await mkdtemp(join(tmpdir(), "omx-native-hook-autopilot-ralplan-pretool-bash-block-"));
14026
+ it("ignores stale Autopilot ralplan skill mirrors after detail state leaves planning", async () => {
14027
+ const cwd = await mkdtemp(join(tmpdir(), "omx-native-hook-autopilot-stale-ralplan-mirror-"));
13895
14028
  try {
13896
14029
  const stateDir = join(cwd, ".omx", "state");
13897
- const sessionId = "sess-autopilot-ralplan-pretool-bash-block";
14030
+ const sessionId = "sess-autopilot-stale-ralplan-mirror";
13898
14031
  await mkdir(join(stateDir, "sessions", sessionId), { recursive: true });
13899
14032
  await writeJson(join(stateDir, "session.json"), { session_id: sessionId });
13900
14033
  await writeJson(join(stateDir, "sessions", sessionId, "skill-active-state.json"), {
13901
14034
  active: true,
13902
14035
  skill: "autopilot",
13903
- phase: "ralplan",
14036
+ phase: "autopilot:ralplan",
13904
14037
  session_id: sessionId,
13905
- active_skills: [{ skill: "autopilot", phase: "ralplan", active: true, session_id: sessionId }],
14038
+ active_skills: [{ skill: "autopilot", phase: "autopilot:ralplan", active: true, session_id: sessionId }],
14039
+ });
14040
+
14041
+ for (const phase of ["ultragoal", "code-review", "completing", "complete"]) {
14042
+ await writeJson(join(stateDir, "sessions", sessionId, "autopilot-state.json"), {
14043
+ active: true,
14044
+ mode: "autopilot",
14045
+ current_phase: phase,
14046
+ session_id: sessionId,
14047
+ });
14048
+
14049
+ const result = await dispatchCodexNativeHook(
14050
+ {
14051
+ hook_event_name: "PreToolUse",
14052
+ cwd,
14053
+ session_id: sessionId,
14054
+ thread_id: "thread-autopilot-stale-ralplan-mirror",
14055
+ tool_name: "Edit",
14056
+ tool_input: { file_path: "src/runtime.ts" },
14057
+ },
14058
+ { cwd },
14059
+ );
14060
+
14061
+ assert.equal(result.omxEventName, "pre-tool-use");
14062
+ assert.equal(result.outputJson, null, `stale skill-active ralplan mirror must not block when Autopilot detail phase is ${phase}`);
14063
+ }
14064
+ } finally {
14065
+ await rm(cwd, { recursive: true, force: true });
14066
+ }
14067
+ });
14068
+
14069
+ it("allows explicit blank Autopilot detail phase to use a ralplan skill mirror", async () => {
14070
+ const cwd = await mkdtemp(join(tmpdir(), "omx-native-hook-autopilot-blank-phase-mirror-"));
14071
+ try {
14072
+ const stateDir = join(cwd, ".omx", "state");
14073
+ const sessionId = "sess-autopilot-blank-phase-mirror";
14074
+ await mkdir(join(stateDir, "sessions", sessionId), { recursive: true });
14075
+ await writeJson(join(stateDir, "session.json"), { session_id: sessionId });
14076
+ await writeJson(join(stateDir, "sessions", sessionId, "skill-active-state.json"), {
14077
+ active: true,
14078
+ skill: "autopilot",
14079
+ phase: "autopilot:ralplan",
14080
+ session_id: sessionId,
14081
+ active_skills: [{ skill: "autopilot", phase: "autopilot:ralplan", active: true, session_id: sessionId }],
13906
14082
  });
13907
14083
  await writeJson(join(stateDir, "sessions", sessionId, "autopilot-state.json"), {
13908
14084
  active: true,
13909
14085
  mode: "autopilot",
13910
- current_phase: "ralplan",
14086
+ current_phase: "",
13911
14087
  session_id: sessionId,
13912
14088
  });
13913
14089
 
@@ -13916,39 +14092,32 @@ exit 0
13916
14092
  hook_event_name: "PreToolUse",
13917
14093
  cwd,
13918
14094
  session_id: sessionId,
13919
- thread_id: "thread-autopilot-ralplan-pretool-bash-block",
13920
- tool_name: "Bash",
13921
- tool_input: { command: "cat <<'EOF' > src/runtime.ts\nimplementation\nEOF" },
14095
+ thread_id: "thread-autopilot-blank-phase-mirror",
14096
+ tool_name: "Edit",
14097
+ tool_input: { file_path: "src/runtime.ts" },
13922
14098
  },
13923
14099
  { cwd },
13924
14100
  );
13925
14101
 
13926
14102
  assert.equal(result.omxEventName, "pre-tool-use");
13927
14103
  assert.equal(result.outputJson?.decision, "block");
13928
- assert.match(String(result.outputJson?.reason ?? ""), /Ralplan is active .*implementation\/write tools are blocked/i);
14104
+ assert.match(String(result.outputJson?.reason ?? ""), /Autopilot planning is active .*implementation\/write tools are blocked/i);
13929
14105
  } finally {
13930
14106
  await rm(cwd, { recursive: true, force: true });
13931
14107
  }
13932
14108
  });
13933
14109
 
13934
- it("allows ralplan planning artifact writes without execution handoff", async () => {
13935
- const cwd = await mkdtemp(join(tmpdir(), "omx-native-hook-ralplan-pretool-artifact-"));
14110
+ it("does not block implementation writes from Autopilot ralplan detail state without canonical skill state", async () => {
14111
+ const cwd = await mkdtemp(join(tmpdir(), "omx-native-hook-autopilot-ralplan-no-canonical-"));
13936
14112
  try {
13937
14113
  const stateDir = join(cwd, ".omx", "state");
13938
- const sessionId = "sess-ralplan-pretool-artifact";
14114
+ const sessionId = "sess-autopilot-ralplan-no-canonical";
13939
14115
  await mkdir(join(stateDir, "sessions", sessionId), { recursive: true });
13940
14116
  await writeJson(join(stateDir, "session.json"), { session_id: sessionId });
13941
- await writeJson(join(stateDir, "sessions", sessionId, "skill-active-state.json"), {
13942
- active: true,
13943
- skill: "ralplan",
13944
- phase: "planning",
13945
- session_id: sessionId,
13946
- active_skills: [{ skill: "ralplan", phase: "planning", active: true, session_id: sessionId }],
13947
- });
13948
- await writeJson(join(stateDir, "sessions", sessionId, "ralplan-state.json"), {
14117
+ await writeJson(join(stateDir, "sessions", sessionId, "autopilot-state.json"), {
13949
14118
  active: true,
13950
- mode: "ralplan",
13951
- current_phase: "planning",
14119
+ mode: "autopilot",
14120
+ current_phase: "ralplan",
13952
14121
  session_id: sessionId,
13953
14122
  });
13954
14123
 
@@ -13957,9 +14126,9 @@ exit 0
13957
14126
  hook_event_name: "PreToolUse",
13958
14127
  cwd,
13959
14128
  session_id: sessionId,
13960
- thread_id: "thread-ralplan-pretool-artifact",
13961
- tool_name: "Write",
13962
- tool_input: { file_path: ".omx/plans/prd-issue-2603.md" },
14129
+ thread_id: "thread-autopilot-ralplan-no-canonical",
14130
+ tool_name: "Edit",
14131
+ tool_input: { file_path: "src/runtime.ts" },
13963
14132
  },
13964
14133
  { cwd },
13965
14134
  );
@@ -13971,65 +14140,74 @@ exit 0
13971
14140
  }
13972
14141
  });
13973
14142
 
13974
- it("blocks bash implementation writes while ralplan is active without execution handoff", async () => {
13975
- const cwd = await mkdtemp(join(tmpdir(), "omx-native-hook-ralplan-pretool-bash-block-"));
14143
+ it("allows implementation writes when terminal Autopilot run-state shadows stale supervised ralplan state", async () => {
14144
+ const cwd = await mkdtemp(join(tmpdir(), "omx-native-hook-autopilot-ralplan-terminal-pretool-"));
13976
14145
  try {
13977
14146
  const stateDir = join(cwd, ".omx", "state");
13978
- const sessionId = "sess-ralplan-pretool-bash-block";
14147
+ const sessionId = "sess-autopilot-ralplan-terminal-pretool";
13979
14148
  await mkdir(join(stateDir, "sessions", sessionId), { recursive: true });
13980
14149
  await writeJson(join(stateDir, "session.json"), { session_id: sessionId });
13981
14150
  await writeJson(join(stateDir, "sessions", sessionId, "skill-active-state.json"), {
13982
14151
  active: true,
13983
- skill: "ralplan",
13984
- phase: "planning",
14152
+ skill: "autopilot",
14153
+ phase: "ralplan",
13985
14154
  session_id: sessionId,
13986
- active_skills: [{ skill: "ralplan", phase: "planning", active: true, session_id: sessionId }],
14155
+ active_skills: [{ skill: "autopilot", phase: "ralplan", active: true, session_id: sessionId }],
13987
14156
  });
13988
- await writeJson(join(stateDir, "sessions", sessionId, "ralplan-state.json"), {
14157
+ await writeJson(join(stateDir, "sessions", sessionId, "autopilot-state.json"), {
13989
14158
  active: true,
13990
- mode: "ralplan",
13991
- current_phase: "planning",
14159
+ mode: "autopilot",
14160
+ current_phase: "ralplan",
13992
14161
  session_id: sessionId,
13993
14162
  });
14163
+ await writeJson(join(stateDir, "sessions", sessionId, "run-state.json"), {
14164
+ version: 1,
14165
+ active: false,
14166
+ mode: "autopilot",
14167
+ outcome: "finish",
14168
+ lifecycle_outcome: "finished",
14169
+ current_phase: "complete",
14170
+ completed_at: "2026-05-30T00:00:00.000Z",
14171
+ updated_at: "2026-05-30T00:00:00.000Z",
14172
+ });
13994
14173
 
13995
14174
  const result = await dispatchCodexNativeHook(
13996
14175
  {
13997
14176
  hook_event_name: "PreToolUse",
13998
14177
  cwd,
13999
14178
  session_id: sessionId,
14000
- thread_id: "thread-ralplan-pretool-bash-block",
14001
- tool_name: "Bash",
14002
- tool_input: { command: "cat <<'EOF' > src/runtime.ts\nimplementation\nEOF" },
14179
+ thread_id: "thread-autopilot-ralplan-terminal-pretool",
14180
+ tool_name: "Edit",
14181
+ tool_input: { file_path: "src/runtime.ts" },
14003
14182
  },
14004
14183
  { cwd },
14005
14184
  );
14006
14185
 
14007
14186
  assert.equal(result.omxEventName, "pre-tool-use");
14008
- assert.equal(result.outputJson?.decision, "block");
14009
- assert.match(String(result.outputJson?.reason ?? ""), /Ralplan is active .*implementation\/write tools are blocked/i);
14187
+ assert.equal(result.outputJson, null);
14010
14188
  } finally {
14011
14189
  await rm(cwd, { recursive: true, force: true });
14012
14190
  }
14013
14191
  });
14014
14192
 
14015
- it("allows bash planning artifact writes while ralplan is active without execution handoff", async () => {
14016
- const cwd = await mkdtemp(join(tmpdir(), "omx-native-hook-ralplan-pretool-bash-artifact-"));
14193
+ it("blocks bash implementation writes while Autopilot is supervising ralplan without handoff", async () => {
14194
+ const cwd = await mkdtemp(join(tmpdir(), "omx-native-hook-autopilot-ralplan-pretool-bash-block-"));
14017
14195
  try {
14018
14196
  const stateDir = join(cwd, ".omx", "state");
14019
- const sessionId = "sess-ralplan-pretool-bash-artifact";
14197
+ const sessionId = "sess-autopilot-ralplan-pretool-bash-block";
14020
14198
  await mkdir(join(stateDir, "sessions", sessionId), { recursive: true });
14021
14199
  await writeJson(join(stateDir, "session.json"), { session_id: sessionId });
14022
14200
  await writeJson(join(stateDir, "sessions", sessionId, "skill-active-state.json"), {
14023
14201
  active: true,
14024
- skill: "ralplan",
14025
- phase: "planning",
14202
+ skill: "autopilot",
14203
+ phase: "ralplan",
14026
14204
  session_id: sessionId,
14027
- active_skills: [{ skill: "ralplan", phase: "planning", active: true, session_id: sessionId }],
14205
+ active_skills: [{ skill: "autopilot", phase: "ralplan", active: true, session_id: sessionId }],
14028
14206
  });
14029
- await writeJson(join(stateDir, "sessions", sessionId, "ralplan-state.json"), {
14207
+ await writeJson(join(stateDir, "sessions", sessionId, "autopilot-state.json"), {
14030
14208
  active: true,
14031
- mode: "ralplan",
14032
- current_phase: "planning",
14209
+ mode: "autopilot",
14210
+ current_phase: "ralplan",
14033
14211
  session_id: sessionId,
14034
14212
  });
14035
14213
 
@@ -14038,47 +14216,48 @@ exit 0
14038
14216
  hook_event_name: "PreToolUse",
14039
14217
  cwd,
14040
14218
  session_id: sessionId,
14041
- thread_id: "thread-ralplan-pretool-bash-artifact",
14219
+ thread_id: "thread-autopilot-ralplan-pretool-bash-block",
14042
14220
  tool_name: "Bash",
14043
- tool_input: { command: "cat <<'EOF' > .omx/plans/prd-issue-2603.md\nplanning\nEOF" },
14221
+ tool_input: { command: "cat <<'EOF' > src/runtime.ts\nimplementation\nEOF" },
14044
14222
  },
14045
14223
  { cwd },
14046
14224
  );
14047
14225
 
14048
14226
  assert.equal(result.omxEventName, "pre-tool-use");
14049
- assert.equal(result.outputJson, null);
14227
+ assert.equal(result.outputJson?.decision, "block");
14228
+ assert.match(String(result.outputJson?.reason ?? ""), /(?:Ralplan|Autopilot planning) is active .*implementation\/write tools are blocked/i);
14050
14229
  } finally {
14051
14230
  await rm(cwd, { recursive: true, force: true });
14052
14231
  }
14053
14232
  });
14054
14233
 
14055
- it("allows implementation writes when an explicit execution handoff is active", async () => {
14056
- const cwd = await mkdtemp(join(tmpdir(), "omx-native-hook-ralplan-pretool-handoff-"));
14234
+ it("blocks implementation writes when ralplan and Autopilot ralplan are both active", async () => {
14235
+ const cwd = await mkdtemp(join(tmpdir(), "omx-native-hook-ralplan-autopilot-mixed-planning-"));
14057
14236
  try {
14058
14237
  const stateDir = join(cwd, ".omx", "state");
14059
- const sessionId = "sess-ralplan-pretool-handoff";
14238
+ const sessionId = "sess-ralplan-autopilot-mixed-planning";
14060
14239
  await mkdir(join(stateDir, "sessions", sessionId), { recursive: true });
14061
14240
  await writeJson(join(stateDir, "session.json"), { session_id: sessionId });
14062
14241
  await writeJson(join(stateDir, "sessions", sessionId, "skill-active-state.json"), {
14063
14242
  active: true,
14064
- skill: "ultragoal",
14065
- phase: "planning",
14243
+ skill: "autopilot",
14244
+ phase: "ralplan",
14066
14245
  session_id: sessionId,
14067
14246
  active_skills: [
14068
14247
  { skill: "ralplan", phase: "planning", active: true, session_id: sessionId },
14069
- { skill: "ultragoal", phase: "planning", active: true, session_id: sessionId },
14248
+ { skill: "autopilot", phase: "ralplan", active: true, session_id: sessionId },
14070
14249
  ],
14071
14250
  });
14072
14251
  await writeJson(join(stateDir, "sessions", sessionId, "ralplan-state.json"), {
14073
14252
  active: true,
14074
14253
  mode: "ralplan",
14075
- current_phase: "complete",
14254
+ current_phase: "planning",
14076
14255
  session_id: sessionId,
14077
14256
  });
14078
- await writeJson(join(stateDir, "sessions", sessionId, "ultragoal-state.json"), {
14257
+ await writeJson(join(stateDir, "sessions", sessionId, "autopilot-state.json"), {
14079
14258
  active: true,
14080
- mode: "ultragoal",
14081
- current_phase: "planning",
14259
+ mode: "autopilot",
14260
+ current_phase: "ralplan",
14082
14261
  session_id: sessionId,
14083
14262
  });
14084
14263
 
@@ -14087,7 +14266,7 @@ exit 0
14087
14266
  hook_event_name: "PreToolUse",
14088
14267
  cwd,
14089
14268
  session_id: sessionId,
14090
- thread_id: "thread-ralplan-pretool-handoff",
14269
+ thread_id: "thread-ralplan-autopilot-mixed-planning",
14091
14270
  tool_name: "Edit",
14092
14271
  tool_input: { file_path: "src/runtime.ts" },
14093
14272
  },
@@ -14095,19 +14274,614 @@ exit 0
14095
14274
  );
14096
14275
 
14097
14276
  assert.equal(result.omxEventName, "pre-tool-use");
14098
- assert.equal(result.outputJson, null);
14277
+ assert.equal(result.outputJson?.decision, "block");
14278
+ assert.match(String(result.outputJson?.reason ?? ""), /(?:Ralplan|Autopilot planning) is active .*implementation\/write tools are blocked/i);
14099
14279
  } finally {
14100
14280
  await rm(cwd, { recursive: true, force: true });
14101
14281
  }
14102
14282
  });
14103
14283
 
14104
- it("does not block Stop from root team state without team_name when no session is known", async () => {
14105
- const cwd = await mkdtemp(join(tmpdir(), "omx-native-hook-stop-root-team-no-session-no-name-"));
14284
+ it("blocks implementation writes while Autopilot is supervising replan without handoff", async () => {
14285
+ const cwd = await mkdtemp(join(tmpdir(), "omx-native-hook-autopilot-replan-pretool-block-"));
14106
14286
  try {
14107
14287
  const stateDir = join(cwd, ".omx", "state");
14108
- await mkdir(stateDir, { recursive: true });
14109
- await writeJson(join(stateDir, "team-state.json"), {
14110
- active: true,
14288
+ const sessionId = "sess-autopilot-replan-pretool-block";
14289
+ await mkdir(join(stateDir, "sessions", sessionId), { recursive: true });
14290
+ await writeJson(join(stateDir, "session.json"), { session_id: sessionId });
14291
+ await writeJson(join(stateDir, "sessions", sessionId, "skill-active-state.json"), {
14292
+ active: true,
14293
+ skill: "autopilot",
14294
+ phase: "replan",
14295
+ session_id: sessionId,
14296
+ active_skills: [{ skill: "autopilot", phase: "replan", active: true, session_id: sessionId }],
14297
+ });
14298
+ await writeJson(join(stateDir, "sessions", sessionId, "autopilot-state.json"), {
14299
+ active: true,
14300
+ mode: "autopilot",
14301
+ current_phase: "replan",
14302
+ session_id: sessionId,
14303
+ });
14304
+
14305
+ const result = await dispatchCodexNativeHook(
14306
+ {
14307
+ hook_event_name: "PreToolUse",
14308
+ cwd,
14309
+ session_id: sessionId,
14310
+ thread_id: "thread-autopilot-replan-pretool-block",
14311
+ tool_name: "Edit",
14312
+ tool_input: { file_path: "src/runtime.ts" },
14313
+ },
14314
+ { cwd },
14315
+ );
14316
+
14317
+ assert.equal(result.omxEventName, "pre-tool-use");
14318
+ assert.equal(result.outputJson?.decision, "block");
14319
+ assert.match(String(result.outputJson?.reason ?? ""), /(?:Ralplan|Autopilot planning) is active .*implementation\/write tools are blocked/i);
14320
+ } finally {
14321
+ await rm(cwd, { recursive: true, force: true });
14322
+ }
14323
+ });
14324
+
14325
+ it("blocks implementation writes when native Codex id maps to OMX Autopilot ralplan state", async () => {
14326
+ const cwd = await mkdtemp(join(tmpdir(), "omx-native-hook-autopilot-ralplan-native-map-block-"));
14327
+ try {
14328
+ const stateDir = join(cwd, ".omx", "state");
14329
+ const sessionId = "sess-autopilot-ralplan-native-map-block";
14330
+ const nativeSessionId = "019e-autopilot-ralplan-native";
14331
+ await writeNativeMappedSessionState(cwd, stateDir, sessionId, nativeSessionId);
14332
+ await writeSessionSkillActiveState(stateDir, sessionId, "autopilot", "ralplan");
14333
+ await writeJson(join(stateDir, "sessions", sessionId, "autopilot-state.json"), {
14334
+ active: true,
14335
+ mode: "autopilot",
14336
+ current_phase: "ralplan",
14337
+ session_id: sessionId,
14338
+ });
14339
+
14340
+ const result = await dispatchCodexNativeHook(
14341
+ {
14342
+ hook_event_name: "PreToolUse",
14343
+ cwd,
14344
+ session_id: nativeSessionId,
14345
+ thread_id: "thread-autopilot-ralplan-native-map-block",
14346
+ tool_name: "apply_patch",
14347
+ tool_input: { file_path: "src/runtime.ts" },
14348
+ },
14349
+ { cwd },
14350
+ );
14351
+
14352
+ assert.equal(result.omxEventName, "pre-tool-use");
14353
+ assert.equal(result.outputJson?.decision, "block");
14354
+ assert.match(String(result.outputJson?.reason ?? ""), /(?:Ralplan|Autopilot planning) is active .*implementation\/write tools are blocked/i);
14355
+ } finally {
14356
+ await rm(cwd, { recursive: true, force: true });
14357
+ }
14358
+ });
14359
+
14360
+ it("blocks bash implementation writes when native Codex id maps to OMX Autopilot ralplan state", async () => {
14361
+ const cwd = await mkdtemp(join(tmpdir(), "omx-native-hook-autopilot-ralplan-native-map-bash-"));
14362
+ try {
14363
+ const stateDir = join(cwd, ".omx", "state");
14364
+ const sessionId = "sess-autopilot-ralplan-native-map-bash";
14365
+ const nativeSessionId = "019e-autopilot-ralplan-native-bash";
14366
+ await writeNativeMappedSessionState(cwd, stateDir, sessionId, nativeSessionId);
14367
+ await writeSessionSkillActiveState(stateDir, sessionId, "autopilot", "ralplan");
14368
+ await writeJson(join(stateDir, "sessions", sessionId, "autopilot-state.json"), {
14369
+ active: true,
14370
+ mode: "autopilot",
14371
+ current_phase: "ralplan",
14372
+ session_id: sessionId,
14373
+ });
14374
+
14375
+ const result = await dispatchCodexNativeHook(
14376
+ {
14377
+ hook_event_name: "PreToolUse",
14378
+ cwd,
14379
+ session_id: nativeSessionId,
14380
+ thread_id: "thread-autopilot-ralplan-native-map-bash",
14381
+ tool_name: "Bash",
14382
+ tool_input: { command: "cat <<'EOF' > src/runtime.ts\nimplementation\nEOF" },
14383
+ },
14384
+ { cwd },
14385
+ );
14386
+
14387
+ assert.equal(result.omxEventName, "pre-tool-use");
14388
+ assert.equal(result.outputJson?.decision, "block");
14389
+ assert.match(String(result.outputJson?.reason ?? ""), /(?:Ralplan|Autopilot planning) is active .*implementation\/write tools are blocked/i);
14390
+ } finally {
14391
+ await rm(cwd, { recursive: true, force: true });
14392
+ }
14393
+ });
14394
+
14395
+ it("blocks standalone ralplan writes when native Codex id maps to OMX session state", async () => {
14396
+ const cwd = await mkdtemp(join(tmpdir(), "omx-native-hook-ralplan-native-map-block-"));
14397
+ try {
14398
+ const stateDir = join(cwd, ".omx", "state");
14399
+ const sessionId = "sess-ralplan-native-map-block";
14400
+ const nativeSessionId = "019e-ralplan-native-map";
14401
+ await writeNativeMappedSessionState(cwd, stateDir, sessionId, nativeSessionId);
14402
+ await writeSessionSkillActiveState(stateDir, sessionId, "ralplan", "planning");
14403
+ await writeJson(join(stateDir, "sessions", sessionId, "ralplan-state.json"), {
14404
+ active: true,
14405
+ mode: "ralplan",
14406
+ current_phase: "planning",
14407
+ session_id: sessionId,
14408
+ });
14409
+
14410
+ const result = await dispatchCodexNativeHook(
14411
+ {
14412
+ hook_event_name: "PreToolUse",
14413
+ cwd,
14414
+ session_id: nativeSessionId,
14415
+ thread_id: "thread-ralplan-native-map-block",
14416
+ tool_name: "Edit",
14417
+ tool_input: { file_path: "src/runtime.ts" },
14418
+ },
14419
+ { cwd },
14420
+ );
14421
+
14422
+ assert.equal(result.omxEventName, "pre-tool-use");
14423
+ assert.equal(result.outputJson?.decision, "block");
14424
+ assert.match(String(result.outputJson?.reason ?? ""), /(?:Ralplan|Autopilot planning) is active .*implementation\/write tools are blocked/i);
14425
+ } finally {
14426
+ await rm(cwd, { recursive: true, force: true });
14427
+ }
14428
+ });
14429
+
14430
+ it("blocks deep-interview writes when native Codex id maps to OMX session state", async () => {
14431
+ const cwd = await mkdtemp(join(tmpdir(), "omx-native-hook-deep-interview-native-map-block-"));
14432
+ try {
14433
+ const stateDir = join(cwd, ".omx", "state");
14434
+ const sessionId = "sess-deep-interview-native-map-block";
14435
+ const nativeSessionId = "019e-deep-interview-native-map";
14436
+ await writeNativeMappedSessionState(cwd, stateDir, sessionId, nativeSessionId);
14437
+ await writeSessionSkillActiveState(stateDir, sessionId, "deep-interview", "interview");
14438
+ await writeJson(join(stateDir, "sessions", sessionId, "deep-interview-state.json"), {
14439
+ active: true,
14440
+ mode: "deep-interview",
14441
+ current_phase: "interview",
14442
+ session_id: sessionId,
14443
+ });
14444
+
14445
+ const result = await dispatchCodexNativeHook(
14446
+ {
14447
+ hook_event_name: "PreToolUse",
14448
+ cwd,
14449
+ session_id: nativeSessionId,
14450
+ thread_id: "thread-deep-interview-native-map-block",
14451
+ tool_name: "Edit",
14452
+ tool_input: { file_path: "src/runtime.ts" },
14453
+ },
14454
+ { cwd },
14455
+ );
14456
+
14457
+ assert.equal(result.omxEventName, "pre-tool-use");
14458
+ assert.equal(result.outputJson?.decision, "block");
14459
+ assert.match(String(result.outputJson?.reason ?? ""), /Deep-interview is active .*implementation\/write tools are blocked/i);
14460
+ } finally {
14461
+ await rm(cwd, { recursive: true, force: true });
14462
+ }
14463
+ });
14464
+
14465
+ it("allows mapped ralplan planning artifact writes without execution handoff", async () => {
14466
+ const cwd = await mkdtemp(join(tmpdir(), "omx-native-hook-ralplan-native-map-artifact-"));
14467
+ try {
14468
+ const stateDir = join(cwd, ".omx", "state");
14469
+ const sessionId = "sess-ralplan-native-map-artifact";
14470
+ const nativeSessionId = "019e-ralplan-native-map-artifact";
14471
+ await writeNativeMappedSessionState(cwd, stateDir, sessionId, nativeSessionId);
14472
+ await writeSessionSkillActiveState(stateDir, sessionId, "ralplan", "planning");
14473
+ await writeJson(join(stateDir, "sessions", sessionId, "ralplan-state.json"), {
14474
+ active: true,
14475
+ mode: "ralplan",
14476
+ current_phase: "planning",
14477
+ session_id: sessionId,
14478
+ });
14479
+
14480
+ const result = await dispatchCodexNativeHook(
14481
+ {
14482
+ hook_event_name: "PreToolUse",
14483
+ cwd,
14484
+ session_id: nativeSessionId,
14485
+ thread_id: "thread-ralplan-native-map-artifact",
14486
+ tool_name: "Bash",
14487
+ tool_input: { command: "cat <<'EOF' > .omx/plans/prd-native-map.md\nplanning\nEOF" },
14488
+ },
14489
+ { cwd },
14490
+ );
14491
+
14492
+ assert.equal(result.omxEventName, "pre-tool-use");
14493
+ assert.equal(result.outputJson, null);
14494
+ } finally {
14495
+ await rm(cwd, { recursive: true, force: true });
14496
+ }
14497
+ });
14498
+
14499
+ it("allows mapped implementation writes when explicit execution handoff is active", async () => {
14500
+ const cwd = await mkdtemp(join(tmpdir(), "omx-native-hook-ralplan-native-map-handoff-"));
14501
+ try {
14502
+ const stateDir = join(cwd, ".omx", "state");
14503
+ const sessionId = "sess-ralplan-native-map-handoff";
14504
+ const nativeSessionId = "019e-ralplan-native-map-handoff";
14505
+ await writeNativeMappedSessionState(cwd, stateDir, sessionId, nativeSessionId);
14506
+ await writeJson(join(stateDir, "sessions", sessionId, "skill-active-state.json"), {
14507
+ active: true,
14508
+ skill: "ultragoal",
14509
+ phase: "planning",
14510
+ session_id: sessionId,
14511
+ active_skills: [
14512
+ { skill: "ralplan", phase: "planning", active: true, session_id: sessionId },
14513
+ { skill: "ultragoal", phase: "planning", active: true, session_id: sessionId },
14514
+ ],
14515
+ });
14516
+ await writeJson(join(stateDir, "sessions", sessionId, "ralplan-state.json"), {
14517
+ active: true,
14518
+ mode: "ralplan",
14519
+ current_phase: "complete",
14520
+ session_id: sessionId,
14521
+ });
14522
+ await writeJson(join(stateDir, "sessions", sessionId, "ultragoal-state.json"), {
14523
+ active: true,
14524
+ mode: "ultragoal",
14525
+ current_phase: "planning",
14526
+ session_id: sessionId,
14527
+ });
14528
+
14529
+ const result = await dispatchCodexNativeHook(
14530
+ {
14531
+ hook_event_name: "PreToolUse",
14532
+ cwd,
14533
+ session_id: nativeSessionId,
14534
+ thread_id: "thread-ralplan-native-map-handoff",
14535
+ tool_name: "Edit",
14536
+ tool_input: { file_path: "src/runtime.ts" },
14537
+ },
14538
+ { cwd },
14539
+ );
14540
+
14541
+ assert.equal(result.omxEventName, "pre-tool-use");
14542
+ assert.equal(result.outputJson, null);
14543
+ } finally {
14544
+ await rm(cwd, { recursive: true, force: true });
14545
+ }
14546
+ });
14547
+
14548
+ it("allows mapped implementation writes when terminal Autopilot run-state shadows stale supervised ralplan state", async () => {
14549
+ const cwd = await mkdtemp(join(tmpdir(), "omx-native-hook-autopilot-ralplan-native-map-terminal-"));
14550
+ try {
14551
+ const stateDir = join(cwd, ".omx", "state");
14552
+ const sessionId = "sess-autopilot-ralplan-native-map-terminal";
14553
+ const nativeSessionId = "019e-autopilot-ralplan-native-terminal";
14554
+ await writeNativeMappedSessionState(cwd, stateDir, sessionId, nativeSessionId);
14555
+ await writeSessionSkillActiveState(stateDir, sessionId, "autopilot", "ralplan");
14556
+ await writeJson(join(stateDir, "sessions", sessionId, "autopilot-state.json"), {
14557
+ active: true,
14558
+ mode: "autopilot",
14559
+ current_phase: "ralplan",
14560
+ session_id: sessionId,
14561
+ });
14562
+ await writeJson(join(stateDir, "sessions", sessionId, "run-state.json"), {
14563
+ version: 1,
14564
+ active: false,
14565
+ mode: "autopilot",
14566
+ outcome: "finish",
14567
+ lifecycle_outcome: "finished",
14568
+ current_phase: "complete",
14569
+ completed_at: "2026-05-30T00:00:00.000Z",
14570
+ updated_at: "2026-05-30T00:00:00.000Z",
14571
+ });
14572
+
14573
+ const result = await dispatchCodexNativeHook(
14574
+ {
14575
+ hook_event_name: "PreToolUse",
14576
+ cwd,
14577
+ session_id: nativeSessionId,
14578
+ thread_id: "thread-autopilot-ralplan-native-map-terminal",
14579
+ tool_name: "Edit",
14580
+ tool_input: { file_path: "src/runtime.ts" },
14581
+ },
14582
+ { cwd },
14583
+ );
14584
+
14585
+ assert.equal(result.omxEventName, "pre-tool-use");
14586
+ assert.equal(result.outputJson, null);
14587
+ } finally {
14588
+ await rm(cwd, { recursive: true, force: true });
14589
+ }
14590
+ });
14591
+
14592
+ it("does not block unrelated native Codex ids when current OMX session mapping does not match", async () => {
14593
+ const cwd = await mkdtemp(join(tmpdir(), "omx-native-hook-ralplan-native-map-unrelated-"));
14594
+ try {
14595
+ const stateDir = join(cwd, ".omx", "state");
14596
+ const sessionId = "sess-ralplan-native-map-owner";
14597
+ const ownerNativeSessionId = "019e-ralplan-native-owner";
14598
+ await writeNativeMappedSessionState(cwd, stateDir, sessionId, ownerNativeSessionId);
14599
+ await writeSessionSkillActiveState(stateDir, sessionId, "ralplan", "planning");
14600
+ await writeJson(join(stateDir, "sessions", sessionId, "ralplan-state.json"), {
14601
+ active: true,
14602
+ mode: "ralplan",
14603
+ current_phase: "planning",
14604
+ session_id: sessionId,
14605
+ });
14606
+
14607
+ const result = await dispatchCodexNativeHook(
14608
+ {
14609
+ hook_event_name: "PreToolUse",
14610
+ cwd,
14611
+ session_id: "019e-unrelated-native-session",
14612
+ thread_id: "thread-ralplan-native-map-unrelated",
14613
+ tool_name: "Edit",
14614
+ tool_input: { file_path: "src/runtime.ts" },
14615
+ },
14616
+ { cwd },
14617
+ );
14618
+
14619
+ assert.equal(result.omxEventName, "pre-tool-use");
14620
+ assert.equal(result.outputJson, null);
14621
+ } finally {
14622
+ await rm(cwd, { recursive: true, force: true });
14623
+ }
14624
+ });
14625
+
14626
+ it("blocks mapped Autopilot ralplan writes from the authoritative team state root", async () => {
14627
+ const cwd = await mkdtemp(join(tmpdir(), "omx-native-hook-autopilot-ralplan-team-root-"));
14628
+ const teamStateRoot = await mkdtemp(join(tmpdir(), "omx-native-hook-team-root-"));
14629
+ const previousTeamStateRoot = process.env.OMX_TEAM_STATE_ROOT;
14630
+ try {
14631
+ process.env.OMX_TEAM_STATE_ROOT = teamStateRoot;
14632
+ const stateDir = teamStateRoot;
14633
+ const sessionId = "sess-autopilot-ralplan-team-root";
14634
+ const nativeSessionId = "019e-autopilot-ralplan-team-root";
14635
+ await writeNativeMappedSessionState(cwd, stateDir, sessionId, nativeSessionId);
14636
+ await writeSessionSkillActiveState(stateDir, sessionId, "autopilot", "ralplan");
14637
+ await writeJson(join(stateDir, "sessions", sessionId, "autopilot-state.json"), {
14638
+ active: true,
14639
+ mode: "autopilot",
14640
+ current_phase: "ralplan",
14641
+ session_id: sessionId,
14642
+ });
14643
+
14644
+ const result = await dispatchCodexNativeHook(
14645
+ {
14646
+ hook_event_name: "PreToolUse",
14647
+ cwd,
14648
+ session_id: nativeSessionId,
14649
+ thread_id: "thread-autopilot-ralplan-team-root",
14650
+ tool_name: "Edit",
14651
+ tool_input: { file_path: "src/runtime.ts" },
14652
+ },
14653
+ { cwd },
14654
+ );
14655
+
14656
+ assert.equal(result.omxEventName, "pre-tool-use");
14657
+ assert.equal(result.outputJson?.decision, "block");
14658
+ assert.match(String(result.outputJson?.reason ?? ""), /(?:Ralplan|Autopilot planning) is active .*implementation\/write tools are blocked/i);
14659
+ assert.equal(existsSync(join(cwd, ".omx", "state", "session.json")), false);
14660
+ } finally {
14661
+ if (typeof previousTeamStateRoot === "string") process.env.OMX_TEAM_STATE_ROOT = previousTeamStateRoot;
14662
+ else delete process.env.OMX_TEAM_STATE_ROOT;
14663
+ await rm(cwd, { recursive: true, force: true });
14664
+ await rm(teamStateRoot, { recursive: true, force: true });
14665
+ }
14666
+ });
14667
+
14668
+ it("does not block unrelated native Codex ids from the authoritative team state root", async () => {
14669
+ const cwd = await mkdtemp(join(tmpdir(), "omx-native-hook-ralplan-team-root-unrelated-"));
14670
+ const teamStateRoot = await mkdtemp(join(tmpdir(), "omx-native-hook-team-root-unrelated-"));
14671
+ const previousTeamStateRoot = process.env.OMX_TEAM_STATE_ROOT;
14672
+ try {
14673
+ process.env.OMX_TEAM_STATE_ROOT = teamStateRoot;
14674
+ const stateDir = teamStateRoot;
14675
+ const sessionId = "sess-ralplan-team-root-owner";
14676
+ const nativeSessionId = "019e-ralplan-team-root-owner";
14677
+ await writeNativeMappedSessionState(cwd, stateDir, sessionId, nativeSessionId);
14678
+ await writeSessionSkillActiveState(stateDir, sessionId, "ralplan", "planning");
14679
+ await writeJson(join(stateDir, "sessions", sessionId, "ralplan-state.json"), {
14680
+ active: true,
14681
+ mode: "ralplan",
14682
+ current_phase: "planning",
14683
+ session_id: sessionId,
14684
+ });
14685
+
14686
+ const result = await dispatchCodexNativeHook(
14687
+ {
14688
+ hook_event_name: "PreToolUse",
14689
+ cwd,
14690
+ session_id: "019e-unrelated-team-root-native",
14691
+ thread_id: "thread-ralplan-team-root-unrelated",
14692
+ tool_name: "Edit",
14693
+ tool_input: { file_path: "src/runtime.ts" },
14694
+ },
14695
+ { cwd },
14696
+ );
14697
+
14698
+ assert.equal(result.omxEventName, "pre-tool-use");
14699
+ assert.equal(result.outputJson, null);
14700
+ } finally {
14701
+ if (typeof previousTeamStateRoot === "string") process.env.OMX_TEAM_STATE_ROOT = previousTeamStateRoot;
14702
+ else delete process.env.OMX_TEAM_STATE_ROOT;
14703
+ await rm(cwd, { recursive: true, force: true });
14704
+ await rm(teamStateRoot, { recursive: true, force: true });
14705
+ }
14706
+ });
14707
+
14708
+ it("allows ralplan planning artifact writes without execution handoff", async () => {
14709
+ const cwd = await mkdtemp(join(tmpdir(), "omx-native-hook-ralplan-pretool-artifact-"));
14710
+ try {
14711
+ const stateDir = join(cwd, ".omx", "state");
14712
+ const sessionId = "sess-ralplan-pretool-artifact";
14713
+ await mkdir(join(stateDir, "sessions", sessionId), { recursive: true });
14714
+ await writeJson(join(stateDir, "session.json"), { session_id: sessionId });
14715
+ await writeJson(join(stateDir, "sessions", sessionId, "skill-active-state.json"), {
14716
+ active: true,
14717
+ skill: "ralplan",
14718
+ phase: "planning",
14719
+ session_id: sessionId,
14720
+ active_skills: [{ skill: "ralplan", phase: "planning", active: true, session_id: sessionId }],
14721
+ });
14722
+ await writeJson(join(stateDir, "sessions", sessionId, "ralplan-state.json"), {
14723
+ active: true,
14724
+ mode: "ralplan",
14725
+ current_phase: "planning",
14726
+ session_id: sessionId,
14727
+ });
14728
+
14729
+ const result = await dispatchCodexNativeHook(
14730
+ {
14731
+ hook_event_name: "PreToolUse",
14732
+ cwd,
14733
+ session_id: sessionId,
14734
+ thread_id: "thread-ralplan-pretool-artifact",
14735
+ tool_name: "Write",
14736
+ tool_input: { file_path: ".omx/plans/prd-issue-2603.md" },
14737
+ },
14738
+ { cwd },
14739
+ );
14740
+
14741
+ assert.equal(result.omxEventName, "pre-tool-use");
14742
+ assert.equal(result.outputJson, null);
14743
+ } finally {
14744
+ await rm(cwd, { recursive: true, force: true });
14745
+ }
14746
+ });
14747
+
14748
+ it("blocks bash implementation writes while ralplan is active without execution handoff", async () => {
14749
+ const cwd = await mkdtemp(join(tmpdir(), "omx-native-hook-ralplan-pretool-bash-block-"));
14750
+ try {
14751
+ const stateDir = join(cwd, ".omx", "state");
14752
+ const sessionId = "sess-ralplan-pretool-bash-block";
14753
+ await mkdir(join(stateDir, "sessions", sessionId), { recursive: true });
14754
+ await writeJson(join(stateDir, "session.json"), { session_id: sessionId });
14755
+ await writeJson(join(stateDir, "sessions", sessionId, "skill-active-state.json"), {
14756
+ active: true,
14757
+ skill: "ralplan",
14758
+ phase: "planning",
14759
+ session_id: sessionId,
14760
+ active_skills: [{ skill: "ralplan", phase: "planning", active: true, session_id: sessionId }],
14761
+ });
14762
+ await writeJson(join(stateDir, "sessions", sessionId, "ralplan-state.json"), {
14763
+ active: true,
14764
+ mode: "ralplan",
14765
+ current_phase: "planning",
14766
+ session_id: sessionId,
14767
+ });
14768
+
14769
+ const result = await dispatchCodexNativeHook(
14770
+ {
14771
+ hook_event_name: "PreToolUse",
14772
+ cwd,
14773
+ session_id: sessionId,
14774
+ thread_id: "thread-ralplan-pretool-bash-block",
14775
+ tool_name: "Bash",
14776
+ tool_input: { command: "cat <<'EOF' > src/runtime.ts\nimplementation\nEOF" },
14777
+ },
14778
+ { cwd },
14779
+ );
14780
+
14781
+ assert.equal(result.omxEventName, "pre-tool-use");
14782
+ assert.equal(result.outputJson?.decision, "block");
14783
+ assert.match(String(result.outputJson?.reason ?? ""), /(?:Ralplan|Autopilot planning) is active .*implementation\/write tools are blocked/i);
14784
+ } finally {
14785
+ await rm(cwd, { recursive: true, force: true });
14786
+ }
14787
+ });
14788
+
14789
+ it("allows bash planning artifact writes while ralplan is active without execution handoff", async () => {
14790
+ const cwd = await mkdtemp(join(tmpdir(), "omx-native-hook-ralplan-pretool-bash-artifact-"));
14791
+ try {
14792
+ const stateDir = join(cwd, ".omx", "state");
14793
+ const sessionId = "sess-ralplan-pretool-bash-artifact";
14794
+ await mkdir(join(stateDir, "sessions", sessionId), { recursive: true });
14795
+ await writeJson(join(stateDir, "session.json"), { session_id: sessionId });
14796
+ await writeJson(join(stateDir, "sessions", sessionId, "skill-active-state.json"), {
14797
+ active: true,
14798
+ skill: "ralplan",
14799
+ phase: "planning",
14800
+ session_id: sessionId,
14801
+ active_skills: [{ skill: "ralplan", phase: "planning", active: true, session_id: sessionId }],
14802
+ });
14803
+ await writeJson(join(stateDir, "sessions", sessionId, "ralplan-state.json"), {
14804
+ active: true,
14805
+ mode: "ralplan",
14806
+ current_phase: "planning",
14807
+ session_id: sessionId,
14808
+ });
14809
+
14810
+ const result = await dispatchCodexNativeHook(
14811
+ {
14812
+ hook_event_name: "PreToolUse",
14813
+ cwd,
14814
+ session_id: sessionId,
14815
+ thread_id: "thread-ralplan-pretool-bash-artifact",
14816
+ tool_name: "Bash",
14817
+ tool_input: { command: "cat <<'EOF' > .omx/plans/prd-issue-2603.md\nplanning\nEOF" },
14818
+ },
14819
+ { cwd },
14820
+ );
14821
+
14822
+ assert.equal(result.omxEventName, "pre-tool-use");
14823
+ assert.equal(result.outputJson, null);
14824
+ } finally {
14825
+ await rm(cwd, { recursive: true, force: true });
14826
+ }
14827
+ });
14828
+
14829
+ it("allows implementation writes when an explicit execution handoff is active", async () => {
14830
+ const cwd = await mkdtemp(join(tmpdir(), "omx-native-hook-ralplan-pretool-handoff-"));
14831
+ try {
14832
+ const stateDir = join(cwd, ".omx", "state");
14833
+ const sessionId = "sess-ralplan-pretool-handoff";
14834
+ await mkdir(join(stateDir, "sessions", sessionId), { recursive: true });
14835
+ await writeJson(join(stateDir, "session.json"), { session_id: sessionId });
14836
+ await writeJson(join(stateDir, "sessions", sessionId, "skill-active-state.json"), {
14837
+ active: true,
14838
+ skill: "ultragoal",
14839
+ phase: "planning",
14840
+ session_id: sessionId,
14841
+ active_skills: [
14842
+ { skill: "ralplan", phase: "planning", active: true, session_id: sessionId },
14843
+ { skill: "ultragoal", phase: "planning", active: true, session_id: sessionId },
14844
+ ],
14845
+ });
14846
+ await writeJson(join(stateDir, "sessions", sessionId, "ralplan-state.json"), {
14847
+ active: true,
14848
+ mode: "ralplan",
14849
+ current_phase: "complete",
14850
+ session_id: sessionId,
14851
+ });
14852
+ await writeJson(join(stateDir, "sessions", sessionId, "ultragoal-state.json"), {
14853
+ active: true,
14854
+ mode: "ultragoal",
14855
+ current_phase: "planning",
14856
+ session_id: sessionId,
14857
+ });
14858
+
14859
+ const result = await dispatchCodexNativeHook(
14860
+ {
14861
+ hook_event_name: "PreToolUse",
14862
+ cwd,
14863
+ session_id: sessionId,
14864
+ thread_id: "thread-ralplan-pretool-handoff",
14865
+ tool_name: "Edit",
14866
+ tool_input: { file_path: "src/runtime.ts" },
14867
+ },
14868
+ { cwd },
14869
+ );
14870
+
14871
+ assert.equal(result.omxEventName, "pre-tool-use");
14872
+ assert.equal(result.outputJson, null);
14873
+ } finally {
14874
+ await rm(cwd, { recursive: true, force: true });
14875
+ }
14876
+ });
14877
+
14878
+ it("does not block Stop from root team state without team_name when no session is known", async () => {
14879
+ const cwd = await mkdtemp(join(tmpdir(), "omx-native-hook-stop-root-team-no-session-no-name-"));
14880
+ try {
14881
+ const stateDir = join(cwd, ".omx", "state");
14882
+ await mkdir(stateDir, { recursive: true });
14883
+ await writeJson(join(stateDir, "team-state.json"), {
14884
+ active: true,
14111
14885
  mode: "team",
14112
14886
  current_phase: "starting",
14113
14887
  });
@@ -14730,6 +15504,80 @@ describe("codex native hook triage integration", () => {
14730
15504
  }
14731
15505
  });
14732
15506
 
15507
+ it("omits Team handoff guidance from autopilot prompt context when Team mode is disabled", async () => {
15508
+ const cwd = await mkdtemp(join(tmpdir(), "omx-autopilot-observable-no-team-"));
15509
+ try {
15510
+ await mkdir(join(cwd, ".omx", "state"), { recursive: true });
15511
+ await writeJson(join(cwd, ".omx", "setup-scope.json"), {
15512
+ scope: "project",
15513
+ teamMode: "disabled",
15514
+ });
15515
+ await writeSessionStart(cwd, "sess-autopilot-observable-no-team");
15516
+
15517
+ const result = await dispatchCodexNativeHook(
15518
+ {
15519
+ hook_event_name: "UserPromptSubmit",
15520
+ cwd,
15521
+ session_id: "sess-autopilot-observable-no-team",
15522
+ thread_id: "thread-autopilot-observable-no-team",
15523
+ turn_id: "turn-autopilot-observable-no-team",
15524
+ prompt: "$autopilot implement issue #2430",
15525
+ },
15526
+ { cwd },
15527
+ );
15528
+
15529
+ assert.equal(result.skillState?.skill, "autopilot");
15530
+ const additionalContext = String(
15531
+ (result.outputJson as { hookSpecificOutput?: { additionalContext?: string } })?.hookSpecificOutput?.additionalContext ?? "",
15532
+ );
15533
+ assert.match(additionalContext, /detected workflow keyword "\$autopilot" -> autopilot/);
15534
+ assert.match(additionalContext, /\$deep-interview -> \$ralplan -> \$ultragoal -> \$code-review -> \$ultraqa/);
15535
+ assert.doesNotMatch(additionalContext, /\$team/);
15536
+ assert.equal(existsSync(join(cwd, ".omx", "state", "team-state.json")), false);
15537
+ } finally {
15538
+ await rm(cwd, { recursive: true, force: true });
15539
+ }
15540
+ });
15541
+
15542
+ it("ignores disabled $team before outside-tmux Team blocking so later workflows can activate", async () => {
15543
+ const cwd = await mkdtemp(join(tmpdir(), "omx-native-hook-disabled-team-primary-"));
15544
+ try {
15545
+ await mkdir(join(cwd, ".omx", "state"), { recursive: true });
15546
+ await writeJson(join(cwd, ".omx", "setup-scope.json"), {
15547
+ scope: "project",
15548
+ teamMode: "disabled",
15549
+ });
15550
+ await writeSessionStart(cwd, "sess-disabled-team-primary");
15551
+
15552
+ const result = await dispatchCodexNativeHook(
15553
+ {
15554
+ hook_event_name: "UserPromptSubmit",
15555
+ cwd,
15556
+ session_id: "sess-disabled-team-primary",
15557
+ thread_id: "thread-disabled-team-primary",
15558
+ turn_id: "turn-disabled-team-primary",
15559
+ prompt: "$team $ralph fix this",
15560
+ },
15561
+ { cwd },
15562
+ );
15563
+
15564
+ assert.equal(result.skillState?.skill, "ralph");
15565
+ assert.equal(result.skillState?.transition_error, undefined);
15566
+ assert.equal(existsSync(join(cwd, ".omx", "state", "team-state.json")), false);
15567
+ assert.equal(
15568
+ existsSync(join(cwd, ".omx", "state", "sessions", "sess-disabled-team-primary", "ralph-state.json")),
15569
+ true,
15570
+ );
15571
+ const additionalContext = String(
15572
+ (result.outputJson as { hookSpecificOutput?: { additionalContext?: string } })?.hookSpecificOutput?.additionalContext ?? "",
15573
+ );
15574
+ assert.match(additionalContext, /detected workflow keyword "\$ralph" -> ralph/);
15575
+ assert.doesNotMatch(additionalContext, /Codex App\/native outside-tmux sessions cannot activate/);
15576
+ } finally {
15577
+ await rm(cwd, { recursive: true, force: true });
15578
+ }
15579
+ });
15580
+
14733
15581
  it("makes bare autopilot command activation observable in state and prompt guidance", async () => {
14734
15582
  const cwd = await mkdtemp(join(tmpdir(), "omx-autopilot-bare-observable-"));
14735
15583
  try {