@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
|
@@ -2,6 +2,7 @@ import { createHmac, timingSafeEqual } from 'node:crypto';
|
|
|
2
2
|
import { getLogger } from '../util/logger.js';
|
|
3
3
|
import { getSecureKey } from '../security/secure-keys.js';
|
|
4
4
|
import { getTwilioCredentials, twilioAuthHeader, twilioBaseUrl } from './twilio-rest.js';
|
|
5
|
+
import { ProviderError } from '../util/errors.js';
|
|
5
6
|
import type { VoiceProvider, InitiateCallOptions } from './voice-provider.js';
|
|
6
7
|
|
|
7
8
|
const log = getLogger('twilio-provider');
|
|
@@ -72,7 +73,7 @@ export class TwilioConversationRelayProvider implements VoiceProvider {
|
|
|
72
73
|
if (!res.ok) {
|
|
73
74
|
const text = await res.text();
|
|
74
75
|
log.error({ status: res.status, body: text }, 'Twilio initiateCall failed');
|
|
75
|
-
throw new
|
|
76
|
+
throw new ProviderError(`Twilio API error ${res.status}: ${text}`, 'twilio', res.status);
|
|
76
77
|
}
|
|
77
78
|
|
|
78
79
|
const data = (await res.json()) as { sid: string };
|
|
@@ -99,7 +100,7 @@ export class TwilioConversationRelayProvider implements VoiceProvider {
|
|
|
99
100
|
if (!res.ok) {
|
|
100
101
|
const text = await res.text();
|
|
101
102
|
log.error({ status: res.status, body: text, callSid }, 'Twilio endCall failed');
|
|
102
|
-
throw new
|
|
103
|
+
throw new ProviderError(`Twilio API error ${res.status}: ${text}`, 'twilio', res.status);
|
|
103
104
|
}
|
|
104
105
|
|
|
105
106
|
log.info({ callSid }, 'Twilio call ended');
|
|
@@ -118,7 +119,7 @@ export class TwilioConversationRelayProvider implements VoiceProvider {
|
|
|
118
119
|
if (!res.ok) {
|
|
119
120
|
const text = await res.text();
|
|
120
121
|
log.error({ status: res.status, body: text, callSid }, 'Twilio getCallStatus failed');
|
|
121
|
-
throw new
|
|
122
|
+
throw new ProviderError(`Twilio API error ${res.status}: ${text}`, 'twilio', res.status);
|
|
122
123
|
}
|
|
123
124
|
|
|
124
125
|
const data = (await res.json()) as { status: string };
|
|
@@ -203,8 +204,9 @@ export class TwilioConversationRelayProvider implements VoiceProvider {
|
|
|
203
204
|
...(!incomingOk ? [`IncomingPhoneNumbers: ${incomingRes.status}`] : []),
|
|
204
205
|
...(!outgoingOk ? [`OutgoingCallerIds: ${outgoingRes.status}`] : []),
|
|
205
206
|
].join(', ');
|
|
206
|
-
throw new
|
|
207
|
+
throw new ProviderError(
|
|
207
208
|
`Unable to verify caller ID eligibility for ${phoneNumber}: Twilio API error (${failedEndpoints}). The number may be eligible but could not be confirmed. Please check your Twilio credentials and try again.`,
|
|
209
|
+
'twilio',
|
|
208
210
|
);
|
|
209
211
|
}
|
|
210
212
|
|
package/src/calls/twilio-rest.ts
CHANGED
|
@@ -7,6 +7,7 @@
|
|
|
7
7
|
*/
|
|
8
8
|
|
|
9
9
|
import { getSecureKey } from '../security/secure-keys.js';
|
|
10
|
+
import { ProviderError, ConfigError } from '../util/errors.js';
|
|
10
11
|
|
|
11
12
|
export interface TwilioCredentials {
|
|
12
13
|
accountSid: string;
|
|
@@ -18,7 +19,7 @@ export function getTwilioCredentials(): TwilioCredentials {
|
|
|
18
19
|
const accountSid = getSecureKey('credential:twilio:account_sid');
|
|
19
20
|
const authToken = getSecureKey('credential:twilio:auth_token');
|
|
20
21
|
if (!accountSid || !authToken) {
|
|
21
|
-
throw new
|
|
22
|
+
throw new ConfigError(
|
|
22
23
|
'Twilio credentials not configured. Set credential:twilio:account_sid and credential:twilio:auth_token via the credential_store tool.',
|
|
23
24
|
);
|
|
24
25
|
}
|
|
@@ -58,7 +59,7 @@ export async function listIncomingPhoneNumbers(
|
|
|
58
59
|
|
|
59
60
|
if (!res.ok) {
|
|
60
61
|
const text = await res.text();
|
|
61
|
-
throw new
|
|
62
|
+
throw new ProviderError(`Twilio API error ${res.status}: ${text}`, 'twilio', res.status);
|
|
62
63
|
}
|
|
63
64
|
|
|
64
65
|
const data = (await res.json()) as {
|
|
@@ -102,7 +103,7 @@ export async function searchAvailableNumbers(
|
|
|
102
103
|
|
|
103
104
|
if (!res.ok) {
|
|
104
105
|
const text = await res.text();
|
|
105
|
-
throw new
|
|
106
|
+
throw new ProviderError(`Twilio API error ${res.status}: ${text}`, 'twilio', res.status);
|
|
106
107
|
}
|
|
107
108
|
|
|
108
109
|
const data = (await res.json()) as {
|
|
@@ -139,7 +140,7 @@ export async function provisionPhoneNumber(
|
|
|
139
140
|
|
|
140
141
|
if (!res.ok) {
|
|
141
142
|
const text = await res.text();
|
|
142
|
-
throw new
|
|
143
|
+
throw new ProviderError(`Twilio API error ${res.status}: ${text}`, 'twilio', res.status);
|
|
143
144
|
}
|
|
144
145
|
|
|
145
146
|
const data = (await res.json()) as {
|
|
@@ -155,6 +156,38 @@ export async function provisionPhoneNumber(
|
|
|
155
156
|
};
|
|
156
157
|
}
|
|
157
158
|
|
|
159
|
+
/** Fetch the current status of a Twilio message by SID. */
|
|
160
|
+
export async function fetchMessageStatus(
|
|
161
|
+
accountSid: string,
|
|
162
|
+
authToken: string,
|
|
163
|
+
messageSid: string,
|
|
164
|
+
): Promise<{ status: string; errorCode?: string; errorMessage?: string }> {
|
|
165
|
+
const res = await fetch(
|
|
166
|
+
`${twilioBaseUrl(accountSid)}/Messages/${encodeURIComponent(messageSid)}.json`,
|
|
167
|
+
{
|
|
168
|
+
method: 'GET',
|
|
169
|
+
headers: { Authorization: twilioAuthHeader(accountSid, authToken) },
|
|
170
|
+
},
|
|
171
|
+
);
|
|
172
|
+
|
|
173
|
+
if (!res.ok) {
|
|
174
|
+
const text = await res.text();
|
|
175
|
+
throw new ProviderError(`Twilio API error ${res.status}: ${text}`, 'twilio', res.status);
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
const data = (await res.json()) as {
|
|
179
|
+
status?: string;
|
|
180
|
+
error_code?: number | null;
|
|
181
|
+
error_message?: string | null;
|
|
182
|
+
};
|
|
183
|
+
|
|
184
|
+
return {
|
|
185
|
+
status: data.status ?? 'unknown',
|
|
186
|
+
errorCode: data.error_code != null ? String(data.error_code) : undefined,
|
|
187
|
+
errorMessage: data.error_message ?? undefined,
|
|
188
|
+
};
|
|
189
|
+
}
|
|
190
|
+
|
|
158
191
|
export interface WebhookUrls {
|
|
159
192
|
voiceUrl: string;
|
|
160
193
|
statusCallbackUrl: string;
|
|
@@ -185,7 +218,7 @@ export async function updatePhoneNumberWebhooks(
|
|
|
185
218
|
|
|
186
219
|
if (!listRes.ok) {
|
|
187
220
|
const text = await listRes.text();
|
|
188
|
-
throw new
|
|
221
|
+
throw new ProviderError(`Twilio API error ${listRes.status} looking up phone number: ${text}`, 'twilio', listRes.status);
|
|
189
222
|
}
|
|
190
223
|
|
|
191
224
|
const listData = (await listRes.json()) as {
|
|
@@ -194,7 +227,7 @@ export async function updatePhoneNumberWebhooks(
|
|
|
194
227
|
|
|
195
228
|
const match = listData.incoming_phone_numbers.find((n) => n.phone_number === phoneNumber);
|
|
196
229
|
if (!match) {
|
|
197
|
-
throw new
|
|
230
|
+
throw new ProviderError(`Phone number ${phoneNumber} not found on Twilio account ${accountSid}`, 'twilio');
|
|
198
231
|
}
|
|
199
232
|
|
|
200
233
|
// Update the phone number's webhook configuration
|
|
@@ -221,6 +254,274 @@ export async function updatePhoneNumberWebhooks(
|
|
|
221
254
|
|
|
222
255
|
if (!updateRes.ok) {
|
|
223
256
|
const text = await updateRes.text();
|
|
224
|
-
throw new
|
|
257
|
+
throw new ProviderError(`Twilio API error ${updateRes.status} updating webhooks: ${text}`, 'twilio', updateRes.status);
|
|
258
|
+
}
|
|
259
|
+
}
|
|
260
|
+
|
|
261
|
+
// ── Toll-Free Verification ──────────────────────────────────────────────
|
|
262
|
+
|
|
263
|
+
/** Twilio Messaging API base URL for toll-free verification endpoints. */
|
|
264
|
+
const TOLLFREE_VERIFICATION_BASE = 'https://messaging.twilio.com/v1/Tollfree/Verifications';
|
|
265
|
+
|
|
266
|
+
export interface TollFreeVerification {
|
|
267
|
+
sid: string;
|
|
268
|
+
status: string;
|
|
269
|
+
rejectionReason?: string;
|
|
270
|
+
rejectionReasons?: string[];
|
|
271
|
+
errorCode?: string;
|
|
272
|
+
editAllowed?: boolean;
|
|
273
|
+
editExpiration?: string;
|
|
274
|
+
regulationType?: string;
|
|
275
|
+
}
|
|
276
|
+
|
|
277
|
+
function parseTollFreeVerification(raw: Record<string, unknown>): TollFreeVerification {
|
|
278
|
+
return {
|
|
279
|
+
sid: raw.sid as string,
|
|
280
|
+
status: raw.status as string,
|
|
281
|
+
rejectionReason: (raw.rejection_reason as string) ?? undefined,
|
|
282
|
+
rejectionReasons: (raw.rejection_reasons as string[]) ?? undefined,
|
|
283
|
+
errorCode: (raw.error_code != null ? String(raw.error_code) : undefined),
|
|
284
|
+
editAllowed: (raw.edit_allowed as boolean) ?? undefined,
|
|
285
|
+
editExpiration: (raw.edit_expiration as string) ?? undefined,
|
|
286
|
+
regulationType: (raw.regulation_type as string) ?? undefined,
|
|
287
|
+
};
|
|
288
|
+
}
|
|
289
|
+
|
|
290
|
+
/**
|
|
291
|
+
* Get toll-free verification status for a phone number.
|
|
292
|
+
* If `phoneNumberSid` is provided, filters by that SID; otherwise returns the
|
|
293
|
+
* first verification found.
|
|
294
|
+
*/
|
|
295
|
+
export async function getTollFreeVerificationStatus(
|
|
296
|
+
accountSid: string,
|
|
297
|
+
authToken: string,
|
|
298
|
+
phoneNumberSid?: string,
|
|
299
|
+
): Promise<TollFreeVerification | null> {
|
|
300
|
+
const params = new URLSearchParams();
|
|
301
|
+
if (phoneNumberSid) params.set('TollfreePhoneNumberSid', phoneNumberSid);
|
|
302
|
+
|
|
303
|
+
const url = params.toString()
|
|
304
|
+
? `${TOLLFREE_VERIFICATION_BASE}?${params.toString()}`
|
|
305
|
+
: TOLLFREE_VERIFICATION_BASE;
|
|
306
|
+
|
|
307
|
+
const res = await fetch(url, {
|
|
308
|
+
method: 'GET',
|
|
309
|
+
headers: { Authorization: twilioAuthHeader(accountSid, authToken) },
|
|
310
|
+
});
|
|
311
|
+
|
|
312
|
+
if (!res.ok) {
|
|
313
|
+
const text = await res.text();
|
|
314
|
+
throw new ProviderError(`Twilio Toll-Free Verification API error ${res.status}: ${text}`, 'twilio', res.status);
|
|
315
|
+
}
|
|
316
|
+
|
|
317
|
+
const data = (await res.json()) as { verifications?: Array<Record<string, unknown>> };
|
|
318
|
+
const verifications = data.verifications ?? [];
|
|
319
|
+
if (verifications.length === 0) return null;
|
|
320
|
+
|
|
321
|
+
return parseTollFreeVerification(verifications[0]);
|
|
322
|
+
}
|
|
323
|
+
|
|
324
|
+
/** Fetch a specific toll-free verification by SID. */
|
|
325
|
+
export async function getTollFreeVerificationBySid(
|
|
326
|
+
accountSid: string,
|
|
327
|
+
authToken: string,
|
|
328
|
+
verificationSid: string,
|
|
329
|
+
): Promise<TollFreeVerification | null> {
|
|
330
|
+
const res = await fetch(`${TOLLFREE_VERIFICATION_BASE}/${encodeURIComponent(verificationSid)}`, {
|
|
331
|
+
method: 'GET',
|
|
332
|
+
headers: { Authorization: twilioAuthHeader(accountSid, authToken) },
|
|
333
|
+
});
|
|
334
|
+
|
|
335
|
+
if (res.status === 404) {
|
|
336
|
+
return null;
|
|
337
|
+
}
|
|
338
|
+
|
|
339
|
+
if (!res.ok) {
|
|
340
|
+
const text = await res.text();
|
|
341
|
+
throw new ProviderError(`Twilio Toll-Free Verification fetch error ${res.status}: ${text}`, 'twilio', res.status);
|
|
342
|
+
}
|
|
343
|
+
|
|
344
|
+
const data = (await res.json()) as Record<string, unknown>;
|
|
345
|
+
return parseTollFreeVerification(data);
|
|
346
|
+
}
|
|
347
|
+
|
|
348
|
+
export interface TollFreeVerificationSubmitParams {
|
|
349
|
+
tollfreePhoneNumberSid: string;
|
|
350
|
+
businessName: string;
|
|
351
|
+
businessWebsite: string;
|
|
352
|
+
notificationEmail: string;
|
|
353
|
+
useCaseCategories: string[];
|
|
354
|
+
useCaseSummary: string;
|
|
355
|
+
productionMessageSample: string;
|
|
356
|
+
optInImageUrls: string[];
|
|
357
|
+
optInType: string;
|
|
358
|
+
messageVolume: string;
|
|
359
|
+
businessType?: string;
|
|
360
|
+
customerProfileSid?: string;
|
|
361
|
+
}
|
|
362
|
+
|
|
363
|
+
/** Submit a new toll-free verification request. */
|
|
364
|
+
export async function submitTollFreeVerification(
|
|
365
|
+
accountSid: string,
|
|
366
|
+
authToken: string,
|
|
367
|
+
params: TollFreeVerificationSubmitParams,
|
|
368
|
+
): Promise<TollFreeVerification> {
|
|
369
|
+
const body = new URLSearchParams();
|
|
370
|
+
body.set('TollfreePhoneNumberSid', params.tollfreePhoneNumberSid);
|
|
371
|
+
body.set('BusinessName', params.businessName);
|
|
372
|
+
body.set('BusinessWebsite', params.businessWebsite);
|
|
373
|
+
body.set('NotificationEmail', params.notificationEmail);
|
|
374
|
+
body.set('UseCaseSummary', params.useCaseSummary);
|
|
375
|
+
body.set('ProductionMessageSample', params.productionMessageSample);
|
|
376
|
+
body.set('OptInType', params.optInType);
|
|
377
|
+
body.set('MessageVolume', params.messageVolume);
|
|
378
|
+
body.set('BusinessType', params.businessType ?? 'SOLE_PROPRIETOR');
|
|
379
|
+
|
|
380
|
+
for (const cat of params.useCaseCategories) {
|
|
381
|
+
body.append('UseCaseCategories', cat);
|
|
382
|
+
}
|
|
383
|
+
for (const url of params.optInImageUrls) {
|
|
384
|
+
body.append('OptInImageUrls', url);
|
|
385
|
+
}
|
|
386
|
+
if (params.customerProfileSid) {
|
|
387
|
+
body.set('CustomerProfileSid', params.customerProfileSid);
|
|
388
|
+
}
|
|
389
|
+
|
|
390
|
+
const res = await fetch(TOLLFREE_VERIFICATION_BASE, {
|
|
391
|
+
method: 'POST',
|
|
392
|
+
headers: {
|
|
393
|
+
Authorization: twilioAuthHeader(accountSid, authToken),
|
|
394
|
+
'Content-Type': 'application/x-www-form-urlencoded',
|
|
395
|
+
},
|
|
396
|
+
body: body.toString(),
|
|
397
|
+
});
|
|
398
|
+
|
|
399
|
+
if (!res.ok) {
|
|
400
|
+
const text = await res.text();
|
|
401
|
+
throw new ProviderError(`Twilio Toll-Free Verification submit error ${res.status}: ${text}`, 'twilio', res.status);
|
|
402
|
+
}
|
|
403
|
+
|
|
404
|
+
const data = (await res.json()) as Record<string, unknown>;
|
|
405
|
+
return parseTollFreeVerification(data);
|
|
406
|
+
}
|
|
407
|
+
|
|
408
|
+
/** Update an existing toll-free verification. */
|
|
409
|
+
export async function updateTollFreeVerification(
|
|
410
|
+
accountSid: string,
|
|
411
|
+
authToken: string,
|
|
412
|
+
verificationSid: string,
|
|
413
|
+
params: Partial<TollFreeVerificationSubmitParams>,
|
|
414
|
+
): Promise<TollFreeVerification> {
|
|
415
|
+
const body = new URLSearchParams();
|
|
416
|
+
if (params.businessName) body.set('BusinessName', params.businessName);
|
|
417
|
+
if (params.businessWebsite) body.set('BusinessWebsite', params.businessWebsite);
|
|
418
|
+
if (params.notificationEmail) body.set('NotificationEmail', params.notificationEmail);
|
|
419
|
+
if (params.useCaseSummary) body.set('UseCaseSummary', params.useCaseSummary);
|
|
420
|
+
if (params.productionMessageSample) body.set('ProductionMessageSample', params.productionMessageSample);
|
|
421
|
+
if (params.optInType) body.set('OptInType', params.optInType);
|
|
422
|
+
if (params.messageVolume) body.set('MessageVolume', params.messageVolume);
|
|
423
|
+
if (params.businessType) body.set('BusinessType', params.businessType);
|
|
424
|
+
if (params.useCaseCategories) {
|
|
425
|
+
for (const cat of params.useCaseCategories) {
|
|
426
|
+
body.append('UseCaseCategories', cat);
|
|
427
|
+
}
|
|
428
|
+
}
|
|
429
|
+
if (params.optInImageUrls) {
|
|
430
|
+
for (const url of params.optInImageUrls) {
|
|
431
|
+
body.append('OptInImageUrls', url);
|
|
432
|
+
}
|
|
433
|
+
}
|
|
434
|
+
if (params.customerProfileSid) body.set('CustomerProfileSid', params.customerProfileSid);
|
|
435
|
+
|
|
436
|
+
const res = await fetch(`${TOLLFREE_VERIFICATION_BASE}/${encodeURIComponent(verificationSid)}`, {
|
|
437
|
+
method: 'POST',
|
|
438
|
+
headers: {
|
|
439
|
+
Authorization: twilioAuthHeader(accountSid, authToken),
|
|
440
|
+
'Content-Type': 'application/x-www-form-urlencoded',
|
|
441
|
+
},
|
|
442
|
+
body: body.toString(),
|
|
443
|
+
});
|
|
444
|
+
|
|
445
|
+
if (!res.ok) {
|
|
446
|
+
const text = await res.text();
|
|
447
|
+
throw new ProviderError(`Twilio Toll-Free Verification update error ${res.status}: ${text}`, 'twilio', res.status);
|
|
448
|
+
}
|
|
449
|
+
|
|
450
|
+
const data = (await res.json()) as Record<string, unknown>;
|
|
451
|
+
return parseTollFreeVerification(data);
|
|
452
|
+
}
|
|
453
|
+
|
|
454
|
+
/** Delete a toll-free verification. */
|
|
455
|
+
export async function deleteTollFreeVerification(
|
|
456
|
+
accountSid: string,
|
|
457
|
+
authToken: string,
|
|
458
|
+
verificationSid: string,
|
|
459
|
+
): Promise<void> {
|
|
460
|
+
const res = await fetch(`${TOLLFREE_VERIFICATION_BASE}/${encodeURIComponent(verificationSid)}`, {
|
|
461
|
+
method: 'DELETE',
|
|
462
|
+
headers: { Authorization: twilioAuthHeader(accountSid, authToken) },
|
|
463
|
+
});
|
|
464
|
+
|
|
465
|
+
if (!res.ok) {
|
|
466
|
+
const text = await res.text();
|
|
467
|
+
throw new ProviderError(`Twilio Toll-Free Verification delete error ${res.status}: ${text}`, 'twilio', res.status);
|
|
468
|
+
}
|
|
469
|
+
}
|
|
470
|
+
|
|
471
|
+
/**
|
|
472
|
+
* Get the SID for an incoming phone number.
|
|
473
|
+
* Looks up the number via `IncomingPhoneNumbers.json?PhoneNumber=...`.
|
|
474
|
+
*/
|
|
475
|
+
export async function getPhoneNumberSid(
|
|
476
|
+
accountSid: string,
|
|
477
|
+
authToken: string,
|
|
478
|
+
phoneNumber: string,
|
|
479
|
+
): Promise<string | null> {
|
|
480
|
+
const res = await fetch(
|
|
481
|
+
`${twilioBaseUrl(accountSid)}/IncomingPhoneNumbers.json?PhoneNumber=${encodeURIComponent(phoneNumber)}`,
|
|
482
|
+
{
|
|
483
|
+
method: 'GET',
|
|
484
|
+
headers: { Authorization: twilioAuthHeader(accountSid, authToken) },
|
|
485
|
+
},
|
|
486
|
+
);
|
|
487
|
+
|
|
488
|
+
if (!res.ok) {
|
|
489
|
+
const text = await res.text();
|
|
490
|
+
throw new ProviderError(`Twilio API error ${res.status} looking up phone number SID: ${text}`, 'twilio', res.status);
|
|
491
|
+
}
|
|
492
|
+
|
|
493
|
+
const data = (await res.json()) as {
|
|
494
|
+
incoming_phone_numbers: Array<{ sid: string; phone_number: string }>;
|
|
495
|
+
};
|
|
496
|
+
|
|
497
|
+
const match = data.incoming_phone_numbers.find((n) => n.phone_number === phoneNumber);
|
|
498
|
+
return match?.sid ?? null;
|
|
499
|
+
}
|
|
500
|
+
|
|
501
|
+
/**
|
|
502
|
+
* Release (delete) an incoming phone number from the Twilio account.
|
|
503
|
+
* Looks up the SID by phone number then sends a DELETE request.
|
|
504
|
+
*/
|
|
505
|
+
export async function releasePhoneNumber(
|
|
506
|
+
accountSid: string,
|
|
507
|
+
authToken: string,
|
|
508
|
+
phoneNumber: string,
|
|
509
|
+
): Promise<void> {
|
|
510
|
+
const sid = await getPhoneNumberSid(accountSid, authToken, phoneNumber);
|
|
511
|
+
if (!sid) {
|
|
512
|
+
throw new ProviderError(`Phone number ${phoneNumber} not found on Twilio account ${accountSid}`, 'twilio');
|
|
513
|
+
}
|
|
514
|
+
|
|
515
|
+
const res = await fetch(
|
|
516
|
+
`${twilioBaseUrl(accountSid)}/IncomingPhoneNumbers/${sid}.json`,
|
|
517
|
+
{
|
|
518
|
+
method: 'DELETE',
|
|
519
|
+
headers: { Authorization: twilioAuthHeader(accountSid, authToken) },
|
|
520
|
+
},
|
|
521
|
+
);
|
|
522
|
+
|
|
523
|
+
if (!res.ok) {
|
|
524
|
+
const text = await res.text();
|
|
525
|
+
throw new ProviderError(`Twilio API error ${res.status} releasing phone number: ${text}`, 'twilio', res.status);
|
|
225
526
|
}
|
|
226
527
|
}
|
|
@@ -7,6 +7,7 @@
|
|
|
7
7
|
*/
|
|
8
8
|
|
|
9
9
|
import { getLogger } from '../util/logger.js';
|
|
10
|
+
import { getCallWelcomeGreeting } from '../config/env.js';
|
|
10
11
|
import {
|
|
11
12
|
getCallSession,
|
|
12
13
|
getCallSessionByCallSid,
|
|
@@ -25,7 +26,9 @@ import { getTwilioConfig } from './twilio-config.js';
|
|
|
25
26
|
import { loadConfig } from '../config/loader.js';
|
|
26
27
|
import { getTwilioRelayUrl } from '../inbound/public-ingress-urls.js';
|
|
27
28
|
import { fireCallCompletionNotifier } from './call-state.js';
|
|
29
|
+
import { persistCallCompletionMessage } from './call-conversation-messages.js';
|
|
28
30
|
import { resolveVoiceQualityProfile, isVoiceProfileValid } from './voice-quality.js';
|
|
31
|
+
import { createInboundVoiceSession } from './call-domain.js';
|
|
29
32
|
|
|
30
33
|
const log = getLogger('twilio-routes');
|
|
31
34
|
|
|
@@ -43,15 +46,18 @@ function escapeXml(str: string): string {
|
|
|
43
46
|
export function generateTwiML(
|
|
44
47
|
callSessionId: string,
|
|
45
48
|
relayUrl: string,
|
|
46
|
-
welcomeGreeting: string,
|
|
49
|
+
welcomeGreeting: string | null,
|
|
47
50
|
profile: { language: string; transcriptionProvider: string; ttsProvider: string; voice: string },
|
|
48
51
|
): string {
|
|
52
|
+
const greetingAttr = welcomeGreeting && welcomeGreeting.trim().length > 0
|
|
53
|
+
? `\n welcomeGreeting="${escapeXml(welcomeGreeting.trim())}"`
|
|
54
|
+
: '';
|
|
49
55
|
return `<?xml version="1.0" encoding="UTF-8"?>
|
|
50
56
|
<Response>
|
|
51
57
|
<Connect>
|
|
52
58
|
<ConversationRelay
|
|
53
59
|
url="${escapeXml(relayUrl)}?callSessionId=${escapeXml(callSessionId)}"
|
|
54
|
-
|
|
60
|
+
${greetingAttr}
|
|
55
61
|
voice="${escapeXml(profile.voice)}"
|
|
56
62
|
language="${escapeXml(profile.language)}"
|
|
57
63
|
transcriptionProvider="${escapeXml(profile.transcriptionProvider)}"
|
|
@@ -63,6 +69,16 @@ export function generateTwiML(
|
|
|
63
69
|
</Response>`;
|
|
64
70
|
}
|
|
65
71
|
|
|
72
|
+
export function buildWelcomeGreeting(task: string | null, configuredGreeting?: string): string {
|
|
73
|
+
void task;
|
|
74
|
+
const override = configuredGreeting?.trim();
|
|
75
|
+
if (override) return override;
|
|
76
|
+
// The contextual first opener now comes from the call orchestrator's
|
|
77
|
+
// initial LLM turn. Keep Twilio's relay-level greeting empty by default
|
|
78
|
+
// so we don't speak a deterministic static line first.
|
|
79
|
+
return '';
|
|
80
|
+
}
|
|
81
|
+
|
|
66
82
|
/**
|
|
67
83
|
* Resolve the WebSocket relay URL from Twilio config.
|
|
68
84
|
*
|
|
@@ -106,16 +122,43 @@ function mapTwilioStatus(twilioStatus: string): CallStatus | null {
|
|
|
106
122
|
/**
|
|
107
123
|
* Receives the initial voice webhook when Twilio connects the call.
|
|
108
124
|
* Returns TwiML XML that tells Twilio to open a ConversationRelay WebSocket.
|
|
125
|
+
*
|
|
126
|
+
* Supports two modes:
|
|
127
|
+
* - **Outbound** (callSessionId present in query): uses the existing session
|
|
128
|
+
* - **Inbound** (callSessionId absent): creates or reuses a session keyed
|
|
129
|
+
* by the Twilio CallSid. The optional `forwardedAssistantId` is resolved
|
|
130
|
+
* by the gateway from the "To" phone number.
|
|
109
131
|
*/
|
|
110
|
-
export async function handleVoiceWebhook(req: Request): Promise<Response> {
|
|
132
|
+
export async function handleVoiceWebhook(req: Request, forwardedAssistantId?: string): Promise<Response> {
|
|
111
133
|
const url = new URL(req.url);
|
|
112
134
|
const callSessionId = url.searchParams.get('callSessionId');
|
|
113
135
|
|
|
136
|
+
// Parse the Twilio POST body to capture CallSid and caller metadata.
|
|
137
|
+
const formBody = new URLSearchParams(await req.text());
|
|
138
|
+
const callSid = formBody.get('CallSid');
|
|
139
|
+
const callerFrom = formBody.get('From') ?? '';
|
|
140
|
+
const callerTo = formBody.get('To') ?? '';
|
|
141
|
+
|
|
142
|
+
// ── Inbound mode: no callSessionId in query ─────────────────────
|
|
114
143
|
if (!callSessionId) {
|
|
115
|
-
|
|
116
|
-
|
|
144
|
+
if (!callSid) {
|
|
145
|
+
log.warn('Inbound voice webhook called without CallSid');
|
|
146
|
+
return new Response('Missing CallSid', { status: 400 });
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
log.info({ callSid, from: callerFrom, to: callerTo, assistantId: forwardedAssistantId }, 'Inbound voice webhook — creating/reusing session');
|
|
150
|
+
|
|
151
|
+
const { session } = createInboundVoiceSession({
|
|
152
|
+
callSid,
|
|
153
|
+
fromNumber: callerFrom,
|
|
154
|
+
toNumber: callerTo,
|
|
155
|
+
assistantId: forwardedAssistantId,
|
|
156
|
+
});
|
|
157
|
+
|
|
158
|
+
return buildVoiceWebhookTwiml(session.id, session.assistantId ?? undefined, session.task);
|
|
117
159
|
}
|
|
118
160
|
|
|
161
|
+
// ── Outbound mode: callSessionId is present ─────────────────────
|
|
119
162
|
const session = getCallSession(callSessionId);
|
|
120
163
|
if (!session) {
|
|
121
164
|
log.warn({ callSessionId }, 'Voice webhook: call session not found');
|
|
@@ -127,16 +170,25 @@ export async function handleVoiceWebhook(req: Request): Promise<Response> {
|
|
|
127
170
|
return new Response('Call session is no longer active', { status: 410 });
|
|
128
171
|
}
|
|
129
172
|
|
|
130
|
-
//
|
|
131
|
-
// callbacks (keyed by CallSid) can locate this session even if the
|
|
132
|
-
// WebSocket relay hasn't been set up yet.
|
|
133
|
-
const formBody = new URLSearchParams(await req.text());
|
|
134
|
-
const callSid = formBody.get('CallSid');
|
|
173
|
+
// Capture CallSid immediately so status callbacks can locate this session
|
|
135
174
|
if (callSid && callSid !== session.providerCallSid) {
|
|
136
175
|
updateCallSession(callSessionId, { providerCallSid: callSid });
|
|
137
176
|
log.info({ callSessionId, callSid }, 'Stored CallSid from voice webhook');
|
|
138
177
|
}
|
|
139
178
|
|
|
179
|
+
return buildVoiceWebhookTwiml(callSessionId, session.assistantId ?? undefined, session.task);
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
/**
|
|
183
|
+
* Shared TwiML generation for both inbound and outbound voice webhooks.
|
|
184
|
+
* Resolves voice quality profile, relay URL, and welcome greeting,
|
|
185
|
+
* then returns a Response with the generated TwiML.
|
|
186
|
+
*/
|
|
187
|
+
function buildVoiceWebhookTwiml(
|
|
188
|
+
callSessionId: string,
|
|
189
|
+
assistantId: string | undefined,
|
|
190
|
+
task: string | null,
|
|
191
|
+
): Response {
|
|
140
192
|
let profile = resolveVoiceQualityProfile(loadConfig());
|
|
141
193
|
|
|
142
194
|
log.info({ callSessionId, mode: profile.mode, ttsProvider: profile.ttsProvider, voice: profile.voice }, 'Voice quality profile resolved');
|
|
@@ -175,7 +227,7 @@ export async function handleVoiceWebhook(req: Request): Promise<Response> {
|
|
|
175
227
|
});
|
|
176
228
|
}
|
|
177
229
|
|
|
178
|
-
const twilioConfig = getTwilioConfig();
|
|
230
|
+
const twilioConfig = getTwilioConfig(assistantId);
|
|
179
231
|
let relayUrl: string;
|
|
180
232
|
try {
|
|
181
233
|
relayUrl = getTwilioRelayUrl(loadConfig());
|
|
@@ -183,7 +235,7 @@ export async function handleVoiceWebhook(req: Request): Promise<Response> {
|
|
|
183
235
|
// Fallback to legacy resolution when ingress is not configured
|
|
184
236
|
relayUrl = resolveRelayUrl(twilioConfig.wssBaseUrl, twilioConfig.webhookBaseUrl);
|
|
185
237
|
}
|
|
186
|
-
const welcomeGreeting =
|
|
238
|
+
const welcomeGreeting = buildWelcomeGreeting(task, getCallWelcomeGreeting());
|
|
187
239
|
|
|
188
240
|
const twiml = generateTwiML(callSessionId, relayUrl, welcomeGreeting, profile);
|
|
189
241
|
|
|
@@ -270,6 +322,7 @@ export async function handleStatusCallback(req: Request): Promise<Response> {
|
|
|
270
322
|
expirePendingQuestions(session.id);
|
|
271
323
|
|
|
272
324
|
if (!wasTerminal) {
|
|
325
|
+
persistCallCompletionMessage(session.conversationId, session.id);
|
|
273
326
|
fireCallCompletionNotifier(session.conversationId, session.id);
|
|
274
327
|
}
|
|
275
328
|
}
|
package/src/calls/types.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
export type CallStatus = 'initiated' | 'ringing' | 'in_progress' | 'waiting_on_user' | 'completed' | 'failed' | 'cancelled';
|
|
2
|
-
export type CallEventType = 'call_started' | 'call_connected' | 'caller_spoke' | 'assistant_spoke' | 'user_question_asked' | 'user_answered' | 'user_instruction_relayed' | 'call_ended' | 'call_failed';
|
|
2
|
+
export type CallEventType = 'call_started' | 'call_connected' | 'caller_spoke' | 'assistant_spoke' | 'user_question_asked' | 'user_answered' | 'user_instruction_relayed' | 'call_ended' | 'call_failed' | 'callee_verification_started' | 'callee_verification_succeeded' | 'callee_verification_failed' | 'guardian_voice_verification_started' | 'guardian_voice_verification_succeeded' | 'guardian_voice_verification_failed';
|
|
3
3
|
export type PendingQuestionStatus = 'pending' | 'answered' | 'expired' | 'cancelled';
|
|
4
4
|
|
|
5
5
|
export interface CallSession {
|
|
@@ -13,6 +13,8 @@ export interface CallSession {
|
|
|
13
13
|
status: CallStatus;
|
|
14
14
|
callerIdentityMode: string | null;
|
|
15
15
|
callerIdentitySource: string | null;
|
|
16
|
+
assistantId: string | null;
|
|
17
|
+
initiatedFromConversationId?: string | null;
|
|
16
18
|
startedAt: number | null;
|
|
17
19
|
endedAt: number | null;
|
|
18
20
|
lastError: string | null;
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
export const CHANNEL_IDS = [
|
|
2
|
+
'telegram', 'sms', 'voice', 'macos', 'ios', 'whatsapp', 'slack', 'email',
|
|
3
|
+
] as const;
|
|
4
|
+
|
|
5
|
+
export type ChannelId = (typeof CHANNEL_IDS)[number];
|
|
6
|
+
|
|
7
|
+
export function isChannelId(value: unknown): value is ChannelId {
|
|
8
|
+
return typeof value === 'string' && (CHANNEL_IDS as readonly string[]).includes(value);
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
export function parseChannelId(value: unknown): ChannelId | null {
|
|
12
|
+
return isChannelId(value) ? value : null;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
export function assertChannelId(value: unknown, field: string): ChannelId {
|
|
16
|
+
if (!isChannelId(value)) {
|
|
17
|
+
throw new Error(`Invalid channel ID for ${field}: ${String(value)}. Valid values: ${CHANNEL_IDS.join(', ')}`);
|
|
18
|
+
}
|
|
19
|
+
return value;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
export interface TurnChannelContext {
|
|
23
|
+
userMessageChannel: ChannelId;
|
|
24
|
+
assistantMessageChannel: ChannelId;
|
|
25
|
+
}
|