@vellumai/assistant 0.4.26 → 0.4.29
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 +169 -69
- package/Dockerfile +1 -1
- package/README.md +111 -112
- 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 +10 -9
- 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 +3 -0
- package/scripts/test.sh +89 -5
- package/src/__tests__/__snapshots__/ipc-snapshot.test.ts.snap +46 -0
- 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 +36 -23
- 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__/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__/browser-fill-credential.test.ts +1 -1
- package/src/__tests__/bundled-skill-retrieval-guard.test.ts +217 -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 +113 -70
- package/src/__tests__/channel-guardian.test.ts +173 -282
- package/src/__tests__/channel-readiness-service.test.ts +6 -2
- package/src/__tests__/channel-reply-delivery.test.ts +2 -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 +67 -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__/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 +3 -0
- 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 +14 -14
- 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 +2 -5
- 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 +2 -2
- 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 +16 -18
- package/src/__tests__/ingress-reconcile.test.ts +2 -2
- package/src/__tests__/ingress-routes-http.test.ts +2 -1
- package/src/__tests__/integrations-cli.test.ts +256 -0
- package/src/__tests__/intent-routing.test.ts +4 -5
- package/src/__tests__/invite-redemption-service.test.ts +4 -3
- package/src/__tests__/ipc-snapshot.test.ts +28 -0
- 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__/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__/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-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 +506 -227
- 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__/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-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-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-load-feature-flag.test.ts +1 -1
- 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-skill.test.ts +3 -2
- 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 +63 -77
- 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 +3 -2
- 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 +68 -48
- 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 +1121 -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 +17 -13
- 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 +362 -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/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/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/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/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 +446 -15
- package/src/config/bundled-skills/contacts/tools/contact-merge.ts +99 -20
- package/src/config/bundled-skills/contacts/tools/contact-search.ts +74 -17
- package/src/config/bundled-skills/contacts/tools/contact-upsert.ts +89 -26
- 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 +9 -9
- 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/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 +46 -24
- package/src/config/bundled-skills/image-studio/tools/media-generate-image.ts +36 -19
- 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 +20 -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 +177 -91
- 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 +7 -7
- 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/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/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/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/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/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/TOOLS.json +33 -15
- 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-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/sms-setup/SKILL.md +5 -8
- 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/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/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/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/get-weather.ts +5 -2
- package/src/config/calls-schema.ts +108 -63
- 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/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 +622 -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 +47 -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 +134 -98
- 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 +31 -18
- package/src/daemon/handlers/apps.ts +257 -111
- 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 +277 -106
- 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 +118 -71
- 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 +923 -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 -49
- 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 +16 -16
- 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 +88 -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/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 +8 -0
- 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 +25 -1
- 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 +261 -135
- package/src/daemon/session-agent-loop.ts +795 -253
- package/src/daemon/session-attachments.ts +104 -39
- package/src/daemon/session-conflict-gate.ts +72 -28
- package/src/daemon/session-dynamic-profile.ts +36 -22
- package/src/daemon/session-error.ts +50 -45
- package/src/daemon/session-evictor.ts +17 -10
- package/src/daemon/session-history.ts +201 -89
- package/src/daemon/session-lifecycle.ts +79 -42
- package/src/daemon/session-media-retry.ts +89 -41
- 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 -211
- package/src/daemon/session-tool-setup.ts +22 -17
- 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 +19 -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 +35 -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 +269 -108
- 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 +28 -21
- 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 +151 -77
- 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 +72 -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 +90 -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 +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/index.ts +82 -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 +44 -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/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 +100 -41
- package/src/messaging/providers/slack/types.ts +6 -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/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 +42 -24
- package/src/notifications/deterministic-checks.ts +78 -27
- package/src/notifications/emit-signal.ts +83 -45
- 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 +80 -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 +325 -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 +106 -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 +84 -36
- 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 +183 -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/route-policy.ts +170 -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 +12 -4
- package/src/runtime/channel-approvals.ts +41 -23
- package/src/runtime/channel-guardian-service.ts +144 -103
- package/src/runtime/channel-invite-transport.ts +4 -2
- 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 +21 -11
- 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 +86 -53
- 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-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 +8 -11
- package/src/runtime/http-router.ts +175 -0
- package/src/runtime/http-server.ts +931 -669
- package/src/runtime/http-types.ts +2 -2
- package/src/runtime/ingress-service.ts +182 -89
- package/src/runtime/invite-redemption-service.ts +211 -134
- package/src/runtime/invite-redemption-templates.ts +18 -11
- 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/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 +165 -74
- 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 +33 -25
- package/src/runtime/routes/channel-routes.ts +4 -6
- package/src/runtime/routes/contact-routes.ts +205 -16
- 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 +147 -56
- package/src/runtime/routes/guardian-approval-interception.ts +255 -880
- 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 +278 -1460
- package/src/runtime/routes/inbound-stages/acl-enforcement.ts +658 -0
- package/src/runtime/routes/inbound-stages/background-dispatch.ts +492 -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/ingress-routes.ts +34 -23
- package/src/runtime/routes/integration-routes.ts +60 -21
- 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/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 +26 -22
- 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 +118 -37
- 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 +9 -6
- 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 +159 -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 +452 -183
- 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 +151 -84
- 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 +6 -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 +124 -68
- package/src/tools/tool-approval-handler.ts +193 -138
- package/src/tools/types.ts +43 -23
- 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 +26 -26
- 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 +18 -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 +93 -27
- 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/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,46 @@
|
|
|
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 * as
|
|
23
|
-
import * as
|
|
24
|
-
import {
|
|
25
|
-
import
|
|
26
|
-
import {
|
|
27
|
-
import {
|
|
28
|
-
import {
|
|
29
|
-
import {
|
|
30
|
-
import {
|
|
31
|
-
import {
|
|
32
|
-
import { mintDaemonDeliveryToken } from '../auth/token-service.js';
|
|
33
|
-
import {
|
|
34
|
-
buildApprovalUIMetadata,
|
|
35
|
-
getApprovalInfoByConversation,
|
|
36
|
-
getChannelApprovalPrompt,
|
|
37
|
-
} from '../channel-approvals.js';
|
|
38
|
-
import {
|
|
39
|
-
bindSessionIdentity,
|
|
40
|
-
createOutboundSession,
|
|
41
|
-
findActiveSession,
|
|
42
|
-
getGuardianBinding,
|
|
43
|
-
getPendingChallenge,
|
|
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';
|
|
8
|
+
CHANNEL_IDS,
|
|
9
|
+
INTERFACE_IDS,
|
|
10
|
+
isChannelId,
|
|
11
|
+
parseInterfaceId,
|
|
12
|
+
} from "../../channels/types.js";
|
|
13
|
+
import type { TrustContext } from "../../daemon/session-runtime-assembly.js";
|
|
14
|
+
import * as attachmentsStore from "../../memory/attachments-store.js";
|
|
15
|
+
import * as channelDeliveryStore from "../../memory/channel-delivery-store.js";
|
|
16
|
+
import { recordConversationSeenSignal } from "../../memory/conversation-attention-store.js";
|
|
17
|
+
import * as externalConversationStore from "../../memory/external-conversation-store.js";
|
|
18
|
+
import { canonicalizeInboundIdentity } from "../../util/canonicalize-identity.js";
|
|
19
|
+
import { getLogger } from "../../util/logger.js";
|
|
20
|
+
import { DAEMON_INTERNAL_ASSISTANT_ID } from "../assistant-scope.js";
|
|
21
|
+
import { mintDaemonDeliveryToken } from "../auth/token-service.js";
|
|
22
|
+
import { deliverChannelReply } from "../gateway-client.js";
|
|
23
|
+
import { httpError } from "../http-errors.js";
|
|
59
24
|
import type {
|
|
60
25
|
ApprovalConversationGenerator,
|
|
61
26
|
ApprovalCopyGenerator,
|
|
62
27
|
GuardianActionCopyGenerator,
|
|
63
28
|
GuardianFollowUpConversationGenerator,
|
|
64
29
|
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
|
-
}
|
|
30
|
+
} from "../http-types.js";
|
|
31
|
+
import { resolveTrustContext } from "../trust-context-resolver.js";
|
|
32
|
+
import { canonicalChannelAssistantId } from "./channel-route-shared.js";
|
|
33
|
+
import { handleApprovalInterception } from "./guardian-approval-interception.js";
|
|
34
|
+
import { enforceIngressAcl } from "./inbound-stages/acl-enforcement.js";
|
|
35
|
+
import { processChannelMessageInBackground } from "./inbound-stages/background-dispatch.js";
|
|
36
|
+
import { handleBootstrapIntercept } from "./inbound-stages/bootstrap-intercept.js";
|
|
37
|
+
import { handleEditIntercept } from "./inbound-stages/edit-intercept.js";
|
|
38
|
+
import { handleEscalationIntercept } from "./inbound-stages/escalation-intercept.js";
|
|
39
|
+
import { handleGuardianReplyIntercept } from "./inbound-stages/guardian-reply-intercept.js";
|
|
40
|
+
import { runSecretIngressCheck } from "./inbound-stages/secret-ingress-check.js";
|
|
41
|
+
import { handleVerificationIntercept } from "./inbound-stages/verification-intercept.js";
|
|
42
|
+
|
|
43
|
+
import "../channel-invite-transports/telegram.js";
|
|
44
|
+
import "../channel-invite-transports/voice.js";
|
|
45
|
+
|
|
46
|
+
const log = getLogger("runtime-http");
|
|
95
47
|
|
|
96
48
|
export async function handleChannelInbound(
|
|
97
49
|
req: Request,
|
|
@@ -113,7 +65,7 @@ export async function handleChannelInbound(
|
|
|
113
65
|
// a single token from request start.
|
|
114
66
|
const mintBearerToken = (): string => mintDaemonDeliveryToken();
|
|
115
67
|
|
|
116
|
-
const body = await req.json() as {
|
|
68
|
+
const body = (await req.json()) as {
|
|
117
69
|
sourceChannel?: string;
|
|
118
70
|
interface?: string;
|
|
119
71
|
conversationExternalId?: string;
|
|
@@ -139,63 +91,92 @@ export async function handleChannelInbound(
|
|
|
139
91
|
sourceMetadata,
|
|
140
92
|
} = body;
|
|
141
93
|
|
|
142
|
-
if (!body.sourceChannel || typeof body.sourceChannel !==
|
|
143
|
-
return httpError(
|
|
94
|
+
if (!body.sourceChannel || typeof body.sourceChannel !== "string") {
|
|
95
|
+
return httpError("BAD_REQUEST", "sourceChannel is required", 400);
|
|
144
96
|
}
|
|
145
97
|
// Validate and narrow to canonical ChannelId at the boundary — the gateway
|
|
146
98
|
// only sends well-known channel strings, so an unknown value is rejected.
|
|
147
99
|
if (!isChannelId(body.sourceChannel)) {
|
|
148
|
-
return httpError(
|
|
100
|
+
return httpError(
|
|
101
|
+
"BAD_REQUEST",
|
|
102
|
+
`Invalid sourceChannel: ${
|
|
103
|
+
body.sourceChannel
|
|
104
|
+
}. Valid values: ${CHANNEL_IDS.join(", ")}`,
|
|
105
|
+
400,
|
|
106
|
+
);
|
|
149
107
|
}
|
|
150
108
|
|
|
151
109
|
const sourceChannel = body.sourceChannel;
|
|
152
110
|
|
|
153
|
-
if (!body.interface || typeof body.interface !==
|
|
154
|
-
return httpError(
|
|
111
|
+
if (!body.interface || typeof body.interface !== "string") {
|
|
112
|
+
return httpError("BAD_REQUEST", "interface is required", 400);
|
|
155
113
|
}
|
|
156
114
|
const sourceInterface = parseInterfaceId(body.interface);
|
|
157
115
|
if (!sourceInterface) {
|
|
158
|
-
return httpError(
|
|
116
|
+
return httpError(
|
|
117
|
+
"BAD_REQUEST",
|
|
118
|
+
`Invalid interface: ${body.interface}. Valid values: ${INTERFACE_IDS.join(
|
|
119
|
+
", ",
|
|
120
|
+
)}`,
|
|
121
|
+
400,
|
|
122
|
+
);
|
|
159
123
|
}
|
|
160
124
|
|
|
161
|
-
if (!conversationExternalId || typeof conversationExternalId !==
|
|
162
|
-
return httpError(
|
|
125
|
+
if (!conversationExternalId || typeof conversationExternalId !== "string") {
|
|
126
|
+
return httpError("BAD_REQUEST", "conversationExternalId is required", 400);
|
|
163
127
|
}
|
|
164
|
-
if (
|
|
165
|
-
|
|
128
|
+
if (
|
|
129
|
+
!body.actorExternalId ||
|
|
130
|
+
typeof body.actorExternalId !== "string" ||
|
|
131
|
+
!body.actorExternalId.trim()
|
|
132
|
+
) {
|
|
133
|
+
return httpError("BAD_REQUEST", "actorExternalId is required", 400);
|
|
166
134
|
}
|
|
167
|
-
if (!externalMessageId || typeof externalMessageId !==
|
|
168
|
-
return httpError(
|
|
135
|
+
if (!externalMessageId || typeof externalMessageId !== "string") {
|
|
136
|
+
return httpError("BAD_REQUEST", "externalMessageId is required", 400);
|
|
169
137
|
}
|
|
170
138
|
|
|
171
139
|
// Reject non-string content regardless of whether attachments are present.
|
|
172
|
-
if (content != null && typeof content !==
|
|
173
|
-
return httpError(
|
|
140
|
+
if (content != null && typeof content !== "string") {
|
|
141
|
+
return httpError("BAD_REQUEST", "content must be a string", 400);
|
|
174
142
|
}
|
|
175
143
|
|
|
176
|
-
const trimmedContent = typeof content ===
|
|
177
|
-
const hasAttachments =
|
|
144
|
+
const trimmedContent = typeof content === "string" ? content.trim() : "";
|
|
145
|
+
const hasAttachments =
|
|
146
|
+
Array.isArray(attachmentIds) && attachmentIds.length > 0;
|
|
178
147
|
|
|
179
|
-
const hasCallbackData =
|
|
148
|
+
const hasCallbackData =
|
|
149
|
+
typeof body.callbackData === "string" && body.callbackData.length > 0;
|
|
180
150
|
|
|
181
|
-
if (
|
|
182
|
-
|
|
151
|
+
if (
|
|
152
|
+
trimmedContent.length === 0 &&
|
|
153
|
+
!hasAttachments &&
|
|
154
|
+
!isEdit &&
|
|
155
|
+
!hasCallbackData
|
|
156
|
+
) {
|
|
157
|
+
return httpError(
|
|
158
|
+
"BAD_REQUEST",
|
|
159
|
+
"content or attachmentIds is required",
|
|
160
|
+
400,
|
|
161
|
+
);
|
|
183
162
|
}
|
|
184
163
|
|
|
185
164
|
// Canonicalize the assistant ID so all DB-facing operations use the
|
|
186
165
|
// consistent 'self' key regardless of what the gateway sent.
|
|
187
166
|
const canonicalAssistantId = canonicalChannelAssistantId(assistantId);
|
|
188
167
|
if (canonicalAssistantId !== assistantId) {
|
|
189
|
-
log.debug(
|
|
168
|
+
log.debug(
|
|
169
|
+
{ raw: assistantId, canonical: canonicalAssistantId },
|
|
170
|
+
"Canonicalized channel assistant ID",
|
|
171
|
+
);
|
|
190
172
|
}
|
|
191
173
|
|
|
192
174
|
// Coerce actorExternalId to a string at the boundary — the field
|
|
193
175
|
// comes from unvalidated JSON and may be a number, object, or other
|
|
194
176
|
// non-string type. Non-string truthy values would throw inside
|
|
195
177
|
// canonicalizeInboundIdentity when it calls .trim().
|
|
196
|
-
const rawSenderId =
|
|
197
|
-
? String(body.actorExternalId)
|
|
198
|
-
: undefined;
|
|
178
|
+
const rawSenderId =
|
|
179
|
+
body.actorExternalId != null ? String(body.actorExternalId) : undefined;
|
|
199
180
|
|
|
200
181
|
// Canonicalize the sender identity so all trust lookups, member matching,
|
|
201
182
|
// and guardian binding comparisons use a normalized form. Phone-like
|
|
@@ -212,255 +193,24 @@ export async function handleChannelInbound(
|
|
|
212
193
|
const hasSenderIdentityClaim = rawSenderId !== undefined;
|
|
213
194
|
|
|
214
195
|
// ── 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,
|
|
196
|
+
const aclResult = await enforceIngressAcl({
|
|
197
|
+
canonicalSenderId,
|
|
198
|
+
hasSenderIdentityClaim,
|
|
199
|
+
rawSenderId,
|
|
200
|
+
sourceChannel,
|
|
201
|
+
conversationExternalId,
|
|
202
|
+
canonicalAssistantId,
|
|
203
|
+
trimmedContent,
|
|
244
204
|
sourceMetadata: body.sourceMetadata,
|
|
205
|
+
actorDisplayName: body.actorDisplayName,
|
|
206
|
+
actorUsername: body.actorUsername,
|
|
207
|
+
replyCallbackUrl: body.replyCallbackUrl,
|
|
208
|
+
mintBearerToken,
|
|
209
|
+
assistantId,
|
|
210
|
+
externalMessageId,
|
|
245
211
|
});
|
|
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
|
-
}
|
|
212
|
+
if (aclResult.earlyResponse) return aclResult.earlyResponse;
|
|
213
|
+
const { resolvedMember, guardianVerifyCode } = aclResult;
|
|
464
214
|
|
|
465
215
|
if (hasAttachments) {
|
|
466
216
|
const resolved = attachmentsStore.getAttachmentsByIds(attachmentIds);
|
|
@@ -468,78 +218,35 @@ export async function handleChannelInbound(
|
|
|
468
218
|
const resolvedIds = new Set(resolved.map((a) => a.id));
|
|
469
219
|
const missing = attachmentIds.filter((id) => !resolvedIds.has(id));
|
|
470
220
|
return Response.json(
|
|
471
|
-
{ error: `Attachment IDs not found: ${missing.join(
|
|
221
|
+
{ error: `Attachment IDs not found: ${missing.join(", ")}` },
|
|
472
222
|
{ status: 400 },
|
|
473
223
|
);
|
|
474
224
|
}
|
|
475
225
|
}
|
|
476
226
|
|
|
477
|
-
const sourceMessageId =
|
|
478
|
-
|
|
479
|
-
|
|
227
|
+
const sourceMessageId =
|
|
228
|
+
typeof sourceMetadata?.messageId === "string"
|
|
229
|
+
? sourceMetadata.messageId
|
|
230
|
+
: undefined;
|
|
480
231
|
|
|
481
232
|
if (isEdit && !sourceMessageId) {
|
|
482
|
-
return httpError(
|
|
233
|
+
return httpError(
|
|
234
|
+
"BAD_REQUEST",
|
|
235
|
+
"sourceMetadata.messageId is required for edits",
|
|
236
|
+
400,
|
|
237
|
+
);
|
|
483
238
|
}
|
|
484
239
|
|
|
485
240
|
// ── Edit path: update existing message content, no new agent loop ──
|
|
486
241
|
if (isEdit && sourceMessageId) {
|
|
487
|
-
|
|
488
|
-
const editResult = channelDeliveryStore.recordInbound(
|
|
242
|
+
return handleEditIntercept({
|
|
489
243
|
sourceChannel,
|
|
490
244
|
conversationExternalId,
|
|
491
245
|
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,
|
|
246
|
+
sourceMessageId,
|
|
247
|
+
canonicalAssistantId,
|
|
248
|
+
assistantId,
|
|
249
|
+
content,
|
|
543
250
|
});
|
|
544
251
|
}
|
|
545
252
|
|
|
@@ -558,18 +265,30 @@ export async function handleChannelInbound(
|
|
|
558
265
|
// gateway retries (duplicates) re-attempt delivery here. On success the
|
|
559
266
|
// pending marker is cleared so further duplicates short-circuit normally.
|
|
560
267
|
if (result.duplicate && replyCallbackUrl) {
|
|
561
|
-
const pendingReply = channelDeliveryStore.getPendingVerificationReply(
|
|
268
|
+
const pendingReply = channelDeliveryStore.getPendingVerificationReply(
|
|
269
|
+
result.eventId,
|
|
270
|
+
);
|
|
562
271
|
if (pendingReply) {
|
|
563
272
|
try {
|
|
564
|
-
await deliverChannelReply(
|
|
565
|
-
|
|
566
|
-
|
|
567
|
-
|
|
568
|
-
|
|
273
|
+
await deliverChannelReply(
|
|
274
|
+
replyCallbackUrl,
|
|
275
|
+
{
|
|
276
|
+
chatId: pendingReply.chatId,
|
|
277
|
+
text: pendingReply.text,
|
|
278
|
+
assistantId: pendingReply.assistantId,
|
|
279
|
+
},
|
|
280
|
+
mintBearerToken(),
|
|
281
|
+
);
|
|
569
282
|
channelDeliveryStore.clearPendingVerificationReply(result.eventId);
|
|
570
|
-
log.info(
|
|
283
|
+
log.info(
|
|
284
|
+
{ eventId: result.eventId },
|
|
285
|
+
"Retried pending verification reply: delivered",
|
|
286
|
+
);
|
|
571
287
|
} catch (retryErr) {
|
|
572
|
-
log.error(
|
|
288
|
+
log.error(
|
|
289
|
+
{ err: retryErr, eventId: result.eventId },
|
|
290
|
+
"Retry of pending verification reply failed; will retry on next duplicate",
|
|
291
|
+
);
|
|
573
292
|
}
|
|
574
293
|
return Response.json({
|
|
575
294
|
accepted: true,
|
|
@@ -594,322 +313,86 @@ export async function handleChannelInbound(
|
|
|
594
313
|
}
|
|
595
314
|
|
|
596
315
|
// ── Ingress escalation ──
|
|
597
|
-
|
|
598
|
-
|
|
599
|
-
|
|
600
|
-
|
|
601
|
-
|
|
602
|
-
|
|
603
|
-
|
|
604
|
-
|
|
605
|
-
|
|
606
|
-
|
|
607
|
-
|
|
608
|
-
|
|
609
|
-
|
|
610
|
-
|
|
611
|
-
|
|
612
|
-
|
|
613
|
-
|
|
614
|
-
|
|
615
|
-
|
|
616
|
-
|
|
617
|
-
assistantId: canonicalAssistantId,
|
|
618
|
-
});
|
|
619
|
-
|
|
620
|
-
try {
|
|
621
|
-
createCanonicalGuardianRequest({
|
|
622
|
-
kind: 'tool_approval',
|
|
623
|
-
sourceType: 'channel',
|
|
624
|
-
sourceChannel,
|
|
625
|
-
conversationId: result.conversationId,
|
|
626
|
-
requesterExternalUserId: canonicalSenderId ?? rawSenderId ?? undefined,
|
|
627
|
-
guardianExternalUserId: binding.guardianExternalUserId,
|
|
628
|
-
guardianPrincipalId: binding.guardianPrincipalId,
|
|
629
|
-
toolName: 'ingress_message',
|
|
630
|
-
questionText: 'Ingress policy requires guardian approval',
|
|
631
|
-
expiresAt: new Date(Date.now() + GUARDIAN_APPROVAL_TTL_MS).toISOString(),
|
|
632
|
-
});
|
|
633
|
-
} catch (err) {
|
|
634
|
-
log.warn(
|
|
635
|
-
{ err, conversationId: result.conversationId, sourceChannel },
|
|
636
|
-
'Failed to create canonical guardian request for ingress escalation — escalation continues via notification pipeline',
|
|
637
|
-
);
|
|
638
|
-
}
|
|
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
|
-
}
|
|
316
|
+
const escalationResponse = handleEscalationIntercept({
|
|
317
|
+
resolvedMember,
|
|
318
|
+
canonicalAssistantId,
|
|
319
|
+
sourceChannel,
|
|
320
|
+
sourceInterface,
|
|
321
|
+
conversationExternalId,
|
|
322
|
+
externalMessageId,
|
|
323
|
+
conversationId: result.conversationId,
|
|
324
|
+
eventId: result.eventId,
|
|
325
|
+
content,
|
|
326
|
+
attachmentIds,
|
|
327
|
+
sourceMetadata: body.sourceMetadata,
|
|
328
|
+
actorDisplayName: body.actorDisplayName,
|
|
329
|
+
actorExternalId: body.actorExternalId,
|
|
330
|
+
actorUsername: body.actorUsername,
|
|
331
|
+
replyCallbackUrl: body.replyCallbackUrl,
|
|
332
|
+
canonicalSenderId,
|
|
333
|
+
rawSenderId,
|
|
334
|
+
});
|
|
335
|
+
if (escalationResponse) return escalationResponse;
|
|
673
336
|
|
|
674
337
|
const metadataHintsRaw = sourceMetadata?.hints;
|
|
675
338
|
const metadataHints = Array.isArray(metadataHintsRaw)
|
|
676
|
-
? metadataHintsRaw.filter(
|
|
339
|
+
? metadataHintsRaw.filter(
|
|
340
|
+
(hint): hint is string =>
|
|
341
|
+
typeof hint === "string" && hint.trim().length > 0,
|
|
342
|
+
)
|
|
677
343
|
: [];
|
|
678
|
-
const metadataUxBrief =
|
|
679
|
-
|
|
680
|
-
|
|
344
|
+
const metadataUxBrief =
|
|
345
|
+
typeof sourceMetadata?.uxBrief === "string" &&
|
|
346
|
+
sourceMetadata.uxBrief.trim().length > 0
|
|
347
|
+
? sourceMetadata.uxBrief.trim()
|
|
348
|
+
: undefined;
|
|
681
349
|
|
|
682
350
|
// Extract channel command intent (e.g. /start from Telegram)
|
|
683
351
|
const rawCommandIntent = sourceMetadata?.commandIntent;
|
|
684
|
-
const commandIntent =
|
|
685
|
-
|
|
686
|
-
|
|
352
|
+
const commandIntent =
|
|
353
|
+
rawCommandIntent &&
|
|
354
|
+
typeof rawCommandIntent === "object" &&
|
|
355
|
+
!Array.isArray(rawCommandIntent)
|
|
356
|
+
? (rawCommandIntent as Record<string, unknown>)
|
|
357
|
+
: undefined;
|
|
687
358
|
|
|
688
359
|
// Preserve locale from sourceMetadata so the model can greet in the user's language
|
|
689
|
-
const sourceLanguageCode =
|
|
690
|
-
|
|
691
|
-
|
|
360
|
+
const sourceLanguageCode =
|
|
361
|
+
typeof sourceMetadata?.languageCode === "string" &&
|
|
362
|
+
sourceMetadata.languageCode.trim().length > 0
|
|
363
|
+
? sourceMetadata.languageCode.trim()
|
|
364
|
+
: undefined;
|
|
692
365
|
|
|
693
366
|
// ── 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
|
-
}
|
|
367
|
+
const bootstrapResponse = await handleBootstrapIntercept({
|
|
368
|
+
isDuplicate: result.duplicate,
|
|
369
|
+
commandIntent,
|
|
370
|
+
rawSenderId,
|
|
371
|
+
canonicalAssistantId,
|
|
372
|
+
sourceChannel,
|
|
373
|
+
conversationExternalId,
|
|
374
|
+
eventId: result.eventId,
|
|
375
|
+
});
|
|
376
|
+
if (bootstrapResponse) return bootstrapResponse;
|
|
752
377
|
|
|
753
378
|
// ── 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
|
-
}
|
|
379
|
+
const verificationResponse = await handleVerificationIntercept({
|
|
380
|
+
isDuplicate: result.duplicate,
|
|
381
|
+
guardianVerifyCode,
|
|
382
|
+
rawSenderId,
|
|
383
|
+
canonicalSenderId,
|
|
384
|
+
canonicalAssistantId,
|
|
385
|
+
sourceChannel,
|
|
386
|
+
conversationExternalId,
|
|
387
|
+
conversationId: result.conversationId,
|
|
388
|
+
eventId: result.eventId,
|
|
389
|
+
replyCallbackUrl,
|
|
390
|
+
mintBearerToken,
|
|
391
|
+
assistantId,
|
|
392
|
+
actorDisplayName: body.actorDisplayName,
|
|
393
|
+
actorUsername: body.actorUsername,
|
|
394
|
+
});
|
|
395
|
+
if (verificationResponse) return verificationResponse;
|
|
913
396
|
|
|
914
397
|
// Legacy voice guardian action interception removed — all guardian reply
|
|
915
398
|
// routing now flows through the canonical router below (routeGuardianReply),
|
|
@@ -919,7 +402,7 @@ export async function handleChannelInbound(
|
|
|
919
402
|
// ── Actor role resolution ──
|
|
920
403
|
// Uses shared channel-agnostic resolution so all ingress paths classify
|
|
921
404
|
// guardian vs non-guardian actors the same way.
|
|
922
|
-
const
|
|
405
|
+
const trustCtx: TrustContext = resolveTrustContext({
|
|
923
406
|
assistantId: canonicalAssistantId,
|
|
924
407
|
sourceChannel,
|
|
925
408
|
conversationExternalId,
|
|
@@ -928,99 +411,26 @@ export async function handleChannelInbound(
|
|
|
928
411
|
actorDisplayName: body.actorDisplayName,
|
|
929
412
|
});
|
|
930
413
|
|
|
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
414
|
// ── 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
|
-
}
|
|
415
|
+
const guardianReplyResult = await handleGuardianReplyIntercept({
|
|
416
|
+
isDuplicate: result.duplicate,
|
|
417
|
+
trimmedContent,
|
|
418
|
+
hasCallbackData,
|
|
419
|
+
callbackData: body.callbackData,
|
|
420
|
+
rawSenderId,
|
|
421
|
+
canonicalSenderId,
|
|
422
|
+
canonicalAssistantId,
|
|
423
|
+
sourceChannel,
|
|
424
|
+
conversationExternalId,
|
|
425
|
+
conversationId: result.conversationId,
|
|
426
|
+
eventId: result.eventId,
|
|
427
|
+
replyCallbackUrl,
|
|
428
|
+
mintBearerToken,
|
|
429
|
+
trustClass: trustCtx.trustClass,
|
|
430
|
+
guardianPrincipalId: trustCtx.guardianPrincipalId,
|
|
431
|
+
approvalConversationGenerator,
|
|
432
|
+
});
|
|
433
|
+
if (guardianReplyResult.response) return guardianReplyResult.response;
|
|
1024
434
|
|
|
1025
435
|
// ── Approval interception ──
|
|
1026
436
|
// Keep this active whenever callback context is available.
|
|
@@ -1030,7 +440,7 @@ export async function handleChannelInbound(
|
|
|
1030
440
|
if (
|
|
1031
441
|
replyCallbackUrl &&
|
|
1032
442
|
!result.duplicate &&
|
|
1033
|
-
!skipApprovalInterception
|
|
443
|
+
!guardianReplyResult.skipApprovalInterception
|
|
1034
444
|
) {
|
|
1035
445
|
const approvalResult = await handleApprovalInterception({
|
|
1036
446
|
conversationId: result.conversationId,
|
|
@@ -1041,7 +451,7 @@ export async function handleChannelInbound(
|
|
|
1041
451
|
actorExternalId: canonicalSenderId ?? rawSenderId,
|
|
1042
452
|
replyCallbackUrl,
|
|
1043
453
|
bearerToken: mintBearerToken(),
|
|
1044
|
-
|
|
454
|
+
trustCtx,
|
|
1045
455
|
assistantId: canonicalAssistantId,
|
|
1046
456
|
approvalCopyGenerator,
|
|
1047
457
|
approvalConversationGenerator,
|
|
@@ -1049,37 +459,42 @@ export async function handleChannelInbound(
|
|
|
1049
459
|
|
|
1050
460
|
if (approvalResult.handled) {
|
|
1051
461
|
// Record inferred seen signal for all handled Telegram approval interactions
|
|
1052
|
-
if (sourceChannel ===
|
|
462
|
+
if (sourceChannel === "telegram") {
|
|
1053
463
|
try {
|
|
1054
464
|
if (hasCallbackData) {
|
|
1055
|
-
const cbPreview =
|
|
1056
|
-
|
|
1057
|
-
|
|
465
|
+
const cbPreview =
|
|
466
|
+
body.callbackData!.length > 80
|
|
467
|
+
? body.callbackData!.slice(0, 80) + "..."
|
|
468
|
+
: body.callbackData!;
|
|
1058
469
|
recordConversationSeenSignal({
|
|
1059
470
|
conversationId: result.conversationId,
|
|
1060
471
|
assistantId: canonicalAssistantId,
|
|
1061
|
-
signalType:
|
|
1062
|
-
confidence:
|
|
1063
|
-
sourceChannel:
|
|
1064
|
-
source:
|
|
472
|
+
signalType: "telegram_callback",
|
|
473
|
+
confidence: "inferred",
|
|
474
|
+
sourceChannel: "telegram",
|
|
475
|
+
source: "inbound-message-handler",
|
|
1065
476
|
evidenceText: `User tapped callback: '${cbPreview}'`,
|
|
1066
477
|
});
|
|
1067
478
|
} else {
|
|
1068
|
-
const msgPreview =
|
|
1069
|
-
|
|
1070
|
-
|
|
479
|
+
const msgPreview =
|
|
480
|
+
trimmedContent.length > 80
|
|
481
|
+
? trimmedContent.slice(0, 80) + "..."
|
|
482
|
+
: trimmedContent;
|
|
1071
483
|
recordConversationSeenSignal({
|
|
1072
484
|
conversationId: result.conversationId,
|
|
1073
485
|
assistantId: canonicalAssistantId,
|
|
1074
|
-
signalType:
|
|
1075
|
-
confidence:
|
|
1076
|
-
sourceChannel:
|
|
1077
|
-
source:
|
|
486
|
+
signalType: "telegram_inbound_message",
|
|
487
|
+
confidence: "inferred",
|
|
488
|
+
sourceChannel: "telegram",
|
|
489
|
+
source: "inbound-message-handler",
|
|
1078
490
|
evidenceText: `User sent plain-text approval reply: '${msgPreview}'`,
|
|
1079
491
|
});
|
|
1080
492
|
}
|
|
1081
493
|
} catch (err) {
|
|
1082
|
-
log.warn(
|
|
494
|
+
log.warn(
|
|
495
|
+
{ err, conversationId: result.conversationId },
|
|
496
|
+
"Failed to record seen signal for Telegram approval interaction",
|
|
497
|
+
);
|
|
1083
498
|
}
|
|
1084
499
|
}
|
|
1085
500
|
|
|
@@ -1098,22 +513,26 @@ export async function handleChannelInbound(
|
|
|
1098
513
|
// so checking for empty content alone would miss stale callbacks.
|
|
1099
514
|
if (hasCallbackData) {
|
|
1100
515
|
// Record seen signal even for stale callbacks — the user still interacted
|
|
1101
|
-
if (sourceChannel ===
|
|
516
|
+
if (sourceChannel === "telegram") {
|
|
1102
517
|
try {
|
|
1103
|
-
const cbPreview =
|
|
1104
|
-
|
|
1105
|
-
|
|
518
|
+
const cbPreview =
|
|
519
|
+
body.callbackData!.length > 80
|
|
520
|
+
? body.callbackData!.slice(0, 80) + "..."
|
|
521
|
+
: body.callbackData!;
|
|
1106
522
|
recordConversationSeenSignal({
|
|
1107
523
|
conversationId: result.conversationId,
|
|
1108
524
|
assistantId: canonicalAssistantId,
|
|
1109
|
-
signalType:
|
|
1110
|
-
confidence:
|
|
1111
|
-
sourceChannel:
|
|
1112
|
-
source:
|
|
525
|
+
signalType: "telegram_callback",
|
|
526
|
+
confidence: "inferred",
|
|
527
|
+
sourceChannel: "telegram",
|
|
528
|
+
source: "inbound-message-handler",
|
|
1113
529
|
evidenceText: `User tapped stale callback: '${cbPreview}'`,
|
|
1114
530
|
});
|
|
1115
531
|
} catch (err) {
|
|
1116
|
-
log.warn(
|
|
532
|
+
log.warn(
|
|
533
|
+
{ err, conversationId: result.conversationId },
|
|
534
|
+
"Failed to record seen signal for stale Telegram callback",
|
|
535
|
+
);
|
|
1117
536
|
}
|
|
1118
537
|
}
|
|
1119
538
|
|
|
@@ -1121,7 +540,7 @@ export async function handleChannelInbound(
|
|
|
1121
540
|
accepted: true,
|
|
1122
541
|
duplicate: false,
|
|
1123
542
|
eventId: result.eventId,
|
|
1124
|
-
approval:
|
|
543
|
+
approval: "stale_ignored",
|
|
1125
544
|
});
|
|
1126
545
|
}
|
|
1127
546
|
}
|
|
@@ -1129,56 +548,24 @@ export async function handleChannelInbound(
|
|
|
1129
548
|
// For new (non-duplicate) messages, run the secret ingress check
|
|
1130
549
|
// synchronously, then fire off the agent loop in the background.
|
|
1131
550
|
if (!result.duplicate && processMessage) {
|
|
1132
|
-
|
|
1133
|
-
|
|
1134
|
-
|
|
1135
|
-
|
|
1136
|
-
|
|
1137
|
-
|
|
1138
|
-
|
|
1139
|
-
|
|
1140
|
-
|
|
1141
|
-
|
|
551
|
+
runSecretIngressCheck({
|
|
552
|
+
eventId: result.eventId,
|
|
553
|
+
sourceChannel,
|
|
554
|
+
conversationExternalId,
|
|
555
|
+
externalMessageId,
|
|
556
|
+
conversationId: result.conversationId,
|
|
557
|
+
content,
|
|
558
|
+
trimmedContent,
|
|
559
|
+
attachmentIds,
|
|
560
|
+
sourceMetadata: body.sourceMetadata,
|
|
561
|
+
actorDisplayName: body.actorDisplayName,
|
|
562
|
+
actorExternalId: body.actorExternalId,
|
|
563
|
+
actorUsername: body.actorUsername,
|
|
564
|
+
trustCtx,
|
|
1142
565
|
replyCallbackUrl,
|
|
1143
|
-
|
|
566
|
+
canonicalAssistantId,
|
|
1144
567
|
});
|
|
1145
568
|
|
|
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
569
|
// Fire-and-forget: process the message and deliver the reply in the background.
|
|
1183
570
|
// The HTTP response returns immediately so the gateway webhook is not blocked.
|
|
1184
571
|
// The onEvent callback in processMessage registers pending interactions, and
|
|
@@ -1187,12 +574,12 @@ export async function handleChannelInbound(
|
|
|
1187
574
|
processMessage,
|
|
1188
575
|
conversationId: result.conversationId,
|
|
1189
576
|
eventId: result.eventId,
|
|
1190
|
-
content: content ??
|
|
577
|
+
content: content ?? "",
|
|
1191
578
|
attachmentIds: hasAttachments ? attachmentIds : undefined,
|
|
1192
579
|
sourceChannel,
|
|
1193
580
|
sourceInterface,
|
|
1194
581
|
externalChatId: conversationExternalId,
|
|
1195
|
-
|
|
582
|
+
trustCtx,
|
|
1196
583
|
metadataHints,
|
|
1197
584
|
metadataUxBrief,
|
|
1198
585
|
commandIntent,
|
|
@@ -1210,572 +597,3 @@ export async function handleChannelInbound(
|
|
|
1210
597
|
eventId: result.eventId,
|
|
1211
598
|
});
|
|
1212
599
|
}
|
|
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
|
-
}
|