@vellumai/assistant 0.7.2 → 0.8.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/ARCHITECTURE.md +45 -29
- package/Dockerfile +1 -0
- package/__tests__/permissions/gateway-threshold-reader.test.ts +236 -9
- package/bun.lock +3 -0
- package/docs/architecture/memory.md +5 -2
- package/knip.json +1 -0
- package/node_modules/@vellumai/gateway-client/src/ipc-client.ts +13 -4
- 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/assistant-event.ts +0 -9
- package/node_modules/@vellumai/slack-text/src/index.test.ts +18 -35
- package/node_modules/@vellumai/slack-text/src/index.ts +2 -48
- package/openapi.yaml +470 -25
- package/package.json +3 -1
- package/src/__tests__/annotate-risk-options.test.ts +291 -0
- package/src/__tests__/app-control-flow.test.ts +21 -11
- package/src/__tests__/approval-cascade.test.ts +8 -16
- package/src/__tests__/approval-routes-http.test.ts +6 -0
- package/src/__tests__/assistant-event-hub.test.ts +48 -0
- package/src/__tests__/assistant-event.test.ts +0 -10
- package/src/__tests__/assistant-events-sse-hardening.test.ts +2 -7
- package/src/__tests__/assistant-feature-flags-integration.test.ts +18 -0
- package/src/__tests__/auto-analysis-end-to-end.test.ts +48 -0
- package/src/__tests__/background-workers-disk-pressure.test.ts +268 -0
- package/src/__tests__/call-constants.test.ts +10 -1
- package/src/__tests__/call-controller.test.ts +127 -0
- package/src/__tests__/call-conversation-messages.test.ts +8 -2
- package/src/__tests__/channel-inbound-disk-pressure.test.ts +537 -0
- package/src/__tests__/channel-readiness-service.test.ts +4 -2
- package/src/__tests__/cli-memory-v2-reembed-skills.test.ts +58 -28
- package/src/__tests__/config-loader-backfill.test.ts +379 -0
- package/src/__tests__/config-loader-platform-defaults.test.ts +284 -1
- package/src/__tests__/config-schema.test.ts +1 -0
- package/src/__tests__/config-watcher-cleanup-throttle.test.ts +18 -9
- package/src/__tests__/config-watcher.test.ts +140 -69
- package/src/__tests__/context-search-agent-runner.test.ts +61 -3
- package/src/__tests__/context-search-conversations-source.test.ts +0 -24
- package/src/__tests__/context-search-fanout.test.ts +0 -1
- package/src/__tests__/context-search-memory-source.test.ts +6 -33
- package/src/__tests__/context-search-memory-v2-source.test.ts +0 -2
- package/src/__tests__/context-search-pkb-source.test.ts +12 -7
- package/src/__tests__/context-search-workspace-source.test.ts +0 -1
- package/src/__tests__/conversation-abort-tool-results.test.ts +1 -0
- package/src/__tests__/conversation-agent-loop-disk-pressure.test.ts +223 -0
- package/src/__tests__/conversation-agent-loop-inference-profile.test.ts +1 -1
- package/src/__tests__/conversation-agent-loop-overflow.test.ts +1 -1
- package/src/__tests__/conversation-agent-loop.test.ts +457 -8
- package/src/__tests__/conversation-confirmation-signals.test.ts +5 -13
- package/src/__tests__/conversation-error.test.ts +150 -3
- package/src/__tests__/conversation-init.benchmark.test.ts +1 -1
- package/src/__tests__/conversation-process-callsite.test.ts +38 -0
- package/src/__tests__/conversation-provider-retry-repair.test.ts +1 -0
- package/src/__tests__/conversation-runtime-assembly.test.ts +74 -0
- package/src/__tests__/conversation-slash-unknown.test.ts +1 -0
- package/src/__tests__/conversation-speed-override.test.ts +0 -3
- package/src/__tests__/conversation-store.test.ts +0 -18
- package/src/__tests__/conversation-surfaces-action-delivery.test.ts +170 -9
- package/src/__tests__/conversation-surfaces-app-control.test.ts +15 -4
- package/src/__tests__/conversation-surfaces-data-persist.test.ts +476 -0
- package/src/__tests__/conversation-tool-setup-app-refresh.test.ts +61 -5
- package/src/__tests__/conversation-workspace-injection.test.ts +1 -1
- package/src/__tests__/conversation-workspace-tool-tracking.test.ts +1 -1
- package/src/__tests__/credentials-cli.test.ts +7 -0
- package/src/__tests__/cu-unified-flow.test.ts +176 -10
- package/src/__tests__/date-context.test.ts +164 -2
- package/src/__tests__/disk-pressure-guard.test.ts +262 -0
- package/src/__tests__/disk-pressure-lifecycle.test.ts +168 -0
- package/src/__tests__/disk-pressure-policy.test.ts +241 -0
- package/src/__tests__/disk-pressure-routes.test.ts +379 -0
- package/src/__tests__/disk-pressure-tools.test.ts +277 -0
- package/src/__tests__/disk-usage.test.ts +150 -0
- package/src/__tests__/events-client-registration.test.ts +52 -0
- package/src/__tests__/events-dev-bypass-actor.test.ts +162 -0
- package/src/__tests__/file-write-tool.test.ts +4 -10
- package/src/__tests__/filing-service.test.ts +2 -20
- package/src/__tests__/handlers-skills-memory-v2-reseed.test.ts +10 -26
- package/src/__tests__/heartbeat-disk-pressure.test.ts +183 -0
- package/src/__tests__/heartbeat-service.test.ts +260 -11
- package/src/__tests__/host-app-control-proxy.test.ts +195 -25
- package/src/__tests__/host-bash-proxy.test.ts +227 -34
- package/src/__tests__/host-bash-routes.test.ts +178 -13
- package/src/__tests__/host-cu-proxy.test.ts +210 -3
- package/src/__tests__/host-cu-routes-targeted.test.ts +141 -12
- package/src/__tests__/host-file-proxy-targeted.test.ts +48 -9
- package/src/__tests__/host-file-proxy.test.ts +268 -6
- package/src/__tests__/host-file-routes-targeted.test.ts +175 -17
- package/src/__tests__/host-transfer-proxy-targeted.test.ts +408 -59
- package/src/__tests__/host-transfer-routes-targeted.test.ts +232 -17
- package/src/__tests__/http-user-message-parity.test.ts +107 -1
- package/src/__tests__/injector-chain.test.ts +36 -16
- package/src/__tests__/injector-disk-pressure.test.ts +224 -0
- package/src/__tests__/injector-pkb-v2-silenced.test.ts +10 -7
- package/src/__tests__/lifecycle-memory-v2-seed.test.ts +154 -67
- package/src/__tests__/managed-profile-guard.test.ts +18 -0
- package/src/__tests__/mcp-abort-signal.test.ts +130 -0
- package/src/__tests__/memory-admin-recall.test.ts +3 -11
- package/src/__tests__/memory-retrieval-pipeline.test.ts +22 -1
- package/src/__tests__/normalize-onboarding.test.ts +180 -0
- package/src/__tests__/notification-decision-fallback.test.ts +91 -0
- package/src/__tests__/notification-decision-strategy.test.ts +22 -0
- package/src/__tests__/oauth-cli.test.ts +121 -0
- package/src/__tests__/oauth-connect-routes.test.ts +316 -0
- package/src/__tests__/oauth-provider-seed-logos.test.ts +24 -2
- package/src/__tests__/onboarding-persona-write.test.ts +308 -0
- package/src/__tests__/openai-provider.test.ts +45 -8
- package/src/__tests__/persist-onboarding-artifacts.test.ts +44 -64
- package/src/__tests__/platform-callback-registration.test.ts +21 -4
- package/src/__tests__/platform.test.ts +2 -1
- package/src/__tests__/playbook-execution.test.ts +0 -43
- package/src/__tests__/plugin-tool-contribution.test.ts +47 -0
- package/src/__tests__/prechat-onboarding-contract.test.ts +214 -27
- package/src/__tests__/provider-tool-name.test.ts +23 -0
- package/src/__tests__/relay-server.test.ts +60 -5
- package/src/__tests__/runtime-events-sse.test.ts +4 -8
- package/src/__tests__/scheduler-disk-pressure.test.ts +148 -0
- package/src/__tests__/secret-ingress-http.test.ts +0 -1
- 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__/server-history-render.test.ts +82 -0
- package/src/__tests__/skill-include-graph.test.ts +31 -0
- package/src/__tests__/skill-load-tool.test.ts +44 -16
- package/src/__tests__/skills.test.ts +39 -0
- package/src/__tests__/suggestion-routes.test.ts +46 -0
- package/src/__tests__/tool-execution-pipeline.benchmark.test.ts +0 -42
- package/src/__tests__/tool-executor.test.ts +155 -0
- package/src/__tests__/twilio-validation.test.ts +2 -2
- package/src/__tests__/voice-session-bridge.test.ts +3 -0
- package/src/__tests__/workspace-migration-065-bump-stale-heartbeat-interval.test.ts +122 -0
- package/src/__tests__/workspace-migration-066-seed-heartbeat-callsite-cost-default.test.ts +285 -0
- package/src/__tests__/workspace-migration-068-release-notes-local-timezone.test.ts +90 -0
- package/src/__tests__/workspace-migration-069-seed-onboarding-threads.test.ts +120 -0
- package/src/__tests__/workspace-migration-071-remove-safe-storage-release-note.test.ts +206 -0
- package/src/__tests__/workspace-migration-safe-storage-limits-release.test.ts +78 -0
- package/src/agent/loop.ts +11 -0
- package/src/approvals/guardian-request-resolvers.ts +3 -32
- package/src/backup/snapshot-lock.ts +2 -27
- package/src/bundler/compiler-tools.ts +3 -2
- package/src/calls/call-constants.ts +5 -8
- package/src/calls/call-controller.ts +130 -67
- package/src/calls/call-conversation-messages.ts +46 -10
- package/src/calls/relay-server.ts +7 -1
- package/src/calls/voice-session-bridge.ts +1 -1
- package/src/cli/commands/__tests__/webhooks.test.ts +0 -4
- package/src/cli/commands/bash.ts +35 -108
- package/src/cli/commands/contacts.ts +64 -25
- package/src/cli/commands/credentials.ts +56 -0
- package/src/cli/commands/memory-v2.ts +11 -10
- package/src/cli/commands/oauth/__tests__/connect.test.ts +401 -219
- package/src/cli/commands/oauth/connect.ts +124 -40
- package/src/cli/commands/platform/__tests__/callback-routes-list.test.ts +0 -3
- package/src/cli/commands/platform/__tests__/connect.test.ts +7 -1
- package/src/cli/commands/platform/__tests__/disconnect.test.ts +7 -1
- package/src/cli/commands/platform/__tests__/status.test.ts +103 -6
- package/src/cli/commands/platform/index.ts +16 -7
- package/src/cli/commands/status.ts +57 -0
- package/src/cli/program.ts +4 -2
- package/src/config/assistant-feature-flags.ts +13 -3
- package/src/config/bundled-skills/app-builder/SKILL.md +1 -3
- package/src/config/bundled-skills/messaging/tools/messaging-analyze-style.ts +4 -3
- package/src/config/bundled-skills/phone-calls/references/TROUBLESHOOTING.md +13 -7
- package/src/config/bundled-skills/playbooks/tools/playbook-create.ts +2 -2
- package/src/config/bundled-skills/playbooks/tools/playbook-delete.ts +2 -2
- package/src/config/bundled-skills/playbooks/tools/playbook-list.ts +2 -2
- package/src/config/bundled-skills/playbooks/tools/playbook-update.ts +2 -2
- package/src/config/env.ts +0 -8
- package/src/config/feature-flag-registry.json +13 -5
- package/src/config/loader.ts +199 -27
- package/src/config/schemas/__tests__/memory-v2.test.ts +10 -5
- package/src/config/schemas/call-site-catalog.ts +14 -0
- package/src/config/schemas/channels.ts +0 -5
- package/src/config/schemas/heartbeat.ts +1 -1
- package/src/config/schemas/llm.ts +2 -0
- package/src/config/schemas/memory-lifecycle.ts +13 -0
- package/src/config/schemas/memory-v2.ts +76 -12
- package/src/config/schemas/platform.ts +43 -3
- package/src/config/schemas/services.ts +28 -0
- package/src/config/seed-inference-profiles.ts +230 -33
- package/src/contacts/contact-store.ts +0 -25
- package/src/daemon/__tests__/conversation-lifecycle-auto-analyze.test.ts +32 -0
- package/src/daemon/__tests__/conversation-tool-setup.test.ts +86 -25
- package/src/daemon/assistant-attachments.ts +4 -4
- package/src/daemon/config-watcher.ts +85 -57
- package/src/daemon/conversation-agent-loop-handlers.ts +38 -0
- package/src/daemon/conversation-agent-loop.ts +183 -43
- package/src/daemon/conversation-error.ts +87 -15
- package/src/daemon/conversation-lifecycle.ts +22 -10
- package/src/daemon/conversation-process.ts +8 -0
- package/src/daemon/conversation-runtime-assembly.ts +26 -0
- package/src/daemon/conversation-store.ts +2 -2
- package/src/daemon/conversation-surfaces.ts +211 -29
- package/src/daemon/conversation-tool-setup.ts +66 -19
- package/src/daemon/conversation.ts +18 -23
- package/src/daemon/date-context.ts +71 -22
- package/src/daemon/disk-pressure-background-gate.ts +73 -0
- package/src/daemon/disk-pressure-guard.ts +343 -0
- package/src/daemon/disk-pressure-policy.ts +163 -0
- package/src/daemon/handlers/shared.ts +26 -1
- package/src/daemon/handlers/skills.ts +3 -4
- package/src/daemon/host-app-control-proxy.ts +137 -41
- package/src/daemon/host-bash-proxy.ts +47 -22
- package/src/daemon/host-browser-proxy.ts +1 -1
- package/src/daemon/host-cu-proxy.ts +50 -4
- package/src/daemon/host-file-proxy.ts +44 -8
- package/src/daemon/host-transfer-proxy.ts +97 -6
- package/src/daemon/lifecycle.ts +167 -101
- package/src/daemon/meet-host-supervisor.ts +4 -4
- package/src/daemon/meet-manifest-loader.ts +0 -1
- package/src/daemon/memory-v2-startup.ts +66 -15
- package/src/daemon/message-protocol.ts +3 -0
- package/src/daemon/message-types/conversations.ts +4 -0
- package/src/daemon/message-types/disk-pressure.ts +9 -0
- package/src/daemon/message-types/messages.ts +22 -1
- package/src/daemon/profiler-run-store.ts +5 -5
- package/src/daemon/tool-setup-types.ts +2 -2
- package/src/documents/document-store.ts +119 -0
- package/src/filing/filing-service.ts +29 -5
- package/src/heartbeat/__tests__/heartbeat-feed-event.test.ts +9 -16
- package/src/heartbeat/__tests__/heartbeat-run-store.test.ts +36 -0
- package/src/heartbeat/heartbeat-run-store.ts +13 -0
- package/src/heartbeat/heartbeat-service.ts +205 -31
- package/src/home/feed-scheduler.ts +18 -0
- package/src/inbound/platform-callback-registration.ts +8 -15
- package/src/ipc/__tests__/clients-list-ipc.test.ts +169 -0
- package/src/ipc/assistant-server.ts +149 -38
- package/src/ipc/gateway-client.ts +37 -3
- package/src/ipc/skill-server.ts +99 -42
- package/src/live-voice/live-voice-archive.ts +4 -4
- package/src/live-voice/protocol.ts +5 -7
- package/src/media/image-service.ts +1 -7
- package/src/memory/__tests__/fixtures/memory-v2-activation-fixtures.ts +21 -13
- package/src/memory/__tests__/jobs-worker-v2-schedule.test.ts +34 -51
- package/src/memory/__tests__/memory-v2-activation-log-store.test.ts +0 -6
- package/src/memory/__tests__/memory-v2-concept-frequency.test.ts +272 -0
- package/src/memory/admin.ts +5 -9
- package/src/memory/context-search/agent-runner.ts +19 -2
- package/src/memory/context-search/sources/conversations.ts +2 -11
- package/src/memory/context-search/sources/memory-v2.ts +1 -16
- package/src/memory/context-search/sources/memory.ts +2 -3
- package/src/memory/context-search/sources/pkb.ts +2 -3
- package/src/memory/context-search/types.ts +0 -1
- package/src/memory/conversation-crud.ts +4 -12
- package/src/memory/db-init.ts +2 -0
- package/src/memory/embedding-runtime-manager.ts +119 -5
- package/src/memory/graph/__tests__/conversation-graph-memory-v2-routing.test.ts +136 -82
- package/src/memory/graph/__tests__/handle-remember-v2.test.ts +11 -26
- package/src/memory/graph/conversation-graph-memory.ts +72 -61
- package/src/memory/graph/extraction.ts +1 -3
- package/src/memory/graph/graph-search.test.ts +11 -67
- package/src/memory/graph/graph-search.ts +4 -24
- package/src/memory/graph/retriever.test.ts +12 -1
- package/src/memory/graph/retriever.ts +10 -15
- package/src/memory/graph/tool-handlers.ts +3 -4
- package/src/memory/graph/tools.ts +4 -4
- package/src/memory/indexer.ts +53 -45
- package/src/memory/job-handlers/backfill.ts +2 -11
- package/src/memory/job-handlers/cleanup.ts +43 -0
- package/src/memory/job-handlers/embedding.ts +6 -8
- package/src/memory/job-handlers/summarization.ts +2 -7
- package/src/memory/jobs/__tests__/embed-concept-page.test.ts +116 -0
- package/src/memory/jobs/embed-concept-page.ts +223 -87
- package/src/memory/jobs-store.ts +48 -0
- package/src/memory/jobs-worker.ts +85 -43
- package/src/memory/memory-v2-activation-log-store.ts +32 -14
- package/src/memory/memory-v2-concept-frequency.ts +169 -0
- package/src/memory/migrations/239-trace-events-created-at-index.ts +18 -0
- package/src/memory/migrations/index.ts +1 -0
- package/src/memory/pkb/pkb-search.test.ts +7 -0
- package/src/memory/pkb/pkb-search.ts +4 -5
- package/src/memory/qdrant-client.ts +3 -13
- package/src/memory/rerank-local.ts +374 -0
- package/src/memory/search/semantic.ts +10 -72
- package/src/memory/trace-event-store.ts +1 -17
- package/src/memory/v2/__tests__/activation.test.ts +346 -255
- package/src/memory/v2/__tests__/consolidation-job.test.ts +61 -40
- package/src/memory/v2/__tests__/injection.test.ts +297 -190
- package/src/memory/v2/__tests__/prompts-consolidation.test.ts +61 -2
- package/src/memory/v2/__tests__/qdrant.test.ts +326 -9
- package/src/memory/v2/__tests__/reranker.test.ts +338 -0
- package/src/memory/v2/__tests__/sim.test.ts +113 -196
- package/src/memory/v2/__tests__/skill-store.test.ts +71 -65
- package/src/memory/v2/__tests__/static-context.test.ts +77 -14
- package/src/memory/v2/__tests__/sweep-job.test.ts +19 -33
- package/src/memory/v2/activation.ts +149 -156
- package/src/memory/v2/consolidation-job.ts +69 -20
- package/src/memory/v2/injection.ts +75 -68
- package/src/memory/v2/page-store.ts +39 -0
- package/src/memory/v2/prompts/consolidation.ts +41 -1
- package/src/memory/v2/qdrant.ts +306 -46
- package/src/memory/v2/reranker.ts +177 -0
- package/src/memory/v2/sim.ts +77 -110
- package/src/memory/v2/skill-content.ts +4 -3
- package/src/memory/v2/skill-store.ts +82 -59
- package/src/memory/v2/static-context.ts +26 -8
- package/src/memory/v2/sweep-job.ts +5 -6
- package/src/memory/v2/types.ts +17 -10
- package/src/notifications/copy-composer.ts +47 -0
- package/src/notifications/decision-engine.ts +46 -0
- package/src/notifications/signal.ts +4 -0
- package/src/oauth/AGENTS.md +3 -1
- package/src/oauth/__tests__/oauth-connect-state.test.ts +137 -0
- package/src/oauth/connect-orchestrator.ts +2 -0
- package/src/oauth/connection-resolver.test.ts +66 -1
- package/src/oauth/connection-resolver.ts +55 -1
- package/src/oauth/oauth-connect-state.ts +77 -0
- package/src/oauth/seed-providers.ts +58 -1
- package/src/permissions/gateway-threshold-reader.ts +116 -8
- package/src/permissions/prompter.ts +86 -96
- package/src/permissions/secret-prompter.ts +31 -31
- package/src/plugins/defaults/injectors.ts +36 -4
- package/src/plugins/defaults/memory-retrieval.ts +5 -6
- package/src/plugins/types.ts +7 -0
- package/src/proactive-artifact/aux-message-injector.ts +74 -0
- package/src/proactive-artifact/decision.test.ts +226 -0
- package/src/proactive-artifact/decision.ts +165 -0
- package/src/proactive-artifact/index.ts +7 -0
- package/src/proactive-artifact/job.test.ts +914 -0
- package/src/proactive-artifact/job.ts +366 -0
- package/src/proactive-artifact/message-copy.ts +58 -0
- package/src/proactive-artifact/trigger-state.test.ts +277 -0
- package/src/proactive-artifact/trigger-state.ts +119 -0
- package/src/prompts/normalize-onboarding.ts +80 -0
- package/src/prompts/persona-resolver.ts +101 -9
- package/src/prompts/system-prompt.ts +21 -7
- package/src/prompts/templates/BOOTSTRAP.md +13 -5
- package/src/prompts/templates/SOUL.md +13 -28
- package/src/providers/__tests__/retry-callsite.test.ts +222 -1
- package/src/providers/model-intents.ts +7 -0
- package/src/providers/openrouter/client.ts +8 -0
- package/src/providers/retry.ts +50 -0
- package/src/providers/types.ts +1 -0
- package/src/runtime/__tests__/agent-wake.test.ts +456 -3
- package/src/runtime/agent-wake.ts +238 -100
- package/src/runtime/assistant-event-hub.ts +36 -6
- package/src/runtime/assistant-event.ts +0 -1
- package/src/runtime/auth/__tests__/route-policy.test.ts +64 -0
- package/src/runtime/auth/route-policy.ts +15 -1
- package/src/runtime/auth/same-actor.ts +216 -0
- package/src/runtime/channel-approvals.ts +3 -2
- package/src/runtime/channel-retry-sweep.ts +65 -1
- package/src/runtime/local-actor-identity.ts +52 -11
- package/src/runtime/pending-interactions.ts +27 -15
- package/src/runtime/routes/__tests__/client-routes.test.ts +155 -0
- package/src/runtime/routes/__tests__/conversation-query-routes.test.ts +0 -5
- package/src/runtime/routes/__tests__/heartbeat-routes.test.ts +1 -1
- package/src/runtime/routes/__tests__/memory-v2-routes.test.ts +147 -0
- package/src/runtime/routes/approval-routes.ts +7 -3
- package/src/runtime/routes/client-routes.ts +20 -2
- package/src/runtime/routes/consolidation-routes.ts +8 -9
- package/src/runtime/routes/contact-routes.ts +0 -25
- package/src/runtime/routes/conversation-query-routes.ts +44 -1
- package/src/runtime/routes/conversation-routes.ts +35 -26
- package/src/runtime/routes/debug-bash-routes.ts +165 -0
- package/src/runtime/routes/disk-pressure-routes.ts +121 -0
- package/src/runtime/routes/document-pdf-renderer.ts +6 -2
- package/src/runtime/routes/documents-routes.ts +2 -75
- package/src/runtime/routes/events-routes.ts +41 -9
- package/src/runtime/routes/filing-routes.ts +2 -3
- package/src/runtime/routes/host-bash-routes.ts +23 -3
- package/src/runtime/routes/host-cu-routes.ts +33 -6
- package/src/runtime/routes/host-file-routes.ts +32 -6
- package/src/runtime/routes/host-transfer-routes.ts +79 -16
- package/src/runtime/routes/identity-routes.ts +7 -138
- package/src/runtime/routes/inbound-message-handler.ts +77 -12
- package/src/runtime/routes/index.ts +6 -0
- package/src/runtime/routes/memory-item-routes.test.ts +37 -17
- package/src/runtime/routes/memory-item-routes.ts +5 -6
- package/src/runtime/routes/memory-v2-routes.ts +136 -17
- package/src/runtime/routes/oauth-connect-routes.ts +153 -0
- package/src/runtime/verification-outbound-actions.ts +4 -4
- package/src/schedule/run-script.ts +37 -5
- package/src/schedule/scheduler.ts +20 -1
- package/src/security/encrypted-store.ts +2 -0
- package/src/security/secure-keys.ts +55 -0
- package/src/skills/include-graph.ts +35 -13
- package/src/skills/remote-skill-policy.ts +4 -10
- package/src/subagent/index.ts +1 -7
- package/src/subagent/manager.ts +1 -15
- package/src/tasks/task-runner.ts +0 -1
- package/src/tasks/task-store.ts +0 -3
- package/src/tools/background-tool-registry.ts +17 -3
- package/src/tools/document/document-tool.ts +20 -0
- package/src/tools/executor.ts +18 -2
- package/src/tools/host-filesystem/edit.test.ts +151 -0
- package/src/tools/host-filesystem/edit.ts +43 -1
- package/src/tools/host-filesystem/read.test.ts +129 -0
- package/src/tools/host-filesystem/read.ts +43 -1
- package/src/tools/host-filesystem/transfer.test.ts +127 -2
- package/src/tools/host-filesystem/transfer.ts +56 -11
- package/src/tools/host-filesystem/write.test.ts +134 -0
- package/src/tools/host-filesystem/write.ts +43 -1
- package/src/tools/host-terminal/host-shell.ts +13 -6
- package/src/tools/mcp/mcp-tool-factory.ts +2 -1
- package/src/tools/memory/register.test.ts +14 -9
- package/src/tools/memory/register.ts +1 -2
- package/src/tools/permission-checker.ts +15 -0
- package/src/tools/provider-tool-name.ts +28 -0
- package/src/tools/registry.ts +30 -9
- package/src/tools/skills/load.ts +24 -20
- package/src/tools/terminal/shell.ts +9 -1
- package/src/tools/tool-approval-handler.ts +31 -6
- package/src/tools/tool-name-aliases.ts +19 -0
- package/src/tools/types.ts +43 -3
- package/src/tts/provider-catalog.ts +3 -5
- package/src/util/disk-usage.ts +138 -0
- package/src/util/platform.ts +21 -11
- package/src/util/process-liveness.ts +26 -0
- package/src/workspace/heartbeat-service.ts +19 -0
- package/src/workspace/migrations/065-bump-stale-heartbeat-interval.ts +60 -0
- package/src/workspace/migrations/066-seed-heartbeat-callsite-cost-default.ts +146 -0
- package/src/workspace/migrations/067-release-notes-safe-storage-limits.ts +14 -0
- package/src/workspace/migrations/068-release-notes-local-timezone.ts +65 -0
- package/src/workspace/migrations/069-seed-onboarding-threads.ts +28 -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/registry.ts +14 -0
- package/src/__tests__/conversation-tool-setup-memory-scope.test.ts +0 -167
- package/src/memory/v2/__tests__/skill-qdrant.test.ts +0 -657
- package/src/memory/v2/skill-qdrant.ts +0 -404
- package/src/signals/bash.ts +0 -198
|
@@ -1,19 +1,14 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Tests for `handleRemember`
|
|
2
|
+
* Tests for `handleRemember` routing between v1 (PKB) and v2 (memory/).
|
|
3
3
|
*
|
|
4
|
-
*
|
|
5
|
-
*
|
|
6
|
-
* and NO PKB re-index job is enqueued.
|
|
7
|
-
* - Flag off → existing PKB path is unchanged (writes to `pkb/buffer.md`
|
|
8
|
-
* + `pkb/archive/<today>.md`, both files are re-indexed).
|
|
9
|
-
* - Archive filename uses today's local date.
|
|
4
|
+
* Routing follows `config.memory.v2.enabled`: when true, writes go to
|
|
5
|
+
* memory/; otherwise they fall back to v1 PKB.
|
|
10
6
|
*/
|
|
11
7
|
import { existsSync, mkdtempSync, readFileSync, rmSync } from "node:fs";
|
|
12
8
|
import { tmpdir } from "node:os";
|
|
13
9
|
import { join } from "node:path";
|
|
14
10
|
import {
|
|
15
11
|
afterAll,
|
|
16
|
-
afterEach,
|
|
17
12
|
beforeAll,
|
|
18
13
|
beforeEach,
|
|
19
14
|
describe,
|
|
@@ -62,11 +57,13 @@ afterAll(() => {
|
|
|
62
57
|
// Imports are deferred to after the env var is set so any internal use of
|
|
63
58
|
// `getWorkspaceDir()` resolves to the tmpdir.
|
|
64
59
|
const { handleRemember } = await import("../tool-handlers.js");
|
|
65
|
-
const { _setOverridesForTesting } =
|
|
66
|
-
await import("../../../config/assistant-feature-flags.js");
|
|
67
60
|
const { applyNestedDefaults } = await import("../../../config/loader.js");
|
|
68
61
|
|
|
69
62
|
const CONFIG = applyNestedDefaults({});
|
|
63
|
+
const CONFIG_V2_OFF = {
|
|
64
|
+
...CONFIG,
|
|
65
|
+
memory: { ...CONFIG.memory, v2: { ...CONFIG.memory.v2, enabled: false } },
|
|
66
|
+
};
|
|
70
67
|
|
|
71
68
|
beforeEach(() => {
|
|
72
69
|
enqueueCalls.length = 0;
|
|
@@ -76,10 +73,6 @@ beforeEach(() => {
|
|
|
76
73
|
rmSync(join(tmpWorkspace, "memory"), { recursive: true, force: true });
|
|
77
74
|
});
|
|
78
75
|
|
|
79
|
-
afterEach(() => {
|
|
80
|
-
_setOverridesForTesting({});
|
|
81
|
-
});
|
|
82
|
-
|
|
83
76
|
function todaysArchiveBasename(now: Date = new Date()): string {
|
|
84
77
|
const yyyy = now.getFullYear();
|
|
85
78
|
const mm = String(now.getMonth() + 1).padStart(2, "0");
|
|
@@ -87,11 +80,7 @@ function todaysArchiveBasename(now: Date = new Date()): string {
|
|
|
87
80
|
return `${yyyy}-${mm}-${dd}.md`;
|
|
88
81
|
}
|
|
89
82
|
|
|
90
|
-
describe("handleRemember — memory
|
|
91
|
-
beforeEach(() => {
|
|
92
|
-
_setOverridesForTesting({ "memory-v2-enabled": true });
|
|
93
|
-
});
|
|
94
|
-
|
|
83
|
+
describe("handleRemember — memory.v2.enabled on", () => {
|
|
95
84
|
test("writes to memory/buffer.md and memory/archive/<today>.md", () => {
|
|
96
85
|
const result = handleRemember(
|
|
97
86
|
{ content: "Alice prefers VS Code over Vim" },
|
|
@@ -175,17 +164,13 @@ describe("handleRemember — memory-v2 flag on", () => {
|
|
|
175
164
|
});
|
|
176
165
|
});
|
|
177
166
|
|
|
178
|
-
describe("handleRemember — memory
|
|
179
|
-
beforeEach(() => {
|
|
180
|
-
_setOverridesForTesting({ "memory-v2-enabled": false });
|
|
181
|
-
});
|
|
182
|
-
|
|
167
|
+
describe("handleRemember — memory.v2.enabled off (v1 PKB path)", () => {
|
|
183
168
|
test("writes to pkb/buffer.md and pkb/archive/<today>.md", () => {
|
|
184
169
|
const result = handleRemember(
|
|
185
170
|
{ content: "v1 path still works" },
|
|
186
171
|
"conv-v1-1",
|
|
187
172
|
"default",
|
|
188
|
-
|
|
173
|
+
CONFIG_V2_OFF,
|
|
189
174
|
);
|
|
190
175
|
|
|
191
176
|
expect(result.success).toBe(true);
|
|
@@ -206,7 +191,7 @@ describe("handleRemember — memory-v2 flag off (v1 PKB path)", () => {
|
|
|
206
191
|
{ content: "index me" },
|
|
207
192
|
"conv-v1-2",
|
|
208
193
|
"default",
|
|
209
|
-
|
|
194
|
+
CONFIG_V2_OFF,
|
|
210
195
|
);
|
|
211
196
|
|
|
212
197
|
expect(result.success).toBe(true);
|
|
@@ -8,7 +8,6 @@
|
|
|
8
8
|
|
|
9
9
|
import { and, desc, eq, inArray, ne, notInArray } from "drizzle-orm";
|
|
10
10
|
|
|
11
|
-
import { isAssistantFeatureFlagEnabled } from "../../config/assistant-feature-flags.js";
|
|
12
11
|
import type { AssistantConfig } from "../../config/types.js";
|
|
13
12
|
import { estimateTextTokens } from "../../context/token-estimator.js";
|
|
14
13
|
import type { ServerMessage } from "../../daemon/message-protocol.js";
|
|
@@ -23,6 +22,11 @@ import { getDb } from "../db-connection.js";
|
|
|
23
22
|
import type { QdrantSparseVector } from "../qdrant-client.js";
|
|
24
23
|
import { memorySummaries } from "../schema.js";
|
|
25
24
|
import { conversations } from "../schema/conversations.js";
|
|
25
|
+
import {
|
|
26
|
+
evictCompactedTurns as evictCompactedTurnsV2,
|
|
27
|
+
hydrate as hydrateV2State,
|
|
28
|
+
save as saveV2State,
|
|
29
|
+
} from "../v2/activation-store.js";
|
|
26
30
|
import {
|
|
27
31
|
injectMemoryV2Block,
|
|
28
32
|
type InjectMemoryV2Mode,
|
|
@@ -62,14 +66,12 @@ export class ConversationGraphMemory {
|
|
|
62
66
|
private initialized = false;
|
|
63
67
|
private needsReload = false;
|
|
64
68
|
private stateRestored = false;
|
|
65
|
-
private scopeId: string;
|
|
66
69
|
private conversationId: string;
|
|
67
70
|
private lastInjectedBlock: string | null = null;
|
|
68
71
|
private lastInjectedNodeIds: string[] = [];
|
|
69
72
|
private lastInjectedImages: Map<string, ResolvedImage> = new Map();
|
|
70
73
|
|
|
71
|
-
constructor(
|
|
72
|
-
this.scopeId = scopeId;
|
|
74
|
+
constructor(conversationId: string) {
|
|
73
75
|
this.conversationId = conversationId;
|
|
74
76
|
}
|
|
75
77
|
|
|
@@ -147,7 +149,6 @@ export class ConversationGraphMemory {
|
|
|
147
149
|
const db = getDb();
|
|
148
150
|
const baseWhere = and(
|
|
149
151
|
eq(memorySummaries.scope, "conversation"),
|
|
150
|
-
eq(memorySummaries.scopeId, this.scopeId),
|
|
151
152
|
ne(memorySummaries.scopeKey, this.conversationId),
|
|
152
153
|
);
|
|
153
154
|
|
|
@@ -209,11 +210,33 @@ export class ConversationGraphMemory {
|
|
|
209
210
|
* Notify that context compaction just happened.
|
|
210
211
|
* On the next turn, we'll re-run full context load.
|
|
211
212
|
*/
|
|
212
|
-
onCompacted(compactedMessageCount: number): void {
|
|
213
|
+
async onCompacted(compactedMessageCount: number): Promise<void> {
|
|
213
214
|
// Evict everything — compaction summarized all prior turns.
|
|
214
215
|
// The tracker can't know exactly which turns were compacted,
|
|
215
216
|
// so we conservatively clear everything and reload.
|
|
216
|
-
this.tracker.
|
|
217
|
+
const upToTurn = this.tracker.getTurn();
|
|
218
|
+
this.tracker.evictCompactedTurns(upToTurn);
|
|
219
|
+
|
|
220
|
+
// Mirror the eviction on the v2 activation row: the cached `<memory>`
|
|
221
|
+
// attachments those slugs lived on are gone, but `everInjected` would
|
|
222
|
+
// otherwise keep them deduped from per-turn deltas forever.
|
|
223
|
+
try {
|
|
224
|
+
const db = getDb();
|
|
225
|
+
const state = await hydrateV2State(db, this.conversationId);
|
|
226
|
+
if (state) {
|
|
227
|
+
await saveV2State(
|
|
228
|
+
db,
|
|
229
|
+
this.conversationId,
|
|
230
|
+
evictCompactedTurnsV2(state, upToTurn),
|
|
231
|
+
);
|
|
232
|
+
}
|
|
233
|
+
} catch (err) {
|
|
234
|
+
log.warn(
|
|
235
|
+
{ err: err instanceof Error ? err.message : String(err) },
|
|
236
|
+
"Failed to evict v2 activation state on compaction (non-fatal)",
|
|
237
|
+
);
|
|
238
|
+
}
|
|
239
|
+
|
|
217
240
|
this.needsReload = true;
|
|
218
241
|
log.info(
|
|
219
242
|
{ compactedMessageCount },
|
|
@@ -385,29 +408,11 @@ export class ConversationGraphMemory {
|
|
|
385
408
|
signal: AbortSignal,
|
|
386
409
|
onEvent: (msg: ServerMessage) => void,
|
|
387
410
|
) {
|
|
388
|
-
const result = await loadContextMemory({
|
|
389
|
-
scopeId: this.scopeId,
|
|
390
|
-
recentSummaries,
|
|
391
|
-
userQuery,
|
|
392
|
-
config,
|
|
393
|
-
signal,
|
|
394
|
-
});
|
|
395
|
-
|
|
396
|
-
this.initialized = true;
|
|
397
|
-
this.needsReload = false;
|
|
398
|
-
|
|
399
|
-
// v2 routing: when the feature flag and workspace config are both on,
|
|
400
|
-
// replace v1's injection with the activation-pipeline output. v1
|
|
401
|
-
// retrieval still runs above so its tracker stays warm — keeps the
|
|
402
|
-
// off→on→off flag flip cheap and avoids invalidating cached metrics.
|
|
403
|
-
// assistantMessage is empty: context-load fires on turn 1 / post-
|
|
404
|
-
// compaction, so there is no immediately-prior assistant turn to
|
|
405
|
-
// weight the activation against.
|
|
406
|
-
//
|
|
407
411
|
// Use the raw user text (no >10-char filter) so even short greetings
|
|
408
412
|
// ("hi") get a fresh top-K activation dump on the first user message.
|
|
409
|
-
// The activation pipeline is robust to weak ANN signal — it
|
|
410
|
-
//
|
|
413
|
+
// The activation pipeline is robust to weak ANN signal — it falls back
|
|
414
|
+
// to spreading + nowText to surface candidates.
|
|
415
|
+
const startedAt = Date.now();
|
|
411
416
|
const rawUserText = readRawUserText(messages[messages.length - 1]);
|
|
412
417
|
const v2 = await this.maybeRouteV2Injection(
|
|
413
418
|
messages,
|
|
@@ -415,7 +420,11 @@ export class ConversationGraphMemory {
|
|
|
415
420
|
"context-load",
|
|
416
421
|
rawUserText ?? userQuery ?? "",
|
|
417
422
|
"",
|
|
423
|
+
signal,
|
|
418
424
|
);
|
|
425
|
+
this.initialized = true;
|
|
426
|
+
this.needsReload = false;
|
|
427
|
+
|
|
419
428
|
if (v2.routed) {
|
|
420
429
|
this.lastInjectedBlock = v2.injectedBlockText;
|
|
421
430
|
this.lastInjectedNodeIds = [];
|
|
@@ -425,17 +434,22 @@ export class ConversationGraphMemory {
|
|
|
425
434
|
injectedTokens: v2.injectedBlockText
|
|
426
435
|
? estimateTextTokens(v2.injectedBlockText)
|
|
427
436
|
: 0,
|
|
428
|
-
latencyMs:
|
|
437
|
+
latencyMs: Date.now() - startedAt,
|
|
429
438
|
mode: "context-load" as const,
|
|
430
439
|
injectedBlockText: v2.injectedBlockText,
|
|
431
|
-
metrics:
|
|
432
|
-
queryVector: result.queryVector,
|
|
433
|
-
sparseVector: result.sparseVector,
|
|
434
|
-
userQueryVector: result.userQueryVector,
|
|
435
|
-
userQuerySparseVector: result.userQuerySparseVector,
|
|
440
|
+
metrics: null,
|
|
436
441
|
};
|
|
437
442
|
}
|
|
438
443
|
|
|
444
|
+
// v1 fallback — only reached when the v2 flag or workspace config is off.
|
|
445
|
+
const result = await loadContextMemory({
|
|
446
|
+
scopeId: "default",
|
|
447
|
+
recentSummaries,
|
|
448
|
+
userQuery,
|
|
449
|
+
config,
|
|
450
|
+
signal,
|
|
451
|
+
});
|
|
452
|
+
|
|
439
453
|
if (result.nodes.length === 0) {
|
|
440
454
|
this.lastInjectedBlock = null;
|
|
441
455
|
this.lastInjectedNodeIds = [];
|
|
@@ -543,27 +557,16 @@ export class ConversationGraphMemory {
|
|
|
543
557
|
if (userLastBlocks.length > 0 && assistantLast) break;
|
|
544
558
|
}
|
|
545
559
|
|
|
546
|
-
|
|
547
|
-
|
|
548
|
-
|
|
549
|
-
userLastMessageBlocks: userLastBlocks,
|
|
550
|
-
scopeId: this.scopeId,
|
|
551
|
-
config,
|
|
552
|
-
tracker: this.tracker,
|
|
553
|
-
signal,
|
|
554
|
-
});
|
|
555
|
-
|
|
556
|
-
// v2 routing: same gating as `runContextLoad` — when the flag and config
|
|
557
|
-
// are both on, the v2 activation pipeline produces the injection block
|
|
558
|
-
// (or `null` for the cache-stable empty path). v1 retrieval above runs
|
|
559
|
-
// unconditionally so the tracker stays in sync with the v1 nodes —
|
|
560
|
-
// cheap insurance for an off→on→off flag flip mid-conversation.
|
|
560
|
+
// v2 path — skip v1 retrieval entirely when v2 is enabled. See the
|
|
561
|
+
// matching comment in `runContextLoad` for rationale.
|
|
562
|
+
const startedAt = Date.now();
|
|
561
563
|
const v2 = await this.maybeRouteV2Injection(
|
|
562
564
|
messages,
|
|
563
565
|
config,
|
|
564
566
|
"per-turn",
|
|
565
567
|
userLast,
|
|
566
568
|
assistantLast,
|
|
569
|
+
signal,
|
|
567
570
|
);
|
|
568
571
|
if (v2.routed) {
|
|
569
572
|
this.lastInjectedBlock = v2.injectedBlockText;
|
|
@@ -574,15 +577,24 @@ export class ConversationGraphMemory {
|
|
|
574
577
|
injectedTokens: v2.injectedBlockText
|
|
575
578
|
? estimateTextTokens(v2.injectedBlockText)
|
|
576
579
|
: 0,
|
|
577
|
-
latencyMs:
|
|
580
|
+
latencyMs: Date.now() - startedAt,
|
|
578
581
|
mode: "per-turn" as const,
|
|
579
582
|
injectedBlockText: v2.injectedBlockText,
|
|
580
|
-
metrics:
|
|
581
|
-
queryVector: result.queryVector,
|
|
582
|
-
sparseVector: result.sparseVector,
|
|
583
|
+
metrics: null,
|
|
583
584
|
};
|
|
584
585
|
}
|
|
585
586
|
|
|
587
|
+
// v1 path (only reached when the v2 flag or workspace config is off).
|
|
588
|
+
const result = await retrieveForTurn({
|
|
589
|
+
assistantLastMessage: assistantLast,
|
|
590
|
+
userLastMessage: userLast,
|
|
591
|
+
userLastMessageBlocks: userLastBlocks,
|
|
592
|
+
scopeId: "default",
|
|
593
|
+
config,
|
|
594
|
+
tracker: this.tracker,
|
|
595
|
+
signal,
|
|
596
|
+
});
|
|
597
|
+
|
|
586
598
|
if (result.nodes.length === 0) {
|
|
587
599
|
this.lastInjectedBlock = null;
|
|
588
600
|
this.lastInjectedNodeIds = [];
|
|
@@ -641,12 +653,12 @@ export class ConversationGraphMemory {
|
|
|
641
653
|
}
|
|
642
654
|
|
|
643
655
|
/**
|
|
644
|
-
*
|
|
645
|
-
*
|
|
646
|
-
* config (`memory.v2.enabled`) are both on.
|
|
656
|
+
* Run the v2 activation pipeline when the workspace config
|
|
657
|
+
* (`memory.v2.enabled`) is on.
|
|
647
658
|
*
|
|
648
659
|
* The two outcomes the caller distinguishes via `routed`:
|
|
649
|
-
* - `routed: false` — v2 disabled; caller
|
|
660
|
+
* - `routed: false` — v2 disabled; caller falls through to the legacy v1
|
|
661
|
+
* retrieval path.
|
|
650
662
|
* - `routed: true` — v2 ran. `runMessages` is either the v2-prepended
|
|
651
663
|
* message list (block was non-null) or the input
|
|
652
664
|
* messages unchanged (cache-stable empty path).
|
|
@@ -658,15 +670,13 @@ export class ConversationGraphMemory {
|
|
|
658
670
|
mode: InjectMemoryV2Mode,
|
|
659
671
|
userMessage: string,
|
|
660
672
|
assistantMessage: string,
|
|
673
|
+
signal: AbortSignal,
|
|
661
674
|
): Promise<{
|
|
662
675
|
routed: boolean;
|
|
663
676
|
runMessages: Message[];
|
|
664
677
|
injectedBlockText: string | null;
|
|
665
678
|
}> {
|
|
666
|
-
if (
|
|
667
|
-
!isAssistantFeatureFlagEnabled("memory-v2-enabled", config) ||
|
|
668
|
-
!config.memory.v2.enabled
|
|
669
|
-
) {
|
|
679
|
+
if (!config.memory.v2.enabled) {
|
|
670
680
|
return { routed: false, runMessages: messages, injectedBlockText: null };
|
|
671
681
|
}
|
|
672
682
|
|
|
@@ -683,6 +693,7 @@ export class ConversationGraphMemory {
|
|
|
683
693
|
messageId: `${this.conversationId}:turn:${currentTurn}`,
|
|
684
694
|
mode,
|
|
685
695
|
config,
|
|
696
|
+
signal,
|
|
686
697
|
});
|
|
687
698
|
|
|
688
699
|
if (!result.block) {
|
|
@@ -1443,9 +1443,7 @@ async function findCandidateNodes(
|
|
|
1443
1443
|
const embedding = await embedWithRetry(config, [searchText]);
|
|
1444
1444
|
const queryVector = embedding.vectors[0];
|
|
1445
1445
|
if (queryVector) {
|
|
1446
|
-
const searchResults = await searchGraphNodes(queryVector, 100
|
|
1447
|
-
scopeId,
|
|
1448
|
-
]);
|
|
1446
|
+
const searchResults = await searchGraphNodes(queryVector, 100);
|
|
1449
1447
|
for (const r of searchResults) allNodeIds.add(r.nodeId);
|
|
1450
1448
|
}
|
|
1451
1449
|
} catch (err) {
|
|
@@ -2,6 +2,13 @@ import { beforeEach, describe, expect, mock, test } from "bun:test";
|
|
|
2
2
|
|
|
3
3
|
import { makeMockLogger } from "../../__tests__/helpers/mock-logger.js";
|
|
4
4
|
|
|
5
|
+
// This test exercises the v1 graph search path. `config.memory.v2.enabled`
|
|
6
|
+
// (default `true`) makes graph-search short-circuit to keep traffic off
|
|
7
|
+
// the legacy collection — force it off so the v1 path stays under test.
|
|
8
|
+
mock.module("../../config/loader.js", () => ({
|
|
9
|
+
getConfig: () => ({ memory: { v2: { enabled: false } } }),
|
|
10
|
+
}));
|
|
11
|
+
|
|
5
12
|
mock.module("../../util/logger.js", () => ({
|
|
6
13
|
getLogger: () => makeMockLogger(),
|
|
7
14
|
}));
|
|
@@ -64,7 +71,7 @@ describe("searchGraphNodes — _meta filter parity", () => {
|
|
|
64
71
|
});
|
|
65
72
|
|
|
66
73
|
test("hybrid path excludes _meta sentinel points", async () => {
|
|
67
|
-
await searchGraphNodes([0.1], 5,
|
|
74
|
+
await searchGraphNodes([0.1], 5, {
|
|
68
75
|
indices: [1],
|
|
69
76
|
values: [1],
|
|
70
77
|
});
|
|
@@ -80,7 +87,7 @@ describe("searchGraphNodes — _meta filter parity", () => {
|
|
|
80
87
|
});
|
|
81
88
|
|
|
82
89
|
test("dense-only path also excludes _meta sentinel points", async () => {
|
|
83
|
-
await searchGraphNodes([0.1], 5
|
|
90
|
+
await searchGraphNodes([0.1], 5);
|
|
84
91
|
|
|
85
92
|
expect(searchCalls).toHaveLength(1);
|
|
86
93
|
const filter = searchCalls[0]?.filter as {
|
|
@@ -93,69 +100,6 @@ describe("searchGraphNodes — _meta filter parity", () => {
|
|
|
93
100
|
});
|
|
94
101
|
});
|
|
95
102
|
|
|
96
|
-
describe("searchGraphNodes — excludeScopeIds", () => {
|
|
97
|
-
beforeEach(() => {
|
|
98
|
-
breakerOpen = false;
|
|
99
|
-
hybridSearchCalls.length = 0;
|
|
100
|
-
searchCalls.length = 0;
|
|
101
|
-
});
|
|
102
|
-
|
|
103
|
-
test("hybrid path adds memory_scope_id must_not when excludeScopeIds provided", async () => {
|
|
104
|
-
await searchGraphNodes(
|
|
105
|
-
[0.1],
|
|
106
|
-
5,
|
|
107
|
-
undefined,
|
|
108
|
-
{ indices: [1], values: [1] },
|
|
109
|
-
undefined,
|
|
110
|
-
["scope:abc", "scope:xyz"],
|
|
111
|
-
);
|
|
112
|
-
|
|
113
|
-
expect(hybridSearchCalls).toHaveLength(1);
|
|
114
|
-
const filter = hybridSearchCalls[0]?.filter as {
|
|
115
|
-
must_not: Array<Record<string, unknown>>;
|
|
116
|
-
};
|
|
117
|
-
const scopeExclude = filter.must_not.find(
|
|
118
|
-
(c) => c.key === "memory_scope_id",
|
|
119
|
-
) as { match: { any: string[] } } | undefined;
|
|
120
|
-
expect(scopeExclude?.match.any).toEqual(["scope:abc", "scope:xyz"]);
|
|
121
|
-
});
|
|
122
|
-
|
|
123
|
-
test("dense-only path adds memory_scope_id must_not when excludeScopeIds provided", async () => {
|
|
124
|
-
await searchGraphNodes([0.1], 5, undefined, undefined, undefined, [
|
|
125
|
-
"scope:abc",
|
|
126
|
-
]);
|
|
127
|
-
|
|
128
|
-
expect(searchCalls).toHaveLength(1);
|
|
129
|
-
const filter = searchCalls[0]?.filter as {
|
|
130
|
-
must_not: Array<Record<string, unknown>>;
|
|
131
|
-
};
|
|
132
|
-
const scopeExclude = filter.must_not.find(
|
|
133
|
-
(c) => c.key === "memory_scope_id",
|
|
134
|
-
) as { match: { any: string[] } } | undefined;
|
|
135
|
-
expect(scopeExclude?.match.any).toEqual(["scope:abc"]);
|
|
136
|
-
});
|
|
137
|
-
|
|
138
|
-
test("hybrid path omits memory_scope_id must_not when excludeScopeIds is empty", async () => {
|
|
139
|
-
await searchGraphNodes(
|
|
140
|
-
[0.1],
|
|
141
|
-
5,
|
|
142
|
-
undefined,
|
|
143
|
-
{ indices: [1], values: [1] },
|
|
144
|
-
undefined,
|
|
145
|
-
[],
|
|
146
|
-
);
|
|
147
|
-
|
|
148
|
-
expect(hybridSearchCalls).toHaveLength(1);
|
|
149
|
-
const filter = hybridSearchCalls[0]?.filter as {
|
|
150
|
-
must_not: Array<Record<string, unknown>>;
|
|
151
|
-
};
|
|
152
|
-
const scopeExclude = filter.must_not.find(
|
|
153
|
-
(c) => c.key === "memory_scope_id",
|
|
154
|
-
);
|
|
155
|
-
expect(scopeExclude).toBeUndefined();
|
|
156
|
-
});
|
|
157
|
-
});
|
|
158
|
-
|
|
159
103
|
describe("searchGraphNodes — prefetch floor", () => {
|
|
160
104
|
beforeEach(() => {
|
|
161
105
|
breakerOpen = false;
|
|
@@ -164,7 +108,7 @@ describe("searchGraphNodes — prefetch floor", () => {
|
|
|
164
108
|
});
|
|
165
109
|
|
|
166
110
|
test("hybrid prefetchLimit floors at 200 for small limits", async () => {
|
|
167
|
-
await searchGraphNodes([0.1], 10,
|
|
111
|
+
await searchGraphNodes([0.1], 10, {
|
|
168
112
|
indices: [1],
|
|
169
113
|
values: [1],
|
|
170
114
|
});
|
|
@@ -174,7 +118,7 @@ describe("searchGraphNodes — prefetch floor", () => {
|
|
|
174
118
|
});
|
|
175
119
|
|
|
176
120
|
test("hybrid prefetchLimit scales with limit when limit*10 exceeds floor", async () => {
|
|
177
|
-
await searchGraphNodes([0.1], 50,
|
|
121
|
+
await searchGraphNodes([0.1], 50, {
|
|
178
122
|
indices: [1],
|
|
179
123
|
values: [1],
|
|
180
124
|
});
|
|
@@ -5,7 +5,6 @@
|
|
|
5
5
|
import { getConfig } from "../../config/loader.js";
|
|
6
6
|
import type { AssistantConfig } from "../../config/types.js";
|
|
7
7
|
import { getLogger } from "../../util/logger.js";
|
|
8
|
-
import { isMemoryV2ReadActive } from "../context-search/sources/memory-v2.js";
|
|
9
8
|
import { selectedBackendSupportsMultimodal } from "../embedding-backend.js";
|
|
10
9
|
import type { EmbeddingInput } from "../embedding-types.js";
|
|
11
10
|
import { embedAndUpsert } from "../job-utils.js";
|
|
@@ -38,23 +37,19 @@ export interface GraphSearchResult {
|
|
|
38
37
|
* Semantic search across graph nodes in Qdrant. Returns scored node IDs
|
|
39
38
|
* that the caller can hydrate from the graph store.
|
|
40
39
|
*
|
|
41
|
-
* Filters to `target_type: "graph_node"
|
|
42
|
-
* `excludeScopeIds` adds a `must_not` against `memory_scope_id` for callers
|
|
43
|
-
* that need to omit specific scopes from a broader recall search.
|
|
40
|
+
* Filters to `target_type: "graph_node"`.
|
|
44
41
|
*/
|
|
45
42
|
export async function searchGraphNodes(
|
|
46
43
|
queryVector: number[],
|
|
47
44
|
limit: number,
|
|
48
|
-
scopeIds?: string[],
|
|
49
45
|
sparseVector?: QdrantSparseVector,
|
|
50
46
|
dateRange?: { afterMs?: number; beforeMs?: number },
|
|
51
|
-
excludeScopeIds?: string[],
|
|
52
47
|
): Promise<GraphSearchResult[]> {
|
|
53
|
-
// v2 owns the read path when
|
|
54
|
-
//
|
|
48
|
+
// v2 owns the read path when enabled. The v1 `memory` collection is in
|
|
49
|
+
// active retirement and a corrupted sparse segment can OOM-crash the
|
|
55
50
|
// shared Qdrant process — short-circuiting here keeps v1 background work
|
|
56
51
|
// and stale callers from taking v2 down with them.
|
|
57
|
-
if (
|
|
52
|
+
if (getConfig().memory.v2.enabled) return [];
|
|
58
53
|
|
|
59
54
|
if (isQdrantBreakerOpen()) {
|
|
60
55
|
log.warn("Qdrant circuit breaker open, skipping graph search");
|
|
@@ -66,21 +61,12 @@ export async function searchGraphNodes(
|
|
|
66
61
|
const mustNot: Record<string, unknown>[] = [
|
|
67
62
|
{ key: "_meta", match: { value: true } },
|
|
68
63
|
];
|
|
69
|
-
if (excludeScopeIds && excludeScopeIds.length > 0) {
|
|
70
|
-
mustNot.push({
|
|
71
|
-
key: "memory_scope_id",
|
|
72
|
-
match: { any: excludeScopeIds },
|
|
73
|
-
});
|
|
74
|
-
}
|
|
75
64
|
|
|
76
65
|
// Use hybrid search (dense + sparse with RRF fusion) when a non-empty
|
|
77
66
|
// sparse vector is available; otherwise fall back to dense-only search.
|
|
78
67
|
if (sparseVector && sparseVector.indices.length > 0) {
|
|
79
68
|
const must: Record<string, unknown>[] = [
|
|
80
69
|
{ key: "target_type", match: { value: "graph_node" } },
|
|
81
|
-
...(scopeIds && scopeIds.length > 0
|
|
82
|
-
? [{ key: "memory_scope_id", match: { any: scopeIds } }]
|
|
83
|
-
: []),
|
|
84
70
|
];
|
|
85
71
|
if (dateRange?.afterMs != null) {
|
|
86
72
|
must.push({ key: "created_at", range: { gte: dateRange.afterMs } });
|
|
@@ -120,12 +106,6 @@ export async function searchGraphNodes(
|
|
|
120
106
|
},
|
|
121
107
|
];
|
|
122
108
|
|
|
123
|
-
if (scopeIds && scopeIds.length > 0) {
|
|
124
|
-
denseMusts.push({
|
|
125
|
-
key: "memory_scope_id",
|
|
126
|
-
match: { any: scopeIds },
|
|
127
|
-
});
|
|
128
|
-
}
|
|
129
109
|
if (dateRange?.afterMs != null) {
|
|
130
110
|
denseMusts.push({ key: "created_at", range: { gte: dateRange.afterMs } });
|
|
131
111
|
}
|
|
@@ -81,7 +81,16 @@ import { loadContextMemory, retrieveForTurn } from "./retriever.js";
|
|
|
81
81
|
import { createNode } from "./store.js";
|
|
82
82
|
import type { NewNode } from "./types.js";
|
|
83
83
|
|
|
84
|
-
|
|
84
|
+
// These tests exercise v1 retrieval. `memory.v2.enabled` (default `true`)
|
|
85
|
+
// makes `loadContextMemory` short-circuit, so disable it here to keep the
|
|
86
|
+
// v1 path under test.
|
|
87
|
+
const TEST_CONFIG: AssistantConfig = {
|
|
88
|
+
...DEFAULT_CONFIG,
|
|
89
|
+
memory: {
|
|
90
|
+
...DEFAULT_CONFIG.memory,
|
|
91
|
+
v2: { ...DEFAULT_CONFIG.memory.v2, enabled: false },
|
|
92
|
+
},
|
|
93
|
+
};
|
|
85
94
|
|
|
86
95
|
function makeCapabilityNode(content: string, capId: string): NewNode {
|
|
87
96
|
const now = Date.now();
|
|
@@ -401,8 +410,10 @@ describe("loadContextMemory — dual-query capability ranking", () => {
|
|
|
401
410
|
|
|
402
411
|
// Build a config where capabilityReserve=1 so the ranking code actually
|
|
403
412
|
// prunes (it only prunes when capabilityNodes.length > capabilityReserve).
|
|
413
|
+
// memory.v2.enabled=false to keep the v1 retrieval path under test.
|
|
404
414
|
const DUAL_QUERY_CONFIG: AssistantConfig = structuredClone(DEFAULT_CONFIG);
|
|
405
415
|
DUAL_QUERY_CONFIG.memory.retrieval.injection.contextLoad.capabilityReserve = 1;
|
|
416
|
+
DUAL_QUERY_CONFIG.memory.v2.enabled = false;
|
|
406
417
|
|
|
407
418
|
// Keyword-routed embed: any text that contains a topic keyword returns a
|
|
408
419
|
// one-hot vector identifying that topic. Anything else falls back to a
|
|
@@ -14,7 +14,6 @@ import {
|
|
|
14
14
|
} from "../../providers/provider-send-message.js";
|
|
15
15
|
import type { ContentBlock, ImageContent } from "../../providers/types.js";
|
|
16
16
|
import { getLogger } from "../../util/logger.js";
|
|
17
|
-
import { isMemoryV2ReadActive } from "../context-search/sources/memory-v2.js";
|
|
18
17
|
import { embedWithRetry } from "../embed.js";
|
|
19
18
|
import {
|
|
20
19
|
generateSparseEmbedding,
|
|
@@ -351,7 +350,7 @@ async function dedupCrossCategory(
|
|
|
351
350
|
// Context load — conversation start
|
|
352
351
|
// ---------------------------------------------------------------------------
|
|
353
352
|
|
|
354
|
-
|
|
353
|
+
interface ContextLoadOpts {
|
|
355
354
|
/** Scope for memory isolation. */
|
|
356
355
|
scopeId: string;
|
|
357
356
|
/** Recent conversation summaries (used as retrieval queries). */
|
|
@@ -373,7 +372,7 @@ export interface ContextLoadOpts {
|
|
|
373
372
|
userQuery?: string;
|
|
374
373
|
}
|
|
375
374
|
|
|
376
|
-
|
|
375
|
+
interface ContextLoadResult {
|
|
377
376
|
nodes: ScoredNode[];
|
|
378
377
|
serendipityNodes: ScoredNode[];
|
|
379
378
|
triggeredNodes: TriggeredResult[];
|
|
@@ -426,12 +425,12 @@ export interface ContextLoadResult {
|
|
|
426
425
|
export async function loadContextMemory(
|
|
427
426
|
opts: ContextLoadOpts,
|
|
428
427
|
): Promise<ContextLoadResult> {
|
|
429
|
-
// v2 owns the read path when
|
|
430
|
-
//
|
|
431
|
-
//
|
|
428
|
+
// v2 owns the read path when enabled. The v1 collection is in active
|
|
429
|
+
// retirement and querying it can OOM-crash Qdrant via a corrupted sparse
|
|
430
|
+
// segment, so we skip the embedding work and downstream searches
|
|
432
431
|
// entirely. Caller (`runContextLoad`) sees zero nodes and routes to the
|
|
433
432
|
// v2 activation pipeline.
|
|
434
|
-
if (
|
|
433
|
+
if (opts.config.memory.v2.enabled) {
|
|
435
434
|
return {
|
|
436
435
|
nodes: [],
|
|
437
436
|
serendipityNodes: [],
|
|
@@ -525,7 +524,6 @@ export async function loadContextMemory(
|
|
|
525
524
|
const results = await searchGraphNodes(
|
|
526
525
|
queryVector,
|
|
527
526
|
maxNodes * 3,
|
|
528
|
-
[opts.scopeId],
|
|
529
527
|
sparseVector,
|
|
530
528
|
);
|
|
531
529
|
for (const r of results) {
|
|
@@ -548,7 +546,6 @@ export async function loadContextMemory(
|
|
|
548
546
|
const results = await searchGraphNodes(
|
|
549
547
|
userQueryVector,
|
|
550
548
|
maxNodes * 3,
|
|
551
|
-
[opts.scopeId],
|
|
552
549
|
undefined,
|
|
553
550
|
);
|
|
554
551
|
for (const r of results) {
|
|
@@ -873,7 +870,7 @@ export async function loadContextMemory(
|
|
|
873
870
|
// Per-turn retrieval — mid-conversation injection
|
|
874
871
|
// ---------------------------------------------------------------------------
|
|
875
872
|
|
|
876
|
-
|
|
873
|
+
interface TurnRetrievalOpts {
|
|
877
874
|
/** The assistant's last message content. */
|
|
878
875
|
assistantLastMessage: string;
|
|
879
876
|
/** The user's last message content. */
|
|
@@ -886,7 +883,7 @@ export interface TurnRetrievalOpts {
|
|
|
886
883
|
signal?: AbortSignal;
|
|
887
884
|
}
|
|
888
885
|
|
|
889
|
-
|
|
886
|
+
interface TurnRetrievalResult {
|
|
890
887
|
/** New nodes to inject (not already in context). */
|
|
891
888
|
nodes: ScoredNode[];
|
|
892
889
|
/** Serendipity picks included in nodes. */
|
|
@@ -1007,9 +1004,7 @@ export async function retrieveForTurn(
|
|
|
1007
1004
|
}
|
|
1008
1005
|
const imgVector = imgResult.vectors[0];
|
|
1009
1006
|
if (imgVector) {
|
|
1010
|
-
const imgResults = await searchGraphNodes(imgVector, 40
|
|
1011
|
-
opts.scopeId,
|
|
1012
|
-
]);
|
|
1007
|
+
const imgResults = await searchGraphNodes(imgVector, 40);
|
|
1013
1008
|
for (const r of imgResults) {
|
|
1014
1009
|
const current = allCandidateIds.get(r.nodeId) ?? 0;
|
|
1015
1010
|
allCandidateIds.set(r.nodeId, Math.max(current, r.score));
|
|
@@ -1097,7 +1092,7 @@ export async function retrieveForTurn(
|
|
|
1097
1092
|
queryEmbeddings = embedResults.vectors;
|
|
1098
1093
|
|
|
1099
1094
|
const searchPromises = queryEmbeddings.map((vec) =>
|
|
1100
|
-
searchGraphNodes(vec, 40
|
|
1095
|
+
searchGraphNodes(vec, 40),
|
|
1101
1096
|
);
|
|
1102
1097
|
const searchResults = await Promise.all(searchPromises);
|
|
1103
1098
|
|