@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
|
@@ -68,17 +68,6 @@ const state = {
|
|
|
68
68
|
points: Array<{ score?: number; payload: Record<string, unknown> }>;
|
|
69
69
|
}>,
|
|
70
70
|
},
|
|
71
|
-
// Separate response queue for the dedicated `memory_v2_skills` collection
|
|
72
|
-
// so a test asserting on skill activation does not have to interleave
|
|
73
|
-
// responses with concept-page queries.
|
|
74
|
-
skillQueryResponses: {
|
|
75
|
-
dense: [] as Array<{
|
|
76
|
-
points: Array<{ score?: number; payload: Record<string, unknown> }>;
|
|
77
|
-
}>,
|
|
78
|
-
sparse: [] as Array<{
|
|
79
|
-
points: Array<{ score?: number; payload: Record<string, unknown> }>;
|
|
80
|
-
}>,
|
|
81
|
-
},
|
|
82
71
|
queryCalls: [] as Array<{
|
|
83
72
|
collection: string;
|
|
84
73
|
using: string;
|
|
@@ -125,12 +114,11 @@ class MockQdrantClient {
|
|
|
125
114
|
limit: params.limit,
|
|
126
115
|
filter: params.filter,
|
|
127
116
|
});
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
return queue.shift() ?? { points: [] };
|
|
117
|
+
// The four-channel hybrid query fires body-dense, body-sparse,
|
|
118
|
+
// summary-dense, summary-sparse in order; both dense channels share
|
|
119
|
+
// the dense queue and both sparse channels share the sparse queue.
|
|
120
|
+
const channel = params.using.endsWith("sparse") ? "sparse" : "dense";
|
|
121
|
+
return state.queryResponses[channel].shift() ?? { points: [] };
|
|
134
122
|
}
|
|
135
123
|
}
|
|
136
124
|
|
|
@@ -138,6 +126,37 @@ mock.module("@qdrant/js-client-rest", () => ({
|
|
|
138
126
|
QdrantClient: MockQdrantClient,
|
|
139
127
|
}));
|
|
140
128
|
|
|
129
|
+
// Reranker mock — keeps the activation tests hermetic when rerank.enabled is
|
|
130
|
+
// flipped on by an integration case. Tests stage `rerankState.scores` to
|
|
131
|
+
// program the boost outcome. The activation pipeline now passes both the
|
|
132
|
+
// user-channel and assistant-channel queries into a single rerank call, so
|
|
133
|
+
// `rerankState.calls` records the full `queries` array per invocation.
|
|
134
|
+
const rerankState = {
|
|
135
|
+
scores: null as Map<string, number> | null,
|
|
136
|
+
calls: [] as Array<{ queries: string[]; candidates: string[] }>,
|
|
137
|
+
};
|
|
138
|
+
mock.module("../reranker.js", () => ({
|
|
139
|
+
rerankCandidates: async (
|
|
140
|
+
queries: readonly string[],
|
|
141
|
+
candidates: readonly string[],
|
|
142
|
+
): Promise<Array<Map<string, number>>> => {
|
|
143
|
+
rerankState.calls.push({
|
|
144
|
+
queries: [...queries],
|
|
145
|
+
candidates: [...candidates],
|
|
146
|
+
});
|
|
147
|
+
return queries.map(() => {
|
|
148
|
+
if (rerankState.scores === null) return new Map();
|
|
149
|
+
const out = new Map<string, number>();
|
|
150
|
+
for (const slug of candidates) {
|
|
151
|
+
const v = rerankState.scores.get(slug);
|
|
152
|
+
if (v !== undefined) out.set(slug, v);
|
|
153
|
+
}
|
|
154
|
+
return out;
|
|
155
|
+
});
|
|
156
|
+
},
|
|
157
|
+
_resetRerankCacheForTests: () => {},
|
|
158
|
+
}));
|
|
159
|
+
|
|
141
160
|
// Static `import type` is fine — types erase, so they don't run module-init
|
|
142
161
|
// code that would race the mocks above.
|
|
143
162
|
import type { EdgeIndex } from "../edge-index.js";
|
|
@@ -145,15 +164,11 @@ import type { ActivationState } from "../types.js";
|
|
|
145
164
|
|
|
146
165
|
const {
|
|
147
166
|
computeOwnActivation,
|
|
148
|
-
computeSkillActivation,
|
|
149
167
|
selectCandidates,
|
|
150
168
|
selectInjections,
|
|
151
|
-
selectSkillInjections,
|
|
152
169
|
spreadActivation,
|
|
153
170
|
} = await import("../activation.js");
|
|
154
171
|
const { _resetMemoryV2QdrantForTests } = await import("../qdrant.js");
|
|
155
|
-
const { _resetMemoryV2SkillQdrantForTests } =
|
|
156
|
-
await import("../skill-qdrant.js");
|
|
157
172
|
|
|
158
173
|
// ---------------------------------------------------------------------------
|
|
159
174
|
// Helpers
|
|
@@ -166,16 +181,15 @@ function resetState(): void {
|
|
|
166
181
|
state.sparseReturn = { indices: [1, 2, 3], values: [0.5, 0.5, 0.5] };
|
|
167
182
|
state.queryResponses.dense.length = 0;
|
|
168
183
|
state.queryResponses.sparse.length = 0;
|
|
169
|
-
state.skillQueryResponses.dense.length = 0;
|
|
170
|
-
state.skillQueryResponses.sparse.length = 0;
|
|
171
184
|
state.queryCalls.length = 0;
|
|
185
|
+
rerankState.scores = null;
|
|
186
|
+
rerankState.calls.length = 0;
|
|
172
187
|
// Bun's `mock.module` persists across files in the same process, so the
|
|
173
|
-
// qdrant
|
|
174
|
-
// instance from a sibling test file (e.g. sim.test.ts). Resetting
|
|
188
|
+
// qdrant module's `_client` singleton may already hold a MockQdrantClient
|
|
189
|
+
// instance from a sibling test file (e.g. sim.test.ts). Resetting the
|
|
175
190
|
// cache AND any latched readiness forces a fresh `new QdrantClient()` —
|
|
176
191
|
// which under our mock above resolves to *this* file's MockQdrantClient.
|
|
177
192
|
_resetMemoryV2QdrantForTests();
|
|
178
|
-
_resetMemoryV2SkillQdrantForTests();
|
|
179
193
|
}
|
|
180
194
|
|
|
181
195
|
/**
|
|
@@ -212,9 +226,20 @@ function makeConfig(
|
|
|
212
226
|
} as unknown as AssistantConfig;
|
|
213
227
|
}
|
|
214
228
|
|
|
215
|
-
/**
|
|
229
|
+
/**
|
|
230
|
+
* Stage a single hybrid-query response — body channels first, then summary
|
|
231
|
+
* channels (which default to empty). The four-channel hybrid query fires
|
|
232
|
+
* body-dense, body-sparse, summary-dense, summary-sparse in that order, so
|
|
233
|
+
* each logical call consumes 2 dense + 2 sparse queue entries.
|
|
234
|
+
*/
|
|
216
235
|
function stageHybridResponse(
|
|
217
|
-
hits: Array<{
|
|
236
|
+
hits: Array<{
|
|
237
|
+
slug: string;
|
|
238
|
+
denseScore?: number;
|
|
239
|
+
sparseScore?: number;
|
|
240
|
+
summaryDenseScore?: number;
|
|
241
|
+
summarySparseScore?: number;
|
|
242
|
+
}>,
|
|
218
243
|
): void {
|
|
219
244
|
state.queryResponses.dense.push({
|
|
220
245
|
points: hits
|
|
@@ -226,6 +251,22 @@ function stageHybridResponse(
|
|
|
226
251
|
.filter((h) => h.sparseScore !== undefined)
|
|
227
252
|
.map((h) => ({ score: h.sparseScore, payload: { slug: h.slug } })),
|
|
228
253
|
});
|
|
254
|
+
state.queryResponses.dense.push({
|
|
255
|
+
points: hits
|
|
256
|
+
.filter((h) => h.summaryDenseScore !== undefined)
|
|
257
|
+
.map((h) => ({
|
|
258
|
+
score: h.summaryDenseScore,
|
|
259
|
+
payload: { slug: h.slug },
|
|
260
|
+
})),
|
|
261
|
+
});
|
|
262
|
+
state.queryResponses.sparse.push({
|
|
263
|
+
points: hits
|
|
264
|
+
.filter((h) => h.summarySparseScore !== undefined)
|
|
265
|
+
.map((h) => ({
|
|
266
|
+
score: h.summarySparseScore,
|
|
267
|
+
payload: { slug: h.slug },
|
|
268
|
+
})),
|
|
269
|
+
});
|
|
229
270
|
}
|
|
230
271
|
|
|
231
272
|
beforeEach(resetState);
|
|
@@ -358,7 +399,7 @@ describe("selectCandidates", () => {
|
|
|
358
399
|
nowText: "",
|
|
359
400
|
config: makeConfig(),
|
|
360
401
|
});
|
|
361
|
-
expect(state.queryCalls).toHaveLength(
|
|
402
|
+
expect(state.queryCalls).toHaveLength(4);
|
|
362
403
|
for (const call of state.queryCalls) {
|
|
363
404
|
expect(call.limit).toBe(1_000_000);
|
|
364
405
|
expect(call.filter).toBeUndefined();
|
|
@@ -374,7 +415,7 @@ describe("selectCandidates", () => {
|
|
|
374
415
|
nowText: "",
|
|
375
416
|
config: makeConfig({ ann_candidate_limit: 25 }),
|
|
376
417
|
});
|
|
377
|
-
expect(state.queryCalls).toHaveLength(
|
|
418
|
+
expect(state.queryCalls).toHaveLength(4);
|
|
378
419
|
for (const call of state.queryCalls) {
|
|
379
420
|
expect(call.limit).toBe(25);
|
|
380
421
|
expect(call.filter).toBeUndefined();
|
|
@@ -554,6 +595,257 @@ describe("computeOwnActivation", () => {
|
|
|
554
595
|
// No prior state → prev=0 → priorContribution=0 regardless of `d`.
|
|
555
596
|
expect(out.breakdown.get("fresh")?.priorContribution).toBe(0);
|
|
556
597
|
});
|
|
598
|
+
|
|
599
|
+
test("rerank boost on user/assistant flips top-1 when fused had it second", async () => {
|
|
600
|
+
// Three Qdrant queries fire in parallel inside computeOwnActivation:
|
|
601
|
+
// user, assistant, now. Stage identical hits for each so the only signal
|
|
602
|
+
// separating slugs is the rerank boost on the user + assistant channels.
|
|
603
|
+
const stagedHits = [
|
|
604
|
+
{ slug: "lexical", denseScore: 0.6, sparseScore: 0 },
|
|
605
|
+
{ slug: "semantic", denseScore: 0.5, sparseScore: 0 },
|
|
606
|
+
];
|
|
607
|
+
stageHybridResponse(stagedHits); // user channel
|
|
608
|
+
stageHybridResponse(stagedHits); // assistant channel
|
|
609
|
+
stageHybridResponse(stagedHits); // now channel
|
|
610
|
+
rerankState.scores = new Map([
|
|
611
|
+
["lexical", 0.05],
|
|
612
|
+
["semantic", 0.95],
|
|
613
|
+
]);
|
|
614
|
+
|
|
615
|
+
const config = {
|
|
616
|
+
memory: {
|
|
617
|
+
v2: {
|
|
618
|
+
d: 0.0,
|
|
619
|
+
c_user: 0.5,
|
|
620
|
+
c_assistant: 0.5,
|
|
621
|
+
c_now: 0.0,
|
|
622
|
+
dense_weight: 1.0,
|
|
623
|
+
sparse_weight: 0.0,
|
|
624
|
+
rerank: {
|
|
625
|
+
enabled: true,
|
|
626
|
+
top_k: 50,
|
|
627
|
+
alpha: 0.5,
|
|
628
|
+
model: "test-model",
|
|
629
|
+
},
|
|
630
|
+
},
|
|
631
|
+
},
|
|
632
|
+
} as unknown as AssistantConfig;
|
|
633
|
+
|
|
634
|
+
const out = await computeOwnActivation({
|
|
635
|
+
candidates: new Set(["lexical", "semantic"]),
|
|
636
|
+
priorState: null,
|
|
637
|
+
userText: "u",
|
|
638
|
+
assistantText: "a",
|
|
639
|
+
nowText: "n",
|
|
640
|
+
config,
|
|
641
|
+
});
|
|
642
|
+
|
|
643
|
+
// Without rerank: lexical (0.6) would beat semantic (0.5) on both
|
|
644
|
+
// user and assistant channels.
|
|
645
|
+
// With rerank (alpha=0.5):
|
|
646
|
+
// lexical: 0.6 + 0.5 · (0.05/0.95) ≈ 0.626
|
|
647
|
+
// semantic: 0.5 + 0.5 · 1.0 = 1.0
|
|
648
|
+
// The semantic candidate now wins on both rerank-boosted channels.
|
|
649
|
+
expect(out.activation.get("semantic")!).toBeGreaterThan(
|
|
650
|
+
out.activation.get("lexical")!,
|
|
651
|
+
);
|
|
652
|
+
// Both rerank-enabled channels ride in a single batched rerank call.
|
|
653
|
+
expect(rerankState.calls).toHaveLength(1);
|
|
654
|
+
expect(rerankState.calls[0].queries).toEqual(["u", "a"]);
|
|
655
|
+
});
|
|
656
|
+
|
|
657
|
+
test("rerank pool is the unified top-K by pre-rerank A_o, not per-channel fused", async () => {
|
|
658
|
+
// Three candidates. The per-channel fused-sim top-2s would have picked
|
|
659
|
+
// different sets:
|
|
660
|
+
// user channel: a=0.9, b=0.5, c=0.4 → per-channel top-2 = [a, b]
|
|
661
|
+
// assistant channel: a=0.5, b=0.4, c=0.9 → per-channel top-2 = [c, a]
|
|
662
|
+
// But pre-rerank A_o (c_user=c_assistant=0.5) is:
|
|
663
|
+
// a = 0.5·0.9 + 0.5·0.5 = 0.70
|
|
664
|
+
// b = 0.5·0.5 + 0.5·0.4 = 0.45
|
|
665
|
+
// c = 0.5·0.4 + 0.5·0.9 = 0.65
|
|
666
|
+
// → unified top-2 = [a, c]. b drops out, even though it would have made
|
|
667
|
+
// the user-channel pool under the old per-channel selection.
|
|
668
|
+
stageHybridResponse([
|
|
669
|
+
{ slug: "a", denseScore: 0.9 },
|
|
670
|
+
{ slug: "b", denseScore: 0.5 },
|
|
671
|
+
{ slug: "c", denseScore: 0.4 },
|
|
672
|
+
]); // user
|
|
673
|
+
stageHybridResponse([
|
|
674
|
+
{ slug: "a", denseScore: 0.5 },
|
|
675
|
+
{ slug: "b", denseScore: 0.4 },
|
|
676
|
+
{ slug: "c", denseScore: 0.9 },
|
|
677
|
+
]); // assistant
|
|
678
|
+
stageHybridResponse([]); // now (no signal)
|
|
679
|
+
rerankState.scores = new Map([
|
|
680
|
+
["a", 0.5],
|
|
681
|
+
["b", 0.5],
|
|
682
|
+
["c", 0.5],
|
|
683
|
+
]);
|
|
684
|
+
|
|
685
|
+
const config = {
|
|
686
|
+
memory: {
|
|
687
|
+
v2: {
|
|
688
|
+
d: 0.0,
|
|
689
|
+
c_user: 0.5,
|
|
690
|
+
c_assistant: 0.5,
|
|
691
|
+
c_now: 0.0,
|
|
692
|
+
dense_weight: 1.0,
|
|
693
|
+
sparse_weight: 0.0,
|
|
694
|
+
rerank: {
|
|
695
|
+
enabled: true,
|
|
696
|
+
top_k: 2,
|
|
697
|
+
alpha: 0.3,
|
|
698
|
+
model: "test-model",
|
|
699
|
+
},
|
|
700
|
+
},
|
|
701
|
+
},
|
|
702
|
+
} as unknown as AssistantConfig;
|
|
703
|
+
|
|
704
|
+
await computeOwnActivation({
|
|
705
|
+
candidates: new Set(["a", "b", "c"]),
|
|
706
|
+
priorState: null,
|
|
707
|
+
userText: "u",
|
|
708
|
+
assistantText: "a",
|
|
709
|
+
nowText: "",
|
|
710
|
+
config,
|
|
711
|
+
});
|
|
712
|
+
|
|
713
|
+
// Single batched rerank call carrying both channel queries against the
|
|
714
|
+
// unified slug set, sorted by pre-rerank A_o descending.
|
|
715
|
+
expect(rerankState.calls).toHaveLength(1);
|
|
716
|
+
expect(rerankState.calls[0].queries).toEqual(["u", "a"]);
|
|
717
|
+
expect(rerankState.calls[0].candidates).toEqual(["a", "c"]);
|
|
718
|
+
});
|
|
719
|
+
|
|
720
|
+
test("rerank-disabled candidates outside the unified pool get zero boost", async () => {
|
|
721
|
+
// Two candidates, top_k=1. The lower pre-rerank A_o slug must end up
|
|
722
|
+
// with simUserRerankBoost=0 / simAssistantRerankBoost=0 in the breakdown.
|
|
723
|
+
stageHybridResponse([
|
|
724
|
+
{ slug: "winner", denseScore: 0.9 },
|
|
725
|
+
{ slug: "loser", denseScore: 0.2 },
|
|
726
|
+
]); // user
|
|
727
|
+
stageHybridResponse([
|
|
728
|
+
{ slug: "winner", denseScore: 0.9 },
|
|
729
|
+
{ slug: "loser", denseScore: 0.2 },
|
|
730
|
+
]); // assistant
|
|
731
|
+
stageHybridResponse([]); // now
|
|
732
|
+
// The mocked reranker hands back scores for whatever slugs it's
|
|
733
|
+
// called with. Stage scores for both; the assertion below is that
|
|
734
|
+
// the loser still receives 0 because it's never sent to the
|
|
735
|
+
// reranker — top_k=1 cuts it off.
|
|
736
|
+
rerankState.scores = new Map([
|
|
737
|
+
["winner", 0.5],
|
|
738
|
+
["loser", 0.5],
|
|
739
|
+
]);
|
|
740
|
+
|
|
741
|
+
const config = {
|
|
742
|
+
memory: {
|
|
743
|
+
v2: {
|
|
744
|
+
d: 0.0,
|
|
745
|
+
c_user: 0.5,
|
|
746
|
+
c_assistant: 0.5,
|
|
747
|
+
c_now: 0.0,
|
|
748
|
+
dense_weight: 1.0,
|
|
749
|
+
sparse_weight: 0.0,
|
|
750
|
+
rerank: {
|
|
751
|
+
enabled: true,
|
|
752
|
+
top_k: 1,
|
|
753
|
+
alpha: 0.3,
|
|
754
|
+
model: "test-model",
|
|
755
|
+
},
|
|
756
|
+
},
|
|
757
|
+
},
|
|
758
|
+
} as unknown as AssistantConfig;
|
|
759
|
+
|
|
760
|
+
const out = await computeOwnActivation({
|
|
761
|
+
candidates: new Set(["winner", "loser"]),
|
|
762
|
+
priorState: null,
|
|
763
|
+
userText: "u",
|
|
764
|
+
assistantText: "a",
|
|
765
|
+
nowText: "",
|
|
766
|
+
config,
|
|
767
|
+
});
|
|
768
|
+
|
|
769
|
+
expect(out.breakdown.get("loser")?.simUserRerankBoost).toBe(0);
|
|
770
|
+
expect(out.breakdown.get("loser")?.simAssistantRerankBoost).toBe(0);
|
|
771
|
+
expect(out.breakdown.get("winner")?.simUserRerankBoost).toBeGreaterThan(0);
|
|
772
|
+
expect(
|
|
773
|
+
out.breakdown.get("winner")?.simAssistantRerankBoost,
|
|
774
|
+
).toBeGreaterThan(0);
|
|
775
|
+
// inRerankPool tags pool membership independently of the boost value, so
|
|
776
|
+
// the inspector can keep the rerank rows visible even when the channel
|
|
777
|
+
// max happened to normalise to 0.
|
|
778
|
+
expect(out.breakdown.get("winner")?.inRerankPool).toBe(true);
|
|
779
|
+
expect(out.breakdown.get("loser")?.inRerankPool).toBe(false);
|
|
780
|
+
});
|
|
781
|
+
|
|
782
|
+
test("inRerankPool is false for every slug when rerank is disabled", async () => {
|
|
783
|
+
stageHybridResponse([{ slug: "alice", denseScore: 0.5 }]);
|
|
784
|
+
stageHybridResponse([{ slug: "alice", denseScore: 0.4 }]);
|
|
785
|
+
stageHybridResponse([{ slug: "alice", denseScore: 0.2 }]);
|
|
786
|
+
|
|
787
|
+
// No `rerank` block at all → rerankCfg is undefined and the rerank
|
|
788
|
+
// branch never runs, so no slug is in the pool.
|
|
789
|
+
const out = await computeOwnActivation({
|
|
790
|
+
candidates: new Set(["alice"]),
|
|
791
|
+
priorState: null,
|
|
792
|
+
userText: "u",
|
|
793
|
+
assistantText: "a",
|
|
794
|
+
nowText: "n",
|
|
795
|
+
config: makeConfig(),
|
|
796
|
+
});
|
|
797
|
+
|
|
798
|
+
expect(out.breakdown.get("alice")?.inRerankPool).toBe(false);
|
|
799
|
+
expect(out.breakdown.get("alice")?.simUserRerankBoost).toBe(0);
|
|
800
|
+
expect(out.breakdown.get("alice")?.simAssistantRerankBoost).toBe(0);
|
|
801
|
+
});
|
|
802
|
+
|
|
803
|
+
test("rerank boost is additive on A_o and leaves raw simUser / simAssistant untouched", async () => {
|
|
804
|
+
stageHybridResponse([{ slug: "a", denseScore: 0.5 }]); // user
|
|
805
|
+
stageHybridResponse([{ slug: "a", denseScore: 0.4 }]); // assistant
|
|
806
|
+
stageHybridResponse([]); // now
|
|
807
|
+
rerankState.scores = new Map([["a", 0.8]]);
|
|
808
|
+
|
|
809
|
+
const config = {
|
|
810
|
+
memory: {
|
|
811
|
+
v2: {
|
|
812
|
+
d: 0.0,
|
|
813
|
+
c_user: 0.5,
|
|
814
|
+
c_assistant: 0.5,
|
|
815
|
+
c_now: 0.0,
|
|
816
|
+
dense_weight: 1.0,
|
|
817
|
+
sparse_weight: 0.0,
|
|
818
|
+
rerank: {
|
|
819
|
+
enabled: true,
|
|
820
|
+
top_k: 50,
|
|
821
|
+
alpha: 0.4,
|
|
822
|
+
model: "test-model",
|
|
823
|
+
},
|
|
824
|
+
},
|
|
825
|
+
},
|
|
826
|
+
} as unknown as AssistantConfig;
|
|
827
|
+
|
|
828
|
+
const out = await computeOwnActivation({
|
|
829
|
+
candidates: new Set(["a"]),
|
|
830
|
+
priorState: null,
|
|
831
|
+
userText: "u",
|
|
832
|
+
assistantText: "a",
|
|
833
|
+
nowText: "",
|
|
834
|
+
config,
|
|
835
|
+
});
|
|
836
|
+
|
|
837
|
+
const breakdown = out.breakdown.get("a");
|
|
838
|
+
// Raw fused similarities are reported untouched by rerank.
|
|
839
|
+
expect(breakdown?.simUser).toBeCloseTo(0.5, 6);
|
|
840
|
+
expect(breakdown?.simAssistant).toBeCloseTo(0.4, 6);
|
|
841
|
+
// Both rerank deltas are alpha · r_norm = 0.4 · 1.0 = 0.4 (single
|
|
842
|
+
// candidate normalises to 1.0 in each channel).
|
|
843
|
+
expect(breakdown?.simUserRerankBoost).toBeCloseTo(0.4, 6);
|
|
844
|
+
expect(breakdown?.simAssistantRerankBoost).toBeCloseTo(0.4, 6);
|
|
845
|
+
// Final A_o = c_user·simU + c_assistant·simA + c_user·boostU + c_assistant·boostA
|
|
846
|
+
// = 0.5·0.5 + 0.5·0.4 + 0.5·0.4 + 0.5·0.4 = 0.25+0.20+0.20+0.20 = 0.85
|
|
847
|
+
expect(out.activation.get("a")).toBeCloseTo(0.85, 6);
|
|
848
|
+
});
|
|
557
849
|
});
|
|
558
850
|
|
|
559
851
|
// ---------------------------------------------------------------------------
|
|
@@ -895,48 +1187,26 @@ describe("selectInjections", () => {
|
|
|
895
1187
|
});
|
|
896
1188
|
|
|
897
1189
|
// ---------------------------------------------------------------------------
|
|
898
|
-
//
|
|
1190
|
+
// Skills as concept slugs — the unified pool
|
|
899
1191
|
// ---------------------------------------------------------------------------
|
|
1192
|
+
//
|
|
1193
|
+
// Skills participate in the concept-page pipeline under the slug prefix
|
|
1194
|
+
// `skills/<id>`. There is no longer a dedicated skill activation function;
|
|
1195
|
+
// the only post-unification behavioral assertion worth preserving here is
|
|
1196
|
+
// that a `skills/<id>` slug flows through `computeOwnActivation` exactly
|
|
1197
|
+
// like a concept slug — same formula, same clamp, same breakdown shape.
|
|
1198
|
+
|
|
1199
|
+
describe("skills participate in the unified pipeline", () => {
|
|
1200
|
+
test("computeOwnActivation scores a `skills/<id>` slug like any concept slug", async () => {
|
|
1201
|
+
// Three simBatch responses, one per channel (user/assistant/now), with
|
|
1202
|
+
// a single skill-prefixed slug as the only candidate.
|
|
1203
|
+
stageHybridResponse([{ slug: "skills/example-skill-a", denseScore: 0.5 }]);
|
|
1204
|
+
stageHybridResponse([{ slug: "skills/example-skill-a", denseScore: 0.4 }]);
|
|
1205
|
+
stageHybridResponse([{ slug: "skills/example-skill-a", denseScore: 0.2 }]);
|
|
900
1206
|
|
|
901
|
-
|
|
902
|
-
|
|
903
|
-
|
|
904
|
-
): void {
|
|
905
|
-
state.skillQueryResponses.dense.push({
|
|
906
|
-
points: hits
|
|
907
|
-
.filter((h) => h.denseScore !== undefined)
|
|
908
|
-
.map((h) => ({ score: h.denseScore, payload: { id: h.id } })),
|
|
909
|
-
});
|
|
910
|
-
state.skillQueryResponses.sparse.push({
|
|
911
|
-
points: hits
|
|
912
|
-
.filter((h) => h.sparseScore !== undefined)
|
|
913
|
-
.map((h) => ({ score: h.sparseScore, payload: { id: h.id } })),
|
|
914
|
-
});
|
|
915
|
-
}
|
|
916
|
-
|
|
917
|
-
describe("computeSkillActivation", () => {
|
|
918
|
-
test("empty candidates short-circuits without backend calls", async () => {
|
|
919
|
-
const out = await computeSkillActivation({
|
|
920
|
-
candidates: new Set(),
|
|
921
|
-
userText: "u",
|
|
922
|
-
assistantText: "a",
|
|
923
|
-
nowText: "n",
|
|
924
|
-
config: makeConfig(),
|
|
925
|
-
});
|
|
926
|
-
expect(out.activation.size).toBe(0);
|
|
927
|
-
expect(out.breakdown.size).toBe(0);
|
|
928
|
-
expect(state.embedCalls).toHaveLength(0);
|
|
929
|
-
expect(state.queryCalls).toHaveLength(0);
|
|
930
|
-
});
|
|
931
|
-
|
|
932
|
-
test("applies similarity-only formula with no decay term", async () => {
|
|
933
|
-
// Stage three skill responses — one per `simSkillBatch` call.
|
|
934
|
-
stageSkillHybridResponse([{ id: "example-skill-a", denseScore: 0.5 }]); // simU
|
|
935
|
-
stageSkillHybridResponse([{ id: "example-skill-a", denseScore: 0.4 }]); // simA
|
|
936
|
-
stageSkillHybridResponse([{ id: "example-skill-a", denseScore: 0.2 }]); // simN
|
|
937
|
-
|
|
938
|
-
const out = await computeSkillActivation({
|
|
939
|
-
candidates: new Set(["example-skill-a"]),
|
|
1207
|
+
const out = await computeOwnActivation({
|
|
1208
|
+
candidates: new Set(["skills/example-skill-a"]),
|
|
1209
|
+
priorState: null,
|
|
940
1210
|
userText: "u",
|
|
941
1211
|
assistantText: "a",
|
|
942
1212
|
nowText: "n",
|
|
@@ -947,191 +1217,12 @@ describe("computeSkillActivation", () => {
|
|
|
947
1217
|
c_now: 0.2,
|
|
948
1218
|
}),
|
|
949
1219
|
});
|
|
950
|
-
// No `d · prev` term: 0.3*0.5 + 0.2*0.4 + 0.2*0.2 = 0.15 + 0.08 + 0.04 = 0.27
|
|
951
|
-
expect(out.activation.get("example-skill-a")).toBeCloseTo(0.27, 6);
|
|
952
|
-
});
|
|
953
|
-
|
|
954
|
-
test("output excludes any decay term — d coefficient is unused", async () => {
|
|
955
|
-
// The skill activation formula is `c_user·simU + c_assistant·simA +
|
|
956
|
-
// c_now·simN`. Run with d=0.9 and d=0.0 — if the implementation
|
|
957
|
-
// accidentally included a `d · prev` term, the two would diverge. The
|
|
958
|
-
// function has no priorState parameter, so prev=0; both runs must equal
|
|
959
|
-
// the d-free formula exactly. Stage three sim responses per run.
|
|
960
|
-
const stage = () => {
|
|
961
|
-
stageSkillHybridResponse([{ id: "alpha", denseScore: 0.4 }]);
|
|
962
|
-
stageSkillHybridResponse([{ id: "alpha", denseScore: 0.4 }]);
|
|
963
|
-
stageSkillHybridResponse([{ id: "alpha", denseScore: 0.4 }]);
|
|
964
|
-
};
|
|
965
|
-
const baseConfig = { c_user: 0.3, c_assistant: 0.2, c_now: 0.2 };
|
|
966
|
-
|
|
967
|
-
stage();
|
|
968
|
-
const withHighD = await computeSkillActivation({
|
|
969
|
-
candidates: new Set(["alpha"]),
|
|
970
|
-
userText: "u",
|
|
971
|
-
assistantText: "a",
|
|
972
|
-
nowText: "n",
|
|
973
|
-
config: makeConfig({ ...baseConfig, d: 0.9 }),
|
|
974
|
-
});
|
|
975
|
-
stage();
|
|
976
|
-
const withZeroD = await computeSkillActivation({
|
|
977
|
-
candidates: new Set(["alpha"]),
|
|
978
|
-
userText: "u",
|
|
979
|
-
assistantText: "a",
|
|
980
|
-
nowText: "n",
|
|
981
|
-
config: makeConfig({ ...baseConfig, d: 0.0 }),
|
|
982
|
-
});
|
|
983
|
-
|
|
984
|
-
// Both equal `0.3*0.4 + 0.2*0.4 + 0.2*0.4 = 0.28` — d is ignored.
|
|
985
|
-
expect(withHighD.activation.get("alpha")).toBeCloseTo(0.28, 6);
|
|
986
|
-
expect(withZeroD.activation.get("alpha")).toBeCloseTo(0.28, 6);
|
|
987
|
-
});
|
|
988
|
-
|
|
989
|
-
test("clamps over-1.0 results down to [0, 1]", async () => {
|
|
990
|
-
stageSkillHybridResponse([{ id: "loud-skill", denseScore: 1.0 }]); // simU
|
|
991
|
-
stageSkillHybridResponse([{ id: "loud-skill", denseScore: 1.0 }]); // simA
|
|
992
|
-
stageSkillHybridResponse([{ id: "loud-skill", denseScore: 1.0 }]); // simN
|
|
993
|
-
|
|
994
|
-
// Coefficients intentionally sum to > 1 so the unclamped result
|
|
995
|
-
// overshoots — the implementation must still produce <= 1.0.
|
|
996
|
-
const out = await computeSkillActivation({
|
|
997
|
-
candidates: new Set(["loud-skill"]),
|
|
998
|
-
userText: "u",
|
|
999
|
-
assistantText: "a",
|
|
1000
|
-
nowText: "n",
|
|
1001
|
-
config: makeConfig({
|
|
1002
|
-
c_user: 0.5,
|
|
1003
|
-
c_assistant: 0.5,
|
|
1004
|
-
c_now: 0.5,
|
|
1005
|
-
}),
|
|
1006
|
-
});
|
|
1007
|
-
expect(out.activation.get("loud-skill")).toBe(1);
|
|
1008
|
-
});
|
|
1009
|
-
|
|
1010
|
-
test("candidate with no sim hits resolves to 0", async () => {
|
|
1011
|
-
stageSkillHybridResponse([]);
|
|
1012
|
-
stageSkillHybridResponse([]);
|
|
1013
|
-
stageSkillHybridResponse([]);
|
|
1014
|
-
|
|
1015
|
-
const out = await computeSkillActivation({
|
|
1016
|
-
candidates: new Set(["ghost-skill"]),
|
|
1017
|
-
userText: "u",
|
|
1018
|
-
assistantText: "a",
|
|
1019
|
-
nowText: "n",
|
|
1020
|
-
config: makeConfig(),
|
|
1021
|
-
});
|
|
1022
|
-
expect(out.activation.get("ghost-skill")).toBe(0);
|
|
1023
|
-
});
|
|
1024
|
-
|
|
1025
|
-
test("breakdown captures the raw sims for each candidate", async () => {
|
|
1026
|
-
stageSkillHybridResponse([{ id: "example-skill-a", denseScore: 0.5 }]); // simU
|
|
1027
|
-
stageSkillHybridResponse([{ id: "example-skill-a", denseScore: 0.4 }]); // simA
|
|
1028
|
-
stageSkillHybridResponse([{ id: "example-skill-a", denseScore: 0.2 }]); // simN
|
|
1029
|
-
|
|
1030
|
-
const out = await computeSkillActivation({
|
|
1031
|
-
candidates: new Set(["example-skill-a"]),
|
|
1032
|
-
userText: "u",
|
|
1033
|
-
assistantText: "a",
|
|
1034
|
-
nowText: "n",
|
|
1035
|
-
config: makeConfig({
|
|
1036
|
-
c_user: 0.3,
|
|
1037
|
-
c_assistant: 0.2,
|
|
1038
|
-
c_now: 0.2,
|
|
1039
|
-
}),
|
|
1040
|
-
});
|
|
1041
|
-
const breakdown = out.breakdown.get("example-skill-a");
|
|
1042
|
-
expect(breakdown).toBeDefined();
|
|
1043
|
-
expect(breakdown?.simUser).toBeCloseTo(0.5, 6);
|
|
1044
|
-
expect(breakdown?.simAssistant).toBeCloseTo(0.4, 6);
|
|
1045
|
-
expect(breakdown?.simNow).toBeCloseTo(0.2, 6);
|
|
1046
|
-
});
|
|
1047
|
-
|
|
1048
|
-
test("uses the dedicated skills collection and never queries concept pages", async () => {
|
|
1049
|
-
stageSkillHybridResponse([{ id: "example-skill-a", denseScore: 0.5 }]);
|
|
1050
|
-
stageSkillHybridResponse([{ id: "example-skill-a", denseScore: 0.5 }]);
|
|
1051
|
-
stageSkillHybridResponse([{ id: "example-skill-a", denseScore: 0.5 }]);
|
|
1052
|
-
|
|
1053
|
-
await computeSkillActivation({
|
|
1054
|
-
candidates: new Set(["example-skill-a"]),
|
|
1055
|
-
userText: "u",
|
|
1056
|
-
assistantText: "a",
|
|
1057
|
-
nowText: "n",
|
|
1058
|
-
config: makeConfig(),
|
|
1059
|
-
});
|
|
1060
|
-
|
|
1061
|
-
// Three simSkillBatch calls × 2 channels = 6 total queries, all against
|
|
1062
|
-
// the skills collection. No spread → no extra calls beyond these.
|
|
1063
|
-
expect(state.queryCalls).toHaveLength(6);
|
|
1064
|
-
for (const call of state.queryCalls) {
|
|
1065
|
-
expect(call.collection).toBe("memory_v2_skills");
|
|
1066
|
-
}
|
|
1067
|
-
});
|
|
1068
|
-
});
|
|
1069
|
-
|
|
1070
|
-
// ---------------------------------------------------------------------------
|
|
1071
|
-
// selectSkillInjections
|
|
1072
|
-
// ---------------------------------------------------------------------------
|
|
1073
|
-
|
|
1074
|
-
describe("selectSkillInjections", () => {
|
|
1075
|
-
test("returns empty when activation is empty", () => {
|
|
1076
|
-
const out = selectSkillInjections({ A: new Map(), topK: 5 });
|
|
1077
|
-
expect(out).toEqual({ topNow: [] });
|
|
1078
|
-
});
|
|
1079
|
-
|
|
1080
|
-
test("returns empty when topK is 0", () => {
|
|
1081
|
-
const out = selectSkillInjections({
|
|
1082
|
-
A: new Map([
|
|
1083
|
-
["example-skill-a", 0.5],
|
|
1084
|
-
["example-skill-b", 0.4],
|
|
1085
|
-
]),
|
|
1086
|
-
topK: 0,
|
|
1087
|
-
});
|
|
1088
|
-
expect(out).toEqual({ topNow: [] });
|
|
1089
|
-
});
|
|
1090
|
-
|
|
1091
|
-
test("ranks by activation descending and trims to topK", () => {
|
|
1092
|
-
const out = selectSkillInjections({
|
|
1093
|
-
A: new Map([
|
|
1094
|
-
["example-skill-a", 0.1],
|
|
1095
|
-
["example-skill-b", 0.9],
|
|
1096
|
-
["example-skill-c", 0.5],
|
|
1097
|
-
["example-skill-d", 0.3],
|
|
1098
|
-
]),
|
|
1099
|
-
topK: 2,
|
|
1100
|
-
});
|
|
1101
|
-
expect(out.topNow).toEqual(["example-skill-b", "example-skill-c"]);
|
|
1102
|
-
});
|
|
1103
1220
|
|
|
1104
|
-
|
|
1105
|
-
//
|
|
1106
|
-
|
|
1107
|
-
|
|
1108
|
-
|
|
1109
|
-
|
|
1110
|
-
["example-skill-b", 0.5],
|
|
1111
|
-
]);
|
|
1112
|
-
const turn1 = selectSkillInjections({ A, topK: 5 });
|
|
1113
|
-
const turn2 = selectSkillInjections({ A, topK: 5 });
|
|
1114
|
-
expect(turn1.topNow).toEqual(["example-skill-a", "example-skill-b"]);
|
|
1115
|
-
expect(turn2.topNow).toEqual(turn1.topNow);
|
|
1116
|
-
});
|
|
1117
|
-
|
|
1118
|
-
test("breaks ties by id ascending for deterministic output", () => {
|
|
1119
|
-
const out = selectSkillInjections({
|
|
1120
|
-
A: new Map([
|
|
1121
|
-
["zeta-skill", 0.5],
|
|
1122
|
-
["example-skill-a", 0.5],
|
|
1123
|
-
["mike-skill", 0.5],
|
|
1124
|
-
]),
|
|
1125
|
-
topK: 5,
|
|
1126
|
-
});
|
|
1127
|
-
expect(out.topNow).toEqual(["example-skill-a", "mike-skill", "zeta-skill"]);
|
|
1128
|
-
});
|
|
1129
|
-
|
|
1130
|
-
test("topK clamps to the available activation entries", () => {
|
|
1131
|
-
const out = selectSkillInjections({
|
|
1132
|
-
A: new Map([["only-skill", 0.7]]),
|
|
1133
|
-
topK: 100,
|
|
1134
|
-
});
|
|
1135
|
-
expect(out.topNow).toEqual(["only-skill"]);
|
|
1221
|
+
// No prior state → priorContribution = 0.
|
|
1222
|
+
// 0.3*0.5 + 0.2*0.4 + 0.2*0.2 = 0.15 + 0.08 + 0.04 = 0.27
|
|
1223
|
+
expect(out.activation.get("skills/example-skill-a")).toBeCloseTo(0.27, 6);
|
|
1224
|
+
expect(out.breakdown.get("skills/example-skill-a")?.priorContribution).toBe(
|
|
1225
|
+
0,
|
|
1226
|
+
);
|
|
1136
1227
|
});
|
|
1137
1228
|
});
|