@vellumai/assistant 0.10.9 → 0.10.10-dev.202607162206.d08e98e
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/ARCHITECTURE.md +1 -1
- package/Dockerfile +8 -0
- package/docs/activation-funnel-telemetry.md +13 -7
- package/node_modules/@vellumai/ces-client/node_modules/@vellumai/service-contracts/package.json +1 -0
- package/node_modules/@vellumai/ces-client/node_modules/@vellumai/service-contracts/src/__tests__/redacted-credential.test.ts +200 -0
- package/node_modules/@vellumai/ces-client/node_modules/@vellumai/service-contracts/src/redacted-credential.ts +226 -0
- package/node_modules/@vellumai/gateway-client/node_modules/@vellumai/service-contracts/package.json +1 -0
- package/node_modules/@vellumai/gateway-client/node_modules/@vellumai/service-contracts/src/__tests__/redacted-credential.test.ts +200 -0
- package/node_modules/@vellumai/gateway-client/node_modules/@vellumai/service-contracts/src/redacted-credential.ts +226 -0
- package/node_modules/@vellumai/service-contracts/package.json +1 -0
- package/node_modules/@vellumai/service-contracts/src/__tests__/redacted-credential.test.ts +200 -0
- package/node_modules/@vellumai/service-contracts/src/redacted-credential.ts +226 -0
- package/openapi.yaml +530 -107
- package/package.json +1 -1
- package/scripts/generate-openapi.ts +8 -0
- package/src/__tests__/activation-early-marking.test.ts +6 -5
- package/src/__tests__/agent-loop-override-profile.test.ts +22 -25
- package/src/__tests__/agent-wake-override-profile.test.ts +21 -48
- package/src/__tests__/app-builder-tool-scripts.test.ts +0 -1
- package/src/__tests__/app-bundler.test.ts +4 -12
- package/src/__tests__/app-executors.test.ts +2 -49
- package/src/__tests__/app-routes-csp.test.ts +114 -146
- package/src/__tests__/auth-fallback-events-store.test.ts +8 -1
- package/src/__tests__/build-persisted-content.test.ts +96 -0
- package/src/__tests__/bundle-scanner.test.ts +27 -1
- package/src/__tests__/call-controller.test.ts +291 -0
- package/src/__tests__/call-site-routing-connection-auto-resolve.test.ts +165 -0
- package/src/__tests__/chat-credential-redaction.test.ts +1395 -0
- package/src/__tests__/chat-reveal-guard-priming.test.ts +791 -0
- package/src/__tests__/compaction.benchmark.test.ts +2 -1
- package/src/__tests__/compactor-image-manifest-trust.test.ts +50 -0
- package/src/__tests__/config-loader-backfill.test.ts +9 -4
- package/src/__tests__/config-schema-cmd.test.ts +10 -11
- package/src/__tests__/config-schema.test.ts +190 -257
- package/src/__tests__/conversation-agent-loop-fatal-cleanup.test.ts +208 -0
- package/src/__tests__/conversation-agent-loop-overflow.test.ts +5 -19
- package/src/__tests__/conversation-agent-loop.test.ts +22 -9
- package/src/__tests__/conversation-error.test.ts +31 -0
- package/src/__tests__/conversation-load-history-repair.test.ts +110 -0
- package/src/__tests__/conversation-process-callsite.test.ts +12 -19
- package/src/__tests__/conversation-routes-slash-commands.test.ts +7 -21
- package/src/__tests__/conversation-summarize-route.test.ts +36 -44
- package/src/__tests__/conversation-surfaces-action-delivery.test.ts +1 -0
- package/src/__tests__/conversation-surfaces-activation-emit.test.ts +7 -7
- package/src/__tests__/conversation-tool-setup-app-refresh.test.ts +0 -1
- package/src/__tests__/conversation-tool-setup-attribution.test.ts +0 -1
- package/src/__tests__/conversation-usage.test.ts +4 -12
- package/src/__tests__/credential-routes.test.ts +250 -0
- package/src/__tests__/credential-security-invariants.test.ts +3 -0
- package/src/__tests__/db-migration-rollback.test.ts +22 -0
- package/src/__tests__/empty-response-hook.test.ts +186 -1
- package/src/__tests__/external-plugin-loader.test.ts +11 -5
- package/src/__tests__/heartbeat-service.test.ts +0 -28
- package/src/__tests__/host-shell-tool.test.ts +2 -0
- package/src/__tests__/inactive-tool-error-messages.test.ts +2 -2
- package/src/__tests__/inference-no-mode-boot-e2e.test.ts +52 -8
- package/src/__tests__/internal-telemetry-routes.test.ts +23 -4
- package/src/__tests__/invite-routes-http.test.ts +12 -16
- package/src/__tests__/list-all-apps.test.ts +0 -4
- package/src/__tests__/llm-context-resolution.test.ts +32 -56
- package/src/__tests__/llm-request-log-turn-query.test.ts +109 -0
- package/src/__tests__/llm-resolver-override-or-default.test.ts +3 -52
- package/src/__tests__/llm-resolver.test.ts +342 -602
- package/src/__tests__/llm-schema.test.ts +79 -37
- package/src/__tests__/max-tokens-continue-hook.test.ts +19 -0
- package/src/__tests__/media-stream-output.test.ts +259 -3
- package/src/__tests__/media-stream-server-integration.test.ts +22 -1
- package/src/__tests__/media-stream-stt-session.test.ts +47 -0
- package/src/__tests__/memory-jobs-worker-cleanup-cadence.test.ts +33 -0
- package/src/__tests__/memory-recall-log-store.test.ts +47 -13
- package/src/__tests__/mock-gateway-ipc.ts +46 -1
- package/src/__tests__/mtime-cache.test.ts +61 -0
- package/src/__tests__/navigate-settings-tab.test.ts +2 -0
- package/src/__tests__/normalize-onboarding.test.ts +33 -0
- package/src/__tests__/onboarding-persona-write.test.ts +26 -0
- package/src/__tests__/plugin-api-resolve-credential.test.ts +140 -0
- package/src/__tests__/plugin-app-serve-routes.test.ts +166 -14
- package/src/__tests__/plugin-import-boundary-reverse-guard.test.ts +2 -0
- package/src/__tests__/post-turn-tool-result-truncation.test.ts +38 -0
- package/src/__tests__/provider-commit-message-generator.test.ts +27 -20
- package/src/__tests__/provider-connections-backfill.test.ts +138 -0
- package/src/__tests__/provider-platform-proxy-integration.test.ts +10 -31
- package/src/__tests__/provider-registry-ollama.test.ts +8 -18
- package/src/__tests__/provider-send-message-override-profile.test.ts +23 -23
- package/src/__tests__/provider-usage-tracking.test.ts +9 -19
- package/src/__tests__/prune-old-conversations-job.test.ts +12 -0
- package/src/__tests__/published-app-updater.test.ts +22 -16
- package/src/__tests__/registry.test.ts +5 -16
- package/src/__tests__/retry-openrouter-only-normalization.test.ts +22 -23
- package/src/__tests__/retry-thinking-adaptive-only.test.ts +43 -48
- package/src/__tests__/retry-thinking-tool-choice.test.ts +57 -66
- package/src/__tests__/retry-verbosity-normalization.test.ts +24 -23
- package/src/__tests__/reveal-success-registry.test.ts +123 -0
- package/src/__tests__/run-conversation-turn-persistence.test.ts +130 -0
- package/src/__tests__/secret-fixtures.ts +9 -0
- package/src/__tests__/server-history-render.test.ts +28 -0
- package/src/__tests__/skills.test.ts +9 -4
- package/src/__tests__/slack-share-routes.test.ts +0 -1
- package/src/__tests__/stt-stream-session.test.ts +6 -5
- package/src/__tests__/subagent-call-site-routing.test.ts +69 -95
- package/src/__tests__/subagent-disposal.test.ts +2 -0
- package/src/__tests__/subagent-fork-notifications.test.ts +2 -0
- package/src/__tests__/subagent-fork-spawn.test.ts +2 -0
- package/src/__tests__/subagent-manager-notify.test.ts +2 -0
- package/src/__tests__/subagent-role-registry.test.ts +37 -0
- package/src/__tests__/subagent-spawn-and-await.test.ts +1 -0
- package/src/__tests__/subagent-terminal-message.test.ts +50 -0
- package/src/__tests__/subagent-tool-gate-mode.test.ts +78 -5
- package/src/__tests__/surface-completion-nudge-hook.test.ts +19 -0
- package/src/__tests__/telemetry-routes.test.ts +99 -19
- package/src/__tests__/tool-audit.test.ts +34 -4
- package/src/__tests__/tool-executor-lifecycle-events.test.ts +1 -1
- package/src/__tests__/tool-profiler.test.ts +72 -1
- package/src/__tests__/tool-result-spool.test.ts +49 -4
- package/src/__tests__/tool-side-effects-slack-dm.test.ts +0 -1
- package/src/__tests__/ui-channel-variants.test.ts +108 -0
- package/src/__tests__/ui-shape-teaching.test.ts +255 -0
- package/src/__tests__/usage-attribution.test.ts +18 -41
- package/src/__tests__/user-plugin-loader.test.ts +4 -4
- package/src/__tests__/voice-config-update.test.ts +46 -0
- package/src/__tests__/voice-session-bridge.test.ts +216 -84
- package/src/__tests__/workspace-migration-131-drop-web-fetch-mode.test.ts +120 -0
- package/src/agent/loop.ts +4 -3
- package/src/api/events/open-conversation.test.ts +64 -0
- package/src/api/events/open-conversation.ts +33 -0
- package/src/api/index.ts +6 -0
- package/src/api/responses/conversation-message.ts +5 -0
- package/src/apps/app-store.ts +25 -35
- package/src/bundler/app-bundler.ts +34 -48
- package/src/bundler/app-compiler.ts +39 -4
- package/src/bundler/bundle-scanner.ts +13 -0
- package/src/bundler/manifest.ts +1 -1
- package/src/calls/__tests__/voice-session-bridge.test.ts +47 -0
- package/src/calls/call-constants.ts +5 -0
- package/src/calls/call-controller.ts +100 -32
- package/src/calls/call-transport.ts +9 -0
- package/src/calls/media-stream-output.ts +107 -4
- package/src/calls/media-stream-server.ts +29 -9
- package/src/calls/media-stream-stt-session.ts +7 -1
- package/src/calls/media-turn-detector.ts +11 -1
- package/src/calls/voice-session-bridge.ts +84 -63
- package/src/cli/commands/__tests__/inference-providers.test.ts +270 -33
- package/src/cli/commands/__tests__/notifications.test.ts +24 -3
- package/src/cli/commands/config.help.ts +6 -6
- package/src/cli/commands/credentials.help.ts +13 -0
- package/src/cli/commands/credentials.ts +6 -1
- package/src/cli/commands/email.help.ts +7 -0
- package/src/cli/commands/email.ts +35 -1
- package/src/cli/commands/inference-providers.ts +168 -107
- package/src/cli/commands/inference.help.ts +118 -46
- package/src/cli/commands/memory/index.help.ts +23 -0
- package/src/cli/commands/memory/nodes.ts +146 -0
- package/src/cli/commands/notifications.help.ts +10 -10
- package/src/cli/commands/oauth/connect-surface-guidance.test.ts +40 -0
- package/src/cli/commands/oauth/connect-surface-guidance.ts +54 -0
- package/src/cli/commands/oauth/connect.test.ts +126 -0
- package/src/cli/commands/oauth/connect.ts +29 -6
- package/src/cli/commands/oauth/index.help.ts +7 -1
- package/src/cli/commands/oauth/status.test.ts +69 -3
- package/src/cli/commands/oauth/status.ts +50 -12
- package/src/cli/commands/plugins.help.ts +13 -2
- package/src/cli/commands/plugins.ts +61 -7
- package/src/cli/commands/telemetry.help.ts +13 -0
- package/src/cli/commands/telemetry.ts +45 -5
- package/src/cli/lib/__tests__/plugin-catalog-local.test.ts +8 -2
- package/src/cli/lib/__tests__/upgrade-plugin.test.ts +213 -9
- package/src/cli/lib/bundled-marketplace.json +93 -36
- package/src/cli/lib/inspect-plugin.ts +5 -1
- package/src/cli/lib/install-from-github.ts +68 -4
- package/src/cli/lib/plugin-catalog-local.ts +6 -2
- package/src/cli/lib/plugin-fingerprint.ts +3 -3
- package/src/cli/lib/upgrade-plugin.ts +236 -35
- package/src/config/__tests__/default-profile-catalog.test.ts +8 -12
- package/src/config/__tests__/plugin-resident-skill-discovery.test.ts +137 -0
- package/src/config/__tests__/profile-materialization.test.ts +1 -88
- package/src/config/bundled-skills/AGENTS.md +3 -30
- package/src/config/bundled-skills/app-builder/SKILL.md +5 -3
- package/src/config/bundled-skills/app-builder/TOOLS.json +23 -0
- package/src/config/bundled-skills/app-builder/tools/app-open.ts +32 -0
- package/src/config/bundled-skills/messaging/tools/messaging-send.ts +1 -1
- package/src/config/bundled-skills/phone-calls/references/CONFIG.md +7 -7
- package/src/config/bundled-skills/schedule/SKILL.md +1 -1
- package/src/config/bundled-skills/schedule/TOOLS.json +1 -1
- package/src/config/bundled-skills/settings/TOOLS.json +3 -1
- package/src/config/bundled-skills/settings/tools/navigate-settings-tab.ts +2 -0
- package/src/config/bundled-skills/settings/tools/voice-config-update.ts +18 -10
- package/src/config/bundled-skills/subagent/SKILL.md +2 -0
- package/src/config/bundled-skills/subagent/TOOLS.json +1 -1
- package/src/config/call-site-defaults.ts +3 -3
- package/src/config/feature-flag-registry.json +24 -39
- package/src/config/llm-resolver.ts +88 -475
- package/src/config/profile-materialization.ts +11 -11
- package/src/config/schema.ts +93 -127
- package/src/config/schemas/__tests__/live-voice.test.ts +24 -6
- package/src/config/schemas/__tests__/stt.test.ts +31 -3
- package/src/config/schemas/live-voice.ts +10 -4
- package/src/config/schemas/llm.ts +52 -15
- package/src/config/schemas/memory-lifecycle.ts +1 -1
- package/src/config/schemas/memory-retrospective.ts +8 -0
- package/src/config/schemas/memory-v2.ts +2 -2
- package/src/config/schemas/memory-v3.ts +1 -1
- package/src/config/schemas/services.ts +6 -3
- package/src/config/schemas/stt.ts +38 -21
- package/src/config/schemas/tts.ts +13 -18
- package/src/config/skills.ts +31 -21
- package/src/context/compactor.ts +44 -7
- package/src/context/post-turn-tool-result-truncation.ts +4 -2
- package/src/context/tool-result-spool.ts +26 -5
- package/src/conversations/__tests__/message-consolidation.test.ts +48 -0
- package/src/conversations/message-consolidation.ts +22 -2
- package/src/daemon/__tests__/conversation-tool-setup.test.ts +5 -12
- package/src/daemon/app-source-watcher.ts +17 -23
- package/src/daemon/chat-credential-redaction.ts +1365 -0
- package/src/daemon/conversation-agent-loop-handlers.ts +627 -34
- package/src/daemon/conversation-agent-loop.ts +71 -31
- package/src/daemon/conversation-error.ts +36 -14
- package/src/daemon/conversation-process.ts +22 -0
- package/src/daemon/conversation-store.ts +35 -0
- package/src/daemon/conversation-surfaces.ts +25 -25
- package/src/daemon/conversation-tool-setup.ts +33 -7
- package/src/daemon/conversation.ts +55 -5
- package/src/daemon/handlers/shared.ts +33 -2
- package/src/daemon/lifecycle.ts +4 -4
- package/src/daemon/message-types/conversations.ts +6 -17
- package/src/daemon/providers-setup.ts +8 -0
- package/src/daemon/tool-setup-types.ts +7 -1
- package/src/daemon/wake-conversation-ops.ts +10 -1
- package/src/hooks/types.ts +9 -0
- package/src/ipc/__tests__/email-ipc.test.ts +90 -0
- package/src/ipc/gateway-client.test.ts +59 -0
- package/src/ipc/gateway-client.ts +70 -27
- package/src/live-voice/__tests__/live-voice-events.test.ts +14 -2
- package/src/live-voice/__tests__/live-voice-integration.test.ts +116 -2
- package/src/live-voice/__tests__/live-voice-vad.test.ts +804 -13
- package/src/live-voice/__tests__/protocol.test.ts +122 -0
- package/src/live-voice/live-voice-session.ts +443 -40
- package/src/live-voice/protocol.ts +143 -1
- package/src/monitoring/__tests__/plugin-source-watch.test.ts +3 -1
- package/src/monitoring/plugin-source-watch.ts +3 -62
- package/src/notifications/README.md +1 -1
- package/src/permissions/checker.ts +8 -4
- package/src/persistence/__tests__/db-init-migrations-ok.test.ts +26 -0
- package/src/persistence/conversation-crud.ts +104 -2
- package/src/persistence/db-init.ts +14 -3
- package/src/persistence/job-handlers/cleanup.ts +15 -7
- package/src/persistence/llm-request-log-store.ts +88 -52
- package/src/persistence/migrations/298-move-memory-jobs-to-memory-db.ts +7 -31
- package/src/persistence/migrations/305-drop-contact-acl-columns.ts +3 -2
- package/src/persistence/migrations/326-move-injection-events-to-memory-db.ts +8 -34
- package/src/persistence/migrations/336-move-memory-v2-activation-logs-to-memory-db.ts +90 -0
- package/src/persistence/migrations/337-move-memory-recall-logs-to-memory-db.ts +114 -0
- package/src/persistence/migrations/338-move-memory-v3-selections-to-memory-db.ts +84 -0
- package/src/persistence/migrations/339-move-activation-sessions-to-memory-db.ts +52 -0
- package/src/persistence/migrations/__tests__/run-migrations.test.ts +155 -0
- package/src/persistence/migrations/helpers/relocation.ts +44 -1
- package/src/persistence/migrations/run-migrations.ts +25 -1
- package/src/persistence/schema/infrastructure.ts +9 -0
- package/src/persistence/schema/memory-core.ts +3 -0
- package/src/persistence/schema/memory-injection.ts +2 -0
- package/src/persistence/steps.ts +36 -0
- package/src/platform/client.test.ts +1 -44
- package/src/platform/client.ts +10 -20
- package/src/platform/consent-cache.test.ts +89 -23
- package/src/platform/consent-cache.ts +71 -33
- package/src/plugin-api/constants.ts +12 -0
- package/src/plugin-api/conversation-turn.ts +37 -14
- package/src/plugin-api/index.ts +11 -1
- package/src/plugin-api/resolve-credential.ts +75 -0
- package/src/plugin-api/vision-support.test.ts +8 -19
- package/src/plugin-api/vision-support.ts +26 -17
- package/src/plugins/collect-source-versions.ts +77 -0
- package/src/plugins/defaults/compaction/compact.ts +6 -0
- package/src/plugins/defaults/compaction/window-manager.ts +9 -0
- package/src/plugins/defaults/empty-response/hooks/post-model-call.ts +18 -24
- package/src/plugins/defaults/empty-response/hooks/user-prompt-submit.ts +38 -0
- package/src/plugins/defaults/empty-response/refusal-quarantine.ts +99 -0
- package/src/plugins/defaults/image-fallback/__tests__/caption-cache-persistence.test.ts +7 -3
- package/src/plugins/defaults/index.ts +8 -1
- package/src/plugins/defaults/max-tokens-continue/hooks/post-model-call.ts +4 -1
- package/src/plugins/defaults/memory/__tests__/activation-session-store.test.ts +48 -6
- package/src/plugins/defaults/memory/__tests__/db-memory-attach.test.ts +28 -22
- package/src/plugins/defaults/memory/__tests__/memory-log-stores-degraded.test.ts +148 -0
- package/src/plugins/defaults/memory/__tests__/memory-retrospective-job.test.ts +80 -8
- package/src/plugins/defaults/memory/__tests__/memory-retrospective-prompt.test.ts +202 -0
- package/src/plugins/defaults/memory/__tests__/memory-v2-activation-log-store.test.ts +64 -25
- package/src/plugins/defaults/memory/__tests__/memory-v2-concept-frequency.test.ts +24 -11
- package/src/plugins/defaults/memory/__tests__/prompt-override.test.ts +70 -6
- package/src/plugins/defaults/memory/__tests__/table-relocation.test.ts +278 -0
- package/src/plugins/defaults/memory/activation-session-store.ts +27 -20
- package/src/plugins/defaults/memory/context-search/sources/memory-v2.ts +2 -9
- package/src/plugins/defaults/memory/context-search/sources/workspace.ts +1 -8
- package/src/plugins/defaults/memory/graph/retriever.test.ts +6 -6
- package/src/plugins/defaults/memory/graph/store.ts +114 -0
- package/src/plugins/defaults/memory/graph/tool-handlers.ts +36 -1
- package/src/plugins/defaults/memory/graph/tools.ts +47 -13
- package/src/plugins/defaults/memory/graph-topology/build-memory-graph.ts +16 -35
- package/src/plugins/defaults/memory/jobs-worker.ts +10 -1
- package/src/plugins/defaults/memory/memory-db.ts +3 -2
- package/src/plugins/defaults/memory/memory-recall-log-store.ts +129 -66
- package/src/plugins/defaults/memory/memory-retrospective-constants.ts +8 -0
- package/src/plugins/defaults/memory/memory-retrospective-job.ts +11 -122
- package/src/plugins/defaults/memory/memory-retrospective-prompt.ts +216 -0
- package/src/plugins/defaults/memory/memory-v2-activation-log-store.ts +84 -46
- package/src/plugins/defaults/memory/memory-v2-concept-frequency.ts +42 -32
- package/src/plugins/defaults/memory/path-containment.ts +21 -0
- package/src/plugins/defaults/memory/prompt-override.ts +51 -13
- package/src/plugins/defaults/memory/tools.test.ts +34 -0
- package/src/plugins/defaults/memory/tools.ts +12 -2
- package/src/plugins/defaults/memory/v2/__tests__/harness-compare.test.ts +19 -15
- package/src/plugins/defaults/memory/v2/__tests__/harness-oracle.test.ts +24 -19
- package/src/plugins/defaults/memory/v2/__tests__/harness-replay-input.test.ts +19 -15
- package/src/plugins/defaults/memory/v2/__tests__/injection.test.ts +22 -2
- package/src/plugins/defaults/memory/v2/__tests__/prompts-consolidation.test.ts +5 -3
- package/src/plugins/defaults/memory/v2/harness/oracle.ts +59 -41
- package/src/plugins/defaults/memory/v2/harness/replay-input.ts +29 -25
- package/src/plugins/defaults/memory/v2/migration.ts +46 -16
- package/src/plugins/defaults/memory/v2/prompts/consolidation.ts +4 -0
- package/src/plugins/defaults/memory/v3/__tests__/carry-integration.test.ts +17 -15
- package/src/plugins/defaults/memory/v3/__tests__/gate.test.ts +24 -22
- package/src/plugins/defaults/memory/v3/__tests__/injection.test.ts +9 -5
- package/src/plugins/defaults/memory/v3/__tests__/orchestrate.test.ts +277 -10
- package/src/plugins/defaults/memory/v3/__tests__/selection-log-store.test.ts +57 -5
- package/src/plugins/defaults/memory/v3/__tests__/shadow-integration.test.ts +9 -7
- package/src/plugins/defaults/memory/v3/__tests__/shadow-plugin.test.ts +38 -7
- package/src/plugins/defaults/memory/v3/hot-set.test.ts +36 -18
- package/src/plugins/defaults/memory/v3/hot-set.ts +12 -14
- package/src/plugins/defaults/memory/v3/learned-edges.test.ts +47 -27
- package/src/plugins/defaults/memory/v3/learned-edges.ts +12 -14
- package/src/plugins/defaults/memory/v3/orchestrate.ts +139 -22
- package/src/plugins/defaults/memory/v3/prune.test.ts +10 -3
- package/src/plugins/defaults/memory/v3/prune.ts +17 -11
- package/src/plugins/defaults/memory/v3/selection-log-store.ts +23 -11
- package/src/plugins/defaults/memory/v3/shadow-plugin.ts +70 -53
- package/src/plugins/defaults/surface-completion-nudge/hooks/post-model-call.ts +9 -6
- package/src/plugins/external-plugin-loader.ts +15 -15
- package/src/plugins/mtime-cache.ts +47 -0
- package/src/plugins/pipeline.ts +9 -1
- package/src/plugins/plugin-execution-context.ts +44 -0
- package/src/plugins/plugin-tree-walk.ts +31 -24
- package/src/plugins/source-fingerprint.ts +3 -4
- package/src/prompts/normalize-onboarding.ts +12 -0
- package/src/prompts/persona-resolver.ts +8 -0
- package/src/prompts/templates/BOOTSTRAP-ACTIVATION-RAIL.md +1 -1
- package/src/providers/__tests__/dispatch-connection-routing.test.ts +6 -9
- package/src/providers/__tests__/registry-native-web-search.test.ts +4 -10
- package/src/providers/__tests__/retry-callsite.test.ts +235 -215
- package/src/providers/__tests__/satellite-connection-routing.test.ts +8 -16
- package/src/providers/atlascloud/client.ts +10 -49
- package/src/providers/baseten/client.ts +43 -0
- package/src/providers/call-site-routing.ts +26 -13
- package/src/providers/connection-resolution.ts +9 -11
- package/src/providers/fetch-provider-catalog.ts +4 -2
- package/src/providers/inference/__tests__/adapter-factory-openai-compatible.test.ts +29 -4
- package/src/providers/inference/__tests__/connection-availability-keyless.test.ts +78 -0
- package/src/providers/inference/adapter-factory.ts +27 -6
- package/src/providers/inference/auth.ts +29 -1
- package/src/providers/inference/backfill.ts +61 -10
- package/src/providers/inference/connection-availability.ts +3 -2
- package/src/providers/inference/resolve-auth.ts +9 -1
- package/src/providers/model-catalog.ts +37 -0
- package/src/providers/openai/__tests__/api-error-normalization.test.ts +24 -2
- package/src/providers/openai/api-key-validation.ts +70 -0
- package/src/providers/retry.ts +2 -0
- package/src/providers/types.ts +11 -4
- package/src/providers/vellum-model-routing.test.ts +26 -0
- package/src/providers/vellum-model-routing.ts +30 -0
- package/src/providers/voice-error-copy.ts +47 -0
- package/src/runtime/agent-wake.ts +40 -24
- package/src/runtime/for-chat-mint-registry.ts +118 -0
- package/src/runtime/reveal-nonce.ts +49 -0
- package/src/runtime/reveal-success-registry.ts +306 -0
- package/src/runtime/routes/__tests__/conversation-query-routes.test.ts +88 -3
- package/src/runtime/routes/__tests__/inference-provider-connection-routes.test.ts +161 -30
- package/src/runtime/routes/__tests__/migration-vellum-metadata-reconcile.test.ts +7 -0
- package/src/runtime/routes/__tests__/schedule-worker-routes.test.ts +15 -0
- package/src/runtime/routes/app-management-routes.ts +152 -45
- package/src/runtime/routes/app-routes.ts +16 -77
- package/src/runtime/routes/canned-message-complete.ts +16 -16
- package/src/runtime/routes/conversation-management-routes.ts +17 -14
- package/src/runtime/routes/conversation-query-routes.ts +54 -6
- package/src/runtime/routes/conversation-routes.ts +48 -2
- package/src/runtime/routes/credential-routes.ts +116 -3
- package/src/runtime/routes/email-routes.ts +17 -1
- package/src/runtime/routes/inbound-stages/transcribe-audio.test.ts +33 -5
- package/src/runtime/routes/inbound-stages/transcribe-audio.ts +6 -5
- package/src/runtime/routes/inference-provider-connection-routes.ts +77 -28
- package/src/runtime/routes/inference-send-routes.ts +1 -1
- package/src/runtime/routes/internal-telemetry-routes.ts +7 -6
- package/src/runtime/routes/plugins-routes.ts +37 -6
- package/src/runtime/routes/publish-routes.ts +15 -18
- package/src/runtime/routes/schedule-worker-routes.ts +9 -0
- package/src/runtime/routes/secret-routes.ts +10 -0
- package/src/runtime/routes/telemetry-routes.ts +149 -45
- package/src/schedule/__tests__/schedule-timezone.test.ts +101 -0
- package/src/schedule/__tests__/worker-watchdog.test.ts +209 -0
- package/src/schedule/schedule-store.ts +12 -1
- package/src/schedule/schedule-timezone.ts +63 -0
- package/src/schedule/scheduler.ts +100 -6
- package/src/schedule/worker-control.ts +19 -0
- package/src/security/auth-fallback-events-store.ts +7 -6
- package/src/security/secret-scanner.ts +26 -1
- package/src/services/published-app-updater.ts +6 -11
- package/src/stt/stt-stream-session.ts +36 -16
- package/src/subagent/manager.ts +34 -8
- package/src/telemetry/AGENTS.md +30 -1
- package/src/telemetry/__tests__/config-setting-snapshot.test.ts +18 -0
- package/src/telemetry/__tests__/outbox-test-harness.ts +5 -3
- package/src/telemetry/config-setting-snapshot.ts +44 -10
- package/src/telemetry/telemetry-event-sources.test.ts +124 -30
- package/src/telemetry/telemetry-event-sources.ts +137 -83
- package/src/telemetry/telemetry-events-outbox.test.ts +46 -1
- package/src/telemetry/telemetry-events-outbox.ts +60 -9
- package/src/telemetry/telemetry-wire-source.json +1 -1
- package/src/telemetry/telemetry-wire-validation.ts +39 -2
- package/src/telemetry/telemetry-wire.generated.ts +8 -0
- package/src/telemetry/tool-audit.ts +15 -9
- package/src/telemetry/tool-executed-events-store.test.ts +1 -1
- package/src/telemetry/turn-events-store.ts +20 -0
- package/src/telemetry/types.ts +45 -12
- package/src/telemetry/usage-telemetry-reporter.test.ts +294 -25
- package/src/telemetry/usage-telemetry-reporter.ts +117 -18
- package/src/telemetry/watchdog-direct-emit.test.ts +11 -3
- package/src/telemetry/watchdog-direct-emit.ts +11 -6
- package/src/tools/apps/executors.ts +11 -36
- package/src/tools/executor.ts +14 -1
- package/src/tools/host-terminal/host-shell.ts +6 -2
- package/src/tools/network/__tests__/web-fetch-firecrawl.test.ts +1 -1
- package/src/tools/network/__tests__/web-fetch-metadata.test.ts +25 -0
- package/src/tools/network/web-fetch.ts +6 -2
- package/src/tools/skills/sandbox-runner.ts +5 -2
- package/src/tools/subagent/spawn.ts +7 -11
- package/src/tools/terminal/shell.ts +5 -2
- package/src/tools/tool-manifest.ts +0 -2
- package/src/tools/tool-profiler.ts +37 -6
- package/src/tools/ui-surface/channel-variants.ts +101 -0
- package/src/tools/ui-surface/definitions.ts +23 -67
- package/src/tools/ui-surface/surface-shape-docs.ts +229 -0
- package/src/tts/__tests__/provider-adapters.test.ts +18 -0
- package/src/tts/provider-catalog.ts +2 -4
- package/src/tts/providers/deepgram-provider.ts +10 -1
- package/src/types/onboarding-context.ts +8 -0
- package/src/usage/attribution.ts +18 -112
- package/src/{config/bundled-skills/messaging/tools/gmail-mime-helpers.ts → util/mime-type.ts} +4 -1
- package/src/util/provider-error-patterns.ts +7 -1
- package/src/util/worker-process.ts +105 -0
- package/src/watcher/__tests__/telemetry.test.ts +17 -4
- package/src/watcher/telemetry.ts +5 -5
- package/src/workspace/migrations/131-drop-web-fetch-mode.ts +61 -0
- package/src/workspace/migrations/registry.ts +2 -0
- package/src/workspace/provider-commit-message-generator.ts +6 -4
- package/src/__tests__/app-open-proxy.test.ts +0 -67
- package/src/onboarding/onboarding-research-events-store.test.ts +0 -230
- package/src/onboarding/onboarding-research-events-store.ts +0 -104
- package/src/runtime/routes/assets/vellum-design-system.css +0 -2236
- package/src/tools/apps/definitions.ts +0 -73
- package/src/tools/apps/open-proxy.ts +0 -43
|
@@ -6,7 +6,7 @@
|
|
|
6
6
|
* GET /v1/inference/provider-connections/:name — single, 404
|
|
7
7
|
* POST /v1/inference/provider-connections — create happy paths + 409 + 400 cases
|
|
8
8
|
* PATCH /v1/inference/provider-connections/:name — update auth, 404
|
|
9
|
-
* DELETE /v1/inference/provider-connections/:name — happy path, 409 with profile ref,
|
|
9
|
+
* DELETE /v1/inference/provider-connections/:name — happy path, 409 with profile ref, llm.defaultProvider guard
|
|
10
10
|
* Auth — 401 (missing key) and 403 (insufficient scope) via route-policy assertions
|
|
11
11
|
*/
|
|
12
12
|
|
|
@@ -32,13 +32,17 @@ await initializeDb();
|
|
|
32
32
|
|
|
33
33
|
function findHandler(operationId: string): RouteDefinition["handler"] {
|
|
34
34
|
const route = ROUTES.find((r) => r.operationId === operationId);
|
|
35
|
-
if (!route)
|
|
35
|
+
if (!route) {
|
|
36
|
+
throw new Error(`Route ${operationId} not found`);
|
|
37
|
+
}
|
|
36
38
|
return route.handler;
|
|
37
39
|
}
|
|
38
40
|
|
|
39
41
|
function findRoute(operationId: string): RouteDefinition {
|
|
40
42
|
const route = ROUTES.find((r) => r.operationId === operationId);
|
|
41
|
-
if (!route)
|
|
43
|
+
if (!route) {
|
|
44
|
+
throw new Error(`Route ${operationId} not found`);
|
|
45
|
+
}
|
|
42
46
|
return route;
|
|
43
47
|
}
|
|
44
48
|
|
|
@@ -225,6 +229,97 @@ describe("POST inference/provider-connections (create)", () => {
|
|
|
225
229
|
expect(result.auth).toEqual({ type: "none" });
|
|
226
230
|
});
|
|
227
231
|
|
|
232
|
+
test("derives api_key auth from provider + credential when auth is omitted", async () => {
|
|
233
|
+
const result = (await call(
|
|
234
|
+
findHandler("inference_provider_connections_create"),
|
|
235
|
+
{
|
|
236
|
+
body: {
|
|
237
|
+
name: "derived-anthropic",
|
|
238
|
+
provider: "anthropic",
|
|
239
|
+
credential: "vault/anthropic/key",
|
|
240
|
+
},
|
|
241
|
+
},
|
|
242
|
+
)) as { auth: object };
|
|
243
|
+
expect(result.auth).toEqual({
|
|
244
|
+
type: "api_key",
|
|
245
|
+
credential: "vault/anthropic/key",
|
|
246
|
+
});
|
|
247
|
+
});
|
|
248
|
+
|
|
249
|
+
test("derives none auth for keyless providers when auth is omitted", async () => {
|
|
250
|
+
const result = (await call(
|
|
251
|
+
findHandler("inference_provider_connections_create"),
|
|
252
|
+
{
|
|
253
|
+
body: { name: "derived-ollama", provider: "ollama" },
|
|
254
|
+
},
|
|
255
|
+
)) as { auth: object };
|
|
256
|
+
expect(result.auth).toEqual({ type: "none" });
|
|
257
|
+
});
|
|
258
|
+
|
|
259
|
+
test("derives platform auth for the vellum provider when auth is omitted", async () => {
|
|
260
|
+
const result = (await call(
|
|
261
|
+
findHandler("inference_provider_connections_create"),
|
|
262
|
+
{
|
|
263
|
+
body: { name: "derived-vellum", provider: "vellum" },
|
|
264
|
+
},
|
|
265
|
+
)) as { auth: object };
|
|
266
|
+
expect(result.auth).toEqual({ type: "platform" });
|
|
267
|
+
});
|
|
268
|
+
|
|
269
|
+
test("derives none auth for openai-compatible without a credential", async () => {
|
|
270
|
+
const result = (await call(
|
|
271
|
+
findHandler("inference_provider_connections_create"),
|
|
272
|
+
{
|
|
273
|
+
body: {
|
|
274
|
+
name: "derived-local-llm",
|
|
275
|
+
provider: "openai-compatible",
|
|
276
|
+
base_url: "http://localhost:1234/v1",
|
|
277
|
+
models: [{ id: "my-model" }],
|
|
278
|
+
},
|
|
279
|
+
},
|
|
280
|
+
)) as { auth: object };
|
|
281
|
+
expect(result.auth).toEqual({ type: "none" });
|
|
282
|
+
});
|
|
283
|
+
|
|
284
|
+
test("derives api_key auth for openai-compatible with a credential", async () => {
|
|
285
|
+
const result = (await call(
|
|
286
|
+
findHandler("inference_provider_connections_create"),
|
|
287
|
+
{
|
|
288
|
+
body: {
|
|
289
|
+
name: "derived-hosted-llm",
|
|
290
|
+
provider: "openai-compatible",
|
|
291
|
+
credential: "credential/hosted/key",
|
|
292
|
+
base_url: "https://api.example.com/v1",
|
|
293
|
+
models: [{ id: "my-model" }],
|
|
294
|
+
},
|
|
295
|
+
},
|
|
296
|
+
)) as { auth: object };
|
|
297
|
+
expect(result.auth).toEqual({
|
|
298
|
+
type: "api_key",
|
|
299
|
+
credential: "credential/hosted/key",
|
|
300
|
+
});
|
|
301
|
+
});
|
|
302
|
+
|
|
303
|
+
test("throws 400 when auth is omitted and a keyed provider has no credential", async () => {
|
|
304
|
+
await expect(
|
|
305
|
+
call(findHandler("inference_provider_connections_create"), {
|
|
306
|
+
body: { name: "derived-no-cred", provider: "anthropic" },
|
|
307
|
+
}),
|
|
308
|
+
).rejects.toBeInstanceOf(BadRequestError);
|
|
309
|
+
});
|
|
310
|
+
|
|
311
|
+
test("throws 400 when the derived credential is not a non-empty string", async () => {
|
|
312
|
+
await expect(
|
|
313
|
+
call(findHandler("inference_provider_connections_create"), {
|
|
314
|
+
body: {
|
|
315
|
+
name: "derived-bad-cred",
|
|
316
|
+
provider: "anthropic",
|
|
317
|
+
credential: "",
|
|
318
|
+
},
|
|
319
|
+
}),
|
|
320
|
+
).rejects.toBeInstanceOf(BadRequestError);
|
|
321
|
+
});
|
|
322
|
+
|
|
228
323
|
test("throws 409 when connection name already exists", async () => {
|
|
229
324
|
seedConnection({
|
|
230
325
|
name: "dup-name",
|
|
@@ -332,6 +427,59 @@ describe("PATCH inference/provider-connections/:name (update)", () => {
|
|
|
332
427
|
}),
|
|
333
428
|
).rejects.toBeInstanceOf(BadRequestError);
|
|
334
429
|
});
|
|
430
|
+
|
|
431
|
+
test("keeps stored auth when both auth and credential are omitted", async () => {
|
|
432
|
+
seedConnection({
|
|
433
|
+
name: "label-only",
|
|
434
|
+
provider: "openai",
|
|
435
|
+
auth: { type: "oauth_subscription", credential: "vault/chatgpt/token" },
|
|
436
|
+
});
|
|
437
|
+
|
|
438
|
+
const result = (await call(
|
|
439
|
+
findHandler("inference_provider_connections_update"),
|
|
440
|
+
{
|
|
441
|
+
pathParams: { name: "label-only" },
|
|
442
|
+
body: { label: "Renamed" },
|
|
443
|
+
},
|
|
444
|
+
)) as { auth: object; label: string | null };
|
|
445
|
+
expect(result.auth).toEqual({
|
|
446
|
+
type: "oauth_subscription",
|
|
447
|
+
credential: "vault/chatgpt/token",
|
|
448
|
+
});
|
|
449
|
+
expect(result.label).toBe("Renamed");
|
|
450
|
+
});
|
|
451
|
+
|
|
452
|
+
test("throws 400 on credential-only PATCH of an oauth_subscription connection", async () => {
|
|
453
|
+
seedConnection({
|
|
454
|
+
name: "chatgpt-subscription",
|
|
455
|
+
provider: "openai",
|
|
456
|
+
auth: { type: "oauth_subscription", credential: "vault/chatgpt/token" },
|
|
457
|
+
});
|
|
458
|
+
|
|
459
|
+
await expect(
|
|
460
|
+
call(findHandler("inference_provider_connections_update"), {
|
|
461
|
+
pathParams: { name: "chatgpt-subscription" },
|
|
462
|
+
body: { credential: "vault/other" },
|
|
463
|
+
}),
|
|
464
|
+
).rejects.toBeInstanceOf(BadRequestError);
|
|
465
|
+
});
|
|
466
|
+
|
|
467
|
+
test("rotates to derived api_key auth when only credential is passed", async () => {
|
|
468
|
+
seedConnection({
|
|
469
|
+
name: "rotate-cred",
|
|
470
|
+
provider: "anthropic",
|
|
471
|
+
auth: { type: "api_key", credential: "vault/old" },
|
|
472
|
+
});
|
|
473
|
+
|
|
474
|
+
const result = (await call(
|
|
475
|
+
findHandler("inference_provider_connections_update"),
|
|
476
|
+
{
|
|
477
|
+
pathParams: { name: "rotate-cred" },
|
|
478
|
+
body: { credential: "vault/new" },
|
|
479
|
+
},
|
|
480
|
+
)) as { auth: object };
|
|
481
|
+
expect(result.auth).toEqual({ type: "api_key", credential: "vault/new" });
|
|
482
|
+
});
|
|
335
483
|
});
|
|
336
484
|
|
|
337
485
|
// ── DELETE ────────────────────────────────────────────────────────────────────
|
|
@@ -390,31 +538,12 @@ describe("DELETE inference/provider-connections/:name (delete)", () => {
|
|
|
390
538
|
expect((err as ConflictError).message).toContain("my-profile");
|
|
391
539
|
});
|
|
392
540
|
|
|
393
|
-
test("throws 409 when
|
|
394
|
-
|
|
395
|
-
name: "default-conn",
|
|
396
|
-
provider: "anthropic",
|
|
397
|
-
auth: { type: "platform" },
|
|
398
|
-
});
|
|
399
|
-
setConfig("llm", {
|
|
400
|
-
default: { provider_connection: "default-conn" },
|
|
401
|
-
});
|
|
402
|
-
|
|
403
|
-
const err = await call(
|
|
404
|
-
findHandler("inference_provider_connections_delete"),
|
|
405
|
-
{ pathParams: { name: "default-conn" } },
|
|
406
|
-
).catch((e: unknown) => e);
|
|
407
|
-
expect(err).toBeInstanceOf(ConflictError);
|
|
408
|
-
expect((err as ConflictError).message).toContain("default-conn");
|
|
409
|
-
expect((err as ConflictError).message).toContain("llm.default");
|
|
410
|
-
});
|
|
411
|
-
|
|
412
|
-
test("throws 404 (not 409) when llm.default references a missing connection", async () => {
|
|
413
|
-
// Stale ref in config: llm.default points at a connection that was
|
|
541
|
+
test("throws 404 (not 409) when a profile references a missing connection", async () => {
|
|
542
|
+
// Stale ref in config: a profile points at a connection that was
|
|
414
543
|
// already deleted. Delete on the dangling name must return 404 so
|
|
415
544
|
// callers can distinguish stale config from active conflicts.
|
|
416
545
|
setConfig("llm", {
|
|
417
|
-
|
|
546
|
+
profiles: { "ghost-prof": { provider_connection: "ghost-conn" } },
|
|
418
547
|
});
|
|
419
548
|
|
|
420
549
|
await expect(
|
|
@@ -424,24 +553,26 @@ describe("DELETE inference/provider-connections/:name (delete)", () => {
|
|
|
424
553
|
).rejects.toBeInstanceOf(NotFoundError);
|
|
425
554
|
});
|
|
426
555
|
|
|
427
|
-
test("throws 409
|
|
556
|
+
test("throws 409 naming every referencing profile", async () => {
|
|
428
557
|
seedConnection({
|
|
429
558
|
name: "shared-conn",
|
|
430
559
|
provider: "anthropic",
|
|
431
560
|
auth: { type: "none" },
|
|
432
561
|
});
|
|
433
562
|
setConfig("llm", {
|
|
434
|
-
|
|
435
|
-
|
|
563
|
+
profiles: {
|
|
564
|
+
"prof-a": { provider_connection: "shared-conn" },
|
|
565
|
+
"prof-b": { provider_connection: "shared-conn" },
|
|
566
|
+
},
|
|
436
567
|
});
|
|
437
568
|
|
|
438
|
-
// llm.default check fires first (before profiles check).
|
|
439
569
|
const err = await call(
|
|
440
570
|
findHandler("inference_provider_connections_delete"),
|
|
441
571
|
{ pathParams: { name: "shared-conn" } },
|
|
442
572
|
).catch((e: unknown) => e);
|
|
443
573
|
expect(err).toBeInstanceOf(ConflictError);
|
|
444
|
-
expect((err as ConflictError).message).toContain("
|
|
574
|
+
expect((err as ConflictError).message).toContain("prof-a");
|
|
575
|
+
expect((err as ConflictError).message).toContain("prof-b");
|
|
445
576
|
});
|
|
446
577
|
});
|
|
447
578
|
|
|
@@ -28,6 +28,7 @@
|
|
|
28
28
|
import { afterEach, beforeEach, describe, expect, mock, test } from "bun:test";
|
|
29
29
|
|
|
30
30
|
import { credentialKey } from "../../../security/credential-key.js";
|
|
31
|
+
import * as actualMetadataStore from "../../../tools/credentials/metadata-store.js";
|
|
31
32
|
|
|
32
33
|
type MetadataRecord = {
|
|
33
34
|
credentialId: string;
|
|
@@ -80,7 +81,13 @@ mock.module("../../../security/secure-keys.js", () => ({
|
|
|
80
81
|
_resetBackend: () => {},
|
|
81
82
|
}));
|
|
82
83
|
|
|
84
|
+
// Spread the real module so every export the reconcile handler's transitive
|
|
85
|
+
// graph links (e.g. getCredentialMetadataById, listCredentialMetadata pulled in
|
|
86
|
+
// via the plugin-api hub) stays present; only the two functions under test are
|
|
87
|
+
// overridden. A partial mock replaces the whole namespace and fails to link the
|
|
88
|
+
// unlisted exports.
|
|
83
89
|
mock.module("../../../tools/credentials/metadata-store.js", () => ({
|
|
90
|
+
...actualMetadataStore,
|
|
84
91
|
getCredentialMetadata: (service: string, field: string) =>
|
|
85
92
|
metadataStore.get(`${service}:${field}`),
|
|
86
93
|
upsertCredentialMetadata: (
|
|
@@ -20,6 +20,7 @@ let stopImpl: () => { status: "running" | "not_running"; pid?: number };
|
|
|
20
20
|
let workerProbe: { status: "running" | "not_running"; pid?: number } = {
|
|
21
21
|
status: "not_running",
|
|
22
22
|
};
|
|
23
|
+
const adminStoppedCalls: boolean[] = [];
|
|
23
24
|
|
|
24
25
|
mock.module("../../../schedule/worker-control.js", () => ({
|
|
25
26
|
ScheduleWorkerSpawnError: FakeSpawnError,
|
|
@@ -32,6 +33,9 @@ mock.module("../../../schedule/worker-control.js", () => ({
|
|
|
32
33
|
},
|
|
33
34
|
stopScheduleWorkerProcess: () => stopImpl(),
|
|
34
35
|
probeScheduleWorker: () => workerProbe,
|
|
36
|
+
setScheduleWorkerAdministrativelyStopped: (value: boolean) => {
|
|
37
|
+
adminStoppedCalls.push(value);
|
|
38
|
+
},
|
|
35
39
|
}));
|
|
36
40
|
|
|
37
41
|
const { ROUTES } = await import("../schedule-worker-routes.js");
|
|
@@ -49,6 +53,7 @@ beforeEach(() => {
|
|
|
49
53
|
spawnImpl = async () => ({ pid: 4242, alreadyRunning: false });
|
|
50
54
|
stopImpl = () => ({ status: "not_running" });
|
|
51
55
|
workerProbe = { status: "not_running" };
|
|
56
|
+
adminStoppedCalls.length = 0;
|
|
52
57
|
});
|
|
53
58
|
|
|
54
59
|
describe("schedules_worker_start", () => {
|
|
@@ -82,6 +87,11 @@ describe("schedules_worker_start", () => {
|
|
|
82
87
|
"worker exited during startup",
|
|
83
88
|
);
|
|
84
89
|
});
|
|
90
|
+
|
|
91
|
+
test("clears the administratively-stopped flag so the watchdog resumes", async () => {
|
|
92
|
+
await handler("schedules_worker_start")();
|
|
93
|
+
expect(adminStoppedCalls).toContain(false);
|
|
94
|
+
});
|
|
85
95
|
});
|
|
86
96
|
|
|
87
97
|
describe("schedules_worker_stop", () => {
|
|
@@ -100,6 +110,11 @@ describe("schedules_worker_stop", () => {
|
|
|
100
110
|
|
|
101
111
|
expect(res).toEqual({ workerWasRunning: false });
|
|
102
112
|
});
|
|
113
|
+
|
|
114
|
+
test("sets the administratively-stopped flag so the watchdog does not respawn", async () => {
|
|
115
|
+
await handler("schedules_worker_stop")();
|
|
116
|
+
expect(adminStoppedCalls).toContain(true);
|
|
117
|
+
});
|
|
103
118
|
});
|
|
104
119
|
|
|
105
120
|
describe("schedules_worker_status", () => {
|
|
@@ -6,8 +6,10 @@ import { randomBytes } from "node:crypto";
|
|
|
6
6
|
import {
|
|
7
7
|
existsSync,
|
|
8
8
|
mkdirSync,
|
|
9
|
+
mkdtempSync,
|
|
9
10
|
readdirSync,
|
|
10
11
|
readFileSync,
|
|
12
|
+
rmSync,
|
|
11
13
|
statSync,
|
|
12
14
|
writeFileSync,
|
|
13
15
|
} from "node:fs";
|
|
@@ -19,6 +21,7 @@ import { z } from "zod";
|
|
|
19
21
|
|
|
20
22
|
import {
|
|
21
23
|
type AppDefinition,
|
|
24
|
+
type AppOrigin,
|
|
22
25
|
createApp,
|
|
23
26
|
createAppRecord,
|
|
24
27
|
deleteApp,
|
|
@@ -38,7 +41,7 @@ import {
|
|
|
38
41
|
} from "../../apps/app-store.js";
|
|
39
42
|
import { createSharedAppLink } from "../../apps/shared-app-links-store.js";
|
|
40
43
|
import { packageApp } from "../../bundler/app-bundler.js";
|
|
41
|
-
import { compileApp } from "../../bundler/app-compiler.js";
|
|
44
|
+
import { compileApp, runCompile } from "../../bundler/app-compiler.js";
|
|
42
45
|
import { scanBundle } from "../../bundler/bundle-scanner.js";
|
|
43
46
|
import type { SignatureJson } from "../../bundler/bundle-signer.js";
|
|
44
47
|
import { verifyBundleSignature } from "../../bundler/signature-verifier.js";
|
|
@@ -278,6 +281,29 @@ function forkSharedApp(
|
|
|
278
281
|
return { success: false, error: "Shared app HTML not found" };
|
|
279
282
|
}
|
|
280
283
|
|
|
284
|
+
// The unpacked bundle keeps its manifest.json; only multi-file (format 2)
|
|
285
|
+
// shared apps may be forked, else the fork would resurrect the retired
|
|
286
|
+
// single-file format through the dist/ materialization below.
|
|
287
|
+
let sharedFormatVersion: unknown;
|
|
288
|
+
const manifestPath = join(dir, appUuid, "manifest.json");
|
|
289
|
+
if (existsSync(manifestPath)) {
|
|
290
|
+
try {
|
|
291
|
+
const manifest = JSON.parse(readFileSync(manifestPath, "utf-8")) as {
|
|
292
|
+
format_version?: unknown;
|
|
293
|
+
};
|
|
294
|
+
sharedFormatVersion = manifest.format_version;
|
|
295
|
+
} catch {
|
|
296
|
+
// Malformed manifest — treated as unsupported below.
|
|
297
|
+
}
|
|
298
|
+
}
|
|
299
|
+
if (Number(sharedFormatVersion) !== 2) {
|
|
300
|
+
return {
|
|
301
|
+
success: false,
|
|
302
|
+
error:
|
|
303
|
+
"This shared app uses the legacy single-file format, which is no longer supported",
|
|
304
|
+
};
|
|
305
|
+
}
|
|
306
|
+
|
|
281
307
|
const htmlContent = readFileSync(htmlPath, "utf-8");
|
|
282
308
|
|
|
283
309
|
const newApp = createApp({
|
|
@@ -287,6 +313,18 @@ function forkSharedApp(
|
|
|
287
313
|
htmlDefinition: htmlContent,
|
|
288
314
|
});
|
|
289
315
|
|
|
316
|
+
// Materialize the shared app's compiled output as the fork's dist/ so the
|
|
317
|
+
// fork opens without a source compile (mirrors bundle import).
|
|
318
|
+
const distDir = join(getAppDirPath(newApp.id), "dist");
|
|
319
|
+
mkdirSync(distDir, { recursive: true });
|
|
320
|
+
writeFileSync(join(distDir, "index.html"), htmlContent, "utf-8");
|
|
321
|
+
for (const asset of ["main.js", "main.css"]) {
|
|
322
|
+
const assetPath = join(dir, appUuid, asset);
|
|
323
|
+
if (existsSync(assetPath)) {
|
|
324
|
+
writeFileSync(join(distDir, asset), readFileSync(assetPath, "utf-8"));
|
|
325
|
+
}
|
|
326
|
+
}
|
|
327
|
+
|
|
290
328
|
return { success: true, appId: newApp.id, name: newApp.name };
|
|
291
329
|
}
|
|
292
330
|
|
|
@@ -450,10 +488,15 @@ async function importBundle(
|
|
|
450
488
|
manifest = JSON.parse(manifestText);
|
|
451
489
|
}
|
|
452
490
|
|
|
491
|
+
if (Number(manifest.format_version) !== 2) {
|
|
492
|
+
throw new BadRequestError(
|
|
493
|
+
"This app bundle uses the legacy single-file format, which is no longer supported. Re-export the app from a current version and try again.",
|
|
494
|
+
);
|
|
495
|
+
}
|
|
496
|
+
|
|
453
497
|
const appName = manifest.name ?? "Imported App";
|
|
454
498
|
const appDescription = manifest.description;
|
|
455
499
|
const entry = manifest.entry ?? "index.html";
|
|
456
|
-
const isMultiFile = manifest.format_version === 2;
|
|
457
500
|
|
|
458
501
|
// Extract entry HTML
|
|
459
502
|
const entryFile = zip.file(entry);
|
|
@@ -476,32 +519,29 @@ async function importBundle(
|
|
|
476
519
|
schemaJson: JSON.stringify({ type: "object", properties: {} }),
|
|
477
520
|
htmlDefinition,
|
|
478
521
|
icon,
|
|
479
|
-
formatVersion: isMultiFile ? 2 : undefined,
|
|
480
522
|
});
|
|
481
523
|
|
|
482
|
-
//
|
|
483
|
-
//
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
mkdirSync(distDir, { recursive: true });
|
|
488
|
-
|
|
489
|
-
// Write dist/index.html
|
|
490
|
-
writeFileSync(join(distDir, "index.html"), htmlDefinition, "utf-8");
|
|
491
|
-
|
|
492
|
-
// Write dist/main.js if present in the bundle
|
|
493
|
-
const mainJsFile = zip.file("main.js");
|
|
494
|
-
if (mainJsFile) {
|
|
495
|
-
const mainJs = await mainJsFile.async("text");
|
|
496
|
-
writeFileSync(join(distDir, "main.js"), mainJs, "utf-8");
|
|
497
|
-
}
|
|
524
|
+
// Extract compiled dist assets (main.js, main.css) into the app's dist/
|
|
525
|
+
// directory so the app can run correctly.
|
|
526
|
+
const appDir = getAppDirPath(newApp.id);
|
|
527
|
+
const distDir = join(appDir, "dist");
|
|
528
|
+
mkdirSync(distDir, { recursive: true });
|
|
498
529
|
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
|
|
530
|
+
// Write dist/index.html
|
|
531
|
+
writeFileSync(join(distDir, "index.html"), htmlDefinition, "utf-8");
|
|
532
|
+
|
|
533
|
+
// Write dist/main.js if present in the bundle
|
|
534
|
+
const mainJsFile = zip.file("main.js");
|
|
535
|
+
if (mainJsFile) {
|
|
536
|
+
const mainJs = await mainJsFile.async("text");
|
|
537
|
+
writeFileSync(join(distDir, "main.js"), mainJs, "utf-8");
|
|
538
|
+
}
|
|
539
|
+
|
|
540
|
+
// Write dist/main.css if present in the bundle
|
|
541
|
+
const mainCssFile = zip.file("main.css");
|
|
542
|
+
if (mainCssFile) {
|
|
543
|
+
const mainCss = await mainCssFile.async("text");
|
|
544
|
+
writeFileSync(join(distDir, "main.css"), mainCss, "utf-8");
|
|
505
545
|
}
|
|
506
546
|
|
|
507
547
|
return {
|
|
@@ -651,6 +691,65 @@ function handleMutateAppData({ pathParams, body }: RouteHandlerArgs) {
|
|
|
651
691
|
return { success: true, result };
|
|
652
692
|
}
|
|
653
693
|
|
|
694
|
+
/**
|
|
695
|
+
* Wire-format provenance tag for an app, matching what `apps_list` reports:
|
|
696
|
+
* `"workspace"` for user-created apps, `"plugin:<name>"` for plugin-bundled
|
|
697
|
+
* ones. Clients read it to hide the mutation actions (delete, share, deploy)
|
|
698
|
+
* the daemon rejects for plugin apps.
|
|
699
|
+
*/
|
|
700
|
+
function appOriginWire(origin: AppOrigin): string {
|
|
701
|
+
return origin.kind === "plugin" ? `plugin:${origin.pluginName}` : "workspace";
|
|
702
|
+
}
|
|
703
|
+
|
|
704
|
+
/**
|
|
705
|
+
* Compile a multi-file plugin app on open, without touching its `dist/`.
|
|
706
|
+
*
|
|
707
|
+
* A plugin app's real `dist/` is owned by the monitor process
|
|
708
|
+
* (`plugin-source-watch`), which builds it on install and on every source
|
|
709
|
+
* change. But a just-installed app can be opened before the monitor's first
|
|
710
|
+
* pass lands, which would otherwise render the "not compiled yet" fallback.
|
|
711
|
+
* Building in place here would both write into the read-only plugin tree and
|
|
712
|
+
* race the monitor's `rm -rf dist` + write. Instead we compile into a private
|
|
713
|
+
* temp dir and return the self-contained HTML from there, leaving `dist/`
|
|
714
|
+
* ownership solely with the monitor. Returns `null` if the build fails, so the
|
|
715
|
+
* caller falls back to the standard fallback HTML.
|
|
716
|
+
*/
|
|
717
|
+
async function compilePluginAppHtmlEphemeral(
|
|
718
|
+
appId: string,
|
|
719
|
+
sourceDir: string,
|
|
720
|
+
): Promise<string | null> {
|
|
721
|
+
// `resolveAppSource` classifies any app without a root index.html as
|
|
722
|
+
// multi-file, even a malformed one with no src/ (or one whose source was
|
|
723
|
+
// removed before the monitor built dist/). Building that would make the
|
|
724
|
+
// compiler throw, so skip it and let the caller serve the standard fallback
|
|
725
|
+
// rather than surfacing a 500 from a plain open.
|
|
726
|
+
if (!existsSync(join(sourceDir, "src"))) {
|
|
727
|
+
return null;
|
|
728
|
+
}
|
|
729
|
+
const tmpRoot = mkdtempSync(join(tmpdir(), "vellum-plugin-app-"));
|
|
730
|
+
try {
|
|
731
|
+
const result = await runCompile(sourceDir, join(tmpRoot, "dist"));
|
|
732
|
+
if (!result.ok) {
|
|
733
|
+
log.warn(
|
|
734
|
+
{ appId, errors: result.errors },
|
|
735
|
+
"Ephemeral compile failed on plugin app open",
|
|
736
|
+
);
|
|
737
|
+
return null;
|
|
738
|
+
}
|
|
739
|
+
// resolveEffectiveAppHtmlFromDir reads `<tmpRoot>/dist/index.html` and
|
|
740
|
+
// inlines its JS/CSS, yielding a self-contained page.
|
|
741
|
+
return resolveEffectiveAppHtmlFromDir(tmpRoot);
|
|
742
|
+
} catch (err) {
|
|
743
|
+
// The compiler reports build failures via `ok: false`; a thrown error is
|
|
744
|
+
// unexpected (e.g. a filesystem fault mid-build) and must still degrade to
|
|
745
|
+
// the fallback instead of a 500.
|
|
746
|
+
log.warn({ appId, err }, "Ephemeral compile threw on plugin app open");
|
|
747
|
+
return null;
|
|
748
|
+
} finally {
|
|
749
|
+
rmSync(tmpRoot, { recursive: true, force: true });
|
|
750
|
+
}
|
|
751
|
+
}
|
|
752
|
+
|
|
654
753
|
async function handleOpenApp({ pathParams }: RouteHandlerArgs) {
|
|
655
754
|
const appId = pathParams?.id as string;
|
|
656
755
|
const source = resolveAppSource(appId);
|
|
@@ -658,33 +757,40 @@ async function handleOpenApp({ pathParams }: RouteHandlerArgs) {
|
|
|
658
757
|
throw new NotFoundError(`App not found: ${appId}`);
|
|
659
758
|
}
|
|
660
759
|
|
|
661
|
-
|
|
662
|
-
|
|
663
|
-
|
|
664
|
-
|
|
665
|
-
|
|
666
|
-
|
|
667
|
-
|
|
668
|
-
|
|
669
|
-
|
|
760
|
+
const result = {
|
|
761
|
+
appId: source.id,
|
|
762
|
+
dirName: source.dirName,
|
|
763
|
+
name: source.name,
|
|
764
|
+
origin: appOriginWire(source.origin),
|
|
765
|
+
};
|
|
766
|
+
|
|
767
|
+
// Apps auto-compile on open when their dist/ is missing so a freshly
|
|
768
|
+
// installed app renders immediately instead of the "not compiled yet"
|
|
769
|
+
// fallback.
|
|
770
|
+
if (!existsSync(join(source.sourceDir, "dist", "index.html"))) {
|
|
771
|
+
if (source.origin.kind === "workspace") {
|
|
772
|
+
// Workspace apps own their dist/ and are only compiled in this (daemon)
|
|
773
|
+
// process, so building in place is safe.
|
|
774
|
+
const compiled = await compileApp(source.sourceDir);
|
|
775
|
+
if (!compiled.ok) {
|
|
670
776
|
log.warn(
|
|
671
|
-
{ appId, errors:
|
|
777
|
+
{ appId, errors: compiled.errors },
|
|
672
778
|
"Auto-compile failed on app open",
|
|
673
779
|
);
|
|
674
780
|
}
|
|
781
|
+
} else {
|
|
782
|
+
// Plugin apps: compile off to the side (see helper) rather than write
|
|
783
|
+
// into the plugin tree or race the monitor.
|
|
784
|
+
const html = await compilePluginAppHtmlEphemeral(appId, source.sourceDir);
|
|
785
|
+
if (html !== null) {
|
|
786
|
+
return { ...result, html };
|
|
787
|
+
}
|
|
788
|
+
// Build failed — fall through to the standard fallback HTML.
|
|
675
789
|
}
|
|
676
790
|
}
|
|
677
791
|
|
|
678
|
-
const html = resolveEffectiveAppHtmlFromDir(
|
|
679
|
-
|
|
680
|
-
source.formatVersion,
|
|
681
|
-
);
|
|
682
|
-
return {
|
|
683
|
-
appId: source.id,
|
|
684
|
-
dirName: source.dirName,
|
|
685
|
-
name: source.name,
|
|
686
|
-
html,
|
|
687
|
-
};
|
|
792
|
+
const html = resolveEffectiveAppHtmlFromDir(source.sourceDir);
|
|
793
|
+
return { ...result, html };
|
|
688
794
|
}
|
|
689
795
|
|
|
690
796
|
/**
|
|
@@ -1015,6 +1121,7 @@ export const ROUTES: RouteDefinition[] = [
|
|
|
1015
1121
|
dirName: z.string(),
|
|
1016
1122
|
name: z.string(),
|
|
1017
1123
|
html: z.string(),
|
|
1124
|
+
origin: z.string(),
|
|
1018
1125
|
}),
|
|
1019
1126
|
},
|
|
1020
1127
|
{
|