@raidiant/notifai 11.7.1-beta.2 → 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 +56 -4
  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,900 +0,0 @@
1
- import { CAPABILITIES_V1, REPLY_MAX_WINDOW_SECONDS, validateDraft, } from '@raidiant/notifai-protocol';
2
- import { ApiCallError, NetworkError } from './client.js';
3
- import { MIN_REPLY_WINDOW_SECONDS } from './hook-lifecycle.js';
4
- import { inspectQuestionState } from './hook-question-state.js';
5
- import { selectedForClaimedDelivery } from './hook-question-retirement.js';
6
- import { readSessionState, recordSessionNotified } from './hook-session-state.js';
7
- import { enableProject, projectBinding } from './project-enablement.js';
8
- import { buildDraft, formatReceipt, receiptExitCode, rejectAccidentalEscapedNewlines, validateMediaInputs, } from './send.js';
9
- import { recordObservedDeliveryProof } from './commands-setup-proof.js';
10
- import { EXIT, authedClient, loadLoggedConfig, log, reportError, } from './commands-core.js';
11
- import { sourceContextHarnessSession } from './commands-harness-context.js';
12
- import { resolveCommandSession } from './command-session.js';
13
- import { activeOpenclawGeneration, openclawOwnsReply, recordOpenclawForegroundReply, } from './openclaw-session-access.js';
14
- import { currentProcessIdentity } from './process-identity.js';
15
- import { readDeliveryJournal, recordUnclaimedHandOffs } from './session-delivery.js';
16
- import { beginSendAttempt, semanticMediaIds, sendDraftFingerprint, settleSendAttempt, } from './send-attempts.js';
17
- import { acknowledgementCommand, printAcknowledgementStatus, resolveDraftInvocation, uploadImage, waitForReply, } from './commands-send-support.js';
18
- // ---------------------------------------------------------------------------
19
- // send / status
20
- // ---------------------------------------------------------------------------
21
- /**
22
- * Servers from before Notification Request contract fingerprints already
23
- * accepted Source Context, but did not persist label provenance. Omitting only
24
- * those server-only provenance markers preserves their exact label behavior;
25
- * every other contract difference remains an explicit service-skew failure.
26
- */
27
- function draftForServerCapabilities(draft, capabilities) {
28
- const source = draft.source;
29
- const acceptsLegacySource = capabilities.fields.some((field) => field.path === 'source' && field.status === 'supported');
30
- if (capabilities.notification_contract_fingerprint !== undefined ||
31
- !acceptsLegacySource ||
32
- source === undefined ||
33
- (source.session_label_source === undefined &&
34
- source.session_label_previous_source === undefined)) {
35
- return draft;
36
- }
37
- const legacySource = { ...source };
38
- delete legacySource.session_label_source;
39
- delete legacySource.session_label_previous_source;
40
- return { ...draft, source: legacySource };
41
- }
42
- export async function sendCommand(deps, flags) {
43
- if (flags.retry === true && flags.idempotencyKey !== undefined) {
44
- deps.io.err('Pass --retry or --idempotency-key, not both.');
45
- return EXIT.usage;
46
- }
47
- const hasChoice = Array.isArray(flags.choice)
48
- ? flags.choice.length > 0
49
- : flags.choice !== undefined;
50
- if (flags.reply && flags.kind !== undefined && flags.kind !== 'question') {
51
- deps.io.err(`--kind ${flags.kind} cannot be combined with --reply; a reply request is a question.`);
52
- return EXIT.usage;
53
- }
54
- // Kind now decides the sound a notification arrives with, so it is asked for
55
- // at the boundary rather than defaulted silently: an unlabelled send would
56
- // reach the user as ordinary news whatever actually happened.
57
- if (!flags.reply && flags.kind === undefined) {
58
- deps.io.err('--kind is required: say what this notification is. ' +
59
- 'update (news) · done (finished) · failed (terminal failure) · blocked (cannot proceed; no User reply would resume it). ' +
60
- '--reply makes it a question without --kind.');
61
- return EXIT.usage;
62
- }
63
- if (!flags.reply && flags.kind === 'question') {
64
- deps.io.err('--kind question requires --reply so the question can be answered.');
65
- return EXIT.usage;
66
- }
67
- if (!flags.reply &&
68
- (flags.replyTimeout !== undefined || flags.replyWindow !== undefined || hasChoice)) {
69
- deps.io.err('Use --reply with --reply-timeout, --reply-window, or --choice.');
70
- return EXIT.usage;
71
- }
72
- const replyTimeout = flags.replyTimeout ?? 900;
73
- if (flags.reply && !isNonNegativeInteger(replyTimeout)) {
74
- deps.io.err('--reply-timeout must be a non-negative integer number of seconds.');
75
- return EXIT.usage;
76
- }
77
- // Asking while declaring that nothing will wait for the answer. The reply is
78
- // captured server-side and then unreachable: only a blocking send waits for
79
- // it, and the hook path drains questions registered by `ask`, never a send's
80
- // request id. So the user gets a real button, taps it, and nothing happens —
81
- // worse than a banner that never asked, because it spends their attention
82
- // and their trust in the channel.
83
- if (flags.reply && replyTimeout === 0) {
84
- deps.io.err('A question needs someone to hear the answer, so --reply cannot be combined ' +
85
- 'with --reply-timeout 0.\n' +
86
- 'To ask and continue independent work, use `notifai ask` — Question Routing owns submission and the answer return path.\n' +
87
- 'To announce finished work, drop --reply and its choices.');
88
- return EXIT.usage;
89
- }
90
- if (flags.reply &&
91
- flags.replyWindow !== undefined &&
92
- (!Number.isInteger(flags.replyWindow) ||
93
- flags.replyWindow < MIN_REPLY_WINDOW_SECONDS ||
94
- flags.replyWindow > REPLY_MAX_WINDOW_SECONDS)) {
95
- deps.io.err(`--reply-window must be an integer from ${MIN_REPLY_WINDOW_SECONDS} to ${REPLY_MAX_WINDOW_SECONDS} seconds.`);
96
- return EXIT.usage;
97
- }
98
- const escapedBody = rejectAccidentalEscapedNewlines(flags.body, flags.literalBackslashN);
99
- if (escapedBody !== null) {
100
- deps.io.err(escapedBody);
101
- return EXIT.usage;
102
- }
103
- const mediaInputError = validateMediaInputs(flags.image, flags.imageAlt);
104
- if (mediaInputError !== null) {
105
- deps.io.err(mediaInputError);
106
- return EXIT.usage;
107
- }
108
- const config = loadLoggedConfig(deps, {
109
- cwd: deps.cwd,
110
- env: deps.env,
111
- flags: { base_url: flags.baseUrl, wait_seconds: flags.wait },
112
- });
113
- const activeSession = sourceContextHarnessSession(deps.env, deps.cwd, (deps.now ?? Date.now)());
114
- const source = resolveDraftInvocation(deps, flags, activeSession);
115
- if (!source.ok) {
116
- deps.io.err(source.error);
117
- return EXIT.usage;
118
- }
119
- let semanticImages;
120
- try {
121
- semanticImages = semanticMediaIds(flags.image ?? [], deps.cwd);
122
- }
123
- catch (err) {
124
- deps.io.err(`Could not fingerprint an image for safe retry: ${String(err)}`);
125
- return EXIT.usage;
126
- }
127
- const semanticBuild = buildDraft(config, { ...flags, ...(semanticImages.length > 0 ? { image: semanticImages } : {}) }, source.invocation);
128
- if (!semanticBuild.ok) {
129
- deps.io.err(semanticBuild.error);
130
- return EXIT.usage;
131
- }
132
- if (semanticBuild.draft.project !== undefined) {
133
- const binding = projectBinding(deps.cwd, deps.env, semanticBuild.draft.project);
134
- if (binding !== null)
135
- enableProject(binding);
136
- }
137
- const authed = authedClient(deps, config);
138
- if (!authed)
139
- return EXIT.auth;
140
- const credential = deps.store.load();
141
- const attempt = beginSendAttempt({
142
- env: deps.env,
143
- credential,
144
- fingerprint: sendDraftFingerprint(semanticBuild.draft, credential),
145
- retry: flags.retry === true,
146
- ...(flags.idempotencyKey === undefined ? {} : { idempotencyKey: flags.idempotencyKey }),
147
- now: (deps.now ?? Date.now)(),
148
- });
149
- if (!attempt.ok) {
150
- deps.io.err(`${attempt.code}: ${attempt.message}`);
151
- return EXIT.usage;
152
- }
153
- const mediaIds = [];
154
- for (const image of flags.image ?? []) {
155
- if (image.startsWith('med_')) {
156
- mediaIds.push(image);
157
- continue;
158
- }
159
- const uploaded = await uploadImage(deps, authed.client, image, config.media_origins.value);
160
- if (!uploaded.ok) {
161
- settleSendAttempt(deps.env, attempt.attemptId);
162
- if (uploaded.error !== null)
163
- deps.io.err(uploaded.error);
164
- return uploaded.exit;
165
- }
166
- mediaIds.push(uploaded.mediaId);
167
- }
168
- flags = { ...flags, image: mediaIds };
169
- const build = buildDraft(config, flags, source.invocation);
170
- if (!build.ok) {
171
- settleSendAttempt(deps.env, attempt.attemptId);
172
- deps.io.err(build.error);
173
- return EXIT.usage;
174
- }
175
- const capabilities = CAPABILITIES_V1.describe(build.platform);
176
- if (!capabilities) {
177
- settleSendAttempt(deps.env, attempt.attemptId);
178
- deps.io.err(`No capability contract is available for ${build.platform}.`);
179
- return EXIT.usage;
180
- }
181
- const validation = validateDraft(build.draft, capabilities);
182
- if (!validation.ok) {
183
- settleSendAttempt(deps.env, attempt.attemptId);
184
- for (const issue of validation.errors)
185
- deps.io.err(`${issue.path}: ${issue.message}`);
186
- return EXIT.usage;
187
- }
188
- for (const warning of validation.warnings) {
189
- deps.io.err(`Heads up (${warning.path}): ${warning.message}`);
190
- }
191
- let serverCapabilities;
192
- try {
193
- serverCapabilities = await authed.client.capabilities(build.platform);
194
- }
195
- catch (err) {
196
- settleSendAttempt(deps.env, attempt.attemptId);
197
- return reportError(deps, err);
198
- }
199
- const submissionDraft = draftForServerCapabilities(build.draft, serverCapabilities);
200
- const foregroundSession = flags.reply && activeSession?.sessionId !== undefined &&
201
- submissionDraft.source?.session_id === activeSession.sessionId
202
- ? activeSession.sessionId : null;
203
- const foregroundGeneration = foregroundSession === null ? null
204
- : activeOpenclawGeneration(foregroundSession, deps.env, deps.cwd);
205
- emitSendWarnings(deps, flags, config);
206
- if (!flags.reply &&
207
- (flags.title.trim().endsWith('?') || flags.summary.trim().endsWith('?'))) {
208
- deps.io.err('Heads up: this notification ends with a question but has no reply action. Add --reply (and optionally --choice) so it can be answered from the notification.');
209
- }
210
- const waitSeconds = flags.noWait ? 0 : config.wait_seconds.value;
211
- const idempotencyKey = attempt.idempotencyKey;
212
- // Persist the retry identity before entering the ambiguous network boundary.
213
- // If the process is killed after the server accepts but before a response,
214
- // this is the only durable way to reconstruct the same Agent Event key.
215
- log(deps).info('send.attempt', {
216
- attempt_id: attempt.attemptId,
217
- idempotency_key: idempotencyKey,
218
- replay: attempt.replay,
219
- });
220
- let receipt;
221
- try {
222
- receipt = await authed.client.submit({ idempotency_key: idempotencyKey, draft: submissionDraft }, waitSeconds);
223
- }
224
- catch (err) {
225
- const exit = reportError(deps, err);
226
- if (exit === EXIT.network) {
227
- deps.io.err('The server may have accepted this Notification Request before the connection failed. ' +
228
- 'Re-run the exact semantic send with `--retry`; the CLI will reuse its opaque attempt identity.');
229
- }
230
- if (err instanceof ApiCallError && err.code === 'no_active_devices') {
231
- deps.io.err('After setup succeeds, repeat this exact original Notification Request with `--retry`; ' +
232
- 'the setup verification request does not replace this Agent Event.');
233
- }
234
- if (!(exit === EXIT.network || (err instanceof ApiCallError && err.code === 'no_active_devices'))) {
235
- settleSendAttempt(deps.env, attempt.attemptId);
236
- }
237
- else {
238
- deps.io.err(`Opaque retry attempt: ${attempt.attemptId}. Re-run the exact semantic send with \`--retry\`; do not retry automatically.`);
239
- }
240
- return exit;
241
- }
242
- settleSendAttempt(deps.env, attempt.attemptId);
243
- if (foregroundSession !== null && foregroundGeneration !== null) {
244
- let recorded = false;
245
- try {
246
- recorded = recordOpenclawForegroundReply(foregroundSession, receipt.request_id, foregroundGeneration, deps.env, deps.cwd);
247
- }
248
- catch (err) {
249
- deps.io.err(`Could not retain ownership of ${receipt.request_id}: ${String(err)}`);
250
- }
251
- if (!recorded) {
252
- deps.io.err(`The creating OpenClaw generation can no longer own ${receipt.request_id}; inspect the submitted request before acting on a reply.`);
253
- if (flags.json)
254
- deps.io.out(JSON.stringify(unansweredReplyResultJson(receipt, false)));
255
- return EXIT.failed;
256
- }
257
- }
258
- const notifiedSession = submissionDraft.source?.session_id;
259
- if (notifiedSession !== undefined) {
260
- // Wakes this session's dormant Session Attendant.
261
- recordSessionNotified(notifiedSession, deps.env, (deps.now ?? Date.now)());
262
- }
263
- const receiptExit = receiptExitCode(receipt);
264
- // The single most useful line in the log: it ties the local invocation to
265
- // the server-side request id, which is what every later question about this
266
- // notification ("did it arrive?", "which device?") is asked in terms of.
267
- log(deps).info('send.submitted', {
268
- request_id: receipt.request_id,
269
- kind: flags.reply ? 'question' : (flags.kind ?? 'update'),
270
- title_chars: flags.title.length,
271
- overall: receipt.overall,
272
- replayed: receipt.replayed,
273
- agent_acknowledgement_required: receipt.agent_acknowledgement_required,
274
- deliveries: receipt.deliveries.length,
275
- wait_seconds: waitSeconds,
276
- exit: receiptExit,
277
- });
278
- log(deps).info('send.outcome', {
279
- request_id: receipt.request_id,
280
- overall: receipt.overall,
281
- // Per device, because "it said accepted but nothing arrived" is answered
282
- // by which device reached which state and why the provider said so.
283
- devices: receipt.deliveries.map((delivery) => ({
284
- device: delivery.device_name,
285
- state: delivery.state,
286
- attempts: delivery.attempts,
287
- provider_status: delivery.provider_status,
288
- provider_reason: delivery.provider_reason,
289
- })),
290
- ...(receipt.warnings.length > 0 ? { warnings: receipt.warnings } : {}),
291
- });
292
- if (!flags.json) {
293
- const quietOrdinarySuccess = !flags.reply &&
294
- receipt.overall === 'provider_accepted_all' &&
295
- receipt.warnings.length === 0;
296
- if (!quietOrdinarySuccess)
297
- deps.io.out(formatReceipt(receipt));
298
- }
299
- // A zero wait can no longer reach here: --reply guarantees a positive one.
300
- if (!flags.reply || receiptExit !== EXIT.ok) {
301
- if (flags.json) {
302
- deps.io.out(flags.reply
303
- ? JSON.stringify(unansweredReplyResultJson(receipt, false))
304
- : JSON.stringify(receipt, null, 2));
305
- }
306
- return receiptExit;
307
- }
308
- try {
309
- const result = await waitForReply(authed.client, receipt.request_id, {
310
- timeoutSeconds: replyTimeout,
311
- afterSeq: 0,
312
- now: deps.now,
313
- sleep: deps.sleep,
314
- });
315
- let response = result.response;
316
- if (response.replies.length > 0 && !result.degraded) {
317
- // The poll only says an answer exists. Close under the server's reply
318
- // fence before exposing an answer to the agent, so a correction racing
319
- // the close becomes the answer we actually hand off and can edit later.
320
- try {
321
- response = await authed.client.closeReplies(receipt.request_id, 'deliver');
322
- }
323
- catch (err) {
324
- const exit = reportError(deps, err, {
325
- operation: 'reply_delivery_close',
326
- request_id: receipt.request_id,
327
- });
328
- deps.io.err(`Could not confirm the answer handoff for ${receipt.request_id}. ` +
329
- 'No answer was returned; inspect the request before acting on it.');
330
- if (flags.json)
331
- deps.io.out(JSON.stringify(unansweredReplyResultJson(receipt, true)));
332
- return exit;
333
- }
334
- if (!selectedForClaimedDelivery(response)) {
335
- log(deps).error('cli.error', {
336
- kind: 'reply_delivery_unconfirmed',
337
- operation: 'reply_delivery_close',
338
- request_id: receipt.request_id,
339
- close_disposition: 'close_disposition' in response ? response.close_disposition : null,
340
- });
341
- deps.io.err(`Could not confirm the answer handoff for ${receipt.request_id}. ` +
342
- 'The question may have been closed elsewhere; no answer was returned.');
343
- if (flags.json)
344
- deps.io.out(JSON.stringify(unansweredReplyResultJson(receipt, true)));
345
- return EXIT.failed;
346
- }
347
- }
348
- recordReplies(deps, receipt.request_id, response.replies);
349
- if (flags.json) {
350
- deps.io.out(JSON.stringify(replyResultJson(response, result.degraded, receipt)));
351
- }
352
- else if (response.replies.length > 0) {
353
- printReplies(deps, response.replies);
354
- printAcknowledgementStatus(deps, response);
355
- }
356
- else {
357
- printNoReply(deps, receipt.request_id, response.reply_expires_at);
358
- printAcknowledgementStatus(deps, response);
359
- }
360
- if (response.replies.length > 0 && !result.degraded &&
361
- activeSession?.sessionId !== undefined && activeSession.sessionId === notifiedSession) {
362
- const handoffExit = await reportForegroundAnswer(deps, authed.client, activeSession.sessionId, receipt.request_id);
363
- if (handoffExit !== EXIT.ok)
364
- return handoffExit;
365
- }
366
- if (result.degraded) {
367
- log(deps).error('cli.error', {
368
- kind: 'network',
369
- operation: 'reply_wait',
370
- request_id: receipt.request_id,
371
- degraded: true,
372
- message: 'the reply wait ended while the server was unreachable or faulting',
373
- });
374
- deps.io.err(degradedWaitWarning(receipt.request_id));
375
- return EXIT.network;
376
- }
377
- if (result.timedOut) {
378
- deps.io.err(`No reply yet. Retrieve it with \`notifai replies ${receipt.request_id}\` or retire the question with ` +
379
- `\`notifai close ${receipt.request_id}\`. ` +
380
- `This is a reply-wait timeout, not a Delivery or Companion Receipt failure — check with \`notifai status ${receipt.request_id}\`.`);
381
- }
382
- return result.timedOut ? EXIT.noReply : EXIT.ok;
383
- }
384
- catch (err) {
385
- // The send already succeeded durably; a wait fault must not read as "send
386
- // failed". Permanent poll errors (auth, closed window, not found) still
387
- // surface, but always name the durable request and point at recovery.
388
- if (err instanceof ApiCallError || err instanceof NetworkError) {
389
- log(deps).error('cli.error', {
390
- kind: err instanceof ApiCallError ? 'api' : 'network',
391
- operation: 'reply_wait',
392
- request_id: receipt.request_id,
393
- ...(err instanceof ApiCallError ? { status: err.status, code: err.code } : {}),
394
- message: err.message,
395
- });
396
- deps.io.err(`notifai: reply wait failed for ${receipt.request_id} (${err instanceof ApiCallError ? err.code : 'network'}: ${err.message}). ` +
397
- `Delivery and Companion Receipt are independent — check with \`notifai status ${receipt.request_id}\` and retry with \`notifai replies ${receipt.request_id}\`.`);
398
- // JSON callers still get their one object: the durable receipt, no
399
- // replies, and `degraded: true` — "could not find out", not "no answer".
400
- if (flags.json)
401
- deps.io.out(JSON.stringify(unansweredReplyResultJson(receipt, true)));
402
- if (err instanceof ApiCallError) {
403
- if (err.code === 'auth_required' || err.code === 'machine_revoked')
404
- return EXIT.auth;
405
- return err.status >= 500 || err.status === 429 || err.status === 408
406
- ? EXIT.network
407
- : EXIT.failed;
408
- }
409
- return EXIT.network;
410
- }
411
- return reportError(deps, err);
412
- }
413
- }
414
- function emitSendWarnings(deps, flags, config) {
415
- if (flags.title.length > 40) {
416
- deps.io.err(`Heads up: this title is ${flags.title.length} characters; notification titles work best around 40 characters or fewer.`);
417
- }
418
- if (/^(?:update|done|question|failed|blocked)\s*(?:[·:—-]|$)/i.test(flags.title.trim())) {
419
- deps.io.err('Heads up: keep the title to the specific substance. Put notification type in --kind; Project identity is inferred separately.');
420
- }
421
- if (flags.collapseKey === undefined &&
422
- config.collapse_key.value !== null &&
423
- config.collapse_key.source.startsWith('global:')) {
424
- deps.io.err('Heads up: collapse_key comes from machine-global config, so unrelated notifications may replace each other. Prefer a project or command-specific --collapse-key.');
425
- }
426
- if (flags.ttl !== undefined && flags.ttl > 259_200) {
427
- deps.io.err('Heads up: this explicit --ttl is longer than 72 hours; stale notifications may arrive after they are useful.');
428
- }
429
- }
430
- export async function repliesCommand(deps, requestedId, flags) {
431
- if (deps.env['NOTIFAI_ACTIVE_HARNESS'] === 'openclaw') {
432
- const sessionKey = deps.env['NOTIFAI_ACTIVE_SESSION_ID'] ?? '';
433
- if (activeOpenclawGeneration(sessionKey, deps.env, deps.cwd) === null ||
434
- (requestedId !== undefined && !openclawOwnsReply(sessionKey, requestedId, deps.env, deps.cwd))) {
435
- deps.io.err('This reply pointer does not belong to the current OpenClaw session generation.');
436
- return EXIT.usage;
437
- }
438
- }
439
- const waitSeconds = flags.wait ?? 0;
440
- const afterSeq = flags.after ?? 0;
441
- if (!isNonNegativeInteger(waitSeconds)) {
442
- deps.io.err('--wait must be a non-negative integer number of seconds.');
443
- return EXIT.usage;
444
- }
445
- if (!isNonNegativeInteger(afterSeq)) {
446
- deps.io.err('--after must be a non-negative integer sequence number.');
447
- return EXIT.usage;
448
- }
449
- if (flags.pending === true && requestedId !== undefined) {
450
- deps.io.err('Pass a request id or --pending, not both.');
451
- return EXIT.usage;
452
- }
453
- if (flags.handoff === true) {
454
- if (requestedId === undefined || flags.pending === true || waitSeconds !== 0 || afterSeq !== 0) {
455
- deps.io.err('--handoff needs one request id without --pending, --wait, or --after.');
456
- return EXIT.usage;
457
- }
458
- const active = sourceContextHarnessSession(deps.env, deps.cwd, (deps.now ?? Date.now)());
459
- if (active?.sessionId === undefined) {
460
- deps.io.err('--handoff needs an unambiguous exact Agent Session in this harness.');
461
- return EXIT.usage;
462
- }
463
- const config = loadLoggedConfig(deps, { cwd: deps.cwd, env: deps.env, sessionId: active.sessionId });
464
- const authed = authedClient(deps, config);
465
- if (!authed)
466
- return EXIT.auth;
467
- try {
468
- const response = await authed.client.closeReplies(requestedId, 'deliver');
469
- if (!selectedForClaimedDelivery(response)) {
470
- deps.io.err(`No selected answer for ${requestedId} can be handed to this Agent Session.`);
471
- return EXIT.failed;
472
- }
473
- recordReplies(deps, requestedId, response.replies);
474
- if (flags.json)
475
- deps.io.out(JSON.stringify(replyResultJson(response, false)));
476
- else {
477
- printReplies(deps, response.replies);
478
- printAcknowledgementStatus(deps, response);
479
- }
480
- return await reportForegroundAnswer(deps, authed.client, active.sessionId, requestedId);
481
- }
482
- catch (err) {
483
- return reportError(deps, err, { operation: 'reply_handoff', request_id: requestedId });
484
- }
485
- }
486
- let requestIds = requestedId === undefined ? [] : [requestedId];
487
- let lifecycleSessionId;
488
- if (flags.pending === true) {
489
- const session = resolveCommandSession(deps);
490
- if (session === null) {
491
- deps.io.err('No active session pointer is available in this directory.');
492
- return EXIT.noReply;
493
- }
494
- const sessionId = session.sessionId;
495
- lifecycleSessionId = sessionId;
496
- // Every delivered question in the session's queue, in registration order —
497
- // an agent may have several outstanding at once.
498
- const state = readSessionState(sessionId, deps.env);
499
- const pending = Array.isArray(state.pending) ? state.pending : [];
500
- requestIds = pending
501
- .map((entry) => entry.request_id)
502
- .filter((id) => id !== undefined);
503
- const unsent = pending.filter((entry) => entry.request_id === undefined);
504
- if (requestIds.length === 0) {
505
- // `--json` is a promise about stdout, and an empty result is still a
506
- // result: a caller that asked for machine-readable output must not have
507
- // to parse an English sentence to learn there was nothing pending.
508
- if (flags.json === true) {
509
- deps.io.out(JSON.stringify({
510
- session_id: sessionId,
511
- pending: unsent.map((entry) => ({
512
- question: entry.question,
513
- ...(entry.question_id === undefined ? {} : { question_id: entry.question_id }),
514
- })),
515
- replies: [],
516
- }, null, 2));
517
- }
518
- else if (unsent.length > 0) {
519
- for (const entry of unsent) {
520
- deps.io.out(entry.question_id === undefined
521
- ? `unpushed question: ${entry.question}`
522
- : `unpushed question ${entry.question_id}: ${entry.question}`);
523
- }
524
- deps.io.out('Retire one with `notifai close <question_id>` so a later Stop will not send it.');
525
- }
526
- else {
527
- deps.io.err(`Session ${sessionId} has no pushed question pending.`);
528
- }
529
- return unsent.length > 0 ? EXIT.ok : EXIT.noReply;
530
- }
531
- if (unsent.length > 0 && flags.json !== true) {
532
- for (const entry of unsent) {
533
- deps.io.out(entry.question_id === undefined
534
- ? `unpushed question: ${entry.question}`
535
- : `unpushed question ${entry.question_id}: ${entry.question}`);
536
- }
537
- }
538
- }
539
- if (requestIds.length === 0) {
540
- deps.io.err('Pass a request id or --pending.');
541
- return EXIT.usage;
542
- }
543
- const config = loadLoggedConfig(deps, {
544
- cwd: deps.cwd,
545
- env: deps.env,
546
- ...(lifecycleSessionId === undefined ? {} : { sessionId: lifecycleSessionId }),
547
- });
548
- const authed = authedClient(deps, config);
549
- if (!authed)
550
- return EXIT.auth;
551
- try {
552
- let anyReplies = false;
553
- const degradedRequestIds = [];
554
- let allTimedOut = true;
555
- const jsonBodies = [];
556
- for (const requestId of requestIds) {
557
- try {
558
- const result = await waitForReply(authed.client, requestId, {
559
- timeoutSeconds: waitSeconds,
560
- afterSeq,
561
- now: deps.now,
562
- sleep: deps.sleep,
563
- });
564
- recordReplies(deps, requestId, result.response.replies);
565
- if (flags.json) {
566
- jsonBodies.push(replyResultJson(result.response, result.degraded));
567
- }
568
- else if (result.response.replies.length > 0) {
569
- if (flags.pending === true)
570
- deps.io.out(`pending request ${requestId}`);
571
- printReplies(deps, result.response.replies);
572
- printAcknowledgementStatus(deps, result.response);
573
- }
574
- else {
575
- printNoReply(deps, requestId, result.response.reply_expires_at);
576
- printAcknowledgementStatus(deps, result.response);
577
- }
578
- anyReplies ||= result.response.replies.length > 0;
579
- if (result.degraded)
580
- degradedRequestIds.push(requestId);
581
- allTimedOut &&= result.timedOut;
582
- }
583
- catch (err) {
584
- recordDegradedReplyWaits(deps, degradedRequestIds);
585
- return reportError(deps, err, { operation: 'reply_wait', request_id: requestId });
586
- }
587
- }
588
- if (flags.json) {
589
- // One request keeps the response shape agents already parse; several
590
- // (only possible via --pending) arrive as an array in queue order.
591
- deps.io.out(JSON.stringify(jsonBodies.length === 1 ? jsonBodies[0] : jsonBodies, null, 2));
592
- }
593
- if (degradedRequestIds.length > 0) {
594
- recordDegradedReplyWaits(deps, degradedRequestIds);
595
- // Name a request whose polls actually degraded, not merely the first item
596
- // in a multi-request queue that may have completed cleanly.
597
- deps.io.err(degradedWaitWarning(degradedRequestIds[0]));
598
- return EXIT.network;
599
- }
600
- if (anyReplies)
601
- return EXIT.ok;
602
- return allTimedOut ? EXIT.noReply : EXIT.ok;
603
- }
604
- catch (err) {
605
- return reportError(deps, err);
606
- }
607
- }
608
- /** The stdout write is the foreground harness hand-off; report only afterwards. */
609
- async function reportForegroundAnswer(deps, client, sessionId, requestId) {
610
- const writer = currentProcessIdentity();
611
- if (writer === null) {
612
- deps.io.err(`Answer printed for ${requestId}, but its hand-off could not be journaled for recovery.`);
613
- return EXIT.failed;
614
- }
615
- let recorded;
616
- try {
617
- recorded = await recordUnclaimedHandOffs({
618
- sessionId,
619
- env: deps.env,
620
- client,
621
- monotonic: () => performance.now(),
622
- wall: deps.now ?? Date.now,
623
- sleep: deps.sleep ?? ((milliseconds) => new Promise((resolve) => setTimeout(resolve, milliseconds))),
624
- writer,
625
- log: log(deps),
626
- }, [requestId]);
627
- }
628
- catch (err) {
629
- deps.io.err(`Answer printed for ${requestId}, but its hand-off journal failed: ${String(err)}. Re-present it with \`notifai replies ${requestId} --handoff\` in this Agent Session.`);
630
- return EXIT.failed;
631
- }
632
- if (!recorded) {
633
- const entry = readDeliveryJournal(sessionId, deps.env).find((candidate) => candidate.attempt_id === `unclaimed:${requestId}`);
634
- deps.io.err(entry?.reported === 'released'
635
- ? `Answer printed for ${requestId}, but the service refused its hand-off report (${entry.refused ?? 'unknown reason'}).`
636
- : `Answer printed for ${requestId}; its hand-off report is pending journal recovery.`);
637
- return entry?.reported === 'released' ? EXIT.failed : EXIT.network;
638
- }
639
- return EXIT.ok;
640
- }
641
- function recordDegradedReplyWaits(deps, requestIds) {
642
- if (requestIds.length === 0)
643
- return;
644
- log(deps).error('cli.error', {
645
- kind: 'network',
646
- operation: 'reply_wait',
647
- request_ids: requestIds,
648
- degraded: true,
649
- message: 'the reply wait ended while the server was unreachable or faulting',
650
- });
651
- }
652
- /**
653
- * Shared by every surface that waits: "the user did not answer" and "I could
654
- * not find out" must not look the same, because agents branch on the exit code
655
- * and one of those two branches is safe to proceed from.
656
- *
657
- * Delivery / Companion Receipt / OS presentation are separate facts — a wait
658
- * fault is not evidence that the push failed.
659
- */
660
- function degradedWaitWarning(requestId) {
661
- return (`notifai: the wait for ${requestId} ended while the server was unreachable or faulting, ` +
662
- `so this is "could not find out", not "no answer" — the request is still durable and the ` +
663
- `reply may already be waiting (Provider Acceptance and Companion Receipt are independent). ` +
664
- `Retry with \`notifai replies ${requestId}\`.`);
665
- }
666
- function isNonNegativeInteger(value) {
667
- return Number.isInteger(value) && value >= 0;
668
- }
669
- /**
670
- * The one JSON object `send --reply --json` prints, whatever happens after the
671
- * durable submit. Embedding the receipt keeps stdout a single parseable line;
672
- * `replies` (from a wait) omits it because there is no submission to report.
673
- */
674
- function replyResultJson(response, degraded, receipt) {
675
- return {
676
- type: 'reply_result',
677
- ...(receipt !== undefined ? { receipt } : {}),
678
- request_id: response.request_id,
679
- reply_expires_at: response.reply_expires_at,
680
- replies: response.replies,
681
- agent_acknowledgement_required: response.replies.length > 0 || response.agent_acknowledgement_required,
682
- agent_acknowledgement: response.agent_acknowledgement,
683
- agent_acknowledgement_text_required: response.agent_acknowledgement_text_required,
684
- acknowledgement_command: acknowledgementCommand(response.request_id, response.agent_acknowledgement_text_required, response.agent_acknowledgement, response.replies.length > 0),
685
- degraded,
686
- };
687
- }
688
- /**
689
- * The reply_result for a reply send that has no answer to report — a receipt
690
- * failure, or a wait that faulted. Same shape, same single line; `degraded`
691
- * distinguishes "the wait could not find out" from "delivery already failed".
692
- */
693
- function unansweredReplyResultJson(receipt, degraded) {
694
- return {
695
- type: 'reply_result',
696
- receipt,
697
- request_id: receipt.request_id,
698
- replies: [],
699
- agent_acknowledgement_required: receipt.agent_acknowledgement_required,
700
- agent_acknowledgement_text_required: receipt.agent_acknowledgement_text_required,
701
- agent_acknowledgement: null,
702
- acknowledgement_command: null,
703
- degraded,
704
- };
705
- }
706
- function recordReplies(deps, requestId, replies) {
707
- const logger = log(deps);
708
- for (const reply of replies) {
709
- logger.info('reply.received', {
710
- request_id: requestId,
711
- sequence: reply.seq,
712
- device: reply.device_name,
713
- text_chars: reply.text.length,
714
- answers_count: reply.answers?.length ?? 0,
715
- source: reply.source,
716
- });
717
- }
718
- }
719
- function printReplies(deps, replies) {
720
- for (const reply of replies)
721
- deps.io.out(`reply from ${reply.device_name}: ${reply.text}`);
722
- const contradiction = contradictingAnswer(replies);
723
- if (contradiction !== null)
724
- deps.io.err(contradiction);
725
- }
726
- /**
727
- * The correction note for a wait that saw more than one answer.
728
- *
729
- * A later reply that conflicts with an earlier one is a correction, so the
730
- * latest answer is the one that counts. The server retires the question on
731
- * the other devices the moment one answers, which makes this rare; it is
732
- * still reachable, because a device can answer between the first reply
733
- * landing and its retirement arriving.
734
- *
735
- * Ordered by seq, so `replies.at(-1)` is the answer to act on.
736
- */
737
- export function contradictingAnswer(replies) {
738
- const winner = replies.at(-1);
739
- if (winner === undefined || replies.length < 2)
740
- return null;
741
- const key = (reply) => reply.answers.length > 0 ? JSON.stringify(reply.answers) : reply.text;
742
- const corrected = replies.slice(0, -1).filter((reply) => key(reply) !== key(winner));
743
- if (corrected.length === 0)
744
- return null;
745
- const names = [...new Set(corrected.map((reply) => reply.device_name))].join(', ');
746
- return (`note: "${winner.text}" from ${winner.device_name} is the answer that counts — a later ` +
747
- `answer corrects an earlier one. Earlier differing answers from ${names} were superseded.`);
748
- }
749
- function printNoReply(deps, requestId, expiresAt) {
750
- // A harness hook may have retired this question, in which case promising an
751
- // open window would send the caller back to wait for an answer the server
752
- // will now refuse.
753
- const closed = expiresAt != null && Date.parse(expiresAt) <= Date.now();
754
- deps.io.out(closed
755
- ? `no reply for request ${requestId}; the reply window has closed`
756
- : `no reply yet for request ${requestId}; the reply window remains open`);
757
- }
758
- export async function statusCommand(deps, id, flags) {
759
- const local = id.startsWith('q_') ? inspectQuestionState(id, deps.env) : null;
760
- if (local?.found === true) {
761
- return questionStatusCommand(deps, local.session_id, local.question, flags);
762
- }
763
- if (local?.found === false) {
764
- deps.io.err(local.ambiguous
765
- ? `Question ${id} appears in more than one local Agent Session; refusing to guess.`
766
- : `No local registration state exists for ${id}. Inspect or close the original identity instead of registering a replacement.`);
767
- return EXIT.failed;
768
- }
769
- const config = loadLoggedConfig(deps, { cwd: deps.cwd, env: deps.env });
770
- const authed = authedClient(deps, config);
771
- if (!authed)
772
- return EXIT.auth;
773
- try {
774
- const snapshot = await authed.client.evidence(id);
775
- recordObservedDeliveryProof(deps, snapshot);
776
- if (flags.json) {
777
- deps.io.out(JSON.stringify(snapshot, null, 2));
778
- return EXIT.ok;
779
- }
780
- printEvidenceSnapshot(deps, snapshot);
781
- return EXIT.ok;
782
- }
783
- catch (err) {
784
- return reportError(deps, err);
785
- }
786
- }
787
- async function questionStatusCommand(deps, sessionId, local, flags) {
788
- if (local.request_id === null) {
789
- const output = questionStatusOutput(local, null);
790
- if (flags.json)
791
- deps.io.out(JSON.stringify(output, null, 2));
792
- else
793
- printLocalQuestionStatus(deps, local);
794
- return EXIT.ok;
795
- }
796
- const config = loadLoggedConfig(deps, { cwd: deps.cwd, env: deps.env, sessionId });
797
- const authed = authedClient(deps, config);
798
- if (!authed)
799
- return EXIT.auth;
800
- try {
801
- const snapshot = await authed.client.evidence(local.request_id);
802
- recordObservedDeliveryProof(deps, snapshot);
803
- const answered = snapshot.deliveries.some((delivery) => delivery.events.some((event) => event.stage === 'reply_received'));
804
- const question = answered && local.state === 'live'
805
- ? { ...local, state: 'answered' }
806
- : local;
807
- if (flags.json) {
808
- deps.io.out(JSON.stringify(questionStatusOutput(question, snapshot), null, 2));
809
- return EXIT.ok;
810
- }
811
- deps.io.out(`question ${question.question_id} — ${question.state} as ${question.request_id}`);
812
- printEvidenceSnapshot(deps, snapshot);
813
- deps.io.out(` Recovery: inspect or close ${question.question_id}; do not register a replacement for the same question.`);
814
- return EXIT.ok;
815
- }
816
- catch (err) {
817
- return reportError(deps, err, { question_id: local.question_id, request_id: local.request_id });
818
- }
819
- }
820
- function questionStatusOutput(local, evidence) {
821
- const receipt = evidence === null
822
- ? 'not_available'
823
- : evidence.deliveries.some((delivery) => delivery.companion_receipt.state === 'observed')
824
- ? 'observed'
825
- : 'unknown';
826
- return {
827
- question_id: local.question_id,
828
- state: local.state,
829
- submitted: local.submitted,
830
- request_id: local.request_id,
831
- frozen_request_id: local.frozen_request_id,
832
- provider_acceptance: evidence?.overall ?? 'not_available',
833
- companion_receipt: receipt,
834
- evidence,
835
- recovery: {
836
- inspect: `notifai status ${local.question_id}`,
837
- close: `notifai close ${local.question_id}`,
838
- register_replacement: false,
839
- },
840
- };
841
- }
842
- function printLocalQuestionStatus(deps, local) {
843
- deps.io.out(`question ${local.question_id} — ${local.state}`);
844
- if (local.state === 'local') {
845
- deps.io.out(' Submission: not submitted — this is only a local registration until question settlement runs');
846
- deps.io.out(' Provider Acceptance: not available — no Notification Request exists yet');
847
- }
848
- else if (local.state === 'frozen') {
849
- deps.io.out(` Submission: frozen under reserved identity ${local.frozen_request_id}; service acceptance is not proven locally`);
850
- deps.io.out(' Provider Acceptance: not available until the frozen identity is promoted locally');
851
- }
852
- else if (local.state === 'withdrawn') {
853
- deps.io.out(' Submission: not submitted — the local registration was withdrawn');
854
- deps.io.out(' Provider Acceptance: not available — no Notification Request was created');
855
- }
856
- else {
857
- deps.io.out(' Submission: retired before local promotion; service acceptance is not proven locally');
858
- deps.io.out(' Provider Acceptance: not available');
859
- }
860
- deps.io.out(` Recovery: inspect or close ${local.question_id}; do not register a replacement for the same question.`);
861
- }
862
- function printEvidenceSnapshot(deps, snapshot) {
863
- deps.io.out(`request ${snapshot.request_id} — ${snapshot.overall}`);
864
- let anyReplyReceived = false;
865
- for (const d of snapshot.deliveries) {
866
- deps.io.out(` ${d.device_name}:`);
867
- deps.io.out(` Delivery: ${d.state} after ${d.attempts} attempt(s)`);
868
- deps.io.out(` Provider Acceptance: ${d.state === 'provider_accepted' ? 'accepted' : 'not recorded'}`);
869
- if (d.companion_receipt.state === 'observed') {
870
- const latency = d.companion_receipt.latency_ms;
871
- deps.io.out(` Companion Receipt (the app's delivery confirmation): observed at ${d.companion_receipt.observed_at}` +
872
- (latency === null ? '' : ` (${formatElapsed(latency)} after Provider Acceptance)`));
873
- }
874
- else {
875
- deps.io.out(" Companion Receipt (the app's delivery confirmation): unknown — not observed; this is not a failure or proof of non-receipt");
876
- }
877
- deps.io.out(' OS presentation: not observed by Notifai — Provider Acceptance and Companion Receipt do not prove a banner was shown');
878
- const replyEvent = d.events.find((e) => e.stage === 'reply_received');
879
- if (replyEvent) {
880
- anyReplyReceived = true;
881
- deps.io.out(` Reply received: yes (first at ${replyEvent.occurred_at})`);
882
- }
883
- else {
884
- deps.io.out(' Reply received: not yet recorded on this delivery');
885
- }
886
- for (const e of d.events) {
887
- deps.io.out(` ${e.occurred_at} ${e.stage}${e.reason ? ` (${e.reason})` : ''}`);
888
- }
889
- }
890
- deps.io.out(anyReplyReceived
891
- ? ` Reply wait: answers are on the server — collect with \`notifai replies ${snapshot.request_id}\` (a local wait fault does not erase them)`
892
- : ` Reply wait: no answer stored yet — a blocking wait failure is independent of Delivery above; retry with \`notifai replies ${snapshot.request_id}\``);
893
- }
894
- function formatElapsed(milliseconds) {
895
- const totalSeconds = Math.floor(milliseconds / 1_000);
896
- const minutes = Math.floor(totalSeconds / 60);
897
- const seconds = totalSeconds % 60;
898
- return minutes === 0 ? `${seconds}s` : `${minutes}m ${seconds}s`;
899
- }
900
- //# sourceMappingURL=commands-send.js.map