@vellumai/assistant 0.8.0 → 0.8.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/AGENTS.md +11 -0
- package/Dockerfile +5 -4
- package/README.md +2 -2
- package/docker-entrypoint.sh +16 -0
- package/eslint-rules/__tests__/cli-no-daemon-internals.test.ts +420 -0
- package/eslint-rules/cli-no-daemon-internals.js +283 -0
- package/eslint.config.mjs +12 -0
- package/knip.json +2 -1
- package/node_modules/@vellumai/skill-host-contracts/src/client.ts +10 -1
- package/openapi.yaml +4847 -1698
- package/package.json +3 -1
- package/scripts/generate-openapi.ts +52 -4
- package/scripts/sync-llm-catalog.ts +165 -0
- package/scripts/sync-web-search-catalog.ts +107 -0
- package/src/__tests__/actor-trust-resolver-address-fallback.test.ts +169 -0
- package/src/__tests__/agent-loop-override-profile.test.ts +26 -1
- package/src/__tests__/anthropic-provider.test.ts +92 -2
- package/src/__tests__/app-control-flow.test.ts +7 -0
- package/src/__tests__/assistant-events-sse-shed.test.ts +232 -0
- package/src/__tests__/avatar-identity-sync.test.ts +87 -0
- package/src/__tests__/background-workers-disk-pressure.test.ts +11 -22
- package/src/__tests__/btw-routes.test.ts +1 -0
- package/src/__tests__/call-site-routing-provider.test.ts +172 -45
- package/src/__tests__/cancel-resolves-conversation-key.test.ts +44 -3
- package/src/__tests__/channel-policy.test.ts +12 -0
- package/src/__tests__/checker.test.ts +89 -0
- package/src/__tests__/cli-memory-v2-reembed-skills.test.ts +35 -7
- package/src/__tests__/compact-event-conversation-id-guard.test.ts +33 -5
- package/src/__tests__/compaction-strip-metadata-clear.test.ts +26 -1
- package/src/__tests__/config-loader-backfill.test.ts +526 -102
- package/src/__tests__/config-loader-corrupt.test.ts +68 -0
- package/src/__tests__/config-loader-platform-defaults.test.ts +77 -23
- package/src/__tests__/config-schema-cmd.test.ts +63 -29
- package/src/__tests__/config-schema.test.ts +14 -3
- package/src/__tests__/config-set-platform-guard.test.ts +75 -152
- package/src/__tests__/config-set-route.test.ts +198 -0
- package/src/__tests__/config-watcher.test.ts +6 -0
- package/src/__tests__/contacts-tools.test.ts +51 -199
- package/src/__tests__/context-search-agent-protocol.test.ts +21 -2
- package/src/__tests__/context-search-agent-runner.test.ts +22 -138
- package/src/__tests__/context-search-conversations-source.test.ts +42 -16
- package/src/__tests__/context-search-fanout.test.ts +20 -157
- package/src/__tests__/context-search-memory-v2-source.test.ts +3 -3
- package/src/__tests__/context-search-types.test.ts +7 -2
- package/src/__tests__/context-window-manager.test.ts +389 -1
- package/src/__tests__/conversation-agent-loop-overflow.test.ts +1 -0
- package/src/__tests__/conversation-crud-inference-profile.test.ts +100 -0
- package/src/__tests__/conversation-error.test.ts +38 -0
- package/src/__tests__/conversation-fork-crud.test.ts +241 -1
- package/src/__tests__/conversation-inference-profile-route.test.ts +14 -14
- package/src/__tests__/conversation-init.benchmark.test.ts +1 -0
- package/src/__tests__/conversation-lifecycle.test.ts +124 -0
- package/src/__tests__/conversation-process-app-control-preactivation.test.ts +100 -1
- package/src/__tests__/conversation-process-callsite.test.ts +21 -1
- package/src/__tests__/conversation-runtime-assembly.test.ts +4 -4
- package/src/__tests__/conversation-slash-commands.test.ts +194 -2
- package/src/__tests__/conversation-surfaces-app-control.test.ts +323 -3
- package/src/__tests__/credential-security-invariants.test.ts +5 -6
- package/src/__tests__/daemon-credential-client.test.ts +56 -1
- package/src/__tests__/db-activation-state-fk-cascade.test.ts +132 -0
- package/src/__tests__/db-conversation-inference-profile-migration.test.ts +37 -0
- package/src/__tests__/db-memory-graph-event-date-repair.test.ts +43 -20
- package/src/__tests__/db-proxy-transaction.test.ts +206 -0
- package/src/__tests__/external-plugin-loader.test.ts +458 -0
- package/src/__tests__/filing-service.test.ts +23 -3
- package/src/__tests__/fixtures/mock-chrome-extension.ts +5 -0
- package/src/__tests__/gateway-only-guard.test.ts +0 -1
- package/src/__tests__/graph-extraction-event-date.test.ts +34 -0
- package/src/__tests__/handlers-skills-memory-v2-reseed.test.ts +0 -8
- package/src/__tests__/heartbeat-disk-pressure.test.ts +21 -8
- package/src/__tests__/heartbeat-service.test.ts +50 -233
- package/src/__tests__/history-repair.test.ts +89 -0
- package/src/__tests__/host-app-control-proxy.test.ts +109 -1
- package/src/__tests__/host-app-control-routes.test.ts +247 -1
- package/src/__tests__/host-browser-proxy.test.ts +416 -20
- package/src/__tests__/host-browser-routes.test.ts +325 -33
- package/src/__tests__/host-proxy-preactivation.test.ts +211 -0
- package/src/__tests__/inference-no-mode-boot-e2e.test.ts +246 -0
- package/src/__tests__/inference-profile-reaper.test.ts +154 -0
- package/src/__tests__/inference-profile-session-handler.test.ts +398 -0
- package/src/__tests__/inference-profile-session-ipc.test.ts +236 -0
- package/src/__tests__/inline-skill-load-permissions.test.ts +6 -1
- package/src/__tests__/install-skill-routing.test.ts +2 -2
- package/src/__tests__/lifecycle-memory-v2-seed.test.ts +15 -0
- package/src/__tests__/llm-callsite-catalog.test.ts +20 -1
- package/src/__tests__/llm-catalog-parity.test.ts +146 -0
- package/src/__tests__/llm-request-log-source-clickhouse.test.ts +188 -0
- package/src/__tests__/llm-request-log-source-factory.test.ts +124 -0
- package/src/__tests__/llm-resolver.test.ts +46 -0
- package/src/__tests__/managed-profile-guard.test.ts +131 -2
- package/src/__tests__/mcp-auth-routes.test.ts +1 -0
- package/src/__tests__/mcp-cli.test.ts +182 -220
- package/src/__tests__/mcp-health-check.test.ts +56 -27
- package/src/__tests__/memory-jobs-worker-lanes.test.ts +18 -11
- package/src/__tests__/message-complete-display-id.test.ts +175 -0
- package/src/__tests__/notification-platform-adapter.test.ts +229 -0
- package/src/__tests__/oauth-cli.test.ts +38 -2009
- package/src/__tests__/oauth-commands-routes.test.ts +711 -0
- package/src/__tests__/oauth-connect-routes.test.ts +174 -11
- package/src/__tests__/oauth-providers-routes.test.ts +14 -10
- package/src/__tests__/openai-responses-cutover-guard.test.ts +33 -12
- package/src/__tests__/openai-responses-provider.test.ts +17 -0
- package/src/__tests__/plugin-bootstrap.test.ts +31 -2
- package/src/__tests__/plugin-route-contribution.test.ts +31 -3
- package/src/__tests__/plugin-tool-contribution.test.ts +31 -3
- package/src/__tests__/plugin-types.test.ts +13 -11
- package/src/__tests__/process-message-background-slack.test.ts +46 -0
- package/src/__tests__/profile-entry-status.test.ts +43 -0
- package/src/__tests__/provider-managed-proxy-integration.test.ts +12 -4
- package/src/__tests__/provider-registry-ollama.test.ts +12 -4
- package/src/__tests__/provider-send-message-override-profile.test.ts +10 -4
- package/src/__tests__/relay-server.test.ts +118 -0
- package/src/__tests__/retry-thinking-tool-choice.test.ts +15 -0
- package/src/__tests__/schedule-retry.test.ts +56 -4
- package/src/__tests__/schedule-routes.test.ts +104 -0
- package/src/__tests__/scheduler-disk-pressure.test.ts +0 -4
- package/src/__tests__/scheduler-recurrence.test.ts +87 -34
- package/src/__tests__/scheduler-reuse-conversation.test.ts +161 -5
- package/src/__tests__/scheduler-wake.test.ts +0 -63
- package/src/__tests__/secret-allowlist.test.ts +1 -0
- package/src/__tests__/secret-routes-managed-proxy.test.ts +12 -4
- package/src/__tests__/shell-credential-ref.test.ts +95 -3
- package/src/__tests__/shell-tool-proxy-mode.test.ts +14 -0
- package/src/__tests__/skill-load-feature-flag.test.ts +1 -0
- package/src/__tests__/skill-load-tool.test.ts +2 -4
- package/src/__tests__/subagent-call-site-routing.test.ts +78 -16
- package/src/__tests__/suggestion-routes.test.ts +3 -3
- package/src/__tests__/sync-message-contract.test.ts +63 -0
- package/src/__tests__/task-scheduler.test.ts +88 -23
- package/src/__tests__/update-bulletin-job.test.ts +96 -193
- package/src/__tests__/usage-cli.test.ts +11 -73
- package/src/__tests__/user-plugin-loader.test.ts +145 -0
- package/src/__tests__/vercel-config.test.ts +168 -0
- package/src/__tests__/web-search-catalog-parity.test.ts +86 -0
- package/src/__tests__/web-search.test.ts +303 -2
- package/src/__tests__/workspace-migration-039-drop-legacy-llm-keys.test.ts +1 -21
- package/src/__tests__/workspace-migration-057-repair-stale-gemini-model-ids.test.ts +58 -0
- package/src/__tests__/workspace-migration-069-seed-onboarding-threads.test.ts +53 -20
- package/src/__tests__/workspace-migration-072-seed-reply-suggestion-callsite.test.ts +191 -0
- package/src/__tests__/workspace-migration-076-drop-services-inference-mode.test.ts +211 -0
- package/src/__tests__/workspace-migration-077-seed-memory-router-callsite.test.ts +174 -0
- package/src/__tests__/workspace-migration-079-home-feed-notification-only.test.ts +323 -0
- package/src/__tests__/workspace-migration-080-restrict-vercel-api-token-metadata.test.ts +299 -0
- package/src/__tests__/workspace-migration-081-backfill-bash-allowed-tools.test.ts +410 -0
- package/src/__tests__/workspace-migration-082-backfill-managed-profile-labels.test.ts +268 -0
- package/src/__tests__/workspace-migration-unify-llm-callsite-configs.test.ts +3 -3
- package/src/__tests__/workspace-release-notes-feature-flag-guard.test.ts +115 -0
- package/src/acp/__tests__/helpers/which-stub.ts +4 -2
- package/src/acp/resolve-agent.test.ts +25 -0
- package/src/acp/resolve-agent.ts +13 -2
- package/src/acp/session-manager.ts +14 -0
- package/src/approvals/guardian-request-resolvers.ts +32 -87
- package/src/calls/relay-server.ts +35 -0
- package/src/calls/relay-setup-router.ts +36 -0
- package/src/calls/types.ts +1 -0
- package/src/calls/voice-session-bridge.ts +23 -4
- package/src/channels/config.ts +14 -1
- package/src/channels/types.ts +1 -0
- package/src/cli/AGENTS.md +164 -4
- package/src/cli/__tests__/notifications.test.ts +54 -0
- package/src/cli/commands/__tests__/avatar.test.ts +540 -0
- package/src/cli/commands/__tests__/backup.test.ts +236 -776
- package/src/cli/commands/__tests__/cache.test.ts +1 -1
- package/src/cli/commands/__tests__/changelog.test.ts +593 -0
- package/src/cli/commands/__tests__/channel-verification-sessions.test.ts +503 -0
- package/src/cli/commands/__tests__/conversations-import.test.ts +515 -0
- package/src/cli/commands/__tests__/domain-register.test.ts +140 -167
- package/src/cli/commands/__tests__/domain-status.test.ts +137 -76
- package/src/cli/commands/__tests__/email-attachment.test.ts +314 -337
- package/src/cli/commands/__tests__/email-core.test.ts +579 -0
- package/src/cli/commands/__tests__/image-generation.test.ts +87 -824
- package/src/cli/commands/__tests__/inference-send.test.ts +30 -266
- package/src/cli/commands/__tests__/inference-session.test.ts +423 -0
- package/src/cli/commands/__tests__/memory-v2.test.ts +81 -110
- package/src/cli/commands/__tests__/skills.test.ts +563 -0
- package/src/cli/commands/__tests__/status.test.ts +249 -0
- package/src/cli/commands/__tests__/stt.test.ts +320 -0
- package/src/cli/commands/__tests__/tts-synthesize.test.ts +4 -603
- package/src/cli/commands/__tests__/tts.test.ts +321 -0
- package/src/cli/commands/__tests__/webhooks.test.ts +86 -511
- package/src/cli/commands/attachment.ts +8 -3
- package/src/cli/commands/audit.ts +95 -64
- package/src/cli/commands/auth.ts +61 -58
- package/src/cli/commands/avatar.ts +276 -390
- package/src/cli/commands/backup.ts +409 -505
- package/src/cli/commands/bash.ts +9 -5
- package/src/cli/commands/browser.ts +28 -9
- package/src/cli/commands/cache.ts +9 -4
- package/src/cli/commands/changelog.ts +414 -0
- package/src/cli/commands/channel-verification-sessions.ts +238 -317
- package/src/cli/commands/clients.ts +8 -3
- package/src/cli/commands/completions.ts +9 -9
- package/src/cli/commands/config.ts +102 -72
- package/src/cli/commands/contacts.ts +575 -696
- package/src/cli/commands/conversations-defer.ts +17 -69
- package/src/cli/commands/conversations-import.ts +90 -253
- package/src/cli/commands/conversations.ts +346 -436
- package/src/cli/commands/credential-execution.ts +9 -6
- package/src/cli/commands/credentials.ts +456 -736
- package/src/cli/commands/domain.ts +128 -206
- package/src/cli/commands/email.ts +606 -794
- package/src/cli/commands/gateway.ts +8 -1
- package/src/cli/commands/image-generation.ts +157 -205
- package/src/cli/commands/inference-providers.ts +352 -0
- package/src/cli/commands/inference-session.ts +415 -0
- package/src/cli/commands/inference.ts +87 -65
- package/src/cli/commands/keys.ts +8 -3
- package/src/cli/commands/mcp.ts +103 -287
- package/src/cli/commands/memory-v2.ts +162 -516
- package/src/cli/commands/notifications.ts +33 -7
- package/src/cli/commands/oauth/apps.ts +292 -261
- package/src/cli/commands/oauth/connect.ts +176 -297
- package/src/cli/commands/oauth/disconnect.ts +16 -215
- package/src/cli/commands/oauth/index.ts +49 -45
- package/src/cli/commands/oauth/mode.ts +43 -199
- package/src/cli/commands/oauth/ping.ts +17 -125
- package/src/cli/commands/oauth/providers.ts +732 -921
- package/src/cli/commands/oauth/request.ts +60 -350
- package/src/cli/commands/oauth/shared.ts +11 -121
- package/src/cli/commands/oauth/status.ts +31 -121
- package/src/cli/commands/oauth/token.ts +13 -55
- package/src/cli/commands/pending.ts +19 -10
- package/src/cli/commands/platform/__tests__/callback-routes-list.test.ts +133 -183
- package/src/cli/commands/platform/__tests__/connect.test.ts +66 -181
- package/src/cli/commands/platform/__tests__/disconnect.test.ts +71 -227
- package/src/cli/commands/platform/__tests__/status.test.ts +169 -287
- package/src/cli/commands/platform/connect.ts +16 -80
- package/src/cli/commands/platform/disconnect.ts +14 -112
- package/src/cli/commands/platform/index.ts +177 -246
- package/src/cli/commands/routes.ts +153 -336
- package/src/cli/commands/sequence.ts +316 -360
- package/src/cli/commands/skills.ts +449 -671
- package/src/cli/commands/status.ts +58 -37
- package/src/cli/commands/stt.ts +94 -262
- package/src/cli/commands/task.ts +14 -40
- package/src/cli/commands/trust.ts +8 -3
- package/src/cli/commands/tts.ts +162 -167
- package/src/cli/commands/ui.ts +35 -42
- package/src/cli/commands/usage.ts +188 -126
- package/src/cli/commands/watchers.ts +8 -3
- package/src/cli/commands/webhooks.ts +99 -193
- package/src/cli/lib/__tests__/register-command.test.ts +85 -0
- package/src/cli/lib/daemon-credential-client.ts +4 -5
- package/src/cli/lib/nested-value.ts +44 -0
- package/src/cli/lib/open-browser.ts +36 -0
- package/src/cli/lib/register-command.ts +19 -0
- package/src/cli/lib/time-ago.ts +34 -0
- package/src/cli/program.ts +2 -4
- package/src/cli/utils/__tests__/conversation-id.test.ts +66 -0
- package/src/cli/utils/__tests__/parse-duration.test.ts +49 -0
- package/src/cli/utils/conversation-id.ts +30 -0
- package/src/cli/utils/parse-duration.ts +41 -0
- package/src/config/acp-defaults.test.ts +5 -1
- package/src/config/acp-defaults.ts +11 -4
- package/src/config/bundled-skills/acp/TOOLS.json +2 -2
- package/src/config/bundled-skills/app-control/TOOLS.json +32 -0
- package/src/config/bundled-skills/contacts/SKILL.md +12 -45
- package/src/config/bundled-skills/contacts/TOOLS.json +0 -57
- package/src/config/bundled-skills/messaging/tools/messaging-archive-by-sender.ts +0 -12
- package/src/config/bundled-skills/messaging/tools/messaging-send.ts +0 -58
- package/src/config/bundled-tool-registry.ts +0 -2
- package/src/config/feature-flag-registry.json +16 -0
- package/src/config/llm-resolver.ts +16 -1
- package/src/config/loader.ts +76 -14
- package/src/config/raw-config-utils.ts +2 -30
- package/src/config/schema.ts +4 -0
- package/src/config/schemas/__tests__/memory-v2.test.ts +49 -0
- package/src/config/schemas/call-site-catalog.ts +29 -7
- package/src/config/schemas/llm-request-logs.ts +57 -0
- package/src/config/schemas/llm.ts +52 -2
- package/src/config/schemas/memory-retrospective.ts +48 -0
- package/src/config/schemas/memory-v2.ts +32 -1
- package/src/config/schemas/memory.ts +4 -0
- package/src/config/schemas/services.ts +15 -12
- package/src/config/seed-inference-profiles.ts +195 -134
- package/src/contacts/contact-store.ts +0 -61
- package/src/context/window-manager.ts +191 -5
- package/src/daemon/__tests__/conversation-lifecycle-auto-analyze.test.ts +79 -0
- package/src/daemon/__tests__/conversation-tool-setup.test.ts +109 -4
- package/src/daemon/__tests__/daemon-skill-host.test.ts +10 -4
- package/src/daemon/approval-generators.ts +23 -29
- package/src/daemon/config-watcher.ts +2 -0
- package/src/daemon/conversation-agent-loop-handlers.ts +24 -0
- package/src/daemon/conversation-agent-loop.ts +127 -97
- package/src/daemon/conversation-error.ts +21 -0
- package/src/daemon/conversation-lifecycle.ts +46 -5
- package/src/daemon/conversation-process.ts +36 -19
- package/src/daemon/conversation-runtime-assembly.ts +14 -5
- package/src/daemon/conversation-slash.ts +175 -23
- package/src/daemon/conversation-store.ts +17 -10
- package/src/daemon/conversation-surfaces.ts +76 -12
- package/src/daemon/conversation-tool-setup.ts +24 -14
- package/src/daemon/conversation.ts +48 -9
- package/src/daemon/external-plugins-bootstrap.ts +18 -8
- package/src/daemon/guardian-action-generators.ts +7 -22
- package/src/daemon/handlers/config-model.ts +8 -126
- package/src/daemon/handlers/config-slack-channel.ts +10 -7
- package/src/daemon/handlers/config-vercel.ts +3 -1
- package/src/daemon/handlers/skills.ts +84 -5
- package/src/daemon/history-repair.ts +33 -6
- package/src/daemon/host-app-control-proxy.ts +44 -19
- package/src/daemon/host-bash-proxy.ts +85 -158
- package/src/daemon/host-browser-proxy.ts +96 -35
- package/src/daemon/host-proxy-base.ts +13 -1
- package/src/daemon/host-proxy-preactivation.ts +25 -1
- package/src/daemon/identity-helpers.ts +19 -0
- package/src/daemon/lifecycle.ts +42 -43
- package/src/daemon/meet-host-supervisor.ts +15 -15
- package/src/daemon/memory-v2-startup.ts +9 -2
- package/src/daemon/message-protocol.ts +6 -0
- package/src/daemon/message-types/bookmarks.ts +18 -0
- package/src/daemon/message-types/conversations.ts +12 -9
- package/src/daemon/message-types/messages.ts +9 -1
- package/src/daemon/message-types/sync.ts +60 -0
- package/src/daemon/pkb-reminder-builder.test.ts +54 -13
- package/src/daemon/pkb-reminder-builder.ts +21 -7
- package/src/daemon/process-message.ts +56 -23
- package/src/daemon/server.ts +23 -18
- package/src/daemon/shutdown-handlers.ts +0 -2
- package/src/daemon/tool-setup-types.ts +9 -0
- package/src/daemon/tool-side-effects.ts +6 -4
- package/src/daemon/wake-target-adapter.ts +11 -0
- package/src/export/transcript-formatter.ts +61 -2
- package/src/filing/filing-service.ts +40 -53
- package/src/heartbeat/__tests__/heartbeat-service.test.ts +359 -0
- package/src/heartbeat/heartbeat-run-store.ts +2 -1
- package/src/heartbeat/heartbeat-service.ts +148 -127
- package/src/home/__tests__/feed-types.test.ts +63 -131
- package/src/home/__tests__/feed-writer.test.ts +77 -278
- package/src/home/__tests__/post-connect-feed.test.ts +9 -12
- package/src/home/feed-types.ts +19 -73
- package/src/home/feed-writer.ts +25 -156
- package/src/home/post-connect-feed.ts +1 -3
- package/src/ipc/__tests__/cli-ipc.test.ts +2 -0
- package/src/ipc/__tests__/email-ipc.test.ts +506 -0
- package/src/ipc/__tests__/exit-helper.test.ts +104 -0
- package/src/ipc/__tests__/streaming-client.test.ts +237 -0
- package/src/ipc/__tests__/streaming-framing.test.ts +142 -0
- package/src/ipc/assistant-server.ts +55 -6
- package/src/ipc/cli-client.ts +370 -50
- package/src/ipc/routes/db-proxy-transaction.ts +151 -0
- package/src/ipc/skill-routes/__tests__/events-ipc.test.ts +60 -0
- package/src/ipc/skill-routes/events.ts +30 -3
- package/src/live-voice/__tests__/live-voice-session-manager.test.ts +46 -0
- package/src/live-voice/__tests__/runtime-websocket-shell.test.ts +1 -0
- package/src/live-voice/live-voice-session-manager.ts +11 -4
- package/src/live-voice/live-voice-session.ts +14 -6
- package/src/memory/__tests__/bookmark-crud.test.ts +258 -0
- package/src/memory/__tests__/bookmark-schema.test.ts +181 -0
- package/src/memory/__tests__/conversation-types.test.ts +36 -0
- package/src/memory/__tests__/find-most-recent-retrospective-for.test.ts +130 -0
- package/src/memory/__tests__/memory-retrospective-enqueue.test.ts +177 -0
- package/src/memory/__tests__/memory-retrospective-job.test.ts +328 -0
- package/src/memory/__tests__/memory-retrospective-startup-cleanup.test.ts +213 -0
- package/src/memory/__tests__/memory-retrospective-trigger-check.test.ts +90 -0
- package/src/memory/__tests__/memory-v2-activation-log-store.test.ts +69 -0
- package/src/memory/__tests__/memory-v2-concept-frequency.test.ts +3 -0
- package/src/memory/bookmark-crud.ts +179 -0
- package/src/memory/context-search/__tests__/agent-runner-redaction.test.ts +31 -9
- package/src/memory/context-search/agent-protocol.ts +5 -1
- package/src/memory/context-search/agent-runner.ts +60 -85
- package/src/memory/context-search/limits.ts +1 -4
- package/src/memory/context-search/search.ts +23 -113
- package/src/memory/context-search/sources/conversations.ts +18 -6
- package/src/memory/context-search/sources/memory-v2.ts +39 -14
- package/src/memory/context-search/sources/memory.ts +7 -0
- package/src/memory/context-search/sources/workspace.ts +13 -10
- package/src/memory/context-search/types.ts +1 -1
- package/src/memory/conversation-bootstrap.ts +11 -0
- package/src/memory/conversation-crud.ts +312 -10
- package/src/memory/conversation-queries.ts +9 -5
- package/src/memory/conversation-title-service.ts +1 -0
- package/src/memory/conversation-types.ts +16 -0
- package/src/memory/db-init.ts +14 -0
- package/src/memory/embedding-backend.ts +2 -1
- package/src/memory/embedding-runtime-manager.ts +1 -2
- package/src/memory/graph/__tests__/remember-description.test.ts +55 -0
- package/src/memory/graph/conversation-graph-memory.ts +76 -5
- package/src/memory/graph/extraction.ts +4 -0
- package/src/memory/graph/graph-memory-state-store.ts +16 -3
- package/src/memory/graph/tool-handlers.ts +17 -7
- package/src/memory/graph/tools.ts +44 -5
- package/src/memory/indexer.ts +17 -0
- package/src/memory/jobs/__tests__/embed-concept-page.test.ts +13 -15
- package/src/memory/jobs/embed-concept-page.ts +45 -9
- package/src/memory/jobs-store.ts +51 -1
- package/src/memory/jobs-worker.ts +52 -3
- package/src/memory/llm-request-log-source-clickhouse.ts +317 -0
- package/src/memory/llm-request-log-source-local.ts +26 -0
- package/src/memory/llm-request-log-source.ts +97 -0
- package/src/memory/llm-request-log-store.ts +1 -1
- package/src/memory/memory-retrospective-constants.ts +13 -0
- package/src/memory/memory-retrospective-enqueue.ts +114 -0
- package/src/memory/memory-retrospective-job.ts +351 -0
- package/src/memory/memory-retrospective-startup-cleanup.ts +108 -0
- package/src/memory/memory-retrospective-state.ts +162 -0
- package/src/memory/memory-retrospective-trigger-check.ts +91 -0
- package/src/memory/memory-v2-activation-log-store.ts +49 -5
- package/src/memory/memory-v2-concept-frequency.ts +4 -0
- package/src/memory/message-content.ts +38 -1
- package/src/memory/migrations/227-add-conversation-inference-profile.ts +6 -1
- package/src/memory/migrations/228-rename-inference-profile-snake-case.ts +20 -7
- package/src/memory/migrations/229-delete-private-conversations.test.ts +70 -1
- package/src/memory/migrations/229-delete-private-conversations.ts +12 -0
- package/src/memory/migrations/231-repair-memory-graph-event-dates.ts +16 -2
- package/src/memory/migrations/240-conversation-inference-profile-session.ts +25 -0
- package/src/memory/migrations/241-activation-state-fk-cascade.ts +50 -0
- package/src/memory/migrations/242-message-bookmarks.ts +38 -0
- package/src/memory/migrations/243-provider-connections.ts +68 -0
- package/src/memory/migrations/244-provider-connection-status-label.ts +23 -0
- package/src/memory/migrations/245-memory-retrospective-state.ts +36 -0
- package/src/memory/migrations/246-backfill-provider-connection-label.ts +81 -0
- package/src/memory/migrations/__tests__/244-provider-connection-status-label.test.ts +84 -0
- package/src/memory/migrations/__tests__/245-memory-retrospective-state.test.ts +125 -0
- package/src/memory/migrations/__tests__/246-backfill-provider-connection-label.test.ts +192 -0
- package/src/memory/migrations/index.ts +7 -0
- package/src/memory/published-pages-store.ts +16 -0
- package/src/memory/schema/bookmarks.ts +38 -0
- package/src/memory/schema/conversations.ts +2 -0
- package/src/memory/schema/index.ts +2 -0
- package/src/memory/schema/inference.ts +29 -0
- package/src/memory/schema/memory-core.ts +9 -0
- package/src/memory/search/semantic.ts +1 -4
- package/src/memory/v2/__tests__/__snapshots__/prompts-router.test.ts.snap +27 -0
- package/src/memory/v2/__tests__/activation-store.test.ts +5 -5
- package/src/memory/v2/__tests__/activation.test.ts +11 -4
- package/src/memory/v2/__tests__/backfill-jobs.test.ts +38 -21
- package/src/memory/v2/__tests__/consolidation-job.test.ts +123 -135
- package/src/memory/v2/__tests__/edge-index.test.ts +1 -1
- package/src/memory/v2/__tests__/frontmatter-sweep.test.ts +111 -0
- package/src/memory/v2/__tests__/injection.test.ts +628 -10
- package/src/memory/v2/__tests__/migration.test.ts +7 -3
- package/src/memory/v2/__tests__/page-index.test.ts +277 -0
- package/src/memory/v2/__tests__/page-store.test.ts +14 -1
- package/src/memory/v2/__tests__/prompts-router.test.ts +257 -0
- package/src/memory/v2/__tests__/qdrant.test.ts +72 -0
- package/src/memory/v2/__tests__/reranker.test.ts +4 -4
- package/src/memory/v2/__tests__/router.test.ts +516 -0
- package/src/memory/v2/__tests__/sim.test.ts +45 -1
- package/src/memory/v2/__tests__/skill-store.test.ts +58 -3
- package/src/memory/v2/__tests__/static-context.test.ts +7 -22
- package/src/memory/v2/__tests__/sweep-job.test.ts +95 -0
- package/src/memory/v2/activation-store.ts +34 -5
- package/src/memory/v2/activation.ts +40 -27
- package/src/memory/v2/backfill-jobs.ts +17 -84
- package/src/memory/v2/consolidation-job.ts +85 -78
- package/src/memory/v2/frontmatter-sweep.ts +91 -0
- package/src/memory/v2/injection.ts +440 -109
- package/src/memory/v2/migration.ts +117 -20
- package/src/memory/v2/page-index.ts +191 -0
- package/src/memory/v2/page-store.ts +3 -0
- package/src/memory/v2/prompts/consolidation.ts +9 -7
- package/src/memory/v2/prompts/router.ts +192 -0
- package/src/memory/v2/qdrant.ts +100 -87
- package/src/memory/v2/reranker.ts +14 -7
- package/src/memory/v2/router.ts +322 -0
- package/src/memory/v2/sim.ts +25 -12
- package/src/memory/v2/skill-store.ts +118 -29
- package/src/memory/v2/static-context.ts +16 -9
- package/src/memory/v2/sweep-job.ts +122 -96
- package/src/memory/v2/types.ts +10 -6
- package/src/memory/validation.ts +13 -0
- package/src/notifications/__tests__/emit-signal-home-feed.test.ts +182 -0
- package/src/notifications/__tests__/home-feed-side-effect.test.ts +199 -0
- package/src/notifications/__tests__/signal-registry.test.ts +17 -0
- package/src/notifications/adapters/platform.ts +171 -0
- package/src/notifications/conversation-pairing.ts +2 -2
- package/src/notifications/copy-composer.ts +15 -0
- package/src/notifications/destination-resolver.ts +21 -0
- package/src/notifications/emit-signal.ts +28 -1
- package/src/notifications/home-feed-side-effect.ts +111 -0
- package/src/notifications/signal.ts +5 -0
- package/src/permissions/checker.ts +12 -0
- package/src/permissions/ipc-risk-types.ts +2 -0
- package/src/plugin-api/index.ts +13 -0
- package/src/plugin-api/package.json +12 -0
- package/src/plugin-api/types.ts +62 -0
- package/src/plugins/defaults/injectors.ts +19 -3
- package/src/plugins/external-plugin-loader.ts +294 -0
- package/src/plugins/types.ts +46 -30
- package/src/plugins/user-loader.ts +64 -41
- package/src/proactive-artifact/job.test.ts +12 -4
- package/src/proactive-artifact/job.ts +4 -0
- package/src/proactive-artifact/trigger-state.test.ts +9 -0
- package/src/proactive-artifact/trigger-state.ts +4 -0
- package/src/prompts/__tests__/system-prompt.test.ts +105 -0
- package/src/prompts/system-prompt.ts +22 -1
- package/src/prompts/update-bulletin-job.ts +61 -73
- package/src/providers/__tests__/dispatch-connection-routing.test.ts +279 -0
- package/src/providers/__tests__/inference.test.ts +288 -0
- package/src/providers/__tests__/provider-env-vars.test.ts +6 -0
- package/src/providers/__tests__/provider-secret-catalog.test.ts +6 -0
- package/src/providers/__tests__/retry-callsite.test.ts +14 -32
- package/src/providers/__tests__/satellite-connection-routing.test.ts +510 -0
- package/src/providers/__tests__/search-provider-catalog.test.ts +80 -0
- package/src/providers/anthropic/client.ts +95 -26
- package/src/providers/call-site-routing.ts +94 -16
- package/src/providers/connection-resolution.ts +163 -0
- package/src/providers/inference/__tests__/connections-status-label.test.ts +250 -0
- package/src/providers/inference/adapter-factory.ts +173 -0
- package/src/providers/inference/auth.ts +112 -0
- package/src/providers/inference/backfill.ts +196 -0
- package/src/providers/inference/connections.ts +356 -0
- package/src/providers/inference/resolve-auth.ts +65 -0
- package/src/providers/model-catalog.ts +104 -6
- package/src/providers/openai/responses-provider.ts +4 -2
- package/src/providers/provider-env-vars.ts +17 -7
- package/src/providers/provider-secret-catalog.ts +49 -30
- package/src/providers/provider-send-message.ts +41 -20
- package/src/providers/registry.ts +143 -159
- package/src/providers/retry.ts +18 -10
- package/src/providers/search-provider-catalog.ts +121 -0
- package/src/runtime/AGENTS.md +18 -5
- package/src/runtime/__tests__/background-job-runner.test.ts +357 -0
- package/src/runtime/__tests__/pre-first-message-gate.test.ts +82 -0
- package/src/runtime/actor-trust-resolver.ts +32 -10
- package/src/runtime/agent-wake.ts +35 -6
- package/src/runtime/assistant-event-hub.ts +3 -85
- package/src/runtime/auth/route-policy.ts +303 -8
- package/src/runtime/auth/same-actor.ts +2 -0
- package/src/runtime/background-job-runner.ts +339 -0
- package/src/runtime/btw-sidechain.ts +1 -0
- package/src/runtime/http-router.ts +36 -1
- package/src/runtime/http-server.ts +31 -5
- package/src/runtime/http-types.ts +2 -0
- package/src/runtime/middleware/__tests__/request-logger.test.ts +162 -0
- package/src/runtime/middleware/request-logger.ts +62 -1
- package/src/runtime/pre-first-message-gate.ts +83 -0
- package/src/runtime/routes/__tests__/backup-routes.test.ts +8 -1
- package/src/runtime/routes/__tests__/bookmark-routes.test.ts +251 -0
- package/src/runtime/routes/__tests__/connection-routes-vs-cli-parity.test.ts +142 -0
- package/src/runtime/routes/__tests__/conversation-management-routes.test.ts +315 -0
- package/src/runtime/routes/__tests__/conversation-query-routes.test.ts +189 -0
- package/src/runtime/routes/__tests__/home-feed-routes.test.ts +15 -136
- package/src/runtime/routes/__tests__/inference-provider-connection-routes.test.ts +736 -0
- package/src/runtime/routes/__tests__/memory-v2-routes.test.ts +4 -4
- package/src/runtime/routes/__tests__/stt-routes.test.ts +5 -1
- package/src/runtime/routes/__tests__/surface-action-routes.test.ts +384 -0
- package/src/runtime/routes/__tests__/tts-routes.test.ts +6 -2
- package/src/runtime/routes/acp-routes.ts +10 -8
- package/src/runtime/routes/app-management-routes.ts +228 -3
- package/src/runtime/routes/approval-routes.ts +0 -18
- package/src/runtime/routes/audit-routes.ts +43 -0
- package/src/runtime/routes/auth-routes.ts +72 -0
- package/src/runtime/routes/avatar-routes.ts +273 -20
- package/src/runtime/routes/backup-routes.ts +406 -2
- package/src/runtime/routes/bookmark-routes.ts +154 -0
- package/src/runtime/routes/channel-verification-routes.ts +2 -1
- package/src/runtime/routes/contact-routes.ts +0 -160
- package/src/runtime/routes/conversation-cli-routes.ts +192 -0
- package/src/runtime/routes/conversation-management-routes.ts +30 -43
- package/src/runtime/routes/conversation-query-routes.ts +334 -86
- package/src/runtime/routes/conversation-routes.ts +31 -10
- package/src/runtime/routes/conversations-import-routes.ts +229 -0
- package/src/runtime/routes/credential-routes.ts +540 -0
- package/src/runtime/routes/debug-routes.ts +2 -2
- package/src/runtime/routes/document-pdf-renderer.ts +5 -1
- package/src/runtime/routes/domain-routes.ts +167 -0
- package/src/runtime/routes/email-routes.ts +603 -0
- package/src/runtime/routes/errors.ts +2 -2
- package/src/runtime/routes/events-routes.ts +192 -0
- package/src/runtime/routes/home-feed-routes.ts +6 -78
- package/src/runtime/routes/host-app-control-routes.ts +44 -2
- package/src/runtime/routes/host-browser-routes.ts +103 -22
- package/src/runtime/routes/http-adapter.ts +2 -0
- package/src/runtime/routes/identity-routes.ts +5 -0
- package/src/runtime/routes/image-generation-routes.ts +99 -0
- package/src/runtime/routes/inbound-stages/background-dispatch.test.ts +137 -1
- package/src/runtime/routes/inbound-stages/background-dispatch.ts +87 -7
- package/src/runtime/routes/inbound-stages/guardian-reply-intercept.test.ts +156 -0
- package/src/runtime/routes/inbound-stages/guardian-reply-intercept.ts +22 -4
- package/src/runtime/routes/index.ts +36 -0
- package/src/runtime/routes/inference-profile-session-handler.ts +312 -0
- package/src/runtime/routes/inference-profile-session-reaper.ts +98 -0
- package/src/runtime/routes/inference-profile-session-routes.ts +146 -0
- package/src/runtime/routes/inference-provider-connection-routes.ts +317 -0
- package/src/runtime/routes/inference-send-routes.ts +115 -0
- package/src/runtime/routes/integrations/twilio.ts +1 -0
- package/src/runtime/routes/mcp-auth-routes.ts +283 -9
- package/src/runtime/routes/memory-v2-routes.ts +13 -398
- package/src/runtime/routes/notification-routes.ts +2 -0
- package/src/runtime/routes/oauth-apps.ts +112 -7
- package/src/runtime/routes/oauth-commands-routes.ts +1007 -0
- package/src/runtime/routes/oauth-connect-routes.ts +67 -5
- package/src/runtime/routes/oauth-providers.ts +298 -8
- package/src/runtime/routes/platform-routes.ts +336 -0
- package/src/runtime/routes/playground/inject-failures.ts +2 -1
- package/src/runtime/routes/playground/reset-circuit.ts +2 -1
- package/src/runtime/routes/playground/state.ts +2 -1
- package/src/runtime/routes/publish-routes.ts +221 -0
- package/src/runtime/routes/schedule-routes.ts +82 -0
- package/src/runtime/routes/sequence-routes.ts +291 -0
- package/src/runtime/routes/settings-routes.ts +2 -10
- package/src/runtime/routes/skills-routes.ts +31 -1
- package/src/runtime/routes/stt-routes.ts +240 -3
- package/src/runtime/routes/surface-action-routes.ts +43 -7
- package/src/runtime/routes/tts-routes.ts +67 -0
- package/src/runtime/routes/types.ts +32 -0
- package/src/runtime/routes/user-routes-cli.ts +243 -0
- package/src/runtime/routes/webhook-routes.ts +165 -0
- package/src/runtime/sync/resource-sync-events.ts +25 -0
- package/src/runtime/sync/sync-publisher.test.ts +105 -0
- package/src/runtime/sync/sync-publisher.ts +21 -0
- package/src/schedule/scheduler.ts +200 -123
- package/src/security/__tests__/provider-key-env-fallback.test.ts +12 -6
- package/src/security/secret-patterns.ts +3 -0
- package/src/sequence/engine.ts +38 -40
- package/src/subagent/manager.ts +20 -15
- package/src/tools/browser/__tests__/browser-execution-acquire.test.ts +206 -0
- package/src/tools/browser/browser-execution.ts +15 -4
- package/src/tools/browser/cdp-client/__tests__/factory.test.ts +174 -0
- package/src/tools/browser/cdp-client/cdp-inspect/__tests__/ws-transport.test.ts +16 -13
- package/src/tools/browser/cdp-client/extension-cdp-client.ts +24 -1
- package/src/tools/browser/cdp-client/factory.ts +66 -5
- package/src/tools/browser/runtime-check.ts +77 -0
- package/src/tools/memory/register.test.ts +3 -3
- package/src/tools/memory/register.ts +9 -1
- package/src/tools/network/__tests__/web-search.test.ts +156 -0
- package/src/tools/network/web-search.ts +280 -37
- package/src/tools/permission-checker.ts +13 -5
- package/src/tools/subagent/spawn.ts +3 -3
- package/src/tools/terminal/shell.ts +44 -0
- package/src/usage/attribution.ts +3 -2
- package/src/util/pricing.ts +86 -160
- package/src/watcher/__tests__/engine.test.ts +301 -0
- package/src/watcher/constants.ts +7 -0
- package/src/watcher/engine.ts +90 -90
- package/src/workspace/migrations/046-seed-conversation-starters-callsite.ts +6 -9
- package/src/workspace/migrations/054-seed-recall-callsite.ts +10 -1
- package/src/workspace/migrations/057-repair-stale-gemini-model-ids.ts +28 -4
- package/src/workspace/migrations/069-seed-onboarding-threads.ts +8 -2
- package/src/workspace/migrations/072-seed-reply-suggestion-callsite.ts +104 -0
- package/src/workspace/migrations/073-repair-recall-callsite-empty-profile.ts +93 -0
- package/src/workspace/migrations/074-drop-deprecated-secret-detection-keys.ts +117 -0
- package/src/workspace/migrations/075-memory-v2-bm25-b-default-reembed.ts +61 -0
- package/src/workspace/migrations/076-drop-services-inference-mode.ts +62 -0
- package/src/workspace/migrations/077-seed-memory-router-callsite.ts +89 -0
- package/src/workspace/migrations/078-release-notes-tavily-web-search.ts +66 -0
- package/src/workspace/migrations/079-home-feed-notification-only.ts +197 -0
- package/src/workspace/migrations/080-restrict-vercel-api-token-metadata.ts +182 -0
- package/src/workspace/migrations/081-backfill-bash-allowed-tools-for-injection-credentials.ts +160 -0
- package/src/workspace/migrations/082-backfill-managed-profile-labels.ts +154 -0
- package/src/workspace/migrations/registry.ts +22 -0
- package/src/workspace/migrations/runner.ts +13 -2
- package/src/workspace/migrations/types.ts +13 -3
- package/src/workspace/provider-commit-message-generator.ts +3 -2
- package/src/__tests__/context-search-pkb-source.test.ts +0 -498
- package/src/__tests__/credentials-cli.test.ts +0 -1225
- package/src/__tests__/memory-admin-recall.test.ts +0 -213
- package/src/approvals/__tests__/guardian-feed-event.test.ts +0 -303
- package/src/cli/commands/__tests__/email-download.test.ts +0 -260
- package/src/cli/commands/__tests__/email-list.test.ts +0 -216
- package/src/cli/commands/__tests__/email-register.test.ts +0 -186
- package/src/cli/commands/__tests__/email-send.test.ts +0 -416
- package/src/cli/commands/__tests__/email-status.test.ts +0 -185
- package/src/cli/commands/__tests__/email-unregister.test.ts +0 -168
- package/src/cli/commands/__tests__/routes.test.ts +0 -562
- package/src/cli/commands/__tests__/stt-transcribe.test.ts +0 -454
- package/src/cli/commands/autonomy.ts +0 -365
- package/src/cli/commands/memory.ts +0 -424
- package/src/cli/commands/oauth/__tests__/connect.test.ts +0 -947
- package/src/cli/commands/oauth/__tests__/disconnect.test.ts +0 -686
- package/src/cli/commands/oauth/__tests__/mode.test.ts +0 -632
- package/src/cli/commands/oauth/__tests__/ping.test.ts +0 -631
- package/src/cli/commands/oauth/__tests__/providers-delete.test.ts +0 -573
- package/src/cli/commands/oauth/__tests__/providers-register.test.ts +0 -330
- package/src/cli/commands/oauth/__tests__/providers-update.test.ts +0 -521
- package/src/cli/commands/oauth/__tests__/status.test.ts +0 -551
- package/src/cli/commands/oauth/__tests__/token.test.ts +0 -420
- package/src/cli/lib/daemon-avatar-client.ts +0 -37
- package/src/config/bundled-skills/contacts/tools/contact-upsert.ts +0 -87
- package/src/config/bundled-skills/messaging/tools/__tests__/messaging-feed-events.test.ts +0 -207
- package/src/daemon/__tests__/conversation-feed-event.test.ts +0 -304
- package/src/heartbeat/__tests__/heartbeat-feed-event.test.ts +0 -233
- package/src/home/__tests__/assistant-feed-authoring.test.ts +0 -156
- package/src/home/__tests__/emit-feed-event.test.ts +0 -169
- package/src/home/__tests__/feed-population-integration.test.ts +0 -312
- package/src/home/__tests__/feed-scheduler.test.ts +0 -222
- package/src/home/__tests__/phase5-exit-criteria.test.ts +0 -229
- package/src/home/__tests__/platform-gmail-digest.test.ts +0 -222
- package/src/home/__tests__/rollup-producer.test.ts +0 -507
- package/src/home/assistant-feed-authoring.ts +0 -135
- package/src/home/emit-feed-event.ts +0 -169
- package/src/home/feed-scheduler.ts +0 -281
- package/src/home/platform-gmail-digest.ts +0 -163
- package/src/home/rewrite-command-preview.ts +0 -66
- package/src/home/rewrite-feed-title.ts +0 -58
- package/src/home/rollup-producer.ts +0 -426
- package/src/memory/admin.ts +0 -326
- package/src/memory/context-search/sources/pkb.ts +0 -476
- package/src/memory/graph/compaction.ts +0 -299
- /package/src/cli/{commands → lib}/cache-fs.ts +0 -0
package/src/memory/v2/qdrant.ts
CHANGED
|
@@ -18,11 +18,16 @@
|
|
|
18
18
|
// fusion using `dense_weight` / `sparse_weight` from `config.memory.v2`,
|
|
19
19
|
// which RRF fusion would discard.
|
|
20
20
|
|
|
21
|
+
import { existsSync } from "node:fs";
|
|
22
|
+
import { mkdir, unlink, writeFile } from "node:fs/promises";
|
|
23
|
+
import { dirname, join } from "node:path";
|
|
24
|
+
|
|
21
25
|
import { QdrantClient as QdrantRestClient } from "@qdrant/js-client-rest";
|
|
22
26
|
import { v5 as uuidv5 } from "uuid";
|
|
23
27
|
|
|
24
28
|
import { getConfig } from "../../config/loader.js";
|
|
25
29
|
import { getLogger } from "../../util/logger.js";
|
|
30
|
+
import { getDataDir } from "../../util/platform.js";
|
|
26
31
|
import type { SparseEmbedding } from "../embedding-types.js";
|
|
27
32
|
import { resolveQdrantUrl } from "../qdrant-client.js";
|
|
28
33
|
|
|
@@ -86,6 +91,49 @@ let _collectionReadyPromise: Promise<{ migrated: boolean }> | null = null;
|
|
|
86
91
|
const REQUIRED_DENSE_VECTORS = ["dense", "summary_dense"] as const;
|
|
87
92
|
const REQUIRED_SPARSE_VECTORS = ["sparse", "summary_sparse"] as const;
|
|
88
93
|
|
|
94
|
+
/**
|
|
95
|
+
* Marker file written before the destructive collection-recreate path runs,
|
|
96
|
+
* cleared by the lifecycle hook once the reembed job has been enqueued.
|
|
97
|
+
*
|
|
98
|
+
* The sentinel exists to close a narrow data-loss window in
|
|
99
|
+
* `ensureConceptPageCollectionOnce`: a transient Qdrant failure between
|
|
100
|
+
* `deleteCollection` and `createCollection` would otherwise lose the
|
|
101
|
+
* "needs reembed" signal — `migrated` is reinitialized on the next call,
|
|
102
|
+
* any subsequent caller (e.g. an upsert) recreates the collection empty,
|
|
103
|
+
* and the lifecycle hook never enqueues the backfill. By persisting the
|
|
104
|
+
* intent on disk *before* delete, the signal survives crashes and
|
|
105
|
+
* intra-process retries: every later `ensureConceptPageCollection` call
|
|
106
|
+
* returns `migrated: true` until the lifecycle hook enqueues the reembed
|
|
107
|
+
* and clears the sentinel.
|
|
108
|
+
*/
|
|
109
|
+
const REEMBED_SENTINEL_FILENAME = ".memory-v2-reembed-required";
|
|
110
|
+
|
|
111
|
+
function reembedSentinelPath(): string {
|
|
112
|
+
return join(getDataDir(), REEMBED_SENTINEL_FILENAME);
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
function reembedSentinelExists(): boolean {
|
|
116
|
+
return existsSync(reembedSentinelPath());
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
async function writeReembedSentinel(): Promise<void> {
|
|
120
|
+
const path = reembedSentinelPath();
|
|
121
|
+
await mkdir(dirname(path), { recursive: true });
|
|
122
|
+
await writeFile(path, "");
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
/**
|
|
126
|
+
* Remove the reembed sentinel after the lifecycle hook has enqueued the
|
|
127
|
+
* `memory_v2_reembed` job. Idempotent — missing-file is not an error.
|
|
128
|
+
*/
|
|
129
|
+
export async function clearReembedSentinel(): Promise<void> {
|
|
130
|
+
try {
|
|
131
|
+
await unlink(reembedSentinelPath());
|
|
132
|
+
} catch (err) {
|
|
133
|
+
if ((err as NodeJS.ErrnoException).code !== "ENOENT") throw err;
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
|
|
89
137
|
/** Lazily create a Qdrant REST client bound to the resolved URL. */
|
|
90
138
|
function getClient(): QdrantRestClient {
|
|
91
139
|
if (_client) return _client;
|
|
@@ -127,7 +175,11 @@ async function ensureConceptPageCollectionOnce(): Promise<{
|
|
|
127
175
|
const vectorSize = config.memory.qdrant.vectorSize;
|
|
128
176
|
const onDisk = config.memory.qdrant.onDisk;
|
|
129
177
|
|
|
130
|
-
|
|
178
|
+
// A leftover sentinel means a prior call deleted the collection but never
|
|
179
|
+
// got to enqueue the reembed (e.g. createCollection threw, or the process
|
|
180
|
+
// died mid-rebuild). Carry that signal forward until the lifecycle hook
|
|
181
|
+
// clears it.
|
|
182
|
+
let migrated = reembedSentinelExists();
|
|
131
183
|
|
|
132
184
|
try {
|
|
133
185
|
const exists = await client.collectionExists(MEMORY_V2_COLLECTION);
|
|
@@ -156,6 +208,10 @@ async function ensureConceptPageCollectionOnce(): Promise<{
|
|
|
156
208
|
{ collection: MEMORY_V2_COLLECTION, missingNamedVectors: missing },
|
|
157
209
|
"Memory v2 concept-page collection schema drift detected — deleting and recreating; embeddings will be regenerated by background reembed",
|
|
158
210
|
);
|
|
211
|
+
// Persist the reembed intent BEFORE the destructive delete so a crash
|
|
212
|
+
// (or transient createCollection failure) between delete and recreate
|
|
213
|
+
// still triggers reembed on the next ensure call.
|
|
214
|
+
await writeReembedSentinel();
|
|
159
215
|
await client.deleteCollection(MEMORY_V2_COLLECTION);
|
|
160
216
|
migrated = true;
|
|
161
217
|
// Fall through to creation below.
|
|
@@ -279,10 +335,17 @@ export async function upsertConceptPageEmbedding(params: {
|
|
|
279
335
|
sparse: SparseEmbedding;
|
|
280
336
|
summary?: { dense: number[]; sparse: SparseEmbedding };
|
|
281
337
|
updatedAt: number;
|
|
338
|
+
/**
|
|
339
|
+
* Optional payload discriminator. Used to distinguish skill-seeded points
|
|
340
|
+
* (`kind: "skill"`) from user-authored concept pages so namespace pruning
|
|
341
|
+
* via {@link pruneSlugsWithPrefixExcept} can scope deletes to a single kind.
|
|
342
|
+
* Omitted for plain concept pages.
|
|
343
|
+
*/
|
|
344
|
+
kind?: string;
|
|
282
345
|
}): Promise<void> {
|
|
283
346
|
await ensureConceptPageCollection();
|
|
284
347
|
|
|
285
|
-
const { slug, dense, sparse, summary, updatedAt } = params;
|
|
348
|
+
const { slug, dense, sparse, summary, updatedAt, kind } = params;
|
|
286
349
|
const client = getClient();
|
|
287
350
|
const pointId = pointIdForSlug(slug);
|
|
288
351
|
|
|
@@ -296,6 +359,9 @@ export async function upsertConceptPageEmbedding(params: {
|
|
|
296
359
|
vector.summary_sparse = summary.sparse;
|
|
297
360
|
}
|
|
298
361
|
|
|
362
|
+
const payload: Record<string, unknown> = { slug, updated_at: updatedAt };
|
|
363
|
+
if (kind !== undefined) payload.kind = kind;
|
|
364
|
+
|
|
299
365
|
const upsertOnce = () =>
|
|
300
366
|
client.upsert(MEMORY_V2_COLLECTION, {
|
|
301
367
|
wait: true,
|
|
@@ -303,7 +369,7 @@ export async function upsertConceptPageEmbedding(params: {
|
|
|
303
369
|
{
|
|
304
370
|
id: pointId,
|
|
305
371
|
vector,
|
|
306
|
-
payload
|
|
372
|
+
payload,
|
|
307
373
|
},
|
|
308
374
|
],
|
|
309
375
|
});
|
|
@@ -352,17 +418,25 @@ export async function deleteConceptPageEmbedding(slug: string): Promise<void> {
|
|
|
352
418
|
* since skills now share the concept-page collection rather than living in a
|
|
353
419
|
* dedicated one.
|
|
354
420
|
*
|
|
421
|
+
* `kind` scopes pruning to a payload discriminator: only points whose
|
|
422
|
+
* `payload.kind` matches are eligible for deletion. This is critical because
|
|
423
|
+
* `validateSlug` permits user-authored concept pages slugged like
|
|
424
|
+
* `skills/foo`; without a kind filter they would collide with the skill
|
|
425
|
+
* namespace and be repeatedly pruned every seed run.
|
|
426
|
+
*
|
|
355
427
|
* Idempotent: when the live `<prefix>*` slugs already match `activeSuffixes`,
|
|
356
428
|
* the function performs a single scroll and no deletes.
|
|
357
429
|
*/
|
|
358
430
|
export async function pruneSlugsWithPrefixExcept(
|
|
359
431
|
prefix: string,
|
|
360
432
|
activeSuffixes: readonly string[],
|
|
433
|
+
options: { kind?: string } = {},
|
|
361
434
|
): Promise<void> {
|
|
362
435
|
await ensureConceptPageCollection();
|
|
363
436
|
|
|
364
437
|
const client = getClient();
|
|
365
438
|
const activeSet = new Set(activeSuffixes);
|
|
439
|
+
const requiredKind = options.kind;
|
|
366
440
|
|
|
367
441
|
const doPrune = async (): Promise<void> => {
|
|
368
442
|
const stalePointIds: Array<string | number> = [];
|
|
@@ -377,9 +451,15 @@ export async function pruneSlugsWithPrefixExcept(
|
|
|
377
451
|
...(offset !== undefined ? { offset } : {}),
|
|
378
452
|
});
|
|
379
453
|
for (const point of result.points) {
|
|
380
|
-
const
|
|
454
|
+
const payload = point.payload as {
|
|
455
|
+
slug?: unknown;
|
|
456
|
+
kind?: unknown;
|
|
457
|
+
} | null;
|
|
458
|
+
const slug = payload?.slug;
|
|
381
459
|
if (typeof slug !== "string") continue;
|
|
382
460
|
if (!slug.startsWith(prefix)) continue;
|
|
461
|
+
if (requiredKind !== undefined && payload?.kind !== requiredKind)
|
|
462
|
+
continue;
|
|
383
463
|
const suffix = slug.slice(prefix.length);
|
|
384
464
|
if (!activeSet.has(suffix)) {
|
|
385
465
|
stalePointIds.push(point.id);
|
|
@@ -435,6 +515,22 @@ export async function countConceptPagePoints(): Promise<number> {
|
|
|
435
515
|
}
|
|
436
516
|
}
|
|
437
517
|
|
|
518
|
+
/**
|
|
519
|
+
* Probe whether the v2 concept-page collection currently exists in Qdrant
|
|
520
|
+
* **without** triggering creation. Read-only diagnostics use this to avoid
|
|
521
|
+
* the side effect of bootstrapping storage just by inspecting it.
|
|
522
|
+
*/
|
|
523
|
+
export async function conceptPageCollectionExists(): Promise<boolean> {
|
|
524
|
+
const client = getClient();
|
|
525
|
+
try {
|
|
526
|
+
const result = await client.collectionExists(MEMORY_V2_COLLECTION);
|
|
527
|
+
return result.exists;
|
|
528
|
+
} catch (err) {
|
|
529
|
+
if (isCollectionMissing(err)) return false;
|
|
530
|
+
throw err;
|
|
531
|
+
}
|
|
532
|
+
}
|
|
533
|
+
|
|
438
534
|
/**
|
|
439
535
|
* Best-effort delete of the legacy `memory_v2_skills` Qdrant collection. Skill
|
|
440
536
|
* embeddings now live alongside concept pages in `memory_v2_concept_pages`
|
|
@@ -579,89 +675,6 @@ export async function hybridQueryConceptPages(
|
|
|
579
675
|
return Array.from(merged.values());
|
|
580
676
|
}
|
|
581
677
|
|
|
582
|
-
/**
|
|
583
|
-
* Page through the v2 concept-page collection and return up to `maxSamples`
|
|
584
|
-
* stored dense vectors. Used by the anisotropy-fit pipeline to compute a
|
|
585
|
-
* corpus mean + top-k principal components without re-embedding every page.
|
|
586
|
-
*
|
|
587
|
-
* Sparse vectors are skipped — anisotropy is a dense-embedding phenomenon, and
|
|
588
|
-
* pulling the sparse side would just inflate the response. Payload is also
|
|
589
|
-
* skipped because the fit doesn't need slug identity.
|
|
590
|
-
*
|
|
591
|
-
* Returns an empty array when the collection is empty or missing. Caller
|
|
592
|
-
* decides what to do (typically: surface a "no vectors to fit" error).
|
|
593
|
-
*/
|
|
594
|
-
export async function sampleConceptPageDenseVectors(
|
|
595
|
-
maxSamples: number,
|
|
596
|
-
): Promise<number[][]> {
|
|
597
|
-
if (maxSamples <= 0) return [];
|
|
598
|
-
await ensureConceptPageCollection();
|
|
599
|
-
|
|
600
|
-
const client = getClient();
|
|
601
|
-
const out: number[][] = [];
|
|
602
|
-
let offset: string | number | undefined = undefined;
|
|
603
|
-
// Same pagination guard pattern as the rest of the file — bounds the loop
|
|
604
|
-
// even if Qdrant somehow keeps handing back a non-null offset.
|
|
605
|
-
const maxIterations = 10_000;
|
|
606
|
-
const batchSize = Math.min(256, maxSamples);
|
|
607
|
-
|
|
608
|
-
for (let i = 0; i < maxIterations; i++) {
|
|
609
|
-
if (out.length >= maxSamples) break;
|
|
610
|
-
const remaining = maxSamples - out.length;
|
|
611
|
-
let result;
|
|
612
|
-
try {
|
|
613
|
-
result = await client.scroll(MEMORY_V2_COLLECTION, {
|
|
614
|
-
limit: Math.min(batchSize, remaining),
|
|
615
|
-
with_payload: false,
|
|
616
|
-
// Fetch only the dense named vector — sparse is irrelevant for
|
|
617
|
-
// anisotropy correction.
|
|
618
|
-
with_vector: ["dense"],
|
|
619
|
-
...(offset !== undefined ? { offset } : {}),
|
|
620
|
-
});
|
|
621
|
-
} catch (err) {
|
|
622
|
-
if (isCollectionMissing(err)) {
|
|
623
|
-
_collectionReady = false;
|
|
624
|
-
return out;
|
|
625
|
-
}
|
|
626
|
-
throw err;
|
|
627
|
-
}
|
|
628
|
-
|
|
629
|
-
for (const point of result.points) {
|
|
630
|
-
const v = extractDenseVector(point.vector);
|
|
631
|
-
if (v) out.push(v);
|
|
632
|
-
if (out.length >= maxSamples) break;
|
|
633
|
-
}
|
|
634
|
-
|
|
635
|
-
const next = result.next_page_offset;
|
|
636
|
-
if (next == null) break;
|
|
637
|
-
offset = typeof next === "string" ? next : (next as number);
|
|
638
|
-
}
|
|
639
|
-
|
|
640
|
-
return out;
|
|
641
|
-
}
|
|
642
|
-
|
|
643
|
-
/**
|
|
644
|
-
* Pull the `dense` named-vector payload out of a Qdrant point. Defensively
|
|
645
|
-
* handles both the named-vector shape (`{ dense: [...] }`) and the legacy
|
|
646
|
-
* unnamed-vector shape (`number[]`) so older collection layouts don't trip
|
|
647
|
-
* the sampler. Returns `null` for shapes we don't recognise.
|
|
648
|
-
*/
|
|
649
|
-
function extractDenseVector(vector: unknown): number[] | null {
|
|
650
|
-
if (Array.isArray(vector)) {
|
|
651
|
-
if (vector.every((n) => typeof n === "number")) {
|
|
652
|
-
return vector as number[];
|
|
653
|
-
}
|
|
654
|
-
return null;
|
|
655
|
-
}
|
|
656
|
-
if (vector && typeof vector === "object") {
|
|
657
|
-
const dense = (vector as { dense?: unknown }).dense;
|
|
658
|
-
if (Array.isArray(dense) && dense.every((n) => typeof n === "number")) {
|
|
659
|
-
return dense as number[];
|
|
660
|
-
}
|
|
661
|
-
}
|
|
662
|
-
return null;
|
|
663
|
-
}
|
|
664
|
-
|
|
665
678
|
/**
|
|
666
679
|
* Detect "collection not found" errors so callers can reset readiness and
|
|
667
680
|
* retry after an external deletion (e.g. workspace reset).
|
|
@@ -10,8 +10,8 @@ import { readPage } from "./page-store.js";
|
|
|
10
10
|
|
|
11
11
|
const log = getLogger("memory-v2-reranker");
|
|
12
12
|
|
|
13
|
-
//
|
|
14
|
-
const PASSAGE_CHAR_CAP =
|
|
13
|
+
// Cap passage input to bound batched payload size and tokenization cost.
|
|
14
|
+
const PASSAGE_CHAR_CAP = 1500;
|
|
15
15
|
|
|
16
16
|
interface CacheEntry {
|
|
17
17
|
scores: Map<string, number>;
|
|
@@ -22,9 +22,16 @@ const CACHE_TTL_MS = 2 * 60 * 1000;
|
|
|
22
22
|
const CACHE_MAX_ENTRIES = 64;
|
|
23
23
|
const cache = new Map<string, CacheEntry>();
|
|
24
24
|
|
|
25
|
-
function cacheKey(
|
|
25
|
+
function cacheKey(
|
|
26
|
+
query: string,
|
|
27
|
+
slugs: readonly string[],
|
|
28
|
+
model: string,
|
|
29
|
+
dtype: string,
|
|
30
|
+
): string {
|
|
26
31
|
const sorted = [...slugs].sort().join("\0");
|
|
27
|
-
return createHash("sha256")
|
|
32
|
+
return createHash("sha256")
|
|
33
|
+
.update(`${model}\0${dtype}\0${query}\0${sorted}`)
|
|
34
|
+
.digest("hex");
|
|
28
35
|
}
|
|
29
36
|
|
|
30
37
|
function evictExpired(now: number): void {
|
|
@@ -73,6 +80,7 @@ export async function rerankCandidates(
|
|
|
73
80
|
if (queries.length === 0) return [];
|
|
74
81
|
if (candidates.length === 0) return queries.map(() => new Map());
|
|
75
82
|
|
|
83
|
+
const { model, dtype } = config.memory.v2.rerank;
|
|
76
84
|
const now = Date.now();
|
|
77
85
|
evictExpired(now);
|
|
78
86
|
|
|
@@ -84,7 +92,7 @@ export async function rerankCandidates(
|
|
|
84
92
|
results[i] = new Map();
|
|
85
93
|
continue;
|
|
86
94
|
}
|
|
87
|
-
const key = cacheKey(q, candidates);
|
|
95
|
+
const key = cacheKey(q, candidates, model, dtype);
|
|
88
96
|
const cached = cache.get(key);
|
|
89
97
|
if (cached) {
|
|
90
98
|
// Refresh insertion order so frequently-hit entries survive eviction.
|
|
@@ -137,7 +145,6 @@ export async function rerankCandidates(
|
|
|
137
145
|
}
|
|
138
146
|
}
|
|
139
147
|
|
|
140
|
-
const { model, dtype } = config.memory.v2.rerank;
|
|
141
148
|
let scores: number[];
|
|
142
149
|
try {
|
|
143
150
|
const backend = getOrCreateRerankBackend(model, dtype);
|
|
@@ -162,7 +169,7 @@ export async function rerankCandidates(
|
|
|
162
169
|
result.set(slugsForPassages[i], Math.max(0, Math.min(1, s)));
|
|
163
170
|
}
|
|
164
171
|
results[qi] = result;
|
|
165
|
-
cache.set(cacheKey(queries[qi], candidates), {
|
|
172
|
+
cache.set(cacheKey(queries[qi], candidates, model, dtype), {
|
|
166
173
|
scores: new Map(result),
|
|
167
174
|
ts: now,
|
|
168
175
|
});
|
|
@@ -0,0 +1,322 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Memory v2 — Sonnet router orchestration.
|
|
3
|
+
*
|
|
4
|
+
* Replaces the per-turn spreading-activation page selector with a single LLM
|
|
5
|
+
* call. Given the rendered page index, the most recent user/assistant turn,
|
|
6
|
+
* and the list of pages already injected on prior turns, the router returns a
|
|
7
|
+
* small set of concept-page IDs to inject for the next reply.
|
|
8
|
+
*
|
|
9
|
+
* The design mirrors `sweep-job.ts`:
|
|
10
|
+
* - resolve the configured provider via `getConfiguredProvider`,
|
|
11
|
+
* - call `provider.sendMessage` with a forced `tool_choice`,
|
|
12
|
+
* - validate the tool input via Zod,
|
|
13
|
+
* - map back to slugs and let the caller drive injection.
|
|
14
|
+
*
|
|
15
|
+
* Cache strategy. Two 1h ephemeral breakpoints carry the bulk of the
|
|
16
|
+
* routing cost across turns:
|
|
17
|
+
* 1. The last text block of the system prompt — the page index is the
|
|
18
|
+
* single largest input and changes only when concept pages are edited.
|
|
19
|
+
* Auto-applied by the Anthropic provider at the configured 1h TTL.
|
|
20
|
+
* 2. The first user-message block (`<now>`) — stable across most turns
|
|
21
|
+
* since NOW.md only changes when the model rewrites it. We set the 1h
|
|
22
|
+
* TTL explicitly here to match the provider-side breakpoints; the
|
|
23
|
+
* default 5m would force unnecessary cache re-creation.
|
|
24
|
+
* The Anthropic provider also auto-applies a 1h breakpoint on the last text
|
|
25
|
+
* block of a turn-starting user message, so the trailing uncached block does
|
|
26
|
+
* not need an explicit `cache_control`.
|
|
27
|
+
*
|
|
28
|
+
* This module is pure orchestration — it does not mutate activation state,
|
|
29
|
+
* write any files, or update the conversation. PR 10 wires it into
|
|
30
|
+
* `injectMemoryV2Block`; until then nothing in the daemon calls it.
|
|
31
|
+
*/
|
|
32
|
+
|
|
33
|
+
import { z } from "zod";
|
|
34
|
+
|
|
35
|
+
import type { AssistantConfig } from "../../config/types.js";
|
|
36
|
+
import {
|
|
37
|
+
getAssistantName,
|
|
38
|
+
resolveUserName,
|
|
39
|
+
} from "../../daemon/identity-helpers.js";
|
|
40
|
+
import {
|
|
41
|
+
extractToolUse,
|
|
42
|
+
getConfiguredProvider,
|
|
43
|
+
} from "../../providers/provider-send-message.js";
|
|
44
|
+
import type {
|
|
45
|
+
ContentBlock,
|
|
46
|
+
Message,
|
|
47
|
+
ToolDefinition,
|
|
48
|
+
} from "../../providers/types.js";
|
|
49
|
+
import { getLogger } from "../../util/logger.js";
|
|
50
|
+
import { getPageIndex } from "./page-index.js";
|
|
51
|
+
import { resolveRouterPrompt } from "./prompts/router.js";
|
|
52
|
+
import type { EverInjectedEntry } from "./types.js";
|
|
53
|
+
|
|
54
|
+
const log = getLogger("memory-v2-router");
|
|
55
|
+
|
|
56
|
+
/**
|
|
57
|
+
* Reasons the router may fall short of returning a usable selection. The
|
|
58
|
+
* caller (PR 10) maps each reason to a fallback path; the closed string-
|
|
59
|
+
* literal union lets that dispatch stay exhaustive without a brittle
|
|
60
|
+
* free-form string match.
|
|
61
|
+
*/
|
|
62
|
+
export type RouterFailureReason =
|
|
63
|
+
| "no_provider"
|
|
64
|
+
| "tool_use_missing"
|
|
65
|
+
| "schema_mismatch"
|
|
66
|
+
| "api_error"
|
|
67
|
+
| "empty_index";
|
|
68
|
+
|
|
69
|
+
/**
|
|
70
|
+
* Result of a single router call. `selectedSlugs` preserves the order the
|
|
71
|
+
* model returned and is already capped at `config.memory.v2.router.max_page_ids`
|
|
72
|
+
* with out-of-range IDs dropped.
|
|
73
|
+
*/
|
|
74
|
+
export interface RouterResult {
|
|
75
|
+
/** Selected page slugs in the order the model returned them. */
|
|
76
|
+
selectedSlugs: string[];
|
|
77
|
+
/** `null` on success; one of the failure reasons above otherwise. */
|
|
78
|
+
failureReason: RouterFailureReason | null;
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
/** Tool name forced via `tool_choice`. Single shared constant so tests can match it. */
|
|
82
|
+
const ROUTER_TOOL_NAME = "select_pages_to_inject";
|
|
83
|
+
|
|
84
|
+
/**
|
|
85
|
+
* Build the tool definition handed to the provider. The JSON schema is what
|
|
86
|
+
* the model sees; the Zod schema below validates the response at runtime.
|
|
87
|
+
*
|
|
88
|
+
* `maxItems` mirrors the runtime `config.memory.v2.router.max_page_ids` cap
|
|
89
|
+
* so the model is told the same upper bound the post-call truncation
|
|
90
|
+
* enforces. Built per-call rather than module-scoped because the cap is
|
|
91
|
+
* configurable per workspace.
|
|
92
|
+
*/
|
|
93
|
+
function buildRouterTool(maxPageIds: number): ToolDefinition {
|
|
94
|
+
return {
|
|
95
|
+
name: ROUTER_TOOL_NAME,
|
|
96
|
+
description: `Choose up to ${maxPageIds} concept page IDs to inject for the next reply. Lean toward inclusion when in doubt — missing a relevant page is a worse error than surfacing unused ones. Return [] only when nothing in the index plausibly bears on the turn.`,
|
|
97
|
+
input_schema: {
|
|
98
|
+
type: "object",
|
|
99
|
+
properties: {
|
|
100
|
+
page_ids: {
|
|
101
|
+
type: "array",
|
|
102
|
+
items: { type: "integer" },
|
|
103
|
+
maxItems: maxPageIds,
|
|
104
|
+
},
|
|
105
|
+
},
|
|
106
|
+
required: ["page_ids"],
|
|
107
|
+
},
|
|
108
|
+
};
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
const RouterResultSchema = z.object({
|
|
112
|
+
page_ids: z.array(z.number().int()),
|
|
113
|
+
});
|
|
114
|
+
|
|
115
|
+
/** Empty-result helper so call sites don't reconstruct the shape inline. */
|
|
116
|
+
function emptyResult(reason: RouterFailureReason | null): RouterResult {
|
|
117
|
+
return { selectedSlugs: [], failureReason: reason };
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
interface RunRouterParams {
|
|
121
|
+
workspaceDir: string;
|
|
122
|
+
userMessage: string;
|
|
123
|
+
assistantMessage: string;
|
|
124
|
+
/** Verbatim contents to inject into `<now>...</now>` on this turn. */
|
|
125
|
+
nowText: string;
|
|
126
|
+
/** Slugs already injected on prior turns (used to seed `<already_injected_ids>`). */
|
|
127
|
+
priorEverInjected: readonly EverInjectedEntry[];
|
|
128
|
+
config: AssistantConfig;
|
|
129
|
+
signal?: AbortSignal;
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
/**
|
|
133
|
+
* Run the router for one turn. The implementation steps (mirroring
|
|
134
|
+
* `sweep-job.ts` end-to-end):
|
|
135
|
+
*
|
|
136
|
+
* 1. Build the page index. If the workspace has no concept pages and no
|
|
137
|
+
* seeded skill entries, abstain immediately with `empty_index`.
|
|
138
|
+
* 2. Resolve the configured provider for the `memoryRouter` call site.
|
|
139
|
+
* Missing → `no_provider` so the caller can fall back to spreading
|
|
140
|
+
* activation or an empty injection.
|
|
141
|
+
* 3. Build system + user prompts. The system prompt is the rendered
|
|
142
|
+
* router template with the page index inlined and gets one ephemeral
|
|
143
|
+
* breakpoint at the end (the page-index block). The user message is
|
|
144
|
+
* *two* text blocks: the cached `<now>` block and the uncached
|
|
145
|
+
* already-injected/last-turn block.
|
|
146
|
+
* 4. Force `tool_choice` so the model can only emit `select_pages_to_inject`.
|
|
147
|
+
* 5. Parse the tool input via Zod. Anything off-shape collapses to
|
|
148
|
+
* `schema_mismatch`.
|
|
149
|
+
* 6. Map IDs to slugs through the page index, dropping IDs outside
|
|
150
|
+
* `[1, N]` and truncating at `max_page_ids`.
|
|
151
|
+
*
|
|
152
|
+
* Any uncaught throw inside the call (network, provider SDK error, abort)
|
|
153
|
+
* collapses to `api_error` and is logged at warn so callers can keep going
|
|
154
|
+
* without crashing the daemon. `AbortSignal.aborted` errors are *not*
|
|
155
|
+
* special-cased; they propagate as `api_error` because the caller treats
|
|
156
|
+
* "router didn't finish" the same regardless of cause.
|
|
157
|
+
*/
|
|
158
|
+
export async function runRouter(
|
|
159
|
+
params: RunRouterParams,
|
|
160
|
+
): Promise<RouterResult> {
|
|
161
|
+
const {
|
|
162
|
+
workspaceDir,
|
|
163
|
+
userMessage,
|
|
164
|
+
assistantMessage,
|
|
165
|
+
nowText,
|
|
166
|
+
priorEverInjected,
|
|
167
|
+
config,
|
|
168
|
+
signal,
|
|
169
|
+
} = params;
|
|
170
|
+
|
|
171
|
+
const pageIndex = await getPageIndex(workspaceDir);
|
|
172
|
+
if (pageIndex.entries.length === 0) {
|
|
173
|
+
return emptyResult("empty_index");
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
const provider = await getConfiguredProvider("memoryRouter");
|
|
177
|
+
if (!provider) {
|
|
178
|
+
log.warn("memoryRouter provider unavailable; router skipped");
|
|
179
|
+
return emptyResult("no_provider");
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
const systemPrompt = resolveRouterPrompt(
|
|
183
|
+
config.memory?.v2?.router?.router_prompt_path ?? null,
|
|
184
|
+
{
|
|
185
|
+
assistantName: getAssistantName(),
|
|
186
|
+
userName: resolveUserName(workspaceDir),
|
|
187
|
+
pageIndexBlock: pageIndex.rendered,
|
|
188
|
+
},
|
|
189
|
+
);
|
|
190
|
+
|
|
191
|
+
// Already-injected slugs that map back to a current index ID. Slugs whose
|
|
192
|
+
// page has been deleted since the prior turn drop out silently — the model
|
|
193
|
+
// only sees IDs that still resolve.
|
|
194
|
+
const priorIds: number[] = [];
|
|
195
|
+
for (const entry of priorEverInjected) {
|
|
196
|
+
const idx = pageIndex.bySlug.get(entry.slug);
|
|
197
|
+
if (idx) priorIds.push(idx.id);
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
// Cache breakpoint 2 — `<now>` is stable across most turns (NOW.md only
|
|
201
|
+
// changes when the model rewrites it), so the bulk of the user message
|
|
202
|
+
// rides the cache. We use a 1h TTL to match the system-prompt breakpoint
|
|
203
|
+
// and the provider's auto-applied breakpoints. The trailing block has no
|
|
204
|
+
// `cache_control`; the Anthropic provider auto-applies a 1h breakpoint on
|
|
205
|
+
// the last text block of a turn-starting user message, which covers it.
|
|
206
|
+
const userMsg: Message = {
|
|
207
|
+
role: "user",
|
|
208
|
+
content: [
|
|
209
|
+
cachedTextBlock(`<now>\n${nowText}\n</now>`),
|
|
210
|
+
{
|
|
211
|
+
type: "text",
|
|
212
|
+
text:
|
|
213
|
+
`<already_injected_ids>\n${priorIds.join(", ")}\n</already_injected_ids>\n\n` +
|
|
214
|
+
`<last_turn>\n[user]: ${userMessage}\n[assistant]: ${assistantMessage}\n</last_turn>`,
|
|
215
|
+
},
|
|
216
|
+
],
|
|
217
|
+
};
|
|
218
|
+
|
|
219
|
+
const maxPageIds = config.memory?.v2?.router?.max_page_ids ?? 25;
|
|
220
|
+
const routerTool = buildRouterTool(maxPageIds);
|
|
221
|
+
|
|
222
|
+
let response;
|
|
223
|
+
try {
|
|
224
|
+
response = await provider.sendMessage(
|
|
225
|
+
[userMsg],
|
|
226
|
+
[routerTool],
|
|
227
|
+
systemPrompt,
|
|
228
|
+
{
|
|
229
|
+
config: {
|
|
230
|
+
callSite: "memoryRouter" as const,
|
|
231
|
+
tool_choice: { type: "tool" as const, name: ROUTER_TOOL_NAME },
|
|
232
|
+
},
|
|
233
|
+
...(signal ? { signal } : {}),
|
|
234
|
+
},
|
|
235
|
+
);
|
|
236
|
+
} catch (err) {
|
|
237
|
+
log.warn({ err }, "Router provider call threw; treating as api_error");
|
|
238
|
+
return emptyResult("api_error");
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
const toolBlock = extractToolUse(response);
|
|
242
|
+
if (!toolBlock || toolBlock.name !== ROUTER_TOOL_NAME) {
|
|
243
|
+
log.warn(
|
|
244
|
+
{ stopReason: response.stopReason },
|
|
245
|
+
"Router model returned no select_pages_to_inject tool_use block",
|
|
246
|
+
);
|
|
247
|
+
return emptyResult("tool_use_missing");
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
const parsed = RouterResultSchema.safeParse(toolBlock.input);
|
|
251
|
+
if (!parsed.success) {
|
|
252
|
+
log.warn(
|
|
253
|
+
{ error: parsed.error.message },
|
|
254
|
+
"Router tool input did not match schema",
|
|
255
|
+
);
|
|
256
|
+
return emptyResult("schema_mismatch");
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
const N = pageIndex.entries.length;
|
|
260
|
+
|
|
261
|
+
const inRangeIds: number[] = [];
|
|
262
|
+
const droppedIds: number[] = [];
|
|
263
|
+
for (const id of parsed.data.page_ids) {
|
|
264
|
+
if (id >= 1 && id <= N) {
|
|
265
|
+
inRangeIds.push(id);
|
|
266
|
+
} else {
|
|
267
|
+
droppedIds.push(id);
|
|
268
|
+
}
|
|
269
|
+
}
|
|
270
|
+
if (droppedIds.length > 0) {
|
|
271
|
+
log.warn(
|
|
272
|
+
{ droppedIds, indexSize: N },
|
|
273
|
+
"Router returned page IDs outside the valid range; dropping",
|
|
274
|
+
);
|
|
275
|
+
}
|
|
276
|
+
|
|
277
|
+
const truncated = inRangeIds.length > maxPageIds;
|
|
278
|
+
const finalIds = truncated ? inRangeIds.slice(0, maxPageIds) : inRangeIds;
|
|
279
|
+
if (truncated) {
|
|
280
|
+
log.warn(
|
|
281
|
+
{ returned: inRangeIds.length, max: maxPageIds },
|
|
282
|
+
"Router returned more page IDs than max_page_ids; truncating",
|
|
283
|
+
);
|
|
284
|
+
}
|
|
285
|
+
|
|
286
|
+
// De-duplicate while preserving order — the index lookup alone wouldn't
|
|
287
|
+
// catch repeats from the model.
|
|
288
|
+
const seen = new Set<number>();
|
|
289
|
+
const selectedSlugs: string[] = [];
|
|
290
|
+
for (const id of finalIds) {
|
|
291
|
+
if (seen.has(id)) continue;
|
|
292
|
+
seen.add(id);
|
|
293
|
+
const entry = pageIndex.byId.get(id);
|
|
294
|
+
if (!entry) continue;
|
|
295
|
+
selectedSlugs.push(entry.slug);
|
|
296
|
+
}
|
|
297
|
+
|
|
298
|
+
return { selectedSlugs, failureReason: null };
|
|
299
|
+
}
|
|
300
|
+
|
|
301
|
+
/**
|
|
302
|
+
* Build a text content block carrying an ephemeral `cache_control`
|
|
303
|
+
* breakpoint with a 1h TTL. The Anthropic SDK accepts the field as an extra
|
|
304
|
+
* property on text blocks, but our internal `TextContent` type intentionally
|
|
305
|
+
* omits it (only the Anthropic provider transforms it onto the wire), so we
|
|
306
|
+
* reach through a `Record` cast here for the same reason `client.ts` does —
|
|
307
|
+
* it keeps the core types provider-agnostic. The 1h TTL matches the
|
|
308
|
+
* provider's auto-applied breakpoints (see `cacheTtl` in
|
|
309
|
+
* `providers/anthropic/client.ts`); the `<now>` block is stable across most
|
|
310
|
+
* turns, so default 5m would force unnecessary re-creation. The
|
|
311
|
+
* `extended-cache-ttl-2025-04-11` beta header is added unconditionally for
|
|
312
|
+
* non-Haiku models in `client.ts`, so this works without any call-site
|
|
313
|
+
* config.
|
|
314
|
+
*/
|
|
315
|
+
function cachedTextBlock(text: string): ContentBlock {
|
|
316
|
+
const block: ContentBlock = { type: "text", text };
|
|
317
|
+
(block as unknown as Record<string, unknown>).cache_control = {
|
|
318
|
+
type: "ephemeral",
|
|
319
|
+
ttl: "1h",
|
|
320
|
+
};
|
|
321
|
+
return block;
|
|
322
|
+
}
|