oh-my-codex 0.18.7 → 0.18.8

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (259) hide show
  1. package/Cargo.lock +6 -6
  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/cli/__tests__/codex-plugin-layout.test.js +512 -1
  16. package/dist/cli/__tests__/codex-plugin-layout.test.js.map +1 -1
  17. package/dist/cli/__tests__/doctor-warning-copy.test.js +39 -0
  18. package/dist/cli/__tests__/doctor-warning-copy.test.js.map +1 -1
  19. package/dist/cli/__tests__/index.test.js +61 -5
  20. package/dist/cli/__tests__/index.test.js.map +1 -1
  21. package/dist/cli/__tests__/package-bin-contract.test.js +8 -4
  22. package/dist/cli/__tests__/package-bin-contract.test.js.map +1 -1
  23. package/dist/cli/__tests__/ralph-goal-mode-contract.test.js +13 -0
  24. package/dist/cli/__tests__/ralph-goal-mode-contract.test.js.map +1 -1
  25. package/dist/cli/__tests__/ralph.test.js +14 -0
  26. package/dist/cli/__tests__/ralph.test.js.map +1 -1
  27. package/dist/cli/__tests__/setup-install-mode.test.js +89 -0
  28. package/dist/cli/__tests__/setup-install-mode.test.js.map +1 -1
  29. package/dist/cli/__tests__/setup-refresh.test.js +65 -0
  30. package/dist/cli/__tests__/setup-refresh.test.js.map +1 -1
  31. package/dist/cli/__tests__/state.test.js +21 -0
  32. package/dist/cli/__tests__/state.test.js.map +1 -1
  33. package/dist/cli/__tests__/team.test.js +2 -2
  34. package/dist/cli/__tests__/update.test.js +110 -2
  35. package/dist/cli/__tests__/update.test.js.map +1 -1
  36. package/dist/cli/doctor.d.ts.map +1 -1
  37. package/dist/cli/doctor.js +8 -1
  38. package/dist/cli/doctor.js.map +1 -1
  39. package/dist/cli/index.d.ts +11 -2
  40. package/dist/cli/index.d.ts.map +1 -1
  41. package/dist/cli/index.js +108 -15
  42. package/dist/cli/index.js.map +1 -1
  43. package/dist/cli/plugin-marketplace.d.ts +14 -2
  44. package/dist/cli/plugin-marketplace.d.ts.map +1 -1
  45. package/dist/cli/plugin-marketplace.js +62 -15
  46. package/dist/cli/plugin-marketplace.js.map +1 -1
  47. package/dist/cli/ralph.d.ts.map +1 -1
  48. package/dist/cli/ralph.js +3 -1
  49. package/dist/cli/ralph.js.map +1 -1
  50. package/dist/cli/setup-preferences.d.ts +2 -0
  51. package/dist/cli/setup-preferences.d.ts.map +1 -1
  52. package/dist/cli/setup-preferences.js +4 -0
  53. package/dist/cli/setup-preferences.js.map +1 -1
  54. package/dist/cli/setup.d.ts +3 -0
  55. package/dist/cli/setup.d.ts.map +1 -1
  56. package/dist/cli/setup.js +166 -27
  57. package/dist/cli/setup.js.map +1 -1
  58. package/dist/cli/state.d.ts.map +1 -1
  59. package/dist/cli/state.js +8 -1
  60. package/dist/cli/state.js.map +1 -1
  61. package/dist/cli/tmux-hook.d.ts.map +1 -1
  62. package/dist/cli/tmux-hook.js +16 -0
  63. package/dist/cli/tmux-hook.js.map +1 -1
  64. package/dist/cli/update.d.ts +2 -0
  65. package/dist/cli/update.d.ts.map +1 -1
  66. package/dist/cli/update.js +47 -3
  67. package/dist/cli/update.js.map +1 -1
  68. package/dist/config/__tests__/generator-notify.test.js +1 -0
  69. package/dist/config/__tests__/generator-notify.test.js.map +1 -1
  70. package/dist/config/generator.d.ts +2 -2
  71. package/dist/config/generator.d.ts.map +1 -1
  72. package/dist/config/generator.js +2 -2
  73. package/dist/config/generator.js.map +1 -1
  74. package/dist/config/team-mode.d.ts +12 -0
  75. package/dist/config/team-mode.d.ts.map +1 -0
  76. package/dist/config/team-mode.js +91 -0
  77. package/dist/config/team-mode.js.map +1 -0
  78. package/dist/hooks/__tests__/agents-overlay.test.js +88 -0
  79. package/dist/hooks/__tests__/agents-overlay.test.js.map +1 -1
  80. package/dist/hooks/__tests__/code-review-skill-contract.test.js +8 -0
  81. package/dist/hooks/__tests__/code-review-skill-contract.test.js.map +1 -1
  82. package/dist/hooks/__tests__/keyword-detector.test.js +423 -3
  83. package/dist/hooks/__tests__/keyword-detector.test.js.map +1 -1
  84. package/dist/hooks/__tests__/notify-fallback-watcher.test.js +1 -1
  85. package/dist/hooks/__tests__/notify-fallback-watcher.test.js.map +1 -1
  86. package/dist/hooks/__tests__/notify-hook-auto-nudge.test.js +189 -0
  87. package/dist/hooks/__tests__/notify-hook-auto-nudge.test.js.map +1 -1
  88. package/dist/hooks/__tests__/notify-hook-team-leader-nudge.test.js +35 -2
  89. package/dist/hooks/__tests__/notify-hook-team-leader-nudge.test.js.map +1 -1
  90. package/dist/hooks/__tests__/notify-hook-tmux-heal.test.js +3 -3
  91. package/dist/hooks/__tests__/notify-hook-tmux-heal.test.js.map +1 -1
  92. package/dist/hooks/__tests__/skill-guidance-contract.test.js +21 -0
  93. package/dist/hooks/__tests__/skill-guidance-contract.test.js.map +1 -1
  94. package/dist/hooks/agents-overlay.d.ts.map +1 -1
  95. package/dist/hooks/agents-overlay.js +36 -50
  96. package/dist/hooks/agents-overlay.js.map +1 -1
  97. package/dist/hooks/extensibility/__tests__/plugin-runner.test.js +31 -0
  98. package/dist/hooks/extensibility/__tests__/plugin-runner.test.js.map +1 -1
  99. package/dist/hooks/extensibility/plugin-runner.js +17 -21
  100. package/dist/hooks/extensibility/plugin-runner.js.map +1 -1
  101. package/dist/hooks/keyword-detector.d.ts.map +1 -1
  102. package/dist/hooks/keyword-detector.js +258 -12
  103. package/dist/hooks/keyword-detector.js.map +1 -1
  104. package/dist/hooks/prompt-guidance-contract.d.ts.map +1 -1
  105. package/dist/hooks/prompt-guidance-contract.js +6 -0
  106. package/dist/hooks/prompt-guidance-contract.js.map +1 -1
  107. package/dist/hooks/session.d.ts +1 -0
  108. package/dist/hooks/session.d.ts.map +1 -1
  109. package/dist/hooks/session.js.map +1 -1
  110. package/dist/hud/__tests__/authority.test.js +435 -32
  111. package/dist/hud/__tests__/authority.test.js.map +1 -1
  112. package/dist/hud/__tests__/hud-tmux-injection.test.js +2 -1
  113. package/dist/hud/__tests__/hud-tmux-injection.test.js.map +1 -1
  114. package/dist/hud/__tests__/index.test.js +42 -0
  115. package/dist/hud/__tests__/index.test.js.map +1 -1
  116. package/dist/hud/__tests__/reconcile.test.js +521 -15
  117. package/dist/hud/__tests__/reconcile.test.js.map +1 -1
  118. package/dist/hud/__tests__/render.test.js +61 -0
  119. package/dist/hud/__tests__/render.test.js.map +1 -1
  120. package/dist/hud/__tests__/state.test.js +132 -4
  121. package/dist/hud/__tests__/state.test.js.map +1 -1
  122. package/dist/hud/__tests__/tmux.test.js +180 -21
  123. package/dist/hud/__tests__/tmux.test.js.map +1 -1
  124. package/dist/hud/authority.d.ts +5 -0
  125. package/dist/hud/authority.d.ts.map +1 -1
  126. package/dist/hud/authority.js +324 -28
  127. package/dist/hud/authority.js.map +1 -1
  128. package/dist/hud/index.d.ts +3 -2
  129. package/dist/hud/index.d.ts.map +1 -1
  130. package/dist/hud/index.js +42 -19
  131. package/dist/hud/index.js.map +1 -1
  132. package/dist/hud/reconcile.d.ts +3 -3
  133. package/dist/hud/reconcile.d.ts.map +1 -1
  134. package/dist/hud/reconcile.js +128 -19
  135. package/dist/hud/reconcile.js.map +1 -1
  136. package/dist/hud/render.d.ts.map +1 -1
  137. package/dist/hud/render.js +35 -0
  138. package/dist/hud/render.js.map +1 -1
  139. package/dist/hud/state.d.ts.map +1 -1
  140. package/dist/hud/state.js +61 -62
  141. package/dist/hud/state.js.map +1 -1
  142. package/dist/hud/tmux.d.ts +24 -6
  143. package/dist/hud/tmux.d.ts.map +1 -1
  144. package/dist/hud/tmux.js +136 -38
  145. package/dist/hud/tmux.js.map +1 -1
  146. package/dist/hud/types.d.ts +11 -0
  147. package/dist/hud/types.d.ts.map +1 -1
  148. package/dist/hud/types.js.map +1 -1
  149. package/dist/mcp/__tests__/state-paths.test.js +71 -1
  150. package/dist/mcp/__tests__/state-paths.test.js.map +1 -1
  151. package/dist/mcp/state-paths.d.ts +32 -0
  152. package/dist/mcp/state-paths.d.ts.map +1 -1
  153. package/dist/mcp/state-paths.js +113 -17
  154. package/dist/mcp/state-paths.js.map +1 -1
  155. package/dist/mcp/state-server.d.ts +4 -4
  156. package/dist/scripts/__tests__/codex-native-hook.test.js +593 -11
  157. package/dist/scripts/__tests__/codex-native-hook.test.js.map +1 -1
  158. package/dist/scripts/__tests__/notify-state-io.test.js +72 -1
  159. package/dist/scripts/__tests__/notify-state-io.test.js.map +1 -1
  160. package/dist/scripts/__tests__/notify-tmux-injection.test.d.ts +2 -0
  161. package/dist/scripts/__tests__/notify-tmux-injection.test.d.ts.map +1 -0
  162. package/dist/scripts/__tests__/notify-tmux-injection.test.js +57 -0
  163. package/dist/scripts/__tests__/notify-tmux-injection.test.js.map +1 -0
  164. package/dist/scripts/__tests__/run-test-files.test.js +74 -0
  165. package/dist/scripts/__tests__/run-test-files.test.js.map +1 -1
  166. package/dist/scripts/__tests__/verify-native-agents.test.js +65 -0
  167. package/dist/scripts/__tests__/verify-native-agents.test.js.map +1 -1
  168. package/dist/scripts/codex-native-hook.d.ts.map +1 -1
  169. package/dist/scripts/codex-native-hook.js +88 -31
  170. package/dist/scripts/codex-native-hook.js.map +1 -1
  171. package/dist/scripts/eval/eval-parity-smoke.js +1 -1
  172. package/dist/scripts/eval/eval-parity-smoke.js.map +1 -1
  173. package/dist/scripts/notify-hook/auto-nudge.d.ts.map +1 -1
  174. package/dist/scripts/notify-hook/auto-nudge.js +3 -1
  175. package/dist/scripts/notify-hook/auto-nudge.js.map +1 -1
  176. package/dist/scripts/notify-hook/ralph-session-resume.d.ts.map +1 -1
  177. package/dist/scripts/notify-hook/ralph-session-resume.js +3 -10
  178. package/dist/scripts/notify-hook/ralph-session-resume.js.map +1 -1
  179. package/dist/scripts/notify-hook/state-io.d.ts.map +1 -1
  180. package/dist/scripts/notify-hook/state-io.js +62 -38
  181. package/dist/scripts/notify-hook/state-io.js.map +1 -1
  182. package/dist/scripts/notify-hook/team-leader-nudge.d.ts.map +1 -1
  183. package/dist/scripts/notify-hook/team-leader-nudge.js +7 -0
  184. package/dist/scripts/notify-hook/team-leader-nudge.js.map +1 -1
  185. package/dist/scripts/notify-hook/tmux-injection.d.ts +7 -0
  186. package/dist/scripts/notify-hook/tmux-injection.d.ts.map +1 -1
  187. package/dist/scripts/notify-hook/tmux-injection.js +24 -18
  188. package/dist/scripts/notify-hook/tmux-injection.js.map +1 -1
  189. package/dist/scripts/notify-hook.js +75 -11
  190. package/dist/scripts/notify-hook.js.map +1 -1
  191. package/dist/scripts/run-test-files.js +193 -22
  192. package/dist/scripts/run-test-files.js.map +1 -1
  193. package/dist/scripts/sync-plugin-mirror.d.ts.map +1 -1
  194. package/dist/scripts/sync-plugin-mirror.js +61 -3
  195. package/dist/scripts/sync-plugin-mirror.js.map +1 -1
  196. package/dist/scripts/verify-native-agents.d.ts.map +1 -1
  197. package/dist/scripts/verify-native-agents.js +58 -1
  198. package/dist/scripts/verify-native-agents.js.map +1 -1
  199. package/dist/state/__tests__/operations.test.js +113 -0
  200. package/dist/state/__tests__/operations.test.js.map +1 -1
  201. package/dist/state/__tests__/skill-active.test.js +3 -16
  202. package/dist/state/__tests__/skill-active.test.js.map +1 -1
  203. package/dist/state/__tests__/workflow-transition.test.js +25 -0
  204. package/dist/state/__tests__/workflow-transition.test.js.map +1 -1
  205. package/dist/state/operations.d.ts.map +1 -1
  206. package/dist/state/operations.js +57 -2
  207. package/dist/state/operations.js.map +1 -1
  208. package/dist/state/skill-active.d.ts.map +1 -1
  209. package/dist/state/skill-active.js +7 -39
  210. package/dist/state/skill-active.js.map +1 -1
  211. package/dist/state/workflow-transition-reconcile.d.ts.map +1 -1
  212. package/dist/state/workflow-transition-reconcile.js +10 -14
  213. package/dist/state/workflow-transition-reconcile.js.map +1 -1
  214. package/dist/team/__tests__/runtime.test.js +1 -1
  215. package/dist/team/__tests__/runtime.test.js.map +1 -1
  216. package/dist/team/__tests__/scaling.test.js +9 -4
  217. package/dist/team/__tests__/scaling.test.js.map +1 -1
  218. package/dist/team/__tests__/tmux-session.test.js +195 -2
  219. package/dist/team/__tests__/tmux-session.test.js.map +1 -1
  220. package/dist/team/__tests__/worker-runtime-identity.test.js +4 -2
  221. package/dist/team/__tests__/worker-runtime-identity.test.js.map +1 -1
  222. package/dist/team/scaling.d.ts.map +1 -1
  223. package/dist/team/scaling.js +3 -2
  224. package/dist/team/scaling.js.map +1 -1
  225. package/dist/team/tmux-session.d.ts +2 -0
  226. package/dist/team/tmux-session.d.ts.map +1 -1
  227. package/dist/team/tmux-session.js +142 -12
  228. package/dist/team/tmux-session.js.map +1 -1
  229. package/dist/verification/__tests__/ci-rust-gates.test.js +81 -1
  230. package/dist/verification/__tests__/ci-rust-gates.test.js.map +1 -1
  231. package/package.json +8 -8
  232. package/plugins/oh-my-codex/.codex-plugin/plugin.json +1 -1
  233. package/plugins/oh-my-codex/hooks/codex-native-hook.mjs +334 -21
  234. package/plugins/oh-my-codex/hooks/hooks.json +1 -2
  235. package/plugins/oh-my-codex/skills/autopilot/SKILL.md +3 -1
  236. package/plugins/oh-my-codex/skills/code-review/SKILL.md +7 -7
  237. package/plugins/oh-my-codex/skills/ralph/SKILL.md +22 -22
  238. package/plugins/oh-my-codex/skills/ultraqa/SKILL.md +9 -0
  239. package/skills/autopilot/SKILL.md +3 -1
  240. package/skills/code-review/SKILL.md +7 -7
  241. package/skills/ralph/SKILL.md +22 -22
  242. package/skills/ultraqa/SKILL.md +9 -0
  243. package/src/scripts/__tests__/codex-native-hook.test.ts +686 -13
  244. package/src/scripts/__tests__/notify-state-io.test.ts +95 -0
  245. package/src/scripts/__tests__/notify-tmux-injection.test.ts +82 -0
  246. package/src/scripts/__tests__/run-test-files.test.ts +102 -0
  247. package/src/scripts/__tests__/verify-native-agents.test.ts +75 -0
  248. package/src/scripts/codex-native-hook.ts +105 -28
  249. package/src/scripts/demo-team-e2e.sh +10 -7
  250. package/src/scripts/eval/eval-parity-smoke.ts +1 -1
  251. package/src/scripts/notify-hook/auto-nudge.ts +3 -1
  252. package/src/scripts/notify-hook/ralph-session-resume.ts +2 -8
  253. package/src/scripts/notify-hook/state-io.ts +75 -37
  254. package/src/scripts/notify-hook/team-leader-nudge.ts +7 -0
  255. package/src/scripts/notify-hook/tmux-injection.ts +35 -19
  256. package/src/scripts/notify-hook.ts +91 -4
  257. package/src/scripts/run-test-files.ts +192 -22
  258. package/src/scripts/sync-plugin-mirror.ts +98 -9
  259. package/src/scripts/verify-native-agents.ts +65 -1
@@ -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
  }
@@ -3460,6 +3497,17 @@ standardMaxRounds = 15
3460
3497
  assert.match(message, /Do not advance from deep-interview to ralplan merely because the first question was answered/);
3461
3498
  assert.match(message, /Planner output has been reviewed sequentially by Architect and then Critic/);
3462
3499
  assert.match(message, /do not hand off to Ultragoal or implementation until .*ralplan_architect_review.*ralplan_critic_review/);
3500
+
3501
+ const autopilotState = JSON.parse(await readFile(
3502
+ join(cwd, ".omx", "state", "sessions", "sess-autopilot-ralplan-gate", "autopilot-state.json"),
3503
+ "utf-8",
3504
+ )) as { state?: { handoff_artifacts?: { context_snapshot_path?: string } } };
3505
+ const snapshotPath = autopilotState.state?.handoff_artifacts?.context_snapshot_path ?? "";
3506
+ assert.match(snapshotPath, /^\.omx\/context\/implement-issue-2430-\d{8}T\d{6}Z\.md$/);
3507
+ const snapshot = await readFile(join(cwd, snapshotPath), "utf-8");
3508
+ assert.match(snapshot, /activation prompt \/ task seed: \$autopilot implement issue #2430/);
3509
+ assert.match(snapshot, /scope note: this seed captures the Autopilot activation prompt/);
3510
+ assert.match(snapshot, /constraints: follow deep-interview -> ralplan -> ultragoal -> code-review -> ultraqa/);
3463
3511
  } finally {
3464
3512
  await rm(cwd, { recursive: true, force: true });
3465
3513
  }
@@ -5035,7 +5083,51 @@ esac
5035
5083
  }
5036
5084
  });
5037
5085
 
5038
- it("reuses an existing owner-tagged HUD pane when UserPromptSubmit revives with the canonical session id", async () => {
5086
+ it("skips prompt-submit HUD reconciliation during doctor smoke validation", async () => {
5087
+ const cwd = await mkdtemp(join(tmpdir(), "omx-native-hook-doctor-smoke-hud-"));
5088
+ const originalTmux = process.env.TMUX;
5089
+ const originalTmuxPane = process.env.TMUX_PANE;
5090
+ const originalHudOwner = process.env[OMX_TMUX_HUD_OWNER_ENV];
5091
+ const originalDoctorSmoke = process.env.OMX_NATIVE_HOOK_DOCTOR_SMOKE;
5092
+ try {
5093
+ process.env.TMUX = "1";
5094
+ process.env.TMUX_PANE = "%1";
5095
+ process.env[OMX_TMUX_HUD_OWNER_ENV] = "1";
5096
+ process.env.OMX_NATIVE_HOOK_DOCTOR_SMOKE = "1";
5097
+
5098
+ let reconcileCalled = false;
5099
+ const result = await dispatchCodexNativeHook(
5100
+ {
5101
+ hook_event_name: "UserPromptSubmit",
5102
+ cwd,
5103
+ session_id: "omx-doctor-plugin-hook-smoke",
5104
+ prompt: "$ralplan doctor plugin hook smoke test",
5105
+ },
5106
+ {
5107
+ cwd,
5108
+ reconcileHudForPromptSubmitFn: async () => {
5109
+ reconcileCalled = true;
5110
+ return { status: "recreated", paneId: "%9", desiredHeight: 3, duplicateCount: 0 };
5111
+ },
5112
+ },
5113
+ );
5114
+
5115
+ assert.equal(result.omxEventName, "keyword-detector");
5116
+ assert.equal(reconcileCalled, false);
5117
+ } finally {
5118
+ if (originalTmux === undefined) delete process.env.TMUX;
5119
+ else process.env.TMUX = originalTmux;
5120
+ if (originalTmuxPane === undefined) delete process.env.TMUX_PANE;
5121
+ else process.env.TMUX_PANE = originalTmuxPane;
5122
+ if (originalHudOwner === undefined) delete process.env[OMX_TMUX_HUD_OWNER_ENV];
5123
+ else process.env[OMX_TMUX_HUD_OWNER_ENV] = originalHudOwner;
5124
+ if (originalDoctorSmoke === undefined) delete process.env.OMX_NATIVE_HOOK_DOCTOR_SMOKE;
5125
+ else process.env.OMX_NATIVE_HOOK_DOCTOR_SMOKE = originalDoctorSmoke;
5126
+ await rm(cwd, { recursive: true, force: true });
5127
+ }
5128
+ });
5129
+
5130
+ it("recreates a leader-only HUD pane when UserPromptSubmit revives with the canonical session id", async () => {
5039
5131
  const cwd = await mkdtemp(join(tmpdir(), "omx-native-hook-hud-reuse-"));
5040
5132
  const originalTmux = process.env.TMUX;
5041
5133
  const originalTmuxPane = process.env.TMUX_PANE;
@@ -5091,8 +5183,8 @@ esac
5091
5183
  assert.equal(result.omxEventName, "keyword-detector");
5092
5184
  const tmuxCalls = await readFile(tmuxLog, "utf-8");
5093
5185
  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/);
5186
+ assert.match(tmuxCalls, /split-window/);
5187
+ assert.match(tmuxCalls, new RegExp(`resize-pane -t %9 -y ${HUD_TMUX_HEIGHT_LINES}`));
5096
5188
  assert.equal(existsSync(join(cwd, ".omx", "state", "sessions", canonicalSessionId, "ralplan-state.json")), true);
5097
5189
  assert.equal(existsSync(join(cwd, ".omx", "state", "sessions", nativeSessionId, "ralplan-state.json")), false);
5098
5190
  } finally {
@@ -13784,7 +13876,7 @@ exit 0
13784
13876
 
13785
13877
  assert.equal(result.omxEventName, "pre-tool-use");
13786
13878
  assert.equal(result.outputJson?.decision, "block");
13787
- assert.match(String(result.outputJson?.reason ?? ""), /Ralplan is active .*implementation\/write tools are blocked/i);
13879
+ assert.match(String(result.outputJson?.reason ?? ""), /(?:Ralplan|Autopilot planning) is active .*implementation\/write tools are blocked/i);
13788
13880
  assert.match(
13789
13881
  String((result.outputJson?.hookSpecificOutput as { additionalContext?: string } | undefined)?.additionalContext ?? ""),
13790
13882
  /\$ultragoal.*\$team.*\$ralph/i,
@@ -13834,7 +13926,40 @@ exit 0
13834
13926
 
13835
13927
  assert.equal(result.omxEventName, "pre-tool-use");
13836
13928
  assert.equal(result.outputJson?.decision, "block");
13837
- assert.match(String(result.outputJson?.reason ?? ""), /Ralplan is active .*implementation\/write tools are blocked/i);
13929
+ assert.match(String(result.outputJson?.reason ?? ""), /(?:Ralplan|Autopilot planning) is active .*implementation\/write tools are blocked/i);
13930
+ } finally {
13931
+ await rm(cwd, { recursive: true, force: true });
13932
+ }
13933
+ });
13934
+
13935
+ it("does not block implementation writes from Autopilot ralplan detail state without canonical skill state", async () => {
13936
+ const cwd = await mkdtemp(join(tmpdir(), "omx-native-hook-autopilot-ralplan-no-canonical-"));
13937
+ try {
13938
+ const stateDir = join(cwd, ".omx", "state");
13939
+ const sessionId = "sess-autopilot-ralplan-no-canonical";
13940
+ await mkdir(join(stateDir, "sessions", sessionId), { recursive: true });
13941
+ await writeJson(join(stateDir, "session.json"), { session_id: sessionId });
13942
+ await writeJson(join(stateDir, "sessions", sessionId, "autopilot-state.json"), {
13943
+ active: true,
13944
+ mode: "autopilot",
13945
+ current_phase: "ralplan",
13946
+ session_id: sessionId,
13947
+ });
13948
+
13949
+ const result = await dispatchCodexNativeHook(
13950
+ {
13951
+ hook_event_name: "PreToolUse",
13952
+ cwd,
13953
+ session_id: sessionId,
13954
+ thread_id: "thread-autopilot-ralplan-no-canonical",
13955
+ tool_name: "Edit",
13956
+ tool_input: { file_path: "src/runtime.ts" },
13957
+ },
13958
+ { cwd },
13959
+ );
13960
+
13961
+ assert.equal(result.omxEventName, "pre-tool-use");
13962
+ assert.equal(result.outputJson, null);
13838
13963
  } finally {
13839
13964
  await rm(cwd, { recursive: true, force: true });
13840
13965
  }
@@ -13925,12 +14050,486 @@ exit 0
13925
14050
 
13926
14051
  assert.equal(result.omxEventName, "pre-tool-use");
13927
14052
  assert.equal(result.outputJson?.decision, "block");
13928
- assert.match(String(result.outputJson?.reason ?? ""), /Ralplan is active .*implementation\/write tools are blocked/i);
14053
+ assert.match(String(result.outputJson?.reason ?? ""), /(?:Ralplan|Autopilot planning) is active .*implementation\/write tools are blocked/i);
14054
+ } finally {
14055
+ await rm(cwd, { recursive: true, force: true });
14056
+ }
14057
+ });
14058
+
14059
+ it("blocks implementation writes when ralplan and Autopilot ralplan are both active", async () => {
14060
+ const cwd = await mkdtemp(join(tmpdir(), "omx-native-hook-ralplan-autopilot-mixed-planning-"));
14061
+ try {
14062
+ const stateDir = join(cwd, ".omx", "state");
14063
+ const sessionId = "sess-ralplan-autopilot-mixed-planning";
14064
+ await mkdir(join(stateDir, "sessions", sessionId), { recursive: true });
14065
+ await writeJson(join(stateDir, "session.json"), { session_id: sessionId });
14066
+ await writeJson(join(stateDir, "sessions", sessionId, "skill-active-state.json"), {
14067
+ active: true,
14068
+ skill: "autopilot",
14069
+ phase: "ralplan",
14070
+ session_id: sessionId,
14071
+ active_skills: [
14072
+ { skill: "ralplan", phase: "planning", active: true, session_id: sessionId },
14073
+ { skill: "autopilot", phase: "ralplan", active: true, session_id: sessionId },
14074
+ ],
14075
+ });
14076
+ await writeJson(join(stateDir, "sessions", sessionId, "ralplan-state.json"), {
14077
+ active: true,
14078
+ mode: "ralplan",
14079
+ current_phase: "planning",
14080
+ session_id: sessionId,
14081
+ });
14082
+ await writeJson(join(stateDir, "sessions", sessionId, "autopilot-state.json"), {
14083
+ active: true,
14084
+ mode: "autopilot",
14085
+ current_phase: "ralplan",
14086
+ session_id: sessionId,
14087
+ });
14088
+
14089
+ const result = await dispatchCodexNativeHook(
14090
+ {
14091
+ hook_event_name: "PreToolUse",
14092
+ cwd,
14093
+ session_id: sessionId,
14094
+ thread_id: "thread-ralplan-autopilot-mixed-planning",
14095
+ tool_name: "Edit",
14096
+ tool_input: { file_path: "src/runtime.ts" },
14097
+ },
14098
+ { cwd },
14099
+ );
14100
+
14101
+ assert.equal(result.omxEventName, "pre-tool-use");
14102
+ assert.equal(result.outputJson?.decision, "block");
14103
+ assert.match(String(result.outputJson?.reason ?? ""), /(?:Ralplan|Autopilot planning) is active .*implementation\/write tools are blocked/i);
14104
+ } finally {
14105
+ await rm(cwd, { recursive: true, force: true });
14106
+ }
14107
+ });
14108
+
14109
+ it("blocks implementation writes while Autopilot is supervising replan without handoff", async () => {
14110
+ const cwd = await mkdtemp(join(tmpdir(), "omx-native-hook-autopilot-replan-pretool-block-"));
14111
+ try {
14112
+ const stateDir = join(cwd, ".omx", "state");
14113
+ const sessionId = "sess-autopilot-replan-pretool-block";
14114
+ await mkdir(join(stateDir, "sessions", sessionId), { recursive: true });
14115
+ await writeJson(join(stateDir, "session.json"), { session_id: sessionId });
14116
+ await writeJson(join(stateDir, "sessions", sessionId, "skill-active-state.json"), {
14117
+ active: true,
14118
+ skill: "autopilot",
14119
+ phase: "replan",
14120
+ session_id: sessionId,
14121
+ active_skills: [{ skill: "autopilot", phase: "replan", active: true, session_id: sessionId }],
14122
+ });
14123
+ await writeJson(join(stateDir, "sessions", sessionId, "autopilot-state.json"), {
14124
+ active: true,
14125
+ mode: "autopilot",
14126
+ current_phase: "replan",
14127
+ session_id: sessionId,
14128
+ });
14129
+
14130
+ const result = await dispatchCodexNativeHook(
14131
+ {
14132
+ hook_event_name: "PreToolUse",
14133
+ cwd,
14134
+ session_id: sessionId,
14135
+ thread_id: "thread-autopilot-replan-pretool-block",
14136
+ tool_name: "Edit",
14137
+ tool_input: { file_path: "src/runtime.ts" },
14138
+ },
14139
+ { cwd },
14140
+ );
14141
+
14142
+ assert.equal(result.omxEventName, "pre-tool-use");
14143
+ assert.equal(result.outputJson?.decision, "block");
14144
+ assert.match(String(result.outputJson?.reason ?? ""), /(?:Ralplan|Autopilot planning) is active .*implementation\/write tools are blocked/i);
14145
+ } finally {
14146
+ await rm(cwd, { recursive: true, force: true });
14147
+ }
14148
+ });
14149
+
14150
+ it("blocks implementation writes when native Codex id maps to OMX Autopilot ralplan state", async () => {
14151
+ const cwd = await mkdtemp(join(tmpdir(), "omx-native-hook-autopilot-ralplan-native-map-block-"));
14152
+ try {
14153
+ const stateDir = join(cwd, ".omx", "state");
14154
+ const sessionId = "sess-autopilot-ralplan-native-map-block";
14155
+ const nativeSessionId = "019e-autopilot-ralplan-native";
14156
+ await writeNativeMappedSessionState(cwd, stateDir, sessionId, nativeSessionId);
14157
+ await writeSessionSkillActiveState(stateDir, sessionId, "autopilot", "ralplan");
14158
+ await writeJson(join(stateDir, "sessions", sessionId, "autopilot-state.json"), {
14159
+ active: true,
14160
+ mode: "autopilot",
14161
+ current_phase: "ralplan",
14162
+ session_id: sessionId,
14163
+ });
14164
+
14165
+ const result = await dispatchCodexNativeHook(
14166
+ {
14167
+ hook_event_name: "PreToolUse",
14168
+ cwd,
14169
+ session_id: nativeSessionId,
14170
+ thread_id: "thread-autopilot-ralplan-native-map-block",
14171
+ tool_name: "apply_patch",
14172
+ tool_input: { file_path: "src/runtime.ts" },
14173
+ },
14174
+ { cwd },
14175
+ );
14176
+
14177
+ assert.equal(result.omxEventName, "pre-tool-use");
14178
+ assert.equal(result.outputJson?.decision, "block");
14179
+ assert.match(String(result.outputJson?.reason ?? ""), /(?:Ralplan|Autopilot planning) is active .*implementation\/write tools are blocked/i);
14180
+ } finally {
14181
+ await rm(cwd, { recursive: true, force: true });
14182
+ }
14183
+ });
14184
+
14185
+ it("blocks bash implementation writes when native Codex id maps to OMX Autopilot ralplan state", async () => {
14186
+ const cwd = await mkdtemp(join(tmpdir(), "omx-native-hook-autopilot-ralplan-native-map-bash-"));
14187
+ try {
14188
+ const stateDir = join(cwd, ".omx", "state");
14189
+ const sessionId = "sess-autopilot-ralplan-native-map-bash";
14190
+ const nativeSessionId = "019e-autopilot-ralplan-native-bash";
14191
+ await writeNativeMappedSessionState(cwd, stateDir, sessionId, nativeSessionId);
14192
+ await writeSessionSkillActiveState(stateDir, sessionId, "autopilot", "ralplan");
14193
+ await writeJson(join(stateDir, "sessions", sessionId, "autopilot-state.json"), {
14194
+ active: true,
14195
+ mode: "autopilot",
14196
+ current_phase: "ralplan",
14197
+ session_id: sessionId,
14198
+ });
14199
+
14200
+ const result = await dispatchCodexNativeHook(
14201
+ {
14202
+ hook_event_name: "PreToolUse",
14203
+ cwd,
14204
+ session_id: nativeSessionId,
14205
+ thread_id: "thread-autopilot-ralplan-native-map-bash",
14206
+ tool_name: "Bash",
14207
+ tool_input: { command: "cat <<'EOF' > src/runtime.ts\nimplementation\nEOF" },
14208
+ },
14209
+ { cwd },
14210
+ );
14211
+
14212
+ assert.equal(result.omxEventName, "pre-tool-use");
14213
+ assert.equal(result.outputJson?.decision, "block");
14214
+ assert.match(String(result.outputJson?.reason ?? ""), /(?:Ralplan|Autopilot planning) is active .*implementation\/write tools are blocked/i);
13929
14215
  } finally {
13930
14216
  await rm(cwd, { recursive: true, force: true });
13931
14217
  }
13932
14218
  });
13933
14219
 
14220
+ it("blocks standalone ralplan writes when native Codex id maps to OMX session state", async () => {
14221
+ const cwd = await mkdtemp(join(tmpdir(), "omx-native-hook-ralplan-native-map-block-"));
14222
+ try {
14223
+ const stateDir = join(cwd, ".omx", "state");
14224
+ const sessionId = "sess-ralplan-native-map-block";
14225
+ const nativeSessionId = "019e-ralplan-native-map";
14226
+ await writeNativeMappedSessionState(cwd, stateDir, sessionId, nativeSessionId);
14227
+ await writeSessionSkillActiveState(stateDir, sessionId, "ralplan", "planning");
14228
+ await writeJson(join(stateDir, "sessions", sessionId, "ralplan-state.json"), {
14229
+ active: true,
14230
+ mode: "ralplan",
14231
+ current_phase: "planning",
14232
+ session_id: sessionId,
14233
+ });
14234
+
14235
+ const result = await dispatchCodexNativeHook(
14236
+ {
14237
+ hook_event_name: "PreToolUse",
14238
+ cwd,
14239
+ session_id: nativeSessionId,
14240
+ thread_id: "thread-ralplan-native-map-block",
14241
+ tool_name: "Edit",
14242
+ tool_input: { file_path: "src/runtime.ts" },
14243
+ },
14244
+ { cwd },
14245
+ );
14246
+
14247
+ assert.equal(result.omxEventName, "pre-tool-use");
14248
+ assert.equal(result.outputJson?.decision, "block");
14249
+ assert.match(String(result.outputJson?.reason ?? ""), /(?:Ralplan|Autopilot planning) is active .*implementation\/write tools are blocked/i);
14250
+ } finally {
14251
+ await rm(cwd, { recursive: true, force: true });
14252
+ }
14253
+ });
14254
+
14255
+ it("blocks deep-interview writes when native Codex id maps to OMX session state", async () => {
14256
+ const cwd = await mkdtemp(join(tmpdir(), "omx-native-hook-deep-interview-native-map-block-"));
14257
+ try {
14258
+ const stateDir = join(cwd, ".omx", "state");
14259
+ const sessionId = "sess-deep-interview-native-map-block";
14260
+ const nativeSessionId = "019e-deep-interview-native-map";
14261
+ await writeNativeMappedSessionState(cwd, stateDir, sessionId, nativeSessionId);
14262
+ await writeSessionSkillActiveState(stateDir, sessionId, "deep-interview", "interview");
14263
+ await writeJson(join(stateDir, "sessions", sessionId, "deep-interview-state.json"), {
14264
+ active: true,
14265
+ mode: "deep-interview",
14266
+ current_phase: "interview",
14267
+ session_id: sessionId,
14268
+ });
14269
+
14270
+ const result = await dispatchCodexNativeHook(
14271
+ {
14272
+ hook_event_name: "PreToolUse",
14273
+ cwd,
14274
+ session_id: nativeSessionId,
14275
+ thread_id: "thread-deep-interview-native-map-block",
14276
+ tool_name: "Edit",
14277
+ tool_input: { file_path: "src/runtime.ts" },
14278
+ },
14279
+ { cwd },
14280
+ );
14281
+
14282
+ assert.equal(result.omxEventName, "pre-tool-use");
14283
+ assert.equal(result.outputJson?.decision, "block");
14284
+ assert.match(String(result.outputJson?.reason ?? ""), /Deep-interview is active .*implementation\/write tools are blocked/i);
14285
+ } finally {
14286
+ await rm(cwd, { recursive: true, force: true });
14287
+ }
14288
+ });
14289
+
14290
+ it("allows mapped ralplan planning artifact writes without execution handoff", async () => {
14291
+ const cwd = await mkdtemp(join(tmpdir(), "omx-native-hook-ralplan-native-map-artifact-"));
14292
+ try {
14293
+ const stateDir = join(cwd, ".omx", "state");
14294
+ const sessionId = "sess-ralplan-native-map-artifact";
14295
+ const nativeSessionId = "019e-ralplan-native-map-artifact";
14296
+ await writeNativeMappedSessionState(cwd, stateDir, sessionId, nativeSessionId);
14297
+ await writeSessionSkillActiveState(stateDir, sessionId, "ralplan", "planning");
14298
+ await writeJson(join(stateDir, "sessions", sessionId, "ralplan-state.json"), {
14299
+ active: true,
14300
+ mode: "ralplan",
14301
+ current_phase: "planning",
14302
+ session_id: sessionId,
14303
+ });
14304
+
14305
+ const result = await dispatchCodexNativeHook(
14306
+ {
14307
+ hook_event_name: "PreToolUse",
14308
+ cwd,
14309
+ session_id: nativeSessionId,
14310
+ thread_id: "thread-ralplan-native-map-artifact",
14311
+ tool_name: "Bash",
14312
+ tool_input: { command: "cat <<'EOF' > .omx/plans/prd-native-map.md\nplanning\nEOF" },
14313
+ },
14314
+ { cwd },
14315
+ );
14316
+
14317
+ assert.equal(result.omxEventName, "pre-tool-use");
14318
+ assert.equal(result.outputJson, null);
14319
+ } finally {
14320
+ await rm(cwd, { recursive: true, force: true });
14321
+ }
14322
+ });
14323
+
14324
+ it("allows mapped implementation writes when explicit execution handoff is active", async () => {
14325
+ const cwd = await mkdtemp(join(tmpdir(), "omx-native-hook-ralplan-native-map-handoff-"));
14326
+ try {
14327
+ const stateDir = join(cwd, ".omx", "state");
14328
+ const sessionId = "sess-ralplan-native-map-handoff";
14329
+ const nativeSessionId = "019e-ralplan-native-map-handoff";
14330
+ await writeNativeMappedSessionState(cwd, stateDir, sessionId, nativeSessionId);
14331
+ await writeJson(join(stateDir, "sessions", sessionId, "skill-active-state.json"), {
14332
+ active: true,
14333
+ skill: "ultragoal",
14334
+ phase: "planning",
14335
+ session_id: sessionId,
14336
+ active_skills: [
14337
+ { skill: "ralplan", phase: "planning", active: true, session_id: sessionId },
14338
+ { skill: "ultragoal", phase: "planning", active: true, session_id: sessionId },
14339
+ ],
14340
+ });
14341
+ await writeJson(join(stateDir, "sessions", sessionId, "ralplan-state.json"), {
14342
+ active: true,
14343
+ mode: "ralplan",
14344
+ current_phase: "complete",
14345
+ session_id: sessionId,
14346
+ });
14347
+ await writeJson(join(stateDir, "sessions", sessionId, "ultragoal-state.json"), {
14348
+ active: true,
14349
+ mode: "ultragoal",
14350
+ current_phase: "planning",
14351
+ session_id: sessionId,
14352
+ });
14353
+
14354
+ const result = await dispatchCodexNativeHook(
14355
+ {
14356
+ hook_event_name: "PreToolUse",
14357
+ cwd,
14358
+ session_id: nativeSessionId,
14359
+ thread_id: "thread-ralplan-native-map-handoff",
14360
+ tool_name: "Edit",
14361
+ tool_input: { file_path: "src/runtime.ts" },
14362
+ },
14363
+ { cwd },
14364
+ );
14365
+
14366
+ assert.equal(result.omxEventName, "pre-tool-use");
14367
+ assert.equal(result.outputJson, null);
14368
+ } finally {
14369
+ await rm(cwd, { recursive: true, force: true });
14370
+ }
14371
+ });
14372
+
14373
+ it("allows mapped implementation writes when terminal Autopilot run-state shadows stale supervised ralplan state", async () => {
14374
+ const cwd = await mkdtemp(join(tmpdir(), "omx-native-hook-autopilot-ralplan-native-map-terminal-"));
14375
+ try {
14376
+ const stateDir = join(cwd, ".omx", "state");
14377
+ const sessionId = "sess-autopilot-ralplan-native-map-terminal";
14378
+ const nativeSessionId = "019e-autopilot-ralplan-native-terminal";
14379
+ await writeNativeMappedSessionState(cwd, stateDir, sessionId, nativeSessionId);
14380
+ await writeSessionSkillActiveState(stateDir, sessionId, "autopilot", "ralplan");
14381
+ await writeJson(join(stateDir, "sessions", sessionId, "autopilot-state.json"), {
14382
+ active: true,
14383
+ mode: "autopilot",
14384
+ current_phase: "ralplan",
14385
+ session_id: sessionId,
14386
+ });
14387
+ await writeJson(join(stateDir, "sessions", sessionId, "run-state.json"), {
14388
+ version: 1,
14389
+ active: false,
14390
+ mode: "autopilot",
14391
+ outcome: "finish",
14392
+ lifecycle_outcome: "finished",
14393
+ current_phase: "complete",
14394
+ completed_at: "2026-05-30T00:00:00.000Z",
14395
+ updated_at: "2026-05-30T00:00:00.000Z",
14396
+ });
14397
+
14398
+ const result = await dispatchCodexNativeHook(
14399
+ {
14400
+ hook_event_name: "PreToolUse",
14401
+ cwd,
14402
+ session_id: nativeSessionId,
14403
+ thread_id: "thread-autopilot-ralplan-native-map-terminal",
14404
+ tool_name: "Edit",
14405
+ tool_input: { file_path: "src/runtime.ts" },
14406
+ },
14407
+ { cwd },
14408
+ );
14409
+
14410
+ assert.equal(result.omxEventName, "pre-tool-use");
14411
+ assert.equal(result.outputJson, null);
14412
+ } finally {
14413
+ await rm(cwd, { recursive: true, force: true });
14414
+ }
14415
+ });
14416
+
14417
+ it("does not block unrelated native Codex ids when current OMX session mapping does not match", async () => {
14418
+ const cwd = await mkdtemp(join(tmpdir(), "omx-native-hook-ralplan-native-map-unrelated-"));
14419
+ try {
14420
+ const stateDir = join(cwd, ".omx", "state");
14421
+ const sessionId = "sess-ralplan-native-map-owner";
14422
+ const ownerNativeSessionId = "019e-ralplan-native-owner";
14423
+ await writeNativeMappedSessionState(cwd, stateDir, sessionId, ownerNativeSessionId);
14424
+ await writeSessionSkillActiveState(stateDir, sessionId, "ralplan", "planning");
14425
+ await writeJson(join(stateDir, "sessions", sessionId, "ralplan-state.json"), {
14426
+ active: true,
14427
+ mode: "ralplan",
14428
+ current_phase: "planning",
14429
+ session_id: sessionId,
14430
+ });
14431
+
14432
+ const result = await dispatchCodexNativeHook(
14433
+ {
14434
+ hook_event_name: "PreToolUse",
14435
+ cwd,
14436
+ session_id: "019e-unrelated-native-session",
14437
+ thread_id: "thread-ralplan-native-map-unrelated",
14438
+ tool_name: "Edit",
14439
+ tool_input: { file_path: "src/runtime.ts" },
14440
+ },
14441
+ { cwd },
14442
+ );
14443
+
14444
+ assert.equal(result.omxEventName, "pre-tool-use");
14445
+ assert.equal(result.outputJson, null);
14446
+ } finally {
14447
+ await rm(cwd, { recursive: true, force: true });
14448
+ }
14449
+ });
14450
+
14451
+ it("blocks mapped Autopilot ralplan writes from the authoritative team state root", async () => {
14452
+ const cwd = await mkdtemp(join(tmpdir(), "omx-native-hook-autopilot-ralplan-team-root-"));
14453
+ const teamStateRoot = await mkdtemp(join(tmpdir(), "omx-native-hook-team-root-"));
14454
+ const previousTeamStateRoot = process.env.OMX_TEAM_STATE_ROOT;
14455
+ try {
14456
+ process.env.OMX_TEAM_STATE_ROOT = teamStateRoot;
14457
+ const stateDir = teamStateRoot;
14458
+ const sessionId = "sess-autopilot-ralplan-team-root";
14459
+ const nativeSessionId = "019e-autopilot-ralplan-team-root";
14460
+ await writeNativeMappedSessionState(cwd, stateDir, sessionId, nativeSessionId);
14461
+ await writeSessionSkillActiveState(stateDir, sessionId, "autopilot", "ralplan");
14462
+ await writeJson(join(stateDir, "sessions", sessionId, "autopilot-state.json"), {
14463
+ active: true,
14464
+ mode: "autopilot",
14465
+ current_phase: "ralplan",
14466
+ session_id: sessionId,
14467
+ });
14468
+
14469
+ const result = await dispatchCodexNativeHook(
14470
+ {
14471
+ hook_event_name: "PreToolUse",
14472
+ cwd,
14473
+ session_id: nativeSessionId,
14474
+ thread_id: "thread-autopilot-ralplan-team-root",
14475
+ tool_name: "Edit",
14476
+ tool_input: { file_path: "src/runtime.ts" },
14477
+ },
14478
+ { cwd },
14479
+ );
14480
+
14481
+ assert.equal(result.omxEventName, "pre-tool-use");
14482
+ assert.equal(result.outputJson?.decision, "block");
14483
+ assert.match(String(result.outputJson?.reason ?? ""), /(?:Ralplan|Autopilot planning) is active .*implementation\/write tools are blocked/i);
14484
+ assert.equal(existsSync(join(cwd, ".omx", "state", "session.json")), false);
14485
+ } finally {
14486
+ if (typeof previousTeamStateRoot === "string") process.env.OMX_TEAM_STATE_ROOT = previousTeamStateRoot;
14487
+ else delete process.env.OMX_TEAM_STATE_ROOT;
14488
+ await rm(cwd, { recursive: true, force: true });
14489
+ await rm(teamStateRoot, { recursive: true, force: true });
14490
+ }
14491
+ });
14492
+
14493
+ it("does not block unrelated native Codex ids from the authoritative team state root", async () => {
14494
+ const cwd = await mkdtemp(join(tmpdir(), "omx-native-hook-ralplan-team-root-unrelated-"));
14495
+ const teamStateRoot = await mkdtemp(join(tmpdir(), "omx-native-hook-team-root-unrelated-"));
14496
+ const previousTeamStateRoot = process.env.OMX_TEAM_STATE_ROOT;
14497
+ try {
14498
+ process.env.OMX_TEAM_STATE_ROOT = teamStateRoot;
14499
+ const stateDir = teamStateRoot;
14500
+ const sessionId = "sess-ralplan-team-root-owner";
14501
+ const nativeSessionId = "019e-ralplan-team-root-owner";
14502
+ await writeNativeMappedSessionState(cwd, stateDir, sessionId, nativeSessionId);
14503
+ await writeSessionSkillActiveState(stateDir, sessionId, "ralplan", "planning");
14504
+ await writeJson(join(stateDir, "sessions", sessionId, "ralplan-state.json"), {
14505
+ active: true,
14506
+ mode: "ralplan",
14507
+ current_phase: "planning",
14508
+ session_id: sessionId,
14509
+ });
14510
+
14511
+ const result = await dispatchCodexNativeHook(
14512
+ {
14513
+ hook_event_name: "PreToolUse",
14514
+ cwd,
14515
+ session_id: "019e-unrelated-team-root-native",
14516
+ thread_id: "thread-ralplan-team-root-unrelated",
14517
+ tool_name: "Edit",
14518
+ tool_input: { file_path: "src/runtime.ts" },
14519
+ },
14520
+ { cwd },
14521
+ );
14522
+
14523
+ assert.equal(result.omxEventName, "pre-tool-use");
14524
+ assert.equal(result.outputJson, null);
14525
+ } finally {
14526
+ if (typeof previousTeamStateRoot === "string") process.env.OMX_TEAM_STATE_ROOT = previousTeamStateRoot;
14527
+ else delete process.env.OMX_TEAM_STATE_ROOT;
14528
+ await rm(cwd, { recursive: true, force: true });
14529
+ await rm(teamStateRoot, { recursive: true, force: true });
14530
+ }
14531
+ });
14532
+
13934
14533
  it("allows ralplan planning artifact writes without execution handoff", async () => {
13935
14534
  const cwd = await mkdtemp(join(tmpdir(), "omx-native-hook-ralplan-pretool-artifact-"));
13936
14535
  try {
@@ -14006,7 +14605,7 @@ exit 0
14006
14605
 
14007
14606
  assert.equal(result.omxEventName, "pre-tool-use");
14008
14607
  assert.equal(result.outputJson?.decision, "block");
14009
- assert.match(String(result.outputJson?.reason ?? ""), /Ralplan is active .*implementation\/write tools are blocked/i);
14608
+ assert.match(String(result.outputJson?.reason ?? ""), /(?:Ralplan|Autopilot planning) is active .*implementation\/write tools are blocked/i);
14010
14609
  } finally {
14011
14610
  await rm(cwd, { recursive: true, force: true });
14012
14611
  }
@@ -14730,6 +15329,80 @@ describe("codex native hook triage integration", () => {
14730
15329
  }
14731
15330
  });
14732
15331
 
15332
+ it("omits Team handoff guidance from autopilot prompt context when Team mode is disabled", async () => {
15333
+ const cwd = await mkdtemp(join(tmpdir(), "omx-autopilot-observable-no-team-"));
15334
+ try {
15335
+ await mkdir(join(cwd, ".omx", "state"), { recursive: true });
15336
+ await writeJson(join(cwd, ".omx", "setup-scope.json"), {
15337
+ scope: "project",
15338
+ teamMode: "disabled",
15339
+ });
15340
+ await writeSessionStart(cwd, "sess-autopilot-observable-no-team");
15341
+
15342
+ const result = await dispatchCodexNativeHook(
15343
+ {
15344
+ hook_event_name: "UserPromptSubmit",
15345
+ cwd,
15346
+ session_id: "sess-autopilot-observable-no-team",
15347
+ thread_id: "thread-autopilot-observable-no-team",
15348
+ turn_id: "turn-autopilot-observable-no-team",
15349
+ prompt: "$autopilot implement issue #2430",
15350
+ },
15351
+ { cwd },
15352
+ );
15353
+
15354
+ assert.equal(result.skillState?.skill, "autopilot");
15355
+ const additionalContext = String(
15356
+ (result.outputJson as { hookSpecificOutput?: { additionalContext?: string } })?.hookSpecificOutput?.additionalContext ?? "",
15357
+ );
15358
+ assert.match(additionalContext, /detected workflow keyword "\$autopilot" -> autopilot/);
15359
+ assert.match(additionalContext, /\$deep-interview -> \$ralplan -> \$ultragoal -> \$code-review -> \$ultraqa/);
15360
+ assert.doesNotMatch(additionalContext, /\$team/);
15361
+ assert.equal(existsSync(join(cwd, ".omx", "state", "team-state.json")), false);
15362
+ } finally {
15363
+ await rm(cwd, { recursive: true, force: true });
15364
+ }
15365
+ });
15366
+
15367
+ it("ignores disabled $team before outside-tmux Team blocking so later workflows can activate", async () => {
15368
+ const cwd = await mkdtemp(join(tmpdir(), "omx-native-hook-disabled-team-primary-"));
15369
+ try {
15370
+ await mkdir(join(cwd, ".omx", "state"), { recursive: true });
15371
+ await writeJson(join(cwd, ".omx", "setup-scope.json"), {
15372
+ scope: "project",
15373
+ teamMode: "disabled",
15374
+ });
15375
+ await writeSessionStart(cwd, "sess-disabled-team-primary");
15376
+
15377
+ const result = await dispatchCodexNativeHook(
15378
+ {
15379
+ hook_event_name: "UserPromptSubmit",
15380
+ cwd,
15381
+ session_id: "sess-disabled-team-primary",
15382
+ thread_id: "thread-disabled-team-primary",
15383
+ turn_id: "turn-disabled-team-primary",
15384
+ prompt: "$team $ralph fix this",
15385
+ },
15386
+ { cwd },
15387
+ );
15388
+
15389
+ assert.equal(result.skillState?.skill, "ralph");
15390
+ assert.equal(result.skillState?.transition_error, undefined);
15391
+ assert.equal(existsSync(join(cwd, ".omx", "state", "team-state.json")), false);
15392
+ assert.equal(
15393
+ existsSync(join(cwd, ".omx", "state", "sessions", "sess-disabled-team-primary", "ralph-state.json")),
15394
+ true,
15395
+ );
15396
+ const additionalContext = String(
15397
+ (result.outputJson as { hookSpecificOutput?: { additionalContext?: string } })?.hookSpecificOutput?.additionalContext ?? "",
15398
+ );
15399
+ assert.match(additionalContext, /detected workflow keyword "\$ralph" -> ralph/);
15400
+ assert.doesNotMatch(additionalContext, /Codex App\/native outside-tmux sessions cannot activate/);
15401
+ } finally {
15402
+ await rm(cwd, { recursive: true, force: true });
15403
+ }
15404
+ });
15405
+
14733
15406
  it("makes bare autopilot command activation observable in state and prompt guidance", async () => {
14734
15407
  const cwd = await mkdtemp(join(tmpdir(), "omx-autopilot-bare-observable-"));
14735
15408
  try {