oh-my-codex 0.20.0 → 0.20.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (661) hide show
  1. package/Cargo.lock +6 -6
  2. package/Cargo.toml +1 -1
  3. package/README.md +10 -1
  4. package/dist/adapt/__tests__/hermes.test.js.map +1 -1
  5. package/dist/adapt/contracts.d.ts.map +1 -1
  6. package/dist/agents/native-config.js.map +1 -1
  7. package/dist/auth/__tests__/hotswap.test.d.ts +2 -0
  8. package/dist/auth/__tests__/hotswap.test.d.ts.map +1 -0
  9. package/dist/auth/__tests__/hotswap.test.js +183 -0
  10. package/dist/auth/__tests__/hotswap.test.js.map +1 -0
  11. package/dist/auth/hotswap.d.ts.map +1 -1
  12. package/dist/auth/hotswap.js +17 -4
  13. package/dist/auth/hotswap.js.map +1 -1
  14. package/dist/auth/paths.js.map +1 -1
  15. package/dist/auth/storage.js.map +1 -1
  16. package/dist/autopilot/__tests__/ralplan-gate.test.js +4 -4
  17. package/dist/autopilot/__tests__/ralplan-gate.test.js.map +1 -1
  18. package/dist/autopilot/completion-gate.js.map +1 -1
  19. package/dist/autopilot/fsm.d.ts +1 -1
  20. package/dist/autopilot/fsm.d.ts.map +1 -1
  21. package/dist/autoresearch/goal.js.map +1 -1
  22. package/dist/autoresearch/runtime.js.map +1 -1
  23. package/dist/capabilities/lockfile.js.map +1 -1
  24. package/dist/catalog/reader.js.map +1 -1
  25. package/dist/catalog/skill-mirror.js.map +1 -1
  26. package/dist/cli/__tests__/adapt-help.test.js.map +1 -1
  27. package/dist/cli/__tests__/agents-init.test.js.map +1 -1
  28. package/dist/cli/__tests__/agents.test.js.map +1 -1
  29. package/dist/cli/__tests__/api.test.js.map +1 -1
  30. package/dist/cli/__tests__/ask.test.js.map +1 -1
  31. package/dist/cli/__tests__/auth.test.js.map +1 -1
  32. package/dist/cli/__tests__/autoresearch-guided.test.js.map +1 -1
  33. package/dist/cli/__tests__/autoresearch.test.js.map +1 -1
  34. package/dist/cli/__tests__/capabilities.test.js.map +1 -1
  35. package/dist/cli/__tests__/codex-plugin-layout.test.js +27 -0
  36. package/dist/cli/__tests__/codex-plugin-layout.test.js.map +1 -1
  37. package/dist/cli/__tests__/doctor-context-window-warning.test.js +83 -78
  38. package/dist/cli/__tests__/doctor-context-window-warning.test.js.map +1 -1
  39. package/dist/cli/__tests__/doctor-invalid-config.test.js +56 -4
  40. package/dist/cli/__tests__/doctor-invalid-config.test.js.map +1 -1
  41. package/dist/cli/__tests__/doctor-team.test.js.map +1 -1
  42. package/dist/cli/__tests__/doctor-warning-copy.test.js +735 -14
  43. package/dist/cli/__tests__/doctor-warning-copy.test.js.map +1 -1
  44. package/dist/cli/__tests__/exec.test.js.map +1 -1
  45. package/dist/cli/__tests__/imagegen-continuation.test.js.map +1 -1
  46. package/dist/cli/__tests__/index.test.js +216 -11
  47. package/dist/cli/__tests__/index.test.js.map +1 -1
  48. package/dist/cli/__tests__/launch-fallback.test.js +49 -3
  49. package/dist/cli/__tests__/launch-fallback.test.js.map +1 -1
  50. package/dist/cli/__tests__/mcp-parity.test.js +84 -12
  51. package/dist/cli/__tests__/mcp-parity.test.js.map +1 -1
  52. package/dist/cli/__tests__/native-hook-claim-journal.test.d.ts +2 -0
  53. package/dist/cli/__tests__/native-hook-claim-journal.test.d.ts.map +1 -0
  54. package/dist/cli/__tests__/native-hook-claim-journal.test.js +128 -0
  55. package/dist/cli/__tests__/native-hook-claim-journal.test.js.map +1 -0
  56. package/dist/cli/__tests__/nested-help-routing.test.js.map +1 -1
  57. package/dist/cli/__tests__/question.test.js.map +1 -1
  58. package/dist/cli/__tests__/ralph-deslop-contract.test.js.map +1 -1
  59. package/dist/cli/__tests__/ralph-goal-mode-contract.test.js +6 -0
  60. package/dist/cli/__tests__/ralph-goal-mode-contract.test.js.map +1 -1
  61. package/dist/cli/__tests__/ralph-prd-deep-interview.test.js.map +1 -1
  62. package/dist/cli/__tests__/ralph-prd-smoke.test.js.map +1 -1
  63. package/dist/cli/__tests__/ralph.test.js +8 -3
  64. package/dist/cli/__tests__/ralph.test.js.map +1 -1
  65. package/dist/cli/__tests__/ralplan-bootstrap-3181.test.d.ts +2 -0
  66. package/dist/cli/__tests__/ralplan-bootstrap-3181.test.d.ts.map +1 -0
  67. package/dist/cli/__tests__/ralplan-bootstrap-3181.test.js +187 -0
  68. package/dist/cli/__tests__/ralplan-bootstrap-3181.test.js.map +1 -0
  69. package/dist/cli/__tests__/ralplan.test.d.ts +2 -0
  70. package/dist/cli/__tests__/ralplan.test.d.ts.map +1 -0
  71. package/dist/cli/__tests__/ralplan.test.js +129 -0
  72. package/dist/cli/__tests__/ralplan.test.js.map +1 -0
  73. package/dist/cli/__tests__/resume.test.js +6 -1
  74. package/dist/cli/__tests__/resume.test.js.map +1 -1
  75. package/dist/cli/__tests__/session-scoped-runtime.test.js +25 -0
  76. package/dist/cli/__tests__/session-scoped-runtime.test.js.map +1 -1
  77. package/dist/cli/__tests__/session-search-help.test.js.map +1 -1
  78. package/dist/cli/__tests__/session-search.test.js.map +1 -1
  79. package/dist/cli/__tests__/setup-agents-overwrite.test.js +79 -0
  80. package/dist/cli/__tests__/setup-agents-overwrite.test.js.map +1 -1
  81. package/dist/cli/__tests__/setup-gh-star.test.js.map +1 -1
  82. package/dist/cli/__tests__/setup-hooks-shared-ownership.test.js +131 -5
  83. package/dist/cli/__tests__/setup-hooks-shared-ownership.test.js.map +1 -1
  84. package/dist/cli/__tests__/setup-hooks-trust-e2e.test.d.ts +2 -0
  85. package/dist/cli/__tests__/setup-hooks-trust-e2e.test.d.ts.map +1 -0
  86. package/dist/cli/__tests__/setup-hooks-trust-e2e.test.js +285 -0
  87. package/dist/cli/__tests__/setup-hooks-trust-e2e.test.js.map +1 -0
  88. package/dist/cli/__tests__/setup-install-mode.test.js +2264 -17
  89. package/dist/cli/__tests__/setup-install-mode.test.js.map +1 -1
  90. package/dist/cli/__tests__/setup-preferences.test.d.ts +2 -0
  91. package/dist/cli/__tests__/setup-preferences.test.d.ts.map +1 -0
  92. package/dist/cli/__tests__/setup-preferences.test.js +79 -0
  93. package/dist/cli/__tests__/setup-preferences.test.js.map +1 -0
  94. package/dist/cli/__tests__/setup-refresh.test.js +168 -5
  95. package/dist/cli/__tests__/setup-refresh.test.js.map +1 -1
  96. package/dist/cli/__tests__/setup-scope.test.js +73 -6
  97. package/dist/cli/__tests__/setup-scope.test.js.map +1 -1
  98. package/dist/cli/__tests__/sparkshell-cli.test.js.map +1 -1
  99. package/dist/cli/__tests__/state.test.js +37 -0
  100. package/dist/cli/__tests__/state.test.js.map +1 -1
  101. package/dist/cli/__tests__/team.test.js.map +1 -1
  102. package/dist/cli/__tests__/ultragoal.test.js +61 -1
  103. package/dist/cli/__tests__/ultragoal.test.js.map +1 -1
  104. package/dist/cli/__tests__/uninstall.test.js +2067 -26
  105. package/dist/cli/__tests__/uninstall.test.js.map +1 -1
  106. package/dist/cli/__tests__/update.test.js +58 -0
  107. package/dist/cli/__tests__/update.test.js.map +1 -1
  108. package/dist/cli/__tests__/url.test.js.map +1 -1
  109. package/dist/cli/__tests__/windows-popup-loop-contract.test.js.map +1 -1
  110. package/dist/cli/adapt.js.map +1 -1
  111. package/dist/cli/agents-init.js.map +1 -1
  112. package/dist/cli/agents.js.map +1 -1
  113. package/dist/cli/api.js.map +1 -1
  114. package/dist/cli/ask.d.ts +1 -1
  115. package/dist/cli/ask.d.ts.map +1 -1
  116. package/dist/cli/ask.js.map +1 -1
  117. package/dist/cli/auth.js.map +1 -1
  118. package/dist/cli/autoresearch-guided.js.map +1 -1
  119. package/dist/cli/autoresearch-intake.js.map +1 -1
  120. package/dist/cli/capabilities.js.map +1 -1
  121. package/dist/cli/cleanup.js.map +1 -1
  122. package/dist/cli/codex-feature-probe.js.map +1 -1
  123. package/dist/cli/codex-home.js.map +1 -1
  124. package/dist/cli/doctor.d.ts +20 -0
  125. package/dist/cli/doctor.d.ts.map +1 -1
  126. package/dist/cli/doctor.js +636 -161
  127. package/dist/cli/doctor.js.map +1 -1
  128. package/dist/cli/explore.js.map +1 -1
  129. package/dist/cli/index.d.ts +11 -3
  130. package/dist/cli/index.d.ts.map +1 -1
  131. package/dist/cli/index.js +108 -20
  132. package/dist/cli/index.js.map +1 -1
  133. package/dist/cli/mcp-serve.js.map +1 -1
  134. package/dist/cli/mission.js.map +1 -1
  135. package/dist/cli/native-assets.js.map +1 -1
  136. package/dist/cli/native-hook-claim-journal.d.ts +18 -0
  137. package/dist/cli/native-hook-claim-journal.d.ts.map +1 -0
  138. package/dist/cli/native-hook-claim-journal.js +239 -0
  139. package/dist/cli/native-hook-claim-journal.js.map +1 -0
  140. package/dist/cli/omx.js.map +1 -1
  141. package/dist/cli/plugin-marketplace.js.map +1 -1
  142. package/dist/cli/project-runtime-codex-homes.js.map +1 -1
  143. package/dist/cli/question.js.map +1 -1
  144. package/dist/cli/ralph.d.ts.map +1 -1
  145. package/dist/cli/ralph.js +3 -2
  146. package/dist/cli/ralph.js.map +1 -1
  147. package/dist/cli/ralplan.d.ts +14 -0
  148. package/dist/cli/ralplan.d.ts.map +1 -0
  149. package/dist/cli/ralplan.js +203 -0
  150. package/dist/cli/ralplan.js.map +1 -0
  151. package/dist/cli/setup-preferences.d.ts +11 -0
  152. package/dist/cli/setup-preferences.d.ts.map +1 -1
  153. package/dist/cli/setup-preferences.js +32 -0
  154. package/dist/cli/setup-preferences.js.map +1 -1
  155. package/dist/cli/setup.d.ts +53 -0
  156. package/dist/cli/setup.d.ts.map +1 -1
  157. package/dist/cli/setup.js +1403 -400
  158. package/dist/cli/setup.js.map +1 -1
  159. package/dist/cli/sparkshell.js.map +1 -1
  160. package/dist/cli/star-prompt.js.map +1 -1
  161. package/dist/cli/state.d.ts.map +1 -1
  162. package/dist/cli/state.js +2 -1
  163. package/dist/cli/state.js.map +1 -1
  164. package/dist/cli/team.js.map +1 -1
  165. package/dist/cli/ultragoal.d.ts +5 -1
  166. package/dist/cli/ultragoal.d.ts.map +1 -1
  167. package/dist/cli/ultragoal.js +34 -2
  168. package/dist/cli/ultragoal.js.map +1 -1
  169. package/dist/cli/uninstall.d.ts +8 -0
  170. package/dist/cli/uninstall.d.ts.map +1 -1
  171. package/dist/cli/uninstall.js +947 -99
  172. package/dist/cli/uninstall.js.map +1 -1
  173. package/dist/cli/update.d.ts.map +1 -1
  174. package/dist/cli/update.js +8 -1
  175. package/dist/cli/update.js.map +1 -1
  176. package/dist/cli/url.js.map +1 -1
  177. package/dist/compat/__tests__/doctor-contract.test.js +5 -1
  178. package/dist/compat/__tests__/doctor-contract.test.js.map +1 -1
  179. package/dist/compat/__tests__/rust-runtime-compat.test.js.map +1 -1
  180. package/dist/config/__tests__/codex-hooks.test.js +1287 -11
  181. package/dist/config/__tests__/codex-hooks.test.js.map +1 -1
  182. package/dist/config/__tests__/generator-idempotent.test.js +1049 -135
  183. package/dist/config/__tests__/generator-idempotent.test.js.map +1 -1
  184. package/dist/config/__tests__/generator-notify.test.js +28 -49
  185. package/dist/config/__tests__/generator-notify.test.js.map +1 -1
  186. package/dist/config/codex-feature-flags.d.ts.map +1 -1
  187. package/dist/config/codex-feature-flags.js.map +1 -1
  188. package/dist/config/codex-hooks.d.ts +124 -1
  189. package/dist/config/codex-hooks.d.ts.map +1 -1
  190. package/dist/config/codex-hooks.js +2142 -265
  191. package/dist/config/codex-hooks.js.map +1 -1
  192. package/dist/config/commit-lore-guard.js.map +1 -1
  193. package/dist/config/generator.d.ts +40 -26
  194. package/dist/config/generator.d.ts.map +1 -1
  195. package/dist/config/generator.js +1159 -288
  196. package/dist/config/generator.js.map +1 -1
  197. package/dist/config/mcp-registry.js.map +1 -1
  198. package/dist/config/models.d.ts +2 -2
  199. package/dist/config/models.d.ts.map +1 -1
  200. package/dist/config/models.js.map +1 -1
  201. package/dist/config/omx-first-party-mcp.js.map +1 -1
  202. package/dist/config/team-mode.d.ts.map +1 -1
  203. package/dist/document-refresh/__tests__/enforcer.test.js.map +1 -1
  204. package/dist/document-refresh/enforcer.js.map +1 -1
  205. package/dist/exec/followup.js.map +1 -1
  206. package/dist/goal-workflows/artifacts.js.map +1 -1
  207. package/dist/hooks/__tests__/agents-overlay.test.js +7 -4
  208. package/dist/hooks/__tests__/agents-overlay.test.js.map +1 -1
  209. package/dist/hooks/__tests__/analyze-routing-contract.test.js.map +1 -1
  210. package/dist/hooks/__tests__/analyze-skill-contract.test.js.map +1 -1
  211. package/dist/hooks/__tests__/anti-slop-workflow.test.js.map +1 -1
  212. package/dist/hooks/__tests__/autopilot-skill-contract.test.js +9 -3
  213. package/dist/hooks/__tests__/autopilot-skill-contract.test.js.map +1 -1
  214. package/dist/hooks/__tests__/code-review-skill-contract.test.js.map +1 -1
  215. package/dist/hooks/__tests__/consensus-execution-handoff.test.js.map +1 -1
  216. package/dist/hooks/__tests__/deep-interview-contract.test.js.map +1 -1
  217. package/dist/hooks/__tests__/design-skill.test.js.map +1 -1
  218. package/dist/hooks/__tests__/keyword-detector.test.js +1372 -2
  219. package/dist/hooks/__tests__/keyword-detector.test.js.map +1 -1
  220. package/dist/hooks/__tests__/notify-fallback-watcher.test.js +14 -0
  221. package/dist/hooks/__tests__/notify-fallback-watcher.test.js.map +1 -1
  222. package/dist/hooks/__tests__/notify-hook-all-workers-idle.test.js.map +1 -1
  223. package/dist/hooks/__tests__/notify-hook-auto-nudge.test.js +669 -1
  224. package/dist/hooks/__tests__/notify-hook-auto-nudge.test.js.map +1 -1
  225. package/dist/hooks/__tests__/notify-hook-cross-worktree-heartbeat.test.js +2 -0
  226. package/dist/hooks/__tests__/notify-hook-cross-worktree-heartbeat.test.js.map +1 -1
  227. package/dist/hooks/__tests__/notify-hook-managed-tmux.test.js +142 -1
  228. package/dist/hooks/__tests__/notify-hook-managed-tmux.test.js.map +1 -1
  229. package/dist/hooks/__tests__/notify-hook-modules.test.js.map +1 -1
  230. package/dist/hooks/__tests__/notify-hook-non-omx-guard.test.js +48 -2
  231. package/dist/hooks/__tests__/notify-hook-non-omx-guard.test.js.map +1 -1
  232. package/dist/hooks/__tests__/notify-hook-ralph-resume.test.js +61 -44
  233. package/dist/hooks/__tests__/notify-hook-ralph-resume.test.js.map +1 -1
  234. package/dist/hooks/__tests__/notify-hook-regression-205.test.js.map +1 -1
  235. package/dist/hooks/__tests__/notify-hook-session-idle-dedupe.test.js +3 -2
  236. package/dist/hooks/__tests__/notify-hook-session-idle-dedupe.test.js.map +1 -1
  237. package/dist/hooks/__tests__/notify-hook-session-scope.test.js +243 -4
  238. package/dist/hooks/__tests__/notify-hook-session-scope.test.js.map +1 -1
  239. package/dist/hooks/__tests__/notify-hook-team-dispatch.test.js.map +1 -1
  240. package/dist/hooks/__tests__/notify-hook-team-leader-nudge.test.js +8 -1
  241. package/dist/hooks/__tests__/notify-hook-team-leader-nudge.test.js.map +1 -1
  242. package/dist/hooks/__tests__/notify-hook-team-tmux-guard.test.js.map +1 -1
  243. package/dist/hooks/__tests__/notify-hook-tmux-heal.test.js.map +1 -1
  244. package/dist/hooks/__tests__/notify-hook-tmux-scrollback.test.js.map +1 -1
  245. package/dist/hooks/__tests__/notify-hook-visual-verdict.test.js.map +1 -1
  246. package/dist/hooks/__tests__/notify-hook-worker-idle.test.js.map +1 -1
  247. package/dist/hooks/__tests__/openclaw-setup-contract.test.js.map +1 -1
  248. package/dist/hooks/__tests__/pre-context-gate-skills.test.js.map +1 -1
  249. package/dist/hooks/__tests__/prometheus-strict-contract.test.js.map +1 -1
  250. package/dist/hooks/__tests__/prompt-guidance-fragments.test.js.map +1 -1
  251. package/dist/hooks/__tests__/prompt-guidance-test-helpers.js.map +1 -1
  252. package/dist/hooks/__tests__/prompt-session-provenance.test.d.ts +2 -0
  253. package/dist/hooks/__tests__/prompt-session-provenance.test.d.ts.map +1 -0
  254. package/dist/hooks/__tests__/prompt-session-provenance.test.js +82 -0
  255. package/dist/hooks/__tests__/prompt-session-provenance.test.js.map +1 -0
  256. package/dist/hooks/__tests__/session.test.js +532 -11
  257. package/dist/hooks/__tests__/session.test.js.map +1 -1
  258. package/dist/hooks/__tests__/skill-catalog-hygiene.test.js.map +1 -1
  259. package/dist/hooks/__tests__/team-runtime-gating-docs-contract.test.js.map +1 -1
  260. package/dist/hooks/__tests__/visual-ralph-skill.test.js.map +1 -1
  261. package/dist/hooks/__tests__/visual-verdict-loop.test.js.map +1 -1
  262. package/dist/hooks/agents-overlay.js +5 -5
  263. package/dist/hooks/agents-overlay.js.map +1 -1
  264. package/dist/hooks/code-simplifier/index.js.map +1 -1
  265. package/dist/hooks/explore-routing.js.map +1 -1
  266. package/dist/hooks/extensibility/dispatcher.js.map +1 -1
  267. package/dist/hooks/extensibility/events.js.map +1 -1
  268. package/dist/hooks/extensibility/loader.js.map +1 -1
  269. package/dist/hooks/extensibility/plugin-runner-stdin.js.map +1 -1
  270. package/dist/hooks/extensibility/sdk/logging.js.map +1 -1
  271. package/dist/hooks/keyword-detector.d.ts +33 -7
  272. package/dist/hooks/keyword-detector.d.ts.map +1 -1
  273. package/dist/hooks/keyword-detector.js +2228 -119
  274. package/dist/hooks/keyword-detector.js.map +1 -1
  275. package/dist/hooks/keyword-registry.d.ts +12 -0
  276. package/dist/hooks/keyword-registry.d.ts.map +1 -1
  277. package/dist/hooks/keyword-registry.js +41 -0
  278. package/dist/hooks/keyword-registry.js.map +1 -1
  279. package/dist/hooks/prompt-session-provenance.d.ts +87 -0
  280. package/dist/hooks/prompt-session-provenance.d.ts.map +1 -0
  281. package/dist/hooks/prompt-session-provenance.js +180 -0
  282. package/dist/hooks/prompt-session-provenance.js.map +1 -0
  283. package/dist/hooks/session.d.ts +139 -34
  284. package/dist/hooks/session.d.ts.map +1 -1
  285. package/dist/hooks/session.js +1239 -317
  286. package/dist/hooks/session.js.map +1 -1
  287. package/dist/hooks/task-size-detector.js.map +1 -1
  288. package/dist/hud/__tests__/state.test.js.map +1 -1
  289. package/dist/hud/authority.js.map +1 -1
  290. package/dist/hud/constants.js.map +1 -1
  291. package/dist/hud/index.js.map +1 -1
  292. package/dist/hud/reconcile.js.map +1 -1
  293. package/dist/hud/render.js.map +1 -1
  294. package/dist/hud/state.js.map +1 -1
  295. package/dist/hud/tmux.js.map +1 -1
  296. package/dist/leader/__tests__/contract.test.js +146 -2
  297. package/dist/leader/__tests__/contract.test.js.map +1 -1
  298. package/dist/leader/contract.d.ts +26 -5
  299. package/dist/leader/contract.d.ts.map +1 -1
  300. package/dist/leader/contract.js +131 -7
  301. package/dist/leader/contract.js.map +1 -1
  302. package/dist/mcp/__tests__/bootstrap.test.js.map +1 -1
  303. package/dist/mcp/__tests__/server-lifecycle.test.js +15 -2
  304. package/dist/mcp/__tests__/server-lifecycle.test.js.map +1 -1
  305. package/dist/mcp/__tests__/state-paths.test.js +98 -1
  306. package/dist/mcp/__tests__/state-paths.test.js.map +1 -1
  307. package/dist/mcp/bootstrap.js.map +1 -1
  308. package/dist/mcp/code-intel-server.d.ts +58 -58
  309. package/dist/mcp/code-intel-server.d.ts.map +1 -1
  310. package/dist/mcp/code-intel-server.js.map +1 -1
  311. package/dist/mcp/hermes-bridge.js.map +1 -1
  312. package/dist/mcp/hermes-server.d.ts +70 -70
  313. package/dist/mcp/hermes-server.d.ts.map +1 -1
  314. package/dist/mcp/lifecycle-telemetry.js.map +1 -1
  315. package/dist/mcp/memory-server.d.ts +34 -34
  316. package/dist/mcp/memory-server.d.ts.map +1 -1
  317. package/dist/mcp/memory-server.js.map +1 -1
  318. package/dist/mcp/state-paths.d.ts +14 -0
  319. package/dist/mcp/state-paths.d.ts.map +1 -1
  320. package/dist/mcp/state-paths.js +96 -20
  321. package/dist/mcp/state-paths.js.map +1 -1
  322. package/dist/mcp/state-server.d.ts +47 -47
  323. package/dist/mcp/state-server.d.ts.map +1 -1
  324. package/dist/mcp/trace-server.d.ts +4 -4
  325. package/dist/mcp/trace-server.d.ts.map +1 -1
  326. package/dist/mcp/trace-server.js.map +1 -1
  327. package/dist/mcp/wiki-server.d.ts +23 -23
  328. package/dist/mcp/wiki-server.d.ts.map +1 -1
  329. package/dist/mcp/wiki-server.js.map +1 -1
  330. package/dist/modes/__tests__/base-session-scope.test.js +53 -1
  331. package/dist/modes/__tests__/base-session-scope.test.js.map +1 -1
  332. package/dist/modes/base.d.ts.map +1 -1
  333. package/dist/modes/base.js +5 -5
  334. package/dist/modes/base.js.map +1 -1
  335. package/dist/notifications/__tests__/lifecycle-dedupe.test.js.map +1 -1
  336. package/dist/notifications/__tests__/reply-listener.test.js.map +1 -1
  337. package/dist/notifications/__tests__/session-idle-tail-dedupe.test.js +177 -85
  338. package/dist/notifications/__tests__/session-idle-tail-dedupe.test.js.map +1 -1
  339. package/dist/notifications/__tests__/session-registry.test.js.map +1 -1
  340. package/dist/notifications/__tests__/template-engine.test.js.map +1 -1
  341. package/dist/notifications/__tests__/verbosity.test.js.map +1 -1
  342. package/dist/notifications/http-client.js.map +1 -1
  343. package/dist/notifications/index.d.ts +4 -1
  344. package/dist/notifications/index.d.ts.map +1 -1
  345. package/dist/notifications/index.js +3 -9
  346. package/dist/notifications/index.js.map +1 -1
  347. package/dist/notifications/lifecycle-dedupe.js.map +1 -1
  348. package/dist/notifications/reply-listener.d.ts.map +1 -1
  349. package/dist/notifications/reply-listener.js.map +1 -1
  350. package/dist/notifications/session-registry.js.map +1 -1
  351. package/dist/notifications/session-status.js.map +1 -1
  352. package/dist/notifications/temp-contract.js.map +1 -1
  353. package/dist/notifications/tmux-detector.js.map +1 -1
  354. package/dist/notifications/tmux.js.map +1 -1
  355. package/dist/openclaw/config.js.map +1 -1
  356. package/dist/openclaw/dispatcher.js.map +1 -1
  357. package/dist/pipeline/__tests__/orchestrator.test.js.map +1 -1
  358. package/dist/pipeline/__tests__/stages.test.js +8 -5
  359. package/dist/pipeline/__tests__/stages.test.js.map +1 -1
  360. package/dist/pipeline/stages/code-review.js.map +1 -1
  361. package/dist/pipeline/stages/ralph-verify.js.map +1 -1
  362. package/dist/pipeline/stages/ralplan.js.map +1 -1
  363. package/dist/pipeline/stages/team-exec.js.map +1 -1
  364. package/dist/pipeline/stages/ultragoal.js.map +1 -1
  365. package/dist/pipeline/stages/ultraqa.js.map +1 -1
  366. package/dist/planning/__tests__/approved-execution-lifecycle-matrix.test.js.map +1 -1
  367. package/dist/planning/artifact-names.js.map +1 -1
  368. package/dist/planning/artifacts.js.map +1 -1
  369. package/dist/question/__tests__/state.test.js.map +1 -1
  370. package/dist/question/__tests__/ui.test.js.map +1 -1
  371. package/dist/question/autopilot-wait.js.map +1 -1
  372. package/dist/question/client.d.ts.map +1 -1
  373. package/dist/question/client.js.map +1 -1
  374. package/dist/question/deep-interview.js.map +1 -1
  375. package/dist/question/events.js.map +1 -1
  376. package/dist/question/renderer.js.map +1 -1
  377. package/dist/question/state.d.ts.map +1 -1
  378. package/dist/question/state.js.map +1 -1
  379. package/dist/question/ui.js.map +1 -1
  380. package/dist/ralph/contract.d.ts +1 -1
  381. package/dist/ralph/contract.d.ts.map +1 -1
  382. package/dist/ralplan/__tests__/consensus-gate.test.js +403 -8
  383. package/dist/ralplan/__tests__/consensus-gate.test.js.map +1 -1
  384. package/dist/ralplan/__tests__/runtime.test.js +112 -14
  385. package/dist/ralplan/__tests__/runtime.test.js.map +1 -1
  386. package/dist/ralplan/consensus-gate.d.ts +3 -3
  387. package/dist/ralplan/consensus-gate.d.ts.map +1 -1
  388. package/dist/ralplan/consensus-gate.js +155 -60
  389. package/dist/ralplan/consensus-gate.js.map +1 -1
  390. package/dist/ralplan/runtime.d.ts +2 -2
  391. package/dist/ralplan/runtime.d.ts.map +1 -1
  392. package/dist/ralplan/runtime.js +1 -2
  393. package/dist/ralplan/runtime.js.map +1 -1
  394. package/dist/runtime/bridge.d.ts.map +1 -1
  395. package/dist/runtime/bridge.js.map +1 -1
  396. package/dist/runtime/process-tree.js.map +1 -1
  397. package/dist/runtime/run-loop.js.map +1 -1
  398. package/dist/runtime/run-outcome.d.ts +3 -3
  399. package/dist/runtime/run-outcome.d.ts.map +1 -1
  400. package/dist/runtime/run-outcome.js.map +1 -1
  401. package/dist/runtime/run-state.js.map +1 -1
  402. package/dist/scripts/__tests__/codex-native-hook.test.js +5414 -801
  403. package/dist/scripts/__tests__/codex-native-hook.test.js.map +1 -1
  404. package/dist/scripts/__tests__/fixtures/issue-3138-installed-smoke.d.ts +2 -0
  405. package/dist/scripts/__tests__/fixtures/issue-3138-installed-smoke.d.ts.map +1 -0
  406. package/dist/scripts/__tests__/fixtures/issue-3138-installed-smoke.js +634 -0
  407. package/dist/scripts/__tests__/fixtures/issue-3138-installed-smoke.js.map +1 -0
  408. package/dist/scripts/__tests__/generate-release-body.test.js.map +1 -1
  409. package/dist/scripts/__tests__/hook-derived-watcher.test.js.map +1 -1
  410. package/dist/scripts/__tests__/notify-dispatcher.test.js.map +1 -1
  411. package/dist/scripts/__tests__/notify-fallback-delivery.test.d.ts +2 -0
  412. package/dist/scripts/__tests__/notify-fallback-delivery.test.d.ts.map +1 -0
  413. package/dist/scripts/__tests__/notify-fallback-delivery.test.js +174 -0
  414. package/dist/scripts/__tests__/notify-fallback-delivery.test.js.map +1 -0
  415. package/dist/scripts/__tests__/notify-state-io.test.js +57 -1
  416. package/dist/scripts/__tests__/notify-state-io.test.js.map +1 -1
  417. package/dist/scripts/__tests__/role-intent-bootstrap-e2e-3181.test.d.ts +2 -0
  418. package/dist/scripts/__tests__/role-intent-bootstrap-e2e-3181.test.d.ts.map +1 -0
  419. package/dist/scripts/__tests__/role-intent-bootstrap-e2e-3181.test.js +205 -0
  420. package/dist/scripts/__tests__/role-intent-bootstrap-e2e-3181.test.js.map +1 -0
  421. package/dist/scripts/__tests__/role-intent-durable-recovery-3181.test.d.ts +2 -0
  422. package/dist/scripts/__tests__/role-intent-durable-recovery-3181.test.d.ts.map +1 -0
  423. package/dist/scripts/__tests__/role-intent-durable-recovery-3181.test.js +175 -0
  424. package/dist/scripts/__tests__/role-intent-durable-recovery-3181.test.js.map +1 -0
  425. package/dist/scripts/__tests__/run-test-files.test.js.map +1 -1
  426. package/dist/scripts/__tests__/smoke-packed-install.test.js +848 -4
  427. package/dist/scripts/__tests__/smoke-packed-install.test.js.map +1 -1
  428. package/dist/scripts/__tests__/test-reply-listener-live.test.js.map +1 -1
  429. package/dist/scripts/build-api.js.map +1 -1
  430. package/dist/scripts/build-explore-harness.js.map +1 -1
  431. package/dist/scripts/build-sparkshell.js.map +1 -1
  432. package/dist/scripts/cleanup-explore-harness.js.map +1 -1
  433. package/dist/scripts/codex-execution-surface.js.map +1 -1
  434. package/dist/scripts/codex-native-hook.d.ts.map +1 -1
  435. package/dist/scripts/codex-native-hook.js +825 -172
  436. package/dist/scripts/codex-native-hook.js.map +1 -1
  437. package/dist/scripts/generate-native-release-manifest.js.map +1 -1
  438. package/dist/scripts/generate-release-body.js.map +1 -1
  439. package/dist/scripts/hook-payload-guard.d.ts.map +1 -1
  440. package/dist/scripts/hook-payload-guard.js.map +1 -1
  441. package/dist/scripts/notify-fallback-delivery.d.ts +48 -0
  442. package/dist/scripts/notify-fallback-delivery.d.ts.map +1 -0
  443. package/dist/scripts/notify-fallback-delivery.js +419 -0
  444. package/dist/scripts/notify-fallback-delivery.js.map +1 -0
  445. package/dist/scripts/notify-fallback-watcher.js +136 -30
  446. package/dist/scripts/notify-fallback-watcher.js.map +1 -1
  447. package/dist/scripts/notify-hook/auto-nudge.d.ts +18 -9
  448. package/dist/scripts/notify-hook/auto-nudge.d.ts.map +1 -1
  449. package/dist/scripts/notify-hook/auto-nudge.js +39 -13
  450. package/dist/scripts/notify-hook/auto-nudge.js.map +1 -1
  451. package/dist/scripts/notify-hook/managed-tmux.d.ts +23 -0
  452. package/dist/scripts/notify-hook/managed-tmux.d.ts.map +1 -1
  453. package/dist/scripts/notify-hook/managed-tmux.js +122 -12
  454. package/dist/scripts/notify-hook/managed-tmux.js.map +1 -1
  455. package/dist/scripts/notify-hook/orchestration-intent.d.ts +7 -7
  456. package/dist/scripts/notify-hook/orchestration-intent.d.ts.map +1 -1
  457. package/dist/scripts/notify-hook/ralph-session-resume.d.ts +5 -2
  458. package/dist/scripts/notify-hook/ralph-session-resume.d.ts.map +1 -1
  459. package/dist/scripts/notify-hook/ralph-session-resume.js +33 -37
  460. package/dist/scripts/notify-hook/ralph-session-resume.js.map +1 -1
  461. package/dist/scripts/notify-hook/state-io.d.ts +17 -0
  462. package/dist/scripts/notify-hook/state-io.d.ts.map +1 -1
  463. package/dist/scripts/notify-hook/state-io.js +73 -11
  464. package/dist/scripts/notify-hook/state-io.js.map +1 -1
  465. package/dist/scripts/notify-hook/team-dispatch.d.ts +1 -1
  466. package/dist/scripts/notify-hook/team-dispatch.js.map +1 -1
  467. package/dist/scripts/notify-hook/team-leader-nudge.d.ts +2 -2
  468. package/dist/scripts/notify-hook/team-tmux-guard.d.ts +4 -4
  469. package/dist/scripts/notify-hook/team-worker-posttooluse.js.map +1 -1
  470. package/dist/scripts/notify-hook/team-worker-stop.d.ts +1 -1
  471. package/dist/scripts/notify-hook/team-worker.d.ts +2 -2
  472. package/dist/scripts/notify-hook/tmux-injection.d.ts +8 -1
  473. package/dist/scripts/notify-hook/tmux-injection.d.ts.map +1 -1
  474. package/dist/scripts/notify-hook/tmux-injection.js +25 -16
  475. package/dist/scripts/notify-hook/tmux-injection.js.map +1 -1
  476. package/dist/scripts/notify-hook/visual-verdict.d.ts +1 -1
  477. package/dist/scripts/notify-hook/visual-verdict.d.ts.map +1 -1
  478. package/dist/scripts/notify-hook/visual-verdict.js +15 -13
  479. package/dist/scripts/notify-hook/visual-verdict.js.map +1 -1
  480. package/dist/scripts/notify-hook.d.ts +24 -1
  481. package/dist/scripts/notify-hook.d.ts.map +1 -1
  482. package/dist/scripts/notify-hook.js +266 -90
  483. package/dist/scripts/notify-hook.js.map +1 -1
  484. package/dist/scripts/postinstall.js.map +1 -1
  485. package/dist/scripts/prompt-inventory.js.map +1 -1
  486. package/dist/scripts/smoke-packed-install.d.ts +879 -2
  487. package/dist/scripts/smoke-packed-install.d.ts.map +1 -1
  488. package/dist/scripts/smoke-packed-install.js +1528 -7
  489. package/dist/scripts/smoke-packed-install.js.map +1 -1
  490. package/dist/scripts/sync-plugin-mirror.js.map +1 -1
  491. package/dist/scripts/test-reply-listener-live.js.map +1 -1
  492. package/dist/scripts/test-sparkshell.js.map +1 -1
  493. package/dist/scripts/verify-native-agents.js.map +1 -1
  494. package/dist/session-history/friction.js.map +1 -1
  495. package/dist/session-history/search.js.map +1 -1
  496. package/dist/sidecar/__tests__/boundary.test.js.map +1 -1
  497. package/dist/sidecar/collector.js.map +1 -1
  498. package/dist/sidecar/index.js.map +1 -1
  499. package/dist/sidecar/render.js.map +1 -1
  500. package/dist/sidecar/tmux.js.map +1 -1
  501. package/dist/state/__tests__/operations.test.js +64 -15
  502. package/dist/state/__tests__/operations.test.js.map +1 -1
  503. package/dist/state/__tests__/skill-active.test.js +19 -1
  504. package/dist/state/__tests__/skill-active.test.js.map +1 -1
  505. package/dist/state/__tests__/workflow-transition.test.js +61 -0
  506. package/dist/state/__tests__/workflow-transition.test.js.map +1 -1
  507. package/dist/state/mode-state-context.js.map +1 -1
  508. package/dist/state/operations.d.ts +1 -1
  509. package/dist/state/operations.d.ts.map +1 -1
  510. package/dist/state/operations.js +49 -55
  511. package/dist/state/operations.js.map +1 -1
  512. package/dist/state/paths.d.ts +1 -1
  513. package/dist/state/paths.d.ts.map +1 -1
  514. package/dist/state/paths.js +1 -1
  515. package/dist/state/paths.js.map +1 -1
  516. package/dist/state/skill-active.d.ts +13 -1
  517. package/dist/state/skill-active.d.ts.map +1 -1
  518. package/dist/state/skill-active.js +43 -4
  519. package/dist/state/skill-active.js.map +1 -1
  520. package/dist/state/terminal-normalization.js.map +1 -1
  521. package/dist/state/workflow-transition-reconcile.d.ts.map +1 -1
  522. package/dist/state/workflow-transition-reconcile.js +20 -4
  523. package/dist/state/workflow-transition-reconcile.js.map +1 -1
  524. package/dist/state/workflow-transition.d.ts +1 -1
  525. package/dist/state/workflow-transition.d.ts.map +1 -1
  526. package/dist/state/workflow-transition.js.map +1 -1
  527. package/dist/subagents/__tests__/adapted-role-binding.test.d.ts +2 -0
  528. package/dist/subagents/__tests__/adapted-role-binding.test.d.ts.map +1 -0
  529. package/dist/subagents/__tests__/adapted-role-binding.test.js +988 -0
  530. package/dist/subagents/__tests__/adapted-role-binding.test.js.map +1 -0
  531. package/dist/subagents/__tests__/leader-bootstrap-3181.test.d.ts +2 -0
  532. package/dist/subagents/__tests__/leader-bootstrap-3181.test.d.ts.map +1 -0
  533. package/dist/subagents/__tests__/leader-bootstrap-3181.test.js +273 -0
  534. package/dist/subagents/__tests__/leader-bootstrap-3181.test.js.map +1 -0
  535. package/dist/subagents/__tests__/tracker.test.js +1550 -10
  536. package/dist/subagents/__tests__/tracker.test.js.map +1 -1
  537. package/dist/subagents/adapted-role-binding.d.ts +17 -0
  538. package/dist/subagents/adapted-role-binding.d.ts.map +1 -0
  539. package/dist/subagents/adapted-role-binding.js +75 -0
  540. package/dist/subagents/adapted-role-binding.js.map +1 -0
  541. package/dist/subagents/role-routing-marker.d.ts +10 -0
  542. package/dist/subagents/role-routing-marker.d.ts.map +1 -0
  543. package/dist/subagents/role-routing-marker.js +136 -0
  544. package/dist/subagents/role-routing-marker.js.map +1 -0
  545. package/dist/subagents/tracker.d.ts +172 -0
  546. package/dist/subagents/tracker.d.ts.map +1 -1
  547. package/dist/subagents/tracker.js +1077 -61
  548. package/dist/subagents/tracker.js.map +1 -1
  549. package/dist/team/__tests__/approved-execution.test.js.map +1 -1
  550. package/dist/team/__tests__/delivery-e2e-smoke.test.js.map +1 -1
  551. package/dist/team/__tests__/model-contract.test.js +231 -1
  552. package/dist/team/__tests__/model-contract.test.js.map +1 -1
  553. package/dist/team/__tests__/role-router.test.js.map +1 -1
  554. package/dist/team/__tests__/runtime.test.js +401 -1
  555. package/dist/team/__tests__/runtime.test.js.map +1 -1
  556. package/dist/team/__tests__/scaling.test.js +181 -0
  557. package/dist/team/__tests__/scaling.test.js.map +1 -1
  558. package/dist/team/__tests__/state-root.test.js.map +1 -1
  559. package/dist/team/__tests__/tmux-session.test.js +434 -4
  560. package/dist/team/__tests__/tmux-session.test.js.map +1 -1
  561. package/dist/team/__tests__/tmux-test-fixture.js.map +1 -1
  562. package/dist/team/api-interop.d.ts +2 -2
  563. package/dist/team/api-interop.d.ts.map +1 -1
  564. package/dist/team/api-interop.js.map +1 -1
  565. package/dist/team/commit-hygiene.js.map +1 -1
  566. package/dist/team/contracts.d.ts +5 -5
  567. package/dist/team/contracts.d.ts.map +1 -1
  568. package/dist/team/delivery-log.js.map +1 -1
  569. package/dist/team/followup-planner.js.map +1 -1
  570. package/dist/team/goal-workflow.js.map +1 -1
  571. package/dist/team/idle-nudge.d.ts.map +1 -1
  572. package/dist/team/model-contract.d.ts +20 -1
  573. package/dist/team/model-contract.d.ts.map +1 -1
  574. package/dist/team/model-contract.js +267 -30
  575. package/dist/team/model-contract.js.map +1 -1
  576. package/dist/team/orchestrator.js.map +1 -1
  577. package/dist/team/pane-status.js.map +1 -1
  578. package/dist/team/phase-controller.js.map +1 -1
  579. package/dist/team/reminder-intents.d.ts +1 -1
  580. package/dist/team/reminder-intents.d.ts.map +1 -1
  581. package/dist/team/reminder-intents.js.map +1 -1
  582. package/dist/team/runtime-cli.js.map +1 -1
  583. package/dist/team/runtime.d.ts.map +1 -1
  584. package/dist/team/runtime.js +83 -52
  585. package/dist/team/runtime.js.map +1 -1
  586. package/dist/team/scaling.d.ts +9 -7
  587. package/dist/team/scaling.d.ts.map +1 -1
  588. package/dist/team/scaling.js +69 -28
  589. package/dist/team/scaling.js.map +1 -1
  590. package/dist/team/state/dispatch-lock.js.map +1 -1
  591. package/dist/team/state/dispatch.js.map +1 -1
  592. package/dist/team/state/events.js.map +1 -1
  593. package/dist/team/state/tasks.js.map +1 -1
  594. package/dist/team/state-root.js.map +1 -1
  595. package/dist/team/state.js.map +1 -1
  596. package/dist/team/team-identity.d.ts.map +1 -1
  597. package/dist/team/team-identity.js.map +1 -1
  598. package/dist/team/tmux-session.d.ts +2 -0
  599. package/dist/team/tmux-session.d.ts.map +1 -1
  600. package/dist/team/tmux-session.js +90 -42
  601. package/dist/team/tmux-session.js.map +1 -1
  602. package/dist/team/worker-bootstrap.js.map +1 -1
  603. package/dist/team/worktree.js.map +1 -1
  604. package/dist/ultragoal/__tests__/artifacts.test.js.map +1 -1
  605. package/dist/ultragoal/__tests__/docs-contract.test.js.map +1 -1
  606. package/dist/ultragoal/artifacts.d.ts.map +1 -1
  607. package/dist/ultragoal/artifacts.js +4 -1
  608. package/dist/ultragoal/artifacts.js.map +1 -1
  609. package/dist/url-reader/index.js.map +1 -1
  610. package/dist/utils/__tests__/agents-md.test.js +16 -0
  611. package/dist/utils/__tests__/agents-md.test.js.map +1 -1
  612. package/dist/utils/agents-md.d.ts.map +1 -1
  613. package/dist/utils/agents-md.js +4 -3
  614. package/dist/utils/agents-md.js.map +1 -1
  615. package/dist/utils/agents-model-table.js.map +1 -1
  616. package/dist/utils/package.js.map +1 -1
  617. package/dist/utils/paths.js.map +1 -1
  618. package/dist/utils/platform-command.js.map +1 -1
  619. package/dist/utils/repo-deps.js.map +1 -1
  620. package/dist/utils/version.js.map +1 -1
  621. package/dist/verification/__tests__/ci-rust-gates.test.js +1 -1
  622. package/dist/verification/__tests__/dev-merge-issue-close-workflow.test.js.map +1 -1
  623. package/dist/verification/verifier.js.map +1 -1
  624. package/dist/visual/constants.d.ts +1 -1
  625. package/dist/visual/constants.d.ts.map +1 -1
  626. package/dist/vscode/__tests__/index.test.js.map +1 -1
  627. package/dist/vscode/index.js.map +1 -1
  628. package/dist/wiki/__tests__/cjk-tokenize.test.js.map +1 -1
  629. package/dist/wiki/__tests__/lint.test.js.map +1 -1
  630. package/dist/wiki/__tests__/query.test.js.map +1 -1
  631. package/dist/wiki/__tests__/storage.test.js.map +1 -1
  632. package/dist/wiki/__tests__/test-helpers.d.ts.map +1 -1
  633. package/dist/wiki/lint.js.map +1 -1
  634. package/dist/wiki/query.js.map +1 -1
  635. package/dist/wiki/storage.js.map +1 -1
  636. package/package.json +2 -2
  637. package/plugins/oh-my-codex/.codex-plugin/plugin.json +1 -1
  638. package/plugins/oh-my-codex/skills/omx-setup/SKILL.md +10 -5
  639. package/plugins/oh-my-codex/skills/ralph/SKILL.md +4 -2
  640. package/plugins/oh-my-codex/skills/ralplan/SKILL.md +5 -2
  641. package/skills/omx-setup/SKILL.md +10 -5
  642. package/skills/ralph/SKILL.md +4 -2
  643. package/skills/ralplan/SKILL.md +5 -2
  644. package/src/scripts/__tests__/codex-native-hook.test.ts +20497 -12377
  645. package/src/scripts/__tests__/fixtures/issue-3138-installed-smoke.ts +734 -0
  646. package/src/scripts/__tests__/notify-fallback-delivery.test.ts +177 -0
  647. package/src/scripts/__tests__/notify-state-io.test.ts +73 -0
  648. package/src/scripts/__tests__/role-intent-bootstrap-e2e-3181.test.ts +215 -0
  649. package/src/scripts/__tests__/role-intent-durable-recovery-3181.test.ts +193 -0
  650. package/src/scripts/__tests__/smoke-packed-install.test.ts +991 -4
  651. package/src/scripts/codex-native-hook.ts +918 -172
  652. package/src/scripts/notify-fallback-delivery.ts +416 -0
  653. package/src/scripts/notify-fallback-watcher.ts +132 -40
  654. package/src/scripts/notify-hook/auto-nudge.ts +43 -12
  655. package/src/scripts/notify-hook/managed-tmux.ts +149 -12
  656. package/src/scripts/notify-hook/ralph-session-resume.ts +37 -36
  657. package/src/scripts/notify-hook/state-io.ts +100 -13
  658. package/src/scripts/notify-hook/tmux-injection.ts +38 -21
  659. package/src/scripts/notify-hook/visual-verdict.ts +22 -12
  660. package/src/scripts/notify-hook.ts +335 -90
  661. package/src/scripts/smoke-packed-install.ts +1829 -6
@@ -6,18 +6,22 @@ import { pathToFileURL } from "url";
6
6
  import { readModeStateForActiveDecision, readModeStateForSession, updateModeState } from "../modes/base.js";
7
7
  import { redactAuthSecrets } from "../auth/redact.js";
8
8
  import { SKILL_ACTIVE_STATE_FILE, extractSessionIdFromInitializedStatePath, getSkillActiveStatePathsForStateDir, listActiveSkills, readSkillActiveState, readVisibleSkillActiveStateForStateDir, } from "../state/skill-active.js";
9
- import { isTrustedSubagentThread, readSubagentSessionSummary, readSubagentSessionLedger, readSubagentTrackingState, recordSubagentTurnForSession, } from "../subagents/tracker.js";
9
+ import { isTrustedSubagentThread, OMX_ADAPTED_PROVENANCE, attestLeaderThread, readSubagentSessionSummary, readSubagentSessionLedger, readSubagentTrackingState, readSubagentTrackingStateStrict, recordSubagentTurn, recordSubagentTurnForSession, resolveInstalledRoleName, } from "../subagents/tracker.js";
10
+ import { bindAndPublishAdaptedRole, NATIVE_SUBAGENT_ROLE_ROUTING_MARKER_TTL_MS, recoverAdaptedRoleBindings, } from "../subagents/adapted-role-binding.js";
11
+ import { readRoleRoutingMarker, writeRoleRoutingMarker } from "../subagents/role-routing-marker.js";
10
12
  import { resolveCanonicalTeamStateRoot, resolveWorkerNotifyTeamStateRootPath } from "../team/state-root.js";
11
13
  import { inferTerminalLifecycleOutcome } from "../runtime/run-outcome.js";
12
- import { appendToLog, isSessionStale, isSessionStateUsable, readSessionState, readUsableSessionState, reconcileNativeSessionStart, } from "../hooks/session.js";
14
+ import { appendPromptSessionProvenanceRejection, appendToLog, isSessionPointerLaunchAbort, isSessionStale, isSessionStateUsable, normalizeSessionId, readSessionPointer, readSessionState, readUsableSessionState, reconcileNativeSessionStart, resolveSessionPointerContext, } from "../hooks/session.js";
15
+ import { evaluateResolvedPromptTurn, extractSelectedTargetOwnerEvidence, preflightSelectedTargetOwner, } from "../hooks/prompt-session-provenance.js";
13
16
  import { appendTeamEvent, readTeamLeaderAttention, readTeamConfig, readTeamManifestV2, readTeamPhase, writeTeamLeaderAttention, writeTeamPhase, } from "../team/state.js";
14
- import { codexAgentsDir, omxNotepadPath, projectCodexAgentsDir, resolveProjectMemoryPath } from "../utils/paths.js";
17
+ import { omxNotepadPath, resolveProjectMemoryPath } from "../utils/paths.js";
15
18
  import { findGitLayout } from "../utils/git-layout.js";
16
- import { getBaseStateDir, getStateFilePath, getStatePath, getAuthoritativeActiveStatePaths } from "../mcp/state-paths.js";
17
- import { detectKeywords, detectPrimaryKeyword, recordSkillActivation, } from "../hooks/keyword-detector.js";
19
+ import { getAuthoritativeActiveStatePaths, getBaseStateDir, getStateFilePath, getStatePath, resolveWritableStateScope, WRITABLE_STATE_SCOPE_ERRORS, } from "../mcp/state-paths.js";
20
+ import { classifyKeywordInput, recordSkillActivation, } from "../hooks/keyword-detector.js";
18
21
  import { buildDeepInterviewConfigInstruction } from "../hooks/deep-interview-config-instruction.js";
19
22
  import { readTeamModeConfig } from "../config/team-mode.js";
20
23
  import { detectNativeStopStallPattern, loadAutoNudgeConfig, normalizeAutoNudgeSignatureText, resolveEffectiveAutoNudgeResponse, } from "./notify-hook/auto-nudge.js";
24
+ import { probeActualTmuxInstanceEvidence, tmuxEvidenceBindsCandidate } from "./notify-hook/managed-tmux.js";
21
25
  import { SLOPPY_FALLBACK_GROUNDING_PATTERNS, SLOPPY_FALLBACK_IMPLEMENTATION_CONTEXT_PATTERNS, SLOPPY_FALLBACK_PHRASE_PATTERNS, buildNativePostToolUseOutput, buildNativePreToolUseOutput, commandInvokesApplyPatch, detectMcpTransportFailure, hasAnyPattern, } from "./codex-native-pre-post.js";
22
26
  import { handleTeamWorkerPostToolUseSuccess } from "./notify-hook/team-worker-posttooluse.js";
23
27
  import { maybeNudgeLeaderForAllowedWorkerStop } from "./notify-hook/team-worker-stop.js";
@@ -29,9 +33,8 @@ import { getNotificationConfig, getVerbosity } from "../notifications/config.js"
29
33
  import { reconcileHudForPromptSubmit } from "../hud/reconcile.js";
30
34
  import { onPreCompact as buildWikiPreCompactContext, onSessionStart as buildWikiSessionStartContext, } from "../wiki/lifecycle.js";
31
35
  import { readAutoresearchCompletionStatus, readAutoresearchModeStateForActiveDecision } from "../autoresearch/skill-validation.js";
32
- import { AGENT_DEFINITIONS } from "../agents/definitions.js";
33
36
  import { deriveAutopilotChildPhase, normalizeAutopilotPhase } from "../autopilot/fsm.js";
34
- import { CONDUCTOR_ORCHESTRATION_METADATA_PREFIXES, LEADER_CONDUCTOR_BLOCK, LEADER_CONDUCTOR_REUSE_AND_LEDGER_GUIDANCE, NATIVE_SUBAGENT_SUPPORT_BLOCKER_FILE, actionKindForConductorArtifact, authorizeConductorAction, buildUnsupportedNativeSubagentGuidance, classifyConductorArtifactKind, isUnsupportedNativeSubagentEvidence, resolveNativeSubagentSupportStatus, } from "../leader/contract.js";
37
+ import { CONDUCTOR_ORCHESTRATION_METADATA_PREFIXES, LEADER_CONDUCTOR_BLOCK, LEADER_CONDUCTOR_REUSE_AND_LEDGER_GUIDANCE, NATIVE_SUBAGENT_SUPPORT_BLOCKER_FILE, actionKindForConductorArtifact, authorizeConductorAction, buildRoleRoutingUnavailableGuidance, buildUnsupportedNativeSubagentGuidance, classifyConductorArtifactKind, isNativeSubagentSpawnToolName, isRoleRoutingUnavailableEvidence, isUnsupportedNativeSubagentEvidence, parseRoleIntentCorrelationToken, resolveNativeSubagentSupportStatus, } from "../leader/contract.js";
35
38
  import { readRunState } from "../runtime/run-state.js";
36
39
  import { evaluateRalphCompletionAuditEvidence, isRalphCompletePhase } from "../ralph/completion-audit.js";
37
40
  import { buildCodexGoalTerminalCleanupNotice, } from "../goal-workflows/codex-goal-snapshot.js";
@@ -42,7 +45,7 @@ import { readTriageConfig } from "../hooks/triage-config.js";
42
45
  import { readTriageState, writeTriageState, shouldSuppressFollowup, promptSignature, } from "../hooks/triage-state.js";
43
46
  import { isPendingDeepInterviewQuestionEnforcement, reconcileDeepInterviewQuestionEnforcementFromAnsweredRecords, } from "../question/deep-interview.js";
44
47
  import { readAutopilotDeepInterviewQuestionWaitState } from "../question/autopilot-wait.js";
45
- import { buildDocumentRefreshAdvisoryOutput, evaluateFinalHandoffDocumentRefresh, isFinalHandoffDocumentRefreshCandidate, } from "../document-refresh/enforcer.js";
48
+ import { evaluateFinalHandoffDocumentRefresh, isFinalHandoffDocumentRefreshCandidate, } from "../document-refresh/enforcer.js";
46
49
  import { buildExecFollowupStopOutput } from "../exec/followup.js";
47
50
  import { MAX_NATIVE_STDIN_JSON_BYTES, extractRawCodexHookEventName, } from "./hook-payload-guard.js";
48
51
  const TERMINAL_MODE_PHASES = new Set(["complete", "completed", "failed", "cancelled"]);
@@ -89,9 +92,6 @@ const RALPH_LIVE_RISK_PATTERNS = [
89
92
  /\b(?:database|db|terraform|kubectl|kubernetes|aws|gcp|azure|external|destructive)\b/i,
90
93
  /\b(?:telegram|vps|service|restart|send|notify|notification|notifications|cron)\b/i,
91
94
  ];
92
- const KNOWN_TYPED_AGENT_ROLES = new Set(Object.keys(AGENT_DEFINITIONS));
93
- let installedTypedAgentRoleNamesCacheKey = "";
94
- let installedTypedAgentRoleNamesCache = new Set();
95
95
  const RALPH_TASK_TEXT_FIELDS = [
96
96
  "task_description",
97
97
  "taskDescription",
@@ -115,6 +115,18 @@ const MAX_SESSION_META_LINE_BYTES = 256 * 1024;
115
115
  function safeString(value) {
116
116
  return typeof value === "string" ? value : "";
117
117
  }
118
+ async function resolveVerifiedOwnerOmxSessionId() {
119
+ const candidate = normalizeSessionId(process.env.OMX_SESSION_ID);
120
+ if (!candidate)
121
+ return undefined;
122
+ const evidence = await probeActualTmuxInstanceEvidence(process.env.TMUX_PANE);
123
+ return tmuxEvidenceBindsCandidate(evidence, candidate) ? candidate : undefined;
124
+ }
125
+ function isImplicitWritableScopeFailure(error) {
126
+ return error instanceof Error
127
+ && (error.message === WRITABLE_STATE_SCOPE_ERRORS.unboundEnvironment
128
+ || error.message === WRITABLE_STATE_SCOPE_ERRORS.unusableSession);
129
+ }
118
130
  function safeObject(value) {
119
131
  return value && typeof value === "object" ? value : {};
120
132
  }
@@ -219,6 +231,14 @@ function readBoundedFirstLineSync(path) {
219
231
  closeSync(fd);
220
232
  }
221
233
  }
234
+ function selectAuthoritativeTaskName(threadSpawn, subagent, payload) {
235
+ for (const obj of [threadSpawn, subagent, payload]) {
236
+ if (obj && typeof obj === "object" && "task_name" in obj) {
237
+ return { present: true, value: obj.task_name };
238
+ }
239
+ }
240
+ return { present: false, value: undefined };
241
+ }
222
242
  function readNativeSubagentSessionStartMetadata(transcriptPath) {
223
243
  const normalizedPath = transcriptPath.trim();
224
244
  if (!normalizedPath)
@@ -237,40 +257,100 @@ function readNativeSubagentSessionStartMetadata(transcriptPath) {
237
257
  const parentThreadId = safeString(threadSpawn.parent_thread_id).trim();
238
258
  if (!parentThreadId)
239
259
  return null;
240
- const agentNickname = safeString(threadSpawn.agent_nickname ?? payload.agent_nickname).trim();
241
- const agentRole = safeString(threadSpawn.agent_role ?? payload.agent_role).trim();
260
+ const agentNicknameCarrierValues = [
261
+ threadSpawn.agent_nickname ?? threadSpawn.agentNickname,
262
+ subagent.agent_nickname ?? subagent.agentNickname,
263
+ payload.agent_nickname ?? payload.agentNickname,
264
+ ];
265
+ const agentNickname = safeString(agentNicknameCarrierValues[0]).trim();
266
+ const authoritativeTaskName = selectAuthoritativeTaskName(threadSpawn, subagent, payload);
267
+ const correlationToken = authoritativeTaskName.present
268
+ ? parseRoleIntentCorrelationToken(authoritativeTaskName.value)
269
+ : undefined;
270
+ const agentRole = safeString(threadSpawn.agent_role
271
+ ?? threadSpawn.agentRole
272
+ ?? threadSpawn.agent_type
273
+ ?? threadSpawn.agentType
274
+ ?? payload.agent_role
275
+ ?? payload.agentRole
276
+ ?? payload.agent_type
277
+ ?? payload.agentType).trim();
242
278
  return {
243
279
  parentThreadId,
244
280
  ...(agentNickname ? { agentNickname } : {}),
245
281
  ...(agentRole ? { agentRole } : {}),
282
+ ...(correlationToken ? { correlationToken } : {}),
246
283
  };
247
284
  }
248
285
  catch {
249
286
  return null;
250
287
  }
251
288
  }
289
+ function reportRoleRoutingBindingFailure(error) {
290
+ console.error(`[omx] SECURITY: native adapted role binding did not complete; retained binding will be recovered: ${error instanceof Error ? error.message : String(error)}`);
291
+ }
252
292
  async function recordNativeSubagentSessionStart(cwd, canonicalSessionId, childSessionId, metadata, transcriptPath) {
253
293
  const parentThreadId = metadata.parentThreadId.trim();
254
294
  const childThreadId = childSessionId.trim();
295
+ const correlationSessionId = canonicalSessionId.trim() || parentThreadId;
255
296
  const trackingSessionIds = [...new Set([
256
297
  canonicalSessionId.trim(),
257
298
  parentThreadId,
258
299
  ].filter(Boolean))];
259
- for (const sessionId of trackingSessionIds) {
260
- if (parentThreadId && parentThreadId !== childThreadId) {
300
+ let adaptedRoleIntent = null;
301
+ if (!metadata.agentRole && correlationSessionId && parentThreadId) {
302
+ try {
303
+ const bound = bindAndPublishAdaptedRole(cwd, getBaseStateDir(cwd), {
304
+ correlationSessionId,
305
+ parentThreadId,
306
+ correlationToken: metadata.correlationToken,
307
+ }, (state, intent) => {
308
+ let next = state;
309
+ for (const sessionId of trackingSessionIds) {
310
+ if (parentThreadId && parentThreadId !== childThreadId) {
311
+ next = recordSubagentTurn(next, {
312
+ sessionId,
313
+ threadId: parentThreadId,
314
+ kind: "leader",
315
+ });
316
+ }
317
+ next = recordSubagentTurn(next, {
318
+ sessionId,
319
+ threadId: childThreadId,
320
+ kind: "subagent",
321
+ ...(parentThreadId && parentThreadId !== childThreadId ? { leaderThreadId: parentThreadId } : {}),
322
+ mode: intent.role,
323
+ role: intent.role,
324
+ provenanceKind: intent.provenanceKind,
325
+ });
326
+ }
327
+ return next;
328
+ });
329
+ adaptedRoleIntent = bound ? { role: bound.role, provenanceKind: OMX_ADAPTED_PROVENANCE } : null;
330
+ }
331
+ catch (error) {
332
+ reportRoleRoutingBindingFailure(error);
333
+ throw error;
334
+ }
335
+ }
336
+ if (!adaptedRoleIntent) {
337
+ for (const sessionId of trackingSessionIds) {
338
+ if (parentThreadId && parentThreadId !== childThreadId) {
339
+ await recordSubagentTurnForSession(cwd, {
340
+ sessionId,
341
+ threadId: parentThreadId,
342
+ kind: "leader",
343
+ }).catch(() => { });
344
+ }
261
345
  await recordSubagentTurnForSession(cwd, {
262
346
  sessionId,
263
- threadId: parentThreadId,
264
- kind: 'leader',
347
+ threadId: childThreadId,
348
+ kind: "subagent",
349
+ ...(parentThreadId && parentThreadId !== childThreadId ? { leaderThreadId: parentThreadId } : {}),
350
+ mode: metadata.agentRole,
265
351
  }).catch(() => { });
266
352
  }
267
- await recordSubagentTurnForSession(cwd, {
268
- sessionId,
269
- threadId: childThreadId,
270
- kind: 'subagent',
271
- ...(parentThreadId && parentThreadId !== childThreadId ? { leaderThreadId: parentThreadId } : {}),
272
- mode: metadata.agentRole,
273
- }).catch(() => { });
353
+ refreshNativeSubagentRoleRoutingMarker(cwd, getBaseStateDir(cwd), correlationSessionId, parentThreadId);
274
354
  }
275
355
  await appendToLog(cwd, {
276
356
  event: "subagent_session_start",
@@ -349,6 +429,26 @@ async function isNativeSubagentHook(cwd, canonicalSessionId, nativeSessionId, th
349
429
  return false;
350
430
  return Object.values(trackingState.sessions).some((session) => (candidateIds.some((id) => isTrustedSubagentThread(session, id))));
351
431
  }
432
+ async function readNativePromptThreadFacts(cwd, nativeSessionId, threadId, currentSessionState) {
433
+ const candidateIds = [nativeSessionId, threadId].map((value) => value.trim()).filter(Boolean);
434
+ if (candidateIds.length === 0)
435
+ return undefined;
436
+ const currentNativeIds = new Set([
437
+ safeString(currentSessionState?.native_session_id).trim(),
438
+ safeString(currentSessionState?.codex_session_id).trim(),
439
+ safeString(currentSessionState?.owner_codex_session_id).trim(),
440
+ ].filter(Boolean));
441
+ const currentTargetAnchored = Boolean(nativeSessionId && currentNativeIds.has(nativeSessionId));
442
+ const trackingState = await readSubagentTrackingState(cwd).catch(() => null);
443
+ if (!trackingState)
444
+ return currentTargetAnchored ? { currentTargetAnchored: true, rootOrDrift: true } : undefined;
445
+ const roots = Object.values(trackingState.sessions).flatMap((session) => (candidateIds.some((candidate) => isTrustedSubagentThread(session, candidate))
446
+ ? [session.session_id]
447
+ : []));
448
+ return roots.length > 0 || currentTargetAnchored
449
+ ? { trackerRootOwnerSessionIds: roots, currentTargetAnchored, rootOrDrift: currentTargetAnchored }
450
+ : undefined;
451
+ }
352
452
  function shouldSuppressSubagentLifecycleHookDispatch() {
353
453
  const config = getNotificationConfig();
354
454
  if (config?.includeChildAgents === true)
@@ -494,8 +594,8 @@ function readPromptText(payload) {
494
594
  payload.userPrompt,
495
595
  ];
496
596
  for (const candidate of candidates) {
497
- const value = safeString(candidate).trim();
498
- if (value)
597
+ const value = safeString(candidate);
598
+ if (value.trim())
499
599
  return value;
500
600
  }
501
601
  return "";
@@ -1759,19 +1859,21 @@ function buildTeamHelpInstruction(cwd, payload) {
1759
1859
  nativeSessionId: safeString(payload?.session_id ?? payload?.sessionId).trim(),
1760
1860
  }).teamHelpInstruction;
1761
1861
  }
1762
- function buildNativeOutsideTmuxTeamPromptBlockState(prompt, cwd, payload, sessionId, threadId, turnId) {
1763
- if (!readTeamModeConfig(cwd).enabled)
1764
- return null;
1765
- const match = detectPrimaryKeyword(prompt);
1766
- if (match?.skill !== "team")
1767
- return null;
1862
+ function isNativeOutsideTmuxUserPrompt(cwd, payload, sessionId) {
1768
1863
  const environment = resolveExecutionEnvironment(cwd, {
1769
1864
  hookEventName: "UserPromptSubmit",
1770
1865
  payload,
1771
1866
  canonicalSessionId: sessionId ?? "",
1772
1867
  nativeSessionId: safeString(payload.session_id ?? payload.sessionId).trim(),
1773
1868
  });
1774
- if (!(environment.launcher === "native" && environment.transport === "outside-tmux"))
1869
+ return environment.launcher === "native" && environment.transport === "outside-tmux";
1870
+ }
1871
+ function buildNativeOutsideTmuxTeamPromptBlockState(classification, cwd, payload, sessionId, threadId, turnId) {
1872
+ const teamMode = readTeamModeConfig(cwd);
1873
+ const match = classification.matches.filter((entry) => teamMode.enabled || entry.skill !== "team")[0] ?? null;
1874
+ if (match?.skill !== "team")
1875
+ return null;
1876
+ if (!isNativeOutsideTmuxUserPrompt(cwd, payload, sessionId))
1775
1877
  return null;
1776
1878
  const nowIso = new Date().toISOString();
1777
1879
  return {
@@ -1803,13 +1905,19 @@ function buildAutopilotPromptActivationNote(skillState, options = {}) {
1803
1905
  const nativeSubagentSupport = resolveNativeSubagentSupportStatus({
1804
1906
  payload: options.payload,
1805
1907
  persistedSupportBlocker: readJsonSyncIfExists(nativeSubagentSupportBlockerPath(stateDir)),
1908
+ persistedRoleRoutingMarker: readRoleRoutingMarker(stateDir, {
1909
+ cwd: options.cwd,
1910
+ sessionId: options.sessionId ?? "",
1911
+ }),
1806
1912
  persistedCapacityBlocker: readJsonSyncIfExists(nativeSubagentCapacityBlockerPath(stateDir)),
1807
1913
  cwd: options.cwd,
1808
1914
  sessionId: options.sessionId,
1809
1915
  });
1810
1916
  const conductorGuidance = nativeSubagentSupport.status === "unsupported"
1811
1917
  ? buildUnsupportedNativeSubagentGuidance(nativeSubagentSupport)
1812
- : `${LEADER_CONDUCTOR_BLOCK} ${LEADER_CONDUCTOR_REUSE_AND_LEDGER_GUIDANCE}`;
1918
+ : nativeSubagentSupport.status === "role_routing_unavailable"
1919
+ ? buildRoleRoutingUnavailableGuidance(nativeSubagentSupport)
1920
+ : `${LEADER_CONDUCTOR_BLOCK} ${LEADER_CONDUCTOR_REUSE_AND_LEDGER_GUIDANCE}`;
1813
1921
  return [
1814
1922
  `Autopilot protocol: the durable default chain is $deep-interview -> $ralplan -> $ultragoal${teamHandoff} -> $code-review -> $ultraqa (deep-interview -> ralplan -> ultragoal -> code-review -> ultraqa).`,
1815
1923
  "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.",
@@ -1829,7 +1937,8 @@ function formatExecutionHandoffList(cwd) {
1829
1937
  ? "`$ultragoal`, `$team`, or `$ralph`"
1830
1938
  : "`$ultragoal` or `$ralph`";
1831
1939
  }
1832
- function buildAdditionalContextMessage(prompt, skillState, cwd = process.cwd(), payload) {
1940
+ function buildAdditionalContextMessage(classification, skillState, cwd = process.cwd(), payload) {
1941
+ const prompt = classification.originalText;
1833
1942
  if (!prompt)
1834
1943
  return null;
1835
1944
  const promptPriorityMessage = buildPromptPriorityMessage(prompt);
@@ -1837,20 +1946,29 @@ function buildAdditionalContextMessage(prompt, skillState, cwd = process.cwd(),
1837
1946
  return promptPriorityMessage;
1838
1947
  }
1839
1948
  const teamMode = readTeamModeConfig(cwd);
1840
- const matches = detectKeywords(prompt).filter((entry) => teamMode.enabled || entry.skill !== "team");
1949
+ const matches = classification.matches.filter((entry) => teamMode.enabled || entry.skill !== "team");
1841
1950
  const match = matches[0] ?? null;
1842
1951
  if (!match) {
1952
+ const markedQuestionAnswer = classification.reservedInput === "omx-question-answered";
1843
1953
  const continuedSkill = safeString(skillState?.skill).trim();
1844
- if (!continuedSkill)
1954
+ const eligibleMarkedContinuation = markedQuestionAnswer
1955
+ && skillState?.active === true
1956
+ && (continuedSkill === "autopilot" || continuedSkill === "deep-interview");
1957
+ const eligibleOrdinaryContinuation = classification.reservedInput === null
1958
+ && !classification.hasExplicitLikeInvocation
1959
+ && skillState?.active === true
1960
+ && Boolean(continuedSkill);
1961
+ if (!eligibleMarkedContinuation && !eligibleOrdinaryContinuation)
1845
1962
  return promptPriorityMessage;
1846
1963
  const deepInterviewPromptActivationNote = skillState?.initialized_mode === "deep-interview"
1847
1964
  ? buildDeepInterviewQuestionBridgeInstruction(cwd, payload)
1848
1965
  : null;
1849
1966
  const deepInterviewConfigPromptActivationNote = buildDeepInterviewConfigInstruction(cwd, skillState);
1850
- const markedQuestionAnswer = /^\s*\[omx question answered\]/i.test(prompt);
1851
1967
  const autopilotPromptActivationNote = buildAutopilotPromptActivationNote(skillState, { markedQuestionAnswer, cwd, payload, sessionId: safeString(skillState?.session_id).trim() });
1852
1968
  return [
1853
- `OMX native UserPromptSubmit continued active workflow skill "${continuedSkill}".`,
1969
+ markedQuestionAnswer
1970
+ ? `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.`
1971
+ : `OMX native UserPromptSubmit continued active workflow skill "${continuedSkill}".`,
1854
1972
  promptPriorityMessage,
1855
1973
  skillState?.initialized_mode && skillState.initialized_state_path
1856
1974
  ? buildSkillStateCliInstruction(skillState.initialized_mode, skillState.initialized_state_path)
@@ -1864,27 +1982,6 @@ function buildAdditionalContextMessage(prompt, skillState, cwd = process.cwd(),
1864
1982
  const detectedKeywordMessage = matches.length > 1
1865
1983
  ? `OMX native UserPromptSubmit detected workflow keywords ${matches.map((entry) => `"${entry.keyword}" -> ${entry.skill}`).join(", ")}.`
1866
1984
  : `OMX native UserPromptSubmit detected workflow keyword "${match.keyword}" -> ${match.skill}.`;
1867
- const continuedSkill = safeString(skillState?.skill).trim();
1868
- if (continuedSkill
1869
- && continuedSkill !== match.skill
1870
- && /^\s*\[omx question answered\]/i.test(prompt)) {
1871
- const deepInterviewPromptActivationNote = skillState?.initialized_mode === "deep-interview"
1872
- ? buildDeepInterviewQuestionBridgeInstruction(cwd, payload)
1873
- : null;
1874
- const deepInterviewConfigPromptActivationNote = buildDeepInterviewConfigInstruction(cwd, skillState);
1875
- const autopilotPromptActivationNote = buildAutopilotPromptActivationNote(skillState, { markedQuestionAnswer: true, cwd, payload, sessionId: safeString(skillState?.session_id).trim() });
1876
- return [
1877
- `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.`,
1878
- promptPriorityMessage,
1879
- skillState?.initialized_mode && skillState.initialized_state_path
1880
- ? buildSkillStateCliInstruction(skillState.initialized_mode, skillState.initialized_state_path)
1881
- : null,
1882
- deepInterviewPromptActivationNote,
1883
- deepInterviewConfigPromptActivationNote,
1884
- autopilotPromptActivationNote,
1885
- "Follow AGENTS.md routing and preserve workflow transition and planning-safety rules.",
1886
- ].filter(Boolean).join(" ");
1887
- }
1888
1985
  const activeSkills = Array.isArray(skillState?.active_skills)
1889
1986
  ? skillState.active_skills.map((entry) => entry.skill)
1890
1987
  : [];
@@ -2252,8 +2349,6 @@ async function buildModeBasedStopOutput(mode, cwd, sessionId) {
2252
2349
  reason: `OMX ${mode} is still active (phase: ${phase}; ${diagnostic}); continue the task and gather fresh verification evidence before stopping.`,
2253
2350
  stopReason: `${mode}_${phase}`,
2254
2351
  systemMessage,
2255
- statePath,
2256
- canonicalDisagreement,
2257
2352
  };
2258
2353
  }
2259
2354
  export function looksLikeGoalCompletionPrompt(text) {
@@ -2629,31 +2724,66 @@ function readPayloadAgentRole(payload) {
2629
2724
  ?? threadSpawn?.agent_type
2630
2725
  ?? threadSpawn?.agentType).trim().toLowerCase();
2631
2726
  }
2632
- function readInstalledTypedAgentRoleNames() {
2633
- const cacheKey = [codexAgentsDir(), projectCodexAgentsDir()].join("|");
2634
- if (installedTypedAgentRoleNamesCacheKey === cacheKey)
2635
- return installedTypedAgentRoleNamesCache;
2636
- const installedRoleNames = new Set();
2637
- for (const agentsDir of [codexAgentsDir(), projectCodexAgentsDir()]) {
2638
- try {
2639
- for (const entry of readdirSync(agentsDir, { withFileTypes: true })) {
2640
- if (!entry.isFile() || !entry.name.endsWith(".toml"))
2641
- continue;
2642
- installedRoleNames.add(entry.name.slice(0, -5).trim().toLowerCase());
2643
- }
2644
- }
2645
- catch {
2646
- // Ignore missing or unreadable agent directories; built-in definitions remain authoritative.
2647
- }
2648
- }
2649
- installedTypedAgentRoleNamesCacheKey = cacheKey;
2650
- installedTypedAgentRoleNamesCache = installedRoleNames;
2651
- return installedTypedAgentRoleNamesCache;
2727
+ function readRequestedSpawnRole(payload) {
2728
+ const toolName = safeString(payload.tool_name).trim();
2729
+ if (!isNativeSubagentSpawnToolName(toolName))
2730
+ return "";
2731
+ const toolInput = safeObject(payload.tool_input);
2732
+ return safeString(toolInput.agent_role
2733
+ ?? toolInput.agentRole
2734
+ ?? toolInput.agent_type
2735
+ ?? toolInput.agentType).trim().toLowerCase();
2652
2736
  }
2653
2737
  function isTypedAgentRolePayload(payload) {
2654
2738
  const agentRole = readPayloadAgentRole(payload);
2655
- return agentRole !== "" && (KNOWN_TYPED_AGENT_ROLES.has(agentRole)
2656
- || readInstalledTypedAgentRoleNames().has(agentRole));
2739
+ return agentRole !== "" && resolveInstalledRoleName(agentRole) !== null;
2740
+ }
2741
+ // #3181: detect a runtime-set native subagent PreToolUse. The runtime sets
2742
+ // source.subagent.thread_spawn (not agent-controlled tool arguments), so the mere
2743
+ // STRUCTURAL PRESENCE of that carrier — even if its parent id is blank, malformed, or
2744
+ // null — marks a child/subagent turn that must never be attested as the session leader.
2745
+ // This is a negative security decision, so it fails closed on any present carrier rather
2746
+ // than requiring a well-formed parent id.
2747
+ function hasSubagentThreadSpawnProvenance(payload) {
2748
+ const source = payload.source;
2749
+ if (!source || typeof source !== "object")
2750
+ return false;
2751
+ const subagent = source.subagent;
2752
+ if (!subagent || typeof subagent !== "object")
2753
+ return false;
2754
+ return Object.prototype.hasOwnProperty.call(subagent, "thread_spawn");
2755
+ }
2756
+ // #3181: positive counter-evidence guard. A thread durably tracked as a subagent in ANY
2757
+ // session must never be attested as a leader, even if the current payload lacks typed-role
2758
+ // or thread_spawn provenance. This closes the source-less/untyped child escalation where a
2759
+ // child recorded at its SessionStart later self-promotes via a source-less PreToolUse.
2760
+ async function isThreadTrackedAsSubagent(cwd, threadId) {
2761
+ const id = threadId.trim();
2762
+ if (!id)
2763
+ return false;
2764
+ // Strict read: an unreadable/corrupt tracker fails closed (treated as "is a subagent")
2765
+ // so corrupt evidence cannot let a source-less child reconcile/attest as leader.
2766
+ const read = await readSubagentTrackingStateStrict(cwd);
2767
+ if (!read.ok)
2768
+ return true;
2769
+ for (const session of Object.values(read.state.sessions)) {
2770
+ if (session.threads?.[id]?.kind === "subagent")
2771
+ return true;
2772
+ }
2773
+ return false;
2774
+ }
2775
+ function buildNativeUnknownRolePreToolUseOutput(payload) {
2776
+ const requestedRole = readRequestedSpawnRole(payload);
2777
+ if (!requestedRole || resolveInstalledRoleName(requestedRole) !== null)
2778
+ return null;
2779
+ return {
2780
+ decision: "block",
2781
+ reason: "Native typed-subagent dispatch denied: supplied agent_type/agent_role is unknown or not installed.",
2782
+ hookSpecificOutput: {
2783
+ hookEventName: "PreToolUse",
2784
+ additionalContext: "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.",
2785
+ },
2786
+ };
2657
2787
  }
2658
2788
  function readPayloadTurnId(payload) {
2659
2789
  return safeString(payload.turn_id ?? payload.turnId).trim();
@@ -2738,6 +2868,21 @@ async function recordNativeSubagentSupportBlocker(cwd, stateDir, payload) {
2738
2868
  cwd,
2739
2869
  }, null, 2));
2740
2870
  }
2871
+ function refreshNativeSubagentRoleRoutingMarker(cwd, stateDir, sessionId, parentThreadId) {
2872
+ const marker = readRoleRoutingMarker(stateDir, {
2873
+ cwd,
2874
+ sessionId,
2875
+ ...(parentThreadId ? { parentThreadId } : {}),
2876
+ });
2877
+ if (!marker)
2878
+ return;
2879
+ const nowMs = Date.now();
2880
+ writeRoleRoutingMarker(stateDir, {
2881
+ ...marker,
2882
+ observed_at: new Date(nowMs).toISOString(),
2883
+ expires_at: new Date(nowMs + NATIVE_SUBAGENT_ROLE_ROUTING_MARKER_TTL_MS).toISOString(),
2884
+ });
2885
+ }
2741
2886
  function summarizeCapacityFailure(text) {
2742
2887
  const normalized = text.replace(/\s+/g, " ").trim();
2743
2888
  if (!normalized)
@@ -2826,13 +2971,13 @@ async function buildNativeSubagentCapacityCloseGuardOutput(payload, cwd, stateDi
2826
2971
  },
2827
2972
  };
2828
2973
  }
2829
- async function resolveInternalSessionIdForPayload(cwd, payloadSessionId, stateDir) {
2830
- const currentSession = stateDir
2974
+ async function resolveInternalSessionIdForPayload(cwd, payloadSessionId, stateDir, pointerSessionState, allowUnboundPayloadFallback = true) {
2975
+ const currentSession = pointerSessionState ?? (stateDir
2831
2976
  ? await readUsableSessionStateFromStateDir(cwd, stateDir)
2832
- : await readUsableSessionState(cwd);
2977
+ : await readUsableSessionState(cwd));
2833
2978
  const canonicalSessionId = safeString(currentSession?.session_id).trim();
2834
2979
  if (!canonicalSessionId)
2835
- return payloadSessionId;
2980
+ return allowUnboundPayloadFallback ? payloadSessionId : "";
2836
2981
  const nativeSessionId = safeString(currentSession?.native_session_id).trim();
2837
2982
  const ownerOmxSessionId = safeString(currentSession?.owner_omx_session_id).trim();
2838
2983
  if (!payloadSessionId)
@@ -2843,7 +2988,7 @@ async function resolveInternalSessionIdForPayload(cwd, payloadSessionId, stateDi
2843
2988
  return canonicalSessionId;
2844
2989
  if (ownerOmxSessionId && payloadSessionId === ownerOmxSessionId)
2845
2990
  return canonicalSessionId;
2846
- return payloadSessionId;
2991
+ return allowUnboundPayloadFallback ? payloadSessionId : "";
2847
2992
  }
2848
2993
  async function readRootSessionStateFromStateDir(stateDir) {
2849
2994
  const sessionPath = join(stateDir, "session.json");
@@ -2947,17 +3092,6 @@ const RALPLAN_EXECUTION_HANDOFF_SKILLS = new Set([
2947
3092
  "ultrawork",
2948
3093
  "ultraqa",
2949
3094
  ]);
2950
- function isActiveDeepInterviewPhase(state) {
2951
- if (!state || state.active !== true)
2952
- return false;
2953
- const mode = safeString(state.mode).trim();
2954
- if (mode && mode !== "deep-interview")
2955
- return false;
2956
- const phase = safeString(state.current_phase ?? state.currentPhase).trim().toLowerCase();
2957
- if (phase && (TERMINAL_MODE_PHASES.has(phase) || phase === "completing"))
2958
- return false;
2959
- return true;
2960
- }
2961
3095
  function isInactiveCompletedDeepInterviewPhase(state) {
2962
3096
  if (!state || state.active !== false)
2963
3097
  return false;
@@ -3065,8 +3199,13 @@ function isAllowedPlanningArtifactPath(cwd, rawPath, allowedPrefixes) {
3065
3199
  function isAllowedDeepInterviewArtifactPath(cwd, rawPath) {
3066
3200
  return isAllowedPlanningArtifactPath(cwd, rawPath, DEEP_INTERVIEW_ALLOWED_WRITE_PREFIXES);
3067
3201
  }
3202
+ function isAllowedRalplanDraftPath(cwd, rawPath) {
3203
+ const relativePath = normalizePlanningArtifactRelativePath(cwd, rawPath);
3204
+ return relativePath !== null && /^\.omx\/drafts\/[^/]+\.md$/.test(relativePath);
3205
+ }
3068
3206
  function isAllowedRalplanArtifactPath(cwd, rawPath) {
3069
- return isAllowedPlanningArtifactPath(cwd, rawPath, RALPLAN_ALLOWED_WRITE_PREFIXES);
3207
+ return isAllowedRalplanDraftPath(cwd, rawPath)
3208
+ || isAllowedPlanningArtifactPath(cwd, rawPath, RALPLAN_ALLOWED_WRITE_PREFIXES);
3070
3209
  }
3071
3210
  function shellTokenizeLiteralCommand(command) {
3072
3211
  const tokens = [];
@@ -3274,9 +3413,73 @@ function resolveCommandRedirectTarget(target, assignments) {
3274
3413
  const resolved = assignments.get(safeString(variableMatch[1]));
3275
3414
  return resolved !== undefined ? resolved : target;
3276
3415
  }
3416
+ // Masks redirect metacharacters (`<`/`>`) that appear INSIDE shell quotes so a
3417
+ // quoted regex/source value (e.g. `gh issue create --body '...>{1,2}...'` or
3418
+ // `omx state write --input '{"reason":"a>b"}'`) is not misread as a redirect
3419
+ // write target. Escaped quotes at the top level (`\'`, `\"`) are literal
3420
+ // characters and must NOT open a span, and `$'...'` ANSI-C quoting processes
3421
+ // backslash escapes (so `\'` does not close it) — otherwise a genuine `>`
3422
+ // redirect could be masked behind a false span and its write missed. Only
3423
+ // characters inside a real quoted span are masked; unquoted redirect operators
3424
+ // survive intact. Unterminated/ambiguous quoting fails closed: the original
3425
+ // command is returned unmasked so genuine redirects stay visible to the scan.
3426
+ function maskQuotedRedirectMetacharsForCommandScan(command) {
3427
+ let masked = "";
3428
+ // null = unquoted; "'" = single quotes (no escapes); '"' = double quotes
3429
+ // (backslash escapes); "$'" = ANSI-C $'...' (backslash escapes, incl. \').
3430
+ let quote = null;
3431
+ for (let index = 0; index < command.length; index += 1) {
3432
+ const char = command[index] ?? "";
3433
+ if (quote === null) {
3434
+ // A backslash escapes the next character at the top level, so an escaped
3435
+ // quote is a literal char and must not open a quoted span.
3436
+ if (char === "\\") {
3437
+ masked += char;
3438
+ const next = command[index + 1];
3439
+ if (next !== undefined) {
3440
+ masked += next;
3441
+ index += 1;
3442
+ }
3443
+ continue;
3444
+ }
3445
+ if (char === "$" && command[index + 1] === "'") {
3446
+ quote = "$'";
3447
+ masked += "$'";
3448
+ index += 1;
3449
+ continue;
3450
+ }
3451
+ if (char === "'" || char === "\"")
3452
+ quote = char;
3453
+ masked += char;
3454
+ continue;
3455
+ }
3456
+ if ((quote === "\"" || quote === "$'") && char === "\\") {
3457
+ // Escapes inside double/ANSI-C spans keep the backslash and its escaped
3458
+ // char (masking a redirect metachar so quoted data cannot be a false
3459
+ // target); the escaped char never closes the span.
3460
+ masked += char;
3461
+ const next = command[index + 1];
3462
+ if (next !== undefined) {
3463
+ masked += next === "<" || next === ">" ? "_" : next;
3464
+ index += 1;
3465
+ }
3466
+ continue;
3467
+ }
3468
+ const closesSpan = quote === "$'" ? char === "'" : char === quote;
3469
+ if (closesSpan) {
3470
+ quote = null;
3471
+ masked += char;
3472
+ continue;
3473
+ }
3474
+ masked += char === "<" || char === ">" ? "_" : char;
3475
+ }
3476
+ if (quote !== null)
3477
+ return command;
3478
+ return masked;
3479
+ }
3277
3480
  function extractDeepInterviewCommandRedirectTargets(command) {
3278
3481
  const targets = [];
3279
- const commandOutsideHeredocBodies = stripHeredocBodiesForCommandScan(command);
3482
+ const commandOutsideHeredocBodies = maskQuotedRedirectMetacharsForCommandScan(stripHeredocBodiesForCommandScan(command));
3280
3483
  for (const match of commandOutsideHeredocBodies.matchAll(/(?:^|[^>])>{1,2}\s*(["']?)([^\s&|;<>]+)\1/g)) {
3281
3484
  const candidate = safeString(match[2]).trim();
3282
3485
  if (candidate && !isNullDeviceRedirectTarget(candidate))
@@ -3388,7 +3591,7 @@ function findGitSubcommandIndex(words, startIndex) {
3388
3591
  }
3389
3592
  return null;
3390
3593
  }
3391
- function commandHasDeepInterviewWriteIntent(command) {
3594
+ function commandHasDeepInterviewWriteIntent(command, depth = 0) {
3392
3595
  return commandInvokesApplyPatch(command)
3393
3596
  || extractDeepInterviewCommandRedirectTargets(command).length > 0
3394
3597
  || /\btee\s+(?:-a\s+)?[^\s&|;]+/.test(command)
@@ -3399,7 +3602,17 @@ function commandHasDeepInterviewWriteIntent(command) {
3399
3602
  || extractConductorBashMutations(command).length > 0
3400
3603
  || extractConductorInterpreterWrites(command).length > 0
3401
3604
  || commandHasDestructiveGitSubcommand(command)
3402
- || commandHasPackageInstallIntent(command);
3605
+ || commandHasPackageInstallIntent(command)
3606
+ // Recurse into wrapped shells (`bash -lc "cat > f"`, `eval`, `env`) and
3607
+ // command substitutions so a real redirect INSIDE a nested command string
3608
+ // is still classified as write intent. This is required because quoted
3609
+ // redirect metacharacters are now masked at the outer scan (#3119): the
3610
+ // mask removes false positives from quoted DATA values while nested-command
3611
+ // recursion re-detects genuine redirects, preserving fail-closed blocking.
3612
+ // The depth guard mirrors evaluateConductorBashWrite's nesting bound;
3613
+ // extractors return strict substrings, so recursion always terminates.
3614
+ || (depth < CONDUCTOR_BASH_MAX_NESTING_DEPTH && (extractNestedShellCommandStringsForStateScan(command).some((nested) => commandHasDeepInterviewWriteIntent(nested, depth + 1))
3615
+ || extractNestedCommandSubstitutionStringsForStateScan(command).some((nested) => commandHasDeepInterviewWriteIntent(nested, depth + 1))));
3403
3616
  }
3404
3617
  function extractDeepInterviewCommandWriteTargets(command) {
3405
3618
  const assignments = extractCommandLiteralAssignments(command);
@@ -6173,6 +6386,121 @@ function isCompleteRalplanTerminalWritePayload(payload, activeState, sessionId)
6173
6386
  return false;
6174
6387
  return true;
6175
6388
  }
6389
+ function hasUnquotedShellControlOrRedirection(command) {
6390
+ let quote = null;
6391
+ for (let index = 0; index < command.length; index += 1) {
6392
+ const char = command[index] ?? "";
6393
+ if (char === "\\" && quote !== "'") {
6394
+ index += 1;
6395
+ continue;
6396
+ }
6397
+ if (char === "'" || char === '"') {
6398
+ quote = quote === char ? null : quote ?? char;
6399
+ continue;
6400
+ }
6401
+ if (!quote && ";&|()<>\n\r".includes(char))
6402
+ return true;
6403
+ }
6404
+ return false;
6405
+ }
6406
+ function suppliedSessionAliasesMatch(payload, sessionId) {
6407
+ return [
6408
+ payload.session_id,
6409
+ payload.owner_omx_session_id,
6410
+ payload.codex_session_id,
6411
+ payload.owner_codex_session_id,
6412
+ ].filter((value) => value !== undefined)
6413
+ .every((value) => safeString(value).trim() === sessionId);
6414
+ }
6415
+ function hasOnlyFinishedExplicitOutcomes(payload) {
6416
+ const values = [
6417
+ payload.lifecycle_outcome,
6418
+ payload.lifecycleOutcome,
6419
+ payload.terminal_outcome,
6420
+ payload.terminalOutcome,
6421
+ payload.run_outcome,
6422
+ payload.runOutcome,
6423
+ ].filter((value) => value !== undefined);
6424
+ return values.every((value) => inferTerminalLifecycleOutcome({ lifecycle_outcome: value }, { includeQuestionEnforcement: false }) === "finished");
6425
+ }
6426
+ function isCompleteDeepInterviewTerminalWritePayload(payload, activeState, sessionId) {
6427
+ if (!sessionId)
6428
+ return false;
6429
+ const activeMode = safeString(activeState.mode).trim().toLowerCase();
6430
+ if (activeMode && activeMode !== "deep-interview")
6431
+ return false;
6432
+ const activeSessionId = safeString(activeState.session_id
6433
+ ?? activeState.owner_omx_session_id
6434
+ ?? activeState.codex_session_id
6435
+ ?? activeState.owner_codex_session_id).trim();
6436
+ if (activeSessionId && activeSessionId !== sessionId)
6437
+ return false;
6438
+ const mode = safeString(payload.mode).trim().toLowerCase();
6439
+ const phase = safeString(payload.current_phase ?? payload.currentPhase).trim().toLowerCase();
6440
+ const payloadSessionId = safeString(payload.session_id).trim();
6441
+ if (!hasOnlyFinishedExplicitOutcomes(payload))
6442
+ return false;
6443
+ return mode === "deep-interview"
6444
+ && payload.active === false
6445
+ && phase === "complete"
6446
+ && payloadSessionId === sessionId
6447
+ && inferTerminalLifecycleOutcome(payload, { includeQuestionEnforcement: false }) === "finished";
6448
+ }
6449
+ function isAllowedDeepInterviewTerminalStateWriteCommand(cwd, command, activeState, sessionId) {
6450
+ const rawWords = tokenizeShellWords(normalizeShellLineContinuations(command).trim());
6451
+ if (rawWords[0] !== "omx")
6452
+ return false;
6453
+ const canonicalCommand = canonicalizeOmxStateTransportCommand(command);
6454
+ if (hasUnquotedShellControlOrRedirection(command))
6455
+ return false;
6456
+ if (hasUnsafeUnquotedHeredocExpansion(canonicalCommand))
6457
+ return false;
6458
+ if (hasUnquotedShellSubstitution(canonicalCommand))
6459
+ return false;
6460
+ if (splitStateScanSegments(canonicalCommand).length !== 1)
6461
+ return false;
6462
+ if (sourcesFileWrittenEarlierInSameCommand(cwd, canonicalCommand))
6463
+ return false;
6464
+ if (findUnquotedOmxStateCommandIndexes(canonicalCommand, "clear").length > 0)
6465
+ return false;
6466
+ if (hasDynamicNestedShellExecution(canonicalCommand))
6467
+ return false;
6468
+ if (extractDeepInterviewCommandWriteTargets(command).length > 0)
6469
+ return false;
6470
+ const operations = collectOmxStateCommandOperations(command, "write");
6471
+ if (operations.length !== 1)
6472
+ return false;
6473
+ const operation = operations[0];
6474
+ if (!operation || operation.nested || operation.commandPrefix.trim() || hasPriorExecutableCommand(operation.prefix))
6475
+ return false;
6476
+ const inlineInputCount = operation.args.filter((arg) => arg === "--input" || arg.startsWith("--input=")).length;
6477
+ if (inlineInputCount !== 1 || operation.args.some((arg) => arg === "--input-file" || arg.startsWith("--input-file=")))
6478
+ return false;
6479
+ const inlineInput = readStateWriteFlagValue(operation.args, "--input");
6480
+ let rawPayload = null;
6481
+ try {
6482
+ rawPayload = inlineInput ? safeObject(JSON.parse(inlineInput)) : null;
6483
+ }
6484
+ catch {
6485
+ rawPayload = null;
6486
+ }
6487
+ const nestedState = safeObject(rawPayload?.state);
6488
+ if (!rawPayload
6489
+ || "workingDirectory" in rawPayload
6490
+ || !suppliedSessionAliasesMatch(rawPayload, sessionId)
6491
+ || !hasOnlyFinishedExplicitOutcomes(rawPayload)
6492
+ || (nestedState && ("mode" in nestedState
6493
+ || "session_id" in nestedState
6494
+ || "workingDirectory" in nestedState
6495
+ || "active" in nestedState
6496
+ || "current_phase" in nestedState
6497
+ || "currentPhase" in nestedState
6498
+ || !suppliedSessionAliasesMatch(nestedState, sessionId)
6499
+ || !hasOnlyFinishedExplicitOutcomes(nestedState))))
6500
+ return false;
6501
+ const payload = readStateWriteInputPayload(cwd, canonicalCommand, command);
6502
+ return payload ? isCompleteDeepInterviewTerminalWritePayload(payload, activeState, sessionId) : false;
6503
+ }
6176
6504
  function isAllowedRalplanTerminalStateWriteCommand(cwd, command, activeState, sessionId) {
6177
6505
  const canonicalCommand = canonicalizeOmxStateTransportCommand(command);
6178
6506
  if (hasUnsafeUnquotedHeredocExpansion(canonicalCommand))
@@ -6218,13 +6546,56 @@ function commandEndsPlanningPhase(cwd, command) {
6218
6546
  const payload = readStateWriteInputPayload(cwd, canonicalCommand, command);
6219
6547
  return payload ? isPlanningPhaseDeactivationPayload(payload) : true;
6220
6548
  }
6221
- function isAllowedDeepInterviewBashWrite(cwd, command) {
6549
+ function isAllowedDeepInterviewBashWrite(cwd, command, activeState, sessionId = "") {
6550
+ const stateWriteOperations = collectOmxStateCommandOperations(command, "write");
6551
+ const hasUnsafeRuntimeStateWrite = (words) => {
6552
+ const stateWordIndex = words.indexOf("state");
6553
+ const writeWordIndex = stateWordIndex >= 0 ? words.indexOf("write", stateWordIndex + 1) : -1;
6554
+ if (writeWordIndex <= stateWordIndex)
6555
+ return false;
6556
+ const hasRuntimeBeforeState = words.slice(0, stateWordIndex).some((word) => {
6557
+ const base = shellWordBaseName(word);
6558
+ return isNodeInterpreterCommandWord(base) || base === "bun" || base === "tsx";
6559
+ });
6560
+ if (!hasRuntimeBeforeState)
6561
+ return false;
6562
+ const inlineInput = readStateWriteFlagValue(words, "--input");
6563
+ let payload = null;
6564
+ try {
6565
+ const parsed = inlineInput ? safeObject(JSON.parse(inlineInput)) : null;
6566
+ const modeOverride = readStateWriteFlagValue(words, "--mode");
6567
+ payload = parsed
6568
+ ? normalizeStateWriteClassificationPayload(modeOverride ? { ...parsed, mode: modeOverride } : parsed)
6569
+ : null;
6570
+ }
6571
+ catch {
6572
+ payload = null;
6573
+ }
6574
+ return (!payload && stateWriteOperations.length === 0)
6575
+ || Boolean(payload && (safeString(payload.mode).trim().toLowerCase() !== "deep-interview"
6576
+ || isPlanningPhaseDeactivationPayload(payload)));
6577
+ };
6578
+ const rawCommand = normalizeShellLineContinuations(command).trim();
6579
+ if ([rawCommand, canonicalizeOmxStateTransportCommand(rawCommand)]
6580
+ .some((candidate) => hasUnsafeRuntimeStateWrite(tokenizeShellWords(candidate))))
6581
+ return false;
6582
+ if (stateWriteOperations.some((operation) => operation.nested))
6583
+ return false;
6222
6584
  if (isAllowedDeepInterviewRalplanHandoffCommand(cwd, command))
6223
6585
  return true;
6224
6586
  if (hasDeepInterviewRalplanHandoffStateMutation(cwd, command))
6225
6587
  return false;
6226
- if (commandEndsPlanningPhase(cwd, command))
6227
- return false;
6588
+ if (commandEndsPlanningPhase(cwd, command)) {
6589
+ return activeState
6590
+ ? isAllowedDeepInterviewTerminalStateWriteCommand(cwd, command, activeState, sessionId)
6591
+ : false;
6592
+ }
6593
+ if (stateWriteOperations.length > 0) {
6594
+ const canonicalCommand = canonicalizeOmxStateTransportCommand(command);
6595
+ const payload = readStateWriteInputPayload(cwd, canonicalCommand, command);
6596
+ if (!payload || safeString(payload.mode).trim().toLowerCase() !== "deep-interview")
6597
+ return false;
6598
+ }
6228
6599
  if (commandHasUntargetedPlanningForbiddenIntent(command))
6229
6600
  return false;
6230
6601
  if (firstPlanningTmpScriptExecutionTarget(cwd, command))
@@ -6247,12 +6618,10 @@ async function readActiveDeepInterviewStateForPreToolUse(cwd, stateDir, sessionI
6247
6618
  const modeState = sessionId
6248
6619
  ? await readStopSessionPinnedState("deep-interview-state.json", cwd, sessionId, stateDir)
6249
6620
  : await readJsonIfExists(join(stateDir, "deep-interview-state.json"));
6250
- if (isActiveDeepInterviewPhase(modeState) && modeState && modeStateMatchesSkillStopContext(modeState, cwd, sessionId)) {
6251
- const hasActiveDeepInterviewSkill = listActiveSkills(canonicalState).some((entry) => (entry.skill === "deep-interview"
6252
- && matchesSkillStopContext(entry, canonicalState, sessionId, threadId)));
6253
- if (hasActiveDeepInterviewSkill)
6254
- return modeState;
6255
- }
6621
+ const hasActiveDeepInterviewSkill = listActiveSkills(canonicalState).some((entry) => (entry.skill === "deep-interview"
6622
+ && matchesSkillStopContext(entry, canonicalState, sessionId, threadId)));
6623
+ if (hasActiveDeepInterviewSkill && modeState?.active === true)
6624
+ return modeState;
6256
6625
  if (isInactiveCompletedDeepInterviewPhase(modeState))
6257
6626
  return null;
6258
6627
  const autopilotState = sessionId
@@ -6399,7 +6768,7 @@ function buildDeepInterviewBashBlockedDetail(cwd, command) {
6399
6768
  }
6400
6769
  async function buildRalplanPreToolUseBoundaryOutput(payload, cwd, stateDir, resolvedSessionId) {
6401
6770
  const sessionId = safeString(resolvedSessionId ?? readPayloadSessionId(payload)).trim();
6402
- if (await hasTrustedTypedSubagentProvenanceForPreToolUse(payload, cwd, sessionId))
6771
+ if (await hasTrustedTypedSubagentProvenanceForPreToolUse(payload, cwd, stateDir, sessionId))
6403
6772
  return null;
6404
6773
  const threadId = readPayloadThreadId(payload);
6405
6774
  const activeState = await readActiveRalplanStateForPreToolUse(cwd, stateDir, sessionId, threadId);
@@ -6450,7 +6819,7 @@ async function buildRalplanPreToolUseBoundaryOutput(payload, cwd, stateDir, reso
6450
6819
  hookSpecificOutput: {
6451
6820
  hookEventName: "PreToolUse",
6452
6821
  additionalContext: `${planningModeDescription}. `
6453
- + "Write only planning artifacts under `.omx/context/`, `.omx/plans/`, `.omx/specs/`, `.omx/tmp/`, required `.omx/state/` files, or tracker metadata under `.beads/`. "
6822
+ + "Write only planning artifacts under `.omx/context/`, `.omx/plans/`, `.omx/specs/`, `.omx/tmp/`, required `.omx/state/` files, Markdown drafts under `.omx/drafts/*.md`, or tracker metadata under `.beads/`. "
6454
6823
  + "Do not edit implementation files or run implementation-focused writes from planning phases. "
6455
6824
  + `To execute, first process an explicit handoff such as ${formatExecutionHandoffList(cwd)}, which must emit terminal planning state before implementation begins.`,
6456
6825
  },
@@ -6458,7 +6827,7 @@ async function buildRalplanPreToolUseBoundaryOutput(payload, cwd, stateDir, reso
6458
6827
  }
6459
6828
  async function buildDeepInterviewPreToolUseBoundaryOutput(payload, cwd, stateDir, resolvedSessionId) {
6460
6829
  const sessionId = safeString(resolvedSessionId ?? readPayloadSessionId(payload)).trim();
6461
- if (await hasTrustedTypedSubagentProvenanceForPreToolUse(payload, cwd, sessionId))
6830
+ if (await hasTrustedTypedSubagentProvenanceForPreToolUse(payload, cwd, stateDir, sessionId))
6462
6831
  return null;
6463
6832
  const threadId = readPayloadThreadId(payload);
6464
6833
  const activeState = await readActiveDeepInterviewStateForPreToolUse(cwd, stateDir, sessionId, threadId);
@@ -6470,7 +6839,7 @@ async function buildDeepInterviewPreToolUseBoundaryOutput(payload, cwd, stateDir
6470
6839
  let blocked = false;
6471
6840
  let blockedDetail = "implementation/write tools are blocked until an explicit handoff workflow is activated";
6472
6841
  if (toolName === "Bash") {
6473
- blocked = !isAllowedDeepInterviewBashWrite(cwd, command);
6842
+ blocked = !isAllowedDeepInterviewBashWrite(cwd, command, activeState, sessionId);
6474
6843
  if (blocked) {
6475
6844
  blockedDetail = buildDeepInterviewBashBlockedDetail(cwd, command);
6476
6845
  }
@@ -6576,18 +6945,71 @@ async function buildPlanningRootPointerConflictPreToolUseOutput(payload, cwd, st
6576
6945
  }
6577
6946
  return blocked ? buildRalplanRootPointerConflictBlock(ralplanState) : null;
6578
6947
  }
6579
- async function hasTrustedTypedSubagentProvenanceForPreToolUse(payload, cwd, sessionId) {
6948
+ async function hasTrustedTypedSubagentProvenanceForPreToolUse(payload, cwd, stateDir, sessionId, options = {}) {
6580
6949
  if (hasTeamWorkerEnvironment())
6581
6950
  return true;
6582
- if (!isTypedAgentRolePayload(payload))
6583
- return false;
6584
6951
  const trackingState = await readSubagentTrackingState(cwd).catch(() => null);
6585
6952
  const session = trackingState?.sessions?.[sessionId];
6586
6953
  if (!session)
6587
6954
  return false;
6588
6955
  const payloadThreadId = readPayloadThreadId(payload);
6956
+ const trackedPayloadThread = payloadThreadId ? session.threads[payloadThreadId] : undefined;
6957
+ const hasTrackedAdaptedTypedRole = payloadThreadId !== ""
6958
+ && isTrustedSubagentThread(session, payloadThreadId)
6959
+ && trackedPayloadThread?.provenance_kind === OMX_ADAPTED_PROVENANCE
6960
+ && resolveInstalledRoleName(trackedPayloadThread.role ?? "") !== null;
6961
+ // Resolve the Main-root leader THREAD identity from the tracker's leader_thread_id
6962
+ // plus the canonical session's native_session_id (the leader's native thread). Only
6963
+ // genuine leader-thread identifiers may anchor the leader: owner_*_session_id are
6964
+ // session-level ids, not thread anchors, so they must NOT populate this set — their
6965
+ // mere presence would make it non-empty and suppress the fail-closed guard below
6966
+ // without ever excluding the leader thread (#3117 P4). The native_session_id anchor is
6967
+ // honored only when the root session pointer provably maps to the sessionId under
6968
+ // evaluation, so a stale/foreign session.json cannot supply another session's leader
6969
+ // anchor (#3117 P3); the tracker alone is insufficient because it can omit
6970
+ // leader_thread_id or corruptly label the leader kind:"subagent" (#3117 P2).
6971
+ const sessionState = await readRootSessionStateFromStateDir(stateDir).catch(() => null);
6972
+ const trackerLeaderThreadId = safeString(session.leader_thread_id).trim();
6973
+ const leaderIdentityAnchors = new Set();
6974
+ if (trackerLeaderThreadId)
6975
+ leaderIdentityAnchors.add(trackerLeaderThreadId);
6976
+ if (sessionState && sessionId && payloadMatchesSessionPointer(sessionId, sessionState)) {
6977
+ const nativeSessionId = safeString(sessionState.native_session_id).trim();
6978
+ if (nativeSessionId)
6979
+ leaderIdentityAnchors.add(nativeSessionId);
6980
+ }
6981
+ // Leader self-guard: the Main-root Conductor is never a delegated executor. Block it
6982
+ // ahead of every trust path, even when tracker or payload provenance is (possibly
6983
+ // corruptly) attached to the leader thread.
6984
+ if (payloadThreadId && leaderIdentityAnchors.has(payloadThreadId))
6985
+ return false;
6986
+ // Fail closed: without an authoritative leader anchor we cannot affirm the payload is
6987
+ // a non-leader delegated performer rather than a mislabeled leader, so we refuse trust
6988
+ // instead of inferring it from a corrupt tracker kind:"subagent" alone (#3117 P2).
6989
+ if (leaderIdentityAnchors.size === 0)
6990
+ return false;
6991
+ // Planning boundary guards (ralplan, deep-interview) still require a recognized typed
6992
+ // agent role, so an untyped collaboration.spawn_agent child cannot write before an
6993
+ // execution handoff/approval. A tracker-recorded OMX-adapted child is the equivalent
6994
+ // of a typed role only when its own persisted role is installed and validated.
6995
+ if (options.allowUntypedProvenance !== true
6996
+ && !isTypedAgentRolePayload(payload)
6997
+ && !hasTrackedAdaptedTypedRole) {
6998
+ return false;
6999
+ }
7000
+ // Tracker-backed provenance: the payload's own thread is a recorded, non-leader
7001
+ // subagent for this session — the non-spoofable anchor that lets native
7002
+ // collaboration.spawn_agent children/descendants edit under the Conductor guard even
7003
+ // without a recognized typed role (#3116). subagent-tracking.json is derived from
7004
+ // child SessionStart transcript metadata, not the live tool-call payload.
6589
7005
  if (payloadThreadId && isTrustedSubagentThread(session, payloadThreadId))
6590
7006
  return true;
7007
+ // Runtime-attached spawn provenance: trust a genuine spawned subagent turn whose
7008
+ // declared parent maps to this session's leader or a tracked thread. parentThreadId
7009
+ // comes from the runtime-set source.subagent.thread_spawn (not agent-controlled tool
7010
+ // arguments); an absent parent is rejected, the leader self-guard above blocks the
7011
+ // main root, and cross-session parents fail because they are not in this session's
7012
+ // tracked threads (#3116).
6591
7013
  const source = safeObject(payload.source);
6592
7014
  const subagent = safeObject(source.subagent);
6593
7015
  const threadSpawn = safeObject(subagent.thread_spawn);
@@ -6597,10 +7019,7 @@ async function hasTrustedTypedSubagentProvenanceForPreToolUse(payload, cwd, sess
6597
7019
  ?? threadSpawn.leaderThreadId).trim();
6598
7020
  if (!parentThreadId)
6599
7021
  return false;
6600
- const leaderThreadId = safeString(session.leader_thread_id).trim();
6601
- if (payloadThreadId && leaderThreadId && payloadThreadId === leaderThreadId)
6602
- return false;
6603
- return leaderThreadId === parentThreadId || parentThreadId in session.threads;
7022
+ return leaderIdentityAnchors.has(parentThreadId) || parentThreadId in session.threads;
6604
7023
  }
6605
7024
  function isActiveConductorModeState(state, mode, sessionId) {
6606
7025
  if (!state || state.active !== true)
@@ -6627,7 +7046,7 @@ async function readActiveMainRootConductorStateForPreToolUse(payload, cwd, state
6627
7046
  const threadId = readPayloadThreadId(payload);
6628
7047
  if (!sessionId)
6629
7048
  return null;
6630
- if (await hasTrustedTypedSubagentProvenanceForPreToolUse(payload, cwd, sessionId))
7049
+ if (await hasTrustedTypedSubagentProvenanceForPreToolUse(payload, cwd, stateDir, sessionId, { allowUntypedProvenance: true }))
6631
7050
  return null;
6632
7051
  const canonicalState = await readVisibleSkillActiveStateForStateDir(stateDir, sessionId);
6633
7052
  if (!canonicalState)
@@ -7445,6 +7864,10 @@ export async function buildConductorPreToolUseWriteGuardOutput(payload, cwd, sta
7445
7864
  const nativeSubagentSupport = resolveNativeSubagentSupportStatus({
7446
7865
  payload,
7447
7866
  persistedSupportBlocker: await readJsonIfExists(nativeSubagentSupportBlockerPath(stateDir)),
7867
+ persistedRoleRoutingMarker: readRoleRoutingMarker(stateDir, {
7868
+ cwd,
7869
+ sessionId: resolvedSessionId || readPayloadSessionId(payload),
7870
+ }),
7448
7871
  persistedCapacityBlocker: await readJsonIfExists(nativeSubagentCapacityBlockerPath(stateDir)),
7449
7872
  cwd,
7450
7873
  sessionId: resolvedSessionId || readPayloadSessionId(payload),
@@ -7478,6 +7901,9 @@ export async function buildConductorPreToolUseWriteGuardOutput(payload, cwd, sta
7478
7901
  const unsupportedNativeGuidance = isUnsupportedNativeSubagentEvidence(nativeSubagentSupport)
7479
7902
  ? ` ${buildUnsupportedNativeSubagentGuidance(nativeSubagentSupport)} Treat the active conductor workflow as blocked/cancelled for native delegation recovery; do not call multi_agent_v1.close_agent.`
7480
7903
  : "";
7904
+ const roleRoutingUnavailableGuidance = isRoleRoutingUnavailableEvidence(nativeSubagentSupport)
7905
+ ? ` ${buildRoleRoutingUnavailableGuidance(nativeSubagentSupport)}`
7906
+ : "";
7481
7907
  return {
7482
7908
  decision: "block",
7483
7909
  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}.`,
@@ -7487,6 +7913,7 @@ export async function buildConductorPreToolUseWriteGuardOutput(payload, cwd, sta
7487
7913
  + "Use specialized agents for source edits and plan/spec authorship. "
7488
7914
  + `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. `
7489
7915
  + unsupportedNativeGuidance
7916
+ + roleRoutingUnavailableGuidance
7490
7917
  + " Autopilot rework and typed subagent/worker lanes are exempt from this guard.",
7491
7918
  },
7492
7919
  };
@@ -8304,7 +8731,8 @@ async function buildStopHookOutput(payload, cwd, stateDir, options = {}) {
8304
8731
  return null;
8305
8732
  }
8306
8733
  const sessionId = readPayloadSessionId(payload);
8307
- const canonicalSessionId = await resolveInternalSessionIdForPayload(cwd, sessionId);
8734
+ const canonicalSessionId = options.canonicalSessionId
8735
+ ?? await resolveInternalSessionIdForPayload(cwd, sessionId);
8308
8736
  const threadId = readPayloadThreadId(payload);
8309
8737
  const suppressParentWorkflowStop = shouldSuppressParentWorkflowStopForSideConversation(payload);
8310
8738
  if (canonicalSessionId) {
@@ -8348,7 +8776,7 @@ async function buildStopHookOutput(payload, cwd, stateDir, options = {}) {
8348
8776
  }
8349
8777
  const ralphState = options.skipRalphStopBlock === true
8350
8778
  ? null
8351
- : await readActiveRalphState(cwd, stateDir, canonicalSessionId, ralphOwnerContext);
8779
+ : await readActiveRalphState(cwd, stateDir, sessionId || canonicalSessionId, ralphOwnerContext);
8352
8780
  if (!ralphState) {
8353
8781
  const autoresearchState = await readActiveAutoresearchState(cwd, canonicalSessionId);
8354
8782
  if (autoresearchState) {
@@ -8429,7 +8857,8 @@ async function buildStopHookOutput(payload, cwd, stateDir, options = {}) {
8429
8857
  return ordinaryNoProgressOutput;
8430
8858
  const autoNudgeConfig = await loadAutoNudgeConfig();
8431
8859
  const autoNudgePhase = await readStopAutoNudgePhase(cwd, stateDir, canonicalSessionId, threadId);
8432
- if (autoNudgeConfig.enabled
8860
+ if (options.skipAutoNudge !== true
8861
+ && autoNudgeConfig.enabled
8433
8862
  && detectNativeStopStallPattern(lastAssistantMessage, autoNudgeConfig.patterns, autoNudgePhase)) {
8434
8863
  const effectiveResponse = resolveEffectiveAutoNudgeResponse(autoNudgeConfig.response);
8435
8864
  return await returnPersistentStopBlock(payload, stateDir, "auto-nudge", lastAssistantMessage, {
@@ -8447,7 +8876,7 @@ async function buildStopHookOutput(payload, cwd, stateDir, options = {}) {
8447
8876
  if (isFinalHandoffDocumentRefreshCandidate(lastAssistantMessage)) {
8448
8877
  const documentRefreshWarning = evaluateFinalHandoffDocumentRefresh(cwd, lastAssistantMessage);
8449
8878
  if (documentRefreshWarning) {
8450
- return await maybeReturnRepeatableStopOutput(payload, stateDir, buildRepeatableStopSignature(payload, "document-refresh-stop", documentRefreshWarning.triggeringPaths.join("|"), canonicalSessionId), buildDocumentRefreshAdvisoryOutput(documentRefreshWarning, "Stop"), canonicalSessionId, { allowRepeatDuringStopHook: false });
8879
+ return await maybeReturnRepeatableStopOutput(payload, stateDir, buildRepeatableStopSignature(payload, "document-refresh-stop", documentRefreshWarning.triggeringPaths.join("|"), canonicalSessionId), { systemMessage: documentRefreshWarning.message }, canonicalSessionId, { allowRepeatDuringStopHook: false });
8451
8880
  }
8452
8881
  }
8453
8882
  return null;
@@ -8463,10 +8892,37 @@ async function buildStopHookOutput(payload, cwd, stateDir, options = {}) {
8463
8892
  systemMessage,
8464
8893
  }, canonicalSessionId);
8465
8894
  }
8895
+ async function preflightNativePromptTarget(stateDir, context) {
8896
+ if (context.status !== "authorized")
8897
+ return context;
8898
+ const targetDir = join(stateDir, "sessions", context.authorization.targetSessionId);
8899
+ const evidence = [];
8900
+ let filenames;
8901
+ try {
8902
+ filenames = await readdir(targetDir);
8903
+ }
8904
+ catch (error) {
8905
+ if (error.code === "ENOENT")
8906
+ return context;
8907
+ return preflightSelectedTargetOwner(context, [{ ownerCodexSessionId: {} }], "native", new Date().toISOString());
8908
+ }
8909
+ for (const filename of filenames) {
8910
+ if (!filename.endsWith("-state.json") && filename !== SKILL_ACTIVE_STATE_FILE)
8911
+ continue;
8912
+ try {
8913
+ const value = JSON.parse(await readFile(join(targetDir, filename), "utf8"));
8914
+ evidence.push(...extractSelectedTargetOwnerEvidence(value));
8915
+ }
8916
+ catch {
8917
+ evidence.push({ ownerCodexSessionId: {} });
8918
+ }
8919
+ }
8920
+ return preflightSelectedTargetOwner(context, evidence, "native", new Date().toISOString());
8921
+ }
8466
8922
  export async function dispatchCodexNativeHook(payload, options = {}) {
8467
8923
  const hookEventName = readHookEventName(payload);
8468
8924
  const cwd = options.cwd ?? (safeString(payload.cwd).trim() || process.cwd());
8469
- if (hookEventName === "Stop" && !hasNativeStopRuntimeSurface(cwd)) {
8925
+ if (hookEventName === "PostCompact" && process.env.OMX_NATIVE_HOOK_DOCTOR_SMOKE === "1") {
8470
8926
  return {
8471
8927
  hookEventName,
8472
8928
  omxEventName: mapCodexHookEventToOmxEvent(hookEventName),
@@ -8474,22 +8930,70 @@ export async function dispatchCodexNativeHook(payload, options = {}) {
8474
8930
  outputJson: null,
8475
8931
  };
8476
8932
  }
8477
- // Native hooks must use the same authoritative runtime state root as HUD/MCP
8478
- // when boxed/team roots are active; do not bypass it with cwd/.omx/state.
8479
- const stateDir = getBaseStateDir(cwd);
8480
- if (hookEventName !== "Stop") {
8481
- await mkdir(stateDir, { recursive: true });
8933
+ try {
8934
+ recoverAdaptedRoleBindings(cwd, getBaseStateDir(cwd));
8935
+ }
8936
+ catch {
8937
+ // Recovery is best-effort for unrelated hook events.
8938
+ }
8939
+ if (hookEventName === "Stop" && !hasNativeStopRuntimeSurface(cwd)) {
8940
+ return {
8941
+ hookEventName,
8942
+ omxEventName: mapCodexHookEventToOmxEvent(hookEventName),
8943
+ skillState: null,
8944
+ outputJson: null,
8945
+ };
8482
8946
  }
8947
+ // Native hooks must use the exact pointer root selected for this dispatch.
8948
+ const pointerContext = resolveSessionPointerContext(cwd);
8949
+ const stateDir = pointerContext.baseStateDir;
8483
8950
  const omxEventName = mapCodexHookEventToOmxEvent(hookEventName);
8484
8951
  let skillState = null;
8485
8952
  let triageAdditionalContext = null;
8486
8953
  let goalWorkflowAdditionalContext = null;
8487
8954
  let ultragoalSteeringAdditionalContext = null;
8955
+ let promptClassification = null;
8488
8956
  const nativeSessionId = safeString(payload.session_id ?? payload.sessionId).trim();
8489
8957
  const threadId = safeString(payload.thread_id ?? payload.threadId).trim();
8490
8958
  const turnId = safeString(payload.turn_id ?? payload.turnId).trim();
8491
- const currentSessionState = await readUsableSessionState(cwd);
8959
+ const pointer = await readSessionPointer(pointerContext);
8960
+ const currentSessionState = pointer.status === "usable" ? pointer.state ?? null : null;
8961
+ let promptTurnContext = hookEventName === "UserPromptSubmit"
8962
+ ? evaluateResolvedPromptTurn({
8963
+ producer: "native",
8964
+ payloadSessionId: payload.session_id ?? payload.sessionId,
8965
+ ownerEnvSessionId: undefined,
8966
+ selectedPointer: pointer,
8967
+ threadFacts: await readNativePromptThreadFacts(cwd, nativeSessionId, threadId, currentSessionState),
8968
+ nowIso: new Date().toISOString(),
8969
+ })
8970
+ : null;
8971
+ if (promptTurnContext) {
8972
+ promptTurnContext = await preflightNativePromptTarget(stateDir, promptTurnContext);
8973
+ }
8974
+ if (promptTurnContext?.status === "rejected") {
8975
+ await appendPromptSessionProvenanceRejection(pointerContext, promptTurnContext.diagnostic).catch(() => { });
8976
+ return { hookEventName, omxEventName, skillState: null, outputJson: null };
8977
+ }
8978
+ if (promptTurnContext?.status === "suppressed-target-child") {
8979
+ return { hookEventName, omxEventName, skillState: null, outputJson: null };
8980
+ }
8981
+ if (hookEventName !== "Stop") {
8982
+ await mkdir(stateDir, { recursive: true });
8983
+ }
8984
+ let allowImplicitSessionSideEffects = pointer.status === "usable" || pointer.status === "absent" || promptTurnContext?.status === "authorized";
8985
+ let stopAuthorizationFailure = allowImplicitSessionSideEffects
8986
+ ? null
8987
+ : {
8988
+ stopReason: "session_pointer_unusable",
8989
+ reason: `OMX cannot authorize Stop while the selected session pointer is ${pointer.status}; repair the pointer evidence before continuing.`,
8990
+ };
8492
8991
  let canonicalSessionId = safeString(currentSessionState?.session_id).trim();
8992
+ if (promptTurnContext?.status === "authorized") {
8993
+ canonicalSessionId = promptTurnContext.authorization.targetSessionId;
8994
+ }
8995
+ const allowPromptGlobalSideEffects = promptTurnContext?.status !== "authorized"
8996
+ || promptTurnContext.authorization.globalSideEffects === "allow";
8493
8997
  let resolvedNativeSessionId = nativeSessionId;
8494
8998
  let skipCanonicalSessionStartContext = false;
8495
8999
  let isSubagentSessionStart = false;
@@ -8528,20 +9032,58 @@ export async function dispatchCodexNativeHook(payload, options = {}) {
8528
9032
  }
8529
9033
  }
8530
9034
  else {
8531
- const sessionState = await reconcileNativeSessionStart(cwd, nativeSessionId, {
8532
- pid: options.sessionOwnerPid ?? resolveSessionOwnerPid(payload),
8533
- });
8534
- canonicalSessionId = safeString(sessionState.session_id).trim();
8535
- resolvedNativeSessionId = safeString(sessionState.native_session_id).trim() || nativeSessionId;
9035
+ const ownerOmxSessionId = await resolveVerifiedOwnerOmxSessionId();
9036
+ try {
9037
+ const sessionState = await reconcileNativeSessionStart(cwd, nativeSessionId, {
9038
+ context: pointerContext,
9039
+ pid: options.sessionOwnerPid ?? resolveSessionOwnerPid(payload),
9040
+ ...(ownerOmxSessionId
9041
+ ? { ownerOmxSessionId, ownerAliasVerified: true }
9042
+ : {}),
9043
+ });
9044
+ canonicalSessionId = safeString(sessionState.session_id).trim();
9045
+ resolvedNativeSessionId = safeString(sessionState.native_session_id).trim() || nativeSessionId;
9046
+ allowImplicitSessionSideEffects = true;
9047
+ stopAuthorizationFailure = null;
9048
+ // #3181: leader attestation is intentionally NOT performed here. This branch is
9049
+ // reached whenever readNativeSubagentSessionStartMetadata() returns null, which
9050
+ // conflates a genuine root start with an unreadable/malformed child transcript, so
9051
+ // it cannot positively classify a leader (a legitimate leader may also carry no
9052
+ // transcript). Leader attestation is performed only on the strictly-gated fresh
9053
+ // leader PreToolUse path below, which fires before the first in-turn role-intent
9054
+ // write. Fail closed here rather than risk a false-leader adoption.
9055
+ }
9056
+ catch (error) {
9057
+ if (!isSessionPointerLaunchAbort(error))
9058
+ throw error;
9059
+ canonicalSessionId = "";
9060
+ resolvedNativeSessionId = nativeSessionId;
9061
+ skipCanonicalSessionStartContext = true;
9062
+ allowImplicitSessionSideEffects = false;
9063
+ stopAuthorizationFailure = {
9064
+ stopReason: "session_pointer_unusable",
9065
+ reason: `OMX cannot authorize Stop while the selected session pointer is ${pointer.status}; repair the pointer evidence before continuing.`,
9066
+ };
9067
+ }
8536
9068
  }
8537
9069
  }
8538
9070
  else if (!canonicalSessionId) {
8539
9071
  canonicalSessionId = safeString(currentSessionState?.session_id).trim();
8540
9072
  }
8541
9073
  if (hookEventName === "Stop") {
8542
- const inheritedSessionId = safeString(process.env.OMX_SESSION_ID || process.env.CODEX_SESSION_ID).trim();
8543
- const stopCanonicalSessionId = await resolveInternalSessionIdForPayload(cwd, readPayloadSessionId(payload) || inheritedSessionId);
8544
- if (stopCanonicalSessionId) {
9074
+ const stopPayloadSessionId = readPayloadSessionId(payload);
9075
+ const stopCanonicalSessionId = await resolveInternalSessionIdForPayload(cwd, stopPayloadSessionId, undefined, currentSessionState, pointer.status === "absent");
9076
+ if (stopPayloadSessionId && !stopCanonicalSessionId) {
9077
+ canonicalSessionId = "";
9078
+ allowImplicitSessionSideEffects = false;
9079
+ if (!stopAuthorizationFailure) {
9080
+ stopAuthorizationFailure = {
9081
+ stopReason: "session_scope_unmatched",
9082
+ reason: `OMX cannot authorize Stop for unmatched session id ${stopPayloadSessionId}; the selected session pointer remains authoritative.`,
9083
+ };
9084
+ }
9085
+ }
9086
+ else if (stopCanonicalSessionId) {
8545
9087
  canonicalSessionId = stopCanonicalSessionId;
8546
9088
  }
8547
9089
  if (canonicalSessionId && safeString(currentSessionState?.session_id).trim() === canonicalSessionId) {
@@ -8549,8 +9091,8 @@ export async function dispatchCodexNativeHook(payload, options = {}) {
8549
9091
  safeString(currentSessionState?.native_session_id).trim() || resolvedNativeSessionId;
8550
9092
  }
8551
9093
  }
8552
- const eventSessionId = canonicalSessionId || nativeSessionId || undefined;
8553
- const sessionIdForState = canonicalSessionId || nativeSessionId;
9094
+ let eventSessionId = canonicalSessionId || nativeSessionId || undefined;
9095
+ let sessionIdForState = canonicalSessionId || null;
8554
9096
  let outputJson = null;
8555
9097
  const typedAgentRolePayload = isTypedAgentRolePayload(payload);
8556
9098
  const isSubagentPromptSubmit = hookEventName === "UserPromptSubmit"
@@ -8565,27 +9107,74 @@ export async function dispatchCodexNativeHook(payload, options = {}) {
8565
9107
  ? safeString(currentSessionState?.native_session_id).trim()
8566
9108
  : "")))).some(Boolean)
8567
9109
  : false;
9110
+ if (isSubagentStop && stopAuthorizationFailure?.stopReason === "session_scope_unmatched") {
9111
+ canonicalSessionId = normalizeSessionId(readPayloadSessionId(payload)) ?? "";
9112
+ allowImplicitSessionSideEffects = true;
9113
+ stopAuthorizationFailure = null;
9114
+ eventSessionId = canonicalSessionId || nativeSessionId || undefined;
9115
+ sessionIdForState = canonicalSessionId || null;
9116
+ }
8568
9117
  const suppressNoisySubagentLifecycleDispatch = (isSubagentSessionStart || isSubagentStop)
8569
9118
  && shouldSuppressSubagentLifecycleHookDispatch();
8570
9119
  if (hookEventName === "UserPromptSubmit") {
8571
9120
  const prompt = readPromptText(payload);
8572
- goalWorkflowAdditionalContext = await buildCompletedGoalCleanupPromptWarning(cwd, prompt).catch(() => null)
8573
- ?? await buildGoalWorkflowReconciliationPromptWarning(cwd, prompt).catch(() => null);
8574
- ultragoalSteeringAdditionalContext = prompt && !isSubagentPromptSubmit
9121
+ if (!isSubagentPromptSubmit) {
9122
+ promptClassification = classifyKeywordInput(prompt);
9123
+ }
9124
+ goalWorkflowAdditionalContext = allowPromptGlobalSideEffects
9125
+ ? await buildCompletedGoalCleanupPromptWarning(cwd, prompt).catch(() => null)
9126
+ ?? await buildGoalWorkflowReconciliationPromptWarning(cwd, prompt).catch(() => null)
9127
+ : null;
9128
+ ultragoalSteeringAdditionalContext = prompt && !isSubagentPromptSubmit && allowImplicitSessionSideEffects && allowPromptGlobalSideEffects
8575
9129
  ? 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)}`)
8576
9130
  : null;
8577
- if (prompt && !isSubagentPromptSubmit) {
8578
- skillState = buildNativeOutsideTmuxTeamPromptBlockState(prompt, cwd, payload, sessionIdForState, threadId || undefined, turnId || undefined) ?? await recordSkillActivation({
9131
+ let suppressActivationSeeding = !allowImplicitSessionSideEffects;
9132
+ if (promptTurnContext?.status === "authorized") {
9133
+ sessionIdForState = promptTurnContext.authorization.targetSessionId;
9134
+ }
9135
+ else if (prompt && !isSubagentPromptSubmit && allowImplicitSessionSideEffects) {
9136
+ const rawHookSessionId = canonicalSessionId || nativeSessionId;
9137
+ const normalizedHookSessionId = normalizeSessionId(rawHookSessionId);
9138
+ const explicitHookSessionId = currentSessionState ? undefined : normalizedHookSessionId;
9139
+ if (rawHookSessionId && !normalizedHookSessionId) {
9140
+ suppressActivationSeeding = true;
9141
+ }
9142
+ else {
9143
+ try {
9144
+ const writableScope = await resolveWritableStateScope(cwd, explicitHookSessionId);
9145
+ sessionIdForState = writableScope.sessionId ?? null;
9146
+ }
9147
+ catch (error) {
9148
+ if (!isImplicitWritableScopeFailure(error))
9149
+ throw error;
9150
+ suppressActivationSeeding = true;
9151
+ }
9152
+ }
9153
+ }
9154
+ if (prompt && promptClassification && !isSubagentPromptSubmit && !suppressActivationSeeding) {
9155
+ skillState = buildNativeOutsideTmuxTeamPromptBlockState(promptClassification, cwd, payload, sessionIdForState || undefined, threadId || undefined, turnId || undefined) ?? await recordSkillActivation({
8579
9156
  stateDir,
8580
9157
  sourceCwd: cwd,
8581
9158
  text: prompt,
8582
- sessionId: sessionIdForState,
9159
+ classification: promptClassification,
9160
+ allowSecondaryTeam: !isNativeOutsideTmuxUserPrompt(cwd, payload, sessionIdForState || undefined),
9161
+ sessionId: sessionIdForState || undefined,
8583
9162
  threadId,
8584
9163
  turnId,
9164
+ resolvedPromptTurnContext: promptTurnContext ?? undefined,
9165
+ onProvenanceRejected: async (diagnostic) => {
9166
+ await appendPromptSessionProvenanceRejection(pointerContext, diagnostic).catch(() => { });
9167
+ },
8585
9168
  });
8586
9169
  }
8587
9170
  // --- Triage classifier (advisory-only, non-keyword prompts) ---
8588
- if (prompt && skillState === null && !isSubagentPromptSubmit) {
9171
+ if (prompt
9172
+ && skillState === null
9173
+ && !isSubagentPromptSubmit
9174
+ && allowPromptGlobalSideEffects
9175
+ && promptClassification?.reservedInput === null
9176
+ && promptClassification.hasExplicitLikeInvocation === false
9177
+ && promptClassification.matches.length === 0) {
8589
9178
  try {
8590
9179
  if (readTriageConfig().enabled) {
8591
9180
  const normalized = prompt.trim().toLowerCase();
@@ -8614,7 +9203,9 @@ export async function dispatchCodexNativeHook(payload, options = {}) {
8614
9203
  },
8615
9204
  suppress_followup: true,
8616
9205
  };
8617
- writeTriageState({ cwd, sessionId: sessionIdForState || null, state: newState });
9206
+ if (!suppressActivationSeeding) {
9207
+ writeTriageState({ cwd, sessionId: sessionIdForState || null, state: newState });
9208
+ }
8618
9209
  }
8619
9210
  else if (decision.lane === "LIGHT") {
8620
9211
  if (decision.destination === "explore") {
@@ -8647,7 +9238,9 @@ export async function dispatchCodexNativeHook(payload, options = {}) {
8647
9238
  },
8648
9239
  suppress_followup: true,
8649
9240
  };
8650
- writeTriageState({ cwd, sessionId: sessionIdForState || null, state: newState });
9241
+ if (!suppressActivationSeeding) {
9242
+ writeTriageState({ cwd, sessionId: sessionIdForState || null, state: newState });
9243
+ }
8651
9244
  }
8652
9245
  }
8653
9246
  // lane === "PASS": no context, no state write
@@ -8662,14 +9255,14 @@ export async function dispatchCodexNativeHook(payload, options = {}) {
8662
9255
  const skipHudReconcileForDoctorSmoke = process.env.OMX_NATIVE_HOOK_DOCTOR_SMOKE === "1";
8663
9256
  const skipHudReconcileForTeamWorkerPane = !isSubagentPromptSubmit
8664
9257
  && await isConfirmedTeamWorkerPromptSubmitPane(cwd).catch(() => false);
8665
- if (!skipHudReconcileForDoctorSmoke && !skipHudReconcileForTeamWorkerPane) {
9258
+ if (allowImplicitSessionSideEffects && allowPromptGlobalSideEffects && !skipHudReconcileForDoctorSmoke && !skipHudReconcileForTeamWorkerPane) {
8666
9259
  const reconcileHudForPromptSubmitFn = options.reconcileHudForPromptSubmitFn ?? reconcileHudForPromptSubmit;
8667
9260
  const hudSessionId = resolveHudReconcileSessionId(currentSessionState, canonicalSessionId, sessionIdForState);
8668
9261
  const hudSessionIds = resolveHudReconcileSessionIds(currentSessionState, canonicalSessionId, sessionIdForState, nativeSessionId);
8669
9262
  await reconcileHudForPromptSubmitFn(cwd, { sessionId: hudSessionId, sessionIds: hudSessionIds }).catch(() => { });
8670
9263
  }
8671
9264
  }
8672
- if (omxEventName && !skipCanonicalSessionStartContext && !suppressNoisySubagentLifecycleDispatch) {
9265
+ if (omxEventName && allowImplicitSessionSideEffects && allowPromptGlobalSideEffects && !skipCanonicalSessionStartContext && !suppressNoisySubagentLifecycleDispatch) {
8673
9266
  const baseContext = buildBaseContext(cwd, payload, hookEventName, canonicalSessionId);
8674
9267
  if (resolvedNativeSessionId) {
8675
9268
  baseContext.native_session_id = resolvedNativeSessionId;
@@ -8706,12 +9299,14 @@ export async function dispatchCodexNativeHook(payload, options = {}) {
8706
9299
  })
8707
9300
  : isSubagentPromptSubmit
8708
9301
  ? null
8709
- : [
8710
- buildAdditionalContextMessage(readPromptText(payload), skillState, cwd, payload),
8711
- ultragoalSteeringAdditionalContext,
8712
- goalWorkflowAdditionalContext,
8713
- triageAdditionalContext,
8714
- ].filter((entry) => Boolean(entry)).join("\n\n") || null;
9302
+ : promptClassification
9303
+ ? [
9304
+ buildAdditionalContextMessage(promptClassification, skillState, cwd, payload),
9305
+ ultragoalSteeringAdditionalContext,
9306
+ goalWorkflowAdditionalContext,
9307
+ triageAdditionalContext,
9308
+ ].filter((entry) => Boolean(entry)).join("\n\n") || null
9309
+ : null;
8715
9310
  if (additionalContext) {
8716
9311
  outputJson = {
8717
9312
  hookSpecificOutput: {
@@ -8722,12 +9317,52 @@ export async function dispatchCodexNativeHook(payload, options = {}) {
8722
9317
  }
8723
9318
  }
8724
9319
  else if (hookEventName === "PreToolUse") {
9320
+ // #3181 Phase-1 (PreToolUse): this hook fires before the shell tool call that runs
9321
+ // the first in-turn `omx ralplan role-intent write`. On a fresh App/outside-tmux
9322
+ // turn where SessionStart did not establish the pointer, reconcile the canonical
9323
+ // pointer and attest the leader here so the first command can bootstrap. Strictly
9324
+ // gated to a fresh (absent-pointer) LEADER turn: no canonical session yet, a present
9325
+ // native session id, a leader-shaped thread (absent or equal to the native session
9326
+ // id), and no subagent/typed-role provenance. Best-effort; never blocks PreToolUse.
9327
+ if (allowImplicitSessionSideEffects
9328
+ && !canonicalSessionId
9329
+ && nativeSessionId
9330
+ && readPayloadAgentRole(payload) === ""
9331
+ && !hasSubagentThreadSpawnProvenance(payload)) {
9332
+ const preToolUseLeaderThreadId = readPayloadThreadId(payload);
9333
+ if (preToolUseLeaderThreadId
9334
+ && preToolUseLeaderThreadId === nativeSessionId
9335
+ && !(await isThreadTrackedAsSubagent(cwd, nativeSessionId))) {
9336
+ try {
9337
+ const ownerOmxSessionId = await resolveVerifiedOwnerOmxSessionId();
9338
+ const sessionState = await reconcileNativeSessionStart(cwd, nativeSessionId, {
9339
+ context: pointerContext,
9340
+ pid: options.sessionOwnerPid ?? resolveSessionOwnerPid(payload),
9341
+ ...(ownerOmxSessionId ? { ownerOmxSessionId, ownerAliasVerified: true } : {}),
9342
+ });
9343
+ const bootstrapSessionId = safeString(sessionState.session_id).trim();
9344
+ const bootstrapLeaderThreadId = safeString(sessionState.native_session_id).trim() || nativeSessionId;
9345
+ if (bootstrapSessionId && bootstrapLeaderThreadId) {
9346
+ canonicalSessionId = bootstrapSessionId;
9347
+ attestLeaderThread(cwd, {
9348
+ sessionId: bootstrapSessionId,
9349
+ leaderThreadId: bootstrapLeaderThreadId,
9350
+ source: 'native-pretooluse',
9351
+ });
9352
+ }
9353
+ }
9354
+ catch {
9355
+ // Best-effort bootstrap; PreToolUse must never fail on this.
9356
+ }
9357
+ }
9358
+ }
8725
9359
  const payloadSessionId = readPayloadSessionId(payload);
8726
9360
  const rootPointerConflict = await readLiveRootSessionPointerConflict(stateDir, payloadSessionId);
8727
9361
  const preToolUseSessionId = payloadSessionId
8728
9362
  ? await resolveInternalSessionIdForPayload(cwd, payloadSessionId, stateDir)
8729
9363
  : "";
8730
- outputJson = await buildDeepInterviewPreToolUseBoundaryOutput(payload, cwd, stateDir, preToolUseSessionId)
9364
+ outputJson = buildNativeUnknownRolePreToolUseOutput(payload)
9365
+ ?? await buildDeepInterviewPreToolUseBoundaryOutput(payload, cwd, stateDir, preToolUseSessionId)
8731
9366
  ?? await buildRalplanPreToolUseBoundaryOutput(payload, cwd, stateDir, preToolUseSessionId)
8732
9367
  ?? await buildPlanningRootPointerConflictPreToolUseOutput(payload, cwd, stateDir, rootPointerConflict)
8733
9368
  ?? await buildConductorPreToolUseWriteGuardOutput(payload, cwd, stateDir, preToolUseSessionId)
@@ -8736,18 +9371,36 @@ export async function dispatchCodexNativeHook(payload, options = {}) {
8736
9371
  ?? buildNativePreToolUseOutput(payload);
8737
9372
  }
8738
9373
  else if (hookEventName === "PostToolUse") {
8739
- await recordNativeSubagentCapacityBlocker(cwd, stateDir, payload).catch(() => { });
8740
- await recordNativeSubagentSupportBlocker(cwd, stateDir, payload).catch(() => { });
8741
- if (detectMcpTransportFailure(payload)) {
8742
- await markTeamTransportFailure(cwd, payload);
9374
+ if (allowImplicitSessionSideEffects) {
9375
+ await recordNativeSubagentCapacityBlocker(cwd, stateDir, payload).catch(() => { });
9376
+ await recordNativeSubagentSupportBlocker(cwd, stateDir, payload).catch(() => { });
9377
+ if (detectMcpTransportFailure(payload)) {
9378
+ await markTeamTransportFailure(cwd, payload);
9379
+ }
9380
+ await handleTeamWorkerPostToolUseSuccess(payload, cwd);
8743
9381
  }
8744
9382
  outputJson = buildNativePostToolUseOutput(payload);
8745
- await handleTeamWorkerPostToolUseSuccess(payload, cwd);
8746
9383
  }
8747
9384
  else if (hookEventName === "Stop") {
8748
- outputJson = await buildStopHookOutput(payload, cwd, stateDir, {
8749
- skipRalphStopBlock: isSubagentStop,
8750
- }) ?? await buildCompletedGoalCleanupStopOutput(payload, cwd);
9385
+ if (allowImplicitSessionSideEffects) {
9386
+ outputJson = await buildStopHookOutput(payload, cwd, stateDir, {
9387
+ canonicalSessionId: canonicalSessionId || undefined,
9388
+ skipRalphStopBlock: isSubagentStop,
9389
+ skipAutoNudge: isSubagentStop,
9390
+ }) ?? await buildCompletedGoalCleanupStopOutput(payload, cwd);
9391
+ }
9392
+ else {
9393
+ const failure = stopAuthorizationFailure ?? {
9394
+ stopReason: "session_pointer_unusable",
9395
+ reason: "OMX cannot authorize Stop without a writable session authority.",
9396
+ };
9397
+ outputJson = {
9398
+ decision: "block",
9399
+ stopReason: failure.stopReason,
9400
+ reason: failure.reason,
9401
+ systemMessage: failure.reason,
9402
+ };
9403
+ }
8751
9404
  }
8752
9405
  return {
8753
9406
  hookEventName,