@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
|
@@ -69,6 +69,15 @@ export const KATA_SAFE_ENV_VARS = [
|
|
|
69
69
|
export const KATA_INJECTED_ENV_VARS = ["LD_LIBRARY_PATH"] as const;
|
|
70
70
|
|
|
71
71
|
const KATA_APT_DATA_ROOT = "/data/system";
|
|
72
|
+
const KATA_FAMILY_SANDBOX_RUNTIMES = new Set([
|
|
73
|
+
"kata",
|
|
74
|
+
"firecracker",
|
|
75
|
+
"cloud-hypervisor",
|
|
76
|
+
]);
|
|
77
|
+
|
|
78
|
+
function isKataFamilyRuntime(runtime: string | undefined): boolean {
|
|
79
|
+
return runtime != null && KATA_FAMILY_SANDBOX_RUNTIMES.has(runtime);
|
|
80
|
+
}
|
|
72
81
|
|
|
73
82
|
function kataAptPaths(dataRoot: string): string[] {
|
|
74
83
|
return [
|
|
@@ -118,7 +127,7 @@ function appendUniquePathEntries(
|
|
|
118
127
|
|
|
119
128
|
export function buildSanitizedEnv(): Record<string, string> {
|
|
120
129
|
const env: Record<string, string> = {};
|
|
121
|
-
const isKataRuntime = process.env.VELLUM_SANDBOX_RUNTIME
|
|
130
|
+
const isKataRuntime = isKataFamilyRuntime(process.env.VELLUM_SANDBOX_RUNTIME);
|
|
122
131
|
const safeEnvVars = isKataRuntime
|
|
123
132
|
? [...SAFE_ENV_VARS, ...KATA_SAFE_ENV_VARS]
|
|
124
133
|
: SAFE_ENV_VARS;
|
|
@@ -105,13 +105,17 @@ export const uiShowTool = {
|
|
|
105
105
|
'Surface structured data or UI in the conversation. For long-form writing use the document skill. For interactive apps, dashboards, games, calculators, or durable tools, call `skill_load` with `skill: "app-builder"` and use the app-builder workflow; do not use `dynamic_page` as a substitute for a persistent app. App-like `dynamic_page` calls are rejected.\n\n' +
|
|
106
106
|
"Surface types (data shapes):\n" +
|
|
107
107
|
'- card: { title, subtitle?, body, metadata?: [{ label, value }], template?, templateData? }. Templates: "weather_forecast" (native weather widget), "task_progress" (live step tracker - update via ui_update on data.templateData; shape: { title, status: "in_progress"|"completed"|"failed", steps: [{ label, status: "pending"|"in_progress"|"completed"|"failed", detail? }] })\n' +
|
|
108
|
+
"- copy_block: { text, label?, language? }. Shows copyable text with a visible copy button; use for prompts, commands, paths, or snippets the user should copy.\n" +
|
|
109
|
+
'- choice: { description?, options: [{ id, title, description?, recommended?, data? }], selectionMode?: "single"|"multiple", commitOnSelect?, submitLabel? }. Single-select choices commit on option click by default. Use for outcome offers and follow-up choices; mark the strongest option with recommended: true.\n' +
|
|
110
|
+
"- oauth_connect: { providerKey, displayName?, description?, logoUrl? }. Shows a managed OAuth connection CTA in chat; use when the current task needs a managed integration account (Google, Linear, GitHub, etc.) instead of asking the user to visit settings or attempting OAuth through shell/tools. The client supplies the CTA label. Do not include OAuth scopes in the surface; managed providers use the platform's configured scopes.\n" +
|
|
108
111
|
'- table: { columns: [{ id, label }], rows: [{ id, cells: Record<id, string | { text, icon?, iconColor?: "success"|"warning"|"error"|"muted" }>, selectable?, selected? }], selectionMode?: "none"|"single"|"multiple", caption? }\n' +
|
|
109
112
|
'- form: { description?, fields: [{ id, type: "text"|"textarea"|"select"|"toggle"|"number"|"password", label, placeholder?, required?, defaultValue?, options?: [{ label, value }] }], submitLabel? }. Multi-page: { pages: [{ id, title, description?, fields }], pageLabels?: { next?, back?, submit? }, submitLabel? }\n' +
|
|
110
113
|
'- list: { items: [{ id, title, subtitle?, icon?, selected? }], selectionMode: "single"|"multiple"|"none" }\n' +
|
|
111
114
|
"- confirmation: { message, detail?, confirmLabel?, confirmedLabel?, cancelLabel?, destructive? }\n" +
|
|
112
115
|
"- dynamic_page: { html, width?, height?, preview?: { title, subtitle?, description?, icon?, metrics?: [{ label, value }] } }\n" +
|
|
113
116
|
"- file_upload: { prompt, acceptedTypes?, maxFiles? }\n" +
|
|
114
|
-
"- task_preferences: {} (no data needed — categories are rendered client-side)\n
|
|
117
|
+
"- task_preferences: {} (no data needed — categories are rendered client-side)\n" +
|
|
118
|
+
'- work_result: { eyebrow?, status?: "completed"|"partial"|"failed"|"in_progress", summary?, metrics?: [{ label, value, detail?, tone?: "neutral"|"positive"|"warning"|"negative" }], sections?: [{ id?, title, description?, type?: "items"|"timeline"|"diff"|"artifacts"|"warnings", items?: [{ id?, title, description?, status?, tone?, metadata?: [{ label, value }], href? }], diffs?: [{ label?, before?, after? }] }] }. Shows a structured receipt after real work: what changed, what was skipped, proof points, and next actions. Keep display-only unless explicit follow-up buttons are needed.\n\n' +
|
|
115
119
|
"Proactively show a task_progress card before multi-step or long-running work (web searches, file operations, research). Show it before your first tool call, then update steps as work progresses.",
|
|
116
120
|
category: "ui-surface",
|
|
117
121
|
defaultRiskLevel: RiskLevel.Low,
|
|
@@ -124,6 +128,9 @@ export const uiShowTool = {
|
|
|
124
128
|
type: "string",
|
|
125
129
|
enum: [
|
|
126
130
|
"card",
|
|
131
|
+
"choice",
|
|
132
|
+
"copy_block",
|
|
133
|
+
"oauth_connect",
|
|
127
134
|
"form",
|
|
128
135
|
"list",
|
|
129
136
|
"table",
|
|
@@ -131,6 +138,7 @@ export const uiShowTool = {
|
|
|
131
138
|
"dynamic_page",
|
|
132
139
|
"file_upload",
|
|
133
140
|
"task_preferences",
|
|
141
|
+
"work_result",
|
|
134
142
|
],
|
|
135
143
|
description: "The type of surface to display",
|
|
136
144
|
},
|
|
@@ -30,8 +30,10 @@ import {
|
|
|
30
30
|
interface ClientCatalogProvider {
|
|
31
31
|
id: string;
|
|
32
32
|
displayName: string;
|
|
33
|
+
subtitle?: string;
|
|
34
|
+
supportsVoiceSelection?: boolean;
|
|
33
35
|
credentialMode?: string;
|
|
34
|
-
credentialsGuide?: { url: string };
|
|
36
|
+
credentialsGuide?: { description: string; url: string; linkLabel: string };
|
|
35
37
|
}
|
|
36
38
|
|
|
37
39
|
interface ClientCatalog {
|
|
@@ -144,6 +146,88 @@ describe("TTS provider catalog / client artifact consistency", () => {
|
|
|
144
146
|
}
|
|
145
147
|
});
|
|
146
148
|
|
|
149
|
+
// -- Display field parity --------------------------------------------------
|
|
150
|
+
|
|
151
|
+
test("subtitle matches between assistant catalog and client artifact", () => {
|
|
152
|
+
const violations: string[] = [];
|
|
153
|
+
for (const clientEntry of clientCatalog.providers) {
|
|
154
|
+
try {
|
|
155
|
+
const assistantEntry = getCatalogProvider(clientEntry.id as any);
|
|
156
|
+
if (clientEntry.subtitle !== assistantEntry.subtitle) {
|
|
157
|
+
violations.push(
|
|
158
|
+
`"${clientEntry.id}": client="${clientEntry.subtitle}" vs assistant="${assistantEntry.subtitle}"`,
|
|
159
|
+
);
|
|
160
|
+
}
|
|
161
|
+
} catch {
|
|
162
|
+
// Unknown ID — covered by provider ID parity tests above.
|
|
163
|
+
}
|
|
164
|
+
}
|
|
165
|
+
if (violations.length > 0) {
|
|
166
|
+
expect(violations, "Subtitle mismatch:\n" + violations.join("\n")).toEqual(
|
|
167
|
+
[],
|
|
168
|
+
);
|
|
169
|
+
}
|
|
170
|
+
});
|
|
171
|
+
|
|
172
|
+
test("supportsVoiceSelection matches between assistant catalog and client artifact", () => {
|
|
173
|
+
const violations: string[] = [];
|
|
174
|
+
for (const clientEntry of clientCatalog.providers) {
|
|
175
|
+
try {
|
|
176
|
+
const assistantEntry = getCatalogProvider(clientEntry.id as any);
|
|
177
|
+
if (
|
|
178
|
+
clientEntry.supportsVoiceSelection !==
|
|
179
|
+
assistantEntry.supportsVoiceSelection
|
|
180
|
+
) {
|
|
181
|
+
violations.push(
|
|
182
|
+
`"${clientEntry.id}": client=${clientEntry.supportsVoiceSelection} vs assistant=${assistantEntry.supportsVoiceSelection}`,
|
|
183
|
+
);
|
|
184
|
+
}
|
|
185
|
+
} catch {
|
|
186
|
+
// Unknown ID — covered by provider ID parity tests above.
|
|
187
|
+
}
|
|
188
|
+
}
|
|
189
|
+
if (violations.length > 0) {
|
|
190
|
+
expect(
|
|
191
|
+
violations,
|
|
192
|
+
"supportsVoiceSelection mismatch:\n" + violations.join("\n"),
|
|
193
|
+
).toEqual([]);
|
|
194
|
+
}
|
|
195
|
+
});
|
|
196
|
+
|
|
197
|
+
test("credentialsGuide matches between assistant catalog and client artifact", () => {
|
|
198
|
+
const violations: string[] = [];
|
|
199
|
+
for (const clientEntry of clientCatalog.providers) {
|
|
200
|
+
try {
|
|
201
|
+
const assistantEntry = getCatalogProvider(clientEntry.id as any);
|
|
202
|
+
const cg = clientEntry.credentialsGuide;
|
|
203
|
+
const ag = assistantEntry.credentialsGuide;
|
|
204
|
+
if (cg && ag) {
|
|
205
|
+
if (cg.url !== ag.url) {
|
|
206
|
+
violations.push(`"${clientEntry.id}": credentialsGuide.url mismatch`);
|
|
207
|
+
}
|
|
208
|
+
if (cg.description !== ag.description) {
|
|
209
|
+
violations.push(
|
|
210
|
+
`"${clientEntry.id}": credentialsGuide.description mismatch`,
|
|
211
|
+
);
|
|
212
|
+
}
|
|
213
|
+
if (cg.linkLabel !== ag.linkLabel) {
|
|
214
|
+
violations.push(
|
|
215
|
+
`"${clientEntry.id}": credentialsGuide.linkLabel mismatch`,
|
|
216
|
+
);
|
|
217
|
+
}
|
|
218
|
+
}
|
|
219
|
+
} catch {
|
|
220
|
+
// Unknown ID — covered by provider ID parity tests above.
|
|
221
|
+
}
|
|
222
|
+
}
|
|
223
|
+
if (violations.length > 0) {
|
|
224
|
+
expect(
|
|
225
|
+
violations,
|
|
226
|
+
"credentialsGuide mismatch:\n" + violations.join("\n"),
|
|
227
|
+
).toEqual([]);
|
|
228
|
+
}
|
|
229
|
+
});
|
|
230
|
+
|
|
147
231
|
// -- Structural sanity ----------------------------------------------------
|
|
148
232
|
|
|
149
233
|
test("client artifact version is a positive integer", () => {
|
|
@@ -55,12 +55,21 @@ interface TtsProviderCatalogCapabilities {
|
|
|
55
55
|
readonly supportedFormats: readonly string[];
|
|
56
56
|
}
|
|
57
57
|
|
|
58
|
+
/**
|
|
59
|
+
* Link to a provider's API-key management page, shown in settings UI.
|
|
60
|
+
*/
|
|
61
|
+
interface TtsCredentialsGuide {
|
|
62
|
+
readonly description: string;
|
|
63
|
+
readonly url: string;
|
|
64
|
+
readonly linkLabel: string;
|
|
65
|
+
}
|
|
66
|
+
|
|
58
67
|
/**
|
|
59
68
|
* A single entry in the TTS provider catalog.
|
|
60
69
|
*
|
|
61
70
|
* Captures everything the system needs to know about a provider at a
|
|
62
71
|
* metadata level — identity, display name, telephony call mode,
|
|
63
|
-
* capabilities, and
|
|
72
|
+
* capabilities, secret requirements, and client-facing display metadata.
|
|
64
73
|
*/
|
|
65
74
|
interface TtsProviderCatalogEntry {
|
|
66
75
|
/** Unique provider identifier matching {@link TtsProviderId}. */
|
|
@@ -69,6 +78,18 @@ interface TtsProviderCatalogEntry {
|
|
|
69
78
|
/** Human-readable name for display in settings UI and logs. */
|
|
70
79
|
readonly displayName: string;
|
|
71
80
|
|
|
81
|
+
/** Short description shown beneath the provider name in settings UI. */
|
|
82
|
+
readonly subtitle: string;
|
|
83
|
+
|
|
84
|
+
/** Whether the provider supports user-chosen voice IDs. */
|
|
85
|
+
readonly supportsVoiceSelection: boolean;
|
|
86
|
+
|
|
87
|
+
/** Placeholder text for the API-key input in settings UI. */
|
|
88
|
+
readonly apiKeyPlaceholder: string;
|
|
89
|
+
|
|
90
|
+
/** Link to the provider's API-key management page. */
|
|
91
|
+
readonly credentialsGuide: TtsCredentialsGuide;
|
|
92
|
+
|
|
72
93
|
/** How this provider integrates with the telephony call path. */
|
|
73
94
|
readonly callMode: TtsCallMode;
|
|
74
95
|
|
|
@@ -106,6 +127,16 @@ const CATALOG: readonly TtsProviderCatalogEntry[] = [
|
|
|
106
127
|
{
|
|
107
128
|
id: "elevenlabs",
|
|
108
129
|
displayName: "ElevenLabs",
|
|
130
|
+
subtitle:
|
|
131
|
+
"High-quality voice synthesis for conversations and read-aloud. Requires an ElevenLabs API key.",
|
|
132
|
+
supportsVoiceSelection: true,
|
|
133
|
+
apiKeyPlaceholder: "sk_…",
|
|
134
|
+
credentialsGuide: {
|
|
135
|
+
description:
|
|
136
|
+
"Sign in to ElevenLabs, go to your Profile, and copy your API key.",
|
|
137
|
+
url: "https://elevenlabs.io/app/settings/api-keys",
|
|
138
|
+
linkLabel: "Open ElevenLabs API Keys",
|
|
139
|
+
},
|
|
109
140
|
callMode: "native-twilio",
|
|
110
141
|
allowNativeFallback: true,
|
|
111
142
|
capabilities: {
|
|
@@ -124,6 +155,16 @@ const CATALOG: readonly TtsProviderCatalogEntry[] = [
|
|
|
124
155
|
{
|
|
125
156
|
id: "fish-audio",
|
|
126
157
|
displayName: "Fish Audio",
|
|
158
|
+
subtitle:
|
|
159
|
+
"Natural-sounding voice synthesis with custom voice cloning. Requires a Fish Audio API key and voice reference ID.",
|
|
160
|
+
supportsVoiceSelection: true,
|
|
161
|
+
apiKeyPlaceholder: "Enter your Fish Audio API key",
|
|
162
|
+
credentialsGuide: {
|
|
163
|
+
description:
|
|
164
|
+
"Sign in to Fish Audio, navigate to API Keys in your dashboard, and create a new key.",
|
|
165
|
+
url: "https://fish.audio/app/api-keys/",
|
|
166
|
+
linkLabel: "Open Fish Audio API Keys",
|
|
167
|
+
},
|
|
127
168
|
callMode: "synthesized-play",
|
|
128
169
|
allowNativeFallback: true,
|
|
129
170
|
capabilities: {
|
|
@@ -142,6 +183,16 @@ const CATALOG: readonly TtsProviderCatalogEntry[] = [
|
|
|
142
183
|
{
|
|
143
184
|
id: "deepgram",
|
|
144
185
|
displayName: "Deepgram",
|
|
186
|
+
subtitle:
|
|
187
|
+
"Fast, accurate text-to-speech synthesis. Uses the same API key as Deepgram speech-to-text.",
|
|
188
|
+
supportsVoiceSelection: false,
|
|
189
|
+
apiKeyPlaceholder: "Enter your Deepgram API key",
|
|
190
|
+
credentialsGuide: {
|
|
191
|
+
description:
|
|
192
|
+
"Sign in to Deepgram, navigate to your API Keys page, and create or copy an existing key. This is the same key used for speech-to-text.",
|
|
193
|
+
url: "https://console.deepgram.com/",
|
|
194
|
+
linkLabel: "Open Deepgram Console",
|
|
195
|
+
},
|
|
145
196
|
callMode: "synthesized-play",
|
|
146
197
|
allowNativeFallback: false,
|
|
147
198
|
capabilities: {
|
|
@@ -159,6 +210,16 @@ const CATALOG: readonly TtsProviderCatalogEntry[] = [
|
|
|
159
210
|
{
|
|
160
211
|
id: "xai",
|
|
161
212
|
displayName: "xAI",
|
|
213
|
+
subtitle:
|
|
214
|
+
"Text-to-speech from xAI with expressive voices (eve, ara, rex, sal, leo). Requires an xAI API key.",
|
|
215
|
+
supportsVoiceSelection: false,
|
|
216
|
+
apiKeyPlaceholder: "Enter your xAI API key",
|
|
217
|
+
credentialsGuide: {
|
|
218
|
+
description:
|
|
219
|
+
"Sign in to the xAI console, navigate to API Keys, and create a new key.",
|
|
220
|
+
url: "https://console.x.ai/",
|
|
221
|
+
linkLabel: "Open xAI Console",
|
|
222
|
+
},
|
|
162
223
|
callMode: "synthesized-play",
|
|
163
224
|
allowNativeFallback: false,
|
|
164
225
|
capabilities: {
|
|
@@ -199,6 +260,20 @@ export function listCatalogProviderIds(): TtsProviderId[] {
|
|
|
199
260
|
return CATALOG.map((entry) => entry.id);
|
|
200
261
|
}
|
|
201
262
|
|
|
263
|
+
/**
|
|
264
|
+
* List all catalog providers projected to client-facing display fields only.
|
|
265
|
+
*/
|
|
266
|
+
export function listCatalogProvidersForDisplay() {
|
|
267
|
+
return CATALOG.map((e) => ({
|
|
268
|
+
id: e.id,
|
|
269
|
+
displayName: e.displayName,
|
|
270
|
+
subtitle: e.subtitle,
|
|
271
|
+
supportsVoiceSelection: e.supportsVoiceSelection,
|
|
272
|
+
apiKeyPlaceholder: e.apiKeyPlaceholder,
|
|
273
|
+
credentialsGuide: e.credentialsGuide,
|
|
274
|
+
}));
|
|
275
|
+
}
|
|
276
|
+
|
|
202
277
|
/**
|
|
203
278
|
* Look up a catalog entry by provider ID.
|
|
204
279
|
*
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Async mutex for serializing access to a shared resource.
|
|
3
|
+
*
|
|
4
|
+
* Callers wait in FIFO order. `withLock(fn)` is the primary API —
|
|
5
|
+
* it acquires the lock, runs `fn`, and releases the lock when `fn`
|
|
6
|
+
* settles (even on throw).
|
|
7
|
+
*
|
|
8
|
+
* Used by git-service (per-workspace repo operations) and
|
|
9
|
+
* conversation-title-service (serial LLM calls) to prevent
|
|
10
|
+
* concurrent access to resources that cannot safely overlap.
|
|
11
|
+
*/
|
|
12
|
+
export class Mutex {
|
|
13
|
+
private locked = false;
|
|
14
|
+
private waitQueue: Array<() => void> = [];
|
|
15
|
+
|
|
16
|
+
async acquire(): Promise<void> {
|
|
17
|
+
if (!this.locked) {
|
|
18
|
+
this.locked = true;
|
|
19
|
+
return;
|
|
20
|
+
}
|
|
21
|
+
await new Promise<void>((resolve) => {
|
|
22
|
+
this.waitQueue.push(resolve);
|
|
23
|
+
});
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
release(): void {
|
|
27
|
+
const next = this.waitQueue.shift();
|
|
28
|
+
if (next) {
|
|
29
|
+
next();
|
|
30
|
+
} else {
|
|
31
|
+
this.locked = false;
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
/**
|
|
36
|
+
* Execute `fn` while holding the lock.
|
|
37
|
+
* Automatically releases the lock when done, even if `fn` throws.
|
|
38
|
+
*/
|
|
39
|
+
async withLock<T>(fn: () => Promise<T>): Promise<T> {
|
|
40
|
+
await this.acquire();
|
|
41
|
+
try {
|
|
42
|
+
return await fn();
|
|
43
|
+
} finally {
|
|
44
|
+
this.release();
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
}
|
|
@@ -12,6 +12,7 @@ import { promisify } from "node:util";
|
|
|
12
12
|
|
|
13
13
|
import { getConfig } from "../config/loader.js";
|
|
14
14
|
import { getLogger } from "../util/logger.js";
|
|
15
|
+
import { Mutex } from "../util/mutex.js";
|
|
15
16
|
import { PromiseGuard } from "../util/promise-guard.js";
|
|
16
17
|
|
|
17
18
|
const execFileAsync = promisify(execFile);
|
|
@@ -119,48 +120,6 @@ interface ExecError extends Error {
|
|
|
119
120
|
code?: string | number;
|
|
120
121
|
}
|
|
121
122
|
|
|
122
|
-
/**
|
|
123
|
-
* Simple mutex implementation for per-workspace git operation serialization.
|
|
124
|
-
* Prevents concurrent git operations from corrupting the repository state.
|
|
125
|
-
*/
|
|
126
|
-
class Mutex {
|
|
127
|
-
private locked = false;
|
|
128
|
-
private waitQueue: Array<() => void> = [];
|
|
129
|
-
|
|
130
|
-
async acquire(): Promise<void> {
|
|
131
|
-
if (!this.locked) {
|
|
132
|
-
this.locked = true;
|
|
133
|
-
return;
|
|
134
|
-
}
|
|
135
|
-
// Wait for the lock to be released
|
|
136
|
-
await new Promise<void>((resolve) => {
|
|
137
|
-
this.waitQueue.push(resolve);
|
|
138
|
-
});
|
|
139
|
-
}
|
|
140
|
-
|
|
141
|
-
release(): void {
|
|
142
|
-
const next = this.waitQueue.shift();
|
|
143
|
-
if (next) {
|
|
144
|
-
next();
|
|
145
|
-
} else {
|
|
146
|
-
this.locked = false;
|
|
147
|
-
}
|
|
148
|
-
}
|
|
149
|
-
|
|
150
|
-
/**
|
|
151
|
-
* Execute a function while holding the lock.
|
|
152
|
-
* Automatically releases the lock when done, even if the function throws.
|
|
153
|
-
*/
|
|
154
|
-
async withLock<T>(fn: () => Promise<T>): Promise<T> {
|
|
155
|
-
await this.acquire();
|
|
156
|
-
try {
|
|
157
|
-
return await fn();
|
|
158
|
-
} finally {
|
|
159
|
-
this.release();
|
|
160
|
-
}
|
|
161
|
-
}
|
|
162
|
-
}
|
|
163
|
-
|
|
164
123
|
interface GitCommitMetadata {
|
|
165
124
|
/** Optional metadata to include in the commit message or as git notes */
|
|
166
125
|
[key: string]: unknown;
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
import { existsSync, readFileSync, writeFileSync } from "node:fs";
|
|
2
|
+
import { join } from "node:path";
|
|
3
|
+
|
|
4
|
+
import type { WorkspaceMigration } from "./types.js";
|
|
5
|
+
|
|
6
|
+
const THIRTY_MINUTES_MS = 30 * 60 * 1000;
|
|
7
|
+
const SIXTY_MINUTES_MS = 60 * 60 * 1000;
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* Bump persisted heartbeat default from 30 minutes to 60 minutes.
|
|
11
|
+
*
|
|
12
|
+
* Migration 065 moved legacy 3h/6h defaults to 30 minutes. The schema default
|
|
13
|
+
* has since been raised to 60 minutes, but existing users whose config.json
|
|
14
|
+
* already has 1800000 persisted won't pick up the new default. This migration
|
|
15
|
+
* idempotently updates those configs.
|
|
16
|
+
*/
|
|
17
|
+
export const bumpHeartbeatInterval30mTo60mMigration: WorkspaceMigration = {
|
|
18
|
+
id: "095-bump-heartbeat-interval-30m-to-60m",
|
|
19
|
+
description:
|
|
20
|
+
"Bump persisted heartbeat.intervalMs from 30 minutes to 60 minutes",
|
|
21
|
+
run(workspaceDir: string): void {
|
|
22
|
+
const configPath = join(workspaceDir, "config.json");
|
|
23
|
+
if (!existsSync(configPath)) return;
|
|
24
|
+
|
|
25
|
+
let config: Record<string, unknown>;
|
|
26
|
+
try {
|
|
27
|
+
const raw = JSON.parse(readFileSync(configPath, "utf-8"));
|
|
28
|
+
if (!raw || typeof raw !== "object" || Array.isArray(raw)) return;
|
|
29
|
+
config = raw as Record<string, unknown>;
|
|
30
|
+
} catch {
|
|
31
|
+
return;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
const heartbeat = config.heartbeat;
|
|
35
|
+
if (!heartbeat || typeof heartbeat !== "object" || Array.isArray(heartbeat))
|
|
36
|
+
return;
|
|
37
|
+
|
|
38
|
+
const heartbeatConfig = heartbeat as Record<string, unknown>;
|
|
39
|
+
const intervalMs = heartbeatConfig.intervalMs;
|
|
40
|
+
if (typeof intervalMs !== "number" || intervalMs !== THIRTY_MINUTES_MS) {
|
|
41
|
+
return;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
heartbeatConfig.intervalMs = SIXTY_MINUTES_MS;
|
|
45
|
+
writeFileSync(configPath, JSON.stringify(config, null, 2) + "\n");
|
|
46
|
+
},
|
|
47
|
+
down(_workspaceDir: string): void {
|
|
48
|
+
// Forward-only: cannot distinguish users who explicitly chose 60 minutes
|
|
49
|
+
// from those migrated by this migration.
|
|
50
|
+
},
|
|
51
|
+
};
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
import { existsSync, readFileSync, writeFileSync } from "node:fs";
|
|
2
|
+
import { join } from "node:path";
|
|
3
|
+
|
|
4
|
+
import type { WorkspaceMigration } from "./types.js";
|
|
5
|
+
|
|
6
|
+
// Reduce effort from "max" to "high" on the quality-optimized profile.
|
|
7
|
+
//
|
|
8
|
+
// With adaptive thinking enabled, the effort parameter acts as a nudge for
|
|
9
|
+
// how much thinking the model does. "max" means "always think with no
|
|
10
|
+
// constraints on thinking depth", which defeats adaptive thinking's ability
|
|
11
|
+
// to skip or minimize thinking for simple queries. "high" (the API default)
|
|
12
|
+
// means "almost always thinks, deep reasoning on complex tasks" — letting
|
|
13
|
+
// adaptive thinking decide when full-depth reasoning is actually needed.
|
|
14
|
+
//
|
|
15
|
+
// This migration patches both managed and user quality-optimized profiles
|
|
16
|
+
// that still have effort: "max". It only downgrades "max" → "high"; any
|
|
17
|
+
// other effort value is preserved.
|
|
18
|
+
|
|
19
|
+
const TARGET_PROFILES = ["quality-optimized", "custom-quality-optimized"];
|
|
20
|
+
|
|
21
|
+
export const reduceQualityProfileEffortMigration: WorkspaceMigration = {
|
|
22
|
+
id: "096-reduce-quality-profile-effort",
|
|
23
|
+
description:
|
|
24
|
+
'Reduce effort from "max" to "high" on quality-optimized profiles to let adaptive thinking work',
|
|
25
|
+
run(workspaceDir: string): void {
|
|
26
|
+
const configPath = join(workspaceDir, "config.json");
|
|
27
|
+
if (!existsSync(configPath)) return;
|
|
28
|
+
|
|
29
|
+
let config: Record<string, unknown>;
|
|
30
|
+
try {
|
|
31
|
+
const raw = JSON.parse(readFileSync(configPath, "utf-8"));
|
|
32
|
+
if (!raw || typeof raw !== "object" || Array.isArray(raw)) return;
|
|
33
|
+
config = raw as Record<string, unknown>;
|
|
34
|
+
} catch {
|
|
35
|
+
return;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
const llm = readObject(config.llm);
|
|
39
|
+
if (llm === null) return;
|
|
40
|
+
|
|
41
|
+
const profiles = readObject(llm.profiles);
|
|
42
|
+
if (profiles === null) return;
|
|
43
|
+
|
|
44
|
+
let changed = false;
|
|
45
|
+
|
|
46
|
+
for (const name of TARGET_PROFILES) {
|
|
47
|
+
const profile = readObject(profiles[name]);
|
|
48
|
+
if (profile === null) continue;
|
|
49
|
+
if (profile.effort !== "max") continue;
|
|
50
|
+
|
|
51
|
+
profile.effort = "high";
|
|
52
|
+
profiles[name] = profile;
|
|
53
|
+
changed = true;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
if (changed) {
|
|
57
|
+
llm.profiles = profiles;
|
|
58
|
+
config.llm = llm;
|
|
59
|
+
writeFileSync(configPath, JSON.stringify(config, null, 2) + "\n");
|
|
60
|
+
}
|
|
61
|
+
},
|
|
62
|
+
down(_workspaceDir: string): void {
|
|
63
|
+
// Forward-only.
|
|
64
|
+
},
|
|
65
|
+
};
|
|
66
|
+
|
|
67
|
+
function readObject(value: unknown): Record<string, unknown> | null {
|
|
68
|
+
if (value === null || typeof value !== "object" || Array.isArray(value)) {
|
|
69
|
+
return null;
|
|
70
|
+
}
|
|
71
|
+
return value as Record<string, unknown>;
|
|
72
|
+
}
|
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
import { existsSync, readFileSync, writeFileSync } from "node:fs";
|
|
2
|
+
import { join } from "node:path";
|
|
3
|
+
|
|
4
|
+
import type { WorkspaceMigration } from "./types.js";
|
|
5
|
+
|
|
6
|
+
// Enable adaptive thinking on the managed "balanced" and "quality-optimized"
|
|
7
|
+
// profiles for existing platform instances.
|
|
8
|
+
//
|
|
9
|
+
// The assistant-side seed defaults (MANAGED_PROFILE_TEMPLATES in
|
|
10
|
+
// seed-inference-profiles.ts) already ship thinking: { enabled: true,
|
|
11
|
+
// streamThinking: true } for both profiles, which normalizes to
|
|
12
|
+
// { type: "adaptive" } on the wire. Off-platform (BYOK) instances pick this
|
|
13
|
+
// up on every boot because the seeder overwrites managed profiles. On-platform
|
|
14
|
+
// instances preserve existing profiles (the platform overlay is authoritative),
|
|
15
|
+
// so instances that were hatched before thinking was enabled in the templates
|
|
16
|
+
// are stuck with thinking disabled or absent.
|
|
17
|
+
//
|
|
18
|
+
// This migration patches the on-disk config for both profiles, adding
|
|
19
|
+
// thinking: { enabled: true, streamThinking: true } where it's missing or
|
|
20
|
+
// explicitly disabled. It skips profiles that:
|
|
21
|
+
// - Don't exist (no profile to patch)
|
|
22
|
+
// - Already have thinking enabled (idempotent)
|
|
23
|
+
// - Are not source: "managed" (user-created profiles are untouched)
|
|
24
|
+
// - Use a non-Anthropic provider (adaptive thinking is Anthropic-specific)
|
|
25
|
+
|
|
26
|
+
const ADAPTIVE_THINKING = { enabled: true, streamThinking: true } as const;
|
|
27
|
+
const TARGET_PROFILES = ["balanced", "quality-optimized"] as const;
|
|
28
|
+
|
|
29
|
+
export const enableAdaptiveThinkingManagedProfilesMigration: WorkspaceMigration =
|
|
30
|
+
{
|
|
31
|
+
id: "097-enable-adaptive-thinking-managed-profiles",
|
|
32
|
+
description:
|
|
33
|
+
"Enable adaptive thinking on managed balanced and quality-optimized profiles",
|
|
34
|
+
run(workspaceDir: string): void {
|
|
35
|
+
const configPath = join(workspaceDir, "config.json");
|
|
36
|
+
if (!existsSync(configPath)) return;
|
|
37
|
+
|
|
38
|
+
let config: Record<string, unknown>;
|
|
39
|
+
try {
|
|
40
|
+
const raw = JSON.parse(readFileSync(configPath, "utf-8"));
|
|
41
|
+
if (!raw || typeof raw !== "object" || Array.isArray(raw)) return;
|
|
42
|
+
config = raw as Record<string, unknown>;
|
|
43
|
+
} catch {
|
|
44
|
+
return;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
const llm = readObject(config.llm);
|
|
48
|
+
if (llm === null) return;
|
|
49
|
+
|
|
50
|
+
const profiles = readObject(llm.profiles);
|
|
51
|
+
if (profiles === null) return;
|
|
52
|
+
|
|
53
|
+
let changed = false;
|
|
54
|
+
|
|
55
|
+
for (const name of TARGET_PROFILES) {
|
|
56
|
+
const profile = readObject(profiles[name]);
|
|
57
|
+
if (profile === null) continue;
|
|
58
|
+
|
|
59
|
+
// Only patch managed Anthropic profiles.
|
|
60
|
+
if (profile.source !== "managed") continue;
|
|
61
|
+
if (
|
|
62
|
+
typeof profile.provider === "string" &&
|
|
63
|
+
profile.provider !== "anthropic"
|
|
64
|
+
) {
|
|
65
|
+
continue;
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
// Skip if thinking is already enabled.
|
|
69
|
+
const thinking = readObject(profile.thinking);
|
|
70
|
+
if (thinking !== null && thinking.enabled === true) continue;
|
|
71
|
+
|
|
72
|
+
profile.thinking = { ...ADAPTIVE_THINKING };
|
|
73
|
+
profiles[name] = profile;
|
|
74
|
+
changed = true;
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
if (changed) {
|
|
78
|
+
llm.profiles = profiles;
|
|
79
|
+
config.llm = llm;
|
|
80
|
+
writeFileSync(configPath, JSON.stringify(config, null, 2) + "\n");
|
|
81
|
+
}
|
|
82
|
+
},
|
|
83
|
+
down(_workspaceDir: string): void {
|
|
84
|
+
// Forward-only.
|
|
85
|
+
},
|
|
86
|
+
};
|
|
87
|
+
|
|
88
|
+
function readObject(value: unknown): Record<string, unknown> | null {
|
|
89
|
+
if (value === null || typeof value !== "object" || Array.isArray(value)) {
|
|
90
|
+
return null;
|
|
91
|
+
}
|
|
92
|
+
return value as Record<string, unknown>;
|
|
93
|
+
}
|
|
@@ -92,6 +92,9 @@ import { retightenMigrationOnboardingThreadMigration } from "./091-retighten-mig
|
|
|
92
92
|
import { backfillV3LeavesMigration } from "./092-backfill-v3-leaves.js";
|
|
93
93
|
import { backfillLeafIdsMigration } from "./093-backfill-leaf-ids.js";
|
|
94
94
|
import { seedAvatarManifestMigration } from "./094-seed-avatar-manifest.js";
|
|
95
|
+
import { bumpHeartbeatInterval30mTo60mMigration } from "./095-bump-heartbeat-interval-30m-to-60m.js";
|
|
96
|
+
import { reduceQualityProfileEffortMigration } from "./096-reduce-quality-profile-effort.js";
|
|
97
|
+
import { enableAdaptiveThinkingManagedProfilesMigration } from "./097-enable-adaptive-thinking-managed-profiles.js";
|
|
95
98
|
import { migrateToWorkspaceVolumeMigration } from "./migrate-to-workspace-volume.js";
|
|
96
99
|
import type { WorkspaceMigration } from "./types.js";
|
|
97
100
|
|
|
@@ -195,4 +198,7 @@ export const WORKSPACE_MIGRATIONS: WorkspaceMigration[] = [
|
|
|
195
198
|
backfillV3LeavesMigration,
|
|
196
199
|
backfillLeafIdsMigration,
|
|
197
200
|
seedAvatarManifestMigration,
|
|
201
|
+
bumpHeartbeatInterval30mTo60mMigration,
|
|
202
|
+
reduceQualityProfileEffortMigration,
|
|
203
|
+
enableAdaptiveThinkingManagedProfilesMigration,
|
|
198
204
|
];
|
|
@@ -1,44 +0,0 @@
|
|
|
1
|
-
import { describe, expect, test } from "bun:test";
|
|
2
|
-
|
|
3
|
-
import {
|
|
4
|
-
BOOTSTRAP_CLEANUP_USER_TURN_THRESHOLD,
|
|
5
|
-
countBootstrapUserTurns,
|
|
6
|
-
shouldCleanupBootstrapAfterTurn,
|
|
7
|
-
} from "../daemon/bootstrap-turn-cleanup.js";
|
|
8
|
-
|
|
9
|
-
function message(role: string, content: string) {
|
|
10
|
-
return { role, content };
|
|
11
|
-
}
|
|
12
|
-
|
|
13
|
-
describe("bootstrap turn cleanup", () => {
|
|
14
|
-
test("does not count the hidden wake-up greeting as a user turn", () => {
|
|
15
|
-
const messages = [
|
|
16
|
-
message(
|
|
17
|
-
"user",
|
|
18
|
-
JSON.stringify([{ type: "text", text: "Wake up, my friend." }]),
|
|
19
|
-
),
|
|
20
|
-
message("assistant", "hello"),
|
|
21
|
-
message("user", "real request"),
|
|
22
|
-
];
|
|
23
|
-
|
|
24
|
-
expect(countBootstrapUserTurns(messages)).toBe(1);
|
|
25
|
-
});
|
|
26
|
-
|
|
27
|
-
test("cleans up after the configured user-turn threshold", () => {
|
|
28
|
-
const messages = Array.from(
|
|
29
|
-
{ length: BOOTSTRAP_CLEANUP_USER_TURN_THRESHOLD },
|
|
30
|
-
(_value, index) => message("user", `request ${index + 1}`),
|
|
31
|
-
);
|
|
32
|
-
|
|
33
|
-
expect(shouldCleanupBootstrapAfterTurn(messages)).toBe(true);
|
|
34
|
-
});
|
|
35
|
-
|
|
36
|
-
test("keeps bootstrap before the configured user-turn threshold", () => {
|
|
37
|
-
const messages = Array.from(
|
|
38
|
-
{ length: BOOTSTRAP_CLEANUP_USER_TURN_THRESHOLD - 1 },
|
|
39
|
-
(_value, index) => message("user", `request ${index + 1}`),
|
|
40
|
-
);
|
|
41
|
-
|
|
42
|
-
expect(shouldCleanupBootstrapAfterTurn(messages)).toBe(false);
|
|
43
|
-
});
|
|
44
|
-
});
|