@vellumai/assistant 0.8.0 → 0.8.1
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/AGENTS.md +11 -0
- package/Dockerfile +5 -4
- package/README.md +2 -2
- package/docker-entrypoint.sh +16 -0
- package/eslint-rules/__tests__/cli-no-daemon-internals.test.ts +420 -0
- package/eslint-rules/cli-no-daemon-internals.js +283 -0
- package/eslint.config.mjs +12 -0
- package/knip.json +2 -1
- package/node_modules/@vellumai/skill-host-contracts/src/client.ts +10 -1
- package/openapi.yaml +4847 -1698
- package/package.json +3 -1
- package/scripts/generate-openapi.ts +52 -4
- package/scripts/sync-llm-catalog.ts +165 -0
- package/scripts/sync-web-search-catalog.ts +107 -0
- package/src/__tests__/actor-trust-resolver-address-fallback.test.ts +169 -0
- package/src/__tests__/agent-loop-override-profile.test.ts +26 -1
- package/src/__tests__/anthropic-provider.test.ts +92 -2
- package/src/__tests__/app-control-flow.test.ts +7 -0
- package/src/__tests__/assistant-events-sse-shed.test.ts +232 -0
- package/src/__tests__/avatar-identity-sync.test.ts +87 -0
- package/src/__tests__/background-workers-disk-pressure.test.ts +11 -22
- package/src/__tests__/btw-routes.test.ts +1 -0
- package/src/__tests__/call-site-routing-provider.test.ts +172 -45
- package/src/__tests__/cancel-resolves-conversation-key.test.ts +44 -3
- package/src/__tests__/channel-policy.test.ts +12 -0
- package/src/__tests__/checker.test.ts +89 -0
- package/src/__tests__/cli-memory-v2-reembed-skills.test.ts +35 -7
- package/src/__tests__/compact-event-conversation-id-guard.test.ts +33 -5
- package/src/__tests__/compaction-strip-metadata-clear.test.ts +26 -1
- package/src/__tests__/config-loader-backfill.test.ts +526 -102
- package/src/__tests__/config-loader-corrupt.test.ts +68 -0
- package/src/__tests__/config-loader-platform-defaults.test.ts +77 -23
- package/src/__tests__/config-schema-cmd.test.ts +63 -29
- package/src/__tests__/config-schema.test.ts +14 -3
- package/src/__tests__/config-set-platform-guard.test.ts +75 -152
- package/src/__tests__/config-set-route.test.ts +198 -0
- package/src/__tests__/config-watcher.test.ts +6 -0
- package/src/__tests__/contacts-tools.test.ts +51 -199
- package/src/__tests__/context-search-agent-protocol.test.ts +21 -2
- package/src/__tests__/context-search-agent-runner.test.ts +22 -138
- package/src/__tests__/context-search-conversations-source.test.ts +42 -16
- package/src/__tests__/context-search-fanout.test.ts +20 -157
- package/src/__tests__/context-search-memory-v2-source.test.ts +3 -3
- package/src/__tests__/context-search-types.test.ts +7 -2
- package/src/__tests__/context-window-manager.test.ts +389 -1
- package/src/__tests__/conversation-agent-loop-overflow.test.ts +1 -0
- package/src/__tests__/conversation-crud-inference-profile.test.ts +100 -0
- package/src/__tests__/conversation-error.test.ts +38 -0
- package/src/__tests__/conversation-fork-crud.test.ts +241 -1
- package/src/__tests__/conversation-inference-profile-route.test.ts +14 -14
- package/src/__tests__/conversation-init.benchmark.test.ts +1 -0
- package/src/__tests__/conversation-lifecycle.test.ts +124 -0
- package/src/__tests__/conversation-process-app-control-preactivation.test.ts +100 -1
- package/src/__tests__/conversation-process-callsite.test.ts +21 -1
- package/src/__tests__/conversation-runtime-assembly.test.ts +4 -4
- package/src/__tests__/conversation-slash-commands.test.ts +194 -2
- package/src/__tests__/conversation-surfaces-app-control.test.ts +323 -3
- package/src/__tests__/credential-security-invariants.test.ts +5 -6
- package/src/__tests__/daemon-credential-client.test.ts +56 -1
- package/src/__tests__/db-activation-state-fk-cascade.test.ts +132 -0
- package/src/__tests__/db-conversation-inference-profile-migration.test.ts +37 -0
- package/src/__tests__/db-memory-graph-event-date-repair.test.ts +43 -20
- package/src/__tests__/db-proxy-transaction.test.ts +206 -0
- package/src/__tests__/external-plugin-loader.test.ts +458 -0
- package/src/__tests__/filing-service.test.ts +23 -3
- package/src/__tests__/fixtures/mock-chrome-extension.ts +5 -0
- package/src/__tests__/gateway-only-guard.test.ts +0 -1
- package/src/__tests__/graph-extraction-event-date.test.ts +34 -0
- package/src/__tests__/handlers-skills-memory-v2-reseed.test.ts +0 -8
- package/src/__tests__/heartbeat-disk-pressure.test.ts +21 -8
- package/src/__tests__/heartbeat-service.test.ts +50 -233
- package/src/__tests__/history-repair.test.ts +89 -0
- package/src/__tests__/host-app-control-proxy.test.ts +109 -1
- package/src/__tests__/host-app-control-routes.test.ts +247 -1
- package/src/__tests__/host-browser-proxy.test.ts +416 -20
- package/src/__tests__/host-browser-routes.test.ts +325 -33
- package/src/__tests__/host-proxy-preactivation.test.ts +211 -0
- package/src/__tests__/inference-no-mode-boot-e2e.test.ts +246 -0
- package/src/__tests__/inference-profile-reaper.test.ts +154 -0
- package/src/__tests__/inference-profile-session-handler.test.ts +398 -0
- package/src/__tests__/inference-profile-session-ipc.test.ts +236 -0
- package/src/__tests__/inline-skill-load-permissions.test.ts +6 -1
- package/src/__tests__/install-skill-routing.test.ts +2 -2
- package/src/__tests__/lifecycle-memory-v2-seed.test.ts +15 -0
- package/src/__tests__/llm-callsite-catalog.test.ts +20 -1
- package/src/__tests__/llm-catalog-parity.test.ts +146 -0
- package/src/__tests__/llm-request-log-source-clickhouse.test.ts +188 -0
- package/src/__tests__/llm-request-log-source-factory.test.ts +124 -0
- package/src/__tests__/llm-resolver.test.ts +46 -0
- package/src/__tests__/managed-profile-guard.test.ts +131 -2
- package/src/__tests__/mcp-auth-routes.test.ts +1 -0
- package/src/__tests__/mcp-cli.test.ts +182 -220
- package/src/__tests__/mcp-health-check.test.ts +56 -27
- package/src/__tests__/memory-jobs-worker-lanes.test.ts +18 -11
- package/src/__tests__/message-complete-display-id.test.ts +175 -0
- package/src/__tests__/notification-platform-adapter.test.ts +229 -0
- package/src/__tests__/oauth-cli.test.ts +38 -2009
- package/src/__tests__/oauth-commands-routes.test.ts +711 -0
- package/src/__tests__/oauth-connect-routes.test.ts +174 -11
- package/src/__tests__/oauth-providers-routes.test.ts +14 -10
- package/src/__tests__/openai-responses-cutover-guard.test.ts +33 -12
- package/src/__tests__/openai-responses-provider.test.ts +17 -0
- package/src/__tests__/plugin-bootstrap.test.ts +31 -2
- package/src/__tests__/plugin-route-contribution.test.ts +31 -3
- package/src/__tests__/plugin-tool-contribution.test.ts +31 -3
- package/src/__tests__/plugin-types.test.ts +13 -11
- package/src/__tests__/process-message-background-slack.test.ts +46 -0
- package/src/__tests__/profile-entry-status.test.ts +43 -0
- package/src/__tests__/provider-managed-proxy-integration.test.ts +12 -4
- package/src/__tests__/provider-registry-ollama.test.ts +12 -4
- package/src/__tests__/provider-send-message-override-profile.test.ts +10 -4
- package/src/__tests__/relay-server.test.ts +118 -0
- package/src/__tests__/retry-thinking-tool-choice.test.ts +15 -0
- package/src/__tests__/schedule-retry.test.ts +56 -4
- package/src/__tests__/schedule-routes.test.ts +104 -0
- package/src/__tests__/scheduler-disk-pressure.test.ts +0 -4
- package/src/__tests__/scheduler-recurrence.test.ts +87 -34
- package/src/__tests__/scheduler-reuse-conversation.test.ts +161 -5
- package/src/__tests__/scheduler-wake.test.ts +0 -63
- package/src/__tests__/secret-allowlist.test.ts +1 -0
- package/src/__tests__/secret-routes-managed-proxy.test.ts +12 -4
- package/src/__tests__/shell-credential-ref.test.ts +95 -3
- package/src/__tests__/shell-tool-proxy-mode.test.ts +14 -0
- package/src/__tests__/skill-load-feature-flag.test.ts +1 -0
- package/src/__tests__/skill-load-tool.test.ts +2 -4
- package/src/__tests__/subagent-call-site-routing.test.ts +78 -16
- package/src/__tests__/suggestion-routes.test.ts +3 -3
- package/src/__tests__/sync-message-contract.test.ts +63 -0
- package/src/__tests__/task-scheduler.test.ts +88 -23
- package/src/__tests__/update-bulletin-job.test.ts +96 -193
- package/src/__tests__/usage-cli.test.ts +11 -73
- package/src/__tests__/user-plugin-loader.test.ts +145 -0
- package/src/__tests__/vercel-config.test.ts +168 -0
- package/src/__tests__/web-search-catalog-parity.test.ts +86 -0
- package/src/__tests__/web-search.test.ts +303 -2
- package/src/__tests__/workspace-migration-039-drop-legacy-llm-keys.test.ts +1 -21
- package/src/__tests__/workspace-migration-057-repair-stale-gemini-model-ids.test.ts +58 -0
- package/src/__tests__/workspace-migration-069-seed-onboarding-threads.test.ts +53 -20
- package/src/__tests__/workspace-migration-072-seed-reply-suggestion-callsite.test.ts +191 -0
- package/src/__tests__/workspace-migration-076-drop-services-inference-mode.test.ts +211 -0
- package/src/__tests__/workspace-migration-077-seed-memory-router-callsite.test.ts +174 -0
- package/src/__tests__/workspace-migration-079-home-feed-notification-only.test.ts +323 -0
- package/src/__tests__/workspace-migration-080-restrict-vercel-api-token-metadata.test.ts +299 -0
- package/src/__tests__/workspace-migration-081-backfill-bash-allowed-tools.test.ts +410 -0
- package/src/__tests__/workspace-migration-082-backfill-managed-profile-labels.test.ts +268 -0
- package/src/__tests__/workspace-migration-unify-llm-callsite-configs.test.ts +3 -3
- package/src/__tests__/workspace-release-notes-feature-flag-guard.test.ts +115 -0
- package/src/acp/__tests__/helpers/which-stub.ts +4 -2
- package/src/acp/resolve-agent.test.ts +25 -0
- package/src/acp/resolve-agent.ts +13 -2
- package/src/acp/session-manager.ts +14 -0
- package/src/approvals/guardian-request-resolvers.ts +32 -87
- package/src/calls/relay-server.ts +35 -0
- package/src/calls/relay-setup-router.ts +36 -0
- package/src/calls/types.ts +1 -0
- package/src/calls/voice-session-bridge.ts +23 -4
- package/src/channels/config.ts +14 -1
- package/src/channels/types.ts +1 -0
- package/src/cli/AGENTS.md +164 -4
- package/src/cli/__tests__/notifications.test.ts +54 -0
- package/src/cli/commands/__tests__/avatar.test.ts +540 -0
- package/src/cli/commands/__tests__/backup.test.ts +236 -776
- package/src/cli/commands/__tests__/cache.test.ts +1 -1
- package/src/cli/commands/__tests__/changelog.test.ts +593 -0
- package/src/cli/commands/__tests__/channel-verification-sessions.test.ts +503 -0
- package/src/cli/commands/__tests__/conversations-import.test.ts +515 -0
- package/src/cli/commands/__tests__/domain-register.test.ts +140 -167
- package/src/cli/commands/__tests__/domain-status.test.ts +137 -76
- package/src/cli/commands/__tests__/email-attachment.test.ts +314 -337
- package/src/cli/commands/__tests__/email-core.test.ts +579 -0
- package/src/cli/commands/__tests__/image-generation.test.ts +87 -824
- package/src/cli/commands/__tests__/inference-send.test.ts +30 -266
- package/src/cli/commands/__tests__/inference-session.test.ts +423 -0
- package/src/cli/commands/__tests__/memory-v2.test.ts +81 -110
- package/src/cli/commands/__tests__/skills.test.ts +563 -0
- package/src/cli/commands/__tests__/status.test.ts +249 -0
- package/src/cli/commands/__tests__/stt.test.ts +320 -0
- package/src/cli/commands/__tests__/tts-synthesize.test.ts +4 -603
- package/src/cli/commands/__tests__/tts.test.ts +321 -0
- package/src/cli/commands/__tests__/webhooks.test.ts +86 -511
- package/src/cli/commands/attachment.ts +8 -3
- package/src/cli/commands/audit.ts +95 -64
- package/src/cli/commands/auth.ts +61 -58
- package/src/cli/commands/avatar.ts +276 -390
- package/src/cli/commands/backup.ts +409 -505
- package/src/cli/commands/bash.ts +9 -5
- package/src/cli/commands/browser.ts +28 -9
- package/src/cli/commands/cache.ts +9 -4
- package/src/cli/commands/changelog.ts +414 -0
- package/src/cli/commands/channel-verification-sessions.ts +238 -317
- package/src/cli/commands/clients.ts +8 -3
- package/src/cli/commands/completions.ts +9 -9
- package/src/cli/commands/config.ts +102 -72
- package/src/cli/commands/contacts.ts +575 -696
- package/src/cli/commands/conversations-defer.ts +17 -69
- package/src/cli/commands/conversations-import.ts +90 -253
- package/src/cli/commands/conversations.ts +346 -436
- package/src/cli/commands/credential-execution.ts +9 -6
- package/src/cli/commands/credentials.ts +456 -736
- package/src/cli/commands/domain.ts +128 -206
- package/src/cli/commands/email.ts +606 -794
- package/src/cli/commands/gateway.ts +8 -1
- package/src/cli/commands/image-generation.ts +157 -205
- package/src/cli/commands/inference-providers.ts +352 -0
- package/src/cli/commands/inference-session.ts +415 -0
- package/src/cli/commands/inference.ts +87 -65
- package/src/cli/commands/keys.ts +8 -3
- package/src/cli/commands/mcp.ts +103 -287
- package/src/cli/commands/memory-v2.ts +162 -516
- package/src/cli/commands/notifications.ts +33 -7
- package/src/cli/commands/oauth/apps.ts +292 -261
- package/src/cli/commands/oauth/connect.ts +176 -297
- package/src/cli/commands/oauth/disconnect.ts +16 -215
- package/src/cli/commands/oauth/index.ts +49 -45
- package/src/cli/commands/oauth/mode.ts +43 -199
- package/src/cli/commands/oauth/ping.ts +17 -125
- package/src/cli/commands/oauth/providers.ts +732 -921
- package/src/cli/commands/oauth/request.ts +60 -350
- package/src/cli/commands/oauth/shared.ts +11 -121
- package/src/cli/commands/oauth/status.ts +31 -121
- package/src/cli/commands/oauth/token.ts +13 -55
- package/src/cli/commands/pending.ts +19 -10
- package/src/cli/commands/platform/__tests__/callback-routes-list.test.ts +133 -183
- package/src/cli/commands/platform/__tests__/connect.test.ts +66 -181
- package/src/cli/commands/platform/__tests__/disconnect.test.ts +71 -227
- package/src/cli/commands/platform/__tests__/status.test.ts +169 -287
- package/src/cli/commands/platform/connect.ts +16 -80
- package/src/cli/commands/platform/disconnect.ts +14 -112
- package/src/cli/commands/platform/index.ts +177 -246
- package/src/cli/commands/routes.ts +153 -336
- package/src/cli/commands/sequence.ts +316 -360
- package/src/cli/commands/skills.ts +449 -671
- package/src/cli/commands/status.ts +58 -37
- package/src/cli/commands/stt.ts +94 -262
- package/src/cli/commands/task.ts +14 -40
- package/src/cli/commands/trust.ts +8 -3
- package/src/cli/commands/tts.ts +162 -167
- package/src/cli/commands/ui.ts +35 -42
- package/src/cli/commands/usage.ts +188 -126
- package/src/cli/commands/watchers.ts +8 -3
- package/src/cli/commands/webhooks.ts +99 -193
- package/src/cli/lib/__tests__/register-command.test.ts +85 -0
- package/src/cli/lib/daemon-credential-client.ts +4 -5
- package/src/cli/lib/nested-value.ts +44 -0
- package/src/cli/lib/open-browser.ts +36 -0
- package/src/cli/lib/register-command.ts +19 -0
- package/src/cli/lib/time-ago.ts +34 -0
- package/src/cli/program.ts +2 -4
- package/src/cli/utils/__tests__/conversation-id.test.ts +66 -0
- package/src/cli/utils/__tests__/parse-duration.test.ts +49 -0
- package/src/cli/utils/conversation-id.ts +30 -0
- package/src/cli/utils/parse-duration.ts +41 -0
- package/src/config/acp-defaults.test.ts +5 -1
- package/src/config/acp-defaults.ts +11 -4
- package/src/config/bundled-skills/acp/TOOLS.json +2 -2
- package/src/config/bundled-skills/app-control/TOOLS.json +32 -0
- package/src/config/bundled-skills/contacts/SKILL.md +12 -45
- package/src/config/bundled-skills/contacts/TOOLS.json +0 -57
- package/src/config/bundled-skills/messaging/tools/messaging-archive-by-sender.ts +0 -12
- package/src/config/bundled-skills/messaging/tools/messaging-send.ts +0 -58
- package/src/config/bundled-tool-registry.ts +0 -2
- package/src/config/feature-flag-registry.json +16 -0
- package/src/config/llm-resolver.ts +16 -1
- package/src/config/loader.ts +76 -14
- package/src/config/raw-config-utils.ts +2 -30
- package/src/config/schema.ts +4 -0
- package/src/config/schemas/__tests__/memory-v2.test.ts +49 -0
- package/src/config/schemas/call-site-catalog.ts +29 -7
- package/src/config/schemas/llm-request-logs.ts +57 -0
- package/src/config/schemas/llm.ts +52 -2
- package/src/config/schemas/memory-retrospective.ts +48 -0
- package/src/config/schemas/memory-v2.ts +32 -1
- package/src/config/schemas/memory.ts +4 -0
- package/src/config/schemas/services.ts +15 -12
- package/src/config/seed-inference-profiles.ts +195 -134
- package/src/contacts/contact-store.ts +0 -61
- package/src/context/window-manager.ts +191 -5
- package/src/daemon/__tests__/conversation-lifecycle-auto-analyze.test.ts +79 -0
- package/src/daemon/__tests__/conversation-tool-setup.test.ts +109 -4
- package/src/daemon/__tests__/daemon-skill-host.test.ts +10 -4
- package/src/daemon/approval-generators.ts +23 -29
- package/src/daemon/config-watcher.ts +2 -0
- package/src/daemon/conversation-agent-loop-handlers.ts +24 -0
- package/src/daemon/conversation-agent-loop.ts +127 -97
- package/src/daemon/conversation-error.ts +21 -0
- package/src/daemon/conversation-lifecycle.ts +46 -5
- package/src/daemon/conversation-process.ts +36 -19
- package/src/daemon/conversation-runtime-assembly.ts +14 -5
- package/src/daemon/conversation-slash.ts +175 -23
- package/src/daemon/conversation-store.ts +17 -10
- package/src/daemon/conversation-surfaces.ts +76 -12
- package/src/daemon/conversation-tool-setup.ts +24 -14
- package/src/daemon/conversation.ts +48 -9
- package/src/daemon/external-plugins-bootstrap.ts +18 -8
- package/src/daemon/guardian-action-generators.ts +7 -22
- package/src/daemon/handlers/config-model.ts +8 -126
- package/src/daemon/handlers/config-slack-channel.ts +10 -7
- package/src/daemon/handlers/config-vercel.ts +3 -1
- package/src/daemon/handlers/skills.ts +84 -5
- package/src/daemon/history-repair.ts +33 -6
- package/src/daemon/host-app-control-proxy.ts +44 -19
- package/src/daemon/host-bash-proxy.ts +85 -158
- package/src/daemon/host-browser-proxy.ts +96 -35
- package/src/daemon/host-proxy-base.ts +13 -1
- package/src/daemon/host-proxy-preactivation.ts +25 -1
- package/src/daemon/identity-helpers.ts +19 -0
- package/src/daemon/lifecycle.ts +42 -43
- package/src/daemon/meet-host-supervisor.ts +15 -15
- package/src/daemon/memory-v2-startup.ts +9 -2
- package/src/daemon/message-protocol.ts +6 -0
- package/src/daemon/message-types/bookmarks.ts +18 -0
- package/src/daemon/message-types/conversations.ts +12 -9
- package/src/daemon/message-types/messages.ts +9 -1
- package/src/daemon/message-types/sync.ts +60 -0
- package/src/daemon/pkb-reminder-builder.test.ts +54 -13
- package/src/daemon/pkb-reminder-builder.ts +21 -7
- package/src/daemon/process-message.ts +56 -23
- package/src/daemon/server.ts +23 -18
- package/src/daemon/shutdown-handlers.ts +0 -2
- package/src/daemon/tool-setup-types.ts +9 -0
- package/src/daemon/tool-side-effects.ts +6 -4
- package/src/daemon/wake-target-adapter.ts +11 -0
- package/src/export/transcript-formatter.ts +61 -2
- package/src/filing/filing-service.ts +40 -53
- package/src/heartbeat/__tests__/heartbeat-service.test.ts +359 -0
- package/src/heartbeat/heartbeat-run-store.ts +2 -1
- package/src/heartbeat/heartbeat-service.ts +148 -127
- package/src/home/__tests__/feed-types.test.ts +63 -131
- package/src/home/__tests__/feed-writer.test.ts +77 -278
- package/src/home/__tests__/post-connect-feed.test.ts +9 -12
- package/src/home/feed-types.ts +19 -73
- package/src/home/feed-writer.ts +25 -156
- package/src/home/post-connect-feed.ts +1 -3
- package/src/ipc/__tests__/cli-ipc.test.ts +2 -0
- package/src/ipc/__tests__/email-ipc.test.ts +506 -0
- package/src/ipc/__tests__/exit-helper.test.ts +104 -0
- package/src/ipc/__tests__/streaming-client.test.ts +237 -0
- package/src/ipc/__tests__/streaming-framing.test.ts +142 -0
- package/src/ipc/assistant-server.ts +55 -6
- package/src/ipc/cli-client.ts +370 -50
- package/src/ipc/routes/db-proxy-transaction.ts +151 -0
- package/src/ipc/skill-routes/__tests__/events-ipc.test.ts +60 -0
- package/src/ipc/skill-routes/events.ts +30 -3
- package/src/live-voice/__tests__/live-voice-session-manager.test.ts +46 -0
- package/src/live-voice/__tests__/runtime-websocket-shell.test.ts +1 -0
- package/src/live-voice/live-voice-session-manager.ts +11 -4
- package/src/live-voice/live-voice-session.ts +14 -6
- package/src/memory/__tests__/bookmark-crud.test.ts +258 -0
- package/src/memory/__tests__/bookmark-schema.test.ts +181 -0
- package/src/memory/__tests__/conversation-types.test.ts +36 -0
- package/src/memory/__tests__/find-most-recent-retrospective-for.test.ts +130 -0
- package/src/memory/__tests__/memory-retrospective-enqueue.test.ts +177 -0
- package/src/memory/__tests__/memory-retrospective-job.test.ts +328 -0
- package/src/memory/__tests__/memory-retrospective-startup-cleanup.test.ts +213 -0
- package/src/memory/__tests__/memory-retrospective-trigger-check.test.ts +90 -0
- package/src/memory/__tests__/memory-v2-activation-log-store.test.ts +69 -0
- package/src/memory/__tests__/memory-v2-concept-frequency.test.ts +3 -0
- package/src/memory/bookmark-crud.ts +179 -0
- package/src/memory/context-search/__tests__/agent-runner-redaction.test.ts +31 -9
- package/src/memory/context-search/agent-protocol.ts +5 -1
- package/src/memory/context-search/agent-runner.ts +60 -85
- package/src/memory/context-search/limits.ts +1 -4
- package/src/memory/context-search/search.ts +23 -113
- package/src/memory/context-search/sources/conversations.ts +18 -6
- package/src/memory/context-search/sources/memory-v2.ts +39 -14
- package/src/memory/context-search/sources/memory.ts +7 -0
- package/src/memory/context-search/sources/workspace.ts +13 -10
- package/src/memory/context-search/types.ts +1 -1
- package/src/memory/conversation-bootstrap.ts +11 -0
- package/src/memory/conversation-crud.ts +312 -10
- package/src/memory/conversation-queries.ts +9 -5
- package/src/memory/conversation-title-service.ts +1 -0
- package/src/memory/conversation-types.ts +16 -0
- package/src/memory/db-init.ts +14 -0
- package/src/memory/embedding-backend.ts +2 -1
- package/src/memory/embedding-runtime-manager.ts +1 -2
- package/src/memory/graph/__tests__/remember-description.test.ts +55 -0
- package/src/memory/graph/conversation-graph-memory.ts +76 -5
- package/src/memory/graph/extraction.ts +4 -0
- package/src/memory/graph/graph-memory-state-store.ts +16 -3
- package/src/memory/graph/tool-handlers.ts +17 -7
- package/src/memory/graph/tools.ts +44 -5
- package/src/memory/indexer.ts +17 -0
- package/src/memory/jobs/__tests__/embed-concept-page.test.ts +13 -15
- package/src/memory/jobs/embed-concept-page.ts +45 -9
- package/src/memory/jobs-store.ts +51 -1
- package/src/memory/jobs-worker.ts +52 -3
- package/src/memory/llm-request-log-source-clickhouse.ts +317 -0
- package/src/memory/llm-request-log-source-local.ts +26 -0
- package/src/memory/llm-request-log-source.ts +97 -0
- package/src/memory/llm-request-log-store.ts +1 -1
- package/src/memory/memory-retrospective-constants.ts +13 -0
- package/src/memory/memory-retrospective-enqueue.ts +114 -0
- package/src/memory/memory-retrospective-job.ts +351 -0
- package/src/memory/memory-retrospective-startup-cleanup.ts +108 -0
- package/src/memory/memory-retrospective-state.ts +162 -0
- package/src/memory/memory-retrospective-trigger-check.ts +91 -0
- package/src/memory/memory-v2-activation-log-store.ts +49 -5
- package/src/memory/memory-v2-concept-frequency.ts +4 -0
- package/src/memory/message-content.ts +38 -1
- package/src/memory/migrations/227-add-conversation-inference-profile.ts +6 -1
- package/src/memory/migrations/228-rename-inference-profile-snake-case.ts +20 -7
- package/src/memory/migrations/229-delete-private-conversations.test.ts +70 -1
- package/src/memory/migrations/229-delete-private-conversations.ts +12 -0
- package/src/memory/migrations/231-repair-memory-graph-event-dates.ts +16 -2
- package/src/memory/migrations/240-conversation-inference-profile-session.ts +25 -0
- package/src/memory/migrations/241-activation-state-fk-cascade.ts +50 -0
- package/src/memory/migrations/242-message-bookmarks.ts +38 -0
- package/src/memory/migrations/243-provider-connections.ts +68 -0
- package/src/memory/migrations/244-provider-connection-status-label.ts +23 -0
- package/src/memory/migrations/245-memory-retrospective-state.ts +36 -0
- package/src/memory/migrations/246-backfill-provider-connection-label.ts +81 -0
- package/src/memory/migrations/__tests__/244-provider-connection-status-label.test.ts +84 -0
- package/src/memory/migrations/__tests__/245-memory-retrospective-state.test.ts +125 -0
- package/src/memory/migrations/__tests__/246-backfill-provider-connection-label.test.ts +192 -0
- package/src/memory/migrations/index.ts +7 -0
- package/src/memory/published-pages-store.ts +16 -0
- package/src/memory/schema/bookmarks.ts +38 -0
- package/src/memory/schema/conversations.ts +2 -0
- package/src/memory/schema/index.ts +2 -0
- package/src/memory/schema/inference.ts +29 -0
- package/src/memory/schema/memory-core.ts +9 -0
- package/src/memory/search/semantic.ts +1 -4
- package/src/memory/v2/__tests__/__snapshots__/prompts-router.test.ts.snap +27 -0
- package/src/memory/v2/__tests__/activation-store.test.ts +5 -5
- package/src/memory/v2/__tests__/activation.test.ts +11 -4
- package/src/memory/v2/__tests__/backfill-jobs.test.ts +38 -21
- package/src/memory/v2/__tests__/consolidation-job.test.ts +123 -135
- package/src/memory/v2/__tests__/edge-index.test.ts +1 -1
- package/src/memory/v2/__tests__/frontmatter-sweep.test.ts +111 -0
- package/src/memory/v2/__tests__/injection.test.ts +628 -10
- package/src/memory/v2/__tests__/migration.test.ts +7 -3
- package/src/memory/v2/__tests__/page-index.test.ts +277 -0
- package/src/memory/v2/__tests__/page-store.test.ts +14 -1
- package/src/memory/v2/__tests__/prompts-router.test.ts +257 -0
- package/src/memory/v2/__tests__/qdrant.test.ts +72 -0
- package/src/memory/v2/__tests__/reranker.test.ts +4 -4
- package/src/memory/v2/__tests__/router.test.ts +516 -0
- package/src/memory/v2/__tests__/sim.test.ts +45 -1
- package/src/memory/v2/__tests__/skill-store.test.ts +58 -3
- package/src/memory/v2/__tests__/static-context.test.ts +7 -22
- package/src/memory/v2/__tests__/sweep-job.test.ts +95 -0
- package/src/memory/v2/activation-store.ts +34 -5
- package/src/memory/v2/activation.ts +40 -27
- package/src/memory/v2/backfill-jobs.ts +17 -84
- package/src/memory/v2/consolidation-job.ts +85 -78
- package/src/memory/v2/frontmatter-sweep.ts +91 -0
- package/src/memory/v2/injection.ts +440 -109
- package/src/memory/v2/migration.ts +117 -20
- package/src/memory/v2/page-index.ts +191 -0
- package/src/memory/v2/page-store.ts +3 -0
- package/src/memory/v2/prompts/consolidation.ts +9 -7
- package/src/memory/v2/prompts/router.ts +192 -0
- package/src/memory/v2/qdrant.ts +100 -87
- package/src/memory/v2/reranker.ts +14 -7
- package/src/memory/v2/router.ts +322 -0
- package/src/memory/v2/sim.ts +25 -12
- package/src/memory/v2/skill-store.ts +118 -29
- package/src/memory/v2/static-context.ts +16 -9
- package/src/memory/v2/sweep-job.ts +122 -96
- package/src/memory/v2/types.ts +10 -6
- package/src/memory/validation.ts +13 -0
- package/src/notifications/__tests__/emit-signal-home-feed.test.ts +182 -0
- package/src/notifications/__tests__/home-feed-side-effect.test.ts +199 -0
- package/src/notifications/__tests__/signal-registry.test.ts +17 -0
- package/src/notifications/adapters/platform.ts +171 -0
- package/src/notifications/conversation-pairing.ts +2 -2
- package/src/notifications/copy-composer.ts +15 -0
- package/src/notifications/destination-resolver.ts +21 -0
- package/src/notifications/emit-signal.ts +28 -1
- package/src/notifications/home-feed-side-effect.ts +111 -0
- package/src/notifications/signal.ts +5 -0
- package/src/permissions/checker.ts +12 -0
- package/src/permissions/ipc-risk-types.ts +2 -0
- package/src/plugin-api/index.ts +13 -0
- package/src/plugin-api/package.json +12 -0
- package/src/plugin-api/types.ts +62 -0
- package/src/plugins/defaults/injectors.ts +19 -3
- package/src/plugins/external-plugin-loader.ts +294 -0
- package/src/plugins/types.ts +46 -30
- package/src/plugins/user-loader.ts +64 -41
- package/src/proactive-artifact/job.test.ts +12 -4
- package/src/proactive-artifact/job.ts +4 -0
- package/src/proactive-artifact/trigger-state.test.ts +9 -0
- package/src/proactive-artifact/trigger-state.ts +4 -0
- package/src/prompts/__tests__/system-prompt.test.ts +105 -0
- package/src/prompts/system-prompt.ts +22 -1
- package/src/prompts/update-bulletin-job.ts +61 -73
- package/src/providers/__tests__/dispatch-connection-routing.test.ts +279 -0
- package/src/providers/__tests__/inference.test.ts +288 -0
- package/src/providers/__tests__/provider-env-vars.test.ts +6 -0
- package/src/providers/__tests__/provider-secret-catalog.test.ts +6 -0
- package/src/providers/__tests__/retry-callsite.test.ts +14 -32
- package/src/providers/__tests__/satellite-connection-routing.test.ts +510 -0
- package/src/providers/__tests__/search-provider-catalog.test.ts +80 -0
- package/src/providers/anthropic/client.ts +95 -26
- package/src/providers/call-site-routing.ts +94 -16
- package/src/providers/connection-resolution.ts +163 -0
- package/src/providers/inference/__tests__/connections-status-label.test.ts +250 -0
- package/src/providers/inference/adapter-factory.ts +173 -0
- package/src/providers/inference/auth.ts +112 -0
- package/src/providers/inference/backfill.ts +196 -0
- package/src/providers/inference/connections.ts +356 -0
- package/src/providers/inference/resolve-auth.ts +65 -0
- package/src/providers/model-catalog.ts +104 -6
- package/src/providers/openai/responses-provider.ts +4 -2
- package/src/providers/provider-env-vars.ts +17 -7
- package/src/providers/provider-secret-catalog.ts +49 -30
- package/src/providers/provider-send-message.ts +41 -20
- package/src/providers/registry.ts +143 -159
- package/src/providers/retry.ts +18 -10
- package/src/providers/search-provider-catalog.ts +121 -0
- package/src/runtime/AGENTS.md +18 -5
- package/src/runtime/__tests__/background-job-runner.test.ts +357 -0
- package/src/runtime/__tests__/pre-first-message-gate.test.ts +82 -0
- package/src/runtime/actor-trust-resolver.ts +32 -10
- package/src/runtime/agent-wake.ts +35 -6
- package/src/runtime/assistant-event-hub.ts +3 -85
- package/src/runtime/auth/route-policy.ts +303 -8
- package/src/runtime/auth/same-actor.ts +2 -0
- package/src/runtime/background-job-runner.ts +339 -0
- package/src/runtime/btw-sidechain.ts +1 -0
- package/src/runtime/http-router.ts +36 -1
- package/src/runtime/http-server.ts +31 -5
- package/src/runtime/http-types.ts +2 -0
- package/src/runtime/middleware/__tests__/request-logger.test.ts +162 -0
- package/src/runtime/middleware/request-logger.ts +62 -1
- package/src/runtime/pre-first-message-gate.ts +83 -0
- package/src/runtime/routes/__tests__/backup-routes.test.ts +8 -1
- package/src/runtime/routes/__tests__/bookmark-routes.test.ts +251 -0
- package/src/runtime/routes/__tests__/connection-routes-vs-cli-parity.test.ts +142 -0
- package/src/runtime/routes/__tests__/conversation-management-routes.test.ts +315 -0
- package/src/runtime/routes/__tests__/conversation-query-routes.test.ts +189 -0
- package/src/runtime/routes/__tests__/home-feed-routes.test.ts +15 -136
- package/src/runtime/routes/__tests__/inference-provider-connection-routes.test.ts +736 -0
- package/src/runtime/routes/__tests__/memory-v2-routes.test.ts +4 -4
- package/src/runtime/routes/__tests__/stt-routes.test.ts +5 -1
- package/src/runtime/routes/__tests__/surface-action-routes.test.ts +384 -0
- package/src/runtime/routes/__tests__/tts-routes.test.ts +6 -2
- package/src/runtime/routes/acp-routes.ts +10 -8
- package/src/runtime/routes/app-management-routes.ts +228 -3
- package/src/runtime/routes/approval-routes.ts +0 -18
- package/src/runtime/routes/audit-routes.ts +43 -0
- package/src/runtime/routes/auth-routes.ts +72 -0
- package/src/runtime/routes/avatar-routes.ts +273 -20
- package/src/runtime/routes/backup-routes.ts +406 -2
- package/src/runtime/routes/bookmark-routes.ts +154 -0
- package/src/runtime/routes/channel-verification-routes.ts +2 -1
- package/src/runtime/routes/contact-routes.ts +0 -160
- package/src/runtime/routes/conversation-cli-routes.ts +192 -0
- package/src/runtime/routes/conversation-management-routes.ts +30 -43
- package/src/runtime/routes/conversation-query-routes.ts +334 -86
- package/src/runtime/routes/conversation-routes.ts +31 -10
- package/src/runtime/routes/conversations-import-routes.ts +229 -0
- package/src/runtime/routes/credential-routes.ts +540 -0
- package/src/runtime/routes/debug-routes.ts +2 -2
- package/src/runtime/routes/document-pdf-renderer.ts +5 -1
- package/src/runtime/routes/domain-routes.ts +167 -0
- package/src/runtime/routes/email-routes.ts +603 -0
- package/src/runtime/routes/errors.ts +2 -2
- package/src/runtime/routes/events-routes.ts +192 -0
- package/src/runtime/routes/home-feed-routes.ts +6 -78
- package/src/runtime/routes/host-app-control-routes.ts +44 -2
- package/src/runtime/routes/host-browser-routes.ts +103 -22
- package/src/runtime/routes/http-adapter.ts +2 -0
- package/src/runtime/routes/identity-routes.ts +5 -0
- package/src/runtime/routes/image-generation-routes.ts +99 -0
- package/src/runtime/routes/inbound-stages/background-dispatch.test.ts +137 -1
- package/src/runtime/routes/inbound-stages/background-dispatch.ts +87 -7
- package/src/runtime/routes/inbound-stages/guardian-reply-intercept.test.ts +156 -0
- package/src/runtime/routes/inbound-stages/guardian-reply-intercept.ts +22 -4
- package/src/runtime/routes/index.ts +36 -0
- package/src/runtime/routes/inference-profile-session-handler.ts +312 -0
- package/src/runtime/routes/inference-profile-session-reaper.ts +98 -0
- package/src/runtime/routes/inference-profile-session-routes.ts +146 -0
- package/src/runtime/routes/inference-provider-connection-routes.ts +317 -0
- package/src/runtime/routes/inference-send-routes.ts +115 -0
- package/src/runtime/routes/integrations/twilio.ts +1 -0
- package/src/runtime/routes/mcp-auth-routes.ts +283 -9
- package/src/runtime/routes/memory-v2-routes.ts +13 -398
- package/src/runtime/routes/notification-routes.ts +2 -0
- package/src/runtime/routes/oauth-apps.ts +112 -7
- package/src/runtime/routes/oauth-commands-routes.ts +1007 -0
- package/src/runtime/routes/oauth-connect-routes.ts +67 -5
- package/src/runtime/routes/oauth-providers.ts +298 -8
- package/src/runtime/routes/platform-routes.ts +336 -0
- package/src/runtime/routes/playground/inject-failures.ts +2 -1
- package/src/runtime/routes/playground/reset-circuit.ts +2 -1
- package/src/runtime/routes/playground/state.ts +2 -1
- package/src/runtime/routes/publish-routes.ts +221 -0
- package/src/runtime/routes/schedule-routes.ts +82 -0
- package/src/runtime/routes/sequence-routes.ts +291 -0
- package/src/runtime/routes/settings-routes.ts +2 -10
- package/src/runtime/routes/skills-routes.ts +31 -1
- package/src/runtime/routes/stt-routes.ts +240 -3
- package/src/runtime/routes/surface-action-routes.ts +43 -7
- package/src/runtime/routes/tts-routes.ts +67 -0
- package/src/runtime/routes/types.ts +32 -0
- package/src/runtime/routes/user-routes-cli.ts +243 -0
- package/src/runtime/routes/webhook-routes.ts +165 -0
- package/src/runtime/sync/resource-sync-events.ts +25 -0
- package/src/runtime/sync/sync-publisher.test.ts +105 -0
- package/src/runtime/sync/sync-publisher.ts +21 -0
- package/src/schedule/scheduler.ts +200 -123
- package/src/security/__tests__/provider-key-env-fallback.test.ts +12 -6
- package/src/security/secret-patterns.ts +3 -0
- package/src/sequence/engine.ts +38 -40
- package/src/subagent/manager.ts +20 -15
- package/src/tools/browser/__tests__/browser-execution-acquire.test.ts +206 -0
- package/src/tools/browser/browser-execution.ts +15 -4
- package/src/tools/browser/cdp-client/__tests__/factory.test.ts +174 -0
- package/src/tools/browser/cdp-client/cdp-inspect/__tests__/ws-transport.test.ts +16 -13
- package/src/tools/browser/cdp-client/extension-cdp-client.ts +24 -1
- package/src/tools/browser/cdp-client/factory.ts +66 -5
- package/src/tools/browser/runtime-check.ts +77 -0
- package/src/tools/memory/register.test.ts +3 -3
- package/src/tools/memory/register.ts +9 -1
- package/src/tools/network/__tests__/web-search.test.ts +156 -0
- package/src/tools/network/web-search.ts +280 -37
- package/src/tools/permission-checker.ts +13 -5
- package/src/tools/subagent/spawn.ts +3 -3
- package/src/tools/terminal/shell.ts +44 -0
- package/src/usage/attribution.ts +3 -2
- package/src/util/pricing.ts +86 -160
- package/src/watcher/__tests__/engine.test.ts +301 -0
- package/src/watcher/constants.ts +7 -0
- package/src/watcher/engine.ts +90 -90
- package/src/workspace/migrations/046-seed-conversation-starters-callsite.ts +6 -9
- package/src/workspace/migrations/054-seed-recall-callsite.ts +10 -1
- package/src/workspace/migrations/057-repair-stale-gemini-model-ids.ts +28 -4
- package/src/workspace/migrations/069-seed-onboarding-threads.ts +8 -2
- package/src/workspace/migrations/072-seed-reply-suggestion-callsite.ts +104 -0
- package/src/workspace/migrations/073-repair-recall-callsite-empty-profile.ts +93 -0
- package/src/workspace/migrations/074-drop-deprecated-secret-detection-keys.ts +117 -0
- package/src/workspace/migrations/075-memory-v2-bm25-b-default-reembed.ts +61 -0
- package/src/workspace/migrations/076-drop-services-inference-mode.ts +62 -0
- package/src/workspace/migrations/077-seed-memory-router-callsite.ts +89 -0
- package/src/workspace/migrations/078-release-notes-tavily-web-search.ts +66 -0
- package/src/workspace/migrations/079-home-feed-notification-only.ts +197 -0
- package/src/workspace/migrations/080-restrict-vercel-api-token-metadata.ts +182 -0
- package/src/workspace/migrations/081-backfill-bash-allowed-tools-for-injection-credentials.ts +160 -0
- package/src/workspace/migrations/082-backfill-managed-profile-labels.ts +154 -0
- package/src/workspace/migrations/registry.ts +22 -0
- package/src/workspace/migrations/runner.ts +13 -2
- package/src/workspace/migrations/types.ts +13 -3
- package/src/workspace/provider-commit-message-generator.ts +3 -2
- package/src/__tests__/context-search-pkb-source.test.ts +0 -498
- package/src/__tests__/credentials-cli.test.ts +0 -1225
- package/src/__tests__/memory-admin-recall.test.ts +0 -213
- package/src/approvals/__tests__/guardian-feed-event.test.ts +0 -303
- package/src/cli/commands/__tests__/email-download.test.ts +0 -260
- package/src/cli/commands/__tests__/email-list.test.ts +0 -216
- package/src/cli/commands/__tests__/email-register.test.ts +0 -186
- package/src/cli/commands/__tests__/email-send.test.ts +0 -416
- package/src/cli/commands/__tests__/email-status.test.ts +0 -185
- package/src/cli/commands/__tests__/email-unregister.test.ts +0 -168
- package/src/cli/commands/__tests__/routes.test.ts +0 -562
- package/src/cli/commands/__tests__/stt-transcribe.test.ts +0 -454
- package/src/cli/commands/autonomy.ts +0 -365
- package/src/cli/commands/memory.ts +0 -424
- package/src/cli/commands/oauth/__tests__/connect.test.ts +0 -947
- package/src/cli/commands/oauth/__tests__/disconnect.test.ts +0 -686
- package/src/cli/commands/oauth/__tests__/mode.test.ts +0 -632
- package/src/cli/commands/oauth/__tests__/ping.test.ts +0 -631
- package/src/cli/commands/oauth/__tests__/providers-delete.test.ts +0 -573
- package/src/cli/commands/oauth/__tests__/providers-register.test.ts +0 -330
- package/src/cli/commands/oauth/__tests__/providers-update.test.ts +0 -521
- package/src/cli/commands/oauth/__tests__/status.test.ts +0 -551
- package/src/cli/commands/oauth/__tests__/token.test.ts +0 -420
- package/src/cli/lib/daemon-avatar-client.ts +0 -37
- package/src/config/bundled-skills/contacts/tools/contact-upsert.ts +0 -87
- package/src/config/bundled-skills/messaging/tools/__tests__/messaging-feed-events.test.ts +0 -207
- package/src/daemon/__tests__/conversation-feed-event.test.ts +0 -304
- package/src/heartbeat/__tests__/heartbeat-feed-event.test.ts +0 -233
- package/src/home/__tests__/assistant-feed-authoring.test.ts +0 -156
- package/src/home/__tests__/emit-feed-event.test.ts +0 -169
- package/src/home/__tests__/feed-population-integration.test.ts +0 -312
- package/src/home/__tests__/feed-scheduler.test.ts +0 -222
- package/src/home/__tests__/phase5-exit-criteria.test.ts +0 -229
- package/src/home/__tests__/platform-gmail-digest.test.ts +0 -222
- package/src/home/__tests__/rollup-producer.test.ts +0 -507
- package/src/home/assistant-feed-authoring.ts +0 -135
- package/src/home/emit-feed-event.ts +0 -169
- package/src/home/feed-scheduler.ts +0 -281
- package/src/home/platform-gmail-digest.ts +0 -163
- package/src/home/rewrite-command-preview.ts +0 -66
- package/src/home/rewrite-feed-title.ts +0 -58
- package/src/home/rollup-producer.ts +0 -426
- package/src/memory/admin.ts +0 -326
- package/src/memory/context-search/sources/pkb.ts +0 -476
- package/src/memory/graph/compaction.ts +0 -299
- /package/src/cli/{commands → lib}/cache-fs.ts +0 -0
|
@@ -403,7 +403,11 @@ describe("synthesizeConceptPage", () => {
|
|
|
403
403
|
);
|
|
404
404
|
const page = await synthesizeConceptPage(cluster, null, provider);
|
|
405
405
|
expect(page.slug).toBe("alice-ides");
|
|
406
|
-
expect(page.frontmatter).toEqual({
|
|
406
|
+
expect(page.frontmatter).toEqual({
|
|
407
|
+
edges: [],
|
|
408
|
+
ref_files: [],
|
|
409
|
+
ref_urls: [],
|
|
410
|
+
});
|
|
407
411
|
expect(page.body).toContain("VS Code");
|
|
408
412
|
expect(page.body.endsWith("\n")).toBe(true);
|
|
409
413
|
});
|
|
@@ -625,7 +629,7 @@ describe("collapseEdges", () => {
|
|
|
625
629
|
|
|
626
630
|
describe("enqueueEmbeds", () => {
|
|
627
631
|
test("enqueues one embed_concept_page job per slug", () => {
|
|
628
|
-
expect(enqueueEmbeds(["alice", "bob", "carol"])).toBe(3);
|
|
632
|
+
expect(enqueueEmbeds(["alice", "bob", "carol"], database)).toBe(3);
|
|
629
633
|
expect(enqueuedJobs).toEqual([
|
|
630
634
|
{ type: "embed_concept_page", payload: { slug: "alice" } },
|
|
631
635
|
{ type: "embed_concept_page", payload: { slug: "bob" } },
|
|
@@ -634,7 +638,7 @@ describe("enqueueEmbeds", () => {
|
|
|
634
638
|
});
|
|
635
639
|
|
|
636
640
|
test("empty list is a no-op", () => {
|
|
637
|
-
expect(enqueueEmbeds([])).toBe(0);
|
|
641
|
+
expect(enqueueEmbeds([], database)).toBe(0);
|
|
638
642
|
expect(enqueuedJobs).toEqual([]);
|
|
639
643
|
});
|
|
640
644
|
});
|
|
@@ -0,0 +1,277 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Tests for `memory/v2/page-index.ts` — the router-prompt page index built
|
|
3
|
+
* from concept pages plus seeded skill entries.
|
|
4
|
+
*
|
|
5
|
+
* Tests live in temp workspaces (`mkdtemp`) and never touch `~/.vellum/`. The
|
|
6
|
+
* skill-store module is mocked so `listSkillEntries()` returns deterministic
|
|
7
|
+
* fixtures, and `page-store.js` is wrapped so we can simulate read failures
|
|
8
|
+
* without breaking writes.
|
|
9
|
+
*/
|
|
10
|
+
|
|
11
|
+
import { existsSync, mkdtempSync, rmSync, writeFileSync } from "node:fs";
|
|
12
|
+
import { tmpdir } from "node:os";
|
|
13
|
+
import { join } from "node:path";
|
|
14
|
+
import { afterEach, beforeEach, describe, expect, mock, test } from "bun:test";
|
|
15
|
+
|
|
16
|
+
import { makeMockLogger } from "../../../__tests__/helpers/mock-logger.js";
|
|
17
|
+
import type { ConceptPage, SkillEntry } from "../types.js";
|
|
18
|
+
|
|
19
|
+
// ---------------------------------------------------------------------------
|
|
20
|
+
// Mocks — programmable skill list, programmable readPage failure set,
|
|
21
|
+
// recursive no-op logger. Mocks are installed BEFORE any imports of the
|
|
22
|
+
// module under test so the page-index module observes them at load time.
|
|
23
|
+
// ---------------------------------------------------------------------------
|
|
24
|
+
|
|
25
|
+
const skillState: { entries: SkillEntry[] } = { entries: [] };
|
|
26
|
+
const failingSlugs = new Set<string>();
|
|
27
|
+
|
|
28
|
+
mock.module("../../../util/logger.js", () => ({
|
|
29
|
+
getLogger: () => makeMockLogger(),
|
|
30
|
+
}));
|
|
31
|
+
|
|
32
|
+
mock.module("../skill-store.js", () => ({
|
|
33
|
+
SKILL_SLUG_PREFIX: "skills/",
|
|
34
|
+
listSkillEntries: () => skillState.entries,
|
|
35
|
+
}));
|
|
36
|
+
|
|
37
|
+
// Wrap page-store so we can simulate read failures via `failingSlugs`.
|
|
38
|
+
// Re-export every other binding identity-style so writes still work.
|
|
39
|
+
//
|
|
40
|
+
// Capture each real export into a local const BEFORE installing the mock —
|
|
41
|
+
// module namespaces hold live bindings, so a post-mock dereference of
|
|
42
|
+
// `realPageStore.readPage` would resolve to the mocked function and recurse
|
|
43
|
+
// infinitely.
|
|
44
|
+
const realPageStore = await import("../page-store.js");
|
|
45
|
+
const realReadPage = realPageStore.readPage;
|
|
46
|
+
mock.module("../page-store.js", () => ({
|
|
47
|
+
...realPageStore,
|
|
48
|
+
readPage: async (workspaceDir: string, slug: string) => {
|
|
49
|
+
if (failingSlugs.has(slug)) {
|
|
50
|
+
throw new Error(`simulated read failure for ${slug}`);
|
|
51
|
+
}
|
|
52
|
+
return realReadPage(workspaceDir, slug);
|
|
53
|
+
},
|
|
54
|
+
}));
|
|
55
|
+
|
|
56
|
+
const { getPageIndex, invalidatePageIndex } = await import("../page-index.js");
|
|
57
|
+
const { writePage } = await import("../page-store.js");
|
|
58
|
+
const { invalidateEdgeIndex } = await import("../edge-index.js");
|
|
59
|
+
|
|
60
|
+
let workspaceDir: string;
|
|
61
|
+
|
|
62
|
+
beforeEach(() => {
|
|
63
|
+
workspaceDir = mkdtempSync(join(tmpdir(), "vellum-memory-v2-page-index-"));
|
|
64
|
+
skillState.entries = [];
|
|
65
|
+
failingSlugs.clear();
|
|
66
|
+
});
|
|
67
|
+
|
|
68
|
+
afterEach(() => {
|
|
69
|
+
invalidatePageIndex();
|
|
70
|
+
invalidateEdgeIndex();
|
|
71
|
+
if (existsSync(workspaceDir)) {
|
|
72
|
+
rmSync(workspaceDir, { recursive: true, force: true });
|
|
73
|
+
}
|
|
74
|
+
});
|
|
75
|
+
|
|
76
|
+
function makePage(
|
|
77
|
+
slug: string,
|
|
78
|
+
opts: { edges?: string[]; summary?: string; body?: string } = {},
|
|
79
|
+
): ConceptPage {
|
|
80
|
+
return {
|
|
81
|
+
slug,
|
|
82
|
+
frontmatter: {
|
|
83
|
+
edges: opts.edges ?? [],
|
|
84
|
+
ref_files: [],
|
|
85
|
+
ref_urls: [],
|
|
86
|
+
...(opts.summary !== undefined ? { summary: opts.summary } : {}),
|
|
87
|
+
},
|
|
88
|
+
body: opts.body ?? "",
|
|
89
|
+
};
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
// ---------------------------------------------------------------------------
|
|
93
|
+
// Build & cache
|
|
94
|
+
// ---------------------------------------------------------------------------
|
|
95
|
+
|
|
96
|
+
describe("getPageIndex", () => {
|
|
97
|
+
test("returns an empty index when there are no pages and no skills", async () => {
|
|
98
|
+
const idx = await getPageIndex(workspaceDir);
|
|
99
|
+
expect(idx.entries).toEqual([]);
|
|
100
|
+
expect(idx.bySlug.size).toBe(0);
|
|
101
|
+
expect(idx.byId.size).toBe(0);
|
|
102
|
+
expect(idx.rendered).toBe("");
|
|
103
|
+
});
|
|
104
|
+
|
|
105
|
+
test("caches the result so repeat calls reuse the prior build", async () => {
|
|
106
|
+
await writePage(workspaceDir, makePage("alice", { summary: "First" }));
|
|
107
|
+
|
|
108
|
+
const first = await getPageIndex(workspaceDir);
|
|
109
|
+
// Mutate disk after the first read WITHOUT going through `writePage`,
|
|
110
|
+
// which would invalidate the page-index cache by design. The raw
|
|
111
|
+
// filesystem write simulates an out-of-band file appearing — without
|
|
112
|
+
// the cache the second call would observe it and return a different
|
|
113
|
+
// object.
|
|
114
|
+
writeFileSync(
|
|
115
|
+
join(workspaceDir, "memory", "concepts", "bob.md"),
|
|
116
|
+
"---\nedges: []\nref_files: []\nref_urls: []\nsummary: Second\n---\n",
|
|
117
|
+
"utf-8",
|
|
118
|
+
);
|
|
119
|
+
|
|
120
|
+
const second = await getPageIndex(workspaceDir);
|
|
121
|
+
expect(second).toBe(first);
|
|
122
|
+
expect(second.entries.map((e) => e.slug)).toEqual(["alice"]);
|
|
123
|
+
});
|
|
124
|
+
|
|
125
|
+
test("writePage invalidates the cache so the next call sees the new page", async () => {
|
|
126
|
+
await writePage(workspaceDir, makePage("alice", { summary: "First" }));
|
|
127
|
+
const before = await getPageIndex(workspaceDir);
|
|
128
|
+
|
|
129
|
+
// `writePage` calls `invalidatePageIndex(workspaceDir)` as a side
|
|
130
|
+
// effect — verify that contract here so the cache-hit test above
|
|
131
|
+
// can't accidentally pass because writePage stopped invalidating.
|
|
132
|
+
await writePage(workspaceDir, makePage("bob", { summary: "Second" }));
|
|
133
|
+
|
|
134
|
+
const after = await getPageIndex(workspaceDir);
|
|
135
|
+
expect(after).not.toBe(before);
|
|
136
|
+
expect(after.entries.map((e) => e.slug)).toEqual(["alice", "bob"]);
|
|
137
|
+
});
|
|
138
|
+
|
|
139
|
+
test("invalidatePageIndex(workspaceDir) forces a rebuild on the next call", async () => {
|
|
140
|
+
await writePage(workspaceDir, makePage("alice", { summary: "First" }));
|
|
141
|
+
const before = await getPageIndex(workspaceDir);
|
|
142
|
+
|
|
143
|
+
invalidatePageIndex(workspaceDir);
|
|
144
|
+
await writePage(workspaceDir, makePage("bob", { summary: "Second" }));
|
|
145
|
+
|
|
146
|
+
const after = await getPageIndex(workspaceDir);
|
|
147
|
+
expect(after).not.toBe(before);
|
|
148
|
+
expect(after.entries.map((e) => e.slug)).toEqual(["alice", "bob"]);
|
|
149
|
+
});
|
|
150
|
+
|
|
151
|
+
test("invalidatePageIndex() with no arg clears any cached workspace", async () => {
|
|
152
|
+
await writePage(workspaceDir, makePage("alice", { summary: "First" }));
|
|
153
|
+
const before = await getPageIndex(workspaceDir);
|
|
154
|
+
invalidatePageIndex();
|
|
155
|
+
const after = await getPageIndex(workspaceDir);
|
|
156
|
+
expect(after).not.toBe(before);
|
|
157
|
+
});
|
|
158
|
+
|
|
159
|
+
test("sorts entries by slug ASCII deterministically across rebuilds", async () => {
|
|
160
|
+
await writePage(workspaceDir, makePage("zulu", { summary: "Z" }));
|
|
161
|
+
await writePage(workspaceDir, makePage("alpha", { summary: "A" }));
|
|
162
|
+
await writePage(workspaceDir, makePage("mike", { summary: "M" }));
|
|
163
|
+
|
|
164
|
+
const first = await getPageIndex(workspaceDir);
|
|
165
|
+
invalidatePageIndex();
|
|
166
|
+
const second = await getPageIndex(workspaceDir);
|
|
167
|
+
|
|
168
|
+
expect(first.entries.map((e) => e.slug)).toEqual(["alpha", "mike", "zulu"]);
|
|
169
|
+
expect(first.entries).toEqual(second.entries);
|
|
170
|
+
});
|
|
171
|
+
|
|
172
|
+
test("assigns dense 1-based IDs in slug order", async () => {
|
|
173
|
+
await writePage(workspaceDir, makePage("bravo", { summary: "B" }));
|
|
174
|
+
await writePage(workspaceDir, makePage("alpha", { summary: "A" }));
|
|
175
|
+
await writePage(workspaceDir, makePage("charlie", { summary: "C" }));
|
|
176
|
+
|
|
177
|
+
const idx = await getPageIndex(workspaceDir);
|
|
178
|
+
expect(idx.entries.map((e) => e.id)).toEqual([1, 2, 3]);
|
|
179
|
+
expect(idx.entries.map((e) => e.slug)).toEqual([
|
|
180
|
+
"alpha",
|
|
181
|
+
"bravo",
|
|
182
|
+
"charlie",
|
|
183
|
+
]);
|
|
184
|
+
expect(idx.byId.get(1)?.slug).toBe("alpha");
|
|
185
|
+
expect(idx.bySlug.get("charlie")?.id).toBe(3);
|
|
186
|
+
});
|
|
187
|
+
|
|
188
|
+
test("drops pages whose read fails and continues the build", async () => {
|
|
189
|
+
await writePage(workspaceDir, makePage("alice", { summary: "Alice" }));
|
|
190
|
+
await writePage(workspaceDir, makePage("bob", { summary: "Bob" }));
|
|
191
|
+
await writePage(workspaceDir, makePage("carol", { summary: "Carol" }));
|
|
192
|
+
|
|
193
|
+
failingSlugs.add("bob");
|
|
194
|
+
|
|
195
|
+
const idx = await getPageIndex(workspaceDir);
|
|
196
|
+
expect(idx.entries.map((e) => e.slug)).toEqual(["alice", "carol"]);
|
|
197
|
+
// IDs remain dense — the dropped page does not leave a hole.
|
|
198
|
+
expect(idx.entries.map((e) => e.id)).toEqual([1, 2]);
|
|
199
|
+
});
|
|
200
|
+
|
|
201
|
+
test("integrates seeded skill entries under the skills/ slug prefix", async () => {
|
|
202
|
+
await writePage(workspaceDir, makePage("alice", { summary: "Alice" }));
|
|
203
|
+
skillState.entries = [
|
|
204
|
+
{ id: "browser", content: "Drive a browser." },
|
|
205
|
+
{ id: "calendar", content: "Schedule meetings." },
|
|
206
|
+
];
|
|
207
|
+
|
|
208
|
+
const idx = await getPageIndex(workspaceDir);
|
|
209
|
+
expect(idx.entries.map((e) => e.slug)).toEqual([
|
|
210
|
+
"alice",
|
|
211
|
+
"skills/browser",
|
|
212
|
+
"skills/calendar",
|
|
213
|
+
]);
|
|
214
|
+
expect(idx.bySlug.get("skills/browser")?.summary).toBe("Drive a browser.");
|
|
215
|
+
// Skill entries always carry an empty edge list.
|
|
216
|
+
expect(idx.bySlug.get("skills/browser")?.edges).toEqual([]);
|
|
217
|
+
});
|
|
218
|
+
|
|
219
|
+
test("resolves outgoing edges to numeric IDs and drops missing targets", async () => {
|
|
220
|
+
await writePage(
|
|
221
|
+
workspaceDir,
|
|
222
|
+
makePage("alice", { summary: "A", edges: ["bob", "ghost"] }),
|
|
223
|
+
);
|
|
224
|
+
await writePage(workspaceDir, makePage("bob", { summary: "B" }));
|
|
225
|
+
|
|
226
|
+
const idx = await getPageIndex(workspaceDir);
|
|
227
|
+
const alice = idx.bySlug.get("alice")!;
|
|
228
|
+
const bob = idx.bySlug.get("bob")!;
|
|
229
|
+
expect(alice.edges).toEqual([bob.id]);
|
|
230
|
+
});
|
|
231
|
+
|
|
232
|
+
test("falls back to body when frontmatter.summary is absent", async () => {
|
|
233
|
+
await writePage(
|
|
234
|
+
workspaceDir,
|
|
235
|
+
makePage("alice", { body: " Body fallback content. " }),
|
|
236
|
+
);
|
|
237
|
+
|
|
238
|
+
const idx = await getPageIndex(workspaceDir);
|
|
239
|
+
expect(idx.bySlug.get("alice")?.summary).toBe("Body fallback content.");
|
|
240
|
+
});
|
|
241
|
+
|
|
242
|
+
test("truncates summary to 200 characters", async () => {
|
|
243
|
+
const long = "x".repeat(500);
|
|
244
|
+
await writePage(workspaceDir, makePage("alice", { summary: long }));
|
|
245
|
+
const idx = await getPageIndex(workspaceDir);
|
|
246
|
+
expect(idx.bySlug.get("alice")?.summary.length).toBe(200);
|
|
247
|
+
});
|
|
248
|
+
});
|
|
249
|
+
|
|
250
|
+
// ---------------------------------------------------------------------------
|
|
251
|
+
// Render
|
|
252
|
+
// ---------------------------------------------------------------------------
|
|
253
|
+
|
|
254
|
+
describe("rendered prompt block", () => {
|
|
255
|
+
test("renders [id] slug — summary lines with edges parenthetical when present", async () => {
|
|
256
|
+
await writePage(
|
|
257
|
+
workspaceDir,
|
|
258
|
+
makePage("alice", { summary: "A page", edges: ["bob"] }),
|
|
259
|
+
);
|
|
260
|
+
await writePage(workspaceDir, makePage("bob", { summary: "B page" }));
|
|
261
|
+
|
|
262
|
+
const idx = await getPageIndex(workspaceDir);
|
|
263
|
+
const alice = idx.bySlug.get("alice")!;
|
|
264
|
+
const bob = idx.bySlug.get("bob")!;
|
|
265
|
+
|
|
266
|
+
const expected =
|
|
267
|
+
`[${alice.id}] alice — A page (edges: ${bob.id})\n` +
|
|
268
|
+
`[${bob.id}] bob — B page\n`;
|
|
269
|
+
expect(idx.rendered).toBe(expected);
|
|
270
|
+
});
|
|
271
|
+
|
|
272
|
+
test("omits the parenthetical for entries with no outgoing edges", async () => {
|
|
273
|
+
await writePage(workspaceDir, makePage("alice", { summary: "A page" }));
|
|
274
|
+
const idx = await getPageIndex(workspaceDir);
|
|
275
|
+
expect(idx.rendered).toBe("[1] alice — A page\n");
|
|
276
|
+
});
|
|
277
|
+
});
|
|
@@ -62,7 +62,7 @@ afterEach(() => {
|
|
|
62
62
|
function makePage(overrides: Partial<ConceptPage> = {}): ConceptPage {
|
|
63
63
|
return {
|
|
64
64
|
slug: "alice-preferences",
|
|
65
|
-
frontmatter: { edges: ["bob-handoff"], ref_files: [] },
|
|
65
|
+
frontmatter: { edges: ["bob-handoff"], ref_files: [], ref_urls: [] },
|
|
66
66
|
body: "Alice prefers VS Code over Vim.\nShe ships at end of day.\n",
|
|
67
67
|
...overrides,
|
|
68
68
|
};
|
|
@@ -238,6 +238,19 @@ describe("writePage + readPage round-trip", () => {
|
|
|
238
238
|
expect(read!.body).toBe(body);
|
|
239
239
|
});
|
|
240
240
|
|
|
241
|
+
test("readPage throws on unknown frontmatter keys instead of silently dropping them", async () => {
|
|
242
|
+
const slug = "extra-keys";
|
|
243
|
+
const raw =
|
|
244
|
+
"---\nedges: []\nref_files: []\nunknown_field: oops\n---\nbody\n";
|
|
245
|
+
writeFileSync(
|
|
246
|
+
join(workspaceDir, "memory", "concepts", `${slug}.md`),
|
|
247
|
+
raw,
|
|
248
|
+
"utf-8",
|
|
249
|
+
);
|
|
250
|
+
|
|
251
|
+
await expect(readPage(workspaceDir, slug)).rejects.toThrow();
|
|
252
|
+
});
|
|
253
|
+
|
|
241
254
|
test("writePage overwrites an existing page", async () => {
|
|
242
255
|
const page1 = makePage({ body: "first version\n" });
|
|
243
256
|
await writePage(workspaceDir, page1);
|
|
@@ -0,0 +1,257 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Tests for `assistant/src/memory/v2/prompts/router.ts` —
|
|
3
|
+
* `renderRouterPrompt` (placeholder substitution in the bundled body) and
|
|
4
|
+
* `resolveRouterPrompt` (file-override path with fallback).
|
|
5
|
+
*/
|
|
6
|
+
import { mkdtempSync, rmSync, writeFileSync } from "node:fs";
|
|
7
|
+
import { tmpdir } from "node:os";
|
|
8
|
+
import { join } from "node:path";
|
|
9
|
+
import { afterEach, beforeEach, describe, expect, test } from "bun:test";
|
|
10
|
+
|
|
11
|
+
import { renderRouterPrompt, resolveRouterPrompt } from "../prompts/router.js";
|
|
12
|
+
|
|
13
|
+
const SAMPLE_INDEX = `[1] morning-routine — coffee, walk, journal (edges: 2)
|
|
14
|
+
[2] journal-style — terse, dated, no fluff (edges: 1)
|
|
15
|
+
[3] taxes-2025 — Q1 estimate due April 15 (edges: )`;
|
|
16
|
+
|
|
17
|
+
describe("renderRouterPrompt — substitution", () => {
|
|
18
|
+
test("replaces all three placeholders with the supplied values", () => {
|
|
19
|
+
const out = renderRouterPrompt({
|
|
20
|
+
assistantName: "Aria",
|
|
21
|
+
userName: "Alice",
|
|
22
|
+
pageIndexBlock: SAMPLE_INDEX,
|
|
23
|
+
});
|
|
24
|
+
|
|
25
|
+
expect(out).not.toContain("{{ASSISTANT_NAME}}");
|
|
26
|
+
expect(out).not.toContain("{{USER_NAME}}");
|
|
27
|
+
expect(out).not.toContain("{{PAGE_INDEX}}");
|
|
28
|
+
expect(out).toContain("Aria");
|
|
29
|
+
expect(out).toContain("Alice");
|
|
30
|
+
expect(out).toContain(SAMPLE_INDEX);
|
|
31
|
+
});
|
|
32
|
+
|
|
33
|
+
test("substitutes every occurrence of the assistant name placeholder", () => {
|
|
34
|
+
const out = renderRouterPrompt({
|
|
35
|
+
assistantName: "Aria",
|
|
36
|
+
userName: "Alice",
|
|
37
|
+
pageIndexBlock: SAMPLE_INDEX,
|
|
38
|
+
});
|
|
39
|
+
|
|
40
|
+
// Body references the assistant name in multiple sentences; ensure none
|
|
41
|
+
// of them leak the raw placeholder.
|
|
42
|
+
const matches = out.match(/Aria/g) ?? [];
|
|
43
|
+
expect(matches.length).toBeGreaterThanOrEqual(2);
|
|
44
|
+
});
|
|
45
|
+
});
|
|
46
|
+
|
|
47
|
+
describe("renderRouterPrompt — neutral fallbacks", () => {
|
|
48
|
+
test("falls back to 'the assistant' when assistantName is null", () => {
|
|
49
|
+
const out = renderRouterPrompt({
|
|
50
|
+
assistantName: null,
|
|
51
|
+
userName: "Alice",
|
|
52
|
+
pageIndexBlock: SAMPLE_INDEX,
|
|
53
|
+
});
|
|
54
|
+
|
|
55
|
+
expect(out).toContain("the assistant");
|
|
56
|
+
expect(out).not.toContain("{{ASSISTANT_NAME}}");
|
|
57
|
+
});
|
|
58
|
+
|
|
59
|
+
test("falls back to 'the user' when userName is null", () => {
|
|
60
|
+
const out = renderRouterPrompt({
|
|
61
|
+
assistantName: "Aria",
|
|
62
|
+
userName: null,
|
|
63
|
+
pageIndexBlock: SAMPLE_INDEX,
|
|
64
|
+
});
|
|
65
|
+
|
|
66
|
+
expect(out).toContain("the user");
|
|
67
|
+
expect(out).not.toContain("{{USER_NAME}}");
|
|
68
|
+
});
|
|
69
|
+
|
|
70
|
+
test("uses both fallbacks when both names are null", () => {
|
|
71
|
+
const out = renderRouterPrompt({
|
|
72
|
+
assistantName: null,
|
|
73
|
+
userName: null,
|
|
74
|
+
pageIndexBlock: SAMPLE_INDEX,
|
|
75
|
+
});
|
|
76
|
+
|
|
77
|
+
expect(out).toContain("the assistant");
|
|
78
|
+
expect(out).toContain("the user");
|
|
79
|
+
});
|
|
80
|
+
|
|
81
|
+
test("falls back when names are whitespace-only strings", () => {
|
|
82
|
+
const out = renderRouterPrompt({
|
|
83
|
+
assistantName: " ",
|
|
84
|
+
userName: "\t\n",
|
|
85
|
+
pageIndexBlock: SAMPLE_INDEX,
|
|
86
|
+
});
|
|
87
|
+
|
|
88
|
+
expect(out).toContain("the assistant");
|
|
89
|
+
expect(out).toContain("the user");
|
|
90
|
+
});
|
|
91
|
+
});
|
|
92
|
+
|
|
93
|
+
describe("renderRouterPrompt — page index handling", () => {
|
|
94
|
+
test("substitutes an empty pageIndexBlock cleanly without double-newline artifacts", () => {
|
|
95
|
+
const out = renderRouterPrompt({
|
|
96
|
+
assistantName: "Aria",
|
|
97
|
+
userName: "Alice",
|
|
98
|
+
pageIndexBlock: "",
|
|
99
|
+
});
|
|
100
|
+
|
|
101
|
+
expect(out).not.toContain("{{PAGE_INDEX}}");
|
|
102
|
+
// The header should still be present and not followed by a stray
|
|
103
|
+
// triple-newline run from collapsing the empty block.
|
|
104
|
+
expect(out).toContain("# Concept Page Index");
|
|
105
|
+
expect(out).not.toMatch(/\n\n\n/);
|
|
106
|
+
// Output should end at the header section without trailing whitespace.
|
|
107
|
+
expect(out.endsWith("# Concept Page Index\n\n")).toBe(true);
|
|
108
|
+
});
|
|
109
|
+
|
|
110
|
+
test("preserves the page index body verbatim, including edges syntax", () => {
|
|
111
|
+
const out = renderRouterPrompt({
|
|
112
|
+
assistantName: "Aria",
|
|
113
|
+
userName: "Alice",
|
|
114
|
+
pageIndexBlock: SAMPLE_INDEX,
|
|
115
|
+
});
|
|
116
|
+
|
|
117
|
+
expect(out).toContain(
|
|
118
|
+
"[1] morning-routine — coffee, walk, journal (edges: 2)",
|
|
119
|
+
);
|
|
120
|
+
expect(out).toContain(
|
|
121
|
+
"[3] taxes-2025 — Q1 estimate due April 15 (edges: )",
|
|
122
|
+
);
|
|
123
|
+
});
|
|
124
|
+
});
|
|
125
|
+
|
|
126
|
+
describe("renderRouterPrompt — determinism & snapshot stability", () => {
|
|
127
|
+
test("returns the same string for the same inputs", () => {
|
|
128
|
+
const opts = {
|
|
129
|
+
assistantName: "Aria",
|
|
130
|
+
userName: "Alice",
|
|
131
|
+
pageIndexBlock: SAMPLE_INDEX,
|
|
132
|
+
};
|
|
133
|
+
expect(renderRouterPrompt(opts)).toBe(renderRouterPrompt(opts));
|
|
134
|
+
});
|
|
135
|
+
|
|
136
|
+
test("snapshot of fixed inputs", () => {
|
|
137
|
+
const out = renderRouterPrompt({
|
|
138
|
+
assistantName: "Aria",
|
|
139
|
+
userName: "Alice",
|
|
140
|
+
pageIndexBlock: SAMPLE_INDEX,
|
|
141
|
+
});
|
|
142
|
+
|
|
143
|
+
expect(out).toMatchSnapshot();
|
|
144
|
+
});
|
|
145
|
+
});
|
|
146
|
+
|
|
147
|
+
describe("renderRouterPrompt — content expectations", () => {
|
|
148
|
+
test("references the select_pages_to_inject tool name", () => {
|
|
149
|
+
const out = renderRouterPrompt({
|
|
150
|
+
assistantName: "Aria",
|
|
151
|
+
userName: "Alice",
|
|
152
|
+
pageIndexBlock: SAMPLE_INDEX,
|
|
153
|
+
});
|
|
154
|
+
|
|
155
|
+
expect(out).toContain("select_pages_to_inject");
|
|
156
|
+
});
|
|
157
|
+
|
|
158
|
+
test("describes the already_injected_ids and now markers", () => {
|
|
159
|
+
const out = renderRouterPrompt({
|
|
160
|
+
assistantName: "Aria",
|
|
161
|
+
userName: "Alice",
|
|
162
|
+
pageIndexBlock: SAMPLE_INDEX,
|
|
163
|
+
});
|
|
164
|
+
|
|
165
|
+
expect(out).toContain("<already_injected_ids>");
|
|
166
|
+
expect(out).toContain("<now>");
|
|
167
|
+
});
|
|
168
|
+
|
|
169
|
+
test("biases toward inclusion when in doubt", () => {
|
|
170
|
+
const out = renderRouterPrompt({
|
|
171
|
+
assistantName: "Aria",
|
|
172
|
+
userName: "Alice",
|
|
173
|
+
pageIndexBlock: SAMPLE_INDEX,
|
|
174
|
+
});
|
|
175
|
+
|
|
176
|
+
expect(out.toLowerCase()).toContain("lean toward inclusion");
|
|
177
|
+
expect(out.toLowerCase()).toContain("missing a relevant page");
|
|
178
|
+
});
|
|
179
|
+
});
|
|
180
|
+
|
|
181
|
+
describe("resolveRouterPrompt — override path", () => {
|
|
182
|
+
let tmpDir: string;
|
|
183
|
+
|
|
184
|
+
beforeEach(() => {
|
|
185
|
+
tmpDir = mkdtempSync(join(tmpdir(), "vellum-router-prompt-"));
|
|
186
|
+
});
|
|
187
|
+
|
|
188
|
+
afterEach(() => {
|
|
189
|
+
rmSync(tmpDir, { recursive: true, force: true });
|
|
190
|
+
});
|
|
191
|
+
|
|
192
|
+
const STD_OPTS = {
|
|
193
|
+
assistantName: "Aria",
|
|
194
|
+
userName: "Alice",
|
|
195
|
+
pageIndexBlock: SAMPLE_INDEX,
|
|
196
|
+
};
|
|
197
|
+
|
|
198
|
+
test("null overridePath returns the bundled prompt verbatim", () => {
|
|
199
|
+
expect(resolveRouterPrompt(null, STD_OPTS)).toEqual(
|
|
200
|
+
renderRouterPrompt(STD_OPTS),
|
|
201
|
+
);
|
|
202
|
+
});
|
|
203
|
+
|
|
204
|
+
test("loads override and substitutes placeholders", () => {
|
|
205
|
+
const overridePath = join(tmpDir, "router.md");
|
|
206
|
+
writeFileSync(
|
|
207
|
+
overridePath,
|
|
208
|
+
"Hi {{ASSISTANT_NAME}}, you are routing for {{USER_NAME}}.\n\n{{PAGE_INDEX}}",
|
|
209
|
+
"utf-8",
|
|
210
|
+
);
|
|
211
|
+
|
|
212
|
+
const out = resolveRouterPrompt(overridePath, STD_OPTS);
|
|
213
|
+
expect(out).toContain("Hi Aria, you are routing for Alice.");
|
|
214
|
+
expect(out).toContain(SAMPLE_INDEX);
|
|
215
|
+
expect(out).not.toContain("{{ASSISTANT_NAME}}");
|
|
216
|
+
expect(out).not.toContain("{{PAGE_INDEX}}");
|
|
217
|
+
});
|
|
218
|
+
|
|
219
|
+
test("missing override file falls back to bundled prompt", () => {
|
|
220
|
+
const overridePath = join(tmpDir, "does-not-exist.md");
|
|
221
|
+
expect(resolveRouterPrompt(overridePath, STD_OPTS)).toEqual(
|
|
222
|
+
renderRouterPrompt(STD_OPTS),
|
|
223
|
+
);
|
|
224
|
+
});
|
|
225
|
+
|
|
226
|
+
test("empty override file falls back to bundled prompt", () => {
|
|
227
|
+
const overridePath = join(tmpDir, "empty.md");
|
|
228
|
+
writeFileSync(overridePath, " \n\t\n", "utf-8");
|
|
229
|
+
expect(resolveRouterPrompt(overridePath, STD_OPTS)).toEqual(
|
|
230
|
+
renderRouterPrompt(STD_OPTS),
|
|
231
|
+
);
|
|
232
|
+
});
|
|
233
|
+
|
|
234
|
+
test("override that is a directory falls back to bundled prompt", () => {
|
|
235
|
+
// Pass the temp directory itself as the override path — lstat sees a
|
|
236
|
+
// directory, not a regular file, so the loader bails to bundled.
|
|
237
|
+
expect(resolveRouterPrompt(tmpDir, STD_OPTS)).toEqual(
|
|
238
|
+
renderRouterPrompt(STD_OPTS),
|
|
239
|
+
);
|
|
240
|
+
});
|
|
241
|
+
|
|
242
|
+
test("override applies neutral fallbacks for missing names", () => {
|
|
243
|
+
const overridePath = join(tmpDir, "neutral.md");
|
|
244
|
+
writeFileSync(
|
|
245
|
+
overridePath,
|
|
246
|
+
"Hi {{ASSISTANT_NAME}}, routing for {{USER_NAME}}.",
|
|
247
|
+
"utf-8",
|
|
248
|
+
);
|
|
249
|
+
|
|
250
|
+
const out = resolveRouterPrompt(overridePath, {
|
|
251
|
+
assistantName: null,
|
|
252
|
+
userName: null,
|
|
253
|
+
pageIndexBlock: "",
|
|
254
|
+
});
|
|
255
|
+
expect(out).toBe("Hi the assistant, routing for the user.");
|
|
256
|
+
});
|
|
257
|
+
});
|
|
@@ -1,11 +1,30 @@
|
|
|
1
|
+
import { existsSync, mkdtempSync, rmSync } from "node:fs";
|
|
2
|
+
import { tmpdir } from "node:os";
|
|
3
|
+
import { join } from "node:path";
|
|
1
4
|
import { afterEach, beforeEach, describe, expect, mock, test } from "bun:test";
|
|
2
5
|
|
|
3
6
|
import { makeMockLogger } from "../../../__tests__/helpers/mock-logger.js";
|
|
4
7
|
|
|
8
|
+
// Per-suite tmp data dir so the reembed sentinel never lands in the
|
|
9
|
+
// developer's real ~/.vellum workspace.
|
|
10
|
+
const TEST_DATA_DIR = mkdtempSync(join(tmpdir(), "memory-v2-qdrant-test-"));
|
|
11
|
+
const REEMBED_SENTINEL_PATH = join(
|
|
12
|
+
TEST_DATA_DIR,
|
|
13
|
+
".memory-v2-reembed-required",
|
|
14
|
+
);
|
|
15
|
+
|
|
5
16
|
mock.module("../../../util/logger.js", () => ({
|
|
6
17
|
getLogger: () => makeMockLogger(),
|
|
7
18
|
}));
|
|
8
19
|
|
|
20
|
+
mock.module("../../../util/platform.js", () => ({
|
|
21
|
+
getDataDir: () => TEST_DATA_DIR,
|
|
22
|
+
// Bun shares mocked modules across test files; some peer tests import
|
|
23
|
+
// `getWorkspaceDir` from this same module, so re-export it here to avoid
|
|
24
|
+
// an `undefined` if this mock is the one that wins evaluation order.
|
|
25
|
+
getWorkspaceDir: () => TEST_DATA_DIR,
|
|
26
|
+
}));
|
|
27
|
+
|
|
9
28
|
// Stub getConfig — only the qdrant.url / vectorSize / onDisk fields matter.
|
|
10
29
|
mock.module("../../../config/loader.js", () => ({
|
|
11
30
|
getConfig: () => ({
|
|
@@ -184,6 +203,7 @@ const {
|
|
|
184
203
|
deleteConceptPageEmbedding,
|
|
185
204
|
hybridQueryConceptPages,
|
|
186
205
|
countConceptPagePoints,
|
|
206
|
+
clearReembedSentinel,
|
|
187
207
|
MEMORY_V2_COLLECTION,
|
|
188
208
|
_resetMemoryV2QdrantForTests,
|
|
189
209
|
} = await import("../qdrant.js");
|
|
@@ -209,6 +229,11 @@ function resetState(): void {
|
|
|
209
229
|
state.countCalls = 0;
|
|
210
230
|
state.upsertThrowQueue.length = 0;
|
|
211
231
|
_resetMemoryV2QdrantForTests();
|
|
232
|
+
// Drop any sentinel a prior test left behind so the no-drift default path
|
|
233
|
+
// doesn't accidentally report `migrated: true`.
|
|
234
|
+
if (existsSync(REEMBED_SENTINEL_PATH)) {
|
|
235
|
+
rmSync(REEMBED_SENTINEL_PATH);
|
|
236
|
+
}
|
|
212
237
|
}
|
|
213
238
|
|
|
214
239
|
describe("memory v2 qdrant — collection lifecycle", () => {
|
|
@@ -361,6 +386,53 @@ describe("memory v2 qdrant — collection lifecycle", () => {
|
|
|
361
386
|
expect(result).toEqual({ migrated: false });
|
|
362
387
|
});
|
|
363
388
|
|
|
389
|
+
test("preserves the reembed signal across calls when createCollection fails after delete", async () => {
|
|
390
|
+
// Pre-#29823 schema triggers the destructive recreate path.
|
|
391
|
+
state.collectionExistsBeforeCreate = true;
|
|
392
|
+
state.getCollectionInfo = {
|
|
393
|
+
config: {
|
|
394
|
+
params: {
|
|
395
|
+
vectors: { dense: { size: 384 } },
|
|
396
|
+
sparse_vectors: { sparse: {} },
|
|
397
|
+
},
|
|
398
|
+
},
|
|
399
|
+
};
|
|
400
|
+
state.createCollectionThrows = new Error("Qdrant transient failure");
|
|
401
|
+
|
|
402
|
+
let firstError: unknown = null;
|
|
403
|
+
try {
|
|
404
|
+
await ensureConceptPageCollection();
|
|
405
|
+
} catch (err) {
|
|
406
|
+
firstError = err;
|
|
407
|
+
}
|
|
408
|
+
expect(firstError).not.toBeNull();
|
|
409
|
+
// The sentinel must outlive the failed call so the retry knows data was lost.
|
|
410
|
+
expect(existsSync(REEMBED_SENTINEL_PATH)).toBe(true);
|
|
411
|
+
|
|
412
|
+
// Simulate a follow-up call after the transient failure clears. The
|
|
413
|
+
// collection no longer exists (delete succeeded earlier) so the ensure
|
|
414
|
+
// path falls through to createCollection without re-entering the drift
|
|
415
|
+
// branch — but the sentinel must still surface as `migrated: true` so
|
|
416
|
+
// the lifecycle hook enqueues reembed.
|
|
417
|
+
state.createCollectionThrows = null;
|
|
418
|
+
_resetMemoryV2QdrantForTests();
|
|
419
|
+
const result = await ensureConceptPageCollection();
|
|
420
|
+
|
|
421
|
+
expect(result).toEqual({ migrated: true });
|
|
422
|
+
|
|
423
|
+
// Lifecycle hook clears the sentinel after enqueueing the reembed job.
|
|
424
|
+
await clearReembedSentinel();
|
|
425
|
+
expect(existsSync(REEMBED_SENTINEL_PATH)).toBe(false);
|
|
426
|
+
});
|
|
427
|
+
|
|
428
|
+
test("clearReembedSentinel is a no-op when no sentinel exists", async () => {
|
|
429
|
+
// Idempotent: missing-file does not throw, so the lifecycle hook can
|
|
430
|
+
// call it unconditionally without guarding.
|
|
431
|
+
expect(existsSync(REEMBED_SENTINEL_PATH)).toBe(false);
|
|
432
|
+
await clearReembedSentinel();
|
|
433
|
+
expect(existsSync(REEMBED_SENTINEL_PATH)).toBe(false);
|
|
434
|
+
});
|
|
435
|
+
|
|
364
436
|
test("concurrent ensure during a schema rebuild only deletes/creates once", async () => {
|
|
365
437
|
state.collectionExistsBeforeCreate = true;
|
|
366
438
|
state.getCollectionInfo = {
|