@ouro.bot/cli 0.1.0-alpha.83 → 0.1.0-alpha.831

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 (647) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +154 -23
  3. package/RepairGuide.ouro/agent.json +5 -0
  4. package/RepairGuide.ouro/psyche/IDENTITY.md +19 -0
  5. package/RepairGuide.ouro/psyche/SOUL.md +55 -0
  6. package/RepairGuide.ouro/skills/diagnose-broken-remote.md +63 -0
  7. package/RepairGuide.ouro/skills/diagnose-stacked-typed-issues.md +35 -0
  8. package/RepairGuide.ouro/skills/diagnose-sync-blocked.md +54 -0
  9. package/RepairGuide.ouro/skills/diagnose-vault-expired.md +60 -0
  10. package/{AdoptionSpecialist.ouro → SerpentGuide.ouro}/agent.json +4 -2
  11. package/{AdoptionSpecialist.ouro → SerpentGuide.ouro}/psyche/SOUL.md +2 -2
  12. package/{AdoptionSpecialist.ouro → SerpentGuide.ouro}/psyche/identities/the-serpent.md +1 -1
  13. package/assets/bluebubbles-host +264 -0
  14. package/assets/sanctuary-host-launcher.sh +16 -0
  15. package/changelog.json +5308 -16
  16. package/deploy/unraid/Dockerfile +54 -0
  17. package/deploy/unraid/README.txt +2983 -0
  18. package/deploy/unraid/audit-container-spec.sh +4 -0
  19. package/deploy/unraid/container-runtime.json +1 -0
  20. package/deploy/unraid/docker-man-template-transaction.mjs +711 -0
  21. package/deploy/unraid/docker-man-template-xml.cjs +105 -0
  22. package/deploy/unraid/migrate-sanctuary-bundle.mjs +60 -0
  23. package/deploy/unraid/ouro-events/bootstrap-spool.sh +92 -0
  24. package/deploy/unraid/ouro-events/emit-event.mjs +310 -0
  25. package/deploy/unraid/ouro-events/emit-usenet-event.sh +54 -0
  26. package/deploy/unraid/ouro-events/install-usenet-guard.sh +435 -0
  27. package/deploy/unraid/ouro-events/usenet-health.sh +176 -0
  28. package/deploy/unraid/sanctuary-acceptance-adapter.sh +43 -0
  29. package/deploy/unraid/sanctuary-acceptance-contract.json +198 -0
  30. package/deploy/unraid/sanctuary-acceptance-harness.sh +23 -0
  31. package/deploy/unraid/sanctuary-authority-installation.json +36 -0
  32. package/deploy/unraid/sanctuary-authority-service.sh +30 -0
  33. package/deploy/unraid/sanctuary-deployment-target.mjs +710 -0
  34. package/deploy/unraid/sanctuary-safe-rename.py +24 -0
  35. package/deploy/unraid/sanctuary-unit16-host-broker.mjs +2084 -0
  36. package/deploy/unraid/sanctuary-unit16-run.sh +560 -0
  37. package/deploy/unraid/sanctuary-unit18-target-audit.sh +27 -0
  38. package/deploy/unraid/sanctuary.ouro/agent.json +66 -0
  39. package/deploy/unraid/sanctuary.ouro/arc/README.md +3 -0
  40. package/deploy/unraid/sanctuary.ouro/bundle-meta.json +5 -0
  41. package/deploy/unraid/sanctuary.ouro/habits/sanctuary-health.md +8 -0
  42. package/deploy/unraid/sanctuary.ouro/mcp/media-mcp.mjs +795 -0
  43. package/deploy/unraid/sanctuary.ouro/provider-readiness.json +11 -0
  44. package/deploy/unraid/sanctuary.ouro/psyche/ASPIRATIONS.md +5 -0
  45. package/deploy/unraid/sanctuary.ouro/psyche/IDENTITY.md +5 -0
  46. package/deploy/unraid/sanctuary.ouro/psyche/LORE.md +15 -0
  47. package/deploy/unraid/sanctuary.ouro/psyche/SOUL.md +25 -0
  48. package/deploy/unraid/sanctuary.ouro/psyche/TACIT.md +15 -0
  49. package/deploy/unraid/sanctuary.ouro/state/policy/steward.json +7 -0
  50. package/deploy/unraid/sanctuary.ouro/tool-profiles.json +135 -0
  51. package/deploy/unraid/sanctuary.xml +28 -0
  52. package/dist/a2a/card.js +57 -0
  53. package/dist/a2a/client.js +180 -0
  54. package/dist/a2a/config.js +50 -0
  55. package/dist/a2a/delegation-stores.js +56 -0
  56. package/dist/a2a/did-resolution.js +93 -0
  57. package/dist/a2a/identity.js +170 -0
  58. package/dist/a2a/inbound-share.js +107 -0
  59. package/dist/a2a/mission-result-wire.js +196 -0
  60. package/dist/a2a/onboarding.js +115 -0
  61. package/dist/a2a/pin-store.js +132 -0
  62. package/dist/a2a/seen-ledger.js +120 -0
  63. package/dist/a2a/server.js +604 -0
  64. package/dist/a2a/task-store.js +69 -0
  65. package/dist/a2a/transport.js +45 -0
  66. package/dist/a2a/types.js +3 -0
  67. package/dist/arc/attention-types.js +8 -0
  68. package/dist/arc/cares.js +360 -0
  69. package/dist/arc/episodes.js +118 -0
  70. package/dist/arc/evolution.js +490 -0
  71. package/dist/arc/flight-recorder.js +1034 -0
  72. package/dist/arc/intentions.js +134 -0
  73. package/dist/arc/json-store.js +117 -0
  74. package/dist/arc/obligations.js +386 -0
  75. package/dist/arc/packets.js +336 -0
  76. package/dist/arc/presence.js +185 -0
  77. package/dist/arc/task-lifecycle.js +57 -0
  78. package/dist/commerce/store.js +755 -0
  79. package/dist/commerce/types.js +3 -0
  80. package/dist/heart/active-work.js +798 -40
  81. package/dist/heart/agent-entry.js +252 -3
  82. package/dist/heart/approval-files.js +113 -0
  83. package/dist/heart/approval-store.js +960 -0
  84. package/dist/heart/attachments/image-normalize.js +194 -0
  85. package/dist/heart/attachments/materialize.js +97 -0
  86. package/dist/heart/attachments/originals.js +88 -0
  87. package/dist/heart/attachments/render.js +29 -0
  88. package/dist/heart/attachments/sources/bluebubbles.js +156 -0
  89. package/dist/heart/attachments/sources/cli-local-file.js +78 -0
  90. package/dist/heart/attachments/sources/index.js +18 -0
  91. package/dist/heart/attachments/sources/telegram.js +71 -0
  92. package/dist/heart/attachments/store.js +132 -0
  93. package/dist/heart/attachments/types.js +93 -0
  94. package/dist/heart/auth/auth-flow.js +495 -0
  95. package/dist/heart/autonomy-budget.js +478 -0
  96. package/dist/heart/awaiting/await-alert.js +148 -0
  97. package/dist/heart/awaiting/await-expiry.js +143 -0
  98. package/dist/heart/awaiting/await-loader.js +91 -0
  99. package/dist/heart/awaiting/await-parser.js +149 -0
  100. package/dist/heart/awaiting/await-runtime-state.js +111 -0
  101. package/dist/heart/awaiting/await-scheduler.js +382 -0
  102. package/dist/heart/background-operations.js +281 -0
  103. package/dist/heart/bridges/manager.js +137 -17
  104. package/dist/heart/bridges/store.js +14 -2
  105. package/dist/heart/bundle-state.js +168 -0
  106. package/dist/heart/commitments.js +44 -18
  107. package/dist/heart/config-registry.js +331 -0
  108. package/dist/heart/config.js +174 -131
  109. package/dist/heart/context-loss-gauntlet.js +387 -0
  110. package/dist/heart/context-loss-sentinel.js +1157 -0
  111. package/dist/heart/core.js +2169 -581
  112. package/dist/heart/cross-chat-delivery.js +9 -22
  113. package/dist/heart/daemon/agent-config-check.js +451 -0
  114. package/dist/heart/daemon/agent-discovery.js +185 -3
  115. package/dist/heart/daemon/agent-service.js +542 -0
  116. package/dist/heart/daemon/agentic-repair.js +547 -0
  117. package/dist/heart/daemon/await-private-wake.js +59 -0
  118. package/dist/heart/daemon/bluebubbles-cli-integration.js +71 -0
  119. package/dist/heart/daemon/bluebubbles-health-diagnostics.js +122 -0
  120. package/dist/heart/daemon/bluebubbles-host-protocol.js +433 -0
  121. package/dist/heart/daemon/bluebubbles-host.js +273 -0
  122. package/dist/heart/daemon/boot-sync-probe.js +197 -0
  123. package/dist/heart/daemon/cadence.js +180 -0
  124. package/dist/heart/daemon/cli-defaults.js +854 -0
  125. package/dist/heart/daemon/cli-desk.js +322 -0
  126. package/dist/heart/daemon/cli-exec.js +8822 -0
  127. package/dist/heart/daemon/cli-help.js +716 -0
  128. package/dist/heart/daemon/cli-parse.js +2771 -0
  129. package/dist/heart/daemon/cli-render-doctor.js +57 -0
  130. package/dist/heart/daemon/cli-render.js +777 -0
  131. package/dist/heart/daemon/cli-types.js +8 -0
  132. package/dist/heart/daemon/connect-bay.js +323 -0
  133. package/dist/heart/daemon/container-credential-bootstrap.js +273 -0
  134. package/dist/heart/daemon/container-healthcheck.js +112 -0
  135. package/dist/heart/daemon/container-runtime.js +97 -0
  136. package/dist/heart/daemon/container-spec-auditor-main.js +171 -0
  137. package/dist/heart/daemon/container-spec-auditor.js +229 -0
  138. package/dist/heart/daemon/daemon-bootstrap-startup.js +115 -0
  139. package/dist/heart/daemon/daemon-cli.js +29 -2349
  140. package/dist/heart/daemon/daemon-entry.js +930 -11
  141. package/dist/heart/daemon/daemon-health.js +186 -0
  142. package/dist/heart/daemon/daemon-rollup.js +57 -0
  143. package/dist/heart/daemon/daemon-runtime-sync.js +88 -13
  144. package/dist/heart/daemon/daemon-tombstone.js +236 -0
  145. package/dist/heart/daemon/daemon.js +2084 -105
  146. package/dist/heart/daemon/dns-workflow.js +394 -0
  147. package/dist/heart/daemon/doctor-types.js +8 -0
  148. package/dist/heart/daemon/doctor.js +1755 -0
  149. package/dist/heart/daemon/freshness.js +345 -0
  150. package/dist/heart/daemon/habit-private-wake.js +61 -0
  151. package/dist/heart/daemon/health-monitor.js +142 -11
  152. package/dist/heart/daemon/hooks/agent-config-v2.js +33 -0
  153. package/dist/heart/daemon/hooks/bundle-meta.js +135 -1
  154. package/dist/heart/daemon/http-health-probe.js +80 -0
  155. package/dist/heart/daemon/human-command-screens.js +234 -0
  156. package/dist/heart/daemon/human-readiness.js +114 -0
  157. package/dist/heart/daemon/inner-status.js +78 -0
  158. package/dist/heart/daemon/interactive-repair.js +394 -0
  159. package/dist/heart/daemon/launchd.js +37 -8
  160. package/dist/heart/daemon/log-tailer.js +79 -10
  161. package/dist/heart/daemon/logs-prune.js +132 -0
  162. package/dist/heart/daemon/mcp-canary.js +524 -0
  163. package/dist/heart/daemon/message-router.js +65 -5
  164. package/dist/heart/daemon/migrate-to-desk.js +848 -0
  165. package/dist/heart/daemon/os-cron-deps.js +135 -0
  166. package/dist/heart/daemon/os-cron.js +22 -14
  167. package/dist/heart/daemon/ouro-bot-entry.js +4 -2
  168. package/dist/heart/daemon/ouro-entry.js +3 -1
  169. package/dist/heart/daemon/plugin-cli.js +432 -0
  170. package/dist/heart/daemon/process-manager.js +733 -61
  171. package/dist/heart/daemon/provider-discovery.js +137 -0
  172. package/dist/heart/daemon/provider-ping-progress.js +83 -0
  173. package/dist/heart/daemon/prunable-bundle.js +144 -0
  174. package/dist/heart/daemon/pulse.js +511 -0
  175. package/dist/heart/daemon/readiness-repair.js +365 -0
  176. package/dist/heart/daemon/run-hooks.js +2 -0
  177. package/dist/heart/daemon/runtime-logging.js +47 -14
  178. package/dist/heart/daemon/runtime-metadata.js +2 -30
  179. package/dist/heart/daemon/runtime-mode.js +13 -2
  180. package/dist/heart/daemon/safe-mode.js +161 -0
  181. package/dist/heart/daemon/sanctuary-acceptance-adapter.js +2654 -0
  182. package/dist/heart/daemon/sanctuary-acceptance-harness.js +1830 -0
  183. package/dist/heart/daemon/sanctuary-acceptance-marker.js +267 -0
  184. package/dist/heart/daemon/sanctuary-acceptance-scenarios.js +955 -0
  185. package/dist/heart/daemon/sanctuary-authority-codec.js +86 -0
  186. package/dist/heart/daemon/sanctuary-authority-epoch.js +256 -0
  187. package/dist/heart/daemon/sanctuary-authority-installation.js +140 -0
  188. package/dist/heart/daemon/sanctuary-authority-ledger.js +241 -0
  189. package/dist/heart/daemon/sanctuary-authority-root-lifecycle.js +819 -0
  190. package/dist/heart/daemon/sanctuary-authority-vault-migration.js +79 -0
  191. package/dist/heart/daemon/sanctuary-bundle-migration.js +729 -0
  192. package/dist/heart/daemon/sanctuary-host-authority.js +1008 -0
  193. package/dist/heart/daemon/sanctuary-host-detached-supervisor.js +494 -0
  194. package/dist/heart/daemon/sanctuary-host-executor.js +670 -0
  195. package/dist/heart/daemon/sanctuary-host-linux-kernel.js +334 -0
  196. package/dist/heart/daemon/sanctuary-host-supervisor-entry.js +207 -0
  197. package/dist/heart/daemon/sanctuary-host-supervisor.js +95 -0
  198. package/dist/heart/daemon/sanctuary-package-management.js +103 -0
  199. package/dist/heart/daemon/sanctuary-scheduler-liveness.js +227 -0
  200. package/dist/heart/daemon/sanctuary-scheduler-origin.js +132 -0
  201. package/dist/heart/daemon/sanctuary-telegram-authority-entry.js +445 -0
  202. package/dist/heart/daemon/sanctuary-telegram-authority-gateway.js +585 -0
  203. package/dist/heart/daemon/sanctuary-telegram-authority-service.js +630 -0
  204. package/dist/heart/daemon/sense-manager.js +699 -48
  205. package/dist/heart/daemon/session-id-resolver.js +131 -0
  206. package/dist/heart/daemon/skill-management-installer.js +1 -1
  207. package/dist/heart/daemon/socket-client.js +180 -12
  208. package/dist/heart/daemon/stale-bundle-prune.js +113 -0
  209. package/dist/heart/daemon/startup-tui.js +339 -0
  210. package/dist/heart/daemon/supercronic-supervisor.js +282 -0
  211. package/dist/heart/daemon/task-scheduler.js +117 -39
  212. package/dist/heart/daemon/terminal-ui.js +499 -0
  213. package/dist/heart/daemon/thoughts.js +138 -54
  214. package/dist/heart/daemon/up-progress.js +366 -0
  215. package/dist/heart/daemon/vault-items.js +56 -0
  216. package/dist/heart/delegation.js +1 -4
  217. package/dist/heart/external-events/router.js +1430 -0
  218. package/dist/heart/frontend-approval-runtime.js +506 -0
  219. package/dist/heart/frontend-journal.js +215 -0
  220. package/dist/heart/frontend-session-service.js +237 -0
  221. package/dist/heart/frontend-socket-client.js +164 -0
  222. package/dist/heart/frontend-socket.js +400 -0
  223. package/dist/heart/habits/habit-cancel.js +810 -0
  224. package/dist/heart/habits/habit-lifecycle.js +1327 -0
  225. package/dist/heart/habits/habit-migration.js +194 -0
  226. package/dist/heart/habits/habit-parser.js +278 -0
  227. package/dist/heart/habits/habit-runtime-state.js +93 -0
  228. package/dist/heart/habits/habit-scheduler.js +653 -0
  229. package/dist/heart/habits/habit-session-summary.js +318 -0
  230. package/dist/heart/habits/habit-session.js +636 -0
  231. package/dist/heart/{daemon → hatch}/hatch-flow.js +55 -62
  232. package/dist/heart/{daemon → hatch}/hatch-specialist.js +6 -8
  233. package/dist/heart/{daemon → hatch}/specialist-prompt.js +12 -9
  234. package/dist/heart/{daemon → hatch}/specialist-tools.js +53 -18
  235. package/dist/heart/identity.js +196 -59
  236. package/dist/heart/kept-notes.js +289 -0
  237. package/dist/heart/kicks.js +1 -1
  238. package/dist/heart/machine-identity.js +161 -0
  239. package/dist/heart/mail-import-discovery.js +356 -0
  240. package/dist/heart/mailbox/mailbox-http-hooks.js +98 -0
  241. package/dist/heart/mailbox/mailbox-http-response.js +7 -0
  242. package/dist/heart/mailbox/mailbox-http-routes.js +398 -0
  243. package/dist/heart/mailbox/mailbox-http-static.js +103 -0
  244. package/dist/heart/mailbox/mailbox-http-transport.js +129 -0
  245. package/dist/heart/mailbox/mailbox-http.js +99 -0
  246. package/dist/heart/mailbox/mailbox-read.js +39 -0
  247. package/dist/heart/mailbox/mailbox-types.js +27 -0
  248. package/dist/heart/mailbox/mailbox-view.js +197 -0
  249. package/dist/heart/mailbox/readers/agent-machine.js +428 -0
  250. package/dist/heart/mailbox/readers/continuity-readers.js +329 -0
  251. package/dist/heart/mailbox/readers/mail.js +375 -0
  252. package/dist/heart/mailbox/readers/runtime-readers.js +823 -0
  253. package/dist/heart/mailbox/readers/sessions.js +231 -0
  254. package/dist/heart/mailbox/readers/shared.js +111 -0
  255. package/dist/heart/mcp/mcp-server.js +696 -0
  256. package/dist/heart/migrate-config.js +100 -0
  257. package/dist/heart/model-capabilities.js +11 -0
  258. package/dist/heart/orientation-frame.js +345 -0
  259. package/dist/heart/platform.js +81 -0
  260. package/dist/heart/primitives.js +1 -1
  261. package/dist/heart/private-runtime/decision-renderer.js +158 -0
  262. package/dist/heart/private-runtime/index.js +18 -0
  263. package/dist/heart/private-runtime/ledger.js +221 -0
  264. package/dist/heart/private-runtime/policy.js +252 -0
  265. package/dist/heart/private-runtime/types.js +2 -0
  266. package/dist/heart/progress-story.js +1 -1
  267. package/dist/heart/provider-attempt.js +137 -0
  268. package/dist/heart/provider-binding-resolver.js +273 -0
  269. package/dist/heart/provider-credentials.js +449 -0
  270. package/dist/heart/provider-failover.js +311 -0
  271. package/dist/heart/provider-models.js +96 -0
  272. package/dist/heart/provider-ping.js +265 -0
  273. package/dist/heart/provider-readiness-cache.js +119 -0
  274. package/dist/heart/provider-visibility.js +188 -0
  275. package/dist/heart/providers/anthropic-token.js +131 -0
  276. package/dist/heart/providers/anthropic.js +173 -60
  277. package/dist/heart/providers/azure.js +23 -11
  278. package/dist/heart/providers/error-classification.js +127 -0
  279. package/dist/heart/providers/github-copilot.js +34 -38
  280. package/dist/heart/providers/minimax-vlm.js +189 -0
  281. package/dist/heart/providers/minimax.js +26 -8
  282. package/dist/heart/providers/openai-codex-token.js +349 -0
  283. package/dist/heart/providers/openai-codex.js +55 -40
  284. package/dist/heart/providers/openai-compatible.js +177 -0
  285. package/dist/heart/run-ledger.js +247 -0
  286. package/dist/heart/runtime-capability-check.js +170 -0
  287. package/dist/heart/runtime-credentials.js +607 -0
  288. package/dist/heart/runtime-cwd.js +87 -0
  289. package/dist/heart/sense-truth.js +22 -4
  290. package/dist/heart/session-activity.js +92 -24
  291. package/dist/heart/session-events.js +1504 -0
  292. package/dist/heart/session-playback-cli-main.js +5 -0
  293. package/dist/heart/session-playback-cli.js +36 -0
  294. package/dist/heart/session-playback.js +231 -0
  295. package/dist/heart/session-redaction-repair-cli-main.js +31 -0
  296. package/dist/heart/session-redaction-repair.js +552 -0
  297. package/dist/heart/session-stats-cli-main.js +5 -0
  298. package/dist/heart/session-stats.js +183 -0
  299. package/dist/heart/session-transcript.js +133 -0
  300. package/dist/heart/start-of-turn-packet.js +372 -0
  301. package/dist/heart/steward-policy.js +618 -0
  302. package/dist/heart/streaming.js +604 -207
  303. package/dist/heart/structured-output.js +196 -0
  304. package/dist/heart/sync-classification.js +176 -0
  305. package/dist/heart/sync.js +449 -0
  306. package/dist/heart/target-resolution.js +11 -7
  307. package/dist/heart/tempo.js +93 -0
  308. package/dist/heart/temporal-view.js +41 -0
  309. package/dist/heart/timeouts.js +101 -0
  310. package/dist/heart/tool-activity-callbacks.js +59 -0
  311. package/dist/heart/tool-approval.js +382 -0
  312. package/dist/heart/tool-description.js +155 -0
  313. package/dist/heart/tool-friction.js +55 -0
  314. package/dist/heart/tool-loop.js +200 -0
  315. package/dist/heart/turn-context.js +482 -0
  316. package/dist/heart/turn-execution-lease.js +30 -0
  317. package/dist/heart/{daemon → versioning}/ouro-bot-global-installer.js +6 -5
  318. package/dist/heart/{daemon → versioning}/ouro-bot-wrapper.js +1 -1
  319. package/dist/heart/versioning/ouro-path-installer.js +432 -0
  320. package/dist/heart/versioning/ouro-recovery-launcher.js +76 -0
  321. package/dist/heart/versioning/ouro-version-manager.js +409 -0
  322. package/dist/heart/{daemon → versioning}/staged-restart.js +40 -8
  323. package/dist/heart/{daemon → versioning}/update-checker.js +6 -1
  324. package/dist/heart/versioning/update-hooks.js +154 -0
  325. package/dist/heart/versioning/version-intent.js +114 -0
  326. package/dist/heart/work-card.js +380 -0
  327. package/dist/mailbox-ui/assets/index-Cyv4mw3Y.js +1 -0
  328. package/dist/mailbox-ui/assets/index-Du_9G9WO.css +1 -0
  329. package/dist/mailbox-ui/assets/vendor-CcN1XpQ9.js +61 -0
  330. package/dist/mailbox-ui/index.html +16 -0
  331. package/dist/mailroom/attention.js +167 -0
  332. package/dist/mailroom/autonomy.js +209 -0
  333. package/dist/mailroom/blob-store.js +889 -0
  334. package/dist/mailroom/body-cache.js +61 -0
  335. package/dist/mailroom/cache-sync-cli.js +58 -0
  336. package/dist/mailroom/core.js +803 -0
  337. package/dist/mailroom/entry.js +160 -0
  338. package/dist/mailroom/file-store.js +568 -0
  339. package/dist/mailroom/hosted-cache-sync.js +384 -0
  340. package/dist/mailroom/mbox-import.js +393 -0
  341. package/dist/mailroom/migration.js +164 -0
  342. package/dist/mailroom/outbound.js +380 -0
  343. package/dist/mailroom/policy.js +263 -0
  344. package/dist/mailroom/reader.js +292 -0
  345. package/dist/mailroom/search-cache.js +513 -0
  346. package/dist/mailroom/search-relevance.js +319 -0
  347. package/dist/mailroom/smtp-ingress.js +176 -0
  348. package/dist/mailroom/source-state.js +176 -0
  349. package/dist/mailroom/thread.js +109 -0
  350. package/dist/mailroom/travel-extract.js +89 -0
  351. package/dist/mind/bundle-manifest.js +24 -2
  352. package/dist/mind/context.js +309 -111
  353. package/dist/mind/desk-section.js +362 -0
  354. package/dist/mind/diary-integrity.js +60 -0
  355. package/dist/mind/{memory.js → diary.js} +68 -77
  356. package/dist/mind/embedding-provider.js +60 -0
  357. package/dist/mind/file-state.js +179 -0
  358. package/dist/mind/{associative-recall.js → note-search.js} +47 -58
  359. package/dist/mind/obligation-steering.js +199 -3
  360. package/dist/mind/pending.js +36 -10
  361. package/dist/mind/prompt-budget.js +479 -0
  362. package/dist/mind/prompt-refresh.js +3 -2
  363. package/dist/mind/prompt.js +1050 -189
  364. package/dist/mind/provenance-trust.js +26 -0
  365. package/dist/mind/record-paths.js +312 -0
  366. package/dist/mind/scrutiny.js +173 -0
  367. package/dist/mind/session-transaction.js +564 -0
  368. package/dist/nerves/cli-logging.js +7 -1
  369. package/dist/nerves/coverage/audit-rules.js +15 -6
  370. package/dist/nerves/coverage/audit.js +28 -2
  371. package/dist/nerves/coverage/cli.js +1 -1
  372. package/dist/nerves/coverage/contract.js +5 -5
  373. package/dist/nerves/coverage/file-completeness.js +139 -5
  374. package/dist/nerves/coverage/run-artifacts.js +55 -35
  375. package/dist/nerves/coverage/source-scanner.js +4 -4
  376. package/dist/nerves/event-buffer.js +111 -0
  377. package/dist/nerves/index.js +293 -9
  378. package/dist/nerves/observation.js +20 -0
  379. package/dist/nerves/redact.js +79 -0
  380. package/dist/nerves/review/cli-main.js +5 -0
  381. package/dist/nerves/review/cli.js +156 -0
  382. package/dist/nerves/review/core.js +159 -0
  383. package/dist/nerves/runtime.js +17 -1
  384. package/dist/repertoire/ado-client.js +15 -56
  385. package/dist/repertoire/ado-semantic.js +16 -10
  386. package/dist/repertoire/api-client.js +97 -0
  387. package/dist/repertoire/bitwarden-store.js +1045 -0
  388. package/dist/repertoire/bundle-templates.js +71 -0
  389. package/dist/repertoire/bw-installer.js +180 -0
  390. package/dist/repertoire/coding/codex-jsonl.js +64 -0
  391. package/dist/repertoire/coding/context-pack.js +331 -0
  392. package/dist/repertoire/coding/feedback.js +174 -32
  393. package/dist/repertoire/coding/index.js +15 -3
  394. package/dist/repertoire/coding/manager.js +159 -10
  395. package/dist/repertoire/coding/spawner.js +58 -11
  396. package/dist/repertoire/coding/tools.js +221 -15
  397. package/dist/repertoire/coding/workbench-client.js +272 -0
  398. package/dist/repertoire/coding/workbench-manager.js +346 -0
  399. package/dist/repertoire/commerce-errors.js +109 -0
  400. package/dist/repertoire/commerce-self-test.js +156 -0
  401. package/dist/repertoire/credential-access.js +178 -0
  402. package/dist/repertoire/desk/classifier.js +362 -0
  403. package/dist/repertoire/duffel-client.js +185 -0
  404. package/dist/repertoire/github-client.js +14 -55
  405. package/dist/repertoire/graph-client.js +11 -52
  406. package/dist/repertoire/guardrails.js +811 -18
  407. package/dist/repertoire/mcp-client.js +44 -3
  408. package/dist/repertoire/mcp-manager.js +427 -139
  409. package/dist/repertoire/mcp-tools.js +124 -0
  410. package/dist/repertoire/plugin-mcp.js +175 -0
  411. package/dist/repertoire/plugins.js +253 -0
  412. package/dist/repertoire/relationship-authorization.js +322 -0
  413. package/dist/repertoire/shell-sessions.js +134 -0
  414. package/dist/repertoire/skills.js +48 -4
  415. package/dist/repertoire/stripe-client.js +131 -0
  416. package/dist/repertoire/tool-arguments.js +170 -0
  417. package/dist/repertoire/tool-results.js +29 -0
  418. package/dist/repertoire/tools-a2a.js +699 -0
  419. package/dist/repertoire/tools-attachments.js +322 -0
  420. package/dist/repertoire/tools-awaiting.js +575 -0
  421. package/dist/repertoire/tools-base.js +77 -1138
  422. package/dist/repertoire/tools-bluebubbles.js +2 -0
  423. package/dist/repertoire/tools-bridge.js +144 -0
  424. package/dist/repertoire/tools-bundle.js +993 -0
  425. package/dist/repertoire/tools-commerce.js +253 -0
  426. package/dist/repertoire/tools-config.js +186 -0
  427. package/dist/repertoire/tools-continuity.js +575 -0
  428. package/dist/repertoire/tools-credential.js +383 -0
  429. package/dist/repertoire/tools-evolution.js +527 -0
  430. package/dist/repertoire/tools-files.js +344 -0
  431. package/dist/repertoire/tools-flight.js +290 -0
  432. package/dist/repertoire/tools-flow.js +119 -0
  433. package/dist/repertoire/tools-github.js +3 -8
  434. package/dist/repertoire/tools-habits.js +103 -0
  435. package/dist/repertoire/tools-mail.js +1828 -0
  436. package/dist/repertoire/tools-notes.js +485 -0
  437. package/dist/repertoire/tools-obligations.js +182 -0
  438. package/dist/repertoire/tools-orientation.js +31 -0
  439. package/dist/repertoire/tools-record.js +482 -0
  440. package/dist/repertoire/tools-rsvp.js +139 -0
  441. package/dist/repertoire/tools-runtime.js +150 -0
  442. package/dist/repertoire/tools-sanctuary-host.js +202 -0
  443. package/dist/repertoire/tools-session.js +1133 -0
  444. package/dist/repertoire/tools-shell.js +149 -0
  445. package/dist/repertoire/tools-steward-policy.js +119 -0
  446. package/dist/repertoire/tools-stripe.js +224 -0
  447. package/dist/repertoire/tools-surface.js +369 -0
  448. package/dist/repertoire/tools-teams.js +12 -39
  449. package/dist/repertoire/tools-telegram-contacts.js +37 -0
  450. package/dist/repertoire/tools-travel.js +125 -0
  451. package/dist/repertoire/tools-trip.js +982 -0
  452. package/dist/repertoire/tools-unraid.js +531 -0
  453. package/dist/repertoire/tools-user-profile.js +146 -0
  454. package/dist/repertoire/tools-vault.js +40 -0
  455. package/dist/repertoire/tools-voice.js +145 -0
  456. package/dist/repertoire/tools.js +494 -148
  457. package/dist/repertoire/travel-api-client.js +360 -0
  458. package/dist/repertoire/unraid-client.js +155 -0
  459. package/dist/repertoire/unraid-restart.js +339 -0
  460. package/dist/repertoire/user-profile.js +131 -0
  461. package/dist/repertoire/vault-setup.js +246 -0
  462. package/dist/repertoire/vault-unlock.js +628 -0
  463. package/dist/rsvp/aisleplanner-client.js +354 -0
  464. package/dist/rsvp/cli.js +926 -0
  465. package/dist/rsvp/config.js +327 -0
  466. package/dist/rsvp/cutover.js +433 -0
  467. package/dist/rsvp/diagnostics.js +232 -0
  468. package/dist/rsvp/diff-renderer.js +176 -0
  469. package/dist/rsvp/habit-policy.js +110 -0
  470. package/dist/rsvp/habit-stage.js +123 -0
  471. package/dist/rsvp/incident-bundle.js +97 -0
  472. package/dist/rsvp/migration.js +204 -0
  473. package/dist/rsvp/native-habit-runner.js +630 -0
  474. package/dist/rsvp/outbound-state.js +628 -0
  475. package/dist/rsvp/query.js +118 -0
  476. package/dist/rsvp/replay.js +233 -0
  477. package/dist/rsvp/snapshot.js +232 -0
  478. package/dist/rsvp/spend-ledger.js +175 -0
  479. package/dist/scripts/claude-code-hook.js +41 -0
  480. package/dist/scripts/claude-code-stop-hook.js +47 -0
  481. package/dist/senses/a2a-entry.js +115 -0
  482. package/dist/senses/acp-server.js +386 -0
  483. package/dist/senses/attention-queue.js +186 -0
  484. package/dist/senses/await-turn-message.js +58 -0
  485. package/dist/senses/bluebubbles/active-turns.js +216 -0
  486. package/dist/senses/bluebubbles/attachment-cache.js +53 -0
  487. package/dist/senses/bluebubbles/attachment-download.js +137 -0
  488. package/dist/senses/{bluebubbles-client.js → bluebubbles/client.js} +355 -40
  489. package/dist/senses/bluebubbles/context-packet.js +207 -0
  490. package/dist/senses/bluebubbles/context-smoke.js +144 -0
  491. package/dist/senses/bluebubbles/entry.js +90 -0
  492. package/dist/senses/{bluebubbles-inbound-log.js → bluebubbles/inbound-log.js} +21 -3
  493. package/dist/senses/bluebubbles/index.js +3841 -0
  494. package/dist/senses/bluebubbles/latest-turn.js +311 -0
  495. package/dist/senses/{bluebubbles-media.js → bluebubbles/media.js} +121 -71
  496. package/dist/senses/{bluebubbles-model.js → bluebubbles/model.js} +159 -28
  497. package/dist/senses/{bluebubbles-mutation-log.js → bluebubbles/mutation-log.js} +24 -13
  498. package/dist/senses/bluebubbles/outbound-state.js +308 -0
  499. package/dist/senses/bluebubbles/processed-log.js +133 -0
  500. package/dist/senses/bluebubbles/reaction-policy.js +47 -0
  501. package/dist/senses/bluebubbles/replay.js +284 -0
  502. package/dist/senses/{bluebubbles-runtime-state.js → bluebubbles/runtime-state.js} +30 -2
  503. package/dist/senses/bluebubbles/semantic-receipts.js +1986 -0
  504. package/dist/senses/{bluebubbles-session-cleanup.js → bluebubbles/session-cleanup.js} +1 -1
  505. package/dist/senses/bluebubbles/webhook-registration.js +228 -0
  506. package/dist/senses/bluebubbles-meta-guard.js +39 -0
  507. package/dist/senses/cli/bracketed-paste.js +82 -0
  508. package/dist/senses/cli/image-paste.js +287 -0
  509. package/dist/senses/cli/image-ref-navigation.js +75 -0
  510. package/dist/senses/cli/ink-app.js +156 -0
  511. package/dist/senses/cli/inline-diff.js +64 -0
  512. package/dist/senses/cli/input-keys.js +174 -0
  513. package/dist/senses/cli/kill-ring.js +86 -0
  514. package/dist/senses/cli/message-list.js +51 -0
  515. package/dist/senses/cli/ouro-tui.js +607 -0
  516. package/dist/senses/cli/spinner-imperative.js +135 -0
  517. package/dist/senses/cli/spinner.js +101 -0
  518. package/dist/senses/cli/status-line.js +60 -0
  519. package/dist/senses/cli/streaming-markdown.js +526 -0
  520. package/dist/senses/cli/tool-display.js +85 -0
  521. package/dist/senses/cli/tool-render.js +85 -0
  522. package/dist/senses/cli/tui-store.js +241 -0
  523. package/dist/senses/cli/virtual-list.js +35 -0
  524. package/dist/senses/cli-entry.js +60 -8
  525. package/dist/senses/cli.js +604 -320
  526. package/dist/senses/commands.js +66 -3
  527. package/dist/senses/context-packet-ledger.js +371 -0
  528. package/dist/senses/context-packets.js +154 -0
  529. package/dist/senses/habit-lifecycle-guard.js +24 -0
  530. package/dist/senses/habit-turn-message.js +160 -0
  531. package/dist/senses/ingress-evidence.js +2 -0
  532. package/dist/senses/mail-entry.js +66 -0
  533. package/dist/senses/mail.js +524 -0
  534. package/dist/senses/pipeline.js +1115 -204
  535. package/dist/senses/private-runtime-worker.js +859 -0
  536. package/dist/senses/private-runtime.js +1517 -0
  537. package/dist/senses/proactive-content-guard.js +51 -0
  538. package/dist/senses/root-host-approval-port.js +345 -0
  539. package/dist/senses/root-host-approval-runtime.js +393 -0
  540. package/dist/senses/sanctuary-authority-resident.js +102 -0
  541. package/dist/senses/sanctuary-download-credit-presentation.js +31 -0
  542. package/dist/senses/sanctuary-full-visibility-contract.js +302 -0
  543. package/dist/senses/sanctuary-grounding.js +118 -0
  544. package/dist/senses/sanctuary-health-acceptance-probe-entry.js +12 -0
  545. package/dist/senses/sanctuary-health-acceptance-probe.js +774 -0
  546. package/dist/senses/sanctuary-health-runner.js +100 -0
  547. package/dist/senses/sanctuary-health.js +408 -0
  548. package/dist/senses/sanctuary-install-state-contract.js +126 -0
  549. package/dist/senses/sanctuary-interactive-control.js +410 -0
  550. package/dist/senses/sanctuary-media-catalog-contract.js +297 -0
  551. package/dist/senses/sanctuary-media-optimization.js +514 -0
  552. package/dist/senses/sanctuary-runtime.js +340 -0
  553. package/dist/senses/sanctuary-sab.js +85 -0
  554. package/dist/senses/sanctuary-storage-optimization-contract.js +32 -0
  555. package/dist/senses/shared-turn.js +772 -0
  556. package/dist/senses/surface-tool.js +109 -0
  557. package/dist/senses/teams-entry.js +60 -8
  558. package/dist/senses/teams.js +519 -218
  559. package/dist/senses/telegram-admission.js +718 -0
  560. package/dist/senses/telegram-approval-runtime.js +793 -0
  561. package/dist/senses/telegram-attachments.js +186 -0
  562. package/dist/senses/telegram-audit-ledger.js +220 -0
  563. package/dist/senses/telegram-authority-transport.js +231 -0
  564. package/dist/senses/telegram-client.js +1749 -0
  565. package/dist/senses/telegram-effect-adapter.js +896 -0
  566. package/dist/senses/telegram-entry.js +83 -0
  567. package/dist/senses/telegram.js +2324 -0
  568. package/dist/senses/trust-gate.js +104 -9
  569. package/dist/senses/voice/audio-playback.js +237 -0
  570. package/dist/senses/voice/audio-routing.js +119 -0
  571. package/dist/senses/voice/elevenlabs.js +202 -0
  572. package/dist/senses/voice/floor-control.js +431 -0
  573. package/dist/senses/voice/floor-controller.js +115 -0
  574. package/dist/senses/voice/golden-path.js +116 -0
  575. package/dist/senses/voice/index.js +29 -0
  576. package/dist/senses/voice/meeting.js +113 -0
  577. package/dist/senses/voice/outbound.js +190 -0
  578. package/dist/senses/voice/phone.js +33 -0
  579. package/dist/senses/voice/playback.js +139 -0
  580. package/dist/senses/voice/realtime-eval.js +496 -0
  581. package/dist/senses/voice/realtime-trace.js +531 -0
  582. package/dist/senses/voice/transcript.js +70 -0
  583. package/dist/senses/voice/turn.js +191 -0
  584. package/dist/senses/voice/twilio-phone-runtime.js +807 -0
  585. package/dist/senses/voice/twilio-phone.js +5031 -0
  586. package/dist/senses/voice/types.js +2 -0
  587. package/dist/senses/voice/whisper.js +161 -0
  588. package/dist/senses/voice-entry.js +81 -0
  589. package/dist/senses/voice-realtime-eval-command.js +99 -0
  590. package/dist/senses/voice-realtime-eval-entry.js +21 -0
  591. package/dist/senses/voice-twilio-entry.js +87 -0
  592. package/dist/trips/core.js +138 -0
  593. package/dist/trips/store.js +265 -0
  594. package/dist/util/frontmatter.js +69 -0
  595. package/npm-shrinkwrap.json +8138 -0
  596. package/package.json +72 -13
  597. package/skills/agent-commerce.md +113 -0
  598. package/skills/browser-navigation.md +117 -0
  599. package/skills/commerce-setup-guide.md +116 -0
  600. package/skills/commerce-setup.md +84 -0
  601. package/skills/configure-dev-tools.md +99 -0
  602. package/skills/travel-planning.md +138 -0
  603. package/dist/heart/daemon/auth-flow.js +0 -430
  604. package/dist/heart/daemon/ouro-path-installer.js +0 -260
  605. package/dist/heart/daemon/ouro-version-manager.js +0 -171
  606. package/dist/heart/daemon/update-hooks.js +0 -138
  607. package/dist/heart/obligations.js +0 -191
  608. package/dist/heart/safe-workspace.js +0 -228
  609. package/dist/heart/session-recall.js +0 -116
  610. package/dist/mind/friends/channel.js +0 -84
  611. package/dist/mind/friends/group-context.js +0 -144
  612. package/dist/mind/friends/resolver.js +0 -84
  613. package/dist/mind/friends/store-file.js +0 -190
  614. package/dist/mind/friends/store.js +0 -4
  615. package/dist/mind/friends/tokens.js +0 -26
  616. package/dist/mind/friends/trust-explanation.js +0 -74
  617. package/dist/mind/friends/types.js +0 -29
  618. package/dist/repertoire/tasks/board.js +0 -134
  619. package/dist/repertoire/tasks/index.js +0 -224
  620. package/dist/repertoire/tasks/lifecycle.js +0 -80
  621. package/dist/repertoire/tasks/middleware.js +0 -65
  622. package/dist/repertoire/tasks/parser.js +0 -173
  623. package/dist/repertoire/tasks/scanner.js +0 -132
  624. package/dist/repertoire/tasks/transitions.js +0 -144
  625. package/dist/senses/bluebubbles-entry.js +0 -13
  626. package/dist/senses/bluebubbles.js +0 -1181
  627. package/dist/senses/debug-activity.js +0 -154
  628. package/dist/senses/inner-dialog-worker.js +0 -90
  629. package/dist/senses/inner-dialog.js +0 -525
  630. package/subagents/README.md +0 -7
  631. /package/{AdoptionSpecialist.ouro → SerpentGuide.ouro}/psyche/identities/basilisk.md +0 -0
  632. /package/{AdoptionSpecialist.ouro → SerpentGuide.ouro}/psyche/identities/jafar.md +0 -0
  633. /package/{AdoptionSpecialist.ouro → SerpentGuide.ouro}/psyche/identities/jormungandr.md +0 -0
  634. /package/{AdoptionSpecialist.ouro → SerpentGuide.ouro}/psyche/identities/kaa.md +0 -0
  635. /package/{AdoptionSpecialist.ouro → SerpentGuide.ouro}/psyche/identities/medusa.md +0 -0
  636. /package/{AdoptionSpecialist.ouro → SerpentGuide.ouro}/psyche/identities/monty.md +0 -0
  637. /package/{AdoptionSpecialist.ouro → SerpentGuide.ouro}/psyche/identities/nagini.md +0 -0
  638. /package/{AdoptionSpecialist.ouro → SerpentGuide.ouro}/psyche/identities/ouroboros.md +0 -0
  639. /package/{AdoptionSpecialist.ouro → SerpentGuide.ouro}/psyche/identities/python.md +0 -0
  640. /package/{AdoptionSpecialist.ouro → SerpentGuide.ouro}/psyche/identities/quetzalcoatl.md +0 -0
  641. /package/{AdoptionSpecialist.ouro → SerpentGuide.ouro}/psyche/identities/sir-hiss.md +0 -0
  642. /package/{AdoptionSpecialist.ouro → SerpentGuide.ouro}/psyche/identities/the-snake.md +0 -0
  643. /package/dist/{repertoire/tasks/types.js → heart/attachments/sources/adapter.js} +0 -0
  644. /package/dist/heart/{daemon → hatch}/hatch-animation.js +0 -0
  645. /package/dist/heart/{daemon → hatch}/specialist-orchestrator.js +0 -0
  646. /package/dist/heart/{daemon → versioning}/ouro-uti.js +0 -0
  647. /package/dist/heart/{daemon → versioning}/wrapper-publish-guard.js +0 -0
@@ -0,0 +1,2324 @@
1
+ "use strict";
2
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
3
+ if (k2 === undefined) k2 = k;
4
+ var desc = Object.getOwnPropertyDescriptor(m, k);
5
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
6
+ desc = { enumerable: true, get: function() { return m[k]; } };
7
+ }
8
+ Object.defineProperty(o, k2, desc);
9
+ }) : (function(o, m, k, k2) {
10
+ if (k2 === undefined) k2 = k;
11
+ o[k2] = m[k];
12
+ }));
13
+ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
14
+ Object.defineProperty(o, "default", { enumerable: true, value: v });
15
+ }) : function(o, v) {
16
+ o["default"] = v;
17
+ });
18
+ var __importStar = (this && this.__importStar) || (function () {
19
+ var ownKeys = function(o) {
20
+ ownKeys = Object.getOwnPropertyNames || function (o) {
21
+ var ar = [];
22
+ for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
23
+ return ar;
24
+ };
25
+ return ownKeys(o);
26
+ };
27
+ return function (mod) {
28
+ if (mod && mod.__esModule) return mod;
29
+ var result = {};
30
+ if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
31
+ __setModuleDefault(result, mod);
32
+ return result;
33
+ };
34
+ })();
35
+ Object.defineProperty(exports, "__esModule", { value: true });
36
+ exports.createSabQueueProtectiveStateVerifier = createSabQueueProtectiveStateVerifier;
37
+ exports.telegramAcceptanceAuditOwnerDigest = telegramAcceptanceAuditOwnerDigest;
38
+ exports.sanctuaryTelegramTurnReceiptDigest = sanctuaryTelegramTurnReceiptDigest;
39
+ exports.sanctuaryTelegramTurnReceiptMac = sanctuaryTelegramTurnReceiptMac;
40
+ exports.sanctuaryTelegramAuditLifecycleMac = sanctuaryTelegramAuditLifecycleMac;
41
+ exports.sanctuaryTelegramUnauthorizedDropMac = sanctuaryTelegramUnauthorizedDropMac;
42
+ exports.sanctuaryTelegramApprovalEvidenceMac = sanctuaryTelegramApprovalEvidenceMac;
43
+ exports.telegramBotIdFromToken = telegramBotIdFromToken;
44
+ exports.readOrCreateTelegramIdentityKey = readOrCreateTelegramIdentityKey;
45
+ exports.opaqueTelegramSubject = opaqueTelegramSubject;
46
+ exports.migrateTelegramFriendIdentity = migrateTelegramFriendIdentity;
47
+ exports.migrateTelegramSessionIdentity = migrateTelegramSessionIdentity;
48
+ exports.parseTelegramSenseCredentials = parseTelegramSenseCredentials;
49
+ exports.createTelegramSenseApp = createTelegramSenseApp;
50
+ exports.loadTelegramSenseCredentials = loadTelegramSenseCredentials;
51
+ exports.createProductionTelegramRelationshipComposition = createProductionTelegramRelationshipComposition;
52
+ exports.startTelegramSenseApp = startTelegramSenseApp;
53
+ exports.sendTelegramExternalEventDecision = sendTelegramExternalEventDecision;
54
+ exports.createProactiveRepetitionGuard = createProactiveRepetitionGuard;
55
+ exports.withTelegramTypingIndicator = withTelegramTypingIndicator;
56
+ exports.sendTelegramAwaitFollowUp = sendTelegramAwaitFollowUp;
57
+ const node_crypto_1 = require("node:crypto");
58
+ const node_async_hooks_1 = require("node:async_hooks");
59
+ const node_fs_1 = require("node:fs");
60
+ const path = __importStar(require("node:path"));
61
+ const friends_1 = require("@ouro.bot/friends");
62
+ const identity_1 = require("../heart/identity");
63
+ const sanctuary_acceptance_marker_1 = require("../heart/daemon/sanctuary-acceptance-marker");
64
+ const runtime_credentials_1 = require("../heart/runtime-credentials");
65
+ const runtime_1 = require("../nerves/runtime");
66
+ const nerves_1 = require("../nerves");
67
+ const sanctuary_interactive_control_1 = require("./sanctuary-interactive-control");
68
+ const sanctuary_runtime_1 = require("./sanctuary-runtime");
69
+ const sanctuary_sab_1 = require("./sanctuary-sab");
70
+ const sanctuary_download_credit_presentation_1 = require("./sanctuary-download-credit-presentation");
71
+ const shared_turn_1 = require("./shared-turn");
72
+ const config_1 = require("../heart/config");
73
+ const telegram_client_1 = require("./telegram-client");
74
+ const sanctuary_authority_resident_1 = require("./sanctuary-authority-resident");
75
+ const root_host_approval_runtime_1 = require("./root-host-approval-runtime");
76
+ const tools_sanctuary_host_1 = require("../repertoire/tools-sanctuary-host");
77
+ const sanctuary_runtime_2 = require("./sanctuary-runtime");
78
+ const sanctuary_full_visibility_contract_1 = require("./sanctuary-full-visibility-contract");
79
+ const sanctuary_install_state_contract_1 = require("./sanctuary-install-state-contract");
80
+ const sanctuary_media_catalog_contract_1 = require("./sanctuary-media-catalog-contract");
81
+ const sanctuary_storage_optimization_contract_1 = require("./sanctuary-storage-optimization-contract");
82
+ const sanctuary_grounding_1 = require("./sanctuary-grounding");
83
+ const telegram_approval_runtime_1 = require("./telegram-approval-runtime");
84
+ const telegram_audit_ledger_1 = require("./telegram-audit-ledger");
85
+ const session_events_1 = require("../heart/session-events");
86
+ const router_1 = require("../heart/external-events/router");
87
+ const session_transaction_1 = require("../mind/session-transaction");
88
+ const approval_files_1 = require("../heart/approval-files");
89
+ const approval_store_1 = require("../heart/approval-store");
90
+ const tools_awaiting_1 = require("../repertoire/tools-awaiting");
91
+ const obligations_1 = require("../arc/obligations");
92
+ const orientation_frame_1 = require("../heart/orientation-frame");
93
+ const render_1 = require("../heart/attachments/render");
94
+ const telegram_attachments_1 = require("./telegram-attachments");
95
+ const pending_1 = require("../mind/pending");
96
+ const relationship_authorization_1 = require("../repertoire/relationship-authorization");
97
+ const telegram_admission_1 = require("./telegram-admission");
98
+ const telegram_effect_adapter_1 = require("./telegram-effect-adapter");
99
+ const EMPTY_SHARED_TURN_DIAGNOSTIC = "(agent responded but response was empty)";
100
+ function hasUserVisibleTurnResponse(response) {
101
+ const text = response.trim();
102
+ return text.length > 0 && text !== EMPTY_SHARED_TURN_DIAGNOSTIC;
103
+ }
104
+ function responseFallbackAfterDeliveries(result, successfulDeliveryCount) {
105
+ const failedTerminal = result.responseDeliveryFailure?.text ?? (result.responseCausalSessionEventId ? result.deliveryFailures.at(-1)?.text : undefined);
106
+ const response = failedTerminal ?? (successfulDeliveryCount === 0 && result.deliveryFailures.length === 0 ? result.response : "");
107
+ return hasUserVisibleTurnResponse(response) ? response : null;
108
+ }
109
+ function createFullVisibilityProgress() {
110
+ const progress = {};
111
+ return { progress, emptyResponseFallback: () => progress.fallback?.() };
112
+ }
113
+ function createSabQueueProtectiveStateVerifier(options = {}) {
114
+ let client = null;
115
+ return async (action) => {
116
+ client ??= (0, sanctuary_sab_1.createSanctuarySabClient)({ loadApiKey: options.loadApiKey ?? (() => (0, sanctuary_runtime_1.loadSanctuarySabApiKey)("sanctuary")), fetch: options.fetch, now: options.now });
117
+ const snapshot = await client.readQueue();
118
+ const paused = snapshot.paused;
119
+ const digest = (0, node_crypto_1.createHash)("sha256").update(`sabnzbd.queue.paused=${String(paused)}`).digest("hex");
120
+ const verified = action.action === "sabnzbd.pause" && paused && action.verification.verified && action.verification.digest === digest;
121
+ return { verified, reference: `sabnzbd.queue.paused:${digest}:${snapshot.observedAt}` };
122
+ };
123
+ }
124
+ const APPROVAL_EXPIRY_RECONCILE_INTERVAL_MS = 1_000;
125
+ const TELEGRAM_SUBJECT_DOMAIN = "ouroboros.telegram.subject.v1";
126
+ const TELEGRAM_IDENTITY_KEY = /^[A-Za-z0-9_-]{43}$/u;
127
+ const TELEGRAM_SUBJECT = /^tg_[A-Za-z0-9_-]{43}$/u;
128
+ const TELEGRAM_SUBJECT_INDEX = "identity-subjects.json";
129
+ const TELEGRAM_ACCEPTANCE_AUDIT_EVENTS = new Set([
130
+ "approval.acceptance_continuation_transition",
131
+ "approval.acceptance_transition",
132
+ "senses.sanctuary_health_delivered",
133
+ "senses.sanctuary_read_receipt",
134
+ "senses.telegram_approved_restart_end",
135
+ "senses.telegram_approved_restart_error",
136
+ "senses.telegram_approved_restart_start",
137
+ "senses.telegram_approval_continuation_delivered",
138
+ "senses.telegram_approval_prompt_bound",
139
+ "senses.telegram_turn_end",
140
+ "senses.telegram_turn_error",
141
+ "senses.telegram_turn_start",
142
+ "telegram.approval_prompt_terminalized",
143
+ "telegram.approval_expiry_observed",
144
+ "telegram.approval_stale_callback_settled",
145
+ "telegram.callback_recovery_settled",
146
+ "telegram.callback_settled",
147
+ "telegram.update_dropped",
148
+ ]);
149
+ const telegramAcceptanceAuditOwner = new node_async_hooks_1.AsyncLocalStorage();
150
+ const telegramAcceptanceAuditScenario = new node_async_hooks_1.AsyncLocalStorage();
151
+ const telegramAcceptanceAuditExplicitCommit = new node_async_hooks_1.AsyncLocalStorage();
152
+ const telegramAcceptanceAudits = new Map();
153
+ const TELEGRAM_TURN_RECEIPT_DOMAINS = {
154
+ "sanctuary-telegram-turn-receipt-v3": "ouroboros.telegram.turn-receipt.v3",
155
+ "sanctuary-telegram-turn-receipt-v4": "ouroboros.telegram.turn-receipt.v4",
156
+ };
157
+ const TELEGRAM_TURN_LEDGER_MAX_BYTES = 4 * 1024 * 1024;
158
+ const TELEGRAM_TURN_LEDGER_MAX_ROWS = 500;
159
+ const TELEGRAM_TURN_LEDGER_MAX_ROW_BYTES = 16 * 1024;
160
+ const telegramTurnLedgerTails = new Map();
161
+ function telegramAcceptanceAuditOwnerDigest(identityKey, agentName, root) {
162
+ return (0, node_crypto_1.createHmac)("sha256", identityKey)
163
+ .update(`ouroboros.telegram.acceptance-audit-owner.v1\0${agentName}\0${root}`, "utf8")
164
+ .digest("hex");
165
+ }
166
+ function acquireTelegramAcceptanceAudit(root, agentName, identityKey, privateValues, scenarioHandleDigest, releaseHook, maxBytes) {
167
+ const existing = telegramAcceptanceAudits.get(root);
168
+ if (existing) {
169
+ if (existing.identityKey !== identityKey)
170
+ throw new Error("Telegram acceptance audit identity changed while active");
171
+ if (existing.scenarioHandleDigest !== scenarioHandleDigest)
172
+ throw new Error("Telegram acceptance audit scenario changed while active");
173
+ existing.ledger.assertHealthy();
174
+ existing.references += 1;
175
+ return { ledger: existing.ledger, ownerDigest: existing.ownerDigest, scenarioHandleDigest: existing.scenarioHandleDigest, release: () => {
176
+ existing.references -= 1;
177
+ if (existing.references === 0) {
178
+ try {
179
+ existing.unregister();
180
+ }
181
+ finally {
182
+ telegramAcceptanceAudits.delete(root);
183
+ }
184
+ releaseHook?.();
185
+ }
186
+ } };
187
+ }
188
+ const ledger = (0, telegram_audit_ledger_1.createTelegramAuditLedger)({ root, identityKey, privateValues, _maxBytes: maxBytes });
189
+ const ownerDigest = telegramAcceptanceAuditOwnerDigest(identityKey, agentName, root);
190
+ let record;
191
+ const unregister = (0, nerves_1.registerGlobalLogSink)((event) => {
192
+ if (!TELEGRAM_ACCEPTANCE_AUDIT_EVENTS.has(event.event))
193
+ return;
194
+ if (telegramAcceptanceAuditExplicitCommit.getStore())
195
+ return;
196
+ const explicitOwner = event.meta.acceptanceAuditOwnerDigest;
197
+ const contextualOwner = telegramAcceptanceAuditOwner.getStore();
198
+ if (explicitOwner !== ownerDigest && contextualOwner !== ownerDigest)
199
+ return;
200
+ const scenario = event.meta.scenarioHandleDigest;
201
+ if (typeof scenario !== "string" || !/^[0-9a-f]{64}$/u.test(scenario) || record.scenarioHandleDigest !== scenario) {
202
+ ledger.poison(new Error("Telegram acceptance audit scenario ownership drift"));
203
+ }
204
+ ledger.append(event);
205
+ });
206
+ record = { agentName, identityKey, ledger, ownerDigest, references: 1, scenarioHandleDigest, unregister };
207
+ telegramAcceptanceAudits.set(root, record);
208
+ return { ledger, ownerDigest, scenarioHandleDigest, release: () => {
209
+ record.references -= 1;
210
+ if (record.references === 0) {
211
+ try {
212
+ record.unregister();
213
+ }
214
+ finally {
215
+ telegramAcceptanceAudits.delete(root);
216
+ }
217
+ releaseHook?.();
218
+ }
219
+ } };
220
+ }
221
+ function sanctuaryTelegramTurnReceiptDigest(identityKey, schemaVersion, purpose, value) {
222
+ return (0, node_crypto_1.createHmac)("sha256", identityKey).update(`${TELEGRAM_TURN_RECEIPT_DOMAINS[schemaVersion]}\0${purpose}\0${value}`, "utf8").digest("hex");
223
+ }
224
+ function canonicalReceiptJson(value) {
225
+ if (Array.isArray(value))
226
+ return `[${value.map(canonicalReceiptJson).join(",")}]`;
227
+ if (value && typeof value === "object") {
228
+ const record = value;
229
+ return `{${Object.keys(record).filter((key) => record[key] !== undefined).sort().map((key) => `${JSON.stringify(key)}:${canonicalReceiptJson(record[key])}`).join(",")}}`;
230
+ }
231
+ const rendered = JSON.stringify(value);
232
+ if (rendered === undefined)
233
+ throw new Error("Telegram receipt contains an unsupported value");
234
+ return rendered;
235
+ }
236
+ function sanctuaryTelegramTurnReceiptMac(identityKey, value) {
237
+ const unsigned = Object.fromEntries(Object.entries(value).filter(([key]) => key !== "receiptMac"));
238
+ return sanctuaryTelegramTurnReceiptDigest(identityKey, "sanctuary-telegram-turn-receipt-v4", "receipt", canonicalReceiptJson(unsigned));
239
+ }
240
+ function sanctuaryTelegramAuditLifecycleMac(identityKey, schemaVersion, event, meta) {
241
+ const unsigned = Object.fromEntries(Object.entries(meta).filter(([key]) => key !== "lifecycleMac"));
242
+ return sanctuaryTelegramTurnReceiptDigest(identityKey, schemaVersion, "audit-lifecycle", canonicalReceiptJson({ event, meta: unsigned }));
243
+ }
244
+ function sanctuaryTelegramUnauthorizedDropMac(identityKey, schemaVersion, value) {
245
+ return sanctuaryTelegramTurnReceiptDigest(identityKey, schemaVersion, "unauthorized-drop", canonicalReceiptJson(value));
246
+ }
247
+ function sanctuaryTelegramApprovalEvidenceMac(identityKey, event, meta) {
248
+ const unsigned = Object.fromEntries(Object.entries(meta).filter(([key]) => key !== "evidenceMac"));
249
+ return sanctuaryTelegramTurnReceiptDigest(identityKey, "sanctuary-telegram-turn-receipt-v3", "approval-evidence", canonicalReceiptJson({ event, meta: unsigned }));
250
+ }
251
+ function sameTelegramLedgerMetadata(left, right) {
252
+ return left.dev === right.dev
253
+ && left.ino === right.ino
254
+ && left.mode === right.mode
255
+ && left.nlink === right.nlink
256
+ && left.uid === right.uid
257
+ && left.gid === right.gid
258
+ && left.size === right.size
259
+ && left.mtimeNs === right.mtimeNs
260
+ && left.ctimeNs === right.ctimeNs;
261
+ }
262
+ function readStableBoundedTelegramLedger(filePath, afterPreReadStat) {
263
+ let handle;
264
+ try {
265
+ handle = (0, node_fs_1.openSync)(filePath, node_fs_1.constants.O_RDONLY | node_fs_1.constants.O_NOFOLLOW);
266
+ }
267
+ catch (error) {
268
+ if (error.code === "ENOENT")
269
+ return null;
270
+ throw error;
271
+ }
272
+ try {
273
+ const before = (0, node_fs_1.fstatSync)(handle, { bigint: true });
274
+ if (!before.isFile())
275
+ throw new Error("Telegram turn receipt ledger must be a regular file");
276
+ if (before.size > BigInt(TELEGRAM_TURN_LEDGER_MAX_BYTES))
277
+ throw new Error("Telegram turn receipt ledger exceeds its bound");
278
+ afterPreReadStat?.(filePath);
279
+ const expectedBytes = Number(before.size);
280
+ const content = Buffer.allocUnsafe(expectedBytes);
281
+ let offset = 0;
282
+ while (offset < expectedBytes) {
283
+ const bytesRead = (0, node_fs_1.readSync)(handle, content, offset, expectedBytes - offset, offset);
284
+ if (bytesRead === 0)
285
+ throw new Error("Telegram turn receipt ledger changed during read");
286
+ offset += bytesRead;
287
+ }
288
+ const overflow = Buffer.allocUnsafe(1);
289
+ const overflowBytes = (0, node_fs_1.readSync)(handle, overflow, 0, 1, expectedBytes);
290
+ const after = (0, node_fs_1.fstatSync)(handle, { bigint: true });
291
+ const pathAfter = (0, node_fs_1.lstatSync)(filePath, { bigint: true });
292
+ if (overflowBytes !== 0 || !sameTelegramLedgerMetadata(before, after)
293
+ || !pathAfter.isFile() || pathAfter.dev !== before.dev || pathAfter.ino !== before.ino) {
294
+ throw new Error("Telegram turn receipt ledger changed during read");
295
+ }
296
+ return content.toString("utf8");
297
+ }
298
+ finally {
299
+ (0, node_fs_1.closeSync)(handle);
300
+ }
301
+ }
302
+ function validateSanctuaryTurnReceipt(value) {
303
+ const grounded = value.schemaVersion === "sanctuary-telegram-turn-receipt-v4";
304
+ const exactKeys = ["completedAt", "deliveries", "deliveryCount", "errorCategory", "providerInvocationCount", "responseDigest", "scenarioHandleDigest", "schemaVersion", "sequenceDigest", "status", "toolInvocationCount", "toolResultDigests", "updateDigest", ...(grounded ? ["receiptMac", "toolGroundings"] : [])].sort();
305
+ const deliveries = value.deliveries;
306
+ if (JSON.stringify(Object.keys(value).sort()) !== JSON.stringify(exactKeys)
307
+ || (!grounded && value.schemaVersion !== "sanctuary-telegram-turn-receipt-v3")
308
+ || typeof value.scenarioHandleDigest !== "string" || !/^[0-9a-f]{64}$/u.test(value.scenarioHandleDigest)
309
+ || (value.status !== "success" && value.status !== "error")
310
+ || (value.status === "success"
311
+ ? value.errorCategory !== null
312
+ : typeof value.errorCategory !== "string" || value.errorCategory.length < 1 || value.errorCategory.length > 128)
313
+ || ![value.updateDigest, value.sequenceDigest, value.responseDigest].every((digest) => typeof digest === "string" && /^[0-9a-f]{64}$/u.test(digest))
314
+ || !Array.isArray(value.toolResultDigests) || value.toolResultDigests.length > 100 || !value.toolResultDigests.every((digest) => typeof digest === "string" && /^[0-9a-f]{64}$/u.test(digest))
315
+ || !Array.isArray(deliveries) || deliveries.length > 100 || !deliveries.every((delivery) => {
316
+ if (!delivery || typeof delivery !== "object" || Array.isArray(delivery))
317
+ return false;
318
+ const record = delivery;
319
+ return JSON.stringify(Object.keys(record).sort()) === JSON.stringify(grounded ? ["chunkDigest", "messageIdDigest", "redactedText", "utf16Units"] : ["chunkDigest", "messageIdDigest"])
320
+ && typeof record.chunkDigest === "string" && /^[0-9a-f]{64}$/u.test(record.chunkDigest)
321
+ && typeof record.messageIdDigest === "string" && /^[0-9a-f]{64}$/u.test(record.messageIdDigest)
322
+ && (!grounded || (typeof record.redactedText === "string" && record.redactedText.length === record.utf16Units && record.utf16Units <= 1_200));
323
+ })
324
+ || (grounded && (!Array.isArray(value.toolGroundings) || value.toolGroundings.length !== 1 || !value.toolGroundings.every((raw) => {
325
+ if (!raw || typeof raw !== "object" || Array.isArray(raw))
326
+ return false;
327
+ const record = raw;
328
+ if (JSON.stringify(Object.keys(record).sort()) !== JSON.stringify(["facts", "groundingDigest", "observedAt", "resultDigest", "sourceIdentityDigest", "toolName"]) || (record.toolName !== "unraid_get_system" && record.toolName !== "unraid_get_storage")
329
+ || typeof record.resultDigest !== "string" || !/^[0-9a-f]{64}$/u.test(record.resultDigest) || typeof record.groundingDigest !== "string" || !/^[0-9a-f]{64}$/u.test(record.groundingDigest)
330
+ || typeof record.sourceIdentityDigest !== "string" || !/^[0-9a-f]{64}$/u.test(record.sourceIdentityDigest) || typeof record.observedAt !== "string" || !Number.isFinite(Date.parse(record.observedAt)) || new Date(Date.parse(record.observedAt)).toISOString() !== record.observedAt
331
+ || !record.facts || typeof record.facts !== "object" || Array.isArray(record.facts))
332
+ return false;
333
+ return (0, sanctuary_grounding_1.sanctuaryGroundingDigest)(record.facts) === record.groundingDigest && value.toolResultDigests.includes(record.resultDigest);
334
+ })))
335
+ || (grounded && (typeof value.receiptMac !== "string" || !/^[0-9a-f]{64}$/u.test(value.receiptMac)))
336
+ || ![value.providerInvocationCount, value.toolInvocationCount].every((count) => Number.isSafeInteger(count) && Number(count) >= 0 && Number(count) <= 1_000)
337
+ || !Number.isSafeInteger(value.deliveryCount) || value.deliveryCount !== deliveries.length
338
+ || typeof value.completedAt !== "string" || value.completedAt.length > 30 || !Number.isFinite(Date.parse(value.completedAt)) || new Date(Date.parse(value.completedAt)).toISOString() !== value.completedAt)
339
+ throw new Error("Telegram turn receipt ledger row is invalid");
340
+ }
341
+ async function appendSanctuaryTurnReceipt(agentRoot, receipt, afterPreReadStat) {
342
+ const filePath = path.join(agentRoot, "state", "acceptance", "telegram-turns.ndjson");
343
+ const previous = telegramTurnLedgerTails.get(filePath) ?? Promise.resolve();
344
+ const current = previous.catch(() => undefined).then(() => {
345
+ (0, node_fs_1.mkdirSync)(path.dirname(filePath), { recursive: true, mode: 0o700 });
346
+ let existing = [];
347
+ const raw = readStableBoundedTelegramLedger(filePath, afterPreReadStat);
348
+ if (raw !== null) {
349
+ existing = raw.split("\n").filter(Boolean);
350
+ if (existing.length > TELEGRAM_TURN_LEDGER_MAX_ROWS || existing.some((line) => Buffer.byteLength(line) > TELEGRAM_TURN_LEDGER_MAX_ROW_BYTES))
351
+ throw new Error("Telegram turn receipt ledger is invalid");
352
+ for (const line of existing) {
353
+ validateSanctuaryTurnReceipt(JSON.parse(line));
354
+ }
355
+ }
356
+ validateSanctuaryTurnReceipt(receipt);
357
+ const serialized = JSON.stringify(receipt);
358
+ if (Buffer.byteLength(serialized) > TELEGRAM_TURN_LEDGER_MAX_ROW_BYTES)
359
+ throw new Error("Telegram turn receipt exceeds its bound");
360
+ const lines = [...existing, serialized].slice(-TELEGRAM_TURN_LEDGER_MAX_ROWS);
361
+ let aggregateBytes = lines.reduce((total, line) => total + Buffer.byteLength(line) + 1, 0);
362
+ while (aggregateBytes > TELEGRAM_TURN_LEDGER_MAX_BYTES && lines.length > 1) {
363
+ aggregateBytes -= Buffer.byteLength(lines.shift()) + 1;
364
+ }
365
+ const temporary = `${filePath}.tmp-${process.pid}-${(0, node_crypto_1.randomUUID)()}`;
366
+ (0, node_fs_1.writeFileSync)(temporary, `${lines.join("\n")}\n`, { mode: 0o600, flag: "wx" });
367
+ const handle = (0, node_fs_1.openSync)(temporary, "r");
368
+ try {
369
+ (0, node_fs_1.fsyncSync)(handle);
370
+ }
371
+ finally {
372
+ (0, node_fs_1.closeSync)(handle);
373
+ }
374
+ (0, node_fs_1.renameSync)(temporary, filePath);
375
+ const directory = (0, node_fs_1.openSync)(path.dirname(filePath), "r");
376
+ try {
377
+ (0, node_fs_1.fsyncSync)(directory);
378
+ }
379
+ finally {
380
+ (0, node_fs_1.closeSync)(directory);
381
+ }
382
+ });
383
+ telegramTurnLedgerTails.set(filePath, current);
384
+ try {
385
+ await current;
386
+ }
387
+ finally {
388
+ if (telegramTurnLedgerTails.get(filePath) === current)
389
+ telegramTurnLedgerTails.delete(filePath);
390
+ }
391
+ }
392
+ function requiredText(value, label) {
393
+ if (typeof value !== "string" || value.trim().length === 0)
394
+ throw new Error(`Telegram ${label} is missing`);
395
+ return value.trim();
396
+ }
397
+ function canonicalTelegramId(value, label) {
398
+ const text = requiredText(value, label);
399
+ if (!/^[1-9][0-9]*$/u.test(text))
400
+ throw new Error(`Telegram ${label} must be a canonical positive decimal string`);
401
+ return text;
402
+ }
403
+ function telegramBotIdFromToken(token) {
404
+ const match = /^([1-9][0-9]*):/u.exec(token.trim());
405
+ if (!match)
406
+ throw new Error("Telegram bot token does not expose a canonical numeric bot id");
407
+ return match[1];
408
+ }
409
+ function canonicalTelegramIdentityKey(value) {
410
+ const key = value.trim();
411
+ if (!TELEGRAM_IDENTITY_KEY.test(key) || Buffer.from(key, "base64url").length !== 32) {
412
+ throw new Error("Telegram opaque identity key is invalid");
413
+ }
414
+ return key;
415
+ }
416
+ function errorCode(error) {
417
+ return error && typeof error === "object" && "code" in error && typeof error.code === "string" ? error.code : undefined;
418
+ }
419
+ function openSecureTelegramDirectory(directory, afterOpen) {
420
+ try {
421
+ const existing = (0, node_fs_1.lstatSync)(directory);
422
+ if (existing.isSymbolicLink())
423
+ throw new Error("Telegram state directory must not be a symbolic link");
424
+ if (!existing.isDirectory())
425
+ throw new Error("Telegram state directory is not a directory");
426
+ }
427
+ catch (error) {
428
+ if (errorCode(error) !== "ENOENT")
429
+ throw error;
430
+ (0, node_fs_1.mkdirSync)(directory, { recursive: true, mode: 0o700 });
431
+ }
432
+ const handle = (0, node_fs_1.openSync)(directory, node_fs_1.constants.O_RDONLY | node_fs_1.constants.O_DIRECTORY | node_fs_1.constants.O_NOFOLLOW);
433
+ try {
434
+ (0, node_fs_1.fchmodSync)(handle, 0o700);
435
+ afterOpen?.(handle);
436
+ const metadata = (0, node_fs_1.fstatSync)(handle);
437
+ if (!metadata.isDirectory() || (metadata.mode & 0o777) !== 0o700) {
438
+ throw new Error("Telegram state directory permissions are invalid");
439
+ }
440
+ return handle;
441
+ }
442
+ catch (error) {
443
+ (0, node_fs_1.closeSync)(handle);
444
+ throw error;
445
+ }
446
+ }
447
+ function readOrCreateTelegramIdentityKey(agentRoot, hooks = {}) {
448
+ const directory = path.join(agentRoot, "state", "senses", "telegram");
449
+ const keyPath = path.join(directory, "identity.key");
450
+ const directoryHandle = openSecureTelegramDirectory(directory, hooks.afterOpenDirectory);
451
+ const readKey = () => {
452
+ const handle = (0, node_fs_1.openSync)(keyPath, node_fs_1.constants.O_RDONLY | node_fs_1.constants.O_NOFOLLOW);
453
+ try {
454
+ const metadata = (0, node_fs_1.fstatSync)(handle);
455
+ if (!metadata.isFile() || (metadata.mode & 0o777) !== 0o600)
456
+ throw new Error("Telegram opaque identity key permissions are invalid");
457
+ return canonicalTelegramIdentityKey((0, node_fs_1.readFileSync)(handle, "utf8"));
458
+ }
459
+ finally {
460
+ (0, node_fs_1.closeSync)(handle);
461
+ }
462
+ };
463
+ try {
464
+ try {
465
+ return readKey();
466
+ }
467
+ catch (error) {
468
+ if (errorCode(error) !== "ENOENT")
469
+ throw error;
470
+ }
471
+ const key = (0, node_crypto_1.randomBytes)(32).toString("base64url");
472
+ hooks.beforeCreate?.(keyPath);
473
+ const temporaryPath = path.join(directory, `.identity.key.${(0, node_crypto_1.randomBytes)(12).toString("base64url")}.tmp`);
474
+ let temporaryHandle;
475
+ try {
476
+ temporaryHandle = (0, node_fs_1.openSync)(temporaryPath, node_fs_1.constants.O_CREAT | node_fs_1.constants.O_EXCL | node_fs_1.constants.O_WRONLY | node_fs_1.constants.O_NOFOLLOW, 0o600);
477
+ hooks.afterCreateTemporary?.(temporaryPath, keyPath);
478
+ (0, node_fs_1.writeFileSync)(temporaryHandle, `${key}\n`);
479
+ (0, node_fs_1.fsyncSync)(temporaryHandle);
480
+ (0, node_fs_1.closeSync)(temporaryHandle);
481
+ temporaryHandle = undefined;
482
+ hooks.beforePublish?.(temporaryPath, keyPath);
483
+ try {
484
+ (0, node_fs_1.linkSync)(temporaryPath, keyPath);
485
+ }
486
+ catch (error) {
487
+ if (errorCode(error) !== "EEXIST")
488
+ throw error;
489
+ return readKey();
490
+ }
491
+ (0, node_fs_1.fsyncSync)(directoryHandle);
492
+ return key;
493
+ }
494
+ finally {
495
+ if (temporaryHandle !== undefined)
496
+ (0, node_fs_1.closeSync)(temporaryHandle);
497
+ if ((0, node_fs_1.existsSync)(temporaryPath))
498
+ (0, node_fs_1.unlinkSync)(temporaryPath);
499
+ (0, node_fs_1.fsyncSync)(directoryHandle);
500
+ }
501
+ }
502
+ finally {
503
+ (0, node_fs_1.closeSync)(directoryHandle);
504
+ }
505
+ }
506
+ function opaqueTelegramSubject(identityKey, botToken, authorizedUserId, authorizedChatId) {
507
+ const payload = [
508
+ TELEGRAM_SUBJECT_DOMAIN,
509
+ `bot:${botToken.length}:${botToken}`,
510
+ `user:${authorizedUserId.length}:${authorizedUserId}`,
511
+ `chat:${authorizedChatId.length}:${authorizedChatId}`,
512
+ ].join("\0");
513
+ return `tg_${(0, node_crypto_1.createHmac)("sha256", identityKey).update(payload, "utf8").digest("base64url")}`;
514
+ }
515
+ function readTelegramSubjectIndex(agentRoot, subject) {
516
+ const indexPath = path.join(agentRoot, "state", "senses", "telegram", TELEGRAM_SUBJECT_INDEX);
517
+ let text;
518
+ let handle;
519
+ try {
520
+ handle = (0, node_fs_1.openSync)(indexPath, node_fs_1.constants.O_RDONLY | node_fs_1.constants.O_NOFOLLOW);
521
+ const metadata = (0, node_fs_1.fstatSync)(handle);
522
+ if (!metadata.isFile() || (metadata.mode & 0o777) !== 0o600)
523
+ throw new Error("Telegram identity subject index permissions are invalid");
524
+ text = (0, node_fs_1.readFileSync)(handle, "utf8");
525
+ }
526
+ catch (error) {
527
+ if (errorCode(error) === "ENOENT")
528
+ return [];
529
+ throw error;
530
+ }
531
+ finally {
532
+ if (handle !== undefined)
533
+ (0, node_fs_1.closeSync)(handle);
534
+ }
535
+ const parsed = JSON.parse(text);
536
+ if (!parsed || typeof parsed !== "object" || Array.isArray(parsed))
537
+ throw new Error("Telegram identity subject index is invalid");
538
+ const record = parsed;
539
+ if (record.version !== 1 || typeof record.subject !== "string" || !TELEGRAM_SUBJECT.test(record.subject) || !Array.isArray(record.legacySubjects)
540
+ || Object.keys(record).sort().join(",") !== "legacySubjects,subject,version") {
541
+ throw new Error("Telegram identity subject index is invalid");
542
+ }
543
+ const legacySubjects = record.legacySubjects;
544
+ if (!legacySubjects.every((candidate) => typeof candidate === "string" && TELEGRAM_SUBJECT.test(candidate))) {
545
+ throw new Error("Telegram identity subject index is invalid");
546
+ }
547
+ return record.subject === subject ? [] : [record.subject];
548
+ }
549
+ function discoverTelegramFilesystemSubjects(agentRoot) {
550
+ const subjects = new Set();
551
+ const addFriendDirectorySubjects = (root) => {
552
+ let entries;
553
+ try {
554
+ entries = (0, node_fs_1.readdirSync)(root, { withFileTypes: true });
555
+ }
556
+ catch (error) {
557
+ if (errorCode(error) === "ENOENT")
558
+ return;
559
+ throw error;
560
+ }
561
+ for (const entry of entries) {
562
+ if (!entry.isDirectory() || !entry.name.startsWith("telegram-user:"))
563
+ continue;
564
+ const candidate = entry.name.slice("telegram-user:".length);
565
+ if (TELEGRAM_SUBJECT.test(candidate))
566
+ subjects.add(candidate);
567
+ }
568
+ };
569
+ addFriendDirectorySubjects(path.join(agentRoot, "state", "sessions"));
570
+ addFriendDirectorySubjects(path.join(agentRoot, "state", "pending"));
571
+ addFriendDirectorySubjects(path.join(agentRoot, "state", "pending-returns"));
572
+ const friendsRoot = path.join(agentRoot, "friends");
573
+ let friendFiles;
574
+ try {
575
+ friendFiles = (0, node_fs_1.readdirSync)(friendsRoot, { withFileTypes: true });
576
+ }
577
+ catch (error) {
578
+ if (errorCode(error) === "ENOENT")
579
+ return [...subjects];
580
+ throw error;
581
+ }
582
+ for (const entry of friendFiles) {
583
+ if (!entry.isFile() || !entry.name.endsWith(".json"))
584
+ continue;
585
+ const friend = JSON.parse((0, node_fs_1.readFileSync)(path.join(friendsRoot, entry.name), "utf8"));
586
+ if (!friend || typeof friend !== "object" || !Array.isArray(friend.externalIds)) {
587
+ throw new Error("Telegram Friend identity record is invalid");
588
+ }
589
+ for (const external of friend.externalIds) {
590
+ if (!external || typeof external !== "object")
591
+ throw new Error("Telegram Friend external identity is invalid");
592
+ const identity = external;
593
+ if (identity.provider === "telegram-user" && typeof identity.externalId === "string" && TELEGRAM_SUBJECT.test(identity.externalId)) {
594
+ subjects.add(identity.externalId);
595
+ }
596
+ }
597
+ }
598
+ return [...subjects];
599
+ }
600
+ function writeTelegramSubjectIndex(agentRoot, subject, legacySubjects, hooks = {}) {
601
+ const directory = path.join(agentRoot, "state", "senses", "telegram");
602
+ const indexPath = path.join(directory, TELEGRAM_SUBJECT_INDEX);
603
+ const directoryHandle = openSecureTelegramDirectory(directory);
604
+ const temporaryPath = path.join(directory, `.identity-subjects.${(0, node_crypto_1.randomBytes)(12).toString("base64url")}.tmp`);
605
+ let temporaryHandle;
606
+ try {
607
+ temporaryHandle = (0, node_fs_1.openSync)(temporaryPath, node_fs_1.constants.O_CREAT | node_fs_1.constants.O_EXCL | node_fs_1.constants.O_WRONLY | node_fs_1.constants.O_NOFOLLOW, 0o600);
608
+ hooks.afterCreateTemporary?.(temporaryPath);
609
+ const contents = `${JSON.stringify({ version: 1, subject, legacySubjects: [...legacySubjects] }, null, 2)}\n`;
610
+ (0, node_fs_1.writeFileSync)(temporaryHandle, contents);
611
+ (0, node_fs_1.fsyncSync)(temporaryHandle);
612
+ (0, node_fs_1.closeSync)(temporaryHandle);
613
+ temporaryHandle = undefined;
614
+ (0, node_fs_1.renameSync)(temporaryPath, indexPath);
615
+ (0, node_fs_1.fsyncSync)(directoryHandle);
616
+ }
617
+ finally {
618
+ if (temporaryHandle !== undefined)
619
+ (0, node_fs_1.closeSync)(temporaryHandle);
620
+ if ((0, node_fs_1.existsSync)(temporaryPath))
621
+ (0, node_fs_1.unlinkSync)(temporaryPath);
622
+ (0, node_fs_1.closeSync)(directoryHandle);
623
+ }
624
+ }
625
+ async function migrateTelegramFriendIdentity(agentRoot, legacyUserId, subject) {
626
+ const store = new friends_1.FileFriendStore(path.join(agentRoot, "friends"));
627
+ const legacy = await store.findByExternalId("telegram-user", legacyUserId);
628
+ if (!legacy)
629
+ return;
630
+ const current = await store.findByExternalId("telegram-user", subject);
631
+ if (current && current.id !== legacy.id)
632
+ throw new Error("Telegram friend identity migration is ambiguous");
633
+ legacy.externalIds = legacy.externalIds.map((external) => external.provider === "telegram-user" && external.externalId === legacyUserId
634
+ ? { ...external, externalId: subject }
635
+ : external);
636
+ if (legacy.name === `Telegram user ${legacyUserId}`)
637
+ legacy.name = `Telegram user ${subject}`;
638
+ legacy.updatedAt = new Date().toISOString();
639
+ await store.put(legacy.id, legacy);
640
+ }
641
+ function migrateTelegramSessionIdentity(agentRoot, legacyUserId, legacyChatId, subject) {
642
+ const legacyFriendId = `telegram-user:${legacyUserId}`;
643
+ const opaqueFriendId = `telegram-user:${subject}`;
644
+ const migrateDirectory = (root) => {
645
+ const legacy = path.join(root, legacyFriendId);
646
+ const opaque = path.join(root, opaqueFriendId);
647
+ if (!(0, node_fs_1.existsSync)(legacy))
648
+ return (0, node_fs_1.existsSync)(opaque) ? opaque : null;
649
+ if ((0, node_fs_1.existsSync)(opaque))
650
+ throw new Error("Telegram session identity migration is ambiguous");
651
+ (0, node_fs_1.mkdirSync)(root, { recursive: true });
652
+ (0, node_fs_1.renameSync)(legacy, opaque);
653
+ return opaque;
654
+ };
655
+ const sessions = migrateDirectory(path.join(agentRoot, "state", "sessions"));
656
+ if (sessions) {
657
+ const legacySession = path.join(sessions, "telegram", `telegram_${legacyChatId}.json`);
658
+ const opaqueSession = path.join(sessions, "telegram", `telegram_${subject}.json`);
659
+ if ((0, node_fs_1.existsSync)(legacySession)) {
660
+ if ((0, node_fs_1.existsSync)(opaqueSession))
661
+ throw new Error("Telegram session file migration is ambiguous");
662
+ (0, node_fs_1.renameSync)(legacySession, opaqueSession);
663
+ }
664
+ }
665
+ const pending = migrateDirectory(path.join(agentRoot, "state", "pending"));
666
+ if (pending) {
667
+ const legacyPending = path.join(pending, "telegram", `telegram:${legacyChatId}`);
668
+ const opaquePending = path.join(pending, "telegram", `telegram:${subject}`);
669
+ if ((0, node_fs_1.existsSync)(legacyPending)) {
670
+ if ((0, node_fs_1.existsSync)(opaquePending))
671
+ throw new Error("Telegram pending identity migration is ambiguous");
672
+ (0, node_fs_1.renameSync)(legacyPending, opaquePending);
673
+ }
674
+ }
675
+ migrateDirectory(path.join(agentRoot, "state", "pending-returns"));
676
+ }
677
+ function redactTelegramPrivateValues(error, privateValues) {
678
+ let message = error instanceof Error ? error.message : String(error);
679
+ for (const privateValue of privateValues) {
680
+ if (privateValue)
681
+ message = message.split(privateValue).join("[redacted]");
682
+ }
683
+ return message;
684
+ }
685
+ function parseTelegramSenseCredentials(value) {
686
+ return {
687
+ botToken: requiredText(value.telegramBotToken, "bot token"),
688
+ authorizedUserId: canonicalTelegramId(value.telegramAuthorizedUserId, "authorized user id"),
689
+ authorizedChatId: canonicalTelegramId(value.telegramAuthorizedChatId, "authorized chat id"),
690
+ };
691
+ }
692
+ function createTelegramSenseApp(options) {
693
+ const authorityTransport = options.authorityTransport;
694
+ if (authorityTransport && options.agentName !== "sanctuary") {
695
+ throw new Error("Sanctuary Telegram authority transport cannot serve another agent");
696
+ }
697
+ if (authorityTransport && options.credentials.botToken !== undefined) {
698
+ throw new Error("Sanctuary Telegram authority transport forbids a resident bot token");
699
+ }
700
+ const botToken = options.credentials.botToken === undefined
701
+ ? null
702
+ : requiredText(options.credentials.botToken, "bot token");
703
+ if (!botToken && !authorityTransport) {
704
+ throw new Error("Telegram bot token is missing; Sanctuary requires an explicit gateway transport");
705
+ }
706
+ const botId = options.credentials.botId !== undefined
707
+ ? canonicalTelegramId(options.credentials.botId, "bot id")
708
+ : options.admission && botToken ? telegramBotIdFromToken(botToken) : null;
709
+ if (authorityTransport && botId === null) {
710
+ throw new Error("Sanctuary Telegram authority transport requires a pinned bot id");
711
+ }
712
+ const authorizedUserId = canonicalTelegramId(options.credentials.authorizedUserId, "authorized user id");
713
+ const authorizedChatId = canonicalTelegramId(options.credentials.authorizedChatId, "authorized chat id");
714
+ const agentRoot = options._agentRoot ?? (0, identity_1.getAgentRoot)(options.agentName);
715
+ const identityKey = options.identityKey === undefined
716
+ ? readOrCreateTelegramIdentityKey(agentRoot)
717
+ : canonicalTelegramIdentityKey(options.identityKey);
718
+ const subject = opaqueTelegramSubject(identityKey, botId ?? botToken, authorizedUserId, authorizedChatId);
719
+ const transportPrivateValues = [botToken ?? "", authorizedUserId, authorizedChatId];
720
+ const transportError = (error) => redactTelegramPrivateValues(error, transportPrivateValues);
721
+ const api = authorityTransport?.api ?? options.api ?? (0, telegram_client_1.createTelegramBotApi)({ token: botToken });
722
+ // Only the root authority transport admits sendChatAction, so presence is offered
723
+ // exactly where it is actually supported rather than attempted everywhere.
724
+ const proactiveRepetitionGuard = createProactiveRepetitionGuard();
725
+ const sendTypingIndicator = authorityTransport
726
+ ? async (chatId) => { await api.request("sendChatAction", { chat_id: chatId, action: "typing" }); }
727
+ : undefined;
728
+ const offsetStore = options.offsetStore ?? new telegram_client_1.FileTelegramOffsetStore(path.join(agentRoot, "state", "senses", "telegram", "offset.json"));
729
+ const inboxStore = options.inboxStore ?? new telegram_client_1.FileTelegramUpdateInboxStore(path.join(agentRoot, "state", "senses", "telegram", "inbox.json"));
730
+ const admissionStore = options.admission
731
+ ? new telegram_admission_1.FileTelegramAdmissionStore(path.join(agentRoot, "state", "senses", "telegram", "admissions"))
732
+ : undefined;
733
+ const runTurn = options.runTurn ?? options._runTurn ?? shared_turn_1.runSenseTurn;
734
+ const collectToolReceipts = options._runWithToolReceiptCollection ?? sanctuary_runtime_2.runWithSanctuaryToolReceiptCollection;
735
+ const useSanctuaryRuntime = options.agentName === "sanctuary" && !options.runTurn;
736
+ const readScenarioHandleDigest = () => (options.acceptanceMarker
737
+ ? options.acceptanceMarker()
738
+ : (0, sanctuary_acceptance_marker_1.readSanctuaryAcceptanceMarker)(options.agentName))?.scenarioHandleDigest;
739
+ let acceptanceAuditLease;
740
+ const retireAcceptanceAudit = () => {
741
+ const lease = acceptanceAuditLease;
742
+ if (!lease)
743
+ return;
744
+ const errors = [];
745
+ try {
746
+ lease.ledger.assertHealthy();
747
+ }
748
+ catch (error) {
749
+ errors.push(error);
750
+ }
751
+ try {
752
+ lease.release();
753
+ }
754
+ catch (error) {
755
+ errors.push(error);
756
+ }
757
+ finally {
758
+ acceptanceAuditLease = undefined;
759
+ }
760
+ if (errors.length === 1)
761
+ throw errors[0];
762
+ if (errors.length > 1)
763
+ throw new AggregateError(errors, "Telegram acceptance audit retirement failed");
764
+ };
765
+ const ensureAcceptanceAudit = () => {
766
+ if (!useSanctuaryRuntime)
767
+ return undefined;
768
+ const scenarioHandleDigest = readScenarioHandleDigest();
769
+ const effectScenarioHandleDigest = telegramAcceptanceAuditScenario.getStore();
770
+ if (effectScenarioHandleDigest !== undefined && (scenarioHandleDigest ?? null) !== effectScenarioHandleDigest) {
771
+ const error = new Error("Telegram acceptance audit scenario ownership drift");
772
+ acceptanceAuditLease?.ledger.poison(error);
773
+ throw error;
774
+ }
775
+ if (scenarioHandleDigest === undefined) {
776
+ if (acceptanceAuditLease) {
777
+ retireAcceptanceAudit();
778
+ }
779
+ return undefined;
780
+ }
781
+ if (!/^[0-9a-f]{64}$/u.test(scenarioHandleDigest))
782
+ throw new Error("Telegram acceptance audit scenario handle is invalid");
783
+ if (!acceptanceAuditLease) {
784
+ acceptanceAuditLease = acquireTelegramAcceptanceAudit(options.acceptanceReceiptRoot ?? agentRoot, options.agentName, identityKey, transportPrivateValues, scenarioHandleDigest, options._acceptanceAuditReleaseHook, options._acceptanceAuditMaxBytes);
785
+ }
786
+ else if (scenarioHandleDigest !== acceptanceAuditLease.scenarioHandleDigest) {
787
+ retireAcceptanceAudit();
788
+ acceptanceAuditLease = acquireTelegramAcceptanceAudit(options.acceptanceReceiptRoot ?? agentRoot, options.agentName, identityKey, transportPrivateValues, scenarioHandleDigest, options._acceptanceAuditReleaseHook, options._acceptanceAuditMaxBytes);
789
+ }
790
+ return acceptanceAuditLease;
791
+ };
792
+ ensureAcceptanceAudit();
793
+ const acceptanceAuditBarrier = () => {
794
+ const lease = ensureAcceptanceAudit();
795
+ if (!lease)
796
+ return;
797
+ lease.ledger.assertHealthy();
798
+ lease.ledger.assertCapacity(8);
799
+ };
800
+ const runWithAcceptanceAuditOwner = async (operation) => {
801
+ const scenarioHandleDigest = readScenarioHandleDigest() ?? null;
802
+ return telegramAcceptanceAuditScenario.run(scenarioHandleDigest, async () => {
803
+ acceptanceAuditBarrier();
804
+ const ownerDigest = acceptanceAuditLease?.ownerDigest ?? "";
805
+ return telegramAcceptanceAuditOwner.run(ownerDigest, async () => {
806
+ try {
807
+ const result = await operation();
808
+ acceptanceAuditBarrier();
809
+ return result;
810
+ }
811
+ catch (error) {
812
+ try {
813
+ acceptanceAuditBarrier();
814
+ }
815
+ catch (auditError) {
816
+ if (error === auditError)
817
+ throw auditError;
818
+ throw new AggregateError([error, auditError], "Telegram effect and acceptance audit verification failed");
819
+ }
820
+ throw error;
821
+ }
822
+ });
823
+ });
824
+ };
825
+ const emitDurableSettlementEvidence = async (event, meta) => {
826
+ if (event === "telegram.callback_settled") {
827
+ await (0, runtime_1.emitNervesEventDurable)({
828
+ component: "senses",
829
+ event: "telegram.callback_settled",
830
+ message: "Telegram approval acceptance evidence durably recorded",
831
+ meta,
832
+ });
833
+ }
834
+ else if (event === "telegram.callback_recovery_settled") {
835
+ await (0, runtime_1.emitNervesEventDurable)({
836
+ component: "senses",
837
+ event: "telegram.callback_recovery_settled",
838
+ message: "Telegram approval acceptance evidence durably recorded",
839
+ meta,
840
+ });
841
+ }
842
+ else {
843
+ throw new Error("Telegram durable acceptance settlement event is unsupported");
844
+ }
845
+ };
846
+ const commitAcceptanceEvidence = useSanctuaryRuntime ? async (event, meta) => {
847
+ acceptanceAuditBarrier();
848
+ const lease = acceptanceAuditLease;
849
+ if (!lease) {
850
+ await emitDurableSettlementEvidence(event, meta);
851
+ return;
852
+ }
853
+ await telegramAcceptanceAuditExplicitCommit.run(true, async () => {
854
+ await emitDurableSettlementEvidence(event, meta);
855
+ });
856
+ acceptanceAuditBarrier();
857
+ lease.ledger.append({
858
+ ts: new Date().toISOString(),
859
+ level: "info",
860
+ event,
861
+ trace_id: (0, node_crypto_1.randomUUID)(),
862
+ component: "senses",
863
+ message: "Telegram approval acceptance evidence durably recorded",
864
+ meta,
865
+ });
866
+ lease.ledger.assertHealthy();
867
+ } : undefined;
868
+ const releaseAcceptanceAudit = (primaryError) => {
869
+ try {
870
+ acceptanceAuditLease?.release();
871
+ }
872
+ catch (releaseError) {
873
+ throw new AggregateError([primaryError, releaseError], "Telegram sense construction and audit release failed");
874
+ }
875
+ throw primaryError;
876
+ };
877
+ let effectJournal;
878
+ let approvalRuntime;
879
+ let rootHostRuntime;
880
+ const approvalCoordinatorFactory = (context) => ({
881
+ propose: (request) => {
882
+ if (request.toolCall.type === "function" && request.toolCall.function.name === "sanctuary_host_execute") {
883
+ if (!rootHostRuntime)
884
+ throw new Error("root host approval coordinator is unavailable");
885
+ return rootHostRuntime.coordinator(context).propose(request);
886
+ }
887
+ return approvalRuntime.coordinator(context).propose(request);
888
+ },
889
+ });
890
+ const getEffectJournal = () => {
891
+ effectJournal ??= new telegram_effect_adapter_1.FileTelegramEffectJournal(path.join(agentRoot, "state", "telegram", "effects"));
892
+ return effectJournal;
893
+ };
894
+ const configuredOwnerFriendId = options.admission?.ownerFriendId ?? `telegram-user:${subject}`;
895
+ const configuredOwnerSessionKey = `telegram:${subject}`;
896
+ const defaultEffectAuthorization = async (input) => {
897
+ const target = input.target;
898
+ if (target.kind === "admission_gate") {
899
+ const pending = admissionStore.read(target.admissionId);
900
+ const authorization = (0, relationship_authorization_1.authorizeRelationshipAccess)({
901
+ profiles: [],
902
+ pendingAdmission: { admissionId: pending.id, botId: pending.botId, userId: pending.userId, chatId: pending.chatId, expiresAt: new Date(pending.expiresAt).toISOString() },
903
+ request: { kind: "admission_gate", admissionId: target.admissionId, botId: target.botId, userId: target.userId, chatId: target.chatId, effect: "fixed_ack", idempotencyKey: input.idempotencyKey, expiresAt: new Date(pending.expiresAt).toISOString() },
904
+ });
905
+ const admissionAuthorization = authorization;
906
+ return { allowed: true, receiptId: admissionAuthorization.receiptId, expiresAt: admissionAuthorization.expiresAt, transport: { chatId: target.chatId } };
907
+ }
908
+ if (input.authorClass === "system_failsafe" && (input.effect.kind !== "text" || input.effect.text !== telegram_effect_adapter_1.FIXED_USENET_SYSTEM_FAILSAFE || !input.idempotencyKey.startsWith("system-failsafe:"))) {
909
+ return { allowed: false, reason: "system failsafe shape is not fixed" };
910
+ }
911
+ if (target.friendId === configuredOwnerFriendId && target.sessionKey === configuredOwnerSessionKey
912
+ && await approvalRuntime?.isPendingTerminalControl?.(input)) {
913
+ return {
914
+ allowed: true, receiptId: `approval-terminal:${(0, node_crypto_1.createHash)("sha256").update(input.idempotencyKey).digest("hex")}`,
915
+ expiresAt: new Date(Date.now() + 5 * 60_000).toISOString(), transport: { chatId: authorizedChatId },
916
+ };
917
+ }
918
+ if (options.authorizeRelationshipEffect) {
919
+ if (target.friendId === configuredOwnerFriendId && target.sessionKey !== configuredOwnerSessionKey)
920
+ return { allowed: false, reason: "owner relationship session binding changed" };
921
+ return options.authorizeRelationshipEffect(input);
922
+ }
923
+ if (target.friendId !== `telegram-user:${subject}` || target.sessionKey !== `telegram:${subject}`) {
924
+ return { allowed: false, reason: "effect target is not the configured owner relationship" };
925
+ }
926
+ return { allowed: true, receiptId: `configured-owner:${subject}`, expiresAt: new Date(Date.now() + 5 * 60_000).toISOString(), transport: { chatId: authorizedChatId } };
927
+ };
928
+ const authorizeEffect = options.authorizeEffect ?? defaultEffectAuthorization;
929
+ let recordAcceptedEffects;
930
+ const configuredOwnerTarget = () => ({ kind: "approved_relationship", friendId: configuredOwnerFriendId, sessionKey: configuredOwnerSessionKey });
931
+ const executeAuthorizedEffect = (0, telegram_effect_adapter_1.createTelegramAuthorizedEffectExecutor)({
932
+ store: getEffectJournal,
933
+ api,
934
+ authorize: authorizeEffect,
935
+ barrier: acceptanceAuditBarrier,
936
+ });
937
+ const recordConfiguredOwnerEffect = async (artifact, causalEventId) => {
938
+ await recordAcceptedEffects((0, shared_turn_1.getSenseSessionPath)(options.agentName, configuredOwnerFriendId, "telegram", configuredOwnerSessionKey, agentRoot), [artifact], undefined, causalEventId ? { [artifact.id]: causalEventId } : undefined);
939
+ };
940
+ const approvalEffects = (0, telegram_effect_adapter_1.createTelegramApprovalEffectPort)({ target: configuredOwnerTarget(), chatId: authorizedChatId, execute: executeAuthorizedEffect, record: recordConfiguredOwnerEffect });
941
+ let toolContext;
942
+ let approvalTransport;
943
+ let interactiveControl;
944
+ try {
945
+ toolContext = useSanctuaryRuntime ? (options._toolContext ?? (0, sanctuary_runtime_2.createSanctuaryToolContext)(options.agentName)) : undefined;
946
+ if (toolContext && options.telegramContactManager)
947
+ toolContext.telegramContactManager = options.telegramContactManager;
948
+ approvalRuntime = options.approvalRuntime ?? (useSanctuaryRuntime ? (options._createApprovalRuntime ?? telegram_approval_runtime_1.createTelegramApprovalRuntime)({
949
+ agentName: options.agentName,
950
+ api,
951
+ authorizedUserId,
952
+ authorizedChatId,
953
+ subject,
954
+ identityKey,
955
+ toolContext: toolContext ?? {},
956
+ ...(options.resolveRelationshipAuthorization ? {
957
+ resolveOwnerRelationship: (binding) => options.resolveRelationshipAuthorization({
958
+ friendId: binding.friendId, requestId: binding.requestId, sessionEventId: binding.sessionEventId, sessionKey: binding.sessionKey,
959
+ botId: botId, userId: authorizedUserId, chatId: authorizedChatId,
960
+ }),
961
+ } : {}),
962
+ resolveLiveToolContext: async (record) => {
963
+ const sessionPath = (0, shared_turn_1.getSenseSessionPath)(options.agentName, configuredOwnerFriendId, "telegram", configuredOwnerSessionKey, agentRoot);
964
+ if (record.transport !== "telegram" || record.requesterId !== subject
965
+ || record.transportUserId !== subject || record.transportChatId !== subject
966
+ || record.sessionKey !== configuredOwnerSessionKey || path.resolve(record.sessionPath) !== path.resolve(sessionPath)) {
967
+ throw new Error("approval is not bound to the configured owner session");
968
+ }
969
+ const envelope = (0, session_events_1.loadSessionEnvelopeFile)(sessionPath);
970
+ const ingress = envelope && (0, session_events_1.selectEffectiveSessionEvents)(envelope.events).findLast((event) => event.role === "user");
971
+ if (!ingress)
972
+ throw new Error("approval owner ingress is unavailable");
973
+ const relationshipAuthorization = await resolveLiveRelationshipAuthorization({
974
+ friendId: configuredOwnerFriendId, requestId: `approval:${record.approvalId}`, sessionEventId: ingress.id,
975
+ botId: botId, userId: authorizedUserId, chatId: authorizedChatId, sessionKey: configuredOwnerSessionKey,
976
+ });
977
+ if (relationshipAuthorization.profileId !== "sanctuary-owner")
978
+ throw new Error("approval owner profile is unavailable");
979
+ const friendStore = new friends_1.FileFriendStore(path.join(agentRoot, "friends"));
980
+ const friend = await friendStore.get(configuredOwnerFriendId);
981
+ if (!friend)
982
+ throw new Error("approval owner Friend is unavailable");
983
+ return {
984
+ ...toolContext, signin: async () => undefined, agentName: options.agentName, agentRoot,
985
+ currentSession: { friendId: friend.id, channel: "telegram", key: configuredOwnerSessionKey, sessionPath },
986
+ currentUserMessage: (0, session_events_1.extractEventText)(ingress),
987
+ context: { friend, channel: (0, friends_1.getChannelCapabilities)("telegram") }, friendStore, relationshipAuthorization,
988
+ };
989
+ },
990
+ effects: approvalEffects,
991
+ effectBarrier: acceptanceAuditBarrier,
992
+ dependencies: {
993
+ agentRoot,
994
+ acceptanceMarker: () => {
995
+ const scenarioHandleDigest = readScenarioHandleDigest();
996
+ return scenarioHandleDigest ? { scenarioHandleDigest } : null;
997
+ },
998
+ commitAcceptanceEvidence,
999
+ },
1000
+ }) : undefined);
1001
+ approvalTransport = options.approvalTransport ?? approvalRuntime?.transport;
1002
+ const port = authorityTransport?.hostApproval;
1003
+ if (useSanctuaryRuntime && port?.pins.expectedBotId === botId
1004
+ && port.pins.expectedOwnerUserId === authorizedUserId && port.pins.expectedOwnerChatId === authorizedChatId) {
1005
+ rootHostRuntime = (0, root_host_approval_runtime_1.createRootHostApprovalRuntime)({
1006
+ agentRoot, port, effectBarrier: acceptanceAuditBarrier, approvalCoordinatorFactory,
1007
+ resolveContext: async (binding) => {
1008
+ const sessionPath = (0, shared_turn_1.getSenseSessionPath)(options.agentName, configuredOwnerFriendId, "telegram", configuredOwnerSessionKey, agentRoot);
1009
+ if (binding.agentRoot !== agentRoot || binding.friendId !== configuredOwnerFriendId
1010
+ || binding.sessionKey !== configuredOwnerSessionKey || binding.sessionPath !== sessionPath) {
1011
+ throw new Error("root host continuation owner session changed");
1012
+ }
1013
+ const relationshipAuthorization = await resolveLiveRelationshipAuthorization({
1014
+ friendId: binding.friendId, requestId: binding.requestId, sessionEventId: binding.sessionEventId,
1015
+ sessionKey: binding.sessionKey, botId: botId, userId: authorizedUserId, chatId: authorizedChatId,
1016
+ });
1017
+ const friendStore = new friends_1.FileFriendStore(path.join(agentRoot, "friends"));
1018
+ const friend = await friendStore.get(binding.friendId);
1019
+ if (!friend)
1020
+ throw new Error("root host owner Friend is unavailable");
1021
+ return {
1022
+ ...toolContext, signin: async () => undefined, agentName: options.agentName, agentRoot,
1023
+ currentSession: { friendId: friend.id, channel: "telegram", key: binding.sessionKey, sessionPath },
1024
+ context: { friend, channel: (0, friends_1.getChannelCapabilities)("telegram") }, friendStore, relationshipAuthorization,
1025
+ rootHost: { port },
1026
+ };
1027
+ },
1028
+ deliver: async (text, approvalId) => {
1029
+ await approvalEffects.sendText({
1030
+ idempotencyKey: `root-host:${approvalId}:continuation:${(0, node_crypto_1.createHash)("sha256").update(text).digest("hex")}`,
1031
+ chatId: authorizedChatId, text, authorClass: "butler",
1032
+ });
1033
+ },
1034
+ });
1035
+ }
1036
+ interactiveControl = useSanctuaryRuntime && approvalTransport
1037
+ ? (options._createInteractiveControl ?? sanctuary_interactive_control_1.createSanctuaryInteractiveControl)({
1038
+ agentRoot,
1039
+ transport: approvalTransport,
1040
+ authorizedUserId,
1041
+ authorizedChatId,
1042
+ runRequest: runWithAcceptanceAuditOwner,
1043
+ })
1044
+ : undefined;
1045
+ }
1046
+ catch (error) {
1047
+ releaseAcceptanceAudit(error);
1048
+ }
1049
+ const migrateIdentity = options.migrateIdentity ?? (async () => {
1050
+ const legacySubjects = new Set([
1051
+ ...readTelegramSubjectIndex(agentRoot, subject),
1052
+ ...discoverTelegramFilesystemSubjects(agentRoot),
1053
+ ...(approvalRuntime?.legacySubjects?.() ?? []),
1054
+ ]);
1055
+ legacySubjects.delete(subject);
1056
+ if (legacySubjects.size > 1)
1057
+ throw new Error("Telegram legacy identity subject migration is ambiguous");
1058
+ const migrationSubjects = [...legacySubjects];
1059
+ for (const legacySubject of migrationSubjects) {
1060
+ acceptanceAuditBarrier();
1061
+ migrateTelegramSessionIdentity(agentRoot, legacySubject, legacySubject, subject);
1062
+ acceptanceAuditBarrier();
1063
+ if (!options.admission)
1064
+ await migrateTelegramFriendIdentity(agentRoot, legacySubject, subject);
1065
+ }
1066
+ acceptanceAuditBarrier();
1067
+ migrateTelegramSessionIdentity(agentRoot, authorizedUserId, authorizedChatId, subject);
1068
+ acceptanceAuditBarrier();
1069
+ if (!options.admission)
1070
+ await migrateTelegramFriendIdentity(agentRoot, authorizedUserId, subject);
1071
+ acceptanceAuditBarrier();
1072
+ approvalRuntime?.migrateIdentity?.(migrationSubjects);
1073
+ acceptanceAuditBarrier();
1074
+ writeTelegramSubjectIndex(agentRoot, subject, migrationSubjects, options.subjectIndexHooks);
1075
+ });
1076
+ let approvalReconcileTimer;
1077
+ let approvalReconciliationActive = false;
1078
+ const approvalReconciliationsInFlight = new Set();
1079
+ let nextApprovalReconcileDeadline;
1080
+ let reconcileSystemFailsafes;
1081
+ let stopPromise;
1082
+ let runPromise;
1083
+ const clearApprovalReconcileTimer = () => {
1084
+ if (approvalReconcileTimer)
1085
+ clearTimeout(approvalReconcileTimer);
1086
+ approvalReconcileTimer = undefined;
1087
+ };
1088
+ const scheduleApprovalReconcile = () => {
1089
+ if (!approvalTransport || !approvalReconciliationActive)
1090
+ return;
1091
+ const scheduledAt = nextApprovalReconcileDeadline ?? (Date.now() + APPROVAL_EXPIRY_RECONCILE_INTERVAL_MS);
1092
+ nextApprovalReconcileDeadline = scheduledAt + APPROVAL_EXPIRY_RECONCILE_INTERVAL_MS;
1093
+ const delay = Math.max(0, scheduledAt - Date.now());
1094
+ approvalReconcileTimer = setTimeout(() => {
1095
+ approvalReconcileTimer = undefined;
1096
+ scheduleApprovalReconcile();
1097
+ const reconciliation = runWithAcceptanceAuditOwner(async () => {
1098
+ await approvalTransport.reconcileExpired();
1099
+ await rootHostRuntime?.recover();
1100
+ }).catch((error) => {
1101
+ acceptanceAuditBarrier();
1102
+ (0, runtime_1.emitNervesEvent)({
1103
+ level: "error",
1104
+ component: "senses",
1105
+ event: "senses.telegram_approval_reconcile_error",
1106
+ message: "Telegram approval expiry reconciliation failed",
1107
+ meta: { agentName: options.agentName, subject, error: transportError(error) },
1108
+ });
1109
+ }).finally(() => {
1110
+ approvalReconciliationsInFlight.delete(reconciliation);
1111
+ });
1112
+ approvalReconciliationsInFlight.add(reconciliation);
1113
+ const failsafe = runWithAcceptanceAuditOwner(reconcileSystemFailsafes)
1114
+ .finally(() => { approvalReconciliationsInFlight.delete(failsafe); });
1115
+ approvalReconciliationsInFlight.add(failsafe);
1116
+ }, delay);
1117
+ };
1118
+ const deliverButlerEffect = async (text, idempotencyKey, signal, onMessageDelivered, exactDownloadCreditQuestion = false) => {
1119
+ const presentedText = (0, sanctuary_download_credit_presentation_1.presentSanctuaryDownloadCreditReply)(options.agentName, exactDownloadCreditQuestion, text);
1120
+ return executeAuthorizedEffect({
1121
+ idempotencyKey,
1122
+ target: configuredOwnerTarget(),
1123
+ authorClass: "butler",
1124
+ effect: { kind: "text", text: presentedText },
1125
+ ...(signal ? { signal } : {}),
1126
+ ...(onMessageDelivered ? { onMessageDelivered } : {}),
1127
+ });
1128
+ };
1129
+ recordAcceptedEffects = async (sessionPath, artifacts, bootstrapInbound, causalEventIds) => {
1130
+ await (0, telegram_effect_adapter_1.recordTelegramEffectsInSession)({ store: getEffectJournal(), sessionPath, artifacts, ...(bootstrapInbound ? { inbound: bootstrapInbound } : {}), ...(causalEventIds ? { causalEventIds } : {}) });
1131
+ for (const artifact of artifacts) {
1132
+ if (artifact.target.kind !== "approved_relationship" || !artifact.target.requestId || artifact.effect.kind !== "text")
1133
+ continue;
1134
+ const recorded = getEffectJournal().read(artifact.id);
1135
+ if (!recorded.parts.every((part) => part.state === "session_recorded"))
1136
+ continue;
1137
+ if (!artifact.obligationReturnId)
1138
+ continue;
1139
+ const obligation = (0, obligations_1.readObligation)(agentRoot, artifact.obligationReturnId);
1140
+ if (obligation?.returnReadyAt && obligation.requestId === artifact.target.requestId
1141
+ && obligation.owedTo?.friendId === artifact.target.friendId && obligation.owedTo.channel === "telegram"
1142
+ && obligation.owedTo.key === artifact.target.sessionKey)
1143
+ (0, obligations_1.fulfillObligation)(agentRoot, obligation.id);
1144
+ }
1145
+ };
1146
+ const deliverAwaitFollowUp = async (request) => {
1147
+ if (!request.requestId)
1148
+ return { status: "blocked", detail: "Telegram follow-up is missing its request binding" };
1149
+ const sessionKey = (0, config_1.canonicalizeTelegramSessionKey)(request.key);
1150
+ try {
1151
+ const obligation = (0, obligations_1.findPendingObligationForRequest)(agentRoot, { requestId: request.requestId, owedTo: { friendId: request.friendId, channel: "telegram", key: sessionKey } });
1152
+ if (obligation && !obligation.returnReadyAt)
1153
+ (0, obligations_1.markObligationReturnReady)(agentRoot, obligation.id, request.deliveryId ?? `telegram-return:${request.requestId}`);
1154
+ const expiry = request.deliveryId?.endsWith(":expired") === true;
1155
+ const artifact = await executeAuthorizedEffect({
1156
+ idempotencyKey: `await-${expiry ? "expiry-" : ""}follow-up:${request.requestId}:${(0, node_crypto_1.createHash)("sha256").update(request.deliveryId ?? request.content).digest("hex")}`,
1157
+ target: { kind: "approved_relationship", friendId: request.friendId, sessionKey, requestId: request.requestId },
1158
+ authorClass: "butler",
1159
+ effect: { kind: "text", text: request.content },
1160
+ ...(obligation ? { obligationReturnId: obligation.id } : {}),
1161
+ });
1162
+ await recordAcceptedEffects((0, shared_turn_1.getSenseSessionPath)(options.agentName, request.friendId, "telegram", sessionKey, agentRoot), [artifact]);
1163
+ return { status: "delivered_now", detail: "sent to the exact request-bound Telegram chat" };
1164
+ }
1165
+ catch (error) {
1166
+ return { status: "blocked", detail: error instanceof Error ? error.message : String(error) };
1167
+ }
1168
+ };
1169
+ (0, tools_awaiting_1.setAwaitToolDeps)({
1170
+ buildDeliveryDeps: () => ({
1171
+ agentName: options.agentName,
1172
+ queuePending: (message) => (0, pending_1.queuePendingMessage)((0, pending_1.getPrivateRuntimePendingDir)(options.agentName), message),
1173
+ deliverers: {
1174
+ telegram: deliverAwaitFollowUp,
1175
+ },
1176
+ }),
1177
+ });
1178
+ reconcileSystemFailsafes = async () => {
1179
+ if (!options.privilegedFailsafe)
1180
+ return;
1181
+ const target = configuredOwnerTarget();
1182
+ try {
1183
+ await (0, telegram_effect_adapter_1.sweepTelegramSystemFailsafes)({
1184
+ eventRoot: options.privilegedFailsafe.eventRoot,
1185
+ target,
1186
+ verifyProtectiveState: options.privilegedFailsafe.verifyProtectiveState,
1187
+ execute: executeAuthorizedEffect,
1188
+ recordArtifact: async (artifact) => {
1189
+ await recordAcceptedEffects((0, shared_turn_1.getSenseSessionPath)(options.agentName, target.friendId, "telegram", target.sessionKey, agentRoot), [artifact]);
1190
+ },
1191
+ });
1192
+ }
1193
+ catch (error) {
1194
+ (0, runtime_1.emitNervesEvent)({ level: "error", component: "senses", event: "senses.telegram_system_failsafe_error", message: "Telegram system failsafe reconciliation failed", meta: { agentName: options.agentName, subject, error: transportError(error) } });
1195
+ }
1196
+ };
1197
+ async function resolveLiveRelationshipAuthorization(input) {
1198
+ if (!options.resolveRelationshipAuthorization)
1199
+ throw new Error("Telegram relationship authorization resolver is unavailable");
1200
+ const authorization = await options.resolveRelationshipAuthorization(input);
1201
+ if (authorization.subject.friendId !== input.friendId || authorization.subject.admissionState !== "active")
1202
+ throw new Error("Telegram relationship admission is not active");
1203
+ return {
1204
+ requestId: input.requestId,
1205
+ profileId: authorization.profileId,
1206
+ authorizedContextScopes: authorization.authorizedContextScopes,
1207
+ advertisedToolNames: authorization.advertisedToolNames,
1208
+ actor: authorization.actor,
1209
+ resolveCurrent: () => resolveLiveRelationshipAuthorization(input),
1210
+ authorizeTool: async (name, args) => (await options.resolveRelationshipAuthorization(input)).authorizeTool(name, args),
1211
+ };
1212
+ }
1213
+ const prepareRelationshipRunAgentOptions = (input) => {
1214
+ if (!options.resolveRelationshipAuthorization)
1215
+ throw new Error("Telegram relationship authorization resolver is unavailable");
1216
+ const relationshipCoordinates = { ...input, botId: botId };
1217
+ return async ({ runAgentOptions, activeCares = [], careEvidenceNow = Date.now() }) => {
1218
+ const relationshipAuthorization = await resolveLiveRelationshipAuthorization(relationshipCoordinates);
1219
+ const isSanctuaryOwner = options.agentName === "sanctuary" && relationshipAuthorization.profileId === "sanctuary-owner";
1220
+ const isSanctuaryHouseholdConversation = options.agentName === "sanctuary"
1221
+ && (relationshipAuthorization.profileId === "sanctuary-owner" || relationshipAuthorization.profileId === "sanctuary-household");
1222
+ const storageOptimization = isSanctuaryOwner
1223
+ ? (0, sanctuary_storage_optimization_contract_1.sanctuaryStorageOptimizationRequiredToolCalls)(input.userMessage, relationshipAuthorization.advertisedToolNames)
1224
+ : undefined;
1225
+ const staleDockerCare = isSanctuaryOwner
1226
+ ? (0, sanctuary_full_visibility_contract_1.sanctuaryStaleDockerCareRequiredToolCalls)(activeCares, careEvidenceNow, relationshipAuthorization.advertisedToolNames)
1227
+ : undefined;
1228
+ const fullVisibility = isSanctuaryOwner
1229
+ ? (0, sanctuary_full_visibility_contract_1.sanctuaryFullVisibilityRequiredToolCalls)(input.userMessage, relationshipAuthorization.advertisedToolNames, () => staleDockerCare?.currentRiskClaims() ?? [])
1230
+ : undefined;
1231
+ const mediaCatalog = isSanctuaryHouseholdConversation
1232
+ ? (0, sanctuary_media_catalog_contract_1.sanctuaryMediaCatalogRequiredToolCalls)(input.userMessage, relationshipAuthorization.advertisedToolNames)
1233
+ : undefined;
1234
+ const installState = isSanctuaryOwner
1235
+ ? (0, sanctuary_install_state_contract_1.sanctuaryInstallStateRequiredToolCalls)(input.userMessage, relationshipAuthorization.advertisedToolNames)
1236
+ : undefined;
1237
+ if (input.fullVisibilityProgress && fullVisibility)
1238
+ input.fullVisibilityProgress.fallback = fullVisibility.emptyResponseFallback;
1239
+ const contracts = [storageOptimization, fullVisibility, staleDockerCare, mediaCatalog, installState].filter(Boolean);
1240
+ const ownedNames = contracts.map((contract) => new Set(contract.names));
1241
+ const requiredToolCalls = contracts.length === 0 ? undefined : {
1242
+ names: [...new Set(contracts.flatMap((contract) => [...contract.names]))],
1243
+ get retryMessage() { return contracts.map((contract) => contract.retryMessage).join(" "); },
1244
+ requireSuccessfulResults: true,
1245
+ validateRequiredToolResult: (name, result, args) => contracts.every((contract, index) => {
1246
+ if (!ownedNames[index].has(name))
1247
+ return true;
1248
+ return contract.validateRequiredToolResult?.(name, result, args) ?? true;
1249
+ }),
1250
+ validateToolCallBeforeDispatch: (name, args) => contracts.map((contract) => contract.validateToolCallBeforeDispatch?.(name, args)).find((rejection) => rejection !== undefined),
1251
+ requiredToolCallsAfterResult: (name, args, result) => {
1252
+ const added = new Set();
1253
+ contracts.forEach((contract, index) => {
1254
+ if (!ownedNames[index].has(name) || !contract.requiredToolCallsAfterResult)
1255
+ return;
1256
+ for (const requiredName of contract.requiredToolCallsAfterResult(name, args, result)) {
1257
+ ownedNames[index].add(requiredName);
1258
+ added.add(requiredName);
1259
+ }
1260
+ });
1261
+ return [...added];
1262
+ },
1263
+ validateTerminalAnswer: (answer) => contracts.map((contract) => contract.validateTerminalAnswer?.(answer)).find((rejection) => rejection !== undefined),
1264
+ };
1265
+ const currentToolContext = { ...runAgentOptions.toolContext, relationshipAuthorization };
1266
+ if (isSanctuaryOwner && rootHostRuntime && input.authority && await authorityTransport.hostApproval.refresh()) {
1267
+ currentToolContext.rootHost = { port: authorityTransport.hostApproval, observation: input.authority };
1268
+ try {
1269
+ await (0, tools_sanctuary_host_1.authorizeRootHostContext)(currentToolContext);
1270
+ }
1271
+ catch {
1272
+ delete currentToolContext.rootHost;
1273
+ }
1274
+ }
1275
+ return {
1276
+ ...runAgentOptions,
1277
+ ...(requiredToolCalls ? { requiredToolCalls } : {}),
1278
+ toolContext: currentToolContext,
1279
+ };
1280
+ };
1281
+ };
1282
+ const dispatchResolvedRelationshipTurn = async (input) => {
1283
+ const sessionPath = (0, shared_turn_1.getSenseSessionPath)(options.agentName, input.friendId, "telegram", input.sessionKey, agentRoot);
1284
+ const effects = [];
1285
+ let ordinal = 0;
1286
+ const deliver = async (text, terminalReturn = false) => {
1287
+ const obligation = terminalReturn ? (0, obligations_1.findPendingObligationForRequest)(agentRoot, {
1288
+ requestId: input.requestId,
1289
+ owedTo: { friendId: input.friendId, channel: "telegram", key: input.sessionKey },
1290
+ }) : undefined;
1291
+ effects.push(await executeAuthorizedEffect({
1292
+ idempotencyKey: `relationship-turn:${input.requestId}:delivery:${ordinal++}`,
1293
+ target: { kind: "approved_relationship", friendId: input.friendId, sessionKey: input.sessionKey, requestId: input.requestId },
1294
+ authorClass: "butler",
1295
+ effect: { kind: "text", text },
1296
+ ...(obligation?.returnReadyAt ? { obligationReturnId: obligation.id } : {}),
1297
+ }));
1298
+ };
1299
+ const orientationFrame = input.orientation?.kind === "newly_admitted"
1300
+ ? {
1301
+ ...(0, orientation_frame_1.buildOrientationFrame)({ channel: "telegram", messages: [{ role: "user", content: input.text }] }),
1302
+ source: {
1303
+ kind: "telegram_newly_admitted",
1304
+ authority: "presentation_only",
1305
+ routingHint: [
1306
+ "This is this person’s first admitted turn. Welcome them warmly and briefly explain what the household Butler can help with before answering their request.",
1307
+ input.orientation.relationship ? `The owner identified this person as their ${input.orientation.relationship}.` : null,
1308
+ input.orientation.attachmentsNeedResend ? "Their original message included attachments that were not downloaded; ask them to resend those attachments now." : null,
1309
+ ].filter(Boolean).join(" "),
1310
+ },
1311
+ }
1312
+ : undefined;
1313
+ const fullVisibility = createFullVisibilityProgress();
1314
+ const result = await runTurn({
1315
+ agentName: options.agentName,
1316
+ channel: "telegram",
1317
+ sessionKey: input.sessionKey,
1318
+ friendId: input.friendId,
1319
+ identity: { provider: "telegram-user", externalId: input.userId, displayName: "Household member", tenantId: botId },
1320
+ userMessage: input.text,
1321
+ ingressRelations: { replyToEventId: null, threadRootEventId: null, references: [input.reference] },
1322
+ precommittedIngress: { eventId: input.eventId, reference: input.reference },
1323
+ ...(orientationFrame ? { orientationFrame } : {}),
1324
+ toolContext: { ...(toolContext ?? {}), attachmentIds: input.attachmentIds ?? [] },
1325
+ prepareRunAgentOptions: prepareRelationshipRunAgentOptions({ friendId: input.friendId, requestId: input.requestId,
1326
+ sessionEventId: input.eventId, userId: input.userId, chatId: input.chatId, sessionKey: input.sessionKey, userMessage: input.text, fullVisibilityProgress: fullVisibility.progress }),
1327
+ emptyResponseFallback: fullVisibility.emptyResponseFallback,
1328
+ deliverySink: { onDelivery: (delivery) => deliver(delivery.text, delivery.kind === "settle") },
1329
+ });
1330
+ let responseFallbackArtifactId;
1331
+ const responseFallback = responseFallbackAfterDeliveries(result, effects.length);
1332
+ if (responseFallback) {
1333
+ await deliver(responseFallback, true);
1334
+ responseFallbackArtifactId = effects.at(-1)?.id;
1335
+ }
1336
+ const causalEventIds = Object.fromEntries(effects.flatMap((artifact, index) => {
1337
+ const eventId = result.causalSessionEventIds?.[index]
1338
+ ?? (artifact.id === responseFallbackArtifactId ? result.responseCausalSessionEventId : undefined);
1339
+ return eventId ? [[artifact.id, eventId]] : [];
1340
+ }));
1341
+ await recordAcceptedEffects(sessionPath, effects, undefined, causalEventIds);
1342
+ };
1343
+ const admissionController = options.admission && admissionStore ? (0, telegram_admission_1.createTelegramAdmissionController)({
1344
+ store: admissionStore,
1345
+ owner: { friendId: configuredOwnerFriendId, sessionKey: configuredOwnerSessionKey },
1346
+ sendEffect: async (request) => {
1347
+ const artifact = await executeAuthorizedEffect(request);
1348
+ if (artifact.target.kind === "approved_relationship") {
1349
+ await recordAcceptedEffects((0, shared_turn_1.getSenseSessionPath)(options.agentName, artifact.target.friendId, "telegram", artifact.target.sessionKey, agentRoot), [artifact]);
1350
+ }
1351
+ return artifact.id;
1352
+ },
1353
+ resolveOwnerCard: (messageId) => {
1354
+ const resolved = (0, telegram_effect_adapter_1.resolveTelegramControlArtifact)(getEffectJournal(), { messageId, friendId: configuredOwnerFriendId, sessionKey: configuredOwnerSessionKey });
1355
+ return resolved?.requestId
1356
+ ? { artifactId: resolved.artifactId, admissionId: resolved.requestId }
1357
+ : null;
1358
+ },
1359
+ resolveOwnerCardMessageId: (artifactId) => {
1360
+ const artifact = getEffectJournal().read(artifactId);
1361
+ if (artifact.authorClass !== "control" || artifact.target.kind !== "approved_relationship"
1362
+ || artifact.target.friendId !== configuredOwnerFriendId || artifact.target.sessionKey !== configuredOwnerSessionKey)
1363
+ return null;
1364
+ return artifact.parts.find((part) => part.state === "session_recorded" && part.messageId)?.messageId ?? null;
1365
+ },
1366
+ claimFriend: options.admission.claimFriend,
1367
+ revokeFriend: options.admission.revokeFriend,
1368
+ ...(authorityTransport ? {
1369
+ registerCommunication: (input) => authorityTransport.admitChat({
1370
+ admissionId: input.id,
1371
+ updateId: input.updateId,
1372
+ userId: input.userId,
1373
+ chatId: input.chatId,
1374
+ }),
1375
+ revokeCommunication: (input) => authorityTransport.revokeChat(input),
1376
+ } : {}),
1377
+ commitApprovedIngress: async (input) => {
1378
+ const sessionKey = `telegram:${input.botId}:${input.userId}`;
1379
+ const reference = `telegram-admission:${input.admissionId}`;
1380
+ const sessionPath = (0, shared_turn_1.getSenseSessionPath)(options.agentName, input.friendId, "telegram", sessionKey, agentRoot);
1381
+ const receipt = await (0, telegram_effect_adapter_1.recordTelegramEffectsInSession)({ store: getEffectJournal(), sessionPath, artifacts: [], inbound: { text: input.text, reference } });
1382
+ return { admissionId: input.admissionId, friendId: input.friendId, sessionKey, eventId: receipt.eventId, reference };
1383
+ },
1384
+ enqueueApprovedWork: async (input) => {
1385
+ if (!inboxStore.captureAdmittedWork)
1386
+ throw new Error("Telegram admitted work inbox is unavailable");
1387
+ inboxStore.captureAdmittedWork(input);
1388
+ },
1389
+ dispatchApprovedWork: async (input) => {
1390
+ if (!inboxStore.admittedWorkState || !inboxStore.claimAdmittedWork || !inboxStore.completeAdmittedWork)
1391
+ return "indeterminate";
1392
+ const state = inboxStore.admittedWorkState(input.admissionId);
1393
+ if (state === "settled")
1394
+ return "settled";
1395
+ if (state !== "pending" || !inboxStore.claimAdmittedWork(input.admissionId))
1396
+ return "indeterminate";
1397
+ try {
1398
+ const sessionPath = (0, shared_turn_1.getSenseSessionPath)(options.agentName, input.friendId, "telegram", input.sessionKey, agentRoot);
1399
+ const envelope = (0, session_events_1.loadSessionEnvelopeFile)(sessionPath);
1400
+ const event = envelope?.events.find((candidate) => candidate.id === input.eventId
1401
+ && candidate.role === "user" && candidate.relations.references.includes(input.reference));
1402
+ if (!event || typeof event.content !== "string")
1403
+ throw new Error("Telegram admitted work ingress is unavailable");
1404
+ const record = admissionStore.read(input.admissionId);
1405
+ await dispatchResolvedRelationshipTurn({ friendId: input.friendId, sessionKey: input.sessionKey, requestId: input.admissionId,
1406
+ eventId: input.eventId, reference: input.reference, text: event.content, userId: record.userId, chatId: record.chatId, orientation: input.orientation });
1407
+ inboxStore.completeAdmittedWork(input.admissionId);
1408
+ return "settled";
1409
+ }
1410
+ catch {
1411
+ return "indeterminate";
1412
+ }
1413
+ },
1414
+ withDecisionLease: (admissionId, work) => (0, session_transaction_1.withSessionTurnLease)(path.join(agentRoot, "state", "senses", "telegram", "admission-decisions", admissionId), async () => work()),
1415
+ ...(options.admission.createDisplayCode ? { createDisplayCode: options.admission.createDisplayCode } : {}),
1416
+ }) : undefined;
1417
+ const recoverEffectOutbox = async () => {
1418
+ if (!effectJournal && !(0, node_fs_1.existsSync)(path.join(agentRoot, "state", "telegram", "effects")))
1419
+ return;
1420
+ await (0, telegram_effect_adapter_1.recoverTelegramEffectOutbox)({
1421
+ store: getEffectJournal(),
1422
+ execute: executeAuthorizedEffect,
1423
+ matches: (artifact) => artifact.target.kind === "approved_relationship",
1424
+ });
1425
+ const accepted = getEffectJournal().list().filter((artifact) => artifact.target.kind === "approved_relationship" && artifact.parts.some((part) => part.state === "accepted"));
1426
+ for (const artifact of accepted) {
1427
+ const target = artifact.target;
1428
+ await recordAcceptedEffects((0, shared_turn_1.getSenseSessionPath)(options.agentName, target.friendId, "telegram", target.sessionKey, agentRoot), [artifact]);
1429
+ }
1430
+ };
1431
+ const hydrateAuthorizedMessage = async (message) => {
1432
+ if (!message.attachments?.length)
1433
+ return { text: [message.text, ...(message.attachmentNotices ?? [])].filter(Boolean).join("\n"), attachmentIds: [] };
1434
+ const ingested = await (0, telegram_attachments_1.ingestTelegramAttachments)({
1435
+ agentName: options.agentName,
1436
+ agentRoot,
1437
+ ...(botToken ? { botToken } : {}),
1438
+ api,
1439
+ ...(authorityTransport ? { downloadFile: authorityTransport.downloadFile } : {}),
1440
+ ...(options.attachmentFetch ? { fetch: options.attachmentFetch } : {}),
1441
+ attachments: message.attachments ?? [],
1442
+ });
1443
+ const attachmentIds = ingested.attachments.map((attachment) => attachment.id);
1444
+ return {
1445
+ text: [message.text, (0, render_1.renderAttachmentBlock)(ingested.attachments), ...(message.attachmentNotices ?? []), ...ingested.notices].filter(Boolean).join("\n"),
1446
+ attachmentIds,
1447
+ };
1448
+ };
1449
+ const onMessageBody = async (rawMessage) => {
1450
+ const hydrated = await hydrateAuthorizedMessage(rawMessage);
1451
+ const message = { ...rawMessage, text: hydrated.text };
1452
+ const currentFriendId = configuredOwnerFriendId;
1453
+ const currentSessionKey = configuredOwnerSessionKey;
1454
+ const currentSessionPath = (0, shared_turn_1.getSenseSessionPath)(options.agentName, currentFriendId, "telegram", currentSessionKey, agentRoot);
1455
+ const inboundReference = `telegram-inbound:${(0, node_crypto_1.createHmac)("sha256", identityKey).update(`${message.updateId}\0${message.messageId}`).digest("hex")}`;
1456
+ const acceptanceMarker = options.acceptanceMarker ? options.acceptanceMarker() : (0, sanctuary_acceptance_marker_1.readSanctuaryAcceptanceMarker)(options.agentName);
1457
+ const acceptanceMeta = (0, sanctuary_acceptance_marker_1.sanctuaryAcceptanceEventMeta)(options.agentName);
1458
+ const groundedAcceptance = acceptanceMarker?.label === "unit-16d-whats-up" || acceptanceMarker?.label === "unit-16d-1-space";
1459
+ const acceptanceSchema = groundedAcceptance ? "sanctuary-telegram-turn-receipt-v4" : "sanctuary-telegram-turn-receipt-v3";
1460
+ const auditDigest = (purpose, value) => sanctuaryTelegramTurnReceiptDigest(identityKey, acceptanceSchema, purpose, value);
1461
+ const lifecycleCoordinates = acceptanceMarker ? {
1462
+ scenarioHandleDigest: acceptanceMarker.scenarioHandleDigest,
1463
+ turnDigest: auditDigest("turn", `${message.updateId}\0${message.messageId}`),
1464
+ updateDigest: auditDigest("update", `${message.updateId}\0${message.messageId}`),
1465
+ subject,
1466
+ identityDigest: auditDigest("identity", subject),
1467
+ sessionDigest: auditDigest("session", `telegram:${subject}`),
1468
+ argumentDigest: auditDigest("argument", message.text),
1469
+ } : {};
1470
+ const lifecycleStartedAt = Date.now();
1471
+ const lifecycleMeta = (event, meta, lifecycleAt) => acceptanceMarker
1472
+ ? { ...meta, lifecycleAt, lifecycleMac: sanctuaryTelegramAuditLifecycleMac(identityKey, acceptanceSchema, event, { ...meta, lifecycleAt }) }
1473
+ : meta;
1474
+ (0, runtime_1.emitNervesEvent)({
1475
+ component: "senses",
1476
+ event: "senses.telegram_turn_start",
1477
+ message: "Telegram authorized turn started",
1478
+ meta: lifecycleMeta("senses.telegram_turn_start", { agentName: options.agentName, subject, ...acceptanceMeta, ...lifecycleCoordinates }, lifecycleStartedAt),
1479
+ });
1480
+ acceptanceAuditBarrier();
1481
+ const deliveredMessageIds = [];
1482
+ const deliveredChunks = [];
1483
+ let receiptStatus = "success";
1484
+ let errorCategory = null;
1485
+ const turnMetricsObserver = { providerInvocationCount: 0, toolInvocationCount: 0 };
1486
+ const toolReceiptObserver = { toolResultDigests: [], toolGroundings: [] };
1487
+ const normalizedRequest = message.text.normalize("NFKC").trim().toLocaleLowerCase("en-US");
1488
+ const exactDownloadCreditQuestion = normalizedRequest === "why aren't my shows downloading?";
1489
+ const groundingIntentTool = groundedAcceptance
1490
+ ? /^(?:what['’]?s up|status)\??$/u.test(normalizedRequest) ? "unraid_get_system"
1491
+ : /^how much (?:space|storage) is left\??$/u.test(normalizedRequest) ? "unraid_get_storage"
1492
+ : null
1493
+ : null;
1494
+ const bufferedGroundedDeliveries = [];
1495
+ const turnEffects = [];
1496
+ let deliveryOrdinal = 0;
1497
+ await recoverEffectOutbox();
1498
+ let ingressRelations = { replyToEventId: null, threadRootEventId: null, references: [inboundReference] };
1499
+ if (message.replyToMessageId && /^[1-9][0-9]*$/u.test(message.replyToMessageId)) {
1500
+ const replyMessageId = Number(message.replyToMessageId);
1501
+ const reply = (0, telegram_effect_adapter_1.resolveTelegramReply)(getEffectJournal(), { messageId: replyMessageId, friendId: currentFriendId, sessionKey: currentSessionKey });
1502
+ const session = reply ? (0, session_events_1.loadSessionEnvelopeFile)(currentSessionPath) : null;
1503
+ if (reply && session?.events.some((event) => event.id === reply.sessionEventId))
1504
+ ingressRelations = {
1505
+ replyToEventId: reply.sessionEventId,
1506
+ threadRootEventId: null,
1507
+ references: [inboundReference, `telegram-artifact:${reply.artifactId}`, ...(reply.requestId ? [`request:${reply.requestId}`] : [])],
1508
+ };
1509
+ }
1510
+ try {
1511
+ acceptanceAuditBarrier();
1512
+ const ingressReceipt = options.resolveRelationshipAuthorization
1513
+ ? await (0, telegram_effect_adapter_1.recordTelegramEffectsInSession)({
1514
+ store: getEffectJournal(),
1515
+ sessionPath: currentSessionPath,
1516
+ artifacts: [],
1517
+ inbound: { text: message.text, reference: inboundReference, attachmentIds: hydrated.attachmentIds, relations: ingressRelations },
1518
+ })
1519
+ : null;
1520
+ const fullVisibility = createFullVisibilityProgress();
1521
+ const collected = await collectToolReceipts(() => runTurn({
1522
+ agentName: options.agentName,
1523
+ channel: "telegram",
1524
+ sessionKey: currentSessionKey,
1525
+ friendId: currentFriendId,
1526
+ identity: {
1527
+ provider: "telegram-user",
1528
+ externalId: options.admission ? message.userId : subject,
1529
+ displayName: options.admission ? "Ari" : `Telegram user ${subject}`,
1530
+ ...(options.admission ? { tenantId: botId } : {}),
1531
+ },
1532
+ userMessage: message.text,
1533
+ ingressRelations,
1534
+ ...(ingressReceipt ? { precommittedIngress: ingressReceipt } : {}),
1535
+ turnMetricsObserver,
1536
+ emptyResponseFallback: fullVisibility.emptyResponseFallback,
1537
+ deliverySink: {
1538
+ onDelivery: async (delivery) => {
1539
+ if (groundingIntentTool) {
1540
+ bufferedGroundedDeliveries.push(delivery.kind);
1541
+ }
1542
+ else {
1543
+ turnEffects.push(await deliverButlerEffect(delivery.text, `turn:${subject}:${message.updateId}:delivery:${deliveryOrdinal++}`, undefined, (messageId, chunk) => { deliveredMessageIds.push(messageId); deliveredChunks.push(chunk); }, exactDownloadCreditQuestion));
1544
+ }
1545
+ },
1546
+ },
1547
+ ...(toolContext || options.resolveRelationshipAuthorization ? {
1548
+ toolContext: { ...(toolContext ?? {}), ...(options.resolveRelationshipAuthorization ? { attachmentIds: hydrated.attachmentIds } : {}) },
1549
+ } : {}),
1550
+ ...(options.resolveRelationshipAuthorization ? {
1551
+ prepareRunAgentOptions: prepareRelationshipRunAgentOptions({
1552
+ friendId: currentFriendId,
1553
+ requestId: inboundReference,
1554
+ sessionEventId: ingressReceipt.eventId,
1555
+ userId: message.userId,
1556
+ chatId: message.chatId,
1557
+ sessionKey: currentSessionKey,
1558
+ userMessage: message.text,
1559
+ authority: message.authority,
1560
+ fullVisibilityProgress: fullVisibility.progress,
1561
+ }),
1562
+ } : {}),
1563
+ ...(approvalRuntime ? { approvalCoordinatorFactory: rootHostRuntime ? approvalCoordinatorFactory : approvalRuntime.coordinator } : {}),
1564
+ }), toolReceiptObserver);
1565
+ const result = collected.result;
1566
+ let responseFallbackArtifactId;
1567
+ turnMetricsObserver.providerInvocationCount = Math.max(turnMetricsObserver.providerInvocationCount, result.providerInvocationCount ?? 0);
1568
+ turnMetricsObserver.toolInvocationCount = Math.max(turnMetricsObserver.toolInvocationCount, result.toolInvocationCount ?? 0);
1569
+ if (groundingIntentTool) {
1570
+ const grounding = toolReceiptObserver.toolGroundings?.length === 1 ? toolReceiptObserver.toolGroundings[0] : undefined;
1571
+ if (!grounding || grounding.toolName !== groundingIntentTool || bufferedGroundedDeliveries.length > 1
1572
+ || (bufferedGroundedDeliveries.length === 1 && bufferedGroundedDeliveries[0] !== "settle"))
1573
+ throw new Error("Canonical Sanctuary query did not produce exactly one matching grounded settle");
1574
+ const canonical = (0, sanctuary_grounding_1.renderSanctuaryGroundedResponse)(grounding.toolName, grounding.facts);
1575
+ turnEffects.push(await deliverButlerEffect(canonical, `turn:${subject}:${message.updateId}:delivery:${deliveryOrdinal++}`, undefined, (messageId, chunk) => { deliveredMessageIds.push(messageId); deliveredChunks.push(chunk); }));
1576
+ }
1577
+ else {
1578
+ const responseFallback = responseFallbackAfterDeliveries(result, turnEffects.length);
1579
+ if (responseFallback) {
1580
+ const artifact = await deliverButlerEffect(responseFallback, `turn:${subject}:${message.updateId}:delivery:${deliveryOrdinal++}`, undefined, (messageId, chunk) => { deliveredMessageIds.push(messageId); deliveredChunks.push(chunk); }, exactDownloadCreditQuestion);
1581
+ turnEffects.push(artifact);
1582
+ responseFallbackArtifactId = artifact.id;
1583
+ }
1584
+ }
1585
+ if (result.sessionPath) {
1586
+ const causalEventIds = Object.fromEntries((groundingIntentTool ? [] : turnEffects).flatMap((artifact, index) => {
1587
+ const eventId = result.causalSessionEventIds?.[index];
1588
+ if (eventId)
1589
+ return [[artifact.id, eventId]];
1590
+ return artifact.id === responseFallbackArtifactId && result.responseCausalSessionEventId
1591
+ ? [[artifact.id, result.responseCausalSessionEventId]]
1592
+ : [];
1593
+ }));
1594
+ await recordAcceptedEffects(result.sessionPath, turnEffects, undefined, causalEventIds);
1595
+ }
1596
+ (0, runtime_1.emitNervesEvent)({
1597
+ component: "senses",
1598
+ event: "senses.telegram_turn_end",
1599
+ message: "Telegram authorized turn completed",
1600
+ meta: lifecycleMeta("senses.telegram_turn_end", { agentName: options.agentName, subject, deliveryCount: turnEffects.length, ...acceptanceMeta, ...lifecycleCoordinates, ...(acceptanceMarker ? { outcome: "success", errorDigest: null } : {}) }, Math.max(Date.now(), lifecycleStartedAt + 1)),
1601
+ });
1602
+ }
1603
+ catch (error) {
1604
+ receiptStatus = "error";
1605
+ errorCategory = (error instanceof Error && error.name.trim() ? error.name : "unknown").slice(0, 128);
1606
+ (0, runtime_1.emitNervesEvent)({
1607
+ level: "error",
1608
+ component: "senses",
1609
+ event: "senses.telegram_turn_error",
1610
+ message: "Telegram authorized turn failed",
1611
+ meta: lifecycleMeta("senses.telegram_turn_error", {
1612
+ agentName: options.agentName,
1613
+ subject,
1614
+ ...acceptanceMeta,
1615
+ ...lifecycleCoordinates,
1616
+ ...(acceptanceMarker ? {
1617
+ outcome: "error",
1618
+ errorDigest: auditDigest("error", redactTelegramPrivateValues(error, [...transportPrivateValues, String(message.updateId), message.messageId])),
1619
+ deliveryCount: deliveredMessageIds.length,
1620
+ } : { error: redactTelegramPrivateValues(error, [...transportPrivateValues, String(message.updateId), message.messageId]) }),
1621
+ }, Math.max(Date.now(), lifecycleStartedAt + 1)),
1622
+ });
1623
+ const fallback = "I couldn't complete that turn. The failure was recorded; please try again.";
1624
+ if (deliveredMessageIds.length === 0)
1625
+ turnEffects.push(await deliverButlerEffect(fallback, `turn:${subject}:${message.updateId}:fallback`, undefined, (messageId, chunk) => { deliveredMessageIds.push(messageId); deliveredChunks.push(chunk); }, exactDownloadCreditQuestion));
1626
+ await recordAcceptedEffects(currentSessionPath, turnEffects, { text: message.text, reference: inboundReference, attachmentIds: hydrated.attachmentIds });
1627
+ }
1628
+ finally {
1629
+ if (acceptanceMarker) {
1630
+ acceptanceAuditBarrier();
1631
+ const grounded = groundedAcceptance;
1632
+ const schemaVersion = grounded ? "sanctuary-telegram-turn-receipt-v4" : "sanctuary-telegram-turn-receipt-v3";
1633
+ const hmac = (purpose, value) => sanctuaryTelegramTurnReceiptDigest(identityKey, schemaVersion, purpose, value);
1634
+ const redact = (value) => [...transportPrivateValues, String(message.updateId), message.messageId]
1635
+ .reduce((text, privateValue) => privateValue.length >= 5 ? text.replaceAll(privateValue, "[REDACTED]") : text, value);
1636
+ const deliveries = deliveredMessageIds.map((messageId, index) => {
1637
+ const chunk = deliveredChunks[index];
1638
+ const redactedText = redact(chunk);
1639
+ return { messageIdDigest: hmac("delivery", String(messageId)), chunkDigest: hmac("chunk", grounded ? redactedText : chunk), ...(grounded ? { redactedText, utf16Units: redactedText.length } : {}) };
1640
+ });
1641
+ try {
1642
+ const receipt = {
1643
+ schemaVersion,
1644
+ scenarioHandleDigest: acceptanceMarker.scenarioHandleDigest,
1645
+ status: receiptStatus,
1646
+ errorCategory,
1647
+ updateDigest: hmac("update", `${message.updateId}\0${message.messageId}`),
1648
+ sequenceDigest: hmac("sequence", String(message.updateId)),
1649
+ responseDigest: hmac("response", JSON.stringify(deliveries)),
1650
+ toolResultDigests: toolReceiptObserver.toolResultDigests,
1651
+ ...(grounded ? { toolGroundings: toolReceiptObserver.toolGroundings } : {}),
1652
+ providerInvocationCount: turnMetricsObserver.providerInvocationCount,
1653
+ toolInvocationCount: turnMetricsObserver.toolInvocationCount,
1654
+ deliveryCount: deliveries.length,
1655
+ deliveries,
1656
+ completedAt: new Date().toISOString(),
1657
+ };
1658
+ if (grounded)
1659
+ receipt.receiptMac = sanctuaryTelegramTurnReceiptMac(identityKey, receipt);
1660
+ await appendSanctuaryTurnReceipt(options.acceptanceReceiptRoot ?? agentRoot, receipt, options._afterAcceptanceLedgerPreReadStat);
1661
+ }
1662
+ catch (error) {
1663
+ (0, runtime_1.emitNervesEvent)({ level: "error", component: "senses", event: "senses.telegram_acceptance_receipt_error", message: "Telegram acceptance receipt persistence failed", meta: { agentName: options.agentName, scenarioHandleDigest: acceptanceMarker.scenarioHandleDigest, category: error instanceof Error ? error.name : "unknown" } });
1664
+ }
1665
+ }
1666
+ }
1667
+ };
1668
+ const onMessage = (message) => runWithAcceptanceAuditOwner(async () => {
1669
+ const ownerDecision = !message.attachments?.length && !message.attachmentNotices?.length ? admissionController?.parseOwnerDecision({
1670
+ text: message.text,
1671
+ ...(message.replyToMessageId ? { replyToMessageId: Number(message.replyToMessageId) } : {}),
1672
+ }) : null;
1673
+ if (ownerDecision) {
1674
+ const actor = await options.admission?.resolveOwner({ botId: botId, userId: message.userId, chatId: message.chatId, sessionKey: configuredOwnerSessionKey });
1675
+ if (!actor)
1676
+ throw new Error("Telegram admission owner decision identity is invalid");
1677
+ await admissionController.decide({ ...ownerDecision, actorFriendId: actor.friendId });
1678
+ return;
1679
+ }
1680
+ if ((message.attachments?.length || message.attachmentNotices?.length) && options.admission) {
1681
+ const actor = await options.admission.resolveOwner({ botId: botId, userId: message.userId, chatId: message.chatId, sessionKey: configuredOwnerSessionKey });
1682
+ if (!actor || actor.friendId !== configuredOwnerFriendId)
1683
+ throw new Error("Telegram attachment owner relationship is not active");
1684
+ }
1685
+ // Show the household that the Butler is working rather than silently thinking.
1686
+ // Presence is deliberately separate from delivery: a transport that cannot do
1687
+ // it degrades to silence instead of failing, and message-delivery assertions
1688
+ // stay about messages.
1689
+ await (sendTypingIndicator
1690
+ ? withTelegramTypingIndicator(() => sendTypingIndicator(message.chatId), () => onMessageBody(message))
1691
+ : onMessageBody(message));
1692
+ });
1693
+ const onUnknownMessage = admissionController && options.admission ? async (message) => {
1694
+ const approved = await options.admission.resolveApprovedFriend(message);
1695
+ if (!approved) {
1696
+ await admissionController.handleUnknown(message);
1697
+ return;
1698
+ }
1699
+ // Existing household Friends need a root-observed communication binding after gateway migration.
1700
+ await authorityTransport?.admitChat({
1701
+ admissionId: (0, node_crypto_1.createHash)("sha256").update(`telegram-communication-v1\0${message.botId}\0${message.userId}\0${message.chatId}`).digest("hex").slice(0, 20),
1702
+ updateId: message.updateId,
1703
+ userId: message.userId,
1704
+ chatId: message.chatId,
1705
+ });
1706
+ const hydrated = await hydrateAuthorizedMessage({
1707
+ updateId: message.updateId,
1708
+ messageId: String(message.messageId),
1709
+ userId: message.userId,
1710
+ chatId: message.chatId,
1711
+ text: message.text,
1712
+ attachments: message.attachments,
1713
+ attachmentNotices: message.attachmentNotices,
1714
+ });
1715
+ const sessionKey = `telegram:${message.botId}:${message.userId}`;
1716
+ const reference = `telegram-inbound:${(0, node_crypto_1.createHmac)("sha256", identityKey).update(`${message.botId}\0${message.userId}\0${message.updateId}\0${message.messageId}`).digest("hex")}`;
1717
+ const sessionPath = (0, shared_turn_1.getSenseSessionPath)(options.agentName, approved.friendId, "telegram", sessionKey, agentRoot);
1718
+ const receipt = await (0, telegram_effect_adapter_1.recordTelegramEffectsInSession)({ store: getEffectJournal(), sessionPath, artifacts: [], inbound: { text: hydrated.text, reference, attachmentIds: hydrated.attachmentIds } });
1719
+ await dispatchResolvedRelationshipTurn({ friendId: approved.friendId, sessionKey, requestId: reference, eventId: receipt.eventId,
1720
+ reference, text: hydrated.text, attachmentIds: hydrated.attachmentIds, userId: message.userId, chatId: message.chatId });
1721
+ } : undefined;
1722
+ const onUpdate = async (update) => {
1723
+ const callback = update.callback_query;
1724
+ if (callback && rootHostRuntime && await runWithAcceptanceAuditOwner(() => rootHostRuntime.handleUpdate(update)))
1725
+ return true;
1726
+ if (callback?.data?.startsWith("admit:") && admissionController && callback.message) {
1727
+ return runWithAcceptanceAuditOwner(async () => {
1728
+ const actor = await options.admission?.resolveOwner({ botId: botId, userId: String(callback.from.id), chatId: String(callback.message.chat.id), sessionKey: configuredOwnerSessionKey });
1729
+ if (!actor)
1730
+ throw new Error("Telegram admission owner callback identity is invalid");
1731
+ const decision = admissionController.parseCallback(callback.data, callback.message.message_id);
1732
+ await admissionController.decide({ ...decision, actorFriendId: actor.friendId });
1733
+ const ownerTarget = configuredOwnerTarget();
1734
+ const artifact = await executeAuthorizedEffect({
1735
+ idempotencyKey: `admission-callback:${callback.id}`,
1736
+ target: { ...ownerTarget, requestId: decision.admissionId },
1737
+ authorClass: "control",
1738
+ effect: { kind: "callback_ack", callbackQueryId: callback.id },
1739
+ });
1740
+ await recordAcceptedEffects((0, shared_turn_1.getSenseSessionPath)(options.agentName, ownerTarget.friendId, "telegram", ownerTarget.sessionKey, agentRoot), [artifact]);
1741
+ return true;
1742
+ });
1743
+ }
1744
+ if (!callback || !approvalTransport)
1745
+ return false;
1746
+ return runWithAcceptanceAuditOwner(async () => (await approvalTransport.handleUpdate(update)).handled);
1747
+ };
1748
+ let poll;
1749
+ try {
1750
+ poll = (options.createLongPoll ?? telegram_client_1.createTelegramLongPoll)({
1751
+ api,
1752
+ expectedUserId: authorizedUserId,
1753
+ expectedChatId: authorizedChatId,
1754
+ ...(onUnknownMessage ? { botId: botId, onUnknownMessage } : {}),
1755
+ offsetStore,
1756
+ inboxStore,
1757
+ onMessage,
1758
+ onUpdate,
1759
+ ...(authorityTransport ? {
1760
+ settleTransport: authorityTransport.settleTransport,
1761
+ transportMetadata: authorityTransport.metadataForUpdate,
1762
+ } : {}),
1763
+ acceptanceEventMeta: (update, distinctAccount) => {
1764
+ const marker = options.acceptanceMarker ? options.acceptanceMarker() : (0, sanctuary_acceptance_marker_1.readSanctuaryAcceptanceMarker)(options.agentName);
1765
+ if (!marker)
1766
+ return {};
1767
+ const auditOwnerMeta = acceptanceAuditLease ? { acceptanceAuditOwnerDigest: acceptanceAuditLease.ownerDigest } : {};
1768
+ const messageId = update?.message?.message_id ?? update?.callback_query?.message?.message_id;
1769
+ const senderId = update?.message?.from?.id;
1770
+ if (!update || messageId === undefined || senderId === undefined)
1771
+ return { scenarioHandleDigest: marker.scenarioHandleDigest, ...auditOwnerMeta };
1772
+ const schemaVersion = marker.label === "unit-16d-whats-up" || marker.label === "unit-16d-1-space"
1773
+ ? "sanctuary-telegram-turn-receipt-v4" : "sanctuary-telegram-turn-receipt-v3";
1774
+ const digest = (purpose, value) => sanctuaryTelegramTurnReceiptDigest(identityKey, schemaVersion, purpose, value);
1775
+ const senderDistinct = String(senderId) !== authorizedUserId;
1776
+ if (distinctAccount !== senderDistinct)
1777
+ throw new Error("Telegram dropped-update identity classification mismatch");
1778
+ const binding = {
1779
+ scenarioHandleDigest: marker.scenarioHandleDigest,
1780
+ updateDigest: digest("update", `${update.update_id}\0${messageId}`),
1781
+ senderIdentityDigest: digest("sender-identity", String(senderId)),
1782
+ authorizedIdentityDigest: digest("sender-identity", authorizedUserId),
1783
+ senderDistinct,
1784
+ nextOffsetDigest: digest("next-update-id", String(update.update_id + 1)),
1785
+ };
1786
+ return { ...binding, ...auditOwnerMeta, dropMac: sanctuaryTelegramUnauthorizedDropMac(identityKey, schemaVersion, binding) };
1787
+ },
1788
+ onBeforeDispatch: acceptanceAuditBarrier,
1789
+ onDispatchSettled: acceptanceAuditBarrier,
1790
+ });
1791
+ }
1792
+ catch (error) {
1793
+ releaseAcceptanceAudit(error);
1794
+ }
1795
+ const pendingCleanup = new Set([
1796
+ () => poll.stop(),
1797
+ async () => { await runPromise?.catch(() => undefined); },
1798
+ async () => { await Promise.all([...approvalReconciliationsInFlight]); },
1799
+ async () => { await interactiveControl?.stop(); },
1800
+ () => api.stop(),
1801
+ () => approvalRuntime?.close(),
1802
+ () => effectJournal?.close(),
1803
+ () => admissionStore?.close(),
1804
+ () => (0, tools_awaiting_1.resetAwaitToolDeps)(),
1805
+ () => runWithAcceptanceAuditOwner(() => {
1806
+ (0, runtime_1.emitNervesEvent)({
1807
+ component: "senses",
1808
+ event: "senses.telegram_poll_end",
1809
+ message: "Telegram long poll stopped",
1810
+ meta: { agentName: options.agentName, subject },
1811
+ });
1812
+ }),
1813
+ retireAcceptanceAudit,
1814
+ ]);
1815
+ return {
1816
+ run(signal) {
1817
+ if (runPromise)
1818
+ return runPromise;
1819
+ runPromise = (async () => {
1820
+ await runWithAcceptanceAuditOwner(migrateIdentity);
1821
+ inboxStore.quarantineStrandedAdmittedWork?.();
1822
+ await runWithAcceptanceAuditOwner(async () => { await admissionController?.recover(); });
1823
+ await runWithAcceptanceAuditOwner(recoverEffectOutbox);
1824
+ await runWithAcceptanceAuditOwner(reconcileSystemFailsafes);
1825
+ try {
1826
+ await runWithAcceptanceAuditOwner(async () => { await toolContext?.sanctuary?.recoverRoutineActions?.(); });
1827
+ }
1828
+ catch (error) {
1829
+ (0, runtime_1.emitNervesEvent)({ level: "error", component: "senses", event: "senses.sanctuary_routine_recovery_error", message: "Sanctuary routine action recovery requires inspection", meta: { agentName: options.agentName, subject, category: error instanceof Error ? error.name : "unknown" } });
1830
+ }
1831
+ await runWithAcceptanceAuditOwner(async () => { await approvalRuntime?.recover(); });
1832
+ await runWithAcceptanceAuditOwner(async () => { await rootHostRuntime?.recover(); });
1833
+ await runWithAcceptanceAuditOwner(async () => { await interactiveControl?.start(); });
1834
+ try {
1835
+ await runWithAcceptanceAuditOwner(async () => { await approvalTransport?.reconcileExpired(); });
1836
+ }
1837
+ catch (error) {
1838
+ acceptanceAuditBarrier();
1839
+ (0, runtime_1.emitNervesEvent)({
1840
+ level: "error",
1841
+ component: "senses",
1842
+ event: "senses.telegram_approval_reconcile_error",
1843
+ message: "Telegram approval expiry reconciliation failed",
1844
+ meta: { agentName: options.agentName, subject, error: transportError(error) },
1845
+ });
1846
+ }
1847
+ (0, runtime_1.emitNervesEvent)({
1848
+ component: "senses",
1849
+ event: "senses.telegram_poll_start",
1850
+ message: "Telegram long poll started",
1851
+ meta: { agentName: options.agentName, subject },
1852
+ });
1853
+ approvalReconciliationActive = true;
1854
+ nextApprovalReconcileDeadline = undefined;
1855
+ scheduleApprovalReconcile();
1856
+ try {
1857
+ await poll.run(signal);
1858
+ }
1859
+ finally {
1860
+ approvalReconciliationActive = false;
1861
+ nextApprovalReconcileDeadline = undefined;
1862
+ clearApprovalReconcileTimer();
1863
+ await Promise.all([...approvalReconciliationsInFlight]);
1864
+ try {
1865
+ await runWithAcceptanceAuditOwner(async () => { await approvalTransport?.reconcileExpired(); });
1866
+ }
1867
+ catch (error) {
1868
+ acceptanceAuditBarrier();
1869
+ (0, runtime_1.emitNervesEvent)({
1870
+ level: "error",
1871
+ component: "senses",
1872
+ event: "senses.telegram_approval_reconcile_error",
1873
+ message: "Telegram approval expiry reconciliation failed",
1874
+ meta: { agentName: options.agentName, subject, error: transportError(error) },
1875
+ });
1876
+ }
1877
+ await interactiveControl?.stop();
1878
+ }
1879
+ })();
1880
+ return runPromise;
1881
+ },
1882
+ async sendProactive(text, signal) {
1883
+ await runWithAcceptanceAuditOwner(async () => {
1884
+ const proactiveText = requiredText(text, "proactive message");
1885
+ // Unsolicited repetition is the one thing a household agent cannot take
1886
+ // back. External-event decisions are deliberately not guarded here:
1887
+ // dropping one could leave its event undispositioned.
1888
+ if (!proactiveRepetitionGuard.shouldSend(proactiveText)) {
1889
+ (0, runtime_1.emitNervesEvent)({
1890
+ component: "senses",
1891
+ event: "senses.telegram_proactive_repetition_suppressed",
1892
+ message: "suppressed a proactive message repeating a recent one",
1893
+ meta: { agentName: options.agentName, subject },
1894
+ });
1895
+ return;
1896
+ }
1897
+ const effect = await deliverButlerEffect(proactiveText, `proactive:${(0, node_crypto_1.randomUUID)()}`, signal);
1898
+ const sessionPath = (0, shared_turn_1.getSenseSessionPath)(options.agentName, configuredOwnerFriendId, "telegram", configuredOwnerSessionKey, agentRoot);
1899
+ await recordAcceptedEffects(sessionPath, [effect]);
1900
+ });
1901
+ },
1902
+ async sendExternalEventDecision(input) {
1903
+ const source = requiredText(input.source, "external event source");
1904
+ const eventId = requiredText(input.eventId, "external event id");
1905
+ if (Buffer.byteLength(source) > 512 || Buffer.byteLength(eventId) > 512 || !Number.isSafeInteger(input.generation) || input.generation < 1)
1906
+ throw new Error("Telegram external event identity is invalid");
1907
+ await runWithAcceptanceAuditOwner(async () => {
1908
+ const receiptIdentity = JSON.stringify([source, eventId, input.generation]);
1909
+ const effect = await deliverButlerEffect(requiredText(input.text, "external event decision"), `external-event:${receiptIdentity}`, input.signal);
1910
+ const sessionPath = (0, shared_turn_1.getSenseSessionPath)(options.agentName, configuredOwnerFriendId, "telegram", configuredOwnerSessionKey, agentRoot);
1911
+ await recordAcceptedEffects(sessionPath, [effect]);
1912
+ });
1913
+ },
1914
+ async sendAwaitFollowUp(request) {
1915
+ return runWithAcceptanceAuditOwner(() => deliverAwaitFollowUp(request));
1916
+ },
1917
+ stop() {
1918
+ if (stopPromise)
1919
+ return stopPromise;
1920
+ stopPromise = (async () => {
1921
+ const errors = [];
1922
+ for (const operation of pendingCleanup) {
1923
+ try {
1924
+ await operation();
1925
+ pendingCleanup.delete(operation);
1926
+ }
1927
+ catch (error) {
1928
+ errors.push(error);
1929
+ }
1930
+ }
1931
+ if (errors.length === 1)
1932
+ throw errors[0];
1933
+ if (errors.length > 1)
1934
+ throw new AggregateError(errors, "Telegram sense cleanup failed");
1935
+ })().catch((error) => { stopPromise = undefined; throw error; });
1936
+ return stopPromise;
1937
+ },
1938
+ };
1939
+ }
1940
+ function loadTelegramSenseCredentials(agentName) {
1941
+ const runtime = (0, runtime_credentials_1.readRuntimeCredentialConfig)(agentName);
1942
+ if (!runtime.ok) {
1943
+ throw new Error(`Telegram runtime config is ${runtime.reason}; actor: agent-runnable; run ouro connect telegram --agent ${agentName}`);
1944
+ }
1945
+ return parseTelegramSenseCredentials(runtime.config);
1946
+ }
1947
+ async function createProductionTelegramRelationshipComposition(agentName, credentials, agentRootOverride, authorityTransport) {
1948
+ const agentRoot = agentRootOverride ?? (0, identity_1.getAgentRoot)(agentName);
1949
+ const botId = canonicalTelegramId(credentials.botId ?? telegramBotIdFromToken(credentials.botToken), "bot id");
1950
+ const ownerUserId = canonicalTelegramId(credentials.authorizedUserId, "authorized user id");
1951
+ const ownerChatId = canonicalTelegramId(credentials.authorizedChatId, "authorized chat id");
1952
+ if (ownerUserId !== ownerChatId)
1953
+ throw new Error("Telegram owner relationship requires a private user-bound chat");
1954
+ const ownerSessionKey = `telegram:${opaqueTelegramSubject(readOrCreateTelegramIdentityKey(agentRoot), botId, ownerUserId, ownerChatId)}`;
1955
+ const store = new friends_1.FileFriendStore(path.join(agentRoot, "friends"));
1956
+ const sameBotTelegramBindings = (friend) => friend.externalIds.filter((external) => external.provider === "telegram-user" && external.tenantId === botId);
1957
+ const exactTelegramIdentity = (friend, userId) => {
1958
+ const bindings = sameBotTelegramBindings(friend);
1959
+ return bindings.length === 1 && /^[1-9][0-9]*$/u.test(bindings[0].externalId) && bindings[0].externalId === userId;
1960
+ };
1961
+ const canonicalOwner = (friend) => Boolean(friend
1962
+ && exactTelegramIdentity(friend, ownerUserId) && friend.admissionState === "active" && friend.trustLevel === "family"
1963
+ && friend.initiativePolicy === "proactive" && friend.capabilityProfileId === "sanctuary-owner");
1964
+ const canonicalHousehold = (friend, userId) => Boolean(friend
1965
+ && exactTelegramIdentity(friend, userId) && friend.admissionState === "active" && friend.trustLevel === "friend"
1966
+ && friend.initiativePolicy === "request_follow_up_only" && friend.capabilityProfileId === "sanctuary-household");
1967
+ const owner = await store.findByExternalId("telegram-user", ownerUserId, botId);
1968
+ if (!owner || !canonicalOwner(owner)) {
1969
+ throw new Error("Telegram owner Friend must be active with the sanctuary-owner profile and exact bot identity");
1970
+ }
1971
+ const registry = (0, relationship_authorization_1.loadRelationshipCapabilityRegistry)(agentRoot);
1972
+ if (!registry.profiles["sanctuary-owner"] || !registry.profiles["sanctuary-household"]) {
1973
+ throw new Error("Telegram relationship capability registry is missing canonical profiles");
1974
+ }
1975
+ const assertLiveOwner = async (actorFriendId) => {
1976
+ const current = await store.get(owner.id);
1977
+ if (actorFriendId !== owner.id || !canonicalOwner(current))
1978
+ throw new Error("Only the live Telegram owner may manage contacts");
1979
+ };
1980
+ const admissionsRoot = path.join(agentRoot, "state", "senses", "telegram", "admissions");
1981
+ const invalidatePendingApprovals = (sessionPath) => {
1982
+ const stateRoot = path.join(agentRoot, "state", "approvals");
1983
+ const checkpoints = new approval_files_1.FileApprovalCheckpointStore(path.join(stateRoot, "checkpoints.json"));
1984
+ const tokens = new approval_files_1.FileApprovalTokenStore(path.join(stateRoot, "tokens.json"));
1985
+ const approvals = (0, approval_store_1.openApprovalStore)({ databasePath: path.join(stateRoot, "approvals.sqlite") });
1986
+ try {
1987
+ for (const checkpoint of checkpoints.list()) {
1988
+ const approval = approvals.read(checkpoint.approvalId);
1989
+ if (!approval || path.resolve(approval.sessionPath) !== path.resolve(sessionPath))
1990
+ continue;
1991
+ const decisionToken = tokens.get(checkpoint.approvalId);
1992
+ if (approval.state === "preparing")
1993
+ approvals.recoverPreparing({ approvalId: approval.approvalId, state: "abandoned_before_attempt", reason: "requesting relationship was revoked" });
1994
+ else if (approval.state === "awaiting_prompt_binding")
1995
+ approvals.abandonPromptBinding({ approvalId: approval.approvalId, reason: "requesting relationship was revoked" });
1996
+ else if (approval.state === "proposed" && decisionToken)
1997
+ approvals.decide({
1998
+ approvalId: approval.approvalId, decisionToken, decision: "deny", requesterId: approval.requesterId,
1999
+ transport: approval.transport, transportUserId: approval.transportUserId, transportChatId: approval.transportChatId,
2000
+ transportMessageId: approval.transportMessageId, sessionKey: approval.sessionKey, ownerId: `relationship-revocation-${(0, node_crypto_1.randomUUID)()}`,
2001
+ });
2002
+ else if (approval.state === "proposed")
2003
+ throw new Error(`Cannot revoke relationship while approval ${approval.approvalId} is missing its decision token`);
2004
+ else if (approval.state === "claimed" && approval.ownerId)
2005
+ approvals.abandonBeforeAttempt({ approvalId: approval.approvalId, ownerId: approval.ownerId, epoch: approval.epoch, reason: "requesting relationship was revoked" });
2006
+ else if (approval.state === "claimed")
2007
+ throw new Error(`Cannot revoke relationship while approval ${approval.approvalId} is missing its claim owner`);
2008
+ const terminal = approvals.read(checkpoint.approvalId);
2009
+ if (terminal && terminal.state !== "attempted")
2010
+ checkpoints.remove(checkpoint.approvalId);
2011
+ tokens.remove(checkpoint.approvalId);
2012
+ }
2013
+ }
2014
+ finally {
2015
+ approvals.close();
2016
+ }
2017
+ };
2018
+ const telegramContactManager = {
2019
+ list: async ({ actorFriendId }) => {
2020
+ await assertLiveOwner(actorFriendId);
2021
+ const contacts = (await store.listAll()).flatMap((friend) => {
2022
+ if (friend.id === owner.id)
2023
+ return [];
2024
+ const bindings = sameBotTelegramBindings(friend);
2025
+ if (bindings.length !== 1 || !/^[1-9][0-9]*$/u.test(bindings[0].externalId))
2026
+ return [];
2027
+ return [{ friendId: friend.id, name: friend.name, userId: bindings[0].externalId, admissionState: friend.admissionState ?? "unverified", initiativePolicy: friend.initiativePolicy ?? "none" }];
2028
+ }).slice(0, 256);
2029
+ const admissions = new telegram_admission_1.FileTelegramAdmissionStore(admissionsRoot);
2030
+ try {
2031
+ const blocked = admissions.list().filter((record) => record.status === "blocked" && record.botId === botId)
2032
+ .slice(0, 256).map((record) => ({ admissionId: record.id, userId: record.userId, displayCode: record.displayCode, status: "blocked", createdAt: new Date(record.createdAt).toISOString() }));
2033
+ return { contacts, blocked };
2034
+ }
2035
+ finally {
2036
+ admissions.close();
2037
+ }
2038
+ },
2039
+ revoke: async ({ actorFriendId, friendId }) => {
2040
+ await assertLiveOwner(actorFriendId);
2041
+ if (friendId === owner.id)
2042
+ throw new Error("The Telegram owner relationship cannot revoke itself");
2043
+ const current = await store.get(friendId);
2044
+ if (!current)
2045
+ throw new Error("Telegram contact was not found");
2046
+ const bindings = sameBotTelegramBindings(current);
2047
+ if (bindings.length !== 1 || !/^[1-9][0-9]*$/u.test(bindings[0].externalId) || bindings[0].externalId === ownerUserId)
2048
+ throw new Error("Telegram contact identity is not exact");
2049
+ const sessionKey = `telegram:${botId}:${bindings[0].externalId}`;
2050
+ const sessionPath = (0, shared_turn_1.getSenseSessionPath)(agentName, current.id, "telegram", sessionKey, agentRoot);
2051
+ await (0, session_transaction_1.withSessionTurnLease)(sessionPath, async () => {
2052
+ invalidatePendingApprovals(sessionPath);
2053
+ const live = await store.get(current.id);
2054
+ if (!live || !exactTelegramIdentity(live, bindings[0].externalId))
2055
+ throw new Error("Telegram contact identity changed");
2056
+ await authorityTransport?.revokeChat({ userId: bindings[0].externalId, chatId: bindings[0].externalId });
2057
+ const { capabilityProfileId: _removedProfile, ...withoutProfile } = live;
2058
+ await store.put(live.id, { ...withoutProfile, admissionState: "revoked", initiativePolicy: "none", updatedAt: new Date().toISOString() });
2059
+ (0, tools_awaiting_1.cancelRelationshipFollowUps)(agentRoot, agentName, { friendId: live.id, channel: "telegram", key: sessionKey });
2060
+ });
2061
+ return { revoked: true, friendId };
2062
+ },
2063
+ unblock: async ({ actorFriendId, admissionId }) => {
2064
+ await assertLiveOwner(actorFriendId);
2065
+ const admissions = new telegram_admission_1.FileTelegramAdmissionStore(admissionsRoot);
2066
+ try {
2067
+ const record = admissions.read(admissionId);
2068
+ if (record.botId !== botId || record.userId !== record.chatId)
2069
+ throw new Error("Blocked Telegram contact identity changed");
2070
+ admissions.releaseBlock(admissionId);
2071
+ }
2072
+ finally {
2073
+ admissions.close();
2074
+ }
2075
+ return { unblocked: true, admissionId };
2076
+ },
2077
+ };
2078
+ const resolveEvaluator = async (input) => {
2079
+ const friend = await store.get(input.friendId);
2080
+ const ownerBound = friend?.id === owner.id;
2081
+ const coordinatesValid = ownerBound
2082
+ ? input.botId === botId && input.userId === ownerUserId && input.chatId === ownerChatId && input.sessionKey === ownerSessionKey
2083
+ : input.botId === botId && input.userId === input.chatId && input.sessionKey === `telegram:${botId}:${input.userId}`;
2084
+ if (!friend || !coordinatesValid || (ownerBound ? !canonicalOwner(friend) : !canonicalHousehold(friend, input.userId))) {
2085
+ throw new Error("Telegram relationship identity binding is not active");
2086
+ }
2087
+ return (0, relationship_authorization_1.createRelationshipAuthorizationEvaluator)({
2088
+ friend,
2089
+ registry: (0, relationship_authorization_1.loadRelationshipCapabilityRegistry)(agentRoot),
2090
+ requestId: input.requestId,
2091
+ requestPhase: "inbound",
2092
+ sessionEventId: input.sessionEventId,
2093
+ });
2094
+ };
2095
+ const admission = {
2096
+ ownerFriendId: owner.id,
2097
+ resolveOwner: async ({ botId: candidateBotId, userId, chatId, sessionKey }) => {
2098
+ if (candidateBotId !== botId || userId !== ownerUserId || chatId !== ownerChatId || sessionKey !== ownerSessionKey)
2099
+ return null;
2100
+ const exact = await store.findByExternalId("telegram-user", userId, botId);
2101
+ const current = await store.get(owner.id);
2102
+ return exact?.id === owner.id && canonicalOwner(current)
2103
+ ? { friendId: owner.id }
2104
+ : null;
2105
+ },
2106
+ resolveApprovedFriend: async ({ botId: candidateBotId, userId, chatId }) => {
2107
+ if (candidateBotId !== botId || userId !== chatId)
2108
+ return null;
2109
+ const friend = await store.findByExternalId("telegram-user", userId, botId);
2110
+ return friend && canonicalHousehold(friend, userId) ? { friendId: friend.id } : null;
2111
+ },
2112
+ claimFriend: async (input) => {
2113
+ if (input.botId !== botId || input.userId !== input.chatId)
2114
+ return { kind: "collision", reason: "Telegram identity is not bound to this bot and private chat" };
2115
+ const claimed = await store.claimExternalId({
2116
+ externalId: { provider: "telegram-user", externalId: input.userId, tenantId: botId, linkedAt: new Date().toISOString() },
2117
+ target: { kind: "create", record: { id: (0, node_crypto_1.randomUUID)(), name: "Household member" } },
2118
+ });
2119
+ const current = claimed.record;
2120
+ const now = new Date().toISOString();
2121
+ const connections = input.relationship
2122
+ ? [{ name: owner.name, relationship: input.relationship }, ...(current.connections ?? []).filter((connection) => !(connection.name === owner.name && connection.relationship === input.relationship))]
2123
+ : current.connections;
2124
+ const updated = { ...current,
2125
+ ...(connections ? { connections } : {}),
2126
+ trustLevel: input.defaults.trustLevel, admissionState: input.defaults.admissionState,
2127
+ initiativePolicy: input.defaults.initiativePolicy, capabilityProfileId: input.defaults.capabilityProfileId, updatedAt: now };
2128
+ await store.put(current.id, updated);
2129
+ return { kind: claimed.status === "created" ? "created" : "existing", friendId: current.id };
2130
+ },
2131
+ revokeFriend: async (input) => {
2132
+ if (input.botId !== botId || input.userId !== input.chatId)
2133
+ return { kind: "collision", reason: "Telegram revocation identity is invalid" };
2134
+ const current = await store.findByExternalId("telegram-user", input.userId, botId);
2135
+ if (!current || current.id !== input.friendId || !exactTelegramIdentity(current, input.userId))
2136
+ return { kind: "collision", reason: "Telegram revocation identity changed" };
2137
+ const { capabilityProfileId: _removedProfile, ...withoutProfile } = current;
2138
+ await store.put(current.id, { ...withoutProfile, admissionState: "revoked", initiativePolicy: "none", updatedAt: new Date().toISOString() });
2139
+ (0, tools_awaiting_1.cancelRelationshipFollowUps)(agentRoot, agentName, { friendId: current.id, channel: "telegram", key: `telegram:${botId}:${input.userId}` });
2140
+ return { kind: "revoked" };
2141
+ },
2142
+ };
2143
+ return {
2144
+ telegramContactManager,
2145
+ admission,
2146
+ resolveRelationshipAuthorization: resolveEvaluator,
2147
+ authorizeRelationshipEffect: async (input) => {
2148
+ if (input.target.kind !== "approved_relationship")
2149
+ return { allowed: false, reason: "relationship effect target is required" };
2150
+ const friend = await store.get(input.target.friendId);
2151
+ if (!friend)
2152
+ return { allowed: false, reason: "relationship admission is not active" };
2153
+ const bindings = sameBotTelegramBindings(friend);
2154
+ if (bindings.length !== 1 || !/^[1-9][0-9]*$/u.test(bindings[0].externalId))
2155
+ return { allowed: false, reason: "relationship Telegram identity binding is unavailable" };
2156
+ const chatId = bindings[0].externalId;
2157
+ if (friend.id === owner.id ? !canonicalOwner(friend) : !canonicalHousehold(friend, chatId))
2158
+ return { allowed: false, reason: "relationship admission is not active or canonical" };
2159
+ if (friend.id === owner.id) {
2160
+ if (chatId !== ownerUserId || chatId !== ownerChatId || input.target.sessionKey !== ownerSessionKey)
2161
+ return { allowed: false, reason: "owner Telegram identity or session binding changed" };
2162
+ }
2163
+ else if (input.target.sessionKey !== `telegram:${botId}:${chatId}`)
2164
+ return { allowed: false, reason: "relationship Telegram session binding changed" };
2165
+ const inboundIdempotency = input.target.requestId
2166
+ ? new RegExp(`^relationship-turn:${input.target.requestId.replace(/[.*+?^${}()|[\]\\]/gu, "\\$&")}:delivery:[0-9]+$`, "u").test(input.idempotencyKey)
2167
+ : false;
2168
+ const durableFollowUp = friend.id !== owner.id && input.target.requestId
2169
+ ? (0, tools_awaiting_1.hasActiveRelationshipFollowUp)(agentRoot, { friendId: friend.id, channel: "telegram", key: input.target.sessionKey, requestId: input.target.requestId, allowElapsed: input.idempotencyKey.startsWith("await-expiry-follow-up:") })
2170
+ : false;
2171
+ if (friend.id !== owner.id && input.target.requestId && !inboundIdempotency && !durableFollowUp)
2172
+ return { allowed: false, reason: "relationship follow-up is not bound to an active request await" };
2173
+ const evaluator = (0, relationship_authorization_1.createRelationshipAuthorizationEvaluator)({
2174
+ friend,
2175
+ registry: (0, relationship_authorization_1.loadRelationshipCapabilityRegistry)(agentRoot),
2176
+ ...(input.target.requestId ? { requestId: input.target.requestId, requestPhase: inboundIdempotency ? "inbound" : "follow_up" } : {}),
2177
+ });
2178
+ const scope = input.target.friendId === owner.id
2179
+ ? (input.target.requestId ? "telegram.owner_event" : "telegram.proactive")
2180
+ : "telegram.request_return";
2181
+ const authorization = evaluator.authorizeEffect(scope);
2182
+ return authorization.allowed
2183
+ ? { allowed: true, receiptId: authorization.receiptId, expiresAt: new Date(Date.now() + 5 * 60_000).toISOString(), transport: { chatId } }
2184
+ : authorization;
2185
+ },
2186
+ };
2187
+ }
2188
+ async function startTelegramSenseApp(agentName, waitForAuthority = false) {
2189
+ let authorityTransport;
2190
+ let credentials;
2191
+ if (agentName === "sanctuary") {
2192
+ const runtime = (0, runtime_credentials_1.readRuntimeCredentialConfig)(agentName);
2193
+ const machine = (0, runtime_credentials_1.readMachineRuntimeCredentialConfig)(agentName);
2194
+ if (!runtime.ok || !machine.ok)
2195
+ throw new Error("Sanctuary Telegram credential inventory is unavailable; migration readback is required");
2196
+ let waiting = false;
2197
+ for (;;) {
2198
+ try {
2199
+ ;
2200
+ ({ credentials, authorityTransport } = (0, sanctuary_authority_resident_1.openSanctuaryResidentAuthority)(runtime.config, machine.config));
2201
+ break;
2202
+ }
2203
+ catch (error) {
2204
+ if (!waitForAuthority || error?.code !== "ENOENT")
2205
+ throw error;
2206
+ if (!waiting) {
2207
+ waiting = true;
2208
+ (0, runtime_1.emitNervesEvent)({ level: "warn", component: "senses", event: "senses.sanctuary_gateway_waiting", message: "Telegram is unavailable while root gateway pins are not yet published", meta: { agentName } });
2209
+ }
2210
+ await new Promise((resolve) => setTimeout(resolve, 1000));
2211
+ }
2212
+ }
2213
+ }
2214
+ else {
2215
+ const loaded = loadTelegramSenseCredentials(agentName);
2216
+ credentials = { ...loaded, botId: telegramBotIdFromToken(loaded.botToken) };
2217
+ }
2218
+ let app;
2219
+ try {
2220
+ app = createTelegramSenseApp({
2221
+ agentName,
2222
+ credentials,
2223
+ authorityTransport,
2224
+ ...(await createProductionTelegramRelationshipComposition(agentName, credentials, undefined, authorityTransport)),
2225
+ ...(agentName === "sanctuary" ? { privilegedFailsafe: {
2226
+ eventRoot: (0, router_1.getExternalEventRoot)(),
2227
+ verifyProtectiveState: createSabQueueProtectiveStateVerifier(),
2228
+ } } : {}),
2229
+ });
2230
+ }
2231
+ catch (error) {
2232
+ authorityTransport?.api.stop();
2233
+ throw error;
2234
+ }
2235
+ (0, runtime_1.emitNervesEvent)({
2236
+ component: "senses",
2237
+ event: "senses.telegram_app_ready",
2238
+ message: "Telegram sense app is ready",
2239
+ meta: { agentName },
2240
+ });
2241
+ return app;
2242
+ }
2243
+ async function sendTelegramExternalEventDecision(agentName, input) {
2244
+ const app = await startTelegramSenseApp(agentName);
2245
+ try {
2246
+ await app.sendExternalEventDecision(input);
2247
+ }
2248
+ finally {
2249
+ await app.stop();
2250
+ }
2251
+ }
2252
+ /**
2253
+ * Suppresses a proactive message that repeats one sent recently.
2254
+ *
2255
+ * Ari received four probe messages in about two minutes, three of them identical
2256
+ * apart from the clock reading. Only the clock is normalised away — counts and
2257
+ * every other number are the payload, so "3 of 10 episodes" and "7 of 10" stay
2258
+ * distinct and both send.
2259
+ *
2260
+ * This catches verbatim-apart-from-the-clock repetition, not semantic near
2261
+ * duplicates: a message reworded between sends still goes out.
2262
+ */
2263
+ function createProactiveRepetitionGuard(options) {
2264
+ const windowMs = options?.windowMs ?? 900_000;
2265
+ const now = options?.now ?? (() => Date.now());
2266
+ const maxEntries = options?.maxEntries ?? 32;
2267
+ const seen = new Map();
2268
+ const normalize = (text) => text
2269
+ .trim()
2270
+ .replace(/\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}(?:\.\d+)?Z?/gu, "<timestamp>")
2271
+ .replace(/\b\d{1,2}:\d{2}(?::\d{2})?\b/gu, "<clock>")
2272
+ .replace(/\s+/gu, " ");
2273
+ return {
2274
+ shouldSend(text) {
2275
+ const at = now();
2276
+ for (const [key, sentAt] of [...seen]) {
2277
+ if (at - sentAt >= windowMs)
2278
+ seen.delete(key);
2279
+ }
2280
+ const key = normalize(text);
2281
+ const previous = seen.get(key);
2282
+ if (previous !== undefined && at - previous < windowMs)
2283
+ return false;
2284
+ seen.set(key, at);
2285
+ // A Map keeps insertion order and entries are inserted in send order, so
2286
+ // the earliest key is the oldest; scanning for a minimum would carry a
2287
+ // comparison branch that monotonic time can never take.
2288
+ for (const oldest of seen.keys()) {
2289
+ if (seen.size <= maxEntries)
2290
+ break;
2291
+ seen.delete(oldest);
2292
+ }
2293
+ return true;
2294
+ },
2295
+ };
2296
+ }
2297
+ /**
2298
+ * Telegram clears a typing indicator after roughly five seconds, so a turn that
2299
+ * thinks for longer has to refresh it or the chat goes silent while the Butler is
2300
+ * still working — which is what "leaving me on read" actually looked like.
2301
+ *
2302
+ * Indicator failures are swallowed on purpose: a missing typing bubble must never
2303
+ * take down the reply it was decorating.
2304
+ */
2305
+ async function withTelegramTypingIndicator(send, run, intervalMs = 4_000) {
2306
+ const tick = () => { void Promise.resolve().then(send).catch(() => undefined); };
2307
+ tick();
2308
+ const timer = setInterval(tick, intervalMs);
2309
+ try {
2310
+ return await run();
2311
+ }
2312
+ finally {
2313
+ clearInterval(timer);
2314
+ }
2315
+ }
2316
+ async function sendTelegramAwaitFollowUp(agentName, request) {
2317
+ const app = await startTelegramSenseApp(agentName);
2318
+ try {
2319
+ return await app.sendAwaitFollowUp(request);
2320
+ }
2321
+ finally {
2322
+ await app.stop();
2323
+ }
2324
+ }