@vellumai/assistant 0.6.4 → 0.6.5
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/.prettierignore +5 -0
- package/ARCHITECTURE.md +32 -36
- package/Dockerfile +12 -0
- package/README.md +3 -4
- package/bun.lock +8 -3
- package/docs/architecture/integrations.md +1 -20
- package/docs/architecture/security.md +16 -16
- package/docs/error-handling.md +111 -0
- package/docs/skills.md +10 -10
- package/docs/stt-provider-onboarding.md +2 -1
- package/knip.json +9 -2
- package/node_modules/@vellumai/ces-contracts/package.json +2 -1
- package/node_modules/@vellumai/ces-contracts/src/__tests__/trust-rules.test.ts +471 -0
- package/node_modules/@vellumai/ces-contracts/src/trust-rules.ts +398 -4
- package/node_modules/@vellumai/credential-storage/bun.lock +2 -2
- package/node_modules/@vellumai/credential-storage/package.json +2 -2
- package/node_modules/@vellumai/credential-storage/src/oauth-runtime.ts +20 -2
- package/node_modules/@vellumai/egress-proxy/bun.lock +2 -2
- package/node_modules/@vellumai/egress-proxy/package.json +2 -2
- package/openapi.yaml +123 -11
- package/package.json +6 -3
- package/scripts/generate-openapi.ts +50 -11
- package/src/__tests__/agent-loop-callsite-precedence.test.ts +318 -0
- package/src/__tests__/agent-loop-sentry-hygiene.test.ts +137 -0
- package/src/__tests__/agent-loop.test.ts +112 -1
- package/src/__tests__/anthropic-error-formatting.test.ts +98 -0
- package/src/__tests__/anthropic-provider.test.ts +171 -2
- package/src/__tests__/approval-cascade.test.ts +31 -10
- package/src/__tests__/approval-routes-http.test.ts +134 -10
- package/src/__tests__/assistant-attachments.test.ts +44 -0
- package/src/__tests__/assistant-feature-flags-integration.test.ts +29 -0
- package/src/__tests__/browser-fill-credential.test.ts +1 -1
- package/src/__tests__/browser-identifier-parity-guard.test.ts +53 -0
- package/src/__tests__/browser-skill-baseline-tool-payload.test.ts +23 -33
- package/src/__tests__/browser-skill-endstate.test.ts +51 -182
- package/src/__tests__/btw-routes.test.ts +47 -1
- package/src/__tests__/call-controller.test.ts +1 -2
- package/src/__tests__/call-site-routing-provider.test.ts +214 -0
- package/src/__tests__/catalog-cache.test.ts +27 -4
- package/src/__tests__/channel-approval-routes.test.ts +4 -4
- package/src/__tests__/channel-reply-delivery.test.ts +300 -2
- package/src/__tests__/checker.test.ts +428 -501
- package/src/__tests__/cli-command-risk-guard.test.ts +30 -33
- package/src/__tests__/compaction-circuit-breaker.test.ts +336 -0
- package/src/__tests__/compaction.benchmark.test.ts +1 -1
- package/src/__tests__/config-analysis.test.ts +11 -28
- package/src/__tests__/config-loader-backfill.test.ts +174 -0
- package/src/__tests__/config-loader-corrupt.test.ts +183 -0
- package/src/__tests__/config-loader-quarantine-bulletin.test.ts +202 -0
- package/src/__tests__/config-schema-cmd.test.ts +11 -5
- package/src/__tests__/config-schema.test.ts +427 -114
- package/src/__tests__/config-watcher.test.ts +2 -2
- package/src/__tests__/contact-store-user-file.test.ts +72 -73
- package/src/__tests__/contacts-write.test.ts +4 -4
- package/src/__tests__/context-token-estimator.test.ts +191 -1
- package/src/__tests__/context-window-manager.test.ts +530 -2
- package/src/__tests__/conversation-abort-tool-results.test.ts +30 -16
- package/src/__tests__/conversation-agent-loop-overflow.test.ts +61 -17
- package/src/__tests__/conversation-agent-loop.test.ts +412 -82
- package/src/__tests__/conversation-attachments.test.ts +1 -1
- package/src/__tests__/conversation-confirmation-signals.test.ts +30 -9
- package/src/__tests__/conversation-error.test.ts +37 -6
- package/src/__tests__/conversation-history-web-search.test.ts +6 -0
- package/src/__tests__/conversation-init.benchmark.test.ts +36 -0
- package/src/__tests__/conversation-lifecycle.test.ts +336 -0
- package/src/__tests__/conversation-load-history-repair.test.ts +27 -10
- package/src/__tests__/conversation-pre-run-repair.test.ts +30 -16
- package/src/__tests__/conversation-process-callsite.test.ts +306 -0
- package/src/__tests__/conversation-provider-retry-repair.test.ts +30 -16
- package/src/__tests__/conversation-queue.test.ts +41 -26
- package/src/__tests__/conversation-routes-disk-view.test.ts +29 -1
- package/src/__tests__/conversation-routes-slash-commands.test.ts +31 -3
- package/src/__tests__/conversation-runtime-assembly.test.ts +2735 -55
- package/src/__tests__/conversation-runtime-workspace.test.ts +12 -12
- package/src/__tests__/conversation-skill-tools.test.ts +12 -146
- package/src/__tests__/conversation-slash-queue.test.ts +34 -19
- package/src/__tests__/conversation-slash-unknown.test.ts +30 -16
- package/src/__tests__/conversation-speed-override.test.ts +30 -11
- package/src/__tests__/conversation-surfaces-standalone-payloads.test.ts +1035 -0
- package/src/__tests__/conversation-surfaces-standalone.test.ts +630 -0
- package/src/__tests__/conversation-title-service.test.ts +2 -2
- package/src/__tests__/conversation-tool-setup-batch-authorized.test.ts +1 -1
- package/src/__tests__/conversation-unread-route.test.ts +2 -2
- package/src/__tests__/conversation-usage.test.ts +3 -1
- package/src/__tests__/conversation-workspace-cache-state.test.ts +31 -10
- package/src/__tests__/conversation-workspace-injection.test.ts +43 -15
- package/src/__tests__/conversation-workspace-tool-tracking.test.ts +44 -16
- package/src/__tests__/credential-broker-browser-fill.test.ts +110 -0
- package/src/__tests__/credential-security-invariants.test.ts +3 -0
- package/src/__tests__/credential-storage-oauth-compat.test.ts +18 -0
- package/src/__tests__/credential-storage-static-compat.test.ts +28 -0
- package/src/__tests__/credential-vault-unit.test.ts +135 -19
- package/src/__tests__/credentials-cli.test.ts +1 -9
- package/src/__tests__/cross-provider-web-search.test.ts +84 -0
- package/src/__tests__/daemon-server-persist-and-process-callsite.test.ts +92 -0
- package/src/__tests__/delete-propagation.test.ts +437 -0
- package/src/__tests__/dm-backfill.test.ts +417 -0
- package/src/__tests__/dm-persistence.test.ts +227 -0
- package/src/__tests__/edit-propagation.test.ts +280 -0
- package/src/__tests__/ephemeral-permissions.test.ts +93 -3
- package/src/__tests__/estimator-calibration-integration.test.ts +208 -0
- package/src/__tests__/estimator-calibration.test.ts +213 -0
- package/src/__tests__/extension-id-sync-guard.test.ts +26 -7
- package/src/__tests__/file-write-tool.test.ts +151 -1
- package/src/__tests__/filing-service.test.ts +255 -0
- package/src/__tests__/gemini-provider.test.ts +0 -3
- package/src/__tests__/guardian-grant-minting.test.ts +8 -0
- package/src/__tests__/headless-browser-interactions.test.ts +1 -1
- package/src/__tests__/heartbeat-service.test.ts +96 -15
- package/src/__tests__/host-shell-tool.test.ts +124 -18
- package/src/__tests__/http-user-message-parity.test.ts +29 -1
- package/src/__tests__/inbound-slack-persistence.test.ts +340 -0
- package/src/__tests__/intent-routing.test.ts +1 -40
- package/src/__tests__/llm-catalog-parity.test.ts +174 -0
- package/src/__tests__/llm-context-normalization.test.ts +121 -0
- package/src/__tests__/llm-resolver.test.ts +214 -0
- package/src/__tests__/llm-schema.test.ts +223 -0
- package/src/__tests__/managed-proxy-context.test.ts +6 -2
- package/src/__tests__/messaging-skill-split.test.ts +3 -34
- package/src/__tests__/migration-import-from-url.test.ts +684 -0
- package/src/__tests__/model-intents.test.ts +9 -83
- package/src/__tests__/notification-decision-fallback.test.ts +0 -10
- package/src/__tests__/notification-decision-identity.test.ts +0 -9
- package/src/__tests__/notification-decision-recipient-context.test.ts +0 -9
- package/src/__tests__/oauth-store.test.ts +10 -7
- package/src/__tests__/oauth2-gateway-transport.test.ts +8 -3
- package/src/__tests__/oauth2-refresh-retry.test.ts +279 -0
- package/src/__tests__/openai-provider.test.ts +7 -0
- package/src/__tests__/openai-responses-provider.test.ts +396 -0
- package/src/__tests__/openrouter-provider-only.test.ts +135 -0
- package/src/__tests__/outbound-slack-persistence.test.ts +293 -0
- package/src/__tests__/permission-checker-host-gate.test.ts +1 -1
- package/src/__tests__/permission-mode.test.ts +16 -0
- package/src/__tests__/permission-types.test.ts +0 -1
- package/src/__tests__/persona-resolver.test.ts +13 -13
- package/src/__tests__/pkb-autoinject.test.ts +37 -1
- package/src/__tests__/platform-bash-auto-approve.test.ts +1 -1
- package/src/__tests__/pricing.test.ts +50 -3
- package/src/__tests__/profiler-routes.test.ts +1 -1
- package/src/__tests__/provider-commit-message-generator.test.ts +14 -84
- package/src/__tests__/provider-env-vars-scope.test.ts +52 -0
- package/src/__tests__/provider-error-scenarios.test.ts +135 -6
- package/src/__tests__/provider-managed-proxy-integration.test.ts +42 -11
- package/src/__tests__/provider-registry-ollama.test.ts +1 -2
- package/src/__tests__/proxy-approval-callback.test.ts +0 -1
- package/src/__tests__/reaction-persistence.test.ts +560 -0
- package/src/__tests__/relay-server.test.ts +1 -1
- package/src/__tests__/require-fresh-approval.test.ts +1 -1
- package/src/__tests__/retry-openrouter-only-normalization.test.ts +136 -0
- package/src/__tests__/retry-thinking-tool-choice.test.ts +226 -0
- package/src/__tests__/risk-classifier-parity.test.ts +230 -0
- package/src/__tests__/sanitize-config-for-transfer.test.ts +78 -1
- package/src/__tests__/secret-ingress-http.test.ts +28 -0
- package/src/__tests__/secret-prompter-channel-fallback.test.ts +125 -0
- package/src/__tests__/secret-routes-managed-proxy.test.ts +2 -3
- package/src/__tests__/secret-scanner-executor.test.ts +1 -1
- package/src/__tests__/send-endpoint-busy.test.ts +29 -1
- package/src/__tests__/server-history-render.test.ts +31 -0
- package/src/__tests__/shell-parser-property.test.ts +13 -13
- package/src/__tests__/skill-cache-store.test.ts +182 -0
- package/src/__tests__/skills.test.ts +19 -33
- package/src/__tests__/slack-app-setup-skill-regression.test.ts +3 -1
- package/src/__tests__/slack-skill.test.ts +3 -8
- package/src/__tests__/starter-bundle.test.ts +35 -0
- package/src/__tests__/subagent-call-site-routing.test.ts +280 -0
- package/src/__tests__/suggestion-routes.test.ts +160 -3
- package/src/__tests__/system-prompt.test.ts +22 -35
- package/src/__tests__/task-runner.test.ts +3 -1
- package/src/__tests__/tcc-sandbox-deny.test.ts +198 -0
- package/src/__tests__/terminal-tools.test.ts +8 -0
- package/src/__tests__/test-support/browser-skill-harness.ts +2 -52
- package/src/__tests__/thread-backfill.test.ts +941 -0
- package/src/__tests__/tool-execution-pipeline.benchmark.test.ts +2 -2
- package/src/__tests__/tool-executor-lifecycle-events.test.ts +2 -2
- package/src/__tests__/tool-executor.test.ts +60 -94
- package/src/__tests__/trust-store.test.ts +442 -109
- package/src/__tests__/update-bulletin-job.test.ts +389 -0
- package/src/__tests__/usage-cache-backfill-migration.test.ts +3 -1
- package/src/__tests__/verification-control-plane-policy.test.ts +1 -22
- package/src/__tests__/voice-session-bridge.test.ts +39 -0
- package/src/__tests__/volume-security-guard.test.ts +3 -2
- package/src/__tests__/web-search-history.test.ts +337 -0
- package/src/__tests__/workspace-migration-039-drop-legacy-llm-keys.test.ts +343 -0
- package/src/__tests__/workspace-migration-043-release-notes-latex-rendering.test.ts +202 -0
- package/src/__tests__/workspace-migration-045-release-notes-meet-avatar.test.ts +210 -0
- package/src/__tests__/workspace-migration-drop-user-md.test.ts +11 -11
- package/src/__tests__/workspace-migration-unify-llm-callsite-configs.test.ts +841 -0
- package/src/__tests__/workspace-policy.test.ts +1 -13
- package/src/acp/client-handler.ts +1 -2
- package/src/agent/loop.ts +209 -17
- package/src/avatar/resvg-lazy.test.ts +136 -0
- package/src/avatar/resvg-lazy.ts +82 -9
- package/src/avatar/traits-png-sync.ts +21 -1
- package/src/browser/__tests__/operations.test.ts +163 -0
- package/src/browser/identifiers.ts +51 -0
- package/src/browser/operations.ts +660 -0
- package/src/browser/types.ts +81 -0
- package/src/calls/guardian-question-copy.ts +2 -2
- package/src/calls/telephony-stt-routing.ts +1 -1
- package/src/calls/voice-session-bridge.ts +1 -0
- package/src/cli/AGENTS.md +1 -1
- package/src/cli/commands/__tests__/attachment.test.ts +438 -0
- package/src/cli/commands/__tests__/browser.test.ts +554 -0
- package/src/cli/commands/__tests__/cache.test.ts +623 -0
- package/src/cli/commands/__tests__/email-list.test.ts +6 -0
- package/src/cli/commands/__tests__/email-send.test.ts +93 -1
- package/src/cli/commands/__tests__/image-generation.test.ts +666 -0
- package/src/cli/commands/__tests__/inference-send.test.ts +451 -0
- package/src/cli/commands/__tests__/stt-transcribe.test.ts +454 -0
- package/src/cli/commands/__tests__/task.test.ts +913 -0
- package/src/cli/commands/__tests__/tts-synthesize.test.ts +594 -0
- package/src/cli/commands/__tests__/ui-confirm.test.ts +650 -0
- package/src/cli/commands/__tests__/ui.test.ts +1215 -0
- package/src/cli/commands/__tests__/watchers.test.ts +716 -0
- package/src/cli/commands/attachment.ts +182 -0
- package/src/cli/commands/browser.ts +350 -0
- package/src/cli/commands/cache.ts +341 -0
- package/src/cli/commands/completions.ts +0 -3
- package/src/cli/commands/config.ts +6 -6
- package/src/cli/commands/conversations-import.ts +347 -0
- package/src/cli/commands/conversations.ts +14 -1
- package/src/cli/commands/email.ts +234 -194
- package/src/cli/commands/image-generation.ts +300 -0
- package/src/cli/commands/inference.ts +200 -0
- package/src/cli/commands/memory.ts +127 -17
- package/src/cli/commands/platform/__tests__/callback-routes-list.test.ts +0 -1
- package/src/cli/commands/platform/__tests__/connect.test.ts +0 -1
- package/src/cli/commands/platform/__tests__/disconnect.test.ts +0 -1
- package/src/cli/commands/platform/__tests__/status.test.ts +0 -1
- package/src/cli/commands/stt.ts +339 -0
- package/src/cli/commands/task.ts +795 -0
- package/src/cli/commands/trust.ts +50 -19
- package/src/cli/commands/tts.ts +273 -0
- package/src/cli/commands/ui.ts +670 -0
- package/src/cli/commands/watchers.ts +509 -0
- package/src/cli/lib/daemon-credential-client.ts +0 -19
- package/src/cli/program.ts +23 -4
- package/src/cli.ts +0 -37
- package/src/config/bundled-skills/conversations/tools/rename-conversation.ts +23 -1
- package/src/config/bundled-skills/media-processing/services/reduce.ts +1 -1
- package/src/config/bundled-skills/messaging/SKILL.md +2 -2
- package/src/config/bundled-skills/messaging/TOOLS.json +4 -0
- package/src/config/bundled-skills/messaging/tools/messaging-archive-by-sender.ts +8 -1
- package/src/config/bundled-skills/messaging/tools/messaging-read.ts +15 -1
- package/src/config/bundled-skills/messaging/tools/messaging-search.ts +21 -1
- package/src/config/bundled-skills/messaging/tools/messaging-send.ts +11 -12
- package/src/config/bundled-skills/phone-calls/references/CONFIG.md +9 -8
- package/src/config/bundled-skills/settings/TOOLS.json +3 -3
- package/src/config/bundled-tool-registry.ts +0 -175
- package/src/config/env.ts +7 -2
- package/src/config/feature-flag-registry.json +25 -9
- package/src/config/llm-resolver.ts +128 -0
- package/src/config/loader.ts +194 -10
- package/src/config/raw-config-utils.ts +30 -2
- package/src/config/sanitize-for-transfer.ts +35 -0
- package/src/config/schema.ts +30 -41
- package/src/config/schemas/analysis.ts +3 -22
- package/src/config/schemas/calls.ts +0 -4
- package/src/config/schemas/filing.ts +2 -7
- package/src/config/schemas/heartbeat.ts +0 -5
- package/src/config/schemas/inference.ts +3 -23
- package/src/config/schemas/llm.ts +318 -0
- package/src/config/schemas/memory-processing.ts +1 -9
- package/src/config/schemas/notifications.ts +4 -11
- package/src/config/schemas/platform.ts +3 -9
- package/src/config/schemas/security.ts +33 -0
- package/src/config/schemas/services.ts +9 -4
- package/src/config/schemas/stt.ts +1 -0
- package/src/config/schemas/tts.ts +53 -0
- package/src/config/schemas/updates.ts +1 -1
- package/src/config/schemas/workspace-git.ts +3 -40
- package/src/config/skills.ts +2 -2
- package/src/context/__tests__/compact-prompt.test.ts +45 -0
- package/src/context/__tests__/microcompact.test.ts +805 -0
- package/src/context/estimator-calibration.ts +136 -0
- package/src/context/microcompact.ts +443 -0
- package/src/context/prompts/compact.md +12 -0
- package/src/context/token-estimator.ts +61 -3
- package/src/context/window-manager.ts +229 -25
- package/src/credential-execution/approval-bridge.ts +0 -1
- package/src/credential-execution/executable-discovery.ts +19 -8
- package/src/credential-execution/process-manager.test.ts +109 -0
- package/src/credential-execution/process-manager.ts +65 -2
- package/src/daemon/approval-generators.ts +29 -4
- package/src/daemon/assistant-attachments.ts +24 -13
- package/src/daemon/classifier.ts +2 -2
- package/src/daemon/config-watcher.ts +0 -1
- package/src/daemon/context-overflow-reducer.ts +4 -1
- package/src/daemon/conversation-agent-loop-handlers.ts +79 -12
- package/src/daemon/conversation-agent-loop.ts +462 -80
- package/src/daemon/conversation-attachments.ts +2 -6
- package/src/daemon/conversation-error.ts +36 -1
- package/src/daemon/conversation-lifecycle.ts +30 -6
- package/src/daemon/conversation-messaging.ts +73 -4
- package/src/daemon/conversation-process.ts +10 -4
- package/src/daemon/conversation-queue-manager.ts +3 -0
- package/src/daemon/conversation-runtime-assembly.ts +760 -29
- package/src/daemon/conversation-slash.ts +2 -2
- package/src/daemon/conversation-surfaces.ts +389 -1
- package/src/daemon/conversation-tool-setup.ts +10 -5
- package/src/daemon/conversation-usage.ts +1 -1
- package/src/daemon/conversation.ts +118 -30
- package/src/daemon/external-skills-bootstrap.ts +41 -0
- package/src/daemon/guardian-action-generators.ts +34 -14
- package/src/daemon/handlers/config-model.test.ts +86 -0
- package/src/daemon/handlers/config-model.ts +54 -12
- package/src/daemon/handlers/conversations.ts +9 -2
- package/src/daemon/handlers/shared.ts +39 -11
- package/src/daemon/handlers/skills.ts +2 -2
- package/src/daemon/handlers/slack-channel-oauth-install.ts +197 -0
- package/src/daemon/lifecycle.ts +76 -14
- package/src/daemon/message-types/conversations.ts +14 -0
- package/src/daemon/message-types/messages.ts +9 -1
- package/src/daemon/message-types/trust.ts +0 -2
- package/src/daemon/parse-actual-tokens-from-error.test.ts +57 -1
- package/src/daemon/parse-actual-tokens-from-error.ts +66 -0
- package/src/daemon/pkb-context-tracker.test.ts +169 -0
- package/src/daemon/pkb-context-tracker.ts +125 -0
- package/src/daemon/pkb-reminder-builder.test.ts +70 -0
- package/src/daemon/pkb-reminder-builder.ts +31 -0
- package/src/daemon/providers-setup.ts +6 -0
- package/src/daemon/server.ts +117 -9
- package/src/daemon/tool-side-effects.ts +0 -9
- package/src/daemon/watch-handler.ts +4 -4
- package/src/daemon/web-search-history.ts +126 -0
- package/src/events/domain-events.ts +0 -1
- package/src/filing/filing-service.ts +9 -10
- package/src/heartbeat/heartbeat-service.ts +76 -28
- package/src/home/__tests__/feed-scheduler.test.ts +39 -11
- package/src/home/__tests__/rollup-producer.test.ts +44 -0
- package/src/home/assistant-feed-authoring.ts +4 -0
- package/src/home/emit-feed-event.ts +4 -0
- package/src/home/feed-scheduler.ts +20 -4
- package/src/home/feed-types.ts +56 -2
- package/src/home/relationship-state-writer.ts +2 -2
- package/src/home/rollup-producer.ts +34 -5
- package/src/home/suggested-prompts.ts +101 -0
- package/src/ipc/__tests__/attachment-ipc.test.ts +213 -0
- package/src/ipc/__tests__/browser-ipc.test.ts +339 -0
- package/src/ipc/__tests__/cache-ipc.test.ts +266 -0
- package/src/ipc/__tests__/socket-path.test.ts +73 -0
- package/src/ipc/__tests__/task-ipc.test.ts +577 -0
- package/src/ipc/__tests__/ui-request-route.test.ts +495 -0
- package/src/ipc/__tests__/watcher-ipc.test.ts +295 -0
- package/src/ipc/cli-client.ts +2 -1
- package/src/ipc/cli-server.ts +26 -8
- package/src/ipc/gateway-client.ts +4 -4
- package/src/ipc/routes/attachment.ts +114 -0
- package/src/ipc/routes/browser-context.ts +61 -0
- package/src/ipc/routes/browser.ts +96 -0
- package/src/ipc/routes/cache.ts +96 -0
- package/src/ipc/routes/index.ts +17 -1
- package/src/ipc/routes/task-queue.ts +226 -0
- package/src/ipc/routes/task.ts +173 -0
- package/src/ipc/routes/ui-request.ts +50 -0
- package/src/ipc/routes/watcher.ts +203 -0
- package/src/ipc/socket-path.ts +100 -0
- package/src/memory/__tests__/conversation-analyze-job.test.ts +9 -8
- package/src/memory/__tests__/conversation-group-migration.test.ts +99 -0
- package/src/memory/admin.ts +18 -0
- package/src/memory/conversation-analyze-job.ts +14 -13
- package/src/memory/conversation-attention-store.ts +13 -6
- package/src/memory/conversation-crud.ts +103 -3
- package/src/memory/conversation-group-migration.ts +38 -6
- package/src/memory/conversation-title-service.ts +7 -4
- package/src/memory/db-init.ts +2 -0
- package/src/memory/embedding-backend.ts +1 -1
- package/src/memory/graph/compaction.ts +299 -0
- package/src/memory/graph/consolidation.ts +4 -4
- package/src/memory/graph/conversation-graph-memory.ts +89 -29
- package/src/memory/graph/extraction.test.ts +272 -2
- package/src/memory/graph/extraction.ts +173 -51
- package/src/memory/graph/graph-search.test.ts +92 -0
- package/src/memory/graph/graph-search.ts +4 -1
- package/src/memory/graph/narrative.ts +2 -2
- package/src/memory/graph/pattern-scan.ts +2 -2
- package/src/memory/graph/retriever.test.ts +459 -0
- package/src/memory/graph/retriever.ts +230 -48
- package/src/memory/graph/store.ts +41 -0
- package/src/memory/graph/tool-handlers.ts +27 -0
- package/src/memory/graph/tools.ts +6 -1
- package/src/memory/indexer.ts +5 -5
- package/src/memory/job-handlers/conversation-starters.ts +23 -20
- package/src/memory/job-handlers/summarization.ts +2 -2
- package/src/memory/job-utils.ts +7 -1
- package/src/memory/jobs/embed-pkb-file.test.ts +168 -0
- package/src/memory/jobs/embed-pkb-file.ts +54 -0
- package/src/memory/jobs-store.ts +44 -3
- package/src/memory/jobs-worker.ts +4 -0
- package/src/memory/migrations/140-backfill-usage-cache-accounting.ts +1 -1
- package/src/memory/migrations/220-normalize-user-file-by-principal.ts +2 -2
- package/src/memory/migrations/222-strip-placeholder-sentinels-from-messages.ts +82 -0
- package/src/memory/migrations/index.ts +1 -0
- package/src/memory/pkb/pkb-index.test.ts +368 -0
- package/src/memory/pkb/pkb-index.ts +255 -0
- package/src/memory/pkb/pkb-reconcile.test.ts +251 -0
- package/src/memory/pkb/pkb-reconcile.ts +148 -0
- package/src/memory/pkb/pkb-search.test.ts +438 -0
- package/src/memory/pkb/pkb-search.ts +137 -0
- package/src/memory/pkb/types.ts +53 -0
- package/src/memory/qdrant-client.ts +122 -1
- package/src/memory/slack-thread-store.ts +37 -0
- package/src/messaging/providers/gmail/adapter.ts +6 -16
- package/src/messaging/providers/gmail/client.ts +22 -0
- package/src/messaging/providers/gmail/types.ts +7 -0
- package/src/messaging/providers/slack/adapter.ts +14 -2
- package/src/messaging/providers/slack/backfill.test.ts +257 -0
- package/src/messaging/providers/slack/backfill.ts +101 -0
- package/src/messaging/providers/slack/message-metadata.test.ts +316 -0
- package/src/messaging/providers/slack/message-metadata.ts +123 -0
- package/src/messaging/providers/slack/render-transcript.test.ts +1373 -0
- package/src/messaging/providers/slack/render-transcript.ts +443 -0
- package/src/messaging/style-analyzer.ts +5 -2
- package/src/notifications/README.md +9 -5
- package/src/notifications/decision-engine.ts +3 -9
- package/src/notifications/preference-extractor.ts +2 -6
- package/src/oauth/oauth-store.ts +1 -0
- package/src/oauth/platform-connection.test.ts +47 -0
- package/src/oauth/platform-connection.ts +15 -5
- package/src/oauth/seed-providers.ts +4 -2
- package/src/permissions/approval-policy.test.ts +948 -0
- package/src/permissions/approval-policy.ts +257 -0
- package/src/permissions/bash-risk-classifier.test.ts +1208 -0
- package/src/permissions/bash-risk-classifier.ts +707 -0
- package/src/permissions/checker.ts +217 -708
- package/src/permissions/command-registry.test.ts +535 -0
- package/src/permissions/command-registry.ts +825 -0
- package/src/permissions/defaults.ts +26 -78
- package/src/permissions/file-risk-classifier.test.ts +535 -0
- package/src/permissions/file-risk-classifier.ts +274 -0
- package/src/permissions/risk-types.ts +205 -0
- package/src/permissions/secret-prompter.ts +53 -2
- package/src/permissions/skill-risk-classifier.test.ts +311 -0
- package/src/permissions/skill-risk-classifier.ts +214 -0
- package/src/permissions/trust-client.ts +52 -25
- package/src/permissions/trust-store-interface.ts +1 -6
- package/src/permissions/trust-store.ts +161 -62
- package/src/permissions/types.ts +23 -14
- package/src/permissions/web-risk-classifier.test.ts +170 -0
- package/src/permissions/web-risk-classifier.ts +89 -0
- package/src/permissions/workspace-policy.ts +1 -16
- package/src/platform/client.ts +19 -1
- package/src/prompts/persona-resolver.ts +3 -3
- package/src/prompts/system-prompt.ts +19 -20
- package/src/prompts/templates/SOUL.md +2 -2
- package/src/prompts/update-bulletin-job.ts +190 -0
- package/src/providers/__tests__/context-overflow-error.test.ts +328 -0
- package/src/providers/__tests__/provider-env-vars.test.ts +102 -0
- package/src/providers/__tests__/retry-callsite.test.ts +424 -0
- package/src/providers/anthropic/client.ts +183 -14
- package/src/providers/call-site-routing.ts +71 -0
- package/src/providers/gemini/client.ts +65 -2
- package/src/providers/managed-proxy/constants.ts +2 -1
- package/src/providers/model-catalog.ts +501 -33
- package/src/providers/model-intents.ts +4 -4
- package/src/providers/openai/chat-completions-provider.ts +57 -1
- package/src/providers/openai/responses-provider.ts +86 -9
- package/src/providers/openrouter/client.ts +76 -9
- package/src/providers/provider-env-vars.ts +56 -0
- package/src/providers/provider-send-message.ts +22 -5
- package/src/providers/ratelimit.ts +4 -0
- package/src/providers/registry.ts +19 -8
- package/src/providers/retry.ts +174 -39
- package/src/providers/speech-to-text/__tests__/resolve.test.ts +55 -0
- package/src/providers/speech-to-text/google-gemini-live-stream.ts +4 -4
- package/src/providers/speech-to-text/provider-catalog.ts +17 -0
- package/src/providers/speech-to-text/resolve.ts +7 -0
- package/src/providers/speech-to-text/xai-realtime.test.ts +578 -0
- package/src/providers/speech-to-text/xai-realtime.ts +796 -0
- package/src/providers/speech-to-text/xai.test.ts +155 -0
- package/src/providers/speech-to-text/xai.ts +97 -0
- package/src/providers/types.ts +93 -3
- package/src/runtime/AGENTS.md +2 -2
- package/src/runtime/__tests__/agent-wake.test.ts +43 -2
- package/src/runtime/__tests__/interactive-ui.test.ts +673 -0
- package/src/runtime/agent-wake.ts +63 -22
- package/src/runtime/auth/route-policy.ts +4 -0
- package/src/runtime/btw-sidechain.ts +13 -3
- package/src/runtime/channel-reply-delivery.ts +106 -2
- package/src/runtime/decision-token.ts +116 -0
- package/src/runtime/gateway-client.ts +2 -2
- package/src/runtime/http-router.ts +32 -0
- package/src/runtime/http-server.ts +52 -1
- package/src/runtime/http-types.ts +23 -1
- package/src/runtime/interactive-ui.ts +362 -0
- package/src/runtime/invite-instruction-generator.ts +2 -2
- package/src/runtime/migrations/__tests__/gcs-signed-url.test.ts +176 -0
- package/src/runtime/migrations/__tests__/vbundle-metadata-merge-integration.test.ts +390 -0
- package/src/runtime/migrations/__tests__/vbundle-metadata-merge.test.ts +221 -0
- package/src/runtime/migrations/__tests__/vbundle-streaming-importer.test.ts +1540 -0
- package/src/runtime/migrations/__tests__/vbundle-streaming-validator.test.ts +453 -0
- package/src/runtime/migrations/__tests__/vbundle-tar-stream.test.ts +222 -0
- package/src/runtime/migrations/gcs-signed-url.ts +162 -0
- package/src/runtime/migrations/vbundle-importer.ts +154 -9
- package/src/runtime/migrations/vbundle-metadata-merge.ts +124 -0
- package/src/runtime/migrations/vbundle-streaming-importer.ts +2522 -0
- package/src/runtime/migrations/vbundle-streaming-validator.ts +244 -0
- package/src/runtime/migrations/vbundle-tar-stream.ts +217 -0
- package/src/runtime/migrations/vbundle-validator.ts +15 -6
- package/src/runtime/routes/__tests__/home-feed-routes.test.ts +111 -0
- package/src/runtime/routes/__tests__/migration-import-credential-filter.test.ts +114 -75
- package/src/runtime/routes/__tests__/migration-vellum-metadata-reconcile.test.ts +246 -0
- package/src/runtime/routes/approval-prompt-ts-tracker.ts +58 -0
- package/src/runtime/routes/approval-routes.ts +12 -17
- package/src/runtime/routes/approval-strategies/guardian-callback-strategy.ts +9 -0
- package/src/runtime/routes/avatar-routes.ts +20 -4
- package/src/runtime/routes/btw-routes.ts +1 -4
- package/src/runtime/routes/conversation-management-routes.ts +20 -2
- package/src/runtime/routes/conversation-routes.ts +133 -27
- package/src/runtime/routes/debug-routes.ts +1 -1
- package/src/runtime/routes/diagnostics-routes.ts +6 -4
- package/src/runtime/routes/events-routes.ts +16 -0
- package/src/runtime/routes/guardian-approval-interception.ts +33 -3
- package/src/runtime/routes/guardian-approval-prompt.ts +13 -3
- package/src/runtime/routes/home-feed-routes.ts +120 -2
- package/src/runtime/routes/inbound-message-handler.ts +912 -2
- package/src/runtime/routes/inbound-stages/background-dispatch.test.ts +113 -2
- package/src/runtime/routes/inbound-stages/background-dispatch.ts +61 -3
- package/src/runtime/routes/inbound-stages/edit-intercept.ts +129 -6
- package/src/runtime/routes/integrations/slack/channel.ts +25 -3
- package/src/runtime/routes/llm-context-normalization.ts +23 -1
- package/src/runtime/routes/migration-routes.ts +720 -124
- package/src/runtime/routes/settings-routes.ts +4 -2
- package/src/runtime/routes/trust-rules-routes.ts +30 -14
- package/src/runtime/routes/work-items-routes.test.ts +1 -1
- package/src/runtime/routes/work-items-routes.ts +3 -2
- package/src/runtime/services/__tests__/analyze-conversation.test.ts +25 -43
- package/src/runtime/services/analyze-conversation.ts +12 -16
- package/src/runtime/skill-route-registry.ts +28 -6
- package/src/schedule/scheduler.ts +8 -0
- package/src/security/__tests__/provider-key-env-fallback.test.ts +119 -0
- package/src/security/__tests__/untrusted-content.test.ts +109 -0
- package/src/security/oauth2.ts +98 -35
- package/src/security/secure-keys.ts +7 -8
- package/src/security/token-manager.ts +27 -13
- package/src/security/untrusted-content.ts +102 -0
- package/src/skills/catalog-cache.ts +26 -7
- package/src/skills/catalog-install.ts +31 -3
- package/src/skills/skill-cache-store.ts +97 -0
- package/src/stt/__tests__/daemon-batch-transcriber.test.ts +76 -0
- package/src/stt/daemon-batch-transcriber.ts +33 -0
- package/src/stt/stt-stream-session.ts +8 -1
- package/src/stt/types.ts +5 -1
- package/src/subagent/manager.ts +41 -13
- package/src/tasks/ephemeral-permissions.ts +9 -4
- package/src/telemetry/usage-telemetry-reporter.ts +27 -5
- package/src/tools/browser/__tests__/browser-status.test.ts +45 -2
- package/src/tools/browser/browser-execution.ts +65 -38
- package/src/tools/browser/cdp-client/cdp-inspect/discovery.ts +22 -0
- package/src/tools/credentials/tool-policy.ts +39 -5
- package/src/tools/credentials/vault.ts +9 -4
- package/src/tools/executor.ts +4 -0
- package/src/tools/filesystem/write.ts +52 -0
- package/src/tools/host-terminal/host-shell.ts +45 -5
- package/src/tools/memory/register.test.ts +185 -0
- package/src/tools/memory/register.ts +3 -1
- package/src/tools/network/web-fetch.ts +20 -10
- package/src/tools/network/web-search.ts +19 -4
- package/src/tools/permission-checker.ts +36 -15
- package/src/tools/policy-context.ts +25 -8
- package/src/tools/registry.ts +55 -3
- package/src/tools/side-effects.ts +0 -11
- package/src/tools/skills/execute.ts +2 -2
- package/src/tools/skills/sandbox-runner.ts +5 -2
- package/src/tools/terminal/backends/native.ts +51 -2
- package/src/tools/terminal/safe-env.ts +3 -2
- package/src/tools/terminal/shell.ts +1 -0
- package/src/tools/tool-manifest.ts +6 -21
- package/src/tools/types.ts +12 -3
- package/src/tools/verification-control-plane-policy.ts +1 -1
- package/src/tts/__tests__/provider-adapters.test.ts +240 -13
- package/src/tts/provider-catalog.ts +18 -0
- package/src/tts/providers/index.ts +2 -0
- package/src/tts/providers/xai-provider.ts +224 -0
- package/src/tts/types.ts +46 -0
- package/src/types/tar-stream.d.ts +66 -0
- package/src/util/json.ts +17 -0
- package/src/util/platform.ts +2 -2
- package/src/util/pricing.ts +15 -5
- package/src/watcher/engine.ts +1 -1
- package/src/watcher/providers/google-calendar.ts +134 -8
- package/src/watcher/providers/outlook-calendar.ts +42 -2
- package/src/workspace/git-service.ts +23 -4
- package/src/workspace/migrations/038-unify-llm-callsite-configs.ts +516 -0
- package/src/workspace/migrations/039-drop-legacy-llm-keys.ts +171 -0
- package/src/workspace/migrations/040-seed-latency-callsite-defaults.ts +154 -0
- package/src/workspace/migrations/041-backfill-google-gmail-settings-scope.ts +57 -0
- package/src/workspace/migrations/042-fix-backfill-google-gmail-settings-scope.ts +70 -0
- package/src/workspace/migrations/043-release-notes-latex-rendering.ts +75 -0
- package/src/workspace/migrations/044-bump-stale-provider-stream-timeout.ts +51 -0
- package/src/workspace/migrations/045-release-notes-meet-avatar.ts +130 -0
- package/src/workspace/migrations/AGENTS.md +1 -1
- package/src/workspace/migrations/registry.ts +16 -0
- package/src/workspace/provider-commit-message-generator.ts +19 -38
- package/src/__tests__/gmail-archive-fallback.test.ts +0 -193
- package/src/__tests__/gmail-archive-gate.test.ts +0 -246
- package/src/__tests__/gmail-preferences.test.ts +0 -117
- package/src/__tests__/outlook-attachments.test.ts +0 -301
- package/src/__tests__/outlook-automation-tools.test.ts +0 -425
- package/src/__tests__/outlook-categories.test.ts +0 -212
- package/src/__tests__/outlook-compose-tools.test.ts +0 -325
- package/src/__tests__/outlook-declutter-tools.test.ts +0 -585
- package/src/__tests__/outlook-follow-up.test.ts +0 -196
- package/src/__tests__/outlook-trash.test.ts +0 -77
- package/src/__tests__/outlook-unsubscribe.test.ts +0 -279
- package/src/__tests__/update-bulletin-format.test.ts +0 -181
- package/src/__tests__/update-bulletin-state.test.ts +0 -135
- package/src/__tests__/update-bulletin.test.ts +0 -478
- package/src/__tests__/update-template-contract.test.ts +0 -29
- package/src/cli/commands/doctor.ts +0 -341
- package/src/config/bundled-skills/browser/SKILL.md +0 -88
- package/src/config/bundled-skills/browser/TOOLS.json +0 -516
- package/src/config/bundled-skills/browser/tools/browser-attach.ts +0 -12
- package/src/config/bundled-skills/browser/tools/browser-click.ts +0 -12
- package/src/config/bundled-skills/browser/tools/browser-close.ts +0 -12
- package/src/config/bundled-skills/browser/tools/browser-detach.ts +0 -12
- package/src/config/bundled-skills/browser/tools/browser-extract.ts +0 -12
- package/src/config/bundled-skills/browser/tools/browser-fill-credential.ts +0 -12
- package/src/config/bundled-skills/browser/tools/browser-hover.ts +0 -12
- package/src/config/bundled-skills/browser/tools/browser-navigate.ts +0 -12
- package/src/config/bundled-skills/browser/tools/browser-press-key.ts +0 -12
- package/src/config/bundled-skills/browser/tools/browser-screenshot.ts +0 -12
- package/src/config/bundled-skills/browser/tools/browser-scroll.ts +0 -12
- package/src/config/bundled-skills/browser/tools/browser-select-option.ts +0 -12
- package/src/config/bundled-skills/browser/tools/browser-snapshot.ts +0 -12
- package/src/config/bundled-skills/browser/tools/browser-status.ts +0 -12
- package/src/config/bundled-skills/browser/tools/browser-type.ts +0 -12
- package/src/config/bundled-skills/browser/tools/browser-wait-for-download.ts +0 -49
- package/src/config/bundled-skills/browser/tools/browser-wait-for.ts +0 -12
- package/src/config/bundled-skills/chatgpt-import/SKILL.md +0 -27
- package/src/config/bundled-skills/chatgpt-import/TOOLS.json +0 -27
- package/src/config/bundled-skills/chatgpt-import/tools/chatgpt-import.ts +0 -378
- package/src/config/bundled-skills/gmail/SKILL.md +0 -221
- package/src/config/bundled-skills/gmail/TOOLS.json +0 -588
- package/src/config/bundled-skills/gmail/tools/gmail-archive.ts +0 -256
- package/src/config/bundled-skills/gmail/tools/gmail-attachments.ts +0 -112
- package/src/config/bundled-skills/gmail/tools/gmail-draft.ts +0 -44
- package/src/config/bundled-skills/gmail/tools/gmail-filters.ts +0 -81
- package/src/config/bundled-skills/gmail/tools/gmail-follow-up.ts +0 -108
- package/src/config/bundled-skills/gmail/tools/gmail-forward.ts +0 -146
- package/src/config/bundled-skills/gmail/tools/gmail-label.ts +0 -53
- package/src/config/bundled-skills/gmail/tools/gmail-outreach-scan.ts +0 -347
- package/src/config/bundled-skills/gmail/tools/gmail-preferences-tool.ts +0 -59
- package/src/config/bundled-skills/gmail/tools/gmail-preferences.ts +0 -82
- package/src/config/bundled-skills/gmail/tools/gmail-send-draft.ts +0 -26
- package/src/config/bundled-skills/gmail/tools/gmail-sender-digest.ts +0 -347
- package/src/config/bundled-skills/gmail/tools/gmail-trash.ts +0 -29
- package/src/config/bundled-skills/gmail/tools/gmail-unsubscribe.ts +0 -122
- package/src/config/bundled-skills/gmail/tools/gmail-vacation.ts +0 -67
- package/src/config/bundled-skills/gmail/tools/scan-result-store.ts +0 -100
- package/src/config/bundled-skills/gmail/tools/shared.ts +0 -47
- package/src/config/bundled-skills/google-calendar/SKILL.md +0 -51
- package/src/config/bundled-skills/google-calendar/TOOLS.json +0 -226
- package/src/config/bundled-skills/google-calendar/calendar-client.ts +0 -223
- package/src/config/bundled-skills/google-calendar/tools/calendar-check-availability.ts +0 -27
- package/src/config/bundled-skills/google-calendar/tools/calendar-create-event.ts +0 -48
- package/src/config/bundled-skills/google-calendar/tools/calendar-get-event.ts +0 -19
- package/src/config/bundled-skills/google-calendar/tools/calendar-list-events.ts +0 -36
- package/src/config/bundled-skills/google-calendar/tools/calendar-rsvp.ts +0 -58
- package/src/config/bundled-skills/google-calendar/tools/shared.ts +0 -17
- package/src/config/bundled-skills/google-calendar/types.ts +0 -97
- package/src/config/bundled-skills/outlook/SKILL.md +0 -196
- package/src/config/bundled-skills/outlook/TOOLS.json +0 -530
- package/src/config/bundled-skills/outlook/tools/outlook-attachments.ts +0 -85
- package/src/config/bundled-skills/outlook/tools/outlook-categories.ts +0 -77
- package/src/config/bundled-skills/outlook/tools/outlook-draft.ts +0 -84
- package/src/config/bundled-skills/outlook/tools/outlook-follow-up.ts +0 -94
- package/src/config/bundled-skills/outlook/tools/outlook-forward.ts +0 -49
- package/src/config/bundled-skills/outlook/tools/outlook-outreach-scan.ts +0 -237
- package/src/config/bundled-skills/outlook/tools/outlook-rules.ts +0 -161
- package/src/config/bundled-skills/outlook/tools/outlook-send-draft.ts +0 -32
- package/src/config/bundled-skills/outlook/tools/outlook-sender-digest.ts +0 -272
- package/src/config/bundled-skills/outlook/tools/outlook-trash.ts +0 -29
- package/src/config/bundled-skills/outlook/tools/outlook-unsubscribe.ts +0 -129
- package/src/config/bundled-skills/outlook/tools/outlook-vacation.ts +0 -87
- package/src/config/bundled-skills/outlook/tools/shared.ts +0 -20
- package/src/config/bundled-skills/outlook-calendar/SKILL.md +0 -51
- package/src/config/bundled-skills/outlook-calendar/TOOLS.json +0 -221
- package/src/config/bundled-skills/outlook-calendar/calendar-client.ts +0 -252
- package/src/config/bundled-skills/outlook-calendar/tools/outlook-calendar-check-availability.ts +0 -53
- package/src/config/bundled-skills/outlook-calendar/tools/outlook-calendar-create-event.ts +0 -74
- package/src/config/bundled-skills/outlook-calendar/tools/outlook-calendar-get-event.ts +0 -18
- package/src/config/bundled-skills/outlook-calendar/tools/outlook-calendar-list-events.ts +0 -46
- package/src/config/bundled-skills/outlook-calendar/tools/outlook-calendar-rsvp.ts +0 -36
- package/src/config/bundled-skills/outlook-calendar/tools/shared.ts +0 -17
- package/src/config/bundled-skills/outlook-calendar/types.ts +0 -120
- package/src/config/bundled-skills/slack/SKILL.md +0 -108
- package/src/config/bundled-skills/tasks/SKILL.md +0 -37
- package/src/config/bundled-skills/tasks/TOOLS.json +0 -353
- package/src/config/bundled-skills/tasks/icon.svg +0 -34
- package/src/config/bundled-skills/tasks/tools/task-delete.ts +0 -12
- package/src/config/bundled-skills/tasks/tools/task-list-add.ts +0 -12
- package/src/config/bundled-skills/tasks/tools/task-list-remove.ts +0 -12
- package/src/config/bundled-skills/tasks/tools/task-list-show.ts +0 -12
- package/src/config/bundled-skills/tasks/tools/task-list-update.ts +0 -12
- package/src/config/bundled-skills/tasks/tools/task-list.ts +0 -12
- package/src/config/bundled-skills/tasks/tools/task-queue-run.ts +0 -12
- package/src/config/bundled-skills/tasks/tools/task-run.ts +0 -12
- package/src/config/bundled-skills/tasks/tools/task-save.ts +0 -12
- package/src/config/bundled-skills/watcher/SKILL.md +0 -31
- package/src/config/bundled-skills/watcher/TOOLS.json +0 -167
- package/src/config/bundled-skills/watcher/tools/watcher-create.ts +0 -12
- package/src/config/bundled-skills/watcher/tools/watcher-delete.ts +0 -12
- package/src/config/bundled-skills/watcher/tools/watcher-digest.ts +0 -12
- package/src/config/bundled-skills/watcher/tools/watcher-list.ts +0 -12
- package/src/config/bundled-skills/watcher/tools/watcher-update.ts +0 -12
- package/src/prompts/templates/UPDATES.md +0 -50
- package/src/prompts/update-bulletin-format.ts +0 -85
- package/src/prompts/update-bulletin-state.ts +0 -58
- package/src/prompts/update-bulletin-template-path.ts +0 -13
- package/src/prompts/update-bulletin.ts +0 -139
- package/src/shared/provider-env-vars.ts +0 -19
- package/src/tools/watcher/create.ts +0 -86
- package/src/tools/watcher/delete.ts +0 -36
- package/src/tools/watcher/digest.ts +0 -54
- package/src/tools/watcher/list.ts +0 -83
- package/src/tools/watcher/update.ts +0 -71
|
@@ -0,0 +1,684 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Integration tests for the JSON `{url}` body on POST /v1/migrations/import.
|
|
3
|
+
*
|
|
4
|
+
* Covered:
|
|
5
|
+
* - Happy path: a local http server serves a valid .vbundle, the handler
|
|
6
|
+
* fetches it, streams it through `streamCommitImport`, and returns the
|
|
7
|
+
* same success-report shape the raw-bytes path returns.
|
|
8
|
+
* - GCS 500: upstream server returns 500 → handler returns 502 with
|
|
9
|
+
* `{ reason: "fetch_failed", upstream_status: 500 }`.
|
|
10
|
+
* - Malformed body: `{}` and `{ "url": "" }` → 400.
|
|
11
|
+
* - Invalid GCS URL: a real https://evil.com URL is rejected with the
|
|
12
|
+
* redacted `Invalid URL: host` message; the raw URL is not echoed back
|
|
13
|
+
* in the response body.
|
|
14
|
+
* - Memory ceiling: a 100 MB fixture streams through with peak RSS delta
|
|
15
|
+
* bounded at ~128 MB. The ceiling is wider than the direct
|
|
16
|
+
* `streamCommitImport` ceiling because the URL handler stacks a Node
|
|
17
|
+
* HTTP response body + gunzip state + tar-stream state on top of the
|
|
18
|
+
* importer's per-entry working set.
|
|
19
|
+
*
|
|
20
|
+
* The raw-bytes ingress path is exercised by a separate test file,
|
|
21
|
+
* `migration-import-commit-http.test.ts`.
|
|
22
|
+
*/
|
|
23
|
+
|
|
24
|
+
import {
|
|
25
|
+
createReadStream,
|
|
26
|
+
existsSync,
|
|
27
|
+
mkdirSync,
|
|
28
|
+
mkdtempSync,
|
|
29
|
+
realpathSync,
|
|
30
|
+
rmSync,
|
|
31
|
+
writeFileSync,
|
|
32
|
+
} from "node:fs";
|
|
33
|
+
import { createServer, type Server } from "node:http";
|
|
34
|
+
import { tmpdir } from "node:os";
|
|
35
|
+
import { join } from "node:path";
|
|
36
|
+
import { Database } from "bun:sqlite";
|
|
37
|
+
import {
|
|
38
|
+
afterAll,
|
|
39
|
+
afterEach,
|
|
40
|
+
beforeAll,
|
|
41
|
+
beforeEach,
|
|
42
|
+
describe,
|
|
43
|
+
expect,
|
|
44
|
+
mock,
|
|
45
|
+
test,
|
|
46
|
+
} from "bun:test";
|
|
47
|
+
|
|
48
|
+
// ---------------------------------------------------------------------------
|
|
49
|
+
// Test isolation: per-file workspace root.
|
|
50
|
+
//
|
|
51
|
+
// The shared test preload points VELLUM_WORKSPACE_DIR at a tmp dir. The
|
|
52
|
+
// streaming importer does an atomic rename of the workspace dir itself,
|
|
53
|
+
// which implicitly invalidates the shared tmp dir for subsequent tests in
|
|
54
|
+
// the same file. Each test below creates its own isolated workspace and
|
|
55
|
+
// re-points getWorkspaceDir() at it via the env var before invoking the
|
|
56
|
+
// handler.
|
|
57
|
+
// ---------------------------------------------------------------------------
|
|
58
|
+
|
|
59
|
+
const originalWorkspaceDir = process.env.VELLUM_WORKSPACE_DIR;
|
|
60
|
+
|
|
61
|
+
function freshWorkspaceRoot(): string {
|
|
62
|
+
const parent = realpathSync(
|
|
63
|
+
mkdtempSync(join(tmpdir(), "migration-import-from-url-")),
|
|
64
|
+
);
|
|
65
|
+
// The streaming importer renames workspaceDir itself, so put the
|
|
66
|
+
// workspace inside a parent dir we own.
|
|
67
|
+
const workspaceDir = join(parent, "workspace");
|
|
68
|
+
mkdirSync(workspaceDir, { recursive: true });
|
|
69
|
+
return workspaceDir;
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
function setWorkspaceDir(dir: string): void {
|
|
73
|
+
process.env.VELLUM_WORKSPACE_DIR = dir;
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
// ---------------------------------------------------------------------------
|
|
77
|
+
// Mocks (mirrors migration-import-commit-http.test.ts)
|
|
78
|
+
// ---------------------------------------------------------------------------
|
|
79
|
+
|
|
80
|
+
mock.module("../util/logger.js", () => ({
|
|
81
|
+
getLogger: () =>
|
|
82
|
+
new Proxy({} as Record<string, unknown>, {
|
|
83
|
+
get: () => () => {},
|
|
84
|
+
}),
|
|
85
|
+
}));
|
|
86
|
+
|
|
87
|
+
mock.module("../permissions/trust-store.js", () => ({
|
|
88
|
+
getAllRules: () => [],
|
|
89
|
+
isStarterBundleAccepted: () => false,
|
|
90
|
+
clearCache: () => {},
|
|
91
|
+
}));
|
|
92
|
+
|
|
93
|
+
mock.module("../config/loader.js", () => ({
|
|
94
|
+
getConfig: () => ({
|
|
95
|
+
ui: {},
|
|
96
|
+
model: "test",
|
|
97
|
+
provider: "test",
|
|
98
|
+
memory: { enabled: false },
|
|
99
|
+
rateLimit: { maxRequestsPerMinute: 0 },
|
|
100
|
+
secretDetection: { enabled: false },
|
|
101
|
+
}),
|
|
102
|
+
invalidateConfigCache: () => {},
|
|
103
|
+
}));
|
|
104
|
+
|
|
105
|
+
mock.module("../config/env.js", () => ({
|
|
106
|
+
isHttpAuthDisabled: () => true,
|
|
107
|
+
hasUngatedHttpAuthDisabled: () => false,
|
|
108
|
+
getGatewayInternalBaseUrl: () => "http://127.0.0.1:7830",
|
|
109
|
+
getGatewayPort: () => 7830,
|
|
110
|
+
getRuntimeHttpPort: () => 7821,
|
|
111
|
+
getRuntimeHttpHost: () => "127.0.0.1",
|
|
112
|
+
getRuntimeGatewayOriginSecret: () => undefined,
|
|
113
|
+
getIngressPublicBaseUrl: () => undefined,
|
|
114
|
+
setIngressPublicBaseUrl: () => {},
|
|
115
|
+
}));
|
|
116
|
+
|
|
117
|
+
// ---------------------------------------------------------------------------
|
|
118
|
+
// Imports (after mocks so module-level code picks up the stubs)
|
|
119
|
+
// ---------------------------------------------------------------------------
|
|
120
|
+
|
|
121
|
+
import { resetDb } from "../memory/db-connection.js";
|
|
122
|
+
import { buildVBundle } from "../runtime/migrations/vbundle-builder.js";
|
|
123
|
+
import {
|
|
124
|
+
_setUrlImportValidatorOptionsForTests,
|
|
125
|
+
handleMigrationImport,
|
|
126
|
+
} from "../runtime/routes/migration-routes.js";
|
|
127
|
+
|
|
128
|
+
// ---------------------------------------------------------------------------
|
|
129
|
+
// Local http fixture server
|
|
130
|
+
// ---------------------------------------------------------------------------
|
|
131
|
+
|
|
132
|
+
interface FixtureServer {
|
|
133
|
+
server: Server;
|
|
134
|
+
port: number;
|
|
135
|
+
close: () => Promise<void>;
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
async function startFixtureServer(
|
|
139
|
+
handler: (
|
|
140
|
+
req: import("node:http").IncomingMessage,
|
|
141
|
+
res: import("node:http").ServerResponse,
|
|
142
|
+
) => void,
|
|
143
|
+
): Promise<FixtureServer> {
|
|
144
|
+
const server = createServer(handler);
|
|
145
|
+
await new Promise<void>((resolve) => server.listen(0, "127.0.0.1", resolve));
|
|
146
|
+
const address = server.address();
|
|
147
|
+
if (!address || typeof address === "string") {
|
|
148
|
+
throw new Error("fixture server bound to unexpected address");
|
|
149
|
+
}
|
|
150
|
+
const port = address.port;
|
|
151
|
+
return {
|
|
152
|
+
server,
|
|
153
|
+
port,
|
|
154
|
+
close: async () => {
|
|
155
|
+
await new Promise<void>((resolve) => server.close(() => resolve()));
|
|
156
|
+
},
|
|
157
|
+
};
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
function makeFakeSignedUrl(port: number): string {
|
|
161
|
+
// `validateGcsSignedUrl` requires a signature query param; pin a dummy.
|
|
162
|
+
return `http://127.0.0.1:${port}/bundle?X-Goog-Signature=fake`;
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
// ---------------------------------------------------------------------------
|
|
166
|
+
// Fixture helpers
|
|
167
|
+
// ---------------------------------------------------------------------------
|
|
168
|
+
|
|
169
|
+
function makeSmallValidBundlePath(parent: string): string {
|
|
170
|
+
const { archive } = buildVBundle({
|
|
171
|
+
files: [
|
|
172
|
+
{
|
|
173
|
+
path: "workspace/data/db/assistant.db",
|
|
174
|
+
data: new TextEncoder().encode("SQLite format 3\0"),
|
|
175
|
+
},
|
|
176
|
+
{
|
|
177
|
+
path: "workspace/config.json",
|
|
178
|
+
data: new TextEncoder().encode(
|
|
179
|
+
JSON.stringify({ provider: "anthropic", model: "test-model" }),
|
|
180
|
+
),
|
|
181
|
+
},
|
|
182
|
+
],
|
|
183
|
+
});
|
|
184
|
+
const bundlePath = join(parent, "fixture-small.vbundle");
|
|
185
|
+
writeFileSync(bundlePath, archive);
|
|
186
|
+
return bundlePath;
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
// ---------------------------------------------------------------------------
|
|
190
|
+
// Global test-only allowlist: widen the URL validator to accept 127.0.0.1.
|
|
191
|
+
// ---------------------------------------------------------------------------
|
|
192
|
+
|
|
193
|
+
beforeAll(() => {
|
|
194
|
+
_setUrlImportValidatorOptionsForTests({
|
|
195
|
+
allowedHosts: ["127.0.0.1", "storage.googleapis.com"],
|
|
196
|
+
});
|
|
197
|
+
});
|
|
198
|
+
|
|
199
|
+
afterAll(() => {
|
|
200
|
+
_setUrlImportValidatorOptionsForTests(undefined);
|
|
201
|
+
if (originalWorkspaceDir !== undefined) {
|
|
202
|
+
process.env.VELLUM_WORKSPACE_DIR = originalWorkspaceDir;
|
|
203
|
+
}
|
|
204
|
+
});
|
|
205
|
+
|
|
206
|
+
// Each test gets its own workspace dir so the streaming importer's atomic
|
|
207
|
+
// swap doesn't leak state across tests.
|
|
208
|
+
let testWorkspaceRoot: string;
|
|
209
|
+
let testParent: string;
|
|
210
|
+
|
|
211
|
+
beforeEach(() => {
|
|
212
|
+
testWorkspaceRoot = freshWorkspaceRoot();
|
|
213
|
+
testParent = join(testWorkspaceRoot, "..");
|
|
214
|
+
setWorkspaceDir(testWorkspaceRoot);
|
|
215
|
+
});
|
|
216
|
+
|
|
217
|
+
afterEach(() => {
|
|
218
|
+
try {
|
|
219
|
+
rmSync(testParent, { recursive: true, force: true });
|
|
220
|
+
} catch {
|
|
221
|
+
/* best effort */
|
|
222
|
+
}
|
|
223
|
+
});
|
|
224
|
+
|
|
225
|
+
// ---------------------------------------------------------------------------
|
|
226
|
+
// Response shape types
|
|
227
|
+
// ---------------------------------------------------------------------------
|
|
228
|
+
|
|
229
|
+
interface ImportCommitResponse {
|
|
230
|
+
success: boolean;
|
|
231
|
+
summary: {
|
|
232
|
+
total_files: number;
|
|
233
|
+
files_created: number;
|
|
234
|
+
files_overwritten: number;
|
|
235
|
+
files_skipped: number;
|
|
236
|
+
backups_created: number;
|
|
237
|
+
};
|
|
238
|
+
files: Array<{
|
|
239
|
+
path: string;
|
|
240
|
+
disk_path: string;
|
|
241
|
+
action: string;
|
|
242
|
+
size: number;
|
|
243
|
+
sha256: string;
|
|
244
|
+
backup_path: string | null;
|
|
245
|
+
}>;
|
|
246
|
+
manifest: Record<string, unknown>;
|
|
247
|
+
warnings: string[];
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
interface FetchFailedResponse {
|
|
251
|
+
success: false;
|
|
252
|
+
reason: "fetch_failed";
|
|
253
|
+
upstream_status?: number;
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
interface BadRequestResponse {
|
|
257
|
+
error: { code: string; message: string };
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
// ---------------------------------------------------------------------------
|
|
261
|
+
// Tests: JSON URL body
|
|
262
|
+
// ---------------------------------------------------------------------------
|
|
263
|
+
|
|
264
|
+
describe("handleMigrationImport — JSON {url} body", () => {
|
|
265
|
+
test("happy path: fetches fixture bundle from local http server and imports", async () => {
|
|
266
|
+
const bundlePath = makeSmallValidBundlePath(testParent);
|
|
267
|
+
|
|
268
|
+
const fixture = await startFixtureServer((req, res) => {
|
|
269
|
+
// Prove the server itself streams the response body.
|
|
270
|
+
res.writeHead(200, { "Content-Type": "application/octet-stream" });
|
|
271
|
+
createReadStream(bundlePath).pipe(res);
|
|
272
|
+
});
|
|
273
|
+
|
|
274
|
+
try {
|
|
275
|
+
const req = new Request("http://localhost/v1/migrations/import", {
|
|
276
|
+
method: "POST",
|
|
277
|
+
headers: { "Content-Type": "application/json" },
|
|
278
|
+
body: JSON.stringify({ url: makeFakeSignedUrl(fixture.port) }),
|
|
279
|
+
});
|
|
280
|
+
|
|
281
|
+
const res = await handleMigrationImport(req);
|
|
282
|
+
const body = (await res.json()) as ImportCommitResponse;
|
|
283
|
+
|
|
284
|
+
expect(res.status).toBe(200);
|
|
285
|
+
expect(body.success).toBe(true);
|
|
286
|
+
expect(body.summary).toBeDefined();
|
|
287
|
+
expect(body.summary.total_files).toBeGreaterThan(0);
|
|
288
|
+
expect(body.files.length).toBeGreaterThan(0);
|
|
289
|
+
expect(body.manifest).toBeDefined();
|
|
290
|
+
|
|
291
|
+
// Workspace was swapped into place and contains the fixture files.
|
|
292
|
+
expect(
|
|
293
|
+
existsSync(join(testWorkspaceRoot, "data", "db", "assistant.db")),
|
|
294
|
+
).toBe(true);
|
|
295
|
+
expect(existsSync(join(testWorkspaceRoot, "config.json"))).toBe(true);
|
|
296
|
+
} finally {
|
|
297
|
+
await fixture.close();
|
|
298
|
+
}
|
|
299
|
+
});
|
|
300
|
+
|
|
301
|
+
test("upstream 500 returns 502 with reason: fetch_failed", async () => {
|
|
302
|
+
const fixture = await startFixtureServer((_req, res) => {
|
|
303
|
+
res.writeHead(500);
|
|
304
|
+
res.end("oh no");
|
|
305
|
+
});
|
|
306
|
+
|
|
307
|
+
try {
|
|
308
|
+
const req = new Request("http://localhost/v1/migrations/import", {
|
|
309
|
+
method: "POST",
|
|
310
|
+
headers: { "Content-Type": "application/json" },
|
|
311
|
+
body: JSON.stringify({ url: makeFakeSignedUrl(fixture.port) }),
|
|
312
|
+
});
|
|
313
|
+
|
|
314
|
+
const res = await handleMigrationImport(req);
|
|
315
|
+
const body = (await res.json()) as FetchFailedResponse;
|
|
316
|
+
|
|
317
|
+
expect(res.status).toBe(502);
|
|
318
|
+
expect(body.success).toBe(false);
|
|
319
|
+
expect(body.reason).toBe("fetch_failed");
|
|
320
|
+
expect(body.upstream_status).toBe(500);
|
|
321
|
+
} finally {
|
|
322
|
+
await fixture.close();
|
|
323
|
+
}
|
|
324
|
+
});
|
|
325
|
+
|
|
326
|
+
test('malformed body: {"url": ""} returns 400', async () => {
|
|
327
|
+
const req = new Request("http://localhost/v1/migrations/import", {
|
|
328
|
+
method: "POST",
|
|
329
|
+
headers: { "Content-Type": "application/json" },
|
|
330
|
+
body: JSON.stringify({ url: "" }),
|
|
331
|
+
});
|
|
332
|
+
|
|
333
|
+
const res = await handleMigrationImport(req);
|
|
334
|
+
const body = (await res.json()) as BadRequestResponse;
|
|
335
|
+
|
|
336
|
+
expect(res.status).toBe(400);
|
|
337
|
+
expect(body.error.code).toBe("BAD_REQUEST");
|
|
338
|
+
});
|
|
339
|
+
|
|
340
|
+
test("missing url key: {} returns 400", async () => {
|
|
341
|
+
const req = new Request("http://localhost/v1/migrations/import", {
|
|
342
|
+
method: "POST",
|
|
343
|
+
headers: { "Content-Type": "application/json" },
|
|
344
|
+
body: JSON.stringify({}),
|
|
345
|
+
});
|
|
346
|
+
|
|
347
|
+
const res = await handleMigrationImport(req);
|
|
348
|
+
const body = (await res.json()) as BadRequestResponse;
|
|
349
|
+
|
|
350
|
+
expect(res.status).toBe(400);
|
|
351
|
+
expect(body.error.code).toBe("BAD_REQUEST");
|
|
352
|
+
});
|
|
353
|
+
|
|
354
|
+
test("unparseable JSON body returns 400", async () => {
|
|
355
|
+
const req = new Request("http://localhost/v1/migrations/import", {
|
|
356
|
+
method: "POST",
|
|
357
|
+
headers: { "Content-Type": "application/json" },
|
|
358
|
+
body: "{not-json",
|
|
359
|
+
});
|
|
360
|
+
|
|
361
|
+
const res = await handleMigrationImport(req);
|
|
362
|
+
const body = (await res.json()) as BadRequestResponse;
|
|
363
|
+
|
|
364
|
+
expect(res.status).toBe(400);
|
|
365
|
+
expect(body.error.code).toBe("BAD_REQUEST");
|
|
366
|
+
});
|
|
367
|
+
|
|
368
|
+
test("invalid GCS URL returns 400 and does not leak the URL", async () => {
|
|
369
|
+
const rawUrl = "https://evil.example.com/bucket/obj?X-Goog-Signature=fake";
|
|
370
|
+
const req = new Request("http://localhost/v1/migrations/import", {
|
|
371
|
+
method: "POST",
|
|
372
|
+
headers: { "Content-Type": "application/json" },
|
|
373
|
+
body: JSON.stringify({ url: rawUrl }),
|
|
374
|
+
});
|
|
375
|
+
|
|
376
|
+
const res = await handleMigrationImport(req);
|
|
377
|
+
const rawBody = await res.text();
|
|
378
|
+
|
|
379
|
+
expect(res.status).toBe(400);
|
|
380
|
+
|
|
381
|
+
const parsed = JSON.parse(rawBody) as BadRequestResponse;
|
|
382
|
+
expect(parsed.error.code).toBe("BAD_REQUEST");
|
|
383
|
+
// The message should carry the redacted reason, not the URL itself.
|
|
384
|
+
expect(parsed.error.message.toLowerCase()).toContain("invalid url");
|
|
385
|
+
// Defense-in-depth: the raw URL must not appear anywhere in the response.
|
|
386
|
+
expect(rawBody).not.toContain("evil.example.com");
|
|
387
|
+
expect(rawBody).not.toContain("X-Goog-Signature=fake");
|
|
388
|
+
});
|
|
389
|
+
|
|
390
|
+
test("non-https scheme on default allowlist is rejected", async () => {
|
|
391
|
+
// Temporarily reset to the strict default allowlist so this test
|
|
392
|
+
// exercises the production validator configuration.
|
|
393
|
+
_setUrlImportValidatorOptionsForTests(undefined);
|
|
394
|
+
try {
|
|
395
|
+
const req = new Request("http://localhost/v1/migrations/import", {
|
|
396
|
+
method: "POST",
|
|
397
|
+
headers: { "Content-Type": "application/json" },
|
|
398
|
+
body: JSON.stringify({
|
|
399
|
+
url: "http://storage.googleapis.com/b/o?X-Goog-Signature=x",
|
|
400
|
+
}),
|
|
401
|
+
});
|
|
402
|
+
|
|
403
|
+
const res = await handleMigrationImport(req);
|
|
404
|
+
const body = (await res.json()) as BadRequestResponse;
|
|
405
|
+
|
|
406
|
+
expect(res.status).toBe(400);
|
|
407
|
+
expect(body.error.code).toBe("BAD_REQUEST");
|
|
408
|
+
expect(body.error.message).toContain("scheme");
|
|
409
|
+
} finally {
|
|
410
|
+
_setUrlImportValidatorOptionsForTests({
|
|
411
|
+
allowedHosts: ["127.0.0.1", "storage.googleapis.com"],
|
|
412
|
+
});
|
|
413
|
+
}
|
|
414
|
+
});
|
|
415
|
+
});
|
|
416
|
+
|
|
417
|
+
// ---------------------------------------------------------------------------
|
|
418
|
+
// Memory ceiling — streams a 100 MB fixture through and caps RSS.
|
|
419
|
+
// ---------------------------------------------------------------------------
|
|
420
|
+
|
|
421
|
+
function writeLargeFixtureToDisk(archivePath: string): void {
|
|
422
|
+
const CHUNK = 25 * 1024 * 1024;
|
|
423
|
+
const files = [0, 1, 2, 3].map((i) => ({
|
|
424
|
+
path: `workspace/big-${i}.bin`,
|
|
425
|
+
data: new Uint8Array(CHUNK).fill(0x41 + i),
|
|
426
|
+
}));
|
|
427
|
+
const { archive } = buildVBundle({ files });
|
|
428
|
+
writeFileSync(archivePath, archive);
|
|
429
|
+
}
|
|
430
|
+
|
|
431
|
+
describe("handleMigrationImport — URL body memory ceiling", () => {
|
|
432
|
+
test("100 MB fixture streams in without pushing RSS past ~128 MB over baseline", async () => {
|
|
433
|
+
const archivePath = join(testParent, "fixture-large.vbundle");
|
|
434
|
+
writeLargeFixtureToDisk(archivePath);
|
|
435
|
+
|
|
436
|
+
const fixture = await startFixtureServer((_req, res) => {
|
|
437
|
+
res.writeHead(200, { "Content-Type": "application/octet-stream" });
|
|
438
|
+
createReadStream(archivePath).pipe(res);
|
|
439
|
+
});
|
|
440
|
+
|
|
441
|
+
try {
|
|
442
|
+
// Force a GC opportunity before measuring baseline so stale fixture
|
|
443
|
+
// buffers don't count against the importer's budget. Bun exposes
|
|
444
|
+
// `Bun.gc` but not process.gc; gate on whether it exists.
|
|
445
|
+
const maybeBunGc = (
|
|
446
|
+
globalThis as { Bun?: { gc?: (sync?: boolean) => void } }
|
|
447
|
+
).Bun?.gc;
|
|
448
|
+
if (typeof maybeBunGc === "function") maybeBunGc(true);
|
|
449
|
+
|
|
450
|
+
const baselineRss = process.memoryUsage().rss;
|
|
451
|
+
|
|
452
|
+
const req = new Request("http://localhost/v1/migrations/import", {
|
|
453
|
+
method: "POST",
|
|
454
|
+
headers: { "Content-Type": "application/json" },
|
|
455
|
+
body: JSON.stringify({ url: makeFakeSignedUrl(fixture.port) }),
|
|
456
|
+
});
|
|
457
|
+
|
|
458
|
+
const res = await handleMigrationImport(req);
|
|
459
|
+
const body = (await res.json()) as ImportCommitResponse;
|
|
460
|
+
|
|
461
|
+
const peakRss = process.memoryUsage().rss;
|
|
462
|
+
|
|
463
|
+
expect(res.status).toBe(200);
|
|
464
|
+
expect(body.success).toBe(true);
|
|
465
|
+
|
|
466
|
+
// 128 MB ceiling: the URL handler pipes the HTTP response body
|
|
467
|
+
// through gunzip + tar-stream on top of the streaming importer's
|
|
468
|
+
// per-entry working set. The extra framing state is bigger than
|
|
469
|
+
// raw-stream importing (which fits in ~64 MB), so 128 MB keeps
|
|
470
|
+
// enough headroom to detect full-bundle buffering without
|
|
471
|
+
// flapping on normal streaming-mode overhead.
|
|
472
|
+
const delta = peakRss - baselineRss;
|
|
473
|
+
expect(delta).toBeLessThan(128 * 1024 * 1024);
|
|
474
|
+
} finally {
|
|
475
|
+
await fixture.close();
|
|
476
|
+
}
|
|
477
|
+
}, 90_000);
|
|
478
|
+
});
|
|
479
|
+
|
|
480
|
+
// ---------------------------------------------------------------------------
|
|
481
|
+
// Gap A regression: no-swap success path must not append a stale
|
|
482
|
+
// "newer migration" warning sourced from the live DB. The warning would
|
|
483
|
+
// wrongly attribute live-DB state to the imported bundle when no bundle
|
|
484
|
+
// files actually land on disk (e.g. credentials-only bundle).
|
|
485
|
+
// ---------------------------------------------------------------------------
|
|
486
|
+
|
|
487
|
+
describe("handleMigrationImport — no-swap path omits newer-migration warning", () => {
|
|
488
|
+
test("credentials-only bundle does not inherit live-DB migration warnings", async () => {
|
|
489
|
+
// Seed the live workspace DB with a migration_* checkpoint that's NOT
|
|
490
|
+
// in the registry. validateMigrationState treats this as a "newer
|
|
491
|
+
// version" and would otherwise push a warning into the report. With
|
|
492
|
+
// the gate in appendNewerMigrationWarningsIfAny the warning must be
|
|
493
|
+
// suppressed when the import didn't modify the workspace.
|
|
494
|
+
const dbDir = join(testWorkspaceRoot, "data", "db");
|
|
495
|
+
mkdirSync(dbDir, { recursive: true });
|
|
496
|
+
const dbPath = join(dbDir, "assistant.db");
|
|
497
|
+
const seed = new Database(dbPath);
|
|
498
|
+
try {
|
|
499
|
+
seed.exec(`
|
|
500
|
+
CREATE TABLE memory_checkpoints (
|
|
501
|
+
key TEXT PRIMARY KEY,
|
|
502
|
+
value TEXT NOT NULL,
|
|
503
|
+
updated_at INTEGER NOT NULL
|
|
504
|
+
);
|
|
505
|
+
`);
|
|
506
|
+
seed
|
|
507
|
+
.query(
|
|
508
|
+
`INSERT INTO memory_checkpoints (key, value, updated_at) VALUES (?, ?, ?)`,
|
|
509
|
+
)
|
|
510
|
+
.run("migration_from_the_future", "1", Date.now());
|
|
511
|
+
} finally {
|
|
512
|
+
seed.close();
|
|
513
|
+
}
|
|
514
|
+
|
|
515
|
+
// Drop any cached Drizzle singleton so getDb() re-opens from the
|
|
516
|
+
// seeded path above when the handler calls it post-import.
|
|
517
|
+
resetDb();
|
|
518
|
+
|
|
519
|
+
// All-`vellum:*` credentials bundle: the streaming importer returns
|
|
520
|
+
// ok=true with zero files_created/overwritten (no-swap success),
|
|
521
|
+
// and the credential-import callback filters every entry as a
|
|
522
|
+
// platform credential so CES is never invoked.
|
|
523
|
+
const { archive } = buildVBundle({
|
|
524
|
+
files: [
|
|
525
|
+
{
|
|
526
|
+
path: "credentials/vellum:device-id",
|
|
527
|
+
data: new TextEncoder().encode("test-device-id"),
|
|
528
|
+
},
|
|
529
|
+
],
|
|
530
|
+
});
|
|
531
|
+
const bundlePath = join(testParent, "fixture-creds-only.vbundle");
|
|
532
|
+
writeFileSync(bundlePath, archive);
|
|
533
|
+
|
|
534
|
+
const fixture = await startFixtureServer((_req, res) => {
|
|
535
|
+
res.writeHead(200, { "Content-Type": "application/octet-stream" });
|
|
536
|
+
createReadStream(bundlePath).pipe(res);
|
|
537
|
+
});
|
|
538
|
+
|
|
539
|
+
try {
|
|
540
|
+
const req = new Request("http://localhost/v1/migrations/import", {
|
|
541
|
+
method: "POST",
|
|
542
|
+
headers: { "Content-Type": "application/json" },
|
|
543
|
+
body: JSON.stringify({ url: makeFakeSignedUrl(fixture.port) }),
|
|
544
|
+
});
|
|
545
|
+
|
|
546
|
+
const res = await handleMigrationImport(req);
|
|
547
|
+
const body = (await res.json()) as ImportCommitResponse;
|
|
548
|
+
|
|
549
|
+
expect(res.status).toBe(200);
|
|
550
|
+
expect(body.success).toBe(true);
|
|
551
|
+
// Zero files touched — this is the no-swap success path.
|
|
552
|
+
expect(body.summary.files_created).toBe(0);
|
|
553
|
+
expect(body.summary.files_overwritten).toBe(0);
|
|
554
|
+
|
|
555
|
+
// The gate must suppress the newer-migration warning text. The
|
|
556
|
+
// helper's wording starts with "Imported data contains" and ends
|
|
557
|
+
// with "migration(s) from a newer version" — matching either
|
|
558
|
+
// substring is sufficient.
|
|
559
|
+
const stale = body.warnings.filter((w) =>
|
|
560
|
+
w.includes("from a newer version"),
|
|
561
|
+
);
|
|
562
|
+
expect(stale).toEqual([]);
|
|
563
|
+
} finally {
|
|
564
|
+
await fixture.close();
|
|
565
|
+
// Close the cached DB handle before the workspace dir gets rm'd
|
|
566
|
+
// in afterEach so we don't leak WAL/SHM files across tests.
|
|
567
|
+
resetDb();
|
|
568
|
+
}
|
|
569
|
+
});
|
|
570
|
+
});
|
|
571
|
+
|
|
572
|
+
// ---------------------------------------------------------------------------
|
|
573
|
+
// Gap B regression: an upstream body that drops mid-stream (peer reset,
|
|
574
|
+
// socket.destroy() after headers are sent) must surface as 502
|
|
575
|
+
// fetch_failed, not 500 extraction_failed. The tag-at-source plumbing in
|
|
576
|
+
// the URL handler routes importer-rethrown upstream errors to the fetch-
|
|
577
|
+
// failed branch.
|
|
578
|
+
// ---------------------------------------------------------------------------
|
|
579
|
+
|
|
580
|
+
describe("handleMigrationImport — upstream body dropped mid-stream", () => {
|
|
581
|
+
test("socket destroy mid-body returns 502 with reason: fetch_failed", async () => {
|
|
582
|
+
// Build a real gzipped tar prefix, then serve only the first N bytes
|
|
583
|
+
// and tear down the connection. gunzip inside streamCommitImport
|
|
584
|
+
// will surface this as a stream error; we tag it at the source so
|
|
585
|
+
// the handler maps it to 502 fetch_failed. Use random payload bytes
|
|
586
|
+
// so the gzip output is ~incompressible — a compressed all-zeros
|
|
587
|
+
// payload shrinks to <500 bytes total, which doesn't give us enough
|
|
588
|
+
// material to reliably deliver a partial body with gunzip state
|
|
589
|
+
// still alive across the socket teardown.
|
|
590
|
+
const incompressible = new Uint8Array(64 * 1024);
|
|
591
|
+
for (let i = 0; i < incompressible.length; i += 1) {
|
|
592
|
+
incompressible[i] = Math.floor(Math.random() * 256);
|
|
593
|
+
}
|
|
594
|
+
const { archive } = buildVBundle({
|
|
595
|
+
files: [
|
|
596
|
+
{
|
|
597
|
+
path: "workspace/data/db/assistant.db",
|
|
598
|
+
data: incompressible,
|
|
599
|
+
},
|
|
600
|
+
],
|
|
601
|
+
});
|
|
602
|
+
// Safety net: if someone changes buildVBundle to return very small
|
|
603
|
+
// outputs, drop the test early rather than flaking on a too-short
|
|
604
|
+
// truncation window. 512 bytes is plenty given 64 KB of random data
|
|
605
|
+
// gzips to essentially its original size.
|
|
606
|
+
expect(archive.byteLength).toBeGreaterThan(512);
|
|
607
|
+
|
|
608
|
+
// Truncate to the first 256 bytes so upstream cannot deliver a
|
|
609
|
+
// usable tar stream. gunzip will error on the abrupt close.
|
|
610
|
+
const truncatedPrefix = archive.slice(0, 256);
|
|
611
|
+
|
|
612
|
+
const fixture = await startFixtureServer((_req, res) => {
|
|
613
|
+
// Chunked transfer (no Content-Length) + socket.destroy() mid-body
|
|
614
|
+
// is the cleanest way to force Bun's fetch to surface a
|
|
615
|
+
// post-headers stream error rather than an initial-fetch throw.
|
|
616
|
+
// We write the prefix in a couple of chunks so the client side can
|
|
617
|
+
// return from fetch() and hand the body stream to the importer
|
|
618
|
+
// before we tear the socket down.
|
|
619
|
+
res.writeHead(200, {
|
|
620
|
+
"Content-Type": "application/octet-stream",
|
|
621
|
+
"Transfer-Encoding": "chunked",
|
|
622
|
+
});
|
|
623
|
+
const half = Math.floor(truncatedPrefix.byteLength / 2);
|
|
624
|
+
res.write(truncatedPrefix.slice(0, half), () => {
|
|
625
|
+
// Give the runtime a chance to deliver the first chunk to the
|
|
626
|
+
// consumer and enter the streaming import pipeline, then rip
|
|
627
|
+
// the socket out so the body stream surfaces an abort error.
|
|
628
|
+
setTimeout(() => {
|
|
629
|
+
res.socket?.destroy();
|
|
630
|
+
}, 100);
|
|
631
|
+
});
|
|
632
|
+
});
|
|
633
|
+
|
|
634
|
+
try {
|
|
635
|
+
const req = new Request("http://localhost/v1/migrations/import", {
|
|
636
|
+
method: "POST",
|
|
637
|
+
headers: { "Content-Type": "application/json" },
|
|
638
|
+
body: JSON.stringify({ url: makeFakeSignedUrl(fixture.port) }),
|
|
639
|
+
});
|
|
640
|
+
|
|
641
|
+
const res = await handleMigrationImport(req);
|
|
642
|
+
const body = (await res.json()) as FetchFailedResponse;
|
|
643
|
+
|
|
644
|
+
expect(res.status).toBe(502);
|
|
645
|
+
expect(body.success).toBe(false);
|
|
646
|
+
expect(body.reason).toBe("fetch_failed");
|
|
647
|
+
} finally {
|
|
648
|
+
await fixture.close();
|
|
649
|
+
}
|
|
650
|
+
}, 15_000);
|
|
651
|
+
});
|
|
652
|
+
|
|
653
|
+
// ---------------------------------------------------------------------------
|
|
654
|
+
// Regression: raw-bytes path still works through the same handler.
|
|
655
|
+
// ---------------------------------------------------------------------------
|
|
656
|
+
|
|
657
|
+
describe("handleMigrationImport — raw-bytes regression", () => {
|
|
658
|
+
test("application/octet-stream body still imports successfully", async () => {
|
|
659
|
+
const { archive } = buildVBundle({
|
|
660
|
+
files: [
|
|
661
|
+
{
|
|
662
|
+
path: "workspace/data/db/assistant.db",
|
|
663
|
+
data: new TextEncoder().encode("SQLite format 3\0"),
|
|
664
|
+
},
|
|
665
|
+
],
|
|
666
|
+
});
|
|
667
|
+
|
|
668
|
+
const req = new Request("http://localhost/v1/migrations/import", {
|
|
669
|
+
method: "POST",
|
|
670
|
+
headers: { "Content-Type": "application/octet-stream" },
|
|
671
|
+
body: archive.buffer.slice(
|
|
672
|
+
archive.byteOffset,
|
|
673
|
+
archive.byteOffset + archive.byteLength,
|
|
674
|
+
) as ArrayBuffer,
|
|
675
|
+
});
|
|
676
|
+
|
|
677
|
+
const res = await handleMigrationImport(req);
|
|
678
|
+
const body = (await res.json()) as ImportCommitResponse;
|
|
679
|
+
|
|
680
|
+
expect(res.status).toBe(200);
|
|
681
|
+
expect(body.success).toBe(true);
|
|
682
|
+
expect(body.files.length).toBeGreaterThan(0);
|
|
683
|
+
});
|
|
684
|
+
});
|