@hraness/ghostget 0.17.2

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 (500) hide show
  1. package/CHANGELOG.md +458 -0
  2. package/LICENSE +21 -0
  3. package/README.md +1359 -0
  4. package/bunfig.toml +2 -0
  5. package/dist/apple-photos-client.js +704 -0
  6. package/dist/beeper-client.js +4602 -0
  7. package/dist/client.js +1605 -0
  8. package/dist/index-26yq8q16.js +31 -0
  9. package/dist/index-9t13b4k7.js +508 -0
  10. package/dist/index-axwm9b3v.js +5 -0
  11. package/dist/index-gwk7rbyj.js +30 -0
  12. package/dist/index.js +701 -0
  13. package/dist/messaging.js +1130 -0
  14. package/dist/omni-client.js +1406 -0
  15. package/dist/whatsapp-client.js +355 -0
  16. package/docs/imessage-direct-provider.md +106 -0
  17. package/docs/rental-listings.md +94 -0
  18. package/package.json +600 -0
  19. package/skills/ghostget/SKILL.md +251 -0
  20. package/skills/ghostget/agents/openai.yaml +4 -0
  21. package/skills/ghostget/references/adapter-contract.md +181 -0
  22. package/skills/ghostget/references/apple-photos.md +115 -0
  23. package/skills/ghostget/references/article-drafts.md +235 -0
  24. package/skills/ghostget/references/code-owned-provider-scaffold.md +134 -0
  25. package/skills/ghostget/references/cross-posting-video.md +139 -0
  26. package/skills/ghostget/references/cross-posting.md +82 -0
  27. package/skills/ghostget/references/derivation.md +390 -0
  28. package/skills/ghostget/references/hraness-social-profile-stats.json +269 -0
  29. package/skills/ghostget/references/install.md +66 -0
  30. package/skills/ghostget/references/linkedin-adapter.md +442 -0
  31. package/skills/ghostget/references/messaging.md +168 -0
  32. package/skills/ghostget/references/meta-comet-contract.md +153 -0
  33. package/skills/ghostget/references/platform-patterns.md +122 -0
  34. package/skills/ghostget/references/provider-plugins.md +496 -0
  35. package/skills/ghostget/references/reddit-flair.md +78 -0
  36. package/skills/ghostget/references/safety-and-state.md +214 -0
  37. package/skills/ghostget/references/settlement-and-duplicate-cleanup.md +79 -0
  38. package/skills/ghostget/references/social-platform-routing.md +66 -0
  39. package/skills/ghostget/references/social-profile-stats.md +195 -0
  40. package/skills/ghostget/references/social-video-platform-routing.md +89 -0
  41. package/skills/ghostget/references/whatsapp-adapter.md +101 -0
  42. package/skills/ghostget/references/x-adapter.md +332 -0
  43. package/skills/ghostget/references/x-ai-disclosure.md +71 -0
  44. package/src/apple-photos-cli.ts +639 -0
  45. package/src/apple-photos-client-types.ts +168 -0
  46. package/src/apple-photos-client.ts +201 -0
  47. package/src/apple-photos-contact-evidence.ts +838 -0
  48. package/src/apple-photos-local-source.ts +1131 -0
  49. package/src/args.ts +2053 -0
  50. package/src/article-draft-document.ts +417 -0
  51. package/src/article-draft-embeds.ts +123 -0
  52. package/src/article-draft-images.ts +214 -0
  53. package/src/assets/adapter-template/wrench-adapter.json +40 -0
  54. package/src/assets/adapters/beeper/wrench-web-adapter.json +1500 -0
  55. package/src/assets/adapters/beeper/wrench-web-adapter.v1.0.0.json +129 -0
  56. package/src/assets/adapters/beeper/wrench-web-adapter.v1.1.0.json +207 -0
  57. package/src/assets/adapters/beeper/wrench-web-adapter.v2.0.0.json +1463 -0
  58. package/src/assets/adapters/beeper/wrench-web-adapter.v2.1.0.json +1463 -0
  59. package/src/assets/adapters/beeper/wrench-web-adapter.v2.2.0.json +1475 -0
  60. package/src/assets/adapters/beeper/wrench-web-adapter.v2.3.0.json +1488 -0
  61. package/src/assets/adapters/bluesky/wrench-web-adapter.json +684 -0
  62. package/src/assets/adapters/bluesky/wrench-web-adapter.v1.0.0.json +581 -0
  63. package/src/assets/adapters/bluesky/wrench-web-adapter.v1.1.0.json +581 -0
  64. package/src/assets/adapters/bluesky/wrench-web-adapter.v1.2.0.json +581 -0
  65. package/src/assets/adapters/bluesky/wrench-web-adapter.v1.3.0.json +615 -0
  66. package/src/assets/adapters/bluesky/wrench-web-adapter.v1.4.0.json +684 -0
  67. package/src/assets/adapters/bluesky/wrench-web-adapter.v1.5.0.json +684 -0
  68. package/src/assets/adapters/bluesky/wrench-web-adapter.v1.6.0.json +684 -0
  69. package/src/assets/adapters/clasificados/wrench-web-adapter.json +54 -0
  70. package/src/assets/adapters/facebook/wrench-web-adapter.json +750 -0
  71. package/src/assets/adapters/facebook/wrench-web-adapter.v1.0.0.json +706 -0
  72. package/src/assets/adapters/facebook/wrench-web-adapter.v1.1.0.json +703 -0
  73. package/src/assets/adapters/facebook/wrench-web-adapter.v1.2.0.json +750 -0
  74. package/src/assets/adapters/facebook-group/wrench-web-adapter.json +682 -0
  75. package/src/assets/adapters/facebook-group/wrench-web-adapter.v1.0.0.json +583 -0
  76. package/src/assets/adapters/facebook-group/wrench-web-adapter.v1.1.0.json +682 -0
  77. package/src/assets/adapters/facebook-marketplace/wrench-web-adapter.json +437 -0
  78. package/src/assets/adapters/facebook-marketplace/wrench-web-adapter.v1.0.0.json +383 -0
  79. package/src/assets/adapters/facebook-marketplace/wrench-web-adapter.v1.1.0.json +437 -0
  80. package/src/assets/adapters/facebook-marketplace/wrench-web-adapter.v1.1.1.json +437 -0
  81. package/src/assets/adapters/facebook-page/wrench-web-adapter.json +880 -0
  82. package/src/assets/adapters/facebook-page/wrench-web-adapter.v1.0.0.json +740 -0
  83. package/src/assets/adapters/facebook-page/wrench-web-adapter.v1.1.0.json +880 -0
  84. package/src/assets/adapters/github/wrench-web-adapter.json +69 -0
  85. package/src/assets/adapters/github/wrench-web-adapter.v1.0.0.json +42 -0
  86. package/src/assets/adapters/github/wrench-web-adapter.v1.1.0.json +69 -0
  87. package/src/assets/adapters/gmail/wrench-adapter.json +161 -0
  88. package/src/assets/adapters/gmail/wrench-adapter.v1.2.0.json +157 -0
  89. package/src/assets/adapters/hacker-news/wrench-web-adapter.json +315 -0
  90. package/src/assets/adapters/hacker-news/wrench-web-adapter.v1.0.0.json +315 -0
  91. package/src/assets/adapters/imessage/wrench-web-adapter.json +236 -0
  92. package/src/assets/adapters/instagram/wrench-web-adapter.json +719 -0
  93. package/src/assets/adapters/instagram/wrench-web-adapter.v1.0.0.json +627 -0
  94. package/src/assets/adapters/instagram/wrench-web-adapter.v1.1.0.json +609 -0
  95. package/src/assets/adapters/instagram/wrench-web-adapter.v1.2.0.json +640 -0
  96. package/src/assets/adapters/instagram/wrench-web-adapter.v1.3.0.json +671 -0
  97. package/src/assets/adapters/instagram/wrench-web-adapter.v1.4.0.json +710 -0
  98. package/src/assets/adapters/instagram/wrench-web-adapter.v1.5.0.json +710 -0
  99. package/src/assets/adapters/instagram/wrench-web-adapter.v1.6.0.json +719 -0
  100. package/src/assets/adapters/instagram/wrench-web-adapter.v1.7.0.json +719 -0
  101. package/src/assets/adapters/linkedin/wrench-adapter.json +442 -0
  102. package/src/assets/adapters/linkedin/wrench-adapter.v0.4.0.json +124 -0
  103. package/src/assets/adapters/linkedin/wrench-adapter.v1.0.0.json +411 -0
  104. package/src/assets/adapters/linkedin/wrench-web-adapter.json +914 -0
  105. package/src/assets/adapters/linkedin/wrench-web-adapter.v1.0.0.json +604 -0
  106. package/src/assets/adapters/linkedin/wrench-web-adapter.v1.1.0.json +604 -0
  107. package/src/assets/adapters/linkedin/wrench-web-adapter.v1.10.0.json +809 -0
  108. package/src/assets/adapters/linkedin/wrench-web-adapter.v1.11.0.json +809 -0
  109. package/src/assets/adapters/linkedin/wrench-web-adapter.v1.12.0.json +820 -0
  110. package/src/assets/adapters/linkedin/wrench-web-adapter.v1.13.0.json +819 -0
  111. package/src/assets/adapters/linkedin/wrench-web-adapter.v1.14.0.json +819 -0
  112. package/src/assets/adapters/linkedin/wrench-web-adapter.v1.15.0.json +819 -0
  113. package/src/assets/adapters/linkedin/wrench-web-adapter.v1.16.0.json +874 -0
  114. package/src/assets/adapters/linkedin/wrench-web-adapter.v1.17.0.json +874 -0
  115. package/src/assets/adapters/linkedin/wrench-web-adapter.v1.18.0.json +874 -0
  116. package/src/assets/adapters/linkedin/wrench-web-adapter.v1.19.0.json +874 -0
  117. package/src/assets/adapters/linkedin/wrench-web-adapter.v1.2.0.json +731 -0
  118. package/src/assets/adapters/linkedin/wrench-web-adapter.v1.3.0.json +768 -0
  119. package/src/assets/adapters/linkedin/wrench-web-adapter.v1.4.0.json +808 -0
  120. package/src/assets/adapters/linkedin/wrench-web-adapter.v1.5.0.json +808 -0
  121. package/src/assets/adapters/linkedin/wrench-web-adapter.v1.6.0.json +808 -0
  122. package/src/assets/adapters/linkedin/wrench-web-adapter.v1.7.0.json +825 -0
  123. package/src/assets/adapters/linkedin/wrench-web-adapter.v1.8.0.json +792 -0
  124. package/src/assets/adapters/linkedin/wrench-web-adapter.v1.9.0.json +809 -0
  125. package/src/assets/adapters/reddit/wrench-web-adapter.json +855 -0
  126. package/src/assets/adapters/reddit/wrench-web-adapter.v1.0.0.json +577 -0
  127. package/src/assets/adapters/reddit/wrench-web-adapter.v1.1.0.json +604 -0
  128. package/src/assets/adapters/reddit/wrench-web-adapter.v1.10.0.json +712 -0
  129. package/src/assets/adapters/reddit/wrench-web-adapter.v1.11.0.json +712 -0
  130. package/src/assets/adapters/reddit/wrench-web-adapter.v1.12.0.json +712 -0
  131. package/src/assets/adapters/reddit/wrench-web-adapter.v1.2.0.json +653 -0
  132. package/src/assets/adapters/reddit/wrench-web-adapter.v1.3.0.json +712 -0
  133. package/src/assets/adapters/reddit/wrench-web-adapter.v1.4.0.json +713 -0
  134. package/src/assets/adapters/reddit/wrench-web-adapter.v1.5.0.json +712 -0
  135. package/src/assets/adapters/reddit/wrench-web-adapter.v1.6.0.json +713 -0
  136. package/src/assets/adapters/reddit/wrench-web-adapter.v1.7.0.json +713 -0
  137. package/src/assets/adapters/reddit/wrench-web-adapter.v1.8.0.json +713 -0
  138. package/src/assets/adapters/reddit/wrench-web-adapter.v1.9.0.json +713 -0
  139. package/src/assets/adapters/substack/wrench-web-adapter.json +936 -0
  140. package/src/assets/adapters/substack/wrench-web-adapter.v1.0.0.json +817 -0
  141. package/src/assets/adapters/substack/wrench-web-adapter.v1.1.0.json +812 -0
  142. package/src/assets/adapters/substack/wrench-web-adapter.v1.2.0.json +812 -0
  143. package/src/assets/adapters/substack/wrench-web-adapter.v1.3.0.json +902 -0
  144. package/src/assets/adapters/substack/wrench-web-adapter.v1.4.0.json +936 -0
  145. package/src/assets/adapters/substack/wrench-web-adapter.v1.5.0.json +936 -0
  146. package/src/assets/adapters/substack/wrench-web-adapter.v1.6.0.json +936 -0
  147. package/src/assets/adapters/threads/wrench-web-adapter.json +692 -0
  148. package/src/assets/adapters/threads/wrench-web-adapter.v1.0.0.json +629 -0
  149. package/src/assets/adapters/threads/wrench-web-adapter.v1.1.0.json +623 -0
  150. package/src/assets/adapters/threads/wrench-web-adapter.v1.2.0.json +622 -0
  151. package/src/assets/adapters/threads/wrench-web-adapter.v1.3.0.json +622 -0
  152. package/src/assets/adapters/threads/wrench-web-adapter.v1.4.0.json +622 -0
  153. package/src/assets/adapters/threads/wrench-web-adapter.v1.5.0.json +699 -0
  154. package/src/assets/adapters/threads/wrench-web-adapter.v1.6.0.json +693 -0
  155. package/src/assets/adapters/threads/wrench-web-adapter.v1.7.0.json +692 -0
  156. package/src/assets/adapters/tiktok/wrench-web-adapter.json +762 -0
  157. package/src/assets/adapters/tiktok/wrench-web-adapter.v1.0.0.json +681 -0
  158. package/src/assets/adapters/tiktok/wrench-web-adapter.v1.1.0.json +708 -0
  159. package/src/assets/adapters/tiktok/wrench-web-adapter.v1.2.0.json +762 -0
  160. package/src/assets/adapters/tiktok/wrench-web-adapter.v1.3.0.json +762 -0
  161. package/src/assets/adapters/twitch/wrench-web-adapter.json +42 -0
  162. package/src/assets/adapters/twitch/wrench-web-adapter.v1.0.0.json +42 -0
  163. package/src/assets/adapters/whatsapp/wrench-web-adapter.json +400 -0
  164. package/src/assets/adapters/whatsapp/wrench-web-adapter.v1.0.0.json +355 -0
  165. package/src/assets/adapters/whatsapp/wrench-web-adapter.v1.1.0.json +386 -0
  166. package/src/assets/adapters/whatsapp/wrench-web-adapter.v1.2.0.json +386 -0
  167. package/src/assets/adapters/whatsapp/wrench-web-adapter.v1.3.0.json +400 -0
  168. package/src/assets/adapters/x/wrench-adapter.json +749 -0
  169. package/src/assets/adapters/x/wrench-adapter.v1.0.0.json +700 -0
  170. package/src/assets/adapters/x/wrench-adapter.v1.1.0.json +707 -0
  171. package/src/assets/adapters/x/wrench-web-adapter.json +712 -0
  172. package/src/assets/adapters/x/wrench-web-adapter.v1.1.0.json +579 -0
  173. package/src/assets/adapters/x/wrench-web-adapter.v1.10.0.json +685 -0
  174. package/src/assets/adapters/x/wrench-web-adapter.v1.11.0.json +712 -0
  175. package/src/assets/adapters/x/wrench-web-adapter.v1.12.0.json +712 -0
  176. package/src/assets/adapters/x/wrench-web-adapter.v1.13.0.json +712 -0
  177. package/src/assets/adapters/x/wrench-web-adapter.v1.2.0.json +578 -0
  178. package/src/assets/adapters/x/wrench-web-adapter.v1.3.0.json +610 -0
  179. package/src/assets/adapters/x/wrench-web-adapter.v1.4.0.json +617 -0
  180. package/src/assets/adapters/x/wrench-web-adapter.v1.5.0.json +634 -0
  181. package/src/assets/adapters/x/wrench-web-adapter.v1.6.0.json +632 -0
  182. package/src/assets/adapters/x/wrench-web-adapter.v1.7.0.json +649 -0
  183. package/src/assets/adapters/x/wrench-web-adapter.v1.8.0.json +649 -0
  184. package/src/assets/adapters/x/wrench-web-adapter.v1.9.0.json +683 -0
  185. package/src/assets/adapters/youtube/wrench-web-adapter.json +561 -0
  186. package/src/assets/adapters/youtube/wrench-web-adapter.v1.0.0.json +465 -0
  187. package/src/assets/adapters/youtube/wrench-web-adapter.v1.1.0.json +519 -0
  188. package/src/assets/adapters/youtube/wrench-web-adapter.v1.2.0.json +561 -0
  189. package/src/assets/adapters/youtube/wrench-web-adapter.v1.3.0.json +561 -0
  190. package/src/assets/code-owned-provider-template/plugin.test.ts.template +54 -0
  191. package/src/assets/code-owned-provider-template/plugin.ts.template +102 -0
  192. package/src/assets/code-owned-provider-template/runtime.internal.test.ts.template +44 -0
  193. package/src/assets/code-owned-provider-template/runtime.ts.template +59 -0
  194. package/src/assets/code-owned-provider-template/wrench-adapter.json.template +33 -0
  195. package/src/assets/fonts/nebula-sans/LICENSE.txt +96 -0
  196. package/src/assets/fonts/nebula-sans/NebulaSans-Book.woff2 +0 -0
  197. package/src/assets/fonts/nebula-sans/PROVENANCE.md +14 -0
  198. package/src/assets.d.ts +4 -0
  199. package/src/auth.ts +1157 -0
  200. package/src/beeper-client-types.ts +169 -0
  201. package/src/beeper-client.ts +250 -0
  202. package/src/beeper-contact-interactions-cli.ts +180 -0
  203. package/src/beeper-contact-interactions.ts +1612 -0
  204. package/src/beeper-message-bundle-v1.ts +257 -0
  205. package/src/beeper-message-like-me-cli.ts +84 -0
  206. package/src/beeper-message-like-me-export.ts +2274 -0
  207. package/src/beeper-message-like-me-recovery.ts +931 -0
  208. package/src/beeper-message-like-me-source.ts +4034 -0
  209. package/src/browser-admission.ts +928 -0
  210. package/src/browser-snapshots.ts +260 -0
  211. package/src/browser.ts +3873 -0
  212. package/src/canonical-json.ts +60 -0
  213. package/src/catalog-cli.ts +638 -0
  214. package/src/cli.ts +208 -0
  215. package/src/client-types.ts +301 -0
  216. package/src/client.ts +2726 -0
  217. package/src/confirmed-write-failure.ts +44 -0
  218. package/src/confirmed-write-model.ts +72 -0
  219. package/src/confirmed-write-platform.ts +1055 -0
  220. package/src/confirmed-write-program.ts +224 -0
  221. package/src/confirmed-write-runtime.ts +16 -0
  222. package/src/cursor-token.ts +228 -0
  223. package/src/derivation-dnr-readiness.ts +367 -0
  224. package/src/derivation-file-chooser.ts +384 -0
  225. package/src/derivation-network-boundary.ts +835 -0
  226. package/src/derivation-network-guard.ts +782 -0
  227. package/src/derivation-network-proxy-helper.ts +440 -0
  228. package/src/derivation-network-proxy.ts +385 -0
  229. package/src/derive-command-helper.ts +693 -0
  230. package/src/derive-fixtures.ts +343 -0
  231. package/src/derive-review.ts +980 -0
  232. package/src/derive.ts +4754 -0
  233. package/src/fixtures/beeper-message-like-me-v1/accounts.ndjson +2 -0
  234. package/src/fixtures/beeper-message-like-me-v1/conversations.ndjson +1 -0
  235. package/src/fixtures/beeper-message-like-me-v1/manifest.json +1 -0
  236. package/src/fixtures/beeper-message-like-me-v1/messages.ndjson +2 -0
  237. package/src/fixtures/beeper-message-like-me-v1/participants.ndjson +3 -0
  238. package/src/fixtures/beeper-message-like-me-v1/reactions.ndjson +1 -0
  239. package/src/fixtures/beeper-message-like-me-v1/tombstones.ndjson +1 -0
  240. package/src/ghostget.ts +3672 -0
  241. package/src/gmail-capture.ts +1244 -0
  242. package/src/har-internal.ts +1242 -0
  243. package/src/har.ts +743 -0
  244. package/src/index.ts +218 -0
  245. package/src/invocation-read-platform.ts +55 -0
  246. package/src/invocation-read-program.ts +93 -0
  247. package/src/invocation-read-runtime.ts +11 -0
  248. package/src/linked-device-lifecycle-admission.ts +450 -0
  249. package/src/linked-device-lifecycle-journal.ts +1560 -0
  250. package/src/linked-device-lifecycle-runtime.ts +1602 -0
  251. package/src/local-cli-admission.ts +135 -0
  252. package/src/local-cli-contracts.ts +271 -0
  253. package/src/local-cli-execution.ts +98 -0
  254. package/src/local-cli-surface-contract.ts +1510 -0
  255. package/src/local-cli-tool-identity.ts +336 -0
  256. package/src/media/archive.ts +2325 -0
  257. package/src/media/args.ts +303 -0
  258. package/src/media/cli.ts +287 -0
  259. package/src/media/doctor.ts +475 -0
  260. package/src/media/ffmpeg.ts +1330 -0
  261. package/src/media/http-capture.ts +711 -0
  262. package/src/media/http-probe.ts +593 -0
  263. package/src/media/http.ts +387 -0
  264. package/src/media/index.ts +10 -0
  265. package/src/media/local-transcription.ts +613 -0
  266. package/src/media/lock.ts +321 -0
  267. package/src/media/manifest.ts +1712 -0
  268. package/src/media/metadata.ts +906 -0
  269. package/src/media/process.ts +723 -0
  270. package/src/media/revision.ts +166 -0
  271. package/src/media/runtime-closure.ts +1063 -0
  272. package/src/media/source-router.ts +187 -0
  273. package/src/media/transcriber-config.ts +1122 -0
  274. package/src/media/transcript-persistence-model.ts +15 -0
  275. package/src/media/transcript-persistence-platform.ts +89 -0
  276. package/src/media/transcript-persistence-program.ts +39 -0
  277. package/src/media/transcript-persistence-runtime.ts +24 -0
  278. package/src/media/transcript.ts +616 -0
  279. package/src/media/utf8-order.ts +30 -0
  280. package/src/media/whisper-cpp.ts +464 -0
  281. package/src/media/whisper-language.ts +58 -0
  282. package/src/media/yt-dlp.ts +439 -0
  283. package/src/message-like-me-agentic-messaging.ts +882 -0
  284. package/src/messaging-action-store.ts +793 -0
  285. package/src/messaging-runtime.ts +2136 -0
  286. package/src/messaging-store.ts +539 -0
  287. package/src/messaging-types.ts +1663 -0
  288. package/src/messaging.ts +334 -0
  289. package/src/model.ts +2671 -0
  290. package/src/oauth-google.ts +888 -0
  291. package/src/omni-client-types.ts +306 -0
  292. package/src/omni-client.ts +2225 -0
  293. package/src/omni-limits.ts +34 -0
  294. package/src/omni-model.ts +1614 -0
  295. package/src/omni-request.ts +379 -0
  296. package/src/omni-runtime.ts +1632 -0
  297. package/src/operation-deadline.ts +189 -0
  298. package/src/path-helper.ts +1656 -0
  299. package/src/pinned-https.ts +194 -0
  300. package/src/plan-assets.ts +832 -0
  301. package/src/platform-catalog.ts +1832 -0
  302. package/src/plugins/beeper-linked-device/plugin.ts +310 -0
  303. package/src/plugins/bluesky-web/plugin.ts +178 -0
  304. package/src/plugins/clasificados-web/plugin.ts +66 -0
  305. package/src/plugins/github-web/plugin.ts +80 -0
  306. package/src/plugins/gmail-official/plugin.ts +110 -0
  307. package/src/plugins/hacker-news-web/plugin.ts +52 -0
  308. package/src/plugins/imessage-direct/plugin.ts +195 -0
  309. package/src/plugins/imessage-direct/vendor/0001-fix-keep-AppleScript-send-payloads-out-of-child-argv.patch +1430 -0
  310. package/src/plugins/imessage-direct/vendor/0002-feat-rpc-add-exact-chat-lookup.patch +225 -0
  311. package/src/plugins/imessage-direct/vendor/provenance.json +80 -0
  312. package/src/plugins/linkedin-official/plugin.ts +84 -0
  313. package/src/plugins/linkedin-web/plugin.ts +412 -0
  314. package/src/plugins/meta-web/plugin.ts +317 -0
  315. package/src/plugins/reddit-web/flair.ts +492 -0
  316. package/src/plugins/reddit-web/plugin.ts +477 -0
  317. package/src/plugins/substack-web/plugin.ts +124 -0
  318. package/src/plugins/tiktok-web/plugin.ts +67 -0
  319. package/src/plugins/twitch-web/plugin.ts +54 -0
  320. package/src/plugins/whatsapp-linked-device/plugin.ts +105 -0
  321. package/src/plugins/x-official/plugin.ts +64 -0
  322. package/src/plugins/x-web/plugin.ts +488 -0
  323. package/src/plugins/youtube-web/plugin.ts +101 -0
  324. package/src/portable-run-recovery.ts +466 -0
  325. package/src/process-identity.ts +501 -0
  326. package/src/profile-clone-helper.ts +104 -0
  327. package/src/provider-catalog-views.ts +51 -0
  328. package/src/provider-context.ts +69 -0
  329. package/src/provider-contract-definitions-x.ts +45 -0
  330. package/src/provider-contract-definitions.ts +948 -0
  331. package/src/provider-contract-input-gmail.ts +176 -0
  332. package/src/provider-contract-input-linkedin.ts +77 -0
  333. package/src/provider-contract-input-x.ts +304 -0
  334. package/src/provider-contract-planning.ts +23 -0
  335. package/src/provider-contract-semantic-identity.ts +54 -0
  336. package/src/provider-contracts.ts +251 -0
  337. package/src/provider-http.ts +723 -0
  338. package/src/provider-plugin-auth.ts +36 -0
  339. package/src/provider-plugin-builtins.ts +229 -0
  340. package/src/provider-plugin-cleanup-barrier.ts +229 -0
  341. package/src/provider-plugin-cleanup-execution.ts +79 -0
  342. package/src/provider-plugin-cleanup-resource.ts +470 -0
  343. package/src/provider-plugin-contract-identity.ts +707 -0
  344. package/src/provider-plugin-host.ts +1972 -0
  345. package/src/provider-plugin-identifiers.ts +60 -0
  346. package/src/provider-plugin-invocation-lease.ts +889 -0
  347. package/src/provider-plugin-lifecycle-kernel.ts +566 -0
  348. package/src/provider-plugin-lifecycle.ts +1455 -0
  349. package/src/provider-plugin-module-analysis.ts +336 -0
  350. package/src/provider-plugin-package.ts +2530 -0
  351. package/src/provider-plugin-portable-authority.ts +106 -0
  352. package/src/provider-plugin-portable-catalog.ts +94 -0
  353. package/src/provider-plugin-portable-identity.ts +315 -0
  354. package/src/provider-plugin-portable-runtime.ts +2057 -0
  355. package/src/provider-plugin-protocol.ts +1625 -0
  356. package/src/provider-plugin-registry.ts +3342 -0
  357. package/src/provider-plugin-store.ts +2155 -0
  358. package/src/provider-plugin-test-fixture.ts +5 -0
  359. package/src/provider-plugin.ts +4907 -0
  360. package/src/provider-plugins.generated.ts +43 -0
  361. package/src/provider-plugins.ts +8 -0
  362. package/src/provider-subject.ts +41 -0
  363. package/src/provider.ts +315 -0
  364. package/src/providers/beeper-local-runtime.ts +5426 -0
  365. package/src/providers/beeper-local.ts +2739 -0
  366. package/src/providers/beeper-messaging.ts +462 -0
  367. package/src/providers/beeper-omni.ts +1159 -0
  368. package/src/providers/bluesky-web-runtime.ts +2895 -0
  369. package/src/providers/bluesky-web.ts +1614 -0
  370. package/src/providers/clasificados-web-runtime.ts +255 -0
  371. package/src/providers/clasificados-web.ts +503 -0
  372. package/src/providers/contact-projection.ts +301 -0
  373. package/src/providers/github-read-model.ts +94 -0
  374. package/src/providers/github-read-platform.ts +165 -0
  375. package/src/providers/github-read-program.ts +97 -0
  376. package/src/providers/github-web-runtime.ts +320 -0
  377. package/src/providers/github-web.ts +403 -0
  378. package/src/providers/gmail-api.ts +2834 -0
  379. package/src/providers/gmail-omni.ts +830 -0
  380. package/src/providers/gmail.ts +1130 -0
  381. package/src/providers/hacker-news-web-runtime.ts +220 -0
  382. package/src/providers/hacker-news-web.ts +809 -0
  383. package/src/providers/imessage-direct-install.ts +354 -0
  384. package/src/providers/imessage-direct-messaging.ts +532 -0
  385. package/src/providers/imessage-direct-omni.ts +400 -0
  386. package/src/providers/imessage-direct-runtime.ts +1498 -0
  387. package/src/providers/imessage-direct.ts +351 -0
  388. package/src/providers/instagram-video-foundations.ts +913 -0
  389. package/src/providers/instagram-web-profile-browser.ts +600 -0
  390. package/src/providers/iso-bmff.ts +338 -0
  391. package/src/providers/linkedin-company-platform.ts +42 -0
  392. package/src/providers/linkedin-company-program.ts +88 -0
  393. package/src/providers/linkedin-contact-failure.ts +84 -0
  394. package/src/providers/linkedin-contact-platform.ts +45 -0
  395. package/src/providers/linkedin-contact-program.ts +91 -0
  396. package/src/providers/linkedin-profile-activity-failure.ts +76 -0
  397. package/src/providers/linkedin-profile-activity-platform.ts +41 -0
  398. package/src/providers/linkedin-profile-activity-program.ts +67 -0
  399. package/src/providers/linkedin-read-failure.ts +52 -0
  400. package/src/providers/linkedin-self-platform.ts +53 -0
  401. package/src/providers/linkedin-self-program.ts +122 -0
  402. package/src/providers/linkedin-web-article-browser.ts +794 -0
  403. package/src/providers/linkedin-web-bootstrap.ts +146 -0
  404. package/src/providers/linkedin-web-contact.ts +830 -0
  405. package/src/providers/linkedin-web-feed-browser.ts +832 -0
  406. package/src/providers/linkedin-web-feed.ts +864 -0
  407. package/src/providers/linkedin-web-post-browser.ts +653 -0
  408. package/src/providers/linkedin-web-profile-browser.ts +683 -0
  409. package/src/providers/linkedin-web-runtime.ts +2388 -0
  410. package/src/providers/linkedin-web.ts +3305 -0
  411. package/src/providers/linkedin.ts +1885 -0
  412. package/src/providers/meta-bootstrap.ts +1043 -0
  413. package/src/providers/meta-facebook-group.ts +690 -0
  414. package/src/providers/meta-marketplace-relay.ts +747 -0
  415. package/src/providers/meta-omni.ts +373 -0
  416. package/src/providers/meta-relay-bundle.ts +1049 -0
  417. package/src/providers/meta-web-descriptors.ts +1740 -0
  418. package/src/providers/meta-web-runtime.ts +3388 -0
  419. package/src/providers/meta-web.ts +2931 -0
  420. package/src/providers/read-failure.ts +139 -0
  421. package/src/providers/reddit-omni.ts +462 -0
  422. package/src/providers/reddit-web-runtime.ts +1929 -0
  423. package/src/providers/reddit-web.ts +1611 -0
  424. package/src/providers/rental-listings.ts +460 -0
  425. package/src/providers/substack-omni.ts +406 -0
  426. package/src/providers/substack-video-mp4.ts +33 -0
  427. package/src/providers/substack-web-runtime.ts +2812 -0
  428. package/src/providers/substack-web.ts +1502 -0
  429. package/src/providers/tiktok-video-mp4.ts +33 -0
  430. package/src/providers/tiktok-web-runtime.ts +770 -0
  431. package/src/providers/tiktok-web.ts +2786 -0
  432. package/src/providers/twitch-web-runtime.ts +226 -0
  433. package/src/providers/twitch-web.ts +274 -0
  434. package/src/providers/whatsapp-account-identity.ts +55 -0
  435. package/src/providers/whatsapp-contact-projection-helper.ts +874 -0
  436. package/src/providers/whatsapp-contact-projection-protocol.ts +403 -0
  437. package/src/providers/whatsapp-interaction-projection-helper.ts +1452 -0
  438. package/src/providers/whatsapp-interaction-projection-protocol.ts +462 -0
  439. package/src/providers/whatsapp-message-export-projection-protocol.ts +532 -0
  440. package/src/providers/whatsapp-messaging.ts +132 -0
  441. package/src/providers/whatsapp-omni.ts +586 -0
  442. package/src/providers/whatsapp-web-runtime.ts +3108 -0
  443. package/src/providers/whatsapp-web.ts +1037 -0
  444. package/src/providers/x-image-provenance.ts +523 -0
  445. package/src/providers/x-made-with-ai.ts +106 -0
  446. package/src/providers/x-transaction-id.ts +413 -0
  447. package/src/providers/x-web-runtime.ts +3485 -0
  448. package/src/providers/x-web.ts +2332 -0
  449. package/src/providers/x.ts +1889 -0
  450. package/src/providers/youtube-web-runtime.ts +1400 -0
  451. package/src/providers/youtube-web.ts +1221 -0
  452. package/src/read-admission-runtime.ts +91 -0
  453. package/src/read-client.ts +521 -0
  454. package/src/read-effect-platform.ts +7 -0
  455. package/src/read-effect-runtime.ts +14 -0
  456. package/src/read-effect.ts +52 -0
  457. package/src/read-projection-admission.ts +866 -0
  458. package/src/read-projections.ts +3151 -0
  459. package/src/recovery.ts +1492 -0
  460. package/src/reviewed-template.ts +146 -0
  461. package/src/run-journal.ts +1272 -0
  462. package/src/runtime.ts +5116 -0
  463. package/src/scripts/generate-provider-plugin-catalog.ts +121 -0
  464. package/src/scripts/install-whatsapp-protocol.sh +232 -0
  465. package/src/scripts/recover-install-stages.ts +1491 -0
  466. package/src/scripts/resolve-state-home.ts +30 -0
  467. package/src/scripts/scaffold-web-provider.ts +973 -0
  468. package/src/scripts/sync-bundled-adapters.ts +695 -0
  469. package/src/session-secrets.ts +1183 -0
  470. package/src/state-helper.bunfig.toml +2 -0
  471. package/src/state-helper.ts +2914 -0
  472. package/src/storage.ts +3842 -0
  473. package/src/transport-policy.ts +2 -0
  474. package/src/usage.ts +182 -0
  475. package/src/version.ts +2 -0
  476. package/src/web-session-authentication-policy.ts +136 -0
  477. package/src/web-session-cleanup-admission.ts +2344 -0
  478. package/src/web-session-cleanup-join.ts +99 -0
  479. package/src/web-session-client.ts +996 -0
  480. package/src/web-session-contract-definitions.ts +820 -0
  481. package/src/web-session-contract-planning.ts +42 -0
  482. package/src/web-session-contracts.ts +228 -0
  483. package/src/web-session-cookies.ts +500 -0
  484. package/src/web-session-execution.ts +385 -0
  485. package/src/web-session-http.ts +373 -0
  486. package/src/web-session-read-errors.ts +19 -0
  487. package/src/web-session-read-runtime.ts +124 -0
  488. package/src/web-session-recovery.ts +818 -0
  489. package/src/web-session-sites.ts +58 -0
  490. package/src/web-session-template.ts +994 -0
  491. package/src/web-session.ts +93 -0
  492. package/src/whatsapp-client-binding.ts +21 -0
  493. package/src/whatsapp-client-types.ts +75 -0
  494. package/src/whatsapp-client.ts +395 -0
  495. package/src/whatsapp-export-coordinate.ts +17 -0
  496. package/src/whatsapp-message-bundle-v2.ts +195 -0
  497. package/src/whatsapp-message-like-me-cli.ts +195 -0
  498. package/src/whatsapp-message-like-me-export.ts +1072 -0
  499. package/src/whatsapp-message-like-me-source.ts +1012 -0
  500. package/tsconfig.json +67 -0
@@ -0,0 +1,2136 @@
1
+ import { randomBytes } from "node:crypto";
2
+ import { isAbsolute, resolve } from "node:path";
3
+
4
+ import type { GhostgetAuth } from "./auth";
5
+ import { canonicalJson, sha256 } from "./canonical-json";
6
+ import {
7
+ parseMessageLikeMeSourceConversationCoordinateBindingV1,
8
+ ghostgetMessagingContextBindingSha256V2,
9
+ } from "./message-like-me-agentic-messaging";
10
+ import {
11
+ MESSAGING_CONTEXT_BINDING_CONTRACT_HASH,
12
+ MESSAGING_CONTEXT_BINDING_CONTRACT_ID,
13
+ parseMessagingContextBindingV2,
14
+ parseMessagingContextRequestV1,
15
+ parseMessagingRouteResolveRequestV2,
16
+ parseMessagingRoutesRequestV1,
17
+ parseMessagingTurnV1,
18
+ messagingTurnDigest,
19
+ type MessagingContextBindingV2,
20
+ type MessagingContextMessageV1,
21
+ type MessagingContextV1,
22
+ type MessagingPreviewV1,
23
+ type MessagingPrivateProviderOutcomeV1,
24
+ type MessagingReceiptBinding,
25
+ type MessagingRouteV2,
26
+ type MessagingRoutesV2,
27
+ type MessagingRunReceipt,
28
+ type MessagingRunV1,
29
+ } from "./messaging-types";
30
+ import {
31
+ loadMessagingContextRecord,
32
+ loadMessagingRouteRecord,
33
+ messagingRouteRecordHash,
34
+ saveMessagingContextRecord,
35
+ saveMessagingRouteRecord,
36
+ type MessagingContextRecordV1,
37
+ type MessagingRouteRecordV1,
38
+ } from "./messaging-store";
39
+ import {
40
+ isLocalCliOperation,
41
+ isProviderOperation,
42
+ isWebSessionOperation,
43
+ manifestHash,
44
+ type OperationInput,
45
+ } from "./model";
46
+ import { runLocalCliOperationWithDeadline } from "./local-cli-execution";
47
+ import { parseMaterializedPageV1, type ProviderMaterializedPageV1, type ProviderMessageV1 } from "./omni-model";
48
+ import {
49
+ OperationDeadline,
50
+ type OperationDeadlineClock,
51
+ } from "./operation-deadline";
52
+ import { withLocalCliProviderCleanupAdmission } from "./local-cli-admission";
53
+ import type {
54
+ ProviderPluginBindingV1,
55
+ ProviderPluginMessagingDefinitionV1,
56
+ ProviderPluginMessagingExpectedOwnPrefixProofV1,
57
+ } from "./provider-plugin";
58
+ import { requireProviderPluginAuth } from "./provider-plugin-auth";
59
+ import type {
60
+ ProviderPluginOperationResolutionV1,
61
+ ProviderPluginRegistry,
62
+ } from "./provider-plugin-registry";
63
+ import { providerPluginRegistry } from "./provider-plugins";
64
+ import {
65
+ bindMessagingCompositePreviewDigest,
66
+ createMessagingCompositeInvocationPlan,
67
+ executeReadInvocation,
68
+ invocationPlanDigest,
69
+ loadInvocationPlan,
70
+ prepareInvocation,
71
+ saveInvocationPlan,
72
+ type PreparedInvocation,
73
+ type StoredPlan,
74
+ } from "./runtime";
75
+ import {
76
+ initializeMessagingRun,
77
+ messagingExpectedOwnPrefix,
78
+ messagingReceiptBinding,
79
+ messagingRunReceipt,
80
+ readMessagingRun,
81
+ updateMessagingRun,
82
+ type MessagingRunSnapshotV1,
83
+ } from "./messaging-action-store";
84
+ import {
85
+ createPrivateJsonIfAbsent,
86
+ isGhostgetStatePath,
87
+ writePrivateJson,
88
+ writePrivateJsonIfUnchanged,
89
+ } from "./storage";
90
+ import {
91
+ runWebSessionOperationWithDeadline,
92
+ } from "./web-session-execution";
93
+ import {
94
+ withWebSessionCleanupAdmission,
95
+ type WebSessionCleanupAdmissionIdentity,
96
+ } from "./web-session-cleanup-admission";
97
+
98
+ const ROUTE_TTL_MS = 15 * 60_000;
99
+ const CONTEXT_TTL_MS = 5 * 60_000;
100
+
101
+ type Environment = Readonly<Record<string, string | undefined>>;
102
+
103
+ export type MessagingRuntimeOptions = {
104
+ readonly environment?: Environment;
105
+ readonly registry?: ProviderPluginRegistry;
106
+ readonly now?: Date;
107
+ readonly signal?: AbortSignal;
108
+ /** Internal deterministic-clock seam for the per-bubble total deadline. */
109
+ readonly deadlineClock?: OperationDeadlineClock;
110
+ };
111
+
112
+ type MessagingResolution = ProviderPluginOperationResolutionV1 & {
113
+ readonly messaging: ProviderPluginMessagingDefinitionV1;
114
+ };
115
+
116
+ function parseExpectedOwnPrefixProof(
117
+ value: unknown,
118
+ acceptedCount: number,
119
+ ): ProviderPluginMessagingExpectedOwnPrefixProofV1 {
120
+ if (
121
+ typeof value !== "object"
122
+ || value === null
123
+ || Array.isArray(value)
124
+ || Object.getPrototypeOf(value) !== Object.prototype
125
+ ) throw new Error("messaging provider returned a malformed prefix proof");
126
+ const descriptors = Object.getOwnPropertyDescriptors(value);
127
+ if (Reflect.ownKeys(descriptors).some((key) => typeof key !== "string")) {
128
+ throw new Error("messaging provider returned a malformed prefix proof");
129
+ }
130
+ for (const descriptor of Object.values(descriptors)) {
131
+ if (!descriptor.enumerable || !("value" in descriptor)) {
132
+ throw new Error("messaging provider returned a malformed prefix proof");
133
+ }
134
+ }
135
+ const source = value as Record<string, unknown>;
136
+ const keys = Object.keys(source).sort();
137
+ if (source.state === "drift" && keys.length === 1 && keys[0] === "state") {
138
+ return Object.freeze({ state: "drift" as const });
139
+ }
140
+ if (
141
+ source.state !== "proven"
142
+ || keys.length !== 2
143
+ || keys[0] !== "matchedAcceptedPrefixCount"
144
+ || keys[1] !== "state"
145
+ || typeof source.matchedAcceptedPrefixCount !== "number"
146
+ || !Number.isSafeInteger(source.matchedAcceptedPrefixCount)
147
+ || source.matchedAcceptedPrefixCount < 0
148
+ || source.matchedAcceptedPrefixCount > acceptedCount
149
+ ) throw new Error("messaging provider returned a malformed prefix proof");
150
+ return Object.freeze({
151
+ state: "proven" as const,
152
+ matchedAcceptedPrefixCount: source.matchedAcceptedPrefixCount,
153
+ });
154
+ }
155
+
156
+ export type MessagingPrivateOutputReceiptV1 = {
157
+ readonly schemaVersion: 1;
158
+ readonly format: "wrench.messaging-private-output-receipt";
159
+ readonly artifactFormat: string;
160
+ readonly artifactSha256: string;
161
+ readonly itemCount: number;
162
+ readonly generatedAt: string;
163
+ readonly expiresAt: string | null;
164
+ };
165
+
166
+ type MessagingPrivateOutputArtifact =
167
+ | MessagingRouteV2
168
+ | MessagingRoutesV2
169
+ | MessagingContextV1
170
+ | MessagingPreviewV1
171
+ | MessagingRunV1
172
+ | MessagingReceiptBinding;
173
+
174
+ export type MessagingPrivateOutputReservationV1 = {
175
+ readonly path: string;
176
+ readonly artifactFormat: MessagingPrivateOutputArtifact["format"];
177
+ readonly reservationId: string;
178
+ readonly reservedContentSha256: string;
179
+ };
180
+
181
+ export type MessagingPrivateOutputReservationPairV1 = {
182
+ readonly run: MessagingPrivateOutputReservationV1;
183
+ readonly receiptBinding: MessagingPrivateOutputReservationV1;
184
+ };
185
+
186
+ function now(options: MessagingRuntimeOptions): Date {
187
+ const value = options.now ?? new Date();
188
+ if (!Number.isFinite(value.getTime())) throw new Error("messaging observation time is invalid");
189
+ return value;
190
+ }
191
+
192
+ function opaque(prefix: "wmroute" | "wmcontext" | "wmreply"): string {
193
+ return `${prefix}_${randomBytes(16).toString("base64url")}`;
194
+ }
195
+
196
+ function operationResolution(
197
+ invocation: PreparedInvocation,
198
+ registry: ProviderPluginRegistry,
199
+ ): ProviderPluginOperationResolutionV1 {
200
+ const operation = invocation.manifest.operations[invocation.operationId];
201
+ if (operation === undefined) throw new Error("messaging operation disappeared");
202
+ if (isLocalCliOperation(operation)) {
203
+ return registry.requireOperationDefinition(
204
+ "local-cli",
205
+ operation.localCli.surface,
206
+ operation.localCli.action,
207
+ operation.localCli.contractVersion,
208
+ );
209
+ }
210
+ if (isWebSessionOperation(operation)) {
211
+ const binding = registry.requireSessionRoute(operation.webSession.site);
212
+ return registry.requireOperationDefinition(
213
+ binding.transport,
214
+ operation.webSession.site,
215
+ operation.webSession.action,
216
+ operation.webSession.contractVersion,
217
+ );
218
+ }
219
+ if (isProviderOperation(operation)) {
220
+ return registry.requireOperationDefinition(
221
+ "provider-api",
222
+ operation.provider.provider,
223
+ operation.provider.action,
224
+ operation.provider.contractVersion,
225
+ );
226
+ }
227
+ throw new Error("messaging requires one code-owned provider operation");
228
+ }
229
+
230
+ function messagingResolution(
231
+ invocation: PreparedInvocation,
232
+ registry: ProviderPluginRegistry,
233
+ expected: "messaging.list" | "messaging.read" | "action",
234
+ ): MessagingResolution {
235
+ const resolution = operationResolution(invocation, registry);
236
+ const messaging = resolution.binding.messaging;
237
+ if (messaging === undefined) {
238
+ throw new Error("selected provider does not expose the messaging SPI");
239
+ }
240
+ const expectedOperation = expected === "action"
241
+ ? messaging.action.state === "supported"
242
+ ? messaging.action.operation
243
+ : null
244
+ : expected === "messaging.list"
245
+ ? messaging.listOperation
246
+ : messaging.contextOperation;
247
+ if (expectedOperation === null || resolution.operation.name !== expectedOperation) {
248
+ throw new Error("selected operation does not match the provider messaging contract");
249
+ }
250
+ return Object.freeze({ ...resolution, messaging });
251
+ }
252
+
253
+ function requireBoundAuth(
254
+ binding: ProviderPluginBindingV1,
255
+ auth: PreparedInvocation["auth"],
256
+ ): asserts auth is GhostgetAuth & { readonly subject: string } {
257
+ if (auth.kind === "public-web-session") {
258
+ throw new Error("messaging requires one private account-bound auth realm");
259
+ }
260
+ requireProviderPluginAuth(binding, auth);
261
+ if (auth.subject === undefined || !binding.subject.matches(auth.subject)) {
262
+ throw new Error("messaging requires one current account-bound auth realm");
263
+ }
264
+ }
265
+
266
+ async function requireRuntimeReady(
267
+ binding: ProviderPluginBindingV1,
268
+ auth: PreparedInvocation["auth"],
269
+ environment: Environment,
270
+ registry: ProviderPluginRegistry,
271
+ ): Promise<void> {
272
+ requireBoundAuth(binding, auth);
273
+ if (binding.transport === "local-cli") {
274
+ const status = await withLocalCliProviderCleanupAdmission(
275
+ {
276
+ registry,
277
+ binding,
278
+ auth: null,
279
+ purpose: { kind: "inspect" },
280
+ environment,
281
+ },
282
+ (registerCleanupBarrier) => binding.inspect(environment, {
283
+ registerCleanupBarrier,
284
+ }),
285
+ );
286
+ if (!status.ready) throw new Error("messaging provider runtime is unavailable");
287
+ return;
288
+ }
289
+ if (binding.transport === "linked-device") {
290
+ const lifecycle = binding.linkedDeviceLifecycle;
291
+ if (lifecycle === undefined) {
292
+ throw new Error("messaging linked-device runtime has no readiness inspection");
293
+ }
294
+ const status = await lifecycle.inspect(environment);
295
+ if (!status.ready) throw new Error("messaging provider runtime is unavailable");
296
+ }
297
+ }
298
+
299
+ function implementationIdentity(
300
+ binding: ProviderPluginBindingV1,
301
+ registry: ProviderPluginRegistry,
302
+ ): string {
303
+ return sha256(canonicalJson({
304
+ surfaceId: binding.surfaceId,
305
+ transport: binding.transport,
306
+ closureHash: registry.implementationClosureHash(binding),
307
+ artifactSha256: registry.artifactSha256(binding),
308
+ ...(binding.transport === "local-cli" ? { tool: binding.tool } : {}),
309
+ }));
310
+ }
311
+
312
+ function messagingWebCleanupAdmissionIdentity(
313
+ invocation: PreparedInvocation,
314
+ resolution: MessagingResolution,
315
+ registry: ProviderPluginRegistry,
316
+ runId: string,
317
+ partIndex: number,
318
+ ): WebSessionCleanupAdmissionIdentity {
319
+ return Object.freeze({
320
+ runId,
321
+ pluginId: resolution.plugin.id,
322
+ pluginVersion: resolution.plugin.version,
323
+ pluginImplementationHash: registry
324
+ .implementationHash(resolution.binding)
325
+ .toString("hex"),
326
+ adapterId: invocation.manifest.id,
327
+ adapterHash: manifestHash(invocation.manifest),
328
+ surfaceId: resolution.binding.surfaceId,
329
+ authId: invocation.auth.id,
330
+ authHash: sha256(canonicalJson(invocation.auth)),
331
+ transport: "web-session-api" as const,
332
+ executionIdentityHash: sha256(canonicalJson({
333
+ schemaVersion: 1,
334
+ kind: "messaging",
335
+ runId,
336
+ partIndex,
337
+ operation: invocation.operationId,
338
+ binding: implementationIdentity(resolution.binding, registry),
339
+ })),
340
+ });
341
+ }
342
+
343
+ function identityFor(
344
+ invocation: PreparedInvocation,
345
+ resolution: MessagingResolution,
346
+ registry: ProviderPluginRegistry,
347
+ ): Pick<MessagingRouteRecordV1, "adapter" | "plugin" | "binding" | "auth"> {
348
+ requireBoundAuth(resolution.binding, invocation.auth);
349
+ return Object.freeze({
350
+ adapter: Object.freeze({
351
+ id: invocation.manifest.id,
352
+ version: invocation.manifest.version,
353
+ hash: manifestHash(invocation.manifest),
354
+ }),
355
+ plugin: Object.freeze({
356
+ id: resolution.plugin.id,
357
+ version: resolution.plugin.version,
358
+ closureHash: registry.implementationClosureHash(resolution.binding),
359
+ }),
360
+ binding: Object.freeze({
361
+ surfaceId: resolution.binding.surfaceId,
362
+ transport: resolution.binding.transport,
363
+ implementationIdentity: implementationIdentity(resolution.binding, registry),
364
+ messagingContractId: resolution.messaging.contractId,
365
+ }),
366
+ auth: Object.freeze({
367
+ id: invocation.auth.id,
368
+ hash: sha256(canonicalJson(invocation.auth)),
369
+ subject: invocation.auth.subject,
370
+ }),
371
+ });
372
+ }
373
+
374
+ function exactLivePage(
375
+ invocation: PreparedInvocation,
376
+ resolution: MessagingResolution,
377
+ output: unknown,
378
+ ): ProviderMaterializedPageV1 {
379
+ const omni = resolution.operation.omni;
380
+ if (omni?.state !== "supported") {
381
+ throw new Error("messaging provider operation lost normalization support");
382
+ }
383
+ return parseMaterializedPageV1(omni.materialize(invocation.input, output));
384
+ }
385
+
386
+ function routeFingerprint(record: MessagingRouteRecordV1): string {
387
+ return sha256(canonicalJson({
388
+ adapter: record.adapter,
389
+ plugin: record.plugin,
390
+ binding: record.binding,
391
+ auth: record.auth,
392
+ target: record.target,
393
+ resolution: record.resolution,
394
+ network: record.network,
395
+ sourceConversationCoordinate: record.sourceConversationCoordinate,
396
+ conversationProviderId: record.conversationProviderId,
397
+ }));
398
+ }
399
+
400
+ function assertRouteIdentity(
401
+ record: MessagingRouteRecordV1,
402
+ invocation: PreparedInvocation,
403
+ resolution: MessagingResolution,
404
+ registry: ProviderPluginRegistry,
405
+ observation: Date,
406
+ ): void {
407
+ if (Date.parse(record.expiresAt) <= observation.getTime()) {
408
+ throw new Error("messaging route is unavailable or expired");
409
+ }
410
+ if (
411
+ canonicalJson(invocation.input) !== canonicalJson(record.list.input)
412
+ || record.list.inputHash !== sha256(canonicalJson(record.list.input))
413
+ ) {
414
+ throw new Error("messaging route list input changed; discover a new route");
415
+ }
416
+ const identity = identityFor(invocation, resolution, registry);
417
+ const expected = sha256(canonicalJson({
418
+ adapter: identity.adapter,
419
+ plugin: identity.plugin,
420
+ binding: identity.binding,
421
+ auth: identity.auth,
422
+ target: resolution.messaging.parseTarget(record.target),
423
+ resolution: record.resolution,
424
+ network: resolution.messaging.network,
425
+ sourceConversationCoordinate: record.sourceConversationCoordinate,
426
+ conversationProviderId: record.conversationProviderId,
427
+ }));
428
+ if (expected !== routeFingerprint(record)) {
429
+ throw new Error("messaging route identity changed; discover a new route");
430
+ }
431
+ }
432
+
433
+ function latestRevision(
434
+ surfaceId: string,
435
+ conversationProviderId: string,
436
+ messages: readonly ProviderMessageV1[],
437
+ exactDataRevision: string,
438
+ ): string {
439
+ if (messages.length === 0) {
440
+ return sha256(canonicalJson({
441
+ surfaceId,
442
+ conversationProviderId,
443
+ empty: true,
444
+ exactDataRevision,
445
+ }));
446
+ }
447
+ const latest = [...messages].sort((left, right) =>
448
+ (left.orderedAt ?? "").localeCompare(right.orderedAt ?? "")
449
+ || left.providerId.localeCompare(right.providerId)).at(-1)!;
450
+ return sha256(canonicalJson({
451
+ surfaceId,
452
+ conversationProviderId,
453
+ providerId: latest.providerId,
454
+ providerRevision: latest.providerRevision,
455
+ orderedAt: latest.orderedAt,
456
+ }));
457
+ }
458
+
459
+ function messagingBaseMessages(
460
+ messages: readonly ProviderMessageV1[],
461
+ ): readonly {
462
+ readonly providerMessageId: string;
463
+ readonly providerRevision: string | null;
464
+ readonly orderedAt: string | null;
465
+ readonly messageSha256: string;
466
+ }[] {
467
+ return Object.freeze([...messages]
468
+ .sort((left, right) => {
469
+ const leftKey = `${left.orderedAt ?? ""}\0${left.providerId}`;
470
+ const rightKey = `${right.orderedAt ?? ""}\0${right.providerId}`;
471
+ return leftKey < rightKey ? -1 : leftKey > rightKey ? 1 : 0;
472
+ })
473
+ .map((message) => Object.freeze({
474
+ providerMessageId: message.providerId,
475
+ providerRevision: message.providerRevision,
476
+ orderedAt: message.orderedAt,
477
+ messageSha256: sha256(canonicalJson(message)),
478
+ })));
479
+ }
480
+
481
+ function pageMessages(
482
+ page: ProviderMaterializedPageV1,
483
+ conversationProviderId: string,
484
+ ): readonly ProviderMessageV1[] {
485
+ return Object.freeze(page.entities.map((entity) => {
486
+ if (
487
+ entity.kind !== "message"
488
+ || entity.conversationProviderId !== conversationProviderId
489
+ ) {
490
+ throw new Error("messaging context did not bind the exact route conversation");
491
+ }
492
+ return entity;
493
+ }));
494
+ }
495
+
496
+ function randomRefMap(
497
+ messages: readonly ProviderMessageV1[],
498
+ ): {
499
+ readonly byProvider: ReadonlyMap<string, string>;
500
+ readonly replyTargets: Readonly<Record<string, string>>;
501
+ } {
502
+ const byProvider = new Map<string, string>();
503
+ const replyTargets: Record<string, string> = {};
504
+ for (const message of messages) {
505
+ if (byProvider.has(message.providerId)) {
506
+ throw new Error("messaging context repeated a provider message identity");
507
+ }
508
+ const reference = opaque("wmreply");
509
+ byProvider.set(message.providerId, reference);
510
+ replyTargets[reference] = message.providerId;
511
+ }
512
+ return Object.freeze({
513
+ byProvider,
514
+ replyTargets: Object.freeze(replyTargets),
515
+ });
516
+ }
517
+
518
+ function publicMessages(
519
+ messages: readonly ProviderMessageV1[],
520
+ refs: ReadonlyMap<string, string>,
521
+ ): readonly MessagingContextMessageV1[] {
522
+ return Object.freeze(messages.map((message) => {
523
+ const messageRef = refs.get(message.providerId);
524
+ if (messageRef === undefined) throw new Error("messaging reply reference disappeared");
525
+ return Object.freeze({
526
+ messageRef,
527
+ direction: message.direction,
528
+ time: message.orderedAt,
529
+ author: message.sender === null
530
+ ? null
531
+ : Object.freeze({
532
+ displayName: message.sender.displayName,
533
+ handle: message.sender.handle,
534
+ }),
535
+ body: message.body,
536
+ bodyTruncated: message.bodyTruncated === true,
537
+ edited: "unknown" as const,
538
+ retracted:
539
+ message.state === "revoked"
540
+ || message.state === "revoked-and-deleted-for-me"
541
+ ? "observed" as const
542
+ : "unknown" as const,
543
+ reply: Object.freeze({
544
+ toMessageRef: message.replyToProviderId === null
545
+ ? null
546
+ : refs.get(message.replyToProviderId) ?? null,
547
+ }),
548
+ attachments: Object.freeze(message.attachments.map((attachment) =>
549
+ Object.freeze({
550
+ kind: attachment.kind,
551
+ mimeType: attachment.mimeType,
552
+ name: attachment.name,
553
+ sizeBytes: attachment.sizeBytes,
554
+ }))),
555
+ untrustedData: true as const,
556
+ });
557
+ }));
558
+ }
559
+
560
+ export function validateMessagingPrivateOutputPath(
561
+ path: string,
562
+ environment: Environment = process.env,
563
+ ): string {
564
+ if (
565
+ !isAbsolute(path)
566
+ || resolve(path) !== path
567
+ || Buffer.byteLength(path, "utf8") > 4_096
568
+ || /[\0\r\n]/u.test(path)
569
+ ) throw new Error("messaging private output path must be normalized and absolute");
570
+ if (isGhostgetStatePath(path, environment)) {
571
+ throw new Error(
572
+ "messaging private output path must be outside GHOSTGET_STATE_HOME",
573
+ );
574
+ }
575
+ return path;
576
+ }
577
+
578
+ export function writeMessagingPrivateOutput(
579
+ path: string,
580
+ artifact: MessagingPrivateOutputArtifact,
581
+ environment: Environment = process.env,
582
+ ): MessagingPrivateOutputReceiptV1 {
583
+ const outputPath = validateMessagingPrivateOutputPath(path, environment);
584
+ writePrivateJson(outputPath, artifact, { privateParent: true });
585
+ const artifactSha256 = sha256(canonicalJson(artifact));
586
+ const expiresAt = artifact.format === "wrench.messaging-context"
587
+ ? artifact.binding?.expiresAt ?? null
588
+ : artifact.format === "wrench.messaging-preview"
589
+ ? artifact.expiresAt
590
+ : artifact.format === "wrench.messaging-route"
591
+ ? artifact.expiresAt
592
+ : artifact.format === "wrench.messaging-run"
593
+ ? null
594
+ : artifact.format === "wrench.messaging-receipt-binding"
595
+ ? null
596
+ : artifact.routes.reduce<string | null>((latest, route) =>
597
+ latest === null || route.expiresAt < latest ? route.expiresAt : latest, null);
598
+ return Object.freeze({
599
+ schemaVersion: 1,
600
+ format: "wrench.messaging-private-output-receipt",
601
+ artifactFormat: artifact.format,
602
+ artifactSha256,
603
+ itemCount: artifact.format === "wrench.messaging-routes"
604
+ ? artifact.routes.length
605
+ : artifact.format === "wrench.messaging-context"
606
+ ? artifact.messages.length
607
+ : artifact.format === "wrench.messaging-route"
608
+ ? 1
609
+ : artifact.format === "wrench.messaging-preview"
610
+ || artifact.format === "wrench.messaging-run"
611
+ ? artifact.partCount
612
+ : 1,
613
+ generatedAt: artifact.format === "wrench.messaging-routes"
614
+ ? artifact.generatedAt
615
+ : artifact.format === "wrench.messaging-context"
616
+ ? artifact.binding?.validatedAt ?? new Date().toISOString()
617
+ : artifact.format === "wrench.messaging-route"
618
+ ? new Date().toISOString()
619
+ : artifact.format === "wrench.messaging-run"
620
+ || artifact.format === "wrench.messaging-receipt-binding"
621
+ ? artifact.recordedAt
622
+ : new Date().toISOString(),
623
+ expiresAt,
624
+ });
625
+ }
626
+
627
+ function reserveMessagingPrivateOutput(
628
+ path: string,
629
+ artifactFormat: MessagingPrivateOutputArtifact["format"],
630
+ reservationId: string,
631
+ environment: Environment,
632
+ ): MessagingPrivateOutputReservationV1 {
633
+ const outputPath = validateMessagingPrivateOutputPath(path, environment);
634
+ const marker = Object.freeze({
635
+ schemaVersion: 1 as const,
636
+ format: "wrench.messaging-private-output-reservation" as const,
637
+ reservationId,
638
+ artifactFormat,
639
+ });
640
+ const reservedContentSha256 = sha256(`${canonicalJson(marker)}\n`);
641
+ let created: boolean;
642
+ try {
643
+ created = createPrivateJsonIfAbsent(
644
+ outputPath,
645
+ marker,
646
+ { privateParent: true },
647
+ ).created;
648
+ } catch {
649
+ throw new Error("messaging private output sink failed physical reservation");
650
+ }
651
+ if (!created) {
652
+ throw new Error("messaging private output sink already exists");
653
+ }
654
+ return Object.freeze({
655
+ path: outputPath,
656
+ artifactFormat,
657
+ reservationId,
658
+ reservedContentSha256,
659
+ });
660
+ }
661
+
662
+ export function reserveMessagingPrivateOutputPair(
663
+ runPath: string,
664
+ receiptBindingPath: string,
665
+ environment: Environment = process.env,
666
+ ): MessagingPrivateOutputReservationPairV1 {
667
+ const exactRunPath = validateMessagingPrivateOutputPath(runPath, environment);
668
+ const exactReceiptBindingPath = validateMessagingPrivateOutputPath(
669
+ receiptBindingPath,
670
+ environment,
671
+ );
672
+ if (exactRunPath === exactReceiptBindingPath) {
673
+ throw new Error(
674
+ "messaging confirmation requires distinct private output and receipt-binding paths",
675
+ );
676
+ }
677
+ const pairId = `wmoutput_${randomBytes(16).toString("base64url")}`;
678
+ const run = reserveMessagingPrivateOutput(
679
+ exactRunPath,
680
+ "wrench.messaging-run",
681
+ pairId,
682
+ environment,
683
+ );
684
+ let receiptBinding: MessagingPrivateOutputReservationV1;
685
+ try {
686
+ receiptBinding = reserveMessagingPrivateOutput(
687
+ exactReceiptBindingPath,
688
+ "wrench.messaging-receipt-binding",
689
+ pairId,
690
+ environment,
691
+ );
692
+ } catch (error) {
693
+ throw new Error(
694
+ "messaging receipt-binding sink failed physical reservation after the body-free run sink was reserved",
695
+ { cause: error },
696
+ );
697
+ }
698
+ return Object.freeze({ run, receiptBinding });
699
+ }
700
+
701
+ export function writeReservedMessagingPrivateOutput(
702
+ reservation: MessagingPrivateOutputReservationV1,
703
+ artifact: MessagingPrivateOutputArtifact,
704
+ _environment: Environment = process.env,
705
+ ): MessagingPrivateOutputReceiptV1 {
706
+ if (artifact.format !== reservation.artifactFormat) {
707
+ throw new Error("messaging private output reservation has another artifact format");
708
+ }
709
+ let written: boolean;
710
+ try {
711
+ written = writePrivateJsonIfUnchanged(reservation.path, artifact, {
712
+ expectedCurrentContentSha256: reservation.reservedContentSha256,
713
+ maximumExpectedCurrentBytes: 4_096,
714
+ privateParent: true,
715
+ });
716
+ } catch {
717
+ throw new Error("messaging private output final export failed");
718
+ }
719
+ if (!written) {
720
+ throw new Error(
721
+ "messaging private output reservation changed before final export; recover by run ID",
722
+ );
723
+ }
724
+ const artifactSha256 = sha256(canonicalJson(artifact));
725
+ const expiresAt = artifact.format === "wrench.messaging-context"
726
+ ? artifact.binding?.expiresAt ?? null
727
+ : artifact.format === "wrench.messaging-preview"
728
+ ? artifact.expiresAt
729
+ : artifact.format === "wrench.messaging-route"
730
+ ? artifact.expiresAt
731
+ : artifact.format === "wrench.messaging-routes"
732
+ ? artifact.routes.reduce<string | null>((latest, route) =>
733
+ latest === null || route.expiresAt < latest
734
+ ? route.expiresAt
735
+ : latest, null)
736
+ : null;
737
+ return Object.freeze({
738
+ schemaVersion: 1,
739
+ format: "wrench.messaging-private-output-receipt",
740
+ artifactFormat: artifact.format,
741
+ artifactSha256,
742
+ itemCount: artifact.format === "wrench.messaging-routes"
743
+ ? artifact.routes.length
744
+ : artifact.format === "wrench.messaging-context"
745
+ ? artifact.messages.length
746
+ : artifact.format === "wrench.messaging-preview"
747
+ || artifact.format === "wrench.messaging-run"
748
+ ? artifact.partCount
749
+ : 1,
750
+ generatedAt: artifact.format === "wrench.messaging-routes"
751
+ ? artifact.generatedAt
752
+ : artifact.format === "wrench.messaging-context"
753
+ ? artifact.binding?.validatedAt ?? new Date().toISOString()
754
+ : artifact.format === "wrench.messaging-run"
755
+ || artifact.format === "wrench.messaging-receipt-binding"
756
+ ? artifact.recordedAt
757
+ : new Date().toISOString(),
758
+ expiresAt,
759
+ });
760
+ }
761
+
762
+ export async function discoverMessagingRoutesInternal(
763
+ value: unknown,
764
+ options: MessagingRuntimeOptions = {},
765
+ ): Promise<MessagingRoutesV2> {
766
+ const request = parseMessagingRoutesRequestV1(value);
767
+ const environment = options.environment ?? process.env;
768
+ const registry = options.registry ?? providerPluginRegistry;
769
+ const observation = now(options);
770
+ const generatedAt = observation.toISOString();
771
+ const expiresAt = new Date(observation.getTime() + ROUTE_TTL_MS).toISOString();
772
+ const routes: MessagingRouteV2[] = [];
773
+ const routeIdentities = new Set<string>();
774
+ const source = request.source;
775
+ const invocation = prepareInvocation(
776
+ source.adapterId,
777
+ "messaging.list",
778
+ source.listInput,
779
+ source.authId,
780
+ environment,
781
+ registry,
782
+ );
783
+ const resolution = messagingResolution(invocation, registry, "messaging.list");
784
+ await requireRuntimeReady(resolution.binding, invocation.auth, environment, registry);
785
+ const live = await executeReadInvocation(invocation, {
786
+ headed: false,
787
+ environment,
788
+ registry,
789
+ ...(options.signal === undefined ? {} : { signal: options.signal }),
790
+ });
791
+ if (live.receipt.status !== "succeeded") {
792
+ throw new Error("messaging route discovery live read did not succeed");
793
+ }
794
+ const exactDataRevision = sha256(canonicalJson(live.output));
795
+ const page = exactLivePage(invocation, resolution, live.output);
796
+ const candidates = resolution.messaging.enumerateRoutes(invocation.input, page);
797
+ if (!Array.isArray(candidates) || candidates.length > 1_000) {
798
+ throw new Error("provider messaging route enumeration exceeded its bound");
799
+ }
800
+ const identity = identityFor(invocation, resolution, registry);
801
+ for (const candidate of candidates) {
802
+ const target = resolution.messaging.parseTarget(candidate.target);
803
+ if (
804
+ typeof candidate.conversationProviderId !== "string"
805
+ || candidate.conversationProviderId.length < 1
806
+ || candidate.conversationProviderId.length > 4_096
807
+ || !Array.isArray(candidate.participants)
808
+ || candidate.participants.length > 10_000
809
+ ) throw new Error("provider messaging route candidate is malformed");
810
+ const dedupe = sha256(canonicalJson({
811
+ adapter: identity.adapter,
812
+ binding: identity.binding,
813
+ auth: identity.auth,
814
+ target,
815
+ }));
816
+ if (routeIdentities.has(dedupe)) {
817
+ throw new Error("messaging route page repeated one exact provider conversation");
818
+ }
819
+ routeIdentities.add(dedupe);
820
+ const routeRef = opaque("wmroute");
821
+ const participantFingerprint = sha256(canonicalJson(candidate.participants));
822
+ const routeRecord: MessagingRouteRecordV1 = Object.freeze({
823
+ schemaVersion: 1,
824
+ format: "wrench.messaging-route-record",
825
+ routeRef,
826
+ createdAt: generatedAt,
827
+ expiresAt,
828
+ ...identity,
829
+ list: Object.freeze({
830
+ operation: "messaging.list",
831
+ input: invocation.input,
832
+ inputHash: sha256(canonicalJson(invocation.input)),
833
+ exactDataRevision,
834
+ validatedAt: generatedAt,
835
+ }),
836
+ target,
837
+ resolution: "list-candidate",
838
+ network: resolution.messaging.network,
839
+ sourceConversationCoordinate: null,
840
+ conversationProviderId: candidate.conversationProviderId,
841
+ conversation: Object.freeze({
842
+ kind: candidate.conversationKind,
843
+ title: candidate.title,
844
+ participantCount: candidate.participants.length,
845
+ participantFingerprint,
846
+ providerRevision: candidate.providerRevision,
847
+ }),
848
+ });
849
+ saveMessagingRouteRecord(routeRecord, environment);
850
+ routes.push(Object.freeze({
851
+ schemaVersion: 2,
852
+ format: "wrench.messaging-route",
853
+ routeRef,
854
+ network: resolution.messaging.network,
855
+ conversation: Object.freeze({
856
+ kind: candidate.conversationKind,
857
+ title: candidate.title,
858
+ participantCount: candidate.participants.length,
859
+ }),
860
+ readiness: Object.freeze({
861
+ context: "resolution-required",
862
+ turn: "unavailable",
863
+ reply: "unsupported",
864
+ reason:
865
+ "an exact provider coordinate must be resolved before this list candidate can become a context or action capability",
866
+ }),
867
+ completeness: page.completeness,
868
+ expiresAt,
869
+ }));
870
+ }
871
+ return Object.freeze({
872
+ schemaVersion: 2,
873
+ format: "wrench.messaging-routes",
874
+ generatedAt,
875
+ completeness: page.completeness,
876
+ continuation: Object.freeze({
877
+ direction: page.cursor.direction,
878
+ request: page.cursor.request,
879
+ nextInput: page.cursor.nextInput,
880
+ }),
881
+ routes: Object.freeze(routes),
882
+ });
883
+ }
884
+
885
+ export async function resolveMessagingRouteInternal(
886
+ value: unknown,
887
+ options: MessagingRuntimeOptions = {},
888
+ ): Promise<MessagingRouteV2> {
889
+ const request = parseMessagingRouteResolveRequestV2(value);
890
+ const environment = options.environment ?? process.env;
891
+ const registry = options.registry ?? providerPluginRegistry;
892
+ const observation = now(options);
893
+ const createdAt = observation.toISOString();
894
+ const expiresAt = new Date(observation.getTime() + ROUTE_TTL_MS).toISOString();
895
+ const listCandidate = loadMessagingRouteRecord(
896
+ request.routeRef,
897
+ environment,
898
+ observation,
899
+ );
900
+ if (listCandidate.resolution !== "list-candidate") {
901
+ throw new Error("messaging route resolution requires one unresolved list candidate");
902
+ }
903
+ const sourceInvocation = prepareInvocation(
904
+ listCandidate.adapter.id,
905
+ listCandidate.list.operation,
906
+ listCandidate.list.input,
907
+ listCandidate.auth.id,
908
+ environment,
909
+ registry,
910
+ );
911
+ const resolution = messagingResolution(
912
+ sourceInvocation,
913
+ registry,
914
+ "messaging.list",
915
+ );
916
+ assertRouteIdentity(
917
+ listCandidate,
918
+ sourceInvocation,
919
+ resolution,
920
+ registry,
921
+ observation,
922
+ );
923
+ await requireRuntimeReady(
924
+ resolution.binding,
925
+ sourceInvocation.auth,
926
+ environment,
927
+ registry,
928
+ );
929
+ const storedTarget = resolution.messaging.parseTarget(listCandidate.target);
930
+ const exactInput = resolution.messaging.resolveRoute.input(
931
+ storedTarget,
932
+ );
933
+ const exactInvocation = prepareInvocation(
934
+ listCandidate.adapter.id,
935
+ resolution.messaging.resolveRoute.operation,
936
+ exactInput,
937
+ listCandidate.auth.id,
938
+ environment,
939
+ registry,
940
+ );
941
+ const exactResolution = operationResolution(exactInvocation, registry);
942
+ if (
943
+ exactResolution.plugin.id !== resolution.plugin.id
944
+ || exactResolution.binding !== resolution.binding
945
+ || exactResolution.binding.messaging?.contractId
946
+ !== resolution.messaging.contractId
947
+ || exactResolution.operation.name
948
+ !== resolution.messaging.resolveRoute.operation
949
+ ) throw new Error("exact messaging route resolution changed provider binding");
950
+ const live = await executeReadInvocation(exactInvocation, {
951
+ headed: false,
952
+ environment,
953
+ registry,
954
+ ...(options.signal === undefined ? {} : { signal: options.signal }),
955
+ });
956
+ if (live.receipt.status !== "succeeded") {
957
+ throw new Error("exact messaging route live read did not succeed");
958
+ }
959
+ const exactDataRevision = sha256(canonicalJson(live.output));
960
+ const candidates = resolution.messaging.resolveRoute.candidates(
961
+ storedTarget,
962
+ live.output,
963
+ );
964
+ if (!Array.isArray(candidates) || candidates.length !== 1) {
965
+ throw new Error(
966
+ Array.isArray(candidates) && candidates.length === 0
967
+ ? "exact messaging route candidate was not proven by the live provider read"
968
+ : "exact messaging route candidate was ambiguous in the live provider read",
969
+ );
970
+ }
971
+ const candidate = candidates[0]!;
972
+ const target = resolution.messaging.parseTarget(candidate.target);
973
+ if (
974
+ candidate.conversationProviderId.length < 1
975
+ || candidate.conversationProviderId.length > 4_096
976
+ || !Array.isArray(candidate.participants)
977
+ || candidate.participants.length > 10_000
978
+ ) throw new Error("provider messaging route candidate is malformed");
979
+ if (
980
+ canonicalJson(target) !== canonicalJson(storedTarget)
981
+ || candidate.conversationProviderId !== listCandidate.conversationProviderId
982
+ ) {
983
+ throw new Error("exact messaging route read returned another conversation");
984
+ }
985
+ const identity = identityFor(sourceInvocation, resolution, registry);
986
+ const sourceConversationCoordinate =
987
+ resolution.messaging.resolveRoute.sourceConversationCoordinate(
988
+ storedTarget,
989
+ live.output,
990
+ identity.auth.subject,
991
+ );
992
+ const network = resolution.messaging.network;
993
+ const routeRef = opaque("wmroute");
994
+ const routeRecord: MessagingRouteRecordV1 = Object.freeze({
995
+ schemaVersion: 1,
996
+ format: "wrench.messaging-route-record",
997
+ routeRef,
998
+ createdAt,
999
+ expiresAt,
1000
+ ...identity,
1001
+ list: Object.freeze({
1002
+ operation: "messaging.list",
1003
+ input: sourceInvocation.input,
1004
+ inputHash: sha256(canonicalJson(sourceInvocation.input)),
1005
+ exactDataRevision,
1006
+ validatedAt: createdAt,
1007
+ }),
1008
+ target,
1009
+ resolution: "exact-coordinate",
1010
+ network,
1011
+ sourceConversationCoordinate,
1012
+ conversationProviderId: candidate.conversationProviderId,
1013
+ conversation: Object.freeze({
1014
+ kind: candidate.conversationKind,
1015
+ title: candidate.title,
1016
+ participantCount: candidate.participants.length,
1017
+ participantFingerprint: sha256(canonicalJson(candidate.participants)),
1018
+ providerRevision: candidate.providerRevision,
1019
+ }),
1020
+ });
1021
+ saveMessagingRouteRecord(routeRecord, environment);
1022
+ const action = resolution.messaging.action;
1023
+ return Object.freeze({
1024
+ schemaVersion: 2,
1025
+ format: "wrench.messaging-route",
1026
+ routeRef,
1027
+ network,
1028
+ conversation: Object.freeze({
1029
+ kind: candidate.conversationKind,
1030
+ title: candidate.title,
1031
+ participantCount: candidate.participants.length,
1032
+ }),
1033
+ readiness: Object.freeze({
1034
+ context: resolution.messaging.contextLiveness === "fresh-as-of-live-preflight"
1035
+ ? "ready"
1036
+ : "historical-readable",
1037
+ turn:
1038
+ sourceConversationCoordinate !== null
1039
+ &&
1040
+ action.state === "supported"
1041
+ && resolution.messaging.contextLiveness === "fresh-as-of-live-preflight"
1042
+ ? "ready"
1043
+ : "unavailable",
1044
+ reply:
1045
+ sourceConversationCoordinate !== null
1046
+ &&
1047
+ action.state === "supported"
1048
+ && resolution.messaging.contextLiveness === "fresh-as-of-live-preflight"
1049
+ ? action.reply
1050
+ : "unsupported",
1051
+ reason: resolution.messaging.contextLiveness === "freshness-unproven"
1052
+ ? "provider context freshness is unproven"
1053
+ : action.state === "supported"
1054
+ ? sourceConversationCoordinate === null
1055
+ ? "this exact route is not eligible for checked turn actions"
1056
+ : null
1057
+ : action.reason,
1058
+ }),
1059
+ completeness: Object.freeze({
1060
+ kind: "complete",
1061
+ reason: "provider-native exact read proved the stored route candidate",
1062
+ }),
1063
+ expiresAt,
1064
+ });
1065
+ }
1066
+
1067
+ async function resolveCurrentRoute(
1068
+ routeRef: string,
1069
+ observation: Date,
1070
+ environment: Environment,
1071
+ registry: ProviderPluginRegistry,
1072
+ ): Promise<{
1073
+ readonly record: MessagingRouteRecordV1;
1074
+ readonly resolution: MessagingResolution;
1075
+ }> {
1076
+ const record = loadMessagingRouteRecord(routeRef, environment, observation);
1077
+ if (record.resolution !== "exact-coordinate") {
1078
+ throw new Error(
1079
+ "messaging route requires exact conversations.read resolution before context or action use",
1080
+ );
1081
+ }
1082
+ const invocation = prepareInvocation(
1083
+ record.adapter.id,
1084
+ record.list.operation,
1085
+ record.list.input,
1086
+ record.auth.id,
1087
+ environment,
1088
+ registry,
1089
+ );
1090
+ const resolution = messagingResolution(invocation, registry, "messaging.list");
1091
+ assertRouteIdentity(record, invocation, resolution, registry, observation);
1092
+ await requireRuntimeReady(resolution.binding, invocation.auth, environment, registry);
1093
+ return Object.freeze({ record, resolution });
1094
+ }
1095
+
1096
+ async function currentContextPage(
1097
+ record: MessagingRouteRecordV1,
1098
+ resolution: MessagingResolution,
1099
+ limit: number,
1100
+ environment: Environment,
1101
+ registry: ProviderPluginRegistry,
1102
+ signal?: AbortSignal,
1103
+ ): Promise<{
1104
+ readonly exactDataRevision: string;
1105
+ readonly latestMessageRevision: string;
1106
+ readonly page: ProviderMaterializedPageV1;
1107
+ readonly messages: readonly ProviderMessageV1[];
1108
+ }> {
1109
+ const target = resolution.messaging.parseTarget(record.target);
1110
+ const input = resolution.messaging.contextInput(target, limit);
1111
+ const invocation = prepareInvocation(
1112
+ record.adapter.id,
1113
+ resolution.messaging.contextOperation,
1114
+ input,
1115
+ record.auth.id,
1116
+ environment,
1117
+ registry,
1118
+ );
1119
+ const contextResolution = messagingResolution(invocation, registry, "messaging.read");
1120
+ if (
1121
+ contextResolution.plugin.id !== resolution.plugin.id
1122
+ || contextResolution.binding !== resolution.binding
1123
+ || contextResolution.messaging.contractId !== resolution.messaging.contractId
1124
+ ) throw new Error("messaging context operation changed provider route");
1125
+ const live = await executeReadInvocation(invocation, {
1126
+ headed: false,
1127
+ environment,
1128
+ registry,
1129
+ ...(signal === undefined ? {} : { signal }),
1130
+ });
1131
+ if (live.receipt.status !== "succeeded") {
1132
+ throw new Error("messaging context live preflight did not succeed");
1133
+ }
1134
+ const exactDataRevision = sha256(canonicalJson(live.output));
1135
+ const page = exactLivePage(invocation, contextResolution, live.output);
1136
+ const messages = pageMessages(page, record.conversationProviderId);
1137
+ return Object.freeze({
1138
+ exactDataRevision,
1139
+ latestMessageRevision: latestRevision(
1140
+ resolution.binding.surfaceId,
1141
+ record.conversationProviderId,
1142
+ messages,
1143
+ exactDataRevision,
1144
+ ),
1145
+ page,
1146
+ messages,
1147
+ });
1148
+ }
1149
+
1150
+ async function currentMessagingRouteState(
1151
+ record: MessagingRouteRecordV1,
1152
+ resolution: MessagingResolution,
1153
+ environment: Environment,
1154
+ registry: ProviderPluginRegistry,
1155
+ signal?: AbortSignal,
1156
+ ): Promise<{
1157
+ readonly revision: string;
1158
+ readonly recipient: MessagingPreviewV1["recipient"];
1159
+ }> {
1160
+ const action = resolution.messaging.action;
1161
+ if (action.state !== "supported") {
1162
+ throw new Error("messaging route does not support an actionable live preflight");
1163
+ }
1164
+ const target = resolution.messaging.parseTarget(record.target);
1165
+ const invocation = prepareInvocation(
1166
+ record.adapter.id,
1167
+ action.livePreflight.operation,
1168
+ action.livePreflight.input(target),
1169
+ record.auth.id,
1170
+ environment,
1171
+ registry,
1172
+ );
1173
+ const liveResolution = operationResolution(invocation, registry);
1174
+ if (
1175
+ liveResolution.binding !== resolution.binding
1176
+ || liveResolution.plugin.id !== resolution.plugin.id
1177
+ ) throw new Error("messaging live preflight changed provider binding");
1178
+ const live = await executeReadInvocation(invocation, {
1179
+ headed: false,
1180
+ environment,
1181
+ registry,
1182
+ ...(signal === undefined ? {} : { signal }),
1183
+ });
1184
+ if (live.receipt.status !== "succeeded") {
1185
+ throw new Error("messaging live route preflight did not succeed");
1186
+ }
1187
+ const snapshot = action.livePreflight.snapshot(
1188
+ live.output,
1189
+ record.auth.subject,
1190
+ );
1191
+ const sourceConversationCoordinate =
1192
+ parseMessageLikeMeSourceConversationCoordinateBindingV1(
1193
+ snapshot.sourceConversationCoordinate,
1194
+ );
1195
+ if (
1196
+ typeof snapshot.conversationProviderId !== "string"
1197
+ || snapshot.conversationProviderId !== record.conversationProviderId
1198
+ || typeof snapshot.network !== "string"
1199
+ || !/^[a-z][a-z0-9-]{0,63}$/u.test(snapshot.network)
1200
+ || snapshot.conversation.kind !== "single"
1201
+ && snapshot.conversation.kind !== "group"
1202
+ && snapshot.conversation.kind !== "unknown"
1203
+ || snapshot.conversation.title !== null
1204
+ && (typeof snapshot.conversation.title !== "string"
1205
+ || Buffer.byteLength(snapshot.conversation.title, "utf8") > 4_096
1206
+ || /[\0\r\n]/u.test(snapshot.conversation.title))
1207
+ || typeof snapshot.conversation.participantCount !== "number"
1208
+ || !Number.isSafeInteger(snapshot.conversation.participantCount)
1209
+ || snapshot.conversation.participantCount < 0
1210
+ || snapshot.conversation.participantCount > 10_000
1211
+ || typeof snapshot.participantFingerprint !== "string"
1212
+ || !/^[a-f0-9]{64}$/u.test(snapshot.participantFingerprint)
1213
+ || snapshot.providerRevision !== null
1214
+ && (typeof snapshot.providerRevision !== "string"
1215
+ || snapshot.providerRevision.length < 1
1216
+ || Buffer.byteLength(snapshot.providerRevision, "utf8") > 4_096
1217
+ || /[\0\r\n]/u.test(snapshot.providerRevision))
1218
+ ) throw new Error("messaging live route preflight returned malformed identity state");
1219
+ if (
1220
+ snapshot.network !== record.network
1221
+ || snapshot.conversation.kind !== record.conversation.kind
1222
+ || snapshot.conversation.title !== record.conversation.title
1223
+ || snapshot.conversation.participantCount
1224
+ !== record.conversation.participantCount
1225
+ || sourceConversationCoordinate.sha256
1226
+ !== record.sourceConversationCoordinate?.sha256
1227
+ || snapshot.participantFingerprint !== record.conversation.participantFingerprint
1228
+ || snapshot.providerRevision !== record.conversation.providerRevision
1229
+ ) throw new Error("messaging route recipient or provider state changed; resolve a new route");
1230
+ const recipient = Object.freeze({
1231
+ network: snapshot.network,
1232
+ conversation: Object.freeze({
1233
+ kind: snapshot.conversation.kind,
1234
+ title: snapshot.conversation.title,
1235
+ participantCount: snapshot.conversation.participantCount,
1236
+ }),
1237
+ });
1238
+ return Object.freeze({
1239
+ revision: sha256(canonicalJson({
1240
+ conversationProviderId: snapshot.conversationProviderId,
1241
+ recipient,
1242
+ sourceConversationCoordinate,
1243
+ participantFingerprint: snapshot.participantFingerprint,
1244
+ providerRevision: snapshot.providerRevision,
1245
+ })),
1246
+ recipient,
1247
+ });
1248
+ }
1249
+
1250
+ export async function readMessagingContextInternal(
1251
+ value: unknown,
1252
+ options: MessagingRuntimeOptions = {},
1253
+ ): Promise<MessagingContextV1> {
1254
+ const request = parseMessagingContextRequestV1(value);
1255
+ const environment = options.environment ?? process.env;
1256
+ const registry = options.registry ?? providerPluginRegistry;
1257
+ const observation = now(options);
1258
+ const { record, resolution } = await resolveCurrentRoute(
1259
+ request.routeRef,
1260
+ observation,
1261
+ environment,
1262
+ registry,
1263
+ );
1264
+ if (
1265
+ record.sourceConversationCoordinate !== null
1266
+ && resolution.messaging.action.state === "supported"
1267
+ ) {
1268
+ await currentMessagingRouteState(
1269
+ record,
1270
+ resolution,
1271
+ environment,
1272
+ registry,
1273
+ options.signal,
1274
+ );
1275
+ }
1276
+ const current = await currentContextPage(
1277
+ record,
1278
+ resolution,
1279
+ request.limit,
1280
+ environment,
1281
+ registry,
1282
+ options.signal,
1283
+ );
1284
+ const validationObservation = options.now ?? new Date();
1285
+ if (!Number.isFinite(validationObservation.getTime())) {
1286
+ throw new Error("messaging live-preflight completion time is invalid");
1287
+ }
1288
+ const validatedAt = validationObservation.toISOString();
1289
+ const expiresAt = new Date(
1290
+ validationObservation.getTime() + CONTEXT_TTL_MS,
1291
+ ).toISOString();
1292
+ const contextRef = opaque("wmcontext");
1293
+ const references = randomRefMap(current.messages);
1294
+ const binding: MessagingContextBindingV2 | null =
1295
+ record.sourceConversationCoordinate === null
1296
+ ? null
1297
+ : parseMessagingContextBindingV2({
1298
+ schemaVersion: 2,
1299
+ format: "wrench.messaging-context-binding",
1300
+ contractId: MESSAGING_CONTEXT_BINDING_CONTRACT_ID,
1301
+ contractHash: MESSAGING_CONTEXT_BINDING_CONTRACT_HASH,
1302
+ sourceConversationCoordinate: record.sourceConversationCoordinate,
1303
+ routeRef: record.routeRef,
1304
+ contextRef,
1305
+ exactDataRevision: current.exactDataRevision,
1306
+ latestMessageRevision: current.latestMessageRevision,
1307
+ validatedAt,
1308
+ expiresAt,
1309
+ });
1310
+ if (binding !== null) {
1311
+ const contextRecord: MessagingContextRecordV1 = Object.freeze({
1312
+ schemaVersion: 1,
1313
+ format: "wrench.messaging-context-record",
1314
+ contextRef,
1315
+ routeRef: record.routeRef,
1316
+ routeRecordHash: messagingRouteRecordHash(record),
1317
+ sourceConversationCoordinate: binding.sourceConversationCoordinate,
1318
+ exactDataRevision: current.exactDataRevision,
1319
+ latestMessageRevision: current.latestMessageRevision,
1320
+ validatedAt,
1321
+ expiresAt,
1322
+ limit: request.limit,
1323
+ liveness: resolution.messaging.contextLiveness,
1324
+ replyTargets: references.replyTargets,
1325
+ });
1326
+ saveMessagingContextRecord(contextRecord, environment);
1327
+ }
1328
+ return Object.freeze({
1329
+ schemaVersion: 1,
1330
+ format: "wrench.messaging-context",
1331
+ binding,
1332
+ network: record.network,
1333
+ liveness: resolution.messaging.contextLiveness,
1334
+ truncated:
1335
+ current.page.completeness.kind !== "complete"
1336
+ || current.messages.some((message) => message.bodyTruncated === true),
1337
+ completeness: current.page.completeness,
1338
+ messages: publicMessages(current.messages, references.byProvider),
1339
+ warnings: Object.freeze([
1340
+ ...(resolution.messaging.contextLiveness === "fresh-as-of-live-preflight"
1341
+ ? ["fresh-as-of-live-preflight-only"]
1342
+ : []),
1343
+ ...(resolution.messaging.contextLiveness === "freshness-unproven"
1344
+ ? ["provider-freshness-unproven-action-blocked"]
1345
+ : []),
1346
+ ...(current.page.completeness.kind === "complete"
1347
+ ? []
1348
+ : ["provider-history-incomplete"]),
1349
+ ]),
1350
+ });
1351
+ }
1352
+
1353
+ export async function previewMessagingTurnInternal(
1354
+ value: unknown,
1355
+ options: MessagingRuntimeOptions = {},
1356
+ ): Promise<MessagingPreviewV1> {
1357
+ const turn = parseMessagingTurnV1(value);
1358
+ const environment = options.environment ?? process.env;
1359
+ const registry = options.registry ?? providerPluginRegistry;
1360
+ const observation = now(options);
1361
+ const { record, resolution } = await resolveCurrentRoute(
1362
+ turn.routeRef,
1363
+ observation,
1364
+ environment,
1365
+ registry,
1366
+ );
1367
+ if (record.sourceConversationCoordinate === null) {
1368
+ throw new Error("messaging route is not eligible for checked turn actions");
1369
+ }
1370
+ if (resolution.messaging.action.state !== "supported") {
1371
+ throw new Error("messaging route does not support checked turn actions");
1372
+ }
1373
+ const action = resolution.messaging.action;
1374
+ const context = loadMessagingContextRecord(
1375
+ turn.contextRef,
1376
+ environment,
1377
+ observation,
1378
+ );
1379
+ if (
1380
+ context.routeRef !== record.routeRef
1381
+ || context.routeRecordHash !== messagingRouteRecordHash(record)
1382
+ || context.sourceConversationCoordinate === null
1383
+ || context.sourceConversationCoordinate.sha256
1384
+ !== record.sourceConversationCoordinate.sha256
1385
+ || Date.parse(context.expiresAt) <= observation.getTime()
1386
+ ) throw new Error("messaging context is stale or belongs to another route");
1387
+ if (context.liveness !== "fresh-as-of-live-preflight") {
1388
+ throw new Error("messaging context freshness is unproven; no action preview is allowed");
1389
+ }
1390
+ const current = await currentContextPage(
1391
+ record,
1392
+ resolution,
1393
+ context.limit,
1394
+ environment,
1395
+ registry,
1396
+ options.signal,
1397
+ );
1398
+ if (
1399
+ current.exactDataRevision !== context.exactDataRevision
1400
+ || current.latestMessageRevision !== context.latestMessageRevision
1401
+ ) throw new Error("messaging context changed; read a new context before preview");
1402
+ const exactContextBinding = parseMessagingContextBindingV2({
1403
+ schemaVersion: 2,
1404
+ format: "wrench.messaging-context-binding",
1405
+ contractId: MESSAGING_CONTEXT_BINDING_CONTRACT_ID,
1406
+ contractHash: MESSAGING_CONTEXT_BINDING_CONTRACT_HASH,
1407
+ sourceConversationCoordinate: context.sourceConversationCoordinate,
1408
+ routeRef: context.routeRef,
1409
+ contextRef: context.contextRef,
1410
+ exactDataRevision: context.exactDataRevision,
1411
+ latestMessageRevision: context.latestMessageRevision,
1412
+ validatedAt: context.validatedAt,
1413
+ expiresAt: context.expiresAt,
1414
+ });
1415
+ const contextBindingSha256 =
1416
+ ghostgetMessagingContextBindingSha256V2(exactContextBinding);
1417
+ const baseRouteState = await currentMessagingRouteState(
1418
+ record,
1419
+ resolution,
1420
+ environment,
1421
+ registry,
1422
+ options.signal,
1423
+ );
1424
+ const target = resolution.messaging.parseTarget(record.target);
1425
+ const plannedParts = turn.parts.map((part) => {
1426
+ const replyToProviderId = part.replyRef === null
1427
+ ? null
1428
+ : context.replyTargets[part.replyRef];
1429
+ if (part.replyRef !== null && replyToProviderId === undefined) {
1430
+ throw new Error("messaging reply reference is not part of the bound context");
1431
+ }
1432
+ if (replyToProviderId !== null && action.reply !== "supported") {
1433
+ throw new Error("messaging route does not support exact replies");
1434
+ }
1435
+ const input: OperationInput = action.compileTurnPart(
1436
+ target,
1437
+ Object.freeze({
1438
+ partId: part.partId,
1439
+ text: part.text,
1440
+ replyToProviderId: replyToProviderId ?? null,
1441
+ }),
1442
+ );
1443
+ const invocation = prepareInvocation(
1444
+ record.adapter.id,
1445
+ action.operation,
1446
+ input,
1447
+ record.auth.id,
1448
+ environment,
1449
+ registry,
1450
+ );
1451
+ const actionResolution = messagingResolution(invocation, registry, "action");
1452
+ if (
1453
+ actionResolution.binding !== resolution.binding
1454
+ || actionResolution.plugin.id !== resolution.plugin.id
1455
+ || actionResolution.messaging.contractId !== resolution.messaging.contractId
1456
+ ) throw new Error("messaging action changed provider route");
1457
+ return Object.freeze({
1458
+ partId: part.partId,
1459
+ text: part.text,
1460
+ replyRef: part.replyRef,
1461
+ replyToProviderId: replyToProviderId ?? null,
1462
+ invocation,
1463
+ });
1464
+ });
1465
+ const initial = createMessagingCompositeInvocationPlan(
1466
+ plannedParts,
1467
+ Object.freeze({
1468
+ routeRef: record.routeRef,
1469
+ contextRef: context.contextRef,
1470
+ clientIntentSha256: turn.clientIntentSha256,
1471
+ contextBindingSha256,
1472
+ sourceConversationCoordinateSha256:
1473
+ exactContextBinding.sourceConversationCoordinate.sha256,
1474
+ turnDigest: messagingTurnDigest(turn),
1475
+ contextLimit: context.limit,
1476
+ baseExactDataRevision: context.exactDataRevision,
1477
+ baseLatestMessageRevision: context.latestMessageRevision,
1478
+ baseRouteStateRevision: baseRouteState.revision,
1479
+ baseMessages: messagingBaseMessages(current.messages),
1480
+ recipient: baseRouteState.recipient,
1481
+ }),
1482
+ observation,
1483
+ registry,
1484
+ );
1485
+ const initialComposite = initial.plan.messagingComposite;
1486
+ if (initialComposite === undefined) throw new Error("messaging composite plan disappeared");
1487
+ const preview: MessagingPreviewV1 = Object.freeze({
1488
+ schemaVersion: 1,
1489
+ format: "wrench.messaging-preview",
1490
+ status: "confirmation-required",
1491
+ planDigest: initial.digest,
1492
+ expiresAt: initial.plan.expiresAt,
1493
+ routeRef: record.routeRef,
1494
+ contextRef: context.contextRef,
1495
+ clientIntentSha256: turn.clientIntentSha256,
1496
+ turnDigest: messagingTurnDigest(turn),
1497
+ recipient: initialComposite.recipient,
1498
+ partCount: turn.parts.length,
1499
+ bubbles: Object.freeze(turn.parts.map((part) => Object.freeze({
1500
+ partId: part.partId,
1501
+ text: part.text,
1502
+ replyRef: part.replyRef,
1503
+ }))),
1504
+ risk: "R3",
1505
+ sideEffect: initial.plan.sideEffect,
1506
+ });
1507
+ const stored = bindMessagingCompositePreviewDigest(
1508
+ initial,
1509
+ sha256(canonicalJson(preview)),
1510
+ );
1511
+ if (invocationPlanDigest(stored.plan) !== preview.planDigest) {
1512
+ throw new Error("messaging preview changed its confirmation digest");
1513
+ }
1514
+ saveInvocationPlan(stored, environment);
1515
+ return preview;
1516
+ }
1517
+
1518
+ export function previewFromStoredMessagingPlan(stored: StoredPlan): MessagingPreviewV1 {
1519
+ const composite = stored.plan.messagingComposite;
1520
+ if (composite === undefined) throw new Error("confirmation plan is not a messaging composite");
1521
+ return Object.freeze({
1522
+ schemaVersion: 1,
1523
+ format: "wrench.messaging-preview",
1524
+ status: "confirmation-required",
1525
+ planDigest: stored.digest,
1526
+ expiresAt: stored.plan.expiresAt,
1527
+ routeRef: composite.routeRef,
1528
+ contextRef: composite.contextRef,
1529
+ clientIntentSha256: composite.clientIntentSha256,
1530
+ turnDigest: composite.turnDigest,
1531
+ recipient: composite.recipient,
1532
+ partCount: composite.parts.length,
1533
+ bubbles: Object.freeze(composite.parts.map((part) => Object.freeze({
1534
+ partId: part.partId,
1535
+ text: part.text,
1536
+ replyRef: part.replyRef,
1537
+ }))),
1538
+ risk: "R3",
1539
+ sideEffect: stored.plan.sideEffect,
1540
+ });
1541
+ }
1542
+
1543
+ export function verifyStoredMessagingPreview(stored: StoredPlan): MessagingPreviewV1 {
1544
+ const composite = stored.plan.messagingComposite;
1545
+ if (composite === undefined) throw new Error("confirmation plan is not a messaging composite");
1546
+ const preview = previewFromStoredMessagingPlan(stored);
1547
+ if (sha256(canonicalJson(preview)) !== composite.previewDigest) {
1548
+ throw new Error("messaging preview digest does not match the exact reconstructed artifact");
1549
+ }
1550
+ return preview;
1551
+ }
1552
+
1553
+ export function initializeMessagingCompositeRunInternal(
1554
+ stored: StoredPlan,
1555
+ runId: string,
1556
+ options: Pick<MessagingRuntimeOptions, "environment" | "now"> = {},
1557
+ ): MessagingRunSnapshotV1 {
1558
+ const composite = stored.plan.messagingComposite;
1559
+ if (composite === undefined) {
1560
+ throw new Error("confirmation plan is not a messaging composite");
1561
+ }
1562
+ if (
1563
+ composite.contextBindingSha256 === null
1564
+ || composite.sourceConversationCoordinateSha256 === null
1565
+ ) {
1566
+ throw new Error(
1567
+ "predecessor messaging plan lacks current context evidence; preview the action again",
1568
+ );
1569
+ }
1570
+ verifyStoredMessagingPreview(stored);
1571
+ return initializeMessagingRun(
1572
+ runId,
1573
+ stored.digest,
1574
+ composite,
1575
+ options.environment ?? process.env,
1576
+ now(options).toISOString(),
1577
+ );
1578
+ }
1579
+
1580
+ function messagingTransitionTime(
1581
+ snapshot: MessagingRunSnapshotV1,
1582
+ options: MessagingRuntimeOptions,
1583
+ ): string {
1584
+ const observed = now(options).toISOString();
1585
+ return Date.parse(observed) < Date.parse(snapshot.run.recordedAt)
1586
+ ? snapshot.run.recordedAt
1587
+ : observed;
1588
+ }
1589
+
1590
+ function stopMessagingBeforeDispatch(
1591
+ snapshot: MessagingRunSnapshotV1,
1592
+ reason:
1593
+ | "context-drift"
1594
+ | "prefix-freshness-unproven"
1595
+ | "provider-failed-before-dispatch"
1596
+ | "journal-recovery-required",
1597
+ options: MessagingRuntimeOptions,
1598
+ ): MessagingRunSnapshotV1 {
1599
+ return updateMessagingRun(snapshot, {
1600
+ type: "categorical-stop",
1601
+ index: snapshot.run.provenPartCount,
1602
+ partState: snapshot.run.provenPartCount === 0
1603
+ ? "failed-before-dispatch"
1604
+ : "failed-permanent",
1605
+ reason,
1606
+ at: messagingTransitionTime(snapshot, options),
1607
+ }, options.environment ?? process.env);
1608
+ }
1609
+
1610
+ function stopMessagingIndeterminate(
1611
+ snapshot: MessagingRunSnapshotV1,
1612
+ reason: "provider-result-indeterminate" | "journal-recovery-required",
1613
+ options: MessagingRuntimeOptions,
1614
+ privateProviderOutcome: MessagingPrivateProviderOutcomeV1 | null = null,
1615
+ ): MessagingRunSnapshotV1 {
1616
+ return updateMessagingRun(
1617
+ snapshot,
1618
+ reason === "provider-result-indeterminate"
1619
+ ? {
1620
+ type: "indeterminate",
1621
+ index: snapshot.run.provenPartCount,
1622
+ reason,
1623
+ privateProviderOutcome,
1624
+ at: messagingTransitionTime(snapshot, options),
1625
+ }
1626
+ : {
1627
+ type: "indeterminate",
1628
+ index: snapshot.run.provenPartCount,
1629
+ reason,
1630
+ at: messagingTransitionTime(snapshot, options),
1631
+ },
1632
+ options.environment ?? process.env,
1633
+ );
1634
+ }
1635
+
1636
+ function stopMessagingAfterError(
1637
+ snapshot: MessagingRunSnapshotV1,
1638
+ options: MessagingRuntimeOptions,
1639
+ ): MessagingRunSnapshotV1 {
1640
+ const active = snapshot.run.parts[snapshot.run.provenPartCount];
1641
+ if (active?.state === "dispatching") {
1642
+ return stopMessagingIndeterminate(
1643
+ snapshot,
1644
+ "provider-result-indeterminate",
1645
+ options,
1646
+ );
1647
+ }
1648
+ if (active?.state === "claimed" || active?.state === "unattempted") {
1649
+ return stopMessagingBeforeDispatch(
1650
+ snapshot,
1651
+ "provider-failed-before-dispatch",
1652
+ options,
1653
+ );
1654
+ }
1655
+ if (snapshot.run.state !== "pending") return snapshot;
1656
+ throw new Error("messaging run error state could not be classified");
1657
+ }
1658
+
1659
+ function stopMessagingAfterCaughtError(
1660
+ snapshot: MessagingRunSnapshotV1,
1661
+ options: MessagingRuntimeOptions,
1662
+ ): MessagingRunSnapshotV1 {
1663
+ try {
1664
+ return stopMessagingAfterError(snapshot, options);
1665
+ } catch {
1666
+ const environment = options.environment ?? process.env;
1667
+ const latest = readMessagingRun(snapshot.run.runId, environment);
1668
+ if (latest.run.state !== "pending") return latest;
1669
+ const active = latest.run.parts[latest.run.provenPartCount];
1670
+ if (active?.state === "dispatching") {
1671
+ return stopMessagingIndeterminate(
1672
+ latest,
1673
+ "journal-recovery-required",
1674
+ options,
1675
+ );
1676
+ }
1677
+ return stopMessagingBeforeDispatch(
1678
+ latest,
1679
+ "journal-recovery-required",
1680
+ options,
1681
+ );
1682
+ }
1683
+ }
1684
+
1685
+ /**
1686
+ * Execute one already-owned and consumed composite confirmation. The caller
1687
+ * owns the outer confirmation claim and ordinary receipt projection. This
1688
+ * function owns the one encrypted per-bubble journal and never retries.
1689
+ */
1690
+ export async function executeMessagingCompositeInternal(
1691
+ stored: StoredPlan,
1692
+ invocation: PreparedInvocation,
1693
+ initialSnapshot: MessagingRunSnapshotV1,
1694
+ options: MessagingRuntimeOptions = {},
1695
+ ): Promise<MessagingRunSnapshotV1> {
1696
+ const composite = stored.plan.messagingComposite;
1697
+ if (composite === undefined) {
1698
+ throw new Error("confirmation plan is not a messaging composite");
1699
+ }
1700
+ if (
1701
+ composite.contextBindingSha256 === null
1702
+ || composite.sourceConversationCoordinateSha256 === null
1703
+ ) {
1704
+ throw new Error(
1705
+ "predecessor messaging plan lacks current context evidence; preview the action again",
1706
+ );
1707
+ }
1708
+ if (
1709
+ initialSnapshot.run.planDigest !== stored.digest
1710
+ || initialSnapshot.run.turnDigest !== composite.turnDigest
1711
+ || initialSnapshot.run.contextBindingSha256
1712
+ !== composite.contextBindingSha256
1713
+ || initialSnapshot.run.sourceConversationCoordinateSha256
1714
+ !== composite.sourceConversationCoordinateSha256
1715
+ || initialSnapshot.run.state !== "pending"
1716
+ ) throw new Error("messaging run does not own the exact composite plan");
1717
+ const environment = options.environment ?? process.env;
1718
+ const registry = options.registry ?? providerPluginRegistry;
1719
+ const actionResolution = messagingResolution(invocation, registry, "action");
1720
+ const action = actionResolution.messaging.action;
1721
+ if (action.state !== "supported") {
1722
+ throw new Error("messaging action support disappeared after confirmation");
1723
+ }
1724
+ if (actionResolution.binding.executeMessagingPart === undefined) {
1725
+ throw new Error("messaging provider has no private action executor");
1726
+ }
1727
+ if (actionResolution.binding.transport === "provider-api") {
1728
+ throw new Error(
1729
+ "messaging actions require a cleanup-qualified session or local CLI transport",
1730
+ );
1731
+ }
1732
+ const operation = invocation.manifest.operations[invocation.operationId];
1733
+ if (operation === undefined) {
1734
+ throw new Error("messaging action lost its exact operation recipe");
1735
+ }
1736
+ const actionTimeoutMs = isLocalCliOperation(operation)
1737
+ ? operation.localCli.timeoutMs
1738
+ : isWebSessionOperation(operation)
1739
+ ? operation.webSession.timeoutMs
1740
+ : null;
1741
+ if (actionTimeoutMs === null) {
1742
+ throw new Error("messaging action has no cleanup-qualified bounded recipe");
1743
+ }
1744
+ const boundAuth = invocation.auth;
1745
+ requireBoundAuth(actionResolution.binding, boundAuth);
1746
+ let snapshot = initialSnapshot;
1747
+ for (let index = snapshot.run.provenPartCount; index < composite.parts.length; index += 1) {
1748
+ if (snapshot.run.state !== "pending" || snapshot.run.provenPartCount !== index) {
1749
+ return snapshot;
1750
+ }
1751
+ if (options.signal?.aborted === true) {
1752
+ return stopMessagingBeforeDispatch(
1753
+ snapshot,
1754
+ "provider-failed-before-dispatch",
1755
+ options,
1756
+ );
1757
+ }
1758
+ const operationDeadline = new OperationDeadline(actionTimeoutMs, {
1759
+ ...(options.signal === undefined ? {} : { signal: options.signal }),
1760
+ ...(options.deadlineClock === undefined
1761
+ ? {}
1762
+ : { clock: options.deadlineClock }),
1763
+ });
1764
+ try {
1765
+ snapshot = await operationDeadline.run(async () => {
1766
+ try {
1767
+ await requireRuntimeReady(
1768
+ actionResolution.binding,
1769
+ boundAuth,
1770
+ environment,
1771
+ registry,
1772
+ );
1773
+ } catch {
1774
+ operationDeadline.throwIfUnavailable("messaging provider action");
1775
+ return stopMessagingBeforeDispatch(
1776
+ snapshot,
1777
+ "prefix-freshness-unproven",
1778
+ options,
1779
+ );
1780
+ }
1781
+ let record: MessagingRouteRecordV1;
1782
+ let routeResolution: MessagingResolution;
1783
+ let current: Awaited<ReturnType<typeof currentContextPage>>;
1784
+ let routeState: Awaited<ReturnType<typeof currentMessagingRouteState>>;
1785
+ try {
1786
+ const route = await resolveCurrentRoute(
1787
+ composite.routeRef,
1788
+ now(options),
1789
+ environment,
1790
+ registry,
1791
+ );
1792
+ record = route.record;
1793
+ routeResolution = route.resolution;
1794
+ if (
1795
+ routeResolution.binding !== actionResolution.binding
1796
+ || routeResolution.plugin.id !== actionResolution.plugin.id
1797
+ || routeResolution.messaging.contractId !== actionResolution.messaging.contractId
1798
+ ) {
1799
+ return stopMessagingBeforeDispatch(snapshot, "context-drift", options);
1800
+ }
1801
+ current = await currentContextPage(
1802
+ record,
1803
+ routeResolution,
1804
+ composite.contextLimit,
1805
+ environment,
1806
+ registry,
1807
+ operationDeadline.signal,
1808
+ );
1809
+ routeState = await currentMessagingRouteState(
1810
+ record,
1811
+ routeResolution,
1812
+ environment,
1813
+ registry,
1814
+ operationDeadline.signal,
1815
+ );
1816
+ } catch {
1817
+ operationDeadline.throwIfUnavailable("messaging provider action");
1818
+ return stopMessagingBeforeDispatch(
1819
+ snapshot,
1820
+ "prefix-freshness-unproven",
1821
+ options,
1822
+ );
1823
+ }
1824
+ if (
1825
+ routeState.revision !== composite.baseRouteStateRevision
1826
+ || record.sourceConversationCoordinate?.sha256
1827
+ !== composite.sourceConversationCoordinateSha256
1828
+ ) {
1829
+ return stopMessagingBeforeDispatch(snapshot, "context-drift", options);
1830
+ }
1831
+ let proof: ProviderPluginMessagingExpectedOwnPrefixProofV1;
1832
+ try {
1833
+ const accepted = messagingExpectedOwnPrefix(snapshot.run);
1834
+ proof = parseExpectedOwnPrefixProof(action.proveExpectedOwnPrefix(Object.freeze({
1835
+ base: Object.freeze({
1836
+ exactDataRevision: composite.baseExactDataRevision,
1837
+ latestMessageRevision: composite.baseLatestMessageRevision,
1838
+ contextLimit: composite.contextLimit,
1839
+ messages: composite.baseMessages,
1840
+ }),
1841
+ current: Object.freeze({
1842
+ exactDataRevision: current.exactDataRevision,
1843
+ latestMessageRevision: current.latestMessageRevision,
1844
+ messages: current.messages,
1845
+ }),
1846
+ accepted,
1847
+ })), accepted.length);
1848
+ } catch {
1849
+ operationDeadline.throwIfUnavailable("messaging provider action");
1850
+ return stopMessagingBeforeDispatch(
1851
+ snapshot,
1852
+ "prefix-freshness-unproven",
1853
+ options,
1854
+ );
1855
+ }
1856
+ operationDeadline.throwIfUnavailable("messaging provider action");
1857
+ if (
1858
+ proof.state !== "proven"
1859
+ || proof.matchedAcceptedPrefixCount < snapshot.run.observedAcceptedPrefixCount
1860
+ ) {
1861
+ return stopMessagingBeforeDispatch(snapshot, "context-drift", options);
1862
+ }
1863
+ snapshot = updateMessagingRun(snapshot, {
1864
+ type: "claimed",
1865
+ index,
1866
+ observedAcceptedPrefixCount: proof.matchedAcceptedPrefixCount,
1867
+ at: messagingTransitionTime(snapshot, options),
1868
+ }, environment);
1869
+ operationDeadline.throwIfUnavailable("messaging provider action");
1870
+ let crossedExternalBoundary = false;
1871
+ try {
1872
+ const recordPrivateIndeterminateOutcome = async (
1873
+ code: string,
1874
+ ): Promise<void> => {
1875
+ if (!crossedExternalBoundary) {
1876
+ throw new Error(
1877
+ "messaging provider outcome preceded its durable dispatch boundary",
1878
+ );
1879
+ }
1880
+ snapshot = stopMessagingIndeterminate(
1881
+ snapshot,
1882
+ "provider-result-indeterminate",
1883
+ options,
1884
+ Object.freeze({
1885
+ schemaVersion: 1,
1886
+ messagingContractId: actionResolution.messaging.contractId,
1887
+ code,
1888
+ }),
1889
+ );
1890
+ };
1891
+ const beforeExternalBegin = async (): Promise<void> => {
1892
+ operationDeadline.throwIfUnavailable("messaging provider action");
1893
+ if (crossedExternalBoundary) {
1894
+ throw new Error(
1895
+ "messaging provider attempted more than one dispatch boundary",
1896
+ );
1897
+ }
1898
+ snapshot = updateMessagingRun(snapshot, {
1899
+ type: "dispatching",
1900
+ index,
1901
+ at: messagingTransitionTime(snapshot, options),
1902
+ }, environment);
1903
+ crossedExternalBoundary = true;
1904
+ operationDeadline.throwIfUnavailable("messaging provider action");
1905
+ };
1906
+ const executePart = (
1907
+ deadline: Parameters<
1908
+ NonNullable<typeof actionResolution.binding.executeMessagingPart>
1909
+ >[3]["operationDeadline"],
1910
+ registerCleanupBarrier?: Parameters<
1911
+ NonNullable<typeof actionResolution.binding.executeMessagingPart>
1912
+ >[3]["registerCleanupBarrier"],
1913
+ ) => actionResolution.binding.executeMessagingPart!(
1914
+ action.operation,
1915
+ composite.parts[index]!.input,
1916
+ boundAuth,
1917
+ Object.freeze({
1918
+ beforeExternalBegin,
1919
+ recordPrivateIndeterminateOutcome,
1920
+ operationDeadline: deadline,
1921
+ signal: deadline.signal,
1922
+ ...(registerCleanupBarrier === undefined
1923
+ ? {}
1924
+ : { registerCleanupBarrier }),
1925
+ environment,
1926
+ }),
1927
+ );
1928
+ const output = actionResolution.binding.transport === "local-cli"
1929
+ ? await (() => {
1930
+ if (!isLocalCliOperation(operation)) {
1931
+ throw new Error(
1932
+ "local CLI messaging action lost its exact operation recipe",
1933
+ );
1934
+ }
1935
+ return withLocalCliProviderCleanupAdmission(
1936
+ {
1937
+ registry,
1938
+ binding: actionResolution.binding,
1939
+ auth: boundAuth,
1940
+ purpose: {
1941
+ kind: "messaging",
1942
+ action: operation.localCli.action,
1943
+ contractVersion: operation.localCli.contractVersion,
1944
+ messagingRunId: snapshot.run.runId,
1945
+ partIndex: index,
1946
+ },
1947
+ environment,
1948
+ ...(options.now === undefined ? {} : { now: options.now }),
1949
+ },
1950
+ (registerCleanupBarrier) => runLocalCliOperationWithDeadline(
1951
+ operation.localCli,
1952
+ {
1953
+ environment,
1954
+ signal: operationDeadline.signal,
1955
+ operationDeadline,
1956
+ registerCleanupBarrier,
1957
+ },
1958
+ (boundedOptions) => {
1959
+ const boundedDeadline = boundedOptions.operationDeadline;
1960
+ if (boundedDeadline === undefined) {
1961
+ throw new Error(
1962
+ "local CLI messaging deadline is unavailable",
1963
+ );
1964
+ }
1965
+ return executePart(
1966
+ boundedDeadline,
1967
+ boundedOptions.registerCleanupBarrier,
1968
+ );
1969
+ },
1970
+ ),
1971
+ );
1972
+ })()
1973
+ : await (() => {
1974
+ if (!isWebSessionOperation(operation)) {
1975
+ throw new Error(
1976
+ "session messaging action lost its exact operation recipe",
1977
+ );
1978
+ }
1979
+ return withWebSessionCleanupAdmission(
1980
+ messagingWebCleanupAdmissionIdentity(
1981
+ invocation,
1982
+ actionResolution,
1983
+ registry,
1984
+ snapshot.run.runId,
1985
+ index,
1986
+ ),
1987
+ environment,
1988
+ (registerCleanupBarrier) =>
1989
+ runWebSessionOperationWithDeadline(
1990
+ operation.webSession,
1991
+ {
1992
+ environment,
1993
+ signal: operationDeadline.signal,
1994
+ operationDeadline,
1995
+ registerCleanupBarrier,
1996
+ },
1997
+ (boundedOptions) => {
1998
+ const boundedDeadline = boundedOptions.operationDeadline;
1999
+ if (boundedDeadline === undefined) {
2000
+ throw new Error(
2001
+ "session messaging deadline is unavailable",
2002
+ );
2003
+ }
2004
+ return executePart(
2005
+ boundedDeadline,
2006
+ boundedOptions.registerCleanupBarrier,
2007
+ );
2008
+ },
2009
+ ),
2010
+ options.now,
2011
+ );
2012
+ })();
2013
+ if (snapshot.run.state !== "pending") return snapshot;
2014
+ operationDeadline.throwIfUnavailable("messaging provider action");
2015
+ if (!crossedExternalBoundary) {
2016
+ throw new Error(
2017
+ "messaging provider returned without crossing its durable dispatch boundary",
2018
+ );
2019
+ }
2020
+ const accepted = action.mapAcceptedResult(output);
2021
+ if (
2022
+ accepted.state !== "submitted"
2023
+ || typeof accepted.providerMessageId !== "string"
2024
+ || accepted.providerMessageId.length < 1
2025
+ || Buffer.byteLength(accepted.providerMessageId, "utf8") > 4_096
2026
+ || /[\0\r\n]/u.test(accepted.providerMessageId)
2027
+ || accepted.providerRevision !== null
2028
+ && (typeof accepted.providerRevision !== "string"
2029
+ || accepted.providerRevision.length < 1
2030
+ || Buffer.byteLength(accepted.providerRevision, "utf8") > 4_096
2031
+ || /[\0\r\n]/u.test(accepted.providerRevision))
2032
+ ) {
2033
+ throw new Error(
2034
+ "messaging provider returned malformed acceptance evidence",
2035
+ );
2036
+ }
2037
+ const existingProviderMessageIds = new Set([
2038
+ ...composite.baseMessages.map((message) =>
2039
+ message.providerMessageId),
2040
+ ...snapshot.run.parts
2041
+ .slice(0, snapshot.run.provenPartCount)
2042
+ .flatMap((part) => part.providerMessageId === null
2043
+ ? []
2044
+ : [part.providerMessageId]),
2045
+ ]);
2046
+ if (existingProviderMessageIds.has(accepted.providerMessageId)) {
2047
+ throw new Error(
2048
+ "messaging provider reused an existing message identity for acceptance",
2049
+ );
2050
+ }
2051
+ operationDeadline.throwIfUnavailable("messaging provider action");
2052
+ snapshot = updateMessagingRun(snapshot, {
2053
+ type: "accepted",
2054
+ index,
2055
+ providerMessageId: accepted.providerMessageId,
2056
+ providerRevision: accepted.providerRevision,
2057
+ at: messagingTransitionTime(snapshot, options),
2058
+ }, environment);
2059
+ operationDeadline.throwIfUnavailable("messaging provider action");
2060
+ return snapshot;
2061
+ } catch {
2062
+ return stopMessagingAfterCaughtError(snapshot, options);
2063
+ }
2064
+ }, "messaging provider action");
2065
+ } catch {
2066
+ snapshot = stopMessagingAfterCaughtError(snapshot, options);
2067
+ } finally {
2068
+ operationDeadline.dispose();
2069
+ }
2070
+ if (snapshot.run.state !== "pending") return snapshot;
2071
+ }
2072
+ return snapshot;
2073
+ }
2074
+
2075
+ export function loadMessagingPreviewForConfirmationInternal(
2076
+ digest: string,
2077
+ options: Pick<MessagingRuntimeOptions, "environment"> = {},
2078
+ ): MessagingPreviewV1 {
2079
+ const stored = loadInvocationPlan(digest, options.environment ?? process.env);
2080
+ return verifyStoredMessagingPreview(stored);
2081
+ }
2082
+
2083
+ export function showMessagingRunInternal(
2084
+ runId: string,
2085
+ options: Pick<MessagingRuntimeOptions, "environment"> = {},
2086
+ ): {
2087
+ readonly run: MessagingRunV1;
2088
+ readonly receipt: MessagingRunReceipt;
2089
+ readonly receiptBinding: MessagingReceiptBinding;
2090
+ } {
2091
+ const run = readMessagingRun(runId, options.environment ?? process.env).run;
2092
+ if (run.state === "pending") {
2093
+ throw new Error("messaging run is pending or requires checked recovery");
2094
+ }
2095
+ return Object.freeze({
2096
+ run,
2097
+ receipt: messagingRunReceipt(run),
2098
+ receiptBinding: messagingReceiptBinding(run),
2099
+ });
2100
+ }
2101
+
2102
+ export function reconcileMessagingRunInternal(
2103
+ runId: string,
2104
+ options: Pick<MessagingRuntimeOptions, "environment"> = {},
2105
+ ): {
2106
+ readonly schemaVersion: 1;
2107
+ readonly format: "wrench.messaging-reconciliation";
2108
+ readonly runId: string;
2109
+ readonly state: MessagingRunV1["state"];
2110
+ readonly action: "not-required" | "retained-unretriable";
2111
+ readonly receiptBindingSha256: string;
2112
+ readonly reason: string;
2113
+ } {
2114
+ const run = readMessagingRun(
2115
+ runId,
2116
+ options.environment ?? process.env,
2117
+ ).run;
2118
+ if (run.state === "pending") {
2119
+ throw new Error(
2120
+ "messaging run is pending; run ghostget doctor before reconciliation",
2121
+ );
2122
+ }
2123
+ const binding = messagingReceiptBinding(run);
2124
+ const indeterminate = run.state === "indeterminate";
2125
+ return Object.freeze({
2126
+ schemaVersion: 1,
2127
+ format: "wrench.messaging-reconciliation",
2128
+ runId,
2129
+ state: run.state,
2130
+ action: indeterminate ? "retained-unretriable" : "not-required",
2131
+ receiptBindingSha256: binding.receiptSha256,
2132
+ reason: indeterminate
2133
+ ? "the provider result has no exact accepted message identity; the run remains indeterminate and must not be retried"
2134
+ : "the messaging run already has a categorical terminal state",
2135
+ });
2136
+ }