@vellumai/assistant 0.10.9 → 0.10.10-dev.202607162206.d08e98e
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__/redacted-credential.test.ts +200 -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__/redacted-credential.test.ts +200 -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__/redacted-credential.test.ts +200 -0
- package/node_modules/@vellumai/service-contracts/src/redacted-credential.ts +226 -0
- package/openapi.yaml +530 -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-routes.test.ts +250 -0
- package/src/__tests__/credential-security-invariants.test.ts +3 -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__/server-history-render.test.ts +28 -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__/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__/workspace-migration-131-drop-web-fetch-mode.test.ts +120 -0
- package/src/agent/loop.ts +4 -3
- package/src/api/events/open-conversation.test.ts +64 -0
- package/src/api/events/open-conversation.ts +33 -0
- package/src/api/index.ts +6 -0
- package/src/api/responses/conversation-message.ts +5 -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__/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 +6 -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/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/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/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 +627 -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 +33 -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/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/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 +37 -0
- package/src/providers/openai/__tests__/api-error-normalization.test.ts +24 -2
- package/src/providers/openai/api-key-validation.ts +70 -0
- package/src/providers/retry.ts +2 -0
- package/src/providers/types.ts +11 -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/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__/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__/migration-vellum-metadata-reconcile.test.ts +7 -0
- package/src/runtime/routes/__tests__/schedule-worker-routes.test.ts +15 -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-routes.ts +116 -3
- package/src/runtime/routes/email-routes.ts +17 -1
- 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/inference-provider-connection-routes.ts +77 -28
- package/src/runtime/routes/inference-send-routes.ts +1 -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 +10 -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/auth-fallback-events-store.ts +7 -6
- 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/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/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
|
@@ -1,3 +1,7 @@
|
|
|
1
|
+
import {
|
|
2
|
+
neutralizeRedactedSentinels,
|
|
3
|
+
SENTINEL_REDACTION_VERSION,
|
|
4
|
+
} from "@vellumai/service-contracts/redacted-credential";
|
|
1
5
|
import { v4 as uuid } from "uuid";
|
|
2
6
|
|
|
3
7
|
import type {
|
|
@@ -11,6 +15,7 @@ import { getConfig } from "../../config/loader.js";
|
|
|
11
15
|
import type { LLMCallSite, Speed } from "../../config/schemas/llm.js";
|
|
12
16
|
import { ipcCall as gatewayIpcCall } from "../../ipc/gateway-client.js";
|
|
13
17
|
import type { SecretPromptResult } from "../../permissions/secret-prompt-types.js";
|
|
18
|
+
import type { ConversationCreateType } from "../../persistence/conversation-types.js";
|
|
14
19
|
import { resolveMediaSourceData } from "../../providers/media-resolve.js";
|
|
15
20
|
import { isPlaceholderSentinelText } from "../../providers/placeholder-sentinels.js";
|
|
16
21
|
import type { MediaSource } from "../../providers/types.js";
|
|
@@ -180,6 +185,13 @@ export interface ConversationCreateOptions {
|
|
|
180
185
|
* chronological renderer to consume.
|
|
181
186
|
*/
|
|
182
187
|
slackInbound?: SlackInboundMessageMetadata;
|
|
188
|
+
/**
|
|
189
|
+
* Conversation type for newly created conversations. When omitted,
|
|
190
|
+
* defaults to `"standard"` (visible in the sidebar). Set to
|
|
191
|
+
* `"background"` for plugin-driven conversations that should not
|
|
192
|
+
* appear in the sidebar's Recents grouping.
|
|
193
|
+
*/
|
|
194
|
+
conversationType?: ConversationCreateType;
|
|
183
195
|
}
|
|
184
196
|
|
|
185
197
|
function isRecord(value: unknown): value is Record<string, unknown> {
|
|
@@ -315,6 +327,10 @@ export function renderHistoryContent(
|
|
|
315
327
|
} else {
|
|
316
328
|
text = unwrapExternalContentForDisplay(String(content));
|
|
317
329
|
}
|
|
330
|
+
// Raw-string rows predate the block-shaped persist path entirely, so
|
|
331
|
+
// they can never carry the `_redactionVersion` rider — always run the
|
|
332
|
+
// sentinel forgery guard (see the rider check in the block walk below).
|
|
333
|
+
text = neutralizeRedactedSentinels(text);
|
|
318
334
|
return {
|
|
319
335
|
text,
|
|
320
336
|
toolCalls: [],
|
|
@@ -445,7 +461,22 @@ export function renderHistoryContent(
|
|
|
445
461
|
}
|
|
446
462
|
|
|
447
463
|
if (block.type === "text" && typeof block.text === "string") {
|
|
448
|
-
|
|
464
|
+
// Forgery guard for pre-feature history: blocks persisted before the
|
|
465
|
+
// chat-credential sentinel work never had sentinel-shaped strings
|
|
466
|
+
// neutralized at write, so a forged `〔redacted:…〕` in an old row could
|
|
467
|
+
// manufacture a reveal chip on a chip-enabled client surface. Blocks
|
|
468
|
+
// written by the neutralization-aware persist path carry the
|
|
469
|
+
// `_redactionVersion` rider (internal, never shipped on the wire) and
|
|
470
|
+
// pass through verbatim — their surviving sentinels are
|
|
471
|
+
// redactor-authored. Everything else is neutralized here at the read
|
|
472
|
+
// boundary, the same belt-and-suspenders posture as the
|
|
473
|
+
// `<no_response/>` and placeholder-sentinel strips below.
|
|
474
|
+
const rawText =
|
|
475
|
+
typeof block._redactionVersion === "number" &&
|
|
476
|
+
block._redactionVersion >= SENTINEL_REDACTION_VERSION
|
|
477
|
+
? block.text
|
|
478
|
+
: neutralizeRedactedSentinels(block.text);
|
|
479
|
+
const displayText = unwrapExternalContentForDisplay(rawText);
|
|
449
480
|
// Skip empty/whitespace-only text blocks. During streaming the client
|
|
450
481
|
// discards empty text deltas (guard !text.isEmpty), so including them
|
|
451
482
|
// here produces a contentOrder that differs from the live streaming
|
|
@@ -673,7 +704,7 @@ export function renderHistoryContent(
|
|
|
673
704
|
continue;
|
|
674
705
|
}
|
|
675
706
|
const resolved = resolveMediaSourceData(source);
|
|
676
|
-
if (resolved) imageDataList.push(resolved.data);
|
|
707
|
+
if (resolved) {imageDataList.push(resolved.data);}
|
|
677
708
|
}
|
|
678
709
|
}
|
|
679
710
|
}
|
package/src/daemon/lifecycle.ts
CHANGED
|
@@ -156,8 +156,8 @@ export async function runDaemon(): Promise<void> {
|
|
|
156
156
|
// so a fast-reconnecting client could otherwise resolve a turn against a
|
|
157
157
|
// partial profile in the window between readiness and the post-overlay
|
|
158
158
|
// ensure call below. Sync, DB-free, and idempotent. Skipped when an
|
|
159
|
-
// unconsumed onboarding overlay is pending: the overlay can rewrite
|
|
160
|
-
// llm
|
|
159
|
+
// unconsumed onboarding overlay is pending: the overlay can rewrite the
|
|
160
|
+
// llm config later this boot, and baking against the pre-overlay default
|
|
161
161
|
// would pin the wrong baseline — on that single boot (a fresh hatch, with
|
|
162
162
|
// no established clients to race the window) the post-overlay pass owns
|
|
163
163
|
// materialization; the overlay file is consumed on merge, so every
|
|
@@ -235,7 +235,7 @@ export async function runDaemon(): Promise<void> {
|
|
|
235
235
|
} else {
|
|
236
236
|
setDbMigrationFailed();
|
|
237
237
|
log.error(
|
|
238
|
-
"Daemon startup: DB opened but one or more migrations failed — /readyz will remain unready",
|
|
238
|
+
"Daemon startup: DB opened but one or more migrations failed or were deferred — /readyz will remain unready",
|
|
239
239
|
);
|
|
240
240
|
}
|
|
241
241
|
// Migrations have settled (successfully or in the failed degraded mode),
|
|
@@ -553,7 +553,7 @@ export async function runDaemon(): Promise<void> {
|
|
|
553
553
|
}
|
|
554
554
|
|
|
555
555
|
// Refresh the consent cache regardless of dev mode so record-time telemetry
|
|
556
|
-
// writes (
|
|
556
|
+
// writes (which drop on a confirmed opt-out) work in dev too. The usage
|
|
557
557
|
// telemetry reporter re-checks share_analytics on every flush, so dev still
|
|
558
558
|
// never sends telemetry to the platform. Fire-and-forget: startConsentRefresh()
|
|
559
559
|
// runs an immediate non-blocking refresh, so the startup hot path is never
|
|
@@ -8,6 +8,7 @@ import type { ConversationNoticeEvent } from "../../api/events/conversation-noti
|
|
|
8
8
|
import type { ConversationTitleUpdatedEvent } from "../../api/events/conversation-title-updated.js";
|
|
9
9
|
import type { GenerationCancelledEvent } from "../../api/events/generation-cancelled.js";
|
|
10
10
|
import type { GenerationHandoffEvent } from "../../api/events/generation-handoff.js";
|
|
11
|
+
import type { OpenConversationEvent } from "../../api/events/open-conversation.js";
|
|
11
12
|
import type { UsageProgressEvent } from "../../api/events/usage-progress.js";
|
|
12
13
|
import type { UsageUpdateEvent } from "../../api/events/usage-update.js";
|
|
13
14
|
import type {
|
|
@@ -505,22 +506,10 @@ export interface ScheduleConversationCreated {
|
|
|
505
506
|
title: string;
|
|
506
507
|
}
|
|
507
508
|
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
* a sidebar entry using the provided `title` before navigating.
|
|
513
|
-
*/
|
|
514
|
-
export interface OpenConversation {
|
|
515
|
-
type: "open_conversation";
|
|
516
|
-
conversationId: string;
|
|
517
|
-
/** Optional conversation title; supplied when the client may not yet have the conversation in its list. */
|
|
518
|
-
title?: string;
|
|
519
|
-
/** Optional message ID to scroll to after focus. */
|
|
520
|
-
anchorMessageId?: string;
|
|
521
|
-
/** When `false`, the client should register the conversation in its sidebar (so it's visible and navigable) but must NOT switch focus to it. Omitting the field defaults to `true` for backward compatibility with existing single-target 'jump to conversation' callers. */
|
|
522
|
-
focus?: boolean;
|
|
523
|
-
}
|
|
509
|
+
// `open_conversation` is a migrated event: its canonical wire contract lives
|
|
510
|
+
// in `../../api/events/open-conversation.ts` (imported as
|
|
511
|
+
// `OpenConversationEvent`). Instructs the client to open and, by default,
|
|
512
|
+
// focus a conversation — see that file for the full field docs.
|
|
524
513
|
|
|
525
514
|
// --- Domain-level union aliases (consumed by the barrel file) ---
|
|
526
515
|
|
|
@@ -565,4 +554,4 @@ export type _ConversationsServerMessages =
|
|
|
565
554
|
| MessageContentResponse
|
|
566
555
|
| ConversationListInvalidatedEvent
|
|
567
556
|
| ScheduleConversationCreated
|
|
568
|
-
|
|
|
557
|
+
| OpenConversationEvent;
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { maybeDefaultSpeechToManaged } from "../config/managed-speech-defaults.js";
|
|
1
2
|
import { rehydratePlatformCredentials } from "../config/platform-rehydration.js";
|
|
2
3
|
import type { AssistantConfig } from "../config/types.js";
|
|
3
4
|
import { getMcpServerManager } from "../mcp/manager.js";
|
|
@@ -32,6 +33,13 @@ export async function initializeProvidersAndTools(
|
|
|
32
33
|
// overrides are normally only set by the secret-routes handlers at runtime.
|
|
33
34
|
await rehydratePlatformCredentials();
|
|
34
35
|
|
|
36
|
+
// Speech defaulting normally fires when the platform credentials land
|
|
37
|
+
// (secret-routes); evaluating it once per boot also covers assistants whose
|
|
38
|
+
// connection predates that trigger, so voice works for every connected
|
|
39
|
+
// assistant with no configured speech credentials. Idempotent, and detached
|
|
40
|
+
// per the startup philosophy — must not block boot.
|
|
41
|
+
void maybeDefaultSpeechToManaged();
|
|
42
|
+
|
|
35
43
|
await initializeProviders(config);
|
|
36
44
|
// initializeTools() also loads workspace tool overrides from
|
|
37
45
|
// `<workspaceDir>/tools/` once core tools have settled, so they own
|
|
@@ -33,7 +33,7 @@ export type SubagentToolGateMode = "wire" | "execution";
|
|
|
33
33
|
* but the definitions themselves are resolved from the live context: a
|
|
34
34
|
* fork-retrospective wake hydrates clientless (`hasNoClient = true`, no
|
|
35
35
|
* transport interface, no channel capabilities), which drops client-gated
|
|
36
|
-
* tools (`host_*`, `ui_*`, `
|
|
36
|
+
* tools (`host_*`, `ui_*`, `ask_question`, `request_system_permission`) from
|
|
37
37
|
* the wire definitions and breaks the cache prefix anyway. When this pin is
|
|
38
38
|
* set on the conversation, `isToolActiveForContext` reads `hasNoClient` and
|
|
39
39
|
* `transportInterface` exclusively from the pin and treats channel
|
|
@@ -80,6 +80,12 @@ export interface ToolSetupContext extends SurfaceConversationContext {
|
|
|
80
80
|
allowedToolNames?: Set<string>;
|
|
81
81
|
/** When set, the subagent/wake tool allowlist (see {@link subagentToolGateMode}). */
|
|
82
82
|
subagentAllowedTools?: Set<string>;
|
|
83
|
+
/**
|
|
84
|
+
* Collects tool names the subagent attempted but that
|
|
85
|
+
* {@link subagentAllowedTools} denied, for parent-visible reporting. The
|
|
86
|
+
* executor records into this Set (shared by reference with the Conversation).
|
|
87
|
+
*/
|
|
88
|
+
subagentDeniedToolNames?: Set<string>;
|
|
83
89
|
/**
|
|
84
90
|
* How {@link subagentAllowedTools} is enforced. Absent or `"wire"` keeps
|
|
85
91
|
* the historical behavior (definitions filtered before the provider
|
|
@@ -342,28 +342,37 @@ export async function persistWakeTriggerMessage(
|
|
|
342
342
|
* inputs for tool-definition resolution — see {@link WakeToolContextPin}.
|
|
343
343
|
* Its `requestOrigin`, when set, is stamped onto the conversation's per-turn
|
|
344
344
|
* origin so the permission checker's origin-scoped auto-grants fire for the
|
|
345
|
-
* wake.
|
|
345
|
+
* wake. `preactivateSkillIds`, when non-empty, activates those skills' bundled
|
|
346
|
+
* tools in the turn's active set (`allowedToolNames`) for the wake so they are
|
|
347
|
+
* callable without a prior `skill_load`. All are set and restored alongside the
|
|
348
|
+
* allowlist.
|
|
346
349
|
*/
|
|
347
350
|
export function scopeWakeAllowedTools(
|
|
348
351
|
conversation: Conversation,
|
|
349
352
|
tools: ReadonlySet<string>,
|
|
350
353
|
gateMode: SubagentToolGateMode = "wire",
|
|
351
354
|
toolContextPin?: WakeToolContextPin,
|
|
355
|
+
preactivateSkillIds?: readonly string[],
|
|
352
356
|
): () => void {
|
|
353
357
|
const previous = conversation.subagentAllowedTools;
|
|
354
358
|
const previousGateMode = conversation.subagentToolGateMode;
|
|
355
359
|
const previousToolContextPin = conversation.toolContextPin;
|
|
356
360
|
const previousRequestOrigin = conversation.currentTurnRequestOrigin;
|
|
361
|
+
const previousPreactivated = conversation.preactivatedSkillIds;
|
|
357
362
|
conversation.setSubagentAllowedTools(new Set(tools));
|
|
358
363
|
conversation.subagentToolGateMode = gateMode;
|
|
359
364
|
conversation.toolContextPin = toolContextPin;
|
|
360
365
|
if (toolContextPin?.requestOrigin !== undefined) {
|
|
361
366
|
conversation.currentTurnRequestOrigin = toolContextPin.requestOrigin;
|
|
362
367
|
}
|
|
368
|
+
if (preactivateSkillIds && preactivateSkillIds.length > 0) {
|
|
369
|
+
conversation.setPreactivatedSkillIds([...preactivateSkillIds]);
|
|
370
|
+
}
|
|
363
371
|
return () => {
|
|
364
372
|
conversation.setSubagentAllowedTools(previous);
|
|
365
373
|
conversation.subagentToolGateMode = previousGateMode;
|
|
366
374
|
conversation.toolContextPin = previousToolContextPin;
|
|
367
375
|
conversation.currentTurnRequestOrigin = previousRequestOrigin;
|
|
376
|
+
conversation.setPreactivatedSkillIds(previousPreactivated);
|
|
368
377
|
};
|
|
369
378
|
}
|
package/src/hooks/types.ts
CHANGED
|
@@ -173,6 +173,15 @@ export interface UserPromptSubmitInputContext {
|
|
|
173
173
|
* user speech (e.g. title generation) should skip these turns.
|
|
174
174
|
*/
|
|
175
175
|
readonly isHiddenPrompt?: boolean;
|
|
176
|
+
/**
|
|
177
|
+
* How the triggering message was initiated when it was sent on the
|
|
178
|
+
* user's behalf by the UI rather than typed by hand (`body.source` on
|
|
179
|
+
* `POST /v1/messages`, persisted as `metadata.userMessageSource`).
|
|
180
|
+
* Current value: `"nav_redirect"` — a navigation shortcut redirected the
|
|
181
|
+
* user into chat with a pre-filled message. Unset for hand-typed
|
|
182
|
+
* messages.
|
|
183
|
+
*/
|
|
184
|
+
readonly messageSource?: string;
|
|
176
185
|
/**
|
|
177
186
|
* The user's original message list, immutable for the hook. Plugins
|
|
178
187
|
* may snapshot or compare against this but MUST NOT mutate it.
|
|
@@ -11,6 +11,9 @@
|
|
|
11
11
|
// Must be first — before any imports that resolve socket paths
|
|
12
12
|
delete process.env.ASSISTANT_IPC_SOCKET_DIR;
|
|
13
13
|
|
|
14
|
+
import { mkdtempSync, rmSync, writeFileSync } from "node:fs";
|
|
15
|
+
import { tmpdir } from "node:os";
|
|
16
|
+
import { join } from "node:path";
|
|
14
17
|
import { afterEach, beforeEach, expect, mock, test } from "bun:test";
|
|
15
18
|
|
|
16
19
|
import { runAssistantCommandFull } from "../../cli/__tests__/run-assistant-command.js";
|
|
@@ -358,6 +361,93 @@ test("send --json returns delivery_id", async () => {
|
|
|
358
361
|
expect(process.exitCode).toBe(0);
|
|
359
362
|
});
|
|
360
363
|
|
|
364
|
+
test("send --attach reads the file and forwards base64 attachments to the platform", async () => {
|
|
365
|
+
const dir = mkdtempSync(join(tmpdir(), "email-attach-"));
|
|
366
|
+
const filePath = join(dir, "report.pdf");
|
|
367
|
+
const fileBytes = "pdf-bytes-here";
|
|
368
|
+
writeFileSync(filePath, fileBytes);
|
|
369
|
+
|
|
370
|
+
let sendBody: Record<string, unknown> | null = null;
|
|
371
|
+
mockFetchFn = async (path, init) => {
|
|
372
|
+
if (path.includes("/email-addresses/")) {
|
|
373
|
+
return new Response(
|
|
374
|
+
JSON.stringify({
|
|
375
|
+
results: [{ id: "addr-1", address: "mybot@example.com" }],
|
|
376
|
+
}),
|
|
377
|
+
{ status: 200 },
|
|
378
|
+
);
|
|
379
|
+
}
|
|
380
|
+
// send endpoint — capture the forwarded payload
|
|
381
|
+
sendBody = JSON.parse(init?.body as string);
|
|
382
|
+
return new Response(
|
|
383
|
+
JSON.stringify({ delivery_id: "del_attach", status: "accepted" }),
|
|
384
|
+
{ status: 200 },
|
|
385
|
+
);
|
|
386
|
+
};
|
|
387
|
+
|
|
388
|
+
try {
|
|
389
|
+
await runAssistantCommandFull(
|
|
390
|
+
"email",
|
|
391
|
+
"--json",
|
|
392
|
+
"send",
|
|
393
|
+
"user@example.com",
|
|
394
|
+
"-s",
|
|
395
|
+
"Report",
|
|
396
|
+
"-b",
|
|
397
|
+
"Attached",
|
|
398
|
+
"--attach",
|
|
399
|
+
filePath,
|
|
400
|
+
);
|
|
401
|
+
|
|
402
|
+
expect(sendBody).not.toBeNull();
|
|
403
|
+
expect(sendBody!.attachments).toEqual([
|
|
404
|
+
{
|
|
405
|
+
filename: "report.pdf",
|
|
406
|
+
content_type: "application/pdf",
|
|
407
|
+
content: Buffer.from(fileBytes).toString("base64"),
|
|
408
|
+
},
|
|
409
|
+
]);
|
|
410
|
+
expect(process.exitCode).toBe(0);
|
|
411
|
+
} finally {
|
|
412
|
+
rmSync(dir, { recursive: true, force: true });
|
|
413
|
+
}
|
|
414
|
+
});
|
|
415
|
+
|
|
416
|
+
test("send with a missing --attach path fails without calling the platform", async () => {
|
|
417
|
+
let sendCalled = false;
|
|
418
|
+
mockFetchFn = async (path) => {
|
|
419
|
+
if (path.includes("/email-addresses/")) {
|
|
420
|
+
return new Response(
|
|
421
|
+
JSON.stringify({
|
|
422
|
+
results: [{ id: "addr-1", address: "mybot@example.com" }],
|
|
423
|
+
}),
|
|
424
|
+
{ status: 200 },
|
|
425
|
+
);
|
|
426
|
+
}
|
|
427
|
+
sendCalled = true;
|
|
428
|
+
return new Response(
|
|
429
|
+
JSON.stringify({ delivery_id: "x", status: "accepted" }),
|
|
430
|
+
{ status: 200 },
|
|
431
|
+
);
|
|
432
|
+
};
|
|
433
|
+
|
|
434
|
+
await runAssistantCommandFull(
|
|
435
|
+
"email",
|
|
436
|
+
"--json",
|
|
437
|
+
"send",
|
|
438
|
+
"user@example.com",
|
|
439
|
+
"-s",
|
|
440
|
+
"Report",
|
|
441
|
+
"-b",
|
|
442
|
+
"Attached",
|
|
443
|
+
"--attach",
|
|
444
|
+
join(tmpdir(), "does-not-exist-xyz.pdf"),
|
|
445
|
+
);
|
|
446
|
+
|
|
447
|
+
expect(process.exitCode).not.toBe(0);
|
|
448
|
+
expect(sendCalled).toBe(false);
|
|
449
|
+
});
|
|
450
|
+
|
|
361
451
|
test("send: 402 billing error surfaces", async () => {
|
|
362
452
|
mockFetchFn = async (path) => {
|
|
363
453
|
if (path.includes("/email-addresses/")) {
|
|
@@ -9,12 +9,14 @@
|
|
|
9
9
|
import { afterEach, describe, expect, test } from "bun:test";
|
|
10
10
|
|
|
11
11
|
import {
|
|
12
|
+
getMockPersistentCallCount,
|
|
12
13
|
mockGatewayIpc,
|
|
13
14
|
resetMockGatewayIpc,
|
|
14
15
|
} from "../__tests__/mock-gateway-ipc.js";
|
|
15
16
|
import {
|
|
16
17
|
ipcCall,
|
|
17
18
|
ipcCallPersistent,
|
|
19
|
+
ipcClassifyRisk,
|
|
18
20
|
ipcGetFeatureFlags,
|
|
19
21
|
resetPersistentClient,
|
|
20
22
|
} from "./gateway-client.js";
|
|
@@ -132,3 +134,60 @@ describe("ipcGetFeatureFlags", () => {
|
|
|
132
134
|
expect(flags).toEqual({});
|
|
133
135
|
});
|
|
134
136
|
});
|
|
137
|
+
|
|
138
|
+
// ---------------------------------------------------------------------------
|
|
139
|
+
// ipcClassifyRisk retry semantics
|
|
140
|
+
// ---------------------------------------------------------------------------
|
|
141
|
+
|
|
142
|
+
describe("ipcClassifyRisk retry", () => {
|
|
143
|
+
const CLASSIFY_OK = {
|
|
144
|
+
risk: "low",
|
|
145
|
+
reason: "safe",
|
|
146
|
+
matchType: "none",
|
|
147
|
+
scopeOptions: [],
|
|
148
|
+
};
|
|
149
|
+
const emptyParams = {} as unknown as Parameters<typeof ipcClassifyRisk>[0];
|
|
150
|
+
|
|
151
|
+
test("retries a transient failure and then succeeds", async () => {
|
|
152
|
+
mockGatewayIpc(null, {
|
|
153
|
+
failFirstN: 1,
|
|
154
|
+
results: { classify_risk: CLASSIFY_OK },
|
|
155
|
+
});
|
|
156
|
+
|
|
157
|
+
const result = await ipcClassifyRisk(emptyParams);
|
|
158
|
+
expect(result?.risk).toBe("low");
|
|
159
|
+
expect(getMockPersistentCallCount("classify_risk")).toBe(2);
|
|
160
|
+
});
|
|
161
|
+
|
|
162
|
+
test("returns undefined after exhausting retries (3 attempts)", async () => {
|
|
163
|
+
mockGatewayIpc(null, { error: true });
|
|
164
|
+
|
|
165
|
+
const result = await ipcClassifyRisk(emptyParams);
|
|
166
|
+
expect(result).toBeUndefined();
|
|
167
|
+
expect(getMockPersistentCallCount("classify_risk")).toBe(3);
|
|
168
|
+
});
|
|
169
|
+
|
|
170
|
+
test("does NOT retry a structured IpcCallError (fails after 1 attempt)", async () => {
|
|
171
|
+
mockGatewayIpc(null, {
|
|
172
|
+
failFirstN: 3,
|
|
173
|
+
ipcCallError: true,
|
|
174
|
+
results: { classify_risk: CLASSIFY_OK },
|
|
175
|
+
});
|
|
176
|
+
|
|
177
|
+
const result = await ipcClassifyRisk(emptyParams);
|
|
178
|
+
expect(result).toBeUndefined();
|
|
179
|
+
expect(getMockPersistentCallCount("classify_risk")).toBe(1);
|
|
180
|
+
});
|
|
181
|
+
|
|
182
|
+
test("stops retrying once the signal is aborted", async () => {
|
|
183
|
+
mockGatewayIpc(null, { error: true });
|
|
184
|
+
const controller = new AbortController();
|
|
185
|
+
controller.abort();
|
|
186
|
+
|
|
187
|
+
const result = await ipcClassifyRisk(emptyParams, controller.signal);
|
|
188
|
+
expect(result).toBeUndefined();
|
|
189
|
+
// An already-aborted signal short-circuits the retry loop after the first
|
|
190
|
+
// failed attempt rather than exhausting all three.
|
|
191
|
+
expect(getMockPersistentCallCount("classify_risk")).toBe(1);
|
|
192
|
+
});
|
|
193
|
+
});
|
|
@@ -12,6 +12,7 @@
|
|
|
12
12
|
|
|
13
13
|
import {
|
|
14
14
|
ipcCall as packageIpcCall,
|
|
15
|
+
IpcCallError,
|
|
15
16
|
PersistentIpcClient as PackagePersistentIpcClient,
|
|
16
17
|
} from "@vellumai/gateway-client/ipc-client";
|
|
17
18
|
|
|
@@ -20,6 +21,7 @@ import type {
|
|
|
20
21
|
ClassifyRiskParams,
|
|
21
22
|
} from "../permissions/ipc-risk-types.js";
|
|
22
23
|
import { getLogger } from "../util/logger.js";
|
|
24
|
+
import { abortableSleep, computeRetryDelay } from "../util/retry.js";
|
|
23
25
|
import { resolveIpcSocketPath } from "./socket-path.js";
|
|
24
26
|
|
|
25
27
|
const log = getLogger("gateway-ipc-client");
|
|
@@ -138,6 +140,19 @@ export async function ipcGetVelayStatus(): Promise<VelayTunnelStatus | null> {
|
|
|
138
140
|
};
|
|
139
141
|
}
|
|
140
142
|
|
|
143
|
+
// classify_risk is an idempotent, side-effect-free read, so a transient gateway
|
|
144
|
+
// blip (socket dropped between calls, momentary unreachability) is safe to
|
|
145
|
+
// retry — the persistent client re-establishes its socket on the next call. The
|
|
146
|
+
// budget is deliberately sized for SUB-SECOND blips, not restart-class outages:
|
|
147
|
+
// each attempt uses a tighter timeout than the transport default so a wedged-
|
|
148
|
+
// but-reachable gateway cannot multiply the worst-case wait, and total latency
|
|
149
|
+
// against a hard-down gateway stays close to the single-attempt ceiling.
|
|
150
|
+
// Deterministic failures — a structured IpcCallError, or a returned-but-
|
|
151
|
+
// malformed payload — are NOT retried; they fail closed immediately.
|
|
152
|
+
const CLASSIFY_RISK_MAX_RETRIES = 2; // 3 attempts total
|
|
153
|
+
const CLASSIFY_RISK_RETRY_BASE_MS = 100; // ~[50,100] then ~[100,200] ms backoff
|
|
154
|
+
const CLASSIFY_RISK_ATTEMPT_TIMEOUT_MS = 1_500; // per-attempt cap (< 5s default)
|
|
155
|
+
|
|
141
156
|
/**
|
|
142
157
|
* Classify risk for a tool invocation via the gateway's persistent IPC
|
|
143
158
|
* connection.
|
|
@@ -146,41 +161,69 @@ export async function ipcGetVelayStatus(): Promise<VelayTunnelStatus | null> {
|
|
|
146
161
|
* classification is on the hot path for every tool invocation and the
|
|
147
162
|
* persistent connection avoids per-call connect overhead.
|
|
148
163
|
*
|
|
149
|
-
*
|
|
150
|
-
*
|
|
151
|
-
*
|
|
164
|
+
* A transient transport failure is retried with bounded backoff (see the
|
|
165
|
+
* constants above) before giving up; a structured `IpcCallError` or a
|
|
166
|
+
* malformed payload fails fast without retrying. Returns `undefined` when the
|
|
167
|
+
* gateway is unreachable or the response is unusable after retries — callers
|
|
168
|
+
* should throw since there is no local fallback (gateway is a hard dependency).
|
|
169
|
+
* When a `signal` is supplied, retries stop as soon as it aborts.
|
|
152
170
|
*/
|
|
153
171
|
export async function ipcClassifyRisk(
|
|
154
172
|
params: ClassifyRiskParams,
|
|
173
|
+
signal?: AbortSignal,
|
|
155
174
|
): Promise<ClassificationResult | undefined> {
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
// Validate the response has at minimum a `risk` field
|
|
163
|
-
if (!result || typeof result !== "object" || Array.isArray(result)) {
|
|
164
|
-
log.warn(
|
|
165
|
-
{ result },
|
|
166
|
-
"ipcClassifyRisk: gateway returned non-object response",
|
|
175
|
+
for (let attempt = 0; ; attempt++) {
|
|
176
|
+
try {
|
|
177
|
+
const result = await ipcCallPersistent(
|
|
178
|
+
"classify_risk",
|
|
179
|
+
params as unknown as Record<string, unknown>,
|
|
180
|
+
CLASSIFY_RISK_ATTEMPT_TIMEOUT_MS,
|
|
167
181
|
);
|
|
168
|
-
return undefined;
|
|
169
|
-
}
|
|
170
182
|
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
183
|
+
// Returned-but-malformed responses are deterministic, not transient:
|
|
184
|
+
// fail closed immediately (no retry).
|
|
185
|
+
if (!result || typeof result !== "object" || Array.isArray(result)) {
|
|
186
|
+
log.warn(
|
|
187
|
+
{ result },
|
|
188
|
+
"ipcClassifyRisk: gateway returned non-object response",
|
|
189
|
+
);
|
|
190
|
+
return undefined;
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
const obj = result as Record<string, unknown>;
|
|
194
|
+
if (typeof obj.risk !== "string") {
|
|
195
|
+
log.warn(
|
|
196
|
+
{ result },
|
|
197
|
+
"ipcClassifyRisk: gateway response missing 'risk' field",
|
|
198
|
+
);
|
|
199
|
+
return undefined;
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
return result as ClassificationResult;
|
|
203
|
+
} catch (err) {
|
|
204
|
+
// A structured gateway error means the gateway was reachable and
|
|
205
|
+
// deterministically rejected the request — not a transient blip.
|
|
206
|
+
const retryable = !(err instanceof IpcCallError);
|
|
207
|
+
if (
|
|
208
|
+
retryable &&
|
|
209
|
+
attempt < CLASSIFY_RISK_MAX_RETRIES &&
|
|
210
|
+
!signal?.aborted
|
|
211
|
+
) {
|
|
212
|
+
log.warn(
|
|
213
|
+
{ err, attempt },
|
|
214
|
+
"ipcClassifyRisk: transient IPC failure, retrying after backoff",
|
|
215
|
+
);
|
|
216
|
+
await abortableSleep(
|
|
217
|
+
computeRetryDelay(attempt, CLASSIFY_RISK_RETRY_BASE_MS),
|
|
218
|
+
signal,
|
|
219
|
+
);
|
|
220
|
+
if (!signal?.aborted) {
|
|
221
|
+
continue;
|
|
222
|
+
}
|
|
223
|
+
}
|
|
224
|
+
log.warn({ err }, "ipcClassifyRisk: persistent IPC call failed");
|
|
177
225
|
return undefined;
|
|
178
226
|
}
|
|
179
|
-
|
|
180
|
-
return result as ClassificationResult;
|
|
181
|
-
} catch (err) {
|
|
182
|
-
log.warn({ err }, "ipcClassifyRisk: persistent IPC call failed");
|
|
183
|
-
return undefined;
|
|
184
227
|
}
|
|
185
228
|
}
|
|
186
229
|
|
|
@@ -4,6 +4,7 @@ import type {
|
|
|
4
4
|
VoiceTurnCallbacks,
|
|
5
5
|
VoiceTurnOptions,
|
|
6
6
|
} from "../../calls/voice-session-bridge.js";
|
|
7
|
+
import { VOICE_NO_SETUP_FLOWS_RULE } from "../../calls/voice-session-bridge.js";
|
|
7
8
|
import type {
|
|
8
9
|
StreamingTranscriber,
|
|
9
10
|
SttStreamServerEvent,
|
|
@@ -129,7 +130,9 @@ async function waitFor(
|
|
|
129
130
|
message = "Timed out waiting for live voice event test condition",
|
|
130
131
|
): Promise<void> {
|
|
131
132
|
for (let attempt = 0; attempt < 60; attempt += 1) {
|
|
132
|
-
if (predicate())
|
|
133
|
+
if (predicate()) {
|
|
134
|
+
return;
|
|
135
|
+
}
|
|
133
136
|
await new Promise((resolve) => setTimeout(resolve, 5));
|
|
134
137
|
}
|
|
135
138
|
throw new Error(message);
|
|
@@ -246,7 +249,16 @@ describe("LiveVoiceSession archive and metrics events", () => {
|
|
|
246
249
|
|
|
247
250
|
await startReleasedTurn(session);
|
|
248
251
|
expect(startVoiceTurn.mock.calls[0]?.[0]).toMatchObject({
|
|
249
|
-
|
|
252
|
+
userMessageChannel: "vellum",
|
|
253
|
+
assistantMessageChannel: "vellum",
|
|
254
|
+
userMessageInterface: "macos",
|
|
255
|
+
assistantMessageInterface: "macos",
|
|
256
|
+
// Pins the full production control prompt, including the no-UI rule
|
|
257
|
+
// (voice turns are non-interactive — everything is conveyed in speech)
|
|
258
|
+
// and the shared no-setup-flows rule (no OAuth/browser flows mid-call).
|
|
259
|
+
voiceControlPrompt:
|
|
260
|
+
"You are speaking in a local live voice session. Keep replies brief and conversational. You cannot display cards, forms, or any on-screen UI during the call — convey everything in speech. " +
|
|
261
|
+
VOICE_NO_SETUP_FLOWS_RULE,
|
|
250
262
|
});
|
|
251
263
|
callbacks?.assistant_text_delta?.(makeTextDelta("Hello there."));
|
|
252
264
|
callbacks?.message_complete?.(makeMessageComplete());
|