@vellumai/assistant 0.8.7 → 0.8.8
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/Dockerfile +20 -4
- package/docker-entrypoint.sh +4 -2
- package/docker-init-apt-root.sh +3 -1
- package/docker-kata-apt-env.sh +3 -1
- package/docker-kata-runtime-family.sh +12 -0
- package/docs/architecture/memory.md +1 -1
- package/docs/plugins.md +75 -79
- package/examples/plugins/echo/README.md +6 -12
- package/examples/plugins/echo/register.ts +0 -41
- package/node_modules/@vellumai/skill-host-contracts/src/server-message.ts +3 -3
- package/openapi.yaml +3381 -348
- package/package.json +1 -1
- package/scripts/generate-openapi.ts +68 -41
- package/src/__tests__/agent-loop-exit-reason.test.ts +34 -39
- package/src/__tests__/agent-loop-provider-error-recording.test.ts +1 -1
- package/src/__tests__/agent-loop.test.ts +37 -87
- package/src/__tests__/agent-wake-disk-pressure-callsite.test.ts +2 -0
- package/src/__tests__/annotate-activity-metadata.test.ts +262 -0
- package/src/__tests__/annotate-risk-options.test.ts +2 -3
- package/src/__tests__/anthropic-provider.test.ts +95 -2
- package/src/__tests__/assistant-event-hub.test.ts +25 -0
- package/src/__tests__/assistant-events-sse-shed.test.ts +8 -0
- package/src/__tests__/{conversation-stream-state.test.ts → assistant-stream-state.test.ts} +252 -91
- package/src/__tests__/auth-fallback-events-store.test.ts +116 -0
- package/src/__tests__/background-workers-disk-pressure.test.ts +6 -0
- package/src/__tests__/btw-routes.test.ts +62 -3
- package/src/__tests__/build-persisted-content.test.ts +184 -0
- package/src/__tests__/catalog-files.test.ts +1 -1
- package/src/__tests__/clawhub-files.test.ts +1 -1
- package/src/__tests__/compaction-pipeline.test.ts +1 -1
- package/src/__tests__/compaction.benchmark.test.ts +0 -30
- package/src/__tests__/config-watcher.test.ts +1 -1
- package/src/__tests__/conversation-abort-tool-results.test.ts +57 -19
- package/src/__tests__/conversation-agent-loop-disk-pressure.test.ts +6 -2
- package/src/__tests__/conversation-agent-loop-inference-profile.test.ts +10 -4
- package/src/__tests__/conversation-agent-loop-overflow.test.ts +313 -1136
- package/src/__tests__/conversation-agent-loop.test.ts +596 -1616
- package/src/__tests__/conversation-analysis-routes.test.ts +6 -0
- package/src/__tests__/conversation-history-web-search.test.ts +11 -1
- package/src/__tests__/conversation-pairing.test.ts +4 -31
- package/src/__tests__/conversation-process-app-control-preactivation.test.ts +6 -0
- package/src/__tests__/conversation-provider-retry-repair.test.ts +26 -5
- package/src/__tests__/conversation-queue.test.ts +2 -0
- package/src/__tests__/conversation-routes-disk-view.test.ts +3 -0
- package/src/__tests__/conversation-routes-slash-commands.test.ts +6 -5
- package/src/__tests__/conversation-runtime-assembly.test.ts +170 -229
- package/src/__tests__/conversation-runtime-workspace.test.ts +3 -24
- package/src/__tests__/conversation-slash-commands.test.ts +8 -42
- package/src/__tests__/conversation-slash-queue.test.ts +6 -1
- package/src/__tests__/conversation-surfaces-action-delivery.test.ts +84 -0
- package/src/__tests__/conversation-sync-tags.test.ts +27 -15
- package/src/__tests__/conversation-title-service.test.ts +135 -2
- package/src/__tests__/conversation-workspace-injection.test.ts +6 -1
- package/src/__tests__/cross-provider-web-search.test.ts +214 -1
- package/src/__tests__/db-schedule-syntax-migration.test.ts +5 -0
- package/src/__tests__/dm-persistence.test.ts +5 -1
- package/src/__tests__/empty-response-hook.test.ts +304 -0
- package/src/__tests__/feature-flag-test-helpers.ts +2 -2
- package/src/__tests__/gemini-image-service.test.ts +13 -0
- package/src/__tests__/helpers/mock-provider.ts +110 -0
- package/src/__tests__/helpers/native-web-search-harness.ts +129 -0
- package/src/__tests__/history-repair-hook.test.ts +1 -0
- package/src/__tests__/identity-intro-cache.test.ts +12 -100
- package/src/__tests__/identity-routes.test.ts +248 -7
- package/src/__tests__/inbound-slack-persistence.test.ts +5 -1
- package/src/__tests__/injector-background-turn.test.ts +2 -8
- package/src/__tests__/injector-chain.test.ts +106 -270
- package/src/__tests__/injector-disk-pressure.test.ts +3 -12
- package/src/__tests__/injector-document-comments.test.ts +2 -2
- package/src/__tests__/injector-pkb-v2-silenced.test.ts +30 -22
- package/src/__tests__/injector-v3-suppression.test.ts +31 -37
- package/src/__tests__/internal-telemetry-routes.test.ts +109 -0
- package/src/__tests__/list-messages-page-latest.test.ts +60 -0
- package/src/__tests__/list-messages-tool-merge.test.ts +20 -0
- package/src/__tests__/llm-usage-store.test.ts +223 -1
- package/src/__tests__/memory-retrieval-hook.test.ts +297 -0
- package/src/__tests__/memory-v2-static-injector.test.ts +103 -35
- package/src/__tests__/native-web-search.test.ts +191 -0
- package/src/__tests__/onboarding-template-contract.test.ts +2 -0
- package/src/__tests__/openai-image-service.test.ts +17 -0
- package/src/__tests__/openai-provider.test.ts +31 -1
- package/src/__tests__/persist-unsendable-image.test.ts +215 -0
- package/src/__tests__/persistence-secret-redaction.test.ts +1 -0
- package/src/__tests__/pipeline-runner.test.ts +29 -39
- package/src/__tests__/pkb-autoinject.test.ts +2 -5
- package/src/__tests__/plugin-bootstrap.test.ts +13 -28
- package/src/__tests__/plugin-registry.test.ts +0 -27
- package/src/__tests__/plugin-types.test.ts +2 -125
- package/src/__tests__/process-message-display-content.test.ts +6 -2
- package/src/__tests__/regenerate-fire-and-forget-trace.test.ts +5 -1
- package/src/__tests__/resolve-trust-class.test.ts +4 -4
- package/src/__tests__/runtime-events-sse-reconnect.test.ts +60 -23
- package/src/__tests__/schedule-routes.test.ts +603 -2
- package/src/__tests__/schedule-store.test.ts +41 -0
- package/src/__tests__/schedule-tools.test.ts +35 -0
- package/src/__tests__/server-history-render.test.ts +314 -1
- package/src/__tests__/skillssh-files.test.ts +1 -1
- package/src/__tests__/system-prompt.test.ts +20 -0
- package/src/__tests__/task-scheduler.test.ts +162 -1
- package/src/__tests__/terminal-tools.test.ts +6 -1
- package/src/__tests__/title-generate-hook.test.ts +319 -0
- package/src/__tests__/tool-error-hook.test.ts +278 -0
- package/src/__tests__/tool-preview-lifecycle.test.ts +468 -5
- package/src/__tests__/tool-result-metadata-plumbing.test.ts +1 -0
- package/src/__tests__/tool-result-truncate-hook.test.ts +127 -0
- package/src/__tests__/tool-result-truncation.test.ts +0 -2
- package/src/__tests__/ui-choice-copy-surfaces.test.ts +254 -0
- package/src/__tests__/ui-work-result-surface.test.ts +159 -0
- package/src/__tests__/usage-routes.test.ts +285 -1
- package/src/__tests__/user-plugin-loader.test.ts +2 -2
- package/src/__tests__/voice-session-bridge.test.ts +6 -3
- package/src/__tests__/web-search-backend-failure.test.ts +166 -0
- package/src/agent/loop.ts +346 -442
- package/src/api/events/assistant-thinking-delta.ts +33 -0
- package/src/api/events/tool-output-chunk.ts +45 -0
- package/src/api/events/tool-use-preview-start.ts +32 -0
- package/src/api/events/trace-event.ts +69 -0
- package/src/api/index.ts +48 -13
- package/src/api/responses/conversation-message.ts +368 -0
- package/src/avatar/__tests__/avatar-store.test.ts +34 -29
- package/src/cli/commands/__tests__/notifications.test.ts +58 -14
- package/src/cli/commands/notifications.ts +112 -60
- package/src/config/assistant-feature-flags.ts +22 -11
- package/src/config/bundled-skills/app-builder/SKILL.md +3 -20
- package/src/config/bundled-skills/app-builder/references/examples/README.md +17 -0
- package/src/config/bundled-skills/app-builder/references/examples/expense-tracker.md +515 -0
- package/src/config/bundled-skills/app-builder/references/examples/focus-timer.md +342 -0
- package/src/config/bundled-skills/app-builder/references/examples/habit-tracker.md +490 -0
- package/src/config/bundled-skills/document-editor/SKILL.md +1 -1
- package/src/config/bundled-skills/messaging/SKILL.md +0 -7
- package/src/config/feature-flag-cache.ts +3 -3
- package/src/config/feature-flag-registry.json +35 -3
- package/src/config/schemas/__tests__/memory-v2.test.ts +1 -0
- package/src/config/schemas/__tests__/memory-v3.test.ts +25 -0
- package/src/config/schemas/llm.ts +1 -0
- package/src/config/schemas/memory-v2.ts +8 -0
- package/src/config/schemas/memory-v3.ts +8 -0
- package/src/config/schemas/platform.ts +8 -0
- package/src/config/seed-inference-profiles.ts +2 -2
- package/src/config/skills.ts +13 -0
- package/src/context/compactor.ts +1 -1
- package/src/context/strip-injections.ts +122 -0
- package/src/context/token-estimator.ts +23 -0
- package/src/context/tool-result-truncation.ts +0 -23
- package/src/context/window-manager.ts +3 -6
- package/src/credential-execution/executable-discovery.ts +16 -0
- package/src/daemon/__tests__/conversation-lifecycle-auto-analyze.test.ts +6 -0
- package/src/daemon/__tests__/inference-profile-notification.test.ts +153 -0
- package/src/daemon/__tests__/native-web-search-metadata.test.ts +10 -8
- package/src/daemon/assistant-attachments.ts +1 -1
- package/src/daemon/config-watcher.ts +2 -2
- package/src/daemon/context-overflow-reducer.ts +0 -1
- package/src/daemon/conversation-agent-loop-handlers.ts +605 -153
- package/src/daemon/conversation-agent-loop.ts +281 -760
- package/src/daemon/conversation-history.ts +5 -4
- package/src/daemon/conversation-lifecycle.ts +3 -4
- package/src/daemon/conversation-messaging.ts +7 -6
- package/src/daemon/conversation-process.ts +11 -16
- package/src/daemon/conversation-runtime-assembly.ts +130 -347
- package/src/daemon/conversation-slash.ts +6 -25
- package/src/daemon/conversation-surfaces.ts +222 -4
- package/src/daemon/conversation-tool-setup.ts +2 -29
- package/src/daemon/conversation.ts +32 -14
- package/src/daemon/external-plugins-bootstrap.ts +9 -10
- package/src/daemon/handlers/config-a2a.ts +51 -36
- package/src/daemon/handlers/config-slack-channel.ts +20 -14
- package/src/daemon/handlers/config-telegram.ts +16 -2
- package/src/daemon/handlers/shared.ts +156 -84
- package/src/daemon/handlers/skills.ts +39 -10
- package/src/daemon/lifecycle.ts +4 -0
- package/src/daemon/message-types/apps.ts +1 -29
- package/src/daemon/message-types/messages.ts +9 -57
- package/src/daemon/message-types/skills.ts +2 -0
- package/src/daemon/message-types/surfaces.ts +136 -3
- package/src/daemon/now-scratchpad.ts +21 -0
- package/src/daemon/orphan-reaper.test.ts +210 -0
- package/src/daemon/orphan-reaper.ts +240 -0
- package/src/daemon/persist-unsendable-image.ts +117 -0
- package/src/daemon/process-message.ts +1 -3
- package/src/daemon/trace-emitter.ts +6 -4
- package/src/daemon/trust-context.ts +19 -0
- package/src/daemon/wake-target-adapter.ts +3 -1
- package/src/home/home-greeting-cache.ts +24 -1
- package/src/ipc/gateway-client.test.ts +2 -2
- package/src/ipc/gateway-client.ts +3 -3
- package/src/media/gemini-image-service.ts +15 -0
- package/src/media/openai-image-service.ts +14 -0
- package/src/media/types.ts +34 -0
- package/src/memory/__tests__/jobs-worker-v2-schedule.test.ts +56 -0
- package/src/memory/auth-fallback-events-store.ts +94 -0
- package/src/memory/conversation-title-service.ts +65 -41
- package/src/memory/db-init.ts +4 -0
- package/src/memory/graph/__tests__/conversation-graph-memory-registry.test.ts +119 -0
- package/src/memory/graph/conversation-graph-memory.ts +65 -0
- package/src/memory/jobs-store.ts +33 -0
- package/src/memory/jobs-worker.ts +31 -4
- package/src/memory/llm-usage-store.ts +224 -50
- package/src/memory/migrations/222-strip-placeholder-sentinels-from-messages.ts +6 -5
- package/src/memory/migrations/270-schedule-source-conversation.ts +13 -0
- package/src/memory/migrations/271-create-auth-fallback-events.ts +21 -0
- package/src/memory/migrations/index.ts +2 -0
- package/src/memory/pkb/autoinject.ts +61 -0
- package/src/memory/pkb/context.ts +50 -0
- package/src/memory/pkb/types.ts +14 -0
- package/src/memory/schedule-attribution-sql.ts +104 -0
- package/src/memory/schema/infrastructure.ts +16 -0
- package/src/memory/usage-grouped-buckets.ts +6 -1
- package/src/memory/v2/__tests__/consolidation-job.test.ts +1 -1
- package/src/memory/v2/consolidation-job.ts +1 -1
- package/src/memory/v3/__tests__/health.test.ts +16 -0
- package/src/memory/v3/__tests__/orchestrate.test.ts +45 -9
- package/src/memory/v3/__tests__/provider-blocks.test.ts +13 -0
- package/src/memory/v3/__tests__/router.test.ts +101 -29
- package/src/memory/v3/__tests__/selector.test.ts +93 -27
- package/src/memory/v3/__tests__/shadow-plugin.test.ts +23 -5
- package/src/memory/v3/health.ts +0 -0
- package/src/memory/v3/llm-retry.ts +32 -0
- package/src/memory/v3/orchestrate.ts +26 -14
- package/src/memory/v3/provider-blocks.ts +15 -5
- package/src/memory/v3/router.ts +48 -42
- package/src/memory/v3/selector.ts +57 -42
- package/src/memory/v3/shadow-plugin.ts +47 -15
- package/src/memory/v3/types.ts +8 -0
- package/src/notifications/conversation-pairing.ts +8 -15
- package/src/notifications/decision-engine.ts +6 -3
- package/src/notifications/home-feed-side-effect.ts +12 -1
- package/src/permissions/prompter.ts +4 -0
- package/src/plugin-api/constants.ts +4 -0
- package/src/plugin-api/index.ts +8 -1
- package/src/plugin-api/types.ts +151 -1
- package/src/plugins/defaults/empty-response/hooks/stop.ts +126 -0
- package/src/plugins/defaults/empty-response/register.ts +8 -13
- package/src/plugins/defaults/index.ts +1 -15
- package/src/plugins/defaults/injectors/register.ts +243 -74
- package/src/plugins/defaults/memory-retrieval/hooks/post-compact.ts +91 -0
- package/src/plugins/defaults/memory-retrieval/hooks/user-prompt-submit-temp.ts +216 -0
- package/src/plugins/defaults/memory-retrieval/injector-chain.ts +35 -0
- package/src/plugins/defaults/title-generate/hooks/stop.ts +75 -0
- package/src/plugins/defaults/title-generate/hooks/user-prompt-submit.ts +35 -0
- package/src/plugins/defaults/title-generate/package.json +1 -1
- package/src/plugins/defaults/title-generate/register.ts +18 -18
- package/src/plugins/defaults/tool-error/hooks/post-tool-use.ts +118 -0
- package/src/plugins/defaults/tool-error/package.json +1 -1
- package/src/plugins/defaults/tool-error/register.ts +9 -21
- package/src/plugins/defaults/tool-result-truncate/hooks/post-tool-use.ts +32 -0
- package/src/plugins/defaults/tool-result-truncate/register.ts +10 -21
- package/src/plugins/defaults/tool-result-truncate/terminal.ts +37 -18
- package/src/plugins/pipeline.ts +6 -18
- package/src/plugins/registry.ts +8 -25
- package/src/plugins/types.ts +43 -474
- package/src/proactive-artifact/aux-message-injector.ts +3 -3
- package/src/proactive-artifact/job.test.ts +7 -12
- package/src/prompts/__tests__/system-prompt.test.ts +36 -0
- package/src/prompts/templates/BOOTSTRAP-ACTIVATION-RAIL.md +62 -0
- package/src/prompts/templates/BOOTSTRAP.md +2 -2
- package/src/prompts/templates/system-sections.ts +15 -0
- package/src/providers/anthropic/client.ts +37 -29
- package/src/providers/openai/__tests__/chat-completions-provider-reasoning.test.ts +112 -0
- package/src/providers/openai/chat-completions-provider.ts +44 -0
- package/src/providers/openrouter/client.ts +1 -0
- package/src/providers/placeholder-sentinels.ts +35 -0
- package/src/runtime/__tests__/agent-wake.test.ts +5 -1
- package/src/runtime/agent-wake.ts +2 -2
- package/src/runtime/assistant-event-hub.ts +36 -6
- package/src/runtime/{conversation-stream-state.ts → assistant-stream-state.ts} +132 -58
- package/src/runtime/http-router.ts +16 -21
- package/src/runtime/http-types.ts +16 -70
- package/src/runtime/pending-interactions.ts +1 -0
- package/src/runtime/routes/__tests__/consolidation-routes.test.ts +265 -2
- package/src/runtime/routes/__tests__/conversation-query-routes.test.ts +31 -1
- package/src/runtime/routes/__tests__/memory-v2-routes.test.ts +6 -2
- package/src/runtime/routes/__tests__/tts-routes.test.ts +6 -2
- package/src/runtime/routes/app-management-routes.ts +6 -117
- package/src/runtime/routes/app-routes.ts +13 -15
- package/src/runtime/routes/attachment-routes.ts +26 -15
- package/src/runtime/routes/avatar-routes.ts +26 -0
- package/src/runtime/routes/btw-routes.ts +29 -23
- package/src/runtime/routes/consolidation-routes.ts +120 -20
- package/src/runtime/routes/conversation-query-routes.ts +2 -0
- package/src/runtime/routes/conversation-routes.ts +358 -184
- package/src/runtime/routes/documents-routes.ts +4 -0
- package/src/runtime/routes/domain-routes.ts +51 -37
- package/src/runtime/routes/epoch-millis-range.ts +34 -0
- package/src/runtime/routes/events-routes.ts +28 -34
- package/src/runtime/routes/gateway-log-routes.ts +26 -4
- package/src/runtime/routes/heartbeat-routes.ts +32 -12
- package/src/runtime/routes/identity-intro-cache.ts +11 -34
- package/src/runtime/routes/identity-routes.ts +208 -17
- package/src/runtime/routes/image-generation-routes.ts +40 -2
- package/src/runtime/routes/index.ts +2 -0
- package/src/runtime/routes/integrations/a2a.ts +12 -10
- package/src/runtime/routes/integrations/slack/__tests__/channel.test.ts +16 -0
- package/src/runtime/routes/integrations/slack/channel.ts +4 -0
- package/src/runtime/routes/integrations/slack/share.ts +27 -6
- package/src/runtime/routes/integrations/telegram.ts +6 -0
- package/src/runtime/routes/integrations/twilio.ts +42 -0
- package/src/runtime/routes/internal-telemetry-routes.ts +88 -0
- package/src/runtime/routes/log-export-routes.ts +8 -0
- package/src/runtime/routes/memory-v2-routes.ts +15 -8
- package/src/runtime/routes/memory-v3-routes.ts +50 -28
- package/src/runtime/routes/oauth-apps.ts +66 -12
- package/src/runtime/routes/oauth-providers.ts +44 -5
- package/src/runtime/routes/platform-routes.ts +81 -5
- package/src/runtime/routes/playground/__tests__/force-compact.test.ts +6 -4
- package/src/runtime/routes/playground/force-compact.ts +1 -1
- package/src/runtime/routes/rename-conversation-routes.ts +5 -0
- package/src/runtime/routes/schedule-routes.ts +152 -42
- package/src/runtime/routes/secret-routes.ts +14 -2
- package/src/runtime/routes/skills-routes.ts +43 -14
- package/src/runtime/routes/tool-call-confirmation-enrichment.test.ts +161 -0
- package/src/runtime/routes/tool-call-confirmation-enrichment.ts +107 -0
- package/src/runtime/routes/trust-rules-routes.ts +26 -2
- package/src/runtime/routes/tts-routes.ts +35 -0
- package/src/runtime/routes/types.ts +66 -8
- package/src/runtime/routes/usage-routes.ts +47 -39
- package/src/runtime/routes/webhook-routes.ts +41 -2
- package/src/runtime/routes/workspace-routes.ts +4 -0
- package/src/runtime/services/__tests__/analyze-conversation.test.ts +6 -0
- package/src/runtime/services/analyze-conversation.ts +2 -2
- package/src/schedule/schedule-store.ts +20 -1
- package/src/schedule/schedule-usage-store.ts +83 -0
- package/src/schedule/scheduler.ts +12 -5
- package/src/skills/catalog-files.ts +2 -2
- package/src/skills/catalog-install.ts +3 -0
- package/src/skills/categories-cache.ts +118 -0
- package/src/skills/clawhub-files.ts +1 -2
- package/src/skills/skillssh-files.ts +1 -2
- package/src/telemetry/types.ts +29 -1
- package/src/telemetry/usage-telemetry-reporter.test.ts +112 -3
- package/src/telemetry/usage-telemetry-reporter.ts +57 -2
- package/src/tools/executor.ts +1 -53
- package/src/tools/network/__tests__/web-search-metadata.test.ts +7 -1
- package/src/tools/network/__tests__/web-search.test.ts +11 -3
- package/src/tools/network/web-search-error.test.ts +248 -0
- package/src/tools/network/web-search-error.ts +267 -0
- package/src/tools/network/web-search.ts +207 -48
- package/src/tools/schedule/create.ts +2 -0
- package/src/tools/terminal/safe-env.ts +10 -1
- package/src/tools/ui-surface/definitions.ts +9 -1
- package/src/tts/__tests__/provider-catalog-consistency.test.ts +85 -1
- package/src/tts/provider-catalog.ts +76 -1
- package/src/util/mutex.ts +47 -0
- package/src/workspace/git-service.ts +1 -42
- package/src/workspace/migrations/095-bump-heartbeat-interval-30m-to-60m.ts +51 -0
- package/src/workspace/migrations/096-reduce-quality-profile-effort.ts +72 -0
- package/src/workspace/migrations/097-enable-adaptive-thinking-managed-profiles.ts +93 -0
- package/src/workspace/migrations/registry.ts +6 -0
- package/src/__tests__/bootstrap-turn-cleanup.test.ts +0 -44
- package/src/__tests__/empty-response-pipeline.test.ts +0 -423
- package/src/__tests__/llm-call-pipeline.test.ts +0 -287
- package/src/__tests__/memory-retrieval-pipeline.test.ts +0 -418
- package/src/__tests__/persistence-pipeline.test.ts +0 -503
- package/src/__tests__/title-generate-pipeline.test.ts +0 -211
- package/src/__tests__/token-estimate-pipeline.test.ts +0 -479
- package/src/__tests__/tool-error-pipeline.test.ts +0 -241
- package/src/__tests__/tool-execute-pipeline.test.ts +0 -417
- package/src/__tests__/tool-result-truncate-pipeline.test.ts +0 -341
- package/src/daemon/bootstrap-turn-cleanup.ts +0 -45
- package/src/gallery/default-gallery.ts +0 -1359
- package/src/gallery/gallery-manifest.ts +0 -28
- package/src/home/feature-gate.ts +0 -22
- package/src/plugins/defaults/empty-response/middlewares/emptyResponse.ts +0 -22
- package/src/plugins/defaults/empty-response/terminal.ts +0 -106
- package/src/plugins/defaults/injectors/package.json +0 -15
- package/src/plugins/defaults/llm-call/middlewares/llmCall.ts +0 -17
- package/src/plugins/defaults/llm-call/package.json +0 -15
- package/src/plugins/defaults/llm-call/register.ts +0 -45
- package/src/plugins/defaults/memory-retrieval/middlewares/memoryRetrieval.ts +0 -17
- package/src/plugins/defaults/memory-retrieval/package.json +0 -15
- package/src/plugins/defaults/memory-retrieval/register.ts +0 -181
- package/src/plugins/defaults/persistence/middlewares/persistence.ts +0 -19
- package/src/plugins/defaults/persistence/package.json +0 -15
- package/src/plugins/defaults/persistence/register.ts +0 -38
- package/src/plugins/defaults/persistence/terminal.ts +0 -83
- package/src/plugins/defaults/title-generate/terminal.ts +0 -31
- package/src/plugins/defaults/token-estimate/middlewares/tokenEstimate.ts +0 -23
- package/src/plugins/defaults/token-estimate/package.json +0 -15
- package/src/plugins/defaults/token-estimate/register.ts +0 -34
- package/src/plugins/defaults/token-estimate/terminal.ts +0 -40
- package/src/plugins/defaults/tool-error/middlewares/toolError.ts +0 -21
- package/src/plugins/defaults/tool-error/terminal.ts +0 -47
- package/src/plugins/defaults/tool-execute/middlewares/toolExecute.ts +0 -23
- package/src/plugins/defaults/tool-execute/package.json +0 -15
- package/src/plugins/defaults/tool-execute/register.ts +0 -49
- package/src/plugins/defaults/tool-result-truncate/middlewares/toolResultTruncate.ts +0 -23
- package/src/plugins/defaults/tool-result-truncate/types.ts +0 -22
- package/src/skills/category-inference.ts +0 -111
|
@@ -24,7 +24,10 @@
|
|
|
24
24
|
* once assistant output exists.
|
|
25
25
|
*/
|
|
26
26
|
|
|
27
|
-
import {
|
|
27
|
+
import { mkdtempSync, readFileSync, rmSync, writeFileSync } from "node:fs";
|
|
28
|
+
import { tmpdir } from "node:os";
|
|
29
|
+
import { join } from "node:path";
|
|
30
|
+
import { afterEach, beforeEach, describe, expect, mock, test } from "bun:test";
|
|
28
31
|
|
|
29
32
|
mock.module("../../../util/logger.js", () => ({
|
|
30
33
|
getLogger: () =>
|
|
@@ -33,19 +36,28 @@ mock.module("../../../util/logger.js", () => ({
|
|
|
33
36
|
}),
|
|
34
37
|
}));
|
|
35
38
|
|
|
39
|
+
import { invalidateConfigCache } from "../../../config/loader.js";
|
|
36
40
|
import { createConversation } from "../../../memory/conversation-crud.js";
|
|
37
41
|
import { getDb } from "../../../memory/db-connection.js";
|
|
38
42
|
import { initializeDb } from "../../../memory/db-init.js";
|
|
39
|
-
import {
|
|
43
|
+
import { enqueueMemoryJob } from "../../../memory/jobs-store.js";
|
|
44
|
+
import { recordUsageEvent } from "../../../memory/llm-usage-store.js";
|
|
45
|
+
import { rawAll, rawRun } from "../../../memory/raw-query.js";
|
|
40
46
|
import { ROUTES } from "../consolidation-routes.js";
|
|
41
47
|
import type { RouteDefinition } from "../types.js";
|
|
42
48
|
|
|
43
49
|
initializeDb();
|
|
44
50
|
|
|
51
|
+
let workspaceDir: string;
|
|
52
|
+
let origWorkspaceDir: string | undefined;
|
|
53
|
+
let configPath: string;
|
|
54
|
+
|
|
45
55
|
function resetTables(): void {
|
|
46
56
|
const db = getDb();
|
|
57
|
+
db.run(`DELETE FROM llm_usage_events`);
|
|
47
58
|
db.run(`DELETE FROM messages`);
|
|
48
59
|
db.run(`DELETE FROM conversations`);
|
|
60
|
+
db.run(`DELETE FROM memory_jobs`);
|
|
49
61
|
}
|
|
50
62
|
|
|
51
63
|
function findHandler(operationId: string): RouteDefinition["handler"] {
|
|
@@ -69,6 +81,59 @@ function insertMessage(
|
|
|
69
81
|
);
|
|
70
82
|
}
|
|
71
83
|
|
|
84
|
+
function recordUsageCostAt(
|
|
85
|
+
conversationId: string,
|
|
86
|
+
requestId: string,
|
|
87
|
+
createdAt: number,
|
|
88
|
+
estimatedCostUsd: number,
|
|
89
|
+
): void {
|
|
90
|
+
const event = recordUsageEvent(
|
|
91
|
+
{
|
|
92
|
+
conversationId,
|
|
93
|
+
runId: null,
|
|
94
|
+
requestId,
|
|
95
|
+
actor: "main_agent",
|
|
96
|
+
callSite: "mainAgent",
|
|
97
|
+
inferenceProfile: "balanced",
|
|
98
|
+
provider: "anthropic",
|
|
99
|
+
model: "claude-sonnet-4-20250514",
|
|
100
|
+
inputTokens: 100,
|
|
101
|
+
outputTokens: 50,
|
|
102
|
+
cacheCreationInputTokens: 0,
|
|
103
|
+
cacheReadInputTokens: 0,
|
|
104
|
+
rawUsage: null,
|
|
105
|
+
},
|
|
106
|
+
{ estimatedCostUsd, pricingStatus: "priced" },
|
|
107
|
+
);
|
|
108
|
+
rawRun(
|
|
109
|
+
"UPDATE llm_usage_events SET created_at = ? WHERE id = ?",
|
|
110
|
+
createdAt,
|
|
111
|
+
event.id,
|
|
112
|
+
);
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
function readConfig(): Record<string, unknown> {
|
|
116
|
+
return JSON.parse(readFileSync(configPath, "utf-8"));
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
function readMemoryJobRows(): Array<{
|
|
120
|
+
id: string;
|
|
121
|
+
status: string;
|
|
122
|
+
lastError: string | null;
|
|
123
|
+
payload: string;
|
|
124
|
+
}> {
|
|
125
|
+
return rawAll<{
|
|
126
|
+
id: string;
|
|
127
|
+
status: string;
|
|
128
|
+
lastError: string | null;
|
|
129
|
+
payload: string;
|
|
130
|
+
}>(`
|
|
131
|
+
SELECT id, status, last_error AS lastError, payload
|
|
132
|
+
FROM memory_jobs
|
|
133
|
+
ORDER BY id
|
|
134
|
+
`);
|
|
135
|
+
}
|
|
136
|
+
|
|
72
137
|
interface RunRecord {
|
|
73
138
|
id: string;
|
|
74
139
|
scheduledFor: number;
|
|
@@ -79,6 +144,7 @@ interface RunRecord {
|
|
|
79
144
|
skipReason: string | null;
|
|
80
145
|
error: string | null;
|
|
81
146
|
conversationId: string | null;
|
|
147
|
+
estimatedCostUsd: number;
|
|
82
148
|
createdAt: number;
|
|
83
149
|
}
|
|
84
150
|
|
|
@@ -88,9 +154,24 @@ interface ListRunsResponse {
|
|
|
88
154
|
|
|
89
155
|
describe("listConsolidationRuns handler", () => {
|
|
90
156
|
beforeEach(() => {
|
|
157
|
+
workspaceDir = mkdtempSync(join(tmpdir(), "vellum-consolidation-routes-"));
|
|
158
|
+
origWorkspaceDir = process.env.VELLUM_WORKSPACE_DIR;
|
|
159
|
+
process.env.VELLUM_WORKSPACE_DIR = workspaceDir;
|
|
160
|
+
configPath = join(workspaceDir, "config.json");
|
|
161
|
+
invalidateConfigCache();
|
|
91
162
|
resetTables();
|
|
92
163
|
});
|
|
93
164
|
|
|
165
|
+
afterEach(() => {
|
|
166
|
+
if (origWorkspaceDir === undefined) {
|
|
167
|
+
delete process.env.VELLUM_WORKSPACE_DIR;
|
|
168
|
+
} else {
|
|
169
|
+
process.env.VELLUM_WORKSPACE_DIR = origWorkspaceDir;
|
|
170
|
+
}
|
|
171
|
+
invalidateConfigCache();
|
|
172
|
+
rmSync(workspaceDir, { recursive: true, force: true });
|
|
173
|
+
});
|
|
174
|
+
|
|
94
175
|
test("returns only conversations sourced from memory_v2_consolidation", async () => {
|
|
95
176
|
createConversation({ title: "c1", source: "memory_v2_consolidation" });
|
|
96
177
|
createConversation({ title: "h1", source: "heartbeat" });
|
|
@@ -138,6 +219,47 @@ describe("listConsolidationRuns handler", () => {
|
|
|
138
219
|
expect(run.createdAt).toBe(1000);
|
|
139
220
|
});
|
|
140
221
|
|
|
222
|
+
test("exposes estimatedCostUsd from the conversation total when available", async () => {
|
|
223
|
+
const conv = createConversation({
|
|
224
|
+
title: "c1",
|
|
225
|
+
source: "memory_v2_consolidation",
|
|
226
|
+
});
|
|
227
|
+
rawRun(
|
|
228
|
+
"UPDATE conversations SET created_at = ?, total_estimated_cost = ? WHERE id = ?",
|
|
229
|
+
1000,
|
|
230
|
+
0.42,
|
|
231
|
+
conv.id,
|
|
232
|
+
);
|
|
233
|
+
insertMessage(conv.id, "assistant", 2000);
|
|
234
|
+
recordUsageCostAt(conv.id, "consolidation-fallback-cost", 1500, 0.99);
|
|
235
|
+
|
|
236
|
+
const handler = findHandler("listConsolidationRuns");
|
|
237
|
+
const result = (await handler({})) as ListRunsResponse;
|
|
238
|
+
|
|
239
|
+
expect(result.runs[0]!.estimatedCostUsd).toBeCloseTo(0.42);
|
|
240
|
+
});
|
|
241
|
+
|
|
242
|
+
test("falls back to conversation-window usage cost when the total is empty", async () => {
|
|
243
|
+
const conv = createConversation({
|
|
244
|
+
title: "c1",
|
|
245
|
+
source: "memory_v2_consolidation",
|
|
246
|
+
});
|
|
247
|
+
rawRun(
|
|
248
|
+
"UPDATE conversations SET created_at = ? WHERE id = ?",
|
|
249
|
+
1000,
|
|
250
|
+
conv.id,
|
|
251
|
+
);
|
|
252
|
+
insertMessage(conv.id, "assistant", 2000);
|
|
253
|
+
recordUsageCostAt(conv.id, "consolidation-before", 999, 0.4);
|
|
254
|
+
recordUsageCostAt(conv.id, "consolidation-inside", 1500, 0.07);
|
|
255
|
+
recordUsageCostAt(conv.id, "consolidation-after", 2001, 0.5);
|
|
256
|
+
|
|
257
|
+
const handler = findHandler("listConsolidationRuns");
|
|
258
|
+
const result = (await handler({})) as ListRunsResponse;
|
|
259
|
+
|
|
260
|
+
expect(result.runs[0]!.estimatedCostUsd).toBeCloseTo(0.07);
|
|
261
|
+
});
|
|
262
|
+
|
|
141
263
|
test("synthesizes status='running' when conversation has no assistant message", async () => {
|
|
142
264
|
createConversation({ title: "c1", source: "memory_v2_consolidation" });
|
|
143
265
|
|
|
@@ -256,3 +378,144 @@ describe("listConsolidationRuns handler", () => {
|
|
|
256
378
|
expect(bad.runs).toHaveLength(5);
|
|
257
379
|
});
|
|
258
380
|
});
|
|
381
|
+
|
|
382
|
+
describe("updateConsolidationConfig handler", () => {
|
|
383
|
+
beforeEach(() => {
|
|
384
|
+
workspaceDir = mkdtempSync(join(tmpdir(), "vellum-consolidation-routes-"));
|
|
385
|
+
origWorkspaceDir = process.env.VELLUM_WORKSPACE_DIR;
|
|
386
|
+
process.env.VELLUM_WORKSPACE_DIR = workspaceDir;
|
|
387
|
+
configPath = join(workspaceDir, "config.json");
|
|
388
|
+
invalidateConfigCache();
|
|
389
|
+
resetTables();
|
|
390
|
+
});
|
|
391
|
+
|
|
392
|
+
afterEach(() => {
|
|
393
|
+
if (origWorkspaceDir === undefined) {
|
|
394
|
+
delete process.env.VELLUM_WORKSPACE_DIR;
|
|
395
|
+
} else {
|
|
396
|
+
process.env.VELLUM_WORKSPACE_DIR = origWorkspaceDir;
|
|
397
|
+
}
|
|
398
|
+
invalidateConfigCache();
|
|
399
|
+
rmSync(workspaceDir, { recursive: true, force: true });
|
|
400
|
+
});
|
|
401
|
+
|
|
402
|
+
test("persists only the consolidation enabled override without disabling memory v2", async () => {
|
|
403
|
+
writeFileSync(
|
|
404
|
+
configPath,
|
|
405
|
+
JSON.stringify(
|
|
406
|
+
{
|
|
407
|
+
memory: {
|
|
408
|
+
v2: {
|
|
409
|
+
enabled: true,
|
|
410
|
+
consolidation_interval_hours: 4,
|
|
411
|
+
},
|
|
412
|
+
},
|
|
413
|
+
},
|
|
414
|
+
null,
|
|
415
|
+
2,
|
|
416
|
+
) + "\n",
|
|
417
|
+
);
|
|
418
|
+
|
|
419
|
+
const handler = findHandler("updateConsolidationConfig");
|
|
420
|
+
const result = (await handler({ body: { enabled: false } })) as {
|
|
421
|
+
available: boolean;
|
|
422
|
+
enabled: boolean;
|
|
423
|
+
intervalMs: number;
|
|
424
|
+
nextRunAt: number | null;
|
|
425
|
+
success: boolean;
|
|
426
|
+
};
|
|
427
|
+
|
|
428
|
+
expect(readConfig()).toEqual({
|
|
429
|
+
memory: {
|
|
430
|
+
v2: {
|
|
431
|
+
enabled: true,
|
|
432
|
+
consolidation_interval_hours: 4,
|
|
433
|
+
consolidation_enabled: false,
|
|
434
|
+
},
|
|
435
|
+
},
|
|
436
|
+
});
|
|
437
|
+
expect(result).toMatchObject({
|
|
438
|
+
available: true,
|
|
439
|
+
enabled: false,
|
|
440
|
+
intervalMs: 4 * 60 * 60 * 1000,
|
|
441
|
+
nextRunAt: null,
|
|
442
|
+
success: true,
|
|
443
|
+
});
|
|
444
|
+
});
|
|
445
|
+
|
|
446
|
+
test("disabling automatic consolidation cancels pending automatic jobs but preserves manual run-now jobs", async () => {
|
|
447
|
+
writeFileSync(
|
|
448
|
+
configPath,
|
|
449
|
+
JSON.stringify(
|
|
450
|
+
{
|
|
451
|
+
memory: {
|
|
452
|
+
v2: {
|
|
453
|
+
enabled: true,
|
|
454
|
+
consolidation_enabled: true,
|
|
455
|
+
},
|
|
456
|
+
},
|
|
457
|
+
},
|
|
458
|
+
null,
|
|
459
|
+
2,
|
|
460
|
+
) + "\n",
|
|
461
|
+
);
|
|
462
|
+
const automaticJobId = enqueueMemoryJob("memory_v2_consolidate", {
|
|
463
|
+
trigger: "automatic",
|
|
464
|
+
});
|
|
465
|
+
const legacyJobId = enqueueMemoryJob("memory_v2_consolidate", {});
|
|
466
|
+
const manualJobId = enqueueMemoryJob("memory_v2_consolidate", {
|
|
467
|
+
trigger: "manual",
|
|
468
|
+
});
|
|
469
|
+
|
|
470
|
+
const handler = findHandler("updateConsolidationConfig");
|
|
471
|
+
await handler({ body: { enabled: false } });
|
|
472
|
+
|
|
473
|
+
const rowsById = new Map(
|
|
474
|
+
readMemoryJobRows().map((row) => [row.id, row] as const),
|
|
475
|
+
);
|
|
476
|
+
expect(rowsById.get(automaticJobId)).toMatchObject({
|
|
477
|
+
status: "failed",
|
|
478
|
+
lastError: "automatic_consolidation_disabled",
|
|
479
|
+
});
|
|
480
|
+
expect(rowsById.get(legacyJobId)).toMatchObject({
|
|
481
|
+
status: "failed",
|
|
482
|
+
lastError: "automatic_consolidation_disabled",
|
|
483
|
+
});
|
|
484
|
+
expect(rowsById.get(manualJobId)).toMatchObject({
|
|
485
|
+
status: "pending",
|
|
486
|
+
lastError: null,
|
|
487
|
+
payload: JSON.stringify({ trigger: "manual" }),
|
|
488
|
+
});
|
|
489
|
+
});
|
|
490
|
+
|
|
491
|
+
test("run-now remains available when automatic consolidation is disabled", async () => {
|
|
492
|
+
writeFileSync(
|
|
493
|
+
configPath,
|
|
494
|
+
JSON.stringify(
|
|
495
|
+
{
|
|
496
|
+
memory: {
|
|
497
|
+
v2: {
|
|
498
|
+
enabled: true,
|
|
499
|
+
consolidation_enabled: false,
|
|
500
|
+
},
|
|
501
|
+
},
|
|
502
|
+
},
|
|
503
|
+
null,
|
|
504
|
+
2,
|
|
505
|
+
) + "\n",
|
|
506
|
+
);
|
|
507
|
+
|
|
508
|
+
const handler = findHandler("runConsolidationNow");
|
|
509
|
+
const result = (await handler({})) as {
|
|
510
|
+
success: boolean;
|
|
511
|
+
ran: boolean;
|
|
512
|
+
jobId: string | null;
|
|
513
|
+
};
|
|
514
|
+
|
|
515
|
+
expect(result.success).toBe(true);
|
|
516
|
+
expect(result.ran).toBe(true);
|
|
517
|
+
expect(result.jobId).toBeString();
|
|
518
|
+
const row = readMemoryJobRows().find((job) => job.id === result.jobId);
|
|
519
|
+
expect(row?.payload).toBe(JSON.stringify({ trigger: "manual" }));
|
|
520
|
+
});
|
|
521
|
+
});
|
|
@@ -175,7 +175,10 @@ function seedRequestLog(
|
|
|
175
175
|
.run();
|
|
176
176
|
}
|
|
177
177
|
|
|
178
|
-
function seedConversationKey(
|
|
178
|
+
function seedConversationKey(
|
|
179
|
+
conversationKey: string,
|
|
180
|
+
conversationId: string,
|
|
181
|
+
): void {
|
|
179
182
|
getDb()
|
|
180
183
|
.insert(conversationKeys)
|
|
181
184
|
.values({
|
|
@@ -776,6 +779,33 @@ describe("PUT /v1/config/llm/profiles/:name", () => {
|
|
|
776
779
|
});
|
|
777
780
|
});
|
|
778
781
|
|
|
782
|
+
test("saves a profile using the minimax provider (regression #32404)", async () => {
|
|
783
|
+
// minimax is exposed as a first-class provider in the catalog, so saving
|
|
784
|
+
// a profile bound to it must pass ProfileEntry validation rather than 400.
|
|
785
|
+
const result = await replaceProfileRoute.handler({
|
|
786
|
+
pathParams: { name: "custom" },
|
|
787
|
+
body: {
|
|
788
|
+
provider: "minimax",
|
|
789
|
+
model: "MiniMax-M2.7",
|
|
790
|
+
},
|
|
791
|
+
});
|
|
792
|
+
|
|
793
|
+
expect(result).toEqual({ ok: true });
|
|
794
|
+
const savedProfile = (
|
|
795
|
+
savedRawConfig?.llm as {
|
|
796
|
+
profiles: Record<string, Record<string, unknown>>;
|
|
797
|
+
}
|
|
798
|
+
).profiles.custom;
|
|
799
|
+
|
|
800
|
+
expect(savedProfile.provider).toBe("minimax");
|
|
801
|
+
expect(savedProfile.model).toBe("MiniMax-M2.7");
|
|
802
|
+
expect(savedProfile.provider_connection).toBe("minimax-personal");
|
|
803
|
+
|
|
804
|
+
const conn = getConnection(getDb(), "minimax-personal");
|
|
805
|
+
expect(conn).not.toBeNull();
|
|
806
|
+
expect(conn!.provider).toBe("minimax");
|
|
807
|
+
});
|
|
808
|
+
|
|
779
809
|
describe("managed profile guard", () => {
|
|
780
810
|
beforeEach(() => {
|
|
781
811
|
// Seed a managed profile alongside the existing custom one.
|
|
@@ -155,7 +155,11 @@ describe("memory_v2_ema_scores route registration", () => {
|
|
|
155
155
|
expect(route!.tags).toContain("memory");
|
|
156
156
|
// Schema rejects unknown keys so accidental request-body fields fail
|
|
157
157
|
// loudly during route adapter parsing rather than silently propagating.
|
|
158
|
-
|
|
159
|
-
|
|
158
|
+
const requestBody = route!.requestBody;
|
|
159
|
+
if (!requestBody || !("parse" in requestBody)) {
|
|
160
|
+
throw new Error("expected a JSON (Zod) request-body schema");
|
|
161
|
+
}
|
|
162
|
+
expect(() => requestBody.parse({ extra: true })).toThrow();
|
|
163
|
+
expect(() => requestBody.parse({})).not.toThrow();
|
|
160
164
|
});
|
|
161
165
|
});
|
|
@@ -164,8 +164,12 @@ afterEach(() => {
|
|
|
164
164
|
// ---------------------------------------------------------------------------
|
|
165
165
|
|
|
166
166
|
describe("tts-routes", () => {
|
|
167
|
-
test("exports route definitions for messages/:messageId/tts, tts/synthesize, and tts/synthesize-cli", () => {
|
|
168
|
-
expect(ROUTES).toHaveLength(
|
|
167
|
+
test("exports route definitions for tts/providers, messages/:messageId/tts, tts/synthesize, and tts/synthesize-cli", () => {
|
|
168
|
+
expect(ROUTES).toHaveLength(4);
|
|
169
|
+
|
|
170
|
+
const providers = getRoute("tts/providers");
|
|
171
|
+
expect(providers.method).toBe("GET");
|
|
172
|
+
expect(providers.policy?.requiredScopes).toContain("settings.read");
|
|
169
173
|
|
|
170
174
|
const msgTts = getRoute("messages/:messageId/tts");
|
|
171
175
|
expect(msgTts.method).toBe("POST");
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Route handlers for app CRUD, bundling, sharing, versioning,
|
|
3
|
-
*
|
|
3
|
+
* and signing operations.
|
|
4
4
|
*/
|
|
5
5
|
import { randomBytes } from "node:crypto";
|
|
6
6
|
import {
|
|
@@ -12,7 +12,7 @@ import {
|
|
|
12
12
|
} from "node:fs";
|
|
13
13
|
import { stat, unlink } from "node:fs/promises";
|
|
14
14
|
import { homedir, tmpdir } from "node:os";
|
|
15
|
-
import {
|
|
15
|
+
import { join } from "node:path";
|
|
16
16
|
|
|
17
17
|
import { z } from "zod";
|
|
18
18
|
|
|
@@ -22,7 +22,6 @@ import { scanBundle } from "../../bundler/bundle-scanner.js";
|
|
|
22
22
|
import type { SignatureJson } from "../../bundler/bundle-signer.js";
|
|
23
23
|
import { verifyBundleSignature } from "../../bundler/signature-verifier.js";
|
|
24
24
|
import { compareSemver } from "../../daemon/handlers/shared.js";
|
|
25
|
-
import { defaultGallery } from "../../gallery/default-gallery.js";
|
|
26
25
|
import {
|
|
27
26
|
getAppDiff,
|
|
28
27
|
getAppHistory,
|
|
@@ -272,47 +271,6 @@ function forkSharedApp(
|
|
|
272
271
|
return { success: true, appId: newApp.id, name: newApp.name };
|
|
273
272
|
}
|
|
274
273
|
|
|
275
|
-
async function installGalleryApp(
|
|
276
|
-
galleryAppId: string,
|
|
277
|
-
): Promise<
|
|
278
|
-
| { success: true; appId: string; name: string }
|
|
279
|
-
| { success: false; error: string }
|
|
280
|
-
> {
|
|
281
|
-
const galleryApp = defaultGallery.apps.find((a) => a.id === galleryAppId);
|
|
282
|
-
if (!galleryApp) {
|
|
283
|
-
return {
|
|
284
|
-
success: false,
|
|
285
|
-
error: `Gallery app not found: ${galleryAppId}`,
|
|
286
|
-
};
|
|
287
|
-
}
|
|
288
|
-
|
|
289
|
-
const app = createApp({
|
|
290
|
-
name: galleryApp.name,
|
|
291
|
-
description: galleryApp.description,
|
|
292
|
-
schemaJson: galleryApp.schemaJson,
|
|
293
|
-
htmlDefinition: galleryApp.htmlDefinition,
|
|
294
|
-
formatVersion: galleryApp.formatVersion,
|
|
295
|
-
});
|
|
296
|
-
|
|
297
|
-
if (galleryApp.formatVersion === 2 && galleryApp.sourceFiles) {
|
|
298
|
-
const appDir = getAppDirPath(app.id);
|
|
299
|
-
for (const [relPath, content] of Object.entries(galleryApp.sourceFiles)) {
|
|
300
|
-
const fullPath = join(appDir, relPath);
|
|
301
|
-
mkdirSync(dirname(fullPath), { recursive: true });
|
|
302
|
-
writeFileSync(fullPath, content, "utf-8");
|
|
303
|
-
}
|
|
304
|
-
const result = await compileApp(appDir);
|
|
305
|
-
if (!result.ok) {
|
|
306
|
-
log.warn(
|
|
307
|
-
{ appId: app.id, errors: result.errors },
|
|
308
|
-
"Gallery app compilation had errors; falling back to htmlDefinition",
|
|
309
|
-
);
|
|
310
|
-
}
|
|
311
|
-
}
|
|
312
|
-
|
|
313
|
-
return { success: true, appId: app.id, name: app.name };
|
|
314
|
-
}
|
|
315
|
-
|
|
316
274
|
async function openBundle(filePath: string): Promise<Record<string, unknown>> {
|
|
317
275
|
const fileStat = await stat(filePath);
|
|
318
276
|
const bundleSizeBytes = fileStat.size;
|
|
@@ -594,22 +552,6 @@ function handleForkSharedApp({ body, headers }: RouteHandlerArgs) {
|
|
|
594
552
|
return result;
|
|
595
553
|
}
|
|
596
554
|
|
|
597
|
-
async function handleInstallGalleryApp({ body, headers }: RouteHandlerArgs) {
|
|
598
|
-
if (!body?.galleryAppId) {
|
|
599
|
-
throw new BadRequestError("galleryAppId is required");
|
|
600
|
-
}
|
|
601
|
-
const result = await installGalleryApp(body.galleryAppId as string);
|
|
602
|
-
if (!result.success) {
|
|
603
|
-
throw new BadRequestError(result.error);
|
|
604
|
-
}
|
|
605
|
-
publishAppsChanged(getOriginClientId(headers));
|
|
606
|
-
return result;
|
|
607
|
-
}
|
|
608
|
-
|
|
609
|
-
function handleListGallery() {
|
|
610
|
-
return { gallery: defaultGallery };
|
|
611
|
-
}
|
|
612
|
-
|
|
613
555
|
function handleSignBundle({ body }: RouteHandlerArgs) {
|
|
614
556
|
if (!body?.payload) {
|
|
615
557
|
throw new BadRequestError("payload is required");
|
|
@@ -900,62 +842,6 @@ export const ROUTES: RouteDefinition[] = [
|
|
|
900
842
|
name: z.string(),
|
|
901
843
|
}),
|
|
902
844
|
},
|
|
903
|
-
{
|
|
904
|
-
operationId: "apps_gallery_install",
|
|
905
|
-
endpoint: "apps/gallery/install",
|
|
906
|
-
method: "POST",
|
|
907
|
-
policy: {
|
|
908
|
-
requiredScopes: ["settings.write"],
|
|
909
|
-
allowedPrincipalTypes: ACTOR_PRINCIPALS,
|
|
910
|
-
},
|
|
911
|
-
handler: handleInstallGalleryApp,
|
|
912
|
-
summary: "Install a gallery app",
|
|
913
|
-
description: "Install an app from the built-in gallery by its ID.",
|
|
914
|
-
tags: ["apps"],
|
|
915
|
-
requestBody: z.object({ galleryAppId: z.string() }),
|
|
916
|
-
responseBody: z.object({
|
|
917
|
-
success: z.literal(true),
|
|
918
|
-
appId: z.string(),
|
|
919
|
-
name: z.string(),
|
|
920
|
-
}),
|
|
921
|
-
},
|
|
922
|
-
{
|
|
923
|
-
operationId: "apps_gallery_list",
|
|
924
|
-
endpoint: "apps/gallery",
|
|
925
|
-
method: "GET",
|
|
926
|
-
policy: {
|
|
927
|
-
requiredScopes: ["settings.read"],
|
|
928
|
-
allowedPrincipalTypes: ACTOR_PRINCIPALS,
|
|
929
|
-
},
|
|
930
|
-
handler: handleListGallery,
|
|
931
|
-
summary: "List gallery apps",
|
|
932
|
-
description: "Return the built-in app gallery catalog.",
|
|
933
|
-
tags: ["apps"],
|
|
934
|
-
responseBody: z.object({
|
|
935
|
-
gallery: z.object({
|
|
936
|
-
version: z.number(),
|
|
937
|
-
updatedAt: z.string(),
|
|
938
|
-
categories: z.array(
|
|
939
|
-
z.object({
|
|
940
|
-
id: z.string(),
|
|
941
|
-
name: z.string(),
|
|
942
|
-
icon: z.string(),
|
|
943
|
-
}),
|
|
944
|
-
),
|
|
945
|
-
apps: z.array(
|
|
946
|
-
z.object({
|
|
947
|
-
id: z.string(),
|
|
948
|
-
name: z.string(),
|
|
949
|
-
description: z.string(),
|
|
950
|
-
icon: z.string(),
|
|
951
|
-
category: z.string(),
|
|
952
|
-
version: z.string(),
|
|
953
|
-
featured: z.boolean().optional(),
|
|
954
|
-
}),
|
|
955
|
-
),
|
|
956
|
-
}),
|
|
957
|
-
}),
|
|
958
|
-
},
|
|
959
845
|
{
|
|
960
846
|
operationId: "apps_import_bundle",
|
|
961
847
|
endpoint: "apps/import-bundle",
|
|
@@ -969,7 +855,10 @@ export const ROUTES: RouteDefinition[] = [
|
|
|
969
855
|
description:
|
|
970
856
|
"Upload, validate, and install a .vbundle archive as a new local app.",
|
|
971
857
|
tags: ["apps"],
|
|
972
|
-
|
|
858
|
+
requestBody: {
|
|
859
|
+
contentType: "application/octet-stream",
|
|
860
|
+
schema: { type: "string", format: "binary" },
|
|
861
|
+
},
|
|
973
862
|
responseBody: z.object({
|
|
974
863
|
success: z.boolean(),
|
|
975
864
|
appId: z.string(),
|
|
@@ -73,14 +73,15 @@ function buildCsp(scriptSrc: string): string {
|
|
|
73
73
|
].join("; ");
|
|
74
74
|
}
|
|
75
75
|
|
|
76
|
-
function servePageHeaders({
|
|
76
|
+
function servePageHeaders({
|
|
77
|
+
pathParams,
|
|
78
|
+
}: ResponseHeaderArgs): Record<string, string> {
|
|
77
79
|
const appId = pathParams?.appId as string;
|
|
78
80
|
const app = getApp(appId);
|
|
79
81
|
// Multifile apps use external scripts — no 'unsafe-inline' for script-src.
|
|
80
82
|
// Legacy apps contain inline event handlers that require 'unsafe-inline'.
|
|
81
|
-
const scriptSrc =
|
|
82
|
-
? "'self'"
|
|
83
|
-
: "'self' 'unsafe-inline'";
|
|
83
|
+
const scriptSrc =
|
|
84
|
+
app && isMultifileApp(app) ? "'self'" : "'self' 'unsafe-inline'";
|
|
84
85
|
return {
|
|
85
86
|
"Content-Type": "text/html; charset=utf-8",
|
|
86
87
|
"Content-Security-Policy": buildCsp(scriptSrc),
|
|
@@ -186,9 +187,7 @@ const DIST_CONTENT_TYPES: Record<string, string> = {
|
|
|
186
187
|
* Serve a static file from an app's dist/ directory.
|
|
187
188
|
* Validates the filename to prevent path traversal.
|
|
188
189
|
*/
|
|
189
|
-
function handleServeDistFile({
|
|
190
|
-
pathParams,
|
|
191
|
-
}: RouteHandlerArgs): Uint8Array {
|
|
190
|
+
function handleServeDistFile({ pathParams }: RouteHandlerArgs): Uint8Array {
|
|
192
191
|
const appId = pathParams?.appId as string;
|
|
193
192
|
const filename = pathParams?.filename as string;
|
|
194
193
|
|
|
@@ -225,9 +224,7 @@ function handleServeDistFile({
|
|
|
225
224
|
/** 50 MB — generous cap for zip app bundles. */
|
|
226
225
|
const MAX_SHARE_BODY_BYTES = 50 * 1024 * 1024;
|
|
227
226
|
|
|
228
|
-
async function handleShareApp({
|
|
229
|
-
rawBody,
|
|
230
|
-
}: RouteHandlerArgs): Promise<{
|
|
227
|
+
async function handleShareApp({ rawBody }: RouteHandlerArgs): Promise<{
|
|
231
228
|
shareToken: string;
|
|
232
229
|
shareUrl: string;
|
|
233
230
|
bundleSizeBytes: number;
|
|
@@ -275,9 +272,7 @@ async function handleShareApp({
|
|
|
275
272
|
};
|
|
276
273
|
}
|
|
277
274
|
|
|
278
|
-
function handleDownloadSharedApp({
|
|
279
|
-
pathParams,
|
|
280
|
-
}: RouteHandlerArgs): Uint8Array {
|
|
275
|
+
function handleDownloadSharedApp({ pathParams }: RouteHandlerArgs): Uint8Array {
|
|
281
276
|
const shareToken = pathParams?.token as string;
|
|
282
277
|
const record = getSharedAppLink(shareToken);
|
|
283
278
|
if (!record) {
|
|
@@ -348,8 +343,7 @@ export const ROUTES: RouteDefinition[] = [
|
|
|
348
343
|
allowedPrincipalTypes: ACTOR_PRINCIPALS,
|
|
349
344
|
},
|
|
350
345
|
summary: "Serve app dist file",
|
|
351
|
-
description:
|
|
352
|
-
"Serve a static asset from an app's compiled dist/ directory.",
|
|
346
|
+
description: "Serve a static asset from an app's compiled dist/ directory.",
|
|
353
347
|
tags: ["apps"],
|
|
354
348
|
responseHeaders: ({ pathParams }) => ({
|
|
355
349
|
"Content-Type":
|
|
@@ -411,6 +405,10 @@ export const ROUTES: RouteDefinition[] = [
|
|
|
411
405
|
"Content-Type": "application/zip",
|
|
412
406
|
"Content-Disposition": 'attachment; filename="app.vellum"',
|
|
413
407
|
},
|
|
408
|
+
responseBody: {
|
|
409
|
+
contentType: "application/zip",
|
|
410
|
+
schema: { type: "string", format: "binary" },
|
|
411
|
+
},
|
|
414
412
|
handler: handleDownloadSharedApp,
|
|
415
413
|
},
|
|
416
414
|
{
|
|
@@ -663,6 +663,10 @@ export const ROUTES: RouteDefinition[] = [
|
|
|
663
663
|
"Serve raw file bytes for an attachment. Supports Range headers.",
|
|
664
664
|
tags: ["attachments"],
|
|
665
665
|
responseStatus: ({ headers }) => (headers?.["range"] ? "206" : "200"),
|
|
666
|
+
responseBody: {
|
|
667
|
+
contentType: "application/octet-stream",
|
|
668
|
+
schema: { type: "string", format: "binary" },
|
|
669
|
+
},
|
|
666
670
|
additionalResponses: {
|
|
667
671
|
"416": { description: "Range Not Satisfiable" },
|
|
668
672
|
},
|
|
@@ -719,21 +723,28 @@ export const ROUTES: RouteDefinition[] = [
|
|
|
719
723
|
description:
|
|
720
724
|
"Upload an attachment. Supports application/json (base64 data or file path reference), multipart/form-data (file + filename + mimeType fields), and application/octet-stream (raw bytes with filename and mimeType query params).",
|
|
721
725
|
tags: ["attachments"],
|
|
722
|
-
|
|
723
|
-
|
|
724
|
-
|
|
725
|
-
|
|
726
|
-
|
|
727
|
-
|
|
728
|
-
|
|
729
|
-
|
|
730
|
-
|
|
731
|
-
|
|
732
|
-
|
|
733
|
-
|
|
734
|
-
|
|
735
|
-
|
|
736
|
-
|
|
726
|
+
// Advertised as multipart/form-data — the form web clients actually send,
|
|
727
|
+
// so the generated SDK types a real `{ file, filename, mimeType }` body
|
|
728
|
+
// instead of erasing it. The handler additionally accepts application/json
|
|
729
|
+
// (base64/file-path) and application/octet-stream by sniffing the request
|
|
730
|
+
// Content-Type header (see handleUploadAttachmentRoute); those forms are
|
|
731
|
+
// used over raw HTTP by the gateway and are not consumed via the SDK.
|
|
732
|
+
requestBody: {
|
|
733
|
+
contentType: "multipart/form-data",
|
|
734
|
+
schema: {
|
|
735
|
+
type: "object",
|
|
736
|
+
properties: {
|
|
737
|
+
file: {
|
|
738
|
+
type: "string",
|
|
739
|
+
format: "binary",
|
|
740
|
+
description: "The file to upload",
|
|
741
|
+
},
|
|
742
|
+
filename: { type: "string" },
|
|
743
|
+
mimeType: { type: "string" },
|
|
744
|
+
},
|
|
745
|
+
required: ["file", "filename", "mimeType"],
|
|
746
|
+
},
|
|
747
|
+
},
|
|
737
748
|
responseBody: z.object({
|
|
738
749
|
id: z.string(),
|
|
739
750
|
original_filename: z.string(),
|