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
@@ -10,6 +10,7 @@ import {
10
10
  initTeamState,
11
11
  readTeamLeaderAttention,
12
12
  readTeamPhase,
13
+ writeTeamLeaderAttention,
13
14
  } from "../../team/state.js";
14
15
  import {
15
16
  dispatchCodexNativeHook,
@@ -23,6 +24,46 @@ async function writeJson(path: string, value: unknown): Promise<void> {
23
24
  await writeFile(path, JSON.stringify(value, null, 2));
24
25
  }
25
26
 
27
+ async function writeReleaseReadinessLeaderAttention(
28
+ teamName: string,
29
+ sessionId: string,
30
+ cwd: string,
31
+ options: { workRemaining: boolean },
32
+ ): Promise<void> {
33
+ await writeTeamLeaderAttention(teamName, {
34
+ team_name: teamName,
35
+ updated_at: "2026-04-12T17:20:00.000Z",
36
+ source: "notify_hook",
37
+ leader_decision_state: "done_waiting_on_leader",
38
+ leader_attention_pending: true,
39
+ leader_attention_reason: "leader_session_stopped",
40
+ attention_reasons: ["leader_session_stopped"],
41
+ leader_stale: true,
42
+ leader_session_active: false,
43
+ leader_session_id: sessionId,
44
+ leader_session_stopped_at: "2026-04-12T17:20:00.000Z",
45
+ unread_leader_message_count: 0,
46
+ work_remaining: options.workRemaining,
47
+ stalled_for_ms: null,
48
+ }, cwd);
49
+ }
50
+
51
+ async function writeReleaseReadinessStateMarker(
52
+ sessionId: string,
53
+ teamName: string,
54
+ cwd: string,
55
+ ): Promise<void> {
56
+ await writeJson(
57
+ join(cwd, ".omx", "state", "sessions", sessionId, "release-readiness-state.json"),
58
+ {
59
+ active: true,
60
+ session_id: sessionId,
61
+ team_name: teamName,
62
+ stable_final_recommendation_emitted: true,
63
+ },
64
+ );
65
+ }
66
+
26
67
  const TEAM_STOP_COMMIT_GUIDANCE =
27
68
  " If system-generated worker auto-checkpoint commits exist, rewrite them into Lore-format final commits before merge/finalization.";
28
69
  const DEFAULT_AUTO_NUDGE_RESPONSE =
@@ -93,6 +134,36 @@ describe("codex native hook config", () => {
93
134
  });
94
135
 
95
136
  describe("codex native hook dispatch", () => {
137
+ it("emits deterministic JSON stdout when CLI stdin is malformed", () => {
138
+ const stdout = execFileSync(
139
+ process.execPath,
140
+ [join(process.cwd(), "dist", "scripts", "codex-native-hook.js")],
141
+ {
142
+ cwd: process.cwd(),
143
+ input: "{",
144
+ encoding: "utf-8",
145
+ stdio: ["pipe", "pipe", "pipe"],
146
+ },
147
+ );
148
+
149
+ const output = JSON.parse(stdout.trim()) as {
150
+ decision?: string;
151
+ reason?: string;
152
+ hookSpecificOutput?: { hookEventName?: string; additionalContext?: string };
153
+ };
154
+
155
+ assert.equal(output.decision, "block");
156
+ assert.equal(
157
+ output.reason,
158
+ "OMX native hook received malformed JSON input. Preserve runtime state and inspect the emitting hook payload before retrying.",
159
+ );
160
+ assert.equal(output.hookSpecificOutput?.hookEventName, "Unknown");
161
+ assert.match(
162
+ String(output.hookSpecificOutput?.additionalContext ?? ""),
163
+ /stdin JSON parsing failed inside codex-native-hook:/,
164
+ );
165
+ });
166
+
96
167
  it("maps Codex events onto OMX logical surfaces", () => {
97
168
  assert.equal(mapCodexHookEventToOmxEvent("SessionStart"), "session-start");
98
169
  assert.equal(mapCodexHookEventToOmxEvent("UserPromptSubmit"), "keyword-detector");
@@ -308,6 +379,52 @@ describe("codex native hook dispatch", () => {
308
379
  }
309
380
  });
310
381
 
382
+ it("does not expose submitted prompt text to keyword-detector hook plugins", async () => {
383
+ const cwd = await mkdtemp(join(tmpdir(), "omx-native-hook-prompt-sanitized-"));
384
+ try {
385
+ await mkdir(join(cwd, ".omx", "hooks"), { recursive: true });
386
+ await writeFile(
387
+ join(cwd, ".omx", "hooks", "capture-keyword-context.mjs"),
388
+ `import { mkdir, writeFile } from "node:fs/promises";
389
+ import { dirname, join } from "node:path";
390
+
391
+ export async function onHookEvent(event) {
392
+ if (event.event !== "keyword-detector") return;
393
+ const outPath = join(process.cwd(), ".omx", "captured-keyword-context.json");
394
+ await mkdir(dirname(outPath), { recursive: true });
395
+ await writeFile(outPath, JSON.stringify(event.context, null, 2));
396
+ }
397
+ `,
398
+ "utf-8",
399
+ );
400
+
401
+ await dispatchCodexNativeHook(
402
+ {
403
+ hook_event_name: "UserPromptSubmit",
404
+ cwd,
405
+ session_id: "sess-sanitized-1",
406
+ thread_id: "thread-sanitized-1",
407
+ turn_id: "turn-sanitized-1",
408
+ prompt: "$ralplan approve this blocker-sensitive request",
409
+ },
410
+ { cwd },
411
+ );
412
+
413
+ const captured = JSON.parse(
414
+ await readFile(join(cwd, ".omx", "captured-keyword-context.json"), "utf-8"),
415
+ ) as { prompt?: string; payload?: Record<string, unknown> };
416
+
417
+ assert.equal(captured.prompt, undefined);
418
+ assert.equal(captured.payload?.prompt, undefined);
419
+ assert.equal(captured.payload?.input, undefined);
420
+ assert.equal(captured.payload?.user_prompt, undefined);
421
+ assert.equal(captured.payload?.userPrompt, undefined);
422
+ assert.equal(captured.payload?.text, undefined);
423
+ } finally {
424
+ await rm(cwd, { recursive: true, force: true });
425
+ }
426
+ });
427
+
311
428
  it("does not emit UserPromptSubmit routing context for unknown $tokens", async () => {
312
429
  const cwd = await mkdtemp(join(tmpdir(), "omx-native-hook-unknown-token-"));
313
430
  try {
@@ -1288,6 +1405,101 @@ esac
1288
1405
  }
1289
1406
  });
1290
1407
 
1408
+ it("emits one concise final decision summary and auto-finalize guidance when release-readiness already has a stable final recommendation and no active worker tasks", async () => {
1409
+ const cwd = await mkdtemp(join(tmpdir(), "omx-native-hook-stop-release-readiness-finalize-"));
1410
+ try {
1411
+ await initTeamState(
1412
+ "release-ready-team",
1413
+ "release readiness finalize",
1414
+ "executor",
1415
+ 1,
1416
+ cwd,
1417
+ undefined,
1418
+ { ...process.env, OMX_SESSION_ID: "sess-stop-release-ready" },
1419
+ );
1420
+ await writeReleaseReadinessLeaderAttention(
1421
+ "release-ready-team",
1422
+ "sess-stop-release-ready",
1423
+ cwd,
1424
+ { workRemaining: false },
1425
+ );
1426
+ await writeReleaseReadinessStateMarker(
1427
+ "sess-stop-release-ready",
1428
+ "release-ready-team",
1429
+ cwd,
1430
+ );
1431
+
1432
+ const result = await dispatchCodexNativeHook(
1433
+ {
1434
+ hook_event_name: "Stop",
1435
+ cwd,
1436
+ session_id: "sess-stop-release-ready",
1437
+ thread_id: "thread-stop-release-ready",
1438
+ turn_id: "turn-stop-release-ready-1",
1439
+ mode: "release-readiness",
1440
+ last_assistant_message: "Launch-ready: yes",
1441
+ },
1442
+ { cwd },
1443
+ );
1444
+
1445
+ assert.equal(result.omxEventName, "stop");
1446
+ assert.deepEqual(result.outputJson, {
1447
+ decision: "block",
1448
+ reason:
1449
+ 'Stable final recommendation already reached with no active worker tasks. Emit exactly one concise final decision summary aligned to "Launch-ready: yes." with no filler or residual acknowledgements (for example "yes"), then stop.',
1450
+ stopReason: "release_readiness_auto_finalize",
1451
+ systemMessage:
1452
+ "OMX release-readiness detected a stable final recommendation with no active worker tasks; emit one concise final decision summary and finalize.",
1453
+ });
1454
+ } finally {
1455
+ await rm(cwd, { recursive: true, force: true });
1456
+ }
1457
+ });
1458
+
1459
+ it("does not auto-finalize non-release team stops that happen to contain a stable recommendation summary", async () => {
1460
+ const cwd = await mkdtemp(join(tmpdir(), "omx-native-hook-stop-non-release-readiness-control-"));
1461
+ try {
1462
+ await initTeamState(
1463
+ "general-review-team",
1464
+ "general team stop control",
1465
+ "executor",
1466
+ 1,
1467
+ cwd,
1468
+ undefined,
1469
+ { ...process.env, OMX_SESSION_ID: "sess-stop-general-review" },
1470
+ );
1471
+ await writeReleaseReadinessLeaderAttention(
1472
+ "general-review-team",
1473
+ "sess-stop-general-review",
1474
+ cwd,
1475
+ { workRemaining: false },
1476
+ );
1477
+
1478
+ const result = await dispatchCodexNativeHook(
1479
+ {
1480
+ hook_event_name: "Stop",
1481
+ cwd,
1482
+ session_id: "sess-stop-general-review",
1483
+ thread_id: "thread-stop-general-review",
1484
+ turn_id: "turn-stop-general-review-1",
1485
+ last_assistant_message: "Launch-ready: yes",
1486
+ },
1487
+ { cwd },
1488
+ );
1489
+
1490
+ assert.equal(result.omxEventName, "stop");
1491
+ assert.deepEqual(result.outputJson, {
1492
+ decision: "block",
1493
+ reason:
1494
+ `OMX team pipeline is still active (general-review-team) at phase team-exec; continue coordinating until the team reaches a terminal phase.${TEAM_STOP_COMMIT_GUIDANCE}`,
1495
+ stopReason: "team_team-exec",
1496
+ systemMessage: "OMX team pipeline is still active at phase team-exec.",
1497
+ });
1498
+ } finally {
1499
+ await rm(cwd, { recursive: true, force: true });
1500
+ }
1501
+ });
1502
+
1291
1503
  it("re-fires canonical-team Stop output for a later fresh Stop reply when coarse mode state is missing", async () => {
1292
1504
  const cwd = await mkdtemp(join(tmpdir(), "omx-native-hook-stop-team-canonical-refire-"));
1293
1505
  try {
@@ -1514,6 +1726,13 @@ esac
1514
1726
  current_phase: "team-exec",
1515
1727
  team_name: "session-live-team",
1516
1728
  });
1729
+ await writeJson(join(stateDir, "team", "session-live-team", "phase.json"), {
1730
+ current_phase: "team-exec",
1731
+ max_fix_attempts: 3,
1732
+ current_fix_attempt: 0,
1733
+ transitions: [],
1734
+ updated_at: new Date().toISOString(),
1735
+ });
1517
1736
 
1518
1737
  const result = await dispatchCodexNativeHook(
1519
1738
  {
@@ -1909,6 +2128,39 @@ esac
1909
2128
  }
1910
2129
  });
1911
2130
 
2131
+ it("does not block Stop when the current session Ralph state is cancelled even if stale root fallback remains", async () => {
2132
+ const cwd = await mkdtemp(join(tmpdir(), "omx-native-hook-stop-cancelled-session-ralph-"));
2133
+ try {
2134
+ const stateDir = join(cwd, ".omx", "state");
2135
+ await mkdir(join(stateDir, "sessions", "sess-current"), { recursive: true });
2136
+ await writeJson(join(stateDir, "session.json"), { session_id: "sess-current", cwd });
2137
+ await writeJson(join(stateDir, "sessions", "sess-current", "ralph-state.json"), {
2138
+ active: false,
2139
+ current_phase: "cancelled",
2140
+ completed_at: "2026-04-10T23:30:38.000Z",
2141
+ session_id: "sess-current",
2142
+ });
2143
+ await writeJson(join(stateDir, "ralph-state.json"), {
2144
+ active: true,
2145
+ current_phase: "starting",
2146
+ });
2147
+
2148
+ const result = await dispatchCodexNativeHook(
2149
+ {
2150
+ hook_event_name: "Stop",
2151
+ cwd,
2152
+ session_id: "sess-current",
2153
+ },
2154
+ { cwd },
2155
+ );
2156
+
2157
+ assert.equal(result.omxEventName, "stop");
2158
+ assert.equal(result.outputJson, null);
2159
+ } finally {
2160
+ await rm(cwd, { recursive: true, force: true });
2161
+ }
2162
+ });
2163
+
1912
2164
  it("does not block Stop from root Ralph fallback when an explicit session_id is present and session.json points to another worktree", async () => {
1913
2165
  const cwd = await mkdtemp(join(tmpdir(), "omx-native-hook-stop-root-fallback-cwd-mismatch-"));
1914
2166
  try {
@@ -2326,6 +2578,48 @@ esac
2326
2578
  }
2327
2579
  });
2328
2580
 
2581
+ it("does not suppress native auto-nudge from active root deep-interview state when the current scoped mode state is explicitly inactive", async () => {
2582
+ const cwd = await mkdtemp(join(tmpdir(), "omx-native-hook-auto-nudge-inactive-scoped-mode-"));
2583
+ try {
2584
+ const stateDir = join(cwd, ".omx", "state");
2585
+ await mkdir(join(stateDir, "sessions", "sess-stop-auto-inactive-mode"), { recursive: true });
2586
+ await writeJson(join(stateDir, "session.json"), { session_id: "sess-stop-auto-inactive-mode" });
2587
+ await writeJson(join(stateDir, "sessions", "sess-stop-auto-inactive-mode", "deep-interview-state.json"), {
2588
+ active: false,
2589
+ mode: "deep-interview",
2590
+ current_phase: "completed",
2591
+ });
2592
+ await writeJson(join(stateDir, "deep-interview-state.json"), {
2593
+ active: true,
2594
+ mode: "deep-interview",
2595
+ current_phase: "intent-first",
2596
+ });
2597
+
2598
+ const result = await dispatchCodexNativeHook(
2599
+ {
2600
+ hook_event_name: "Stop",
2601
+ cwd,
2602
+ session_id: "sess-stop-auto-inactive-mode",
2603
+ thread_id: "thread-stop-auto-inactive-mode",
2604
+ turn_id: "turn-stop-auto-inactive-mode-1",
2605
+ last_assistant_message: "Keep going and finish the cleanup.",
2606
+ },
2607
+ { cwd },
2608
+ );
2609
+
2610
+ assert.equal(result.omxEventName, "stop");
2611
+ assert.deepEqual(result.outputJson, {
2612
+ decision: "block",
2613
+ reason: DEFAULT_AUTO_NUDGE_RESPONSE,
2614
+ stopReason: "auto_nudge",
2615
+ systemMessage:
2616
+ "OMX native Stop detected a stall/permission-style handoff and continued the turn automatically.",
2617
+ });
2618
+ } finally {
2619
+ await rm(cwd, { recursive: true, force: true });
2620
+ }
2621
+ });
2622
+
2329
2623
  it("re-fires team Stop output for a later fresh Stop reply while the team is still active", async () => {
2330
2624
  const cwd = await mkdtemp(join(tmpdir(), "omx-native-hook-stop-team-refire-"));
2331
2625
  try {
@@ -2416,6 +2710,90 @@ esac
2416
2710
  }
2417
2711
  });
2418
2712
 
2713
+ it("does not block Stop from orphaned team mode state after cleanup removed canonical team artifacts", async () => {
2714
+ const cwd = await mkdtemp(join(tmpdir(), "omx-native-hook-stop-orphaned-team-state-"));
2715
+ try {
2716
+ const stateDir = join(cwd, ".omx", "state");
2717
+ await mkdir(join(stateDir, "sessions", "sess-current"), { recursive: true });
2718
+ await writeJson(join(stateDir, "session.json"), { session_id: "sess-current" });
2719
+ await writeJson(join(stateDir, "team-state.json"), {
2720
+ active: true,
2721
+ current_phase: "starting",
2722
+ team_name: "cleaned-team",
2723
+ session_id: "sess-current",
2724
+ });
2725
+
2726
+ const result = await dispatchCodexNativeHook(
2727
+ {
2728
+ hook_event_name: "Stop",
2729
+ cwd,
2730
+ session_id: "sess-current",
2731
+ },
2732
+ { cwd },
2733
+ );
2734
+
2735
+ assert.equal(result.omxEventName, "stop");
2736
+ assert.equal(result.outputJson, null);
2737
+ } finally {
2738
+ await rm(cwd, { recursive: true, force: true });
2739
+ }
2740
+ });
2741
+
2742
+ it("prefers the current session team state over a stale root team fallback during Stop", async () => {
2743
+ const cwd = await mkdtemp(join(tmpdir(), "omx-native-hook-stop-current-session-team-preferred-"));
2744
+ try {
2745
+ const stateDir = join(cwd, ".omx", "state");
2746
+ await mkdir(join(stateDir, "sessions", "sess-current"), { recursive: true });
2747
+ await writeJson(join(stateDir, "session.json"), { session_id: "sess-current" });
2748
+ await writeJson(join(stateDir, "sessions", "sess-current", "team-state.json"), {
2749
+ active: true,
2750
+ current_phase: "starting",
2751
+ team_name: "current-team",
2752
+ session_id: "sess-current",
2753
+ });
2754
+ await writeJson(join(stateDir, "team", "current-team", "phase.json"), {
2755
+ current_phase: "team-verify",
2756
+ max_fix_attempts: 3,
2757
+ current_fix_attempt: 1,
2758
+ transitions: [],
2759
+ updated_at: new Date().toISOString(),
2760
+ });
2761
+ await writeJson(join(stateDir, "team-state.json"), {
2762
+ active: true,
2763
+ current_phase: "starting",
2764
+ team_name: "stale-root-team",
2765
+ session_id: "sess-other",
2766
+ });
2767
+ await writeJson(join(stateDir, "team", "stale-root-team", "phase.json"), {
2768
+ current_phase: "team-exec",
2769
+ max_fix_attempts: 3,
2770
+ current_fix_attempt: 0,
2771
+ transitions: [],
2772
+ updated_at: new Date().toISOString(),
2773
+ });
2774
+
2775
+ const result = await dispatchCodexNativeHook(
2776
+ {
2777
+ hook_event_name: "Stop",
2778
+ cwd,
2779
+ session_id: "sess-current",
2780
+ },
2781
+ { cwd },
2782
+ );
2783
+
2784
+ assert.equal(result.omxEventName, "stop");
2785
+ assert.deepEqual(result.outputJson, {
2786
+ decision: "block",
2787
+ reason:
2788
+ `OMX team pipeline is still active (current-team) at phase team-verify; continue coordinating until the team reaches a terminal phase.${TEAM_STOP_COMMIT_GUIDANCE}`,
2789
+ stopReason: "team_team-verify",
2790
+ systemMessage: "OMX team pipeline is still active at phase team-verify.",
2791
+ });
2792
+ } finally {
2793
+ await rm(cwd, { recursive: true, force: true });
2794
+ }
2795
+ });
2796
+
2419
2797
  it("does not fall back to active root team state when the current scoped team state is inactive", async () => {
2420
2798
  const cwd = await mkdtemp(join(tmpdir(), "omx-native-hook-stop-inactive-scoped-team-"));
2421
2799
  try {
@@ -0,0 +1,111 @@
1
+ import assert from 'node:assert/strict';
2
+ import { spawnSync } from 'node:child_process';
3
+ import { existsSync } from 'node:fs';
4
+ import { mkdir, mkdtemp, readFile, rm, writeFile } from 'node:fs/promises';
5
+ import { tmpdir } from 'node:os';
6
+ import { join } from 'node:path';
7
+ import { describe, it } from 'node:test';
8
+
9
+ function todaySessionDir(baseHome: string): string {
10
+ const now = new Date();
11
+ return join(
12
+ baseHome,
13
+ '.codex',
14
+ 'sessions',
15
+ String(now.getUTCFullYear()),
16
+ String(now.getUTCMonth() + 1).padStart(2, '0'),
17
+ String(now.getUTCDate()).padStart(2, '0'),
18
+ );
19
+ }
20
+
21
+ describe('hook-derived-watcher', () => {
22
+ it('dispatches needs-input for assistant_message content arrays', async () => {
23
+ const base = await mkdtemp(join(tmpdir(), 'omx-hook-derived-array-'));
24
+ const homeDir = join(base, 'home');
25
+ const cwd = join(base, 'cwd');
26
+ const hookLogPath = join(cwd, '.omx', 'hook-events.jsonl');
27
+
28
+ try {
29
+ await mkdir(todaySessionDir(homeDir), { recursive: true });
30
+ await mkdir(join(cwd, '.omx', 'hooks'), { recursive: true });
31
+
32
+ await writeFile(
33
+ join(cwd, '.omx', 'hooks', 'capture-needs-input.mjs'),
34
+ `import { appendFile, mkdir } from 'node:fs/promises';
35
+ import { dirname } from 'node:path';
36
+
37
+ export async function onHookEvent(event) {
38
+ await mkdir(dirname(${JSON.stringify(hookLogPath)}), { recursive: true });
39
+ await appendFile(${JSON.stringify(hookLogPath)}, JSON.stringify(event) + '\\n');
40
+ }
41
+ `,
42
+ );
43
+
44
+ const rolloutPath = join(todaySessionDir(homeDir), 'rollout-hook-derived-array.jsonl');
45
+ await writeFile(
46
+ rolloutPath,
47
+ [
48
+ JSON.stringify({
49
+ type: 'session_meta',
50
+ payload: {
51
+ id: 'thread-hook-array',
52
+ cwd,
53
+ },
54
+ }),
55
+ JSON.stringify({
56
+ timestamp: new Date().toISOString(),
57
+ type: 'event_msg',
58
+ payload: {
59
+ type: 'assistant_message',
60
+ turn_id: 'turn-hook-array',
61
+ content: [
62
+ {
63
+ type: 'output_text',
64
+ text: 'Would you like me to continue with the cleanup?',
65
+ },
66
+ {
67
+ type: 'output_text',
68
+ text: 'I need your approval before I keep going.',
69
+ },
70
+ ],
71
+ },
72
+ }),
73
+ '',
74
+ ].join('\n'),
75
+ );
76
+
77
+ const watcherScript = new URL('../hook-derived-watcher.js', import.meta.url).pathname;
78
+ const result = spawnSync(
79
+ process.execPath,
80
+ [watcherScript, '--once', '--cwd', cwd, '--poll-ms', '250'],
81
+ {
82
+ cwd,
83
+ env: {
84
+ ...process.env,
85
+ HOME: homeDir,
86
+ OMX_HOOK_DERIVED_SIGNALS: '1',
87
+ OMX_HOOK_PLUGINS: '1',
88
+ },
89
+ encoding: 'utf8',
90
+ },
91
+ );
92
+
93
+ assert.equal(result.status, 0, result.stderr || result.stdout);
94
+ assert.equal(existsSync(hookLogPath), true, 'expected needs-input hook log to be written');
95
+
96
+ const events = (await readFile(hookLogPath, 'utf-8'))
97
+ .split('\n')
98
+ .map((line) => line.trim())
99
+ .filter(Boolean)
100
+ .map((line) => JSON.parse(line) as Record<string, unknown>);
101
+
102
+ assert.equal(events.length, 1);
103
+ assert.equal(events[0].event, 'needs-input');
104
+ assert.equal(events[0].source, 'derived');
105
+ assert.equal(events[0].parser_reason, 'assistant_message_heuristic_question');
106
+ assert.match(String((events[0].context as Record<string, unknown>)?.preview ?? ''), /Would you like me to continue/i);
107
+ } finally {
108
+ await rm(base, { recursive: true, force: true });
109
+ }
110
+ });
111
+ });