@vellumai/assistant 0.7.1 → 0.7.2
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/ARCHITECTURE.md +32 -49
- package/Dockerfile +1 -0
- package/README.md +1 -2
- package/__tests__/permissions/gateway-threshold-reader.test.ts +9 -3
- package/bun.lock +26 -26
- package/docs/architecture/security.md +20 -0
- package/docs/plugins.md +7 -9
- package/knip.json +1 -0
- package/node_modules/@vellumai/gateway-client/src/index.ts +1 -0
- package/node_modules/@vellumai/gateway-client/src/ipc-client.ts +39 -1
- package/node_modules/@vellumai/gateway-client/src/types.ts +11 -0
- package/node_modules/@vellumai/service-contracts/package.json +2 -0
- package/node_modules/@vellumai/service-contracts/src/__tests__/contracts.test.ts +4 -0
- package/node_modules/@vellumai/service-contracts/src/__tests__/ingress.test.ts +107 -0
- package/node_modules/@vellumai/service-contracts/src/index.ts +5 -1
- package/node_modules/@vellumai/service-contracts/src/ingress.ts +24 -0
- package/node_modules/@vellumai/service-contracts/src/twilio-ingress.ts +84 -0
- package/node_modules/@vellumai/skill-host-contracts/src/assistant-event.ts +9 -0
- package/node_modules/@vellumai/twilio-client/bun.lock +24 -0
- package/node_modules/@vellumai/twilio-client/package.json +18 -0
- package/node_modules/@vellumai/twilio-client/src/__tests__/twilio-client.test.ts +128 -0
- package/node_modules/@vellumai/twilio-client/src/index.ts +179 -0
- package/node_modules/@vellumai/twilio-client/tsconfig.json +20 -0
- package/openapi.yaml +565 -12
- package/package.json +6 -3
- package/src/__tests__/app-builder-tool-scripts.test.ts +3 -3
- package/src/__tests__/app-bundler.test.ts +170 -1
- package/src/__tests__/app-control-flow.test.ts +374 -0
- package/src/__tests__/app-control-no-global-cgevent.test.ts +98 -0
- package/src/__tests__/app-control-tool-schemas.test.ts +621 -0
- package/src/__tests__/app-executors.test.ts +30 -43
- package/src/__tests__/approval-routes-http.test.ts +23 -6
- package/src/__tests__/assistant-event-hub-machine-name.test.ts +146 -0
- package/src/__tests__/assistant-event-hub-targeted.test.ts +257 -0
- package/src/__tests__/assistant-event-hub.test.ts +109 -2
- package/src/__tests__/assistant-event.test.ts +10 -0
- package/src/__tests__/assistant-events-sse-hardening.test.ts +7 -2
- package/src/__tests__/assistant-feature-flags-integration.test.ts +11 -7
- package/src/__tests__/background-shell-host-bash.test.ts +14 -15
- package/src/__tests__/bootstrap-turn-cleanup.test.ts +44 -0
- package/src/__tests__/btw-routes.test.ts +13 -4
- package/src/__tests__/call-controller.test.ts +49 -1
- package/src/__tests__/call-domain.test.ts +0 -2
- package/src/__tests__/call-routes-http.test.ts +0 -2
- package/src/__tests__/channel-readiness-service.test.ts +59 -1
- package/src/__tests__/checker.test.ts +3 -4
- package/src/__tests__/config-loader-backfill.test.ts +90 -155
- package/src/__tests__/config-loader-platform-defaults.test.ts +196 -0
- package/src/__tests__/config-schema-cmd.test.ts +0 -1
- package/src/__tests__/config-set-platform-guard.test.ts +48 -4
- package/src/__tests__/config-watcher-cleanup-throttle.test.ts +2 -2
- package/src/__tests__/config-watcher.test.ts +2 -2
- package/src/__tests__/conversation-app-control-instantiation.test.ts +392 -0
- package/src/__tests__/conversation-app-control-lifecycle.test.ts +237 -0
- package/src/__tests__/conversation-init.benchmark.test.ts +0 -2
- package/src/__tests__/conversation-lifecycle.test.ts +36 -0
- package/src/__tests__/conversation-process-app-control-preactivation.test.ts +283 -0
- package/src/__tests__/conversation-routes-disk-view.test.ts +6 -0
- package/src/__tests__/conversation-routes-guardian-reply.test.ts +120 -72
- package/src/__tests__/conversation-routes-slash-commands.test.ts +1 -0
- package/src/__tests__/conversation-slash-commands.test.ts +0 -4
- package/src/__tests__/conversation-surfaces-action-delivery.test.ts +202 -0
- package/src/__tests__/conversation-surfaces-app-control.test.ts +317 -0
- package/src/__tests__/credential-execution-feature-gates.test.ts +5 -12
- package/src/__tests__/credential-execution-managed-contract.test.ts +3 -131
- package/src/__tests__/credentials-cli.test.ts +5 -12
- package/src/__tests__/cu-unified-flow.test.ts +185 -23
- package/src/__tests__/daemon-credential-client.test.ts +101 -19
- package/src/__tests__/db-schedule-syntax-migration.test.ts +2 -0
- package/src/__tests__/dynamic-skill-workflow-prompt.test.ts +0 -1
- package/src/__tests__/gateway-only-enforcement.test.ts +0 -1
- package/src/__tests__/guardian-verification-voice-binding.test.ts +0 -2
- package/src/__tests__/handlers-skills-memory-v2-reseed.test.ts +0 -2
- package/src/__tests__/handlers-user-message-approval-consumption.test.ts +0 -1
- package/src/__tests__/heartbeat-service.test.ts +718 -1
- package/src/__tests__/helpers/call-route-handler.ts +7 -1
- package/src/__tests__/host-app-control-proxy.test.ts +602 -0
- package/src/__tests__/host-app-control-routes.test.ts +263 -0
- package/src/__tests__/host-bash-proxy.test.ts +246 -47
- package/src/__tests__/host-bash-routes.test.ts +294 -0
- package/src/__tests__/host-browser-proxy.test.ts +24 -22
- package/src/__tests__/host-browser-routes.test.ts +39 -13
- package/src/__tests__/host-cu-proxy.test.ts +41 -52
- package/src/__tests__/host-cu-routes-targeted.test.ts +300 -0
- package/src/__tests__/host-file-edit-tool.test.ts +47 -1
- package/src/__tests__/host-file-proxy-targeted.test.ts +339 -0
- package/src/__tests__/host-file-proxy.test.ts +37 -43
- package/src/__tests__/host-file-read-tool.test.ts +17 -0
- package/src/__tests__/host-file-routes-targeted.test.ts +262 -0
- package/src/__tests__/host-file-write-tool.test.ts +42 -1
- package/src/__tests__/host-proxy-base.test.ts +312 -0
- package/src/__tests__/host-shell-tool.test.ts +22 -4
- package/src/__tests__/host-transfer-proxy-targeted.test.ts +583 -0
- package/src/__tests__/host-transfer-proxy.test.ts +121 -22
- package/src/__tests__/host-transfer-routes-targeted.test.ts +447 -0
- package/src/__tests__/http-user-message-parity.test.ts +1 -0
- package/src/__tests__/identity-intro-cache.test.ts +29 -0
- package/src/__tests__/identity-routes.test.ts +103 -1
- package/src/__tests__/init-feature-flag-overrides.test.ts +26 -3
- package/src/__tests__/inline-command-runner.test.ts +0 -1
- package/src/__tests__/inline-skill-load-permissions.test.ts +5 -11
- package/src/__tests__/integration-status.test.ts +85 -5
- package/src/__tests__/intent-routing.test.ts +0 -1
- package/src/__tests__/jobs-store-qdrant-breaker.test.ts +95 -5
- package/src/__tests__/lifecycle-memory-v2-seed.test.ts +17 -0
- package/src/__tests__/managed-skill-lifecycle.test.ts +0 -1
- package/src/__tests__/mcp-auth-routes.test.ts +197 -0
- package/src/__tests__/mcp-cli.test.ts +338 -2
- package/src/__tests__/memory-jobs-worker-lanes.test.ts +188 -0
- package/src/__tests__/migration-import-commit-http.test.ts +108 -2
- package/src/__tests__/mock-gateway-ipc.ts +1 -0
- package/src/__tests__/oauth-cli.test.ts +0 -2
- package/src/__tests__/oauth2-gateway-transport.test.ts +0 -1
- package/src/__tests__/persistence-secret-redaction.test.ts +299 -0
- package/src/__tests__/platform-bash-auto-approve.test.ts +5 -9
- package/src/__tests__/prechat-onboarding-contract.test.ts +3 -1
- package/src/__tests__/process-message-background-slack.test.ts +2 -0
- package/src/__tests__/provider-commit-message-generator.test.ts +0 -1
- package/src/__tests__/public-ingress-urls.test.ts +97 -0
- package/src/__tests__/require-fresh-approval.test.ts +0 -1
- package/src/__tests__/retry-backoff.test.ts +87 -0
- package/src/__tests__/runtime-events-sse.test.ts +10 -6
- package/src/__tests__/sanitize-config-for-transfer.test.ts +24 -2
- package/src/__tests__/schedule-retry.test.ts +715 -0
- package/src/__tests__/script-proxy-mitm-handler.test.ts +1 -1
- package/src/__tests__/secret-ingress-http.test.ts +1 -0
- package/src/__tests__/send-endpoint-busy.test.ts +3 -0
- package/src/__tests__/shell-tool-proxy-mode.test.ts +0 -1
- package/src/__tests__/skill-feature-flags.test.ts +43 -41
- package/src/__tests__/skill-load-feature-flag.test.ts +13 -14
- package/src/__tests__/skill-load-inline-command.test.ts +0 -51
- package/src/__tests__/skill-load-inline-includes.test.ts +0 -43
- package/src/__tests__/skill-projection.benchmark.test.ts +0 -1
- package/src/__tests__/skill-script-runner-sandbox.test.ts +0 -1
- package/src/__tests__/slack-channel-config.test.ts +9 -14
- package/src/__tests__/system-prompt-ask-mode.test.ts +0 -1
- package/src/__tests__/system-prompt.test.ts +0 -1
- package/src/__tests__/telegram-config.test.ts +0 -1
- package/src/__tests__/test-preload.ts +8 -0
- package/src/__tests__/tool-approval-handler.test.ts +3 -4
- package/src/__tests__/tool-audit-listener.test.ts +48 -0
- package/src/__tests__/tool-execute-pipeline.test.ts +0 -1
- package/src/__tests__/tool-execution-abort-cleanup.test.ts +0 -1
- package/src/__tests__/tool-executor-lifecycle-events.test.ts +0 -1
- package/src/__tests__/tool-executor.test.ts +0 -1
- package/src/__tests__/twilio-config.test.ts +3 -16
- package/src/__tests__/twilio-routes.test.ts +3 -5
- package/src/__tests__/twilio-validation.test.ts +93 -0
- package/src/__tests__/vellum-self-knowledge-inline-command.test.ts +1 -4
- package/src/__tests__/verification-control-plane-policy.test.ts +2 -4
- package/src/__tests__/voice-ingress-preflight.test.ts +19 -0
- package/src/__tests__/workspace-migration-006-services-config.test.ts +3 -2
- package/src/__tests__/workspace-migration-backfill-installation-id.test.ts +1 -5
- package/src/__tests__/workspace-migration-down-functions.test.ts +8 -8
- package/src/__tests__/workspace-migration-unify-llm-callsite-configs.test.ts +10 -6
- package/src/backup/__tests__/paths.test.ts +0 -22
- package/src/backup/__tests__/restore.test.ts +51 -151
- package/src/backup/paths.ts +2 -18
- package/src/backup/restore.ts +107 -231
- package/src/bundler/app-bundler.ts +51 -3
- package/src/calls/relay-server.ts +4 -44
- package/src/calls/twilio-config.ts +2 -17
- package/src/calls/twilio-rest.ts +33 -105
- package/src/calls/twilio-routes.ts +11 -12
- package/src/channels/types.ts +8 -7
- package/src/cli/commands/__tests__/backup.test.ts +6 -277
- package/src/cli/commands/__tests__/gateway.test.ts +288 -0
- package/src/cli/commands/__tests__/memory-v2.test.ts +4 -0
- package/src/cli/commands/__tests__/webhooks.test.ts +0 -1
- package/src/cli/commands/backup.ts +6 -331
- package/src/cli/commands/clients.ts +36 -37
- package/src/cli/commands/contacts.ts +73 -0
- package/src/cli/commands/conversations.ts +2 -5
- package/src/cli/commands/credentials.ts +15 -7
- package/src/cli/commands/domain.ts +66 -15
- package/src/cli/commands/gateway.ts +183 -0
- package/src/cli/commands/keys.ts +9 -6
- package/src/cli/commands/mcp.ts +116 -156
- package/src/cli/commands/memory-v2.ts +296 -1
- package/src/cli/commands/platform/__tests__/callback-routes-list.test.ts +0 -1
- package/src/cli/commands/platform/__tests__/connect.test.ts +0 -2
- package/src/cli/commands/platform/__tests__/disconnect.test.ts +0 -2
- package/src/cli/commands/platform/__tests__/status.test.ts +13 -15
- package/src/cli/commands/platform/disconnect.ts +5 -4
- package/src/cli/commands/platform/index.ts +0 -18
- package/src/cli/lib/daemon-credential-client.ts +110 -28
- package/src/cli/program.ts +2 -0
- package/src/config/assistant-feature-flags.ts +67 -10
- package/src/config/bundled-skills/acp/SKILL.md +6 -0
- package/src/config/bundled-skills/acp/TOOLS.json +1 -22
- package/src/config/bundled-skills/app-builder/SKILL.md +14 -109
- package/src/config/bundled-skills/app-builder/TOOLS.json +1 -28
- package/src/config/bundled-skills/app-builder/tools/app-create.ts +1 -10
- package/src/config/bundled-skills/app-control/SKILL.md +75 -0
- package/src/config/bundled-skills/app-control/TOOLS.json +299 -0
- package/src/config/bundled-skills/app-control/tools/app-control-click.ts +12 -0
- package/src/config/bundled-skills/app-control/tools/app-control-combo.ts +12 -0
- package/src/config/bundled-skills/app-control/tools/app-control-drag.ts +12 -0
- package/src/config/bundled-skills/app-control/tools/app-control-observe.ts +12 -0
- package/src/config/bundled-skills/app-control/tools/app-control-press.ts +12 -0
- package/src/config/bundled-skills/app-control/tools/app-control-sequence.ts +12 -0
- package/src/config/bundled-skills/app-control/tools/app-control-start.ts +12 -0
- package/src/config/bundled-skills/app-control/tools/app-control-stop.ts +12 -0
- package/src/config/bundled-skills/app-control/tools/app-control-type.ts +12 -0
- package/src/config/bundled-skills/computer-use/SKILL.md +6 -0
- package/src/config/bundled-skills/computer-use/TOOLS.json +67 -43
- package/src/config/bundled-skills/contacts/TOOLS.json +0 -16
- package/src/config/bundled-skills/document/TOOLS.json +0 -8
- package/src/config/bundled-skills/followups/TOOLS.json +0 -12
- package/src/config/bundled-skills/image-studio/SKILL.md +4 -0
- package/src/config/bundled-skills/image-studio/TOOLS.json +0 -4
- package/src/config/bundled-skills/media-processing/TOOLS.json +0 -24
- package/src/config/bundled-skills/messaging/TOOLS.json +0 -40
- package/src/config/bundled-skills/phone-calls/TOOLS.json +0 -12
- package/src/config/bundled-skills/phone-calls/references/TROUBLESHOOTING.md +19 -4
- package/src/config/bundled-skills/playbooks/TOOLS.json +0 -16
- package/src/config/bundled-skills/schedule/TOOLS.json +14 -14
- package/src/config/bundled-skills/sequences/TOOLS.json +0 -36
- package/src/config/bundled-skills/settings/SKILL.md +4 -0
- package/src/config/bundled-skills/settings/TOOLS.json +0 -12
- package/src/config/bundled-skills/skill-management/SKILL.md +6 -0
- package/src/config/bundled-skills/skill-management/TOOLS.json +0 -8
- package/src/config/bundled-skills/subagent/SKILL.md +6 -2
- package/src/config/bundled-skills/subagent/TOOLS.json +0 -20
- package/src/config/bundled-skills/transcribe/SKILL.md +4 -0
- package/src/config/bundled-skills/transcribe/TOOLS.json +0 -4
- package/src/config/bundled-tool-registry.ts +21 -0
- package/src/config/env-registry.ts +0 -2
- package/src/config/env.ts +19 -12
- package/src/config/feature-flag-registry.json +21 -133
- package/src/config/loader.ts +73 -99
- package/src/config/sanitize-for-transfer.ts +2 -0
- package/src/config/schemas/__tests__/memory-lifecycle.test.ts +80 -0
- package/src/config/schemas/__tests__/memory-v2.test.ts +7 -4
- package/src/config/schemas/calls.ts +0 -9
- package/src/config/schemas/heartbeat.ts +63 -0
- package/src/config/schemas/ingress.ts +10 -6
- package/src/config/schemas/llm.ts +5 -10
- package/src/config/schemas/memory-lifecycle.ts +77 -24
- package/src/config/schemas/memory-v2.ts +48 -4
- package/src/config/schemas/platform.ts +6 -0
- package/src/config/schemas/services.ts +1 -15
- package/src/config/schemas/skills.ts +0 -6
- package/src/config/seed-inference-profiles.ts +1 -1
- package/src/contacts/contact-store.ts +0 -30
- package/src/contacts/contacts-write.ts +0 -27
- package/src/context/window-manager.ts +1 -2
- package/src/credential-execution/feature-gates.ts +10 -10
- package/src/credential-execution/process-manager.ts +12 -41
- package/src/daemon/__tests__/conversation-tool-setup.test.ts +126 -5
- package/src/daemon/bootstrap-turn-cleanup.ts +45 -0
- package/src/daemon/config-watcher.ts +4 -3
- package/src/daemon/conversation-agent-loop-handlers.ts +21 -3
- package/src/daemon/conversation-agent-loop.ts +32 -28
- package/src/daemon/conversation-lifecycle.ts +8 -1
- package/src/daemon/conversation-process.ts +16 -11
- package/src/daemon/conversation-runtime-assembly.ts +2 -2
- package/src/daemon/conversation-surfaces.ts +125 -4
- package/src/daemon/conversation-tool-setup.ts +16 -55
- package/src/daemon/conversation.ts +21 -2
- package/src/daemon/doordash-steps.ts +1 -1
- package/src/daemon/handlers/shared.ts +4 -1
- package/src/daemon/host-app-control-proxy.ts +293 -0
- package/src/daemon/host-bash-proxy.ts +84 -74
- package/src/daemon/host-browser-proxy.ts +67 -82
- package/src/daemon/host-cu-proxy.ts +81 -86
- package/src/daemon/host-file-proxy.ts +93 -69
- package/src/daemon/host-proxy-base.ts +294 -0
- package/src/daemon/host-proxy-preactivation.ts +82 -0
- package/src/daemon/host-transfer-proxy.ts +247 -129
- package/src/daemon/lifecycle.ts +115 -117
- package/src/daemon/message-protocol.ts +3 -8
- package/src/daemon/message-types/contacts.ts +23 -1
- package/src/daemon/message-types/conversations.ts +11 -8
- package/src/daemon/message-types/host-app-control.ts +150 -0
- package/src/daemon/message-types/host-bash.ts +4 -0
- package/src/daemon/message-types/host-cu.ts +2 -0
- package/src/daemon/message-types/host-file.ts +4 -0
- package/src/daemon/message-types/host-transfer.ts +3 -0
- package/src/daemon/message-types/schedules.ts +8 -3
- package/src/daemon/message-types/skills.ts +2 -2
- package/src/daemon/process-message.ts +18 -1
- package/src/daemon/shutdown-handlers.ts +0 -3
- package/src/daemon/tool-setup-types.ts +51 -0
- package/src/daemon/tool-side-effects.ts +1 -1
- package/src/events/tool-audit-listener.ts +2 -1
- package/src/heartbeat/__tests__/heartbeat-feed-event.test.ts +15 -7
- package/src/heartbeat/__tests__/heartbeat-run-store.test.ts +216 -0
- package/src/heartbeat/heartbeat-run-store.ts +236 -0
- package/src/heartbeat/heartbeat-service.ts +280 -49
- package/src/home/__tests__/post-connect-feed.test.ts +99 -0
- package/src/home/__tests__/relationship-state-writer.test.ts +11 -9
- package/src/home/__tests__/suggested-prompts.test.ts +89 -0
- package/src/home/post-connect-feed.ts +68 -0
- package/src/home/relationship-state-writer.ts +17 -92
- package/src/home/suggested-prompts.ts +46 -10
- package/src/inbound/public-ingress-urls.ts +32 -34
- package/src/ipc/__tests__/route-error-envelope.test.ts +80 -0
- package/src/ipc/assistant-server.ts +14 -1
- package/src/ipc/cli-client.ts +32 -1
- package/src/live-voice/live-voice-metrics.ts +10 -10
- package/src/mcp/__tests__/mcp-auth-orchestrator.test.ts +304 -0
- package/src/mcp/mcp-auth-orchestrator.ts +213 -0
- package/src/mcp/mcp-auth-state.ts +133 -0
- package/src/mcp/mcp-oauth-provider.ts +19 -0
- package/src/memory/__tests__/jobs-store-job-classes.test.ts +24 -0
- package/src/memory/__tests__/qdrant-client-sentinel.test.ts +49 -0
- package/src/memory/__tests__/sparse-tokenize.test.ts +66 -0
- package/src/memory/anisotropy.test.ts +247 -0
- package/src/memory/anisotropy.ts +443 -0
- package/src/memory/auto-analysis-constants.ts +17 -0
- package/src/memory/auto-analysis-guard.ts +5 -15
- package/src/memory/canonical-guardian-store.ts +7 -7
- package/src/memory/context-search/__tests__/agent-runner-redaction.test.ts +122 -0
- package/src/memory/context-search/agent-protocol.ts +6 -6
- package/src/memory/context-search/agent-runner.ts +32 -7
- package/src/memory/context-search/sources/memory-v2.ts +17 -5
- package/src/memory/conversation-crud.ts +1 -1
- package/src/memory/conversation-key-store.ts +2 -15
- package/src/memory/db-init.ts +4 -0
- package/src/memory/embedding-backend.ts +9 -21
- package/src/memory/graph/__tests__/conversation-graph-memory-v2-routing.test.ts +49 -4
- package/src/memory/graph/conversation-graph-memory.ts +1 -24
- package/src/memory/graph/graph-search.ts +8 -0
- package/src/memory/graph/retriever.ts +28 -0
- package/src/memory/graph/tools.ts +1 -1
- package/src/memory/jobs/__tests__/embed-concept-page.test.ts +8 -2
- package/src/memory/jobs/embed-concept-page.ts +28 -2
- package/src/memory/jobs/embed-pkb-file.test.ts +2 -2
- package/src/memory/jobs-store.ts +66 -22
- package/src/memory/jobs-worker.ts +112 -63
- package/src/memory/memory-v2-activation-log-store.ts +1 -1
- package/src/memory/migrations/237-heartbeat-runs.ts +45 -0
- package/src/memory/migrations/238-schedule-retry-policy.ts +20 -0
- package/src/memory/migrations/index.ts +5 -0
- package/src/memory/migrations/registry.ts +8 -0
- package/src/memory/pkb/pkb-search.ts +7 -0
- package/src/memory/qdrant-client.ts +50 -20
- package/src/memory/schema/infrastructure.ts +15 -0
- package/src/memory/search/semantic.ts +7 -0
- package/src/memory/sparse-tokenize.ts +49 -0
- package/src/memory/v2/__tests__/activation.test.ts +77 -95
- package/src/memory/v2/__tests__/injection.test.ts +43 -21
- package/src/memory/v2/__tests__/sim.test.ts +166 -6
- package/src/memory/v2/__tests__/sparse-bm25.test.ts +292 -0
- package/src/memory/v2/__tests__/static-context.test.ts +0 -1
- package/src/memory/v2/activation.ts +69 -88
- package/src/memory/v2/consolidation-job.ts +3 -5
- package/src/memory/v2/constants.ts +7 -0
- package/src/memory/v2/injection.ts +86 -53
- package/src/memory/v2/prompts/consolidation.ts +312 -91
- package/src/memory/v2/qdrant.ts +99 -1
- package/src/memory/v2/sim.ts +126 -16
- package/src/memory/v2/skill-qdrant.ts +12 -3
- package/src/memory/v2/skill-store.ts +16 -1
- package/src/memory/v2/sparse-bm25.ts +245 -0
- package/src/memory/v2/static-context.ts +6 -5
- package/src/messaging/providers/gmail/types.ts +0 -49
- package/src/messaging/providers/slack/adapter.ts +1 -31
- package/src/messaging/providers/slack/types.ts +0 -32
- package/src/notifications/README.md +10 -10
- package/src/notifications/broadcaster.ts +1 -1
- package/src/notifications/guardian-question-mode.ts +5 -5
- package/src/oauth/connect-orchestrator.ts +4 -0
- package/src/oauth/credential-token-resolver.ts +1 -3
- package/src/oauth/manual-token-connection.ts +0 -4
- package/src/outbound-proxy/index.ts +1 -37
- package/src/outbound-proxy/logging.ts +1 -1
- package/src/outbound-proxy/policy.ts +6 -5
- package/src/outbound-proxy/router.ts +2 -1
- package/src/permissions/approval-policy.test.ts +6 -275
- package/src/permissions/approval-policy.ts +0 -51
- package/src/permissions/checker.test.ts +0 -1
- package/src/permissions/checker.ts +3 -17
- package/src/permissions/gateway-threshold-reader.ts +2 -0
- package/src/permissions/prompter.ts +34 -1
- package/src/permissions/secret-prompter.ts +6 -2
- package/src/prompts/bootstrap-cleanup.ts +27 -0
- package/src/prompts/system-prompt.ts +3 -18
- package/src/prompts/templates/SOUL.md +13 -1
- package/src/providers/speech-to-text/provider-catalog.ts +7 -8
- package/src/runtime/assistant-event-hub.ts +118 -96
- package/src/runtime/assistant-event.ts +1 -0
- package/src/runtime/auth/__tests__/middleware.test.ts +11 -56
- package/src/runtime/auth/middleware.ts +0 -96
- package/src/runtime/auth/route-policy.ts +19 -0
- package/src/runtime/btw-sidechain.ts +2 -3
- package/src/runtime/channel-invite-transport.ts +2 -48
- package/src/runtime/channel-invite-transports/email.ts +1 -1
- package/src/runtime/channel-invite-transports/slack.ts +1 -1
- package/src/runtime/channel-invite-transports/telegram.ts +1 -1
- package/src/runtime/channel-invite-transports/voice.ts +1 -1
- package/src/runtime/channel-invite-transports/whatsapp.ts +1 -1
- package/src/runtime/channel-invite-types.ts +54 -0
- package/src/runtime/channel-readiness-service.ts +32 -13
- package/src/runtime/http-server.ts +3 -329
- package/src/runtime/http-types.ts +0 -5
- package/src/runtime/migrations/__tests__/vbundle-import-parity.test.ts +413 -0
- package/src/runtime/migrations/__tests__/vbundle-import-policy.test.ts +260 -0
- package/src/runtime/migrations/__tests__/vbundle-import-version-compat.test.ts +189 -0
- package/src/runtime/migrations/__tests__/vbundle-streaming-importer.test.ts +153 -1
- package/src/runtime/migrations/__tests__/vbundle-symlink-importer.test.ts +451 -0
- package/src/runtime/migrations/__tests__/vbundle-symlink-streaming-importer.test.ts +0 -0
- package/src/runtime/migrations/__tests__/vbundle-symlink-streaming.test.ts +515 -0
- package/src/runtime/migrations/__tests__/vbundle-symlink-tar.test.ts +437 -0
- package/src/runtime/migrations/__tests__/vbundle-symlink-walker.test.ts +319 -0
- package/src/runtime/migrations/__tests__/vbundle-validator-v1-schema.test.ts +51 -1
- package/src/runtime/migrations/migration-transport.ts +7 -7
- package/src/runtime/migrations/vbundle-builder.ts +327 -60
- package/src/runtime/migrations/vbundle-import-analyzer.ts +4 -4
- package/src/runtime/migrations/vbundle-import-policy.ts +172 -0
- package/src/runtime/migrations/vbundle-importer.ts +245 -68
- package/src/runtime/migrations/vbundle-streaming-importer.ts +326 -35
- package/src/runtime/migrations/vbundle-streaming-validator.ts +157 -4
- package/src/runtime/migrations/vbundle-tar-stream.ts +15 -6
- package/src/runtime/migrations/vbundle-validator.ts +114 -0
- package/src/runtime/pending-interactions.ts +35 -9
- package/src/runtime/routes/__tests__/backup-routes.test.ts +22 -150
- package/src/runtime/routes/__tests__/conversation-query-routes.test.ts +98 -0
- package/src/runtime/routes/__tests__/gateway-log-routes.test.ts +242 -0
- package/src/runtime/routes/__tests__/heartbeat-routes.test.ts +112 -0
- package/src/runtime/routes/approval-interception-types.ts +13 -0
- package/src/runtime/routes/approval-strategies/guardian-text-engine-strategy.ts +1 -1
- package/src/runtime/routes/backup-routes.ts +15 -38
- package/src/runtime/routes/btw-routes.ts +14 -37
- package/src/runtime/routes/client-routes.ts +1 -0
- package/src/runtime/routes/contact-prompt-routes.ts +183 -0
- package/src/runtime/routes/conversation-query-routes.ts +36 -1
- package/src/runtime/routes/conversation-routes.ts +30 -13
- package/src/runtime/routes/document-pdf-renderer.ts +165 -0
- package/src/runtime/routes/documents-routes.ts +30 -0
- package/src/runtime/routes/errors.ts +19 -4
- package/src/runtime/routes/events-routes.ts +12 -6
- package/src/runtime/routes/gateway-log-routes.ts +79 -0
- package/src/runtime/routes/guardian-approval-interception.ts +2 -8
- package/src/runtime/routes/heartbeat-routes.ts +103 -38
- package/src/runtime/routes/host-app-control-routes.ts +134 -0
- package/src/runtime/routes/host-bash-routes.ts +36 -6
- package/src/runtime/routes/host-browser-routes.ts +108 -13
- package/src/runtime/routes/host-cu-routes.ts +44 -14
- package/src/runtime/routes/host-file-routes.ts +33 -10
- package/src/runtime/routes/host-transfer-routes.ts +64 -24
- package/src/runtime/routes/http-adapter.ts +1 -0
- package/src/runtime/routes/identity-intro-cache.ts +30 -0
- package/src/runtime/routes/identity-routes.ts +15 -43
- package/src/runtime/routes/inbound-message-handler.ts +1 -9
- package/src/runtime/routes/inbound-stages/acl-enforcement.ts +0 -7
- package/src/runtime/routes/inbound-stages/edit-intercept.ts +0 -8
- package/src/runtime/routes/inbound-stages/transcribe-audio.test.ts +0 -20
- package/src/runtime/routes/inbound-stages/transcribe-audio.ts +5 -13
- package/src/runtime/routes/index.ts +8 -0
- package/src/runtime/routes/mcp-auth-routes.ts +132 -0
- package/src/runtime/routes/memory-item-routes.ts +10 -12
- package/src/runtime/routes/memory-v2-routes.ts +441 -1
- package/src/runtime/routes/migration-routes.ts +96 -0
- package/src/runtime/routes/schedule-routes.ts +7 -0
- package/src/runtime/verification-templates.ts +4 -7
- package/src/schedule/integration-status.ts +66 -2
- package/src/schedule/recurrence-engine.ts +4 -1
- package/src/schedule/retry-backoff.ts +18 -0
- package/src/schedule/retry-policy.ts +82 -0
- package/src/schedule/schedule-recovery.ts +64 -0
- package/src/schedule/schedule-store.ts +106 -2
- package/src/schedule/scheduler-types.ts +25 -0
- package/src/schedule/scheduler.ts +63 -38
- package/src/security/oauth-callback-registry.ts +8 -0
- package/src/sequence/analytics.ts +5 -5
- package/src/sequence/engine.ts +1 -1
- package/src/skills/catalog-files.ts +2 -8
- package/src/skills/include-graph.ts +5 -5
- package/src/skills/remote-skill-policy.ts +5 -5
- package/src/skills/skill-file-provider.ts +1 -1
- package/src/skills/skill-file-types.ts +13 -0
- package/src/skills/skillssh-audit-types.ts +28 -0
- package/src/skills/skillssh-registry.ts +8 -21
- package/src/telemetry/types.ts +2 -0
- package/src/telemetry/usage-telemetry-reporter.test.ts +21 -0
- package/src/telemetry/usage-telemetry-reporter.ts +1 -0
- package/src/tools/app-control/skill-proxy-bridge.ts +28 -0
- package/src/tools/apps/executors.ts +56 -69
- package/src/tools/browser/__tests__/browser-status.test.ts +21 -18
- package/src/tools/browser/browser-execution.ts +2 -2
- package/src/tools/browser/cdp-client/__tests__/factory.test.ts +55 -4
- package/src/tools/browser/cdp-client/cdp-inspect/__tests__/ws-transport.test.ts +12 -6
- package/src/tools/browser/cdp-client/factory.ts +23 -24
- package/src/tools/browser/cdp-client/index.ts +1 -14
- package/src/tools/computer-use/definitions.ts +42 -20
- package/src/tools/executor.ts +2 -0
- package/src/tools/host-filesystem/edit.ts +26 -0
- package/src/tools/host-filesystem/read.ts +26 -0
- package/src/tools/host-filesystem/transfer.ts +31 -1
- package/src/tools/host-filesystem/write.ts +26 -0
- package/src/tools/host-terminal/host-shell.ts +58 -0
- package/src/tools/schedule/create.ts +6 -0
- package/src/tools/schedule/list.ts +2 -0
- package/src/tools/schedule/update.ts +10 -0
- package/src/tools/shared/filesystem/file-ops-service.ts +2 -0
- package/src/tools/shared/filesystem/path-policy.ts +25 -1
- package/src/tools/skills/load.ts +0 -32
- package/src/tools/tool-approval-handler.ts +1 -5
- package/src/tools/types.ts +4 -0
- package/src/usage/pricing.ts +1 -1
- package/src/workspace/hatched-date.ts +86 -0
- package/src/workspace/migrations/003-seed-device-id.ts +1 -1
- package/src/workspace/migrations/006-services-config.ts +8 -5
- package/src/workspace/migrations/016-extract-feature-flags-to-protected.ts +3 -9
- package/src/workspace/migrations/021-move-signals-to-workspace.ts +4 -10
- package/src/workspace/migrations/022-move-hooks-to-workspace.ts +4 -10
- package/src/workspace/migrations/023-move-config-files-to-workspace.ts +4 -11
- package/src/workspace/migrations/024-move-runtime-files-to-workspace.ts +3 -10
- package/src/workspace/migrations/040-seed-latency-callsite-defaults.ts +3 -2
- package/src/workspace/migrations/050-seed-main-agent-opus-callsite.ts +2 -1
- package/src/workspace/migrations/059-move-pid-to-workspace.ts +3 -8
- package/src/workspace/migrations/061-move-backup-key-to-workspace.ts +3 -8
- package/src/workspace/migrations/AGENTS.md +1 -1
- package/src/workspace/migrations/migrate-to-workspace-volume.ts +4 -10
- package/src/workspace/migrations/utils.ts +21 -0
- package/src/__tests__/host-browser-e2e-cloud.test.ts +0 -443
- package/src/__tests__/host-browser-e2e-self-hosted-capability.test.ts +0 -226
- package/src/__tests__/host-browser-ws-events-e2e.test.ts +0 -427
- package/src/__tests__/twilio-rest.test.ts +0 -34
- package/src/backup/__tests__/backup-key.test.ts +0 -152
- package/src/backup/__tests__/backup-worker.test.ts +0 -782
- package/src/backup/__tests__/offsite-writer.test.ts +0 -641
- package/src/backup/__tests__/stream-crypt.test.ts +0 -228
- package/src/backup/backup-key.ts +0 -137
- package/src/backup/backup-worker.ts +0 -472
- package/src/backup/offsite-writer.ts +0 -222
- package/src/backup/stream-crypt.ts +0 -263
- package/src/daemon/message-types/pairing.ts +0 -58
- package/src/outbound-proxy/config.ts +0 -20
- package/src/outbound-proxy/health.ts +0 -18
- package/src/outbound-proxy/types.ts +0 -150
- package/src/runtime/capability-tokens.ts +0 -190
- package/src/signals/mcp-reload.ts +0 -18
package/docs/plugins.md
CHANGED
|
@@ -66,7 +66,7 @@ skill can omit middleware entirely.
|
|
|
66
66
|
|
|
67
67
|
## Where plugins live
|
|
68
68
|
|
|
69
|
-
The assistant scans
|
|
69
|
+
The assistant scans `<workspaceDir>/plugins/*` at startup. Any subdirectory
|
|
70
70
|
containing `register.js` or `register.ts` is dynamic-imported once. The
|
|
71
71
|
loader lives in
|
|
72
72
|
[`assistant/src/plugins/user-loader.ts`](../src/plugins/user-loader.ts) and
|
|
@@ -78,9 +78,8 @@ has three key properties:
|
|
|
78
78
|
- **Per-plugin isolation.** If one plugin throws at import time, the error
|
|
79
79
|
is logged with the plugin directory and the loader moves on. Other
|
|
80
80
|
plugins still load. One broken plugin cannot brick the assistant.
|
|
81
|
-
- **Per-instance.** The scan runs under `vellumRoot()
|
|
82
|
-
|
|
83
|
-
its own plugin set.
|
|
81
|
+
- **Per-instance.** The scan runs under `vellumRoot()`. Each assistant
|
|
82
|
+
instance loads its own plugin set.
|
|
84
83
|
|
|
85
84
|
The loader runs after first-party plugin registrations and before
|
|
86
85
|
`bootstrapPlugins()` invokes every plugin's `init()`.
|
|
@@ -105,7 +104,7 @@ export interface PluginManifest {
|
|
|
105
104
|
|
|
106
105
|
| Field | Required | Purpose |
|
|
107
106
|
| -------------------- | -------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
108
|
-
| `name` | yes | Unique plugin identifier. Duplicate names fail registration. Used as the directory under
|
|
107
|
+
| `name` | yes | Unique plugin identifier. Duplicate names fail registration. Used as the directory under `<workspaceDir>/plugins-data/<name>/` and the attribution tag in logs. |
|
|
109
108
|
| `version` | yes | Plugin's own semver. Informational — the registry does not compare it. |
|
|
110
109
|
| `provides` | no | Reserved for future cross-plugin composition and not currently consumed by the assistant. Plugin authors may set this field, but no runtime code reads it yet — it is declared here so future cross-plugin work can land without a manifest version bump. Do not rely on it for any runtime behavior today. |
|
|
111
110
|
| `requires` | yes | Must include `pluginRuntime: "v1"` at minimum. The registry checks every entry against `ASSISTANT_API_VERSIONS` and refuses to register plugins that ask for a capability or version the assistant does not expose. |
|
|
@@ -487,7 +486,7 @@ export interface PluginInitContext {
|
|
|
487
486
|
config: unknown; // parsed config (or raw if no validator)
|
|
488
487
|
credentials: Record<string, string>; // resolved credentials from requiresCredential
|
|
489
488
|
logger: unknown; // pino child logger, tagged { plugin: <name> }
|
|
490
|
-
pluginStorageDir: string; //
|
|
489
|
+
pluginStorageDir: string; // <workspaceDir>/plugins-data/<name>/ (created by bootstrap)
|
|
491
490
|
assistantVersion: string; // assistant semver
|
|
492
491
|
apiVersions: Record<string, string[]>; // ASSISTANT_API_VERSIONS, for runtime checks
|
|
493
492
|
}
|
|
@@ -657,7 +656,7 @@ The registry's internal state is not mutable at runtime. `init()` and
|
|
|
657
656
|
`onShutdown()` hooks are fired exactly once per assistant boot.
|
|
658
657
|
|
|
659
658
|
If you need hot reload for development, symlink your plugin directory
|
|
660
|
-
into
|
|
659
|
+
into `<workspaceDir>/plugins/` so edits propagate, and automate the restart
|
|
661
660
|
loop externally.
|
|
662
661
|
|
|
663
662
|
## Troubleshooting
|
|
@@ -748,8 +747,7 @@ tail -f ~/.vellum/daemon.log \
|
|
|
748
747
|
|
|
749
748
|
### Plugin not loading at all
|
|
750
749
|
|
|
751
|
-
- Confirm the directory is under
|
|
752
|
-
equivalent under `$BASE_DATA_DIR/.vellum/plugins/`).
|
|
750
|
+
- Confirm the directory is under `<workspaceDir>/plugins/`.
|
|
753
751
|
- Confirm it has a `register.ts` or `register.js` at the top level.
|
|
754
752
|
- Check the assistant's stderr for a line like
|
|
755
753
|
`loaded user plugin (side-effect import completed)` or
|
package/knip.json
CHANGED
|
@@ -13,6 +13,38 @@ import { connect, type Socket } from "node:net";
|
|
|
13
13
|
import type { IpcRequest, IpcResponse, Logger } from "./types.js";
|
|
14
14
|
import { noopLogger } from "./types.js";
|
|
15
15
|
|
|
16
|
+
// ---------------------------------------------------------------------------
|
|
17
|
+
// Error surface
|
|
18
|
+
// ---------------------------------------------------------------------------
|
|
19
|
+
|
|
20
|
+
/**
|
|
21
|
+
* Error class thrown by `PersistentIpcClient.call` when the daemon returns
|
|
22
|
+
* a structured error envelope (i.e. `RouteError`-derived). Mirrors the HTTP
|
|
23
|
+
* adapter's `error.details` shape so IPC callers can branch on `errorCode`
|
|
24
|
+
* or recover machine-readable `errorDetails` (e.g. `version_incompatible`).
|
|
25
|
+
*/
|
|
26
|
+
export class IpcCallError extends Error {
|
|
27
|
+
readonly statusCode?: number;
|
|
28
|
+
readonly errorCode?: string;
|
|
29
|
+
readonly errorDetails?: unknown;
|
|
30
|
+
|
|
31
|
+
constructor(
|
|
32
|
+
message: string,
|
|
33
|
+
fields: {
|
|
34
|
+
statusCode?: number;
|
|
35
|
+
errorCode?: string;
|
|
36
|
+
errorDetails?: unknown;
|
|
37
|
+
} = {},
|
|
38
|
+
) {
|
|
39
|
+
super(message);
|
|
40
|
+
this.name = "IpcCallError";
|
|
41
|
+
if (fields.statusCode !== undefined) this.statusCode = fields.statusCode;
|
|
42
|
+
if (fields.errorCode !== undefined) this.errorCode = fields.errorCode;
|
|
43
|
+
if (fields.errorDetails !== undefined)
|
|
44
|
+
this.errorDetails = fields.errorDetails;
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
|
|
16
48
|
// ---------------------------------------------------------------------------
|
|
17
49
|
// Constants
|
|
18
50
|
// ---------------------------------------------------------------------------
|
|
@@ -294,7 +326,13 @@ export class PersistentIpcClient {
|
|
|
294
326
|
this.pending.delete(msg.id);
|
|
295
327
|
clearTimeout(entry.timer);
|
|
296
328
|
if (msg.error) {
|
|
297
|
-
entry.reject(
|
|
329
|
+
entry.reject(
|
|
330
|
+
new IpcCallError(msg.error, {
|
|
331
|
+
statusCode: msg.statusCode,
|
|
332
|
+
errorCode: msg.errorCode,
|
|
333
|
+
errorDetails: msg.errorDetails,
|
|
334
|
+
}),
|
|
335
|
+
);
|
|
298
336
|
} else {
|
|
299
337
|
entry.resolve(msg.result);
|
|
300
338
|
}
|
|
@@ -105,6 +105,17 @@ export interface IpcResponse {
|
|
|
105
105
|
id: string;
|
|
106
106
|
result?: unknown;
|
|
107
107
|
error?: string;
|
|
108
|
+
/** HTTP-style status code mirrored from `RouteError.statusCode`. */
|
|
109
|
+
statusCode?: number;
|
|
110
|
+
/** Machine-readable error code (e.g. "UNPROCESSABLE_ENTITY"). */
|
|
111
|
+
errorCode?: string;
|
|
112
|
+
/**
|
|
113
|
+
* Structured error payload mirroring `RouteError.details` — present only
|
|
114
|
+
* when the originating error carried a `details` field. Mirrors the HTTP
|
|
115
|
+
* adapter's `error.details` envelope so IPC clients can recover the same
|
|
116
|
+
* machine-readable context as HTTP clients.
|
|
117
|
+
*/
|
|
118
|
+
errorDetails?: unknown;
|
|
108
119
|
}
|
|
109
120
|
|
|
110
121
|
// ---------------------------------------------------------------------------
|
|
@@ -7,6 +7,8 @@
|
|
|
7
7
|
"exports": {
|
|
8
8
|
".": "./src/index.ts",
|
|
9
9
|
"./credential-rpc": "./src/credential-rpc.ts",
|
|
10
|
+
"./ingress": "./src/ingress.ts",
|
|
11
|
+
"./twilio-ingress": "./src/twilio-ingress.ts",
|
|
10
12
|
"./trust-rules": "./src/trust-rules.ts",
|
|
11
13
|
"./handles": "./src/handles.ts",
|
|
12
14
|
"./grants": "./src/grants.ts",
|
|
@@ -33,6 +33,8 @@ describe("package independence", () => {
|
|
|
33
33
|
"../transport.ts",
|
|
34
34
|
"../credential-rpc.ts",
|
|
35
35
|
"../trust-rules.ts",
|
|
36
|
+
"../ingress.ts",
|
|
37
|
+
"../twilio-ingress.ts",
|
|
36
38
|
"../error.ts",
|
|
37
39
|
];
|
|
38
40
|
|
|
@@ -219,6 +221,7 @@ describe("ToolResponseBaseSchema", () => {
|
|
|
219
221
|
result: { html: "<html></html>" },
|
|
220
222
|
});
|
|
221
223
|
expect(result.success).toBe(true);
|
|
224
|
+
if (!result.success) throw new Error("Expected successful response");
|
|
222
225
|
expect(result.result).toEqual({ html: "<html></html>" });
|
|
223
226
|
});
|
|
224
227
|
|
|
@@ -238,6 +241,7 @@ describe("ToolResponseBaseSchema", () => {
|
|
|
238
241
|
},
|
|
239
242
|
});
|
|
240
243
|
expect(result.success).toBe(false);
|
|
244
|
+
if (result.success) throw new Error("Expected failed response");
|
|
241
245
|
expect(result.error.code).toBe("TOOL_FAILED");
|
|
242
246
|
});
|
|
243
247
|
|
|
@@ -0,0 +1,107 @@
|
|
|
1
|
+
import { describe, expect, test } from "bun:test";
|
|
2
|
+
|
|
3
|
+
import {
|
|
4
|
+
normalizeHttpPublicBaseUrl,
|
|
5
|
+
normalizePublicBaseUrl,
|
|
6
|
+
} from "../ingress.js";
|
|
7
|
+
import {
|
|
8
|
+
buildTwilioConnectActionUrl,
|
|
9
|
+
buildTwilioMediaStreamUrl,
|
|
10
|
+
buildTwilioPhoneNumberWebhookUrls,
|
|
11
|
+
buildTwilioRelayUrl,
|
|
12
|
+
buildTwilioVoiceWebhookUrl,
|
|
13
|
+
resolveTwilioPublicBaseUrl,
|
|
14
|
+
} from "../twilio-ingress.js";
|
|
15
|
+
|
|
16
|
+
describe("normalizePublicBaseUrl", () => {
|
|
17
|
+
test("trims whitespace and trailing slashes", () => {
|
|
18
|
+
expect(normalizePublicBaseUrl(" https://example.test/path/// ")).toBe(
|
|
19
|
+
"https://example.test/path",
|
|
20
|
+
);
|
|
21
|
+
});
|
|
22
|
+
|
|
23
|
+
test("rejects non-string and empty values", () => {
|
|
24
|
+
expect(normalizePublicBaseUrl(undefined)).toBeUndefined();
|
|
25
|
+
expect(normalizePublicBaseUrl(" ")).toBeUndefined();
|
|
26
|
+
});
|
|
27
|
+
});
|
|
28
|
+
|
|
29
|
+
describe("normalizeHttpPublicBaseUrl", () => {
|
|
30
|
+
test("normalizes valid HTTP and HTTPS URLs", () => {
|
|
31
|
+
expect(normalizeHttpPublicBaseUrl(" HTTPS://EXAMPLE.TEST/twilio ")).toBe(
|
|
32
|
+
"https://example.test/twilio",
|
|
33
|
+
);
|
|
34
|
+
expect(normalizeHttpPublicBaseUrl("https://example.test/twilio///")).toBe(
|
|
35
|
+
"https://example.test/twilio",
|
|
36
|
+
);
|
|
37
|
+
expect(normalizeHttpPublicBaseUrl("https://example.test")).toBe(
|
|
38
|
+
"https://example.test/",
|
|
39
|
+
);
|
|
40
|
+
});
|
|
41
|
+
|
|
42
|
+
test("rejects non-HTTP URLs and malformed values", () => {
|
|
43
|
+
expect(normalizeHttpPublicBaseUrl("ftp://example.test")).toBeUndefined();
|
|
44
|
+
expect(normalizeHttpPublicBaseUrl("notaurl")).toBeUndefined();
|
|
45
|
+
expect(normalizeHttpPublicBaseUrl("")).toBeUndefined();
|
|
46
|
+
});
|
|
47
|
+
|
|
48
|
+
test("rejects query strings and fragments instead of mutating them", () => {
|
|
49
|
+
expect(
|
|
50
|
+
normalizeHttpPublicBaseUrl("https://example.test/twilio?token=abc/"),
|
|
51
|
+
).toBeUndefined();
|
|
52
|
+
expect(
|
|
53
|
+
normalizeHttpPublicBaseUrl("https://example.test/twilio#section/"),
|
|
54
|
+
).toBeUndefined();
|
|
55
|
+
expect(
|
|
56
|
+
normalizeHttpPublicBaseUrl("https://example.test/twilio?"),
|
|
57
|
+
).toBeUndefined();
|
|
58
|
+
expect(
|
|
59
|
+
normalizeHttpPublicBaseUrl("https://example.test/twilio#"),
|
|
60
|
+
).toBeUndefined();
|
|
61
|
+
});
|
|
62
|
+
});
|
|
63
|
+
|
|
64
|
+
describe("Twilio ingress helpers", () => {
|
|
65
|
+
test("resolves public base URL with fallback", () => {
|
|
66
|
+
expect(
|
|
67
|
+
resolveTwilioPublicBaseUrl({
|
|
68
|
+
publicBaseUrl: " https://twilio.example.test/twilio/ ",
|
|
69
|
+
}),
|
|
70
|
+
).toBe("https://twilio.example.test/twilio");
|
|
71
|
+
expect(
|
|
72
|
+
resolveTwilioPublicBaseUrl({
|
|
73
|
+
publicBaseUrl: " ",
|
|
74
|
+
}),
|
|
75
|
+
).toBeUndefined();
|
|
76
|
+
expect(
|
|
77
|
+
resolveTwilioPublicBaseUrl({
|
|
78
|
+
publicBaseUrl: " ",
|
|
79
|
+
}, "https://fallback.example.test/"),
|
|
80
|
+
).toBe("https://fallback.example.test");
|
|
81
|
+
expect(
|
|
82
|
+
resolveTwilioPublicBaseUrl({}, "https://fallback.example.test/"),
|
|
83
|
+
).toBe("https://fallback.example.test");
|
|
84
|
+
});
|
|
85
|
+
|
|
86
|
+
test("builds Twilio webhook and WebSocket URLs from one base URL", () => {
|
|
87
|
+
expect(buildTwilioVoiceWebhookUrl("https://example.test")).toBe(
|
|
88
|
+
"https://example.test/webhooks/twilio/voice",
|
|
89
|
+
);
|
|
90
|
+
expect(buildTwilioVoiceWebhookUrl("https://example.test", "call-123")).toBe(
|
|
91
|
+
"https://example.test/webhooks/twilio/voice?callSessionId=call-123",
|
|
92
|
+
);
|
|
93
|
+
expect(buildTwilioConnectActionUrl("https://example.test")).toBe(
|
|
94
|
+
"https://example.test/webhooks/twilio/connect-action",
|
|
95
|
+
);
|
|
96
|
+
expect(buildTwilioRelayUrl("https://example.test")).toBe(
|
|
97
|
+
"wss://example.test/webhooks/twilio/relay",
|
|
98
|
+
);
|
|
99
|
+
expect(buildTwilioMediaStreamUrl("http://example.test")).toBe(
|
|
100
|
+
"ws://example.test/webhooks/twilio/media-stream",
|
|
101
|
+
);
|
|
102
|
+
expect(buildTwilioPhoneNumberWebhookUrls("https://example.test")).toEqual({
|
|
103
|
+
statusCallbackUrl: "https://example.test/webhooks/twilio/status",
|
|
104
|
+
voiceUrl: "https://example.test/webhooks/twilio/voice",
|
|
105
|
+
});
|
|
106
|
+
});
|
|
107
|
+
});
|
|
@@ -6,9 +6,11 @@
|
|
|
6
6
|
*
|
|
7
7
|
* - `@vellumai/service-contracts/credential-rpc` — transport, RPC, handles, grants, rendering, error
|
|
8
8
|
* - `@vellumai/service-contracts/trust-rules` — trust-rule types and parsing helpers
|
|
9
|
+
* - `@vellumai/service-contracts/twilio-ingress` — shared Twilio ingress config constants
|
|
10
|
+
* - `@vellumai/service-contracts/ingress` — shared public ingress URL helpers
|
|
9
11
|
*
|
|
10
12
|
* Fine-grained subpaths are also available for low-friction migration:
|
|
11
|
-
* `./rpc`, `./handles`, `./grants`, `./rendering`, `./error`, `./trust-rules`
|
|
13
|
+
* `./rpc`, `./handles`, `./grants`, `./rendering`, `./error`, `./trust-rules`, `./ingress`, `./twilio-ingress`
|
|
12
14
|
*
|
|
13
15
|
* Neutral wire-protocol contracts for communication between the assistant
|
|
14
16
|
* daemon and the Credential Execution Service (CES). This package is
|
|
@@ -23,3 +25,5 @@ export * from "./grants.js";
|
|
|
23
25
|
export * from "./rpc.js";
|
|
24
26
|
export * from "./rendering.js";
|
|
25
27
|
export * from "./trust-rules.js";
|
|
28
|
+
export * from "./ingress.js";
|
|
29
|
+
export * from "./twilio-ingress.js";
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
export function normalizePublicBaseUrl(value: unknown): string | undefined {
|
|
2
|
+
if (typeof value !== "string") return undefined;
|
|
3
|
+
const normalized = value.trim().replace(/\/+$/, "");
|
|
4
|
+
return normalized.length > 0 ? normalized : undefined;
|
|
5
|
+
}
|
|
6
|
+
|
|
7
|
+
export function normalizeHttpPublicBaseUrl(value: unknown): string | undefined {
|
|
8
|
+
if (typeof value !== "string") return undefined;
|
|
9
|
+
const trimmed = value.trim();
|
|
10
|
+
if (trimmed.length === 0) return undefined;
|
|
11
|
+
if (/[?#]/.test(trimmed)) return undefined;
|
|
12
|
+
|
|
13
|
+
try {
|
|
14
|
+
const url = new URL(trimmed);
|
|
15
|
+
if (url.protocol !== "http:" && url.protocol !== "https:") {
|
|
16
|
+
return undefined;
|
|
17
|
+
}
|
|
18
|
+
if (!url.hostname) return undefined;
|
|
19
|
+
url.pathname = url.pathname.replace(/\/+$/, "") || "/";
|
|
20
|
+
return url.toString();
|
|
21
|
+
} catch {
|
|
22
|
+
return undefined;
|
|
23
|
+
}
|
|
24
|
+
}
|
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
import { normalizePublicBaseUrl } from "./ingress.js";
|
|
2
|
+
|
|
3
|
+
export const TWILIO_VOICE_WEBHOOK_PATH = "/webhooks/twilio/voice";
|
|
4
|
+
export const TWILIO_STATUS_WEBHOOK_PATH = "/webhooks/twilio/status";
|
|
5
|
+
export const TWILIO_CONNECT_ACTION_WEBHOOK_PATH =
|
|
6
|
+
"/webhooks/twilio/connect-action";
|
|
7
|
+
export const TWILIO_RELAY_WEBHOOK_PATH = "/webhooks/twilio/relay";
|
|
8
|
+
export const TWILIO_MEDIA_STREAM_WEBHOOK_PATH = "/webhooks/twilio/media-stream";
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* Sentinel placeholder embedded in TwiML by the assistant where the real
|
|
12
|
+
* public base URL should go. The gateway replaces `wss://__VELLUM_PUBLIC_BASE_URL__/…`
|
|
13
|
+
* with the actual public URL (from Velay registration, config, or the
|
|
14
|
+
* `X-Vellum-Ingress-URL` header) before returning TwiML to Twilio.
|
|
15
|
+
*
|
|
16
|
+
* The placeholder uses `https://` so that `buildTwilioRelayUrl` /
|
|
17
|
+
* `buildTwilioMediaStreamUrl` can apply the standard `http→ws` scheme
|
|
18
|
+
* conversion, producing `wss://__VELLUM_PUBLIC_BASE_URL__/…` in the output.
|
|
19
|
+
*/
|
|
20
|
+
export const TWILIO_PUBLIC_BASE_URL_PLACEHOLDER =
|
|
21
|
+
"https://__VELLUM_PUBLIC_BASE_URL__";
|
|
22
|
+
|
|
23
|
+
/**
|
|
24
|
+
* The WebSocket-scheme form of the placeholder that appears in TwiML after
|
|
25
|
+
* the `http→ws` scheme conversion applied by the URL builders.
|
|
26
|
+
*/
|
|
27
|
+
export const TWILIO_PUBLIC_BASE_WSS_PLACEHOLDER =
|
|
28
|
+
"wss://__VELLUM_PUBLIC_BASE_URL__";
|
|
29
|
+
|
|
30
|
+
export { normalizePublicBaseUrl } from "./ingress.js";
|
|
31
|
+
|
|
32
|
+
export type TwilioPhoneNumberWebhookUrls = {
|
|
33
|
+
statusCallbackUrl: string;
|
|
34
|
+
voiceUrl: string;
|
|
35
|
+
};
|
|
36
|
+
|
|
37
|
+
export function resolveTwilioPublicBaseUrl(
|
|
38
|
+
ingress: { publicBaseUrl?: unknown } | undefined,
|
|
39
|
+
fallbackPublicBaseUrl?: unknown,
|
|
40
|
+
): string | undefined {
|
|
41
|
+
const publicBaseUrl = normalizePublicBaseUrl(ingress?.publicBaseUrl);
|
|
42
|
+
if (publicBaseUrl) return publicBaseUrl;
|
|
43
|
+
|
|
44
|
+
return normalizePublicBaseUrl(fallbackPublicBaseUrl);
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
export function buildTwilioVoiceWebhookUrl(
|
|
48
|
+
baseUrl: string,
|
|
49
|
+
callSessionId?: string,
|
|
50
|
+
): string {
|
|
51
|
+
if (callSessionId) {
|
|
52
|
+
return `${baseUrl}${TWILIO_VOICE_WEBHOOK_PATH}?callSessionId=${callSessionId}`;
|
|
53
|
+
}
|
|
54
|
+
return `${baseUrl}${TWILIO_VOICE_WEBHOOK_PATH}`;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
export function buildTwilioStatusWebhookUrl(baseUrl: string): string {
|
|
58
|
+
return `${baseUrl}${TWILIO_STATUS_WEBHOOK_PATH}`;
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
export function buildTwilioConnectActionUrl(baseUrl: string): string {
|
|
62
|
+
return `${baseUrl}${TWILIO_CONNECT_ACTION_WEBHOOK_PATH}`;
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
export function buildTwilioRelayUrl(baseUrl: string): string {
|
|
66
|
+
return `${toTwilioWebSocketBaseUrl(baseUrl)}${TWILIO_RELAY_WEBHOOK_PATH}`;
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
export function buildTwilioMediaStreamUrl(baseUrl: string): string {
|
|
70
|
+
return `${toTwilioWebSocketBaseUrl(baseUrl)}${TWILIO_MEDIA_STREAM_WEBHOOK_PATH}`;
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
export function buildTwilioPhoneNumberWebhookUrls(
|
|
74
|
+
baseUrl: string,
|
|
75
|
+
): TwilioPhoneNumberWebhookUrls {
|
|
76
|
+
return {
|
|
77
|
+
statusCallbackUrl: buildTwilioStatusWebhookUrl(baseUrl),
|
|
78
|
+
voiceUrl: buildTwilioVoiceWebhookUrl(baseUrl),
|
|
79
|
+
};
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
function toTwilioWebSocketBaseUrl(baseUrl: string): string {
|
|
83
|
+
return baseUrl.replace(/^http(s?)/, "ws$1");
|
|
84
|
+
}
|
|
@@ -84,3 +84,12 @@ export function formatSseFrame(event: AssistantEvent): string {
|
|
|
84
84
|
export function formatSseHeartbeat(): string {
|
|
85
85
|
return ": heartbeat\n\n";
|
|
86
86
|
}
|
|
87
|
+
|
|
88
|
+
/**
|
|
89
|
+
* Format a keep-alive as both an SSE comment (for proxy keepalive) and a
|
|
90
|
+
* data-bearing event (so fetch-based SSE clients that cannot observe comment
|
|
91
|
+
* lines can still detect heartbeats for disconnect watchdogs).
|
|
92
|
+
*/
|
|
93
|
+
export function formatSseHeartbeatWithData(): string {
|
|
94
|
+
return `${formatSseHeartbeat()}event: assistant_event\ndata: ${JSON.stringify({ type: "heartbeat" })}\n\n`;
|
|
95
|
+
}
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
{
|
|
2
|
+
"lockfileVersion": 1,
|
|
3
|
+
"configVersion": 1,
|
|
4
|
+
"workspaces": {
|
|
5
|
+
"": {
|
|
6
|
+
"name": "@vellumai/twilio-client",
|
|
7
|
+
"devDependencies": {
|
|
8
|
+
"@types/bun": "1.3.10",
|
|
9
|
+
"typescript": "5.9.3",
|
|
10
|
+
},
|
|
11
|
+
},
|
|
12
|
+
},
|
|
13
|
+
"packages": {
|
|
14
|
+
"@types/bun": ["@types/bun@1.3.10", "", { "dependencies": { "bun-types": "1.3.10" } }, "sha512-0+rlrUrOrTSskibryHbvQkDOWRJwJZqZlxrUs1u4oOoTln8+WIXBPmAuCF35SWB2z4Zl3E84Nl/D0P7803nigQ=="],
|
|
15
|
+
|
|
16
|
+
"@types/node": ["@types/node@25.6.0", "", { "dependencies": { "undici-types": "~7.19.0" } }, "sha512-+qIYRKdNYJwY3vRCZMdJbPLJAtGjQBudzZzdzwQYkEPQd+PJGixUL5QfvCLDaULoLv+RhT3LDkwEfKaAkgSmNQ=="],
|
|
17
|
+
|
|
18
|
+
"bun-types": ["bun-types@1.3.10", "", { "dependencies": { "@types/node": "*" } }, "sha512-tcpfCCl6XWo6nCVnpcVrxQ+9AYN1iqMIzgrSKYMB/fjLtV2eyAVEg7AxQJuCq/26R6HpKWykQXuSOq/21RYcbg=="],
|
|
19
|
+
|
|
20
|
+
"typescript": ["typescript@5.9.3", "", { "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" } }, "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw=="],
|
|
21
|
+
|
|
22
|
+
"undici-types": ["undici-types@7.19.2", "", {}, "sha512-qYVnV5OEm2AW8cJMCpdV20CDyaN3g0AjDlOGf1OW4iaDEx8MwdtChUp4zu4H0VP3nDRF/8RKWH+IPp9uW0YGZg=="],
|
|
23
|
+
}
|
|
24
|
+
}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@vellumai/twilio-client",
|
|
3
|
+
"version": "0.0.1",
|
|
4
|
+
"private": true,
|
|
5
|
+
"license": "MIT",
|
|
6
|
+
"type": "module",
|
|
7
|
+
"exports": {
|
|
8
|
+
".": "./src/index.ts"
|
|
9
|
+
},
|
|
10
|
+
"scripts": {
|
|
11
|
+
"typecheck": "bunx tsc --noEmit",
|
|
12
|
+
"test": "bun test src/"
|
|
13
|
+
},
|
|
14
|
+
"devDependencies": {
|
|
15
|
+
"@types/bun": "1.3.10",
|
|
16
|
+
"typescript": "5.9.3"
|
|
17
|
+
}
|
|
18
|
+
}
|
|
@@ -0,0 +1,128 @@
|
|
|
1
|
+
import { describe, expect, mock, test } from "bun:test";
|
|
2
|
+
|
|
3
|
+
import {
|
|
4
|
+
lookupIncomingPhoneNumberSid,
|
|
5
|
+
twilioAuthHeader,
|
|
6
|
+
twilioBaseUrl,
|
|
7
|
+
TwilioRestError,
|
|
8
|
+
updatePhoneNumberWebhooks,
|
|
9
|
+
type TwilioFetch,
|
|
10
|
+
} from "../index.js";
|
|
11
|
+
|
|
12
|
+
const ACCOUNT_SID = "AC123";
|
|
13
|
+
const AUTH_TOKEN = "auth-token";
|
|
14
|
+
const PHONE_NUMBER = "+15550100";
|
|
15
|
+
const PHONE_NUMBER_SID = "PN123";
|
|
16
|
+
|
|
17
|
+
function jsonResponse(body: unknown, status = 200): Response {
|
|
18
|
+
return new Response(JSON.stringify(body), {
|
|
19
|
+
headers: { "Content-Type": "application/json" },
|
|
20
|
+
status,
|
|
21
|
+
});
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
describe("twilioAuthHeader", () => {
|
|
25
|
+
test("returns a Basic auth header", () => {
|
|
26
|
+
expect(twilioAuthHeader("AC_test_sid", "test_token")).toBe(
|
|
27
|
+
"Basic " + Buffer.from("AC_test_sid:test_token").toString("base64"),
|
|
28
|
+
);
|
|
29
|
+
});
|
|
30
|
+
});
|
|
31
|
+
|
|
32
|
+
describe("twilioBaseUrl", () => {
|
|
33
|
+
test("constructs the account-scoped REST API URL", () => {
|
|
34
|
+
expect(twilioBaseUrl("AC_abc123")).toBe(
|
|
35
|
+
"https://api.twilio.com/2010-04-01/Accounts/AC_abc123",
|
|
36
|
+
);
|
|
37
|
+
});
|
|
38
|
+
});
|
|
39
|
+
|
|
40
|
+
describe("lookupIncomingPhoneNumberSid", () => {
|
|
41
|
+
test("returns the matching incoming phone number SID", async () => {
|
|
42
|
+
const fetchImpl = mock<TwilioFetch>(async () =>
|
|
43
|
+
jsonResponse({
|
|
44
|
+
incoming_phone_numbers: [
|
|
45
|
+
{ phone_number: "+15550199", sid: "PN_OTHER" },
|
|
46
|
+
{ phone_number: PHONE_NUMBER, sid: PHONE_NUMBER_SID },
|
|
47
|
+
],
|
|
48
|
+
}),
|
|
49
|
+
);
|
|
50
|
+
|
|
51
|
+
await expect(
|
|
52
|
+
lookupIncomingPhoneNumberSid({
|
|
53
|
+
accountSid: ACCOUNT_SID,
|
|
54
|
+
authToken: AUTH_TOKEN,
|
|
55
|
+
fetchImpl,
|
|
56
|
+
phoneNumber: PHONE_NUMBER,
|
|
57
|
+
}),
|
|
58
|
+
).resolves.toBe(PHONE_NUMBER_SID);
|
|
59
|
+
});
|
|
60
|
+
|
|
61
|
+
test("throws TwilioRestError for lookup failures", async () => {
|
|
62
|
+
const fetchImpl = mock<TwilioFetch>(
|
|
63
|
+
async () => new Response("unavailable", { status: 503 }),
|
|
64
|
+
);
|
|
65
|
+
|
|
66
|
+
await expect(
|
|
67
|
+
lookupIncomingPhoneNumberSid({
|
|
68
|
+
accountSid: ACCOUNT_SID,
|
|
69
|
+
authToken: AUTH_TOKEN,
|
|
70
|
+
fetchImpl,
|
|
71
|
+
phoneNumber: PHONE_NUMBER,
|
|
72
|
+
}),
|
|
73
|
+
).rejects.toBeInstanceOf(TwilioRestError);
|
|
74
|
+
});
|
|
75
|
+
});
|
|
76
|
+
|
|
77
|
+
describe("updatePhoneNumberWebhooks", () => {
|
|
78
|
+
test("looks up the SID and posts voice webhook settings", async () => {
|
|
79
|
+
const calls: Array<{ input: string | URL | Request; init?: RequestInit }> =
|
|
80
|
+
[];
|
|
81
|
+
const fetchImpl = mock<TwilioFetch>(
|
|
82
|
+
async (input: string | URL | Request, init?: RequestInit) => {
|
|
83
|
+
calls.push({ input, init });
|
|
84
|
+
if (calls.length === 1) {
|
|
85
|
+
return jsonResponse({
|
|
86
|
+
incoming_phone_numbers: [
|
|
87
|
+
{ phone_number: PHONE_NUMBER, sid: PHONE_NUMBER_SID },
|
|
88
|
+
],
|
|
89
|
+
});
|
|
90
|
+
}
|
|
91
|
+
return jsonResponse({});
|
|
92
|
+
},
|
|
93
|
+
);
|
|
94
|
+
|
|
95
|
+
await updatePhoneNumberWebhooks({
|
|
96
|
+
accountSid: ACCOUNT_SID,
|
|
97
|
+
authToken: AUTH_TOKEN,
|
|
98
|
+
fetchImpl,
|
|
99
|
+
phoneNumber: PHONE_NUMBER,
|
|
100
|
+
webhooks: {
|
|
101
|
+
statusCallbackUrl: "https://example.test/webhooks/twilio/status",
|
|
102
|
+
voiceUrl: "https://example.test/webhooks/twilio/voice",
|
|
103
|
+
},
|
|
104
|
+
});
|
|
105
|
+
|
|
106
|
+
expect(calls).toHaveLength(2);
|
|
107
|
+
expect(String(calls[0].input)).toContain(
|
|
108
|
+
`/Accounts/${ACCOUNT_SID}/IncomingPhoneNumbers.json?PhoneNumber=%2B15550100`,
|
|
109
|
+
);
|
|
110
|
+
expect(String(calls[1].input)).toContain(
|
|
111
|
+
`/Accounts/${ACCOUNT_SID}/IncomingPhoneNumbers/${PHONE_NUMBER_SID}.json`,
|
|
112
|
+
);
|
|
113
|
+
expect(calls[1].init?.method).toBe("POST");
|
|
114
|
+
expect(calls[1].init?.headers).toEqual({
|
|
115
|
+
Authorization:
|
|
116
|
+
"Basic " +
|
|
117
|
+
Buffer.from(`${ACCOUNT_SID}:${AUTH_TOKEN}`).toString("base64"),
|
|
118
|
+
"Content-Type": "application/x-www-form-urlencoded",
|
|
119
|
+
});
|
|
120
|
+
const body = new URLSearchParams(String(calls[1].init?.body));
|
|
121
|
+
expect(body.get("VoiceUrl")).toBe(
|
|
122
|
+
"https://example.test/webhooks/twilio/voice",
|
|
123
|
+
);
|
|
124
|
+
expect(body.get("StatusCallback")).toBe(
|
|
125
|
+
"https://example.test/webhooks/twilio/status",
|
|
126
|
+
);
|
|
127
|
+
});
|
|
128
|
+
});
|