@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
package/src/memory/v2/sim.ts
CHANGED
|
@@ -30,7 +30,6 @@ import { applyCorrectionIfCalibrated } from "../anisotropy.js";
|
|
|
30
30
|
import { embedWithBackend } from "../embedding-backend.js";
|
|
31
31
|
import { clampUnitInterval } from "../validation.js";
|
|
32
32
|
import { hybridQueryConceptPages } from "./qdrant.js";
|
|
33
|
-
import { hybridQuerySkills } from "./skill-qdrant.js";
|
|
34
33
|
import { generateBm25QueryEmbedding } from "./sparse-bm25.js";
|
|
35
34
|
|
|
36
35
|
/**
|
|
@@ -121,14 +120,18 @@ export function effectiveWeights(
|
|
|
121
120
|
* sparse via the in-process TF-IDF encoder).
|
|
122
121
|
* 2. Run server-side dense + sparse queries against the v2 concept-page
|
|
123
122
|
* Qdrant collection, restricted to `candidateSlugs` so we don't waste
|
|
124
|
-
* query bandwidth on unrelated pages.
|
|
125
|
-
*
|
|
126
|
-
*
|
|
127
|
-
*
|
|
128
|
-
*
|
|
123
|
+
* query bandwidth on unrelated pages. The query hits four channels per
|
|
124
|
+
* page: body dense + body sparse, and (for pages that have a summary
|
|
125
|
+
* embedded) summary dense + summary sparse.
|
|
126
|
+
* 3. Fuse: per slug, score = `max(fused(body), fused(summary))`. Each
|
|
127
|
+
* half is `clamp01(dense_weight · denseCosine + sparse_weight ·
|
|
128
|
+
* normalizedSparse)` with sparse normalized by the per-batch maximum.
|
|
129
|
+
* Pages without a summary embedding fall back to body-only fusion —
|
|
130
|
+
* the summary half is undefined and the max collapses to the body
|
|
131
|
+
* score.
|
|
129
132
|
*
|
|
130
133
|
* Returns a `Map<slug, score>` containing only the candidate slugs that hit
|
|
131
|
-
* in at least one channel. Slugs in `candidateSlugs` that miss
|
|
134
|
+
* in at least one channel. Slugs in `candidateSlugs` that miss every channel
|
|
132
135
|
* are absent from the map; callers should treat absence as score = 0 (the
|
|
133
136
|
* activation pipeline does this implicitly when reading back A_o).
|
|
134
137
|
*
|
|
@@ -147,6 +150,7 @@ export async function simBatch(
|
|
|
147
150
|
text: string,
|
|
148
151
|
candidateSlugs: readonly string[],
|
|
149
152
|
config: AssistantConfig,
|
|
153
|
+
options?: { signal?: AbortSignal },
|
|
150
154
|
): Promise<Map<string, number>> {
|
|
151
155
|
if (candidateSlugs.length === 0) {
|
|
152
156
|
return new Map();
|
|
@@ -158,12 +162,16 @@ export async function simBatch(
|
|
|
158
162
|
// Sparse uses BM25: the query side encodes binary occurrences per token,
|
|
159
163
|
// and the stored doc vectors carry the IDF · TF-saturated weights — Qdrant
|
|
160
164
|
// dot product then yields the BM25 score directly.
|
|
161
|
-
|
|
165
|
+
throwIfAborted(options?.signal);
|
|
166
|
+
const denseResult = await embedWithBackend(config, [text], {
|
|
167
|
+
signal: options?.signal,
|
|
168
|
+
});
|
|
162
169
|
const denseVector = await applyCorrectionIfCalibrated(
|
|
163
170
|
denseResult.vectors[0],
|
|
164
171
|
denseResult.provider,
|
|
165
172
|
denseResult.model,
|
|
166
173
|
);
|
|
174
|
+
throwIfAborted(options?.signal);
|
|
167
175
|
const sparseVector = generateBm25QueryEmbedding(text);
|
|
168
176
|
|
|
169
177
|
const hits = await hybridQueryConceptPages(
|
|
@@ -177,140 +185,99 @@ export async function simBatch(
|
|
|
177
185
|
return new Map();
|
|
178
186
|
}
|
|
179
187
|
|
|
180
|
-
|
|
188
|
+
// Compute per-batch sparse maxima independently for the body and summary
|
|
189
|
+
// channels so each side normalizes against its own scale. Mixing the two
|
|
190
|
+
// — e.g. dividing every sparse score by the larger of the two maxima —
|
|
191
|
+
// would punish whichever channel happened to have lower-magnitude scores
|
|
192
|
+
// even when its hits were the best matches available.
|
|
193
|
+
const maxBodySparse = computeMaxSparse(hits, (h) => h.sparseScore);
|
|
194
|
+
const maxSummarySparse = computeMaxSparse(hits, (h) => h.summarySparseScore);
|
|
181
195
|
const { dense_weight: baseDense, sparse_weight: baseSparse } =
|
|
182
196
|
config.memory.v2;
|
|
183
|
-
const { dense:
|
|
184
|
-
hits,
|
|
185
|
-
|
|
197
|
+
const { dense: bodyDenseWeight, sparse: bodySparseWeight } = effectiveWeights(
|
|
198
|
+
hits.map((h) => ({ sparseScore: h.sparseScore })),
|
|
199
|
+
maxBodySparse,
|
|
186
200
|
baseDense,
|
|
187
201
|
baseSparse,
|
|
188
202
|
config,
|
|
189
203
|
);
|
|
204
|
+
const { dense: summaryDenseWeight, sparse: summarySparseWeight } =
|
|
205
|
+
effectiveWeights(
|
|
206
|
+
hits.map((h) => ({ sparseScore: h.summarySparseScore })),
|
|
207
|
+
maxSummarySparse,
|
|
208
|
+
baseDense,
|
|
209
|
+
baseSparse,
|
|
210
|
+
config,
|
|
211
|
+
);
|
|
190
212
|
|
|
191
213
|
const scores = new Map<string, number>();
|
|
192
214
|
for (const hit of hits) {
|
|
193
|
-
|
|
215
|
+
const bodyScore = fuseHalf(
|
|
216
|
+
hit.denseScore,
|
|
217
|
+
hit.sparseScore,
|
|
218
|
+
maxBodySparse,
|
|
219
|
+
bodyDenseWeight,
|
|
220
|
+
bodySparseWeight,
|
|
221
|
+
);
|
|
222
|
+
const summaryScore = fuseHalf(
|
|
223
|
+
hit.summaryDenseScore,
|
|
224
|
+
hit.summarySparseScore,
|
|
225
|
+
maxSummarySparse,
|
|
226
|
+
summaryDenseWeight,
|
|
227
|
+
summarySparseWeight,
|
|
228
|
+
);
|
|
229
|
+
// Pages without a summary embedding return undefined for both summary
|
|
230
|
+
// channels; their `summaryScore` falls back to the body score so the
|
|
231
|
+
// max collapses cleanly to body-only behavior.
|
|
232
|
+
const score = Math.max(bodyScore ?? 0, summaryScore ?? bodyScore ?? 0);
|
|
233
|
+
scores.set(hit.slug, score);
|
|
194
234
|
}
|
|
235
|
+
|
|
195
236
|
return scores;
|
|
196
237
|
}
|
|
197
238
|
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
* dedicated `memory_v2_skills` Qdrant collection via `hybridQuerySkills`.
|
|
202
|
-
*
|
|
203
|
-
* Differences from `simBatch`:
|
|
204
|
-
* - Keys are skill `id` values (not concept-page slugs).
|
|
205
|
-
* - Restricts the query to the caller's candidate ids server-side via
|
|
206
|
-
* `hybridQuerySkills`'s `restrictToIds` parameter. Without this, when the
|
|
207
|
-
* skills collection has more skills than `ids.length`, Qdrant would
|
|
208
|
-
* return its global top-K and candidate ids absent from that top-K would
|
|
209
|
-
* silently score 0 — corrupting the activation calculation.
|
|
210
|
-
*
|
|
211
|
-
* Returns a `Map<id, score>` of fused scores in [0, 1]. Ids that did not hit
|
|
212
|
-
* either channel are absent from the map.
|
|
213
|
-
*
|
|
214
|
-
* Edge cases:
|
|
215
|
-
* - Empty `ids` → returns an empty map without touching Qdrant or the
|
|
216
|
-
* embedding backend.
|
|
217
|
-
* - Empty / whitespace-only `text` → returns an empty map without touching
|
|
218
|
-
* Qdrant or the embedding backend. Same rationale as {@link simBatch}:
|
|
219
|
-
* Gemini rejects empty content with HTTP 400, so the activation pipeline
|
|
220
|
-
* would otherwise fail on turn 1 (where the assistant-text channel is
|
|
221
|
-
* `""`). Treating the channel's contribution as 0 matches a no-hit
|
|
222
|
-
* query.
|
|
223
|
-
*/
|
|
224
|
-
export async function simSkillBatch(
|
|
225
|
-
text: string,
|
|
226
|
-
ids: readonly string[],
|
|
227
|
-
config: AssistantConfig,
|
|
228
|
-
): Promise<Map<string, number>> {
|
|
229
|
-
if (ids.length === 0) {
|
|
230
|
-
return new Map();
|
|
231
|
-
}
|
|
232
|
-
if (text.trim().length === 0) {
|
|
233
|
-
return new Map();
|
|
239
|
+
function throwIfAborted(signal: AbortSignal | undefined): void {
|
|
240
|
+
if (signal?.aborted) {
|
|
241
|
+
throw new DOMException("Aborted", "AbortError");
|
|
234
242
|
}
|
|
235
|
-
|
|
236
|
-
const denseResult = await embedWithBackend(config, [text]);
|
|
237
|
-
const denseVector = await applyCorrectionIfCalibrated(
|
|
238
|
-
denseResult.vectors[0],
|
|
239
|
-
denseResult.provider,
|
|
240
|
-
denseResult.model,
|
|
241
|
-
);
|
|
242
|
-
const sparseVector = generateBm25QueryEmbedding(text);
|
|
243
|
-
|
|
244
|
-
const hits = await hybridQuerySkills(
|
|
245
|
-
denseVector,
|
|
246
|
-
sparseVector,
|
|
247
|
-
ids.length,
|
|
248
|
-
ids,
|
|
249
|
-
);
|
|
250
|
-
|
|
251
|
-
if (hits.length === 0) {
|
|
252
|
-
return new Map();
|
|
253
|
-
}
|
|
254
|
-
|
|
255
|
-
// Defensive post-filter — `hybridQuerySkills` restricts server-side, so
|
|
256
|
-
// every hit should already be in `ids`, but keep this guard so a buggy
|
|
257
|
-
// payload (e.g. a missing/typoed id index) can't silently inject
|
|
258
|
-
// out-of-set ids into the score map.
|
|
259
|
-
const idSet = new Set(ids);
|
|
260
|
-
const filtered = hits.filter((h) => idSet.has(h.id));
|
|
261
|
-
if (filtered.length === 0) {
|
|
262
|
-
return new Map();
|
|
263
|
-
}
|
|
264
|
-
|
|
265
|
-
const maxSparse = computeMaxSparse(filtered);
|
|
266
|
-
const { dense_weight: baseDense, sparse_weight: baseSparse } =
|
|
267
|
-
config.memory.v2;
|
|
268
|
-
const { dense: denseWeight, sparse: sparseWeight } = effectiveWeights(
|
|
269
|
-
filtered,
|
|
270
|
-
maxSparse,
|
|
271
|
-
baseDense,
|
|
272
|
-
baseSparse,
|
|
273
|
-
config,
|
|
274
|
-
);
|
|
275
|
-
|
|
276
|
-
const scores = new Map<string, number>();
|
|
277
|
-
for (const hit of filtered) {
|
|
278
|
-
scores.set(hit.id, fuseHit(hit, maxSparse, denseWeight, sparseWeight));
|
|
279
|
-
}
|
|
280
|
-
return scores;
|
|
281
243
|
}
|
|
282
244
|
|
|
283
245
|
/**
|
|
284
|
-
* Per-batch sparse-score maximum used for normalization.
|
|
285
|
-
*
|
|
246
|
+
* Per-batch sparse-score maximum used for normalization. The accessor picks
|
|
247
|
+
* which sparse channel to scan — `sparseScore` for the body channel,
|
|
248
|
+
* `summarySparseScore` for the summary channel. Hits missing from the
|
|
249
|
+
* channel contribute 0 (handled by the `undefined` guard).
|
|
286
250
|
*/
|
|
287
|
-
function computeMaxSparse(
|
|
288
|
-
hits: ReadonlyArray<
|
|
251
|
+
function computeMaxSparse<T>(
|
|
252
|
+
hits: ReadonlyArray<T>,
|
|
253
|
+
accessor: (hit: T) => number | undefined,
|
|
289
254
|
): number {
|
|
290
255
|
let max = 0;
|
|
291
256
|
for (const hit of hits) {
|
|
292
|
-
|
|
293
|
-
|
|
257
|
+
const value = accessor(hit);
|
|
258
|
+
if (value !== undefined && value > max) {
|
|
259
|
+
max = value;
|
|
294
260
|
}
|
|
295
261
|
}
|
|
296
262
|
return max;
|
|
297
263
|
}
|
|
298
264
|
|
|
299
265
|
/**
|
|
300
|
-
* Fuse a
|
|
266
|
+
* Fuse one half of a hit (body or summary) into a normalized [0, 1] score
|
|
301
267
|
* via `clamp01(dense_weight · dense + sparse_weight · sparse/maxSparse)`.
|
|
302
|
-
*
|
|
268
|
+
* Returns `undefined` when neither channel hit — a signal the half had no
|
|
269
|
+
* match at all, so the caller can fall back to the other half cleanly.
|
|
303
270
|
*/
|
|
304
|
-
function
|
|
305
|
-
|
|
271
|
+
function fuseHalf(
|
|
272
|
+
denseScore: number | undefined,
|
|
273
|
+
sparseScore: number | undefined,
|
|
306
274
|
maxSparse: number,
|
|
307
275
|
denseWeight: number,
|
|
308
276
|
sparseWeight: number,
|
|
309
|
-
): number {
|
|
310
|
-
|
|
277
|
+
): number | undefined {
|
|
278
|
+
if (denseScore === undefined && sparseScore === undefined) return undefined;
|
|
279
|
+
const dense = denseScore ?? 0;
|
|
311
280
|
const sparseNormalized =
|
|
312
|
-
|
|
313
|
-
? hit.sparseScore / maxSparse
|
|
314
|
-
: 0;
|
|
281
|
+
sparseScore !== undefined && maxSparse > 0 ? sparseScore / maxSparse : 0;
|
|
315
282
|
return clamp01(denseWeight * dense + sparseWeight * sparseNormalized);
|
|
316
283
|
}
|
|
@@ -2,9 +2,10 @@ import { getConfig } from "../../config/loader.js";
|
|
|
2
2
|
import type { SkillCapabilityInput } from "../../skills/skill-memory.js";
|
|
3
3
|
|
|
4
4
|
/**
|
|
5
|
-
* Render the prose-style capability statement embedded into the
|
|
6
|
-
* `
|
|
7
|
-
* `### Skills You Can Use`. Capped at 500 chars to
|
|
5
|
+
* Render the prose-style capability statement embedded into the unified
|
|
6
|
+
* `memory_v2_concept_pages` Qdrant collection (under the `skills/<id>` slug
|
|
7
|
+
* prefix) and rendered in `### Skills You Can Use`. Capped at 500 chars to
|
|
8
|
+
* match v1's behavior.
|
|
8
9
|
*/
|
|
9
10
|
export function buildSkillContent(input: SkillCapabilityInput): string {
|
|
10
11
|
let content = `The "${input.displayName}" skill (${input.id}) is available. ${input.description}.`;
|
|
@@ -2,18 +2,22 @@
|
|
|
2
2
|
// Memory v2 — Skill catalog → embedded skill entries
|
|
3
3
|
// ---------------------------------------------------------------------------
|
|
4
4
|
//
|
|
5
|
-
//
|
|
6
|
-
//
|
|
7
|
-
//
|
|
8
|
-
//
|
|
9
|
-
//
|
|
10
|
-
// catalog state. Including uninstalled catalog skills ensures their activation
|
|
11
|
-
// hints are discoverable by intent so the model can auto-install them.
|
|
5
|
+
// Enumerate the enabled-skill catalog AND uninstalled catalog skills, render
|
|
6
|
+
// each skill's prose statement via `buildSkillContent`, embed dense + sparse,
|
|
7
|
+
// and upsert into `memory_v2_concept_pages` under the slug `skills/<id>`.
|
|
8
|
+
// Including uninstalled catalog skills ensures their activation hints are
|
|
9
|
+
// discoverable by intent so the model can auto-install them.
|
|
12
10
|
//
|
|
13
|
-
//
|
|
14
|
-
//
|
|
15
|
-
//
|
|
16
|
-
//
|
|
11
|
+
// Skills share the concept-page collection rather than living in a dedicated
|
|
12
|
+
// one so the per-turn activation pipeline scores them against the same
|
|
13
|
+
// candidate ANN as concept pages, with the same decay and spread machinery.
|
|
14
|
+
// The render path branches on the `skills/` slug prefix to surface them as
|
|
15
|
+
// the `### Skills You Can Use` subsection.
|
|
16
|
+
//
|
|
17
|
+
// Skill entries are kept in a small in-process cache so the render path can
|
|
18
|
+
// fetch a `SkillEntry` synchronously by id without round-tripping to Qdrant.
|
|
19
|
+
// The cache is replaced atomically at the end of a successful seed run; on
|
|
20
|
+
// error the prior cache stays intact (skills are best-effort).
|
|
17
21
|
|
|
18
22
|
import { isAssistantFeatureFlagEnabled } from "../../config/assistant-feature-flags.js";
|
|
19
23
|
import { getConfig } from "../../config/loader.js";
|
|
@@ -30,15 +34,31 @@ import {
|
|
|
30
34
|
embedWithBackend,
|
|
31
35
|
generateSparseEmbedding,
|
|
32
36
|
} from "../embedding-backend.js";
|
|
37
|
+
import {
|
|
38
|
+
pruneSlugsWithPrefixExcept,
|
|
39
|
+
upsertConceptPageEmbedding,
|
|
40
|
+
} from "./qdrant.js";
|
|
33
41
|
import {
|
|
34
42
|
augmentMcpSetupDescription,
|
|
35
43
|
buildSkillContent,
|
|
36
44
|
} from "./skill-content.js";
|
|
37
|
-
import { pruneSkillsExcept, upsertSkillEmbedding } from "./skill-qdrant.js";
|
|
38
45
|
import type { SkillEntry } from "./types.js";
|
|
39
46
|
|
|
40
47
|
const log = getLogger("memory-v2-skill-store");
|
|
41
48
|
|
|
49
|
+
/**
|
|
50
|
+
* Slug prefix under which skill embeddings are indexed in
|
|
51
|
+
* `memory_v2_concept_pages`. Concept-page slugs must match
|
|
52
|
+
* `[a-z0-9][a-z0-9-]*(/...)*`, and `skills` matches that pattern, so the
|
|
53
|
+
* prefix coexists with hand-authored concept pages without escape work.
|
|
54
|
+
*/
|
|
55
|
+
export const SKILL_SLUG_PREFIX = "skills/";
|
|
56
|
+
|
|
57
|
+
/** Compose the unified-collection slug for a skill id. */
|
|
58
|
+
export function skillSlugFor(id: string): string {
|
|
59
|
+
return `${SKILL_SLUG_PREFIX}${id}`;
|
|
60
|
+
}
|
|
61
|
+
|
|
42
62
|
/**
|
|
43
63
|
* Module-level cache of rendered skill entries keyed by skill id. `null` until
|
|
44
64
|
* the first successful seed run completes; replaced atomically on each
|
|
@@ -47,30 +67,27 @@ const log = getLogger("memory-v2-skill-store");
|
|
|
47
67
|
let entries: Map<string, SkillEntry> | null = null;
|
|
48
68
|
|
|
49
69
|
/**
|
|
50
|
-
* Seed (or re-seed)
|
|
51
|
-
*
|
|
52
|
-
*
|
|
70
|
+
* Seed (or re-seed) skill embeddings into the unified concept-page collection.
|
|
71
|
+
* Idempotent: safe to call repeatedly. Best-effort: never throws — any
|
|
72
|
+
* failure leaves the prior `entries` cache in place and logs a warning.
|
|
53
73
|
*
|
|
54
74
|
* Steps:
|
|
55
|
-
* 1. Enumerate the local skill catalog and resolve each skill's enabled
|
|
56
|
-
* (`resolveSkillStates`).
|
|
57
|
-
* 2. Build a `
|
|
58
|
-
*
|
|
59
|
-
*
|
|
75
|
+
* 1. Enumerate the local skill catalog and resolve each skill's enabled
|
|
76
|
+
* state (`resolveSkillStates`).
|
|
77
|
+
* 2. Build a `SkillEntry` per enabled skill, applying the mcp-setup
|
|
78
|
+
* augmentation and the prose-style content render (`buildSkillContent`,
|
|
79
|
+
* capped at 500 chars).
|
|
60
80
|
* 3. Defense-in-depth feature-flag filter: drop any skill whose declared
|
|
61
|
-
* `metadata.vellum.feature-flag` is currently disabled.
|
|
62
|
-
*
|
|
63
|
-
*
|
|
64
|
-
*
|
|
65
|
-
* 3b. Fetch the full remote catalog and seed any uninstalled skills so their
|
|
66
|
-
* activation hints are discoverable by semantic search. Best-effort: if
|
|
67
|
-
* the catalog fetch fails, only installed skills are seeded.
|
|
81
|
+
* `metadata.vellum.feature-flag` is currently disabled.
|
|
82
|
+
* 3b. Fetch the full remote catalog and seed any uninstalled skills so
|
|
83
|
+
* their activation hints are discoverable by semantic search. Best-effort:
|
|
84
|
+
* if the catalog fetch fails, only installed skills are seeded.
|
|
68
85
|
* 4. Embed all `content` strings in a single dense `embedWithBackend` call,
|
|
69
86
|
* and a per-skill synchronous `generateSparseEmbedding`.
|
|
70
|
-
* 5. Upsert one Qdrant point per skill via `
|
|
71
|
-
* deterministically on id
|
|
72
|
-
* 6. Call `
|
|
73
|
-
* points from prior catalog state (e.g. uninstalled skills).
|
|
87
|
+
* 5. Upsert one Qdrant point per skill via `upsertConceptPageEmbedding`
|
|
88
|
+
* keyed deterministically on slug `skills/<id>`.
|
|
89
|
+
* 6. Call `pruneSlugsWithPrefixExcept(SKILL_SLUG_PREFIX, ...)` to drop any
|
|
90
|
+
* stale points from prior catalog state (e.g. uninstalled skills).
|
|
74
91
|
* 7. Replace the module-level `entries` cache with the freshly built map.
|
|
75
92
|
*/
|
|
76
93
|
export async function seedV2SkillEntries(): Promise<void> {
|
|
@@ -83,8 +100,7 @@ export async function seedV2SkillEntries(): Promise<void> {
|
|
|
83
100
|
// Track every locally-installed skill id (regardless of enabled/disabled
|
|
84
101
|
// state) so the catalog-seeding loop below treats them all as "installed"
|
|
85
102
|
// and never re-seeds a disabled skill from `getCatalog()` as if it were
|
|
86
|
-
// uninstalled.
|
|
87
|
-
// keys off `loadSkillCatalog()` (the installed set) for the same reason.
|
|
103
|
+
// uninstalled.
|
|
88
104
|
const installedIds = new Set<string>(catalog.map((s) => s.id));
|
|
89
105
|
|
|
90
106
|
// Build the input list, applying the mcp-setup description augmentation
|
|
@@ -100,8 +116,8 @@ export async function seedV2SkillEntries(): Promise<void> {
|
|
|
100
116
|
}
|
|
101
117
|
|
|
102
118
|
// Seed uninstalled catalog skills so their activation hints are
|
|
103
|
-
// discoverable by intent
|
|
104
|
-
//
|
|
119
|
+
// discoverable by intent. Track whether the catalog was available so we
|
|
120
|
+
// can guard pruning below.
|
|
105
121
|
let catalogAvailable = false;
|
|
106
122
|
try {
|
|
107
123
|
const fullCatalog = await getCatalog();
|
|
@@ -135,24 +151,29 @@ export async function seedV2SkillEntries(): Promise<void> {
|
|
|
135
151
|
|
|
136
152
|
const now = Date.now();
|
|
137
153
|
const nextEntries = new Map<string, SkillEntry>();
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
154
|
+
await Promise.all(
|
|
155
|
+
seeds.map((seed, i) =>
|
|
156
|
+
upsertConceptPageEmbedding({
|
|
157
|
+
slug: skillSlugFor(seed.id),
|
|
158
|
+
dense: denseVectors[i],
|
|
159
|
+
sparse: generateSparseEmbedding(seed.content),
|
|
160
|
+
updatedAt: now,
|
|
161
|
+
}),
|
|
162
|
+
),
|
|
163
|
+
);
|
|
164
|
+
for (const seed of seeds) {
|
|
146
165
|
nextEntries.set(seed.id, seed);
|
|
147
166
|
}
|
|
148
167
|
|
|
149
|
-
// Prune stale
|
|
150
|
-
// network failure or cold cache), we cannot enumerate which
|
|
151
|
-
// catalog skills should exist, so skip pruning entirely to
|
|
152
|
-
// aggressively removing previously-seeded catalog skill embeddings.
|
|
153
|
-
// Mirrors v1's safeguard in capability-seed.ts (lines 124–143).
|
|
168
|
+
// Prune stale skill slugs. When the catalog is unavailable (empty array
|
|
169
|
+
// from network failure or cold cache), we cannot enumerate which
|
|
170
|
+
// uninstalled catalog skills should exist, so skip pruning entirely to
|
|
171
|
+
// avoid aggressively removing previously-seeded catalog skill embeddings.
|
|
154
172
|
if (catalogAvailable) {
|
|
155
|
-
await
|
|
173
|
+
await pruneSlugsWithPrefixExcept(
|
|
174
|
+
SKILL_SLUG_PREFIX,
|
|
175
|
+
seeds.map((s) => s.id),
|
|
176
|
+
);
|
|
156
177
|
} else {
|
|
157
178
|
log.info(
|
|
158
179
|
"Catalog unavailable — skipping skill pruning to preserve prior catalog embeddings",
|
|
@@ -169,20 +190,22 @@ export async function seedV2SkillEntries(): Promise<void> {
|
|
|
169
190
|
/**
|
|
170
191
|
* Synchronous lookup of a previously-seeded `SkillEntry` by skill id. Returns
|
|
171
192
|
* `null` when the cache has not yet been populated, when the id is unknown,
|
|
172
|
-
* or when a prior seed run dropped the id (e.g. the skill was disabled).
|
|
173
|
-
*
|
|
193
|
+
* or when a prior seed run dropped the id (e.g. the skill was disabled).
|
|
194
|
+
*
|
|
195
|
+
* Accepts either a bare skill id (`example-skill`) or its unified-collection
|
|
196
|
+
* slug (`skills/example-skill`) so render-side callers can pass through what
|
|
197
|
+
* they have without a manual prefix strip.
|
|
174
198
|
*/
|
|
175
|
-
export function getSkillCapability(
|
|
199
|
+
export function getSkillCapability(idOrSlug: string): SkillEntry | null {
|
|
200
|
+
const id = idOrSlug.startsWith(SKILL_SLUG_PREFIX)
|
|
201
|
+
? idOrSlug.slice(SKILL_SLUG_PREFIX.length)
|
|
202
|
+
: idOrSlug;
|
|
176
203
|
return entries?.get(id) ?? null;
|
|
177
204
|
}
|
|
178
205
|
|
|
179
|
-
/**
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
* run completes.
|
|
183
|
-
*/
|
|
184
|
-
export function getAllSkillIds(): string[] {
|
|
185
|
-
return entries ? [...entries.keys()] : [];
|
|
206
|
+
/** True iff the slug refers to a skill entry in the unified collection. */
|
|
207
|
+
export function isSkillSlug(slug: string): boolean {
|
|
208
|
+
return slug.startsWith(SKILL_SLUG_PREFIX);
|
|
186
209
|
}
|
|
187
210
|
|
|
188
211
|
/** @internal Test-only: clear the module-level cache. */
|
|
@@ -17,7 +17,7 @@
|
|
|
17
17
|
// content through when `mode === "full"` (first turn / post-compaction),
|
|
18
18
|
// matching the existing PKB auto-inject pattern.
|
|
19
19
|
|
|
20
|
-
import {
|
|
20
|
+
import type { ChannelId } from "../../channels/types.js";
|
|
21
21
|
import { loadConfig } from "../../config/loader.js";
|
|
22
22
|
import { readPromptFile } from "../../prompts/system-prompt.js";
|
|
23
23
|
import { getWorkspacePromptPath } from "../../util/platform.js";
|
|
@@ -35,9 +35,9 @@ const MEMORY_V2_STATIC_BLOCKS: readonly MemoryV2StaticBlock[] = [
|
|
|
35
35
|
];
|
|
36
36
|
|
|
37
37
|
/**
|
|
38
|
-
* Build the v2 static memory block, gated on `memory
|
|
39
|
-
*
|
|
40
|
-
*
|
|
38
|
+
* Build the v2 static memory block, gated on `config.memory.v2.enabled`.
|
|
39
|
+
* Empty/missing files are skipped; returns `null` when the gate is off or
|
|
40
|
+
* every file is empty.
|
|
41
41
|
*/
|
|
42
42
|
export function readMemoryV2StaticContent(): string | null {
|
|
43
43
|
let config;
|
|
@@ -46,10 +46,7 @@ export function readMemoryV2StaticContent(): string | null {
|
|
|
46
46
|
} catch {
|
|
47
47
|
return null;
|
|
48
48
|
}
|
|
49
|
-
if (
|
|
50
|
-
!isAssistantFeatureFlagEnabled("memory-v2-enabled", config) ||
|
|
51
|
-
!config.memory.v2.enabled
|
|
52
|
-
) {
|
|
49
|
+
if (!config.memory.v2.enabled) {
|
|
53
50
|
return null;
|
|
54
51
|
}
|
|
55
52
|
|
|
@@ -61,3 +58,24 @@ export function readMemoryV2StaticContent(): string | null {
|
|
|
61
58
|
}
|
|
62
59
|
return sections.length > 0 ? sections.join("\n\n") : null;
|
|
63
60
|
}
|
|
61
|
+
|
|
62
|
+
/**
|
|
63
|
+
* Static memory holds the user's aggregate personal pages
|
|
64
|
+
* (essentials/threads/recent/buffer). Block injection when a non-guardian
|
|
65
|
+
* actor reaches the assistant over a remote channel — otherwise the model
|
|
66
|
+
* can be prompt-injected into reciting private memory. Internal flows
|
|
67
|
+
* (`sourceChannel: "vellum"`) and turns with no trust context pass through
|
|
68
|
+
* unchanged; this gate exists only to keep remote untrusted actors out.
|
|
69
|
+
*/
|
|
70
|
+
export function shouldLoadMemoryV2Static(args: {
|
|
71
|
+
shouldInjectNowAndPkb: boolean;
|
|
72
|
+
sourceChannel: ChannelId | undefined;
|
|
73
|
+
isTrustedActor: boolean;
|
|
74
|
+
}): boolean {
|
|
75
|
+
if (!args.shouldInjectNowAndPkb) return false;
|
|
76
|
+
const isRemoteUntrustedActor =
|
|
77
|
+
args.sourceChannel !== undefined &&
|
|
78
|
+
args.sourceChannel !== "vellum" &&
|
|
79
|
+
!args.isTrustedActor;
|
|
80
|
+
return !isRemoteUntrustedActor;
|
|
81
|
+
}
|
|
@@ -13,10 +13,10 @@
|
|
|
13
13
|
* extraction-trigger path. Until then this handler is invoked only by
|
|
14
14
|
* `memory_v2_sweep` rows enqueued explicitly (tests, future CLI).
|
|
15
15
|
*
|
|
16
|
-
* Skipped entirely when
|
|
16
|
+
* Skipped entirely when `config.memory.v2.enabled` is false, or when
|
|
17
17
|
* `config.memory.v2.sweep_enabled` is false — keeps the sweep dormant in
|
|
18
18
|
* v1-only workspaces and in v2 workspaces that haven't opted in, even if a
|
|
19
|
-
* stale row sits in the queue
|
|
19
|
+
* stale row sits in the queue when v2 is disabled.
|
|
20
20
|
*/
|
|
21
21
|
|
|
22
22
|
import { readFileSync } from "node:fs";
|
|
@@ -25,7 +25,6 @@ import { join } from "node:path";
|
|
|
25
25
|
import { desc, gt } from "drizzle-orm";
|
|
26
26
|
import { z } from "zod";
|
|
27
27
|
|
|
28
|
-
import { isAssistantFeatureFlagEnabled } from "../../config/assistant-feature-flags.js";
|
|
29
28
|
import type { AssistantConfig } from "../../config/types.js";
|
|
30
29
|
import { getAssistantName } from "../../daemon/identity-helpers.js";
|
|
31
30
|
import {
|
|
@@ -104,12 +103,12 @@ export async function memoryV2SweepJob(
|
|
|
104
103
|
_job: MemoryJob,
|
|
105
104
|
config: AssistantConfig,
|
|
106
105
|
): Promise<number> {
|
|
107
|
-
if (!
|
|
108
|
-
log.debug("memory
|
|
106
|
+
if (!config.memory?.v2?.enabled) {
|
|
107
|
+
log.debug("memory.v2.enabled is false; sweep skipped");
|
|
109
108
|
return 0;
|
|
110
109
|
}
|
|
111
110
|
|
|
112
|
-
if (!config.memory
|
|
111
|
+
if (!config.memory.v2.sweep_enabled) {
|
|
113
112
|
log.debug("memory.v2.sweep_enabled is false; sweep skipped");
|
|
114
113
|
return 0;
|
|
115
114
|
}
|
package/src/memory/v2/types.ts
CHANGED
|
@@ -26,10 +26,17 @@ import { z } from "zod";
|
|
|
26
26
|
* B → A. The full graph is the union of every page's `edges:` list — there
|
|
27
27
|
* is no separate edges-index file. `ref_files` lists paths to attached media
|
|
28
28
|
* (images, audio, etc.).
|
|
29
|
+
*
|
|
30
|
+
* `summary` is a 1-4 sentence prose description of the page. When present,
|
|
31
|
+
* retrieval injects the path + summary instead of the full page so the agent
|
|
32
|
+
* can decide whether to read the file. Optional because legacy pages predating
|
|
33
|
+
* the summary field still parse — those fall back to full-page injection and
|
|
34
|
+
* full-page-only similarity.
|
|
29
35
|
*/
|
|
30
36
|
export const ConceptPageFrontmatterSchema = z.object({
|
|
31
37
|
edges: z.array(z.string()).default([]),
|
|
32
38
|
ref_files: z.array(z.string()).default([]),
|
|
39
|
+
summary: z.string().optional(),
|
|
33
40
|
});
|
|
34
41
|
|
|
35
42
|
export type ConceptPageFrontmatter = z.infer<
|
|
@@ -85,20 +92,20 @@ export const ActivationStateSchema = z.object({
|
|
|
85
92
|
export type ActivationState = z.infer<typeof ActivationStateSchema>;
|
|
86
93
|
|
|
87
94
|
// ---------------------------------------------------------------------------
|
|
88
|
-
// Skill
|
|
95
|
+
// Skill entries (synthetic concept-collection rows, not on-disk pages)
|
|
89
96
|
// ---------------------------------------------------------------------------
|
|
90
97
|
|
|
91
98
|
/**
|
|
92
|
-
* Per-skill capability snapshot held in-process and embedded into the
|
|
93
|
-
* `
|
|
94
|
-
* `buildSkillContent` string — already capped at
|
|
95
|
-
* already containing the skill's display name — and
|
|
96
|
-
* what we render verbatim in `### Skills You Can Use`.
|
|
99
|
+
* Per-skill capability snapshot held in-process and embedded into the unified
|
|
100
|
+
* `memory_v2_concept_pages` Qdrant collection under the slug `skills/<id>`.
|
|
101
|
+
* `content` is the rendered `buildSkillContent` string — already capped at
|
|
102
|
+
* 500 chars upstream and already containing the skill's display name — and
|
|
103
|
+
* is what we embed and what we render verbatim in `### Skills You Can Use`.
|
|
97
104
|
*
|
|
98
|
-
* Plain interface (no Zod) because skill data does not cross a
|
|
99
|
-
*
|
|
100
|
-
*
|
|
101
|
-
*
|
|
105
|
+
* Plain interface (no Zod) because skill data does not cross a serialization
|
|
106
|
+
* boundary: it is built in-process by `seedV2SkillEntries` and read in-process
|
|
107
|
+
* by `renderInjectionBlock`. The Qdrant payload is not parsed back through
|
|
108
|
+
* this type.
|
|
102
109
|
*/
|
|
103
110
|
export interface SkillEntry {
|
|
104
111
|
id: string;
|