oh-my-codex 0.18.6 → 0.18.8

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (444) hide show
  1. package/Cargo.lock +6 -6
  2. package/Cargo.toml +1 -1
  3. package/README.md +59 -10
  4. package/crates/omx-sparkshell/tests/execution.rs +1 -1
  5. package/dist/agents/__tests__/definitions.test.js +11 -0
  6. package/dist/agents/__tests__/definitions.test.js.map +1 -1
  7. package/dist/agents/__tests__/native-config.test.js +56 -6
  8. package/dist/agents/__tests__/native-config.test.js.map +1 -1
  9. package/dist/agents/definitions.d.ts +10 -0
  10. package/dist/agents/definitions.d.ts.map +1 -1
  11. package/dist/agents/definitions.js +5 -1
  12. package/dist/agents/definitions.js.map +1 -1
  13. package/dist/agents/native-config.d.ts +5 -1
  14. package/dist/agents/native-config.d.ts.map +1 -1
  15. package/dist/agents/native-config.js +19 -4
  16. package/dist/agents/native-config.js.map +1 -1
  17. package/dist/autopilot/__tests__/fsm.test.d.ts +2 -0
  18. package/dist/autopilot/__tests__/fsm.test.d.ts.map +1 -0
  19. package/dist/autopilot/__tests__/fsm.test.js +75 -0
  20. package/dist/autopilot/__tests__/fsm.test.js.map +1 -0
  21. package/dist/autopilot/__tests__/ralplan-gate.test.d.ts +2 -0
  22. package/dist/autopilot/__tests__/ralplan-gate.test.d.ts.map +1 -0
  23. package/dist/autopilot/__tests__/ralplan-gate.test.js +79 -0
  24. package/dist/autopilot/__tests__/ralplan-gate.test.js.map +1 -0
  25. package/dist/autopilot/deep-interview-gate.d.ts +18 -0
  26. package/dist/autopilot/deep-interview-gate.d.ts.map +1 -0
  27. package/dist/autopilot/deep-interview-gate.js +256 -0
  28. package/dist/autopilot/deep-interview-gate.js.map +1 -0
  29. package/dist/autopilot/fsm.d.ts +13 -0
  30. package/dist/autopilot/fsm.d.ts.map +1 -0
  31. package/dist/autopilot/fsm.js +70 -0
  32. package/dist/autopilot/fsm.js.map +1 -0
  33. package/dist/autopilot/ralplan-gate.d.ts +17 -0
  34. package/dist/autopilot/ralplan-gate.d.ts.map +1 -0
  35. package/dist/autopilot/ralplan-gate.js +61 -0
  36. package/dist/autopilot/ralplan-gate.js.map +1 -0
  37. package/dist/cli/__tests__/codex-plugin-layout.test.js +512 -1
  38. package/dist/cli/__tests__/codex-plugin-layout.test.js.map +1 -1
  39. package/dist/cli/__tests__/doctor-warning-copy.test.js +39 -0
  40. package/dist/cli/__tests__/doctor-warning-copy.test.js.map +1 -1
  41. package/dist/cli/__tests__/index.test.js +83 -7
  42. package/dist/cli/__tests__/index.test.js.map +1 -1
  43. package/dist/cli/__tests__/launch-fallback.test.js +175 -6
  44. package/dist/cli/__tests__/launch-fallback.test.js.map +1 -1
  45. package/dist/cli/__tests__/package-bin-contract.test.js +8 -4
  46. package/dist/cli/__tests__/package-bin-contract.test.js.map +1 -1
  47. package/dist/cli/__tests__/question.test.js +100 -0
  48. package/dist/cli/__tests__/question.test.js.map +1 -1
  49. package/dist/cli/__tests__/ralph-goal-mode-contract.test.js +13 -0
  50. package/dist/cli/__tests__/ralph-goal-mode-contract.test.js.map +1 -1
  51. package/dist/cli/__tests__/ralph.test.js +14 -0
  52. package/dist/cli/__tests__/ralph.test.js.map +1 -1
  53. package/dist/cli/__tests__/setup-install-mode.test.js +89 -0
  54. package/dist/cli/__tests__/setup-install-mode.test.js.map +1 -1
  55. package/dist/cli/__tests__/setup-refresh.test.js +83 -0
  56. package/dist/cli/__tests__/setup-refresh.test.js.map +1 -1
  57. package/dist/cli/__tests__/state.test.js +21 -0
  58. package/dist/cli/__tests__/state.test.js.map +1 -1
  59. package/dist/cli/__tests__/team.test.js +2 -2
  60. package/dist/cli/__tests__/team.test.js.map +1 -1
  61. package/dist/cli/__tests__/update.test.js +110 -2
  62. package/dist/cli/__tests__/update.test.js.map +1 -1
  63. package/dist/cli/doctor.d.ts.map +1 -1
  64. package/dist/cli/doctor.js +8 -1
  65. package/dist/cli/doctor.js.map +1 -1
  66. package/dist/cli/index.d.ts +14 -3
  67. package/dist/cli/index.d.ts.map +1 -1
  68. package/dist/cli/index.js +298 -50
  69. package/dist/cli/index.js.map +1 -1
  70. package/dist/cli/plugin-marketplace.d.ts +14 -2
  71. package/dist/cli/plugin-marketplace.d.ts.map +1 -1
  72. package/dist/cli/plugin-marketplace.js +62 -15
  73. package/dist/cli/plugin-marketplace.js.map +1 -1
  74. package/dist/cli/question.d.ts.map +1 -1
  75. package/dist/cli/question.js +36 -5
  76. package/dist/cli/question.js.map +1 -1
  77. package/dist/cli/ralph.d.ts.map +1 -1
  78. package/dist/cli/ralph.js +3 -1
  79. package/dist/cli/ralph.js.map +1 -1
  80. package/dist/cli/setup-preferences.d.ts +2 -0
  81. package/dist/cli/setup-preferences.d.ts.map +1 -1
  82. package/dist/cli/setup-preferences.js +4 -0
  83. package/dist/cli/setup-preferences.js.map +1 -1
  84. package/dist/cli/setup.d.ts +3 -0
  85. package/dist/cli/setup.d.ts.map +1 -1
  86. package/dist/cli/setup.js +166 -27
  87. package/dist/cli/setup.js.map +1 -1
  88. package/dist/cli/state.d.ts.map +1 -1
  89. package/dist/cli/state.js +8 -1
  90. package/dist/cli/state.js.map +1 -1
  91. package/dist/cli/tmux-hook.d.ts.map +1 -1
  92. package/dist/cli/tmux-hook.js +16 -0
  93. package/dist/cli/tmux-hook.js.map +1 -1
  94. package/dist/cli/update.d.ts +2 -0
  95. package/dist/cli/update.d.ts.map +1 -1
  96. package/dist/cli/update.js +47 -3
  97. package/dist/cli/update.js.map +1 -1
  98. package/dist/config/__tests__/deep-interview.test.js +7 -6
  99. package/dist/config/__tests__/deep-interview.test.js.map +1 -1
  100. package/dist/config/__tests__/generator-notify.test.js +1 -0
  101. package/dist/config/__tests__/generator-notify.test.js.map +1 -1
  102. package/dist/config/deep-interview.d.ts.map +1 -1
  103. package/dist/config/deep-interview.js +14 -4
  104. package/dist/config/deep-interview.js.map +1 -1
  105. package/dist/config/generator.d.ts +2 -2
  106. package/dist/config/generator.d.ts.map +1 -1
  107. package/dist/config/generator.js +2 -2
  108. package/dist/config/generator.js.map +1 -1
  109. package/dist/config/team-mode.d.ts +12 -0
  110. package/dist/config/team-mode.d.ts.map +1 -0
  111. package/dist/config/team-mode.js +91 -0
  112. package/dist/config/team-mode.js.map +1 -0
  113. package/dist/hooks/__tests__/agents-overlay.test.js +88 -0
  114. package/dist/hooks/__tests__/agents-overlay.test.js.map +1 -1
  115. package/dist/hooks/__tests__/autopilot-skill-contract.test.js +8 -0
  116. package/dist/hooks/__tests__/autopilot-skill-contract.test.js.map +1 -1
  117. package/dist/hooks/__tests__/code-review-skill-contract.test.js +8 -0
  118. package/dist/hooks/__tests__/code-review-skill-contract.test.js.map +1 -1
  119. package/dist/hooks/__tests__/deep-interview-contract.test.js +10 -0
  120. package/dist/hooks/__tests__/deep-interview-contract.test.js.map +1 -1
  121. package/dist/hooks/__tests__/keyword-detector.test.js +1072 -14
  122. package/dist/hooks/__tests__/keyword-detector.test.js.map +1 -1
  123. package/dist/hooks/__tests__/notify-fallback-watcher.test.js +64 -1
  124. package/dist/hooks/__tests__/notify-fallback-watcher.test.js.map +1 -1
  125. package/dist/hooks/__tests__/notify-hook-auto-nudge.test.js +189 -0
  126. package/dist/hooks/__tests__/notify-hook-auto-nudge.test.js.map +1 -1
  127. package/dist/hooks/__tests__/notify-hook-team-leader-nudge.test.js +35 -2
  128. package/dist/hooks/__tests__/notify-hook-team-leader-nudge.test.js.map +1 -1
  129. package/dist/hooks/__tests__/notify-hook-tmux-heal.test.js +3 -3
  130. package/dist/hooks/__tests__/notify-hook-tmux-heal.test.js.map +1 -1
  131. package/dist/hooks/__tests__/session.test.js +25 -0
  132. package/dist/hooks/__tests__/session.test.js.map +1 -1
  133. package/dist/hooks/__tests__/skill-guidance-contract.test.js +21 -0
  134. package/dist/hooks/__tests__/skill-guidance-contract.test.js.map +1 -1
  135. package/dist/hooks/agents-overlay.d.ts.map +1 -1
  136. package/dist/hooks/agents-overlay.js +36 -50
  137. package/dist/hooks/agents-overlay.js.map +1 -1
  138. package/dist/hooks/deep-interview-config-instruction.js +1 -1
  139. package/dist/hooks/deep-interview-config-instruction.js.map +1 -1
  140. package/dist/hooks/extensibility/__tests__/plugin-runner.test.js +31 -0
  141. package/dist/hooks/extensibility/__tests__/plugin-runner.test.js.map +1 -1
  142. package/dist/hooks/extensibility/plugin-runner.js +17 -21
  143. package/dist/hooks/extensibility/plugin-runner.js.map +1 -1
  144. package/dist/hooks/keyword-detector.d.ts +1 -0
  145. package/dist/hooks/keyword-detector.d.ts.map +1 -1
  146. package/dist/hooks/keyword-detector.js +428 -32
  147. package/dist/hooks/keyword-detector.js.map +1 -1
  148. package/dist/hooks/keyword-registry.d.ts.map +1 -1
  149. package/dist/hooks/keyword-registry.js +1 -0
  150. package/dist/hooks/keyword-registry.js.map +1 -1
  151. package/dist/hooks/prompt-guidance-contract.d.ts.map +1 -1
  152. package/dist/hooks/prompt-guidance-contract.js +6 -0
  153. package/dist/hooks/prompt-guidance-contract.js.map +1 -1
  154. package/dist/hooks/session.d.ts +3 -0
  155. package/dist/hooks/session.d.ts.map +1 -1
  156. package/dist/hooks/session.js +13 -5
  157. package/dist/hooks/session.js.map +1 -1
  158. package/dist/hud/__tests__/authority.test.js +469 -31
  159. package/dist/hud/__tests__/authority.test.js.map +1 -1
  160. package/dist/hud/__tests__/hud-tmux-injection.test.js +2 -1
  161. package/dist/hud/__tests__/hud-tmux-injection.test.js.map +1 -1
  162. package/dist/hud/__tests__/index.test.js +210 -2
  163. package/dist/hud/__tests__/index.test.js.map +1 -1
  164. package/dist/hud/__tests__/reconcile.test.js +588 -28
  165. package/dist/hud/__tests__/reconcile.test.js.map +1 -1
  166. package/dist/hud/__tests__/render.test.js +61 -0
  167. package/dist/hud/__tests__/render.test.js.map +1 -1
  168. package/dist/hud/__tests__/state.test.js +208 -0
  169. package/dist/hud/__tests__/state.test.js.map +1 -1
  170. package/dist/hud/__tests__/tmux.test.js +314 -22
  171. package/dist/hud/__tests__/tmux.test.js.map +1 -1
  172. package/dist/hud/authority.d.ts +5 -0
  173. package/dist/hud/authority.d.ts.map +1 -1
  174. package/dist/hud/authority.js +337 -30
  175. package/dist/hud/authority.js.map +1 -1
  176. package/dist/hud/index.d.ts +20 -2
  177. package/dist/hud/index.d.ts.map +1 -1
  178. package/dist/hud/index.js +103 -26
  179. package/dist/hud/index.js.map +1 -1
  180. package/dist/hud/reconcile.d.ts +3 -3
  181. package/dist/hud/reconcile.d.ts.map +1 -1
  182. package/dist/hud/reconcile.js +129 -20
  183. package/dist/hud/reconcile.js.map +1 -1
  184. package/dist/hud/render.d.ts.map +1 -1
  185. package/dist/hud/render.js +35 -0
  186. package/dist/hud/render.js.map +1 -1
  187. package/dist/hud/state.d.ts.map +1 -1
  188. package/dist/hud/state.js +64 -50
  189. package/dist/hud/state.js.map +1 -1
  190. package/dist/hud/tmux.d.ts +26 -6
  191. package/dist/hud/tmux.d.ts.map +1 -1
  192. package/dist/hud/tmux.js +173 -38
  193. package/dist/hud/tmux.js.map +1 -1
  194. package/dist/hud/types.d.ts +11 -0
  195. package/dist/hud/types.d.ts.map +1 -1
  196. package/dist/hud/types.js.map +1 -1
  197. package/dist/mcp/__tests__/hermes-bridge.test.js +203 -7
  198. package/dist/mcp/__tests__/hermes-bridge.test.js.map +1 -1
  199. package/dist/mcp/__tests__/state-paths.test.js +71 -1
  200. package/dist/mcp/__tests__/state-paths.test.js.map +1 -1
  201. package/dist/mcp/__tests__/state-server.test.js +13 -1
  202. package/dist/mcp/__tests__/state-server.test.js.map +1 -1
  203. package/dist/mcp/hermes-bridge.d.ts +12 -2
  204. package/dist/mcp/hermes-bridge.d.ts.map +1 -1
  205. package/dist/mcp/hermes-bridge.js +83 -9
  206. package/dist/mcp/hermes-bridge.js.map +1 -1
  207. package/dist/mcp/state-paths.d.ts +32 -0
  208. package/dist/mcp/state-paths.d.ts.map +1 -1
  209. package/dist/mcp/state-paths.js +113 -17
  210. package/dist/mcp/state-paths.js.map +1 -1
  211. package/dist/mcp/state-server.d.ts +4 -4
  212. package/dist/modes/__tests__/base-autoresearch-contract.test.js +7 -1
  213. package/dist/modes/__tests__/base-autoresearch-contract.test.js.map +1 -1
  214. package/dist/pipeline/__tests__/stages.test.js +130 -0
  215. package/dist/pipeline/__tests__/stages.test.js.map +1 -1
  216. package/dist/pipeline/orchestrator.js +1 -1
  217. package/dist/pipeline/orchestrator.js.map +1 -1
  218. package/dist/pipeline/stages/ralplan.d.ts +1 -0
  219. package/dist/pipeline/stages/ralplan.d.ts.map +1 -1
  220. package/dist/pipeline/stages/ralplan.js +14 -5
  221. package/dist/pipeline/stages/ralplan.js.map +1 -1
  222. package/dist/question/__tests__/deep-interview.test.js +160 -2
  223. package/dist/question/__tests__/deep-interview.test.js.map +1 -1
  224. package/dist/question/__tests__/policy.test.js +63 -3
  225. package/dist/question/__tests__/policy.test.js.map +1 -1
  226. package/dist/question/__tests__/renderer.test.js +191 -2
  227. package/dist/question/__tests__/renderer.test.js.map +1 -1
  228. package/dist/question/__tests__/state.test.js +94 -3
  229. package/dist/question/__tests__/state.test.js.map +1 -1
  230. package/dist/question/__tests__/ui.test.js +4 -0
  231. package/dist/question/__tests__/ui.test.js.map +1 -1
  232. package/dist/question/autopilot-wait.d.ts +12 -2
  233. package/dist/question/autopilot-wait.d.ts.map +1 -1
  234. package/dist/question/autopilot-wait.js +158 -47
  235. package/dist/question/autopilot-wait.js.map +1 -1
  236. package/dist/question/deep-interview.d.ts.map +1 -1
  237. package/dist/question/deep-interview.js +22 -6
  238. package/dist/question/deep-interview.js.map +1 -1
  239. package/dist/question/policy.d.ts.map +1 -1
  240. package/dist/question/policy.js +2 -5
  241. package/dist/question/policy.js.map +1 -1
  242. package/dist/question/renderer.d.ts +12 -0
  243. package/dist/question/renderer.d.ts.map +1 -1
  244. package/dist/question/renderer.js +87 -3
  245. package/dist/question/renderer.js.map +1 -1
  246. package/dist/question/state.d.ts +8 -1
  247. package/dist/question/state.d.ts.map +1 -1
  248. package/dist/question/state.js +54 -14
  249. package/dist/question/state.js.map +1 -1
  250. package/dist/question/types.d.ts +1 -1
  251. package/dist/question/types.d.ts.map +1 -1
  252. package/dist/question/ui.d.ts +1 -0
  253. package/dist/question/ui.d.ts.map +1 -1
  254. package/dist/question/ui.js +1 -0
  255. package/dist/question/ui.js.map +1 -1
  256. package/dist/ralplan/__tests__/runtime.test.js +191 -0
  257. package/dist/ralplan/__tests__/runtime.test.js.map +1 -1
  258. package/dist/ralplan/consensus-gate.d.ts +9 -1
  259. package/dist/ralplan/consensus-gate.d.ts.map +1 -1
  260. package/dist/ralplan/consensus-gate.js +84 -2
  261. package/dist/ralplan/consensus-gate.js.map +1 -1
  262. package/dist/ralplan/runtime.d.ts +9 -0
  263. package/dist/ralplan/runtime.d.ts.map +1 -1
  264. package/dist/ralplan/runtime.js +32 -11
  265. package/dist/ralplan/runtime.js.map +1 -1
  266. package/dist/scripts/__tests__/codex-native-hook.test.js +2315 -280
  267. package/dist/scripts/__tests__/codex-native-hook.test.js.map +1 -1
  268. package/dist/scripts/__tests__/notify-state-io.test.js +72 -1
  269. package/dist/scripts/__tests__/notify-state-io.test.js.map +1 -1
  270. package/dist/scripts/__tests__/notify-tmux-injection.test.d.ts +2 -0
  271. package/dist/scripts/__tests__/notify-tmux-injection.test.d.ts.map +1 -0
  272. package/dist/scripts/__tests__/notify-tmux-injection.test.js +57 -0
  273. package/dist/scripts/__tests__/notify-tmux-injection.test.js.map +1 -0
  274. package/dist/scripts/__tests__/run-test-files.test.js +74 -0
  275. package/dist/scripts/__tests__/run-test-files.test.js.map +1 -1
  276. package/dist/scripts/__tests__/verify-native-agents.test.js +65 -0
  277. package/dist/scripts/__tests__/verify-native-agents.test.js.map +1 -1
  278. package/dist/scripts/codex-native-hook.d.ts.map +1 -1
  279. package/dist/scripts/codex-native-hook.js +431 -56
  280. package/dist/scripts/codex-native-hook.js.map +1 -1
  281. package/dist/scripts/codex-native-pre-post.d.ts.map +1 -1
  282. package/dist/scripts/codex-native-pre-post.js +79 -1
  283. package/dist/scripts/codex-native-pre-post.js.map +1 -1
  284. package/dist/scripts/eval/eval-parity-smoke.js +1 -1
  285. package/dist/scripts/eval/eval-parity-smoke.js.map +1 -1
  286. package/dist/scripts/hook-payload-guard.d.ts +9 -0
  287. package/dist/scripts/hook-payload-guard.d.ts.map +1 -0
  288. package/dist/scripts/hook-payload-guard.js +111 -0
  289. package/dist/scripts/hook-payload-guard.js.map +1 -0
  290. package/dist/scripts/notify-fallback-watcher.js +8 -1
  291. package/dist/scripts/notify-fallback-watcher.js.map +1 -1
  292. package/dist/scripts/notify-hook/__tests__/payload-guard.test.d.ts +2 -0
  293. package/dist/scripts/notify-hook/__tests__/payload-guard.test.d.ts.map +1 -0
  294. package/dist/scripts/notify-hook/__tests__/payload-guard.test.js +39 -0
  295. package/dist/scripts/notify-hook/__tests__/payload-guard.test.js.map +1 -0
  296. package/dist/scripts/notify-hook/auto-nudge.d.ts.map +1 -1
  297. package/dist/scripts/notify-hook/auto-nudge.js +3 -1
  298. package/dist/scripts/notify-hook/auto-nudge.js.map +1 -1
  299. package/dist/scripts/notify-hook/ralph-session-resume.d.ts.map +1 -1
  300. package/dist/scripts/notify-hook/ralph-session-resume.js +3 -10
  301. package/dist/scripts/notify-hook/ralph-session-resume.js.map +1 -1
  302. package/dist/scripts/notify-hook/state-io.d.ts.map +1 -1
  303. package/dist/scripts/notify-hook/state-io.js +62 -38
  304. package/dist/scripts/notify-hook/state-io.js.map +1 -1
  305. package/dist/scripts/notify-hook/team-leader-nudge.d.ts.map +1 -1
  306. package/dist/scripts/notify-hook/team-leader-nudge.js +7 -0
  307. package/dist/scripts/notify-hook/team-leader-nudge.js.map +1 -1
  308. package/dist/scripts/notify-hook/team-worker-stop.d.ts.map +1 -1
  309. package/dist/scripts/notify-hook/team-worker-stop.js +234 -86
  310. package/dist/scripts/notify-hook/team-worker-stop.js.map +1 -1
  311. package/dist/scripts/notify-hook/tmux-injection.d.ts +7 -0
  312. package/dist/scripts/notify-hook/tmux-injection.d.ts.map +1 -1
  313. package/dist/scripts/notify-hook/tmux-injection.js +24 -18
  314. package/dist/scripts/notify-hook/tmux-injection.js.map +1 -1
  315. package/dist/scripts/notify-hook.js +86 -13
  316. package/dist/scripts/notify-hook.js.map +1 -1
  317. package/dist/scripts/run-test-files.js +193 -22
  318. package/dist/scripts/run-test-files.js.map +1 -1
  319. package/dist/scripts/sync-plugin-mirror.d.ts.map +1 -1
  320. package/dist/scripts/sync-plugin-mirror.js +61 -3
  321. package/dist/scripts/sync-plugin-mirror.js.map +1 -1
  322. package/dist/scripts/verify-native-agents.d.ts.map +1 -1
  323. package/dist/scripts/verify-native-agents.js +58 -1
  324. package/dist/scripts/verify-native-agents.js.map +1 -1
  325. package/dist/state/__tests__/operations.test.js +1125 -1
  326. package/dist/state/__tests__/operations.test.js.map +1 -1
  327. package/dist/state/__tests__/skill-active.test.js +46 -1
  328. package/dist/state/__tests__/skill-active.test.js.map +1 -1
  329. package/dist/state/__tests__/workflow-transition.test.js +98 -7
  330. package/dist/state/__tests__/workflow-transition.test.js.map +1 -1
  331. package/dist/state/operations.d.ts.map +1 -1
  332. package/dist/state/operations.js +159 -2
  333. package/dist/state/operations.js.map +1 -1
  334. package/dist/state/skill-active.js +6 -8
  335. package/dist/state/skill-active.js.map +1 -1
  336. package/dist/state/workflow-transition-reconcile.d.ts +6 -0
  337. package/dist/state/workflow-transition-reconcile.d.ts.map +1 -1
  338. package/dist/state/workflow-transition-reconcile.js +38 -15
  339. package/dist/state/workflow-transition-reconcile.js.map +1 -1
  340. package/dist/state/workflow-transition.d.ts.map +1 -1
  341. package/dist/state/workflow-transition.js +10 -3
  342. package/dist/state/workflow-transition.js.map +1 -1
  343. package/dist/subagents/__tests__/tracker.test.js +139 -0
  344. package/dist/subagents/__tests__/tracker.test.js.map +1 -1
  345. package/dist/subagents/tracker.d.ts +3 -0
  346. package/dist/subagents/tracker.d.ts.map +1 -1
  347. package/dist/subagents/tracker.js +41 -4
  348. package/dist/subagents/tracker.js.map +1 -1
  349. package/dist/team/__tests__/coordination-protocol.test.d.ts +2 -0
  350. package/dist/team/__tests__/coordination-protocol.test.d.ts.map +1 -0
  351. package/dist/team/__tests__/coordination-protocol.test.js +173 -0
  352. package/dist/team/__tests__/coordination-protocol.test.js.map +1 -0
  353. package/dist/team/__tests__/runtime.test.js +52 -3
  354. package/dist/team/__tests__/runtime.test.js.map +1 -1
  355. package/dist/team/__tests__/scaling.test.js +9 -4
  356. package/dist/team/__tests__/scaling.test.js.map +1 -1
  357. package/dist/team/__tests__/state.test.js +83 -0
  358. package/dist/team/__tests__/state.test.js.map +1 -1
  359. package/dist/team/__tests__/tmux-session.test.js +240 -2
  360. package/dist/team/__tests__/tmux-session.test.js.map +1 -1
  361. package/dist/team/__tests__/worker-bootstrap.test.js +84 -0
  362. package/dist/team/__tests__/worker-bootstrap.test.js.map +1 -1
  363. package/dist/team/__tests__/worker-runtime-identity.test.js +4 -2
  364. package/dist/team/__tests__/worker-runtime-identity.test.js.map +1 -1
  365. package/dist/team/coordination-protocol.d.ts +14 -0
  366. package/dist/team/coordination-protocol.d.ts.map +1 -0
  367. package/dist/team/coordination-protocol.js +244 -0
  368. package/dist/team/coordination-protocol.js.map +1 -0
  369. package/dist/team/runtime.d.ts +1 -0
  370. package/dist/team/runtime.d.ts.map +1 -1
  371. package/dist/team/runtime.js +19 -3
  372. package/dist/team/runtime.js.map +1 -1
  373. package/dist/team/scaling.d.ts.map +1 -1
  374. package/dist/team/scaling.js +3 -2
  375. package/dist/team/scaling.js.map +1 -1
  376. package/dist/team/state/tasks.d.ts.map +1 -1
  377. package/dist/team/state/tasks.js +24 -0
  378. package/dist/team/state/tasks.js.map +1 -1
  379. package/dist/team/state/types.d.ts +21 -1
  380. package/dist/team/state/types.d.ts.map +1 -1
  381. package/dist/team/state/types.js.map +1 -1
  382. package/dist/team/state.d.ts +17 -1
  383. package/dist/team/state.d.ts.map +1 -1
  384. package/dist/team/state.js +12 -5
  385. package/dist/team/state.js.map +1 -1
  386. package/dist/team/team-ops.d.ts +1 -1
  387. package/dist/team/team-ops.d.ts.map +1 -1
  388. package/dist/team/team-ops.js.map +1 -1
  389. package/dist/team/tmux-session.d.ts +2 -0
  390. package/dist/team/tmux-session.d.ts.map +1 -1
  391. package/dist/team/tmux-session.js +161 -13
  392. package/dist/team/tmux-session.js.map +1 -1
  393. package/dist/team/worker-bootstrap.d.ts.map +1 -1
  394. package/dist/team/worker-bootstrap.js +63 -0
  395. package/dist/team/worker-bootstrap.js.map +1 -1
  396. package/dist/utils/__tests__/agents-model-table.test.js +4 -2
  397. package/dist/utils/__tests__/agents-model-table.test.js.map +1 -1
  398. package/dist/utils/agents-model-table.d.ts.map +1 -1
  399. package/dist/utils/agents-model-table.js +3 -0
  400. package/dist/utils/agents-model-table.js.map +1 -1
  401. package/dist/verification/__tests__/ci-rust-gates.test.js +81 -1
  402. package/dist/verification/__tests__/ci-rust-gates.test.js.map +1 -1
  403. package/package.json +8 -8
  404. package/plugins/oh-my-codex/.codex-plugin/plugin.json +1 -1
  405. package/plugins/oh-my-codex/hooks/codex-native-hook.mjs +334 -21
  406. package/plugins/oh-my-codex/hooks/hooks.json +1 -2
  407. package/plugins/oh-my-codex/skills/autopilot/SKILL.md +13 -6
  408. package/plugins/oh-my-codex/skills/code-review/SKILL.md +7 -7
  409. package/plugins/oh-my-codex/skills/deep-interview/SKILL.md +9 -4
  410. package/plugins/oh-my-codex/skills/ralph/SKILL.md +22 -22
  411. package/plugins/oh-my-codex/skills/ralplan/SKILL.md +12 -0
  412. package/plugins/oh-my-codex/skills/team/SKILL.md +16 -0
  413. package/plugins/oh-my-codex/skills/ultraqa/SKILL.md +9 -0
  414. package/plugins/oh-my-codex/skills/worker/SKILL.md +14 -0
  415. package/skills/autopilot/SKILL.md +13 -6
  416. package/skills/code-review/SKILL.md +7 -7
  417. package/skills/deep-interview/SKILL.md +9 -4
  418. package/skills/ralph/SKILL.md +22 -22
  419. package/skills/ralplan/SKILL.md +12 -0
  420. package/skills/team/SKILL.md +16 -0
  421. package/skills/ultraqa/SKILL.md +9 -0
  422. package/skills/worker/SKILL.md +14 -0
  423. package/src/scripts/__tests__/codex-native-hook.test.ts +4435 -2083
  424. package/src/scripts/__tests__/notify-state-io.test.ts +95 -0
  425. package/src/scripts/__tests__/notify-tmux-injection.test.ts +82 -0
  426. package/src/scripts/__tests__/run-test-files.test.ts +102 -0
  427. package/src/scripts/__tests__/verify-native-agents.test.ts +75 -0
  428. package/src/scripts/codex-native-hook.ts +536 -51
  429. package/src/scripts/codex-native-pre-post.ts +80 -0
  430. package/src/scripts/demo-team-e2e.sh +10 -7
  431. package/src/scripts/eval/eval-parity-smoke.ts +1 -1
  432. package/src/scripts/hook-payload-guard.ts +113 -0
  433. package/src/scripts/notify-fallback-watcher.ts +8 -1
  434. package/src/scripts/notify-hook/__tests__/payload-guard.test.ts +41 -0
  435. package/src/scripts/notify-hook/auto-nudge.ts +3 -1
  436. package/src/scripts/notify-hook/ralph-session-resume.ts +2 -8
  437. package/src/scripts/notify-hook/state-io.ts +75 -37
  438. package/src/scripts/notify-hook/team-leader-nudge.ts +7 -0
  439. package/src/scripts/notify-hook/team-worker-stop.ts +193 -52
  440. package/src/scripts/notify-hook/tmux-injection.ts +35 -19
  441. package/src/scripts/notify-hook.ts +105 -6
  442. package/src/scripts/run-test-files.ts +192 -22
  443. package/src/scripts/sync-plugin-mirror.ts +98 -9
  444. package/src/scripts/verify-native-agents.ts +65 -1
@@ -6,11 +6,12 @@
6
6
  * It must not depend on idle/heartbeat freshness or inferred progress stalls.
7
7
  */
8
8
 
9
- import { appendFile, mkdir, rename, writeFile } from 'fs/promises';
10
- import { dirname, join } from 'path';
9
+ import { existsSync } from 'fs';
10
+ import { appendFile, mkdir, rename, rm, stat, writeFile } from 'fs/promises';
11
+ import { dirname, join, relative } from 'path';
11
12
  import { DEFAULT_MARKER, paneHasActiveTask } from '../tmux-hook-engine.js';
12
13
  import { appendTeamDeliveryLog } from '../../team/delivery-log.js';
13
- import { safeString, asNumber } from './utils.js';
14
+ import { safeString, asNumber, isTerminalPhase } from './utils.js';
14
15
  import { readJsonIfExists } from './state-io.js';
15
16
  import { logTmuxHookEvent } from './log.js';
16
17
  import { evaluatePaneInjectionReadiness, queuePaneInput, sendPaneInput } from './team-tmux-guard.js';
@@ -21,6 +22,110 @@ const STOP_NUDGE_COOLDOWN_MS = 30_000;
21
22
  const SOURCE_TYPE = 'worker_stop';
22
23
  const LEADER_PANE_MISSING_NO_INJECTION_REASON = 'leader_pane_missing_no_injection';
23
24
  const LEADER_PANE_SHELL_NO_INJECTION_REASON = 'leader_pane_shell_no_injection';
25
+ const TEAM_SHUTDOWN_NO_INJECTION_REASON = 'team_state_gone_or_shutdown';
26
+ const TEAM_LOCK_HELD_REASON = 'suppressed_team_lock_held';
27
+
28
+ async function teamStateAllowsWorkerStopNudge(stateDir, teamName) {
29
+ const teamDir = join(stateDir, 'team', teamName);
30
+ if (!existsSync(teamDir)) return false;
31
+ if (existsSync(join(teamDir, 'shutdown.json'))) return false;
32
+
33
+ const phase = await readJsonIfExists(join(teamDir, 'phase.json'), null);
34
+ const currentPhase = safeString(phase?.current_phase || phase?.phase || '').trim();
35
+ if (currentPhase && isTerminalPhase(currentPhase)) return false;
36
+
37
+ return true;
38
+ }
39
+
40
+ async function acquireTeamStopNudgeLock(teamDir, nowMs, cooldownMs) {
41
+ const lockDir = join(teamDir, 'worker-stop-nudge.lock');
42
+ const staleAfterMs = Math.max(cooldownMs, 30_000);
43
+
44
+ for (let attempt = 0; attempt < 2; attempt += 1) {
45
+ try {
46
+ await mkdir(lockDir);
47
+ await writeFile(join(lockDir, 'owner.json'), JSON.stringify({
48
+ pid: process.pid,
49
+ acquired_at_ms: nowMs,
50
+ acquired_at: new Date(nowMs).toISOString(),
51
+ }, null, 2)).catch(() => {});
52
+ return { acquired: true, lockDir };
53
+ } catch (error) {
54
+ if (error?.code === 'ENOENT') return { acquired: false, reason: TEAM_SHUTDOWN_NO_INJECTION_REASON };
55
+ if (error?.code !== 'EEXIST') return { acquired: false, reason: TEAM_LOCK_HELD_REASON };
56
+
57
+ try {
58
+ const lockStat = await stat(lockDir);
59
+ if ((nowMs - lockStat.mtimeMs) >= staleAfterMs) {
60
+ await rm(lockDir, { recursive: true, force: true });
61
+ continue;
62
+ }
63
+ } catch {
64
+ continue;
65
+ }
66
+ return { acquired: false, reason: TEAM_LOCK_HELD_REASON };
67
+ }
68
+ }
69
+
70
+ return { acquired: false, reason: TEAM_LOCK_HELD_REASON };
71
+ }
72
+
73
+ async function releaseTeamStopNudgeLock(lockDir) {
74
+ if (!lockDir) return;
75
+ await rm(lockDir, { recursive: true, force: true }).catch(() => {});
76
+ }
77
+
78
+ async function ensureDirectoryUnderExistingTeam(teamDir, targetDir) {
79
+ if (!existsSync(teamDir)) return false;
80
+ const rel = relative(teamDir, targetDir);
81
+ if (!rel) return true;
82
+ if (rel.startsWith('..')) throw new Error('state_path_outside_team_dir');
83
+
84
+ let current = teamDir;
85
+ for (const segment of rel.split(/[\\/]+/).filter(Boolean)) {
86
+ if (!existsSync(teamDir)) return false;
87
+ current = join(current, segment);
88
+ try {
89
+ await mkdir(current);
90
+ } catch (error) {
91
+ if (error?.code === 'ENOENT' && !existsSync(teamDir)) return false;
92
+ if (error?.code !== 'EEXIST') throw error;
93
+ const currentStat = await stat(current);
94
+ if (!currentStat.isDirectory()) throw error;
95
+ }
96
+ }
97
+ return existsSync(teamDir);
98
+ }
99
+
100
+ async function writeStopNudgeStateIfTeamExists(teamDir, statePath, state) {
101
+ const stateDir = dirname(statePath);
102
+ const ready = await ensureDirectoryUnderExistingTeam(teamDir, stateDir);
103
+ if (!ready) return false;
104
+ try {
105
+ const tmpPath = `${statePath}.tmp.${process.pid}`;
106
+ await writeFile(tmpPath, JSON.stringify(state, null, 2));
107
+ await rename(tmpPath, statePath);
108
+ } catch (error) {
109
+ if (error?.code === 'ENOENT' && !existsSync(teamDir)) return false;
110
+ throw error;
111
+ }
112
+ return existsSync(teamDir);
113
+ }
114
+
115
+ async function appendWorkerStopEventIfTeamExists(stateDir, teamName, event) {
116
+ const teamDir = join(stateDir, 'team', teamName);
117
+ const eventsDir = join(teamDir, 'events');
118
+ const ready = await ensureDirectoryUnderExistingTeam(teamDir, eventsDir);
119
+ if (!ready) return false;
120
+ const eventsPath = join(eventsDir, 'events.ndjson');
121
+ try {
122
+ await appendFile(eventsPath, JSON.stringify(event) + '\n');
123
+ } catch (error) {
124
+ if (error?.code === 'ENOENT' && !existsSync(teamDir)) return false;
125
+ throw error;
126
+ }
127
+ return existsSync(teamDir);
128
+ }
24
129
 
25
130
  function resolveWorkerStopCooldownMs() {
26
131
  const raw = safeString(process.env.OMX_TEAM_WORKER_STOP_COOLDOWN_MS || '');
@@ -42,20 +147,6 @@ async function resolveCanonicalLeaderPaneId(leaderPaneId) {
42
147
  return normalizedLeaderPaneId;
43
148
  }
44
149
 
45
- async function appendWorkerStopEvent(stateDir, teamName, event) {
46
- const eventsDir = join(stateDir, 'team', teamName, 'events');
47
- const eventsPath = join(eventsDir, 'events.ndjson');
48
- await mkdir(eventsDir, { recursive: true }).catch(() => {});
49
- await appendFile(eventsPath, JSON.stringify(event) + '\n').catch(() => {});
50
- }
51
-
52
- async function writeStopNudgeState(statePath, state) {
53
- await mkdir(dirname(statePath), { recursive: true }).catch(() => {});
54
- const tmpPath = `${statePath}.tmp.${process.pid}`;
55
- await writeFile(tmpPath, JSON.stringify(state, null, 2));
56
- await rename(tmpPath, statePath);
57
- }
58
-
59
150
  async function recordDeferred({
60
151
  stateDir,
61
152
  logsDir,
@@ -69,23 +160,26 @@ async function recordDeferred({
69
160
  paneCurrentCommand = '',
70
161
  }) {
71
162
  const nowIso = nextState.last_notified_at;
72
- await writeStopNudgeState(statePath, {
163
+ const teamDir = join(stateDir, 'team', teamName);
164
+ const wroteState = await writeStopNudgeStateIfTeamExists(teamDir, statePath, {
73
165
  ...nextState,
74
166
  delivery: 'deferred',
75
167
  reason,
76
168
  pane_current_command: paneCurrentCommand || null,
77
169
  }).catch(() => {});
78
- await appendWorkerStopEvent(stateDir, teamName, {
79
- event_id: `worker-stop-deferred-${Date.now()}-${Math.random().toString(16).slice(2, 8)}`,
80
- team: teamName,
81
- type: 'worker_stop_leader_nudge',
82
- worker: workerName,
83
- to_worker: 'leader-fixed',
84
- delivery: 'deferred',
85
- reason,
86
- created_at: nowIso,
87
- source_type: SOURCE_TYPE,
88
- });
170
+ if (wroteState) {
171
+ await appendWorkerStopEventIfTeamExists(stateDir, teamName, {
172
+ event_id: `worker-stop-deferred-${Date.now()}-${Math.random().toString(16).slice(2, 8)}`,
173
+ team: teamName,
174
+ type: 'worker_stop_leader_nudge',
175
+ worker: workerName,
176
+ to_worker: 'leader-fixed',
177
+ delivery: 'deferred',
178
+ reason,
179
+ created_at: nowIso,
180
+ source_type: SOURCE_TYPE,
181
+ }).catch(() => {});
182
+ }
89
183
  await logTmuxHookEvent(logsDir, {
90
184
  timestamp: nowIso,
91
185
  type: 'leader_notification_deferred',
@@ -119,30 +213,58 @@ export async function maybeNudgeLeaderForAllowedWorkerStop({
119
213
  const { teamName, workerName } = workerContext || {};
120
214
  if (!teamName || !workerName || !stateDir) return { ok: false, result: 'unresolved' };
121
215
 
122
- const workerDir = join(stateDir, 'team', teamName, 'workers', workerName);
216
+ const teamDir = join(stateDir, 'team', teamName);
217
+ const workerDir = join(teamDir, 'workers', workerName);
123
218
  const statePath = join(workerDir, 'worker-stop-nudge.json');
219
+ const teamStatePath = join(teamDir, 'worker-stop-nudge.json');
124
220
  const nowMs = Date.now();
125
221
  const nowIso = new Date(nowMs).toISOString();
222
+ const cooldownMs = resolveWorkerStopCooldownMs();
223
+ const nextState = {
224
+ last_notified_at_ms: nowMs,
225
+ last_notified_at: nowIso,
226
+ team: teamName,
227
+ worker: workerName,
228
+ source_type: SOURCE_TYPE,
229
+ };
230
+ let tmuxSession = '';
231
+ let leaderPaneId = '';
232
+
233
+ if (!(await teamStateAllowsWorkerStopNudge(stateDir, teamName))) {
234
+ return { ok: true, result: TEAM_SHUTDOWN_NO_INJECTION_REASON };
235
+ }
236
+
237
+ const lock = await acquireTeamStopNudgeLock(teamDir, nowMs, cooldownMs);
238
+ if (!lock.acquired) {
239
+ return { ok: true, result: lock.reason || TEAM_LOCK_HELD_REASON };
240
+ }
241
+
242
+ try {
243
+ if (!(await teamStateAllowsWorkerStopNudge(stateDir, teamName))) {
244
+ return { ok: true, result: TEAM_SHUTDOWN_NO_INJECTION_REASON };
245
+ }
246
+
247
+ const teamExisting = (await readJsonIfExists(teamStatePath, null)) || {};
248
+ const teamLastNotifiedMs = asNumber(teamExisting.last_notified_at_ms) ?? 0;
249
+ if ((nowMs - teamLastNotifiedMs) < cooldownMs) {
250
+ return { ok: true, result: 'suppressed_team_cooldown' };
251
+ }
252
+
126
253
  const existing = (await readJsonIfExists(statePath, null)) || {};
127
254
  const lastNotifiedMs = asNumber(existing.last_notified_at_ms) ?? 0;
128
- const cooldownMs = resolveWorkerStopCooldownMs();
129
255
  if ((nowMs - lastNotifiedMs) < cooldownMs) {
130
256
  return { ok: true, result: 'suppressed_cooldown' };
131
257
  }
132
258
 
133
259
  const teamInfo = await readTeamWorkersForIdleCheck(stateDir, teamName);
134
260
  if (!teamInfo) return { ok: false, result: 'unresolved' };
135
- const { tmuxSession, leaderPaneId } = teamInfo;
261
+ ({ tmuxSession, leaderPaneId } = teamInfo);
136
262
  const tmuxTarget = await resolveCanonicalLeaderPaneId(leaderPaneId);
137
- const nextState = {
138
- last_notified_at_ms: nowMs,
139
- last_notified_at: nowIso,
140
- team: teamName,
141
- worker: workerName,
142
- source_type: SOURCE_TYPE,
143
- };
144
263
 
145
264
  if (!tmuxTarget) {
265
+ if (!(await teamStateAllowsWorkerStopNudge(stateDir, teamName))) {
266
+ return { ok: true, result: TEAM_SHUTDOWN_NO_INJECTION_REASON };
267
+ }
146
268
  await recordDeferred({
147
269
  stateDir,
148
270
  logsDir,
@@ -164,6 +286,9 @@ export async function maybeNudgeLeaderForAllowedWorkerStop({
164
286
  requireIdle: false,
165
287
  });
166
288
  if (!paneGuard.ok) {
289
+ if (!(await teamStateAllowsWorkerStopNudge(stateDir, teamName))) {
290
+ return { ok: true, result: TEAM_SHUTDOWN_NO_INJECTION_REASON };
291
+ }
167
292
  await recordDeferred({
168
293
  stateDir,
169
294
  logsDir,
@@ -179,12 +304,15 @@ export async function maybeNudgeLeaderForAllowedWorkerStop({
179
304
  return { ok: true, result: 'deferred' };
180
305
  }
181
306
 
307
+ if (!(await teamStateAllowsWorkerStopNudge(stateDir, teamName))) {
308
+ return { ok: true, result: TEAM_SHUTDOWN_NO_INJECTION_REASON };
309
+ }
310
+
182
311
  const prompt =
183
312
  `[OMX] ${workerName} native Stop allowed. `
184
313
  + `Run \`omx team status ${teamName}\`, read worker messages/results, then assign next task, reconcile completion, or shut down. `
185
314
  + DEFAULT_MARKER;
186
315
 
187
- try {
188
316
  const leaderHasActiveTask = paneHasActiveTask(paneGuard.paneCapture);
189
317
  let deliveryMode = 'sent';
190
318
  if (leaderHasActiveTask) {
@@ -204,22 +332,28 @@ export async function maybeNudgeLeaderForAllowedWorkerStop({
204
332
  if (!sendResult.ok) throw new Error(sendResult.error || sendResult.reason || 'send_failed');
205
333
  }
206
334
 
207
- await writeStopNudgeState(statePath, {
335
+ const deliveryState = {
208
336
  ...nextState,
209
337
  delivery: deliveryMode,
210
338
  leader_pane_id: leaderPaneId || null,
211
339
  tmux_target: tmuxTarget,
212
- });
213
- await appendWorkerStopEvent(stateDir, teamName, {
214
- event_id: `worker-stop-nudge-${Date.now()}-${Math.random().toString(16).slice(2, 8)}`,
215
- team: teamName,
216
- type: 'worker_stop_leader_nudge',
217
- worker: workerName,
218
- to_worker: 'leader-fixed',
219
- delivery: deliveryMode,
220
- created_at: nowIso,
221
- source_type: SOURCE_TYPE,
222
- });
340
+ };
341
+ const wroteWorkerState = await writeStopNudgeStateIfTeamExists(teamDir, statePath, deliveryState);
342
+ const wroteTeamState = wroteWorkerState
343
+ ? await writeStopNudgeStateIfTeamExists(teamDir, teamStatePath, deliveryState)
344
+ : false;
345
+ if (wroteTeamState) {
346
+ await appendWorkerStopEventIfTeamExists(stateDir, teamName, {
347
+ event_id: `worker-stop-nudge-${Date.now()}-${Math.random().toString(16).slice(2, 8)}`,
348
+ team: teamName,
349
+ type: 'worker_stop_leader_nudge',
350
+ worker: workerName,
351
+ to_worker: 'leader-fixed',
352
+ delivery: deliveryMode,
353
+ created_at: nowIso,
354
+ source_type: SOURCE_TYPE,
355
+ }).catch(() => {});
356
+ }
223
357
  await logTmuxHookEvent(logsDir, {
224
358
  timestamp: nowIso,
225
359
  type: 'worker_stop_leader_nudge',
@@ -241,6 +375,11 @@ export async function maybeNudgeLeaderForAllowedWorkerStop({
241
375
  }).catch(() => {});
242
376
  return { ok: true, result: deliveryMode };
243
377
  } catch (err) {
378
+ if (!(await teamStateAllowsWorkerStopNudge(stateDir, teamName))) {
379
+ return { ok: true, result: TEAM_SHUTDOWN_NO_INJECTION_REASON };
380
+ }
381
+ // Worker Stop is already allowed before this helper runs; nudge failures are
382
+ // surfaced as deferred operational evidence instead of re-blocking Stop.
244
383
  await recordDeferred({
245
384
  stateDir,
246
385
  logsDir,
@@ -253,5 +392,7 @@ export async function maybeNudgeLeaderForAllowedWorkerStop({
253
392
  leaderPaneId,
254
393
  });
255
394
  return { ok: true, result: 'deferred' };
395
+ } finally {
396
+ await releaseTeamStopNudgeLock(lock.lockDir);
256
397
  }
257
398
  }
@@ -21,7 +21,7 @@ import { runProcess } from './process-runner.js';
21
21
  import { logTmuxHookEvent } from './log.js';
22
22
  import { resolveInvocationSessionId, resolveManagedCurrentPane, resolveManagedSessionContext, verifyManagedPaneTarget } from './managed-tmux.js';
23
23
  import { evaluatePaneInjectionReadiness, mapPaneInjectionReadinessReason, sendPaneInput } from './team-tmux-guard.js';
24
- import { listActiveSkills, readVisibleSkillActiveState } from '../../state/skill-active.js';
24
+ import { listActiveSkills, readVisibleSkillActiveStateForStateDir } from '../../state/skill-active.js';
25
25
  import {
26
26
  normalizeTmuxHookConfig,
27
27
  pickActiveMode,
@@ -99,12 +99,14 @@ async function resolveCanonicalPaneFromPaneTarget(paneTarget: any, expectedCwd:
99
99
  return finalizeResolvedPane(healedPaneId, 'healed_hud_pane_target', expectedCwd);
100
100
  }
101
101
 
102
- async function resolvePreferredModePane(stateDir: string, allowedModes: string[]): Promise<{ mode: string; state: any; pane: string; stateDir: string } | null> {
103
- const scopedDirs = await getScopedStateDirsForCurrentSession(stateDir).catch(() => [stateDir]);
104
- const dirs = [...scopedDirs];
105
- if (!dirs.map((dir) => resolvePath(dir)).includes(resolvePath(stateDir))) {
106
- dirs.push(stateDir);
107
- }
102
+ async function resolvePreferredModePane(
103
+ stateDir: string,
104
+ allowedModes: string[],
105
+ options: { includeRootFallback?: boolean } = {},
106
+ ): Promise<{ mode: string; state: any; pane: string; stateDir: string } | null> {
107
+ const dirs = await getScopedStateDirsForCurrentSession(stateDir, undefined, {
108
+ includeRootFallback: options.includeRootFallback !== false,
109
+ }).catch(() => [stateDir]);
108
110
  for (const dir of dirs) {
109
111
  for (const mode of allowedModes || []) {
110
112
  const path = join(dir, `${mode}-state.json`);
@@ -189,12 +191,18 @@ async function validateResolvedInjectionOwnership({
189
191
  return { ok: true };
190
192
  }
191
193
 
192
- async function readVisibleAllowedModes(
194
+ export async function readVisibleAllowedModes(
193
195
  cwd: string,
194
196
  stateDir: string,
195
197
  payload: any,
196
198
  allowedModes: string[],
197
- ): Promise<{ canonicalPresent: boolean; allowedSet: Set<string> | null; preferredMode: string | null }> {
199
+ ): Promise<{
200
+ canonicalPresent: boolean;
201
+ activeSkillCount: number;
202
+ allowedSet: Set<string> | null;
203
+ preferredMode: string | null;
204
+ sessionScoped: boolean;
205
+ }> {
198
206
  const candidateSessionIds = [
199
207
  await readCurrentSessionId(stateDir).catch(() => undefined),
200
208
  resolveInvocationSessionId(payload),
@@ -203,41 +211,49 @@ async function readVisibleAllowedModes(
203
211
  .filter(Boolean);
204
212
 
205
213
  for (const sessionId of candidateSessionIds) {
206
- const canonicalState = await readVisibleSkillActiveState(cwd, sessionId);
214
+ const canonicalState = await readVisibleSkillActiveStateForStateDir(stateDir, sessionId);
207
215
  if (!canonicalState) continue;
208
216
 
217
+ const activeSkills = listActiveSkills(canonicalState);
209
218
  const allowedSet = new Set(
210
- listActiveSkills(canonicalState)
219
+ activeSkills
211
220
  .map((entry) => entry.skill)
212
221
  .filter((skill) => allowedModes.includes(skill)),
213
222
  );
214
223
  return {
215
224
  canonicalPresent: true,
225
+ activeSkillCount: activeSkills.length,
216
226
  allowedSet,
217
227
  preferredMode: pickActiveMode([...allowedSet], allowedModes),
228
+ sessionScoped: true,
218
229
  };
219
230
  }
220
231
 
221
232
  if (candidateSessionIds.length === 0) {
222
- const rootCanonicalState = await readVisibleSkillActiveState(cwd).catch(() => null);
233
+ const rootCanonicalState = await readVisibleSkillActiveStateForStateDir(stateDir).catch(() => null);
223
234
  if (rootCanonicalState) {
235
+ const activeSkills = listActiveSkills(rootCanonicalState);
224
236
  const allowedSet = new Set(
225
- listActiveSkills(rootCanonicalState)
237
+ activeSkills
226
238
  .map((entry) => entry.skill)
227
239
  .filter((skill) => allowedModes.includes(skill)),
228
240
  );
229
241
  return {
230
242
  canonicalPresent: true,
243
+ activeSkillCount: activeSkills.length,
231
244
  allowedSet,
232
245
  preferredMode: pickActiveMode([...allowedSet], allowedModes),
246
+ sessionScoped: false,
233
247
  };
234
248
  }
235
249
  }
236
250
 
237
251
  return {
238
252
  canonicalPresent: false,
253
+ activeSkillCount: 0,
239
254
  allowedSet: null,
240
255
  preferredMode: null,
256
+ sessionScoped: candidateSessionIds.length > 0,
241
257
  };
242
258
  }
243
259
 
@@ -415,10 +431,11 @@ export async function handleTmuxInjection({
415
431
  state.recent_keys = pruneRecentKeys(state.recent_keys, now);
416
432
  const canonicalModeState = await readVisibleAllowedModes(cwd, stateDir, payload, config.allowed_modes).catch(() => ({
417
433
  canonicalPresent: false,
434
+ activeSkillCount: 0,
418
435
  allowedSet: null,
419
436
  preferredMode: null,
420
437
  }));
421
- if (canonicalModeState.canonicalPresent && !canonicalModeState.preferredMode) {
438
+ if (canonicalModeState.canonicalPresent && canonicalModeState.activeSkillCount > 0 && !canonicalModeState.preferredMode) {
422
439
  const nextState = {
423
440
  ...state,
424
441
  last_reason: 'mode_not_allowed',
@@ -468,12 +485,10 @@ export async function handleTmuxInjection({
468
485
  }
469
486
  };
470
487
  try {
471
- const scopedDirs = await getScopedStateDirsForCurrentSession(stateDir);
488
+ const scopedDirs = await getScopedStateDirsForCurrentSession(stateDir, undefined, {
489
+ includeRootFallback: !canonicalModeState.canonicalPresent && !canonicalModeState.sessionScoped,
490
+ });
472
491
  await scanActiveModeStateDirs(scopedDirs);
473
-
474
- if (!pickActiveMode(activeModes, config.allowed_modes) && !scannedStateDirs.has(resolvePath(stateDir))) {
475
- await scanActiveModeStateDirs([stateDir], true);
476
- }
477
492
  } catch {
478
493
  // Non-fatal
479
494
  }
@@ -483,6 +498,7 @@ export async function handleTmuxInjection({
483
498
  canonicalModeState.canonicalPresent
484
499
  ? (canonicalModeState.preferredMode ? [canonicalModeState.preferredMode] : [])
485
500
  : config.allowed_modes,
501
+ { includeRootFallback: !canonicalModeState.sessionScoped },
486
502
  ).catch(() => null);
487
503
  const mode = canonicalModeState.canonicalPresent
488
504
  ? canonicalModeState.preferredMode
@@ -68,6 +68,11 @@ import {
68
68
  } from './notify-hook/team-worker.js';
69
69
  import { DEFAULT_MARKER } from './tmux-hook-engine.js';
70
70
  import { sameFilePath } from '../utils/paths.js';
71
+ import {
72
+ MAX_NOTIFY_ARGV_JSON_BYTES,
73
+ extractRawJsonStringField,
74
+ utf8ByteLength,
75
+ } from './hook-payload-guard.js';
71
76
 
72
77
  const RALPH_ACTIVE_PROGRESS_PHASES = new Set([
73
78
  'start',
@@ -257,6 +262,78 @@ function classifyIdleNotificationPhase(message: unknown): 'idle' | 'progress' |
257
262
  return 'idle';
258
263
  }
259
264
 
265
+
266
+ function isExplicitAutopilotActivationText(text: string): boolean {
267
+ return /(?:^|[^\w])\$autopilot\b/i.test(text)
268
+ || /^\s*\/autopilot\b/i.test(text)
269
+ || /^\s*(?:please\s+)?autopilot(?:\s+(?:this|mode|workflow|skill|loop|now))?\s*[.!]?\s*$/i.test(text)
270
+ || /\b(?:use|run|start|enable|launch|invoke|activate|resume|continue)\s+(?:the\s+)?autopilot(?:\s+(?:mode|workflow|skill|loop|now))?\s*[.!]?\s*$/i.test(text)
271
+ || /\bautopilot\s+(?:mode|workflow|skill|loop)\b/i.test(text);
272
+ }
273
+
274
+ function looksLikeAutopilotTerminalHandoff(text: string): boolean {
275
+ return /\bAutopilot complete\b/i.test(text)
276
+ || /\btask_complete\b/i.test(text)
277
+ || /\bautopilot\b[\s\S]{0,120}\b(?:complete|completed|finished)\b/i.test(text);
278
+ }
279
+
280
+ function isTerminalModeStateObject(value: unknown, mode: string): boolean {
281
+ if (!value || typeof value !== 'object' || Array.isArray(value)) return false;
282
+ const state = value as Record<string, unknown>;
283
+ if (safeString(state.mode).trim() !== mode) return false;
284
+ if (state.active === true) return false;
285
+ const phase = safeString(state.current_phase || state.currentPhase).trim().toLowerCase().replace(/_/g, '-');
286
+ if (['complete', 'completed', 'failed', 'cancelled', 'canceled', 'stopped', 'user-stopped'].includes(phase)) return true;
287
+ const outcome = safeString(state.run_outcome || state.outcome || state.lifecycle_outcome || state.terminal_outcome).trim().toLowerCase();
288
+ return ['finish', 'finished', 'complete', 'completed', 'failed', 'cancelled', 'canceled'].includes(outcome)
289
+ || safeString(state.completed_at || state.completedAt).trim() !== '';
290
+ }
291
+
292
+ function terminalStateMatchesNotifyTurn(state: Record<string, unknown>, payload: Record<string, unknown>): boolean {
293
+ const payloadTurnId = safeString(payload['turn-id'] || payload.turn_id || '').trim();
294
+ const stateTurnId = safeString(state.turn_id || state.turnId || '').trim();
295
+ const payloadThreadId = safeString(payload['thread-id'] || payload.thread_id || '').trim();
296
+ const stateThreadId = safeString(state.thread_id || state.threadId || '').trim();
297
+
298
+ if (payloadTurnId || stateTurnId) {
299
+ if (!payloadTurnId || !stateTurnId || payloadTurnId !== stateTurnId) return false;
300
+ return !payloadThreadId || !stateThreadId || payloadThreadId === stateThreadId;
301
+ }
302
+
303
+ return Boolean(payloadThreadId && stateThreadId && payloadThreadId === stateThreadId);
304
+ }
305
+
306
+ async function hasTerminalAutopilotStateForNotifyTurn(
307
+ stateDir: string,
308
+ sessionId: string,
309
+ payload: Record<string, unknown>,
310
+ ): Promise<boolean> {
311
+ const state = await readScopedJsonIfExists(
312
+ stateDir,
313
+ 'autopilot-state.json',
314
+ sessionId || undefined,
315
+ null,
316
+ { includeRootFallback: true },
317
+ );
318
+ return isTerminalModeStateObject(state, 'autopilot')
319
+ && terminalStateMatchesNotifyTurn(state as Record<string, unknown>, payload);
320
+ }
321
+
322
+ async function shouldSuppressAutopilotTerminalReplayActivation(
323
+ stateDir: string,
324
+ payload: Record<string, unknown>,
325
+ isAutopilotActivation: boolean,
326
+ sessionId: string,
327
+ ): Promise<boolean> {
328
+ if (!isTurnCompletePayload(payload) && !isNotifyFallbackTaskCompletePayload(payload)) return false;
329
+ if (!isAutopilotActivation) return false;
330
+
331
+ const lastAssistantMessage = safeString(payload['last-assistant-message'] || payload.last_assistant_message || '');
332
+ if (!looksLikeAutopilotTerminalHandoff(lastAssistantMessage) && !isNotifyFallbackTaskCompletePayload(payload)) return false;
333
+
334
+ return hasTerminalAutopilotStateForNotifyTurn(stateDir, sessionId, payload);
335
+ }
336
+
260
337
  function buildIdleNotificationFingerprint(payload: Record<string, unknown>): string {
261
338
  const lastAssistantMessage = safeString(payload['last-assistant-message'] || payload.last_assistant_message || '');
262
339
  const summary = summarizeIdleNotificationMessage(lastAssistantMessage);
@@ -285,6 +362,9 @@ async function main() {
285
362
  if (!rawPayload || rawPayload.startsWith('-')) {
286
363
  process.exit(0);
287
364
  }
365
+ if (utf8ByteLength(rawPayload) > MAX_NOTIFY_ARGV_JSON_BYTES) {
366
+ process.exit(0);
367
+ }
288
368
 
289
369
  let payload;
290
370
  try {
@@ -633,16 +713,28 @@ async function main() {
633
713
 
634
714
  // 4.45. Skill activation tracking: update skill-active-state.json before any nudge logic.
635
715
  try {
636
- const { recordSkillActivation } = await import('../hooks/keyword-detector.js');
716
+ const { detectKeywords, recordSkillActivation } = await import('../hooks/keyword-detector.js');
637
717
  if (latestUserInput) {
718
+ const activationSessionId = getEffectiveSessionId();
719
+ const isAutopilotActivation = detectKeywords(latestUserInput)
720
+ .some((match) => match.skill === 'autopilot')
721
+ || isExplicitAutopilotActivationText(latestUserInput);
722
+ const suppressTerminalReplay = await shouldSuppressAutopilotTerminalReplayActivation(
723
+ stateDir,
724
+ payload,
725
+ isAutopilotActivation,
726
+ activationSessionId,
727
+ );
728
+ if (!suppressTerminalReplay) {
638
729
  await recordSkillActivation({
639
730
  stateDir,
640
731
  sourceCwd: cwd,
641
732
  text: latestUserInput,
642
- sessionId: getEffectiveSessionId(),
733
+ sessionId: activationSessionId,
643
734
  threadId: payloadThreadId,
644
735
  turnId: safeString(payload['turn-id'] || payload.turn_id || ''),
645
736
  });
737
+ }
646
738
  }
647
739
  } catch {
648
740
  // Non-fatal: keyword detector module may not be built yet
@@ -654,8 +746,11 @@ async function main() {
654
746
  // Non-fatal: lifecycle sync should not block the hook
655
747
  }
656
748
 
657
- const deepInterviewStateActive = await isDeepInterviewStateActive(stateDir, getEffectiveSessionId());
658
- const deepInterviewInputLockActive = await isDeepInterviewInputLockActive(stateDir, getEffectiveSessionId());
749
+ const effectiveSessionId = getEffectiveSessionId();
750
+ const deepInterviewStateActive = effectiveSessionId
751
+ ? await isDeepInterviewStateActive(stateDir, effectiveSessionId)
752
+ : await isDeepInterviewStateActive(stateDir, undefined);
753
+ const deepInterviewInputLockActive = await isDeepInterviewInputLockActive(stateDir, effectiveSessionId);
659
754
 
660
755
  // 4.55. Notify leader when individual worker transitions to idle (worker session only)
661
756
  if (isTeamWorker && parsedTeamWorker && !deepInterviewStateActive) {
@@ -909,8 +1004,12 @@ async function logFatalNotifyHookError(err: unknown): Promise<void> {
909
1004
  try {
910
1005
  const rawPayload = process.argv[process.argv.length - 1];
911
1006
  if (rawPayload && !rawPayload.startsWith('-')) {
912
- const payload = JSON.parse(rawPayload) as Record<string, unknown>;
913
- cwd = safeString(payload.cwd || payload['cwd'] || cwd) || cwd;
1007
+ if (utf8ByteLength(rawPayload) <= MAX_NOTIFY_ARGV_JSON_BYTES) {
1008
+ const payload = JSON.parse(rawPayload) as Record<string, unknown>;
1009
+ cwd = safeString(payload.cwd || payload['cwd'] || cwd) || cwd;
1010
+ } else {
1011
+ cwd = extractRawJsonStringField(rawPayload, ['cwd']) || cwd;
1012
+ }
914
1013
  }
915
1014
  } catch {
916
1015
  // Keep notification hook failures silent in Codex TUI surfaces.