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
@@ -48,6 +48,7 @@ import {
48
48
  type SkillActiveState,
49
49
  } from "../hooks/keyword-detector.js";
50
50
  import { buildDeepInterviewConfigInstruction } from "../hooks/deep-interview-config-instruction.js";
51
+ import { readTeamModeConfig } from "../config/team-mode.js";
51
52
  import {
52
53
  detectNativeStopStallPattern,
53
54
  loadAutoNudgeConfig,
@@ -82,6 +83,7 @@ import {
82
83
  onSessionStart as buildWikiSessionStartContext,
83
84
  } from "../wiki/lifecycle.js";
84
85
  import { readAutoresearchCompletionStatus, readAutoresearchModeStateForActiveDecision } from "../autoresearch/skill-validation.js";
86
+ import { normalizeAutopilotPhase } from "../autopilot/fsm.js";
85
87
  import { readRunState } from "../runtime/run-state.js";
86
88
  import { evaluateRalphCompletionAuditEvidence, isRalphCompletePhase } from "../ralph/completion-audit.js";
87
89
  import { getRunContinuationSnapshot, shouldContinueRun } from "../runtime/run-loop.js";
@@ -189,6 +191,25 @@ function resolveHudReconcileSessionId(
189
191
  return canonicalSessionId || sessionIdForState || undefined;
190
192
  }
191
193
 
194
+ function resolveHudReconcileSessionIds(
195
+ currentSessionState: SessionState | null,
196
+ canonicalSessionId: string | null,
197
+ sessionIdForState: string | null,
198
+ nativeSessionId: string | null,
199
+ ): string[] {
200
+ const ownerOmxSessionId = safeString(currentSessionState?.owner_omx_session_id).trim();
201
+ return uniqueNonEmpty([
202
+ resolveHudReconcileSessionId(currentSessionState, canonicalSessionId, sessionIdForState),
203
+ canonicalSessionId ?? undefined,
204
+ sessionIdForState ?? undefined,
205
+ nativeSessionId ?? undefined,
206
+ safeString(currentSessionState?.session_id),
207
+ safeString(currentSessionState?.native_session_id),
208
+ OMX_OWNER_SESSION_ID_PATTERN.test(ownerOmxSessionId) ? ownerOmxSessionId : undefined,
209
+ safeString(currentSessionState?.owner_codex_session_id),
210
+ ]);
211
+ }
212
+
192
213
  function safeContextSnippet(value: unknown, maxLength = 300): string {
193
214
  const text = safeString(value).replace(/\s+/g, " ").trim();
194
215
  if (text.length <= maxLength) return text;
@@ -1730,6 +1751,7 @@ function buildNativeOutsideTmuxTeamPromptBlockState(
1730
1751
  threadId?: string,
1731
1752
  turnId?: string,
1732
1753
  ): SkillActiveState | null {
1754
+ if (!readTeamModeConfig(cwd).enabled) return null;
1733
1755
  const match = detectPrimaryKeyword(prompt);
1734
1756
  if (match?.skill !== "team") return null;
1735
1757
 
@@ -1765,11 +1787,14 @@ function buildSkillStateCliInstruction(mode: string, statePath: string): string
1765
1787
 
1766
1788
  function buildAutopilotPromptActivationNote(
1767
1789
  skillState?: SkillActiveState | null,
1768
- options: { markedQuestionAnswer?: boolean } = {},
1790
+ options: { markedQuestionAnswer?: boolean; cwd?: string } = {},
1769
1791
  ): string | null {
1770
1792
  if (skillState?.initialized_mode !== "autopilot") return null;
1793
+ const teamHandoff = readTeamModeConfig(options.cwd).enabled
1794
+ ? " (+ $team if needed)"
1795
+ : "";
1771
1796
  return [
1772
- "Autopilot protocol: the durable default chain is $deep-interview -> $ralplan -> $ultragoal (+ $team if needed) -> $code-review -> $ultraqa (deep-interview -> ralplan -> ultragoal -> code-review -> ultraqa).",
1797
+ `Autopilot protocol: the durable default chain is $deep-interview -> $ralplan -> $ultragoal${teamHandoff} -> $code-review -> $ultraqa (deep-interview -> ralplan -> ultragoal -> code-review -> ultraqa).`,
1773
1798
  "Start/resume at current_phase=deep-interview unless the task is clear and bounded; if deep-interview is intentionally skipped, persist and state an explicit deep_interview_gate.skip_reason before moving to ralplan.",
1774
1799
  "Deep-interview is a structured question chain, not a one-question gate: after an omx question answer, re-score ambiguity against the active threshold, treat max_rounds as a cap, and crystallize once ambiguity is at or below threshold and readiness gates pass.",
1775
1800
  options.markedQuestionAnswer
@@ -1782,6 +1807,12 @@ function buildAutopilotPromptActivationNote(
1782
1807
  ].filter(Boolean).join(" ");
1783
1808
  }
1784
1809
 
1810
+ function formatExecutionHandoffList(cwd: string): string {
1811
+ return readTeamModeConfig(cwd).enabled
1812
+ ? "`$ultragoal`, `$team`, or `$ralph`"
1813
+ : "`$ultragoal` or `$ralph`";
1814
+ }
1815
+
1785
1816
  function buildAdditionalContextMessage(
1786
1817
  prompt: string,
1787
1818
  skillState?: SkillActiveState | null,
@@ -1790,8 +1821,9 @@ function buildAdditionalContextMessage(
1790
1821
  ): string | null {
1791
1822
  if (!prompt) return null;
1792
1823
  const promptPriorityMessage = buildPromptPriorityMessage(prompt);
1793
- const matches = detectKeywords(prompt);
1794
- const match = detectPrimaryKeyword(prompt);
1824
+ const teamMode = readTeamModeConfig(cwd);
1825
+ const matches = detectKeywords(prompt).filter((entry) => teamMode.enabled || entry.skill !== "team");
1826
+ const match = matches[0] ?? null;
1795
1827
  if (!match) {
1796
1828
  const continuedSkill = safeString(skillState?.skill).trim();
1797
1829
  if (!continuedSkill) return promptPriorityMessage;
@@ -1800,7 +1832,7 @@ function buildAdditionalContextMessage(
1800
1832
  : null;
1801
1833
  const deepInterviewConfigPromptActivationNote = buildDeepInterviewConfigInstruction(cwd, skillState);
1802
1834
  const markedQuestionAnswer = /^\s*\[omx question answered\]/i.test(prompt);
1803
- const autopilotPromptActivationNote = buildAutopilotPromptActivationNote(skillState, { markedQuestionAnswer });
1835
+ const autopilotPromptActivationNote = buildAutopilotPromptActivationNote(skillState, { markedQuestionAnswer, cwd });
1804
1836
  return [
1805
1837
  `OMX native UserPromptSubmit continued active workflow skill "${continuedSkill}".`,
1806
1838
  promptPriorityMessage,
@@ -1826,7 +1858,7 @@ function buildAdditionalContextMessage(
1826
1858
  ? buildDeepInterviewQuestionBridgeInstruction(cwd, payload)
1827
1859
  : null;
1828
1860
  const deepInterviewConfigPromptActivationNote = buildDeepInterviewConfigInstruction(cwd, skillState);
1829
- const autopilotPromptActivationNote = buildAutopilotPromptActivationNote(skillState, { markedQuestionAnswer: true });
1861
+ const autopilotPromptActivationNote = buildAutopilotPromptActivationNote(skillState, { markedQuestionAnswer: true, cwd });
1830
1862
  return [
1831
1863
  `OMX native UserPromptSubmit continued active workflow skill "${continuedSkill}"; workflow-like tokens inside the marked omx question answer are treated as answer text, not a new workflow activation.`,
1832
1864
  promptPriorityMessage,
@@ -1859,7 +1891,7 @@ function buildAdditionalContextMessage(
1859
1891
  const ultragoalPromptActivationNote = match.skill === "ultragoal"
1860
1892
  ? "Ultragoal protocol: use `omx ultragoal create-goals` / `complete-goals` / `checkpoint` for `.omx/ultragoal` artifacts, then use Codex goal model tools only from the active agent handoff (`get_goal`, `create_goal`, `update_goal`) and never overwrite a different active Codex goal. Ultragoal does not call `/goal clear`; for multiple sequential ultragoal runs in one Codex session/thread, manually clear the completed Codex goal in the UI before creating the next aggregate goal."
1861
1893
  : null;
1862
- const autopilotPromptActivationNote = buildAutopilotPromptActivationNote(skillState);
1894
+ const autopilotPromptActivationNote = buildAutopilotPromptActivationNote(skillState, { cwd });
1863
1895
  const combinedTransitionMessage = (() => {
1864
1896
  if (!skillState?.transition_message) return null;
1865
1897
  if (matches.length <= 1 || activeSkills.length <= 1) return skillState.transition_message;
@@ -2464,8 +2496,11 @@ function readPayloadTurnId(payload: CodexHookPayload): string {
2464
2496
  async function resolveInternalSessionIdForPayload(
2465
2497
  cwd: string,
2466
2498
  payloadSessionId: string,
2499
+ stateDir?: string,
2467
2500
  ): Promise<string> {
2468
- const currentSession = await readUsableSessionState(cwd);
2501
+ const currentSession = stateDir
2502
+ ? await readUsableSessionStateFromStateDir(cwd, stateDir)
2503
+ : await readUsableSessionState(cwd);
2469
2504
  const canonicalSessionId = safeString(currentSession?.session_id).trim();
2470
2505
  if (!canonicalSessionId) return payloadSessionId;
2471
2506
 
@@ -2476,6 +2511,22 @@ async function resolveInternalSessionIdForPayload(
2476
2511
  return payloadSessionId;
2477
2512
  }
2478
2513
 
2514
+ async function readUsableSessionStateFromStateDir(
2515
+ cwd: string,
2516
+ stateDir: string,
2517
+ ): Promise<SessionState | null> {
2518
+ const sessionPath = join(stateDir, "session.json");
2519
+ if (!existsSync(sessionPath)) return null;
2520
+
2521
+ try {
2522
+ const content = await readFile(sessionPath, "utf-8");
2523
+ const state = JSON.parse(content) as SessionState;
2524
+ return isSessionStateUsable(state, cwd) ? state : null;
2525
+ } catch {
2526
+ return null;
2527
+ }
2528
+ }
2529
+
2479
2530
  async function readStopSessionPinnedState(
2480
2531
  fileName: string,
2481
2532
  cwd: string,
@@ -2514,7 +2565,8 @@ const PLANNING_MODE_IMPLEMENTATION_TOOL_NAMES = new Set([
2514
2565
  const DEEP_INTERVIEW_IMPLEMENTATION_TOOL_NAMES = PLANNING_MODE_IMPLEMENTATION_TOOL_NAMES;
2515
2566
 
2516
2567
  const RALPLAN_EXECUTION_HANDOFF_SKILLS = new Set([
2517
- "autopilot",
2568
+ // Autopilot is intentionally excluded: it supervises planning phases such as
2569
+ // ralplan/replan and is not by itself an execution authorization.
2518
2570
  "autoresearch",
2519
2571
  "ralph",
2520
2572
  "team",
@@ -2541,12 +2593,12 @@ function isActiveRalplanPhase(state: Record<string, unknown> | null): boolean {
2541
2593
  return true;
2542
2594
  }
2543
2595
 
2544
- function isActiveAutopilotRalplanPhase(state: Record<string, unknown> | null): boolean {
2545
- if (!state || state.active !== true) return false;
2546
- const mode = safeString(state.mode).trim();
2547
- if (mode && mode !== "autopilot") return false;
2548
- const phase = safeString(state.current_phase ?? state.currentPhase).trim().toLowerCase();
2549
- return phase === "ralplan";
2596
+ function isAutopilotRalplanLikePhase(phase: string): boolean {
2597
+ return normalizeAutopilotPhase(phase) === "ralplan";
2598
+ }
2599
+
2600
+ function canAutopilotSkillMirrorSupplyRalplanPhase(phase: string): boolean {
2601
+ return phase === "" || normalizeAutopilotPhase(phase) === "ralplan";
2550
2602
  }
2551
2603
 
2552
2604
  function hasExplicitExecutionHandoffSkill(
@@ -2649,7 +2701,7 @@ async function readActiveDeepInterviewStateForPreToolUse(
2649
2701
  const canonicalState = sessionId
2650
2702
  ? await readVisibleSkillActiveStateForStateDir(stateDir, sessionId)
2651
2703
  : await readSkillActiveState(join(stateDir, SKILL_ACTIVE_STATE_FILE));
2652
- if (!canonicalState) return modeState;
2704
+ if (!canonicalState) return null;
2653
2705
  const hasActiveDeepInterviewSkill = listActiveSkills(canonicalState).some((entry) => (
2654
2706
  entry.skill === "deep-interview"
2655
2707
  && matchesSkillStopContext(entry, canonicalState, sessionId, threadId)
@@ -2671,7 +2723,7 @@ async function readActiveRalplanStateForPreToolUse(
2671
2723
  : await readSkillActiveState(join(stateDir, SKILL_ACTIVE_STATE_FILE));
2672
2724
  if (isActiveRalplanPhase(modeState) && modeState && modeStateMatchesSkillStopContext(modeState, cwd, sessionId)) {
2673
2725
  if (hasExplicitExecutionHandoffSkill(canonicalState, sessionId, threadId)) return null;
2674
- if (!canonicalState) return modeState;
2726
+ if (!canonicalState) return null;
2675
2727
  const hasActiveRalplanSkill = listActiveSkills(canonicalState).some((entry) => (
2676
2728
  entry.skill === "ralplan"
2677
2729
  && matchesSkillStopContext(entry, canonicalState, sessionId, threadId)
@@ -2682,15 +2734,26 @@ async function readActiveRalplanStateForPreToolUse(
2682
2734
  const autopilotState = sessionId
2683
2735
  ? await readStopSessionPinnedState("autopilot-state.json", cwd, sessionId, stateDir)
2684
2736
  : await readJsonIfExists(join(stateDir, "autopilot-state.json"));
2685
- if (!isActiveAutopilotRalplanPhase(autopilotState) || !autopilotState) return null;
2737
+ if (!autopilotState || autopilotState.active !== true) return null;
2738
+ const autopilotMode = safeString(autopilotState.mode).trim();
2739
+ if (autopilotMode && autopilotMode !== "autopilot") return null;
2686
2740
  if (!modeStateMatchesSkillStopContext(autopilotState, cwd, sessionId)) return null;
2687
2741
  const terminalAutopilotRunState = await readCanonicalTerminalRunStateForStop(cwd, sessionId, "autopilot");
2688
2742
  if (terminalAutopilotRunState) return null;
2689
- if (!canonicalState) return autopilotState;
2743
+ if (!canonicalState) return null;
2690
2744
  const hasActiveAutopilotSkill = listActiveSkills(canonicalState).some((entry) => (
2691
2745
  entry.skill === "autopilot"
2692
2746
  && matchesSkillStopContext(entry, canonicalState, sessionId, threadId)
2693
2747
  ));
2748
+ if (!hasActiveAutopilotSkill) return null;
2749
+ const autopilotStatePhase = safeString(autopilotState.current_phase ?? autopilotState.currentPhase).trim().toLowerCase();
2750
+ if (!canAutopilotSkillMirrorSupplyRalplanPhase(autopilotStatePhase)) return null;
2751
+ const hasRalplanScopedAutopilotSkill = listActiveSkills(canonicalState).some((entry) => (
2752
+ entry.skill === "autopilot"
2753
+ && isAutopilotRalplanLikePhase(safeString(entry.phase).trim().toLowerCase())
2754
+ && matchesSkillStopContext(entry, canonicalState, sessionId, threadId)
2755
+ ));
2756
+ if (!isAutopilotRalplanLikePhase(autopilotStatePhase) && !hasRalplanScopedAutopilotSkill) return null;
2694
2757
  return hasActiveAutopilotSkill ? autopilotState : null;
2695
2758
  }
2696
2759
 
@@ -2705,8 +2768,9 @@ async function buildRalplanPreToolUseBoundaryOutput(
2705
2768
  payload: CodexHookPayload,
2706
2769
  cwd: string,
2707
2770
  stateDir: string,
2771
+ resolvedSessionId?: string,
2708
2772
  ): Promise<Record<string, unknown> | null> {
2709
- const sessionId = readPayloadSessionId(payload);
2773
+ const sessionId = safeString(resolvedSessionId ?? readPayloadSessionId(payload)).trim();
2710
2774
  const threadId = readPayloadThreadId(payload);
2711
2775
  const activeState = await readActiveRalplanStateForPreToolUse(cwd, stateDir, sessionId, threadId);
2712
2776
  if (!activeState) return null;
@@ -2726,13 +2790,21 @@ async function buildRalplanPreToolUseBoundaryOutput(
2726
2790
  if (!blocked) return null;
2727
2791
 
2728
2792
  const phase = formatPhase(activeState.current_phase ?? activeState.currentPhase, "planning");
2793
+ const activeMode = safeString(activeState.mode).trim().toLowerCase();
2794
+ const planningModeLabel = activeMode === "autopilot" ? "Autopilot planning" : "Ralplan";
2795
+ const planningModeDescription = activeMode === "autopilot"
2796
+ ? "Autopilot is supervising a planning phase"
2797
+ : "Ralplan is consensus-planning mode";
2729
2798
  return {
2730
2799
  decision: "block",
2731
- reason: `Ralplan is active (phase: ${phase}); implementation/write tools are blocked until an explicit execution handoff workflow is activated.`,
2800
+ reason: `${planningModeLabel} is active (phase: ${phase}); implementation/write tools are blocked until an explicit execution handoff workflow is activated.`,
2732
2801
  hookSpecificOutput: {
2733
2802
  hookEventName: "PreToolUse",
2734
2803
  additionalContext:
2735
- "Ralplan is consensus-planning mode. Write only planning artifacts under `.omx/context/`, `.omx/plans/`, `.omx/specs/`, or required `.omx/state/` files. Do not edit implementation files or run implementation-focused writes from ralplan. To execute, first process an explicit handoff such as `$ultragoal`, `$team`, or `$ralph`, which must emit terminal ralplan state before implementation begins.",
2804
+ `${planningModeDescription}. `
2805
+ + "Write only planning artifacts under `.omx/context/`, `.omx/plans/`, `.omx/specs/`, or required `.omx/state/` files. "
2806
+ + "Do not edit implementation files or run implementation-focused writes from planning phases. "
2807
+ + `To execute, first process an explicit handoff such as ${formatExecutionHandoffList(cwd)}, which must emit terminal planning state before implementation begins.`,
2736
2808
  },
2737
2809
  };
2738
2810
  }
@@ -2741,8 +2813,9 @@ async function buildDeepInterviewPreToolUseBoundaryOutput(
2741
2813
  payload: CodexHookPayload,
2742
2814
  cwd: string,
2743
2815
  stateDir: string,
2816
+ resolvedSessionId?: string,
2744
2817
  ): Promise<Record<string, unknown> | null> {
2745
- const sessionId = readPayloadSessionId(payload);
2818
+ const sessionId = safeString(resolvedSessionId ?? readPayloadSessionId(payload)).trim();
2746
2819
  const threadId = readPayloadThreadId(payload);
2747
2820
  const activeState = await readActiveDeepInterviewStateForPreToolUse(cwd, stateDir, sessionId, threadId);
2748
2821
  if (!activeState) return null;
@@ -2768,7 +2841,7 @@ async function buildDeepInterviewPreToolUseBoundaryOutput(
2768
2841
  hookSpecificOutput: {
2769
2842
  hookEventName: "PreToolUse",
2770
2843
  additionalContext:
2771
- "Deep-interview is requirements/spec mode. Treat detailed user answers as interview/spec material, not implicit implementation authorization. You may write only deep-interview artifacts under `.omx/context/`, `.omx/interviews/`, `.omx/specs/`, or required `.omx/state/` files. To implement, first ask for or process an explicit transition such as `$ralplan`, `$autopilot`, `$ralph`, `$team`, or `$ultragoal`.",
2844
+ `Deep-interview is requirements/spec mode. Treat detailed user answers as interview/spec material, not implicit implementation authorization. You may write only deep-interview artifacts under \`.omx/context/\`, \`.omx/interviews/\`, \`.omx/specs/\`, or required \`.omx/state/\` files. To implement, first ask for or process an explicit transition such as \`$ralplan\`, \`$autopilot\`, ${formatExecutionHandoffList(cwd)}.`,
2772
2845
  },
2773
2846
  };
2774
2847
  }
@@ -3027,6 +3100,7 @@ async function reconcileStaleRootSkillActiveStateForStop(
3027
3100
  function buildRalplanContinuationStatus(
3028
3101
  blocker: { phase: string; latestPlanPath?: string; planningComplete?: boolean; runOutcome?: string },
3029
3102
  activeSubagentCount: number,
3103
+ cwd: string,
3030
3104
  ): { reason: string; systemMessage: string; stopReasonSuffix: string } {
3031
3105
  const phase = blocker.phase || "planning";
3032
3106
  const artifact = blocker.latestPlanPath
@@ -3062,7 +3136,7 @@ function buildRalplanContinuationStatus(
3062
3136
  }
3063
3137
 
3064
3138
  const completeHint = blocker.planningComplete
3065
- ? " The planning artifacts are present; if consensus is approved, emit terminal ralplan complete/approved handoff state and stop planning. Implementation must wait for an explicit $ultragoal, $team, or $ralph handoff."
3139
+ ? ` The planning artifacts are present; if consensus is approved, emit terminal ralplan complete/approved handoff state and stop planning. Implementation must wait for an explicit ${formatExecutionHandoffList(cwd).replaceAll("`", "")} handoff.`
3066
3140
  : "";
3067
3141
 
3068
3142
  return {
@@ -3282,6 +3356,11 @@ async function maybeBuildOrdinaryStopNoProgressOutput(
3282
3356
  stateDir: string,
3283
3357
  canonicalSessionId?: string,
3284
3358
  ): Promise<Record<string, unknown> | null> {
3359
+ const lastAssistantMessage = safeString(
3360
+ payload.last_assistant_message ?? payload.lastAssistantMessage,
3361
+ ).trim();
3362
+ if (!lastAssistantMessage) return null;
3363
+
3285
3364
  const statePath = join(stateDir, NATIVE_STOP_STATE_FILE);
3286
3365
  const state = await readJsonIfExists(statePath) ?? {};
3287
3366
  const sessions = safeObject(state.sessions);
@@ -3313,9 +3392,6 @@ async function maybeBuildOrdinaryStopNoProgressOutput(
3313
3392
  await mkdir(stateDir, { recursive: true });
3314
3393
  await writeFile(statePath, JSON.stringify({ ...state, sessions }, null, 2));
3315
3394
 
3316
- const stopHookActive = payload.stop_hook_active === true || payload.stopHookActive === true;
3317
- if (!stopHookActive) return null;
3318
-
3319
3395
  const maxRepeats = parseBoundedPositiveInteger(
3320
3396
  process.env.OMX_NATIVE_STOP_NO_PROGRESS_MAX_REPEATS,
3321
3397
  ORDINARY_STOP_NO_PROGRESS_DEFAULT_MAX_REPEATS,
@@ -3520,7 +3596,7 @@ async function buildSkillStopOutput(
3520
3596
  const activeSubagentCount = subagentSummary?.activeSubagentThreadIds.length ?? 0;
3521
3597
 
3522
3598
  if (blocker.skill === "ralplan") {
3523
- const status = buildRalplanContinuationStatus(blocker, activeSubagentCount);
3599
+ const status = buildRalplanContinuationStatus(blocker, activeSubagentCount, cwd);
3524
3600
  return {
3525
3601
  decision: "block",
3526
3602
  reason: status.reason,
@@ -3978,7 +4054,9 @@ export async function dispatchCodexNativeHook(
3978
4054
  // Native hooks must use the same authoritative runtime state root as HUD/MCP
3979
4055
  // when boxed/team roots are active; do not bypass it with cwd/.omx/state.
3980
4056
  const stateDir = getBaseStateDir(cwd);
3981
- await mkdir(stateDir, { recursive: true });
4057
+ if (hookEventName !== "Stop") {
4058
+ await mkdir(stateDir, { recursive: true });
4059
+ }
3982
4060
 
3983
4061
  const omxEventName = mapCodexHookEventToOmxEvent(hookEventName);
3984
4062
  let skillState: SkillActiveState | null = null;
@@ -4179,16 +4257,23 @@ export async function dispatchCodexNativeHook(
4179
4257
  triageAdditionalContext = null;
4180
4258
  }
4181
4259
  }
4260
+ const skipHudReconcileForDoctorSmoke = process.env.OMX_NATIVE_HOOK_DOCTOR_SMOKE === "1";
4182
4261
  const skipHudReconcileForTeamWorkerPane = !isSubagentPromptSubmit
4183
4262
  && await isConfirmedTeamWorkerPromptSubmitPane(cwd).catch(() => false);
4184
- if (!skipHudReconcileForTeamWorkerPane) {
4263
+ if (!skipHudReconcileForDoctorSmoke && !skipHudReconcileForTeamWorkerPane) {
4185
4264
  const reconcileHudForPromptSubmitFn = options.reconcileHudForPromptSubmitFn ?? reconcileHudForPromptSubmit;
4186
4265
  const hudSessionId = resolveHudReconcileSessionId(
4187
4266
  currentSessionState,
4188
4267
  canonicalSessionId,
4189
4268
  sessionIdForState,
4190
4269
  );
4191
- await reconcileHudForPromptSubmitFn(cwd, { sessionId: hudSessionId }).catch(() => {});
4270
+ const hudSessionIds = resolveHudReconcileSessionIds(
4271
+ currentSessionState,
4272
+ canonicalSessionId,
4273
+ sessionIdForState,
4274
+ nativeSessionId,
4275
+ );
4276
+ await reconcileHudForPromptSubmitFn(cwd, { sessionId: hudSessionId, sessionIds: hudSessionIds }).catch(() => {});
4192
4277
  }
4193
4278
  }
4194
4279
 
@@ -4248,8 +4333,12 @@ export async function dispatchCodexNativeHook(
4248
4333
  };
4249
4334
  }
4250
4335
  } else if (hookEventName === "PreToolUse") {
4251
- outputJson = await buildDeepInterviewPreToolUseBoundaryOutput(payload, cwd, stateDir)
4252
- ?? await buildRalplanPreToolUseBoundaryOutput(payload, cwd, stateDir)
4336
+ const payloadSessionId = readPayloadSessionId(payload);
4337
+ const preToolUseSessionId = payloadSessionId
4338
+ ? await resolveInternalSessionIdForPayload(cwd, payloadSessionId, stateDir)
4339
+ : "";
4340
+ outputJson = await buildDeepInterviewPreToolUseBoundaryOutput(payload, cwd, stateDir, preToolUseSessionId)
4341
+ ?? await buildRalplanPreToolUseBoundaryOutput(payload, cwd, stateDir, preToolUseSessionId)
4253
4342
  ?? buildNativePreToolUseOutput(payload);
4254
4343
  } else if (hookEventName === "PostToolUse") {
4255
4344
  if (detectMcpTransportFailure(payload)) {
@@ -89,10 +89,8 @@ OMX_TEAM_WORKER_LAUNCH_ARGS="${OMX_TEAM_WORKER_LAUNCH_ARGS:--c model_reasoning_e
89
89
  TEAM_STARTED=0
90
90
  cleanup() {
91
91
  if ((TEAM_STARTED == 1)); then
92
- echo "[cleanup] shutting down team: $TEAM_NAME"
93
- omx team shutdown "$TEAM_NAME" >/dev/null 2>&1 || true
94
- echo "[cleanup] cleaning state for team: $TEAM_NAME"
95
- omx team api cleanup --input "{\"team_name\":\"$TEAM_NAME\"}" --json >/dev/null 2>&1 || true
92
+ echo "[cleanup] force-cleaning demo team: $TEAM_NAME"
93
+ omx team api cleanup --input "{\"team_name\":\"$TEAM_NAME\",\"force\":true,\"confirm_issues\":true}" --json >/dev/null 2>&1 || true
96
94
  fi
97
95
  }
98
96
  trap cleanup EXIT
@@ -106,7 +104,13 @@ echo "OMX_TEAM_WORKER_CLI_MAP=$OMX_TEAM_WORKER_CLI_MAP"
106
104
  echo "OMX_TEAM_WORKER_LAUNCH_ARGS=$OMX_TEAM_WORKER_LAUNCH_ARGS"
107
105
 
108
106
  echo "[1/8] start team (${WORKER_COUNT} mixed workers)"
109
- omx team "${WORKER_COUNT}:executor" "$TEAM_TASK"
107
+ START_OUTPUT="$(omx team "${WORKER_COUNT}:executor" "$TEAM_TASK")"
108
+ echo "$START_OUTPUT"
109
+ ACTUAL_TEAM_NAME="$(echo "$START_OUTPUT" | sed -nE 's/^Team started: ([^[:space:]]+)$/\1/p' | head -n 1)"
110
+ if [[ -n "$ACTUAL_TEAM_NAME" && "$ACTUAL_TEAM_NAME" != "$TEAM_NAME" ]]; then
111
+ echo "TEAM_NAME_RESOLVED=$ACTUAL_TEAM_NAME"
112
+ TEAM_NAME="$ACTUAL_TEAM_NAME"
113
+ fi
110
114
  TEAM_STARTED=1
111
115
 
112
116
  echo "[2/8] status"
@@ -174,8 +178,7 @@ SUMMARY_JSON="$(omx team api get-summary --input "$SUMMARY_INPUT" --json)"
174
178
  echo "$SUMMARY_JSON" | jq -e '.schema_version == "1.0" and .operation == "get-summary" and .ok == true' >/dev/null
175
179
 
176
180
  echo "[8/8] shutdown + cleanup"
177
- omx team shutdown "$TEAM_NAME"
178
- omx team api cleanup --input "{\"team_name\":\"$TEAM_NAME\"}" --json >/dev/null
181
+ omx team api cleanup --input "{\"team_name\":\"$TEAM_NAME\",\"force\":true,\"confirm_issues\":true}" --json >/dev/null
179
182
  TEAM_STARTED=0
180
183
 
181
184
  echo "E2E demo complete."
@@ -9,7 +9,7 @@ if (build.status !== 0) {
9
9
  }
10
10
 
11
11
  const test = spawnSync('node', [
12
- '--test',
12
+ 'dist/scripts/run-test-files.js',
13
13
  'dist/autoresearch/__tests__/contracts.test.js',
14
14
  'dist/autoresearch/__tests__/runtime.test.js',
15
15
  'dist/cli/__tests__/autoresearch.test.js',
@@ -269,7 +269,9 @@ export async function syncSkillStateFromTurn(stateDir, payload) {
269
269
 
270
270
 
271
271
  export async function isDeepInterviewStateActive(stateDir, sessionId) {
272
- const modeState = await readScopedJsonIfExists(stateDir, 'deep-interview-state.json', sessionId, null);
272
+ const modeState = typeof sessionId === 'string' && sessionId.trim()
273
+ ? await readScopedJsonIfExists(stateDir, 'deep-interview-state.json', sessionId, null)
274
+ : await readJsonIfExists(join(stateDir, 'deep-interview-state.json'), null);
273
275
  return Boolean(modeState && modeState.active === true);
274
276
  }
275
277
 
@@ -1,8 +1,7 @@
1
1
  import { existsSync } from 'fs';
2
2
  import { mkdir, readFile, readdir, rename, rm, stat, writeFile } from 'fs/promises';
3
- import { dirname, join, resolve } from 'path';
3
+ import { dirname, join } from 'path';
4
4
  import { captureTmuxPaneFromEnv } from '../../state/mode-state-context.js';
5
- import { isSessionStateUsable } from '../../hooks/session.js';
6
5
  import { resolveCodexPane } from '../tmux-hook-engine.js';
7
6
  import { safeString } from './utils.js';
8
7
 
@@ -222,15 +221,10 @@ function readSessionIdFromEnvironment(env: NodeJS.ProcessEnv = process.env): str
222
221
 
223
222
  async function readCurrentOmxSessionId(stateDir: string, env: NodeJS.ProcessEnv = process.env): Promise<string> {
224
223
  const envSessionId = readSessionIdFromEnvironment(env);
225
- if (envSessionId) {
226
- const envScopedDir = join(stateDir, 'sessions', envSessionId);
227
- if (existsSync(envScopedDir)) return envSessionId;
228
- }
224
+ if (envSessionId) return envSessionId;
229
225
 
230
- const cwd = resolve(stateDir, '..', '..');
231
226
  const session = await readJson(join(stateDir, 'session.json'));
232
227
  if (!session || typeof session !== 'object') return '';
233
- if (!isSessionStateUsable(session as any, cwd)) return '';
234
228
  const sessionId = safeString(session?.session_id).trim();
235
229
  return SESSION_ID_PATTERN.test(sessionId) ? sessionId : '';
236
230
  }
@@ -3,12 +3,10 @@
3
3
  */
4
4
 
5
5
  import { mkdir, readFile, readdir, writeFile } from 'fs/promises';
6
- import { dirname, join, resolve } from 'path';
7
- import { existsSync } from 'fs';
8
- import { isSessionStateUsable } from '../../hooks/session.js';
6
+ import { dirname, join } from 'path';
7
+ import { validateSessionId } from '../../mcp/state-paths.js';
9
8
  import { asNumber, safeString } from './utils.js';
10
9
 
11
- const SESSION_ID_PATTERN = /^[A-Za-z0-9_-]{1,64}$/;
12
10
 
13
11
  export { readdir };
14
12
 
@@ -25,46 +23,90 @@ function isSafeStateFileName(fileName: string): boolean {
25
23
  && !fileName.includes('\\');
26
24
  }
27
25
 
28
- function readSessionIdFromEnvironment(env: NodeJS.ProcessEnv = process.env): string | undefined {
29
- const candidates = [env.OMX_SESSION_ID, env.CODEX_SESSION_ID, env.SESSION_ID];
30
- for (const candidate of candidates) {
31
- const sessionId = safeString(candidate).trim();
32
- if (!SESSION_ID_PATTERN.test(sessionId)) continue;
33
- return sessionId;
26
+ interface SessionMetadata {
27
+ sessionId?: string;
28
+ nativeAliases: string[];
29
+ }
30
+
31
+ async function readSessionMetadataFromBaseStateDir(baseStateDir: string): Promise<SessionMetadata> {
32
+ const session = await readJsonIfExists(join(baseStateDir, 'session.json'), null);
33
+ let sessionId: string | undefined;
34
+ try {
35
+ sessionId = validateSessionId(session?.session_id);
36
+ } catch {
37
+ sessionId = undefined;
34
38
  }
35
- return undefined;
39
+ const nativeAliases = [
40
+ session?.native_session_id,
41
+ session?.codex_session_id,
42
+ session?.previous_native_session_id,
43
+ ]
44
+ .map((value) => safeString(value).trim())
45
+ .filter(Boolean);
46
+ return { sessionId, nativeAliases: [...new Set(nativeAliases)] };
36
47
  }
37
48
 
38
- export async function readCurrentSessionId(baseStateDir: string): Promise<string | undefined> {
39
- const envSessionId = readSessionIdFromEnvironment();
40
- if (envSessionId) {
41
- const envScopedDir = join(baseStateDir, 'sessions', envSessionId);
42
- if (existsSync(envScopedDir)) return envSessionId;
49
+ function readSessionIdFromEnvironment(): string | undefined {
50
+ for (const candidate of [process.env.OMX_SESSION_ID, process.env.CODEX_SESSION_ID, process.env.SESSION_ID]) {
51
+ if (typeof candidate !== 'string') continue;
52
+ const trimmed = candidate.trim();
53
+ if (!trimmed) continue;
54
+ try {
55
+ const sessionId = validateSessionId(trimmed);
56
+ if (sessionId) return sessionId;
57
+ } catch {
58
+ continue;
59
+ }
43
60
  }
61
+ return undefined;
62
+ }
44
63
 
45
- const cwd = resolve(baseStateDir, '..', '..');
46
- const session = await readJsonIfExists(join(baseStateDir, 'session.json'), null);
47
- if (!session || typeof session !== 'object') return undefined;
48
- if (!isSessionStateUsable(session, cwd)) return undefined;
49
- const sessionId = safeString(session?.session_id);
50
- return SESSION_ID_PATTERN.test(sessionId) ? sessionId : undefined;
64
+ function resolveCanonicalSessionId(candidate: string | undefined, metadata: SessionMetadata): string | undefined {
65
+ if (!candidate) return undefined;
66
+ return metadata.sessionId && metadata.nativeAliases.includes(candidate)
67
+ ? metadata.sessionId
68
+ : candidate;
51
69
  }
52
70
 
53
- export async function resolveScopedStateDir(
71
+ async function resolveBaseScopedStateDir(
54
72
  baseStateDir: string,
55
73
  explicitSessionId?: string,
56
74
  ): Promise<string> {
57
- const normalizedExplicit = safeString(explicitSessionId).trim();
58
- if (SESSION_ID_PATTERN.test(normalizedExplicit)) {
59
- return join(baseStateDir, 'sessions', normalizedExplicit);
60
- }
75
+ const normalizedExplicit = typeof explicitSessionId === 'string' && explicitSessionId.trim()
76
+ ? explicitSessionId.trim()
77
+ : undefined;
78
+ const validatedExplicit = validateSessionId(normalizedExplicit);
79
+ const metadata = await readSessionMetadataFromBaseStateDir(baseStateDir);
80
+ const sessionId = resolveCanonicalSessionId(validatedExplicit, metadata)
81
+ ?? resolveCanonicalSessionId(readSessionIdFromEnvironment(), metadata)
82
+ ?? metadata.sessionId;
83
+ return sessionId ? join(baseStateDir, 'sessions', sessionId) : baseStateDir;
84
+ }
61
85
 
62
- const currentSessionId = await readCurrentSessionId(baseStateDir);
63
- if (currentSessionId) {
64
- return join(baseStateDir, 'sessions', currentSessionId);
65
- }
86
+ async function resolveBaseScopedStateDirs(
87
+ baseStateDir: string,
88
+ explicitSessionId?: string,
89
+ options: { includeRootFallback?: boolean } = {},
90
+ ): Promise<string[]> {
91
+ const scopedDir = await resolveBaseScopedStateDir(baseStateDir, explicitSessionId);
92
+ return options.includeRootFallback === true && scopedDir !== baseStateDir
93
+ ? [scopedDir, baseStateDir]
94
+ : [scopedDir];
95
+ }
96
+
97
+
98
+
99
+
100
+ export async function readCurrentSessionId(baseStateDir: string): Promise<string | undefined> {
101
+ const metadata = await readSessionMetadataFromBaseStateDir(baseStateDir);
102
+ return resolveCanonicalSessionId(readSessionIdFromEnvironment(), metadata) ?? metadata.sessionId;
103
+ }
66
104
 
67
- return baseStateDir;
105
+ export async function resolveScopedStateDir(
106
+ baseStateDir: string,
107
+ explicitSessionId?: string,
108
+ ): Promise<string> {
109
+ return resolveBaseScopedStateDir(baseStateDir, explicitSessionId);
68
110
  }
69
111
 
70
112
  export async function getScopedStateDirsForCurrentSession(
@@ -72,11 +114,7 @@ export async function getScopedStateDirsForCurrentSession(
72
114
  explicitSessionId?: string,
73
115
  options: { includeRootFallback?: boolean } = {},
74
116
  ): Promise<string[]> {
75
- const scopedDir = await resolveScopedStateDir(baseStateDir, explicitSessionId);
76
- if (scopedDir === baseStateDir || options.includeRootFallback !== true) {
77
- return [scopedDir];
78
- }
79
- return [scopedDir, baseStateDir];
117
+ return resolveBaseScopedStateDirs(baseStateDir, explicitSessionId, options);
80
118
  }
81
119
 
82
120
  export async function getScopedStatePath(
@@ -25,6 +25,7 @@ import { writeTeamLeaderAttention } from '../../team/state.js';
25
25
  import { readLatestTeamProgressEvidenceMs } from '../../team/progress-evidence.js';
26
26
  import { validateSessionId } from '../../mcp/state-paths.js';
27
27
  import { TEAM_NAME_SAFE_PATTERN } from '../../team/contracts.js';
28
+ import { isDeepInterviewStateActive } from './auto-nudge.js';
28
29
  const LEADER_PANE_MISSING_NO_INJECTION_REASON = 'leader_pane_missing_no_injection';
29
30
  const LEADER_PANE_SHELL_NO_INJECTION_REASON = 'leader_pane_shell_no_injection';
30
31
  const LEADER_PANE_SAME_CLASSIFIED_STATE_SUPPRESSED_REASON = 'pane_already_shows_same_classified_state';
@@ -571,6 +572,12 @@ export async function maybeNudgeTeamLeader({
571
572
 
572
573
  const candidateTeamNames = new Set();
573
574
  const currentSessionId = await resolveCurrentSessionId(stateDir);
575
+ const deepInterviewActive = currentSessionId
576
+ ? await isDeepInterviewStateActive(stateDir, currentSessionId).catch(() => false)
577
+ : await isDeepInterviewStateActive(stateDir, undefined).catch(() => false);
578
+ if (deepInterviewActive) {
579
+ return;
580
+ }
574
581
  try {
575
582
  const scopedDirs = await getScopedStateDirsForCurrentSession(stateDir);
576
583
  const candidateStateDirs = [...new Set([...scopedDirs, stateDir])];