@vellumai/assistant 0.6.4 → 0.6.6
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.prettierignore +5 -0
- package/AGENTS.md +9 -1
- package/ARCHITECTURE.md +43 -49
- package/Dockerfile +17 -3
- package/README.md +3 -4
- package/__tests__/permissions/gateway-threshold-reader.test.ts +283 -0
- package/bun.lock +8 -3
- package/docs/architecture/integrations.md +33 -59
- package/docs/architecture/memory.md +25 -30
- package/docs/architecture/security.md +19 -18
- package/docs/browser-use-architecture-phase2.md +63 -20
- package/docs/error-handling.md +111 -0
- package/docs/plugins.md +761 -0
- package/docs/skills.md +10 -10
- package/docs/stt-provider-onboarding.md +2 -1
- package/examples/plugins/echo/README.md +132 -0
- package/examples/plugins/echo/package.json +17 -0
- package/examples/plugins/echo/register.ts +187 -0
- 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/node_modules/@vellumai/egress-proxy/src/types.ts +19 -0
- package/openapi.yaml +334 -78
- 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__/app-compiler.test.ts +57 -0
- package/src/__tests__/approval-cascade.test.ts +36 -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__/auto-analysis-end-to-end.test.ts +1 -0
- package/src/__tests__/avatar-generator.test.ts +4 -2
- 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__/bundled-asset.test.ts +6 -6
- 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 +96 -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 +870 -655
- package/src/__tests__/circuit-breaker-pipeline.test.ts +406 -0
- package/src/__tests__/cli-command-risk-guard.test.ts +30 -33
- package/src/__tests__/compaction-events.test.ts +501 -0
- package/src/__tests__/compaction-pipeline.test.ts +210 -0
- package/src/__tests__/compaction-strip-metadata-clear.test.ts +181 -0
- package/src/__tests__/compaction-timeout-recovery.test.ts +262 -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-model-image-provider.test.ts +110 -0
- package/src/__tests__/config-schema-cmd.test.ts +11 -5
- package/src/__tests__/config-schema.test.ts +440 -114
- package/src/__tests__/config-watcher-cleanup-throttle.test.ts +0 -4
- package/src/__tests__/config-watcher.test.ts +2 -2
- package/src/__tests__/contact-store-user-file.test.ts +72 -73
- package/src/__tests__/contacts-tools.test.ts +26 -0
- package/src/__tests__/contacts-write.test.ts +4 -4
- package/src/__tests__/context-overflow-policy.test.ts +7 -7
- package/src/__tests__/context-token-estimator.test.ts +191 -1
- package/src/__tests__/context-window-manager.test.ts +883 -4
- package/src/__tests__/conversation-abort-tool-results.test.ts +32 -15
- package/src/__tests__/conversation-agent-loop-overflow.test.ts +86 -46
- package/src/__tests__/conversation-agent-loop.test.ts +435 -216
- package/src/__tests__/conversation-attachments.test.ts +1 -1
- package/src/__tests__/conversation-confirmation-signals.test.ts +36 -10
- package/src/__tests__/conversation-error.test.ts +37 -6
- package/src/__tests__/conversation-history-web-search.test.ts +7 -0
- package/src/__tests__/conversation-init.benchmark.test.ts +34 -12
- package/src/__tests__/conversation-lifecycle.test.ts +336 -0
- package/src/__tests__/conversation-load-history-repair.test.ts +27 -10
- package/src/__tests__/conversation-pairing.test.ts +174 -10
- package/src/__tests__/conversation-pre-run-repair.test.ts +32 -15
- package/src/__tests__/conversation-process-callsite.test.ts +309 -0
- package/src/__tests__/conversation-provider-retry-repair.test.ts +44 -21
- package/src/__tests__/conversation-queue.test.ts +68 -38
- package/src/__tests__/conversation-routes-disk-view.test.ts +36 -7
- package/src/__tests__/conversation-routes-slash-commands.test.ts +31 -3
- package/src/__tests__/conversation-runtime-assembly.test.ts +2877 -152
- package/src/__tests__/conversation-runtime-workspace.test.ts +35 -50
- package/src/__tests__/conversation-seed-composer.test.ts +2 -2
- package/src/__tests__/conversation-skill-tools.test.ts +12 -146
- package/src/__tests__/conversation-slash-queue.test.ts +39 -19
- package/src/__tests__/conversation-slash-unknown.test.ts +53 -16
- package/src/__tests__/conversation-speed-override.test.ts +36 -12
- 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 +118 -2
- package/src/__tests__/conversation-tool-setup-app-refresh.test.ts +41 -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 +4 -2
- package/src/__tests__/conversation-workspace-cache-state.test.ts +33 -9
- package/src/__tests__/conversation-workspace-injection.test.ts +46 -15
- package/src/__tests__/conversation-workspace-tool-tracking.test.ts +46 -15
- package/src/__tests__/credential-broker-browser-fill.test.ts +110 -0
- package/src/__tests__/credential-health-service.test.ts +78 -9
- package/src/__tests__/credential-security-invariants.test.ts +5 -2
- 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__/db-schedule-syntax-migration.test.ts +1 -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__/empty-response-pipeline.test.ts +305 -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 +29 -10
- package/src/__tests__/file-write-tool.test.ts +151 -1
- package/src/__tests__/filing-service.test.ts +255 -0
- package/src/__tests__/first-greeting.test.ts +247 -5
- 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__/headless-browser-mode.test.ts +57 -0
- package/src/__tests__/heartbeat-service.test.ts +96 -15
- package/src/__tests__/history-repair-pipeline.test.ts +399 -0
- package/src/__tests__/host-browser-e2e-cloud.test.ts +307 -0
- package/src/__tests__/host-browser-e2e-self-hosted.test.ts +3 -3
- package/src/__tests__/host-proxy-interface.test.ts +36 -2
- package/src/__tests__/host-shell-tool.test.ts +124 -18
- package/src/__tests__/http-user-message-parity.test.ts +29 -1
- package/src/__tests__/image-credentials.test.ts +137 -0
- package/src/__tests__/image-service-dispatcher.test.ts +186 -0
- package/src/__tests__/inbound-slack-persistence.test.ts +340 -0
- package/src/__tests__/injector-chain.test.ts +526 -0
- package/src/__tests__/intent-routing.test.ts +1 -66
- package/src/__tests__/llm-call-pipeline.test.ts +285 -0
- 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__/media-generate-image.test.ts +119 -13
- package/src/__tests__/memory-retrieval-pipeline.test.ts +401 -0
- package/src/__tests__/memory-upsert-concurrency.test.ts +1 -0
- package/src/__tests__/messaging-skill-split.test.ts +3 -34
- package/src/__tests__/migration-import-from-url.test.ts +621 -0
- package/src/__tests__/model-intents.test.ts +11 -83
- package/src/__tests__/notification-broadcaster.test.ts +3 -3
- 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__/notification-decision-strategy.test.ts +0 -11
- package/src/__tests__/notification-schedule-notify-dedup.test.ts +108 -0
- package/src/__tests__/oauth-apps-routes.test.ts +1 -1
- package/src/__tests__/oauth-cli.test.ts +14 -12
- package/src/__tests__/oauth-connect-orchestrator.test.ts +4 -13
- package/src/__tests__/oauth-provider-serializer.test.ts +6 -4
- package/src/__tests__/oauth-provider-visibility.test.ts +3 -5
- package/src/__tests__/oauth-providers-routes.test.ts +3 -2
- package/src/__tests__/oauth-store.test.ts +46 -78
- package/src/__tests__/oauth2-gateway-transport.test.ts +8 -3
- package/src/__tests__/oauth2-refresh-retry.test.ts +279 -0
- package/src/__tests__/onboarding-template-contract.test.ts +16 -64
- package/src/__tests__/openai-image-service.test.ts +368 -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__/overflow-reduce-pipeline.test.ts +676 -0
- package/src/__tests__/permission-checker-host-gate.test.ts +1 -25
- package/src/__tests__/permission-mode.test.ts +16 -0
- package/src/__tests__/permission-types.test.ts +0 -1
- package/src/__tests__/persist-onboarding-artifacts.test.ts +266 -0
- package/src/__tests__/persistence-pipeline.test.ts +377 -0
- package/src/__tests__/persona-resolver.test.ts +13 -13
- package/src/__tests__/pipeline-runner.test.ts +565 -0
- package/src/__tests__/pkb-autoinject.test.ts +37 -1
- package/src/__tests__/platform-bash-auto-approve.test.ts +1 -1
- package/src/__tests__/platform.test.ts +5 -2
- package/src/__tests__/plugin-bootstrap.test.ts +483 -0
- package/src/__tests__/plugin-registry.test.ts +273 -0
- package/src/__tests__/plugin-route-contribution.test.ts +288 -0
- package/src/__tests__/plugin-skill-contribution.test.ts +367 -0
- package/src/__tests__/plugin-tool-contribution.test.ts +286 -0
- package/src/__tests__/plugin-types.test.ts +320 -0
- package/src/__tests__/pricing.test.ts +93 -14
- 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 +69 -9
- package/src/__tests__/reaction-persistence.test.ts +561 -0
- package/src/__tests__/regenerate-fire-and-forget-trace.test.ts +1 -0
- package/src/__tests__/registry.test.ts +0 -2
- 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__/schedule-routes.test.ts +131 -1
- package/src/__tests__/scheduler-recurrence.test.ts +14 -70
- package/src/__tests__/scheduler-reuse-conversation.test.ts +10 -50
- package/src/__tests__/secret-detection-handler.test.ts +0 -10
- 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-identity.test.ts +0 -134
- 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 +259 -3
- package/src/__tests__/system-prompt.test.ts +22 -35
- package/src/__tests__/task-memory-cleanup.test.ts +1 -0
- package/src/__tests__/task-runner.test.ts +3 -1
- package/src/__tests__/task-scheduler.test.ts +3 -15
- package/src/__tests__/tcc-sandbox-deny.test.ts +198 -0
- package/src/__tests__/terminal-tools.test.ts +8 -0
- package/src/__tests__/test-preload.ts +11 -0
- package/src/__tests__/test-support/browser-skill-harness.ts +2 -52
- package/src/__tests__/thread-backfill.test.ts +941 -0
- package/src/__tests__/title-generate-pipeline.test.ts +224 -0
- package/src/__tests__/token-estimate-pipeline.test.ts +431 -0
- package/src/__tests__/tool-error-pipeline.test.ts +244 -0
- package/src/__tests__/tool-execute-pipeline.test.ts +431 -0
- package/src/__tests__/tool-execution-pipeline.benchmark.test.ts +2 -8
- package/src/__tests__/tool-executor-lifecycle-events.test.ts +2 -2
- package/src/__tests__/tool-executor-shell-integration.test.ts +7 -10
- package/src/__tests__/tool-executor.test.ts +201 -94
- package/src/__tests__/tool-result-truncate-pipeline.test.ts +356 -0
- package/src/__tests__/tool-result-truncation.test.ts +0 -110
- 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__/user-plugin-loader.test.ts +191 -0
- 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-046-seed-conversation-starters-callsite.test.ts +185 -0
- package/src/__tests__/workspace-migration-049-release-notes-default-sonnet.test.ts +100 -0
- package/src/__tests__/workspace-migration-050-seed-main-agent-opus-callsite.test.ts +171 -0
- package/src/__tests__/workspace-migration-051-seed-conversation-summarization-callsite.test.ts +252 -0
- package/src/__tests__/workspace-migration-drop-user-md.test.ts +11 -11
- package/src/__tests__/workspace-migration-remove-hooks.test.ts +99 -0
- package/src/__tests__/workspace-migration-unify-llm-callsite-configs.test.ts +841 -0
- package/src/__tests__/workspace-policy.test.ts +22 -16
- package/src/acp/client-handler.ts +1 -2
- package/src/agent/loop.ts +545 -115
- package/src/approvals/__tests__/guardian-feed-event.test.ts +304 -0
- package/src/approvals/guardian-request-resolvers.ts +80 -0
- 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/backup/__tests__/backup-worker.test.ts +2 -13
- package/src/backup/backup-worker.ts +3 -15
- 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/bundler/app-compiler.ts +84 -1
- package/src/calls/call-state.ts +2 -2
- 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/channels/__tests__/types.test.ts +3 -3
- package/src/channels/types.ts +6 -4
- package/src/cli/AGENTS.md +1 -1
- package/src/cli/__tests__/notifications.test.ts +87 -211
- package/src/cli/commands/__tests__/attachment.test.ts +438 -0
- package/src/cli/commands/__tests__/backup.test.ts +1 -1
- 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 +886 -0
- package/src/cli/commands/__tests__/inference-send.test.ts +463 -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 +606 -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/backup.ts +2 -2
- package/src/cli/commands/browser.ts +350 -0
- package/src/cli/commands/cache.ts +341 -0
- package/src/cli/commands/clients.ts +138 -0
- package/src/cli/commands/completions.ts +2 -12
- package/src/cli/commands/config.ts +6 -6
- package/src/cli/commands/conversations-import.ts +347 -0
- package/src/cli/commands/conversations.ts +69 -8
- package/src/cli/commands/email.ts +234 -194
- package/src/cli/commands/image-generation.ts +299 -0
- package/src/cli/commands/inference.ts +200 -0
- package/src/cli/commands/memory.ts +127 -17
- package/src/cli/commands/notifications.ts +68 -103
- package/src/cli/commands/oauth/__tests__/providers-register.test.ts +1 -1
- package/src/cli/commands/oauth/__tests__/providers-update.test.ts +1 -1
- package/src/cli/commands/oauth/connect.ts +2 -2
- package/src/cli/commands/oauth/providers.ts +176 -8
- package/src/cli/commands/oauth/status.ts +46 -36
- 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/skills.ts +3 -4
- 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 +39 -24
- package/src/cli.ts +0 -37
- package/src/config/__tests__/backup-schema.test.ts +7 -2
- package/src/config/bundled-skills/app-builder/SKILL.md +2 -2
- package/src/config/bundled-skills/app-builder/references/WIDGETS.md +10 -10
- package/src/config/bundled-skills/contacts/tools/contact-merge.ts +66 -87
- package/src/config/bundled-skills/contacts/tools/contact-search.ts +28 -51
- package/src/config/bundled-skills/contacts/tools/contact-upsert.ts +22 -40
- package/src/config/bundled-skills/image-studio/SKILL.md +2 -1
- package/src/config/bundled-skills/image-studio/TOOLS.json +2 -1
- package/src/config/bundled-skills/image-studio/tools/media-generate-image.ts +23 -39
- package/src/config/bundled-skills/media-processing/services/reduce.ts +1 -1
- package/src/config/bundled-skills/messaging/SKILL.md +5 -5
- package/src/config/bundled-skills/messaging/TOOLS.json +4 -0
- package/src/config/bundled-skills/messaging/tools/__tests__/messaging-feed-events.test.ts +207 -0
- package/src/config/bundled-skills/messaging/tools/messaging-archive-by-sender.ts +20 -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 +69 -12
- package/src/config/bundled-skills/phone-calls/references/CONFIG.md +9 -8
- package/src/config/bundled-skills/schedule/SKILL.md +8 -3
- package/src/config/bundled-skills/schedule/TOOLS.json +15 -7
- package/src/config/bundled-skills/schedule/references/SCRIPT_MODE_PATTERNS.md +59 -0
- package/src/config/bundled-skills/settings/TOOLS.json +3 -3
- package/src/config/bundled-tool-registry.ts +0 -190
- package/src/config/env.ts +7 -2
- package/src/config/feature-flag-registry.json +42 -10
- 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 +49 -41
- package/src/config/schemas/analysis.ts +3 -22
- package/src/config/schemas/backup.ts +1 -1
- package/src/config/schemas/calls.ts +0 -4
- package/src/config/schemas/conversations.ts +16 -0
- 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 +317 -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 +64 -0
- package/src/config/schemas/updates.ts +1 -1
- package/src/config/schemas/workspace-git.ts +3 -40
- package/src/config/skill-state.ts +6 -2
- package/src/config/skills.ts +96 -7
- package/src/context/__tests__/compact-prompt.test.ts +63 -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 +26 -0
- package/src/context/token-estimator.ts +61 -3
- package/src/context/tool-result-truncation.ts +3 -63
- package/src/context/window-manager.ts +417 -39
- 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/credential-health/credential-health-service.ts +19 -6
- package/src/daemon/__tests__/conversation-feed-event.test.ts +317 -0
- package/src/daemon/__tests__/conversation-lifecycle-auto-analyze.test.ts +4 -12
- package/src/daemon/__tests__/conversation-tool-setup.test.ts +14 -15
- 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 -3
- package/src/daemon/context-overflow-policy.ts +4 -13
- package/src/daemon/context-overflow-reducer.ts +4 -1
- package/src/daemon/conversation-agent-loop-handlers.ts +162 -34
- package/src/daemon/conversation-agent-loop.ts +1282 -599
- package/src/daemon/conversation-attachments.ts +2 -6
- package/src/daemon/conversation-error.ts +36 -1
- package/src/daemon/conversation-history.ts +10 -19
- package/src/daemon/conversation-lifecycle.ts +59 -17
- package/src/daemon/conversation-messaging.ts +73 -4
- package/src/daemon/conversation-notifiers.ts +2 -110
- package/src/daemon/conversation-process.ts +24 -11
- package/src/daemon/conversation-queue-manager.ts +3 -0
- package/src/daemon/conversation-runtime-assembly.ts +1063 -211
- package/src/daemon/conversation-slash.ts +2 -2
- package/src/daemon/conversation-surfaces.ts +389 -1
- package/src/daemon/conversation-tool-setup.ts +51 -9
- package/src/daemon/conversation-usage.ts +1 -1
- package/src/daemon/conversation.ts +197 -64
- package/src/daemon/external-plugins-bootstrap.ts +478 -0
- package/src/daemon/external-skills-bootstrap.ts +41 -0
- package/src/daemon/first-greeting.ts +191 -14
- 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 +65 -12
- package/src/daemon/handlers/conversations.ts +9 -2
- package/src/daemon/handlers/shared.ts +39 -11
- package/src/daemon/handlers/skills.ts +7 -3
- package/src/daemon/handlers/slack-channel-oauth-install.ts +197 -0
- package/src/daemon/lifecycle.ts +109 -82
- package/src/daemon/message-types/computer-use.ts +2 -34
- package/src/daemon/message-types/conversations.ts +63 -0
- package/src/daemon/message-types/messages.ts +21 -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 +122 -12
- package/src/daemon/shutdown-handlers.ts +2 -12
- package/src/daemon/tool-side-effects.ts +14 -65
- 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/__tests__/heartbeat-feed-event.test.ts +160 -0
- package/src/heartbeat/heartbeat-service.ts +99 -28
- package/src/home/__tests__/feed-population-integration.test.ts +312 -0
- 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 +11 -0
- package/src/home/feed-scheduler.ts +20 -4
- package/src/home/feed-types.ts +97 -4
- package/src/home/relationship-state-writer.ts +2 -2
- package/src/home/rewrite-command-preview.ts +66 -0
- 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 +34 -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 +6 -3
- package/src/ipc/routes/attachment.ts +114 -0
- package/src/ipc/routes/browser-context.ts +63 -0
- package/src/ipc/routes/browser.ts +97 -0
- package/src/ipc/routes/cache.ts +96 -0
- package/src/ipc/routes/get-contact.ts +16 -0
- package/src/ipc/routes/index.ts +31 -1
- package/src/ipc/routes/list-clients.ts +31 -0
- package/src/ipc/routes/merge-contacts.ts +17 -0
- package/src/ipc/routes/notification.ts +133 -0
- package/src/ipc/routes/rename-conversation.ts +59 -0
- package/src/ipc/routes/search-contacts.ts +19 -0
- 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/upsert-contact.ts +25 -0
- package/src/ipc/routes/watcher.ts +203 -0
- package/src/ipc/socket-path.ts +76 -0
- package/src/media/app-icon-generator.ts +23 -46
- package/src/media/avatar-router.ts +26 -41
- package/src/media/gemini-image-service.ts +8 -41
- package/src/media/image-credentials.ts +73 -0
- package/src/media/image-service.ts +85 -0
- package/src/media/openai-image-service.ts +131 -0
- package/src/media/types.ts +46 -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 +133 -3
- package/src/memory/conversation-group-migration.ts +38 -6
- package/src/memory/conversation-queries.ts +57 -4
- package/src/memory/conversation-title-service.ts +32 -4
- package/src/memory/db-init.ts +10 -0
- package/src/memory/embedding-backend.ts +1 -1
- package/src/memory/embedding-gemini.test.ts +41 -2
- package/src/memory/embedding-gemini.ts +6 -1
- package/src/memory/graph/bootstrap.test.ts +282 -0
- package/src/memory/graph/bootstrap.ts +8 -5
- 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 +183 -53
- package/src/memory/graph/graph-search.test.ts +93 -0
- package/src/memory/graph/graph-search.ts +4 -1
- package/src/memory/graph/inspect.ts +2 -2
- 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 +237 -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/041-approval-prompt-ts-tracker.ts +26 -0
- package/src/memory/migrations/140-backfill-usage-cache-accounting.ts +1 -1
- package/src/memory/migrations/149-oauth-tables.ts +1 -0
- 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/223-schedule-script-column.ts +11 -0
- package/src/memory/migrations/224-oauth-providers-managed-service-is-paid.ts +24 -0
- package/src/memory/migrations/225-oauth-providers-available-scopes.ts +13 -0
- package/src/memory/migrations/index.ts +5 -0
- package/src/memory/pkb/pkb-index.test.ts +369 -0
- package/src/memory/pkb/pkb-index.ts +255 -0
- package/src/memory/pkb/pkb-reconcile.test.ts +252 -0
- package/src/memory/pkb/pkb-reconcile.ts +148 -0
- package/src/memory/pkb/pkb-search.test.ts +499 -0
- package/src/memory/pkb/pkb-search.ts +159 -0
- package/src/memory/pkb/types.ts +53 -0
- package/src/memory/qdrant-client.test.ts +60 -0
- package/src/memory/qdrant-client.ts +147 -1
- package/src/memory/schema/infrastructure.ts +1 -0
- package/src/memory/schema/oauth.ts +4 -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 +1421 -0
- package/src/messaging/providers/slack/render-transcript.ts +501 -0
- package/src/messaging/style-analyzer.ts +5 -2
- package/src/notifications/README.md +9 -5
- package/src/notifications/conversation-pairing.ts +78 -19
- package/src/notifications/copy-composer.ts +0 -5
- package/src/notifications/decision-engine.ts +3 -9
- package/src/notifications/emit-signal.ts +1 -1
- package/src/notifications/preference-extractor.ts +2 -6
- package/src/notifications/signal.ts +1 -2
- package/src/oauth/AGENTS.md +1 -1
- package/src/oauth/__tests__/identity-verifier.test.ts +2 -1
- package/src/oauth/connect-orchestrator.ts +8 -34
- package/src/oauth/connect-types.ts +6 -10
- package/src/oauth/manual-token-connection.ts +23 -0
- package/src/oauth/oauth-store.ts +31 -14
- package/src/oauth/platform-connection.test.ts +47 -0
- package/src/oauth/platform-connection.ts +15 -5
- package/src/oauth/provider-serializer.ts +6 -1
- package/src/oauth/seed-providers.ts +56 -106
- package/src/outbound-proxy/http-forwarder.ts +9 -0
- package/src/permissions/approval-policy.test.ts +1223 -0
- package/src/permissions/approval-policy.ts +309 -0
- package/src/permissions/arg-parser.test.ts +161 -0
- package/src/permissions/arg-parser.ts +141 -0
- package/src/permissions/bash-risk-classifier.test.ts +1620 -0
- package/src/permissions/bash-risk-classifier.ts +950 -0
- package/src/permissions/checker.ts +348 -711
- package/src/permissions/command-registry.test.ts +774 -0
- package/src/permissions/command-registry.ts +1005 -0
- package/src/permissions/defaults.ts +28 -79
- package/src/permissions/file-risk-classifier.test.ts +535 -0
- package/src/permissions/file-risk-classifier.ts +274 -0
- package/src/permissions/gateway-threshold-reader.ts +196 -0
- package/src/permissions/prompter.ts +4 -0
- package/src/permissions/risk-types.ts +262 -0
- package/src/permissions/schedule-risk-classifier.test.ts +129 -0
- package/src/permissions/schedule-risk-classifier.ts +85 -0
- package/src/permissions/secret-prompter.ts +53 -2
- package/src/permissions/shell-identity.ts +2 -42
- 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 +25 -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 +9 -19
- package/src/platform/client.ts +19 -1
- package/src/plugins/defaults/circuit-breaker.ts +146 -0
- package/src/plugins/defaults/compaction.ts +145 -0
- package/src/plugins/defaults/empty-response.ts +126 -0
- package/src/plugins/defaults/history-repair.ts +85 -0
- package/src/plugins/defaults/index.ts +116 -0
- package/src/plugins/defaults/injectors.ts +491 -0
- package/src/plugins/defaults/llm-call.ts +82 -0
- package/src/plugins/defaults/memory-retrieval.ts +226 -0
- package/src/plugins/defaults/overflow-reduce.ts +181 -0
- package/src/plugins/defaults/persistence.ts +129 -0
- package/src/plugins/defaults/title-generate.ts +95 -0
- package/src/plugins/defaults/token-estimate.ts +104 -0
- package/src/plugins/defaults/tool-error.ts +126 -0
- package/src/plugins/defaults/tool-execute.ts +89 -0
- package/src/plugins/defaults/tool-result-truncate.ts +88 -0
- package/src/plugins/pipeline.ts +316 -0
- package/src/plugins/plugin-skill-contributions.ts +292 -0
- package/src/plugins/registry.ts +241 -0
- package/src/plugins/types.ts +1134 -0
- package/src/plugins/user-loader.ts +177 -0
- package/src/prompts/persona-resolver.ts +3 -3
- package/src/prompts/system-prompt.ts +19 -20
- package/src/prompts/templates/BOOTSTRAP.md +27 -77
- 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 +524 -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 +80 -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/deepgram-realtime.test.ts +61 -0
- package/src/providers/speech-to-text/deepgram-realtime.ts +57 -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 +646 -0
- package/src/providers/speech-to-text/xai-realtime.ts +821 -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 +27 -18
- package/src/runtime/__tests__/agent-wake.test.ts +43 -2
- package/src/runtime/__tests__/browser-extension-pair-routes.test.ts +3 -3
- package/src/runtime/__tests__/client-registry.test.ts +293 -0
- 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/client-registry.ts +261 -0
- 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 +129 -9
- package/src/runtime/http-types.ts +23 -3
- 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-builder.ts +1 -22
- 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 +78 -0
- package/src/runtime/routes/approval-routes.ts +29 -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/browser-extension-pair-routes.ts +27 -8
- 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 +351 -138
- 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 +987 -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/memory-item-routes.test.ts +1 -0
- package/src/runtime/routes/migration-routes.ts +720 -127
- package/src/runtime/routes/playground/__tests__/force-compact.test.ts +284 -0
- package/src/runtime/routes/playground/__tests__/guard.test.ts +80 -0
- package/src/runtime/routes/playground/__tests__/inject-failures.test.ts +294 -0
- package/src/runtime/routes/playground/__tests__/reset-circuit.test.ts +271 -0
- package/src/runtime/routes/playground/__tests__/seed-conversation.test.ts +202 -0
- package/src/runtime/routes/playground/__tests__/seeded-conversations.test.ts +309 -0
- package/src/runtime/routes/playground/__tests__/state.test.ts +224 -0
- package/src/runtime/routes/playground/conversation-not-found.ts +29 -0
- package/src/runtime/routes/playground/deps.ts +56 -0
- package/src/runtime/routes/playground/force-compact.ts +73 -0
- package/src/runtime/routes/playground/guard.ts +37 -0
- package/src/runtime/routes/playground/index.ts +28 -0
- package/src/runtime/routes/playground/inject-failures.ts +159 -0
- package/src/runtime/routes/playground/reset-circuit.ts +115 -0
- package/src/runtime/routes/playground/seed-conversation.ts +139 -0
- package/src/runtime/routes/playground/seeded-conversations.ts +78 -0
- package/src/runtime/routes/playground/state.ts +78 -0
- package/src/runtime/routes/schedule-routes.ts +89 -8
- 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 +97 -15
- package/src/schedule/run-script.ts +68 -0
- package/src/schedule/schedule-store.ts +7 -1
- package/src/schedule/scheduler.ts +56 -8
- 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 +35 -9
- 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 +234 -2
- package/src/tools/browser/browser-execution.ts +150 -54
- package/src/tools/browser/cdp-client/__tests__/extension-cdp-client.test.ts +230 -0
- package/src/tools/browser/cdp-client/__tests__/factory.test.ts +146 -3
- package/src/tools/browser/cdp-client/cdp-inspect/discovery.ts +22 -0
- package/src/tools/browser/cdp-client/extension-cdp-client.ts +54 -3
- package/src/tools/browser/cdp-client/factory.ts +15 -4
- package/src/tools/credentials/tool-policy.ts +39 -5
- package/src/tools/credentials/vault.ts +9 -4
- package/src/tools/executor.ts +129 -73
- 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/script-proxy/session-manager.ts +37 -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 +116 -46
- package/src/tools/policy-context.ts +29 -8
- package/src/tools/registry.ts +195 -6
- package/src/tools/schedule/create.ts +23 -8
- package/src/tools/schedule/update.ts +3 -1
- package/src/tools/secret-detection-handler.ts +0 -51
- 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/system/avatar-generator.ts +6 -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 +40 -5
- 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 +9 -4
- package/src/util/pricing.ts +41 -8
- 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/006-services-config.ts +2 -4
- package/src/workspace/migrations/022-move-hooks-to-workspace.ts +2 -3
- 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 +56 -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/046-seed-conversation-starters-callsite.ts +108 -0
- package/src/workspace/migrations/047-remove-watch-callsites.ts +54 -0
- package/src/workspace/migrations/048-remove-workspace-hooks.ts +81 -0
- package/src/workspace/migrations/049-release-notes-default-sonnet.ts +80 -0
- package/src/workspace/migrations/050-seed-main-agent-opus-callsite.ts +86 -0
- package/src/workspace/migrations/051-seed-conversation-summarization-callsite.ts +128 -0
- package/src/workspace/migrations/AGENTS.md +1 -1
- package/src/workspace/migrations/registry.ts +28 -0
- package/src/workspace/provider-commit-message-generator.ts +19 -38
- package/tsconfig.json +1 -1
- package/hook-templates/debug-prompt-logger/hook.json +0 -7
- package/hook-templates/debug-prompt-logger/run.sh +0 -66
- package/src/__tests__/context-overflow-approval.test.ts +0 -156
- 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__/hooks-blocking.test.ts +0 -178
- package/src/__tests__/hooks-cli.test.ts +0 -182
- package/src/__tests__/hooks-config.test.ts +0 -108
- package/src/__tests__/hooks-discovery.test.ts +0 -211
- package/src/__tests__/hooks-integration.test.ts +0 -196
- package/src/__tests__/hooks-manager.test.ts +0 -226
- package/src/__tests__/hooks-runner.test.ts +0 -175
- package/src/__tests__/hooks-settings.test.ts +0 -160
- package/src/__tests__/hooks-templates.test.ts +0 -169
- package/src/__tests__/hooks-ts-runner.test.ts +0 -170
- package/src/__tests__/hooks-watch.test.ts +0 -112
- package/src/__tests__/notification-schedule-dedup.test.ts +0 -213
- package/src/__tests__/oauth-scope-policy.test.ts +0 -180
- 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__/send-notification-tool.test.ts +0 -83
- 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/cli/commands/shotgun.ts +0 -266
- 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/conversations/SKILL.md +0 -20
- package/src/config/bundled-skills/conversations/TOOLS.json +0 -23
- package/src/config/bundled-skills/conversations/tools/rename-conversation.ts +0 -66
- 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/heartbeat/SKILL.md +0 -43
- package/src/config/bundled-skills/notifications/SKILL.md +0 -40
- package/src/config/bundled-skills/notifications/TOOLS.json +0 -80
- package/src/config/bundled-skills/notifications/tools/send-notification.ts +0 -152
- package/src/config/bundled-skills/notifications/tools/shared.ts +0 -13
- 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/screen-watch/SKILL.md +0 -27
- package/src/config/bundled-skills/screen-watch/TOOLS.json +0 -35
- package/src/config/bundled-skills/screen-watch/tools/start-screen-watch.ts +0 -12
- package/src/config/bundled-skills/skills-catalog/SKILL.md +0 -84
- 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/daemon/context-overflow-approval.ts +0 -52
- package/src/daemon/watch-handler.ts +0 -399
- package/src/hooks/cli.ts +0 -253
- package/src/hooks/config.ts +0 -100
- package/src/hooks/discovery.ts +0 -135
- package/src/hooks/manager.ts +0 -179
- package/src/hooks/runner.ts +0 -117
- package/src/hooks/templates.ts +0 -77
- package/src/hooks/types.ts +0 -75
- package/src/oauth/scope-policy.ts +0 -89
- 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/runtime/gateway-internal-client.ts +0 -94
- package/src/runtime/routes/watch-routes.ts +0 -156
- package/src/shared/provider-env-vars.ts +0 -19
- package/src/signals/shotgun.ts +0 -203
- package/src/tools/watch/screen-watch.ts +0 -144
- package/src/tools/watch/watch-state.ts +0 -142
- 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
|
@@ -6,13 +6,16 @@
|
|
|
6
6
|
* POST /v1/migrations/import-preflight — dry-run import analysis of a .vbundle archive.
|
|
7
7
|
* POST /v1/migrations/import — commit a .vbundle archive import to disk.
|
|
8
8
|
*
|
|
9
|
-
* Accepts raw binary body (Content-Type: application/octet-stream)
|
|
10
|
-
* multipart form data with a "file" field
|
|
11
|
-
*
|
|
9
|
+
* Accepts raw binary body (Content-Type: application/octet-stream),
|
|
10
|
+
* multipart form data with a "file" field, or — on /import only — a JSON
|
|
11
|
+
* body of shape `{ "url": "<signed-gcs-url>" }` that causes the daemon to
|
|
12
|
+
* fetch the bundle from GCS and stream it through `streamCommitImport`.
|
|
13
|
+
* Returns structured validation results with is_valid flag and detailed
|
|
14
|
+
* error descriptions.
|
|
12
15
|
*/
|
|
13
16
|
|
|
14
17
|
import { createReadStream } from "node:fs";
|
|
15
|
-
import { Readable } from "node:stream";
|
|
18
|
+
import { PassThrough, Readable } from "node:stream";
|
|
16
19
|
import { Database } from "bun:sqlite";
|
|
17
20
|
|
|
18
21
|
import { z } from "zod";
|
|
@@ -21,11 +24,17 @@ import { invalidateConfigCache } from "../../config/loader.js";
|
|
|
21
24
|
import { getDb, resetDb } from "../../memory/db-connection.js";
|
|
22
25
|
import { validateMigrationState } from "../../memory/migrations/validate-migration-state.js";
|
|
23
26
|
import { clearCache as clearTrustCache } from "../../permissions/trust-store.js";
|
|
27
|
+
import { credentialKey } from "../../security/credential-key.js";
|
|
24
28
|
import {
|
|
25
29
|
bulkSetSecureKeysAsync,
|
|
30
|
+
getSecureKeyAsync,
|
|
26
31
|
getSecureKeyResultAsync,
|
|
27
32
|
listSecureKeysAsync,
|
|
28
33
|
} from "../../security/secure-keys.js";
|
|
34
|
+
import {
|
|
35
|
+
getCredentialMetadata,
|
|
36
|
+
upsertCredentialMetadata,
|
|
37
|
+
} from "../../tools/credentials/metadata-store.js";
|
|
29
38
|
import { getLogger } from "../../util/logger.js";
|
|
30
39
|
import {
|
|
31
40
|
getDbPath,
|
|
@@ -34,6 +43,10 @@ import {
|
|
|
34
43
|
} from "../../util/platform.js";
|
|
35
44
|
import { httpError } from "../http-errors.js";
|
|
36
45
|
import type { RouteDefinition } from "../http-router.js";
|
|
46
|
+
import {
|
|
47
|
+
validateGcsSignedUrl,
|
|
48
|
+
type ValidateGcsSignedUrlOptions,
|
|
49
|
+
} from "../migrations/gcs-signed-url.js";
|
|
37
50
|
import { streamExportVBundle } from "../migrations/vbundle-builder.js";
|
|
38
51
|
import {
|
|
39
52
|
analyzeImport,
|
|
@@ -42,11 +55,74 @@ import {
|
|
|
42
55
|
import {
|
|
43
56
|
commitImport,
|
|
44
57
|
extractCredentialsFromBundle,
|
|
58
|
+
type ImportCommitReport,
|
|
59
|
+
type ImportCommitResult,
|
|
45
60
|
} from "../migrations/vbundle-importer.js";
|
|
61
|
+
import { streamCommitImport } from "../migrations/vbundle-streaming-importer.js";
|
|
46
62
|
import { validateVBundle } from "../migrations/vbundle-validator.js";
|
|
47
63
|
|
|
48
|
-
/**
|
|
49
|
-
|
|
64
|
+
/**
|
|
65
|
+
* CES account prefix for platform-identity (`vellum:*`) credentials. Entries
|
|
66
|
+
* with an account that starts with this string are filtered out of any
|
|
67
|
+
* imported bundle so they don't overwrite the target's own Django-provisioned
|
|
68
|
+
* platform identity (most notably `assistant_api_key`).
|
|
69
|
+
*
|
|
70
|
+
* Derived from `credentialKey("vellum", "")` so the prefix automatically
|
|
71
|
+
* tracks the real CES account format — the literal string `"credential/vellum/"`.
|
|
72
|
+
*/
|
|
73
|
+
const PLATFORM_CREDENTIAL_PREFIX = credentialKey("vellum", "");
|
|
74
|
+
|
|
75
|
+
/**
|
|
76
|
+
* Platform-identity fields that the managed runtime expects to see in CES.
|
|
77
|
+
* Django's post-hatch provisioning populates the first four via
|
|
78
|
+
* `POST /v1/secrets`; `platform_organization_id` and `platform_user_id` are
|
|
79
|
+
* populated by the signed-in client after hatch (onboarding, teleport,
|
|
80
|
+
* local→managed transfer) because Django has no signed-in user session to
|
|
81
|
+
* resolve them. Either set of writes can race with the import — the CES
|
|
82
|
+
* write survives (separate volume), but the metadata upsert may be
|
|
83
|
+
* clobbered by the in-place clear / atomic swap. After every import we
|
|
84
|
+
* reconcile metadata.json against CES so any field CES already holds a
|
|
85
|
+
* value for gets a matching metadata entry.
|
|
86
|
+
*/
|
|
87
|
+
const VELLUM_PLATFORM_IDENTITY_FIELDS = [
|
|
88
|
+
"platform_base_url",
|
|
89
|
+
"assistant_api_key",
|
|
90
|
+
"platform_assistant_id",
|
|
91
|
+
"platform_organization_id",
|
|
92
|
+
"platform_user_id",
|
|
93
|
+
"webhook_secret",
|
|
94
|
+
] as const;
|
|
95
|
+
|
|
96
|
+
/**
|
|
97
|
+
* Idempotent post-import reconciliation: for each vellum:* field, if CES
|
|
98
|
+
* has a value but metadata.json doesn't list it, upsert the entry. Pure
|
|
99
|
+
* add-only — never deletes anything. Safe to run whether or not Django's
|
|
100
|
+
* post-hatch provisioning has completed (missing CES values are skipped).
|
|
101
|
+
*
|
|
102
|
+
* Exported for direct unit-testing.
|
|
103
|
+
*/
|
|
104
|
+
export async function reconcileVellumMetadataFromCes(warningSink: {
|
|
105
|
+
warnings: string[];
|
|
106
|
+
}): Promise<void> {
|
|
107
|
+
for (const field of VELLUM_PLATFORM_IDENTITY_FIELDS) {
|
|
108
|
+
try {
|
|
109
|
+
const value = await getSecureKeyAsync(credentialKey("vellum", field));
|
|
110
|
+
if (!value) continue;
|
|
111
|
+
if (getCredentialMetadata("vellum", field)) continue;
|
|
112
|
+
upsertCredentialMetadata("vellum", field, {});
|
|
113
|
+
log.info(
|
|
114
|
+
{ field },
|
|
115
|
+
"Reconciled vellum:* metadata entry from CES after import",
|
|
116
|
+
);
|
|
117
|
+
} catch (err) {
|
|
118
|
+
warningSink.warnings.push(
|
|
119
|
+
`Failed to reconcile vellum:${field} metadata: ${
|
|
120
|
+
err instanceof Error ? err.message : String(err)
|
|
121
|
+
}`,
|
|
122
|
+
);
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
}
|
|
50
126
|
|
|
51
127
|
const log = getLogger("migration-routes");
|
|
52
128
|
|
|
@@ -179,9 +255,6 @@ export async function handleMigrationExport(req: Request): Promise<Response> {
|
|
|
179
255
|
}
|
|
180
256
|
|
|
181
257
|
const result = await streamExportVBundle({
|
|
182
|
-
// hooksDir is intentionally omitted — hooks now live under workspace/hooks/
|
|
183
|
-
// and are included in the workspace walk. Passing hooksDir separately would
|
|
184
|
-
// export them twice (once as workspace/hooks/... and again as hooks/...).
|
|
185
258
|
workspaceDir: getWorkspaceDir(),
|
|
186
259
|
source: "runtime-export",
|
|
187
260
|
description,
|
|
@@ -380,11 +453,15 @@ export async function handleMigrationImportPreflight(
|
|
|
380
453
|
* 5. Verifies post-write integrity (SHA-256 check)
|
|
381
454
|
* 6. Returns a detailed report of what was imported
|
|
382
455
|
*
|
|
383
|
-
* The
|
|
456
|
+
* The bundle can be supplied in any of three ways:
|
|
384
457
|
* - Raw binary body with Content-Type: application/octet-stream
|
|
385
458
|
* - Multipart form data with a "file" field
|
|
459
|
+
* - JSON body `{ "url": "<signed-gcs-url>" }` (Content-Type:
|
|
460
|
+
* application/json). The daemon fetches and streams the archive
|
|
461
|
+
* through `streamCommitImport`, so peak memory stays bounded by a
|
|
462
|
+
* single tar entry rather than bundle size.
|
|
386
463
|
*
|
|
387
|
-
* Returns:
|
|
464
|
+
* Returns (all three paths):
|
|
388
465
|
* 200: {
|
|
389
466
|
* success: true,
|
|
390
467
|
* summary: { total_files, files_created, files_overwritten, files_skipped, backups_created },
|
|
@@ -393,12 +470,25 @@ export async function handleMigrationImportPreflight(
|
|
|
393
470
|
* warnings: [...]
|
|
394
471
|
* }
|
|
395
472
|
* 200: { success: false, reason: "validation_failed", errors: [...] }
|
|
396
|
-
* 400: Standard error envelope for missing/empty body
|
|
473
|
+
* 400: Standard error envelope for missing/empty body or malformed URL
|
|
397
474
|
* 500: Standard error envelope for unexpected failures
|
|
475
|
+
* 502: { success: false, reason: "fetch_failed", upstream_status?: number }
|
|
476
|
+
* (URL path only — upstream GCS fetch failed)
|
|
398
477
|
*
|
|
399
478
|
* Auth: Requires settings.write scope. Allowed for actor, svc_gateway, svc_daemon, local.
|
|
400
479
|
*/
|
|
401
480
|
export async function handleMigrationImport(req: Request): Promise<Response> {
|
|
481
|
+
// JSON body means the caller is asking us to fetch the bundle from a
|
|
482
|
+
// signed URL and stream it through the importer. This keeps the daemon's
|
|
483
|
+
// peak memory bounded by one tar entry instead of bundle size, which is
|
|
484
|
+
// the whole point of supporting URL-based imports for large bundles.
|
|
485
|
+
//
|
|
486
|
+
// Raw-bytes path (octet-stream / multipart) is untouched below.
|
|
487
|
+
const contentType = req.headers.get("content-type") ?? "";
|
|
488
|
+
if (contentType.includes("application/json")) {
|
|
489
|
+
return handleMigrationImportFromUrl(req);
|
|
490
|
+
}
|
|
491
|
+
|
|
402
492
|
const extracted = await extractFileData(req);
|
|
403
493
|
if ("error" in extracted) {
|
|
404
494
|
return extracted.error;
|
|
@@ -445,122 +535,29 @@ export async function handleMigrationImport(req: Request): Promise<Response> {
|
|
|
445
535
|
});
|
|
446
536
|
|
|
447
537
|
if (!result.ok) {
|
|
448
|
-
|
|
449
|
-
return Response.json({
|
|
450
|
-
success: false,
|
|
451
|
-
reason: "validation_failed",
|
|
452
|
-
errors: result.errors,
|
|
453
|
-
});
|
|
454
|
-
}
|
|
455
|
-
|
|
456
|
-
if (result.reason === "extraction_failed") {
|
|
457
|
-
return Response.json(
|
|
458
|
-
{
|
|
459
|
-
success: false,
|
|
460
|
-
reason: "extraction_failed",
|
|
461
|
-
message: result.message,
|
|
462
|
-
},
|
|
463
|
-
{ status: 500 },
|
|
464
|
-
);
|
|
465
|
-
}
|
|
466
|
-
|
|
467
|
-
// write_failed
|
|
468
|
-
return Response.json(
|
|
469
|
-
{
|
|
470
|
-
success: false,
|
|
471
|
-
reason: "write_failed",
|
|
472
|
-
message: result.message,
|
|
473
|
-
...(result.partial_report
|
|
474
|
-
? { partial_report: result.partial_report }
|
|
475
|
-
: {}),
|
|
476
|
-
},
|
|
477
|
-
{ status: 500 },
|
|
478
|
-
);
|
|
538
|
+
return importCommitFailureResponse(result);
|
|
479
539
|
}
|
|
480
540
|
|
|
481
541
|
// Import credentials from the bundle into CES (non-blocking — failures
|
|
482
542
|
// are logged as warnings but do not fail the overall import).
|
|
483
|
-
let credentialsImported:
|
|
484
|
-
| {
|
|
485
|
-
total: number;
|
|
486
|
-
succeeded: number;
|
|
487
|
-
failed: number;
|
|
488
|
-
failedAccounts: string[];
|
|
489
|
-
skippedPlatform: number;
|
|
490
|
-
}
|
|
491
|
-
| undefined;
|
|
543
|
+
let credentialsImported: CredentialImportSummary | undefined;
|
|
492
544
|
|
|
493
545
|
if (validation.entries) {
|
|
494
546
|
const bundleCredentials = extractCredentialsFromBundle(
|
|
495
547
|
validation.entries,
|
|
496
548
|
validation.manifest!,
|
|
497
549
|
);
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
const userCredentials = bundleCredentials.filter(
|
|
502
|
-
(c) => !c.account.startsWith(PLATFORM_CREDENTIAL_PREFIX),
|
|
550
|
+
credentialsImported = await importBundleCredentialsIntoCes(
|
|
551
|
+
bundleCredentials,
|
|
552
|
+
result.report,
|
|
503
553
|
);
|
|
504
|
-
const skippedPlatform = bundleCredentials.length - userCredentials.length;
|
|
505
|
-
if (skippedPlatform > 0) {
|
|
506
|
-
log.info(
|
|
507
|
-
`Skipped ${skippedPlatform} platform credential(s) from import`,
|
|
508
|
-
);
|
|
509
|
-
}
|
|
510
|
-
|
|
511
|
-
if (userCredentials.length > 0) {
|
|
512
|
-
try {
|
|
513
|
-
const credResults = await bulkSetSecureKeysAsync(userCredentials);
|
|
514
|
-
const failedResults = credResults.filter((r) => !r.ok);
|
|
515
|
-
if (failedResults.length > 0) {
|
|
516
|
-
log.warn(
|
|
517
|
-
{ failed: failedResults.map((f) => f.account) },
|
|
518
|
-
"Some credentials failed to import",
|
|
519
|
-
);
|
|
520
|
-
}
|
|
521
|
-
log.info(
|
|
522
|
-
{ total: userCredentials.length, failed: failedResults.length },
|
|
523
|
-
"Credential import complete",
|
|
524
|
-
);
|
|
525
|
-
const succeeded = userCredentials.length - failedResults.length;
|
|
526
|
-
credentialsImported = {
|
|
527
|
-
total: userCredentials.length,
|
|
528
|
-
succeeded,
|
|
529
|
-
failed: failedResults.length,
|
|
530
|
-
failedAccounts: failedResults.map((f) => f.account),
|
|
531
|
-
skippedPlatform,
|
|
532
|
-
};
|
|
533
|
-
if (failedResults.length > 0) {
|
|
534
|
-
result.report.warnings.push(
|
|
535
|
-
`Imported ${succeeded} credential(s), ${failedResults.length} failed`,
|
|
536
|
-
);
|
|
537
|
-
}
|
|
538
|
-
} catch (err) {
|
|
539
|
-
log.warn({ err }, "Credential import failed entirely");
|
|
540
|
-
result.report.warnings.push(
|
|
541
|
-
`Credential import failed: ${err instanceof Error ? err.message : String(err)}`,
|
|
542
|
-
);
|
|
543
|
-
credentialsImported = {
|
|
544
|
-
total: userCredentials.length,
|
|
545
|
-
succeeded: 0,
|
|
546
|
-
failed: userCredentials.length,
|
|
547
|
-
failedAccounts: userCredentials.map((c) => c.account),
|
|
548
|
-
skippedPlatform,
|
|
549
|
-
};
|
|
550
|
-
}
|
|
551
|
-
} else if (skippedPlatform > 0) {
|
|
552
|
-
// All credentials in the bundle were platform credentials — report
|
|
553
|
-
// the skip count even though nothing was sent to CES.
|
|
554
|
-
credentialsImported = {
|
|
555
|
-
total: userCredentials.length,
|
|
556
|
-
succeeded: 0,
|
|
557
|
-
failed: 0,
|
|
558
|
-
failedAccounts: [],
|
|
559
|
-
skippedPlatform,
|
|
560
|
-
};
|
|
561
|
-
}
|
|
562
554
|
}
|
|
563
555
|
|
|
556
|
+
// Reconcile vellum:* metadata against CES so the gateway's
|
|
557
|
+
// readServiceCredentials can still find platform identity values even
|
|
558
|
+
// if Django's post-hatch provisioning raced with the import.
|
|
559
|
+
await reconcileVellumMetadataFromCes(result.report);
|
|
560
|
+
|
|
564
561
|
// Invalidate in-process caches so imported settings.json and trust.json take effect
|
|
565
562
|
invalidateConfigCache();
|
|
566
563
|
clearTrustCache();
|
|
@@ -569,29 +566,571 @@ export async function handleMigrationImport(req: Request): Promise<Response> {
|
|
|
569
566
|
// a newer version. This is non-blocking — the import has already
|
|
570
567
|
// succeeded — but we surface a warning so the caller knows some data may
|
|
571
568
|
// not be fully compatible with this daemon's schema.
|
|
569
|
+
appendNewerMigrationWarningsIfAny(result.report);
|
|
570
|
+
|
|
571
|
+
return importCommitSuccessResponse(result.report, credentialsImported);
|
|
572
|
+
} catch (err) {
|
|
573
|
+
log.error({ err }, "Unexpected error during import commit");
|
|
574
|
+
return httpError(
|
|
575
|
+
"INTERNAL_ERROR",
|
|
576
|
+
err instanceof Error ? err.message : "Unexpected import error",
|
|
577
|
+
500,
|
|
578
|
+
);
|
|
579
|
+
}
|
|
580
|
+
}
|
|
581
|
+
|
|
582
|
+
// ---------------------------------------------------------------------------
|
|
583
|
+
// URL-body variant of POST /v1/migrations/import
|
|
584
|
+
// ---------------------------------------------------------------------------
|
|
585
|
+
|
|
586
|
+
/** 60 minutes — matches the gateway's upstream fetch deadline. */
|
|
587
|
+
const URL_FETCH_TIMEOUT_MS = 60 * 60 * 1000;
|
|
588
|
+
|
|
589
|
+
const MigrationImportUrlBody = z.object({ url: z.string().min(1) });
|
|
590
|
+
|
|
591
|
+
/**
|
|
592
|
+
* Marker attached to errors that originate from the upstream HTTP body
|
|
593
|
+
* stream (peer reset, abort mid-stream, DNS/transport failure after
|
|
594
|
+
* headers were received). The handler's catch/result-mapping path looks
|
|
595
|
+
* for this tag to return 502 `fetch_failed` instead of 500
|
|
596
|
+
* `extraction_failed` for truncated bodies, matching the OpenAPI
|
|
597
|
+
* contract.
|
|
598
|
+
*/
|
|
599
|
+
const kFetchBodyError = Symbol.for("vellum.migrationImport.fetchBodyError");
|
|
600
|
+
|
|
601
|
+
/**
|
|
602
|
+
* Sidecar flag on the wrapper PassThrough indicating that its upstream
|
|
603
|
+
* was torn down by a tagged fetch-body error. Checked after
|
|
604
|
+
* streamCommitImport returns — the importer preserves the error message
|
|
605
|
+
* in `result.reason = "extraction_failed"` but strips the tag.
|
|
606
|
+
*/
|
|
607
|
+
const kFetchBodyTornDown = Symbol.for(
|
|
608
|
+
"vellum.migrationImport.fetchBodyTornDown",
|
|
609
|
+
);
|
|
610
|
+
|
|
611
|
+
function tagFetchBodyError(err: NodeJS.ErrnoException): void {
|
|
612
|
+
(err as unknown as Record<symbol, boolean>)[kFetchBodyError] = true;
|
|
613
|
+
}
|
|
614
|
+
|
|
615
|
+
function isFetchBodyError(err: unknown): boolean {
|
|
616
|
+
if (!err || typeof err !== "object") return false;
|
|
617
|
+
return (err as unknown as Record<symbol, boolean>)[kFetchBodyError] === true;
|
|
618
|
+
}
|
|
619
|
+
|
|
620
|
+
function wasFetchBodyTornDown(stream: PassThrough): boolean {
|
|
621
|
+
return (
|
|
622
|
+
(stream as unknown as Record<symbol, boolean>)[kFetchBodyTornDown] === true
|
|
623
|
+
);
|
|
624
|
+
}
|
|
625
|
+
|
|
626
|
+
/**
|
|
627
|
+
* Test seam: the integration test needs to point the validator at a local
|
|
628
|
+
* HTTP server fixture. Production callers never pass this — the default
|
|
629
|
+
* keeps the validator strict (GCS host, HTTPS only, no explicit port).
|
|
630
|
+
*/
|
|
631
|
+
let urlValidatorOptions: ValidateGcsSignedUrlOptions | undefined;
|
|
632
|
+
|
|
633
|
+
/**
|
|
634
|
+
* Test-only: override the allowed-host list used by the URL-body import
|
|
635
|
+
* handler. Call with `undefined` (or no arguments) to reset to production
|
|
636
|
+
* defaults. This is intentionally not exported from the module's public
|
|
637
|
+
* surface — tests import it directly from this file.
|
|
638
|
+
*/
|
|
639
|
+
export function _setUrlImportValidatorOptionsForTests(
|
|
640
|
+
options: ValidateGcsSignedUrlOptions | undefined,
|
|
641
|
+
): void {
|
|
642
|
+
urlValidatorOptions = options;
|
|
643
|
+
}
|
|
644
|
+
|
|
645
|
+
/**
|
|
646
|
+
* Handle a JSON `{ "url": "..." }` body on POST /v1/migrations/import.
|
|
647
|
+
*
|
|
648
|
+
* Fetches the signed URL, pipes the response body through the streaming
|
|
649
|
+
* importer, and returns the same response shapes as the raw-bytes path.
|
|
650
|
+
* The signed URL is never logged or included in error responses — only the
|
|
651
|
+
* extracted host and path make it into logs.
|
|
652
|
+
*/
|
|
653
|
+
async function handleMigrationImportFromUrl(req: Request): Promise<Response> {
|
|
654
|
+
// ── 1. Parse JSON body ────────────────────────────────────────────────
|
|
655
|
+
let rawBody: unknown;
|
|
656
|
+
try {
|
|
657
|
+
rawBody = await req.json();
|
|
658
|
+
} catch (err) {
|
|
659
|
+
log.warn(
|
|
660
|
+
{ err: err instanceof Error ? err.message : String(err) },
|
|
661
|
+
"Failed to parse JSON body on migration import URL request",
|
|
662
|
+
);
|
|
663
|
+
return httpError("BAD_REQUEST", "Invalid JSON body", 400);
|
|
664
|
+
}
|
|
665
|
+
|
|
666
|
+
const parsed = MigrationImportUrlBody.safeParse(rawBody);
|
|
667
|
+
if (!parsed.success) {
|
|
668
|
+
return httpError(
|
|
669
|
+
"BAD_REQUEST",
|
|
670
|
+
"Request body must be { url: string } with a non-empty url",
|
|
671
|
+
400,
|
|
672
|
+
);
|
|
673
|
+
}
|
|
674
|
+
|
|
675
|
+
// ── 2. Validate the URL (defense-in-depth; never log `parsed.data.url`).
|
|
676
|
+
const validated = validateGcsSignedUrl(parsed.data.url, urlValidatorOptions);
|
|
677
|
+
if (!validated.ok) {
|
|
678
|
+
// `reason` is a stable enum string and safe to include. The raw URL is
|
|
679
|
+
// not — it may contain a live signature. Callers get the reason so they
|
|
680
|
+
// can correct the URL without leaking anything into observability.
|
|
681
|
+
log.warn({ reason: validated.reason }, "Rejected migration import URL");
|
|
682
|
+
return httpError("BAD_REQUEST", `Invalid URL: ${validated.reason}`, 400);
|
|
683
|
+
}
|
|
684
|
+
|
|
685
|
+
log.info(
|
|
686
|
+
{ host: validated.host, path: validated.path },
|
|
687
|
+
"migration import from URL",
|
|
688
|
+
);
|
|
689
|
+
|
|
690
|
+
const startedAt = Date.now();
|
|
691
|
+
|
|
692
|
+
// ── 3. Fetch the URL ──────────────────────────────────────────────────
|
|
693
|
+
let upstream: Response;
|
|
694
|
+
try {
|
|
695
|
+
upstream = await fetch(parsed.data.url, {
|
|
696
|
+
signal: AbortSignal.timeout(URL_FETCH_TIMEOUT_MS),
|
|
697
|
+
// SSRF guard: `validateGcsSignedUrl` only vetted the initial URL.
|
|
698
|
+
// Default fetch behavior follows 3xx responses, which would let a
|
|
699
|
+
// validated `storage.googleapis.com` URL redirect to an arbitrary
|
|
700
|
+
// host and bypass the allowlist. Reject redirects so we only ever
|
|
701
|
+
// read bytes from the URL the caller handed us.
|
|
702
|
+
redirect: "error",
|
|
703
|
+
});
|
|
704
|
+
} catch (err) {
|
|
705
|
+
log.error(
|
|
706
|
+
{
|
|
707
|
+
host: validated.host,
|
|
708
|
+
path: validated.path,
|
|
709
|
+
err: err instanceof Error ? err.message : String(err),
|
|
710
|
+
},
|
|
711
|
+
"Failed to fetch migration import URL",
|
|
712
|
+
);
|
|
713
|
+
return Response.json(
|
|
714
|
+
{ success: false, reason: "fetch_failed" },
|
|
715
|
+
{ status: 502 },
|
|
716
|
+
);
|
|
717
|
+
}
|
|
718
|
+
|
|
719
|
+
if (!upstream.ok) {
|
|
720
|
+
log.error(
|
|
721
|
+
{
|
|
722
|
+
host: validated.host,
|
|
723
|
+
path: validated.path,
|
|
724
|
+
upstream_status: upstream.status,
|
|
725
|
+
},
|
|
726
|
+
"Migration import URL fetch returned non-2xx",
|
|
727
|
+
);
|
|
728
|
+
// Drain the body so the underlying socket can be released promptly.
|
|
572
729
|
try {
|
|
573
|
-
|
|
574
|
-
if (migrationValidation.unknownCheckpoints.length > 0) {
|
|
575
|
-
result.report.warnings.push(
|
|
576
|
-
`Imported data contains ${migrationValidation.unknownCheckpoints.length} migration(s) from a newer version. Some data may not be fully compatible.`,
|
|
577
|
-
);
|
|
578
|
-
}
|
|
730
|
+
await upstream.body?.cancel();
|
|
579
731
|
} catch {
|
|
580
|
-
|
|
732
|
+
/* best effort */
|
|
733
|
+
}
|
|
734
|
+
return Response.json(
|
|
735
|
+
{
|
|
736
|
+
success: false,
|
|
737
|
+
reason: "fetch_failed",
|
|
738
|
+
upstream_status: upstream.status,
|
|
739
|
+
},
|
|
740
|
+
{ status: 502 },
|
|
741
|
+
);
|
|
742
|
+
}
|
|
743
|
+
|
|
744
|
+
if (!upstream.body) {
|
|
745
|
+
log.error(
|
|
746
|
+
{ host: validated.host, path: validated.path },
|
|
747
|
+
"Migration import URL fetch returned no body",
|
|
748
|
+
);
|
|
749
|
+
return Response.json(
|
|
750
|
+
{ success: false, reason: "fetch_failed" },
|
|
751
|
+
{ status: 502 },
|
|
752
|
+
);
|
|
753
|
+
}
|
|
754
|
+
|
|
755
|
+
// ── 4. Stream the response through the importer ──────────────────────
|
|
756
|
+
// Convert the WHATWG ReadableStream from fetch() into a Node Readable so
|
|
757
|
+
// the tar-stream / gunzip / hash-verifier pipeline inside
|
|
758
|
+
// streamCommitImport can consume it via `.pipe()`.
|
|
759
|
+
const upstreamNodeStream = Readable.fromWeb(
|
|
760
|
+
upstream.body as unknown as import("node:stream/web").ReadableStream<Uint8Array>,
|
|
761
|
+
);
|
|
762
|
+
|
|
763
|
+
// Wrap the upstream stream in a PassThrough that tags any error bubbling
|
|
764
|
+
// from the upstream HTTP body (peer reset, abort mid-stream, etc.) with a
|
|
765
|
+
// known symbol. When that tagged error surfaces out of
|
|
766
|
+
// streamCommitImport's gunzip/tar pipeline, we can distinguish it from a
|
|
767
|
+
// legitimate bundle-format failure and map it to 502 fetch_failed instead
|
|
768
|
+
// of 500 extraction_failed — matching the OpenAPI contract for the URL
|
|
769
|
+
// body shape. We also propagate errors from the wrapper back to the
|
|
770
|
+
// upstream stream so its underlying connection is torn down cleanly.
|
|
771
|
+
//
|
|
772
|
+
// Bun's `Readable.fromWeb(fetchBody)` does NOT emit `'error'` when the
|
|
773
|
+
// TCP socket is torn down mid-response — it just emits `'close'` with
|
|
774
|
+
// no final `'end'`. We therefore track BOTH signals:
|
|
775
|
+
// • explicit `'error'` → tag the error, destroy the wrapper.
|
|
776
|
+
// • premature `'close'` → synthesize an error, tag it, destroy the
|
|
777
|
+
// wrapper. "Premature" = close fired without end first.
|
|
778
|
+
const taggedSource = new PassThrough();
|
|
779
|
+
let upstreamEnded = false;
|
|
780
|
+
// True once the importer (or any local consumer) initiates a teardown of
|
|
781
|
+
// `taggedSource`. The subsequent `close` on `upstreamNodeStream` is then a
|
|
782
|
+
// cascaded effect of our own teardown, NOT a real upstream failure — so
|
|
783
|
+
// we must NOT tag it as a fetch-body error, or local validation /
|
|
784
|
+
// extraction errors would be masked as 502 fetch_failed.
|
|
785
|
+
let localTeardownInitiated = false;
|
|
786
|
+
upstreamNodeStream.on("end", () => {
|
|
787
|
+
upstreamEnded = true;
|
|
788
|
+
});
|
|
789
|
+
upstreamNodeStream.on("error", (err: NodeJS.ErrnoException) => {
|
|
790
|
+
tagFetchBodyError(err);
|
|
791
|
+
(taggedSource as unknown as Record<symbol, boolean>)[kFetchBodyTornDown] =
|
|
792
|
+
true;
|
|
793
|
+
taggedSource.destroy(err);
|
|
794
|
+
});
|
|
795
|
+
upstreamNodeStream.on("close", () => {
|
|
796
|
+
if (upstreamEnded) return;
|
|
797
|
+
// A local teardown path closed us; don't treat this as an upstream
|
|
798
|
+
// failure. The real error (validation / extraction / hash mismatch) is
|
|
799
|
+
// already propagating through `streamCommitImport`'s result.
|
|
800
|
+
if (localTeardownInitiated) return;
|
|
801
|
+
const err = new Error(
|
|
802
|
+
"Upstream body stream closed before end",
|
|
803
|
+
) as NodeJS.ErrnoException;
|
|
804
|
+
err.code = "ERR_UPSTREAM_BODY_CLOSED";
|
|
805
|
+
tagFetchBodyError(err);
|
|
806
|
+
(taggedSource as unknown as Record<symbol, boolean>)[kFetchBodyTornDown] =
|
|
807
|
+
true;
|
|
808
|
+
taggedSource.destroy(err);
|
|
809
|
+
});
|
|
810
|
+
upstreamNodeStream.pipe(taggedSource);
|
|
811
|
+
// Propagate wrapper teardown back to the upstream fetch body. When the
|
|
812
|
+
// streaming importer hits a validation/extraction error, it destroys
|
|
813
|
+
// `source` (which is `taggedSource`). Without this listener the
|
|
814
|
+
// `Readable.fromWeb(fetchBody)` stream would stay alive and continue
|
|
815
|
+
// buffering the remote response in the background until GC or the
|
|
816
|
+
// 60-minute timeout — a socket/bandwidth leak for any non-upstream error
|
|
817
|
+
// (malformed bundle, hash mismatch, size cap, etc.). We set
|
|
818
|
+
// `localTeardownInitiated` BEFORE destroying upstream so the resulting
|
|
819
|
+
// cascaded `close` on `upstreamNodeStream` isn't misclassified as a real
|
|
820
|
+
// upstream failure (which would return 502 fetch_failed and mask the
|
|
821
|
+
// actual validation error).
|
|
822
|
+
taggedSource.on("close", () => {
|
|
823
|
+
if (!upstreamNodeStream.destroyed) {
|
|
824
|
+
localTeardownInitiated = true;
|
|
825
|
+
upstreamNodeStream.destroy();
|
|
581
826
|
}
|
|
827
|
+
});
|
|
582
828
|
|
|
583
|
-
|
|
584
|
-
|
|
585
|
-
|
|
829
|
+
const pathResolver = new DefaultPathResolver(
|
|
830
|
+
getWorkspaceDir(),
|
|
831
|
+
getWorkspaceHooksDir(),
|
|
832
|
+
);
|
|
833
|
+
|
|
834
|
+
// streamCommitImport does its own resetDb() internally before the atomic
|
|
835
|
+
// swap, so we don't need to call it here.
|
|
836
|
+
let result: ImportCommitResult;
|
|
837
|
+
// Track credential-import outcome for inclusion in the success response.
|
|
838
|
+
// The streaming importer invokes our callback only after the atomic swap,
|
|
839
|
+
// so filling this in here is safe.
|
|
840
|
+
let credentialsImported: CredentialImportSummary | undefined;
|
|
841
|
+
// Per-invocation warning collector — scoped to this request so concurrent
|
|
842
|
+
// URL imports can't trample each other's warnings.
|
|
843
|
+
const credentialImportWarningSink: CredentialWarningSink = { warnings: [] };
|
|
844
|
+
|
|
845
|
+
try {
|
|
846
|
+
result = await streamCommitImport({
|
|
847
|
+
source: taggedSource,
|
|
848
|
+
pathResolver,
|
|
849
|
+
workspaceDir: getWorkspaceDir(),
|
|
850
|
+
importCredentials: async (bundleCredentials) => {
|
|
851
|
+
// We can't mutate `result.report.warnings` in place here — the
|
|
852
|
+
// streaming importer hasn't returned its report yet. Accumulate
|
|
853
|
+
// into a sidecar and merge into the final report below.
|
|
854
|
+
credentialsImported = await importBundleCredentialsIntoCes(
|
|
855
|
+
bundleCredentials,
|
|
856
|
+
credentialImportWarningSink,
|
|
857
|
+
);
|
|
858
|
+
},
|
|
586
859
|
});
|
|
587
860
|
} catch (err) {
|
|
588
|
-
|
|
861
|
+
if (isFetchBodyError(err)) {
|
|
862
|
+
log.error(
|
|
863
|
+
{
|
|
864
|
+
host: validated.host,
|
|
865
|
+
path: validated.path,
|
|
866
|
+
err: err instanceof Error ? err.message : String(err),
|
|
867
|
+
},
|
|
868
|
+
"Upstream body stream failed mid-import",
|
|
869
|
+
);
|
|
870
|
+
return Response.json(
|
|
871
|
+
{ success: false, reason: "fetch_failed" },
|
|
872
|
+
{ status: 502 },
|
|
873
|
+
);
|
|
874
|
+
}
|
|
875
|
+
log.error(
|
|
876
|
+
{
|
|
877
|
+
host: validated.host,
|
|
878
|
+
path: validated.path,
|
|
879
|
+
err: err instanceof Error ? err.message : String(err),
|
|
880
|
+
},
|
|
881
|
+
"streamCommitImport threw during URL-body import",
|
|
882
|
+
);
|
|
589
883
|
return httpError(
|
|
590
884
|
"INTERNAL_ERROR",
|
|
591
885
|
err instanceof Error ? err.message : "Unexpected import error",
|
|
592
886
|
500,
|
|
593
887
|
);
|
|
594
888
|
}
|
|
889
|
+
|
|
890
|
+
if (!result.ok) {
|
|
891
|
+
// streamCommitImport swallows the raw cause and maps any
|
|
892
|
+
// non-validation throw to `extraction_failed`. If the cause was an
|
|
893
|
+
// upstream body failure that we tagged at the source, surface the
|
|
894
|
+
// tag through the result (the importer preserves the message) by
|
|
895
|
+
// detecting the latched flag on the wrapper stream.
|
|
896
|
+
if (wasFetchBodyTornDown(taggedSource)) {
|
|
897
|
+
log.error(
|
|
898
|
+
{
|
|
899
|
+
host: validated.host,
|
|
900
|
+
path: validated.path,
|
|
901
|
+
reason: result.reason,
|
|
902
|
+
},
|
|
903
|
+
"Upstream body stream failed mid-import (detected via result)",
|
|
904
|
+
);
|
|
905
|
+
return Response.json(
|
|
906
|
+
{ success: false, reason: "fetch_failed" },
|
|
907
|
+
{ status: 502 },
|
|
908
|
+
);
|
|
909
|
+
}
|
|
910
|
+
log.warn(
|
|
911
|
+
{
|
|
912
|
+
host: validated.host,
|
|
913
|
+
path: validated.path,
|
|
914
|
+
reason: result.reason,
|
|
915
|
+
},
|
|
916
|
+
"streamCommitImport returned failure during URL-body import",
|
|
917
|
+
);
|
|
918
|
+
return importCommitFailureResponse(result);
|
|
919
|
+
}
|
|
920
|
+
|
|
921
|
+
// Merge any warnings accumulated by the credential-import callback into
|
|
922
|
+
// the final report.
|
|
923
|
+
if (credentialImportWarningSink.warnings.length > 0) {
|
|
924
|
+
result.report.warnings.push(...credentialImportWarningSink.warnings);
|
|
925
|
+
}
|
|
926
|
+
|
|
927
|
+
// Reconcile vellum:* metadata against CES so the gateway's
|
|
928
|
+
// readServiceCredentials can still find platform identity values even
|
|
929
|
+
// if Django's post-hatch provisioning raced with the streaming import
|
|
930
|
+
// (its metadata upsert may have landed in the backup-dir copy that the
|
|
931
|
+
// swap pushed aside, while its CES write survived on the separate
|
|
932
|
+
// volume).
|
|
933
|
+
await reconcileVellumMetadataFromCes(result.report);
|
|
934
|
+
|
|
935
|
+
// streamCommitImport already invalidated config + trust caches inside its
|
|
936
|
+
// post-swap cleanup. We only need to check whether the newly-imported DB
|
|
937
|
+
// carries migration checkpoints from a newer daemon version.
|
|
938
|
+
appendNewerMigrationWarningsIfAny(result.report);
|
|
939
|
+
|
|
940
|
+
const elapsedMs = Date.now() - startedAt;
|
|
941
|
+
log.info(
|
|
942
|
+
{
|
|
943
|
+
host: validated.host,
|
|
944
|
+
path: validated.path,
|
|
945
|
+
files_written: result.report.summary.files_created,
|
|
946
|
+
bytes_written: result.report.files.reduce((n, f) => n + f.size, 0),
|
|
947
|
+
elapsed_ms: elapsedMs,
|
|
948
|
+
},
|
|
949
|
+
"Migration import from URL complete",
|
|
950
|
+
);
|
|
951
|
+
|
|
952
|
+
return importCommitSuccessResponse(result.report, credentialsImported);
|
|
953
|
+
}
|
|
954
|
+
|
|
955
|
+
// ---------------------------------------------------------------------------
|
|
956
|
+
// Shared helpers for raw-bytes and URL paths
|
|
957
|
+
// ---------------------------------------------------------------------------
|
|
958
|
+
|
|
959
|
+
interface CredentialImportSummary {
|
|
960
|
+
total: number;
|
|
961
|
+
succeeded: number;
|
|
962
|
+
failed: number;
|
|
963
|
+
failedAccounts: string[];
|
|
964
|
+
skippedPlatform: number;
|
|
965
|
+
}
|
|
966
|
+
|
|
967
|
+
/**
|
|
968
|
+
* Minimal surface the credential-import helper needs to stash warnings —
|
|
969
|
+
* either a full `ImportCommitReport` (raw-bytes path, after commitImport
|
|
970
|
+
* returns) or an ephemeral per-request collector (streaming path, where the
|
|
971
|
+
* report doesn't exist yet when the callback fires).
|
|
972
|
+
*/
|
|
973
|
+
interface CredentialWarningSink {
|
|
974
|
+
warnings: string[];
|
|
975
|
+
}
|
|
976
|
+
|
|
977
|
+
/**
|
|
978
|
+
* Filter platform-identity (vellum:*) credentials out of the bundle, push
|
|
979
|
+
* user credentials into CES via `bulkSetSecureKeysAsync`, and return a
|
|
980
|
+
* structured summary. Never throws — CES failures become report warnings.
|
|
981
|
+
*/
|
|
982
|
+
async function importBundleCredentialsIntoCes(
|
|
983
|
+
bundleCredentials: Array<{ account: string; value: string }>,
|
|
984
|
+
warningSink: CredentialWarningSink,
|
|
985
|
+
): Promise<CredentialImportSummary | undefined> {
|
|
986
|
+
// Filter out platform-identity credentials (vellum:*) — these are
|
|
987
|
+
// environment-specific and must not overwrite the target's own identity.
|
|
988
|
+
const userCredentials = bundleCredentials.filter(
|
|
989
|
+
(c) => !c.account.startsWith(PLATFORM_CREDENTIAL_PREFIX),
|
|
990
|
+
);
|
|
991
|
+
const skippedPlatform = bundleCredentials.length - userCredentials.length;
|
|
992
|
+
if (skippedPlatform > 0) {
|
|
993
|
+
log.info(`Skipped ${skippedPlatform} platform credential(s) from import`);
|
|
994
|
+
}
|
|
995
|
+
|
|
996
|
+
if (userCredentials.length === 0) {
|
|
997
|
+
if (skippedPlatform > 0) {
|
|
998
|
+
// All credentials in the bundle were platform credentials — report
|
|
999
|
+
// the skip count even though nothing was sent to CES.
|
|
1000
|
+
return {
|
|
1001
|
+
total: 0,
|
|
1002
|
+
succeeded: 0,
|
|
1003
|
+
failed: 0,
|
|
1004
|
+
failedAccounts: [],
|
|
1005
|
+
skippedPlatform,
|
|
1006
|
+
};
|
|
1007
|
+
}
|
|
1008
|
+
return undefined;
|
|
1009
|
+
}
|
|
1010
|
+
|
|
1011
|
+
try {
|
|
1012
|
+
const credResults = await bulkSetSecureKeysAsync(userCredentials);
|
|
1013
|
+
const failedResults = credResults.filter((r) => !r.ok);
|
|
1014
|
+
if (failedResults.length > 0) {
|
|
1015
|
+
log.warn(
|
|
1016
|
+
{ failed: failedResults.map((f) => f.account) },
|
|
1017
|
+
"Some credentials failed to import",
|
|
1018
|
+
);
|
|
1019
|
+
}
|
|
1020
|
+
log.info(
|
|
1021
|
+
{ total: userCredentials.length, failed: failedResults.length },
|
|
1022
|
+
"Credential import complete",
|
|
1023
|
+
);
|
|
1024
|
+
const succeeded = userCredentials.length - failedResults.length;
|
|
1025
|
+
if (failedResults.length > 0) {
|
|
1026
|
+
warningSink.warnings.push(
|
|
1027
|
+
`Imported ${succeeded} credential(s), ${failedResults.length} failed`,
|
|
1028
|
+
);
|
|
1029
|
+
}
|
|
1030
|
+
return {
|
|
1031
|
+
total: userCredentials.length,
|
|
1032
|
+
succeeded,
|
|
1033
|
+
failed: failedResults.length,
|
|
1034
|
+
failedAccounts: failedResults.map((f) => f.account),
|
|
1035
|
+
skippedPlatform,
|
|
1036
|
+
};
|
|
1037
|
+
} catch (err) {
|
|
1038
|
+
log.warn({ err }, "Credential import failed entirely");
|
|
1039
|
+
warningSink.warnings.push(
|
|
1040
|
+
`Credential import failed: ${err instanceof Error ? err.message : String(err)}`,
|
|
1041
|
+
);
|
|
1042
|
+
return {
|
|
1043
|
+
total: userCredentials.length,
|
|
1044
|
+
succeeded: 0,
|
|
1045
|
+
failed: userCredentials.length,
|
|
1046
|
+
failedAccounts: userCredentials.map((c) => c.account),
|
|
1047
|
+
skippedPlatform,
|
|
1048
|
+
};
|
|
1049
|
+
}
|
|
1050
|
+
}
|
|
1051
|
+
|
|
1052
|
+
/**
|
|
1053
|
+
* Append a warning to `report` when the newly-imported database contains
|
|
1054
|
+
* migration checkpoints from a daemon version newer than this one. Silent
|
|
1055
|
+
* on any validation error — the import has already succeeded.
|
|
1056
|
+
*
|
|
1057
|
+
* Gated on the report's own file counts: if the import didn't create or
|
|
1058
|
+
* overwrite any workspace files (no-swap success — e.g. credentials-only
|
|
1059
|
+
* bundle, all-skipped legacy bundle), the live DB is unchanged and any
|
|
1060
|
+
* "newer migrations" detected there came from the existing workspace,
|
|
1061
|
+
* NOT from the imported bundle. Attributing them to the bundle would be a
|
|
1062
|
+
* false positive, so skip the check entirely in that case.
|
|
1063
|
+
*/
|
|
1064
|
+
function appendNewerMigrationWarningsIfAny(report: ImportCommitReport): void {
|
|
1065
|
+
if (report.summary.files_created + report.summary.files_overwritten === 0) {
|
|
1066
|
+
return;
|
|
1067
|
+
}
|
|
1068
|
+
try {
|
|
1069
|
+
const migrationValidation = validateMigrationState(getDb());
|
|
1070
|
+
if (migrationValidation.unknownCheckpoints.length > 0) {
|
|
1071
|
+
report.warnings.push(
|
|
1072
|
+
`Imported data contains ${migrationValidation.unknownCheckpoints.length} migration(s) from a newer version. Some data may not be fully compatible.`,
|
|
1073
|
+
);
|
|
1074
|
+
}
|
|
1075
|
+
} catch {
|
|
1076
|
+
// Don't fail the import if validation itself errors
|
|
1077
|
+
}
|
|
1078
|
+
}
|
|
1079
|
+
|
|
1080
|
+
/**
|
|
1081
|
+
* Build a success Response from an ImportCommitReport. The report fields
|
|
1082
|
+
* are spread at the top level, with an optional `credentialsImported`
|
|
1083
|
+
* summary alongside.
|
|
1084
|
+
*/
|
|
1085
|
+
function importCommitSuccessResponse(
|
|
1086
|
+
report: ImportCommitReport,
|
|
1087
|
+
credentialsImported: CredentialImportSummary | undefined,
|
|
1088
|
+
): Response {
|
|
1089
|
+
return Response.json({
|
|
1090
|
+
...report,
|
|
1091
|
+
...(credentialsImported ? { credentialsImported } : {}),
|
|
1092
|
+
});
|
|
1093
|
+
}
|
|
1094
|
+
|
|
1095
|
+
/**
|
|
1096
|
+
* Map an `ImportCommitResult` failure to the Response shape callers of
|
|
1097
|
+
* `POST /v1/migrations/import` depend on. Status codes and body shapes
|
|
1098
|
+
* are part of the public contract and must remain stable.
|
|
1099
|
+
*/
|
|
1100
|
+
function importCommitFailureResponse(
|
|
1101
|
+
result: Extract<ImportCommitResult, { ok: false }>,
|
|
1102
|
+
): Response {
|
|
1103
|
+
if (result.reason === "validation_failed") {
|
|
1104
|
+
return Response.json({
|
|
1105
|
+
success: false,
|
|
1106
|
+
reason: "validation_failed",
|
|
1107
|
+
errors: result.errors,
|
|
1108
|
+
});
|
|
1109
|
+
}
|
|
1110
|
+
|
|
1111
|
+
if (result.reason === "extraction_failed") {
|
|
1112
|
+
return Response.json(
|
|
1113
|
+
{
|
|
1114
|
+
success: false,
|
|
1115
|
+
reason: "extraction_failed",
|
|
1116
|
+
message: result.message,
|
|
1117
|
+
},
|
|
1118
|
+
{ status: 500 },
|
|
1119
|
+
);
|
|
1120
|
+
}
|
|
1121
|
+
|
|
1122
|
+
// write_failed
|
|
1123
|
+
return Response.json(
|
|
1124
|
+
{
|
|
1125
|
+
success: false,
|
|
1126
|
+
reason: "write_failed",
|
|
1127
|
+
message: result.message,
|
|
1128
|
+
...(result.partial_report
|
|
1129
|
+
? { partial_report: result.partial_report }
|
|
1130
|
+
: {}),
|
|
1131
|
+
},
|
|
1132
|
+
{ status: 500 },
|
|
1133
|
+
);
|
|
595
1134
|
}
|
|
596
1135
|
|
|
597
1136
|
// ---------------------------------------------------------------------------
|
|
@@ -647,8 +1186,62 @@ export function migrationRouteDefinitions(): RouteDefinition[] {
|
|
|
647
1186
|
method: "POST",
|
|
648
1187
|
summary: "Import a .vbundle archive",
|
|
649
1188
|
description:
|
|
650
|
-
"Commit a .vbundle archive import to disk — destructive.
|
|
1189
|
+
"Commit a .vbundle archive import to disk — destructive. Accepts the bundle as raw bytes (application/octet-stream), multipart/form-data, or a JSON body carrying a signed URL the daemon fetches and streams through the importer.",
|
|
651
1190
|
tags: ["migrations"],
|
|
1191
|
+
requestBodies: [
|
|
1192
|
+
{
|
|
1193
|
+
contentType: "application/octet-stream",
|
|
1194
|
+
schema: {
|
|
1195
|
+
type: "string",
|
|
1196
|
+
format: "binary",
|
|
1197
|
+
description: "Raw .vbundle archive bytes.",
|
|
1198
|
+
},
|
|
1199
|
+
},
|
|
1200
|
+
{
|
|
1201
|
+
contentType: "multipart/form-data",
|
|
1202
|
+
schema: {
|
|
1203
|
+
type: "object",
|
|
1204
|
+
properties: {
|
|
1205
|
+
file: {
|
|
1206
|
+
type: "string",
|
|
1207
|
+
format: "binary",
|
|
1208
|
+
description: "The .vbundle archive uploaded as a file field.",
|
|
1209
|
+
},
|
|
1210
|
+
},
|
|
1211
|
+
required: ["file"],
|
|
1212
|
+
},
|
|
1213
|
+
},
|
|
1214
|
+
{
|
|
1215
|
+
contentType: "application/json",
|
|
1216
|
+
schema: {
|
|
1217
|
+
type: "object",
|
|
1218
|
+
properties: {
|
|
1219
|
+
url: {
|
|
1220
|
+
type: "string",
|
|
1221
|
+
format: "uri",
|
|
1222
|
+
description:
|
|
1223
|
+
"A signed GCS URL pointing to the .vbundle archive. The daemon fetches the URL and streams the body through the importer.",
|
|
1224
|
+
},
|
|
1225
|
+
},
|
|
1226
|
+
required: ["url"],
|
|
1227
|
+
},
|
|
1228
|
+
},
|
|
1229
|
+
],
|
|
1230
|
+
additionalResponses: {
|
|
1231
|
+
"502": {
|
|
1232
|
+
description:
|
|
1233
|
+
"Upstream fetch failed (URL body only). Body shape: { success: false, reason: 'fetch_failed', upstream_status?: number }.",
|
|
1234
|
+
schema: {
|
|
1235
|
+
type: "object",
|
|
1236
|
+
properties: {
|
|
1237
|
+
success: { type: "boolean" },
|
|
1238
|
+
reason: { type: "string", enum: ["fetch_failed"] },
|
|
1239
|
+
upstream_status: { type: "integer" },
|
|
1240
|
+
},
|
|
1241
|
+
required: ["success", "reason"],
|
|
1242
|
+
},
|
|
1243
|
+
},
|
|
1244
|
+
},
|
|
652
1245
|
responseBody: z.object({
|
|
653
1246
|
success: z.boolean(),
|
|
654
1247
|
summary: z.object({}).passthrough(),
|