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
@@ -46,6 +46,23 @@ async function writeJson(path, value) {
46
46
  await mkdir(dirname(path), { recursive: true }).catch(() => { });
47
47
  await writeFile(path, JSON.stringify(value, null, 2));
48
48
  }
49
+ async function writeNativeMappedSessionState(cwd, stateDir, sessionId, nativeSessionId) {
50
+ await mkdir(join(stateDir, "sessions", sessionId), { recursive: true });
51
+ await writeJson(join(stateDir, "session.json"), {
52
+ session_id: sessionId,
53
+ native_session_id: nativeSessionId,
54
+ cwd,
55
+ });
56
+ }
57
+ async function writeSessionSkillActiveState(stateDir, sessionId, skill, phase) {
58
+ await writeJson(join(stateDir, "sessions", sessionId, "skill-active-state.json"), {
59
+ active: true,
60
+ skill,
61
+ phase,
62
+ session_id: sessionId,
63
+ active_skills: [{ skill, phase, active: true, session_id: sessionId }],
64
+ });
65
+ }
49
66
  async function setTeamPaneIds(cwd, teamName, paneIds) {
50
67
  for (const fileName of ["config.json", "manifest.v2.json"]) {
51
68
  const filePath = join(cwd, ".omx", "state", "team", teamName, fileName);
@@ -1280,12 +1297,16 @@ describe("codex native hook dispatch", () => {
1280
1297
  }, {
1281
1298
  cwd,
1282
1299
  reconcileHudForPromptSubmitFn: async (hookCwd, deps = {}) => {
1283
- reconcileCall = { cwd: hookCwd, sessionId: deps.sessionId };
1300
+ reconcileCall = { cwd: hookCwd, sessionId: deps.sessionId, sessionIds: deps.sessionIds };
1284
1301
  return { status: "recreated", paneId: "%9", desiredHeight: 3, duplicateCount: 0 };
1285
1302
  },
1286
1303
  });
1287
1304
  assert.equal(promptResult.omxEventName, "keyword-detector");
1288
- assert.deepEqual(reconcileCall, { cwd, sessionId: ownerSessionId });
1305
+ assert.deepEqual(reconcileCall, {
1306
+ cwd,
1307
+ sessionId: ownerSessionId,
1308
+ sessionIds: [ownerSessionId, nativeSessionId],
1309
+ });
1289
1310
  }
1290
1311
  finally {
1291
1312
  await rm(cwd, { recursive: true, force: true });
@@ -1315,12 +1336,16 @@ describe("codex native hook dispatch", () => {
1315
1336
  }, {
1316
1337
  cwd,
1317
1338
  reconcileHudForPromptSubmitFn: async (hookCwd, deps = {}) => {
1318
- reconcileCall = { cwd: hookCwd, sessionId: deps.sessionId };
1339
+ reconcileCall = { cwd: hookCwd, sessionId: deps.sessionId, sessionIds: deps.sessionIds };
1319
1340
  return { status: "recreated", paneId: "%9", desiredHeight: 3, duplicateCount: 0 };
1320
1341
  },
1321
1342
  });
1322
1343
  assert.equal(promptResult.omxEventName, "keyword-detector");
1323
- assert.deepEqual(reconcileCall, { cwd, sessionId: canonicalSessionId });
1344
+ assert.deepEqual(reconcileCall, {
1345
+ cwd,
1346
+ sessionId: canonicalSessionId,
1347
+ sessionIds: [canonicalSessionId, nativeSessionId],
1348
+ });
1324
1349
  }
1325
1350
  finally {
1326
1351
  await rm(cwd, { recursive: true, force: true });
@@ -1457,6 +1482,46 @@ describe("codex native hook dispatch", () => {
1457
1482
  await rm(cwd, { recursive: true, force: true });
1458
1483
  }
1459
1484
  });
1485
+ it("includes repo-local .omx project-memory during SessionStart when OMX_ROOT is boxed", async () => {
1486
+ const cwd = await mkdtemp(join(tmpdir(), "omx-native-hook-session-boxed-memory-"));
1487
+ const boxedRoot = await mkdtemp(join(tmpdir(), "omx-native-hook-boxed-root-"));
1488
+ const previousOmxRoot = process.env.OMX_ROOT;
1489
+ try {
1490
+ process.env.OMX_ROOT = boxedRoot;
1491
+ await writeJson(join(cwd, ".omx", "project-memory.json"), {
1492
+ techStack: "Repo-local CLI memory",
1493
+ conventions: "SessionStart should load CLI-written project memory",
1494
+ directives: [
1495
+ { directive: "Prefer repo-local .omx project memory over boxed runtime fallback.", priority: "high" },
1496
+ ],
1497
+ });
1498
+ await writeJson(join(boxedRoot, ".omx", "project-memory.json"), {
1499
+ techStack: "Boxed runtime memory should not win",
1500
+ notes: [{ category: "runtime", content: "stale boxed runtime note", timestamp: new Date().toISOString() }],
1501
+ });
1502
+ const result = await dispatchCodexNativeHook({
1503
+ hook_event_name: "SessionStart",
1504
+ cwd,
1505
+ session_id: "sess-boxed-memory-1",
1506
+ }, { cwd, sessionOwnerPid: 43210 });
1507
+ const additionalContext = String(result.outputJson?.hookSpecificOutput?.additionalContext ?? "");
1508
+ assert.match(additionalContext, /\[Project memory\]/);
1509
+ assert.match(additionalContext, /source: \.omx\/project-memory\.json/);
1510
+ assert.match(additionalContext, /Repo-local CLI memory/);
1511
+ assert.match(additionalContext, /SessionStart should load CLI-written project memory/);
1512
+ assert.match(additionalContext, /Prefer repo-local \.omx project memory over boxed runtime fallback\./);
1513
+ assert.doesNotMatch(additionalContext, /Boxed runtime memory should not win/);
1514
+ assert.doesNotMatch(additionalContext, /stale boxed runtime note/);
1515
+ }
1516
+ finally {
1517
+ if (previousOmxRoot === undefined)
1518
+ delete process.env.OMX_ROOT;
1519
+ else
1520
+ process.env.OMX_ROOT = previousOmxRoot;
1521
+ await rm(cwd, { recursive: true, force: true });
1522
+ await rm(boxedRoot, { recursive: true, force: true });
1523
+ }
1524
+ });
1460
1525
  it("prefers repository project-memory.json during SessionStart while preserving legacy wiki guidance", async () => {
1461
1526
  const cwd = await mkdtemp(join(tmpdir(), "omx-native-hook-session-root-memory-legacy-wiki-"));
1462
1527
  try {
@@ -2853,6 +2918,13 @@ standardMaxRounds = 15
2853
2918
  assert.match(message, /Do not advance from deep-interview to ralplan merely because the first question was answered/);
2854
2919
  assert.match(message, /Planner output has been reviewed sequentially by Architect and then Critic/);
2855
2920
  assert.match(message, /do not hand off to Ultragoal or implementation until .*ralplan_architect_review.*ralplan_critic_review/);
2921
+ const autopilotState = JSON.parse(await readFile(join(cwd, ".omx", "state", "sessions", "sess-autopilot-ralplan-gate", "autopilot-state.json"), "utf-8"));
2922
+ const snapshotPath = autopilotState.state?.handoff_artifacts?.context_snapshot_path ?? "";
2923
+ assert.match(snapshotPath, /^\.omx\/context\/implement-issue-2430-\d{8}T\d{6}Z\.md$/);
2924
+ const snapshot = await readFile(join(cwd, snapshotPath), "utf-8");
2925
+ assert.match(snapshot, /activation prompt \/ task seed: \$autopilot implement issue #2430/);
2926
+ assert.match(snapshot, /scope note: this seed captures the Autopilot activation prompt/);
2927
+ assert.match(snapshot, /constraints: follow deep-interview -> ralplan -> ultragoal -> code-review -> ultraqa/);
2856
2928
  }
2857
2929
  finally {
2858
2930
  await rm(cwd, { recursive: true, force: true });
@@ -4172,7 +4244,54 @@ esac
4172
4244
  await rm(cwd, { recursive: true, force: true });
4173
4245
  }
4174
4246
  });
4175
- it("reuses an existing owner-tagged HUD pane when UserPromptSubmit revives with the canonical session id", async () => {
4247
+ it("skips prompt-submit HUD reconciliation during doctor smoke validation", async () => {
4248
+ const cwd = await mkdtemp(join(tmpdir(), "omx-native-hook-doctor-smoke-hud-"));
4249
+ const originalTmux = process.env.TMUX;
4250
+ const originalTmuxPane = process.env.TMUX_PANE;
4251
+ const originalHudOwner = process.env[OMX_TMUX_HUD_OWNER_ENV];
4252
+ const originalDoctorSmoke = process.env.OMX_NATIVE_HOOK_DOCTOR_SMOKE;
4253
+ try {
4254
+ process.env.TMUX = "1";
4255
+ process.env.TMUX_PANE = "%1";
4256
+ process.env[OMX_TMUX_HUD_OWNER_ENV] = "1";
4257
+ process.env.OMX_NATIVE_HOOK_DOCTOR_SMOKE = "1";
4258
+ let reconcileCalled = false;
4259
+ const result = await dispatchCodexNativeHook({
4260
+ hook_event_name: "UserPromptSubmit",
4261
+ cwd,
4262
+ session_id: "omx-doctor-plugin-hook-smoke",
4263
+ prompt: "$ralplan doctor plugin hook smoke test",
4264
+ }, {
4265
+ cwd,
4266
+ reconcileHudForPromptSubmitFn: async () => {
4267
+ reconcileCalled = true;
4268
+ return { status: "recreated", paneId: "%9", desiredHeight: 3, duplicateCount: 0 };
4269
+ },
4270
+ });
4271
+ assert.equal(result.omxEventName, "keyword-detector");
4272
+ assert.equal(reconcileCalled, false);
4273
+ }
4274
+ finally {
4275
+ if (originalTmux === undefined)
4276
+ delete process.env.TMUX;
4277
+ else
4278
+ process.env.TMUX = originalTmux;
4279
+ if (originalTmuxPane === undefined)
4280
+ delete process.env.TMUX_PANE;
4281
+ else
4282
+ process.env.TMUX_PANE = originalTmuxPane;
4283
+ if (originalHudOwner === undefined)
4284
+ delete process.env[OMX_TMUX_HUD_OWNER_ENV];
4285
+ else
4286
+ process.env[OMX_TMUX_HUD_OWNER_ENV] = originalHudOwner;
4287
+ if (originalDoctorSmoke === undefined)
4288
+ delete process.env.OMX_NATIVE_HOOK_DOCTOR_SMOKE;
4289
+ else
4290
+ process.env.OMX_NATIVE_HOOK_DOCTOR_SMOKE = originalDoctorSmoke;
4291
+ await rm(cwd, { recursive: true, force: true });
4292
+ }
4293
+ });
4294
+ it("recreates a leader-only HUD pane when UserPromptSubmit revives with the canonical session id", async () => {
4176
4295
  const cwd = await mkdtemp(join(tmpdir(), "omx-native-hook-hud-reuse-"));
4177
4296
  const originalTmux = process.env.TMUX;
4178
4297
  const originalTmuxPane = process.env.TMUX_PANE;
@@ -4219,8 +4338,8 @@ esac
4219
4338
  assert.equal(result.omxEventName, "keyword-detector");
4220
4339
  const tmuxCalls = await readFile(tmuxLog, "utf-8");
4221
4340
  assert.match(tmuxCalls, /list-panes -t %1 -F/);
4222
- assert.match(tmuxCalls, new RegExp(`resize-pane -t %2 -y ${HUD_TMUX_HEIGHT_LINES}`));
4223
- assert.doesNotMatch(tmuxCalls, /split-window/);
4341
+ assert.match(tmuxCalls, /split-window/);
4342
+ assert.match(tmuxCalls, new RegExp(`resize-pane -t %9 -y ${HUD_TMUX_HEIGHT_LINES}`));
4224
4343
  assert.equal(existsSync(join(cwd, ".omx", "state", "sessions", canonicalSessionId, "ralplan-state.json")), true);
4225
4344
  assert.equal(existsSync(join(cwd, ".omx", "state", "sessions", nativeSessionId, "ralplan-state.json")), false);
4226
4345
  }
@@ -11483,7 +11602,7 @@ exit 0
11483
11602
  }, { cwd });
11484
11603
  assert.equal(result.omxEventName, "pre-tool-use");
11485
11604
  assert.equal(result.outputJson?.decision, "block");
11486
- assert.match(String(result.outputJson?.reason ?? ""), /Ralplan is active .*implementation\/write tools are blocked/i);
11605
+ assert.match(String(result.outputJson?.reason ?? ""), /(?:Ralplan|Autopilot planning) is active .*implementation\/write tools are blocked/i);
11487
11606
  assert.match(String(result.outputJson?.hookSpecificOutput?.additionalContext ?? ""), /\$ultragoal.*\$team.*\$ralph/i);
11488
11607
  }
11489
11608
  finally {
@@ -11525,120 +11644,146 @@ exit 0
11525
11644
  }, { cwd });
11526
11645
  assert.equal(result.omxEventName, "pre-tool-use");
11527
11646
  assert.equal(result.outputJson?.decision, "block");
11528
- assert.match(String(result.outputJson?.reason ?? ""), /Ralplan is active .*implementation\/write tools are blocked/i);
11647
+ assert.match(String(result.outputJson?.reason ?? ""), /(?:Ralplan|Autopilot planning) is active .*implementation\/write tools are blocked/i);
11529
11648
  }
11530
11649
  finally {
11531
11650
  await rm(cwd, { recursive: true, force: true });
11532
11651
  }
11533
11652
  });
11534
- it("allows implementation writes when terminal Autopilot run-state shadows stale supervised ralplan state", async () => {
11535
- const cwd = await mkdtemp(join(tmpdir(), "omx-native-hook-autopilot-ralplan-terminal-pretool-"));
11653
+ it("blocks implementation writes when Autopilot ralplan is visible only in skill-active phase", async () => {
11654
+ const cwd = await mkdtemp(join(tmpdir(), "omx-native-hook-autopilot-skill-ralplan-pretool-block-"));
11536
11655
  try {
11537
11656
  const stateDir = join(cwd, ".omx", "state");
11538
- const sessionId = "sess-autopilot-ralplan-terminal-pretool";
11657
+ const sessionId = "sess-autopilot-skill-ralplan-pretool-block";
11539
11658
  await mkdir(join(stateDir, "sessions", sessionId), { recursive: true });
11540
11659
  await writeJson(join(stateDir, "session.json"), { session_id: sessionId });
11541
11660
  await writeJson(join(stateDir, "sessions", sessionId, "skill-active-state.json"), {
11542
11661
  active: true,
11543
11662
  skill: "autopilot",
11544
- phase: "ralplan",
11663
+ phase: "autopilot:ralplan",
11545
11664
  session_id: sessionId,
11546
- active_skills: [{ skill: "autopilot", phase: "ralplan", active: true, session_id: sessionId }],
11665
+ active_skills: [{ skill: "autopilot", phase: "autopilot:ralplan", active: true, session_id: sessionId }],
11547
11666
  });
11548
11667
  await writeJson(join(stateDir, "sessions", sessionId, "autopilot-state.json"), {
11549
11668
  active: true,
11550
11669
  mode: "autopilot",
11551
- current_phase: "ralplan",
11670
+ current_phase: "planning",
11552
11671
  session_id: sessionId,
11553
- });
11554
- await writeJson(join(stateDir, "sessions", sessionId, "run-state.json"), {
11555
- version: 1,
11556
- active: false,
11557
- mode: "autopilot",
11558
- outcome: "finish",
11559
- lifecycle_outcome: "finished",
11560
- current_phase: "complete",
11561
- completed_at: "2026-05-30T00:00:00.000Z",
11562
- updated_at: "2026-05-30T00:00:00.000Z",
11672
+ state: {
11673
+ handoff_artifacts: {
11674
+ ralplan_consensus_gate: { required: true, complete: false },
11675
+ },
11676
+ },
11563
11677
  });
11564
11678
  const result = await dispatchCodexNativeHook({
11565
11679
  hook_event_name: "PreToolUse",
11566
11680
  cwd,
11567
11681
  session_id: sessionId,
11568
- thread_id: "thread-autopilot-ralplan-terminal-pretool",
11682
+ thread_id: "thread-autopilot-skill-ralplan-pretool-block",
11569
11683
  tool_name: "Edit",
11570
11684
  tool_input: { file_path: "src/runtime.ts" },
11571
11685
  }, { cwd });
11572
11686
  assert.equal(result.omxEventName, "pre-tool-use");
11573
- assert.equal(result.outputJson, null);
11687
+ assert.equal(result.outputJson?.decision, "block");
11688
+ assert.match(String(result.outputJson?.reason ?? ""), /Autopilot planning is active .*implementation\/write tools are blocked/i);
11574
11689
  }
11575
11690
  finally {
11576
11691
  await rm(cwd, { recursive: true, force: true });
11577
11692
  }
11578
11693
  });
11579
- it("blocks bash implementation writes while Autopilot is supervising ralplan without handoff", async () => {
11580
- const cwd = await mkdtemp(join(tmpdir(), "omx-native-hook-autopilot-ralplan-pretool-bash-block-"));
11694
+ it("ignores stale Autopilot ralplan skill mirrors after detail state leaves planning", async () => {
11695
+ const cwd = await mkdtemp(join(tmpdir(), "omx-native-hook-autopilot-stale-ralplan-mirror-"));
11581
11696
  try {
11582
11697
  const stateDir = join(cwd, ".omx", "state");
11583
- const sessionId = "sess-autopilot-ralplan-pretool-bash-block";
11698
+ const sessionId = "sess-autopilot-stale-ralplan-mirror";
11584
11699
  await mkdir(join(stateDir, "sessions", sessionId), { recursive: true });
11585
11700
  await writeJson(join(stateDir, "session.json"), { session_id: sessionId });
11586
11701
  await writeJson(join(stateDir, "sessions", sessionId, "skill-active-state.json"), {
11587
11702
  active: true,
11588
11703
  skill: "autopilot",
11589
- phase: "ralplan",
11704
+ phase: "autopilot:ralplan",
11590
11705
  session_id: sessionId,
11591
- active_skills: [{ skill: "autopilot", phase: "ralplan", active: true, session_id: sessionId }],
11706
+ active_skills: [{ skill: "autopilot", phase: "autopilot:ralplan", active: true, session_id: sessionId }],
11707
+ });
11708
+ for (const phase of ["ultragoal", "code-review", "completing", "complete"]) {
11709
+ await writeJson(join(stateDir, "sessions", sessionId, "autopilot-state.json"), {
11710
+ active: true,
11711
+ mode: "autopilot",
11712
+ current_phase: phase,
11713
+ session_id: sessionId,
11714
+ });
11715
+ const result = await dispatchCodexNativeHook({
11716
+ hook_event_name: "PreToolUse",
11717
+ cwd,
11718
+ session_id: sessionId,
11719
+ thread_id: "thread-autopilot-stale-ralplan-mirror",
11720
+ tool_name: "Edit",
11721
+ tool_input: { file_path: "src/runtime.ts" },
11722
+ }, { cwd });
11723
+ assert.equal(result.omxEventName, "pre-tool-use");
11724
+ assert.equal(result.outputJson, null, `stale skill-active ralplan mirror must not block when Autopilot detail phase is ${phase}`);
11725
+ }
11726
+ }
11727
+ finally {
11728
+ await rm(cwd, { recursive: true, force: true });
11729
+ }
11730
+ });
11731
+ it("allows explicit blank Autopilot detail phase to use a ralplan skill mirror", async () => {
11732
+ const cwd = await mkdtemp(join(tmpdir(), "omx-native-hook-autopilot-blank-phase-mirror-"));
11733
+ try {
11734
+ const stateDir = join(cwd, ".omx", "state");
11735
+ const sessionId = "sess-autopilot-blank-phase-mirror";
11736
+ await mkdir(join(stateDir, "sessions", sessionId), { recursive: true });
11737
+ await writeJson(join(stateDir, "session.json"), { session_id: sessionId });
11738
+ await writeJson(join(stateDir, "sessions", sessionId, "skill-active-state.json"), {
11739
+ active: true,
11740
+ skill: "autopilot",
11741
+ phase: "autopilot:ralplan",
11742
+ session_id: sessionId,
11743
+ active_skills: [{ skill: "autopilot", phase: "autopilot:ralplan", active: true, session_id: sessionId }],
11592
11744
  });
11593
11745
  await writeJson(join(stateDir, "sessions", sessionId, "autopilot-state.json"), {
11594
11746
  active: true,
11595
11747
  mode: "autopilot",
11596
- current_phase: "ralplan",
11748
+ current_phase: "",
11597
11749
  session_id: sessionId,
11598
11750
  });
11599
11751
  const result = await dispatchCodexNativeHook({
11600
11752
  hook_event_name: "PreToolUse",
11601
11753
  cwd,
11602
11754
  session_id: sessionId,
11603
- thread_id: "thread-autopilot-ralplan-pretool-bash-block",
11604
- tool_name: "Bash",
11605
- tool_input: { command: "cat <<'EOF' > src/runtime.ts\nimplementation\nEOF" },
11755
+ thread_id: "thread-autopilot-blank-phase-mirror",
11756
+ tool_name: "Edit",
11757
+ tool_input: { file_path: "src/runtime.ts" },
11606
11758
  }, { cwd });
11607
11759
  assert.equal(result.omxEventName, "pre-tool-use");
11608
11760
  assert.equal(result.outputJson?.decision, "block");
11609
- assert.match(String(result.outputJson?.reason ?? ""), /Ralplan is active .*implementation\/write tools are blocked/i);
11761
+ assert.match(String(result.outputJson?.reason ?? ""), /Autopilot planning is active .*implementation\/write tools are blocked/i);
11610
11762
  }
11611
11763
  finally {
11612
11764
  await rm(cwd, { recursive: true, force: true });
11613
11765
  }
11614
11766
  });
11615
- it("allows ralplan planning artifact writes without execution handoff", async () => {
11616
- const cwd = await mkdtemp(join(tmpdir(), "omx-native-hook-ralplan-pretool-artifact-"));
11767
+ it("does not block implementation writes from Autopilot ralplan detail state without canonical skill state", async () => {
11768
+ const cwd = await mkdtemp(join(tmpdir(), "omx-native-hook-autopilot-ralplan-no-canonical-"));
11617
11769
  try {
11618
11770
  const stateDir = join(cwd, ".omx", "state");
11619
- const sessionId = "sess-ralplan-pretool-artifact";
11771
+ const sessionId = "sess-autopilot-ralplan-no-canonical";
11620
11772
  await mkdir(join(stateDir, "sessions", sessionId), { recursive: true });
11621
11773
  await writeJson(join(stateDir, "session.json"), { session_id: sessionId });
11622
- await writeJson(join(stateDir, "sessions", sessionId, "skill-active-state.json"), {
11623
- active: true,
11624
- skill: "ralplan",
11625
- phase: "planning",
11626
- session_id: sessionId,
11627
- active_skills: [{ skill: "ralplan", phase: "planning", active: true, session_id: sessionId }],
11628
- });
11629
- await writeJson(join(stateDir, "sessions", sessionId, "ralplan-state.json"), {
11774
+ await writeJson(join(stateDir, "sessions", sessionId, "autopilot-state.json"), {
11630
11775
  active: true,
11631
- mode: "ralplan",
11632
- current_phase: "planning",
11776
+ mode: "autopilot",
11777
+ current_phase: "ralplan",
11633
11778
  session_id: sessionId,
11634
11779
  });
11635
11780
  const result = await dispatchCodexNativeHook({
11636
11781
  hook_event_name: "PreToolUse",
11637
11782
  cwd,
11638
11783
  session_id: sessionId,
11639
- thread_id: "thread-ralplan-pretool-artifact",
11640
- tool_name: "Write",
11641
- tool_input: { file_path: ".omx/plans/prd-issue-2603.md" },
11784
+ thread_id: "thread-autopilot-ralplan-no-canonical",
11785
+ tool_name: "Edit",
11786
+ tool_input: { file_path: "src/runtime.ts" },
11642
11787
  }, { cwd });
11643
11788
  assert.equal(result.omxEventName, "pre-tool-use");
11644
11789
  assert.equal(result.outputJson, null);
@@ -11647,143 +11792,677 @@ exit 0
11647
11792
  await rm(cwd, { recursive: true, force: true });
11648
11793
  }
11649
11794
  });
11650
- it("blocks bash implementation writes while ralplan is active without execution handoff", async () => {
11651
- const cwd = await mkdtemp(join(tmpdir(), "omx-native-hook-ralplan-pretool-bash-block-"));
11795
+ it("allows implementation writes when terminal Autopilot run-state shadows stale supervised ralplan state", async () => {
11796
+ const cwd = await mkdtemp(join(tmpdir(), "omx-native-hook-autopilot-ralplan-terminal-pretool-"));
11652
11797
  try {
11653
11798
  const stateDir = join(cwd, ".omx", "state");
11654
- const sessionId = "sess-ralplan-pretool-bash-block";
11799
+ const sessionId = "sess-autopilot-ralplan-terminal-pretool";
11655
11800
  await mkdir(join(stateDir, "sessions", sessionId), { recursive: true });
11656
11801
  await writeJson(join(stateDir, "session.json"), { session_id: sessionId });
11657
11802
  await writeJson(join(stateDir, "sessions", sessionId, "skill-active-state.json"), {
11658
11803
  active: true,
11659
- skill: "ralplan",
11660
- phase: "planning",
11804
+ skill: "autopilot",
11805
+ phase: "ralplan",
11661
11806
  session_id: sessionId,
11662
- active_skills: [{ skill: "ralplan", phase: "planning", active: true, session_id: sessionId }],
11807
+ active_skills: [{ skill: "autopilot", phase: "ralplan", active: true, session_id: sessionId }],
11663
11808
  });
11664
- await writeJson(join(stateDir, "sessions", sessionId, "ralplan-state.json"), {
11809
+ await writeJson(join(stateDir, "sessions", sessionId, "autopilot-state.json"), {
11665
11810
  active: true,
11666
- mode: "ralplan",
11667
- current_phase: "planning",
11811
+ mode: "autopilot",
11812
+ current_phase: "ralplan",
11668
11813
  session_id: sessionId,
11669
11814
  });
11815
+ await writeJson(join(stateDir, "sessions", sessionId, "run-state.json"), {
11816
+ version: 1,
11817
+ active: false,
11818
+ mode: "autopilot",
11819
+ outcome: "finish",
11820
+ lifecycle_outcome: "finished",
11821
+ current_phase: "complete",
11822
+ completed_at: "2026-05-30T00:00:00.000Z",
11823
+ updated_at: "2026-05-30T00:00:00.000Z",
11824
+ });
11670
11825
  const result = await dispatchCodexNativeHook({
11671
11826
  hook_event_name: "PreToolUse",
11672
11827
  cwd,
11673
11828
  session_id: sessionId,
11674
- thread_id: "thread-ralplan-pretool-bash-block",
11675
- tool_name: "Bash",
11676
- tool_input: { command: "cat <<'EOF' > src/runtime.ts\nimplementation\nEOF" },
11829
+ thread_id: "thread-autopilot-ralplan-terminal-pretool",
11830
+ tool_name: "Edit",
11831
+ tool_input: { file_path: "src/runtime.ts" },
11677
11832
  }, { cwd });
11678
11833
  assert.equal(result.omxEventName, "pre-tool-use");
11679
- assert.equal(result.outputJson?.decision, "block");
11680
- assert.match(String(result.outputJson?.reason ?? ""), /Ralplan is active .*implementation\/write tools are blocked/i);
11834
+ assert.equal(result.outputJson, null);
11681
11835
  }
11682
11836
  finally {
11683
11837
  await rm(cwd, { recursive: true, force: true });
11684
11838
  }
11685
11839
  });
11686
- it("allows bash planning artifact writes while ralplan is active without execution handoff", async () => {
11687
- const cwd = await mkdtemp(join(tmpdir(), "omx-native-hook-ralplan-pretool-bash-artifact-"));
11840
+ it("blocks bash implementation writes while Autopilot is supervising ralplan without handoff", async () => {
11841
+ const cwd = await mkdtemp(join(tmpdir(), "omx-native-hook-autopilot-ralplan-pretool-bash-block-"));
11688
11842
  try {
11689
11843
  const stateDir = join(cwd, ".omx", "state");
11690
- const sessionId = "sess-ralplan-pretool-bash-artifact";
11844
+ const sessionId = "sess-autopilot-ralplan-pretool-bash-block";
11691
11845
  await mkdir(join(stateDir, "sessions", sessionId), { recursive: true });
11692
11846
  await writeJson(join(stateDir, "session.json"), { session_id: sessionId });
11693
11847
  await writeJson(join(stateDir, "sessions", sessionId, "skill-active-state.json"), {
11694
11848
  active: true,
11695
- skill: "ralplan",
11696
- phase: "planning",
11849
+ skill: "autopilot",
11850
+ phase: "ralplan",
11697
11851
  session_id: sessionId,
11698
- active_skills: [{ skill: "ralplan", phase: "planning", active: true, session_id: sessionId }],
11852
+ active_skills: [{ skill: "autopilot", phase: "ralplan", active: true, session_id: sessionId }],
11699
11853
  });
11700
- await writeJson(join(stateDir, "sessions", sessionId, "ralplan-state.json"), {
11854
+ await writeJson(join(stateDir, "sessions", sessionId, "autopilot-state.json"), {
11701
11855
  active: true,
11702
- mode: "ralplan",
11703
- current_phase: "planning",
11856
+ mode: "autopilot",
11857
+ current_phase: "ralplan",
11704
11858
  session_id: sessionId,
11705
11859
  });
11706
11860
  const result = await dispatchCodexNativeHook({
11707
11861
  hook_event_name: "PreToolUse",
11708
11862
  cwd,
11709
11863
  session_id: sessionId,
11710
- thread_id: "thread-ralplan-pretool-bash-artifact",
11864
+ thread_id: "thread-autopilot-ralplan-pretool-bash-block",
11711
11865
  tool_name: "Bash",
11712
- tool_input: { command: "cat <<'EOF' > .omx/plans/prd-issue-2603.md\nplanning\nEOF" },
11866
+ tool_input: { command: "cat <<'EOF' > src/runtime.ts\nimplementation\nEOF" },
11713
11867
  }, { cwd });
11714
11868
  assert.equal(result.omxEventName, "pre-tool-use");
11715
- assert.equal(result.outputJson, null);
11869
+ assert.equal(result.outputJson?.decision, "block");
11870
+ assert.match(String(result.outputJson?.reason ?? ""), /(?:Ralplan|Autopilot planning) is active .*implementation\/write tools are blocked/i);
11716
11871
  }
11717
11872
  finally {
11718
11873
  await rm(cwd, { recursive: true, force: true });
11719
11874
  }
11720
11875
  });
11721
- it("allows implementation writes when an explicit execution handoff is active", async () => {
11722
- const cwd = await mkdtemp(join(tmpdir(), "omx-native-hook-ralplan-pretool-handoff-"));
11876
+ it("blocks implementation writes when ralplan and Autopilot ralplan are both active", async () => {
11877
+ const cwd = await mkdtemp(join(tmpdir(), "omx-native-hook-ralplan-autopilot-mixed-planning-"));
11723
11878
  try {
11724
11879
  const stateDir = join(cwd, ".omx", "state");
11725
- const sessionId = "sess-ralplan-pretool-handoff";
11880
+ const sessionId = "sess-ralplan-autopilot-mixed-planning";
11726
11881
  await mkdir(join(stateDir, "sessions", sessionId), { recursive: true });
11727
11882
  await writeJson(join(stateDir, "session.json"), { session_id: sessionId });
11728
11883
  await writeJson(join(stateDir, "sessions", sessionId, "skill-active-state.json"), {
11729
11884
  active: true,
11730
- skill: "ultragoal",
11731
- phase: "planning",
11885
+ skill: "autopilot",
11886
+ phase: "ralplan",
11732
11887
  session_id: sessionId,
11733
11888
  active_skills: [
11734
11889
  { skill: "ralplan", phase: "planning", active: true, session_id: sessionId },
11735
- { skill: "ultragoal", phase: "planning", active: true, session_id: sessionId },
11890
+ { skill: "autopilot", phase: "ralplan", active: true, session_id: sessionId },
11736
11891
  ],
11737
11892
  });
11738
11893
  await writeJson(join(stateDir, "sessions", sessionId, "ralplan-state.json"), {
11739
11894
  active: true,
11740
11895
  mode: "ralplan",
11741
- current_phase: "complete",
11896
+ current_phase: "planning",
11742
11897
  session_id: sessionId,
11743
11898
  });
11744
- await writeJson(join(stateDir, "sessions", sessionId, "ultragoal-state.json"), {
11899
+ await writeJson(join(stateDir, "sessions", sessionId, "autopilot-state.json"), {
11745
11900
  active: true,
11746
- mode: "ultragoal",
11747
- current_phase: "planning",
11901
+ mode: "autopilot",
11902
+ current_phase: "ralplan",
11748
11903
  session_id: sessionId,
11749
11904
  });
11750
11905
  const result = await dispatchCodexNativeHook({
11751
11906
  hook_event_name: "PreToolUse",
11752
11907
  cwd,
11753
11908
  session_id: sessionId,
11754
- thread_id: "thread-ralplan-pretool-handoff",
11909
+ thread_id: "thread-ralplan-autopilot-mixed-planning",
11755
11910
  tool_name: "Edit",
11756
11911
  tool_input: { file_path: "src/runtime.ts" },
11757
11912
  }, { cwd });
11758
11913
  assert.equal(result.omxEventName, "pre-tool-use");
11759
- assert.equal(result.outputJson, null);
11914
+ assert.equal(result.outputJson?.decision, "block");
11915
+ assert.match(String(result.outputJson?.reason ?? ""), /(?:Ralplan|Autopilot planning) is active .*implementation\/write tools are blocked/i);
11760
11916
  }
11761
11917
  finally {
11762
11918
  await rm(cwd, { recursive: true, force: true });
11763
11919
  }
11764
11920
  });
11765
- it("does not block Stop from root team state without team_name when no session is known", async () => {
11766
- const cwd = await mkdtemp(join(tmpdir(), "omx-native-hook-stop-root-team-no-session-no-name-"));
11921
+ it("blocks implementation writes while Autopilot is supervising replan without handoff", async () => {
11922
+ const cwd = await mkdtemp(join(tmpdir(), "omx-native-hook-autopilot-replan-pretool-block-"));
11767
11923
  try {
11768
11924
  const stateDir = join(cwd, ".omx", "state");
11769
- await mkdir(stateDir, { recursive: true });
11770
- await writeJson(join(stateDir, "team-state.json"), {
11925
+ const sessionId = "sess-autopilot-replan-pretool-block";
11926
+ await mkdir(join(stateDir, "sessions", sessionId), { recursive: true });
11927
+ await writeJson(join(stateDir, "session.json"), { session_id: sessionId });
11928
+ await writeJson(join(stateDir, "sessions", sessionId, "skill-active-state.json"), {
11771
11929
  active: true,
11772
- mode: "team",
11773
- current_phase: "starting",
11930
+ skill: "autopilot",
11931
+ phase: "replan",
11932
+ session_id: sessionId,
11933
+ active_skills: [{ skill: "autopilot", phase: "replan", active: true, session_id: sessionId }],
11934
+ });
11935
+ await writeJson(join(stateDir, "sessions", sessionId, "autopilot-state.json"), {
11936
+ active: true,
11937
+ mode: "autopilot",
11938
+ current_phase: "replan",
11939
+ session_id: sessionId,
11774
11940
  });
11775
11941
  const result = await dispatchCodexNativeHook({
11776
- hook_event_name: "Stop",
11942
+ hook_event_name: "PreToolUse",
11777
11943
  cwd,
11944
+ session_id: sessionId,
11945
+ thread_id: "thread-autopilot-replan-pretool-block",
11946
+ tool_name: "Edit",
11947
+ tool_input: { file_path: "src/runtime.ts" },
11778
11948
  }, { cwd });
11779
- assert.equal(result.omxEventName, "stop");
11780
- assert.equal(result.outputJson, null);
11949
+ assert.equal(result.omxEventName, "pre-tool-use");
11950
+ assert.equal(result.outputJson?.decision, "block");
11951
+ assert.match(String(result.outputJson?.reason ?? ""), /(?:Ralplan|Autopilot planning) is active .*implementation\/write tools are blocked/i);
11781
11952
  }
11782
11953
  finally {
11783
11954
  await rm(cwd, { recursive: true, force: true });
11784
11955
  }
11785
11956
  });
11786
- it("does not block Stop from root team state without team_name for a foreign session", async () => {
11957
+ it("blocks implementation writes when native Codex id maps to OMX Autopilot ralplan state", async () => {
11958
+ const cwd = await mkdtemp(join(tmpdir(), "omx-native-hook-autopilot-ralplan-native-map-block-"));
11959
+ try {
11960
+ const stateDir = join(cwd, ".omx", "state");
11961
+ const sessionId = "sess-autopilot-ralplan-native-map-block";
11962
+ const nativeSessionId = "019e-autopilot-ralplan-native";
11963
+ await writeNativeMappedSessionState(cwd, stateDir, sessionId, nativeSessionId);
11964
+ await writeSessionSkillActiveState(stateDir, sessionId, "autopilot", "ralplan");
11965
+ await writeJson(join(stateDir, "sessions", sessionId, "autopilot-state.json"), {
11966
+ active: true,
11967
+ mode: "autopilot",
11968
+ current_phase: "ralplan",
11969
+ session_id: sessionId,
11970
+ });
11971
+ const result = await dispatchCodexNativeHook({
11972
+ hook_event_name: "PreToolUse",
11973
+ cwd,
11974
+ session_id: nativeSessionId,
11975
+ thread_id: "thread-autopilot-ralplan-native-map-block",
11976
+ tool_name: "apply_patch",
11977
+ tool_input: { file_path: "src/runtime.ts" },
11978
+ }, { cwd });
11979
+ assert.equal(result.omxEventName, "pre-tool-use");
11980
+ assert.equal(result.outputJson?.decision, "block");
11981
+ assert.match(String(result.outputJson?.reason ?? ""), /(?:Ralplan|Autopilot planning) is active .*implementation\/write tools are blocked/i);
11982
+ }
11983
+ finally {
11984
+ await rm(cwd, { recursive: true, force: true });
11985
+ }
11986
+ });
11987
+ it("blocks bash implementation writes when native Codex id maps to OMX Autopilot ralplan state", async () => {
11988
+ const cwd = await mkdtemp(join(tmpdir(), "omx-native-hook-autopilot-ralplan-native-map-bash-"));
11989
+ try {
11990
+ const stateDir = join(cwd, ".omx", "state");
11991
+ const sessionId = "sess-autopilot-ralplan-native-map-bash";
11992
+ const nativeSessionId = "019e-autopilot-ralplan-native-bash";
11993
+ await writeNativeMappedSessionState(cwd, stateDir, sessionId, nativeSessionId);
11994
+ await writeSessionSkillActiveState(stateDir, sessionId, "autopilot", "ralplan");
11995
+ await writeJson(join(stateDir, "sessions", sessionId, "autopilot-state.json"), {
11996
+ active: true,
11997
+ mode: "autopilot",
11998
+ current_phase: "ralplan",
11999
+ session_id: sessionId,
12000
+ });
12001
+ const result = await dispatchCodexNativeHook({
12002
+ hook_event_name: "PreToolUse",
12003
+ cwd,
12004
+ session_id: nativeSessionId,
12005
+ thread_id: "thread-autopilot-ralplan-native-map-bash",
12006
+ tool_name: "Bash",
12007
+ tool_input: { command: "cat <<'EOF' > src/runtime.ts\nimplementation\nEOF" },
12008
+ }, { cwd });
12009
+ assert.equal(result.omxEventName, "pre-tool-use");
12010
+ assert.equal(result.outputJson?.decision, "block");
12011
+ assert.match(String(result.outputJson?.reason ?? ""), /(?:Ralplan|Autopilot planning) is active .*implementation\/write tools are blocked/i);
12012
+ }
12013
+ finally {
12014
+ await rm(cwd, { recursive: true, force: true });
12015
+ }
12016
+ });
12017
+ it("blocks standalone ralplan writes when native Codex id maps to OMX session state", async () => {
12018
+ const cwd = await mkdtemp(join(tmpdir(), "omx-native-hook-ralplan-native-map-block-"));
12019
+ try {
12020
+ const stateDir = join(cwd, ".omx", "state");
12021
+ const sessionId = "sess-ralplan-native-map-block";
12022
+ const nativeSessionId = "019e-ralplan-native-map";
12023
+ await writeNativeMappedSessionState(cwd, stateDir, sessionId, nativeSessionId);
12024
+ await writeSessionSkillActiveState(stateDir, sessionId, "ralplan", "planning");
12025
+ await writeJson(join(stateDir, "sessions", sessionId, "ralplan-state.json"), {
12026
+ active: true,
12027
+ mode: "ralplan",
12028
+ current_phase: "planning",
12029
+ session_id: sessionId,
12030
+ });
12031
+ const result = await dispatchCodexNativeHook({
12032
+ hook_event_name: "PreToolUse",
12033
+ cwd,
12034
+ session_id: nativeSessionId,
12035
+ thread_id: "thread-ralplan-native-map-block",
12036
+ tool_name: "Edit",
12037
+ tool_input: { file_path: "src/runtime.ts" },
12038
+ }, { cwd });
12039
+ assert.equal(result.omxEventName, "pre-tool-use");
12040
+ assert.equal(result.outputJson?.decision, "block");
12041
+ assert.match(String(result.outputJson?.reason ?? ""), /(?:Ralplan|Autopilot planning) is active .*implementation\/write tools are blocked/i);
12042
+ }
12043
+ finally {
12044
+ await rm(cwd, { recursive: true, force: true });
12045
+ }
12046
+ });
12047
+ it("blocks deep-interview writes when native Codex id maps to OMX session state", async () => {
12048
+ const cwd = await mkdtemp(join(tmpdir(), "omx-native-hook-deep-interview-native-map-block-"));
12049
+ try {
12050
+ const stateDir = join(cwd, ".omx", "state");
12051
+ const sessionId = "sess-deep-interview-native-map-block";
12052
+ const nativeSessionId = "019e-deep-interview-native-map";
12053
+ await writeNativeMappedSessionState(cwd, stateDir, sessionId, nativeSessionId);
12054
+ await writeSessionSkillActiveState(stateDir, sessionId, "deep-interview", "interview");
12055
+ await writeJson(join(stateDir, "sessions", sessionId, "deep-interview-state.json"), {
12056
+ active: true,
12057
+ mode: "deep-interview",
12058
+ current_phase: "interview",
12059
+ session_id: sessionId,
12060
+ });
12061
+ const result = await dispatchCodexNativeHook({
12062
+ hook_event_name: "PreToolUse",
12063
+ cwd,
12064
+ session_id: nativeSessionId,
12065
+ thread_id: "thread-deep-interview-native-map-block",
12066
+ tool_name: "Edit",
12067
+ tool_input: { file_path: "src/runtime.ts" },
12068
+ }, { cwd });
12069
+ assert.equal(result.omxEventName, "pre-tool-use");
12070
+ assert.equal(result.outputJson?.decision, "block");
12071
+ assert.match(String(result.outputJson?.reason ?? ""), /Deep-interview is active .*implementation\/write tools are blocked/i);
12072
+ }
12073
+ finally {
12074
+ await rm(cwd, { recursive: true, force: true });
12075
+ }
12076
+ });
12077
+ it("allows mapped ralplan planning artifact writes without execution handoff", async () => {
12078
+ const cwd = await mkdtemp(join(tmpdir(), "omx-native-hook-ralplan-native-map-artifact-"));
12079
+ try {
12080
+ const stateDir = join(cwd, ".omx", "state");
12081
+ const sessionId = "sess-ralplan-native-map-artifact";
12082
+ const nativeSessionId = "019e-ralplan-native-map-artifact";
12083
+ await writeNativeMappedSessionState(cwd, stateDir, sessionId, nativeSessionId);
12084
+ await writeSessionSkillActiveState(stateDir, sessionId, "ralplan", "planning");
12085
+ await writeJson(join(stateDir, "sessions", sessionId, "ralplan-state.json"), {
12086
+ active: true,
12087
+ mode: "ralplan",
12088
+ current_phase: "planning",
12089
+ session_id: sessionId,
12090
+ });
12091
+ const result = await dispatchCodexNativeHook({
12092
+ hook_event_name: "PreToolUse",
12093
+ cwd,
12094
+ session_id: nativeSessionId,
12095
+ thread_id: "thread-ralplan-native-map-artifact",
12096
+ tool_name: "Bash",
12097
+ tool_input: { command: "cat <<'EOF' > .omx/plans/prd-native-map.md\nplanning\nEOF" },
12098
+ }, { cwd });
12099
+ assert.equal(result.omxEventName, "pre-tool-use");
12100
+ assert.equal(result.outputJson, null);
12101
+ }
12102
+ finally {
12103
+ await rm(cwd, { recursive: true, force: true });
12104
+ }
12105
+ });
12106
+ it("allows mapped implementation writes when explicit execution handoff is active", async () => {
12107
+ const cwd = await mkdtemp(join(tmpdir(), "omx-native-hook-ralplan-native-map-handoff-"));
12108
+ try {
12109
+ const stateDir = join(cwd, ".omx", "state");
12110
+ const sessionId = "sess-ralplan-native-map-handoff";
12111
+ const nativeSessionId = "019e-ralplan-native-map-handoff";
12112
+ await writeNativeMappedSessionState(cwd, stateDir, sessionId, nativeSessionId);
12113
+ await writeJson(join(stateDir, "sessions", sessionId, "skill-active-state.json"), {
12114
+ active: true,
12115
+ skill: "ultragoal",
12116
+ phase: "planning",
12117
+ session_id: sessionId,
12118
+ active_skills: [
12119
+ { skill: "ralplan", phase: "planning", active: true, session_id: sessionId },
12120
+ { skill: "ultragoal", phase: "planning", active: true, session_id: sessionId },
12121
+ ],
12122
+ });
12123
+ await writeJson(join(stateDir, "sessions", sessionId, "ralplan-state.json"), {
12124
+ active: true,
12125
+ mode: "ralplan",
12126
+ current_phase: "complete",
12127
+ session_id: sessionId,
12128
+ });
12129
+ await writeJson(join(stateDir, "sessions", sessionId, "ultragoal-state.json"), {
12130
+ active: true,
12131
+ mode: "ultragoal",
12132
+ current_phase: "planning",
12133
+ session_id: sessionId,
12134
+ });
12135
+ const result = await dispatchCodexNativeHook({
12136
+ hook_event_name: "PreToolUse",
12137
+ cwd,
12138
+ session_id: nativeSessionId,
12139
+ thread_id: "thread-ralplan-native-map-handoff",
12140
+ tool_name: "Edit",
12141
+ tool_input: { file_path: "src/runtime.ts" },
12142
+ }, { cwd });
12143
+ assert.equal(result.omxEventName, "pre-tool-use");
12144
+ assert.equal(result.outputJson, null);
12145
+ }
12146
+ finally {
12147
+ await rm(cwd, { recursive: true, force: true });
12148
+ }
12149
+ });
12150
+ it("allows mapped implementation writes when terminal Autopilot run-state shadows stale supervised ralplan state", async () => {
12151
+ const cwd = await mkdtemp(join(tmpdir(), "omx-native-hook-autopilot-ralplan-native-map-terminal-"));
12152
+ try {
12153
+ const stateDir = join(cwd, ".omx", "state");
12154
+ const sessionId = "sess-autopilot-ralplan-native-map-terminal";
12155
+ const nativeSessionId = "019e-autopilot-ralplan-native-terminal";
12156
+ await writeNativeMappedSessionState(cwd, stateDir, sessionId, nativeSessionId);
12157
+ await writeSessionSkillActiveState(stateDir, sessionId, "autopilot", "ralplan");
12158
+ await writeJson(join(stateDir, "sessions", sessionId, "autopilot-state.json"), {
12159
+ active: true,
12160
+ mode: "autopilot",
12161
+ current_phase: "ralplan",
12162
+ session_id: sessionId,
12163
+ });
12164
+ await writeJson(join(stateDir, "sessions", sessionId, "run-state.json"), {
12165
+ version: 1,
12166
+ active: false,
12167
+ mode: "autopilot",
12168
+ outcome: "finish",
12169
+ lifecycle_outcome: "finished",
12170
+ current_phase: "complete",
12171
+ completed_at: "2026-05-30T00:00:00.000Z",
12172
+ updated_at: "2026-05-30T00:00:00.000Z",
12173
+ });
12174
+ const result = await dispatchCodexNativeHook({
12175
+ hook_event_name: "PreToolUse",
12176
+ cwd,
12177
+ session_id: nativeSessionId,
12178
+ thread_id: "thread-autopilot-ralplan-native-map-terminal",
12179
+ tool_name: "Edit",
12180
+ tool_input: { file_path: "src/runtime.ts" },
12181
+ }, { cwd });
12182
+ assert.equal(result.omxEventName, "pre-tool-use");
12183
+ assert.equal(result.outputJson, null);
12184
+ }
12185
+ finally {
12186
+ await rm(cwd, { recursive: true, force: true });
12187
+ }
12188
+ });
12189
+ it("does not block unrelated native Codex ids when current OMX session mapping does not match", async () => {
12190
+ const cwd = await mkdtemp(join(tmpdir(), "omx-native-hook-ralplan-native-map-unrelated-"));
12191
+ try {
12192
+ const stateDir = join(cwd, ".omx", "state");
12193
+ const sessionId = "sess-ralplan-native-map-owner";
12194
+ const ownerNativeSessionId = "019e-ralplan-native-owner";
12195
+ await writeNativeMappedSessionState(cwd, stateDir, sessionId, ownerNativeSessionId);
12196
+ await writeSessionSkillActiveState(stateDir, sessionId, "ralplan", "planning");
12197
+ await writeJson(join(stateDir, "sessions", sessionId, "ralplan-state.json"), {
12198
+ active: true,
12199
+ mode: "ralplan",
12200
+ current_phase: "planning",
12201
+ session_id: sessionId,
12202
+ });
12203
+ const result = await dispatchCodexNativeHook({
12204
+ hook_event_name: "PreToolUse",
12205
+ cwd,
12206
+ session_id: "019e-unrelated-native-session",
12207
+ thread_id: "thread-ralplan-native-map-unrelated",
12208
+ tool_name: "Edit",
12209
+ tool_input: { file_path: "src/runtime.ts" },
12210
+ }, { cwd });
12211
+ assert.equal(result.omxEventName, "pre-tool-use");
12212
+ assert.equal(result.outputJson, null);
12213
+ }
12214
+ finally {
12215
+ await rm(cwd, { recursive: true, force: true });
12216
+ }
12217
+ });
12218
+ it("blocks mapped Autopilot ralplan writes from the authoritative team state root", async () => {
12219
+ const cwd = await mkdtemp(join(tmpdir(), "omx-native-hook-autopilot-ralplan-team-root-"));
12220
+ const teamStateRoot = await mkdtemp(join(tmpdir(), "omx-native-hook-team-root-"));
12221
+ const previousTeamStateRoot = process.env.OMX_TEAM_STATE_ROOT;
12222
+ try {
12223
+ process.env.OMX_TEAM_STATE_ROOT = teamStateRoot;
12224
+ const stateDir = teamStateRoot;
12225
+ const sessionId = "sess-autopilot-ralplan-team-root";
12226
+ const nativeSessionId = "019e-autopilot-ralplan-team-root";
12227
+ await writeNativeMappedSessionState(cwd, stateDir, sessionId, nativeSessionId);
12228
+ await writeSessionSkillActiveState(stateDir, sessionId, "autopilot", "ralplan");
12229
+ await writeJson(join(stateDir, "sessions", sessionId, "autopilot-state.json"), {
12230
+ active: true,
12231
+ mode: "autopilot",
12232
+ current_phase: "ralplan",
12233
+ session_id: sessionId,
12234
+ });
12235
+ const result = await dispatchCodexNativeHook({
12236
+ hook_event_name: "PreToolUse",
12237
+ cwd,
12238
+ session_id: nativeSessionId,
12239
+ thread_id: "thread-autopilot-ralplan-team-root",
12240
+ tool_name: "Edit",
12241
+ tool_input: { file_path: "src/runtime.ts" },
12242
+ }, { cwd });
12243
+ assert.equal(result.omxEventName, "pre-tool-use");
12244
+ assert.equal(result.outputJson?.decision, "block");
12245
+ assert.match(String(result.outputJson?.reason ?? ""), /(?:Ralplan|Autopilot planning) is active .*implementation\/write tools are blocked/i);
12246
+ assert.equal(existsSync(join(cwd, ".omx", "state", "session.json")), false);
12247
+ }
12248
+ finally {
12249
+ if (typeof previousTeamStateRoot === "string")
12250
+ process.env.OMX_TEAM_STATE_ROOT = previousTeamStateRoot;
12251
+ else
12252
+ delete process.env.OMX_TEAM_STATE_ROOT;
12253
+ await rm(cwd, { recursive: true, force: true });
12254
+ await rm(teamStateRoot, { recursive: true, force: true });
12255
+ }
12256
+ });
12257
+ it("does not block unrelated native Codex ids from the authoritative team state root", async () => {
12258
+ const cwd = await mkdtemp(join(tmpdir(), "omx-native-hook-ralplan-team-root-unrelated-"));
12259
+ const teamStateRoot = await mkdtemp(join(tmpdir(), "omx-native-hook-team-root-unrelated-"));
12260
+ const previousTeamStateRoot = process.env.OMX_TEAM_STATE_ROOT;
12261
+ try {
12262
+ process.env.OMX_TEAM_STATE_ROOT = teamStateRoot;
12263
+ const stateDir = teamStateRoot;
12264
+ const sessionId = "sess-ralplan-team-root-owner";
12265
+ const nativeSessionId = "019e-ralplan-team-root-owner";
12266
+ await writeNativeMappedSessionState(cwd, stateDir, sessionId, nativeSessionId);
12267
+ await writeSessionSkillActiveState(stateDir, sessionId, "ralplan", "planning");
12268
+ await writeJson(join(stateDir, "sessions", sessionId, "ralplan-state.json"), {
12269
+ active: true,
12270
+ mode: "ralplan",
12271
+ current_phase: "planning",
12272
+ session_id: sessionId,
12273
+ });
12274
+ const result = await dispatchCodexNativeHook({
12275
+ hook_event_name: "PreToolUse",
12276
+ cwd,
12277
+ session_id: "019e-unrelated-team-root-native",
12278
+ thread_id: "thread-ralplan-team-root-unrelated",
12279
+ tool_name: "Edit",
12280
+ tool_input: { file_path: "src/runtime.ts" },
12281
+ }, { cwd });
12282
+ assert.equal(result.omxEventName, "pre-tool-use");
12283
+ assert.equal(result.outputJson, null);
12284
+ }
12285
+ finally {
12286
+ if (typeof previousTeamStateRoot === "string")
12287
+ process.env.OMX_TEAM_STATE_ROOT = previousTeamStateRoot;
12288
+ else
12289
+ delete process.env.OMX_TEAM_STATE_ROOT;
12290
+ await rm(cwd, { recursive: true, force: true });
12291
+ await rm(teamStateRoot, { recursive: true, force: true });
12292
+ }
12293
+ });
12294
+ it("allows ralplan planning artifact writes without execution handoff", async () => {
12295
+ const cwd = await mkdtemp(join(tmpdir(), "omx-native-hook-ralplan-pretool-artifact-"));
12296
+ try {
12297
+ const stateDir = join(cwd, ".omx", "state");
12298
+ const sessionId = "sess-ralplan-pretool-artifact";
12299
+ await mkdir(join(stateDir, "sessions", sessionId), { recursive: true });
12300
+ await writeJson(join(stateDir, "session.json"), { session_id: sessionId });
12301
+ await writeJson(join(stateDir, "sessions", sessionId, "skill-active-state.json"), {
12302
+ active: true,
12303
+ skill: "ralplan",
12304
+ phase: "planning",
12305
+ session_id: sessionId,
12306
+ active_skills: [{ skill: "ralplan", phase: "planning", active: true, session_id: sessionId }],
12307
+ });
12308
+ await writeJson(join(stateDir, "sessions", sessionId, "ralplan-state.json"), {
12309
+ active: true,
12310
+ mode: "ralplan",
12311
+ current_phase: "planning",
12312
+ session_id: sessionId,
12313
+ });
12314
+ const result = await dispatchCodexNativeHook({
12315
+ hook_event_name: "PreToolUse",
12316
+ cwd,
12317
+ session_id: sessionId,
12318
+ thread_id: "thread-ralplan-pretool-artifact",
12319
+ tool_name: "Write",
12320
+ tool_input: { file_path: ".omx/plans/prd-issue-2603.md" },
12321
+ }, { cwd });
12322
+ assert.equal(result.omxEventName, "pre-tool-use");
12323
+ assert.equal(result.outputJson, null);
12324
+ }
12325
+ finally {
12326
+ await rm(cwd, { recursive: true, force: true });
12327
+ }
12328
+ });
12329
+ it("blocks bash implementation writes while ralplan is active without execution handoff", async () => {
12330
+ const cwd = await mkdtemp(join(tmpdir(), "omx-native-hook-ralplan-pretool-bash-block-"));
12331
+ try {
12332
+ const stateDir = join(cwd, ".omx", "state");
12333
+ const sessionId = "sess-ralplan-pretool-bash-block";
12334
+ await mkdir(join(stateDir, "sessions", sessionId), { recursive: true });
12335
+ await writeJson(join(stateDir, "session.json"), { session_id: sessionId });
12336
+ await writeJson(join(stateDir, "sessions", sessionId, "skill-active-state.json"), {
12337
+ active: true,
12338
+ skill: "ralplan",
12339
+ phase: "planning",
12340
+ session_id: sessionId,
12341
+ active_skills: [{ skill: "ralplan", phase: "planning", active: true, session_id: sessionId }],
12342
+ });
12343
+ await writeJson(join(stateDir, "sessions", sessionId, "ralplan-state.json"), {
12344
+ active: true,
12345
+ mode: "ralplan",
12346
+ current_phase: "planning",
12347
+ session_id: sessionId,
12348
+ });
12349
+ const result = await dispatchCodexNativeHook({
12350
+ hook_event_name: "PreToolUse",
12351
+ cwd,
12352
+ session_id: sessionId,
12353
+ thread_id: "thread-ralplan-pretool-bash-block",
12354
+ tool_name: "Bash",
12355
+ tool_input: { command: "cat <<'EOF' > src/runtime.ts\nimplementation\nEOF" },
12356
+ }, { cwd });
12357
+ assert.equal(result.omxEventName, "pre-tool-use");
12358
+ assert.equal(result.outputJson?.decision, "block");
12359
+ assert.match(String(result.outputJson?.reason ?? ""), /(?:Ralplan|Autopilot planning) is active .*implementation\/write tools are blocked/i);
12360
+ }
12361
+ finally {
12362
+ await rm(cwd, { recursive: true, force: true });
12363
+ }
12364
+ });
12365
+ it("allows bash planning artifact writes while ralplan is active without execution handoff", async () => {
12366
+ const cwd = await mkdtemp(join(tmpdir(), "omx-native-hook-ralplan-pretool-bash-artifact-"));
12367
+ try {
12368
+ const stateDir = join(cwd, ".omx", "state");
12369
+ const sessionId = "sess-ralplan-pretool-bash-artifact";
12370
+ await mkdir(join(stateDir, "sessions", sessionId), { recursive: true });
12371
+ await writeJson(join(stateDir, "session.json"), { session_id: sessionId });
12372
+ await writeJson(join(stateDir, "sessions", sessionId, "skill-active-state.json"), {
12373
+ active: true,
12374
+ skill: "ralplan",
12375
+ phase: "planning",
12376
+ session_id: sessionId,
12377
+ active_skills: [{ skill: "ralplan", phase: "planning", active: true, session_id: sessionId }],
12378
+ });
12379
+ await writeJson(join(stateDir, "sessions", sessionId, "ralplan-state.json"), {
12380
+ active: true,
12381
+ mode: "ralplan",
12382
+ current_phase: "planning",
12383
+ session_id: sessionId,
12384
+ });
12385
+ const result = await dispatchCodexNativeHook({
12386
+ hook_event_name: "PreToolUse",
12387
+ cwd,
12388
+ session_id: sessionId,
12389
+ thread_id: "thread-ralplan-pretool-bash-artifact",
12390
+ tool_name: "Bash",
12391
+ tool_input: { command: "cat <<'EOF' > .omx/plans/prd-issue-2603.md\nplanning\nEOF" },
12392
+ }, { cwd });
12393
+ assert.equal(result.omxEventName, "pre-tool-use");
12394
+ assert.equal(result.outputJson, null);
12395
+ }
12396
+ finally {
12397
+ await rm(cwd, { recursive: true, force: true });
12398
+ }
12399
+ });
12400
+ it("allows implementation writes when an explicit execution handoff is active", async () => {
12401
+ const cwd = await mkdtemp(join(tmpdir(), "omx-native-hook-ralplan-pretool-handoff-"));
12402
+ try {
12403
+ const stateDir = join(cwd, ".omx", "state");
12404
+ const sessionId = "sess-ralplan-pretool-handoff";
12405
+ await mkdir(join(stateDir, "sessions", sessionId), { recursive: true });
12406
+ await writeJson(join(stateDir, "session.json"), { session_id: sessionId });
12407
+ await writeJson(join(stateDir, "sessions", sessionId, "skill-active-state.json"), {
12408
+ active: true,
12409
+ skill: "ultragoal",
12410
+ phase: "planning",
12411
+ session_id: sessionId,
12412
+ active_skills: [
12413
+ { skill: "ralplan", phase: "planning", active: true, session_id: sessionId },
12414
+ { skill: "ultragoal", phase: "planning", active: true, session_id: sessionId },
12415
+ ],
12416
+ });
12417
+ await writeJson(join(stateDir, "sessions", sessionId, "ralplan-state.json"), {
12418
+ active: true,
12419
+ mode: "ralplan",
12420
+ current_phase: "complete",
12421
+ session_id: sessionId,
12422
+ });
12423
+ await writeJson(join(stateDir, "sessions", sessionId, "ultragoal-state.json"), {
12424
+ active: true,
12425
+ mode: "ultragoal",
12426
+ current_phase: "planning",
12427
+ session_id: sessionId,
12428
+ });
12429
+ const result = await dispatchCodexNativeHook({
12430
+ hook_event_name: "PreToolUse",
12431
+ cwd,
12432
+ session_id: sessionId,
12433
+ thread_id: "thread-ralplan-pretool-handoff",
12434
+ tool_name: "Edit",
12435
+ tool_input: { file_path: "src/runtime.ts" },
12436
+ }, { cwd });
12437
+ assert.equal(result.omxEventName, "pre-tool-use");
12438
+ assert.equal(result.outputJson, null);
12439
+ }
12440
+ finally {
12441
+ await rm(cwd, { recursive: true, force: true });
12442
+ }
12443
+ });
12444
+ it("does not block Stop from root team state without team_name when no session is known", async () => {
12445
+ const cwd = await mkdtemp(join(tmpdir(), "omx-native-hook-stop-root-team-no-session-no-name-"));
12446
+ try {
12447
+ const stateDir = join(cwd, ".omx", "state");
12448
+ await mkdir(stateDir, { recursive: true });
12449
+ await writeJson(join(stateDir, "team-state.json"), {
12450
+ active: true,
12451
+ mode: "team",
12452
+ current_phase: "starting",
12453
+ });
12454
+ const result = await dispatchCodexNativeHook({
12455
+ hook_event_name: "Stop",
12456
+ cwd,
12457
+ }, { cwd });
12458
+ assert.equal(result.omxEventName, "stop");
12459
+ assert.equal(result.outputJson, null);
12460
+ }
12461
+ finally {
12462
+ await rm(cwd, { recursive: true, force: true });
12463
+ }
12464
+ });
12465
+ it("does not block Stop from root team state without team_name for a foreign session", async () => {
11787
12466
  const cwd = await mkdtemp(join(tmpdir(), "omx-native-hook-stop-root-team-foreign-no-name-"));
11788
12467
  try {
11789
12468
  const stateDir = join(cwd, ".omx", "state");
@@ -12291,6 +12970,63 @@ describe("codex native hook triage integration", () => {
12291
12970
  await rm(cwd, { recursive: true, force: true });
12292
12971
  }
12293
12972
  });
12973
+ it("omits Team handoff guidance from autopilot prompt context when Team mode is disabled", async () => {
12974
+ const cwd = await mkdtemp(join(tmpdir(), "omx-autopilot-observable-no-team-"));
12975
+ try {
12976
+ await mkdir(join(cwd, ".omx", "state"), { recursive: true });
12977
+ await writeJson(join(cwd, ".omx", "setup-scope.json"), {
12978
+ scope: "project",
12979
+ teamMode: "disabled",
12980
+ });
12981
+ await writeSessionStart(cwd, "sess-autopilot-observable-no-team");
12982
+ const result = await dispatchCodexNativeHook({
12983
+ hook_event_name: "UserPromptSubmit",
12984
+ cwd,
12985
+ session_id: "sess-autopilot-observable-no-team",
12986
+ thread_id: "thread-autopilot-observable-no-team",
12987
+ turn_id: "turn-autopilot-observable-no-team",
12988
+ prompt: "$autopilot implement issue #2430",
12989
+ }, { cwd });
12990
+ assert.equal(result.skillState?.skill, "autopilot");
12991
+ const additionalContext = String(result.outputJson?.hookSpecificOutput?.additionalContext ?? "");
12992
+ assert.match(additionalContext, /detected workflow keyword "\$autopilot" -> autopilot/);
12993
+ assert.match(additionalContext, /\$deep-interview -> \$ralplan -> \$ultragoal -> \$code-review -> \$ultraqa/);
12994
+ assert.doesNotMatch(additionalContext, /\$team/);
12995
+ assert.equal(existsSync(join(cwd, ".omx", "state", "team-state.json")), false);
12996
+ }
12997
+ finally {
12998
+ await rm(cwd, { recursive: true, force: true });
12999
+ }
13000
+ });
13001
+ it("ignores disabled $team before outside-tmux Team blocking so later workflows can activate", async () => {
13002
+ const cwd = await mkdtemp(join(tmpdir(), "omx-native-hook-disabled-team-primary-"));
13003
+ try {
13004
+ await mkdir(join(cwd, ".omx", "state"), { recursive: true });
13005
+ await writeJson(join(cwd, ".omx", "setup-scope.json"), {
13006
+ scope: "project",
13007
+ teamMode: "disabled",
13008
+ });
13009
+ await writeSessionStart(cwd, "sess-disabled-team-primary");
13010
+ const result = await dispatchCodexNativeHook({
13011
+ hook_event_name: "UserPromptSubmit",
13012
+ cwd,
13013
+ session_id: "sess-disabled-team-primary",
13014
+ thread_id: "thread-disabled-team-primary",
13015
+ turn_id: "turn-disabled-team-primary",
13016
+ prompt: "$team $ralph fix this",
13017
+ }, { cwd });
13018
+ assert.equal(result.skillState?.skill, "ralph");
13019
+ assert.equal(result.skillState?.transition_error, undefined);
13020
+ assert.equal(existsSync(join(cwd, ".omx", "state", "team-state.json")), false);
13021
+ assert.equal(existsSync(join(cwd, ".omx", "state", "sessions", "sess-disabled-team-primary", "ralph-state.json")), true);
13022
+ const additionalContext = String(result.outputJson?.hookSpecificOutput?.additionalContext ?? "");
13023
+ assert.match(additionalContext, /detected workflow keyword "\$ralph" -> ralph/);
13024
+ assert.doesNotMatch(additionalContext, /Codex App\/native outside-tmux sessions cannot activate/);
13025
+ }
13026
+ finally {
13027
+ await rm(cwd, { recursive: true, force: true });
13028
+ }
13029
+ });
12294
13030
  it("makes bare autopilot command activation observable in state and prompt guidance", async () => {
12295
13031
  const cwd = await mkdtemp(join(tmpdir(), "omx-autopilot-bare-observable-"));
12296
13032
  try {