@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/docs/skills.md
CHANGED
|
@@ -156,3 +156,103 @@ Trust rules are stored in `~/.vellum/protected/trust.json`. You can inspect this
|
|
|
156
156
|
### "A skill tool keeps prompting even though I approved it."
|
|
157
157
|
|
|
158
158
|
Check whether the rule has the correct `executionTarget` — a rule scoped to `sandbox` will not match a tool running on `host`.
|
|
159
|
+
|
|
160
|
+
## Inline Command Expansions
|
|
161
|
+
|
|
162
|
+
Skills can embed dynamic content by using the **inline command expansion** syntax. When a skill containing these tokens is loaded, each token is executed and replaced with its output before the skill body is delivered to the model. The syntax is shown in the fenced block below.
|
|
163
|
+
|
|
164
|
+
This syntax is intentionally compatible with the convention established by [inline skill commands](https://x.com) for portable cross-agent skill authoring. Vellum adopts the exact same token format so that externally authored skills load without rewriting — but applies stricter execution constraints.
|
|
165
|
+
|
|
166
|
+
### Syntax
|
|
167
|
+
|
|
168
|
+
The canonical syntax is:
|
|
169
|
+
|
|
170
|
+
```
|
|
171
|
+
!`command`
|
|
172
|
+
```
|
|
173
|
+
|
|
174
|
+
Where `command` is any shell command string. The exclamation mark immediately precedes the opening backtick with no whitespace in between. Examples:
|
|
175
|
+
|
|
176
|
+
```markdown
|
|
177
|
+
Current branch: !`git branch --show-current`
|
|
178
|
+
Recent changes: !`git log --oneline -5`
|
|
179
|
+
Project info: !`cat package.json | jq '.name, .version'`
|
|
180
|
+
```
|
|
181
|
+
|
|
182
|
+
Tokens inside fenced code blocks (` ``` ` or `~~~`) are **not** expanded — they are treated as documentation examples. This allows skills to safely include syntax examples without triggering execution.
|
|
183
|
+
|
|
184
|
+
### Parsing rules
|
|
185
|
+
|
|
186
|
+
The parser (`parseInlineCommandExpansions`) enforces fail-closed semantics:
|
|
187
|
+
|
|
188
|
+
| Condition | Behavior |
|
|
189
|
+
| ------------------------------------------------- | ---------------------- |
|
|
190
|
+
| Well-formed token outside fenced code | Parsed as an expansion |
|
|
191
|
+
| Token inside a fenced code block | Skipped (not expanded) |
|
|
192
|
+
| Empty command text (no content between backticks) | Rejected as malformed |
|
|
193
|
+
| Whitespace-only command text | Rejected as malformed |
|
|
194
|
+
| Unmatched opening (no closing backtick found) | Rejected as malformed |
|
|
195
|
+
| Nested backticks inside command text | Rejected as malformed |
|
|
196
|
+
|
|
197
|
+
Malformed tokens do not silently pass through — they are collected as errors and logged. If a skill body contains any malformed tokens, the valid tokens are still expanded, but the errors are reported for diagnostics.
|
|
198
|
+
|
|
199
|
+
### Feature flag
|
|
200
|
+
|
|
201
|
+
Inline command expansion is gated by the `inline-skill-commands` feature flag (key: `feature_flags.inline-skill-commands.enabled`). The flag defaults to **enabled**.
|
|
202
|
+
|
|
203
|
+
When the flag is disabled and a skill contains inline command expansion tokens, `skill_load` returns an error rather than delivering unexpanded tokens to the model. This fail-closed behavior prevents the LLM from seeing raw expansion tokens and attempting to interpret them.
|
|
204
|
+
|
|
205
|
+
### Approval model
|
|
206
|
+
|
|
207
|
+
Skills with inline command expansions use a separate permission namespace: `skill_load_dynamic:*`. This ensures they do not silently inherit the permissive default `skill_load:*` allow rule.
|
|
208
|
+
|
|
209
|
+
When a user is prompted to approve a dynamic skill load, the allowlist options are:
|
|
210
|
+
|
|
211
|
+
| Option | Pattern | Behavior |
|
|
212
|
+
| -------------- | ------------------------------------------- | --------------------------------------------------------------------------------------------------- |
|
|
213
|
+
| Version-pinned | `skill_load_dynamic:<id>@<transitive-hash>` | Approved for this exact version only. Any change to the skill or its includes invalidates the rule. |
|
|
214
|
+
| Any-version | `skill_load_dynamic:<id>` | Approved for all versions of this skill. |
|
|
215
|
+
|
|
216
|
+
The transitive hash covers the skill's own content plus all included skills, so a change anywhere in the dependency graph triggers re-approval for version-pinned rules.
|
|
217
|
+
|
|
218
|
+
### v1 execution limits
|
|
219
|
+
|
|
220
|
+
In the initial implementation, inline command execution enforces these constraints:
|
|
221
|
+
|
|
222
|
+
| Constraint | Value |
|
|
223
|
+
| ---------------- | ------------------------------------------------------- |
|
|
224
|
+
| Execution target | Sandbox only (no host fallback) |
|
|
225
|
+
| Network access | Off (no outbound connections) |
|
|
226
|
+
| Environment | Sanitized (no API keys, tokens, or credentials) |
|
|
227
|
+
| Timeout | 10 seconds per command |
|
|
228
|
+
| Output cap | 20,000 characters (truncated with `[output truncated]`) |
|
|
229
|
+
| Binary output | Rejected if >10% non-printable characters |
|
|
230
|
+
| ANSI sequences | Stripped before output processing |
|
|
231
|
+
| stderr | Discarded (only stdout is captured) |
|
|
232
|
+
|
|
233
|
+
Commands that fail (timeout, non-zero exit, spawn failure, binary output) produce a deterministic stub in the rendered body rather than leaking raw error output:
|
|
234
|
+
|
|
235
|
+
```
|
|
236
|
+
<inline_skill_command index="0">[inline command unavailable: command timed out]</inline_skill_command>
|
|
237
|
+
```
|
|
238
|
+
|
|
239
|
+
### Eligible skill sources
|
|
240
|
+
|
|
241
|
+
Only **bundled**, **managed**, and **workspace** skills may use inline command expansions. Third-party **extra** skill sources are explicitly rejected — `skill_load` returns an error if an extra-source skill contains inline expansion tokens.
|
|
242
|
+
|
|
243
|
+
| Source | Eligible | Reason |
|
|
244
|
+
| ----------- | -------- | -------------------------------------- |
|
|
245
|
+
| `bundled` | Yes | Shipped with the application, trusted |
|
|
246
|
+
| `managed` | Yes | User-installed, subject to approval |
|
|
247
|
+
| `workspace` | Yes | Project-local, subject to approval |
|
|
248
|
+
| `extra` | No | Third-party roots, out of scope for v1 |
|
|
249
|
+
|
|
250
|
+
### Fail-closed summary
|
|
251
|
+
|
|
252
|
+
The system fails closed at every layer:
|
|
253
|
+
|
|
254
|
+
1. **Flag off** — skill_load returns an error, tokens never reach the model.
|
|
255
|
+
2. **Malformed syntax** — rejected by the parser, logged as errors.
|
|
256
|
+
3. **Unsupported source** — skill_load returns an error for extra-source skills.
|
|
257
|
+
4. **Command failure** — deterministic stub replaces the token, no raw stderr.
|
|
258
|
+
5. **No permission** — `skill_load_dynamic:*` namespace requires explicit approval.
|
package/package.json
CHANGED
|
@@ -1629,4 +1629,115 @@ describe("AgentLoop", () => {
|
|
|
1629
1629
|
);
|
|
1630
1630
|
expect(textBlock!.text).toBe("Normal response with no placeholders.");
|
|
1631
1631
|
});
|
|
1632
|
+
|
|
1633
|
+
// Tool error retry nudge — when a tool returns isError: true, the loop
|
|
1634
|
+
// should inject a system_notice nudging the LLM to retry instead of ending.
|
|
1635
|
+
test("injects retry nudge system_notice when tool returns an error", async () => {
|
|
1636
|
+
const { provider, calls } = createMockProvider([
|
|
1637
|
+
// First turn: LLM calls a tool that errors
|
|
1638
|
+
toolUseResponse("t1", "read_file", { path: "/missing.txt" }),
|
|
1639
|
+
// Second turn: LLM retries after seeing the error + nudge
|
|
1640
|
+
toolUseResponse("t2", "read_file", { path: "/existing.txt" }),
|
|
1641
|
+
// Third turn: LLM responds with success
|
|
1642
|
+
textResponse("Got the file."),
|
|
1643
|
+
]);
|
|
1644
|
+
|
|
1645
|
+
let callCount = 0;
|
|
1646
|
+
const toolExecutor = async (
|
|
1647
|
+
_name: string,
|
|
1648
|
+
_input: Record<string, unknown>,
|
|
1649
|
+
) => {
|
|
1650
|
+
callCount++;
|
|
1651
|
+
if (callCount === 1) {
|
|
1652
|
+
return {
|
|
1653
|
+
content:
|
|
1654
|
+
'{"error":"name is required and must be a non-empty string"}',
|
|
1655
|
+
isError: true,
|
|
1656
|
+
};
|
|
1657
|
+
}
|
|
1658
|
+
return { content: "file contents", isError: false };
|
|
1659
|
+
};
|
|
1660
|
+
|
|
1661
|
+
const loop = new AgentLoop(
|
|
1662
|
+
provider,
|
|
1663
|
+
"system",
|
|
1664
|
+
{},
|
|
1665
|
+
dummyTools,
|
|
1666
|
+
toolExecutor,
|
|
1667
|
+
);
|
|
1668
|
+
await loop.run([userMessage], () => {});
|
|
1669
|
+
|
|
1670
|
+
// Provider should have been called 3 times (error -> retry -> final text)
|
|
1671
|
+
expect(calls).toHaveLength(3);
|
|
1672
|
+
|
|
1673
|
+
// The second call's messages should contain the retry nudge system_notice
|
|
1674
|
+
const secondCallMessages = calls[1].messages;
|
|
1675
|
+
const toolResultMessage = secondCallMessages[secondCallMessages.length - 1];
|
|
1676
|
+
expect(toolResultMessage.role).toBe("user");
|
|
1677
|
+
|
|
1678
|
+
const retryNudge = toolResultMessage.content.find(
|
|
1679
|
+
(b): b is Extract<ContentBlock, { type: "text" }> =>
|
|
1680
|
+
b.type === "text" && b.text.includes("looks recoverable"),
|
|
1681
|
+
);
|
|
1682
|
+
expect(retryNudge).toBeDefined();
|
|
1683
|
+
|
|
1684
|
+
// The third call should NOT have the retry nudge (successful tool result)
|
|
1685
|
+
const thirdCallMessages = calls[2].messages;
|
|
1686
|
+
const thirdToolResultMessage =
|
|
1687
|
+
thirdCallMessages[thirdCallMessages.length - 1];
|
|
1688
|
+
const noRetryNudge = thirdToolResultMessage.content.find(
|
|
1689
|
+
(b): b is Extract<ContentBlock, { type: "text" }> =>
|
|
1690
|
+
b.type === "text" && b.text.includes("looks recoverable"),
|
|
1691
|
+
);
|
|
1692
|
+
expect(noRetryNudge).toBeUndefined();
|
|
1693
|
+
});
|
|
1694
|
+
|
|
1695
|
+
// Retry nudge stops after MAX_CONSECUTIVE_ERROR_NUDGES (3) consecutive errors
|
|
1696
|
+
test("stops injecting retry nudge after 3 consecutive error turns", async () => {
|
|
1697
|
+
const { provider, calls } = createMockProvider([
|
|
1698
|
+
// 4 consecutive error turns, then final text
|
|
1699
|
+
toolUseResponse("t1", "read_file", { path: "/a" }),
|
|
1700
|
+
toolUseResponse("t2", "read_file", { path: "/b" }),
|
|
1701
|
+
toolUseResponse("t3", "read_file", { path: "/c" }),
|
|
1702
|
+
toolUseResponse("t4", "read_file", { path: "/d" }),
|
|
1703
|
+
textResponse("Giving up."),
|
|
1704
|
+
]);
|
|
1705
|
+
|
|
1706
|
+
const toolExecutor = async (
|
|
1707
|
+
_name: string,
|
|
1708
|
+
_input: Record<string, unknown>,
|
|
1709
|
+
) => {
|
|
1710
|
+
return { content: "service unavailable", isError: true };
|
|
1711
|
+
};
|
|
1712
|
+
|
|
1713
|
+
const loop = new AgentLoop(
|
|
1714
|
+
provider,
|
|
1715
|
+
"system",
|
|
1716
|
+
{},
|
|
1717
|
+
dummyTools,
|
|
1718
|
+
toolExecutor,
|
|
1719
|
+
);
|
|
1720
|
+
await loop.run([userMessage], () => {});
|
|
1721
|
+
|
|
1722
|
+
expect(calls).toHaveLength(5);
|
|
1723
|
+
|
|
1724
|
+
// Helper to check if a call's last user message has the retry nudge
|
|
1725
|
+
const hasRetryNudge = (callIndex: number): boolean => {
|
|
1726
|
+
const msgs = calls[callIndex].messages;
|
|
1727
|
+
const lastMsg = msgs[msgs.length - 1];
|
|
1728
|
+
return lastMsg.content.some(
|
|
1729
|
+
(b) =>
|
|
1730
|
+
b.type === "text" &&
|
|
1731
|
+
"text" in b &&
|
|
1732
|
+
(b as { text: string }).text.includes("looks recoverable"),
|
|
1733
|
+
);
|
|
1734
|
+
};
|
|
1735
|
+
|
|
1736
|
+
// Turns 1-3 should have the nudge
|
|
1737
|
+
expect(hasRetryNudge(1)).toBe(true);
|
|
1738
|
+
expect(hasRetryNudge(2)).toBe(true);
|
|
1739
|
+
expect(hasRetryNudge(3)).toBe(true);
|
|
1740
|
+
// Turn 4 should NOT have the nudge (exceeded limit)
|
|
1741
|
+
expect(hasRetryNudge(4)).toBe(false);
|
|
1742
|
+
});
|
|
1632
1743
|
});
|
|
@@ -2,8 +2,8 @@
|
|
|
2
2
|
* Guard test: always-loaded tool count
|
|
3
3
|
*
|
|
4
4
|
* This test asserts the exact set of tools that are active when no client is
|
|
5
|
-
* connected, no host proxy is available, no
|
|
6
|
-
*
|
|
5
|
+
* connected, no host proxy is available, and no special channel capabilities
|
|
6
|
+
* exist. This represents the minimal "always-loaded"
|
|
7
7
|
* baseline that is sent to the LLM on every turn.
|
|
8
8
|
*
|
|
9
9
|
* Adding a tool to this set increases token cost for every request. If this test
|
|
@@ -32,14 +32,13 @@ describe("always-loaded tool count", () => {
|
|
|
32
32
|
await initializeTools();
|
|
33
33
|
const allDefs = buildToolDefinitions();
|
|
34
34
|
|
|
35
|
-
// Minimal context: no client, no
|
|
35
|
+
// Minimal context: no client, no capabilities
|
|
36
36
|
const minimalContext: SkillProjectionContext = {
|
|
37
37
|
skillProjectionState: new Map(),
|
|
38
38
|
skillProjectionCache: {},
|
|
39
39
|
coreToolNames: new Set(),
|
|
40
40
|
toolsDisabledDepth: 0,
|
|
41
41
|
hasNoClient: true,
|
|
42
|
-
hasAttachments: false,
|
|
43
42
|
channelCapabilities: undefined,
|
|
44
43
|
};
|
|
45
44
|
|
|
@@ -2,7 +2,6 @@ import { describe, expect, mock, test } from "bun:test";
|
|
|
2
2
|
|
|
3
3
|
import type { AppDefinition } from "../memory/app-store.js";
|
|
4
4
|
import type { AppStore } from "../tools/apps/executors.js";
|
|
5
|
-
import type { EditEngineResult } from "../tools/shared/filesystem/edit-engine.js";
|
|
6
5
|
import type { ToolContext } from "../tools/types.js";
|
|
7
6
|
|
|
8
7
|
// ---------------------------------------------------------------------------
|
|
@@ -26,24 +25,11 @@ function makeMockStore(overrides: Partial<AppStore> = {}): AppStore {
|
|
|
26
25
|
return {
|
|
27
26
|
getApp: () => makeApp(),
|
|
28
27
|
listApps: () => [makeApp()],
|
|
29
|
-
queryAppRecords: () => [],
|
|
30
|
-
listAppFiles: () => ["index.html"],
|
|
31
|
-
readAppFile: () => "<h1>Hi</h1>",
|
|
32
28
|
createApp: (params) =>
|
|
33
29
|
makeApp({ name: params.name, description: params.description }),
|
|
34
30
|
updateApp: (id, updates) => makeApp({ id, ...updates }),
|
|
35
31
|
deleteApp: () => {},
|
|
36
32
|
writeAppFile: () => {},
|
|
37
|
-
editAppFile: () =>
|
|
38
|
-
({
|
|
39
|
-
ok: true,
|
|
40
|
-
updatedContent: "new",
|
|
41
|
-
matchCount: 1,
|
|
42
|
-
matchMethod: "exact",
|
|
43
|
-
similarity: 1,
|
|
44
|
-
actualOld: "old",
|
|
45
|
-
actualNew: "new",
|
|
46
|
-
}) as EditEngineResult,
|
|
47
33
|
...overrides,
|
|
48
34
|
};
|
|
49
35
|
}
|
|
@@ -66,6 +52,11 @@ const mockStore = makeMockStore();
|
|
|
66
52
|
mock.module("../memory/app-store.js", () => ({
|
|
67
53
|
...mockStore,
|
|
68
54
|
getAppsDir: () => "/tmp/test-apps",
|
|
55
|
+
getAppDirPath: (appId: string) => `/tmp/test-apps/${appId}`,
|
|
56
|
+
resolveAppDir: (id: string) => ({
|
|
57
|
+
dirName: id,
|
|
58
|
+
appDir: `/tmp/test-apps/${id}`,
|
|
59
|
+
}),
|
|
69
60
|
isMultifileApp: (app: AppDefinition) => app.formatVersion === 2,
|
|
70
61
|
}));
|
|
71
62
|
|
|
@@ -85,13 +76,7 @@ mock.module("../bundler/app-compiler.js", () => ({
|
|
|
85
76
|
|
|
86
77
|
import * as appCreateScript from "../config/bundled-skills/app-builder/tools/app-create.js";
|
|
87
78
|
import * as appDeleteScript from "../config/bundled-skills/app-builder/tools/app-delete.js";
|
|
88
|
-
import * as
|
|
89
|
-
import * as appFileListScript from "../config/bundled-skills/app-builder/tools/app-file-list.js";
|
|
90
|
-
import * as appFileReadScript from "../config/bundled-skills/app-builder/tools/app-file-read.js";
|
|
91
|
-
import * as appFileWriteScript from "../config/bundled-skills/app-builder/tools/app-file-write.js";
|
|
92
|
-
import * as appListScript from "../config/bundled-skills/app-builder/tools/app-list.js";
|
|
93
|
-
import * as appQueryScript from "../config/bundled-skills/app-builder/tools/app-query.js";
|
|
94
|
-
import * as appUpdateScript from "../config/bundled-skills/app-builder/tools/app-update.js";
|
|
79
|
+
import * as appRefreshScript from "../config/bundled-skills/app-builder/tools/app-refresh.js";
|
|
95
80
|
|
|
96
81
|
// ---------------------------------------------------------------------------
|
|
97
82
|
// Tests
|
|
@@ -142,58 +127,6 @@ describe("app-builder skill tool scripts", () => {
|
|
|
142
127
|
});
|
|
143
128
|
});
|
|
144
129
|
|
|
145
|
-
// ---- app-list ----------------------------------------------------------
|
|
146
|
-
|
|
147
|
-
describe("app-list", () => {
|
|
148
|
-
test("exports a run function", () => {
|
|
149
|
-
expect(typeof appListScript.run).toBe("function");
|
|
150
|
-
});
|
|
151
|
-
|
|
152
|
-
test("delegates to executeAppList and returns result", async () => {
|
|
153
|
-
const result = await appListScript.run({}, makeContext());
|
|
154
|
-
expect(result.isError).toBe(false);
|
|
155
|
-
const parsed = JSON.parse(result.content);
|
|
156
|
-
expect(Array.isArray(parsed)).toBe(true);
|
|
157
|
-
expect(parsed.length).toBeGreaterThan(0);
|
|
158
|
-
expect(parsed[0].id).toBe("app-1");
|
|
159
|
-
});
|
|
160
|
-
});
|
|
161
|
-
|
|
162
|
-
// ---- app-query ---------------------------------------------------------
|
|
163
|
-
|
|
164
|
-
describe("app-query", () => {
|
|
165
|
-
test("exports a run function", () => {
|
|
166
|
-
expect(typeof appQueryScript.run).toBe("function");
|
|
167
|
-
});
|
|
168
|
-
|
|
169
|
-
test("delegates to executeAppQuery and returns result", async () => {
|
|
170
|
-
const result = await appQueryScript.run(
|
|
171
|
-
{ app_id: "app-1" },
|
|
172
|
-
makeContext(),
|
|
173
|
-
);
|
|
174
|
-
expect(result.isError).toBe(false);
|
|
175
|
-
expect(JSON.parse(result.content)).toEqual([]);
|
|
176
|
-
});
|
|
177
|
-
});
|
|
178
|
-
|
|
179
|
-
// ---- app-update --------------------------------------------------------
|
|
180
|
-
|
|
181
|
-
describe("app-update", () => {
|
|
182
|
-
test("exports a run function", () => {
|
|
183
|
-
expect(typeof appUpdateScript.run).toBe("function");
|
|
184
|
-
});
|
|
185
|
-
|
|
186
|
-
test("delegates to executeAppUpdate and returns result", async () => {
|
|
187
|
-
const result = await appUpdateScript.run(
|
|
188
|
-
{ app_id: "app-1", name: "Updated" },
|
|
189
|
-
makeContext(),
|
|
190
|
-
);
|
|
191
|
-
expect(result.isError).toBe(false);
|
|
192
|
-
const parsed = JSON.parse(result.content);
|
|
193
|
-
expect(parsed.name).toBe("Updated");
|
|
194
|
-
});
|
|
195
|
-
});
|
|
196
|
-
|
|
197
130
|
// ---- app-delete --------------------------------------------------------
|
|
198
131
|
|
|
199
132
|
describe("app-delete", () => {
|
|
@@ -213,93 +146,22 @@ describe("app-builder skill tool scripts", () => {
|
|
|
213
146
|
});
|
|
214
147
|
});
|
|
215
148
|
|
|
216
|
-
// ---- app-
|
|
149
|
+
// ---- app-refresh -------------------------------------------------------
|
|
217
150
|
|
|
218
|
-
describe("app-
|
|
151
|
+
describe("app-refresh", () => {
|
|
219
152
|
test("exports a run function", () => {
|
|
220
|
-
expect(typeof
|
|
153
|
+
expect(typeof appRefreshScript.run).toBe("function");
|
|
221
154
|
});
|
|
222
155
|
|
|
223
|
-
test("delegates to
|
|
224
|
-
const result = await
|
|
156
|
+
test("delegates to executeAppRefresh and returns result", async () => {
|
|
157
|
+
const result = await appRefreshScript.run(
|
|
225
158
|
{ app_id: "app-1" },
|
|
226
159
|
makeContext(),
|
|
227
160
|
);
|
|
228
161
|
expect(result.isError).toBe(false);
|
|
229
|
-
expect(JSON.parse(result.content)).toEqual(["index.html"]);
|
|
230
|
-
});
|
|
231
|
-
});
|
|
232
|
-
|
|
233
|
-
// ---- app-file-read -----------------------------------------------------
|
|
234
|
-
|
|
235
|
-
describe("app-file-read", () => {
|
|
236
|
-
test("exports a run function", () => {
|
|
237
|
-
expect(typeof appFileReadScript.run).toBe("function");
|
|
238
|
-
});
|
|
239
|
-
|
|
240
|
-
test("delegates to executeAppFileRead and returns formatted content", async () => {
|
|
241
|
-
const result = await appFileReadScript.run(
|
|
242
|
-
{ app_id: "app-1", path: "index.html" },
|
|
243
|
-
makeContext(),
|
|
244
|
-
);
|
|
245
|
-
expect(result.isError).toBe(false);
|
|
246
|
-
// Content should include line numbers
|
|
247
|
-
expect(result.content).toContain("1\t");
|
|
248
|
-
});
|
|
249
|
-
});
|
|
250
|
-
|
|
251
|
-
// ---- app-file-edit -----------------------------------------------------
|
|
252
|
-
|
|
253
|
-
describe("app-file-edit", () => {
|
|
254
|
-
test("exports a run function", () => {
|
|
255
|
-
expect(typeof appFileEditScript.run).toBe("function");
|
|
256
|
-
});
|
|
257
|
-
|
|
258
|
-
test("delegates to executeAppFileEdit and returns result", async () => {
|
|
259
|
-
const result = await appFileEditScript.run(
|
|
260
|
-
{
|
|
261
|
-
app_id: "app-1",
|
|
262
|
-
path: "index.html",
|
|
263
|
-
old_string: "old",
|
|
264
|
-
new_string: "new",
|
|
265
|
-
},
|
|
266
|
-
makeContext(),
|
|
267
|
-
);
|
|
268
|
-
expect(result.isError).toBe(false);
|
|
269
162
|
const parsed = JSON.parse(result.content);
|
|
270
|
-
expect(parsed.
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
test("returns error when old_string is empty", async () => {
|
|
274
|
-
const result = await appFileEditScript.run(
|
|
275
|
-
{
|
|
276
|
-
app_id: "app-1",
|
|
277
|
-
path: "index.html",
|
|
278
|
-
old_string: "",
|
|
279
|
-
new_string: "new",
|
|
280
|
-
},
|
|
281
|
-
makeContext(),
|
|
282
|
-
);
|
|
283
|
-
expect(result.isError).toBe(true);
|
|
284
|
-
});
|
|
285
|
-
});
|
|
286
|
-
|
|
287
|
-
// ---- app-file-write ----------------------------------------------------
|
|
288
|
-
|
|
289
|
-
describe("app-file-write", () => {
|
|
290
|
-
test("exports a run function", () => {
|
|
291
|
-
expect(typeof appFileWriteScript.run).toBe("function");
|
|
292
|
-
});
|
|
293
|
-
|
|
294
|
-
test("delegates to executeAppFileWrite and returns result", async () => {
|
|
295
|
-
const result = await appFileWriteScript.run(
|
|
296
|
-
{ app_id: "app-1", path: "new.html", content: "<div/>" },
|
|
297
|
-
makeContext(),
|
|
298
|
-
);
|
|
299
|
-
expect(result.isError).toBe(false);
|
|
300
|
-
const parsed = JSON.parse(result.content);
|
|
301
|
-
expect(parsed.written).toBe(true);
|
|
302
|
-
expect(parsed.path).toBe("new.html");
|
|
163
|
+
expect(parsed.refreshed).toBe(true);
|
|
164
|
+
expect(parsed.appId).toBe("app-1");
|
|
303
165
|
});
|
|
304
166
|
});
|
|
305
167
|
});
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
import { execSync } from "node:child_process";
|
|
2
|
+
import { describe, expect, test } from "bun:test";
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Guard test: files outside app-store.ts must not import getAppsDir and use
|
|
6
|
+
* it to construct paths with an app ID. All app path construction must go
|
|
7
|
+
* through getAppDirPath() or resolveAppDir() from app-store.ts.
|
|
8
|
+
*
|
|
9
|
+
* This prevents regressions where new code bypasses the dirName-based path
|
|
10
|
+
* resolution and constructs UUID-based paths directly.
|
|
11
|
+
*
|
|
12
|
+
* Allowlist: only app-store.ts itself, app-git-service.ts (uses getAppsDir
|
|
13
|
+
* for the git repo root, not for per-app paths), and workspace migrations
|
|
14
|
+
* (self-contained, don't import from app-store).
|
|
15
|
+
*/
|
|
16
|
+
|
|
17
|
+
/** Files that are permitted to import getAppsDir. */
|
|
18
|
+
const ALLOWLIST = new Set([
|
|
19
|
+
"assistant/src/memory/app-store.ts", // defines getAppsDir
|
|
20
|
+
"assistant/src/memory/app-git-service.ts", // uses getAppsDir for git repo root, not per-app paths
|
|
21
|
+
]);
|
|
22
|
+
|
|
23
|
+
function isTestFile(filePath: string): boolean {
|
|
24
|
+
return (
|
|
25
|
+
filePath.includes("/__tests__/") ||
|
|
26
|
+
filePath.endsWith(".test.ts") ||
|
|
27
|
+
filePath.endsWith(".test.js") ||
|
|
28
|
+
filePath.endsWith(".spec.ts")
|
|
29
|
+
);
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
function isMigrationFile(filePath: string): boolean {
|
|
33
|
+
return filePath.includes("/workspace/migrations/");
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
describe("app directory path construction guard", () => {
|
|
37
|
+
test("no non-allowlisted production files import getAppsDir", () => {
|
|
38
|
+
// Search for files that import getAppsDir (not just mention it in comments)
|
|
39
|
+
const pattern = "import.*getAppsDir.*from|getAppsDir\\(\\)";
|
|
40
|
+
|
|
41
|
+
let grepOutput = "";
|
|
42
|
+
try {
|
|
43
|
+
grepOutput = execSync(`git grep -lE '${pattern}' -- '*.ts'`, {
|
|
44
|
+
encoding: "utf-8",
|
|
45
|
+
cwd: process.cwd() + "/..",
|
|
46
|
+
}).trim();
|
|
47
|
+
} catch (err) {
|
|
48
|
+
// Exit code 1 means no matches
|
|
49
|
+
if ((err as { status?: number }).status === 1) {
|
|
50
|
+
return;
|
|
51
|
+
}
|
|
52
|
+
throw err;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
const files = grepOutput.split("\n").filter((f) => f.length > 0);
|
|
56
|
+
const violations = files.filter((f) => {
|
|
57
|
+
if (isTestFile(f)) return false;
|
|
58
|
+
if (isMigrationFile(f)) return false;
|
|
59
|
+
if (ALLOWLIST.has(f)) return false;
|
|
60
|
+
return true;
|
|
61
|
+
});
|
|
62
|
+
|
|
63
|
+
if (violations.length > 0) {
|
|
64
|
+
const message = [
|
|
65
|
+
"Found non-allowlisted production files importing or using getAppsDir().",
|
|
66
|
+
"Use getAppDirPath(appId) or resolveAppDir(appId) from app-store.ts instead.",
|
|
67
|
+
"",
|
|
68
|
+
"Violations:",
|
|
69
|
+
...violations.map((f) => ` - ${f}`),
|
|
70
|
+
"",
|
|
71
|
+
"To fix: replace getAppsDir() + appId path construction with getAppDirPath(appId).",
|
|
72
|
+
"If this is an intentional exception, add it to the ALLOWLIST in app-dir-path-guard.test.ts.",
|
|
73
|
+
].join("\n");
|
|
74
|
+
|
|
75
|
+
expect(violations, message).toEqual([]);
|
|
76
|
+
}
|
|
77
|
+
});
|
|
78
|
+
});
|