oh-my-codex 0.12.5 → 0.12.6

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 (386) hide show
  1. package/Cargo.lock +5 -5
  2. package/Cargo.toml +1 -1
  3. package/README.md +17 -0
  4. package/dist/cli/__tests__/ask.test.js +26 -0
  5. package/dist/cli/__tests__/ask.test.js.map +1 -1
  6. package/dist/cli/__tests__/explore.test.js +95 -8
  7. package/dist/cli/__tests__/explore.test.js.map +1 -1
  8. package/dist/cli/__tests__/index.test.js +58 -0
  9. package/dist/cli/__tests__/index.test.js.map +1 -1
  10. package/dist/cli/__tests__/launch-fallback.test.js +169 -0
  11. package/dist/cli/__tests__/launch-fallback.test.js.map +1 -1
  12. package/dist/cli/__tests__/mcp-parity.test.js +31 -0
  13. package/dist/cli/__tests__/mcp-parity.test.js.map +1 -1
  14. package/dist/cli/__tests__/setup-agents-overwrite.test.js +66 -2
  15. package/dist/cli/__tests__/setup-agents-overwrite.test.js.map +1 -1
  16. package/dist/cli/__tests__/setup-refresh.test.js +11 -1
  17. package/dist/cli/__tests__/setup-refresh.test.js.map +1 -1
  18. package/dist/cli/__tests__/team.test.js +92 -0
  19. package/dist/cli/__tests__/team.test.js.map +1 -1
  20. package/dist/cli/__tests__/uninstall.test.js +14 -1
  21. package/dist/cli/__tests__/uninstall.test.js.map +1 -1
  22. package/dist/cli/cleanup.js +1 -1
  23. package/dist/cli/cleanup.js.map +1 -1
  24. package/dist/cli/constants.d.ts +1 -0
  25. package/dist/cli/constants.d.ts.map +1 -1
  26. package/dist/cli/constants.js +1 -0
  27. package/dist/cli/constants.js.map +1 -1
  28. package/dist/cli/explore.d.ts +1 -0
  29. package/dist/cli/explore.d.ts.map +1 -1
  30. package/dist/cli/explore.js +45 -1
  31. package/dist/cli/explore.js.map +1 -1
  32. package/dist/cli/index.d.ts +1 -1
  33. package/dist/cli/index.d.ts.map +1 -1
  34. package/dist/cli/index.js +103 -10
  35. package/dist/cli/index.js.map +1 -1
  36. package/dist/cli/mcp-parity.d.ts +1 -1
  37. package/dist/cli/mcp-parity.d.ts.map +1 -1
  38. package/dist/cli/mcp-parity.js +24 -0
  39. package/dist/cli/mcp-parity.js.map +1 -1
  40. package/dist/cli/setup.d.ts.map +1 -1
  41. package/dist/cli/setup.js +2 -2
  42. package/dist/cli/setup.js.map +1 -1
  43. package/dist/cli/team.d.ts.map +1 -1
  44. package/dist/cli/team.js +54 -14
  45. package/dist/cli/team.js.map +1 -1
  46. package/dist/cli/uninstall.d.ts.map +1 -1
  47. package/dist/cli/uninstall.js +1 -0
  48. package/dist/cli/uninstall.js.map +1 -1
  49. package/dist/config/__tests__/generator-idempotent.test.js +1 -0
  50. package/dist/config/__tests__/generator-idempotent.test.js.map +1 -1
  51. package/dist/config/__tests__/mcp-registry.test.js +61 -0
  52. package/dist/config/__tests__/mcp-registry.test.js.map +1 -1
  53. package/dist/config/__tests__/wiki-config-contract.test.d.ts +2 -0
  54. package/dist/config/__tests__/wiki-config-contract.test.d.ts.map +1 -0
  55. package/dist/config/__tests__/wiki-config-contract.test.js +19 -0
  56. package/dist/config/__tests__/wiki-config-contract.test.js.map +1 -0
  57. package/dist/config/generator.d.ts.map +1 -1
  58. package/dist/config/generator.js +8 -0
  59. package/dist/config/generator.js.map +1 -1
  60. package/dist/config/mcp-registry.d.ts +2 -0
  61. package/dist/config/mcp-registry.d.ts.map +1 -1
  62. package/dist/config/mcp-registry.js +12 -0
  63. package/dist/config/mcp-registry.js.map +1 -1
  64. package/dist/hooks/__tests__/agents-overlay.test.js +18 -0
  65. package/dist/hooks/__tests__/agents-overlay.test.js.map +1 -1
  66. package/dist/hooks/__tests__/keyword-detector.test.js +75 -0
  67. package/dist/hooks/__tests__/keyword-detector.test.js.map +1 -1
  68. package/dist/hooks/__tests__/notify-fallback-watcher.test.js +140 -1
  69. package/dist/hooks/__tests__/notify-fallback-watcher.test.js.map +1 -1
  70. package/dist/hooks/__tests__/notify-hook-auto-nudge.test.js +35 -0
  71. package/dist/hooks/__tests__/notify-hook-auto-nudge.test.js.map +1 -1
  72. package/dist/hooks/__tests__/notify-hook-managed-tmux.test.js +86 -2
  73. package/dist/hooks/__tests__/notify-hook-managed-tmux.test.js.map +1 -1
  74. package/dist/hooks/__tests__/notify-hook-team-dispatch.test.js +176 -1
  75. package/dist/hooks/__tests__/notify-hook-team-dispatch.test.js.map +1 -1
  76. package/dist/hooks/__tests__/notify-hook-team-leader-nudge.test.js +193 -0
  77. package/dist/hooks/__tests__/notify-hook-team-leader-nudge.test.js.map +1 -1
  78. package/dist/hooks/__tests__/notify-hook-tmux-heal.test.js +90 -2
  79. package/dist/hooks/__tests__/notify-hook-tmux-heal.test.js.map +1 -1
  80. package/dist/hooks/__tests__/session.test.js +41 -1
  81. package/dist/hooks/__tests__/session.test.js.map +1 -1
  82. package/dist/hooks/__tests__/wiki-docs-contract.test.d.ts +2 -0
  83. package/dist/hooks/__tests__/wiki-docs-contract.test.d.ts.map +1 -0
  84. package/dist/hooks/__tests__/wiki-docs-contract.test.js +34 -0
  85. package/dist/hooks/__tests__/wiki-docs-contract.test.js.map +1 -0
  86. package/dist/hooks/agents-overlay.d.ts.map +1 -1
  87. package/dist/hooks/agents-overlay.js +0 -1
  88. package/dist/hooks/agents-overlay.js.map +1 -1
  89. package/dist/hooks/extensibility/__tests__/dispatcher.test.js +32 -0
  90. package/dist/hooks/extensibility/__tests__/dispatcher.test.js.map +1 -1
  91. package/dist/hooks/extensibility/__tests__/sdk.test.js +33 -3
  92. package/dist/hooks/extensibility/__tests__/sdk.test.js.map +1 -1
  93. package/dist/hooks/extensibility/dispatcher.d.ts.map +1 -1
  94. package/dist/hooks/extensibility/dispatcher.js +41 -0
  95. package/dist/hooks/extensibility/dispatcher.js.map +1 -1
  96. package/dist/hooks/extensibility/sdk/runtime-state.d.ts.map +1 -1
  97. package/dist/hooks/extensibility/sdk/runtime-state.js +7 -1
  98. package/dist/hooks/extensibility/sdk/runtime-state.js.map +1 -1
  99. package/dist/hooks/keyword-detector.d.ts.map +1 -1
  100. package/dist/hooks/keyword-detector.js +42 -5
  101. package/dist/hooks/keyword-detector.js.map +1 -1
  102. package/dist/hooks/keyword-registry.d.ts.map +1 -1
  103. package/dist/hooks/keyword-registry.js +3 -0
  104. package/dist/hooks/keyword-registry.js.map +1 -1
  105. package/dist/hooks/session.d.ts.map +1 -1
  106. package/dist/hooks/session.js +26 -4
  107. package/dist/hooks/session.js.map +1 -1
  108. package/dist/hud/__tests__/state.test.js +27 -0
  109. package/dist/hud/__tests__/state.test.js.map +1 -1
  110. package/dist/hud/state.d.ts.map +1 -1
  111. package/dist/hud/state.js +3 -1
  112. package/dist/hud/state.js.map +1 -1
  113. package/dist/mcp/__tests__/bootstrap.test.js +65 -1
  114. package/dist/mcp/__tests__/bootstrap.test.js.map +1 -1
  115. package/dist/mcp/__tests__/state-server.test.js +31 -0
  116. package/dist/mcp/__tests__/state-server.test.js.map +1 -1
  117. package/dist/mcp/__tests__/wiki-server.test.d.ts +2 -0
  118. package/dist/mcp/__tests__/wiki-server.test.d.ts.map +1 -0
  119. package/dist/mcp/__tests__/wiki-server.test.js +30 -0
  120. package/dist/mcp/__tests__/wiki-server.test.js.map +1 -0
  121. package/dist/mcp/bootstrap.d.ts +18 -1
  122. package/dist/mcp/bootstrap.d.ts.map +1 -1
  123. package/dist/mcp/bootstrap.js +158 -0
  124. package/dist/mcp/bootstrap.js.map +1 -1
  125. package/dist/mcp/state-server.d.ts +4 -4
  126. package/dist/mcp/state-server.d.ts.map +1 -1
  127. package/dist/mcp/state-server.js +27 -22
  128. package/dist/mcp/state-server.js.map +1 -1
  129. package/dist/mcp/wiki-server.d.ts +181 -0
  130. package/dist/mcp/wiki-server.d.ts.map +1 -0
  131. package/dist/mcp/wiki-server.js +235 -0
  132. package/dist/mcp/wiki-server.js.map +1 -0
  133. package/dist/modes/__tests__/base-autoresearch-contract.test.js +22 -1
  134. package/dist/modes/__tests__/base-autoresearch-contract.test.js.map +1 -1
  135. package/dist/modes/base.d.ts.map +1 -1
  136. package/dist/modes/base.js +10 -0
  137. package/dist/modes/base.js.map +1 -1
  138. package/dist/notifications/__tests__/formatter.test.js +11 -0
  139. package/dist/notifications/__tests__/formatter.test.js.map +1 -1
  140. package/dist/notifications/__tests__/idle-cooldown.test.js +32 -1
  141. package/dist/notifications/__tests__/idle-cooldown.test.js.map +1 -1
  142. package/dist/notifications/__tests__/index.test.d.ts +2 -0
  143. package/dist/notifications/__tests__/index.test.d.ts.map +1 -0
  144. package/dist/notifications/__tests__/index.test.js +113 -0
  145. package/dist/notifications/__tests__/index.test.js.map +1 -0
  146. package/dist/notifications/__tests__/lifecycle-dedupe.test.d.ts +2 -0
  147. package/dist/notifications/__tests__/lifecycle-dedupe.test.d.ts.map +1 -0
  148. package/dist/notifications/__tests__/lifecycle-dedupe.test.js +86 -0
  149. package/dist/notifications/__tests__/lifecycle-dedupe.test.js.map +1 -0
  150. package/dist/notifications/__tests__/reply-listener.test.js +155 -0
  151. package/dist/notifications/__tests__/reply-listener.test.js.map +1 -1
  152. package/dist/notifications/__tests__/session-idle-tail-dedupe.test.d.ts +2 -0
  153. package/dist/notifications/__tests__/session-idle-tail-dedupe.test.d.ts.map +1 -0
  154. package/dist/notifications/__tests__/session-idle-tail-dedupe.test.js +93 -0
  155. package/dist/notifications/__tests__/session-idle-tail-dedupe.test.js.map +1 -0
  156. package/dist/notifications/__tests__/session-registry.test.js +48 -1
  157. package/dist/notifications/__tests__/session-registry.test.js.map +1 -1
  158. package/dist/notifications/__tests__/session-status.test.d.ts +2 -0
  159. package/dist/notifications/__tests__/session-status.test.d.ts.map +1 -0
  160. package/dist/notifications/__tests__/session-status.test.js +159 -0
  161. package/dist/notifications/__tests__/session-status.test.js.map +1 -0
  162. package/dist/notifications/__tests__/tmux.test.js +58 -1
  163. package/dist/notifications/__tests__/tmux.test.js.map +1 -1
  164. package/dist/notifications/idle-cooldown.d.ts +11 -0
  165. package/dist/notifications/idle-cooldown.d.ts.map +1 -1
  166. package/dist/notifications/idle-cooldown.js +42 -8
  167. package/dist/notifications/idle-cooldown.js.map +1 -1
  168. package/dist/notifications/index.d.ts +1 -1
  169. package/dist/notifications/index.d.ts.map +1 -1
  170. package/dist/notifications/index.js +41 -8
  171. package/dist/notifications/index.js.map +1 -1
  172. package/dist/notifications/lifecycle-dedupe.d.ts +8 -0
  173. package/dist/notifications/lifecycle-dedupe.d.ts.map +1 -0
  174. package/dist/notifications/lifecycle-dedupe.js +112 -0
  175. package/dist/notifications/lifecycle-dedupe.js.map +1 -0
  176. package/dist/notifications/reply-listener.d.ts +2 -0
  177. package/dist/notifications/reply-listener.d.ts.map +1 -1
  178. package/dist/notifications/reply-listener.js +42 -6
  179. package/dist/notifications/reply-listener.js.map +1 -1
  180. package/dist/notifications/session-registry.d.ts.map +1 -1
  181. package/dist/notifications/session-registry.js +7 -1
  182. package/dist/notifications/session-registry.js.map +1 -1
  183. package/dist/notifications/session-status.d.ts +23 -0
  184. package/dist/notifications/session-status.d.ts.map +1 -0
  185. package/dist/notifications/session-status.js +187 -0
  186. package/dist/notifications/session-status.js.map +1 -0
  187. package/dist/notifications/tmux.d.ts +10 -0
  188. package/dist/notifications/tmux.d.ts.map +1 -1
  189. package/dist/notifications/tmux.js +59 -5
  190. package/dist/notifications/tmux.js.map +1 -1
  191. package/dist/notifications/types.d.ts +2 -0
  192. package/dist/notifications/types.d.ts.map +1 -1
  193. package/dist/openclaw/__tests__/index.test.js +84 -0
  194. package/dist/openclaw/__tests__/index.test.js.map +1 -1
  195. package/dist/openclaw/index.d.ts.map +1 -1
  196. package/dist/openclaw/index.js +7 -14
  197. package/dist/openclaw/index.js.map +1 -1
  198. package/dist/openclaw/types.d.ts +2 -2
  199. package/dist/openclaw/types.d.ts.map +1 -1
  200. package/dist/scripts/__tests__/codex-native-hook.test.js +270 -1
  201. package/dist/scripts/__tests__/codex-native-hook.test.js.map +1 -1
  202. package/dist/scripts/__tests__/hook-derived-watcher.test.d.ts +2 -0
  203. package/dist/scripts/__tests__/hook-derived-watcher.test.d.ts.map +1 -0
  204. package/dist/scripts/__tests__/hook-derived-watcher.test.js +87 -0
  205. package/dist/scripts/__tests__/hook-derived-watcher.test.js.map +1 -0
  206. package/dist/scripts/codex-native-hook.d.ts.map +1 -1
  207. package/dist/scripts/codex-native-hook.js +134 -6
  208. package/dist/scripts/codex-native-hook.js.map +1 -1
  209. package/dist/scripts/hook-derived-watcher.js +43 -1
  210. package/dist/scripts/hook-derived-watcher.js.map +1 -1
  211. package/dist/scripts/notify-fallback-watcher.js +27 -2
  212. package/dist/scripts/notify-fallback-watcher.js.map +1 -1
  213. package/dist/scripts/notify-hook/auto-nudge.d.ts.map +1 -1
  214. package/dist/scripts/notify-hook/auto-nudge.js +17 -1
  215. package/dist/scripts/notify-hook/auto-nudge.js.map +1 -1
  216. package/dist/scripts/notify-hook/managed-tmux.d.ts.map +1 -1
  217. package/dist/scripts/notify-hook/managed-tmux.js +5 -3
  218. package/dist/scripts/notify-hook/managed-tmux.js.map +1 -1
  219. package/dist/scripts/notify-hook/team-dispatch.d.ts.map +1 -1
  220. package/dist/scripts/notify-hook/team-dispatch.js +305 -167
  221. package/dist/scripts/notify-hook/team-dispatch.js.map +1 -1
  222. package/dist/scripts/notify-hook/team-leader-nudge.d.ts.map +1 -1
  223. package/dist/scripts/notify-hook/team-leader-nudge.js +79 -3
  224. package/dist/scripts/notify-hook/team-leader-nudge.js.map +1 -1
  225. package/dist/scripts/notify-hook/tmux-injection.d.ts.map +1 -1
  226. package/dist/scripts/notify-hook/tmux-injection.js +2 -1
  227. package/dist/scripts/notify-hook/tmux-injection.js.map +1 -1
  228. package/dist/scripts/run-provider-advisor.js +20 -2
  229. package/dist/scripts/run-provider-advisor.js.map +1 -1
  230. package/dist/scripts/smoke-packed-install.d.ts +1 -8
  231. package/dist/scripts/smoke-packed-install.d.ts.map +1 -1
  232. package/dist/scripts/smoke-packed-install.js +12 -68
  233. package/dist/scripts/smoke-packed-install.js.map +1 -1
  234. package/dist/state/__tests__/operations.test.js +113 -0
  235. package/dist/state/__tests__/operations.test.js.map +1 -1
  236. package/dist/state/operations.d.ts +1 -1
  237. package/dist/state/operations.d.ts.map +1 -1
  238. package/dist/state/operations.js +88 -2
  239. package/dist/state/operations.js.map +1 -1
  240. package/dist/state/skill-active.d.ts +1 -1
  241. package/dist/state/skill-active.d.ts.map +1 -1
  242. package/dist/state/skill-active.js +1 -0
  243. package/dist/state/skill-active.js.map +1 -1
  244. package/dist/team/__tests__/api-interop.test.js +90 -0
  245. package/dist/team/__tests__/api-interop.test.js.map +1 -1
  246. package/dist/team/__tests__/hardening-e2e.test.js +17 -0
  247. package/dist/team/__tests__/hardening-e2e.test.js.map +1 -1
  248. package/dist/team/__tests__/runtime.test.js +23 -0
  249. package/dist/team/__tests__/runtime.test.js.map +1 -1
  250. package/dist/team/__tests__/tmux-session.test.js +210 -0
  251. package/dist/team/__tests__/tmux-session.test.js.map +1 -1
  252. package/dist/team/api-interop.d.ts.map +1 -1
  253. package/dist/team/api-interop.js +10 -1
  254. package/dist/team/api-interop.js.map +1 -1
  255. package/dist/team/delivery-log.d.ts +1 -1
  256. package/dist/team/delivery-log.d.ts.map +1 -1
  257. package/dist/team/delivery-log.js.map +1 -1
  258. package/dist/team/leader-activity.d.ts +1 -0
  259. package/dist/team/leader-activity.d.ts.map +1 -1
  260. package/dist/team/leader-activity.js +4 -2
  261. package/dist/team/leader-activity.js.map +1 -1
  262. package/dist/team/progress-evidence.d.ts +2 -0
  263. package/dist/team/progress-evidence.d.ts.map +1 -0
  264. package/dist/team/progress-evidence.js +77 -0
  265. package/dist/team/progress-evidence.js.map +1 -0
  266. package/dist/team/runtime.d.ts.map +1 -1
  267. package/dist/team/runtime.js +44 -0
  268. package/dist/team/runtime.js.map +1 -1
  269. package/dist/team/tmux-session.d.ts.map +1 -1
  270. package/dist/team/tmux-session.js +57 -8
  271. package/dist/team/tmux-session.js.map +1 -1
  272. package/dist/team/worktree.d.ts +6 -1
  273. package/dist/team/worktree.d.ts.map +1 -1
  274. package/dist/team/worktree.js +4 -2
  275. package/dist/team/worktree.js.map +1 -1
  276. package/dist/utils/__tests__/agents-md.test.js +21 -1
  277. package/dist/utils/__tests__/agents-md.test.js.map +1 -1
  278. package/dist/utils/__tests__/repo-deps.test.d.ts +2 -0
  279. package/dist/utils/__tests__/repo-deps.test.d.ts.map +1 -0
  280. package/dist/utils/__tests__/repo-deps.test.js +71 -0
  281. package/dist/utils/__tests__/repo-deps.test.js.map +1 -0
  282. package/dist/utils/agents-md.d.ts +1 -0
  283. package/dist/utils/agents-md.d.ts.map +1 -1
  284. package/dist/utils/agents-md.js +7 -3
  285. package/dist/utils/agents-md.js.map +1 -1
  286. package/dist/utils/paths.d.ts +4 -0
  287. package/dist/utils/paths.d.ts.map +1 -1
  288. package/dist/utils/paths.js +20 -0
  289. package/dist/utils/paths.js.map +1 -1
  290. package/dist/utils/repo-deps.d.ts +20 -0
  291. package/dist/utils/repo-deps.d.ts.map +1 -0
  292. package/dist/utils/repo-deps.js +78 -0
  293. package/dist/utils/repo-deps.js.map +1 -0
  294. package/dist/verification/__tests__/dev-merge-issue-close-workflow.test.d.ts +2 -0
  295. package/dist/verification/__tests__/dev-merge-issue-close-workflow.test.d.ts.map +1 -0
  296. package/dist/verification/__tests__/dev-merge-issue-close-workflow.test.js +54 -0
  297. package/dist/verification/__tests__/dev-merge-issue-close-workflow.test.js.map +1 -0
  298. package/dist/wiki/__tests__/cjk-tokenize.test.d.ts +12 -0
  299. package/dist/wiki/__tests__/cjk-tokenize.test.d.ts.map +1 -0
  300. package/dist/wiki/__tests__/cjk-tokenize.test.js +139 -0
  301. package/dist/wiki/__tests__/cjk-tokenize.test.js.map +1 -0
  302. package/dist/wiki/__tests__/crlf-parse.test.d.ts +2 -0
  303. package/dist/wiki/__tests__/crlf-parse.test.d.ts.map +1 -0
  304. package/dist/wiki/__tests__/crlf-parse.test.js +24 -0
  305. package/dist/wiki/__tests__/crlf-parse.test.js.map +1 -0
  306. package/dist/wiki/__tests__/escape-newline.test.d.ts +2 -0
  307. package/dist/wiki/__tests__/escape-newline.test.d.ts.map +1 -0
  308. package/dist/wiki/__tests__/escape-newline.test.js +45 -0
  309. package/dist/wiki/__tests__/escape-newline.test.js.map +1 -0
  310. package/dist/wiki/__tests__/ingest.test.d.ts +5 -0
  311. package/dist/wiki/__tests__/ingest.test.d.ts.map +1 -0
  312. package/dist/wiki/__tests__/ingest.test.js +181 -0
  313. package/dist/wiki/__tests__/ingest.test.js.map +1 -0
  314. package/dist/wiki/__tests__/lint.test.d.ts +5 -0
  315. package/dist/wiki/__tests__/lint.test.d.ts.map +1 -0
  316. package/dist/wiki/__tests__/lint.test.js +163 -0
  317. package/dist/wiki/__tests__/lint.test.js.map +1 -0
  318. package/dist/wiki/__tests__/query.test.d.ts +5 -0
  319. package/dist/wiki/__tests__/query.test.d.ts.map +1 -0
  320. package/dist/wiki/__tests__/query.test.js +141 -0
  321. package/dist/wiki/__tests__/query.test.js.map +1 -0
  322. package/dist/wiki/__tests__/reserved-file-guard.test.d.ts +2 -0
  323. package/dist/wiki/__tests__/reserved-file-guard.test.d.ts.map +1 -0
  324. package/dist/wiki/__tests__/reserved-file-guard.test.js +44 -0
  325. package/dist/wiki/__tests__/reserved-file-guard.test.js.map +1 -0
  326. package/dist/wiki/__tests__/session-hooks.test.d.ts +5 -0
  327. package/dist/wiki/__tests__/session-hooks.test.d.ts.map +1 -0
  328. package/dist/wiki/__tests__/session-hooks.test.js +36 -0
  329. package/dist/wiki/__tests__/session-hooks.test.js.map +1 -0
  330. package/dist/wiki/__tests__/slug-nonascii.test.d.ts +2 -0
  331. package/dist/wiki/__tests__/slug-nonascii.test.d.ts.map +1 -0
  332. package/dist/wiki/__tests__/slug-nonascii.test.js +24 -0
  333. package/dist/wiki/__tests__/slug-nonascii.test.js.map +1 -0
  334. package/dist/wiki/__tests__/storage.test.d.ts +5 -0
  335. package/dist/wiki/__tests__/storage.test.d.ts.map +1 -0
  336. package/dist/wiki/__tests__/storage.test.js +278 -0
  337. package/dist/wiki/__tests__/storage.test.js.map +1 -0
  338. package/dist/wiki/__tests__/test-helpers.d.ts +31 -0
  339. package/dist/wiki/__tests__/test-helpers.d.ts.map +1 -0
  340. package/dist/wiki/__tests__/test-helpers.js +108 -0
  341. package/dist/wiki/__tests__/test-helpers.js.map +1 -0
  342. package/dist/wiki/index.d.ts +14 -0
  343. package/dist/wiki/index.d.ts.map +1 -0
  344. package/dist/wiki/index.js +17 -0
  345. package/dist/wiki/index.js.map +1 -0
  346. package/dist/wiki/ingest.d.ts +20 -0
  347. package/dist/wiki/ingest.d.ts.map +1 -0
  348. package/dist/wiki/ingest.js +115 -0
  349. package/dist/wiki/ingest.js.map +1 -0
  350. package/dist/wiki/lifecycle.d.ts +20 -0
  351. package/dist/wiki/lifecycle.d.ts.map +1 -0
  352. package/dist/wiki/lifecycle.js +212 -0
  353. package/dist/wiki/lifecycle.js.map +1 -0
  354. package/dist/wiki/lint.d.ts +25 -0
  355. package/dist/wiki/lint.d.ts.map +1 -0
  356. package/dist/wiki/lint.js +166 -0
  357. package/dist/wiki/lint.js.map +1 -0
  358. package/dist/wiki/query.d.ts +36 -0
  359. package/dist/wiki/query.d.ts.map +1 -0
  360. package/dist/wiki/query.js +138 -0
  361. package/dist/wiki/query.js.map +1 -0
  362. package/dist/wiki/storage.d.ts +33 -0
  363. package/dist/wiki/storage.d.ts.map +1 -0
  364. package/dist/wiki/storage.js +321 -0
  365. package/dist/wiki/storage.js.map +1 -0
  366. package/dist/wiki/types.d.ts +83 -0
  367. package/dist/wiki/types.d.ts.map +1 -0
  368. package/dist/wiki/types.js +15 -0
  369. package/dist/wiki/types.js.map +1 -0
  370. package/package.json +3 -1
  371. package/skills/configure-notifications/SKILL.md +1 -0
  372. package/skills/doctor/SKILL.md +11 -0
  373. package/skills/omx-setup/SKILL.md +1 -1
  374. package/skills/wiki/SKILL.md +57 -0
  375. package/src/scripts/__tests__/codex-native-hook.test.ts +378 -0
  376. package/src/scripts/__tests__/hook-derived-watcher.test.ts +111 -0
  377. package/src/scripts/codex-native-hook.ts +187 -7
  378. package/src/scripts/hook-derived-watcher.ts +43 -1
  379. package/src/scripts/notify-fallback-watcher.ts +26 -2
  380. package/src/scripts/notify-hook/auto-nudge.ts +19 -1
  381. package/src/scripts/notify-hook/managed-tmux.ts +8 -3
  382. package/src/scripts/notify-hook/team-dispatch.ts +326 -168
  383. package/src/scripts/notify-hook/team-leader-nudge.ts +82 -3
  384. package/src/scripts/notify-hook/tmux-injection.ts +2 -1
  385. package/src/scripts/run-provider-advisor.ts +20 -2
  386. package/src/scripts/smoke-packed-install.ts +16 -83
@@ -97,6 +97,7 @@ async function writeJsonAtomic(path, value) {
97
97
 
98
98
  // Keep stale-timeout semantics aligned with src/team/state.ts LOCK_STALE_MS.
99
99
  const DISPATCH_LOCK_STALE_MS = 5 * 60 * 1000;
100
+ const DISPATCH_REQUEST_LEASE_STALE_MS = 30 * 1000;
100
101
  const DEFAULT_ISSUE_DISPATCH_COOLDOWN_MS = 15 * 60 * 1000;
101
102
  const ISSUE_DISPATCH_COOLDOWN_ENV = 'OMX_TEAM_DISPATCH_ISSUE_COOLDOWN_MS';
102
103
  const DEFAULT_DISPATCH_TRIGGER_COOLDOWN_MS = 30 * 1000;
@@ -183,6 +184,31 @@ function parseTriggerCooldownEntry(entry) {
183
184
  };
184
185
  }
185
186
 
187
+ function reserveDispatchCooldowns({
188
+ issueCooldownMs,
189
+ triggerCooldownMs,
190
+ issueCooldownByIssue,
191
+ triggerCooldownByKey,
192
+ issueKey,
193
+ triggerKey,
194
+ requestId,
195
+ reservedAt = Date.now(),
196
+ }) {
197
+ let mutated = false;
198
+ if (issueKey && issueCooldownMs > 0) {
199
+ issueCooldownByIssue[issueKey] = reservedAt;
200
+ mutated = true;
201
+ }
202
+ if (triggerKey && triggerCooldownMs > 0) {
203
+ triggerCooldownByKey[triggerKey] = {
204
+ at: reservedAt,
205
+ last_request_id: safeString(requestId).trim(),
206
+ };
207
+ mutated = true;
208
+ }
209
+ return mutated;
210
+ }
211
+
186
212
  async function withLockDirectory(lockDir, timeoutError, fn) {
187
213
  const ownerPath = join(lockDir, 'owner');
188
214
  const ownerToken = `${process.pid}.${Date.now()}.${Math.random().toString(16).slice(2)}`;
@@ -245,6 +271,49 @@ async function withMailboxLock(teamDirPath, workerName, fn) {
245
271
  );
246
272
  }
247
273
 
274
+ function dispatchRequestLeaseDir(teamDirPath, requestId) {
275
+ return join(teamDirPath, 'dispatch', `.processing-${safeString(requestId).trim()}`);
276
+ }
277
+
278
+ async function tryAcquireDispatchRequestLease(teamDirPath, requestId) {
279
+ const lockDir = dispatchRequestLeaseDir(teamDirPath, requestId);
280
+ const ownerPath = join(lockDir, 'owner');
281
+ const ownerToken = `${process.pid}.${Date.now()}.${Math.random().toString(16).slice(2)}`;
282
+ await mkdir(dirname(lockDir), { recursive: true });
283
+
284
+ while (true) {
285
+ try {
286
+ await mkdir(lockDir, { recursive: false });
287
+ await writeFile(ownerPath, ownerToken, 'utf8');
288
+ return { lockDir, ownerPath, ownerToken, requestId };
289
+ } catch (error) {
290
+ if (error?.code !== 'EEXIST') throw error;
291
+ try {
292
+ const info = await stat(lockDir);
293
+ if (Date.now() - info.mtimeMs > DISPATCH_REQUEST_LEASE_STALE_MS) {
294
+ await rm(lockDir, { recursive: true, force: true });
295
+ continue;
296
+ }
297
+ } catch {
298
+ // best effort
299
+ }
300
+ return null;
301
+ }
302
+ }
303
+ }
304
+
305
+ async function releaseDispatchRequestLease(lease) {
306
+ if (!lease?.lockDir || !lease?.ownerPath || !lease?.ownerToken) return;
307
+ try {
308
+ const currentOwner = await readFile(lease.ownerPath, 'utf8');
309
+ if (currentOwner.trim() === lease.ownerToken) {
310
+ await rm(lease.lockDir, { recursive: true, force: true });
311
+ }
312
+ } catch {
313
+ // best effort
314
+ }
315
+ }
316
+
248
317
  function resolveLeaderPaneId(config) {
249
318
  return safeString(config?.leader_pane_id).trim();
250
319
  }
@@ -336,6 +405,213 @@ async function appendLeaderNotificationDeferredEvent({
336
405
  await appendFile(eventsPath, JSON.stringify(event) + '\n').catch(() => {});
337
406
  }
338
407
 
408
+ async function finalizeClaimedDispatchRequest({
409
+ claim,
410
+ result,
411
+ teamName,
412
+ teamDirPath,
413
+ config,
414
+ cwd,
415
+ stateDir,
416
+ logsDir,
417
+ issueCooldownMs,
418
+ triggerCooldownMs,
419
+ }) {
420
+ const requestsPath = join(teamDirPath, 'dispatch', 'requests.json');
421
+ const issueKey = extractIssueKey(claim.request.trigger_message);
422
+ const triggerKey = normalizeTriggerKey(claim.request.trigger_message);
423
+ let summary = { processed: 0, skipped: 0, failed: 0 };
424
+
425
+ await withDispatchLock(teamDirPath, async () => {
426
+ const bridgeRequests = await readBridgeDispatchRequests(stateDir, teamName);
427
+ const usingLegacyRequests = bridgeRequests === null;
428
+ const requests = usingLegacyRequests ? await readJson(requestsPath, []) : bridgeRequests;
429
+ if (!Array.isArray(requests)) return;
430
+
431
+ const index = requests.findIndex((entry) => safeString(entry?.request_id).trim() === claim.request.request_id);
432
+ if (index < 0) return;
433
+
434
+ const request = requests[index];
435
+ if (!request || typeof request !== 'object' || shouldSkipRequest(request) || request.status !== 'pending') return;
436
+
437
+ const issueCooldownState = await readIssueCooldownState(teamDirPath);
438
+ const triggerCooldownState = await readTriggerCooldownState(teamDirPath);
439
+ const issueCooldownByIssue = issueCooldownState.by_issue || {};
440
+ const triggerCooldownByKey = triggerCooldownState.by_trigger || {};
441
+ const nowIso = new Date().toISOString();
442
+ let mutated = false;
443
+
444
+ mutated = reserveDispatchCooldowns({
445
+ issueCooldownMs,
446
+ triggerCooldownMs,
447
+ issueCooldownByIssue,
448
+ triggerCooldownByKey,
449
+ issueKey,
450
+ triggerKey,
451
+ requestId: request.request_id,
452
+ }) || mutated;
453
+
454
+ request.attempt_count = Number.isFinite(request.attempt_count) ? Math.max(0, request.attempt_count + 1) : 1;
455
+ request.updated_at = nowIso;
456
+
457
+ if (result.ok) {
458
+ const MAX_UNCONFIRMED_ATTEMPTS = 3;
459
+ if (result.reason === 'tmux_send_keys_unconfirmed' && request.attempt_count < MAX_UNCONFIRMED_ATTEMPTS) {
460
+ request.last_reason = result.reason;
461
+ summary.skipped += 1;
462
+ mutated = true;
463
+ await appendDispatchLog(logsDir, {
464
+ type: 'dispatch_unconfirmed_retry',
465
+ team: teamName,
466
+ request_id: request.request_id,
467
+ worker: request.to_worker,
468
+ attempt: request.attempt_count,
469
+ reason: result.reason,
470
+ ...buildDispatchAttemptEvidence(result),
471
+ });
472
+ await appendDeliveryTelemetry(logsDir, {
473
+ event: 'dispatch_result',
474
+ team: teamName,
475
+ request_id: request.request_id,
476
+ message_id: request.message_id || null,
477
+ to_worker: request.to_worker,
478
+ transport: 'send-keys',
479
+ result: 'retry',
480
+ reason: result.reason,
481
+ });
482
+ await emitOperationalHookEvent(cwd, 'retry-needed', {
483
+ team: teamName,
484
+ worker: request.to_worker,
485
+ request_id: request.request_id,
486
+ attempt: request.attempt_count,
487
+ command: request.trigger_message,
488
+ reason: result.reason,
489
+ status: 'retry-needed',
490
+ });
491
+ } else if (result.reason === 'tmux_send_keys_unconfirmed') {
492
+ request.status = 'failed';
493
+ request.failed_at = nowIso;
494
+ request.last_reason = 'unconfirmed_after_max_retries';
495
+ runtimeExec({ command: 'MarkFailed', request_id: request.request_id, reason: 'unconfirmed_after_max_retries' }, stateDir);
496
+ summary.processed += 1;
497
+ summary.failed += 1;
498
+ mutated = true;
499
+ await appendDispatchLog(logsDir, {
500
+ type: 'dispatch_failed',
501
+ team: teamName,
502
+ request_id: request.request_id,
503
+ worker: request.to_worker,
504
+ message_id: request.message_id || null,
505
+ reason: request.last_reason,
506
+ ...buildDispatchAttemptEvidence(result),
507
+ });
508
+ await appendDeliveryTelemetry(logsDir, {
509
+ event: 'dispatch_result',
510
+ team: teamName,
511
+ request_id: request.request_id,
512
+ message_id: request.message_id || null,
513
+ to_worker: request.to_worker,
514
+ transport: 'send-keys',
515
+ result: 'failed',
516
+ reason: request.last_reason,
517
+ });
518
+ await emitOperationalHookEvent(cwd, 'failed', {
519
+ team: teamName,
520
+ worker: request.to_worker,
521
+ request_id: request.request_id,
522
+ message_id: request.message_id || null,
523
+ command: request.trigger_message,
524
+ reason: request.last_reason,
525
+ error_summary: request.last_reason,
526
+ status: 'failed',
527
+ });
528
+ } else {
529
+ request.status = 'notified';
530
+ request.notified_at = nowIso;
531
+ request.last_reason = result.reason;
532
+ runtimeExec({ command: 'MarkNotified', request_id: request.request_id, channel: 'tmux' }, stateDir);
533
+ if (request.kind === 'mailbox' && request.message_id) {
534
+ runtimeExec({ command: 'MarkMailboxNotified', message_id: request.message_id }, stateDir);
535
+ if (usingLegacyRequests) {
536
+ await updateMailboxNotified(stateDir, teamName, request.to_worker, request.message_id).catch(() => {});
537
+ }
538
+ }
539
+ summary.processed += 1;
540
+ mutated = true;
541
+ await appendDispatchLog(logsDir, {
542
+ type: 'dispatch_notified',
543
+ team: teamName,
544
+ request_id: request.request_id,
545
+ worker: request.to_worker,
546
+ message_id: request.message_id || null,
547
+ reason: result.reason,
548
+ ...buildDispatchAttemptEvidence(result),
549
+ });
550
+ await appendDeliveryTelemetry(logsDir, {
551
+ event: 'dispatch_result',
552
+ team: teamName,
553
+ request_id: request.request_id,
554
+ message_id: request.message_id || null,
555
+ to_worker: request.to_worker,
556
+ transport: 'send-keys',
557
+ result: 'notified',
558
+ reason: result.reason,
559
+ });
560
+ }
561
+ } else {
562
+ request.status = 'failed';
563
+ request.failed_at = nowIso;
564
+ request.last_reason = result.reason;
565
+ runtimeExec({ command: 'MarkFailed', request_id: request.request_id, reason: result.reason }, stateDir);
566
+ summary.processed += 1;
567
+ summary.failed += 1;
568
+ mutated = true;
569
+ await appendDispatchLog(logsDir, {
570
+ type: 'dispatch_failed',
571
+ team: teamName,
572
+ request_id: request.request_id,
573
+ worker: request.to_worker,
574
+ message_id: request.message_id || null,
575
+ reason: result.reason,
576
+ ...buildDispatchAttemptEvidence(result),
577
+ });
578
+ await appendDeliveryTelemetry(logsDir, {
579
+ event: 'dispatch_result',
580
+ team: teamName,
581
+ request_id: request.request_id,
582
+ message_id: request.message_id || null,
583
+ to_worker: request.to_worker,
584
+ transport: 'send-keys',
585
+ result: 'failed',
586
+ reason: result.reason,
587
+ });
588
+ await emitOperationalHookEvent(cwd, result.reason === LEADER_PANE_MISSING_DEFERRED_REASON ? 'handoff-needed' : 'failed', {
589
+ team: teamName,
590
+ worker: request.to_worker,
591
+ request_id: request.request_id,
592
+ message_id: request.message_id || null,
593
+ command: request.trigger_message,
594
+ reason: result.reason,
595
+ ...(result.reason === LEADER_PANE_MISSING_DEFERRED_REASON
596
+ ? { status: 'handoff-needed' }
597
+ : { status: 'failed', error_summary: result.reason }),
598
+ });
599
+ }
600
+
601
+ if (!mutated) return;
602
+ issueCooldownState.by_issue = issueCooldownByIssue;
603
+ await writeJsonAtomic(issueCooldownStatePath(teamDirPath), issueCooldownState);
604
+ triggerCooldownState.by_trigger = triggerCooldownByKey;
605
+ await writeJsonAtomic(triggerCooldownStatePath(teamDirPath), triggerCooldownState);
606
+ await writeJsonAtomic(requestsPath, requests);
607
+ if (!usingLegacyRequests) {
608
+ await writeBridgeDispatchCompat(stateDir, teamName, requests);
609
+ }
610
+ });
611
+
612
+ return summary;
613
+ }
614
+
339
615
  function resolveWorkerCliForRequest(request, config) {
340
616
  const workers = Array.isArray(config?.workers) ? config.workers : [];
341
617
  const idx = Number.isFinite(request?.worker_index) ? Number(request.worker_index) : null;
@@ -602,8 +878,8 @@ export async function drainPendingTeamDispatch({
602
878
  const manifestPath = join(teamDirPath, 'manifest.v2.json');
603
879
  const configPath = join(teamDirPath, 'config.json');
604
880
  const requestsPath = join(teamDirPath, 'dispatch', 'requests.json');
605
-
606
881
  const config = await readJson(existsSync(manifestPath) ? manifestPath : configPath, {});
882
+ const claims = [];
607
883
  await withDispatchLock(teamDirPath, async () => {
608
884
  const bridgeRequests = await readBridgeDispatchRequests(stateDir, teamName);
609
885
  const usingLegacyRequests = bridgeRequests === null;
@@ -617,7 +893,7 @@ export async function drainPendingTeamDispatch({
617
893
 
618
894
  let mutated = false;
619
895
  for (const request of requests) {
620
- if (processed >= maxPerTick) break;
896
+ if (processed + claims.length >= maxPerTick) break;
621
897
  if (!request || typeof request !== 'object') continue;
622
898
  if (shouldSkipRequest(request)) {
623
899
  skipped += 1;
@@ -660,9 +936,6 @@ export async function drainPendingTeamDispatch({
660
936
  result: 'deferred',
661
937
  reason: LEADER_PANE_MISSING_DEFERRED_REASON,
662
938
  });
663
- // On the legacy fallback lane, requests.json still carries the queue
664
- // state for this deferred request; this event stays a progress
665
- // artifact for hook/watcher readers.
666
939
  await appendLeaderNotificationDeferredEvent({
667
940
  stateDir,
668
941
  teamName,
@@ -697,170 +970,24 @@ export async function drainPendingTeamDispatch({
697
970
  }
698
971
  }
699
972
 
700
- const result = await injector(request, config, resolve(cwd), stateDir);
701
- if (issueKey && issueCooldownMs > 0) {
702
- issueCooldownByIssue[issueKey] = Date.now();
703
- mutated = true;
704
- }
705
- if (triggerKey && triggerCooldownMs > 0) {
706
- triggerCooldownByKey[triggerKey] = {
707
- at: Date.now(),
708
- last_request_id: safeString(request.request_id).trim(),
709
- };
710
- mutated = true;
711
- }
712
- const nowIso = new Date().toISOString();
713
- request.attempt_count = Number.isFinite(request.attempt_count) ? Math.max(0, request.attempt_count + 1) : 1;
714
- request.updated_at = nowIso;
715
-
716
- if (result.ok) {
717
- // Unconfirmed sends: trigger text was still visible after retry
718
- // rounds. Leave as pending for the next tick to retry (up to 3
719
- // total attempts) rather than marking notified. Fixes #391.
720
- const MAX_UNCONFIRMED_ATTEMPTS = 3;
721
- if (result.reason === 'tmux_send_keys_unconfirmed' && request.attempt_count < MAX_UNCONFIRMED_ATTEMPTS) {
722
- request.last_reason = result.reason;
723
- mutated = true;
724
- skipped += 1;
725
- await appendDispatchLog(logsDir, {
726
- type: 'dispatch_unconfirmed_retry',
727
- team: teamName,
728
- request_id: request.request_id,
729
- worker: request.to_worker,
730
- attempt: request.attempt_count,
731
- reason: result.reason,
732
- ...buildDispatchAttemptEvidence(result),
733
- });
734
- await appendDeliveryTelemetry(logsDir, {
735
- event: 'dispatch_result',
736
- team: teamName,
737
- request_id: request.request_id,
738
- message_id: request.message_id || null,
739
- to_worker: request.to_worker,
740
- transport: 'send-keys',
741
- result: 'retry',
742
- reason: result.reason,
743
- });
744
- await emitOperationalHookEvent(cwd, 'retry-needed', {
745
- team: teamName,
746
- worker: request.to_worker,
747
- request_id: request.request_id,
748
- attempt: request.attempt_count,
749
- command: request.trigger_message,
750
- reason: result.reason,
751
- status: 'retry-needed',
752
- });
753
- continue;
754
- }
755
- if (result.reason === 'tmux_send_keys_unconfirmed') {
756
- request.status = 'failed';
757
- request.failed_at = nowIso;
758
- request.last_reason = 'unconfirmed_after_max_retries';
759
- runtimeExec({ command: 'MarkFailed', request_id: request.request_id, reason: 'unconfirmed_after_max_retries' }, stateDir);
760
- processed += 1;
761
- failed += 1;
762
- mutated = true;
763
- await appendDispatchLog(logsDir, {
764
- type: 'dispatch_failed',
765
- team: teamName,
766
- request_id: request.request_id,
767
- worker: request.to_worker,
768
- message_id: request.message_id || null,
769
- reason: request.last_reason,
770
- ...buildDispatchAttemptEvidence(result),
771
- });
772
- await appendDeliveryTelemetry(logsDir, {
773
- event: 'dispatch_result',
774
- team: teamName,
775
- request_id: request.request_id,
776
- message_id: request.message_id || null,
777
- to_worker: request.to_worker,
778
- transport: 'send-keys',
779
- result: 'failed',
780
- reason: request.last_reason,
781
- });
782
- await emitOperationalHookEvent(cwd, 'failed', {
783
- team: teamName,
784
- worker: request.to_worker,
785
- request_id: request.request_id,
786
- message_id: request.message_id || null,
787
- command: request.trigger_message,
788
- reason: request.last_reason,
789
- error_summary: request.last_reason,
790
- status: 'failed',
791
- });
792
- continue;
793
- }
794
- request.status = 'notified';
795
- request.notified_at = nowIso;
796
- request.last_reason = result.reason;
797
- runtimeExec({ command: 'MarkNotified', request_id: request.request_id, channel: 'tmux' }, stateDir);
798
- if (request.kind === 'mailbox' && request.message_id) {
799
- runtimeExec({ command: 'MarkMailboxNotified', message_id: request.message_id }, stateDir);
800
- if (usingLegacyRequests) {
801
- await updateMailboxNotified(stateDir, teamName, request.to_worker, request.message_id).catch(() => {});
802
- }
803
- }
804
- processed += 1;
805
- mutated = true;
806
- await appendDispatchLog(logsDir, {
807
- type: 'dispatch_notified',
808
- team: teamName,
809
- request_id: request.request_id,
810
- worker: request.to_worker,
811
- message_id: request.message_id || null,
812
- reason: result.reason,
813
- ...buildDispatchAttemptEvidence(result),
814
- });
815
- await appendDeliveryTelemetry(logsDir, {
816
- event: 'dispatch_result',
817
- team: teamName,
818
- request_id: request.request_id,
819
- message_id: request.message_id || null,
820
- to_worker: request.to_worker,
821
- transport: 'send-keys',
822
- result: 'notified',
823
- reason: result.reason,
824
- });
825
- } else {
826
- request.status = 'failed';
827
- request.failed_at = nowIso;
828
- request.last_reason = result.reason;
829
- runtimeExec({ command: 'MarkFailed', request_id: request.request_id, reason: result.reason }, stateDir);
830
- processed += 1;
831
- failed += 1;
832
- mutated = true;
833
- await appendDispatchLog(logsDir, {
834
- type: 'dispatch_failed',
835
- team: teamName,
836
- request_id: request.request_id,
837
- worker: request.to_worker,
838
- message_id: request.message_id || null,
839
- reason: result.reason,
840
- ...buildDispatchAttemptEvidence(result),
841
- });
842
- await appendDeliveryTelemetry(logsDir, {
843
- event: 'dispatch_result',
844
- team: teamName,
845
- request_id: request.request_id,
846
- message_id: request.message_id || null,
847
- to_worker: request.to_worker,
848
- transport: 'send-keys',
849
- result: 'failed',
850
- reason: result.reason,
851
- });
852
- await emitOperationalHookEvent(cwd, result.reason === LEADER_PANE_MISSING_DEFERRED_REASON ? 'handoff-needed' : 'failed', {
853
- team: teamName,
854
- worker: request.to_worker,
855
- request_id: request.request_id,
856
- message_id: request.message_id || null,
857
- command: request.trigger_message,
858
- reason: result.reason,
859
- ...(result.reason === LEADER_PANE_MISSING_DEFERRED_REASON
860
- ? { status: 'handoff-needed' }
861
- : { status: 'failed', error_summary: result.reason }),
862
- });
973
+ const lease = await tryAcquireDispatchRequestLease(teamDirPath, request.request_id);
974
+ if (!lease) {
975
+ skipped += 1;
976
+ continue;
863
977
  }
978
+ mutated = reserveDispatchCooldowns({
979
+ issueCooldownMs,
980
+ triggerCooldownMs,
981
+ issueCooldownByIssue,
982
+ triggerCooldownByKey,
983
+ issueKey,
984
+ triggerKey,
985
+ requestId: request.request_id,
986
+ }) || mutated;
987
+ claims.push({
988
+ request: { ...request },
989
+ lease,
990
+ });
864
991
  }
865
992
 
866
993
  if (mutated) {
@@ -874,6 +1001,37 @@ export async function drainPendingTeamDispatch({
874
1001
  }
875
1002
  }
876
1003
  });
1004
+
1005
+ try {
1006
+ for (const claim of claims) {
1007
+ try {
1008
+ const result = await injector(claim.request, config, resolve(cwd), stateDir);
1009
+ const delta = await finalizeClaimedDispatchRequest({
1010
+ claim,
1011
+ result,
1012
+ teamName,
1013
+ teamDirPath,
1014
+ config,
1015
+ cwd,
1016
+ stateDir,
1017
+ logsDir,
1018
+ issueCooldownMs,
1019
+ triggerCooldownMs,
1020
+ });
1021
+ processed += delta.processed;
1022
+ skipped += delta.skipped;
1023
+ failed += delta.failed;
1024
+ } finally {
1025
+ claim.released = true;
1026
+ await releaseDispatchRequestLease(claim.lease);
1027
+ }
1028
+ }
1029
+ } finally {
1030
+ for (const claim of claims) {
1031
+ if (claim.released) continue;
1032
+ await releaseDispatchRequestLease(claim.lease);
1033
+ }
1034
+ }
877
1035
  }
878
1036
 
879
1037
  return { processed, skipped, failed };
@@ -22,8 +22,10 @@ import { DEFAULT_MARKER } from '../tmux-hook-engine.js';
22
22
  import { isLeaderRuntimeStale } from '../../team/leader-activity.js';
23
23
  import { appendTeamDeliveryLog } from '../../team/delivery-log.js';
24
24
  import { writeTeamLeaderAttention } from '../../team/state.js';
25
+ import { readLatestTeamProgressEvidenceMs } from '../../team/progress-evidence.js';
25
26
  const LEADER_PANE_MISSING_NO_INJECTION_REASON = 'leader_pane_missing_no_injection';
26
27
  const LEADER_PANE_SHELL_NO_INJECTION_REASON = 'leader_pane_shell_no_injection';
28
+ const LEADER_PANE_SAME_CLASSIFIED_STATE_SUPPRESSED_REASON = 'pane_already_shows_same_classified_state';
27
29
  const LEADER_NOTIFICATION_DEFERRED_TYPE = 'leader_notification_deferred';
28
30
  const ACK_WITHOUT_START_EVIDENCE_REASON = 'ack_without_start_evidence';
29
31
  const ACK_LIKE_PATTERNS = [
@@ -438,6 +440,27 @@ function formatMailboxBodyForLeader(body, maxLength = 40) {
438
440
  return `${normalized.slice(0, maxLength - 1)}…`;
439
441
  }
440
442
 
443
+ function normalizeVisibleLeaderStateText(text) {
444
+ return safeString(text)
445
+ .toLowerCase()
446
+ .replace(/\[omx_tmux_inject\]/g, ' ')
447
+ .replace(/\[omx_intent:[^\]]+\]/g, ' ')
448
+ .replace(/said\s+"[^"]*"/g, 'said "<content>"')
449
+ .replace(/said\s+'[^']*'/g, 'said "<content>"')
450
+ .replace(/\b\d+[smhd](?:\s+\d+[smhd])*\b/g, '<duration>')
451
+ .replace(/\b\d+\b/g, '<n>')
452
+ .replace(/\s+/g, ' ')
453
+ .trim();
454
+ }
455
+
456
+ function paneAlreadyShowsVisibleLeaderState(paneCapture, visibleText) {
457
+ const normalizedVisibleText = normalizeVisibleLeaderStateText(visibleText);
458
+ if (!normalizedVisibleText) return false;
459
+ const normalizedPaneCapture = normalizeVisibleLeaderStateText(paneCapture);
460
+ if (!normalizedPaneCapture) return false;
461
+ return normalizedPaneCapture.includes(normalizedVisibleText);
462
+ }
463
+
441
464
  async function workerHasOwnedStartedTask(stateDir, teamName, workerName) {
442
465
  const tasksDir = join(stateDir, 'team', teamName, 'tasks');
443
466
  if (!existsSync(tasksDir)) return false;
@@ -674,11 +697,15 @@ export async function maybeNudgeTeamLeader({
674
697
  const workerTurnProgress = hasWorkerTurnProgress(progressSnapshot.workerSnapshot, previousTurnCounts);
675
698
  const hasTrackableTurnSignals = hasTrackableActiveWorkerTurns(progressSnapshot.workerSnapshot, previousTurnCounts);
676
699
  const progressChanged = !previousSignature || previousSignature !== progressSnapshot.signature || workerTurnProgress;
700
+ const extraProgressEvidenceMs = await readLatestTeamProgressEvidenceMs(cwd, teamName).catch(() => Number.NaN);
677
701
  const effectiveProgressAtMs = progressChanged || !Number.isFinite(previousProgressAtMs)
678
702
  ? nowMs
679
703
  : previousProgressAtMs;
680
- const effectiveProgressAtIso = new Date(effectiveProgressAtMs).toISOString();
681
- const stalledForMs = Math.max(0, nowMs - effectiveProgressAtMs);
704
+ const latestProgressEvidenceMs = Number.isFinite(extraProgressEvidenceMs)
705
+ ? Math.max(effectiveProgressAtMs, extraProgressEvidenceMs)
706
+ : effectiveProgressAtMs;
707
+ const effectiveProgressAtIso = new Date(latestProgressEvidenceMs).toISOString();
708
+ const stalledForMs = Math.max(0, nowMs - latestProgressEvidenceMs);
682
709
  const stallThresholdMs = hasTrackableTurnSignals ? workerTurnStallThresholdMs : fallbackProgressStallThresholdMs;
683
710
  const teamProgressStalled =
684
711
  progressSnapshot.workRemaining
@@ -686,6 +713,9 @@ export async function maybeNudgeTeamLeader({
686
713
  && !allWorkersIdle
687
714
  && !progressChanged
688
715
  && stalledForMs >= stallThresholdMs;
716
+ const hasFreshProgressEvidence =
717
+ progressSnapshot.workRemaining
718
+ && stalledForMs < stallThresholdMs;
689
719
  const leaderActionState = classifyLeaderActionState({
690
720
  allWorkersIdle,
691
721
  workerPanesAlive: paneStatus.alive,
@@ -722,7 +752,7 @@ export async function maybeNudgeTeamLeader({
722
752
 
723
753
  // Stale-leader follow-up is the only periodic visible nudge path.
724
754
  // This keeps the leader pane quieter when the leader is not actually stale.
725
- const stalePanesNudge = paneStatus.alive && leaderStale;
755
+ const stalePanesNudge = paneStatus.alive && leaderStale && !hasFreshProgressEvidence;
726
756
  const previousStalledTeamNudge = prevReason === 'stuck_waiting_on_leader';
727
757
  const stalledTeamNudge = teamProgressStalled && (dueByTime || !previousStalledTeamNudge);
728
758
  const staleFollowupDue = stalePanesNudge && dueByTime;
@@ -923,6 +953,55 @@ export async function maybeNudgeTeamLeader({
923
953
  continue;
924
954
  }
925
955
 
956
+ if (paneAlreadyShowsVisibleLeaderState(paneGuard.paneCapture, capped)) {
957
+ nudgeState.last_nudged_by_team[teamName] = {
958
+ at: nowIso,
959
+ last_message_id: newestId || prevMsgId || '',
960
+ reason: nudgeReason,
961
+ orchestration_intent: orchestrationIntent,
962
+ };
963
+ if (shouldSendAllIdleNudge) {
964
+ nudgeState.last_idle_nudged_by_team[teamName] = {
965
+ at: nowIso,
966
+ worker_count: workerNames.length,
967
+ orchestration_intent: orchestrationIntent,
968
+ };
969
+ }
970
+
971
+ await emitTeamNudgeEvent(cwd, teamName, nudgeReason, orchestrationIntent, nowIso);
972
+
973
+ try {
974
+ await logTmuxHookEvent(logsDir, {
975
+ timestamp: nowIso,
976
+ type: 'team_leader_nudge',
977
+ team: teamName,
978
+ tmux_target: tmuxTarget,
979
+ reason: nudgeReason,
980
+ orchestration_intent: orchestrationIntent,
981
+ pane_count: paneStatus.paneCount,
982
+ leader_stale: leaderStale,
983
+ message_count: messages.length,
984
+ stalled_for_ms: teamProgressStalled ? stalledForMs : undefined,
985
+ missing_signal_workers: progressSnapshot.missingSignalWorkers,
986
+ visible_injection_suppressed: true,
987
+ suppression_reason: LEADER_PANE_SAME_CLASSIFIED_STATE_SUPPRESSED_REASON,
988
+ });
989
+ } catch { /* ignore */ }
990
+ await appendTeamDeliveryLog(logsDir, {
991
+ event: 'nudge_triggered',
992
+ source,
993
+ team: teamName,
994
+ to_worker: 'leader-fixed',
995
+ transport: 'send-keys',
996
+ result: 'suppressed',
997
+ reason: nudgeReason,
998
+ orchestration_intent: orchestrationIntent,
999
+ visible_injection_suppressed: true,
1000
+ suppression_reason: LEADER_PANE_SAME_CLASSIFIED_STATE_SUPPRESSED_REASON,
1001
+ }).catch(() => {});
1002
+ continue;
1003
+ }
1004
+
926
1005
  try {
927
1006
  const sendResult = await sendPaneInput({
928
1007
  paneTarget: tmuxTarget,