@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
|
@@ -89,7 +89,7 @@ export function runWorkItemInBackground(workItemId: string): RunWorkItemResult {
|
|
|
89
89
|
|
|
90
90
|
// Resolve required tools
|
|
91
91
|
let requiredTools: string[];
|
|
92
|
-
if (workItem.requiredTools
|
|
92
|
+
if (workItem.requiredTools != null) {
|
|
93
93
|
requiredTools = sanitizeToolList(JSON.parse(workItem.requiredTools));
|
|
94
94
|
} else {
|
|
95
95
|
requiredTools = task.requiredTools
|
|
@@ -651,7 +651,7 @@ export class WorkspaceGitService {
|
|
|
651
651
|
return;
|
|
652
652
|
}
|
|
653
653
|
|
|
654
|
-
const state = currentBranch
|
|
654
|
+
const state = currentBranch == null ? 'detached HEAD' : `branch '${currentBranch}'`;
|
|
655
655
|
log.warn(
|
|
656
656
|
{ workspaceDir: this.workspaceDir, currentBranch },
|
|
657
657
|
`Workspace repo is on ${state}; auto-switching to main`,
|
|
@@ -3,6 +3,7 @@ import { getConfig } from '../config/loader.js';
|
|
|
3
3
|
import type { CommitContext } from './commit-message-provider.js';
|
|
4
4
|
import { DefaultCommitMessageProvider } from './commit-message-provider.js';
|
|
5
5
|
import type { Message } from '../providers/types.js';
|
|
6
|
+
import { resolveConfiguredProvider } from '../providers/provider-send-message.js';
|
|
6
7
|
|
|
7
8
|
const log = getLogger('commit-message-llm');
|
|
8
9
|
|
|
@@ -49,6 +50,18 @@ const KEYLESS_PROVIDERS = new Set(['ollama']);
|
|
|
49
50
|
|
|
50
51
|
const deterministicProvider = new DefaultCommitMessageProvider();
|
|
51
52
|
|
|
53
|
+
function getProviderCandidates(config: ReturnType<typeof getConfig>): string[] {
|
|
54
|
+
const order = Array.isArray(config.providerOrder) ? config.providerOrder : [];
|
|
55
|
+
const seen = new Set<string>();
|
|
56
|
+
const out: string[] = [];
|
|
57
|
+
for (const name of [config.provider, ...order]) {
|
|
58
|
+
if (seen.has(name)) continue;
|
|
59
|
+
seen.add(name);
|
|
60
|
+
out.push(name);
|
|
61
|
+
}
|
|
62
|
+
return out;
|
|
63
|
+
}
|
|
64
|
+
|
|
52
65
|
function buildDeterministicResult(
|
|
53
66
|
context: CommitContext,
|
|
54
67
|
reason: LLMFallbackReason,
|
|
@@ -106,11 +119,12 @@ export class ProviderCommitMessageGenerator {
|
|
|
106
119
|
|
|
107
120
|
// ── Fallback check order (canonical) ──────────────────────────────
|
|
108
121
|
// 1. disabled
|
|
109
|
-
// 2.
|
|
110
|
-
//
|
|
111
|
-
//
|
|
112
|
-
//
|
|
113
|
-
//
|
|
122
|
+
// 2. resolve configured provider via fail-open selection:
|
|
123
|
+
// - missing_provider_api_key OR provider_not_initialized
|
|
124
|
+
// 3. selected-provider API key preflight (except keyless providers)
|
|
125
|
+
// 4. breaker_open
|
|
126
|
+
// 5. insufficient_budget
|
|
127
|
+
// 6. missing_fast_model
|
|
114
128
|
// 7. call provider → timeout / provider_error / invalid_output
|
|
115
129
|
// ──────────────────────────────────────────────────────────────────
|
|
116
130
|
|
|
@@ -122,11 +136,36 @@ export class ProviderCommitMessageGenerator {
|
|
|
122
136
|
return buildDeterministicResult(context, 'disabled');
|
|
123
137
|
}
|
|
124
138
|
|
|
125
|
-
// Step 2:
|
|
126
|
-
|
|
127
|
-
|
|
139
|
+
// Step 2: Resolve configured provider using fail-open semantics.
|
|
140
|
+
// If nothing is resolvable, differentiate likely missing-key cases from
|
|
141
|
+
// true registry/init failures.
|
|
142
|
+
const resolved = resolveConfiguredProvider();
|
|
143
|
+
if (!resolved) {
|
|
144
|
+
const candidates = getProviderCandidates(config);
|
|
145
|
+
const hasAnyKeylessCandidate = candidates.some((name) => KEYLESS_PROVIDERS.has(name));
|
|
146
|
+
const hasAnyProviderKey = candidates.some((name) => {
|
|
147
|
+
const value = config.apiKeys[name];
|
|
148
|
+
return typeof value === 'string' && value.length > 0;
|
|
149
|
+
});
|
|
150
|
+
if (!hasAnyKeylessCandidate && !hasAnyProviderKey) {
|
|
151
|
+
log.debug('No API keys available for configured/fallback providers; falling back to deterministic');
|
|
152
|
+
return buildDeterministicResult(context, 'missing_provider_api_key');
|
|
153
|
+
}
|
|
154
|
+
log.debug({ provider: config.provider }, 'Provider not initialized; falling back to deterministic');
|
|
155
|
+
return buildDeterministicResult(context, 'provider_not_initialized');
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
const provider = resolved.provider;
|
|
159
|
+
const selectedProviderName = resolved.selectedProviderName;
|
|
160
|
+
|
|
161
|
+
// Step 2b: API key preflight for the selected provider (skip keyless).
|
|
162
|
+
if (!KEYLESS_PROVIDERS.has(selectedProviderName)) {
|
|
163
|
+
const providerApiKey = config.apiKeys[selectedProviderName];
|
|
128
164
|
if (!providerApiKey || providerApiKey === '') {
|
|
129
|
-
log.debug(
|
|
165
|
+
log.debug(
|
|
166
|
+
{ selectedProvider: selectedProviderName, configuredProvider: config.provider },
|
|
167
|
+
'Selected provider API key missing; falling back to deterministic',
|
|
168
|
+
);
|
|
130
169
|
return buildDeterministicResult(context, 'missing_provider_api_key');
|
|
131
170
|
}
|
|
132
171
|
}
|
|
@@ -153,12 +192,12 @@ export class ProviderCommitMessageGenerator {
|
|
|
153
192
|
}
|
|
154
193
|
|
|
155
194
|
// Step 5: Fast model preflight — resolve before any provider call
|
|
156
|
-
const fastModel = llmConfig.providerFastModelOverrides[
|
|
157
|
-
?? PROVIDER_DEFAULT_FAST_MODELS[
|
|
195
|
+
const fastModel = llmConfig.providerFastModelOverrides[selectedProviderName]
|
|
196
|
+
?? PROVIDER_DEFAULT_FAST_MODELS[selectedProviderName];
|
|
158
197
|
|
|
159
198
|
if (!fastModel) {
|
|
160
199
|
log.debug(
|
|
161
|
-
{ provider: config.provider },
|
|
200
|
+
{ provider: selectedProviderName, configuredProvider: config.provider },
|
|
162
201
|
'No fast model resolvable for provider; falling back to deterministic',
|
|
163
202
|
);
|
|
164
203
|
return buildDeterministicResult(context, 'missing_fast_model');
|
|
@@ -166,16 +205,6 @@ export class ProviderCommitMessageGenerator {
|
|
|
166
205
|
|
|
167
206
|
// Step 6 + 7: Call the provider
|
|
168
207
|
try {
|
|
169
|
-
const { getProvider } = await import('../providers/registry.js');
|
|
170
|
-
|
|
171
|
-
let provider;
|
|
172
|
-
try {
|
|
173
|
-
provider = getProvider(config.provider);
|
|
174
|
-
} catch {
|
|
175
|
-
log.debug({ provider: config.provider }, 'Provider not initialized; falling back to deterministic');
|
|
176
|
-
return buildDeterministicResult(context, 'provider_not_initialized');
|
|
177
|
-
}
|
|
178
|
-
|
|
179
208
|
// Build prompt
|
|
180
209
|
const fileList = options.changedFiles
|
|
181
210
|
.slice(0, llmConfig.maxFilesInPrompt)
|
|
@@ -1,517 +0,0 @@
|
|
|
1
|
-
import { describe, test, expect, beforeEach, afterAll, mock } from 'bun:test';
|
|
2
|
-
import { mkdtempSync, rmSync } from 'node:fs';
|
|
3
|
-
import { tmpdir } from 'node:os';
|
|
4
|
-
import { join } from 'node:path';
|
|
5
|
-
import { EventEmitter } from 'node:events';
|
|
6
|
-
|
|
7
|
-
const testDir = mkdtempSync(join(tmpdir(), 'call-bridge-test-'));
|
|
8
|
-
|
|
9
|
-
// ── Platform + logger mocks (must come before any source imports) ────
|
|
10
|
-
|
|
11
|
-
mock.module('../util/platform.js', () => ({
|
|
12
|
-
getDataDir: () => testDir,
|
|
13
|
-
isMacOS: () => process.platform === 'darwin',
|
|
14
|
-
isLinux: () => process.platform === 'linux',
|
|
15
|
-
isWindows: () => process.platform === 'win32',
|
|
16
|
-
getSocketPath: () => join(testDir, 'test.sock'),
|
|
17
|
-
getPidPath: () => join(testDir, 'test.pid'),
|
|
18
|
-
getDbPath: () => join(testDir, 'test.db'),
|
|
19
|
-
getLogPath: () => join(testDir, 'test.log'),
|
|
20
|
-
ensureDataDir: () => {},
|
|
21
|
-
}));
|
|
22
|
-
|
|
23
|
-
mock.module('../util/logger.js', () => ({
|
|
24
|
-
getLogger: () =>
|
|
25
|
-
new Proxy({} as Record<string, unknown>, {
|
|
26
|
-
get: () => () => {},
|
|
27
|
-
}),
|
|
28
|
-
}));
|
|
29
|
-
|
|
30
|
-
// ── Config mock ─────────────────────────────────────────────────────
|
|
31
|
-
|
|
32
|
-
mock.module('../config/loader.js', () => ({
|
|
33
|
-
getConfig: () => ({
|
|
34
|
-
apiKeys: { anthropic: 'test-key' },
|
|
35
|
-
memory: { enabled: false },
|
|
36
|
-
calls: {
|
|
37
|
-
enabled: true,
|
|
38
|
-
provider: 'twilio',
|
|
39
|
-
maxDurationSeconds: 3600,
|
|
40
|
-
userConsultTimeoutSeconds: 120,
|
|
41
|
-
disclosure: { enabled: false, text: '' },
|
|
42
|
-
safety: { denyCategories: [] },
|
|
43
|
-
},
|
|
44
|
-
}),
|
|
45
|
-
}));
|
|
46
|
-
|
|
47
|
-
// ── Anthropic SDK mock ──────────────────────────────────────────────
|
|
48
|
-
|
|
49
|
-
function createMockStream(tokens: string[]) {
|
|
50
|
-
const emitter = new EventEmitter();
|
|
51
|
-
const fullText = tokens.join('');
|
|
52
|
-
|
|
53
|
-
const stream = {
|
|
54
|
-
on: (event: string, handler: (...args: unknown[]) => void) => {
|
|
55
|
-
emitter.on(event, handler);
|
|
56
|
-
return stream;
|
|
57
|
-
},
|
|
58
|
-
finalMessage: () => {
|
|
59
|
-
for (const token of tokens) {
|
|
60
|
-
emitter.emit('text', token);
|
|
61
|
-
}
|
|
62
|
-
return Promise.resolve({
|
|
63
|
-
content: [{ type: 'text', text: fullText }],
|
|
64
|
-
});
|
|
65
|
-
},
|
|
66
|
-
};
|
|
67
|
-
|
|
68
|
-
return stream;
|
|
69
|
-
}
|
|
70
|
-
|
|
71
|
-
const mockStreamFn = mock((..._args: unknown[]) => createMockStream(['Hello']));
|
|
72
|
-
|
|
73
|
-
mock.module('@anthropic-ai/sdk', () => ({
|
|
74
|
-
default: class MockAnthropic {
|
|
75
|
-
messages = {
|
|
76
|
-
stream: (...args: unknown[]) => mockStreamFn(...args),
|
|
77
|
-
};
|
|
78
|
-
},
|
|
79
|
-
}));
|
|
80
|
-
|
|
81
|
-
// ── Import source modules after all mocks ───────────────────────────
|
|
82
|
-
|
|
83
|
-
import { initializeDb, getDb, resetDb } from '../memory/db.js';
|
|
84
|
-
import { conversations } from '../memory/schema.js';
|
|
85
|
-
import {
|
|
86
|
-
createCallSession,
|
|
87
|
-
getPendingQuestion,
|
|
88
|
-
updateCallSession,
|
|
89
|
-
recordCallEvent,
|
|
90
|
-
createPendingQuestion,
|
|
91
|
-
} from '../calls/call-store.js';
|
|
92
|
-
import {
|
|
93
|
-
registerCallQuestionNotifier,
|
|
94
|
-
unregisterCallQuestionNotifier,
|
|
95
|
-
registerCallTranscriptNotifier,
|
|
96
|
-
unregisterCallTranscriptNotifier,
|
|
97
|
-
fireCallTranscriptNotifier,
|
|
98
|
-
registerCallCompletionNotifier,
|
|
99
|
-
unregisterCallCompletionNotifier,
|
|
100
|
-
fireCallQuestionNotifier,
|
|
101
|
-
fireCallCompletionNotifier,
|
|
102
|
-
} from '../calls/call-state.js';
|
|
103
|
-
import { CallOrchestrator } from '../calls/call-orchestrator.js';
|
|
104
|
-
import { tryRouteCallMessage } from '../calls/call-bridge.js';
|
|
105
|
-
import * as conversationStore from '../memory/conversation-store.js';
|
|
106
|
-
import type { RelayConnection } from '../calls/relay-server.js';
|
|
107
|
-
|
|
108
|
-
initializeDb();
|
|
109
|
-
|
|
110
|
-
afterAll(() => {
|
|
111
|
-
resetDb();
|
|
112
|
-
try {
|
|
113
|
-
rmSync(testDir, { recursive: true });
|
|
114
|
-
} catch {
|
|
115
|
-
/* best effort */
|
|
116
|
-
}
|
|
117
|
-
});
|
|
118
|
-
|
|
119
|
-
// ── Relay mock factory ──────────────────────────────────────────────
|
|
120
|
-
|
|
121
|
-
interface MockRelay extends RelayConnection {
|
|
122
|
-
sentTokens: Array<{ token: string; last: boolean }>;
|
|
123
|
-
endCalled: boolean;
|
|
124
|
-
}
|
|
125
|
-
|
|
126
|
-
function createMockRelay(): MockRelay {
|
|
127
|
-
const state = {
|
|
128
|
-
sentTokens: [] as Array<{ token: string; last: boolean }>,
|
|
129
|
-
_endCalled: false,
|
|
130
|
-
};
|
|
131
|
-
|
|
132
|
-
return {
|
|
133
|
-
get sentTokens() { return state.sentTokens; },
|
|
134
|
-
get endCalled() { return state._endCalled; },
|
|
135
|
-
sendTextToken(token: string, last: boolean) {
|
|
136
|
-
state.sentTokens.push({ token, last });
|
|
137
|
-
},
|
|
138
|
-
endSession(_reason?: string) {
|
|
139
|
-
state._endCalled = true;
|
|
140
|
-
},
|
|
141
|
-
} as unknown as MockRelay;
|
|
142
|
-
}
|
|
143
|
-
|
|
144
|
-
// ── Helpers ─────────────────────────────────────────────────────────
|
|
145
|
-
|
|
146
|
-
let ensuredConvIds = new Set<string>();
|
|
147
|
-
function ensureConversation(id: string): void {
|
|
148
|
-
if (ensuredConvIds.has(id)) return;
|
|
149
|
-
const db = getDb();
|
|
150
|
-
const now = Date.now();
|
|
151
|
-
db.insert(conversations).values({
|
|
152
|
-
id,
|
|
153
|
-
title: `Test conversation ${id}`,
|
|
154
|
-
createdAt: now,
|
|
155
|
-
updatedAt: now,
|
|
156
|
-
}).run();
|
|
157
|
-
ensuredConvIds.add(id);
|
|
158
|
-
}
|
|
159
|
-
|
|
160
|
-
function resetTables() {
|
|
161
|
-
const db = getDb();
|
|
162
|
-
db.run('DELETE FROM call_pending_questions');
|
|
163
|
-
db.run('DELETE FROM call_events');
|
|
164
|
-
db.run('DELETE FROM call_sessions');
|
|
165
|
-
db.run('DELETE FROM messages');
|
|
166
|
-
db.run('DELETE FROM conversations');
|
|
167
|
-
ensuredConvIds = new Set();
|
|
168
|
-
}
|
|
169
|
-
|
|
170
|
-
function getMessagesForConversation(conversationId: string) {
|
|
171
|
-
return conversationStore.getMessages(conversationId);
|
|
172
|
-
}
|
|
173
|
-
|
|
174
|
-
describe('call-bridge', () => {
|
|
175
|
-
beforeEach(() => {
|
|
176
|
-
resetTables();
|
|
177
|
-
mockStreamFn.mockImplementation(() => createMockStream(['Hello']));
|
|
178
|
-
});
|
|
179
|
-
|
|
180
|
-
// ── tryRouteCallMessage — answer path ───────────────────────
|
|
181
|
-
|
|
182
|
-
test('returns handled:false when no active call exists', async () => {
|
|
183
|
-
ensureConversation('conv-no-call');
|
|
184
|
-
const result = await tryRouteCallMessage('conv-no-call', 'some answer');
|
|
185
|
-
expect(result.handled).toBe(false);
|
|
186
|
-
expect(result.reason).toBe('no_active_call');
|
|
187
|
-
});
|
|
188
|
-
|
|
189
|
-
test('returns instruction_relay_failed (consumed) when call exists but no orchestrator and no pending question', async () => {
|
|
190
|
-
ensureConversation('conv-no-orch');
|
|
191
|
-
createCallSession({
|
|
192
|
-
conversationId: 'conv-no-orch',
|
|
193
|
-
provider: 'twilio',
|
|
194
|
-
fromNumber: '+15551111111',
|
|
195
|
-
toNumber: '+15552222222',
|
|
196
|
-
});
|
|
197
|
-
const result = await tryRouteCallMessage('conv-no-orch', 'some instruction');
|
|
198
|
-
expect(result.handled).toBe(true);
|
|
199
|
-
expect(result.reason).toBe('instruction_relay_failed');
|
|
200
|
-
expect(result.userFacingText).toBe('Failed to relay instruction to the active call.');
|
|
201
|
-
});
|
|
202
|
-
|
|
203
|
-
test('returns handled:false when orchestrator is not found (call still active but no orchestrator)', async () => {
|
|
204
|
-
ensureConversation('conv-ended');
|
|
205
|
-
const callSession = createCallSession({
|
|
206
|
-
conversationId: 'conv-ended',
|
|
207
|
-
provider: 'twilio',
|
|
208
|
-
fromNumber: '+15551111111',
|
|
209
|
-
toNumber: '+15552222222',
|
|
210
|
-
});
|
|
211
|
-
// Leave the session in an active (non-terminal) state but do NOT register an orchestrator.
|
|
212
|
-
// This simulates a race where the orchestrator was destroyed but the session hasn't
|
|
213
|
-
// been marked terminal yet.
|
|
214
|
-
updateCallSession(callSession.id, { status: 'in_progress' });
|
|
215
|
-
|
|
216
|
-
// Create a pending question without an orchestrator
|
|
217
|
-
createPendingQuestion(callSession.id, 'What time?');
|
|
218
|
-
|
|
219
|
-
const result = await tryRouteCallMessage('conv-ended', 'Too late');
|
|
220
|
-
expect(result.handled).toBe(false);
|
|
221
|
-
expect(result.reason).toBe('orchestrator_not_found');
|
|
222
|
-
});
|
|
223
|
-
|
|
224
|
-
test('returns no_active_call when call has already completed', async () => {
|
|
225
|
-
ensureConversation('conv-completed');
|
|
226
|
-
const callSession = createCallSession({
|
|
227
|
-
conversationId: 'conv-completed',
|
|
228
|
-
provider: 'twilio',
|
|
229
|
-
fromNumber: '+15551111111',
|
|
230
|
-
toNumber: '+15552222222',
|
|
231
|
-
});
|
|
232
|
-
// Mark the call as completed — getActiveCallSessionForConversation will return null
|
|
233
|
-
updateCallSession(callSession.id, { status: 'completed', endedAt: Date.now() });
|
|
234
|
-
|
|
235
|
-
const result = await tryRouteCallMessage('conv-completed', 'Too late');
|
|
236
|
-
expect(result.handled).toBe(false);
|
|
237
|
-
expect(result.reason).toBe('no_active_call');
|
|
238
|
-
});
|
|
239
|
-
|
|
240
|
-
test('returns handled:false when orchestrator is not in waiting_on_user state', async () => {
|
|
241
|
-
ensureConversation('conv-not-waiting');
|
|
242
|
-
const callSession = createCallSession({
|
|
243
|
-
conversationId: 'conv-not-waiting',
|
|
244
|
-
provider: 'twilio',
|
|
245
|
-
fromNumber: '+15551111111',
|
|
246
|
-
toNumber: '+15552222222',
|
|
247
|
-
});
|
|
248
|
-
|
|
249
|
-
// Create orchestrator (state=idle by default)
|
|
250
|
-
const relay = createMockRelay();
|
|
251
|
-
const orchestrator = new CallOrchestrator(callSession.id, relay as unknown as RelayConnection, null);
|
|
252
|
-
|
|
253
|
-
// Create a pending question in the DB but orchestrator is idle, not waiting_on_user
|
|
254
|
-
createPendingQuestion(callSession.id, 'What time?');
|
|
255
|
-
|
|
256
|
-
const result = await tryRouteCallMessage('conv-not-waiting', 'answer');
|
|
257
|
-
expect(result.handled).toBe(false);
|
|
258
|
-
expect(result.reason).toBe('orchestrator_not_waiting');
|
|
259
|
-
|
|
260
|
-
orchestrator.destroy();
|
|
261
|
-
});
|
|
262
|
-
|
|
263
|
-
test('routes answer to orchestrator when waiting and returns handled:true', async () => {
|
|
264
|
-
// Setup: trigger ASK_USER to put orchestrator in waiting_on_user state
|
|
265
|
-
mockStreamFn.mockImplementation(() =>
|
|
266
|
-
createMockStream(['Hold on. [ASK_USER: Preferred date?]']),
|
|
267
|
-
);
|
|
268
|
-
|
|
269
|
-
ensureConversation('conv-bridge');
|
|
270
|
-
const callSession = createCallSession({
|
|
271
|
-
conversationId: 'conv-bridge',
|
|
272
|
-
provider: 'twilio',
|
|
273
|
-
fromNumber: '+15551111111',
|
|
274
|
-
toNumber: '+15552222222',
|
|
275
|
-
});
|
|
276
|
-
|
|
277
|
-
const relay = createMockRelay();
|
|
278
|
-
const orchestrator = new CallOrchestrator(callSession.id, relay as unknown as RelayConnection, 'test task');
|
|
279
|
-
|
|
280
|
-
await orchestrator.handleCallerUtterance('I need a reservation');
|
|
281
|
-
|
|
282
|
-
// Verify the orchestrator is now waiting
|
|
283
|
-
expect(orchestrator.getState()).toBe('waiting_on_user');
|
|
284
|
-
|
|
285
|
-
// Now provide the answer — set up mock for the LLM call after answer
|
|
286
|
-
mockStreamFn.mockImplementation(() => createMockStream(['Great, booking for tomorrow.']));
|
|
287
|
-
|
|
288
|
-
const result = await tryRouteCallMessage('conv-bridge', 'Tomorrow at noon');
|
|
289
|
-
expect(result.handled).toBe(true);
|
|
290
|
-
|
|
291
|
-
// Wait for the fire-and-forget LLM call
|
|
292
|
-
await new Promise((r) => setTimeout(r, 50));
|
|
293
|
-
|
|
294
|
-
// Verify the pending question was answered
|
|
295
|
-
const question = getPendingQuestion(callSession.id);
|
|
296
|
-
// After answering, there should be no pending question left
|
|
297
|
-
expect(question).toBeNull();
|
|
298
|
-
|
|
299
|
-
orchestrator.destroy();
|
|
300
|
-
});
|
|
301
|
-
|
|
302
|
-
// ── tryRouteCallMessage — instruction path ────────────────────
|
|
303
|
-
|
|
304
|
-
test('routes instruction to orchestrator when active call exists with no pending question', async () => {
|
|
305
|
-
ensureConversation('conv-instruct');
|
|
306
|
-
const callSession = createCallSession({
|
|
307
|
-
conversationId: 'conv-instruct',
|
|
308
|
-
provider: 'twilio',
|
|
309
|
-
fromNumber: '+15551111111',
|
|
310
|
-
toNumber: '+15552222222',
|
|
311
|
-
});
|
|
312
|
-
|
|
313
|
-
const relay = createMockRelay();
|
|
314
|
-
const orchestrator = new CallOrchestrator(callSession.id, relay as unknown as RelayConnection, 'test task');
|
|
315
|
-
|
|
316
|
-
const result = await tryRouteCallMessage('conv-instruct', 'Please ask about pricing');
|
|
317
|
-
expect(result.handled).toBe(true);
|
|
318
|
-
expect(result.userFacingText).toBe('Instruction relayed to active call.');
|
|
319
|
-
|
|
320
|
-
// Verify acknowledgement was persisted
|
|
321
|
-
const msgs = getMessagesForConversation('conv-instruct');
|
|
322
|
-
const ackMsg = msgs.find((m) => m.content.includes('Instruction relayed'));
|
|
323
|
-
expect(ackMsg).toBeDefined();
|
|
324
|
-
expect(ackMsg!.role).toBe('assistant');
|
|
325
|
-
|
|
326
|
-
orchestrator.destroy();
|
|
327
|
-
});
|
|
328
|
-
|
|
329
|
-
test('prefers answer path over instruction path when pending question exists', async () => {
|
|
330
|
-
// Setup: trigger ASK_USER to put orchestrator in waiting_on_user state
|
|
331
|
-
mockStreamFn.mockImplementation(() =>
|
|
332
|
-
createMockStream(['Hold on. [ASK_USER: Budget range?]']),
|
|
333
|
-
);
|
|
334
|
-
|
|
335
|
-
ensureConversation('conv-prefer-answer');
|
|
336
|
-
const callSession = createCallSession({
|
|
337
|
-
conversationId: 'conv-prefer-answer',
|
|
338
|
-
provider: 'twilio',
|
|
339
|
-
fromNumber: '+15551111111',
|
|
340
|
-
toNumber: '+15552222222',
|
|
341
|
-
});
|
|
342
|
-
|
|
343
|
-
const relay = createMockRelay();
|
|
344
|
-
const orchestrator = new CallOrchestrator(callSession.id, relay as unknown as RelayConnection, 'test task');
|
|
345
|
-
|
|
346
|
-
await orchestrator.handleCallerUtterance('What is your budget?');
|
|
347
|
-
expect(orchestrator.getState()).toBe('waiting_on_user');
|
|
348
|
-
|
|
349
|
-
// Mock the next LLM call
|
|
350
|
-
mockStreamFn.mockImplementation(() => createMockStream(['Got it, thanks.']));
|
|
351
|
-
|
|
352
|
-
// This should route as answer, not instruction
|
|
353
|
-
const result = await tryRouteCallMessage('conv-prefer-answer', '$500');
|
|
354
|
-
expect(result.handled).toBe(true);
|
|
355
|
-
|
|
356
|
-
// Wait for fire-and-forget LLM call
|
|
357
|
-
await new Promise((r) => setTimeout(r, 50));
|
|
358
|
-
|
|
359
|
-
// Should have answered the pending question, not relayed as instruction
|
|
360
|
-
const question = getPendingQuestion(callSession.id);
|
|
361
|
-
expect(question).toBeNull();
|
|
362
|
-
|
|
363
|
-
// No instruction acknowledgement should be persisted
|
|
364
|
-
const msgs = getMessagesForConversation('conv-prefer-answer');
|
|
365
|
-
const ackMsg = msgs.find((m) => m.content.includes('Instruction relayed'));
|
|
366
|
-
expect(ackMsg).toBeUndefined();
|
|
367
|
-
|
|
368
|
-
orchestrator.destroy();
|
|
369
|
-
});
|
|
370
|
-
|
|
371
|
-
test('instruction relay failure persists notice and is consumed (handled:true)', async () => {
|
|
372
|
-
ensureConversation('conv-no-orch-instruct');
|
|
373
|
-
createCallSession({
|
|
374
|
-
conversationId: 'conv-no-orch-instruct',
|
|
375
|
-
provider: 'twilio',
|
|
376
|
-
fromNumber: '+15551111111',
|
|
377
|
-
toNumber: '+15552222222',
|
|
378
|
-
});
|
|
379
|
-
|
|
380
|
-
// No orchestrator registered — relay should fail but still be consumed
|
|
381
|
-
const result = await tryRouteCallMessage('conv-no-orch-instruct', 'Change the topic');
|
|
382
|
-
expect(result.handled).toBe(true);
|
|
383
|
-
expect(result.reason).toBe('instruction_relay_failed');
|
|
384
|
-
expect(result.userFacingText).toBe('Failed to relay instruction to the active call.');
|
|
385
|
-
|
|
386
|
-
// Verify failure notice was persisted in-thread
|
|
387
|
-
const msgs = getMessagesForConversation('conv-no-orch-instruct');
|
|
388
|
-
const failMsg = msgs.find((m) => m.content.includes('Failed to relay'));
|
|
389
|
-
expect(failMsg).toBeDefined();
|
|
390
|
-
expect(failMsg!.role).toBe('assistant');
|
|
391
|
-
});
|
|
392
|
-
|
|
393
|
-
// ── Call question notifier ──────────────────────────────────────
|
|
394
|
-
|
|
395
|
-
test('call question notifier persists assistant message and emits events', () => {
|
|
396
|
-
ensureConversation('conv-notifier-q');
|
|
397
|
-
|
|
398
|
-
const emittedEvents: Array<{ type: string; text?: string }> = [];
|
|
399
|
-
const sendToClient = (msg: { type: string; text?: string }) => {
|
|
400
|
-
emittedEvents.push(msg);
|
|
401
|
-
};
|
|
402
|
-
|
|
403
|
-
// Register notifier (as Session would)
|
|
404
|
-
registerCallQuestionNotifier('conv-notifier-q', (_callSessionId: string, question: string) => {
|
|
405
|
-
const questionText = `**Live call question**:\n\n${question}\n\n_Reply in this thread to answer._`;
|
|
406
|
-
conversationStore.addMessage(
|
|
407
|
-
'conv-notifier-q',
|
|
408
|
-
'assistant',
|
|
409
|
-
JSON.stringify([{ type: 'text', text: questionText }]),
|
|
410
|
-
);
|
|
411
|
-
sendToClient({ type: 'assistant_text_delta', text: questionText });
|
|
412
|
-
sendToClient({ type: 'message_complete' });
|
|
413
|
-
});
|
|
414
|
-
|
|
415
|
-
// Fire the notifier
|
|
416
|
-
fireCallQuestionNotifier('conv-notifier-q', 'call-session-1', 'What time works best?');
|
|
417
|
-
|
|
418
|
-
// Verify message was persisted
|
|
419
|
-
const msgs = getMessagesForConversation('conv-notifier-q');
|
|
420
|
-
expect(msgs.length).toBe(1);
|
|
421
|
-
expect(msgs[0].role).toBe('assistant');
|
|
422
|
-
expect(msgs[0].content).toContain('What time works best?');
|
|
423
|
-
|
|
424
|
-
// Verify events were emitted
|
|
425
|
-
expect(emittedEvents.length).toBe(2);
|
|
426
|
-
expect(emittedEvents[0].type).toBe('assistant_text_delta');
|
|
427
|
-
expect(emittedEvents[0].text).toContain('What time works best?');
|
|
428
|
-
expect(emittedEvents[1].type).toBe('message_complete');
|
|
429
|
-
|
|
430
|
-
unregisterCallQuestionNotifier('conv-notifier-q');
|
|
431
|
-
});
|
|
432
|
-
|
|
433
|
-
// ── Call transcript notifier ─────────────────────────────────────
|
|
434
|
-
|
|
435
|
-
test('call transcript notifier persists transcript line and emits events', () => {
|
|
436
|
-
ensureConversation('conv-notifier-t');
|
|
437
|
-
|
|
438
|
-
const emittedEvents: Array<{ type: string; text?: string }> = [];
|
|
439
|
-
const sendToClient = (msg: { type: string; text?: string }) => {
|
|
440
|
-
emittedEvents.push(msg);
|
|
441
|
-
};
|
|
442
|
-
|
|
443
|
-
registerCallTranscriptNotifier('conv-notifier-t', (_callSessionId: string, speaker: 'caller' | 'assistant', text: string) => {
|
|
444
|
-
const speakerLabel = speaker === 'caller' ? 'Caller' : 'Assistant';
|
|
445
|
-
const transcriptText = `**Live call transcript**\n${speakerLabel}: ${text}`;
|
|
446
|
-
conversationStore.addMessage(
|
|
447
|
-
'conv-notifier-t',
|
|
448
|
-
'assistant',
|
|
449
|
-
JSON.stringify([{ type: 'text', text: transcriptText }]),
|
|
450
|
-
);
|
|
451
|
-
sendToClient({ type: 'assistant_text_delta', text: transcriptText });
|
|
452
|
-
sendToClient({ type: 'message_complete' });
|
|
453
|
-
});
|
|
454
|
-
|
|
455
|
-
fireCallTranscriptNotifier('conv-notifier-t', 'call-session-1', 'caller', 'Can you confirm the appointment?');
|
|
456
|
-
|
|
457
|
-
const msgs = getMessagesForConversation('conv-notifier-t');
|
|
458
|
-
expect(msgs.length).toBe(1);
|
|
459
|
-
expect(msgs[0].role).toBe('assistant');
|
|
460
|
-
expect(msgs[0].content).toContain('Caller: Can you confirm the appointment?');
|
|
461
|
-
|
|
462
|
-
expect(emittedEvents.length).toBe(2);
|
|
463
|
-
expect(emittedEvents[0].type).toBe('assistant_text_delta');
|
|
464
|
-
expect(emittedEvents[0].text).toContain('Live call transcript');
|
|
465
|
-
expect(emittedEvents[1].type).toBe('message_complete');
|
|
466
|
-
|
|
467
|
-
unregisterCallTranscriptNotifier('conv-notifier-t');
|
|
468
|
-
});
|
|
469
|
-
|
|
470
|
-
// ── Call completion notifier ────────────────────────────────────
|
|
471
|
-
|
|
472
|
-
test('call completion notifier persists summary and emits events', () => {
|
|
473
|
-
ensureConversation('conv-notifier-c');
|
|
474
|
-
|
|
475
|
-
const emittedEvents: Array<{ type: string; text?: string }> = [];
|
|
476
|
-
const sendToClient = (msg: { type: string; text?: string }) => {
|
|
477
|
-
emittedEvents.push(msg);
|
|
478
|
-
};
|
|
479
|
-
|
|
480
|
-
// Create a call session so getCallSession works
|
|
481
|
-
const callSession = createCallSession({
|
|
482
|
-
conversationId: 'conv-notifier-c',
|
|
483
|
-
provider: 'twilio',
|
|
484
|
-
fromNumber: '+15551111111',
|
|
485
|
-
toNumber: '+15552222222',
|
|
486
|
-
});
|
|
487
|
-
updateCallSession(callSession.id, { status: 'completed', startedAt: Date.now() - 30000, endedAt: Date.now() });
|
|
488
|
-
recordCallEvent(callSession.id, 'call_started', {});
|
|
489
|
-
recordCallEvent(callSession.id, 'call_ended', {});
|
|
490
|
-
|
|
491
|
-
registerCallCompletionNotifier('conv-notifier-c', (_callSessionId: string) => {
|
|
492
|
-
const summaryText = `**Call completed**. Events recorded.`;
|
|
493
|
-
conversationStore.addMessage(
|
|
494
|
-
'conv-notifier-c',
|
|
495
|
-
'assistant',
|
|
496
|
-
JSON.stringify([{ type: 'text', text: summaryText }]),
|
|
497
|
-
);
|
|
498
|
-
sendToClient({ type: 'assistant_text_delta', text: summaryText });
|
|
499
|
-
sendToClient({ type: 'message_complete' });
|
|
500
|
-
});
|
|
501
|
-
|
|
502
|
-
fireCallCompletionNotifier('conv-notifier-c', callSession.id);
|
|
503
|
-
|
|
504
|
-
// Verify message persisted
|
|
505
|
-
const msgs = getMessagesForConversation('conv-notifier-c');
|
|
506
|
-
expect(msgs.length).toBe(1);
|
|
507
|
-
expect(msgs[0].role).toBe('assistant');
|
|
508
|
-
expect(msgs[0].content).toContain('Call completed');
|
|
509
|
-
|
|
510
|
-
// Verify events emitted
|
|
511
|
-
expect(emittedEvents.length).toBe(2);
|
|
512
|
-
expect(emittedEvents[0].type).toBe('assistant_text_delta');
|
|
513
|
-
expect(emittedEvents[1].type).toBe('message_complete');
|
|
514
|
-
|
|
515
|
-
unregisterCallCompletionNotifier('conv-notifier-c');
|
|
516
|
-
});
|
|
517
|
-
});
|