@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
|
@@ -41,6 +41,7 @@ const IMAGE_FILENAME = "avatar-image.png";
|
|
|
41
41
|
const TRAITS_FILENAME = "character-traits.json";
|
|
42
42
|
const ASCII_FILENAME = "character-ascii.txt";
|
|
43
43
|
const MANIFEST_FILENAME = "avatar.json";
|
|
44
|
+
const NATIVE_RENDER_TEST_TIMEOUT_MS = 15_000;
|
|
44
45
|
|
|
45
46
|
interface ManifestShape {
|
|
46
47
|
kind: string;
|
|
@@ -84,35 +85,39 @@ describe("avatar-store", () => {
|
|
|
84
85
|
};
|
|
85
86
|
|
|
86
87
|
describe("setCharacter", () => {
|
|
87
|
-
test(
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
88
|
+
test(
|
|
89
|
+
"writes traits + PNG and a character manifest when render succeeds",
|
|
90
|
+
() => {
|
|
91
|
+
const result = setCharacter(VALID_TRAITS);
|
|
92
|
+
|
|
93
|
+
// The native @resvg/resvg-js binding may be absent in this environment.
|
|
94
|
+
// When it is, the store returns `native_unavailable` and writes nothing —
|
|
95
|
+
// we assert that contract instead of the success path so the suite is
|
|
96
|
+
// deterministic either way.
|
|
97
|
+
if (!result.ok) {
|
|
98
|
+
expect(result.reason).toBe("native_unavailable");
|
|
99
|
+
expect(existsSync(path(TRAITS_FILENAME))).toBe(false);
|
|
100
|
+
expect(existsSync(path(IMAGE_FILENAME))).toBe(false);
|
|
101
|
+
expect(existsSync(path(MANIFEST_FILENAME))).toBe(false);
|
|
102
|
+
return;
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
expect(existsSync(path(TRAITS_FILENAME))).toBe(true);
|
|
106
|
+
expect(existsSync(path(IMAGE_FILENAME))).toBe(true);
|
|
107
|
+
expect(
|
|
108
|
+
JSON.parse(readFileSync(path(TRAITS_FILENAME), "utf-8")),
|
|
109
|
+
).toEqual(VALID_TRAITS);
|
|
110
|
+
|
|
111
|
+
const manifest = readManifestFile();
|
|
112
|
+
expect(manifest).not.toBeNull();
|
|
113
|
+
expect(manifest!.kind).toBe("character");
|
|
114
|
+
expect(manifest!.traits).toEqual(VALID_TRAITS);
|
|
115
|
+
expect(manifest!.source).toBe("builder");
|
|
116
|
+
expect(manifest!.image).not.toBeNull();
|
|
117
|
+
expect(manifest!.image!.etag).toMatch(/^[0-9a-f]{16}$/);
|
|
118
|
+
},
|
|
119
|
+
NATIVE_RENDER_TEST_TIMEOUT_MS,
|
|
120
|
+
);
|
|
116
121
|
|
|
117
122
|
test("propagates invalid_traits without writing a manifest", () => {
|
|
118
123
|
const result = setCharacter({ bodyShape: "", eyeStyle: "", color: "" });
|
|
@@ -66,6 +66,12 @@ interface CommandResult {
|
|
|
66
66
|
exitCode: number;
|
|
67
67
|
}
|
|
68
68
|
|
|
69
|
+
interface RawCommandResult {
|
|
70
|
+
stdout: string;
|
|
71
|
+
stderr: string;
|
|
72
|
+
exitCode: number;
|
|
73
|
+
}
|
|
74
|
+
|
|
69
75
|
/**
|
|
70
76
|
* Run a notifications subcommand and capture the JSON output.
|
|
71
77
|
* Always passes --json to get compact, single-line JSON output and suppress log messages.
|
|
@@ -74,6 +80,17 @@ interface CommandResult {
|
|
|
74
80
|
* reset to 0, capture, then reset back to 0 so bun test exits cleanly.
|
|
75
81
|
*/
|
|
76
82
|
async function runCommand(args: string[]): Promise<CommandResult> {
|
|
83
|
+
const raw = await runRawCommand(["notifications", "--json", ...args]);
|
|
84
|
+
|
|
85
|
+
const firstLine = raw.stdout.trim().split("\n")[0];
|
|
86
|
+
const parsed = firstLine
|
|
87
|
+
? (JSON.parse(firstLine) as Record<string, unknown>)
|
|
88
|
+
: {};
|
|
89
|
+
|
|
90
|
+
return { parsed, stderr: raw.stderr, exitCode: raw.exitCode };
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
async function runRawCommand(args: string[]): Promise<RawCommandResult> {
|
|
77
94
|
const chunks: string[] = [];
|
|
78
95
|
const stderrChunks: string[] = [];
|
|
79
96
|
const originalWrite = process.stdout.write;
|
|
@@ -95,13 +112,7 @@ async function runCommand(args: string[]): Promise<CommandResult> {
|
|
|
95
112
|
const program = new Command();
|
|
96
113
|
program.exitOverride();
|
|
97
114
|
registerNotificationsCommand(program);
|
|
98
|
-
await program.parseAsync([
|
|
99
|
-
"node",
|
|
100
|
-
"test",
|
|
101
|
-
"notifications",
|
|
102
|
-
"--json",
|
|
103
|
-
...args,
|
|
104
|
-
]);
|
|
115
|
+
await program.parseAsync(["node", "test", ...args]);
|
|
105
116
|
} catch {
|
|
106
117
|
// Commander throws on .exitOverride() for --help/errors; ignore
|
|
107
118
|
} finally {
|
|
@@ -112,13 +123,11 @@ async function runCommand(args: string[]): Promise<CommandResult> {
|
|
|
112
123
|
const exitCode = process.exitCode ?? 0;
|
|
113
124
|
process.exitCode = 0;
|
|
114
125
|
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
return { parsed, stderr: stderrChunks.join(""), exitCode };
|
|
126
|
+
return {
|
|
127
|
+
stdout: chunks.join(""),
|
|
128
|
+
stderr: stderrChunks.join(""),
|
|
129
|
+
exitCode,
|
|
130
|
+
};
|
|
122
131
|
}
|
|
123
132
|
|
|
124
133
|
function lastSendBody(): Record<string, unknown> {
|
|
@@ -244,6 +253,41 @@ describe("notifications send", () => {
|
|
|
244
253
|
expect(ipcCalls).toHaveLength(0);
|
|
245
254
|
});
|
|
246
255
|
|
|
256
|
+
test("send rejects invalid source-channel", async () => {
|
|
257
|
+
const { parsed, exitCode } = await runCommand([
|
|
258
|
+
"send",
|
|
259
|
+
"--source-channel",
|
|
260
|
+
"water_reminder",
|
|
261
|
+
"--source-event-name",
|
|
262
|
+
"user.send_notification",
|
|
263
|
+
"--message",
|
|
264
|
+
"Hello",
|
|
265
|
+
]);
|
|
266
|
+
|
|
267
|
+
expect(exitCode).toBe(1);
|
|
268
|
+
expect(parsed).toEqual({
|
|
269
|
+
ok: false,
|
|
270
|
+
error:
|
|
271
|
+
'Invalid source-channel "water_reminder". Must be one of: assistant_tool, vellum, phone, telegram, slack, scheduler, watcher',
|
|
272
|
+
});
|
|
273
|
+
expect(ipcCalls).toHaveLength(0);
|
|
274
|
+
});
|
|
275
|
+
|
|
276
|
+
test("send help lists source-channel values", async () => {
|
|
277
|
+
const { stdout, exitCode } = await runRawCommand([
|
|
278
|
+
"notifications",
|
|
279
|
+
"send",
|
|
280
|
+
"--help",
|
|
281
|
+
]);
|
|
282
|
+
|
|
283
|
+
expect(exitCode).toBe(0);
|
|
284
|
+
const normalizedHelp = stdout.replace(/\s+/g, " ");
|
|
285
|
+
expect(normalizedHelp).toContain(
|
|
286
|
+
"One of: assistant_tool, vellum, phone, telegram, slack, scheduler, watcher",
|
|
287
|
+
);
|
|
288
|
+
expect(normalizedHelp).toContain("(default: assistant_tool)");
|
|
289
|
+
});
|
|
290
|
+
|
|
247
291
|
test("send --conversation-id pins the vellum affinity hint", async () => {
|
|
248
292
|
const { parsed, exitCode } = await runCommand([
|
|
249
293
|
"send",
|
|
@@ -23,6 +23,39 @@ interface ListHomeFeedPayload {
|
|
|
23
23
|
updatedAt: string;
|
|
24
24
|
}
|
|
25
25
|
|
|
26
|
+
const NOTIFICATION_SOURCE_CHANNEL_VALUES = [
|
|
27
|
+
"assistant_tool",
|
|
28
|
+
"vellum",
|
|
29
|
+
"phone",
|
|
30
|
+
"telegram",
|
|
31
|
+
"slack",
|
|
32
|
+
"scheduler",
|
|
33
|
+
"watcher",
|
|
34
|
+
] as const;
|
|
35
|
+
|
|
36
|
+
const URGENCY_VALUES = ["low", "medium", "high", "critical"] as const;
|
|
37
|
+
|
|
38
|
+
const NOTIFICATION_STATUS_VALUES = [
|
|
39
|
+
"new",
|
|
40
|
+
"seen",
|
|
41
|
+
"acted_on",
|
|
42
|
+
"dismissed",
|
|
43
|
+
] as const;
|
|
44
|
+
|
|
45
|
+
const NOTIFICATION_CATEGORY_VALUES = [
|
|
46
|
+
"security",
|
|
47
|
+
"scheduling",
|
|
48
|
+
"background",
|
|
49
|
+
"email",
|
|
50
|
+
"system",
|
|
51
|
+
] as const;
|
|
52
|
+
|
|
53
|
+
const DEFAULT_SOURCE_CHANNEL = "assistant_tool";
|
|
54
|
+
const SOURCE_CHANNEL_HELP = NOTIFICATION_SOURCE_CHANNEL_VALUES.join(", ");
|
|
55
|
+
const URGENCY_HELP = URGENCY_VALUES.join(", ");
|
|
56
|
+
const STATUS_HELP = NOTIFICATION_STATUS_VALUES.join(", ");
|
|
57
|
+
const CATEGORY_HELP = NOTIFICATION_CATEGORY_VALUES.join(", ");
|
|
58
|
+
|
|
26
59
|
function parseBoundedInt(
|
|
27
60
|
raw: string | undefined,
|
|
28
61
|
label: string,
|
|
@@ -71,6 +104,30 @@ function renderFeedItemsHuman(payload: ListHomeFeedPayload): void {
|
|
|
71
104
|
}
|
|
72
105
|
}
|
|
73
106
|
|
|
107
|
+
function validateEnumValue(
|
|
108
|
+
value: string | undefined,
|
|
109
|
+
label: string,
|
|
110
|
+
allowed: readonly string[],
|
|
111
|
+
): { error?: string } {
|
|
112
|
+
if (value === undefined || allowed.includes(value)) return {};
|
|
113
|
+
return {
|
|
114
|
+
error: `Invalid ${label} "${value}". Must be one of: ${allowed.join(", ")}`,
|
|
115
|
+
};
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
function validateEnumFlag(
|
|
119
|
+
values: string[] | undefined,
|
|
120
|
+
label: string,
|
|
121
|
+
allowed: readonly string[],
|
|
122
|
+
): { error?: string } {
|
|
123
|
+
if (!values) return {};
|
|
124
|
+
for (const v of values) {
|
|
125
|
+
const result = validateEnumValue(v, label, allowed);
|
|
126
|
+
if (result.error) return result;
|
|
127
|
+
}
|
|
128
|
+
return {};
|
|
129
|
+
}
|
|
130
|
+
|
|
74
131
|
// ---------------------------------------------------------------------------
|
|
75
132
|
// Command registration
|
|
76
133
|
// ---------------------------------------------------------------------------
|
|
@@ -122,7 +179,7 @@ Examples:
|
|
|
122
179
|
)
|
|
123
180
|
.option(
|
|
124
181
|
"--source-channel <channel>",
|
|
125
|
-
|
|
182
|
+
`Source channel producing this notification. One of: ${SOURCE_CHANNEL_HELP} (default: ${DEFAULT_SOURCE_CHANNEL})`,
|
|
126
183
|
)
|
|
127
184
|
.option(
|
|
128
185
|
"--source-event-name <name>",
|
|
@@ -134,7 +191,7 @@ Examples:
|
|
|
134
191
|
)
|
|
135
192
|
.option(
|
|
136
193
|
"--urgency <urgency>",
|
|
137
|
-
|
|
194
|
+
`Urgency hint. One of: ${URGENCY_HELP} (default: low; use --urgent for critical)`,
|
|
138
195
|
)
|
|
139
196
|
.option(
|
|
140
197
|
"--requires-action",
|
|
@@ -233,9 +290,24 @@ Examples:
|
|
|
233
290
|
try {
|
|
234
291
|
// Apply defaults for optional source fields (minimal-surface
|
|
235
292
|
// ergonomics; explicit values from the CLI still win).
|
|
236
|
-
const sourceChannel =
|
|
293
|
+
const sourceChannel =
|
|
294
|
+
opts.sourceChannel ?? DEFAULT_SOURCE_CHANNEL;
|
|
237
295
|
const sourceEventName = opts.sourceEventName ?? "assistant.share";
|
|
238
296
|
|
|
297
|
+
const sourceChannelError = validateEnumValue(
|
|
298
|
+
sourceChannel,
|
|
299
|
+
"source-channel",
|
|
300
|
+
NOTIFICATION_SOURCE_CHANNEL_VALUES,
|
|
301
|
+
);
|
|
302
|
+
if (sourceChannelError.error) {
|
|
303
|
+
writeOutput(cmd, {
|
|
304
|
+
ok: false,
|
|
305
|
+
error: sourceChannelError.error,
|
|
306
|
+
});
|
|
307
|
+
process.exitCode = 1;
|
|
308
|
+
return;
|
|
309
|
+
}
|
|
310
|
+
|
|
239
311
|
// Validate --message (keep basic validation for immediate CLI feedback)
|
|
240
312
|
const message = opts.message.trim();
|
|
241
313
|
if (message.length === 0) {
|
|
@@ -256,15 +328,15 @@ Examples:
|
|
|
256
328
|
|
|
257
329
|
// Validate --urgency
|
|
258
330
|
const urgency = opts.urgency ?? urgentDefaults.urgency;
|
|
259
|
-
|
|
260
|
-
urgency
|
|
261
|
-
urgency
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
) {
|
|
331
|
+
const urgencyError = validateEnumValue(
|
|
332
|
+
urgency,
|
|
333
|
+
"urgency",
|
|
334
|
+
URGENCY_VALUES,
|
|
335
|
+
);
|
|
336
|
+
if (urgencyError.error) {
|
|
265
337
|
writeOutput(cmd, {
|
|
266
338
|
ok: false,
|
|
267
|
-
error:
|
|
339
|
+
error: urgencyError.error,
|
|
268
340
|
});
|
|
269
341
|
process.exitCode = 1;
|
|
270
342
|
return;
|
|
@@ -425,22 +497,6 @@ Examples:
|
|
|
425
497
|
prev: string[] | undefined,
|
|
426
498
|
): string[] => [...(prev ?? []), value];
|
|
427
499
|
|
|
428
|
-
function validateEnumFlag(
|
|
429
|
-
values: string[] | undefined,
|
|
430
|
-
label: string,
|
|
431
|
-
allowed: readonly string[],
|
|
432
|
-
): { error?: string } {
|
|
433
|
-
if (!values) return {};
|
|
434
|
-
for (const v of values) {
|
|
435
|
-
if (!allowed.includes(v)) {
|
|
436
|
-
return {
|
|
437
|
-
error: `Invalid ${label} "${v}". Must be one of: ${allowed.join(", ")}`,
|
|
438
|
-
};
|
|
439
|
-
}
|
|
440
|
-
}
|
|
441
|
-
return {};
|
|
442
|
-
}
|
|
443
|
-
|
|
444
500
|
notifications
|
|
445
501
|
.command("list")
|
|
446
502
|
.description(
|
|
@@ -453,7 +509,7 @@ Examples:
|
|
|
453
509
|
)
|
|
454
510
|
.option(
|
|
455
511
|
"--status <status>",
|
|
456
|
-
|
|
512
|
+
`Filter by status. One of: ${STATUS_HELP}; repeatable. Overrides --all default behavior.`,
|
|
457
513
|
collectFlag,
|
|
458
514
|
)
|
|
459
515
|
.option(
|
|
@@ -466,12 +522,12 @@ Examples:
|
|
|
466
522
|
)
|
|
467
523
|
.option(
|
|
468
524
|
"--urgency <urgency>",
|
|
469
|
-
|
|
525
|
+
`Filter by urgency. One of: ${URGENCY_HELP}; repeatable`,
|
|
470
526
|
collectFlag,
|
|
471
527
|
)
|
|
472
528
|
.option(
|
|
473
529
|
"--category <category>",
|
|
474
|
-
|
|
530
|
+
`Filter by category. One of: ${CATEGORY_HELP}; repeatable`,
|
|
475
531
|
collectFlag,
|
|
476
532
|
)
|
|
477
533
|
.option(
|
|
@@ -525,25 +581,17 @@ Examples:
|
|
|
525
581
|
) => {
|
|
526
582
|
try {
|
|
527
583
|
const enumChecks: Array<{ error?: string }> = [
|
|
528
|
-
validateEnumFlag(
|
|
529
|
-
|
|
530
|
-
"
|
|
531
|
-
|
|
532
|
-
|
|
533
|
-
|
|
534
|
-
validateEnumFlag(
|
|
535
|
-
|
|
536
|
-
"
|
|
537
|
-
|
|
538
|
-
|
|
539
|
-
]),
|
|
540
|
-
validateEnumFlag(opts.category, "category", [
|
|
541
|
-
"security",
|
|
542
|
-
"scheduling",
|
|
543
|
-
"background",
|
|
544
|
-
"email",
|
|
545
|
-
"system",
|
|
546
|
-
]),
|
|
584
|
+
validateEnumFlag(
|
|
585
|
+
opts.status,
|
|
586
|
+
"status",
|
|
587
|
+
NOTIFICATION_STATUS_VALUES,
|
|
588
|
+
),
|
|
589
|
+
validateEnumFlag(opts.urgency, "urgency", URGENCY_VALUES),
|
|
590
|
+
validateEnumFlag(
|
|
591
|
+
opts.category,
|
|
592
|
+
"category",
|
|
593
|
+
NOTIFICATION_CATEGORY_VALUES,
|
|
594
|
+
),
|
|
547
595
|
];
|
|
548
596
|
const enumError = enumChecks.find((c) => c.error);
|
|
549
597
|
if (enumError) {
|
|
@@ -641,11 +689,11 @@ Examples:
|
|
|
641
689
|
.option("--title <title>", "New short headline (≤ 8 words).")
|
|
642
690
|
.option(
|
|
643
691
|
"--urgency <urgency>",
|
|
644
|
-
|
|
692
|
+
`Set urgency. One of: ${URGENCY_HELP}. Feed-only — does not re-push channel messages.`,
|
|
645
693
|
)
|
|
646
694
|
.option(
|
|
647
695
|
"--status <status>",
|
|
648
|
-
|
|
696
|
+
`Set lifecycle status. One of: ${STATUS_HELP}. Feed-only.`,
|
|
649
697
|
)
|
|
650
698
|
.addHelpText(
|
|
651
699
|
"after",
|
|
@@ -701,24 +749,28 @@ Examples:
|
|
|
701
749
|
return;
|
|
702
750
|
}
|
|
703
751
|
|
|
704
|
-
|
|
705
|
-
opts.urgency
|
|
706
|
-
|
|
707
|
-
|
|
752
|
+
const urgencyError = validateEnumValue(
|
|
753
|
+
opts.urgency,
|
|
754
|
+
"urgency",
|
|
755
|
+
URGENCY_VALUES,
|
|
756
|
+
);
|
|
757
|
+
if (urgencyError.error) {
|
|
708
758
|
writeOutput(cmd, {
|
|
709
759
|
ok: false,
|
|
710
|
-
error:
|
|
760
|
+
error: urgencyError.error,
|
|
711
761
|
});
|
|
712
762
|
process.exitCode = 1;
|
|
713
763
|
return;
|
|
714
764
|
}
|
|
715
|
-
|
|
716
|
-
opts.status
|
|
717
|
-
|
|
718
|
-
|
|
765
|
+
const statusError = validateEnumValue(
|
|
766
|
+
opts.status,
|
|
767
|
+
"status",
|
|
768
|
+
NOTIFICATION_STATUS_VALUES,
|
|
769
|
+
);
|
|
770
|
+
if (statusError.error) {
|
|
719
771
|
writeOutput(cmd, {
|
|
720
772
|
ok: false,
|
|
721
|
-
error:
|
|
773
|
+
error: statusError.error,
|
|
722
774
|
});
|
|
723
775
|
process.exitCode = 1;
|
|
724
776
|
return;
|
|
@@ -33,7 +33,7 @@ const log = getLogger("assistant-feature-flags");
|
|
|
33
33
|
// ---------------------------------------------------------------------------
|
|
34
34
|
|
|
35
35
|
export interface FeatureFlagDefault {
|
|
36
|
-
defaultEnabled: boolean;
|
|
36
|
+
defaultEnabled: boolean | string;
|
|
37
37
|
description: string;
|
|
38
38
|
label: string;
|
|
39
39
|
}
|
|
@@ -103,7 +103,7 @@ function parseRegistryToDefaults(parsed: unknown): FeatureFlagDefaultsRegistry {
|
|
|
103
103
|
const entry = flag as Record<string, unknown>;
|
|
104
104
|
if (entry.scope !== "assistant" && entry.scope !== "both") continue;
|
|
105
105
|
if (typeof entry.key !== "string") continue;
|
|
106
|
-
if (typeof entry.defaultEnabled !== "boolean") continue;
|
|
106
|
+
if (typeof entry.defaultEnabled !== "boolean" && typeof entry.defaultEnabled !== "string") continue;
|
|
107
107
|
|
|
108
108
|
result[entry.key as string] = {
|
|
109
109
|
defaultEnabled: entry.defaultEnabled,
|
|
@@ -133,7 +133,7 @@ function parseRegistryToDefaults(parsed: unknown): FeatureFlagDefaultsRegistry {
|
|
|
133
133
|
*/
|
|
134
134
|
async function fetchOverridesFromGateway(
|
|
135
135
|
timeoutMs?: number,
|
|
136
|
-
): Promise<Record<string, boolean>> {
|
|
136
|
+
): Promise<Record<string, boolean | string>> {
|
|
137
137
|
try {
|
|
138
138
|
return await ipcGetFeatureFlags(timeoutMs);
|
|
139
139
|
} catch {
|
|
@@ -238,7 +238,7 @@ export async function initFeatureFlagOverrides(options?: {
|
|
|
238
238
|
* Returns the gateway-populated cache if `initFeatureFlagOverrides()` was
|
|
239
239
|
* called at startup, or an empty record otherwise.
|
|
240
240
|
*/
|
|
241
|
-
function loadOverrides(): Record<string, boolean> {
|
|
241
|
+
function loadOverrides(): Record<string, boolean | string> {
|
|
242
242
|
return getCachedOverrides() ?? {};
|
|
243
243
|
}
|
|
244
244
|
|
|
@@ -273,7 +273,7 @@ export async function refreshOverridesFromGateway(): Promise<void> {
|
|
|
273
273
|
// ---------------------------------------------------------------------------
|
|
274
274
|
|
|
275
275
|
/**
|
|
276
|
-
* Resolve
|
|
276
|
+
* Resolve the raw value for an assistant feature flag.
|
|
277
277
|
*
|
|
278
278
|
* Resolution order:
|
|
279
279
|
* 1. Override from the gateway IPC fetch (includes platform-pushed remote
|
|
@@ -282,21 +282,32 @@ export async function refreshOverridesFromGateway(): Promise<void> {
|
|
|
282
282
|
* 2. Registry `defaultEnabled` (for declared assistant-scope keys)
|
|
283
283
|
* 3. `false` (for undeclared keys with no override)
|
|
284
284
|
*/
|
|
285
|
-
export function
|
|
285
|
+
export function getAssistantFeatureFlagValue(
|
|
286
286
|
key: string,
|
|
287
287
|
_config: AssistantConfig,
|
|
288
|
-
): boolean {
|
|
288
|
+
): boolean | string {
|
|
289
289
|
const defaults = loadDefaultsRegistry();
|
|
290
290
|
const declared = defaults[key];
|
|
291
291
|
const overrides = loadOverrides();
|
|
292
292
|
|
|
293
|
-
// 1. Check overrides from the gateway IPC cache.
|
|
294
293
|
const explicit = overrides[key];
|
|
295
|
-
if (
|
|
294
|
+
if (explicit !== undefined) return explicit;
|
|
296
295
|
|
|
297
|
-
// 2. For declared keys, use the registry default.
|
|
298
296
|
if (declared) return declared.defaultEnabled;
|
|
299
297
|
|
|
300
|
-
// 3. Undeclared keys with no override fail closed.
|
|
301
298
|
return false;
|
|
302
299
|
}
|
|
300
|
+
|
|
301
|
+
/**
|
|
302
|
+
* Resolve whether an assistant feature flag is enabled (boolean coercion).
|
|
303
|
+
*
|
|
304
|
+
* For boolean flags, returns the resolved value directly.
|
|
305
|
+
* For string flags, returns true if the value is non-empty.
|
|
306
|
+
* Undeclared keys return `false` (fail closed).
|
|
307
|
+
*/
|
|
308
|
+
export function isAssistantFeatureFlagEnabled(
|
|
309
|
+
key: string,
|
|
310
|
+
config: AssistantConfig,
|
|
311
|
+
): boolean {
|
|
312
|
+
return !!getAssistantFeatureFlagValue(key, config);
|
|
313
|
+
}
|
|
@@ -18,7 +18,7 @@ You are an expert app builder and visual designer. When the user asks you to cre
|
|
|
18
18
|
|
|
19
19
|
**Your default behavior:** Build immediately. The user types "build me a habit tracker" and you deliver a complete, polished app with a domain-matched color palette, atmospheric background, and thoughtful interactions. Don't ask what colors they want. Don't show wireframes. Just build something stunning and let them refine from there.
|
|
20
20
|
|
|
21
|
-
**Design quality is delegated to the `frontend-design` skill, so you must also load/install that before proceeding.** That skill defines your aesthetic principles: typography, color strategy, motion, spatial composition, and visual detail. Follow it completely for every build. This skill (app-builder) handles the technical infrastructure: sandbox constraints, data
|
|
21
|
+
**Design quality is delegated to the `frontend-design` skill, so you must also load/install that before proceeding.** That skill defines your aesthetic principles: typography, color strategy, motion, spatial composition, and visual detail. Follow it completely for every build. This skill (app-builder) handles the technical infrastructure: sandbox constraints, data persistence, widget API, app lifecycle, and interaction patterns.
|
|
22
22
|
|
|
23
23
|
## Filesystem Layout
|
|
24
24
|
|
|
@@ -411,31 +411,14 @@ A CSS/JS widget library is auto-injected alongside the design system. Use `.v-*`
|
|
|
411
411
|
|
|
412
412
|
For the full widget reference (class names, JS APIs, chart functions, formatting utilities), see **[Widget Component Library](references/WIDGETS.md)**.
|
|
413
413
|
|
|
414
|
-
#### Data bridge API (deprecated)
|
|
415
|
-
|
|
416
|
-
> **Prefer custom route handlers** for new apps. The data bridge (`window.vellum.data`) only works for assistants that run on the same machine as the desktop app, which will also be deprecated soon.
|
|
417
|
-
|
|
418
|
-
The native WebView can read and write app records via `window.vellum.data`. All methods return Promises.
|
|
419
|
-
|
|
420
|
-
- `window.vellum.data.query()` - Returns all records: `{ id, appId, data, createdAt, updatedAt }[]`
|
|
421
|
-
- `window.vellum.data.create(data)` - Creates a record. Returns the created record.
|
|
422
|
-
- `window.vellum.data.update(recordId, data)` - Updates a record by ID. Returns updated record.
|
|
423
|
-
- `window.vellum.data.delete(recordId)` - Deletes a record by ID. Returns void.
|
|
424
|
-
|
|
425
|
-
Important:
|
|
426
|
-
|
|
427
|
-
- Call `query()` on page load to populate initial state
|
|
428
|
-
- User fields live in `record.data` (e.g., `record.data.title`)
|
|
429
|
-
- Record IDs are UUID strings
|
|
430
|
-
- All operations are async - use `async/await`
|
|
431
|
-
- Wrap all calls in `try/catch`
|
|
432
|
-
|
|
433
414
|
#### Custom route handlers (user-defined routes)
|
|
434
415
|
|
|
435
416
|
When the app needs server-side persistence, custom API logic, or workspace file access, use **user-defined routes**. Route handlers are TypeScript/JavaScript files in the workspace `routes/` directory, served under `/v1/x/`. Call them from the frontend via `window.vellum.fetch("/v1/x/...")`. **Never use raw `fetch()` for `/v1/x/` routes** — it will fail in the sandboxed origin.
|
|
436
417
|
|
|
437
418
|
For handler conventions, examples, key rules, and frontend usage patterns, see **[Custom Route Handlers](references/CUSTOM_ROUTES.md)**.
|
|
438
419
|
|
|
420
|
+
For complete, copyable apps wiring this persistence pattern end-to-end (multi-file TSX frontend + `routes/*.ts` handler), see the **[example apps](references/examples/README.md)**: a [Focus Timer](references/examples/focus-timer.md) (append-only log), a [Habit Tracker](references/examples/habit-tracker.md) (full CRUD), and an [Expense Tracker](references/examples/expense-tracker.md) (create/read/delete + aggregation).
|
|
421
|
+
|
|
439
422
|
#### Client-side state management
|
|
440
423
|
|
|
441
424
|
`localStorage` and `sessionStorage` are available for ephemeral UI state (filters, view modes, collapsed state, preferences, form drafts). Use custom routes for persistent app records, `localStorage` for UI preferences.
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
# Example Apps
|
|
2
|
+
|
|
3
|
+
Complete, copyable reference apps that demonstrate the recommended patterns from this skill:
|
|
4
|
+
multi-file TSX (`formatVersion: 2`), web-compatible persistence via **custom route handlers**
|
|
5
|
+
(`window.vellum.fetch("/v1/x/…")`), and disciplined error handling. Use them as starting
|
|
6
|
+
points — match the structure, not the styling (give every new app its own visual identity per
|
|
7
|
+
the `frontend-design` skill).
|
|
8
|
+
|
|
9
|
+
| Example | Persistence pattern | Route methods |
|
|
10
|
+
| --------------------------------------- | ------------------------------------------------ | -------------------------------- |
|
|
11
|
+
| [Focus Timer](./focus-timer.md) | Append-only log + aggregate stats read on mount | `GET`, `POST` |
|
|
12
|
+
| [Habit Tracker](./habit-tracker.md) | Full CRUD addressed by `id` query param | `GET`, `POST`, `PATCH`, `DELETE` |
|
|
13
|
+
| [Expense Tracker](./expense-tracker.md) | Create / read / delete + client-side aggregation | `GET`, `POST`, `DELETE` |
|
|
14
|
+
|
|
15
|
+
All three persist through a `routes/*.ts` handler — never through `window.vellum.data`, which
|
|
16
|
+
is a desktop-only bridge that silently no-ops on web. See
|
|
17
|
+
[CUSTOM_ROUTES.md](../CUSTOM_ROUTES.md) for the full route handler reference.
|