@vellumai/assistant 0.8.8 → 0.8.9-staging.2
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 +6 -6
- package/bun.lock +2 -2
- package/examples/plugins/echo/README.md +61 -60
- package/examples/plugins/echo/hooks/post-tool-use.ts +18 -0
- package/examples/plugins/echo/hooks/stop.ts +16 -0
- package/examples/plugins/echo/hooks/user-prompt-submit.ts +18 -0
- package/examples/plugins/echo/package.json +1 -2
- package/examples/plugins/echo/src/emit.ts +19 -0
- package/node_modules/@vellumai/skill-host-contracts/src/skill-host.ts +7 -6
- package/openapi.yaml +235 -6
- package/package.json +2 -2
- package/src/__tests__/agent-loop-callsite-precedence.test.ts +69 -14
- package/src/__tests__/agent-loop-exit-reason.test.ts +204 -144
- package/src/__tests__/agent-loop-mutable-latest-user-message.test.ts +50 -35
- package/src/__tests__/agent-loop-output-hooks.test.ts +357 -0
- package/src/__tests__/agent-loop-override-profile.test.ts +25 -6
- package/src/__tests__/agent-loop-provider-error-recording.test.ts +41 -21
- package/src/__tests__/agent-loop-thinking.test.ts +36 -20
- package/src/__tests__/agent-loop.test.ts +441 -96
- package/src/__tests__/agent-wake-disk-pressure-callsite.test.ts +14 -14
- package/src/__tests__/agent-wake-override-profile.test.ts +17 -21
- package/src/__tests__/anthropic-provider.test.ts +1 -1
- package/src/__tests__/app-builder-tool-scripts.test.ts +21 -0
- package/src/__tests__/app-control-flow.test.ts +1 -1
- package/src/__tests__/app-dir-path-guard.test.ts +1 -0
- package/src/__tests__/app-executors.test.ts +132 -0
- package/src/__tests__/approval-cascade.test.ts +5 -4
- package/src/__tests__/approval-routes-http.test.ts +4 -1
- package/src/__tests__/background-workers-disk-pressure.test.ts +1 -1
- package/src/__tests__/channel-approval-routes.test.ts +1 -1
- package/src/__tests__/channel-approvals.test.ts +1 -1
- package/src/__tests__/compaction-circuit.test.ts +258 -0
- package/src/__tests__/compaction-direct.test.ts +132 -0
- package/src/__tests__/compaction-events.test.ts +5 -5
- package/src/__tests__/compactor-web-search-strip.test.ts +213 -0
- package/src/__tests__/context-overflow-reducer.test.ts +1 -1
- package/src/__tests__/conversation-abort-tool-results.test.ts +6 -4
- package/src/__tests__/conversation-agent-loop-disk-pressure.test.ts +7 -10
- package/src/__tests__/conversation-agent-loop-inference-profile.test.ts +78 -119
- package/src/__tests__/conversation-agent-loop-overflow.test.ts +142 -218
- package/src/__tests__/conversation-agent-loop.test.ts +297 -586
- package/src/__tests__/conversation-clean-command.test.ts +5 -2
- package/src/__tests__/conversation-confirmation-signals.test.ts +5 -4
- package/src/__tests__/conversation-crud-inference-profile.test.ts +7 -9
- package/src/__tests__/conversation-history-web-search.test.ts +1 -1
- package/src/__tests__/conversation-process-app-control-preactivation.test.ts +4 -4
- package/src/__tests__/conversation-process-callsite.test.ts +14 -14
- package/src/__tests__/conversation-provider-retry-repair.test.ts +70 -65
- package/src/__tests__/conversation-queue.test.ts +9 -9
- package/src/__tests__/conversation-runtime-assembly.test.ts +923 -231
- package/src/__tests__/conversation-runtime-workspace.test.ts +115 -20
- package/src/__tests__/conversation-slash-queue.test.ts +6 -4
- package/src/__tests__/conversation-slash-unknown.test.ts +6 -4
- package/src/__tests__/conversation-speed-override.test.ts +10 -9
- package/src/__tests__/conversation-starter-routes.test.ts +14 -6
- package/src/__tests__/conversation-workspace-cache-state.test.ts +23 -20
- package/src/__tests__/conversation-workspace-injection.test.ts +68 -6
- package/src/__tests__/conversation-workspace-tool-tracking.test.ts +14 -11
- package/src/__tests__/conversations-import-system-filter.test.ts +101 -0
- package/src/__tests__/credential-security-invariants.test.ts +0 -1
- package/src/__tests__/db-acp-history.test.ts +101 -0
- package/src/__tests__/dynamic-page-surface.test.ts +31 -0
- package/src/__tests__/empty-response-hook.test.ts +1 -1
- package/src/__tests__/file-write-tool.test.ts +63 -0
- package/src/__tests__/gateway-only-guard.test.ts +12 -2
- package/src/__tests__/guardian-grant-minting.test.ts +1 -1
- package/src/__tests__/guardian-routing-invariants.test.ts +2 -4
- package/src/__tests__/handlers-user-message-approval-consumption.test.ts +1 -1
- package/src/__tests__/heartbeat-disk-pressure.test.ts +1 -0
- package/src/__tests__/heartbeat-service.test.ts +1 -0
- package/src/__tests__/history-repair-hook.test.ts +1 -1
- package/src/__tests__/host-app-control-routes.test.ts +1 -1
- package/src/__tests__/host-cu-routes-targeted.test.ts +3 -3
- package/src/__tests__/inference-profile-reaper.test.ts +62 -0
- package/src/__tests__/inference-profile-session-handler.test.ts +86 -0
- package/src/__tests__/injector-background-turn.test.ts +13 -23
- package/src/__tests__/injector-chain.test.ts +268 -44
- package/src/__tests__/injector-disk-pressure.test.ts +210 -52
- package/src/__tests__/injector-document-comments.test.ts +97 -114
- package/src/__tests__/injector-pkb-v2-silenced.test.ts +2 -2
- package/src/__tests__/injector-v3-suppression.test.ts +4 -4
- package/src/__tests__/list-messages-client-message-id.test.ts +91 -0
- package/src/__tests__/list-messages-hidden-metadata.test.ts +38 -0
- package/src/__tests__/memory-retrieval-hook.test.ts +131 -26
- package/src/__tests__/memory-v2-static-injector.test.ts +86 -8
- package/src/__tests__/parallel-tool.benchmark.test.ts +35 -8
- package/src/__tests__/plugin-api-shim.test.ts +6 -9
- package/src/__tests__/plugin-bootstrap.test.ts +12 -23
- package/src/__tests__/plugin-registry.test.ts +3 -49
- package/src/__tests__/plugin-types.test.ts +0 -70
- package/src/__tests__/pre-model-call-sanitize.test.ts +109 -0
- package/src/__tests__/reaction-persistence.test.ts +1 -1
- package/src/__tests__/send-endpoint-busy.test.ts +4 -1
- package/src/__tests__/skill-feature-flags-integration.test.ts +33 -0
- package/src/__tests__/steer-tool-repair.test.ts +1 -1
- package/src/__tests__/subagent-call-site-routing.test.ts +1 -1
- package/src/__tests__/subagent-detail.test.ts +25 -7
- package/src/__tests__/subagent-fork-notifications.test.ts +1 -3
- package/src/__tests__/subagent-fork-spawn.test.ts +1 -1
- package/src/__tests__/subagent-manager-notify.test.ts +1 -3
- package/src/__tests__/subagent-notify-parent.test.ts +1 -3
- package/src/__tests__/subagent-spawn-tool-fork.test.ts +1 -1
- package/src/__tests__/title-generate-hook.test.ts +1 -1
- package/src/__tests__/tool-error-hook.test.ts +1 -1
- package/src/__tests__/tool-result-truncate-hook.test.ts +1 -1
- package/src/__tests__/user-plugin-loader.test.ts +54 -286
- package/src/acp/__tests__/agent-process.test.ts +161 -0
- package/src/acp/__tests__/client-handler.test.ts +40 -0
- package/src/acp/__tests__/helpers/acp-history-db.ts +82 -0
- package/src/acp/__tests__/helpers/exec-file-stub.ts +106 -0
- package/src/acp/__tests__/prepare-agent-env.test.ts +97 -0
- package/src/acp/__tests__/session-manager-persistence.test.ts +95 -28
- package/src/acp/__tests__/session-manager-resume.test.ts +888 -0
- package/src/acp/agent-process.ts +61 -1
- package/src/acp/auto-install.test.ts +280 -0
- package/src/acp/auto-install.ts +232 -0
- package/src/acp/client-handler.ts +31 -0
- package/src/acp/feature-gate.test.ts +48 -0
- package/src/acp/feature-gate.ts +34 -0
- package/src/acp/prepare-agent-env.ts +80 -27
- package/src/acp/resolve-agent.test.ts +225 -9
- package/src/acp/resolve-agent.ts +122 -17
- package/src/acp/resume-hint.ts +23 -0
- package/src/acp/session-manager.ts +507 -73
- package/src/agent/compaction-circuit.ts +60 -102
- package/src/agent/loop.ts +414 -248
- package/src/api/responses/conversation-message.ts +14 -1
- package/src/approvals/guardian-request-resolvers.ts +1 -1
- package/src/background-wake/next-wake.ts +1 -0
- package/src/cli/commands/db/__tests__/repair.test.ts +3 -1
- package/src/cli/commands/plugins.ts +43 -37
- package/src/cli/lib/__tests__/install-from-github.test.ts +429 -111
- package/src/cli/lib/__tests__/plugin-catalog-cache.test.ts +196 -0
- package/src/cli/lib/__tests__/plugin-details.test.ts +372 -0
- package/src/cli/lib/__tests__/plugin-marketplace.test.ts +220 -0
- package/src/cli/lib/__tests__/search-plugins.test.ts +226 -32
- package/src/cli/lib/install-from-github.ts +464 -55
- package/src/cli/lib/plugin-catalog-cache.ts +84 -0
- package/src/cli/lib/plugin-details.ts +409 -0
- package/src/cli/lib/plugin-marketplace.ts +197 -0
- package/src/cli/lib/search-plugins.ts +195 -29
- package/src/config/__tests__/feature-flag-registry-guard.test.ts +2 -2
- package/src/config/acp-defaults.test.ts +10 -0
- package/src/config/acp-defaults.ts +6 -0
- package/src/config/bundled-skills/acp/SKILL.md +85 -33
- package/src/config/bundled-skills/acp/TOOLS.json +4 -4
- package/src/config/bundled-skills/app-builder/SKILL.md +224 -381
- package/src/config/bundled-skills/app-builder/TOOLS.json +72 -2
- package/src/config/bundled-skills/app-builder/references/DESIGN_SYSTEM.md +48 -0
- package/src/config/bundled-skills/app-builder/references/RESPONSIVE.md +57 -0
- package/src/config/bundled-skills/app-builder/references/SLIDES.md +38 -0
- package/src/config/bundled-skills/app-builder/tools/app-list.ts +62 -0
- package/src/config/bundled-skills/app-builder/tools/app-update.ts +18 -0
- package/src/config/bundled-skills/document-editor/SKILL.md +28 -23
- package/src/config/bundled-skills/document-editor/TOOLS.json +1 -1
- package/src/config/bundled-tool-registry.ts +4 -0
- package/src/config/call-site-defaults.ts +0 -2
- package/src/config/feature-flag-registry.json +15 -6
- package/src/config/schemas/call-site-catalog.ts +0 -14
- package/src/config/schemas/heartbeat.ts +9 -0
- package/src/config/schemas/llm.ts +0 -2
- package/src/context/compactor.ts +22 -4
- package/src/context/strip-injections.ts +8 -2
- package/src/context/window-manager.ts +27 -13
- package/src/daemon/conversation-agent-loop-handlers.ts +10 -35
- package/src/daemon/conversation-agent-loop.ts +175 -1055
- package/src/daemon/conversation-lifecycle.ts +11 -255
- package/src/daemon/conversation-process.ts +8 -136
- package/src/daemon/conversation-registry.ts +159 -0
- package/src/daemon/conversation-runtime-assembly.ts +293 -392
- package/src/daemon/conversation-store.ts +9 -90
- package/src/daemon/conversation-surfaces.ts +24 -8
- package/src/daemon/conversation-workspace.ts +17 -0
- package/src/daemon/conversation.ts +404 -57
- package/src/daemon/disk-pressure-policy.ts +0 -1
- package/src/daemon/external-plugins-bootstrap.ts +14 -19
- package/src/daemon/handlers/conversations.ts +3 -1
- package/src/daemon/handlers/skills.ts +4 -1
- package/src/daemon/host-proxy-preactivation.ts +1 -3
- package/src/daemon/lifecycle.ts +21 -7
- package/src/daemon/server.ts +2 -0
- package/src/daemon/wake-conversation-ops.ts +269 -0
- package/src/embedded/plugin-api.ts +2 -2
- package/src/export/__tests__/transcript-formatter.test.ts +5 -0
- package/src/heartbeat/__tests__/heartbeat-service.test.ts +3 -0
- package/src/heartbeat/heartbeat-run-store.ts +23 -1
- package/src/heartbeat/heartbeat-service.ts +26 -0
- package/src/ipc/__tests__/browser-ipc.test.ts +1 -1
- package/src/ipc/__tests__/ui-request-route.test.ts +3 -3
- package/src/ipc/skill-routes/__tests__/memory.test.ts +15 -0
- package/src/ipc/skill-routes/memory.ts +4 -2
- package/src/memory/__tests__/jobs-worker-v2-schedule.test.ts +87 -0
- package/src/memory/conversation-crud.ts +29 -19
- package/src/memory/conversation-starter-checkpoints.ts +1 -0
- package/src/memory/db-init.ts +2 -0
- package/src/memory/job-handlers/conversation-starters.ts +13 -2
- package/src/memory/jobs/__tests__/embed-concept-page.test.ts +5 -4
- package/src/memory/jobs-worker.ts +25 -1
- package/src/memory/migrations/272-acp-session-history-cwd.ts +36 -0
- package/src/memory/migrations/index.ts +1 -0
- package/src/memory/schema/acp.ts +4 -0
- package/src/memory/v2/__tests__/consolidation-job.test.ts +3 -3
- package/src/memory/v2/consolidation-job.ts +13 -4
- package/src/plugin-api/constants.ts +4 -0
- package/src/plugin-api/index.ts +6 -5
- package/src/plugin-api/types.ts +75 -0
- package/src/plugins/defaults/compaction/compact.ts +59 -0
- package/src/{daemon → plugins/defaults/compaction}/context-overflow-reducer.ts +7 -7
- package/src/plugins/defaults/compaction/manager-store.ts +57 -0
- package/src/plugins/defaults/compaction/package.json +1 -2
- package/src/plugins/defaults/empty-response/package.json +0 -1
- package/src/plugins/defaults/history-repair/package.json +0 -1
- package/src/plugins/defaults/index.ts +135 -26
- package/src/plugins/defaults/memory-retrieval/hooks/post-compact.ts +100 -52
- package/src/plugins/defaults/memory-retrieval/hooks/user-prompt-submit-temp.ts +184 -74
- package/src/plugins/defaults/memory-retrieval/injector-chain.ts +2 -2
- package/src/plugins/defaults/{injectors/register.ts → memory-retrieval/injectors.ts} +148 -73
- package/src/plugins/defaults/memory-retrieval/unified-turn-context.ts +223 -0
- package/src/{memory/v3 → plugins/defaults/memory-v3-shadow}/__tests__/assign.test.ts +4 -4
- package/src/{memory/v3 → plugins/defaults/memory-v3-shadow}/__tests__/live-integration.test.ts +9 -6
- package/src/{memory/v3 → plugins/defaults/memory-v3-shadow}/__tests__/maintain-job.test.ts +5 -5
- package/src/{memory/v3 → plugins/defaults/memory-v3-shadow}/__tests__/orchestrate.test.ts +8 -5
- package/src/{memory/v3 → plugins/defaults/memory-v3-shadow}/__tests__/reconcile.test.ts +2 -2
- package/src/{memory/v3 → plugins/defaults/memory-v3-shadow}/__tests__/render-injection.test.ts +1 -1
- package/src/{memory/v3 → plugins/defaults/memory-v3-shadow}/__tests__/router.test.ts +10 -5
- package/src/{memory/v3 → plugins/defaults/memory-v3-shadow}/__tests__/selection-log-store.test.ts +8 -8
- package/src/{memory/v3 → plugins/defaults/memory-v3-shadow}/__tests__/selector.test.ts +5 -5
- package/src/{memory/v3 → plugins/defaults/memory-v3-shadow}/__tests__/shadow-plugin.test.ts +16 -17
- package/src/{memory/v3 → plugins/defaults/memory-v3-shadow}/__tests__/types.test.ts +2 -2
- package/src/{memory/v3 → plugins/defaults/memory-v3-shadow}/assign.ts +9 -5
- package/src/{memory/v3 → plugins/defaults/memory-v3-shadow}/capabilities.ts +5 -2
- package/src/plugins/defaults/memory-v3-shadow/hooks/post-compact.ts +14 -0
- package/src/plugins/defaults/memory-v3-shadow/hooks/user-prompt-submit.ts +19 -0
- package/src/plugins/defaults/memory-v3-shadow/injector.ts +75 -0
- package/src/{memory/v3 → plugins/defaults/memory-v3-shadow}/maintain-job.ts +15 -8
- package/src/{memory/v3 → plugins/defaults/memory-v3-shadow}/orchestrate.ts +2 -2
- package/src/plugins/defaults/memory-v3-shadow/package.json +14 -0
- package/src/{memory/v3 → plugins/defaults/memory-v3-shadow}/page-content.ts +2 -2
- package/src/{memory/v3 → plugins/defaults/memory-v3-shadow}/provider-blocks.ts +1 -1
- package/src/{memory/v3 → plugins/defaults/memory-v3-shadow}/reconcile.ts +7 -3
- package/src/{memory/v3 → plugins/defaults/memory-v3-shadow}/render-injection.ts +1 -1
- package/src/{memory/v3 → plugins/defaults/memory-v3-shadow}/router.ts +5 -5
- package/src/{memory/v3 → plugins/defaults/memory-v3-shadow}/selection-log-store.ts +4 -4
- package/src/{memory/v3 → plugins/defaults/memory-v3-shadow}/selector.ts +7 -7
- package/src/{memory/v3 → plugins/defaults/memory-v3-shadow}/shadow-plugin.ts +32 -94
- package/src/{memory/v3 → plugins/defaults/memory-v3-shadow}/tree.ts +1 -1
- package/src/{memory/v3 → plugins/defaults/memory-v3-shadow}/types.ts +1 -1
- package/src/plugins/defaults/title-generate/package.json +0 -1
- package/src/plugins/defaults/tool-error/package.json +0 -1
- package/src/plugins/defaults/tool-result-truncate/package.json +0 -1
- package/src/plugins/pipeline.ts +6 -293
- package/src/plugins/registry.ts +9 -37
- package/src/plugins/types.ts +76 -381
- package/src/plugins/user-loader.ts +30 -127
- package/src/prompts/__tests__/system-prompt.test.ts +6 -0
- package/src/prompts/templates/BOOTSTRAP-ACTIVATION-RAIL.md +35 -3
- package/src/runtime/__tests__/agent-wake.test.ts +555 -691
- package/src/runtime/__tests__/interactive-ui.test.ts +1 -1
- package/src/runtime/agent-wake.ts +108 -209
- package/src/runtime/assistant-event-hub.ts +1 -1
- package/src/runtime/channel-approvals.ts +1 -1
- package/src/runtime/interactive-ui.ts +1 -1
- package/src/runtime/routes/__tests__/acp-routes.test.ts +315 -55
- package/src/runtime/routes/__tests__/conversation-list-routes.test.ts +1 -1
- package/src/runtime/routes/__tests__/plugins-routes.test.ts +423 -73
- package/src/runtime/routes/__tests__/surface-action-routes.test.ts +5 -4
- package/src/runtime/routes/__tests__/surface-content-routes.test.ts +4 -1
- package/src/runtime/routes/acp-routes.test.ts +89 -25
- package/src/runtime/routes/acp-routes.ts +81 -29
- package/src/runtime/routes/approval-routes.ts +1 -1
- package/src/runtime/routes/browser-routes.ts +1 -1
- package/src/runtime/routes/browser-tabs-routes.ts +6 -10
- package/src/runtime/routes/conversation-cli-routes.ts +1 -1
- package/src/runtime/routes/conversation-list-routes.ts +1 -1
- package/src/runtime/routes/conversation-query-routes.ts +1 -1
- package/src/runtime/routes/conversation-routes.ts +28 -2
- package/src/runtime/routes/conversation-starter-routes.ts +13 -7
- package/src/runtime/routes/conversations-import-routes.ts +24 -7
- package/src/runtime/routes/host-app-control-routes.ts +1 -1
- package/src/runtime/routes/host-cu-routes.ts +1 -1
- package/src/runtime/routes/identity-routes.ts +18 -3
- package/src/runtime/routes/inbound-message-handler.ts +1 -1
- package/src/runtime/routes/inference-profile-session-handler.ts +11 -0
- package/src/runtime/routes/inference-profile-session-reaper.ts +6 -0
- package/src/runtime/routes/memory-v3-routes.ts +16 -6
- package/src/runtime/routes/playground/helpers.ts +1 -1
- package/src/runtime/routes/plugins-routes.ts +337 -35
- package/src/runtime/routes/surface-conversation-resolver.ts +4 -3
- package/src/runtime/routes/work-items-routes.ts +2 -4
- package/src/runtime/services/conversation-serializer.ts +1 -1
- package/src/signals/cancel.ts +2 -4
- package/src/subagent/manager.ts +21 -5
- package/src/tools/acp/context.ts +20 -0
- package/src/tools/acp/list-agents.test.ts +8 -2
- package/src/tools/acp/spawn.test.ts +176 -195
- package/src/tools/acp/spawn.ts +37 -172
- package/src/tools/acp/steer.test.ts +105 -8
- package/src/tools/acp/steer.ts +48 -17
- package/src/tools/apps/executors.ts +166 -50
- package/src/tools/filesystem/write.ts +34 -0
- package/src/tools/subagent/spawn.ts +2 -4
- package/src/tools/ui-surface/definitions.ts +25 -5
- package/src/workspace/migrations/051-seed-conversation-summarization-callsite.ts +4 -5
- package/src/workspace/migrations/097-enable-adaptive-thinking-managed-profiles.ts +69 -45
- package/docs/plugins.md +0 -832
- package/examples/plugins/echo/register.ts +0 -143
- package/src/__tests__/circuit-breaker-pipeline.test.ts +0 -405
- package/src/__tests__/compaction-pipeline.test.ts +0 -210
- package/src/__tests__/compaction-timeout-recovery.test.ts +0 -251
- package/src/__tests__/overflow-reduce-pipeline.test.ts +0 -667
- package/src/__tests__/pipeline-runner.test.ts +0 -554
- package/src/__tests__/plugin-external-api.test.ts +0 -68
- package/src/daemon/wake-target-adapter.ts +0 -253
- package/src/plugins/defaults/circuit-breaker/middlewares/circuitBreaker.ts +0 -93
- package/src/plugins/defaults/circuit-breaker/package.json +0 -15
- package/src/plugins/defaults/circuit-breaker/register.ts +0 -39
- package/src/plugins/defaults/compaction/middlewares/compaction.ts +0 -25
- package/src/plugins/defaults/compaction/register.ts +0 -35
- package/src/plugins/defaults/compaction/terminal.ts +0 -73
- package/src/plugins/defaults/empty-response/register.ts +0 -23
- package/src/plugins/defaults/history-repair/register.ts +0 -24
- package/src/plugins/defaults/overflow-reduce/middlewares/overflowReduce.ts +0 -126
- package/src/plugins/defaults/overflow-reduce/package.json +0 -15
- package/src/plugins/defaults/overflow-reduce/register.ts +0 -42
- package/src/plugins/defaults/title-generate/register.ts +0 -35
- package/src/plugins/defaults/tool-error/register.ts +0 -23
- package/src/plugins/defaults/tool-result-truncate/register.ts +0 -24
- package/src/plugins/external-api.ts +0 -104
- package/src/proactive-artifact/aux-message-injector.ts +0 -97
- package/src/proactive-artifact/decision.test.ts +0 -226
- package/src/proactive-artifact/decision.ts +0 -165
- package/src/proactive-artifact/index.ts +0 -7
- package/src/proactive-artifact/job.test.ts +0 -962
- package/src/proactive-artifact/job.ts +0 -372
- package/src/proactive-artifact/message-copy.ts +0 -58
- package/src/proactive-artifact/trigger-state.test.ts +0 -286
- package/src/proactive-artifact/trigger-state.ts +0 -123
- /package/src/{memory/v3 → plugins/defaults/memory-v3-shadow}/__tests__/capabilities.test.ts +0 -0
- /package/src/{memory/v3 → plugins/defaults/memory-v3-shadow}/__tests__/core.test.ts +0 -0
- /package/src/{memory/v3 → plugins/defaults/memory-v3-shadow}/__tests__/fixtures/eval-turns.json +0 -0
- /package/src/{memory/v3 → plugins/defaults/memory-v3-shadow}/__tests__/fixtures/live-turns.json +0 -0
- /package/src/{memory/v3 → plugins/defaults/memory-v3-shadow}/__tests__/health.test.ts +0 -0
- /package/src/{memory/v3 → plugins/defaults/memory-v3-shadow}/__tests__/needle.test.ts +0 -0
- /package/src/{memory/v3 → plugins/defaults/memory-v3-shadow}/__tests__/provider-blocks.test.ts +0 -0
- /package/src/{memory/v3 → plugins/defaults/memory-v3-shadow}/__tests__/snapshot.test.ts +0 -0
- /package/src/{memory/v3 → plugins/defaults/memory-v3-shadow}/__tests__/tree.test.ts +0 -0
- /package/src/{memory/v3 → plugins/defaults/memory-v3-shadow}/__tests__/working-set-eviction.test.ts +0 -0
- /package/src/{memory/v3 → plugins/defaults/memory-v3-shadow}/__tests__/working-set-skeleton.test.ts +0 -0
- /package/src/{memory/v3 → plugins/defaults/memory-v3-shadow}/core.ts +0 -0
- /package/src/{memory/v3 → plugins/defaults/memory-v3-shadow}/data/README.md +0 -0
- /package/src/{memory/v3 → plugins/defaults/memory-v3-shadow}/data/assignments.json +0 -0
- /package/src/{memory/v3 → plugins/defaults/memory-v3-shadow}/data/core.json +0 -0
- /package/src/{memory/v3 → plugins/defaults/memory-v3-shadow}/data/leaves/domain-a/topic-x.md +0 -0
- /package/src/{memory/v3 → plugins/defaults/memory-v3-shadow}/data/leaves/domain-a/topic-y.md +0 -0
- /package/src/{memory/v3 → plugins/defaults/memory-v3-shadow}/data/leaves/domain-b/topic-z.md +0 -0
- /package/src/{memory/v3 → plugins/defaults/memory-v3-shadow}/health.ts +0 -0
- /package/src/{memory/v3 → plugins/defaults/memory-v3-shadow}/llm-retry.ts +0 -0
- /package/src/{memory/v3 → plugins/defaults/memory-v3-shadow}/needle.ts +0 -0
- /package/src/{memory/v3 → plugins/defaults/memory-v3-shadow}/snapshot.ts +0 -0
- /package/src/{memory/v3 → plugins/defaults/memory-v3-shadow}/working-set.ts +0 -0
|
@@ -110,7 +110,7 @@ describe("memory-v3-live v2 suppression", () => {
|
|
|
110
110
|
];
|
|
111
111
|
|
|
112
112
|
const result = await applyRuntimeInjections(runMessages, {
|
|
113
|
-
|
|
113
|
+
...makeTurnContext(),
|
|
114
114
|
});
|
|
115
115
|
|
|
116
116
|
// Exactly one <memory> source across the WHOLE assembled context.
|
|
@@ -149,7 +149,7 @@ describe("memory-v3-live v2 suppression", () => {
|
|
|
149
149
|
];
|
|
150
150
|
|
|
151
151
|
const result = await applyRuntimeInjections(runMessages, {
|
|
152
|
-
|
|
152
|
+
...makeTurnContext(),
|
|
153
153
|
});
|
|
154
154
|
|
|
155
155
|
// v2's block survives — the turn still ships memory.
|
|
@@ -177,7 +177,7 @@ describe("memory-v3-live v2 suppression", () => {
|
|
|
177
177
|
// captures the exact pre-flag assembly behavior: v2 prefix stays, v3
|
|
178
178
|
// splices after it.
|
|
179
179
|
const offResult = await applyRuntimeInjections(runMessages, {
|
|
180
|
-
|
|
180
|
+
...makeTurnContext(),
|
|
181
181
|
});
|
|
182
182
|
|
|
183
183
|
// The tail keeps v2's block AND gains v3's (the historical double-injection
|
|
@@ -206,7 +206,7 @@ describe("memory-v3-live v2 suppression", () => {
|
|
|
206
206
|
];
|
|
207
207
|
|
|
208
208
|
const result = await applyRuntimeInjections(runMessages, {
|
|
209
|
-
|
|
209
|
+
...makeTurnContext(),
|
|
210
210
|
});
|
|
211
211
|
|
|
212
212
|
expect(result.messages).toEqual(runMessages);
|
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Tests for handleListMessages clientMessageId projection.
|
|
3
|
+
*
|
|
4
|
+
* Verifies that the persisted idempotency nonce is echoed back on the
|
|
5
|
+
* messages snapshot row so a client can correlate its optimistic row with
|
|
6
|
+
* the confirmed server row by identity instead of matching message text.
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
import { beforeEach, describe, expect, mock, test } from "bun:test";
|
|
10
|
+
|
|
11
|
+
mock.module("../util/logger.js", () => ({
|
|
12
|
+
getLogger: () =>
|
|
13
|
+
new Proxy({} as Record<string, unknown>, {
|
|
14
|
+
get: () => () => {},
|
|
15
|
+
}),
|
|
16
|
+
}));
|
|
17
|
+
|
|
18
|
+
mock.module("../config/loader.js", () => ({
|
|
19
|
+
getConfig: () => ({
|
|
20
|
+
ui: {},
|
|
21
|
+
model: "test",
|
|
22
|
+
provider: "test",
|
|
23
|
+
memory: { enabled: false },
|
|
24
|
+
rateLimit: { maxRequestsPerMinute: 0 },
|
|
25
|
+
}),
|
|
26
|
+
}));
|
|
27
|
+
|
|
28
|
+
import { addMessage, createConversation } from "../memory/conversation-crud.js";
|
|
29
|
+
import { getDb } from "../memory/db-connection.js";
|
|
30
|
+
import { initializeDb } from "../memory/db-init.js";
|
|
31
|
+
import { handleListMessages } from "../runtime/routes/conversation-routes.js";
|
|
32
|
+
|
|
33
|
+
initializeDb();
|
|
34
|
+
|
|
35
|
+
function resetTables() {
|
|
36
|
+
const db = getDb();
|
|
37
|
+
db.run("DELETE FROM messages");
|
|
38
|
+
db.run("DELETE FROM conversations");
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
interface MessagePayload {
|
|
42
|
+
role: string;
|
|
43
|
+
clientMessageId?: string;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
describe("handleListMessages clientMessageId", () => {
|
|
47
|
+
beforeEach(resetTables);
|
|
48
|
+
|
|
49
|
+
test("echoes the persisted clientMessageId onto the user row", async () => {
|
|
50
|
+
// GIVEN a user message persisted with a client-generated idempotency nonce
|
|
51
|
+
const conv = createConversation();
|
|
52
|
+
await addMessage(
|
|
53
|
+
conv.id,
|
|
54
|
+
"user",
|
|
55
|
+
JSON.stringify([{ type: "text", text: "hello" }]),
|
|
56
|
+
{ clientMessageId: "nonce-abc", skipIndexing: true },
|
|
57
|
+
);
|
|
58
|
+
|
|
59
|
+
// WHEN the messages snapshot is built
|
|
60
|
+
const response = handleListMessages({
|
|
61
|
+
queryParams: { conversationId: conv.id },
|
|
62
|
+
});
|
|
63
|
+
const body = response as { messages: MessagePayload[] };
|
|
64
|
+
|
|
65
|
+
// THEN the snapshot row carries the nonce back for id-based correlation
|
|
66
|
+
expect(body.messages).toHaveLength(1);
|
|
67
|
+
expect(body.messages[0].role).toBe("user");
|
|
68
|
+
expect(body.messages[0].clientMessageId).toBe("nonce-abc");
|
|
69
|
+
});
|
|
70
|
+
|
|
71
|
+
test("omits clientMessageId when the row was persisted without one", async () => {
|
|
72
|
+
// GIVEN a user message persisted without a client nonce (e.g. a channel send)
|
|
73
|
+
const conv = createConversation();
|
|
74
|
+
await addMessage(
|
|
75
|
+
conv.id,
|
|
76
|
+
"user",
|
|
77
|
+
JSON.stringify([{ type: "text", text: "hello" }]),
|
|
78
|
+
{ skipIndexing: true },
|
|
79
|
+
);
|
|
80
|
+
|
|
81
|
+
// WHEN the messages snapshot is built
|
|
82
|
+
const response = handleListMessages({
|
|
83
|
+
queryParams: { conversationId: conv.id },
|
|
84
|
+
});
|
|
85
|
+
const body = response as { messages: MessagePayload[] };
|
|
86
|
+
|
|
87
|
+
// THEN the field is absent rather than emitted as null/empty
|
|
88
|
+
expect(body.messages).toHaveLength(1);
|
|
89
|
+
expect(body.messages[0].clientMessageId).toBeUndefined();
|
|
90
|
+
});
|
|
91
|
+
});
|
|
@@ -96,6 +96,44 @@ describe("handleListMessages metadata.hidden filtering", () => {
|
|
|
96
96
|
expect(llmRows[1].metadata).toContain('"hidden":true');
|
|
97
97
|
});
|
|
98
98
|
|
|
99
|
+
test("UI serializer omits system rows but LLM-side getMessages includes them", async () => {
|
|
100
|
+
// GIVEN a conversation with a system row sandwiched between two
|
|
101
|
+
// renderable turns (e.g. a skill-authored context message)
|
|
102
|
+
const conv = createConversation();
|
|
103
|
+
await addMessage(
|
|
104
|
+
conv.id,
|
|
105
|
+
"user",
|
|
106
|
+
JSON.stringify([{ type: "text", text: "first visible" }]),
|
|
107
|
+
);
|
|
108
|
+
await addMessage(
|
|
109
|
+
conv.id,
|
|
110
|
+
"system",
|
|
111
|
+
JSON.stringify([{ type: "text", text: "system scaffolding" }]),
|
|
112
|
+
);
|
|
113
|
+
await addMessage(
|
|
114
|
+
conv.id,
|
|
115
|
+
"assistant",
|
|
116
|
+
JSON.stringify([{ type: "text", text: "second visible" }]),
|
|
117
|
+
);
|
|
118
|
+
|
|
119
|
+
// WHEN the UI history list is serialized
|
|
120
|
+
const response = handleListMessages({
|
|
121
|
+
queryParams: { conversationId: conv.id },
|
|
122
|
+
});
|
|
123
|
+
const body = response as { messages: MessagePayload[] };
|
|
124
|
+
|
|
125
|
+
// THEN only the user/assistant turns are returned, never the system row
|
|
126
|
+
expect(body.messages.map((m) => m.role)).toEqual(["user", "assistant"]);
|
|
127
|
+
expect(
|
|
128
|
+
body.messages.some((m) => plainText(m).includes("system scaffolding")),
|
|
129
|
+
).toBe(false);
|
|
130
|
+
|
|
131
|
+
// AND the LLM-side loader still includes the system row for agent context
|
|
132
|
+
const llmRows = getMessages(conv.id);
|
|
133
|
+
expect(llmRows).toHaveLength(3);
|
|
134
|
+
expect(llmRows[1].role).toBe("system");
|
|
135
|
+
});
|
|
136
|
+
|
|
99
137
|
test("messages without metadata or with hidden=false are returned", async () => {
|
|
100
138
|
const conv = createConversation();
|
|
101
139
|
await addMessage(
|
|
@@ -1,12 +1,16 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Tests for the default `user-prompt-submit-temp` hook (memory retrieval
|
|
2
|
+
* Tests for the default `user-prompt-submit-temp` hook (memory retrieval +
|
|
3
|
+
* runtime injection).
|
|
3
4
|
*
|
|
4
5
|
* Covers the retrieval behavior, the side effects the hook owns (injected-block
|
|
5
|
-
* metadata, recall log, `memory_recalled` event), trust gating,
|
|
6
|
-
*
|
|
7
|
-
*
|
|
8
|
-
*
|
|
9
|
-
*
|
|
6
|
+
* metadata, recall log, `memory_recalled` event), trust gating, the runtime
|
|
7
|
+
* injection the hook applies for every actor (and the assembled-block persist),
|
|
8
|
+
* error propagation, and abort-signal forwarding. Uses `mock.module` to stub
|
|
9
|
+
* the persistence helpers, `applyRuntimeInjections`, and the conversation
|
|
10
|
+
* registry / trust resolver so the test doesn't touch the developer's real
|
|
11
|
+
* `~/.vellum`, database, or live conversation state. The memory graph handle,
|
|
12
|
+
* abort signal, and trust class are self-resolved by the hook from a fake
|
|
13
|
+
* conversation installed in the registry mock.
|
|
10
14
|
*/
|
|
11
15
|
|
|
12
16
|
import { beforeEach, describe, expect, mock, test } from "bun:test";
|
|
@@ -23,7 +27,36 @@ mock.module("../memory/memory-recall-log-store.js", () => ({
|
|
|
23
27
|
recordMemoryRecallLog: recordMemoryRecallLogMock,
|
|
24
28
|
}));
|
|
25
29
|
|
|
30
|
+
// Pass-through injection stub: returns the input messages unchanged with no
|
|
31
|
+
// assembled blocks, so existing array-identity assertions hold. Individual
|
|
32
|
+
// tests override it via `mockImplementationOnce` to exercise injected blocks.
|
|
33
|
+
const applyRuntimeInjectionsMock = mock(async (messages: unknown) => ({
|
|
34
|
+
messages,
|
|
35
|
+
blocks: {},
|
|
36
|
+
}));
|
|
37
|
+
mock.module("../daemon/conversation-runtime-assembly.js", () => ({
|
|
38
|
+
applyRuntimeInjections: applyRuntimeInjectionsMock,
|
|
39
|
+
}));
|
|
40
|
+
|
|
41
|
+
// The hook self-resolves the live conversation and its trust class; both are
|
|
42
|
+
// driven from these refs so each test controls what the hook sees.
|
|
43
|
+
let currentConversation: Conversation | undefined;
|
|
44
|
+
let currentTrustClass: "guardian" | "unknown" = "guardian";
|
|
45
|
+
const findConversationOrSubagentMock = mock(
|
|
46
|
+
(_conversationId?: string) => currentConversation,
|
|
47
|
+
);
|
|
48
|
+
mock.module("../daemon/conversation-registry.js", () => ({
|
|
49
|
+
findConversationOrSubagent: findConversationOrSubagentMock,
|
|
50
|
+
}));
|
|
51
|
+
mock.module("../daemon/trust-context.js", () => ({
|
|
52
|
+
resolveTrustClass: () => currentTrustClass,
|
|
53
|
+
}));
|
|
54
|
+
mock.module("../config/loader.js", () => ({
|
|
55
|
+
getConfig: () => ({}) as AssistantConfig,
|
|
56
|
+
}));
|
|
57
|
+
|
|
26
58
|
import type { AssistantConfig } from "../config/schema.js";
|
|
59
|
+
import type { Conversation } from "../daemon/conversation.js";
|
|
27
60
|
import type { ServerMessage } from "../daemon/message-protocol.js";
|
|
28
61
|
import type { ConversationGraphMemory } from "../memory/graph/conversation-graph-memory.js";
|
|
29
62
|
import type { QdrantSparseVector } from "../memory/qdrant-client.js";
|
|
@@ -104,26 +137,47 @@ function makeFakeGraphMemory(overrides?: {
|
|
|
104
137
|
function makeHookCtx(
|
|
105
138
|
overrides: Partial<MemoryRetrievalHookContext> = {},
|
|
106
139
|
): MemoryRetrievalHookContext {
|
|
107
|
-
const { memory } = makeFakeGraphMemory();
|
|
108
140
|
return {
|
|
109
|
-
graphMemory: memory,
|
|
110
|
-
config: {} as AssistantConfig,
|
|
111
141
|
onEvent: () => {},
|
|
112
|
-
isTrustedActor: true,
|
|
113
142
|
conversationId: "conv-test",
|
|
114
143
|
userMessageId: "msg-test",
|
|
115
144
|
logger: {
|
|
116
145
|
warn: () => {},
|
|
117
146
|
} as unknown as MemoryRetrievalHookContext["logger"],
|
|
118
|
-
signal: new AbortController().signal,
|
|
119
147
|
latestMessages: [],
|
|
148
|
+
requestId: "req-test",
|
|
149
|
+
mode: "full",
|
|
150
|
+
isNonInteractive: false,
|
|
151
|
+
modelProfile: null,
|
|
152
|
+
actorContext: null,
|
|
120
153
|
...overrides,
|
|
121
154
|
};
|
|
122
155
|
}
|
|
123
156
|
|
|
157
|
+
/**
|
|
158
|
+
* Install a fake live conversation for the hook to self-resolve by id: the
|
|
159
|
+
* graph handle, abort signal, and trust class all come from here rather than
|
|
160
|
+
* the hook context.
|
|
161
|
+
*/
|
|
162
|
+
function installConversation(
|
|
163
|
+
graphMemory: ConversationGraphMemory,
|
|
164
|
+
opts?: { trusted?: boolean; signal?: AbortSignal },
|
|
165
|
+
): void {
|
|
166
|
+
currentTrustClass = opts?.trusted === false ? "unknown" : "guardian";
|
|
167
|
+
currentConversation = {
|
|
168
|
+
graphMemory,
|
|
169
|
+
trustContext: undefined,
|
|
170
|
+
abortController: { signal: opts?.signal ?? new AbortController().signal },
|
|
171
|
+
} as unknown as Conversation;
|
|
172
|
+
}
|
|
173
|
+
|
|
124
174
|
beforeEach(() => {
|
|
125
175
|
updateMessageMetadataMock.mockReset();
|
|
126
176
|
recordMemoryRecallLogMock.mockReset();
|
|
177
|
+
applyRuntimeInjectionsMock.mockClear();
|
|
178
|
+
findConversationOrSubagentMock.mockClear();
|
|
179
|
+
currentConversation = undefined;
|
|
180
|
+
currentTrustClass = "guardian";
|
|
127
181
|
});
|
|
128
182
|
|
|
129
183
|
describe("user-prompt-submit-temp hook (memory retrieval)", () => {
|
|
@@ -134,7 +188,8 @@ describe("user-prompt-submit-temp hook (memory retrieval)", () => {
|
|
|
134
188
|
const { memory, prepareMemoryMock } = makeFakeGraphMemory({
|
|
135
189
|
messages: injected,
|
|
136
190
|
});
|
|
137
|
-
|
|
191
|
+
installConversation(memory, { trusted: true });
|
|
192
|
+
const ctx = makeHookCtx();
|
|
138
193
|
|
|
139
194
|
await userPromptSubmitMemoryRetrieval(ctx);
|
|
140
195
|
|
|
@@ -156,7 +211,8 @@ describe("user-prompt-submit-temp hook (memory retrieval)", () => {
|
|
|
156
211
|
userQueryVector: userDense,
|
|
157
212
|
userQuerySparseVector: userSparse,
|
|
158
213
|
});
|
|
159
|
-
|
|
214
|
+
installConversation(withUserQuery.memory, { trusted: true });
|
|
215
|
+
const userCtx = makeHookCtx();
|
|
160
216
|
await userPromptSubmitMemoryRetrieval(userCtx);
|
|
161
217
|
// User-query pair wins — never crossed with the summary signal — and is
|
|
162
218
|
// recorded back onto the graph handle for the PKB-reminder injector.
|
|
@@ -169,7 +225,8 @@ describe("user-prompt-submit-temp hook (memory retrieval)", () => {
|
|
|
169
225
|
queryVector: summaryDense,
|
|
170
226
|
sparseVector: summarySparse,
|
|
171
227
|
});
|
|
172
|
-
|
|
228
|
+
installConversation(summaryOnly.memory, { trusted: true });
|
|
229
|
+
const summaryCtx = makeHookCtx();
|
|
173
230
|
await userPromptSubmitMemoryRetrieval(summaryCtx);
|
|
174
231
|
expect(summaryOnly.recordPkbQueryVectorsMock).toHaveBeenCalledWith(
|
|
175
232
|
summaryDense,
|
|
@@ -177,17 +234,59 @@ describe("user-prompt-submit-temp hook (memory retrieval)", () => {
|
|
|
177
234
|
);
|
|
178
235
|
});
|
|
179
236
|
|
|
237
|
+
test("applies runtime injection on the retrieved history and persists the assembled blocks", async () => {
|
|
238
|
+
const retrieved: Message[] = [
|
|
239
|
+
{ role: "user", content: [{ type: "text", text: "retrieved" }] },
|
|
240
|
+
];
|
|
241
|
+
const injected: Message[] = [
|
|
242
|
+
{ role: "user", content: [{ type: "text", text: "injected" }] },
|
|
243
|
+
];
|
|
244
|
+
const { memory } = makeFakeGraphMemory({ messages: retrieved });
|
|
245
|
+
applyRuntimeInjectionsMock.mockImplementationOnce(async () => ({
|
|
246
|
+
messages: injected,
|
|
247
|
+
blocks: { unifiedTurnContext: "tc-block" },
|
|
248
|
+
}));
|
|
249
|
+
installConversation(memory, { trusted: true });
|
|
250
|
+
const ctx = makeHookCtx({ userMessageId: "msg-77" });
|
|
251
|
+
|
|
252
|
+
await userPromptSubmitMemoryRetrieval(ctx);
|
|
253
|
+
|
|
254
|
+
// Injection runs on the retrieved history; its result becomes the working
|
|
255
|
+
// list the loop reads back.
|
|
256
|
+
expect(applyRuntimeInjectionsMock).toHaveBeenCalledTimes(1);
|
|
257
|
+
expect(applyRuntimeInjectionsMock.mock.calls[0]?.[0]).toBe(retrieved);
|
|
258
|
+
expect(ctx.latestMessages).toBe(injected);
|
|
259
|
+
// The assembled blocks are persisted onto the user message metadata.
|
|
260
|
+
expect(updateMessageMetadataMock).toHaveBeenCalledWith("msg-77", {
|
|
261
|
+
turnContextBlock: "tc-block",
|
|
262
|
+
});
|
|
263
|
+
});
|
|
264
|
+
|
|
265
|
+
test("applies runtime injection for untrusted actors despite skipping retrieval", async () => {
|
|
266
|
+
const { memory, prepareMemoryMock } = makeFakeGraphMemory();
|
|
267
|
+
const seeded: Message[] = [
|
|
268
|
+
{ role: "user", content: [{ type: "text", text: "seeded" }] },
|
|
269
|
+
];
|
|
270
|
+
installConversation(memory, { trusted: false });
|
|
271
|
+
const ctx = makeHookCtx({ latestMessages: seeded });
|
|
272
|
+
|
|
273
|
+
await userPromptSubmitMemoryRetrieval(ctx);
|
|
274
|
+
|
|
275
|
+
// The memory-graph step is gated on trust, but injection runs for everyone
|
|
276
|
+
// — on the seeded history, since no retrieval replaced it.
|
|
277
|
+
expect(prepareMemoryMock).not.toHaveBeenCalled();
|
|
278
|
+
expect(applyRuntimeInjectionsMock).toHaveBeenCalledTimes(1);
|
|
279
|
+
expect(applyRuntimeInjectionsMock.mock.calls[0]?.[0]).toBe(seeded);
|
|
280
|
+
});
|
|
281
|
+
|
|
180
282
|
test("skips graph retrieval and side effects for untrusted actors", async () => {
|
|
181
283
|
const { memory, prepareMemoryMock, recordPkbQueryVectorsMock } =
|
|
182
284
|
makeFakeGraphMemory();
|
|
183
285
|
const seeded: Message[] = [
|
|
184
286
|
{ role: "user", content: [{ type: "text", text: "seeded" }] },
|
|
185
287
|
];
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
isTrustedActor: false,
|
|
189
|
-
latestMessages: seeded,
|
|
190
|
-
});
|
|
288
|
+
installConversation(memory, { trusted: false });
|
|
289
|
+
const ctx = makeHookCtx({ latestMessages: seeded });
|
|
191
290
|
|
|
192
291
|
await userPromptSubmitMemoryRetrieval(ctx);
|
|
193
292
|
|
|
@@ -206,8 +305,8 @@ describe("user-prompt-submit-temp hook (memory retrieval)", () => {
|
|
|
206
305
|
injectedBlockText: "injected-block",
|
|
207
306
|
metrics: makeMetrics(),
|
|
208
307
|
});
|
|
308
|
+
installConversation(memory, { trusted: true });
|
|
209
309
|
const ctx = makeHookCtx({
|
|
210
|
-
graphMemory: memory,
|
|
211
310
|
onEvent: (msg) => received.push(msg),
|
|
212
311
|
userMessageId: "msg-42",
|
|
213
312
|
conversationId: "conv-42",
|
|
@@ -231,7 +330,8 @@ describe("user-prompt-submit-temp hook (memory retrieval)", () => {
|
|
|
231
330
|
|
|
232
331
|
test("skips metadata persist when no block text is injected", async () => {
|
|
233
332
|
const { memory } = makeFakeGraphMemory({ injectedBlockText: null });
|
|
234
|
-
|
|
333
|
+
installConversation(memory, { trusted: true });
|
|
334
|
+
const ctx = makeHookCtx();
|
|
235
335
|
|
|
236
336
|
await userPromptSubmitMemoryRetrieval(ctx);
|
|
237
337
|
|
|
@@ -254,16 +354,17 @@ describe("user-prompt-submit-temp hook (memory retrieval)", () => {
|
|
|
254
354
|
const graphMemory = {
|
|
255
355
|
prepareMemory: failingPrepare,
|
|
256
356
|
} as unknown as ConversationGraphMemory;
|
|
257
|
-
|
|
357
|
+
installConversation(graphMemory, { trusted: true });
|
|
358
|
+
const ctx = makeHookCtx();
|
|
258
359
|
|
|
259
360
|
await expect(userPromptSubmitMemoryRetrieval(ctx)).rejects.toThrow(
|
|
260
361
|
"retrieval failed",
|
|
261
362
|
);
|
|
262
363
|
});
|
|
263
364
|
|
|
264
|
-
test("forwards the
|
|
265
|
-
// The hook hands
|
|
266
|
-
// external cancel aborts the underlying retrieval.
|
|
365
|
+
test("forwards the conversation abort signal into prepareMemory", async () => {
|
|
366
|
+
// The hook hands the live conversation's abort signal straight to
|
|
367
|
+
// `prepareMemory` so an external cancel aborts the underlying retrieval.
|
|
267
368
|
let capturedSignal: AbortSignal | undefined;
|
|
268
369
|
const prepareMemoryMock = mock(
|
|
269
370
|
async (
|
|
@@ -288,7 +389,11 @@ describe("user-prompt-submit-temp hook (memory retrieval)", () => {
|
|
|
288
389
|
recordPkbQueryVectors: mock(() => {}),
|
|
289
390
|
} as unknown as ConversationGraphMemory;
|
|
290
391
|
const controller = new AbortController();
|
|
291
|
-
|
|
392
|
+
installConversation(graphMemory, {
|
|
393
|
+
trusted: true,
|
|
394
|
+
signal: controller.signal,
|
|
395
|
+
});
|
|
396
|
+
const ctx = makeHookCtx();
|
|
292
397
|
|
|
293
398
|
await userPromptSubmitMemoryRetrieval(ctx);
|
|
294
399
|
|
|
@@ -11,6 +11,9 @@
|
|
|
11
11
|
* - Skips (re)injection when the `<info>` block is already present in the
|
|
12
12
|
* turn's working messages (presence detection — the block persists in
|
|
13
13
|
* history between compactions).
|
|
14
|
+
* - Still injects when only a dynamic `<memory>` activation block is present
|
|
15
|
+
* (that wrapper must not be mistaken for the static block) — both on a
|
|
16
|
+
* normal turn and after compaction strips the prior `<info>` block.
|
|
14
17
|
*
|
|
15
18
|
* The injector sources its content itself via `readMemoryV2StaticContent()`
|
|
16
19
|
* behind the personal-memory trust gate, so each test seeds the workspace
|
|
@@ -35,7 +38,7 @@ mock.module("../config/loader.js", () => ({
|
|
|
35
38
|
}));
|
|
36
39
|
|
|
37
40
|
const { defaultInjectors } =
|
|
38
|
-
await import("../plugins/defaults/injectors
|
|
41
|
+
await import("../plugins/defaults/memory-retrieval/injectors.js");
|
|
39
42
|
import type { Injector, TurnContext } from "../plugins/types.js";
|
|
40
43
|
import type { Message } from "../providers/types.js";
|
|
41
44
|
import { getWorkspacePromptPath } from "../util/platform.js";
|
|
@@ -54,7 +57,6 @@ function makeContext(overrides: Partial<TurnContext> = {}): TurnContext {
|
|
|
54
57
|
conversationId: "conv-test",
|
|
55
58
|
turnIndex: 0,
|
|
56
59
|
trust: { sourceChannel: "vellum", trustClass: "guardian" },
|
|
57
|
-
injectionInputs: {},
|
|
58
60
|
...overrides,
|
|
59
61
|
};
|
|
60
62
|
}
|
|
@@ -96,7 +98,7 @@ describe("memory-v2-static injector", () => {
|
|
|
96
98
|
|
|
97
99
|
test("returns null in minimal mode even with content", async () => {
|
|
98
100
|
seedEssentials("Alice prefers VS Code.");
|
|
99
|
-
const ctx = makeContext({
|
|
101
|
+
const ctx = makeContext({ mode: "minimal" });
|
|
100
102
|
expect(await memoryV2StaticInjector.produce(ctx)).toBeNull();
|
|
101
103
|
});
|
|
102
104
|
|
|
@@ -140,10 +142,12 @@ describe("memory-v2-static injector", () => {
|
|
|
140
142
|
expect(await memoryV2StaticInjector.produce(ctx, runMessages)).toBeNull();
|
|
141
143
|
});
|
|
142
144
|
|
|
143
|
-
test("
|
|
144
|
-
//
|
|
145
|
-
// `<memory>…</memory
|
|
146
|
-
//
|
|
145
|
+
test("injects the <info> block even when a dynamic <memory> activation block is present", async () => {
|
|
146
|
+
// Regression (#33612): the v2 *dynamic* activation block uses the
|
|
147
|
+
// `<memory>…</memory>` wrapper and `prepareMemory` prepends it to the tail
|
|
148
|
+
// user message every turn, before this injector runs. The static injector
|
|
149
|
+
// must NOT treat that as its own `<info>` block — doing so suppressed the
|
|
150
|
+
// static block on essentially every turn.
|
|
147
151
|
seedEssentials("Alice prefers VS Code.");
|
|
148
152
|
const ctx = makeContext();
|
|
149
153
|
const runMessages: Message[] = [
|
|
@@ -152,12 +156,86 @@ describe("memory-v2-static injector", () => {
|
|
|
152
156
|
content: [
|
|
153
157
|
{
|
|
154
158
|
type: "text",
|
|
155
|
-
text: "<memory>\n
|
|
159
|
+
text: "<memory>\n# memory/concepts/foo.md\nactivated page summary\n</memory>",
|
|
156
160
|
},
|
|
157
161
|
{ type: "text", text: "What next?" },
|
|
158
162
|
],
|
|
159
163
|
},
|
|
160
164
|
];
|
|
165
|
+
const block = await memoryV2StaticInjector.produce(ctx, runMessages);
|
|
166
|
+
expect(block).not.toBeNull();
|
|
167
|
+
expect(block!.id).toBe("memory-v2-static");
|
|
168
|
+
expect(block!.text).toBe(
|
|
169
|
+
"<info>\n## Essentials\n\nAlice prefers VS Code.\n</info>",
|
|
170
|
+
);
|
|
171
|
+
});
|
|
172
|
+
|
|
173
|
+
test("reinjects the <info> block after compaction strips it, alongside a fresh <memory> block", async () => {
|
|
174
|
+
// Post-compaction state: `stripInjectionsForCompaction` removed the prior
|
|
175
|
+
// `<info>` block, and the next turn's `prepareMemory` re-added a fresh
|
|
176
|
+
// dynamic `<memory>` block ahead of the chain. With no `<info>` present, the
|
|
177
|
+
// injector must reinject it rather than skip on the `<memory>` block.
|
|
178
|
+
seedEssentials("Alice prefers VS Code.");
|
|
179
|
+
const ctx = makeContext();
|
|
180
|
+
const postCompactMessages: Message[] = [
|
|
181
|
+
{
|
|
182
|
+
role: "user",
|
|
183
|
+
content: [
|
|
184
|
+
{ type: "text", text: "<memory>\nactivated page summary\n</memory>" },
|
|
185
|
+
{ type: "text", text: "Continue please." },
|
|
186
|
+
],
|
|
187
|
+
},
|
|
188
|
+
];
|
|
189
|
+
const block = await memoryV2StaticInjector.produce(
|
|
190
|
+
ctx,
|
|
191
|
+
postCompactMessages,
|
|
192
|
+
);
|
|
193
|
+
expect(block).not.toBeNull();
|
|
194
|
+
expect(block!.id).toBe("memory-v2-static");
|
|
195
|
+
expect(block!.text).toContain("## Essentials");
|
|
196
|
+
});
|
|
197
|
+
|
|
198
|
+
test("skips (re)injection when the <info> block is carried in a flattened history block", async () => {
|
|
199
|
+
// Regression: when a historical user message's separate injection content
|
|
200
|
+
// blocks get collapsed into one text block (observed after a daemon restart
|
|
201
|
+
// / plugin hot-reload rebuilt the rendered history mid-conversation — the
|
|
202
|
+
// blocks are joined with "\n"), the `<info>` wrapper is no longer its own
|
|
203
|
+
// block but a newline-delimited section inside a larger block. Presence
|
|
204
|
+
// detection must still recognize it; otherwise the injector re-injects a
|
|
205
|
+
// duplicate `<info>` onto the new tail (the double-injection bug).
|
|
206
|
+
seedEssentials("Alice prefers VS Code.");
|
|
207
|
+
const ctx = makeContext();
|
|
208
|
+
const flattenedFirstUser = [
|
|
209
|
+
"<workspace>\nRoot: /ws\nFiles: a.md\n</workspace>",
|
|
210
|
+
"<turn_context>\ncurrent_time: now\n</turn_context>",
|
|
211
|
+
"<memory>\nactivated page summary\n</memory>",
|
|
212
|
+
"<info>\n## Essentials\n\nAlice prefers VS Code.\n</info>",
|
|
213
|
+
"what should we do next?",
|
|
214
|
+
].join("\n");
|
|
215
|
+
const runMessages: Message[] = [
|
|
216
|
+
{ role: "user", content: [{ type: "text", text: flattenedFirstUser }] },
|
|
217
|
+
{ role: "assistant", content: [{ type: "text", text: "On it." }] },
|
|
218
|
+
{ role: "user", content: [{ type: "text", text: "great, continue" }] },
|
|
219
|
+
];
|
|
161
220
|
expect(await memoryV2StaticInjector.produce(ctx, runMessages)).toBeNull();
|
|
162
221
|
});
|
|
222
|
+
|
|
223
|
+
test("still injects when a user message opens an <info> tag without closing it", async () => {
|
|
224
|
+
// False-positive guard: the wrapper matcher requires BOTH the opening and
|
|
225
|
+
// closing tags, so user prose that merely opens an injection-like tag must
|
|
226
|
+
// not be mistaken for a carried block and suppress injection.
|
|
227
|
+
seedEssentials("Alice prefers VS Code.");
|
|
228
|
+
const ctx = makeContext();
|
|
229
|
+
const runMessages: Message[] = [
|
|
230
|
+
{
|
|
231
|
+
role: "user",
|
|
232
|
+
content: [
|
|
233
|
+
{ type: "text", text: "<info>\n is this how the tag works?" },
|
|
234
|
+
],
|
|
235
|
+
},
|
|
236
|
+
];
|
|
237
|
+
const block = await memoryV2StaticInjector.produce(ctx, runMessages);
|
|
238
|
+
expect(block).not.toBeNull();
|
|
239
|
+
expect(block!.id).toBe("memory-v2-static");
|
|
240
|
+
});
|
|
163
241
|
});
|
|
@@ -128,12 +128,19 @@ describe("Parallel tool execution benchmarks", () => {
|
|
|
128
128
|
return { content: "ok", isError: false };
|
|
129
129
|
};
|
|
130
130
|
|
|
131
|
-
const loop = new AgentLoop(
|
|
131
|
+
const loop = new AgentLoop({
|
|
132
|
+
provider: provider,
|
|
133
|
+
systemPrompt: "system",
|
|
134
|
+
conversationId: "test-conversation",
|
|
132
135
|
tools: dummyTools,
|
|
133
136
|
toolExecutor: toolExecutor,
|
|
134
137
|
});
|
|
135
138
|
const start = Date.now();
|
|
136
|
-
await loop.run(
|
|
139
|
+
await loop.run({
|
|
140
|
+
messages: [userMessage],
|
|
141
|
+
onEvent: () => {},
|
|
142
|
+
trust: { sourceChannel: "vellum", trustClass: "unknown" },
|
|
143
|
+
});
|
|
137
144
|
const elapsed = Date.now() - start;
|
|
138
145
|
|
|
139
146
|
// Parallel: ~50ms + overhead. Sequential would be ~250ms.
|
|
@@ -169,12 +176,19 @@ describe("Parallel tool execution benchmarks", () => {
|
|
|
169
176
|
return { content: "ok", isError: false };
|
|
170
177
|
};
|
|
171
178
|
|
|
172
|
-
const loop = new AgentLoop(
|
|
179
|
+
const loop = new AgentLoop({
|
|
180
|
+
provider: provider,
|
|
181
|
+
systemPrompt: "system",
|
|
182
|
+
conversationId: "test-conversation",
|
|
173
183
|
tools: dummyTools,
|
|
174
184
|
toolExecutor: toolExecutor,
|
|
175
185
|
});
|
|
176
186
|
const start = Date.now();
|
|
177
|
-
await loop.run(
|
|
187
|
+
await loop.run({
|
|
188
|
+
messages: [userMessage],
|
|
189
|
+
onEvent: () => {},
|
|
190
|
+
trust: { sourceChannel: "vellum", trustClass: "unknown" },
|
|
191
|
+
});
|
|
178
192
|
const elapsed = Date.now() - start;
|
|
179
193
|
|
|
180
194
|
// All 10 tools should have executed
|
|
@@ -222,13 +236,20 @@ describe("Parallel tool execution benchmarks", () => {
|
|
|
222
236
|
return { content: "ok", isError: false };
|
|
223
237
|
};
|
|
224
238
|
|
|
225
|
-
const loop = new AgentLoop(
|
|
239
|
+
const loop = new AgentLoop({
|
|
240
|
+
provider: provider,
|
|
241
|
+
systemPrompt: "system",
|
|
242
|
+
conversationId: "test-conversation",
|
|
226
243
|
tools: dummyTools,
|
|
227
244
|
toolExecutor: toolExecutor,
|
|
228
245
|
});
|
|
229
246
|
const events: AgentEvent[] = [];
|
|
230
247
|
const start = Date.now();
|
|
231
|
-
await loop.run(
|
|
248
|
+
await loop.run({
|
|
249
|
+
messages: [userMessage],
|
|
250
|
+
onEvent: collectEvents(events),
|
|
251
|
+
trust: { sourceChannel: "vellum", trustClass: "unknown" },
|
|
252
|
+
});
|
|
232
253
|
const elapsed = Date.now() - start;
|
|
233
254
|
|
|
234
255
|
// Parallel: ~2000ms (dominated by slow tool). Sequential: ~2400ms (2000 + 4*100).
|
|
@@ -289,12 +310,18 @@ describe("Parallel tool execution benchmarks", () => {
|
|
|
289
310
|
return { content: "should not return", isError: false };
|
|
290
311
|
};
|
|
291
312
|
|
|
292
|
-
const loop = new AgentLoop(
|
|
313
|
+
const loop = new AgentLoop({
|
|
314
|
+
provider: provider,
|
|
315
|
+
systemPrompt: "system",
|
|
316
|
+
conversationId: "test-conversation",
|
|
293
317
|
tools: dummyTools,
|
|
294
318
|
toolExecutor: toolExecutor,
|
|
295
319
|
});
|
|
296
320
|
const start = Date.now();
|
|
297
|
-
const { history } = await loop.run(
|
|
321
|
+
const { history } = await loop.run({
|
|
322
|
+
messages: [userMessage],
|
|
323
|
+
onEvent: () => {},
|
|
324
|
+
trust: { sourceChannel: "vellum", trustClass: "unknown" },
|
|
298
325
|
signal: controller.signal,
|
|
299
326
|
});
|
|
300
327
|
const elapsed = Date.now() - start;
|