@raidiant/notifai 11.7.1 → 12.0.0-beta.5

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 +48 -0
  2. package/NOTICE +16 -0
  3. package/THIRD_PARTY_NOTICES +725 -0
  4. package/bin/notifai.mjs +11387 -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,655 +0,0 @@
1
- import { hasSessionInputs, stageSessionMessages, wakeSessionInputs, wakeCodexSessionInputs, reconcileSessionInputWakes } from './session-inputs.js';
2
- /**
3
- * `notifai hook attend`: the asynchronous handler that becomes an Agent
4
- * Session's Session Attendant, or exits within milliseconds when a healthy
5
- * attendant for this exact session incarnation already runs.
6
- *
7
- * Installed on SessionStart next to the short activation handler (which it
8
- * never delays: the harness does not wait for an async handler), and on
9
- * UserPromptSubmit and Stop to re-arm a session whose attendant died.
10
- */
11
- import { existsSync, readFileSync, realpathSync, readdirSync } from 'node:fs';
12
- import path from 'node:path';
13
- import { createHash } from 'node:crypto';
14
- import { fileURLToPath } from 'node:url';
15
- import { EXIT, makeClient } from './commands-core.js';
16
- import { claudeSessionPid } from './commands-harness-context.js';
17
- import { loadConfig } from './config.js';
18
- import { inspectHookAdapter, isNpxAdapterTarget } from './hook-adapter.js';
19
- import { acquireClaimFile, claimHolderMayRun, readClaimFile, releaseClaimFile, requestClaimHandoff } from './hook-question-lock.js';
20
- import { beginSessionIncarnation, endsIncarnation, readSessionEndMarker, readSessionIncarnation, readSessionState, refreshSessionMarkers, rotateSessionIncarnation, sessionNotified, sessionHasEnded, updateSessionState, happenedBefore, } from './hook-session-state.js';
21
- import { findInstallations, findLegacyProjectInstallations, handlerEvent } from './install-hooks.js';
22
- import { logSettingsFrom } from './logging.js';
23
- import { processExecutableName, processStartTime } from './process-identity.js';
24
- import { projectBinding, projectEnabled } from './project-enablement.js';
25
- import { packageVersion } from './release.js';
26
- import { runSessionAttendant, systemAttendantClock, } from './session-attendant.js';
27
- import { attendantSupport, claudeAttendanceProbe, codexAttendanceProbe, openclawAttendanceProbe, systemClaudeProbeAdapters, } from './session-attendant-probe.js';
28
- import { attendantClaimPath, attendantStatusPath, observedCodexTurnActivity, readAttendantLease, recordTurnEnd, recordTurnStart, reconcileNativeTurn, turnActivityPath, writeAttendantStatus, } from './session-attendant-state.js';
29
- import { CLI_PACKAGE_NAME } from './cli-contract.js';
30
- import { inspectClaudeInbox, systemClaudeWakeAdapters } from './claude-wake.js';
31
- import { currentProcessIdentity } from './process-identity.js';
32
- import { inspectCodexQueue, systemCodexWakeAdapters } from './codex-wake.js';
33
- import { claudeSourceDescriptor, deliverIntoClaudeSession, deliverIntoCodexThread } from './session-handoff.js';
34
- import { handOffSessionMessages } from './session-message-handoff.js';
35
- import { compareVersions } from './version.js';
36
- import { readOpenclawGeneration } from './openclaw-generation.js';
37
- import { integrationFaultNotice } from './integration-health.js';
38
- import { openclawBridgeActivity, openclawMessageBridge, openclawMessageBridgeAvailable } from './openclaw-message-bridge.js';
39
- import { readNativeTurnSnapshot } from './codex-native-turn.js';
40
- import { codexInputObserver, refreshCodexInputActivity } from './codex-input-lifecycle.js';
41
- // Captured when this module loads, so an in-place build cannot make a resident
42
- // writer mistake the replacement files for its own loaded implementation.
43
- export const attendantRuntimeRevision = (() => {
44
- const file = fileURLToPath(import.meta.url), extension = path.extname(file), directory = path.dirname(file);
45
- const hash = createHash('sha256');
46
- for (const name of readdirSync(directory).filter(name => name.endsWith(extension) &&
47
- !name.endsWith('.d.ts') && !name.endsWith('.test.ts')).sort()) {
48
- hash.update(name).update('\0').update(readFileSync(path.join(directory, name)));
49
- }
50
- return hash.digest('hex');
51
- })();
52
- const SUPERSEDED_OWNER_WAIT_MS = 6_000;
53
- /**
54
- * Bound on Codex SessionEnd's `ended` report. Codex allows SessionEnd at most
55
- * three seconds, and the CLI's own start and durable cleanup come first.
56
- */
57
- export const CODEX_SESSION_END_REPORT_MS = 1_000;
58
- export async function attendHook(deps, input) {
59
- const { envelope, harness, cwd, logger } = input;
60
- const seams = deps.attendant ?? {};
61
- const end = (outcome, data = {}) => {
62
- if (input.recovery !== undefined)
63
- logger.info('attendant.state', { source: 'update-resume', outcome, ...data });
64
- else
65
- logger.info('hook.end', { hook: 'attend', outcome, decided: false, ...data });
66
- return EXIT.ok;
67
- };
68
- // Read once, now: an in-place reinstall replaces the manifest on disk, and
69
- // rereading it later would compare the installed files with themselves.
70
- const runningVersion = deps.runningVersion === undefined ? packageVersion() : deps.runningVersion;
71
- const sessionId = envelope.session_id;
72
- if (sessionId === undefined)
73
- return end('ignored', { reason: 'missing-session-id' });
74
- const support = attendantSupport(harness, deps.hookPlatform ?? process.platform);
75
- if (!support.supported)
76
- return end('unsupported', { reason: support.reason });
77
- if (harness !== 'claude-code' && harness !== 'codex' && harness !== 'openclaw') {
78
- return end('unsupported', { reason: 'harness-has-no-attendant' });
79
- }
80
- const starting = envelope.hook_event_name === 'SessionStart' || envelope.source !== undefined;
81
- const claimFile = attendantClaimPath(sessionId, deps.env);
82
- // The hook adapter names the harness process that ran it. Claude Code also
83
- // names itself; Codex is only ever the declared parent.
84
- const pid = input.recovery?.harnessProcess.pid ?? (harness === 'codex' || harness === 'openclaw'
85
- ? declaredHookSourcePid(deps.env)
86
- : declaredHookSourcePid(deps.env) ?? claudeSessionPid(deps.env));
87
- if (pid === undefined)
88
- return end('ignored', { reason: 'harness-process-unproven' });
89
- const nativeOwner = harness === 'codex'
90
- ? seams.harnessProcess ?? (() => {
91
- const start = processStartTime(pid);
92
- return start === null ? null : { pid, start };
93
- })()
94
- : null;
95
- const ownsNative = (key) => {
96
- const current = readSessionIncarnation(sessionId, deps.env);
97
- return nativeOwner !== null && current?.key === key &&
98
- envelope.agent_id === undefined && envelope.agent_type === undefined &&
99
- current.harness_process?.pid === nativeOwner.pid && current.harness_process.start === nativeOwner.start &&
100
- (input.recovery === undefined || current.key === input.recovery.key) &&
101
- !happenedBefore(input.invokedAt, current.start) && !sessionHasEnded(sessionId, deps.env);
102
- };
103
- const observeNative = (key, initial = false) => {
104
- if (!ownsNative(key))
105
- return;
106
- if (envelope.hook_event_name === 'Stop') {
107
- refreshCodexInputActivity(sessionId, deps.env, key, envelope.transcript_path);
108
- return;
109
- }
110
- if (envelope.hook_event_name === 'Interrupt') {
111
- recordCodexTurnEnd(envelope, sessionId, deps.env);
112
- return;
113
- }
114
- if (!initial && envelope.hook_event_name !== 'UserPromptSubmit' && input.recovery === undefined)
115
- return;
116
- const native = readNativeTurnSnapshot(input.recovery?.transcriptPath ?? envelope.transcript_path, sessionId, deps.env);
117
- const turnId = envelope.turn_id ?? (initial || input.recovery !== undefined ? native?.latest.id : undefined);
118
- if (native === null || turnId === undefined)
119
- return;
120
- try {
121
- if (reconcileNativeTurn(sessionId, deps.env, key, turnId, native, () => ownsNative(key), initial)) {
122
- updateSessionState(sessionId, deps.env, state => ({ ...state, codex_native_turn: {
123
- key, turn_id: turnId, transcript_path: native.file,
124
- } }));
125
- }
126
- }
127
- catch { /* A busy observation lock must not cost this session its attendant. */ }
128
- };
129
- if (harness === 'codex') {
130
- const current = readSessionIncarnation(sessionId, deps.env);
131
- if (input.recovery !== undefined && (current === null || !ownsNative(current.key))) {
132
- return end('ignored', { reason: 'recovery-owner-changed' });
133
- }
134
- if (current !== null)
135
- observeNative(current.key);
136
- }
137
- // Interrupt is a bounded observation, never a replacement attendant.
138
- if (envelope.hook_event_name === 'Interrupt')
139
- return end('recorded');
140
- if (!starting) {
141
- // Re-arm fast path, before any configuration or gate work: this runs on
142
- // every prompt and every turn end, and almost always finds its owner.
143
- const current = readSessionIncarnation(sessionId, deps.env);
144
- const holder = readClaimFile(claimFile);
145
- if (current !== null &&
146
- current.harness_process?.pid === (seams.harnessProcess?.pid ?? pid) &&
147
- holder?.['incarnation'] === current.incarnation &&
148
- claimHolderMayRun(holder)) {
149
- // An authenticated native event can hand the resident writer over to
150
- // installed code. The old owner exits through claim-lost, not SessionEnd.
151
- const upgrade = harness === 'codex' && ownsNative(current.key) &&
152
- runningVersion !== null && (holder['runtime_version'] !== runningVersion ||
153
- holder['runtime_revision'] !== attendantRuntimeRevision) && holder['pid'] !== process.pid &&
154
- (seams.gates ?? (() => attendantGates(deps, cwd, sessionId, harness, runningVersion)))().ok &&
155
- (holder['handoff'] === true || (typeof holder['token'] === 'string' &&
156
- requestClaimHandoff(claimFile, holder['token'], current.incarnation)));
157
- if (!upgrade)
158
- return end('owner-present', { same_incarnation: true, holder_alive: true });
159
- logger.info('attendant.state', { reason: 'installed-runtime-changed', phase: 'handoff' });
160
- }
161
- }
162
- const assessGates = seams.gates ?? (() => attendantGates(deps, cwd, sessionId, harness, runningVersion));
163
- const gates = () => {
164
- const result = assessGates();
165
- if (!result.ok && !['project-disabled', 'enablement-unavailable'].includes(result.reason)) {
166
- // Preserve evidence before the missing contract withdraws this observer.
167
- // An explicit loss invalidates a recent healthy cache immediately.
168
- integrationFaultNotice({ ...deps, cwd }, harness, false, true);
169
- }
170
- return result;
171
- };
172
- try {
173
- const config = loadConfig({ cwd, env: deps.env, sessionId });
174
- logger.adopt(logSettingsFrom(config));
175
- logger.bind({ project: config.project.value });
176
- }
177
- catch {
178
- // The gate below reports enablement it cannot read.
179
- }
180
- const gate = gates();
181
- if (!gate.ok)
182
- return end('ignored', { reason: gate.reason });
183
- // Codex runs a hook through `$SHELL -lc`. bash and zsh exec the adapter, so
184
- // its parent is Codex; a shell that does not would be named instead and
185
- // outlive a crashed Codex. Only the Codex executable itself is proof.
186
- if (seams.harnessProcess === undefined && harness === 'codex' && processExecutableName(pid) !== 'codex') {
187
- return end('ignored', { reason: 'harness-process-not-codex' });
188
- }
189
- const harnessProcess = seams.harnessProcess ?? (() => {
190
- const start = processStartTime(pid);
191
- return start === null ? null : { pid, start };
192
- })();
193
- if (harnessProcess === null)
194
- return end('ignored', { reason: 'harness-process-unproven' });
195
- const openclawGeneration = harness === 'openclaw'
196
- ? readOpenclawGeneration(sessionId, deps.env)
197
- : null;
198
- if (harness === 'openclaw' && (openclawGeneration === null ||
199
- openclawGeneration.ended || !openclawGeneration.activated ||
200
- readSessionIncarnation(sessionId, deps.env)?.openclaw_generation !== openclawGeneration.id)) {
201
- return end('ignored', { reason: 'stale-openclaw-generation' });
202
- }
203
- const clock = seams.clock ?? systemAttendantClock;
204
- let record = input.recovery !== undefined ? readSessionIncarnation(sessionId, deps.env) : await withLockRetry(clock, () => beginSessionIncarnation(sessionId, deps.env, {
205
- stamp: input.invokedAt,
206
- harnessProcess,
207
- clearEarlierEnd: starting,
208
- ...(openclawGeneration === null ? {} : { openclawGeneration: openclawGeneration.id }),
209
- }));
210
- if (record === null || (input.recovery !== undefined && !ownsNative(record.key)))
211
- return end('ignored', { reason: 'recovery-owner-changed' });
212
- if (harness === 'codex' && starting)
213
- observeNative(record.key, true);
214
- // One live attendant per session. A holder serving an older incarnation of
215
- // this session (an in-process resume moments after a clear) steps aside
216
- // within one probe; a holder serving this one is healthy, so this exits.
217
- const waitUntil = clock.monotonic() + (seams.supersededOwnerWaitMs ?? SUPERSEDED_OWNER_WAIT_MS);
218
- let token = null;
219
- while (true) {
220
- token = acquireClaimFile(claimFile, { incarnation: record.incarnation, runtime_version: runningVersion, runtime_revision: attendantRuntimeRevision }, clock.wall());
221
- if (token !== null)
222
- break;
223
- const holder = readClaimFile(claimFile);
224
- if ((holder?.['incarnation'] === record.incarnation && holder['handoff'] !== true) || clock.monotonic() >= waitUntil) {
225
- return end('owner-present', {
226
- same_incarnation: holder?.['incarnation'] === record.incarnation,
227
- holder_alive: claimHolderMayRun(holder),
228
- });
229
- }
230
- await clock.sleep(250, new AbortController().signal).catch(() => undefined);
231
- if (input.recovery !== undefined && !ownsNative(record.key))
232
- return end('ignored', { reason: 'recovery-owner-changed' });
233
- record = readSessionIncarnation(sessionId, deps.env) ?? record;
234
- }
235
- logger.info(input.recovery === undefined ? 'hook.end' : 'attendant.state', {
236
- hook: 'attend',
237
- outcome: 'attending',
238
- decided: false,
239
- source: input.recovery === undefined ? envelope.source ?? envelope.hook_event_name ?? null : 'update-resume',
240
- });
241
- const served = record;
242
- const codexActivity = harness === 'codex'
243
- ? codexInputObserver(sessionId, deps.env, served.key, clock.monotonic, input.recovery?.transcriptPath ?? envelope.transcript_path)
244
- : null;
245
- const probeAdapters = seams.probeAdapters ?? systemClaudeProbeAdapters(deps.env);
246
- // SessionEnd names the incarnation it ended; an end of any other one,
247
- // earlier or later, is not this attendant's.
248
- const endedByHook = () => endsIncarnation(readSessionEndMarker(sessionId, deps.env), served);
249
- const probe = harness === 'codex'
250
- ? codexAttendanceProbe({
251
- harness: harnessProcess,
252
- endedByHook,
253
- activity: () => {
254
- // Presence remains live through a read gap. The binary service
255
- // activity is last-observed; scheduling reads afresh.
256
- const activity = codexActivity.observe();
257
- if (activity === 'unknown')
258
- logger.debug('attendant.state', { activity: 'cached', reason: 'native-activity-unreadable' });
259
- return observedCodexTurnActivity(sessionId, deps.env, served.key);
260
- },
261
- adapters: probeAdapters,
262
- })
263
- : harness === 'openclaw'
264
- ? openclawAttendanceProbe({
265
- sessionKey: sessionId,
266
- generationId: served.openclaw_generation ?? '',
267
- gateway: harnessProcess,
268
- env: deps.env,
269
- endedByHook,
270
- activity: openclawBridgeActivity,
271
- })
272
- : claudeAttendanceProbe({ sessionId, harness: harnessProcess, endedByHook, adapters: probeAdapters });
273
- let client;
274
- const connect = () => {
275
- if (client !== undefined)
276
- return client;
277
- const credential = deps.store.load();
278
- client = credential
279
- ? makeClient(deps, credential.baseUrl, `Bearer nfm_${credential.machineId}.${credential.secret}`)
280
- : null;
281
- return client;
282
- };
283
- const codexWakeNeeded = codexActivity?.mayWake ?? null;
284
- const messages = sessionMessageWriter({
285
- deps,
286
- harness: harness,
287
- sessionId,
288
- cwd: envelope.cwd ?? cwd,
289
- harnessPid: harnessProcess.pid,
290
- writer: seams.writer === undefined ? currentProcessIdentity() : seams.writer,
291
- clock,
292
- logger,
293
- mayWake: codexWakeNeeded ?? (() => true),
294
- unknownAllowed: codexActivity?.unknownAllowed ?? (() => false),
295
- });
296
- let wakeCleanup = null;
297
- const signals = seams.signalled === undefined ? terminationSignal() : null;
298
- try {
299
- const result = await runSessionAttendant({
300
- sessionId,
301
- incarnation: record.incarnation,
302
- incarnationNow: () => readSessionIncarnation(sessionId, deps.env)?.incarnation ?? null,
303
- rotateIncarnation: (expected) => {
304
- const next = rotateSessionIncarnation(sessionId, deps.env, expected);
305
- if (next === null)
306
- return null;
307
- // Keep the claim naming the incarnation it serves.
308
- releaseClaimFile(claimFile, token);
309
- token = acquireClaimFile(claimFile, { incarnation: next.incarnation, runtime_version: runningVersion, runtime_revision: attendantRuntimeRevision }, clock.wall());
310
- return token === null ? null : next.incarnation;
311
- },
312
- probe,
313
- // A claim that vanished or changed hands fences this attendant: another
314
- // may already serve the same incarnation.
315
- claimHeld: () => token !== null && readClaimFile(claimFile)?.['token'] === token,
316
- notified: () => sessionNotified(sessionId, deps.env),
317
- gates,
318
- client: connect,
319
- serverSupportsAttendance: async (api) => (await api.compatibility()).server_capabilities.includes('session_attendance'),
320
- // The service accepts notes for this session only while an attendant
321
- // that can hand them in place says so.
322
- acceptsMessages: messages !== null,
323
- ...(harness === 'claude-code' && messages !== null ? {
324
- localInputPending: () => !readSessionState(sessionId, deps.env).input_wake?.queued &&
325
- hasSessionInputs(sessionId, deps.env, null),
326
- } : {}),
327
- ...(codexWakeNeeded === null ? {} : {
328
- localInputPending: () => !readSessionState(sessionId, deps.env).input_wake?.queued &&
329
- hasSessionInputs(sessionId, deps.env, readAttendantLease(sessionId, deps.env)) && codexWakeNeeded(),
330
- }),
331
- ...(messages === null
332
- ? {}
333
- : {
334
- onMessages: async (batch, attendant) => {
335
- const api = connect();
336
- return api === null ? 'done' : messages(api, batch, attendant);
337
- },
338
- }),
339
- clock,
340
- logger,
341
- writeStatus: (status) => writeAttendantStatus(sessionId, deps.env, status),
342
- onProbe: () => {
343
- if (harness === 'codex' && wakeCleanup === null) {
344
- wakeCleanup = reconcileSessionInputWakes(sessionId, deps.env, deps.codexQueueControl)
345
- .catch(() => undefined).finally(() => { wakeCleanup = null; });
346
- }
347
- // Observe locally without consuming the agent's next context notice.
348
- // This catches removal of the very hook that would report the fault.
349
- const notice = integrationFaultNotice({ ...deps, cwd: envelope.cwd ?? cwd, now: () => clock.wall() }, harness, false);
350
- if (notice !== undefined)
351
- logger.info('attendant.state', { integration: 'needs-attention' });
352
- },
353
- heartbeat: () => {
354
- refreshSessionMarkers(sessionId, deps.env, clock.wall(), [
355
- claimFile,
356
- attendantStatusPath(sessionId, deps.env),
357
- turnActivityPath(sessionId, deps.env),
358
- ]);
359
- },
360
- signalled: seams.signalled ?? signals.promise,
361
- ...(seams.probeIntervalMs === undefined ? {} : { probeIntervalMs: seams.probeIntervalMs }),
362
- ...(seams.waitSeconds === undefined ? {} : { waitSeconds: seams.waitSeconds }),
363
- });
364
- seams.onExit?.(result);
365
- }
366
- finally {
367
- signals?.dispose();
368
- if (token !== null)
369
- releaseClaimFile(claimFile, token);
370
- }
371
- return EXIT.ok;
372
- }
373
- /**
374
- * The attendant's Session Message writer, or null when this session cannot
375
- * take a message in place: for Claude Code no inbox socket (`--bare`, an older
376
- * Claude Code, an unsupported protocol), for Codex no thread id, and for
377
- * either no provable writer identity. Null keeps the attendant presence-only,
378
- * and the service accepts no notes for the session.
379
- */
380
- function sessionMessageWriter(input) {
381
- const { deps, sessionId, harnessPid, writer, clock, logger } = input;
382
- if (writer === null)
383
- return null;
384
- const sequencerFor = (client) => ({
385
- sessionId,
386
- env: deps.env,
387
- client,
388
- monotonic: () => clock.monotonic(),
389
- wall: () => clock.wall(),
390
- sleep: (milliseconds) => clock.sleep(milliseconds, new AbortController().signal),
391
- writer,
392
- log: logger,
393
- });
394
- if (input.harness === 'codex')
395
- return codexMessageWriter({ deps, sessionId, cwd: input.cwd, logger, mayWake: input.mayWake, unknownAllowed: input.unknownAllowed });
396
- if (input.harness === 'openclaw') {
397
- if (!openclawMessageBridgeAvailable(deps.env))
398
- return null;
399
- const generation = readOpenclawGeneration(sessionId, deps.env);
400
- if (generation === null || generation.ended)
401
- return null;
402
- const send = openclawMessageBridge();
403
- return (client, batch, attendant) => handOffSessionMessages(batch, attendant, {
404
- sequencer: sequencerFor(client),
405
- openclawGeneration: generation.id,
406
- write: (text, begin, guard, _writerGroup, message) => {
407
- return send(message.message_id, sessionId, generation.id, text, begin, guard);
408
- },
409
- });
410
- }
411
- if (input.harness !== 'claude-code')
412
- return null;
413
- const adapters = deps.claudeWake ?? systemClaudeWakeAdapters(deps.env);
414
- const inbox = inspectClaudeInbox({
415
- pid: harnessPid,
416
- platform: deps.hookPlatform ?? process.platform,
417
- readDescriptor: adapters.readDescriptor,
418
- socketExists: existsSync,
419
- });
420
- if (inbox.state !== 'ready') {
421
- logger.info('attendant.state', { messages: 'unavailable', reason: inbox.reason });
422
- return null;
423
- }
424
- return async (_client, batch, attendant) => {
425
- const generation = attendant.generation();
426
- if (generation === null || !attendant.mayWrite())
427
- return 'retry-soon';
428
- stageSessionMessages(sessionId, deps.env, { incarnation: attendant.incarnation(), generation }, batch);
429
- if (!hasSessionInputs(sessionId, deps.env, { incarnation: attendant.incarnation(), generation }))
430
- return 'done';
431
- await wakeSessionInputs(sessionId, deps.env, async (text) => {
432
- const result = await deliverIntoClaudeSession({
433
- sessionId, sourcePid: harnessPid,
434
- sourceDescriptor: claudeSourceDescriptor(sessionId, harnessPid, adapters),
435
- adapters, text, begin: () => attendant.mayWrite(), holdAfterSend: false, writer: 'Session Attendant',
436
- });
437
- return result.status === 'written';
438
- }, logger);
439
- return 'done';
440
- };
441
- }
442
- /** Stage pending input for foreground consumption and coalesce its native wake. */
443
- function codexMessageWriter(input) {
444
- const { deps, sessionId, cwd, logger, mayWake } = input;
445
- const queue = inspectCodexQueue(sessionId, deps.env);
446
- if (queue.state !== 'ready') {
447
- logger.info('attendant.state', { messages: 'unavailable', reason: queue.reason });
448
- return null;
449
- }
450
- const adapters = deps.codexWake ?? systemCodexWakeAdapters(deps.env);
451
- if (adapters.available?.() === false) {
452
- // A queue writer that cannot start would leave every note unconfirmed.
453
- logger.info('attendant.state', { messages: 'unavailable', reason: 'codex-executable-not-found' });
454
- return null;
455
- }
456
- return async (_client, batch, attendant) => {
457
- const incarnation = readSessionIncarnation(sessionId, deps.env);
458
- const generation = attendant.generation();
459
- if (incarnation === null || generation === null || !attendant.mayWrite())
460
- return 'retry-soon';
461
- stageSessionMessages(sessionId, deps.env, { incarnation: attendant.incarnation(), generation }, batch);
462
- if (await wakeCodexSessionInputs({ sessionId, env: deps.env,
463
- lease: { incarnation: attendant.incarnation(), generation },
464
- mayWrite: () => attendant.mayWrite(), mayWake,
465
- unknownAllowed: input.unknownAllowed,
466
- ...(deps.codexQueueControl === undefined ? {} : { connect: deps.codexQueueControl }),
467
- }))
468
- return 'done';
469
- if (!hasSessionInputs(sessionId, deps.env, { incarnation: attendant.incarnation(), generation }))
470
- return 'done';
471
- if (!mayWake())
472
- return 'done';
473
- // The resident observes completion even when no further tool runs. Recheck
474
- // eligibility and pending input immediately before queue admission; a hook
475
- // may have consumed the batch since the last attendance exchange.
476
- await wakeSessionInputs(sessionId, deps.env, async (text) => {
477
- if (!attendant.mayWrite() || !mayWake() ||
478
- !hasSessionInputs(sessionId, deps.env, { incarnation: attendant.incarnation(), generation }))
479
- return false;
480
- const result = await deliverIntoCodexThread({
481
- threadId: queue.threadId, cwd, env: deps.env, adapters, text,
482
- begin: () => attendant.mayWrite() && mayWake() &&
483
- hasSessionInputs(sessionId, deps.env, { incarnation: attendant.incarnation(), generation }),
484
- });
485
- return result.status === 'written';
486
- }, logger);
487
- return 'done';
488
- };
489
- }
490
- /** Record the Codex turn this turn end or interrupt closes. */
491
- function recordCodexTurnEnd(envelope, sessionId, env) {
492
- const turnId = envelope.turn_id;
493
- const event = envelope.hook_event_name;
494
- if (typeof turnId !== 'string' || turnId === '' || (event !== 'Stop' && event !== 'Interrupt'))
495
- return;
496
- try {
497
- recordTurnEnd(sessionId, env, turnId);
498
- }
499
- catch {
500
- // Activity is a hint; a busy lock never costs the session its attendant.
501
- }
502
- }
503
- /**
504
- * Record the Codex turn this prompt starts. Called from the synchronous
505
- * UserPromptSubmit hook: Codex runs it before the turn and one turn at a time,
506
- * which is what orders recorded starts. Never throws.
507
- */
508
- export function recordCodexTurnStart(envelope, env) {
509
- const sessionId = envelope.session_id;
510
- const turnId = envelope.turn_id;
511
- if (sessionId === undefined || typeof turnId !== 'string' || turnId === '')
512
- return;
513
- try {
514
- const current = readSessionIncarnation(sessionId, env);
515
- if (current !== null)
516
- recordTurnStart(sessionId, env, current.key, turnId);
517
- }
518
- catch {
519
- // Activity is a hint; it never delays or fails the User's prompt.
520
- }
521
- }
522
- /**
523
- * Re-checked before every exchange: the User-owned Project Enablement, and
524
- * the installed CLI/hook contract. Hooks removed or replaced by a build that
525
- * does not attend, or an installed CLI older than this attendant, withdraw it.
526
- */
527
- export function attendantGates(deps, cwd, sessionId, harness, runningVersion) {
528
- try {
529
- const config = loadConfig({ cwd, env: deps.env, sessionId });
530
- if (!projectEnabled(projectBinding(cwd, deps.env, config.project.value))) {
531
- return { ok: false, reason: 'project-disabled' };
532
- }
533
- }
534
- catch {
535
- return { ok: false, reason: 'enablement-unavailable' };
536
- }
537
- // The handler that started this attendant lives in the Machine layer, or in
538
- // a Project layer an older build wrote; either keeps the contract in place.
539
- const attendInstalled = [
540
- ...findInstallations(deps.env, deps.hookAdapterHome, deps.hookPlatform),
541
- ...findLegacyProjectInstallations(cwd, deps.env, deps.hookAdapterHome, deps.hookPlatform),
542
- ]
543
- .filter((installation) => installation.harness === harness)
544
- .some((installation) => harness === 'openclaw'
545
- ? (installation.problems ?? []).length === 0
546
- : installation.handlers.some((handler) => handlerEvent(handler.command) === 'attend'));
547
- if (!attendInstalled)
548
- return { ok: false, reason: 'attend-handler-removed' };
549
- // Fail closed: an installed contract this attendant cannot establish is not
550
- // one it may keep attending under.
551
- const installed = installedCliVersion(inspectHookAdapter(deps.hookAdapterHome, deps.hookPlatform).target);
552
- if (runningVersion === null || installed === null)
553
- return { ok: false, reason: 'cli-contract-unknown' };
554
- const order = compareVersions(installed, runningVersion);
555
- if (order === 'unparseable')
556
- return { ok: false, reason: 'cli-contract-unknown' };
557
- if (order === 'before')
558
- return { ok: false, reason: 'cli-downgraded' };
559
- return { ok: true };
560
- }
561
- function installedCliVersion(target) {
562
- if (target === null)
563
- return null;
564
- if (isNpxAdapterTarget(target)) {
565
- // Installers pin npx targets to an exact version: `@raidiant/notifai@1.2.3`.
566
- const prefix = `${CLI_PACKAGE_NAME}@`;
567
- return target.spec.startsWith(prefix) ? target.spec.slice(prefix.length) : null;
568
- }
569
- try {
570
- const manifest = path.join(path.dirname(realpathSync(target.scriptPath)), '..', 'package.json');
571
- const parsed = JSON.parse(readFileSync(manifest, 'utf8'));
572
- return typeof parsed.version === 'string' ? parsed.version : null;
573
- }
574
- catch {
575
- return null;
576
- }
577
- }
578
- /**
579
- * The session-state lock is shared with the synchronous activation handler
580
- * running beside this one. This handler is asynchronous and nobody waits on
581
- * it, so it outlasts a busy moment instead of giving the session up.
582
- */
583
- async function withLockRetry(clock, action) {
584
- for (let attempt = 1;; attempt += 1) {
585
- try {
586
- return action();
587
- }
588
- catch (err) {
589
- if (attempt >= 8 || !(err instanceof Error) || !err.message.startsWith('timed out waiting for file lock')) {
590
- throw err;
591
- }
592
- await clock.sleep(250 * attempt, new AbortController().signal);
593
- }
594
- }
595
- }
596
- function declaredHookSourcePid(env) {
597
- const value = Number(env['NOTIFAI_HOOK_SOURCE_PID']);
598
- return Number.isInteger(value) && value > 0 ? value : undefined;
599
- }
600
- /**
601
- * Terminal close reaches the attendant as SIGTERM with no SessionEnd; treat
602
- * any termination signal as "ending, report once".
603
- */
604
- function terminationSignal() {
605
- const signals = ['SIGTERM', 'SIGHUP', 'SIGINT'];
606
- let resolve;
607
- const promise = new Promise((done) => {
608
- resolve = done;
609
- });
610
- const onSignal = () => resolve();
611
- for (const signal of signals)
612
- process.on(signal, onSignal);
613
- return {
614
- promise,
615
- dispose: () => {
616
- for (const signal of signals)
617
- process.off(signal, onSignal);
618
- },
619
- };
620
- }
621
- /**
622
- * Codex's SessionEnd reports the attended session `ended`.
623
- *
624
- * On Codex the attendant cannot: Codex SIGKILLs every unfinished async hook's
625
- * process group the moment SessionEnd returns, before the attendant's next
626
- * probe could see the end marker, and may kill it before SessionEnd starts.
627
- * SessionEnd therefore ends the saved lease of the current incarnation, even
628
- * without a living attendant. Its exact incarnation and generation let the
629
- * service fence it as it would the attendant's own report. Without saved
630
- * fencing identity there is nothing safe to end; a failed report leaves the
631
- * lease to lapse, and presence reads out of reach.
632
- */
633
- export async function reportCodexSessionEnded(deps, sessionId, lease) {
634
- if (lease === null)
635
- return 'no-lease';
636
- const credential = deps.store.load();
637
- if (!credential)
638
- return 'not-paired';
639
- const client = makeClient(deps, credential.baseUrl, `Bearer nfm_${credential.machineId}.${credential.secret}`, {
640
- timeoutMs: CODEX_SESSION_END_REPORT_MS,
641
- });
642
- const bound = new AbortController();
643
- const timer = setTimeout(() => bound.abort(), CODEX_SESSION_END_REPORT_MS);
644
- try {
645
- await client.attend(sessionId, { incarnation: lease.incarnation, generation: lease.generation, state: 'ended' }, { waitSeconds: 0, signal: bound.signal });
646
- return 'reported';
647
- }
648
- catch {
649
- return 'failed';
650
- }
651
- finally {
652
- clearTimeout(timer);
653
- }
654
- }
655
- //# sourceMappingURL=commands-hook-attend.js.map