@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
|
@@ -1,4 +1,3 @@
|
|
|
1
|
-
/* eslint-disable @typescript-eslint/no-explicit-any */
|
|
2
1
|
/**
|
|
3
2
|
* Integration tests: ToolExecutor → real checker.js → real shell-identity → real tree-sitter parser.
|
|
4
3
|
*
|
|
@@ -10,6 +9,7 @@
|
|
|
10
9
|
*/
|
|
11
10
|
import { describe, test, expect, beforeAll, mock } from 'bun:test';
|
|
12
11
|
import type { ToolContext } from '../tools/types.js';
|
|
12
|
+
import type { AllowlistOption, ScopeOption } from '../permissions/types.js';
|
|
13
13
|
import { PermissionPrompter } from '../permissions/prompter.js';
|
|
14
14
|
|
|
15
15
|
// ── Config mock ──────────────────────────────────────────────────────
|
|
@@ -133,16 +133,16 @@ function makeContext(overrides?: Partial<ToolContext>): ToolContext {
|
|
|
133
133
|
* passed to the prompter by the executor, then allows the tool.
|
|
134
134
|
*/
|
|
135
135
|
function makeCapturingPrompter() {
|
|
136
|
-
let capturedAllowlist:
|
|
137
|
-
let capturedScopes:
|
|
136
|
+
let capturedAllowlist: AllowlistOption[] | undefined;
|
|
137
|
+
let capturedScopes: ScopeOption[] | undefined;
|
|
138
138
|
|
|
139
139
|
const prompter = {
|
|
140
140
|
prompt: async (
|
|
141
141
|
_toolName: string,
|
|
142
142
|
_input: Record<string, unknown>,
|
|
143
143
|
_riskLevel: string,
|
|
144
|
-
allowlistOptions:
|
|
145
|
-
scopeOptions:
|
|
144
|
+
allowlistOptions: AllowlistOption[],
|
|
145
|
+
scopeOptions: ScopeOption[],
|
|
146
146
|
) => {
|
|
147
147
|
capturedAllowlist = allowlistOptions;
|
|
148
148
|
capturedScopes = scopeOptions;
|
|
@@ -177,7 +177,7 @@ describe('ToolExecutor → real shell allowlist integration', () => {
|
|
|
177
177
|
expect(allowlist).toBeDefined();
|
|
178
178
|
expect(allowlist!.length).toBeGreaterThan(1);
|
|
179
179
|
|
|
180
|
-
const patterns = allowlist!.map((o:
|
|
180
|
+
const patterns = allowlist!.map((o: AllowlistOption) => o.pattern);
|
|
181
181
|
|
|
182
182
|
// Should contain the exact command
|
|
183
183
|
expect(patterns).toContain('npm install express');
|
|
@@ -197,8 +197,8 @@ describe('ToolExecutor → real shell allowlist integration', () => {
|
|
|
197
197
|
const scopes = getScopes();
|
|
198
198
|
expect(scopes).toBeDefined();
|
|
199
199
|
expect(scopes!.length).toBeGreaterThanOrEqual(2);
|
|
200
|
-
expect(scopes!.some((s:
|
|
201
|
-
expect(scopes!.some((s:
|
|
200
|
+
expect(scopes!.some((s: ScopeOption) => s.scope === '/tmp/project')).toBe(true);
|
|
201
|
+
expect(scopes!.some((s: ScopeOption) => s.scope === 'everywhere')).toBe(true);
|
|
202
202
|
});
|
|
203
203
|
|
|
204
204
|
test('compound command produces only exact compound option (no action keys)', async () => {
|
|
@@ -226,7 +226,7 @@ describe('ToolExecutor → real shell allowlist integration', () => {
|
|
|
226
226
|
expect(allowlist).toBeDefined();
|
|
227
227
|
expect(allowlist!.length).toBeGreaterThan(1);
|
|
228
228
|
|
|
229
|
-
const patterns = allowlist!.map((o:
|
|
229
|
+
const patterns = allowlist!.map((o: AllowlistOption) => o.pattern);
|
|
230
230
|
|
|
231
231
|
// Should contain the full original command as the exact option
|
|
232
232
|
expect(patterns).toContain('cd /repo && gh pr view 123');
|
|
@@ -250,7 +250,7 @@ describe('ToolExecutor → real shell allowlist integration', () => {
|
|
|
250
250
|
expect(scopes).toBeDefined();
|
|
251
251
|
expect(scopes!.length).toBeGreaterThanOrEqual(2);
|
|
252
252
|
|
|
253
|
-
const scopeValues = scopes!.map((s:
|
|
253
|
+
const scopeValues = scopes!.map((s: ScopeOption) => s.scope);
|
|
254
254
|
|
|
255
255
|
// Project-scoped option
|
|
256
256
|
expect(scopeValues).toContain('/Users/test/my-project');
|
|
@@ -276,7 +276,7 @@ describe('ToolExecutor → real shell allowlist integration', () => {
|
|
|
276
276
|
expect(allowlist).toBeDefined();
|
|
277
277
|
expect(allowlist!.length).toBeGreaterThan(1);
|
|
278
278
|
|
|
279
|
-
const patterns = allowlist!.map((o:
|
|
279
|
+
const patterns = allowlist!.map((o: AllowlistOption) => o.pattern);
|
|
280
280
|
|
|
281
281
|
// Should contain exact command and action keys
|
|
282
282
|
expect(patterns).toContain('git status');
|
|
@@ -1,8 +1,7 @@
|
|
|
1
|
-
/* eslint-disable @typescript-eslint/no-explicit-any */
|
|
2
1
|
import { describe, test, expect, beforeEach, afterEach, afterAll, mock, spyOn } from 'bun:test';
|
|
3
|
-
import type { ToolExecutionResult, Tool } from '../tools/types.js';
|
|
2
|
+
import type { ToolExecutionResult, Tool, ToolLifecycleEvent, ToolPermissionPromptEvent } from '../tools/types.js';
|
|
4
3
|
import { RiskLevel } from '../permissions/types.js';
|
|
5
|
-
import type { PolicyContext } from '../permissions/types.js';
|
|
4
|
+
import type { AllowlistOption, ScopeOption, PolicyContext, TrustRule } from '../permissions/types.js';
|
|
6
5
|
|
|
7
6
|
const mockConfig = {
|
|
8
7
|
provider: 'anthropic',
|
|
@@ -322,8 +321,8 @@ describe('ToolExecutor contextual rule creation', () => {
|
|
|
322
321
|
|
|
323
322
|
function setupAddRuleSpy() {
|
|
324
323
|
addRuleSpy = spyOn(trustStore, 'addRule').mockImplementation(
|
|
325
|
-
(tool: string, pattern: string, scope: string, decision = 'allow', priority = 100, options?:
|
|
326
|
-
return { id: 'spy-rule-id', tool, pattern, scope, decision, priority, createdAt: Date.now(), ...options } as
|
|
324
|
+
(tool: string, pattern: string, scope: string, decision = 'allow', priority = 100, options?: { allowHighRisk?: boolean; executionTarget?: string }) => {
|
|
325
|
+
return { id: 'spy-rule-id', tool, pattern, scope, decision, priority, createdAt: Date.now(), ...options } as TrustRule;
|
|
327
326
|
},
|
|
328
327
|
);
|
|
329
328
|
return addRuleSpy;
|
|
@@ -508,8 +507,8 @@ describe('ToolExecutor strict mode + high-risk integration (PR 25)', () => {
|
|
|
508
507
|
|
|
509
508
|
function setupAddRuleSpy() {
|
|
510
509
|
addRuleSpy = spyOn(trustStore, 'addRule').mockImplementation(
|
|
511
|
-
(tool: string, pattern: string, scope: string, decision = 'allow', priority = 100, options?:
|
|
512
|
-
return { id: 'spy-rule-id', tool, pattern, scope, decision, priority, createdAt: Date.now(), ...options } as
|
|
510
|
+
(tool: string, pattern: string, scope: string, decision = 'allow', priority = 100, options?: { allowHighRisk?: boolean; executionTarget?: string }) => {
|
|
511
|
+
return { id: 'spy-rule-id', tool, pattern, scope, decision, priority, createdAt: Date.now(), ...options } as TrustRule;
|
|
513
512
|
},
|
|
514
513
|
);
|
|
515
514
|
return addRuleSpy;
|
|
@@ -1481,8 +1480,8 @@ describe('ToolExecutor persistentDecisionsAllowed contract', () => {
|
|
|
1481
1480
|
|
|
1482
1481
|
function setupAddRuleSpy() {
|
|
1483
1482
|
addRuleSpy = spyOn(trustStore, 'addRule').mockImplementation(
|
|
1484
|
-
(tool: string, pattern: string, scope: string, decision = 'allow', priority = 100, options?:
|
|
1485
|
-
return { id: 'spy-rule-id', tool, pattern, scope, decision, priority, createdAt: Date.now(), ...options } as
|
|
1483
|
+
(tool: string, pattern: string, scope: string, decision = 'allow', priority = 100, options?: { allowHighRisk?: boolean; executionTarget?: string }) => {
|
|
1484
|
+
return { id: 'spy-rule-id', tool, pattern, scope, decision, priority, createdAt: Date.now(), ...options } as TrustRule;
|
|
1486
1485
|
},
|
|
1487
1486
|
);
|
|
1488
1487
|
return addRuleSpy;
|
|
@@ -1534,14 +1533,14 @@ describe('ToolExecutor persistentDecisionsAllowed contract', () => {
|
|
|
1534
1533
|
});
|
|
1535
1534
|
|
|
1536
1535
|
test('persistentDecisionsAllowed: false is emitted in lifecycle event for proxied bash', async () => {
|
|
1537
|
-
let capturedEvent:
|
|
1536
|
+
let capturedEvent: ToolPermissionPromptEvent | undefined;
|
|
1538
1537
|
const prompter = makePrompterWithDecision('allow');
|
|
1539
1538
|
const executor = new ToolExecutor(prompter);
|
|
1540
1539
|
const result = await executor.execute(
|
|
1541
1540
|
'bash',
|
|
1542
1541
|
{ command: 'curl https://example.com', network_mode: 'proxied' },
|
|
1543
1542
|
makeContext({
|
|
1544
|
-
onToolLifecycleEvent: (event:
|
|
1543
|
+
onToolLifecycleEvent: (event: ToolLifecycleEvent) => {
|
|
1545
1544
|
if (event.type === 'permission_prompt') {
|
|
1546
1545
|
capturedEvent = event;
|
|
1547
1546
|
}
|
|
@@ -1551,18 +1550,18 @@ describe('ToolExecutor persistentDecisionsAllowed contract', () => {
|
|
|
1551
1550
|
|
|
1552
1551
|
expect(result.isError).toBe(false);
|
|
1553
1552
|
expect(capturedEvent).toBeDefined();
|
|
1554
|
-
expect(capturedEvent
|
|
1553
|
+
expect(capturedEvent!.persistentDecisionsAllowed).toBe(false);
|
|
1555
1554
|
});
|
|
1556
1555
|
|
|
1557
1556
|
test('persistentDecisionsAllowed: true is emitted in lifecycle event for non-proxied bash', async () => {
|
|
1558
|
-
let capturedEvent:
|
|
1557
|
+
let capturedEvent: ToolPermissionPromptEvent | undefined;
|
|
1559
1558
|
const prompter = makePrompterWithDecision('allow');
|
|
1560
1559
|
const executor = new ToolExecutor(prompter);
|
|
1561
1560
|
const result = await executor.execute(
|
|
1562
1561
|
'bash',
|
|
1563
1562
|
{ command: 'echo hello' },
|
|
1564
1563
|
makeContext({
|
|
1565
|
-
onToolLifecycleEvent: (event:
|
|
1564
|
+
onToolLifecycleEvent: (event: ToolLifecycleEvent) => {
|
|
1566
1565
|
if (event.type === 'permission_prompt') {
|
|
1567
1566
|
capturedEvent = event;
|
|
1568
1567
|
}
|
|
@@ -1572,7 +1571,7 @@ describe('ToolExecutor persistentDecisionsAllowed contract', () => {
|
|
|
1572
1571
|
|
|
1573
1572
|
expect(result.isError).toBe(false);
|
|
1574
1573
|
expect(capturedEvent).toBeDefined();
|
|
1575
|
-
expect(capturedEvent
|
|
1574
|
+
expect(capturedEvent!.persistentDecisionsAllowed).toBe(true);
|
|
1576
1575
|
});
|
|
1577
1576
|
|
|
1578
1577
|
test('persistentDecisionsAllowed is passed to prompter confirmation_request for proxied bash', async () => {
|
|
@@ -1580,8 +1579,8 @@ describe('ToolExecutor persistentDecisionsAllowed contract', () => {
|
|
|
1580
1579
|
const prompter = {
|
|
1581
1580
|
prompt: async (
|
|
1582
1581
|
_toolName: string, _input: Record<string, unknown>, _riskLevel: string,
|
|
1583
|
-
_allowlistOptions:
|
|
1584
|
-
_sessionId:
|
|
1582
|
+
_allowlistOptions: AllowlistOption[], _scopeOptions: ScopeOption[], _diff: unknown, _sandboxed: unknown,
|
|
1583
|
+
_sessionId: unknown, _executionTarget: unknown, persistentDecisionsAllowed: boolean | undefined,
|
|
1585
1584
|
) => {
|
|
1586
1585
|
capturedPersistent = persistentDecisionsAllowed;
|
|
1587
1586
|
return { decision: 'allow' as const };
|
|
@@ -1643,8 +1642,8 @@ describe('E2E: proxied bash activation vs proxy approval persistence', () => {
|
|
|
1643
1642
|
|
|
1644
1643
|
function setupAddRuleSpy() {
|
|
1645
1644
|
addRuleSpy = spyOn(trustStore, 'addRule').mockImplementation(
|
|
1646
|
-
(tool: string, pattern: string, scope: string, decision = 'allow', priority = 100, options?:
|
|
1647
|
-
return { id: 'spy-rule-id', tool, pattern, scope, decision, priority, createdAt: Date.now(), ...options } as
|
|
1645
|
+
(tool: string, pattern: string, scope: string, decision = 'allow', priority = 100, options?: { allowHighRisk?: boolean; executionTarget?: string }) => {
|
|
1646
|
+
return { id: 'spy-rule-id', tool, pattern, scope, decision, priority, createdAt: Date.now(), ...options } as TrustRule;
|
|
1648
1647
|
},
|
|
1649
1648
|
);
|
|
1650
1649
|
return addRuleSpy;
|
|
@@ -1871,8 +1870,8 @@ describe('ToolExecutor persistent-allow lifecycle', () => {
|
|
|
1871
1870
|
|
|
1872
1871
|
function setupAddRuleSpy() {
|
|
1873
1872
|
addRuleSpy = spyOn(trustStore, 'addRule').mockImplementation(
|
|
1874
|
-
(tool: string, pattern: string, scope: string, decision = 'allow', priority = 100, options?:
|
|
1875
|
-
return { id: 'spy-rule-id', tool, pattern, scope, decision, priority, createdAt: Date.now(), ...options } as
|
|
1873
|
+
(tool: string, pattern: string, scope: string, decision = 'allow', priority = 100, options?: { allowHighRisk?: boolean; executionTarget?: string }) => {
|
|
1874
|
+
return { id: 'spy-rule-id', tool, pattern, scope, decision, priority, createdAt: Date.now(), ...options } as TrustRule;
|
|
1876
1875
|
},
|
|
1877
1876
|
);
|
|
1878
1877
|
return addRuleSpy;
|
|
@@ -1954,12 +1953,12 @@ describe('integration regressions — prompt payload (PR 11)', () => {
|
|
|
1954
1953
|
test('shell command prompt payload includes allowlist and scope options', async () => {
|
|
1955
1954
|
checkResultOverride = { decision: 'prompt', reason: 'Medium risk: requires approval' };
|
|
1956
1955
|
|
|
1957
|
-
let capturedAllowlist:
|
|
1958
|
-
let capturedScopes:
|
|
1956
|
+
let capturedAllowlist: AllowlistOption[] | undefined;
|
|
1957
|
+
let capturedScopes: ScopeOption[] | undefined;
|
|
1959
1958
|
const prompter = {
|
|
1960
1959
|
prompt: async (
|
|
1961
1960
|
_toolName: string, _input: Record<string, unknown>, _riskLevel: string,
|
|
1962
|
-
allowlistOptions:
|
|
1961
|
+
allowlistOptions: AllowlistOption[], scopeOptions: ScopeOption[],
|
|
1963
1962
|
) => {
|
|
1964
1963
|
capturedAllowlist = allowlistOptions;
|
|
1965
1964
|
capturedScopes = scopeOptions;
|
|
@@ -845,7 +845,7 @@ describe('Trust Store', () => {
|
|
|
845
845
|
const safePath = join(testDir, 'data', 'assistant.db');
|
|
846
846
|
const match = findHighestPriorityRule('file_read', [`file_read:${safePath}`], '/tmp');
|
|
847
847
|
// Should not match a default deny rule
|
|
848
|
-
expect(match
|
|
848
|
+
expect(match == null || !match.id.startsWith('default:')).toBe(true);
|
|
849
849
|
});
|
|
850
850
|
|
|
851
851
|
test('default rules are backfilled after malformed JSON in trust file', () => {
|
|
@@ -1396,7 +1396,7 @@ describe('Trust Store', () => {
|
|
|
1396
1396
|
const match = findHighestPriorityRule('bash', ['run script.js'], '/tmp', {
|
|
1397
1397
|
executionTarget: '/usr/local/bin/bun',
|
|
1398
1398
|
});
|
|
1399
|
-
expect(match
|
|
1399
|
+
expect(match == null || match.id !== 'et-diff').toBe(true);
|
|
1400
1400
|
});
|
|
1401
1401
|
|
|
1402
1402
|
test('rule with executionTarget does NOT match when no target in context', () => {
|
|
@@ -1411,7 +1411,7 @@ describe('Trust Store', () => {
|
|
|
1411
1411
|
executionTarget: '/usr/local/bin/node',
|
|
1412
1412
|
}]);
|
|
1413
1413
|
const match = findHighestPriorityRule('bash', ['run script.js'], '/tmp', {});
|
|
1414
|
-
expect(match
|
|
1414
|
+
expect(match == null || match.id !== 'et-no-ctx').toBe(true);
|
|
1415
1415
|
});
|
|
1416
1416
|
|
|
1417
1417
|
test('rule WITHOUT executionTarget matches any target (wildcard)', () => {
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import { describe, test, expect } from 'bun:test';
|
|
2
|
+
import { twilioAuthHeader, twilioBaseUrl } from '../calls/twilio-rest.js';
|
|
3
|
+
|
|
4
|
+
describe('twilioAuthHeader', () => {
|
|
5
|
+
test('returns a valid Basic auth header', () => {
|
|
6
|
+
const header = twilioAuthHeader('AC_test_sid', 'test_token');
|
|
7
|
+
const expected = 'Basic ' + Buffer.from('AC_test_sid:test_token').toString('base64');
|
|
8
|
+
expect(header).toBe(expected);
|
|
9
|
+
});
|
|
10
|
+
|
|
11
|
+
test('encodes special characters correctly', () => {
|
|
12
|
+
const header = twilioAuthHeader('AC_special!@#', 'tok$%^&');
|
|
13
|
+
const decoded = Buffer.from(header.replace('Basic ', ''), 'base64').toString();
|
|
14
|
+
expect(decoded).toBe('AC_special!@#:tok$%^&');
|
|
15
|
+
});
|
|
16
|
+
});
|
|
17
|
+
|
|
18
|
+
describe('twilioBaseUrl', () => {
|
|
19
|
+
test('constructs correct base URL for a given account SID', () => {
|
|
20
|
+
const url = twilioBaseUrl('AC_abc123');
|
|
21
|
+
expect(url).toBe('https://api.twilio.com/2010-04-01/Accounts/AC_abc123');
|
|
22
|
+
});
|
|
23
|
+
|
|
24
|
+
test('handles different account SIDs', () => {
|
|
25
|
+
const url = twilioBaseUrl('AC_xyz789');
|
|
26
|
+
expect(url).toContain('AC_xyz789');
|
|
27
|
+
expect(url).toStartWith('https://api.twilio.com/2010-04-01/Accounts/');
|
|
28
|
+
});
|
|
29
|
+
});
|
|
@@ -169,10 +169,13 @@ function ensureConversation(id: string): void {
|
|
|
169
169
|
|
|
170
170
|
function resetTables() {
|
|
171
171
|
const db = getDb();
|
|
172
|
+
db.run('DELETE FROM guardian_action_deliveries');
|
|
173
|
+
db.run('DELETE FROM guardian_action_requests');
|
|
172
174
|
db.run('DELETE FROM processed_callbacks');
|
|
173
175
|
db.run('DELETE FROM call_pending_questions');
|
|
174
176
|
db.run('DELETE FROM call_events');
|
|
175
177
|
db.run('DELETE FROM call_sessions');
|
|
178
|
+
db.run('DELETE FROM messages');
|
|
176
179
|
db.run('DELETE FROM conversations');
|
|
177
180
|
ensuredConvIds = new Set();
|
|
178
181
|
}
|
|
@@ -124,4 +124,15 @@ describe('generateTwiML with voice quality profile', () => {
|
|
|
124
124
|
expect(twiml).toContain('interruptible="true"');
|
|
125
125
|
expect(twiml).toContain('dtmfDetection="true"');
|
|
126
126
|
});
|
|
127
|
+
|
|
128
|
+
test('TwiML omits welcomeGreeting attribute when not provided', () => {
|
|
129
|
+
const twiml = generateTwiML(callSessionId, relayUrl, null, {
|
|
130
|
+
language: 'en-US',
|
|
131
|
+
transcriptionProvider: 'Deepgram',
|
|
132
|
+
ttsProvider: 'Google',
|
|
133
|
+
voice: 'Google.en-US-Journey-O',
|
|
134
|
+
});
|
|
135
|
+
|
|
136
|
+
expect(twiml).not.toContain('welcomeGreeting=');
|
|
137
|
+
});
|
|
127
138
|
});
|
|
@@ -31,6 +31,7 @@ mock.module('../util/platform.js', () => ({
|
|
|
31
31
|
getDbPath: () => join(testDir, 'test.db'),
|
|
32
32
|
getLogPath: () => join(testDir, 'test.log'),
|
|
33
33
|
ensureDataDir: () => {},
|
|
34
|
+
readHttpToken: () => null,
|
|
34
35
|
}));
|
|
35
36
|
|
|
36
37
|
mock.module('../util/logger.js', () => ({
|
|
@@ -39,15 +40,27 @@ mock.module('../util/logger.js', () => ({
|
|
|
39
40
|
}),
|
|
40
41
|
}));
|
|
41
42
|
|
|
43
|
+
const mockConfigObj = {
|
|
44
|
+
model: 'test',
|
|
45
|
+
provider: 'test',
|
|
46
|
+
apiKeys: {},
|
|
47
|
+
memory: { enabled: false },
|
|
48
|
+
rateLimit: { maxRequestsPerMinute: 0, maxTokensPerSession: 0 },
|
|
49
|
+
secretDetection: { enabled: false },
|
|
50
|
+
calls: {
|
|
51
|
+
voice: {
|
|
52
|
+
mode: 'twilio_standard',
|
|
53
|
+
language: 'en-US',
|
|
54
|
+
transcriptionProvider: 'Deepgram',
|
|
55
|
+
fallbackToStandardOnError: true,
|
|
56
|
+
elevenlabs: { voiceId: '' },
|
|
57
|
+
},
|
|
58
|
+
},
|
|
59
|
+
};
|
|
60
|
+
|
|
42
61
|
mock.module('../config/loader.js', () => ({
|
|
43
|
-
getConfig: () =>
|
|
44
|
-
|
|
45
|
-
provider: 'test',
|
|
46
|
-
apiKeys: {},
|
|
47
|
-
memory: { enabled: false },
|
|
48
|
-
rateLimit: { maxRequestsPerMinute: 0, maxTokensPerSession: 0 },
|
|
49
|
-
secretDetection: { enabled: false },
|
|
50
|
-
}),
|
|
62
|
+
getConfig: () => mockConfigObj,
|
|
63
|
+
loadConfig: () => mockConfigObj,
|
|
51
64
|
}));
|
|
52
65
|
|
|
53
66
|
mock.module('../security/secure-keys.js', () => ({
|
|
@@ -84,10 +97,11 @@ import * as callStore from '../calls/call-store.js';
|
|
|
84
97
|
import {
|
|
85
98
|
createCallSession,
|
|
86
99
|
getCallSession,
|
|
100
|
+
getCallSessionByCallSid,
|
|
87
101
|
updateCallSession,
|
|
88
102
|
getCallEvents,
|
|
89
103
|
} from '../calls/call-store.js';
|
|
90
|
-
import { resolveRelayUrl, handleStatusCallback, handleVoiceWebhook } from '../calls/twilio-routes.js';
|
|
104
|
+
import { resolveRelayUrl, buildWelcomeGreeting, handleStatusCallback, handleVoiceWebhook } from '../calls/twilio-routes.js';
|
|
91
105
|
import { registerCallCompletionNotifier, unregisterCallCompletionNotifier } from '../calls/call-state.js';
|
|
92
106
|
|
|
93
107
|
initializeDb();
|
|
@@ -111,22 +125,28 @@ function ensureConversation(id: string): void {
|
|
|
111
125
|
|
|
112
126
|
function resetTables() {
|
|
113
127
|
const db = getDb();
|
|
128
|
+
db.run('DELETE FROM guardian_action_deliveries');
|
|
129
|
+
db.run('DELETE FROM guardian_action_requests');
|
|
114
130
|
db.run('DELETE FROM processed_callbacks');
|
|
115
131
|
db.run('DELETE FROM call_pending_questions');
|
|
116
132
|
db.run('DELETE FROM call_events');
|
|
117
133
|
db.run('DELETE FROM call_sessions');
|
|
134
|
+
db.run('DELETE FROM external_conversation_bindings');
|
|
135
|
+
db.run('DELETE FROM conversation_keys');
|
|
136
|
+
db.run('DELETE FROM tool_invocations');
|
|
137
|
+
db.run('DELETE FROM messages');
|
|
118
138
|
db.run('DELETE FROM conversations');
|
|
119
139
|
ensuredConvIds = new Set();
|
|
120
140
|
}
|
|
121
141
|
|
|
122
|
-
function createTestSession(convId: string, callSid: string) {
|
|
142
|
+
function createTestSession(convId: string, callSid: string, task = 'test task') {
|
|
123
143
|
ensureConversation(convId);
|
|
124
144
|
const session = createCallSession({
|
|
125
145
|
conversationId: convId,
|
|
126
146
|
provider: 'twilio',
|
|
127
147
|
fromNumber: '+15550001111',
|
|
128
148
|
toNumber: '+15559998888',
|
|
129
|
-
task
|
|
149
|
+
task,
|
|
130
150
|
});
|
|
131
151
|
updateCallSession(session.id, { providerCallSid: callSid });
|
|
132
152
|
return session;
|
|
@@ -148,6 +168,14 @@ function makeVoiceRequest(sessionId: string, params: Record<string, string>): Re
|
|
|
148
168
|
});
|
|
149
169
|
}
|
|
150
170
|
|
|
171
|
+
function makeInboundVoiceRequest(params: Record<string, string>): Request {
|
|
172
|
+
return new Request('http://127.0.0.1/v1/calls/twilio/voice-webhook', {
|
|
173
|
+
method: 'POST',
|
|
174
|
+
headers: { 'Content-Type': 'application/x-www-form-urlencoded' },
|
|
175
|
+
body: new URLSearchParams(params).toString(),
|
|
176
|
+
});
|
|
177
|
+
}
|
|
178
|
+
|
|
151
179
|
// ── Tests ──────────────────────────────────────────────────────────────
|
|
152
180
|
|
|
153
181
|
describe('twilio webhook routes', () => {
|
|
@@ -155,6 +183,7 @@ describe('twilio webhook routes', () => {
|
|
|
155
183
|
resetTables();
|
|
156
184
|
mockWssBaseUrl = 'wss://test.example.com';
|
|
157
185
|
mockWebhookBaseUrl = 'https://test.example.com';
|
|
186
|
+
delete process.env.CALL_WELCOME_GREETING;
|
|
158
187
|
});
|
|
159
188
|
|
|
160
189
|
afterAll(() => {
|
|
@@ -416,6 +445,18 @@ describe('twilio webhook routes', () => {
|
|
|
416
445
|
});
|
|
417
446
|
});
|
|
418
447
|
|
|
448
|
+
describe('buildWelcomeGreeting', () => {
|
|
449
|
+
test('returns empty by default so orchestrator drives first opener', () => {
|
|
450
|
+
const greeting = buildWelcomeGreeting('check store hours for tomorrow');
|
|
451
|
+
expect(greeting).toBe('');
|
|
452
|
+
});
|
|
453
|
+
|
|
454
|
+
test('uses configured greeting override when provided', () => {
|
|
455
|
+
const greeting = buildWelcomeGreeting('check store hours', 'Custom hello');
|
|
456
|
+
expect(greeting).toBe('Custom hello');
|
|
457
|
+
});
|
|
458
|
+
});
|
|
459
|
+
|
|
419
460
|
// ── TwiML relay URL generation ──────────────────────────────────────
|
|
420
461
|
// Call handleVoiceWebhook directly since direct routes are blocked.
|
|
421
462
|
|
|
@@ -446,6 +487,33 @@ describe('twilio webhook routes', () => {
|
|
|
446
487
|
const twiml = await res.text();
|
|
447
488
|
expect(twiml).toContain('wss://gateway.example.com/v1/calls/relay');
|
|
448
489
|
});
|
|
490
|
+
|
|
491
|
+
test('TwiML omits welcome greeting by default so call opener is model-driven', async () => {
|
|
492
|
+
const session = createTestSession(
|
|
493
|
+
'conv-twiml-3',
|
|
494
|
+
'CA_twiml_3',
|
|
495
|
+
'confirm appointment time\n\nContext: Prior email thread',
|
|
496
|
+
);
|
|
497
|
+
const req = makeVoiceRequest(session.id, { CallSid: 'CA_twiml_3' });
|
|
498
|
+
|
|
499
|
+
const res = await handleVoiceWebhook(req);
|
|
500
|
+
|
|
501
|
+
expect(res.status).toBe(200);
|
|
502
|
+
const twiml = await res.text();
|
|
503
|
+
expect(twiml).not.toContain('welcomeGreeting=');
|
|
504
|
+
});
|
|
505
|
+
|
|
506
|
+
test('TwiML includes explicit welcome greeting override when configured', async () => {
|
|
507
|
+
process.env.CALL_WELCOME_GREETING = 'Custom transport greeting';
|
|
508
|
+
const session = createTestSession('conv-twiml-4', 'CA_twiml_4');
|
|
509
|
+
const req = makeVoiceRequest(session.id, { CallSid: 'CA_twiml_4' });
|
|
510
|
+
|
|
511
|
+
const res = await handleVoiceWebhook(req);
|
|
512
|
+
|
|
513
|
+
expect(res.status).toBe(200);
|
|
514
|
+
const twiml = await res.text();
|
|
515
|
+
expect(twiml).toContain('welcomeGreeting="Custom transport greeting"');
|
|
516
|
+
});
|
|
449
517
|
});
|
|
450
518
|
|
|
451
519
|
// ── Handler-level idempotency concurrency tests ─────────────────
|
|
@@ -574,4 +642,92 @@ describe('twilio webhook routes', () => {
|
|
|
574
642
|
expect(events2.filter(e => e.eventType === 'call_ended').length).toBe(1);
|
|
575
643
|
});
|
|
576
644
|
});
|
|
645
|
+
|
|
646
|
+
// ── Inbound voice webhook tests ─────────────────────────────────────
|
|
647
|
+
// Tests the inbound mode where callSessionId is absent and a session
|
|
648
|
+
// is created/reused from the Twilio CallSid.
|
|
649
|
+
|
|
650
|
+
describe('inbound voice webhook', () => {
|
|
651
|
+
test('creates a new session from CallSid when callSessionId is absent', async () => {
|
|
652
|
+
const req = makeInboundVoiceRequest({
|
|
653
|
+
CallSid: 'CA_inbound_new_1',
|
|
654
|
+
From: '+14155551234',
|
|
655
|
+
To: '+15550001111',
|
|
656
|
+
});
|
|
657
|
+
|
|
658
|
+
const res = await handleVoiceWebhook(req);
|
|
659
|
+
|
|
660
|
+
expect(res.status).toBe(200);
|
|
661
|
+
const twiml = await res.text();
|
|
662
|
+
expect(twiml).toContain('<ConversationRelay');
|
|
663
|
+
expect(twiml).toContain('callSessionId=');
|
|
664
|
+
|
|
665
|
+
// Verify session was created with the CallSid
|
|
666
|
+
const session = getCallSessionByCallSid('CA_inbound_new_1');
|
|
667
|
+
expect(session).not.toBeNull();
|
|
668
|
+
expect(session!.fromNumber).toBe('+14155551234');
|
|
669
|
+
expect(session!.toNumber).toBe('+15550001111');
|
|
670
|
+
expect(session!.providerCallSid).toBe('CA_inbound_new_1');
|
|
671
|
+
});
|
|
672
|
+
|
|
673
|
+
test('replayed inbound webhook for same CallSid does not create duplicate sessions', async () => {
|
|
674
|
+
const params = {
|
|
675
|
+
CallSid: 'CA_inbound_replay_1',
|
|
676
|
+
From: '+14155551234',
|
|
677
|
+
To: '+15550001111',
|
|
678
|
+
};
|
|
679
|
+
|
|
680
|
+
// First call — creates the session
|
|
681
|
+
const res1 = await handleVoiceWebhook(makeInboundVoiceRequest(params));
|
|
682
|
+
expect(res1.status).toBe(200);
|
|
683
|
+
|
|
684
|
+
const session1 = getCallSessionByCallSid('CA_inbound_replay_1');
|
|
685
|
+
expect(session1).not.toBeNull();
|
|
686
|
+
|
|
687
|
+
// Second call (replay) — reuses the same session
|
|
688
|
+
const res2 = await handleVoiceWebhook(makeInboundVoiceRequest(params));
|
|
689
|
+
expect(res2.status).toBe(200);
|
|
690
|
+
|
|
691
|
+
const session2 = getCallSessionByCallSid('CA_inbound_replay_1');
|
|
692
|
+
expect(session2).not.toBeNull();
|
|
693
|
+
expect(session2!.id).toBe(session1!.id);
|
|
694
|
+
});
|
|
695
|
+
|
|
696
|
+
test('inbound webhook without CallSid returns 400', async () => {
|
|
697
|
+
const req = makeInboundVoiceRequest({
|
|
698
|
+
From: '+14155551234',
|
|
699
|
+
To: '+15550001111',
|
|
700
|
+
});
|
|
701
|
+
|
|
702
|
+
const res = await handleVoiceWebhook(req);
|
|
703
|
+
expect(res.status).toBe(400);
|
|
704
|
+
});
|
|
705
|
+
|
|
706
|
+
test('inbound webhook with forwarded assistantId creates session with correct assistantId', async () => {
|
|
707
|
+
const req = makeInboundVoiceRequest({
|
|
708
|
+
CallSid: 'CA_inbound_assist_1',
|
|
709
|
+
From: '+14155551234',
|
|
710
|
+
To: '+15550001111',
|
|
711
|
+
});
|
|
712
|
+
|
|
713
|
+
const res = await handleVoiceWebhook(req, 'my-assistant-id');
|
|
714
|
+
|
|
715
|
+
expect(res.status).toBe(200);
|
|
716
|
+
const session = getCallSessionByCallSid('CA_inbound_assist_1');
|
|
717
|
+
expect(session).not.toBeNull();
|
|
718
|
+
expect(session!.assistantId).toBe('my-assistant-id');
|
|
719
|
+
});
|
|
720
|
+
|
|
721
|
+
test('outbound call flow remains non-regressed with callSessionId present', async () => {
|
|
722
|
+
const session = createTestSession('conv-outbound-compat-1', 'CA_outbound_compat_1');
|
|
723
|
+
const req = makeVoiceRequest(session.id, { CallSid: 'CA_outbound_compat_1' });
|
|
724
|
+
|
|
725
|
+
const res = await handleVoiceWebhook(req);
|
|
726
|
+
|
|
727
|
+
expect(res.status).toBe(200);
|
|
728
|
+
const twiml = await res.text();
|
|
729
|
+
expect(twiml).toContain('<ConversationRelay');
|
|
730
|
+
expect(twiml).toContain(`callSessionId=${session.id}`);
|
|
731
|
+
});
|
|
732
|
+
});
|
|
577
733
|
});
|
|
@@ -100,7 +100,7 @@ describe('CLI error shaping', () => {
|
|
|
100
100
|
|
|
101
101
|
test('routed non-session error with suggestAlternative emits structured JSON', () => {
|
|
102
102
|
const err = Object.assign(
|
|
103
|
-
new Error('OAuth is not configured.
|
|
103
|
+
new Error('OAuth is not configured. Provide your X developer credentials here in the chat to set up OAuth, or switch to browser strategy.'),
|
|
104
104
|
{
|
|
105
105
|
pathUsed: 'oauth' as const,
|
|
106
106
|
suggestAlternative: 'browser' as const,
|
|
@@ -110,7 +110,7 @@ describe('CLI error shaping', () => {
|
|
|
110
110
|
|
|
111
111
|
expect(payload).toEqual({
|
|
112
112
|
ok: false,
|
|
113
|
-
error: 'OAuth is not configured.
|
|
113
|
+
error: 'OAuth is not configured. Provide your X developer credentials here in the chat to set up OAuth, or switch to browser strategy.',
|
|
114
114
|
pathUsed: 'oauth',
|
|
115
115
|
suggestAlternative: 'browser',
|
|
116
116
|
});
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { describe, test, expect, mock, beforeEach } from 'bun:test';
|
|
2
2
|
import { join } from 'node:path';
|
|
3
|
+
import * as realFs from 'node:fs';
|
|
3
4
|
|
|
4
5
|
const TEST_DIR = '/tmp/vellum-user-ref-test';
|
|
5
6
|
|
|
@@ -12,6 +13,7 @@ let mockFileExists = false;
|
|
|
12
13
|
let mockFileContent = '';
|
|
13
14
|
|
|
14
15
|
mock.module('node:fs', () => ({
|
|
16
|
+
...realFs,
|
|
15
17
|
existsSync: (path: string) => {
|
|
16
18
|
if (path === join(TEST_DIR, 'USER.md')) return mockFileExists;
|
|
17
19
|
return false;
|