@vellumai/assistant 0.7.3 → 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/ARCHITECTURE.md +29 -28
- package/Dockerfile +6 -4
- package/README.md +2 -2
- package/__tests__/permissions/gateway-threshold-reader.test.ts +236 -9
- package/bun.lock +3 -0
- 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 +3 -1
- package/node_modules/@vellumai/ipc-server-utils/bun.lock +24 -0
- package/node_modules/@vellumai/ipc-server-utils/package.json +18 -0
- package/node_modules/@vellumai/ipc-server-utils/src/index.ts +6 -0
- package/node_modules/@vellumai/ipc-server-utils/src/socket-watchdog.test.ts +430 -0
- package/node_modules/@vellumai/ipc-server-utils/src/socket-watchdog.ts +221 -0
- package/node_modules/@vellumai/ipc-server-utils/tsconfig.json +20 -0
- package/node_modules/@vellumai/skill-host-contracts/src/client.ts +10 -1
- package/openapi.yaml +4126 -959
- package/package.json +5 -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__/annotate-risk-options.test.ts +291 -0
- package/src/__tests__/anthropic-provider.test.ts +92 -2
- package/src/__tests__/app-control-flow.test.ts +7 -0
- package/src/__tests__/approval-cascade.test.ts +8 -16
- package/src/__tests__/approval-routes-http.test.ts +6 -0
- package/src/__tests__/assistant-events-sse-shed.test.ts +232 -0
- package/src/__tests__/auto-analysis-end-to-end.test.ts +12 -25
- 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-constants.test.ts +10 -1
- package/src/__tests__/call-controller.test.ts +127 -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 +88 -30
- 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 +345 -8
- 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-source.test.ts +3 -26
- 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-abort-tool-results.test.ts +1 -6
- package/src/__tests__/conversation-agent-loop-inference-profile.test.ts +1 -1
- package/src/__tests__/conversation-agent-loop-overflow.test.ts +2 -1
- package/src/__tests__/conversation-agent-loop.test.ts +3 -3
- package/src/__tests__/conversation-confirmation-signals.test.ts +5 -13
- 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 +2 -1
- 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 +22 -7
- package/src/__tests__/conversation-provider-retry-repair.test.ts +1 -6
- package/src/__tests__/conversation-runtime-assembly.test.ts +19 -10
- package/src/__tests__/conversation-slash-commands.test.ts +194 -2
- package/src/__tests__/conversation-slash-unknown.test.ts +1 -6
- package/src/__tests__/conversation-surfaces-action-delivery.test.ts +170 -9
- package/src/__tests__/conversation-surfaces-app-control.test.ts +323 -3
- package/src/__tests__/conversation-surfaces-data-persist.test.ts +73 -1
- package/src/__tests__/conversation-tool-setup-app-refresh.test.ts +59 -0
- package/src/__tests__/conversation-workspace-injection.test.ts +1 -7
- package/src/__tests__/conversation-workspace-tool-tracking.test.ts +1 -7
- 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 +25 -22
- 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 +10 -34
- 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__/injector-chain.test.ts +24 -16
- package/src/__tests__/injector-pkb-v2-silenced.test.ts +10 -7
- 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 +169 -67
- 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-decision-fallback.test.ts +91 -0
- package/src/__tests__/notification-decision-strategy.test.ts +22 -0
- package/src/__tests__/notification-platform-adapter.test.ts +229 -0
- package/src/__tests__/oauth-cli.test.ts +38 -1888
- 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 +164 -2
- 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-prompt-log-hygiene.test.ts +7 -5
- package/src/__tests__/secret-prompter-channel-fallback.test.ts +7 -5
- package/src/__tests__/secret-response-routing.test.ts +7 -5
- package/src/__tests__/secret-routes-managed-proxy.test.ts +12 -4
- package/src/__tests__/server-history-render.test.ts +82 -0
- 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-include-graph.test.ts +31 -0
- package/src/__tests__/skill-load-feature-flag.test.ts +1 -0
- package/src/__tests__/skill-load-tool.test.ts +42 -16
- package/src/__tests__/skills.test.ts +39 -0
- 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__/tool-execution-pipeline.benchmark.test.ts +0 -42
- package/src/__tests__/tool-executor.test.ts +155 -0
- 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__/voice-session-bridge.test.ts +3 -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 +153 -0
- package/src/__tests__/workspace-migration-071-remove-safe-storage-release-note.test.ts +206 -0
- 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-safe-storage-limits-release.test.ts +15 -27
- 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/agent/loop.ts +11 -0
- package/src/approvals/guardian-decision-primitive.ts +0 -13
- package/src/approvals/guardian-request-resolvers.ts +19 -102
- package/src/calls/call-constants.ts +5 -8
- package/src/calls/call-controller.ts +130 -67
- package/src/calls/relay-server.ts +42 -1
- package/src/calls/relay-setup-router.ts +36 -0
- package/src/calls/types.ts +1 -0
- package/src/calls/voice-session-bridge.ts +24 -5
- 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 +163 -517
- package/src/cli/commands/notifications.ts +33 -7
- package/src/cli/commands/oauth/apps.ts +292 -261
- package/src/cli/commands/oauth/connect.ts +182 -345
- 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-builder/SKILL.md +1 -3
- 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 +17 -17
- package/src/config/llm-resolver.ts +16 -1
- package/src/config/loader.ts +148 -33
- 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 +33 -2
- 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 +111 -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 +56 -0
- package/src/daemon/conversation-agent-loop.ts +140 -107
- package/src/daemon/conversation-error.ts +21 -0
- package/src/daemon/conversation-lifecycle.ts +68 -13
- 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 +92 -26
- package/src/daemon/conversation-tool-setup.ts +33 -19
- package/src/daemon/conversation.ts +49 -10
- 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/shared.ts +26 -0
- 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 +97 -36
- package/src/daemon/host-cu-proxy.ts +1 -1
- package/src/daemon/host-file-proxy.ts +1 -1
- package/src/daemon/host-proxy-base.ts +13 -1
- package/src/daemon/host-proxy-preactivation.ts +25 -1
- package/src/daemon/host-transfer-proxy.ts +2 -2
- package/src/daemon/identity-helpers.ts +19 -0
- package/src/daemon/lifecycle.ts +128 -114
- package/src/daemon/meet-host-supervisor.ts +15 -15
- package/src/daemon/memory-v2-startup.ts +62 -14
- 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 +28 -2
- 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/documents/document-store.ts +35 -1
- package/src/export/transcript-formatter.ts +61 -2
- package/src/filing/filing-service.ts +42 -56
- 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 +149 -128
- 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 +148 -42
- 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/ipc/skill-server.ts +99 -42
- 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__/jobs-worker-v2-schedule.test.ts +10 -57
- 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 +40 -31
- package/src/memory/context-search/sources/memory.ts +9 -2
- 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__/conversation-graph-memory-v2-routing.test.ts +104 -61
- package/src/memory/graph/__tests__/handle-remember-v2.test.ts +11 -26
- package/src/memory/graph/__tests__/remember-description.test.ts +55 -0
- package/src/memory/graph/conversation-graph-memory.ts +108 -14
- package/src/memory/graph/extraction.ts +4 -0
- package/src/memory/graph/graph-memory-state-store.ts +16 -3
- package/src/memory/graph/graph-search.test.ts +6 -5
- package/src/memory/graph/graph-search.ts +3 -4
- package/src/memory/graph/retriever.test.ts +12 -7
- package/src/memory/graph/retriever.ts +4 -5
- package/src/memory/graph/tool-handlers.ts +20 -11
- package/src/memory/graph/tools.ts +48 -9
- package/src/memory/indexer.ts +18 -2
- package/src/memory/jobs/__tests__/embed-concept-page.test.ts +120 -6
- package/src/memory/jobs/embed-concept-page.ts +261 -89
- package/src/memory/jobs-store.ts +51 -1
- package/src/memory/jobs-worker.ts +60 -7
- 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/pkb/pkb-search.test.ts +6 -5
- package/src/memory/pkb/pkb-search.ts +4 -5
- package/src/memory/published-pages-store.ts +16 -0
- package/src/memory/qdrant-client.ts +3 -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 +5 -9
- 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 +46 -9
- package/src/memory/v2/__tests__/backfill-jobs.test.ts +38 -21
- package/src/memory/v2/__tests__/consolidation-job.test.ts +140 -163
- 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 +768 -33
- 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 +382 -9
- 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 +163 -8
- package/src/memory/v2/__tests__/skill-store.test.ts +58 -3
- package/src/memory/v2/__tests__/static-context.test.ts +8 -35
- package/src/memory/v2/__tests__/sweep-job.test.ts +114 -33
- 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 +92 -86
- package/src/memory/v2/frontmatter-sweep.ts +91 -0
- package/src/memory/v2/injection.ts +466 -115
- package/src/memory/v2/migration.ts +117 -20
- package/src/memory/v2/page-index.ts +191 -0
- package/src/memory/v2/page-store.ts +42 -0
- package/src/memory/v2/prompts/consolidation.ts +14 -7
- package/src/memory/v2/prompts/router.ts +192 -0
- package/src/memory/v2/qdrant.ts +307 -133
- package/src/memory/v2/reranker.ts +14 -7
- package/src/memory/v2/router.ts +322 -0
- package/src/memory/v2/sim.ts +88 -34
- package/src/memory/v2/skill-store.ts +118 -29
- package/src/memory/v2/static-context.ts +20 -17
- package/src/memory/v2/sweep-job.ts +127 -102
- package/src/memory/v2/types.ts +16 -5
- 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 +61 -12
- package/src/notifications/decision-engine.ts +46 -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/gateway-threshold-reader.ts +116 -8
- package/src/permissions/ipc-risk-types.ts +2 -0
- package/src/permissions/prompter.ts +86 -96
- package/src/permissions/secret-prompter.ts +31 -31
- 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 +20 -5
- 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 +63 -8
- package/src/proactive-artifact/job.ts +20 -2
- package/src/proactive-artifact/message-copy.ts +18 -1
- 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/templates/SOUL.md +13 -28
- 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 +304 -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/channel-approvals.ts +3 -2
- package/src/runtime/guardian-reply-router.ts +0 -10
- 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/pending-interactions.ts +19 -15
- 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 +147 -0
- 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 +7 -21
- 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/consolidation-routes.ts +8 -9
- 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 +373 -82
- 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-bash-routes.ts +2 -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/filing-routes.ts +2 -3
- 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 -7
- 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-item-routes.test.ts +3 -9
- package/src/runtime/routes/memory-item-routes.ts +5 -6
- package/src/runtime/routes/memory-v2-routes.ts +105 -404
- 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/skills/include-graph.ts +35 -13
- 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/document/document-tool.ts +20 -0
- package/src/tools/executor.ts +18 -2
- package/src/tools/memory/register.test.ts +10 -8
- 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 +28 -5
- package/src/tools/skills/load.ts +24 -20
- package/src/tools/subagent/spawn.ts +3 -3
- package/src/tools/terminal/shell.ts +44 -0
- package/src/tools/tool-name-aliases.ts +19 -0
- package/src/tools/types.ts +19 -1
- 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/067-release-notes-safe-storage-limits.ts +4 -62
- package/src/workspace/migrations/069-seed-onboarding-threads.ts +34 -0
- package/src/workspace/migrations/070-memory-v2-summary-schema-rebuild.ts +31 -0
- package/src/workspace/migrations/071-remove-safe-storage-release-note.ts +111 -0
- 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 +28 -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 -492
- 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 -1201
- 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 -477
- 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";
|
|
@@ -114,8 +115,11 @@ class MockQdrantClient {
|
|
|
114
115
|
_name: string,
|
|
115
116
|
params: { using: string; limit: number; filter?: unknown },
|
|
116
117
|
) {
|
|
117
|
-
|
|
118
|
-
|
|
118
|
+
// The four-channel hybrid query fires body-dense, body-sparse,
|
|
119
|
+
// summary-dense, summary-sparse in order; both dense channels share
|
|
120
|
+
// the dense queue and both sparse channels share the sparse queue.
|
|
121
|
+
const channel = params.using.endsWith("sparse") ? "sparse" : "dense";
|
|
122
|
+
return state.queryResponses[channel].shift() ?? { points: [] };
|
|
119
123
|
}
|
|
120
124
|
}
|
|
121
125
|
|
|
@@ -150,6 +154,11 @@ mock.module("../skill-store.js", () => ({
|
|
|
150
154
|
isSkillSlug: (slug: string) => slug.startsWith("skills/"),
|
|
151
155
|
SKILL_SLUG_PREFIX: "skills/",
|
|
152
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()),
|
|
153
162
|
}));
|
|
154
163
|
|
|
155
164
|
// ---------------------------------------------------------------------------
|
|
@@ -176,6 +185,93 @@ mock.module("../../memory-v2-activation-log-store.js", () => ({
|
|
|
176
185
|
},
|
|
177
186
|
}));
|
|
178
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
|
+
|
|
179
275
|
// ---------------------------------------------------------------------------
|
|
180
276
|
// Workspace + DB setup
|
|
181
277
|
// ---------------------------------------------------------------------------
|
|
@@ -229,6 +325,18 @@ ref_files:
|
|
|
229
325
|
---
|
|
230
326
|
Demo body content.`,
|
|
231
327
|
);
|
|
328
|
+
// A page WITH a `summary` in its frontmatter — exercises the summary-only
|
|
329
|
+
// injection path. Body is intentionally longer than the summary so tests
|
|
330
|
+
// can assert that the body is *not* injected when the summary is present.
|
|
331
|
+
writeFileSync(
|
|
332
|
+
join(tmpWorkspace, "memory", "concepts", "summarized-page.md"),
|
|
333
|
+
`---
|
|
334
|
+
edges: []
|
|
335
|
+
ref_files: []
|
|
336
|
+
summary: A short prose description of the summarized page that retrieval injects in place of the full body.
|
|
337
|
+
---
|
|
338
|
+
Long-form body content that should NOT appear in the injection block when the page has a summary in frontmatter — the agent reads the file on demand instead.`,
|
|
339
|
+
);
|
|
232
340
|
});
|
|
233
341
|
|
|
234
342
|
afterAll(() => {
|
|
@@ -284,8 +392,10 @@ function makeConfig(
|
|
|
284
392
|
epsilon: number;
|
|
285
393
|
dense_weight: number;
|
|
286
394
|
sparse_weight: number;
|
|
395
|
+
router: { enabled: boolean; max_page_ids?: number };
|
|
287
396
|
}> = {},
|
|
288
397
|
): AssistantConfig {
|
|
398
|
+
const { router, ...rest } = overrides;
|
|
289
399
|
return {
|
|
290
400
|
memory: {
|
|
291
401
|
v2: {
|
|
@@ -299,7 +409,8 @@ function makeConfig(
|
|
|
299
409
|
epsilon: 0.01,
|
|
300
410
|
dense_weight: 1.0,
|
|
301
411
|
sparse_weight: 0.0,
|
|
302
|
-
...
|
|
412
|
+
router: { enabled: false, max_page_ids: 25, ...(router ?? {}) },
|
|
413
|
+
...rest,
|
|
303
414
|
},
|
|
304
415
|
},
|
|
305
416
|
} as unknown as AssistantConfig;
|
|
@@ -308,14 +419,26 @@ function makeConfig(
|
|
|
308
419
|
/**
|
|
309
420
|
* Stage one set of dense/sparse hits, used uniformly by every `simBatch`
|
|
310
421
|
* channel call (user/assistant/now) AND by the un-restricted ANN candidate
|
|
311
|
-
* query. The candidate query runs first, then three simBatch calls
|
|
312
|
-
*
|
|
422
|
+
* query. The candidate query runs first, then three simBatch calls — that's
|
|
423
|
+
* `channels` (= 4) logical hybrid queries. Each logical hybrid query now
|
|
424
|
+
* fires a four-channel fan-out (body dense, body sparse, summary dense,
|
|
425
|
+
* summary sparse), so we push 2 dense + 2 sparse responses per logical
|
|
426
|
+
* call to match the post-summary-vector wire pattern.
|
|
313
427
|
*
|
|
314
428
|
* Each entry is mapped to a hit per channel; pass `denseScore`/`sparseScore`
|
|
315
|
-
* undefined to omit a slug from that channel.
|
|
429
|
+
* undefined to omit a slug from that channel. `summaryDenseScore` /
|
|
430
|
+
* `summarySparseScore` route to the summary-side channels — tests that
|
|
431
|
+
* don't care about summary scoring leave them undefined and the summary
|
|
432
|
+
* channel falls back to body-only behavior.
|
|
316
433
|
*/
|
|
317
434
|
function stageTurn(
|
|
318
|
-
hits: Array<{
|
|
435
|
+
hits: Array<{
|
|
436
|
+
slug: string;
|
|
437
|
+
denseScore?: number;
|
|
438
|
+
sparseScore?: number;
|
|
439
|
+
summaryDenseScore?: number;
|
|
440
|
+
summarySparseScore?: number;
|
|
441
|
+
}>,
|
|
319
442
|
channels = 4,
|
|
320
443
|
): void {
|
|
321
444
|
// Clear any leftovers from a prior turn before staging this one so unused
|
|
@@ -336,6 +459,22 @@ function stageTurn(
|
|
|
336
459
|
.filter((h) => h.sparseScore !== undefined)
|
|
337
460
|
.map((h) => ({ score: h.sparseScore, payload: { slug: h.slug } })),
|
|
338
461
|
});
|
|
462
|
+
state.queryResponses.dense.push({
|
|
463
|
+
points: hits
|
|
464
|
+
.filter((h) => h.summaryDenseScore !== undefined)
|
|
465
|
+
.map((h) => ({
|
|
466
|
+
score: h.summaryDenseScore,
|
|
467
|
+
payload: { slug: h.slug },
|
|
468
|
+
})),
|
|
469
|
+
});
|
|
470
|
+
state.queryResponses.sparse.push({
|
|
471
|
+
points: hits
|
|
472
|
+
.filter((h) => h.summarySparseScore !== undefined)
|
|
473
|
+
.map((h) => ({
|
|
474
|
+
score: h.summarySparseScore,
|
|
475
|
+
payload: { slug: h.slug },
|
|
476
|
+
})),
|
|
477
|
+
});
|
|
339
478
|
}
|
|
340
479
|
}
|
|
341
480
|
|
|
@@ -347,6 +486,10 @@ function resetState(): void {
|
|
|
347
486
|
skillState.entries.clear();
|
|
348
487
|
telemetryState.recordCalls.length = 0;
|
|
349
488
|
telemetryState.recordShouldThrow = false;
|
|
489
|
+
pageStoreState.failingSlugs.clear();
|
|
490
|
+
activationStoreState.saveShouldThrow = false;
|
|
491
|
+
routerState.nextResult = null;
|
|
492
|
+
routerState.callCount = 0;
|
|
350
493
|
// The qdrant module caches its client; the cached client may be a
|
|
351
494
|
// MockQdrantClient instance from a sibling test file. Reset to force a
|
|
352
495
|
// fresh `new QdrantClient()` against this file's mock.
|
|
@@ -395,7 +538,7 @@ describe("injectMemoryV2Block", () => {
|
|
|
395
538
|
expect(result.block).not.toContain("<memory>");
|
|
396
539
|
expect(result.block).not.toContain("</memory>");
|
|
397
540
|
expect(result.block).not.toContain("## What I Remember Right Now");
|
|
398
|
-
expect(result.block).toContain("
|
|
541
|
+
expect(result.block).toContain("# memory/concepts/alice-vscode.md");
|
|
399
542
|
expect(result.block).toContain("VS Code");
|
|
400
543
|
|
|
401
544
|
// State persisted: alice's activation is above epsilon and recorded;
|
|
@@ -484,10 +627,10 @@ describe("injectMemoryV2Block", () => {
|
|
|
484
627
|
});
|
|
485
628
|
|
|
486
629
|
expect(result.toInject).toEqual(["carol-jazz"]);
|
|
487
|
-
expect(result.block).toContain("
|
|
630
|
+
expect(result.block).toContain("# memory/concepts/carol-jazz.md");
|
|
488
631
|
// The block only shows the new slug — alice's attachment lives on the
|
|
489
632
|
// previous turn's user message.
|
|
490
|
-
expect(result.block).not.toContain("
|
|
633
|
+
expect(result.block).not.toContain("# memory/concepts/alice-vscode.md");
|
|
491
634
|
|
|
492
635
|
const persisted = await hydrate(db, "conv-1");
|
|
493
636
|
expect(persisted!.everInjected).toEqual([
|
|
@@ -532,7 +675,7 @@ describe("injectMemoryV2Block", () => {
|
|
|
532
675
|
});
|
|
533
676
|
|
|
534
677
|
expect(result.toInject).toEqual(["alice-vscode"]);
|
|
535
|
-
expect(result.block).toContain("
|
|
678
|
+
expect(result.block).toContain("# memory/concepts/alice-vscode.md");
|
|
536
679
|
|
|
537
680
|
const persisted = await hydrate(db, "conv-1");
|
|
538
681
|
expect(persisted!.everInjected).toEqual([
|
|
@@ -540,6 +683,74 @@ describe("injectMemoryV2Block", () => {
|
|
|
540
683
|
]);
|
|
541
684
|
});
|
|
542
685
|
|
|
686
|
+
test("page with summary renders as path + summary, no body, with the CRITICAL header", async () => {
|
|
687
|
+
// Pages whose frontmatter carries a `summary` should inject only the
|
|
688
|
+
// summary text behind the path header — the agent reads the full file
|
|
689
|
+
// on demand. The leading `**CRITICAL:**` line tells the agent how to
|
|
690
|
+
// read the block.
|
|
691
|
+
stageTurn([{ slug: "summarized-page", denseScore: 0.9 }]);
|
|
692
|
+
|
|
693
|
+
const result = await injectMemoryV2Block({
|
|
694
|
+
database: db,
|
|
695
|
+
conversationId: "conv-1",
|
|
696
|
+
currentTurn: 1,
|
|
697
|
+
userMessage: "tell me about the summarized page",
|
|
698
|
+
assistantMessage: "",
|
|
699
|
+
nowText: "Now",
|
|
700
|
+
messageId: "msg-1",
|
|
701
|
+
config: makeConfig(),
|
|
702
|
+
});
|
|
703
|
+
|
|
704
|
+
expect(result.block).not.toBeNull();
|
|
705
|
+
expect(result.block).toContain(
|
|
706
|
+
"**CRITICAL:** These are page summaries. Read the page file if it looks relevant.",
|
|
707
|
+
);
|
|
708
|
+
expect(result.block).toContain(
|
|
709
|
+
"# memory/concepts/summarized-page.md\nA short prose description",
|
|
710
|
+
);
|
|
711
|
+
// Body is NOT in the block — the agent must follow up with a read tool.
|
|
712
|
+
expect(result.block).not.toContain("Long-form body content");
|
|
713
|
+
// Frontmatter is also omitted; the path header carries the identifying
|
|
714
|
+
// information by itself, and edges flow through the activation graph.
|
|
715
|
+
expect(result.block).not.toContain("---\nedges:");
|
|
716
|
+
});
|
|
717
|
+
|
|
718
|
+
test("mixed batch — summary page renders short, fallback page renders full", async () => {
|
|
719
|
+
// Both pages rank into top-K. summarized-page has a summary → short
|
|
720
|
+
// form. frontmatter-demo has no summary → full-page fallback. The
|
|
721
|
+
// single CRITICAL header sits at the top regardless.
|
|
722
|
+
stageTurn([
|
|
723
|
+
{ slug: "summarized-page", denseScore: 0.95 },
|
|
724
|
+
{ slug: "frontmatter-demo", denseScore: 0.85 },
|
|
725
|
+
]);
|
|
726
|
+
|
|
727
|
+
const result = await injectMemoryV2Block({
|
|
728
|
+
database: db,
|
|
729
|
+
conversationId: "conv-1",
|
|
730
|
+
currentTurn: 1,
|
|
731
|
+
userMessage: "show me everything",
|
|
732
|
+
assistantMessage: "",
|
|
733
|
+
nowText: "Now",
|
|
734
|
+
messageId: "msg-1",
|
|
735
|
+
config: makeConfig(),
|
|
736
|
+
});
|
|
737
|
+
|
|
738
|
+
expect(result.block).not.toBeNull();
|
|
739
|
+
// CRITICAL header appears exactly once.
|
|
740
|
+
const criticalCount = (
|
|
741
|
+
result.block!.match(/\*\*CRITICAL:\*\* These are page summaries\./g) ?? []
|
|
742
|
+
).length;
|
|
743
|
+
expect(criticalCount).toBe(1);
|
|
744
|
+
// summarized-page → short form (path + summary, no body, no frontmatter).
|
|
745
|
+
expect(result.block).toContain("# memory/concepts/summarized-page.md\nA");
|
|
746
|
+
expect(result.block).not.toContain("Long-form body content");
|
|
747
|
+
// frontmatter-demo → full-page fallback (path + frontmatter + body).
|
|
748
|
+
expect(result.block).toContain(
|
|
749
|
+
"# memory/concepts/frontmatter-demo.md\n---\n",
|
|
750
|
+
);
|
|
751
|
+
expect(result.block).toContain("Demo body content.");
|
|
752
|
+
});
|
|
753
|
+
|
|
543
754
|
test("includes the page frontmatter (edges, ref_files) in each rendered section", async () => {
|
|
544
755
|
// The frontmatter (`edges`, `ref_files`) lives on disk above the page
|
|
545
756
|
// body and is part of the page's content. Injection must reproduce both
|
|
@@ -560,8 +771,12 @@ describe("injectMemoryV2Block", () => {
|
|
|
560
771
|
});
|
|
561
772
|
|
|
562
773
|
expect(result.block).not.toBeNull();
|
|
563
|
-
//
|
|
564
|
-
|
|
774
|
+
// Path header is immediately followed by the frontmatter open delimiter.
|
|
775
|
+
// The fallback path renders the full page (frontmatter + body) when the
|
|
776
|
+
// page has no `summary` field — `frontmatter-demo` predates the field.
|
|
777
|
+
expect(result.block).toContain(
|
|
778
|
+
"# memory/concepts/frontmatter-demo.md\n---\n",
|
|
779
|
+
);
|
|
565
780
|
// Both fields render in YAML block style with their populated values.
|
|
566
781
|
expect(result.block).toContain("edges:\n - alice-vscode");
|
|
567
782
|
expect(result.block).toContain("ref_files:\n - images/demo.jpg");
|
|
@@ -589,19 +804,21 @@ describe("injectMemoryV2Block", () => {
|
|
|
589
804
|
});
|
|
590
805
|
|
|
591
806
|
expect(result.toInject).toEqual(["carol-jazz", "alice-vscode"]);
|
|
592
|
-
const carolIdx = result.block!.indexOf("
|
|
593
|
-
const aliceIdx = result.block!.indexOf("
|
|
807
|
+
const carolIdx = result.block!.indexOf("# memory/concepts/carol-jazz.md");
|
|
808
|
+
const aliceIdx = result.block!.indexOf("# memory/concepts/alice-vscode.md");
|
|
594
809
|
expect(carolIdx).toBeGreaterThan(-1);
|
|
595
810
|
expect(aliceIdx).toBeGreaterThan(-1);
|
|
596
811
|
expect(carolIdx).toBeLessThan(aliceIdx);
|
|
597
812
|
});
|
|
598
813
|
|
|
599
814
|
test("persists sparse state — only slugs above epsilon survive", async () => {
|
|
600
|
-
// Carol scores high; alice
|
|
601
|
-
//
|
|
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.
|
|
602
819
|
stageTurn([
|
|
603
820
|
{ slug: "carol-jazz", denseScore: 1.0 },
|
|
604
|
-
{ slug: "alice-vscode", denseScore:
|
|
821
|
+
{ slug: "alice-vscode", denseScore: -1.0 },
|
|
605
822
|
]);
|
|
606
823
|
await injectMemoryV2Block({
|
|
607
824
|
database: db,
|
|
@@ -692,7 +909,7 @@ describe("injectMemoryV2Block", () => {
|
|
|
692
909
|
expect(result.block).not.toContain("<memory>");
|
|
693
910
|
expect(result.block).not.toContain("</memory>");
|
|
694
911
|
expect(result.block).not.toContain("## What I Remember Right Now");
|
|
695
|
-
expect(result.block).not.toContain("
|
|
912
|
+
expect(result.block).not.toContain("# memory/concepts/alice-vscode.md");
|
|
696
913
|
expect(result.block).toContain("### Skills You Can Use");
|
|
697
914
|
expect(result.block).toContain(
|
|
698
915
|
'- The "Example Skill A" skill (example-skill-a) is available. Helps with examples. → use skill_load to activate',
|
|
@@ -731,11 +948,13 @@ describe("injectMemoryV2Block", () => {
|
|
|
731
948
|
);
|
|
732
949
|
expect(result.block).not.toBeNull();
|
|
733
950
|
|
|
734
|
-
const
|
|
951
|
+
const aliceHeaderIdx = result.block!.indexOf(
|
|
952
|
+
"# memory/concepts/alice-vscode.md",
|
|
953
|
+
);
|
|
735
954
|
const skillsIdx = result.block!.indexOf("### Skills You Can Use");
|
|
736
|
-
expect(
|
|
955
|
+
expect(aliceHeaderIdx).toBeGreaterThan(-1);
|
|
737
956
|
expect(skillsIdx).toBeGreaterThan(-1);
|
|
738
|
-
expect(
|
|
957
|
+
expect(aliceHeaderIdx).toBeLessThan(skillsIdx);
|
|
739
958
|
|
|
740
959
|
expect(result.block).toContain(
|
|
741
960
|
'- The "Example Skill A" skill (example-skill-a) is available. Helps with examples. → use skill_load to activate',
|
|
@@ -790,9 +1009,10 @@ describe("injectMemoryV2Block", () => {
|
|
|
790
1009
|
|
|
791
1010
|
test("skill slugs whose entry is missing from the cache are dropped silently", async () => {
|
|
792
1011
|
// The skill ranks into top-K but the in-process cache no longer knows
|
|
793
|
-
// its content (skill uninstalled mid-run
|
|
794
|
-
//
|
|
795
|
-
//
|
|
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
|
|
796
1016
|
// skill entries.
|
|
797
1017
|
stageTurn([{ slug: "skills/missing-skill", denseScore: 0.9 }]);
|
|
798
1018
|
// No `stageSkills` call — cache stays empty.
|
|
@@ -808,10 +1028,16 @@ describe("injectMemoryV2Block", () => {
|
|
|
808
1028
|
config: makeConfig(),
|
|
809
1029
|
});
|
|
810
1030
|
|
|
811
|
-
//
|
|
812
|
-
//
|
|
813
|
-
|
|
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([]);
|
|
814
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([]);
|
|
815
1041
|
});
|
|
816
1042
|
|
|
817
1043
|
test("returns null when both concept pages and skills are empty", async () => {
|
|
@@ -864,7 +1090,7 @@ describe("injectMemoryV2Block", () => {
|
|
|
864
1090
|
});
|
|
865
1091
|
|
|
866
1092
|
expect(result.block).not.toBeNull();
|
|
867
|
-
expect(result.block).toContain("
|
|
1093
|
+
expect(result.block).toContain("# memory/concepts/alice-vscode.md");
|
|
868
1094
|
// No newly-injected slug — alice was already in everInjected.
|
|
869
1095
|
expect(result.toInject).toEqual([]);
|
|
870
1096
|
|
|
@@ -900,9 +1126,9 @@ describe("injectMemoryV2Block", () => {
|
|
|
900
1126
|
});
|
|
901
1127
|
|
|
902
1128
|
expect(result.block).not.toBeNull();
|
|
903
|
-
expect(result.block).toContain("
|
|
904
|
-
expect(result.block).toContain("
|
|
905
|
-
expect(result.block).toContain("
|
|
1129
|
+
expect(result.block).toContain("# memory/concepts/alice-vscode.md");
|
|
1130
|
+
expect(result.block).toContain("# memory/concepts/bob-coffee.md");
|
|
1131
|
+
expect(result.block).toContain("# memory/concepts/carol-jazz.md");
|
|
906
1132
|
// The seeded directed edges (alice→bob, bob→alice, frontmatter-demo→alice)
|
|
907
1133
|
// mean alice has two incoming predecessors and bob has one, so directed
|
|
908
1134
|
// spread normalizes alice's activation more aggressively than bob's. The
|
|
@@ -1163,11 +1389,520 @@ describe("injectMemoryV2Block", () => {
|
|
|
1163
1389
|
expect(telemetryState.recordCalls.length).toBe(0);
|
|
1164
1390
|
expect(result.toInject).toEqual(["alice-vscode"]);
|
|
1165
1391
|
expect(result.block).not.toBeNull();
|
|
1166
|
-
expect(result.block).toContain("
|
|
1392
|
+
expect(result.block).toContain("# memory/concepts/alice-vscode.md");
|
|
1167
1393
|
|
|
1168
1394
|
const persisted = await hydrate(db, "conv-1");
|
|
1169
1395
|
expect(persisted!.everInjected).toEqual([
|
|
1170
1396
|
{ slug: "alice-vscode", turn: 1 },
|
|
1171
1397
|
]);
|
|
1172
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
|
+
});
|
|
1173
1908
|
});
|