@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
|
@@ -9,7 +9,7 @@ import type {
|
|
|
9
9
|
RecallSearchContext,
|
|
10
10
|
RecallSearchResult,
|
|
11
11
|
} from "../types.js";
|
|
12
|
-
import {
|
|
12
|
+
import { searchMemoryV2Source } from "./memory-v2.js";
|
|
13
13
|
|
|
14
14
|
const log = getLogger("context-search-memory-source");
|
|
15
15
|
|
|
@@ -23,7 +23,7 @@ export async function searchMemorySource(
|
|
|
23
23
|
return { evidence: [] };
|
|
24
24
|
}
|
|
25
25
|
|
|
26
|
-
if (
|
|
26
|
+
if (context.config.memory.v2.enabled) {
|
|
27
27
|
return searchMemoryV2Source(query, context, normalizedLimit);
|
|
28
28
|
}
|
|
29
29
|
|
|
@@ -47,7 +47,6 @@ export async function searchMemorySource(
|
|
|
47
47
|
const searchResults = await searchGraphNodes(
|
|
48
48
|
queryVector,
|
|
49
49
|
normalizedLimit,
|
|
50
|
-
[context.memoryScopeId],
|
|
51
50
|
sparseVector,
|
|
52
51
|
);
|
|
53
52
|
|
|
@@ -15,7 +15,6 @@ import type {
|
|
|
15
15
|
RecallSearchContext,
|
|
16
16
|
RecallSearchResult,
|
|
17
17
|
} from "../types.js";
|
|
18
|
-
import { isMemoryV2ReadActive } from "./memory-v2.js";
|
|
19
18
|
|
|
20
19
|
const log = getLogger("context-search-pkb-source");
|
|
21
20
|
|
|
@@ -76,7 +75,7 @@ export async function searchPkbSource(
|
|
|
76
75
|
context: RecallSearchContext,
|
|
77
76
|
limit: number,
|
|
78
77
|
): Promise<RecallSearchResult> {
|
|
79
|
-
if (
|
|
78
|
+
if (context.config.memory.v2.enabled) {
|
|
80
79
|
return { evidence: [] };
|
|
81
80
|
}
|
|
82
81
|
|
|
@@ -139,7 +138,7 @@ export async function searchPkbSource(
|
|
|
139
138
|
export function readPkbContextEvidence(
|
|
140
139
|
context: RecallSearchContext,
|
|
141
140
|
): RecallEvidence[] {
|
|
142
|
-
if (
|
|
141
|
+
if (context.config.memory.v2.enabled) {
|
|
143
142
|
return [];
|
|
144
143
|
}
|
|
145
144
|
|
|
@@ -115,8 +115,6 @@ export const messageMetadataSchema = z
|
|
|
115
115
|
})
|
|
116
116
|
.passthrough();
|
|
117
117
|
|
|
118
|
-
export type MessageMetadata = z.infer<typeof messageMetadataSchema>;
|
|
119
|
-
|
|
120
118
|
function cloneForkMessageMetadata(
|
|
121
119
|
metadata: string | null,
|
|
122
120
|
sourceMessageId: string,
|
|
@@ -444,11 +442,6 @@ export function getConversationSource(conversationId: string): string | null {
|
|
|
444
442
|
return row?.source ?? null;
|
|
445
443
|
}
|
|
446
444
|
|
|
447
|
-
export function getConversationMemoryScopeId(conversationId: string): string {
|
|
448
|
-
const conv = getConversation(conversationId);
|
|
449
|
-
return conv?.memoryScopeId ?? "default";
|
|
450
|
-
}
|
|
451
|
-
|
|
452
445
|
/**
|
|
453
446
|
* Fetch group_id for a conversation via raw SQL. group_id is NOT in the
|
|
454
447
|
* Drizzle schema (raw-query-only pattern), so ConversationRow doesn't
|
|
@@ -912,7 +905,6 @@ export async function addMessage(
|
|
|
912
905
|
if (!opts?.skipIndexing) {
|
|
913
906
|
try {
|
|
914
907
|
const config = getConfig();
|
|
915
|
-
const scopeId = getConversationMemoryScopeId(conversationId);
|
|
916
908
|
const parsed = metadata
|
|
917
909
|
? messageMetadataSchema.safeParse(metadata)
|
|
918
910
|
: null;
|
|
@@ -927,7 +919,7 @@ export async function addMessage(
|
|
|
927
919
|
role: message.role,
|
|
928
920
|
content: message.content,
|
|
929
921
|
createdAt: message.createdAt,
|
|
930
|
-
scopeId,
|
|
922
|
+
scopeId: "default",
|
|
931
923
|
provenanceTrustClass,
|
|
932
924
|
automated,
|
|
933
925
|
},
|
|
@@ -1027,7 +1019,7 @@ export function hasMessages(conversationId: string): boolean {
|
|
|
1027
1019
|
return row !== undefined;
|
|
1028
1020
|
}
|
|
1029
1021
|
|
|
1030
|
-
|
|
1022
|
+
interface PaginatedMessagesResult {
|
|
1031
1023
|
messages: MessageRow[];
|
|
1032
1024
|
hasMore: boolean;
|
|
1033
1025
|
}
|
|
@@ -1441,12 +1433,12 @@ export function deleteLastExchange(conversationId: string): number {
|
|
|
1441
1433
|
* Callers must delete these from the Qdrant collection after the
|
|
1442
1434
|
* SQLite transaction commits.
|
|
1443
1435
|
*/
|
|
1444
|
-
|
|
1436
|
+
interface DeletedMemoryIds {
|
|
1445
1437
|
segmentIds: string[];
|
|
1446
1438
|
deletedSummaryIds: string[];
|
|
1447
1439
|
}
|
|
1448
1440
|
|
|
1449
|
-
|
|
1441
|
+
interface WipeConversationResult extends DeletedMemoryIds {
|
|
1450
1442
|
cancelledJobCount: number;
|
|
1451
1443
|
}
|
|
1452
1444
|
|
package/src/memory/db-init.ts
CHANGED
|
@@ -165,6 +165,7 @@ import {
|
|
|
165
165
|
migrateStripPlaceholderSentinelsFromMessages,
|
|
166
166
|
migrateStripThinkingFromConsolidated,
|
|
167
167
|
migrateToolInvocationsMatchedRuleId,
|
|
168
|
+
migrateTraceEventsCreatedAtIndex,
|
|
168
169
|
migrateUsageDashboardIndexes,
|
|
169
170
|
migrateUsageLlmCallCount,
|
|
170
171
|
migrateVoiceInviteColumns,
|
|
@@ -408,6 +409,7 @@ export function initializeDb(): void {
|
|
|
408
409
|
backfillAppConversationIds();
|
|
409
410
|
},
|
|
410
411
|
migrateScheduleRetryPolicy,
|
|
412
|
+
migrateTraceEventsCreatedAtIndex,
|
|
411
413
|
];
|
|
412
414
|
|
|
413
415
|
// Run each migration step, catching and logging individual failures so one
|
|
@@ -38,10 +38,16 @@ const ONNXRUNTIME_COMMON_VERSION = "1.21.0";
|
|
|
38
38
|
const TRANSFORMERS_VERSION = "3.8.1";
|
|
39
39
|
const JINJA_VERSION = "0.5.5";
|
|
40
40
|
|
|
41
|
-
/**
|
|
42
|
-
|
|
41
|
+
/**
|
|
42
|
+
* Composite version string for cache invalidation. Bumping the trailing
|
|
43
|
+
* `_workers-vN` suffix forces existing installs to regenerate the worker
|
|
44
|
+
* scripts when the worker IPC contract or spawn-args list changes (without
|
|
45
|
+
* requiring an `@huggingface/transformers` version bump).
|
|
46
|
+
*/
|
|
47
|
+
const RUNTIME_VERSION = `ort-${ONNXRUNTIME_NODE_VERSION}_hf-${TRANSFORMERS_VERSION}_jinja-${JINJA_VERSION}_workers-v2`;
|
|
43
48
|
|
|
44
49
|
const WORKER_FILENAME = "embed-worker.mjs";
|
|
50
|
+
const RERANK_WORKER_FILENAME = "rerank-worker.mjs";
|
|
45
51
|
|
|
46
52
|
/** Module-level guard so concurrent in-process calls share one download. */
|
|
47
53
|
const installGuard = new PromiseGuard<void>();
|
|
@@ -171,6 +177,101 @@ process.stdin.on('end', () => process.exit(0));
|
|
|
171
177
|
`;
|
|
172
178
|
}
|
|
173
179
|
|
|
180
|
+
function generateRerankWorkerScript(): string {
|
|
181
|
+
// Cross-encoder rerank worker. Loads a sequence-classification model and
|
|
182
|
+
// scores paired (queries[i], passages[i]) tuples in one batched ONNX
|
|
183
|
+
// inference call. Mirrors the embed worker's lifecycle (ready signal,
|
|
184
|
+
// JSON-lines IPC, sequential queue) so LocalRerankBackend can reuse the
|
|
185
|
+
// same supervisor pattern.
|
|
186
|
+
//
|
|
187
|
+
// Request shape: { id, queries: string[], passages: string[] } with
|
|
188
|
+
// queries.length === passages.length. Each pair is one (query, passage)
|
|
189
|
+
// tuple; multiple distinct queries can ride in a single batch so the
|
|
190
|
+
// activation pipeline can score the user-channel and assistant-channel
|
|
191
|
+
// queries against a shared candidate set in one tokenizer + ONNX call.
|
|
192
|
+
return `\
|
|
193
|
+
// rerank-worker.mjs — Auto-generated by EmbeddingRuntimeManager
|
|
194
|
+
// Runs in a separate bun process, communicates via JSON-lines over stdin/stdout.
|
|
195
|
+
process.title = 'rerank-worker';
|
|
196
|
+
import {
|
|
197
|
+
AutoModelForSequenceClassification,
|
|
198
|
+
AutoTokenizer,
|
|
199
|
+
env,
|
|
200
|
+
} from '@huggingface/transformers';
|
|
201
|
+
|
|
202
|
+
const model = process.argv[2];
|
|
203
|
+
const cacheDir = process.argv[3];
|
|
204
|
+
const dtype = process.argv[4] || 'q8';
|
|
205
|
+
if (cacheDir && env) env.cacheDir = cacheDir;
|
|
206
|
+
|
|
207
|
+
let tokenizer;
|
|
208
|
+
let session;
|
|
209
|
+
try {
|
|
210
|
+
tokenizer = await AutoTokenizer.from_pretrained(model);
|
|
211
|
+
session = await AutoModelForSequenceClassification.from_pretrained(model, { dtype });
|
|
212
|
+
process.stdout.write(JSON.stringify({ type: 'ready' }) + '\\n');
|
|
213
|
+
} catch (err) {
|
|
214
|
+
process.stdout.write(JSON.stringify({ type: 'error', error: err.message || String(err) }) + '\\n');
|
|
215
|
+
process.exit(1);
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
const sigmoid = (x) => 1 / (1 + Math.exp(-x));
|
|
219
|
+
|
|
220
|
+
const decoder = new TextDecoder();
|
|
221
|
+
let buffer = '';
|
|
222
|
+
let processing = false;
|
|
223
|
+
const queue = [];
|
|
224
|
+
|
|
225
|
+
process.stdin.on('data', (chunk) => {
|
|
226
|
+
buffer += typeof chunk === 'string' ? chunk : decoder.decode(chunk, { stream: true });
|
|
227
|
+
let idx;
|
|
228
|
+
while ((idx = buffer.indexOf('\\n')) !== -1) {
|
|
229
|
+
const line = buffer.slice(0, idx);
|
|
230
|
+
buffer = buffer.slice(idx + 1);
|
|
231
|
+
if (line.trim()) queue.push(line);
|
|
232
|
+
}
|
|
233
|
+
if (!processing) processQueue();
|
|
234
|
+
});
|
|
235
|
+
|
|
236
|
+
async function processQueue() {
|
|
237
|
+
processing = true;
|
|
238
|
+
while (queue.length > 0) {
|
|
239
|
+
const line = queue.shift();
|
|
240
|
+
let req;
|
|
241
|
+
try { req = JSON.parse(line); } catch { continue; }
|
|
242
|
+
try {
|
|
243
|
+
const { id, queries, passages } = req;
|
|
244
|
+
if (
|
|
245
|
+
!Array.isArray(queries) || !Array.isArray(passages) ||
|
|
246
|
+
queries.length !== passages.length || passages.length === 0
|
|
247
|
+
) {
|
|
248
|
+
process.stdout.write(JSON.stringify({ id, scores: [] }) + '\\n');
|
|
249
|
+
continue;
|
|
250
|
+
}
|
|
251
|
+
const inputs = await tokenizer(queries, {
|
|
252
|
+
text_pair: passages,
|
|
253
|
+
padding: true,
|
|
254
|
+
truncation: true,
|
|
255
|
+
return_tensors: 'pt',
|
|
256
|
+
});
|
|
257
|
+
const out = await session(inputs);
|
|
258
|
+
const logits = out.logits.data;
|
|
259
|
+
const scores = new Array(passages.length);
|
|
260
|
+
for (let i = 0; i < passages.length; i++) {
|
|
261
|
+
scores[i] = sigmoid(Number(logits[i]));
|
|
262
|
+
}
|
|
263
|
+
process.stdout.write(JSON.stringify({ id, scores }) + '\\n');
|
|
264
|
+
} catch (err) {
|
|
265
|
+
process.stdout.write(JSON.stringify({ id: req?.id, error: err.message || String(err) }) + '\\n');
|
|
266
|
+
}
|
|
267
|
+
}
|
|
268
|
+
processing = false;
|
|
269
|
+
}
|
|
270
|
+
|
|
271
|
+
process.stdin.on('end', () => process.exit(0));
|
|
272
|
+
`;
|
|
273
|
+
}
|
|
274
|
+
|
|
174
275
|
// ── Main manager ────────────────────────────────────────────────────
|
|
175
276
|
|
|
176
277
|
export class EmbeddingRuntimeManager {
|
|
@@ -186,8 +287,12 @@ export class EmbeddingRuntimeManager {
|
|
|
186
287
|
if (!manifest) return false;
|
|
187
288
|
if (manifest.runtimeVersion !== RUNTIME_VERSION) return false;
|
|
188
289
|
|
|
189
|
-
// Verify
|
|
190
|
-
return
|
|
290
|
+
// Verify both worker scripts exist and a bun binary is available
|
|
291
|
+
return (
|
|
292
|
+
existsSync(this.getWorkerPath()) &&
|
|
293
|
+
existsSync(this.getRerankWorkerPath()) &&
|
|
294
|
+
this.getBunPath() !== undefined
|
|
295
|
+
);
|
|
191
296
|
}
|
|
192
297
|
|
|
193
298
|
/** Path to the embed worker script. */
|
|
@@ -195,6 +300,11 @@ export class EmbeddingRuntimeManager {
|
|
|
195
300
|
return join(this.baseDir, WORKER_FILENAME);
|
|
196
301
|
}
|
|
197
302
|
|
|
303
|
+
/** Path to the rerank worker script. */
|
|
304
|
+
getRerankWorkerPath(): string {
|
|
305
|
+
return join(this.baseDir, RERANK_WORKER_FILENAME);
|
|
306
|
+
}
|
|
307
|
+
|
|
198
308
|
/**
|
|
199
309
|
* Find a usable bun binary.
|
|
200
310
|
* Delegates to the shared bun-runtime helper, also checking
|
|
@@ -375,8 +485,12 @@ export class EmbeddingRuntimeManager {
|
|
|
375
485
|
].join("\n"),
|
|
376
486
|
);
|
|
377
487
|
|
|
378
|
-
// Step 4: Write embed worker
|
|
488
|
+
// Step 4: Write embed + rerank worker scripts
|
|
379
489
|
writeFileSync(join(tmpDir, WORKER_FILENAME), generateWorkerScript());
|
|
490
|
+
writeFileSync(
|
|
491
|
+
join(tmpDir, RERANK_WORKER_FILENAME),
|
|
492
|
+
generateRerankWorkerScript(),
|
|
493
|
+
);
|
|
380
494
|
|
|
381
495
|
// Step 5: Write version manifest
|
|
382
496
|
const manifest: VersionManifest = {
|
|
@@ -1,18 +1,16 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Tests for the v2 routing wired into `ConversationGraphMemory.prepareMemory`.
|
|
3
3
|
*
|
|
4
|
-
* The wiring layer at `conversation-graph-memory.ts` reads
|
|
5
|
-
* decide whether to swap v1's injection step
|
|
4
|
+
* The wiring layer at `conversation-graph-memory.ts` reads
|
|
5
|
+
* `config.memory.v2.enabled` to decide whether to swap v1's injection step
|
|
6
|
+
* for the v2 activation pipeline.
|
|
6
7
|
*
|
|
7
|
-
*
|
|
8
|
-
*
|
|
9
|
-
*
|
|
10
|
-
*
|
|
11
|
-
* `
|
|
12
|
-
*
|
|
13
|
-
* mocking `injection.js` itself would clobber that sibling test when both
|
|
14
|
-
* files run in the same `bun test` invocation, since `mock.module` is
|
|
15
|
-
* process-global. Avoiding the mock keeps the suite hermetic in either order.
|
|
8
|
+
* This file uses the *real* `injectMemoryV2Block` and stubs only the
|
|
9
|
+
* lower-level deps (Qdrant client, embedding backend) the way
|
|
10
|
+
* `memory/v2/__tests__/injection.test.ts` does — mocking `injection.js`
|
|
11
|
+
* itself would clobber that sibling test when both files run in the same
|
|
12
|
+
* `bun test` invocation, since `mock.module` is process-global. Avoiding
|
|
13
|
+
* the mock keeps the suite hermetic in either order.
|
|
16
14
|
*/
|
|
17
15
|
import { mkdirSync, mkdtempSync, rmSync, writeFileSync } from "node:fs";
|
|
18
16
|
import { tmpdir } from "node:os";
|
|
@@ -20,7 +18,6 @@ import { join } from "node:path";
|
|
|
20
18
|
import { Database } from "bun:sqlite";
|
|
21
19
|
import {
|
|
22
20
|
afterAll,
|
|
23
|
-
afterEach,
|
|
24
21
|
beforeAll,
|
|
25
22
|
beforeEach,
|
|
26
23
|
describe,
|
|
@@ -45,25 +42,28 @@ mock.module("../../../util/logger.js", () => ({
|
|
|
45
42
|
|
|
46
43
|
// Stub the v1 retriever so we don't reach Qdrant. Both modes return zero
|
|
47
44
|
// nodes — the v1 injection branch becomes a no-op, isolating the assertion
|
|
48
|
-
// to "did the v2 routing fire?".
|
|
45
|
+
// to "did the v2 routing fire?". Tracked via `mock()` so tests can also
|
|
46
|
+
// assert that v1 retrieval is *not* called when v2 is enabled.
|
|
47
|
+
const loadContextMemoryMock = mock(async () => ({
|
|
48
|
+
nodes: [],
|
|
49
|
+
serendipityNodes: [],
|
|
50
|
+
latencyMs: 1,
|
|
51
|
+
metrics: null,
|
|
52
|
+
queryVector: undefined,
|
|
53
|
+
sparseVector: undefined,
|
|
54
|
+
userQueryVector: undefined,
|
|
55
|
+
userQuerySparseVector: undefined,
|
|
56
|
+
}));
|
|
57
|
+
const retrieveForTurnMock = mock(async () => ({
|
|
58
|
+
nodes: [],
|
|
59
|
+
latencyMs: 1,
|
|
60
|
+
metrics: null,
|
|
61
|
+
queryVector: undefined,
|
|
62
|
+
sparseVector: undefined,
|
|
63
|
+
}));
|
|
49
64
|
mock.module("../retriever.js", () => ({
|
|
50
|
-
loadContextMemory:
|
|
51
|
-
|
|
52
|
-
serendipityNodes: [],
|
|
53
|
-
latencyMs: 1,
|
|
54
|
-
metrics: null,
|
|
55
|
-
queryVector: undefined,
|
|
56
|
-
sparseVector: undefined,
|
|
57
|
-
userQueryVector: undefined,
|
|
58
|
-
userQuerySparseVector: undefined,
|
|
59
|
-
}),
|
|
60
|
-
retrieveForTurn: async () => ({
|
|
61
|
-
nodes: [],
|
|
62
|
-
latencyMs: 1,
|
|
63
|
-
metrics: null,
|
|
64
|
-
queryVector: undefined,
|
|
65
|
-
sparseVector: undefined,
|
|
66
|
-
}),
|
|
65
|
+
loadContextMemory: loadContextMemoryMock,
|
|
66
|
+
retrieveForTurn: retrieveForTurnMock,
|
|
67
67
|
}));
|
|
68
68
|
|
|
69
69
|
// Programmable embedding + Qdrant state. Mirrors the pattern in
|
|
@@ -95,9 +95,11 @@ class MockQdrantClient {
|
|
|
95
95
|
_name: string,
|
|
96
96
|
params: { using: string; limit: number; filter?: unknown },
|
|
97
97
|
) {
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
98
|
+
// The four-channel hybrid query fires body-dense, body-sparse,
|
|
99
|
+
// summary-dense, summary-sparse in order; both dense channels share
|
|
100
|
+
// the dense queue and both sparse channels share the sparse queue.
|
|
101
|
+
const channel = params.using.endsWith("sparse") ? "sparse" : "dense";
|
|
102
|
+
return qdrantState.queryResponses[channel].shift() ?? { points: [] };
|
|
101
103
|
}
|
|
102
104
|
}
|
|
103
105
|
|
|
@@ -167,14 +169,14 @@ import type { DrizzleDb } from "../../db-connection.js";
|
|
|
167
169
|
|
|
168
170
|
const { ConversationGraphMemory } =
|
|
169
171
|
await import("../conversation-graph-memory.js");
|
|
170
|
-
const { _setOverridesForTesting } =
|
|
171
|
-
await import("../../../config/assistant-feature-flags.js");
|
|
172
172
|
const { applyNestedDefaults } = await import("../../../config/loader.js");
|
|
173
173
|
const { getSqliteFrom } = await import("../../db-connection.js");
|
|
174
174
|
const { migrateActivationState } =
|
|
175
175
|
await import("../../migrations/232-activation-state.js");
|
|
176
176
|
const schema = await import("../../schema.js");
|
|
177
177
|
const { _resetMemoryV2QdrantForTests } = await import("../../v2/qdrant.js");
|
|
178
|
+
const { hydrate: hydrateActivationState } =
|
|
179
|
+
await import("../../v2/activation-store.js");
|
|
178
180
|
|
|
179
181
|
// The wiring layer calls `getDb()` to fetch the SQLite handle. We mock
|
|
180
182
|
// only that one export and spread the real module so unrelated callers
|
|
@@ -232,15 +234,26 @@ function makeMemory(): InstanceType<typeof ConversationGraphMemory> {
|
|
|
232
234
|
// `initialized = true` skips the context-load branch and the
|
|
233
235
|
// `fetchRecentSummaries` DB read it depends on, isolating the per-turn path
|
|
234
236
|
// for these unit tests. Context-load is covered by its own block below.
|
|
235
|
-
const m = new ConversationGraphMemory("
|
|
237
|
+
const m = new ConversationGraphMemory("conv-test-1");
|
|
236
238
|
(m as unknown as { initialized: boolean }).initialized = true;
|
|
237
239
|
return m;
|
|
238
240
|
}
|
|
239
241
|
|
|
240
|
-
/** Stage one set of dense/sparse hits for each channel of
|
|
241
|
-
* pipeline (1 candidate query + 3 simBatch channels).
|
|
242
|
+
/** Stage one set of body and summary dense/sparse hits for each channel of
|
|
243
|
+
* the activation pipeline (1 candidate query + 3 simBatch channels). Each
|
|
244
|
+
* `hybridQueryConceptPages` call now fires four sub-queries (body-dense,
|
|
245
|
+
* body-sparse, summary-dense, summary-sparse) so we push four entries per
|
|
246
|
+
* channel iteration. Hits without `summary*Score` set produce empty point
|
|
247
|
+
* lists for the summary channels — fine for tests that only care about body
|
|
248
|
+
* scoring. */
|
|
242
249
|
function stageTurn(
|
|
243
|
-
hits: Array<{
|
|
250
|
+
hits: Array<{
|
|
251
|
+
slug: string;
|
|
252
|
+
denseScore?: number;
|
|
253
|
+
sparseScore?: number;
|
|
254
|
+
summaryDenseScore?: number;
|
|
255
|
+
summarySparseScore?: number;
|
|
256
|
+
}>,
|
|
244
257
|
): void {
|
|
245
258
|
for (let i = 0; i < 4; i++) {
|
|
246
259
|
qdrantState.queryResponses.dense.push({
|
|
@@ -253,6 +266,22 @@ function stageTurn(
|
|
|
253
266
|
.filter((h) => h.sparseScore !== undefined)
|
|
254
267
|
.map((h) => ({ score: h.sparseScore, payload: { slug: h.slug } })),
|
|
255
268
|
});
|
|
269
|
+
qdrantState.queryResponses.dense.push({
|
|
270
|
+
points: hits
|
|
271
|
+
.filter((h) => h.summaryDenseScore !== undefined)
|
|
272
|
+
.map((h) => ({
|
|
273
|
+
score: h.summaryDenseScore,
|
|
274
|
+
payload: { slug: h.slug },
|
|
275
|
+
})),
|
|
276
|
+
});
|
|
277
|
+
qdrantState.queryResponses.sparse.push({
|
|
278
|
+
points: hits
|
|
279
|
+
.filter((h) => h.summarySparseScore !== undefined)
|
|
280
|
+
.map((h) => ({
|
|
281
|
+
score: h.summarySparseScore,
|
|
282
|
+
payload: { slug: h.slug },
|
|
283
|
+
})),
|
|
284
|
+
});
|
|
256
285
|
}
|
|
257
286
|
}
|
|
258
287
|
|
|
@@ -262,42 +291,17 @@ beforeEach(() => {
|
|
|
262
291
|
testDbHandle = createTestDb();
|
|
263
292
|
qdrantState.queryResponses.dense.length = 0;
|
|
264
293
|
qdrantState.queryResponses.sparse.length = 0;
|
|
294
|
+
loadContextMemoryMock.mockClear();
|
|
295
|
+
retrieveForTurnMock.mockClear();
|
|
265
296
|
_resetMemoryV2QdrantForTests();
|
|
266
297
|
});
|
|
267
298
|
|
|
268
|
-
afterEach(() => {
|
|
269
|
-
_setOverridesForTesting({});
|
|
270
|
-
});
|
|
271
|
-
|
|
272
299
|
// ---------------------------------------------------------------------------
|
|
273
300
|
// Tests
|
|
274
301
|
// ---------------------------------------------------------------------------
|
|
275
302
|
|
|
276
303
|
describe("ConversationGraphMemory.prepareMemory — v2 routing (per-turn path)", () => {
|
|
277
|
-
test("
|
|
278
|
-
_setOverridesForTesting({ "memory-v2-enabled": false });
|
|
279
|
-
stageTurn([{ slug: "alice-vscode", denseScore: 0.9 }]);
|
|
280
|
-
|
|
281
|
-
const memory = makeMemory();
|
|
282
|
-
const config = makeConfig(true);
|
|
283
|
-
const messages = makeMessages();
|
|
284
|
-
|
|
285
|
-
const result = await memory.prepareMemory(
|
|
286
|
-
messages,
|
|
287
|
-
config,
|
|
288
|
-
new AbortController().signal,
|
|
289
|
-
noopEvent,
|
|
290
|
-
);
|
|
291
|
-
|
|
292
|
-
expect(result.mode).toBe("per-turn");
|
|
293
|
-
expect(result.injectedBlockText).toBeNull();
|
|
294
|
-
// No v2 block prepended — the v1 retriever returned zero nodes so the
|
|
295
|
-
// user message is exactly the input.
|
|
296
|
-
expect(result.runMessages).toEqual(messages);
|
|
297
|
-
});
|
|
298
|
-
|
|
299
|
-
test("flag on + config off → v2 not run, messages unchanged", async () => {
|
|
300
|
-
_setOverridesForTesting({ "memory-v2-enabled": true });
|
|
304
|
+
test("config off → v2 not run, messages unchanged", async () => {
|
|
301
305
|
stageTurn([{ slug: "alice-vscode", denseScore: 0.9 }]);
|
|
302
306
|
|
|
303
307
|
const memory = makeMemory();
|
|
@@ -316,8 +320,7 @@ describe("ConversationGraphMemory.prepareMemory — v2 routing (per-turn path)",
|
|
|
316
320
|
expect(result.runMessages).toEqual(messages);
|
|
317
321
|
});
|
|
318
322
|
|
|
319
|
-
test("
|
|
320
|
-
_setOverridesForTesting({ "memory-v2-enabled": true });
|
|
323
|
+
test("config on → v2 block prepended, mode is per-turn", async () => {
|
|
321
324
|
stageTurn([{ slug: "alice-vscode", denseScore: 0.9 }]);
|
|
322
325
|
|
|
323
326
|
const memory = makeMemory();
|
|
@@ -334,7 +337,9 @@ describe("ConversationGraphMemory.prepareMemory — v2 routing (per-turn path)",
|
|
|
334
337
|
expect(result.mode).toBe("per-turn");
|
|
335
338
|
expect(result.injectedBlockText).not.toBeNull();
|
|
336
339
|
expect(result.injectedBlockText).not.toContain("<memory>");
|
|
337
|
-
expect(result.injectedBlockText).toContain(
|
|
340
|
+
expect(result.injectedBlockText).toContain(
|
|
341
|
+
"# memory/concepts/alice-vscode.md",
|
|
342
|
+
);
|
|
338
343
|
|
|
339
344
|
// The leading content block on the user message is the v2 block,
|
|
340
345
|
// wrapped exactly once.
|
|
@@ -347,13 +352,15 @@ describe("ConversationGraphMemory.prepareMemory — v2 routing (per-turn path)",
|
|
|
347
352
|
expect(firstBlock.text.endsWith("\n</memory>")).toBe(true);
|
|
348
353
|
// No nested wrapper.
|
|
349
354
|
expect(firstBlock.text.match(/<memory>/g)?.length).toBe(1);
|
|
355
|
+
|
|
356
|
+
// v1 retrieval is fully bypassed when v2 is enabled.
|
|
357
|
+
expect(retrieveForTurnMock).not.toHaveBeenCalled();
|
|
350
358
|
});
|
|
351
359
|
|
|
352
360
|
test("reinjectCachedMemory after v2 injection wraps exactly once (no double-wrap)", async () => {
|
|
353
361
|
// Regression for the double-wrap bug: v2 cached `lastInjectedBlock`
|
|
354
362
|
// already wrapped, then `reinjectCachedMemory` re-wrapped via
|
|
355
363
|
// `injectTextBlock`, producing `<memory>\n<memory>\n...\n</memory>\n</memory>`.
|
|
356
|
-
_setOverridesForTesting({ "memory-v2-enabled": true });
|
|
357
364
|
stageTurn([{ slug: "alice-vscode", denseScore: 0.9 }]);
|
|
358
365
|
|
|
359
366
|
const memory = makeMemory();
|
|
@@ -380,11 +387,10 @@ describe("ConversationGraphMemory.prepareMemory — v2 routing (per-turn path)",
|
|
|
380
387
|
expect(firstBlock.text.endsWith("\n</memory>")).toBe(true);
|
|
381
388
|
expect(firstBlock.text.match(/<memory>/g)?.length).toBe(1);
|
|
382
389
|
expect(firstBlock.text.match(/<\/memory>/g)?.length).toBe(1);
|
|
383
|
-
expect(firstBlock.text).toContain("
|
|
390
|
+
expect(firstBlock.text).toContain("# memory/concepts/alice-vscode.md");
|
|
384
391
|
});
|
|
385
392
|
|
|
386
|
-
test("
|
|
387
|
-
_setOverridesForTesting({ "memory-v2-enabled": true });
|
|
393
|
+
test("config on with empty Qdrant hits → no v2 block, v1 fallback skipped", async () => {
|
|
388
394
|
// No `stageTurn` call — every channel returns `{ points: [] }` so the
|
|
389
395
|
// candidate set is empty and `injectMemoryV2Block` returns block=null.
|
|
390
396
|
const memory = makeMemory();
|
|
@@ -404,12 +410,11 @@ describe("ConversationGraphMemory.prepareMemory — v2 routing (per-turn path)",
|
|
|
404
410
|
});
|
|
405
411
|
|
|
406
412
|
describe("ConversationGraphMemory.prepareMemory — v2 routing (context-load path)", () => {
|
|
407
|
-
test("
|
|
408
|
-
_setOverridesForTesting({ "memory-v2-enabled": true });
|
|
413
|
+
test("config on → v2 fires with mode=context-load", async () => {
|
|
409
414
|
stageTurn([{ slug: "alice-vscode", denseScore: 0.9 }]);
|
|
410
415
|
|
|
411
416
|
// Fresh memory → initialized=false → runContextLoad branch.
|
|
412
|
-
const memory = new ConversationGraphMemory("
|
|
417
|
+
const memory = new ConversationGraphMemory("conv-test-cl");
|
|
413
418
|
const config = makeConfig(true);
|
|
414
419
|
const messages = makeMessages("first message of the conversation here");
|
|
415
420
|
|
|
@@ -422,7 +427,9 @@ describe("ConversationGraphMemory.prepareMemory — v2 routing (context-load pat
|
|
|
422
427
|
|
|
423
428
|
expect(result.mode).toBe("context-load");
|
|
424
429
|
expect(result.injectedBlockText).not.toBeNull();
|
|
425
|
-
expect(result.injectedBlockText).toContain(
|
|
430
|
+
expect(result.injectedBlockText).toContain(
|
|
431
|
+
"# memory/concepts/alice-vscode.md",
|
|
432
|
+
);
|
|
426
433
|
// injectedBlockText is the unwrapped inner content; the wrapper is
|
|
427
434
|
// applied at injection time on the run message.
|
|
428
435
|
expect(result.injectedBlockText).not.toContain("<memory>");
|
|
@@ -430,14 +437,16 @@ describe("ConversationGraphMemory.prepareMemory — v2 routing (context-load pat
|
|
|
430
437
|
const firstBlock = lastMsg?.content[0];
|
|
431
438
|
if (firstBlock?.type !== "text") throw new Error("unexpected block type");
|
|
432
439
|
expect(firstBlock.text.match(/<memory>/g)?.length).toBe(1);
|
|
440
|
+
|
|
441
|
+
// v1 retrieval is fully bypassed when v2 is enabled.
|
|
442
|
+
expect(loadContextMemoryMock).not.toHaveBeenCalled();
|
|
433
443
|
});
|
|
434
444
|
|
|
435
|
-
test("
|
|
436
|
-
_setOverridesForTesting({ "memory-v2-enabled": false });
|
|
445
|
+
test("config off → v2 not run on first turn either", async () => {
|
|
437
446
|
stageTurn([{ slug: "alice-vscode", denseScore: 0.9 }]);
|
|
438
447
|
|
|
439
|
-
const memory = new ConversationGraphMemory("
|
|
440
|
-
const config = makeConfig(
|
|
448
|
+
const memory = new ConversationGraphMemory("conv-test-cl-off");
|
|
449
|
+
const config = makeConfig(false);
|
|
441
450
|
const messages = makeMessages("first message of the conversation here");
|
|
442
451
|
|
|
443
452
|
const result = await memory.prepareMemory(
|
|
@@ -451,3 +460,48 @@ describe("ConversationGraphMemory.prepareMemory — v2 routing (context-load pat
|
|
|
451
460
|
expect(result.injectedBlockText).toBeNull();
|
|
452
461
|
});
|
|
453
462
|
});
|
|
463
|
+
|
|
464
|
+
describe("ConversationGraphMemory.onCompacted — v2 activation eviction", () => {
|
|
465
|
+
test("clears everInjected so a previously-injected slug can re-attach", async () => {
|
|
466
|
+
// Without this wiring, `selectInjections` keeps subtracting the slug from
|
|
467
|
+
// every per-turn delta even though compaction discarded the cached
|
|
468
|
+
// `<memory>` attachment that previously made it visible.
|
|
469
|
+
const conversationId = "conv-test-evict";
|
|
470
|
+
const memory = new ConversationGraphMemory(conversationId);
|
|
471
|
+
const config = makeConfig(true);
|
|
472
|
+
|
|
473
|
+
// Turn 1 — context-load fires (initialized=false), injecting alice-vscode.
|
|
474
|
+
stageTurn([{ slug: "alice-vscode", denseScore: 0.9 }]);
|
|
475
|
+
const initial = await memory.prepareMemory(
|
|
476
|
+
makeMessages("Tell me about Alice's editor preferences"),
|
|
477
|
+
config,
|
|
478
|
+
new AbortController().signal,
|
|
479
|
+
noopEvent,
|
|
480
|
+
);
|
|
481
|
+
expect(initial.injectedBlockText).toContain(
|
|
482
|
+
"# memory/concepts/alice-vscode.md",
|
|
483
|
+
);
|
|
484
|
+
|
|
485
|
+
const before = await hydrateActivationState(testDbHandle!, conversationId);
|
|
486
|
+
expect(before?.everInjected.map((e) => e.slug)).toContain("alice-vscode");
|
|
487
|
+
|
|
488
|
+
await memory.onCompacted(1);
|
|
489
|
+
|
|
490
|
+
const after = await hydrateActivationState(testDbHandle!, conversationId);
|
|
491
|
+
expect(after?.everInjected).toEqual([]);
|
|
492
|
+
|
|
493
|
+
// Turn 2 — same Qdrant relevance. With everInjected cleared the slug
|
|
494
|
+
// should appear again in the injection block (re-attached on the new
|
|
495
|
+
// user message after compaction).
|
|
496
|
+
stageTurn([{ slug: "alice-vscode", denseScore: 0.9 }]);
|
|
497
|
+
const next = await memory.prepareMemory(
|
|
498
|
+
makeMessages("And what about Alice's editor again?"),
|
|
499
|
+
config,
|
|
500
|
+
new AbortController().signal,
|
|
501
|
+
noopEvent,
|
|
502
|
+
);
|
|
503
|
+
expect(next.injectedBlockText).toContain(
|
|
504
|
+
"# memory/concepts/alice-vscode.md",
|
|
505
|
+
);
|
|
506
|
+
});
|
|
507
|
+
});
|