@vellumai/assistant 0.5.1 → 0.5.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/ARCHITECTURE.md +163 -54
- package/docs/architecture/integrations.md +62 -67
- package/docs/credential-execution-service.md +3 -3
- package/docs/skills.md +100 -0
- package/package.json +1 -1
- package/src/__tests__/agent-loop.test.ts +111 -0
- package/src/__tests__/always-loaded-tools-guard.test.ts +3 -4
- package/src/__tests__/app-builder-tool-scripts.test.ts +13 -151
- package/src/__tests__/app-dir-path-guard.test.ts +78 -0
- package/src/__tests__/app-executors.test.ts +1 -291
- package/src/__tests__/app-git-history.test.ts +4 -4
- package/src/__tests__/app-routes-csp.test.ts +1 -0
- package/src/__tests__/app-store-dir-names.test.ts +426 -0
- package/src/__tests__/attachments-store.test.ts +169 -21
- package/src/__tests__/attachments.test.ts +115 -1
- package/src/__tests__/btw-routes.test.ts +1 -0
- package/src/__tests__/canonical-guardian-store.test.ts +38 -0
- package/src/__tests__/channel-reply-delivery.test.ts +55 -0
- package/src/__tests__/checker.test.ts +54 -0
- package/src/__tests__/claude-code-skill-regression.test.ts +2 -0
- package/src/__tests__/claude-code-tool-profiles.test.ts +2 -0
- package/src/__tests__/compaction.benchmark.test.ts +2 -1
- package/src/__tests__/config-schema-cmd.test.ts +68 -21
- package/src/__tests__/config-schema.test.ts +1 -1
- package/src/__tests__/conversation-agent-loop-overflow.test.ts +156 -5
- package/src/__tests__/conversation-agent-loop.test.ts +297 -2
- package/src/__tests__/conversation-attachments.test.ts +17 -19
- package/src/__tests__/conversation-disk-view-integration.test.ts +277 -0
- package/src/__tests__/conversation-disk-view.test.ts +810 -0
- package/src/__tests__/conversation-error.test.ts +1 -1
- package/src/__tests__/conversation-fork-crud.test.ts +551 -0
- package/src/__tests__/conversation-fork-route.test.ts +386 -0
- package/src/__tests__/conversation-history-web-search.test.ts +1 -1
- package/src/__tests__/conversation-key-store-disk-view.test.ts +130 -0
- package/src/__tests__/conversation-media-retry.test.ts +8 -2
- package/src/__tests__/conversation-memory-dirty-tail.test.ts +150 -0
- package/src/__tests__/conversation-provider-retry-repair.test.ts +7 -0
- package/src/__tests__/conversation-queue.test.ts +36 -1
- package/src/__tests__/conversation-routes-disk-view.test.ts +439 -0
- package/src/__tests__/conversation-routes-guardian-reply.test.ts +2 -2
- package/src/__tests__/conversation-routes-slash-commands.test.ts +2 -7
- package/src/__tests__/conversation-runtime-assembly.test.ts +17 -2
- package/src/__tests__/conversation-skill-tools.test.ts +4 -9
- package/src/__tests__/conversation-slash-commands.test.ts +149 -0
- package/src/__tests__/conversation-store.test.ts +24 -21
- package/src/__tests__/conversation-surfaces-state-update.test.ts +246 -0
- package/src/__tests__/conversation-surfaces-task-progress.test.ts +1 -0
- package/src/__tests__/conversation-title-service.test.ts +137 -0
- package/src/__tests__/conversation-tool-setup-app-refresh.test.ts +25 -315
- package/src/__tests__/conversation-tool-setup-memory-scope.test.ts +1 -0
- package/src/__tests__/conversation-tool-setup-side-effect-flag.test.ts +1 -0
- package/src/__tests__/conversation-wipe.test.ts +226 -0
- package/src/__tests__/conversation-workspace-cache-state.test.ts +44 -2
- package/src/__tests__/conversation-workspace-injection.test.ts +11 -0
- package/src/__tests__/credential-security-invariants.test.ts +3 -0
- package/src/__tests__/credential-vault-unit.test.ts +5 -10
- package/src/__tests__/cu-unified-flow.test.ts +1 -0
- package/src/__tests__/db-conversation-fork-lineage-migration.test.ts +241 -0
- package/src/__tests__/db-llm-request-log-provider-migration.test.ts +214 -0
- package/src/__tests__/db-memory-archive-migration.test.ts +372 -0
- package/src/__tests__/db-memory-brief-state-migration.test.ts +213 -0
- package/src/__tests__/db-memory-reducer-checkpoints.test.ts +273 -0
- package/src/__tests__/diagnostics-export.test.ts +70 -1
- package/src/__tests__/first-greeting.test.ts +80 -0
- package/src/__tests__/gateway-only-guard.test.ts +1 -0
- package/src/__tests__/handlers-user-message-approval-consumption.test.ts +3 -7
- package/src/__tests__/history-repair.test.ts +32 -10
- package/src/__tests__/http-conversation-lineage.test.ts +251 -0
- package/src/__tests__/image-source-path-reinject.test.ts +136 -0
- package/src/__tests__/inline-command-runner.test.ts +311 -0
- package/src/__tests__/inline-skill-authoring-guard.test.ts +220 -0
- package/src/__tests__/inline-skill-load-permissions.test.ts +435 -0
- package/src/__tests__/list-messages-attachments.test.ts +96 -0
- package/src/__tests__/llm-context-normalization.test.ts +1116 -0
- package/src/__tests__/llm-context-route-provider.test.ts +217 -0
- package/src/__tests__/llm-request-log-turn-query.test.ts +270 -0
- package/src/__tests__/media-generate-image.test.ts +47 -94
- package/src/__tests__/memory-brief-open-loops.test.ts +530 -0
- package/src/__tests__/memory-brief-time.test.ts +285 -0
- package/src/__tests__/memory-brief-wrapper.test.ts +311 -0
- package/src/__tests__/memory-chunk-archive.test.ts +400 -0
- package/src/__tests__/memory-chunk-dual-write.test.ts +453 -0
- package/src/__tests__/memory-episode-archive.test.ts +370 -0
- package/src/__tests__/memory-episode-dual-write.test.ts +626 -0
- package/src/__tests__/memory-lifecycle-e2e.test.ts +3 -1
- package/src/__tests__/memory-observation-archive.test.ts +375 -0
- package/src/__tests__/memory-observation-dual-write.test.ts +318 -0
- package/src/__tests__/memory-recall-quality.test.ts +7 -7
- package/src/__tests__/memory-reducer-store.test.ts +728 -0
- package/src/__tests__/memory-reducer-types.test.ts +699 -0
- package/src/__tests__/memory-reducer.test.ts +698 -0
- package/src/__tests__/memory-regressions.test.ts +6 -4
- package/src/__tests__/memory-simplified-config.test.ts +281 -0
- package/src/__tests__/migration-cross-version-compatibility.test.ts +4 -1
- package/src/__tests__/migration-export-http.test.ts +3 -1
- package/src/__tests__/migration-import-commit-http.test.ts +18 -4
- package/src/__tests__/migration-import-preflight-http.test.ts +1 -3
- package/src/__tests__/mime-builder.test.ts +3 -2
- package/src/__tests__/non-member-access-request.test.ts +12 -1
- package/src/__tests__/notification-decision-identity.test.ts +52 -0
- package/src/__tests__/oauth-apps-routes.test.ts +103 -0
- package/src/__tests__/oauth-store.test.ts +115 -0
- package/src/__tests__/parse-identity-fields.test.ts +129 -0
- package/src/__tests__/provider-error-scenarios.test.ts +1 -3
- package/src/__tests__/provider-failover-actual-provider.test.ts +66 -0
- package/src/__tests__/recording-handler.test.ts +17 -0
- package/src/__tests__/registry.test.ts +3 -8
- package/src/__tests__/relay-server.test.ts +1 -1
- package/src/__tests__/runtime-attachment-metadata.test.ts +7 -3
- package/src/__tests__/schema-transforms.test.ts +165 -5
- package/src/__tests__/server-history-render.test.ts +2 -2
- package/src/__tests__/skill-load-inline-command.test.ts +598 -0
- package/src/__tests__/skill-load-inline-includes.test.ts +644 -0
- package/src/__tests__/skills-inline-command-expansions.test.ts +301 -0
- package/src/__tests__/skills-transitive-hash.test.ts +333 -0
- package/src/__tests__/slack-app-setup-skill-regression.test.ts +3 -1
- package/src/__tests__/slack-inbound-verification.test.ts +2 -2
- package/src/__tests__/starter-task-flow.test.ts +1 -0
- package/src/__tests__/suggestion-routes.test.ts +443 -0
- package/src/__tests__/swarm-conversation-integration.test.ts +1 -0
- package/src/__tests__/swarm-recursion.test.ts +1 -0
- package/src/__tests__/swarm-tool.test.ts +1 -0
- package/src/__tests__/tool-execution-abort-cleanup.test.ts +1 -0
- package/src/__tests__/tool-preview-lifecycle.test.ts +32 -5
- package/src/__tests__/top-level-renderer.test.ts +22 -0
- package/src/__tests__/turn-boundary-resolution.test.ts +243 -0
- package/src/__tests__/vellum-self-knowledge-inline-command.test.ts +320 -0
- package/src/__tests__/web-fetch.test.ts +6 -2
- package/src/__tests__/workspace-migration-006-services-config.test.ts +335 -0
- package/src/__tests__/workspace-migration-007-web-search-provider-rename.test.ts +312 -0
- package/src/__tests__/workspace-migration-009-backfill-conversation-disk-view.test.ts +278 -0
- package/src/__tests__/workspace-migration-010-app-dir-rename.test.ts +275 -0
- package/src/__tests__/workspace-migration-012-rename-conversation-disk-view-dirs.test.ts +77 -0
- package/src/__tests__/workspace-migration-013-repair-conversation-disk-view.test.ts +401 -0
- package/src/__tests__/workspace-migration-backfill-installation-id.test.ts +328 -0
- package/src/__tests__/workspace-migration-seed-device-id.test.ts +6 -10
- package/src/agent/attachments.ts +27 -1
- package/src/agent/loop.ts +29 -1
- package/src/avatar/traits-png-sync.ts +80 -25
- package/src/bundler/app-bundler.ts +4 -4
- package/src/calls/call-domain.ts +1 -0
- package/src/calls/voice-session-bridge.ts +1 -0
- package/src/cli/commands/auth.ts +92 -0
- package/src/cli/commands/avatar.ts +7 -6
- package/src/cli/commands/config.ts +2 -0
- package/src/cli/commands/oauth/providers.ts +29 -0
- package/src/cli/program.ts +12 -0
- package/src/cli.ts +15 -48
- package/src/config/bundled-skills/app-builder/SKILL.md +103 -28
- package/src/config/bundled-skills/app-builder/TOOLS.json +5 -199
- package/src/config/bundled-skills/app-builder/tools/{app-query.ts → app-refresh.ts} +2 -2
- package/src/config/bundled-skills/contacts/tools/google-contacts.ts +2 -3
- package/src/config/bundled-skills/gmail/tools/gmail-archive.ts +6 -9
- package/src/config/bundled-skills/gmail/tools/gmail-attachments.ts +4 -6
- package/src/config/bundled-skills/gmail/tools/gmail-draft.ts +2 -3
- package/src/config/bundled-skills/gmail/tools/gmail-filters.ts +2 -3
- package/src/config/bundled-skills/gmail/tools/gmail-follow-up.ts +2 -3
- package/src/config/bundled-skills/gmail/tools/gmail-forward.ts +2 -3
- package/src/config/bundled-skills/gmail/tools/gmail-label.ts +4 -6
- package/src/config/bundled-skills/gmail/tools/gmail-outreach-scan.ts +2 -3
- package/src/config/bundled-skills/gmail/tools/gmail-send-draft.ts +2 -3
- package/src/config/bundled-skills/gmail/tools/gmail-sender-digest.ts +2 -3
- package/src/config/bundled-skills/gmail/tools/gmail-trash.ts +2 -3
- package/src/config/bundled-skills/gmail/tools/gmail-unsubscribe.ts +2 -3
- package/src/config/bundled-skills/gmail/tools/gmail-vacation.ts +2 -3
- package/src/config/bundled-skills/google-calendar/tools/shared.ts +1 -1
- package/src/config/bundled-skills/image-studio/SKILL.md +2 -2
- package/src/config/bundled-skills/image-studio/TOOLS.json +2 -2
- package/src/config/bundled-skills/image-studio/tools/media-generate-image.ts +45 -72
- package/src/config/bundled-skills/media-processing/tools/extract-keyframes.ts +2 -2
- package/src/config/bundled-skills/messaging/tools/shared.ts +1 -1
- package/src/config/bundled-skills/settings/tools/voice-config-update.ts +19 -3
- package/src/config/bundled-skills/skill-management/SKILL.md +1 -1
- package/src/config/bundled-skills/skill-management/TOOLS.json +2 -2
- package/src/config/bundled-skills/slack/tools/shared.ts +19 -4
- package/src/config/bundled-skills/slack/tools/slack-scan-digest.ts +2 -3
- package/src/config/bundled-skills/transcribe/SKILL.md +1 -1
- package/src/config/bundled-skills/transcribe/TOOLS.json +2 -6
- package/src/config/bundled-skills/transcribe/tools/transcribe-media.ts +19 -83
- package/src/config/bundled-tool-registry.ts +2 -14
- package/src/config/feature-flag-registry.json +24 -0
- package/src/config/loader.ts +65 -0
- package/src/config/raw-config-utils.ts +58 -0
- package/src/config/schema-utils.ts +28 -7
- package/src/config/schema.ts +20 -0
- package/src/config/schemas/elevenlabs.ts +18 -0
- package/src/config/schemas/memory-lifecycle.ts +4 -2
- package/src/config/schemas/memory-simplified.ts +101 -0
- package/src/config/schemas/memory-storage.ts +1 -1
- package/src/config/schemas/memory.ts +4 -0
- package/src/config/schemas/services.ts +8 -6
- package/src/config/skills.ts +50 -4
- package/src/contacts/contact-store.ts +13 -6
- package/src/contacts/contacts-write.ts +0 -1
- package/src/context/window-manager.ts +13 -2
- package/src/daemon/conversation-agent-loop-handlers.ts +54 -8
- package/src/daemon/conversation-agent-loop.ts +127 -20
- package/src/daemon/conversation-attachments.ts +18 -36
- package/src/daemon/conversation-error.ts +2 -1
- package/src/daemon/conversation-history.ts +18 -4
- package/src/daemon/conversation-lifecycle.ts +50 -16
- package/src/daemon/conversation-messaging.ts +70 -26
- package/src/daemon/conversation-process.ts +58 -34
- package/src/daemon/conversation-runtime-assembly.ts +22 -38
- package/src/daemon/conversation-slash.ts +121 -256
- package/src/daemon/conversation-surfaces.ts +170 -24
- package/src/daemon/conversation-tool-setup.ts +0 -6
- package/src/daemon/conversation-workspace.ts +21 -1
- package/src/daemon/conversation.ts +69 -30
- package/src/daemon/first-greeting.ts +35 -0
- package/src/daemon/handlers/config-embeddings.ts +156 -0
- package/src/daemon/handlers/config-model.ts +62 -26
- package/src/daemon/handlers/conversations.ts +0 -23
- package/src/daemon/handlers/identity.ts +12 -1
- package/src/daemon/handlers/recording.ts +26 -21
- package/src/daemon/host-cu-proxy.ts +2 -2
- package/src/daemon/lifecycle.ts +115 -65
- package/src/daemon/message-protocol.ts +3 -0
- package/src/daemon/message-types/conversations.ts +18 -0
- package/src/daemon/message-types/messages.ts +1 -0
- package/src/daemon/message-types/shared.ts +2 -0
- package/src/daemon/message-types/surfaces.ts +2 -0
- package/src/daemon/message-types/upgrades.ts +23 -0
- package/src/daemon/server.ts +83 -12
- package/src/daemon/shutdown-handlers.ts +8 -5
- package/src/daemon/startup-error.ts +9 -0
- package/src/daemon/tool-side-effects.ts +11 -28
- package/src/events/tool-permission-telemetry-listener.ts +1 -3
- package/src/followups/followup-store.ts +47 -1
- package/src/instrument.ts +0 -4
- package/src/media/app-icon-generator.ts +2 -2
- package/src/memory/app-git-service.ts +28 -16
- package/src/memory/app-store.ts +230 -41
- package/src/memory/archive-store.ts +400 -0
- package/src/memory/attachments-store.ts +558 -130
- package/src/memory/brief-formatting.ts +33 -0
- package/src/memory/brief-open-loops.ts +266 -0
- package/src/memory/brief-time.ts +161 -0
- package/src/memory/brief.ts +75 -0
- package/src/memory/conversation-attention-store.ts +70 -0
- package/src/memory/conversation-crud.ts +591 -8
- package/src/memory/conversation-directories.ts +125 -0
- package/src/memory/conversation-disk-view.ts +390 -0
- package/src/memory/conversation-key-store.ts +17 -5
- package/src/memory/conversation-queries.ts +5 -1
- package/src/memory/conversation-title-service.ts +21 -49
- package/src/memory/db-init.ts +40 -0
- package/src/memory/embedding-backend.ts +42 -53
- package/src/memory/embedding-gemini.test.ts +4 -4
- package/src/memory/embedding-local.ts +1 -3
- package/src/memory/embedding-ollama.ts +1 -3
- package/src/memory/embedding-openai.ts +1 -3
- package/src/memory/indexer.ts +114 -21
- package/src/memory/items-extractor.ts +42 -13
- package/src/memory/job-handlers/conversation-starters.ts +6 -1
- package/src/memory/job-handlers/embedding.test.ts +2 -4
- package/src/memory/job-handlers/embedding.ts +83 -0
- package/src/memory/job-utils.ts +1 -1
- package/src/memory/jobs-store.ts +6 -0
- package/src/memory/jobs-worker.ts +12 -0
- package/src/memory/llm-request-log-store.ts +100 -1
- package/src/memory/migrations/102-alter-table-columns.ts +5 -0
- package/src/memory/migrations/146-schedule-oneshot-routing.ts +3 -3
- package/src/memory/migrations/147-migrate-reminders-to-schedules.ts +66 -70
- package/src/memory/migrations/148-drop-reminders-table.ts +5 -9
- package/src/memory/migrations/160-drop-loopback-port-column.ts +1 -3
- package/src/memory/migrations/174-rename-thread-starters-table.ts +0 -7
- package/src/memory/migrations/178-oauth-providers-managed-service-config-key.ts +15 -0
- package/src/memory/migrations/179-llm-request-log-message-id.ts +16 -0
- package/src/memory/migrations/180-backfill-inline-attachments-to-disk.ts +66 -0
- package/src/memory/migrations/181-rename-thread-starters-checkpoints.ts +46 -0
- package/src/memory/migrations/182-oauth-providers-display-metadata.ts +20 -0
- package/src/memory/migrations/183-add-conversation-fork-lineage.ts +22 -0
- package/src/memory/migrations/184-llm-request-log-provider.ts +12 -0
- package/src/memory/migrations/185-memory-brief-state.ts +52 -0
- package/src/memory/migrations/186-memory-archive.ts +109 -0
- package/src/memory/migrations/187-memory-reducer-checkpoints.ts +19 -0
- package/src/memory/migrations/index.ts +10 -0
- package/src/memory/migrations/registry.ts +13 -0
- package/src/memory/qdrant-client.ts +23 -4
- package/src/memory/reducer-store.ts +271 -0
- package/src/memory/reducer-types.ts +99 -0
- package/src/memory/reducer.ts +453 -0
- package/src/memory/retriever.test.ts +601 -2
- package/src/memory/retriever.ts +85 -9
- package/src/memory/schema/conversations.ts +9 -0
- package/src/memory/schema/index.ts +2 -0
- package/src/memory/schema/infrastructure.ts +13 -7
- package/src/memory/schema/memory-archive.ts +121 -0
- package/src/memory/schema/memory-brief.ts +55 -0
- package/src/memory/schema/oauth.ts +6 -0
- package/src/memory/search/semantic.ts +17 -4
- package/src/messaging/providers/gmail/mime-builder.ts +3 -1
- package/src/notifications/copy-composer.ts +26 -0
- package/src/notifications/decision-engine.ts +14 -1
- package/src/notifications/emit-signal.ts +1 -1
- package/src/notifications/signal.ts +36 -0
- package/src/oauth/byo-connection.test.ts +1 -45
- package/src/oauth/byo-connection.ts +2 -8
- package/src/oauth/connect-orchestrator.ts +15 -11
- package/src/oauth/connection-resolver.test.ts +191 -0
- package/src/oauth/connection-resolver.ts +66 -38
- package/src/oauth/connection.ts +0 -1
- package/src/oauth/oauth-store.ts +99 -47
- package/src/oauth/platform-connection.test.ts +0 -1
- package/src/oauth/platform-connection.ts +11 -3
- package/src/oauth/seed-providers.ts +78 -3
- package/src/oauth/token-persistence.ts +16 -10
- package/src/permissions/checker.ts +160 -14
- package/src/permissions/defaults.ts +14 -0
- package/src/prompts/templates/BOOTSTRAP.md +2 -0
- package/src/providers/anthropic/client.ts +8 -1
- package/src/providers/failover.ts +4 -1
- package/src/providers/gemini/client.ts +50 -0
- package/src/providers/model-catalog.ts +92 -0
- package/src/providers/model-intents.ts +29 -20
- package/src/providers/openai/client.ts +49 -0
- package/src/providers/types.ts +2 -0
- package/src/runtime/access-request-helper.ts +16 -7
- package/src/runtime/auth/credential-service.ts +3 -1
- package/src/runtime/auth/route-policy.ts +14 -1
- package/src/runtime/btw-sidechain.ts +101 -0
- package/src/runtime/channel-reply-delivery.ts +17 -1
- package/src/runtime/http-router.ts +3 -1
- package/src/runtime/http-server.ts +196 -141
- package/src/runtime/http-types.ts +1 -0
- package/src/runtime/migrations/vbundle-builder.ts +5 -1
- package/src/runtime/routes/access-request-decision.ts +41 -0
- package/src/runtime/routes/app-management-routes.ts +6 -3
- package/src/runtime/routes/app-routes.ts +7 -3
- package/src/runtime/routes/approval-routes.ts +1 -0
- package/src/runtime/routes/approval-strategies/guardian-callback-strategy.ts +34 -2
- package/src/runtime/routes/attachment-routes.ts +45 -15
- package/src/runtime/routes/btw-routes.ts +21 -61
- package/src/runtime/routes/conversation-management-routes.ts +74 -0
- package/src/runtime/routes/conversation-query-routes.ts +187 -10
- package/src/runtime/routes/conversation-routes.ts +269 -28
- package/src/runtime/routes/conversation-starter-routes.ts +9 -11
- package/src/runtime/routes/diagnostics-routes.ts +1 -0
- package/src/runtime/routes/identity-routes.ts +2 -35
- package/src/runtime/routes/inbound-stages/acl-enforcement.ts +2 -2
- package/src/runtime/routes/llm-context-normalization.ts +1212 -0
- package/src/runtime/routes/log-export-routes.ts +3 -0
- package/src/runtime/routes/memory-item-routes.test.ts +34 -0
- package/src/runtime/routes/memory-item-routes.ts +94 -5
- package/src/runtime/routes/migration-routes.ts +4 -1
- package/src/runtime/routes/oauth-apps.ts +291 -0
- package/src/runtime/routes/secret-routes.ts +30 -1
- package/src/runtime/routes/settings-routes.ts +14 -0
- package/src/runtime/routes/surface-action-routes.ts +68 -1
- package/src/runtime/routes/trace-event-routes.ts +4 -1
- package/src/schedule/schedule-store.ts +30 -21
- package/src/security/secure-keys.ts +21 -0
- package/src/signals/bash.ts +1 -1
- package/src/skills/inline-command-expansions.ts +204 -0
- package/src/skills/inline-command-render.ts +127 -0
- package/src/skills/inline-command-runner.ts +242 -0
- package/src/skills/transitive-version-hash.ts +88 -0
- package/src/swarm/backend-claude-code.ts +3 -6
- package/src/tasks/task-store.ts +43 -1
- package/src/telemetry/usage-telemetry-reporter.test.ts +3 -2
- package/src/telemetry/usage-telemetry-reporter.ts +3 -1
- package/src/tools/AGENTS.md +6 -10
- package/src/tools/apps/executors.ts +17 -232
- package/src/tools/claude-code/claude-code.ts +2 -3
- package/src/tools/credentials/vault.ts +7 -12
- package/src/tools/host-filesystem/read.ts +13 -10
- package/src/tools/network/__tests__/web-search.test.ts +4 -2
- package/src/tools/permission-checker.ts +8 -1
- package/src/tools/schedule/list.ts +2 -7
- package/src/tools/schema-transforms.ts +5 -0
- package/src/tools/shared/filesystem/format-diff.ts +2 -7
- package/src/tools/skills/execute.ts +1 -1
- package/src/tools/skills/load.ts +140 -6
- package/src/tools/tool-manifest.ts +0 -6
- package/src/tools/ui-surface/definitions.ts +2 -2
- package/src/util/device-id.ts +28 -5
- package/src/util/platform.ts +24 -0
- package/src/util/pricing.ts +1 -0
- package/src/util/retry.ts +1 -3
- package/src/workspace/migrations/003-seed-device-id.ts +3 -4
- package/src/workspace/migrations/006-services-config.ts +5 -0
- package/src/workspace/migrations/008-voice-timeout-and-max-steps.ts +12 -0
- package/src/workspace/migrations/009-backfill-conversation-disk-view.ts +10 -0
- package/src/workspace/migrations/010-app-dir-rename.ts +223 -0
- package/src/workspace/migrations/{002-backfill-installation-id.ts → 011-backfill-installation-id.ts} +24 -13
- package/src/workspace/migrations/012-rename-conversation-disk-view-dirs.ts +64 -0
- package/src/workspace/migrations/013-repair-conversation-disk-view.ts +11 -0
- package/src/workspace/migrations/rebuild-conversation-disk-view.ts +186 -0
- package/src/workspace/migrations/registry.ts +11 -1
- package/src/workspace/top-level-renderer.ts +12 -0
- package/src/__tests__/asset-materialize-tool.test.ts +0 -523
- package/src/__tests__/asset-search-tool.test.ts +0 -536
- package/src/__tests__/fixtures/media-reuse-fixtures.ts +0 -56
- package/src/__tests__/media-reuse-story.e2e.test.ts +0 -762
- package/src/__tests__/media-visibility-policy.test.ts +0 -190
- package/src/config/bundled-skills/app-builder/tools/app-file-edit.ts +0 -14
- package/src/config/bundled-skills/app-builder/tools/app-file-list.ts +0 -13
- package/src/config/bundled-skills/app-builder/tools/app-file-read.ts +0 -21
- package/src/config/bundled-skills/app-builder/tools/app-file-write.ts +0 -14
- package/src/config/bundled-skills/app-builder/tools/app-list.ts +0 -13
- package/src/config/bundled-skills/app-builder/tools/app-update.ts +0 -23
- package/src/daemon/media-visibility-policy.ts +0 -59
- package/src/tools/assets/materialize.ts +0 -248
- package/src/tools/assets/search.ts +0 -400
package/src/tools/skills/load.ts
CHANGED
|
@@ -21,12 +21,24 @@ import {
|
|
|
21
21
|
indexCatalogById,
|
|
22
22
|
validateIncludes,
|
|
23
23
|
} from "../../skills/include-graph.js";
|
|
24
|
+
import { renderInlineCommands } from "../../skills/inline-command-render.js";
|
|
24
25
|
import { parseToolManifestFile } from "../../skills/tool-manifest.js";
|
|
25
26
|
import { computeSkillVersionHash } from "../../skills/version-hash.js";
|
|
26
27
|
import { getLogger } from "../../util/logger.js";
|
|
28
|
+
import { getWorkspaceDirDisplay } from "../../util/platform.js";
|
|
27
29
|
import { registerTool } from "../registry.js";
|
|
28
30
|
import type { Tool, ToolContext, ToolExecutionResult } from "../types.js";
|
|
29
31
|
|
|
32
|
+
/** Canonical feature flag key for inline skill command expansion. */
|
|
33
|
+
const INLINE_COMMANDS_FLAG_KEY = "feature_flags.inline-skill-commands.enabled";
|
|
34
|
+
|
|
35
|
+
/** Skill sources eligible for inline command expansion in v1. */
|
|
36
|
+
const INLINE_COMMAND_ELIGIBLE_SOURCES = new Set([
|
|
37
|
+
"bundled",
|
|
38
|
+
"managed",
|
|
39
|
+
"workspace",
|
|
40
|
+
]);
|
|
41
|
+
|
|
30
42
|
const log = getLogger("skill-load");
|
|
31
43
|
|
|
32
44
|
/**
|
|
@@ -76,7 +88,9 @@ function formatToolSchemas(
|
|
|
76
88
|
|
|
77
89
|
for (const tool of manifest.tools) {
|
|
78
90
|
lines.push(`${toolHeadingLevel} ${tool.name}`);
|
|
79
|
-
lines.push(
|
|
91
|
+
lines.push(
|
|
92
|
+
tool.description.replaceAll("{workspaceDir}", getWorkspaceDirDisplay()),
|
|
93
|
+
);
|
|
80
94
|
|
|
81
95
|
const schema = tool.input_schema;
|
|
82
96
|
const properties = schema.properties as
|
|
@@ -96,7 +110,7 @@ function formatToolSchemas(
|
|
|
96
110
|
: "optional";
|
|
97
111
|
const descPart =
|
|
98
112
|
typeof paramDef.description === "string"
|
|
99
|
-
? `: ${paramDef.description}`
|
|
113
|
+
? `: ${paramDef.description.replaceAll("{workspaceDir}", getWorkspaceDirDisplay())}`
|
|
100
114
|
: "";
|
|
101
115
|
lines.push(
|
|
102
116
|
`- ${paramName} (${paramType}, ${requiredLabel})${descPart}`,
|
|
@@ -113,7 +127,7 @@ function formatToolSchemas(
|
|
|
113
127
|
export class SkillLoadTool implements Tool {
|
|
114
128
|
name = "skill_load";
|
|
115
129
|
description =
|
|
116
|
-
"Load full instructions for a skill. Works for both bundled skills (listed in the catalog) and workspace skills
|
|
130
|
+
"Load full instructions for a skill. Works for both bundled skills (listed in the catalog) and custom workspace skills.";
|
|
117
131
|
category = "skills";
|
|
118
132
|
defaultRiskLevel = RiskLevel.Low;
|
|
119
133
|
|
|
@@ -136,7 +150,7 @@ export class SkillLoadTool implements Tool {
|
|
|
136
150
|
|
|
137
151
|
async execute(
|
|
138
152
|
input: Record<string, unknown>,
|
|
139
|
-
|
|
153
|
+
context: ToolContext,
|
|
140
154
|
): Promise<ToolExecutionResult> {
|
|
141
155
|
const selector = input.skill;
|
|
142
156
|
if (typeof selector !== "string" || selector.trim().length === 0) {
|
|
@@ -279,7 +293,63 @@ export class SkillLoadTool implements Tool {
|
|
|
279
293
|
}
|
|
280
294
|
}
|
|
281
295
|
|
|
282
|
-
|
|
296
|
+
let body = skill.body.length > 0 ? skill.body : "(No body content)";
|
|
297
|
+
|
|
298
|
+
// ── Inline command expansion ──────────────────────────────────────────
|
|
299
|
+
const hasInlineCommands =
|
|
300
|
+
skill.inlineCommandExpansions && skill.inlineCommandExpansions.length > 0;
|
|
301
|
+
|
|
302
|
+
if (hasInlineCommands) {
|
|
303
|
+
const inlineFlagEnabled = isAssistantFeatureFlagEnabled(
|
|
304
|
+
INLINE_COMMANDS_FLAG_KEY,
|
|
305
|
+
config,
|
|
306
|
+
);
|
|
307
|
+
|
|
308
|
+
if (!inlineFlagEnabled) {
|
|
309
|
+
// Feature flag is off: fail closed instead of leaving live tokens in
|
|
310
|
+
// the prompt that the LLM might try to interpret.
|
|
311
|
+
return {
|
|
312
|
+
content: `Error: skill "${skill.id}" contains inline command expansions but the inline-skill-commands feature flag is disabled. Enable the flag to use this skill.`,
|
|
313
|
+
isError: true,
|
|
314
|
+
};
|
|
315
|
+
}
|
|
316
|
+
|
|
317
|
+
if (skill.source === "extra") {
|
|
318
|
+
// Third-party extra roots are out of scope for inline command
|
|
319
|
+
// expansion in v1. Reject explicitly so the failure is clear.
|
|
320
|
+
return {
|
|
321
|
+
content: `Error: skill "${skill.id}" contains inline command expansions but inline commands are not supported for third-party (extra) skill sources.`,
|
|
322
|
+
isError: true,
|
|
323
|
+
};
|
|
324
|
+
}
|
|
325
|
+
|
|
326
|
+
if (!INLINE_COMMAND_ELIGIBLE_SOURCES.has(skill.source)) {
|
|
327
|
+
// Defensive: reject any other unknown sources that somehow have
|
|
328
|
+
// inline commands. Should not happen with current SkillSource values,
|
|
329
|
+
// but fail closed if a new source type is added without updating this.
|
|
330
|
+
return {
|
|
331
|
+
content: `Error: skill "${skill.id}" contains inline command expansions but source "${skill.source}" is not eligible for inline command expansion.`,
|
|
332
|
+
isError: true,
|
|
333
|
+
};
|
|
334
|
+
}
|
|
335
|
+
|
|
336
|
+
// Render inline commands by executing each through the sandbox runner
|
|
337
|
+
const renderResult = await renderInlineCommands(
|
|
338
|
+
body,
|
|
339
|
+
skill.inlineCommandExpansions!,
|
|
340
|
+
context.workingDir,
|
|
341
|
+
);
|
|
342
|
+
body = renderResult.renderedBody;
|
|
343
|
+
|
|
344
|
+
log.info(
|
|
345
|
+
{
|
|
346
|
+
skillId: skill.id,
|
|
347
|
+
expandedCount: renderResult.expandedCount,
|
|
348
|
+
failedCount: renderResult.failedCount,
|
|
349
|
+
},
|
|
350
|
+
"Rendered inline command expansions",
|
|
351
|
+
);
|
|
352
|
+
}
|
|
283
353
|
|
|
284
354
|
// Build reference file listing (if any)
|
|
285
355
|
const referenceListing = listReferenceFiles(skill.directoryPath);
|
|
@@ -313,8 +383,72 @@ export class SkillLoadTool implements Tool {
|
|
|
313
383
|
// Load the included skill's body content
|
|
314
384
|
const childLoaded = loadSkillBySelector(childId);
|
|
315
385
|
if (childLoaded.skill && childLoaded.skill.body.length > 0) {
|
|
386
|
+
let childBody = childLoaded.skill.body;
|
|
387
|
+
|
|
388
|
+
// ── Inline command expansion for included child skill ─────────
|
|
389
|
+
const childHasInlineCommands =
|
|
390
|
+
childLoaded.skill.inlineCommandExpansions &&
|
|
391
|
+
childLoaded.skill.inlineCommandExpansions.length > 0;
|
|
392
|
+
|
|
393
|
+
if (childHasInlineCommands) {
|
|
394
|
+
const childInlineFlagEnabled = isAssistantFeatureFlagEnabled(
|
|
395
|
+
INLINE_COMMANDS_FLAG_KEY,
|
|
396
|
+
config,
|
|
397
|
+
);
|
|
398
|
+
|
|
399
|
+
// Fail closed: if the flag is off, reject the entire skill_load
|
|
400
|
+
// just like we do for root skills. Leaving raw !`...` tokens in
|
|
401
|
+
// the prompt would violate the documented fail-closed contract.
|
|
402
|
+
if (!childInlineFlagEnabled) {
|
|
403
|
+
return {
|
|
404
|
+
content: `Error: included skill "${childId}" contains inline command expansions but the inline-skill-commands feature flag is disabled. Enable the flag to use skill "${skill.id}".`,
|
|
405
|
+
isError: true,
|
|
406
|
+
};
|
|
407
|
+
}
|
|
408
|
+
|
|
409
|
+
if (childLoaded.skill.source === "extra") {
|
|
410
|
+
return {
|
|
411
|
+
content: `Error: included skill "${childId}" contains inline command expansions but inline commands are not supported for third-party (extra) skill sources.`,
|
|
412
|
+
isError: true,
|
|
413
|
+
};
|
|
414
|
+
}
|
|
415
|
+
|
|
416
|
+
if (
|
|
417
|
+
!INLINE_COMMAND_ELIGIBLE_SOURCES.has(childLoaded.skill.source)
|
|
418
|
+
) {
|
|
419
|
+
return {
|
|
420
|
+
content: `Error: included skill "${childId}" contains inline command expansions but source "${childLoaded.skill.source}" is not eligible for inline command expansion.`,
|
|
421
|
+
isError: true,
|
|
422
|
+
};
|
|
423
|
+
}
|
|
424
|
+
|
|
425
|
+
try {
|
|
426
|
+
const childRenderResult = await renderInlineCommands(
|
|
427
|
+
childBody,
|
|
428
|
+
childLoaded.skill.inlineCommandExpansions!,
|
|
429
|
+
context.workingDir,
|
|
430
|
+
);
|
|
431
|
+
childBody = childRenderResult.renderedBody;
|
|
432
|
+
|
|
433
|
+
log.info(
|
|
434
|
+
{
|
|
435
|
+
skillId: childId,
|
|
436
|
+
parentSkillId: skill.id,
|
|
437
|
+
expandedCount: childRenderResult.expandedCount,
|
|
438
|
+
failedCount: childRenderResult.failedCount,
|
|
439
|
+
},
|
|
440
|
+
"Rendered inline command expansions for included skill",
|
|
441
|
+
);
|
|
442
|
+
} catch (err) {
|
|
443
|
+
log.warn(
|
|
444
|
+
{ err, skillId: childId, parentSkillId: skill.id },
|
|
445
|
+
"Failed to render inline commands for included skill, using raw body",
|
|
446
|
+
);
|
|
447
|
+
}
|
|
448
|
+
}
|
|
449
|
+
|
|
316
450
|
includedBodies.push(
|
|
317
|
-
`--- Included Skill: ${childLoaded.skill.displayName} (${childId}) ---\n${
|
|
451
|
+
`--- Included Skill: ${childLoaded.skill.displayName} (${childId}) ---\n${childBody}`,
|
|
318
452
|
);
|
|
319
453
|
|
|
320
454
|
// List reference files for the included skill
|
|
@@ -11,8 +11,6 @@ import {
|
|
|
11
11
|
isCesSecureInstallEnabled,
|
|
12
12
|
isCesToolsEnabled,
|
|
13
13
|
} from "../credential-execution/feature-gates.js";
|
|
14
|
-
import { assetMaterializeTool } from "./assets/materialize.js";
|
|
15
|
-
import { assetSearchTool } from "./assets/search.js";
|
|
16
14
|
import { makeAuthenticatedRequestTool } from "./credential-execution/make-authenticated-request.js";
|
|
17
15
|
import { manageSecureCommandTool } from "./credential-execution/manage-secure-command-tool.js";
|
|
18
16
|
import { runAuthenticatedCommandTool } from "./credential-execution/run-authenticated-command.js";
|
|
@@ -63,8 +61,6 @@ export const eagerModuleToolNames: string[] = [
|
|
|
63
61
|
"skill_execute",
|
|
64
62
|
"skill_load",
|
|
65
63
|
"request_system_permission",
|
|
66
|
-
"asset_search",
|
|
67
|
-
"asset_materialize",
|
|
68
64
|
];
|
|
69
65
|
|
|
70
66
|
// ── Explicit tool instances ─────────────────────────────────────────
|
|
@@ -85,8 +81,6 @@ export const explicitTools: Tool[] = [
|
|
|
85
81
|
skillExecuteTool,
|
|
86
82
|
skillLoadTool,
|
|
87
83
|
requestSystemPermissionTool,
|
|
88
|
-
assetSearchTool,
|
|
89
|
-
assetMaterializeTool,
|
|
90
84
|
// Always-explicit tools
|
|
91
85
|
memoryManageTool,
|
|
92
86
|
memoryRecallTool,
|
|
@@ -30,7 +30,7 @@ export const uiShowTool: Tool = {
|
|
|
30
30
|
description:
|
|
31
31
|
"Show structured data or UI to the user. For long-form writing use the document skill; for interactive apps use the app-builder skill.\n\n" +
|
|
32
32
|
"Surface types (data shapes):\n" +
|
|
33
|
-
|
|
33
|
+
'- 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' +
|
|
34
34
|
'- table: { columns: [{ id, label, width? }], rows: [{ id, cells: Record<id, string | { text, icon?, iconColor?: "success"|"warning"|"error"|"muted" }>, selectable?, selected? }], selectionMode?: "none"|"single"|"multiple", caption? }\n' +
|
|
35
35
|
'- 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' +
|
|
36
36
|
'- list: { items: [{ id, title, subtitle?, icon?, selected? }], selectionMode: "single"|"multiple"|"none" }\n' +
|
|
@@ -91,7 +91,7 @@ export const uiShowTool: Tool = {
|
|
|
91
91
|
type: "string",
|
|
92
92
|
enum: ["inline", "panel"],
|
|
93
93
|
description:
|
|
94
|
-
'Where to render the surface. "inline" embeds it in the chat message. "panel" shows a floating window. Defaults to "inline".',
|
|
94
|
+
'Where to render the surface. "inline" embeds it in the chat message. "panel" shows a floating window. Defaults to "inline". Prefer inline — only use panel when the user explicitly asks for a separate window.',
|
|
95
95
|
},
|
|
96
96
|
await_action: {
|
|
97
97
|
type: "boolean",
|
package/src/util/device-id.ts
CHANGED
|
@@ -1,9 +1,16 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Device ID resolver.
|
|
3
3
|
*
|
|
4
|
-
* Reads or creates a stable per-device UUID stored in
|
|
5
|
-
* The file is a JSON object (`{ "deviceId": "<uuid>" }`)
|
|
6
|
-
* future per-device metadata.
|
|
4
|
+
* Reads or creates a stable per-device UUID stored in device.json under the
|
|
5
|
+
* Vellum config directory. The file is a JSON object (`{ "deviceId": "<uuid>" }`)
|
|
6
|
+
* extensible for future per-device metadata.
|
|
7
|
+
*
|
|
8
|
+
* Path resolution:
|
|
9
|
+
* - Containerized (IS_CONTAINERIZED=true): uses BASE_DATA_DIR, which maps to a
|
|
10
|
+
* persistent volume. Each container is effectively its own "device."
|
|
11
|
+
* - Local (single or multi-instance): uses homedir() so all instances on the
|
|
12
|
+
* same machine share a single device ID, even when BASE_DATA_DIR is set to
|
|
13
|
+
* an instance-scoped directory.
|
|
7
14
|
*
|
|
8
15
|
* The value is cached in memory after the first successful read/write.
|
|
9
16
|
* Falls back to a generated UUID if the file cannot be read or written.
|
|
@@ -14,18 +21,34 @@ import { existsSync, mkdirSync, readFileSync, writeFileSync } from "node:fs";
|
|
|
14
21
|
import { homedir } from "node:os";
|
|
15
22
|
import { join } from "node:path";
|
|
16
23
|
|
|
24
|
+
import { getBaseDataDir, getIsContainerized } from "../config/env-registry.js";
|
|
17
25
|
import { getLogger } from "./logger.js";
|
|
18
26
|
|
|
19
27
|
const log = getLogger("device-id");
|
|
20
28
|
|
|
21
29
|
let cached: string | undefined;
|
|
22
30
|
|
|
31
|
+
/**
|
|
32
|
+
* Resolve the base directory for device.json.
|
|
33
|
+
*
|
|
34
|
+
* In containerized environments, BASE_DATA_DIR points to a persistent volume
|
|
35
|
+
* and homedir() is ephemeral, so we must use BASE_DATA_DIR.
|
|
36
|
+
* In local environments (including multi-instance), homedir() is stable and
|
|
37
|
+
* shared across instances, giving a true per-machine device ID.
|
|
38
|
+
*/
|
|
39
|
+
export function getDeviceIdBaseDir(): string {
|
|
40
|
+
if (getIsContainerized()) {
|
|
41
|
+
return getBaseDataDir() || homedir();
|
|
42
|
+
}
|
|
43
|
+
return homedir();
|
|
44
|
+
}
|
|
45
|
+
|
|
23
46
|
/**
|
|
24
47
|
* Get the stable device ID for this machine.
|
|
25
48
|
*
|
|
26
49
|
* Resolution order:
|
|
27
50
|
* 1. Cached in-memory value (populated on first call)
|
|
28
|
-
* 2. `deviceId` field from
|
|
51
|
+
* 2. `deviceId` field from device.json
|
|
29
52
|
* 3. Generate a new UUID, persist it to device.json, and return it
|
|
30
53
|
*
|
|
31
54
|
* On any read/write error the generated UUID is still cached so the
|
|
@@ -36,7 +59,7 @@ export function getDeviceId(): string {
|
|
|
36
59
|
return cached;
|
|
37
60
|
}
|
|
38
61
|
|
|
39
|
-
const vellumDir = join(
|
|
62
|
+
const vellumDir = join(getDeviceIdBaseDir(), ".vellum");
|
|
40
63
|
const filePath = join(vellumDir, "device.json");
|
|
41
64
|
const generated = randomUUID();
|
|
42
65
|
|
package/src/util/platform.ts
CHANGED
|
@@ -366,6 +366,24 @@ export function getWorkspaceDir(): string {
|
|
|
366
366
|
return join(getRootDir(), "workspace");
|
|
367
367
|
}
|
|
368
368
|
|
|
369
|
+
/**
|
|
370
|
+
* Returns a display-friendly workspace path for embedding in agent-facing text
|
|
371
|
+
* (skill bodies, tool descriptions). Replaces the home directory prefix with `~`
|
|
372
|
+
* so paths stay concise and portable across machines.
|
|
373
|
+
*
|
|
374
|
+
* Examples:
|
|
375
|
+
* /Users/sidd/.vellum/workspace → ~/.vellum/workspace
|
|
376
|
+
* /data/.vellum/workspace → /data/.vellum/workspace
|
|
377
|
+
*/
|
|
378
|
+
export function getWorkspaceDirDisplay(): string {
|
|
379
|
+
const abs = getWorkspaceDir();
|
|
380
|
+
const home = homedir();
|
|
381
|
+
if (abs.startsWith(home + "/") || abs === home) {
|
|
382
|
+
return "~" + abs.slice(home.length);
|
|
383
|
+
}
|
|
384
|
+
return abs;
|
|
385
|
+
}
|
|
386
|
+
|
|
369
387
|
/** Returns ~/.vellum/workspace/config.json */
|
|
370
388
|
export function getWorkspaceConfigPath(): string {
|
|
371
389
|
return join(getWorkspaceDir(), "config.json");
|
|
@@ -381,6 +399,11 @@ export function getWorkspaceHooksDir(): string {
|
|
|
381
399
|
return join(getWorkspaceDir(), "hooks");
|
|
382
400
|
}
|
|
383
401
|
|
|
402
|
+
/** Returns ~/.vellum/workspace/conversations */
|
|
403
|
+
export function getConversationsDir(): string {
|
|
404
|
+
return join(getWorkspaceDir(), "conversations");
|
|
405
|
+
}
|
|
406
|
+
|
|
384
407
|
/** Returns the workspace path for a prompt file (e.g. IDENTITY.md, SOUL.md, USER.md). */
|
|
385
408
|
export function getWorkspacePromptPath(file: string): string {
|
|
386
409
|
return join(getWorkspaceDir(), file);
|
|
@@ -399,6 +422,7 @@ export function ensureDataDir(): void {
|
|
|
399
422
|
join(root, "hooks"),
|
|
400
423
|
join(workspace, "skills"),
|
|
401
424
|
join(workspace, "embedding-models"),
|
|
425
|
+
join(workspace, "conversations"),
|
|
402
426
|
// Data sub-dirs under workspace
|
|
403
427
|
wsData,
|
|
404
428
|
join(wsData, "db"),
|
package/src/util/pricing.ts
CHANGED
|
@@ -25,6 +25,7 @@ const PROVIDER_PRICING: Record<string, Record<string, ModelPricing>> = {
|
|
|
25
25
|
},
|
|
26
26
|
openai: {
|
|
27
27
|
"gpt-5.4": { inputPer1M: 2.5, outputPer1M: 15 },
|
|
28
|
+
"gpt-5.4-mini": { inputPer1M: 0.5, outputPer1M: 3 },
|
|
28
29
|
"gpt-5.4-nano": { inputPer1M: 0.2, outputPer1M: 1.25 },
|
|
29
30
|
"gpt-5.2": { inputPer1M: 1.75, outputPer1M: 14 },
|
|
30
31
|
"gpt-4o": { inputPer1M: 2.5, outputPer1M: 10 },
|
package/src/util/retry.ts
CHANGED
|
@@ -108,9 +108,7 @@ export function isRetryableNetworkError(error: unknown): boolean {
|
|
|
108
108
|
|
|
109
109
|
// Fall back to message-based detection for errors without errno codes
|
|
110
110
|
// (e.g. Bun's "The socket connection was closed unexpectedly")
|
|
111
|
-
if (
|
|
112
|
-
RETRYABLE_NETWORK_MESSAGE_PATTERNS.some((p) => p.test(error.message))
|
|
113
|
-
) {
|
|
111
|
+
if (RETRYABLE_NETWORK_MESSAGE_PATTERNS.some((p) => p.test(error.message))) {
|
|
114
112
|
return true;
|
|
115
113
|
}
|
|
116
114
|
|
|
@@ -1,16 +1,15 @@
|
|
|
1
1
|
import { existsSync, mkdirSync, readFileSync, writeFileSync } from "node:fs";
|
|
2
|
-
import { homedir } from "node:os";
|
|
3
2
|
import { join } from "node:path";
|
|
4
3
|
|
|
5
|
-
import {
|
|
4
|
+
import { getDeviceIdBaseDir } from "../../util/device-id.js";
|
|
6
5
|
import type { WorkspaceMigration } from "./types.js";
|
|
7
6
|
|
|
8
7
|
export const seedDeviceIdMigration: WorkspaceMigration = {
|
|
9
8
|
id: "003-seed-device-id",
|
|
10
9
|
description:
|
|
11
|
-
"Seed
|
|
10
|
+
"Seed device.json deviceId from the most recent lockfile installationId for continuity",
|
|
12
11
|
run(_workspaceDir: string): void {
|
|
13
|
-
const base =
|
|
12
|
+
const base = getDeviceIdBaseDir();
|
|
14
13
|
const vellumDir = join(base, ".vellum");
|
|
15
14
|
const devicePath = join(vellumDir, "device.json");
|
|
16
15
|
|
|
@@ -83,6 +83,11 @@ export const servicesConfigMigration: WorkspaceMigration = {
|
|
|
83
83
|
...existingServices,
|
|
84
84
|
};
|
|
85
85
|
|
|
86
|
+
// Legacy top-level fields (provider, model) are the user's actual
|
|
87
|
+
// configuration from before the services structure existed. If they're
|
|
88
|
+
// present as strings they take precedence over any `existingServices`
|
|
89
|
+
// values, which are just defaults written by backfillConfigDefaults().
|
|
90
|
+
// The spread preserves any extra keys that future backfills may add.
|
|
86
91
|
services.inference = {
|
|
87
92
|
...(existingServices.inference ?? {}),
|
|
88
93
|
mode: inferenceMode,
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import type { WorkspaceMigration } from "./types.js";
|
|
2
|
+
|
|
3
|
+
export const voiceTimeoutAndMaxStepsMigration: WorkspaceMigration = {
|
|
4
|
+
id: "008-voice-timeout-and-max-steps",
|
|
5
|
+
description:
|
|
6
|
+
"Add elevenlabs.conversationTimeoutSeconds and maxStepsPerSession to config schema (defaults handle new installs; macOS client syncs existing UserDefaults values on startup)",
|
|
7
|
+
run(_workspaceDir: string): void {
|
|
8
|
+
// No-op — schema defaults handle new installs.
|
|
9
|
+
// Existing users: macOS client will sync UserDefaults values
|
|
10
|
+
// to config on next startup via settings sync endpoints.
|
|
11
|
+
},
|
|
12
|
+
};
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import { rebuildConversationDiskViewFromDb } from "./rebuild-conversation-disk-view.js";
|
|
2
|
+
import type { WorkspaceMigration } from "./types.js";
|
|
3
|
+
|
|
4
|
+
export const backfillConversationDiskViewMigration: WorkspaceMigration = {
|
|
5
|
+
id: "009-backfill-conversation-disk-view",
|
|
6
|
+
description: "Rebuild conversation disk view for existing conversations",
|
|
7
|
+
run(_workspaceDir: string): void {
|
|
8
|
+
rebuildConversationDiskViewFromDb();
|
|
9
|
+
},
|
|
10
|
+
};
|
|
@@ -0,0 +1,223 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Workspace migration 010: Rename UUID-based app directories and files to
|
|
3
|
+
* human-readable slugified names.
|
|
4
|
+
*
|
|
5
|
+
* Inline slugify + dedup logic (not imported from app-store) so the migration
|
|
6
|
+
* remains stable even if runtime code changes in the future.
|
|
7
|
+
*
|
|
8
|
+
* Idempotent: safe to re-run after interruption at any point. Handles
|
|
9
|
+
* partially-renamed states (crash between JSON write and file rename).
|
|
10
|
+
*/
|
|
11
|
+
|
|
12
|
+
import { execSync } from "node:child_process";
|
|
13
|
+
import { randomUUID } from "node:crypto";
|
|
14
|
+
import {
|
|
15
|
+
existsSync,
|
|
16
|
+
mkdirSync,
|
|
17
|
+
readdirSync,
|
|
18
|
+
readFileSync,
|
|
19
|
+
renameSync,
|
|
20
|
+
unlinkSync,
|
|
21
|
+
writeFileSync,
|
|
22
|
+
} from "node:fs";
|
|
23
|
+
import { join } from "node:path";
|
|
24
|
+
|
|
25
|
+
import type { WorkspaceMigration } from "./types.js";
|
|
26
|
+
|
|
27
|
+
// ---------------------------------------------------------------------------
|
|
28
|
+
// Self-contained slug generation (do NOT import from app-store)
|
|
29
|
+
// ---------------------------------------------------------------------------
|
|
30
|
+
|
|
31
|
+
function slugify(name: string): string {
|
|
32
|
+
let slug = name
|
|
33
|
+
.toLowerCase()
|
|
34
|
+
.replace(/[^a-z0-9-]/g, "-")
|
|
35
|
+
.replace(/-{2,}/g, "-")
|
|
36
|
+
.replace(/^-+|-+$/g, "");
|
|
37
|
+
|
|
38
|
+
if (slug.length > 60) {
|
|
39
|
+
slug = slug.slice(0, 60).replace(/-+$/, "");
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
if (!slug) {
|
|
43
|
+
slug = `app-${randomUUID().slice(0, 8)}`;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
return slug;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
function generateUniqueDirName(name: string, usedNames: Set<string>): string {
|
|
50
|
+
const base = slugify(name);
|
|
51
|
+
if (!usedNames.has(base)) return base;
|
|
52
|
+
let counter = 2;
|
|
53
|
+
while (usedNames.has(`${base}-${counter}`)) {
|
|
54
|
+
counter++;
|
|
55
|
+
}
|
|
56
|
+
return `${base}-${counter}`;
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
/** Defense-in-depth: reject dirNames that could cause path traversal. */
|
|
60
|
+
function isValidDirName(dirName: string): boolean {
|
|
61
|
+
return (
|
|
62
|
+
!!dirName &&
|
|
63
|
+
!dirName.includes("/") &&
|
|
64
|
+
!dirName.includes("\\") &&
|
|
65
|
+
!dirName.includes("..") &&
|
|
66
|
+
dirName === dirName.trim()
|
|
67
|
+
);
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
// ---------------------------------------------------------------------------
|
|
71
|
+
// Migration
|
|
72
|
+
// ---------------------------------------------------------------------------
|
|
73
|
+
|
|
74
|
+
export const appDirRenameMigration: WorkspaceMigration = {
|
|
75
|
+
id: "010-app-dir-rename",
|
|
76
|
+
description:
|
|
77
|
+
"Rename UUID-based app directories and files to human-readable slugified names",
|
|
78
|
+
|
|
79
|
+
run(workspaceDir: string): void {
|
|
80
|
+
const appsDir = join(workspaceDir, "data", "apps");
|
|
81
|
+
if (!existsSync(appsDir)) return;
|
|
82
|
+
|
|
83
|
+
// Read all JSON files (sorted for deterministic ordering)
|
|
84
|
+
const jsonFiles = readdirSync(appsDir)
|
|
85
|
+
.filter((f) => f.endsWith(".json"))
|
|
86
|
+
.sort();
|
|
87
|
+
|
|
88
|
+
if (jsonFiles.length === 0) return;
|
|
89
|
+
|
|
90
|
+
const usedNames = new Set<string>();
|
|
91
|
+
|
|
92
|
+
for (const jsonFile of jsonFiles) {
|
|
93
|
+
const jsonPath = join(appsDir, jsonFile);
|
|
94
|
+
let raw: string;
|
|
95
|
+
try {
|
|
96
|
+
raw = readFileSync(jsonPath, "utf-8");
|
|
97
|
+
} catch {
|
|
98
|
+
continue; // skip unreadable files
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
let parsed: {
|
|
102
|
+
id?: string;
|
|
103
|
+
name?: string;
|
|
104
|
+
dirName?: string;
|
|
105
|
+
};
|
|
106
|
+
try {
|
|
107
|
+
parsed = JSON.parse(raw);
|
|
108
|
+
} catch {
|
|
109
|
+
continue; // skip malformed JSON
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
const appId = parsed.id;
|
|
113
|
+
const appName = parsed.name ?? "untitled";
|
|
114
|
+
if (!appId) continue;
|
|
115
|
+
|
|
116
|
+
// Check if already migrated: has dirName AND filesystem matches
|
|
117
|
+
if (parsed.dirName && isValidDirName(parsed.dirName)) {
|
|
118
|
+
const expectedJsonFile = `${parsed.dirName}.json`;
|
|
119
|
+
if (
|
|
120
|
+
jsonFile === expectedJsonFile &&
|
|
121
|
+
existsSync(join(appsDir, parsed.dirName))
|
|
122
|
+
) {
|
|
123
|
+
// Already fully migrated -- just track the name
|
|
124
|
+
usedNames.add(parsed.dirName);
|
|
125
|
+
continue;
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
// Partially renamed: JSON has dirName but files may still be at old paths.
|
|
129
|
+
// Use the dirName from JSON but rename from wherever the files actually are.
|
|
130
|
+
const dirName = parsed.dirName;
|
|
131
|
+
usedNames.add(dirName);
|
|
132
|
+
renameAppFiles(appsDir, jsonFile, appId, dirName, parsed, raw);
|
|
133
|
+
continue;
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
// No dirName yet -- generate one
|
|
137
|
+
const dirName = generateUniqueDirName(appName, usedNames);
|
|
138
|
+
if (!isValidDirName(dirName)) continue; // safety check
|
|
139
|
+
usedNames.add(dirName);
|
|
140
|
+
renameAppFiles(appsDir, jsonFile, appId, dirName, parsed, raw);
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
// Best-effort git commit
|
|
144
|
+
try {
|
|
145
|
+
const gitDir = join(appsDir, ".git");
|
|
146
|
+
if (existsSync(gitDir)) {
|
|
147
|
+
execSync(
|
|
148
|
+
"git add -A && git commit -m 'Migration 010: rename app dirs to slugified names' --allow-empty",
|
|
149
|
+
{
|
|
150
|
+
cwd: appsDir,
|
|
151
|
+
stdio: "ignore",
|
|
152
|
+
timeout: 10_000,
|
|
153
|
+
},
|
|
154
|
+
);
|
|
155
|
+
}
|
|
156
|
+
} catch {
|
|
157
|
+
// Git failure is non-fatal -- log nothing since we don't have
|
|
158
|
+
// the logger available in migrations. The next commitAppChange()
|
|
159
|
+
// call will pick up the renamed files naturally.
|
|
160
|
+
}
|
|
161
|
+
},
|
|
162
|
+
};
|
|
163
|
+
|
|
164
|
+
/**
|
|
165
|
+
* Rename app files from their current location to dirName-based paths.
|
|
166
|
+
* Each step checks existence to handle partial completion.
|
|
167
|
+
*/
|
|
168
|
+
function renameAppFiles(
|
|
169
|
+
appsDir: string,
|
|
170
|
+
currentJsonFile: string,
|
|
171
|
+
appId: string,
|
|
172
|
+
dirName: string,
|
|
173
|
+
parsed: Record<string, unknown>,
|
|
174
|
+
_rawJson: string,
|
|
175
|
+
): void {
|
|
176
|
+
const targetJsonFile = `${dirName}.json`;
|
|
177
|
+
const targetPreviewFile = `${dirName}.preview`;
|
|
178
|
+
|
|
179
|
+
// 1. Rename the app directory: {appId}/ -> {dirName}/
|
|
180
|
+
const oldDir = join(appsDir, appId);
|
|
181
|
+
const newDir = join(appsDir, dirName);
|
|
182
|
+
if (existsSync(oldDir) && !existsSync(newDir) && oldDir !== newDir) {
|
|
183
|
+
renameSync(oldDir, newDir);
|
|
184
|
+
} else if (!existsSync(newDir)) {
|
|
185
|
+
// Directory doesn't exist at either location -- create it
|
|
186
|
+
mkdirSync(newDir, { recursive: true });
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
// 2. Rename the preview file: {appId}.preview -> {dirName}.preview
|
|
190
|
+
const oldPreview = join(appsDir, `${appId}.preview`);
|
|
191
|
+
const newPreview = join(appsDir, targetPreviewFile);
|
|
192
|
+
if (
|
|
193
|
+
existsSync(oldPreview) &&
|
|
194
|
+
!existsSync(newPreview) &&
|
|
195
|
+
oldPreview !== newPreview
|
|
196
|
+
) {
|
|
197
|
+
renameSync(oldPreview, newPreview);
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
// 3. Rename the JSON file: {currentFilename} -> {dirName}.json
|
|
201
|
+
// Also update the dirName field in the JSON content.
|
|
202
|
+
const currentJsonPath = join(appsDir, currentJsonFile);
|
|
203
|
+
const targetJsonPath = join(appsDir, targetJsonFile);
|
|
204
|
+
|
|
205
|
+
// Update the JSON with dirName field
|
|
206
|
+
const updatedParsed = { ...parsed, dirName };
|
|
207
|
+
const updatedJson = JSON.stringify(updatedParsed, null, 2);
|
|
208
|
+
|
|
209
|
+
if (currentJsonFile !== targetJsonFile) {
|
|
210
|
+
// Write to new location, then remove old
|
|
211
|
+
writeFileSync(targetJsonPath, updatedJson, "utf-8");
|
|
212
|
+
if (existsSync(currentJsonPath) && currentJsonPath !== targetJsonPath) {
|
|
213
|
+
try {
|
|
214
|
+
unlinkSync(currentJsonPath);
|
|
215
|
+
} catch {
|
|
216
|
+
// Old file cleanup is best-effort
|
|
217
|
+
}
|
|
218
|
+
}
|
|
219
|
+
} else {
|
|
220
|
+
// Just update the content in place
|
|
221
|
+
writeFileSync(targetJsonPath, updatedJson, "utf-8");
|
|
222
|
+
}
|
|
223
|
+
}
|