@vellumai/assistant 0.4.26 → 0.4.30
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.env.example +2 -2
- package/AGENTS.md +5 -0
- package/ARCHITECTURE.md +207 -105
- package/Dockerfile +1 -1
- package/README.md +111 -113
- package/bun.lock +0 -3
- package/docs/architecture/integrations.md +0 -1
- package/docs/architecture/memory.md +100 -63
- package/docs/error-handling.md +71 -0
- package/docs/runbook-trusted-contacts.md +89 -52
- package/docs/trusted-contact-access.md +48 -46
- package/package.json +3 -3
- package/scripts/compare-benchmarks.sh +12 -5
- package/scripts/ipc/check-swift-decoder-drift.ts +5 -3
- package/scripts/test.sh +89 -5
- package/src/__tests__/__snapshots__/ipc-snapshot.test.ts.snap +50 -37
- package/src/__tests__/access-request-decision.test.ts +0 -1
- package/src/__tests__/account-registry.test.ts +1 -1
- package/src/__tests__/actor-token-service.test.ts +40 -26
- package/src/__tests__/agent-loop-thinking.test.ts +29 -13
- package/src/__tests__/agent-loop.test.ts +2 -1
- package/src/__tests__/app-builder-tool-scripts.test.ts +1 -1
- package/src/__tests__/app-executors.test.ts +7 -17
- package/src/__tests__/approval-routes-http.test.ts +2 -2
- package/src/__tests__/asset-materialize-tool.test.ts +7 -7
- package/src/__tests__/asset-search-tool.test.ts +7 -7
- package/src/__tests__/assistant-feature-flags-integration.test.ts +18 -10
- package/src/__tests__/browser-fill-credential.test.ts +1 -1
- package/src/__tests__/browser-skill-endstate.test.ts +10 -1
- package/src/__tests__/bundled-skill-retrieval-guard.test.ts +218 -0
- package/src/__tests__/call-controller.test.ts +99 -69
- package/src/__tests__/call-start-guardian-guard.test.ts +1 -1
- package/src/__tests__/channel-approval-routes.test.ts +157 -114
- package/src/__tests__/channel-approval.test.ts +8 -0
- package/src/__tests__/channel-approvals.test.ts +39 -1
- package/src/__tests__/channel-guardian.test.ts +176 -275
- package/src/__tests__/channel-readiness-service.test.ts +6 -2
- package/src/__tests__/channel-reply-delivery.test.ts +33 -2
- package/src/__tests__/channel-retry-sweep.test.ts +14 -14
- package/src/__tests__/checker.test.ts +12 -31
- package/src/__tests__/claude-code-tool-profiles.test.ts +1 -1
- package/src/__tests__/commit-message-enrichment-service.test.ts +71 -59
- package/src/__tests__/compaction.benchmark.test.ts +6 -2
- package/src/__tests__/computer-use-tools.test.ts +1 -1
- package/src/__tests__/config-schema.test.ts +66 -7
- package/src/__tests__/confirmation-request-guardian-bridge.test.ts +29 -29
- package/src/__tests__/contacts-tools.test.ts +63 -2
- package/src/__tests__/context-overflow-approval.test.ts +141 -0
- package/src/__tests__/context-overflow-policy.test.ts +171 -0
- package/src/__tests__/context-overflow-reducer.test.ts +533 -0
- package/src/__tests__/context-window-manager.test.ts +97 -0
- package/src/__tests__/conversation-attention-telegram.test.ts +38 -46
- package/src/__tests__/conversation-pairing.test.ts +2 -2
- package/src/__tests__/conversation-routes-guardian-reply.test.ts +214 -10
- package/src/__tests__/conversation-routes.test.ts +4 -7
- package/src/__tests__/credential-broker-browser-fill.test.ts +13 -2
- package/src/__tests__/credential-security-e2e.test.ts +1 -1
- package/src/__tests__/credential-security-invariants.test.ts +1 -1
- package/src/__tests__/credential-vault-unit.test.ts +1 -1
- package/src/__tests__/credential-vault.test.ts +11 -8
- package/src/__tests__/daemon-lifecycle.test.ts +2 -2
- package/src/__tests__/daemon-server-session-init.test.ts +6 -6
- package/src/__tests__/delete-managed-skill-tool.test.ts +1 -1
- package/src/__tests__/deterministic-verification-control-plane.test.ts +2 -2
- package/src/__tests__/dynamic-skill-workflow-prompt.test.ts +9 -0
- package/src/__tests__/emit-signal-routing-intent.test.ts +4 -0
- package/src/__tests__/encrypted-store.test.ts +10 -7
- package/src/__tests__/ephemeral-permissions.test.ts +3 -3
- package/src/__tests__/file-edit-tool.test.ts +1 -1
- package/src/__tests__/file-read-tool.test.ts +1 -1
- package/src/__tests__/file-write-tool.test.ts +1 -1
- package/src/__tests__/fixtures/credential-security-fixtures.ts +87 -64
- package/src/__tests__/fixtures/media-reuse-fixtures.ts +37 -31
- package/src/__tests__/fixtures/mock-signup-server.ts +171 -115
- package/src/__tests__/fixtures/proxy-fixtures.ts +39 -39
- package/src/__tests__/followup-tools.test.ts +1 -1
- package/src/__tests__/gateway-only-guard.test.ts +4 -0
- package/src/__tests__/gemini-image-service.test.ts +2 -2
- package/src/__tests__/guardian-actions-endpoint.test.ts +543 -1
- package/src/__tests__/guardian-control-plane-policy.test.ts +15 -15
- package/src/__tests__/guardian-dispatch.test.ts +79 -1
- package/src/__tests__/guardian-grant-minting.test.ts +20 -20
- package/src/__tests__/guardian-outbound-http.test.ts +1 -2
- package/src/__tests__/guardian-principal-id-roundtrip.test.ts +0 -41
- package/src/__tests__/guardian-routing-invariants.test.ts +36 -16
- package/src/__tests__/guardian-routing-state.test.ts +36 -52
- package/src/__tests__/guardian-verification-intent-routing.test.ts +4 -6
- package/src/__tests__/guardian-verify-setup-skill-regression.test.ts +6 -8
- package/src/__tests__/handle-user-message-secret-resume.test.ts +39 -1
- package/src/__tests__/handlers-cu-observation-blob.test.ts +21 -10
- package/src/__tests__/handlers-telegram-config.test.ts +14 -14
- package/src/__tests__/handlers-user-message-approval-consumption.test.ts +23 -2
- package/src/__tests__/headless-browser-interactions.test.ts +1 -1
- package/src/__tests__/headless-browser-navigate.test.ts +1 -1
- package/src/__tests__/headless-browser-read-tools.test.ts +1 -1
- package/src/__tests__/headless-browser-snapshot.test.ts +1 -1
- package/src/__tests__/heartbeat-service.test.ts +45 -2
- package/src/__tests__/host-file-edit-tool.test.ts +1 -1
- package/src/__tests__/host-file-read-tool.test.ts +1 -1
- package/src/__tests__/host-file-write-tool.test.ts +1 -1
- package/src/__tests__/host-shell-tool.test.ts +1 -1
- package/src/__tests__/inbound-invite-redemption.test.ts +17 -19
- package/src/__tests__/ingress-reconcile.test.ts +2 -2
- package/src/__tests__/integrations-cli.test.ts +232 -0
- package/src/__tests__/intent-routing.test.ts +7 -5
- package/src/__tests__/invite-redemption-service.test.ts +5 -4
- package/src/__tests__/{ingress-routes-http.test.ts → invite-routes-http.test.ts} +42 -321
- package/src/__tests__/ipc-snapshot.test.ts +32 -31
- package/src/__tests__/managed-skill-lifecycle.test.ts +1 -1
- package/src/__tests__/mcp-cli.test.ts +136 -57
- package/src/__tests__/mcp-client-auth.test.ts +95 -0
- package/src/__tests__/media-generate-image.test.ts +2 -2
- package/src/__tests__/media-reuse-story.e2e.test.ts +8 -8
- package/src/__tests__/memory-regressions.test.ts +6 -6
- package/src/__tests__/messaging-send-tool.test.ts +1 -1
- package/src/__tests__/migration-cross-version-compatibility.test.ts +1855 -0
- package/src/__tests__/migration-export-http.test.ts +540 -0
- package/src/__tests__/migration-import-commit-http.test.ts +823 -0
- package/src/__tests__/migration-import-preflight-http.test.ts +755 -0
- package/src/__tests__/migration-parity-persistence.test.ts +1854 -0
- package/src/__tests__/migration-transport.test.ts +904 -0
- package/src/__tests__/migration-validate-http.test.ts +698 -0
- package/src/__tests__/migration-wizard.test.ts +1289 -0
- package/src/__tests__/nl-approval-parser.test.ts +305 -0
- package/src/__tests__/non-member-access-request.test.ts +17 -17
- package/src/__tests__/notification-decision-strategy.test.ts +110 -2
- package/src/__tests__/notification-deep-link.test.ts +18 -0
- package/src/__tests__/notification-guardian-path.test.ts +0 -1
- package/src/__tests__/oauth-provider-profiles.test.ts +34 -0
- package/src/__tests__/oauth2-gateway-transport.test.ts +1 -1
- package/src/__tests__/playbook-execution.test.ts +1 -1
- package/src/__tests__/playbook-tools.test.ts +1 -1
- package/src/__tests__/provider-error-scenarios.test.ts +68 -0
- package/src/__tests__/provider-streaming.benchmark.test.ts +3 -1
- package/src/__tests__/proxy-approval-callback.test.ts +1 -1
- package/src/__tests__/qdrant-manager.test.ts +40 -11
- package/src/__tests__/rebind-secrets-screen.test.ts +839 -0
- package/src/__tests__/recording-handler.test.ts +2 -2
- package/src/__tests__/recording-intent-handler.test.ts +3 -3
- package/src/__tests__/recording-state-machine.test.ts +2 -2
- package/src/__tests__/relay-server.test.ts +507 -228
- package/src/__tests__/reminder-store.test.ts +8 -0
- package/src/__tests__/reminder.test.ts +8 -0
- package/src/__tests__/{resolve-guardian-trust-class.test.ts → resolve-trust-class.test.ts} +11 -17
- package/src/__tests__/retry-after-extraction.test.ts +111 -0
- package/src/__tests__/scaffold-managed-skill-tool.test.ts +1 -1
- package/src/__tests__/schedule-tools.test.ts +1 -1
- package/src/__tests__/script-proxy-certs.test.ts +1 -1
- package/src/__tests__/script-proxy-connect-tunnel.test.ts +2 -3
- package/src/__tests__/script-proxy-decision-trace.test.ts +2 -2
- package/src/__tests__/script-proxy-http-forwarder.test.ts +1 -1
- package/src/__tests__/script-proxy-injection-runtime.test.ts +5 -5
- package/src/__tests__/script-proxy-mitm-handler.test.ts +4 -4
- package/src/__tests__/script-proxy-policy-runtime.test.ts +2 -2
- package/src/__tests__/script-proxy-policy.test.ts +2 -2
- package/src/__tests__/script-proxy-profile-template-fallback.test.ts +127 -0
- package/src/__tests__/script-proxy-session-manager.test.ts +4 -7
- package/src/__tests__/script-proxy-session-runtime.test.ts +1 -6
- package/src/__tests__/secret-onetime-send.test.ts +4 -4
- package/src/__tests__/secret-scanner-executor.test.ts +2 -2
- package/src/__tests__/send-endpoint-busy.test.ts +11 -9
- package/src/__tests__/send-notification-tool.test.ts +2 -2
- package/src/__tests__/session-abort-tool-results.test.ts +17 -2
- package/src/__tests__/session-agent-loop.test.ts +456 -35
- package/src/__tests__/session-confirmation-signals.test.ts +3 -2
- package/src/__tests__/session-conflict-gate.test.ts +20 -3
- package/src/__tests__/session-init.benchmark.test.ts +2 -2
- package/src/__tests__/session-load-history-repair.test.ts +7 -7
- package/src/__tests__/session-media-retry.test.ts +147 -0
- package/src/__tests__/session-pre-run-repair.test.ts +17 -2
- package/src/__tests__/session-profile-injection.test.ts +20 -3
- package/src/__tests__/session-provider-retry-repair.test.ts +86 -6
- package/src/__tests__/session-queue.test.ts +33 -18
- package/src/__tests__/session-runtime-assembly.test.ts +147 -1
- package/src/__tests__/session-runtime-workspace.test.ts +40 -0
- package/src/__tests__/session-slash-known.test.ts +21 -3
- package/src/__tests__/session-slash-queue.test.ts +17 -2
- package/src/__tests__/session-slash-unknown.test.ts +17 -2
- package/src/__tests__/session-surfaces-deselection.test.ts +208 -0
- package/src/__tests__/session-workspace-cache-state.test.ts +2 -2
- package/src/__tests__/session-workspace-injection.test.ts +17 -2
- package/src/__tests__/session-workspace-tool-tracking.test.ts +17 -2
- package/src/__tests__/shell-credential-ref.test.ts +1 -1
- package/src/__tests__/shell-tool-proxy-mode.test.ts +1 -1
- package/src/__tests__/skill-feature-flags-integration.test.ts +9 -5
- package/src/__tests__/skill-feature-flags.test.ts +18 -12
- package/src/__tests__/skill-load-feature-flag.test.ts +5 -4
- package/src/__tests__/skill-load-tool.test.ts +1 -1
- package/src/__tests__/skill-script-runner-host.test.ts +1 -1
- package/src/__tests__/skill-script-runner-sandbox.test.ts +1 -1
- package/src/__tests__/skill-script-runner.test.ts +1 -1
- package/src/__tests__/skill-tool-factory.test.ts +1 -1
- package/src/__tests__/slack-block-formatting.test.ts +100 -0
- package/src/__tests__/slack-inbound-verification.test.ts +346 -0
- package/src/__tests__/slack-reaction-approvals.test.ts +77 -0
- package/src/__tests__/slack-skill.test.ts +4 -2
- package/src/__tests__/starter-task-flow.test.ts +0 -1
- package/src/__tests__/subagent-tools.test.ts +3 -3
- package/src/__tests__/swarm-recursion.test.ts +1 -1
- package/src/__tests__/swarm-session-integration.test.ts +1 -1
- package/src/__tests__/swarm-tool.test.ts +1 -1
- package/src/__tests__/task-management-tools.test.ts +1 -1
- package/src/__tests__/task-tools.test.ts +1 -1
- package/src/__tests__/terminal-tools.test.ts +1 -1
- package/src/__tests__/test-support/browser-skill-harness.ts +39 -27
- package/src/__tests__/test-support/computer-use-skill-harness.ts +14 -14
- package/src/__tests__/tool-approval-handler.test.ts +15 -15
- package/src/__tests__/tool-execution-abort-cleanup.test.ts +1 -1
- package/src/__tests__/tool-execution-pipeline.benchmark.test.ts +1 -1
- package/src/__tests__/tool-executor-lifecycle-events.test.ts +2 -2
- package/src/__tests__/tool-executor-shell-integration.test.ts +1 -1
- package/src/__tests__/tool-executor.test.ts +23 -182
- package/src/__tests__/tool-grant-request-escalation.test.ts +11 -11
- package/src/__tests__/tool-permission-simulate-handler.test.ts +4 -4
- package/src/__tests__/transfer-progress-screen.test.ts +1180 -0
- package/src/__tests__/trust-context-guards.test.ts +25 -29
- package/src/__tests__/trusted-contact-approval-notifier.test.ts +23 -21
- package/src/__tests__/trusted-contact-inline-approval-integration.test.ts +37 -40
- package/src/__tests__/trusted-contact-lifecycle-notifications.test.ts +29 -25
- package/src/__tests__/trusted-contact-multichannel.test.ts +25 -24
- package/src/__tests__/trusted-contact-verification.test.ts +64 -76
- package/src/__tests__/turn-commit.test.ts +18 -18
- package/src/__tests__/twilio-provider.test.ts +7 -7
- package/src/__tests__/validation-results-screen.test.ts +1107 -0
- package/src/__tests__/view-image-tool.test.ts +1 -1
- package/src/__tests__/voice-invite-redemption.test.ts +4 -3
- package/src/__tests__/voice-scoped-grant-consumer.test.ts +12 -12
- package/src/__tests__/voice-session-bridge.test.ts +24 -24
- package/src/agent/attachments.ts +3 -1
- package/src/agent/loop.ts +13 -13
- package/src/agent/message-types.ts +13 -7
- package/src/amazon/cart.ts +59 -32
- package/src/amazon/checkout.ts +25 -14
- package/src/amazon/client.ts +61 -58
- package/src/amazon/product-details.ts +3 -3
- package/src/amazon/request-extractor.ts +46 -31
- package/src/amazon/search.ts +6 -4
- package/src/amazon/session.ts +33 -24
- package/src/approvals/AGENTS.md +26 -0
- package/src/approvals/approval-primitive.ts +87 -64
- package/src/approvals/guardian-decision-primitive.ts +172 -81
- package/src/approvals/guardian-request-resolvers.ts +262 -155
- package/src/autonomy/autonomy-resolver.ts +7 -5
- package/src/autonomy/autonomy-store.ts +34 -19
- package/src/autonomy/disposition-mapper.ts +5 -5
- package/src/autonomy/index.ts +6 -6
- package/src/autonomy/types.ts +7 -3
- package/src/browser-extension-relay/client.ts +50 -19
- package/src/browser-extension-relay/protocol.ts +11 -11
- package/src/browser-extension-relay/server.ts +45 -20
- package/src/bundler/app-bundler.ts +75 -50
- package/src/bundler/bundle-scanner.ts +145 -41
- package/src/bundler/bundle-signer.ts +16 -14
- package/src/bundler/signature-verifier.ts +36 -33
- package/src/calls/call-constants.ts +10 -3
- package/src/calls/call-controller.ts +473 -214
- package/src/calls/call-conversation-messages.ts +25 -15
- package/src/calls/call-domain.ts +401 -148
- package/src/calls/call-pointer-message-composer.ts +26 -21
- package/src/calls/call-pointer-messages.ts +52 -28
- package/src/calls/call-recovery.ts +53 -37
- package/src/calls/call-state-machine.ts +37 -7
- package/src/calls/call-state.ts +35 -13
- package/src/calls/call-store.ts +165 -77
- package/src/calls/elevenlabs-client.ts +39 -20
- package/src/calls/guardian-action-sweep.ts +42 -24
- package/src/calls/guardian-dispatch.ts +79 -56
- package/src/calls/guardian-question-copy.ts +28 -23
- package/src/calls/relay-server.ts +1149 -532
- package/src/calls/speaker-identification.ts +21 -15
- package/src/calls/twilio-config.ts +34 -17
- package/src/calls/twilio-provider.ts +108 -55
- package/src/calls/twilio-rest.ts +212 -100
- package/src/calls/twilio-routes.ts +165 -92
- package/src/calls/types.ts +55 -7
- package/src/calls/voice-quality.ts +6 -4
- package/src/calls/voice-session-bridge.ts +181 -133
- package/src/channels/config.ts +18 -14
- package/src/channels/types.ts +38 -10
- package/src/cli/amazon.ts +333 -227
- package/src/cli/config-commands.ts +236 -146
- package/src/cli/core-commands.ts +403 -329
- package/src/cli/email-guardrails.ts +38 -19
- package/src/cli/email.ts +207 -153
- package/src/cli/influencer.ts +58 -56
- package/src/cli/integrations.ts +306 -0
- package/src/cli/ipc-client.ts +24 -19
- package/src/cli/map.ts +176 -129
- package/src/cli/mcp.ts +260 -152
- package/src/cli/sequence.ts +165 -107
- package/src/cli/twitter.ts +302 -218
- package/src/cli.ts +418 -279
- package/src/commands/cc-command-registry.ts +52 -27
- package/src/config/agent-schema.ts +217 -134
- package/src/config/assistant-feature-flags.ts +23 -18
- package/src/config/bundled-skills/_shared/CLI_RETRIEVAL_PATTERN.md +19 -0
- package/src/config/bundled-skills/app-builder/SKILL.md +193 -1500
- package/src/config/bundled-skills/app-builder/TOOLS.json +70 -18
- package/src/config/bundled-skills/app-builder/tools/app-create.ts +7 -4
- package/src/config/bundled-skills/app-builder/tools/app-delete.ts +6 -3
- package/src/config/bundled-skills/app-builder/tools/app-file-edit.ts +7 -4
- package/src/config/bundled-skills/app-builder/tools/app-file-list.ts +6 -3
- package/src/config/bundled-skills/app-builder/tools/app-file-read.ts +6 -3
- package/src/config/bundled-skills/app-builder/tools/app-file-write.ts +7 -4
- package/src/config/bundled-skills/app-builder/tools/app-list.ts +6 -3
- package/src/config/bundled-skills/app-builder/tools/app-query.ts +6 -3
- package/src/config/bundled-skills/app-builder/tools/app-update.ts +6 -3
- package/src/config/bundled-skills/browser/TOOLS.json +59 -2
- package/src/config/bundled-skills/browser/tools/browser-click.ts +5 -2
- package/src/config/bundled-skills/browser/tools/browser-close.ts +5 -2
- package/src/config/bundled-skills/browser/tools/browser-extract.ts +5 -2
- package/src/config/bundled-skills/browser/tools/browser-fill-credential.ts +5 -2
- package/src/config/bundled-skills/browser/tools/browser-hover.ts +5 -2
- package/src/config/bundled-skills/browser/tools/browser-navigate.ts +5 -2
- package/src/config/bundled-skills/browser/tools/browser-press-key.ts +5 -2
- package/src/config/bundled-skills/browser/tools/browser-screenshot.ts +5 -2
- package/src/config/bundled-skills/browser/tools/browser-scroll.ts +5 -2
- package/src/config/bundled-skills/browser/tools/browser-select-option.ts +5 -2
- package/src/config/bundled-skills/browser/tools/browser-snapshot.ts +5 -2
- package/src/config/bundled-skills/browser/tools/browser-type.ts +5 -2
- package/src/config/bundled-skills/browser/tools/browser-wait-for-download.ts +13 -6
- package/src/config/bundled-skills/browser/tools/browser-wait-for.ts +5 -2
- package/src/config/bundled-skills/chatgpt-import/TOOLS.json +4 -0
- package/src/config/bundled-skills/claude-code/TOOLS.json +4 -0
- package/src/config/bundled-skills/claude-code/tools/claude-code.ts +5 -2
- package/src/config/bundled-skills/computer-use/SKILL.md +2 -2
- package/src/config/bundled-skills/computer-use/TOOLS.json +50 -2
- package/src/config/bundled-skills/computer-use/tools/computer-use-click.ts +6 -3
- package/src/config/bundled-skills/computer-use/tools/computer-use-done.ts +6 -3
- package/src/config/bundled-skills/computer-use/tools/computer-use-double-click.ts +10 -3
- package/src/config/bundled-skills/computer-use/tools/computer-use-drag.ts +6 -3
- package/src/config/bundled-skills/computer-use/tools/computer-use-key.ts +6 -3
- package/src/config/bundled-skills/computer-use/tools/computer-use-open-app.ts +6 -3
- package/src/config/bundled-skills/computer-use/tools/computer-use-request-control.ts +10 -3
- package/src/config/bundled-skills/computer-use/tools/computer-use-respond.ts +6 -3
- package/src/config/bundled-skills/computer-use/tools/computer-use-right-click.ts +10 -3
- package/src/config/bundled-skills/computer-use/tools/computer-use-run-applescript.ts +10 -3
- package/src/config/bundled-skills/computer-use/tools/computer-use-scroll.ts +6 -3
- package/src/config/bundled-skills/computer-use/tools/computer-use-type-text.ts +6 -3
- package/src/config/bundled-skills/computer-use/tools/computer-use-wait.ts +6 -3
- package/src/config/bundled-skills/configure-settings/SKILL.md +28 -14
- package/src/config/bundled-skills/contacts/SKILL.md +453 -15
- package/src/config/bundled-skills/contacts/TOOLS.json +22 -2
- package/src/config/bundled-skills/contacts/tools/contact-merge.ts +79 -20
- package/src/config/bundled-skills/contacts/tools/contact-search.ts +55 -18
- package/src/config/bundled-skills/contacts/tools/contact-upsert.ts +64 -19
- package/src/config/bundled-skills/document/TOOLS.json +8 -0
- package/src/config/bundled-skills/document/tools/document-create.ts +5 -2
- package/src/config/bundled-skills/document/tools/document-update.ts +5 -2
- package/src/config/bundled-skills/doordash/doordash-cli.ts +17 -7
- package/src/config/bundled-skills/email-setup/SKILL.md +12 -9
- package/src/config/bundled-skills/followups/TOOLS.json +12 -0
- package/src/config/bundled-skills/followups/tools/followup-create.ts +5 -2
- package/src/config/bundled-skills/followups/tools/followup-list.ts +5 -2
- package/src/config/bundled-skills/followups/tools/followup-resolve.ts +5 -2
- package/src/config/bundled-skills/google-calendar/TOOLS.json +124 -26
- package/src/config/bundled-skills/google-calendar/calendar-client.ts +44 -32
- package/src/config/bundled-skills/google-calendar/tools/calendar-check-availability.ts +11 -5
- package/src/config/bundled-skills/google-calendar/tools/calendar-create-event.ts +13 -7
- package/src/config/bundled-skills/google-calendar/tools/calendar-get-event.ts +11 -5
- package/src/config/bundled-skills/google-calendar/tools/calendar-list-events.ts +13 -7
- package/src/config/bundled-skills/google-calendar/tools/calendar-rsvp.ts +28 -12
- package/src/config/bundled-skills/google-calendar/tools/shared.ts +6 -4
- package/src/config/bundled-skills/google-calendar/types.ts +3 -3
- package/src/config/bundled-skills/guardian-verify-setup/SKILL.md +88 -33
- package/src/config/bundled-skills/image-studio/TOOLS.json +12 -2
- package/src/config/bundled-skills/image-studio/tools/media-generate-image.ts +48 -25
- package/src/config/bundled-skills/knowledge-graph/TOOLS.json +13 -3
- package/src/config/bundled-skills/knowledge-graph/tools/graph-query.ts +60 -35
- package/src/config/bundled-skills/mcp-setup/SKILL.md +75 -0
- package/src/config/bundled-skills/media-processing/SKILL.md +55 -15
- package/src/config/bundled-skills/media-processing/TOOLS.json +48 -2
- package/src/config/bundled-skills/media-processing/__tests__/concurrency-pool.test.ts +12 -10
- package/src/config/bundled-skills/media-processing/__tests__/cost-tracker.test.ts +34 -19
- package/src/config/bundled-skills/media-processing/__tests__/preprocess.test.ts +82 -66
- package/src/config/bundled-skills/media-processing/services/audio-transcribe.ts +148 -0
- package/src/config/bundled-skills/media-processing/services/concurrency-pool.ts +1 -1
- package/src/config/bundled-skills/media-processing/services/cost-tracker.ts +8 -3
- package/src/config/bundled-skills/media-processing/services/gemini-map.ts +117 -53
- package/src/config/bundled-skills/media-processing/services/gemini-video.ts +273 -0
- package/src/config/bundled-skills/media-processing/services/preprocess.ts +185 -97
- package/src/config/bundled-skills/media-processing/services/processing-pipeline.ts +32 -27
- package/src/config/bundled-skills/media-processing/services/reduce.ts +101 -24
- package/src/config/bundled-skills/media-processing/tools/analyze-keyframes.ts +121 -55
- package/src/config/bundled-skills/media-processing/tools/extract-keyframes.ts +58 -24
- package/src/config/bundled-skills/media-processing/tools/generate-clip.ts +198 -92
- package/src/config/bundled-skills/media-processing/tools/ingest-media.ts +98 -70
- package/src/config/bundled-skills/media-processing/tools/media-diagnostics.ts +59 -19
- package/src/config/bundled-skills/media-processing/tools/media-status.ts +26 -10
- package/src/config/bundled-skills/media-processing/tools/query-media-events.ts +29 -14
- package/src/config/bundled-skills/messaging/SKILL.md +7 -5
- package/src/config/bundled-skills/messaging/TOOLS.json +232 -186
- package/src/config/bundled-skills/messaging/tools/gmail-archive-by-query.ts +31 -13
- package/src/config/bundled-skills/messaging/tools/gmail-archive.ts +16 -10
- package/src/config/bundled-skills/messaging/tools/gmail-batch-label.ts +18 -9
- package/src/config/bundled-skills/messaging/tools/gmail-download-attachment.ts +23 -16
- package/src/config/bundled-skills/messaging/tools/gmail-draft.ts +28 -12
- package/src/config/bundled-skills/messaging/tools/gmail-filters.ts +41 -21
- package/src/config/bundled-skills/messaging/tools/gmail-follow-up.ts +44 -23
- package/src/config/bundled-skills/messaging/tools/gmail-forward.ts +73 -33
- package/src/config/bundled-skills/messaging/tools/gmail-label.ts +15 -9
- package/src/config/bundled-skills/messaging/tools/gmail-list-attachments.ts +22 -14
- package/src/config/bundled-skills/messaging/tools/gmail-outreach-scan.ts +99 -50
- package/src/config/bundled-skills/messaging/tools/gmail-send-draft.ts +14 -8
- package/src/config/bundled-skills/messaging/tools/gmail-send-with-attachments.ts +63 -44
- package/src/config/bundled-skills/messaging/tools/gmail-sender-digest.ts +90 -46
- package/src/config/bundled-skills/messaging/tools/gmail-summarize-thread.ts +43 -22
- package/src/config/bundled-skills/messaging/tools/gmail-trash.ts +15 -9
- package/src/config/bundled-skills/messaging/tools/gmail-triage.ts +51 -22
- package/src/config/bundled-skills/messaging/tools/gmail-unsubscribe.ts +62 -26
- package/src/config/bundled-skills/messaging/tools/gmail-vacation.ts +34 -19
- package/src/config/bundled-skills/messaging/tools/google-contacts.ts +32 -16
- package/src/config/bundled-skills/messaging/tools/messaging-analyze-activity.ts +10 -4
- package/src/config/bundled-skills/messaging/tools/messaging-analyze-style.ts +91 -47
- package/src/config/bundled-skills/messaging/tools/messaging-archive-by-sender.ts +21 -9
- package/src/config/bundled-skills/messaging/tools/messaging-auth-test.ts +9 -3
- package/src/config/bundled-skills/messaging/tools/messaging-draft.ts +30 -17
- package/src/config/bundled-skills/messaging/tools/messaging-list-conversations.ts +10 -4
- package/src/config/bundled-skills/messaging/tools/messaging-mark-read.ts +14 -6
- package/src/config/bundled-skills/messaging/tools/messaging-read.ts +16 -5
- package/src/config/bundled-skills/messaging/tools/messaging-reply.ts +63 -36
- package/src/config/bundled-skills/messaging/tools/messaging-search.ts +10 -4
- package/src/config/bundled-skills/messaging/tools/messaging-send.ts +30 -12
- package/src/config/bundled-skills/messaging/tools/messaging-sender-digest.ts +48 -29
- package/src/config/bundled-skills/messaging/tools/scan-result-store.ts +20 -6
- package/src/config/bundled-skills/messaging/tools/send-notification.ts +1 -1
- package/src/config/bundled-skills/messaging/tools/sequence-analytics.ts +59 -22
- package/src/config/bundled-skills/messaging/tools/sequence-cancel.ts +13 -7
- package/src/config/bundled-skills/messaging/tools/sequence-create.ts +27 -12
- package/src/config/bundled-skills/messaging/tools/sequence-delete.ts +14 -6
- package/src/config/bundled-skills/messaging/tools/sequence-enroll.ts +30 -11
- package/src/config/bundled-skills/messaging/tools/sequence-enrollment-list.ts +16 -8
- package/src/config/bundled-skills/messaging/tools/sequence-get.ts +31 -13
- package/src/config/bundled-skills/messaging/tools/sequence-import.ts +38 -22
- package/src/config/bundled-skills/messaging/tools/sequence-list.ts +16 -7
- package/src/config/bundled-skills/messaging/tools/sequence-pause.ts +29 -10
- package/src/config/bundled-skills/messaging/tools/sequence-resume.ts +16 -8
- package/src/config/bundled-skills/messaging/tools/sequence-update.ts +35 -16
- package/src/config/bundled-skills/messaging/tools/shared.ts +26 -12
- package/src/config/bundled-skills/notifications/SKILL.md +3 -2
- package/src/config/bundled-skills/notifications/TOOLS.json +7 -13
- package/src/config/bundled-skills/notifications/tools/send-notification.ts +69 -34
- package/src/config/bundled-skills/notifications/tools/shared.ts +1 -1
- package/src/config/bundled-skills/phone-calls/SKILL.md +46 -48
- package/src/config/bundled-skills/phone-calls/TOOLS.json +13 -1
- package/src/config/bundled-skills/phone-calls/tools/call-end.ts +1 -1
- package/src/config/bundled-skills/phone-calls/tools/call-start.ts +1 -1
- package/src/config/bundled-skills/phone-calls/tools/call-status.ts +1 -1
- package/src/config/bundled-skills/playbooks/TOOLS.json +16 -0
- package/src/config/bundled-skills/playbooks/tools/playbook-create.ts +91 -51
- package/src/config/bundled-skills/playbooks/tools/playbook-delete.ts +30 -16
- package/src/config/bundled-skills/playbooks/tools/playbook-list.ts +66 -27
- package/src/config/bundled-skills/playbooks/tools/playbook-update.ts +89 -42
- package/src/config/bundled-skills/public-ingress/SKILL.md +26 -19
- package/src/config/bundled-skills/reminder/TOOLS.json +15 -2
- package/src/config/bundled-skills/reminder/tools/reminder-cancel.ts +5 -2
- package/src/config/bundled-skills/reminder/tools/reminder-create.ts +5 -2
- package/src/config/bundled-skills/reminder/tools/reminder-list.ts +5 -2
- package/src/config/bundled-skills/schedule/SKILL.md +33 -15
- package/src/config/bundled-skills/schedule/TOOLS.json +17 -1
- package/src/config/bundled-skills/schedule/tools/schedule-create.ts +5 -2
- package/src/config/bundled-skills/schedule/tools/schedule-delete.ts +5 -2
- package/src/config/bundled-skills/schedule/tools/schedule-list.ts +5 -2
- package/src/config/bundled-skills/schedule/tools/schedule-update.ts +5 -2
- package/src/config/bundled-skills/screen-recording/SKILL.md +11 -3
- package/src/config/bundled-skills/self-upgrade/SKILL.md +9 -8
- package/src/config/bundled-skills/slack/SKILL.md +30 -1
- package/src/config/bundled-skills/slack/TOOLS.json +122 -17
- package/src/config/bundled-skills/slack/tools/shared.ts +7 -5
- package/src/config/bundled-skills/slack/tools/slack-add-reaction.ts +11 -5
- package/src/config/bundled-skills/slack/tools/slack-channel-details.ts +11 -5
- package/src/config/bundled-skills/slack/tools/slack-channel-permissions.ts +146 -0
- package/src/config/bundled-skills/slack/tools/slack-configure-channels.ts +46 -16
- package/src/config/bundled-skills/slack/tools/slack-delete-message.ts +11 -5
- package/src/config/bundled-skills/slack/tools/slack-edit-message.ts +28 -0
- package/src/config/bundled-skills/slack/tools/slack-leave-channel.ts +12 -6
- package/src/config/bundled-skills/slack/tools/slack-scan-digest.ts +120 -0
- package/src/config/bundled-skills/slack-app-setup/SKILL.md +200 -0
- package/src/config/bundled-skills/sms-setup/SKILL.md +5 -8
- package/src/config/bundled-skills/subagent/TOOLS.json +22 -2
- package/src/config/bundled-skills/subagent/tools/subagent-abort.ts +5 -2
- package/src/config/bundled-skills/subagent/tools/subagent-message.ts +5 -2
- package/src/config/bundled-skills/subagent/tools/subagent-read.ts +5 -2
- package/src/config/bundled-skills/subagent/tools/subagent-spawn.ts +5 -2
- package/src/config/bundled-skills/subagent/tools/subagent-status.ts +5 -2
- package/src/config/bundled-skills/tasks/TOOLS.json +86 -14
- package/src/config/bundled-skills/tasks/tools/task-delete.ts +5 -2
- package/src/config/bundled-skills/tasks/tools/task-list-add.ts +5 -2
- package/src/config/bundled-skills/tasks/tools/task-list-remove.ts +5 -2
- package/src/config/bundled-skills/tasks/tools/task-list-show.ts +5 -2
- package/src/config/bundled-skills/tasks/tools/task-list-update.ts +5 -2
- package/src/config/bundled-skills/tasks/tools/task-list.ts +5 -2
- package/src/config/bundled-skills/tasks/tools/task-queue-run.ts +5 -2
- package/src/config/bundled-skills/tasks/tools/task-run.ts +5 -2
- package/src/config/bundled-skills/tasks/tools/task-save.ts +5 -2
- package/src/config/bundled-skills/telegram-setup/SKILL.md +7 -8
- package/src/config/bundled-skills/transcribe/TOOLS.json +4 -0
- package/src/config/bundled-skills/transcribe/tools/transcribe-media.ts +232 -127
- package/src/config/bundled-skills/twilio-setup/SKILL.md +7 -12
- package/src/config/bundled-skills/twitter/SKILL.md +19 -2
- package/src/config/bundled-skills/voice-setup/SKILL.md +5 -5
- package/src/config/bundled-skills/watcher/TOOLS.json +20 -0
- package/src/config/bundled-skills/watcher/tools/watcher-create.ts +5 -2
- package/src/config/bundled-skills/watcher/tools/watcher-delete.ts +5 -2
- package/src/config/bundled-skills/watcher/tools/watcher-digest.ts +5 -2
- package/src/config/bundled-skills/watcher/tools/watcher-list.ts +5 -2
- package/src/config/bundled-skills/watcher/tools/watcher-update.ts +5 -2
- package/src/config/bundled-skills/weather/TOOLS.json +4 -0
- package/src/config/bundled-skills/weather/tools/get-weather.ts +5 -2
- package/src/config/bundled-tool-registry.ts +2 -0
- package/src/config/calls-schema.ts +108 -63
- package/src/config/channel-permission-profiles.ts +155 -0
- package/src/config/computer-use-prompt.ts +7 -7
- package/src/config/core-schema.ts +239 -155
- package/src/config/defaults.ts +2 -2
- package/src/config/elevenlabs-schema.ts +15 -15
- package/src/config/env-registry.ts +33 -33
- package/src/config/env.ts +4 -1
- package/src/config/feature-flag-registry.json +31 -7
- package/src/config/loader.ts +118 -58
- package/src/config/mcp-schema.ts +29 -15
- package/src/config/memory-schema.ts +434 -229
- package/src/config/notifications-schema.ts +4 -4
- package/src/config/sandbox-schema.ts +2 -2
- package/src/config/schema.ts +12 -2
- package/src/config/skill-state.ts +27 -15
- package/src/config/skills-schema.ts +72 -23
- package/src/config/skills.ts +303 -143
- package/src/config/system-prompt.ts +25 -6
- package/src/config/types.ts +1 -1
- package/src/config/update-bulletin-format.ts +3 -3
- package/src/config/update-bulletin-state.ts +15 -6
- package/src/config/update-bulletin-template-path.ts +8 -4
- package/src/config/update-bulletin.ts +33 -14
- package/src/config/user-reference.ts +8 -8
- package/src/contacts/contact-events.ts +21 -0
- package/src/contacts/contact-store.ts +813 -100
- package/src/contacts/contacts-write.ts +287 -0
- package/src/contacts/index.ts +13 -4
- package/src/contacts/startup-migration.ts +21 -0
- package/src/contacts/types.ts +73 -2
- package/src/context/token-estimator.ts +54 -31
- package/src/context/tool-result-truncation.ts +41 -7
- package/src/context/window-manager.ts +225 -120
- package/src/daemon/approval-generators.ts +83 -55
- package/src/daemon/approved-devices-store.ts +33 -20
- package/src/daemon/assistant-attachments.ts +157 -101
- package/src/daemon/auth-manager.ts +17 -15
- package/src/daemon/classifier.ts +117 -46
- package/src/daemon/computer-use-session.ts +316 -187
- package/src/daemon/config-watcher.ts +91 -44
- package/src/daemon/connection-policy.ts +18 -10
- package/src/daemon/context-overflow-approval.ts +48 -0
- package/src/daemon/context-overflow-policy.ts +50 -0
- package/src/daemon/context-overflow-reducer.ts +300 -0
- package/src/daemon/daemon-control.ts +79 -51
- package/src/daemon/date-context.ts +119 -69
- package/src/daemon/dictation-profile-store.ts +94 -48
- package/src/daemon/dictation-text-processing.ts +33 -12
- package/src/daemon/doordash-steps.ts +92 -49
- package/src/daemon/guardian-action-generators.ts +62 -46
- package/src/daemon/guardian-verification-intent.ts +35 -19
- package/src/daemon/handlers/apps.ts +258 -113
- package/src/daemon/handlers/avatar.ts +20 -15
- package/src/daemon/handlers/computer-use.ts +82 -39
- package/src/daemon/handlers/config-channels.ts +146 -69
- package/src/daemon/handlers/config-heartbeat.ts +114 -59
- package/src/daemon/handlers/config-inbox.ts +213 -160
- package/src/daemon/handlers/config-ingress.ts +127 -55
- package/src/daemon/handlers/config-integrations.ts +145 -88
- package/src/daemon/handlers/config-model.ts +58 -22
- package/src/daemon/handlers/config-platform.ts +40 -16
- package/src/daemon/handlers/config-scheduling.ts +109 -48
- package/src/daemon/handlers/config-slack-channel.ts +67 -35
- package/src/daemon/handlers/config-slack.ts +21 -20
- package/src/daemon/handlers/config-telegram.ts +100 -70
- package/src/daemon/handlers/config-tools.ts +103 -55
- package/src/daemon/handlers/config-trust.ts +50 -20
- package/src/daemon/handlers/config.ts +72 -24
- package/src/daemon/handlers/contacts.ts +163 -0
- package/src/daemon/handlers/diagnostics.ts +90 -48
- package/src/daemon/handlers/documents.ts +74 -46
- package/src/daemon/handlers/guardian-actions.ts +57 -77
- package/src/daemon/handlers/home-base.ts +19 -16
- package/src/daemon/handlers/identity.ts +65 -45
- package/src/daemon/handlers/index.ts +78 -54
- package/src/daemon/handlers/misc.ts +664 -234
- package/src/daemon/handlers/navigate-settings.ts +14 -11
- package/src/daemon/handlers/oauth-connect.ts +48 -35
- package/src/daemon/handlers/open-bundle-handler.ts +31 -24
- package/src/daemon/handlers/pairing.ts +51 -25
- package/src/daemon/handlers/publish.ts +55 -33
- package/src/daemon/handlers/recording.ts +378 -162
- package/src/daemon/handlers/sessions.ts +922 -423
- package/src/daemon/handlers/shared.ts +202 -117
- package/src/daemon/handlers/signing.ts +25 -6
- package/src/daemon/handlers/subagents.ts +117 -56
- package/src/daemon/handlers/twitter-auth.ts +70 -49
- package/src/daemon/handlers/work-items.ts +264 -112
- package/src/daemon/handlers/workspace-files.ts +27 -20
- package/src/daemon/handlers.ts +2 -2
- package/src/daemon/history-repair.ts +16 -15
- package/src/daemon/identity-helpers.ts +4 -4
- package/src/daemon/install-cli-launchers.ts +33 -22
- package/src/daemon/ipc-blob-store.ts +38 -24
- package/src/daemon/ipc-contract/apps.ts +61 -50
- package/src/daemon/ipc-contract/computer-use.ts +47 -37
- package/src/daemon/ipc-contract/contacts.ts +69 -0
- package/src/daemon/ipc-contract/diagnostics.ts +14 -14
- package/src/daemon/ipc-contract/documents.ts +8 -8
- package/src/daemon/ipc-contract/guardian-actions.ts +4 -4
- package/src/daemon/ipc-contract/inbox.ts +12 -71
- package/src/daemon/ipc-contract/integrations.ts +57 -44
- package/src/daemon/ipc-contract/memory.ts +3 -5
- package/src/daemon/ipc-contract/messages.ts +95 -69
- package/src/daemon/ipc-contract/notifications.ts +10 -6
- package/src/daemon/ipc-contract/pairing.ts +8 -8
- package/src/daemon/ipc-contract/schedules.ts +20 -20
- package/src/daemon/ipc-contract/sessions.ts +89 -57
- package/src/daemon/ipc-contract/settings.ts +12 -7
- package/src/daemon/ipc-contract/shared.ts +9 -7
- package/src/daemon/ipc-contract/skills.ts +46 -26
- package/src/daemon/ipc-contract/subagents.ts +9 -9
- package/src/daemon/ipc-contract/surfaces.ts +0 -1
- package/src/daemon/ipc-contract/trust.ts +11 -11
- package/src/daemon/ipc-contract/work-items.ts +33 -28
- package/src/daemon/ipc-contract/workspace.ts +28 -21
- package/src/daemon/ipc-contract-inventory.json +10 -4
- package/src/daemon/ipc-contract-inventory.ts +29 -26
- package/src/daemon/ipc-contract.ts +111 -44
- package/src/daemon/ipc-handler.ts +27 -19
- package/src/daemon/ipc-protocol.ts +22 -12
- package/src/daemon/ipc-validate.ts +91 -46
- package/src/daemon/lifecycle.ts +39 -3
- package/src/daemon/main.ts +10 -8
- package/src/daemon/media-visibility-policy.ts +3 -1
- package/src/daemon/pairing-store.ts +72 -40
- package/src/daemon/providers-setup.ts +35 -25
- package/src/daemon/recording-executor.ts +37 -30
- package/src/daemon/recording-intent-fallback.ts +58 -28
- package/src/daemon/recording-intent.ts +71 -61
- package/src/daemon/ride-shotgun-handler.ts +201 -121
- package/src/daemon/seed-files.ts +28 -17
- package/src/daemon/server.ts +23 -14
- package/src/daemon/session-agent-loop-handlers.ts +270 -135
- package/src/daemon/session-agent-loop.ts +796 -253
- package/src/daemon/session-attachments.ts +109 -40
- package/src/daemon/session-conflict-gate.ts +72 -28
- package/src/daemon/session-dynamic-profile.ts +36 -22
- package/src/daemon/session-error.ts +68 -45
- package/src/daemon/session-evictor.ts +17 -10
- package/src/daemon/session-history.ts +201 -89
- package/src/daemon/session-lifecycle.ts +80 -44
- package/src/daemon/session-media-retry.ts +104 -42
- package/src/daemon/session-memory.ts +77 -55
- package/src/daemon/session-messaging.ts +261 -111
- package/src/daemon/session-notifiers.ts +57 -45
- package/src/daemon/session-process.ts +370 -154
- package/src/daemon/session-queue-manager.ts +30 -13
- package/src/daemon/session-runtime-assembly.ts +61 -15
- package/src/daemon/session-skill-tools.ts +84 -36
- package/src/daemon/session-slash.ts +178 -113
- package/src/daemon/session-surfaces.ts +498 -212
- package/src/daemon/session-tool-setup.ts +24 -16
- package/src/daemon/session-usage.ts +26 -13
- package/src/daemon/session-workspace.ts +7 -4
- package/src/daemon/session.ts +18 -19
- package/src/daemon/shutdown-handlers.ts +36 -33
- package/src/daemon/tls-certs.ts +90 -57
- package/src/daemon/tool-side-effects.ts +97 -65
- package/src/daemon/trace-emitter.ts +8 -7
- package/src/daemon/video-thumbnail.ts +55 -25
- package/src/daemon/watch-handler.ts +164 -86
- package/src/email/provider.ts +1 -1
- package/src/email/providers/agentmail.ts +87 -45
- package/src/email/providers/index.ts +19 -14
- package/src/email/service.ts +52 -24
- package/src/email/types.ts +2 -2
- package/src/errors.ts +1 -1
- package/src/events/bus.ts +30 -10
- package/src/events/domain-events.ts +20 -13
- package/src/events/index.ts +6 -6
- package/src/events/tool-audit-listener.ts +34 -20
- package/src/events/tool-domain-event-publisher.ts +22 -20
- package/src/events/tool-metrics-listener.ts +26 -21
- package/src/events/tool-notification-listener.ts +5 -5
- package/src/events/tool-profiling-listener.ts +33 -23
- package/src/events/tool-trace-listener.ts +70 -46
- package/src/export/formatter.ts +38 -32
- package/src/followups/followup-store.ts +43 -36
- package/src/followups/index.ts +2 -2
- package/src/followups/types.ts +1 -1
- package/src/gallery/default-gallery.ts +37 -34
- package/src/gallery/gallery-manifest.ts +9 -9
- package/src/heartbeat/heartbeat-service.ts +59 -37
- package/src/home-base/app-link-store.ts +14 -12
- package/src/home-base/bootstrap.ts +14 -8
- package/src/home-base/prebuilt/seed.ts +34 -26
- package/src/home-base/prebuilt-home-base-updater.ts +14 -8
- package/src/hooks/cli.ts +56 -43
- package/src/hooks/config.ts +27 -14
- package/src/hooks/discovery.ts +53 -33
- package/src/hooks/manager.ts +50 -26
- package/src/hooks/runner.ts +35 -29
- package/src/hooks/templates.ts +38 -15
- package/src/hooks/types.ts +13 -13
- package/src/inbound/platform-callback-registration.ts +21 -15
- package/src/inbound/public-ingress-urls.ts +9 -6
- package/src/index.ts +20 -19
- package/src/influencer/client.ts +261 -117
- package/src/instrument.ts +3 -1
- package/src/logfire.ts +64 -39
- package/src/mcp/client.ts +107 -55
- package/src/mcp/manager.ts +45 -18
- package/src/mcp/mcp-oauth-provider.ts +114 -62
- package/src/media/gemini-image-service.ts +75 -23
- package/src/memory/account-store.ts +16 -9
- package/src/memory/admin.ts +87 -57
- package/src/memory/app-git-service.ts +77 -47
- package/src/memory/app-store.ts +148 -78
- package/src/memory/attachments-store.ts +123 -53
- package/src/memory/canonical-guardian-store.ts +190 -48
- package/src/memory/channel-delivery-store.ts +5 -5
- package/src/memory/channel-guardian-store.ts +31 -16
- package/src/memory/checkpoints.ts +14 -7
- package/src/memory/clarification-resolver.ts +219 -104
- package/src/memory/conflict-intent.ts +74 -23
- package/src/memory/conflict-policy.ts +20 -7
- package/src/memory/conflict-store.ts +144 -94
- package/src/memory/contradiction-checker.ts +257 -132
- package/src/memory/conversation-attention-store.ts +74 -32
- package/src/memory/conversation-bootstrap.ts +28 -0
- package/src/memory/conversation-crud.ts +12 -5
- package/src/memory/conversation-display-order-migration.ts +7 -7
- package/src/memory/conversation-key-store.ts +18 -13
- package/src/memory/conversation-queries.ts +130 -52
- package/src/memory/conversation-store.ts +43 -26
- package/src/memory/conversation-title-service.ts +89 -66
- package/src/memory/db-init.ts +94 -2
- package/src/memory/db.ts +10 -3
- package/src/memory/delivery-channels.ts +12 -6
- package/src/memory/delivery-crud.ts +26 -12
- package/src/memory/delivery-status.ts +19 -16
- package/src/memory/embedding-backend.ts +205 -77
- package/src/memory/embedding-gemini.ts +23 -10
- package/src/memory/embedding-local.ts +89 -44
- package/src/memory/embedding-ollama.ts +25 -13
- package/src/memory/embedding-openai.ts +20 -11
- package/src/memory/embedding-runtime-manager.ts +163 -90
- package/src/memory/entity-extractor.ts +185 -123
- package/src/memory/external-conversation-store.ts +30 -12
- package/src/memory/fingerprint.ts +2 -2
- package/src/memory/fts-reconciler.ts +57 -28
- package/src/memory/guardian-action-store.ts +162 -100
- package/src/memory/guardian-approvals.ts +63 -129
- package/src/memory/guardian-rate-limits.ts +20 -9
- package/src/memory/guardian-verification.ts +82 -35
- package/src/memory/indexer.ts +96 -55
- package/src/memory/{ingress-invite-store.ts → invite-store.ts} +28 -169
- package/src/memory/items-extractor.ts +313 -157
- package/src/memory/job-handlers/backfill.ts +116 -63
- package/src/memory/job-handlers/cleanup.ts +64 -41
- package/src/memory/job-handlers/conflict.ts +90 -49
- package/src/memory/job-handlers/embedding.ts +32 -17
- package/src/memory/job-handlers/extraction.ts +58 -33
- package/src/memory/job-handlers/index-maintenance.ts +31 -17
- package/src/memory/job-handlers/media-processing.ts +65 -24
- package/src/memory/job-handlers/summarization.ts +186 -128
- package/src/memory/job-utils.ts +100 -57
- package/src/memory/jobs-store.ts +235 -142
- package/src/memory/jobs-worker.ts +167 -83
- package/src/memory/llm-request-log-store.ts +13 -11
- package/src/memory/llm-usage-store.ts +35 -26
- package/src/memory/media-store.ts +151 -44
- package/src/memory/message-content.ts +28 -18
- package/src/memory/migrations/001-job-deferrals.ts +11 -5
- package/src/memory/migrations/002-tool-invocations-fk.ts +14 -6
- package/src/memory/migrations/003-memory-fts-backfill.ts +11 -5
- package/src/memory/migrations/004-entity-relation-dedup.ts +17 -11
- package/src/memory/migrations/005-fingerprint-scope-unique.ts +36 -21
- package/src/memory/migrations/006-scope-salted-fingerprints.ts +35 -20
- package/src/memory/migrations/007-assistant-id-to-self.ts +40 -27
- package/src/memory/migrations/008-remove-assistant-id-columns.ts +58 -36
- package/src/memory/migrations/009-llm-usage-events-drop-assistant-id.ts +36 -22
- package/src/memory/migrations/010-ext-conv-bindings-channel-chat-unique.ts +21 -11
- package/src/memory/migrations/011-call-sessions-provider-sid-dedup.ts +30 -15
- package/src/memory/migrations/012-call-sessions-add-initiated-from.ts +4 -2
- package/src/memory/migrations/013-guardian-action-tables.ts +29 -11
- package/src/memory/migrations/014-backfill-inbox-thread-state.ts +35 -21
- package/src/memory/migrations/015-drop-active-search-index.ts +17 -11
- package/src/memory/migrations/016-memory-segments-indexes.ts +7 -3
- package/src/memory/migrations/017-memory-items-indexes.ts +4 -2
- package/src/memory/migrations/018-remaining-table-indexes.ts +13 -5
- package/src/memory/migrations/019-notification-tables-schema-migration.ts +34 -20
- package/src/memory/migrations/020-rename-macos-ios-channel-to-vellum.ts +87 -53
- package/src/memory/migrations/021-conversation-status-indexes.ts +7 -3
- package/src/memory/migrations/022-add-origin-interface.ts +4 -2
- package/src/memory/migrations/023-memory-item-sources-indexes.ts +4 -2
- package/src/memory/migrations/024-embedding-vector-blob.ts +34 -18
- package/src/memory/migrations/025-messages-fts-backfill.ts +11 -5
- package/src/memory/migrations/026-guardian-verification-sessions.ts +80 -14
- package/src/memory/migrations/026a-embeddings-nullable-vector-json.ts +42 -26
- package/src/memory/migrations/027-notification-delivery-pairing-columns.ts +22 -8
- package/src/memory/migrations/027a-guardian-bootstrap-token.ts +11 -3
- package/src/memory/migrations/028-call-session-mode.ts +13 -3
- package/src/memory/migrations/028-notification-delivery-client-ack.ts +22 -8
- package/src/memory/migrations/029-channel-inbound-delivered-segments.ts +7 -3
- package/src/memory/migrations/030-guardian-action-followup.ts +46 -8
- package/src/memory/migrations/030-guardian-verification-purpose.ts +4 -2
- package/src/memory/migrations/031-conversations-thread-type-index.ts +4 -2
- package/src/memory/migrations/032-guardian-delivery-conversation-index.ts +4 -2
- package/src/memory/migrations/032-notification-delivery-thread-decision.ts +22 -8
- package/src/memory/migrations/033-scoped-approval-grants.ts +1 -1
- package/src/memory/migrations/034-guardian-action-tool-metadata.ts +15 -3
- package/src/memory/migrations/035-guardian-action-supersession.ts +15 -3
- package/src/memory/migrations/036-normalize-phone-identities.ts +101 -87
- package/src/memory/migrations/037-voice-invite-columns.ts +22 -4
- package/src/memory/migrations/038-actor-token-records.ts +5 -9
- package/src/memory/migrations/039-actor-refresh-token-records.ts +7 -13
- package/src/memory/migrations/100-core-tables.ts +1 -1
- package/src/memory/migrations/101-watchers-and-logs.ts +1 -1
- package/src/memory/migrations/103-complex-migrations.ts +9 -9
- package/src/memory/migrations/104-core-indexes.ts +188 -64
- package/src/memory/migrations/105-contacts-and-triage.ts +28 -10
- package/src/memory/migrations/106-call-sessions.ts +58 -16
- package/src/memory/migrations/107-followups.ts +16 -6
- package/src/memory/migrations/108-tasks-and-work-items.ts +43 -11
- package/src/memory/migrations/109-external-conversation-bindings.ts +11 -5
- package/src/memory/migrations/110-channel-guardian.ts +48 -10
- package/src/memory/migrations/111-media-assets.ts +52 -18
- package/src/memory/migrations/112-assistant-inbox.ts +32 -12
- package/src/memory/migrations/113-late-migrations.ts +12 -12
- package/src/memory/migrations/114-notifications.ts +28 -12
- package/src/memory/migrations/115-sequences.ts +10 -4
- package/src/memory/migrations/116-messages-fts.ts +1 -1
- package/src/memory/migrations/117-conversation-attention.ts +16 -6
- package/src/memory/migrations/118-reminder-routing-intent.ts +7 -3
- package/src/memory/migrations/119-schema-indexes-and-columns.ts +35 -15
- package/src/memory/migrations/120-fk-cascade-rebuilds.ts +36 -17
- package/src/memory/migrations/121-canonical-guardian-requests.ts +25 -9
- package/src/memory/migrations/122-canonical-guardian-requester-chat-id.ts +11 -3
- package/src/memory/migrations/123-canonical-guardian-deliveries-destination-index.ts +4 -2
- package/src/memory/migrations/124-voice-invite-display-metadata.ts +15 -3
- package/src/memory/migrations/125-guardian-principal-id-columns.ts +22 -4
- package/src/memory/migrations/126-backfill-guardian-principal-id.ts +174 -126
- package/src/memory/migrations/127-guardian-principal-id-not-null.ts +58 -42
- package/src/memory/migrations/128-contacts-role-principal.ts +26 -0
- package/src/memory/migrations/129-contact-channels-access-fields.ts +105 -0
- package/src/memory/migrations/130-contact-channels-type-ext-chat-id-index.ts +15 -0
- package/src/memory/migrations/131-drop-legacy-member-guardian-tables.ts +134 -0
- package/src/memory/migrations/132-contacts-assistant-id.ts +21 -0
- package/src/memory/migrations/133-assistant-contact-metadata.ts +21 -0
- package/src/memory/migrations/index.ts +83 -73
- package/src/memory/migrations/registry.ts +53 -37
- package/src/memory/migrations/validate-migration-state.ts +73 -46
- package/src/memory/profile-compiler.ts +58 -24
- package/src/memory/published-pages-store.ts +12 -16
- package/src/memory/qdrant-circuit-breaker.ts +28 -20
- package/src/memory/qdrant-client.ts +99 -63
- package/src/memory/qdrant-manager.ts +89 -57
- package/src/memory/query-builder.ts +9 -7
- package/src/memory/raw-query.ts +63 -14
- package/src/memory/recall-cache.ts +15 -8
- package/src/memory/retrieval-budget.ts +0 -1
- package/src/memory/retriever.ts +385 -192
- package/src/memory/schema-migration.ts +1 -1
- package/src/memory/schema.ts +56 -56
- package/src/memory/scoped-approval-grants.ts +99 -45
- package/src/memory/search/entity.ts +102 -40
- package/src/memory/search/formatting.ts +70 -52
- package/src/memory/search/lexical.ts +82 -43
- package/src/memory/search/ranking.ts +103 -39
- package/src/memory/search/semantic.ts +59 -35
- package/src/memory/search/types.ts +8 -8
- package/src/memory/segmenter.ts +20 -12
- package/src/memory/shared-app-links-store.ts +21 -16
- package/src/memory/slack-thread-store.ts +187 -0
- package/src/memory/task-memory-cleanup.ts +18 -8
- package/src/memory/tool-usage-store.ts +27 -19
- package/src/memory/validation.ts +4 -2
- package/src/messaging/activity-analyzer.ts +7 -7
- package/src/messaging/draft-store.ts +13 -10
- package/src/messaging/email-classifier.ts +73 -37
- package/src/messaging/index.ts +3 -3
- package/src/messaging/outreach-classifier.ts +76 -38
- package/src/messaging/provider-types.ts +2 -4
- package/src/messaging/provider.ts +37 -8
- package/src/messaging/providers/gmail/adapter.ts +183 -66
- package/src/messaging/providers/gmail/client.ts +3 -1
- package/src/messaging/providers/gmail/mime-builder.ts +21 -19
- package/src/messaging/providers/gmail/people-client.ts +22 -9
- package/src/messaging/providers/gmail/types.ts +6 -6
- package/src/messaging/providers/slack/adapter.ts +93 -43
- package/src/messaging/providers/slack/client.ts +165 -48
- package/src/messaging/providers/slack/types.ts +10 -0
- package/src/messaging/providers/sms/adapter.ts +76 -40
- package/src/messaging/providers/sms/client.ts +4 -4
- package/src/messaging/providers/telegram-bot/adapter.ts +52 -30
- package/src/messaging/providers/telegram-bot/client.ts +7 -7
- package/src/messaging/providers/whatsapp/adapter.ts +58 -31
- package/src/messaging/providers/whatsapp/client.ts +4 -4
- package/src/messaging/registry.ts +9 -5
- package/src/messaging/style-analyzer.ts +69 -39
- package/src/messaging/thread-summarizer.ts +101 -53
- package/src/messaging/triage-engine.ts +111 -82
- package/src/messaging/types.ts +10 -10
- package/src/migrations/config-merge.ts +18 -10
- package/src/migrations/data-layout.ts +35 -22
- package/src/migrations/data-merge.ts +17 -7
- package/src/migrations/hooks-merge.ts +43 -16
- package/src/migrations/index.ts +6 -6
- package/src/migrations/log.ts +9 -5
- package/src/migrations/skills-merge.ts +17 -7
- package/src/migrations/workspace-layout.ts +39 -25
- package/src/notifications/AGENTS.md +5 -0
- package/src/notifications/adapters/macos.ts +21 -14
- package/src/notifications/adapters/slack.ts +90 -0
- package/src/notifications/adapters/sms.ts +28 -15
- package/src/notifications/adapters/telegram.ts +24 -15
- package/src/notifications/broadcaster.ts +108 -52
- package/src/notifications/conversation-pairing.ts +64 -29
- package/src/notifications/copy-composer.ts +165 -95
- package/src/notifications/decision-engine.ts +353 -147
- package/src/notifications/decisions-store.ts +26 -10
- package/src/notifications/deliveries-store.ts +23 -13
- package/src/notifications/destination-resolver.ts +83 -24
- package/src/notifications/deterministic-checks.ts +78 -27
- package/src/notifications/emit-signal.ts +95 -41
- package/src/notifications/events-store.ts +13 -7
- package/src/notifications/guardian-question-mode.ts +125 -75
- package/src/notifications/preference-extractor.ts +85 -53
- package/src/notifications/preference-summary.ts +31 -18
- package/src/notifications/preferences-store.ts +29 -18
- package/src/notifications/runtime-dispatch.ts +22 -12
- package/src/notifications/signal.ts +4 -4
- package/src/notifications/thread-candidates.ts +59 -23
- package/src/notifications/thread-seed-composer.ts +45 -27
- package/src/notifications/types.ts +19 -10
- package/src/oauth/connect-orchestrator.ts +105 -54
- package/src/oauth/connect-types.ts +3 -3
- package/src/oauth/provider-profiles.ts +102 -59
- package/src/oauth/scope-policy.ts +5 -2
- package/src/oauth/token-persistence.ts +58 -24
- package/src/outbound-proxy/certs.ts +284 -0
- package/src/outbound-proxy/config.ts +94 -0
- package/src/outbound-proxy/connect-tunnel.ts +84 -0
- package/src/outbound-proxy/health.ts +62 -0
- package/src/outbound-proxy/host-pattern-match.ts +67 -0
- package/src/outbound-proxy/http-forwarder.ts +162 -0
- package/src/outbound-proxy/index.ts +80 -0
- package/src/outbound-proxy/logging.ts +193 -0
- package/src/outbound-proxy/mitm-handler.ts +292 -0
- package/src/outbound-proxy/policy.ts +172 -0
- package/src/outbound-proxy/router.ts +64 -0
- package/src/outbound-proxy/server.ts +145 -0
- package/src/outbound-proxy/types.ts +150 -0
- package/src/permissions/checker.ts +481 -189
- package/src/permissions/defaults.ts +135 -108
- package/src/permissions/prompter.ts +53 -27
- package/src/permissions/secret-prompter.ts +21 -15
- package/src/permissions/shell-identity.ts +47 -16
- package/src/permissions/trust-store.ts +185 -73
- package/src/permissions/types.ts +22 -12
- package/src/permissions/workspace-policy.ts +47 -38
- package/src/playbooks/index.ts +10 -2
- package/src/playbooks/playbook-compiler.ts +30 -24
- package/src/playbooks/types.ts +11 -8
- package/src/providers/anthropic/client.ts +328 -168
- package/src/providers/failover.ts +57 -22
- package/src/providers/fireworks/client.ts +9 -5
- package/src/providers/gemini/client.ts +61 -39
- package/src/providers/model-intents.ts +40 -33
- package/src/providers/ollama/client.ts +7 -7
- package/src/providers/openai/client.ts +109 -68
- package/src/providers/openrouter/client.ts +9 -5
- package/src/providers/provider-send-message.ts +59 -27
- package/src/providers/ratelimit.ts +25 -8
- package/src/providers/registry.ts +86 -38
- package/src/providers/retry.ts +93 -37
- package/src/providers/stream-timeout.ts +5 -3
- package/src/providers/types.ts +7 -6
- package/src/runtime/AGENTS.md +42 -0
- package/src/runtime/access-request-helper.ts +118 -68
- package/src/runtime/actor-refresh-token-store.ts +21 -16
- package/src/runtime/actor-token-store.ts +25 -18
- package/src/runtime/actor-trust-resolver.ts +191 -80
- package/src/runtime/approval-conversation-turn.ts +39 -26
- package/src/runtime/approval-message-composer.ts +116 -84
- package/src/runtime/assistant-event-hub.ts +25 -6
- package/src/runtime/assistant-event.ts +4 -4
- package/src/runtime/assistant-scope.ts +1 -1
- package/src/runtime/auth/__tests__/guard-tests.test.ts +36 -14
- package/src/runtime/auth/context.ts +8 -7
- package/src/runtime/auth/credential-service.ts +60 -38
- package/src/runtime/auth/external-assistant-id.ts +16 -8
- package/src/runtime/auth/index.ts +23 -16
- package/src/runtime/auth/require-bound-guardian.ts +44 -0
- package/src/runtime/auth/route-policy.ts +166 -104
- package/src/runtime/auth/scopes.ts +22 -29
- package/src/runtime/auth/subject.ts +19 -13
- package/src/runtime/auth/token-service.ts +3 -3
- package/src/runtime/auth/types.ts +23 -23
- package/src/runtime/channel-approval-parser.ts +37 -14
- package/src/runtime/channel-approval-types.ts +30 -4
- package/src/runtime/channel-approvals.ts +49 -23
- package/src/runtime/channel-guardian-service.ts +144 -103
- package/src/runtime/channel-invite-transport.ts +5 -3
- package/src/runtime/channel-invite-transports/telegram.ts +16 -10
- package/src/runtime/channel-invite-transports/voice.ts +7 -7
- package/src/runtime/channel-readiness-service.ts +139 -90
- package/src/runtime/channel-readiness-types.ts +4 -2
- package/src/runtime/channel-reply-delivery.ts +83 -14
- package/src/runtime/channel-retry-sweep.ts +111 -62
- package/src/runtime/confirmation-request-guardian-bridge.ts +73 -54
- package/src/runtime/gateway-client.ts +122 -55
- package/src/runtime/gateway-internal-client.ts +86 -0
- package/src/runtime/guardian-action-conversation-turn.ts +34 -18
- package/src/runtime/guardian-action-followup-executor.ts +115 -45
- package/src/runtime/guardian-action-grant-minter.ts +40 -24
- package/src/runtime/guardian-action-message-composer.ts +105 -84
- package/src/runtime/guardian-action-service.ts +127 -0
- package/src/runtime/guardian-decision-types.ts +28 -13
- package/src/runtime/guardian-outbound-actions.ts +9 -0
- package/src/runtime/guardian-reply-router.ts +274 -145
- package/src/runtime/guardian-vellum-migration.ts +38 -24
- package/src/runtime/guardian-verification-templates.ts +24 -12
- package/src/runtime/http-router.ts +175 -0
- package/src/runtime/http-server.ts +913 -680
- package/src/runtime/http-types.ts +2 -2
- package/src/runtime/invite-redemption-service.ts +211 -134
- package/src/runtime/invite-redemption-templates.ts +18 -11
- package/src/runtime/{ingress-service.ts → invite-service.ts} +92 -151
- package/src/runtime/local-actor-identity.ts +73 -55
- package/src/runtime/middleware/auth.ts +25 -14
- package/src/runtime/middleware/error-handler.ts +15 -11
- package/src/runtime/middleware/rate-limiter.ts +23 -17
- package/src/runtime/middleware/request-logger.ts +4 -4
- package/src/runtime/middleware/twilio-validation.ts +29 -20
- package/src/runtime/migrations/migration-transport.ts +575 -0
- package/src/runtime/migrations/migration-wizard.ts +715 -0
- package/src/runtime/migrations/rebind-secrets-screen.ts +351 -0
- package/src/runtime/migrations/transfer-progress-screen.ts +321 -0
- package/src/runtime/migrations/validation-results-screen.ts +467 -0
- package/src/runtime/migrations/vbundle-builder.ts +295 -0
- package/src/runtime/migrations/vbundle-import-analyzer.ts +212 -0
- package/src/runtime/migrations/vbundle-importer.ts +339 -0
- package/src/runtime/migrations/vbundle-validator.ts +356 -0
- package/src/runtime/nl-approval-parser.ts +138 -0
- package/src/runtime/pending-interactions.ts +16 -7
- package/src/runtime/routes/access-request-decision.ts +73 -52
- package/src/runtime/routes/app-routes.ts +56 -38
- package/src/runtime/routes/approval-routes.ts +144 -92
- package/src/runtime/routes/approval-strategies/guardian-callback-strategy.ts +930 -0
- package/src/runtime/routes/approval-strategies/guardian-legacy-fallback-strategy.ts +82 -0
- package/src/runtime/routes/approval-strategies/guardian-text-engine-strategy.ts +151 -0
- package/src/runtime/routes/attachment-routes.ts +59 -48
- package/src/runtime/routes/brain-graph-routes.ts +85 -69
- package/src/runtime/routes/call-routes.ts +79 -38
- package/src/runtime/routes/canonical-guardian-expiry-sweep.ts +10 -10
- package/src/runtime/routes/channel-delivery-routes.ts +19 -14
- package/src/runtime/routes/channel-guardian-routes.ts +3 -3
- package/src/runtime/routes/channel-inbound-routes.ts +2 -2
- package/src/runtime/routes/channel-readiness-routes.ts +12 -6
- package/src/runtime/routes/channel-route-shared.ts +67 -25
- package/src/runtime/routes/channel-routes.ts +4 -6
- package/src/runtime/routes/contact-routes.ts +374 -17
- package/src/runtime/routes/conversation-attention-routes.ts +57 -28
- package/src/runtime/routes/conversation-routes.ts +321 -174
- package/src/runtime/routes/debug-routes.ts +14 -10
- package/src/runtime/routes/events-routes.ts +90 -57
- package/src/runtime/routes/global-search-routes.ts +266 -0
- package/src/runtime/routes/guardian-action-routes.ts +112 -113
- package/src/runtime/routes/guardian-approval-interception.ts +325 -874
- package/src/runtime/routes/guardian-approval-prompt.ts +40 -24
- package/src/runtime/routes/guardian-approval-reply-helpers.ts +135 -0
- package/src/runtime/routes/guardian-bootstrap-routes.ts +55 -36
- package/src/runtime/routes/guardian-expiry-sweep.ts +63 -37
- package/src/runtime/routes/guardian-refresh-routes.ts +40 -19
- package/src/runtime/routes/identity-routes.ts +71 -42
- package/src/runtime/routes/inbound-conversation.ts +17 -11
- package/src/runtime/routes/inbound-message-handler.ts +305 -1459
- package/src/runtime/routes/inbound-stages/acl-enforcement.ts +880 -0
- package/src/runtime/routes/inbound-stages/background-dispatch.ts +600 -0
- package/src/runtime/routes/inbound-stages/bootstrap-intercept.ts +214 -0
- package/src/runtime/routes/inbound-stages/edit-intercept.ts +116 -0
- package/src/runtime/routes/inbound-stages/escalation-intercept.ts +167 -0
- package/src/runtime/routes/inbound-stages/guardian-reply-intercept.ts +185 -0
- package/src/runtime/routes/inbound-stages/secret-ingress-check.ts +132 -0
- package/src/runtime/routes/inbound-stages/verification-intercept.ts +340 -0
- package/src/runtime/routes/integration-routes.ts +60 -21
- package/src/runtime/routes/invite-routes.ts +140 -0
- package/src/runtime/routes/migration-routes.ts +434 -0
- package/src/runtime/routes/pairing-routes.ts +157 -79
- package/src/runtime/routes/secret-routes.ts +6 -2
- package/src/runtime/routes/twilio-routes.ts +443 -249
- package/src/runtime/slack-block-formatting.ts +176 -0
- package/src/runtime/tool-grant-request-helper.ts +36 -27
- package/src/runtime/{guardian-context-resolver.ts → trust-context-resolver.ts} +29 -41
- package/src/schedule/integration-status.ts +44 -9
- package/src/schedule/recurrence-engine.ts +47 -24
- package/src/schedule/recurrence-types.ts +12 -7
- package/src/schedule/schedule-store.ts +166 -83
- package/src/schedule/scheduler.ts +37 -24
- package/src/security/encrypted-store.ts +68 -38
- package/src/security/keychain.ts +183 -120
- package/src/security/oauth-callback-registry.ts +3 -3
- package/src/security/oauth2.ts +226 -138
- package/src/security/redaction.ts +24 -24
- package/src/security/secret-allowlist.ts +46 -21
- package/src/security/secret-ingress.ts +15 -7
- package/src/security/secret-scanner.ts +193 -104
- package/src/security/secure-keys.ts +9 -3
- package/src/security/token-manager.ts +99 -40
- package/src/security/tool-approval-digest.ts +3 -3
- package/src/sequence/analytics.ts +52 -27
- package/src/sequence/engine.ts +135 -72
- package/src/sequence/guardrails.ts +32 -20
- package/src/sequence/importer.ts +75 -37
- package/src/sequence/reply-matcher.ts +36 -18
- package/src/sequence/store.ts +137 -75
- package/src/sequence/types.ts +30 -16
- package/src/services/published-app-updater.ts +26 -16
- package/src/services/vercel-deploy.ts +19 -15
- package/src/skills/active-skill-tools.ts +3 -3
- package/src/skills/clawhub.ts +178 -90
- package/src/skills/include-graph.ts +24 -17
- package/src/skills/managed-store.ts +89 -42
- package/src/skills/path-classifier.ts +10 -10
- package/src/skills/remote-skill-policy.ts +31 -22
- package/src/skills/slash-commands.ts +36 -30
- package/src/skills/tool-manifest.ts +60 -31
- package/src/skills/version-hash.ts +25 -15
- package/src/slack/slack-webhook.ts +19 -15
- package/src/subagent/index.ts +4 -8
- package/src/subagent/manager.ts +119 -69
- package/src/subagent/types.ts +9 -12
- package/src/swarm/backend-claude-code.ts +124 -45
- package/src/swarm/checkpoint.ts +36 -16
- package/src/swarm/graph-utils.ts +1 -3
- package/src/swarm/index.ts +38 -19
- package/src/swarm/limits.ts +13 -4
- package/src/swarm/orchestrator.ts +108 -57
- package/src/swarm/plan-validator.ts +23 -17
- package/src/swarm/router-planner.ts +51 -22
- package/src/swarm/router-prompts.ts +4 -1
- package/src/swarm/synthesizer.ts +26 -18
- package/src/swarm/types.ts +14 -4
- package/src/swarm/worker-backend.ts +36 -26
- package/src/swarm/worker-prompts.ts +13 -9
- package/src/swarm/worker-runner.ts +40 -34
- package/src/tasks/candidate-store.ts +14 -6
- package/src/tasks/ephemeral-permissions.ts +9 -5
- package/src/tasks/task-compiler.ts +41 -38
- package/src/tasks/task-runner.ts +54 -26
- package/src/tasks/task-scheduler.ts +1 -1
- package/src/tasks/task-store.ts +20 -7
- package/src/tasks/tool-sanitizer.ts +3 -3
- package/src/tools/apps/definitions.ts +23 -15
- package/src/tools/apps/executors.ts +122 -40
- package/src/tools/apps/open-proxy.ts +5 -5
- package/src/tools/apps/registry.ts +2 -2
- package/src/tools/assets/materialize.ts +59 -41
- package/src/tools/assets/search.ts +86 -48
- package/src/tools/browser/api-map.ts +52 -36
- package/src/tools/browser/auth-cache.ts +21 -18
- package/src/tools/browser/auth-detector.ts +43 -28
- package/src/tools/browser/auto-navigate.ts +149 -68
- package/src/tools/browser/browser-execution.ts +9 -3
- package/src/tools/browser/headless-browser.ts +287 -150
- package/src/tools/browser/jit-auth.ts +37 -21
- package/src/tools/browser/network-recorder.ts +138 -56
- package/src/tools/browser/recording-store.ts +22 -15
- package/src/tools/browser/runtime-check.ts +8 -5
- package/src/tools/browser/x-auto-navigate.ts +88 -47
- package/src/tools/calls/call-end.ts +10 -7
- package/src/tools/calls/call-start.ts +30 -20
- package/src/tools/calls/call-status.ts +8 -5
- package/src/tools/claude-code/claude-code.ts +301 -165
- package/src/tools/computer-use/definitions.ts +175 -130
- package/src/tools/computer-use/registry.ts +2 -2
- package/src/tools/computer-use/request-computer-control.ts +21 -13
- package/src/tools/computer-use/skill-proxy-bridge.ts +1 -1
- package/src/tools/credentials/account-registry.ts +52 -35
- package/src/tools/credentials/broker-types.ts +1 -1
- package/src/tools/credentials/broker.ts +97 -55
- package/src/tools/credentials/domain-policy.ts +5 -2
- package/src/tools/credentials/host-pattern-match.ts +15 -8
- package/src/tools/credentials/metadata-store.ts +93 -43
- package/src/tools/credentials/policy-types.ts +5 -2
- package/src/tools/credentials/policy-validate.ts +21 -14
- package/src/tools/credentials/post-connect-hooks.ts +18 -7
- package/src/tools/credentials/resolve.ts +11 -10
- package/src/tools/credentials/selection.ts +30 -25
- package/src/tools/credentials/tool-policy.ts +5 -2
- package/src/tools/credentials/vault.ts +538 -185
- package/src/tools/document/document-tool.ts +23 -17
- package/src/tools/document/editor-template.ts +12 -7
- package/src/tools/execution-target.ts +13 -10
- package/src/tools/execution-timeout.ts +6 -5
- package/src/tools/executor.ts +141 -74
- package/src/tools/filesystem/edit.ts +82 -45
- package/src/tools/filesystem/fuzzy-match.ts +70 -32
- package/src/tools/filesystem/read.ts +46 -28
- package/src/tools/filesystem/view-image.ts +86 -42
- package/src/tools/filesystem/write.ts +53 -32
- package/src/tools/followups/followup_create.ts +43 -17
- package/src/tools/followups/followup_list.ts +28 -13
- package/src/tools/followups/followup_resolve.ts +9 -6
- package/src/tools/guardian-control-plane-policy.ts +15 -14
- package/src/tools/host-filesystem/edit.ts +77 -42
- package/src/tools/host-filesystem/read.ts +52 -33
- package/src/tools/host-filesystem/write.ts +50 -29
- package/src/tools/host-terminal/host-shell.ts +97 -61
- package/src/tools/mcp/mcp-tool-factory.ts +21 -14
- package/src/tools/memory/definitions.ts +60 -28
- package/src/tools/memory/handlers.ts +149 -77
- package/src/tools/memory/register.ts +39 -16
- package/src/tools/network/__tests__/web-search.test.ts +236 -177
- package/src/tools/network/domain-normalize.ts +13 -9
- package/src/tools/network/script-proxy/__tests__/logging.test.ts +193 -123
- package/src/tools/network/script-proxy/__tests__/policy.test.ts +225 -127
- package/src/tools/network/script-proxy/index.ts +1 -17
- package/src/tools/network/script-proxy/session-manager.ts +178 -86
- package/src/tools/network/url-safety.ts +56 -34
- package/src/tools/network/web-fetch.ts +273 -155
- package/src/tools/network/web-search.ts +166 -81
- package/src/tools/permission-checker.ts +24 -25
- package/src/tools/policy-context.ts +8 -5
- package/src/tools/registry.ts +73 -46
- package/src/tools/reminder/reminder-store.ts +65 -44
- package/src/tools/reminder/reminder.ts +76 -35
- package/src/tools/schedule/create.ts +44 -21
- package/src/tools/schedule/delete.ts +8 -5
- package/src/tools/schedule/list.ts +39 -19
- package/src/tools/schedule/update.ts +49 -26
- package/src/tools/secret-detection-handler.ts +130 -49
- package/src/tools/sensitive-output-placeholders.ts +15 -8
- package/src/tools/shared/filesystem/edit-engine.ts +45 -14
- package/src/tools/shared/filesystem/errors.ts +18 -18
- package/src/tools/shared/filesystem/file-ops-service.ts +59 -32
- package/src/tools/shared/filesystem/format-diff.ts +21 -11
- package/src/tools/shared/filesystem/path-policy.ts +17 -13
- package/src/tools/shared/filesystem/size-guard.ts +8 -4
- package/src/tools/shared/filesystem/types.ts +2 -2
- package/src/tools/shared/shell-output.ts +4 -3
- package/src/tools/side-effects.ts +36 -28
- package/src/tools/skills/delete-managed.ts +30 -17
- package/src/tools/skills/load.ts +88 -46
- package/src/tools/skills/sandbox-runner.ts +62 -46
- package/src/tools/skills/scaffold-managed.ts +98 -48
- package/src/tools/skills/script-contract.ts +5 -2
- package/src/tools/skills/skill-script-runner.ts +29 -13
- package/src/tools/skills/skill-tool-factory.ts +20 -10
- package/src/tools/subagent/abort.ts +10 -4
- package/src/tools/subagent/message.ts +14 -8
- package/src/tools/subagent/read.ts +20 -11
- package/src/tools/subagent/spawn.ts +14 -6
- package/src/tools/subagent/status.ts +7 -4
- package/src/tools/swarm/delegate.ts +75 -49
- package/src/tools/system/avatar-generator.ts +46 -33
- package/src/tools/system/navigate-settings.ts +29 -19
- package/src/tools/system/open-system-settings.ts +30 -20
- package/src/tools/system/request-permission.ts +59 -44
- package/src/tools/system/version.ts +27 -16
- package/src/tools/system/voice-config.ts +116 -53
- package/src/tools/tasks/index.ts +8 -8
- package/src/tools/tasks/task-delete.ts +61 -22
- package/src/tools/tasks/task-list.ts +23 -11
- package/src/tools/tasks/task-run.ts +41 -16
- package/src/tools/tasks/task-save.ts +27 -10
- package/src/tools/tasks/work-item-enqueue.ts +114 -48
- package/src/tools/tasks/work-item-list.ts +20 -10
- package/src/tools/tasks/work-item-remove.ts +49 -15
- package/src/tools/tasks/work-item-run.ts +34 -13
- package/src/tools/tasks/work-item-update.ts +84 -31
- package/src/tools/terminal/backends/native.ts +64 -35
- package/src/tools/terminal/backends/types.ts +6 -2
- package/src/tools/terminal/parser.ts +200 -125
- package/src/tools/terminal/safe-env.ts +27 -21
- package/src/tools/terminal/sandbox-diagnostics.ts +31 -13
- package/src/tools/terminal/sandbox.ts +10 -6
- package/src/tools/terminal/shell.ts +134 -68
- package/src/tools/tool-approval-handler.ts +239 -140
- package/src/tools/types.ts +79 -22
- package/src/tools/ui-surface/definitions.ts +124 -89
- package/src/tools/ui-surface/registry.ts +2 -2
- package/src/tools/watch/screen-watch.ts +50 -32
- package/src/tools/watch/watch-state.ts +41 -15
- package/src/tools/watcher/create.ts +37 -15
- package/src/tools/watcher/delete.ts +9 -6
- package/src/tools/watcher/digest.ts +10 -6
- package/src/tools/watcher/list.ts +37 -14
- package/src/tools/watcher/update.ts +33 -18
- package/src/tools/weather/service.ts +331 -174
- package/src/twitter/client.ts +261 -138
- package/src/twitter/oauth-client.ts +17 -13
- package/src/twitter/router.ts +51 -23
- package/src/twitter/session.ts +27 -18
- package/src/types/qrcode.d.ts +6 -3
- package/src/usage/actors.ts +16 -16
- package/src/usage/types.ts +3 -3
- package/src/util/bundled-asset.ts +10 -6
- package/src/util/canonicalize-identity.ts +11 -4
- package/src/util/clipboard.ts +7 -7
- package/src/util/content-id.ts +3 -3
- package/src/util/debounce.ts +3 -2
- package/src/util/diff.ts +55 -33
- package/src/util/errors.ts +31 -27
- package/src/util/fs.ts +8 -2
- package/src/util/log-redact.ts +12 -12
- package/src/util/logger.ts +112 -51
- package/src/util/network-info.ts +13 -5
- package/src/util/object.ts +4 -2
- package/src/util/phone.ts +4 -4
- package/src/util/platform.ts +80 -58
- package/src/util/pricing.ts +49 -31
- package/src/util/retry.ts +39 -7
- package/src/util/row-mapper.ts +7 -4
- package/src/util/silently.ts +7 -4
- package/src/util/spawn.ts +48 -0
- package/src/util/spinner.ts +9 -7
- package/src/util/time.ts +16 -3
- package/src/util/truncate.ts +1 -1
- package/src/util/voice-code.ts +6 -4
- package/src/util/xml.ts +5 -1
- package/src/version.ts +12 -8
- package/src/watcher/engine.ts +71 -44
- package/src/watcher/provider-registry.ts +1 -1
- package/src/watcher/providers/github.ts +40 -23
- package/src/watcher/providers/gmail.ts +59 -38
- package/src/watcher/providers/google-calendar.ts +62 -48
- package/src/watcher/providers/linear.ts +219 -150
- package/src/watcher/providers/slack.ts +125 -29
- package/src/watcher/watcher-store.ts +75 -55
- package/src/work-items/work-item-runner.ts +62 -29
- package/src/work-items/work-item-store.ts +137 -47
- package/src/workspace/commit-message-enrichment-service.ts +65 -25
- package/src/workspace/commit-message-provider.ts +14 -12
- package/src/workspace/git-service.ts +355 -239
- package/src/workspace/heartbeat-service.ts +74 -37
- package/src/workspace/provider-commit-message-generator.ts +95 -70
- package/src/workspace/top-level-renderer.ts +10 -8
- package/src/workspace/top-level-scanner.ts +9 -3
- package/src/workspace/turn-commit.ts +63 -36
- package/src/__tests__/ingress-member-store.test.ts +0 -294
- package/src/__tests__/script-proxy-router.test.ts +0 -215
- package/src/config/bundled-skills/trusted-contacts/SKILL.md +0 -372
- package/src/memory/guardian-bindings.ts +0 -158
- package/src/memory/ingress-member-store.ts +0 -352
- package/src/runtime/routes/ingress-routes.ts +0 -229
- package/src/tools/network/script-proxy/__tests__/router.test.ts +0 -77
- package/src/tools/network/script-proxy/certs.ts +0 -7
- package/src/tools/network/script-proxy/connect-tunnel.ts +0 -1
- package/src/tools/network/script-proxy/http-forwarder.ts +0 -2
- package/src/tools/network/script-proxy/logging.ts +0 -12
- package/src/tools/network/script-proxy/mitm-handler.ts +0 -2
- package/src/tools/network/script-proxy/policy.ts +0 -4
- package/src/tools/network/script-proxy/router.ts +0 -2
- package/src/tools/network/script-proxy/server.ts +0 -5
- package/src/tools/network/script-proxy/types.ts +0 -19
|
@@ -6,38 +6,46 @@
|
|
|
6
6
|
* from Twilio and can send text tokens back for TTS.
|
|
7
7
|
*/
|
|
8
8
|
|
|
9
|
-
import { randomInt } from
|
|
10
|
-
|
|
11
|
-
import type { ServerWebSocket } from
|
|
12
|
-
|
|
13
|
-
import { getConfig } from
|
|
14
|
-
import { resolveUserReference } from
|
|
15
|
-
import {
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
import {
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
9
|
+
import { randomInt } from "node:crypto";
|
|
10
|
+
|
|
11
|
+
import type { ServerWebSocket } from "bun";
|
|
12
|
+
|
|
13
|
+
import { getConfig } from "../config/loader.js";
|
|
14
|
+
import { resolveUserReference } from "../config/user-reference.js";
|
|
15
|
+
import {
|
|
16
|
+
findContactChannel,
|
|
17
|
+
findGuardianForChannel,
|
|
18
|
+
listGuardianChannels,
|
|
19
|
+
} from "../contacts/contact-store.js";
|
|
20
|
+
import {
|
|
21
|
+
createGuardianBinding,
|
|
22
|
+
revokeGuardianBinding,
|
|
23
|
+
upsertMember,
|
|
24
|
+
} from "../contacts/contacts-write.js";
|
|
25
|
+
import { getAssistantName } from "../daemon/identity-helpers.js";
|
|
26
|
+
import { getCanonicalGuardianRequest } from "../memory/canonical-guardian-store.js";
|
|
27
|
+
import * as conversationStore from "../memory/conversation-store.js";
|
|
28
|
+
import { findActiveVoiceInvites } from "../memory/invite-store.js";
|
|
29
|
+
import { revokeScopedApprovalGrantsForContext } from "../memory/scoped-approval-grants.js";
|
|
30
|
+
import { emitNotificationSignal } from "../notifications/emit-signal.js";
|
|
31
|
+
import { notifyGuardianOfAccessRequest } from "../runtime/access-request-helper.js";
|
|
24
32
|
import {
|
|
25
33
|
resolveActorTrust,
|
|
26
|
-
|
|
27
|
-
} from
|
|
28
|
-
import { DAEMON_INTERNAL_ASSISTANT_ID } from
|
|
34
|
+
toTrustContext,
|
|
35
|
+
} from "../runtime/actor-trust-resolver.js";
|
|
36
|
+
import { DAEMON_INTERNAL_ASSISTANT_ID } from "../runtime/assistant-scope.js";
|
|
29
37
|
import {
|
|
30
38
|
getGuardianBinding,
|
|
31
39
|
getPendingChallenge,
|
|
32
40
|
validateAndConsumeChallenge,
|
|
33
|
-
} from
|
|
41
|
+
} from "../runtime/channel-guardian-service.js";
|
|
34
42
|
import {
|
|
35
43
|
composeVerificationVoice,
|
|
36
44
|
GUARDIAN_VERIFY_TEMPLATE_KEYS,
|
|
37
|
-
} from
|
|
38
|
-
import { redeemVoiceInviteCode } from
|
|
39
|
-
import { parseJsonSafe } from
|
|
40
|
-
import { getLogger } from
|
|
45
|
+
} from "../runtime/guardian-verification-templates.js";
|
|
46
|
+
import { redeemVoiceInviteCode } from "../runtime/invite-service.js";
|
|
47
|
+
import { parseJsonSafe } from "../util/json.js";
|
|
48
|
+
import { getLogger } from "../util/logger.js";
|
|
41
49
|
import {
|
|
42
50
|
getAccessRequestPollIntervalMs,
|
|
43
51
|
getGuardianWaitUpdateInitialIntervalMs,
|
|
@@ -46,31 +54,34 @@ import {
|
|
|
46
54
|
getGuardianWaitUpdateSteadyMinIntervalMs,
|
|
47
55
|
getTtsPlaybackDelayMs,
|
|
48
56
|
getUserConsultationTimeoutMs,
|
|
49
|
-
} from
|
|
50
|
-
import { CallController } from
|
|
51
|
-
import { persistCallCompletionMessage } from
|
|
52
|
-
import { addPointerMessage, formatDuration } from
|
|
53
|
-
import {
|
|
54
|
-
|
|
57
|
+
} from "./call-constants.js";
|
|
58
|
+
import { CallController } from "./call-controller.js";
|
|
59
|
+
import { persistCallCompletionMessage } from "./call-conversation-messages.js";
|
|
60
|
+
import { addPointerMessage, formatDuration } from "./call-pointer-messages.js";
|
|
61
|
+
import {
|
|
62
|
+
fireCallCompletionNotifier,
|
|
63
|
+
fireCallTranscriptNotifier,
|
|
64
|
+
} from "./call-state.js";
|
|
65
|
+
import { isTerminalState } from "./call-state-machine.js";
|
|
55
66
|
import {
|
|
56
67
|
expirePendingQuestions,
|
|
57
68
|
getCallSession,
|
|
58
69
|
recordCallEvent,
|
|
59
70
|
updateCallSession,
|
|
60
|
-
} from
|
|
71
|
+
} from "./call-store.js";
|
|
61
72
|
import {
|
|
62
73
|
extractPromptSpeakerMetadata,
|
|
63
74
|
type PromptSpeakerContext,
|
|
64
75
|
SpeakerIdentityTracker,
|
|
65
|
-
} from
|
|
76
|
+
} from "./speaker-identification.js";
|
|
66
77
|
|
|
67
|
-
const log = getLogger(
|
|
78
|
+
const log = getLogger("relay-server");
|
|
68
79
|
|
|
69
80
|
// ── ConversationRelay message types ──────────────────────────────────
|
|
70
81
|
|
|
71
82
|
// Messages FROM Twilio
|
|
72
83
|
export interface RelaySetupMessage {
|
|
73
|
-
type:
|
|
84
|
+
type: "setup";
|
|
74
85
|
callSid: string;
|
|
75
86
|
from: string;
|
|
76
87
|
to: string;
|
|
@@ -78,7 +89,7 @@ export interface RelaySetupMessage {
|
|
|
78
89
|
}
|
|
79
90
|
|
|
80
91
|
export interface RelayPromptMessage {
|
|
81
|
-
type:
|
|
92
|
+
type: "prompt";
|
|
82
93
|
voicePrompt: string;
|
|
83
94
|
lang: string;
|
|
84
95
|
last: boolean;
|
|
@@ -102,17 +113,17 @@ export interface RelayPromptMessage {
|
|
|
102
113
|
}
|
|
103
114
|
|
|
104
115
|
export interface RelayInterruptMessage {
|
|
105
|
-
type:
|
|
116
|
+
type: "interrupt";
|
|
106
117
|
utteranceUntilInterrupt: string;
|
|
107
118
|
}
|
|
108
119
|
|
|
109
120
|
export interface RelayDtmfMessage {
|
|
110
|
-
type:
|
|
121
|
+
type: "dtmf";
|
|
111
122
|
digit: string;
|
|
112
123
|
}
|
|
113
124
|
|
|
114
125
|
export interface RelayErrorMessage {
|
|
115
|
-
type:
|
|
126
|
+
type: "error";
|
|
116
127
|
description: string;
|
|
117
128
|
}
|
|
118
129
|
|
|
@@ -125,13 +136,13 @@ export type RelayInboundMessage =
|
|
|
125
136
|
|
|
126
137
|
// Messages TO Twilio
|
|
127
138
|
export interface RelayTextMessage {
|
|
128
|
-
type:
|
|
139
|
+
type: "text";
|
|
129
140
|
token: string;
|
|
130
141
|
last: boolean;
|
|
131
142
|
}
|
|
132
143
|
|
|
133
144
|
export interface RelayEndMessage {
|
|
134
|
-
type:
|
|
145
|
+
type: "end";
|
|
135
146
|
handoffData?: string;
|
|
136
147
|
}
|
|
137
148
|
|
|
@@ -147,10 +158,14 @@ export interface RelayWebSocketData {
|
|
|
147
158
|
export const activeRelayConnections = new Map<string, RelayConnection>();
|
|
148
159
|
|
|
149
160
|
/** Module-level broadcast function, set by the HTTP server during startup. */
|
|
150
|
-
let globalBroadcast:
|
|
161
|
+
let globalBroadcast:
|
|
162
|
+
| ((msg: import("../daemon/ipc-contract.js").ServerMessage) => void)
|
|
163
|
+
| undefined;
|
|
151
164
|
|
|
152
165
|
/** Register a broadcast function so RelayConnection can forward IPC events. */
|
|
153
|
-
export function setRelayBroadcast(
|
|
166
|
+
export function setRelayBroadcast(
|
|
167
|
+
fn: (msg: import("../daemon/ipc-contract.js").ServerMessage) => void,
|
|
168
|
+
): void {
|
|
154
169
|
globalBroadcast = fn;
|
|
155
170
|
}
|
|
156
171
|
|
|
@@ -159,13 +174,18 @@ export function setRelayBroadcast(fn: (msg: import('../daemon/ipc-contract.js').
|
|
|
159
174
|
/**
|
|
160
175
|
* Manages a single WebSocket connection for one call.
|
|
161
176
|
*/
|
|
162
|
-
export type RelayConnectionState =
|
|
177
|
+
export type RelayConnectionState =
|
|
178
|
+
| "connected"
|
|
179
|
+
| "verification_pending"
|
|
180
|
+
| "awaiting_name"
|
|
181
|
+
| "awaiting_guardian_decision"
|
|
182
|
+
| "disconnecting";
|
|
163
183
|
|
|
164
184
|
export class RelayConnection {
|
|
165
185
|
private ws: ServerWebSocket<RelayWebSocketData>;
|
|
166
186
|
private callSessionId: string;
|
|
167
187
|
private conversationHistory: Array<{
|
|
168
|
-
role:
|
|
188
|
+
role: "caller" | "assistant";
|
|
169
189
|
text: string;
|
|
170
190
|
timestamp: number;
|
|
171
191
|
speaker?: PromptSpeakerContext;
|
|
@@ -175,12 +195,12 @@ export class RelayConnection {
|
|
|
175
195
|
private speakerIdentityTracker: SpeakerIdentityTracker;
|
|
176
196
|
|
|
177
197
|
// Verification state (outbound callee verification)
|
|
178
|
-
private connectionState: RelayConnectionState =
|
|
198
|
+
private connectionState: RelayConnectionState = "connected";
|
|
179
199
|
private verificationCode: string | null = null;
|
|
180
200
|
private verificationAttempts = 0;
|
|
181
201
|
private verificationMaxAttempts = 3;
|
|
182
202
|
private verificationCodeLength = 6;
|
|
183
|
-
private dtmfBuffer =
|
|
203
|
+
private dtmfBuffer = "";
|
|
184
204
|
|
|
185
205
|
// Inbound voice guardian verification state
|
|
186
206
|
private guardianVerificationActive = false;
|
|
@@ -204,14 +224,16 @@ export class RelayConnection {
|
|
|
204
224
|
private accessRequestAssistantId: string | null = null;
|
|
205
225
|
private accessRequestFromNumber: string | null = null;
|
|
206
226
|
private accessRequestPollTimer: ReturnType<typeof setInterval> | null = null;
|
|
207
|
-
private accessRequestTimeoutTimer: ReturnType<typeof setTimeout> | null =
|
|
227
|
+
private accessRequestTimeoutTimer: ReturnType<typeof setTimeout> | null =
|
|
228
|
+
null;
|
|
208
229
|
private accessRequestCallerName: string | null = null;
|
|
209
230
|
|
|
210
231
|
// Name capture timeout (unknown inbound callers)
|
|
211
232
|
private nameCaptureTimeoutTimer: ReturnType<typeof setTimeout> | null = null;
|
|
212
233
|
|
|
213
234
|
// Guardian wait heartbeat state
|
|
214
|
-
private accessRequestHeartbeatTimer: ReturnType<typeof setTimeout> | null =
|
|
235
|
+
private accessRequestHeartbeatTimer: ReturnType<typeof setTimeout> | null =
|
|
236
|
+
null;
|
|
215
237
|
private accessRequestWaitStartedAt: number = 0;
|
|
216
238
|
private heartbeatSequence = 0;
|
|
217
239
|
|
|
@@ -259,28 +281,37 @@ export class RelayConnection {
|
|
|
259
281
|
async handleMessage(data: string): Promise<void> {
|
|
260
282
|
const parsed = parseJsonSafe<RelayInboundMessage>(data);
|
|
261
283
|
if (!parsed) {
|
|
262
|
-
log.warn(
|
|
284
|
+
log.warn(
|
|
285
|
+
{ callSessionId: this.callSessionId, data },
|
|
286
|
+
"Failed to parse relay message",
|
|
287
|
+
);
|
|
263
288
|
return;
|
|
264
289
|
}
|
|
265
290
|
|
|
266
291
|
switch (parsed.type) {
|
|
267
|
-
case
|
|
292
|
+
case "setup":
|
|
268
293
|
await this.handleSetup(parsed);
|
|
269
294
|
break;
|
|
270
|
-
case
|
|
295
|
+
case "prompt":
|
|
271
296
|
await this.handlePrompt(parsed);
|
|
272
297
|
break;
|
|
273
|
-
case
|
|
298
|
+
case "interrupt":
|
|
274
299
|
this.handleInterrupt(parsed);
|
|
275
300
|
break;
|
|
276
|
-
case
|
|
301
|
+
case "dtmf":
|
|
277
302
|
this.handleDtmf(parsed);
|
|
278
303
|
break;
|
|
279
|
-
case
|
|
304
|
+
case "error":
|
|
280
305
|
this.handleError(parsed);
|
|
281
306
|
break;
|
|
282
307
|
default:
|
|
283
|
-
log.warn(
|
|
308
|
+
log.warn(
|
|
309
|
+
{
|
|
310
|
+
callSessionId: this.callSessionId,
|
|
311
|
+
type: (parsed as { type: unknown }).type,
|
|
312
|
+
},
|
|
313
|
+
"Unknown relay message type",
|
|
314
|
+
);
|
|
284
315
|
}
|
|
285
316
|
}
|
|
286
317
|
|
|
@@ -288,11 +319,14 @@ export class RelayConnection {
|
|
|
288
319
|
* Send a text token to the caller for TTS playback.
|
|
289
320
|
*/
|
|
290
321
|
sendTextToken(token: string, last: boolean): void {
|
|
291
|
-
const message: RelayTextMessage = { type:
|
|
322
|
+
const message: RelayTextMessage = { type: "text", token, last };
|
|
292
323
|
try {
|
|
293
324
|
this.ws.send(JSON.stringify(message));
|
|
294
325
|
} catch (err) {
|
|
295
|
-
log.error(
|
|
326
|
+
log.error(
|
|
327
|
+
{ err, callSessionId: this.callSessionId },
|
|
328
|
+
"Failed to send text token",
|
|
329
|
+
);
|
|
296
330
|
}
|
|
297
331
|
}
|
|
298
332
|
|
|
@@ -300,22 +334,33 @@ export class RelayConnection {
|
|
|
300
334
|
* End the ConversationRelay session.
|
|
301
335
|
*/
|
|
302
336
|
endSession(reason?: string): void {
|
|
303
|
-
const message: RelayEndMessage = { type:
|
|
337
|
+
const message: RelayEndMessage = { type: "end" };
|
|
304
338
|
if (reason) {
|
|
305
339
|
message.handoffData = JSON.stringify({ reason });
|
|
306
340
|
}
|
|
307
341
|
try {
|
|
308
342
|
this.ws.send(JSON.stringify(message));
|
|
309
343
|
} catch (err) {
|
|
310
|
-
log.error(
|
|
344
|
+
log.error(
|
|
345
|
+
{ err, callSessionId: this.callSessionId },
|
|
346
|
+
"Failed to send end message",
|
|
347
|
+
);
|
|
311
348
|
}
|
|
312
349
|
}
|
|
313
350
|
|
|
314
351
|
/**
|
|
315
352
|
* Get the conversation history for context.
|
|
316
353
|
*/
|
|
317
|
-
getConversationHistory(): Array<{
|
|
318
|
-
|
|
354
|
+
getConversationHistory(): Array<{
|
|
355
|
+
role: string;
|
|
356
|
+
text: string;
|
|
357
|
+
speaker?: PromptSpeakerContext;
|
|
358
|
+
}> {
|
|
359
|
+
return this.conversationHistory.map(({ role, text, speaker }) => ({
|
|
360
|
+
role,
|
|
361
|
+
text,
|
|
362
|
+
speaker,
|
|
363
|
+
}));
|
|
319
364
|
}
|
|
320
365
|
|
|
321
366
|
/**
|
|
@@ -365,7 +410,10 @@ export class RelayConnection {
|
|
|
365
410
|
}
|
|
366
411
|
this.accessRequestWaitActive = false;
|
|
367
412
|
this.abortController.abort();
|
|
368
|
-
log.info(
|
|
413
|
+
log.info(
|
|
414
|
+
{ callSessionId: this.callSessionId },
|
|
415
|
+
"RelayConnection destroyed",
|
|
416
|
+
);
|
|
369
417
|
}
|
|
370
418
|
|
|
371
419
|
/**
|
|
@@ -378,7 +426,7 @@ export class RelayConnection {
|
|
|
378
426
|
// If the call was still in guardian-wait with callback opt-in, emit the
|
|
379
427
|
// handoff notification before cleaning up wait state.
|
|
380
428
|
if (this.accessRequestWaitActive && this.callbackOptIn) {
|
|
381
|
-
this.emitAccessRequestCallbackHandoff(
|
|
429
|
+
this.emitAccessRequestCallbackHandoff("transport_closed");
|
|
382
430
|
}
|
|
383
431
|
|
|
384
432
|
// Clean up access request wait state on disconnect to stop polling
|
|
@@ -395,41 +443,60 @@ export class RelayConnection {
|
|
|
395
443
|
const isNormalClose = code === 1000;
|
|
396
444
|
if (isNormalClose) {
|
|
397
445
|
updateCallSession(this.callSessionId, {
|
|
398
|
-
status:
|
|
446
|
+
status: "completed",
|
|
399
447
|
endedAt: Date.now(),
|
|
400
448
|
});
|
|
401
|
-
recordCallEvent(this.callSessionId,
|
|
402
|
-
reason: reason ||
|
|
449
|
+
recordCallEvent(this.callSessionId, "call_ended", {
|
|
450
|
+
reason: reason || "relay_closed",
|
|
403
451
|
closeCode: code,
|
|
404
452
|
});
|
|
405
453
|
|
|
406
454
|
// Post a pointer message in the initiating conversation
|
|
407
455
|
if (session.initiatedFromConversationId) {
|
|
408
|
-
const durationMs = session.startedAt
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
456
|
+
const durationMs = session.startedAt
|
|
457
|
+
? Date.now() - session.startedAt
|
|
458
|
+
: 0;
|
|
459
|
+
addPointerMessage(
|
|
460
|
+
session.initiatedFromConversationId,
|
|
461
|
+
"completed",
|
|
462
|
+
session.toNumber,
|
|
463
|
+
{
|
|
464
|
+
duration: durationMs > 0 ? formatDuration(durationMs) : undefined,
|
|
465
|
+
},
|
|
466
|
+
).catch((err) => {
|
|
467
|
+
log.warn(
|
|
468
|
+
{ conversationId: session.initiatedFromConversationId, err },
|
|
469
|
+
"Skipping pointer write — origin conversation may no longer exist",
|
|
470
|
+
);
|
|
413
471
|
});
|
|
414
472
|
}
|
|
415
473
|
} else {
|
|
416
|
-
const detail =
|
|
474
|
+
const detail =
|
|
475
|
+
reason || (code ? `relay_closed_${code}` : "relay_closed_abnormal");
|
|
417
476
|
updateCallSession(this.callSessionId, {
|
|
418
|
-
status:
|
|
477
|
+
status: "failed",
|
|
419
478
|
endedAt: Date.now(),
|
|
420
479
|
lastError: `Relay websocket closed unexpectedly: ${detail}`,
|
|
421
480
|
});
|
|
422
|
-
recordCallEvent(this.callSessionId,
|
|
481
|
+
recordCallEvent(this.callSessionId, "call_failed", {
|
|
423
482
|
reason: detail,
|
|
424
483
|
closeCode: code,
|
|
425
484
|
});
|
|
426
485
|
|
|
427
486
|
// Post a failure pointer message in the initiating conversation
|
|
428
487
|
if (session.initiatedFromConversationId) {
|
|
429
|
-
addPointerMessage(
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
488
|
+
addPointerMessage(
|
|
489
|
+
session.initiatedFromConversationId,
|
|
490
|
+
"failed",
|
|
491
|
+
session.toNumber,
|
|
492
|
+
{
|
|
493
|
+
reason: detail,
|
|
494
|
+
},
|
|
495
|
+
).catch((err) => {
|
|
496
|
+
log.warn(
|
|
497
|
+
{ conversationId: session.initiatedFromConversationId, err },
|
|
498
|
+
"Skipping pointer write — origin conversation may no longer exist",
|
|
499
|
+
);
|
|
433
500
|
});
|
|
434
501
|
}
|
|
435
502
|
}
|
|
@@ -441,14 +508,31 @@ export class RelayConnection {
|
|
|
441
508
|
// guardian-approval-interception minting path sets callSessionId: null
|
|
442
509
|
// but always sets conversationId.
|
|
443
510
|
try {
|
|
444
|
-
revokeScopedApprovalGrantsForContext({
|
|
445
|
-
|
|
511
|
+
revokeScopedApprovalGrantsForContext({
|
|
512
|
+
callSessionId: this.callSessionId,
|
|
513
|
+
});
|
|
514
|
+
revokeScopedApprovalGrantsForContext({
|
|
515
|
+
conversationId: session.conversationId,
|
|
516
|
+
});
|
|
446
517
|
} catch (err) {
|
|
447
|
-
log.warn(
|
|
518
|
+
log.warn(
|
|
519
|
+
{ err, callSessionId: this.callSessionId },
|
|
520
|
+
"Failed to revoke scoped grants on transport close",
|
|
521
|
+
);
|
|
448
522
|
}
|
|
449
523
|
|
|
450
|
-
persistCallCompletionMessage(
|
|
451
|
-
|
|
524
|
+
persistCallCompletionMessage(
|
|
525
|
+
session.conversationId,
|
|
526
|
+
this.callSessionId,
|
|
527
|
+
).catch((err) => {
|
|
528
|
+
log.error(
|
|
529
|
+
{
|
|
530
|
+
err,
|
|
531
|
+
conversationId: session.conversationId,
|
|
532
|
+
callSessionId: this.callSessionId,
|
|
533
|
+
},
|
|
534
|
+
"Failed to persist call completion message",
|
|
535
|
+
);
|
|
452
536
|
});
|
|
453
537
|
fireCallCompletionNotifier(session.conversationId, this.callSessionId);
|
|
454
538
|
}
|
|
@@ -457,8 +541,13 @@ export class RelayConnection {
|
|
|
457
541
|
|
|
458
542
|
private async handleSetup(msg: RelaySetupMessage): Promise<void> {
|
|
459
543
|
log.info(
|
|
460
|
-
{
|
|
461
|
-
|
|
544
|
+
{
|
|
545
|
+
callSessionId: this.callSessionId,
|
|
546
|
+
callSid: msg.callSid,
|
|
547
|
+
from: msg.from,
|
|
548
|
+
to: msg.to,
|
|
549
|
+
},
|
|
550
|
+
"ConversationRelay setup received",
|
|
462
551
|
);
|
|
463
552
|
|
|
464
553
|
// Store the callSid association on the call session
|
|
@@ -467,8 +556,12 @@ export class RelayConnection {
|
|
|
467
556
|
const updates: Parameters<typeof updateCallSession>[1] = {
|
|
468
557
|
providerCallSid: msg.callSid,
|
|
469
558
|
};
|
|
470
|
-
if (
|
|
471
|
-
|
|
559
|
+
if (
|
|
560
|
+
!isTerminalState(session.status) &&
|
|
561
|
+
session.status !== "in_progress" &&
|
|
562
|
+
session.status !== "waiting_on_user"
|
|
563
|
+
) {
|
|
564
|
+
updates.status = "in_progress";
|
|
472
565
|
if (!session.startedAt) {
|
|
473
566
|
updates.startedAt = Date.now();
|
|
474
567
|
}
|
|
@@ -481,12 +574,12 @@ export class RelayConnection {
|
|
|
481
574
|
const safeCustomParameters = msg.customParameters
|
|
482
575
|
? Object.fromEntries(
|
|
483
576
|
Object.entries(msg.customParameters).filter(
|
|
484
|
-
([key]) => !key.toLowerCase().includes(
|
|
577
|
+
([key]) => !key.toLowerCase().includes("secret"),
|
|
485
578
|
),
|
|
486
579
|
)
|
|
487
580
|
: undefined;
|
|
488
581
|
|
|
489
|
-
recordCallEvent(this.callSessionId,
|
|
582
|
+
recordCallEvent(this.callSessionId, "call_connected", {
|
|
490
583
|
callSid: msg.callSid,
|
|
491
584
|
from: msg.from,
|
|
492
585
|
to: msg.to,
|
|
@@ -511,17 +604,25 @@ export class RelayConnection {
|
|
|
511
604
|
const otherPartyNumber = isInbound ? msg.from : msg.to;
|
|
512
605
|
const initialActorTrust = resolveActorTrust({
|
|
513
606
|
assistantId,
|
|
514
|
-
sourceChannel:
|
|
607
|
+
sourceChannel: "voice",
|
|
515
608
|
conversationExternalId: otherPartyNumber,
|
|
516
609
|
actorExternalId: otherPartyNumber || undefined,
|
|
517
610
|
});
|
|
518
|
-
const
|
|
611
|
+
const initialTrustContext = toTrustContext(
|
|
612
|
+
initialActorTrust,
|
|
613
|
+
otherPartyNumber,
|
|
614
|
+
);
|
|
519
615
|
|
|
520
|
-
const controller = new CallController(
|
|
521
|
-
|
|
522
|
-
|
|
523
|
-
|
|
524
|
-
|
|
616
|
+
const controller = new CallController(
|
|
617
|
+
this.callSessionId,
|
|
618
|
+
this,
|
|
619
|
+
session?.task ?? null,
|
|
620
|
+
{
|
|
621
|
+
broadcast: globalBroadcast,
|
|
622
|
+
assistantId,
|
|
623
|
+
trustContext: initialTrustContext,
|
|
624
|
+
},
|
|
625
|
+
);
|
|
525
626
|
this.setController(controller);
|
|
526
627
|
|
|
527
628
|
// Detect outbound guardian verification call from persisted call session
|
|
@@ -529,21 +630,37 @@ export class RelayConnection {
|
|
|
529
630
|
// as secondary signal for backward compatibility and observability.
|
|
530
631
|
const persistedMode = session?.callMode;
|
|
531
632
|
const persistedGvSessionId = session?.guardianVerificationSessionId;
|
|
532
|
-
const customParamGvSessionId =
|
|
533
|
-
|
|
534
|
-
|
|
535
|
-
|
|
536
|
-
|
|
633
|
+
const customParamGvSessionId =
|
|
634
|
+
msg.customParameters?.guardianVerificationSessionId;
|
|
635
|
+
const guardianVerificationSessionId =
|
|
636
|
+
persistedGvSessionId ?? customParamGvSessionId;
|
|
637
|
+
|
|
638
|
+
if (
|
|
639
|
+
persistedMode === "guardian_verification" &&
|
|
640
|
+
guardianVerificationSessionId
|
|
641
|
+
) {
|
|
642
|
+
this.startOutboundGuardianVerification(
|
|
643
|
+
assistantId,
|
|
644
|
+
guardianVerificationSessionId,
|
|
645
|
+
msg.to,
|
|
646
|
+
);
|
|
537
647
|
return;
|
|
538
648
|
}
|
|
539
649
|
|
|
540
650
|
// Secondary signal: custom parameter without persisted mode (pre-migration sessions)
|
|
541
651
|
if (!persistedMode && customParamGvSessionId) {
|
|
542
652
|
log.warn(
|
|
543
|
-
{
|
|
544
|
-
|
|
653
|
+
{
|
|
654
|
+
callSessionId: this.callSessionId,
|
|
655
|
+
guardianVerificationSessionId: customParamGvSessionId,
|
|
656
|
+
},
|
|
657
|
+
"Guardian verification detected via setup custom parameter (no persisted call_mode) — entering verification path",
|
|
658
|
+
);
|
|
659
|
+
this.startOutboundGuardianVerification(
|
|
660
|
+
assistantId,
|
|
661
|
+
customParamGvSessionId,
|
|
662
|
+
msg.to,
|
|
545
663
|
);
|
|
546
|
-
this.startOutboundGuardianVerification(assistantId, customParamGvSessionId, msg.to);
|
|
547
664
|
return;
|
|
548
665
|
}
|
|
549
666
|
|
|
@@ -562,7 +679,7 @@ export class RelayConnection {
|
|
|
562
679
|
// verification flow.
|
|
563
680
|
const actorTrust = resolveActorTrust({
|
|
564
681
|
assistantId,
|
|
565
|
-
sourceChannel:
|
|
682
|
+
sourceChannel: "voice",
|
|
566
683
|
conversationExternalId: msg.from,
|
|
567
684
|
actorExternalId: msg.from || undefined,
|
|
568
685
|
});
|
|
@@ -570,9 +687,9 @@ export class RelayConnection {
|
|
|
570
687
|
// Check for a pending voice guardian challenge before the ACL deny
|
|
571
688
|
// gate. An unknown caller with a pending challenge is expected —
|
|
572
689
|
// they need to complete verification to establish a binding.
|
|
573
|
-
const pendingChallenge = getPendingChallenge(assistantId,
|
|
690
|
+
const pendingChallenge = getPendingChallenge(assistantId, "voice");
|
|
574
691
|
|
|
575
|
-
if (actorTrust.trustClass ===
|
|
692
|
+
if (actorTrust.trustClass === "unknown" && !pendingChallenge) {
|
|
576
693
|
// Before entering the name capture flow, check if there is an
|
|
577
694
|
// active voice invite bound to the caller's phone number. If so,
|
|
578
695
|
// enter the invite redemption subflow instead.
|
|
@@ -583,42 +700,54 @@ export class RelayConnection {
|
|
|
583
700
|
expectedExternalUserId: msg.from,
|
|
584
701
|
});
|
|
585
702
|
} catch (err) {
|
|
586
|
-
log.warn(
|
|
703
|
+
log.warn(
|
|
704
|
+
{ err, callSessionId: this.callSessionId },
|
|
705
|
+
"Failed to check voice invites for unknown caller",
|
|
706
|
+
);
|
|
587
707
|
}
|
|
588
708
|
|
|
589
709
|
// Exclude invites that are past their expiresAt even if the DB
|
|
590
710
|
// status hasn't been lazily flipped to 'expired' yet.
|
|
591
711
|
const now = Date.now();
|
|
592
|
-
const nonExpiredInvites = voiceInvites.filter(
|
|
712
|
+
const nonExpiredInvites = voiceInvites.filter(
|
|
713
|
+
(i) => !i.expiresAt || i.expiresAt > now,
|
|
714
|
+
);
|
|
593
715
|
|
|
594
716
|
// Blocked members get immediate denial — the guardian already made
|
|
595
717
|
// an explicit decision to block them. This must be checked before
|
|
596
718
|
// invite redemption so a blocked caller cannot bypass the block by
|
|
597
719
|
// redeeming an active invite.
|
|
598
|
-
if (actorTrust.memberRecord?.status ===
|
|
720
|
+
if (actorTrust.memberRecord?.channel.status === "blocked") {
|
|
599
721
|
log.info(
|
|
600
|
-
{
|
|
601
|
-
|
|
722
|
+
{
|
|
723
|
+
callSessionId: this.callSessionId,
|
|
724
|
+
from: msg.from,
|
|
725
|
+
trustClass: actorTrust.trustClass,
|
|
726
|
+
},
|
|
727
|
+
"Inbound voice ACL: blocked caller denied",
|
|
602
728
|
);
|
|
603
729
|
|
|
604
|
-
recordCallEvent(this.callSessionId,
|
|
730
|
+
recordCallEvent(this.callSessionId, "inbound_acl_denied", {
|
|
605
731
|
from: msg.from,
|
|
606
732
|
trustClass: actorTrust.trustClass,
|
|
607
733
|
denialReason: actorTrust.denialReason,
|
|
608
734
|
});
|
|
609
735
|
|
|
610
|
-
this.sendTextToken(
|
|
736
|
+
this.sendTextToken(
|
|
737
|
+
"This number is not authorized to use this assistant.",
|
|
738
|
+
true,
|
|
739
|
+
);
|
|
611
740
|
|
|
612
|
-
this.connectionState =
|
|
741
|
+
this.connectionState = "disconnecting";
|
|
613
742
|
|
|
614
743
|
updateCallSession(this.callSessionId, {
|
|
615
|
-
status:
|
|
744
|
+
status: "failed",
|
|
616
745
|
endedAt: Date.now(),
|
|
617
|
-
lastError:
|
|
746
|
+
lastError: "Inbound voice ACL: caller blocked",
|
|
618
747
|
});
|
|
619
748
|
|
|
620
749
|
setTimeout(() => {
|
|
621
|
-
this.endSession(
|
|
750
|
+
this.endSession("Inbound voice ACL denied — blocked");
|
|
622
751
|
}, getTtsPlaybackDelayMs());
|
|
623
752
|
return;
|
|
624
753
|
}
|
|
@@ -628,23 +757,36 @@ export class RelayConnection {
|
|
|
628
757
|
const matchedInvite = nonExpiredInvites[0];
|
|
629
758
|
log.info(
|
|
630
759
|
{ callSessionId: this.callSessionId, from: msg.from },
|
|
631
|
-
|
|
760
|
+
"Inbound voice ACL: unknown caller has active voice invite — entering redemption flow",
|
|
761
|
+
);
|
|
762
|
+
this.startInviteRedemption(
|
|
763
|
+
assistantId,
|
|
764
|
+
msg.from,
|
|
765
|
+
matchedInvite.friendName,
|
|
766
|
+
matchedInvite.guardianName,
|
|
632
767
|
);
|
|
633
|
-
this.startInviteRedemption(assistantId, msg.from, matchedInvite.friendName, matchedInvite.guardianName);
|
|
634
768
|
return;
|
|
635
769
|
}
|
|
636
770
|
|
|
637
771
|
// Unknown/revoked/pending callers enter the name capture + guardian
|
|
638
772
|
// approval wait flow instead of being hard-rejected.
|
|
639
773
|
log.info(
|
|
640
|
-
{
|
|
641
|
-
|
|
774
|
+
{
|
|
775
|
+
callSessionId: this.callSessionId,
|
|
776
|
+
from: msg.from,
|
|
777
|
+
trustClass: actorTrust.trustClass,
|
|
778
|
+
},
|
|
779
|
+
"Inbound voice ACL: unknown caller — entering name capture flow",
|
|
642
780
|
);
|
|
643
781
|
|
|
644
|
-
recordCallEvent(
|
|
645
|
-
|
|
646
|
-
|
|
647
|
-
|
|
782
|
+
recordCallEvent(
|
|
783
|
+
this.callSessionId,
|
|
784
|
+
"inbound_acl_name_capture_started",
|
|
785
|
+
{
|
|
786
|
+
from: msg.from,
|
|
787
|
+
trustClass: actorTrust.trustClass,
|
|
788
|
+
},
|
|
789
|
+
);
|
|
648
790
|
|
|
649
791
|
this.startNameCapture(assistantId, msg.from);
|
|
650
792
|
return;
|
|
@@ -653,31 +795,39 @@ export class RelayConnection {
|
|
|
653
795
|
// Members with policy: 'deny' have status: 'active' so resolveActorTrust
|
|
654
796
|
// classifies them as trusted_contact, but the guardian has explicitly
|
|
655
797
|
// denied their access. Block them the same way the text-channel path does.
|
|
656
|
-
if (actorTrust.memberRecord?.policy ===
|
|
798
|
+
if (actorTrust.memberRecord?.channel.policy === "deny") {
|
|
657
799
|
log.info(
|
|
658
|
-
{
|
|
659
|
-
|
|
800
|
+
{
|
|
801
|
+
callSessionId: this.callSessionId,
|
|
802
|
+
from: msg.from,
|
|
803
|
+
channelId: actorTrust.memberRecord.channel.id,
|
|
804
|
+
trustClass: actorTrust.trustClass,
|
|
805
|
+
},
|
|
806
|
+
"Inbound voice ACL: member policy deny",
|
|
660
807
|
);
|
|
661
808
|
|
|
662
|
-
recordCallEvent(this.callSessionId,
|
|
809
|
+
recordCallEvent(this.callSessionId, "inbound_acl_denied", {
|
|
663
810
|
from: msg.from,
|
|
664
811
|
trustClass: actorTrust.trustClass,
|
|
665
|
-
|
|
666
|
-
memberPolicy: actorTrust.memberRecord.policy,
|
|
812
|
+
channelId: actorTrust.memberRecord.channel.id,
|
|
813
|
+
memberPolicy: actorTrust.memberRecord.channel.policy,
|
|
667
814
|
});
|
|
668
815
|
|
|
669
|
-
this.sendTextToken(
|
|
816
|
+
this.sendTextToken(
|
|
817
|
+
"This number is not authorized to use this assistant.",
|
|
818
|
+
true,
|
|
819
|
+
);
|
|
670
820
|
|
|
671
|
-
this.connectionState =
|
|
821
|
+
this.connectionState = "disconnecting";
|
|
672
822
|
|
|
673
823
|
updateCallSession(this.callSessionId, {
|
|
674
|
-
status:
|
|
824
|
+
status: "failed",
|
|
675
825
|
endedAt: Date.now(),
|
|
676
|
-
lastError:
|
|
826
|
+
lastError: "Inbound voice ACL: member policy deny",
|
|
677
827
|
});
|
|
678
828
|
|
|
679
829
|
setTimeout(() => {
|
|
680
|
-
this.endSession(
|
|
830
|
+
this.endSession("Inbound voice ACL: member policy deny");
|
|
681
831
|
}, getTtsPlaybackDelayMs());
|
|
682
832
|
return;
|
|
683
833
|
}
|
|
@@ -685,31 +835,40 @@ export class RelayConnection {
|
|
|
685
835
|
// Members with policy: 'escalate' require guardian approval, but a live
|
|
686
836
|
// voice call cannot be paused for async approval. Fail-closed by denying
|
|
687
837
|
// the call with an appropriate message — mirrors the deny block above.
|
|
688
|
-
if (actorTrust.memberRecord?.policy ===
|
|
838
|
+
if (actorTrust.memberRecord?.channel.policy === "escalate") {
|
|
689
839
|
log.info(
|
|
690
|
-
{
|
|
691
|
-
|
|
840
|
+
{
|
|
841
|
+
callSessionId: this.callSessionId,
|
|
842
|
+
from: msg.from,
|
|
843
|
+
channelId: actorTrust.memberRecord.channel.id,
|
|
844
|
+
trustClass: actorTrust.trustClass,
|
|
845
|
+
},
|
|
846
|
+
"Inbound voice ACL: member policy escalate — cannot hold live call for guardian approval",
|
|
692
847
|
);
|
|
693
848
|
|
|
694
|
-
recordCallEvent(this.callSessionId,
|
|
849
|
+
recordCallEvent(this.callSessionId, "inbound_acl_denied", {
|
|
695
850
|
from: msg.from,
|
|
696
851
|
trustClass: actorTrust.trustClass,
|
|
697
|
-
|
|
698
|
-
memberPolicy: actorTrust.memberRecord.policy,
|
|
852
|
+
channelId: actorTrust.memberRecord.channel.id,
|
|
853
|
+
memberPolicy: actorTrust.memberRecord.channel.policy,
|
|
699
854
|
});
|
|
700
855
|
|
|
701
|
-
this.sendTextToken(
|
|
856
|
+
this.sendTextToken(
|
|
857
|
+
"This number requires guardian approval for calls. Please have the account guardian update your permissions.",
|
|
858
|
+
true,
|
|
859
|
+
);
|
|
702
860
|
|
|
703
|
-
this.connectionState =
|
|
861
|
+
this.connectionState = "disconnecting";
|
|
704
862
|
|
|
705
863
|
updateCallSession(this.callSessionId, {
|
|
706
|
-
status:
|
|
864
|
+
status: "failed",
|
|
707
865
|
endedAt: Date.now(),
|
|
708
|
-
lastError:
|
|
866
|
+
lastError:
|
|
867
|
+
"Inbound voice ACL: member policy escalate — voice calls cannot await guardian approval",
|
|
709
868
|
});
|
|
710
869
|
|
|
711
870
|
setTimeout(() => {
|
|
712
|
-
this.endSession(
|
|
871
|
+
this.endSession("Inbound voice ACL: member policy escalate");
|
|
713
872
|
}, getTtsPlaybackDelayMs());
|
|
714
873
|
return;
|
|
715
874
|
}
|
|
@@ -717,9 +876,9 @@ export class RelayConnection {
|
|
|
717
876
|
// Guardian and trusted-contact callers proceed normally.
|
|
718
877
|
// Update the controller's guardian context with the trust-resolved
|
|
719
878
|
// context so downstream policy gates have accurate actor metadata.
|
|
720
|
-
if (this.controller && actorTrust.trustClass !==
|
|
721
|
-
const
|
|
722
|
-
this.controller.
|
|
879
|
+
if (this.controller && actorTrust.trustClass !== "unknown") {
|
|
880
|
+
const resolvedTrustContext = toTrustContext(actorTrust, msg.from);
|
|
881
|
+
this.controller.setTrustContext(resolvedTrustContext);
|
|
723
882
|
}
|
|
724
883
|
|
|
725
884
|
if (pendingChallenge) {
|
|
@@ -742,22 +901,27 @@ export class RelayConnection {
|
|
|
742
901
|
this.verificationMaxAttempts = verificationConfig.maxAttempts;
|
|
743
902
|
this.verificationCodeLength = verificationConfig.codeLength;
|
|
744
903
|
this.verificationAttempts = 0;
|
|
745
|
-
this.dtmfBuffer =
|
|
904
|
+
this.dtmfBuffer = "";
|
|
746
905
|
|
|
747
906
|
// Generate a random numeric code
|
|
748
907
|
const maxValue = Math.pow(10, this.verificationCodeLength);
|
|
749
|
-
const code = randomInt(0, maxValue)
|
|
908
|
+
const code = randomInt(0, maxValue)
|
|
909
|
+
.toString()
|
|
910
|
+
.padStart(this.verificationCodeLength, "0");
|
|
750
911
|
this.verificationCode = code;
|
|
751
|
-
this.connectionState =
|
|
912
|
+
this.connectionState = "verification_pending";
|
|
752
913
|
|
|
753
|
-
recordCallEvent(this.callSessionId,
|
|
914
|
+
recordCallEvent(this.callSessionId, "callee_verification_started", {
|
|
754
915
|
codeLength: this.verificationCodeLength,
|
|
755
916
|
maxAttempts: this.verificationMaxAttempts,
|
|
756
917
|
});
|
|
757
918
|
|
|
758
919
|
// Send a TTS prompt with the code spoken digit by digit
|
|
759
|
-
const spokenCode = code.split(
|
|
760
|
-
this.sendTextToken(
|
|
920
|
+
const spokenCode = code.split("").join(". ");
|
|
921
|
+
this.sendTextToken(
|
|
922
|
+
`Please enter the verification code: ${spokenCode}.`,
|
|
923
|
+
true,
|
|
924
|
+
);
|
|
761
925
|
|
|
762
926
|
// Post the verification code to the initiating conversation so the
|
|
763
927
|
// guardian (user) can share it with the callee.
|
|
@@ -765,15 +929,23 @@ export class RelayConnection {
|
|
|
765
929
|
const codeMsg = `\u{1F510} Verification code for call to ${session.toNumber}: ${code}`;
|
|
766
930
|
await conversationStore.addMessage(
|
|
767
931
|
session.initiatedFromConversationId,
|
|
768
|
-
|
|
769
|
-
JSON.stringify([{ type:
|
|
770
|
-
{
|
|
932
|
+
"assistant",
|
|
933
|
+
JSON.stringify([{ type: "text", text: codeMsg }]),
|
|
934
|
+
{
|
|
935
|
+
userMessageChannel: "voice",
|
|
936
|
+
assistantMessageChannel: "voice",
|
|
937
|
+
userMessageInterface: "voice",
|
|
938
|
+
assistantMessageInterface: "voice",
|
|
939
|
+
},
|
|
771
940
|
);
|
|
772
941
|
}
|
|
773
942
|
|
|
774
943
|
log.info(
|
|
775
|
-
{
|
|
776
|
-
|
|
944
|
+
{
|
|
945
|
+
callSessionId: this.callSessionId,
|
|
946
|
+
codeLength: this.verificationCodeLength,
|
|
947
|
+
},
|
|
948
|
+
"Callee verification started",
|
|
777
949
|
);
|
|
778
950
|
}
|
|
779
951
|
|
|
@@ -781,13 +953,91 @@ export class RelayConnection {
|
|
|
781
953
|
* Start normal call flow — fire the controller greeting unless a
|
|
782
954
|
* static welcome greeting is configured.
|
|
783
955
|
*/
|
|
784
|
-
private startNormalCallFlow(
|
|
956
|
+
private startNormalCallFlow(
|
|
957
|
+
controller: CallController,
|
|
958
|
+
isInbound: boolean,
|
|
959
|
+
): void {
|
|
785
960
|
const hasStaticGreeting = !!process.env.CALL_WELCOME_GREETING?.trim();
|
|
786
961
|
if (!hasStaticGreeting) {
|
|
787
|
-
controller
|
|
788
|
-
|
|
962
|
+
controller
|
|
963
|
+
.startInitialGreeting()
|
|
964
|
+
.catch((err) =>
|
|
965
|
+
log.error(
|
|
966
|
+
{ err, callSessionId: this.callSessionId },
|
|
967
|
+
`Failed to start initial ${isInbound ? "inbound" : "outbound"} greeting`,
|
|
968
|
+
),
|
|
969
|
+
);
|
|
970
|
+
}
|
|
971
|
+
}
|
|
972
|
+
|
|
973
|
+
/**
|
|
974
|
+
* Shared post-activation handoff for all trusted-contact success paths
|
|
975
|
+
* (access-request approval, invite redemption, verification code).
|
|
976
|
+
* Activates the caller, updates guardian context, delivers deterministic
|
|
977
|
+
* transition copy, and marks the next utterance as opening-ack so the
|
|
978
|
+
* LLM continues naturally.
|
|
979
|
+
*/
|
|
980
|
+
private continueCallAfterTrustedContactActivation(params: {
|
|
981
|
+
assistantId: string;
|
|
982
|
+
fromNumber: string;
|
|
983
|
+
callerName?: string;
|
|
984
|
+
skipMemberActivation?: boolean;
|
|
985
|
+
}): void {
|
|
986
|
+
const { assistantId, fromNumber, callerName } = params;
|
|
987
|
+
|
|
988
|
+
if (!params.skipMemberActivation) {
|
|
989
|
+
try {
|
|
990
|
+
upsertMember({
|
|
991
|
+
assistantId,
|
|
992
|
+
sourceChannel: "voice",
|
|
993
|
+
externalUserId: fromNumber,
|
|
994
|
+
externalChatId: fromNumber,
|
|
995
|
+
displayName: callerName,
|
|
996
|
+
status: "active",
|
|
997
|
+
policy: "allow",
|
|
998
|
+
});
|
|
999
|
+
} catch (err) {
|
|
1000
|
+
log.error(
|
|
1001
|
+
{ err, callSessionId: this.callSessionId },
|
|
1002
|
+
"Failed to activate voice caller as trusted contact",
|
|
1003
|
+
);
|
|
1004
|
+
}
|
|
1005
|
+
}
|
|
1006
|
+
|
|
1007
|
+
const updatedTrust = resolveActorTrust({
|
|
1008
|
+
assistantId,
|
|
1009
|
+
sourceChannel: "voice",
|
|
1010
|
+
conversationExternalId: fromNumber,
|
|
1011
|
+
actorExternalId: fromNumber,
|
|
1012
|
+
});
|
|
1013
|
+
|
|
1014
|
+
if (this.controller) {
|
|
1015
|
+
this.controller.setTrustContext(toTrustContext(updatedTrust, fromNumber));
|
|
1016
|
+
}
|
|
1017
|
+
|
|
1018
|
+
this.connectionState = "connected";
|
|
1019
|
+
updateCallSession(this.callSessionId, { status: "in_progress" });
|
|
1020
|
+
|
|
1021
|
+
const guardianLabel = this.resolveGuardianLabel();
|
|
1022
|
+
const handoffText = `Great! ${guardianLabel} said I can speak with you. How can I help?`;
|
|
1023
|
+
this.sendTextToken(handoffText, true);
|
|
1024
|
+
|
|
1025
|
+
recordCallEvent(this.callSessionId, "assistant_spoke", {
|
|
1026
|
+
text: handoffText,
|
|
1027
|
+
});
|
|
1028
|
+
const session = getCallSession(this.callSessionId);
|
|
1029
|
+
if (session) {
|
|
1030
|
+
fireCallTranscriptNotifier(
|
|
1031
|
+
session.conversationId,
|
|
1032
|
+
this.callSessionId,
|
|
1033
|
+
"assistant",
|
|
1034
|
+
handoffText,
|
|
789
1035
|
);
|
|
790
1036
|
}
|
|
1037
|
+
|
|
1038
|
+
if (this.controller) {
|
|
1039
|
+
this.controller.markNextCallerTurnAsOpeningAck();
|
|
1040
|
+
}
|
|
791
1041
|
}
|
|
792
1042
|
|
|
793
1043
|
/**
|
|
@@ -795,29 +1045,32 @@ export class RelayConnection {
|
|
|
795
1045
|
* voice guardian challenge. Prompts the caller to enter their six-digit
|
|
796
1046
|
* verification code via DTMF or by speaking it.
|
|
797
1047
|
*/
|
|
798
|
-
private startInboundGuardianVerification(
|
|
1048
|
+
private startInboundGuardianVerification(
|
|
1049
|
+
assistantId: string,
|
|
1050
|
+
fromNumber: string,
|
|
1051
|
+
): void {
|
|
799
1052
|
this.guardianVerificationActive = true;
|
|
800
1053
|
this.guardianChallengeAssistantId = assistantId;
|
|
801
1054
|
this.guardianVerificationFromNumber = fromNumber;
|
|
802
|
-
this.connectionState =
|
|
1055
|
+
this.connectionState = "verification_pending";
|
|
803
1056
|
this.verificationAttempts = 0;
|
|
804
1057
|
this.verificationMaxAttempts = 3;
|
|
805
1058
|
this.verificationCodeLength = 6;
|
|
806
|
-
this.dtmfBuffer =
|
|
1059
|
+
this.dtmfBuffer = "";
|
|
807
1060
|
|
|
808
|
-
recordCallEvent(this.callSessionId,
|
|
1061
|
+
recordCallEvent(this.callSessionId, "guardian_voice_verification_started", {
|
|
809
1062
|
assistantId,
|
|
810
1063
|
maxAttempts: this.verificationMaxAttempts,
|
|
811
1064
|
});
|
|
812
1065
|
|
|
813
1066
|
this.sendTextToken(
|
|
814
|
-
|
|
1067
|
+
"Welcome. Please enter your six-digit verification code using your keypad, or speak the digits now.",
|
|
815
1068
|
true,
|
|
816
1069
|
);
|
|
817
1070
|
|
|
818
1071
|
log.info(
|
|
819
1072
|
{ callSessionId: this.callSessionId, assistantId },
|
|
820
|
-
|
|
1073
|
+
"Inbound guardian voice verification started",
|
|
821
1074
|
);
|
|
822
1075
|
}
|
|
823
1076
|
|
|
@@ -836,17 +1089,21 @@ export class RelayConnection {
|
|
|
836
1089
|
this.guardianChallengeAssistantId = assistantId;
|
|
837
1090
|
// For outbound guardian calls, the "to" number is the guardian's phone
|
|
838
1091
|
this.guardianVerificationFromNumber = toNumber;
|
|
839
|
-
this.connectionState =
|
|
1092
|
+
this.connectionState = "verification_pending";
|
|
840
1093
|
this.verificationAttempts = 0;
|
|
841
1094
|
this.verificationMaxAttempts = 3;
|
|
842
1095
|
this.verificationCodeLength = 6;
|
|
843
|
-
this.dtmfBuffer =
|
|
1096
|
+
this.dtmfBuffer = "";
|
|
844
1097
|
|
|
845
|
-
recordCallEvent(
|
|
846
|
-
|
|
847
|
-
|
|
848
|
-
|
|
849
|
-
|
|
1098
|
+
recordCallEvent(
|
|
1099
|
+
this.callSessionId,
|
|
1100
|
+
"outbound_guardian_voice_verification_started",
|
|
1101
|
+
{
|
|
1102
|
+
assistantId,
|
|
1103
|
+
guardianVerificationSessionId,
|
|
1104
|
+
maxAttempts: this.verificationMaxAttempts,
|
|
1105
|
+
},
|
|
1106
|
+
);
|
|
850
1107
|
|
|
851
1108
|
const introText = composeVerificationVoice(
|
|
852
1109
|
GUARDIAN_VERIFY_TEMPLATE_KEYS.VOICE_CALL_INTRO,
|
|
@@ -855,8 +1112,12 @@ export class RelayConnection {
|
|
|
855
1112
|
this.sendTextToken(introText, true);
|
|
856
1113
|
|
|
857
1114
|
log.info(
|
|
858
|
-
{
|
|
859
|
-
|
|
1115
|
+
{
|
|
1116
|
+
callSessionId: this.callSessionId,
|
|
1117
|
+
assistantId,
|
|
1118
|
+
guardianVerificationSessionId,
|
|
1119
|
+
},
|
|
1120
|
+
"Outbound guardian voice verification started",
|
|
860
1121
|
);
|
|
861
1122
|
}
|
|
862
1123
|
|
|
@@ -866,16 +1127,24 @@ export class RelayConnection {
|
|
|
866
1127
|
*/
|
|
867
1128
|
private static parseDigitsFromSpeech(transcript: string): string {
|
|
868
1129
|
const wordToDigit: Record<string, string> = {
|
|
869
|
-
zero:
|
|
870
|
-
|
|
871
|
-
|
|
872
|
-
|
|
873
|
-
|
|
874
|
-
|
|
875
|
-
|
|
876
|
-
|
|
877
|
-
|
|
878
|
-
|
|
1130
|
+
zero: "0",
|
|
1131
|
+
oh: "0",
|
|
1132
|
+
o: "0",
|
|
1133
|
+
one: "1",
|
|
1134
|
+
won: "1",
|
|
1135
|
+
two: "2",
|
|
1136
|
+
too: "2",
|
|
1137
|
+
to: "2",
|
|
1138
|
+
three: "3",
|
|
1139
|
+
four: "4",
|
|
1140
|
+
for: "4",
|
|
1141
|
+
fore: "4",
|
|
1142
|
+
five: "5",
|
|
1143
|
+
six: "6",
|
|
1144
|
+
seven: "7",
|
|
1145
|
+
eight: "8",
|
|
1146
|
+
ate: "8",
|
|
1147
|
+
nine: "9",
|
|
879
1148
|
};
|
|
880
1149
|
|
|
881
1150
|
const digits: string[] = [];
|
|
@@ -890,11 +1159,11 @@ export class RelayConnection {
|
|
|
890
1159
|
digits.push(wordToDigit[token]);
|
|
891
1160
|
} else if (/^\d+$/.test(token)) {
|
|
892
1161
|
// Multi-digit number like "123456" — split into individual digits
|
|
893
|
-
digits.push(...token.split(
|
|
1162
|
+
digits.push(...token.split(""));
|
|
894
1163
|
}
|
|
895
1164
|
}
|
|
896
1165
|
|
|
897
|
-
return digits.join(
|
|
1166
|
+
return digits.join("");
|
|
898
1167
|
}
|
|
899
1168
|
|
|
900
1169
|
/**
|
|
@@ -906,7 +1175,10 @@ export class RelayConnection {
|
|
|
906
1175
|
* On failure, enforces max attempts and terminates the call if exhausted.
|
|
907
1176
|
*/
|
|
908
1177
|
private attemptGuardianCodeVerification(enteredCode: string): void {
|
|
909
|
-
if (
|
|
1178
|
+
if (
|
|
1179
|
+
!this.guardianChallengeAssistantId ||
|
|
1180
|
+
!this.guardianVerificationFromNumber
|
|
1181
|
+
) {
|
|
910
1182
|
return;
|
|
911
1183
|
}
|
|
912
1184
|
|
|
@@ -915,37 +1187,77 @@ export class RelayConnection {
|
|
|
915
1187
|
|
|
916
1188
|
const result = validateAndConsumeChallenge(
|
|
917
1189
|
this.guardianChallengeAssistantId,
|
|
918
|
-
|
|
1190
|
+
"voice",
|
|
919
1191
|
enteredCode,
|
|
920
1192
|
this.guardianVerificationFromNumber,
|
|
921
1193
|
this.guardianVerificationFromNumber,
|
|
922
1194
|
);
|
|
923
1195
|
|
|
924
1196
|
if (result.success) {
|
|
925
|
-
|
|
926
|
-
this.connectionState = 'connected';
|
|
1197
|
+
this.connectionState = "connected";
|
|
927
1198
|
this.guardianVerificationActive = false;
|
|
928
1199
|
this.verificationAttempts = 0;
|
|
929
|
-
this.dtmfBuffer =
|
|
1200
|
+
this.dtmfBuffer = "";
|
|
930
1201
|
|
|
931
1202
|
const eventName = isOutbound
|
|
932
|
-
?
|
|
933
|
-
:
|
|
1203
|
+
? "outbound_guardian_voice_verification_succeeded"
|
|
1204
|
+
: "guardian_voice_verification_succeeded";
|
|
934
1205
|
|
|
935
1206
|
recordCallEvent(this.callSessionId, eventName, {
|
|
936
|
-
|
|
1207
|
+
verificationType: result.verificationType,
|
|
937
1208
|
});
|
|
938
1209
|
log.info(
|
|
939
1210
|
{ callSessionId: this.callSessionId, isOutbound },
|
|
940
|
-
|
|
1211
|
+
"Guardian voice verification succeeded",
|
|
941
1212
|
);
|
|
942
1213
|
|
|
1214
|
+
// Create the guardian binding now that verification succeeded.
|
|
1215
|
+
if (result.verificationType === "guardian") {
|
|
1216
|
+
const existingBinding = getGuardianBinding(
|
|
1217
|
+
this.guardianChallengeAssistantId,
|
|
1218
|
+
"voice",
|
|
1219
|
+
);
|
|
1220
|
+
if (
|
|
1221
|
+
existingBinding &&
|
|
1222
|
+
existingBinding.guardianExternalUserId !==
|
|
1223
|
+
this.guardianVerificationFromNumber
|
|
1224
|
+
) {
|
|
1225
|
+
log.warn(
|
|
1226
|
+
{
|
|
1227
|
+
callSessionId: this.callSessionId,
|
|
1228
|
+
existingGuardian: existingBinding.guardianExternalUserId,
|
|
1229
|
+
},
|
|
1230
|
+
"Guardian binding conflict: another user already holds the voice binding",
|
|
1231
|
+
);
|
|
1232
|
+
} else {
|
|
1233
|
+
revokeGuardianBinding(this.guardianChallengeAssistantId, "voice");
|
|
1234
|
+
|
|
1235
|
+
// Unify all channel bindings onto the canonical (vellum) principal
|
|
1236
|
+
const vellumBinding = getGuardianBinding(
|
|
1237
|
+
this.guardianChallengeAssistantId,
|
|
1238
|
+
"vellum",
|
|
1239
|
+
);
|
|
1240
|
+
const canonicalPrincipal =
|
|
1241
|
+
vellumBinding?.guardianPrincipalId ??
|
|
1242
|
+
this.guardianVerificationFromNumber;
|
|
1243
|
+
|
|
1244
|
+
createGuardianBinding({
|
|
1245
|
+
assistantId: this.guardianChallengeAssistantId,
|
|
1246
|
+
channel: "voice",
|
|
1247
|
+
guardianExternalUserId: this.guardianVerificationFromNumber,
|
|
1248
|
+
guardianDeliveryChatId: this.guardianVerificationFromNumber,
|
|
1249
|
+
guardianPrincipalId: canonicalPrincipal,
|
|
1250
|
+
verifiedVia: "challenge",
|
|
1251
|
+
});
|
|
1252
|
+
}
|
|
1253
|
+
}
|
|
1254
|
+
|
|
943
1255
|
if (isOutbound) {
|
|
944
1256
|
// Outbound guardian verification: play success and hang up.
|
|
945
1257
|
// There is no normal conversation to transition to.
|
|
946
1258
|
// Set disconnecting to ignore any further DTMF/speech input
|
|
947
1259
|
// during the brief delay before the session ends.
|
|
948
|
-
this.connectionState =
|
|
1260
|
+
this.connectionState = "disconnecting";
|
|
949
1261
|
|
|
950
1262
|
const successText = composeVerificationVoice(
|
|
951
1263
|
GUARDIAN_VERIFY_TEMPLATE_KEYS.VOICE_SUCCESS,
|
|
@@ -954,7 +1266,7 @@ export class RelayConnection {
|
|
|
954
1266
|
this.sendTextToken(successText, true);
|
|
955
1267
|
|
|
956
1268
|
updateCallSession(this.callSessionId, {
|
|
957
|
-
status:
|
|
1269
|
+
status: "completed",
|
|
958
1270
|
endedAt: Date.now(),
|
|
959
1271
|
});
|
|
960
1272
|
|
|
@@ -964,28 +1276,83 @@ export class RelayConnection {
|
|
|
964
1276
|
if (successSession?.initiatedFromConversationId) {
|
|
965
1277
|
addPointerMessage(
|
|
966
1278
|
successSession.initiatedFromConversationId,
|
|
967
|
-
|
|
1279
|
+
"guardian_verification_succeeded",
|
|
968
1280
|
successSession.toNumber,
|
|
969
|
-
{ channel:
|
|
1281
|
+
{ channel: "voice" },
|
|
970
1282
|
).catch((err) => {
|
|
971
|
-
log.warn(
|
|
1283
|
+
log.warn(
|
|
1284
|
+
{
|
|
1285
|
+
conversationId: successSession.initiatedFromConversationId,
|
|
1286
|
+
err,
|
|
1287
|
+
},
|
|
1288
|
+
"Skipping pointer write — origin conversation may no longer exist",
|
|
1289
|
+
);
|
|
972
1290
|
});
|
|
973
1291
|
}
|
|
974
1292
|
|
|
975
1293
|
setTimeout(() => {
|
|
976
|
-
this.endSession(
|
|
1294
|
+
this.endSession("Verified — guardian challenge passed");
|
|
977
1295
|
}, getTtsPlaybackDelayMs());
|
|
1296
|
+
} else if (result.verificationType === "trusted_contact") {
|
|
1297
|
+
// Inbound trusted-contact verification: activate and continue
|
|
1298
|
+
// the live call with the shared handoff primitive.
|
|
1299
|
+
this.continueCallAfterTrustedContactActivation({
|
|
1300
|
+
assistantId: this.guardianChallengeAssistantId,
|
|
1301
|
+
fromNumber: this.guardianVerificationFromNumber,
|
|
1302
|
+
});
|
|
978
1303
|
} else {
|
|
979
|
-
// Inbound:
|
|
1304
|
+
// Inbound guardian verification: create/update binding, then proceed
|
|
1305
|
+
// to normal call flow. Mirrors the binding creation logic in
|
|
1306
|
+
// verification-intercept.ts for the inbound channel path.
|
|
1307
|
+
const guardianAssistantId = this.guardianChallengeAssistantId;
|
|
1308
|
+
const callerNumber = this.guardianVerificationFromNumber;
|
|
1309
|
+
|
|
1310
|
+
const existingBinding = getGuardianBinding(
|
|
1311
|
+
guardianAssistantId,
|
|
1312
|
+
"voice",
|
|
1313
|
+
);
|
|
1314
|
+
if (
|
|
1315
|
+
existingBinding &&
|
|
1316
|
+
existingBinding.guardianExternalUserId !== callerNumber
|
|
1317
|
+
) {
|
|
1318
|
+
log.warn(
|
|
1319
|
+
{
|
|
1320
|
+
sourceChannel: "voice",
|
|
1321
|
+
existingGuardian: existingBinding.guardianExternalUserId,
|
|
1322
|
+
},
|
|
1323
|
+
"Guardian binding conflict: another user already holds the voice channel binding",
|
|
1324
|
+
);
|
|
1325
|
+
} else {
|
|
1326
|
+
revokeGuardianBinding(guardianAssistantId, "voice");
|
|
1327
|
+
|
|
1328
|
+
// Resolve canonical principal from the vellum channel binding
|
|
1329
|
+
// so all channel bindings share a single principal identity.
|
|
1330
|
+
const vellumBinding = getGuardianBinding(
|
|
1331
|
+
guardianAssistantId,
|
|
1332
|
+
"vellum",
|
|
1333
|
+
);
|
|
1334
|
+
const canonicalPrincipal =
|
|
1335
|
+
vellumBinding?.guardianPrincipalId ?? callerNumber;
|
|
1336
|
+
|
|
1337
|
+
createGuardianBinding({
|
|
1338
|
+
assistantId: guardianAssistantId,
|
|
1339
|
+
channel: "voice",
|
|
1340
|
+
guardianExternalUserId: callerNumber,
|
|
1341
|
+
guardianDeliveryChatId: callerNumber,
|
|
1342
|
+
guardianPrincipalId: canonicalPrincipal,
|
|
1343
|
+
verifiedVia: "challenge",
|
|
1344
|
+
});
|
|
1345
|
+
}
|
|
1346
|
+
|
|
980
1347
|
if (this.controller) {
|
|
981
1348
|
const verifiedActorTrust = resolveActorTrust({
|
|
982
|
-
assistantId:
|
|
983
|
-
sourceChannel:
|
|
984
|
-
conversationExternalId:
|
|
985
|
-
actorExternalId:
|
|
1349
|
+
assistantId: guardianAssistantId,
|
|
1350
|
+
sourceChannel: "voice",
|
|
1351
|
+
conversationExternalId: callerNumber,
|
|
1352
|
+
actorExternalId: callerNumber,
|
|
986
1353
|
});
|
|
987
|
-
this.controller.
|
|
988
|
-
|
|
1354
|
+
this.controller.setTrustContext(
|
|
1355
|
+
toTrustContext(verifiedActorTrust, callerNumber),
|
|
989
1356
|
);
|
|
990
1357
|
this.startNormalCallFlow(this.controller, true);
|
|
991
1358
|
}
|
|
@@ -999,61 +1366,99 @@ export class RelayConnection {
|
|
|
999
1366
|
this.guardianVerificationActive = false;
|
|
1000
1367
|
|
|
1001
1368
|
const failEventName = isOutbound
|
|
1002
|
-
?
|
|
1003
|
-
:
|
|
1369
|
+
? "outbound_guardian_voice_verification_failed"
|
|
1370
|
+
: "guardian_voice_verification_failed";
|
|
1004
1371
|
|
|
1005
1372
|
recordCallEvent(this.callSessionId, failEventName, {
|
|
1006
1373
|
attempts: this.verificationAttempts,
|
|
1007
1374
|
});
|
|
1008
1375
|
log.warn(
|
|
1009
|
-
{
|
|
1010
|
-
|
|
1376
|
+
{
|
|
1377
|
+
callSessionId: this.callSessionId,
|
|
1378
|
+
attempts: this.verificationAttempts,
|
|
1379
|
+
isOutbound,
|
|
1380
|
+
},
|
|
1381
|
+
"Guardian voice verification failed — max attempts reached",
|
|
1011
1382
|
);
|
|
1012
1383
|
|
|
1013
1384
|
const failureText = isOutbound
|
|
1014
|
-
? composeVerificationVoice(
|
|
1015
|
-
|
|
1385
|
+
? composeVerificationVoice(
|
|
1386
|
+
GUARDIAN_VERIFY_TEMPLATE_KEYS.VOICE_FAILURE,
|
|
1387
|
+
{ codeDigits },
|
|
1388
|
+
)
|
|
1389
|
+
: "Verification failed. Goodbye.";
|
|
1016
1390
|
this.sendTextToken(failureText, true);
|
|
1017
1391
|
|
|
1018
1392
|
updateCallSession(this.callSessionId, {
|
|
1019
|
-
status:
|
|
1393
|
+
status: "failed",
|
|
1020
1394
|
endedAt: Date.now(),
|
|
1021
|
-
lastError:
|
|
1395
|
+
lastError:
|
|
1396
|
+
"Guardian voice verification failed — max attempts exceeded",
|
|
1022
1397
|
});
|
|
1023
1398
|
|
|
1024
1399
|
const failSession = getCallSession(this.callSessionId);
|
|
1025
1400
|
if (failSession) {
|
|
1026
1401
|
expirePendingQuestions(this.callSessionId);
|
|
1027
|
-
persistCallCompletionMessage(
|
|
1028
|
-
|
|
1402
|
+
persistCallCompletionMessage(
|
|
1403
|
+
failSession.conversationId,
|
|
1404
|
+
this.callSessionId,
|
|
1405
|
+
).catch((err) => {
|
|
1406
|
+
log.error(
|
|
1407
|
+
{
|
|
1408
|
+
err,
|
|
1409
|
+
conversationId: failSession.conversationId,
|
|
1410
|
+
callSessionId: this.callSessionId,
|
|
1411
|
+
},
|
|
1412
|
+
"Failed to persist call completion message",
|
|
1413
|
+
);
|
|
1029
1414
|
});
|
|
1030
|
-
fireCallCompletionNotifier(
|
|
1415
|
+
fireCallCompletionNotifier(
|
|
1416
|
+
failSession.conversationId,
|
|
1417
|
+
this.callSessionId,
|
|
1418
|
+
);
|
|
1031
1419
|
|
|
1032
1420
|
// Emit a pointer message to the origin conversation so the
|
|
1033
1421
|
// requesting chat sees a deterministic failure notice.
|
|
1034
1422
|
if (isOutbound && failSession.initiatedFromConversationId) {
|
|
1035
1423
|
addPointerMessage(
|
|
1036
1424
|
failSession.initiatedFromConversationId,
|
|
1037
|
-
|
|
1425
|
+
"guardian_verification_failed",
|
|
1038
1426
|
failSession.toNumber,
|
|
1039
|
-
{
|
|
1427
|
+
{
|
|
1428
|
+
channel: "voice",
|
|
1429
|
+
reason: "Max verification attempts exceeded",
|
|
1430
|
+
},
|
|
1040
1431
|
).catch((err) => {
|
|
1041
|
-
log.warn(
|
|
1432
|
+
log.warn(
|
|
1433
|
+
{
|
|
1434
|
+
conversationId: failSession.initiatedFromConversationId,
|
|
1435
|
+
err,
|
|
1436
|
+
},
|
|
1437
|
+
"Skipping pointer write — origin conversation may no longer exist",
|
|
1438
|
+
);
|
|
1042
1439
|
});
|
|
1043
1440
|
}
|
|
1044
1441
|
}
|
|
1045
1442
|
|
|
1046
1443
|
setTimeout(() => {
|
|
1047
|
-
this.endSession(
|
|
1444
|
+
this.endSession("Verification failed — challenge rejected");
|
|
1048
1445
|
}, getTtsPlaybackDelayMs());
|
|
1049
1446
|
} else {
|
|
1050
1447
|
const retryText = isOutbound
|
|
1051
|
-
? composeVerificationVoice(
|
|
1052
|
-
|
|
1448
|
+
? composeVerificationVoice(
|
|
1449
|
+
GUARDIAN_VERIFY_TEMPLATE_KEYS.VOICE_RETRY,
|
|
1450
|
+
{ codeDigits },
|
|
1451
|
+
)
|
|
1452
|
+
: "That code was incorrect. Please try again.";
|
|
1053
1453
|
|
|
1054
1454
|
log.info(
|
|
1055
|
-
{
|
|
1056
|
-
|
|
1455
|
+
{
|
|
1456
|
+
callSessionId: this.callSessionId,
|
|
1457
|
+
attempt: this.verificationAttempts,
|
|
1458
|
+
maxAttempts: this.verificationMaxAttempts,
|
|
1459
|
+
isOutbound,
|
|
1460
|
+
},
|
|
1461
|
+
"Guardian voice verification attempt failed — retrying",
|
|
1057
1462
|
);
|
|
1058
1463
|
this.sendTextToken(retryText, true);
|
|
1059
1464
|
}
|
|
@@ -1065,26 +1470,31 @@ export class RelayConnection {
|
|
|
1065
1470
|
* who has an active voice invite. Prompts the caller to enter their
|
|
1066
1471
|
* invite code via DTMF or speech.
|
|
1067
1472
|
*/
|
|
1068
|
-
private startInviteRedemption(
|
|
1473
|
+
private startInviteRedemption(
|
|
1474
|
+
assistantId: string,
|
|
1475
|
+
fromNumber: string,
|
|
1476
|
+
friendName: string | null,
|
|
1477
|
+
guardianName: string | null,
|
|
1478
|
+
): void {
|
|
1069
1479
|
this.inviteRedemptionActive = true;
|
|
1070
1480
|
this.inviteRedemptionAssistantId = assistantId;
|
|
1071
1481
|
this.inviteRedemptionFromNumber = fromNumber;
|
|
1072
1482
|
this.inviteRedemptionFriendName = friendName;
|
|
1073
1483
|
this.inviteRedemptionGuardianName = guardianName;
|
|
1074
|
-
this.connectionState =
|
|
1484
|
+
this.connectionState = "verification_pending";
|
|
1075
1485
|
this.verificationAttempts = 0;
|
|
1076
1486
|
this.verificationMaxAttempts = 1;
|
|
1077
1487
|
this.inviteRedemptionCodeLength = 6;
|
|
1078
|
-
this.dtmfBuffer =
|
|
1488
|
+
this.dtmfBuffer = "";
|
|
1079
1489
|
|
|
1080
|
-
recordCallEvent(this.callSessionId,
|
|
1490
|
+
recordCallEvent(this.callSessionId, "invite_redemption_started", {
|
|
1081
1491
|
assistantId,
|
|
1082
1492
|
codeLength: 6,
|
|
1083
1493
|
maxAttempts: this.verificationMaxAttempts,
|
|
1084
1494
|
});
|
|
1085
1495
|
|
|
1086
|
-
const displayFriend = friendName ??
|
|
1087
|
-
const displayGuardian = guardianName ??
|
|
1496
|
+
const displayFriend = friendName ?? "there";
|
|
1497
|
+
const displayGuardian = guardianName ?? "your contact";
|
|
1088
1498
|
this.sendTextToken(
|
|
1089
1499
|
`Welcome ${displayFriend}. Please enter the 6-digit code that ${displayGuardian} provided you to verify your identity.`,
|
|
1090
1500
|
true,
|
|
@@ -1092,7 +1502,7 @@ export class RelayConnection {
|
|
|
1092
1502
|
|
|
1093
1503
|
log.info(
|
|
1094
1504
|
{ callSessionId: this.callSessionId, assistantId },
|
|
1095
|
-
|
|
1505
|
+
"Inbound voice invite redemption started",
|
|
1096
1506
|
);
|
|
1097
1507
|
}
|
|
1098
1508
|
|
|
@@ -1104,7 +1514,7 @@ export class RelayConnection {
|
|
|
1104
1514
|
private startNameCapture(assistantId: string, fromNumber: string): void {
|
|
1105
1515
|
this.accessRequestAssistantId = assistantId;
|
|
1106
1516
|
this.accessRequestFromNumber = fromNumber;
|
|
1107
|
-
this.connectionState =
|
|
1517
|
+
this.connectionState = "awaiting_name";
|
|
1108
1518
|
|
|
1109
1519
|
const guardianLabel = this.resolveGuardianLabel();
|
|
1110
1520
|
const assistantName = this.resolveAssistantLabel();
|
|
@@ -1120,13 +1530,17 @@ export class RelayConnection {
|
|
|
1120
1530
|
// to avoid wasting resources on callers who never respond.
|
|
1121
1531
|
const NAME_CAPTURE_TIMEOUT_MS = 30_000;
|
|
1122
1532
|
this.nameCaptureTimeoutTimer = setTimeout(() => {
|
|
1123
|
-
if (this.connectionState !==
|
|
1533
|
+
if (this.connectionState !== "awaiting_name") return;
|
|
1124
1534
|
this.handleNameCaptureTimeout();
|
|
1125
1535
|
}, NAME_CAPTURE_TIMEOUT_MS);
|
|
1126
1536
|
|
|
1127
1537
|
log.info(
|
|
1128
|
-
{
|
|
1129
|
-
|
|
1538
|
+
{
|
|
1539
|
+
callSessionId: this.callSessionId,
|
|
1540
|
+
assistantId,
|
|
1541
|
+
timeoutMs: NAME_CAPTURE_TIMEOUT_MS,
|
|
1542
|
+
},
|
|
1543
|
+
"Name capture started for unknown inbound caller",
|
|
1130
1544
|
);
|
|
1131
1545
|
}
|
|
1132
1546
|
|
|
@@ -1148,7 +1562,7 @@ export class RelayConnection {
|
|
|
1148
1562
|
|
|
1149
1563
|
this.accessRequestCallerName = callerName;
|
|
1150
1564
|
|
|
1151
|
-
recordCallEvent(this.callSessionId,
|
|
1565
|
+
recordCallEvent(this.callSessionId, "inbound_acl_name_captured", {
|
|
1152
1566
|
from: this.accessRequestFromNumber,
|
|
1153
1567
|
callerName,
|
|
1154
1568
|
});
|
|
@@ -1158,7 +1572,7 @@ export class RelayConnection {
|
|
|
1158
1572
|
try {
|
|
1159
1573
|
const accessResult = notifyGuardianOfAccessRequest({
|
|
1160
1574
|
canonicalAssistantId: this.accessRequestAssistantId,
|
|
1161
|
-
sourceChannel:
|
|
1575
|
+
sourceChannel: "voice",
|
|
1162
1576
|
conversationExternalId: this.accessRequestFromNumber,
|
|
1163
1577
|
actorExternalId: this.accessRequestFromNumber,
|
|
1164
1578
|
actorDisplayName: callerName,
|
|
@@ -1167,17 +1581,24 @@ export class RelayConnection {
|
|
|
1167
1581
|
if (accessResult.notified) {
|
|
1168
1582
|
this.accessRequestId = accessResult.requestId;
|
|
1169
1583
|
log.info(
|
|
1170
|
-
{
|
|
1171
|
-
|
|
1584
|
+
{
|
|
1585
|
+
callSessionId: this.callSessionId,
|
|
1586
|
+
requestId: accessResult.requestId,
|
|
1587
|
+
callerName,
|
|
1588
|
+
},
|
|
1589
|
+
"Guardian notified of voice access request with caller name",
|
|
1172
1590
|
);
|
|
1173
1591
|
} else {
|
|
1174
1592
|
log.warn(
|
|
1175
1593
|
{ callSessionId: this.callSessionId },
|
|
1176
|
-
|
|
1594
|
+
"Failed to notify guardian of voice access request — no sender ID",
|
|
1177
1595
|
);
|
|
1178
1596
|
}
|
|
1179
1597
|
} catch (err) {
|
|
1180
|
-
log.error(
|
|
1598
|
+
log.error(
|
|
1599
|
+
{ err, callSessionId: this.callSessionId },
|
|
1600
|
+
"Failed to create access request for voice caller",
|
|
1601
|
+
);
|
|
1181
1602
|
}
|
|
1182
1603
|
|
|
1183
1604
|
// If the access request was not successfully created (notifyGuardianOfAccessRequest
|
|
@@ -1186,7 +1607,7 @@ export class RelayConnection {
|
|
|
1186
1607
|
if (!this.accessRequestId) {
|
|
1187
1608
|
log.warn(
|
|
1188
1609
|
{ callSessionId: this.callSessionId },
|
|
1189
|
-
|
|
1610
|
+
"Access request ID is null after notification attempt — failing closed",
|
|
1190
1611
|
);
|
|
1191
1612
|
this.handleAccessRequestTimeout();
|
|
1192
1613
|
return;
|
|
@@ -1202,7 +1623,7 @@ export class RelayConnection {
|
|
|
1202
1623
|
*/
|
|
1203
1624
|
private startAccessRequestWait(): void {
|
|
1204
1625
|
this.accessRequestWaitActive = true;
|
|
1205
|
-
this.connectionState =
|
|
1626
|
+
this.connectionState = "awaiting_guardian_decision";
|
|
1206
1627
|
|
|
1207
1628
|
const timeoutMs = getUserConsultationTimeoutMs();
|
|
1208
1629
|
const pollIntervalMs = getAccessRequestPollIntervalMs();
|
|
@@ -1213,12 +1634,17 @@ export class RelayConnection {
|
|
|
1213
1634
|
true,
|
|
1214
1635
|
);
|
|
1215
1636
|
|
|
1216
|
-
updateCallSession(this.callSessionId, { status:
|
|
1637
|
+
updateCallSession(this.callSessionId, { status: "waiting_on_user" });
|
|
1217
1638
|
|
|
1218
1639
|
// Start the heartbeat timer for periodic progress updates.
|
|
1219
1640
|
// Delay the first heartbeat by the estimated TTS playback duration so
|
|
1220
1641
|
// the initial hold message finishes before any heartbeat fires.
|
|
1221
1642
|
this.heartbeatSequence = 0;
|
|
1643
|
+
// Set the wait start time now so scheduleNextHeartbeat() always has a
|
|
1644
|
+
// valid reference point — even if the TTS delay timer is cancelled early
|
|
1645
|
+
// (e.g. by handleWaitStatePrompt when the caller speaks during playback).
|
|
1646
|
+
// The callback below re-stamps it to exclude the TTS delay if it fires.
|
|
1647
|
+
this.accessRequestWaitStartedAt = Date.now();
|
|
1222
1648
|
this.accessRequestHeartbeatTimer = setTimeout(() => {
|
|
1223
1649
|
this.accessRequestWaitStartedAt = Date.now();
|
|
1224
1650
|
this.scheduleNextHeartbeat();
|
|
@@ -1236,9 +1662,9 @@ export class RelayConnection {
|
|
|
1236
1662
|
return;
|
|
1237
1663
|
}
|
|
1238
1664
|
|
|
1239
|
-
if (request.status ===
|
|
1665
|
+
if (request.status === "approved") {
|
|
1240
1666
|
this.handleAccessRequestApproved();
|
|
1241
|
-
} else if (request.status ===
|
|
1667
|
+
} else if (request.status === "denied") {
|
|
1242
1668
|
this.handleAccessRequestDenied();
|
|
1243
1669
|
}
|
|
1244
1670
|
// 'pending' continues polling; 'expired'/'cancelled' handled by timeout
|
|
@@ -1250,15 +1676,19 @@ export class RelayConnection {
|
|
|
1250
1676
|
|
|
1251
1677
|
log.info(
|
|
1252
1678
|
{ callSessionId: this.callSessionId, requestId: this.accessRequestId },
|
|
1253
|
-
|
|
1679
|
+
"Access request in-call wait timed out",
|
|
1254
1680
|
);
|
|
1255
1681
|
|
|
1256
1682
|
this.handleAccessRequestTimeout();
|
|
1257
1683
|
}, timeoutMs);
|
|
1258
1684
|
|
|
1259
1685
|
log.info(
|
|
1260
|
-
{
|
|
1261
|
-
|
|
1686
|
+
{
|
|
1687
|
+
callSessionId: this.callSessionId,
|
|
1688
|
+
requestId: this.accessRequestId,
|
|
1689
|
+
timeoutMs,
|
|
1690
|
+
},
|
|
1691
|
+
"Access request in-call wait started",
|
|
1262
1692
|
);
|
|
1263
1693
|
}
|
|
1264
1694
|
|
|
@@ -1287,80 +1717,35 @@ export class RelayConnection {
|
|
|
1287
1717
|
*/
|
|
1288
1718
|
private handleAccessRequestApproved(): void {
|
|
1289
1719
|
this.clearAccessRequestWait();
|
|
1290
|
-
this.connectionState = 'connected';
|
|
1291
1720
|
|
|
1292
1721
|
const assistantId = this.accessRequestAssistantId!;
|
|
1293
1722
|
const fromNumber = this.accessRequestFromNumber!;
|
|
1294
1723
|
const callerName = this.accessRequestCallerName;
|
|
1295
1724
|
|
|
1296
|
-
recordCallEvent(this.callSessionId,
|
|
1725
|
+
recordCallEvent(this.callSessionId, "inbound_acl_access_approved", {
|
|
1297
1726
|
from: fromNumber,
|
|
1298
1727
|
callerName,
|
|
1299
1728
|
requestId: this.accessRequestId,
|
|
1300
1729
|
});
|
|
1301
1730
|
|
|
1302
|
-
// Activate the caller as a trusted contact via the existing upsert path
|
|
1303
|
-
try {
|
|
1304
|
-
upsertMember({
|
|
1305
|
-
assistantId,
|
|
1306
|
-
sourceChannel: 'voice',
|
|
1307
|
-
externalUserId: fromNumber,
|
|
1308
|
-
externalChatId: fromNumber,
|
|
1309
|
-
displayName: callerName ?? undefined,
|
|
1310
|
-
status: 'active',
|
|
1311
|
-
policy: 'allow',
|
|
1312
|
-
});
|
|
1313
|
-
} catch (err) {
|
|
1314
|
-
log.error({ err, callSessionId: this.callSessionId }, 'Failed to activate voice caller as trusted contact');
|
|
1315
|
-
}
|
|
1316
|
-
|
|
1317
|
-
// Re-resolve actor trust now that the member is active
|
|
1318
|
-
const updatedTrust = resolveActorTrust({
|
|
1319
|
-
assistantId,
|
|
1320
|
-
sourceChannel: 'voice',
|
|
1321
|
-
conversationExternalId: fromNumber,
|
|
1322
|
-
actorExternalId: fromNumber,
|
|
1323
|
-
});
|
|
1324
|
-
|
|
1325
|
-
if (this.controller) {
|
|
1326
|
-
this.controller.setGuardianContext(
|
|
1327
|
-
toGuardianRuntimeContextFromTrust(updatedTrust, fromNumber),
|
|
1328
|
-
);
|
|
1329
|
-
}
|
|
1330
|
-
|
|
1331
|
-
updateCallSession(this.callSessionId, { status: 'in_progress' });
|
|
1332
|
-
|
|
1333
1731
|
log.info(
|
|
1334
1732
|
{ callSessionId: this.callSessionId, from: fromNumber },
|
|
1335
|
-
|
|
1733
|
+
"Access request approved — caller activated and continuing call",
|
|
1336
1734
|
);
|
|
1337
1735
|
|
|
1338
|
-
|
|
1339
|
-
|
|
1340
|
-
|
|
1341
|
-
|
|
1342
|
-
const handoffText = `Great! ${guardianLabel} said I can speak with you. How can I help?`;
|
|
1343
|
-
this.sendTextToken(handoffText, true);
|
|
1344
|
-
|
|
1345
|
-
// Record the deterministic handoff as an assistant_spoke event and
|
|
1346
|
-
// fire the transcript notifier so it appears in conversation history
|
|
1347
|
-
// and real-time transcript subscribers — matching the parity of text
|
|
1348
|
-
// spoken through the normal runTurn() pipeline.
|
|
1349
|
-
recordCallEvent(this.callSessionId, 'assistant_spoke', { text: handoffText });
|
|
1350
|
-
const session = getCallSession(this.callSessionId);
|
|
1351
|
-
if (session) {
|
|
1352
|
-
fireCallTranscriptNotifier(session.conversationId, this.callSessionId, 'assistant', handoffText);
|
|
1353
|
-
}
|
|
1354
|
-
|
|
1355
|
-
recordCallEvent(this.callSessionId, 'inbound_acl_post_approval_handoff_spoken', {
|
|
1356
|
-
from: fromNumber,
|
|
1736
|
+
this.continueCallAfterTrustedContactActivation({
|
|
1737
|
+
assistantId,
|
|
1738
|
+
fromNumber,
|
|
1739
|
+
callerName: callerName ?? undefined,
|
|
1357
1740
|
});
|
|
1358
1741
|
|
|
1359
|
-
|
|
1360
|
-
|
|
1361
|
-
|
|
1362
|
-
|
|
1363
|
-
|
|
1742
|
+
recordCallEvent(
|
|
1743
|
+
this.callSessionId,
|
|
1744
|
+
"inbound_acl_post_approval_handoff_spoken",
|
|
1745
|
+
{
|
|
1746
|
+
from: fromNumber,
|
|
1747
|
+
},
|
|
1748
|
+
);
|
|
1364
1749
|
}
|
|
1365
1750
|
|
|
1366
1751
|
/**
|
|
@@ -1371,7 +1756,7 @@ export class RelayConnection {
|
|
|
1371
1756
|
|
|
1372
1757
|
const guardianLabel = this.resolveGuardianLabel();
|
|
1373
1758
|
|
|
1374
|
-
recordCallEvent(this.callSessionId,
|
|
1759
|
+
recordCallEvent(this.callSessionId, "inbound_acl_access_denied", {
|
|
1375
1760
|
from: this.accessRequestFromNumber,
|
|
1376
1761
|
requestId: this.accessRequestId,
|
|
1377
1762
|
});
|
|
@@ -1381,21 +1766,21 @@ export class RelayConnection {
|
|
|
1381
1766
|
true,
|
|
1382
1767
|
);
|
|
1383
1768
|
|
|
1384
|
-
this.connectionState =
|
|
1769
|
+
this.connectionState = "disconnecting";
|
|
1385
1770
|
|
|
1386
1771
|
updateCallSession(this.callSessionId, {
|
|
1387
|
-
status:
|
|
1772
|
+
status: "failed",
|
|
1388
1773
|
endedAt: Date.now(),
|
|
1389
|
-
lastError:
|
|
1774
|
+
lastError: "Inbound voice ACL: guardian denied access request",
|
|
1390
1775
|
});
|
|
1391
1776
|
|
|
1392
1777
|
log.info(
|
|
1393
1778
|
{ callSessionId: this.callSessionId },
|
|
1394
|
-
|
|
1779
|
+
"Access request denied — ending call",
|
|
1395
1780
|
);
|
|
1396
1781
|
|
|
1397
1782
|
setTimeout(() => {
|
|
1398
|
-
this.endSession(
|
|
1783
|
+
this.endSession("Access request denied");
|
|
1399
1784
|
}, getTtsPlaybackDelayMs());
|
|
1400
1785
|
}
|
|
1401
1786
|
|
|
@@ -1404,13 +1789,13 @@ export class RelayConnection {
|
|
|
1404
1789
|
*/
|
|
1405
1790
|
private handleAccessRequestTimeout(): void {
|
|
1406
1791
|
// Emit callback handoff notification before clearing wait state
|
|
1407
|
-
this.emitAccessRequestCallbackHandoff(
|
|
1792
|
+
this.emitAccessRequestCallbackHandoff("timeout");
|
|
1408
1793
|
|
|
1409
1794
|
this.clearAccessRequestWait();
|
|
1410
1795
|
|
|
1411
1796
|
const guardianLabel = this.resolveGuardianLabel();
|
|
1412
1797
|
|
|
1413
|
-
recordCallEvent(this.callSessionId,
|
|
1798
|
+
recordCallEvent(this.callSessionId, "inbound_acl_access_timeout", {
|
|
1414
1799
|
from: this.accessRequestFromNumber,
|
|
1415
1800
|
requestId: this.accessRequestId,
|
|
1416
1801
|
callbackOptIn: this.callbackOptIn,
|
|
@@ -1418,27 +1803,27 @@ export class RelayConnection {
|
|
|
1418
1803
|
|
|
1419
1804
|
const callbackNote = this.callbackOptIn
|
|
1420
1805
|
? ` I've noted that you'd like a callback — I'll pass that along to ${guardianLabel}.`
|
|
1421
|
-
:
|
|
1806
|
+
: "";
|
|
1422
1807
|
this.sendTextToken(
|
|
1423
1808
|
`Sorry, I can't get ahold of ${guardianLabel} right now. I'll let them know you called.${callbackNote}`,
|
|
1424
1809
|
true,
|
|
1425
1810
|
);
|
|
1426
1811
|
|
|
1427
|
-
this.connectionState =
|
|
1812
|
+
this.connectionState = "disconnecting";
|
|
1428
1813
|
|
|
1429
1814
|
updateCallSession(this.callSessionId, {
|
|
1430
|
-
status:
|
|
1815
|
+
status: "failed",
|
|
1431
1816
|
endedAt: Date.now(),
|
|
1432
|
-
lastError:
|
|
1817
|
+
lastError: "Inbound voice ACL: guardian approval wait timed out",
|
|
1433
1818
|
});
|
|
1434
1819
|
|
|
1435
1820
|
log.info(
|
|
1436
1821
|
{ callSessionId: this.callSessionId },
|
|
1437
|
-
|
|
1822
|
+
"Access request timed out — ending call",
|
|
1438
1823
|
);
|
|
1439
1824
|
|
|
1440
1825
|
setTimeout(() => {
|
|
1441
|
-
this.endSession(
|
|
1826
|
+
this.endSession("Access request timed out");
|
|
1442
1827
|
}, getTtsPlaybackDelayMs());
|
|
1443
1828
|
}
|
|
1444
1829
|
|
|
@@ -1450,14 +1835,17 @@ export class RelayConnection {
|
|
|
1450
1835
|
* Idempotent: uses callbackHandoffNotified guard + deterministic dedupeKey
|
|
1451
1836
|
* to ensure at most one notification per call/request.
|
|
1452
1837
|
*/
|
|
1453
|
-
private emitAccessRequestCallbackHandoff(
|
|
1838
|
+
private emitAccessRequestCallbackHandoff(
|
|
1839
|
+
reason: "timeout" | "transport_closed",
|
|
1840
|
+
): void {
|
|
1454
1841
|
if (!this.callbackOptIn) return;
|
|
1455
1842
|
if (!this.accessRequestId) return;
|
|
1456
1843
|
if (this.callbackHandoffNotified) return;
|
|
1457
1844
|
|
|
1458
1845
|
this.callbackHandoffNotified = true;
|
|
1459
1846
|
|
|
1460
|
-
const assistantId =
|
|
1847
|
+
const assistantId =
|
|
1848
|
+
this.accessRequestAssistantId ?? DAEMON_INTERNAL_ASSISTANT_ID;
|
|
1461
1849
|
const fromNumber = this.accessRequestFromNumber ?? null;
|
|
1462
1850
|
|
|
1463
1851
|
// Resolve canonical request for requestCode and conversationId
|
|
@@ -1469,32 +1857,39 @@ export class RelayConnection {
|
|
|
1469
1857
|
let requesterMemberId: string | null = null;
|
|
1470
1858
|
if (fromNumber) {
|
|
1471
1859
|
try {
|
|
1472
|
-
const
|
|
1473
|
-
|
|
1474
|
-
sourceChannel: 'voice',
|
|
1860
|
+
const contactResult = findContactChannel({
|
|
1861
|
+
channelType: "voice",
|
|
1475
1862
|
externalUserId: fromNumber,
|
|
1476
1863
|
externalChatId: fromNumber,
|
|
1477
1864
|
});
|
|
1478
|
-
if (
|
|
1479
|
-
|
|
1865
|
+
if (
|
|
1866
|
+
contactResult &&
|
|
1867
|
+
contactResult.channel.status === "active" &&
|
|
1868
|
+
contactResult.channel.policy === "allow"
|
|
1869
|
+
) {
|
|
1870
|
+
requesterMemberId = contactResult.channel.id;
|
|
1480
1871
|
}
|
|
1481
1872
|
} catch (err) {
|
|
1482
|
-
log.warn(
|
|
1873
|
+
log.warn(
|
|
1874
|
+
{ err, callSessionId: this.callSessionId },
|
|
1875
|
+
"Failed to resolve member for callback handoff",
|
|
1876
|
+
);
|
|
1483
1877
|
}
|
|
1484
1878
|
}
|
|
1485
1879
|
|
|
1486
1880
|
const dedupeKey = `access-request-callback-handoff:${this.accessRequestId}`;
|
|
1487
|
-
const sourceSessionId =
|
|
1488
|
-
??
|
|
1881
|
+
const sourceSessionId =
|
|
1882
|
+
canonicalRequest?.conversationId ??
|
|
1883
|
+
`access-req-callback-${this.accessRequestId}`;
|
|
1489
1884
|
|
|
1490
1885
|
void emitNotificationSignal({
|
|
1491
|
-
sourceEventName:
|
|
1492
|
-
sourceChannel:
|
|
1886
|
+
sourceEventName: "ingress.access_request.callback_handoff",
|
|
1887
|
+
sourceChannel: "voice",
|
|
1493
1888
|
sourceSessionId,
|
|
1494
1889
|
assistantId,
|
|
1495
1890
|
attentionHints: {
|
|
1496
1891
|
requiresAction: false,
|
|
1497
|
-
urgency:
|
|
1892
|
+
urgency: "medium",
|
|
1498
1893
|
isAsyncBackground: true,
|
|
1499
1894
|
visibleInSourceNow: false,
|
|
1500
1895
|
},
|
|
@@ -1502,7 +1897,7 @@ export class RelayConnection {
|
|
|
1502
1897
|
requestId: this.accessRequestId,
|
|
1503
1898
|
requestCode: canonicalRequest?.requestCode ?? null,
|
|
1504
1899
|
callSessionId: this.callSessionId,
|
|
1505
|
-
sourceChannel:
|
|
1900
|
+
sourceChannel: "voice",
|
|
1506
1901
|
reason,
|
|
1507
1902
|
callbackOptIn: true,
|
|
1508
1903
|
callerPhoneNumber: fromNumber,
|
|
@@ -1510,30 +1905,40 @@ export class RelayConnection {
|
|
|
1510
1905
|
requesterExternalUserId: fromNumber,
|
|
1511
1906
|
requesterChatId: fromNumber,
|
|
1512
1907
|
requesterMemberId,
|
|
1513
|
-
requesterMemberSourceChannel: requesterMemberId ?
|
|
1908
|
+
requesterMemberSourceChannel: requesterMemberId ? "voice" : null,
|
|
1514
1909
|
},
|
|
1515
1910
|
dedupeKey,
|
|
1516
|
-
})
|
|
1517
|
-
|
|
1518
|
-
|
|
1519
|
-
|
|
1520
|
-
|
|
1521
|
-
|
|
1522
|
-
|
|
1523
|
-
|
|
1524
|
-
|
|
1525
|
-
|
|
1526
|
-
|
|
1527
|
-
|
|
1528
|
-
|
|
1529
|
-
|
|
1530
|
-
|
|
1911
|
+
})
|
|
1912
|
+
.then(() => {
|
|
1913
|
+
recordCallEvent(this.callSessionId, "callback_handoff_notified", {
|
|
1914
|
+
requestId: this.accessRequestId,
|
|
1915
|
+
reason,
|
|
1916
|
+
requesterMemberId,
|
|
1917
|
+
});
|
|
1918
|
+
log.info(
|
|
1919
|
+
{
|
|
1920
|
+
callSessionId: this.callSessionId,
|
|
1921
|
+
requestId: this.accessRequestId,
|
|
1922
|
+
reason,
|
|
1923
|
+
},
|
|
1924
|
+
"Callback handoff notification emitted",
|
|
1925
|
+
);
|
|
1926
|
+
})
|
|
1927
|
+
.catch((err) => {
|
|
1928
|
+
recordCallEvent(this.callSessionId, "callback_handoff_failed", {
|
|
1929
|
+
requestId: this.accessRequestId,
|
|
1930
|
+
reason,
|
|
1931
|
+
error: err instanceof Error ? err.message : String(err),
|
|
1932
|
+
});
|
|
1933
|
+
log.error(
|
|
1934
|
+
{
|
|
1935
|
+
err,
|
|
1936
|
+
callSessionId: this.callSessionId,
|
|
1937
|
+
requestId: this.accessRequestId,
|
|
1938
|
+
},
|
|
1939
|
+
"Failed to emit callback handoff notification",
|
|
1940
|
+
);
|
|
1531
1941
|
});
|
|
1532
|
-
log.error(
|
|
1533
|
-
{ err, callSessionId: this.callSessionId, requestId: this.accessRequestId },
|
|
1534
|
-
'Failed to emit callback handoff notification',
|
|
1535
|
-
);
|
|
1536
|
-
});
|
|
1537
1942
|
}
|
|
1538
1943
|
|
|
1539
1944
|
/**
|
|
@@ -1546,7 +1951,7 @@ export class RelayConnection {
|
|
|
1546
1951
|
this.nameCaptureTimeoutTimer = null;
|
|
1547
1952
|
}
|
|
1548
1953
|
|
|
1549
|
-
recordCallEvent(this.callSessionId,
|
|
1954
|
+
recordCallEvent(this.callSessionId, "inbound_acl_name_capture_timeout", {
|
|
1550
1955
|
from: this.accessRequestFromNumber,
|
|
1551
1956
|
});
|
|
1552
1957
|
|
|
@@ -1555,27 +1960,27 @@ export class RelayConnection {
|
|
|
1555
1960
|
true,
|
|
1556
1961
|
);
|
|
1557
1962
|
|
|
1558
|
-
this.connectionState =
|
|
1963
|
+
this.connectionState = "disconnecting";
|
|
1559
1964
|
|
|
1560
1965
|
updateCallSession(this.callSessionId, {
|
|
1561
|
-
status:
|
|
1966
|
+
status: "failed",
|
|
1562
1967
|
endedAt: Date.now(),
|
|
1563
|
-
lastError:
|
|
1968
|
+
lastError: "Inbound voice ACL: name capture timed out",
|
|
1564
1969
|
});
|
|
1565
1970
|
|
|
1566
1971
|
log.info(
|
|
1567
1972
|
{ callSessionId: this.callSessionId },
|
|
1568
|
-
|
|
1973
|
+
"Name capture timed out — ending call",
|
|
1569
1974
|
);
|
|
1570
1975
|
|
|
1571
1976
|
setTimeout(() => {
|
|
1572
|
-
this.endSession(
|
|
1977
|
+
this.endSession("Name capture timed out");
|
|
1573
1978
|
}, getTtsPlaybackDelayMs());
|
|
1574
1979
|
}
|
|
1575
1980
|
|
|
1576
1981
|
/**
|
|
1577
1982
|
* Validate an entered invite code against active voice invites for the
|
|
1578
|
-
* caller. On success, create/activate the
|
|
1983
|
+
* caller. On success, create/activate the contact and transition
|
|
1579
1984
|
* to the normal call flow. On failure, allow retries up to max attempts.
|
|
1580
1985
|
*/
|
|
1581
1986
|
private attemptInviteCodeRedemption(enteredCode: string): void {
|
|
@@ -1586,74 +1991,85 @@ export class RelayConnection {
|
|
|
1586
1991
|
const result = redeemVoiceInviteCode({
|
|
1587
1992
|
assistantId: this.inviteRedemptionAssistantId,
|
|
1588
1993
|
callerExternalUserId: this.inviteRedemptionFromNumber,
|
|
1589
|
-
sourceChannel:
|
|
1994
|
+
sourceChannel: "voice",
|
|
1590
1995
|
code: enteredCode,
|
|
1591
1996
|
});
|
|
1592
1997
|
|
|
1593
1998
|
if (result.ok) {
|
|
1594
|
-
this.connectionState = 'connected';
|
|
1595
1999
|
this.inviteRedemptionActive = false;
|
|
1596
2000
|
this.verificationAttempts = 0;
|
|
1597
|
-
this.dtmfBuffer =
|
|
2001
|
+
this.dtmfBuffer = "";
|
|
1598
2002
|
|
|
1599
|
-
recordCallEvent(this.callSessionId,
|
|
2003
|
+
recordCallEvent(this.callSessionId, "invite_redemption_succeeded", {
|
|
1600
2004
|
memberId: result.memberId,
|
|
1601
|
-
...(result.type ===
|
|
2005
|
+
...(result.type === "redeemed" ? { inviteId: result.inviteId } : {}),
|
|
1602
2006
|
});
|
|
1603
2007
|
log.info(
|
|
1604
|
-
{
|
|
1605
|
-
|
|
2008
|
+
{
|
|
2009
|
+
callSessionId: this.callSessionId,
|
|
2010
|
+
memberId: result.memberId,
|
|
2011
|
+
type: result.type,
|
|
2012
|
+
},
|
|
2013
|
+
"Voice invite redemption succeeded",
|
|
1606
2014
|
);
|
|
1607
2015
|
|
|
1608
|
-
|
|
1609
|
-
|
|
1610
|
-
|
|
1611
|
-
|
|
1612
|
-
|
|
1613
|
-
|
|
1614
|
-
});
|
|
1615
|
-
this.controller.setGuardianContext(
|
|
1616
|
-
toGuardianRuntimeContextFromTrust(redeemedActorTrust, this.inviteRedemptionFromNumber),
|
|
1617
|
-
);
|
|
1618
|
-
this.startNormalCallFlow(this.controller, true);
|
|
1619
|
-
}
|
|
2016
|
+
this.continueCallAfterTrustedContactActivation({
|
|
2017
|
+
assistantId: this.inviteRedemptionAssistantId,
|
|
2018
|
+
fromNumber: this.inviteRedemptionFromNumber,
|
|
2019
|
+
callerName: this.inviteRedemptionFriendName ?? undefined,
|
|
2020
|
+
skipMemberActivation: true,
|
|
2021
|
+
});
|
|
1620
2022
|
} else {
|
|
1621
2023
|
// On any invalid/expired code, emit exact deterministic failure copy and end call immediately.
|
|
1622
2024
|
this.inviteRedemptionActive = false;
|
|
1623
2025
|
|
|
1624
|
-
recordCallEvent(this.callSessionId,
|
|
2026
|
+
recordCallEvent(this.callSessionId, "invite_redemption_failed", {
|
|
1625
2027
|
attempts: 1,
|
|
1626
2028
|
});
|
|
1627
2029
|
log.warn(
|
|
1628
2030
|
{ callSessionId: this.callSessionId },
|
|
1629
|
-
|
|
2031
|
+
"Voice invite redemption failed — invalid or expired code",
|
|
1630
2032
|
);
|
|
1631
2033
|
|
|
1632
|
-
const displayGuardian =
|
|
2034
|
+
const displayGuardian =
|
|
2035
|
+
this.inviteRedemptionGuardianName ?? "your contact";
|
|
1633
2036
|
this.sendTextToken(
|
|
1634
2037
|
`Sorry, the code you provided is incorrect or has since expired. Please ask ${displayGuardian} for a new code. Goodbye.`,
|
|
1635
2038
|
true,
|
|
1636
2039
|
);
|
|
1637
2040
|
|
|
1638
|
-
this.connectionState =
|
|
2041
|
+
this.connectionState = "disconnecting";
|
|
1639
2042
|
|
|
1640
2043
|
updateCallSession(this.callSessionId, {
|
|
1641
|
-
status:
|
|
2044
|
+
status: "failed",
|
|
1642
2045
|
endedAt: Date.now(),
|
|
1643
|
-
lastError:
|
|
2046
|
+
lastError: "Voice invite redemption failed — invalid or expired code",
|
|
1644
2047
|
});
|
|
1645
2048
|
|
|
1646
2049
|
const failSession = getCallSession(this.callSessionId);
|
|
1647
2050
|
if (failSession) {
|
|
1648
2051
|
expirePendingQuestions(this.callSessionId);
|
|
1649
|
-
persistCallCompletionMessage(
|
|
1650
|
-
|
|
2052
|
+
persistCallCompletionMessage(
|
|
2053
|
+
failSession.conversationId,
|
|
2054
|
+
this.callSessionId,
|
|
2055
|
+
).catch((err) => {
|
|
2056
|
+
log.error(
|
|
2057
|
+
{
|
|
2058
|
+
err,
|
|
2059
|
+
conversationId: failSession.conversationId,
|
|
2060
|
+
callSessionId: this.callSessionId,
|
|
2061
|
+
},
|
|
2062
|
+
"Failed to persist call completion message",
|
|
2063
|
+
);
|
|
1651
2064
|
});
|
|
1652
|
-
fireCallCompletionNotifier(
|
|
2065
|
+
fireCallCompletionNotifier(
|
|
2066
|
+
failSession.conversationId,
|
|
2067
|
+
this.callSessionId,
|
|
2068
|
+
);
|
|
1653
2069
|
}
|
|
1654
2070
|
|
|
1655
2071
|
setTimeout(() => {
|
|
1656
|
-
this.endSession(
|
|
2072
|
+
this.endSession("Invite redemption failed");
|
|
1657
2073
|
}, getTtsPlaybackDelayMs());
|
|
1658
2074
|
}
|
|
1659
2075
|
}
|
|
@@ -1666,29 +2082,58 @@ export class RelayConnection {
|
|
|
1666
2082
|
* to @username, then the user's preferred name from USER.md.
|
|
1667
2083
|
*/
|
|
1668
2084
|
private resolveGuardianLabel(): string {
|
|
1669
|
-
const assistantId =
|
|
2085
|
+
const assistantId =
|
|
2086
|
+
this.accessRequestAssistantId ?? DAEMON_INTERNAL_ASSISTANT_ID;
|
|
1670
2087
|
|
|
1671
2088
|
// Try the voice-channel binding first, then fall back to any active
|
|
1672
2089
|
// binding for the assistant (mirrors the cross-channel fallback pattern
|
|
1673
2090
|
// in access-request-helper.ts).
|
|
1674
2091
|
let metadataJson: string | null = null;
|
|
1675
|
-
|
|
1676
|
-
|
|
1677
|
-
|
|
1678
|
-
|
|
1679
|
-
|
|
1680
|
-
|
|
1681
|
-
|
|
2092
|
+
// Contacts-first: prefer the voice-bound guardian, then fall back to
|
|
2093
|
+
// any guardian channel (mirrors the voice-first pattern in the legacy path).
|
|
2094
|
+
const voiceGuardian = findGuardianForChannel("voice", assistantId);
|
|
2095
|
+
const guardianChannels = voiceGuardian
|
|
2096
|
+
? null
|
|
2097
|
+
: listGuardianChannels(assistantId);
|
|
2098
|
+
const guardianContact = voiceGuardian?.contact ?? guardianChannels?.contact;
|
|
2099
|
+
if (guardianContact) {
|
|
2100
|
+
const meta: Record<string, string> = {};
|
|
2101
|
+
if (guardianContact.displayName) {
|
|
2102
|
+
meta.displayName = guardianContact.displayName;
|
|
2103
|
+
}
|
|
2104
|
+
// Preserve the username fallback: use the voice channel's externalUserId
|
|
2105
|
+
// so downstream parsing can fall back to @username when displayName is a
|
|
2106
|
+
// raw external ID (e.g., phone number from contact-sync).
|
|
2107
|
+
const voiceChannel =
|
|
2108
|
+
voiceGuardian?.channel ??
|
|
2109
|
+
guardianChannels?.channels.find((ch) => ch.type === "voice");
|
|
2110
|
+
if (voiceChannel?.externalUserId) {
|
|
2111
|
+
meta.username = voiceChannel.externalUserId;
|
|
2112
|
+
}
|
|
2113
|
+
if (Object.keys(meta).length > 0) {
|
|
2114
|
+
metadataJson = JSON.stringify(meta);
|
|
2115
|
+
}
|
|
2116
|
+
}
|
|
2117
|
+
if (!metadataJson) {
|
|
2118
|
+
const voiceBinding = getGuardianBinding(assistantId, "voice");
|
|
2119
|
+
if (voiceBinding?.metadataJson) {
|
|
2120
|
+
metadataJson = voiceBinding.metadataJson;
|
|
1682
2121
|
}
|
|
1683
2122
|
}
|
|
1684
2123
|
|
|
1685
2124
|
if (metadataJson) {
|
|
1686
2125
|
try {
|
|
1687
2126
|
const parsed = JSON.parse(metadataJson) as Record<string, unknown>;
|
|
1688
|
-
if (
|
|
2127
|
+
if (
|
|
2128
|
+
typeof parsed.displayName === "string" &&
|
|
2129
|
+
parsed.displayName.trim().length > 0
|
|
2130
|
+
) {
|
|
1689
2131
|
return parsed.displayName.trim();
|
|
1690
2132
|
}
|
|
1691
|
-
if (
|
|
2133
|
+
if (
|
|
2134
|
+
typeof parsed.username === "string" &&
|
|
2135
|
+
parsed.username.trim().length > 0
|
|
2136
|
+
) {
|
|
1692
2137
|
return `@${parsed.username.trim()}`;
|
|
1693
2138
|
}
|
|
1694
2139
|
} catch {
|
|
@@ -1738,10 +2183,18 @@ export class RelayConnection {
|
|
|
1738
2183
|
|
|
1739
2184
|
const elapsed = Date.now() - this.accessRequestWaitStartedAt;
|
|
1740
2185
|
const initialWindow = getGuardianWaitUpdateInitialWindowMs();
|
|
1741
|
-
const intervalMs =
|
|
1742
|
-
|
|
1743
|
-
|
|
1744
|
-
|
|
2186
|
+
const intervalMs =
|
|
2187
|
+
elapsed < initialWindow
|
|
2188
|
+
? getGuardianWaitUpdateInitialIntervalMs()
|
|
2189
|
+
: getGuardianWaitUpdateSteadyMinIntervalMs() +
|
|
2190
|
+
Math.floor(
|
|
2191
|
+
Math.random() *
|
|
2192
|
+
Math.max(
|
|
2193
|
+
0,
|
|
2194
|
+
getGuardianWaitUpdateSteadyMaxIntervalMs() -
|
|
2195
|
+
getGuardianWaitUpdateSteadyMinIntervalMs(),
|
|
2196
|
+
),
|
|
2197
|
+
);
|
|
1745
2198
|
|
|
1746
2199
|
this.accessRequestHeartbeatTimer = setTimeout(() => {
|
|
1747
2200
|
if (!this.accessRequestWaitActive) return;
|
|
@@ -1749,14 +2202,21 @@ export class RelayConnection {
|
|
|
1749
2202
|
const message = this.getHeartbeatMessage();
|
|
1750
2203
|
this.sendTextToken(message, true);
|
|
1751
2204
|
|
|
1752
|
-
recordCallEvent(
|
|
1753
|
-
|
|
1754
|
-
|
|
1755
|
-
|
|
2205
|
+
recordCallEvent(
|
|
2206
|
+
this.callSessionId,
|
|
2207
|
+
"voice_guardian_wait_heartbeat_sent",
|
|
2208
|
+
{
|
|
2209
|
+
sequence: this.heartbeatSequence - 1,
|
|
2210
|
+
message,
|
|
2211
|
+
},
|
|
2212
|
+
);
|
|
1756
2213
|
|
|
1757
2214
|
log.debug(
|
|
1758
|
-
{
|
|
1759
|
-
|
|
2215
|
+
{
|
|
2216
|
+
callSessionId: this.callSessionId,
|
|
2217
|
+
sequence: this.heartbeatSequence - 1,
|
|
2218
|
+
},
|
|
2219
|
+
"Guardian wait heartbeat sent",
|
|
1760
2220
|
);
|
|
1761
2221
|
|
|
1762
2222
|
// Schedule the next heartbeat
|
|
@@ -1773,52 +2233,72 @@ export class RelayConnection {
|
|
|
1773
2233
|
* - 'callback_decline': explicitly declining a callback
|
|
1774
2234
|
* - 'neutral': anything else
|
|
1775
2235
|
*/
|
|
1776
|
-
private classifyWaitUtterance(
|
|
2236
|
+
private classifyWaitUtterance(
|
|
2237
|
+
text: string,
|
|
2238
|
+
):
|
|
2239
|
+
| "empty"
|
|
2240
|
+
| "patience_check"
|
|
2241
|
+
| "impatient"
|
|
2242
|
+
| "callback_opt_in"
|
|
2243
|
+
| "callback_decline"
|
|
2244
|
+
| "neutral" {
|
|
1777
2245
|
const lower = text.toLowerCase().trim();
|
|
1778
|
-
if (lower.length === 0) return
|
|
2246
|
+
if (lower.length === 0) return "empty";
|
|
1779
2247
|
|
|
1780
2248
|
// Callback opt-in patterns (check before impatience to catch "yes call me back")
|
|
1781
2249
|
if (this.callbackOfferMade) {
|
|
1782
|
-
if (
|
|
1783
|
-
|
|
1784
|
-
|
|
1785
|
-
||
|
|
1786
|
-
|
|
1787
|
-
|
|
2250
|
+
if (
|
|
2251
|
+
/\b(yes|yeah|yep|sure|okay|ok|please)\b.*\b(call\s*(me\s*)?back|callback)\b/.test(
|
|
2252
|
+
lower,
|
|
2253
|
+
) ||
|
|
2254
|
+
/\b(call\s*(me\s*)?back|callback)\b.*\b(yes|yeah|please|sure)\b/.test(
|
|
2255
|
+
lower,
|
|
2256
|
+
) ||
|
|
2257
|
+
/^(yes|yeah|yep|sure|okay|ok|please)\s*[.,!]?\s*$/.test(lower) ||
|
|
2258
|
+
/\bcall\s*(me\s*)?back\b/.test(lower) ||
|
|
2259
|
+
/\bplease\s+do\b/.test(lower)
|
|
2260
|
+
) {
|
|
2261
|
+
return "callback_opt_in";
|
|
1788
2262
|
}
|
|
1789
|
-
if (
|
|
1790
|
-
|
|
1791
|
-
|
|
1792
|
-
|
|
2263
|
+
if (
|
|
2264
|
+
/\b(no|nah|nope)\b/.test(lower) ||
|
|
2265
|
+
/\bi('?ll| will)\s+hold\b/.test(lower) ||
|
|
2266
|
+
/\bi('?ll| will)\s+wait\b/.test(lower)
|
|
2267
|
+
) {
|
|
2268
|
+
return "callback_decline";
|
|
1793
2269
|
}
|
|
1794
2270
|
}
|
|
1795
2271
|
|
|
1796
2272
|
// Impatience patterns
|
|
1797
|
-
if (
|
|
1798
|
-
|
|
1799
|
-
|
|
1800
|
-
|
|
1801
|
-
|
|
1802
|
-
|
|
1803
|
-
|
|
1804
|
-
|
|
1805
|
-
|
|
1806
|
-
|
|
1807
|
-
|
|
2273
|
+
if (
|
|
2274
|
+
/\bhurry\s*(up)?\b/.test(lower) ||
|
|
2275
|
+
/\btaking\s+(too\s+|so\s+)?long\b/.test(lower) ||
|
|
2276
|
+
/\bforget\s+it\b/.test(lower) ||
|
|
2277
|
+
/\bnever\s*mind\b/.test(lower) ||
|
|
2278
|
+
/\bdon'?t\s+have\s+time\b/.test(lower) ||
|
|
2279
|
+
/\bhow\s+much\s+longer\b/.test(lower) ||
|
|
2280
|
+
/\bi('?m| am)\s+(getting\s+)?impatient\b/.test(lower) ||
|
|
2281
|
+
/\bthis\s+is\s+(ridiculous|absurd|crazy)\b/.test(lower) ||
|
|
2282
|
+
/\bcome\s+on\b/.test(lower) ||
|
|
2283
|
+
/\bi\s+(gotta|have\s+to|need\s+to)\s+go\b/.test(lower)
|
|
2284
|
+
) {
|
|
2285
|
+
return "impatient";
|
|
1808
2286
|
}
|
|
1809
2287
|
|
|
1810
2288
|
// Patience check / status inquiry patterns
|
|
1811
|
-
if (
|
|
1812
|
-
|
|
1813
|
-
|
|
1814
|
-
|
|
1815
|
-
|
|
1816
|
-
|
|
1817
|
-
|
|
1818
|
-
|
|
2289
|
+
if (
|
|
2290
|
+
/\bhello\??\s*$/.test(lower) ||
|
|
2291
|
+
/\bstill\s+there\b/.test(lower) ||
|
|
2292
|
+
/\bany\s+(update|news)\b/.test(lower) ||
|
|
2293
|
+
/\bwhat('?s| is)\s+(happening|going\s+on)\b/.test(lower) ||
|
|
2294
|
+
/\bare\s+you\s+still\b/.test(lower) ||
|
|
2295
|
+
/\bhow\s+(long|much\s+longer)\b/.test(lower) ||
|
|
2296
|
+
/\banyone\s+there\b/.test(lower)
|
|
2297
|
+
) {
|
|
2298
|
+
return "patience_check";
|
|
1819
2299
|
}
|
|
1820
2300
|
|
|
1821
|
-
return
|
|
2301
|
+
return "neutral";
|
|
1822
2302
|
}
|
|
1823
2303
|
|
|
1824
2304
|
/**
|
|
@@ -1829,12 +2309,16 @@ export class RelayConnection {
|
|
|
1829
2309
|
const now = Date.now();
|
|
1830
2310
|
const classification = this.classifyWaitUtterance(text);
|
|
1831
2311
|
|
|
1832
|
-
recordCallEvent(
|
|
1833
|
-
|
|
1834
|
-
|
|
1835
|
-
|
|
2312
|
+
recordCallEvent(
|
|
2313
|
+
this.callSessionId,
|
|
2314
|
+
"voice_guardian_wait_prompt_classified",
|
|
2315
|
+
{
|
|
2316
|
+
classification,
|
|
2317
|
+
transcript: text,
|
|
2318
|
+
},
|
|
2319
|
+
);
|
|
1836
2320
|
|
|
1837
|
-
if (classification ===
|
|
2321
|
+
if (classification === "empty") return;
|
|
1838
2322
|
|
|
1839
2323
|
const guardianLabel = this.resolveGuardianLabel();
|
|
1840
2324
|
|
|
@@ -1842,10 +2326,14 @@ export class RelayConnection {
|
|
|
1842
2326
|
// the caller is answering a direct question and dropping their response
|
|
1843
2327
|
// would silently discard their decision.
|
|
1844
2328
|
switch (classification) {
|
|
1845
|
-
case
|
|
2329
|
+
case "callback_opt_in": {
|
|
1846
2330
|
this.callbackOptIn = true;
|
|
1847
2331
|
this.lastInWaitReplyAt = now;
|
|
1848
|
-
recordCallEvent(
|
|
2332
|
+
recordCallEvent(
|
|
2333
|
+
this.callSessionId,
|
|
2334
|
+
"voice_guardian_wait_callback_opt_in_set",
|
|
2335
|
+
{},
|
|
2336
|
+
);
|
|
1849
2337
|
if (this.accessRequestHeartbeatTimer) {
|
|
1850
2338
|
clearTimeout(this.accessRequestHeartbeatTimer);
|
|
1851
2339
|
this.accessRequestHeartbeatTimer = null;
|
|
@@ -1857,10 +2345,14 @@ export class RelayConnection {
|
|
|
1857
2345
|
this.scheduleNextHeartbeat();
|
|
1858
2346
|
return;
|
|
1859
2347
|
}
|
|
1860
|
-
case
|
|
2348
|
+
case "callback_decline": {
|
|
1861
2349
|
this.callbackOptIn = false;
|
|
1862
2350
|
this.lastInWaitReplyAt = now;
|
|
1863
|
-
recordCallEvent(
|
|
2351
|
+
recordCallEvent(
|
|
2352
|
+
this.callSessionId,
|
|
2353
|
+
"voice_guardian_wait_callback_opt_in_declined",
|
|
2354
|
+
{},
|
|
2355
|
+
);
|
|
1864
2356
|
if (this.accessRequestHeartbeatTimer) {
|
|
1865
2357
|
clearTimeout(this.accessRequestHeartbeatTimer);
|
|
1866
2358
|
this.accessRequestHeartbeatTimer = null;
|
|
@@ -1877,21 +2369,31 @@ export class RelayConnection {
|
|
|
1877
2369
|
}
|
|
1878
2370
|
|
|
1879
2371
|
// Enforce cooldown on non-callback utterances to prevent spam
|
|
1880
|
-
if (
|
|
1881
|
-
|
|
2372
|
+
if (
|
|
2373
|
+
now - this.lastInWaitReplyAt <
|
|
2374
|
+
RelayConnection.IN_WAIT_REPLY_COOLDOWN_MS
|
|
2375
|
+
) {
|
|
2376
|
+
log.debug(
|
|
2377
|
+
{ callSessionId: this.callSessionId },
|
|
2378
|
+
"In-wait reply suppressed by cooldown",
|
|
2379
|
+
);
|
|
1882
2380
|
return;
|
|
1883
2381
|
}
|
|
1884
2382
|
this.lastInWaitReplyAt = now;
|
|
1885
2383
|
|
|
1886
2384
|
switch (classification) {
|
|
1887
|
-
case
|
|
2385
|
+
case "impatient": {
|
|
1888
2386
|
if (this.accessRequestHeartbeatTimer) {
|
|
1889
2387
|
clearTimeout(this.accessRequestHeartbeatTimer);
|
|
1890
2388
|
this.accessRequestHeartbeatTimer = null;
|
|
1891
2389
|
}
|
|
1892
2390
|
if (!this.callbackOfferMade) {
|
|
1893
2391
|
this.callbackOfferMade = true;
|
|
1894
|
-
recordCallEvent(
|
|
2392
|
+
recordCallEvent(
|
|
2393
|
+
this.callSessionId,
|
|
2394
|
+
"voice_guardian_wait_callback_offer_sent",
|
|
2395
|
+
{},
|
|
2396
|
+
);
|
|
1895
2397
|
this.sendTextToken(
|
|
1896
2398
|
`I understand this is taking a while. I can have ${guardianLabel} call you back once I hear from them. Would you like that, or would you prefer to keep holding?`,
|
|
1897
2399
|
true,
|
|
@@ -1906,7 +2408,7 @@ export class RelayConnection {
|
|
|
1906
2408
|
this.scheduleNextHeartbeat();
|
|
1907
2409
|
break;
|
|
1908
2410
|
}
|
|
1909
|
-
case
|
|
2411
|
+
case "patience_check": {
|
|
1910
2412
|
// Immediate reassurance — reset the heartbeat timer so we
|
|
1911
2413
|
// don't double up with a scheduled heartbeat
|
|
1912
2414
|
if (this.accessRequestHeartbeatTimer) {
|
|
@@ -1920,7 +2422,7 @@ export class RelayConnection {
|
|
|
1920
2422
|
this.scheduleNextHeartbeat();
|
|
1921
2423
|
break;
|
|
1922
2424
|
}
|
|
1923
|
-
case
|
|
2425
|
+
case "neutral":
|
|
1924
2426
|
default: {
|
|
1925
2427
|
if (this.accessRequestHeartbeatTimer) {
|
|
1926
2428
|
clearTimeout(this.accessRequestHeartbeatTimer);
|
|
@@ -1937,7 +2439,7 @@ export class RelayConnection {
|
|
|
1937
2439
|
}
|
|
1938
2440
|
|
|
1939
2441
|
private async handlePrompt(msg: RelayPromptMessage): Promise<void> {
|
|
1940
|
-
if (this.connectionState ===
|
|
2442
|
+
if (this.connectionState === "disconnecting") {
|
|
1941
2443
|
return;
|
|
1942
2444
|
}
|
|
1943
2445
|
|
|
@@ -1947,7 +2449,7 @@ export class RelayConnection {
|
|
|
1947
2449
|
}
|
|
1948
2450
|
|
|
1949
2451
|
// During name capture, the caller's response is their name.
|
|
1950
|
-
if (this.connectionState ===
|
|
2452
|
+
if (this.connectionState === "awaiting_name") {
|
|
1951
2453
|
const callerName = msg.voicePrompt.trim();
|
|
1952
2454
|
if (!callerName) {
|
|
1953
2455
|
// Whitespace-only or empty transcript (e.g. silence/noise) —
|
|
@@ -1957,7 +2459,7 @@ export class RelayConnection {
|
|
|
1957
2459
|
}
|
|
1958
2460
|
log.info(
|
|
1959
2461
|
{ callSessionId: this.callSessionId, callerName },
|
|
1960
|
-
|
|
2462
|
+
"Name captured from unknown inbound caller",
|
|
1961
2463
|
);
|
|
1962
2464
|
this.handleNameCaptureResponse(callerName);
|
|
1963
2465
|
return;
|
|
@@ -1965,18 +2467,27 @@ export class RelayConnection {
|
|
|
1965
2467
|
|
|
1966
2468
|
// During guardian decision wait, classify caller speech for
|
|
1967
2469
|
// reassurance, impatience detection, and callback offer.
|
|
1968
|
-
if (this.connectionState ===
|
|
2470
|
+
if (this.connectionState === "awaiting_guardian_decision") {
|
|
1969
2471
|
this.handleWaitStatePrompt(msg.voicePrompt);
|
|
1970
2472
|
return;
|
|
1971
2473
|
}
|
|
1972
2474
|
|
|
1973
2475
|
// During guardian verification (inbound or outbound), attempt to parse
|
|
1974
2476
|
// spoken digits from the transcript and validate them.
|
|
1975
|
-
if (
|
|
1976
|
-
|
|
2477
|
+
if (
|
|
2478
|
+
this.connectionState === "verification_pending" &&
|
|
2479
|
+
this.guardianVerificationActive
|
|
2480
|
+
) {
|
|
2481
|
+
const spokenDigits = RelayConnection.parseDigitsFromSpeech(
|
|
2482
|
+
msg.voicePrompt,
|
|
2483
|
+
);
|
|
1977
2484
|
log.info(
|
|
1978
|
-
{
|
|
1979
|
-
|
|
2485
|
+
{
|
|
2486
|
+
callSessionId: this.callSessionId,
|
|
2487
|
+
transcript: msg.voicePrompt,
|
|
2488
|
+
spokenDigits,
|
|
2489
|
+
},
|
|
2490
|
+
"Speech received during guardian voice verification",
|
|
1980
2491
|
);
|
|
1981
2492
|
if (spokenDigits.length >= this.verificationCodeLength) {
|
|
1982
2493
|
const enteredCode = spokenDigits.slice(0, this.verificationCodeLength);
|
|
@@ -1992,14 +2503,26 @@ export class RelayConnection {
|
|
|
1992
2503
|
|
|
1993
2504
|
// During invite redemption, attempt to parse spoken digits from the
|
|
1994
2505
|
// transcript and validate against the caller's active voice invite.
|
|
1995
|
-
if (
|
|
1996
|
-
|
|
2506
|
+
if (
|
|
2507
|
+
this.connectionState === "verification_pending" &&
|
|
2508
|
+
this.inviteRedemptionActive
|
|
2509
|
+
) {
|
|
2510
|
+
const spokenDigits = RelayConnection.parseDigitsFromSpeech(
|
|
2511
|
+
msg.voicePrompt,
|
|
2512
|
+
);
|
|
1997
2513
|
log.info(
|
|
1998
|
-
{
|
|
1999
|
-
|
|
2514
|
+
{
|
|
2515
|
+
callSessionId: this.callSessionId,
|
|
2516
|
+
transcript: msg.voicePrompt,
|
|
2517
|
+
spokenDigits,
|
|
2518
|
+
},
|
|
2519
|
+
"Speech received during invite redemption",
|
|
2000
2520
|
);
|
|
2001
2521
|
if (spokenDigits.length >= this.inviteRedemptionCodeLength) {
|
|
2002
|
-
const enteredCode = spokenDigits.slice(
|
|
2522
|
+
const enteredCode = spokenDigits.slice(
|
|
2523
|
+
0,
|
|
2524
|
+
this.inviteRedemptionCodeLength,
|
|
2525
|
+
);
|
|
2003
2526
|
this.attemptInviteCodeRedemption(enteredCode);
|
|
2004
2527
|
} else if (spokenDigits.length > 0) {
|
|
2005
2528
|
this.sendTextToken(
|
|
@@ -2012,31 +2535,39 @@ export class RelayConnection {
|
|
|
2012
2535
|
|
|
2013
2536
|
// During outbound callee verification, ignore voice prompts — the callee
|
|
2014
2537
|
// should be entering DTMF digits, not speaking.
|
|
2015
|
-
if (this.connectionState ===
|
|
2016
|
-
log.debug(
|
|
2538
|
+
if (this.connectionState === "verification_pending") {
|
|
2539
|
+
log.debug(
|
|
2540
|
+
{ callSessionId: this.callSessionId },
|
|
2541
|
+
"Ignoring voice prompt during callee verification",
|
|
2542
|
+
);
|
|
2017
2543
|
return;
|
|
2018
2544
|
}
|
|
2019
2545
|
|
|
2020
2546
|
log.info(
|
|
2021
|
-
{
|
|
2022
|
-
|
|
2547
|
+
{
|
|
2548
|
+
callSessionId: this.callSessionId,
|
|
2549
|
+
transcript: msg.voicePrompt,
|
|
2550
|
+
lang: msg.lang,
|
|
2551
|
+
},
|
|
2552
|
+
"Caller transcript received (final)",
|
|
2023
2553
|
);
|
|
2024
2554
|
|
|
2025
2555
|
// Spread to widen the typed message into a plain record — extractPromptSpeakerMetadata
|
|
2026
2556
|
// probes for snake_case and nested property variants not on RelayPromptMessage.
|
|
2027
2557
|
const speakerMetadata = extractPromptSpeakerMetadata({ ...msg });
|
|
2028
|
-
const speaker =
|
|
2558
|
+
const speaker =
|
|
2559
|
+
this.speakerIdentityTracker.identifySpeaker(speakerMetadata);
|
|
2029
2560
|
|
|
2030
2561
|
// Record in conversation history
|
|
2031
2562
|
this.conversationHistory.push({
|
|
2032
|
-
role:
|
|
2563
|
+
role: "caller",
|
|
2033
2564
|
text: msg.voicePrompt,
|
|
2034
2565
|
timestamp: Date.now(),
|
|
2035
2566
|
speaker,
|
|
2036
2567
|
});
|
|
2037
2568
|
|
|
2038
2569
|
// Record event
|
|
2039
|
-
recordCallEvent(this.callSessionId,
|
|
2570
|
+
recordCallEvent(this.callSessionId, "caller_spoke", {
|
|
2040
2571
|
transcript: msg.voicePrompt,
|
|
2041
2572
|
lang: msg.lang,
|
|
2042
2573
|
speakerId: speaker.speakerId,
|
|
@@ -2050,7 +2581,12 @@ export class RelayConnection {
|
|
|
2050
2581
|
// User message persistence is handled by the session pipeline
|
|
2051
2582
|
// (voice-session-bridge -> session.persistUserMessage) so we only
|
|
2052
2583
|
// need to fire the transcript notifier for UI subscribers here.
|
|
2053
|
-
fireCallTranscriptNotifier(
|
|
2584
|
+
fireCallTranscriptNotifier(
|
|
2585
|
+
session.conversationId,
|
|
2586
|
+
this.callSessionId,
|
|
2587
|
+
"caller",
|
|
2588
|
+
msg.voicePrompt,
|
|
2589
|
+
);
|
|
2054
2590
|
}
|
|
2055
2591
|
|
|
2056
2592
|
// Route to controller for session-backed response
|
|
@@ -2065,24 +2601,35 @@ export class RelayConnection {
|
|
|
2065
2601
|
try {
|
|
2066
2602
|
await conversationStore.addMessage(
|
|
2067
2603
|
session.conversationId,
|
|
2068
|
-
|
|
2069
|
-
JSON.stringify([{ type:
|
|
2070
|
-
{
|
|
2604
|
+
"user",
|
|
2605
|
+
JSON.stringify([{ type: "text", text: msg.voicePrompt }]),
|
|
2606
|
+
{
|
|
2607
|
+
userMessageChannel: "voice",
|
|
2608
|
+
assistantMessageChannel: "voice",
|
|
2609
|
+
userMessageInterface: "voice",
|
|
2610
|
+
assistantMessageInterface: "voice",
|
|
2611
|
+
},
|
|
2071
2612
|
);
|
|
2072
2613
|
} catch (err) {
|
|
2073
2614
|
// Best-effort — don't let persistence failures prevent the hold
|
|
2074
2615
|
// response from reaching the caller.
|
|
2075
|
-
log.warn(
|
|
2616
|
+
log.warn(
|
|
2617
|
+
{ err, callSessionId: this.callSessionId },
|
|
2618
|
+
"Failed to persist early caller utterance",
|
|
2619
|
+
);
|
|
2076
2620
|
}
|
|
2077
2621
|
}
|
|
2078
|
-
this.sendTextToken(
|
|
2622
|
+
this.sendTextToken("I'm still setting up. Please hold.", true);
|
|
2079
2623
|
}
|
|
2080
2624
|
}
|
|
2081
2625
|
|
|
2082
2626
|
private handleInterrupt(msg: RelayInterruptMessage): void {
|
|
2083
2627
|
log.info(
|
|
2084
|
-
{
|
|
2085
|
-
|
|
2628
|
+
{
|
|
2629
|
+
callSessionId: this.callSessionId,
|
|
2630
|
+
utteranceUntilInterrupt: msg.utteranceUntilInterrupt,
|
|
2631
|
+
},
|
|
2632
|
+
"Caller interrupted assistant",
|
|
2086
2633
|
);
|
|
2087
2634
|
|
|
2088
2635
|
// Abort any in-flight processing
|
|
@@ -2096,32 +2643,41 @@ export class RelayConnection {
|
|
|
2096
2643
|
}
|
|
2097
2644
|
|
|
2098
2645
|
private handleDtmf(msg: RelayDtmfMessage): void {
|
|
2099
|
-
if (this.connectionState ===
|
|
2646
|
+
if (this.connectionState === "disconnecting") {
|
|
2100
2647
|
return;
|
|
2101
2648
|
}
|
|
2102
2649
|
|
|
2103
2650
|
// Ignore DTMF during name capture and guardian decision wait
|
|
2104
|
-
if (
|
|
2651
|
+
if (
|
|
2652
|
+
this.connectionState === "awaiting_name" ||
|
|
2653
|
+
this.connectionState === "awaiting_guardian_decision"
|
|
2654
|
+
) {
|
|
2105
2655
|
return;
|
|
2106
2656
|
}
|
|
2107
2657
|
|
|
2108
2658
|
log.info(
|
|
2109
2659
|
{ callSessionId: this.callSessionId, digit: msg.digit },
|
|
2110
|
-
|
|
2660
|
+
"DTMF digit received",
|
|
2111
2661
|
);
|
|
2112
2662
|
|
|
2113
|
-
recordCallEvent(this.callSessionId,
|
|
2663
|
+
recordCallEvent(this.callSessionId, "caller_spoke", {
|
|
2114
2664
|
dtmfDigit: msg.digit,
|
|
2115
2665
|
});
|
|
2116
2666
|
|
|
2117
2667
|
// If guardian verification (inbound or outbound) is pending, accumulate
|
|
2118
2668
|
// digits and validate against the challenge via the guardian service.
|
|
2119
|
-
if (
|
|
2669
|
+
if (
|
|
2670
|
+
this.connectionState === "verification_pending" &&
|
|
2671
|
+
this.guardianVerificationActive
|
|
2672
|
+
) {
|
|
2120
2673
|
this.dtmfBuffer += msg.digit;
|
|
2121
2674
|
|
|
2122
2675
|
if (this.dtmfBuffer.length >= this.verificationCodeLength) {
|
|
2123
|
-
const enteredCode = this.dtmfBuffer.slice(
|
|
2124
|
-
|
|
2676
|
+
const enteredCode = this.dtmfBuffer.slice(
|
|
2677
|
+
0,
|
|
2678
|
+
this.verificationCodeLength,
|
|
2679
|
+
);
|
|
2680
|
+
this.dtmfBuffer = "";
|
|
2125
2681
|
this.attemptGuardianCodeVerification(enteredCode);
|
|
2126
2682
|
}
|
|
2127
2683
|
return;
|
|
@@ -2129,39 +2685,63 @@ export class RelayConnection {
|
|
|
2129
2685
|
|
|
2130
2686
|
// If invite redemption is pending, accumulate digits and validate
|
|
2131
2687
|
// the code against the caller's active voice invite.
|
|
2132
|
-
if (
|
|
2688
|
+
if (
|
|
2689
|
+
this.connectionState === "verification_pending" &&
|
|
2690
|
+
this.inviteRedemptionActive
|
|
2691
|
+
) {
|
|
2133
2692
|
this.dtmfBuffer += msg.digit;
|
|
2134
2693
|
|
|
2135
2694
|
if (this.dtmfBuffer.length >= this.inviteRedemptionCodeLength) {
|
|
2136
|
-
const enteredCode = this.dtmfBuffer.slice(
|
|
2137
|
-
|
|
2695
|
+
const enteredCode = this.dtmfBuffer.slice(
|
|
2696
|
+
0,
|
|
2697
|
+
this.inviteRedemptionCodeLength,
|
|
2698
|
+
);
|
|
2699
|
+
this.dtmfBuffer = "";
|
|
2138
2700
|
this.attemptInviteCodeRedemption(enteredCode);
|
|
2139
2701
|
}
|
|
2140
2702
|
return;
|
|
2141
2703
|
}
|
|
2142
2704
|
|
|
2143
2705
|
// If outbound callee verification is pending, accumulate digits and check the code
|
|
2144
|
-
if (
|
|
2706
|
+
if (
|
|
2707
|
+
this.connectionState === "verification_pending" &&
|
|
2708
|
+
this.verificationCode
|
|
2709
|
+
) {
|
|
2145
2710
|
this.dtmfBuffer += msg.digit;
|
|
2146
2711
|
|
|
2147
2712
|
if (this.dtmfBuffer.length >= this.verificationCodeLength) {
|
|
2148
|
-
const enteredCode = this.dtmfBuffer.slice(
|
|
2149
|
-
|
|
2713
|
+
const enteredCode = this.dtmfBuffer.slice(
|
|
2714
|
+
0,
|
|
2715
|
+
this.verificationCodeLength,
|
|
2716
|
+
);
|
|
2717
|
+
this.dtmfBuffer = "";
|
|
2150
2718
|
|
|
2151
2719
|
if (enteredCode === this.verificationCode) {
|
|
2152
2720
|
// Verification succeeded
|
|
2153
|
-
this.connectionState =
|
|
2721
|
+
this.connectionState = "connected";
|
|
2154
2722
|
this.verificationCode = null;
|
|
2155
2723
|
this.verificationAttempts = 0;
|
|
2156
2724
|
|
|
2157
|
-
recordCallEvent(
|
|
2158
|
-
|
|
2725
|
+
recordCallEvent(
|
|
2726
|
+
this.callSessionId,
|
|
2727
|
+
"callee_verification_succeeded",
|
|
2728
|
+
{},
|
|
2729
|
+
);
|
|
2730
|
+
log.info(
|
|
2731
|
+
{ callSessionId: this.callSessionId },
|
|
2732
|
+
"Callee verification succeeded",
|
|
2733
|
+
);
|
|
2159
2734
|
|
|
2160
2735
|
// Proceed to the normal call flow
|
|
2161
2736
|
if (this.controller) {
|
|
2162
|
-
this.controller
|
|
2163
|
-
|
|
2164
|
-
|
|
2737
|
+
this.controller
|
|
2738
|
+
.startInitialGreeting()
|
|
2739
|
+
.catch((err) =>
|
|
2740
|
+
log.error(
|
|
2741
|
+
{ err, callSessionId: this.callSessionId },
|
|
2742
|
+
"Failed to start initial outbound greeting after verification",
|
|
2743
|
+
),
|
|
2744
|
+
);
|
|
2165
2745
|
}
|
|
2166
2746
|
} else {
|
|
2167
2747
|
// Verification failed for this attempt
|
|
@@ -2169,48 +2749,85 @@ export class RelayConnection {
|
|
|
2169
2749
|
|
|
2170
2750
|
if (this.verificationAttempts >= this.verificationMaxAttempts) {
|
|
2171
2751
|
// Max attempts reached — end the call
|
|
2172
|
-
recordCallEvent(this.callSessionId,
|
|
2752
|
+
recordCallEvent(this.callSessionId, "callee_verification_failed", {
|
|
2173
2753
|
attempts: this.verificationAttempts,
|
|
2174
2754
|
});
|
|
2175
|
-
log.warn(
|
|
2755
|
+
log.warn(
|
|
2756
|
+
{
|
|
2757
|
+
callSessionId: this.callSessionId,
|
|
2758
|
+
attempts: this.verificationAttempts,
|
|
2759
|
+
},
|
|
2760
|
+
"Callee verification failed — max attempts reached",
|
|
2761
|
+
);
|
|
2176
2762
|
|
|
2177
|
-
this.sendTextToken(
|
|
2763
|
+
this.sendTextToken("Verification failed. Goodbye.", true);
|
|
2178
2764
|
|
|
2179
2765
|
// Mark failed immediately so a relay close during the goodbye TTS
|
|
2180
2766
|
// window cannot race this into a terminal "completed" status.
|
|
2181
2767
|
updateCallSession(this.callSessionId, {
|
|
2182
|
-
status:
|
|
2768
|
+
status: "failed",
|
|
2183
2769
|
endedAt: Date.now(),
|
|
2184
|
-
lastError:
|
|
2770
|
+
lastError: "Callee verification failed — max attempts exceeded",
|
|
2185
2771
|
});
|
|
2186
2772
|
|
|
2187
2773
|
const session = getCallSession(this.callSessionId);
|
|
2188
2774
|
if (session) {
|
|
2189
2775
|
expirePendingQuestions(this.callSessionId);
|
|
2190
|
-
persistCallCompletionMessage(
|
|
2191
|
-
|
|
2776
|
+
persistCallCompletionMessage(
|
|
2777
|
+
session.conversationId,
|
|
2778
|
+
this.callSessionId,
|
|
2779
|
+
).catch((err) => {
|
|
2780
|
+
log.error(
|
|
2781
|
+
{
|
|
2782
|
+
err,
|
|
2783
|
+
conversationId: session.conversationId,
|
|
2784
|
+
callSessionId: this.callSessionId,
|
|
2785
|
+
},
|
|
2786
|
+
"Failed to persist call completion message",
|
|
2787
|
+
);
|
|
2192
2788
|
});
|
|
2193
|
-
fireCallCompletionNotifier(
|
|
2789
|
+
fireCallCompletionNotifier(
|
|
2790
|
+
session.conversationId,
|
|
2791
|
+
this.callSessionId,
|
|
2792
|
+
);
|
|
2194
2793
|
if (session.initiatedFromConversationId) {
|
|
2195
|
-
addPointerMessage(
|
|
2196
|
-
|
|
2197
|
-
|
|
2198
|
-
|
|
2794
|
+
addPointerMessage(
|
|
2795
|
+
session.initiatedFromConversationId,
|
|
2796
|
+
"failed",
|
|
2797
|
+
session.toNumber,
|
|
2798
|
+
{
|
|
2799
|
+
reason: "Callee verification failed",
|
|
2800
|
+
},
|
|
2801
|
+
).catch((err) => {
|
|
2802
|
+
log.warn(
|
|
2803
|
+
{
|
|
2804
|
+
conversationId: session.initiatedFromConversationId,
|
|
2805
|
+
err,
|
|
2806
|
+
},
|
|
2807
|
+
"Skipping pointer write — origin conversation may no longer exist",
|
|
2808
|
+
);
|
|
2199
2809
|
});
|
|
2200
2810
|
}
|
|
2201
2811
|
}
|
|
2202
2812
|
|
|
2203
2813
|
// End the call with failed status after TTS plays
|
|
2204
2814
|
setTimeout(() => {
|
|
2205
|
-
this.endSession(
|
|
2815
|
+
this.endSession("Verification failed");
|
|
2206
2816
|
}, getTtsPlaybackDelayMs());
|
|
2207
2817
|
} else {
|
|
2208
2818
|
// Allow another attempt
|
|
2209
2819
|
log.info(
|
|
2210
|
-
{
|
|
2211
|
-
|
|
2820
|
+
{
|
|
2821
|
+
callSessionId: this.callSessionId,
|
|
2822
|
+
attempt: this.verificationAttempts,
|
|
2823
|
+
maxAttempts: this.verificationMaxAttempts,
|
|
2824
|
+
},
|
|
2825
|
+
"Callee verification attempt failed — retrying",
|
|
2826
|
+
);
|
|
2827
|
+
this.sendTextToken(
|
|
2828
|
+
"That code was incorrect. Please try again.",
|
|
2829
|
+
true,
|
|
2212
2830
|
);
|
|
2213
|
-
this.sendTextToken('That code was incorrect. Please try again.', true);
|
|
2214
2831
|
}
|
|
2215
2832
|
}
|
|
2216
2833
|
}
|
|
@@ -2220,10 +2837,10 @@ export class RelayConnection {
|
|
|
2220
2837
|
private handleError(msg: RelayErrorMessage): void {
|
|
2221
2838
|
log.error(
|
|
2222
2839
|
{ callSessionId: this.callSessionId, description: msg.description },
|
|
2223
|
-
|
|
2840
|
+
"ConversationRelay error",
|
|
2224
2841
|
);
|
|
2225
2842
|
|
|
2226
|
-
recordCallEvent(this.callSessionId,
|
|
2843
|
+
recordCallEvent(this.callSessionId, "call_failed", {
|
|
2227
2844
|
error: msg.description,
|
|
2228
2845
|
});
|
|
2229
2846
|
}
|