oh-my-codex 0.20.1 → 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 (654) 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.map +1 -1
  36. package/dist/cli/__tests__/doctor-context-window-warning.test.js.map +1 -1
  37. package/dist/cli/__tests__/doctor-invalid-config.test.js +47 -4
  38. package/dist/cli/__tests__/doctor-invalid-config.test.js.map +1 -1
  39. package/dist/cli/__tests__/doctor-team.test.js.map +1 -1
  40. package/dist/cli/__tests__/doctor-warning-copy.test.js +650 -15
  41. package/dist/cli/__tests__/doctor-warning-copy.test.js.map +1 -1
  42. package/dist/cli/__tests__/exec.test.js.map +1 -1
  43. package/dist/cli/__tests__/imagegen-continuation.test.js.map +1 -1
  44. package/dist/cli/__tests__/index.test.js +216 -11
  45. package/dist/cli/__tests__/index.test.js.map +1 -1
  46. package/dist/cli/__tests__/launch-fallback.test.js +49 -3
  47. package/dist/cli/__tests__/launch-fallback.test.js.map +1 -1
  48. package/dist/cli/__tests__/mcp-parity.test.js +84 -12
  49. package/dist/cli/__tests__/mcp-parity.test.js.map +1 -1
  50. package/dist/cli/__tests__/native-hook-claim-journal.test.d.ts +2 -0
  51. package/dist/cli/__tests__/native-hook-claim-journal.test.d.ts.map +1 -0
  52. package/dist/cli/__tests__/native-hook-claim-journal.test.js +128 -0
  53. package/dist/cli/__tests__/native-hook-claim-journal.test.js.map +1 -0
  54. package/dist/cli/__tests__/nested-help-routing.test.js.map +1 -1
  55. package/dist/cli/__tests__/question.test.js.map +1 -1
  56. package/dist/cli/__tests__/ralph-deslop-contract.test.js.map +1 -1
  57. package/dist/cli/__tests__/ralph-goal-mode-contract.test.js +6 -0
  58. package/dist/cli/__tests__/ralph-goal-mode-contract.test.js.map +1 -1
  59. package/dist/cli/__tests__/ralph-prd-deep-interview.test.js.map +1 -1
  60. package/dist/cli/__tests__/ralph-prd-smoke.test.js.map +1 -1
  61. package/dist/cli/__tests__/ralph.test.js +8 -3
  62. package/dist/cli/__tests__/ralph.test.js.map +1 -1
  63. package/dist/cli/__tests__/ralplan-bootstrap-3181.test.d.ts +2 -0
  64. package/dist/cli/__tests__/ralplan-bootstrap-3181.test.d.ts.map +1 -0
  65. package/dist/cli/__tests__/ralplan-bootstrap-3181.test.js +187 -0
  66. package/dist/cli/__tests__/ralplan-bootstrap-3181.test.js.map +1 -0
  67. package/dist/cli/__tests__/ralplan.test.d.ts +2 -0
  68. package/dist/cli/__tests__/ralplan.test.d.ts.map +1 -0
  69. package/dist/cli/__tests__/ralplan.test.js +129 -0
  70. package/dist/cli/__tests__/ralplan.test.js.map +1 -0
  71. package/dist/cli/__tests__/resume.test.js +6 -1
  72. package/dist/cli/__tests__/resume.test.js.map +1 -1
  73. package/dist/cli/__tests__/session-scoped-runtime.test.js +25 -0
  74. package/dist/cli/__tests__/session-scoped-runtime.test.js.map +1 -1
  75. package/dist/cli/__tests__/session-search-help.test.js.map +1 -1
  76. package/dist/cli/__tests__/session-search.test.js.map +1 -1
  77. package/dist/cli/__tests__/setup-agents-overwrite.test.js +79 -0
  78. package/dist/cli/__tests__/setup-agents-overwrite.test.js.map +1 -1
  79. package/dist/cli/__tests__/setup-gh-star.test.js.map +1 -1
  80. package/dist/cli/__tests__/setup-hooks-shared-ownership.test.js +77 -3
  81. package/dist/cli/__tests__/setup-hooks-shared-ownership.test.js.map +1 -1
  82. package/dist/cli/__tests__/setup-hooks-trust-e2e.test.d.ts +2 -0
  83. package/dist/cli/__tests__/setup-hooks-trust-e2e.test.d.ts.map +1 -0
  84. package/dist/cli/__tests__/setup-hooks-trust-e2e.test.js +285 -0
  85. package/dist/cli/__tests__/setup-hooks-trust-e2e.test.js.map +1 -0
  86. package/dist/cli/__tests__/setup-install-mode.test.js +2261 -14
  87. package/dist/cli/__tests__/setup-install-mode.test.js.map +1 -1
  88. package/dist/cli/__tests__/setup-preferences.test.d.ts +2 -0
  89. package/dist/cli/__tests__/setup-preferences.test.d.ts.map +1 -0
  90. package/dist/cli/__tests__/setup-preferences.test.js +79 -0
  91. package/dist/cli/__tests__/setup-preferences.test.js.map +1 -0
  92. package/dist/cli/__tests__/setup-refresh.test.js +33 -2
  93. package/dist/cli/__tests__/setup-refresh.test.js.map +1 -1
  94. package/dist/cli/__tests__/setup-scope.test.js +46 -3
  95. package/dist/cli/__tests__/setup-scope.test.js.map +1 -1
  96. package/dist/cli/__tests__/sparkshell-cli.test.js.map +1 -1
  97. package/dist/cli/__tests__/state.test.js +37 -0
  98. package/dist/cli/__tests__/state.test.js.map +1 -1
  99. package/dist/cli/__tests__/team.test.js.map +1 -1
  100. package/dist/cli/__tests__/ultragoal.test.js +61 -1
  101. package/dist/cli/__tests__/ultragoal.test.js.map +1 -1
  102. package/dist/cli/__tests__/uninstall.test.js +1771 -24
  103. package/dist/cli/__tests__/uninstall.test.js.map +1 -1
  104. package/dist/cli/__tests__/update.test.js +58 -0
  105. package/dist/cli/__tests__/update.test.js.map +1 -1
  106. package/dist/cli/__tests__/url.test.js.map +1 -1
  107. package/dist/cli/__tests__/windows-popup-loop-contract.test.js.map +1 -1
  108. package/dist/cli/adapt.js.map +1 -1
  109. package/dist/cli/agents-init.js.map +1 -1
  110. package/dist/cli/agents.js.map +1 -1
  111. package/dist/cli/api.js.map +1 -1
  112. package/dist/cli/ask.d.ts +1 -1
  113. package/dist/cli/ask.d.ts.map +1 -1
  114. package/dist/cli/ask.js.map +1 -1
  115. package/dist/cli/auth.js.map +1 -1
  116. package/dist/cli/autoresearch-guided.js.map +1 -1
  117. package/dist/cli/autoresearch-intake.js.map +1 -1
  118. package/dist/cli/capabilities.js.map +1 -1
  119. package/dist/cli/cleanup.js.map +1 -1
  120. package/dist/cli/codex-feature-probe.js.map +1 -1
  121. package/dist/cli/codex-home.js.map +1 -1
  122. package/dist/cli/doctor.d.ts +18 -0
  123. package/dist/cli/doctor.d.ts.map +1 -1
  124. package/dist/cli/doctor.js +594 -130
  125. package/dist/cli/doctor.js.map +1 -1
  126. package/dist/cli/explore.js.map +1 -1
  127. package/dist/cli/index.d.ts +11 -3
  128. package/dist/cli/index.d.ts.map +1 -1
  129. package/dist/cli/index.js +108 -20
  130. package/dist/cli/index.js.map +1 -1
  131. package/dist/cli/mcp-serve.js.map +1 -1
  132. package/dist/cli/mission.js.map +1 -1
  133. package/dist/cli/native-assets.js.map +1 -1
  134. package/dist/cli/native-hook-claim-journal.d.ts +18 -0
  135. package/dist/cli/native-hook-claim-journal.d.ts.map +1 -0
  136. package/dist/cli/native-hook-claim-journal.js +239 -0
  137. package/dist/cli/native-hook-claim-journal.js.map +1 -0
  138. package/dist/cli/omx.js.map +1 -1
  139. package/dist/cli/plugin-marketplace.js.map +1 -1
  140. package/dist/cli/project-runtime-codex-homes.js.map +1 -1
  141. package/dist/cli/question.js.map +1 -1
  142. package/dist/cli/ralph.d.ts.map +1 -1
  143. package/dist/cli/ralph.js +3 -2
  144. package/dist/cli/ralph.js.map +1 -1
  145. package/dist/cli/ralplan.d.ts +14 -0
  146. package/dist/cli/ralplan.d.ts.map +1 -0
  147. package/dist/cli/ralplan.js +203 -0
  148. package/dist/cli/ralplan.js.map +1 -0
  149. package/dist/cli/setup-preferences.d.ts +11 -0
  150. package/dist/cli/setup-preferences.d.ts.map +1 -1
  151. package/dist/cli/setup-preferences.js +32 -0
  152. package/dist/cli/setup-preferences.js.map +1 -1
  153. package/dist/cli/setup.d.ts +53 -0
  154. package/dist/cli/setup.d.ts.map +1 -1
  155. package/dist/cli/setup.js +1401 -398
  156. package/dist/cli/setup.js.map +1 -1
  157. package/dist/cli/sparkshell.js.map +1 -1
  158. package/dist/cli/star-prompt.js.map +1 -1
  159. package/dist/cli/state.d.ts.map +1 -1
  160. package/dist/cli/state.js +2 -1
  161. package/dist/cli/state.js.map +1 -1
  162. package/dist/cli/team.js.map +1 -1
  163. package/dist/cli/ultragoal.d.ts +5 -1
  164. package/dist/cli/ultragoal.d.ts.map +1 -1
  165. package/dist/cli/ultragoal.js +34 -2
  166. package/dist/cli/ultragoal.js.map +1 -1
  167. package/dist/cli/uninstall.d.ts +8 -0
  168. package/dist/cli/uninstall.d.ts.map +1 -1
  169. package/dist/cli/uninstall.js +944 -95
  170. package/dist/cli/uninstall.js.map +1 -1
  171. package/dist/cli/update.d.ts.map +1 -1
  172. package/dist/cli/update.js +8 -1
  173. package/dist/cli/update.js.map +1 -1
  174. package/dist/cli/url.js.map +1 -1
  175. package/dist/compat/__tests__/doctor-contract.test.js +5 -1
  176. package/dist/compat/__tests__/doctor-contract.test.js.map +1 -1
  177. package/dist/compat/__tests__/rust-runtime-compat.test.js.map +1 -1
  178. package/dist/config/__tests__/codex-hooks.test.js +1287 -11
  179. package/dist/config/__tests__/codex-hooks.test.js.map +1 -1
  180. package/dist/config/__tests__/generator-idempotent.test.js +850 -32
  181. package/dist/config/__tests__/generator-idempotent.test.js.map +1 -1
  182. package/dist/config/__tests__/generator-notify.test.js +6 -1
  183. package/dist/config/__tests__/generator-notify.test.js.map +1 -1
  184. package/dist/config/codex-feature-flags.d.ts.map +1 -1
  185. package/dist/config/codex-feature-flags.js.map +1 -1
  186. package/dist/config/codex-hooks.d.ts +124 -1
  187. package/dist/config/codex-hooks.d.ts.map +1 -1
  188. package/dist/config/codex-hooks.js +2142 -265
  189. package/dist/config/codex-hooks.js.map +1 -1
  190. package/dist/config/commit-lore-guard.js.map +1 -1
  191. package/dist/config/generator.d.ts +21 -16
  192. package/dist/config/generator.d.ts.map +1 -1
  193. package/dist/config/generator.js +991 -137
  194. package/dist/config/generator.js.map +1 -1
  195. package/dist/config/mcp-registry.js.map +1 -1
  196. package/dist/config/models.d.ts +2 -2
  197. package/dist/config/models.d.ts.map +1 -1
  198. package/dist/config/models.js.map +1 -1
  199. package/dist/config/omx-first-party-mcp.js.map +1 -1
  200. package/dist/config/team-mode.d.ts.map +1 -1
  201. package/dist/document-refresh/__tests__/enforcer.test.js.map +1 -1
  202. package/dist/document-refresh/enforcer.js.map +1 -1
  203. package/dist/exec/followup.js.map +1 -1
  204. package/dist/goal-workflows/artifacts.js.map +1 -1
  205. package/dist/hooks/__tests__/agents-overlay.test.js +7 -4
  206. package/dist/hooks/__tests__/agents-overlay.test.js.map +1 -1
  207. package/dist/hooks/__tests__/analyze-routing-contract.test.js.map +1 -1
  208. package/dist/hooks/__tests__/analyze-skill-contract.test.js.map +1 -1
  209. package/dist/hooks/__tests__/anti-slop-workflow.test.js.map +1 -1
  210. package/dist/hooks/__tests__/autopilot-skill-contract.test.js +9 -3
  211. package/dist/hooks/__tests__/autopilot-skill-contract.test.js.map +1 -1
  212. package/dist/hooks/__tests__/code-review-skill-contract.test.js.map +1 -1
  213. package/dist/hooks/__tests__/consensus-execution-handoff.test.js.map +1 -1
  214. package/dist/hooks/__tests__/deep-interview-contract.test.js.map +1 -1
  215. package/dist/hooks/__tests__/design-skill.test.js.map +1 -1
  216. package/dist/hooks/__tests__/keyword-detector.test.js +1372 -2
  217. package/dist/hooks/__tests__/keyword-detector.test.js.map +1 -1
  218. package/dist/hooks/__tests__/notify-fallback-watcher.test.js +14 -0
  219. package/dist/hooks/__tests__/notify-fallback-watcher.test.js.map +1 -1
  220. package/dist/hooks/__tests__/notify-hook-all-workers-idle.test.js.map +1 -1
  221. package/dist/hooks/__tests__/notify-hook-auto-nudge.test.js +669 -1
  222. package/dist/hooks/__tests__/notify-hook-auto-nudge.test.js.map +1 -1
  223. package/dist/hooks/__tests__/notify-hook-cross-worktree-heartbeat.test.js +2 -0
  224. package/dist/hooks/__tests__/notify-hook-cross-worktree-heartbeat.test.js.map +1 -1
  225. package/dist/hooks/__tests__/notify-hook-managed-tmux.test.js +142 -1
  226. package/dist/hooks/__tests__/notify-hook-managed-tmux.test.js.map +1 -1
  227. package/dist/hooks/__tests__/notify-hook-modules.test.js.map +1 -1
  228. package/dist/hooks/__tests__/notify-hook-non-omx-guard.test.js +48 -2
  229. package/dist/hooks/__tests__/notify-hook-non-omx-guard.test.js.map +1 -1
  230. package/dist/hooks/__tests__/notify-hook-ralph-resume.test.js +61 -44
  231. package/dist/hooks/__tests__/notify-hook-ralph-resume.test.js.map +1 -1
  232. package/dist/hooks/__tests__/notify-hook-regression-205.test.js.map +1 -1
  233. package/dist/hooks/__tests__/notify-hook-session-idle-dedupe.test.js +3 -2
  234. package/dist/hooks/__tests__/notify-hook-session-idle-dedupe.test.js.map +1 -1
  235. package/dist/hooks/__tests__/notify-hook-session-scope.test.js +243 -4
  236. package/dist/hooks/__tests__/notify-hook-session-scope.test.js.map +1 -1
  237. package/dist/hooks/__tests__/notify-hook-team-dispatch.test.js.map +1 -1
  238. package/dist/hooks/__tests__/notify-hook-team-leader-nudge.test.js +8 -1
  239. package/dist/hooks/__tests__/notify-hook-team-leader-nudge.test.js.map +1 -1
  240. package/dist/hooks/__tests__/notify-hook-team-tmux-guard.test.js.map +1 -1
  241. package/dist/hooks/__tests__/notify-hook-tmux-heal.test.js.map +1 -1
  242. package/dist/hooks/__tests__/notify-hook-tmux-scrollback.test.js.map +1 -1
  243. package/dist/hooks/__tests__/notify-hook-visual-verdict.test.js.map +1 -1
  244. package/dist/hooks/__tests__/notify-hook-worker-idle.test.js.map +1 -1
  245. package/dist/hooks/__tests__/openclaw-setup-contract.test.js.map +1 -1
  246. package/dist/hooks/__tests__/pre-context-gate-skills.test.js.map +1 -1
  247. package/dist/hooks/__tests__/prometheus-strict-contract.test.js.map +1 -1
  248. package/dist/hooks/__tests__/prompt-guidance-fragments.test.js.map +1 -1
  249. package/dist/hooks/__tests__/prompt-guidance-test-helpers.js.map +1 -1
  250. package/dist/hooks/__tests__/prompt-session-provenance.test.d.ts +2 -0
  251. package/dist/hooks/__tests__/prompt-session-provenance.test.d.ts.map +1 -0
  252. package/dist/hooks/__tests__/prompt-session-provenance.test.js +82 -0
  253. package/dist/hooks/__tests__/prompt-session-provenance.test.js.map +1 -0
  254. package/dist/hooks/__tests__/session.test.js +532 -11
  255. package/dist/hooks/__tests__/session.test.js.map +1 -1
  256. package/dist/hooks/__tests__/skill-catalog-hygiene.test.js.map +1 -1
  257. package/dist/hooks/__tests__/team-runtime-gating-docs-contract.test.js.map +1 -1
  258. package/dist/hooks/__tests__/visual-ralph-skill.test.js.map +1 -1
  259. package/dist/hooks/__tests__/visual-verdict-loop.test.js.map +1 -1
  260. package/dist/hooks/agents-overlay.js +5 -5
  261. package/dist/hooks/agents-overlay.js.map +1 -1
  262. package/dist/hooks/code-simplifier/index.js.map +1 -1
  263. package/dist/hooks/explore-routing.js.map +1 -1
  264. package/dist/hooks/extensibility/dispatcher.js.map +1 -1
  265. package/dist/hooks/extensibility/events.js.map +1 -1
  266. package/dist/hooks/extensibility/loader.js.map +1 -1
  267. package/dist/hooks/extensibility/plugin-runner-stdin.js.map +1 -1
  268. package/dist/hooks/extensibility/sdk/logging.js.map +1 -1
  269. package/dist/hooks/keyword-detector.d.ts +33 -7
  270. package/dist/hooks/keyword-detector.d.ts.map +1 -1
  271. package/dist/hooks/keyword-detector.js +2228 -119
  272. package/dist/hooks/keyword-detector.js.map +1 -1
  273. package/dist/hooks/keyword-registry.d.ts +12 -0
  274. package/dist/hooks/keyword-registry.d.ts.map +1 -1
  275. package/dist/hooks/keyword-registry.js +41 -0
  276. package/dist/hooks/keyword-registry.js.map +1 -1
  277. package/dist/hooks/prompt-session-provenance.d.ts +87 -0
  278. package/dist/hooks/prompt-session-provenance.d.ts.map +1 -0
  279. package/dist/hooks/prompt-session-provenance.js +180 -0
  280. package/dist/hooks/prompt-session-provenance.js.map +1 -0
  281. package/dist/hooks/session.d.ts +139 -34
  282. package/dist/hooks/session.d.ts.map +1 -1
  283. package/dist/hooks/session.js +1239 -317
  284. package/dist/hooks/session.js.map +1 -1
  285. package/dist/hooks/task-size-detector.js.map +1 -1
  286. package/dist/hud/__tests__/state.test.js.map +1 -1
  287. package/dist/hud/authority.js.map +1 -1
  288. package/dist/hud/constants.js.map +1 -1
  289. package/dist/hud/index.js.map +1 -1
  290. package/dist/hud/reconcile.js.map +1 -1
  291. package/dist/hud/render.js.map +1 -1
  292. package/dist/hud/state.js.map +1 -1
  293. package/dist/hud/tmux.js.map +1 -1
  294. package/dist/leader/__tests__/contract.test.js +103 -1
  295. package/dist/leader/__tests__/contract.test.js.map +1 -1
  296. package/dist/leader/contract.d.ts +26 -5
  297. package/dist/leader/contract.d.ts.map +1 -1
  298. package/dist/leader/contract.js +109 -3
  299. package/dist/leader/contract.js.map +1 -1
  300. package/dist/mcp/__tests__/bootstrap.test.js.map +1 -1
  301. package/dist/mcp/__tests__/server-lifecycle.test.js +15 -2
  302. package/dist/mcp/__tests__/server-lifecycle.test.js.map +1 -1
  303. package/dist/mcp/__tests__/state-paths.test.js +98 -1
  304. package/dist/mcp/__tests__/state-paths.test.js.map +1 -1
  305. package/dist/mcp/bootstrap.js.map +1 -1
  306. package/dist/mcp/code-intel-server.d.ts +58 -58
  307. package/dist/mcp/code-intel-server.d.ts.map +1 -1
  308. package/dist/mcp/code-intel-server.js.map +1 -1
  309. package/dist/mcp/hermes-bridge.js.map +1 -1
  310. package/dist/mcp/hermes-server.d.ts +70 -70
  311. package/dist/mcp/hermes-server.d.ts.map +1 -1
  312. package/dist/mcp/lifecycle-telemetry.js.map +1 -1
  313. package/dist/mcp/memory-server.d.ts +34 -34
  314. package/dist/mcp/memory-server.d.ts.map +1 -1
  315. package/dist/mcp/memory-server.js.map +1 -1
  316. package/dist/mcp/state-paths.d.ts +14 -0
  317. package/dist/mcp/state-paths.d.ts.map +1 -1
  318. package/dist/mcp/state-paths.js +96 -20
  319. package/dist/mcp/state-paths.js.map +1 -1
  320. package/dist/mcp/state-server.d.ts +47 -47
  321. package/dist/mcp/state-server.d.ts.map +1 -1
  322. package/dist/mcp/trace-server.d.ts +4 -4
  323. package/dist/mcp/trace-server.d.ts.map +1 -1
  324. package/dist/mcp/trace-server.js.map +1 -1
  325. package/dist/mcp/wiki-server.d.ts +23 -23
  326. package/dist/mcp/wiki-server.d.ts.map +1 -1
  327. package/dist/mcp/wiki-server.js.map +1 -1
  328. package/dist/modes/__tests__/base-session-scope.test.js +53 -1
  329. package/dist/modes/__tests__/base-session-scope.test.js.map +1 -1
  330. package/dist/modes/base.d.ts.map +1 -1
  331. package/dist/modes/base.js +5 -5
  332. package/dist/modes/base.js.map +1 -1
  333. package/dist/notifications/__tests__/lifecycle-dedupe.test.js.map +1 -1
  334. package/dist/notifications/__tests__/reply-listener.test.js.map +1 -1
  335. package/dist/notifications/__tests__/session-idle-tail-dedupe.test.js +177 -85
  336. package/dist/notifications/__tests__/session-idle-tail-dedupe.test.js.map +1 -1
  337. package/dist/notifications/__tests__/session-registry.test.js.map +1 -1
  338. package/dist/notifications/__tests__/template-engine.test.js.map +1 -1
  339. package/dist/notifications/__tests__/verbosity.test.js.map +1 -1
  340. package/dist/notifications/http-client.js.map +1 -1
  341. package/dist/notifications/index.d.ts +4 -1
  342. package/dist/notifications/index.d.ts.map +1 -1
  343. package/dist/notifications/index.js +3 -9
  344. package/dist/notifications/index.js.map +1 -1
  345. package/dist/notifications/lifecycle-dedupe.js.map +1 -1
  346. package/dist/notifications/reply-listener.d.ts.map +1 -1
  347. package/dist/notifications/reply-listener.js.map +1 -1
  348. package/dist/notifications/session-registry.js.map +1 -1
  349. package/dist/notifications/session-status.js.map +1 -1
  350. package/dist/notifications/temp-contract.js.map +1 -1
  351. package/dist/notifications/tmux-detector.js.map +1 -1
  352. package/dist/notifications/tmux.js.map +1 -1
  353. package/dist/openclaw/config.js.map +1 -1
  354. package/dist/openclaw/dispatcher.js.map +1 -1
  355. package/dist/pipeline/__tests__/orchestrator.test.js.map +1 -1
  356. package/dist/pipeline/__tests__/stages.test.js +8 -5
  357. package/dist/pipeline/__tests__/stages.test.js.map +1 -1
  358. package/dist/pipeline/stages/code-review.js.map +1 -1
  359. package/dist/pipeline/stages/ralph-verify.js.map +1 -1
  360. package/dist/pipeline/stages/ralplan.js.map +1 -1
  361. package/dist/pipeline/stages/team-exec.js.map +1 -1
  362. package/dist/pipeline/stages/ultragoal.js.map +1 -1
  363. package/dist/pipeline/stages/ultraqa.js.map +1 -1
  364. package/dist/planning/__tests__/approved-execution-lifecycle-matrix.test.js.map +1 -1
  365. package/dist/planning/artifact-names.js.map +1 -1
  366. package/dist/planning/artifacts.js.map +1 -1
  367. package/dist/question/__tests__/state.test.js.map +1 -1
  368. package/dist/question/__tests__/ui.test.js.map +1 -1
  369. package/dist/question/autopilot-wait.js.map +1 -1
  370. package/dist/question/client.d.ts.map +1 -1
  371. package/dist/question/client.js.map +1 -1
  372. package/dist/question/deep-interview.js.map +1 -1
  373. package/dist/question/events.js.map +1 -1
  374. package/dist/question/renderer.js.map +1 -1
  375. package/dist/question/state.d.ts.map +1 -1
  376. package/dist/question/state.js.map +1 -1
  377. package/dist/question/ui.js.map +1 -1
  378. package/dist/ralph/contract.d.ts +1 -1
  379. package/dist/ralph/contract.d.ts.map +1 -1
  380. package/dist/ralplan/__tests__/consensus-gate.test.js +403 -8
  381. package/dist/ralplan/__tests__/consensus-gate.test.js.map +1 -1
  382. package/dist/ralplan/__tests__/runtime.test.js +112 -14
  383. package/dist/ralplan/__tests__/runtime.test.js.map +1 -1
  384. package/dist/ralplan/consensus-gate.d.ts +3 -3
  385. package/dist/ralplan/consensus-gate.d.ts.map +1 -1
  386. package/dist/ralplan/consensus-gate.js +155 -60
  387. package/dist/ralplan/consensus-gate.js.map +1 -1
  388. package/dist/ralplan/runtime.d.ts +2 -2
  389. package/dist/ralplan/runtime.d.ts.map +1 -1
  390. package/dist/ralplan/runtime.js +1 -2
  391. package/dist/ralplan/runtime.js.map +1 -1
  392. package/dist/runtime/bridge.d.ts.map +1 -1
  393. package/dist/runtime/bridge.js.map +1 -1
  394. package/dist/runtime/process-tree.js.map +1 -1
  395. package/dist/runtime/run-loop.js.map +1 -1
  396. package/dist/runtime/run-outcome.d.ts +3 -3
  397. package/dist/runtime/run-outcome.d.ts.map +1 -1
  398. package/dist/runtime/run-outcome.js.map +1 -1
  399. package/dist/runtime/run-state.js.map +1 -1
  400. package/dist/scripts/__tests__/codex-native-hook.test.js +4121 -592
  401. package/dist/scripts/__tests__/codex-native-hook.test.js.map +1 -1
  402. package/dist/scripts/__tests__/fixtures/issue-3138-installed-smoke.d.ts +2 -0
  403. package/dist/scripts/__tests__/fixtures/issue-3138-installed-smoke.d.ts.map +1 -0
  404. package/dist/scripts/__tests__/fixtures/issue-3138-installed-smoke.js +634 -0
  405. package/dist/scripts/__tests__/fixtures/issue-3138-installed-smoke.js.map +1 -0
  406. package/dist/scripts/__tests__/generate-release-body.test.js.map +1 -1
  407. package/dist/scripts/__tests__/hook-derived-watcher.test.js.map +1 -1
  408. package/dist/scripts/__tests__/notify-dispatcher.test.js.map +1 -1
  409. package/dist/scripts/__tests__/notify-fallback-delivery.test.d.ts +2 -0
  410. package/dist/scripts/__tests__/notify-fallback-delivery.test.d.ts.map +1 -0
  411. package/dist/scripts/__tests__/notify-fallback-delivery.test.js +174 -0
  412. package/dist/scripts/__tests__/notify-fallback-delivery.test.js.map +1 -0
  413. package/dist/scripts/__tests__/notify-state-io.test.js +57 -1
  414. package/dist/scripts/__tests__/notify-state-io.test.js.map +1 -1
  415. package/dist/scripts/__tests__/role-intent-bootstrap-e2e-3181.test.d.ts +2 -0
  416. package/dist/scripts/__tests__/role-intent-bootstrap-e2e-3181.test.d.ts.map +1 -0
  417. package/dist/scripts/__tests__/role-intent-bootstrap-e2e-3181.test.js +205 -0
  418. package/dist/scripts/__tests__/role-intent-bootstrap-e2e-3181.test.js.map +1 -0
  419. package/dist/scripts/__tests__/role-intent-durable-recovery-3181.test.d.ts +2 -0
  420. package/dist/scripts/__tests__/role-intent-durable-recovery-3181.test.d.ts.map +1 -0
  421. package/dist/scripts/__tests__/role-intent-durable-recovery-3181.test.js +175 -0
  422. package/dist/scripts/__tests__/role-intent-durable-recovery-3181.test.js.map +1 -0
  423. package/dist/scripts/__tests__/run-test-files.test.js.map +1 -1
  424. package/dist/scripts/__tests__/smoke-packed-install.test.js +848 -4
  425. package/dist/scripts/__tests__/smoke-packed-install.test.js.map +1 -1
  426. package/dist/scripts/__tests__/test-reply-listener-live.test.js.map +1 -1
  427. package/dist/scripts/build-api.js.map +1 -1
  428. package/dist/scripts/build-explore-harness.js.map +1 -1
  429. package/dist/scripts/build-sparkshell.js.map +1 -1
  430. package/dist/scripts/cleanup-explore-harness.js.map +1 -1
  431. package/dist/scripts/codex-execution-surface.js.map +1 -1
  432. package/dist/scripts/codex-native-hook.d.ts.map +1 -1
  433. package/dist/scripts/codex-native-hook.js +689 -156
  434. package/dist/scripts/codex-native-hook.js.map +1 -1
  435. package/dist/scripts/generate-native-release-manifest.js.map +1 -1
  436. package/dist/scripts/generate-release-body.js.map +1 -1
  437. package/dist/scripts/hook-payload-guard.d.ts.map +1 -1
  438. package/dist/scripts/hook-payload-guard.js.map +1 -1
  439. package/dist/scripts/notify-fallback-delivery.d.ts +48 -0
  440. package/dist/scripts/notify-fallback-delivery.d.ts.map +1 -0
  441. package/dist/scripts/notify-fallback-delivery.js +419 -0
  442. package/dist/scripts/notify-fallback-delivery.js.map +1 -0
  443. package/dist/scripts/notify-fallback-watcher.js +136 -30
  444. package/dist/scripts/notify-fallback-watcher.js.map +1 -1
  445. package/dist/scripts/notify-hook/auto-nudge.d.ts +18 -9
  446. package/dist/scripts/notify-hook/auto-nudge.d.ts.map +1 -1
  447. package/dist/scripts/notify-hook/auto-nudge.js +39 -13
  448. package/dist/scripts/notify-hook/auto-nudge.js.map +1 -1
  449. package/dist/scripts/notify-hook/managed-tmux.d.ts +23 -0
  450. package/dist/scripts/notify-hook/managed-tmux.d.ts.map +1 -1
  451. package/dist/scripts/notify-hook/managed-tmux.js +122 -12
  452. package/dist/scripts/notify-hook/managed-tmux.js.map +1 -1
  453. package/dist/scripts/notify-hook/orchestration-intent.d.ts +7 -7
  454. package/dist/scripts/notify-hook/orchestration-intent.d.ts.map +1 -1
  455. package/dist/scripts/notify-hook/ralph-session-resume.d.ts +5 -2
  456. package/dist/scripts/notify-hook/ralph-session-resume.d.ts.map +1 -1
  457. package/dist/scripts/notify-hook/ralph-session-resume.js +33 -37
  458. package/dist/scripts/notify-hook/ralph-session-resume.js.map +1 -1
  459. package/dist/scripts/notify-hook/state-io.d.ts +17 -0
  460. package/dist/scripts/notify-hook/state-io.d.ts.map +1 -1
  461. package/dist/scripts/notify-hook/state-io.js +73 -11
  462. package/dist/scripts/notify-hook/state-io.js.map +1 -1
  463. package/dist/scripts/notify-hook/team-dispatch.d.ts +1 -1
  464. package/dist/scripts/notify-hook/team-dispatch.js.map +1 -1
  465. package/dist/scripts/notify-hook/team-leader-nudge.d.ts +2 -2
  466. package/dist/scripts/notify-hook/team-tmux-guard.d.ts +4 -4
  467. package/dist/scripts/notify-hook/team-worker-posttooluse.js.map +1 -1
  468. package/dist/scripts/notify-hook/team-worker-stop.d.ts +1 -1
  469. package/dist/scripts/notify-hook/team-worker.d.ts +2 -2
  470. package/dist/scripts/notify-hook/tmux-injection.d.ts +8 -1
  471. package/dist/scripts/notify-hook/tmux-injection.d.ts.map +1 -1
  472. package/dist/scripts/notify-hook/tmux-injection.js +25 -16
  473. package/dist/scripts/notify-hook/tmux-injection.js.map +1 -1
  474. package/dist/scripts/notify-hook/visual-verdict.d.ts +1 -1
  475. package/dist/scripts/notify-hook/visual-verdict.d.ts.map +1 -1
  476. package/dist/scripts/notify-hook/visual-verdict.js +15 -13
  477. package/dist/scripts/notify-hook/visual-verdict.js.map +1 -1
  478. package/dist/scripts/notify-hook.d.ts +24 -1
  479. package/dist/scripts/notify-hook.d.ts.map +1 -1
  480. package/dist/scripts/notify-hook.js +266 -90
  481. package/dist/scripts/notify-hook.js.map +1 -1
  482. package/dist/scripts/postinstall.js.map +1 -1
  483. package/dist/scripts/prompt-inventory.js.map +1 -1
  484. package/dist/scripts/smoke-packed-install.d.ts +879 -2
  485. package/dist/scripts/smoke-packed-install.d.ts.map +1 -1
  486. package/dist/scripts/smoke-packed-install.js +1528 -7
  487. package/dist/scripts/smoke-packed-install.js.map +1 -1
  488. package/dist/scripts/sync-plugin-mirror.js.map +1 -1
  489. package/dist/scripts/test-reply-listener-live.js.map +1 -1
  490. package/dist/scripts/test-sparkshell.js.map +1 -1
  491. package/dist/scripts/verify-native-agents.js.map +1 -1
  492. package/dist/session-history/friction.js.map +1 -1
  493. package/dist/session-history/search.js.map +1 -1
  494. package/dist/sidecar/__tests__/boundary.test.js.map +1 -1
  495. package/dist/sidecar/collector.js.map +1 -1
  496. package/dist/sidecar/index.js.map +1 -1
  497. package/dist/sidecar/render.js.map +1 -1
  498. package/dist/sidecar/tmux.js.map +1 -1
  499. package/dist/state/__tests__/operations.test.js +64 -15
  500. package/dist/state/__tests__/operations.test.js.map +1 -1
  501. package/dist/state/__tests__/skill-active.test.js +19 -1
  502. package/dist/state/__tests__/skill-active.test.js.map +1 -1
  503. package/dist/state/__tests__/workflow-transition.test.js +61 -0
  504. package/dist/state/__tests__/workflow-transition.test.js.map +1 -1
  505. package/dist/state/mode-state-context.js.map +1 -1
  506. package/dist/state/operations.d.ts +1 -1
  507. package/dist/state/operations.d.ts.map +1 -1
  508. package/dist/state/operations.js +49 -55
  509. package/dist/state/operations.js.map +1 -1
  510. package/dist/state/paths.d.ts +1 -1
  511. package/dist/state/paths.d.ts.map +1 -1
  512. package/dist/state/paths.js +1 -1
  513. package/dist/state/paths.js.map +1 -1
  514. package/dist/state/skill-active.d.ts +13 -1
  515. package/dist/state/skill-active.d.ts.map +1 -1
  516. package/dist/state/skill-active.js +43 -4
  517. package/dist/state/skill-active.js.map +1 -1
  518. package/dist/state/terminal-normalization.js.map +1 -1
  519. package/dist/state/workflow-transition-reconcile.d.ts.map +1 -1
  520. package/dist/state/workflow-transition-reconcile.js +20 -4
  521. package/dist/state/workflow-transition-reconcile.js.map +1 -1
  522. package/dist/state/workflow-transition.d.ts +1 -1
  523. package/dist/state/workflow-transition.d.ts.map +1 -1
  524. package/dist/state/workflow-transition.js.map +1 -1
  525. package/dist/subagents/__tests__/adapted-role-binding.test.d.ts +2 -0
  526. package/dist/subagents/__tests__/adapted-role-binding.test.d.ts.map +1 -0
  527. package/dist/subagents/__tests__/adapted-role-binding.test.js +988 -0
  528. package/dist/subagents/__tests__/adapted-role-binding.test.js.map +1 -0
  529. package/dist/subagents/__tests__/leader-bootstrap-3181.test.d.ts +2 -0
  530. package/dist/subagents/__tests__/leader-bootstrap-3181.test.d.ts.map +1 -0
  531. package/dist/subagents/__tests__/leader-bootstrap-3181.test.js +273 -0
  532. package/dist/subagents/__tests__/leader-bootstrap-3181.test.js.map +1 -0
  533. package/dist/subagents/__tests__/tracker.test.js +1550 -10
  534. package/dist/subagents/__tests__/tracker.test.js.map +1 -1
  535. package/dist/subagents/adapted-role-binding.d.ts +17 -0
  536. package/dist/subagents/adapted-role-binding.d.ts.map +1 -0
  537. package/dist/subagents/adapted-role-binding.js +75 -0
  538. package/dist/subagents/adapted-role-binding.js.map +1 -0
  539. package/dist/subagents/role-routing-marker.d.ts +10 -0
  540. package/dist/subagents/role-routing-marker.d.ts.map +1 -0
  541. package/dist/subagents/role-routing-marker.js +136 -0
  542. package/dist/subagents/role-routing-marker.js.map +1 -0
  543. package/dist/subagents/tracker.d.ts +172 -0
  544. package/dist/subagents/tracker.d.ts.map +1 -1
  545. package/dist/subagents/tracker.js +1077 -61
  546. package/dist/subagents/tracker.js.map +1 -1
  547. package/dist/team/__tests__/approved-execution.test.js.map +1 -1
  548. package/dist/team/__tests__/delivery-e2e-smoke.test.js.map +1 -1
  549. package/dist/team/__tests__/model-contract.test.js +231 -1
  550. package/dist/team/__tests__/model-contract.test.js.map +1 -1
  551. package/dist/team/__tests__/role-router.test.js.map +1 -1
  552. package/dist/team/__tests__/runtime.test.js +401 -1
  553. package/dist/team/__tests__/runtime.test.js.map +1 -1
  554. package/dist/team/__tests__/scaling.test.js +181 -0
  555. package/dist/team/__tests__/scaling.test.js.map +1 -1
  556. package/dist/team/__tests__/state-root.test.js.map +1 -1
  557. package/dist/team/__tests__/tmux-session.test.js +434 -4
  558. package/dist/team/__tests__/tmux-session.test.js.map +1 -1
  559. package/dist/team/__tests__/tmux-test-fixture.js.map +1 -1
  560. package/dist/team/api-interop.d.ts +2 -2
  561. package/dist/team/api-interop.d.ts.map +1 -1
  562. package/dist/team/api-interop.js.map +1 -1
  563. package/dist/team/commit-hygiene.js.map +1 -1
  564. package/dist/team/contracts.d.ts +5 -5
  565. package/dist/team/contracts.d.ts.map +1 -1
  566. package/dist/team/delivery-log.js.map +1 -1
  567. package/dist/team/followup-planner.js.map +1 -1
  568. package/dist/team/goal-workflow.js.map +1 -1
  569. package/dist/team/idle-nudge.d.ts.map +1 -1
  570. package/dist/team/model-contract.d.ts +20 -1
  571. package/dist/team/model-contract.d.ts.map +1 -1
  572. package/dist/team/model-contract.js +267 -30
  573. package/dist/team/model-contract.js.map +1 -1
  574. package/dist/team/orchestrator.js.map +1 -1
  575. package/dist/team/pane-status.js.map +1 -1
  576. package/dist/team/phase-controller.js.map +1 -1
  577. package/dist/team/reminder-intents.d.ts +1 -1
  578. package/dist/team/reminder-intents.d.ts.map +1 -1
  579. package/dist/team/reminder-intents.js.map +1 -1
  580. package/dist/team/runtime-cli.js.map +1 -1
  581. package/dist/team/runtime.d.ts.map +1 -1
  582. package/dist/team/runtime.js +83 -52
  583. package/dist/team/runtime.js.map +1 -1
  584. package/dist/team/scaling.d.ts +9 -7
  585. package/dist/team/scaling.d.ts.map +1 -1
  586. package/dist/team/scaling.js +69 -28
  587. package/dist/team/scaling.js.map +1 -1
  588. package/dist/team/state/dispatch-lock.js.map +1 -1
  589. package/dist/team/state/dispatch.js.map +1 -1
  590. package/dist/team/state/events.js.map +1 -1
  591. package/dist/team/state/tasks.js.map +1 -1
  592. package/dist/team/state-root.js.map +1 -1
  593. package/dist/team/state.js.map +1 -1
  594. package/dist/team/team-identity.d.ts.map +1 -1
  595. package/dist/team/team-identity.js.map +1 -1
  596. package/dist/team/tmux-session.d.ts +2 -0
  597. package/dist/team/tmux-session.d.ts.map +1 -1
  598. package/dist/team/tmux-session.js +90 -42
  599. package/dist/team/tmux-session.js.map +1 -1
  600. package/dist/team/worker-bootstrap.js.map +1 -1
  601. package/dist/team/worktree.js.map +1 -1
  602. package/dist/ultragoal/__tests__/artifacts.test.js.map +1 -1
  603. package/dist/ultragoal/__tests__/docs-contract.test.js.map +1 -1
  604. package/dist/ultragoal/artifacts.d.ts.map +1 -1
  605. package/dist/ultragoal/artifacts.js +4 -1
  606. package/dist/ultragoal/artifacts.js.map +1 -1
  607. package/dist/url-reader/index.js.map +1 -1
  608. package/dist/utils/agents-model-table.js.map +1 -1
  609. package/dist/utils/package.js.map +1 -1
  610. package/dist/utils/paths.js.map +1 -1
  611. package/dist/utils/platform-command.js.map +1 -1
  612. package/dist/utils/repo-deps.js.map +1 -1
  613. package/dist/utils/version.js.map +1 -1
  614. package/dist/verification/__tests__/ci-rust-gates.test.js +1 -1
  615. package/dist/verification/__tests__/dev-merge-issue-close-workflow.test.js.map +1 -1
  616. package/dist/verification/verifier.js.map +1 -1
  617. package/dist/visual/constants.d.ts +1 -1
  618. package/dist/visual/constants.d.ts.map +1 -1
  619. package/dist/vscode/__tests__/index.test.js.map +1 -1
  620. package/dist/vscode/index.js.map +1 -1
  621. package/dist/wiki/__tests__/cjk-tokenize.test.js.map +1 -1
  622. package/dist/wiki/__tests__/lint.test.js.map +1 -1
  623. package/dist/wiki/__tests__/query.test.js.map +1 -1
  624. package/dist/wiki/__tests__/storage.test.js.map +1 -1
  625. package/dist/wiki/__tests__/test-helpers.d.ts.map +1 -1
  626. package/dist/wiki/lint.js.map +1 -1
  627. package/dist/wiki/query.js.map +1 -1
  628. package/dist/wiki/storage.js.map +1 -1
  629. package/package.json +2 -2
  630. package/plugins/oh-my-codex/.codex-plugin/plugin.json +1 -1
  631. package/plugins/oh-my-codex/skills/omx-setup/SKILL.md +10 -5
  632. package/plugins/oh-my-codex/skills/ralph/SKILL.md +4 -2
  633. package/plugins/oh-my-codex/skills/ralplan/SKILL.md +5 -2
  634. package/skills/omx-setup/SKILL.md +10 -5
  635. package/skills/ralph/SKILL.md +4 -2
  636. package/skills/ralplan/SKILL.md +5 -2
  637. package/src/scripts/__tests__/codex-native-hook.test.ts +17522 -10630
  638. package/src/scripts/__tests__/fixtures/issue-3138-installed-smoke.ts +734 -0
  639. package/src/scripts/__tests__/notify-fallback-delivery.test.ts +177 -0
  640. package/src/scripts/__tests__/notify-state-io.test.ts +73 -0
  641. package/src/scripts/__tests__/role-intent-bootstrap-e2e-3181.test.ts +215 -0
  642. package/src/scripts/__tests__/role-intent-durable-recovery-3181.test.ts +193 -0
  643. package/src/scripts/__tests__/smoke-packed-install.test.ts +991 -4
  644. package/src/scripts/codex-native-hook.ts +779 -159
  645. package/src/scripts/notify-fallback-delivery.ts +416 -0
  646. package/src/scripts/notify-fallback-watcher.ts +132 -40
  647. package/src/scripts/notify-hook/auto-nudge.ts +43 -12
  648. package/src/scripts/notify-hook/managed-tmux.ts +149 -12
  649. package/src/scripts/notify-hook/ralph-session-resume.ts +37 -36
  650. package/src/scripts/notify-hook/state-io.ts +100 -13
  651. package/src/scripts/notify-hook/tmux-injection.ts +38 -21
  652. package/src/scripts/notify-hook/visual-verdict.ts +22 -12
  653. package/src/scripts/notify-hook.ts +335 -90
  654. 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";
@@ -208,9 +242,6 @@ const RALPH_LIVE_RISK_PATTERNS = [
208
242
  /\b(?:database|db|terraform|kubectl|kubernetes|aws|gcp|azure|external|destructive)\b/i,
209
243
  /\b(?:telegram|vps|service|restart|send|notify|notification|notifications|cron)\b/i,
210
244
  ] as const;
211
- const KNOWN_TYPED_AGENT_ROLES = new Set(Object.keys(AGENT_DEFINITIONS));
212
- let installedTypedAgentRoleNamesCacheKey = "";
213
- let installedTypedAgentRoleNamesCache: Set<string> = new Set();
214
245
  const RALPH_TASK_TEXT_FIELDS = [
215
246
  "task_description",
216
247
  "taskDescription",
@@ -236,6 +267,19 @@ function safeString(value: unknown): string {
236
267
  return typeof value === "string" ? value : "";
237
268
  }
238
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
+
239
283
  function safeObject(value: unknown): Record<string, unknown> {
240
284
  return value && typeof value === "object" ? value as Record<string, unknown> : {};
241
285
  }
@@ -331,8 +375,10 @@ interface NativeSubagentSessionStartMetadata {
331
375
  parentThreadId: string;
332
376
  agentNickname?: string;
333
377
  agentRole?: string;
378
+ correlationToken?: string;
334
379
  }
335
380
 
381
+
336
382
  function readBoundedFirstLineSync(path: string): string {
337
383
  const fd = openSync(path, "r");
338
384
  try {
@@ -361,6 +407,20 @@ function readBoundedFirstLineSync(path: string): string {
361
407
  }
362
408
  }
363
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
+
364
424
  function readNativeSubagentSessionStartMetadata(transcriptPath: string): NativeSubagentSessionStartMetadata | null {
365
425
  const normalizedPath = transcriptPath.trim();
366
426
  if (!normalizedPath) return null;
@@ -378,18 +438,44 @@ function readNativeSubagentSessionStartMetadata(transcriptPath: string): NativeS
378
438
  const parentThreadId = safeString(threadSpawn.parent_thread_id).trim();
379
439
  if (!parentThreadId) return null;
380
440
 
381
- const agentNickname = safeString(threadSpawn.agent_nickname ?? payload.agent_nickname).trim();
382
- 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();
383
461
  return {
384
462
  parentThreadId,
385
463
  ...(agentNickname ? { agentNickname } : {}),
386
464
  ...(agentRole ? { agentRole } : {}),
465
+ ...(correlationToken ? { correlationToken } : {}),
387
466
  };
467
+
388
468
  } catch {
389
469
  return null;
390
470
  }
391
471
  }
392
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
+
393
479
  async function recordNativeSubagentSessionStart(
394
480
  cwd: string,
395
481
  canonicalSessionId: string,
@@ -399,25 +485,79 @@ async function recordNativeSubagentSessionStart(
399
485
  ): Promise<void> {
400
486
  const parentThreadId = metadata.parentThreadId.trim();
401
487
  const childThreadId = childSessionId.trim();
488
+ const correlationSessionId = canonicalSessionId.trim() || parentThreadId;
402
489
  const trackingSessionIds = [...new Set([
403
490
  canonicalSessionId.trim(),
404
491
  parentThreadId,
405
492
  ].filter(Boolean))];
406
- for (const sessionId of trackingSessionIds) {
407
- 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
+ }
408
547
  await recordSubagentTurnForSession(cwd, {
409
548
  sessionId,
410
- threadId: parentThreadId,
411
- kind: 'leader',
549
+ threadId: childThreadId,
550
+ kind: "subagent",
551
+ ...(parentThreadId && parentThreadId !== childThreadId ? { leaderThreadId: parentThreadId } : {}),
552
+ mode: metadata.agentRole,
412
553
  }).catch(() => {});
413
554
  }
414
- await recordSubagentTurnForSession(cwd, {
415
- sessionId,
416
- threadId: childThreadId,
417
- kind: 'subagent',
418
- ...(parentThreadId && parentThreadId !== childThreadId ? { leaderThreadId: parentThreadId } : {}),
419
- mode: metadata.agentRole,
420
- }).catch(() => {});
555
+ refreshNativeSubagentRoleRoutingMarker(
556
+ cwd,
557
+ getBaseStateDir(cwd),
558
+ correlationSessionId,
559
+ parentThreadId,
560
+ );
421
561
  }
422
562
  await appendToLog(cwd, {
423
563
  event: "subagent_session_start",
@@ -523,6 +663,32 @@ async function isNativeSubagentHook(
523
663
  ));
524
664
  }
525
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
+
526
692
  function shouldSuppressSubagentLifecycleHookDispatch(): boolean {
527
693
  const config = getNotificationConfig();
528
694
  if (config?.includeChildAgents === true) return false;
@@ -690,8 +856,8 @@ function readPromptText(payload: CodexHookPayload): string {
690
856
  payload.userPrompt,
691
857
  ];
692
858
  for (const candidate of candidates) {
693
- const value = safeString(candidate).trim();
694
- if (value) return value;
859
+ const value = safeString(candidate);
860
+ if (value.trim()) return value;
695
861
  }
696
862
  return "";
697
863
  }
@@ -2175,25 +2341,29 @@ function buildTeamHelpInstruction(cwd: string, payload?: CodexHookPayload): stri
2175
2341
  }).teamHelpInstruction;
2176
2342
  }
2177
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
+
2178
2354
  function buildNativeOutsideTmuxTeamPromptBlockState(
2179
- prompt: string,
2355
+ classification: KeywordInputClassification,
2180
2356
  cwd: string,
2181
2357
  payload: CodexHookPayload,
2182
2358
  sessionId?: string,
2183
2359
  threadId?: string,
2184
2360
  turnId?: string,
2185
2361
  ): SkillActiveState | null {
2186
- if (!readTeamModeConfig(cwd).enabled) return null;
2187
- const match = detectPrimaryKeyword(prompt);
2362
+ const teamMode = readTeamModeConfig(cwd);
2363
+ const match = classification.matches.filter((entry) => teamMode.enabled || entry.skill !== "team")[0] ?? null;
2188
2364
  if (match?.skill !== "team") return null;
2189
2365
 
2190
- const environment = resolveExecutionEnvironment(cwd, {
2191
- hookEventName: "UserPromptSubmit",
2192
- payload,
2193
- canonicalSessionId: sessionId ?? "",
2194
- nativeSessionId: safeString(payload.session_id ?? payload.sessionId).trim(),
2195
- });
2196
- if (!(environment.launcher === "native" && environment.transport === "outside-tmux")) return null;
2366
+ if (!isNativeOutsideTmuxUserPrompt(cwd, payload, sessionId)) return null;
2197
2367
 
2198
2368
  const nowIso = new Date().toISOString();
2199
2369
  return {
@@ -2229,13 +2399,19 @@ function buildAutopilotPromptActivationNote(
2229
2399
  const nativeSubagentSupport = resolveNativeSubagentSupportStatus({
2230
2400
  payload: options.payload,
2231
2401
  persistedSupportBlocker: readJsonSyncIfExists(nativeSubagentSupportBlockerPath(stateDir)),
2402
+ persistedRoleRoutingMarker: readRoleRoutingMarker(stateDir, {
2403
+ cwd: options.cwd,
2404
+ sessionId: options.sessionId ?? "",
2405
+ }),
2232
2406
  persistedCapacityBlocker: readJsonSyncIfExists(nativeSubagentCapacityBlockerPath(stateDir)),
2233
2407
  cwd: options.cwd,
2234
2408
  sessionId: options.sessionId,
2235
2409
  });
2236
2410
  const conductorGuidance = nativeSubagentSupport.status === "unsupported"
2237
2411
  ? buildUnsupportedNativeSubagentGuidance(nativeSubagentSupport)
2238
- : `${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}`;
2239
2415
  return [
2240
2416
  `Autopilot protocol: the durable default chain is $deep-interview -> $ralplan -> $ultragoal${teamHandoff} -> $code-review -> $ultraqa (deep-interview -> ralplan -> ultragoal -> code-review -> ultraqa).`,
2241
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.",
@@ -2258,30 +2434,40 @@ function formatExecutionHandoffList(cwd: string): string {
2258
2434
  }
2259
2435
 
2260
2436
  function buildAdditionalContextMessage(
2261
- prompt: string,
2437
+ classification: KeywordInputClassification,
2262
2438
  skillState?: SkillActiveState | null,
2263
2439
  cwd: string = process.cwd(),
2264
2440
  payload?: CodexHookPayload,
2265
2441
  ): string | null {
2442
+ const prompt = classification.originalText;
2266
2443
  if (!prompt) return null;
2267
2444
  const promptPriorityMessage = buildPromptPriorityMessage(prompt);
2268
2445
  if (payload && isTypedAgentRolePayload(payload)) {
2269
2446
  return promptPriorityMessage;
2270
2447
  }
2271
2448
  const teamMode = readTeamModeConfig(cwd);
2272
- const matches = detectKeywords(prompt).filter((entry) => teamMode.enabled || entry.skill !== "team");
2449
+ const matches = classification.matches.filter((entry) => teamMode.enabled || entry.skill !== "team");
2273
2450
  const match = matches[0] ?? null;
2274
2451
  if (!match) {
2452
+ const markedQuestionAnswer = classification.reservedInput === "omx-question-answered";
2275
2453
  const continuedSkill = safeString(skillState?.skill).trim();
2276
- 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;
2277
2462
  const deepInterviewPromptActivationNote = skillState?.initialized_mode === "deep-interview"
2278
2463
  ? buildDeepInterviewQuestionBridgeInstruction(cwd, payload)
2279
2464
  : null;
2280
2465
  const deepInterviewConfigPromptActivationNote = buildDeepInterviewConfigInstruction(cwd, skillState);
2281
- const markedQuestionAnswer = /^\s*\[omx question answered\]/i.test(prompt);
2282
2466
  const autopilotPromptActivationNote = buildAutopilotPromptActivationNote(skillState, { markedQuestionAnswer, cwd, payload, sessionId: safeString(skillState?.session_id).trim() });
2283
2467
  return [
2284
- `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}".`,
2285
2471
  promptPriorityMessage,
2286
2472
  skillState?.initialized_mode && skillState.initialized_state_path
2287
2473
  ? buildSkillStateCliInstruction(skillState.initialized_mode, skillState.initialized_state_path)
@@ -2295,29 +2481,6 @@ function buildAdditionalContextMessage(
2295
2481
  const detectedKeywordMessage = matches.length > 1
2296
2482
  ? `OMX native UserPromptSubmit detected workflow keywords ${matches.map((entry) => `"${entry.keyword}" -> ${entry.skill}`).join(", ")}.`
2297
2483
  : `OMX native UserPromptSubmit detected workflow keyword "${match.keyword}" -> ${match.skill}.`;
2298
- const continuedSkill = safeString(skillState?.skill).trim();
2299
- if (
2300
- continuedSkill
2301
- && continuedSkill !== match.skill
2302
- && /^\s*\[omx question answered\]/i.test(prompt)
2303
- ) {
2304
- const deepInterviewPromptActivationNote = skillState?.initialized_mode === "deep-interview"
2305
- ? buildDeepInterviewQuestionBridgeInstruction(cwd, payload)
2306
- : null;
2307
- const deepInterviewConfigPromptActivationNote = buildDeepInterviewConfigInstruction(cwd, skillState);
2308
- const autopilotPromptActivationNote = buildAutopilotPromptActivationNote(skillState, { markedQuestionAnswer: true, cwd, payload, sessionId: safeString(skillState?.session_id).trim() });
2309
- return [
2310
- `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.`,
2311
- promptPriorityMessage,
2312
- skillState?.initialized_mode && skillState.initialized_state_path
2313
- ? buildSkillStateCliInstruction(skillState.initialized_mode, skillState.initialized_state_path)
2314
- : null,
2315
- deepInterviewPromptActivationNote,
2316
- deepInterviewConfigPromptActivationNote,
2317
- autopilotPromptActivationNote,
2318
- "Follow AGENTS.md routing and preserve workflow transition and planning-safety rules.",
2319
- ].filter(Boolean).join(" ");
2320
- }
2321
2484
  const activeSkills = Array.isArray(skillState?.active_skills)
2322
2485
  ? skillState.active_skills.map((entry) => entry.skill)
2323
2486
  : [];
@@ -3161,33 +3324,68 @@ function readPayloadAgentRole(payload: CodexHookPayload): string {
3161
3324
  ).trim().toLowerCase();
3162
3325
  }
3163
3326
 
3164
- function readInstalledTypedAgentRoleNames(): Set<string> {
3165
- const cacheKey = [codexAgentsDir(), projectCodexAgentsDir()].join("|");
3166
- if (installedTypedAgentRoleNamesCacheKey === cacheKey) return installedTypedAgentRoleNamesCache;
3167
-
3168
- const installedRoleNames = new Set<string>();
3169
- for (const agentsDir of [codexAgentsDir(), projectCodexAgentsDir()]) {
3170
- try {
3171
- for (const entry of readdirSync(agentsDir, { withFileTypes: true })) {
3172
- if (!entry.isFile() || !entry.name.endsWith(".toml")) continue;
3173
- installedRoleNames.add(entry.name.slice(0, -5).trim().toLowerCase());
3174
- }
3175
- } catch {
3176
- // Ignore missing or unreadable agent directories; built-in definitions remain authoritative.
3177
- }
3178
- }
3179
-
3180
- installedTypedAgentRoleNamesCacheKey = cacheKey;
3181
- installedTypedAgentRoleNamesCache = installedRoleNames;
3182
- 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();
3183
3337
  }
3184
3338
 
3185
3339
  function isTypedAgentRolePayload(payload: CodexHookPayload): boolean {
3186
3340
  const agentRole = readPayloadAgentRole(payload);
3187
- return agentRole !== "" && (
3188
- KNOWN_TYPED_AGENT_ROLES.has(agentRole)
3189
- || readInstalledTypedAgentRoleNames().has(agentRole)
3190
- );
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
+ };
3191
3389
  }
3192
3390
 
3193
3391
  function readPayloadTurnId(payload: CodexHookPayload): string {
@@ -3288,6 +3486,27 @@ async function recordNativeSubagentSupportBlocker(
3288
3486
  }, null, 2));
3289
3487
  }
3290
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
+
3291
3510
  function summarizeCapacityFailure(text: string): string {
3292
3511
  const normalized = text.replace(/\s+/g, " ").trim();
3293
3512
  if (!normalized) return "agent thread limit reached";
@@ -3387,12 +3606,14 @@ async function resolveInternalSessionIdForPayload(
3387
3606
  cwd: string,
3388
3607
  payloadSessionId: string,
3389
3608
  stateDir?: string,
3609
+ pointerSessionState?: SessionState | null,
3610
+ allowUnboundPayloadFallback = true,
3390
3611
  ): Promise<string> {
3391
- const currentSession = stateDir
3612
+ const currentSession = pointerSessionState ?? (stateDir
3392
3613
  ? await readUsableSessionStateFromStateDir(cwd, stateDir)
3393
- : await readUsableSessionState(cwd);
3614
+ : await readUsableSessionState(cwd));
3394
3615
  const canonicalSessionId = safeString(currentSession?.session_id).trim();
3395
- if (!canonicalSessionId) return payloadSessionId;
3616
+ if (!canonicalSessionId) return allowUnboundPayloadFallback ? payloadSessionId : "";
3396
3617
 
3397
3618
  const nativeSessionId = safeString(currentSession?.native_session_id).trim();
3398
3619
  const ownerOmxSessionId = safeString(currentSession?.owner_omx_session_id).trim();
@@ -3400,7 +3621,7 @@ async function resolveInternalSessionIdForPayload(
3400
3621
  if (payloadSessionId === canonicalSessionId) return canonicalSessionId;
3401
3622
  if (nativeSessionId && payloadSessionId === nativeSessionId) return canonicalSessionId;
3402
3623
  if (ownerOmxSessionId && payloadSessionId === ownerOmxSessionId) return canonicalSessionId;
3403
- return payloadSessionId;
3624
+ return allowUnboundPayloadFallback ? payloadSessionId : "";
3404
3625
  }
3405
3626
 
3406
3627
  async function readRootSessionStateFromStateDir(stateDir: string): Promise<SessionState | null> {
@@ -3521,14 +3742,6 @@ const RALPLAN_EXECUTION_HANDOFF_SKILLS = new Set([
3521
3742
  "ultraqa",
3522
3743
  ]);
3523
3744
 
3524
- function isActiveDeepInterviewPhase(state: Record<string, unknown> | null): boolean {
3525
- if (!state || state.active !== true) return false;
3526
- const mode = safeString(state.mode).trim();
3527
- if (mode && mode !== "deep-interview") return false;
3528
- const phase = safeString(state.current_phase ?? state.currentPhase).trim().toLowerCase();
3529
- if (phase && (TERMINAL_MODE_PHASES.has(phase) || phase === "completing")) return false;
3530
- return true;
3531
- }
3532
3745
 
3533
3746
  function isInactiveCompletedDeepInterviewPhase(state: Record<string, unknown> | null): boolean {
3534
3747
  if (!state || state.active !== false) return false;
@@ -6816,6 +7029,128 @@ function isCompleteRalplanTerminalWritePayload(
6816
7029
  return true;
6817
7030
  }
6818
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
+
6819
7154
  function isAllowedRalplanTerminalStateWriteCommand(
6820
7155
  cwd: string,
6821
7156
  command: string,
@@ -6854,10 +7189,56 @@ function commandEndsPlanningPhase(cwd: string, command: string): boolean {
6854
7189
  return payload ? isPlanningPhaseDeactivationPayload(payload) : true;
6855
7190
  }
6856
7191
 
6857
- 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;
6858
7230
  if (isAllowedDeepInterviewRalplanHandoffCommand(cwd, command)) return true;
6859
7231
  if (hasDeepInterviewRalplanHandoffStateMutation(cwd, command)) return false;
6860
- 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
+ }
6861
7242
  if (commandHasUntargetedPlanningForbiddenIntent(command)) return false;
6862
7243
  if (firstPlanningTmpScriptExecutionTarget(cwd, command)) return false;
6863
7244
  if (!commandHasDeepInterviewWriteIntent(command)) return true;
@@ -6882,13 +7263,11 @@ async function readActiveDeepInterviewStateForPreToolUse(
6882
7263
  const modeState = sessionId
6883
7264
  ? await readStopSessionPinnedState("deep-interview-state.json", cwd, sessionId, stateDir)
6884
7265
  : await readJsonIfExists(join(stateDir, "deep-interview-state.json"));
6885
- if (isActiveDeepInterviewPhase(modeState) && modeState && modeStateMatchesSkillStopContext(modeState, cwd, sessionId)) {
6886
- const hasActiveDeepInterviewSkill = listActiveSkills(canonicalState).some((entry) => (
6887
- entry.skill === "deep-interview"
6888
- && matchesSkillStopContext(entry, canonicalState, sessionId, threadId)
6889
- ));
6890
- if (hasActiveDeepInterviewSkill) return modeState;
6891
- }
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;
6892
7271
  if (isInactiveCompletedDeepInterviewPhase(modeState)) return null;
6893
7272
 
6894
7273
  const autopilotState = sessionId
@@ -7129,7 +7508,7 @@ async function buildDeepInterviewPreToolUseBoundaryOutput(
7129
7508
  let blockedDetail = "implementation/write tools are blocked until an explicit handoff workflow is activated";
7130
7509
 
7131
7510
  if (toolName === "Bash") {
7132
- blocked = !isAllowedDeepInterviewBashWrite(cwd, command);
7511
+ blocked = !isAllowedDeepInterviewBashWrite(cwd, command, activeState, sessionId);
7133
7512
  if (blocked) {
7134
7513
  blockedDetail = buildDeepInterviewBashBlockedDetail(cwd, command);
7135
7514
  }
@@ -7298,6 +7677,11 @@ async function hasTrustedTypedSubagentProvenanceForPreToolUse(
7298
7677
  if (!session) return false;
7299
7678
 
7300
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;
7301
7685
 
7302
7686
  // Resolve the Main-root leader THREAD identity from the tracker's leader_thread_id
7303
7687
  // plus the canonical session's native_session_id (the leader's native thread). Only
@@ -7330,9 +7714,13 @@ async function hasTrustedTypedSubagentProvenanceForPreToolUse(
7330
7714
 
7331
7715
  // Planning boundary guards (ralplan, deep-interview) still require a recognized typed
7332
7716
  // agent role, so an untyped collaboration.spawn_agent child cannot write before an
7333
- // execution handoff/approval. Only the Main-root Conductor/Ralph executing guard opts
7334
- // into untyped tracker/runtime provenance (#3116, #3117).
7335
- if (options.allowUntypedProvenance !== true && !isTypedAgentRolePayload(payload)) {
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
+ ) {
7336
7724
  return false;
7337
7725
  }
7338
7726
 
@@ -8242,6 +8630,10 @@ export async function buildConductorPreToolUseWriteGuardOutput(
8242
8630
  const nativeSubagentSupport = resolveNativeSubagentSupportStatus({
8243
8631
  payload,
8244
8632
  persistedSupportBlocker: await readJsonIfExists(nativeSubagentSupportBlockerPath(stateDir)),
8633
+ persistedRoleRoutingMarker: readRoleRoutingMarker(stateDir, {
8634
+ cwd,
8635
+ sessionId: resolvedSessionId || readPayloadSessionId(payload),
8636
+ }),
8245
8637
  persistedCapacityBlocker: await readJsonIfExists(nativeSubagentCapacityBlockerPath(stateDir)),
8246
8638
  cwd,
8247
8639
  sessionId: resolvedSessionId || readPayloadSessionId(payload),
@@ -8276,6 +8668,9 @@ export async function buildConductorPreToolUseWriteGuardOutput(
8276
8668
  const unsupportedNativeGuidance = isUnsupportedNativeSubagentEvidence(nativeSubagentSupport)
8277
8669
  ? ` ${buildUnsupportedNativeSubagentGuidance(nativeSubagentSupport)} Treat the active conductor workflow as blocked/cancelled for native delegation recovery; do not call multi_agent_v1.close_agent.`
8278
8670
  : "";
8671
+ const roleRoutingUnavailableGuidance = isRoleRoutingUnavailableEvidence(nativeSubagentSupport)
8672
+ ? ` ${buildRoleRoutingUnavailableGuidance(nativeSubagentSupport)}`
8673
+ : "";
8279
8674
  return {
8280
8675
  decision: "block",
8281
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}.`,
@@ -8286,11 +8681,12 @@ export async function buildConductorPreToolUseWriteGuardOutput(
8286
8681
  + "Use specialized agents for source edits and plan/spec authorship. "
8287
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. `
8288
8683
  + unsupportedNativeGuidance
8684
+ + roleRoutingUnavailableGuidance
8289
8685
  + " Autopilot rework and typed subagent/worker lanes are exempt from this guard.",
8290
8686
  },
8291
8687
  };
8292
- }
8293
8688
 
8689
+ }
8294
8690
  function isInPlaceEditorCommand(word: string, commandName: string): boolean {
8295
8691
  if (commandName === "sed") return word === "--in-place" || /^--in-place(?:=.+)?$/.test(word) || /^-[^-\s]*i(?:.*)?$/.test(word);
8296
8692
  if (commandName === "perl") return word === "-i" || word === "-pi" || /^-pi(?:\..+)?$/.test(word) || /^-p.*i(?:\..+)?$/.test(word);
@@ -9376,14 +9772,15 @@ async function buildStopHookOutput(
9376
9772
  payload: CodexHookPayload,
9377
9773
  cwd: string,
9378
9774
  stateDir: string,
9379
- options: { skipRalphStopBlock?: boolean } = {},
9775
+ options: { skipAutoNudge?: boolean; skipRalphStopBlock?: boolean; canonicalSessionId?: string } = {},
9380
9776
  ): Promise<Record<string, unknown> | null> {
9381
9777
  if (isStopExempt(payload)) {
9382
9778
  return null;
9383
9779
  }
9384
9780
 
9385
9781
  const sessionId = readPayloadSessionId(payload);
9386
- const canonicalSessionId = await resolveInternalSessionIdForPayload(cwd, sessionId);
9782
+ const canonicalSessionId = options.canonicalSessionId
9783
+ ?? await resolveInternalSessionIdForPayload(cwd, sessionId);
9387
9784
  const threadId = readPayloadThreadId(payload);
9388
9785
  const suppressParentWorkflowStop = shouldSuppressParentWorkflowStopForSideConversation(payload);
9389
9786
  if (canonicalSessionId) {
@@ -9434,7 +9831,7 @@ async function buildStopHookOutput(
9434
9831
  }
9435
9832
  const ralphState = options.skipRalphStopBlock === true
9436
9833
  ? null
9437
- : await readActiveRalphState(cwd, stateDir, canonicalSessionId, ralphOwnerContext);
9834
+ : await readActiveRalphState(cwd, stateDir, sessionId || canonicalSessionId, ralphOwnerContext);
9438
9835
  if (!ralphState) {
9439
9836
  const autoresearchState = await readActiveAutoresearchState(cwd, canonicalSessionId);
9440
9837
  if (autoresearchState) {
@@ -9618,7 +10015,8 @@ async function buildStopHookOutput(
9618
10015
  const autoNudgePhase = await readStopAutoNudgePhase(cwd, stateDir, canonicalSessionId, threadId);
9619
10016
 
9620
10017
  if (
9621
- autoNudgeConfig.enabled
10018
+ options.skipAutoNudge !== true
10019
+ && autoNudgeConfig.enabled
9622
10020
  && detectNativeStopStallPattern(lastAssistantMessage, autoNudgeConfig.patterns, autoNudgePhase)
9623
10021
  ) {
9624
10022
  const effectiveResponse = resolveEffectiveAutoNudgeResponse(autoNudgeConfig.response);
@@ -9695,13 +10093,39 @@ async function buildStopHookOutput(
9695
10093
  );
9696
10094
  }
9697
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
+
9698
10122
  export async function dispatchCodexNativeHook(
9699
10123
  payload: CodexHookPayload,
9700
10124
  options: NativeHookDispatchOptions = {},
9701
10125
  ): Promise<NativeHookDispatchResult> {
9702
10126
  const hookEventName = readHookEventName(payload);
9703
10127
  const cwd = options.cwd ?? (safeString(payload.cwd).trim() || process.cwd());
9704
- if (hookEventName === "Stop" && !hasNativeStopRuntimeSurface(cwd)) {
10128
+ if (hookEventName === "PostCompact" && process.env.OMX_NATIVE_HOOK_DOCTOR_SMOKE === "1") {
9705
10129
  return {
9706
10130
  hookEventName,
9707
10131
  omxEventName: mapCodexHookEventToOmxEvent(hookEventName),
@@ -9709,24 +10133,71 @@ export async function dispatchCodexNativeHook(
9709
10133
  outputJson: null,
9710
10134
  };
9711
10135
  }
9712
- // Native hooks must use the same authoritative runtime state root as HUD/MCP
9713
- // when boxed/team roots are active; do not bypass it with cwd/.omx/state.
9714
- const stateDir = getBaseStateDir(cwd);
9715
- if (hookEventName !== "Stop") {
9716
- 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
+ };
9717
10148
  }
10149
+ // Native hooks must use the exact pointer root selected for this dispatch.
10150
+ const pointerContext = resolveSessionPointerContext(cwd);
10151
+ const stateDir = pointerContext.baseStateDir;
9718
10152
 
9719
10153
  const omxEventName = mapCodexHookEventToOmxEvent(hookEventName);
9720
10154
  let skillState: SkillActiveState | null = null;
9721
10155
  let triageAdditionalContext: string | null = null;
9722
10156
  let goalWorkflowAdditionalContext: string | null = null;
9723
10157
  let ultragoalSteeringAdditionalContext: string | null = null;
10158
+ let promptClassification: KeywordInputClassification | null = null;
9724
10159
 
9725
10160
  const nativeSessionId = safeString(payload.session_id ?? payload.sessionId).trim();
9726
10161
  const threadId = safeString(payload.thread_id ?? payload.threadId).trim();
9727
10162
  const turnId = safeString(payload.turn_id ?? payload.turnId).trim();
9728
- 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
+ };
9729
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";
9730
10201
  let resolvedNativeSessionId = nativeSessionId;
9731
10202
  let skipCanonicalSessionStartContext = false;
9732
10203
  let isSubagentSessionStart = false;
@@ -9786,23 +10257,61 @@ export async function dispatchCodexNativeHook(
9786
10257
  );
9787
10258
  }
9788
10259
  } else {
9789
- const sessionState = await reconcileNativeSessionStart(cwd, nativeSessionId, {
9790
- pid: options.sessionOwnerPid ?? resolveSessionOwnerPid(payload),
9791
- });
9792
- canonicalSessionId = safeString(sessionState.session_id).trim();
9793
- 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
+ }
9794
10291
  }
9795
10292
  } else if (!canonicalSessionId) {
9796
10293
  canonicalSessionId = safeString(currentSessionState?.session_id).trim();
9797
10294
  }
9798
10295
 
9799
10296
  if (hookEventName === "Stop") {
9800
- const inheritedSessionId = safeString(process.env.OMX_SESSION_ID || process.env.CODEX_SESSION_ID).trim();
10297
+ const stopPayloadSessionId = readPayloadSessionId(payload);
9801
10298
  const stopCanonicalSessionId = await resolveInternalSessionIdForPayload(
9802
10299
  cwd,
9803
- readPayloadSessionId(payload) || inheritedSessionId,
10300
+ stopPayloadSessionId,
10301
+ undefined,
10302
+ currentSessionState,
10303
+ pointer.status === "absent",
9804
10304
  );
9805
- 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) {
9806
10315
  canonicalSessionId = stopCanonicalSessionId;
9807
10316
  }
9808
10317
  if (canonicalSessionId && safeString(currentSessionState?.session_id).trim() === canonicalSessionId) {
@@ -9811,8 +10320,8 @@ export async function dispatchCodexNativeHook(
9811
10320
  }
9812
10321
  }
9813
10322
 
9814
- const eventSessionId = canonicalSessionId || nativeSessionId || undefined;
9815
- const sessionIdForState = canonicalSessionId || nativeSessionId;
10323
+ let eventSessionId = canonicalSessionId || nativeSessionId || undefined;
10324
+ let sessionIdForState: string | null = canonicalSessionId || null;
9816
10325
  let outputJson: Record<string, unknown> | null = null;
9817
10326
  const typedAgentRolePayload = isTypedAgentRolePayload(payload);
9818
10327
  const isSubagentPromptSubmit = hookEventName === "UserPromptSubmit"
@@ -9841,36 +10350,81 @@ export async function dispatchCodexNativeHook(
9841
10350
  )),
9842
10351
  )).some(Boolean)
9843
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
+ }
9844
10360
  const suppressNoisySubagentLifecycleDispatch =
9845
10361
  (isSubagentSessionStart || isSubagentStop)
9846
10362
  && shouldSuppressSubagentLifecycleHookDispatch();
9847
10363
 
9848
10364
  if (hookEventName === "UserPromptSubmit") {
9849
10365
  const prompt = readPromptText(payload);
9850
- goalWorkflowAdditionalContext = await buildCompletedGoalCleanupPromptWarning(cwd, prompt).catch(() => null)
9851
- ?? await buildGoalWorkflowReconciliationPromptWarning(cwd, prompt).catch(() => null);
9852
- 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
9853
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)}`)
9854
10375
  : null;
9855
- 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) {
9856
10396
  skillState = buildNativeOutsideTmuxTeamPromptBlockState(
9857
- prompt,
10397
+ promptClassification,
9858
10398
  cwd,
9859
10399
  payload,
9860
- sessionIdForState,
10400
+ sessionIdForState || undefined,
9861
10401
  threadId || undefined,
9862
10402
  turnId || undefined,
9863
10403
  ) ?? await recordSkillActivation({
9864
10404
  stateDir,
9865
10405
  sourceCwd: cwd,
9866
10406
  text: prompt,
9867
- sessionId: sessionIdForState,
10407
+ classification: promptClassification,
10408
+ allowSecondaryTeam: !isNativeOutsideTmuxUserPrompt(cwd, payload, sessionIdForState || undefined),
10409
+ sessionId: sessionIdForState || undefined,
9868
10410
  threadId,
9869
10411
  turnId,
10412
+ resolvedPromptTurnContext: promptTurnContext ?? undefined,
10413
+ onProvenanceRejected: async (diagnostic) => {
10414
+ await appendPromptSessionProvenanceRejection(pointerContext, diagnostic).catch(() => {});
10415
+ },
9870
10416
  });
9871
10417
  }
9872
10418
  // --- Triage classifier (advisory-only, non-keyword prompts) ---
9873
- 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
+ ) {
9874
10428
  try {
9875
10429
  if (readTriageConfig().enabled) {
9876
10430
  const normalized = prompt.trim().toLowerCase();
@@ -9899,7 +10453,9 @@ export async function dispatchCodexNativeHook(
9899
10453
  },
9900
10454
  suppress_followup: true,
9901
10455
  };
9902
- writeTriageState({ cwd, sessionId: sessionIdForState || null, state: newState });
10456
+ if (!suppressActivationSeeding) {
10457
+ writeTriageState({ cwd, sessionId: sessionIdForState || null, state: newState });
10458
+ }
9903
10459
  } else if (decision.lane === "LIGHT") {
9904
10460
  if (decision.destination === "explore") {
9905
10461
  triageAdditionalContext =
@@ -9928,7 +10484,9 @@ export async function dispatchCodexNativeHook(
9928
10484
  },
9929
10485
  suppress_followup: true,
9930
10486
  };
9931
- writeTriageState({ cwd, sessionId: sessionIdForState || null, state: newState });
10487
+ if (!suppressActivationSeeding) {
10488
+ writeTriageState({ cwd, sessionId: sessionIdForState || null, state: newState });
10489
+ }
9932
10490
  }
9933
10491
  }
9934
10492
  // lane === "PASS": no context, no state write
@@ -9942,7 +10500,7 @@ export async function dispatchCodexNativeHook(
9942
10500
  const skipHudReconcileForDoctorSmoke = process.env.OMX_NATIVE_HOOK_DOCTOR_SMOKE === "1";
9943
10501
  const skipHudReconcileForTeamWorkerPane = !isSubagentPromptSubmit
9944
10502
  && await isConfirmedTeamWorkerPromptSubmitPane(cwd).catch(() => false);
9945
- if (!skipHudReconcileForDoctorSmoke && !skipHudReconcileForTeamWorkerPane) {
10503
+ if (allowImplicitSessionSideEffects && allowPromptGlobalSideEffects && !skipHudReconcileForDoctorSmoke && !skipHudReconcileForTeamWorkerPane) {
9946
10504
  const reconcileHudForPromptSubmitFn = options.reconcileHudForPromptSubmitFn ?? reconcileHudForPromptSubmit;
9947
10505
  const hudSessionId = resolveHudReconcileSessionId(
9948
10506
  currentSessionState,
@@ -9959,7 +10517,7 @@ export async function dispatchCodexNativeHook(
9959
10517
  }
9960
10518
  }
9961
10519
 
9962
- if (omxEventName && !skipCanonicalSessionStartContext && !suppressNoisySubagentLifecycleDispatch) {
10520
+ if (omxEventName && allowImplicitSessionSideEffects && allowPromptGlobalSideEffects && !skipCanonicalSessionStartContext && !suppressNoisySubagentLifecycleDispatch) {
9963
10521
  const baseContext = buildBaseContext(cwd, payload, hookEventName!, canonicalSessionId);
9964
10522
  if (resolvedNativeSessionId) {
9965
10523
  baseContext.native_session_id = resolvedNativeSessionId;
@@ -10000,12 +10558,14 @@ export async function dispatchCodexNativeHook(
10000
10558
  })
10001
10559
  : isSubagentPromptSubmit
10002
10560
  ? null
10003
- : [
10004
- buildAdditionalContextMessage(readPromptText(payload), skillState, cwd, payload),
10005
- ultragoalSteeringAdditionalContext,
10006
- goalWorkflowAdditionalContext,
10007
- triageAdditionalContext,
10008
- ].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;
10009
10569
  if (additionalContext) {
10010
10570
  outputJson = {
10011
10571
  hookSpecificOutput: {
@@ -10015,12 +10575,55 @@ export async function dispatchCodexNativeHook(
10015
10575
  };
10016
10576
  }
10017
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
+ }
10018
10620
  const payloadSessionId = readPayloadSessionId(payload);
10019
10621
  const rootPointerConflict = await readLiveRootSessionPointerConflict(stateDir, payloadSessionId);
10020
10622
  const preToolUseSessionId = payloadSessionId
10021
10623
  ? await resolveInternalSessionIdForPayload(cwd, payloadSessionId, stateDir)
10022
10624
  : "";
10023
- outputJson = await buildDeepInterviewPreToolUseBoundaryOutput(payload, cwd, stateDir, preToolUseSessionId)
10625
+ outputJson = buildNativeUnknownRolePreToolUseOutput(payload)
10626
+ ?? await buildDeepInterviewPreToolUseBoundaryOutput(payload, cwd, stateDir, preToolUseSessionId)
10024
10627
  ?? await buildRalplanPreToolUseBoundaryOutput(payload, cwd, stateDir, preToolUseSessionId)
10025
10628
  ?? await buildPlanningRootPointerConflictPreToolUseOutput(payload, cwd, stateDir, rootPointerConflict)
10026
10629
  ?? await buildConductorPreToolUseWriteGuardOutput(payload, cwd, stateDir, preToolUseSessionId)
@@ -10028,17 +10631,34 @@ export async function dispatchCodexNativeHook(
10028
10631
  ?? buildMalformedPreToolUseBlockTestOutput(payload)
10029
10632
  ?? buildNativePreToolUseOutput(payload);
10030
10633
  } else if (hookEventName === "PostToolUse") {
10031
- await recordNativeSubagentCapacityBlocker(cwd, stateDir, payload).catch(() => {});
10032
- await recordNativeSubagentSupportBlocker(cwd, stateDir, payload).catch(() => {});
10033
- if (detectMcpTransportFailure(payload)) {
10034
- 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);
10035
10641
  }
10036
10642
  outputJson = buildNativePostToolUseOutput(payload);
10037
- await handleTeamWorkerPostToolUseSuccess(payload, cwd);
10038
10643
  } else if (hookEventName === "Stop") {
10039
- outputJson = await buildStopHookOutput(payload, cwd, stateDir, {
10040
- skipRalphStopBlock: isSubagentStop,
10041
- }) ?? 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
+ }
10042
10662
  }
10043
10663
 
10044
10664
  return {