@vellumai/assistant 0.8.8 → 0.8.9-dev.202606091853.fbaa2ae
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/docs/activation-funnel-telemetry.md +310 -0
- 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 +263 -121
- package/package.json +2 -2
- package/src/__tests__/activation-early-marking.test.ts +120 -0
- 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 +23 -8
- 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 +6 -5
- 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 +146 -0
- package/src/__tests__/compaction-events.test.ts +7 -7
- package/src/__tests__/compaction.benchmark.test.ts +1 -1
- package/src/__tests__/compactor-web-search-strip.test.ts +213 -0
- package/src/__tests__/context-overflow-reducer.test.ts +265 -125
- package/src/__tests__/context-window-manager-compact-retry.test.ts +121 -15
- package/src/__tests__/conversation-abort-tool-results.test.ts +7 -5
- 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 +158 -234
- package/src/__tests__/conversation-agent-loop.test.ts +342 -623
- package/src/__tests__/conversation-clean-command.test.ts +5 -2
- package/src/__tests__/conversation-confirmation-signals.test.ts +6 -5
- package/src/__tests__/conversation-crud-inference-profile.test.ts +7 -9
- package/src/__tests__/conversation-error.test.ts +15 -1
- package/src/__tests__/conversation-history-web-search.test.ts +6 -1
- package/src/__tests__/conversation-media-retry.test.ts +1 -1
- package/src/__tests__/conversation-process-app-control-preactivation.test.ts +44 -4
- package/src/__tests__/conversation-process-callsite.test.ts +15 -15
- package/src/__tests__/conversation-provider-retry-repair.test.ts +66 -65
- package/src/__tests__/conversation-queue.test.ts +10 -10
- package/src/__tests__/conversation-runtime-assembly.test.ts +1059 -231
- package/src/__tests__/conversation-runtime-workspace.test.ts +115 -20
- package/src/__tests__/conversation-slash-queue.test.ts +7 -5
- package/src/__tests__/conversation-slash-unknown.test.ts +7 -5
- package/src/__tests__/conversation-speed-override.test.ts +11 -10
- package/src/__tests__/conversation-starter-routes.test.ts +14 -6
- package/src/__tests__/conversation-surfaces-activation-emit.test.ts +395 -0
- package/src/__tests__/conversation-surfaces-app-control.test.ts +48 -1
- package/src/__tests__/conversation-tool-setup-app-refresh.test.ts +73 -4
- package/src/__tests__/conversation-undo.test.ts +2 -2
- package/src/__tests__/conversation-workspace-cache-state.test.ts +24 -21
- package/src/__tests__/conversation-workspace-injection.test.ts +69 -7
- package/src/__tests__/conversation-workspace-tool-tracking.test.ts +15 -12
- package/src/__tests__/conversations-import-system-filter.test.ts +101 -0
- package/src/__tests__/corrected-target.test.ts +93 -0
- package/src/__tests__/credential-security-invariants.test.ts +1 -0
- package/src/__tests__/cu-unified-flow.test.ts +36 -0
- 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 +17 -2
- package/src/__tests__/guardian-grant-minting.test.ts +1 -1
- package/src/__tests__/guardian-routing-invariants.test.ts +93 -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 +5 -1
- package/src/__tests__/host-app-control-proxy.test.ts +45 -0
- 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__/llm-resolver.test.ts +73 -0
- package/src/__tests__/memory-retrieval-hook.test.ts +156 -38
- package/src/__tests__/memory-v2-static-injector.test.ts +86 -8
- package/src/__tests__/notification-decision-strategy.test.ts +3 -3
- package/src/__tests__/parallel-tool.benchmark.test.ts +35 -8
- package/src/__tests__/persist-unsendable-image-downscale.test.ts +145 -0
- package/src/__tests__/persist-unsendable-image.test.ts +97 -1
- package/src/__tests__/plugin-api-shim.test.ts +6 -9
- package/src/__tests__/plugin-bootstrap.test.ts +91 -81
- package/src/__tests__/plugin-registry.test.ts +3 -49
- package/src/__tests__/plugin-skill-contribution.test.ts +15 -14
- package/src/__tests__/plugin-tool-contribution.test.ts +7 -4
- package/src/__tests__/plugin-types.test.ts +0 -70
- package/src/__tests__/post-turn-tool-result-truncation.test.ts +69 -0
- package/src/__tests__/pre-model-call-sanitize.test.ts +109 -0
- package/src/__tests__/published-app-updater.test.ts +138 -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 +31 -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 +5 -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/__tests__/web-fetch.test.ts +45 -0
- 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-config-stub.ts +0 -2
- 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/prepare-agent-env.ts +80 -27
- package/src/acp/resolve-agent.test.ts +188 -28
- package/src/acp/resolve-agent.ts +119 -42
- 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 +403 -251
- package/src/api/responses/conversation-message.ts +22 -2
- package/src/api/responses/memory-v3-selection-log.ts +19 -10
- package/src/approvals/guardian-request-resolvers.ts +27 -1
- package/src/background-wake/next-wake.ts +1 -0
- package/src/cli/commands/__tests__/memory-v3.test.ts +191 -210
- package/src/cli/commands/channel-verification-sessions.ts +6 -6
- package/src/cli/commands/db/__tests__/repair.test.ts +3 -1
- package/src/cli/commands/memory-v3.ts +57 -199
- package/src/cli/commands/plugins.ts +43 -37
- package/src/cli/lib/__tests__/install-from-github.test.ts +790 -111
- package/src/cli/lib/__tests__/plugin-catalog-cache.test.ts +196 -0
- package/src/cli/lib/__tests__/plugin-details.test.ts +381 -0
- package/src/cli/lib/__tests__/plugin-marketplace.test.ts +270 -0
- package/src/cli/lib/__tests__/search-plugins.test.ts +233 -32
- package/src/cli/lib/install-from-github.ts +797 -60
- package/src/cli/lib/plugin-catalog-cache.ts +84 -0
- package/src/cli/lib/plugin-details.ts +416 -0
- package/src/cli/lib/plugin-marketplace.ts +215 -0
- package/src/cli/lib/search-plugins.ts +201 -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 +9 -3
- package/src/config/acp-schema.ts +1 -7
- package/src/config/bundled-skills/acp/SKILL.md +80 -41
- 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 -3
- package/src/config/feature-flag-registry.json +10 -16
- package/src/config/llm-resolver.ts +39 -7
- package/src/config/schemas/__tests__/memory-v3.test.ts +25 -9
- package/src/config/schemas/call-site-catalog.ts +0 -21
- package/src/config/schemas/heartbeat.ts +9 -0
- package/src/config/schemas/llm.ts +17 -3
- package/src/config/schemas/memory-v3.ts +58 -8
- package/src/config/seed-inference-profiles.ts +18 -0
- package/src/context/compactor.ts +22 -4
- package/src/context/post-turn-tool-result-truncation.ts +39 -1
- package/src/context/strip-injections.ts +8 -2
- package/src/daemon/conversation-agent-loop-handlers.ts +18 -36
- package/src/daemon/conversation-agent-loop.ts +308 -1297
- package/src/daemon/conversation-error.ts +31 -4
- package/src/daemon/conversation-history.ts +1 -1
- package/src/daemon/conversation-lifecycle.ts +11 -255
- package/src/daemon/conversation-media-retry.ts +19 -6
- package/src/daemon/conversation-messaging.ts +17 -0
- package/src/daemon/conversation-process.ts +22 -141
- package/src/daemon/conversation-queue-manager.ts +8 -0
- package/src/daemon/conversation-registry.ts +159 -0
- package/src/daemon/conversation-runtime-assembly.ts +361 -393
- package/src/daemon/conversation-store.ts +9 -90
- package/src/daemon/conversation-surfaces.ts +165 -11
- package/src/daemon/conversation-workspace.ts +17 -0
- package/src/daemon/conversation.ts +444 -62
- package/src/daemon/disk-pressure-policy.ts +0 -1
- package/src/daemon/external-plugins-bootstrap.ts +36 -46
- package/src/daemon/handlers/config-channels.ts +11 -2
- package/src/daemon/handlers/conversations.ts +3 -1
- package/src/daemon/handlers/skills.ts +4 -1
- package/src/daemon/host-app-control-proxy.ts +72 -57
- package/src/daemon/host-proxy-preactivation.ts +1 -3
- package/src/daemon/lifecycle.ts +21 -7
- package/src/daemon/persist-unsendable-image.ts +62 -25
- package/src/daemon/process-message.ts +1 -1
- package/src/daemon/server.ts +2 -0
- package/src/daemon/tool-side-effects.ts +15 -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__/activation-session-store.test.ts +41 -0
- package/src/memory/__tests__/jobs-worker-v2-schedule.test.ts +87 -0
- package/src/memory/__tests__/onboarding-events-store.test.ts +80 -0
- package/src/memory/activation-session-store.ts +43 -0
- package/src/memory/conversation-crud.ts +29 -19
- package/src/memory/conversation-starter-checkpoints.ts +1 -0
- package/src/memory/db-init.ts +6 -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/273-onboarding-events-funnel-columns.ts +46 -0
- package/src/memory/migrations/274-create-activation-sessions.ts +15 -0
- package/src/memory/migrations/index.ts +3 -0
- package/src/memory/onboarding-events-store.ts +66 -18
- package/src/memory/schema/acp.ts +4 -0
- package/src/memory/schema/infrastructure.ts +13 -0
- package/src/memory/v2/__tests__/consolidation-job.test.ts +4 -97
- package/src/memory/v2/__tests__/page-store.test.ts +22 -0
- package/src/memory/v2/consolidation-job.ts +2 -63
- package/src/memory/v2/types.ts +5 -0
- package/src/messaging/providers/telegram-bot/api.ts +14 -5
- package/src/notifications/__tests__/copy-composer.test.ts +244 -0
- package/src/notifications/access-request-copy.ts +298 -0
- package/src/notifications/adapters/slack.ts +3 -3
- package/src/notifications/adapters/telegram.ts +9 -2
- package/src/notifications/copy-composer.ts +49 -267
- package/src/notifications/decision-engine.ts +16 -35
- package/src/notifications/home-feed-side-effect.ts +1 -6
- package/src/plugin-api/constants.ts +4 -0
- package/src/plugin-api/index.ts +40 -11
- package/src/plugin-api/types.ts +108 -0
- package/src/plugins/defaults/compaction/compact.ts +106 -0
- package/src/{daemon → plugins/defaults/compaction}/context-overflow-reducer.ts +250 -42
- package/src/plugins/defaults/compaction/corrected-target.ts +53 -0
- package/src/plugins/defaults/compaction/manager-store.ts +57 -0
- package/src/plugins/defaults/compaction/package.json +1 -2
- package/src/{context → plugins/defaults/compaction}/window-manager.ts +95 -25
- 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 +156 -26
- package/src/plugins/defaults/memory-retrieval/hooks/post-compact.ts +100 -52
- package/src/plugins/defaults/memory-retrieval/hooks/user-prompt-submit.ts +285 -0
- 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/package.json +14 -0
- package/src/plugins/defaults/memory-retrieval/unified-turn-context.ts +223 -0
- package/src/plugins/defaults/memory-v3-shadow/__tests__/capabilities.test.ts +71 -0
- package/src/plugins/defaults/memory-v3-shadow/__tests__/dense.test.ts +181 -0
- package/src/plugins/defaults/memory-v3-shadow/__tests__/edge.test.ts +247 -0
- package/src/{memory/v3 → plugins/defaults/memory-v3-shadow}/__tests__/live-integration.test.ts +147 -134
- package/src/plugins/defaults/memory-v3-shadow/__tests__/maintain-job.test.ts +456 -0
- package/src/plugins/defaults/memory-v3-shadow/__tests__/orchestrate.test.ts +662 -0
- package/src/plugins/defaults/memory-v3-shadow/__tests__/pool-select.test.ts +306 -0
- package/src/plugins/defaults/memory-v3-shadow/__tests__/render-injection.test.ts +91 -0
- package/src/plugins/defaults/memory-v3-shadow/__tests__/section-dense-store.test.ts +402 -0
- package/src/plugins/defaults/memory-v3-shadow/__tests__/section-needle.test.ts +135 -0
- package/src/plugins/defaults/memory-v3-shadow/__tests__/sections.test.ts +125 -0
- package/src/{memory/v3 → plugins/defaults/memory-v3-shadow}/__tests__/selection-log-store.test.ts +74 -19
- package/src/plugins/defaults/memory-v3-shadow/__tests__/shadow-integration.test.ts +446 -0
- package/src/plugins/defaults/memory-v3-shadow/__tests__/shadow-plugin.test.ts +592 -0
- package/src/{memory/v3 → plugins/defaults/memory-v3-shadow}/__tests__/types.test.ts +2 -24
- package/src/plugins/defaults/memory-v3-shadow/capabilities.ts +91 -0
- package/src/plugins/defaults/memory-v3-shadow/dense.ts +97 -0
- package/src/plugins/defaults/memory-v3-shadow/edge.ts +252 -0
- 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 +76 -0
- package/src/plugins/defaults/memory-v3-shadow/maintain-job.ts +555 -0
- package/src/plugins/defaults/memory-v3-shadow/orchestrate.ts +266 -0
- package/src/plugins/defaults/memory-v3-shadow/package.json +14 -0
- package/src/plugins/defaults/memory-v3-shadow/page-content.ts +71 -0
- package/src/plugins/defaults/memory-v3-shadow/pool-select.ts +204 -0
- package/src/{memory/v3 → plugins/defaults/memory-v3-shadow}/render-injection.ts +16 -8
- package/src/plugins/defaults/memory-v3-shadow/section-dense-store.ts +236 -0
- package/src/plugins/defaults/memory-v3-shadow/section-needle.ts +200 -0
- package/src/plugins/defaults/memory-v3-shadow/sections.ts +115 -0
- package/src/plugins/defaults/memory-v3-shadow/selection-log-store.ts +156 -0
- package/src/plugins/defaults/memory-v3-shadow/shadow-plugin.ts +382 -0
- package/src/plugins/defaults/memory-v3-shadow/types.ts +92 -0
- package/src/{memory/v3 → plugins/defaults/memory-v3-shadow}/working-set.ts +4 -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/external-api.ts +12 -2
- 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/system-prompt.ts +61 -10
- package/src/prompts/templates/BOOTSTRAP-ACTIVATION-RAIL.md +71 -4
- package/src/providers/anthropic/client.ts +9 -10
- package/src/providers/inference/kimi-cjk-token-ids.ts +493 -0
- package/src/providers/inference/logit-bias.ts +55 -0
- package/src/providers/model-catalog.ts +34 -0
- package/src/providers/openai/__tests__/vision-not-supported.test.ts +75 -0
- package/src/providers/openai/chat-completions-provider.ts +44 -1
- package/src/providers/retry.ts +22 -0
- package/src/providers/types.ts +6 -0
- 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 +466 -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__/stt-routes.test.ts +112 -0
- 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 +185 -32
- package/src/runtime/routes/acp-routes.ts +328 -30
- package/src/runtime/routes/app-management-routes.ts +3 -0
- 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/channel-verification-routes.ts +14 -5
- 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 +30 -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/inbound-stages/acl-enforcement.ts +33 -34
- 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 +65 -305
- package/src/runtime/routes/playground/__tests__/force-compact.test.ts +1 -1
- package/src/runtime/routes/playground/helpers.ts +1 -1
- package/src/runtime/routes/plugins-routes.ts +337 -35
- package/src/runtime/routes/stt-routes.ts +45 -12
- package/src/runtime/routes/surface-conversation-resolver.ts +4 -3
- package/src/runtime/routes/work-items-routes.ts +2 -4
- package/src/runtime/routes/workspace-routes.ts +50 -15
- package/src/runtime/services/conversation-serializer.ts +1 -1
- package/src/runtime/verification-outbound-actions.ts +147 -2
- package/src/runtime/verification-templates.ts +29 -3
- package/src/services/published-app-updater.ts +30 -8
- package/src/signals/cancel.ts +2 -4
- package/src/subagent/manager.ts +21 -5
- package/src/telemetry/__tests__/activation-funnel.test.ts +95 -0
- package/src/telemetry/activation-funnel.ts +167 -0
- package/src/telemetry/types.ts +13 -0
- package/src/telemetry/usage-telemetry-reporter.test.ts +154 -0
- package/src/telemetry/usage-telemetry-reporter.ts +26 -1
- package/src/tools/acp/context.ts +20 -0
- package/src/tools/acp/list-agents.test.ts +9 -19
- package/src/tools/acp/list-agents.ts +3 -15
- package/src/tools/acp/spawn.test.ts +173 -202
- 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/browser/browser-execution.ts +12 -2
- package/src/tools/filesystem/write.ts +34 -0
- package/src/tools/network/web-fetch.ts +65 -24
- package/src/tools/skills/load.ts +1 -1
- package/src/tools/subagent/spawn.ts +2 -4
- package/src/tools/ui-surface/definitions.ts +32 -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/daemon/wake-target-adapter.ts +0 -253
- package/src/memory/v3/__tests__/assign.test.ts +0 -242
- package/src/memory/v3/__tests__/capabilities.test.ts +0 -118
- package/src/memory/v3/__tests__/core.test.ts +0 -39
- package/src/memory/v3/__tests__/health.test.ts +0 -219
- package/src/memory/v3/__tests__/maintain-job.test.ts +0 -288
- package/src/memory/v3/__tests__/needle.test.ts +0 -107
- package/src/memory/v3/__tests__/orchestrate.test.ts +0 -436
- package/src/memory/v3/__tests__/provider-blocks.test.ts +0 -13
- package/src/memory/v3/__tests__/reconcile.test.ts +0 -274
- package/src/memory/v3/__tests__/render-injection.test.ts +0 -61
- package/src/memory/v3/__tests__/router.test.ts +0 -332
- package/src/memory/v3/__tests__/selector.test.ts +0 -470
- package/src/memory/v3/__tests__/shadow-plugin.test.ts +0 -432
- package/src/memory/v3/__tests__/snapshot.test.ts +0 -168
- package/src/memory/v3/__tests__/tree.test.ts +0 -192
- package/src/memory/v3/assign.ts +0 -268
- package/src/memory/v3/capabilities.ts +0 -124
- package/src/memory/v3/core.ts +0 -26
- package/src/memory/v3/health.ts +0 -0
- package/src/memory/v3/maintain-job.ts +0 -314
- package/src/memory/v3/needle.ts +0 -115
- package/src/memory/v3/orchestrate.ts +0 -126
- package/src/memory/v3/page-content.ts +0 -34
- package/src/memory/v3/provider-blocks.ts +0 -26
- package/src/memory/v3/reconcile.ts +0 -523
- package/src/memory/v3/router.ts +0 -190
- package/src/memory/v3/selection-log-store.ts +0 -84
- package/src/memory/v3/selector.ts +0 -226
- package/src/memory/v3/shadow-plugin.ts +0 -411
- package/src/memory/v3/snapshot.ts +0 -209
- package/src/memory/v3/tree.ts +0 -174
- package/src/memory/v3/types.ts +0 -59
- package/src/notifications/__tests__/emit-signal-home-feed.test.ts +0 -187
- 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/memory-retrieval/hooks/user-prompt-submit-temp.ts +0 -216
- 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/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/util/map-limit.ts +0 -27
- /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__/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}/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}/llm-retry.ts +0 -0
|
@@ -0,0 +1,592 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Tests for `shadow-plugin.ts` (section-lane pipeline).
|
|
3
|
+
*
|
|
4
|
+
* The v3 plugin is flag-gated. These tests assert:
|
|
5
|
+
* - both flags OFF → orchestrate is never called and no DB rows are written;
|
|
6
|
+
* - either flag ON → orchestrate runs and selection rows land in
|
|
7
|
+
* `memory_v3_selections` with the new lane source tags;
|
|
8
|
+
* - shadow-only (live off) → the injector returns `null` (never mutates the
|
|
9
|
+
* turn) but still logs;
|
|
10
|
+
* - live on → the injector returns the rendered `<memory>` block;
|
|
11
|
+
* - an empty selection under live → `null`;
|
|
12
|
+
* - lazy-init runs the lane builders only once across multiple turns, and
|
|
13
|
+
* `invalidateLanes` forces exactly one rebuild;
|
|
14
|
+
* - `initLanes` feeds synthetic capability pages (skills / CLI commands) into
|
|
15
|
+
* the section index via `renderCapabilityContent`, so the needle lane ranks
|
|
16
|
+
* them like any other page (they are no longer always-added to the pool).
|
|
17
|
+
*
|
|
18
|
+
* All heavy dependencies (config, flag resolver, conversation reads, v2 page
|
|
19
|
+
* index + page store, the lane builders, orchestrate) are mocked BEFORE
|
|
20
|
+
* importing the plugin so the module observes them at load time. A real
|
|
21
|
+
* in-memory SQLite DB backs the write assertions via the
|
|
22
|
+
* `memory_v3_selections` migration.
|
|
23
|
+
*/
|
|
24
|
+
|
|
25
|
+
import { Database } from "bun:sqlite";
|
|
26
|
+
import { afterAll, beforeEach, describe, expect, mock, test } from "bun:test";
|
|
27
|
+
|
|
28
|
+
import { drizzle } from "drizzle-orm/bun-sqlite";
|
|
29
|
+
|
|
30
|
+
import { migrateAddMemoryV3Selections } from "../../../../memory/migrations/268-add-memory-v3-selections.js";
|
|
31
|
+
import * as schema from "../../../../memory/schema.js";
|
|
32
|
+
import type { CandidateLane, SectionIndex, SelectionSource } from "../types.js";
|
|
33
|
+
|
|
34
|
+
// `mock.module` is process-global and, in Bun, neither `mock.restore()` nor a
|
|
35
|
+
// re-mock in `afterAll` reverts it for files that load LATER in the same
|
|
36
|
+
// `bun test src/plugins/defaults/memory-v3-shadow/` run. Sibling files import
|
|
37
|
+
// these same modules for real, so an unconditional stub here would leak in and
|
|
38
|
+
// break them. Instead each stub below DELEGATES to the real implementation
|
|
39
|
+
// unless this test is actively running (`shadowMockActive`, toggled in
|
|
40
|
+
// beforeEach/afterAll).
|
|
41
|
+
//
|
|
42
|
+
// Snapshot the real exports into plain objects NOW: a module namespace object
|
|
43
|
+
// is a live view, so reading the real export *after* the stub is installed
|
|
44
|
+
// would resolve back to the stub (infinite recursion).
|
|
45
|
+
const realSections = { ...(await import("../sections.js")) };
|
|
46
|
+
const realSectionNeedle = { ...(await import("../section-needle.js")) };
|
|
47
|
+
const realEdge = { ...(await import("../edge.js")) };
|
|
48
|
+
const realSectionDenseStore = {
|
|
49
|
+
...(await import("../section-dense-store.js")),
|
|
50
|
+
};
|
|
51
|
+
const realOrchestrate = { ...(await import("../orchestrate.js")) };
|
|
52
|
+
const realPlatform = { ...(await import("../../../../util/platform.js")) };
|
|
53
|
+
const realPageStore = {
|
|
54
|
+
...(await import("../../../../memory/v2/page-store.js")),
|
|
55
|
+
};
|
|
56
|
+
const realConversationCrud = {
|
|
57
|
+
...(await import("../../../../memory/conversation-crud.js")),
|
|
58
|
+
};
|
|
59
|
+
const realSkillStore = {
|
|
60
|
+
...(await import("../../../../memory/v2/skill-store.js")),
|
|
61
|
+
};
|
|
62
|
+
const realCliCommandStore = {
|
|
63
|
+
...(await import("../../../../memory/v2/cli-command-store.js")),
|
|
64
|
+
};
|
|
65
|
+
|
|
66
|
+
let shadowMockActive = false;
|
|
67
|
+
|
|
68
|
+
// ─── mutable test state, read by the mocks below ────────────────────────────
|
|
69
|
+
|
|
70
|
+
let liveEnabled = false;
|
|
71
|
+
let shadowEnabled = false;
|
|
72
|
+
let messages: Array<{ role: string; content: string }> = [];
|
|
73
|
+
|
|
74
|
+
// A synthetic skill capability slug the page index carries. Its rendered
|
|
75
|
+
// content holds a distinctive term ("kumquat") so the real needle, built over
|
|
76
|
+
// the section index `initLanes` feeds, ranks it. Generic placeholder only.
|
|
77
|
+
const CAPABILITY_SLUG = "skills/example";
|
|
78
|
+
const CAPABILITY_CONTENT = "use the kumquat skill to do the thing";
|
|
79
|
+
|
|
80
|
+
// The orchestrate result the spy returns. `laneBySlug` records the lane that
|
|
81
|
+
// surfaced each pooled slug: page-1 → "needle", page-2 → "dense", page-3 →
|
|
82
|
+
// "edge"; `attributeSelections` reads it directly. `carried` is a carry-forward
|
|
83
|
+
// slug not re-selected this turn. `sectionBySlug` carries the matched section
|
|
84
|
+
// for the slugs that had one (page-1/page-2) — consumed by the live injector's
|
|
85
|
+
// progressive disclosure, independent of source attribution.
|
|
86
|
+
const orchestrateSpy = mock(async () => ({
|
|
87
|
+
currentSelections: [
|
|
88
|
+
{ slug: "page-1", pinned: true },
|
|
89
|
+
{ slug: "page-2", pinned: false },
|
|
90
|
+
{ slug: "page-3", pinned: false },
|
|
91
|
+
],
|
|
92
|
+
workingSetUnion: new Set<string>(["page-1", "page-2", "page-3"]),
|
|
93
|
+
finalInjection: ["page-1", "page-2", "page-3", "carried"],
|
|
94
|
+
sectionBySlug: new Map([
|
|
95
|
+
["page-1", { article: "page-1", title: "", text: "x", ordinal: 0 }],
|
|
96
|
+
["page-2", { article: "page-2", title: "", text: "y", ordinal: 0 }],
|
|
97
|
+
]),
|
|
98
|
+
laneBySlug: new Map<string, CandidateLane>([
|
|
99
|
+
["page-1", "needle"],
|
|
100
|
+
["page-2", "dense"],
|
|
101
|
+
["page-3", "edge"],
|
|
102
|
+
]),
|
|
103
|
+
}));
|
|
104
|
+
|
|
105
|
+
let sectionBuilds = 0;
|
|
106
|
+
let needleBuilds = 0;
|
|
107
|
+
let edgeBuilds = 0;
|
|
108
|
+
let ensureCollectionCalls = 0;
|
|
109
|
+
let ensureCollectionThrows = false;
|
|
110
|
+
|
|
111
|
+
// The `pageBody` resolver `initLanes` passes to `buildSectionIndex` (its second
|
|
112
|
+
// arg), captured by the stub below so a test can drive it directly: a capability
|
|
113
|
+
// slug must resolve to its rendered capability content, an on-disk slug to its
|
|
114
|
+
// page body.
|
|
115
|
+
let capturedPageBody: ((slug: string) => Promise<string>) | null = null;
|
|
116
|
+
|
|
117
|
+
// Shared in-memory DB so writes are observable from the test.
|
|
118
|
+
let testSqlite: Database;
|
|
119
|
+
let testDb = makeDb();
|
|
120
|
+
function makeDb() {
|
|
121
|
+
testSqlite = new Database(":memory:");
|
|
122
|
+
testSqlite.exec("PRAGMA journal_mode=WAL");
|
|
123
|
+
const db = drizzle(testSqlite, { schema });
|
|
124
|
+
migrateAddMemoryV3Selections(db);
|
|
125
|
+
return db;
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
const FAKE_SECTION_INDEX: SectionIndex = {
|
|
129
|
+
sections: [],
|
|
130
|
+
byArticle: new Map(),
|
|
131
|
+
};
|
|
132
|
+
|
|
133
|
+
// ─── module mocks (installed before the plugin import) ──────────────────────
|
|
134
|
+
|
|
135
|
+
mock.module("../../../../config/assistant-feature-flags.js", () => ({
|
|
136
|
+
isAssistantFeatureFlagEnabled: (key: string) =>
|
|
137
|
+
key === "memory-v3-live"
|
|
138
|
+
? liveEnabled
|
|
139
|
+
: key === "memory-v3-shadow"
|
|
140
|
+
? shadowEnabled
|
|
141
|
+
: false,
|
|
142
|
+
}));
|
|
143
|
+
|
|
144
|
+
mock.module("../../../../config/loader.js", () => ({
|
|
145
|
+
getConfig: () => ({
|
|
146
|
+
memory: {
|
|
147
|
+
v3: {
|
|
148
|
+
workingSet: { maxPages: 150, evictWindow: 5 },
|
|
149
|
+
needleK: 100,
|
|
150
|
+
denseK: 100,
|
|
151
|
+
edge: { hubDegree: 30, seedCount: 18, perSeed: 6, cap: 45 },
|
|
152
|
+
},
|
|
153
|
+
qdrant: { vectorSize: 8, onDisk: false },
|
|
154
|
+
},
|
|
155
|
+
}),
|
|
156
|
+
}));
|
|
157
|
+
|
|
158
|
+
// Spread the real module so every export the live path transitively imports
|
|
159
|
+
// stays present; only `getMessages` is overridden.
|
|
160
|
+
mock.module("../../../../memory/conversation-crud.js", () => ({
|
|
161
|
+
...realConversationCrud,
|
|
162
|
+
getMessages: () => messages.map((m, i) => ({ ...m, id: `m${i}` })),
|
|
163
|
+
}));
|
|
164
|
+
|
|
165
|
+
mock.module("../../../../memory/db-connection.js", () => ({
|
|
166
|
+
getDb: () => testDb,
|
|
167
|
+
getSqliteFrom: () => testSqlite,
|
|
168
|
+
}));
|
|
169
|
+
|
|
170
|
+
mock.module("../../../../memory/v2/page-index.js", () => ({
|
|
171
|
+
getPageIndex: async () => ({
|
|
172
|
+
entries: [
|
|
173
|
+
{
|
|
174
|
+
slug: "page-1",
|
|
175
|
+
id: 1,
|
|
176
|
+
summary: "",
|
|
177
|
+
edges: [],
|
|
178
|
+
leaves: [],
|
|
179
|
+
modifiedAt: 0,
|
|
180
|
+
},
|
|
181
|
+
{
|
|
182
|
+
slug: "page-2",
|
|
183
|
+
id: 2,
|
|
184
|
+
summary: "",
|
|
185
|
+
edges: [],
|
|
186
|
+
leaves: [],
|
|
187
|
+
modifiedAt: 0,
|
|
188
|
+
},
|
|
189
|
+
// A synthetic capability row — same shape the v2 page index appends for
|
|
190
|
+
// skills/CLI commands. `initLanes` must route it through the capability
|
|
191
|
+
// resolver, not an on-disk read.
|
|
192
|
+
{
|
|
193
|
+
slug: CAPABILITY_SLUG,
|
|
194
|
+
id: 3,
|
|
195
|
+
summary: "",
|
|
196
|
+
edges: [],
|
|
197
|
+
leaves: [],
|
|
198
|
+
modifiedAt: 0,
|
|
199
|
+
},
|
|
200
|
+
],
|
|
201
|
+
bySlug: new Map(),
|
|
202
|
+
}),
|
|
203
|
+
}));
|
|
204
|
+
|
|
205
|
+
// `pageContent` (live mode) reads the full page via `readPage`/`renderPageContent`.
|
|
206
|
+
mock.module("../../../../memory/v2/page-store.js", () => ({
|
|
207
|
+
...realPageStore,
|
|
208
|
+
readPage: async (workspaceDir: string, slug: string) =>
|
|
209
|
+
shadowMockActive
|
|
210
|
+
? { slug, frontmatter: {}, body: `body for ${slug}` }
|
|
211
|
+
: realPageStore.readPage(workspaceDir, slug),
|
|
212
|
+
renderPageContent: (page: { slug: string; body: string }) =>
|
|
213
|
+
shadowMockActive
|
|
214
|
+
? page.body
|
|
215
|
+
: realPageStore.renderPageContent(
|
|
216
|
+
page as Parameters<typeof realPageStore.renderPageContent>[0],
|
|
217
|
+
),
|
|
218
|
+
}));
|
|
219
|
+
|
|
220
|
+
mock.module("../../../../util/platform.js", () => ({
|
|
221
|
+
...realPlatform,
|
|
222
|
+
getWorkspaceDir: () =>
|
|
223
|
+
shadowMockActive
|
|
224
|
+
? "/tmp/shadow-test-workspace"
|
|
225
|
+
: realPlatform.getWorkspaceDir(),
|
|
226
|
+
}));
|
|
227
|
+
|
|
228
|
+
// Capability stores: `renderCapabilityContent` (reached from `initLanes`' pageBody
|
|
229
|
+
// and from the live injector) resolves synthetic slugs through these. Spread the
|
|
230
|
+
// real module so the prefix predicates (`isSkillSlug`/`isCliCommandSlug`) stay
|
|
231
|
+
// intact; override only the content lookup so the capability slug resolves.
|
|
232
|
+
mock.module("../../../../memory/v2/skill-store.js", () => ({
|
|
233
|
+
...realSkillStore,
|
|
234
|
+
getSkillCapability: (idOrSlug: string) =>
|
|
235
|
+
shadowMockActive
|
|
236
|
+
? idOrSlug === CAPABILITY_SLUG
|
|
237
|
+
? { id: "example", content: CAPABILITY_CONTENT }
|
|
238
|
+
: null
|
|
239
|
+
: realSkillStore.getSkillCapability(idOrSlug),
|
|
240
|
+
}));
|
|
241
|
+
|
|
242
|
+
mock.module("../../../../memory/v2/cli-command-store.js", () => ({
|
|
243
|
+
...realCliCommandStore,
|
|
244
|
+
getCliCommandCapability: (idOrSlug: string) =>
|
|
245
|
+
shadowMockActive
|
|
246
|
+
? null
|
|
247
|
+
: realCliCommandStore.getCliCommandCapability(idOrSlug),
|
|
248
|
+
}));
|
|
249
|
+
|
|
250
|
+
mock.module("../sections.js", () => ({
|
|
251
|
+
...realSections,
|
|
252
|
+
buildSectionIndex: async (
|
|
253
|
+
...args: Parameters<typeof realSections.buildSectionIndex>
|
|
254
|
+
) => {
|
|
255
|
+
if (!shadowMockActive) return realSections.buildSectionIndex(...args);
|
|
256
|
+
sectionBuilds++;
|
|
257
|
+
// Capture the `pageBody` resolver so a test can exercise the capability
|
|
258
|
+
// branch directly. Returning the FAKE index keeps the other tests cheap.
|
|
259
|
+
capturedPageBody = args[1];
|
|
260
|
+
return FAKE_SECTION_INDEX;
|
|
261
|
+
},
|
|
262
|
+
}));
|
|
263
|
+
|
|
264
|
+
mock.module("../section-needle.js", () => ({
|
|
265
|
+
...realSectionNeedle,
|
|
266
|
+
buildSectionNeedle: (
|
|
267
|
+
...args: Parameters<typeof realSectionNeedle.buildSectionNeedle>
|
|
268
|
+
) => {
|
|
269
|
+
if (!shadowMockActive) return realSectionNeedle.buildSectionNeedle(...args);
|
|
270
|
+
needleBuilds++;
|
|
271
|
+
return { query: () => [], bestSection: () => -1 };
|
|
272
|
+
},
|
|
273
|
+
}));
|
|
274
|
+
|
|
275
|
+
mock.module("../edge.js", () => ({
|
|
276
|
+
...realEdge,
|
|
277
|
+
buildEdgeGraph: async (
|
|
278
|
+
...args: Parameters<typeof realEdge.buildEdgeGraph>
|
|
279
|
+
) => {
|
|
280
|
+
if (!shadowMockActive) return realEdge.buildEdgeGraph(...args);
|
|
281
|
+
edgeBuilds++;
|
|
282
|
+
return { adjacency: new Map(), hubs: new Set(), slugs: new Set() };
|
|
283
|
+
},
|
|
284
|
+
}));
|
|
285
|
+
|
|
286
|
+
mock.module("../section-dense-store.js", () => ({
|
|
287
|
+
...realSectionDenseStore,
|
|
288
|
+
ensureSectionCollection: async (
|
|
289
|
+
...args: Parameters<typeof realSectionDenseStore.ensureSectionCollection>
|
|
290
|
+
) => {
|
|
291
|
+
if (!shadowMockActive) {
|
|
292
|
+
return realSectionDenseStore.ensureSectionCollection(...args);
|
|
293
|
+
}
|
|
294
|
+
ensureCollectionCalls++;
|
|
295
|
+
if (ensureCollectionThrows) throw new Error("qdrant unavailable");
|
|
296
|
+
},
|
|
297
|
+
}));
|
|
298
|
+
|
|
299
|
+
mock.module("../orchestrate.js", () => ({
|
|
300
|
+
...realOrchestrate,
|
|
301
|
+
orchestrate: (...args: Parameters<typeof realOrchestrate.orchestrate>) =>
|
|
302
|
+
shadowMockActive
|
|
303
|
+
? orchestrateSpy(...(args as unknown as []))
|
|
304
|
+
: realOrchestrate.orchestrate(...args),
|
|
305
|
+
}));
|
|
306
|
+
|
|
307
|
+
// Import AFTER mocks so the plugin binds to them.
|
|
308
|
+
const { runShadowObservation, resetShadowLanesForTests, invalidateLanes } =
|
|
309
|
+
await import("../shadow-plugin.js");
|
|
310
|
+
const { memoryV3Injector } = await import("../injector.js");
|
|
311
|
+
|
|
312
|
+
afterAll(() => {
|
|
313
|
+
shadowMockActive = false;
|
|
314
|
+
});
|
|
315
|
+
|
|
316
|
+
function readRows() {
|
|
317
|
+
return testSqlite
|
|
318
|
+
.query(
|
|
319
|
+
`SELECT slug, source, pinned FROM memory_v3_selections ORDER BY slug`,
|
|
320
|
+
)
|
|
321
|
+
.all() as Array<{ slug: string; source: SelectionSource; pinned: number }>;
|
|
322
|
+
}
|
|
323
|
+
|
|
324
|
+
beforeEach(() => {
|
|
325
|
+
shadowMockActive = true;
|
|
326
|
+
liveEnabled = false;
|
|
327
|
+
shadowEnabled = false;
|
|
328
|
+
messages = [
|
|
329
|
+
{
|
|
330
|
+
role: "user",
|
|
331
|
+
content: JSON.stringify([{ type: "text", text: "hello world" }]),
|
|
332
|
+
},
|
|
333
|
+
];
|
|
334
|
+
orchestrateSpy.mockClear();
|
|
335
|
+
sectionBuilds = 0;
|
|
336
|
+
needleBuilds = 0;
|
|
337
|
+
edgeBuilds = 0;
|
|
338
|
+
ensureCollectionCalls = 0;
|
|
339
|
+
ensureCollectionThrows = false;
|
|
340
|
+
capturedPageBody = null;
|
|
341
|
+
testDb = makeDb();
|
|
342
|
+
resetShadowLanesForTests();
|
|
343
|
+
});
|
|
344
|
+
|
|
345
|
+
/** Invoke the memory-v3 injector's `produce()` for a turn. */
|
|
346
|
+
function produce(conversationId: string, turnIndex: number) {
|
|
347
|
+
return memoryV3Injector.produce({
|
|
348
|
+
requestId: "r1",
|
|
349
|
+
conversationId,
|
|
350
|
+
turnIndex,
|
|
351
|
+
trust: {} as never,
|
|
352
|
+
});
|
|
353
|
+
}
|
|
354
|
+
|
|
355
|
+
describe("memory-v3 shadow plugin", () => {
|
|
356
|
+
test("shadow flag OFF → orchestrate not called, no DB writes", async () => {
|
|
357
|
+
shadowEnabled = false;
|
|
358
|
+
await runShadowObservation("conv-1", 0);
|
|
359
|
+
expect(orchestrateSpy).not.toHaveBeenCalled();
|
|
360
|
+
expect(sectionBuilds).toBe(0);
|
|
361
|
+
expect(readRows()).toHaveLength(0);
|
|
362
|
+
});
|
|
363
|
+
|
|
364
|
+
test("shadow flag ON → orchestrate runs and rows are written with per-lane sources", async () => {
|
|
365
|
+
shadowEnabled = true;
|
|
366
|
+
await runShadowObservation("conv-1", 2);
|
|
367
|
+
|
|
368
|
+
expect(orchestrateSpy).toHaveBeenCalledTimes(1);
|
|
369
|
+
const rows = readRows();
|
|
370
|
+
// Each selection is attributed to the lane that surfaced it
|
|
371
|
+
// (`result.laneBySlug`), not re-derived from section presence — so the
|
|
372
|
+
// dense-only page-2 logs "dense", not "needle".
|
|
373
|
+
expect(rows).toEqual([
|
|
374
|
+
{ slug: "carried", source: "carry-forward", pinned: 0 },
|
|
375
|
+
// page-1 was surfaced by the needle lane → "needle", pinned.
|
|
376
|
+
{ slug: "page-1", source: "needle", pinned: 1 },
|
|
377
|
+
// page-2 was surfaced by the dense lane → "dense".
|
|
378
|
+
{ slug: "page-2", source: "dense", pinned: 0 },
|
|
379
|
+
// page-3 was surfaced by the edge lane → "edge".
|
|
380
|
+
{ slug: "page-3", source: "edge", pinned: 0 },
|
|
381
|
+
]);
|
|
382
|
+
});
|
|
383
|
+
|
|
384
|
+
test("the turn passed to orchestrate carries the latest user message", async () => {
|
|
385
|
+
shadowEnabled = true;
|
|
386
|
+
await runShadowObservation("conv-1", 0);
|
|
387
|
+
const turn = (
|
|
388
|
+
orchestrateSpy.mock.calls as unknown as unknown[][]
|
|
389
|
+
)[0]![0] as {
|
|
390
|
+
conversationId: string;
|
|
391
|
+
turnNumber: number;
|
|
392
|
+
currentMessage: string;
|
|
393
|
+
};
|
|
394
|
+
expect(turn.conversationId).toBe("conv-1");
|
|
395
|
+
expect(turn.turnNumber).toBe(0);
|
|
396
|
+
expect(turn.currentMessage).toBe("hello world");
|
|
397
|
+
});
|
|
398
|
+
|
|
399
|
+
test("orchestrate receives the lane deps", async () => {
|
|
400
|
+
shadowEnabled = true;
|
|
401
|
+
await runShadowObservation("conv-1", 0);
|
|
402
|
+
const deps = (
|
|
403
|
+
orchestrateSpy.mock.calls as unknown as unknown[][]
|
|
404
|
+
)[0]![1] as {
|
|
405
|
+
sectionIndex?: unknown;
|
|
406
|
+
needle?: unknown;
|
|
407
|
+
edgeGraph?: unknown;
|
|
408
|
+
workingSet?: unknown;
|
|
409
|
+
};
|
|
410
|
+
expect(deps.sectionIndex).toBeDefined();
|
|
411
|
+
expect(deps.needle).toBeDefined();
|
|
412
|
+
expect(deps.edgeGraph).toBeDefined();
|
|
413
|
+
expect(deps.workingSet).toBeDefined();
|
|
414
|
+
});
|
|
415
|
+
|
|
416
|
+
test("both flags OFF → produce returns null, no orchestrate, no writes", async () => {
|
|
417
|
+
liveEnabled = false;
|
|
418
|
+
shadowEnabled = false;
|
|
419
|
+
const block = await produce("conv-1", 0);
|
|
420
|
+
expect(block).toBeNull();
|
|
421
|
+
expect(orchestrateSpy).not.toHaveBeenCalled();
|
|
422
|
+
expect(readRows()).toHaveLength(0);
|
|
423
|
+
});
|
|
424
|
+
|
|
425
|
+
test("shadow-only (live off) → produce returns null but still logs", async () => {
|
|
426
|
+
liveEnabled = false;
|
|
427
|
+
shadowEnabled = true;
|
|
428
|
+
const block = await produce("conv-1", 0);
|
|
429
|
+
expect(block).toBeNull();
|
|
430
|
+
expect(orchestrateSpy).toHaveBeenCalledTimes(1);
|
|
431
|
+
expect(readRows().length).toBeGreaterThan(0);
|
|
432
|
+
});
|
|
433
|
+
|
|
434
|
+
test("live on → produce returns the rendered <memory> block and logs", async () => {
|
|
435
|
+
liveEnabled = true;
|
|
436
|
+
shadowEnabled = false;
|
|
437
|
+
const block = await produce("conv-1", 0);
|
|
438
|
+
expect(block).not.toBeNull();
|
|
439
|
+
expect(block!.placement).toBe("after-memory-prefix");
|
|
440
|
+
expect(block!.text.startsWith("<memory>\n")).toBe(true);
|
|
441
|
+
expect(block!.text.endsWith("\n</memory>")).toBe(true);
|
|
442
|
+
// Progressive disclosure: a slug with a matched section renders that
|
|
443
|
+
// section's text (page-1 → "x"), NOT the full page body.
|
|
444
|
+
expect(block!.text).toContain("# memory/concepts/page-1.md\nx");
|
|
445
|
+
expect(block!.text).not.toContain("body for page-1");
|
|
446
|
+
// A slug with no matched section (carry-forward) falls back to the full body.
|
|
447
|
+
expect(block!.text).toContain("body for carried");
|
|
448
|
+
// Selections are still logged in live mode.
|
|
449
|
+
expect(readRows().length).toBeGreaterThan(0);
|
|
450
|
+
});
|
|
451
|
+
|
|
452
|
+
test("live on but empty selection → produce returns null", async () => {
|
|
453
|
+
liveEnabled = true;
|
|
454
|
+
shadowEnabled = false;
|
|
455
|
+
orchestrateSpy.mockImplementationOnce(async () => ({
|
|
456
|
+
currentSelections: [],
|
|
457
|
+
workingSetUnion: new Set<string>(),
|
|
458
|
+
finalInjection: [],
|
|
459
|
+
sectionBySlug: new Map(),
|
|
460
|
+
laneBySlug: new Map(),
|
|
461
|
+
}));
|
|
462
|
+
const block = await produce("conv-1", 0);
|
|
463
|
+
expect(block).toBeNull();
|
|
464
|
+
expect(orchestrateSpy).toHaveBeenCalledTimes(1);
|
|
465
|
+
expect(readRows()).toHaveLength(0);
|
|
466
|
+
});
|
|
467
|
+
|
|
468
|
+
test("lazy-init runs the lane builders only once across turns", async () => {
|
|
469
|
+
shadowEnabled = true;
|
|
470
|
+
await runShadowObservation("conv-1", 0);
|
|
471
|
+
await runShadowObservation("conv-1", 1);
|
|
472
|
+
await runShadowObservation("conv-1", 2);
|
|
473
|
+
expect(sectionBuilds).toBe(1);
|
|
474
|
+
expect(needleBuilds).toBe(1);
|
|
475
|
+
expect(edgeBuilds).toBe(1);
|
|
476
|
+
expect(ensureCollectionCalls).toBe(1);
|
|
477
|
+
expect(orchestrateSpy).toHaveBeenCalledTimes(3);
|
|
478
|
+
});
|
|
479
|
+
|
|
480
|
+
test("a Qdrant section-collection failure does not disable the in-memory lanes", async () => {
|
|
481
|
+
shadowEnabled = true;
|
|
482
|
+
ensureCollectionThrows = true;
|
|
483
|
+
// initLanes still builds the needle + edge lanes and orchestrate runs — a
|
|
484
|
+
// Qdrant outage degrades only the dense lane, it does not take down all of v3
|
|
485
|
+
// (and does not poison the memoized lanes by rejecting init).
|
|
486
|
+
await runShadowObservation("conv-1", 0);
|
|
487
|
+
expect(needleBuilds).toBe(1);
|
|
488
|
+
expect(edgeBuilds).toBe(1);
|
|
489
|
+
expect(ensureCollectionCalls).toBe(1);
|
|
490
|
+
expect(orchestrateSpy).toHaveBeenCalledTimes(1);
|
|
491
|
+
});
|
|
492
|
+
|
|
493
|
+
test("invalidateLanes forces a one-time rebuild on the next turn", async () => {
|
|
494
|
+
shadowEnabled = true;
|
|
495
|
+
await runShadowObservation("conv-1", 0);
|
|
496
|
+
await runShadowObservation("conv-1", 1);
|
|
497
|
+
expect(sectionBuilds).toBe(1);
|
|
498
|
+
expect(needleBuilds).toBe(1);
|
|
499
|
+
|
|
500
|
+
invalidateLanes();
|
|
501
|
+
|
|
502
|
+
await runShadowObservation("conv-1", 2);
|
|
503
|
+
expect(sectionBuilds).toBe(2);
|
|
504
|
+
expect(needleBuilds).toBe(2);
|
|
505
|
+
expect(edgeBuilds).toBe(2);
|
|
506
|
+
|
|
507
|
+
// ...and the rebuild is memoized again — no further builds until the next
|
|
508
|
+
// invalidation.
|
|
509
|
+
await runShadowObservation("conv-1", 3);
|
|
510
|
+
expect(sectionBuilds).toBe(2);
|
|
511
|
+
expect(needleBuilds).toBe(2);
|
|
512
|
+
});
|
|
513
|
+
|
|
514
|
+
test("resetShadowLanesForTests invalidates like invalidateLanes", async () => {
|
|
515
|
+
shadowEnabled = true;
|
|
516
|
+
await runShadowObservation("conv-1", 0);
|
|
517
|
+
expect(sectionBuilds).toBe(1);
|
|
518
|
+
|
|
519
|
+
resetShadowLanesForTests();
|
|
520
|
+
|
|
521
|
+
await runShadowObservation("conv-1", 1);
|
|
522
|
+
expect(sectionBuilds).toBe(2);
|
|
523
|
+
});
|
|
524
|
+
|
|
525
|
+
test("concurrent first turns after invalidation share a single build", async () => {
|
|
526
|
+
shadowEnabled = true;
|
|
527
|
+
await runShadowObservation("conv-1", 0);
|
|
528
|
+
expect(sectionBuilds).toBe(1);
|
|
529
|
+
|
|
530
|
+
invalidateLanes();
|
|
531
|
+
|
|
532
|
+
await Promise.all([
|
|
533
|
+
runShadowObservation("conv-1", 1),
|
|
534
|
+
runShadowObservation("conv-1", 2),
|
|
535
|
+
]);
|
|
536
|
+
expect(sectionBuilds).toBe(2);
|
|
537
|
+
expect(needleBuilds).toBe(2);
|
|
538
|
+
});
|
|
539
|
+
|
|
540
|
+
test("no user message → no orchestrate, no writes", async () => {
|
|
541
|
+
shadowEnabled = true;
|
|
542
|
+
messages = [
|
|
543
|
+
{
|
|
544
|
+
role: "assistant",
|
|
545
|
+
content: JSON.stringify([{ type: "text", text: "hi" }]),
|
|
546
|
+
},
|
|
547
|
+
];
|
|
548
|
+
await runShadowObservation("conv-1", 0);
|
|
549
|
+
expect(orchestrateSpy).not.toHaveBeenCalled();
|
|
550
|
+
expect(readRows()).toHaveLength(0);
|
|
551
|
+
});
|
|
552
|
+
|
|
553
|
+
describe("initLanes feeds synthetic capability pages into the section index", () => {
|
|
554
|
+
test("the pageBody resolver returns capability content for a synthetic slug and disk body otherwise", async () => {
|
|
555
|
+
shadowEnabled = true;
|
|
556
|
+
await runShadowObservation("conv-1", 0);
|
|
557
|
+
|
|
558
|
+
// `initLanes` ran the real pageBody-building closure and handed it to
|
|
559
|
+
// `buildSectionIndex`; the stub captured it.
|
|
560
|
+
expect(capturedPageBody).not.toBeNull();
|
|
561
|
+
const pageBody = capturedPageBody!;
|
|
562
|
+
|
|
563
|
+
// A capability slug resolves to its rendered capability content (NOT an
|
|
564
|
+
// on-disk read, which would miss), so the needle can rank it.
|
|
565
|
+
const capBody = await pageBody(CAPABILITY_SLUG);
|
|
566
|
+
expect(capBody).toContain("kumquat");
|
|
567
|
+
// On-disk slugs still resolve to their page body.
|
|
568
|
+
expect(await pageBody("page-1")).toBe("body for page-1");
|
|
569
|
+
});
|
|
570
|
+
|
|
571
|
+
test("a synthetic slug's capability content yields a section the needle ranks", async () => {
|
|
572
|
+
shadowEnabled = true;
|
|
573
|
+
await runShadowObservation("conv-1", 0);
|
|
574
|
+
const pageBody = capturedPageBody!;
|
|
575
|
+
|
|
576
|
+
// Feed the captured resolver through the REAL section builder + needle:
|
|
577
|
+
// the capability slug yields ≥1 section and is ranked on a term from its
|
|
578
|
+
// capability content — the path that makes synthetic pages lane-rankable.
|
|
579
|
+
const index = await realSections.buildSectionIndex(
|
|
580
|
+
[CAPABILITY_SLUG],
|
|
581
|
+
pageBody,
|
|
582
|
+
);
|
|
583
|
+
expect(index.byArticle.get(CAPABILITY_SLUG)?.length ?? 0).toBeGreaterThan(
|
|
584
|
+
0,
|
|
585
|
+
);
|
|
586
|
+
|
|
587
|
+
const needle = realSectionNeedle.buildSectionNeedle(index);
|
|
588
|
+
const hits = needle.query("kumquat", 5);
|
|
589
|
+
expect(hits.map((h) => h.article)).toContain(CAPABILITY_SLUG);
|
|
590
|
+
});
|
|
591
|
+
});
|
|
592
|
+
});
|
|
@@ -1,35 +1,15 @@
|
|
|
1
1
|
import { expect, test } from "bun:test";
|
|
2
2
|
|
|
3
3
|
import type {
|
|
4
|
-
|
|
5
|
-
LeafNode,
|
|
6
|
-
LeafPath,
|
|
7
|
-
LeafTree,
|
|
4
|
+
MemoryRoutingTurn,
|
|
8
5
|
SelectionSource,
|
|
9
6
|
Slug,
|
|
10
|
-
TurnContext,
|
|
11
7
|
WorkingSetEntry,
|
|
12
8
|
} from "../types.js";
|
|
13
9
|
|
|
14
10
|
test("v3 core types instantiate", () => {
|
|
15
|
-
const path: LeafPath = "domain/topic/leaf";
|
|
16
11
|
const slug: Slug = "page-123";
|
|
17
12
|
|
|
18
|
-
const frontmatter: LeafFrontmatter = { path, in_core: true };
|
|
19
|
-
|
|
20
|
-
const node: LeafNode = {
|
|
21
|
-
path,
|
|
22
|
-
frontmatter,
|
|
23
|
-
description: "an example leaf node",
|
|
24
|
-
members: [slug],
|
|
25
|
-
domain: "example",
|
|
26
|
-
};
|
|
27
|
-
|
|
28
|
-
const tree: LeafTree = {
|
|
29
|
-
leaves: new Map<LeafPath, LeafNode>([[path, node]]),
|
|
30
|
-
byPage: new Map<Slug, LeafPath[]>([[slug, [path]]]),
|
|
31
|
-
};
|
|
32
|
-
|
|
33
13
|
const entry: WorkingSetEntry = {
|
|
34
14
|
slug,
|
|
35
15
|
selectedAtTurn: 1,
|
|
@@ -37,7 +17,7 @@ test("v3 core types instantiate", () => {
|
|
|
37
17
|
lastSeenTurn: 2,
|
|
38
18
|
};
|
|
39
19
|
|
|
40
|
-
const turnContext:
|
|
20
|
+
const turnContext: MemoryRoutingTurn = {
|
|
41
21
|
conversationId: "conv-xyz",
|
|
42
22
|
turnNumber: 3,
|
|
43
23
|
currentMessage: "hello",
|
|
@@ -46,8 +26,6 @@ test("v3 core types instantiate", () => {
|
|
|
46
26
|
|
|
47
27
|
const source: SelectionSource = "carry-forward";
|
|
48
28
|
|
|
49
|
-
expect(tree.leaves.get(path)).toBe(node);
|
|
50
|
-
expect(tree.byPage.get(slug)).toEqual([path]);
|
|
51
29
|
expect(entry.slug).toBe(slug);
|
|
52
30
|
expect(turnContext.turnNumber).toBe(3);
|
|
53
31
|
expect(source).toBe("carry-forward");
|