oh-my-codex 0.20.0 → 0.20.2

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 (661) hide show
  1. package/Cargo.lock +6 -6
  2. package/Cargo.toml +1 -1
  3. package/README.md +10 -1
  4. package/dist/adapt/__tests__/hermes.test.js.map +1 -1
  5. package/dist/adapt/contracts.d.ts.map +1 -1
  6. package/dist/agents/native-config.js.map +1 -1
  7. package/dist/auth/__tests__/hotswap.test.d.ts +2 -0
  8. package/dist/auth/__tests__/hotswap.test.d.ts.map +1 -0
  9. package/dist/auth/__tests__/hotswap.test.js +183 -0
  10. package/dist/auth/__tests__/hotswap.test.js.map +1 -0
  11. package/dist/auth/hotswap.d.ts.map +1 -1
  12. package/dist/auth/hotswap.js +17 -4
  13. package/dist/auth/hotswap.js.map +1 -1
  14. package/dist/auth/paths.js.map +1 -1
  15. package/dist/auth/storage.js.map +1 -1
  16. package/dist/autopilot/__tests__/ralplan-gate.test.js +4 -4
  17. package/dist/autopilot/__tests__/ralplan-gate.test.js.map +1 -1
  18. package/dist/autopilot/completion-gate.js.map +1 -1
  19. package/dist/autopilot/fsm.d.ts +1 -1
  20. package/dist/autopilot/fsm.d.ts.map +1 -1
  21. package/dist/autoresearch/goal.js.map +1 -1
  22. package/dist/autoresearch/runtime.js.map +1 -1
  23. package/dist/capabilities/lockfile.js.map +1 -1
  24. package/dist/catalog/reader.js.map +1 -1
  25. package/dist/catalog/skill-mirror.js.map +1 -1
  26. package/dist/cli/__tests__/adapt-help.test.js.map +1 -1
  27. package/dist/cli/__tests__/agents-init.test.js.map +1 -1
  28. package/dist/cli/__tests__/agents.test.js.map +1 -1
  29. package/dist/cli/__tests__/api.test.js.map +1 -1
  30. package/dist/cli/__tests__/ask.test.js.map +1 -1
  31. package/dist/cli/__tests__/auth.test.js.map +1 -1
  32. package/dist/cli/__tests__/autoresearch-guided.test.js.map +1 -1
  33. package/dist/cli/__tests__/autoresearch.test.js.map +1 -1
  34. package/dist/cli/__tests__/capabilities.test.js.map +1 -1
  35. package/dist/cli/__tests__/codex-plugin-layout.test.js +27 -0
  36. package/dist/cli/__tests__/codex-plugin-layout.test.js.map +1 -1
  37. package/dist/cli/__tests__/doctor-context-window-warning.test.js +83 -78
  38. package/dist/cli/__tests__/doctor-context-window-warning.test.js.map +1 -1
  39. package/dist/cli/__tests__/doctor-invalid-config.test.js +56 -4
  40. package/dist/cli/__tests__/doctor-invalid-config.test.js.map +1 -1
  41. package/dist/cli/__tests__/doctor-team.test.js.map +1 -1
  42. package/dist/cli/__tests__/doctor-warning-copy.test.js +735 -14
  43. package/dist/cli/__tests__/doctor-warning-copy.test.js.map +1 -1
  44. package/dist/cli/__tests__/exec.test.js.map +1 -1
  45. package/dist/cli/__tests__/imagegen-continuation.test.js.map +1 -1
  46. package/dist/cli/__tests__/index.test.js +216 -11
  47. package/dist/cli/__tests__/index.test.js.map +1 -1
  48. package/dist/cli/__tests__/launch-fallback.test.js +49 -3
  49. package/dist/cli/__tests__/launch-fallback.test.js.map +1 -1
  50. package/dist/cli/__tests__/mcp-parity.test.js +84 -12
  51. package/dist/cli/__tests__/mcp-parity.test.js.map +1 -1
  52. package/dist/cli/__tests__/native-hook-claim-journal.test.d.ts +2 -0
  53. package/dist/cli/__tests__/native-hook-claim-journal.test.d.ts.map +1 -0
  54. package/dist/cli/__tests__/native-hook-claim-journal.test.js +128 -0
  55. package/dist/cli/__tests__/native-hook-claim-journal.test.js.map +1 -0
  56. package/dist/cli/__tests__/nested-help-routing.test.js.map +1 -1
  57. package/dist/cli/__tests__/question.test.js.map +1 -1
  58. package/dist/cli/__tests__/ralph-deslop-contract.test.js.map +1 -1
  59. package/dist/cli/__tests__/ralph-goal-mode-contract.test.js +6 -0
  60. package/dist/cli/__tests__/ralph-goal-mode-contract.test.js.map +1 -1
  61. package/dist/cli/__tests__/ralph-prd-deep-interview.test.js.map +1 -1
  62. package/dist/cli/__tests__/ralph-prd-smoke.test.js.map +1 -1
  63. package/dist/cli/__tests__/ralph.test.js +8 -3
  64. package/dist/cli/__tests__/ralph.test.js.map +1 -1
  65. package/dist/cli/__tests__/ralplan-bootstrap-3181.test.d.ts +2 -0
  66. package/dist/cli/__tests__/ralplan-bootstrap-3181.test.d.ts.map +1 -0
  67. package/dist/cli/__tests__/ralplan-bootstrap-3181.test.js +187 -0
  68. package/dist/cli/__tests__/ralplan-bootstrap-3181.test.js.map +1 -0
  69. package/dist/cli/__tests__/ralplan.test.d.ts +2 -0
  70. package/dist/cli/__tests__/ralplan.test.d.ts.map +1 -0
  71. package/dist/cli/__tests__/ralplan.test.js +129 -0
  72. package/dist/cli/__tests__/ralplan.test.js.map +1 -0
  73. package/dist/cli/__tests__/resume.test.js +6 -1
  74. package/dist/cli/__tests__/resume.test.js.map +1 -1
  75. package/dist/cli/__tests__/session-scoped-runtime.test.js +25 -0
  76. package/dist/cli/__tests__/session-scoped-runtime.test.js.map +1 -1
  77. package/dist/cli/__tests__/session-search-help.test.js.map +1 -1
  78. package/dist/cli/__tests__/session-search.test.js.map +1 -1
  79. package/dist/cli/__tests__/setup-agents-overwrite.test.js +79 -0
  80. package/dist/cli/__tests__/setup-agents-overwrite.test.js.map +1 -1
  81. package/dist/cli/__tests__/setup-gh-star.test.js.map +1 -1
  82. package/dist/cli/__tests__/setup-hooks-shared-ownership.test.js +131 -5
  83. package/dist/cli/__tests__/setup-hooks-shared-ownership.test.js.map +1 -1
  84. package/dist/cli/__tests__/setup-hooks-trust-e2e.test.d.ts +2 -0
  85. package/dist/cli/__tests__/setup-hooks-trust-e2e.test.d.ts.map +1 -0
  86. package/dist/cli/__tests__/setup-hooks-trust-e2e.test.js +285 -0
  87. package/dist/cli/__tests__/setup-hooks-trust-e2e.test.js.map +1 -0
  88. package/dist/cli/__tests__/setup-install-mode.test.js +2264 -17
  89. package/dist/cli/__tests__/setup-install-mode.test.js.map +1 -1
  90. package/dist/cli/__tests__/setup-preferences.test.d.ts +2 -0
  91. package/dist/cli/__tests__/setup-preferences.test.d.ts.map +1 -0
  92. package/dist/cli/__tests__/setup-preferences.test.js +79 -0
  93. package/dist/cli/__tests__/setup-preferences.test.js.map +1 -0
  94. package/dist/cli/__tests__/setup-refresh.test.js +168 -5
  95. package/dist/cli/__tests__/setup-refresh.test.js.map +1 -1
  96. package/dist/cli/__tests__/setup-scope.test.js +73 -6
  97. package/dist/cli/__tests__/setup-scope.test.js.map +1 -1
  98. package/dist/cli/__tests__/sparkshell-cli.test.js.map +1 -1
  99. package/dist/cli/__tests__/state.test.js +37 -0
  100. package/dist/cli/__tests__/state.test.js.map +1 -1
  101. package/dist/cli/__tests__/team.test.js.map +1 -1
  102. package/dist/cli/__tests__/ultragoal.test.js +61 -1
  103. package/dist/cli/__tests__/ultragoal.test.js.map +1 -1
  104. package/dist/cli/__tests__/uninstall.test.js +2067 -26
  105. package/dist/cli/__tests__/uninstall.test.js.map +1 -1
  106. package/dist/cli/__tests__/update.test.js +58 -0
  107. package/dist/cli/__tests__/update.test.js.map +1 -1
  108. package/dist/cli/__tests__/url.test.js.map +1 -1
  109. package/dist/cli/__tests__/windows-popup-loop-contract.test.js.map +1 -1
  110. package/dist/cli/adapt.js.map +1 -1
  111. package/dist/cli/agents-init.js.map +1 -1
  112. package/dist/cli/agents.js.map +1 -1
  113. package/dist/cli/api.js.map +1 -1
  114. package/dist/cli/ask.d.ts +1 -1
  115. package/dist/cli/ask.d.ts.map +1 -1
  116. package/dist/cli/ask.js.map +1 -1
  117. package/dist/cli/auth.js.map +1 -1
  118. package/dist/cli/autoresearch-guided.js.map +1 -1
  119. package/dist/cli/autoresearch-intake.js.map +1 -1
  120. package/dist/cli/capabilities.js.map +1 -1
  121. package/dist/cli/cleanup.js.map +1 -1
  122. package/dist/cli/codex-feature-probe.js.map +1 -1
  123. package/dist/cli/codex-home.js.map +1 -1
  124. package/dist/cli/doctor.d.ts +20 -0
  125. package/dist/cli/doctor.d.ts.map +1 -1
  126. package/dist/cli/doctor.js +636 -161
  127. package/dist/cli/doctor.js.map +1 -1
  128. package/dist/cli/explore.js.map +1 -1
  129. package/dist/cli/index.d.ts +11 -3
  130. package/dist/cli/index.d.ts.map +1 -1
  131. package/dist/cli/index.js +108 -20
  132. package/dist/cli/index.js.map +1 -1
  133. package/dist/cli/mcp-serve.js.map +1 -1
  134. package/dist/cli/mission.js.map +1 -1
  135. package/dist/cli/native-assets.js.map +1 -1
  136. package/dist/cli/native-hook-claim-journal.d.ts +18 -0
  137. package/dist/cli/native-hook-claim-journal.d.ts.map +1 -0
  138. package/dist/cli/native-hook-claim-journal.js +239 -0
  139. package/dist/cli/native-hook-claim-journal.js.map +1 -0
  140. package/dist/cli/omx.js.map +1 -1
  141. package/dist/cli/plugin-marketplace.js.map +1 -1
  142. package/dist/cli/project-runtime-codex-homes.js.map +1 -1
  143. package/dist/cli/question.js.map +1 -1
  144. package/dist/cli/ralph.d.ts.map +1 -1
  145. package/dist/cli/ralph.js +3 -2
  146. package/dist/cli/ralph.js.map +1 -1
  147. package/dist/cli/ralplan.d.ts +14 -0
  148. package/dist/cli/ralplan.d.ts.map +1 -0
  149. package/dist/cli/ralplan.js +203 -0
  150. package/dist/cli/ralplan.js.map +1 -0
  151. package/dist/cli/setup-preferences.d.ts +11 -0
  152. package/dist/cli/setup-preferences.d.ts.map +1 -1
  153. package/dist/cli/setup-preferences.js +32 -0
  154. package/dist/cli/setup-preferences.js.map +1 -1
  155. package/dist/cli/setup.d.ts +53 -0
  156. package/dist/cli/setup.d.ts.map +1 -1
  157. package/dist/cli/setup.js +1403 -400
  158. package/dist/cli/setup.js.map +1 -1
  159. package/dist/cli/sparkshell.js.map +1 -1
  160. package/dist/cli/star-prompt.js.map +1 -1
  161. package/dist/cli/state.d.ts.map +1 -1
  162. package/dist/cli/state.js +2 -1
  163. package/dist/cli/state.js.map +1 -1
  164. package/dist/cli/team.js.map +1 -1
  165. package/dist/cli/ultragoal.d.ts +5 -1
  166. package/dist/cli/ultragoal.d.ts.map +1 -1
  167. package/dist/cli/ultragoal.js +34 -2
  168. package/dist/cli/ultragoal.js.map +1 -1
  169. package/dist/cli/uninstall.d.ts +8 -0
  170. package/dist/cli/uninstall.d.ts.map +1 -1
  171. package/dist/cli/uninstall.js +947 -99
  172. package/dist/cli/uninstall.js.map +1 -1
  173. package/dist/cli/update.d.ts.map +1 -1
  174. package/dist/cli/update.js +8 -1
  175. package/dist/cli/update.js.map +1 -1
  176. package/dist/cli/url.js.map +1 -1
  177. package/dist/compat/__tests__/doctor-contract.test.js +5 -1
  178. package/dist/compat/__tests__/doctor-contract.test.js.map +1 -1
  179. package/dist/compat/__tests__/rust-runtime-compat.test.js.map +1 -1
  180. package/dist/config/__tests__/codex-hooks.test.js +1287 -11
  181. package/dist/config/__tests__/codex-hooks.test.js.map +1 -1
  182. package/dist/config/__tests__/generator-idempotent.test.js +1049 -135
  183. package/dist/config/__tests__/generator-idempotent.test.js.map +1 -1
  184. package/dist/config/__tests__/generator-notify.test.js +28 -49
  185. package/dist/config/__tests__/generator-notify.test.js.map +1 -1
  186. package/dist/config/codex-feature-flags.d.ts.map +1 -1
  187. package/dist/config/codex-feature-flags.js.map +1 -1
  188. package/dist/config/codex-hooks.d.ts +124 -1
  189. package/dist/config/codex-hooks.d.ts.map +1 -1
  190. package/dist/config/codex-hooks.js +2142 -265
  191. package/dist/config/codex-hooks.js.map +1 -1
  192. package/dist/config/commit-lore-guard.js.map +1 -1
  193. package/dist/config/generator.d.ts +40 -26
  194. package/dist/config/generator.d.ts.map +1 -1
  195. package/dist/config/generator.js +1159 -288
  196. package/dist/config/generator.js.map +1 -1
  197. package/dist/config/mcp-registry.js.map +1 -1
  198. package/dist/config/models.d.ts +2 -2
  199. package/dist/config/models.d.ts.map +1 -1
  200. package/dist/config/models.js.map +1 -1
  201. package/dist/config/omx-first-party-mcp.js.map +1 -1
  202. package/dist/config/team-mode.d.ts.map +1 -1
  203. package/dist/document-refresh/__tests__/enforcer.test.js.map +1 -1
  204. package/dist/document-refresh/enforcer.js.map +1 -1
  205. package/dist/exec/followup.js.map +1 -1
  206. package/dist/goal-workflows/artifacts.js.map +1 -1
  207. package/dist/hooks/__tests__/agents-overlay.test.js +7 -4
  208. package/dist/hooks/__tests__/agents-overlay.test.js.map +1 -1
  209. package/dist/hooks/__tests__/analyze-routing-contract.test.js.map +1 -1
  210. package/dist/hooks/__tests__/analyze-skill-contract.test.js.map +1 -1
  211. package/dist/hooks/__tests__/anti-slop-workflow.test.js.map +1 -1
  212. package/dist/hooks/__tests__/autopilot-skill-contract.test.js +9 -3
  213. package/dist/hooks/__tests__/autopilot-skill-contract.test.js.map +1 -1
  214. package/dist/hooks/__tests__/code-review-skill-contract.test.js.map +1 -1
  215. package/dist/hooks/__tests__/consensus-execution-handoff.test.js.map +1 -1
  216. package/dist/hooks/__tests__/deep-interview-contract.test.js.map +1 -1
  217. package/dist/hooks/__tests__/design-skill.test.js.map +1 -1
  218. package/dist/hooks/__tests__/keyword-detector.test.js +1372 -2
  219. package/dist/hooks/__tests__/keyword-detector.test.js.map +1 -1
  220. package/dist/hooks/__tests__/notify-fallback-watcher.test.js +14 -0
  221. package/dist/hooks/__tests__/notify-fallback-watcher.test.js.map +1 -1
  222. package/dist/hooks/__tests__/notify-hook-all-workers-idle.test.js.map +1 -1
  223. package/dist/hooks/__tests__/notify-hook-auto-nudge.test.js +669 -1
  224. package/dist/hooks/__tests__/notify-hook-auto-nudge.test.js.map +1 -1
  225. package/dist/hooks/__tests__/notify-hook-cross-worktree-heartbeat.test.js +2 -0
  226. package/dist/hooks/__tests__/notify-hook-cross-worktree-heartbeat.test.js.map +1 -1
  227. package/dist/hooks/__tests__/notify-hook-managed-tmux.test.js +142 -1
  228. package/dist/hooks/__tests__/notify-hook-managed-tmux.test.js.map +1 -1
  229. package/dist/hooks/__tests__/notify-hook-modules.test.js.map +1 -1
  230. package/dist/hooks/__tests__/notify-hook-non-omx-guard.test.js +48 -2
  231. package/dist/hooks/__tests__/notify-hook-non-omx-guard.test.js.map +1 -1
  232. package/dist/hooks/__tests__/notify-hook-ralph-resume.test.js +61 -44
  233. package/dist/hooks/__tests__/notify-hook-ralph-resume.test.js.map +1 -1
  234. package/dist/hooks/__tests__/notify-hook-regression-205.test.js.map +1 -1
  235. package/dist/hooks/__tests__/notify-hook-session-idle-dedupe.test.js +3 -2
  236. package/dist/hooks/__tests__/notify-hook-session-idle-dedupe.test.js.map +1 -1
  237. package/dist/hooks/__tests__/notify-hook-session-scope.test.js +243 -4
  238. package/dist/hooks/__tests__/notify-hook-session-scope.test.js.map +1 -1
  239. package/dist/hooks/__tests__/notify-hook-team-dispatch.test.js.map +1 -1
  240. package/dist/hooks/__tests__/notify-hook-team-leader-nudge.test.js +8 -1
  241. package/dist/hooks/__tests__/notify-hook-team-leader-nudge.test.js.map +1 -1
  242. package/dist/hooks/__tests__/notify-hook-team-tmux-guard.test.js.map +1 -1
  243. package/dist/hooks/__tests__/notify-hook-tmux-heal.test.js.map +1 -1
  244. package/dist/hooks/__tests__/notify-hook-tmux-scrollback.test.js.map +1 -1
  245. package/dist/hooks/__tests__/notify-hook-visual-verdict.test.js.map +1 -1
  246. package/dist/hooks/__tests__/notify-hook-worker-idle.test.js.map +1 -1
  247. package/dist/hooks/__tests__/openclaw-setup-contract.test.js.map +1 -1
  248. package/dist/hooks/__tests__/pre-context-gate-skills.test.js.map +1 -1
  249. package/dist/hooks/__tests__/prometheus-strict-contract.test.js.map +1 -1
  250. package/dist/hooks/__tests__/prompt-guidance-fragments.test.js.map +1 -1
  251. package/dist/hooks/__tests__/prompt-guidance-test-helpers.js.map +1 -1
  252. package/dist/hooks/__tests__/prompt-session-provenance.test.d.ts +2 -0
  253. package/dist/hooks/__tests__/prompt-session-provenance.test.d.ts.map +1 -0
  254. package/dist/hooks/__tests__/prompt-session-provenance.test.js +82 -0
  255. package/dist/hooks/__tests__/prompt-session-provenance.test.js.map +1 -0
  256. package/dist/hooks/__tests__/session.test.js +532 -11
  257. package/dist/hooks/__tests__/session.test.js.map +1 -1
  258. package/dist/hooks/__tests__/skill-catalog-hygiene.test.js.map +1 -1
  259. package/dist/hooks/__tests__/team-runtime-gating-docs-contract.test.js.map +1 -1
  260. package/dist/hooks/__tests__/visual-ralph-skill.test.js.map +1 -1
  261. package/dist/hooks/__tests__/visual-verdict-loop.test.js.map +1 -1
  262. package/dist/hooks/agents-overlay.js +5 -5
  263. package/dist/hooks/agents-overlay.js.map +1 -1
  264. package/dist/hooks/code-simplifier/index.js.map +1 -1
  265. package/dist/hooks/explore-routing.js.map +1 -1
  266. package/dist/hooks/extensibility/dispatcher.js.map +1 -1
  267. package/dist/hooks/extensibility/events.js.map +1 -1
  268. package/dist/hooks/extensibility/loader.js.map +1 -1
  269. package/dist/hooks/extensibility/plugin-runner-stdin.js.map +1 -1
  270. package/dist/hooks/extensibility/sdk/logging.js.map +1 -1
  271. package/dist/hooks/keyword-detector.d.ts +33 -7
  272. package/dist/hooks/keyword-detector.d.ts.map +1 -1
  273. package/dist/hooks/keyword-detector.js +2228 -119
  274. package/dist/hooks/keyword-detector.js.map +1 -1
  275. package/dist/hooks/keyword-registry.d.ts +12 -0
  276. package/dist/hooks/keyword-registry.d.ts.map +1 -1
  277. package/dist/hooks/keyword-registry.js +41 -0
  278. package/dist/hooks/keyword-registry.js.map +1 -1
  279. package/dist/hooks/prompt-session-provenance.d.ts +87 -0
  280. package/dist/hooks/prompt-session-provenance.d.ts.map +1 -0
  281. package/dist/hooks/prompt-session-provenance.js +180 -0
  282. package/dist/hooks/prompt-session-provenance.js.map +1 -0
  283. package/dist/hooks/session.d.ts +139 -34
  284. package/dist/hooks/session.d.ts.map +1 -1
  285. package/dist/hooks/session.js +1239 -317
  286. package/dist/hooks/session.js.map +1 -1
  287. package/dist/hooks/task-size-detector.js.map +1 -1
  288. package/dist/hud/__tests__/state.test.js.map +1 -1
  289. package/dist/hud/authority.js.map +1 -1
  290. package/dist/hud/constants.js.map +1 -1
  291. package/dist/hud/index.js.map +1 -1
  292. package/dist/hud/reconcile.js.map +1 -1
  293. package/dist/hud/render.js.map +1 -1
  294. package/dist/hud/state.js.map +1 -1
  295. package/dist/hud/tmux.js.map +1 -1
  296. package/dist/leader/__tests__/contract.test.js +146 -2
  297. package/dist/leader/__tests__/contract.test.js.map +1 -1
  298. package/dist/leader/contract.d.ts +26 -5
  299. package/dist/leader/contract.d.ts.map +1 -1
  300. package/dist/leader/contract.js +131 -7
  301. package/dist/leader/contract.js.map +1 -1
  302. package/dist/mcp/__tests__/bootstrap.test.js.map +1 -1
  303. package/dist/mcp/__tests__/server-lifecycle.test.js +15 -2
  304. package/dist/mcp/__tests__/server-lifecycle.test.js.map +1 -1
  305. package/dist/mcp/__tests__/state-paths.test.js +98 -1
  306. package/dist/mcp/__tests__/state-paths.test.js.map +1 -1
  307. package/dist/mcp/bootstrap.js.map +1 -1
  308. package/dist/mcp/code-intel-server.d.ts +58 -58
  309. package/dist/mcp/code-intel-server.d.ts.map +1 -1
  310. package/dist/mcp/code-intel-server.js.map +1 -1
  311. package/dist/mcp/hermes-bridge.js.map +1 -1
  312. package/dist/mcp/hermes-server.d.ts +70 -70
  313. package/dist/mcp/hermes-server.d.ts.map +1 -1
  314. package/dist/mcp/lifecycle-telemetry.js.map +1 -1
  315. package/dist/mcp/memory-server.d.ts +34 -34
  316. package/dist/mcp/memory-server.d.ts.map +1 -1
  317. package/dist/mcp/memory-server.js.map +1 -1
  318. package/dist/mcp/state-paths.d.ts +14 -0
  319. package/dist/mcp/state-paths.d.ts.map +1 -1
  320. package/dist/mcp/state-paths.js +96 -20
  321. package/dist/mcp/state-paths.js.map +1 -1
  322. package/dist/mcp/state-server.d.ts +47 -47
  323. package/dist/mcp/state-server.d.ts.map +1 -1
  324. package/dist/mcp/trace-server.d.ts +4 -4
  325. package/dist/mcp/trace-server.d.ts.map +1 -1
  326. package/dist/mcp/trace-server.js.map +1 -1
  327. package/dist/mcp/wiki-server.d.ts +23 -23
  328. package/dist/mcp/wiki-server.d.ts.map +1 -1
  329. package/dist/mcp/wiki-server.js.map +1 -1
  330. package/dist/modes/__tests__/base-session-scope.test.js +53 -1
  331. package/dist/modes/__tests__/base-session-scope.test.js.map +1 -1
  332. package/dist/modes/base.d.ts.map +1 -1
  333. package/dist/modes/base.js +5 -5
  334. package/dist/modes/base.js.map +1 -1
  335. package/dist/notifications/__tests__/lifecycle-dedupe.test.js.map +1 -1
  336. package/dist/notifications/__tests__/reply-listener.test.js.map +1 -1
  337. package/dist/notifications/__tests__/session-idle-tail-dedupe.test.js +177 -85
  338. package/dist/notifications/__tests__/session-idle-tail-dedupe.test.js.map +1 -1
  339. package/dist/notifications/__tests__/session-registry.test.js.map +1 -1
  340. package/dist/notifications/__tests__/template-engine.test.js.map +1 -1
  341. package/dist/notifications/__tests__/verbosity.test.js.map +1 -1
  342. package/dist/notifications/http-client.js.map +1 -1
  343. package/dist/notifications/index.d.ts +4 -1
  344. package/dist/notifications/index.d.ts.map +1 -1
  345. package/dist/notifications/index.js +3 -9
  346. package/dist/notifications/index.js.map +1 -1
  347. package/dist/notifications/lifecycle-dedupe.js.map +1 -1
  348. package/dist/notifications/reply-listener.d.ts.map +1 -1
  349. package/dist/notifications/reply-listener.js.map +1 -1
  350. package/dist/notifications/session-registry.js.map +1 -1
  351. package/dist/notifications/session-status.js.map +1 -1
  352. package/dist/notifications/temp-contract.js.map +1 -1
  353. package/dist/notifications/tmux-detector.js.map +1 -1
  354. package/dist/notifications/tmux.js.map +1 -1
  355. package/dist/openclaw/config.js.map +1 -1
  356. package/dist/openclaw/dispatcher.js.map +1 -1
  357. package/dist/pipeline/__tests__/orchestrator.test.js.map +1 -1
  358. package/dist/pipeline/__tests__/stages.test.js +8 -5
  359. package/dist/pipeline/__tests__/stages.test.js.map +1 -1
  360. package/dist/pipeline/stages/code-review.js.map +1 -1
  361. package/dist/pipeline/stages/ralph-verify.js.map +1 -1
  362. package/dist/pipeline/stages/ralplan.js.map +1 -1
  363. package/dist/pipeline/stages/team-exec.js.map +1 -1
  364. package/dist/pipeline/stages/ultragoal.js.map +1 -1
  365. package/dist/pipeline/stages/ultraqa.js.map +1 -1
  366. package/dist/planning/__tests__/approved-execution-lifecycle-matrix.test.js.map +1 -1
  367. package/dist/planning/artifact-names.js.map +1 -1
  368. package/dist/planning/artifacts.js.map +1 -1
  369. package/dist/question/__tests__/state.test.js.map +1 -1
  370. package/dist/question/__tests__/ui.test.js.map +1 -1
  371. package/dist/question/autopilot-wait.js.map +1 -1
  372. package/dist/question/client.d.ts.map +1 -1
  373. package/dist/question/client.js.map +1 -1
  374. package/dist/question/deep-interview.js.map +1 -1
  375. package/dist/question/events.js.map +1 -1
  376. package/dist/question/renderer.js.map +1 -1
  377. package/dist/question/state.d.ts.map +1 -1
  378. package/dist/question/state.js.map +1 -1
  379. package/dist/question/ui.js.map +1 -1
  380. package/dist/ralph/contract.d.ts +1 -1
  381. package/dist/ralph/contract.d.ts.map +1 -1
  382. package/dist/ralplan/__tests__/consensus-gate.test.js +403 -8
  383. package/dist/ralplan/__tests__/consensus-gate.test.js.map +1 -1
  384. package/dist/ralplan/__tests__/runtime.test.js +112 -14
  385. package/dist/ralplan/__tests__/runtime.test.js.map +1 -1
  386. package/dist/ralplan/consensus-gate.d.ts +3 -3
  387. package/dist/ralplan/consensus-gate.d.ts.map +1 -1
  388. package/dist/ralplan/consensus-gate.js +155 -60
  389. package/dist/ralplan/consensus-gate.js.map +1 -1
  390. package/dist/ralplan/runtime.d.ts +2 -2
  391. package/dist/ralplan/runtime.d.ts.map +1 -1
  392. package/dist/ralplan/runtime.js +1 -2
  393. package/dist/ralplan/runtime.js.map +1 -1
  394. package/dist/runtime/bridge.d.ts.map +1 -1
  395. package/dist/runtime/bridge.js.map +1 -1
  396. package/dist/runtime/process-tree.js.map +1 -1
  397. package/dist/runtime/run-loop.js.map +1 -1
  398. package/dist/runtime/run-outcome.d.ts +3 -3
  399. package/dist/runtime/run-outcome.d.ts.map +1 -1
  400. package/dist/runtime/run-outcome.js.map +1 -1
  401. package/dist/runtime/run-state.js.map +1 -1
  402. package/dist/scripts/__tests__/codex-native-hook.test.js +5414 -801
  403. package/dist/scripts/__tests__/codex-native-hook.test.js.map +1 -1
  404. package/dist/scripts/__tests__/fixtures/issue-3138-installed-smoke.d.ts +2 -0
  405. package/dist/scripts/__tests__/fixtures/issue-3138-installed-smoke.d.ts.map +1 -0
  406. package/dist/scripts/__tests__/fixtures/issue-3138-installed-smoke.js +634 -0
  407. package/dist/scripts/__tests__/fixtures/issue-3138-installed-smoke.js.map +1 -0
  408. package/dist/scripts/__tests__/generate-release-body.test.js.map +1 -1
  409. package/dist/scripts/__tests__/hook-derived-watcher.test.js.map +1 -1
  410. package/dist/scripts/__tests__/notify-dispatcher.test.js.map +1 -1
  411. package/dist/scripts/__tests__/notify-fallback-delivery.test.d.ts +2 -0
  412. package/dist/scripts/__tests__/notify-fallback-delivery.test.d.ts.map +1 -0
  413. package/dist/scripts/__tests__/notify-fallback-delivery.test.js +174 -0
  414. package/dist/scripts/__tests__/notify-fallback-delivery.test.js.map +1 -0
  415. package/dist/scripts/__tests__/notify-state-io.test.js +57 -1
  416. package/dist/scripts/__tests__/notify-state-io.test.js.map +1 -1
  417. package/dist/scripts/__tests__/role-intent-bootstrap-e2e-3181.test.d.ts +2 -0
  418. package/dist/scripts/__tests__/role-intent-bootstrap-e2e-3181.test.d.ts.map +1 -0
  419. package/dist/scripts/__tests__/role-intent-bootstrap-e2e-3181.test.js +205 -0
  420. package/dist/scripts/__tests__/role-intent-bootstrap-e2e-3181.test.js.map +1 -0
  421. package/dist/scripts/__tests__/role-intent-durable-recovery-3181.test.d.ts +2 -0
  422. package/dist/scripts/__tests__/role-intent-durable-recovery-3181.test.d.ts.map +1 -0
  423. package/dist/scripts/__tests__/role-intent-durable-recovery-3181.test.js +175 -0
  424. package/dist/scripts/__tests__/role-intent-durable-recovery-3181.test.js.map +1 -0
  425. package/dist/scripts/__tests__/run-test-files.test.js.map +1 -1
  426. package/dist/scripts/__tests__/smoke-packed-install.test.js +848 -4
  427. package/dist/scripts/__tests__/smoke-packed-install.test.js.map +1 -1
  428. package/dist/scripts/__tests__/test-reply-listener-live.test.js.map +1 -1
  429. package/dist/scripts/build-api.js.map +1 -1
  430. package/dist/scripts/build-explore-harness.js.map +1 -1
  431. package/dist/scripts/build-sparkshell.js.map +1 -1
  432. package/dist/scripts/cleanup-explore-harness.js.map +1 -1
  433. package/dist/scripts/codex-execution-surface.js.map +1 -1
  434. package/dist/scripts/codex-native-hook.d.ts.map +1 -1
  435. package/dist/scripts/codex-native-hook.js +825 -172
  436. package/dist/scripts/codex-native-hook.js.map +1 -1
  437. package/dist/scripts/generate-native-release-manifest.js.map +1 -1
  438. package/dist/scripts/generate-release-body.js.map +1 -1
  439. package/dist/scripts/hook-payload-guard.d.ts.map +1 -1
  440. package/dist/scripts/hook-payload-guard.js.map +1 -1
  441. package/dist/scripts/notify-fallback-delivery.d.ts +48 -0
  442. package/dist/scripts/notify-fallback-delivery.d.ts.map +1 -0
  443. package/dist/scripts/notify-fallback-delivery.js +419 -0
  444. package/dist/scripts/notify-fallback-delivery.js.map +1 -0
  445. package/dist/scripts/notify-fallback-watcher.js +136 -30
  446. package/dist/scripts/notify-fallback-watcher.js.map +1 -1
  447. package/dist/scripts/notify-hook/auto-nudge.d.ts +18 -9
  448. package/dist/scripts/notify-hook/auto-nudge.d.ts.map +1 -1
  449. package/dist/scripts/notify-hook/auto-nudge.js +39 -13
  450. package/dist/scripts/notify-hook/auto-nudge.js.map +1 -1
  451. package/dist/scripts/notify-hook/managed-tmux.d.ts +23 -0
  452. package/dist/scripts/notify-hook/managed-tmux.d.ts.map +1 -1
  453. package/dist/scripts/notify-hook/managed-tmux.js +122 -12
  454. package/dist/scripts/notify-hook/managed-tmux.js.map +1 -1
  455. package/dist/scripts/notify-hook/orchestration-intent.d.ts +7 -7
  456. package/dist/scripts/notify-hook/orchestration-intent.d.ts.map +1 -1
  457. package/dist/scripts/notify-hook/ralph-session-resume.d.ts +5 -2
  458. package/dist/scripts/notify-hook/ralph-session-resume.d.ts.map +1 -1
  459. package/dist/scripts/notify-hook/ralph-session-resume.js +33 -37
  460. package/dist/scripts/notify-hook/ralph-session-resume.js.map +1 -1
  461. package/dist/scripts/notify-hook/state-io.d.ts +17 -0
  462. package/dist/scripts/notify-hook/state-io.d.ts.map +1 -1
  463. package/dist/scripts/notify-hook/state-io.js +73 -11
  464. package/dist/scripts/notify-hook/state-io.js.map +1 -1
  465. package/dist/scripts/notify-hook/team-dispatch.d.ts +1 -1
  466. package/dist/scripts/notify-hook/team-dispatch.js.map +1 -1
  467. package/dist/scripts/notify-hook/team-leader-nudge.d.ts +2 -2
  468. package/dist/scripts/notify-hook/team-tmux-guard.d.ts +4 -4
  469. package/dist/scripts/notify-hook/team-worker-posttooluse.js.map +1 -1
  470. package/dist/scripts/notify-hook/team-worker-stop.d.ts +1 -1
  471. package/dist/scripts/notify-hook/team-worker.d.ts +2 -2
  472. package/dist/scripts/notify-hook/tmux-injection.d.ts +8 -1
  473. package/dist/scripts/notify-hook/tmux-injection.d.ts.map +1 -1
  474. package/dist/scripts/notify-hook/tmux-injection.js +25 -16
  475. package/dist/scripts/notify-hook/tmux-injection.js.map +1 -1
  476. package/dist/scripts/notify-hook/visual-verdict.d.ts +1 -1
  477. package/dist/scripts/notify-hook/visual-verdict.d.ts.map +1 -1
  478. package/dist/scripts/notify-hook/visual-verdict.js +15 -13
  479. package/dist/scripts/notify-hook/visual-verdict.js.map +1 -1
  480. package/dist/scripts/notify-hook.d.ts +24 -1
  481. package/dist/scripts/notify-hook.d.ts.map +1 -1
  482. package/dist/scripts/notify-hook.js +266 -90
  483. package/dist/scripts/notify-hook.js.map +1 -1
  484. package/dist/scripts/postinstall.js.map +1 -1
  485. package/dist/scripts/prompt-inventory.js.map +1 -1
  486. package/dist/scripts/smoke-packed-install.d.ts +879 -2
  487. package/dist/scripts/smoke-packed-install.d.ts.map +1 -1
  488. package/dist/scripts/smoke-packed-install.js +1528 -7
  489. package/dist/scripts/smoke-packed-install.js.map +1 -1
  490. package/dist/scripts/sync-plugin-mirror.js.map +1 -1
  491. package/dist/scripts/test-reply-listener-live.js.map +1 -1
  492. package/dist/scripts/test-sparkshell.js.map +1 -1
  493. package/dist/scripts/verify-native-agents.js.map +1 -1
  494. package/dist/session-history/friction.js.map +1 -1
  495. package/dist/session-history/search.js.map +1 -1
  496. package/dist/sidecar/__tests__/boundary.test.js.map +1 -1
  497. package/dist/sidecar/collector.js.map +1 -1
  498. package/dist/sidecar/index.js.map +1 -1
  499. package/dist/sidecar/render.js.map +1 -1
  500. package/dist/sidecar/tmux.js.map +1 -1
  501. package/dist/state/__tests__/operations.test.js +64 -15
  502. package/dist/state/__tests__/operations.test.js.map +1 -1
  503. package/dist/state/__tests__/skill-active.test.js +19 -1
  504. package/dist/state/__tests__/skill-active.test.js.map +1 -1
  505. package/dist/state/__tests__/workflow-transition.test.js +61 -0
  506. package/dist/state/__tests__/workflow-transition.test.js.map +1 -1
  507. package/dist/state/mode-state-context.js.map +1 -1
  508. package/dist/state/operations.d.ts +1 -1
  509. package/dist/state/operations.d.ts.map +1 -1
  510. package/dist/state/operations.js +49 -55
  511. package/dist/state/operations.js.map +1 -1
  512. package/dist/state/paths.d.ts +1 -1
  513. package/dist/state/paths.d.ts.map +1 -1
  514. package/dist/state/paths.js +1 -1
  515. package/dist/state/paths.js.map +1 -1
  516. package/dist/state/skill-active.d.ts +13 -1
  517. package/dist/state/skill-active.d.ts.map +1 -1
  518. package/dist/state/skill-active.js +43 -4
  519. package/dist/state/skill-active.js.map +1 -1
  520. package/dist/state/terminal-normalization.js.map +1 -1
  521. package/dist/state/workflow-transition-reconcile.d.ts.map +1 -1
  522. package/dist/state/workflow-transition-reconcile.js +20 -4
  523. package/dist/state/workflow-transition-reconcile.js.map +1 -1
  524. package/dist/state/workflow-transition.d.ts +1 -1
  525. package/dist/state/workflow-transition.d.ts.map +1 -1
  526. package/dist/state/workflow-transition.js.map +1 -1
  527. package/dist/subagents/__tests__/adapted-role-binding.test.d.ts +2 -0
  528. package/dist/subagents/__tests__/adapted-role-binding.test.d.ts.map +1 -0
  529. package/dist/subagents/__tests__/adapted-role-binding.test.js +988 -0
  530. package/dist/subagents/__tests__/adapted-role-binding.test.js.map +1 -0
  531. package/dist/subagents/__tests__/leader-bootstrap-3181.test.d.ts +2 -0
  532. package/dist/subagents/__tests__/leader-bootstrap-3181.test.d.ts.map +1 -0
  533. package/dist/subagents/__tests__/leader-bootstrap-3181.test.js +273 -0
  534. package/dist/subagents/__tests__/leader-bootstrap-3181.test.js.map +1 -0
  535. package/dist/subagents/__tests__/tracker.test.js +1550 -10
  536. package/dist/subagents/__tests__/tracker.test.js.map +1 -1
  537. package/dist/subagents/adapted-role-binding.d.ts +17 -0
  538. package/dist/subagents/adapted-role-binding.d.ts.map +1 -0
  539. package/dist/subagents/adapted-role-binding.js +75 -0
  540. package/dist/subagents/adapted-role-binding.js.map +1 -0
  541. package/dist/subagents/role-routing-marker.d.ts +10 -0
  542. package/dist/subagents/role-routing-marker.d.ts.map +1 -0
  543. package/dist/subagents/role-routing-marker.js +136 -0
  544. package/dist/subagents/role-routing-marker.js.map +1 -0
  545. package/dist/subagents/tracker.d.ts +172 -0
  546. package/dist/subagents/tracker.d.ts.map +1 -1
  547. package/dist/subagents/tracker.js +1077 -61
  548. package/dist/subagents/tracker.js.map +1 -1
  549. package/dist/team/__tests__/approved-execution.test.js.map +1 -1
  550. package/dist/team/__tests__/delivery-e2e-smoke.test.js.map +1 -1
  551. package/dist/team/__tests__/model-contract.test.js +231 -1
  552. package/dist/team/__tests__/model-contract.test.js.map +1 -1
  553. package/dist/team/__tests__/role-router.test.js.map +1 -1
  554. package/dist/team/__tests__/runtime.test.js +401 -1
  555. package/dist/team/__tests__/runtime.test.js.map +1 -1
  556. package/dist/team/__tests__/scaling.test.js +181 -0
  557. package/dist/team/__tests__/scaling.test.js.map +1 -1
  558. package/dist/team/__tests__/state-root.test.js.map +1 -1
  559. package/dist/team/__tests__/tmux-session.test.js +434 -4
  560. package/dist/team/__tests__/tmux-session.test.js.map +1 -1
  561. package/dist/team/__tests__/tmux-test-fixture.js.map +1 -1
  562. package/dist/team/api-interop.d.ts +2 -2
  563. package/dist/team/api-interop.d.ts.map +1 -1
  564. package/dist/team/api-interop.js.map +1 -1
  565. package/dist/team/commit-hygiene.js.map +1 -1
  566. package/dist/team/contracts.d.ts +5 -5
  567. package/dist/team/contracts.d.ts.map +1 -1
  568. package/dist/team/delivery-log.js.map +1 -1
  569. package/dist/team/followup-planner.js.map +1 -1
  570. package/dist/team/goal-workflow.js.map +1 -1
  571. package/dist/team/idle-nudge.d.ts.map +1 -1
  572. package/dist/team/model-contract.d.ts +20 -1
  573. package/dist/team/model-contract.d.ts.map +1 -1
  574. package/dist/team/model-contract.js +267 -30
  575. package/dist/team/model-contract.js.map +1 -1
  576. package/dist/team/orchestrator.js.map +1 -1
  577. package/dist/team/pane-status.js.map +1 -1
  578. package/dist/team/phase-controller.js.map +1 -1
  579. package/dist/team/reminder-intents.d.ts +1 -1
  580. package/dist/team/reminder-intents.d.ts.map +1 -1
  581. package/dist/team/reminder-intents.js.map +1 -1
  582. package/dist/team/runtime-cli.js.map +1 -1
  583. package/dist/team/runtime.d.ts.map +1 -1
  584. package/dist/team/runtime.js +83 -52
  585. package/dist/team/runtime.js.map +1 -1
  586. package/dist/team/scaling.d.ts +9 -7
  587. package/dist/team/scaling.d.ts.map +1 -1
  588. package/dist/team/scaling.js +69 -28
  589. package/dist/team/scaling.js.map +1 -1
  590. package/dist/team/state/dispatch-lock.js.map +1 -1
  591. package/dist/team/state/dispatch.js.map +1 -1
  592. package/dist/team/state/events.js.map +1 -1
  593. package/dist/team/state/tasks.js.map +1 -1
  594. package/dist/team/state-root.js.map +1 -1
  595. package/dist/team/state.js.map +1 -1
  596. package/dist/team/team-identity.d.ts.map +1 -1
  597. package/dist/team/team-identity.js.map +1 -1
  598. package/dist/team/tmux-session.d.ts +2 -0
  599. package/dist/team/tmux-session.d.ts.map +1 -1
  600. package/dist/team/tmux-session.js +90 -42
  601. package/dist/team/tmux-session.js.map +1 -1
  602. package/dist/team/worker-bootstrap.js.map +1 -1
  603. package/dist/team/worktree.js.map +1 -1
  604. package/dist/ultragoal/__tests__/artifacts.test.js.map +1 -1
  605. package/dist/ultragoal/__tests__/docs-contract.test.js.map +1 -1
  606. package/dist/ultragoal/artifacts.d.ts.map +1 -1
  607. package/dist/ultragoal/artifacts.js +4 -1
  608. package/dist/ultragoal/artifacts.js.map +1 -1
  609. package/dist/url-reader/index.js.map +1 -1
  610. package/dist/utils/__tests__/agents-md.test.js +16 -0
  611. package/dist/utils/__tests__/agents-md.test.js.map +1 -1
  612. package/dist/utils/agents-md.d.ts.map +1 -1
  613. package/dist/utils/agents-md.js +4 -3
  614. package/dist/utils/agents-md.js.map +1 -1
  615. package/dist/utils/agents-model-table.js.map +1 -1
  616. package/dist/utils/package.js.map +1 -1
  617. package/dist/utils/paths.js.map +1 -1
  618. package/dist/utils/platform-command.js.map +1 -1
  619. package/dist/utils/repo-deps.js.map +1 -1
  620. package/dist/utils/version.js.map +1 -1
  621. package/dist/verification/__tests__/ci-rust-gates.test.js +1 -1
  622. package/dist/verification/__tests__/dev-merge-issue-close-workflow.test.js.map +1 -1
  623. package/dist/verification/verifier.js.map +1 -1
  624. package/dist/visual/constants.d.ts +1 -1
  625. package/dist/visual/constants.d.ts.map +1 -1
  626. package/dist/vscode/__tests__/index.test.js.map +1 -1
  627. package/dist/vscode/index.js.map +1 -1
  628. package/dist/wiki/__tests__/cjk-tokenize.test.js.map +1 -1
  629. package/dist/wiki/__tests__/lint.test.js.map +1 -1
  630. package/dist/wiki/__tests__/query.test.js.map +1 -1
  631. package/dist/wiki/__tests__/storage.test.js.map +1 -1
  632. package/dist/wiki/__tests__/test-helpers.d.ts.map +1 -1
  633. package/dist/wiki/lint.js.map +1 -1
  634. package/dist/wiki/query.js.map +1 -1
  635. package/dist/wiki/storage.js.map +1 -1
  636. package/package.json +2 -2
  637. package/plugins/oh-my-codex/.codex-plugin/plugin.json +1 -1
  638. package/plugins/oh-my-codex/skills/omx-setup/SKILL.md +10 -5
  639. package/plugins/oh-my-codex/skills/ralph/SKILL.md +4 -2
  640. package/plugins/oh-my-codex/skills/ralplan/SKILL.md +5 -2
  641. package/skills/omx-setup/SKILL.md +10 -5
  642. package/skills/ralph/SKILL.md +4 -2
  643. package/skills/ralplan/SKILL.md +5 -2
  644. package/src/scripts/__tests__/codex-native-hook.test.ts +20497 -12377
  645. package/src/scripts/__tests__/fixtures/issue-3138-installed-smoke.ts +734 -0
  646. package/src/scripts/__tests__/notify-fallback-delivery.test.ts +177 -0
  647. package/src/scripts/__tests__/notify-state-io.test.ts +73 -0
  648. package/src/scripts/__tests__/role-intent-bootstrap-e2e-3181.test.ts +215 -0
  649. package/src/scripts/__tests__/role-intent-durable-recovery-3181.test.ts +193 -0
  650. package/src/scripts/__tests__/smoke-packed-install.test.ts +991 -4
  651. package/src/scripts/codex-native-hook.ts +918 -172
  652. package/src/scripts/notify-fallback-delivery.ts +416 -0
  653. package/src/scripts/notify-fallback-watcher.ts +132 -40
  654. package/src/scripts/notify-hook/auto-nudge.ts +43 -12
  655. package/src/scripts/notify-hook/managed-tmux.ts +149 -12
  656. package/src/scripts/notify-hook/ralph-session-resume.ts +37 -36
  657. package/src/scripts/notify-hook/state-io.ts +100 -13
  658. package/src/scripts/notify-hook/tmux-injection.ts +38 -21
  659. package/src/scripts/notify-hook/visual-verdict.ts +22 -12
  660. package/src/scripts/notify-hook.ts +335 -90
  661. package/src/scripts/smoke-packed-install.ts +1829 -6
@@ -17,22 +17,45 @@ import {
17
17
  } from "../state/skill-active.js";
18
18
  import {
19
19
  isTrustedSubagentThread,
20
+ OMX_ADAPTED_PROVENANCE,
21
+ attestLeaderThread,
20
22
  readSubagentSessionSummary,
21
23
  readSubagentSessionLedger,
22
24
  readSubagentTrackingState,
25
+ readSubagentTrackingStateStrict,
26
+ recordSubagentTurn,
23
27
  recordSubagentTurnForSession,
28
+ resolveInstalledRoleName,
24
29
  } from "../subagents/tracker.js";
30
+ import {
31
+ bindAndPublishAdaptedRole,
32
+ NATIVE_SUBAGENT_ROLE_ROUTING_MARKER_TTL_MS,
33
+ recoverAdaptedRoleBindings,
34
+ } from "../subagents/adapted-role-binding.js";
35
+ import { readRoleRoutingMarker, writeRoleRoutingMarker } from "../subagents/role-routing-marker.js";
25
36
  import { resolveCanonicalTeamStateRoot, resolveWorkerNotifyTeamStateRootPath } from "../team/state-root.js";
26
37
  import { inferTerminalLifecycleOutcome } from "../runtime/run-outcome.js";
27
38
  import {
39
+ appendPromptSessionProvenanceRejection,
28
40
  appendToLog,
41
+ isSessionPointerLaunchAbort,
29
42
  isSessionStale,
30
43
  isSessionStateUsable,
44
+ normalizeSessionId,
45
+ readSessionPointer,
31
46
  readSessionState,
32
47
  readUsableSessionState,
33
48
  reconcileNativeSessionStart,
49
+ resolveSessionPointerContext,
34
50
  type SessionState,
35
51
  } from "../hooks/session.js";
52
+ import {
53
+ evaluateResolvedPromptTurn,
54
+ extractSelectedTargetOwnerEvidence,
55
+ preflightSelectedTargetOwner,
56
+ type PromptThreadFacts,
57
+ type ResolvedPromptTurnContext,
58
+ } from "../hooks/prompt-session-provenance.js";
36
59
  import {
37
60
  appendTeamEvent,
38
61
  readTeamLeaderAttention,
@@ -42,13 +65,20 @@ import {
42
65
  writeTeamLeaderAttention,
43
66
  writeTeamPhase,
44
67
  } from "../team/state.js";
45
- import { codexAgentsDir, omxNotepadPath, projectCodexAgentsDir, resolveProjectMemoryPath } from "../utils/paths.js";
68
+ import { omxNotepadPath, resolveProjectMemoryPath } from "../utils/paths.js";
46
69
  import { findGitLayout } from "../utils/git-layout.js";
47
- import { getBaseStateDir, getStateFilePath, getStatePath, getAuthoritativeActiveStatePaths } from "../mcp/state-paths.js";
48
70
  import {
49
- detectKeywords,
50
- detectPrimaryKeyword,
71
+ getAuthoritativeActiveStatePaths,
72
+ getBaseStateDir,
73
+ getStateFilePath,
74
+ getStatePath,
75
+ resolveWritableStateScope,
76
+ WRITABLE_STATE_SCOPE_ERRORS,
77
+ } from "../mcp/state-paths.js";
78
+ import {
79
+ classifyKeywordInput,
51
80
  recordSkillActivation,
81
+ type KeywordInputClassification,
52
82
  type SkillActiveState,
53
83
  } from "../hooks/keyword-detector.js";
54
84
  import { buildDeepInterviewConfigInstruction } from "../hooks/deep-interview-config-instruction.js";
@@ -59,6 +89,7 @@ import {
59
89
  normalizeAutoNudgeSignatureText,
60
90
  resolveEffectiveAutoNudgeResponse,
61
91
  } from "./notify-hook/auto-nudge.js";
92
+ import { probeActualTmuxInstanceEvidence, tmuxEvidenceBindsCandidate } from "./notify-hook/managed-tmux.js";
62
93
  import {
63
94
  SLOPPY_FALLBACK_GROUNDING_PATTERNS,
64
95
  SLOPPY_FALLBACK_IMPLEMENTATION_CONTEXT_PATTERNS,
@@ -89,7 +120,6 @@ import {
89
120
  onSessionStart as buildWikiSessionStartContext,
90
121
  } from "../wiki/lifecycle.js";
91
122
  import { readAutoresearchCompletionStatus, readAutoresearchModeStateForActiveDecision } from "../autoresearch/skill-validation.js";
92
- import { AGENT_DEFINITIONS } from "../agents/definitions.js";
93
123
  import { deriveAutopilotChildPhase, normalizeAutopilotPhase } from "../autopilot/fsm.js";
94
124
  import {
95
125
  CONDUCTOR_ORCHESTRATION_METADATA_PREFIXES,
@@ -98,9 +128,13 @@ import {
98
128
  NATIVE_SUBAGENT_SUPPORT_BLOCKER_FILE,
99
129
  actionKindForConductorArtifact,
100
130
  authorizeConductorAction,
131
+ buildRoleRoutingUnavailableGuidance,
101
132
  buildUnsupportedNativeSubagentGuidance,
102
133
  classifyConductorArtifactKind,
134
+ isNativeSubagentSpawnToolName,
135
+ isRoleRoutingUnavailableEvidence,
103
136
  isUnsupportedNativeSubagentEvidence,
137
+ parseRoleIntentCorrelationToken,
104
138
  resolveNativeSubagentSupportStatus,
105
139
  type NativeSubagentUnsupportedReason,
106
140
  } from "../leader/contract.js";
@@ -130,7 +164,6 @@ import {
130
164
  } from "../question/deep-interview.js";
131
165
  import { readAutopilotDeepInterviewQuestionWaitState } from "../question/autopilot-wait.js";
132
166
  import {
133
- buildDocumentRefreshAdvisoryOutput,
134
167
  evaluateFinalHandoffDocumentRefresh,
135
168
  isFinalHandoffDocumentRefreshCandidate,
136
169
  } from "../document-refresh/enforcer.js";
@@ -209,9 +242,6 @@ const RALPH_LIVE_RISK_PATTERNS = [
209
242
  /\b(?:database|db|terraform|kubectl|kubernetes|aws|gcp|azure|external|destructive)\b/i,
210
243
  /\b(?:telegram|vps|service|restart|send|notify|notification|notifications|cron)\b/i,
211
244
  ] as const;
212
- const KNOWN_TYPED_AGENT_ROLES = new Set(Object.keys(AGENT_DEFINITIONS));
213
- let installedTypedAgentRoleNamesCacheKey = "";
214
- let installedTypedAgentRoleNamesCache: Set<string> = new Set();
215
245
  const RALPH_TASK_TEXT_FIELDS = [
216
246
  "task_description",
217
247
  "taskDescription",
@@ -237,6 +267,19 @@ function safeString(value: unknown): string {
237
267
  return typeof value === "string" ? value : "";
238
268
  }
239
269
 
270
+ async function resolveVerifiedOwnerOmxSessionId(): Promise<string | undefined> {
271
+ const candidate = normalizeSessionId(process.env.OMX_SESSION_ID);
272
+ if (!candidate) return undefined;
273
+ const evidence = await probeActualTmuxInstanceEvidence(process.env.TMUX_PANE);
274
+ return tmuxEvidenceBindsCandidate(evidence, candidate) ? candidate : undefined;
275
+ }
276
+
277
+ function isImplicitWritableScopeFailure(error: unknown): boolean {
278
+ return error instanceof Error
279
+ && (error.message === WRITABLE_STATE_SCOPE_ERRORS.unboundEnvironment
280
+ || error.message === WRITABLE_STATE_SCOPE_ERRORS.unusableSession);
281
+ }
282
+
240
283
  function safeObject(value: unknown): Record<string, unknown> {
241
284
  return value && typeof value === "object" ? value as Record<string, unknown> : {};
242
285
  }
@@ -332,8 +375,10 @@ interface NativeSubagentSessionStartMetadata {
332
375
  parentThreadId: string;
333
376
  agentNickname?: string;
334
377
  agentRole?: string;
378
+ correlationToken?: string;
335
379
  }
336
380
 
381
+
337
382
  function readBoundedFirstLineSync(path: string): string {
338
383
  const fd = openSync(path, "r");
339
384
  try {
@@ -362,6 +407,20 @@ function readBoundedFirstLineSync(path: string): string {
362
407
  }
363
408
  }
364
409
 
410
+ function selectAuthoritativeTaskName(
411
+ threadSpawn: unknown,
412
+ subagent: unknown,
413
+ payload: unknown,
414
+ ): { present: boolean; value: unknown } {
415
+ for (const obj of [threadSpawn, subagent, payload]) {
416
+ if (obj && typeof obj === "object" && "task_name" in obj) {
417
+ return { present: true, value: (obj as Record<string, unknown>).task_name };
418
+ }
419
+ }
420
+ return { present: false, value: undefined };
421
+ }
422
+
423
+
365
424
  function readNativeSubagentSessionStartMetadata(transcriptPath: string): NativeSubagentSessionStartMetadata | null {
366
425
  const normalizedPath = transcriptPath.trim();
367
426
  if (!normalizedPath) return null;
@@ -379,18 +438,44 @@ function readNativeSubagentSessionStartMetadata(transcriptPath: string): NativeS
379
438
  const parentThreadId = safeString(threadSpawn.parent_thread_id).trim();
380
439
  if (!parentThreadId) return null;
381
440
 
382
- const agentNickname = safeString(threadSpawn.agent_nickname ?? payload.agent_nickname).trim();
383
- const agentRole = safeString(threadSpawn.agent_role ?? payload.agent_role).trim();
441
+ const agentNicknameCarrierValues = [
442
+ threadSpawn.agent_nickname ?? threadSpawn.agentNickname,
443
+ subagent.agent_nickname ?? subagent.agentNickname,
444
+ payload.agent_nickname ?? payload.agentNickname,
445
+ ];
446
+ const agentNickname = safeString(agentNicknameCarrierValues[0]).trim();
447
+ const authoritativeTaskName = selectAuthoritativeTaskName(threadSpawn, subagent, payload);
448
+ const correlationToken = authoritativeTaskName.present
449
+ ? parseRoleIntentCorrelationToken(authoritativeTaskName.value)
450
+ : undefined;
451
+ const agentRole = safeString(
452
+ threadSpawn.agent_role
453
+ ?? threadSpawn.agentRole
454
+ ?? threadSpawn.agent_type
455
+ ?? threadSpawn.agentType
456
+ ?? payload.agent_role
457
+ ?? payload.agentRole
458
+ ?? payload.agent_type
459
+ ?? payload.agentType,
460
+ ).trim();
384
461
  return {
385
462
  parentThreadId,
386
463
  ...(agentNickname ? { agentNickname } : {}),
387
464
  ...(agentRole ? { agentRole } : {}),
465
+ ...(correlationToken ? { correlationToken } : {}),
388
466
  };
467
+
389
468
  } catch {
390
469
  return null;
391
470
  }
392
471
  }
393
472
 
473
+ function reportRoleRoutingBindingFailure(error: unknown): void {
474
+ console.error(
475
+ `[omx] SECURITY: native adapted role binding did not complete; retained binding will be recovered: ${error instanceof Error ? error.message : String(error)}`,
476
+ );
477
+ }
478
+
394
479
  async function recordNativeSubagentSessionStart(
395
480
  cwd: string,
396
481
  canonicalSessionId: string,
@@ -400,25 +485,79 @@ async function recordNativeSubagentSessionStart(
400
485
  ): Promise<void> {
401
486
  const parentThreadId = metadata.parentThreadId.trim();
402
487
  const childThreadId = childSessionId.trim();
488
+ const correlationSessionId = canonicalSessionId.trim() || parentThreadId;
403
489
  const trackingSessionIds = [...new Set([
404
490
  canonicalSessionId.trim(),
405
491
  parentThreadId,
406
492
  ].filter(Boolean))];
407
- for (const sessionId of trackingSessionIds) {
408
- if (parentThreadId && parentThreadId !== childThreadId) {
493
+ let adaptedRoleIntent: {
494
+ role: string;
495
+ provenanceKind: typeof OMX_ADAPTED_PROVENANCE;
496
+ } | null = null;
497
+
498
+ if (!metadata.agentRole && correlationSessionId && parentThreadId) {
499
+ try {
500
+ const bound = bindAndPublishAdaptedRole(
501
+ cwd,
502
+ getBaseStateDir(cwd),
503
+ {
504
+ correlationSessionId,
505
+ parentThreadId,
506
+ correlationToken: metadata.correlationToken,
507
+ },
508
+ (state, intent) => {
509
+ let next = state;
510
+ for (const sessionId of trackingSessionIds) {
511
+ if (parentThreadId && parentThreadId !== childThreadId) {
512
+ next = recordSubagentTurn(next, {
513
+ sessionId,
514
+ threadId: parentThreadId,
515
+ kind: "leader",
516
+ });
517
+ }
518
+ next = recordSubagentTurn(next, {
519
+ sessionId,
520
+ threadId: childThreadId,
521
+ kind: "subagent",
522
+ ...(parentThreadId && parentThreadId !== childThreadId ? { leaderThreadId: parentThreadId } : {}),
523
+ mode: intent.role,
524
+ role: intent.role,
525
+ provenanceKind: intent.provenanceKind,
526
+ });
527
+ }
528
+ return next;
529
+ },
530
+ );
531
+ adaptedRoleIntent = bound ? { role: bound.role, provenanceKind: OMX_ADAPTED_PROVENANCE } : null;
532
+ } catch (error) {
533
+ reportRoleRoutingBindingFailure(error);
534
+ throw error;
535
+ }
536
+ }
537
+
538
+ if (!adaptedRoleIntent) {
539
+ for (const sessionId of trackingSessionIds) {
540
+ if (parentThreadId && parentThreadId !== childThreadId) {
541
+ await recordSubagentTurnForSession(cwd, {
542
+ sessionId,
543
+ threadId: parentThreadId,
544
+ kind: "leader",
545
+ }).catch(() => {});
546
+ }
409
547
  await recordSubagentTurnForSession(cwd, {
410
548
  sessionId,
411
- threadId: parentThreadId,
412
- kind: 'leader',
549
+ threadId: childThreadId,
550
+ kind: "subagent",
551
+ ...(parentThreadId && parentThreadId !== childThreadId ? { leaderThreadId: parentThreadId } : {}),
552
+ mode: metadata.agentRole,
413
553
  }).catch(() => {});
414
554
  }
415
- await recordSubagentTurnForSession(cwd, {
416
- sessionId,
417
- threadId: childThreadId,
418
- kind: 'subagent',
419
- ...(parentThreadId && parentThreadId !== childThreadId ? { leaderThreadId: parentThreadId } : {}),
420
- mode: metadata.agentRole,
421
- }).catch(() => {});
555
+ refreshNativeSubagentRoleRoutingMarker(
556
+ cwd,
557
+ getBaseStateDir(cwd),
558
+ correlationSessionId,
559
+ parentThreadId,
560
+ );
422
561
  }
423
562
  await appendToLog(cwd, {
424
563
  event: "subagent_session_start",
@@ -524,6 +663,32 @@ async function isNativeSubagentHook(
524
663
  ));
525
664
  }
526
665
 
666
+ async function readNativePromptThreadFacts(
667
+ cwd: string,
668
+ nativeSessionId: string,
669
+ threadId: string,
670
+ currentSessionState?: SessionState | null,
671
+ ): Promise<PromptThreadFacts | undefined> {
672
+ const candidateIds = [nativeSessionId, threadId].map((value) => value.trim()).filter(Boolean);
673
+ if (candidateIds.length === 0) return undefined;
674
+ const currentNativeIds = new Set([
675
+ safeString(currentSessionState?.native_session_id).trim(),
676
+ safeString(currentSessionState?.codex_session_id).trim(),
677
+ safeString(currentSessionState?.owner_codex_session_id).trim(),
678
+ ].filter(Boolean));
679
+ const currentTargetAnchored = Boolean(nativeSessionId && currentNativeIds.has(nativeSessionId));
680
+ const trackingState = await readSubagentTrackingState(cwd).catch(() => null);
681
+ if (!trackingState) return currentTargetAnchored ? { currentTargetAnchored: true, rootOrDrift: true } : undefined;
682
+ const roots = Object.values(trackingState.sessions).flatMap((session) => (
683
+ candidateIds.some((candidate) => isTrustedSubagentThread(session, candidate))
684
+ ? [session.session_id]
685
+ : []
686
+ ));
687
+ return roots.length > 0 || currentTargetAnchored
688
+ ? { trackerRootOwnerSessionIds: roots, currentTargetAnchored, rootOrDrift: currentTargetAnchored }
689
+ : undefined;
690
+ }
691
+
527
692
  function shouldSuppressSubagentLifecycleHookDispatch(): boolean {
528
693
  const config = getNotificationConfig();
529
694
  if (config?.includeChildAgents === true) return false;
@@ -691,8 +856,8 @@ function readPromptText(payload: CodexHookPayload): string {
691
856
  payload.userPrompt,
692
857
  ];
693
858
  for (const candidate of candidates) {
694
- const value = safeString(candidate).trim();
695
- if (value) return value;
859
+ const value = safeString(candidate);
860
+ if (value.trim()) return value;
696
861
  }
697
862
  return "";
698
863
  }
@@ -2176,25 +2341,29 @@ function buildTeamHelpInstruction(cwd: string, payload?: CodexHookPayload): stri
2176
2341
  }).teamHelpInstruction;
2177
2342
  }
2178
2343
 
2344
+ function isNativeOutsideTmuxUserPrompt(cwd: string, payload: CodexHookPayload, sessionId?: string): boolean {
2345
+ const environment = resolveExecutionEnvironment(cwd, {
2346
+ hookEventName: "UserPromptSubmit",
2347
+ payload,
2348
+ canonicalSessionId: sessionId ?? "",
2349
+ nativeSessionId: safeString(payload.session_id ?? payload.sessionId).trim(),
2350
+ });
2351
+ return environment.launcher === "native" && environment.transport === "outside-tmux";
2352
+ }
2353
+
2179
2354
  function buildNativeOutsideTmuxTeamPromptBlockState(
2180
- prompt: string,
2355
+ classification: KeywordInputClassification,
2181
2356
  cwd: string,
2182
2357
  payload: CodexHookPayload,
2183
2358
  sessionId?: string,
2184
2359
  threadId?: string,
2185
2360
  turnId?: string,
2186
2361
  ): SkillActiveState | null {
2187
- if (!readTeamModeConfig(cwd).enabled) return null;
2188
- const match = detectPrimaryKeyword(prompt);
2362
+ const teamMode = readTeamModeConfig(cwd);
2363
+ const match = classification.matches.filter((entry) => teamMode.enabled || entry.skill !== "team")[0] ?? null;
2189
2364
  if (match?.skill !== "team") return null;
2190
2365
 
2191
- const environment = resolveExecutionEnvironment(cwd, {
2192
- hookEventName: "UserPromptSubmit",
2193
- payload,
2194
- canonicalSessionId: sessionId ?? "",
2195
- nativeSessionId: safeString(payload.session_id ?? payload.sessionId).trim(),
2196
- });
2197
- if (!(environment.launcher === "native" && environment.transport === "outside-tmux")) return null;
2366
+ if (!isNativeOutsideTmuxUserPrompt(cwd, payload, sessionId)) return null;
2198
2367
 
2199
2368
  const nowIso = new Date().toISOString();
2200
2369
  return {
@@ -2230,13 +2399,19 @@ function buildAutopilotPromptActivationNote(
2230
2399
  const nativeSubagentSupport = resolveNativeSubagentSupportStatus({
2231
2400
  payload: options.payload,
2232
2401
  persistedSupportBlocker: readJsonSyncIfExists(nativeSubagentSupportBlockerPath(stateDir)),
2402
+ persistedRoleRoutingMarker: readRoleRoutingMarker(stateDir, {
2403
+ cwd: options.cwd,
2404
+ sessionId: options.sessionId ?? "",
2405
+ }),
2233
2406
  persistedCapacityBlocker: readJsonSyncIfExists(nativeSubagentCapacityBlockerPath(stateDir)),
2234
2407
  cwd: options.cwd,
2235
2408
  sessionId: options.sessionId,
2236
2409
  });
2237
2410
  const conductorGuidance = nativeSubagentSupport.status === "unsupported"
2238
2411
  ? buildUnsupportedNativeSubagentGuidance(nativeSubagentSupport)
2239
- : `${LEADER_CONDUCTOR_BLOCK} ${LEADER_CONDUCTOR_REUSE_AND_LEDGER_GUIDANCE}`;
2412
+ : nativeSubagentSupport.status === "role_routing_unavailable"
2413
+ ? buildRoleRoutingUnavailableGuidance(nativeSubagentSupport)
2414
+ : `${LEADER_CONDUCTOR_BLOCK} ${LEADER_CONDUCTOR_REUSE_AND_LEDGER_GUIDANCE}`;
2240
2415
  return [
2241
2416
  `Autopilot protocol: the durable default chain is $deep-interview -> $ralplan -> $ultragoal${teamHandoff} -> $code-review -> $ultraqa (deep-interview -> ralplan -> ultragoal -> code-review -> ultraqa).`,
2242
2417
  "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.",
@@ -2259,30 +2434,40 @@ function formatExecutionHandoffList(cwd: string): string {
2259
2434
  }
2260
2435
 
2261
2436
  function buildAdditionalContextMessage(
2262
- prompt: string,
2437
+ classification: KeywordInputClassification,
2263
2438
  skillState?: SkillActiveState | null,
2264
2439
  cwd: string = process.cwd(),
2265
2440
  payload?: CodexHookPayload,
2266
2441
  ): string | null {
2442
+ const prompt = classification.originalText;
2267
2443
  if (!prompt) return null;
2268
2444
  const promptPriorityMessage = buildPromptPriorityMessage(prompt);
2269
2445
  if (payload && isTypedAgentRolePayload(payload)) {
2270
2446
  return promptPriorityMessage;
2271
2447
  }
2272
2448
  const teamMode = readTeamModeConfig(cwd);
2273
- const matches = detectKeywords(prompt).filter((entry) => teamMode.enabled || entry.skill !== "team");
2449
+ const matches = classification.matches.filter((entry) => teamMode.enabled || entry.skill !== "team");
2274
2450
  const match = matches[0] ?? null;
2275
2451
  if (!match) {
2452
+ const markedQuestionAnswer = classification.reservedInput === "omx-question-answered";
2276
2453
  const continuedSkill = safeString(skillState?.skill).trim();
2277
- if (!continuedSkill) return promptPriorityMessage;
2454
+ const eligibleMarkedContinuation = markedQuestionAnswer
2455
+ && skillState?.active === true
2456
+ && (continuedSkill === "autopilot" || continuedSkill === "deep-interview");
2457
+ const eligibleOrdinaryContinuation = classification.reservedInput === null
2458
+ && !classification.hasExplicitLikeInvocation
2459
+ && skillState?.active === true
2460
+ && Boolean(continuedSkill);
2461
+ if (!eligibleMarkedContinuation && !eligibleOrdinaryContinuation) return promptPriorityMessage;
2278
2462
  const deepInterviewPromptActivationNote = skillState?.initialized_mode === "deep-interview"
2279
2463
  ? buildDeepInterviewQuestionBridgeInstruction(cwd, payload)
2280
2464
  : null;
2281
2465
  const deepInterviewConfigPromptActivationNote = buildDeepInterviewConfigInstruction(cwd, skillState);
2282
- const markedQuestionAnswer = /^\s*\[omx question answered\]/i.test(prompt);
2283
2466
  const autopilotPromptActivationNote = buildAutopilotPromptActivationNote(skillState, { markedQuestionAnswer, cwd, payload, sessionId: safeString(skillState?.session_id).trim() });
2284
2467
  return [
2285
- `OMX native UserPromptSubmit continued active workflow skill "${continuedSkill}".`,
2468
+ markedQuestionAnswer
2469
+ ? `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.`
2470
+ : `OMX native UserPromptSubmit continued active workflow skill "${continuedSkill}".`,
2286
2471
  promptPriorityMessage,
2287
2472
  skillState?.initialized_mode && skillState.initialized_state_path
2288
2473
  ? buildSkillStateCliInstruction(skillState.initialized_mode, skillState.initialized_state_path)
@@ -2296,29 +2481,6 @@ function buildAdditionalContextMessage(
2296
2481
  const detectedKeywordMessage = matches.length > 1
2297
2482
  ? `OMX native UserPromptSubmit detected workflow keywords ${matches.map((entry) => `"${entry.keyword}" -> ${entry.skill}`).join(", ")}.`
2298
2483
  : `OMX native UserPromptSubmit detected workflow keyword "${match.keyword}" -> ${match.skill}.`;
2299
- const continuedSkill = safeString(skillState?.skill).trim();
2300
- if (
2301
- continuedSkill
2302
- && continuedSkill !== match.skill
2303
- && /^\s*\[omx question answered\]/i.test(prompt)
2304
- ) {
2305
- const deepInterviewPromptActivationNote = skillState?.initialized_mode === "deep-interview"
2306
- ? buildDeepInterviewQuestionBridgeInstruction(cwd, payload)
2307
- : null;
2308
- const deepInterviewConfigPromptActivationNote = buildDeepInterviewConfigInstruction(cwd, skillState);
2309
- const autopilotPromptActivationNote = buildAutopilotPromptActivationNote(skillState, { markedQuestionAnswer: true, cwd, payload, sessionId: safeString(skillState?.session_id).trim() });
2310
- return [
2311
- `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.`,
2312
- promptPriorityMessage,
2313
- skillState?.initialized_mode && skillState.initialized_state_path
2314
- ? buildSkillStateCliInstruction(skillState.initialized_mode, skillState.initialized_state_path)
2315
- : null,
2316
- deepInterviewPromptActivationNote,
2317
- deepInterviewConfigPromptActivationNote,
2318
- autopilotPromptActivationNote,
2319
- "Follow AGENTS.md routing and preserve workflow transition and planning-safety rules.",
2320
- ].filter(Boolean).join(" ");
2321
- }
2322
2484
  const activeSkills = Array.isArray(skillState?.active_skills)
2323
2485
  ? skillState.active_skills.map((entry) => entry.skill)
2324
2486
  : [];
@@ -2740,8 +2902,6 @@ async function buildModeBasedStopOutput(
2740
2902
  reason: `OMX ${mode} is still active (phase: ${phase}; ${diagnostic}); continue the task and gather fresh verification evidence before stopping.`,
2741
2903
  stopReason: `${mode}_${phase}`,
2742
2904
  systemMessage,
2743
- statePath,
2744
- canonicalDisagreement,
2745
2905
  };
2746
2906
  }
2747
2907
 
@@ -3164,33 +3324,68 @@ function readPayloadAgentRole(payload: CodexHookPayload): string {
3164
3324
  ).trim().toLowerCase();
3165
3325
  }
3166
3326
 
3167
- function readInstalledTypedAgentRoleNames(): Set<string> {
3168
- const cacheKey = [codexAgentsDir(), projectCodexAgentsDir()].join("|");
3169
- if (installedTypedAgentRoleNamesCacheKey === cacheKey) return installedTypedAgentRoleNamesCache;
3170
-
3171
- const installedRoleNames = new Set<string>();
3172
- for (const agentsDir of [codexAgentsDir(), projectCodexAgentsDir()]) {
3173
- try {
3174
- for (const entry of readdirSync(agentsDir, { withFileTypes: true })) {
3175
- if (!entry.isFile() || !entry.name.endsWith(".toml")) continue;
3176
- installedRoleNames.add(entry.name.slice(0, -5).trim().toLowerCase());
3177
- }
3178
- } catch {
3179
- // Ignore missing or unreadable agent directories; built-in definitions remain authoritative.
3180
- }
3181
- }
3182
-
3183
- installedTypedAgentRoleNamesCacheKey = cacheKey;
3184
- installedTypedAgentRoleNamesCache = installedRoleNames;
3185
- return installedTypedAgentRoleNamesCache;
3327
+ function readRequestedSpawnRole(payload: CodexHookPayload): string {
3328
+ const toolName = safeString(payload.tool_name).trim();
3329
+ if (!isNativeSubagentSpawnToolName(toolName)) return "";
3330
+ const toolInput = safeObject(payload.tool_input);
3331
+ return safeString(
3332
+ toolInput.agent_role
3333
+ ?? toolInput.agentRole
3334
+ ?? toolInput.agent_type
3335
+ ?? toolInput.agentType,
3336
+ ).trim().toLowerCase();
3186
3337
  }
3187
3338
 
3188
3339
  function isTypedAgentRolePayload(payload: CodexHookPayload): boolean {
3189
3340
  const agentRole = readPayloadAgentRole(payload);
3190
- return agentRole !== "" && (
3191
- KNOWN_TYPED_AGENT_ROLES.has(agentRole)
3192
- || readInstalledTypedAgentRoleNames().has(agentRole)
3193
- );
3341
+ return agentRole !== "" && resolveInstalledRoleName(agentRole) !== null;
3342
+ }
3343
+
3344
+ // #3181: detect a runtime-set native subagent PreToolUse. The runtime sets
3345
+ // source.subagent.thread_spawn (not agent-controlled tool arguments), so the mere
3346
+ // STRUCTURAL PRESENCE of that carrier — even if its parent id is blank, malformed, or
3347
+ // null — marks a child/subagent turn that must never be attested as the session leader.
3348
+ // This is a negative security decision, so it fails closed on any present carrier rather
3349
+ // than requiring a well-formed parent id.
3350
+ function hasSubagentThreadSpawnProvenance(payload: CodexHookPayload): boolean {
3351
+ const source = payload.source;
3352
+ if (!source || typeof source !== "object") return false;
3353
+ const subagent = (source as Record<string, unknown>).subagent;
3354
+ if (!subagent || typeof subagent !== "object") return false;
3355
+ return Object.prototype.hasOwnProperty.call(subagent, "thread_spawn");
3356
+ }
3357
+
3358
+ // #3181: positive counter-evidence guard. A thread durably tracked as a subagent in ANY
3359
+ // session must never be attested as a leader, even if the current payload lacks typed-role
3360
+ // or thread_spawn provenance. This closes the source-less/untyped child escalation where a
3361
+ // child recorded at its SessionStart later self-promotes via a source-less PreToolUse.
3362
+ async function isThreadTrackedAsSubagent(cwd: string, threadId: string): Promise<boolean> {
3363
+ const id = threadId.trim();
3364
+ if (!id) return false;
3365
+ // Strict read: an unreadable/corrupt tracker fails closed (treated as "is a subagent")
3366
+ // so corrupt evidence cannot let a source-less child reconcile/attest as leader.
3367
+ const read = await readSubagentTrackingStateStrict(cwd);
3368
+ if (!read.ok) return true;
3369
+ for (const session of Object.values(read.state.sessions)) {
3370
+ if (session.threads?.[id]?.kind === "subagent") return true;
3371
+ }
3372
+ return false;
3373
+ }
3374
+
3375
+ function buildNativeUnknownRolePreToolUseOutput(
3376
+ payload: CodexHookPayload,
3377
+ ): Record<string, unknown> | null {
3378
+ const requestedRole = readRequestedSpawnRole(payload);
3379
+ if (!requestedRole || resolveInstalledRoleName(requestedRole) !== null) return null;
3380
+ return {
3381
+ decision: "block",
3382
+ reason: "Native typed-subagent dispatch denied: supplied agent_type/agent_role is unknown or not installed.",
3383
+ hookSpecificOutput: {
3384
+ hookEventName: "PreToolUse",
3385
+ additionalContext:
3386
+ "Use an installed OMX role for native agent_type/agent_role dispatch. On a role-routing-unavailable surface, record a validated OMX adapted role intent before spawning without fabricating agent_type.",
3387
+ },
3388
+ };
3194
3389
  }
3195
3390
 
3196
3391
  function readPayloadTurnId(payload: CodexHookPayload): string {
@@ -3291,6 +3486,27 @@ async function recordNativeSubagentSupportBlocker(
3291
3486
  }, null, 2));
3292
3487
  }
3293
3488
 
3489
+
3490
+ function refreshNativeSubagentRoleRoutingMarker(
3491
+ cwd: string,
3492
+ stateDir: string,
3493
+ sessionId: string,
3494
+ parentThreadId?: string,
3495
+ ): void {
3496
+ const marker = readRoleRoutingMarker(stateDir, {
3497
+ cwd,
3498
+ sessionId,
3499
+ ...(parentThreadId ? { parentThreadId } : {}),
3500
+ });
3501
+ if (!marker) return;
3502
+ const nowMs = Date.now();
3503
+ writeRoleRoutingMarker(stateDir, {
3504
+ ...marker,
3505
+ observed_at: new Date(nowMs).toISOString(),
3506
+ expires_at: new Date(nowMs + NATIVE_SUBAGENT_ROLE_ROUTING_MARKER_TTL_MS).toISOString(),
3507
+ });
3508
+ }
3509
+
3294
3510
  function summarizeCapacityFailure(text: string): string {
3295
3511
  const normalized = text.replace(/\s+/g, " ").trim();
3296
3512
  if (!normalized) return "agent thread limit reached";
@@ -3390,12 +3606,14 @@ async function resolveInternalSessionIdForPayload(
3390
3606
  cwd: string,
3391
3607
  payloadSessionId: string,
3392
3608
  stateDir?: string,
3609
+ pointerSessionState?: SessionState | null,
3610
+ allowUnboundPayloadFallback = true,
3393
3611
  ): Promise<string> {
3394
- const currentSession = stateDir
3612
+ const currentSession = pointerSessionState ?? (stateDir
3395
3613
  ? await readUsableSessionStateFromStateDir(cwd, stateDir)
3396
- : await readUsableSessionState(cwd);
3614
+ : await readUsableSessionState(cwd));
3397
3615
  const canonicalSessionId = safeString(currentSession?.session_id).trim();
3398
- if (!canonicalSessionId) return payloadSessionId;
3616
+ if (!canonicalSessionId) return allowUnboundPayloadFallback ? payloadSessionId : "";
3399
3617
 
3400
3618
  const nativeSessionId = safeString(currentSession?.native_session_id).trim();
3401
3619
  const ownerOmxSessionId = safeString(currentSession?.owner_omx_session_id).trim();
@@ -3403,7 +3621,7 @@ async function resolveInternalSessionIdForPayload(
3403
3621
  if (payloadSessionId === canonicalSessionId) return canonicalSessionId;
3404
3622
  if (nativeSessionId && payloadSessionId === nativeSessionId) return canonicalSessionId;
3405
3623
  if (ownerOmxSessionId && payloadSessionId === ownerOmxSessionId) return canonicalSessionId;
3406
- return payloadSessionId;
3624
+ return allowUnboundPayloadFallback ? payloadSessionId : "";
3407
3625
  }
3408
3626
 
3409
3627
  async function readRootSessionStateFromStateDir(stateDir: string): Promise<SessionState | null> {
@@ -3524,14 +3742,6 @@ const RALPLAN_EXECUTION_HANDOFF_SKILLS = new Set([
3524
3742
  "ultraqa",
3525
3743
  ]);
3526
3744
 
3527
- function isActiveDeepInterviewPhase(state: Record<string, unknown> | null): boolean {
3528
- if (!state || state.active !== true) return false;
3529
- const mode = safeString(state.mode).trim();
3530
- if (mode && mode !== "deep-interview") return false;
3531
- const phase = safeString(state.current_phase ?? state.currentPhase).trim().toLowerCase();
3532
- if (phase && (TERMINAL_MODE_PHASES.has(phase) || phase === "completing")) return false;
3533
- return true;
3534
- }
3535
3745
 
3536
3746
  function isInactiveCompletedDeepInterviewPhase(state: Record<string, unknown> | null): boolean {
3537
3747
  if (!state || state.active !== false) return false;
@@ -3653,8 +3863,14 @@ function isAllowedDeepInterviewArtifactPath(cwd: string, rawPath: string): boole
3653
3863
  return isAllowedPlanningArtifactPath(cwd, rawPath, DEEP_INTERVIEW_ALLOWED_WRITE_PREFIXES);
3654
3864
  }
3655
3865
 
3866
+ function isAllowedRalplanDraftPath(cwd: string, rawPath: string): boolean {
3867
+ const relativePath = normalizePlanningArtifactRelativePath(cwd, rawPath);
3868
+ return relativePath !== null && /^\.omx\/drafts\/[^/]+\.md$/.test(relativePath);
3869
+ }
3870
+
3656
3871
  function isAllowedRalplanArtifactPath(cwd: string, rawPath: string): boolean {
3657
- return isAllowedPlanningArtifactPath(cwd, rawPath, RALPLAN_ALLOWED_WRITE_PREFIXES);
3872
+ return isAllowedRalplanDraftPath(cwd, rawPath)
3873
+ || isAllowedPlanningArtifactPath(cwd, rawPath, RALPLAN_ALLOWED_WRITE_PREFIXES);
3658
3874
  }
3659
3875
 
3660
3876
  interface RalplanBeadsCommandClassification {
@@ -3874,9 +4090,72 @@ function resolveCommandRedirectTarget(target: string, assignments: Map<string, s
3874
4090
  return resolved !== undefined ? resolved : target;
3875
4091
  }
3876
4092
 
4093
+ // Masks redirect metacharacters (`<`/`>`) that appear INSIDE shell quotes so a
4094
+ // quoted regex/source value (e.g. `gh issue create --body '...>{1,2}...'` or
4095
+ // `omx state write --input '{"reason":"a>b"}'`) is not misread as a redirect
4096
+ // write target. Escaped quotes at the top level (`\'`, `\"`) are literal
4097
+ // characters and must NOT open a span, and `$'...'` ANSI-C quoting processes
4098
+ // backslash escapes (so `\'` does not close it) — otherwise a genuine `>`
4099
+ // redirect could be masked behind a false span and its write missed. Only
4100
+ // characters inside a real quoted span are masked; unquoted redirect operators
4101
+ // survive intact. Unterminated/ambiguous quoting fails closed: the original
4102
+ // command is returned unmasked so genuine redirects stay visible to the scan.
4103
+ function maskQuotedRedirectMetacharsForCommandScan(command: string): string {
4104
+ let masked = "";
4105
+ // null = unquoted; "'" = single quotes (no escapes); '"' = double quotes
4106
+ // (backslash escapes); "$'" = ANSI-C $'...' (backslash escapes, incl. \').
4107
+ let quote: "'" | "\"" | "$'" | null = null;
4108
+ for (let index = 0; index < command.length; index += 1) {
4109
+ const char = command[index] ?? "";
4110
+ if (quote === null) {
4111
+ // A backslash escapes the next character at the top level, so an escaped
4112
+ // quote is a literal char and must not open a quoted span.
4113
+ if (char === "\\") {
4114
+ masked += char;
4115
+ const next = command[index + 1];
4116
+ if (next !== undefined) {
4117
+ masked += next;
4118
+ index += 1;
4119
+ }
4120
+ continue;
4121
+ }
4122
+ if (char === "$" && command[index + 1] === "'") {
4123
+ quote = "$'";
4124
+ masked += "$'";
4125
+ index += 1;
4126
+ continue;
4127
+ }
4128
+ if (char === "'" || char === "\"") quote = char;
4129
+ masked += char;
4130
+ continue;
4131
+ }
4132
+ if ((quote === "\"" || quote === "$'") && char === "\\") {
4133
+ // Escapes inside double/ANSI-C spans keep the backslash and its escaped
4134
+ // char (masking a redirect metachar so quoted data cannot be a false
4135
+ // target); the escaped char never closes the span.
4136
+ masked += char;
4137
+ const next = command[index + 1];
4138
+ if (next !== undefined) {
4139
+ masked += next === "<" || next === ">" ? "_" : next;
4140
+ index += 1;
4141
+ }
4142
+ continue;
4143
+ }
4144
+ const closesSpan = quote === "$'" ? char === "'" : char === quote;
4145
+ if (closesSpan) {
4146
+ quote = null;
4147
+ masked += char;
4148
+ continue;
4149
+ }
4150
+ masked += char === "<" || char === ">" ? "_" : char;
4151
+ }
4152
+ if (quote !== null) return command;
4153
+ return masked;
4154
+ }
4155
+
3877
4156
  function extractDeepInterviewCommandRedirectTargets(command: string): string[] {
3878
4157
  const targets: string[] = [];
3879
- const commandOutsideHeredocBodies = stripHeredocBodiesForCommandScan(command);
4158
+ const commandOutsideHeredocBodies = maskQuotedRedirectMetacharsForCommandScan(stripHeredocBodiesForCommandScan(command));
3880
4159
  for (const match of commandOutsideHeredocBodies.matchAll(/(?:^|[^>])>{1,2}\s*(["']?)([^\s&|;<>]+)\1/g)) {
3881
4160
  const candidate = safeString(match[2]).trim();
3882
4161
  if (candidate && !isNullDeviceRedirectTarget(candidate)) targets.push(candidate);
@@ -3974,7 +4253,7 @@ function findGitSubcommandIndex(words: string[], startIndex: number): number | n
3974
4253
  }
3975
4254
 
3976
4255
 
3977
- function commandHasDeepInterviewWriteIntent(command: string): boolean {
4256
+ function commandHasDeepInterviewWriteIntent(command: string, depth = 0): boolean {
3978
4257
  return commandInvokesApplyPatch(command)
3979
4258
  || extractDeepInterviewCommandRedirectTargets(command).length > 0
3980
4259
  || /\btee\s+(?:-a\s+)?[^\s&|;]+/.test(command)
@@ -3985,7 +4264,19 @@ function commandHasDeepInterviewWriteIntent(command: string): boolean {
3985
4264
  || extractConductorBashMutations(command).length > 0
3986
4265
  || extractConductorInterpreterWrites(command).length > 0
3987
4266
  || commandHasDestructiveGitSubcommand(command)
3988
- || commandHasPackageInstallIntent(command);
4267
+ || commandHasPackageInstallIntent(command)
4268
+ // Recurse into wrapped shells (`bash -lc "cat > f"`, `eval`, `env`) and
4269
+ // command substitutions so a real redirect INSIDE a nested command string
4270
+ // is still classified as write intent. This is required because quoted
4271
+ // redirect metacharacters are now masked at the outer scan (#3119): the
4272
+ // mask removes false positives from quoted DATA values while nested-command
4273
+ // recursion re-detects genuine redirects, preserving fail-closed blocking.
4274
+ // The depth guard mirrors evaluateConductorBashWrite's nesting bound;
4275
+ // extractors return strict substrings, so recursion always terminates.
4276
+ || (depth < CONDUCTOR_BASH_MAX_NESTING_DEPTH && (
4277
+ extractNestedShellCommandStringsForStateScan(command).some((nested) => commandHasDeepInterviewWriteIntent(nested, depth + 1))
4278
+ || extractNestedCommandSubstitutionStringsForStateScan(command).some((nested) => commandHasDeepInterviewWriteIntent(nested, depth + 1))
4279
+ ));
3989
4280
  }
3990
4281
 
3991
4282
  function extractDeepInterviewCommandWriteTargets(command: string): string[] {
@@ -6738,6 +7029,128 @@ function isCompleteRalplanTerminalWritePayload(
6738
7029
  return true;
6739
7030
  }
6740
7031
 
7032
+ function hasUnquotedShellControlOrRedirection(command: string): boolean {
7033
+ let quote: "'" | '"' | null = null;
7034
+ for (let index = 0; index < command.length; index += 1) {
7035
+ const char = command[index] ?? "";
7036
+ if (char === "\\" && quote !== "'") {
7037
+ index += 1;
7038
+ continue;
7039
+ }
7040
+ if (char === "'" || char === '"') {
7041
+ quote = quote === char ? null : quote ?? char;
7042
+ continue;
7043
+ }
7044
+ if (!quote && ";&|()<>\n\r".includes(char)) return true;
7045
+ }
7046
+ return false;
7047
+ }
7048
+
7049
+ function suppliedSessionAliasesMatch(payload: Record<string, unknown>, sessionId: string): boolean {
7050
+ return [
7051
+ payload.session_id,
7052
+ payload.owner_omx_session_id,
7053
+ payload.codex_session_id,
7054
+ payload.owner_codex_session_id,
7055
+ ].filter((value) => value !== undefined)
7056
+ .every((value) => safeString(value).trim() === sessionId);
7057
+ }
7058
+
7059
+ function hasOnlyFinishedExplicitOutcomes(payload: Record<string, unknown>): boolean {
7060
+ const values = [
7061
+ payload.lifecycle_outcome,
7062
+ payload.lifecycleOutcome,
7063
+ payload.terminal_outcome,
7064
+ payload.terminalOutcome,
7065
+ payload.run_outcome,
7066
+ payload.runOutcome,
7067
+ ].filter((value) => value !== undefined);
7068
+ return values.every((value) => inferTerminalLifecycleOutcome(
7069
+ { lifecycle_outcome: value },
7070
+ { includeQuestionEnforcement: false },
7071
+ ) === "finished");
7072
+ }
7073
+
7074
+ function isCompleteDeepInterviewTerminalWritePayload(
7075
+ payload: Record<string, unknown>,
7076
+ activeState: Record<string, unknown>,
7077
+ sessionId: string,
7078
+ ): boolean {
7079
+ if (!sessionId) return false;
7080
+ const activeMode = safeString(activeState.mode).trim().toLowerCase();
7081
+ if (activeMode && activeMode !== "deep-interview") return false;
7082
+ const activeSessionId = safeString(
7083
+ activeState.session_id
7084
+ ?? activeState.owner_omx_session_id
7085
+ ?? activeState.codex_session_id
7086
+ ?? activeState.owner_codex_session_id,
7087
+ ).trim();
7088
+ if (activeSessionId && activeSessionId !== sessionId) return false;
7089
+
7090
+ const mode = safeString(payload.mode).trim().toLowerCase();
7091
+ const phase = safeString(payload.current_phase ?? payload.currentPhase).trim().toLowerCase();
7092
+ const payloadSessionId = safeString(payload.session_id).trim();
7093
+ if (!hasOnlyFinishedExplicitOutcomes(payload)) return false;
7094
+ return mode === "deep-interview"
7095
+ && payload.active === false
7096
+ && phase === "complete"
7097
+ && payloadSessionId === sessionId
7098
+ && inferTerminalLifecycleOutcome(payload, { includeQuestionEnforcement: false }) === "finished";
7099
+ }
7100
+
7101
+ function isAllowedDeepInterviewTerminalStateWriteCommand(
7102
+ cwd: string,
7103
+ command: string,
7104
+ activeState: Record<string, unknown>,
7105
+ sessionId: string,
7106
+ ): boolean {
7107
+ const rawWords = tokenizeShellWords(normalizeShellLineContinuations(command).trim());
7108
+ if (rawWords[0] !== "omx") return false;
7109
+ const canonicalCommand = canonicalizeOmxStateTransportCommand(command);
7110
+ if (hasUnquotedShellControlOrRedirection(command)) return false;
7111
+ if (hasUnsafeUnquotedHeredocExpansion(canonicalCommand)) return false;
7112
+ if (hasUnquotedShellSubstitution(canonicalCommand)) return false;
7113
+ if (splitStateScanSegments(canonicalCommand).length !== 1) return false;
7114
+ if (sourcesFileWrittenEarlierInSameCommand(cwd, canonicalCommand)) return false;
7115
+ if (findUnquotedOmxStateCommandIndexes(canonicalCommand, "clear").length > 0) return false;
7116
+ if (hasDynamicNestedShellExecution(canonicalCommand)) return false;
7117
+ if (extractDeepInterviewCommandWriteTargets(command).length > 0) return false;
7118
+
7119
+ const operations = collectOmxStateCommandOperations(command, "write");
7120
+ if (operations.length !== 1) return false;
7121
+ const operation = operations[0];
7122
+ if (!operation || operation.nested || operation.commandPrefix.trim() || hasPriorExecutableCommand(operation.prefix)) return false;
7123
+ const inlineInputCount = operation.args.filter((arg) => arg === "--input" || arg.startsWith("--input=")).length;
7124
+ if (inlineInputCount !== 1 || operation.args.some((arg) => arg === "--input-file" || arg.startsWith("--input-file="))) return false;
7125
+ const inlineInput = readStateWriteFlagValue(operation.args, "--input");
7126
+ let rawPayload: Record<string, unknown> | null = null;
7127
+ try {
7128
+ rawPayload = inlineInput ? safeObject(JSON.parse(inlineInput)) : null;
7129
+ } catch {
7130
+ rawPayload = null;
7131
+ }
7132
+ const nestedState = safeObject(rawPayload?.state);
7133
+ if (
7134
+ !rawPayload
7135
+ || "workingDirectory" in rawPayload
7136
+ || !suppliedSessionAliasesMatch(rawPayload, sessionId)
7137
+ || !hasOnlyFinishedExplicitOutcomes(rawPayload)
7138
+ || (nestedState && (
7139
+ "mode" in nestedState
7140
+ || "session_id" in nestedState
7141
+ || "workingDirectory" in nestedState
7142
+ || "active" in nestedState
7143
+ || "current_phase" in nestedState
7144
+ || "currentPhase" in nestedState
7145
+ || !suppliedSessionAliasesMatch(nestedState, sessionId)
7146
+ || !hasOnlyFinishedExplicitOutcomes(nestedState)
7147
+ ))
7148
+ ) return false;
7149
+
7150
+ const payload = readStateWriteInputPayload(cwd, canonicalCommand, command);
7151
+ return payload ? isCompleteDeepInterviewTerminalWritePayload(payload, activeState, sessionId) : false;
7152
+ }
7153
+
6741
7154
  function isAllowedRalplanTerminalStateWriteCommand(
6742
7155
  cwd: string,
6743
7156
  command: string,
@@ -6776,10 +7189,56 @@ function commandEndsPlanningPhase(cwd: string, command: string): boolean {
6776
7189
  return payload ? isPlanningPhaseDeactivationPayload(payload) : true;
6777
7190
  }
6778
7191
 
6779
- function isAllowedDeepInterviewBashWrite(cwd: string, command: string): boolean {
7192
+ function isAllowedDeepInterviewBashWrite(
7193
+ cwd: string,
7194
+ command: string,
7195
+ activeState?: Record<string, unknown>,
7196
+ sessionId = "",
7197
+ ): boolean {
7198
+ const stateWriteOperations = collectOmxStateCommandOperations(command, "write");
7199
+ const hasUnsafeRuntimeStateWrite = (words: string[]): boolean => {
7200
+ const stateWordIndex = words.indexOf("state");
7201
+ const writeWordIndex = stateWordIndex >= 0 ? words.indexOf("write", stateWordIndex + 1) : -1;
7202
+ if (writeWordIndex <= stateWordIndex) return false;
7203
+ const hasRuntimeBeforeState = words.slice(0, stateWordIndex).some((word) => {
7204
+ const base = shellWordBaseName(word);
7205
+ return isNodeInterpreterCommandWord(base) || base === "bun" || base === "tsx";
7206
+ });
7207
+ if (!hasRuntimeBeforeState) return false;
7208
+
7209
+ const inlineInput = readStateWriteFlagValue(words, "--input");
7210
+ let payload: Record<string, unknown> | null = null;
7211
+ try {
7212
+ const parsed = inlineInput ? safeObject(JSON.parse(inlineInput)) : null;
7213
+ const modeOverride = readStateWriteFlagValue(words, "--mode");
7214
+ payload = parsed
7215
+ ? normalizeStateWriteClassificationPayload(modeOverride ? { ...parsed, mode: modeOverride } : parsed)
7216
+ : null;
7217
+ } catch {
7218
+ payload = null;
7219
+ }
7220
+ return (!payload && stateWriteOperations.length === 0)
7221
+ || Boolean(payload && (
7222
+ safeString(payload.mode).trim().toLowerCase() !== "deep-interview"
7223
+ || isPlanningPhaseDeactivationPayload(payload)
7224
+ ));
7225
+ };
7226
+ const rawCommand = normalizeShellLineContinuations(command).trim();
7227
+ if ([rawCommand, canonicalizeOmxStateTransportCommand(rawCommand)]
7228
+ .some((candidate) => hasUnsafeRuntimeStateWrite(tokenizeShellWords(candidate)))) return false;
7229
+ if (stateWriteOperations.some((operation) => operation.nested)) return false;
6780
7230
  if (isAllowedDeepInterviewRalplanHandoffCommand(cwd, command)) return true;
6781
7231
  if (hasDeepInterviewRalplanHandoffStateMutation(cwd, command)) return false;
6782
- if (commandEndsPlanningPhase(cwd, command)) return false;
7232
+ if (commandEndsPlanningPhase(cwd, command)) {
7233
+ return activeState
7234
+ ? isAllowedDeepInterviewTerminalStateWriteCommand(cwd, command, activeState, sessionId)
7235
+ : false;
7236
+ }
7237
+ if (stateWriteOperations.length > 0) {
7238
+ const canonicalCommand = canonicalizeOmxStateTransportCommand(command);
7239
+ const payload = readStateWriteInputPayload(cwd, canonicalCommand, command);
7240
+ if (!payload || safeString(payload.mode).trim().toLowerCase() !== "deep-interview") return false;
7241
+ }
6783
7242
  if (commandHasUntargetedPlanningForbiddenIntent(command)) return false;
6784
7243
  if (firstPlanningTmpScriptExecutionTarget(cwd, command)) return false;
6785
7244
  if (!commandHasDeepInterviewWriteIntent(command)) return true;
@@ -6804,13 +7263,11 @@ async function readActiveDeepInterviewStateForPreToolUse(
6804
7263
  const modeState = sessionId
6805
7264
  ? await readStopSessionPinnedState("deep-interview-state.json", cwd, sessionId, stateDir)
6806
7265
  : await readJsonIfExists(join(stateDir, "deep-interview-state.json"));
6807
- if (isActiveDeepInterviewPhase(modeState) && modeState && modeStateMatchesSkillStopContext(modeState, cwd, sessionId)) {
6808
- const hasActiveDeepInterviewSkill = listActiveSkills(canonicalState).some((entry) => (
6809
- entry.skill === "deep-interview"
6810
- && matchesSkillStopContext(entry, canonicalState, sessionId, threadId)
6811
- ));
6812
- if (hasActiveDeepInterviewSkill) return modeState;
6813
- }
7266
+ const hasActiveDeepInterviewSkill = listActiveSkills(canonicalState).some((entry) => (
7267
+ entry.skill === "deep-interview"
7268
+ && matchesSkillStopContext(entry, canonicalState, sessionId, threadId)
7269
+ ));
7270
+ if (hasActiveDeepInterviewSkill && modeState?.active === true) return modeState;
6814
7271
  if (isInactiveCompletedDeepInterviewPhase(modeState)) return null;
6815
7272
 
6816
7273
  const autopilotState = sessionId
@@ -6971,7 +7428,7 @@ async function buildRalplanPreToolUseBoundaryOutput(
6971
7428
  resolvedSessionId?: string,
6972
7429
  ): Promise<Record<string, unknown> | null> {
6973
7430
  const sessionId = safeString(resolvedSessionId ?? readPayloadSessionId(payload)).trim();
6974
- if (await hasTrustedTypedSubagentProvenanceForPreToolUse(payload, cwd, sessionId)) return null;
7431
+ if (await hasTrustedTypedSubagentProvenanceForPreToolUse(payload, cwd, stateDir, sessionId)) return null;
6975
7432
  const threadId = readPayloadThreadId(payload);
6976
7433
  const activeState = await readActiveRalplanStateForPreToolUse(cwd, stateDir, sessionId, threadId);
6977
7434
  if (!activeState) return null;
@@ -7025,7 +7482,7 @@ async function buildRalplanPreToolUseBoundaryOutput(
7025
7482
  hookEventName: "PreToolUse",
7026
7483
  additionalContext:
7027
7484
  `${planningModeDescription}. `
7028
- + "Write only planning artifacts under `.omx/context/`, `.omx/plans/`, `.omx/specs/`, `.omx/tmp/`, required `.omx/state/` files, or tracker metadata under `.beads/`. "
7485
+ + "Write only planning artifacts under `.omx/context/`, `.omx/plans/`, `.omx/specs/`, `.omx/tmp/`, required `.omx/state/` files, Markdown drafts under `.omx/drafts/*.md`, or tracker metadata under `.beads/`. "
7029
7486
  + "Do not edit implementation files or run implementation-focused writes from planning phases. "
7030
7487
  + `To execute, first process an explicit handoff such as ${formatExecutionHandoffList(cwd)}, which must emit terminal planning state before implementation begins.`,
7031
7488
  },
@@ -7039,7 +7496,7 @@ async function buildDeepInterviewPreToolUseBoundaryOutput(
7039
7496
  resolvedSessionId?: string,
7040
7497
  ): Promise<Record<string, unknown> | null> {
7041
7498
  const sessionId = safeString(resolvedSessionId ?? readPayloadSessionId(payload)).trim();
7042
- if (await hasTrustedTypedSubagentProvenanceForPreToolUse(payload, cwd, sessionId)) return null;
7499
+ if (await hasTrustedTypedSubagentProvenanceForPreToolUse(payload, cwd, stateDir, sessionId)) return null;
7043
7500
  const threadId = readPayloadThreadId(payload);
7044
7501
  const activeState = await readActiveDeepInterviewStateForPreToolUse(cwd, stateDir, sessionId, threadId);
7045
7502
  if (!activeState) return null;
@@ -7051,7 +7508,7 @@ async function buildDeepInterviewPreToolUseBoundaryOutput(
7051
7508
  let blockedDetail = "implementation/write tools are blocked until an explicit handoff workflow is activated";
7052
7509
 
7053
7510
  if (toolName === "Bash") {
7054
- blocked = !isAllowedDeepInterviewBashWrite(cwd, command);
7511
+ blocked = !isAllowedDeepInterviewBashWrite(cwd, command, activeState, sessionId);
7055
7512
  if (blocked) {
7056
7513
  blockedDetail = buildDeepInterviewBashBlockedDetail(cwd, command);
7057
7514
  }
@@ -7210,17 +7667,76 @@ interface ActiveConductorState {
7210
7667
  async function hasTrustedTypedSubagentProvenanceForPreToolUse(
7211
7668
  payload: CodexHookPayload,
7212
7669
  cwd: string,
7670
+ stateDir: string,
7213
7671
  sessionId: string,
7672
+ options: { allowUntypedProvenance?: boolean } = {},
7214
7673
  ): Promise<boolean> {
7215
7674
  if (hasTeamWorkerEnvironment()) return true;
7216
- if (!isTypedAgentRolePayload(payload)) return false;
7217
7675
  const trackingState = await readSubagentTrackingState(cwd).catch(() => null);
7218
7676
  const session = trackingState?.sessions?.[sessionId];
7219
7677
  if (!session) return false;
7220
7678
 
7221
7679
  const payloadThreadId = readPayloadThreadId(payload);
7680
+ const trackedPayloadThread = payloadThreadId ? session.threads[payloadThreadId] : undefined;
7681
+ const hasTrackedAdaptedTypedRole = payloadThreadId !== ""
7682
+ && isTrustedSubagentThread(session, payloadThreadId)
7683
+ && trackedPayloadThread?.provenance_kind === OMX_ADAPTED_PROVENANCE
7684
+ && resolveInstalledRoleName(trackedPayloadThread.role ?? "") !== null;
7685
+
7686
+ // Resolve the Main-root leader THREAD identity from the tracker's leader_thread_id
7687
+ // plus the canonical session's native_session_id (the leader's native thread). Only
7688
+ // genuine leader-thread identifiers may anchor the leader: owner_*_session_id are
7689
+ // session-level ids, not thread anchors, so they must NOT populate this set — their
7690
+ // mere presence would make it non-empty and suppress the fail-closed guard below
7691
+ // without ever excluding the leader thread (#3117 P4). The native_session_id anchor is
7692
+ // honored only when the root session pointer provably maps to the sessionId under
7693
+ // evaluation, so a stale/foreign session.json cannot supply another session's leader
7694
+ // anchor (#3117 P3); the tracker alone is insufficient because it can omit
7695
+ // leader_thread_id or corruptly label the leader kind:"subagent" (#3117 P2).
7696
+ const sessionState = await readRootSessionStateFromStateDir(stateDir).catch(() => null);
7697
+ const trackerLeaderThreadId = safeString(session.leader_thread_id).trim();
7698
+ const leaderIdentityAnchors = new Set<string>();
7699
+ if (trackerLeaderThreadId) leaderIdentityAnchors.add(trackerLeaderThreadId);
7700
+ if (sessionState && sessionId && payloadMatchesSessionPointer(sessionId, sessionState)) {
7701
+ const nativeSessionId = safeString(sessionState.native_session_id).trim();
7702
+ if (nativeSessionId) leaderIdentityAnchors.add(nativeSessionId);
7703
+ }
7704
+
7705
+ // Leader self-guard: the Main-root Conductor is never a delegated executor. Block it
7706
+ // ahead of every trust path, even when tracker or payload provenance is (possibly
7707
+ // corruptly) attached to the leader thread.
7708
+ if (payloadThreadId && leaderIdentityAnchors.has(payloadThreadId)) return false;
7709
+
7710
+ // Fail closed: without an authoritative leader anchor we cannot affirm the payload is
7711
+ // a non-leader delegated performer rather than a mislabeled leader, so we refuse trust
7712
+ // instead of inferring it from a corrupt tracker kind:"subagent" alone (#3117 P2).
7713
+ if (leaderIdentityAnchors.size === 0) return false;
7714
+
7715
+ // Planning boundary guards (ralplan, deep-interview) still require a recognized typed
7716
+ // agent role, so an untyped collaboration.spawn_agent child cannot write before an
7717
+ // execution handoff/approval. A tracker-recorded OMX-adapted child is the equivalent
7718
+ // of a typed role only when its own persisted role is installed and validated.
7719
+ if (
7720
+ options.allowUntypedProvenance !== true
7721
+ && !isTypedAgentRolePayload(payload)
7722
+ && !hasTrackedAdaptedTypedRole
7723
+ ) {
7724
+ return false;
7725
+ }
7726
+
7727
+ // Tracker-backed provenance: the payload's own thread is a recorded, non-leader
7728
+ // subagent for this session — the non-spoofable anchor that lets native
7729
+ // collaboration.spawn_agent children/descendants edit under the Conductor guard even
7730
+ // without a recognized typed role (#3116). subagent-tracking.json is derived from
7731
+ // child SessionStart transcript metadata, not the live tool-call payload.
7222
7732
  if (payloadThreadId && isTrustedSubagentThread(session, payloadThreadId)) return true;
7223
7733
 
7734
+ // Runtime-attached spawn provenance: trust a genuine spawned subagent turn whose
7735
+ // declared parent maps to this session's leader or a tracked thread. parentThreadId
7736
+ // comes from the runtime-set source.subagent.thread_spawn (not agent-controlled tool
7737
+ // arguments); an absent parent is rejected, the leader self-guard above blocks the
7738
+ // main root, and cross-session parents fail because they are not in this session's
7739
+ // tracked threads (#3116).
7224
7740
  const source = safeObject(payload.source);
7225
7741
  const subagent = safeObject(source.subagent);
7226
7742
  const threadSpawn = safeObject(subagent.thread_spawn);
@@ -7231,9 +7747,7 @@ async function hasTrustedTypedSubagentProvenanceForPreToolUse(
7231
7747
  ?? threadSpawn.leaderThreadId,
7232
7748
  ).trim();
7233
7749
  if (!parentThreadId) return false;
7234
- const leaderThreadId = safeString(session.leader_thread_id).trim();
7235
- if (payloadThreadId && leaderThreadId && payloadThreadId === leaderThreadId) return false;
7236
- return leaderThreadId === parentThreadId || parentThreadId in session.threads;
7750
+ return leaderIdentityAnchors.has(parentThreadId) || parentThreadId in session.threads;
7237
7751
  }
7238
7752
 
7239
7753
  function isActiveConductorModeState(state: Record<string, unknown> | null, mode: string, sessionId: string): boolean {
@@ -7262,7 +7776,7 @@ async function readActiveMainRootConductorStateForPreToolUse(
7262
7776
  }
7263
7777
  const threadId = readPayloadThreadId(payload);
7264
7778
  if (!sessionId) return null;
7265
- if (await hasTrustedTypedSubagentProvenanceForPreToolUse(payload, cwd, sessionId)) return null;
7779
+ if (await hasTrustedTypedSubagentProvenanceForPreToolUse(payload, cwd, stateDir, sessionId, { allowUntypedProvenance: true })) return null;
7266
7780
 
7267
7781
  const canonicalState = await readVisibleSkillActiveStateForStateDir(stateDir, sessionId);
7268
7782
  if (!canonicalState) return null;
@@ -8116,6 +8630,10 @@ export async function buildConductorPreToolUseWriteGuardOutput(
8116
8630
  const nativeSubagentSupport = resolveNativeSubagentSupportStatus({
8117
8631
  payload,
8118
8632
  persistedSupportBlocker: await readJsonIfExists(nativeSubagentSupportBlockerPath(stateDir)),
8633
+ persistedRoleRoutingMarker: readRoleRoutingMarker(stateDir, {
8634
+ cwd,
8635
+ sessionId: resolvedSessionId || readPayloadSessionId(payload),
8636
+ }),
8119
8637
  persistedCapacityBlocker: await readJsonIfExists(nativeSubagentCapacityBlockerPath(stateDir)),
8120
8638
  cwd,
8121
8639
  sessionId: resolvedSessionId || readPayloadSessionId(payload),
@@ -8150,6 +8668,9 @@ export async function buildConductorPreToolUseWriteGuardOutput(
8150
8668
  const unsupportedNativeGuidance = isUnsupportedNativeSubagentEvidence(nativeSubagentSupport)
8151
8669
  ? ` ${buildUnsupportedNativeSubagentGuidance(nativeSubagentSupport)} Treat the active conductor workflow as blocked/cancelled for native delegation recovery; do not call multi_agent_v1.close_agent.`
8152
8670
  : "";
8671
+ const roleRoutingUnavailableGuidance = isRoleRoutingUnavailableEvidence(nativeSubagentSupport)
8672
+ ? ` ${buildRoleRoutingUnavailableGuidance(nativeSubagentSupport)}`
8673
+ : "";
8153
8674
  return {
8154
8675
  decision: "block",
8155
8676
  reason: `Main-root Conductor mode is active (${activeState.mode} phase: ${formatPhase(activeState.phase, "active")}); direct plan/code writes are blocked and must be delegated; ${blockedDetail}.`,
@@ -8160,11 +8681,12 @@ export async function buildConductorPreToolUseWriteGuardOutput(
8160
8681
  + "Use specialized agents for source edits and plan/spec authorship. "
8161
8682
  + `Main-root Conductor may write only orchestration metadata/transport/ledger artifacts under ${CONDUCTOR_ORCHESTRATION_METADATA_PREFIXES.join(", ")}; path location alone is not authorization for substantive deliverables. `
8162
8683
  + unsupportedNativeGuidance
8684
+ + roleRoutingUnavailableGuidance
8163
8685
  + " Autopilot rework and typed subagent/worker lanes are exempt from this guard.",
8164
8686
  },
8165
8687
  };
8166
- }
8167
8688
 
8689
+ }
8168
8690
  function isInPlaceEditorCommand(word: string, commandName: string): boolean {
8169
8691
  if (commandName === "sed") return word === "--in-place" || /^--in-place(?:=.+)?$/.test(word) || /^-[^-\s]*i(?:.*)?$/.test(word);
8170
8692
  if (commandName === "perl") return word === "-i" || word === "-pi" || /^-pi(?:\..+)?$/.test(word) || /^-p.*i(?:\..+)?$/.test(word);
@@ -9250,14 +9772,15 @@ async function buildStopHookOutput(
9250
9772
  payload: CodexHookPayload,
9251
9773
  cwd: string,
9252
9774
  stateDir: string,
9253
- options: { skipRalphStopBlock?: boolean } = {},
9775
+ options: { skipAutoNudge?: boolean; skipRalphStopBlock?: boolean; canonicalSessionId?: string } = {},
9254
9776
  ): Promise<Record<string, unknown> | null> {
9255
9777
  if (isStopExempt(payload)) {
9256
9778
  return null;
9257
9779
  }
9258
9780
 
9259
9781
  const sessionId = readPayloadSessionId(payload);
9260
- const canonicalSessionId = await resolveInternalSessionIdForPayload(cwd, sessionId);
9782
+ const canonicalSessionId = options.canonicalSessionId
9783
+ ?? await resolveInternalSessionIdForPayload(cwd, sessionId);
9261
9784
  const threadId = readPayloadThreadId(payload);
9262
9785
  const suppressParentWorkflowStop = shouldSuppressParentWorkflowStopForSideConversation(payload);
9263
9786
  if (canonicalSessionId) {
@@ -9308,7 +9831,7 @@ async function buildStopHookOutput(
9308
9831
  }
9309
9832
  const ralphState = options.skipRalphStopBlock === true
9310
9833
  ? null
9311
- : await readActiveRalphState(cwd, stateDir, canonicalSessionId, ralphOwnerContext);
9834
+ : await readActiveRalphState(cwd, stateDir, sessionId || canonicalSessionId, ralphOwnerContext);
9312
9835
  if (!ralphState) {
9313
9836
  const autoresearchState = await readActiveAutoresearchState(cwd, canonicalSessionId);
9314
9837
  if (autoresearchState) {
@@ -9492,7 +10015,8 @@ async function buildStopHookOutput(
9492
10015
  const autoNudgePhase = await readStopAutoNudgePhase(cwd, stateDir, canonicalSessionId, threadId);
9493
10016
 
9494
10017
  if (
9495
- autoNudgeConfig.enabled
10018
+ options.skipAutoNudge !== true
10019
+ && autoNudgeConfig.enabled
9496
10020
  && detectNativeStopStallPattern(lastAssistantMessage, autoNudgeConfig.patterns, autoNudgePhase)
9497
10021
  ) {
9498
10022
  const effectiveResponse = resolveEffectiveAutoNudgeResponse(autoNudgeConfig.response);
@@ -9538,7 +10062,7 @@ async function buildStopHookOutput(
9538
10062
  documentRefreshWarning.triggeringPaths.join("|"),
9539
10063
  canonicalSessionId,
9540
10064
  ),
9541
- buildDocumentRefreshAdvisoryOutput(documentRefreshWarning, "Stop"),
10065
+ { systemMessage: documentRefreshWarning.message },
9542
10066
  canonicalSessionId,
9543
10067
  { allowRepeatDuringStopHook: false },
9544
10068
  );
@@ -9569,13 +10093,39 @@ async function buildStopHookOutput(
9569
10093
  );
9570
10094
  }
9571
10095
 
10096
+ async function preflightNativePromptTarget(
10097
+ stateDir: string,
10098
+ context: ResolvedPromptTurnContext,
10099
+ ): Promise<ResolvedPromptTurnContext> {
10100
+ if (context.status !== "authorized") return context;
10101
+ const targetDir = join(stateDir, "sessions", context.authorization.targetSessionId);
10102
+ const evidence: Array<{ ownerCodexSessionId?: unknown; targetSessionId?: unknown }> = [];
10103
+ let filenames: string[];
10104
+ try {
10105
+ filenames = await readdir(targetDir);
10106
+ } catch (error) {
10107
+ if ((error as NodeJS.ErrnoException).code === "ENOENT") return context;
10108
+ return preflightSelectedTargetOwner(context, [{ ownerCodexSessionId: {} }], "native", new Date().toISOString());
10109
+ }
10110
+ for (const filename of filenames) {
10111
+ if (!filename.endsWith("-state.json") && filename !== SKILL_ACTIVE_STATE_FILE) continue;
10112
+ try {
10113
+ const value = JSON.parse(await readFile(join(targetDir, filename), "utf8")) as unknown;
10114
+ evidence.push(...extractSelectedTargetOwnerEvidence(value));
10115
+ } catch {
10116
+ evidence.push({ ownerCodexSessionId: {} });
10117
+ }
10118
+ }
10119
+ return preflightSelectedTargetOwner(context, evidence, "native", new Date().toISOString());
10120
+ }
10121
+
9572
10122
  export async function dispatchCodexNativeHook(
9573
10123
  payload: CodexHookPayload,
9574
10124
  options: NativeHookDispatchOptions = {},
9575
10125
  ): Promise<NativeHookDispatchResult> {
9576
10126
  const hookEventName = readHookEventName(payload);
9577
10127
  const cwd = options.cwd ?? (safeString(payload.cwd).trim() || process.cwd());
9578
- if (hookEventName === "Stop" && !hasNativeStopRuntimeSurface(cwd)) {
10128
+ if (hookEventName === "PostCompact" && process.env.OMX_NATIVE_HOOK_DOCTOR_SMOKE === "1") {
9579
10129
  return {
9580
10130
  hookEventName,
9581
10131
  omxEventName: mapCodexHookEventToOmxEvent(hookEventName),
@@ -9583,24 +10133,71 @@ export async function dispatchCodexNativeHook(
9583
10133
  outputJson: null,
9584
10134
  };
9585
10135
  }
9586
- // Native hooks must use the same authoritative runtime state root as HUD/MCP
9587
- // when boxed/team roots are active; do not bypass it with cwd/.omx/state.
9588
- const stateDir = getBaseStateDir(cwd);
9589
- if (hookEventName !== "Stop") {
9590
- await mkdir(stateDir, { recursive: true });
10136
+ try {
10137
+ recoverAdaptedRoleBindings(cwd, getBaseStateDir(cwd));
10138
+ } catch {
10139
+ // Recovery is best-effort for unrelated hook events.
10140
+ }
10141
+ if (hookEventName === "Stop" && !hasNativeStopRuntimeSurface(cwd)) {
10142
+ return {
10143
+ hookEventName,
10144
+ omxEventName: mapCodexHookEventToOmxEvent(hookEventName),
10145
+ skillState: null,
10146
+ outputJson: null,
10147
+ };
9591
10148
  }
10149
+ // Native hooks must use the exact pointer root selected for this dispatch.
10150
+ const pointerContext = resolveSessionPointerContext(cwd);
10151
+ const stateDir = pointerContext.baseStateDir;
9592
10152
 
9593
10153
  const omxEventName = mapCodexHookEventToOmxEvent(hookEventName);
9594
10154
  let skillState: SkillActiveState | null = null;
9595
10155
  let triageAdditionalContext: string | null = null;
9596
10156
  let goalWorkflowAdditionalContext: string | null = null;
9597
10157
  let ultragoalSteeringAdditionalContext: string | null = null;
10158
+ let promptClassification: KeywordInputClassification | null = null;
9598
10159
 
9599
10160
  const nativeSessionId = safeString(payload.session_id ?? payload.sessionId).trim();
9600
10161
  const threadId = safeString(payload.thread_id ?? payload.threadId).trim();
9601
10162
  const turnId = safeString(payload.turn_id ?? payload.turnId).trim();
9602
- const currentSessionState = await readUsableSessionState(cwd);
10163
+ const pointer = await readSessionPointer(pointerContext);
10164
+ const currentSessionState = pointer.status === "usable" ? pointer.state ?? null : null;
10165
+ let promptTurnContext: ResolvedPromptTurnContext | null = hookEventName === "UserPromptSubmit"
10166
+ ? evaluateResolvedPromptTurn({
10167
+ producer: "native",
10168
+ payloadSessionId: payload.session_id ?? payload.sessionId,
10169
+ ownerEnvSessionId: undefined,
10170
+ selectedPointer: pointer,
10171
+ threadFacts: await readNativePromptThreadFacts(cwd, nativeSessionId, threadId, currentSessionState),
10172
+ nowIso: new Date().toISOString(),
10173
+ })
10174
+ : null;
10175
+ if (promptTurnContext) {
10176
+ promptTurnContext = await preflightNativePromptTarget(stateDir, promptTurnContext);
10177
+ }
10178
+ if (promptTurnContext?.status === "rejected") {
10179
+ await appendPromptSessionProvenanceRejection(pointerContext, promptTurnContext.diagnostic).catch(() => {});
10180
+ return { hookEventName, omxEventName, skillState: null, outputJson: null };
10181
+ }
10182
+ if (promptTurnContext?.status === "suppressed-target-child") {
10183
+ return { hookEventName, omxEventName, skillState: null, outputJson: null };
10184
+ }
10185
+ if (hookEventName !== "Stop") {
10186
+ await mkdir(stateDir, { recursive: true });
10187
+ }
10188
+ let allowImplicitSessionSideEffects = pointer.status === "usable" || pointer.status === "absent" || promptTurnContext?.status === "authorized";
10189
+ let stopAuthorizationFailure: { stopReason: string; reason: string } | null = allowImplicitSessionSideEffects
10190
+ ? null
10191
+ : {
10192
+ stopReason: "session_pointer_unusable",
10193
+ reason: `OMX cannot authorize Stop while the selected session pointer is ${pointer.status}; repair the pointer evidence before continuing.`,
10194
+ };
9603
10195
  let canonicalSessionId = safeString(currentSessionState?.session_id).trim();
10196
+ if (promptTurnContext?.status === "authorized") {
10197
+ canonicalSessionId = promptTurnContext.authorization.targetSessionId;
10198
+ }
10199
+ const allowPromptGlobalSideEffects = promptTurnContext?.status !== "authorized"
10200
+ || promptTurnContext.authorization.globalSideEffects === "allow";
9604
10201
  let resolvedNativeSessionId = nativeSessionId;
9605
10202
  let skipCanonicalSessionStartContext = false;
9606
10203
  let isSubagentSessionStart = false;
@@ -9660,23 +10257,61 @@ export async function dispatchCodexNativeHook(
9660
10257
  );
9661
10258
  }
9662
10259
  } else {
9663
- const sessionState = await reconcileNativeSessionStart(cwd, nativeSessionId, {
9664
- pid: options.sessionOwnerPid ?? resolveSessionOwnerPid(payload),
9665
- });
9666
- canonicalSessionId = safeString(sessionState.session_id).trim();
9667
- resolvedNativeSessionId = safeString(sessionState.native_session_id).trim() || nativeSessionId;
10260
+ const ownerOmxSessionId = await resolveVerifiedOwnerOmxSessionId();
10261
+ try {
10262
+ const sessionState = await reconcileNativeSessionStart(cwd, nativeSessionId, {
10263
+ context: pointerContext,
10264
+ pid: options.sessionOwnerPid ?? resolveSessionOwnerPid(payload),
10265
+ ...(ownerOmxSessionId
10266
+ ? { ownerOmxSessionId, ownerAliasVerified: true }
10267
+ : {}),
10268
+ });
10269
+ canonicalSessionId = safeString(sessionState.session_id).trim();
10270
+ resolvedNativeSessionId = safeString(sessionState.native_session_id).trim() || nativeSessionId;
10271
+ allowImplicitSessionSideEffects = true;
10272
+ stopAuthorizationFailure = null;
10273
+ // #3181: leader attestation is intentionally NOT performed here. This branch is
10274
+ // reached whenever readNativeSubagentSessionStartMetadata() returns null, which
10275
+ // conflates a genuine root start with an unreadable/malformed child transcript, so
10276
+ // it cannot positively classify a leader (a legitimate leader may also carry no
10277
+ // transcript). Leader attestation is performed only on the strictly-gated fresh
10278
+ // leader PreToolUse path below, which fires before the first in-turn role-intent
10279
+ // write. Fail closed here rather than risk a false-leader adoption.
10280
+ } catch (error) {
10281
+ if (!isSessionPointerLaunchAbort(error)) throw error;
10282
+ canonicalSessionId = "";
10283
+ resolvedNativeSessionId = nativeSessionId;
10284
+ skipCanonicalSessionStartContext = true;
10285
+ allowImplicitSessionSideEffects = false;
10286
+ stopAuthorizationFailure = {
10287
+ stopReason: "session_pointer_unusable",
10288
+ reason: `OMX cannot authorize Stop while the selected session pointer is ${pointer.status}; repair the pointer evidence before continuing.`,
10289
+ };
10290
+ }
9668
10291
  }
9669
10292
  } else if (!canonicalSessionId) {
9670
10293
  canonicalSessionId = safeString(currentSessionState?.session_id).trim();
9671
10294
  }
9672
10295
 
9673
10296
  if (hookEventName === "Stop") {
9674
- const inheritedSessionId = safeString(process.env.OMX_SESSION_ID || process.env.CODEX_SESSION_ID).trim();
10297
+ const stopPayloadSessionId = readPayloadSessionId(payload);
9675
10298
  const stopCanonicalSessionId = await resolveInternalSessionIdForPayload(
9676
10299
  cwd,
9677
- readPayloadSessionId(payload) || inheritedSessionId,
10300
+ stopPayloadSessionId,
10301
+ undefined,
10302
+ currentSessionState,
10303
+ pointer.status === "absent",
9678
10304
  );
9679
- if (stopCanonicalSessionId) {
10305
+ if (stopPayloadSessionId && !stopCanonicalSessionId) {
10306
+ canonicalSessionId = "";
10307
+ allowImplicitSessionSideEffects = false;
10308
+ if (!stopAuthorizationFailure) {
10309
+ stopAuthorizationFailure = {
10310
+ stopReason: "session_scope_unmatched",
10311
+ reason: `OMX cannot authorize Stop for unmatched session id ${stopPayloadSessionId}; the selected session pointer remains authoritative.`,
10312
+ };
10313
+ }
10314
+ } else if (stopCanonicalSessionId) {
9680
10315
  canonicalSessionId = stopCanonicalSessionId;
9681
10316
  }
9682
10317
  if (canonicalSessionId && safeString(currentSessionState?.session_id).trim() === canonicalSessionId) {
@@ -9685,8 +10320,8 @@ export async function dispatchCodexNativeHook(
9685
10320
  }
9686
10321
  }
9687
10322
 
9688
- const eventSessionId = canonicalSessionId || nativeSessionId || undefined;
9689
- const sessionIdForState = canonicalSessionId || nativeSessionId;
10323
+ let eventSessionId = canonicalSessionId || nativeSessionId || undefined;
10324
+ let sessionIdForState: string | null = canonicalSessionId || null;
9690
10325
  let outputJson: Record<string, unknown> | null = null;
9691
10326
  const typedAgentRolePayload = isTypedAgentRolePayload(payload);
9692
10327
  const isSubagentPromptSubmit = hookEventName === "UserPromptSubmit"
@@ -9715,36 +10350,81 @@ export async function dispatchCodexNativeHook(
9715
10350
  )),
9716
10351
  )).some(Boolean)
9717
10352
  : false;
10353
+ if (isSubagentStop && stopAuthorizationFailure?.stopReason === "session_scope_unmatched") {
10354
+ canonicalSessionId = normalizeSessionId(readPayloadSessionId(payload)) ?? "";
10355
+ allowImplicitSessionSideEffects = true;
10356
+ stopAuthorizationFailure = null;
10357
+ eventSessionId = canonicalSessionId || nativeSessionId || undefined;
10358
+ sessionIdForState = canonicalSessionId || null;
10359
+ }
9718
10360
  const suppressNoisySubagentLifecycleDispatch =
9719
10361
  (isSubagentSessionStart || isSubagentStop)
9720
10362
  && shouldSuppressSubagentLifecycleHookDispatch();
9721
10363
 
9722
10364
  if (hookEventName === "UserPromptSubmit") {
9723
10365
  const prompt = readPromptText(payload);
9724
- goalWorkflowAdditionalContext = await buildCompletedGoalCleanupPromptWarning(cwd, prompt).catch(() => null)
9725
- ?? await buildGoalWorkflowReconciliationPromptWarning(cwd, prompt).catch(() => null);
9726
- ultragoalSteeringAdditionalContext = prompt && !isSubagentPromptSubmit
10366
+ if (!isSubagentPromptSubmit) {
10367
+ promptClassification = classifyKeywordInput(prompt);
10368
+ }
10369
+ goalWorkflowAdditionalContext = allowPromptGlobalSideEffects
10370
+ ? await buildCompletedGoalCleanupPromptWarning(cwd, prompt).catch(() => null)
10371
+ ?? await buildGoalWorkflowReconciliationPromptWarning(cwd, prompt).catch(() => null)
10372
+ : null;
10373
+ ultragoalSteeringAdditionalContext = prompt && !isSubagentPromptSubmit && allowImplicitSessionSideEffects && allowPromptGlobalSideEffects
9727
10374
  ? await applyUserPromptUltragoalSteering(cwd, prompt).catch((error) => `OMX native UserPromptSubmit rejected bounded .omx/ultragoal steering for G002-cli-and-prompt-submit-bridge: ${error instanceof Error ? error.message : String(error)}`)
9728
10375
  : null;
9729
- if (prompt && !isSubagentPromptSubmit) {
10376
+ let suppressActivationSeeding = !allowImplicitSessionSideEffects;
10377
+ if (promptTurnContext?.status === "authorized") {
10378
+ sessionIdForState = promptTurnContext.authorization.targetSessionId;
10379
+ } else if (prompt && !isSubagentPromptSubmit && allowImplicitSessionSideEffects) {
10380
+ const rawHookSessionId = canonicalSessionId || nativeSessionId;
10381
+ const normalizedHookSessionId = normalizeSessionId(rawHookSessionId);
10382
+ const explicitHookSessionId = currentSessionState ? undefined : normalizedHookSessionId;
10383
+ if (rawHookSessionId && !normalizedHookSessionId) {
10384
+ suppressActivationSeeding = true;
10385
+ } else {
10386
+ try {
10387
+ const writableScope = await resolveWritableStateScope(cwd, explicitHookSessionId);
10388
+ sessionIdForState = writableScope.sessionId ?? null;
10389
+ } catch (error) {
10390
+ if (!isImplicitWritableScopeFailure(error)) throw error;
10391
+ suppressActivationSeeding = true;
10392
+ }
10393
+ }
10394
+ }
10395
+ if (prompt && promptClassification && !isSubagentPromptSubmit && !suppressActivationSeeding) {
9730
10396
  skillState = buildNativeOutsideTmuxTeamPromptBlockState(
9731
- prompt,
10397
+ promptClassification,
9732
10398
  cwd,
9733
10399
  payload,
9734
- sessionIdForState,
10400
+ sessionIdForState || undefined,
9735
10401
  threadId || undefined,
9736
10402
  turnId || undefined,
9737
10403
  ) ?? await recordSkillActivation({
9738
10404
  stateDir,
9739
10405
  sourceCwd: cwd,
9740
10406
  text: prompt,
9741
- sessionId: sessionIdForState,
10407
+ classification: promptClassification,
10408
+ allowSecondaryTeam: !isNativeOutsideTmuxUserPrompt(cwd, payload, sessionIdForState || undefined),
10409
+ sessionId: sessionIdForState || undefined,
9742
10410
  threadId,
9743
10411
  turnId,
10412
+ resolvedPromptTurnContext: promptTurnContext ?? undefined,
10413
+ onProvenanceRejected: async (diagnostic) => {
10414
+ await appendPromptSessionProvenanceRejection(pointerContext, diagnostic).catch(() => {});
10415
+ },
9744
10416
  });
9745
10417
  }
9746
10418
  // --- Triage classifier (advisory-only, non-keyword prompts) ---
9747
- if (prompt && skillState === null && !isSubagentPromptSubmit) {
10419
+ if (
10420
+ prompt
10421
+ && skillState === null
10422
+ && !isSubagentPromptSubmit
10423
+ && allowPromptGlobalSideEffects
10424
+ && promptClassification?.reservedInput === null
10425
+ && promptClassification.hasExplicitLikeInvocation === false
10426
+ && promptClassification.matches.length === 0
10427
+ ) {
9748
10428
  try {
9749
10429
  if (readTriageConfig().enabled) {
9750
10430
  const normalized = prompt.trim().toLowerCase();
@@ -9773,7 +10453,9 @@ export async function dispatchCodexNativeHook(
9773
10453
  },
9774
10454
  suppress_followup: true,
9775
10455
  };
9776
- writeTriageState({ cwd, sessionId: sessionIdForState || null, state: newState });
10456
+ if (!suppressActivationSeeding) {
10457
+ writeTriageState({ cwd, sessionId: sessionIdForState || null, state: newState });
10458
+ }
9777
10459
  } else if (decision.lane === "LIGHT") {
9778
10460
  if (decision.destination === "explore") {
9779
10461
  triageAdditionalContext =
@@ -9802,7 +10484,9 @@ export async function dispatchCodexNativeHook(
9802
10484
  },
9803
10485
  suppress_followup: true,
9804
10486
  };
9805
- writeTriageState({ cwd, sessionId: sessionIdForState || null, state: newState });
10487
+ if (!suppressActivationSeeding) {
10488
+ writeTriageState({ cwd, sessionId: sessionIdForState || null, state: newState });
10489
+ }
9806
10490
  }
9807
10491
  }
9808
10492
  // lane === "PASS": no context, no state write
@@ -9816,7 +10500,7 @@ export async function dispatchCodexNativeHook(
9816
10500
  const skipHudReconcileForDoctorSmoke = process.env.OMX_NATIVE_HOOK_DOCTOR_SMOKE === "1";
9817
10501
  const skipHudReconcileForTeamWorkerPane = !isSubagentPromptSubmit
9818
10502
  && await isConfirmedTeamWorkerPromptSubmitPane(cwd).catch(() => false);
9819
- if (!skipHudReconcileForDoctorSmoke && !skipHudReconcileForTeamWorkerPane) {
10503
+ if (allowImplicitSessionSideEffects && allowPromptGlobalSideEffects && !skipHudReconcileForDoctorSmoke && !skipHudReconcileForTeamWorkerPane) {
9820
10504
  const reconcileHudForPromptSubmitFn = options.reconcileHudForPromptSubmitFn ?? reconcileHudForPromptSubmit;
9821
10505
  const hudSessionId = resolveHudReconcileSessionId(
9822
10506
  currentSessionState,
@@ -9833,7 +10517,7 @@ export async function dispatchCodexNativeHook(
9833
10517
  }
9834
10518
  }
9835
10519
 
9836
- if (omxEventName && !skipCanonicalSessionStartContext && !suppressNoisySubagentLifecycleDispatch) {
10520
+ if (omxEventName && allowImplicitSessionSideEffects && allowPromptGlobalSideEffects && !skipCanonicalSessionStartContext && !suppressNoisySubagentLifecycleDispatch) {
9837
10521
  const baseContext = buildBaseContext(cwd, payload, hookEventName!, canonicalSessionId);
9838
10522
  if (resolvedNativeSessionId) {
9839
10523
  baseContext.native_session_id = resolvedNativeSessionId;
@@ -9874,12 +10558,14 @@ export async function dispatchCodexNativeHook(
9874
10558
  })
9875
10559
  : isSubagentPromptSubmit
9876
10560
  ? null
9877
- : [
9878
- buildAdditionalContextMessage(readPromptText(payload), skillState, cwd, payload),
9879
- ultragoalSteeringAdditionalContext,
9880
- goalWorkflowAdditionalContext,
9881
- triageAdditionalContext,
9882
- ].filter((entry): entry is string => Boolean(entry)).join("\n\n") || null;
10561
+ : promptClassification
10562
+ ? [
10563
+ buildAdditionalContextMessage(promptClassification, skillState, cwd, payload),
10564
+ ultragoalSteeringAdditionalContext,
10565
+ goalWorkflowAdditionalContext,
10566
+ triageAdditionalContext,
10567
+ ].filter((entry): entry is string => Boolean(entry)).join("\n\n") || null
10568
+ : null;
9883
10569
  if (additionalContext) {
9884
10570
  outputJson = {
9885
10571
  hookSpecificOutput: {
@@ -9889,12 +10575,55 @@ export async function dispatchCodexNativeHook(
9889
10575
  };
9890
10576
  }
9891
10577
  } else if (hookEventName === "PreToolUse") {
10578
+ // #3181 Phase-1 (PreToolUse): this hook fires before the shell tool call that runs
10579
+ // the first in-turn `omx ralplan role-intent write`. On a fresh App/outside-tmux
10580
+ // turn where SessionStart did not establish the pointer, reconcile the canonical
10581
+ // pointer and attest the leader here so the first command can bootstrap. Strictly
10582
+ // gated to a fresh (absent-pointer) LEADER turn: no canonical session yet, a present
10583
+ // native session id, a leader-shaped thread (absent or equal to the native session
10584
+ // id), and no subagent/typed-role provenance. Best-effort; never blocks PreToolUse.
10585
+ if (
10586
+ allowImplicitSessionSideEffects
10587
+ && !canonicalSessionId
10588
+ && nativeSessionId
10589
+ && readPayloadAgentRole(payload) === ""
10590
+ && !hasSubagentThreadSpawnProvenance(payload)
10591
+ ) {
10592
+ const preToolUseLeaderThreadId = readPayloadThreadId(payload);
10593
+ if (
10594
+ preToolUseLeaderThreadId
10595
+ && preToolUseLeaderThreadId === nativeSessionId
10596
+ && !(await isThreadTrackedAsSubagent(cwd, nativeSessionId))
10597
+ ) {
10598
+ try {
10599
+ const ownerOmxSessionId = await resolveVerifiedOwnerOmxSessionId();
10600
+ const sessionState = await reconcileNativeSessionStart(cwd, nativeSessionId, {
10601
+ context: pointerContext,
10602
+ pid: options.sessionOwnerPid ?? resolveSessionOwnerPid(payload),
10603
+ ...(ownerOmxSessionId ? { ownerOmxSessionId, ownerAliasVerified: true } : {}),
10604
+ });
10605
+ const bootstrapSessionId = safeString(sessionState.session_id).trim();
10606
+ const bootstrapLeaderThreadId = safeString(sessionState.native_session_id).trim() || nativeSessionId;
10607
+ if (bootstrapSessionId && bootstrapLeaderThreadId) {
10608
+ canonicalSessionId = bootstrapSessionId;
10609
+ attestLeaderThread(cwd, {
10610
+ sessionId: bootstrapSessionId,
10611
+ leaderThreadId: bootstrapLeaderThreadId,
10612
+ source: 'native-pretooluse',
10613
+ });
10614
+ }
10615
+ } catch {
10616
+ // Best-effort bootstrap; PreToolUse must never fail on this.
10617
+ }
10618
+ }
10619
+ }
9892
10620
  const payloadSessionId = readPayloadSessionId(payload);
9893
10621
  const rootPointerConflict = await readLiveRootSessionPointerConflict(stateDir, payloadSessionId);
9894
10622
  const preToolUseSessionId = payloadSessionId
9895
10623
  ? await resolveInternalSessionIdForPayload(cwd, payloadSessionId, stateDir)
9896
10624
  : "";
9897
- outputJson = await buildDeepInterviewPreToolUseBoundaryOutput(payload, cwd, stateDir, preToolUseSessionId)
10625
+ outputJson = buildNativeUnknownRolePreToolUseOutput(payload)
10626
+ ?? await buildDeepInterviewPreToolUseBoundaryOutput(payload, cwd, stateDir, preToolUseSessionId)
9898
10627
  ?? await buildRalplanPreToolUseBoundaryOutput(payload, cwd, stateDir, preToolUseSessionId)
9899
10628
  ?? await buildPlanningRootPointerConflictPreToolUseOutput(payload, cwd, stateDir, rootPointerConflict)
9900
10629
  ?? await buildConductorPreToolUseWriteGuardOutput(payload, cwd, stateDir, preToolUseSessionId)
@@ -9902,17 +10631,34 @@ export async function dispatchCodexNativeHook(
9902
10631
  ?? buildMalformedPreToolUseBlockTestOutput(payload)
9903
10632
  ?? buildNativePreToolUseOutput(payload);
9904
10633
  } else if (hookEventName === "PostToolUse") {
9905
- await recordNativeSubagentCapacityBlocker(cwd, stateDir, payload).catch(() => {});
9906
- await recordNativeSubagentSupportBlocker(cwd, stateDir, payload).catch(() => {});
9907
- if (detectMcpTransportFailure(payload)) {
9908
- await markTeamTransportFailure(cwd, payload);
10634
+ if (allowImplicitSessionSideEffects) {
10635
+ await recordNativeSubagentCapacityBlocker(cwd, stateDir, payload).catch(() => {});
10636
+ await recordNativeSubagentSupportBlocker(cwd, stateDir, payload).catch(() => {});
10637
+ if (detectMcpTransportFailure(payload)) {
10638
+ await markTeamTransportFailure(cwd, payload);
10639
+ }
10640
+ await handleTeamWorkerPostToolUseSuccess(payload, cwd);
9909
10641
  }
9910
10642
  outputJson = buildNativePostToolUseOutput(payload);
9911
- await handleTeamWorkerPostToolUseSuccess(payload, cwd);
9912
10643
  } else if (hookEventName === "Stop") {
9913
- outputJson = await buildStopHookOutput(payload, cwd, stateDir, {
9914
- skipRalphStopBlock: isSubagentStop,
9915
- }) ?? await buildCompletedGoalCleanupStopOutput(payload, cwd);
10644
+ if (allowImplicitSessionSideEffects) {
10645
+ outputJson = await buildStopHookOutput(payload, cwd, stateDir, {
10646
+ canonicalSessionId: canonicalSessionId || undefined,
10647
+ skipRalphStopBlock: isSubagentStop,
10648
+ skipAutoNudge: isSubagentStop,
10649
+ }) ?? await buildCompletedGoalCleanupStopOutput(payload, cwd);
10650
+ } else {
10651
+ const failure = stopAuthorizationFailure ?? {
10652
+ stopReason: "session_pointer_unusable",
10653
+ reason: "OMX cannot authorize Stop without a writable session authority.",
10654
+ };
10655
+ outputJson = {
10656
+ decision: "block",
10657
+ stopReason: failure.stopReason,
10658
+ reason: failure.reason,
10659
+ systemMessage: failure.reason,
10660
+ };
10661
+ }
9916
10662
  }
9917
10663
 
9918
10664
  return {