@vellumai/assistant 0.5.0 → 0.5.2
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 +54 -54
- package/docs/architecture/integrations.md +62 -67
- package/docs/credential-execution-service.md +3 -3
- 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__/assistant-feature-flags-integration.test.ts +7 -9
- 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 +149 -5
- package/src/__tests__/conversation-agent-loop.test.ts +290 -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-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-workspace-cache-state.test.ts +44 -2
- package/src/__tests__/conversation-workspace-injection.test.ts +11 -0
- package/src/__tests__/credential-execution-feature-gates.test.ts +3 -3
- 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__/diagnostics-export.test.ts +70 -1
- package/src/__tests__/filesystem-tools.test.ts +4 -2
- 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 +103 -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__/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-lifecycle-e2e.test.ts +3 -1
- package/src/__tests__/memory-recall-quality.test.ts +5 -5
- 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__/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-feature-flags-integration.test.ts +18 -17
- package/src/__tests__/skill-feature-flags.test.ts +13 -13
- package/src/__tests__/skill-load-feature-flag.test.ts +4 -4
- 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__/system-prompt.test.ts +8 -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__/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/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 +16 -0
- package/src/config/loader.ts +64 -0
- package/src/config/raw-config-utils.ts +30 -0
- package/src/config/schema-utils.ts +28 -7
- package/src/config/schema.ts +8 -0
- package/src/config/schemas/elevenlabs.ts +18 -0
- package/src/config/schemas/memory-lifecycle.ts +4 -2
- package/src/config/schemas/memory-storage.ts +1 -1
- package/src/config/schemas/services.ts +8 -6
- 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 +46 -42
- package/src/daemon/conversation-agent-loop.ts +56 -19
- 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 +39 -15
- package/src/daemon/conversation-messaging.ts +70 -26
- package/src/daemon/conversation-process.ts +58 -34
- package/src/daemon/conversation-runtime-assembly.ts +21 -38
- package/src/daemon/conversation-slash.ts +121 -256
- package/src/daemon/conversation-surfaces.ts +143 -20
- package/src/daemon/conversation-tool-setup.ts +0 -6
- package/src/daemon/conversation-workspace.ts +21 -1
- package/src/daemon/conversation.ts +51 -29
- package/src/daemon/first-greeting.ts +35 -0
- package/src/daemon/handlers/config-embeddings.ts +148 -0
- package/src/daemon/handlers/config-model.ts +71 -26
- package/src/daemon/handlers/conversations.ts +0 -23
- package/src/daemon/handlers/recording.ts +26 -21
- package/src/daemon/history-repair.ts +28 -8
- package/src/daemon/host-cu-proxy.ts +2 -2
- package/src/daemon/lifecycle.ts +106 -64
- package/src/daemon/message-protocol.ts +3 -0
- package/src/daemon/message-types/conversations.ts +19 -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/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/attachments-store.ts +558 -130
- package/src/memory/conversation-attention-store.ts +70 -0
- package/src/memory/conversation-crud.ts +442 -3
- 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 +28 -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 +9 -7
- 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 +1 -4
- 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/index.ts +7 -0
- package/src/memory/migrations/registry.ts +13 -0
- package/src/memory/retriever.test.ts +601 -2
- package/src/memory/retriever.ts +85 -9
- package/src/memory/schema/conversations.ts +6 -0
- package/src/memory/schema/infrastructure.ts +13 -7
- package/src/memory/schema/oauth.ts +6 -0
- 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 +97 -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 +62 -19
- package/src/prompts/system-prompt.ts +2 -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 +68 -0
- package/src/runtime/routes/conversation-query-routes.ts +180 -10
- package/src/runtime/routes/conversation-routes.ts +222 -28
- package/src/runtime/routes/conversation-starter-routes.ts +9 -11
- package/src/runtime/routes/diagnostics-routes.ts +1 -0
- package/src/runtime/routes/inbound-stages/acl-enforcement.ts +2 -2
- package/src/runtime/routes/llm-context-normalization.ts +1199 -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 +4 -0
- 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 +28 -1
- package/src/runtime/routes/settings-routes.ts +14 -0
- package/src/runtime/routes/trace-event-routes.ts +4 -1
- package/src/schedule/schedule-store.ts +9 -21
- package/src/security/secure-keys.ts +21 -0
- package/src/signals/bash.ts +1 -1
- package/src/swarm/backend-claude-code.ts +3 -6
- 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/schedule/list.ts +2 -7
- package/src/tools/schema-transforms.ts +5 -0
- package/src/tools/shared/filesystem/format-diff.ts +4 -21
- package/src/tools/skills/execute.ts +1 -1
- 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 +6 -0
- package/src/util/pricing.ts +1 -0
- package/src/util/retry.ts +1 -3
- package/src/workspace/migrations/002-backfill-installation-id.ts +23 -12
- 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/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 +10 -0
- 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,426 @@
|
|
|
1
|
+
import {
|
|
2
|
+
existsSync,
|
|
3
|
+
mkdirSync,
|
|
4
|
+
readFileSync,
|
|
5
|
+
rmSync,
|
|
6
|
+
writeFileSync,
|
|
7
|
+
} from "node:fs";
|
|
8
|
+
import { tmpdir } from "node:os";
|
|
9
|
+
import { join } from "node:path";
|
|
10
|
+
import { afterEach, beforeEach, describe, expect, mock, test } from "bun:test";
|
|
11
|
+
|
|
12
|
+
// ---------------------------------------------------------------------------
|
|
13
|
+
// Mock getDataDir to use a temp directory
|
|
14
|
+
// ---------------------------------------------------------------------------
|
|
15
|
+
|
|
16
|
+
let testDataDir: string;
|
|
17
|
+
|
|
18
|
+
mock.module("../util/platform.js", () => ({
|
|
19
|
+
getDataDir: () => testDataDir,
|
|
20
|
+
getProjectDir: () => testDataDir,
|
|
21
|
+
}));
|
|
22
|
+
|
|
23
|
+
// Re-import app-store after mocking so it uses our temp dir
|
|
24
|
+
const {
|
|
25
|
+
slugify,
|
|
26
|
+
generateAppDirName,
|
|
27
|
+
validateDirName,
|
|
28
|
+
resolveAppDir,
|
|
29
|
+
getAppDirPath,
|
|
30
|
+
getAppsDir,
|
|
31
|
+
createApp,
|
|
32
|
+
getApp,
|
|
33
|
+
updateApp,
|
|
34
|
+
deleteApp,
|
|
35
|
+
} = await import("../memory/app-store.js");
|
|
36
|
+
|
|
37
|
+
// ---------------------------------------------------------------------------
|
|
38
|
+
// Helpers
|
|
39
|
+
// ---------------------------------------------------------------------------
|
|
40
|
+
|
|
41
|
+
function freshTempDir(): string {
|
|
42
|
+
return join(
|
|
43
|
+
tmpdir(),
|
|
44
|
+
`vellum-app-dir-test-${Date.now()}-${Math.random().toString(36).slice(2)}`,
|
|
45
|
+
);
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
function makeAppParams(name: string) {
|
|
49
|
+
return {
|
|
50
|
+
name,
|
|
51
|
+
schemaJson: "{}",
|
|
52
|
+
htmlDefinition: "<h1>Hello</h1>",
|
|
53
|
+
};
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
// ---------------------------------------------------------------------------
|
|
57
|
+
// Setup / teardown
|
|
58
|
+
// ---------------------------------------------------------------------------
|
|
59
|
+
|
|
60
|
+
beforeEach(() => {
|
|
61
|
+
testDataDir = freshTempDir();
|
|
62
|
+
mkdirSync(join(testDataDir, "apps"), { recursive: true });
|
|
63
|
+
});
|
|
64
|
+
|
|
65
|
+
afterEach(() => {
|
|
66
|
+
if (existsSync(testDataDir)) {
|
|
67
|
+
rmSync(testDataDir, { recursive: true, force: true });
|
|
68
|
+
}
|
|
69
|
+
});
|
|
70
|
+
|
|
71
|
+
// ---------------------------------------------------------------------------
|
|
72
|
+
// slugify()
|
|
73
|
+
// ---------------------------------------------------------------------------
|
|
74
|
+
|
|
75
|
+
describe("slugify()", () => {
|
|
76
|
+
test("normal names", () => {
|
|
77
|
+
expect(slugify("My Cool App")).toBe("my-cool-app");
|
|
78
|
+
});
|
|
79
|
+
|
|
80
|
+
test("special characters are replaced with hyphens", () => {
|
|
81
|
+
expect(slugify("hello@world!#$%")).toBe("hello-world");
|
|
82
|
+
});
|
|
83
|
+
|
|
84
|
+
test("unicode characters are replaced", () => {
|
|
85
|
+
expect(slugify("café résumé")).toBe("caf-r-sum");
|
|
86
|
+
});
|
|
87
|
+
|
|
88
|
+
test("emoji-only names produce fallback slug", () => {
|
|
89
|
+
const result = slugify("🚀🎉");
|
|
90
|
+
expect(result).toMatch(/^app-[a-f0-9]{8}$/);
|
|
91
|
+
});
|
|
92
|
+
|
|
93
|
+
test("empty string produces fallback slug", () => {
|
|
94
|
+
const result = slugify("");
|
|
95
|
+
expect(result).toMatch(/^app-[a-f0-9]{8}$/);
|
|
96
|
+
});
|
|
97
|
+
|
|
98
|
+
test("very long names are truncated to 60 chars", () => {
|
|
99
|
+
const longName = "a".repeat(100);
|
|
100
|
+
const result = slugify(longName);
|
|
101
|
+
expect(result.length).toBeLessThanOrEqual(60);
|
|
102
|
+
expect(result).toBe("a".repeat(60));
|
|
103
|
+
});
|
|
104
|
+
|
|
105
|
+
test("truncation removes trailing hyphens", () => {
|
|
106
|
+
// Create a name where position 60 lands on a hyphen sequence
|
|
107
|
+
const name = "a".repeat(58) + "--bbb";
|
|
108
|
+
const result = slugify(name);
|
|
109
|
+
expect(result.length).toBeLessThanOrEqual(60);
|
|
110
|
+
expect(result).not.toMatch(/-$/);
|
|
111
|
+
});
|
|
112
|
+
|
|
113
|
+
test("names with only hyphens produce fallback slug", () => {
|
|
114
|
+
const result = slugify("---");
|
|
115
|
+
expect(result).toMatch(/^app-[a-f0-9]{8}$/);
|
|
116
|
+
});
|
|
117
|
+
|
|
118
|
+
test("leading and trailing hyphens are stripped", () => {
|
|
119
|
+
expect(slugify("-hello-world-")).toBe("hello-world");
|
|
120
|
+
});
|
|
121
|
+
|
|
122
|
+
test("consecutive hyphens are collapsed", () => {
|
|
123
|
+
expect(slugify("hello---world")).toBe("hello-world");
|
|
124
|
+
});
|
|
125
|
+
});
|
|
126
|
+
|
|
127
|
+
// ---------------------------------------------------------------------------
|
|
128
|
+
// generateAppDirName()
|
|
129
|
+
// ---------------------------------------------------------------------------
|
|
130
|
+
|
|
131
|
+
describe("generateAppDirName()", () => {
|
|
132
|
+
test("returns base slug when no collision", () => {
|
|
133
|
+
const result = generateAppDirName("My App", new Set());
|
|
134
|
+
expect(result).toBe("my-app");
|
|
135
|
+
});
|
|
136
|
+
|
|
137
|
+
test("appends -2 on first collision", () => {
|
|
138
|
+
const result = generateAppDirName("My App", new Set(["my-app"]));
|
|
139
|
+
expect(result).toBe("my-app-2");
|
|
140
|
+
});
|
|
141
|
+
|
|
142
|
+
test("escalates numeric suffix on multiple collisions", () => {
|
|
143
|
+
const existing = new Set(["my-app", "my-app-2", "my-app-3"]);
|
|
144
|
+
const result = generateAppDirName("My App", existing);
|
|
145
|
+
expect(result).toBe("my-app-4");
|
|
146
|
+
});
|
|
147
|
+
|
|
148
|
+
test("collision with truncated names still deduplicates", () => {
|
|
149
|
+
const longName = "a".repeat(100);
|
|
150
|
+
const base = slugify(longName);
|
|
151
|
+
const existing = new Set([base]);
|
|
152
|
+
const result = generateAppDirName(longName, existing);
|
|
153
|
+
expect(result).toBe(`${base}-2`);
|
|
154
|
+
});
|
|
155
|
+
});
|
|
156
|
+
|
|
157
|
+
// ---------------------------------------------------------------------------
|
|
158
|
+
// createApp() — directory named after slug
|
|
159
|
+
// ---------------------------------------------------------------------------
|
|
160
|
+
|
|
161
|
+
describe("createApp()", () => {
|
|
162
|
+
test("directory is named after slug, not UUID", () => {
|
|
163
|
+
const app = createApp(makeAppParams("My Test App"));
|
|
164
|
+
const appsDir = getAppsDir();
|
|
165
|
+
|
|
166
|
+
// dirName should be the slug
|
|
167
|
+
expect(app.dirName).toBe("my-test-app");
|
|
168
|
+
|
|
169
|
+
// JSON file should be named after slug
|
|
170
|
+
expect(existsSync(join(appsDir, "my-test-app.json"))).toBe(true);
|
|
171
|
+
|
|
172
|
+
// Directory should be named after slug
|
|
173
|
+
expect(existsSync(join(appsDir, "my-test-app"))).toBe(true);
|
|
174
|
+
|
|
175
|
+
// UUID-named files should NOT exist
|
|
176
|
+
expect(existsSync(join(appsDir, `${app.id}.json`))).toBe(false);
|
|
177
|
+
});
|
|
178
|
+
|
|
179
|
+
test("dirName is persisted in JSON", () => {
|
|
180
|
+
const app = createApp(makeAppParams("Slug Test"));
|
|
181
|
+
const appsDir = getAppsDir();
|
|
182
|
+
const jsonPath = join(appsDir, "slug-test.json");
|
|
183
|
+
const persisted = JSON.parse(readFileSync(jsonPath, "utf-8"));
|
|
184
|
+
expect(persisted.dirName).toBe("slug-test");
|
|
185
|
+
expect(persisted.id).toBe(app.id);
|
|
186
|
+
});
|
|
187
|
+
|
|
188
|
+
test("index.html is in the slugified directory", () => {
|
|
189
|
+
createApp(makeAppParams("Html App"));
|
|
190
|
+
const appsDir = getAppsDir();
|
|
191
|
+
const indexPath = join(appsDir, "html-app", "index.html");
|
|
192
|
+
expect(existsSync(indexPath)).toBe(true);
|
|
193
|
+
expect(readFileSync(indexPath, "utf-8")).toBe("<h1>Hello</h1>");
|
|
194
|
+
});
|
|
195
|
+
|
|
196
|
+
test("deduplicates dirNames across multiple creates", () => {
|
|
197
|
+
const app1 = createApp(makeAppParams("Duplicate"));
|
|
198
|
+
const app2 = createApp(makeAppParams("Duplicate"));
|
|
199
|
+
expect(app1.dirName).toBe("duplicate");
|
|
200
|
+
expect(app2.dirName).toBe("duplicate-2");
|
|
201
|
+
});
|
|
202
|
+
});
|
|
203
|
+
|
|
204
|
+
// ---------------------------------------------------------------------------
|
|
205
|
+
// createApp() + updateApp() — frozen dirName invariant
|
|
206
|
+
// ---------------------------------------------------------------------------
|
|
207
|
+
|
|
208
|
+
describe("frozen dirName invariant", () => {
|
|
209
|
+
test("renaming an app does NOT change its directory name", () => {
|
|
210
|
+
const app = createApp(makeAppParams("Original Name"));
|
|
211
|
+
const appsDir = getAppsDir();
|
|
212
|
+
|
|
213
|
+
expect(app.dirName).toBe("original-name");
|
|
214
|
+
expect(existsSync(join(appsDir, "original-name.json"))).toBe(true);
|
|
215
|
+
|
|
216
|
+
// Rename the app
|
|
217
|
+
const updated = updateApp(app.id, { name: "New Name" });
|
|
218
|
+
expect(updated.name).toBe("New Name");
|
|
219
|
+
|
|
220
|
+
// Directory and files should still be at original slug
|
|
221
|
+
expect(existsSync(join(appsDir, "original-name.json"))).toBe(true);
|
|
222
|
+
expect(existsSync(join(appsDir, "original-name"))).toBe(true);
|
|
223
|
+
|
|
224
|
+
// New name slug should NOT exist as files
|
|
225
|
+
expect(existsSync(join(appsDir, "new-name.json"))).toBe(false);
|
|
226
|
+
});
|
|
227
|
+
});
|
|
228
|
+
|
|
229
|
+
// ---------------------------------------------------------------------------
|
|
230
|
+
// getApp() — lookup by UUID with slugified dirs
|
|
231
|
+
// ---------------------------------------------------------------------------
|
|
232
|
+
|
|
233
|
+
describe("getApp()", () => {
|
|
234
|
+
test("lookup by UUID works with slugified dirs", () => {
|
|
235
|
+
const created = createApp(makeAppParams("Lookup App"));
|
|
236
|
+
const fetched = getApp(created.id);
|
|
237
|
+
expect(fetched).not.toBeNull();
|
|
238
|
+
expect(fetched!.id).toBe(created.id);
|
|
239
|
+
expect(fetched!.name).toBe("Lookup App");
|
|
240
|
+
expect(fetched!.htmlDefinition).toBe("<h1>Hello</h1>");
|
|
241
|
+
});
|
|
242
|
+
|
|
243
|
+
test("backward compat: works when JSON has no dirName (uses id as fallback)", () => {
|
|
244
|
+
const appsDir = getAppsDir();
|
|
245
|
+
const fakeId = "aaaaaaaa-bbbb-cccc-dddd-eeeeeeeeeeee";
|
|
246
|
+
|
|
247
|
+
// Write a JSON file with no dirName, using the UUID as the filename
|
|
248
|
+
const appData = {
|
|
249
|
+
id: fakeId,
|
|
250
|
+
name: "Legacy App",
|
|
251
|
+
schemaJson: "{}",
|
|
252
|
+
createdAt: Date.now(),
|
|
253
|
+
updatedAt: Date.now(),
|
|
254
|
+
};
|
|
255
|
+
writeFileSync(
|
|
256
|
+
join(appsDir, `${fakeId}.json`),
|
|
257
|
+
JSON.stringify(appData, null, 2),
|
|
258
|
+
);
|
|
259
|
+
|
|
260
|
+
// Create directory with index.html
|
|
261
|
+
const appDir = join(appsDir, fakeId);
|
|
262
|
+
mkdirSync(appDir, { recursive: true });
|
|
263
|
+
writeFileSync(join(appDir, "index.html"), "<p>legacy</p>", "utf-8");
|
|
264
|
+
|
|
265
|
+
const fetched = getApp(fakeId);
|
|
266
|
+
expect(fetched).not.toBeNull();
|
|
267
|
+
expect(fetched!.id).toBe(fakeId);
|
|
268
|
+
expect(fetched!.name).toBe("Legacy App");
|
|
269
|
+
expect(fetched!.htmlDefinition).toBe("<p>legacy</p>");
|
|
270
|
+
});
|
|
271
|
+
});
|
|
272
|
+
|
|
273
|
+
// ---------------------------------------------------------------------------
|
|
274
|
+
// deleteApp() — cleans up slugified files
|
|
275
|
+
// ---------------------------------------------------------------------------
|
|
276
|
+
|
|
277
|
+
describe("deleteApp()", () => {
|
|
278
|
+
test("cleans up slugified directory, JSON, and preview files", () => {
|
|
279
|
+
const app = createApp({
|
|
280
|
+
...makeAppParams("Delete Me"),
|
|
281
|
+
preview: "base64-preview-data",
|
|
282
|
+
});
|
|
283
|
+
const appsDir = getAppsDir();
|
|
284
|
+
|
|
285
|
+
// Verify files exist before deletion
|
|
286
|
+
expect(existsSync(join(appsDir, "delete-me.json"))).toBe(true);
|
|
287
|
+
expect(existsSync(join(appsDir, "delete-me"))).toBe(true);
|
|
288
|
+
expect(existsSync(join(appsDir, "delete-me.preview"))).toBe(true);
|
|
289
|
+
|
|
290
|
+
deleteApp(app.id);
|
|
291
|
+
|
|
292
|
+
// All files should be gone
|
|
293
|
+
expect(existsSync(join(appsDir, "delete-me.json"))).toBe(false);
|
|
294
|
+
expect(existsSync(join(appsDir, "delete-me"))).toBe(false);
|
|
295
|
+
expect(existsSync(join(appsDir, "delete-me.preview"))).toBe(false);
|
|
296
|
+
});
|
|
297
|
+
});
|
|
298
|
+
|
|
299
|
+
// ---------------------------------------------------------------------------
|
|
300
|
+
// resolveAppDir() — validation rejects malicious dirName values
|
|
301
|
+
// When a JSON file has an invalid dirName, resolveAppDir defensively falls
|
|
302
|
+
// back to using the app ID instead of the malicious dirName. The
|
|
303
|
+
// validateDirName() call inside the try/catch causes the invalid entry to
|
|
304
|
+
// be skipped, and the function returns the safe fallback.
|
|
305
|
+
// ---------------------------------------------------------------------------
|
|
306
|
+
|
|
307
|
+
describe("resolveAppDir() validation", () => {
|
|
308
|
+
test("falls back to id when dirName contains path traversal (..)", () => {
|
|
309
|
+
const appsDir = getAppsDir();
|
|
310
|
+
const fakeId = "bbbbbbbb-cccc-dddd-eeee-ffffffffffff";
|
|
311
|
+
writeFileSync(
|
|
312
|
+
join(appsDir, `${fakeId}.json`),
|
|
313
|
+
JSON.stringify({ id: fakeId, name: "Evil", dirName: "../etc" }),
|
|
314
|
+
);
|
|
315
|
+
|
|
316
|
+
// Should NOT use the malicious dirName — falls back to id
|
|
317
|
+
const result = resolveAppDir(fakeId);
|
|
318
|
+
expect(result.dirName).toBe(fakeId);
|
|
319
|
+
expect(result.appDir).toBe(join(appsDir, fakeId));
|
|
320
|
+
});
|
|
321
|
+
|
|
322
|
+
test("falls back to id when dirName contains forward slash", () => {
|
|
323
|
+
const appsDir = getAppsDir();
|
|
324
|
+
const fakeId = "cccccccc-dddd-eeee-ffff-aaaaaaaaaaaa";
|
|
325
|
+
writeFileSync(
|
|
326
|
+
join(appsDir, `${fakeId}.json`),
|
|
327
|
+
JSON.stringify({ id: fakeId, name: "Evil", dirName: "foo/bar" }),
|
|
328
|
+
);
|
|
329
|
+
|
|
330
|
+
const result = resolveAppDir(fakeId);
|
|
331
|
+
expect(result.dirName).toBe(fakeId);
|
|
332
|
+
});
|
|
333
|
+
|
|
334
|
+
test("falls back to id when dirName contains backslash", () => {
|
|
335
|
+
const appsDir = getAppsDir();
|
|
336
|
+
const fakeId = "dddddddd-eeee-ffff-aaaa-bbbbbbbbbbbb";
|
|
337
|
+
writeFileSync(
|
|
338
|
+
join(appsDir, `${fakeId}.json`),
|
|
339
|
+
JSON.stringify({ id: fakeId, name: "Evil", dirName: "foo\\bar" }),
|
|
340
|
+
);
|
|
341
|
+
|
|
342
|
+
const result = resolveAppDir(fakeId);
|
|
343
|
+
expect(result.dirName).toBe(fakeId);
|
|
344
|
+
});
|
|
345
|
+
|
|
346
|
+
test("falls back to id when dirName is empty string", () => {
|
|
347
|
+
const appsDir = getAppsDir();
|
|
348
|
+
const fakeId = "eeeeeeee-ffff-aaaa-bbbb-cccccccccccc";
|
|
349
|
+
writeFileSync(
|
|
350
|
+
join(appsDir, `${fakeId}.json`),
|
|
351
|
+
JSON.stringify({ id: fakeId, name: "Evil", dirName: "" }),
|
|
352
|
+
);
|
|
353
|
+
|
|
354
|
+
// Empty string is falsy, so `parsed.dirName || id` falls back to the app ID.
|
|
355
|
+
// This prevents appDir from resolving to the apps root directory.
|
|
356
|
+
const result = resolveAppDir(fakeId);
|
|
357
|
+
expect(result.dirName).toBe(fakeId);
|
|
358
|
+
expect(result.appDir).toBe(join(appsDir, fakeId));
|
|
359
|
+
});
|
|
360
|
+
});
|
|
361
|
+
|
|
362
|
+
// ---------------------------------------------------------------------------
|
|
363
|
+
// validateDirName() — direct unit tests
|
|
364
|
+
// ---------------------------------------------------------------------------
|
|
365
|
+
|
|
366
|
+
describe("validateDirName()", () => {
|
|
367
|
+
test("accepts valid slug names", () => {
|
|
368
|
+
expect(() => validateDirName("my-cool-app")).not.toThrow();
|
|
369
|
+
expect(() => validateDirName("app-123")).not.toThrow();
|
|
370
|
+
});
|
|
371
|
+
|
|
372
|
+
test("rejects git pathspec metacharacters", () => {
|
|
373
|
+
expect(() => validateDirName("app*")).toThrow("git pathspec");
|
|
374
|
+
expect(() => validateDirName("app?")).toThrow("git pathspec");
|
|
375
|
+
expect(() => validateDirName("app[0]")).toThrow("git pathspec");
|
|
376
|
+
expect(() => validateDirName("app:foo")).toThrow("git pathspec");
|
|
377
|
+
expect(() => validateDirName("app(1)")).toThrow("git pathspec");
|
|
378
|
+
});
|
|
379
|
+
|
|
380
|
+
test("rejects path traversal", () => {
|
|
381
|
+
expect(() => validateDirName("..")).toThrow("Invalid dirName");
|
|
382
|
+
expect(() => validateDirName("foo/bar")).toThrow("Invalid dirName");
|
|
383
|
+
});
|
|
384
|
+
});
|
|
385
|
+
|
|
386
|
+
// ---------------------------------------------------------------------------
|
|
387
|
+
// getAppDirPath() — returns correct path
|
|
388
|
+
// ---------------------------------------------------------------------------
|
|
389
|
+
|
|
390
|
+
describe("getAppDirPath()", () => {
|
|
391
|
+
test("returns correct path for slugified apps", () => {
|
|
392
|
+
const app = createApp(makeAppParams("Path Test"));
|
|
393
|
+
const appsDir = getAppsDir();
|
|
394
|
+
const result = getAppDirPath(app.id);
|
|
395
|
+
expect(result).toBe(join(appsDir, "path-test"));
|
|
396
|
+
});
|
|
397
|
+
|
|
398
|
+
test("returns correct path for legacy apps (no dirName)", () => {
|
|
399
|
+
const appsDir = getAppsDir();
|
|
400
|
+
const fakeId = "11111111-2222-3333-4444-555555555555";
|
|
401
|
+
// Write a legacy JSON with no dirName
|
|
402
|
+
writeFileSync(
|
|
403
|
+
join(appsDir, `${fakeId}.json`),
|
|
404
|
+
JSON.stringify({ id: fakeId, name: "Legacy" }),
|
|
405
|
+
);
|
|
406
|
+
|
|
407
|
+
const result = getAppDirPath(fakeId);
|
|
408
|
+
expect(result).toBe(join(appsDir, fakeId));
|
|
409
|
+
});
|
|
410
|
+
});
|
|
411
|
+
|
|
412
|
+
// ---------------------------------------------------------------------------
|
|
413
|
+
// Guard test: no file outside app-store.ts constructs getAppsDir() + appId
|
|
414
|
+
// (Note: the primary guard test is in app-dir-path-guard.test.ts; this is
|
|
415
|
+
// a complementary check that we can import and use the guard-relevant
|
|
416
|
+
// functions without issues.)
|
|
417
|
+
// ---------------------------------------------------------------------------
|
|
418
|
+
|
|
419
|
+
describe("guard: getAppsDir + appId path construction", () => {
|
|
420
|
+
test("app-dir-path-guard.test.ts exists and covers this concern", () => {
|
|
421
|
+
// This is a meta-test to ensure the guard test file is present
|
|
422
|
+
expect(existsSync(join(__dirname, "app-dir-path-guard.test.ts"))).toBe(
|
|
423
|
+
true,
|
|
424
|
+
);
|
|
425
|
+
});
|
|
426
|
+
});
|
|
@@ -228,7 +228,7 @@ describe("buildSystemPrompt assistant feature flag filtering", () => {
|
|
|
228
228
|
expect(result).not.toContain(`**${DECLARED_SKILL_ID}**`);
|
|
229
229
|
});
|
|
230
230
|
|
|
231
|
-
test("
|
|
231
|
+
test("contacts visible but email-channel hidden when no flag overrides set (contacts defaults true, email-channel defaults false)", () => {
|
|
232
232
|
createSkillOnDisk(
|
|
233
233
|
DECLARED_SKILL_ID,
|
|
234
234
|
"Contacts",
|
|
@@ -263,8 +263,8 @@ describe("buildSystemPrompt assistant feature flag filtering", () => {
|
|
|
263
263
|
|
|
264
264
|
const result = buildSystemPrompt();
|
|
265
265
|
|
|
266
|
-
//
|
|
267
|
-
expect(result).
|
|
266
|
+
// contacts defaults to true, email-channel defaults to false
|
|
267
|
+
expect(result).toContain(`**${DECLARED_SKILL_ID}**`);
|
|
268
268
|
expect(result).not.toContain("**email-channel**");
|
|
269
269
|
});
|
|
270
270
|
|
|
@@ -466,12 +466,10 @@ describe("isAssistantFeatureFlagEnabled", () => {
|
|
|
466
466
|
|
|
467
467
|
test("missing persisted value falls back to defaults registry defaultEnabled", () => {
|
|
468
468
|
// No explicit config at all — should fall back to defaults registry
|
|
469
|
-
// which has defaultEnabled:
|
|
469
|
+
// which has defaultEnabled: true for contacts
|
|
470
470
|
const config = {} as any;
|
|
471
471
|
|
|
472
|
-
expect(isAssistantFeatureFlagEnabled(DECLARED_FLAG_KEY, config)).toBe(
|
|
473
|
-
false,
|
|
474
|
-
);
|
|
472
|
+
expect(isAssistantFeatureFlagEnabled(DECLARED_FLAG_KEY, config)).toBe(true);
|
|
475
473
|
});
|
|
476
474
|
|
|
477
475
|
test("unknown flag defaults to true when no persisted override", () => {
|
|
@@ -510,7 +508,7 @@ describe("isAssistantFeatureFlagEnabled with skillFlagKey", () => {
|
|
|
510
508
|
).toBe(false);
|
|
511
509
|
});
|
|
512
510
|
|
|
513
|
-
test("
|
|
511
|
+
test("enabled when no override set (registry default is true)", () => {
|
|
514
512
|
const config = {} as any;
|
|
515
513
|
|
|
516
514
|
expect(
|
|
@@ -518,6 +516,6 @@ describe("isAssistantFeatureFlagEnabled with skillFlagKey", () => {
|
|
|
518
516
|
skillFlagKey({ featureFlag: DECLARED_FLAG_ID })!,
|
|
519
517
|
config,
|
|
520
518
|
),
|
|
521
|
-
).toBe(
|
|
519
|
+
).toBe(true);
|
|
522
520
|
});
|
|
523
521
|
});
|