@vellumai/assistant 0.10.9 → 0.10.10-dev.202607170046.8482d40
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 +1 -1
- package/Dockerfile +8 -0
- package/docs/activation-funnel-telemetry.md +13 -7
- package/node_modules/@vellumai/ces-client/node_modules/@vellumai/service-contracts/package.json +1 -0
- package/node_modules/@vellumai/ces-client/node_modules/@vellumai/service-contracts/src/__tests__/ingress.test.ts +26 -0
- package/node_modules/@vellumai/ces-client/node_modules/@vellumai/service-contracts/src/__tests__/redacted-credential.test.ts +200 -0
- package/node_modules/@vellumai/ces-client/node_modules/@vellumai/service-contracts/src/ingress.ts +16 -0
- package/node_modules/@vellumai/ces-client/node_modules/@vellumai/service-contracts/src/redacted-credential.ts +226 -0
- package/node_modules/@vellumai/gateway-client/node_modules/@vellumai/service-contracts/package.json +1 -0
- package/node_modules/@vellumai/gateway-client/node_modules/@vellumai/service-contracts/src/__tests__/ingress.test.ts +26 -0
- package/node_modules/@vellumai/gateway-client/node_modules/@vellumai/service-contracts/src/__tests__/redacted-credential.test.ts +200 -0
- package/node_modules/@vellumai/gateway-client/node_modules/@vellumai/service-contracts/src/ingress.ts +16 -0
- package/node_modules/@vellumai/gateway-client/node_modules/@vellumai/service-contracts/src/redacted-credential.ts +226 -0
- package/node_modules/@vellumai/service-contracts/package.json +1 -0
- package/node_modules/@vellumai/service-contracts/src/__tests__/ingress.test.ts +26 -0
- package/node_modules/@vellumai/service-contracts/src/__tests__/redacted-credential.test.ts +200 -0
- package/node_modules/@vellumai/service-contracts/src/ingress.ts +16 -0
- package/node_modules/@vellumai/service-contracts/src/redacted-credential.ts +226 -0
- package/openapi.yaml +678 -107
- package/package.json +1 -1
- package/scripts/generate-openapi.ts +8 -0
- package/src/__tests__/activation-early-marking.test.ts +6 -5
- package/src/__tests__/agent-loop-override-profile.test.ts +22 -25
- package/src/__tests__/agent-wake-override-profile.test.ts +21 -48
- package/src/__tests__/app-builder-tool-scripts.test.ts +0 -1
- package/src/__tests__/app-bundler.test.ts +4 -12
- package/src/__tests__/app-executors.test.ts +2 -49
- package/src/__tests__/app-routes-csp.test.ts +114 -146
- package/src/__tests__/auth-fallback-events-store.test.ts +8 -1
- package/src/__tests__/build-persisted-content.test.ts +96 -0
- package/src/__tests__/bundle-scanner.test.ts +27 -1
- package/src/__tests__/call-controller.test.ts +291 -0
- package/src/__tests__/call-site-routing-connection-auto-resolve.test.ts +165 -0
- package/src/__tests__/chat-credential-redaction.test.ts +1395 -0
- package/src/__tests__/chat-reveal-guard-priming.test.ts +791 -0
- package/src/__tests__/compaction.benchmark.test.ts +2 -1
- package/src/__tests__/compactor-image-manifest-trust.test.ts +50 -0
- package/src/__tests__/config-loader-backfill.test.ts +9 -4
- package/src/__tests__/config-schema-cmd.test.ts +10 -11
- package/src/__tests__/config-schema.test.ts +190 -257
- package/src/__tests__/conversation-agent-loop-fatal-cleanup.test.ts +208 -0
- package/src/__tests__/conversation-agent-loop-overflow.test.ts +5 -19
- package/src/__tests__/conversation-agent-loop.test.ts +22 -9
- package/src/__tests__/conversation-error.test.ts +31 -0
- package/src/__tests__/conversation-load-history-repair.test.ts +110 -0
- package/src/__tests__/conversation-process-callsite.test.ts +12 -19
- package/src/__tests__/conversation-routes-slash-commands.test.ts +7 -21
- package/src/__tests__/conversation-summarize-route.test.ts +36 -44
- package/src/__tests__/conversation-surfaces-action-delivery.test.ts +1 -0
- package/src/__tests__/conversation-surfaces-activation-emit.test.ts +7 -7
- package/src/__tests__/conversation-tool-setup-app-refresh.test.ts +0 -1
- package/src/__tests__/conversation-tool-setup-attribution.test.ts +0 -1
- package/src/__tests__/conversation-usage.test.ts +4 -12
- package/src/__tests__/credential-prompt-route.test.ts +137 -0
- package/src/__tests__/credential-routes.test.ts +355 -0
- package/src/__tests__/credential-security-invariants.test.ts +5 -0
- package/src/__tests__/db-migration-rollback.test.ts +22 -0
- package/src/__tests__/empty-response-hook.test.ts +186 -1
- package/src/__tests__/external-plugin-loader.test.ts +11 -5
- package/src/__tests__/heartbeat-service.test.ts +0 -28
- package/src/__tests__/host-shell-tool.test.ts +2 -0
- package/src/__tests__/inactive-tool-error-messages.test.ts +2 -2
- package/src/__tests__/inference-no-mode-boot-e2e.test.ts +52 -8
- package/src/__tests__/internal-telemetry-routes.test.ts +23 -4
- package/src/__tests__/invite-routes-http.test.ts +12 -16
- package/src/__tests__/list-all-apps.test.ts +0 -4
- package/src/__tests__/llm-context-resolution.test.ts +32 -56
- package/src/__tests__/llm-request-log-turn-query.test.ts +109 -0
- package/src/__tests__/llm-resolver-override-or-default.test.ts +3 -52
- package/src/__tests__/llm-resolver.test.ts +342 -602
- package/src/__tests__/llm-schema.test.ts +79 -37
- package/src/__tests__/max-tokens-continue-hook.test.ts +19 -0
- package/src/__tests__/media-stream-output.test.ts +259 -3
- package/src/__tests__/media-stream-server-integration.test.ts +22 -1
- package/src/__tests__/media-stream-stt-session.test.ts +47 -0
- package/src/__tests__/memory-jobs-worker-cleanup-cadence.test.ts +33 -0
- package/src/__tests__/memory-recall-log-store.test.ts +47 -13
- package/src/__tests__/mock-gateway-ipc.ts +46 -1
- package/src/__tests__/mtime-cache.test.ts +61 -0
- package/src/__tests__/navigate-settings-tab.test.ts +2 -0
- package/src/__tests__/normalize-onboarding.test.ts +33 -0
- package/src/__tests__/onboarding-persona-write.test.ts +26 -0
- package/src/__tests__/plugin-api-resolve-credential.test.ts +140 -0
- package/src/__tests__/plugin-app-serve-routes.test.ts +166 -14
- package/src/__tests__/plugin-import-boundary-reverse-guard.test.ts +2 -0
- package/src/__tests__/post-turn-tool-result-truncation.test.ts +38 -0
- package/src/__tests__/provider-commit-message-generator.test.ts +27 -20
- package/src/__tests__/provider-connections-backfill.test.ts +138 -0
- package/src/__tests__/provider-platform-proxy-integration.test.ts +10 -31
- package/src/__tests__/provider-registry-ollama.test.ts +8 -18
- package/src/__tests__/provider-send-message-override-profile.test.ts +23 -23
- package/src/__tests__/provider-usage-tracking.test.ts +9 -19
- package/src/__tests__/prune-old-conversations-job.test.ts +12 -0
- package/src/__tests__/published-app-updater.test.ts +22 -16
- package/src/__tests__/registry.test.ts +5 -16
- package/src/__tests__/retry-openrouter-only-normalization.test.ts +22 -23
- package/src/__tests__/retry-thinking-adaptive-only.test.ts +43 -48
- package/src/__tests__/retry-thinking-tool-choice.test.ts +57 -66
- package/src/__tests__/retry-verbosity-normalization.test.ts +24 -23
- package/src/__tests__/reveal-success-registry.test.ts +123 -0
- package/src/__tests__/run-conversation-turn-persistence.test.ts +130 -0
- package/src/__tests__/secret-fixtures.ts +9 -0
- package/src/__tests__/secret-routes-acp-guard.test.ts +77 -0
- package/src/__tests__/server-history-render.test.ts +48 -0
- package/src/__tests__/skills.test.ts +9 -4
- package/src/__tests__/slack-share-routes.test.ts +0 -1
- package/src/__tests__/stt-stream-session.test.ts +6 -5
- package/src/__tests__/subagent-call-site-routing.test.ts +69 -95
- package/src/__tests__/subagent-disposal.test.ts +2 -0
- package/src/__tests__/subagent-fork-notifications.test.ts +2 -0
- package/src/__tests__/subagent-fork-spawn.test.ts +2 -0
- package/src/__tests__/subagent-manager-notify.test.ts +2 -0
- package/src/__tests__/subagent-role-registry.test.ts +37 -0
- package/src/__tests__/subagent-spawn-and-await.test.ts +1 -0
- package/src/__tests__/subagent-terminal-message.test.ts +50 -0
- package/src/__tests__/subagent-tool-gate-mode.test.ts +78 -5
- package/src/__tests__/surface-completion-nudge-hook.test.ts +19 -0
- package/src/__tests__/task-progress-nudge-hook.test.ts +1 -1
- package/src/__tests__/telemetry-routes.test.ts +99 -19
- package/src/__tests__/tool-audit.test.ts +34 -4
- package/src/__tests__/tool-executor-lifecycle-events.test.ts +1 -1
- package/src/__tests__/tool-profiler.test.ts +72 -1
- package/src/__tests__/tool-result-spool.test.ts +49 -4
- package/src/__tests__/tool-side-effects-slack-dm.test.ts +0 -1
- package/src/__tests__/ui-channel-variants.test.ts +108 -0
- package/src/__tests__/ui-shape-teaching.test.ts +255 -0
- package/src/__tests__/usage-attribution.test.ts +18 -41
- package/src/__tests__/user-plugin-loader.test.ts +4 -4
- package/src/__tests__/voice-config-update.test.ts +46 -0
- package/src/__tests__/voice-session-bridge.test.ts +216 -84
- package/src/__tests__/weak-open-model.test.ts +8 -1
- package/src/__tests__/workspace-migration-131-drop-web-fetch-mode.test.ts +120 -0
- package/src/acp/__tests__/acp-claude-oauth.test.ts +206 -0
- package/src/acp/__tests__/acp-connect-card-state.test.ts +23 -0
- package/src/acp/__tests__/acp-credentials.test.ts +87 -0
- package/src/acp/__tests__/prepare-agent-env.test.ts +161 -1
- package/src/acp/acp-claude-oauth.ts +144 -0
- package/src/acp/acp-connect-card-state.ts +39 -0
- package/src/acp/acp-credentials.ts +67 -0
- package/src/acp/prepare-agent-env.ts +103 -6
- package/src/agent/loop.ts +8 -3
- package/src/api/events/open-conversation.test.ts +64 -0
- package/src/api/events/open-conversation.ts +33 -0
- package/src/api/events/tool-result.ts +8 -0
- package/src/api/index.ts +6 -0
- package/src/api/responses/conversation-message.ts +14 -0
- package/src/apps/app-store.ts +25 -35
- package/src/bundler/app-bundler.ts +34 -48
- package/src/bundler/app-compiler.ts +39 -4
- package/src/bundler/bundle-scanner.ts +13 -0
- package/src/bundler/manifest.ts +1 -1
- package/src/calls/__tests__/voice-session-bridge.test.ts +47 -0
- package/src/calls/call-constants.ts +5 -0
- package/src/calls/call-controller.ts +100 -32
- package/src/calls/call-transport.ts +9 -0
- package/src/calls/media-stream-output.ts +107 -4
- package/src/calls/media-stream-server.ts +29 -9
- package/src/calls/media-stream-stt-session.ts +7 -1
- package/src/calls/media-turn-detector.ts +11 -1
- package/src/calls/voice-session-bridge.ts +84 -63
- package/src/cli/commands/__tests__/inference-providers.test.ts +270 -33
- package/src/cli/commands/__tests__/inference-send.test.ts +50 -0
- package/src/cli/commands/__tests__/notifications.test.ts +24 -3
- package/src/cli/commands/config.help.ts +6 -6
- package/src/cli/commands/credentials.help.ts +13 -0
- package/src/cli/commands/credentials.ts +23 -1
- package/src/cli/commands/email.help.ts +7 -0
- package/src/cli/commands/email.ts +35 -1
- package/src/cli/commands/inference-providers.ts +168 -107
- package/src/cli/commands/inference.help.ts +118 -46
- package/src/cli/commands/inference.ts +6 -0
- package/src/cli/commands/memory/index.help.ts +23 -0
- package/src/cli/commands/memory/nodes.ts +146 -0
- package/src/cli/commands/notifications.help.ts +10 -10
- package/src/cli/commands/oauth/connect-surface-guidance.test.ts +40 -0
- package/src/cli/commands/oauth/connect-surface-guidance.ts +54 -0
- package/src/cli/commands/oauth/connect.test.ts +126 -0
- package/src/cli/commands/oauth/connect.ts +29 -6
- package/src/cli/commands/oauth/index.help.ts +7 -1
- package/src/cli/commands/oauth/status.test.ts +69 -3
- package/src/cli/commands/oauth/status.ts +50 -12
- package/src/cli/commands/plugins.help.ts +13 -2
- package/src/cli/commands/plugins.ts +61 -7
- package/src/cli/commands/telemetry.help.ts +13 -0
- package/src/cli/commands/telemetry.ts +45 -5
- package/src/cli/lib/__tests__/plugin-catalog-local.test.ts +8 -2
- package/src/cli/lib/__tests__/upgrade-plugin.test.ts +213 -9
- package/src/cli/lib/bundled-marketplace.json +93 -36
- package/src/cli/lib/inspect-plugin.ts +5 -1
- package/src/cli/lib/install-from-github.ts +68 -4
- package/src/cli/lib/plugin-catalog-local.ts +6 -2
- package/src/cli/lib/plugin-fingerprint.ts +3 -3
- package/src/cli/lib/upgrade-plugin.ts +236 -35
- package/src/config/__tests__/default-profile-catalog.test.ts +8 -12
- package/src/config/__tests__/plugin-resident-skill-discovery.test.ts +137 -0
- package/src/config/__tests__/profile-materialization.test.ts +1 -88
- package/src/config/bundled-skills/AGENTS.md +3 -30
- package/src/config/bundled-skills/acp/SKILL.md +11 -3
- package/src/config/bundled-skills/app-builder/SKILL.md +5 -3
- package/src/config/bundled-skills/app-builder/TOOLS.json +23 -0
- package/src/config/bundled-skills/app-builder/tools/app-open.ts +32 -0
- package/src/config/bundled-skills/messaging/tools/messaging-send.ts +1 -1
- package/src/config/bundled-skills/phone-calls/references/CONFIG.md +7 -7
- package/src/config/bundled-skills/schedule/SKILL.md +1 -1
- package/src/config/bundled-skills/schedule/TOOLS.json +1 -1
- package/src/config/bundled-skills/settings/TOOLS.json +3 -1
- package/src/config/bundled-skills/settings/tools/navigate-settings-tab.ts +2 -0
- package/src/config/bundled-skills/settings/tools/voice-config-update.ts +18 -10
- package/src/config/bundled-skills/subagent/SKILL.md +2 -0
- package/src/config/bundled-skills/subagent/TOOLS.json +1 -1
- package/src/config/call-site-defaults.ts +3 -3
- package/src/config/feature-flag-registry.json +24 -39
- package/src/config/llm-resolver.ts +88 -475
- package/src/config/profile-materialization.ts +11 -11
- package/src/config/schema.ts +93 -127
- package/src/config/schemas/__tests__/live-voice.test.ts +24 -6
- package/src/config/schemas/__tests__/stt.test.ts +31 -3
- package/src/config/schemas/live-voice.ts +10 -4
- package/src/config/schemas/llm.ts +52 -15
- package/src/config/schemas/memory-lifecycle.ts +1 -1
- package/src/config/schemas/memory-retrospective.ts +8 -0
- package/src/config/schemas/memory-v2.ts +2 -2
- package/src/config/schemas/memory-v3.ts +1 -1
- package/src/config/schemas/services.ts +6 -3
- package/src/config/schemas/stt.ts +38 -21
- package/src/config/schemas/tts.ts +13 -18
- package/src/config/skills.ts +31 -21
- package/src/context/compactor.ts +44 -7
- package/src/context/post-turn-tool-result-truncation.ts +4 -2
- package/src/context/tool-result-spool.ts +26 -5
- package/src/conversations/__tests__/message-consolidation.test.ts +48 -0
- package/src/conversations/message-consolidation.ts +22 -2
- package/src/credential-execution/prompted-credential.ts +18 -0
- package/src/daemon/__tests__/conversation-tool-setup.test.ts +5 -12
- package/src/daemon/app-source-watcher.ts +17 -23
- package/src/daemon/chat-credential-redaction.ts +1365 -0
- package/src/daemon/conversation-agent-loop-handlers.ts +640 -34
- package/src/daemon/conversation-agent-loop.ts +71 -31
- package/src/daemon/conversation-error.ts +36 -14
- package/src/daemon/conversation-process.ts +22 -0
- package/src/daemon/conversation-store.ts +35 -0
- package/src/daemon/conversation-surfaces.ts +25 -25
- package/src/daemon/conversation-tool-setup.ts +33 -7
- package/src/daemon/conversation.ts +55 -5
- package/src/daemon/handlers/shared.ts +40 -2
- package/src/daemon/lifecycle.ts +4 -4
- package/src/daemon/message-types/conversations.ts +6 -17
- package/src/daemon/providers-setup.ts +8 -0
- package/src/daemon/tool-setup-types.ts +7 -1
- package/src/daemon/wake-conversation-ops.ts +10 -1
- package/src/hooks/types.ts +9 -0
- package/src/ipc/__tests__/email-ipc.test.ts +90 -0
- package/src/ipc/gateway-client.test.ts +59 -0
- package/src/ipc/gateway-client.ts +70 -27
- package/src/live-voice/__tests__/live-voice-events.test.ts +14 -2
- package/src/live-voice/__tests__/live-voice-integration.test.ts +116 -2
- package/src/live-voice/__tests__/live-voice-vad.test.ts +804 -13
- package/src/live-voice/__tests__/protocol.test.ts +122 -0
- package/src/live-voice/live-voice-session.ts +443 -40
- package/src/live-voice/protocol.ts +143 -1
- package/src/monitoring/__tests__/plugin-source-watch.test.ts +3 -1
- package/src/monitoring/plugin-source-watch.ts +3 -62
- package/src/notifications/README.md +1 -1
- package/src/permissions/checker.ts +8 -4
- package/src/persistence/__tests__/db-init-migrations-ok.test.ts +26 -0
- package/src/persistence/conversation-crud.ts +104 -2
- package/src/persistence/db-init.ts +14 -3
- package/src/persistence/job-handlers/cleanup.ts +15 -7
- package/src/persistence/llm-request-log-store.ts +88 -52
- package/src/persistence/migrations/298-move-memory-jobs-to-memory-db.ts +7 -31
- package/src/persistence/migrations/305-drop-contact-acl-columns.ts +3 -2
- package/src/persistence/migrations/326-move-injection-events-to-memory-db.ts +8 -34
- package/src/persistence/migrations/336-move-memory-v2-activation-logs-to-memory-db.ts +90 -0
- package/src/persistence/migrations/337-move-memory-recall-logs-to-memory-db.ts +114 -0
- package/src/persistence/migrations/338-move-memory-v3-selections-to-memory-db.ts +84 -0
- package/src/persistence/migrations/339-move-activation-sessions-to-memory-db.ts +52 -0
- package/src/persistence/migrations/__tests__/run-migrations.test.ts +155 -0
- package/src/persistence/migrations/helpers/relocation.ts +44 -1
- package/src/persistence/migrations/run-migrations.ts +25 -1
- package/src/persistence/schema/infrastructure.ts +9 -0
- package/src/persistence/schema/memory-core.ts +3 -0
- package/src/persistence/schema/memory-injection.ts +2 -0
- package/src/persistence/steps.ts +36 -0
- package/src/platform/client.test.ts +1 -44
- package/src/platform/client.ts +10 -20
- package/src/platform/consent-cache.test.ts +89 -23
- package/src/platform/consent-cache.ts +71 -33
- package/src/plugin-api/constants.ts +12 -0
- package/src/plugin-api/conversation-turn.ts +37 -14
- package/src/plugin-api/index.ts +11 -1
- package/src/plugin-api/resolve-credential.ts +75 -0
- package/src/plugin-api/vision-support.test.ts +8 -19
- package/src/plugin-api/vision-support.ts +26 -17
- package/src/plugins/collect-source-versions.ts +77 -0
- package/src/plugins/defaults/compaction/compact.ts +6 -0
- package/src/plugins/defaults/compaction/window-manager.ts +9 -0
- package/src/plugins/defaults/empty-response/hooks/post-model-call.ts +18 -24
- package/src/plugins/defaults/empty-response/hooks/user-prompt-submit.ts +38 -0
- package/src/plugins/defaults/empty-response/refusal-quarantine.ts +99 -0
- package/src/plugins/defaults/image-fallback/__tests__/caption-cache-persistence.test.ts +7 -3
- package/src/plugins/defaults/index.ts +8 -1
- package/src/plugins/defaults/max-tokens-continue/hooks/post-model-call.ts +4 -1
- package/src/plugins/defaults/memory/__tests__/activation-session-store.test.ts +48 -6
- package/src/plugins/defaults/memory/__tests__/db-memory-attach.test.ts +28 -22
- package/src/plugins/defaults/memory/__tests__/memory-log-stores-degraded.test.ts +148 -0
- package/src/plugins/defaults/memory/__tests__/memory-retrospective-job.test.ts +80 -8
- package/src/plugins/defaults/memory/__tests__/memory-retrospective-prompt.test.ts +202 -0
- package/src/plugins/defaults/memory/__tests__/memory-v2-activation-log-store.test.ts +64 -25
- package/src/plugins/defaults/memory/__tests__/memory-v2-concept-frequency.test.ts +24 -11
- package/src/plugins/defaults/memory/__tests__/prompt-override.test.ts +70 -6
- package/src/plugins/defaults/memory/__tests__/table-relocation.test.ts +278 -0
- package/src/plugins/defaults/memory/activation-session-store.ts +27 -20
- package/src/plugins/defaults/memory/context-search/sources/memory-v2.ts +2 -9
- package/src/plugins/defaults/memory/context-search/sources/workspace.ts +1 -8
- package/src/plugins/defaults/memory/graph/retriever.test.ts +6 -6
- package/src/plugins/defaults/memory/graph/store.ts +114 -0
- package/src/plugins/defaults/memory/graph/tool-handlers.ts +36 -1
- package/src/plugins/defaults/memory/graph/tools.ts +47 -13
- package/src/plugins/defaults/memory/graph-topology/build-memory-graph.ts +16 -35
- package/src/plugins/defaults/memory/jobs-worker.ts +10 -1
- package/src/plugins/defaults/memory/memory-db.ts +3 -2
- package/src/plugins/defaults/memory/memory-recall-log-store.ts +129 -66
- package/src/plugins/defaults/memory/memory-retrospective-constants.ts +8 -0
- package/src/plugins/defaults/memory/memory-retrospective-job.ts +11 -122
- package/src/plugins/defaults/memory/memory-retrospective-prompt.ts +216 -0
- package/src/plugins/defaults/memory/memory-v2-activation-log-store.ts +84 -46
- package/src/plugins/defaults/memory/memory-v2-concept-frequency.ts +42 -32
- package/src/plugins/defaults/memory/path-containment.ts +21 -0
- package/src/plugins/defaults/memory/prompt-override.ts +51 -13
- package/src/plugins/defaults/memory/tools.test.ts +34 -0
- package/src/plugins/defaults/memory/tools.ts +12 -2
- package/src/plugins/defaults/memory/v2/__tests__/harness-compare.test.ts +19 -15
- package/src/plugins/defaults/memory/v2/__tests__/harness-oracle.test.ts +24 -19
- package/src/plugins/defaults/memory/v2/__tests__/harness-replay-input.test.ts +19 -15
- package/src/plugins/defaults/memory/v2/__tests__/injection.test.ts +22 -2
- package/src/plugins/defaults/memory/v2/__tests__/prompts-consolidation.test.ts +5 -3
- package/src/plugins/defaults/memory/v2/harness/oracle.ts +59 -41
- package/src/plugins/defaults/memory/v2/harness/replay-input.ts +29 -25
- package/src/plugins/defaults/memory/v2/migration.ts +46 -16
- package/src/plugins/defaults/memory/v2/prompts/consolidation.ts +4 -0
- package/src/plugins/defaults/memory/v3/__tests__/carry-integration.test.ts +17 -15
- package/src/plugins/defaults/memory/v3/__tests__/gate.test.ts +24 -22
- package/src/plugins/defaults/memory/v3/__tests__/injection.test.ts +9 -5
- package/src/plugins/defaults/memory/v3/__tests__/orchestrate.test.ts +277 -10
- package/src/plugins/defaults/memory/v3/__tests__/selection-log-store.test.ts +57 -5
- package/src/plugins/defaults/memory/v3/__tests__/shadow-integration.test.ts +9 -7
- package/src/plugins/defaults/memory/v3/__tests__/shadow-plugin.test.ts +38 -7
- package/src/plugins/defaults/memory/v3/hot-set.test.ts +36 -18
- package/src/plugins/defaults/memory/v3/hot-set.ts +12 -14
- package/src/plugins/defaults/memory/v3/learned-edges.test.ts +47 -27
- package/src/plugins/defaults/memory/v3/learned-edges.ts +12 -14
- package/src/plugins/defaults/memory/v3/orchestrate.ts +139 -22
- package/src/plugins/defaults/memory/v3/prune.test.ts +10 -3
- package/src/plugins/defaults/memory/v3/prune.ts +17 -11
- package/src/plugins/defaults/memory/v3/selection-log-store.ts +23 -11
- package/src/plugins/defaults/memory/v3/shadow-plugin.ts +70 -53
- package/src/plugins/defaults/surface-completion-nudge/hooks/post-model-call.ts +9 -6
- package/src/plugins/defaults/task-progress-nudge/hooks/post-tool-use.ts +8 -6
- package/src/plugins/external-plugin-loader.ts +15 -15
- package/src/plugins/mtime-cache.ts +47 -0
- package/src/plugins/pipeline.ts +9 -1
- package/src/plugins/plugin-execution-context.ts +44 -0
- package/src/plugins/plugin-tree-walk.ts +31 -24
- package/src/plugins/source-fingerprint.ts +3 -4
- package/src/prompts/normalize-onboarding.ts +12 -0
- package/src/prompts/persona-resolver.ts +8 -0
- package/src/prompts/templates/BOOTSTRAP-ACTIVATION-RAIL.md +1 -1
- package/src/providers/__tests__/dispatch-connection-routing.test.ts +6 -9
- package/src/providers/__tests__/registry-native-web-search.test.ts +4 -10
- package/src/providers/__tests__/retry-callsite.test.ts +235 -215
- package/src/providers/__tests__/satellite-connection-routing.test.ts +8 -16
- package/src/providers/anthropic/client.ts +1 -0
- package/src/providers/atlascloud/client.ts +10 -49
- package/src/providers/baseten/client.ts +43 -0
- package/src/providers/call-site-routing.ts +26 -13
- package/src/providers/connection-resolution.ts +9 -11
- package/src/providers/fetch-provider-catalog.ts +4 -2
- package/src/providers/inference/__tests__/adapter-factory-openai-compatible.test.ts +29 -4
- package/src/providers/inference/__tests__/connection-availability-keyless.test.ts +78 -0
- package/src/providers/inference/adapter-factory.ts +27 -6
- package/src/providers/inference/auth.ts +29 -1
- package/src/providers/inference/backfill.ts +61 -10
- package/src/providers/inference/connection-availability.ts +3 -2
- package/src/providers/inference/resolve-auth.ts +9 -1
- package/src/providers/model-catalog.ts +49 -0
- package/src/providers/openai/__tests__/api-error-normalization.test.ts +24 -2
- package/src/providers/openai/__tests__/chat-completions-provider-endpoint.test.ts +159 -0
- package/src/providers/openai/api-key-validation.ts +70 -0
- package/src/providers/openai/chat-completions-provider.ts +1 -0
- package/src/providers/openai/responses-provider.ts +1 -0
- package/src/providers/retry.ts +2 -0
- package/src/providers/types.ts +18 -4
- package/src/providers/vellum-model-routing.test.ts +26 -0
- package/src/providers/vellum-model-routing.ts +30 -0
- package/src/providers/voice-error-copy.ts +47 -0
- package/src/providers/weak-open-model.ts +14 -11
- package/src/runtime/agent-wake.ts +40 -24
- package/src/runtime/for-chat-mint-registry.ts +118 -0
- package/src/runtime/reveal-nonce.ts +49 -0
- package/src/runtime/reveal-success-registry.ts +306 -0
- package/src/runtime/routes/__tests__/acp-claude-auth-routes.test.ts +408 -0
- package/src/runtime/routes/__tests__/conversation-query-routes.test.ts +88 -3
- package/src/runtime/routes/__tests__/inference-provider-connection-routes.test.ts +161 -30
- package/src/runtime/routes/__tests__/inference-send-routes.test.ts +97 -0
- package/src/runtime/routes/__tests__/migration-vellum-metadata-reconcile.test.ts +7 -0
- package/src/runtime/routes/__tests__/schedule-worker-routes.test.ts +15 -0
- package/src/runtime/routes/acp-claude-auth-routes.ts +389 -0
- package/src/runtime/routes/app-management-routes.ts +152 -45
- package/src/runtime/routes/app-routes.ts +16 -77
- package/src/runtime/routes/canned-message-complete.ts +16 -16
- package/src/runtime/routes/conversation-management-routes.ts +17 -14
- package/src/runtime/routes/conversation-query-routes.ts +54 -6
- package/src/runtime/routes/conversation-routes.ts +48 -2
- package/src/runtime/routes/credential-prompt-routes.ts +45 -0
- package/src/runtime/routes/credential-routes.ts +134 -3
- package/src/runtime/routes/email-routes.ts +17 -1
- package/src/runtime/routes/errors.ts +2 -2
- package/src/runtime/routes/inbound-stages/transcribe-audio.test.ts +33 -5
- package/src/runtime/routes/inbound-stages/transcribe-audio.ts +6 -5
- package/src/runtime/routes/index.ts +2 -0
- package/src/runtime/routes/inference-provider-connection-routes.ts +77 -28
- package/src/runtime/routes/inference-send-routes.ts +17 -1
- package/src/runtime/routes/internal-telemetry-routes.ts +7 -6
- package/src/runtime/routes/plugins-routes.ts +37 -6
- package/src/runtime/routes/publish-routes.ts +15 -18
- package/src/runtime/routes/schedule-worker-routes.ts +9 -0
- package/src/runtime/routes/secret-routes.ts +29 -0
- package/src/runtime/routes/telemetry-routes.ts +149 -45
- package/src/schedule/__tests__/schedule-timezone.test.ts +101 -0
- package/src/schedule/__tests__/worker-watchdog.test.ts +209 -0
- package/src/schedule/schedule-store.ts +12 -1
- package/src/schedule/schedule-timezone.ts +63 -0
- package/src/schedule/scheduler.ts +100 -6
- package/src/schedule/worker-control.ts +19 -0
- package/src/security/__tests__/oauth2-exchange-body.test.ts +123 -0
- package/src/security/auth-fallback-events-store.ts +7 -6
- package/src/security/oauth2.ts +37 -9
- package/src/security/secret-scanner.ts +26 -1
- package/src/services/published-app-updater.ts +6 -11
- package/src/stt/stt-stream-session.ts +36 -16
- package/src/subagent/manager.ts +34 -8
- package/src/telemetry/AGENTS.md +30 -1
- package/src/telemetry/__tests__/config-setting-snapshot.test.ts +18 -0
- package/src/telemetry/__tests__/outbox-test-harness.ts +5 -3
- package/src/telemetry/config-setting-snapshot.ts +44 -10
- package/src/telemetry/telemetry-event-sources.test.ts +124 -30
- package/src/telemetry/telemetry-event-sources.ts +137 -83
- package/src/telemetry/telemetry-events-outbox.test.ts +46 -1
- package/src/telemetry/telemetry-events-outbox.ts +60 -9
- package/src/telemetry/telemetry-wire-source.json +1 -1
- package/src/telemetry/telemetry-wire-validation.ts +39 -2
- package/src/telemetry/telemetry-wire.generated.ts +8 -0
- package/src/telemetry/tool-audit.ts +15 -9
- package/src/telemetry/tool-executed-events-store.test.ts +1 -1
- package/src/telemetry/turn-events-store.ts +20 -0
- package/src/telemetry/types.ts +45 -12
- package/src/telemetry/usage-telemetry-reporter.test.ts +294 -25
- package/src/telemetry/usage-telemetry-reporter.ts +117 -18
- package/src/telemetry/watchdog-direct-emit.test.ts +11 -3
- package/src/telemetry/watchdog-direct-emit.ts +11 -6
- package/src/tools/acp/spawn.test.ts +17 -0
- package/src/tools/acp/spawn.ts +31 -2
- package/src/tools/apps/executors.ts +11 -36
- package/src/tools/executor.ts +14 -1
- package/src/tools/host-terminal/host-shell.ts +6 -2
- package/src/tools/network/__tests__/web-fetch-firecrawl.test.ts +1 -1
- package/src/tools/network/__tests__/web-fetch-metadata.test.ts +25 -0
- package/src/tools/network/web-fetch.ts +6 -2
- package/src/tools/skills/sandbox-runner.ts +5 -2
- package/src/tools/subagent/spawn.ts +7 -11
- package/src/tools/terminal/shell.ts +5 -2
- package/src/tools/tool-manifest.ts +0 -2
- package/src/tools/tool-profiler.ts +37 -6
- package/src/tools/types.ts +8 -0
- package/src/tools/ui-surface/channel-variants.ts +101 -0
- package/src/tools/ui-surface/definitions.ts +23 -67
- package/src/tools/ui-surface/surface-shape-docs.ts +229 -0
- package/src/tts/__tests__/provider-adapters.test.ts +18 -0
- package/src/tts/provider-catalog.ts +2 -4
- package/src/tts/providers/deepgram-provider.ts +10 -1
- package/src/types/onboarding-context.ts +8 -0
- package/src/usage/attribution.ts +18 -112
- package/src/{config/bundled-skills/messaging/tools/gmail-mime-helpers.ts → util/mime-type.ts} +4 -1
- package/src/util/provider-error-patterns.ts +7 -1
- package/src/util/worker-process.ts +105 -0
- package/src/watcher/__tests__/telemetry.test.ts +17 -4
- package/src/watcher/telemetry.ts +5 -5
- package/src/workspace/migrations/131-drop-web-fetch-mode.ts +61 -0
- package/src/workspace/migrations/registry.ts +2 -0
- package/src/workspace/provider-commit-message-generator.ts +6 -4
- package/src/__tests__/app-open-proxy.test.ts +0 -67
- package/src/onboarding/onboarding-research-events-store.test.ts +0 -230
- package/src/onboarding/onboarding-research-events-store.ts +0 -104
- package/src/runtime/routes/assets/vellum-design-system.css +0 -2236
- package/src/tools/apps/definitions.ts +0 -73
- package/src/tools/apps/open-proxy.ts +0 -43
package/ARCHITECTURE.md
CHANGED
|
@@ -1944,7 +1944,7 @@ Connected channels are resolved at signal emission time: vellum is always includ
|
|
|
1944
1944
|
|
|
1945
1945
|
**Audit trail (SQLite):** `notification_events` → `notification_decisions` (with `conversationActions` in validation results) → `notification_deliveries` (with `conversation_id`, `message_id`, `conversation_strategy`, `conversation_action`, `conversation_target_id`, `conversation_fallback_used`)
|
|
1946
1946
|
|
|
1947
|
-
**Configuration:** `llm.callSites.notificationDecision` (decision engine) and `llm.callSites.preferenceExtraction` (preference extractor) in `config.json`. Both fall back to
|
|
1947
|
+
**Configuration:** `llm.callSites.notificationDecision` (decision engine) and `llm.callSites.preferenceExtraction` (preference extractor) in `config.json`. Both fall back to their shipped call-site defaults when unset.
|
|
1948
1948
|
|
|
1949
1949
|
---
|
|
1950
1950
|
|
package/Dockerfile
CHANGED
|
@@ -129,6 +129,14 @@ COPY packages/twilio-client /app/packages/twilio-client
|
|
|
129
129
|
|
|
130
130
|
COPY assistant /app/assistant
|
|
131
131
|
|
|
132
|
+
# Materialize the git-ignored offline plugin manifest from its canonical source.
|
|
133
|
+
# `assistant/src/cli/lib/plugin-catalog-local.ts` statically imports this JSON
|
|
134
|
+
# (loaded even when platform features are enabled), so it must exist in the image
|
|
135
|
+
# — the build installs with --ignore-scripts, so the postinstall sync that
|
|
136
|
+
# regenerates it in dev never runs here. Mirrors how CI materializes the
|
|
137
|
+
# feature-flag registry before building.
|
|
138
|
+
COPY plugins/marketplace.json /app/assistant/src/cli/lib/bundled-marketplace.json
|
|
139
|
+
|
|
132
140
|
# Install assistant CLI launcher backed by the bundled assistant package
|
|
133
141
|
RUN printf '#!/usr/bin/env sh\nexec bun run /app/assistant/src/index.ts "$@"\n' > /usr/local/bin/assistant && \
|
|
134
142
|
chmod +x /usr/local/bin/assistant
|
|
@@ -36,7 +36,11 @@ Flow, end to end:
|
|
|
36
36
|
`assistant/src/onboarding/onboarding-events-store.ts`:
|
|
37
37
|
- emission is **deterministic, tied to a `ui_show` surface** — there is no
|
|
38
38
|
model-facing tool. The model tags the surface it is already rendering for a
|
|
39
|
-
rail move with an optional `activation_moment` parameter on `ui_show
|
|
39
|
+
rail move with an optional `activation_moment` parameter on `ui_show` (the
|
|
40
|
+
param is present in ui_show's schema only for activation-rail
|
|
41
|
+
conversations — `injectActivationMomentParam` in
|
|
42
|
+
`tools/ui-surface/channel-variants.ts`, applied at tool resolution when
|
|
43
|
+
`isActivationSession` is true); the
|
|
40
44
|
daemon captures that tag on the surface's server-side state and records the
|
|
41
45
|
milestone (gated on `isActivationSession`). **Timing is per-moment**
|
|
42
46
|
(`ACTIVATION_MOMENT_EMIT_AT` in `activation-funnel.ts`): most moments record
|
|
@@ -53,8 +57,9 @@ Flow, end to end:
|
|
|
53
57
|
- emission is best-effort (wrapped in try/catch) and never blocks or alters
|
|
54
58
|
the surface-action flow;
|
|
55
59
|
- `recordActivationEvent` respects the platform `share_analytics` consent gate
|
|
56
|
-
via `
|
|
57
|
-
|
|
60
|
+
via `getRawShareAnalytics()`: only a confirmed opt-out (`false`) drops the
|
|
61
|
+
event; an `"unknown"` state (cold cache, no platform session) records and
|
|
62
|
+
lets the flush/ingest gates enforce consent before anything ships.
|
|
58
63
|
2. **Reporter flushes** every ~5 min: `usage-telemetry-reporter.ts`
|
|
59
64
|
(`REPORT_INTERVAL_MS = 5 * 60 * 1000`, with a one-time
|
|
60
65
|
`INITIAL_FLUSH_DELAY_MS = 30_000` after startup) POSTs queued onboarding
|
|
@@ -180,10 +185,11 @@ the conversation in `activation_sessions` on first build of the system prompt.
|
|
|
180
185
|
Two distinct concerns: whether record-time rows reach SQLite (consent gate), and
|
|
181
186
|
whether those rows reach BigQuery (flush gate, dev-disabled).
|
|
182
187
|
|
|
183
|
-
1. `recordActivationEvent` no-ops
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
188
|
+
1. `recordActivationEvent` no-ops only on a confirmed `share_analytics` opt-out
|
|
189
|
+
(it reads `getRawShareAnalytics()`); an `"unknown"` state records, but the
|
|
190
|
+
flush defers until consent resolves. So for rows to actually SHIP, the dev
|
|
191
|
+
session must be signed in to
|
|
192
|
+
the platform with `share_analytics` consent enabled, or no rows reach
|
|
187
193
|
SQLite. The consent cache is refreshed by `startConsentRefresh()` in
|
|
188
194
|
`assistant/src/daemon/lifecycle.ts`, which runs **regardless of dev mode** — so
|
|
189
195
|
a dev session signed in with `share_analytics` consent enabled writes
|
|
@@ -3,6 +3,7 @@ import { describe, expect, test } from "bun:test";
|
|
|
3
3
|
import {
|
|
4
4
|
normalizeHttpPublicBaseUrl,
|
|
5
5
|
normalizePublicBaseUrl,
|
|
6
|
+
velayHostForPlatformHost,
|
|
6
7
|
} from "../ingress.js";
|
|
7
8
|
import {
|
|
8
9
|
buildTwilioMediaStreamUrl,
|
|
@@ -11,6 +12,31 @@ import {
|
|
|
11
12
|
resolveTwilioPublicBaseUrl,
|
|
12
13
|
} from "../twilio-ingress.js";
|
|
13
14
|
|
|
15
|
+
describe("velayHostForPlatformHost", () => {
|
|
16
|
+
test("maps the prod platform host to prod velay", () => {
|
|
17
|
+
expect(velayHostForPlatformHost("platform.vellum.ai")).toBe(
|
|
18
|
+
"velay.vellum.ai",
|
|
19
|
+
);
|
|
20
|
+
});
|
|
21
|
+
|
|
22
|
+
test("maps env-prefixed platform hosts to their env velay", () => {
|
|
23
|
+
expect(velayHostForPlatformHost("staging-platform.vellum.ai")).toBe(
|
|
24
|
+
"velay-staging.vellum.ai",
|
|
25
|
+
);
|
|
26
|
+
expect(velayHostForPlatformHost("dev-platform.vellum.ai")).toBe(
|
|
27
|
+
"velay-dev.vellum.ai",
|
|
28
|
+
);
|
|
29
|
+
});
|
|
30
|
+
|
|
31
|
+
test("returns null for hosts outside the deployment convention", () => {
|
|
32
|
+
expect(velayHostForPlatformHost("localhost")).toBeNull();
|
|
33
|
+
expect(velayHostForPlatformHost("platform.example.com")).toBeNull();
|
|
34
|
+
expect(
|
|
35
|
+
velayHostForPlatformHost("staging-platform.vellum.ai.evil.com"),
|
|
36
|
+
).toBeNull();
|
|
37
|
+
});
|
|
38
|
+
});
|
|
39
|
+
|
|
14
40
|
describe("normalizePublicBaseUrl", () => {
|
|
15
41
|
test("trims whitespace and trailing slashes", () => {
|
|
16
42
|
expect(normalizePublicBaseUrl(" https://example.test/path/// ")).toBe(
|
|
@@ -0,0 +1,200 @@
|
|
|
1
|
+
import { describe, expect, test } from "bun:test";
|
|
2
|
+
|
|
3
|
+
import {
|
|
4
|
+
buildRedactedSentinel,
|
|
5
|
+
createRedactedSentinelRegex,
|
|
6
|
+
isRevealableSentinel,
|
|
7
|
+
neutralizeRedactedSentinels,
|
|
8
|
+
parseRedactedSentinel,
|
|
9
|
+
REDACTED_SENTINEL_CLOSE,
|
|
10
|
+
REDACTED_SENTINEL_OPEN,
|
|
11
|
+
} from "../redacted-credential";
|
|
12
|
+
|
|
13
|
+
describe("buildRedactedSentinel", () => {
|
|
14
|
+
test("plain shape carries only the type", () => {
|
|
15
|
+
expect(buildRedactedSentinel({ type: "OpenAI Project Key" })).toBe(
|
|
16
|
+
`${REDACTED_SENTINEL_OPEN}redacted:OpenAI Project Key${REDACTED_SENTINEL_CLOSE}`,
|
|
17
|
+
);
|
|
18
|
+
});
|
|
19
|
+
|
|
20
|
+
test("enriched shape carries service and field", () => {
|
|
21
|
+
expect(
|
|
22
|
+
buildRedactedSentinel({
|
|
23
|
+
type: "Anthropic API Key",
|
|
24
|
+
service: "anthropic",
|
|
25
|
+
field: "api_key",
|
|
26
|
+
}),
|
|
27
|
+
).toBe(
|
|
28
|
+
`${REDACTED_SENTINEL_OPEN}redacted:Anthropic API Key:anthropic:api_key${REDACTED_SENTINEL_CLOSE}`,
|
|
29
|
+
);
|
|
30
|
+
});
|
|
31
|
+
|
|
32
|
+
test("colon-qualified segments are percent-encoded, not rejected", () => {
|
|
33
|
+
// Real vaults contain colon-qualified service names (migration
|
|
34
|
+
// 018-rekey-compound-credential-keys produces `integration:google`).
|
|
35
|
+
// The builder must keep those revealable by encoding the delimiter.
|
|
36
|
+
expect(
|
|
37
|
+
buildRedactedSentinel({
|
|
38
|
+
type: "Generic Secret",
|
|
39
|
+
service: "integration:google",
|
|
40
|
+
field: "access_token",
|
|
41
|
+
}),
|
|
42
|
+
).toBe(
|
|
43
|
+
`${REDACTED_SENTINEL_OPEN}redacted:Generic Secret:integration%3Agoogle:access_token${REDACTED_SENTINEL_CLOSE}`,
|
|
44
|
+
);
|
|
45
|
+
});
|
|
46
|
+
|
|
47
|
+
test("plain identifier segments stay literal (unencoded)", () => {
|
|
48
|
+
expect(
|
|
49
|
+
buildRedactedSentinel({
|
|
50
|
+
type: "Anthropic API Key",
|
|
51
|
+
service: "anthropic",
|
|
52
|
+
field: "api-key.v2_test",
|
|
53
|
+
}),
|
|
54
|
+
).toBe(
|
|
55
|
+
`${REDACTED_SENTINEL_OPEN}redacted:Anthropic API Key:anthropic:api-key.v2_test${REDACTED_SENTINEL_CLOSE}`,
|
|
56
|
+
);
|
|
57
|
+
});
|
|
58
|
+
|
|
59
|
+
test("empty service or field degrades to the plain shape", () => {
|
|
60
|
+
expect(
|
|
61
|
+
buildRedactedSentinel({
|
|
62
|
+
type: "Generic Secret",
|
|
63
|
+
service: "",
|
|
64
|
+
field: "x",
|
|
65
|
+
}),
|
|
66
|
+
).toBe(
|
|
67
|
+
`${REDACTED_SENTINEL_OPEN}redacted:Generic Secret${REDACTED_SENTINEL_CLOSE}`,
|
|
68
|
+
);
|
|
69
|
+
expect(
|
|
70
|
+
buildRedactedSentinel({
|
|
71
|
+
type: "Generic Secret",
|
|
72
|
+
service: "x",
|
|
73
|
+
field: "",
|
|
74
|
+
}),
|
|
75
|
+
).toBe(
|
|
76
|
+
`${REDACTED_SENTINEL_OPEN}redacted:Generic Secret${REDACTED_SENTINEL_CLOSE}`,
|
|
77
|
+
);
|
|
78
|
+
});
|
|
79
|
+
|
|
80
|
+
test("missing field alone degrades to the plain shape", () => {
|
|
81
|
+
expect(
|
|
82
|
+
buildRedactedSentinel({ type: "Generic Secret", service: "anthropic" }),
|
|
83
|
+
).toBe(
|
|
84
|
+
`${REDACTED_SENTINEL_OPEN}redacted:Generic Secret${REDACTED_SENTINEL_CLOSE}`,
|
|
85
|
+
);
|
|
86
|
+
});
|
|
87
|
+
|
|
88
|
+
test("throws on a type label that would corrupt the format", () => {
|
|
89
|
+
expect(() => buildRedactedSentinel({ type: "bad:type" })).toThrow();
|
|
90
|
+
expect(() =>
|
|
91
|
+
buildRedactedSentinel({ type: `bad${REDACTED_SENTINEL_CLOSE}type` }),
|
|
92
|
+
).toThrow();
|
|
93
|
+
});
|
|
94
|
+
});
|
|
95
|
+
|
|
96
|
+
describe("parseRedactedSentinel", () => {
|
|
97
|
+
test("round-trips the plain shape", () => {
|
|
98
|
+
const s = buildRedactedSentinel({ type: "GitHub Token" });
|
|
99
|
+
expect(parseRedactedSentinel(s)).toEqual({ type: "GitHub Token" });
|
|
100
|
+
});
|
|
101
|
+
|
|
102
|
+
test("round-trips the enriched shape", () => {
|
|
103
|
+
const s = buildRedactedSentinel({
|
|
104
|
+
type: "GitHub Token",
|
|
105
|
+
service: "github-app",
|
|
106
|
+
field: "pem",
|
|
107
|
+
});
|
|
108
|
+
const parsed = parseRedactedSentinel(s);
|
|
109
|
+
expect(parsed).toEqual({
|
|
110
|
+
type: "GitHub Token",
|
|
111
|
+
service: "github-app",
|
|
112
|
+
field: "pem",
|
|
113
|
+
});
|
|
114
|
+
expect(parsed && isRevealableSentinel(parsed)).toBe(true);
|
|
115
|
+
});
|
|
116
|
+
|
|
117
|
+
test("round-trips encoded segments back to the original identifiers", () => {
|
|
118
|
+
const original = {
|
|
119
|
+
type: "OAuth Access Token",
|
|
120
|
+
service: "integration:google",
|
|
121
|
+
field: "access token/v2 (staging)",
|
|
122
|
+
};
|
|
123
|
+
const parsed = parseRedactedSentinel(buildRedactedSentinel(original));
|
|
124
|
+
expect(parsed).toEqual(original);
|
|
125
|
+
expect(parsed && isRevealableSentinel(parsed)).toBe(true);
|
|
126
|
+
});
|
|
127
|
+
|
|
128
|
+
test("malformed percent-escape degrades to the plain shape", () => {
|
|
129
|
+
// Daemon-encoded segments always decode; a hand-forged sentinel with a
|
|
130
|
+
// broken escape must not surface bogus vault coordinates.
|
|
131
|
+
const forged = `${REDACTED_SENTINEL_OPEN}redacted:Generic Secret:bad%zzsvc:field${REDACTED_SENTINEL_CLOSE}`;
|
|
132
|
+
const parsed = parseRedactedSentinel(forged);
|
|
133
|
+
expect(parsed).toEqual({ type: "Generic Secret" });
|
|
134
|
+
expect(parsed && isRevealableSentinel(parsed)).toBe(false);
|
|
135
|
+
});
|
|
136
|
+
|
|
137
|
+
test("rejects non-sentinel and partial inputs", () => {
|
|
138
|
+
expect(parseRedactedSentinel("nope")).toBeUndefined();
|
|
139
|
+
expect(
|
|
140
|
+
parseRedactedSentinel(`${REDACTED_SENTINEL_OPEN}redacted:Type`),
|
|
141
|
+
).toBeUndefined();
|
|
142
|
+
expect(
|
|
143
|
+
parseRedactedSentinel(
|
|
144
|
+
`prefix ${buildRedactedSentinel({ type: "T" })} suffix`,
|
|
145
|
+
),
|
|
146
|
+
).toBeUndefined();
|
|
147
|
+
});
|
|
148
|
+
|
|
149
|
+
test("plain shape is not revealable", () => {
|
|
150
|
+
const parsed = parseRedactedSentinel(
|
|
151
|
+
buildRedactedSentinel({ type: "AWS Access Key" }),
|
|
152
|
+
);
|
|
153
|
+
expect(parsed && isRevealableSentinel(parsed)).toBe(false);
|
|
154
|
+
});
|
|
155
|
+
});
|
|
156
|
+
|
|
157
|
+
describe("neutralizeRedactedSentinels", () => {
|
|
158
|
+
test("a forged sentinel no longer matches the consumer regex", () => {
|
|
159
|
+
const forged = buildRedactedSentinel({
|
|
160
|
+
type: "GitHub Token",
|
|
161
|
+
service: "github-app",
|
|
162
|
+
field: "pem",
|
|
163
|
+
});
|
|
164
|
+
const neutralized = neutralizeRedactedSentinels(`quote: ${forged}`);
|
|
165
|
+
expect([...neutralized.matchAll(createRedactedSentinelRegex())]).toEqual(
|
|
166
|
+
[],
|
|
167
|
+
);
|
|
168
|
+
// Visually identical: only a zero-width word joiner was inserted.
|
|
169
|
+
expect(neutralized.replaceAll("\u2060", "")).toBe(`quote: ${forged}`);
|
|
170
|
+
});
|
|
171
|
+
|
|
172
|
+
test("is idempotent", () => {
|
|
173
|
+
const once = neutralizeRedactedSentinels(
|
|
174
|
+
buildRedactedSentinel({ type: "A" }),
|
|
175
|
+
);
|
|
176
|
+
expect(neutralizeRedactedSentinels(once)).toBe(once);
|
|
177
|
+
});
|
|
178
|
+
|
|
179
|
+
test("leaves ordinary text untouched", () => {
|
|
180
|
+
expect(neutralizeRedactedSentinels("plain text")).toBe("plain text");
|
|
181
|
+
expect(
|
|
182
|
+
neutralizeRedactedSentinels(`${REDACTED_SENTINEL_OPEN}not a sentinel`),
|
|
183
|
+
).toBe(`${REDACTED_SENTINEL_OPEN}not a sentinel`);
|
|
184
|
+
});
|
|
185
|
+
});
|
|
186
|
+
|
|
187
|
+
describe("createRedactedSentinelRegex", () => {
|
|
188
|
+
test("finds multiple sentinels of both shapes in one text", () => {
|
|
189
|
+
const text = `a ${buildRedactedSentinel({ type: "A Key" })} b ${buildRedactedSentinel(
|
|
190
|
+
{ type: "B Key", service: "svc", field: "f" },
|
|
191
|
+
)} c`;
|
|
192
|
+
const matches = [...text.matchAll(createRedactedSentinelRegex())];
|
|
193
|
+
expect(matches).toHaveLength(2);
|
|
194
|
+
expect(matches[0][1]).toBe("A Key");
|
|
195
|
+
expect(matches[0][2]).toBeUndefined();
|
|
196
|
+
expect(matches[1][1]).toBe("B Key");
|
|
197
|
+
expect(matches[1][2]).toBe("svc");
|
|
198
|
+
expect(matches[1][3]).toBe("f");
|
|
199
|
+
});
|
|
200
|
+
});
|
package/node_modules/@vellumai/ces-client/node_modules/@vellumai/service-contracts/src/ingress.ts
CHANGED
|
@@ -1,3 +1,19 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Map a Vellum platform host onto its environment's velay host, following the
|
|
3
|
+
* deployment naming convention (`platform.vellum.ai` → `velay.vellum.ai`,
|
|
4
|
+
* `{env}-platform.vellum.ai` → `velay-{env}.vellum.ai`). Returns null for
|
|
5
|
+
* hosts outside that convention (localhost, custom domains). Shared by the
|
|
6
|
+
* gateway speech relay and the web live-voice client so the two ends of the
|
|
7
|
+
* managed-speech transport can't drift.
|
|
8
|
+
*/
|
|
9
|
+
export function velayHostForPlatformHost(host: string): string | null {
|
|
10
|
+
if (host === "platform.vellum.ai") {
|
|
11
|
+
return "velay.vellum.ai";
|
|
12
|
+
}
|
|
13
|
+
const match = /^([a-z0-9-]+)-platform\.vellum\.ai$/.exec(host);
|
|
14
|
+
return match ? `velay-${match[1]}.vellum.ai` : null;
|
|
15
|
+
}
|
|
16
|
+
|
|
1
17
|
export function normalizePublicBaseUrl(value: unknown): string | undefined {
|
|
2
18
|
if (typeof value !== "string") return undefined;
|
|
3
19
|
const normalized = value.trim().replace(/\/+$/, "");
|
|
@@ -0,0 +1,226 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Redacted-credential sentinel — the shared wire format for chat-persisted
|
|
3
|
+
* secret redactions.
|
|
4
|
+
*
|
|
5
|
+
* When the daemon persists a chat message containing a detected secret (and
|
|
6
|
+
* the `chat-credential-reveal` feature flag is on), the secret is replaced
|
|
7
|
+
* with a plain-text sentinel instead of the legacy `<redacted type="…" />`
|
|
8
|
+
* HTML marker. The sentinel is plain text on purpose: the chat markdown
|
|
9
|
+
* pipeline has no rehype-raw, so an HTML marker can never become an element —
|
|
10
|
+
* a text sentinel survives markdown parsing as an ordinary text node that a
|
|
11
|
+
* rehype plugin can match and upgrade into an interactive chip.
|
|
12
|
+
*
|
|
13
|
+
* Two shapes:
|
|
14
|
+
* `〔redacted:TYPE〕` — secret detected, vault identity unknown
|
|
15
|
+
* `〔redacted:TYPE:SERVICE:FIELD〕` — secret byte-matched to a stored credential
|
|
16
|
+
*
|
|
17
|
+
* The enriched shape is only emitted when the daemon has *proven* the match
|
|
18
|
+
* (exact plaintext comparison against a credential fetched for this turn) —
|
|
19
|
+
* never inferred. A wrong SERVICE:FIELD here would let the client reveal the
|
|
20
|
+
* wrong secret, so producers must treat enrichment as opt-in per proven match.
|
|
21
|
+
*
|
|
22
|
+
* The corner brackets (U+3014/U+3015) were chosen because they are not
|
|
23
|
+
* markdown syntax characters, never appear in the secret formats we detect,
|
|
24
|
+
* and are visually distinct if a client without chip support renders the raw
|
|
25
|
+
* sentinel.
|
|
26
|
+
*
|
|
27
|
+
* This module is shared by the daemon (producer) and clients/web (consumer)
|
|
28
|
+
* so the format can never drift between the two. It is intentionally
|
|
29
|
+
* dependency-free and data-only.
|
|
30
|
+
*/
|
|
31
|
+
|
|
32
|
+
export const REDACTED_SENTINEL_OPEN = "\u3014"; // 〔
|
|
33
|
+
export const REDACTED_SENTINEL_CLOSE = "\u3015"; // 〕
|
|
34
|
+
export const REDACTED_SENTINEL_TAG = "redacted";
|
|
35
|
+
|
|
36
|
+
/**
|
|
37
|
+
* Version stamped as a `_redactionVersion` rider on daemon-persisted text
|
|
38
|
+
* blocks whose raw text passed through {@link neutralizeRedactedSentinels}
|
|
39
|
+
* before persistence. The daemon's history read boundary
|
|
40
|
+
* (`renderHistoryContent`) neutralizes text blocks WITHOUT this rider —
|
|
41
|
+
* rows persisted before the forgery guard existed — so a forged sentinel in
|
|
42
|
+
* old history can never reach a chip-enabled client surface. Internal to the
|
|
43
|
+
* daemon: the wire projection rebuilds blocks and never ships the rider.
|
|
44
|
+
*/
|
|
45
|
+
export const SENTINEL_REDACTION_VERSION = 2;
|
|
46
|
+
|
|
47
|
+
/**
|
|
48
|
+
* Service/field segments are percent-encoded into the sentinel so arbitrary
|
|
49
|
+
* credential identifiers survive the colon-delimited format. The credential
|
|
50
|
+
* routes accept service/field as arbitrary strings — colon-qualified names
|
|
51
|
+
* exist in real vaults (migration `018-rekey-compound-credential-keys`
|
|
52
|
+
* produces `service = "integration:google"`), so a delimiter-collision
|
|
53
|
+
* charset restriction would silently downgrade proven reveals for those
|
|
54
|
+
* credentials to the non-revealable shape. Encoding keeps the common
|
|
55
|
+
* kebab/snake identifiers fully legible while making every other string
|
|
56
|
+
* representable.
|
|
57
|
+
*
|
|
58
|
+
* The encoded form contains only `[A-Za-z0-9_.%-]` — `encodeURIComponent`
|
|
59
|
+
* plus the characters it leaves bare that fall outside that set — so the
|
|
60
|
+
* sentinel regex stays unambiguous around its `:` delimiters and `〕` close.
|
|
61
|
+
*/
|
|
62
|
+
function encodeSentinelSegment(segment: string): string {
|
|
63
|
+
return encodeURIComponent(segment).replace(
|
|
64
|
+
/[!'()*~]/g,
|
|
65
|
+
(c) => `%${c.charCodeAt(0).toString(16).toUpperCase()}`,
|
|
66
|
+
);
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
/**
|
|
70
|
+
* Inverse of {@link encodeSentinelSegment}. Returns undefined for malformed
|
|
71
|
+
* percent-escapes — daemon-encoded segments always decode, and forged
|
|
72
|
+
* sentinels are neutralized at persist time, so this path is defensive.
|
|
73
|
+
*/
|
|
74
|
+
function decodeSentinelSegment(segment: string): string | undefined {
|
|
75
|
+
try {
|
|
76
|
+
return decodeURIComponent(segment);
|
|
77
|
+
} catch {
|
|
78
|
+
return undefined;
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
/**
|
|
83
|
+
* Charset for the TYPE label. Pattern labels are our own
|
|
84
|
+
* (`secret-patterns.ts` / scanner-only patterns) and contain only words,
|
|
85
|
+
* spaces, and hyphens — but validate anyway so a future label containing a
|
|
86
|
+
* colon or bracket cannot silently corrupt the format.
|
|
87
|
+
*/
|
|
88
|
+
const TYPE_RE = /^[^:\u3014\u3015]+$/;
|
|
89
|
+
|
|
90
|
+
export interface RedactedCredentialSentinel {
|
|
91
|
+
/** Human-readable secret type label, e.g. "Anthropic API Key". */
|
|
92
|
+
type: string;
|
|
93
|
+
/** Vault service namespace — present only when the match was proven. */
|
|
94
|
+
service?: string;
|
|
95
|
+
/** Vault field name — present only when the match was proven. */
|
|
96
|
+
field?: string;
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
/** True when the sentinel carries a proven vault identity. */
|
|
100
|
+
export function isRevealableSentinel(
|
|
101
|
+
s: RedactedCredentialSentinel,
|
|
102
|
+
): s is RedactedCredentialSentinel & { service: string; field: string } {
|
|
103
|
+
return s.service !== undefined && s.field !== undefined;
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
/**
|
|
107
|
+
* Build a sentinel string. Service/field are percent-encoded (see
|
|
108
|
+
* {@link encodeSentinelSegment}), so any non-empty identifier the credential
|
|
109
|
+
* routes accept — including colon-qualified names like `integration:google`
|
|
110
|
+
* — round-trips through the enriched shape. Empty segments fall back to the
|
|
111
|
+
* plain (non-revealable) shape; the function throws only if the type label
|
|
112
|
+
* itself is unusable (which would mean a broken pattern table).
|
|
113
|
+
*/
|
|
114
|
+
export function buildRedactedSentinel(
|
|
115
|
+
sentinel: RedactedCredentialSentinel,
|
|
116
|
+
): string {
|
|
117
|
+
const { type, service, field } = sentinel;
|
|
118
|
+
if (!TYPE_RE.test(type)) {
|
|
119
|
+
throw new Error(`Invalid redacted-sentinel type label: ${type}`);
|
|
120
|
+
}
|
|
121
|
+
const base = `${REDACTED_SENTINEL_OPEN}${REDACTED_SENTINEL_TAG}:${type}`;
|
|
122
|
+
if (
|
|
123
|
+
service !== undefined &&
|
|
124
|
+
field !== undefined &&
|
|
125
|
+
service !== "" &&
|
|
126
|
+
field !== ""
|
|
127
|
+
) {
|
|
128
|
+
const encService = encodeSentinelSegment(service);
|
|
129
|
+
const encField = encodeSentinelSegment(field);
|
|
130
|
+
return `${base}:${encService}:${encField}${REDACTED_SENTINEL_CLOSE}`;
|
|
131
|
+
}
|
|
132
|
+
return `${base}${REDACTED_SENTINEL_CLOSE}`;
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
/**
|
|
136
|
+
* Global-flag regex for locating sentinels inside a larger text. Callers must
|
|
137
|
+
* not share a single instance across concurrent iterations (stateful
|
|
138
|
+
* `lastIndex`); use {@link createRedactedSentinelRegex}.
|
|
139
|
+
*/
|
|
140
|
+
export function createRedactedSentinelRegex(): RegExp {
|
|
141
|
+
// 〔redacted:TYPE〕 or 〔redacted:TYPE:SERVICE:FIELD〕
|
|
142
|
+
// TYPE = anything except colon/brackets; SERVICE/FIELD = the
|
|
143
|
+
// percent-encoded segment charset (see encodeSentinelSegment). Capture
|
|
144
|
+
// groups carry the ENCODED segments — decode via decodeRedactedSentinelMatch.
|
|
145
|
+
return new RegExp(
|
|
146
|
+
`${REDACTED_SENTINEL_OPEN}${REDACTED_SENTINEL_TAG}:([^:\u3014\u3015]+?)(?::([A-Za-z0-9_.%-]+):([A-Za-z0-9_.%-]+))?${REDACTED_SENTINEL_CLOSE}`,
|
|
147
|
+
"g",
|
|
148
|
+
);
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
/**
|
|
152
|
+
* Global-flag regex for locating NEUTRALIZED sentinels — the word-joiner
|
|
153
|
+
* form {@link neutralizeRedactedSentinels} produces — inside a larger text.
|
|
154
|
+
* Renderers use this to show a defused redaction marker as an inert badge
|
|
155
|
+
* instead of leaking bare sentinel glyphs into the transcript. The capture
|
|
156
|
+
* groups mirror {@link createRedactedSentinelRegex}, but consumers must NOT
|
|
157
|
+
* trust them as vault coordinates: a neutralized span is by definition text
|
|
158
|
+
* the daemon refused to vouch for. Same statefulness caveat as the genuine
|
|
159
|
+
* regex — create a fresh instance per iteration.
|
|
160
|
+
*/
|
|
161
|
+
export function createNeutralizedSentinelRegex(): RegExp {
|
|
162
|
+
return new RegExp(
|
|
163
|
+
`${REDACTED_SENTINEL_OPEN}\\u2060${REDACTED_SENTINEL_TAG}:([^:\\u3014\\u3015]+?)(?::([A-Za-z0-9_.%-]+):([A-Za-z0-9_.%-]+))?${REDACTED_SENTINEL_CLOSE}`,
|
|
164
|
+
"g",
|
|
165
|
+
);
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
/**
|
|
169
|
+
* Map a {@link createRedactedSentinelRegex} match to a decoded sentinel.
|
|
170
|
+
* Shared by {@link parseRedactedSentinel} and the web rehype plugin so the
|
|
171
|
+
* segment decoding never drifts between consumers. A malformed
|
|
172
|
+
* percent-escape (unreachable for daemon-minted sentinels; forged ones are
|
|
173
|
+
* neutralized at persist) degrades to the plain non-revealable shape rather
|
|
174
|
+
* than surfacing bogus vault coordinates.
|
|
175
|
+
*/
|
|
176
|
+
export function decodeRedactedSentinelMatch(
|
|
177
|
+
m: RegExpExecArray,
|
|
178
|
+
): RedactedCredentialSentinel {
|
|
179
|
+
const [, type, encService, encField] = m;
|
|
180
|
+
if (encService !== undefined && encField !== undefined) {
|
|
181
|
+
const service = decodeSentinelSegment(encService);
|
|
182
|
+
const field = decodeSentinelSegment(encField);
|
|
183
|
+
if (service !== undefined && field !== undefined) {
|
|
184
|
+
return { type, service, field };
|
|
185
|
+
}
|
|
186
|
+
}
|
|
187
|
+
return { type };
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
/**
|
|
191
|
+
* Neutralize sentinel-shaped strings that were NOT produced by the redactor.
|
|
192
|
+
*
|
|
193
|
+
* The sentinel is plain text, so any content source (model output quoting a
|
|
194
|
+
* transcript, fetched web content, a pasted message) could otherwise forge
|
|
195
|
+
* one and manufacture a reveal chip for an arbitrary stored credential.
|
|
196
|
+
* Producers must call this on raw text *before* inserting their own
|
|
197
|
+
* sentinels, so the only sentinels that survive persistence are the ones the
|
|
198
|
+
* redactor itself emitted from a detected secret.
|
|
199
|
+
*
|
|
200
|
+
* Neutralization inserts a word joiner (U+2060, zero-width, non-breaking)
|
|
201
|
+
* after the opening bracket — visually identical for a human reading a
|
|
202
|
+
* quoted sentinel, but the consumer regex no longer matches. Idempotent:
|
|
203
|
+
* after one pass the trigger prefix no longer occurs, so re-running is a
|
|
204
|
+
* no-op and never corrupts text further.
|
|
205
|
+
*/
|
|
206
|
+
export function neutralizeRedactedSentinels(text: string): string {
|
|
207
|
+
const trigger = `${REDACTED_SENTINEL_OPEN}${REDACTED_SENTINEL_TAG}:`;
|
|
208
|
+
if (!text.includes(trigger)) return text;
|
|
209
|
+
return text.replaceAll(
|
|
210
|
+
trigger,
|
|
211
|
+
`${REDACTED_SENTINEL_OPEN}\u2060${REDACTED_SENTINEL_TAG}:`,
|
|
212
|
+
);
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
/**
|
|
216
|
+
* Parse a single exact sentinel string (the whole input must be one
|
|
217
|
+
* sentinel). Returns undefined when the input is not a valid sentinel.
|
|
218
|
+
*/
|
|
219
|
+
export function parseRedactedSentinel(
|
|
220
|
+
text: string,
|
|
221
|
+
): RedactedCredentialSentinel | undefined {
|
|
222
|
+
const re = createRedactedSentinelRegex();
|
|
223
|
+
const m = re.exec(text);
|
|
224
|
+
if (!m || m[0] !== text) return undefined;
|
|
225
|
+
return decodeRedactedSentinelMatch(m);
|
|
226
|
+
}
|
|
@@ -3,6 +3,7 @@ import { describe, expect, test } from "bun:test";
|
|
|
3
3
|
import {
|
|
4
4
|
normalizeHttpPublicBaseUrl,
|
|
5
5
|
normalizePublicBaseUrl,
|
|
6
|
+
velayHostForPlatformHost,
|
|
6
7
|
} from "../ingress.js";
|
|
7
8
|
import {
|
|
8
9
|
buildTwilioMediaStreamUrl,
|
|
@@ -11,6 +12,31 @@ import {
|
|
|
11
12
|
resolveTwilioPublicBaseUrl,
|
|
12
13
|
} from "../twilio-ingress.js";
|
|
13
14
|
|
|
15
|
+
describe("velayHostForPlatformHost", () => {
|
|
16
|
+
test("maps the prod platform host to prod velay", () => {
|
|
17
|
+
expect(velayHostForPlatformHost("platform.vellum.ai")).toBe(
|
|
18
|
+
"velay.vellum.ai",
|
|
19
|
+
);
|
|
20
|
+
});
|
|
21
|
+
|
|
22
|
+
test("maps env-prefixed platform hosts to their env velay", () => {
|
|
23
|
+
expect(velayHostForPlatformHost("staging-platform.vellum.ai")).toBe(
|
|
24
|
+
"velay-staging.vellum.ai",
|
|
25
|
+
);
|
|
26
|
+
expect(velayHostForPlatformHost("dev-platform.vellum.ai")).toBe(
|
|
27
|
+
"velay-dev.vellum.ai",
|
|
28
|
+
);
|
|
29
|
+
});
|
|
30
|
+
|
|
31
|
+
test("returns null for hosts outside the deployment convention", () => {
|
|
32
|
+
expect(velayHostForPlatformHost("localhost")).toBeNull();
|
|
33
|
+
expect(velayHostForPlatformHost("platform.example.com")).toBeNull();
|
|
34
|
+
expect(
|
|
35
|
+
velayHostForPlatformHost("staging-platform.vellum.ai.evil.com"),
|
|
36
|
+
).toBeNull();
|
|
37
|
+
});
|
|
38
|
+
});
|
|
39
|
+
|
|
14
40
|
describe("normalizePublicBaseUrl", () => {
|
|
15
41
|
test("trims whitespace and trailing slashes", () => {
|
|
16
42
|
expect(normalizePublicBaseUrl(" https://example.test/path/// ")).toBe(
|