oh-my-codex 0.11.11 → 0.11.13

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 (342) hide show
  1. package/Cargo.lock +5 -5
  2. package/Cargo.toml +1 -1
  3. package/README.de.md +12 -6
  4. package/README.el.md +223 -0
  5. package/README.es.md +12 -6
  6. package/README.fr.md +11 -5
  7. package/README.it.md +12 -6
  8. package/README.ja.md +12 -6
  9. package/README.ko.md +12 -6
  10. package/README.md +56 -28
  11. package/README.pl.md +216 -0
  12. package/README.pt.md +12 -6
  13. package/README.ru.md +12 -6
  14. package/README.tr.md +12 -6
  15. package/README.vi.md +148 -183
  16. package/README.zh-TW.md +14 -17
  17. package/README.zh.md +12 -6
  18. package/crates/omx-runtime-core/src/engine.rs +122 -4
  19. package/crates/omx-runtime-core/src/lib.rs +17 -0
  20. package/dist/autoresearch/contracts.d.ts.map +1 -1
  21. package/dist/autoresearch/contracts.js +1 -0
  22. package/dist/autoresearch/contracts.js.map +1 -1
  23. package/dist/autoresearch/runtime.d.ts.map +1 -1
  24. package/dist/autoresearch/runtime.js +7 -1
  25. package/dist/autoresearch/runtime.js.map +1 -1
  26. package/dist/cli/__tests__/agents.test.js +24 -1
  27. package/dist/cli/__tests__/agents.test.js.map +1 -1
  28. package/dist/cli/__tests__/autoresearch.test.js +11 -0
  29. package/dist/cli/__tests__/autoresearch.test.js.map +1 -1
  30. package/dist/cli/__tests__/cleanup.test.js +117 -4
  31. package/dist/cli/__tests__/cleanup.test.js.map +1 -1
  32. package/dist/cli/__tests__/doctor-warning-copy.test.js +33 -3
  33. package/dist/cli/__tests__/doctor-warning-copy.test.js.map +1 -1
  34. package/dist/cli/__tests__/error-handling-warnings.test.js +13 -0
  35. package/dist/cli/__tests__/error-handling-warnings.test.js.map +1 -1
  36. package/dist/cli/__tests__/exec.test.js +6 -0
  37. package/dist/cli/__tests__/exec.test.js.map +1 -1
  38. package/dist/cli/__tests__/index.test.js +101 -1
  39. package/dist/cli/__tests__/index.test.js.map +1 -1
  40. package/dist/cli/__tests__/launch-fallback.test.js +3 -0
  41. package/dist/cli/__tests__/launch-fallback.test.js.map +1 -1
  42. package/dist/cli/__tests__/package-bin-contract.test.js +10 -0
  43. package/dist/cli/__tests__/package-bin-contract.test.js.map +1 -1
  44. package/dist/cli/__tests__/packaged-script-resolution.test.js +4 -3
  45. package/dist/cli/__tests__/packaged-script-resolution.test.js.map +1 -1
  46. package/dist/cli/__tests__/resume.test.js +6 -0
  47. package/dist/cli/__tests__/resume.test.js.map +1 -1
  48. package/dist/cli/__tests__/setup-refresh.test.js +29 -12
  49. package/dist/cli/__tests__/setup-refresh.test.js.map +1 -1
  50. package/dist/cli/__tests__/setup-scope.test.js +1 -1
  51. package/dist/cli/__tests__/setup-scope.test.js.map +1 -1
  52. package/dist/cli/__tests__/star-prompt.test.js +16 -0
  53. package/dist/cli/__tests__/star-prompt.test.js.map +1 -1
  54. package/dist/cli/__tests__/uninstall.test.js +112 -1
  55. package/dist/cli/__tests__/uninstall.test.js.map +1 -1
  56. package/dist/cli/__tests__/windows-popup-loop-contract.test.d.ts +2 -0
  57. package/dist/cli/__tests__/windows-popup-loop-contract.test.d.ts.map +1 -0
  58. package/dist/cli/__tests__/windows-popup-loop-contract.test.js +30 -0
  59. package/dist/cli/__tests__/windows-popup-loop-contract.test.js.map +1 -0
  60. package/dist/cli/agents.d.ts.map +1 -1
  61. package/dist/cli/agents.js +9 -3
  62. package/dist/cli/agents.js.map +1 -1
  63. package/dist/cli/autoresearch-guided.d.ts.map +1 -1
  64. package/dist/cli/autoresearch-guided.js +9 -3
  65. package/dist/cli/autoresearch-guided.js.map +1 -1
  66. package/dist/cli/autoresearch.d.ts.map +1 -1
  67. package/dist/cli/autoresearch.js +8 -2
  68. package/dist/cli/autoresearch.js.map +1 -1
  69. package/dist/cli/cleanup.d.ts +2 -0
  70. package/dist/cli/cleanup.d.ts.map +1 -1
  71. package/dist/cli/cleanup.js +27 -1
  72. package/dist/cli/cleanup.js.map +1 -1
  73. package/dist/cli/doctor.js +7 -0
  74. package/dist/cli/doctor.js.map +1 -1
  75. package/dist/cli/index.d.ts +9 -1
  76. package/dist/cli/index.d.ts.map +1 -1
  77. package/dist/cli/index.js +171 -55
  78. package/dist/cli/index.js.map +1 -1
  79. package/dist/cli/setup.d.ts.map +1 -1
  80. package/dist/cli/setup.js +18 -15
  81. package/dist/cli/setup.js.map +1 -1
  82. package/dist/cli/star-prompt.d.ts.map +1 -1
  83. package/dist/cli/star-prompt.js +2 -0
  84. package/dist/cli/star-prompt.js.map +1 -1
  85. package/dist/cli/team.d.ts.map +1 -1
  86. package/dist/cli/team.js +5 -1
  87. package/dist/cli/team.js.map +1 -1
  88. package/dist/cli/tmux-hook.d.ts.map +1 -1
  89. package/dist/cli/tmux-hook.js +4 -1
  90. package/dist/cli/tmux-hook.js.map +1 -1
  91. package/dist/cli/uninstall.d.ts.map +1 -1
  92. package/dist/cli/uninstall.js +26 -0
  93. package/dist/cli/uninstall.js.map +1 -1
  94. package/dist/cli/update.d.ts.map +1 -1
  95. package/dist/cli/update.js +1 -0
  96. package/dist/cli/update.js.map +1 -1
  97. package/dist/compat/__tests__/rust-runtime-compat.test.js +84 -1
  98. package/dist/compat/__tests__/rust-runtime-compat.test.js.map +1 -1
  99. package/dist/config/__tests__/generator-idempotent.test.js +4 -4
  100. package/dist/config/__tests__/generator-idempotent.test.js.map +1 -1
  101. package/dist/config/__tests__/mcp-registry.test.js +13 -16
  102. package/dist/config/__tests__/mcp-registry.test.js.map +1 -1
  103. package/dist/config/mcp-registry.d.ts +1 -0
  104. package/dist/config/mcp-registry.d.ts.map +1 -1
  105. package/dist/config/mcp-registry.js +4 -4
  106. package/dist/config/mcp-registry.js.map +1 -1
  107. package/dist/config/models.d.ts +1 -0
  108. package/dist/config/models.d.ts.map +1 -1
  109. package/dist/config/models.js +39 -1
  110. package/dist/config/models.js.map +1 -1
  111. package/dist/hooks/__tests__/keyword-detector.test.js +12 -1
  112. package/dist/hooks/__tests__/keyword-detector.test.js.map +1 -1
  113. package/dist/hooks/__tests__/notify-fallback-watcher.test.js +554 -18
  114. package/dist/hooks/__tests__/notify-fallback-watcher.test.js.map +1 -1
  115. package/dist/hooks/__tests__/notify-hook-auto-nudge.test.js +347 -16
  116. package/dist/hooks/__tests__/notify-hook-auto-nudge.test.js.map +1 -1
  117. package/dist/hooks/__tests__/notify-hook-modules.test.js +5 -0
  118. package/dist/hooks/__tests__/notify-hook-modules.test.js.map +1 -1
  119. package/dist/hooks/__tests__/notify-hook-ralph-resume.test.d.ts +2 -0
  120. package/dist/hooks/__tests__/notify-hook-ralph-resume.test.d.ts.map +1 -0
  121. package/dist/hooks/__tests__/notify-hook-ralph-resume.test.js +597 -0
  122. package/dist/hooks/__tests__/notify-hook-ralph-resume.test.js.map +1 -0
  123. package/dist/hooks/__tests__/notify-hook-regression-205.test.js +19 -1
  124. package/dist/hooks/__tests__/notify-hook-regression-205.test.js.map +1 -1
  125. package/dist/hooks/__tests__/notify-hook-session-scope.test.js +73 -53
  126. package/dist/hooks/__tests__/notify-hook-session-scope.test.js.map +1 -1
  127. package/dist/hooks/__tests__/notify-hook-team-dispatch.test.js +193 -2
  128. package/dist/hooks/__tests__/notify-hook-team-dispatch.test.js.map +1 -1
  129. package/dist/hooks/__tests__/notify-hook-team-leader-nudge.test.js +183 -0
  130. package/dist/hooks/__tests__/notify-hook-team-leader-nudge.test.js.map +1 -1
  131. package/dist/hooks/__tests__/notify-hook-tmux-heal.test.js +255 -97
  132. package/dist/hooks/__tests__/notify-hook-tmux-heal.test.js.map +1 -1
  133. package/dist/hooks/__tests__/notify-hook-tmux-scrollback.test.js +0 -0
  134. package/dist/hooks/__tests__/notify-hook-tmux-scrollback.test.js.map +1 -1
  135. package/dist/hooks/__tests__/notify-hook-worker-idle.test.js +46 -0
  136. package/dist/hooks/__tests__/notify-hook-worker-idle.test.js.map +1 -1
  137. package/dist/hooks/__tests__/prompt-team-routing.test.js +34 -0
  138. package/dist/hooks/__tests__/prompt-team-routing.test.js.map +1 -1
  139. package/dist/hooks/__tests__/tmux-hook-engine.test.js +32 -1
  140. package/dist/hooks/__tests__/tmux-hook-engine.test.js.map +1 -1
  141. package/dist/hooks/code-simplifier/index.d.ts.map +1 -1
  142. package/dist/hooks/code-simplifier/index.js +1 -0
  143. package/dist/hooks/code-simplifier/index.js.map +1 -1
  144. package/dist/hooks/codebase-map.d.ts.map +1 -1
  145. package/dist/hooks/codebase-map.js +1 -0
  146. package/dist/hooks/codebase-map.js.map +1 -1
  147. package/dist/hooks/extensibility/sdk/tmux.d.ts.map +1 -1
  148. package/dist/hooks/extensibility/sdk/tmux.js +3 -1
  149. package/dist/hooks/extensibility/sdk/tmux.js.map +1 -1
  150. package/dist/hooks/keyword-detector.d.ts +1 -0
  151. package/dist/hooks/keyword-detector.d.ts.map +1 -1
  152. package/dist/hooks/keyword-detector.js +48 -0
  153. package/dist/hooks/keyword-detector.js.map +1 -1
  154. package/dist/hooks/prompt-guidance-contract.d.ts.map +1 -1
  155. package/dist/hooks/prompt-guidance-contract.js +6 -0
  156. package/dist/hooks/prompt-guidance-contract.js.map +1 -1
  157. package/dist/hooks/session.d.ts.map +1 -1
  158. package/dist/hooks/session.js +1 -0
  159. package/dist/hooks/session.js.map +1 -1
  160. package/dist/hud/__tests__/state.test.js +70 -1
  161. package/dist/hud/__tests__/state.test.js.map +1 -1
  162. package/dist/hud/authority.d.ts.map +1 -1
  163. package/dist/hud/authority.js +1 -0
  164. package/dist/hud/authority.js.map +1 -1
  165. package/dist/hud/state.d.ts.map +1 -1
  166. package/dist/hud/state.js +52 -0
  167. package/dist/hud/state.js.map +1 -1
  168. package/dist/mcp/state-server.d.ts.map +1 -1
  169. package/dist/mcp/state-server.js +5 -0
  170. package/dist/mcp/state-server.js.map +1 -1
  171. package/dist/modes/__tests__/base-session-scope.test.js +46 -0
  172. package/dist/modes/__tests__/base-session-scope.test.js.map +1 -1
  173. package/dist/modes/base.d.ts.map +1 -1
  174. package/dist/modes/base.js +4 -0
  175. package/dist/modes/base.js.map +1 -1
  176. package/dist/notifications/__tests__/custom-alias-enablement.test.d.ts +2 -0
  177. package/dist/notifications/__tests__/custom-alias-enablement.test.d.ts.map +1 -0
  178. package/dist/notifications/__tests__/custom-alias-enablement.test.js +84 -0
  179. package/dist/notifications/__tests__/custom-alias-enablement.test.js.map +1 -0
  180. package/dist/notifications/__tests__/idle-cooldown.test.js +55 -0
  181. package/dist/notifications/__tests__/idle-cooldown.test.js.map +1 -1
  182. package/dist/notifications/idle-cooldown.d.ts +8 -6
  183. package/dist/notifications/idle-cooldown.d.ts.map +1 -1
  184. package/dist/notifications/idle-cooldown.js +53 -22
  185. package/dist/notifications/idle-cooldown.js.map +1 -1
  186. package/dist/notifications/notifier.js +1 -1
  187. package/dist/notifications/notifier.js.map +1 -1
  188. package/dist/notifications/reply-listener.d.ts.map +1 -1
  189. package/dist/notifications/reply-listener.js +1 -0
  190. package/dist/notifications/reply-listener.js.map +1 -1
  191. package/dist/notifications/tmux.d.ts.map +1 -1
  192. package/dist/notifications/tmux.js +4 -0
  193. package/dist/notifications/tmux.js.map +1 -1
  194. package/dist/openclaw/config.js +2 -2
  195. package/dist/openclaw/config.js.map +1 -1
  196. package/dist/runtime/bridge.d.ts +2 -0
  197. package/dist/runtime/bridge.d.ts.map +1 -1
  198. package/dist/runtime/bridge.js +8 -0
  199. package/dist/runtime/bridge.js.map +1 -1
  200. package/dist/scripts/notify-fallback-watcher.js +103 -53
  201. package/dist/scripts/notify-fallback-watcher.js.map +1 -1
  202. package/dist/scripts/notify-hook/auto-nudge.d.ts +2 -1
  203. package/dist/scripts/notify-hook/auto-nudge.d.ts.map +1 -1
  204. package/dist/scripts/notify-hook/auto-nudge.js +90 -104
  205. package/dist/scripts/notify-hook/auto-nudge.js.map +1 -1
  206. package/dist/scripts/notify-hook/managed-tmux.d.ts +19 -0
  207. package/dist/scripts/notify-hook/managed-tmux.d.ts.map +1 -0
  208. package/dist/scripts/notify-hook/managed-tmux.js +320 -0
  209. package/dist/scripts/notify-hook/managed-tmux.js.map +1 -0
  210. package/dist/scripts/notify-hook/operational-events.d.ts.map +1 -1
  211. package/dist/scripts/notify-hook/operational-events.js +2 -0
  212. package/dist/scripts/notify-hook/operational-events.js.map +1 -1
  213. package/dist/scripts/notify-hook/ralph-session-resume.d.ts +22 -0
  214. package/dist/scripts/notify-hook/ralph-session-resume.d.ts.map +1 -0
  215. package/dist/scripts/notify-hook/ralph-session-resume.js +277 -0
  216. package/dist/scripts/notify-hook/ralph-session-resume.js.map +1 -0
  217. package/dist/scripts/notify-hook/state-io.d.ts +1 -1
  218. package/dist/scripts/notify-hook/state-io.d.ts.map +1 -1
  219. package/dist/scripts/notify-hook/state-io.js +2 -10
  220. package/dist/scripts/notify-hook/state-io.js.map +1 -1
  221. package/dist/scripts/notify-hook/team-dispatch.d.ts +1 -1
  222. package/dist/scripts/notify-hook/team-dispatch.d.ts.map +1 -1
  223. package/dist/scripts/notify-hook/team-dispatch.js +123 -72
  224. package/dist/scripts/notify-hook/team-dispatch.js.map +1 -1
  225. package/dist/scripts/notify-hook/team-leader-nudge.d.ts +2 -1
  226. package/dist/scripts/notify-hook/team-leader-nudge.d.ts.map +1 -1
  227. package/dist/scripts/notify-hook/team-leader-nudge.js +13 -5
  228. package/dist/scripts/notify-hook/team-leader-nudge.js.map +1 -1
  229. package/dist/scripts/notify-hook/team-tmux-guard.d.ts.map +1 -1
  230. package/dist/scripts/notify-hook/team-tmux-guard.js +1 -19
  231. package/dist/scripts/notify-hook/team-tmux-guard.js.map +1 -1
  232. package/dist/scripts/notify-hook/team-worker.js +4 -4
  233. package/dist/scripts/notify-hook/team-worker.js.map +1 -1
  234. package/dist/scripts/notify-hook/tmux-injection.d.ts +1 -1
  235. package/dist/scripts/notify-hook/tmux-injection.d.ts.map +1 -1
  236. package/dist/scripts/notify-hook/tmux-injection.js +102 -35
  237. package/dist/scripts/notify-hook/tmux-injection.js.map +1 -1
  238. package/dist/scripts/notify-hook.js +144 -20
  239. package/dist/scripts/notify-hook.js.map +1 -1
  240. package/dist/scripts/run-provider-advisor.js +2 -0
  241. package/dist/scripts/run-provider-advisor.js.map +1 -1
  242. package/dist/scripts/run-test-files.d.ts +2 -0
  243. package/dist/scripts/run-test-files.d.ts.map +1 -0
  244. package/dist/scripts/run-test-files.js +41 -0
  245. package/dist/scripts/run-test-files.js.map +1 -0
  246. package/dist/scripts/tmux-hook-engine.d.ts +2 -0
  247. package/dist/scripts/tmux-hook-engine.d.ts.map +1 -1
  248. package/dist/scripts/tmux-hook-engine.js +15 -0
  249. package/dist/scripts/tmux-hook-engine.js.map +1 -1
  250. package/dist/team/__tests__/api-interop.test.js +136 -4
  251. package/dist/team/__tests__/api-interop.test.js.map +1 -1
  252. package/dist/team/__tests__/leader-activity.test.js +107 -2
  253. package/dist/team/__tests__/leader-activity.test.js.map +1 -1
  254. package/dist/team/__tests__/runtime-cli.test.js +32 -0
  255. package/dist/team/__tests__/runtime-cli.test.js.map +1 -1
  256. package/dist/team/__tests__/runtime.test.js +148 -0
  257. package/dist/team/__tests__/runtime.test.js.map +1 -1
  258. package/dist/team/__tests__/shutdown-fallback.test.js +13 -0
  259. package/dist/team/__tests__/shutdown-fallback.test.js.map +1 -1
  260. package/dist/team/__tests__/state-root.test.js +11 -1
  261. package/dist/team/__tests__/state-root.test.js.map +1 -1
  262. package/dist/team/__tests__/state.test.js +237 -0
  263. package/dist/team/__tests__/state.test.js.map +1 -1
  264. package/dist/team/__tests__/tmux-session.test.js +521 -2
  265. package/dist/team/__tests__/tmux-session.test.js.map +1 -1
  266. package/dist/team/api-interop.d.ts.map +1 -1
  267. package/dist/team/api-interop.js +41 -31
  268. package/dist/team/api-interop.js.map +1 -1
  269. package/dist/team/commit-hygiene.d.ts +60 -0
  270. package/dist/team/commit-hygiene.d.ts.map +1 -0
  271. package/dist/team/commit-hygiene.js +232 -0
  272. package/dist/team/commit-hygiene.js.map +1 -0
  273. package/dist/team/leader-activity.d.ts.map +1 -1
  274. package/dist/team/leader-activity.js +56 -4
  275. package/dist/team/leader-activity.js.map +1 -1
  276. package/dist/team/runtime-cli.d.ts +9 -1
  277. package/dist/team/runtime-cli.d.ts.map +1 -1
  278. package/dist/team/runtime-cli.js +15 -6
  279. package/dist/team/runtime-cli.js.map +1 -1
  280. package/dist/team/runtime.d.ts +7 -2
  281. package/dist/team/runtime.d.ts.map +1 -1
  282. package/dist/team/runtime.js +392 -171
  283. package/dist/team/runtime.js.map +1 -1
  284. package/dist/team/scaling.d.ts.map +1 -1
  285. package/dist/team/scaling.js +6 -2
  286. package/dist/team/scaling.js.map +1 -1
  287. package/dist/team/state/dispatch.d.ts +2 -0
  288. package/dist/team/state/dispatch.d.ts.map +1 -1
  289. package/dist/team/state/dispatch.js +86 -40
  290. package/dist/team/state/dispatch.js.map +1 -1
  291. package/dist/team/state/mailbox.d.ts +3 -0
  292. package/dist/team/state/mailbox.d.ts.map +1 -1
  293. package/dist/team/state/mailbox.js +93 -19
  294. package/dist/team/state/mailbox.js.map +1 -1
  295. package/dist/team/state-root.d.ts +1 -1
  296. package/dist/team/state-root.d.ts.map +1 -1
  297. package/dist/team/state-root.js +8 -3
  298. package/dist/team/state-root.js.map +1 -1
  299. package/dist/team/state.d.ts.map +1 -1
  300. package/dist/team/state.js +96 -2
  301. package/dist/team/state.js.map +1 -1
  302. package/dist/team/tmux-session.d.ts.map +1 -1
  303. package/dist/team/tmux-session.js +81 -29
  304. package/dist/team/tmux-session.js.map +1 -1
  305. package/dist/team/worker-bootstrap.d.ts.map +1 -1
  306. package/dist/team/worker-bootstrap.js +4 -0
  307. package/dist/team/worker-bootstrap.js.map +1 -1
  308. package/dist/team/worktree.d.ts.map +1 -1
  309. package/dist/team/worktree.js +9 -0
  310. package/dist/team/worktree.js.map +1 -1
  311. package/dist/utils/__tests__/paths.test.js +98 -11
  312. package/dist/utils/__tests__/paths.test.js.map +1 -1
  313. package/dist/utils/__tests__/platform-command.test.js +101 -2
  314. package/dist/utils/__tests__/platform-command.test.js.map +1 -1
  315. package/dist/utils/git-layout.d.ts +8 -0
  316. package/dist/utils/git-layout.d.ts.map +1 -0
  317. package/dist/utils/git-layout.js +58 -0
  318. package/dist/utils/git-layout.js.map +1 -0
  319. package/dist/utils/paths.d.ts +3 -0
  320. package/dist/utils/paths.d.ts.map +1 -1
  321. package/dist/utils/paths.js +14 -4
  322. package/dist/utils/paths.js.map +1 -1
  323. package/dist/utils/platform-command.d.ts.map +1 -1
  324. package/dist/utils/platform-command.js +35 -3
  325. package/dist/utils/platform-command.js.map +1 -1
  326. package/package.json +9 -5
  327. package/src/scripts/notify-fallback-watcher.ts +103 -53
  328. package/src/scripts/notify-hook/auto-nudge.ts +97 -103
  329. package/src/scripts/notify-hook/managed-tmux.ts +324 -0
  330. package/src/scripts/notify-hook/operational-events.ts +2 -0
  331. package/src/scripts/notify-hook/ralph-session-resume.ts +337 -0
  332. package/src/scripts/notify-hook/state-io.ts +2 -10
  333. package/src/scripts/notify-hook/team-dispatch.ts +131 -66
  334. package/src/scripts/notify-hook/team-leader-nudge.ts +19 -5
  335. package/src/scripts/notify-hook/team-tmux-guard.ts +0 -20
  336. package/src/scripts/notify-hook/team-worker.ts +4 -4
  337. package/src/scripts/notify-hook/tmux-injection.ts +103 -33
  338. package/src/scripts/notify-hook.ts +150 -21
  339. package/src/scripts/run-provider-advisor.ts +4 -2
  340. package/src/scripts/run-test-files.ts +48 -0
  341. package/src/scripts/tmux-hook-engine.ts +16 -0
  342. package/templates/AGENTS.md +51 -43
@@ -5,12 +5,12 @@ import { execFileSync } from 'child_process';
5
5
  import { dirname, join, resolve } from 'path';
6
6
  import { fileURLToPath } from 'node:url';
7
7
  import { safeString } from './utils.js';
8
- import { resolveRuntimeBinaryPath } from '../../runtime/bridge.js';
8
+ import { resolveBridgeStateDir, resolveRuntimeBinaryPath } from '../../runtime/bridge.js';
9
9
 
10
10
  const __filename = fileURLToPath(import.meta.url);
11
11
  const __dirname = dirname(__filename);
12
12
  import { runProcess } from './process-runner.js';
13
- import { resolvePaneTarget } from './tmux-injection.js';
13
+ import { resolvePaneTarget, resolveSessionToPane } from './tmux-injection.js';
14
14
  import { evaluatePaneInjectionReadiness, sendPaneInput } from './team-tmux-guard.js';
15
15
  import {
16
16
  buildCapturePaneArgv,
@@ -21,16 +21,19 @@ import {
21
21
 
22
22
  /**
23
23
  * Route dispatch state transitions through the Rust runtime binary.
24
- * Non-fatal: if the binary is missing or fails, the JS path remains canonical.
24
+ * Non-fatal: if the binary is missing or fails, the legacy JSON fallback lane
25
+ * remains available when the caller is already operating outside the bridge-
26
+ * owned path.
25
27
  * Disable entirely with OMX_RUNTIME_BRIDGE=0.
26
28
  */
27
- function runtimeExec(command) {
29
+ function runtimeExec(command, stateDir) {
28
30
  if (process.env.OMX_RUNTIME_BRIDGE === '0') return;
29
31
  try {
30
32
  const binaryPath = resolveRuntimeBinaryPath();
31
- execFileSync(binaryPath, ['exec', JSON.stringify(command)], {
33
+ execFileSync(binaryPath, ['exec', JSON.stringify(command), `--state-dir=${stateDir}`], {
32
34
  timeout: 5000,
33
35
  stdio: ['pipe', 'pipe', 'pipe'],
36
+ windowsHide: true,
34
37
  });
35
38
  } catch {
36
39
  // non-fatal: JS path is the fallback
@@ -43,6 +46,47 @@ function readJson(path, fallback) {
43
46
  .catch(() => fallback);
44
47
  }
45
48
 
49
+ async function readBridgeDispatchRequests(stateDir, teamName) {
50
+ const candidate = join(stateDir, 'dispatch.json');
51
+ if (!existsSync(candidate)) return null;
52
+ const parsed = await readJson(candidate, null);
53
+ if (!parsed || !Array.isArray(parsed.records)) return null;
54
+ return parsed.records
55
+ .map((record) => {
56
+ if (!record || typeof record !== 'object') return null;
57
+ const metadata = record.metadata && typeof record.metadata === 'object' ? record.metadata : {};
58
+ const metadataTeam = safeString(metadata.team_name).trim();
59
+ if (metadataTeam && metadataTeam !== teamName) return null;
60
+ return {
61
+ request_id: safeString(record.request_id).trim(),
62
+ kind: safeString(metadata.kind).trim() || 'inbox',
63
+ team_name: teamName,
64
+ to_worker: safeString(record.target).trim(),
65
+ worker_index: typeof metadata.worker_index === 'number' ? metadata.worker_index : undefined,
66
+ pane_id: safeString(metadata.pane_id).trim() || undefined,
67
+ trigger_message: safeString(metadata.trigger_message).trim() || safeString(record.reason).trim() || safeString(record.request_id).trim(),
68
+ message_id: safeString(metadata.message_id).trim() || undefined,
69
+ inbox_correlation_key: safeString(metadata.inbox_correlation_key).trim() || undefined,
70
+ transport_preference: safeString(metadata.transport_preference).trim() || 'hook_preferred_with_fallback',
71
+ fallback_allowed: typeof metadata.fallback_allowed === 'boolean' ? metadata.fallback_allowed : true,
72
+ status: safeString(record.status).trim() || 'pending',
73
+ attempt_count: Number.isFinite(metadata.attempt_count) ? Number(metadata.attempt_count) : 0,
74
+ created_at: safeString(record.created_at).trim() || new Date().toISOString(),
75
+ updated_at:
76
+ safeString(record.delivered_at).trim()
77
+ || safeString(record.failed_at).trim()
78
+ || safeString(record.notified_at).trim()
79
+ || safeString(record.created_at).trim()
80
+ || new Date().toISOString(),
81
+ notified_at: safeString(record.notified_at).trim() || undefined,
82
+ delivered_at: safeString(record.delivered_at).trim() || undefined,
83
+ failed_at: safeString(record.failed_at).trim() || undefined,
84
+ last_reason: safeString(record.reason).trim() || undefined,
85
+ };
86
+ })
87
+ .filter((record) => record && record.request_id && record.to_worker && record.trigger_message);
88
+ }
89
+
46
90
  async function writeJsonAtomic(path, value) {
47
91
  await mkdir(dirname(path), { recursive: true });
48
92
  const tmp = `${path}.tmp-${process.pid}-${Date.now()}-${Math.random().toString(16).slice(2)}`;
@@ -138,8 +182,7 @@ function parseTriggerCooldownEntry(entry) {
138
182
  };
139
183
  }
140
184
 
141
- async function withDispatchLock(teamDirPath, fn) {
142
- const lockDir = join(teamDirPath, 'dispatch', '.lock');
185
+ async function withLockDirectory(lockDir, timeoutError, fn) {
143
186
  const ownerPath = join(lockDir, 'owner');
144
187
  const ownerToken = `${process.pid}.${Date.now()}.${Math.random().toString(16).slice(2)}`;
145
188
  const deadline = Date.now() + 5_000;
@@ -166,7 +209,7 @@ async function withDispatchLock(teamDirPath, fn) {
166
209
  } catch {
167
210
  // best effort
168
211
  }
169
- if (Date.now() > deadline) throw new Error(`Timed out acquiring dispatch lock for ${teamDirPath}`);
212
+ if (Date.now() > deadline) throw new Error(timeoutError);
170
213
  await new Promise((resolveDelay) => setTimeout(resolveDelay, 25));
171
214
  }
172
215
  }
@@ -185,57 +228,63 @@ async function withDispatchLock(teamDirPath, fn) {
185
228
  }
186
229
  }
187
230
 
188
- async function withMailboxLock(teamDirPath, workerName, fn) {
189
- const lockDir = join(teamDirPath, 'mailbox', `.lock-${workerName}`);
190
- const ownerPath = join(lockDir, 'owner');
191
- const ownerToken = `${process.pid}.${Date.now()}.${Math.random().toString(16).slice(2)}`;
192
- const deadline = Date.now() + 5_000;
193
- await mkdir(dirname(lockDir), { recursive: true });
194
-
195
- while (true) {
196
- try {
197
- await mkdir(lockDir, { recursive: false });
198
- try {
199
- await writeFile(ownerPath, ownerToken, 'utf8');
200
- } catch (error) {
201
- await rm(lockDir, { recursive: true, force: true });
202
- throw error;
203
- }
204
- break;
205
- } catch (error) {
206
- if (error?.code !== 'EEXIST') throw error;
207
- try {
208
- const info = await stat(lockDir);
209
- if (Date.now() - info.mtimeMs > DISPATCH_LOCK_STALE_MS) {
210
- await rm(lockDir, { recursive: true, force: true });
211
- continue;
212
- }
213
- } catch {
214
- // best effort
215
- }
216
- if (Date.now() > deadline) throw new Error(`Timed out acquiring mailbox lock for ${teamDirPath}/${workerName}`);
217
- await new Promise((resolveDelay) => setTimeout(resolveDelay, 25));
218
- }
219
- }
231
+ async function withDispatchLock(teamDirPath, fn) {
232
+ return await withLockDirectory(
233
+ join(teamDirPath, 'dispatch', '.lock'),
234
+ `Timed out acquiring dispatch lock for ${teamDirPath}`,
235
+ fn,
236
+ );
237
+ }
220
238
 
221
- try {
222
- return await fn();
223
- } finally {
224
- try {
225
- const currentOwner = await readFile(ownerPath, 'utf8');
226
- if (currentOwner.trim() === ownerToken) {
227
- await rm(lockDir, { recursive: true, force: true });
228
- }
229
- } catch {
230
- // best effort
231
- }
232
- }
239
+ async function withMailboxLock(teamDirPath, workerName, fn) {
240
+ return await withLockDirectory(
241
+ join(teamDirPath, 'mailbox', `.lock-${workerName}`),
242
+ `Timed out acquiring mailbox lock for ${teamDirPath}/${workerName}`,
243
+ fn,
244
+ );
233
245
  }
234
246
 
235
247
  function resolveLeaderPaneId(config) {
236
248
  return safeString(config?.leader_pane_id).trim();
237
249
  }
238
250
 
251
+ function serializeDispatchRequestRecord(request) {
252
+ return {
253
+ request_id: safeString(request.request_id).trim(),
254
+ target: safeString(request.to_worker).trim(),
255
+ status: safeString(request.status).trim() || 'pending',
256
+ created_at: safeString(request.created_at).trim() || new Date().toISOString(),
257
+ notified_at: safeString(request.notified_at).trim() || null,
258
+ delivered_at: safeString(request.delivered_at).trim() || null,
259
+ failed_at: safeString(request.failed_at).trim() || null,
260
+ reason: safeString(request.last_reason).trim() || null,
261
+ metadata: {
262
+ kind: safeString(request.kind).trim() || 'inbox',
263
+ team_name: safeString(request.team_name).trim(),
264
+ worker_index: Number.isFinite(request.worker_index) ? Number(request.worker_index) : undefined,
265
+ pane_id: safeString(request.pane_id).trim() || undefined,
266
+ trigger_message: safeString(request.trigger_message).trim(),
267
+ message_id: safeString(request.message_id).trim() || undefined,
268
+ inbox_correlation_key: safeString(request.inbox_correlation_key).trim() || undefined,
269
+ transport_preference: safeString(request.transport_preference).trim() || 'hook_preferred_with_fallback',
270
+ fallback_allowed: typeof request.fallback_allowed === 'boolean' ? request.fallback_allowed : true,
271
+ attempt_count: Number.isFinite(request.attempt_count) ? Number(request.attempt_count) : 0,
272
+ },
273
+ };
274
+ }
275
+
276
+ async function writeBridgeDispatchCompat(stateDir, teamName, requests) {
277
+ const compatPath = join(stateDir, 'dispatch.json');
278
+ const current = await readJson(compatPath, { records: [] });
279
+ const existing = Array.isArray(current?.records) ? current.records : [];
280
+ const otherTeams = existing.filter((record) => {
281
+ const metadata = record?.metadata && typeof record.metadata === 'object' ? record.metadata : {};
282
+ return safeString(metadata.team_name).trim() !== teamName;
283
+ });
284
+ const records = [...otherTeams, ...requests.map(serializeDispatchRequestRecord)];
285
+ await writeJsonAtomic(compatPath, { records });
286
+ }
287
+
239
288
 
240
289
  function defaultInjectTarget(request, config) {
241
290
  if (request.to_worker === 'leader-fixed') {
@@ -318,12 +367,20 @@ function capturedPaneContainsTriggerNearTail(captured, trigger, nonEmptyTailLine
318
367
  const INJECT_VERIFY_DELAY_MS = 250;
319
368
  const INJECT_VERIFY_ROUNDS = 3;
320
369
 
321
- async function injectDispatchRequest(request, config, cwd) {
370
+ async function injectDispatchRequest(request, config, cwd, stateDir) {
322
371
  const target = defaultInjectTarget(request, config);
323
372
  if (!target) {
324
373
  return { ok: false, reason: 'missing_tmux_target' };
325
374
  }
326
- const resolution = await resolvePaneTarget(target, '', cwd, '');
375
+ let resolution;
376
+ if (target.type === 'session') {
377
+ const paneId = await resolveSessionToPane(target.value).catch(() => null);
378
+ resolution = paneId
379
+ ? { paneTarget: paneId, reason: 'session_target_resolved' }
380
+ : { paneTarget: null, reason: 'target_not_found' };
381
+ } else {
382
+ resolution = await resolvePaneTarget(target, '', '', '', {});
383
+ }
327
384
  if (!resolution.paneTarget) {
328
385
  return { ok: false, reason: `target_resolution_failed:${resolution.reason}` };
329
386
  }
@@ -390,7 +447,7 @@ async function injectDispatchRequest(request, config, cwd) {
390
447
  const wideCap = await runProcess('tmux', verifyWideArgv, 2000);
391
448
  // Worker is actively processing (mirrors sync path tmux-session.ts:1292-1294)
392
449
  if (paneHasActiveTask(wideCap.stdout)) {
393
- runtimeExec({ command: 'MarkDelivered', request_id: request.request_id });
450
+ runtimeExec({ command: 'MarkDelivered', request_id: request.request_id }, stateDir);
394
451
  return { ok: true, reason: 'tmux_send_keys_confirmed_active_task', pane: resolution.paneTarget };
395
452
  }
396
453
  // Do not declare success while a *worker* pane is still bootstrapping / not
@@ -403,7 +460,7 @@ async function injectDispatchRequest(request, config, cwd) {
403
460
  const triggerInNarrow = capturedPaneContainsTrigger(narrowCap.stdout, request.trigger_message);
404
461
  const triggerNearTail = capturedPaneContainsTriggerNearTail(wideCap.stdout, request.trigger_message);
405
462
  if (!triggerInNarrow && !triggerNearTail) {
406
- runtimeExec({ command: 'MarkDelivered', request_id: request.request_id });
463
+ runtimeExec({ command: 'MarkDelivered', request_id: request.request_id }, stateDir);
407
464
  return { ok: true, reason: 'tmux_send_keys_confirmed', pane: resolution.paneTarget };
408
465
  }
409
466
  } catch {
@@ -450,7 +507,7 @@ async function appendDispatchLog(logsDir, event) {
450
507
 
451
508
  export async function drainPendingTeamDispatch({
452
509
  cwd,
453
- stateDir = join(cwd, '.omx', 'state'),
510
+ stateDir = resolveBridgeStateDir(cwd),
454
511
  logsDir = join(cwd, '.omx', 'logs'),
455
512
  maxPerTick = 5,
456
513
  injector = injectDispatchRequest,
@@ -475,11 +532,12 @@ export async function drainPendingTeamDispatch({
475
532
  const manifestPath = join(teamDirPath, 'manifest.v2.json');
476
533
  const configPath = join(teamDirPath, 'config.json');
477
534
  const requestsPath = join(teamDirPath, 'dispatch', 'requests.json');
478
- if (!existsSync(requestsPath)) continue;
479
535
 
480
536
  const config = await readJson(existsSync(manifestPath) ? manifestPath : configPath, {});
481
537
  await withDispatchLock(teamDirPath, async () => {
482
- const requests = await readJson(requestsPath, []);
538
+ const bridgeRequests = await readBridgeDispatchRequests(stateDir, teamName);
539
+ const usingLegacyRequests = bridgeRequests === null;
540
+ const requests = usingLegacyRequests ? await readJson(requestsPath, []) : bridgeRequests;
483
541
  if (!Array.isArray(requests)) return;
484
542
  const issueCooldownState = await readIssueCooldownState(teamDirPath);
485
543
  const triggerCooldownState = await readTriggerCooldownState(teamDirPath);
@@ -518,8 +576,9 @@ export async function drainPendingTeamDispatch({
518
576
  leader_pane_id: safeString(config?.leader_pane_id).trim() || null,
519
577
  tmux_injection_attempted: false,
520
578
  });
521
- // Requests JSON is the canonical queue state; this event is a progress artifact
522
- // for hook/watcher readers until shared readers normalize everything later.
579
+ // On the legacy fallback lane, requests.json still carries the queue
580
+ // state for this deferred request; this event stays a progress
581
+ // artifact for hook/watcher readers.
523
582
  await appendLeaderNotificationDeferredEvent({
524
583
  stateDir,
525
584
  teamName,
@@ -554,7 +613,7 @@ export async function drainPendingTeamDispatch({
554
613
  }
555
614
  }
556
615
 
557
- const result = await injector(request, config, resolve(cwd));
616
+ const result = await injector(request, config, resolve(cwd), stateDir);
558
617
  if (issueKey && issueCooldownMs > 0) {
559
618
  issueCooldownByIssue[issueKey] = Date.now();
560
619
  mutated = true;
@@ -602,7 +661,7 @@ export async function drainPendingTeamDispatch({
602
661
  request.status = 'failed';
603
662
  request.failed_at = nowIso;
604
663
  request.last_reason = 'unconfirmed_after_max_retries';
605
- runtimeExec({ command: 'MarkFailed', request_id: request.request_id, reason: 'unconfirmed_after_max_retries' });
664
+ runtimeExec({ command: 'MarkFailed', request_id: request.request_id, reason: 'unconfirmed_after_max_retries' }, stateDir);
606
665
  processed += 1;
607
666
  failed += 1;
608
667
  mutated = true;
@@ -629,9 +688,12 @@ export async function drainPendingTeamDispatch({
629
688
  request.status = 'notified';
630
689
  request.notified_at = nowIso;
631
690
  request.last_reason = result.reason;
632
- runtimeExec({ command: 'MarkNotified', request_id: request.request_id, channel: 'tmux' });
691
+ runtimeExec({ command: 'MarkNotified', request_id: request.request_id, channel: 'tmux' }, stateDir);
633
692
  if (request.kind === 'mailbox' && request.message_id) {
634
- await updateMailboxNotified(stateDir, teamName, request.to_worker, request.message_id).catch(() => {});
693
+ runtimeExec({ command: 'MarkMailboxNotified', message_id: request.message_id }, stateDir);
694
+ if (usingLegacyRequests) {
695
+ await updateMailboxNotified(stateDir, teamName, request.to_worker, request.message_id).catch(() => {});
696
+ }
635
697
  }
636
698
  processed += 1;
637
699
  mutated = true;
@@ -647,7 +709,7 @@ export async function drainPendingTeamDispatch({
647
709
  request.status = 'failed';
648
710
  request.failed_at = nowIso;
649
711
  request.last_reason = result.reason;
650
- runtimeExec({ command: 'MarkFailed', request_id: request.request_id, reason: result.reason });
712
+ runtimeExec({ command: 'MarkFailed', request_id: request.request_id, reason: result.reason }, stateDir);
651
713
  processed += 1;
652
714
  failed += 1;
653
715
  mutated = true;
@@ -679,6 +741,9 @@ export async function drainPendingTeamDispatch({
679
741
  triggerCooldownState.by_trigger = triggerCooldownByKey;
680
742
  await writeJsonAtomic(triggerCooldownStatePath(teamDirPath), triggerCooldownState);
681
743
  await writeJsonAtomic(requestsPath, requests);
744
+ if (!usingLegacyRequests) {
745
+ await writeBridgeDispatchCompat(stateDir, teamName, requests);
746
+ }
682
747
  }
683
748
  });
684
749
  }
@@ -510,7 +510,13 @@ async function emitLeaderNudgeDeferredEvent(cwd, teamName, reason, nowIso, { tmu
510
510
  }
511
511
  }
512
512
 
513
- export async function maybeNudgeTeamLeader({ cwd, stateDir, logsDir, preComputedLeaderStale }) {
513
+ export async function maybeNudgeTeamLeader({
514
+ cwd,
515
+ stateDir,
516
+ logsDir,
517
+ preComputedLeaderStale,
518
+ allowFreshMailboxNudges = true,
519
+ }) {
514
520
  const intervalMs = resolveLeaderNudgeIntervalMs();
515
521
  const idleCooldownMs = resolveLeaderAllIdleNudgeCooldownMs();
516
522
  const fallbackProgressStallThresholdMs = resolveFallbackProgressStallThresholdMs();
@@ -683,7 +689,8 @@ export async function maybeNudgeTeamLeader({ cwd, stateDir, logsDir, preComputed
683
689
  const stalledTeamNudge = teamProgressStalled && (dueByTime || !previousStalledTeamNudge);
684
690
  const staleFollowupDue = stalePanesNudge && dueByTime;
685
691
 
686
- if (!shouldSendAllIdleNudge && !hasNewMessage && !stalledTeamNudge && !staleFollowupDue) continue;
692
+ const hasActionableNewMessage = hasNewMessage && (allowFreshMailboxNudges || leaderStale);
693
+ if (!shouldSendAllIdleNudge && !hasActionableNewMessage && !stalledTeamNudge && !staleFollowupDue) continue;
687
694
 
688
695
  let nudgeReason = '';
689
696
  let text = '';
@@ -717,7 +724,7 @@ export async function maybeNudgeTeamLeader({ cwd, stateDir, logsDir, preComputed
717
724
  `Team ${teamName}: ${stallPrefix}no progress ${formatDurationMs(stalledForMs)}. `
718
725
  + `${leaderActionGuidance} `
719
726
  + `(p:${pending} ip:${in_progress} b:${blocked}${missingSignals})`;
720
- } else if (stalePanesNudge && hasNewMessage) {
727
+ } else if (stalePanesNudge && hasActionableNewMessage) {
721
728
  nudgeReason = 'stale_leader_with_messages';
722
729
  text =
723
730
  `Team ${teamName}: leader stale, ${paneStatus.paneCount} pane(s) active, ${messages.length} msg(s) pending. `
@@ -727,7 +734,7 @@ export async function maybeNudgeTeamLeader({ cwd, stateDir, logsDir, preComputed
727
734
  text =
728
735
  `Team ${teamName}: leader stale, ${paneStatus.paneCount} worker pane(s) still active. `
729
736
  + leaderActionGuidance;
730
- } else if (hasNewMessage) {
737
+ } else if (hasActionableNewMessage) {
731
738
  nudgeReason = 'new_mailbox_message';
732
739
  text = `Team ${teamName}: ${messages.length} msg(s) for leader. ${buildMailboxCheckReminder(teamName)}`;
733
740
  } else {
@@ -763,7 +770,14 @@ export async function maybeNudgeTeamLeader({ cwd, stateDir, logsDir, preComputed
763
770
  continue;
764
771
  }
765
772
 
766
- const paneGuard = await evaluatePaneInjectionReadiness(tmuxTarget, { skipIfScrolling: true });
773
+ const paneGuard = await evaluatePaneInjectionReadiness(tmuxTarget, {
774
+ skipIfScrolling: true,
775
+ // Leader nudges should still queue into a live Codex pane even while the
776
+ // agent is busy; shell/copy-mode guards stay enforced.
777
+ requireRunningAgent: true,
778
+ requireReady: false,
779
+ requireIdle: false,
780
+ });
767
781
  if (!paneGuard.ok) {
768
782
  const deferredReason = paneGuard.reason === 'pane_running_shell'
769
783
  ? LEADER_PANE_SHELL_NO_INJECTION_REASON
@@ -8,7 +8,6 @@ import {
8
8
  isPaneRunningShell,
9
9
  paneHasActiveTask,
10
10
  paneLooksReady,
11
- resolveCodexPane,
12
11
  } from '../tmux-hook-engine.js';
13
12
 
14
13
  export function mapPaneInjectionReadinessReason(reason: any): any {
@@ -33,25 +32,6 @@ export async function evaluatePaneInjectionReadiness(paneTarget: any, {
33
32
  paneCapture: '',
34
33
  };
35
34
  }
36
-
37
- // Canonical bypass: if resolveCodexPane confirms this is a codex pane
38
- // (via pane_start_command), skip all readiness guards. The pane IS running
39
- // codex even though tmux may report cmd=sh (shell wrapper).
40
- try {
41
- if (resolveCodexPane() === target) {
42
- return {
43
- ok: true,
44
- sent: false,
45
- reason: 'ok',
46
- paneTarget: target,
47
- paneCurrentCommand: 'codex',
48
- paneCapture: '',
49
- };
50
- }
51
- } catch {
52
- // Non-fatal: fall through to normal readiness checks
53
- }
54
-
55
35
  if (skipIfScrolling) {
56
36
  try {
57
37
  const modeResult = await runProcess('tmux', buildPaneInModeArgv(target), 1000);
@@ -262,7 +262,7 @@ async function resolveCanonicalLeaderPaneId(_tmuxSession, leaderPaneId) {
262
262
  const normalizedLeaderPaneId = safeString(leaderPaneId).trim();
263
263
  if (normalizedLeaderPaneId) {
264
264
  try {
265
- const resolved = await resolvePaneTarget({ type: 'pane', value: normalizedLeaderPaneId }, '', '', '');
265
+ const resolved = await resolvePaneTarget({ type: 'pane', value: normalizedLeaderPaneId }, '', '', '', {});
266
266
  const paneTarget = safeString(resolved?.paneTarget).trim();
267
267
  if (paneTarget) return paneTarget;
268
268
  } catch {
@@ -541,8 +541,8 @@ export async function maybeNotifyLeaderWorkerIdle({ cwd, stateDir, logsDir, pars
541
541
  await rename(tmpPath, prevStatePath);
542
542
  } catch { /* best effort */ }
543
543
 
544
- // Only fire on working->idle transition (non-idle to idle)
545
- if (currentState !== 'idle') return;
544
+ // Fire when a worker leaves active work into an idle-ish terminal state.
545
+ if (currentState !== 'idle' && currentState !== 'done') return;
546
546
  if (!statusFresh) return;
547
547
  if (prevState === 'idle' || prevState === 'done') return;
548
548
 
@@ -608,7 +608,7 @@ export async function maybeNotifyLeaderWorkerIdle({ cwd, stateDir, logsDir, pars
608
608
  }
609
609
 
610
610
  // Build notification message with context
611
- const parts = [`[OMX] ${workerName} idle`];
611
+ const parts = [`[OMX] ${workerName} ${currentState}`];
612
612
  if (prevState && prevState !== 'unknown') parts.push(`(was: ${prevState})`);
613
613
  if (currentTaskId) parts.push(`task: ${currentTaskId}`);
614
614
  if (currentReason) parts.push(`reason: ${currentReason}`);