oh-my-codex 0.12.4 → 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 (475) hide show
  1. package/Cargo.lock +5 -5
  2. package/Cargo.toml +1 -1
  3. package/README.md +27 -3
  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__/doctor-warning-copy.test.js +28 -0
  7. package/dist/cli/__tests__/doctor-warning-copy.test.js.map +1 -1
  8. package/dist/cli/__tests__/explore.test.js +95 -8
  9. package/dist/cli/__tests__/explore.test.js.map +1 -1
  10. package/dist/cli/__tests__/index.test.js +102 -4
  11. package/dist/cli/__tests__/index.test.js.map +1 -1
  12. package/dist/cli/__tests__/launch-fallback.test.js +169 -0
  13. package/dist/cli/__tests__/launch-fallback.test.js.map +1 -1
  14. package/dist/cli/__tests__/mcp-parity.test.js +31 -0
  15. package/dist/cli/__tests__/mcp-parity.test.js.map +1 -1
  16. package/dist/cli/__tests__/setup-agents-overwrite.test.js +66 -2
  17. package/dist/cli/__tests__/setup-agents-overwrite.test.js.map +1 -1
  18. package/dist/cli/__tests__/setup-refresh.test.js +51 -1
  19. package/dist/cli/__tests__/setup-refresh.test.js.map +1 -1
  20. package/dist/cli/__tests__/team.test.js +148 -3
  21. package/dist/cli/__tests__/team.test.js.map +1 -1
  22. package/dist/cli/__tests__/uninstall.test.js +14 -1
  23. package/dist/cli/__tests__/uninstall.test.js.map +1 -1
  24. package/dist/cli/cleanup.js +1 -1
  25. package/dist/cli/cleanup.js.map +1 -1
  26. package/dist/cli/constants.d.ts +1 -0
  27. package/dist/cli/constants.d.ts.map +1 -1
  28. package/dist/cli/constants.js +1 -0
  29. package/dist/cli/constants.js.map +1 -1
  30. package/dist/cli/doctor.d.ts.map +1 -1
  31. package/dist/cli/doctor.js +15 -0
  32. package/dist/cli/doctor.js.map +1 -1
  33. package/dist/cli/explore.d.ts +1 -0
  34. package/dist/cli/explore.d.ts.map +1 -1
  35. package/dist/cli/explore.js +49 -1
  36. package/dist/cli/explore.js.map +1 -1
  37. package/dist/cli/index.d.ts +2 -1
  38. package/dist/cli/index.d.ts.map +1 -1
  39. package/dist/cli/index.js +127 -14
  40. package/dist/cli/index.js.map +1 -1
  41. package/dist/cli/mcp-parity.d.ts +1 -1
  42. package/dist/cli/mcp-parity.d.ts.map +1 -1
  43. package/dist/cli/mcp-parity.js +24 -0
  44. package/dist/cli/mcp-parity.js.map +1 -1
  45. package/dist/cli/setup.d.ts.map +1 -1
  46. package/dist/cli/setup.js +17 -5
  47. package/dist/cli/setup.js.map +1 -1
  48. package/dist/cli/team.d.ts.map +1 -1
  49. package/dist/cli/team.js +80 -6
  50. package/dist/cli/team.js.map +1 -1
  51. package/dist/cli/uninstall.d.ts.map +1 -1
  52. package/dist/cli/uninstall.js +1 -0
  53. package/dist/cli/uninstall.js.map +1 -1
  54. package/dist/config/__tests__/generator-idempotent.test.js +60 -0
  55. package/dist/config/__tests__/generator-idempotent.test.js.map +1 -1
  56. package/dist/config/__tests__/mcp-registry.test.js +61 -0
  57. package/dist/config/__tests__/mcp-registry.test.js.map +1 -1
  58. package/dist/config/__tests__/wiki-config-contract.test.d.ts +2 -0
  59. package/dist/config/__tests__/wiki-config-contract.test.d.ts.map +1 -0
  60. package/dist/config/__tests__/wiki-config-contract.test.js +19 -0
  61. package/dist/config/__tests__/wiki-config-contract.test.js.map +1 -0
  62. package/dist/config/generator.d.ts +1 -0
  63. package/dist/config/generator.d.ts.map +1 -1
  64. package/dist/config/generator.js +88 -3
  65. package/dist/config/generator.js.map +1 -1
  66. package/dist/config/mcp-registry.d.ts +2 -0
  67. package/dist/config/mcp-registry.d.ts.map +1 -1
  68. package/dist/config/mcp-registry.js +12 -0
  69. package/dist/config/mcp-registry.js.map +1 -1
  70. package/dist/hooks/__tests__/agents-overlay.test.js +39 -0
  71. package/dist/hooks/__tests__/agents-overlay.test.js.map +1 -1
  72. package/dist/hooks/__tests__/keyword-detector.test.js +297 -4
  73. package/dist/hooks/__tests__/keyword-detector.test.js.map +1 -1
  74. package/dist/hooks/__tests__/notify-fallback-watcher.test.js +392 -22
  75. package/dist/hooks/__tests__/notify-fallback-watcher.test.js.map +1 -1
  76. package/dist/hooks/__tests__/notify-hook-auto-nudge.test.js +166 -67
  77. package/dist/hooks/__tests__/notify-hook-auto-nudge.test.js.map +1 -1
  78. package/dist/hooks/__tests__/notify-hook-managed-tmux.test.js +112 -2
  79. package/dist/hooks/__tests__/notify-hook-managed-tmux.test.js.map +1 -1
  80. package/dist/hooks/__tests__/notify-hook-modules.test.js +52 -12
  81. package/dist/hooks/__tests__/notify-hook-modules.test.js.map +1 -1
  82. package/dist/hooks/__tests__/notify-hook-regression-205.test.d.ts +2 -3
  83. package/dist/hooks/__tests__/notify-hook-regression-205.test.d.ts.map +1 -1
  84. package/dist/hooks/__tests__/notify-hook-regression-205.test.js +18 -23
  85. package/dist/hooks/__tests__/notify-hook-regression-205.test.js.map +1 -1
  86. package/dist/hooks/__tests__/notify-hook-session-scope.test.js +33 -0
  87. package/dist/hooks/__tests__/notify-hook-session-scope.test.js.map +1 -1
  88. package/dist/hooks/__tests__/notify-hook-team-dispatch.test.js +176 -1
  89. package/dist/hooks/__tests__/notify-hook-team-dispatch.test.js.map +1 -1
  90. package/dist/hooks/__tests__/notify-hook-team-leader-nudge.test.js +355 -7
  91. package/dist/hooks/__tests__/notify-hook-team-leader-nudge.test.js.map +1 -1
  92. package/dist/hooks/__tests__/notify-hook-tmux-heal.test.js +90 -2
  93. package/dist/hooks/__tests__/notify-hook-tmux-heal.test.js.map +1 -1
  94. package/dist/hooks/__tests__/session.test.js +142 -2
  95. package/dist/hooks/__tests__/session.test.js.map +1 -1
  96. package/dist/hooks/__tests__/wiki-docs-contract.test.d.ts +2 -0
  97. package/dist/hooks/__tests__/wiki-docs-contract.test.d.ts.map +1 -0
  98. package/dist/hooks/__tests__/wiki-docs-contract.test.js +34 -0
  99. package/dist/hooks/__tests__/wiki-docs-contract.test.js.map +1 -0
  100. package/dist/hooks/agents-overlay.d.ts.map +1 -1
  101. package/dist/hooks/agents-overlay.js +0 -1
  102. package/dist/hooks/agents-overlay.js.map +1 -1
  103. package/dist/hooks/extensibility/__tests__/dispatcher.test.js +32 -0
  104. package/dist/hooks/extensibility/__tests__/dispatcher.test.js.map +1 -1
  105. package/dist/hooks/extensibility/__tests__/runtime.test.js +31 -0
  106. package/dist/hooks/extensibility/__tests__/runtime.test.js.map +1 -1
  107. package/dist/hooks/extensibility/__tests__/sdk.test.js +33 -3
  108. package/dist/hooks/extensibility/__tests__/sdk.test.js.map +1 -1
  109. package/dist/hooks/extensibility/dispatcher.d.ts.map +1 -1
  110. package/dist/hooks/extensibility/dispatcher.js +41 -0
  111. package/dist/hooks/extensibility/dispatcher.js.map +1 -1
  112. package/dist/hooks/extensibility/sdk/runtime-state.d.ts.map +1 -1
  113. package/dist/hooks/extensibility/sdk/runtime-state.js +7 -1
  114. package/dist/hooks/extensibility/sdk/runtime-state.js.map +1 -1
  115. package/dist/hooks/extensibility/types.d.ts +1 -0
  116. package/dist/hooks/extensibility/types.d.ts.map +1 -1
  117. package/dist/hooks/keyword-detector.d.ts +6 -1
  118. package/dist/hooks/keyword-detector.d.ts.map +1 -1
  119. package/dist/hooks/keyword-detector.js +207 -10
  120. package/dist/hooks/keyword-detector.js.map +1 -1
  121. package/dist/hooks/keyword-registry.d.ts.map +1 -1
  122. package/dist/hooks/keyword-registry.js +3 -0
  123. package/dist/hooks/keyword-registry.js.map +1 -1
  124. package/dist/hooks/session.d.ts +14 -2
  125. package/dist/hooks/session.d.ts.map +1 -1
  126. package/dist/hooks/session.js +120 -16
  127. package/dist/hooks/session.js.map +1 -1
  128. package/dist/hud/__tests__/state.test.js +111 -2
  129. package/dist/hud/__tests__/state.test.js.map +1 -1
  130. package/dist/hud/state.d.ts.map +1 -1
  131. package/dist/hud/state.js +18 -21
  132. package/dist/hud/state.js.map +1 -1
  133. package/dist/mcp/__tests__/bootstrap.test.js +88 -1
  134. package/dist/mcp/__tests__/bootstrap.test.js.map +1 -1
  135. package/dist/mcp/__tests__/server-lifecycle.test.js +3 -0
  136. package/dist/mcp/__tests__/server-lifecycle.test.js.map +1 -1
  137. package/dist/mcp/__tests__/state-paths.test.js +30 -2
  138. package/dist/mcp/__tests__/state-paths.test.js.map +1 -1
  139. package/dist/mcp/__tests__/state-server.test.js +415 -0
  140. package/dist/mcp/__tests__/state-server.test.js.map +1 -1
  141. package/dist/mcp/__tests__/wiki-server.test.d.ts +2 -0
  142. package/dist/mcp/__tests__/wiki-server.test.d.ts.map +1 -0
  143. package/dist/mcp/__tests__/wiki-server.test.js +30 -0
  144. package/dist/mcp/__tests__/wiki-server.test.js.map +1 -0
  145. package/dist/mcp/bootstrap.d.ts +19 -1
  146. package/dist/mcp/bootstrap.d.ts.map +1 -1
  147. package/dist/mcp/bootstrap.js +185 -0
  148. package/dist/mcp/bootstrap.js.map +1 -1
  149. package/dist/mcp/state-paths.d.ts +5 -0
  150. package/dist/mcp/state-paths.d.ts.map +1 -1
  151. package/dist/mcp/state-paths.js +41 -11
  152. package/dist/mcp/state-paths.js.map +1 -1
  153. package/dist/mcp/state-server.d.ts +4 -4
  154. package/dist/mcp/state-server.d.ts.map +1 -1
  155. package/dist/mcp/state-server.js +49 -2
  156. package/dist/mcp/state-server.js.map +1 -1
  157. package/dist/mcp/wiki-server.d.ts +181 -0
  158. package/dist/mcp/wiki-server.d.ts.map +1 -0
  159. package/dist/mcp/wiki-server.js +235 -0
  160. package/dist/mcp/wiki-server.js.map +1 -0
  161. package/dist/modes/__tests__/base-autoresearch-contract.test.js +74 -2
  162. package/dist/modes/__tests__/base-autoresearch-contract.test.js.map +1 -1
  163. package/dist/modes/__tests__/base-multi-state-compat.test.d.ts +2 -0
  164. package/dist/modes/__tests__/base-multi-state-compat.test.d.ts.map +1 -0
  165. package/dist/modes/__tests__/base-multi-state-compat.test.js +38 -0
  166. package/dist/modes/__tests__/base-multi-state-compat.test.js.map +1 -0
  167. package/dist/modes/__tests__/base-tmux-pane.test.js +1 -1
  168. package/dist/modes/__tests__/base-tmux-pane.test.js.map +1 -1
  169. package/dist/modes/base.d.ts +2 -1
  170. package/dist/modes/base.d.ts.map +1 -1
  171. package/dist/modes/base.js +55 -31
  172. package/dist/modes/base.js.map +1 -1
  173. package/dist/notifications/__tests__/formatter.test.js +11 -0
  174. package/dist/notifications/__tests__/formatter.test.js.map +1 -1
  175. package/dist/notifications/__tests__/idle-cooldown.test.js +32 -1
  176. package/dist/notifications/__tests__/idle-cooldown.test.js.map +1 -1
  177. package/dist/notifications/__tests__/index.test.d.ts +2 -0
  178. package/dist/notifications/__tests__/index.test.d.ts.map +1 -0
  179. package/dist/notifications/__tests__/index.test.js +113 -0
  180. package/dist/notifications/__tests__/index.test.js.map +1 -0
  181. package/dist/notifications/__tests__/lifecycle-dedupe.test.d.ts +2 -0
  182. package/dist/notifications/__tests__/lifecycle-dedupe.test.d.ts.map +1 -0
  183. package/dist/notifications/__tests__/lifecycle-dedupe.test.js +86 -0
  184. package/dist/notifications/__tests__/lifecycle-dedupe.test.js.map +1 -0
  185. package/dist/notifications/__tests__/reply-listener.test.js +174 -0
  186. package/dist/notifications/__tests__/reply-listener.test.js.map +1 -1
  187. package/dist/notifications/__tests__/session-idle-tail-dedupe.test.d.ts +2 -0
  188. package/dist/notifications/__tests__/session-idle-tail-dedupe.test.d.ts.map +1 -0
  189. package/dist/notifications/__tests__/session-idle-tail-dedupe.test.js +93 -0
  190. package/dist/notifications/__tests__/session-idle-tail-dedupe.test.js.map +1 -0
  191. package/dist/notifications/__tests__/session-registry.test.js +48 -1
  192. package/dist/notifications/__tests__/session-registry.test.js.map +1 -1
  193. package/dist/notifications/__tests__/session-status.test.d.ts +2 -0
  194. package/dist/notifications/__tests__/session-status.test.d.ts.map +1 -0
  195. package/dist/notifications/__tests__/session-status.test.js +159 -0
  196. package/dist/notifications/__tests__/session-status.test.js.map +1 -0
  197. package/dist/notifications/__tests__/tmux.test.js +58 -1
  198. package/dist/notifications/__tests__/tmux.test.js.map +1 -1
  199. package/dist/notifications/idle-cooldown.d.ts +11 -0
  200. package/dist/notifications/idle-cooldown.d.ts.map +1 -1
  201. package/dist/notifications/idle-cooldown.js +42 -8
  202. package/dist/notifications/idle-cooldown.js.map +1 -1
  203. package/dist/notifications/index.d.ts +1 -1
  204. package/dist/notifications/index.d.ts.map +1 -1
  205. package/dist/notifications/index.js +41 -8
  206. package/dist/notifications/index.js.map +1 -1
  207. package/dist/notifications/lifecycle-dedupe.d.ts +8 -0
  208. package/dist/notifications/lifecycle-dedupe.d.ts.map +1 -0
  209. package/dist/notifications/lifecycle-dedupe.js +112 -0
  210. package/dist/notifications/lifecycle-dedupe.js.map +1 -0
  211. package/dist/notifications/reply-listener.d.ts +10 -1
  212. package/dist/notifications/reply-listener.d.ts.map +1 -1
  213. package/dist/notifications/reply-listener.js +49 -11
  214. package/dist/notifications/reply-listener.js.map +1 -1
  215. package/dist/notifications/session-registry.d.ts.map +1 -1
  216. package/dist/notifications/session-registry.js +7 -1
  217. package/dist/notifications/session-registry.js.map +1 -1
  218. package/dist/notifications/session-status.d.ts +23 -0
  219. package/dist/notifications/session-status.d.ts.map +1 -0
  220. package/dist/notifications/session-status.js +187 -0
  221. package/dist/notifications/session-status.js.map +1 -0
  222. package/dist/notifications/tmux.d.ts +10 -0
  223. package/dist/notifications/tmux.d.ts.map +1 -1
  224. package/dist/notifications/tmux.js +59 -5
  225. package/dist/notifications/tmux.js.map +1 -1
  226. package/dist/notifications/types.d.ts +2 -0
  227. package/dist/notifications/types.d.ts.map +1 -1
  228. package/dist/openclaw/__tests__/index.test.js +84 -0
  229. package/dist/openclaw/__tests__/index.test.js.map +1 -1
  230. package/dist/openclaw/index.d.ts.map +1 -1
  231. package/dist/openclaw/index.js +7 -14
  232. package/dist/openclaw/index.js.map +1 -1
  233. package/dist/openclaw/types.d.ts +2 -2
  234. package/dist/openclaw/types.d.ts.map +1 -1
  235. package/dist/scripts/__tests__/codex-native-hook.test.js +692 -40
  236. package/dist/scripts/__tests__/codex-native-hook.test.js.map +1 -1
  237. package/dist/scripts/__tests__/hook-derived-watcher.test.d.ts +2 -0
  238. package/dist/scripts/__tests__/hook-derived-watcher.test.d.ts.map +1 -0
  239. package/dist/scripts/__tests__/hook-derived-watcher.test.js +87 -0
  240. package/dist/scripts/__tests__/hook-derived-watcher.test.js.map +1 -0
  241. package/dist/scripts/codex-native-hook.d.ts.map +1 -1
  242. package/dist/scripts/codex-native-hook.js +309 -77
  243. package/dist/scripts/codex-native-hook.js.map +1 -1
  244. package/dist/scripts/hook-derived-watcher.js +43 -1
  245. package/dist/scripts/hook-derived-watcher.js.map +1 -1
  246. package/dist/scripts/notify-fallback-watcher.js +95 -21
  247. package/dist/scripts/notify-fallback-watcher.js.map +1 -1
  248. package/dist/scripts/notify-hook/active-team.d.ts +9 -0
  249. package/dist/scripts/notify-hook/active-team.d.ts.map +1 -0
  250. package/dist/scripts/notify-hook/active-team.js +44 -0
  251. package/dist/scripts/notify-hook/active-team.js.map +1 -0
  252. package/dist/scripts/notify-hook/auto-nudge.d.ts +5 -3
  253. package/dist/scripts/notify-hook/auto-nudge.d.ts.map +1 -1
  254. package/dist/scripts/notify-hook/auto-nudge.js +121 -78
  255. package/dist/scripts/notify-hook/auto-nudge.js.map +1 -1
  256. package/dist/scripts/notify-hook/managed-tmux.d.ts.map +1 -1
  257. package/dist/scripts/notify-hook/managed-tmux.js +18 -4
  258. package/dist/scripts/notify-hook/managed-tmux.js.map +1 -1
  259. package/dist/scripts/notify-hook/operational-events.d.ts.map +1 -1
  260. package/dist/scripts/notify-hook/operational-events.js +21 -0
  261. package/dist/scripts/notify-hook/operational-events.js.map +1 -1
  262. package/dist/scripts/notify-hook/ralph-session-resume.d.ts.map +1 -1
  263. package/dist/scripts/notify-hook/ralph-session-resume.js +3 -2
  264. package/dist/scripts/notify-hook/ralph-session-resume.js.map +1 -1
  265. package/dist/scripts/notify-hook/state-io.d.ts +10 -1
  266. package/dist/scripts/notify-hook/state-io.d.ts.map +1 -1
  267. package/dist/scripts/notify-hook/state-io.js +56 -12
  268. package/dist/scripts/notify-hook/state-io.js.map +1 -1
  269. package/dist/scripts/notify-hook/team-dispatch.d.ts.map +1 -1
  270. package/dist/scripts/notify-hook/team-dispatch.js +305 -167
  271. package/dist/scripts/notify-hook/team-dispatch.js.map +1 -1
  272. package/dist/scripts/notify-hook/team-leader-nudge.d.ts.map +1 -1
  273. package/dist/scripts/notify-hook/team-leader-nudge.js +87 -15
  274. package/dist/scripts/notify-hook/team-leader-nudge.js.map +1 -1
  275. package/dist/scripts/notify-hook/tmux-injection.d.ts.map +1 -1
  276. package/dist/scripts/notify-hook/tmux-injection.js +11 -2
  277. package/dist/scripts/notify-hook/tmux-injection.js.map +1 -1
  278. package/dist/scripts/notify-hook.js +26 -16
  279. package/dist/scripts/notify-hook.js.map +1 -1
  280. package/dist/scripts/run-provider-advisor.js +20 -2
  281. package/dist/scripts/run-provider-advisor.js.map +1 -1
  282. package/dist/scripts/smoke-packed-install.d.ts +1 -8
  283. package/dist/scripts/smoke-packed-install.d.ts.map +1 -1
  284. package/dist/scripts/smoke-packed-install.js +12 -68
  285. package/dist/scripts/smoke-packed-install.js.map +1 -1
  286. package/dist/state/__tests__/operations.test.js +113 -0
  287. package/dist/state/__tests__/operations.test.js.map +1 -1
  288. package/dist/state/__tests__/skill-active.test.js +35 -0
  289. package/dist/state/__tests__/skill-active.test.js.map +1 -1
  290. package/dist/state/__tests__/workflow-transition.test.d.ts +2 -0
  291. package/dist/state/__tests__/workflow-transition.test.d.ts.map +1 -0
  292. package/dist/state/__tests__/workflow-transition.test.js +56 -0
  293. package/dist/state/__tests__/workflow-transition.test.js.map +1 -0
  294. package/dist/state/operations.d.ts +1 -1
  295. package/dist/state/operations.d.ts.map +1 -1
  296. package/dist/state/operations.js +88 -2
  297. package/dist/state/operations.js.map +1 -1
  298. package/dist/state/skill-active.d.ts +2 -2
  299. package/dist/state/skill-active.d.ts.map +1 -1
  300. package/dist/state/skill-active.js +119 -33
  301. package/dist/state/skill-active.js.map +1 -1
  302. package/dist/state/workflow-transition-reconcile.d.ts +15 -0
  303. package/dist/state/workflow-transition-reconcile.d.ts.map +1 -0
  304. package/dist/state/workflow-transition-reconcile.js +100 -0
  305. package/dist/state/workflow-transition-reconcile.js.map +1 -0
  306. package/dist/state/workflow-transition.d.ts +22 -0
  307. package/dist/state/workflow-transition.d.ts.map +1 -0
  308. package/dist/state/workflow-transition.js +188 -0
  309. package/dist/state/workflow-transition.js.map +1 -0
  310. package/dist/team/__tests__/api-interop.test.js +90 -0
  311. package/dist/team/__tests__/api-interop.test.js.map +1 -1
  312. package/dist/team/__tests__/current-task-baseline.test.d.ts +2 -0
  313. package/dist/team/__tests__/current-task-baseline.test.d.ts.map +1 -0
  314. package/dist/team/__tests__/current-task-baseline.test.js +87 -0
  315. package/dist/team/__tests__/current-task-baseline.test.js.map +1 -0
  316. package/dist/team/__tests__/hardening-e2e.test.js +17 -0
  317. package/dist/team/__tests__/hardening-e2e.test.js.map +1 -1
  318. package/dist/team/__tests__/runtime.test.js +673 -65
  319. package/dist/team/__tests__/runtime.test.js.map +1 -1
  320. package/dist/team/__tests__/shutdown-fallback.test.js +11 -1
  321. package/dist/team/__tests__/shutdown-fallback.test.js.map +1 -1
  322. package/dist/team/__tests__/tmux-session.test.js +447 -4
  323. package/dist/team/__tests__/tmux-session.test.js.map +1 -1
  324. package/dist/team/api-interop.d.ts.map +1 -1
  325. package/dist/team/api-interop.js +10 -1
  326. package/dist/team/api-interop.js.map +1 -1
  327. package/dist/team/current-task-baseline.d.ts +32 -0
  328. package/dist/team/current-task-baseline.d.ts.map +1 -0
  329. package/dist/team/current-task-baseline.js +85 -0
  330. package/dist/team/current-task-baseline.js.map +1 -0
  331. package/dist/team/delivery-log.d.ts +1 -1
  332. package/dist/team/delivery-log.d.ts.map +1 -1
  333. package/dist/team/delivery-log.js.map +1 -1
  334. package/dist/team/leader-activity.d.ts +1 -0
  335. package/dist/team/leader-activity.d.ts.map +1 -1
  336. package/dist/team/leader-activity.js +4 -2
  337. package/dist/team/leader-activity.js.map +1 -1
  338. package/dist/team/progress-evidence.d.ts +2 -0
  339. package/dist/team/progress-evidence.d.ts.map +1 -0
  340. package/dist/team/progress-evidence.js +77 -0
  341. package/dist/team/progress-evidence.js.map +1 -0
  342. package/dist/team/runtime.d.ts.map +1 -1
  343. package/dist/team/runtime.js +269 -64
  344. package/dist/team/runtime.js.map +1 -1
  345. package/dist/team/scaling.d.ts.map +1 -1
  346. package/dist/team/scaling.js +1 -1
  347. package/dist/team/scaling.js.map +1 -1
  348. package/dist/team/state.d.ts.map +1 -1
  349. package/dist/team/state.js +2 -13
  350. package/dist/team/state.js.map +1 -1
  351. package/dist/team/tmux-session.d.ts +12 -3
  352. package/dist/team/tmux-session.d.ts.map +1 -1
  353. package/dist/team/tmux-session.js +174 -20
  354. package/dist/team/tmux-session.js.map +1 -1
  355. package/dist/team/worktree.d.ts +6 -1
  356. package/dist/team/worktree.d.ts.map +1 -1
  357. package/dist/team/worktree.js +28 -4
  358. package/dist/team/worktree.js.map +1 -1
  359. package/dist/utils/__tests__/agents-md.test.js +21 -1
  360. package/dist/utils/__tests__/agents-md.test.js.map +1 -1
  361. package/dist/utils/__tests__/repo-deps.test.d.ts +2 -0
  362. package/dist/utils/__tests__/repo-deps.test.d.ts.map +1 -0
  363. package/dist/utils/__tests__/repo-deps.test.js +71 -0
  364. package/dist/utils/__tests__/repo-deps.test.js.map +1 -0
  365. package/dist/utils/agents-md.d.ts +1 -0
  366. package/dist/utils/agents-md.d.ts.map +1 -1
  367. package/dist/utils/agents-md.js +7 -3
  368. package/dist/utils/agents-md.js.map +1 -1
  369. package/dist/utils/paths.d.ts +4 -0
  370. package/dist/utils/paths.d.ts.map +1 -1
  371. package/dist/utils/paths.js +20 -0
  372. package/dist/utils/paths.js.map +1 -1
  373. package/dist/utils/repo-deps.d.ts +20 -0
  374. package/dist/utils/repo-deps.d.ts.map +1 -0
  375. package/dist/utils/repo-deps.js +78 -0
  376. package/dist/utils/repo-deps.js.map +1 -0
  377. package/dist/verification/__tests__/dev-merge-issue-close-workflow.test.d.ts +2 -0
  378. package/dist/verification/__tests__/dev-merge-issue-close-workflow.test.d.ts.map +1 -0
  379. package/dist/verification/__tests__/dev-merge-issue-close-workflow.test.js +54 -0
  380. package/dist/verification/__tests__/dev-merge-issue-close-workflow.test.js.map +1 -0
  381. package/dist/wiki/__tests__/cjk-tokenize.test.d.ts +12 -0
  382. package/dist/wiki/__tests__/cjk-tokenize.test.d.ts.map +1 -0
  383. package/dist/wiki/__tests__/cjk-tokenize.test.js +139 -0
  384. package/dist/wiki/__tests__/cjk-tokenize.test.js.map +1 -0
  385. package/dist/wiki/__tests__/crlf-parse.test.d.ts +2 -0
  386. package/dist/wiki/__tests__/crlf-parse.test.d.ts.map +1 -0
  387. package/dist/wiki/__tests__/crlf-parse.test.js +24 -0
  388. package/dist/wiki/__tests__/crlf-parse.test.js.map +1 -0
  389. package/dist/wiki/__tests__/escape-newline.test.d.ts +2 -0
  390. package/dist/wiki/__tests__/escape-newline.test.d.ts.map +1 -0
  391. package/dist/wiki/__tests__/escape-newline.test.js +45 -0
  392. package/dist/wiki/__tests__/escape-newline.test.js.map +1 -0
  393. package/dist/wiki/__tests__/ingest.test.d.ts +5 -0
  394. package/dist/wiki/__tests__/ingest.test.d.ts.map +1 -0
  395. package/dist/wiki/__tests__/ingest.test.js +181 -0
  396. package/dist/wiki/__tests__/ingest.test.js.map +1 -0
  397. package/dist/wiki/__tests__/lint.test.d.ts +5 -0
  398. package/dist/wiki/__tests__/lint.test.d.ts.map +1 -0
  399. package/dist/wiki/__tests__/lint.test.js +163 -0
  400. package/dist/wiki/__tests__/lint.test.js.map +1 -0
  401. package/dist/wiki/__tests__/query.test.d.ts +5 -0
  402. package/dist/wiki/__tests__/query.test.d.ts.map +1 -0
  403. package/dist/wiki/__tests__/query.test.js +141 -0
  404. package/dist/wiki/__tests__/query.test.js.map +1 -0
  405. package/dist/wiki/__tests__/reserved-file-guard.test.d.ts +2 -0
  406. package/dist/wiki/__tests__/reserved-file-guard.test.d.ts.map +1 -0
  407. package/dist/wiki/__tests__/reserved-file-guard.test.js +44 -0
  408. package/dist/wiki/__tests__/reserved-file-guard.test.js.map +1 -0
  409. package/dist/wiki/__tests__/session-hooks.test.d.ts +5 -0
  410. package/dist/wiki/__tests__/session-hooks.test.d.ts.map +1 -0
  411. package/dist/wiki/__tests__/session-hooks.test.js +36 -0
  412. package/dist/wiki/__tests__/session-hooks.test.js.map +1 -0
  413. package/dist/wiki/__tests__/slug-nonascii.test.d.ts +2 -0
  414. package/dist/wiki/__tests__/slug-nonascii.test.d.ts.map +1 -0
  415. package/dist/wiki/__tests__/slug-nonascii.test.js +24 -0
  416. package/dist/wiki/__tests__/slug-nonascii.test.js.map +1 -0
  417. package/dist/wiki/__tests__/storage.test.d.ts +5 -0
  418. package/dist/wiki/__tests__/storage.test.d.ts.map +1 -0
  419. package/dist/wiki/__tests__/storage.test.js +278 -0
  420. package/dist/wiki/__tests__/storage.test.js.map +1 -0
  421. package/dist/wiki/__tests__/test-helpers.d.ts +31 -0
  422. package/dist/wiki/__tests__/test-helpers.d.ts.map +1 -0
  423. package/dist/wiki/__tests__/test-helpers.js +108 -0
  424. package/dist/wiki/__tests__/test-helpers.js.map +1 -0
  425. package/dist/wiki/index.d.ts +14 -0
  426. package/dist/wiki/index.d.ts.map +1 -0
  427. package/dist/wiki/index.js +17 -0
  428. package/dist/wiki/index.js.map +1 -0
  429. package/dist/wiki/ingest.d.ts +20 -0
  430. package/dist/wiki/ingest.d.ts.map +1 -0
  431. package/dist/wiki/ingest.js +115 -0
  432. package/dist/wiki/ingest.js.map +1 -0
  433. package/dist/wiki/lifecycle.d.ts +20 -0
  434. package/dist/wiki/lifecycle.d.ts.map +1 -0
  435. package/dist/wiki/lifecycle.js +212 -0
  436. package/dist/wiki/lifecycle.js.map +1 -0
  437. package/dist/wiki/lint.d.ts +25 -0
  438. package/dist/wiki/lint.d.ts.map +1 -0
  439. package/dist/wiki/lint.js +166 -0
  440. package/dist/wiki/lint.js.map +1 -0
  441. package/dist/wiki/query.d.ts +36 -0
  442. package/dist/wiki/query.d.ts.map +1 -0
  443. package/dist/wiki/query.js +138 -0
  444. package/dist/wiki/query.js.map +1 -0
  445. package/dist/wiki/storage.d.ts +33 -0
  446. package/dist/wiki/storage.d.ts.map +1 -0
  447. package/dist/wiki/storage.js +321 -0
  448. package/dist/wiki/storage.js.map +1 -0
  449. package/dist/wiki/types.d.ts +83 -0
  450. package/dist/wiki/types.d.ts.map +1 -0
  451. package/dist/wiki/types.js +15 -0
  452. package/dist/wiki/types.js.map +1 -0
  453. package/package.json +3 -1
  454. package/skills/configure-notifications/SKILL.md +1 -0
  455. package/skills/doctor/SKILL.md +11 -0
  456. package/skills/omx-setup/SKILL.md +1 -1
  457. package/skills/wiki/SKILL.md +57 -0
  458. package/src/scripts/__tests__/codex-native-hook.test.ts +920 -56
  459. package/src/scripts/__tests__/hook-derived-watcher.test.ts +111 -0
  460. package/src/scripts/codex-native-hook.ts +377 -83
  461. package/src/scripts/hook-derived-watcher.ts +43 -1
  462. package/src/scripts/notify-fallback-watcher.ts +99 -20
  463. package/src/scripts/notify-hook/active-team.ts +54 -0
  464. package/src/scripts/notify-hook/auto-nudge.ts +132 -79
  465. package/src/scripts/notify-hook/managed-tmux.ts +22 -4
  466. package/src/scripts/notify-hook/operational-events.ts +21 -0
  467. package/src/scripts/notify-hook/ralph-session-resume.ts +3 -2
  468. package/src/scripts/notify-hook/state-io.ts +89 -12
  469. package/src/scripts/notify-hook/team-dispatch.ts +326 -168
  470. package/src/scripts/notify-hook/team-leader-nudge.ts +91 -14
  471. package/src/scripts/notify-hook/tmux-injection.ts +11 -2
  472. package/src/scripts/notify-hook.ts +36 -22
  473. package/src/scripts/run-provider-advisor.ts +20 -2
  474. package/src/scripts/smoke-packed-install.ts +16 -83
  475. package/templates/AGENTS.md +3 -4
@@ -1,7 +1,7 @@
1
1
  import { describe, it } from 'node:test';
2
2
  import { once } from 'node:events';
3
3
  import assert from 'node:assert/strict';
4
- import { chmod, mkdtemp, mkdir, readFile, rm, writeFile } from 'node:fs/promises';
4
+ import { chmod, mkdtemp, mkdir, readFile, rm, symlink, writeFile } from 'node:fs/promises';
5
5
  import { tmpdir } from 'node:os';
6
6
  import { join } from 'node:path';
7
7
  import { spawn, spawnSync } from 'node:child_process';
@@ -9,6 +9,7 @@ import { randomUUID } from 'node:crypto';
9
9
  import { initTeamState, enqueueDispatchRequest, readDispatchRequest } from '../../team/state.js';
10
10
  import { buildWindowsMsysBackgroundHelperBootstrapScript } from '../../cli/index.js';
11
11
  import { writeSessionStart } from '../session.js';
12
+ const DEFAULT_AUTO_NUDGE_RESPONSE = 'continue with the current task only if it is already authorized';
12
13
  async function appendLine(path, line) {
13
14
  const prev = await readFile(path, 'utf-8');
14
15
  const content = prev + `${JSON.stringify(line)}\n`;
@@ -30,6 +31,80 @@ async function readJsonLines(path) {
30
31
  .filter(Boolean)
31
32
  .map((line) => JSON.parse(line));
32
33
  }
34
+ async function writeCanonicalWatcherTeamFixture(wd, { teamName = 'dispatch-team', sessionId = 'sess-current', ownerSessionId = sessionId, coarseState = 'missing', terminal = false, } = {}) {
35
+ const stateDir = join(wd, '.omx', 'state');
36
+ const teamDir = join(stateDir, 'team', teamName);
37
+ const nowIso = new Date().toISOString();
38
+ await mkdir(join(wd, '.omx', 'logs'), { recursive: true });
39
+ await mkdir(join(teamDir, 'workers'), { recursive: true });
40
+ await writeFile(join(stateDir, 'session.json'), JSON.stringify({ session_id: sessionId }, null, 2));
41
+ if (coarseState !== 'missing') {
42
+ await writeFile(join(stateDir, 'team-state.json'), JSON.stringify({
43
+ active: coarseState === 'active',
44
+ team_name: teamName,
45
+ current_phase: terminal ? 'complete' : 'team-exec',
46
+ ...(terminal ? { completed_at: nowIso } : {}),
47
+ }, null, 2));
48
+ }
49
+ await writeFile(join(stateDir, 'hud-state.json'), JSON.stringify({
50
+ last_turn_at: new Date(Date.now() - 300_000).toISOString(),
51
+ turn_count: 3,
52
+ }, null, 2));
53
+ const manifest = {
54
+ schema_version: 2,
55
+ name: teamName,
56
+ task: 'canonical watcher fallback repro',
57
+ leader: {
58
+ session_id: ownerSessionId,
59
+ worker_id: 'leader-fixed',
60
+ role: 'coordinator',
61
+ },
62
+ policy: {
63
+ worker_launch_mode: 'interactive',
64
+ display_mode: 'split_pane',
65
+ dispatch_mode: 'hook_preferred_with_fallback',
66
+ dispatch_ack_timeout_ms: 2000,
67
+ },
68
+ governance: {
69
+ delegation_only: false,
70
+ plan_approval_required: false,
71
+ nested_teams_allowed: false,
72
+ one_team_per_leader_session: true,
73
+ cleanup_requires_all_workers_inactive: true,
74
+ },
75
+ lifecycle_profile: 'default',
76
+ permissions_snapshot: {
77
+ approval_mode: 'never',
78
+ sandbox_mode: 'danger-full-access',
79
+ network_access: true,
80
+ },
81
+ tmux_session: `${teamName}:0`,
82
+ leader_pane_id: '%42',
83
+ hud_pane_id: null,
84
+ resize_hook_name: null,
85
+ resize_hook_target: null,
86
+ worker_count: 1,
87
+ next_task_id: 1,
88
+ workers: [
89
+ { name: 'worker-1', index: 1, pane_id: '%42', role: 'executor' },
90
+ ],
91
+ created_at: nowIso,
92
+ };
93
+ await writeFile(join(teamDir, 'manifest.v2.json'), JSON.stringify(manifest, null, 2));
94
+ await writeFile(join(teamDir, 'config.json'), JSON.stringify({
95
+ name: teamName,
96
+ tmux_session: `${teamName}:0`,
97
+ leader_pane_id: '%42',
98
+ workers: [
99
+ { name: 'worker-1', pane_id: '%42' },
100
+ ],
101
+ }, null, 2));
102
+ await writeFile(join(teamDir, 'phase.json'), JSON.stringify({
103
+ current_phase: terminal ? 'complete' : 'team-exec',
104
+ updated_at: nowIso,
105
+ transitions: terminal ? [{ from: 'team-exec', to: 'complete', at: nowIso }] : [],
106
+ }, null, 2));
107
+ }
33
108
  async function sleep(ms) {
34
109
  await new Promise(resolve => setTimeout(resolve, ms));
35
110
  }
@@ -63,6 +138,9 @@ async function waitForExit(child, timeoutMs = 4000) {
63
138
  }),
64
139
  ]);
65
140
  }
141
+ function defaultAutoNudgePattern(targetPane) {
142
+ return new RegExp(`send-keys -t ${targetPane} -l ${DEFAULT_AUTO_NUDGE_RESPONSE.replace(/[-/\\^$*+?.()|[\]{}]/g, '\\$&')} \\[OMX_TMUX_INJECT\\]`);
143
+ }
66
144
  function buildFakeTmux(tmuxLogPath, options = {}) {
67
145
  return `#!/usr/bin/env bash
68
146
  set -eu
@@ -530,10 +608,11 @@ describe('notify-fallback watcher', () => {
530
608
  autoNudge: { enabled: true, delaySec: 0, ttlMs: 30_000 },
531
609
  }, null, 2));
532
610
  await writeSessionStart(wd, 'sess-managed-fallback');
533
- await writeFile(join(wd, '.omx', 'state', 'hud-state.json'), JSON.stringify({
611
+ await mkdir(join(wd, '.omx', 'state', 'sessions', 'sess-managed-fallback'), { recursive: true });
612
+ await writeFile(join(wd, '.omx', 'state', 'sessions', 'sess-managed-fallback', 'hud-state.json'), JSON.stringify({
534
613
  last_turn_at: new Date(Date.now() - 6_000).toISOString(),
535
614
  turn_count: 7,
536
- last_agent_output: 'If you want, I can keep going from here.',
615
+ last_agent_output: 'Keep going and finish the cleanup from here.',
537
616
  }, null, 2));
538
617
  await writeFile(join(wd, '.omx', 'state', 'notify-fallback.pid'), JSON.stringify({
539
618
  pid: process.pid,
@@ -562,7 +641,7 @@ describe('notify-fallback watcher', () => {
562
641
  });
563
642
  assert.equal(result.status, 0, result.stderr || result.stdout);
564
643
  const tmuxLog = await readFile(tmuxLogPath, 'utf8').catch(() => '');
565
- assert.doesNotMatch(tmuxLog, /send-keys -t %42 -l yes, proceed \[OMX_TMUX_INJECT\]/);
644
+ assert.doesNotMatch(tmuxLog, defaultAutoNudgePattern('%42'));
566
645
  const watcherState = JSON.parse(await readFile(join(wd, '.omx', 'state', 'notify-fallback-state.json'), 'utf-8'));
567
646
  assert.equal(watcherState.pid, process.pid, 'authority backoff should preserve the primary watcher state owner');
568
647
  assert.equal(watcherState.authority_only, false, 'authority backoff should not overwrite primary watcher ownership');
@@ -578,6 +657,52 @@ describe('notify-fallback watcher', () => {
578
657
  await rm(wd, { recursive: true, force: true });
579
658
  }
580
659
  });
660
+ it('treats symlinked cwd aliases as the same primary watcher during authority handoff', async () => {
661
+ const wd = await mkdtemp(join(tmpdir(), 'omx-fallback-cwd-alias-'));
662
+ const aliasWd = `${wd}-alias`;
663
+ const fakeBinDir = join(wd, 'fake-bin');
664
+ const tmuxLogPath = join(wd, 'tmux.log');
665
+ try {
666
+ await symlink(wd, aliasWd, process.platform === 'win32' ? 'junction' : 'dir');
667
+ await mkdir(join(wd, '.omx', 'logs'), { recursive: true });
668
+ await mkdir(fakeBinDir, { recursive: true });
669
+ await writeFile(join(fakeBinDir, 'tmux'), buildFakeTmux(tmuxLogPath));
670
+ await chmod(join(fakeBinDir, 'tmux'), 0o755);
671
+ await writeSessionStart(wd, 'sess-cwd-alias');
672
+ await writeFile(join(wd, '.omx', 'state', 'notify-fallback.pid'), JSON.stringify({
673
+ pid: process.pid,
674
+ cwd: wd,
675
+ started_at: new Date().toISOString(),
676
+ }, null, 2));
677
+ await writeFile(join(wd, '.omx', 'state', 'notify-fallback-state.json'), JSON.stringify({
678
+ pid: process.pid,
679
+ cwd: wd,
680
+ authority_only: false,
681
+ poll_ms: 250,
682
+ dispatch_drain: { last_tick_at: new Date().toISOString() },
683
+ }, null, 2));
684
+ const watcherScript = new URL('../../../dist/scripts/notify-fallback-watcher.js', import.meta.url).pathname;
685
+ const notifyHook = new URL('../../../dist/scripts/notify-hook.js', import.meta.url).pathname;
686
+ const result = spawnSync(process.execPath, [watcherScript, '--once', '--authority-only', '--cwd', aliasWd, '--notify-script', notifyHook, '--poll-ms', '50'], {
687
+ encoding: 'utf-8',
688
+ env: buildCleanNotifyEnv({
689
+ PATH: `${fakeBinDir}:${process.env.PATH || ''}`,
690
+ OMX_SESSION_ID: 'sess-cwd-alias',
691
+ TMUX: '1',
692
+ TMUX_PANE: '%42',
693
+ }),
694
+ });
695
+ assert.equal(result.status, 0, result.stderr || result.stdout);
696
+ const watcherState = JSON.parse(await readFile(join(wd, '.omx', 'state', 'notify-fallback-state.json'), 'utf-8'));
697
+ assert.equal(watcherState.authority_backoff?.active, true);
698
+ assert.equal(watcherState.authority_backoff?.reason, 'primary_watcher_healthy');
699
+ assert.equal(watcherState.authority_backoff?.primary_pid, process.pid);
700
+ }
701
+ finally {
702
+ await rm(aliasWd, { recursive: true, force: true });
703
+ await rm(wd, { recursive: true, force: true });
704
+ }
705
+ });
581
706
  it('disables fallback watcher nudges when deep-interview state is active', async () => {
582
707
  const wd = await mkdtemp(join(tmpdir(), 'omx-fallback-deep-interview-suppressed-'));
583
708
  const fakeBinDir = join(wd, 'fake-bin');
@@ -623,7 +748,7 @@ describe('notify-fallback watcher', () => {
623
748
  const tmuxLog = await readFile(tmuxLogPath, 'utf8').catch(() => '');
624
749
  assert.doesNotMatch(tmuxLog, /Ralph loop active continue/);
625
750
  assert.doesNotMatch(tmuxLog, /Team dispatch-team:/);
626
- assert.doesNotMatch(tmuxLog, /yes, proceed \[OMX_TMUX_INJECT\]/);
751
+ assert.doesNotMatch(tmuxLog, new RegExp(`${DEFAULT_AUTO_NUDGE_RESPONSE.replace(/[-/\\^$*+?.()|[\]{}]/g, '\\$&')} \\[OMX_TMUX_INJECT\\]`));
627
752
  }
628
753
  finally {
629
754
  await rm(wd, { recursive: true, force: true });
@@ -686,7 +811,7 @@ describe('notify-fallback watcher', () => {
686
811
  const tmuxLog = await readFile(tmuxLogPath, 'utf8').catch(() => '');
687
812
  assert.doesNotMatch(tmuxLog, /Ralph loop active continue/);
688
813
  assert.doesNotMatch(tmuxLog, /Team dispatch-team:/);
689
- assert.doesNotMatch(tmuxLog, /yes, proceed \[OMX_TMUX_INJECT\]/);
814
+ assert.doesNotMatch(tmuxLog, new RegExp(`${DEFAULT_AUTO_NUDGE_RESPONSE.replace(/[-/\\^$*+?.()|[\]{}]/g, '\\$&')} \\[OMX_TMUX_INJECT\\]`));
690
815
  }
691
816
  finally {
692
817
  await rm(wd, { recursive: true, force: true });
@@ -722,6 +847,7 @@ describe('notify-fallback watcher', () => {
722
847
  encoding: 'utf-8',
723
848
  env: buildCleanNotifyEnv({
724
849
  PATH: `${fakeBinDir}:${process.env.PATH || ''}`,
850
+ OMX_SESSION_ID: 'sess-canonical-inactive',
725
851
  }),
726
852
  });
727
853
  assert.equal(result.status, 0, result.stderr || result.stdout);
@@ -751,6 +877,37 @@ describe('notify-fallback watcher', () => {
751
877
  await rm(wd, { recursive: true, force: true });
752
878
  }
753
879
  });
880
+ it('runs leader nudge checks from canonical fallback when coarse team-state is inactive', async () => {
881
+ const wd = await mkdtemp(join(tmpdir(), 'omx-fallback-leader-nudge-canonical-inactive-'));
882
+ const fakeBinDir = join(wd, 'fake-bin');
883
+ const tmuxLogPath = join(wd, 'tmux.log');
884
+ try {
885
+ await mkdir(fakeBinDir, { recursive: true });
886
+ await writeCanonicalWatcherTeamFixture(wd, {
887
+ teamName: 'dispatch-team',
888
+ sessionId: 'sess-canonical-inactive',
889
+ ownerSessionId: 'sess-canonical-inactive',
890
+ coarseState: 'inactive',
891
+ });
892
+ await writeFile(join(fakeBinDir, 'tmux'), buildFakeTmux(tmuxLogPath));
893
+ await chmod(join(fakeBinDir, 'tmux'), 0o755);
894
+ const watcherScript = new URL('../../../dist/scripts/notify-fallback-watcher.js', import.meta.url).pathname;
895
+ const notifyHook = new URL('../../../dist/scripts/notify-hook.js', import.meta.url).pathname;
896
+ const result = spawnSync(process.execPath, [watcherScript, '--once', '--cwd', wd, '--notify-script', notifyHook], {
897
+ encoding: 'utf-8',
898
+ env: buildCleanNotifyEnv({
899
+ PATH: `${fakeBinDir}:${process.env.PATH || ''}`,
900
+ OMX_SESSION_ID: 'sess-canonical-inactive',
901
+ }),
902
+ });
903
+ assert.equal(result.status, 0, result.stderr || result.stdout);
904
+ const tmuxLog = await readFile(tmuxLogPath, 'utf8');
905
+ assert.match(tmuxLog, /send-keys -t %42 -l Team dispatch-team: leader stale, \d+ worker pane\(s\) still active\./);
906
+ }
907
+ finally {
908
+ await rm(wd, { recursive: true, force: true });
909
+ }
910
+ });
754
911
  it('skips fallback watcher leader nudges when the leader is not stale even if mailbox messages exist', async () => {
755
912
  const wd = await mkdtemp(join(tmpdir(), 'omx-fallback-leader-nudge-fresh-'));
756
913
  const fakeBinDir = join(wd, 'fake-bin');
@@ -793,6 +950,7 @@ describe('notify-fallback watcher', () => {
793
950
  encoding: 'utf-8',
794
951
  env: buildCleanNotifyEnv({
795
952
  PATH: `${fakeBinDir}:${process.env.PATH || ''}`,
953
+ OMX_SESSION_ID: 'sess-canonical-inactive',
796
954
  }),
797
955
  });
798
956
  assert.equal(result.status, 0, result.stderr || result.stdout);
@@ -1003,10 +1161,11 @@ exit 0
1003
1161
  autoNudge: { enabled: true, delaySec: 0, ttlMs: 30_000 },
1004
1162
  }, null, 2));
1005
1163
  await writeSessionStart(wd, 'sess-managed-fallback');
1006
- await writeFile(join(wd, '.omx', 'state', 'hud-state.json'), JSON.stringify({
1164
+ await mkdir(join(wd, '.omx', 'state', 'sessions', 'sess-managed-fallback'), { recursive: true });
1165
+ await writeFile(join(wd, '.omx', 'state', 'sessions', 'sess-managed-fallback', 'hud-state.json'), JSON.stringify({
1007
1166
  last_turn_at: new Date(Date.now() - 6_000).toISOString(),
1008
1167
  turn_count: 7,
1009
- last_agent_output: 'If you want, I can keep going from here.',
1168
+ last_agent_output: 'Keep going and finish the cleanup from here.',
1010
1169
  }, null, 2));
1011
1170
  const watcherScript = new URL('../../../dist/scripts/notify-fallback-watcher.js', import.meta.url).pathname;
1012
1171
  const notifyHook = new URL('../../../dist/scripts/notify-hook.js', import.meta.url).pathname;
@@ -1024,7 +1183,7 @@ exit 0
1024
1183
  });
1025
1184
  assert.equal(result.status, 0, result.stderr || result.stdout);
1026
1185
  const tmuxLog = await readFile(tmuxLogPath, 'utf8');
1027
- assert.match(tmuxLog, /send-keys -t %42 -l yes, proceed \[OMX_TMUX_INJECT\]/);
1186
+ assert.match(tmuxLog, defaultAutoNudgePattern('%42'));
1028
1187
  const watcherStatePath = join(wd, '.omx', 'state', 'notify-fallback-state.json');
1029
1188
  const watcherState = JSON.parse(await readFile(watcherStatePath, 'utf-8'));
1030
1189
  assert.equal(watcherState.fallback_auto_nudge?.last_reason, 'sent');
@@ -1055,10 +1214,11 @@ exit 0
1055
1214
  target: { type: 'pane', value: '%42' },
1056
1215
  }, null, 2));
1057
1216
  await writeSessionStart(wd, 'sess-managed-fallback');
1058
- await writeFile(join(wd, '.omx', 'state', 'hud-state.json'), JSON.stringify({
1217
+ await mkdir(join(wd, '.omx', 'state', 'sessions', 'sess-managed-fallback'), { recursive: true });
1218
+ await writeFile(join(wd, '.omx', 'state', 'sessions', 'sess-managed-fallback', 'hud-state.json'), JSON.stringify({
1059
1219
  last_turn_at: new Date(Date.now() - 6_000).toISOString(),
1060
1220
  turn_count: 7,
1061
- last_agent_output: 'If you want, I can keep going from here.',
1221
+ last_agent_output: 'Keep going and finish the cleanup from here.',
1062
1222
  }, null, 2));
1063
1223
  const watcherScript = new URL('../../../dist/scripts/notify-fallback-watcher.js', import.meta.url).pathname;
1064
1224
  const notifyHook = new URL('../../../dist/scripts/notify-hook.js', import.meta.url).pathname;
@@ -1075,7 +1235,7 @@ exit 0
1075
1235
  });
1076
1236
  assert.equal(result.status, 0, result.stderr || result.stdout);
1077
1237
  const tmuxLog = await readFile(tmuxLogPath, 'utf8').catch(() => '');
1078
- assert.doesNotMatch(tmuxLog, /send-keys -t %42 -l yes, proceed \[OMX_TMUX_INJECT\]/);
1238
+ assert.doesNotMatch(tmuxLog, defaultAutoNudgePattern('%42'));
1079
1239
  }
1080
1240
  finally {
1081
1241
  await rm(wd, { recursive: true, force: true });
@@ -1099,7 +1259,7 @@ exit 0
1099
1259
  await writeFile(join(wd, '.omx', 'state', 'hud-state.json'), JSON.stringify({
1100
1260
  last_turn_at: new Date(Date.now() - 6_000).toISOString(),
1101
1261
  turn_count: 9,
1102
- last_agent_output: 'If you want, I can keep going from here.',
1262
+ last_agent_output: 'Keep going and finish the cleanup from here.',
1103
1263
  }, null, 2));
1104
1264
  const watcherScript = new URL('../../../dist/scripts/notify-fallback-watcher.js', import.meta.url).pathname;
1105
1265
  const notifyHook = new URL('../../../dist/scripts/notify-hook.js', import.meta.url).pathname;
@@ -1115,7 +1275,7 @@ exit 0
1115
1275
  });
1116
1276
  assert.equal(result.status, 0, result.stderr || result.stdout);
1117
1277
  const tmuxLog = await readFile(tmuxLogPath, 'utf8').catch(() => '');
1118
- assert.doesNotMatch(tmuxLog, /send-keys -t %42 -l yes, proceed \[OMX_TMUX_INJECT\]/);
1278
+ assert.doesNotMatch(tmuxLog, defaultAutoNudgePattern('%42'));
1119
1279
  const watcherStatePath = join(wd, '.omx', 'state', 'notify-fallback-state.json');
1120
1280
  const watcherState = JSON.parse(await readFile(watcherStatePath, 'utf-8'));
1121
1281
  assert.equal(watcherState.fallback_auto_nudge?.last_reason, 'eligible_but_not_sent');
@@ -1145,7 +1305,7 @@ exit 0
1145
1305
  await writeFile(join(wd, '.omx', 'state', 'hud-state.json'), JSON.stringify({
1146
1306
  last_turn_at: new Date(Date.now() - 1_000).toISOString(),
1147
1307
  turn_count: 8,
1148
- last_agent_output: 'If you want, I can keep going from here.',
1308
+ last_agent_output: 'Keep going and finish the cleanup from here.',
1149
1309
  }, null, 2));
1150
1310
  const watcherScript = new URL('../../../dist/scripts/notify-fallback-watcher.js', import.meta.url).pathname;
1151
1311
  const notifyHook = new URL('../../../dist/scripts/notify-hook.js', import.meta.url).pathname;
@@ -1161,7 +1321,7 @@ exit 0
1161
1321
  });
1162
1322
  assert.equal(result.status, 0, result.stderr || result.stdout);
1163
1323
  const tmuxLog = await readFile(tmuxLogPath, 'utf8').catch(() => '');
1164
- assert.doesNotMatch(tmuxLog, /send-keys -t %42 -l yes, proceed \[OMX_TMUX_INJECT\]/);
1324
+ assert.doesNotMatch(tmuxLog, defaultAutoNudgePattern('%42'));
1165
1325
  const watcherStatePath = join(wd, '.omx', 'state', 'notify-fallback-state.json');
1166
1326
  const watcherState = JSON.parse(await readFile(watcherStatePath, 'utf-8'));
1167
1327
  assert.equal(watcherState.fallback_auto_nudge?.last_reason, 'recent_turn_activity');
@@ -1177,7 +1337,7 @@ exit 0
1177
1337
  const tmuxLogPath = join(wd, 'tmux.log');
1178
1338
  const codexHome = join(wd, 'codex-home');
1179
1339
  const lastTurnAt = new Date(Date.now() - 6_000).toISOString();
1180
- const lastMessage = 'If you want, I can keep going from here.';
1340
+ const lastMessage = 'Keep going and finish the cleanup from here.';
1181
1341
  try {
1182
1342
  await mkdir(join(wd, '.omx', 'logs'), { recursive: true });
1183
1343
  await mkdir(join(wd, '.omx', 'state'), { recursive: true });
@@ -1213,7 +1373,7 @@ exit 0
1213
1373
  });
1214
1374
  assert.equal(result.status, 0, result.stderr || result.stdout);
1215
1375
  const tmuxLog = await readFile(tmuxLogPath, 'utf8').catch(() => '');
1216
- assert.doesNotMatch(tmuxLog, /send-keys -t %42 -l yes, proceed \[OMX_TMUX_INJECT\]/);
1376
+ assert.doesNotMatch(tmuxLog, defaultAutoNudgePattern('%42'));
1217
1377
  const watcherStatePath = join(wd, '.omx', 'state', 'notify-fallback-state.json');
1218
1378
  const watcherState = JSON.parse(await readFile(watcherStatePath, 'utf-8'));
1219
1379
  assert.equal(watcherState.fallback_auto_nudge?.last_reason, 'already_nudged_for_signature');
@@ -1229,7 +1389,7 @@ exit 0
1229
1389
  const tmuxLogPath = join(wd, 'tmux.log');
1230
1390
  const codexHome = join(wd, 'codex-home');
1231
1391
  const lastTurnAt = '2026-03-01T00:00:00.000Z';
1232
- const lastMessage = 'If you want, I can keep going from here.';
1392
+ const lastMessage = 'Keep going and finish the cleanup from here.';
1233
1393
  try {
1234
1394
  await mkdir(join(wd, '.omx', 'logs'), { recursive: true });
1235
1395
  await mkdir(join(wd, '.omx', 'state'), { recursive: true });
@@ -1265,7 +1425,7 @@ exit 0
1265
1425
  });
1266
1426
  assert.equal(result.status, 0, result.stderr || result.stdout);
1267
1427
  const tmuxLog = await readFile(tmuxLogPath, 'utf8').catch(() => '');
1268
- assert.doesNotMatch(tmuxLog, /send-keys -t %42 -l yes, proceed \[OMX_TMUX_INJECT\]/);
1428
+ assert.doesNotMatch(tmuxLog, defaultAutoNudgePattern('%42'));
1269
1429
  const watcherStatePath = join(wd, '.omx', 'state', 'notify-fallback-state.json');
1270
1430
  const watcherState = JSON.parse(await readFile(watcherStatePath, 'utf-8'));
1271
1431
  assert.equal(watcherState.fallback_auto_nudge?.last_reason, 'already_nudged_for_signature');
@@ -1530,6 +1690,53 @@ exit 0
1530
1690
  await rm(wd, { recursive: true, force: true });
1531
1691
  }
1532
1692
  });
1693
+ it('suppresses Ralph continue steer when session-scoped Ralph is stuck in stale starting phase', async () => {
1694
+ const wd = await mkdtemp(join(tmpdir(), 'omx-fallback-ralph-starting-stale-'));
1695
+ const fakeBinDir = join(wd, 'fake-bin');
1696
+ const tmuxLogPath = join(wd, 'tmux.log');
1697
+ const stateDir = join(wd, '.omx', 'state');
1698
+ const sessionId = 'sess-starting-stale';
1699
+ const sessionStateDir = join(stateDir, 'sessions', sessionId);
1700
+ const watcherStatePath = join(stateDir, 'notify-fallback-state.json');
1701
+ try {
1702
+ await mkdir(sessionStateDir, { recursive: true });
1703
+ await mkdir(fakeBinDir, { recursive: true });
1704
+ await writeFile(join(fakeBinDir, 'tmux'), buildFakeTmux(tmuxLogPath));
1705
+ await chmod(join(fakeBinDir, 'tmux'), 0o755);
1706
+ await writeSessionStart(wd, sessionId);
1707
+ await writeFile(join(sessionStateDir, 'ralph-state.json'), JSON.stringify({
1708
+ active: true,
1709
+ current_phase: 'starting',
1710
+ started_at: new Date(Date.now() - 180_000).toISOString(),
1711
+ tmux_pane_id: '%42',
1712
+ }, null, 2));
1713
+ await writeFile(join(sessionStateDir, 'hud-state.json'), JSON.stringify({
1714
+ last_progress_at: new Date(Date.now() - 180_000).toISOString(),
1715
+ }, null, 2));
1716
+ await writeFile(watcherStatePath, JSON.stringify({
1717
+ ralph_continue_steer: {
1718
+ last_sent_at: new Date(Date.now() - 61_000).toISOString(),
1719
+ },
1720
+ }, null, 2));
1721
+ const watcherScript = new URL('../../../dist/scripts/notify-fallback-watcher.js', import.meta.url).pathname;
1722
+ const notifyHook = new URL('../../../dist/scripts/notify-hook.js', import.meta.url).pathname;
1723
+ const run = spawnSync(process.execPath, [watcherScript, '--once', '--cwd', wd, '--notify-script', notifyHook, '--poll-ms', '50'], {
1724
+ encoding: 'utf-8',
1725
+ env: buildCleanNotifyEnv({
1726
+ PATH: `${fakeBinDir}:${process.env.PATH || ''}`,
1727
+ }),
1728
+ });
1729
+ assert.equal(run.status, 0, run.stderr || run.stdout);
1730
+ const tmuxLog = await readFile(tmuxLogPath, 'utf8').catch(() => '');
1731
+ const sends = tmuxLog.match(/send-keys -t %42 -l Ralph loop active continue \[OMX_TMUX_INJECT\]/g) || [];
1732
+ assert.equal(sends.length, 0, 'stale starting phase should suppress continue steer');
1733
+ const watcherState = JSON.parse(await readFile(watcherStatePath, 'utf-8'));
1734
+ assert.equal(watcherState.ralph_continue_steer?.last_reason, 'starting_stale');
1735
+ }
1736
+ finally {
1737
+ await rm(wd, { recursive: true, force: true });
1738
+ }
1739
+ });
1533
1740
  it('suppresses Ralph continue steer while tracked native subagents are still active', async () => {
1534
1741
  const wd = await mkdtemp(join(tmpdir(), 'omx-fallback-ralph-subagents-active-'));
1535
1742
  const fakeBinDir = join(wd, 'fake-bin');
@@ -1551,7 +1758,7 @@ exit 0
1551
1758
  owner_omx_session_id: omxSessionId,
1552
1759
  owner_codex_session_id: codexSessionId,
1553
1760
  }, null, 2));
1554
- await writeFile(join(stateDir, 'hud-state.json'), JSON.stringify({
1761
+ await writeFile(join(stateDir, 'sessions', omxSessionId, 'hud-state.json'), JSON.stringify({
1555
1762
  last_progress_at: new Date(Date.now() - 61_000).toISOString(),
1556
1763
  }, null, 2));
1557
1764
  await writeFile(statePath, JSON.stringify({
@@ -1846,6 +2053,52 @@ exit 0
1846
2053
  await rm(wd, { recursive: true, force: true });
1847
2054
  }
1848
2055
  });
2056
+ it('treats a long-running starting phase as terminal so Ralph steer stops', async () => {
2057
+ const wd = await mkdtemp(join(tmpdir(), 'omx-fallback-starting-phase-stale-'));
2058
+ const fakeBinDir = join(wd, 'fake-bin');
2059
+ const tmuxLogPath = join(wd, 'tmux.log');
2060
+ const stateDir = join(wd, '.omx', 'state');
2061
+ const watcherStatePath = join(stateDir, 'notify-fallback-state.json');
2062
+ const staleStartedAt = new Date(Date.now() - 3 * 60_000).toISOString();
2063
+ try {
2064
+ await mkdir(stateDir, { recursive: true });
2065
+ await mkdir(fakeBinDir, { recursive: true });
2066
+ await writeFile(join(fakeBinDir, 'tmux'), buildFakeTmux(tmuxLogPath));
2067
+ await chmod(join(fakeBinDir, 'tmux'), 0o755);
2068
+ await writeFile(join(stateDir, 'ralph-state.json'), JSON.stringify({
2069
+ active: true,
2070
+ current_phase: 'starting',
2071
+ started_at: staleStartedAt,
2072
+ tmux_pane_id: '%42',
2073
+ }, null, 2));
2074
+ await writeFile(join(stateDir, 'hud-state.json'), JSON.stringify({
2075
+ last_progress_at: new Date(Date.now() - 5 * 60_000).toISOString(),
2076
+ }, null, 2));
2077
+ await writeFile(watcherStatePath, JSON.stringify({
2078
+ ralph_continue_steer: {
2079
+ last_sent_at: new Date(Date.now() - 61_000).toISOString(),
2080
+ },
2081
+ }, null, 2));
2082
+ const watcherScript = new URL('../../../dist/scripts/notify-fallback-watcher.js', import.meta.url).pathname;
2083
+ const notifyHook = new URL('../../../dist/scripts/notify-hook.js', import.meta.url).pathname;
2084
+ const run = spawnSync(process.execPath, [watcherScript, '--once', '--cwd', wd, '--notify-script', notifyHook, '--poll-ms', '50'], {
2085
+ encoding: 'utf-8',
2086
+ env: buildCleanNotifyEnv({
2087
+ PATH: `${fakeBinDir}:${process.env.PATH || ''}`,
2088
+ }),
2089
+ });
2090
+ assert.equal(run.status, 0, run.stderr || run.stdout);
2091
+ const tmuxLog = await readFile(tmuxLogPath, 'utf8').catch(() => '');
2092
+ const sends = tmuxLog.match(/send-keys -t %42 -l Ralph loop active continue \[OMX_TMUX_INJECT\]/g) || [];
2093
+ assert.equal(sends.length, 0, 'stale starting phase should block Ralph continue steer');
2094
+ const watcherState = JSON.parse(await readFile(watcherStatePath, 'utf-8'));
2095
+ assert.equal(watcherState.ralph_continue_steer?.active, false);
2096
+ assert.equal(watcherState.ralph_continue_steer?.last_reason, 'terminal');
2097
+ }
2098
+ finally {
2099
+ await rm(wd, { recursive: true, force: true });
2100
+ }
2101
+ });
1849
2102
  it('globally debounces Ralph continue steer across concurrent watcher instances', async () => {
1850
2103
  const wd = await mkdtemp(join(tmpdir(), 'omx-fallback-ralph-global-debounce-'));
1851
2104
  const fakeBinDir = join(wd, 'fake-bin');
@@ -2178,7 +2431,7 @@ exit 0
2178
2431
  current_phase: 'executing',
2179
2432
  tmux_pane_id: '%42',
2180
2433
  }, null, 2));
2181
- await writeFile(join(stateDir, 'hud-state.json'), JSON.stringify({
2434
+ await writeFile(join(sessionStateDir, 'hud-state.json'), JSON.stringify({
2182
2435
  last_progress_at: new Date(Date.now() - 61_000).toISOString(),
2183
2436
  }, null, 2));
2184
2437
  await writeFile(join(stateDir, 'notify-fallback-state.json'), JSON.stringify({
@@ -2320,6 +2573,123 @@ exit 0
2320
2573
  await rm(tempHome, { recursive: true, force: true });
2321
2574
  }
2322
2575
  });
2576
+ it('stays alive after parent exit when coarse team-state is missing but canonical team is active for the current session', async () => {
2577
+ const wd = await mkdtemp(join(tmpdir(), 'omx-fallback-parent-gone-canonical-team-'));
2578
+ const tempHome = await mkdtemp(join(tmpdir(), 'omx-fallback-parent-gone-canonical-home-'));
2579
+ const fakeBinDir = join(wd, 'fake-bin');
2580
+ const tmuxLogPath = join(wd, 'tmux.log');
2581
+ let child;
2582
+ try {
2583
+ await mkdir(fakeBinDir, { recursive: true });
2584
+ await writeCanonicalWatcherTeamFixture(wd, {
2585
+ teamName: 'dispatch-team',
2586
+ sessionId: 'sess-parent-canonical',
2587
+ ownerSessionId: 'sess-parent-canonical',
2588
+ });
2589
+ await writeFile(join(fakeBinDir, 'tmux'), buildFakeTmux(tmuxLogPath));
2590
+ await chmod(join(fakeBinDir, 'tmux'), 0o755);
2591
+ const watcherScript = new URL('../../../dist/scripts/notify-fallback-watcher.js', import.meta.url).pathname;
2592
+ const notifyHook = new URL('../../../dist/scripts/notify-hook.js', import.meta.url).pathname;
2593
+ const logPath = join(wd, '.omx', 'logs', `notify-fallback-${new Date().toISOString().split('T')[0]}.jsonl`);
2594
+ const shortLivedParent = spawn(process.execPath, ['-e', 'setTimeout(() => process.exit(0), 10)'], {
2595
+ stdio: 'ignore',
2596
+ });
2597
+ assert.ok(shortLivedParent.pid, 'expected short-lived parent pid');
2598
+ const parentPid = shortLivedParent.pid;
2599
+ await once(shortLivedParent, 'exit');
2600
+ child = spawn(process.execPath, [
2601
+ watcherScript,
2602
+ '--cwd',
2603
+ wd,
2604
+ '--notify-script',
2605
+ notifyHook,
2606
+ '--poll-ms',
2607
+ '50',
2608
+ '--parent-pid',
2609
+ String(parentPid),
2610
+ '--max-lifetime-ms',
2611
+ '5000',
2612
+ ], {
2613
+ cwd: wd,
2614
+ stdio: 'ignore',
2615
+ env: buildCleanNotifyEnv({ HOME: tempHome, PATH: `${fakeBinDir}:${process.env.PATH || ''}` }),
2616
+ });
2617
+ await waitFor(async () => isPidAlive(child?.pid), 4000, 50);
2618
+ await waitFor(async () => {
2619
+ const logEntries = (await readFile(logPath, 'utf-8').catch(() => ''))
2620
+ .trim()
2621
+ .split('\n')
2622
+ .filter(Boolean)
2623
+ .map((line) => JSON.parse(line));
2624
+ return logEntries.some((entry) => (entry.type === 'watcher_parent_guard' && entry.reason === 'parent_gone_deferred_for_active_team'));
2625
+ }, 4000, 50);
2626
+ assert.ok(isPidAlive(child.pid), 'expected watcher to stay alive while canonical team panes remain active');
2627
+ }
2628
+ finally {
2629
+ if (child && isPidAlive(child.pid)) {
2630
+ child.kill('SIGTERM');
2631
+ await waitForExit(child, 4000).catch(() => { });
2632
+ }
2633
+ await rm(wd, { recursive: true, force: true });
2634
+ await rm(tempHome, { recursive: true, force: true });
2635
+ }
2636
+ });
2637
+ it('does not defer parent-loss shutdown when canonical owner session is blank and coarse team-state is missing', async () => {
2638
+ const wd = await mkdtemp(join(tmpdir(), 'omx-fallback-parent-gone-ownerless-team-'));
2639
+ const tempHome = await mkdtemp(join(tmpdir(), 'omx-fallback-parent-gone-ownerless-home-'));
2640
+ const fakeBinDir = join(wd, 'fake-bin');
2641
+ const tmuxLogPath = join(wd, 'tmux.log');
2642
+ let child;
2643
+ try {
2644
+ await mkdir(fakeBinDir, { recursive: true });
2645
+ await writeCanonicalWatcherTeamFixture(wd, {
2646
+ teamName: 'dispatch-team',
2647
+ sessionId: 'sess-parent-ownerless',
2648
+ ownerSessionId: '',
2649
+ });
2650
+ await writeFile(join(fakeBinDir, 'tmux'), buildFakeTmux(tmuxLogPath));
2651
+ await chmod(join(fakeBinDir, 'tmux'), 0o755);
2652
+ const watcherScript = new URL('../../../dist/scripts/notify-fallback-watcher.js', import.meta.url).pathname;
2653
+ const notifyHook = new URL('../../../dist/scripts/notify-hook.js', import.meta.url).pathname;
2654
+ const logPath = join(wd, '.omx', 'logs', `notify-fallback-${new Date().toISOString().split('T')[0]}.jsonl`);
2655
+ const shortLivedParent = spawn(process.execPath, ['-e', 'setTimeout(() => process.exit(0), 10)'], {
2656
+ stdio: 'ignore',
2657
+ });
2658
+ assert.ok(shortLivedParent.pid, 'expected short-lived parent pid');
2659
+ const parentPid = shortLivedParent.pid;
2660
+ await once(shortLivedParent, 'exit');
2661
+ child = spawn(process.execPath, [
2662
+ watcherScript,
2663
+ '--cwd',
2664
+ wd,
2665
+ '--notify-script',
2666
+ notifyHook,
2667
+ '--poll-ms',
2668
+ '50',
2669
+ '--parent-pid',
2670
+ String(parentPid),
2671
+ '--max-lifetime-ms',
2672
+ '5000',
2673
+ ], {
2674
+ cwd: wd,
2675
+ stdio: 'ignore',
2676
+ env: buildCleanNotifyEnv({ HOME: tempHome, PATH: `${fakeBinDir}:${process.env.PATH || ''}` }),
2677
+ });
2678
+ await waitForExit(child, 4000);
2679
+ assert.equal(child.exitCode, 0);
2680
+ const logEntries = (await readFile(logPath, 'utf-8')).trim().split('\n').filter(Boolean).map((line) => JSON.parse(line));
2681
+ assert.ok(logEntries.some((entry) => (entry.type === 'watcher_stop' && entry.reason === 'parent_gone')));
2682
+ assert.ok(!logEntries.some((entry) => (entry.type === 'watcher_parent_guard' && entry.reason === 'parent_gone_deferred_for_active_team')), 'ownerless canonical team must not defer parent-loss shutdown');
2683
+ }
2684
+ finally {
2685
+ if (child && isPidAlive(child.pid)) {
2686
+ child.kill('SIGTERM');
2687
+ await waitForExit(child, 4000).catch(() => { });
2688
+ }
2689
+ await rm(wd, { recursive: true, force: true });
2690
+ await rm(tempHome, { recursive: true, force: true });
2691
+ }
2692
+ });
2323
2693
  it('does not defer parent-loss shutdown for a team that is already terminal in phase.json', async () => {
2324
2694
  const wd = await mkdtemp(join(tmpdir(), 'omx-fallback-parent-gone-terminal-team-'));
2325
2695
  const tempHome = await mkdtemp(join(tmpdir(), 'omx-fallback-parent-gone-terminal-team-home-'));