@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
|
@@ -41,6 +41,7 @@ import {
|
|
|
41
41
|
} from "bun:test";
|
|
42
42
|
|
|
43
43
|
import { drizzle } from "drizzle-orm/bun-sqlite";
|
|
44
|
+
import { z } from "zod";
|
|
44
45
|
|
|
45
46
|
import { makeMockLogger } from "../../../__tests__/helpers/mock-logger.js";
|
|
46
47
|
import type { AssistantConfig } from "../../../config/types.js";
|
|
@@ -153,6 +154,11 @@ mock.module("../skill-store.js", () => ({
|
|
|
153
154
|
isSkillSlug: (slug: string) => slug.startsWith("skills/"),
|
|
154
155
|
SKILL_SLUG_PREFIX: "skills/",
|
|
155
156
|
skillSlugFor: (id: string) => `skills/${id}`,
|
|
157
|
+
// PR 4 added `listSkillEntries`; `page-index.ts` (transitively imported
|
|
158
|
+
// via `page-store.ts` and `skill-store.ts`) consumes it at module-init
|
|
159
|
+
// time. Tests stage skill content via `skillState.entries`; expose them
|
|
160
|
+
// here so the page-index loader sees a consistent view.
|
|
161
|
+
listSkillEntries: () => Array.from(skillState.entries.values()),
|
|
156
162
|
}));
|
|
157
163
|
|
|
158
164
|
// ---------------------------------------------------------------------------
|
|
@@ -179,6 +185,93 @@ mock.module("../../memory-v2-activation-log-store.js", () => ({
|
|
|
179
185
|
},
|
|
180
186
|
}));
|
|
181
187
|
|
|
188
|
+
// ---------------------------------------------------------------------------
|
|
189
|
+
// Page-store mock — pass-through with optional per-slug failure injection
|
|
190
|
+
// ---------------------------------------------------------------------------
|
|
191
|
+
//
|
|
192
|
+
// Most tests want the real `readPage` (it walks the temp workspace seeded in
|
|
193
|
+
// `beforeAll`). The error-isolation tests stage a slug whose `readPage` call
|
|
194
|
+
// must throw — typically a Zod validation error mimicking the real-world
|
|
195
|
+
// "unrecognized frontmatter key" failure that motivated this work. Tests
|
|
196
|
+
// stage entries via `pageStoreState.failingSlugs` and reset in `resetState`.
|
|
197
|
+
//
|
|
198
|
+
// Bun's `mock.module` mutates the module's exports object in place, so
|
|
199
|
+
// `realPageStore.readPage` AFTER the mock applies would resolve to the mock
|
|
200
|
+
// itself — calling it would recurse. We capture the original function value
|
|
201
|
+
// (not a property lookup) before installing the mock so the pass-through
|
|
202
|
+
// path has a real reference to the underlying implementation.
|
|
203
|
+
|
|
204
|
+
const realPageStoreModule = await import("../page-store.js");
|
|
205
|
+
const realReadPage = realPageStoreModule.readPage;
|
|
206
|
+
const pageStoreState = {
|
|
207
|
+
failingSlugs: new Map<string, Error>(),
|
|
208
|
+
};
|
|
209
|
+
mock.module("../page-store.js", () => ({
|
|
210
|
+
...realPageStoreModule,
|
|
211
|
+
readPage: async (workspaceDir: string, slug: string) => {
|
|
212
|
+
const err = pageStoreState.failingSlugs.get(slug);
|
|
213
|
+
if (err) throw err;
|
|
214
|
+
return realReadPage(workspaceDir, slug);
|
|
215
|
+
},
|
|
216
|
+
}));
|
|
217
|
+
|
|
218
|
+
// ---------------------------------------------------------------------------
|
|
219
|
+
// Router mock — programmable per-call result
|
|
220
|
+
// ---------------------------------------------------------------------------
|
|
221
|
+
//
|
|
222
|
+
// PR 10 wires `runRouter` into `injectMemoryV2Block` behind the
|
|
223
|
+
// `memory.v2.router.enabled` flag. The activation-mode tests above never
|
|
224
|
+
// flip the flag, so the default mock returns a no-op result and the router
|
|
225
|
+
// branch is never exercised. Router-mode tests set `routerState.nextResult`
|
|
226
|
+
// to stage a deterministic outcome before each call.
|
|
227
|
+
|
|
228
|
+
interface RouterResultStub {
|
|
229
|
+
selectedSlugs: string[];
|
|
230
|
+
failureReason: string | null;
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
const routerState = {
|
|
234
|
+
nextResult: null as RouterResultStub | null,
|
|
235
|
+
callCount: 0,
|
|
236
|
+
};
|
|
237
|
+
|
|
238
|
+
mock.module("../router.js", () => ({
|
|
239
|
+
runRouter: async () => {
|
|
240
|
+
routerState.callCount++;
|
|
241
|
+
return (
|
|
242
|
+
routerState.nextResult ?? {
|
|
243
|
+
selectedSlugs: [],
|
|
244
|
+
failureReason: null,
|
|
245
|
+
}
|
|
246
|
+
);
|
|
247
|
+
},
|
|
248
|
+
}));
|
|
249
|
+
|
|
250
|
+
// ---------------------------------------------------------------------------
|
|
251
|
+
// Activation-store mock — pass-through with optional `save` failure injection
|
|
252
|
+
// ---------------------------------------------------------------------------
|
|
253
|
+
//
|
|
254
|
+
// One regression test forces `save` to throw to exercise the
|
|
255
|
+
// `injectMemoryV2Block` outer try/finally — telemetry must still be flushed
|
|
256
|
+
// (with `mode: "errored"`) and the error must propagate. Default behavior
|
|
257
|
+
// delegates to the real activation-store so the rest of the suite stays
|
|
258
|
+
// untouched. Same pre-mock function-capture trick as `readPage` above.
|
|
259
|
+
|
|
260
|
+
const realActivationStoreModule = await import("../activation-store.js");
|
|
261
|
+
const realSave = realActivationStoreModule.save;
|
|
262
|
+
const activationStoreState = {
|
|
263
|
+
saveShouldThrow: false,
|
|
264
|
+
};
|
|
265
|
+
mock.module("../activation-store.js", () => ({
|
|
266
|
+
...realActivationStoreModule,
|
|
267
|
+
save: async (...args: Parameters<typeof realSave>) => {
|
|
268
|
+
if (activationStoreState.saveShouldThrow) {
|
|
269
|
+
throw new Error("simulated activation-store save failure");
|
|
270
|
+
}
|
|
271
|
+
return realSave(...args);
|
|
272
|
+
},
|
|
273
|
+
}));
|
|
274
|
+
|
|
182
275
|
// ---------------------------------------------------------------------------
|
|
183
276
|
// Workspace + DB setup
|
|
184
277
|
// ---------------------------------------------------------------------------
|
|
@@ -299,8 +392,10 @@ function makeConfig(
|
|
|
299
392
|
epsilon: number;
|
|
300
393
|
dense_weight: number;
|
|
301
394
|
sparse_weight: number;
|
|
395
|
+
router: { enabled: boolean; max_page_ids?: number };
|
|
302
396
|
}> = {},
|
|
303
397
|
): AssistantConfig {
|
|
398
|
+
const { router, ...rest } = overrides;
|
|
304
399
|
return {
|
|
305
400
|
memory: {
|
|
306
401
|
v2: {
|
|
@@ -314,7 +409,8 @@ function makeConfig(
|
|
|
314
409
|
epsilon: 0.01,
|
|
315
410
|
dense_weight: 1.0,
|
|
316
411
|
sparse_weight: 0.0,
|
|
317
|
-
...
|
|
412
|
+
router: { enabled: false, max_page_ids: 25, ...(router ?? {}) },
|
|
413
|
+
...rest,
|
|
318
414
|
},
|
|
319
415
|
},
|
|
320
416
|
} as unknown as AssistantConfig;
|
|
@@ -390,6 +486,10 @@ function resetState(): void {
|
|
|
390
486
|
skillState.entries.clear();
|
|
391
487
|
telemetryState.recordCalls.length = 0;
|
|
392
488
|
telemetryState.recordShouldThrow = false;
|
|
489
|
+
pageStoreState.failingSlugs.clear();
|
|
490
|
+
activationStoreState.saveShouldThrow = false;
|
|
491
|
+
routerState.nextResult = null;
|
|
492
|
+
routerState.callCount = 0;
|
|
393
493
|
// The qdrant module caches its client; the cached client may be a
|
|
394
494
|
// MockQdrantClient instance from a sibling test file. Reset to force a
|
|
395
495
|
// fresh `new QdrantClient()` against this file's mock.
|
|
@@ -712,11 +812,13 @@ describe("injectMemoryV2Block", () => {
|
|
|
712
812
|
});
|
|
713
813
|
|
|
714
814
|
test("persists sparse state — only slugs above epsilon survive", async () => {
|
|
715
|
-
// Carol scores high; alice
|
|
716
|
-
//
|
|
815
|
+
// Carol scores high; alice essentially zero. After saving, only carol
|
|
816
|
+
// should appear in the persisted state map. denseScore is the raw
|
|
817
|
+
// Qdrant cosine in [-1, 1]; alice uses -1 so the post `(x+1)/2`
|
|
818
|
+
// unit-mapping pins her fused score to 0 — below epsilon.
|
|
717
819
|
stageTurn([
|
|
718
820
|
{ slug: "carol-jazz", denseScore: 1.0 },
|
|
719
|
-
{ slug: "alice-vscode", denseScore:
|
|
821
|
+
{ slug: "alice-vscode", denseScore: -1.0 },
|
|
720
822
|
]);
|
|
721
823
|
await injectMemoryV2Block({
|
|
722
824
|
database: db,
|
|
@@ -907,9 +1009,10 @@ describe("injectMemoryV2Block", () => {
|
|
|
907
1009
|
|
|
908
1010
|
test("skill slugs whose entry is missing from the cache are dropped silently", async () => {
|
|
909
1011
|
// The skill ranks into top-K but the in-process cache no longer knows
|
|
910
|
-
// its content (skill uninstalled mid-run
|
|
911
|
-
//
|
|
912
|
-
//
|
|
1012
|
+
// its content (skill uninstalled mid-run, or a startup race where the
|
|
1013
|
+
// Qdrant row landed before the skill cache was seeded). The render path
|
|
1014
|
+
// drops it without surfacing it as a `missingSlugs` page-missing event —
|
|
1015
|
+
// that status is reserved for on-disk concept pages, not catalog-derived
|
|
913
1016
|
// skill entries.
|
|
914
1017
|
stageTurn([{ slug: "skills/missing-skill", denseScore: 0.9 }]);
|
|
915
1018
|
// No `stageSkills` call — cache stays empty.
|
|
@@ -925,10 +1028,16 @@ describe("injectMemoryV2Block", () => {
|
|
|
925
1028
|
config: makeConfig(),
|
|
926
1029
|
});
|
|
927
1030
|
|
|
928
|
-
//
|
|
929
|
-
//
|
|
930
|
-
|
|
1031
|
+
// The skill is excluded from `toInject` (and `everInjected`) so future
|
|
1032
|
+
// per-turn runs re-attempt the attach once the cache is populated.
|
|
1033
|
+
// `block` collapses to null because the only candidate was a cache miss.
|
|
1034
|
+
expect(result.toInject).toEqual([]);
|
|
931
1035
|
expect(result.block).toBeNull();
|
|
1036
|
+
|
|
1037
|
+
// Persisted `everInjected` must not record the missing skill — that
|
|
1038
|
+
// would block retry on a later turn until compaction-driven eviction.
|
|
1039
|
+
const persisted = await hydrate(db, "conv-1");
|
|
1040
|
+
expect(persisted!.everInjected).toEqual([]);
|
|
932
1041
|
});
|
|
933
1042
|
|
|
934
1043
|
test("returns null when both concept pages and skills are empty", async () => {
|
|
@@ -1287,4 +1396,513 @@ describe("injectMemoryV2Block", () => {
|
|
|
1287
1396
|
{ slug: "alice-vscode", turn: 1 },
|
|
1288
1397
|
]);
|
|
1289
1398
|
});
|
|
1399
|
+
|
|
1400
|
+
// ---------------------------------------------------------------------------
|
|
1401
|
+
// Per-page error isolation + on-throw telemetry
|
|
1402
|
+
// ---------------------------------------------------------------------------
|
|
1403
|
+
|
|
1404
|
+
test("one slug's page-read failing isolates the error — other slugs still render and the corrupt slug records `status: corrupt`", async () => {
|
|
1405
|
+
// Two slugs rank into top-K together. Carol's page reads cleanly; alice's
|
|
1406
|
+
// `readPage` throws a ZodError mimicking the real "unrecognized
|
|
1407
|
+
// frontmatter key" failure that motivated this work. Before the fix, the
|
|
1408
|
+
// bare `Promise.all` rejected and the entire turn lost its block AND its
|
|
1409
|
+
// activation log row. With per-page isolation, carol still renders and
|
|
1410
|
+
// the activation log row marks alice as `corrupt` (telemetry remains
|
|
1411
|
+
// observable for triage).
|
|
1412
|
+
const zodErr = z.object({ x: z.string() }).safeParse({ x: 1 }).error!;
|
|
1413
|
+
pageStoreState.failingSlugs.set("alice-vscode", zodErr);
|
|
1414
|
+
stageTurn([
|
|
1415
|
+
{ slug: "alice-vscode", denseScore: 0.95 },
|
|
1416
|
+
{ slug: "carol-jazz", denseScore: 0.9 },
|
|
1417
|
+
]);
|
|
1418
|
+
|
|
1419
|
+
const result = await injectMemoryV2Block({
|
|
1420
|
+
database: db,
|
|
1421
|
+
conversationId: "conv-1",
|
|
1422
|
+
currentTurn: 1,
|
|
1423
|
+
userMessage: "music and editors",
|
|
1424
|
+
assistantMessage: "",
|
|
1425
|
+
nowText: "Now",
|
|
1426
|
+
messageId: "msg-1",
|
|
1427
|
+
config: makeConfig(),
|
|
1428
|
+
});
|
|
1429
|
+
|
|
1430
|
+
// (a) Block is non-null and contains content from the OTHER slug; alice
|
|
1431
|
+
// is dropped from the rendered block but does not poison the batch.
|
|
1432
|
+
expect(result.block).not.toBeNull();
|
|
1433
|
+
expect(result.block).toContain("# memory/concepts/carol-jazz.md");
|
|
1434
|
+
expect(result.block).not.toContain("# memory/concepts/alice-vscode.md");
|
|
1435
|
+
|
|
1436
|
+
// (b) Activation log row exists with carol `injected` and alice
|
|
1437
|
+
// `corrupt`. Status `corrupt` is reserved for read-time throws and is
|
|
1438
|
+
// distinct from `page_missing` (which is null-return / file vanished).
|
|
1439
|
+
expect(telemetryState.recordCalls.length).toBe(1);
|
|
1440
|
+
const row = telemetryState.recordCalls[0] as {
|
|
1441
|
+
mode: string;
|
|
1442
|
+
concepts: Array<{ slug: string; status: string }>;
|
|
1443
|
+
};
|
|
1444
|
+
expect(row.mode).toBe("per-turn");
|
|
1445
|
+
const byslug = new Map(row.concepts.map((c) => [c.slug, c]));
|
|
1446
|
+
expect(byslug.get("alice-vscode")!.status).toBe("corrupt");
|
|
1447
|
+
expect(byslug.get("carol-jazz")!.status).toBe("injected");
|
|
1448
|
+
|
|
1449
|
+
// (c) Both slugs land in `toInject` and `everInjected` — same handling
|
|
1450
|
+
// as `page_missing` (see the phantom-slug test): the slug was attempted
|
|
1451
|
+
// this turn, telemetry records the outcome, and we don't keep re-trying
|
|
1452
|
+
// a stale Qdrant / edge-index entry on every subsequent turn.
|
|
1453
|
+
expect(new Set(result.toInject)).toEqual(
|
|
1454
|
+
new Set(["alice-vscode", "carol-jazz"]),
|
|
1455
|
+
);
|
|
1456
|
+
const persisted = await hydrate(db, "conv-1");
|
|
1457
|
+
const everInjectedSlugs = persisted!.everInjected.map((e) => e.slug);
|
|
1458
|
+
expect(new Set(everInjectedSlugs)).toEqual(
|
|
1459
|
+
new Set(["alice-vscode", "carol-jazz"]),
|
|
1460
|
+
);
|
|
1461
|
+
});
|
|
1462
|
+
|
|
1463
|
+
test("a throw before renderInjectionBlock still flushes telemetry as `mode: errored` and re-throws", async () => {
|
|
1464
|
+
// The activation-state save throws — the most realistic non-render
|
|
1465
|
+
// failure mode (transient SQLite write error mid-injection). The
|
|
1466
|
+
// `injectMemoryV2Block` outer try/finally must (a) flush an activation
|
|
1467
|
+
// log row tagged `mode: "errored"` so silent failures stay observable
|
|
1468
|
+
// in the database, and (b) re-throw so callers (e.g. `prepareMemory`'s
|
|
1469
|
+
// outer catch) see the original error and can degrade gracefully.
|
|
1470
|
+
activationStoreState.saveShouldThrow = true;
|
|
1471
|
+
stageTurn([{ slug: "alice-vscode", denseScore: 0.9 }]);
|
|
1472
|
+
|
|
1473
|
+
let threw: unknown = undefined;
|
|
1474
|
+
try {
|
|
1475
|
+
await injectMemoryV2Block({
|
|
1476
|
+
database: db,
|
|
1477
|
+
conversationId: "conv-1",
|
|
1478
|
+
currentTurn: 1,
|
|
1479
|
+
userMessage: "Alice's editor",
|
|
1480
|
+
assistantMessage: "",
|
|
1481
|
+
nowText: "Now",
|
|
1482
|
+
messageId: "msg-1",
|
|
1483
|
+
config: makeConfig(),
|
|
1484
|
+
});
|
|
1485
|
+
} catch (err) {
|
|
1486
|
+
threw = err;
|
|
1487
|
+
}
|
|
1488
|
+
|
|
1489
|
+
// The original error propagates to the caller.
|
|
1490
|
+
expect(threw).toBeInstanceOf(Error);
|
|
1491
|
+
expect((threw as Error).message).toContain(
|
|
1492
|
+
"simulated activation-store save failure",
|
|
1493
|
+
);
|
|
1494
|
+
|
|
1495
|
+
// A telemetry row was still written, tagged `errored`. `concepts` is
|
|
1496
|
+
// empty because the throw fired before the row-builder ran — that's
|
|
1497
|
+
// expected and documented as part of the contract.
|
|
1498
|
+
expect(telemetryState.recordCalls.length).toBe(1);
|
|
1499
|
+
const row = telemetryState.recordCalls[0] as {
|
|
1500
|
+
mode: string;
|
|
1501
|
+
conversationId: string;
|
|
1502
|
+
turn: number;
|
|
1503
|
+
concepts: unknown[];
|
|
1504
|
+
};
|
|
1505
|
+
expect(row.mode).toBe("errored");
|
|
1506
|
+
expect(row.conversationId).toBe("conv-1");
|
|
1507
|
+
expect(row.turn).toBe(1);
|
|
1508
|
+
expect(row.concepts).toEqual([]);
|
|
1509
|
+
});
|
|
1510
|
+
|
|
1511
|
+
test("activation pipeline routes through `finalizeInjection` — telemetry shape and config snapshot match the contract", async () => {
|
|
1512
|
+
// Pure-refactor regression check: `injectMemoryV2Block` now delegates the
|
|
1513
|
+
// tail (state save + render + telemetry finalization + log write) to a
|
|
1514
|
+
// private `finalizeInjection` helper. This test asserts the helper is
|
|
1515
|
+
// exercised by verifying `recordMemoryV2ActivationLog` is called with the
|
|
1516
|
+
// same arg shape as before — same conversationId/turn/mode, same config
|
|
1517
|
+
// snapshot, and a fully populated concept row whose status was finalized
|
|
1518
|
+
// to `"injected"` on the freshly-attached slug.
|
|
1519
|
+
stageTurn([{ slug: "alice-vscode", denseScore: 0.9 }]);
|
|
1520
|
+
|
|
1521
|
+
const result = await injectMemoryV2Block({
|
|
1522
|
+
database: db,
|
|
1523
|
+
conversationId: "conv-finalize",
|
|
1524
|
+
currentTurn: 7,
|
|
1525
|
+
userMessage: "Alice's editor",
|
|
1526
|
+
assistantMessage: "",
|
|
1527
|
+
nowText: "Now",
|
|
1528
|
+
messageId: "msg-finalize",
|
|
1529
|
+
config: makeConfig(),
|
|
1530
|
+
});
|
|
1531
|
+
|
|
1532
|
+
// The helper rendered + persisted just like the original tail did.
|
|
1533
|
+
expect(result.block).toContain("alice-vscode");
|
|
1534
|
+
expect(result.toInject).toEqual(["alice-vscode"]);
|
|
1535
|
+
|
|
1536
|
+
expect(telemetryState.recordCalls.length).toBe(1);
|
|
1537
|
+
const row = telemetryState.recordCalls[0] as {
|
|
1538
|
+
conversationId: string;
|
|
1539
|
+
turn: number;
|
|
1540
|
+
mode: string;
|
|
1541
|
+
concepts: Array<{
|
|
1542
|
+
slug: string;
|
|
1543
|
+
status: string;
|
|
1544
|
+
finalActivation: number;
|
|
1545
|
+
}>;
|
|
1546
|
+
config: {
|
|
1547
|
+
d: number;
|
|
1548
|
+
c_user: number;
|
|
1549
|
+
c_assistant: number;
|
|
1550
|
+
c_now: number;
|
|
1551
|
+
k: number;
|
|
1552
|
+
hops: number;
|
|
1553
|
+
top_k: number;
|
|
1554
|
+
epsilon: number;
|
|
1555
|
+
};
|
|
1556
|
+
};
|
|
1557
|
+
expect(row.conversationId).toBe("conv-finalize");
|
|
1558
|
+
expect(row.turn).toBe(7);
|
|
1559
|
+
expect(row.mode).toBe("per-turn");
|
|
1560
|
+
// Config snapshot must include all eight tunables — proves the helper is
|
|
1561
|
+
// pulling from `config.memory.v2` rather than synthesizing a partial.
|
|
1562
|
+
expect(Object.keys(row.config).sort()).toEqual(
|
|
1563
|
+
[
|
|
1564
|
+
"c_assistant",
|
|
1565
|
+
"c_now",
|
|
1566
|
+
"c_user",
|
|
1567
|
+
"d",
|
|
1568
|
+
"epsilon",
|
|
1569
|
+
"hops",
|
|
1570
|
+
"k",
|
|
1571
|
+
"top_k",
|
|
1572
|
+
].sort(),
|
|
1573
|
+
);
|
|
1574
|
+
// Status finalization ran inside the helper — alice was selected and
|
|
1575
|
+
// rendered, so its row reads `injected`.
|
|
1576
|
+
const aliceRow = row.concepts.find((c) => c.slug === "alice-vscode");
|
|
1577
|
+
expect(aliceRow?.status).toBe("injected");
|
|
1578
|
+
});
|
|
1579
|
+
|
|
1580
|
+
// ---------------------------------------------------------------------------
|
|
1581
|
+
// Router mode (flag-gated)
|
|
1582
|
+
// ---------------------------------------------------------------------------
|
|
1583
|
+
|
|
1584
|
+
describe("router mode", () => {
|
|
1585
|
+
test("flag-on: router-selected slugs render and append to everInjected", async () => {
|
|
1586
|
+
// Router picks alice. The activation pipeline never runs — we don't
|
|
1587
|
+
// stage any qdrant responses here, and that's intentional. The
|
|
1588
|
+
// candidate set comes straight from the router's `selectedSlugs`.
|
|
1589
|
+
routerState.nextResult = {
|
|
1590
|
+
selectedSlugs: ["alice-vscode"],
|
|
1591
|
+
failureReason: null,
|
|
1592
|
+
};
|
|
1593
|
+
|
|
1594
|
+
const result = await injectMemoryV2Block({
|
|
1595
|
+
database: db,
|
|
1596
|
+
conversationId: "conv-router-1",
|
|
1597
|
+
currentTurn: 1,
|
|
1598
|
+
userMessage: "Tell me about Alice",
|
|
1599
|
+
assistantMessage: "",
|
|
1600
|
+
nowText: "Now",
|
|
1601
|
+
messageId: "msg-1",
|
|
1602
|
+
config: makeConfig({ router: { enabled: true } }),
|
|
1603
|
+
});
|
|
1604
|
+
|
|
1605
|
+
expect(routerState.callCount).toBe(1);
|
|
1606
|
+
expect(result.toInject).toEqual(["alice-vscode"]);
|
|
1607
|
+
expect(result.block).not.toBeNull();
|
|
1608
|
+
expect(result.block).toContain("# memory/concepts/alice-vscode.md");
|
|
1609
|
+
|
|
1610
|
+
const persisted = await hydrate(db, "conv-router-1");
|
|
1611
|
+
expect(persisted!.everInjected).toEqual([
|
|
1612
|
+
{ slug: "alice-vscode", turn: 1 },
|
|
1613
|
+
]);
|
|
1614
|
+
// Router mode persists an empty sparse activation map — the router
|
|
1615
|
+
// does not compute spreading-activation scores.
|
|
1616
|
+
expect(persisted!.state).toEqual({});
|
|
1617
|
+
|
|
1618
|
+
// Telemetry: success rows get `mode: "router"` and `source: "router"`,
|
|
1619
|
+
// with all activation fields zeroed.
|
|
1620
|
+
expect(telemetryState.recordCalls.length).toBe(1);
|
|
1621
|
+
const row = telemetryState.recordCalls[0] as {
|
|
1622
|
+
mode: string;
|
|
1623
|
+
concepts: Array<{
|
|
1624
|
+
slug: string;
|
|
1625
|
+
source: string;
|
|
1626
|
+
status: string;
|
|
1627
|
+
finalActivation: number;
|
|
1628
|
+
ownActivation: number;
|
|
1629
|
+
}>;
|
|
1630
|
+
};
|
|
1631
|
+
expect(row.mode).toBe("router");
|
|
1632
|
+
const aliceRow = row.concepts.find((c) => c.slug === "alice-vscode");
|
|
1633
|
+
expect(aliceRow).toBeDefined();
|
|
1634
|
+
expect(aliceRow!.source).toBe("router");
|
|
1635
|
+
expect(aliceRow!.status).toBe("injected");
|
|
1636
|
+
expect(aliceRow!.finalActivation).toBe(0);
|
|
1637
|
+
expect(aliceRow!.ownActivation).toBe(0);
|
|
1638
|
+
});
|
|
1639
|
+
|
|
1640
|
+
test("flag-on: router failure logs warn, writes mode:`errored` telemetry, returns null block", async () => {
|
|
1641
|
+
routerState.nextResult = {
|
|
1642
|
+
selectedSlugs: [],
|
|
1643
|
+
failureReason: "api_error",
|
|
1644
|
+
};
|
|
1645
|
+
|
|
1646
|
+
const result = await injectMemoryV2Block({
|
|
1647
|
+
database: db,
|
|
1648
|
+
conversationId: "conv-router-fail",
|
|
1649
|
+
currentTurn: 3,
|
|
1650
|
+
userMessage: "anything",
|
|
1651
|
+
assistantMessage: "ok",
|
|
1652
|
+
nowText: "Now",
|
|
1653
|
+
messageId: "msg-fail",
|
|
1654
|
+
config: makeConfig({ router: { enabled: true } }),
|
|
1655
|
+
});
|
|
1656
|
+
|
|
1657
|
+
expect(result.block).toBeNull();
|
|
1658
|
+
expect(result.toInject).toEqual([]);
|
|
1659
|
+
|
|
1660
|
+
// Stub state still advanced.
|
|
1661
|
+
const persisted = await hydrate(db, "conv-router-fail");
|
|
1662
|
+
expect(persisted).not.toBeNull();
|
|
1663
|
+
expect(persisted!.currentTurn).toBe(3);
|
|
1664
|
+
expect(persisted!.messageId).toBe("msg-fail");
|
|
1665
|
+
expect(persisted!.state).toEqual({});
|
|
1666
|
+
expect(persisted!.everInjected).toEqual([]);
|
|
1667
|
+
|
|
1668
|
+
// Single telemetry row with `mode: "errored"` (not `"router"`).
|
|
1669
|
+
expect(telemetryState.recordCalls.length).toBe(1);
|
|
1670
|
+
const row = telemetryState.recordCalls[0] as {
|
|
1671
|
+
mode: string;
|
|
1672
|
+
conversationId: string;
|
|
1673
|
+
turn: number;
|
|
1674
|
+
concepts: unknown[];
|
|
1675
|
+
};
|
|
1676
|
+
expect(row.mode).toBe("errored");
|
|
1677
|
+
expect(row.conversationId).toBe("conv-router-fail");
|
|
1678
|
+
expect(row.turn).toBe(3);
|
|
1679
|
+
expect(row.concepts).toEqual([]);
|
|
1680
|
+
});
|
|
1681
|
+
|
|
1682
|
+
test("flag-on: router abstention (empty selectedSlugs, no failure) writes mode:`router` row with no injected pages", async () => {
|
|
1683
|
+
routerState.nextResult = {
|
|
1684
|
+
selectedSlugs: [],
|
|
1685
|
+
failureReason: null,
|
|
1686
|
+
};
|
|
1687
|
+
|
|
1688
|
+
const result = await injectMemoryV2Block({
|
|
1689
|
+
database: db,
|
|
1690
|
+
conversationId: "conv-router-abstain",
|
|
1691
|
+
currentTurn: 1,
|
|
1692
|
+
userMessage: "small talk",
|
|
1693
|
+
assistantMessage: "",
|
|
1694
|
+
nowText: "Now",
|
|
1695
|
+
messageId: "msg-abstain",
|
|
1696
|
+
config: makeConfig({ router: { enabled: true } }),
|
|
1697
|
+
});
|
|
1698
|
+
|
|
1699
|
+
expect(result.block).toBeNull();
|
|
1700
|
+
expect(result.toInject).toEqual([]);
|
|
1701
|
+
|
|
1702
|
+
// No prior everInjected to dedup against, so toInject is empty and
|
|
1703
|
+
// nothing renders. State still advanced.
|
|
1704
|
+
const persisted = await hydrate(db, "conv-router-abstain");
|
|
1705
|
+
expect(persisted!.everInjected).toEqual([]);
|
|
1706
|
+
expect(persisted!.currentTurn).toBe(1);
|
|
1707
|
+
|
|
1708
|
+
// Telemetry: `mode: "router"` row with zero injected pages.
|
|
1709
|
+
expect(telemetryState.recordCalls.length).toBe(1);
|
|
1710
|
+
const row = telemetryState.recordCalls[0] as {
|
|
1711
|
+
mode: string;
|
|
1712
|
+
concepts: Array<{ slug: string; status: string }>;
|
|
1713
|
+
};
|
|
1714
|
+
expect(row.mode).toBe("router");
|
|
1715
|
+
const injectedCount = row.concepts.filter(
|
|
1716
|
+
(c) => c.status === "injected",
|
|
1717
|
+
).length;
|
|
1718
|
+
expect(injectedCount).toBe(0);
|
|
1719
|
+
});
|
|
1720
|
+
|
|
1721
|
+
test("flag-on: router-selected slug whose page is missing on disk records `page_missing` and is NOT added to everInjected", async () => {
|
|
1722
|
+
routerState.nextResult = {
|
|
1723
|
+
selectedSlugs: ["phantom-router-slug"],
|
|
1724
|
+
failureReason: null,
|
|
1725
|
+
};
|
|
1726
|
+
|
|
1727
|
+
const result = await injectMemoryV2Block({
|
|
1728
|
+
database: db,
|
|
1729
|
+
conversationId: "conv-router-missing",
|
|
1730
|
+
currentTurn: 1,
|
|
1731
|
+
userMessage: "phantom",
|
|
1732
|
+
assistantMessage: "",
|
|
1733
|
+
nowText: "Now",
|
|
1734
|
+
messageId: "msg-missing",
|
|
1735
|
+
config: makeConfig({ router: { enabled: true } }),
|
|
1736
|
+
});
|
|
1737
|
+
|
|
1738
|
+
// No backing page → block collapses to null.
|
|
1739
|
+
expect(result.block).toBeNull();
|
|
1740
|
+
// toInject mirrors `newlyInjected` from `finalizeInjection` — the
|
|
1741
|
+
// missing slug still flowed through `slugsToRender` so it's recorded
|
|
1742
|
+
// here (matching the activation-mode phantom-slug contract).
|
|
1743
|
+
expect(result.toInject).toEqual(["phantom-router-slug"]);
|
|
1744
|
+
|
|
1745
|
+
// Activation-mode parity: the phantom slug DOES land in everInjected
|
|
1746
|
+
// so we don't infinite-retry it. (This matches the behavior the
|
|
1747
|
+
// existing `returns null block when toInject slugs all reference
|
|
1748
|
+
// missing pages` test asserts for activation mode.)
|
|
1749
|
+
const persisted = await hydrate(db, "conv-router-missing");
|
|
1750
|
+
expect(persisted!.everInjected).toEqual([
|
|
1751
|
+
{ slug: "phantom-router-slug", turn: 1 },
|
|
1752
|
+
]);
|
|
1753
|
+
|
|
1754
|
+
// Telemetry: `status: "page_missing"` for the phantom slug.
|
|
1755
|
+
expect(telemetryState.recordCalls.length).toBe(1);
|
|
1756
|
+
const row = telemetryState.recordCalls[0] as {
|
|
1757
|
+
mode: string;
|
|
1758
|
+
concepts: Array<{ slug: string; status: string; source: string }>;
|
|
1759
|
+
};
|
|
1760
|
+
expect(row.mode).toBe("router");
|
|
1761
|
+
const phantom = row.concepts.find(
|
|
1762
|
+
(c) => c.slug === "phantom-router-slug",
|
|
1763
|
+
);
|
|
1764
|
+
expect(phantom).toBeDefined();
|
|
1765
|
+
expect(phantom!.status).toBe("page_missing");
|
|
1766
|
+
expect(phantom!.source).toBe("router");
|
|
1767
|
+
});
|
|
1768
|
+
|
|
1769
|
+
test("flag-on: router re-picking a prior-everInjected slug does NOT re-render it; non-overlapping picks render and append to everInjected", async () => {
|
|
1770
|
+
// Turn 1: router picks alice. Standard append.
|
|
1771
|
+
routerState.nextResult = {
|
|
1772
|
+
selectedSlugs: ["alice-vscode"],
|
|
1773
|
+
failureReason: null,
|
|
1774
|
+
};
|
|
1775
|
+
const turn1 = await injectMemoryV2Block({
|
|
1776
|
+
database: db,
|
|
1777
|
+
conversationId: "conv-router-dedup",
|
|
1778
|
+
currentTurn: 1,
|
|
1779
|
+
userMessage: "Tell me about Alice",
|
|
1780
|
+
assistantMessage: "",
|
|
1781
|
+
nowText: "Now",
|
|
1782
|
+
messageId: "msg-1",
|
|
1783
|
+
config: makeConfig({ router: { enabled: true } }),
|
|
1784
|
+
});
|
|
1785
|
+
expect(turn1.toInject).toEqual(["alice-vscode"]);
|
|
1786
|
+
|
|
1787
|
+
// Turn 2: router re-picks alice (the "re-anchor" prompt branch) AND
|
|
1788
|
+
// adds bob. The block must NOT contain alice's body — her cached
|
|
1789
|
+
// attachment from turn 1 is still on the prior user message — but
|
|
1790
|
+
// must contain bob's.
|
|
1791
|
+
telemetryState.recordCalls.length = 0;
|
|
1792
|
+
routerState.nextResult = {
|
|
1793
|
+
selectedSlugs: ["alice-vscode", "bob-coffee"],
|
|
1794
|
+
failureReason: null,
|
|
1795
|
+
};
|
|
1796
|
+
const turn2 = await injectMemoryV2Block({
|
|
1797
|
+
database: db,
|
|
1798
|
+
conversationId: "conv-router-dedup",
|
|
1799
|
+
currentTurn: 2,
|
|
1800
|
+
userMessage: "And Bob?",
|
|
1801
|
+
assistantMessage: "Sure",
|
|
1802
|
+
nowText: "Now",
|
|
1803
|
+
messageId: "msg-2",
|
|
1804
|
+
config: makeConfig({ router: { enabled: true } }),
|
|
1805
|
+
});
|
|
1806
|
+
|
|
1807
|
+
// Re-picked alice was deduped; only bob is freshly injected.
|
|
1808
|
+
expect(turn2.toInject).toEqual(["bob-coffee"]);
|
|
1809
|
+
expect(turn2.block).not.toBeNull();
|
|
1810
|
+
expect(turn2.block).toContain("# memory/concepts/bob-coffee.md");
|
|
1811
|
+
expect(turn2.block).toContain("Bob takes his coffee");
|
|
1812
|
+
expect(turn2.block).not.toContain("VS Code");
|
|
1813
|
+
expect(turn2.block).not.toContain("# memory/concepts/alice-vscode.md");
|
|
1814
|
+
|
|
1815
|
+
// everInjected only gained bob — alice was already there.
|
|
1816
|
+
const persisted = await hydrate(db, "conv-router-dedup");
|
|
1817
|
+
expect(persisted!.everInjected).toEqual([
|
|
1818
|
+
{ slug: "alice-vscode", turn: 1 },
|
|
1819
|
+
{ slug: "bob-coffee", turn: 2 },
|
|
1820
|
+
]);
|
|
1821
|
+
});
|
|
1822
|
+
|
|
1823
|
+
test("flag-on: telemetry distinguishes `source: router` (router picks) from `source: carry_over` (prior-everInjected slugs the router did not re-pick)", async () => {
|
|
1824
|
+
// Turn 1: seed everInjected with alice.
|
|
1825
|
+
routerState.nextResult = {
|
|
1826
|
+
selectedSlugs: ["alice-vscode"],
|
|
1827
|
+
failureReason: null,
|
|
1828
|
+
};
|
|
1829
|
+
await injectMemoryV2Block({
|
|
1830
|
+
database: db,
|
|
1831
|
+
conversationId: "conv-router-source",
|
|
1832
|
+
currentTurn: 1,
|
|
1833
|
+
userMessage: "Alice",
|
|
1834
|
+
assistantMessage: "",
|
|
1835
|
+
nowText: "Now",
|
|
1836
|
+
messageId: "msg-1",
|
|
1837
|
+
config: makeConfig({ router: { enabled: true } }),
|
|
1838
|
+
});
|
|
1839
|
+
telemetryState.recordCalls.length = 0;
|
|
1840
|
+
|
|
1841
|
+
// Turn 2: router picks bob only. alice is still in everInjected but
|
|
1842
|
+
// not re-picked — her telemetry row must read `source: carry_over`,
|
|
1843
|
+
// not `source: router`.
|
|
1844
|
+
routerState.nextResult = {
|
|
1845
|
+
selectedSlugs: ["bob-coffee"],
|
|
1846
|
+
failureReason: null,
|
|
1847
|
+
};
|
|
1848
|
+
await injectMemoryV2Block({
|
|
1849
|
+
database: db,
|
|
1850
|
+
conversationId: "conv-router-source",
|
|
1851
|
+
currentTurn: 2,
|
|
1852
|
+
userMessage: "Bob",
|
|
1853
|
+
assistantMessage: "",
|
|
1854
|
+
nowText: "Now",
|
|
1855
|
+
messageId: "msg-2",
|
|
1856
|
+
config: makeConfig({ router: { enabled: true } }),
|
|
1857
|
+
});
|
|
1858
|
+
|
|
1859
|
+
expect(telemetryState.recordCalls.length).toBe(1);
|
|
1860
|
+
const row = telemetryState.recordCalls[0] as {
|
|
1861
|
+
mode: string;
|
|
1862
|
+
concepts: Array<{ slug: string; source: string; status: string }>;
|
|
1863
|
+
};
|
|
1864
|
+
expect(row.mode).toBe("router");
|
|
1865
|
+
const aliceRow = row.concepts.find((c) => c.slug === "alice-vscode");
|
|
1866
|
+
const bobRow = row.concepts.find((c) => c.slug === "bob-coffee");
|
|
1867
|
+
expect(aliceRow).toBeDefined();
|
|
1868
|
+
expect(bobRow).toBeDefined();
|
|
1869
|
+
expect(aliceRow!.source).toBe("carry_over");
|
|
1870
|
+
expect(aliceRow!.status).toBe("in_context");
|
|
1871
|
+
expect(bobRow!.source).toBe("router");
|
|
1872
|
+
expect(bobRow!.status).toBe("injected");
|
|
1873
|
+
});
|
|
1874
|
+
|
|
1875
|
+
test("flag-off (default): activation pipeline still runs unchanged", async () => {
|
|
1876
|
+
// Regression check — with the router flag explicitly off (the
|
|
1877
|
+
// production default), `runRouter` must never be called and the
|
|
1878
|
+
// activation pipeline drives the selection just like before.
|
|
1879
|
+
stageTurn([{ slug: "alice-vscode", denseScore: 0.9 }]);
|
|
1880
|
+
routerState.nextResult = {
|
|
1881
|
+
selectedSlugs: ["should-not-be-used"],
|
|
1882
|
+
failureReason: null,
|
|
1883
|
+
};
|
|
1884
|
+
|
|
1885
|
+
const result = await injectMemoryV2Block({
|
|
1886
|
+
database: db,
|
|
1887
|
+
conversationId: "conv-flag-off",
|
|
1888
|
+
currentTurn: 1,
|
|
1889
|
+
userMessage: "Alice's editor",
|
|
1890
|
+
assistantMessage: "",
|
|
1891
|
+
nowText: "Now",
|
|
1892
|
+
messageId: "msg-1",
|
|
1893
|
+
config: makeConfig({ router: { enabled: false } }),
|
|
1894
|
+
});
|
|
1895
|
+
|
|
1896
|
+
// Router was not called.
|
|
1897
|
+
expect(routerState.callCount).toBe(0);
|
|
1898
|
+
// Activation pipeline produced its normal result.
|
|
1899
|
+
expect(result.toInject).toEqual(["alice-vscode"]);
|
|
1900
|
+
expect(result.block).toContain("# memory/concepts/alice-vscode.md");
|
|
1901
|
+
|
|
1902
|
+
// Telemetry row carries the activation mode, not router.
|
|
1903
|
+
expect(telemetryState.recordCalls.length).toBe(1);
|
|
1904
|
+
const row = telemetryState.recordCalls[0] as { mode: string };
|
|
1905
|
+
expect(row.mode).toBe("per-turn");
|
|
1906
|
+
});
|
|
1907
|
+
});
|
|
1290
1908
|
});
|