@raidiant/notifai 11.7.1-beta.2 → 12.0.0-beta.11

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 (409) hide show
  1. package/CHANGELOG.md +61 -4
  2. package/NOTICE +16 -0
  3. package/THIRD_PARTY_NOTICES +725 -0
  4. package/bin/notifai.mjs +11420 -0
  5. package/data/install.ps1 +318 -0
  6. package/data/release.json +1 -0
  7. package/inventory.json +1 -0
  8. package/npm-adapter-files.json +52 -0
  9. package/package.json +14 -44
  10. package/README.md +0 -168
  11. package/dist/agent-update-notice.d.ts +0 -7
  12. package/dist/agent-update-notice.js +0 -77
  13. package/dist/agent-update-notice.js.map +0 -1
  14. package/dist/atomic-file.d.ts +0 -30
  15. package/dist/atomic-file.js +0 -148
  16. package/dist/atomic-file.js.map +0 -1
  17. package/dist/attendant-update.d.ts +0 -15
  18. package/dist/attendant-update.js +0 -96
  19. package/dist/attendant-update.js.map +0 -1
  20. package/dist/claude-wake.d.ts +0 -108
  21. package/dist/claude-wake.js +0 -369
  22. package/dist/claude-wake.js.map +0 -1
  23. package/dist/cli-bin.d.ts +0 -26
  24. package/dist/cli-bin.js +0 -220
  25. package/dist/cli-bin.js.map +0 -1
  26. package/dist/cli-contract.d.ts +0 -16
  27. package/dist/cli-contract.js +0 -27
  28. package/dist/cli-contract.js.map +0 -1
  29. package/dist/cli-release.d.ts +0 -36
  30. package/dist/cli-release.js +0 -85
  31. package/dist/cli-release.js.map +0 -1
  32. package/dist/client.d.ts +0 -109
  33. package/dist/client.js +0 -217
  34. package/dist/client.js.map +0 -1
  35. package/dist/codex-answer-control.d.ts +0 -14
  36. package/dist/codex-answer-control.js +0 -41
  37. package/dist/codex-answer-control.js.map +0 -1
  38. package/dist/codex-answer-presentation.d.ts +0 -18
  39. package/dist/codex-answer-presentation.js +0 -113
  40. package/dist/codex-answer-presentation.js.map +0 -1
  41. package/dist/codex-input-lifecycle.d.ts +0 -10
  42. package/dist/codex-input-lifecycle.js +0 -63
  43. package/dist/codex-input-lifecycle.js.map +0 -1
  44. package/dist/codex-native-control.d.ts +0 -13
  45. package/dist/codex-native-control.js +0 -120
  46. package/dist/codex-native-control.js.map +0 -1
  47. package/dist/codex-native-turn.d.ts +0 -37
  48. package/dist/codex-native-turn.js +0 -155
  49. package/dist/codex-native-turn.js.map +0 -1
  50. package/dist/codex-question-bindings.d.ts +0 -76
  51. package/dist/codex-question-bindings.js +0 -151
  52. package/dist/codex-question-bindings.js.map +0 -1
  53. package/dist/codex-queue-control.d.ts +0 -21
  54. package/dist/codex-queue-control.js +0 -59
  55. package/dist/codex-queue-control.js.map +0 -1
  56. package/dist/codex-tool-messages.d.ts +0 -7
  57. package/dist/codex-tool-messages.js +0 -126
  58. package/dist/codex-tool-messages.js.map +0 -1
  59. package/dist/codex-wake.d.ts +0 -58
  60. package/dist/codex-wake.js +0 -135
  61. package/dist/codex-wake.js.map +0 -1
  62. package/dist/command-session.d.ts +0 -14
  63. package/dist/command-session.js +0 -31
  64. package/dist/command-session.js.map +0 -1
  65. package/dist/commands-acknowledge.d.ts +0 -13
  66. package/dist/commands-acknowledge.js +0 -116
  67. package/dist/commands-acknowledge.js.map +0 -1
  68. package/dist/commands-agent-sessions.d.ts +0 -6
  69. package/dist/commands-agent-sessions.js +0 -51
  70. package/dist/commands-agent-sessions.js.map +0 -1
  71. package/dist/commands-ask.d.ts +0 -62
  72. package/dist/commands-ask.js +0 -503
  73. package/dist/commands-ask.js.map +0 -1
  74. package/dist/commands-auth.d.ts +0 -76
  75. package/dist/commands-auth.js +0 -421
  76. package/dist/commands-auth.js.map +0 -1
  77. package/dist/commands-close.d.ts +0 -6
  78. package/dist/commands-close.js +0 -273
  79. package/dist/commands-close.js.map +0 -1
  80. package/dist/commands-config.d.ts +0 -31
  81. package/dist/commands-config.js +0 -272
  82. package/dist/commands-config.js.map +0 -1
  83. package/dist/commands-core.d.ts +0 -145
  84. package/dist/commands-core.js +0 -190
  85. package/dist/commands-core.js.map +0 -1
  86. package/dist/commands-devices.d.ts +0 -41
  87. package/dist/commands-devices.js +0 -144
  88. package/dist/commands-devices.js.map +0 -1
  89. package/dist/commands-doctor.d.ts +0 -25
  90. package/dist/commands-doctor.js +0 -1553
  91. package/dist/commands-doctor.js.map +0 -1
  92. package/dist/commands-guidance.d.ts +0 -27
  93. package/dist/commands-guidance.js +0 -150
  94. package/dist/commands-guidance.js.map +0 -1
  95. package/dist/commands-harness-context.d.ts +0 -57
  96. package/dist/commands-harness-context.js +0 -102
  97. package/dist/commands-harness-context.js.map +0 -1
  98. package/dist/commands-hook-attend.d.ts +0 -70
  99. package/dist/commands-hook-attend.js +0 -655
  100. package/dist/commands-hook-attend.js.map +0 -1
  101. package/dist/commands-hook-diagnostics.d.ts +0 -37
  102. package/dist/commands-hook-diagnostics.js +0 -136
  103. package/dist/commands-hook-diagnostics.js.map +0 -1
  104. package/dist/commands-hook-install.d.ts +0 -26
  105. package/dist/commands-hook-install.js +0 -784
  106. package/dist/commands-hook-install.js.map +0 -1
  107. package/dist/commands-hook-run.d.ts +0 -29
  108. package/dist/commands-hook-run.js +0 -933
  109. package/dist/commands-hook-run.js.map +0 -1
  110. package/dist/commands-hook-shape.d.ts +0 -19
  111. package/dist/commands-hook-shape.js +0 -48
  112. package/dist/commands-hook-shape.js.map +0 -1
  113. package/dist/commands-init.d.ts +0 -42
  114. package/dist/commands-init.js +0 -901
  115. package/dist/commands-init.js.map +0 -1
  116. package/dist/commands-io.d.ts +0 -2
  117. package/dist/commands-io.js +0 -117
  118. package/dist/commands-io.js.map +0 -1
  119. package/dist/commands-logs.d.ts +0 -30
  120. package/dist/commands-logs.js +0 -201
  121. package/dist/commands-logs.js.map +0 -1
  122. package/dist/commands-native-acknowledge.d.ts +0 -8
  123. package/dist/commands-native-acknowledge.js +0 -114
  124. package/dist/commands-native-acknowledge.js.map +0 -1
  125. package/dist/commands-project.d.ts +0 -4
  126. package/dist/commands-project.js +0 -41
  127. package/dist/commands-project.js.map +0 -1
  128. package/dist/commands-receive.d.ts +0 -3
  129. package/dist/commands-receive.js +0 -59
  130. package/dist/commands-receive.js.map +0 -1
  131. package/dist/commands-send-support.d.ts +0 -59
  132. package/dist/commands-send-support.js +0 -258
  133. package/dist/commands-send-support.js.map +0 -1
  134. package/dist/commands-send.d.ts +0 -34
  135. package/dist/commands-send.js +0 -900
  136. package/dist/commands-send.js.map +0 -1
  137. package/dist/commands-setup-proof.d.ts +0 -43
  138. package/dist/commands-setup-proof.js +0 -134
  139. package/dist/commands-setup-proof.js.map +0 -1
  140. package/dist/commands-skill.d.ts +0 -21
  141. package/dist/commands-skill.js +0 -199
  142. package/dist/commands-skill.js.map +0 -1
  143. package/dist/commands-sounds.d.ts +0 -12
  144. package/dist/commands-sounds.js +0 -35
  145. package/dist/commands-sounds.js.map +0 -1
  146. package/dist/commands-update-check.d.ts +0 -6
  147. package/dist/commands-update-check.js +0 -102
  148. package/dist/commands-update-check.js.map +0 -1
  149. package/dist/commands-update-resume.d.ts +0 -7
  150. package/dist/commands-update-resume.js +0 -143
  151. package/dist/commands-update-resume.js.map +0 -1
  152. package/dist/commands-update-skill.d.ts +0 -5
  153. package/dist/commands-update-skill.js +0 -36
  154. package/dist/commands-update-skill.js.map +0 -1
  155. package/dist/commands-update.d.ts +0 -11
  156. package/dist/commands-update.js +0 -286
  157. package/dist/commands-update.js.map +0 -1
  158. package/dist/commands.d.ts +0 -26
  159. package/dist/commands.js +0 -27
  160. package/dist/commands.js.map +0 -1
  161. package/dist/config-schema.d.ts +0 -62
  162. package/dist/config-schema.js +0 -302
  163. package/dist/config-schema.js.map +0 -1
  164. package/dist/config.d.ts +0 -181
  165. package/dist/config.js +0 -357
  166. package/dist/config.js.map +0 -1
  167. package/dist/credentials.d.ts +0 -96
  168. package/dist/credentials.js +0 -342
  169. package/dist/credentials.js.map +0 -1
  170. package/dist/file-lock.d.ts +0 -34
  171. package/dist/file-lock.js +0 -367
  172. package/dist/file-lock.js.map +0 -1
  173. package/dist/generated-session-label.d.ts +0 -2
  174. package/dist/generated-session-label.js +0 -152
  175. package/dist/generated-session-label.js.map +0 -1
  176. package/dist/guidance-content.d.ts +0 -53
  177. package/dist/guidance-content.js +0 -248
  178. package/dist/guidance-content.js.map +0 -1
  179. package/dist/guidance-render.d.ts +0 -25
  180. package/dist/guidance-render.js +0 -47
  181. package/dist/guidance-render.js.map +0 -1
  182. package/dist/guidance.d.ts +0 -69
  183. package/dist/guidance.js +0 -173
  184. package/dist/guidance.js.map +0 -1
  185. package/dist/harness-session-title.d.ts +0 -22
  186. package/dist/harness-session-title.js +0 -74
  187. package/dist/harness-session-title.js.map +0 -1
  188. package/dist/harnesses.d.ts +0 -65
  189. package/dist/harnesses.js +0 -159
  190. package/dist/harnesses.js.map +0 -1
  191. package/dist/hermes-attendant.d.ts +0 -38
  192. package/dist/hermes-attendant.js +0 -337
  193. package/dist/hermes-attendant.js.map +0 -1
  194. package/dist/hermes-plugin.d.ts +0 -11
  195. package/dist/hermes-plugin.js +0 -299
  196. package/dist/hermes-plugin.js.map +0 -1
  197. package/dist/hook-acknowledgements.d.ts +0 -70
  198. package/dist/hook-acknowledgements.js +0 -490
  199. package/dist/hook-acknowledgements.js.map +0 -1
  200. package/dist/hook-adapter.d.ts +0 -48
  201. package/dist/hook-adapter.js +0 -419
  202. package/dist/hook-adapter.js.map +0 -1
  203. package/dist/hook-events.d.ts +0 -105
  204. package/dist/hook-events.js +0 -130
  205. package/dist/hook-events.js.map +0 -1
  206. package/dist/hook-gates.d.ts +0 -7
  207. package/dist/hook-gates.js +0 -20
  208. package/dist/hook-gates.js.map +0 -1
  209. package/dist/hook-input.d.ts +0 -10
  210. package/dist/hook-input.js +0 -64
  211. package/dist/hook-input.js.map +0 -1
  212. package/dist/hook-lifecycle.d.ts +0 -112
  213. package/dist/hook-lifecycle.js +0 -2031
  214. package/dist/hook-lifecycle.js.map +0 -1
  215. package/dist/hook-project-sessions.d.ts +0 -28
  216. package/dist/hook-project-sessions.js +0 -141
  217. package/dist/hook-project-sessions.js.map +0 -1
  218. package/dist/hook-question-lock.d.ts +0 -29
  219. package/dist/hook-question-lock.js +0 -217
  220. package/dist/hook-question-lock.js.map +0 -1
  221. package/dist/hook-question-retirement.d.ts +0 -74
  222. package/dist/hook-question-retirement.js +0 -314
  223. package/dist/hook-question-retirement.js.map +0 -1
  224. package/dist/hook-question-state.d.ts +0 -58
  225. package/dist/hook-question-state.js +0 -239
  226. package/dist/hook-question-state.js.map +0 -1
  227. package/dist/hook-session-state.d.ts +0 -127
  228. package/dist/hook-session-state.js +0 -488
  229. package/dist/hook-session-state.js.map +0 -1
  230. package/dist/hook-types.d.ts +0 -476
  231. package/dist/hook-types.js +0 -4
  232. package/dist/hook-types.js.map +0 -1
  233. package/dist/injection-render.d.ts +0 -36
  234. package/dist/injection-render.js +0 -61
  235. package/dist/injection-render.js.map +0 -1
  236. package/dist/install-hooks.d.ts +0 -470
  237. package/dist/install-hooks.js +0 -1622
  238. package/dist/install-hooks.js.map +0 -1
  239. package/dist/integration-health.d.ts +0 -26
  240. package/dist/integration-health.js +0 -133
  241. package/dist/integration-health.js.map +0 -1
  242. package/dist/interactive.d.ts +0 -22
  243. package/dist/interactive.js +0 -644
  244. package/dist/interactive.js.map +0 -1
  245. package/dist/invocation-context.d.ts +0 -47
  246. package/dist/invocation-context.js +0 -164
  247. package/dist/invocation-context.js.map +0 -1
  248. package/dist/local-path.d.ts +0 -7
  249. package/dist/local-path.js +0 -27
  250. package/dist/local-path.js.map +0 -1
  251. package/dist/logging.d.ts +0 -157
  252. package/dist/logging.js +0 -599
  253. package/dist/logging.js.map +0 -1
  254. package/dist/main-run.d.ts +0 -1
  255. package/dist/main-run.js +0 -42
  256. package/dist/main-run.js.map +0 -1
  257. package/dist/main.d.ts +0 -2
  258. package/dist/main.js +0 -12
  259. package/dist/main.js.map +0 -1
  260. package/dist/native-answer-operation.d.ts +0 -70
  261. package/dist/native-answer-operation.js +0 -247
  262. package/dist/native-answer-operation.js.map +0 -1
  263. package/dist/native-skills.d.ts +0 -64
  264. package/dist/native-skills.js +0 -167
  265. package/dist/native-skills.js.map +0 -1
  266. package/dist/node-floor.d.ts +0 -19
  267. package/dist/node-floor.js +0 -32
  268. package/dist/node-floor.js.map +0 -1
  269. package/dist/npm-invocation.d.ts +0 -34
  270. package/dist/npm-invocation.js +0 -53
  271. package/dist/npm-invocation.js.map +0 -1
  272. package/dist/openclaw-continuation-bridge.d.ts +0 -7
  273. package/dist/openclaw-continuation-bridge.js +0 -64
  274. package/dist/openclaw-continuation-bridge.js.map +0 -1
  275. package/dist/openclaw-gateway-readiness.d.ts +0 -1
  276. package/dist/openclaw-gateway-readiness.js +0 -24
  277. package/dist/openclaw-gateway-readiness.js.map +0 -1
  278. package/dist/openclaw-generation.d.ts +0 -21
  279. package/dist/openclaw-generation.js +0 -115
  280. package/dist/openclaw-generation.js.map +0 -1
  281. package/dist/openclaw-message-bridge.d.ts +0 -8
  282. package/dist/openclaw-message-bridge.js +0 -93
  283. package/dist/openclaw-message-bridge.js.map +0 -1
  284. package/dist/openclaw-pending.d.ts +0 -8
  285. package/dist/openclaw-pending.js +0 -47
  286. package/dist/openclaw-pending.js.map +0 -1
  287. package/dist/openclaw-plugin.d.ts +0 -75
  288. package/dist/openclaw-plugin.js +0 -1353
  289. package/dist/openclaw-plugin.js.map +0 -1
  290. package/dist/openclaw-session-access.d.ts +0 -12
  291. package/dist/openclaw-session-access.js +0 -79
  292. package/dist/openclaw-session-access.js.map +0 -1
  293. package/dist/opencode-plugin.d.ts +0 -88
  294. package/dist/opencode-plugin.js +0 -286
  295. package/dist/opencode-plugin.js.map +0 -1
  296. package/dist/orca-session-title.d.ts +0 -11
  297. package/dist/orca-session-title.js +0 -111
  298. package/dist/orca-session-title.js.map +0 -1
  299. package/dist/pack-feedback-slice.d.ts +0 -35
  300. package/dist/pack-feedback-slice.js +0 -126
  301. package/dist/pack-feedback-slice.js.map +0 -1
  302. package/dist/pairing-qr.d.ts +0 -5
  303. package/dist/pairing-qr.js +0 -24
  304. package/dist/pairing-qr.js.map +0 -1
  305. package/dist/pending-pairing.d.ts +0 -38
  306. package/dist/pending-pairing.js +0 -70
  307. package/dist/pending-pairing.js.map +0 -1
  308. package/dist/platform.d.ts +0 -67
  309. package/dist/platform.js +0 -140
  310. package/dist/platform.js.map +0 -1
  311. package/dist/process-identity.d.ts +0 -26
  312. package/dist/process-identity.js +0 -82
  313. package/dist/process-identity.js.map +0 -1
  314. package/dist/program.d.ts +0 -59
  315. package/dist/program.js +0 -691
  316. package/dist/program.js.map +0 -1
  317. package/dist/project-enablement.d.ts +0 -21
  318. package/dist/project-enablement.js +0 -71
  319. package/dist/project-enablement.js.map +0 -1
  320. package/dist/question-settlement-process.d.ts +0 -17
  321. package/dist/question-settlement-process.js +0 -35
  322. package/dist/question-settlement-process.js.map +0 -1
  323. package/dist/question-timing.d.ts +0 -46
  324. package/dist/question-timing.js +0 -48
  325. package/dist/question-timing.js.map +0 -1
  326. package/dist/readiness.d.ts +0 -153
  327. package/dist/readiness.js +0 -149
  328. package/dist/readiness.js.map +0 -1
  329. package/dist/release.d.ts +0 -31
  330. package/dist/release.js +0 -52
  331. package/dist/release.js.map +0 -1
  332. package/dist/send-attempts.d.ts +0 -26
  333. package/dist/send-attempts.js +0 -139
  334. package/dist/send-attempts.js.map +0 -1
  335. package/dist/send.d.ts +0 -115
  336. package/dist/send.js +0 -421
  337. package/dist/send.js.map +0 -1
  338. package/dist/session-activation.d.ts +0 -13
  339. package/dist/session-activation.js +0 -64
  340. package/dist/session-activation.js.map +0 -1
  341. package/dist/session-attendant-probe.d.ts +0 -85
  342. package/dist/session-attendant-probe.js +0 -173
  343. package/dist/session-attendant-probe.js.map +0 -1
  344. package/dist/session-attendant-state.d.ts +0 -70
  345. package/dist/session-attendant-state.js +0 -256
  346. package/dist/session-attendant-state.js.map +0 -1
  347. package/dist/session-attendant.d.ts +0 -143
  348. package/dist/session-attendant.js +0 -520
  349. package/dist/session-attendant.js.map +0 -1
  350. package/dist/session-delivery.d.ts +0 -249
  351. package/dist/session-delivery.js +0 -576
  352. package/dist/session-delivery.js.map +0 -1
  353. package/dist/session-handoff.d.ts +0 -98
  354. package/dist/session-handoff.js +0 -118
  355. package/dist/session-handoff.js.map +0 -1
  356. package/dist/session-input-wakes.d.ts +0 -78
  357. package/dist/session-input-wakes.js +0 -239
  358. package/dist/session-input-wakes.js.map +0 -1
  359. package/dist/session-inputs.d.ts +0 -57
  360. package/dist/session-inputs.js +0 -434
  361. package/dist/session-inputs.js.map +0 -1
  362. package/dist/session-labels.d.ts +0 -56
  363. package/dist/session-labels.js +0 -396
  364. package/dist/session-labels.js.map +0 -1
  365. package/dist/session-message-handoff.d.ts +0 -39
  366. package/dist/session-message-handoff.js +0 -107
  367. package/dist/session-message-handoff.js.map +0 -1
  368. package/dist/setup-destinations.d.ts +0 -23
  369. package/dist/setup-destinations.js +0 -44
  370. package/dist/setup-destinations.js.map +0 -1
  371. package/dist/skill-integrity.d.ts +0 -54
  372. package/dist/skill-integrity.js +0 -164
  373. package/dist/skill-integrity.js.map +0 -1
  374. package/dist/skill-source/manifest.json +0 -40
  375. package/dist/skill-source/notifai/SKILL.md +0 -350
  376. package/dist/skill-source/notifai/references/diagnostics.md +0 -36
  377. package/dist/skill-source/notifai/references/harness-setup.md +0 -384
  378. package/dist/skill-source/notifai/references/native-questions.md +0 -51
  379. package/dist/skill-source/notifai/references/notes-and-edits.md +0 -54
  380. package/dist/skill-source/notifai/references/send-details.md +0 -14
  381. package/dist/skill-source/notifai/references/updates.md +0 -116
  382. package/dist/skill-source/notifai/references/writing-examples.md +0 -56
  383. package/dist/sound-ref.d.ts +0 -23
  384. package/dist/sound-ref.js +0 -28
  385. package/dist/sound-ref.js.map +0 -1
  386. package/dist/ui/banner.d.ts +0 -22
  387. package/dist/ui/banner.js +0 -87
  388. package/dist/ui/banner.js.map +0 -1
  389. package/dist/ui/config-view.d.ts +0 -30
  390. package/dist/ui/config-view.js +0 -108
  391. package/dist/ui/config-view.js.map +0 -1
  392. package/dist/ui/help.d.ts +0 -44
  393. package/dist/ui/help.js +0 -107
  394. package/dist/ui/help.js.map +0 -1
  395. package/dist/ui/theme.d.ts +0 -90
  396. package/dist/ui/theme.js +0 -244
  397. package/dist/ui/theme.js.map +0 -1
  398. package/dist/update-handoff.d.ts +0 -29
  399. package/dist/update-handoff.js +0 -69
  400. package/dist/update-handoff.js.map +0 -1
  401. package/dist/url-policy.d.ts +0 -85
  402. package/dist/url-policy.js +0 -369
  403. package/dist/url-policy.js.map +0 -1
  404. package/dist/version.d.ts +0 -40
  405. package/dist/version.js +0 -132
  406. package/dist/version.js.map +0 -1
  407. package/dist/wake-support.d.ts +0 -40
  408. package/dist/wake-support.js +0 -95
  409. package/dist/wake-support.js.map +0 -1
@@ -1,933 +0,0 @@
1
- /** Fail-open CLI adapter from harness input to hook lifecycle handlers. */
2
- import { agentUpdateNotice } from './agent-update-notice.js';
3
- import { integrationFaultNotice } from './integration-health.js';
4
- import { claudeWakeRoute } from './claude-wake.js';
5
- import { ApiCallError } from './client.js';
6
- import { codexWakeRoute } from './codex-wake.js';
7
- import { deliverCodexToolMessage } from './codex-tool-messages.js';
8
- import { EXIT, SETUP_COMMAND, diagnoseIgnoredOriginOverride, log, makeClient, rejectedPaths, updateCliCommand, } from './commands-core.js';
9
- import { claudeSessionPid } from './commands-harness-context.js';
10
- import { attendHook, recordCodexTurnStart, reportCodexSessionEnded } from './commands-hook-attend.js';
11
- import { waitForReply } from './commands-send-support.js';
12
- import { loadConfig } from './config.js';
13
- import { withFileLock } from './file-lock.js';
14
- import { questionRoutingCapability } from './harnesses.js';
15
- import { HOOK_EVENTS } from './hook-events.js';
16
- import { handleSessionEnd, handleStop, handleUserPromptSubmit, parseHookInput, submitSessionQuestions, } from './hook-lifecycle.js';
17
- import { claimCursorStopActivation, confirmCursorStopActivation, lifecycleStamp, pruneAbandonedSessions, readSessionIncarnation, readSessionState, updateSessionState, recordSessionStart, resetCursorStopActivation, sessionHasEnded, sessionStatePath, } from './hook-session-state.js';
18
- import {} from './hook-types.js';
19
- import { codexStopDefinitionFingerprint, findInstallations } from './install-hooks.js';
20
- import { logConfigResolved, logSettingsFrom } from './logging.js';
21
- import { observeOpenclawPrompt, observeOpenclawStart, openclawGenerationLockPath, readOpenclawGeneration, writeOpenclawGeneration, } from './openclaw-generation.js';
22
- import { projectBinding, projectEnabled } from './project-enablement.js';
23
- import { spawnQuestionSettlement } from './question-settlement-process.js';
24
- import { QUESTION_WAITER_CEILING_SECONDS } from './question-timing.js';
25
- import { cursorStopActivationOutput, sessionActivationOutput, userPromptContextOutput } from './session-activation.js';
26
- import { currentProcessIdentity } from './process-identity.js';
27
- import { attendantSupport } from './session-attendant-probe.js';
28
- import { readAttendantEndingLease, readAttendantLease } from './session-attendant-state.js';
29
- import { openclawContinuationRoute } from './openclaw-continuation-bridge.js';
30
- import { listPendingOpenclawSessions } from './openclaw-pending.js';
31
- import { readDeliveryJournal } from './session-delivery.js';
32
- import { sessionInputRoute, stageSessionAnswers, observeSessionInputWake, sessionInputWake } from './session-inputs.js';
33
- import { receiveSessionInputs } from './commands-receive.js';
34
- const INTERNAL_HOOK_EVENTS = [
35
- 'question-submission', 'question-settlement', 'openclaw-lifecycle', 'openclaw-generation',
36
- 'openclaw-turn-start', 'openclaw-turn-end', 'openclaw-list-pending',
37
- 'openclaw-attendance-ready', 'openclaw-settlement',
38
- 'openclaw-verify-prepared',
39
- ];
40
- /** Keep diagnostic context in the same document as an answer or Session Note. */
41
- function appendIntegrationContext(output, notice, harness, event) {
42
- if (notice === undefined)
43
- return output ?? '';
44
- if (harness === 'opencode' || harness === 'openclaw')
45
- return [output, notice].filter(Boolean).join('\n\n');
46
- if (output === undefined) {
47
- return event === 'UserPromptSubmit' ? userPromptContextOutput(harness, notice) ?? ''
48
- : JSON.stringify({ hookSpecificOutput: { hookEventName: event, additionalContext: notice } });
49
- }
50
- try {
51
- const value = JSON.parse(output);
52
- if (harness === 'cursor')
53
- value.additional_context = [value.additional_context, notice].filter(Boolean).join('\n\n');
54
- else {
55
- value.hookSpecificOutput ??= { hookEventName: event };
56
- value.hookSpecificOutput.additionalContext = [value.hookSpecificOutput.additionalContext, notice].filter(Boolean).join('\n\n');
57
- }
58
- return JSON.stringify(value);
59
- }
60
- catch {
61
- return output;
62
- }
63
- }
64
- /** SessionEnd cleanup must precede every diagnostic that can wait on a file lock. */
65
- export function hookDefersDiagnosticsUntilAfterCleanup(event) {
66
- return event === 'session-end';
67
- }
68
- /**
69
- * Runs one harness hook. Contract with every harness: hook JSON arrives on
70
- * stdin, harness output (if any) goes to stdout, diagnostics go to stderr, and
71
- * exit 0 with no stdout means "no decision or added context — carry on as normal".
72
- *
73
- * Every failure path in here must reach that no-decision state. A hook that
74
- * throws, or that blocks past the harness's timeout, degrades the agent for a
75
- * feature the user only asked to make it more convenient.
76
- */
77
- export async function hookRunCommand(deps, event, readStdin, harness) {
78
- if (!HOOK_EVENTS.includes(event) &&
79
- !INTERNAL_HOOK_EVENTS.includes(event)) {
80
- deps.io.err(`Unknown hook event "${event}". Valid: ${HOOK_EVENTS.join(', ')}`);
81
- return EXIT.usage;
82
- }
83
- // One clock owns the complete Stop invocation, including stdin, config,
84
- // credentials, and client construction. Starting this inside `handleStop`
85
- // would grant slow setup a second budget and let the harness kill us before
86
- // an accepted answer is journaled or written to stdout.
87
- const now = deps.now ?? Date.now;
88
- // Taken before stdin is read: an end the harness recorded before this
89
- // invocation began belongs to an earlier incarnation of the session.
90
- const invokedAt = lifecycleStamp(now());
91
- // One owner lifetime covers startup and the longest answer window. Native
92
- // observers run detached; held Stop routes keep stdout. The delivery mechanism does not
93
- // change how long the exact Agent Session remains reachable.
94
- const processDeadlineAt = now() + QUESTION_WAITER_CEILING_SECONDS * 1000;
95
- const logger = log(deps);
96
- logger.bind({ cmd: `hook ${event}` });
97
- let started = false;
98
- const start = (data = {}) => {
99
- if (started)
100
- return;
101
- started = true;
102
- logger.info('hook.start', { hook: event, harness: harness ?? 'unknown', ...data });
103
- };
104
- const failureData = (err) => err instanceof ApiCallError
105
- ? { status: err.status, code: err.code, message: err.message, details: err.details }
106
- : { message: err instanceof Error ? err.message : String(err) };
107
- // Cursor may load ~/.claude/settings.json in addition to its own native
108
- // hooks. Cursor guarantees CURSOR_PROJECT_DIR to hook processes, so a Claude
109
- // compatibility copy becomes a no-op and the native Cursor definition is
110
- // the single owner. Real Claude hooks do not receive that hook-only marker.
111
- if (harness === 'claude-code' && deps.env['CURSOR_PROJECT_DIR']) {
112
- start({ outcome: 'cursor-compatibility-copy-skipped' });
113
- logger.info('hook.end', {
114
- hook: event,
115
- outcome: 'ignored',
116
- reason: 'cursor-native-handler-owns-event',
117
- decided: false,
118
- });
119
- return EXIT.ok;
120
- }
121
- // Grok loads Claude settings by default. Its native Notifai definition is
122
- // the sole owner; the inherited Claude copy must stop before reading stdin.
123
- if (harness === 'claude-code' && (deps.env['GROK_HOOK_EVENT'] ?? '') !== '') {
124
- start({ outcome: 'grok-compatibility-copy-skipped' });
125
- logger.info('hook.end', {
126
- hook: event,
127
- outcome: 'ignored',
128
- reason: 'grok-native-handler-owns-event',
129
- decided: false,
130
- });
131
- return EXIT.ok;
132
- }
133
- let raw;
134
- try {
135
- raw = await readStdin();
136
- }
137
- catch (err) {
138
- start({ input: 'unavailable' });
139
- logger.error('hook.end', {
140
- hook: event,
141
- outcome: 'ignored',
142
- reason: 'input-read-failed',
143
- ...failureData(err),
144
- });
145
- return EXIT.ok;
146
- }
147
- if (raw.trim() !== '') {
148
- try {
149
- const parsed = JSON.parse(raw);
150
- if (typeof parsed !== 'object' || parsed === null)
151
- throw new Error('not an object');
152
- }
153
- catch {
154
- start({ input: 'malformed' });
155
- logger.error('hook.end', { hook: event, outcome: 'ignored', reason: 'malformed-input' });
156
- deps.io.err('notifai: ignored malformed or truncated hook input; no routing action was taken');
157
- return EXIT.ok;
158
- }
159
- }
160
- let envelope = parseHookInput(raw);
161
- // Codex subagents share the root session_id and harness process, while
162
- // turn_id belongs to the child. They must never own the root's lifecycle
163
- // or consume its input. SubagentStart only teaches worker ownership and
164
- // deliberately does not activate or update the parent session.
165
- if (harness === 'codex' && event !== 'subagent-start' &&
166
- (envelope.agent_id !== undefined || envelope.agent_type !== undefined)) {
167
- start({ outcome: 'codex-child-callback-skipped' });
168
- logger.info('hook.end', {
169
- hook: event, outcome: 'ignored', reason: 'codex-subagent-owns-event', decided: false,
170
- });
171
- return EXIT.ok;
172
- }
173
- if (harness === 'cursor') {
174
- const sessionId = envelope.session_id ?? envelope.conversation_id;
175
- const cwd = envelope.cwd ?? envelope.workspace_roots?.[0];
176
- envelope = {
177
- ...envelope,
178
- ...(sessionId === undefined ? {} : { session_id: sessionId }),
179
- ...(cwd === undefined ? {} : { cwd }),
180
- stop_hook_active: envelope.stop_hook_active ??
181
- (typeof envelope.loop_count === 'number' && envelope.loop_count > 0),
182
- };
183
- }
184
- if (harness === 'grok') {
185
- envelope = {
186
- ...envelope,
187
- ...(envelope.stop_hook_active === undefined && envelope.stopHookActive !== undefined
188
- ? { stop_hook_active: envelope.stopHookActive }
189
- : {}),
190
- };
191
- }
192
- const cwd = envelope.cwd ?? deps.cwd;
193
- if (event === 'openclaw-lifecycle') {
194
- logger.bind({ session: envelope.session_id ?? null });
195
- start({ cwd, event: envelope.hook_event_name ?? null });
196
- if (harness !== 'openclaw' || envelope.session_id === undefined)
197
- return EXIT.ok;
198
- try {
199
- const sessionKey = envelope.session_id;
200
- const outcome = withFileLock(openclawGenerationLockPath(sessionKey, deps.env), () => {
201
- const current = readOpenclawGeneration(sessionKey, deps.env);
202
- if (envelope.hook_event_name === 'SessionStart') {
203
- const next = observeOpenclawStart(current, envelope.openclaw_session_id, envelope.openclaw_resumed_from);
204
- if (envelope.openclaw_session_id !== undefined &&
205
- next.sessionId !== envelope.openclaw_session_id)
206
- return 'stale-start-ignored';
207
- writeOpenclawGeneration(sessionKey, deps.env, next);
208
- return 'start-observed';
209
- }
210
- if (envelope.hook_event_name === 'BeforeReset' && current !== null && !current.ended) {
211
- // OpenClaw dispatches before_reset after committing its new entry and
212
- // does not await the hook. A late old reset must not end a generation
213
- // already observed from the replacement entry's native revision.
214
- if (current.lifecycleRevision !== undefined &&
215
- (typeof envelope.openclaw_lifecycle_revision !== 'string' ||
216
- envelope.openclaw_lifecycle_revision === current.lifecycleRevision ||
217
- current.supersededLifecycleRevisions?.includes(envelope.openclaw_lifecycle_revision))) {
218
- return 'stale-reset-ignored';
219
- }
220
- if (envelope.openclaw_session_id !== undefined && current.sessionId !== undefined &&
221
- envelope.openclaw_session_id !== current.sessionId)
222
- return 'stale-reset-ignored';
223
- if (current.activated)
224
- handleSessionEnd(deps.env, envelope, now());
225
- writeOpenclawGeneration(sessionKey, deps.env, {
226
- ...current, ended: true, resetPending: true,
227
- });
228
- return 'reset-observed';
229
- }
230
- return 'ignored';
231
- });
232
- logger.info('hook.end', { hook: event, outcome, decided: false });
233
- }
234
- catch (err) {
235
- logger.error('hook.end', { hook: event, outcome: 'failed', ...failureData(err) });
236
- }
237
- return EXIT.ok;
238
- }
239
- if (event === 'attend') {
240
- logger.bind({ session: envelope.session_id ?? null });
241
- start({ cwd, event: envelope.hook_event_name ?? null, source: envelope.source ?? null });
242
- try {
243
- return await attendHook(deps, { envelope, harness, cwd, invokedAt, logger });
244
- }
245
- catch (err) {
246
- // A resident process that throws must still hand the harness exit 0.
247
- logger.error('hook.end', { hook: event, outcome: 'failed', ...failureData(err) });
248
- return EXIT.ok;
249
- }
250
- }
251
- const launchSettlement = () => {
252
- const sessionId = envelope.session_id;
253
- if (sessionId === undefined || harness === undefined)
254
- return {};
255
- try {
256
- const launched = withFileLock(`${sessionStatePath(sessionId, deps.env)}.lock`, () => {
257
- if (sessionHasEnded(sessionId, deps.env))
258
- return false;
259
- const current = readSessionState(sessionId, deps.env);
260
- if ((current.pending?.length ?? 0) === 0 && current.accepted === undefined)
261
- return false;
262
- const spawnSettlement = deps.spawnQuestionSettlement ?? spawnQuestionSettlement;
263
- spawnSettlement({
264
- envelope: { session_id: sessionId, cwd }, harness,
265
- });
266
- return true;
267
- });
268
- return { settlement: launched ? 'launched' : 'cancelled' };
269
- }
270
- catch (err) {
271
- return { settlement: 'launch-failed', settlement_error: failureData(err) };
272
- }
273
- };
274
- const sessionEnd = hookDefersDiagnosticsUntilAfterCleanup(event);
275
- // Codex may kill the attendant before this hook starts. Capture its saved
276
- // fencing identity before cleanup or another start can replace local state.
277
- const codexEndingLease = sessionEnd && harness === 'codex' && envelope.session_id !== undefined
278
- ? readAttendantEndingLease(envelope.session_id, deps.env)
279
- : null;
280
- logger.bind({ session: envelope.session_id ?? null });
281
- const lifecycleEnabled = () => {
282
- try {
283
- const activationConfig = loadConfig({ cwd, env: deps.env, sessionId: envelope.session_id });
284
- logger.bind({ project: activationConfig.project.value });
285
- return projectEnabled(projectBinding(cwd, deps.env, activationConfig.project.value));
286
- }
287
- catch (err) {
288
- logger.error('hook.end', { hook: event, outcome: 'enablement-unavailable', ...failureData(err) });
289
- return false;
290
- }
291
- };
292
- const currentOpenclawOwner = () => {
293
- if (harness !== 'openclaw' || envelope.session_id === undefined || !lifecycleEnabled())
294
- return null;
295
- const current = readOpenclawGeneration(envelope.session_id, deps.env);
296
- const incarnation = readSessionIncarnation(envelope.session_id, deps.env);
297
- return current !== null && current.activated && !current.ended &&
298
- incarnation?.openclaw_generation === current.id &&
299
- !sessionHasEnded(envelope.session_id, deps.env) &&
300
- (envelope.openclaw_lifecycle_revision === undefined ||
301
- current.lifecycleRevision === envelope.openclaw_lifecycle_revision) &&
302
- (envelope.openclaw_session_id === undefined || current.sessionId === envelope.openclaw_session_id)
303
- ? current.id : null;
304
- };
305
- if (event === 'post-tool-use') {
306
- start({ cwd });
307
- try {
308
- if (harness === 'codex' && lifecycleEnabled()) {
309
- const notice = integrationFaultNotice({ ...deps, cwd }, harness);
310
- let wrote = false;
311
- const io = { ...deps.io, out: (line) => {
312
- wrote = true;
313
- deps.io.out(appendIntegrationContext(line, notice, harness, 'PostToolUse'));
314
- } };
315
- await deliverCodexToolMessage({ ...deps, io }, envelope, logger);
316
- if (!wrote && notice !== undefined)
317
- deps.io.out(appendIntegrationContext(undefined, notice, harness, 'PostToolUse'));
318
- }
319
- logger.info('hook.end', { hook: event, outcome: 'checked', decided: false });
320
- }
321
- catch (err) {
322
- logger.error('hook.end', { hook: event, outcome: 'ignored', ...failureData(err) });
323
- }
324
- return EXIT.ok;
325
- }
326
- if (event === 'openclaw-generation') {
327
- start({ cwd });
328
- const generation = currentOpenclawOwner();
329
- if (generation !== null)
330
- deps.io.out(generation);
331
- return EXIT.ok;
332
- }
333
- if (event === 'openclaw-turn-start') {
334
- start({ cwd });
335
- const generation = currentOpenclawOwner();
336
- if (generation !== null && envelope.session_id !== undefined) {
337
- updateSessionState(envelope.session_id, deps.env, (state) => ({
338
- ...state, last_prompt_at: now(),
339
- }));
340
- }
341
- return EXIT.ok;
342
- }
343
- if (event === 'openclaw-turn-end') {
344
- start({ cwd });
345
- const generation = currentOpenclawOwner();
346
- if (generation !== null && envelope.session_id !== undefined) {
347
- updateSessionState(envelope.session_id, deps.env, (state) => ({
348
- ...state, last_stop_at: now(),
349
- }));
350
- }
351
- return EXIT.ok;
352
- }
353
- if (event === 'openclaw-list-pending') {
354
- start({ cwd });
355
- if (harness === 'openclaw')
356
- deps.io.out(JSON.stringify(listPendingOpenclawSessions(deps.env)));
357
- return EXIT.ok;
358
- }
359
- if (event === 'openclaw-attendance-ready') {
360
- start({ cwd });
361
- if (currentOpenclawOwner() !== null && envelope.session_id !== undefined &&
362
- readAttendantLease(envelope.session_id, deps.env) !== null &&
363
- (() => {
364
- try {
365
- const config = loadConfig({ cwd, env: deps.env, sessionId: envelope.session_id });
366
- return projectEnabled(projectBinding(cwd, deps.env, config.project.value));
367
- }
368
- catch {
369
- return false;
370
- }
371
- })())
372
- deps.io.out('ready');
373
- return EXIT.ok;
374
- }
375
- if (event === 'openclaw-verify-prepared') {
376
- start({ cwd });
377
- const ids = envelope.openclaw_request_ids;
378
- if (currentOpenclawOwner() !== null && envelope.session_id !== undefined &&
379
- Array.isArray(ids) && ids.length > 0 &&
380
- ids.every((id) => typeof id === 'string' && /^req_[A-Za-z0-9_-]+$/.test(id))) {
381
- const entries = readDeliveryJournal(envelope.session_id, deps.env);
382
- if (ids.every((id) => entries.some((entry) => entry.subject.type === 'answer' && entry.subject.request_id === id &&
383
- (entry.stage === 'writing' || entry.stage === 'written' || entry.stage === 'failed')))) {
384
- deps.io.out('committed');
385
- }
386
- }
387
- return EXIT.ok;
388
- }
389
- if (event === 'openclaw-settlement' && currentOpenclawOwner() === null)
390
- return EXIT.ok;
391
- // Installation only makes lifecycle hooks available. Model-visible
392
- // activation is a separate User-owned Project decision, checked anew on
393
- // every run so disabling takes effect without reinstalling anything.
394
- if (event === 'session-start' || event === 'subagent-start') {
395
- start({ cwd, source: envelope.source ?? null });
396
- if (!lifecycleEnabled()) {
397
- logger.info('hook.end', {
398
- hook: event,
399
- outcome: 'ignored',
400
- reason: 'project-disabled',
401
- decided: false,
402
- });
403
- return EXIT.ok;
404
- }
405
- if (event === 'session-start' && harness === 'cursor' && envelope.session_id !== undefined) {
406
- try {
407
- resetCursorStopActivation(envelope.session_id, deps.env);
408
- }
409
- catch (err) {
410
- logger.error('hook.end', {
411
- hook: event,
412
- outcome: 'reset-failed',
413
- ...failureData(err),
414
- });
415
- }
416
- }
417
- const updateNotice = event === 'session-start' &&
418
- ['claude-code', 'codex', 'opencode', 'openclaw'].includes(harness ?? '')
419
- ? await agentUpdateNotice({ env: deps.env, now: now(), updateCommand: updateCliCommand(deps),
420
- ...(deps.fetchImpl === undefined ? {} : { fetchImpl: deps.fetchImpl }) })
421
- : undefined;
422
- const faultNotice = event === 'session-start' && harness !== undefined && harness !== 'grok'
423
- ? integrationFaultNotice({ ...deps, cwd }, harness) : undefined;
424
- const notice = [updateNotice, faultNotice].filter(part => part !== undefined).join('\n\n') || undefined;
425
- if (harness === 'openclaw' && envelope.session_id !== undefined) {
426
- try {
427
- const sessionKey = envelope.session_id;
428
- const outcome = withFileLock(openclawGenerationLockPath(sessionKey, deps.env), () => {
429
- const current = readOpenclawGeneration(sessionKey, deps.env);
430
- if (envelope.openclaw_lifecycle_revision !== undefined &&
431
- current?.supersededLifecycleRevisions?.includes(envelope.openclaw_lifecycle_revision)) {
432
- return 'stale-prompt-ignored';
433
- }
434
- const generation = observeOpenclawPrompt(current, envelope.openclaw_session_id, envelope.openclaw_lifecycle_revision);
435
- if (envelope.openclaw_session_id !== undefined &&
436
- generation.sessionId !== envelope.openclaw_session_id)
437
- return 'stale-prompt-ignored';
438
- writeOpenclawGeneration(sessionKey, deps.env, generation);
439
- if (generation.activated)
440
- return 'already-activated';
441
- const previous = readSessionIncarnation(sessionKey, deps.env);
442
- if (previous !== null && previous.openclaw_generation !== generation.id) {
443
- // A new sessionId or resumed generation is authoritative even if
444
- // its old session_end was dropped or delayed.
445
- handleSessionEnd(deps.env, envelope, now());
446
- }
447
- const output = sessionActivationOutput(harness, event === 'session-start' ? 'SessionStart' : 'SubagentStart', cwd, deps.env, notice);
448
- if (output === undefined || output.trim().length === 0)
449
- return 'activation-unavailable';
450
- recordSessionStart(sessionKey, deps.env, harness, cwd, undefined, lifecycleStamp(now()), generation.id);
451
- writeOpenclawGeneration(sessionKey, deps.env, { ...generation, activated: true });
452
- deps.io.out(output);
453
- return 'context-added';
454
- });
455
- logger.info('hook.end', { hook: event, outcome, decided: false });
456
- }
457
- catch (err) {
458
- logger.error('hook.end', {
459
- hook: event, outcome: 'record-failed', reason: 'session-state-failed', ...failureData(err),
460
- });
461
- }
462
- return EXIT.ok;
463
- }
464
- const stdout = sessionActivationOutput(harness, event === 'session-start' ? 'SessionStart' : 'SubagentStart', cwd, deps.env, notice);
465
- if (stdout !== undefined)
466
- deps.io.out(stdout);
467
- let settlementRecovery = {};
468
- if (event === 'session-start' && envelope.session_id !== undefined) {
469
- try {
470
- const stopFingerprint = harness === 'codex'
471
- ? codexStopDefinitionFingerprint(findInstallations(deps.env, deps.hookAdapterHome, deps.hookPlatform))
472
- : undefined;
473
- recordSessionStart(envelope.session_id, deps.env, harness, cwd, stopFingerprint, invokedAt);
474
- if (harness === 'codex') {
475
- // Pending state is the durable handoff debt if the prior process
476
- // died after queue commit but before starting its successor.
477
- settlementRecovery = launchSettlement();
478
- }
479
- }
480
- catch (err) {
481
- logger.error('hook.end', {
482
- hook: event,
483
- outcome: 'record-failed',
484
- reason: 'session-state-failed',
485
- ...failureData(err),
486
- });
487
- }
488
- }
489
- logger.info('hook.end', {
490
- hook: event,
491
- outcome: stdout === undefined
492
- ? harness === 'grok' ? 'activation-context-unsupported' : 'unsupported-harness'
493
- : 'context-added',
494
- decided: false,
495
- ...settlementRecovery,
496
- });
497
- return EXIT.ok;
498
- }
499
- // A Codex turn starts here. This prompt hook is synchronous, so Codex runs it
500
- // before the turn and one turn at a time: the Session Attendant reads the
501
- // thread's activity from starts recorded in that order.
502
- if (event === 'user-prompt-submit' && harness === 'codex')
503
- recordCodexTurnStart(envelope, deps.env);
504
- // Cursor has a confirmed host bug in which sessionStart.additional_context
505
- // is accepted but never reaches the model. A native Stop follow-up is the
506
- // host's guaranteed model-visible channel. Claim it once per conversation,
507
- // before config/auth/network, and leave the ordinary Stop handler separate
508
- // so question delivery is never displaced by activation.
509
- if (event === 'activation-stop') {
510
- start({ cwd, stop_hook_active: envelope.stop_hook_active ?? null });
511
- if (harness !== 'cursor') {
512
- logger.info('hook.end', { hook: event, outcome: 'unsupported-harness', decided: false });
513
- return EXIT.ok;
514
- }
515
- if (!lifecycleEnabled()) {
516
- logger.info('hook.end', {
517
- hook: event,
518
- outcome: 'ignored',
519
- reason: 'project-disabled',
520
- decided: false,
521
- });
522
- return EXIT.ok;
523
- }
524
- if (envelope.status === 'aborted') {
525
- logger.info('hook.end', {
526
- hook: event,
527
- outcome: 'ignored',
528
- reason: `turn-${envelope.status}`,
529
- decided: false,
530
- });
531
- return EXIT.ok;
532
- }
533
- if (envelope.session_id === undefined) {
534
- logger.info('hook.end', {
535
- hook: event,
536
- outcome: 'ignored',
537
- reason: 'missing-conversation-id',
538
- decided: false,
539
- });
540
- return EXIT.ok;
541
- }
542
- if (envelope.session_id !== undefined &&
543
- typeof envelope.loop_count === 'number' &&
544
- envelope.loop_count > 0) {
545
- let activationOwned = false;
546
- try {
547
- activationOwned = confirmCursorStopActivation(envelope.session_id, deps.env, now());
548
- }
549
- catch (err) {
550
- logger.error('hook.end', {
551
- hook: event,
552
- outcome: 'confirm-failed',
553
- ...failureData(err),
554
- });
555
- }
556
- if (activationOwned) {
557
- logger.info('hook.end', { hook: event, outcome: 'confirmed', decided: false });
558
- return EXIT.ok;
559
- }
560
- }
561
- const cursorState = readSessionState(envelope.session_id, deps.env);
562
- if ((cursorState.pending?.length ?? 0) > 0 ||
563
- cursorState.accepted !== undefined ||
564
- (cursorState.acknowledgement_due?.length ?? 0) > 0) {
565
- logger.info('hook.end', {
566
- hook: event,
567
- outcome: 'deferred',
568
- reason: 'question-continuation-owns-stop',
569
- decided: false,
570
- });
571
- return EXIT.ok;
572
- }
573
- let claimed = false;
574
- try {
575
- claimed = claimCursorStopActivation(envelope.session_id, deps.env, now());
576
- }
577
- catch (err) {
578
- logger.error('hook.end', {
579
- hook: event,
580
- outcome: 'claim-failed',
581
- ...failureData(err),
582
- });
583
- }
584
- logger.info('hook.end', {
585
- hook: event,
586
- outcome: claimed ? 'followup-added' : 'already-activated',
587
- decided: claimed,
588
- });
589
- if (claimed) {
590
- const updateNotice = await agentUpdateNotice({ env: deps.env, now: now(), updateCommand: updateCliCommand(deps),
591
- ...(deps.fetchImpl === undefined ? {} : { fetchImpl: deps.fetchImpl }) });
592
- const notice = [updateNotice, integrationFaultNotice({ ...deps, cwd }, 'cursor')].filter(Boolean).join('\n\n') || undefined;
593
- deps.io.out(cursorStopActivationOutput(cwd, deps.env, notice));
594
- }
595
- return EXIT.ok;
596
- }
597
- let config = null;
598
- let configFailure;
599
- try {
600
- config = loadConfig({ cwd, env: deps.env, sessionId: envelope.session_id });
601
- // The hook's project is the session's, not this process's, and the log
602
- // settings that apply are that project's too. Keeping a mutable bootstrap
603
- // logger lets this more-specific layer turn logging back on.
604
- logger.adopt(logSettingsFrom(config));
605
- logger.bind({ project: config.project.value });
606
- if (!sessionEnd) {
607
- start({ cwd, stop_hook_active: envelope.stop_hook_active ?? null });
608
- logConfigResolved(logger, config);
609
- }
610
- }
611
- catch (err) {
612
- configFailure = err;
613
- if (!sessionEnd) {
614
- start({ cwd, stop_hook_active: envelope.stop_hook_active ?? null });
615
- logger.error('hook.end', {
616
- hook: event,
617
- outcome: 'failed',
618
- reason: 'config-failed',
619
- ...failureData(err),
620
- });
621
- for (const line of describeHookFailure(err))
622
- deps.io.err(`notifai: ${line}`);
623
- return EXIT.ok;
624
- }
625
- }
626
- // Everything below is inside one fail-open boundary. Credential loading,
627
- // client construction and hook handling can all throw, and a hook that exits
628
- // non-zero makes the harness report a failure — strictly worse than skipping.
629
- try {
630
- if (sessionEnd) {
631
- // Codex gives SessionEnd one second total. Do every durable cleanup write
632
- // before lifecycle diagnostics: the log lock is deliberately allowed to
633
- // wait that long, and a busy log must never preserve ended-session state
634
- // or its inherited configuration. The resolved config above is retained
635
- // in memory so logging still uses the ending session's settings afterwards.
636
- const outcome = harness === 'openclaw' && envelope.session_id !== undefined
637
- ? withFileLock(openclawGenerationLockPath(envelope.session_id, deps.env), () => {
638
- const sessionKey = envelope.session_id;
639
- const current = readOpenclawGeneration(sessionKey, deps.env);
640
- const reason = envelope.openclaw_reason;
641
- if (reason === 'new' || reason === 'reset') {
642
- // These can arrive after a same-ID replacement. before_reset
643
- // owns cleanup; an end event alone cannot identify the old ID.
644
- return { notes: [], log: { outcome: 'ignored', reason: 'reset-end' } };
645
- }
646
- if (current === null || current.ended ||
647
- (envelope.openclaw_session_id !== undefined && current.sessionId !== undefined &&
648
- envelope.openclaw_session_id !== current.sessionId)) {
649
- return { notes: [], log: { outcome: 'ignored', reason: 'stale-openclaw-generation' } };
650
- }
651
- const ended = current.activated
652
- ? handleSessionEnd(deps.env, envelope, now())
653
- : { notes: [], log: { outcome: 'ignored', reason: 'inactive-openclaw-generation' } };
654
- writeOpenclawGeneration(sessionKey, deps.env, { ...current, ended: true });
655
- return ended;
656
- })
657
- : handleSessionEnd(deps.env, envelope, now());
658
- start({ cwd, stop_hook_active: envelope.stop_hook_active ?? null });
659
- if (config !== null)
660
- logConfigResolved(logger, config);
661
- const data = { hook: event, decided: false, ...outcome.log };
662
- if (configFailure === undefined)
663
- logger.info('hook.end', data);
664
- else {
665
- logger.error('hook.end', {
666
- ...data,
667
- reason: 'config-failed',
668
- config_error: failureData(configFailure),
669
- });
670
- }
671
- for (const note of outcome.notes)
672
- deps.io.err(`notifai: ${note}`);
673
- if (harness === 'codex' && envelope.session_id !== undefined) {
674
- // Codex kills the attendant as soon as this hook returns.
675
- const reported = await reportCodexSessionEnded(deps, envelope.session_id, codexEndingLease);
676
- logger.info('attendant.lease', { event: 'ended-by-session-end', outcome: reported });
677
- }
678
- return EXIT.ok;
679
- }
680
- // Non-SessionEnd hooks cannot reach here without resolved configuration.
681
- const resolved = config;
682
- const credential = deps.store.load();
683
- if (!credential) {
684
- logger.error('hook.end', { hook: event, outcome: 'not-paired' });
685
- deps.io.err(`notifai: hook skipped: this machine is not paired; run \`${SETUP_COMMAND}\``);
686
- return EXIT.ok;
687
- }
688
- // Pin authenticated traffic to the origin the credential was issued for. A
689
- // repository can commit `.notifai/config.toml`, and honouring a base_url
690
- // from it would hand this machine's bearer token to whatever host it names.
691
- const baseUrl = credential.baseUrl;
692
- diagnoseIgnoredOriginOverride(deps.io, resolved, credential);
693
- // UserPromptSubmit runs in front of the user's own prompt under a 15s
694
- // harness ceiling and can make two calls, so each gets a small slice of it;
695
- // Stop is allowed to block and keeps the ordinary budget.
696
- const client = makeClient(deps, baseUrl, `Bearer nfm_${credential.machineId}.${credential.secret}`, {
697
- timeoutMs: event === 'user-prompt-submit' ? 4_000 : 20_000,
698
- ...(event === 'stop' || event === 'question-settlement'
699
- ? { deadlineAt: processDeadlineAt, now }
700
- : {}),
701
- });
702
- const ctx = {
703
- client,
704
- service_identity: { base_url: credential.baseUrl, machine_id: credential.machineId },
705
- config: resolved,
706
- env: deps.env,
707
- now,
708
- sleep: deps.sleep ?? ((ms) => new Promise((resolve) => setTimeout(resolve, ms))),
709
- waitForFirstReply: async (requestId, timeoutSeconds) => {
710
- const result = await waitForReply(client, requestId, {
711
- timeoutSeconds,
712
- afterSeq: 0,
713
- now: deps.now,
714
- sleep: deps.sleep,
715
- });
716
- return {
717
- replies: result.response.replies,
718
- timedOut: result.timedOut,
719
- degraded: result.degraded,
720
- };
721
- },
722
- log: logger,
723
- ...(harness === undefined ? {} : { harness }),
724
- ...answerClaimsFor(deps, harness, envelope.session_id, event),
725
- };
726
- if (event === 'question-submission') {
727
- const notes = [];
728
- const sessionId = envelope.session_id;
729
- if (sessionId === undefined || harness === undefined ||
730
- readSessionState(sessionId, deps.env).harness !== harness || !lifecycleEnabled())
731
- return EXIT.ok;
732
- await submitSessionQuestions(ctx, envelope, processDeadlineAt, notes);
733
- // Native queues can observe answers while the asking turn keeps working.
734
- // Held Stop and plugin routes retain their own genuine output owner.
735
- const nativeObserver = harness === 'codex' ||
736
- (harness === 'claude-code' && (deps.hookPlatform ?? process.platform) !== 'win32');
737
- logger.info('hook.end', { hook: event, outcome: 'submission-checked', notes,
738
- ...(nativeObserver ? launchSettlement() : {}) });
739
- return EXIT.ok;
740
- }
741
- // Real clock, deliberately, not `deps.now`. This compares against file
742
- // mtimes, which are wall-clock facts — handing it a virtual or skewed clock
743
- // would have it delete live session state as "abandoned".
744
- // Daily state pruning is housekeeping, not part of the Stop delivery
745
- // contract. Its directory scan has no useful bound, so keep it on the
746
- // short prompt path and never spend the answer owner's finite budget on it.
747
- if (event !== 'stop' && event !== 'question-settlement' && event !== 'openclaw-settlement') {
748
- pruneAbandonedSessions(deps.env);
749
- }
750
- let outcome;
751
- if (event === 'user-prompt-submit') {
752
- if (envelope.session_id !== undefined)
753
- observeSessionInputWake(envelope.session_id, deps.env, envelope.prompt);
754
- const notice = lifecycleEnabled() && harness !== undefined && harness !== 'grok'
755
- ? integrationFaultNotice({ ...deps, cwd }, harness) : undefined;
756
- outcome = envelope.session_id !== undefined && envelope.prompt === sessionInputWake()
757
- ? { notes: [], log: { stage: 'input-wake-observed' } }
758
- : await handleUserPromptSubmit(ctx, envelope);
759
- if (notice !== undefined)
760
- outcome.stdout = appendIntegrationContext(outcome.stdout, notice, harness, 'UserPromptSubmit');
761
- }
762
- else {
763
- outcome = await handleStop(ctx, envelope, processDeadlineAt, event === 'openclaw-settlement' && envelope.session_id !== undefined
764
- ? openclawContinuationRoute(envelope.session_id, currentOpenclawOwner())
765
- : stopWakeRoute(deps, harness, envelope.session_id, cwd, event !== 'stop'),
766
- // The Gateway settlement process is not an agent turn boundary. Only
767
- // agent_end records Stop; a background poll must not spend the three
768
- // acknowledgement reminders or abandon a Session Message debt.
769
- event === 'stop');
770
- }
771
- if (outcome.settlementRequired === true && harness !== undefined && harness !== 'openclaw' &&
772
- questionRoutingCapability(harness, deps.hookPlatform ?? process.platform)
773
- .stopContinuation !== 'unsupported') {
774
- // handleStop released its question-owner lease before returning here.
775
- outcome.log = { ...outcome.log, ...launchSettlement() };
776
- }
777
- // Answer diagnostics are already persisted once as hook.answer. Keep every
778
- // other note in the lifecycle record without duplicating the user's text.
779
- const notes = outcome.notes.filter((note) => !/^(?:late )?answer from /.test(note));
780
- logger.info('hook.end', {
781
- hook: event,
782
- decided: outcome.decided ?? outcome.stdout !== undefined,
783
- ...(notes.length === 0 ? {} : { notes }),
784
- ...outcome.log,
785
- });
786
- for (const note of outcome.notes)
787
- deps.io.err(`notifai: ${note}`);
788
- let inputWritten = false;
789
- if (event === 'user-prompt-submit' && (harness === 'codex' || harness === 'claude-code') &&
790
- envelope.session_id !== undefined && outcome.commitStdout === undefined &&
791
- readSessionIncarnation(envelope.session_id, deps.env)?.harness_process?.pid === declaredHookSourcePid(deps)) {
792
- inputWritten = await receiveSessionInputs(deps, envelope.session_id, (text) => {
793
- deps.io.out(appendIntegrationContext(outcome.stdout, text, harness, 'UserPromptSubmit'));
794
- });
795
- if (inputWritten)
796
- await outcome.afterOutput?.();
797
- }
798
- if (!inputWritten && outcome.stdout !== undefined) {
799
- // No work, await, or diagnostic may sit between this cross-process
800
- // SessionEnd fence and the irreversible harness stdout write.
801
- if (outcome.commitStdout === undefined || outcome.commitStdout()) {
802
- deps.io.out(outcome.stdout);
803
- try {
804
- await outcome.afterOutput?.();
805
- }
806
- catch {
807
- // Recording what the hand-off proved never fails the hook.
808
- }
809
- }
810
- else {
811
- deps.io.err('notifai: the Agent Session ended before answer delivery; no continuation was written');
812
- }
813
- }
814
- return EXIT.ok;
815
- }
816
- catch (err) {
817
- // SessionEnd defers its start record until after cleanup; if cleanup itself
818
- // fails, begin the after-the-fact lifecycle here before recording why.
819
- if (sessionEnd)
820
- start({ cwd, stop_hook_active: envelope.stop_hook_active ?? null });
821
- // The hook still exits 0 — handing the terminal back is always right. What
822
- // this adds is that the reason survives, including the server's own words.
823
- logger.error('hook.end', {
824
- hook: event,
825
- outcome: 'failed',
826
- reason: 'execution-failed',
827
- ...failureData(err),
828
- });
829
- for (const line of describeHookFailure(err))
830
- deps.io.err(`notifai: ${line}`);
831
- return EXIT.ok;
832
- }
833
- }
834
- /**
835
- * The last meter for an answer this Stop hook accepted, chosen by harness.
836
- *
837
- * Claude's adapter needs the harness process that invoked this hook, to prove
838
- * exact own-child session ownership before it posts to the inbox socket. Codex's
839
- * needs only the thread id: queueing is a write to that thread's own durable
840
- * inbox, which no other process can be confused for. Without an exact session id
841
- * neither can prove anything, so the waiter falls back to the plain Stop
842
- * continuation.
843
- */
844
- function stopWakeRoute(deps, harness, sessionId, cwd, background) {
845
- if (harness === 'grok')
846
- return undefined;
847
- if (sessionId === undefined)
848
- return undefined;
849
- const declaredSourcePid = declaredHookSourcePid(deps);
850
- if (harness === 'claude-code') {
851
- if ((deps.hookPlatform ?? process.platform) === 'win32')
852
- return undefined;
853
- const route = sessionInputRoute(sessionId, deps.env, claudeWakeRoute({
854
- sessionId,
855
- cwd,
856
- sourcePid: deps.claudeSourcePid ?? declaredSourcePid ?? claudeSessionPid(deps.env),
857
- ...(deps.claudeWake === undefined ? {} : { adapters: deps.claudeWake }),
858
- }), log(deps));
859
- // A detached subprocess can be reparented after ask exits. Only the
860
- // resident Session Attendant retains Claude's required own-child ancestry.
861
- // Its ordinary attendance exchange wakes staged inputs, even with no notes.
862
- return background ? { ...route, defer: async (accepted) => {
863
- stageSessionAnswers(sessionId, deps.env, accepted);
864
- } } : route;
865
- }
866
- if (harness === 'codex') {
867
- return sessionInputRoute(sessionId, deps.env, codexWakeRoute({
868
- threadId: sessionId,
869
- cwd,
870
- env: deps.env,
871
- ...(deps.codexWake === undefined ? {} : { adapters: deps.codexWake }),
872
- }), log(deps), 'attendant');
873
- }
874
- return undefined;
875
- }
876
- /**
877
- * The Stop waiter claims fenced answers only where a Session Attendant can
878
- * hold the session's lease and the answer is written in place: Claude Code on
879
- * macOS and Linux. Everywhere else it closes and writes exactly as before.
880
- */
881
- function answerClaimsFor(deps, harness, sessionId, event) {
882
- if (sessionId === undefined ||
883
- (event !== 'stop' && event !== 'question-settlement' && event !== 'openclaw-settlement'))
884
- return {};
885
- if (!attendantSupport(harness, deps.hookPlatform ?? process.platform).supported)
886
- return {};
887
- const writer = deps.answerWriter === undefined ? currentProcessIdentity() : deps.answerWriter;
888
- if (writer === null)
889
- return {};
890
- return {
891
- answerClaims: {
892
- lease: () => readAttendantLease(sessionId, deps.env),
893
- writer,
894
- monotonic: () => performance.now(),
895
- },
896
- };
897
- }
898
- /** Stable harness parent propagated by the managed adapter across child tools. */
899
- function declaredHookSourcePid(deps) {
900
- const value = Number(deps.env['NOTIFAI_HOOK_SOURCE_PID']);
901
- return Number.isInteger(value) && value > 0 ? value : undefined;
902
- }
903
- /**
904
- * What went wrong, in terms of what to do about it.
905
- *
906
- * On 2026-08-03 a contract change shipped without the server deploy that goes
907
- * with it. The CLI stamped `lifecycle` on every question draft, the deployed
908
- * server rejected the unknown field, and escalation stopped working entirely —
909
- * announced as "hook failed, deferring to the terminal", which reads like a
910
- * flaky network. The information needed to diagnose it in one second was
911
- * already in hand: a 422 whose details name the offending path. It was being
912
- * thrown away by `String(err)`.
913
- *
914
- * A hook still exits 0 whatever this says. Handing the terminal back is always
915
- * right; the only question is whether the user is told anything they can use.
916
- */
917
- export function describeHookFailure(err) {
918
- if (!(err instanceof ApiCallError)) {
919
- return [`hook failed, deferring to the terminal (${String(err)})`];
920
- }
921
- const lines = [`hook failed, deferring to the terminal (${err.code}: ${err.message})`];
922
- const paths = rejectedPaths(err.details);
923
- if (paths.length > 0)
924
- lines.push(`the server rejected: ${paths.join(', ')}`);
925
- // A 422 on a draft this CLI built is not a user error — this CLI's own
926
- // contract produced it. Either the server is behind, or the two disagree.
927
- if (err.status === 422) {
928
- lines.push('this build sent a field the server did not accept, which usually means the server ' +
929
- 'is older than this CLI — check with `notifai doctor`');
930
- }
931
- return lines;
932
- }
933
- //# sourceMappingURL=commands-hook-run.js.map