@vellumai/assistant 0.3.4 → 0.3.6
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/Dockerfile +2 -0
- package/README.md +88 -2
- package/eslint.config.mjs +31 -0
- package/package.json +1 -1
- package/scripts/ipc/check-swift-decoder-drift.ts +4 -1
- package/scripts/ipc/generate-swift.ts +31 -2
- package/src/__tests__/__snapshots__/ipc-snapshot.test.ts.snap +438 -1
- package/src/__tests__/approval-conversation-turn.test.ts +214 -0
- package/src/__tests__/approval-hardcoded-copy-guard.test.ts +41 -0
- package/src/__tests__/approval-message-composer.test.ts +253 -0
- package/src/__tests__/browser-manager.test.ts +1 -0
- package/src/__tests__/call-conversation-messages.test.ts +130 -0
- package/src/__tests__/call-domain.test.ts +12 -2
- package/src/__tests__/call-orchestrator.test.ts +799 -249
- package/src/__tests__/call-pointer-messages.test.ts +148 -0
- package/src/__tests__/call-recovery.test.ts +3 -0
- package/src/__tests__/call-routes-http.test.ts +32 -2
- package/src/__tests__/call-store.test.ts +3 -0
- package/src/__tests__/channel-approval-routes.test.ts +1277 -98
- package/src/__tests__/channel-approval.test.ts +37 -0
- package/src/__tests__/channel-approvals.test.ts +36 -50
- package/src/__tests__/channel-guardian.test.ts +630 -22
- package/src/__tests__/channel-readiness-service.test.ts +324 -0
- package/src/__tests__/checker.test.ts +14 -7
- package/src/__tests__/clarification-resolver.test.ts +44 -24
- package/src/__tests__/commit-message-enrichment-service.test.ts +9 -4
- package/src/__tests__/computer-use-session-working-dir.test.ts +8 -0
- package/src/__tests__/config-schema.test.ts +14 -8
- package/src/__tests__/context-window-manager.test.ts +30 -2
- package/src/__tests__/contradiction-checker.test.ts +20 -5
- package/src/__tests__/credential-security-invariants.test.ts +7 -2
- package/src/__tests__/daemon-lifecycle.test.ts +13 -12
- package/src/__tests__/db-migration-rollback.test.ts +752 -0
- package/src/__tests__/dictation-mode-detection.test.ts +63 -0
- package/src/__tests__/dynamic-skill-workflow-prompt.test.ts +2 -0
- package/src/__tests__/entity-search.test.ts +615 -0
- package/src/__tests__/fuzzy-match-property.test.ts +5 -5
- package/src/__tests__/guardian-action-store.test.ts +123 -0
- package/src/__tests__/guardian-action-sweep.test.ts +277 -0
- package/src/__tests__/guardian-dispatch.test.ts +389 -0
- package/src/__tests__/guardian-question-copy.test.ts +47 -0
- package/src/__tests__/handlers-telegram-config.test.ts +4 -2
- package/src/__tests__/handlers-twilio-config.test.ts +533 -0
- package/src/__tests__/intent-routing.test.ts +2 -0
- package/src/__tests__/ipc-snapshot.test.ts +291 -1
- package/src/__tests__/memory-upsert-concurrency.test.ts +828 -0
- package/src/__tests__/messaging-send-tool.test.ts +65 -0
- package/src/__tests__/model-intents.test.ts +96 -0
- package/src/__tests__/no-direct-anthropic-sdk-imports.test.ts +42 -0
- package/src/__tests__/oauth2-gateway-transport.test.ts +130 -0
- package/src/__tests__/onboarding-starter-tasks.test.ts +2 -0
- package/src/__tests__/provider-commit-message-generator.test.ts +89 -13
- package/src/__tests__/provider-error-scenarios.test.ts +621 -0
- package/src/__tests__/provider-fail-open-selection.test.ts +119 -0
- package/src/__tests__/qdrant-manager.test.ts +27 -20
- package/src/__tests__/relay-server.test.ts +779 -40
- package/src/__tests__/run-orchestrator-assistant-events.test.ts +6 -0
- package/src/__tests__/run-orchestrator.test.ts +42 -4
- package/src/__tests__/runtime-runs-http.test.ts +17 -1
- package/src/__tests__/runtime-runs.test.ts +16 -0
- package/src/__tests__/schedule-store.test.ts +18 -4
- package/src/__tests__/scheduler-recurrence.test.ts +13 -4
- package/src/__tests__/session-abort-tool-results.test.ts +6 -0
- package/src/__tests__/session-agent-loop.test.ts +857 -0
- package/src/__tests__/session-conflict-gate.test.ts +6 -0
- package/src/__tests__/session-pre-run-repair.test.ts +6 -0
- package/src/__tests__/session-profile-injection.test.ts +6 -0
- package/src/__tests__/session-provider-retry-repair.test.ts +6 -0
- package/src/__tests__/session-queue.test.ts +6 -0
- package/src/__tests__/session-runtime-assembly.test.ts +321 -13
- package/src/__tests__/session-slash-known.test.ts +6 -0
- package/src/__tests__/session-slash-queue.test.ts +6 -0
- package/src/__tests__/session-slash-unknown.test.ts +6 -0
- package/src/__tests__/session-surfaces-task-progress.test.ts +2 -0
- package/src/__tests__/session-tool-setup-app-refresh.test.ts +1 -0
- package/src/__tests__/session-tool-setup-memory-scope.test.ts +1 -0
- package/src/__tests__/session-tool-setup-side-effect-flag.test.ts +1 -0
- package/src/__tests__/session-workspace-injection.test.ts +6 -0
- package/src/__tests__/session-workspace-tool-tracking.test.ts +6 -0
- package/src/__tests__/skills.test.ts +2 -0
- package/src/__tests__/sms-messaging-provider.test.ts +126 -0
- package/src/__tests__/starter-task-flow.test.ts +2 -0
- package/src/__tests__/swarm-dag-pathological.test.ts +535 -0
- package/src/__tests__/system-prompt.test.ts +2 -0
- package/src/__tests__/task-management-tools.test.ts +2 -2
- package/src/__tests__/task-runner.test.ts +14 -4
- package/src/__tests__/terminal-tools.test.ts +25 -19
- package/src/__tests__/tool-execution-abort-cleanup.test.ts +545 -0
- package/src/__tests__/tool-executor-shell-integration.test.ts +11 -11
- package/src/__tests__/tool-executor.test.ts +23 -24
- package/src/__tests__/trust-store.test.ts +3 -3
- package/src/__tests__/twilio-rest.test.ts +29 -0
- package/src/__tests__/twilio-routes-elevenlabs.test.ts +3 -0
- package/src/__tests__/twilio-routes-twiml.test.ts +11 -0
- package/src/__tests__/twilio-routes.test.ts +167 -11
- package/src/__tests__/twitter-cli-error-shaping.test.ts +2 -2
- package/src/__tests__/user-reference.test.ts +2 -0
- package/src/__tests__/voice-quality.test.ts +222 -0
- package/src/__tests__/web-search.test.ts +46 -30
- package/src/__tests__/work-item-output.test.ts +110 -0
- package/src/agent/loop.ts +1 -1
- package/src/agent-heartbeat/agent-heartbeat-service.ts +2 -10
- package/src/amazon/client.ts +1418 -0
- package/src/amazon/request-extractor.ts +135 -0
- package/src/amazon/session.ts +109 -0
- package/src/autonomy/autonomy-store.ts +5 -5
- package/src/browser-extension-relay/client.ts +124 -0
- package/src/browser-extension-relay/protocol.ts +63 -0
- package/src/browser-extension-relay/server.ts +177 -0
- package/src/bundler/app-bundler.ts +3 -3
- package/src/bundler/bundle-signer.ts +1 -1
- package/src/bundler/signature-verifier.ts +1 -1
- package/src/calls/call-conversation-messages.ts +33 -0
- package/src/calls/call-domain.ts +114 -10
- package/src/calls/call-orchestrator.ts +268 -59
- package/src/calls/call-pointer-messages.ts +53 -0
- package/src/calls/call-recovery.ts +3 -8
- package/src/calls/call-store.ts +69 -87
- package/src/calls/elevenlabs-config.ts +3 -2
- package/src/calls/guardian-action-sweep.ts +105 -0
- package/src/calls/guardian-dispatch.ts +203 -0
- package/src/calls/guardian-question-copy.ts +133 -0
- package/src/calls/relay-server.ts +466 -8
- package/src/calls/speaker-identification.ts +1 -1
- package/src/calls/twilio-config.ts +22 -14
- package/src/calls/twilio-provider.ts +6 -4
- package/src/calls/twilio-rest.ts +308 -7
- package/src/calls/twilio-routes.ts +65 -12
- package/src/calls/types.ts +3 -1
- package/src/channels/types.ts +25 -0
- package/src/cli/amazon.ts +815 -0
- package/src/cli/config-commands.ts +2 -2
- package/src/cli/core-commands.ts +4 -3
- package/src/cli/influencer.ts +244 -0
- package/src/cli/map.ts +89 -6
- package/src/cli.ts +1 -1
- package/src/config/agent-schema.ts +171 -0
- package/src/config/bundled-skills/amazon/SKILL.md +127 -0
- package/src/config/bundled-skills/amazon/icon.svg +13 -0
- package/src/config/bundled-skills/api-mapping/SKILL.md +78 -0
- package/src/config/bundled-skills/browser/SKILL.md +1 -0
- package/src/config/bundled-skills/browser/TOOLS.json +17 -0
- package/src/config/bundled-skills/browser/tools/browser-wait-for-download.ts +25 -0
- package/src/config/bundled-skills/doordash/SKILL.md +51 -51
- package/src/config/bundled-skills/email-setup/SKILL.md +14 -5
- package/src/config/bundled-skills/google-oauth-setup/SKILL.md +183 -0
- package/src/config/bundled-skills/influencer/SKILL.md +144 -0
- package/src/config/bundled-skills/knowledge-graph/SKILL.md +15 -0
- package/src/config/bundled-skills/knowledge-graph/TOOLS.json +56 -0
- package/src/config/bundled-skills/knowledge-graph/tools/graph-query.ts +185 -0
- package/src/config/bundled-skills/macos-automation/icon.svg +12 -0
- package/src/config/bundled-skills/media-processing/SKILL.md +176 -0
- package/src/config/bundled-skills/media-processing/TOOLS.json +230 -0
- package/src/config/bundled-skills/media-processing/__tests__/concurrency-pool.test.ts +77 -0
- package/src/config/bundled-skills/media-processing/__tests__/cost-tracker.test.ts +69 -0
- package/src/config/bundled-skills/media-processing/__tests__/preprocess.test.ts +303 -0
- package/src/config/bundled-skills/media-processing/services/concurrency-pool.ts +55 -0
- package/src/config/bundled-skills/media-processing/services/cost-tracker.ts +86 -0
- package/src/config/bundled-skills/media-processing/services/gemini-map.ts +339 -0
- package/src/config/bundled-skills/media-processing/services/preprocess.ts +551 -0
- package/src/config/bundled-skills/media-processing/services/processing-pipeline.ts +259 -0
- package/src/config/bundled-skills/media-processing/services/reduce.ts +197 -0
- package/src/config/bundled-skills/media-processing/tools/analyze-keyframes.ts +136 -0
- package/src/config/bundled-skills/media-processing/tools/extract-keyframes.ts +59 -0
- package/src/config/bundled-skills/media-processing/tools/generate-clip.ts +195 -0
- package/src/config/bundled-skills/media-processing/tools/ingest-media.ts +197 -0
- package/src/config/bundled-skills/media-processing/tools/media-diagnostics.ts +143 -0
- package/src/config/bundled-skills/media-processing/tools/media-status.ts +75 -0
- package/src/config/bundled-skills/media-processing/tools/query-media-events.ts +65 -0
- package/src/config/bundled-skills/messaging/SKILL.md +33 -8
- package/src/config/bundled-skills/messaging/tools/messaging-analyze-style.ts +4 -7
- package/src/config/bundled-skills/messaging/tools/messaging-reply.ts +2 -1
- package/src/config/bundled-skills/messaging/tools/messaging-send.ts +5 -1
- package/src/config/bundled-skills/phone-calls/SKILL.md +88 -23
- package/src/config/bundled-skills/twitter/SKILL.md +19 -3
- package/src/config/bundled-skills/twitter/icon.svg +14 -0
- package/src/config/bundled-tool-registry.ts +310 -0
- package/src/config/calls-schema.ts +181 -0
- package/src/config/core-schema.ts +309 -0
- package/src/config/defaults.ts +28 -3
- package/src/config/env-registry.ts +162 -0
- package/src/config/env.ts +175 -0
- package/src/config/loader.ts +6 -6
- package/src/config/memory-schema.ts +528 -0
- package/src/config/sandbox-schema.ts +55 -0
- package/src/config/schema.ts +158 -1133
- package/src/config/skill-state.ts +1 -1
- package/src/config/skills-schema.ts +32 -0
- package/src/config/skills.ts +35 -24
- package/src/config/system-prompt.ts +131 -56
- package/src/config/templates/IDENTITY.md +2 -2
- package/src/config/templates/SOUL.md +1 -1
- package/src/config/types.ts +1 -0
- package/src/config/user-reference.ts +4 -9
- package/src/config/vellum-skills/catalog.json +6 -7
- package/src/config/vellum-skills/chatgpt-import/tools/chatgpt-import.ts +5 -1
- package/src/config/vellum-skills/slack-oauth-setup/SKILL.md +4 -3
- package/src/config/vellum-skills/sms-setup/SKILL.md +216 -0
- package/src/config/vellum-skills/twilio-setup/SKILL.md +40 -8
- package/src/context/window-manager.ts +27 -7
- package/src/daemon/approval-generators.ts +186 -0
- package/src/daemon/approved-devices-store.ts +140 -0
- package/src/daemon/assistant-attachments.ts +1 -1
- package/src/daemon/classifier.ts +35 -32
- package/src/daemon/config-watcher.ts +1 -1
- package/src/daemon/daemon-control.ts +217 -0
- package/src/daemon/handlers/apps.ts +2 -3
- package/src/daemon/handlers/config-channels.ts +158 -0
- package/src/daemon/handlers/config-inbox.ts +540 -0
- package/src/daemon/handlers/config-ingress.ts +231 -0
- package/src/daemon/handlers/config-integrations.ts +258 -0
- package/src/daemon/handlers/config-model.ts +143 -0
- package/src/daemon/handlers/config-parental.ts +163 -0
- package/src/daemon/handlers/config-scheduling.ts +172 -0
- package/src/daemon/handlers/config-slack.ts +92 -0
- package/src/daemon/handlers/config-telegram.ts +301 -0
- package/src/daemon/handlers/config-tools.ts +177 -0
- package/src/daemon/handlers/config-trust.ts +104 -0
- package/src/daemon/handlers/config-twilio.ts +1080 -0
- package/src/daemon/handlers/config.ts +53 -1689
- package/src/daemon/handlers/diagnostics.ts +1 -1
- package/src/daemon/handlers/dictation.ts +180 -0
- package/src/daemon/handlers/documents.ts +18 -32
- package/src/daemon/handlers/identity.ts +14 -23
- package/src/daemon/handlers/index.ts +11 -0
- package/src/daemon/handlers/misc.ts +3 -5
- package/src/daemon/handlers/pairing.ts +98 -0
- package/src/daemon/handlers/sessions.ts +56 -5
- package/src/daemon/handlers/shared.ts +6 -1
- package/src/daemon/handlers/skills.ts +1 -1
- package/src/daemon/handlers/twitter-auth.ts +2 -0
- package/src/daemon/handlers/work-items.ts +17 -9
- package/src/daemon/handlers/workspace-files.ts +4 -3
- package/src/daemon/install-cli-launchers.ts +113 -0
- package/src/daemon/ipc-contract/apps.ts +356 -0
- package/src/daemon/ipc-contract/browser.ts +74 -0
- package/src/daemon/ipc-contract/computer-use.ts +151 -0
- package/src/daemon/ipc-contract/diagnostics.ts +56 -0
- package/src/daemon/ipc-contract/documents.ts +74 -0
- package/src/daemon/ipc-contract/inbox.ts +209 -0
- package/src/daemon/ipc-contract/integrations.ts +284 -0
- package/src/daemon/ipc-contract/memory.ts +48 -0
- package/src/daemon/ipc-contract/messages.ts +211 -0
- package/src/daemon/ipc-contract/pairing.ts +45 -0
- package/src/daemon/ipc-contract/parental-control.ts +95 -0
- package/src/daemon/ipc-contract/schedules.ts +97 -0
- package/src/daemon/ipc-contract/sessions.ts +315 -0
- package/src/daemon/ipc-contract/shared.ts +42 -0
- package/src/daemon/ipc-contract/skills.ts +120 -0
- package/src/daemon/ipc-contract/subagents.ts +58 -0
- package/src/daemon/ipc-contract/surfaces.ts +250 -0
- package/src/daemon/ipc-contract/trust.ts +60 -0
- package/src/daemon/ipc-contract/work-items.ts +225 -0
- package/src/daemon/ipc-contract/workspace.ts +113 -0
- package/src/daemon/ipc-contract-inventory.json +70 -0
- package/src/daemon/ipc-contract-inventory.ts +55 -29
- package/src/daemon/ipc-contract.ts +229 -2426
- package/src/daemon/ipc-protocol.ts +1 -1
- package/src/daemon/ipc-validate.ts +7 -0
- package/src/daemon/lifecycle.ts +97 -377
- package/src/daemon/pairing-store.ts +177 -0
- package/src/daemon/providers-setup.ts +43 -0
- package/src/daemon/ride-shotgun-handler.ts +68 -3
- package/src/daemon/server.ts +66 -46
- package/src/daemon/session-agent-loop-handlers.ts +421 -0
- package/src/daemon/session-agent-loop.ts +117 -275
- package/src/daemon/session-dynamic-profile.ts +1 -1
- package/src/daemon/session-history.ts +1 -1
- package/src/daemon/session-media-retry.ts +1 -1
- package/src/daemon/session-messaging.ts +37 -2
- package/src/daemon/session-notifiers.ts +5 -25
- package/src/daemon/session-process.ts +99 -59
- package/src/daemon/session-queue-manager.ts +96 -4
- package/src/daemon/session-runtime-assembly.ts +199 -10
- package/src/daemon/session-surfaces.ts +19 -4
- package/src/daemon/session-tool-setup.ts +30 -30
- package/src/daemon/session-workspace.ts +1 -1
- package/src/daemon/session.ts +35 -2
- package/src/daemon/shutdown-handlers.ts +122 -0
- package/src/daemon/trace-emitter.ts +1 -1
- package/src/daemon/watch-handler.ts +36 -33
- package/src/doordash/cart-queries.ts +787 -0
- package/src/doordash/client.ts +144 -127
- package/src/doordash/order-queries.ts +85 -0
- package/src/doordash/queries.ts +10 -1308
- package/src/doordash/search-queries.ts +203 -0
- package/src/doordash/session.ts +3 -2
- package/src/doordash/store-queries.ts +246 -0
- package/src/doordash/types.ts +367 -0
- package/src/email/providers/agentmail.ts +2 -1
- package/src/email/providers/index.ts +3 -2
- package/src/email/service.ts +3 -2
- package/src/errors.ts +43 -0
- package/src/home-base/prebuilt/seed.ts +1 -1
- package/src/hooks/cli.ts +6 -5
- package/src/hooks/config.ts +6 -8
- package/src/hooks/discovery.ts +6 -5
- package/src/hooks/manager.ts +4 -3
- package/src/hooks/runner.ts +2 -2
- package/src/hooks/templates.ts +5 -5
- package/src/inbound/public-ingress-urls.ts +6 -4
- package/src/index.ts +4 -2
- package/src/influencer/client.ts +1104 -0
- package/src/instrument.ts +4 -3
- package/src/logfire.ts +4 -3
- package/src/memory/admin.ts +25 -35
- package/src/memory/attachments-store.ts +4 -7
- package/src/memory/channel-delivery-store.ts +30 -1
- package/src/memory/channel-guardian-store.ts +202 -2
- package/src/memory/clarification-resolver.ts +37 -33
- package/src/memory/conflict-store.ts +67 -61
- package/src/memory/contradiction-checker.ts +141 -117
- package/src/memory/conversation-store.ts +335 -51
- package/src/memory/db-connection.ts +27 -4
- package/src/memory/db-init.ts +265 -4
- package/src/memory/db.ts +14 -1
- package/src/memory/embedding-backend.ts +27 -5
- package/src/memory/embedding-ollama.ts +2 -1
- package/src/memory/entity-extractor.ts +38 -35
- package/src/memory/guardian-action-store.ts +430 -0
- package/src/memory/inbox-escalation-projection.ts +59 -0
- package/src/memory/inbox-thread-store.ts +218 -0
- package/src/memory/ingress-invite-store.ts +338 -0
- package/src/memory/ingress-member-store.ts +350 -0
- package/src/memory/items-extractor.ts +91 -97
- package/src/memory/job-handlers/index-maintenance.ts +3 -3
- package/src/memory/job-handlers/media-processing.ts +69 -0
- package/src/memory/job-handlers/summarization.ts +32 -26
- package/src/memory/job-utils.ts +3 -10
- package/src/memory/jobs-store.ts +8 -10
- package/src/memory/jobs-worker.ts +55 -36
- package/src/memory/media-store.ts +759 -0
- package/src/memory/migrations/001-job-deferrals.ts +45 -0
- package/src/memory/migrations/002-tool-invocations-fk.ts +43 -0
- package/src/memory/migrations/003-memory-fts-backfill.ts +24 -0
- package/src/memory/migrations/004-entity-relation-dedup.ts +87 -0
- package/src/memory/migrations/005-fingerprint-scope-unique.ts +80 -0
- package/src/memory/migrations/006-scope-salted-fingerprints.ts +62 -0
- package/src/memory/migrations/007-assistant-id-to-self.ts +254 -0
- package/src/memory/migrations/008-remove-assistant-id-columns.ts +208 -0
- package/src/memory/migrations/009-llm-usage-events-drop-assistant-id.ts +83 -0
- package/src/memory/migrations/010-ext-conv-bindings-channel-chat-unique.ts +56 -0
- package/src/memory/migrations/011-call-sessions-provider-sid-dedup.ts +63 -0
- package/src/memory/migrations/012-call-sessions-add-initiated-from.ts +19 -0
- package/src/memory/migrations/013-guardian-action-tables.ts +68 -0
- package/src/memory/migrations/014-backfill-inbox-thread-state.ts +76 -0
- package/src/memory/migrations/015-drop-active-search-index.ts +27 -0
- package/src/memory/migrations/016-memory-segments-indexes.ts +11 -0
- package/src/memory/migrations/017-memory-items-indexes.ts +10 -0
- package/src/memory/migrations/018-remaining-table-indexes.ts +13 -0
- package/src/memory/migrations/index.ts +24 -0
- package/src/memory/migrations/registry.ts +79 -0
- package/src/memory/migrations/validate-migration-state.ts +69 -0
- package/src/memory/qdrant-manager.ts +49 -8
- package/src/memory/query-builder.ts +1 -1
- package/src/memory/raw-query.ts +119 -0
- package/src/memory/recall-cache.ts +4 -1
- package/src/memory/retriever.ts +165 -47
- package/src/memory/schema-migration.ts +25 -984
- package/src/memory/schema.ts +228 -7
- package/src/memory/search/entity.ts +205 -31
- package/src/memory/search/lexical.ts +81 -52
- package/src/memory/search/ranking.ts +27 -23
- package/src/memory/search/semantic.ts +157 -19
- package/src/memory/search/types.ts +24 -0
- package/src/memory/shared-app-links-store.ts +4 -5
- package/src/memory/validation.ts +19 -0
- package/src/messaging/draft-store.ts +5 -6
- package/src/messaging/provider-types.ts +2 -0
- package/src/messaging/providers/sms/adapter.ts +201 -0
- package/src/messaging/providers/sms/client.ts +93 -0
- package/src/messaging/providers/sms/types.ts +7 -0
- package/src/messaging/providers/telegram-bot/adapter.ts +2 -5
- package/src/messaging/providers/whatsapp/adapter.ts +136 -0
- package/src/messaging/providers/whatsapp/client.ts +67 -0
- package/src/messaging/style-analyzer.ts +5 -4
- package/src/messaging/thread-summarizer.ts +61 -69
- package/src/messaging/triage-engine.ts +62 -71
- package/src/migrations/config-merge.ts +53 -0
- package/src/migrations/data-layout.ts +68 -0
- package/src/migrations/data-merge.ts +33 -0
- package/src/migrations/hooks-merge.ts +90 -0
- package/src/migrations/index.ts +6 -0
- package/src/migrations/log.ts +23 -0
- package/src/migrations/skills-merge.ts +33 -0
- package/src/migrations/workspace-layout.ts +79 -0
- package/src/permissions/checker.ts +133 -11
- package/src/permissions/prompter.ts +14 -0
- package/src/permissions/shell-identity.ts +31 -1
- package/src/permissions/trust-store.ts +21 -1
- package/src/providers/anthropic/client.ts +4 -4
- package/src/providers/failover.ts +2 -2
- package/src/providers/model-intents.ts +70 -0
- package/src/providers/ollama/client.ts +2 -1
- package/src/providers/provider-send-message.ts +176 -0
- package/src/providers/registry.ts +71 -30
- package/src/providers/retry.ts +35 -1
- package/src/providers/types.ts +12 -1
- package/src/runtime/approval-conversation-turn.ts +97 -0
- package/src/runtime/approval-message-composer.ts +253 -0
- package/src/runtime/channel-approval-parser.ts +36 -2
- package/src/runtime/channel-approvals.ts +11 -24
- package/src/runtime/channel-guardian-service.ts +88 -21
- package/src/runtime/channel-readiness-service.ts +418 -0
- package/src/runtime/channel-readiness-types.ts +35 -0
- package/src/runtime/channel-retry-sweep.ts +184 -0
- package/src/runtime/guardian-context-resolver.ts +108 -0
- package/src/runtime/http-server.ts +275 -717
- package/src/runtime/http-types.ts +59 -3
- package/src/runtime/middleware/auth.ts +116 -0
- package/src/runtime/middleware/error-handler.ts +33 -0
- package/src/runtime/middleware/twilio-validation.ts +127 -0
- package/src/runtime/routes/app-routes.ts +1 -1
- package/src/runtime/routes/call-routes.ts +51 -7
- package/src/runtime/routes/channel-delivery-routes.ts +170 -0
- package/src/runtime/routes/channel-guardian-routes.ts +1191 -0
- package/src/runtime/routes/channel-inbound-routes.ts +1152 -0
- package/src/runtime/routes/channel-route-shared.ts +144 -0
- package/src/runtime/routes/channel-routes.ts +32 -1588
- package/src/runtime/routes/conversation-routes.ts +50 -7
- package/src/runtime/routes/events-routes.ts +2 -2
- package/src/runtime/routes/identity-routes.ts +126 -0
- package/src/runtime/routes/pairing-routes.ts +143 -0
- package/src/runtime/routes/run-routes.ts +15 -1
- package/src/runtime/run-orchestrator.ts +86 -35
- package/src/schedule/schedule-store.ts +36 -32
- package/src/schedule/scheduler.ts +3 -3
- package/src/security/encrypted-store.ts +5 -7
- package/src/security/oauth2.ts +45 -15
- package/src/security/parental-control-store.ts +183 -0
- package/src/security/secret-allowlist.ts +4 -3
- package/src/security/secret-scanner.ts +5 -5
- package/src/security/secure-keys.ts +1 -1
- package/src/security/token-manager.ts +3 -2
- package/src/services/vercel-deploy.ts +6 -2
- package/src/skills/tool-manifest.ts +3 -3
- package/src/skills/vellum-catalog-remote.ts +75 -16
- package/src/slack/slack-webhook.ts +2 -1
- package/src/swarm/orchestrator.ts +92 -1
- package/src/swarm/router-planner.ts +6 -9
- package/src/swarm/worker-prompts.ts +9 -12
- package/src/tasks/task-compiler.ts +19 -28
- package/src/tasks/task-runner.ts +1 -1
- package/src/tools/assets/materialize.ts +2 -2
- package/src/tools/assets/search.ts +15 -14
- package/src/tools/browser/__tests__/auth-detector.test.ts +1 -0
- package/src/tools/browser/auto-navigate.ts +1 -0
- package/src/tools/browser/browser-execution.ts +10 -1
- package/src/tools/browser/browser-manager.ts +119 -4
- package/src/tools/browser/network-recorder.ts +5 -0
- package/src/tools/calls/call-start.ts +1 -0
- package/src/tools/credentials/broker.ts +11 -2
- package/src/tools/credentials/metadata-store.ts +18 -14
- package/src/tools/credentials/post-connect-hooks.ts +61 -0
- package/src/tools/credentials/vault.ts +49 -23
- package/src/tools/execution-target.ts +11 -1
- package/src/tools/executor.ts +68 -9
- package/src/tools/host-terminal/cli-discover.ts +1 -1
- package/src/tools/network/script-proxy/http-forwarder.ts +1 -1
- package/src/tools/network/script-proxy/mitm-handler.ts +1 -1
- package/src/tools/network/script-proxy/server.ts +1 -1
- package/src/tools/network/script-proxy/session-manager.ts +6 -5
- package/src/tools/network/web-fetch.ts +18 -2
- package/src/tools/network/web-search.ts +8 -4
- package/src/tools/reminder/reminder-store.ts +14 -15
- package/src/tools/schedule/create.ts +1 -0
- package/src/tools/schedule/list.ts +2 -1
- package/src/tools/shared/filesystem/file-ops-service.ts +5 -7
- package/src/tools/skills/skill-script-runner.ts +24 -9
- package/src/tools/skills/skill-tool-factory.ts +1 -0
- package/src/tools/tasks/work-item-enqueue.ts +2 -2
- package/src/tools/terminal/evaluate-typescript.ts +21 -12
- package/src/tools/terminal/parser.ts +50 -0
- package/src/tools/types.ts +2 -0
- package/src/tools/watcher/delete.ts +6 -0
- package/src/tools/weather/service.ts +1 -1
- package/src/twitter/client.ts +190 -24
- package/src/twitter/router.ts +1 -1
- package/src/twitter/session.ts +4 -3
- package/src/util/clipboard.ts +1 -1
- package/src/util/errors.ts +65 -8
- package/src/util/fs.ts +40 -0
- package/src/util/json.ts +10 -0
- package/src/util/log-redact.ts +189 -0
- package/src/util/logger.ts +19 -17
- package/src/util/object.ts +3 -0
- package/src/util/platform.ts +105 -363
- package/src/util/pricing.ts +1 -1
- package/src/util/promise-guard.ts +1 -1
- package/src/util/retry.ts +19 -0
- package/src/util/row-mapper.ts +79 -0
- package/src/util/silently.ts +21 -0
- package/src/watcher/engine.ts +5 -1
- package/src/watcher/provider-types.ts +20 -0
- package/src/watcher/providers/github.ts +156 -0
- package/src/watcher/providers/gmail.ts +1 -0
- package/src/watcher/providers/google-calendar.ts +1 -0
- package/src/watcher/providers/linear.ts +460 -0
- package/src/watcher/providers/slack.ts +1 -0
- package/src/work-items/work-item-runner.ts +1 -1
- package/src/workspace/git-service.ts +1 -1
- package/src/workspace/provider-commit-message-generator.ts +51 -22
- package/src/__tests__/call-bridge.test.ts +0 -517
- package/src/__tests__/session-process-bridge.test.ts +0 -244
- package/src/calls/call-bridge.ts +0 -168
- package/src/config/vellum-skills/google-oauth-setup/SKILL.md +0 -199
|
@@ -7,7 +7,10 @@
|
|
|
7
7
|
*/
|
|
8
8
|
|
|
9
9
|
import type { ServerWebSocket } from 'bun';
|
|
10
|
+
import { randomInt } from 'node:crypto';
|
|
10
11
|
import { getLogger } from '../util/logger.js';
|
|
12
|
+
import { parseJsonSafe } from '../util/json.js';
|
|
13
|
+
import { getConfig } from '../config/loader.js';
|
|
11
14
|
import {
|
|
12
15
|
getCallSession,
|
|
13
16
|
updateCallSession,
|
|
@@ -16,12 +19,24 @@ import {
|
|
|
16
19
|
} from './call-store.js';
|
|
17
20
|
import { CallOrchestrator } from './call-orchestrator.js';
|
|
18
21
|
import { fireCallTranscriptNotifier, fireCallCompletionNotifier } from './call-state.js';
|
|
22
|
+
import { addPointerMessage, formatDuration } from './call-pointer-messages.js';
|
|
23
|
+
import { persistCallCompletionMessage } from './call-conversation-messages.js';
|
|
24
|
+
import * as conversationStore from '../memory/conversation-store.js';
|
|
19
25
|
import {
|
|
20
26
|
extractPromptSpeakerMetadata,
|
|
21
27
|
SpeakerIdentityTracker,
|
|
22
28
|
type PromptSpeakerContext,
|
|
23
29
|
} from './speaker-identification.js';
|
|
24
30
|
import { isTerminalState } from './call-state-machine.js';
|
|
31
|
+
import {
|
|
32
|
+
getPendingChallenge,
|
|
33
|
+
validateAndConsumeChallenge,
|
|
34
|
+
} from '../runtime/channel-guardian-service.js';
|
|
35
|
+
import {
|
|
36
|
+
resolveGuardianContext,
|
|
37
|
+
toGuardianRuntimeContext,
|
|
38
|
+
} from '../runtime/guardian-context-resolver.js';
|
|
39
|
+
import { normalizeAssistantId } from '../util/platform.js';
|
|
25
40
|
|
|
26
41
|
const log = getLogger('relay-server');
|
|
27
42
|
|
|
@@ -105,11 +120,21 @@ export interface RelayWebSocketData {
|
|
|
105
120
|
/** Active relay connections keyed by callSessionId. */
|
|
106
121
|
export const activeRelayConnections = new Map<string, RelayConnection>();
|
|
107
122
|
|
|
123
|
+
/** Module-level broadcast function, set by the HTTP server during startup. */
|
|
124
|
+
let globalBroadcast: ((msg: import('../daemon/ipc-contract.js').ServerMessage) => void) | undefined;
|
|
125
|
+
|
|
126
|
+
/** Register a broadcast function so RelayConnection can forward IPC events. */
|
|
127
|
+
export function setRelayBroadcast(fn: (msg: import('../daemon/ipc-contract.js').ServerMessage) => void): void {
|
|
128
|
+
globalBroadcast = fn;
|
|
129
|
+
}
|
|
130
|
+
|
|
108
131
|
// ── RelayConnection ──────────────────────────────────────────────────
|
|
109
132
|
|
|
110
133
|
/**
|
|
111
134
|
* Manages a single WebSocket connection for one call.
|
|
112
135
|
*/
|
|
136
|
+
export type RelayConnectionState = 'connected' | 'verification_pending';
|
|
137
|
+
|
|
113
138
|
export class RelayConnection {
|
|
114
139
|
private ws: ServerWebSocket<RelayWebSocketData>;
|
|
115
140
|
private callSessionId: string;
|
|
@@ -123,6 +148,19 @@ export class RelayConnection {
|
|
|
123
148
|
private orchestrator: CallOrchestrator | null = null;
|
|
124
149
|
private speakerIdentityTracker: SpeakerIdentityTracker;
|
|
125
150
|
|
|
151
|
+
// Verification state (outbound callee verification)
|
|
152
|
+
private connectionState: RelayConnectionState = 'connected';
|
|
153
|
+
private verificationCode: string | null = null;
|
|
154
|
+
private verificationAttempts = 0;
|
|
155
|
+
private verificationMaxAttempts = 3;
|
|
156
|
+
private verificationCodeLength = 6;
|
|
157
|
+
private dtmfBuffer = '';
|
|
158
|
+
|
|
159
|
+
// Inbound voice guardian verification state
|
|
160
|
+
private guardianVerificationActive = false;
|
|
161
|
+
private guardianChallengeAssistantId: string | null = null;
|
|
162
|
+
private guardianVerificationFromNumber: string | null = null;
|
|
163
|
+
|
|
126
164
|
constructor(ws: ServerWebSocket<RelayWebSocketData>, callSessionId: string) {
|
|
127
165
|
this.ws = ws;
|
|
128
166
|
this.callSessionId = callSessionId;
|
|
@@ -131,14 +169,33 @@ export class RelayConnection {
|
|
|
131
169
|
this.speakerIdentityTracker = new SpeakerIdentityTracker();
|
|
132
170
|
}
|
|
133
171
|
|
|
172
|
+
/**
|
|
173
|
+
* Get the verification code for this connection (if verification is active).
|
|
174
|
+
*/
|
|
175
|
+
getVerificationCode(): string | null {
|
|
176
|
+
return this.verificationCode;
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
/**
|
|
180
|
+
* Whether inbound guardian voice verification is currently active.
|
|
181
|
+
*/
|
|
182
|
+
isGuardianVerificationActive(): boolean {
|
|
183
|
+
return this.guardianVerificationActive;
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
/**
|
|
187
|
+
* Get the current connection state.
|
|
188
|
+
*/
|
|
189
|
+
getConnectionState(): RelayConnectionState {
|
|
190
|
+
return this.connectionState;
|
|
191
|
+
}
|
|
192
|
+
|
|
134
193
|
/**
|
|
135
194
|
* Handle an inbound message from Twilio via the ConversationRelay WebSocket.
|
|
136
195
|
*/
|
|
137
196
|
async handleMessage(data: string): Promise<void> {
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
parsed = JSON.parse(data) as RelayInboundMessage;
|
|
141
|
-
} catch {
|
|
197
|
+
const parsed = parseJsonSafe<RelayInboundMessage>(data);
|
|
198
|
+
if (!parsed) {
|
|
142
199
|
log.warn({ callSessionId: this.callSessionId, data }, 'Failed to parse relay message');
|
|
143
200
|
return;
|
|
144
201
|
}
|
|
@@ -160,7 +217,7 @@ export class RelayConnection {
|
|
|
160
217
|
this.handleError(parsed);
|
|
161
218
|
break;
|
|
162
219
|
default:
|
|
163
|
-
log.warn({ callSessionId: this.callSessionId, type: (parsed as
|
|
220
|
+
log.warn({ callSessionId: this.callSessionId, type: (parsed as { type: unknown }).type }, 'Unknown relay message type');
|
|
164
221
|
}
|
|
165
222
|
}
|
|
166
223
|
|
|
@@ -252,6 +309,14 @@ export class RelayConnection {
|
|
|
252
309
|
reason: reason || 'relay_closed',
|
|
253
310
|
closeCode: code,
|
|
254
311
|
});
|
|
312
|
+
|
|
313
|
+
// Post a pointer message in the initiating conversation
|
|
314
|
+
if (session.initiatedFromConversationId) {
|
|
315
|
+
const durationMs = session.startedAt ? Date.now() - session.startedAt : 0;
|
|
316
|
+
addPointerMessage(session.initiatedFromConversationId, 'completed', session.toNumber, {
|
|
317
|
+
duration: durationMs > 0 ? formatDuration(durationMs) : undefined,
|
|
318
|
+
});
|
|
319
|
+
}
|
|
255
320
|
} else {
|
|
256
321
|
const detail = reason || (code ? `relay_closed_${code}` : 'relay_closed_abnormal');
|
|
257
322
|
updateCallSession(this.callSessionId, {
|
|
@@ -263,9 +328,17 @@ export class RelayConnection {
|
|
|
263
328
|
reason: detail,
|
|
264
329
|
closeCode: code,
|
|
265
330
|
});
|
|
331
|
+
|
|
332
|
+
// Post a failure pointer message in the initiating conversation
|
|
333
|
+
if (session.initiatedFromConversationId) {
|
|
334
|
+
addPointerMessage(session.initiatedFromConversationId, 'failed', session.toNumber, {
|
|
335
|
+
reason: detail,
|
|
336
|
+
});
|
|
337
|
+
}
|
|
266
338
|
}
|
|
267
339
|
|
|
268
340
|
expirePendingQuestions(this.callSessionId);
|
|
341
|
+
persistCallCompletionMessage(session.conversationId, this.callSessionId);
|
|
269
342
|
fireCallCompletionNotifier(session.conversationId, this.callSessionId);
|
|
270
343
|
}
|
|
271
344
|
|
|
@@ -299,9 +372,272 @@ export class RelayConnection {
|
|
|
299
372
|
customParameters: msg.customParameters,
|
|
300
373
|
});
|
|
301
374
|
|
|
302
|
-
//
|
|
303
|
-
|
|
375
|
+
// Inbound calls skip callee verification — verification is an
|
|
376
|
+
// outbound-call concern where we need to confirm the callee's identity.
|
|
377
|
+
// We use initiatedFromConversationId rather than task == null because
|
|
378
|
+
// outbound calls always have an initiating conversation, while inbound
|
|
379
|
+
// calls (created via createInboundVoiceSession) never do. Relying on
|
|
380
|
+
// task == null is unreliable: task-less outbound sessions would
|
|
381
|
+
// incorrectly bypass outbound verification.
|
|
382
|
+
const assistantId = normalizeAssistantId(session?.assistantId ?? 'self');
|
|
383
|
+
const isInbound = session?.initiatedFromConversationId == null;
|
|
384
|
+
|
|
385
|
+
// Create and attach the LLM-driven orchestrator. For inbound voice,
|
|
386
|
+
// seed guardian actor context from caller identity + active binding so
|
|
387
|
+
// first-turn behavior matches channel ingress semantics.
|
|
388
|
+
const initialGuardianContext = isInbound
|
|
389
|
+
? toGuardianRuntimeContext(
|
|
390
|
+
'voice',
|
|
391
|
+
resolveGuardianContext({
|
|
392
|
+
assistantId,
|
|
393
|
+
sourceChannel: 'voice',
|
|
394
|
+
externalChatId: msg.from,
|
|
395
|
+
senderExternalUserId: msg.from || undefined,
|
|
396
|
+
}),
|
|
397
|
+
)
|
|
398
|
+
: undefined;
|
|
399
|
+
|
|
400
|
+
const orchestrator = new CallOrchestrator(this.callSessionId, this, session?.task ?? null, {
|
|
401
|
+
broadcast: globalBroadcast,
|
|
402
|
+
assistantId,
|
|
403
|
+
guardianContext: initialGuardianContext,
|
|
404
|
+
});
|
|
304
405
|
this.setOrchestrator(orchestrator);
|
|
406
|
+
|
|
407
|
+
const config = getConfig();
|
|
408
|
+
const verificationConfig = config.calls.verification;
|
|
409
|
+
if (!isInbound && verificationConfig.enabled) {
|
|
410
|
+
this.startVerification(session, verificationConfig);
|
|
411
|
+
} else if (isInbound) {
|
|
412
|
+
// For inbound calls, check if there's a pending voice guardian
|
|
413
|
+
// challenge that the caller needs to complete before proceeding.
|
|
414
|
+
const pendingChallenge = getPendingChallenge(assistantId, 'voice');
|
|
415
|
+
|
|
416
|
+
if (pendingChallenge) {
|
|
417
|
+
this.startInboundGuardianVerification(assistantId, msg.from);
|
|
418
|
+
} else {
|
|
419
|
+
this.startNormalCallFlow(orchestrator, true);
|
|
420
|
+
}
|
|
421
|
+
} else {
|
|
422
|
+
this.startNormalCallFlow(orchestrator, false);
|
|
423
|
+
}
|
|
424
|
+
}
|
|
425
|
+
|
|
426
|
+
/**
|
|
427
|
+
* Generate a verification code and prompt the callee to enter it via DTMF.
|
|
428
|
+
*/
|
|
429
|
+
private startVerification(
|
|
430
|
+
session: ReturnType<typeof getCallSession>,
|
|
431
|
+
verificationConfig: { maxAttempts: number; codeLength: number },
|
|
432
|
+
): void {
|
|
433
|
+
this.verificationMaxAttempts = verificationConfig.maxAttempts;
|
|
434
|
+
this.verificationCodeLength = verificationConfig.codeLength;
|
|
435
|
+
this.verificationAttempts = 0;
|
|
436
|
+
this.dtmfBuffer = '';
|
|
437
|
+
|
|
438
|
+
// Generate a random numeric code
|
|
439
|
+
const maxValue = Math.pow(10, this.verificationCodeLength);
|
|
440
|
+
const code = randomInt(0, maxValue).toString().padStart(this.verificationCodeLength, '0');
|
|
441
|
+
this.verificationCode = code;
|
|
442
|
+
this.connectionState = 'verification_pending';
|
|
443
|
+
|
|
444
|
+
recordCallEvent(this.callSessionId, 'callee_verification_started', {
|
|
445
|
+
codeLength: this.verificationCodeLength,
|
|
446
|
+
maxAttempts: this.verificationMaxAttempts,
|
|
447
|
+
});
|
|
448
|
+
|
|
449
|
+
// Send a TTS prompt with the code spoken digit by digit
|
|
450
|
+
const spokenCode = code.split('').join('. ');
|
|
451
|
+
this.sendTextToken(`Please enter the verification code: ${spokenCode}.`, true);
|
|
452
|
+
|
|
453
|
+
// Post the verification code to the initiating conversation so the
|
|
454
|
+
// guardian (user) can share it with the callee.
|
|
455
|
+
if (session?.initiatedFromConversationId) {
|
|
456
|
+
const codeMsg = `\u{1F510} Verification code for call to ${session.toNumber}: ${code}`;
|
|
457
|
+
conversationStore.addMessage(
|
|
458
|
+
session.initiatedFromConversationId,
|
|
459
|
+
'assistant',
|
|
460
|
+
JSON.stringify([{ type: 'text', text: codeMsg }]),
|
|
461
|
+
{ userMessageChannel: 'voice', assistantMessageChannel: 'voice' },
|
|
462
|
+
);
|
|
463
|
+
}
|
|
464
|
+
|
|
465
|
+
log.info(
|
|
466
|
+
{ callSessionId: this.callSessionId, codeLength: this.verificationCodeLength },
|
|
467
|
+
'Callee verification started',
|
|
468
|
+
);
|
|
469
|
+
}
|
|
470
|
+
|
|
471
|
+
/**
|
|
472
|
+
* Start normal call flow — fire the orchestrator greeting unless a
|
|
473
|
+
* static welcome greeting is configured.
|
|
474
|
+
*/
|
|
475
|
+
private startNormalCallFlow(orchestrator: CallOrchestrator, isInbound: boolean): void {
|
|
476
|
+
const hasStaticGreeting = !!process.env.CALL_WELCOME_GREETING?.trim();
|
|
477
|
+
if (!hasStaticGreeting) {
|
|
478
|
+
orchestrator.startInitialGreeting().catch((err) =>
|
|
479
|
+
log.error({ err, callSessionId: this.callSessionId }, `Failed to start initial ${isInbound ? 'inbound' : 'outbound'} greeting`),
|
|
480
|
+
);
|
|
481
|
+
}
|
|
482
|
+
}
|
|
483
|
+
|
|
484
|
+
/**
|
|
485
|
+
* Enter verification-pending state for an inbound call with a pending
|
|
486
|
+
* voice guardian challenge. Prompts the caller to enter their six-digit
|
|
487
|
+
* verification code via DTMF or by speaking it.
|
|
488
|
+
*/
|
|
489
|
+
private startInboundGuardianVerification(assistantId: string, fromNumber: string): void {
|
|
490
|
+
this.guardianVerificationActive = true;
|
|
491
|
+
this.guardianChallengeAssistantId = assistantId;
|
|
492
|
+
this.guardianVerificationFromNumber = fromNumber;
|
|
493
|
+
this.connectionState = 'verification_pending';
|
|
494
|
+
this.verificationAttempts = 0;
|
|
495
|
+
this.verificationMaxAttempts = 3;
|
|
496
|
+
this.verificationCodeLength = 6;
|
|
497
|
+
this.dtmfBuffer = '';
|
|
498
|
+
|
|
499
|
+
recordCallEvent(this.callSessionId, 'guardian_voice_verification_started', {
|
|
500
|
+
assistantId,
|
|
501
|
+
maxAttempts: this.verificationMaxAttempts,
|
|
502
|
+
});
|
|
503
|
+
|
|
504
|
+
this.sendTextToken(
|
|
505
|
+
'Welcome. Please enter your six-digit verification code using your keypad, or speak the digits now.',
|
|
506
|
+
true,
|
|
507
|
+
);
|
|
508
|
+
|
|
509
|
+
log.info(
|
|
510
|
+
{ callSessionId: this.callSessionId, assistantId },
|
|
511
|
+
'Inbound guardian voice verification started',
|
|
512
|
+
);
|
|
513
|
+
}
|
|
514
|
+
|
|
515
|
+
/**
|
|
516
|
+
* Extract digit characters from a speech transcript. Recognizes both
|
|
517
|
+
* raw digit characters ("1 2 3") and spoken number words ("one two three").
|
|
518
|
+
*/
|
|
519
|
+
private static parseDigitsFromSpeech(transcript: string): string {
|
|
520
|
+
const wordToDigit: Record<string, string> = {
|
|
521
|
+
zero: '0', oh: '0', o: '0',
|
|
522
|
+
one: '1', won: '1',
|
|
523
|
+
two: '2', too: '2', to: '2',
|
|
524
|
+
three: '3',
|
|
525
|
+
four: '4', for: '4', fore: '4',
|
|
526
|
+
five: '5',
|
|
527
|
+
six: '6',
|
|
528
|
+
seven: '7',
|
|
529
|
+
eight: '8', ate: '8',
|
|
530
|
+
nine: '9',
|
|
531
|
+
};
|
|
532
|
+
|
|
533
|
+
const digits: string[] = [];
|
|
534
|
+
const lower = transcript.toLowerCase();
|
|
535
|
+
|
|
536
|
+
// Split on whitespace and non-alphanumeric boundaries
|
|
537
|
+
const tokens = lower.split(/[\s,.\-;:!?]+/);
|
|
538
|
+
for (const token of tokens) {
|
|
539
|
+
if (/^\d$/.test(token)) {
|
|
540
|
+
digits.push(token);
|
|
541
|
+
} else if (wordToDigit[token]) {
|
|
542
|
+
digits.push(wordToDigit[token]);
|
|
543
|
+
} else if (/^\d+$/.test(token)) {
|
|
544
|
+
// Multi-digit number like "123456" — split into individual digits
|
|
545
|
+
digits.push(...token.split(''));
|
|
546
|
+
}
|
|
547
|
+
}
|
|
548
|
+
|
|
549
|
+
return digits.join('');
|
|
550
|
+
}
|
|
551
|
+
|
|
552
|
+
/**
|
|
553
|
+
* Attempt to validate an entered code against the pending voice guardian
|
|
554
|
+
* challenge via validateAndConsumeChallenge. On success, binds the
|
|
555
|
+
* guardian and transitions to normal call flow. On failure, enforces
|
|
556
|
+
* max attempts and terminates the call if exhausted.
|
|
557
|
+
*/
|
|
558
|
+
private attemptGuardianCodeVerification(enteredCode: string): void {
|
|
559
|
+
if (!this.guardianChallengeAssistantId || !this.guardianVerificationFromNumber) {
|
|
560
|
+
return;
|
|
561
|
+
}
|
|
562
|
+
|
|
563
|
+
const result = validateAndConsumeChallenge(
|
|
564
|
+
this.guardianChallengeAssistantId,
|
|
565
|
+
'voice',
|
|
566
|
+
enteredCode,
|
|
567
|
+
this.guardianVerificationFromNumber,
|
|
568
|
+
this.guardianVerificationFromNumber,
|
|
569
|
+
);
|
|
570
|
+
|
|
571
|
+
if (result.success) {
|
|
572
|
+
// Guardian binding was created by validateAndConsumeChallenge
|
|
573
|
+
this.connectionState = 'connected';
|
|
574
|
+
this.guardianVerificationActive = false;
|
|
575
|
+
this.verificationAttempts = 0;
|
|
576
|
+
this.dtmfBuffer = '';
|
|
577
|
+
|
|
578
|
+
recordCallEvent(this.callSessionId, 'guardian_voice_verification_succeeded', {
|
|
579
|
+
bindingId: result.bindingId,
|
|
580
|
+
});
|
|
581
|
+
log.info({ callSessionId: this.callSessionId }, 'Inbound guardian voice verification succeeded');
|
|
582
|
+
|
|
583
|
+
// Proceed to normal call flow (use startNormalCallFlow to respect
|
|
584
|
+
// the CALL_WELCOME_GREETING static greeting guard)
|
|
585
|
+
if (this.orchestrator) {
|
|
586
|
+
this.orchestrator.setGuardianContext(
|
|
587
|
+
toGuardianRuntimeContext(
|
|
588
|
+
'voice',
|
|
589
|
+
resolveGuardianContext({
|
|
590
|
+
assistantId: this.guardianChallengeAssistantId,
|
|
591
|
+
sourceChannel: 'voice',
|
|
592
|
+
externalChatId: this.guardianVerificationFromNumber,
|
|
593
|
+
senderExternalUserId: this.guardianVerificationFromNumber,
|
|
594
|
+
}),
|
|
595
|
+
),
|
|
596
|
+
);
|
|
597
|
+
this.startNormalCallFlow(this.orchestrator, true);
|
|
598
|
+
}
|
|
599
|
+
} else {
|
|
600
|
+
this.verificationAttempts++;
|
|
601
|
+
|
|
602
|
+
if (this.verificationAttempts >= this.verificationMaxAttempts) {
|
|
603
|
+
// Immediately deactivate verification so DTMF/speech input during
|
|
604
|
+
// the goodbye window doesn't trigger more verification attempts.
|
|
605
|
+
this.guardianVerificationActive = false;
|
|
606
|
+
|
|
607
|
+
recordCallEvent(this.callSessionId, 'guardian_voice_verification_failed', {
|
|
608
|
+
attempts: this.verificationAttempts,
|
|
609
|
+
});
|
|
610
|
+
log.warn(
|
|
611
|
+
{ callSessionId: this.callSessionId, attempts: this.verificationAttempts },
|
|
612
|
+
'Inbound guardian voice verification failed — max attempts reached',
|
|
613
|
+
);
|
|
614
|
+
|
|
615
|
+
this.sendTextToken('Verification failed. Goodbye.', true);
|
|
616
|
+
|
|
617
|
+
updateCallSession(this.callSessionId, {
|
|
618
|
+
status: 'failed',
|
|
619
|
+
endedAt: Date.now(),
|
|
620
|
+
lastError: 'Guardian voice verification failed — max attempts exceeded',
|
|
621
|
+
});
|
|
622
|
+
|
|
623
|
+
const session = getCallSession(this.callSessionId);
|
|
624
|
+
if (session) {
|
|
625
|
+
expirePendingQuestions(this.callSessionId);
|
|
626
|
+
persistCallCompletionMessage(session.conversationId, this.callSessionId);
|
|
627
|
+
fireCallCompletionNotifier(session.conversationId, this.callSessionId);
|
|
628
|
+
}
|
|
629
|
+
|
|
630
|
+
setTimeout(() => {
|
|
631
|
+
this.endSession('Guardian verification failed');
|
|
632
|
+
}, 2000);
|
|
633
|
+
} else {
|
|
634
|
+
log.info(
|
|
635
|
+
{ callSessionId: this.callSessionId, attempt: this.verificationAttempts, maxAttempts: this.verificationMaxAttempts },
|
|
636
|
+
'Inbound guardian voice verification attempt failed — retrying',
|
|
637
|
+
);
|
|
638
|
+
this.sendTextToken('That code was incorrect. Please try again.', true);
|
|
639
|
+
}
|
|
640
|
+
}
|
|
305
641
|
}
|
|
306
642
|
|
|
307
643
|
private async handlePrompt(msg: RelayPromptMessage): Promise<void> {
|
|
@@ -310,12 +646,41 @@ export class RelayConnection {
|
|
|
310
646
|
return;
|
|
311
647
|
}
|
|
312
648
|
|
|
649
|
+
// During inbound guardian verification, attempt to parse spoken digits
|
|
650
|
+
// from the transcript and validate them.
|
|
651
|
+
if (this.connectionState === 'verification_pending' && this.guardianVerificationActive) {
|
|
652
|
+
const spokenDigits = RelayConnection.parseDigitsFromSpeech(msg.voicePrompt);
|
|
653
|
+
log.info(
|
|
654
|
+
{ callSessionId: this.callSessionId, transcript: msg.voicePrompt, spokenDigits },
|
|
655
|
+
'Speech received during guardian voice verification',
|
|
656
|
+
);
|
|
657
|
+
if (spokenDigits.length >= this.verificationCodeLength) {
|
|
658
|
+
const enteredCode = spokenDigits.slice(0, this.verificationCodeLength);
|
|
659
|
+
this.attemptGuardianCodeVerification(enteredCode);
|
|
660
|
+
} else if (spokenDigits.length > 0) {
|
|
661
|
+
this.sendTextToken(
|
|
662
|
+
`I heard ${spokenDigits.length} digits. Please enter all ${this.verificationCodeLength} digits of your code.`,
|
|
663
|
+
true,
|
|
664
|
+
);
|
|
665
|
+
}
|
|
666
|
+
return;
|
|
667
|
+
}
|
|
668
|
+
|
|
669
|
+
// During outbound callee verification, ignore voice prompts — the callee
|
|
670
|
+
// should be entering DTMF digits, not speaking.
|
|
671
|
+
if (this.connectionState === 'verification_pending') {
|
|
672
|
+
log.debug({ callSessionId: this.callSessionId }, 'Ignoring voice prompt during callee verification');
|
|
673
|
+
return;
|
|
674
|
+
}
|
|
675
|
+
|
|
313
676
|
log.info(
|
|
314
677
|
{ callSessionId: this.callSessionId, transcript: msg.voicePrompt, lang: msg.lang },
|
|
315
678
|
'Caller transcript received (final)',
|
|
316
679
|
);
|
|
317
680
|
|
|
318
|
-
|
|
681
|
+
// Spread to widen the typed message into a plain record — extractPromptSpeakerMetadata
|
|
682
|
+
// probes for snake_case and nested property variants not on RelayPromptMessage.
|
|
683
|
+
const speakerMetadata = extractPromptSpeakerMetadata({ ...msg });
|
|
319
684
|
const speaker = this.speakerIdentityTracker.identifySpeaker(speakerMetadata);
|
|
320
685
|
|
|
321
686
|
// Record in conversation history
|
|
@@ -338,6 +703,14 @@ export class RelayConnection {
|
|
|
338
703
|
|
|
339
704
|
const session = getCallSession(this.callSessionId);
|
|
340
705
|
if (session) {
|
|
706
|
+
// Persist caller transcript to the voice conversation so it survives
|
|
707
|
+
// even when no live daemon Session is listening.
|
|
708
|
+
conversationStore.addMessage(
|
|
709
|
+
session.conversationId,
|
|
710
|
+
'user',
|
|
711
|
+
JSON.stringify([{ type: 'text', text: msg.voicePrompt }]),
|
|
712
|
+
{ userMessageChannel: 'voice', assistantMessageChannel: 'voice' },
|
|
713
|
+
);
|
|
341
714
|
fireCallTranscriptNotifier(session.conversationId, this.callSessionId, 'caller', msg.voicePrompt);
|
|
342
715
|
}
|
|
343
716
|
|
|
@@ -375,6 +748,91 @@ export class RelayConnection {
|
|
|
375
748
|
recordCallEvent(this.callSessionId, 'caller_spoke', {
|
|
376
749
|
dtmfDigit: msg.digit,
|
|
377
750
|
});
|
|
751
|
+
|
|
752
|
+
// If inbound guardian verification is pending, accumulate digits and
|
|
753
|
+
// validate against the challenge via the guardian service.
|
|
754
|
+
if (this.connectionState === 'verification_pending' && this.guardianVerificationActive) {
|
|
755
|
+
this.dtmfBuffer += msg.digit;
|
|
756
|
+
|
|
757
|
+
if (this.dtmfBuffer.length >= this.verificationCodeLength) {
|
|
758
|
+
const enteredCode = this.dtmfBuffer.slice(0, this.verificationCodeLength);
|
|
759
|
+
this.dtmfBuffer = '';
|
|
760
|
+
this.attemptGuardianCodeVerification(enteredCode);
|
|
761
|
+
}
|
|
762
|
+
return;
|
|
763
|
+
}
|
|
764
|
+
|
|
765
|
+
// If outbound callee verification is pending, accumulate digits and check the code
|
|
766
|
+
if (this.connectionState === 'verification_pending' && this.verificationCode) {
|
|
767
|
+
this.dtmfBuffer += msg.digit;
|
|
768
|
+
|
|
769
|
+
if (this.dtmfBuffer.length >= this.verificationCodeLength) {
|
|
770
|
+
const enteredCode = this.dtmfBuffer.slice(0, this.verificationCodeLength);
|
|
771
|
+
this.dtmfBuffer = '';
|
|
772
|
+
|
|
773
|
+
if (enteredCode === this.verificationCode) {
|
|
774
|
+
// Verification succeeded
|
|
775
|
+
this.connectionState = 'connected';
|
|
776
|
+
this.verificationCode = null;
|
|
777
|
+
this.verificationAttempts = 0;
|
|
778
|
+
|
|
779
|
+
recordCallEvent(this.callSessionId, 'callee_verification_succeeded', {});
|
|
780
|
+
log.info({ callSessionId: this.callSessionId }, 'Callee verification succeeded');
|
|
781
|
+
|
|
782
|
+
// Proceed to the normal call flow
|
|
783
|
+
if (this.orchestrator) {
|
|
784
|
+
this.orchestrator.startInitialGreeting().catch((err) =>
|
|
785
|
+
log.error({ err, callSessionId: this.callSessionId }, 'Failed to start initial outbound greeting after verification'),
|
|
786
|
+
);
|
|
787
|
+
}
|
|
788
|
+
} else {
|
|
789
|
+
// Verification failed for this attempt
|
|
790
|
+
this.verificationAttempts++;
|
|
791
|
+
|
|
792
|
+
if (this.verificationAttempts >= this.verificationMaxAttempts) {
|
|
793
|
+
// Max attempts reached — end the call
|
|
794
|
+
recordCallEvent(this.callSessionId, 'callee_verification_failed', {
|
|
795
|
+
attempts: this.verificationAttempts,
|
|
796
|
+
});
|
|
797
|
+
log.warn({ callSessionId: this.callSessionId, attempts: this.verificationAttempts }, 'Callee verification failed — max attempts reached');
|
|
798
|
+
|
|
799
|
+
this.sendTextToken('Verification failed. Goodbye.', true);
|
|
800
|
+
|
|
801
|
+
// Mark failed immediately so a relay close during the goodbye TTS
|
|
802
|
+
// window cannot race this into a terminal "completed" status.
|
|
803
|
+
updateCallSession(this.callSessionId, {
|
|
804
|
+
status: 'failed',
|
|
805
|
+
endedAt: Date.now(),
|
|
806
|
+
lastError: 'Callee verification failed — max attempts exceeded',
|
|
807
|
+
});
|
|
808
|
+
|
|
809
|
+
const session = getCallSession(this.callSessionId);
|
|
810
|
+
if (session) {
|
|
811
|
+
expirePendingQuestions(this.callSessionId);
|
|
812
|
+
persistCallCompletionMessage(session.conversationId, this.callSessionId);
|
|
813
|
+
fireCallCompletionNotifier(session.conversationId, this.callSessionId);
|
|
814
|
+
if (session.initiatedFromConversationId) {
|
|
815
|
+
addPointerMessage(session.initiatedFromConversationId, 'failed', session.toNumber, {
|
|
816
|
+
reason: 'Callee verification failed',
|
|
817
|
+
});
|
|
818
|
+
}
|
|
819
|
+
}
|
|
820
|
+
|
|
821
|
+
// End the call with failed status after TTS plays
|
|
822
|
+
setTimeout(() => {
|
|
823
|
+
this.endSession('Verification failed');
|
|
824
|
+
}, 2000);
|
|
825
|
+
} else {
|
|
826
|
+
// Allow another attempt
|
|
827
|
+
log.info(
|
|
828
|
+
{ callSessionId: this.callSessionId, attempt: this.verificationAttempts, maxAttempts: this.verificationMaxAttempts },
|
|
829
|
+
'Callee verification attempt failed — retrying',
|
|
830
|
+
);
|
|
831
|
+
this.sendTextToken('That code was incorrect. Please try again.', true);
|
|
832
|
+
}
|
|
833
|
+
}
|
|
834
|
+
}
|
|
835
|
+
}
|
|
378
836
|
}
|
|
379
837
|
|
|
380
838
|
private handleError(msg: RelayErrorMessage): void {
|
|
@@ -66,7 +66,7 @@ export function extractPromptSpeakerMetadata(message: Record<string, unknown>):
|
|
|
66
66
|
const pickNumber = (...values: unknown[]): number | undefined => {
|
|
67
67
|
for (const value of values) {
|
|
68
68
|
const parsed = toNumber(value);
|
|
69
|
-
if (parsed
|
|
69
|
+
if (parsed != null) return parsed;
|
|
70
70
|
}
|
|
71
71
|
return undefined;
|
|
72
72
|
};
|
|
@@ -2,6 +2,8 @@ import { getSecureKey } from '../security/secure-keys.js';
|
|
|
2
2
|
import { getLogger } from '../util/logger.js';
|
|
3
3
|
import { loadConfig } from '../config/loader.js';
|
|
4
4
|
import { getPublicBaseUrl, getTwilioRelayUrl } from '../inbound/public-ingress-urls.js';
|
|
5
|
+
import { ConfigError } from '../util/errors.js';
|
|
6
|
+
import { getTwilioPhoneNumberEnv, getTwilioWssBaseUrl } from '../config/env.js';
|
|
5
7
|
|
|
6
8
|
const log = getLogger('twilio-config');
|
|
7
9
|
|
|
@@ -13,27 +15,33 @@ export interface TwilioConfig {
|
|
|
13
15
|
wssBaseUrl: string;
|
|
14
16
|
}
|
|
15
17
|
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
18
|
+
function resolveTwilioPhoneNumber(assistantId: string | undefined, config: ReturnType<typeof loadConfig>): string {
|
|
19
|
+
if (assistantId) {
|
|
20
|
+
const assistantPhone = config.sms?.assistantPhoneNumbers?.[assistantId];
|
|
21
|
+
if (assistantPhone) {
|
|
22
|
+
return assistantPhone;
|
|
23
|
+
}
|
|
24
|
+
}
|
|
20
25
|
|
|
21
|
-
//
|
|
26
|
+
// Global fallback order:
|
|
22
27
|
// 1. TWILIO_PHONE_NUMBER env var (explicit override)
|
|
23
28
|
// 2. config file sms.phoneNumber (primary storage)
|
|
24
29
|
// 3. credential:twilio:phone_number secure key (backward-compat fallback)
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
+
return getTwilioPhoneNumberEnv() || config.sms?.phoneNumber || getSecureKey('credential:twilio:phone_number') || '';
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
export function getTwilioConfig(assistantId?: string): TwilioConfig {
|
|
34
|
+
const accountSid = getSecureKey('credential:twilio:account_sid');
|
|
35
|
+
const authToken = getSecureKey('credential:twilio:auth_token');
|
|
36
|
+
const config = loadConfig();
|
|
37
|
+
const phoneNumber = resolveTwilioPhoneNumber(assistantId, config);
|
|
30
38
|
const webhookBaseUrl = getPublicBaseUrl(config);
|
|
31
39
|
|
|
32
40
|
// Always use the centralized relay URL derived from the public ingress base URL.
|
|
33
41
|
// TWILIO_WSS_BASE_URL is ignored.
|
|
34
42
|
let wssBaseUrl: string;
|
|
35
|
-
if (
|
|
36
|
-
log.warn('TWILIO_WSS_BASE_URL env var is
|
|
43
|
+
if (getTwilioWssBaseUrl()) {
|
|
44
|
+
log.warn('TWILIO_WSS_BASE_URL env var is deprecated. Relay URL is derived from ingress.publicBaseUrl.');
|
|
37
45
|
}
|
|
38
46
|
try {
|
|
39
47
|
wssBaseUrl = getTwilioRelayUrl(config);
|
|
@@ -42,10 +50,10 @@ export function getTwilioConfig(): TwilioConfig {
|
|
|
42
50
|
}
|
|
43
51
|
|
|
44
52
|
if (!accountSid || !authToken) {
|
|
45
|
-
throw new
|
|
53
|
+
throw new ConfigError('Twilio credentials not configured. Set credential:twilio:account_sid and credential:twilio:auth_token via the credential_store tool.');
|
|
46
54
|
}
|
|
47
55
|
if (!phoneNumber) {
|
|
48
|
-
throw new
|
|
56
|
+
throw new ConfigError('Twilio phone number not configured.');
|
|
49
57
|
}
|
|
50
58
|
|
|
51
59
|
log.debug('Twilio config loaded successfully');
|