@vellumai/assistant 0.7.2 → 0.7.3
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 +16 -1
- package/docs/architecture/memory.md +5 -2
- package/node_modules/@vellumai/gateway-client/src/ipc-client.ts +13 -4
- 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 +449 -22
- package/package.json +1 -1
- package/src/__tests__/app-control-flow.test.ts +21 -11
- 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 +62 -1
- package/src/__tests__/background-workers-disk-pressure.test.ts +268 -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__/config-loader-backfill.test.ts +379 -0
- 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 +3 -7
- package/src/__tests__/context-search-memory-v2-source.test.ts +0 -2
- package/src/__tests__/context-search-pkb-source.test.ts +0 -1
- package/src/__tests__/context-search-workspace-source.test.ts +0 -1
- package/src/__tests__/conversation-abort-tool-results.test.ts +6 -0
- package/src/__tests__/conversation-agent-loop-disk-pressure.test.ts +223 -0
- package/src/__tests__/conversation-agent-loop.test.ts +454 -5
- package/src/__tests__/conversation-error.test.ts +150 -3
- package/src/__tests__/conversation-process-callsite.test.ts +43 -0
- package/src/__tests__/conversation-provider-retry-repair.test.ts +6 -0
- package/src/__tests__/conversation-runtime-assembly.test.ts +65 -0
- package/src/__tests__/conversation-slash-unknown.test.ts +6 -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-app-control.test.ts +15 -4
- package/src/__tests__/conversation-surfaces-data-persist.test.ts +404 -0
- package/src/__tests__/conversation-tool-setup-app-refresh.test.ts +2 -5
- package/src/__tests__/conversation-workspace-injection.test.ts +6 -0
- package/src/__tests__/conversation-workspace-tool-tracking.test.ts +6 -0
- 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 +3 -4
- 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 +18 -6
- package/src/__tests__/injector-disk-pressure.test.ts +224 -0
- 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__/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 +15 -4
- 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__/suggestion-routes.test.ts +46 -0
- package/src/__tests__/twilio-validation.test.ts +2 -2
- 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-safe-storage-limits-release.test.ts +90 -0
- package/src/approvals/guardian-decision-primitive.ts +13 -0
- package/src/approvals/guardian-request-resolvers.ts +16 -17
- package/src/backup/snapshot-lock.ts +2 -27
- package/src/bundler/compiler-tools.ts +3 -2
- package/src/calls/call-conversation-messages.ts +46 -10
- 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 +7 -6
- package/src/cli/commands/oauth/__tests__/connect.test.ts +437 -1
- package/src/cli/commands/oauth/connect.ts +127 -1
- 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/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 +27 -3
- package/src/config/loader.ts +127 -8
- 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 +75 -11
- 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-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 +6 -0
- package/src/daemon/conversation-agent-loop.ts +170 -33
- package/src/daemon/conversation-error.ts +87 -15
- package/src/daemon/conversation-lifecycle.ts +1 -3
- 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 +195 -15
- package/src/daemon/conversation-tool-setup.ts +57 -14
- package/src/daemon/conversation.ts +17 -22
- 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 +0 -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 +46 -21
- package/src/daemon/host-cu-proxy.ts +49 -3
- package/src/daemon/host-file-proxy.ts +43 -7
- package/src/daemon/host-transfer-proxy.ts +95 -4
- package/src/daemon/lifecycle.ts +79 -28
- 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 +14 -4
- 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 +3 -0
- package/src/daemon/profiler-run-store.ts +5 -5
- package/src/daemon/tool-setup-types.ts +2 -2
- package/src/documents/document-store.ts +85 -0
- package/src/filing/filing-service.ts +30 -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 +56 -2
- package/src/ipc/gateway-client.ts +37 -3
- 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 +52 -22
- 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 +5 -4
- package/src/memory/context-search/sources/memory.ts +0 -1
- 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 +32 -21
- package/src/memory/graph/conversation-graph-memory.ts +42 -54
- package/src/memory/graph/extraction.ts +1 -3
- package/src/memory/graph/graph-search.test.ts +10 -67
- package/src/memory/graph/graph-search.ts +1 -20
- package/src/memory/graph/retriever.test.ts +6 -0
- package/src/memory/graph/retriever.ts +6 -10
- package/src/memory/indexer.ts +54 -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-store.ts +48 -0
- package/src/memory/jobs-worker.ts +81 -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 +6 -0
- package/src/memory/qdrant-client.ts +0 -13
- package/src/memory/rerank-local.ts +374 -0
- package/src/memory/search/semantic.ts +6 -67
- package/src/memory/trace-event-store.ts +1 -17
- package/src/memory/v2/__tests__/activation.test.ts +311 -250
- package/src/memory/v2/__tests__/consolidation-job.test.ts +40 -8
- package/src/memory/v2/__tests__/injection.test.ts +157 -167
- package/src/memory/v2/__tests__/prompts-consolidation.test.ts +61 -2
- package/src/memory/v2/__tests__/qdrant.test.ts +16 -0
- package/src/memory/v2/__tests__/reranker.test.ts +338 -0
- package/src/memory/v2/__tests__/sim.test.ts +5 -199
- package/src/memory/v2/__tests__/skill-store.test.ts +71 -65
- package/src/memory/v2/__tests__/static-context.test.ts +76 -1
- package/src/memory/v2/activation.ts +149 -156
- package/src/memory/v2/consolidation-job.ts +62 -12
- package/src/memory/v2/injection.ts +47 -60
- package/src/memory/v2/prompts/consolidation.ts +36 -1
- package/src/memory/v2/qdrant.ts +99 -0
- package/src/memory/v2/reranker.ts +177 -0
- package/src/memory/v2/sim.ts +10 -84
- 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 +22 -0
- package/src/memory/v2/types.ts +10 -10
- package/src/notifications/copy-composer.ts +13 -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/plugins/defaults/injectors.ts +35 -2
- 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 +867 -0
- package/src/proactive-artifact/job.ts +352 -0
- package/src/proactive-artifact/message-copy.ts +41 -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/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 +14 -1
- package/src/runtime/auth/same-actor.ts +216 -0
- package/src/runtime/channel-retry-sweep.ts +65 -1
- package/src/runtime/guardian-reply-router.ts +10 -0
- package/src/runtime/local-actor-identity.ts +52 -11
- package/src/runtime/pending-interactions.ts +8 -0
- 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/client-routes.ts +20 -2
- package/src/runtime/routes/contact-routes.ts +0 -25
- package/src/runtime/routes/conversation-routes.ts +35 -26
- package/src/runtime/routes/debug-bash-routes.ts +163 -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/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/inbound-stages/guardian-reply-intercept.ts +3 -0
- package/src/runtime/routes/index.ts +6 -0
- package/src/runtime/routes/memory-item-routes.test.ts +41 -15
- package/src/runtime/routes/memory-v2-routes.ts +33 -0
- 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/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/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 +12 -9
- package/src/tools/memory/register.ts +1 -2
- package/src/tools/provider-tool-name.ts +28 -0
- package/src/tools/registry.ts +30 -9
- package/src/tools/terminal/shell.ts +9 -1
- package/src/tools/tool-approval-handler.ts +31 -6
- package/src/tools/types.ts +24 -2
- 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 +72 -0
- package/src/workspace/migrations/068-release-notes-local-timezone.ts +65 -0
- package/src/workspace/migrations/registry.ts +8 -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
|
@@ -8,18 +8,18 @@
|
|
|
8
8
|
* - A new topic appearing on a later turn injects only the new slug.
|
|
9
9
|
* - `evictCompactedTurns` re-enables a previously-injected slug —
|
|
10
10
|
* after eviction the same slug appears again in `toInject`.
|
|
11
|
-
* -
|
|
12
|
-
*
|
|
13
|
-
*
|
|
11
|
+
* - Unified-pool skills: a `skills/<id>` slug ranked into the top-K is
|
|
12
|
+
* rendered under `### Skills You Can Use`, mixed concept-page+skill
|
|
13
|
+
* blocks render concept sections first then the skills suffix, both
|
|
14
|
+
* empty → null block, skills participate in `everInjected` so they
|
|
15
|
+
* deduplicate across turns just like concepts.
|
|
14
16
|
*
|
|
15
17
|
* Hermetic by design: the embedding backend, qdrant client, and `getConfig`
|
|
16
18
|
* are mocked at the module level so the suite never reaches a real backend.
|
|
17
|
-
* The skill
|
|
18
|
-
*
|
|
19
|
-
*
|
|
20
|
-
*
|
|
21
|
-
* collection. The activation-store uses an in-memory SQLite database so
|
|
22
|
-
* writes are real but contained.
|
|
19
|
+
* The skill-store cache (`getSkillCapability`, `isSkillSlug`) is mocked so
|
|
20
|
+
* each test can stage skill content without touching the real catalog.
|
|
21
|
+
* The activation-store uses an in-memory SQLite database so writes are
|
|
22
|
+
* real but contained.
|
|
23
23
|
*
|
|
24
24
|
* Tests use a temp workspace (mkdtemp) and never touch `~/.vellum/`. Sample
|
|
25
25
|
* page content uses generic placeholders (Alice, Bob, etc.) per the cross-
|
|
@@ -124,44 +124,32 @@ mock.module("@qdrant/js-client-rest", () => ({
|
|
|
124
124
|
}));
|
|
125
125
|
|
|
126
126
|
// ---------------------------------------------------------------------------
|
|
127
|
-
// Skill
|
|
127
|
+
// Skill-store mock
|
|
128
128
|
// ---------------------------------------------------------------------------
|
|
129
129
|
//
|
|
130
|
-
//
|
|
131
|
-
// `
|
|
132
|
-
//
|
|
133
|
-
//
|
|
134
|
-
//
|
|
135
|
-
//
|
|
136
|
-
//
|
|
137
|
-
// directly.
|
|
130
|
+
// Skills now flow through the unified pipeline under the `skills/<id>` slug
|
|
131
|
+
// prefix — they are scored by `simBatch` against the same Qdrant collection
|
|
132
|
+
// as concept pages, ranked by `selectInjections`, and rendered alongside
|
|
133
|
+
// concept sections. The render path branches on `isSkillSlug(slug)` to fetch
|
|
134
|
+
// content from the in-process cache via `getSkillCapability` instead of
|
|
135
|
+
// reading a page from disk. Tests stage that cache and rely on the regular
|
|
136
|
+
// `stageTurn` plumbing to land skill slugs in the candidate set.
|
|
138
137
|
|
|
139
138
|
const skillState = {
|
|
140
|
-
/**
|
|
141
|
-
topSkillIds: [] as string[],
|
|
142
|
-
/** id → SkillEntry used by `getSkillCapability` and `getAllSkillIds`. */
|
|
139
|
+
/** id → SkillEntry consulted by `getSkillCapability`. */
|
|
143
140
|
entries: new Map<string, SkillEntry>(),
|
|
144
141
|
};
|
|
145
142
|
|
|
146
|
-
const realActivation = await import("../activation.js");
|
|
147
|
-
mock.module("../activation.js", () => ({
|
|
148
|
-
...realActivation,
|
|
149
|
-
// The injection wiring only consumes `topNow` — the candidate set and
|
|
150
|
-
// activation map are inputs to `selectSkillInjections`, not anything the
|
|
151
|
-
// injection logic introspects. Stub them to empty so the test stays focused
|
|
152
|
-
// on the wiring, not the pipeline internals (covered in activation.test.ts).
|
|
153
|
-
computeSkillActivation: async () => ({
|
|
154
|
-
activation: new Map<string, number>(),
|
|
155
|
-
breakdown: new Map(),
|
|
156
|
-
}),
|
|
157
|
-
selectSkillInjections: ({ topK }: { topK: number }) => ({
|
|
158
|
-
topNow: skillState.topSkillIds.slice(0, topK),
|
|
159
|
-
}),
|
|
160
|
-
}));
|
|
161
|
-
|
|
162
143
|
mock.module("../skill-store.js", () => ({
|
|
163
|
-
|
|
164
|
-
|
|
144
|
+
getSkillCapability: (idOrSlug: string) => {
|
|
145
|
+
const id = idOrSlug.startsWith("skills/")
|
|
146
|
+
? idOrSlug.slice("skills/".length)
|
|
147
|
+
: idOrSlug;
|
|
148
|
+
return skillState.entries.get(id) ?? null;
|
|
149
|
+
},
|
|
150
|
+
isSkillSlug: (slug: string) => slug.startsWith("skills/"),
|
|
151
|
+
SKILL_SLUG_PREFIX: "skills/",
|
|
152
|
+
skillSlugFor: (id: string) => `skills/${id}`,
|
|
165
153
|
}));
|
|
166
154
|
|
|
167
155
|
// ---------------------------------------------------------------------------
|
|
@@ -293,7 +281,6 @@ function makeConfig(
|
|
|
293
281
|
k: number;
|
|
294
282
|
hops: number;
|
|
295
283
|
top_k: number;
|
|
296
|
-
top_k_skills: number;
|
|
297
284
|
epsilon: number;
|
|
298
285
|
dense_weight: number;
|
|
299
286
|
sparse_weight: number;
|
|
@@ -308,8 +295,7 @@ function makeConfig(
|
|
|
308
295
|
c_now: 0.2,
|
|
309
296
|
k: 0.5,
|
|
310
297
|
hops: 2,
|
|
311
|
-
top_k:
|
|
312
|
-
top_k_skills: 5,
|
|
298
|
+
top_k: 25,
|
|
313
299
|
epsilon: 0.01,
|
|
314
300
|
dense_weight: 1.0,
|
|
315
301
|
sparse_weight: 0.0,
|
|
@@ -358,7 +344,6 @@ function resetState(): void {
|
|
|
358
344
|
state.sparseReturn = { indices: [1, 2, 3], values: [0.5, 0.5, 0.5] };
|
|
359
345
|
state.queryResponses.dense.length = 0;
|
|
360
346
|
state.queryResponses.sparse.length = 0;
|
|
361
|
-
skillState.topSkillIds.length = 0;
|
|
362
347
|
skillState.entries.clear();
|
|
363
348
|
telemetryState.recordCalls.length = 0;
|
|
364
349
|
telemetryState.recordShouldThrow = false;
|
|
@@ -368,10 +353,8 @@ function resetState(): void {
|
|
|
368
353
|
_resetMemoryV2QdrantForTests();
|
|
369
354
|
}
|
|
370
355
|
|
|
371
|
-
/** Stage
|
|
372
|
-
function stageSkills(
|
|
373
|
-
skillState.topSkillIds.length = 0;
|
|
374
|
-
skillState.topSkillIds.push(...ids);
|
|
356
|
+
/** Stage skill-store cache entries for the upcoming render. */
|
|
357
|
+
function stageSkills(entries: SkillEntry[]): void {
|
|
375
358
|
for (const entry of entries) {
|
|
376
359
|
skillState.entries.set(entry.id, entry);
|
|
377
360
|
}
|
|
@@ -676,24 +659,22 @@ describe("injectMemoryV2Block", () => {
|
|
|
676
659
|
});
|
|
677
660
|
|
|
678
661
|
// ---------------------------------------------------------------------------
|
|
679
|
-
//
|
|
662
|
+
// Unified pool — skills as `skills/<id>` slugs
|
|
680
663
|
// ---------------------------------------------------------------------------
|
|
681
664
|
|
|
682
|
-
test("renders a skill-only block
|
|
683
|
-
// No concept-page candidates this turn — the
|
|
684
|
-
//
|
|
685
|
-
//
|
|
686
|
-
|
|
687
|
-
|
|
688
|
-
|
|
689
|
-
|
|
690
|
-
|
|
691
|
-
|
|
692
|
-
|
|
693
|
-
|
|
694
|
-
|
|
695
|
-
],
|
|
696
|
-
);
|
|
665
|
+
test("renders a skill-only block via the skills/ slug prefix", async () => {
|
|
666
|
+
// No concept-page candidates this turn — the only ANN hit is a skill
|
|
667
|
+
// slug. The render path branches on `skills/` prefix: it pulls the
|
|
668
|
+
// entry from the skill-store cache (mocked) and emits the bullet under
|
|
669
|
+
// the `### Skills You Can Use` subsection.
|
|
670
|
+
stageTurn([{ slug: "skills/example-skill-a", denseScore: 0.9 }]);
|
|
671
|
+
stageSkills([
|
|
672
|
+
{
|
|
673
|
+
id: "example-skill-a",
|
|
674
|
+
content:
|
|
675
|
+
'The "Example Skill A" skill (example-skill-a) is available. Helps with examples.',
|
|
676
|
+
},
|
|
677
|
+
]);
|
|
697
678
|
|
|
698
679
|
const result = await injectMemoryV2Block({
|
|
699
680
|
database: db,
|
|
@@ -706,15 +687,11 @@ describe("injectMemoryV2Block", () => {
|
|
|
706
687
|
config: makeConfig(),
|
|
707
688
|
});
|
|
708
689
|
|
|
709
|
-
expect(result.toInject).toEqual([]);
|
|
690
|
+
expect(result.toInject).toEqual(["skills/example-skill-a"]);
|
|
710
691
|
expect(result.block).not.toBeNull();
|
|
711
|
-
// `block` is the unwrapped inner content; the caller adds the
|
|
712
|
-
// `<memory>...</memory>` wrapper exactly once at injection time.
|
|
713
692
|
expect(result.block).not.toContain("<memory>");
|
|
714
693
|
expect(result.block).not.toContain("</memory>");
|
|
715
694
|
expect(result.block).not.toContain("## What I Remember Right Now");
|
|
716
|
-
// No concept-page sections; skills subsection present with the right
|
|
717
|
-
// bullet shape and the unconditional `→ use skill_load to activate` suffix.
|
|
718
695
|
expect(result.block).not.toContain("### alice-vscode");
|
|
719
696
|
expect(result.block).toContain("### Skills You Can Use");
|
|
720
697
|
expect(result.block).toContain(
|
|
@@ -723,19 +700,19 @@ describe("injectMemoryV2Block", () => {
|
|
|
723
700
|
});
|
|
724
701
|
|
|
725
702
|
test("renders concept-page sections before the skills subsection in mixed blocks", async () => {
|
|
726
|
-
// Concept page
|
|
703
|
+
// Concept page hit AND a skill — concept-page sections come first, then
|
|
727
704
|
// the skills subsection.
|
|
728
|
-
stageTurn([
|
|
729
|
-
|
|
730
|
-
|
|
731
|
-
|
|
732
|
-
|
|
733
|
-
|
|
734
|
-
|
|
735
|
-
|
|
736
|
-
|
|
737
|
-
|
|
738
|
-
);
|
|
705
|
+
stageTurn([
|
|
706
|
+
{ slug: "alice-vscode", denseScore: 0.9 },
|
|
707
|
+
{ slug: "skills/example-skill-a", denseScore: 0.7 },
|
|
708
|
+
]);
|
|
709
|
+
stageSkills([
|
|
710
|
+
{
|
|
711
|
+
id: "example-skill-a",
|
|
712
|
+
content:
|
|
713
|
+
'The "Example Skill A" skill (example-skill-a) is available. Helps with examples.',
|
|
714
|
+
},
|
|
715
|
+
]);
|
|
739
716
|
|
|
740
717
|
const result = await injectMemoryV2Block({
|
|
741
718
|
database: db,
|
|
@@ -748,7 +725,10 @@ describe("injectMemoryV2Block", () => {
|
|
|
748
725
|
config: makeConfig(),
|
|
749
726
|
});
|
|
750
727
|
|
|
751
|
-
|
|
728
|
+
// Both slugs ranked into top-K and got freshly attached.
|
|
729
|
+
expect(new Set(result.toInject)).toEqual(
|
|
730
|
+
new Set(["alice-vscode", "skills/example-skill-a"]),
|
|
731
|
+
);
|
|
752
732
|
expect(result.block).not.toBeNull();
|
|
753
733
|
|
|
754
734
|
const aliceIdx = result.block!.indexOf("### alice-vscode");
|
|
@@ -757,46 +737,20 @@ describe("injectMemoryV2Block", () => {
|
|
|
757
737
|
expect(skillsIdx).toBeGreaterThan(-1);
|
|
758
738
|
expect(aliceIdx).toBeLessThan(skillsIdx);
|
|
759
739
|
|
|
760
|
-
// The activation suffix is always appended for skills.
|
|
761
740
|
expect(result.block).toContain(
|
|
762
741
|
'- The "Example Skill A" skill (example-skill-a) is available. Helps with examples. → use skill_load to activate',
|
|
763
742
|
);
|
|
764
743
|
});
|
|
765
744
|
|
|
766
|
-
test("
|
|
767
|
-
//
|
|
768
|
-
// AND no skill ids.
|
|
769
|
-
stageTurn([]);
|
|
770
|
-
stageSkills([]);
|
|
771
|
-
|
|
772
|
-
const result = await injectMemoryV2Block({
|
|
773
|
-
database: db,
|
|
774
|
-
conversationId: "conv-1",
|
|
775
|
-
currentTurn: 1,
|
|
776
|
-
userMessage: "anything",
|
|
777
|
-
assistantMessage: "",
|
|
778
|
-
nowText: "",
|
|
779
|
-
messageId: "msg-1",
|
|
780
|
-
config: makeConfig(),
|
|
781
|
-
});
|
|
782
|
-
|
|
783
|
-
expect(result.toInject).toEqual([]);
|
|
784
|
-
expect(result.block).toBeNull();
|
|
785
|
-
});
|
|
786
|
-
|
|
787
|
-
test("re-renders the same top-ranked skill on consecutive turns (no dedup)", async () => {
|
|
788
|
-
// Skills are stateless: the same id can appear on back-to-back turns.
|
|
789
|
-
// Stage no concept-page candidates so the block content is purely the
|
|
790
|
-
// skills subsection.
|
|
745
|
+
test("skills participate in everInjected — an attached skill is not re-attached on the next turn", async () => {
|
|
746
|
+
// Turn 1: skill ranks high, gets attached.
|
|
791
747
|
const skillEntry = {
|
|
792
748
|
id: "example-skill-a",
|
|
793
749
|
content:
|
|
794
750
|
'The "Example Skill A" skill (example-skill-a) is available. Helps with examples.',
|
|
795
751
|
};
|
|
796
|
-
|
|
797
|
-
|
|
798
|
-
stageTurn([]);
|
|
799
|
-
stageSkills(["example-skill-a"], [skillEntry]);
|
|
752
|
+
stageTurn([{ slug: "skills/example-skill-a", denseScore: 0.9 }]);
|
|
753
|
+
stageSkills([skillEntry]);
|
|
800
754
|
const result1 = await injectMemoryV2Block({
|
|
801
755
|
database: db,
|
|
802
756
|
conversationId: "conv-1",
|
|
@@ -807,15 +761,14 @@ describe("injectMemoryV2Block", () => {
|
|
|
807
761
|
messageId: "msg-1",
|
|
808
762
|
config: makeConfig(),
|
|
809
763
|
});
|
|
810
|
-
expect(result1.
|
|
764
|
+
expect(result1.toInject).toEqual(["skills/example-skill-a"]);
|
|
811
765
|
expect(result1.block).toContain("### Skills You Can Use");
|
|
812
|
-
expect(result1.block).toContain("example-skill-a");
|
|
813
766
|
|
|
814
|
-
// Turn 2
|
|
815
|
-
//
|
|
816
|
-
//
|
|
817
|
-
stageTurn([]);
|
|
818
|
-
stageSkills([
|
|
767
|
+
// Turn 2: same skill ranks top again. It is already in `everInjected`, so
|
|
768
|
+
// `toInject` is empty and the block is null — the attachment from turn 1
|
|
769
|
+
// remains visible to the agent via the cached prior user message.
|
|
770
|
+
stageTurn([{ slug: "skills/example-skill-a", denseScore: 0.9 }]);
|
|
771
|
+
stageSkills([skillEntry]);
|
|
819
772
|
const result2 = await injectMemoryV2Block({
|
|
820
773
|
database: db,
|
|
821
774
|
conversationId: "conv-1",
|
|
@@ -826,21 +779,57 @@ describe("injectMemoryV2Block", () => {
|
|
|
826
779
|
messageId: "msg-2",
|
|
827
780
|
config: makeConfig(),
|
|
828
781
|
});
|
|
829
|
-
expect(result2.
|
|
830
|
-
expect(result2.block).
|
|
831
|
-
|
|
832
|
-
|
|
833
|
-
// The skill content line is identical across the two turns — the renderer
|
|
834
|
-
// is deterministic in `id → entry` lookup and the entry is unchanged.
|
|
835
|
-
const skillLine =
|
|
836
|
-
'- The "Example Skill A" skill (example-skill-a) is available. Helps with examples. → use skill_load to activate';
|
|
837
|
-
expect(result1.block).toContain(skillLine);
|
|
838
|
-
expect(result2.block).toContain(skillLine);
|
|
839
|
-
|
|
840
|
-
// `everInjected` is untouched by the skill pipeline — both turns left it
|
|
841
|
-
// empty (no concept pages were injected).
|
|
782
|
+
expect(result2.toInject).toEqual([]);
|
|
783
|
+
expect(result2.block).toBeNull();
|
|
784
|
+
|
|
842
785
|
const persisted = await hydrate(db, "conv-1");
|
|
843
|
-
expect(persisted!.everInjected).toEqual([
|
|
786
|
+
expect(persisted!.everInjected).toEqual([
|
|
787
|
+
{ slug: "skills/example-skill-a", turn: 1 },
|
|
788
|
+
]);
|
|
789
|
+
});
|
|
790
|
+
|
|
791
|
+
test("skill slugs whose entry is missing from the cache are dropped silently", async () => {
|
|
792
|
+
// The skill ranks into top-K but the in-process cache no longer knows
|
|
793
|
+
// its content (skill uninstalled mid-run). The render path drops it
|
|
794
|
+
// without surfacing it as a `missingSlugs` page-missing event — that
|
|
795
|
+
// status is reserved for on-disk concept pages, not catalog-derived
|
|
796
|
+
// skill entries.
|
|
797
|
+
stageTurn([{ slug: "skills/missing-skill", denseScore: 0.9 }]);
|
|
798
|
+
// No `stageSkills` call — cache stays empty.
|
|
799
|
+
|
|
800
|
+
const result = await injectMemoryV2Block({
|
|
801
|
+
database: db,
|
|
802
|
+
conversationId: "conv-1",
|
|
803
|
+
currentTurn: 1,
|
|
804
|
+
userMessage: "anything",
|
|
805
|
+
assistantMessage: "",
|
|
806
|
+
nowText: "Now",
|
|
807
|
+
messageId: "msg-1",
|
|
808
|
+
config: makeConfig(),
|
|
809
|
+
});
|
|
810
|
+
|
|
811
|
+
// `toInject` still records the slug (it ranked into top-K) but the
|
|
812
|
+
// block collapses to null because the only entry was a cache miss.
|
|
813
|
+
expect(result.toInject).toEqual(["skills/missing-skill"]);
|
|
814
|
+
expect(result.block).toBeNull();
|
|
815
|
+
});
|
|
816
|
+
|
|
817
|
+
test("returns null when both concept pages and skills are empty", async () => {
|
|
818
|
+
stageTurn([]);
|
|
819
|
+
|
|
820
|
+
const result = await injectMemoryV2Block({
|
|
821
|
+
database: db,
|
|
822
|
+
conversationId: "conv-1",
|
|
823
|
+
currentTurn: 1,
|
|
824
|
+
userMessage: "anything",
|
|
825
|
+
assistantMessage: "",
|
|
826
|
+
nowText: "",
|
|
827
|
+
messageId: "msg-1",
|
|
828
|
+
config: makeConfig(),
|
|
829
|
+
});
|
|
830
|
+
|
|
831
|
+
expect(result.toInject).toEqual([]);
|
|
832
|
+
expect(result.block).toBeNull();
|
|
844
833
|
});
|
|
845
834
|
|
|
846
835
|
test("context-load mode renders topNow even when every slug was previously injected", async () => {
|
|
@@ -932,39 +921,6 @@ describe("injectMemoryV2Block", () => {
|
|
|
932
921
|
expect(persisted!.everInjected).toHaveLength(3);
|
|
933
922
|
});
|
|
934
923
|
|
|
935
|
-
test("`top_k_skills: 0` short-circuits to no skills subsection", async () => {
|
|
936
|
-
// Even when the underlying mock would surface skills, the cap at 0 must
|
|
937
|
-
// drop them via `selectSkillInjections.topK = 0` → empty `topNow`.
|
|
938
|
-
stageTurn([{ slug: "alice-vscode", denseScore: 0.9 }]);
|
|
939
|
-
stageSkills(
|
|
940
|
-
["example-skill-a"],
|
|
941
|
-
[
|
|
942
|
-
{
|
|
943
|
-
id: "example-skill-a",
|
|
944
|
-
content:
|
|
945
|
-
'The "Example Skill A" skill (example-skill-a) is available.',
|
|
946
|
-
},
|
|
947
|
-
],
|
|
948
|
-
);
|
|
949
|
-
|
|
950
|
-
const result = await injectMemoryV2Block({
|
|
951
|
-
database: db,
|
|
952
|
-
conversationId: "conv-1",
|
|
953
|
-
currentTurn: 1,
|
|
954
|
-
userMessage: "Alice's editor",
|
|
955
|
-
assistantMessage: "",
|
|
956
|
-
nowText: "Now",
|
|
957
|
-
messageId: "msg-1",
|
|
958
|
-
config: makeConfig({ top_k_skills: 0 }),
|
|
959
|
-
});
|
|
960
|
-
|
|
961
|
-
expect(result.toInject).toEqual(["alice-vscode"]);
|
|
962
|
-
expect(result.block).not.toBeNull();
|
|
963
|
-
expect(result.block).toContain("### alice-vscode");
|
|
964
|
-
expect(result.block).not.toContain("### Skills You Can Use");
|
|
965
|
-
expect(result.block).not.toContain("example-skill-a");
|
|
966
|
-
});
|
|
967
|
-
|
|
968
924
|
// ---------------------------------------------------------------------------
|
|
969
925
|
// Activation-log telemetry
|
|
970
926
|
// ---------------------------------------------------------------------------
|
|
@@ -1013,13 +969,12 @@ describe("injectMemoryV2Block", () => {
|
|
|
1013
969
|
status: string;
|
|
1014
970
|
source: string;
|
|
1015
971
|
}>;
|
|
1016
|
-
skills: unknown[];
|
|
1017
972
|
config: { top_k: number };
|
|
1018
973
|
};
|
|
1019
974
|
expect(row.conversationId).toBe("conv-1");
|
|
1020
975
|
expect(row.turn).toBe(2);
|
|
1021
976
|
expect(row.mode).toBe("per-turn");
|
|
1022
|
-
expect(row.config.top_k).toBe(
|
|
977
|
+
expect(row.config.top_k).toBe(25);
|
|
1023
978
|
|
|
1024
979
|
// The candidate set is the union of fromPrior (alice) and fromAnn
|
|
1025
980
|
// (alice + carol) → two concept rows.
|
|
@@ -1041,6 +996,41 @@ describe("injectMemoryV2Block", () => {
|
|
|
1041
996
|
expect(byslug.get("carol-jazz")!.status).toBe("injected");
|
|
1042
997
|
});
|
|
1043
998
|
|
|
999
|
+
test("activation-log concepts include skill rows under the skills/ prefix", async () => {
|
|
1000
|
+
// Skills participate in the unified telemetry list — they live in the
|
|
1001
|
+
// same `concepts` array, identifiable by the `skills/` slug prefix.
|
|
1002
|
+
stageTurn([
|
|
1003
|
+
{ slug: "alice-vscode", denseScore: 0.9 },
|
|
1004
|
+
{ slug: "skills/example-skill-a", denseScore: 0.7 },
|
|
1005
|
+
]);
|
|
1006
|
+
stageSkills([
|
|
1007
|
+
{
|
|
1008
|
+
id: "example-skill-a",
|
|
1009
|
+
content: "skill content",
|
|
1010
|
+
},
|
|
1011
|
+
]);
|
|
1012
|
+
|
|
1013
|
+
await injectMemoryV2Block({
|
|
1014
|
+
database: db,
|
|
1015
|
+
conversationId: "conv-1",
|
|
1016
|
+
currentTurn: 1,
|
|
1017
|
+
userMessage: "Alice's editor",
|
|
1018
|
+
assistantMessage: "",
|
|
1019
|
+
nowText: "Now",
|
|
1020
|
+
messageId: "msg-1",
|
|
1021
|
+
config: makeConfig(),
|
|
1022
|
+
});
|
|
1023
|
+
|
|
1024
|
+
expect(telemetryState.recordCalls.length).toBe(1);
|
|
1025
|
+
const row = telemetryState.recordCalls[0] as {
|
|
1026
|
+
concepts: Array<{ slug: string; status: string }>;
|
|
1027
|
+
};
|
|
1028
|
+
const slugs = row.concepts.map((c) => c.slug);
|
|
1029
|
+
expect(new Set(slugs)).toEqual(
|
|
1030
|
+
new Set(["alice-vscode", "skills/example-skill-a"]),
|
|
1031
|
+
);
|
|
1032
|
+
});
|
|
1033
|
+
|
|
1044
1034
|
test("context-load mode marks every rendered slug as `injected`, never `in_context`", async () => {
|
|
1045
1035
|
// Turn 1 (per-turn): seed alice as injected so the next turn's prior
|
|
1046
1036
|
// `everInjected` includes her — the same setup the per-turn telemetry
|
|
@@ -4,7 +4,14 @@
|
|
|
4
4
|
* file-based override and falls back to the bundled prompt when the
|
|
5
5
|
* override is missing/empty/unreadable.
|
|
6
6
|
*/
|
|
7
|
-
import {
|
|
7
|
+
import { execFileSync } from "node:child_process";
|
|
8
|
+
import {
|
|
9
|
+
mkdirSync,
|
|
10
|
+
mkdtempSync,
|
|
11
|
+
rmSync,
|
|
12
|
+
symlinkSync,
|
|
13
|
+
writeFileSync,
|
|
14
|
+
} from "node:fs";
|
|
8
15
|
import { homedir, tmpdir } from "node:os";
|
|
9
16
|
import { join } from "node:path";
|
|
10
17
|
import {
|
|
@@ -67,7 +74,14 @@ beforeEach(() => {
|
|
|
67
74
|
});
|
|
68
75
|
|
|
69
76
|
afterEach(() => {
|
|
70
|
-
for (const entry of [
|
|
77
|
+
for (const entry of [
|
|
78
|
+
"custom-prompt.md",
|
|
79
|
+
"empty.md",
|
|
80
|
+
"no-placeholder.md",
|
|
81
|
+
"huge.md",
|
|
82
|
+
"link.md",
|
|
83
|
+
"fifo",
|
|
84
|
+
]) {
|
|
71
85
|
rmSync(join(tmpWorkspace, entry), { force: true });
|
|
72
86
|
}
|
|
73
87
|
});
|
|
@@ -178,4 +192,49 @@ describe("resolveConsolidationPrompt — failure modes", () => {
|
|
|
178
192
|
const data = warnCalls[0].data as Record<string, unknown>;
|
|
179
193
|
expect(data.reason).toBe("empty_override");
|
|
180
194
|
});
|
|
195
|
+
|
|
196
|
+
test("falls back to bundled prompt when the override exceeds the size limit", () => {
|
|
197
|
+
const path = join(tmpWorkspace, "huge.md");
|
|
198
|
+
// 1 MiB + 1 byte — just over the cap so we don't waste test memory.
|
|
199
|
+
writeFileSync(path, Buffer.alloc(1 * 1024 * 1024 + 1, 0x61));
|
|
200
|
+
|
|
201
|
+
const result = resolveConsolidationPrompt(path, CUTOFF);
|
|
202
|
+
|
|
203
|
+
expect(result).toBe(bundledPrompt());
|
|
204
|
+
expect(warnCalls).toHaveLength(1);
|
|
205
|
+
const data = warnCalls[0].data as Record<string, unknown>;
|
|
206
|
+
expect(data.reason).toBe("oversized_override");
|
|
207
|
+
expect(data.size).toBe(1 * 1024 * 1024 + 1);
|
|
208
|
+
});
|
|
209
|
+
|
|
210
|
+
test("falls back to bundled prompt when the override is a symlink", () => {
|
|
211
|
+
const target = join(tmpWorkspace, "custom-prompt.md");
|
|
212
|
+
writeFileSync(target, "real prompt body\n");
|
|
213
|
+
const link = join(tmpWorkspace, "link.md");
|
|
214
|
+
symlinkSync(target, link);
|
|
215
|
+
|
|
216
|
+
const result = resolveConsolidationPrompt(link, CUTOFF);
|
|
217
|
+
|
|
218
|
+
expect(result).toBe(bundledPrompt());
|
|
219
|
+
expect(warnCalls).toHaveLength(1);
|
|
220
|
+
const data = warnCalls[0].data as Record<string, unknown>;
|
|
221
|
+
expect(data.reason).toBe("not_regular_file");
|
|
222
|
+
});
|
|
223
|
+
|
|
224
|
+
test("falls back to bundled prompt when the override is a FIFO", () => {
|
|
225
|
+
const fifoPath = join(tmpWorkspace, "fifo");
|
|
226
|
+
try {
|
|
227
|
+
execFileSync("mkfifo", [fifoPath]);
|
|
228
|
+
} catch {
|
|
229
|
+
// mkfifo unavailable on this platform — skip without failing.
|
|
230
|
+
return;
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
const result = resolveConsolidationPrompt(fifoPath, CUTOFF);
|
|
234
|
+
|
|
235
|
+
expect(result).toBe(bundledPrompt());
|
|
236
|
+
expect(warnCalls).toHaveLength(1);
|
|
237
|
+
const data = warnCalls[0].data as Record<string, unknown>;
|
|
238
|
+
expect(data.reason).toBe("not_regular_file");
|
|
239
|
+
});
|
|
181
240
|
});
|
|
@@ -190,6 +190,22 @@ describe("memory v2 qdrant — collection lifecycle", () => {
|
|
|
190
190
|
expect(state.collectionExistsCalls).toBe(1);
|
|
191
191
|
});
|
|
192
192
|
|
|
193
|
+
test("deduplicates concurrent collection creation", async () => {
|
|
194
|
+
state.collectionExistsBeforeCreate = false;
|
|
195
|
+
|
|
196
|
+
await Promise.all([
|
|
197
|
+
ensureConceptPageCollection(),
|
|
198
|
+
ensureConceptPageCollection(),
|
|
199
|
+
ensureConceptPageCollection(),
|
|
200
|
+
]);
|
|
201
|
+
|
|
202
|
+
expect(state.collectionExistsCalls).toBe(1);
|
|
203
|
+
expect(state.createCollectionCalls).toBe(1);
|
|
204
|
+
expect(state.createIndexCalls).toEqual([
|
|
205
|
+
{ field_name: "slug", field_schema: "keyword" },
|
|
206
|
+
]);
|
|
207
|
+
});
|
|
208
|
+
|
|
193
209
|
test("treats 409-on-create as success (concurrent creation race)", async () => {
|
|
194
210
|
state.collectionExistsBeforeCreate = false;
|
|
195
211
|
const conflict = Object.assign(new Error("Conflict"), { status: 409 });
|