@vellumai/assistant 0.8.3 → 0.8.4
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/docker-entrypoint.sh +0 -1
- package/node_modules/@vellumai/gateway-client/src/types.ts +2 -0
- package/openapi.yaml +610 -16
- package/package.json +1 -1
- package/src/__tests__/agent-loop-exit-reason.test.ts +4 -5
- package/src/__tests__/agent-loop-override-profile.test.ts +1 -1
- package/src/__tests__/agent-loop.test.ts +88 -3
- package/src/__tests__/anthropic-provider.test.ts +272 -0
- package/src/__tests__/approval-cascade.test.ts +1 -1
- package/src/__tests__/background-workers-disk-pressure.test.ts +2 -1
- package/src/__tests__/channel-delivery-store.test.ts +193 -0
- package/src/__tests__/channel-reply-delivery.test.ts +284 -5
- package/src/__tests__/channel-retry-sweep.test.ts +274 -1
- package/src/__tests__/compaction-events.test.ts +1 -1
- package/src/__tests__/compactor-preserved-tail-count.test.ts +110 -0
- package/src/__tests__/config-watcher.test.ts +1 -1
- package/src/__tests__/context-token-estimator.test.ts +91 -1
- package/src/__tests__/conversation-abort-tool-results.test.ts +1 -1
- package/src/__tests__/conversation-agent-loop-inference-profile.test.ts +54 -3
- package/src/__tests__/conversation-agent-loop-overflow.test.ts +31 -6
- package/src/__tests__/conversation-agent-loop.test.ts +25 -7
- package/src/__tests__/conversation-app-control-lifecycle.test.ts +1 -1
- package/src/__tests__/conversation-clean-command.test.ts +137 -0
- package/src/__tests__/conversation-confirmation-signals.test.ts +1 -1
- package/src/__tests__/conversation-fork-crud.test.ts +161 -0
- package/src/__tests__/conversation-lifecycle.test.ts +1 -1
- package/src/__tests__/conversation-load-cleaned-at.test.ts +279 -0
- package/src/__tests__/conversation-load-history-repair.test.ts +1 -1
- package/src/__tests__/conversation-pairing.test.ts +2 -2
- package/src/__tests__/conversation-process-callsite.test.ts +1 -1
- package/src/__tests__/conversation-provider-retry-repair.test.ts +1 -1
- package/src/__tests__/conversation-queue.test.ts +1 -1
- package/src/__tests__/conversation-runtime-assembly.test.ts +264 -81
- package/src/__tests__/conversation-seed-composer.test.ts +66 -4
- package/src/__tests__/conversation-slash-commands.test.ts +36 -8
- package/src/__tests__/conversation-slash-queue.test.ts +1 -1
- package/src/__tests__/conversation-slash-unknown.test.ts +1 -1
- package/src/__tests__/conversation-speed-override.test.ts +1 -1
- package/src/__tests__/conversation-surfaces-task-progress.test.ts +220 -0
- package/src/__tests__/conversation-workspace-cache-state.test.ts +1 -1
- package/src/__tests__/conversation-workspace-injection.test.ts +5 -1
- package/src/__tests__/conversation-workspace-tool-tracking.test.ts +5 -1
- package/src/__tests__/credential-security-invariants.test.ts +6 -0
- package/src/__tests__/cu-unified-flow.test.ts +10 -1
- package/src/__tests__/dm-backfill.test.ts +64 -0
- package/src/__tests__/dm-persistence.test.ts +33 -0
- package/src/__tests__/document-find-replace.test.ts +501 -0
- package/src/__tests__/first-greeting.test.ts +23 -2
- package/src/__tests__/headless-browser-navigate.test.ts +172 -0
- package/src/__tests__/host-bash-proxy.test.ts +6 -0
- package/src/__tests__/host-browser-proxy.test.ts +10 -0
- package/src/__tests__/host-cu-proxy.test.ts +8 -1
- package/src/__tests__/host-file-proxy.test.ts +8 -1
- package/src/__tests__/host-transfer-proxy.test.ts +8 -1
- package/src/__tests__/identity-routes.test.ts +57 -0
- package/src/__tests__/inbound-slack-persistence.test.ts +3 -0
- package/src/__tests__/injector-chain.test.ts +2 -0
- package/src/__tests__/injector-document-comments.test.ts +378 -0
- package/src/__tests__/injector-pkb-v2-silenced.test.ts +4 -25
- package/src/__tests__/list-messages-attachments.test.ts +21 -17
- package/src/__tests__/list-messages-hidden-metadata.test.ts +217 -0
- package/src/__tests__/list-messages-page-latest.test.ts +130 -14
- package/src/__tests__/list-messages-tool-merge.test.ts +17 -16
- package/src/__tests__/llm-context-normalization.test.ts +0 -2
- package/src/__tests__/llm-resolver.test.ts +85 -1
- package/src/__tests__/log-export-routes.test.ts +99 -2
- package/src/__tests__/message-queue-steer.test.ts +114 -0
- package/src/__tests__/openai-provider.test.ts +105 -0
- package/src/__tests__/openai-responses-provider.test.ts +4 -4
- package/src/__tests__/outbound-slack-persistence.test.ts +187 -20
- package/src/__tests__/pending-interactions-resolved-event.test.ts +190 -0
- package/src/__tests__/platform.test.ts +0 -3
- package/src/__tests__/plugin-source-watcher.test.ts +302 -0
- package/src/__tests__/process-message-background-slack.test.ts +1 -51
- package/src/__tests__/process-message-display-content.test.ts +21 -16
- package/src/__tests__/server-history-render.test.ts +83 -4
- package/src/__tests__/steer-tool-repair.test.ts +249 -0
- package/src/__tests__/system-prompt.test.ts +51 -28
- package/src/__tests__/terminal-tools.test.ts +11 -1
- package/src/__tests__/thinking-block-replay.test.ts +113 -0
- package/src/__tests__/thread-backfill.test.ts +370 -22
- package/src/__tests__/tool-executor.test.ts +90 -1
- package/src/__tests__/tool-result-metadata-plumbing.test.ts +167 -0
- package/src/__tests__/twilio-routes.test.ts +1 -1
- package/src/__tests__/web-fetch.test.ts +2 -2
- package/src/__tests__/workspace-git-service.test.ts +88 -5
- package/src/__tests__/workspace-migration-088-deprecate-background-conversation-override.test.ts +158 -0
- package/src/agent/attachments.ts +1 -0
- package/src/agent/loop.ts +57 -20
- package/src/background-wake/next-wake.test.ts +289 -0
- package/src/background-wake/next-wake.ts +172 -0
- package/src/browser/operations.ts +15 -0
- package/src/cli/commands/__tests__/conversations-slack.test.ts +572 -0
- package/src/cli/commands/__tests__/memory-v2.test.ts +9 -12
- package/src/cli/commands/conversations.ts +128 -1
- package/src/cli/commands/inference-providers.ts +147 -1
- package/src/cli/commands/memory-v2.ts +308 -0
- package/src/cli/commands/notifications.ts +24 -2
- package/src/cli/utils/conversation-id.ts +17 -5
- package/src/config/bundled-skills/app-builder/SKILL.md +2 -2
- package/src/config/bundled-skills/document-editor/SKILL.md +115 -0
- package/src/config/bundled-skills/document-editor/TOOLS.json +240 -0
- package/src/config/bundled-skills/document-editor/tools/comment-list.ts +12 -0
- package/src/config/bundled-skills/document-editor/tools/comment-reply.ts +12 -0
- package/src/config/bundled-skills/document-editor/tools/comment-resolve.ts +12 -0
- package/src/config/bundled-skills/document-editor/tools/document-find.ts +12 -0
- package/src/config/bundled-skills/document-editor/tools/document-replace-text.ts +12 -0
- package/src/config/bundled-skills/media-processing/SKILL.md +8 -0
- package/src/config/bundled-skills/schedule/SKILL.md +8 -0
- package/src/config/bundled-tool-registry.ts +22 -12
- package/src/config/call-site-defaults.ts +19 -0
- package/src/config/feature-flag-registry.json +99 -3
- package/src/config/llm-resolver.ts +16 -2
- package/src/config/schemas/__tests__/memory-v2.test.ts +4 -0
- package/src/config/schemas/call-site-catalog.ts +21 -0
- package/src/config/schemas/llm.ts +3 -0
- package/src/config/schemas/memory-v2.ts +48 -1
- package/src/context/compactor.ts +8 -1
- package/src/context/token-estimator.ts +47 -4
- package/src/context/window-manager.ts +25 -0
- package/src/credential-health/credential-health-service.ts +34 -19
- package/src/daemon/__tests__/conversation-tool-setup.test.ts +66 -6
- package/src/daemon/__tests__/native-web-search-metadata.test.ts +357 -0
- package/src/daemon/__tests__/web-search-status-text.test.ts +287 -0
- package/src/daemon/conversation-agent-loop-handlers.ts +153 -23
- package/src/daemon/conversation-agent-loop.ts +223 -54
- package/src/daemon/conversation-lifecycle.ts +142 -116
- package/src/daemon/conversation-messaging.ts +3 -0
- package/src/daemon/conversation-process.ts +273 -0
- package/src/daemon/conversation-queue-manager.ts +14 -0
- package/src/daemon/conversation-runtime-assembly.ts +135 -75
- package/src/daemon/conversation-slash.ts +37 -5
- package/src/daemon/conversation-surfaces.ts +45 -2
- package/src/daemon/conversation-tool-setup.ts +7 -0
- package/src/daemon/conversation.ts +42 -5
- package/src/daemon/first-greeting.ts +10 -0
- package/src/daemon/handlers/__tests__/config-a2a-accept.test.ts +498 -0
- package/src/daemon/handlers/config-a2a.ts +160 -0
- package/src/daemon/handlers/config-model.test.ts +1 -0
- package/src/daemon/handlers/conversations.ts +79 -0
- package/src/daemon/handlers/shared.ts +92 -29
- package/src/daemon/host-bash-proxy.ts +1 -1
- package/src/daemon/host-cu-proxy.ts +1 -1
- package/src/daemon/host-file-proxy.ts +1 -1
- package/src/daemon/host-transfer-proxy.ts +1 -1
- package/src/daemon/lifecycle.ts +18 -4
- package/src/daemon/message-protocol.ts +4 -0
- package/src/daemon/message-types/conversations.ts +8 -0
- package/src/daemon/message-types/document-comments.ts +50 -0
- package/src/daemon/message-types/messages.ts +68 -1
- package/src/daemon/message-types/surfaces.ts +3 -1
- package/src/daemon/message-types/web-activity.ts +57 -0
- package/src/daemon/plugin-source-watcher.ts +135 -3
- package/src/daemon/process-message.ts +69 -12
- package/src/daemon/query-complexity-router.ts +75 -0
- package/src/daemon/trust-context.ts +6 -0
- package/src/documents/document-comments-store.test.ts +338 -0
- package/src/documents/document-comments-store.ts +237 -0
- package/src/documents/document-store.ts +202 -0
- package/src/heartbeat/__tests__/heartbeat-service.test.ts +0 -1
- package/src/heartbeat/heartbeat-service.ts +1 -0
- package/src/home/__tests__/suggested-prompts.test.ts +33 -2
- package/src/home/feed-types.ts +6 -1
- package/src/home/home-content-refresh.ts +52 -0
- package/src/home/home-greeting-cache.ts +69 -0
- package/src/home/home-greeting.ts +94 -0
- package/src/home/suggested-prompts.ts +177 -9
- package/src/memory/__tests__/jobs-worker-v2-schedule.test.ts +135 -2
- package/src/memory/__tests__/memory-retrospective-job.test.ts +320 -6
- package/src/memory/conversation-crud.ts +133 -43
- package/src/memory/db-init.ts +16 -0
- package/src/memory/delivery-crud.ts +41 -0
- package/src/memory/delivery-status.ts +141 -15
- package/src/memory/external-conversation-store.ts +32 -1
- package/src/memory/jobs-worker.ts +21 -1
- package/src/memory/memory-retrospective-constants.ts +28 -0
- package/src/memory/memory-retrospective-enqueue.ts +3 -2
- package/src/memory/memory-retrospective-job.ts +408 -18
- package/src/memory/memory-retrospective-startup-cleanup.ts +3 -3
- package/src/memory/memory-v2-activation-log-store.ts +26 -8
- package/src/memory/migrations/100-core-tables.ts +1 -0
- package/src/memory/migrations/109-external-conversation-bindings.ts +1 -0
- package/src/memory/migrations/253-conversation-last-notified-profile.ts +15 -0
- package/src/memory/migrations/253-document-comments.ts +47 -0
- package/src/memory/migrations/254-external-conversation-binding-chat-name.ts +43 -0
- package/src/memory/migrations/255-channel-inbound-delivery-attempts.ts +24 -0
- package/src/memory/migrations/256-memory-v2-injection-events.ts +113 -0
- package/src/memory/migrations/257-strip-base-url-non-openai-compatible.ts +22 -0
- package/src/memory/migrations/258-onboarding-events-prior-assistants.ts +13 -0
- package/src/memory/migrations/259-conversation-cleaned-at.ts +33 -0
- package/src/memory/migrations/index.ts +17 -0
- package/src/memory/migrations/registry.ts +25 -0
- package/src/memory/onboarding-events-store.ts +7 -0
- package/src/memory/schema/calls.ts +1 -0
- package/src/memory/schema/conversations.ts +3 -0
- package/src/memory/schema/infrastructure.ts +1 -0
- package/src/memory/v2/__tests__/injection-events.test.ts +318 -0
- package/src/memory/v2/__tests__/injection.test.ts +31 -14
- package/src/memory/v2/__tests__/page-index.test.ts +365 -1
- package/src/memory/v2/__tests__/router.test.ts +489 -1
- package/src/memory/v2/consolidation-job.ts +14 -0
- package/src/memory/v2/injection-events.ts +101 -0
- package/src/memory/v2/injection.ts +21 -10
- package/src/memory/v2/page-index.ts +209 -7
- package/src/memory/v2/page-store.ts +18 -0
- package/src/memory/v2/router.ts +209 -55
- package/src/messaging/providers/index.ts +7 -1
- package/src/messaging/providers/slack/__tests__/adapter-mention-rendering.test.ts +329 -3
- package/src/messaging/providers/slack/__tests__/adapter-token-routing.test.ts +34 -1
- package/src/messaging/providers/slack/adapter.ts +178 -25
- package/src/messaging/providers/slack/api.test.ts +54 -0
- package/src/messaging/providers/slack/api.ts +119 -3
- package/src/messaging/providers/slack/client.ts +12 -0
- package/src/messaging/providers/slack/deep-link.ts +20 -1
- package/src/messaging/providers/slack/message-metadata.test.ts +48 -0
- package/src/messaging/providers/slack/message-metadata.ts +156 -0
- package/src/messaging/providers/slack/render-transcript.test.ts +107 -75
- package/src/messaging/providers/slack/render-transcript.ts +176 -49
- package/src/messaging/providers/slack/send.test.ts +77 -0
- package/src/messaging/providers/slack/send.ts +8 -2
- package/src/messaging/providers/slack/types.ts +14 -0
- package/src/notifications/__tests__/emit-signal-home-feed.test.ts +4 -1
- package/src/notifications/__tests__/home-feed-side-effect.test.ts +116 -54
- package/src/notifications/conversation-seed-composer.ts +14 -2
- package/src/notifications/deferred-emit.ts +135 -0
- package/src/notifications/emit-signal.ts +9 -1
- package/src/notifications/home-feed-side-effect.ts +60 -30
- package/src/oauth/connect-orchestrator.ts +3 -0
- package/src/oauth/credential-token-resolver.ts +2 -0
- package/src/oauth/manual-token-connection.ts +19 -0
- package/src/oauth/oauth-store.ts +12 -0
- package/src/oauth/seed-providers.ts +22 -0
- package/src/permissions/prompter.ts +5 -2
- package/src/permissions/secret-prompter.ts +4 -1
- package/src/plugins/defaults/injectors.ts +82 -9
- package/src/prompts/__tests__/system-prompt.test.ts +46 -2
- package/src/prompts/normalize-onboarding.ts +40 -0
- package/src/prompts/sections.ts +32 -14
- package/src/prompts/system-prompt.ts +105 -68
- package/src/prompts/template-detection.ts +37 -0
- package/src/prompts/templates/BOOTSTRAP-CONTENT-AUTOMATION.md +141 -0
- package/src/prompts/templates/BOOTSTRAP.md +8 -0
- package/src/prompts/templates/VOICE.md +3 -0
- package/src/prompts/templates/system-sections.ts +53 -3
- package/src/providers/anthropic/client.ts +132 -5
- package/src/providers/fireworks/client.ts +20 -2
- package/src/providers/inference/__tests__/base-url-route-validation.test.ts +342 -0
- package/src/providers/inference/__tests__/base-url-security.test.ts +189 -0
- package/src/providers/inference/__tests__/codex-token-refresh.test.ts +254 -0
- package/src/providers/inference/adapter-factory.ts +15 -1
- package/src/providers/inference/auth.ts +3 -3
- package/src/providers/inference/codex-token-refresh.ts +128 -0
- package/src/providers/inference/resolve-auth.ts +49 -6
- package/src/providers/model-catalog.ts +48 -1
- package/src/providers/openai/chat-completions-provider.ts +57 -20
- package/src/providers/openai/responses-provider.ts +9 -3
- package/src/providers/openrouter/client.ts +5 -1
- package/src/providers/types.ts +25 -0
- package/src/runtime/__tests__/agent-wake.test.ts +214 -0
- package/src/runtime/__tests__/background-job-runner.test.ts +128 -0
- package/src/runtime/agent-wake.ts +151 -56
- package/src/runtime/auth/route-policy.ts +7 -3
- package/src/runtime/background-job-runner.ts +26 -0
- package/src/runtime/channel-reply-delivery.ts +182 -47
- package/src/runtime/channel-retry-sweep.ts +141 -16
- package/src/runtime/http-types.ts +7 -4
- package/src/runtime/pending-interactions.ts +51 -8
- package/src/runtime/routes/__tests__/content-source-routes.test.ts +162 -0
- package/src/runtime/routes/__tests__/conversation-query-routes.test.ts +55 -1
- package/src/runtime/routes/__tests__/memory-v2-routes.test.ts +14 -0
- package/src/runtime/routes/__tests__/memory-v2-simulate-route.test.ts +271 -0
- package/src/runtime/routes/__tests__/sanity-routes.test.ts +280 -0
- package/src/runtime/routes/__tests__/slack-channel-routes.test.ts +266 -0
- package/src/runtime/routes/approval-routes.ts +4 -1
- package/src/runtime/routes/chatgpt-subscription-auth-routes.ts +246 -0
- package/src/runtime/routes/content-source-routes.ts +78 -0
- package/src/runtime/routes/conversation-cli-routes.ts +146 -1
- package/src/runtime/routes/conversation-query-routes.ts +60 -1
- package/src/runtime/routes/conversation-routes.ts +281 -76
- package/src/runtime/routes/document-comments-routes.ts +287 -0
- package/src/runtime/routes/documents-routes.ts +33 -0
- package/src/runtime/routes/home-feed-routes.ts +6 -3
- package/src/runtime/routes/host-app-control-routes.ts +1 -1
- package/src/runtime/routes/host-browser-routes.ts +8 -1
- package/src/runtime/routes/identity-routes.ts +21 -0
- package/src/runtime/routes/inbound-message-handler.ts +288 -58
- package/src/runtime/routes/inbound-stages/background-dispatch.test.ts +365 -6
- package/src/runtime/routes/inbound-stages/background-dispatch.ts +283 -82
- package/src/runtime/routes/index.ts +12 -4
- package/src/runtime/routes/inference-provider-connection-routes.ts +63 -7
- package/src/runtime/routes/integrations/a2a.ts +60 -1
- package/src/runtime/routes/log-export-routes.ts +39 -0
- package/src/runtime/routes/memory-v2-routes.ts +217 -0
- package/src/runtime/routes/notification-routes.ts +19 -2
- package/src/runtime/routes/question-routes.ts +4 -1
- package/src/runtime/routes/sanity-routes.ts +159 -0
- package/src/runtime/routes/slack-channel-routes.ts +187 -0
- package/src/runtime/services/conversation-serializer.ts +30 -4
- package/src/schedule/integration-status.ts +3 -1
- package/src/security/__tests__/oauth2-device-code.test.ts +479 -0
- package/src/security/oauth2-device-code.ts +307 -0
- package/src/security/oauth2.ts +26 -9
- package/src/security/secure-keys.ts +5 -0
- package/src/skills/catalog-install.ts +6 -2
- package/src/tools/browser/__tests__/pinned-tabs.test.ts +80 -0
- package/src/tools/browser/browser-execution.ts +93 -0
- package/src/tools/browser/cdp-client/__tests__/factory.test.ts +28 -0
- package/src/tools/browser/cdp-client/__tests__/types.test.ts +1 -0
- package/src/tools/browser/cdp-client/cdp-inspect-client.ts +10 -0
- package/src/tools/browser/cdp-client/extension-cdp-client.ts +15 -1
- package/src/tools/browser/cdp-client/factory.ts +87 -3
- package/src/tools/browser/cdp-client/local-cdp-client.ts +9 -0
- package/src/tools/browser/cdp-client/types.ts +36 -0
- package/src/tools/browser/pinned-tabs.ts +90 -0
- package/src/tools/document/document-comment-tool.test.ts +379 -0
- package/src/tools/document/document-comment-tool.ts +156 -0
- package/src/tools/document/document-tool.ts +128 -2
- package/src/tools/network/__tests__/web-fetch-metadata.test.ts +229 -0
- package/src/tools/network/__tests__/web-search-metadata.test.ts +346 -0
- package/src/tools/network/domain-normalize.ts +17 -0
- package/src/tools/network/web-fetch.ts +213 -64
- package/src/tools/network/web-search.ts +191 -66
- package/src/tools/terminal/safe-env.ts +3 -2
- package/src/tools/tool-approval-handler.ts +19 -12
- package/src/tools/types.ts +4 -0
- package/src/tools/ui-surface/definitions.ts +3 -1
- package/src/types/onboarding-context.ts +4 -0
- package/src/util/__tests__/favicon.test.ts +84 -0
- package/src/util/favicon.ts +40 -0
- package/src/util/platform.ts +0 -5
- package/src/workspace/git-service.ts +75 -4
- package/src/workspace/migrations/088-deprecate-background-conversation-override.ts +103 -0
- package/src/workspace/migrations/registry.ts +2 -0
- package/src/config/bundled-skills/document/SKILL.md +0 -54
- package/src/config/bundled-skills/document/TOOLS.json +0 -106
- package/src/daemon/seed-files.ts +0 -18
- package/src/runtime/routes/interface-routes.ts +0 -43
- /package/src/config/bundled-skills/{document → document-editor}/tools/document-create.ts +0 -0
- /package/src/config/bundled-skills/{document → document-editor}/tools/document-delete.ts +0 -0
- /package/src/config/bundled-skills/{document → document-editor}/tools/document-list.ts +0 -0
- /package/src/config/bundled-skills/{document → document-editor}/tools/document-read.ts +0 -0
- /package/src/config/bundled-skills/{document → document-editor}/tools/document-update.ts +0 -0
|
@@ -0,0 +1,162 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Unit tests for content-source-routes.ts.
|
|
3
|
+
*
|
|
4
|
+
* Drives the handler function directly (bypassing the router) and mocks
|
|
5
|
+
* out node:fs writes so no real I/O occurs.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
import { afterAll, beforeEach, describe, expect, mock, test } from "bun:test";
|
|
9
|
+
|
|
10
|
+
// ---------------------------------------------------------------------------
|
|
11
|
+
// Module mocks — must appear before any imports of the module under test
|
|
12
|
+
// ---------------------------------------------------------------------------
|
|
13
|
+
|
|
14
|
+
mock.module("../../../util/logger.js", () => ({
|
|
15
|
+
getLogger: () =>
|
|
16
|
+
new Proxy({} as Record<string, unknown>, {
|
|
17
|
+
get: () => () => {},
|
|
18
|
+
}),
|
|
19
|
+
}));
|
|
20
|
+
|
|
21
|
+
const FAKE_WORKSPACE = "/tmp/content-source-routes-test-workspace";
|
|
22
|
+
|
|
23
|
+
mock.module("../../../util/platform.js", () => ({
|
|
24
|
+
getWorkspaceDir: () => FAKE_WORKSPACE,
|
|
25
|
+
}));
|
|
26
|
+
|
|
27
|
+
const writtenFiles = new Map<string, string>();
|
|
28
|
+
|
|
29
|
+
mock.module("node:fs", () => ({
|
|
30
|
+
mkdirSync: () => {},
|
|
31
|
+
writeFileSync: (path: string, content: string) => {
|
|
32
|
+
writtenFiles.set(path, content);
|
|
33
|
+
},
|
|
34
|
+
}));
|
|
35
|
+
|
|
36
|
+
// ---------------------------------------------------------------------------
|
|
37
|
+
// Imports after mocks
|
|
38
|
+
// ---------------------------------------------------------------------------
|
|
39
|
+
|
|
40
|
+
import { ROUTES } from "../content-source-routes.js";
|
|
41
|
+
import type { RouteDefinition, RouteHandlerArgs } from "../types.js";
|
|
42
|
+
|
|
43
|
+
afterAll(() => {
|
|
44
|
+
mock.restore();
|
|
45
|
+
});
|
|
46
|
+
|
|
47
|
+
// ---------------------------------------------------------------------------
|
|
48
|
+
// Helpers
|
|
49
|
+
// ---------------------------------------------------------------------------
|
|
50
|
+
|
|
51
|
+
function findHandler(operationId: string): RouteDefinition["handler"] {
|
|
52
|
+
const route = ROUTES.find((r) => r.operationId === operationId);
|
|
53
|
+
if (!route) throw new Error(`Route ${operationId} not found`);
|
|
54
|
+
return route.handler;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
function makeArgs(body?: Record<string, unknown>): RouteHandlerArgs {
|
|
58
|
+
return { body };
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
const handler = findHandler("content_source_set");
|
|
62
|
+
|
|
63
|
+
beforeEach(() => {
|
|
64
|
+
writtenFiles.clear();
|
|
65
|
+
});
|
|
66
|
+
|
|
67
|
+
// ---------------------------------------------------------------------------
|
|
68
|
+
// URL validation
|
|
69
|
+
// ---------------------------------------------------------------------------
|
|
70
|
+
|
|
71
|
+
describe("content_source_set — URL validation", () => {
|
|
72
|
+
test("https URL is accepted and sidecar written", () => {
|
|
73
|
+
const result = handler(makeArgs({ url: "https://myblog.com/posts" }));
|
|
74
|
+
expect(result).toEqual({ ok: true });
|
|
75
|
+
|
|
76
|
+
const expectedPath = `${FAKE_WORKSPACE}/data/content-source.json`;
|
|
77
|
+
expect(writtenFiles.has(expectedPath)).toBe(true);
|
|
78
|
+
const written = JSON.parse(writtenFiles.get(expectedPath)!);
|
|
79
|
+
expect(written.url).toBe("https://myblog.com/posts");
|
|
80
|
+
});
|
|
81
|
+
|
|
82
|
+
test("http URL is accepted and sidecar written", () => {
|
|
83
|
+
const result = handler(makeArgs({ url: "http://intranet.example.com" }));
|
|
84
|
+
expect(result).toEqual({ ok: true });
|
|
85
|
+
});
|
|
86
|
+
|
|
87
|
+
test("URL with leading/trailing whitespace is trimmed", () => {
|
|
88
|
+
const result = handler(makeArgs({ url: " https://blog.example.com " }));
|
|
89
|
+
expect(result).toEqual({ ok: true });
|
|
90
|
+
|
|
91
|
+
const expectedPath = `${FAKE_WORKSPACE}/data/content-source.json`;
|
|
92
|
+
const written = JSON.parse(writtenFiles.get(expectedPath)!);
|
|
93
|
+
expect(written.url).toBe("https://blog.example.com/");
|
|
94
|
+
});
|
|
95
|
+
|
|
96
|
+
test("bare hostname without protocol returns invalid_url", () => {
|
|
97
|
+
const result = handler(makeArgs({ url: "myblog.com" }));
|
|
98
|
+
expect(result).toEqual({ ok: false, error: "invalid_url" });
|
|
99
|
+
expect(writtenFiles.size).toBe(0);
|
|
100
|
+
});
|
|
101
|
+
|
|
102
|
+
test("ftp:// URL is rejected", () => {
|
|
103
|
+
const result = handler(makeArgs({ url: "ftp://files.example.com" }));
|
|
104
|
+
expect(result).toEqual({ ok: false, error: "invalid_url" });
|
|
105
|
+
expect(writtenFiles.size).toBe(0);
|
|
106
|
+
});
|
|
107
|
+
|
|
108
|
+
test("empty string returns invalid_url", () => {
|
|
109
|
+
const result = handler(makeArgs({ url: "" }));
|
|
110
|
+
expect(result).toEqual({ ok: false, error: "invalid_url" });
|
|
111
|
+
expect(writtenFiles.size).toBe(0);
|
|
112
|
+
});
|
|
113
|
+
|
|
114
|
+
test("whitespace-only string returns invalid_url", () => {
|
|
115
|
+
const result = handler(makeArgs({ url: " " }));
|
|
116
|
+
expect(result).toEqual({ ok: false, error: "invalid_url" });
|
|
117
|
+
expect(writtenFiles.size).toBe(0);
|
|
118
|
+
});
|
|
119
|
+
|
|
120
|
+
test("javascript: URL is rejected", () => {
|
|
121
|
+
const result = handler(makeArgs({ url: "javascript:alert(1)" }));
|
|
122
|
+
expect(result).toEqual({ ok: false, error: "invalid_url" });
|
|
123
|
+
expect(writtenFiles.size).toBe(0);
|
|
124
|
+
});
|
|
125
|
+
|
|
126
|
+
test("missing url field returns invalid_url", () => {
|
|
127
|
+
const result = handler(makeArgs({}));
|
|
128
|
+
expect(result).toEqual({ ok: false, error: "invalid_url" });
|
|
129
|
+
});
|
|
130
|
+
});
|
|
131
|
+
|
|
132
|
+
// ---------------------------------------------------------------------------
|
|
133
|
+
// Sidecar content verification
|
|
134
|
+
// ---------------------------------------------------------------------------
|
|
135
|
+
|
|
136
|
+
describe("content_source_set — sidecar contents", () => {
|
|
137
|
+
test("writes url to data/content-source.json", () => {
|
|
138
|
+
handler(makeArgs({ url: "https://example.com/blog" }));
|
|
139
|
+
|
|
140
|
+
const expectedPath = `${FAKE_WORKSPACE}/data/content-source.json`;
|
|
141
|
+
expect(writtenFiles.has(expectedPath)).toBe(true);
|
|
142
|
+
|
|
143
|
+
const written = JSON.parse(writtenFiles.get(expectedPath)!);
|
|
144
|
+
expect(Object.keys(written)).toEqual(["url"]);
|
|
145
|
+
});
|
|
146
|
+
|
|
147
|
+
test("no sidecar written on invalid URL", () => {
|
|
148
|
+
handler(makeArgs({ url: "not-a-url" }));
|
|
149
|
+
expect(writtenFiles.size).toBe(0);
|
|
150
|
+
});
|
|
151
|
+
});
|
|
152
|
+
|
|
153
|
+
// ---------------------------------------------------------------------------
|
|
154
|
+
// Policy key verification
|
|
155
|
+
// ---------------------------------------------------------------------------
|
|
156
|
+
|
|
157
|
+
describe("route policy key", () => {
|
|
158
|
+
test("content_source_set uses policyKey: secrets", () => {
|
|
159
|
+
const route = ROUTES.find((r) => r.operationId === "content_source_set");
|
|
160
|
+
expect(route?.policyKey).toBe("secrets");
|
|
161
|
+
});
|
|
162
|
+
});
|
|
@@ -141,7 +141,11 @@ function seedConversationAndMessage(args: {
|
|
|
141
141
|
.run();
|
|
142
142
|
}
|
|
143
143
|
|
|
144
|
-
function seedRequestLog(
|
|
144
|
+
function seedRequestLog(
|
|
145
|
+
messageId: string,
|
|
146
|
+
id: string,
|
|
147
|
+
options: { agentLoopExitReason?: string | null } = {},
|
|
148
|
+
): void {
|
|
145
149
|
getDb()
|
|
146
150
|
.insert(llmRequestLogs)
|
|
147
151
|
.values({
|
|
@@ -154,6 +158,9 @@ function seedRequestLog(messageId: string, id: string): void {
|
|
|
154
158
|
choices: [{ message: { content: "hi" } }],
|
|
155
159
|
}),
|
|
156
160
|
createdAt: 1_700_000_000_000,
|
|
161
|
+
...(options.agentLoopExitReason != null
|
|
162
|
+
? { agentLoopExitReason: options.agentLoopExitReason }
|
|
163
|
+
: {}),
|
|
157
164
|
})
|
|
158
165
|
.run();
|
|
159
166
|
}
|
|
@@ -317,6 +324,53 @@ describe("GET /v1/messages/:id/llm-context — conversationTotalEstimatedCostUsd
|
|
|
317
324
|
});
|
|
318
325
|
});
|
|
319
326
|
|
|
327
|
+
describe("GET /v1/messages/:id/llm-context — agentLoopExitReason", () => {
|
|
328
|
+
beforeEach(() => {
|
|
329
|
+
clearTables();
|
|
330
|
+
});
|
|
331
|
+
|
|
332
|
+
test("surfaces the stamped agent_loop_exit_reason on the terminal log", async () => {
|
|
333
|
+
const messageId = "msg-with-exit";
|
|
334
|
+
seedConversationAndMessage({
|
|
335
|
+
conversationId: "conv-1",
|
|
336
|
+
messageId,
|
|
337
|
+
source: "user",
|
|
338
|
+
conversationType: "standard",
|
|
339
|
+
});
|
|
340
|
+
// Two logs in the same turn — only the terminal one is stamped.
|
|
341
|
+
seedRequestLog(messageId, "log-non-terminal");
|
|
342
|
+
seedRequestLog(messageId, "log-terminal", {
|
|
343
|
+
agentLoopExitReason: "no_tool_calls",
|
|
344
|
+
});
|
|
345
|
+
|
|
346
|
+
const body = (await dispatchLlmContext(messageId)) as {
|
|
347
|
+
logs: Array<{ id: string; agentLoopExitReason: string | null }>;
|
|
348
|
+
};
|
|
349
|
+
|
|
350
|
+
const byId = new Map(body.logs.map((l) => [l.id, l.agentLoopExitReason]));
|
|
351
|
+
expect(byId.get("log-non-terminal")).toBeNull();
|
|
352
|
+
expect(byId.get("log-terminal")).toBe("no_tool_calls");
|
|
353
|
+
});
|
|
354
|
+
|
|
355
|
+
test("returns null when no log in the turn has been stamped", async () => {
|
|
356
|
+
const messageId = "msg-no-exit";
|
|
357
|
+
seedConversationAndMessage({
|
|
358
|
+
conversationId: "conv-1",
|
|
359
|
+
messageId,
|
|
360
|
+
source: "user",
|
|
361
|
+
conversationType: "standard",
|
|
362
|
+
});
|
|
363
|
+
seedRequestLog(messageId, "log-unstamped");
|
|
364
|
+
|
|
365
|
+
const body = (await dispatchLlmContext(messageId)) as {
|
|
366
|
+
logs: Array<{ id: string; agentLoopExitReason: string | null }>;
|
|
367
|
+
};
|
|
368
|
+
|
|
369
|
+
expect(body.logs).toHaveLength(1);
|
|
370
|
+
expect(body.logs[0]!.agentLoopExitReason).toBeNull();
|
|
371
|
+
});
|
|
372
|
+
});
|
|
373
|
+
|
|
320
374
|
describe("PUT /v1/config/llm/profiles/:name", () => {
|
|
321
375
|
beforeEach(() => {
|
|
322
376
|
savedRawConfig = null;
|
|
@@ -145,3 +145,17 @@ describe("memory_v2_list_concept_pages handler", () => {
|
|
|
145
145
|
expect(result.pages.map((p) => p.slug)).toEqual(["valid-page"]);
|
|
146
146
|
});
|
|
147
147
|
});
|
|
148
|
+
|
|
149
|
+
describe("memory_v2_ema_scores route registration", () => {
|
|
150
|
+
test("route is registered with the expected operationId and POST endpoint", () => {
|
|
151
|
+
const route = ROUTES.find((r) => r.operationId === "memory_v2_ema_scores");
|
|
152
|
+
expect(route).toBeDefined();
|
|
153
|
+
expect(route!.method).toBe("POST");
|
|
154
|
+
expect(route!.endpoint).toBe("memory/v2/ema-scores");
|
|
155
|
+
expect(route!.tags).toContain("memory");
|
|
156
|
+
// Schema rejects unknown keys so accidental request-body fields fail
|
|
157
|
+
// loudly during route adapter parsing rather than silently propagating.
|
|
158
|
+
expect(() => route!.requestBody!.parse({ extra: true })).toThrow();
|
|
159
|
+
expect(() => route!.requestBody!.parse({})).not.toThrow();
|
|
160
|
+
});
|
|
161
|
+
});
|
|
@@ -0,0 +1,271 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Tests for the `memory_v2_simulate_router` route handler.
|
|
3
|
+
*
|
|
4
|
+
* The route is a read-only playground for previewing router config knob
|
|
5
|
+
* changes (`tier1_size`, `tier2_size`, `batch_size`) against the live page
|
|
6
|
+
* index. Tests assert:
|
|
7
|
+
* 1. The handler returns selected slugs with `sourceBySlug` populated.
|
|
8
|
+
* 2. Overrides are reflected in the response's `effectiveConfig`.
|
|
9
|
+
* 3. The handler never calls `recordInjectionEvents` — the simulate path
|
|
10
|
+
* must not touch the EMA event log.
|
|
11
|
+
*
|
|
12
|
+
* Workspace lives in a `mkdtemp` directory per test; `~/.vellum/` is never
|
|
13
|
+
* touched. The provider and DB are stubbed so no network or SQLite I/O
|
|
14
|
+
* fires.
|
|
15
|
+
*/
|
|
16
|
+
|
|
17
|
+
import { mkdtempSync, rmSync } from "node:fs";
|
|
18
|
+
import { tmpdir } from "node:os";
|
|
19
|
+
import { join } from "node:path";
|
|
20
|
+
import { afterEach, beforeEach, describe, expect, mock, test } from "bun:test";
|
|
21
|
+
|
|
22
|
+
import type {
|
|
23
|
+
Message,
|
|
24
|
+
Provider,
|
|
25
|
+
ProviderResponse,
|
|
26
|
+
SendMessageOptions,
|
|
27
|
+
ToolDefinition,
|
|
28
|
+
ToolUseContent,
|
|
29
|
+
} from "../../../providers/types.js";
|
|
30
|
+
|
|
31
|
+
// ---------------------------------------------------------------------------
|
|
32
|
+
// Mocks (installed before the route module is imported)
|
|
33
|
+
// ---------------------------------------------------------------------------
|
|
34
|
+
|
|
35
|
+
mock.module("../../../util/logger.js", () => ({
|
|
36
|
+
getLogger: () =>
|
|
37
|
+
new Proxy({} as Record<string, unknown>, {
|
|
38
|
+
get: () => () => {},
|
|
39
|
+
}),
|
|
40
|
+
}));
|
|
41
|
+
|
|
42
|
+
// Skill store: empty by default so the page index only contains test pages.
|
|
43
|
+
mock.module("../../../memory/v2/skill-store.js", () => ({
|
|
44
|
+
SKILL_SLUG_PREFIX: "skills/",
|
|
45
|
+
listSkillEntries: () => [],
|
|
46
|
+
seedV2SkillEntries: async () => undefined,
|
|
47
|
+
}));
|
|
48
|
+
|
|
49
|
+
// NOW.md loader: return a fixed string. The route reads NOW from the
|
|
50
|
+
// workspace at call time; stubbing keeps the test independent of disk state.
|
|
51
|
+
mock.module("../../../memory/v2/now-text.js", () => ({
|
|
52
|
+
loadNowText: async () => "2026-05-22 14:00 PT",
|
|
53
|
+
}));
|
|
54
|
+
|
|
55
|
+
// Injection-events module. `recordInjectionEvents` must never be called by
|
|
56
|
+
// the simulate path — record any call so the assertion catches a regression.
|
|
57
|
+
// `computeInjectionScores` is called twice per simulate (once inside runRouter
|
|
58
|
+
// for tier 2, once in the handler for the response payload); always returns
|
|
59
|
+
// zero scores since the test workspace has no event history.
|
|
60
|
+
const recordCalls: Array<{ slugs: readonly string[]; at: number }> = [];
|
|
61
|
+
mock.module("../../../memory/v2/injection-events.js", () => ({
|
|
62
|
+
recordInjectionEvents: (
|
|
63
|
+
_db: unknown,
|
|
64
|
+
slugs: readonly string[],
|
|
65
|
+
at: number,
|
|
66
|
+
) => {
|
|
67
|
+
recordCalls.push({ slugs, at });
|
|
68
|
+
},
|
|
69
|
+
computeInjectionScores: (
|
|
70
|
+
_db: unknown,
|
|
71
|
+
slugs: readonly string[],
|
|
72
|
+
_now: number,
|
|
73
|
+
): Map<string, number> => new Map(slugs.map((s) => [s, 0])),
|
|
74
|
+
}));
|
|
75
|
+
|
|
76
|
+
// Database handle: the simulate route only passes this through to
|
|
77
|
+
// `runRouter` and `computeInjectionScores`. Both are stubbed above, so a
|
|
78
|
+
// sentinel object is sufficient.
|
|
79
|
+
mock.module("../../../memory/db-connection.js", () => ({
|
|
80
|
+
getDb: () => ({ __stub: true }),
|
|
81
|
+
}));
|
|
82
|
+
|
|
83
|
+
// Config loader. The simulate route reads `memory.v2.enabled` (must be
|
|
84
|
+
// true) and the full `memory.v2.router` block (overrides merged on top).
|
|
85
|
+
const liveRouterConfig = {
|
|
86
|
+
enabled: true,
|
|
87
|
+
max_page_ids: 25,
|
|
88
|
+
router_prompt_path: null,
|
|
89
|
+
batch_size: null,
|
|
90
|
+
tier1_size: null,
|
|
91
|
+
tier2_size: null,
|
|
92
|
+
};
|
|
93
|
+
const mockConfigValue = {
|
|
94
|
+
memory: {
|
|
95
|
+
v2: {
|
|
96
|
+
enabled: true,
|
|
97
|
+
router: liveRouterConfig,
|
|
98
|
+
},
|
|
99
|
+
},
|
|
100
|
+
};
|
|
101
|
+
mock.module("../../../config/loader.js", () => ({
|
|
102
|
+
loadConfig: () => mockConfigValue,
|
|
103
|
+
getConfig: () => mockConfigValue,
|
|
104
|
+
getConfigReadOnly: () => mockConfigValue,
|
|
105
|
+
invalidateConfigCache: () => {},
|
|
106
|
+
API_KEY_PROVIDERS: [],
|
|
107
|
+
}));
|
|
108
|
+
|
|
109
|
+
// Provider stub. Default returns [1, 2] (selects first two pages).
|
|
110
|
+
let providerStub: Provider | null = null;
|
|
111
|
+
interface ProviderCall {
|
|
112
|
+
messages: Message[];
|
|
113
|
+
tools: ToolDefinition[] | undefined;
|
|
114
|
+
systemPrompt: string | undefined;
|
|
115
|
+
options: SendMessageOptions | undefined;
|
|
116
|
+
}
|
|
117
|
+
const providerCalls: ProviderCall[] = [];
|
|
118
|
+
mock.module("../../../providers/provider-send-message.js", () => ({
|
|
119
|
+
getConfiguredProvider: async () => providerStub,
|
|
120
|
+
extractToolUse: (response: ProviderResponse) =>
|
|
121
|
+
response.content.find((b): b is ToolUseContent => b.type === "tool_use"),
|
|
122
|
+
}));
|
|
123
|
+
|
|
124
|
+
// Platform helpers. `getWorkspaceDir` must return the per-test tmp dir so
|
|
125
|
+
// the route's page index points at the test workspace.
|
|
126
|
+
let workspaceDir = "";
|
|
127
|
+
mock.module("../../../util/platform.js", () => ({
|
|
128
|
+
getWorkspaceDir: () => workspaceDir,
|
|
129
|
+
}));
|
|
130
|
+
|
|
131
|
+
// ---------------------------------------------------------------------------
|
|
132
|
+
// Import under test (after all mocks above)
|
|
133
|
+
// ---------------------------------------------------------------------------
|
|
134
|
+
|
|
135
|
+
const { handleSimulateRouter } = await import("../memory-v2-routes.js");
|
|
136
|
+
const { writePage } = await import("../../../memory/v2/page-store.js");
|
|
137
|
+
const { invalidatePageIndex } =
|
|
138
|
+
await import("../../../memory/v2/page-index.js");
|
|
139
|
+
|
|
140
|
+
// ---------------------------------------------------------------------------
|
|
141
|
+
// Helpers
|
|
142
|
+
// ---------------------------------------------------------------------------
|
|
143
|
+
|
|
144
|
+
function makeProvider(pageIds: number[]): Provider {
|
|
145
|
+
return {
|
|
146
|
+
name: "stub",
|
|
147
|
+
sendMessage: async (messages, tools, systemPrompt, options) => {
|
|
148
|
+
providerCalls.push({ messages, tools, systemPrompt, options });
|
|
149
|
+
return {
|
|
150
|
+
model: "stub-model",
|
|
151
|
+
stopReason: "tool_use",
|
|
152
|
+
usage: { inputTokens: 0, outputTokens: 0 },
|
|
153
|
+
content: [
|
|
154
|
+
{
|
|
155
|
+
type: "tool_use",
|
|
156
|
+
id: "tu-1",
|
|
157
|
+
name: "select_pages_to_inject",
|
|
158
|
+
input: { page_ids: pageIds },
|
|
159
|
+
},
|
|
160
|
+
],
|
|
161
|
+
};
|
|
162
|
+
},
|
|
163
|
+
};
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
function makePage(slug: string, summary: string) {
|
|
167
|
+
return {
|
|
168
|
+
slug,
|
|
169
|
+
frontmatter: {
|
|
170
|
+
edges: [],
|
|
171
|
+
ref_files: [],
|
|
172
|
+
ref_urls: [],
|
|
173
|
+
summary,
|
|
174
|
+
},
|
|
175
|
+
body: "",
|
|
176
|
+
};
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
// ---------------------------------------------------------------------------
|
|
180
|
+
// Per-test setup / teardown
|
|
181
|
+
// ---------------------------------------------------------------------------
|
|
182
|
+
|
|
183
|
+
beforeEach(() => {
|
|
184
|
+
workspaceDir = mkdtempSync(join(tmpdir(), "memory-v2-simulate-test-"));
|
|
185
|
+
recordCalls.length = 0;
|
|
186
|
+
providerCalls.length = 0;
|
|
187
|
+
providerStub = null;
|
|
188
|
+
// Reset live router config to defaults between tests.
|
|
189
|
+
liveRouterConfig.batch_size = null;
|
|
190
|
+
liveRouterConfig.tier1_size = null;
|
|
191
|
+
liveRouterConfig.tier2_size = null;
|
|
192
|
+
liveRouterConfig.max_page_ids = 25;
|
|
193
|
+
invalidatePageIndex();
|
|
194
|
+
});
|
|
195
|
+
|
|
196
|
+
afterEach(() => {
|
|
197
|
+
invalidatePageIndex();
|
|
198
|
+
rmSync(workspaceDir, { recursive: true, force: true });
|
|
199
|
+
});
|
|
200
|
+
|
|
201
|
+
// ---------------------------------------------------------------------------
|
|
202
|
+
// Tests
|
|
203
|
+
// ---------------------------------------------------------------------------
|
|
204
|
+
|
|
205
|
+
describe("handleSimulateRouter", () => {
|
|
206
|
+
test("returns selectedSlugs + sourceBySlug populated for each pick", async () => {
|
|
207
|
+
await writePage(workspaceDir, makePage("alice", "A"));
|
|
208
|
+
await writePage(workspaceDir, makePage("bob", "B"));
|
|
209
|
+
await writePage(workspaceDir, makePage("carol", "C"));
|
|
210
|
+
providerStub = makeProvider([3, 1]);
|
|
211
|
+
|
|
212
|
+
const result = await handleSimulateRouter({
|
|
213
|
+
body: { query: "what's relevant?" },
|
|
214
|
+
});
|
|
215
|
+
|
|
216
|
+
expect(result.failureReason).toBeNull();
|
|
217
|
+
expect(result.selectedSlugs).toEqual(["carol", "alice"]);
|
|
218
|
+
expect(result.sourceBySlug["carol"]).toBe("tier3:0");
|
|
219
|
+
expect(result.sourceBySlug["alice"]).toBe("tier3:0");
|
|
220
|
+
expect(result.scores["carol"]).toBe(0);
|
|
221
|
+
expect(result.scores["alice"]).toBe(0);
|
|
222
|
+
expect(result.totalCandidatePages).toBe(3);
|
|
223
|
+
});
|
|
224
|
+
|
|
225
|
+
test("propagates overrides into effectiveConfig and reports them in overrides", async () => {
|
|
226
|
+
await writePage(workspaceDir, makePage("alice", "A"));
|
|
227
|
+
providerStub = makeProvider([1]);
|
|
228
|
+
|
|
229
|
+
const result = await handleSimulateRouter({
|
|
230
|
+
body: {
|
|
231
|
+
query: "test",
|
|
232
|
+
configOverrides: {
|
|
233
|
+
tier1_size: 50,
|
|
234
|
+
batch_size: 25,
|
|
235
|
+
},
|
|
236
|
+
},
|
|
237
|
+
});
|
|
238
|
+
|
|
239
|
+
expect(result.effectiveConfig.tier1_size).toBe(50);
|
|
240
|
+
expect(result.effectiveConfig.tier2_size).toBeNull(); // not overridden, inherits live
|
|
241
|
+
expect(result.effectiveConfig.batch_size).toBe(25);
|
|
242
|
+
expect(result.effectiveConfig.max_page_ids).toBe(25);
|
|
243
|
+
expect(result.overrides).toEqual({ tier1_size: 50, batch_size: 25 });
|
|
244
|
+
});
|
|
245
|
+
|
|
246
|
+
test("never writes to the EMA event log (recordInjectionEvents not called)", async () => {
|
|
247
|
+
await writePage(workspaceDir, makePage("alice", "A"));
|
|
248
|
+
await writePage(workspaceDir, makePage("bob", "B"));
|
|
249
|
+
providerStub = makeProvider([1, 2]);
|
|
250
|
+
|
|
251
|
+
await handleSimulateRouter({
|
|
252
|
+
body: { query: "should not record" },
|
|
253
|
+
});
|
|
254
|
+
|
|
255
|
+
expect(recordCalls).toEqual([]);
|
|
256
|
+
});
|
|
257
|
+
|
|
258
|
+
test("rejects an empty query at the schema layer", async () => {
|
|
259
|
+
await expect(
|
|
260
|
+
handleSimulateRouter({ body: { query: "" } }),
|
|
261
|
+
).rejects.toThrow();
|
|
262
|
+
});
|
|
263
|
+
|
|
264
|
+
test("rejects negative tier size at the schema layer", async () => {
|
|
265
|
+
await expect(
|
|
266
|
+
handleSimulateRouter({
|
|
267
|
+
body: { query: "test", configOverrides: { tier1_size: -5 } },
|
|
268
|
+
}),
|
|
269
|
+
).rejects.toThrow();
|
|
270
|
+
});
|
|
271
|
+
});
|