@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
|
@@ -0,0 +1,224 @@
|
|
|
1
|
+
import { beforeEach, describe, expect, test } from "bun:test";
|
|
2
|
+
|
|
3
|
+
import { _setOverridesForTesting } from "../config/assistant-feature-flags.js";
|
|
4
|
+
import {
|
|
5
|
+
applyRuntimeInjections,
|
|
6
|
+
stripInjectionsForCompaction,
|
|
7
|
+
} from "../daemon/conversation-runtime-assembly.js";
|
|
8
|
+
import {
|
|
9
|
+
DEFAULT_INJECTOR_ORDER,
|
|
10
|
+
defaultInjectorsPlugin,
|
|
11
|
+
DISK_PRESSURE_WARNING_PROMPT,
|
|
12
|
+
} from "../plugins/defaults/injectors.js";
|
|
13
|
+
import {
|
|
14
|
+
registerPlugin,
|
|
15
|
+
resetPluginRegistryForTests,
|
|
16
|
+
} from "../plugins/registry.js";
|
|
17
|
+
import type { Injector, TurnContext } from "../plugins/types.js";
|
|
18
|
+
import type { Message } from "../providers/types.js";
|
|
19
|
+
|
|
20
|
+
function findInjector(name: string): Injector {
|
|
21
|
+
const injector = defaultInjectorsPlugin.injectors?.find(
|
|
22
|
+
(candidate) => candidate.name === name,
|
|
23
|
+
);
|
|
24
|
+
if (!injector) {
|
|
25
|
+
throw new Error(`injector '${name}' not registered`);
|
|
26
|
+
}
|
|
27
|
+
return injector;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
function makeContext(overrides: Partial<TurnContext> = {}): TurnContext {
|
|
31
|
+
return {
|
|
32
|
+
requestId: "req-test",
|
|
33
|
+
conversationId: "conv-test",
|
|
34
|
+
turnIndex: 0,
|
|
35
|
+
trust: { sourceChannel: "vellum", trustClass: "guardian" },
|
|
36
|
+
...overrides,
|
|
37
|
+
};
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
function tailTexts(messages: Message[]): string[] {
|
|
41
|
+
const tail = messages[messages.length - 1];
|
|
42
|
+
if (!tail || tail.role !== "user") return [];
|
|
43
|
+
return tail.content
|
|
44
|
+
.filter((block): block is { type: "text"; text: string } => {
|
|
45
|
+
return block.type === "text";
|
|
46
|
+
})
|
|
47
|
+
.map((block) => block.text);
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
const diskPressureInjector = findInjector("disk-pressure-warning");
|
|
51
|
+
const cleanupContext = { cleanupModeActive: true };
|
|
52
|
+
|
|
53
|
+
describe("disk-pressure-warning injector", () => {
|
|
54
|
+
beforeEach(() => {
|
|
55
|
+
resetPluginRegistryForTests();
|
|
56
|
+
registerPlugin(defaultInjectorsPlugin);
|
|
57
|
+
_setOverridesForTesting({ "safe-storage-limits": true });
|
|
58
|
+
});
|
|
59
|
+
|
|
60
|
+
test("emits the exact cleanup prompt while safe storage limits are enabled", async () => {
|
|
61
|
+
const block = await diskPressureInjector.produce(
|
|
62
|
+
makeContext({
|
|
63
|
+
injectionInputs: { diskPressureContext: cleanupContext },
|
|
64
|
+
}),
|
|
65
|
+
);
|
|
66
|
+
|
|
67
|
+
expect(block).toEqual({
|
|
68
|
+
id: "disk-pressure-warning",
|
|
69
|
+
text: DISK_PRESSURE_WARNING_PROMPT,
|
|
70
|
+
placement: "prepend-user-tail",
|
|
71
|
+
});
|
|
72
|
+
expect(diskPressureInjector.order).toBe(
|
|
73
|
+
DEFAULT_INJECTOR_ORDER.diskPressureWarning,
|
|
74
|
+
);
|
|
75
|
+
expect(DISK_PRESSURE_WARNING_PROMPT).toBe(`<disk_pressure_warning>
|
|
76
|
+
Disk usage is critically low: this assistant is in storage cleanup mode because the workspace volume is at least 95% full.
|
|
77
|
+
|
|
78
|
+
In your first paragraph, warn the user that storage is critically low and that normal work is suspended until space is freed.
|
|
79
|
+
|
|
80
|
+
Then help the user clean up storage. Prefer safe inspection steps first, such as checking available space and finding large directories. Ask before deleting files or caches unless the user has already clearly approved the specific cleanup action.
|
|
81
|
+
|
|
82
|
+
Do not work on unrelated tasks until disk usage drops below the critical threshold or the user explicitly overrides the lock. Background processes and messages from trusted contacts are blocked while this cleanup mode is active.
|
|
83
|
+
</disk_pressure_warning>`);
|
|
84
|
+
});
|
|
85
|
+
|
|
86
|
+
test("omits the prompt when cleanup context is null or inactive", async () => {
|
|
87
|
+
await expect(
|
|
88
|
+
diskPressureInjector.produce(
|
|
89
|
+
makeContext({ injectionInputs: { diskPressureContext: null } }),
|
|
90
|
+
),
|
|
91
|
+
).resolves.toBeNull();
|
|
92
|
+
|
|
93
|
+
await expect(
|
|
94
|
+
diskPressureInjector.produce(
|
|
95
|
+
makeContext({
|
|
96
|
+
injectionInputs: {
|
|
97
|
+
diskPressureContext: { cleanupModeActive: false },
|
|
98
|
+
},
|
|
99
|
+
}),
|
|
100
|
+
),
|
|
101
|
+
).resolves.toBeNull();
|
|
102
|
+
});
|
|
103
|
+
|
|
104
|
+
test("omits the prompt when safe storage limits are disabled", async () => {
|
|
105
|
+
_setOverridesForTesting({ "safe-storage-limits": false });
|
|
106
|
+
|
|
107
|
+
await expect(
|
|
108
|
+
diskPressureInjector.produce(
|
|
109
|
+
makeContext({
|
|
110
|
+
injectionInputs: { diskPressureContext: cleanupContext },
|
|
111
|
+
}),
|
|
112
|
+
),
|
|
113
|
+
).resolves.toBeNull();
|
|
114
|
+
});
|
|
115
|
+
|
|
116
|
+
test("prepends ahead of workspace and unified turn context in full mode", async () => {
|
|
117
|
+
const runMessages: Message[] = [
|
|
118
|
+
{ role: "user", content: [{ type: "text", text: "clean up space" }] },
|
|
119
|
+
];
|
|
120
|
+
const workspace = "<workspace>\nRoot: /workspace\n</workspace>";
|
|
121
|
+
const turnContext = "<turn_context>\ninterface: macos\n</turn_context>";
|
|
122
|
+
|
|
123
|
+
const result = await applyRuntimeInjections(runMessages, {
|
|
124
|
+
turnContext: makeContext(),
|
|
125
|
+
diskPressureContext: cleanupContext,
|
|
126
|
+
workspaceTopLevelContext: workspace,
|
|
127
|
+
unifiedTurnContext: turnContext,
|
|
128
|
+
});
|
|
129
|
+
|
|
130
|
+
expect(tailTexts(result.messages).slice(0, 4)).toEqual([
|
|
131
|
+
DISK_PRESSURE_WARNING_PROMPT,
|
|
132
|
+
workspace,
|
|
133
|
+
turnContext,
|
|
134
|
+
"clean up space",
|
|
135
|
+
]);
|
|
136
|
+
expect(
|
|
137
|
+
result.blocks.injectorChainBlock?.startsWith(
|
|
138
|
+
DISK_PRESSURE_WARNING_PROMPT,
|
|
139
|
+
),
|
|
140
|
+
).toBe(true);
|
|
141
|
+
});
|
|
142
|
+
|
|
143
|
+
test("survives minimal mode as safety-critical context", async () => {
|
|
144
|
+
const result = await applyRuntimeInjections(
|
|
145
|
+
[{ role: "user", content: [{ type: "text", text: "status" }] }],
|
|
146
|
+
{
|
|
147
|
+
turnContext: makeContext(),
|
|
148
|
+
mode: "minimal",
|
|
149
|
+
diskPressureContext: cleanupContext,
|
|
150
|
+
workspaceTopLevelContext: "<workspace>...</workspace>",
|
|
151
|
+
unifiedTurnContext: "<turn_context>...</turn_context>",
|
|
152
|
+
},
|
|
153
|
+
);
|
|
154
|
+
|
|
155
|
+
expect(tailTexts(result.messages)).toEqual([
|
|
156
|
+
DISK_PRESSURE_WARNING_PROMPT,
|
|
157
|
+
"<turn_context>...</turn_context>",
|
|
158
|
+
"status",
|
|
159
|
+
]);
|
|
160
|
+
});
|
|
161
|
+
|
|
162
|
+
test("applies after Slack chronological transcript replacement", async () => {
|
|
163
|
+
const originalRun: Message[] = [
|
|
164
|
+
{
|
|
165
|
+
role: "user",
|
|
166
|
+
content: [{ type: "text", text: "latest raw user text" }],
|
|
167
|
+
},
|
|
168
|
+
];
|
|
169
|
+
const slackTranscript: Message[] = [
|
|
170
|
+
{
|
|
171
|
+
role: "user",
|
|
172
|
+
content: [{ type: "text", text: "[12:00 user]: earlier" }],
|
|
173
|
+
},
|
|
174
|
+
{
|
|
175
|
+
role: "user",
|
|
176
|
+
content: [{ type: "text", text: "[12:01 @assistant]: cleanup?" }],
|
|
177
|
+
},
|
|
178
|
+
];
|
|
179
|
+
|
|
180
|
+
const result = await applyRuntimeInjections(originalRun, {
|
|
181
|
+
turnContext: makeContext(),
|
|
182
|
+
diskPressureContext: cleanupContext,
|
|
183
|
+
channelCapabilities: {
|
|
184
|
+
channel: "slack",
|
|
185
|
+
dashboardCapable: false,
|
|
186
|
+
supportsDynamicUi: false,
|
|
187
|
+
supportsVoiceInput: false,
|
|
188
|
+
chatType: "channel",
|
|
189
|
+
},
|
|
190
|
+
slackChronologicalMessages: slackTranscript,
|
|
191
|
+
});
|
|
192
|
+
|
|
193
|
+
expect(result.messages).toHaveLength(2);
|
|
194
|
+
const texts = tailTexts(result.messages);
|
|
195
|
+
expect(texts[0]).toBe(DISK_PRESSURE_WARNING_PROMPT);
|
|
196
|
+
expect(
|
|
197
|
+
texts.some((text) => text.startsWith("<channel_capabilities>")),
|
|
198
|
+
).toBe(true);
|
|
199
|
+
expect(texts[texts.length - 1]).toBe("[12:01 @assistant]: cleanup?");
|
|
200
|
+
});
|
|
201
|
+
|
|
202
|
+
test("compaction strip plus re-apply does not duplicate the warning", async () => {
|
|
203
|
+
const runMessages: Message[] = [
|
|
204
|
+
{ role: "user", content: [{ type: "text", text: "find large files" }] },
|
|
205
|
+
];
|
|
206
|
+
|
|
207
|
+
const first = await applyRuntimeInjections(runMessages, {
|
|
208
|
+
turnContext: makeContext(),
|
|
209
|
+
diskPressureContext: cleanupContext,
|
|
210
|
+
});
|
|
211
|
+
const stripped = stripInjectionsForCompaction(first.messages);
|
|
212
|
+
expect(tailTexts(stripped)).toEqual(["find large files"]);
|
|
213
|
+
|
|
214
|
+
const second = await applyRuntimeInjections(stripped, {
|
|
215
|
+
turnContext: makeContext(),
|
|
216
|
+
diskPressureContext: cleanupContext,
|
|
217
|
+
});
|
|
218
|
+
expect(
|
|
219
|
+
tailTexts(second.messages).filter(
|
|
220
|
+
(text) => text === DISK_PRESSURE_WARNING_PROMPT,
|
|
221
|
+
),
|
|
222
|
+
).toHaveLength(1);
|
|
223
|
+
});
|
|
224
|
+
});
|
|
@@ -1,23 +1,26 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* v2 read-side cutover behavior for the PKB-derived default injectors.
|
|
3
3
|
*
|
|
4
|
-
* When `
|
|
4
|
+
* When `getConfig().memory.v2.enabled` is true:
|
|
5
5
|
* - `pkb-context` silences itself (concept pages own retrieval).
|
|
6
6
|
* - `pkb-reminder` still fires (its body is generic recall/remember
|
|
7
7
|
* guidance) but skips the PKB-search hints — those name PKB paths.
|
|
8
8
|
* - `now-md` fires unchanged (workspace state, independent of PKB).
|
|
9
9
|
*
|
|
10
|
-
* Mocks `
|
|
11
|
-
*
|
|
12
|
-
*
|
|
13
|
-
*
|
|
10
|
+
* Mocks `getConfig` at the module level so each test can flip the effective
|
|
11
|
+
* gate state without standing up a full config stack. Mocks the PKB hybrid
|
|
12
|
+
* search so the reminder-with-hints branch can resolve deterministically
|
|
13
|
+
* when called.
|
|
14
14
|
*/
|
|
15
15
|
import { beforeEach, describe, expect, mock, test } from "bun:test";
|
|
16
16
|
|
|
17
17
|
let v2Active = false;
|
|
18
18
|
|
|
19
|
-
|
|
20
|
-
|
|
19
|
+
const realLoader = await import("../config/loader.js");
|
|
20
|
+
|
|
21
|
+
mock.module("../config/loader.js", () => ({
|
|
22
|
+
...realLoader,
|
|
23
|
+
getConfig: () => ({ memory: { v2: { enabled: v2Active } } }),
|
|
21
24
|
}));
|
|
22
25
|
|
|
23
26
|
mock.module("../memory/pkb/pkb-search.js", () => ({
|
|
@@ -1,17 +1,17 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Tests for the memory-v2 skill seed gate
|
|
3
|
-
*
|
|
2
|
+
* Tests for the memory-v2 skill seed gate and the v2 concept-page schema
|
|
3
|
+
* rebuild gate, both invoked from the daemon startup path
|
|
4
|
+
* (`assistant/src/daemon/memory-v2-startup.ts`).
|
|
4
5
|
*
|
|
5
|
-
* The
|
|
6
|
-
* import graph. Coverage matrix
|
|
7
|
-
* -
|
|
8
|
-
*
|
|
9
|
-
* -
|
|
10
|
-
*
|
|
11
|
-
*
|
|
6
|
+
* The gates are exercised in isolation rather than mounting the full
|
|
7
|
+
* lifecycle import graph. Coverage matrix:
|
|
8
|
+
* - Skill seed (`maybeSeedMemoryV2Skills`): config gating, rejection
|
|
9
|
+
* swallowing.
|
|
10
|
+
* - Schema rebuild (`maybeRebuildMemoryV2Concepts`): config gating,
|
|
11
|
+
* drift-triggered reembed enqueue, empty-after-create reembed enqueue,
|
|
12
|
+
* no enqueue when collection is healthy, error swallowing.
|
|
12
13
|
*
|
|
13
|
-
*
|
|
14
|
-
* never block startup or surface an exception.
|
|
14
|
+
* Both gates must never block startup or surface an exception.
|
|
15
15
|
*/
|
|
16
16
|
import { beforeEach, describe, expect, mock, test } from "bun:test";
|
|
17
17
|
|
|
@@ -22,31 +22,36 @@ import type { AssistantConfig } from "../config/schema.js";
|
|
|
22
22
|
// ---------------------------------------------------------------------------
|
|
23
23
|
|
|
24
24
|
interface TestState {
|
|
25
|
-
flagOverrides: Record<string, boolean>;
|
|
26
25
|
seedCallCount: number;
|
|
27
26
|
seedShouldReject: Error | null;
|
|
28
27
|
warnCalls: Array<{ obj: unknown; msg: unknown }>;
|
|
28
|
+
infoCalls: Array<{ obj: unknown; msg: unknown }>;
|
|
29
|
+
// Rebuild-gate mocks (drive maybeRebuildMemoryV2Concepts).
|
|
30
|
+
ensureCollectionCallCount: number;
|
|
31
|
+
ensureCollectionResult: { migrated: boolean };
|
|
32
|
+
ensureCollectionThrows: Error | null;
|
|
33
|
+
countResult: number;
|
|
34
|
+
listPagesResult: string[];
|
|
35
|
+
enqueueCalls: Array<{ type: string; payload: Record<string, unknown> }>;
|
|
29
36
|
}
|
|
30
37
|
|
|
31
38
|
const state: TestState = {
|
|
32
|
-
flagOverrides: {},
|
|
33
39
|
seedCallCount: 0,
|
|
34
40
|
seedShouldReject: null,
|
|
35
41
|
warnCalls: [],
|
|
42
|
+
infoCalls: [],
|
|
43
|
+
ensureCollectionCallCount: 0,
|
|
44
|
+
ensureCollectionResult: { migrated: false },
|
|
45
|
+
ensureCollectionThrows: null,
|
|
46
|
+
countResult: 0,
|
|
47
|
+
listPagesResult: [],
|
|
48
|
+
enqueueCalls: [],
|
|
36
49
|
};
|
|
37
50
|
|
|
38
51
|
// ---------------------------------------------------------------------------
|
|
39
52
|
// Mocks — installed before the module under test is loaded.
|
|
40
53
|
// ---------------------------------------------------------------------------
|
|
41
54
|
|
|
42
|
-
mock.module("../config/assistant-feature-flags.js", () => ({
|
|
43
|
-
isAssistantFeatureFlagEnabled: (key: string, _config: unknown): boolean => {
|
|
44
|
-
const explicit = state.flagOverrides[key];
|
|
45
|
-
if (typeof explicit === "boolean") return explicit;
|
|
46
|
-
return true; // undeclared flags default to enabled
|
|
47
|
-
},
|
|
48
|
-
}));
|
|
49
|
-
|
|
50
55
|
mock.module("../memory/v2/skill-store.js", () => ({
|
|
51
56
|
seedV2SkillEntries: async (): Promise<void> => {
|
|
52
57
|
state.seedCallCount += 1;
|
|
@@ -54,18 +59,51 @@ mock.module("../memory/v2/skill-store.js", () => ({
|
|
|
54
59
|
},
|
|
55
60
|
}));
|
|
56
61
|
|
|
62
|
+
mock.module("../memory/v2/qdrant.js", () => ({
|
|
63
|
+
ensureConceptPageCollection: async (): Promise<{ migrated: boolean }> => {
|
|
64
|
+
state.ensureCollectionCallCount += 1;
|
|
65
|
+
if (state.ensureCollectionThrows) throw state.ensureCollectionThrows;
|
|
66
|
+
return state.ensureCollectionResult;
|
|
67
|
+
},
|
|
68
|
+
countConceptPagePoints: async (): Promise<number> => state.countResult,
|
|
69
|
+
// The rebuild gate does not call this, but the seed gate's fire-and-forget
|
|
70
|
+
// chain imports it; provide a no-op so the dynamic import resolves.
|
|
71
|
+
dropLegacySkillsCollection: async (): Promise<void> => {},
|
|
72
|
+
}));
|
|
73
|
+
|
|
74
|
+
mock.module("../memory/v2/page-store.js", () => ({
|
|
75
|
+
hasConceptPages: async (): Promise<boolean> =>
|
|
76
|
+
state.listPagesResult.length > 0,
|
|
77
|
+
}));
|
|
78
|
+
|
|
79
|
+
mock.module("../memory/jobs-store.js", () => ({
|
|
80
|
+
enqueueMemoryJob: (
|
|
81
|
+
type: string,
|
|
82
|
+
payload: Record<string, unknown>,
|
|
83
|
+
): string => {
|
|
84
|
+
state.enqueueCalls.push({ type, payload });
|
|
85
|
+
return "test-job-id";
|
|
86
|
+
},
|
|
87
|
+
}));
|
|
88
|
+
|
|
89
|
+
mock.module("../util/platform.js", () => ({
|
|
90
|
+
getWorkspaceDir: () => "/tmp/test-workspace",
|
|
91
|
+
}));
|
|
92
|
+
|
|
57
93
|
mock.module("../util/logger.js", () => ({
|
|
58
94
|
getLogger: () => ({
|
|
59
95
|
warn: (obj: unknown, msg: unknown) => {
|
|
60
96
|
state.warnCalls.push({ obj, msg });
|
|
61
97
|
},
|
|
62
|
-
info: () => {
|
|
98
|
+
info: (obj: unknown, msg: unknown) => {
|
|
99
|
+
state.infoCalls.push({ obj, msg });
|
|
100
|
+
},
|
|
63
101
|
error: () => {},
|
|
64
102
|
debug: () => {},
|
|
65
103
|
}),
|
|
66
104
|
}));
|
|
67
105
|
|
|
68
|
-
const { maybeSeedMemoryV2Skills } =
|
|
106
|
+
const { maybeSeedMemoryV2Skills, maybeRebuildMemoryV2Concepts } =
|
|
69
107
|
await import("../daemon/memory-v2-startup.js");
|
|
70
108
|
|
|
71
109
|
// ---------------------------------------------------------------------------
|
|
@@ -99,65 +137,37 @@ async function flushMicrotasks(): Promise<void> {
|
|
|
99
137
|
// Tests
|
|
100
138
|
// ---------------------------------------------------------------------------
|
|
101
139
|
|
|
140
|
+
function resetState(): void {
|
|
141
|
+
state.seedCallCount = 0;
|
|
142
|
+
state.seedShouldReject = null;
|
|
143
|
+
state.warnCalls = [];
|
|
144
|
+
state.infoCalls = [];
|
|
145
|
+
state.ensureCollectionCallCount = 0;
|
|
146
|
+
state.ensureCollectionResult = { migrated: false };
|
|
147
|
+
state.ensureCollectionThrows = null;
|
|
148
|
+
state.countResult = 0;
|
|
149
|
+
state.listPagesResult = [];
|
|
150
|
+
state.enqueueCalls = [];
|
|
151
|
+
}
|
|
152
|
+
|
|
102
153
|
describe("maybeSeedMemoryV2Skills (daemon startup gate)", () => {
|
|
103
|
-
beforeEach(
|
|
104
|
-
state.flagOverrides = {};
|
|
105
|
-
state.seedCallCount = 0;
|
|
106
|
-
state.seedShouldReject = null;
|
|
107
|
-
state.warnCalls = [];
|
|
108
|
-
});
|
|
154
|
+
beforeEach(resetState);
|
|
109
155
|
|
|
110
|
-
test("invokes seedV2SkillEntries when
|
|
111
|
-
state.flagOverrides = { "memory-v2-enabled": true };
|
|
156
|
+
test("invokes seedV2SkillEntries when memory.v2.enabled is true", async () => {
|
|
112
157
|
maybeSeedMemoryV2Skills(makeConfig(true));
|
|
113
158
|
await flushMicrotasks();
|
|
114
159
|
expect(state.seedCallCount).toBe(1);
|
|
115
160
|
expect(state.warnCalls).toHaveLength(0);
|
|
116
161
|
});
|
|
117
162
|
|
|
118
|
-
test("does not invoke seedV2SkillEntries when
|
|
119
|
-
state.flagOverrides = { "memory-v2-enabled": false };
|
|
120
|
-
maybeSeedMemoryV2Skills(makeConfig(true));
|
|
121
|
-
await flushMicrotasks();
|
|
122
|
-
expect(state.seedCallCount).toBe(0);
|
|
123
|
-
expect(state.warnCalls).toHaveLength(0);
|
|
124
|
-
});
|
|
125
|
-
|
|
126
|
-
test("does not invoke seedV2SkillEntries when config.memory.v2.enabled is off", async () => {
|
|
127
|
-
state.flagOverrides = { "memory-v2-enabled": true };
|
|
128
|
-
maybeSeedMemoryV2Skills(makeConfig(false));
|
|
129
|
-
await flushMicrotasks();
|
|
130
|
-
expect(state.seedCallCount).toBe(0);
|
|
131
|
-
expect(state.warnCalls).toHaveLength(0);
|
|
132
|
-
});
|
|
133
|
-
|
|
134
|
-
test("does not invoke seedV2SkillEntries when both gates are off", async () => {
|
|
135
|
-
state.flagOverrides = { "memory-v2-enabled": false };
|
|
163
|
+
test("does not invoke seedV2SkillEntries when memory.v2.enabled is false", async () => {
|
|
136
164
|
maybeSeedMemoryV2Skills(makeConfig(false));
|
|
137
165
|
await flushMicrotasks();
|
|
138
166
|
expect(state.seedCallCount).toBe(0);
|
|
139
167
|
expect(state.warnCalls).toHaveLength(0);
|
|
140
168
|
});
|
|
141
169
|
|
|
142
|
-
test("re-invocation seeds after flag flips on (deferred-init race recovery)", async () => {
|
|
143
|
-
// Models the lifecycle-startup race: the synchronous seed call evaluates
|
|
144
|
-
// the flag while the gateway IPC override fetch is still in flight, falls
|
|
145
|
-
// through to the registry default (`false`), and skips. Once
|
|
146
|
-
// `initFeatureFlagOverrides()` resolves, the chained `.then` re-invokes
|
|
147
|
-
// the seed with the now-populated cache and the flag flips to `true`.
|
|
148
|
-
state.flagOverrides = { "memory-v2-enabled": false };
|
|
149
|
-
maybeSeedMemoryV2Skills(makeConfig(true));
|
|
150
|
-
await flushMicrotasks();
|
|
151
|
-
expect(state.seedCallCount).toBe(0);
|
|
152
|
-
|
|
153
|
-
state.flagOverrides = { "memory-v2-enabled": true };
|
|
154
|
-
maybeSeedMemoryV2Skills(makeConfig(true));
|
|
155
|
-
await flushMicrotasks();
|
|
156
|
-
expect(state.seedCallCount).toBe(1);
|
|
157
|
-
});
|
|
158
|
-
|
|
159
170
|
test("swallows seedV2SkillEntries rejections and logs a warning", async () => {
|
|
160
|
-
state.flagOverrides = { "memory-v2-enabled": true };
|
|
161
171
|
state.seedShouldReject = new Error("seed failed");
|
|
162
172
|
|
|
163
173
|
// The gate must not throw — startup must not block on this.
|
|
@@ -172,3 +182,80 @@ describe("maybeSeedMemoryV2Skills (daemon startup gate)", () => {
|
|
|
172
182
|
expect(msg).toBe("Failed to seed v2 skill entries");
|
|
173
183
|
});
|
|
174
184
|
});
|
|
185
|
+
|
|
186
|
+
describe("maybeRebuildMemoryV2Concepts (daemon startup gate)", () => {
|
|
187
|
+
beforeEach(resetState);
|
|
188
|
+
|
|
189
|
+
test("does nothing when memory.v2.enabled is false", async () => {
|
|
190
|
+
await maybeRebuildMemoryV2Concepts(makeConfig(false));
|
|
191
|
+
|
|
192
|
+
expect(state.ensureCollectionCallCount).toBe(0);
|
|
193
|
+
expect(state.enqueueCalls).toEqual([]);
|
|
194
|
+
});
|
|
195
|
+
|
|
196
|
+
test("enqueues memory_v2_reembed when the collection was migrated", async () => {
|
|
197
|
+
state.ensureCollectionResult = { migrated: true };
|
|
198
|
+
|
|
199
|
+
await maybeRebuildMemoryV2Concepts(makeConfig(true));
|
|
200
|
+
|
|
201
|
+
expect(state.ensureCollectionCallCount).toBe(1);
|
|
202
|
+
expect(state.enqueueCalls).toEqual([
|
|
203
|
+
{ type: "memory_v2_reembed", payload: {} },
|
|
204
|
+
]);
|
|
205
|
+
// Migrated path skips the count probe — drift detection is the trigger.
|
|
206
|
+
// (The mock's countConceptPagePoints would silently return 0 either way,
|
|
207
|
+
// but keeping the path conditional keeps the lifecycle hook predictable.)
|
|
208
|
+
});
|
|
209
|
+
|
|
210
|
+
test("enqueues reembed when the collection is empty but pages exist on disk (crash-mid-rebuild recovery)", async () => {
|
|
211
|
+
state.ensureCollectionResult = { migrated: false };
|
|
212
|
+
state.countResult = 0;
|
|
213
|
+
state.listPagesResult = ["people/alice", "topics/zsh"];
|
|
214
|
+
|
|
215
|
+
await maybeRebuildMemoryV2Concepts(makeConfig(true));
|
|
216
|
+
|
|
217
|
+
expect(state.enqueueCalls).toEqual([
|
|
218
|
+
{ type: "memory_v2_reembed", payload: {} },
|
|
219
|
+
]);
|
|
220
|
+
});
|
|
221
|
+
|
|
222
|
+
test("does not enqueue when the collection is healthy and populated", async () => {
|
|
223
|
+
state.ensureCollectionResult = { migrated: false };
|
|
224
|
+
state.countResult = 1185;
|
|
225
|
+
state.listPagesResult = ["people/alice"];
|
|
226
|
+
|
|
227
|
+
await maybeRebuildMemoryV2Concepts(makeConfig(true));
|
|
228
|
+
|
|
229
|
+
expect(state.enqueueCalls).toEqual([]);
|
|
230
|
+
});
|
|
231
|
+
|
|
232
|
+
test("does not enqueue when the collection is empty AND no pages exist on disk (fresh workspace)", async () => {
|
|
233
|
+
state.ensureCollectionResult = { migrated: false };
|
|
234
|
+
state.countResult = 0;
|
|
235
|
+
state.listPagesResult = [];
|
|
236
|
+
|
|
237
|
+
await maybeRebuildMemoryV2Concepts(makeConfig(true));
|
|
238
|
+
|
|
239
|
+
expect(state.enqueueCalls).toEqual([]);
|
|
240
|
+
});
|
|
241
|
+
|
|
242
|
+
test("swallows ensureConceptPageCollection failures and logs a warning", async () => {
|
|
243
|
+
state.ensureCollectionThrows = new Error("Qdrant unreachable");
|
|
244
|
+
|
|
245
|
+
// Must not throw — startup never blocks on this gate.
|
|
246
|
+
let thrown: unknown = null;
|
|
247
|
+
try {
|
|
248
|
+
await maybeRebuildMemoryV2Concepts(makeConfig(true));
|
|
249
|
+
} catch (err) {
|
|
250
|
+
thrown = err;
|
|
251
|
+
}
|
|
252
|
+
expect(thrown).toBeNull();
|
|
253
|
+
|
|
254
|
+
expect(state.enqueueCalls).toEqual([]);
|
|
255
|
+
expect(state.warnCalls.length).toBeGreaterThan(0);
|
|
256
|
+
const lastWarn = state.warnCalls[state.warnCalls.length - 1];
|
|
257
|
+
expect((lastWarn.obj as { err: Error }).err.message).toBe(
|
|
258
|
+
"Qdrant unreachable",
|
|
259
|
+
);
|
|
260
|
+
});
|
|
261
|
+
});
|
|
@@ -125,6 +125,16 @@ describe("PUT /v1/config/llm/profiles/:name — managed profile guard", () => {
|
|
|
125
125
|
).toThrow(BadRequestError);
|
|
126
126
|
});
|
|
127
127
|
|
|
128
|
+
test("allows edits to custom-balanced (user-owned)", () => {
|
|
129
|
+
savedRaw = null;
|
|
130
|
+
const result = replaceRoute.handler({
|
|
131
|
+
pathParams: { name: "custom-balanced" },
|
|
132
|
+
body: { provider: "openai", model: "gpt-4o" },
|
|
133
|
+
});
|
|
134
|
+
expect(result).toEqual({ ok: true });
|
|
135
|
+
expect(savedRaw).not.toBeNull();
|
|
136
|
+
});
|
|
137
|
+
|
|
128
138
|
test("allows edits to a user-defined profile", () => {
|
|
129
139
|
savedRaw = null;
|
|
130
140
|
const result = replaceRoute.handler({
|
|
@@ -165,6 +175,14 @@ describe("PATCH /v1/config — managed profile deletion guard", () => {
|
|
|
165
175
|
).rejects.toThrow(BadRequestError);
|
|
166
176
|
});
|
|
167
177
|
|
|
178
|
+
test("allows deletion of custom-balanced via null (user-owned)", async () => {
|
|
179
|
+
savedRaw = null;
|
|
180
|
+
const result = await patchRoute.handler({
|
|
181
|
+
body: { llm: { profiles: { "custom-balanced": null } } },
|
|
182
|
+
});
|
|
183
|
+
expect(result).toEqual({ ok: true });
|
|
184
|
+
});
|
|
185
|
+
|
|
168
186
|
test("allows deletion of a user-defined profile via null", async () => {
|
|
169
187
|
savedRaw = null;
|
|
170
188
|
const result = await patchRoute.handler({
|