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