@vellumai/assistant 0.4.26 → 0.4.30
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/.env.example +2 -2
- package/AGENTS.md +5 -0
- package/ARCHITECTURE.md +207 -105
- package/Dockerfile +1 -1
- package/README.md +111 -113
- package/bun.lock +0 -3
- package/docs/architecture/integrations.md +0 -1
- package/docs/architecture/memory.md +100 -63
- package/docs/error-handling.md +71 -0
- package/docs/runbook-trusted-contacts.md +89 -52
- package/docs/trusted-contact-access.md +48 -46
- package/package.json +3 -3
- package/scripts/compare-benchmarks.sh +12 -5
- package/scripts/ipc/check-swift-decoder-drift.ts +5 -3
- package/scripts/test.sh +89 -5
- package/src/__tests__/__snapshots__/ipc-snapshot.test.ts.snap +50 -37
- package/src/__tests__/access-request-decision.test.ts +0 -1
- package/src/__tests__/account-registry.test.ts +1 -1
- package/src/__tests__/actor-token-service.test.ts +40 -26
- package/src/__tests__/agent-loop-thinking.test.ts +29 -13
- package/src/__tests__/agent-loop.test.ts +2 -1
- package/src/__tests__/app-builder-tool-scripts.test.ts +1 -1
- package/src/__tests__/app-executors.test.ts +7 -17
- package/src/__tests__/approval-routes-http.test.ts +2 -2
- package/src/__tests__/asset-materialize-tool.test.ts +7 -7
- package/src/__tests__/asset-search-tool.test.ts +7 -7
- package/src/__tests__/assistant-feature-flags-integration.test.ts +18 -10
- package/src/__tests__/browser-fill-credential.test.ts +1 -1
- package/src/__tests__/browser-skill-endstate.test.ts +10 -1
- package/src/__tests__/bundled-skill-retrieval-guard.test.ts +218 -0
- package/src/__tests__/call-controller.test.ts +99 -69
- package/src/__tests__/call-start-guardian-guard.test.ts +1 -1
- package/src/__tests__/channel-approval-routes.test.ts +157 -114
- package/src/__tests__/channel-approval.test.ts +8 -0
- package/src/__tests__/channel-approvals.test.ts +39 -1
- package/src/__tests__/channel-guardian.test.ts +176 -275
- package/src/__tests__/channel-readiness-service.test.ts +6 -2
- package/src/__tests__/channel-reply-delivery.test.ts +33 -2
- package/src/__tests__/channel-retry-sweep.test.ts +14 -14
- package/src/__tests__/checker.test.ts +12 -31
- package/src/__tests__/claude-code-tool-profiles.test.ts +1 -1
- package/src/__tests__/commit-message-enrichment-service.test.ts +71 -59
- package/src/__tests__/compaction.benchmark.test.ts +6 -2
- package/src/__tests__/computer-use-tools.test.ts +1 -1
- package/src/__tests__/config-schema.test.ts +66 -7
- package/src/__tests__/confirmation-request-guardian-bridge.test.ts +29 -29
- package/src/__tests__/contacts-tools.test.ts +63 -2
- package/src/__tests__/context-overflow-approval.test.ts +141 -0
- package/src/__tests__/context-overflow-policy.test.ts +171 -0
- package/src/__tests__/context-overflow-reducer.test.ts +533 -0
- package/src/__tests__/context-window-manager.test.ts +97 -0
- package/src/__tests__/conversation-attention-telegram.test.ts +38 -46
- package/src/__tests__/conversation-pairing.test.ts +2 -2
- package/src/__tests__/conversation-routes-guardian-reply.test.ts +214 -10
- package/src/__tests__/conversation-routes.test.ts +4 -7
- package/src/__tests__/credential-broker-browser-fill.test.ts +13 -2
- package/src/__tests__/credential-security-e2e.test.ts +1 -1
- package/src/__tests__/credential-security-invariants.test.ts +1 -1
- package/src/__tests__/credential-vault-unit.test.ts +1 -1
- package/src/__tests__/credential-vault.test.ts +11 -8
- package/src/__tests__/daemon-lifecycle.test.ts +2 -2
- package/src/__tests__/daemon-server-session-init.test.ts +6 -6
- package/src/__tests__/delete-managed-skill-tool.test.ts +1 -1
- package/src/__tests__/deterministic-verification-control-plane.test.ts +2 -2
- package/src/__tests__/dynamic-skill-workflow-prompt.test.ts +9 -0
- package/src/__tests__/emit-signal-routing-intent.test.ts +4 -0
- package/src/__tests__/encrypted-store.test.ts +10 -7
- package/src/__tests__/ephemeral-permissions.test.ts +3 -3
- package/src/__tests__/file-edit-tool.test.ts +1 -1
- package/src/__tests__/file-read-tool.test.ts +1 -1
- package/src/__tests__/file-write-tool.test.ts +1 -1
- package/src/__tests__/fixtures/credential-security-fixtures.ts +87 -64
- package/src/__tests__/fixtures/media-reuse-fixtures.ts +37 -31
- package/src/__tests__/fixtures/mock-signup-server.ts +171 -115
- package/src/__tests__/fixtures/proxy-fixtures.ts +39 -39
- package/src/__tests__/followup-tools.test.ts +1 -1
- package/src/__tests__/gateway-only-guard.test.ts +4 -0
- package/src/__tests__/gemini-image-service.test.ts +2 -2
- package/src/__tests__/guardian-actions-endpoint.test.ts +543 -1
- package/src/__tests__/guardian-control-plane-policy.test.ts +15 -15
- package/src/__tests__/guardian-dispatch.test.ts +79 -1
- package/src/__tests__/guardian-grant-minting.test.ts +20 -20
- package/src/__tests__/guardian-outbound-http.test.ts +1 -2
- package/src/__tests__/guardian-principal-id-roundtrip.test.ts +0 -41
- package/src/__tests__/guardian-routing-invariants.test.ts +36 -16
- package/src/__tests__/guardian-routing-state.test.ts +36 -52
- package/src/__tests__/guardian-verification-intent-routing.test.ts +4 -6
- package/src/__tests__/guardian-verify-setup-skill-regression.test.ts +6 -8
- package/src/__tests__/handle-user-message-secret-resume.test.ts +39 -1
- package/src/__tests__/handlers-cu-observation-blob.test.ts +21 -10
- package/src/__tests__/handlers-telegram-config.test.ts +14 -14
- package/src/__tests__/handlers-user-message-approval-consumption.test.ts +23 -2
- package/src/__tests__/headless-browser-interactions.test.ts +1 -1
- package/src/__tests__/headless-browser-navigate.test.ts +1 -1
- package/src/__tests__/headless-browser-read-tools.test.ts +1 -1
- package/src/__tests__/headless-browser-snapshot.test.ts +1 -1
- package/src/__tests__/heartbeat-service.test.ts +45 -2
- package/src/__tests__/host-file-edit-tool.test.ts +1 -1
- package/src/__tests__/host-file-read-tool.test.ts +1 -1
- package/src/__tests__/host-file-write-tool.test.ts +1 -1
- package/src/__tests__/host-shell-tool.test.ts +1 -1
- package/src/__tests__/inbound-invite-redemption.test.ts +17 -19
- package/src/__tests__/ingress-reconcile.test.ts +2 -2
- package/src/__tests__/integrations-cli.test.ts +232 -0
- package/src/__tests__/intent-routing.test.ts +7 -5
- package/src/__tests__/invite-redemption-service.test.ts +5 -4
- package/src/__tests__/{ingress-routes-http.test.ts → invite-routes-http.test.ts} +42 -321
- package/src/__tests__/ipc-snapshot.test.ts +32 -31
- package/src/__tests__/managed-skill-lifecycle.test.ts +1 -1
- package/src/__tests__/mcp-cli.test.ts +136 -57
- package/src/__tests__/mcp-client-auth.test.ts +95 -0
- package/src/__tests__/media-generate-image.test.ts +2 -2
- package/src/__tests__/media-reuse-story.e2e.test.ts +8 -8
- package/src/__tests__/memory-regressions.test.ts +6 -6
- package/src/__tests__/messaging-send-tool.test.ts +1 -1
- package/src/__tests__/migration-cross-version-compatibility.test.ts +1855 -0
- package/src/__tests__/migration-export-http.test.ts +540 -0
- package/src/__tests__/migration-import-commit-http.test.ts +823 -0
- package/src/__tests__/migration-import-preflight-http.test.ts +755 -0
- package/src/__tests__/migration-parity-persistence.test.ts +1854 -0
- package/src/__tests__/migration-transport.test.ts +904 -0
- package/src/__tests__/migration-validate-http.test.ts +698 -0
- package/src/__tests__/migration-wizard.test.ts +1289 -0
- package/src/__tests__/nl-approval-parser.test.ts +305 -0
- package/src/__tests__/non-member-access-request.test.ts +17 -17
- package/src/__tests__/notification-decision-strategy.test.ts +110 -2
- package/src/__tests__/notification-deep-link.test.ts +18 -0
- package/src/__tests__/notification-guardian-path.test.ts +0 -1
- package/src/__tests__/oauth-provider-profiles.test.ts +34 -0
- package/src/__tests__/oauth2-gateway-transport.test.ts +1 -1
- package/src/__tests__/playbook-execution.test.ts +1 -1
- package/src/__tests__/playbook-tools.test.ts +1 -1
- package/src/__tests__/provider-error-scenarios.test.ts +68 -0
- package/src/__tests__/provider-streaming.benchmark.test.ts +3 -1
- package/src/__tests__/proxy-approval-callback.test.ts +1 -1
- package/src/__tests__/qdrant-manager.test.ts +40 -11
- package/src/__tests__/rebind-secrets-screen.test.ts +839 -0
- package/src/__tests__/recording-handler.test.ts +2 -2
- package/src/__tests__/recording-intent-handler.test.ts +3 -3
- package/src/__tests__/recording-state-machine.test.ts +2 -2
- package/src/__tests__/relay-server.test.ts +507 -228
- package/src/__tests__/reminder-store.test.ts +8 -0
- package/src/__tests__/reminder.test.ts +8 -0
- package/src/__tests__/{resolve-guardian-trust-class.test.ts → resolve-trust-class.test.ts} +11 -17
- package/src/__tests__/retry-after-extraction.test.ts +111 -0
- package/src/__tests__/scaffold-managed-skill-tool.test.ts +1 -1
- package/src/__tests__/schedule-tools.test.ts +1 -1
- package/src/__tests__/script-proxy-certs.test.ts +1 -1
- package/src/__tests__/script-proxy-connect-tunnel.test.ts +2 -3
- package/src/__tests__/script-proxy-decision-trace.test.ts +2 -2
- package/src/__tests__/script-proxy-http-forwarder.test.ts +1 -1
- package/src/__tests__/script-proxy-injection-runtime.test.ts +5 -5
- package/src/__tests__/script-proxy-mitm-handler.test.ts +4 -4
- package/src/__tests__/script-proxy-policy-runtime.test.ts +2 -2
- package/src/__tests__/script-proxy-policy.test.ts +2 -2
- package/src/__tests__/script-proxy-profile-template-fallback.test.ts +127 -0
- package/src/__tests__/script-proxy-session-manager.test.ts +4 -7
- package/src/__tests__/script-proxy-session-runtime.test.ts +1 -6
- package/src/__tests__/secret-onetime-send.test.ts +4 -4
- package/src/__tests__/secret-scanner-executor.test.ts +2 -2
- package/src/__tests__/send-endpoint-busy.test.ts +11 -9
- package/src/__tests__/send-notification-tool.test.ts +2 -2
- package/src/__tests__/session-abort-tool-results.test.ts +17 -2
- package/src/__tests__/session-agent-loop.test.ts +456 -35
- package/src/__tests__/session-confirmation-signals.test.ts +3 -2
- package/src/__tests__/session-conflict-gate.test.ts +20 -3
- package/src/__tests__/session-init.benchmark.test.ts +2 -2
- package/src/__tests__/session-load-history-repair.test.ts +7 -7
- package/src/__tests__/session-media-retry.test.ts +147 -0
- package/src/__tests__/session-pre-run-repair.test.ts +17 -2
- package/src/__tests__/session-profile-injection.test.ts +20 -3
- package/src/__tests__/session-provider-retry-repair.test.ts +86 -6
- package/src/__tests__/session-queue.test.ts +33 -18
- package/src/__tests__/session-runtime-assembly.test.ts +147 -1
- package/src/__tests__/session-runtime-workspace.test.ts +40 -0
- package/src/__tests__/session-slash-known.test.ts +21 -3
- package/src/__tests__/session-slash-queue.test.ts +17 -2
- package/src/__tests__/session-slash-unknown.test.ts +17 -2
- package/src/__tests__/session-surfaces-deselection.test.ts +208 -0
- package/src/__tests__/session-workspace-cache-state.test.ts +2 -2
- package/src/__tests__/session-workspace-injection.test.ts +17 -2
- package/src/__tests__/session-workspace-tool-tracking.test.ts +17 -2
- package/src/__tests__/shell-credential-ref.test.ts +1 -1
- package/src/__tests__/shell-tool-proxy-mode.test.ts +1 -1
- package/src/__tests__/skill-feature-flags-integration.test.ts +9 -5
- package/src/__tests__/skill-feature-flags.test.ts +18 -12
- package/src/__tests__/skill-load-feature-flag.test.ts +5 -4
- package/src/__tests__/skill-load-tool.test.ts +1 -1
- package/src/__tests__/skill-script-runner-host.test.ts +1 -1
- package/src/__tests__/skill-script-runner-sandbox.test.ts +1 -1
- package/src/__tests__/skill-script-runner.test.ts +1 -1
- package/src/__tests__/skill-tool-factory.test.ts +1 -1
- package/src/__tests__/slack-block-formatting.test.ts +100 -0
- package/src/__tests__/slack-inbound-verification.test.ts +346 -0
- package/src/__tests__/slack-reaction-approvals.test.ts +77 -0
- package/src/__tests__/slack-skill.test.ts +4 -2
- package/src/__tests__/starter-task-flow.test.ts +0 -1
- package/src/__tests__/subagent-tools.test.ts +3 -3
- package/src/__tests__/swarm-recursion.test.ts +1 -1
- package/src/__tests__/swarm-session-integration.test.ts +1 -1
- package/src/__tests__/swarm-tool.test.ts +1 -1
- package/src/__tests__/task-management-tools.test.ts +1 -1
- package/src/__tests__/task-tools.test.ts +1 -1
- package/src/__tests__/terminal-tools.test.ts +1 -1
- package/src/__tests__/test-support/browser-skill-harness.ts +39 -27
- package/src/__tests__/test-support/computer-use-skill-harness.ts +14 -14
- package/src/__tests__/tool-approval-handler.test.ts +15 -15
- package/src/__tests__/tool-execution-abort-cleanup.test.ts +1 -1
- package/src/__tests__/tool-execution-pipeline.benchmark.test.ts +1 -1
- package/src/__tests__/tool-executor-lifecycle-events.test.ts +2 -2
- package/src/__tests__/tool-executor-shell-integration.test.ts +1 -1
- package/src/__tests__/tool-executor.test.ts +23 -182
- package/src/__tests__/tool-grant-request-escalation.test.ts +11 -11
- package/src/__tests__/tool-permission-simulate-handler.test.ts +4 -4
- package/src/__tests__/transfer-progress-screen.test.ts +1180 -0
- package/src/__tests__/trust-context-guards.test.ts +25 -29
- package/src/__tests__/trusted-contact-approval-notifier.test.ts +23 -21
- package/src/__tests__/trusted-contact-inline-approval-integration.test.ts +37 -40
- package/src/__tests__/trusted-contact-lifecycle-notifications.test.ts +29 -25
- package/src/__tests__/trusted-contact-multichannel.test.ts +25 -24
- package/src/__tests__/trusted-contact-verification.test.ts +64 -76
- package/src/__tests__/turn-commit.test.ts +18 -18
- package/src/__tests__/twilio-provider.test.ts +7 -7
- package/src/__tests__/validation-results-screen.test.ts +1107 -0
- package/src/__tests__/view-image-tool.test.ts +1 -1
- package/src/__tests__/voice-invite-redemption.test.ts +4 -3
- package/src/__tests__/voice-scoped-grant-consumer.test.ts +12 -12
- package/src/__tests__/voice-session-bridge.test.ts +24 -24
- package/src/agent/attachments.ts +3 -1
- package/src/agent/loop.ts +13 -13
- package/src/agent/message-types.ts +13 -7
- package/src/amazon/cart.ts +59 -32
- package/src/amazon/checkout.ts +25 -14
- package/src/amazon/client.ts +61 -58
- package/src/amazon/product-details.ts +3 -3
- package/src/amazon/request-extractor.ts +46 -31
- package/src/amazon/search.ts +6 -4
- package/src/amazon/session.ts +33 -24
- package/src/approvals/AGENTS.md +26 -0
- package/src/approvals/approval-primitive.ts +87 -64
- package/src/approvals/guardian-decision-primitive.ts +172 -81
- package/src/approvals/guardian-request-resolvers.ts +262 -155
- package/src/autonomy/autonomy-resolver.ts +7 -5
- package/src/autonomy/autonomy-store.ts +34 -19
- package/src/autonomy/disposition-mapper.ts +5 -5
- package/src/autonomy/index.ts +6 -6
- package/src/autonomy/types.ts +7 -3
- package/src/browser-extension-relay/client.ts +50 -19
- package/src/browser-extension-relay/protocol.ts +11 -11
- package/src/browser-extension-relay/server.ts +45 -20
- package/src/bundler/app-bundler.ts +75 -50
- package/src/bundler/bundle-scanner.ts +145 -41
- package/src/bundler/bundle-signer.ts +16 -14
- package/src/bundler/signature-verifier.ts +36 -33
- package/src/calls/call-constants.ts +10 -3
- package/src/calls/call-controller.ts +473 -214
- package/src/calls/call-conversation-messages.ts +25 -15
- package/src/calls/call-domain.ts +401 -148
- package/src/calls/call-pointer-message-composer.ts +26 -21
- package/src/calls/call-pointer-messages.ts +52 -28
- package/src/calls/call-recovery.ts +53 -37
- package/src/calls/call-state-machine.ts +37 -7
- package/src/calls/call-state.ts +35 -13
- package/src/calls/call-store.ts +165 -77
- package/src/calls/elevenlabs-client.ts +39 -20
- package/src/calls/guardian-action-sweep.ts +42 -24
- package/src/calls/guardian-dispatch.ts +79 -56
- package/src/calls/guardian-question-copy.ts +28 -23
- package/src/calls/relay-server.ts +1149 -532
- package/src/calls/speaker-identification.ts +21 -15
- package/src/calls/twilio-config.ts +34 -17
- package/src/calls/twilio-provider.ts +108 -55
- package/src/calls/twilio-rest.ts +212 -100
- package/src/calls/twilio-routes.ts +165 -92
- package/src/calls/types.ts +55 -7
- package/src/calls/voice-quality.ts +6 -4
- package/src/calls/voice-session-bridge.ts +181 -133
- package/src/channels/config.ts +18 -14
- package/src/channels/types.ts +38 -10
- package/src/cli/amazon.ts +333 -227
- package/src/cli/config-commands.ts +236 -146
- package/src/cli/core-commands.ts +403 -329
- package/src/cli/email-guardrails.ts +38 -19
- package/src/cli/email.ts +207 -153
- package/src/cli/influencer.ts +58 -56
- package/src/cli/integrations.ts +306 -0
- package/src/cli/ipc-client.ts +24 -19
- package/src/cli/map.ts +176 -129
- package/src/cli/mcp.ts +260 -152
- package/src/cli/sequence.ts +165 -107
- package/src/cli/twitter.ts +302 -218
- package/src/cli.ts +418 -279
- package/src/commands/cc-command-registry.ts +52 -27
- package/src/config/agent-schema.ts +217 -134
- package/src/config/assistant-feature-flags.ts +23 -18
- package/src/config/bundled-skills/_shared/CLI_RETRIEVAL_PATTERN.md +19 -0
- package/src/config/bundled-skills/app-builder/SKILL.md +193 -1500
- package/src/config/bundled-skills/app-builder/TOOLS.json +70 -18
- package/src/config/bundled-skills/app-builder/tools/app-create.ts +7 -4
- package/src/config/bundled-skills/app-builder/tools/app-delete.ts +6 -3
- package/src/config/bundled-skills/app-builder/tools/app-file-edit.ts +7 -4
- package/src/config/bundled-skills/app-builder/tools/app-file-list.ts +6 -3
- package/src/config/bundled-skills/app-builder/tools/app-file-read.ts +6 -3
- package/src/config/bundled-skills/app-builder/tools/app-file-write.ts +7 -4
- package/src/config/bundled-skills/app-builder/tools/app-list.ts +6 -3
- package/src/config/bundled-skills/app-builder/tools/app-query.ts +6 -3
- package/src/config/bundled-skills/app-builder/tools/app-update.ts +6 -3
- package/src/config/bundled-skills/browser/TOOLS.json +59 -2
- package/src/config/bundled-skills/browser/tools/browser-click.ts +5 -2
- package/src/config/bundled-skills/browser/tools/browser-close.ts +5 -2
- package/src/config/bundled-skills/browser/tools/browser-extract.ts +5 -2
- package/src/config/bundled-skills/browser/tools/browser-fill-credential.ts +5 -2
- package/src/config/bundled-skills/browser/tools/browser-hover.ts +5 -2
- package/src/config/bundled-skills/browser/tools/browser-navigate.ts +5 -2
- package/src/config/bundled-skills/browser/tools/browser-press-key.ts +5 -2
- package/src/config/bundled-skills/browser/tools/browser-screenshot.ts +5 -2
- package/src/config/bundled-skills/browser/tools/browser-scroll.ts +5 -2
- package/src/config/bundled-skills/browser/tools/browser-select-option.ts +5 -2
- package/src/config/bundled-skills/browser/tools/browser-snapshot.ts +5 -2
- package/src/config/bundled-skills/browser/tools/browser-type.ts +5 -2
- package/src/config/bundled-skills/browser/tools/browser-wait-for-download.ts +13 -6
- package/src/config/bundled-skills/browser/tools/browser-wait-for.ts +5 -2
- package/src/config/bundled-skills/chatgpt-import/TOOLS.json +4 -0
- package/src/config/bundled-skills/claude-code/TOOLS.json +4 -0
- package/src/config/bundled-skills/claude-code/tools/claude-code.ts +5 -2
- package/src/config/bundled-skills/computer-use/SKILL.md +2 -2
- package/src/config/bundled-skills/computer-use/TOOLS.json +50 -2
- package/src/config/bundled-skills/computer-use/tools/computer-use-click.ts +6 -3
- package/src/config/bundled-skills/computer-use/tools/computer-use-done.ts +6 -3
- package/src/config/bundled-skills/computer-use/tools/computer-use-double-click.ts +10 -3
- package/src/config/bundled-skills/computer-use/tools/computer-use-drag.ts +6 -3
- package/src/config/bundled-skills/computer-use/tools/computer-use-key.ts +6 -3
- package/src/config/bundled-skills/computer-use/tools/computer-use-open-app.ts +6 -3
- package/src/config/bundled-skills/computer-use/tools/computer-use-request-control.ts +10 -3
- package/src/config/bundled-skills/computer-use/tools/computer-use-respond.ts +6 -3
- package/src/config/bundled-skills/computer-use/tools/computer-use-right-click.ts +10 -3
- package/src/config/bundled-skills/computer-use/tools/computer-use-run-applescript.ts +10 -3
- package/src/config/bundled-skills/computer-use/tools/computer-use-scroll.ts +6 -3
- package/src/config/bundled-skills/computer-use/tools/computer-use-type-text.ts +6 -3
- package/src/config/bundled-skills/computer-use/tools/computer-use-wait.ts +6 -3
- package/src/config/bundled-skills/configure-settings/SKILL.md +28 -14
- package/src/config/bundled-skills/contacts/SKILL.md +453 -15
- package/src/config/bundled-skills/contacts/TOOLS.json +22 -2
- package/src/config/bundled-skills/contacts/tools/contact-merge.ts +79 -20
- package/src/config/bundled-skills/contacts/tools/contact-search.ts +55 -18
- package/src/config/bundled-skills/contacts/tools/contact-upsert.ts +64 -19
- package/src/config/bundled-skills/document/TOOLS.json +8 -0
- package/src/config/bundled-skills/document/tools/document-create.ts +5 -2
- package/src/config/bundled-skills/document/tools/document-update.ts +5 -2
- package/src/config/bundled-skills/doordash/doordash-cli.ts +17 -7
- package/src/config/bundled-skills/email-setup/SKILL.md +12 -9
- package/src/config/bundled-skills/followups/TOOLS.json +12 -0
- package/src/config/bundled-skills/followups/tools/followup-create.ts +5 -2
- package/src/config/bundled-skills/followups/tools/followup-list.ts +5 -2
- package/src/config/bundled-skills/followups/tools/followup-resolve.ts +5 -2
- package/src/config/bundled-skills/google-calendar/TOOLS.json +124 -26
- package/src/config/bundled-skills/google-calendar/calendar-client.ts +44 -32
- package/src/config/bundled-skills/google-calendar/tools/calendar-check-availability.ts +11 -5
- package/src/config/bundled-skills/google-calendar/tools/calendar-create-event.ts +13 -7
- package/src/config/bundled-skills/google-calendar/tools/calendar-get-event.ts +11 -5
- package/src/config/bundled-skills/google-calendar/tools/calendar-list-events.ts +13 -7
- package/src/config/bundled-skills/google-calendar/tools/calendar-rsvp.ts +28 -12
- package/src/config/bundled-skills/google-calendar/tools/shared.ts +6 -4
- package/src/config/bundled-skills/google-calendar/types.ts +3 -3
- package/src/config/bundled-skills/guardian-verify-setup/SKILL.md +88 -33
- package/src/config/bundled-skills/image-studio/TOOLS.json +12 -2
- package/src/config/bundled-skills/image-studio/tools/media-generate-image.ts +48 -25
- package/src/config/bundled-skills/knowledge-graph/TOOLS.json +13 -3
- package/src/config/bundled-skills/knowledge-graph/tools/graph-query.ts +60 -35
- package/src/config/bundled-skills/mcp-setup/SKILL.md +75 -0
- package/src/config/bundled-skills/media-processing/SKILL.md +55 -15
- package/src/config/bundled-skills/media-processing/TOOLS.json +48 -2
- package/src/config/bundled-skills/media-processing/__tests__/concurrency-pool.test.ts +12 -10
- package/src/config/bundled-skills/media-processing/__tests__/cost-tracker.test.ts +34 -19
- package/src/config/bundled-skills/media-processing/__tests__/preprocess.test.ts +82 -66
- package/src/config/bundled-skills/media-processing/services/audio-transcribe.ts +148 -0
- package/src/config/bundled-skills/media-processing/services/concurrency-pool.ts +1 -1
- package/src/config/bundled-skills/media-processing/services/cost-tracker.ts +8 -3
- package/src/config/bundled-skills/media-processing/services/gemini-map.ts +117 -53
- package/src/config/bundled-skills/media-processing/services/gemini-video.ts +273 -0
- package/src/config/bundled-skills/media-processing/services/preprocess.ts +185 -97
- package/src/config/bundled-skills/media-processing/services/processing-pipeline.ts +32 -27
- package/src/config/bundled-skills/media-processing/services/reduce.ts +101 -24
- package/src/config/bundled-skills/media-processing/tools/analyze-keyframes.ts +121 -55
- package/src/config/bundled-skills/media-processing/tools/extract-keyframes.ts +58 -24
- package/src/config/bundled-skills/media-processing/tools/generate-clip.ts +198 -92
- package/src/config/bundled-skills/media-processing/tools/ingest-media.ts +98 -70
- package/src/config/bundled-skills/media-processing/tools/media-diagnostics.ts +59 -19
- package/src/config/bundled-skills/media-processing/tools/media-status.ts +26 -10
- package/src/config/bundled-skills/media-processing/tools/query-media-events.ts +29 -14
- package/src/config/bundled-skills/messaging/SKILL.md +7 -5
- package/src/config/bundled-skills/messaging/TOOLS.json +232 -186
- package/src/config/bundled-skills/messaging/tools/gmail-archive-by-query.ts +31 -13
- package/src/config/bundled-skills/messaging/tools/gmail-archive.ts +16 -10
- package/src/config/bundled-skills/messaging/tools/gmail-batch-label.ts +18 -9
- package/src/config/bundled-skills/messaging/tools/gmail-download-attachment.ts +23 -16
- package/src/config/bundled-skills/messaging/tools/gmail-draft.ts +28 -12
- package/src/config/bundled-skills/messaging/tools/gmail-filters.ts +41 -21
- package/src/config/bundled-skills/messaging/tools/gmail-follow-up.ts +44 -23
- package/src/config/bundled-skills/messaging/tools/gmail-forward.ts +73 -33
- package/src/config/bundled-skills/messaging/tools/gmail-label.ts +15 -9
- package/src/config/bundled-skills/messaging/tools/gmail-list-attachments.ts +22 -14
- package/src/config/bundled-skills/messaging/tools/gmail-outreach-scan.ts +99 -50
- package/src/config/bundled-skills/messaging/tools/gmail-send-draft.ts +14 -8
- package/src/config/bundled-skills/messaging/tools/gmail-send-with-attachments.ts +63 -44
- package/src/config/bundled-skills/messaging/tools/gmail-sender-digest.ts +90 -46
- package/src/config/bundled-skills/messaging/tools/gmail-summarize-thread.ts +43 -22
- package/src/config/bundled-skills/messaging/tools/gmail-trash.ts +15 -9
- package/src/config/bundled-skills/messaging/tools/gmail-triage.ts +51 -22
- package/src/config/bundled-skills/messaging/tools/gmail-unsubscribe.ts +62 -26
- package/src/config/bundled-skills/messaging/tools/gmail-vacation.ts +34 -19
- package/src/config/bundled-skills/messaging/tools/google-contacts.ts +32 -16
- package/src/config/bundled-skills/messaging/tools/messaging-analyze-activity.ts +10 -4
- package/src/config/bundled-skills/messaging/tools/messaging-analyze-style.ts +91 -47
- package/src/config/bundled-skills/messaging/tools/messaging-archive-by-sender.ts +21 -9
- package/src/config/bundled-skills/messaging/tools/messaging-auth-test.ts +9 -3
- package/src/config/bundled-skills/messaging/tools/messaging-draft.ts +30 -17
- package/src/config/bundled-skills/messaging/tools/messaging-list-conversations.ts +10 -4
- package/src/config/bundled-skills/messaging/tools/messaging-mark-read.ts +14 -6
- package/src/config/bundled-skills/messaging/tools/messaging-read.ts +16 -5
- package/src/config/bundled-skills/messaging/tools/messaging-reply.ts +63 -36
- package/src/config/bundled-skills/messaging/tools/messaging-search.ts +10 -4
- package/src/config/bundled-skills/messaging/tools/messaging-send.ts +30 -12
- package/src/config/bundled-skills/messaging/tools/messaging-sender-digest.ts +48 -29
- package/src/config/bundled-skills/messaging/tools/scan-result-store.ts +20 -6
- package/src/config/bundled-skills/messaging/tools/send-notification.ts +1 -1
- package/src/config/bundled-skills/messaging/tools/sequence-analytics.ts +59 -22
- package/src/config/bundled-skills/messaging/tools/sequence-cancel.ts +13 -7
- package/src/config/bundled-skills/messaging/tools/sequence-create.ts +27 -12
- package/src/config/bundled-skills/messaging/tools/sequence-delete.ts +14 -6
- package/src/config/bundled-skills/messaging/tools/sequence-enroll.ts +30 -11
- package/src/config/bundled-skills/messaging/tools/sequence-enrollment-list.ts +16 -8
- package/src/config/bundled-skills/messaging/tools/sequence-get.ts +31 -13
- package/src/config/bundled-skills/messaging/tools/sequence-import.ts +38 -22
- package/src/config/bundled-skills/messaging/tools/sequence-list.ts +16 -7
- package/src/config/bundled-skills/messaging/tools/sequence-pause.ts +29 -10
- package/src/config/bundled-skills/messaging/tools/sequence-resume.ts +16 -8
- package/src/config/bundled-skills/messaging/tools/sequence-update.ts +35 -16
- package/src/config/bundled-skills/messaging/tools/shared.ts +26 -12
- package/src/config/bundled-skills/notifications/SKILL.md +3 -2
- package/src/config/bundled-skills/notifications/TOOLS.json +7 -13
- package/src/config/bundled-skills/notifications/tools/send-notification.ts +69 -34
- package/src/config/bundled-skills/notifications/tools/shared.ts +1 -1
- package/src/config/bundled-skills/phone-calls/SKILL.md +46 -48
- package/src/config/bundled-skills/phone-calls/TOOLS.json +13 -1
- package/src/config/bundled-skills/phone-calls/tools/call-end.ts +1 -1
- package/src/config/bundled-skills/phone-calls/tools/call-start.ts +1 -1
- package/src/config/bundled-skills/phone-calls/tools/call-status.ts +1 -1
- package/src/config/bundled-skills/playbooks/TOOLS.json +16 -0
- package/src/config/bundled-skills/playbooks/tools/playbook-create.ts +91 -51
- package/src/config/bundled-skills/playbooks/tools/playbook-delete.ts +30 -16
- package/src/config/bundled-skills/playbooks/tools/playbook-list.ts +66 -27
- package/src/config/bundled-skills/playbooks/tools/playbook-update.ts +89 -42
- package/src/config/bundled-skills/public-ingress/SKILL.md +26 -19
- package/src/config/bundled-skills/reminder/TOOLS.json +15 -2
- package/src/config/bundled-skills/reminder/tools/reminder-cancel.ts +5 -2
- package/src/config/bundled-skills/reminder/tools/reminder-create.ts +5 -2
- package/src/config/bundled-skills/reminder/tools/reminder-list.ts +5 -2
- package/src/config/bundled-skills/schedule/SKILL.md +33 -15
- package/src/config/bundled-skills/schedule/TOOLS.json +17 -1
- package/src/config/bundled-skills/schedule/tools/schedule-create.ts +5 -2
- package/src/config/bundled-skills/schedule/tools/schedule-delete.ts +5 -2
- package/src/config/bundled-skills/schedule/tools/schedule-list.ts +5 -2
- package/src/config/bundled-skills/schedule/tools/schedule-update.ts +5 -2
- package/src/config/bundled-skills/screen-recording/SKILL.md +11 -3
- package/src/config/bundled-skills/self-upgrade/SKILL.md +9 -8
- package/src/config/bundled-skills/slack/SKILL.md +30 -1
- package/src/config/bundled-skills/slack/TOOLS.json +122 -17
- package/src/config/bundled-skills/slack/tools/shared.ts +7 -5
- package/src/config/bundled-skills/slack/tools/slack-add-reaction.ts +11 -5
- package/src/config/bundled-skills/slack/tools/slack-channel-details.ts +11 -5
- package/src/config/bundled-skills/slack/tools/slack-channel-permissions.ts +146 -0
- package/src/config/bundled-skills/slack/tools/slack-configure-channels.ts +46 -16
- package/src/config/bundled-skills/slack/tools/slack-delete-message.ts +11 -5
- package/src/config/bundled-skills/slack/tools/slack-edit-message.ts +28 -0
- package/src/config/bundled-skills/slack/tools/slack-leave-channel.ts +12 -6
- package/src/config/bundled-skills/slack/tools/slack-scan-digest.ts +120 -0
- package/src/config/bundled-skills/slack-app-setup/SKILL.md +200 -0
- package/src/config/bundled-skills/sms-setup/SKILL.md +5 -8
- package/src/config/bundled-skills/subagent/TOOLS.json +22 -2
- package/src/config/bundled-skills/subagent/tools/subagent-abort.ts +5 -2
- package/src/config/bundled-skills/subagent/tools/subagent-message.ts +5 -2
- package/src/config/bundled-skills/subagent/tools/subagent-read.ts +5 -2
- package/src/config/bundled-skills/subagent/tools/subagent-spawn.ts +5 -2
- package/src/config/bundled-skills/subagent/tools/subagent-status.ts +5 -2
- package/src/config/bundled-skills/tasks/TOOLS.json +86 -14
- package/src/config/bundled-skills/tasks/tools/task-delete.ts +5 -2
- package/src/config/bundled-skills/tasks/tools/task-list-add.ts +5 -2
- package/src/config/bundled-skills/tasks/tools/task-list-remove.ts +5 -2
- package/src/config/bundled-skills/tasks/tools/task-list-show.ts +5 -2
- package/src/config/bundled-skills/tasks/tools/task-list-update.ts +5 -2
- package/src/config/bundled-skills/tasks/tools/task-list.ts +5 -2
- package/src/config/bundled-skills/tasks/tools/task-queue-run.ts +5 -2
- package/src/config/bundled-skills/tasks/tools/task-run.ts +5 -2
- package/src/config/bundled-skills/tasks/tools/task-save.ts +5 -2
- package/src/config/bundled-skills/telegram-setup/SKILL.md +7 -8
- package/src/config/bundled-skills/transcribe/TOOLS.json +4 -0
- package/src/config/bundled-skills/transcribe/tools/transcribe-media.ts +232 -127
- package/src/config/bundled-skills/twilio-setup/SKILL.md +7 -12
- package/src/config/bundled-skills/twitter/SKILL.md +19 -2
- package/src/config/bundled-skills/voice-setup/SKILL.md +5 -5
- package/src/config/bundled-skills/watcher/TOOLS.json +20 -0
- package/src/config/bundled-skills/watcher/tools/watcher-create.ts +5 -2
- package/src/config/bundled-skills/watcher/tools/watcher-delete.ts +5 -2
- package/src/config/bundled-skills/watcher/tools/watcher-digest.ts +5 -2
- package/src/config/bundled-skills/watcher/tools/watcher-list.ts +5 -2
- package/src/config/bundled-skills/watcher/tools/watcher-update.ts +5 -2
- package/src/config/bundled-skills/weather/TOOLS.json +4 -0
- package/src/config/bundled-skills/weather/tools/get-weather.ts +5 -2
- package/src/config/bundled-tool-registry.ts +2 -0
- package/src/config/calls-schema.ts +108 -63
- package/src/config/channel-permission-profiles.ts +155 -0
- package/src/config/computer-use-prompt.ts +7 -7
- package/src/config/core-schema.ts +239 -155
- package/src/config/defaults.ts +2 -2
- package/src/config/elevenlabs-schema.ts +15 -15
- package/src/config/env-registry.ts +33 -33
- package/src/config/env.ts +4 -1
- package/src/config/feature-flag-registry.json +31 -7
- package/src/config/loader.ts +118 -58
- package/src/config/mcp-schema.ts +29 -15
- package/src/config/memory-schema.ts +434 -229
- package/src/config/notifications-schema.ts +4 -4
- package/src/config/sandbox-schema.ts +2 -2
- package/src/config/schema.ts +12 -2
- package/src/config/skill-state.ts +27 -15
- package/src/config/skills-schema.ts +72 -23
- package/src/config/skills.ts +303 -143
- package/src/config/system-prompt.ts +25 -6
- package/src/config/types.ts +1 -1
- package/src/config/update-bulletin-format.ts +3 -3
- package/src/config/update-bulletin-state.ts +15 -6
- package/src/config/update-bulletin-template-path.ts +8 -4
- package/src/config/update-bulletin.ts +33 -14
- package/src/config/user-reference.ts +8 -8
- package/src/contacts/contact-events.ts +21 -0
- package/src/contacts/contact-store.ts +813 -100
- package/src/contacts/contacts-write.ts +287 -0
- package/src/contacts/index.ts +13 -4
- package/src/contacts/startup-migration.ts +21 -0
- package/src/contacts/types.ts +73 -2
- package/src/context/token-estimator.ts +54 -31
- package/src/context/tool-result-truncation.ts +41 -7
- package/src/context/window-manager.ts +225 -120
- package/src/daemon/approval-generators.ts +83 -55
- package/src/daemon/approved-devices-store.ts +33 -20
- package/src/daemon/assistant-attachments.ts +157 -101
- package/src/daemon/auth-manager.ts +17 -15
- package/src/daemon/classifier.ts +117 -46
- package/src/daemon/computer-use-session.ts +316 -187
- package/src/daemon/config-watcher.ts +91 -44
- package/src/daemon/connection-policy.ts +18 -10
- package/src/daemon/context-overflow-approval.ts +48 -0
- package/src/daemon/context-overflow-policy.ts +50 -0
- package/src/daemon/context-overflow-reducer.ts +300 -0
- package/src/daemon/daemon-control.ts +79 -51
- package/src/daemon/date-context.ts +119 -69
- package/src/daemon/dictation-profile-store.ts +94 -48
- package/src/daemon/dictation-text-processing.ts +33 -12
- package/src/daemon/doordash-steps.ts +92 -49
- package/src/daemon/guardian-action-generators.ts +62 -46
- package/src/daemon/guardian-verification-intent.ts +35 -19
- package/src/daemon/handlers/apps.ts +258 -113
- package/src/daemon/handlers/avatar.ts +20 -15
- package/src/daemon/handlers/computer-use.ts +82 -39
- package/src/daemon/handlers/config-channels.ts +146 -69
- package/src/daemon/handlers/config-heartbeat.ts +114 -59
- package/src/daemon/handlers/config-inbox.ts +213 -160
- package/src/daemon/handlers/config-ingress.ts +127 -55
- package/src/daemon/handlers/config-integrations.ts +145 -88
- package/src/daemon/handlers/config-model.ts +58 -22
- package/src/daemon/handlers/config-platform.ts +40 -16
- package/src/daemon/handlers/config-scheduling.ts +109 -48
- package/src/daemon/handlers/config-slack-channel.ts +67 -35
- package/src/daemon/handlers/config-slack.ts +21 -20
- package/src/daemon/handlers/config-telegram.ts +100 -70
- package/src/daemon/handlers/config-tools.ts +103 -55
- package/src/daemon/handlers/config-trust.ts +50 -20
- package/src/daemon/handlers/config.ts +72 -24
- package/src/daemon/handlers/contacts.ts +163 -0
- package/src/daemon/handlers/diagnostics.ts +90 -48
- package/src/daemon/handlers/documents.ts +74 -46
- package/src/daemon/handlers/guardian-actions.ts +57 -77
- package/src/daemon/handlers/home-base.ts +19 -16
- package/src/daemon/handlers/identity.ts +65 -45
- package/src/daemon/handlers/index.ts +78 -54
- package/src/daemon/handlers/misc.ts +664 -234
- package/src/daemon/handlers/navigate-settings.ts +14 -11
- package/src/daemon/handlers/oauth-connect.ts +48 -35
- package/src/daemon/handlers/open-bundle-handler.ts +31 -24
- package/src/daemon/handlers/pairing.ts +51 -25
- package/src/daemon/handlers/publish.ts +55 -33
- package/src/daemon/handlers/recording.ts +378 -162
- package/src/daemon/handlers/sessions.ts +922 -423
- package/src/daemon/handlers/shared.ts +202 -117
- package/src/daemon/handlers/signing.ts +25 -6
- package/src/daemon/handlers/subagents.ts +117 -56
- package/src/daemon/handlers/twitter-auth.ts +70 -49
- package/src/daemon/handlers/work-items.ts +264 -112
- package/src/daemon/handlers/workspace-files.ts +27 -20
- package/src/daemon/handlers.ts +2 -2
- package/src/daemon/history-repair.ts +16 -15
- package/src/daemon/identity-helpers.ts +4 -4
- package/src/daemon/install-cli-launchers.ts +33 -22
- package/src/daemon/ipc-blob-store.ts +38 -24
- package/src/daemon/ipc-contract/apps.ts +61 -50
- package/src/daemon/ipc-contract/computer-use.ts +47 -37
- package/src/daemon/ipc-contract/contacts.ts +69 -0
- package/src/daemon/ipc-contract/diagnostics.ts +14 -14
- package/src/daemon/ipc-contract/documents.ts +8 -8
- package/src/daemon/ipc-contract/guardian-actions.ts +4 -4
- package/src/daemon/ipc-contract/inbox.ts +12 -71
- package/src/daemon/ipc-contract/integrations.ts +57 -44
- package/src/daemon/ipc-contract/memory.ts +3 -5
- package/src/daemon/ipc-contract/messages.ts +95 -69
- package/src/daemon/ipc-contract/notifications.ts +10 -6
- package/src/daemon/ipc-contract/pairing.ts +8 -8
- package/src/daemon/ipc-contract/schedules.ts +20 -20
- package/src/daemon/ipc-contract/sessions.ts +89 -57
- package/src/daemon/ipc-contract/settings.ts +12 -7
- package/src/daemon/ipc-contract/shared.ts +9 -7
- package/src/daemon/ipc-contract/skills.ts +46 -26
- package/src/daemon/ipc-contract/subagents.ts +9 -9
- package/src/daemon/ipc-contract/surfaces.ts +0 -1
- package/src/daemon/ipc-contract/trust.ts +11 -11
- package/src/daemon/ipc-contract/work-items.ts +33 -28
- package/src/daemon/ipc-contract/workspace.ts +28 -21
- package/src/daemon/ipc-contract-inventory.json +10 -4
- package/src/daemon/ipc-contract-inventory.ts +29 -26
- package/src/daemon/ipc-contract.ts +111 -44
- package/src/daemon/ipc-handler.ts +27 -19
- package/src/daemon/ipc-protocol.ts +22 -12
- package/src/daemon/ipc-validate.ts +91 -46
- package/src/daemon/lifecycle.ts +39 -3
- package/src/daemon/main.ts +10 -8
- package/src/daemon/media-visibility-policy.ts +3 -1
- package/src/daemon/pairing-store.ts +72 -40
- package/src/daemon/providers-setup.ts +35 -25
- package/src/daemon/recording-executor.ts +37 -30
- package/src/daemon/recording-intent-fallback.ts +58 -28
- package/src/daemon/recording-intent.ts +71 -61
- package/src/daemon/ride-shotgun-handler.ts +201 -121
- package/src/daemon/seed-files.ts +28 -17
- package/src/daemon/server.ts +23 -14
- package/src/daemon/session-agent-loop-handlers.ts +270 -135
- package/src/daemon/session-agent-loop.ts +796 -253
- package/src/daemon/session-attachments.ts +109 -40
- package/src/daemon/session-conflict-gate.ts +72 -28
- package/src/daemon/session-dynamic-profile.ts +36 -22
- package/src/daemon/session-error.ts +68 -45
- package/src/daemon/session-evictor.ts +17 -10
- package/src/daemon/session-history.ts +201 -89
- package/src/daemon/session-lifecycle.ts +80 -44
- package/src/daemon/session-media-retry.ts +104 -42
- package/src/daemon/session-memory.ts +77 -55
- package/src/daemon/session-messaging.ts +261 -111
- package/src/daemon/session-notifiers.ts +57 -45
- package/src/daemon/session-process.ts +370 -154
- package/src/daemon/session-queue-manager.ts +30 -13
- package/src/daemon/session-runtime-assembly.ts +61 -15
- package/src/daemon/session-skill-tools.ts +84 -36
- package/src/daemon/session-slash.ts +178 -113
- package/src/daemon/session-surfaces.ts +498 -212
- package/src/daemon/session-tool-setup.ts +24 -16
- package/src/daemon/session-usage.ts +26 -13
- package/src/daemon/session-workspace.ts +7 -4
- package/src/daemon/session.ts +18 -19
- package/src/daemon/shutdown-handlers.ts +36 -33
- package/src/daemon/tls-certs.ts +90 -57
- package/src/daemon/tool-side-effects.ts +97 -65
- package/src/daemon/trace-emitter.ts +8 -7
- package/src/daemon/video-thumbnail.ts +55 -25
- package/src/daemon/watch-handler.ts +164 -86
- package/src/email/provider.ts +1 -1
- package/src/email/providers/agentmail.ts +87 -45
- package/src/email/providers/index.ts +19 -14
- package/src/email/service.ts +52 -24
- package/src/email/types.ts +2 -2
- package/src/errors.ts +1 -1
- package/src/events/bus.ts +30 -10
- package/src/events/domain-events.ts +20 -13
- package/src/events/index.ts +6 -6
- package/src/events/tool-audit-listener.ts +34 -20
- package/src/events/tool-domain-event-publisher.ts +22 -20
- package/src/events/tool-metrics-listener.ts +26 -21
- package/src/events/tool-notification-listener.ts +5 -5
- package/src/events/tool-profiling-listener.ts +33 -23
- package/src/events/tool-trace-listener.ts +70 -46
- package/src/export/formatter.ts +38 -32
- package/src/followups/followup-store.ts +43 -36
- package/src/followups/index.ts +2 -2
- package/src/followups/types.ts +1 -1
- package/src/gallery/default-gallery.ts +37 -34
- package/src/gallery/gallery-manifest.ts +9 -9
- package/src/heartbeat/heartbeat-service.ts +59 -37
- package/src/home-base/app-link-store.ts +14 -12
- package/src/home-base/bootstrap.ts +14 -8
- package/src/home-base/prebuilt/seed.ts +34 -26
- package/src/home-base/prebuilt-home-base-updater.ts +14 -8
- package/src/hooks/cli.ts +56 -43
- package/src/hooks/config.ts +27 -14
- package/src/hooks/discovery.ts +53 -33
- package/src/hooks/manager.ts +50 -26
- package/src/hooks/runner.ts +35 -29
- package/src/hooks/templates.ts +38 -15
- package/src/hooks/types.ts +13 -13
- package/src/inbound/platform-callback-registration.ts +21 -15
- package/src/inbound/public-ingress-urls.ts +9 -6
- package/src/index.ts +20 -19
- package/src/influencer/client.ts +261 -117
- package/src/instrument.ts +3 -1
- package/src/logfire.ts +64 -39
- package/src/mcp/client.ts +107 -55
- package/src/mcp/manager.ts +45 -18
- package/src/mcp/mcp-oauth-provider.ts +114 -62
- package/src/media/gemini-image-service.ts +75 -23
- package/src/memory/account-store.ts +16 -9
- package/src/memory/admin.ts +87 -57
- package/src/memory/app-git-service.ts +77 -47
- package/src/memory/app-store.ts +148 -78
- package/src/memory/attachments-store.ts +123 -53
- package/src/memory/canonical-guardian-store.ts +190 -48
- package/src/memory/channel-delivery-store.ts +5 -5
- package/src/memory/channel-guardian-store.ts +31 -16
- package/src/memory/checkpoints.ts +14 -7
- package/src/memory/clarification-resolver.ts +219 -104
- package/src/memory/conflict-intent.ts +74 -23
- package/src/memory/conflict-policy.ts +20 -7
- package/src/memory/conflict-store.ts +144 -94
- package/src/memory/contradiction-checker.ts +257 -132
- package/src/memory/conversation-attention-store.ts +74 -32
- package/src/memory/conversation-bootstrap.ts +28 -0
- package/src/memory/conversation-crud.ts +12 -5
- package/src/memory/conversation-display-order-migration.ts +7 -7
- package/src/memory/conversation-key-store.ts +18 -13
- package/src/memory/conversation-queries.ts +130 -52
- package/src/memory/conversation-store.ts +43 -26
- package/src/memory/conversation-title-service.ts +89 -66
- package/src/memory/db-init.ts +94 -2
- package/src/memory/db.ts +10 -3
- package/src/memory/delivery-channels.ts +12 -6
- package/src/memory/delivery-crud.ts +26 -12
- package/src/memory/delivery-status.ts +19 -16
- package/src/memory/embedding-backend.ts +205 -77
- package/src/memory/embedding-gemini.ts +23 -10
- package/src/memory/embedding-local.ts +89 -44
- package/src/memory/embedding-ollama.ts +25 -13
- package/src/memory/embedding-openai.ts +20 -11
- package/src/memory/embedding-runtime-manager.ts +163 -90
- package/src/memory/entity-extractor.ts +185 -123
- package/src/memory/external-conversation-store.ts +30 -12
- package/src/memory/fingerprint.ts +2 -2
- package/src/memory/fts-reconciler.ts +57 -28
- package/src/memory/guardian-action-store.ts +162 -100
- package/src/memory/guardian-approvals.ts +63 -129
- package/src/memory/guardian-rate-limits.ts +20 -9
- package/src/memory/guardian-verification.ts +82 -35
- package/src/memory/indexer.ts +96 -55
- package/src/memory/{ingress-invite-store.ts → invite-store.ts} +28 -169
- package/src/memory/items-extractor.ts +313 -157
- package/src/memory/job-handlers/backfill.ts +116 -63
- package/src/memory/job-handlers/cleanup.ts +64 -41
- package/src/memory/job-handlers/conflict.ts +90 -49
- package/src/memory/job-handlers/embedding.ts +32 -17
- package/src/memory/job-handlers/extraction.ts +58 -33
- package/src/memory/job-handlers/index-maintenance.ts +31 -17
- package/src/memory/job-handlers/media-processing.ts +65 -24
- package/src/memory/job-handlers/summarization.ts +186 -128
- package/src/memory/job-utils.ts +100 -57
- package/src/memory/jobs-store.ts +235 -142
- package/src/memory/jobs-worker.ts +167 -83
- package/src/memory/llm-request-log-store.ts +13 -11
- package/src/memory/llm-usage-store.ts +35 -26
- package/src/memory/media-store.ts +151 -44
- package/src/memory/message-content.ts +28 -18
- package/src/memory/migrations/001-job-deferrals.ts +11 -5
- package/src/memory/migrations/002-tool-invocations-fk.ts +14 -6
- package/src/memory/migrations/003-memory-fts-backfill.ts +11 -5
- package/src/memory/migrations/004-entity-relation-dedup.ts +17 -11
- package/src/memory/migrations/005-fingerprint-scope-unique.ts +36 -21
- package/src/memory/migrations/006-scope-salted-fingerprints.ts +35 -20
- package/src/memory/migrations/007-assistant-id-to-self.ts +40 -27
- package/src/memory/migrations/008-remove-assistant-id-columns.ts +58 -36
- package/src/memory/migrations/009-llm-usage-events-drop-assistant-id.ts +36 -22
- package/src/memory/migrations/010-ext-conv-bindings-channel-chat-unique.ts +21 -11
- package/src/memory/migrations/011-call-sessions-provider-sid-dedup.ts +30 -15
- package/src/memory/migrations/012-call-sessions-add-initiated-from.ts +4 -2
- package/src/memory/migrations/013-guardian-action-tables.ts +29 -11
- package/src/memory/migrations/014-backfill-inbox-thread-state.ts +35 -21
- package/src/memory/migrations/015-drop-active-search-index.ts +17 -11
- package/src/memory/migrations/016-memory-segments-indexes.ts +7 -3
- package/src/memory/migrations/017-memory-items-indexes.ts +4 -2
- package/src/memory/migrations/018-remaining-table-indexes.ts +13 -5
- package/src/memory/migrations/019-notification-tables-schema-migration.ts +34 -20
- package/src/memory/migrations/020-rename-macos-ios-channel-to-vellum.ts +87 -53
- package/src/memory/migrations/021-conversation-status-indexes.ts +7 -3
- package/src/memory/migrations/022-add-origin-interface.ts +4 -2
- package/src/memory/migrations/023-memory-item-sources-indexes.ts +4 -2
- package/src/memory/migrations/024-embedding-vector-blob.ts +34 -18
- package/src/memory/migrations/025-messages-fts-backfill.ts +11 -5
- package/src/memory/migrations/026-guardian-verification-sessions.ts +80 -14
- package/src/memory/migrations/026a-embeddings-nullable-vector-json.ts +42 -26
- package/src/memory/migrations/027-notification-delivery-pairing-columns.ts +22 -8
- package/src/memory/migrations/027a-guardian-bootstrap-token.ts +11 -3
- package/src/memory/migrations/028-call-session-mode.ts +13 -3
- package/src/memory/migrations/028-notification-delivery-client-ack.ts +22 -8
- package/src/memory/migrations/029-channel-inbound-delivered-segments.ts +7 -3
- package/src/memory/migrations/030-guardian-action-followup.ts +46 -8
- package/src/memory/migrations/030-guardian-verification-purpose.ts +4 -2
- package/src/memory/migrations/031-conversations-thread-type-index.ts +4 -2
- package/src/memory/migrations/032-guardian-delivery-conversation-index.ts +4 -2
- package/src/memory/migrations/032-notification-delivery-thread-decision.ts +22 -8
- package/src/memory/migrations/033-scoped-approval-grants.ts +1 -1
- package/src/memory/migrations/034-guardian-action-tool-metadata.ts +15 -3
- package/src/memory/migrations/035-guardian-action-supersession.ts +15 -3
- package/src/memory/migrations/036-normalize-phone-identities.ts +101 -87
- package/src/memory/migrations/037-voice-invite-columns.ts +22 -4
- package/src/memory/migrations/038-actor-token-records.ts +5 -9
- package/src/memory/migrations/039-actor-refresh-token-records.ts +7 -13
- package/src/memory/migrations/100-core-tables.ts +1 -1
- package/src/memory/migrations/101-watchers-and-logs.ts +1 -1
- package/src/memory/migrations/103-complex-migrations.ts +9 -9
- package/src/memory/migrations/104-core-indexes.ts +188 -64
- package/src/memory/migrations/105-contacts-and-triage.ts +28 -10
- package/src/memory/migrations/106-call-sessions.ts +58 -16
- package/src/memory/migrations/107-followups.ts +16 -6
- package/src/memory/migrations/108-tasks-and-work-items.ts +43 -11
- package/src/memory/migrations/109-external-conversation-bindings.ts +11 -5
- package/src/memory/migrations/110-channel-guardian.ts +48 -10
- package/src/memory/migrations/111-media-assets.ts +52 -18
- package/src/memory/migrations/112-assistant-inbox.ts +32 -12
- package/src/memory/migrations/113-late-migrations.ts +12 -12
- package/src/memory/migrations/114-notifications.ts +28 -12
- package/src/memory/migrations/115-sequences.ts +10 -4
- package/src/memory/migrations/116-messages-fts.ts +1 -1
- package/src/memory/migrations/117-conversation-attention.ts +16 -6
- package/src/memory/migrations/118-reminder-routing-intent.ts +7 -3
- package/src/memory/migrations/119-schema-indexes-and-columns.ts +35 -15
- package/src/memory/migrations/120-fk-cascade-rebuilds.ts +36 -17
- package/src/memory/migrations/121-canonical-guardian-requests.ts +25 -9
- package/src/memory/migrations/122-canonical-guardian-requester-chat-id.ts +11 -3
- package/src/memory/migrations/123-canonical-guardian-deliveries-destination-index.ts +4 -2
- package/src/memory/migrations/124-voice-invite-display-metadata.ts +15 -3
- package/src/memory/migrations/125-guardian-principal-id-columns.ts +22 -4
- package/src/memory/migrations/126-backfill-guardian-principal-id.ts +174 -126
- package/src/memory/migrations/127-guardian-principal-id-not-null.ts +58 -42
- package/src/memory/migrations/128-contacts-role-principal.ts +26 -0
- package/src/memory/migrations/129-contact-channels-access-fields.ts +105 -0
- package/src/memory/migrations/130-contact-channels-type-ext-chat-id-index.ts +15 -0
- package/src/memory/migrations/131-drop-legacy-member-guardian-tables.ts +134 -0
- package/src/memory/migrations/132-contacts-assistant-id.ts +21 -0
- package/src/memory/migrations/133-assistant-contact-metadata.ts +21 -0
- package/src/memory/migrations/index.ts +83 -73
- package/src/memory/migrations/registry.ts +53 -37
- package/src/memory/migrations/validate-migration-state.ts +73 -46
- package/src/memory/profile-compiler.ts +58 -24
- package/src/memory/published-pages-store.ts +12 -16
- package/src/memory/qdrant-circuit-breaker.ts +28 -20
- package/src/memory/qdrant-client.ts +99 -63
- package/src/memory/qdrant-manager.ts +89 -57
- package/src/memory/query-builder.ts +9 -7
- package/src/memory/raw-query.ts +63 -14
- package/src/memory/recall-cache.ts +15 -8
- package/src/memory/retrieval-budget.ts +0 -1
- package/src/memory/retriever.ts +385 -192
- package/src/memory/schema-migration.ts +1 -1
- package/src/memory/schema.ts +56 -56
- package/src/memory/scoped-approval-grants.ts +99 -45
- package/src/memory/search/entity.ts +102 -40
- package/src/memory/search/formatting.ts +70 -52
- package/src/memory/search/lexical.ts +82 -43
- package/src/memory/search/ranking.ts +103 -39
- package/src/memory/search/semantic.ts +59 -35
- package/src/memory/search/types.ts +8 -8
- package/src/memory/segmenter.ts +20 -12
- package/src/memory/shared-app-links-store.ts +21 -16
- package/src/memory/slack-thread-store.ts +187 -0
- package/src/memory/task-memory-cleanup.ts +18 -8
- package/src/memory/tool-usage-store.ts +27 -19
- package/src/memory/validation.ts +4 -2
- package/src/messaging/activity-analyzer.ts +7 -7
- package/src/messaging/draft-store.ts +13 -10
- package/src/messaging/email-classifier.ts +73 -37
- package/src/messaging/index.ts +3 -3
- package/src/messaging/outreach-classifier.ts +76 -38
- package/src/messaging/provider-types.ts +2 -4
- package/src/messaging/provider.ts +37 -8
- package/src/messaging/providers/gmail/adapter.ts +183 -66
- package/src/messaging/providers/gmail/client.ts +3 -1
- package/src/messaging/providers/gmail/mime-builder.ts +21 -19
- package/src/messaging/providers/gmail/people-client.ts +22 -9
- package/src/messaging/providers/gmail/types.ts +6 -6
- package/src/messaging/providers/slack/adapter.ts +93 -43
- package/src/messaging/providers/slack/client.ts +165 -48
- package/src/messaging/providers/slack/types.ts +10 -0
- package/src/messaging/providers/sms/adapter.ts +76 -40
- package/src/messaging/providers/sms/client.ts +4 -4
- package/src/messaging/providers/telegram-bot/adapter.ts +52 -30
- package/src/messaging/providers/telegram-bot/client.ts +7 -7
- package/src/messaging/providers/whatsapp/adapter.ts +58 -31
- package/src/messaging/providers/whatsapp/client.ts +4 -4
- package/src/messaging/registry.ts +9 -5
- package/src/messaging/style-analyzer.ts +69 -39
- package/src/messaging/thread-summarizer.ts +101 -53
- package/src/messaging/triage-engine.ts +111 -82
- package/src/messaging/types.ts +10 -10
- package/src/migrations/config-merge.ts +18 -10
- package/src/migrations/data-layout.ts +35 -22
- package/src/migrations/data-merge.ts +17 -7
- package/src/migrations/hooks-merge.ts +43 -16
- package/src/migrations/index.ts +6 -6
- package/src/migrations/log.ts +9 -5
- package/src/migrations/skills-merge.ts +17 -7
- package/src/migrations/workspace-layout.ts +39 -25
- package/src/notifications/AGENTS.md +5 -0
- package/src/notifications/adapters/macos.ts +21 -14
- package/src/notifications/adapters/slack.ts +90 -0
- package/src/notifications/adapters/sms.ts +28 -15
- package/src/notifications/adapters/telegram.ts +24 -15
- package/src/notifications/broadcaster.ts +108 -52
- package/src/notifications/conversation-pairing.ts +64 -29
- package/src/notifications/copy-composer.ts +165 -95
- package/src/notifications/decision-engine.ts +353 -147
- package/src/notifications/decisions-store.ts +26 -10
- package/src/notifications/deliveries-store.ts +23 -13
- package/src/notifications/destination-resolver.ts +83 -24
- package/src/notifications/deterministic-checks.ts +78 -27
- package/src/notifications/emit-signal.ts +95 -41
- package/src/notifications/events-store.ts +13 -7
- package/src/notifications/guardian-question-mode.ts +125 -75
- package/src/notifications/preference-extractor.ts +85 -53
- package/src/notifications/preference-summary.ts +31 -18
- package/src/notifications/preferences-store.ts +29 -18
- package/src/notifications/runtime-dispatch.ts +22 -12
- package/src/notifications/signal.ts +4 -4
- package/src/notifications/thread-candidates.ts +59 -23
- package/src/notifications/thread-seed-composer.ts +45 -27
- package/src/notifications/types.ts +19 -10
- package/src/oauth/connect-orchestrator.ts +105 -54
- package/src/oauth/connect-types.ts +3 -3
- package/src/oauth/provider-profiles.ts +102 -59
- package/src/oauth/scope-policy.ts +5 -2
- package/src/oauth/token-persistence.ts +58 -24
- package/src/outbound-proxy/certs.ts +284 -0
- package/src/outbound-proxy/config.ts +94 -0
- package/src/outbound-proxy/connect-tunnel.ts +84 -0
- package/src/outbound-proxy/health.ts +62 -0
- package/src/outbound-proxy/host-pattern-match.ts +67 -0
- package/src/outbound-proxy/http-forwarder.ts +162 -0
- package/src/outbound-proxy/index.ts +80 -0
- package/src/outbound-proxy/logging.ts +193 -0
- package/src/outbound-proxy/mitm-handler.ts +292 -0
- package/src/outbound-proxy/policy.ts +172 -0
- package/src/outbound-proxy/router.ts +64 -0
- package/src/outbound-proxy/server.ts +145 -0
- package/src/outbound-proxy/types.ts +150 -0
- package/src/permissions/checker.ts +481 -189
- package/src/permissions/defaults.ts +135 -108
- package/src/permissions/prompter.ts +53 -27
- package/src/permissions/secret-prompter.ts +21 -15
- package/src/permissions/shell-identity.ts +47 -16
- package/src/permissions/trust-store.ts +185 -73
- package/src/permissions/types.ts +22 -12
- package/src/permissions/workspace-policy.ts +47 -38
- package/src/playbooks/index.ts +10 -2
- package/src/playbooks/playbook-compiler.ts +30 -24
- package/src/playbooks/types.ts +11 -8
- package/src/providers/anthropic/client.ts +328 -168
- package/src/providers/failover.ts +57 -22
- package/src/providers/fireworks/client.ts +9 -5
- package/src/providers/gemini/client.ts +61 -39
- package/src/providers/model-intents.ts +40 -33
- package/src/providers/ollama/client.ts +7 -7
- package/src/providers/openai/client.ts +109 -68
- package/src/providers/openrouter/client.ts +9 -5
- package/src/providers/provider-send-message.ts +59 -27
- package/src/providers/ratelimit.ts +25 -8
- package/src/providers/registry.ts +86 -38
- package/src/providers/retry.ts +93 -37
- package/src/providers/stream-timeout.ts +5 -3
- package/src/providers/types.ts +7 -6
- package/src/runtime/AGENTS.md +42 -0
- package/src/runtime/access-request-helper.ts +118 -68
- package/src/runtime/actor-refresh-token-store.ts +21 -16
- package/src/runtime/actor-token-store.ts +25 -18
- package/src/runtime/actor-trust-resolver.ts +191 -80
- package/src/runtime/approval-conversation-turn.ts +39 -26
- package/src/runtime/approval-message-composer.ts +116 -84
- package/src/runtime/assistant-event-hub.ts +25 -6
- package/src/runtime/assistant-event.ts +4 -4
- package/src/runtime/assistant-scope.ts +1 -1
- package/src/runtime/auth/__tests__/guard-tests.test.ts +36 -14
- package/src/runtime/auth/context.ts +8 -7
- package/src/runtime/auth/credential-service.ts +60 -38
- package/src/runtime/auth/external-assistant-id.ts +16 -8
- package/src/runtime/auth/index.ts +23 -16
- package/src/runtime/auth/require-bound-guardian.ts +44 -0
- package/src/runtime/auth/route-policy.ts +166 -104
- package/src/runtime/auth/scopes.ts +22 -29
- package/src/runtime/auth/subject.ts +19 -13
- package/src/runtime/auth/token-service.ts +3 -3
- package/src/runtime/auth/types.ts +23 -23
- package/src/runtime/channel-approval-parser.ts +37 -14
- package/src/runtime/channel-approval-types.ts +30 -4
- package/src/runtime/channel-approvals.ts +49 -23
- package/src/runtime/channel-guardian-service.ts +144 -103
- package/src/runtime/channel-invite-transport.ts +5 -3
- package/src/runtime/channel-invite-transports/telegram.ts +16 -10
- package/src/runtime/channel-invite-transports/voice.ts +7 -7
- package/src/runtime/channel-readiness-service.ts +139 -90
- package/src/runtime/channel-readiness-types.ts +4 -2
- package/src/runtime/channel-reply-delivery.ts +83 -14
- package/src/runtime/channel-retry-sweep.ts +111 -62
- package/src/runtime/confirmation-request-guardian-bridge.ts +73 -54
- package/src/runtime/gateway-client.ts +122 -55
- package/src/runtime/gateway-internal-client.ts +86 -0
- package/src/runtime/guardian-action-conversation-turn.ts +34 -18
- package/src/runtime/guardian-action-followup-executor.ts +115 -45
- package/src/runtime/guardian-action-grant-minter.ts +40 -24
- package/src/runtime/guardian-action-message-composer.ts +105 -84
- package/src/runtime/guardian-action-service.ts +127 -0
- package/src/runtime/guardian-decision-types.ts +28 -13
- package/src/runtime/guardian-outbound-actions.ts +9 -0
- package/src/runtime/guardian-reply-router.ts +274 -145
- package/src/runtime/guardian-vellum-migration.ts +38 -24
- package/src/runtime/guardian-verification-templates.ts +24 -12
- package/src/runtime/http-router.ts +175 -0
- package/src/runtime/http-server.ts +913 -680
- package/src/runtime/http-types.ts +2 -2
- package/src/runtime/invite-redemption-service.ts +211 -134
- package/src/runtime/invite-redemption-templates.ts +18 -11
- package/src/runtime/{ingress-service.ts → invite-service.ts} +92 -151
- package/src/runtime/local-actor-identity.ts +73 -55
- package/src/runtime/middleware/auth.ts +25 -14
- package/src/runtime/middleware/error-handler.ts +15 -11
- package/src/runtime/middleware/rate-limiter.ts +23 -17
- package/src/runtime/middleware/request-logger.ts +4 -4
- package/src/runtime/middleware/twilio-validation.ts +29 -20
- package/src/runtime/migrations/migration-transport.ts +575 -0
- package/src/runtime/migrations/migration-wizard.ts +715 -0
- package/src/runtime/migrations/rebind-secrets-screen.ts +351 -0
- package/src/runtime/migrations/transfer-progress-screen.ts +321 -0
- package/src/runtime/migrations/validation-results-screen.ts +467 -0
- package/src/runtime/migrations/vbundle-builder.ts +295 -0
- package/src/runtime/migrations/vbundle-import-analyzer.ts +212 -0
- package/src/runtime/migrations/vbundle-importer.ts +339 -0
- package/src/runtime/migrations/vbundle-validator.ts +356 -0
- package/src/runtime/nl-approval-parser.ts +138 -0
- package/src/runtime/pending-interactions.ts +16 -7
- package/src/runtime/routes/access-request-decision.ts +73 -52
- package/src/runtime/routes/app-routes.ts +56 -38
- package/src/runtime/routes/approval-routes.ts +144 -92
- package/src/runtime/routes/approval-strategies/guardian-callback-strategy.ts +930 -0
- package/src/runtime/routes/approval-strategies/guardian-legacy-fallback-strategy.ts +82 -0
- package/src/runtime/routes/approval-strategies/guardian-text-engine-strategy.ts +151 -0
- package/src/runtime/routes/attachment-routes.ts +59 -48
- package/src/runtime/routes/brain-graph-routes.ts +85 -69
- package/src/runtime/routes/call-routes.ts +79 -38
- package/src/runtime/routes/canonical-guardian-expiry-sweep.ts +10 -10
- package/src/runtime/routes/channel-delivery-routes.ts +19 -14
- package/src/runtime/routes/channel-guardian-routes.ts +3 -3
- package/src/runtime/routes/channel-inbound-routes.ts +2 -2
- package/src/runtime/routes/channel-readiness-routes.ts +12 -6
- package/src/runtime/routes/channel-route-shared.ts +67 -25
- package/src/runtime/routes/channel-routes.ts +4 -6
- package/src/runtime/routes/contact-routes.ts +374 -17
- package/src/runtime/routes/conversation-attention-routes.ts +57 -28
- package/src/runtime/routes/conversation-routes.ts +321 -174
- package/src/runtime/routes/debug-routes.ts +14 -10
- package/src/runtime/routes/events-routes.ts +90 -57
- package/src/runtime/routes/global-search-routes.ts +266 -0
- package/src/runtime/routes/guardian-action-routes.ts +112 -113
- package/src/runtime/routes/guardian-approval-interception.ts +325 -874
- package/src/runtime/routes/guardian-approval-prompt.ts +40 -24
- package/src/runtime/routes/guardian-approval-reply-helpers.ts +135 -0
- package/src/runtime/routes/guardian-bootstrap-routes.ts +55 -36
- package/src/runtime/routes/guardian-expiry-sweep.ts +63 -37
- package/src/runtime/routes/guardian-refresh-routes.ts +40 -19
- package/src/runtime/routes/identity-routes.ts +71 -42
- package/src/runtime/routes/inbound-conversation.ts +17 -11
- package/src/runtime/routes/inbound-message-handler.ts +305 -1459
- package/src/runtime/routes/inbound-stages/acl-enforcement.ts +880 -0
- package/src/runtime/routes/inbound-stages/background-dispatch.ts +600 -0
- package/src/runtime/routes/inbound-stages/bootstrap-intercept.ts +214 -0
- package/src/runtime/routes/inbound-stages/edit-intercept.ts +116 -0
- package/src/runtime/routes/inbound-stages/escalation-intercept.ts +167 -0
- package/src/runtime/routes/inbound-stages/guardian-reply-intercept.ts +185 -0
- package/src/runtime/routes/inbound-stages/secret-ingress-check.ts +132 -0
- package/src/runtime/routes/inbound-stages/verification-intercept.ts +340 -0
- package/src/runtime/routes/integration-routes.ts +60 -21
- package/src/runtime/routes/invite-routes.ts +140 -0
- package/src/runtime/routes/migration-routes.ts +434 -0
- package/src/runtime/routes/pairing-routes.ts +157 -79
- package/src/runtime/routes/secret-routes.ts +6 -2
- package/src/runtime/routes/twilio-routes.ts +443 -249
- package/src/runtime/slack-block-formatting.ts +176 -0
- package/src/runtime/tool-grant-request-helper.ts +36 -27
- package/src/runtime/{guardian-context-resolver.ts → trust-context-resolver.ts} +29 -41
- package/src/schedule/integration-status.ts +44 -9
- package/src/schedule/recurrence-engine.ts +47 -24
- package/src/schedule/recurrence-types.ts +12 -7
- package/src/schedule/schedule-store.ts +166 -83
- package/src/schedule/scheduler.ts +37 -24
- package/src/security/encrypted-store.ts +68 -38
- package/src/security/keychain.ts +183 -120
- package/src/security/oauth-callback-registry.ts +3 -3
- package/src/security/oauth2.ts +226 -138
- package/src/security/redaction.ts +24 -24
- package/src/security/secret-allowlist.ts +46 -21
- package/src/security/secret-ingress.ts +15 -7
- package/src/security/secret-scanner.ts +193 -104
- package/src/security/secure-keys.ts +9 -3
- package/src/security/token-manager.ts +99 -40
- package/src/security/tool-approval-digest.ts +3 -3
- package/src/sequence/analytics.ts +52 -27
- package/src/sequence/engine.ts +135 -72
- package/src/sequence/guardrails.ts +32 -20
- package/src/sequence/importer.ts +75 -37
- package/src/sequence/reply-matcher.ts +36 -18
- package/src/sequence/store.ts +137 -75
- package/src/sequence/types.ts +30 -16
- package/src/services/published-app-updater.ts +26 -16
- package/src/services/vercel-deploy.ts +19 -15
- package/src/skills/active-skill-tools.ts +3 -3
- package/src/skills/clawhub.ts +178 -90
- package/src/skills/include-graph.ts +24 -17
- package/src/skills/managed-store.ts +89 -42
- package/src/skills/path-classifier.ts +10 -10
- package/src/skills/remote-skill-policy.ts +31 -22
- package/src/skills/slash-commands.ts +36 -30
- package/src/skills/tool-manifest.ts +60 -31
- package/src/skills/version-hash.ts +25 -15
- package/src/slack/slack-webhook.ts +19 -15
- package/src/subagent/index.ts +4 -8
- package/src/subagent/manager.ts +119 -69
- package/src/subagent/types.ts +9 -12
- package/src/swarm/backend-claude-code.ts +124 -45
- package/src/swarm/checkpoint.ts +36 -16
- package/src/swarm/graph-utils.ts +1 -3
- package/src/swarm/index.ts +38 -19
- package/src/swarm/limits.ts +13 -4
- package/src/swarm/orchestrator.ts +108 -57
- package/src/swarm/plan-validator.ts +23 -17
- package/src/swarm/router-planner.ts +51 -22
- package/src/swarm/router-prompts.ts +4 -1
- package/src/swarm/synthesizer.ts +26 -18
- package/src/swarm/types.ts +14 -4
- package/src/swarm/worker-backend.ts +36 -26
- package/src/swarm/worker-prompts.ts +13 -9
- package/src/swarm/worker-runner.ts +40 -34
- package/src/tasks/candidate-store.ts +14 -6
- package/src/tasks/ephemeral-permissions.ts +9 -5
- package/src/tasks/task-compiler.ts +41 -38
- package/src/tasks/task-runner.ts +54 -26
- package/src/tasks/task-scheduler.ts +1 -1
- package/src/tasks/task-store.ts +20 -7
- package/src/tasks/tool-sanitizer.ts +3 -3
- package/src/tools/apps/definitions.ts +23 -15
- package/src/tools/apps/executors.ts +122 -40
- package/src/tools/apps/open-proxy.ts +5 -5
- package/src/tools/apps/registry.ts +2 -2
- package/src/tools/assets/materialize.ts +59 -41
- package/src/tools/assets/search.ts +86 -48
- package/src/tools/browser/api-map.ts +52 -36
- package/src/tools/browser/auth-cache.ts +21 -18
- package/src/tools/browser/auth-detector.ts +43 -28
- package/src/tools/browser/auto-navigate.ts +149 -68
- package/src/tools/browser/browser-execution.ts +9 -3
- package/src/tools/browser/headless-browser.ts +287 -150
- package/src/tools/browser/jit-auth.ts +37 -21
- package/src/tools/browser/network-recorder.ts +138 -56
- package/src/tools/browser/recording-store.ts +22 -15
- package/src/tools/browser/runtime-check.ts +8 -5
- package/src/tools/browser/x-auto-navigate.ts +88 -47
- package/src/tools/calls/call-end.ts +10 -7
- package/src/tools/calls/call-start.ts +30 -20
- package/src/tools/calls/call-status.ts +8 -5
- package/src/tools/claude-code/claude-code.ts +301 -165
- package/src/tools/computer-use/definitions.ts +175 -130
- package/src/tools/computer-use/registry.ts +2 -2
- package/src/tools/computer-use/request-computer-control.ts +21 -13
- package/src/tools/computer-use/skill-proxy-bridge.ts +1 -1
- package/src/tools/credentials/account-registry.ts +52 -35
- package/src/tools/credentials/broker-types.ts +1 -1
- package/src/tools/credentials/broker.ts +97 -55
- package/src/tools/credentials/domain-policy.ts +5 -2
- package/src/tools/credentials/host-pattern-match.ts +15 -8
- package/src/tools/credentials/metadata-store.ts +93 -43
- package/src/tools/credentials/policy-types.ts +5 -2
- package/src/tools/credentials/policy-validate.ts +21 -14
- package/src/tools/credentials/post-connect-hooks.ts +18 -7
- package/src/tools/credentials/resolve.ts +11 -10
- package/src/tools/credentials/selection.ts +30 -25
- package/src/tools/credentials/tool-policy.ts +5 -2
- package/src/tools/credentials/vault.ts +538 -185
- package/src/tools/document/document-tool.ts +23 -17
- package/src/tools/document/editor-template.ts +12 -7
- package/src/tools/execution-target.ts +13 -10
- package/src/tools/execution-timeout.ts +6 -5
- package/src/tools/executor.ts +141 -74
- package/src/tools/filesystem/edit.ts +82 -45
- package/src/tools/filesystem/fuzzy-match.ts +70 -32
- package/src/tools/filesystem/read.ts +46 -28
- package/src/tools/filesystem/view-image.ts +86 -42
- package/src/tools/filesystem/write.ts +53 -32
- package/src/tools/followups/followup_create.ts +43 -17
- package/src/tools/followups/followup_list.ts +28 -13
- package/src/tools/followups/followup_resolve.ts +9 -6
- package/src/tools/guardian-control-plane-policy.ts +15 -14
- package/src/tools/host-filesystem/edit.ts +77 -42
- package/src/tools/host-filesystem/read.ts +52 -33
- package/src/tools/host-filesystem/write.ts +50 -29
- package/src/tools/host-terminal/host-shell.ts +97 -61
- package/src/tools/mcp/mcp-tool-factory.ts +21 -14
- package/src/tools/memory/definitions.ts +60 -28
- package/src/tools/memory/handlers.ts +149 -77
- package/src/tools/memory/register.ts +39 -16
- package/src/tools/network/__tests__/web-search.test.ts +236 -177
- package/src/tools/network/domain-normalize.ts +13 -9
- package/src/tools/network/script-proxy/__tests__/logging.test.ts +193 -123
- package/src/tools/network/script-proxy/__tests__/policy.test.ts +225 -127
- package/src/tools/network/script-proxy/index.ts +1 -17
- package/src/tools/network/script-proxy/session-manager.ts +178 -86
- package/src/tools/network/url-safety.ts +56 -34
- package/src/tools/network/web-fetch.ts +273 -155
- package/src/tools/network/web-search.ts +166 -81
- package/src/tools/permission-checker.ts +24 -25
- package/src/tools/policy-context.ts +8 -5
- package/src/tools/registry.ts +73 -46
- package/src/tools/reminder/reminder-store.ts +65 -44
- package/src/tools/reminder/reminder.ts +76 -35
- package/src/tools/schedule/create.ts +44 -21
- package/src/tools/schedule/delete.ts +8 -5
- package/src/tools/schedule/list.ts +39 -19
- package/src/tools/schedule/update.ts +49 -26
- package/src/tools/secret-detection-handler.ts +130 -49
- package/src/tools/sensitive-output-placeholders.ts +15 -8
- package/src/tools/shared/filesystem/edit-engine.ts +45 -14
- package/src/tools/shared/filesystem/errors.ts +18 -18
- package/src/tools/shared/filesystem/file-ops-service.ts +59 -32
- package/src/tools/shared/filesystem/format-diff.ts +21 -11
- package/src/tools/shared/filesystem/path-policy.ts +17 -13
- package/src/tools/shared/filesystem/size-guard.ts +8 -4
- package/src/tools/shared/filesystem/types.ts +2 -2
- package/src/tools/shared/shell-output.ts +4 -3
- package/src/tools/side-effects.ts +36 -28
- package/src/tools/skills/delete-managed.ts +30 -17
- package/src/tools/skills/load.ts +88 -46
- package/src/tools/skills/sandbox-runner.ts +62 -46
- package/src/tools/skills/scaffold-managed.ts +98 -48
- package/src/tools/skills/script-contract.ts +5 -2
- package/src/tools/skills/skill-script-runner.ts +29 -13
- package/src/tools/skills/skill-tool-factory.ts +20 -10
- package/src/tools/subagent/abort.ts +10 -4
- package/src/tools/subagent/message.ts +14 -8
- package/src/tools/subagent/read.ts +20 -11
- package/src/tools/subagent/spawn.ts +14 -6
- package/src/tools/subagent/status.ts +7 -4
- package/src/tools/swarm/delegate.ts +75 -49
- package/src/tools/system/avatar-generator.ts +46 -33
- package/src/tools/system/navigate-settings.ts +29 -19
- package/src/tools/system/open-system-settings.ts +30 -20
- package/src/tools/system/request-permission.ts +59 -44
- package/src/tools/system/version.ts +27 -16
- package/src/tools/system/voice-config.ts +116 -53
- package/src/tools/tasks/index.ts +8 -8
- package/src/tools/tasks/task-delete.ts +61 -22
- package/src/tools/tasks/task-list.ts +23 -11
- package/src/tools/tasks/task-run.ts +41 -16
- package/src/tools/tasks/task-save.ts +27 -10
- package/src/tools/tasks/work-item-enqueue.ts +114 -48
- package/src/tools/tasks/work-item-list.ts +20 -10
- package/src/tools/tasks/work-item-remove.ts +49 -15
- package/src/tools/tasks/work-item-run.ts +34 -13
- package/src/tools/tasks/work-item-update.ts +84 -31
- package/src/tools/terminal/backends/native.ts +64 -35
- package/src/tools/terminal/backends/types.ts +6 -2
- package/src/tools/terminal/parser.ts +200 -125
- package/src/tools/terminal/safe-env.ts +27 -21
- package/src/tools/terminal/sandbox-diagnostics.ts +31 -13
- package/src/tools/terminal/sandbox.ts +10 -6
- package/src/tools/terminal/shell.ts +134 -68
- package/src/tools/tool-approval-handler.ts +239 -140
- package/src/tools/types.ts +79 -22
- package/src/tools/ui-surface/definitions.ts +124 -89
- package/src/tools/ui-surface/registry.ts +2 -2
- package/src/tools/watch/screen-watch.ts +50 -32
- package/src/tools/watch/watch-state.ts +41 -15
- package/src/tools/watcher/create.ts +37 -15
- package/src/tools/watcher/delete.ts +9 -6
- package/src/tools/watcher/digest.ts +10 -6
- package/src/tools/watcher/list.ts +37 -14
- package/src/tools/watcher/update.ts +33 -18
- package/src/tools/weather/service.ts +331 -174
- package/src/twitter/client.ts +261 -138
- package/src/twitter/oauth-client.ts +17 -13
- package/src/twitter/router.ts +51 -23
- package/src/twitter/session.ts +27 -18
- package/src/types/qrcode.d.ts +6 -3
- package/src/usage/actors.ts +16 -16
- package/src/usage/types.ts +3 -3
- package/src/util/bundled-asset.ts +10 -6
- package/src/util/canonicalize-identity.ts +11 -4
- package/src/util/clipboard.ts +7 -7
- package/src/util/content-id.ts +3 -3
- package/src/util/debounce.ts +3 -2
- package/src/util/diff.ts +55 -33
- package/src/util/errors.ts +31 -27
- package/src/util/fs.ts +8 -2
- package/src/util/log-redact.ts +12 -12
- package/src/util/logger.ts +112 -51
- package/src/util/network-info.ts +13 -5
- package/src/util/object.ts +4 -2
- package/src/util/phone.ts +4 -4
- package/src/util/platform.ts +80 -58
- package/src/util/pricing.ts +49 -31
- package/src/util/retry.ts +39 -7
- package/src/util/row-mapper.ts +7 -4
- package/src/util/silently.ts +7 -4
- package/src/util/spawn.ts +48 -0
- package/src/util/spinner.ts +9 -7
- package/src/util/time.ts +16 -3
- package/src/util/truncate.ts +1 -1
- package/src/util/voice-code.ts +6 -4
- package/src/util/xml.ts +5 -1
- package/src/version.ts +12 -8
- package/src/watcher/engine.ts +71 -44
- package/src/watcher/provider-registry.ts +1 -1
- package/src/watcher/providers/github.ts +40 -23
- package/src/watcher/providers/gmail.ts +59 -38
- package/src/watcher/providers/google-calendar.ts +62 -48
- package/src/watcher/providers/linear.ts +219 -150
- package/src/watcher/providers/slack.ts +125 -29
- package/src/watcher/watcher-store.ts +75 -55
- package/src/work-items/work-item-runner.ts +62 -29
- package/src/work-items/work-item-store.ts +137 -47
- package/src/workspace/commit-message-enrichment-service.ts +65 -25
- package/src/workspace/commit-message-provider.ts +14 -12
- package/src/workspace/git-service.ts +355 -239
- package/src/workspace/heartbeat-service.ts +74 -37
- package/src/workspace/provider-commit-message-generator.ts +95 -70
- package/src/workspace/top-level-renderer.ts +10 -8
- package/src/workspace/top-level-scanner.ts +9 -3
- package/src/workspace/turn-commit.ts +63 -36
- package/src/__tests__/ingress-member-store.test.ts +0 -294
- package/src/__tests__/script-proxy-router.test.ts +0 -215
- package/src/config/bundled-skills/trusted-contacts/SKILL.md +0 -372
- package/src/memory/guardian-bindings.ts +0 -158
- package/src/memory/ingress-member-store.ts +0 -352
- package/src/runtime/routes/ingress-routes.ts +0 -229
- package/src/tools/network/script-proxy/__tests__/router.test.ts +0 -77
- package/src/tools/network/script-proxy/certs.ts +0 -7
- package/src/tools/network/script-proxy/connect-tunnel.ts +0 -1
- package/src/tools/network/script-proxy/http-forwarder.ts +0 -2
- package/src/tools/network/script-proxy/logging.ts +0 -12
- package/src/tools/network/script-proxy/mitm-handler.ts +0 -2
- package/src/tools/network/script-proxy/policy.ts +0 -4
- package/src/tools/network/script-proxy/router.ts +0 -2
- package/src/tools/network/script-proxy/server.ts +0 -5
- package/src/tools/network/script-proxy/types.ts +0 -19
|
@@ -4,94 +4,50 @@
|
|
|
4
4
|
* verification, guardian action answers, approval interception, and
|
|
5
5
|
* invite token redemption.
|
|
6
6
|
*/
|
|
7
|
-
// Side-effect import: registers the Telegram invite transport adapter so
|
|
8
|
-
// getTransport('telegram') resolves at runtime.
|
|
9
|
-
import type { ChannelId, InterfaceId } from '../../channels/types.js';
|
|
10
|
-
import { CHANNEL_IDS, INTERFACE_IDS, isChannelId, parseInterfaceId } from '../../channels/types.js';
|
|
11
|
-
import { getGatewayInternalBaseUrl } from '../../config/env.js';
|
|
12
|
-
import { resolveUserReference } from '../../config/user-reference.js';
|
|
13
|
-
import { RESEND_COOLDOWN_MS } from '../../daemon/handlers/config-channels.js';
|
|
14
|
-
import * as attachmentsStore from '../../memory/attachments-store.js';
|
|
15
7
|
import {
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
import {
|
|
22
|
-
import
|
|
23
|
-
import * as
|
|
24
|
-
import
|
|
25
|
-
import { emitNotificationSignal } from '../../notifications/emit-signal.js';
|
|
26
|
-
import { checkIngressForSecrets } from '../../security/secret-ingress.js';
|
|
27
|
-
import { canonicalizeInboundIdentity } from '../../util/canonicalize-identity.js';
|
|
28
|
-
import { IngressBlockedError } from '../../util/errors.js';
|
|
29
|
-
import { getLogger } from '../../util/logger.js';
|
|
30
|
-
import { notifyGuardianOfAccessRequest } from '../access-request-helper.js';
|
|
31
|
-
import { DAEMON_INTERNAL_ASSISTANT_ID } from '../assistant-scope.js';
|
|
32
|
-
import { mintDaemonDeliveryToken } from '../auth/token-service.js';
|
|
8
|
+
CHANNEL_IDS,
|
|
9
|
+
INTERFACE_IDS,
|
|
10
|
+
isChannelId,
|
|
11
|
+
parseInterfaceId,
|
|
12
|
+
} from "../../channels/types.js";
|
|
13
|
+
import { getChannelPermissionProfile } from "../../config/channel-permission-profiles.js";
|
|
14
|
+
import type { TrustContext } from "../../daemon/session-runtime-assembly.js";
|
|
15
|
+
import * as attachmentsStore from "../../memory/attachments-store.js";
|
|
16
|
+
import * as channelDeliveryStore from "../../memory/channel-delivery-store.js";
|
|
33
17
|
import {
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
import {
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
resolveBootstrapToken,
|
|
45
|
-
updateSessionDelivery,
|
|
46
|
-
updateSessionStatus,
|
|
47
|
-
validateAndConsumeChallenge,
|
|
48
|
-
} from '../channel-guardian-service.js';
|
|
49
|
-
import { getTransport } from '../channel-invite-transport.js';
|
|
50
|
-
import { deliverChannelReply } from '../gateway-client.js';
|
|
51
|
-
import { resolveGuardianContext, resolveRoutingState } from '../guardian-context-resolver.js';
|
|
52
|
-
import { routeGuardianReply } from '../guardian-reply-router.js';
|
|
53
|
-
import {
|
|
54
|
-
composeChannelVerifyReply,
|
|
55
|
-
composeVerificationTelegram,
|
|
56
|
-
GUARDIAN_VERIFY_TEMPLATE_KEYS,
|
|
57
|
-
} from '../guardian-verification-templates.js';
|
|
58
|
-
import { httpError } from '../http-errors.js';
|
|
18
|
+
recordConversationSeenSignal,
|
|
19
|
+
type SignalType,
|
|
20
|
+
} from "../../memory/conversation-attention-store.js";
|
|
21
|
+
import * as externalConversationStore from "../../memory/external-conversation-store.js";
|
|
22
|
+
import { canonicalizeInboundIdentity } from "../../util/canonicalize-identity.js";
|
|
23
|
+
import { getLogger } from "../../util/logger.js";
|
|
24
|
+
import { DAEMON_INTERNAL_ASSISTANT_ID } from "../assistant-scope.js";
|
|
25
|
+
import { mintDaemonDeliveryToken } from "../auth/token-service.js";
|
|
26
|
+
import { deliverChannelReply } from "../gateway-client.js";
|
|
27
|
+
import { httpError } from "../http-errors.js";
|
|
59
28
|
import type {
|
|
60
29
|
ApprovalConversationGenerator,
|
|
61
30
|
ApprovalCopyGenerator,
|
|
62
31
|
GuardianActionCopyGenerator,
|
|
63
32
|
GuardianFollowUpConversationGenerator,
|
|
64
33
|
MessageProcessor,
|
|
65
|
-
} from
|
|
66
|
-
import {
|
|
67
|
-
import {
|
|
68
|
-
import {
|
|
69
|
-
import {
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
} from
|
|
75
|
-
import {
|
|
76
|
-
import {
|
|
77
|
-
|
|
78
|
-
import
|
|
79
|
-
import
|
|
80
|
-
|
|
81
|
-
const log = getLogger(
|
|
82
|
-
|
|
83
|
-
/**
|
|
84
|
-
* Parse a guardian verification code from message content.
|
|
85
|
-
* Accepts a bare code as the entire message: 6-digit numeric OR 64-char hex
|
|
86
|
-
* (hex is retained for compatibility with unbound inbound/bootstrap sessions
|
|
87
|
-
* that intentionally use high-entropy secrets).
|
|
88
|
-
*/
|
|
89
|
-
function parseGuardianVerifyCode(content: string): string | undefined {
|
|
90
|
-
const bareMatch = content.match(/^([0-9a-fA-F]{64}|\d{6})$/);
|
|
91
|
-
if (bareMatch) return bareMatch[1];
|
|
92
|
-
|
|
93
|
-
return undefined;
|
|
94
|
-
}
|
|
34
|
+
} from "../http-types.js";
|
|
35
|
+
import { resolveTrustContext } from "../trust-context-resolver.js";
|
|
36
|
+
import { canonicalChannelAssistantId } from "./channel-route-shared.js";
|
|
37
|
+
import { handleApprovalInterception } from "./guardian-approval-interception.js";
|
|
38
|
+
import { enforceIngressAcl } from "./inbound-stages/acl-enforcement.js";
|
|
39
|
+
import { processChannelMessageInBackground } from "./inbound-stages/background-dispatch.js";
|
|
40
|
+
import { handleBootstrapIntercept } from "./inbound-stages/bootstrap-intercept.js";
|
|
41
|
+
import { handleEditIntercept } from "./inbound-stages/edit-intercept.js";
|
|
42
|
+
import { handleEscalationIntercept } from "./inbound-stages/escalation-intercept.js";
|
|
43
|
+
import { handleGuardianReplyIntercept } from "./inbound-stages/guardian-reply-intercept.js";
|
|
44
|
+
import { runSecretIngressCheck } from "./inbound-stages/secret-ingress-check.js";
|
|
45
|
+
import { handleVerificationIntercept } from "./inbound-stages/verification-intercept.js";
|
|
46
|
+
|
|
47
|
+
import "../channel-invite-transports/telegram.js";
|
|
48
|
+
import "../channel-invite-transports/voice.js";
|
|
49
|
+
|
|
50
|
+
const log = getLogger("runtime-http");
|
|
95
51
|
|
|
96
52
|
export async function handleChannelInbound(
|
|
97
53
|
req: Request,
|
|
@@ -113,7 +69,7 @@ export async function handleChannelInbound(
|
|
|
113
69
|
// a single token from request start.
|
|
114
70
|
const mintBearerToken = (): string => mintDaemonDeliveryToken();
|
|
115
71
|
|
|
116
|
-
const body = await req.json() as {
|
|
72
|
+
const body = (await req.json()) as {
|
|
117
73
|
sourceChannel?: string;
|
|
118
74
|
interface?: string;
|
|
119
75
|
conversationExternalId?: string;
|
|
@@ -139,63 +95,92 @@ export async function handleChannelInbound(
|
|
|
139
95
|
sourceMetadata,
|
|
140
96
|
} = body;
|
|
141
97
|
|
|
142
|
-
if (!body.sourceChannel || typeof body.sourceChannel !==
|
|
143
|
-
return httpError(
|
|
98
|
+
if (!body.sourceChannel || typeof body.sourceChannel !== "string") {
|
|
99
|
+
return httpError("BAD_REQUEST", "sourceChannel is required", 400);
|
|
144
100
|
}
|
|
145
101
|
// Validate and narrow to canonical ChannelId at the boundary — the gateway
|
|
146
102
|
// only sends well-known channel strings, so an unknown value is rejected.
|
|
147
103
|
if (!isChannelId(body.sourceChannel)) {
|
|
148
|
-
return httpError(
|
|
104
|
+
return httpError(
|
|
105
|
+
"BAD_REQUEST",
|
|
106
|
+
`Invalid sourceChannel: ${
|
|
107
|
+
body.sourceChannel
|
|
108
|
+
}. Valid values: ${CHANNEL_IDS.join(", ")}`,
|
|
109
|
+
400,
|
|
110
|
+
);
|
|
149
111
|
}
|
|
150
112
|
|
|
151
113
|
const sourceChannel = body.sourceChannel;
|
|
152
114
|
|
|
153
|
-
if (!body.interface || typeof body.interface !==
|
|
154
|
-
return httpError(
|
|
115
|
+
if (!body.interface || typeof body.interface !== "string") {
|
|
116
|
+
return httpError("BAD_REQUEST", "interface is required", 400);
|
|
155
117
|
}
|
|
156
118
|
const sourceInterface = parseInterfaceId(body.interface);
|
|
157
119
|
if (!sourceInterface) {
|
|
158
|
-
return httpError(
|
|
120
|
+
return httpError(
|
|
121
|
+
"BAD_REQUEST",
|
|
122
|
+
`Invalid interface: ${body.interface}. Valid values: ${INTERFACE_IDS.join(
|
|
123
|
+
", ",
|
|
124
|
+
)}`,
|
|
125
|
+
400,
|
|
126
|
+
);
|
|
159
127
|
}
|
|
160
128
|
|
|
161
|
-
if (!conversationExternalId || typeof conversationExternalId !==
|
|
162
|
-
return httpError(
|
|
129
|
+
if (!conversationExternalId || typeof conversationExternalId !== "string") {
|
|
130
|
+
return httpError("BAD_REQUEST", "conversationExternalId is required", 400);
|
|
163
131
|
}
|
|
164
|
-
if (
|
|
165
|
-
|
|
132
|
+
if (
|
|
133
|
+
!body.actorExternalId ||
|
|
134
|
+
typeof body.actorExternalId !== "string" ||
|
|
135
|
+
!body.actorExternalId.trim()
|
|
136
|
+
) {
|
|
137
|
+
return httpError("BAD_REQUEST", "actorExternalId is required", 400);
|
|
166
138
|
}
|
|
167
|
-
if (!externalMessageId || typeof externalMessageId !==
|
|
168
|
-
return httpError(
|
|
139
|
+
if (!externalMessageId || typeof externalMessageId !== "string") {
|
|
140
|
+
return httpError("BAD_REQUEST", "externalMessageId is required", 400);
|
|
169
141
|
}
|
|
170
142
|
|
|
171
143
|
// Reject non-string content regardless of whether attachments are present.
|
|
172
|
-
if (content != null && typeof content !==
|
|
173
|
-
return httpError(
|
|
144
|
+
if (content != null && typeof content !== "string") {
|
|
145
|
+
return httpError("BAD_REQUEST", "content must be a string", 400);
|
|
174
146
|
}
|
|
175
147
|
|
|
176
|
-
const trimmedContent = typeof content ===
|
|
177
|
-
const hasAttachments =
|
|
148
|
+
const trimmedContent = typeof content === "string" ? content.trim() : "";
|
|
149
|
+
const hasAttachments =
|
|
150
|
+
Array.isArray(attachmentIds) && attachmentIds.length > 0;
|
|
178
151
|
|
|
179
|
-
const hasCallbackData =
|
|
152
|
+
const hasCallbackData =
|
|
153
|
+
typeof body.callbackData === "string" && body.callbackData.length > 0;
|
|
180
154
|
|
|
181
|
-
if (
|
|
182
|
-
|
|
155
|
+
if (
|
|
156
|
+
trimmedContent.length === 0 &&
|
|
157
|
+
!hasAttachments &&
|
|
158
|
+
!isEdit &&
|
|
159
|
+
!hasCallbackData
|
|
160
|
+
) {
|
|
161
|
+
return httpError(
|
|
162
|
+
"BAD_REQUEST",
|
|
163
|
+
"content or attachmentIds is required",
|
|
164
|
+
400,
|
|
165
|
+
);
|
|
183
166
|
}
|
|
184
167
|
|
|
185
168
|
// Canonicalize the assistant ID so all DB-facing operations use the
|
|
186
169
|
// consistent 'self' key regardless of what the gateway sent.
|
|
187
170
|
const canonicalAssistantId = canonicalChannelAssistantId(assistantId);
|
|
188
171
|
if (canonicalAssistantId !== assistantId) {
|
|
189
|
-
log.debug(
|
|
172
|
+
log.debug(
|
|
173
|
+
{ raw: assistantId, canonical: canonicalAssistantId },
|
|
174
|
+
"Canonicalized channel assistant ID",
|
|
175
|
+
);
|
|
190
176
|
}
|
|
191
177
|
|
|
192
178
|
// Coerce actorExternalId to a string at the boundary — the field
|
|
193
179
|
// comes from unvalidated JSON and may be a number, object, or other
|
|
194
180
|
// non-string type. Non-string truthy values would throw inside
|
|
195
181
|
// canonicalizeInboundIdentity when it calls .trim().
|
|
196
|
-
const rawSenderId =
|
|
197
|
-
? String(body.actorExternalId)
|
|
198
|
-
: undefined;
|
|
182
|
+
const rawSenderId =
|
|
183
|
+
body.actorExternalId != null ? String(body.actorExternalId) : undefined;
|
|
199
184
|
|
|
200
185
|
// Canonicalize the sender identity so all trust lookups, member matching,
|
|
201
186
|
// and guardian binding comparisons use a normalized form. Phone-like
|
|
@@ -212,255 +197,24 @@ export async function handleChannelInbound(
|
|
|
212
197
|
const hasSenderIdentityClaim = rawSenderId !== undefined;
|
|
213
198
|
|
|
214
199
|
// ── Ingress ACL enforcement ──
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
// /start gv_<token> bootstrap commands must also bypass ACL — the user
|
|
225
|
-
// hasn't been verified yet and needs to complete the bootstrap handshake.
|
|
226
|
-
const rawCommandIntentForAcl = sourceMetadata?.commandIntent;
|
|
227
|
-
const isBootstrapCommand = rawCommandIntentForAcl &&
|
|
228
|
-
typeof rawCommandIntentForAcl === 'object' &&
|
|
229
|
-
!Array.isArray(rawCommandIntentForAcl) &&
|
|
230
|
-
(rawCommandIntentForAcl as Record<string, unknown>).type === 'start' &&
|
|
231
|
-
typeof (rawCommandIntentForAcl as Record<string, unknown>).payload === 'string' &&
|
|
232
|
-
((rawCommandIntentForAcl as Record<string, unknown>).payload as string).startsWith('gv_');
|
|
233
|
-
|
|
234
|
-
// Parse invite token from /start payloads using the channel transport
|
|
235
|
-
// adapter. The token is extracted once here so both the ACL bypass and
|
|
236
|
-
// the intercept handler can reference it without re-parsing.
|
|
237
|
-
const commandIntentForAcl = rawCommandIntentForAcl && typeof rawCommandIntentForAcl === 'object' && !Array.isArray(rawCommandIntentForAcl)
|
|
238
|
-
? rawCommandIntentForAcl as Record<string, unknown>
|
|
239
|
-
: undefined;
|
|
240
|
-
const inviteTransport = getTransport(sourceChannel);
|
|
241
|
-
const inviteToken = inviteTransport?.extractInboundToken({
|
|
242
|
-
commandIntent: commandIntentForAcl,
|
|
243
|
-
content: trimmedContent,
|
|
200
|
+
const aclResult = await enforceIngressAcl({
|
|
201
|
+
canonicalSenderId,
|
|
202
|
+
hasSenderIdentityClaim,
|
|
203
|
+
rawSenderId,
|
|
204
|
+
sourceChannel,
|
|
205
|
+
conversationExternalId,
|
|
206
|
+
canonicalAssistantId,
|
|
207
|
+
trimmedContent,
|
|
244
208
|
sourceMetadata: body.sourceMetadata,
|
|
209
|
+
actorDisplayName: body.actorDisplayName,
|
|
210
|
+
actorUsername: body.actorUsername,
|
|
211
|
+
replyCallbackUrl: body.replyCallbackUrl,
|
|
212
|
+
mintBearerToken,
|
|
213
|
+
assistantId,
|
|
214
|
+
externalMessageId,
|
|
245
215
|
});
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
// Only perform member lookup when we have a usable canonical ID.
|
|
249
|
-
// Whitespace-only senders (hasSenderIdentityClaim=true but
|
|
250
|
-
// canonicalSenderId=null) skip the lookup and fall into the deny path.
|
|
251
|
-
if (canonicalSenderId) {
|
|
252
|
-
resolvedMember = findMember({
|
|
253
|
-
assistantId: canonicalAssistantId,
|
|
254
|
-
sourceChannel,
|
|
255
|
-
externalUserId: canonicalSenderId,
|
|
256
|
-
externalChatId: conversationExternalId,
|
|
257
|
-
});
|
|
258
|
-
}
|
|
259
|
-
|
|
260
|
-
if (!resolvedMember) {
|
|
261
|
-
// Determine whether a verification-code bypass is warranted: only allow
|
|
262
|
-
// when there is a pending (unconsumed, unexpired) challenge AND no
|
|
263
|
-
// active guardian binding for this (assistantId, channel).
|
|
264
|
-
let denyNonMember = true;
|
|
265
|
-
if (isGuardianVerifyCode) {
|
|
266
|
-
// Allow bypass when there is any consumable challenge or active
|
|
267
|
-
// outbound session. The !hasActiveBinding guard is intentionally
|
|
268
|
-
// omitted: rebind sessions create a consumable challenge while a
|
|
269
|
-
// binding already exists, and the identity check inside
|
|
270
|
-
// validateAndConsumeChallenge prevents unauthorized takeovers.
|
|
271
|
-
const hasPendingChallenge = !!getPendingChallenge(canonicalAssistantId, sourceChannel);
|
|
272
|
-
const hasActiveOutboundSession = !!findActiveSession(canonicalAssistantId, sourceChannel);
|
|
273
|
-
if (hasPendingChallenge || hasActiveOutboundSession) {
|
|
274
|
-
denyNonMember = false;
|
|
275
|
-
} else {
|
|
276
|
-
log.info({ sourceChannel, hasPendingChallenge, hasActiveOutboundSession }, 'Ingress ACL: guardian verification bypass denied');
|
|
277
|
-
}
|
|
278
|
-
}
|
|
279
|
-
|
|
280
|
-
// Bootstrap deep-link commands bypass ACL only when the token
|
|
281
|
-
// resolves to a real pending_bootstrap session. Without this check,
|
|
282
|
-
// any `/start gv_<garbage>` would bypass the not_a_member gate and
|
|
283
|
-
// fall through to normal /start processing.
|
|
284
|
-
if (isBootstrapCommand) {
|
|
285
|
-
const bootstrapPayload = (rawCommandIntentForAcl as Record<string, unknown>).payload as string;
|
|
286
|
-
const bootstrapTokenForAcl = bootstrapPayload.slice(3); // strip 'gv_' prefix
|
|
287
|
-
const bootstrapSessionForAcl = resolveBootstrapToken(canonicalAssistantId, sourceChannel, bootstrapTokenForAcl);
|
|
288
|
-
if (bootstrapSessionForAcl && bootstrapSessionForAcl.status === 'pending_bootstrap') {
|
|
289
|
-
denyNonMember = false;
|
|
290
|
-
} else {
|
|
291
|
-
log.info({ sourceChannel, hasValidBootstrapSession: false }, 'Ingress ACL: bootstrap command bypass denied — no valid pending_bootstrap session');
|
|
292
|
-
}
|
|
293
|
-
}
|
|
294
|
-
|
|
295
|
-
// ── Invite token intercept (non-member) ──
|
|
296
|
-
// /start invite deep links grant access without guardian approval.
|
|
297
|
-
// Intercept here — before the deny gate — so valid invites short-circuit
|
|
298
|
-
// the ACL rejection and never reach the agent pipeline.
|
|
299
|
-
if (inviteToken && denyNonMember) {
|
|
300
|
-
const inviteResult = await handleInviteTokenIntercept({
|
|
301
|
-
rawToken: inviteToken,
|
|
302
|
-
sourceChannel,
|
|
303
|
-
externalChatId: conversationExternalId,
|
|
304
|
-
externalMessageId,
|
|
305
|
-
senderExternalUserId: canonicalSenderId ?? rawSenderId,
|
|
306
|
-
senderName: body.actorDisplayName,
|
|
307
|
-
senderUsername: body.actorUsername,
|
|
308
|
-
replyCallbackUrl: body.replyCallbackUrl,
|
|
309
|
-
bearerToken: mintBearerToken(),
|
|
310
|
-
assistantId,
|
|
311
|
-
canonicalAssistantId,
|
|
312
|
-
});
|
|
313
|
-
if (inviteResult) return inviteResult;
|
|
314
|
-
}
|
|
315
|
-
|
|
316
|
-
if (denyNonMember) {
|
|
317
|
-
log.info({ sourceChannel, externalUserId: canonicalSenderId }, 'Ingress ACL: no member record, denying');
|
|
318
|
-
|
|
319
|
-
// Notify the guardian about the access request so they can approve/deny.
|
|
320
|
-
// Uses the shared helper which handles guardian binding lookup,
|
|
321
|
-
// deduplication, canonical request creation, and notification emission.
|
|
322
|
-
let guardianNotified = false;
|
|
323
|
-
try {
|
|
324
|
-
const accessResult = notifyGuardianOfAccessRequest({
|
|
325
|
-
canonicalAssistantId,
|
|
326
|
-
sourceChannel,
|
|
327
|
-
conversationExternalId,
|
|
328
|
-
actorExternalId: canonicalSenderId ?? rawSenderId,
|
|
329
|
-
actorDisplayName: body.actorDisplayName,
|
|
330
|
-
actorUsername: body.actorUsername,
|
|
331
|
-
});
|
|
332
|
-
guardianNotified = accessResult.notified;
|
|
333
|
-
} catch (err) {
|
|
334
|
-
log.error({ err, sourceChannel, conversationExternalId }, 'Failed to notify guardian of access request');
|
|
335
|
-
}
|
|
336
|
-
|
|
337
|
-
if (body.replyCallbackUrl) {
|
|
338
|
-
const replyText = guardianNotified
|
|
339
|
-
? "Hmm looks like you don't have access to talk to me. I'll let them know you tried talking to me and get back to you."
|
|
340
|
-
: "Sorry, you haven't been approved to message this assistant.";
|
|
341
|
-
try {
|
|
342
|
-
await deliverChannelReply(body.replyCallbackUrl, {
|
|
343
|
-
chatId: conversationExternalId,
|
|
344
|
-
text: replyText,
|
|
345
|
-
assistantId,
|
|
346
|
-
}, mintBearerToken());
|
|
347
|
-
} catch (err) {
|
|
348
|
-
log.error({ err, conversationExternalId }, 'Failed to deliver ACL rejection reply');
|
|
349
|
-
}
|
|
350
|
-
}
|
|
351
|
-
|
|
352
|
-
return Response.json({ accepted: true, denied: true, reason: 'not_a_member' });
|
|
353
|
-
}
|
|
354
|
-
}
|
|
355
|
-
|
|
356
|
-
if (resolvedMember) {
|
|
357
|
-
if (resolvedMember.status !== 'active') {
|
|
358
|
-
// Same bypass logic as the no-member branch: verification codes and
|
|
359
|
-
// bootstrap commands must pass through even when the member record is
|
|
360
|
-
// revoked/blocked — otherwise the user can never re-verify.
|
|
361
|
-
let denyInactiveMember = true;
|
|
362
|
-
if (isGuardianVerifyCode) {
|
|
363
|
-
const hasPendingChallenge = !!getPendingChallenge(canonicalAssistantId, sourceChannel);
|
|
364
|
-
const hasActiveOutboundSession = !!findActiveSession(canonicalAssistantId, sourceChannel);
|
|
365
|
-
if (hasPendingChallenge || hasActiveOutboundSession) {
|
|
366
|
-
denyInactiveMember = false;
|
|
367
|
-
} else {
|
|
368
|
-
log.info({ sourceChannel, memberId: resolvedMember.id, hasPendingChallenge, hasActiveOutboundSession }, 'Ingress ACL: inactive member verification bypass denied');
|
|
369
|
-
}
|
|
370
|
-
}
|
|
371
|
-
if (isBootstrapCommand) {
|
|
372
|
-
const bootstrapPayload = (rawCommandIntentForAcl as Record<string, unknown>).payload as string;
|
|
373
|
-
const bootstrapTokenForAcl = bootstrapPayload.slice(3);
|
|
374
|
-
const bootstrapSessionForAcl = resolveBootstrapToken(canonicalAssistantId, sourceChannel, bootstrapTokenForAcl);
|
|
375
|
-
if (bootstrapSessionForAcl && bootstrapSessionForAcl.status === 'pending_bootstrap') {
|
|
376
|
-
denyInactiveMember = false;
|
|
377
|
-
} else {
|
|
378
|
-
log.info({ sourceChannel, memberId: resolvedMember.id, hasValidBootstrapSession: false }, 'Ingress ACL: inactive member bootstrap bypass denied');
|
|
379
|
-
}
|
|
380
|
-
}
|
|
381
|
-
|
|
382
|
-
// ── Invite token intercept (inactive member) ──
|
|
383
|
-
// Same as the non-member branch: invite tokens can reactivate
|
|
384
|
-
// revoked/pending members without requiring guardian approval.
|
|
385
|
-
if (inviteToken && denyInactiveMember) {
|
|
386
|
-
const inviteResult = await handleInviteTokenIntercept({
|
|
387
|
-
rawToken: inviteToken,
|
|
388
|
-
sourceChannel,
|
|
389
|
-
externalChatId: conversationExternalId,
|
|
390
|
-
externalMessageId,
|
|
391
|
-
senderExternalUserId: canonicalSenderId ?? rawSenderId,
|
|
392
|
-
senderName: body.actorDisplayName,
|
|
393
|
-
senderUsername: body.actorUsername,
|
|
394
|
-
replyCallbackUrl: body.replyCallbackUrl,
|
|
395
|
-
bearerToken: mintBearerToken(),
|
|
396
|
-
assistantId,
|
|
397
|
-
canonicalAssistantId,
|
|
398
|
-
});
|
|
399
|
-
if (inviteResult) return inviteResult;
|
|
400
|
-
}
|
|
401
|
-
|
|
402
|
-
if (denyInactiveMember) {
|
|
403
|
-
log.info({ sourceChannel, memberId: resolvedMember.id, status: resolvedMember.status }, 'Ingress ACL: member not active, denying');
|
|
404
|
-
|
|
405
|
-
// For revoked/pending members, notify the guardian so they can
|
|
406
|
-
// re-approve. Blocked members are intentionally excluded — the
|
|
407
|
-
// guardian already made an explicit decision to block them.
|
|
408
|
-
let guardianNotified = false;
|
|
409
|
-
if (resolvedMember.status !== 'blocked') {
|
|
410
|
-
try {
|
|
411
|
-
const accessResult = notifyGuardianOfAccessRequest({
|
|
412
|
-
canonicalAssistantId,
|
|
413
|
-
sourceChannel,
|
|
414
|
-
conversationExternalId,
|
|
415
|
-
actorExternalId: canonicalSenderId ?? rawSenderId,
|
|
416
|
-
actorDisplayName: body.actorDisplayName,
|
|
417
|
-
actorUsername: body.actorUsername,
|
|
418
|
-
previousMemberStatus: resolvedMember.status,
|
|
419
|
-
});
|
|
420
|
-
guardianNotified = accessResult.notified;
|
|
421
|
-
} catch (err) {
|
|
422
|
-
log.error({ err, sourceChannel, conversationExternalId }, 'Failed to notify guardian of access request');
|
|
423
|
-
}
|
|
424
|
-
}
|
|
425
|
-
|
|
426
|
-
if (body.replyCallbackUrl) {
|
|
427
|
-
const replyText = guardianNotified
|
|
428
|
-
? "Hmm looks like you don't have access to talk to me. I'll let them know you tried talking to me and get back to you."
|
|
429
|
-
: "Sorry, you haven't been approved to message this assistant.";
|
|
430
|
-
try {
|
|
431
|
-
await deliverChannelReply(body.replyCallbackUrl, {
|
|
432
|
-
chatId: conversationExternalId,
|
|
433
|
-
text: replyText,
|
|
434
|
-
assistantId,
|
|
435
|
-
}, mintBearerToken());
|
|
436
|
-
} catch (err) {
|
|
437
|
-
log.error({ err, conversationExternalId }, 'Failed to deliver ACL rejection reply');
|
|
438
|
-
}
|
|
439
|
-
}
|
|
440
|
-
return Response.json({ accepted: true, denied: true, reason: `member_${resolvedMember.status}` });
|
|
441
|
-
}
|
|
442
|
-
}
|
|
443
|
-
|
|
444
|
-
if (resolvedMember.policy === 'deny') {
|
|
445
|
-
log.info({ sourceChannel, memberId: resolvedMember.id }, 'Ingress ACL: member policy deny');
|
|
446
|
-
if (body.replyCallbackUrl) {
|
|
447
|
-
try {
|
|
448
|
-
await deliverChannelReply(body.replyCallbackUrl, {
|
|
449
|
-
chatId: conversationExternalId,
|
|
450
|
-
text: "Sorry, you haven't been approved to message this assistant.",
|
|
451
|
-
assistantId,
|
|
452
|
-
}, mintBearerToken());
|
|
453
|
-
} catch (err) {
|
|
454
|
-
log.error({ err, conversationExternalId }, 'Failed to deliver ACL rejection reply');
|
|
455
|
-
}
|
|
456
|
-
}
|
|
457
|
-
return Response.json({ accepted: true, denied: true, reason: 'policy_deny' });
|
|
458
|
-
}
|
|
459
|
-
|
|
460
|
-
// 'allow' or 'escalate' — update last seen and continue
|
|
461
|
-
updateLastSeen(resolvedMember.id);
|
|
462
|
-
}
|
|
463
|
-
}
|
|
216
|
+
if (aclResult.earlyResponse) return aclResult.earlyResponse;
|
|
217
|
+
const { resolvedMember, guardianVerifyCode } = aclResult;
|
|
464
218
|
|
|
465
219
|
if (hasAttachments) {
|
|
466
220
|
const resolved = attachmentsStore.getAttachmentsByIds(attachmentIds);
|
|
@@ -468,78 +222,35 @@ export async function handleChannelInbound(
|
|
|
468
222
|
const resolvedIds = new Set(resolved.map((a) => a.id));
|
|
469
223
|
const missing = attachmentIds.filter((id) => !resolvedIds.has(id));
|
|
470
224
|
return Response.json(
|
|
471
|
-
{ error: `Attachment IDs not found: ${missing.join(
|
|
225
|
+
{ error: `Attachment IDs not found: ${missing.join(", ")}` },
|
|
472
226
|
{ status: 400 },
|
|
473
227
|
);
|
|
474
228
|
}
|
|
475
229
|
}
|
|
476
230
|
|
|
477
|
-
const sourceMessageId =
|
|
478
|
-
|
|
479
|
-
|
|
231
|
+
const sourceMessageId =
|
|
232
|
+
typeof sourceMetadata?.messageId === "string"
|
|
233
|
+
? sourceMetadata.messageId
|
|
234
|
+
: undefined;
|
|
480
235
|
|
|
481
236
|
if (isEdit && !sourceMessageId) {
|
|
482
|
-
return httpError(
|
|
237
|
+
return httpError(
|
|
238
|
+
"BAD_REQUEST",
|
|
239
|
+
"sourceMetadata.messageId is required for edits",
|
|
240
|
+
400,
|
|
241
|
+
);
|
|
483
242
|
}
|
|
484
243
|
|
|
485
244
|
// ── Edit path: update existing message content, no new agent loop ──
|
|
486
245
|
if (isEdit && sourceMessageId) {
|
|
487
|
-
|
|
488
|
-
const editResult = channelDeliveryStore.recordInbound(
|
|
246
|
+
return handleEditIntercept({
|
|
489
247
|
sourceChannel,
|
|
490
248
|
conversationExternalId,
|
|
491
249
|
externalMessageId,
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
return Response.json({
|
|
497
|
-
accepted: true,
|
|
498
|
-
duplicate: true,
|
|
499
|
-
eventId: editResult.eventId,
|
|
500
|
-
});
|
|
501
|
-
}
|
|
502
|
-
|
|
503
|
-
// Retry lookup a few times — the original message may still be processing
|
|
504
|
-
// (linkMessage hasn't been called yet). Short backoff avoids losing edits
|
|
505
|
-
// that arrive while the original agent loop is in progress.
|
|
506
|
-
const EDIT_LOOKUP_RETRIES = 5;
|
|
507
|
-
const EDIT_LOOKUP_DELAY_MS = 2000;
|
|
508
|
-
|
|
509
|
-
let original: { messageId: string; conversationId: string } | null = null;
|
|
510
|
-
for (let attempt = 0; attempt <= EDIT_LOOKUP_RETRIES; attempt++) {
|
|
511
|
-
original = channelDeliveryStore.findMessageBySourceId(
|
|
512
|
-
sourceChannel,
|
|
513
|
-
conversationExternalId,
|
|
514
|
-
sourceMessageId,
|
|
515
|
-
);
|
|
516
|
-
if (original) break;
|
|
517
|
-
if (attempt < EDIT_LOOKUP_RETRIES) {
|
|
518
|
-
log.info(
|
|
519
|
-
{ assistantId, sourceMessageId, attempt: attempt + 1, maxAttempts: EDIT_LOOKUP_RETRIES },
|
|
520
|
-
'Original message not linked yet, retrying edit lookup',
|
|
521
|
-
);
|
|
522
|
-
await new Promise((resolve) => setTimeout(resolve, EDIT_LOOKUP_DELAY_MS));
|
|
523
|
-
}
|
|
524
|
-
}
|
|
525
|
-
|
|
526
|
-
if (original) {
|
|
527
|
-
conversationStore.updateMessageContent(original.messageId, content ?? '');
|
|
528
|
-
log.info(
|
|
529
|
-
{ assistantId, sourceMessageId, messageId: original.messageId },
|
|
530
|
-
'Updated message content from edited_message',
|
|
531
|
-
);
|
|
532
|
-
} else {
|
|
533
|
-
log.warn(
|
|
534
|
-
{ assistantId, sourceChannel, conversationExternalId, sourceMessageId },
|
|
535
|
-
'Could not find original message for edit after retries, ignoring',
|
|
536
|
-
);
|
|
537
|
-
}
|
|
538
|
-
|
|
539
|
-
return Response.json({
|
|
540
|
-
accepted: true,
|
|
541
|
-
duplicate: false,
|
|
542
|
-
eventId: editResult.eventId,
|
|
250
|
+
sourceMessageId,
|
|
251
|
+
canonicalAssistantId,
|
|
252
|
+
assistantId,
|
|
253
|
+
content,
|
|
543
254
|
});
|
|
544
255
|
}
|
|
545
256
|
|
|
@@ -558,18 +269,30 @@ export async function handleChannelInbound(
|
|
|
558
269
|
// gateway retries (duplicates) re-attempt delivery here. On success the
|
|
559
270
|
// pending marker is cleared so further duplicates short-circuit normally.
|
|
560
271
|
if (result.duplicate && replyCallbackUrl) {
|
|
561
|
-
const pendingReply = channelDeliveryStore.getPendingVerificationReply(
|
|
272
|
+
const pendingReply = channelDeliveryStore.getPendingVerificationReply(
|
|
273
|
+
result.eventId,
|
|
274
|
+
);
|
|
562
275
|
if (pendingReply) {
|
|
563
276
|
try {
|
|
564
|
-
await deliverChannelReply(
|
|
565
|
-
|
|
566
|
-
|
|
567
|
-
|
|
568
|
-
|
|
277
|
+
await deliverChannelReply(
|
|
278
|
+
replyCallbackUrl,
|
|
279
|
+
{
|
|
280
|
+
chatId: pendingReply.chatId,
|
|
281
|
+
text: pendingReply.text,
|
|
282
|
+
assistantId: pendingReply.assistantId,
|
|
283
|
+
},
|
|
284
|
+
mintBearerToken(),
|
|
285
|
+
);
|
|
569
286
|
channelDeliveryStore.clearPendingVerificationReply(result.eventId);
|
|
570
|
-
log.info(
|
|
287
|
+
log.info(
|
|
288
|
+
{ eventId: result.eventId },
|
|
289
|
+
"Retried pending verification reply: delivered",
|
|
290
|
+
);
|
|
571
291
|
} catch (retryErr) {
|
|
572
|
-
log.error(
|
|
292
|
+
log.error(
|
|
293
|
+
{ err: retryErr, eventId: result.eventId },
|
|
294
|
+
"Retry of pending verification reply failed; will retry on next duplicate",
|
|
295
|
+
);
|
|
573
296
|
}
|
|
574
297
|
return Response.json({
|
|
575
298
|
accepted: true,
|
|
@@ -594,322 +317,109 @@ export async function handleChannelInbound(
|
|
|
594
317
|
}
|
|
595
318
|
|
|
596
319
|
// ── Ingress escalation ──
|
|
597
|
-
|
|
598
|
-
|
|
599
|
-
|
|
600
|
-
|
|
601
|
-
|
|
602
|
-
|
|
603
|
-
|
|
604
|
-
|
|
605
|
-
|
|
606
|
-
|
|
320
|
+
const escalationResponse = handleEscalationIntercept({
|
|
321
|
+
resolvedMember,
|
|
322
|
+
canonicalAssistantId,
|
|
323
|
+
sourceChannel,
|
|
324
|
+
sourceInterface,
|
|
325
|
+
conversationExternalId,
|
|
326
|
+
externalMessageId,
|
|
327
|
+
conversationId: result.conversationId,
|
|
328
|
+
eventId: result.eventId,
|
|
329
|
+
content,
|
|
330
|
+
attachmentIds,
|
|
331
|
+
sourceMetadata: body.sourceMetadata,
|
|
332
|
+
actorDisplayName: body.actorDisplayName,
|
|
333
|
+
actorExternalId: body.actorExternalId,
|
|
334
|
+
actorUsername: body.actorUsername,
|
|
335
|
+
replyCallbackUrl: body.replyCallbackUrl,
|
|
336
|
+
canonicalSenderId,
|
|
337
|
+
rawSenderId,
|
|
338
|
+
});
|
|
339
|
+
if (escalationResponse) return escalationResponse;
|
|
607
340
|
|
|
608
|
-
|
|
609
|
-
|
|
610
|
-
|
|
611
|
-
|
|
612
|
-
|
|
613
|
-
|
|
614
|
-
|
|
615
|
-
senderUsername: body.actorUsername,
|
|
616
|
-
replyCallbackUrl: body.replyCallbackUrl,
|
|
617
|
-
assistantId: canonicalAssistantId,
|
|
618
|
-
});
|
|
341
|
+
const metadataHintsRaw = sourceMetadata?.hints;
|
|
342
|
+
const metadataHints = Array.isArray(metadataHintsRaw)
|
|
343
|
+
? metadataHintsRaw.filter(
|
|
344
|
+
(hint): hint is string =>
|
|
345
|
+
typeof hint === "string" && hint.trim().length > 0,
|
|
346
|
+
)
|
|
347
|
+
: [];
|
|
619
348
|
|
|
620
|
-
|
|
621
|
-
|
|
622
|
-
|
|
623
|
-
|
|
624
|
-
|
|
625
|
-
|
|
626
|
-
|
|
627
|
-
|
|
628
|
-
|
|
629
|
-
|
|
630
|
-
|
|
631
|
-
|
|
632
|
-
|
|
633
|
-
|
|
634
|
-
|
|
635
|
-
|
|
636
|
-
|
|
637
|
-
|
|
349
|
+
// Inject channel-scoped permission hints for Slack channel messages
|
|
350
|
+
if (sourceChannel === "slack") {
|
|
351
|
+
const channelProfile = getChannelPermissionProfile(conversationExternalId);
|
|
352
|
+
if (channelProfile) {
|
|
353
|
+
if (channelProfile.blockedTools?.length) {
|
|
354
|
+
metadataHints.push(
|
|
355
|
+
`Channel policy: the following tools are blocked in this channel: ${channelProfile.blockedTools.join(", ")}`,
|
|
356
|
+
);
|
|
357
|
+
}
|
|
358
|
+
if (channelProfile.allowedToolCategories?.length) {
|
|
359
|
+
metadataHints.push(
|
|
360
|
+
`Channel policy: only these tool categories are allowed in this channel: ${channelProfile.allowedToolCategories.join(", ")}`,
|
|
361
|
+
);
|
|
362
|
+
}
|
|
363
|
+
if (channelProfile.trustLevel === "restricted") {
|
|
364
|
+
metadataHints.push(
|
|
365
|
+
"Channel policy: this channel has restricted trust level. Exercise caution with tool usage.",
|
|
366
|
+
);
|
|
367
|
+
}
|
|
638
368
|
}
|
|
639
|
-
|
|
640
|
-
// Emit notification signal through the unified pipeline (fire-and-forget).
|
|
641
|
-
// This lets the decision engine route escalation alerts to all configured
|
|
642
|
-
// channels, supplementing the direct guardian notification below.
|
|
643
|
-
void emitNotificationSignal({
|
|
644
|
-
sourceEventName: 'ingress.escalation',
|
|
645
|
-
sourceChannel: sourceChannel,
|
|
646
|
-
sourceSessionId: result.conversationId,
|
|
647
|
-
assistantId: canonicalAssistantId,
|
|
648
|
-
attentionHints: {
|
|
649
|
-
requiresAction: true,
|
|
650
|
-
urgency: 'high',
|
|
651
|
-
isAsyncBackground: false,
|
|
652
|
-
visibleInSourceNow: false,
|
|
653
|
-
},
|
|
654
|
-
contextPayload: {
|
|
655
|
-
conversationId: result.conversationId,
|
|
656
|
-
sourceChannel,
|
|
657
|
-
conversationExternalId,
|
|
658
|
-
senderIdentifier: body.actorDisplayName || body.actorUsername || rawSenderId || 'Unknown sender',
|
|
659
|
-
eventId: result.eventId,
|
|
660
|
-
},
|
|
661
|
-
dedupeKey: `escalation:${result.eventId}`,
|
|
662
|
-
});
|
|
663
|
-
|
|
664
|
-
// Guardian escalation channel delivery is handled by the notification
|
|
665
|
-
// pipeline — no legacy callback dispatch needed.
|
|
666
|
-
log.info(
|
|
667
|
-
{ conversationId: result.conversationId },
|
|
668
|
-
'Guardian escalation created — notification pipeline handles channel delivery',
|
|
669
|
-
);
|
|
670
|
-
|
|
671
|
-
return Response.json({ accepted: true, escalated: true, reason: 'policy_escalate' });
|
|
672
369
|
}
|
|
673
370
|
|
|
674
|
-
const
|
|
675
|
-
|
|
676
|
-
|
|
677
|
-
|
|
678
|
-
|
|
679
|
-
? sourceMetadata.uxBrief.trim()
|
|
680
|
-
: undefined;
|
|
371
|
+
const metadataUxBrief =
|
|
372
|
+
typeof sourceMetadata?.uxBrief === "string" &&
|
|
373
|
+
sourceMetadata.uxBrief.trim().length > 0
|
|
374
|
+
? sourceMetadata.uxBrief.trim()
|
|
375
|
+
: undefined;
|
|
681
376
|
|
|
682
377
|
// Extract channel command intent (e.g. /start from Telegram)
|
|
683
378
|
const rawCommandIntent = sourceMetadata?.commandIntent;
|
|
684
|
-
const commandIntent =
|
|
685
|
-
|
|
686
|
-
|
|
379
|
+
const commandIntent =
|
|
380
|
+
rawCommandIntent &&
|
|
381
|
+
typeof rawCommandIntent === "object" &&
|
|
382
|
+
!Array.isArray(rawCommandIntent)
|
|
383
|
+
? (rawCommandIntent as Record<string, unknown>)
|
|
384
|
+
: undefined;
|
|
687
385
|
|
|
688
386
|
// Preserve locale from sourceMetadata so the model can greet in the user's language
|
|
689
|
-
const sourceLanguageCode =
|
|
690
|
-
|
|
691
|
-
|
|
387
|
+
const sourceLanguageCode =
|
|
388
|
+
typeof sourceMetadata?.languageCode === "string" &&
|
|
389
|
+
sourceMetadata.languageCode.trim().length > 0
|
|
390
|
+
? sourceMetadata.languageCode.trim()
|
|
391
|
+
: undefined;
|
|
692
392
|
|
|
693
393
|
// ── Telegram bootstrap deep-link handling ──
|
|
694
|
-
|
|
695
|
-
|
|
696
|
-
|
|
697
|
-
|
|
698
|
-
|
|
699
|
-
|
|
700
|
-
|
|
701
|
-
|
|
702
|
-
|
|
703
|
-
|
|
704
|
-
rawSenderId
|
|
705
|
-
) {
|
|
706
|
-
const bootstrapToken = (commandIntent.payload as string).slice(3);
|
|
707
|
-
const bootstrapSession = resolveBootstrapToken(canonicalAssistantId, sourceChannel, bootstrapToken);
|
|
708
|
-
|
|
709
|
-
if (bootstrapSession && bootstrapSession.status === 'pending_bootstrap') {
|
|
710
|
-
// Bind the pending_bootstrap session to the sender's identity
|
|
711
|
-
bindSessionIdentity(bootstrapSession.id, rawSenderId!, conversationExternalId);
|
|
712
|
-
|
|
713
|
-
// Transition bootstrap session to awaiting_response
|
|
714
|
-
updateSessionStatus(bootstrapSession.id, 'awaiting_response');
|
|
715
|
-
|
|
716
|
-
// Create a new identity-bound outbound session with a fresh secret.
|
|
717
|
-
// The old bootstrap session is auto-revoked by createOutboundSession.
|
|
718
|
-
const newSession = createOutboundSession({
|
|
719
|
-
assistantId: canonicalAssistantId,
|
|
720
|
-
channel: sourceChannel,
|
|
721
|
-
expectedExternalUserId: rawSenderId!,
|
|
722
|
-
expectedChatId: conversationExternalId,
|
|
723
|
-
identityBindingStatus: 'bound',
|
|
724
|
-
destinationAddress: conversationExternalId,
|
|
725
|
-
});
|
|
726
|
-
|
|
727
|
-
// Compose and send the verification prompt via Telegram
|
|
728
|
-
const telegramBody = composeVerificationTelegram(
|
|
729
|
-
GUARDIAN_VERIFY_TEMPLATE_KEYS.TELEGRAM_CHALLENGE_REQUEST,
|
|
730
|
-
{
|
|
731
|
-
code: newSession.secret,
|
|
732
|
-
expiresInMinutes: Math.floor((newSession.expiresAt - Date.now()) / 60_000),
|
|
733
|
-
},
|
|
734
|
-
);
|
|
735
|
-
|
|
736
|
-
// Deliver verification Telegram message via the gateway (fire-and-forget)
|
|
737
|
-
deliverBootstrapVerificationTelegram(conversationExternalId, telegramBody, canonicalAssistantId);
|
|
738
|
-
|
|
739
|
-
// Update delivery tracking
|
|
740
|
-
const now = Date.now();
|
|
741
|
-
updateSessionDelivery(newSession.sessionId, now, 1, now + RESEND_COOLDOWN_MS);
|
|
742
|
-
|
|
743
|
-
return Response.json({
|
|
744
|
-
accepted: true,
|
|
745
|
-
duplicate: false,
|
|
746
|
-
eventId: result.eventId,
|
|
747
|
-
guardianVerification: 'bootstrap_bound',
|
|
748
|
-
});
|
|
749
|
-
}
|
|
750
|
-
// If not found or expired, fall through to normal /start handling
|
|
751
|
-
}
|
|
394
|
+
const bootstrapResponse = await handleBootstrapIntercept({
|
|
395
|
+
isDuplicate: result.duplicate,
|
|
396
|
+
commandIntent,
|
|
397
|
+
rawSenderId,
|
|
398
|
+
canonicalAssistantId,
|
|
399
|
+
sourceChannel,
|
|
400
|
+
conversationExternalId,
|
|
401
|
+
eventId: result.eventId,
|
|
402
|
+
});
|
|
403
|
+
if (bootstrapResponse) return bootstrapResponse;
|
|
752
404
|
|
|
753
405
|
// ── Guardian verification code intercept (deterministic) ──
|
|
754
|
-
|
|
755
|
-
|
|
756
|
-
|
|
757
|
-
|
|
758
|
-
|
|
759
|
-
|
|
760
|
-
|
|
761
|
-
|
|
762
|
-
|
|
763
|
-
|
|
764
|
-
|
|
765
|
-
|
|
766
|
-
|
|
767
|
-
|
|
768
|
-
|
|
769
|
-
|
|
770
|
-
|
|
771
|
-
shouldInterceptVerification &&
|
|
772
|
-
guardianVerifyCode !== undefined &&
|
|
773
|
-
rawSenderId
|
|
774
|
-
) {
|
|
775
|
-
const verifyResult = validateAndConsumeChallenge(
|
|
776
|
-
canonicalAssistantId,
|
|
777
|
-
sourceChannel,
|
|
778
|
-
guardianVerifyCode,
|
|
779
|
-
canonicalSenderId ?? rawSenderId!,
|
|
780
|
-
conversationExternalId,
|
|
781
|
-
body.actorUsername,
|
|
782
|
-
body.actorDisplayName,
|
|
783
|
-
);
|
|
784
|
-
|
|
785
|
-
const guardianVerifyOutcome: 'verified' | 'failed' = verifyResult.success ? 'verified' : 'failed';
|
|
786
|
-
|
|
787
|
-
if (verifyResult.success) {
|
|
788
|
-
const existingMember = (canonicalSenderId ?? rawSenderId)
|
|
789
|
-
? findMember({
|
|
790
|
-
assistantId: canonicalAssistantId,
|
|
791
|
-
sourceChannel,
|
|
792
|
-
externalUserId: canonicalSenderId ?? rawSenderId!,
|
|
793
|
-
externalChatId: conversationExternalId,
|
|
794
|
-
})
|
|
795
|
-
: null;
|
|
796
|
-
const memberMatchesSender = existingMember?.externalUserId
|
|
797
|
-
? canonicalizeInboundIdentity(sourceChannel, existingMember.externalUserId) === (canonicalSenderId ?? rawSenderId)
|
|
798
|
-
: false;
|
|
799
|
-
const preservedDisplayName = memberMatchesSender && existingMember?.displayName?.trim().length
|
|
800
|
-
? existingMember.displayName
|
|
801
|
-
: body.actorDisplayName;
|
|
802
|
-
|
|
803
|
-
upsertMember({
|
|
804
|
-
assistantId: canonicalAssistantId,
|
|
805
|
-
sourceChannel,
|
|
806
|
-
externalUserId: canonicalSenderId ?? rawSenderId!,
|
|
807
|
-
externalChatId: conversationExternalId,
|
|
808
|
-
status: 'active',
|
|
809
|
-
policy: 'allow',
|
|
810
|
-
// Keep guardian-curated member name stable across re-verification.
|
|
811
|
-
displayName: preservedDisplayName,
|
|
812
|
-
username: body.actorUsername,
|
|
813
|
-
});
|
|
814
|
-
|
|
815
|
-
const verifyLogLabel = verifyResult.verificationType === 'trusted_contact'
|
|
816
|
-
? 'Trusted contact verified'
|
|
817
|
-
: 'Guardian verified';
|
|
818
|
-
log.info({ sourceChannel, externalUserId: canonicalSenderId, verificationType: verifyResult.verificationType }, `${verifyLogLabel}: auto-upserted ingress member`);
|
|
819
|
-
|
|
820
|
-
// Emit activated signal when a trusted contact completes verification.
|
|
821
|
-
// Member record is persisted above before this event fires, satisfying
|
|
822
|
-
// the persistence-before-event ordering invariant.
|
|
823
|
-
if (verifyResult.verificationType === 'trusted_contact') {
|
|
824
|
-
void emitNotificationSignal({
|
|
825
|
-
sourceEventName: 'ingress.trusted_contact.activated',
|
|
826
|
-
sourceChannel,
|
|
827
|
-
sourceSessionId: result.conversationId,
|
|
828
|
-
assistantId: canonicalAssistantId,
|
|
829
|
-
attentionHints: {
|
|
830
|
-
requiresAction: false,
|
|
831
|
-
urgency: 'low',
|
|
832
|
-
isAsyncBackground: false,
|
|
833
|
-
visibleInSourceNow: false,
|
|
834
|
-
},
|
|
835
|
-
contextPayload: {
|
|
836
|
-
sourceChannel,
|
|
837
|
-
actorExternalId: canonicalSenderId ?? rawSenderId!,
|
|
838
|
-
conversationExternalId,
|
|
839
|
-
actorDisplayName: body.actorDisplayName ?? null,
|
|
840
|
-
actorUsername: body.actorUsername ?? null,
|
|
841
|
-
},
|
|
842
|
-
dedupeKey: `trusted-contact:activated:${canonicalAssistantId}:${sourceChannel}:${canonicalSenderId ?? rawSenderId!}`,
|
|
843
|
-
});
|
|
844
|
-
}
|
|
845
|
-
}
|
|
846
|
-
|
|
847
|
-
// Deliver a deterministic template-driven reply and short-circuit.
|
|
848
|
-
// Verification code messages must never produce agent-generated copy.
|
|
849
|
-
if (replyCallbackUrl) {
|
|
850
|
-
let replyText: string;
|
|
851
|
-
if (!verifyResult.success) {
|
|
852
|
-
replyText = composeChannelVerifyReply(GUARDIAN_VERIFY_TEMPLATE_KEYS.CHANNEL_VERIFY_FAILED, {
|
|
853
|
-
failureReason: stripVerificationFailurePrefix(verifyResult.reason),
|
|
854
|
-
});
|
|
855
|
-
} else if (verifyResult.verificationType === 'trusted_contact') {
|
|
856
|
-
replyText = composeChannelVerifyReply(GUARDIAN_VERIFY_TEMPLATE_KEYS.CHANNEL_TRUSTED_CONTACT_VERIFY_SUCCESS);
|
|
857
|
-
} else {
|
|
858
|
-
replyText = composeChannelVerifyReply(GUARDIAN_VERIFY_TEMPLATE_KEYS.CHANNEL_VERIFY_SUCCESS);
|
|
859
|
-
}
|
|
860
|
-
try {
|
|
861
|
-
await deliverChannelReply(replyCallbackUrl, {
|
|
862
|
-
chatId: conversationExternalId,
|
|
863
|
-
text: replyText,
|
|
864
|
-
assistantId,
|
|
865
|
-
}, mintBearerToken());
|
|
866
|
-
} catch (err) {
|
|
867
|
-
// The challenge is already consumed and side effects applied, so
|
|
868
|
-
// we cannot simply re-throw and let the gateway retry the full
|
|
869
|
-
// flow. Instead, persist the reply so that gateway retries
|
|
870
|
-
// (which arrive as duplicates) can re-attempt delivery.
|
|
871
|
-
log.error({ err, conversationExternalId }, 'Failed to deliver deterministic verification reply; persisting for retry');
|
|
872
|
-
channelDeliveryStore.storePendingVerificationReply(result.eventId, {
|
|
873
|
-
chatId: conversationExternalId,
|
|
874
|
-
text: replyText,
|
|
875
|
-
assistantId,
|
|
876
|
-
});
|
|
877
|
-
|
|
878
|
-
// Self-retry after a short delay. The gateway deduplicates
|
|
879
|
-
// inbound webhooks after a successful forward, so duplicate
|
|
880
|
-
// retries may never arrive. This fire-and-forget retry ensures
|
|
881
|
-
// delivery is re-attempted even without a gateway duplicate.
|
|
882
|
-
setTimeout(async () => {
|
|
883
|
-
try {
|
|
884
|
-
await deliverChannelReply(replyCallbackUrl, {
|
|
885
|
-
chatId: conversationExternalId,
|
|
886
|
-
text: replyText,
|
|
887
|
-
assistantId,
|
|
888
|
-
}, mintBearerToken());
|
|
889
|
-
log.info({ eventId: result.eventId }, 'Verification reply delivered on self-retry');
|
|
890
|
-
channelDeliveryStore.clearPendingVerificationReply(result.eventId);
|
|
891
|
-
} catch (retryErr) {
|
|
892
|
-
log.error({ err: retryErr, eventId: result.eventId }, 'Verification reply self-retry also failed; pending reply remains as fallback');
|
|
893
|
-
}
|
|
894
|
-
}, 3000);
|
|
895
|
-
|
|
896
|
-
return Response.json({
|
|
897
|
-
accepted: true,
|
|
898
|
-
duplicate: false,
|
|
899
|
-
eventId: result.eventId,
|
|
900
|
-
guardianVerification: guardianVerifyOutcome,
|
|
901
|
-
deliveryPending: true,
|
|
902
|
-
});
|
|
903
|
-
}
|
|
904
|
-
}
|
|
905
|
-
|
|
906
|
-
return Response.json({
|
|
907
|
-
accepted: true,
|
|
908
|
-
duplicate: false,
|
|
909
|
-
eventId: result.eventId,
|
|
910
|
-
guardianVerification: guardianVerifyOutcome,
|
|
911
|
-
});
|
|
912
|
-
}
|
|
406
|
+
const verificationResponse = await handleVerificationIntercept({
|
|
407
|
+
isDuplicate: result.duplicate,
|
|
408
|
+
guardianVerifyCode,
|
|
409
|
+
rawSenderId,
|
|
410
|
+
canonicalSenderId,
|
|
411
|
+
canonicalAssistantId,
|
|
412
|
+
sourceChannel,
|
|
413
|
+
conversationExternalId,
|
|
414
|
+
conversationId: result.conversationId,
|
|
415
|
+
eventId: result.eventId,
|
|
416
|
+
replyCallbackUrl,
|
|
417
|
+
mintBearerToken,
|
|
418
|
+
assistantId,
|
|
419
|
+
actorDisplayName: body.actorDisplayName,
|
|
420
|
+
actorUsername: body.actorUsername,
|
|
421
|
+
});
|
|
422
|
+
if (verificationResponse) return verificationResponse;
|
|
913
423
|
|
|
914
424
|
// Legacy voice guardian action interception removed — all guardian reply
|
|
915
425
|
// routing now flows through the canonical router below (routeGuardianReply),
|
|
@@ -919,7 +429,7 @@ export async function handleChannelInbound(
|
|
|
919
429
|
// ── Actor role resolution ──
|
|
920
430
|
// Uses shared channel-agnostic resolution so all ingress paths classify
|
|
921
431
|
// guardian vs non-guardian actors the same way.
|
|
922
|
-
const
|
|
432
|
+
const trustCtx: TrustContext = resolveTrustContext({
|
|
923
433
|
assistantId: canonicalAssistantId,
|
|
924
434
|
sourceChannel,
|
|
925
435
|
conversationExternalId,
|
|
@@ -928,99 +438,26 @@ export async function handleChannelInbound(
|
|
|
928
438
|
actorDisplayName: body.actorDisplayName,
|
|
929
439
|
});
|
|
930
440
|
|
|
931
|
-
// Hoisted flag: set by the canonical guardian reply router when the invite
|
|
932
|
-
// handoff bypass fires. Prevents legacy approval interception from swallowing
|
|
933
|
-
// the message when other approvals are pending in the same chat.
|
|
934
|
-
let skipApprovalInterception = false;
|
|
935
|
-
|
|
936
441
|
// ── Canonical guardian reply router ──
|
|
937
|
-
|
|
938
|
-
|
|
939
|
-
|
|
940
|
-
|
|
941
|
-
|
|
942
|
-
|
|
943
|
-
|
|
944
|
-
|
|
945
|
-
|
|
946
|
-
|
|
947
|
-
|
|
948
|
-
|
|
949
|
-
|
|
950
|
-
|
|
951
|
-
|
|
952
|
-
|
|
953
|
-
|
|
954
|
-
|
|
955
|
-
|
|
956
|
-
// Pass undefined (not []) when there are zero combined results so the
|
|
957
|
-
// router's own identity-based fallback stays active.
|
|
958
|
-
const deliveryScopedPendingRequests = listPendingCanonicalGuardianRequestsByDestinationChat(
|
|
959
|
-
sourceChannel,
|
|
960
|
-
conversationExternalId,
|
|
961
|
-
);
|
|
962
|
-
let pendingRequestIds: string[] | undefined;
|
|
963
|
-
if (deliveryScopedPendingRequests.length > 0) {
|
|
964
|
-
const deliveryIds = new Set(deliveryScopedPendingRequests.map(r => r.id));
|
|
965
|
-
// Also include identity-based pending requests so we don't hide them
|
|
966
|
-
const identityId = canonicalSenderId ?? rawSenderId!;
|
|
967
|
-
const identityPending = listCanonicalGuardianRequests({
|
|
968
|
-
status: 'pending',
|
|
969
|
-
guardianExternalUserId: identityId,
|
|
970
|
-
});
|
|
971
|
-
for (const r of identityPending) {
|
|
972
|
-
deliveryIds.add(r.id);
|
|
973
|
-
}
|
|
974
|
-
pendingRequestIds = [...deliveryIds];
|
|
975
|
-
}
|
|
976
|
-
|
|
977
|
-
const routerResult = await routeGuardianReply({
|
|
978
|
-
messageText: trimmedContent,
|
|
979
|
-
channel: sourceChannel,
|
|
980
|
-
actor: {
|
|
981
|
-
externalUserId: canonicalSenderId ?? rawSenderId!,
|
|
982
|
-
channel: sourceChannel,
|
|
983
|
-
guardianPrincipalId: guardianCtx.guardianPrincipalId ?? undefined,
|
|
984
|
-
},
|
|
985
|
-
conversationId: result.conversationId,
|
|
986
|
-
callbackData: body.callbackData,
|
|
987
|
-
pendingRequestIds,
|
|
988
|
-
approvalConversationGenerator,
|
|
989
|
-
channelDeliveryContext: {
|
|
990
|
-
replyCallbackUrl,
|
|
991
|
-
guardianChatId: conversationExternalId,
|
|
992
|
-
assistantId: canonicalAssistantId,
|
|
993
|
-
bearerToken: mintBearerToken(),
|
|
994
|
-
},
|
|
995
|
-
});
|
|
996
|
-
|
|
997
|
-
if (routerResult.consumed) {
|
|
998
|
-
// Deliver reply text if the router produced one
|
|
999
|
-
if (routerResult.replyText) {
|
|
1000
|
-
try {
|
|
1001
|
-
await deliverChannelReply(replyCallbackUrl, {
|
|
1002
|
-
chatId: conversationExternalId,
|
|
1003
|
-
text: routerResult.replyText,
|
|
1004
|
-
assistantId: canonicalAssistantId,
|
|
1005
|
-
}, mintBearerToken());
|
|
1006
|
-
} catch (err) {
|
|
1007
|
-
log.error({ err, conversationExternalId }, 'Failed to deliver canonical router reply');
|
|
1008
|
-
}
|
|
1009
|
-
}
|
|
1010
|
-
|
|
1011
|
-
return Response.json({
|
|
1012
|
-
accepted: true,
|
|
1013
|
-
duplicate: false,
|
|
1014
|
-
eventId: result.eventId,
|
|
1015
|
-
canonicalRouter: routerResult.type,
|
|
1016
|
-
requestId: routerResult.requestId,
|
|
1017
|
-
});
|
|
1018
|
-
}
|
|
1019
|
-
|
|
1020
|
-
if (routerResult.skipApprovalInterception) {
|
|
1021
|
-
skipApprovalInterception = true;
|
|
1022
|
-
}
|
|
1023
|
-
}
|
|
442
|
+
const guardianReplyResult = await handleGuardianReplyIntercept({
|
|
443
|
+
isDuplicate: result.duplicate,
|
|
444
|
+
trimmedContent,
|
|
445
|
+
hasCallbackData,
|
|
446
|
+
callbackData: body.callbackData,
|
|
447
|
+
rawSenderId,
|
|
448
|
+
canonicalSenderId,
|
|
449
|
+
canonicalAssistantId,
|
|
450
|
+
sourceChannel,
|
|
451
|
+
conversationExternalId,
|
|
452
|
+
conversationId: result.conversationId,
|
|
453
|
+
eventId: result.eventId,
|
|
454
|
+
replyCallbackUrl,
|
|
455
|
+
mintBearerToken,
|
|
456
|
+
trustClass: trustCtx.trustClass,
|
|
457
|
+
guardianPrincipalId: trustCtx.guardianPrincipalId,
|
|
458
|
+
approvalConversationGenerator,
|
|
459
|
+
});
|
|
460
|
+
if (guardianReplyResult.response) return guardianReplyResult.response;
|
|
1024
461
|
|
|
1025
462
|
// ── Approval interception ──
|
|
1026
463
|
// Keep this active whenever callback context is available.
|
|
@@ -1030,7 +467,7 @@ export async function handleChannelInbound(
|
|
|
1030
467
|
if (
|
|
1031
468
|
replyCallbackUrl &&
|
|
1032
469
|
!result.duplicate &&
|
|
1033
|
-
!skipApprovalInterception
|
|
470
|
+
!guardianReplyResult.skipApprovalInterception
|
|
1034
471
|
) {
|
|
1035
472
|
const approvalResult = await handleApprovalInterception({
|
|
1036
473
|
conversationId: result.conversationId,
|
|
@@ -1041,45 +478,50 @@ export async function handleChannelInbound(
|
|
|
1041
478
|
actorExternalId: canonicalSenderId ?? rawSenderId,
|
|
1042
479
|
replyCallbackUrl,
|
|
1043
480
|
bearerToken: mintBearerToken(),
|
|
1044
|
-
|
|
481
|
+
trustCtx,
|
|
1045
482
|
assistantId: canonicalAssistantId,
|
|
1046
483
|
approvalCopyGenerator,
|
|
1047
484
|
approvalConversationGenerator,
|
|
1048
485
|
});
|
|
1049
486
|
|
|
1050
487
|
if (approvalResult.handled) {
|
|
1051
|
-
// Record inferred seen signal for
|
|
1052
|
-
if (sourceChannel ===
|
|
488
|
+
// Record inferred seen signal for handled approval interactions
|
|
489
|
+
if (sourceChannel === "telegram" || sourceChannel === "slack") {
|
|
1053
490
|
try {
|
|
1054
491
|
if (hasCallbackData) {
|
|
1055
|
-
const cbPreview =
|
|
1056
|
-
|
|
1057
|
-
|
|
492
|
+
const cbPreview =
|
|
493
|
+
body.callbackData!.length > 80
|
|
494
|
+
? body.callbackData!.slice(0, 80) + "..."
|
|
495
|
+
: body.callbackData!;
|
|
1058
496
|
recordConversationSeenSignal({
|
|
1059
497
|
conversationId: result.conversationId,
|
|
1060
498
|
assistantId: canonicalAssistantId,
|
|
1061
|
-
signalType:
|
|
1062
|
-
confidence:
|
|
1063
|
-
sourceChannel
|
|
1064
|
-
source:
|
|
499
|
+
signalType: `${sourceChannel}_callback` as SignalType,
|
|
500
|
+
confidence: "inferred",
|
|
501
|
+
sourceChannel,
|
|
502
|
+
source: "inbound-message-handler",
|
|
1065
503
|
evidenceText: `User tapped callback: '${cbPreview}'`,
|
|
1066
504
|
});
|
|
1067
505
|
} else {
|
|
1068
|
-
const msgPreview =
|
|
1069
|
-
|
|
1070
|
-
|
|
506
|
+
const msgPreview =
|
|
507
|
+
trimmedContent.length > 80
|
|
508
|
+
? trimmedContent.slice(0, 80) + "..."
|
|
509
|
+
: trimmedContent;
|
|
1071
510
|
recordConversationSeenSignal({
|
|
1072
511
|
conversationId: result.conversationId,
|
|
1073
512
|
assistantId: canonicalAssistantId,
|
|
1074
|
-
signalType:
|
|
1075
|
-
confidence:
|
|
1076
|
-
sourceChannel
|
|
1077
|
-
source:
|
|
513
|
+
signalType: `${sourceChannel}_inbound_message` as SignalType,
|
|
514
|
+
confidence: "inferred",
|
|
515
|
+
sourceChannel,
|
|
516
|
+
source: "inbound-message-handler",
|
|
1078
517
|
evidenceText: `User sent plain-text approval reply: '${msgPreview}'`,
|
|
1079
518
|
});
|
|
1080
519
|
}
|
|
1081
520
|
} catch (err) {
|
|
1082
|
-
log.warn(
|
|
521
|
+
log.warn(
|
|
522
|
+
{ err, conversationId: result.conversationId },
|
|
523
|
+
"Failed to record seen signal for approval interaction",
|
|
524
|
+
);
|
|
1083
525
|
}
|
|
1084
526
|
}
|
|
1085
527
|
|
|
@@ -1098,22 +540,26 @@ export async function handleChannelInbound(
|
|
|
1098
540
|
// so checking for empty content alone would miss stale callbacks.
|
|
1099
541
|
if (hasCallbackData) {
|
|
1100
542
|
// Record seen signal even for stale callbacks — the user still interacted
|
|
1101
|
-
if (sourceChannel ===
|
|
543
|
+
if (sourceChannel === "telegram" || sourceChannel === "slack") {
|
|
1102
544
|
try {
|
|
1103
|
-
const cbPreview =
|
|
1104
|
-
|
|
1105
|
-
|
|
545
|
+
const cbPreview =
|
|
546
|
+
body.callbackData!.length > 80
|
|
547
|
+
? body.callbackData!.slice(0, 80) + "..."
|
|
548
|
+
: body.callbackData!;
|
|
1106
549
|
recordConversationSeenSignal({
|
|
1107
550
|
conversationId: result.conversationId,
|
|
1108
551
|
assistantId: canonicalAssistantId,
|
|
1109
|
-
signalType:
|
|
1110
|
-
confidence:
|
|
1111
|
-
sourceChannel
|
|
1112
|
-
source:
|
|
552
|
+
signalType: `${sourceChannel}_callback` as SignalType,
|
|
553
|
+
confidence: "inferred",
|
|
554
|
+
sourceChannel,
|
|
555
|
+
source: "inbound-message-handler",
|
|
1113
556
|
evidenceText: `User tapped stale callback: '${cbPreview}'`,
|
|
1114
557
|
});
|
|
1115
558
|
} catch (err) {
|
|
1116
|
-
log.warn(
|
|
559
|
+
log.warn(
|
|
560
|
+
{ err, conversationId: result.conversationId },
|
|
561
|
+
"Failed to record seen signal for stale callback",
|
|
562
|
+
);
|
|
1117
563
|
}
|
|
1118
564
|
}
|
|
1119
565
|
|
|
@@ -1121,7 +567,7 @@ export async function handleChannelInbound(
|
|
|
1121
567
|
accepted: true,
|
|
1122
568
|
duplicate: false,
|
|
1123
569
|
eventId: result.eventId,
|
|
1124
|
-
approval:
|
|
570
|
+
approval: "stale_ignored",
|
|
1125
571
|
});
|
|
1126
572
|
}
|
|
1127
573
|
}
|
|
@@ -1129,56 +575,24 @@ export async function handleChannelInbound(
|
|
|
1129
575
|
// For new (non-duplicate) messages, run the secret ingress check
|
|
1130
576
|
// synchronously, then fire off the agent loop in the background.
|
|
1131
577
|
if (!result.duplicate && processMessage) {
|
|
1132
|
-
|
|
1133
|
-
|
|
1134
|
-
|
|
1135
|
-
|
|
1136
|
-
|
|
1137
|
-
|
|
1138
|
-
|
|
1139
|
-
|
|
1140
|
-
|
|
1141
|
-
|
|
578
|
+
runSecretIngressCheck({
|
|
579
|
+
eventId: result.eventId,
|
|
580
|
+
sourceChannel,
|
|
581
|
+
conversationExternalId,
|
|
582
|
+
externalMessageId,
|
|
583
|
+
conversationId: result.conversationId,
|
|
584
|
+
content,
|
|
585
|
+
trimmedContent,
|
|
586
|
+
attachmentIds,
|
|
587
|
+
sourceMetadata: body.sourceMetadata,
|
|
588
|
+
actorDisplayName: body.actorDisplayName,
|
|
589
|
+
actorExternalId: body.actorExternalId,
|
|
590
|
+
actorUsername: body.actorUsername,
|
|
591
|
+
trustCtx,
|
|
1142
592
|
replyCallbackUrl,
|
|
1143
|
-
|
|
593
|
+
canonicalAssistantId,
|
|
1144
594
|
});
|
|
1145
595
|
|
|
1146
|
-
const contentToCheck = content ?? '';
|
|
1147
|
-
let ingressCheck: ReturnType<typeof checkIngressForSecrets>;
|
|
1148
|
-
try {
|
|
1149
|
-
ingressCheck = checkIngressForSecrets(contentToCheck);
|
|
1150
|
-
} catch (checkErr) {
|
|
1151
|
-
channelDeliveryStore.clearPayload(result.eventId);
|
|
1152
|
-
throw checkErr;
|
|
1153
|
-
}
|
|
1154
|
-
if (ingressCheck.blocked) {
|
|
1155
|
-
channelDeliveryStore.clearPayload(result.eventId);
|
|
1156
|
-
throw new IngressBlockedError(ingressCheck.userNotice!, ingressCheck.detectedTypes);
|
|
1157
|
-
}
|
|
1158
|
-
|
|
1159
|
-
// Record inferred seen signal for non-duplicate Telegram inbound messages
|
|
1160
|
-
if (sourceChannel === 'telegram') {
|
|
1161
|
-
try {
|
|
1162
|
-
const msgPreview = trimmedContent.length > 80
|
|
1163
|
-
? trimmedContent.slice(0, 80) + '...'
|
|
1164
|
-
: trimmedContent;
|
|
1165
|
-
const evidence = trimmedContent.length > 0
|
|
1166
|
-
? `User sent message: '${msgPreview}'`
|
|
1167
|
-
: 'User sent media attachment';
|
|
1168
|
-
recordConversationSeenSignal({
|
|
1169
|
-
conversationId: result.conversationId,
|
|
1170
|
-
assistantId: canonicalAssistantId,
|
|
1171
|
-
signalType: 'telegram_inbound_message',
|
|
1172
|
-
confidence: 'inferred',
|
|
1173
|
-
sourceChannel: 'telegram',
|
|
1174
|
-
source: 'inbound-message-handler',
|
|
1175
|
-
evidenceText: evidence,
|
|
1176
|
-
});
|
|
1177
|
-
} catch (err) {
|
|
1178
|
-
log.warn({ err, conversationId: result.conversationId }, 'Failed to record seen signal for Telegram inbound message');
|
|
1179
|
-
}
|
|
1180
|
-
}
|
|
1181
|
-
|
|
1182
596
|
// Fire-and-forget: process the message and deliver the reply in the background.
|
|
1183
597
|
// The HTTP response returns immediately so the gateway webhook is not blocked.
|
|
1184
598
|
// The onEvent callback in processMessage registers pending interactions, and
|
|
@@ -1187,12 +601,12 @@ export async function handleChannelInbound(
|
|
|
1187
601
|
processMessage,
|
|
1188
602
|
conversationId: result.conversationId,
|
|
1189
603
|
eventId: result.eventId,
|
|
1190
|
-
content: content ??
|
|
604
|
+
content: content ?? "",
|
|
1191
605
|
attachmentIds: hasAttachments ? attachmentIds : undefined,
|
|
1192
606
|
sourceChannel,
|
|
1193
607
|
sourceInterface,
|
|
1194
608
|
externalChatId: conversationExternalId,
|
|
1195
|
-
|
|
609
|
+
trustCtx,
|
|
1196
610
|
metadataHints,
|
|
1197
611
|
metadataUxBrief,
|
|
1198
612
|
commandIntent,
|
|
@@ -1201,6 +615,7 @@ export async function handleChannelInbound(
|
|
|
1201
615
|
mintBearerToken,
|
|
1202
616
|
assistantId: canonicalAssistantId,
|
|
1203
617
|
approvalCopyGenerator,
|
|
618
|
+
externalMessageId: sourceMessageId ?? externalMessageId,
|
|
1204
619
|
});
|
|
1205
620
|
}
|
|
1206
621
|
|
|
@@ -1210,572 +625,3 @@ export async function handleChannelInbound(
|
|
|
1210
625
|
eventId: result.eventId,
|
|
1211
626
|
});
|
|
1212
627
|
}
|
|
1213
|
-
|
|
1214
|
-
// ---------------------------------------------------------------------------
|
|
1215
|
-
// Invite token intercept
|
|
1216
|
-
// ---------------------------------------------------------------------------
|
|
1217
|
-
|
|
1218
|
-
/**
|
|
1219
|
-
* Handle an inbound invite token for a non-member or inactive member.
|
|
1220
|
-
*
|
|
1221
|
-
* Redeems the invite, delivers a deterministic reply, and returns a Response
|
|
1222
|
-
* to short-circuit the handler. Returns `null` when the intercept should not
|
|
1223
|
-
* fire (e.g. already_member outcome — let normal flow handle it).
|
|
1224
|
-
*/
|
|
1225
|
-
async function handleInviteTokenIntercept(params: {
|
|
1226
|
-
rawToken: string;
|
|
1227
|
-
sourceChannel: ChannelId;
|
|
1228
|
-
externalChatId: string;
|
|
1229
|
-
externalMessageId: string;
|
|
1230
|
-
senderExternalUserId?: string;
|
|
1231
|
-
senderName?: string;
|
|
1232
|
-
senderUsername?: string;
|
|
1233
|
-
replyCallbackUrl?: string;
|
|
1234
|
-
bearerToken?: string;
|
|
1235
|
-
assistantId?: string;
|
|
1236
|
-
canonicalAssistantId: string;
|
|
1237
|
-
}): Promise<Response | null> {
|
|
1238
|
-
const {
|
|
1239
|
-
rawToken,
|
|
1240
|
-
sourceChannel,
|
|
1241
|
-
externalChatId,
|
|
1242
|
-
externalMessageId,
|
|
1243
|
-
senderExternalUserId,
|
|
1244
|
-
senderName,
|
|
1245
|
-
senderUsername,
|
|
1246
|
-
replyCallbackUrl,
|
|
1247
|
-
bearerToken,
|
|
1248
|
-
assistantId,
|
|
1249
|
-
canonicalAssistantId,
|
|
1250
|
-
} = params;
|
|
1251
|
-
|
|
1252
|
-
// Record the inbound event for dedup tracking BEFORE performing redemption.
|
|
1253
|
-
// Without this, duplicate webhook deliveries (common with Telegram) would
|
|
1254
|
-
// not be tracked: the first delivery redeems the invite and returns early,
|
|
1255
|
-
// then the retry finds an active member, passes ACL, and the raw
|
|
1256
|
-
// /start iv_<token> message leaks into the agent pipeline.
|
|
1257
|
-
const dedupResult = channelDeliveryStore.recordInbound(
|
|
1258
|
-
sourceChannel,
|
|
1259
|
-
externalChatId,
|
|
1260
|
-
externalMessageId,
|
|
1261
|
-
{ assistantId: canonicalAssistantId },
|
|
1262
|
-
);
|
|
1263
|
-
|
|
1264
|
-
if (dedupResult.duplicate) {
|
|
1265
|
-
return Response.json({
|
|
1266
|
-
accepted: true,
|
|
1267
|
-
duplicate: true,
|
|
1268
|
-
eventId: dedupResult.eventId,
|
|
1269
|
-
});
|
|
1270
|
-
}
|
|
1271
|
-
|
|
1272
|
-
const outcome = redeemInvite({
|
|
1273
|
-
rawToken,
|
|
1274
|
-
sourceChannel,
|
|
1275
|
-
externalUserId: senderExternalUserId,
|
|
1276
|
-
externalChatId,
|
|
1277
|
-
displayName: senderName,
|
|
1278
|
-
username: senderUsername,
|
|
1279
|
-
assistantId: canonicalAssistantId,
|
|
1280
|
-
});
|
|
1281
|
-
|
|
1282
|
-
log.info(
|
|
1283
|
-
{ sourceChannel, externalChatId: params.externalChatId, ok: outcome.ok, type: outcome.ok ? outcome.type : undefined, reason: !outcome.ok ? outcome.reason : undefined },
|
|
1284
|
-
'Invite token intercept: redemption result',
|
|
1285
|
-
);
|
|
1286
|
-
|
|
1287
|
-
// already_member means the user has an active record — let the normal
|
|
1288
|
-
// flow handle them (they passed ACL or the member is active).
|
|
1289
|
-
if (outcome.ok && outcome.type === 'already_member') {
|
|
1290
|
-
// Deliver a quick acknowledgement and short-circuit so the user
|
|
1291
|
-
// does not trigger the deny gate or a duplicate agent loop.
|
|
1292
|
-
const replyText = getInviteRedemptionReply(outcome);
|
|
1293
|
-
if (replyCallbackUrl) {
|
|
1294
|
-
try {
|
|
1295
|
-
await deliverChannelReply(replyCallbackUrl, {
|
|
1296
|
-
chatId: externalChatId,
|
|
1297
|
-
text: replyText,
|
|
1298
|
-
assistantId,
|
|
1299
|
-
}, bearerToken);
|
|
1300
|
-
} catch (err) {
|
|
1301
|
-
log.error({ err, externalChatId }, 'Failed to deliver invite already-member reply');
|
|
1302
|
-
}
|
|
1303
|
-
}
|
|
1304
|
-
channelDeliveryStore.markProcessed(dedupResult.eventId);
|
|
1305
|
-
return Response.json({ accepted: true, eventId: dedupResult.eventId, inviteRedemption: 'already_member' });
|
|
1306
|
-
}
|
|
1307
|
-
|
|
1308
|
-
const replyText = getInviteRedemptionReply(outcome);
|
|
1309
|
-
|
|
1310
|
-
if (replyCallbackUrl) {
|
|
1311
|
-
try {
|
|
1312
|
-
await deliverChannelReply(replyCallbackUrl, {
|
|
1313
|
-
chatId: externalChatId,
|
|
1314
|
-
text: replyText,
|
|
1315
|
-
assistantId,
|
|
1316
|
-
}, bearerToken);
|
|
1317
|
-
} catch (err) {
|
|
1318
|
-
log.error({ err, externalChatId }, 'Failed to deliver invite redemption reply');
|
|
1319
|
-
}
|
|
1320
|
-
}
|
|
1321
|
-
|
|
1322
|
-
if (outcome.ok && outcome.type === 'redeemed') {
|
|
1323
|
-
channelDeliveryStore.markProcessed(dedupResult.eventId);
|
|
1324
|
-
return Response.json({ accepted: true, eventId: dedupResult.eventId, inviteRedemption: 'redeemed', memberId: outcome.memberId });
|
|
1325
|
-
}
|
|
1326
|
-
|
|
1327
|
-
// Failed redemption — inform the user and deny
|
|
1328
|
-
channelDeliveryStore.markProcessed(dedupResult.eventId);
|
|
1329
|
-
return Response.json({ accepted: true, eventId: dedupResult.eventId, denied: true, inviteRedemption: outcome.reason });
|
|
1330
|
-
}
|
|
1331
|
-
|
|
1332
|
-
// ---------------------------------------------------------------------------
|
|
1333
|
-
// Background message processing
|
|
1334
|
-
// ---------------------------------------------------------------------------
|
|
1335
|
-
|
|
1336
|
-
interface BackgroundProcessingParams {
|
|
1337
|
-
processMessage: MessageProcessor;
|
|
1338
|
-
conversationId: string;
|
|
1339
|
-
eventId: string;
|
|
1340
|
-
content: string;
|
|
1341
|
-
attachmentIds?: string[];
|
|
1342
|
-
sourceChannel: ChannelId;
|
|
1343
|
-
sourceInterface: InterfaceId;
|
|
1344
|
-
externalChatId: string;
|
|
1345
|
-
guardianCtx: GuardianContext;
|
|
1346
|
-
metadataHints: string[];
|
|
1347
|
-
metadataUxBrief?: string;
|
|
1348
|
-
replyCallbackUrl?: string;
|
|
1349
|
-
/** Factory that mints a fresh delivery JWT for each HTTP attempt. */
|
|
1350
|
-
mintBearerToken: () => string;
|
|
1351
|
-
assistantId?: string;
|
|
1352
|
-
approvalCopyGenerator?: ApprovalCopyGenerator;
|
|
1353
|
-
commandIntent?: Record<string, unknown>;
|
|
1354
|
-
sourceLanguageCode?: string;
|
|
1355
|
-
}
|
|
1356
|
-
|
|
1357
|
-
const TELEGRAM_TYPING_INTERVAL_MS = 4_000;
|
|
1358
|
-
const PENDING_APPROVAL_POLL_INTERVAL_MS = 300;
|
|
1359
|
-
|
|
1360
|
-
// Module-level map tracking which approval requestIds have already been
|
|
1361
|
-
// notified to trusted contacts. Maps requestId -> conversationId so that
|
|
1362
|
-
// cleanup can be scoped to the owning conversation's poller, preventing
|
|
1363
|
-
// concurrent pollers from different conversations from evicting each
|
|
1364
|
-
// other's entries.
|
|
1365
|
-
const globalNotifiedApprovalRequestIds = new Map<string, string>();
|
|
1366
|
-
|
|
1367
|
-
function delay(ms: number): Promise<void> {
|
|
1368
|
-
return new Promise((resolve) => setTimeout(resolve, ms));
|
|
1369
|
-
}
|
|
1370
|
-
|
|
1371
|
-
function shouldEmitTelegramTyping(
|
|
1372
|
-
sourceChannel: ChannelId,
|
|
1373
|
-
replyCallbackUrl?: string,
|
|
1374
|
-
): boolean {
|
|
1375
|
-
if (sourceChannel !== 'telegram' || !replyCallbackUrl) return false;
|
|
1376
|
-
try {
|
|
1377
|
-
return new URL(replyCallbackUrl).pathname.endsWith('/deliver/telegram');
|
|
1378
|
-
} catch {
|
|
1379
|
-
return replyCallbackUrl.endsWith('/deliver/telegram');
|
|
1380
|
-
}
|
|
1381
|
-
}
|
|
1382
|
-
|
|
1383
|
-
function startTelegramTypingHeartbeat(
|
|
1384
|
-
callbackUrl: string,
|
|
1385
|
-
chatId: string,
|
|
1386
|
-
mintBearerToken: () => string,
|
|
1387
|
-
assistantId?: string,
|
|
1388
|
-
): () => void {
|
|
1389
|
-
let active = true;
|
|
1390
|
-
let inFlight = false;
|
|
1391
|
-
|
|
1392
|
-
const emitTyping = (): void => {
|
|
1393
|
-
if (!active || inFlight) return;
|
|
1394
|
-
inFlight = true;
|
|
1395
|
-
void deliverChannelReply(
|
|
1396
|
-
callbackUrl,
|
|
1397
|
-
{ chatId, chatAction: 'typing', assistantId },
|
|
1398
|
-
mintBearerToken(),
|
|
1399
|
-
).catch((err) => {
|
|
1400
|
-
log.debug({ err, chatId }, 'Failed to deliver Telegram typing indicator');
|
|
1401
|
-
}).finally(() => {
|
|
1402
|
-
inFlight = false;
|
|
1403
|
-
});
|
|
1404
|
-
};
|
|
1405
|
-
|
|
1406
|
-
emitTyping();
|
|
1407
|
-
|
|
1408
|
-
const interval = setInterval(emitTyping, TELEGRAM_TYPING_INTERVAL_MS);
|
|
1409
|
-
(interval as { unref?: () => void }).unref?.();
|
|
1410
|
-
|
|
1411
|
-
return () => {
|
|
1412
|
-
active = false;
|
|
1413
|
-
clearInterval(interval);
|
|
1414
|
-
};
|
|
1415
|
-
}
|
|
1416
|
-
|
|
1417
|
-
function startPendingApprovalPromptWatcher(params: {
|
|
1418
|
-
conversationId: string;
|
|
1419
|
-
sourceChannel: ChannelId;
|
|
1420
|
-
externalChatId: string;
|
|
1421
|
-
guardianTrustClass: GuardianContext['trustClass'];
|
|
1422
|
-
guardianExternalUserId?: string;
|
|
1423
|
-
requesterExternalUserId?: string;
|
|
1424
|
-
replyCallbackUrl: string;
|
|
1425
|
-
mintBearerToken: () => string;
|
|
1426
|
-
assistantId?: string;
|
|
1427
|
-
approvalCopyGenerator?: ApprovalCopyGenerator;
|
|
1428
|
-
}): () => void {
|
|
1429
|
-
const {
|
|
1430
|
-
conversationId,
|
|
1431
|
-
sourceChannel,
|
|
1432
|
-
externalChatId,
|
|
1433
|
-
guardianTrustClass,
|
|
1434
|
-
guardianExternalUserId,
|
|
1435
|
-
requesterExternalUserId,
|
|
1436
|
-
replyCallbackUrl,
|
|
1437
|
-
mintBearerToken,
|
|
1438
|
-
assistantId,
|
|
1439
|
-
approvalCopyGenerator,
|
|
1440
|
-
} = params;
|
|
1441
|
-
|
|
1442
|
-
// Approval prompt delivery is guardian-only. Non-guardian and unverified
|
|
1443
|
-
// actors must never receive approval prompt broadcasts for the conversation.
|
|
1444
|
-
// We also require an explicit identity match against the bound guardian to
|
|
1445
|
-
// avoid broadcasting prompts when trustClass is stale/mis-scoped.
|
|
1446
|
-
const isBoundGuardianActor = guardianTrustClass === 'guardian'
|
|
1447
|
-
&& !!guardianExternalUserId
|
|
1448
|
-
&& requesterExternalUserId === guardianExternalUserId;
|
|
1449
|
-
if (!isBoundGuardianActor) {
|
|
1450
|
-
return () => {};
|
|
1451
|
-
}
|
|
1452
|
-
|
|
1453
|
-
let active = true;
|
|
1454
|
-
const deliveredRequestIds = new Set<string>();
|
|
1455
|
-
|
|
1456
|
-
const poll = async (): Promise<void> => {
|
|
1457
|
-
while (active) {
|
|
1458
|
-
try {
|
|
1459
|
-
const prompt = getChannelApprovalPrompt(conversationId);
|
|
1460
|
-
const pending = getApprovalInfoByConversation(conversationId);
|
|
1461
|
-
const info = pending[0];
|
|
1462
|
-
if (prompt && info && !deliveredRequestIds.has(info.requestId)) {
|
|
1463
|
-
deliveredRequestIds.add(info.requestId);
|
|
1464
|
-
const delivered = await deliverGeneratedApprovalPrompt({
|
|
1465
|
-
replyCallbackUrl,
|
|
1466
|
-
chatId: externalChatId,
|
|
1467
|
-
sourceChannel,
|
|
1468
|
-
assistantId: assistantId ?? DAEMON_INTERNAL_ASSISTANT_ID,
|
|
1469
|
-
bearerToken: mintBearerToken(),
|
|
1470
|
-
prompt,
|
|
1471
|
-
uiMetadata: buildApprovalUIMetadata(prompt, info),
|
|
1472
|
-
messageContext: {
|
|
1473
|
-
scenario: 'standard_prompt',
|
|
1474
|
-
toolName: info.toolName,
|
|
1475
|
-
channel: sourceChannel,
|
|
1476
|
-
},
|
|
1477
|
-
approvalCopyGenerator,
|
|
1478
|
-
});
|
|
1479
|
-
if (!delivered) {
|
|
1480
|
-
// Delivery can fail transiently (network or gateway outage).
|
|
1481
|
-
// Keep polling and retry prompt delivery for the same request.
|
|
1482
|
-
deliveredRequestIds.delete(info.requestId);
|
|
1483
|
-
}
|
|
1484
|
-
}
|
|
1485
|
-
} catch (err) {
|
|
1486
|
-
log.warn({ err, conversationId }, 'Pending approval prompt watcher failed');
|
|
1487
|
-
}
|
|
1488
|
-
await delay(PENDING_APPROVAL_POLL_INTERVAL_MS);
|
|
1489
|
-
}
|
|
1490
|
-
};
|
|
1491
|
-
|
|
1492
|
-
void poll();
|
|
1493
|
-
return () => {
|
|
1494
|
-
active = false;
|
|
1495
|
-
};
|
|
1496
|
-
}
|
|
1497
|
-
|
|
1498
|
-
/**
|
|
1499
|
-
* Resolve a human-readable guardian name from the guardian binding metadata.
|
|
1500
|
-
* Returns the display name, username (prefixed with @), or undefined if
|
|
1501
|
-
* no name is available.
|
|
1502
|
-
*/
|
|
1503
|
-
function resolveGuardianDisplayName(
|
|
1504
|
-
assistantId: string,
|
|
1505
|
-
sourceChannel: ChannelId,
|
|
1506
|
-
): string | undefined {
|
|
1507
|
-
const binding = getGuardianBinding(assistantId, sourceChannel);
|
|
1508
|
-
if (!binding?.metadataJson) return undefined;
|
|
1509
|
-
try {
|
|
1510
|
-
const parsed = JSON.parse(binding.metadataJson) as Record<string, unknown>;
|
|
1511
|
-
if (typeof parsed.displayName === 'string' && parsed.displayName.trim().length > 0) {
|
|
1512
|
-
return parsed.displayName.trim();
|
|
1513
|
-
}
|
|
1514
|
-
if (typeof parsed.username === 'string' && parsed.username.trim().length > 0) {
|
|
1515
|
-
return `@${parsed.username.trim()}`;
|
|
1516
|
-
}
|
|
1517
|
-
} catch {
|
|
1518
|
-
// ignore malformed metadata
|
|
1519
|
-
}
|
|
1520
|
-
return undefined;
|
|
1521
|
-
}
|
|
1522
|
-
|
|
1523
|
-
/**
|
|
1524
|
-
* Start a poller that sends a one-shot "waiting for guardian approval" message
|
|
1525
|
-
* to the trusted contact when a confirmation_request enters guardian approval
|
|
1526
|
-
* wait. Deduplicates by requestId so each request only produces one message.
|
|
1527
|
-
*
|
|
1528
|
-
* Only activates for trusted-contact actors with a resolvable guardian route.
|
|
1529
|
-
*/
|
|
1530
|
-
function startTrustedContactApprovalNotifier(params: {
|
|
1531
|
-
conversationId: string;
|
|
1532
|
-
sourceChannel: ChannelId;
|
|
1533
|
-
externalChatId: string;
|
|
1534
|
-
guardianTrustClass: GuardianContext['trustClass'];
|
|
1535
|
-
guardianExternalUserId?: string;
|
|
1536
|
-
replyCallbackUrl: string;
|
|
1537
|
-
mintBearerToken: () => string;
|
|
1538
|
-
assistantId?: string;
|
|
1539
|
-
}): () => void {
|
|
1540
|
-
const {
|
|
1541
|
-
conversationId,
|
|
1542
|
-
sourceChannel,
|
|
1543
|
-
externalChatId,
|
|
1544
|
-
guardianTrustClass,
|
|
1545
|
-
guardianExternalUserId,
|
|
1546
|
-
replyCallbackUrl,
|
|
1547
|
-
mintBearerToken,
|
|
1548
|
-
assistantId,
|
|
1549
|
-
} = params;
|
|
1550
|
-
|
|
1551
|
-
// Only notify trusted contacts who have a resolvable guardian route.
|
|
1552
|
-
if (guardianTrustClass !== 'trusted_contact' || !guardianExternalUserId) {
|
|
1553
|
-
return () => {};
|
|
1554
|
-
}
|
|
1555
|
-
|
|
1556
|
-
let active = true;
|
|
1557
|
-
|
|
1558
|
-
const poll = async (): Promise<void> => {
|
|
1559
|
-
while (active) {
|
|
1560
|
-
try {
|
|
1561
|
-
const pending = getApprovalInfoByConversation(conversationId);
|
|
1562
|
-
const info = pending[0];
|
|
1563
|
-
|
|
1564
|
-
// Clean up resolved requests from the module-level dedupe map.
|
|
1565
|
-
// Only remove entries that belong to THIS conversation — other
|
|
1566
|
-
// conversations' pollers own their own entries. Without this
|
|
1567
|
-
// scoping, concurrent pollers would evict each other's request
|
|
1568
|
-
// IDs and cause duplicate notifications.
|
|
1569
|
-
const currentPendingIds = new Set(pending.map(p => p.requestId));
|
|
1570
|
-
for (const [rid, cid] of globalNotifiedApprovalRequestIds) {
|
|
1571
|
-
if (cid === conversationId && !currentPendingIds.has(rid)) {
|
|
1572
|
-
globalNotifiedApprovalRequestIds.delete(rid);
|
|
1573
|
-
}
|
|
1574
|
-
}
|
|
1575
|
-
|
|
1576
|
-
if (info && !globalNotifiedApprovalRequestIds.has(info.requestId)) {
|
|
1577
|
-
globalNotifiedApprovalRequestIds.set(info.requestId, conversationId);
|
|
1578
|
-
const guardianName = resolveGuardianDisplayName(
|
|
1579
|
-
assistantId ?? DAEMON_INTERNAL_ASSISTANT_ID,
|
|
1580
|
-
sourceChannel,
|
|
1581
|
-
) ?? resolveUserReference();
|
|
1582
|
-
const waitingText = `Waiting for ${guardianName}'s approval...`;
|
|
1583
|
-
try {
|
|
1584
|
-
await deliverChannelReply(replyCallbackUrl, {
|
|
1585
|
-
chatId: externalChatId,
|
|
1586
|
-
text: waitingText,
|
|
1587
|
-
assistantId: assistantId ?? DAEMON_INTERNAL_ASSISTANT_ID,
|
|
1588
|
-
}, mintBearerToken());
|
|
1589
|
-
} catch (err) {
|
|
1590
|
-
log.warn({ err, conversationId }, 'Failed to deliver trusted-contact pending-approval notification');
|
|
1591
|
-
// Remove from notified set so delivery is retried on next poll
|
|
1592
|
-
globalNotifiedApprovalRequestIds.delete(info.requestId);
|
|
1593
|
-
}
|
|
1594
|
-
}
|
|
1595
|
-
} catch (err) {
|
|
1596
|
-
log.warn({ err, conversationId }, 'Trusted-contact approval notifier poll failed');
|
|
1597
|
-
}
|
|
1598
|
-
await delay(PENDING_APPROVAL_POLL_INTERVAL_MS);
|
|
1599
|
-
}
|
|
1600
|
-
};
|
|
1601
|
-
|
|
1602
|
-
void poll();
|
|
1603
|
-
return () => {
|
|
1604
|
-
active = false;
|
|
1605
|
-
|
|
1606
|
-
// Evict all dedupe entries owned by this conversation so the
|
|
1607
|
-
// module-level map doesn't grow unboundedly after the poller stops.
|
|
1608
|
-
for (const [rid, cid] of globalNotifiedApprovalRequestIds) {
|
|
1609
|
-
if (cid === conversationId) {
|
|
1610
|
-
globalNotifiedApprovalRequestIds.delete(rid);
|
|
1611
|
-
}
|
|
1612
|
-
}
|
|
1613
|
-
};
|
|
1614
|
-
}
|
|
1615
|
-
|
|
1616
|
-
function processChannelMessageInBackground(params: BackgroundProcessingParams): void {
|
|
1617
|
-
const {
|
|
1618
|
-
processMessage,
|
|
1619
|
-
conversationId,
|
|
1620
|
-
eventId,
|
|
1621
|
-
content,
|
|
1622
|
-
attachmentIds,
|
|
1623
|
-
sourceChannel,
|
|
1624
|
-
sourceInterface,
|
|
1625
|
-
externalChatId,
|
|
1626
|
-
guardianCtx,
|
|
1627
|
-
metadataHints,
|
|
1628
|
-
metadataUxBrief,
|
|
1629
|
-
replyCallbackUrl,
|
|
1630
|
-
mintBearerToken,
|
|
1631
|
-
assistantId,
|
|
1632
|
-
approvalCopyGenerator,
|
|
1633
|
-
commandIntent,
|
|
1634
|
-
sourceLanguageCode,
|
|
1635
|
-
} = params;
|
|
1636
|
-
|
|
1637
|
-
(async () => {
|
|
1638
|
-
const typingCallbackUrl = shouldEmitTelegramTyping(sourceChannel, replyCallbackUrl)
|
|
1639
|
-
? replyCallbackUrl
|
|
1640
|
-
: undefined;
|
|
1641
|
-
const stopTypingHeartbeat = typingCallbackUrl
|
|
1642
|
-
? startTelegramTypingHeartbeat(typingCallbackUrl, externalChatId, mintBearerToken, assistantId)
|
|
1643
|
-
: undefined;
|
|
1644
|
-
const stopApprovalWatcher = replyCallbackUrl
|
|
1645
|
-
? startPendingApprovalPromptWatcher({
|
|
1646
|
-
conversationId,
|
|
1647
|
-
sourceChannel,
|
|
1648
|
-
externalChatId,
|
|
1649
|
-
guardianTrustClass: guardianCtx.trustClass,
|
|
1650
|
-
guardianExternalUserId: guardianCtx.guardianExternalUserId,
|
|
1651
|
-
requesterExternalUserId: guardianCtx.requesterExternalUserId,
|
|
1652
|
-
replyCallbackUrl,
|
|
1653
|
-
mintBearerToken,
|
|
1654
|
-
assistantId,
|
|
1655
|
-
approvalCopyGenerator,
|
|
1656
|
-
})
|
|
1657
|
-
: undefined;
|
|
1658
|
-
const stopTcApprovalNotifier = replyCallbackUrl
|
|
1659
|
-
? startTrustedContactApprovalNotifier({
|
|
1660
|
-
conversationId,
|
|
1661
|
-
sourceChannel,
|
|
1662
|
-
externalChatId,
|
|
1663
|
-
guardianTrustClass: guardianCtx.trustClass,
|
|
1664
|
-
guardianExternalUserId: guardianCtx.guardianExternalUserId,
|
|
1665
|
-
replyCallbackUrl,
|
|
1666
|
-
mintBearerToken,
|
|
1667
|
-
assistantId,
|
|
1668
|
-
})
|
|
1669
|
-
: undefined;
|
|
1670
|
-
|
|
1671
|
-
try {
|
|
1672
|
-
const cmdIntent = commandIntent && typeof commandIntent.type === 'string'
|
|
1673
|
-
? { type: commandIntent.type as string, ...(typeof commandIntent.payload === 'string' ? { payload: commandIntent.payload } : {}), ...(sourceLanguageCode ? { languageCode: sourceLanguageCode } : {}) }
|
|
1674
|
-
: undefined;
|
|
1675
|
-
const { messageId: userMessageId } = await processMessage(
|
|
1676
|
-
conversationId,
|
|
1677
|
-
content,
|
|
1678
|
-
attachmentIds,
|
|
1679
|
-
{
|
|
1680
|
-
transport: {
|
|
1681
|
-
channelId: sourceChannel,
|
|
1682
|
-
hints: metadataHints.length > 0 ? metadataHints : undefined,
|
|
1683
|
-
uxBrief: metadataUxBrief,
|
|
1684
|
-
},
|
|
1685
|
-
assistantId,
|
|
1686
|
-
guardianContext: guardianCtx,
|
|
1687
|
-
isInteractive: resolveRoutingState(guardianCtx).promptWaitingAllowed,
|
|
1688
|
-
...(cmdIntent ? { commandIntent: cmdIntent } : {}),
|
|
1689
|
-
},
|
|
1690
|
-
sourceChannel,
|
|
1691
|
-
sourceInterface,
|
|
1692
|
-
);
|
|
1693
|
-
channelDeliveryStore.linkMessage(eventId, userMessageId);
|
|
1694
|
-
channelDeliveryStore.markProcessed(eventId);
|
|
1695
|
-
|
|
1696
|
-
if (replyCallbackUrl) {
|
|
1697
|
-
await deliverReplyViaCallback(
|
|
1698
|
-
conversationId,
|
|
1699
|
-
externalChatId,
|
|
1700
|
-
replyCallbackUrl,
|
|
1701
|
-
mintBearerToken(),
|
|
1702
|
-
assistantId,
|
|
1703
|
-
{
|
|
1704
|
-
onSegmentDelivered: (count) =>
|
|
1705
|
-
channelDeliveryStore.updateDeliveredSegmentCount(eventId, count),
|
|
1706
|
-
},
|
|
1707
|
-
);
|
|
1708
|
-
}
|
|
1709
|
-
} catch (err) {
|
|
1710
|
-
log.error({ err, conversationId }, 'Background channel message processing failed');
|
|
1711
|
-
channelDeliveryStore.recordProcessingFailure(eventId, err);
|
|
1712
|
-
} finally {
|
|
1713
|
-
stopTypingHeartbeat?.();
|
|
1714
|
-
stopApprovalWatcher?.();
|
|
1715
|
-
stopTcApprovalNotifier?.();
|
|
1716
|
-
}
|
|
1717
|
-
})();
|
|
1718
|
-
}
|
|
1719
|
-
|
|
1720
|
-
// ---------------------------------------------------------------------------
|
|
1721
|
-
// Bootstrap verification Telegram delivery helper
|
|
1722
|
-
// ---------------------------------------------------------------------------
|
|
1723
|
-
|
|
1724
|
-
/**
|
|
1725
|
-
* Deliver a verification Telegram message during bootstrap.
|
|
1726
|
-
* Fire-and-forget with error logging and a single self-retry on failure.
|
|
1727
|
-
*/
|
|
1728
|
-
function deliverBootstrapVerificationTelegram(
|
|
1729
|
-
chatId: string,
|
|
1730
|
-
text: string,
|
|
1731
|
-
assistantId: string,
|
|
1732
|
-
): void {
|
|
1733
|
-
const attemptDelivery = async (): Promise<boolean> => {
|
|
1734
|
-
const gatewayUrl = getGatewayInternalBaseUrl();
|
|
1735
|
-
const bearerToken = mintDaemonDeliveryToken();
|
|
1736
|
-
const url = `${gatewayUrl}/deliver/telegram`;
|
|
1737
|
-
const resp = await fetch(url, {
|
|
1738
|
-
method: 'POST',
|
|
1739
|
-
headers: {
|
|
1740
|
-
'Content-Type': 'application/json',
|
|
1741
|
-
Authorization: `Bearer ${bearerToken}`,
|
|
1742
|
-
},
|
|
1743
|
-
body: JSON.stringify({ chatId, text, assistantId }),
|
|
1744
|
-
});
|
|
1745
|
-
if (!resp.ok) {
|
|
1746
|
-
const body = await resp.text().catch(() => '<unreadable>');
|
|
1747
|
-
log.error({ chatId, assistantId, status: resp.status, body }, 'Gateway /deliver/telegram failed for bootstrap verification');
|
|
1748
|
-
return false;
|
|
1749
|
-
}
|
|
1750
|
-
return true;
|
|
1751
|
-
};
|
|
1752
|
-
|
|
1753
|
-
(async () => {
|
|
1754
|
-
try {
|
|
1755
|
-
const delivered = await attemptDelivery();
|
|
1756
|
-
if (delivered) {
|
|
1757
|
-
log.info({ chatId, assistantId }, 'Bootstrap verification Telegram message delivered');
|
|
1758
|
-
return;
|
|
1759
|
-
}
|
|
1760
|
-
} catch (err) {
|
|
1761
|
-
log.error({ err, chatId, assistantId }, 'Failed to deliver bootstrap verification Telegram message');
|
|
1762
|
-
}
|
|
1763
|
-
|
|
1764
|
-
// Self-retry after a short delay. The gateway deduplicates inbound
|
|
1765
|
-
// webhooks after a successful forward, so duplicate retries from the
|
|
1766
|
-
// user re-clicking the deep link may never arrive. This ensures
|
|
1767
|
-
// delivery is re-attempted even without a gateway duplicate.
|
|
1768
|
-
setTimeout(async () => {
|
|
1769
|
-
try {
|
|
1770
|
-
const delivered = await attemptDelivery();
|
|
1771
|
-
if (delivered) {
|
|
1772
|
-
log.info({ chatId, assistantId }, 'Bootstrap verification Telegram message delivered on self-retry');
|
|
1773
|
-
} else {
|
|
1774
|
-
log.error({ chatId, assistantId }, 'Bootstrap verification Telegram self-retry also failed');
|
|
1775
|
-
}
|
|
1776
|
-
} catch (retryErr) {
|
|
1777
|
-
log.error({ err: retryErr, chatId, assistantId }, 'Bootstrap verification Telegram self-retry threw; giving up');
|
|
1778
|
-
}
|
|
1779
|
-
}, 3000);
|
|
1780
|
-
})();
|
|
1781
|
-
}
|