@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
|
@@ -0,0 +1,598 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Tests for inline command expansion rendering during skill_load.
|
|
3
|
+
*
|
|
4
|
+
* Validates that:
|
|
5
|
+
* - Root skills with `!\`command\`` tokens get those tokens expanded exactly
|
|
6
|
+
* once at skill_load time, wrapped in <inline_skill_command> XML tags.
|
|
7
|
+
* - When the feature flag is off, skill_load returns an error instead of
|
|
8
|
+
* leaving unresolved live tokens in the prompt.
|
|
9
|
+
* - When the skill source is "extra", skill_load rejects with a specific error.
|
|
10
|
+
* - Render failures produce stable inline stubs rather than raw stderr.
|
|
11
|
+
*/
|
|
12
|
+
|
|
13
|
+
import {
|
|
14
|
+
existsSync,
|
|
15
|
+
mkdirSync,
|
|
16
|
+
mkdtempSync,
|
|
17
|
+
rmSync,
|
|
18
|
+
writeFileSync,
|
|
19
|
+
} from "node:fs";
|
|
20
|
+
import { tmpdir } from "node:os";
|
|
21
|
+
import { join } from "node:path";
|
|
22
|
+
import { afterEach, beforeEach, describe, expect, mock, test } from "bun:test";
|
|
23
|
+
|
|
24
|
+
// ── Test directory ────────────────────────────────────────────────────────────
|
|
25
|
+
|
|
26
|
+
const TEST_DIR = mkdtempSync(
|
|
27
|
+
join(tmpdir(), "vellum-skill-load-inline-cmd-test-"),
|
|
28
|
+
);
|
|
29
|
+
|
|
30
|
+
// ── Mocks (must be declared before any imports from the project) ─────────────
|
|
31
|
+
|
|
32
|
+
const platformOverrides: Record<string, (...args: unknown[]) => unknown> = {
|
|
33
|
+
getRootDir: () => TEST_DIR,
|
|
34
|
+
getDataDir: () => join(TEST_DIR, "data"),
|
|
35
|
+
ensureDataDir: () => {},
|
|
36
|
+
getPidPath: () => join(TEST_DIR, "vellum.pid"),
|
|
37
|
+
getDbPath: () => join(TEST_DIR, "data", "assistant.db"),
|
|
38
|
+
getLogPath: () => join(TEST_DIR, "logs", "vellum.log"),
|
|
39
|
+
getWorkspaceDir: () => join(TEST_DIR, "workspace"),
|
|
40
|
+
getWorkspaceSkillsDir: () => join(TEST_DIR, "skills"),
|
|
41
|
+
getWorkspaceConfigPath: () => join(TEST_DIR, "workspace", "config.json"),
|
|
42
|
+
getWorkspaceHooksDir: () => join(TEST_DIR, "workspace", "hooks"),
|
|
43
|
+
getWorkspacePromptPath: (f: unknown) =>
|
|
44
|
+
join(TEST_DIR, "workspace", String(f)),
|
|
45
|
+
getInterfacesDir: () => join(TEST_DIR, "interfaces"),
|
|
46
|
+
getHooksDir: () => join(TEST_DIR, "hooks"),
|
|
47
|
+
getSandboxRootDir: () => join(TEST_DIR, "sandbox"),
|
|
48
|
+
getSandboxWorkingDir: () => join(TEST_DIR, "sandbox", "work"),
|
|
49
|
+
getHistoryPath: () => join(TEST_DIR, "history"),
|
|
50
|
+
getSessionTokenPath: () => join(TEST_DIR, "session-token"),
|
|
51
|
+
readSessionToken: () => null,
|
|
52
|
+
getClipboardCommand: () => null,
|
|
53
|
+
readLockfile: () => null,
|
|
54
|
+
normalizeAssistantId: (id: unknown) => String(id),
|
|
55
|
+
writeLockfile: () => {},
|
|
56
|
+
getEmbeddingModelsDir: () => join(TEST_DIR, "embedding-models"),
|
|
57
|
+
getTCPPort: () => 8765,
|
|
58
|
+
isTCPEnabled: () => false,
|
|
59
|
+
getTCPHost: () => "127.0.0.1",
|
|
60
|
+
isIOSPairingEnabled: () => false,
|
|
61
|
+
getPlatformTokenPath: () => join(TEST_DIR, "platform-token"),
|
|
62
|
+
readPlatformToken: () => null,
|
|
63
|
+
isMacOS: () => process.platform === "darwin",
|
|
64
|
+
isLinux: () => process.platform === "linux",
|
|
65
|
+
isWindows: () => process.platform === "win32",
|
|
66
|
+
getPlatformName: () => process.platform,
|
|
67
|
+
getWorkspaceDirDisplay: () => "~/.vellum/workspace",
|
|
68
|
+
getConversationsDir: () => join(TEST_DIR, "conversations"),
|
|
69
|
+
};
|
|
70
|
+
mock.module("../util/platform.js", () => platformOverrides);
|
|
71
|
+
|
|
72
|
+
mock.module("../util/logger.js", () => ({
|
|
73
|
+
getLogger: () =>
|
|
74
|
+
new Proxy({} as Record<string, unknown>, {
|
|
75
|
+
get: () => () => {},
|
|
76
|
+
}),
|
|
77
|
+
truncateForLog: (s: unknown) => String(s),
|
|
78
|
+
}));
|
|
79
|
+
|
|
80
|
+
// Track inline command runner calls
|
|
81
|
+
interface RunInlineCommandCall {
|
|
82
|
+
command: string;
|
|
83
|
+
workingDir: string;
|
|
84
|
+
}
|
|
85
|
+
const runInlineCommandCalls: RunInlineCommandCall[] = [];
|
|
86
|
+
|
|
87
|
+
/** Return type matching InlineCommandResult from the runner module. */
|
|
88
|
+
interface MockInlineCommandResult {
|
|
89
|
+
output: string;
|
|
90
|
+
ok: boolean;
|
|
91
|
+
failureReason?:
|
|
92
|
+
| "timeout"
|
|
93
|
+
| "non_zero_exit"
|
|
94
|
+
| "binary_output"
|
|
95
|
+
| "spawn_failure";
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
type MockRunFn = (
|
|
99
|
+
command: string,
|
|
100
|
+
workingDir: string,
|
|
101
|
+
) => Promise<MockInlineCommandResult>;
|
|
102
|
+
|
|
103
|
+
// Default mock: commands succeed with their command string echoed
|
|
104
|
+
let mockRunInlineCommand = mock<MockRunFn>(
|
|
105
|
+
(command: string, workingDir: string) => {
|
|
106
|
+
runInlineCommandCalls.push({ command, workingDir });
|
|
107
|
+
return Promise.resolve({
|
|
108
|
+
output: `result of: ${command}`,
|
|
109
|
+
ok: true,
|
|
110
|
+
});
|
|
111
|
+
},
|
|
112
|
+
);
|
|
113
|
+
|
|
114
|
+
mock.module("../skills/inline-command-runner.js", () => ({
|
|
115
|
+
runInlineCommand: (command: string, workingDir: string, _options?: unknown) =>
|
|
116
|
+
mockRunInlineCommand(command, workingDir),
|
|
117
|
+
}));
|
|
118
|
+
|
|
119
|
+
// Mock autoInstallFromCatalog
|
|
120
|
+
const mockAutoInstall = mock((_skillId: string) => Promise.resolve(false));
|
|
121
|
+
mock.module("../skills/catalog-install.js", () => ({
|
|
122
|
+
autoInstallFromCatalog: (skillId: string) => mockAutoInstall(skillId),
|
|
123
|
+
resolveCatalog: (_skillId?: string) => Promise.resolve([]),
|
|
124
|
+
}));
|
|
125
|
+
|
|
126
|
+
interface TestConfig {
|
|
127
|
+
permissions: { mode: "strict" | "workspace" };
|
|
128
|
+
skills: { load: { extraDirs: string[] } };
|
|
129
|
+
sandbox: { enabled: boolean };
|
|
130
|
+
assistantFeatureFlagValues?: Record<string, boolean>;
|
|
131
|
+
[key: string]: unknown;
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
const testConfig: TestConfig = {
|
|
135
|
+
permissions: { mode: "workspace" },
|
|
136
|
+
skills: { load: { extraDirs: [] } },
|
|
137
|
+
sandbox: { enabled: true },
|
|
138
|
+
assistantFeatureFlagValues: {
|
|
139
|
+
"feature_flags.inline-skill-commands.enabled": true,
|
|
140
|
+
},
|
|
141
|
+
};
|
|
142
|
+
|
|
143
|
+
mock.module("../config/loader.js", () => ({
|
|
144
|
+
getConfig: () => testConfig,
|
|
145
|
+
loadConfig: () => testConfig,
|
|
146
|
+
invalidateConfigCache: () => {},
|
|
147
|
+
saveConfig: () => {},
|
|
148
|
+
loadRawConfig: () => ({}),
|
|
149
|
+
saveRawConfig: () => {},
|
|
150
|
+
getNestedValue: () => undefined,
|
|
151
|
+
setNestedValue: () => {},
|
|
152
|
+
}));
|
|
153
|
+
|
|
154
|
+
// ── Imports (after mocks) ─────────────────────────────────────────────────
|
|
155
|
+
|
|
156
|
+
await import("../tools/skills/load.js");
|
|
157
|
+
const { getTool } = await import("../tools/registry.js");
|
|
158
|
+
|
|
159
|
+
// ── Helpers ───────────────────────────────────────────────────────────────
|
|
160
|
+
|
|
161
|
+
function writeSkill(
|
|
162
|
+
skillId: string,
|
|
163
|
+
name: string,
|
|
164
|
+
description: string,
|
|
165
|
+
body: string,
|
|
166
|
+
): void {
|
|
167
|
+
const skillDir = join(TEST_DIR, "skills", skillId);
|
|
168
|
+
mkdirSync(skillDir, { recursive: true });
|
|
169
|
+
writeFileSync(
|
|
170
|
+
join(skillDir, "SKILL.md"),
|
|
171
|
+
`---\nname: "${name}"\ndescription: "${description}"\n---\n\n${body}\n`,
|
|
172
|
+
);
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
async function executeSkillLoad(
|
|
176
|
+
input: Record<string, unknown>,
|
|
177
|
+
workingDir = "/tmp",
|
|
178
|
+
): Promise<{ content: string; isError: boolean }> {
|
|
179
|
+
const tool = getTool("skill_load");
|
|
180
|
+
if (!tool) throw new Error("skill_load tool was not registered");
|
|
181
|
+
|
|
182
|
+
const result = await tool.execute(input, {
|
|
183
|
+
workingDir,
|
|
184
|
+
conversationId: "conversation-1",
|
|
185
|
+
trustClass: "guardian",
|
|
186
|
+
});
|
|
187
|
+
return { content: result.content, isError: result.isError };
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
// ── Tests ─────────────────────────────────────────────────────────────────
|
|
191
|
+
|
|
192
|
+
describe("skill_load inline command expansion", () => {
|
|
193
|
+
beforeEach(() => {
|
|
194
|
+
mkdirSync(join(TEST_DIR, "skills"), { recursive: true });
|
|
195
|
+
runInlineCommandCalls.length = 0;
|
|
196
|
+
mockAutoInstall.mockReset();
|
|
197
|
+
mockAutoInstall.mockImplementation(() => Promise.resolve(false));
|
|
198
|
+
|
|
199
|
+
// Reset to default: commands succeed
|
|
200
|
+
mockRunInlineCommand = mock<MockRunFn>(
|
|
201
|
+
(command: string, workingDir: string) => {
|
|
202
|
+
runInlineCommandCalls.push({ command, workingDir });
|
|
203
|
+
return Promise.resolve({
|
|
204
|
+
output: `result of: ${command}`,
|
|
205
|
+
ok: true,
|
|
206
|
+
});
|
|
207
|
+
},
|
|
208
|
+
);
|
|
209
|
+
mock.module("../skills/inline-command-runner.js", () => ({
|
|
210
|
+
runInlineCommand: (
|
|
211
|
+
command: string,
|
|
212
|
+
workingDir: string,
|
|
213
|
+
_options?: unknown,
|
|
214
|
+
) => mockRunInlineCommand(command, workingDir),
|
|
215
|
+
}));
|
|
216
|
+
|
|
217
|
+
// Enable the feature flag
|
|
218
|
+
testConfig.assistantFeatureFlagValues = {
|
|
219
|
+
"feature_flags.inline-skill-commands.enabled": true,
|
|
220
|
+
};
|
|
221
|
+
testConfig.skills = { load: { extraDirs: [] } };
|
|
222
|
+
});
|
|
223
|
+
|
|
224
|
+
afterEach(() => {
|
|
225
|
+
if (existsSync(TEST_DIR)) {
|
|
226
|
+
rmSync(TEST_DIR, { recursive: true, force: true });
|
|
227
|
+
}
|
|
228
|
+
});
|
|
229
|
+
|
|
230
|
+
// ── Basic expansion ──────────────────────────────────────────────────
|
|
231
|
+
|
|
232
|
+
describe("basic expansion", () => {
|
|
233
|
+
test("expands a single inline command token in a root skill", async () => {
|
|
234
|
+
writeSkill(
|
|
235
|
+
"dynamic-skill",
|
|
236
|
+
"Dynamic Skill",
|
|
237
|
+
"A skill with inline commands",
|
|
238
|
+
'Current date: !`echo "2024-01-01"`',
|
|
239
|
+
);
|
|
240
|
+
|
|
241
|
+
const result = await executeSkillLoad({ skill: "dynamic-skill" });
|
|
242
|
+
expect(result.isError).toBe(false);
|
|
243
|
+
expect(result.content).toContain(
|
|
244
|
+
'<inline_skill_command index="0">result of: echo "2024-01-01"</inline_skill_command>',
|
|
245
|
+
);
|
|
246
|
+
// The original token should be replaced
|
|
247
|
+
expect(result.content).not.toContain("!`echo");
|
|
248
|
+
});
|
|
249
|
+
|
|
250
|
+
test("expands multiple inline command tokens in encounter order", async () => {
|
|
251
|
+
writeSkill(
|
|
252
|
+
"multi-cmd-skill",
|
|
253
|
+
"Multi Command Skill",
|
|
254
|
+
"A skill with multiple inline commands",
|
|
255
|
+
"First: !`cmd-one`\nSecond: !`cmd-two`\nThird: !`cmd-three`",
|
|
256
|
+
);
|
|
257
|
+
|
|
258
|
+
const result = await executeSkillLoad({ skill: "multi-cmd-skill" });
|
|
259
|
+
expect(result.isError).toBe(false);
|
|
260
|
+
expect(result.content).toContain(
|
|
261
|
+
'<inline_skill_command index="0">result of: cmd-one</inline_skill_command>',
|
|
262
|
+
);
|
|
263
|
+
expect(result.content).toContain(
|
|
264
|
+
'<inline_skill_command index="1">result of: cmd-two</inline_skill_command>',
|
|
265
|
+
);
|
|
266
|
+
expect(result.content).toContain(
|
|
267
|
+
'<inline_skill_command index="2">result of: cmd-three</inline_skill_command>',
|
|
268
|
+
);
|
|
269
|
+
});
|
|
270
|
+
|
|
271
|
+
test("tokens are expanded exactly once (not re-expanded)", async () => {
|
|
272
|
+
writeSkill(
|
|
273
|
+
"once-skill",
|
|
274
|
+
"Once Skill",
|
|
275
|
+
"Expand only once",
|
|
276
|
+
"Data: !`echo hello`",
|
|
277
|
+
);
|
|
278
|
+
|
|
279
|
+
await executeSkillLoad({ skill: "once-skill" });
|
|
280
|
+
// The runner should be called exactly once for this one token
|
|
281
|
+
expect(runInlineCommandCalls).toHaveLength(1);
|
|
282
|
+
expect(runInlineCommandCalls[0].command).toBe("echo hello");
|
|
283
|
+
});
|
|
284
|
+
|
|
285
|
+
test("passes the conversation working directory to the runner", async () => {
|
|
286
|
+
writeSkill("cwd-skill", "CWD Skill", "Check working dir", "Info: !`pwd`");
|
|
287
|
+
|
|
288
|
+
const workingDir = "/my/project/root";
|
|
289
|
+
await executeSkillLoad({ skill: "cwd-skill" }, workingDir);
|
|
290
|
+
expect(runInlineCommandCalls).toHaveLength(1);
|
|
291
|
+
expect(runInlineCommandCalls[0].workingDir).toBe(workingDir);
|
|
292
|
+
});
|
|
293
|
+
});
|
|
294
|
+
|
|
295
|
+
// ── Plain skills (no inline commands) ────────────────────────────────
|
|
296
|
+
|
|
297
|
+
describe("plain skills", () => {
|
|
298
|
+
test("plain skill without inline commands loads normally", async () => {
|
|
299
|
+
writeSkill(
|
|
300
|
+
"plain-skill",
|
|
301
|
+
"Plain Skill",
|
|
302
|
+
"No inline commands",
|
|
303
|
+
"Just regular content.",
|
|
304
|
+
);
|
|
305
|
+
|
|
306
|
+
const result = await executeSkillLoad({ skill: "plain-skill" });
|
|
307
|
+
expect(result.isError).toBe(false);
|
|
308
|
+
expect(result.content).toContain("Just regular content.");
|
|
309
|
+
expect(result.content).not.toContain("inline_skill_command");
|
|
310
|
+
// Runner should not be called
|
|
311
|
+
expect(runInlineCommandCalls).toHaveLength(0);
|
|
312
|
+
});
|
|
313
|
+
});
|
|
314
|
+
|
|
315
|
+
// ── Feature flag off ─────────────────────────────────────────────────
|
|
316
|
+
|
|
317
|
+
describe("feature flag disabled", () => {
|
|
318
|
+
test("returns error when flag is off and skill has inline commands", async () => {
|
|
319
|
+
testConfig.assistantFeatureFlagValues = {
|
|
320
|
+
"feature_flags.inline-skill-commands.enabled": false,
|
|
321
|
+
};
|
|
322
|
+
|
|
323
|
+
writeSkill(
|
|
324
|
+
"flagged-off-skill",
|
|
325
|
+
"Flagged Off Skill",
|
|
326
|
+
"Has inline commands",
|
|
327
|
+
"Data: !`echo hello`",
|
|
328
|
+
);
|
|
329
|
+
|
|
330
|
+
const result = await executeSkillLoad({ skill: "flagged-off-skill" });
|
|
331
|
+
expect(result.isError).toBe(true);
|
|
332
|
+
expect(result.content).toContain(
|
|
333
|
+
"inline-skill-commands feature flag is disabled",
|
|
334
|
+
);
|
|
335
|
+
// Runner should not be called when flag is off
|
|
336
|
+
expect(runInlineCommandCalls).toHaveLength(0);
|
|
337
|
+
});
|
|
338
|
+
|
|
339
|
+
test("plain skill still loads when flag is off", async () => {
|
|
340
|
+
testConfig.assistantFeatureFlagValues = {
|
|
341
|
+
"feature_flags.inline-skill-commands.enabled": false,
|
|
342
|
+
};
|
|
343
|
+
|
|
344
|
+
writeSkill(
|
|
345
|
+
"plain-flag-off",
|
|
346
|
+
"Plain Flag Off",
|
|
347
|
+
"No inline commands",
|
|
348
|
+
"Regular content.",
|
|
349
|
+
);
|
|
350
|
+
|
|
351
|
+
const result = await executeSkillLoad({ skill: "plain-flag-off" });
|
|
352
|
+
expect(result.isError).toBe(false);
|
|
353
|
+
expect(result.content).toContain("Regular content.");
|
|
354
|
+
});
|
|
355
|
+
});
|
|
356
|
+
|
|
357
|
+
// ── Extra source rejection ───────────────────────────────────────────
|
|
358
|
+
//
|
|
359
|
+
// The SkillLoadTool checks `skill.source === "extra"` and rejects inline
|
|
360
|
+
// command expansion for third-party skill sources. Since `loadSkillBySelector`
|
|
361
|
+
// doesn't propagate `extraDirs` from config, extra-source skills can't be
|
|
362
|
+
// easily loaded through the tool in a unit test without deep mocking.
|
|
363
|
+
//
|
|
364
|
+
// We verify the rejection logic exists by importing the tool class directly
|
|
365
|
+
// and confirming the code path rejects extra sources when inline commands
|
|
366
|
+
// are present. The actual source-level guard is tested by checking that
|
|
367
|
+
// workspace-source skills *do* expand (above) while the code explicitly
|
|
368
|
+
// gates on INLINE_COMMAND_ELIGIBLE_SOURCES which excludes "extra".
|
|
369
|
+
|
|
370
|
+
describe("extra source rejection (code-level verification)", () => {
|
|
371
|
+
test("INLINE_COMMAND_ELIGIBLE_SOURCES does not include 'extra'", async () => {
|
|
372
|
+
// Read the load.ts source to verify the eligible sources set
|
|
373
|
+
const { readFileSync } = await import("node:fs");
|
|
374
|
+
const { join: pjoin } = await import("node:path");
|
|
375
|
+
const loadSrc = readFileSync(
|
|
376
|
+
pjoin(
|
|
377
|
+
import.meta.dirname ?? __dirname,
|
|
378
|
+
"..",
|
|
379
|
+
"tools",
|
|
380
|
+
"skills",
|
|
381
|
+
"load.ts",
|
|
382
|
+
),
|
|
383
|
+
"utf-8",
|
|
384
|
+
);
|
|
385
|
+
// The eligible sources set must include bundled, managed, workspace but NOT extra
|
|
386
|
+
expect(loadSrc).toContain('"bundled"');
|
|
387
|
+
expect(loadSrc).toContain('"managed"');
|
|
388
|
+
expect(loadSrc).toContain('"workspace"');
|
|
389
|
+
expect(loadSrc).toContain('skill.source === "extra"');
|
|
390
|
+
});
|
|
391
|
+
});
|
|
392
|
+
|
|
393
|
+
// ── Render failures ──────────────────────────────────────────────────
|
|
394
|
+
|
|
395
|
+
describe("render failures", () => {
|
|
396
|
+
test("timeout renders stable stub", async () => {
|
|
397
|
+
mockRunInlineCommand = mock<MockRunFn>(
|
|
398
|
+
(command: string, workingDir: string) => {
|
|
399
|
+
runInlineCommandCalls.push({ command, workingDir });
|
|
400
|
+
return Promise.resolve({
|
|
401
|
+
output: "Inline command timed out after 10000ms.",
|
|
402
|
+
ok: false,
|
|
403
|
+
failureReason: "timeout",
|
|
404
|
+
});
|
|
405
|
+
},
|
|
406
|
+
);
|
|
407
|
+
mock.module("../skills/inline-command-runner.js", () => ({
|
|
408
|
+
runInlineCommand: (
|
|
409
|
+
command: string,
|
|
410
|
+
workingDir: string,
|
|
411
|
+
_options?: unknown,
|
|
412
|
+
) => mockRunInlineCommand(command, workingDir),
|
|
413
|
+
}));
|
|
414
|
+
|
|
415
|
+
writeSkill(
|
|
416
|
+
"timeout-skill",
|
|
417
|
+
"Timeout Skill",
|
|
418
|
+
"Command times out",
|
|
419
|
+
"Data: !`sleep 999`",
|
|
420
|
+
);
|
|
421
|
+
|
|
422
|
+
const result = await executeSkillLoad({ skill: "timeout-skill" });
|
|
423
|
+
expect(result.isError).toBe(false);
|
|
424
|
+
expect(result.content).toContain(
|
|
425
|
+
'<inline_skill_command index="0">[inline command unavailable: command timed out]</inline_skill_command>',
|
|
426
|
+
);
|
|
427
|
+
// Original token should be replaced
|
|
428
|
+
expect(result.content).not.toContain("!`sleep");
|
|
429
|
+
});
|
|
430
|
+
|
|
431
|
+
test("non-zero exit renders stable stub", async () => {
|
|
432
|
+
mockRunInlineCommand = mock<MockRunFn>(
|
|
433
|
+
(command: string, workingDir: string) => {
|
|
434
|
+
runInlineCommandCalls.push({ command, workingDir });
|
|
435
|
+
return Promise.resolve({
|
|
436
|
+
output: "Inline command failed (exit code 1).",
|
|
437
|
+
ok: false,
|
|
438
|
+
failureReason: "non_zero_exit",
|
|
439
|
+
});
|
|
440
|
+
},
|
|
441
|
+
);
|
|
442
|
+
mock.module("../skills/inline-command-runner.js", () => ({
|
|
443
|
+
runInlineCommand: (
|
|
444
|
+
command: string,
|
|
445
|
+
workingDir: string,
|
|
446
|
+
_options?: unknown,
|
|
447
|
+
) => mockRunInlineCommand(command, workingDir),
|
|
448
|
+
}));
|
|
449
|
+
|
|
450
|
+
writeSkill("fail-skill", "Fail Skill", "Command fails", "Data: !`false`");
|
|
451
|
+
|
|
452
|
+
const result = await executeSkillLoad({ skill: "fail-skill" });
|
|
453
|
+
expect(result.isError).toBe(false);
|
|
454
|
+
expect(result.content).toContain(
|
|
455
|
+
'<inline_skill_command index="0">[inline command unavailable: command failed]</inline_skill_command>',
|
|
456
|
+
);
|
|
457
|
+
});
|
|
458
|
+
|
|
459
|
+
test("spawn failure renders stable stub", async () => {
|
|
460
|
+
mockRunInlineCommand = mock<MockRunFn>(
|
|
461
|
+
(command: string, workingDir: string) => {
|
|
462
|
+
runInlineCommandCalls.push({ command, workingDir });
|
|
463
|
+
return Promise.resolve({
|
|
464
|
+
output: "Inline command could not be started.",
|
|
465
|
+
ok: false,
|
|
466
|
+
failureReason: "spawn_failure",
|
|
467
|
+
});
|
|
468
|
+
},
|
|
469
|
+
);
|
|
470
|
+
mock.module("../skills/inline-command-runner.js", () => ({
|
|
471
|
+
runInlineCommand: (
|
|
472
|
+
command: string,
|
|
473
|
+
workingDir: string,
|
|
474
|
+
_options?: unknown,
|
|
475
|
+
) => mockRunInlineCommand(command, workingDir),
|
|
476
|
+
}));
|
|
477
|
+
|
|
478
|
+
writeSkill(
|
|
479
|
+
"spawn-fail-skill",
|
|
480
|
+
"Spawn Fail Skill",
|
|
481
|
+
"Command spawn fails",
|
|
482
|
+
"Data: !`nonexistent-binary`",
|
|
483
|
+
);
|
|
484
|
+
|
|
485
|
+
const result = await executeSkillLoad({ skill: "spawn-fail-skill" });
|
|
486
|
+
expect(result.isError).toBe(false);
|
|
487
|
+
expect(result.content).toContain(
|
|
488
|
+
'<inline_skill_command index="0">[inline command unavailable: command could not be started]</inline_skill_command>',
|
|
489
|
+
);
|
|
490
|
+
});
|
|
491
|
+
|
|
492
|
+
test("binary output renders stable stub", async () => {
|
|
493
|
+
mockRunInlineCommand = mock<MockRunFn>(
|
|
494
|
+
(command: string, workingDir: string) => {
|
|
495
|
+
runInlineCommandCalls.push({ command, workingDir });
|
|
496
|
+
return Promise.resolve({
|
|
497
|
+
output: "Inline command produced binary output.",
|
|
498
|
+
ok: false,
|
|
499
|
+
failureReason: "binary_output",
|
|
500
|
+
});
|
|
501
|
+
},
|
|
502
|
+
);
|
|
503
|
+
mock.module("../skills/inline-command-runner.js", () => ({
|
|
504
|
+
runInlineCommand: (
|
|
505
|
+
command: string,
|
|
506
|
+
workingDir: string,
|
|
507
|
+
_options?: unknown,
|
|
508
|
+
) => mockRunInlineCommand(command, workingDir),
|
|
509
|
+
}));
|
|
510
|
+
|
|
511
|
+
writeSkill(
|
|
512
|
+
"binary-skill",
|
|
513
|
+
"Binary Skill",
|
|
514
|
+
"Command produces binary",
|
|
515
|
+
"Data: !`cat /dev/urandom`",
|
|
516
|
+
);
|
|
517
|
+
|
|
518
|
+
const result = await executeSkillLoad({ skill: "binary-skill" });
|
|
519
|
+
expect(result.isError).toBe(false);
|
|
520
|
+
expect(result.content).toContain(
|
|
521
|
+
'<inline_skill_command index="0">[inline command unavailable: command produced binary output]</inline_skill_command>',
|
|
522
|
+
);
|
|
523
|
+
});
|
|
524
|
+
|
|
525
|
+
test("mixed success and failure renders both correctly", async () => {
|
|
526
|
+
let callIndex = 0;
|
|
527
|
+
mockRunInlineCommand = mock<MockRunFn>(
|
|
528
|
+
(command: string, workingDir: string) => {
|
|
529
|
+
runInlineCommandCalls.push({ command, workingDir });
|
|
530
|
+
const idx = callIndex++;
|
|
531
|
+
if (idx === 1) {
|
|
532
|
+
// Second command fails
|
|
533
|
+
return Promise.resolve({
|
|
534
|
+
output: "Inline command failed (exit code 1).",
|
|
535
|
+
ok: false,
|
|
536
|
+
failureReason: "non_zero_exit",
|
|
537
|
+
});
|
|
538
|
+
}
|
|
539
|
+
return Promise.resolve({
|
|
540
|
+
output: `result of: ${command}`,
|
|
541
|
+
ok: true,
|
|
542
|
+
});
|
|
543
|
+
},
|
|
544
|
+
);
|
|
545
|
+
mock.module("../skills/inline-command-runner.js", () => ({
|
|
546
|
+
runInlineCommand: (
|
|
547
|
+
command: string,
|
|
548
|
+
workingDir: string,
|
|
549
|
+
_options?: unknown,
|
|
550
|
+
) => mockRunInlineCommand(command, workingDir),
|
|
551
|
+
}));
|
|
552
|
+
|
|
553
|
+
writeSkill(
|
|
554
|
+
"mixed-skill",
|
|
555
|
+
"Mixed Skill",
|
|
556
|
+
"Some commands fail",
|
|
557
|
+
"A: !`echo ok` B: !`bad-cmd` C: !`echo fine`",
|
|
558
|
+
);
|
|
559
|
+
|
|
560
|
+
const result = await executeSkillLoad({ skill: "mixed-skill" });
|
|
561
|
+
expect(result.isError).toBe(false);
|
|
562
|
+
// First and third succeed
|
|
563
|
+
expect(result.content).toContain(
|
|
564
|
+
'<inline_skill_command index="0">result of: echo ok</inline_skill_command>',
|
|
565
|
+
);
|
|
566
|
+
expect(result.content).toContain(
|
|
567
|
+
'<inline_skill_command index="2">result of: echo fine</inline_skill_command>',
|
|
568
|
+
);
|
|
569
|
+
// Second fails with stub
|
|
570
|
+
expect(result.content).toContain(
|
|
571
|
+
'<inline_skill_command index="1">[inline command unavailable: command failed]</inline_skill_command>',
|
|
572
|
+
);
|
|
573
|
+
});
|
|
574
|
+
});
|
|
575
|
+
|
|
576
|
+
// ── XML wrapper format ───────────────────────────────────────────────
|
|
577
|
+
|
|
578
|
+
describe("XML wrapper format", () => {
|
|
579
|
+
test("output is wrapped in <inline_skill_command> with index attribute", async () => {
|
|
580
|
+
writeSkill(
|
|
581
|
+
"xml-skill",
|
|
582
|
+
"XML Skill",
|
|
583
|
+
"Check XML wrapping",
|
|
584
|
+
"Info: !`echo data`",
|
|
585
|
+
);
|
|
586
|
+
|
|
587
|
+
const result = await executeSkillLoad({ skill: "xml-skill" });
|
|
588
|
+
expect(result.isError).toBe(false);
|
|
589
|
+
// Verify exact XML tag format
|
|
590
|
+
const match = result.content.match(
|
|
591
|
+
/<inline_skill_command index="(\d+)">(.*?)<\/inline_skill_command>/,
|
|
592
|
+
);
|
|
593
|
+
expect(match).not.toBeNull();
|
|
594
|
+
expect(match![1]).toBe("0");
|
|
595
|
+
expect(match![2]).toBe("result of: echo data");
|
|
596
|
+
});
|
|
597
|
+
});
|
|
598
|
+
});
|