@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
package/src/{memory/v3 → plugins/defaults/memory-v3-shadow}/__tests__/selection-log-store.test.ts
RENAMED
|
@@ -20,14 +20,14 @@ import { afterAll, beforeEach, describe, expect, mock, test } from "bun:test";
|
|
|
20
20
|
|
|
21
21
|
import { drizzle } from "drizzle-orm/bun-sqlite";
|
|
22
22
|
|
|
23
|
-
import { migrateAddMemoryV3Selections } from "
|
|
24
|
-
import * as schema from "
|
|
23
|
+
import { migrateAddMemoryV3Selections } from "../../../../memory/migrations/268-add-memory-v3-selections.js";
|
|
24
|
+
import * as schema from "../../../../memory/schema.js";
|
|
25
25
|
|
|
26
26
|
const realFlags = {
|
|
27
|
-
...(await import("
|
|
27
|
+
...(await import("../../../../config/assistant-feature-flags.js")),
|
|
28
28
|
};
|
|
29
|
-
const realLoader = { ...(await import("
|
|
30
|
-
const realDb = { ...(await import("
|
|
29
|
+
const realLoader = { ...(await import("../../../../config/loader.js")) };
|
|
30
|
+
const realDb = { ...(await import("../../../../memory/db-connection.js")) };
|
|
31
31
|
const realPageContent = { ...(await import("../page-content.js")) };
|
|
32
32
|
|
|
33
33
|
let storeMockActive = false;
|
|
@@ -64,7 +64,7 @@ function seed(
|
|
|
64
64
|
}
|
|
65
65
|
}
|
|
66
66
|
|
|
67
|
-
mock.module("
|
|
67
|
+
mock.module("../../../../config/assistant-feature-flags.js", () => ({
|
|
68
68
|
...realFlags,
|
|
69
69
|
isAssistantFeatureFlagEnabled: (key: string, config: unknown) =>
|
|
70
70
|
storeMockActive
|
|
@@ -81,12 +81,12 @@ mock.module("../../../config/assistant-feature-flags.js", () => ({
|
|
|
81
81
|
),
|
|
82
82
|
}));
|
|
83
83
|
|
|
84
|
-
mock.module("
|
|
84
|
+
mock.module("../../../../config/loader.js", () => ({
|
|
85
85
|
...realLoader,
|
|
86
86
|
getConfig: () => (storeMockActive ? {} : realLoader.getConfig()),
|
|
87
87
|
}));
|
|
88
88
|
|
|
89
|
-
mock.module("
|
|
89
|
+
mock.module("../../../../memory/db-connection.js", () => ({
|
|
90
90
|
...realDb,
|
|
91
91
|
getDb: () => (storeMockActive ? testDb : realDb.getDb()),
|
|
92
92
|
getSqliteFrom: (db: unknown) =>
|
|
@@ -97,13 +97,16 @@ mock.module("../../db-connection.js", () => ({
|
|
|
97
97
|
|
|
98
98
|
mock.module("../page-content.js", () => ({
|
|
99
99
|
...realPageContent,
|
|
100
|
-
|
|
100
|
+
// The inspector store renders via `renderV3SectionContent` with an empty
|
|
101
|
+
// section map (sections are unavailable post-orchestration), so it falls back
|
|
102
|
+
// to the full page. The mock stands in for that full-page render.
|
|
103
|
+
renderV3SectionContent: async (slug: string) =>
|
|
101
104
|
storeMockActive
|
|
102
105
|
? `body for ${slug}`
|
|
103
|
-
: realPageContent.
|
|
106
|
+
: realPageContent.renderV3SectionContent(slug, undefined),
|
|
104
107
|
}));
|
|
105
108
|
|
|
106
|
-
const { getMemoryV3SelectionForInspector } =
|
|
109
|
+
const { getMemoryV3SelectionForInspector, summarizeSelections } =
|
|
107
110
|
await import("../selection-log-store.js");
|
|
108
111
|
|
|
109
112
|
beforeEach(() => {
|
|
@@ -119,7 +122,7 @@ afterAll(() => {
|
|
|
119
122
|
|
|
120
123
|
describe("getMemoryV3SelectionForInspector", () => {
|
|
121
124
|
test("returns null for a null/undefined turn", async () => {
|
|
122
|
-
seed("conv-x", 3, [{ slug: "domain-a/page-1", source: "
|
|
125
|
+
seed("conv-x", 3, [{ slug: "domain-a/page-1", source: "needle" }]);
|
|
123
126
|
expect(await getMemoryV3SelectionForInspector("conv-x", null)).toBeNull();
|
|
124
127
|
expect(
|
|
125
128
|
await getMemoryV3SelectionForInspector("conv-x", undefined),
|
|
@@ -127,8 +130,8 @@ describe("getMemoryV3SelectionForInspector", () => {
|
|
|
127
130
|
});
|
|
128
131
|
|
|
129
132
|
test("returns the selection for the exact turn", async () => {
|
|
130
|
-
seed("conv-2", 2, [{ slug: "domain-a/page-1", source: "
|
|
131
|
-
seed("conv-2", 7, [{ slug: "domain-b/page-9", source: "
|
|
133
|
+
seed("conv-2", 2, [{ slug: "domain-a/page-1", source: "needle" }]);
|
|
134
|
+
seed("conv-2", 7, [{ slug: "domain-b/page-9", source: "needle" }]);
|
|
132
135
|
|
|
133
136
|
const log = await getMemoryV3SelectionForInspector("conv-2", 2);
|
|
134
137
|
expect(log?.turn).toBe(2);
|
|
@@ -136,26 +139,26 @@ describe("getMemoryV3SelectionForInspector", () => {
|
|
|
136
139
|
});
|
|
137
140
|
|
|
138
141
|
test("returns null when the turn has no v3 rows", async () => {
|
|
139
|
-
seed("conv-1", 3, [{ slug: "domain-a/page-1", source: "
|
|
142
|
+
seed("conv-1", 3, [{ slug: "domain-a/page-1", source: "needle" }]);
|
|
140
143
|
expect(await getMemoryV3SelectionForInspector("conv-1", 4)).toBeNull();
|
|
141
144
|
});
|
|
142
145
|
|
|
143
146
|
test("does NOT fall back to another turn for an unmatched lookup", async () => {
|
|
144
147
|
// Turn 5 has rows, but inspecting turn 3 (no rows) must return null —
|
|
145
148
|
// never turn 5's selection, which would misattribute it to turn 3.
|
|
146
|
-
seed("conv-3", 5, [{ slug: "domain-a/page-1", source: "
|
|
149
|
+
seed("conv-3", 5, [{ slug: "domain-a/page-1", source: "needle" }]);
|
|
147
150
|
expect(await getMemoryV3SelectionForInspector("conv-3", 3)).toBeNull();
|
|
148
151
|
});
|
|
149
152
|
|
|
150
153
|
test("maps source/pinned and renders the <memory> block", async () => {
|
|
151
154
|
seed("conv-4", 1, [
|
|
152
|
-
{ slug: "domain-a/page-1", source: "
|
|
155
|
+
{ slug: "domain-a/page-1", source: "edge", pinned: true },
|
|
153
156
|
{ slug: "domain-b/page-2", source: "carry-forward", pinned: false },
|
|
154
157
|
]);
|
|
155
158
|
|
|
156
159
|
const log = await getMemoryV3SelectionForInspector("conv-4", 1);
|
|
157
160
|
expect(log?.selections).toEqual([
|
|
158
|
-
{ slug: "domain-a/page-1", source: "
|
|
161
|
+
{ slug: "domain-a/page-1", source: "edge", pinned: true },
|
|
159
162
|
{ slug: "domain-b/page-2", source: "carry-forward", pinned: false },
|
|
160
163
|
]);
|
|
161
164
|
expect(log?.injectedText).toContain("<memory>");
|
|
@@ -164,7 +167,7 @@ describe("getMemoryV3SelectionForInspector", () => {
|
|
|
164
167
|
});
|
|
165
168
|
|
|
166
169
|
test("live/shadow reflect the flag resolver", async () => {
|
|
167
|
-
seed("conv-5", 1, [{ slug: "domain-a/page-1", source: "
|
|
170
|
+
seed("conv-5", 1, [{ slug: "domain-a/page-1", source: "needle" }]);
|
|
168
171
|
|
|
169
172
|
const off = await getMemoryV3SelectionForInspector("conv-5", 1);
|
|
170
173
|
expect(off?.live).toBe(false);
|
|
@@ -177,3 +180,55 @@ describe("getMemoryV3SelectionForInspector", () => {
|
|
|
177
180
|
expect(on?.shadow).toBe(true);
|
|
178
181
|
});
|
|
179
182
|
});
|
|
183
|
+
|
|
184
|
+
describe("summarizeSelections", () => {
|
|
185
|
+
test("aggregates per-source counts, turn count, and distinct slugs", () => {
|
|
186
|
+
// Turn 1: a needle + an edge selection.
|
|
187
|
+
seed("conv-a", 1, [
|
|
188
|
+
{ slug: "domain-a/page-1", source: "needle" },
|
|
189
|
+
{ slug: "domain-b/page-2", source: "edge" },
|
|
190
|
+
]);
|
|
191
|
+
// Turn 2: page-1 re-selected (needle) + page-2 carried forward.
|
|
192
|
+
seed("conv-a", 2, [
|
|
193
|
+
{ slug: "domain-a/page-1", source: "needle", pinned: true },
|
|
194
|
+
{ slug: "domain-b/page-2", source: "carry-forward" },
|
|
195
|
+
]);
|
|
196
|
+
// A different conversation must not bleed into the aggregate.
|
|
197
|
+
seed("conv-b", 1, [{ slug: "domain-c/page-9", source: "dense" }]);
|
|
198
|
+
|
|
199
|
+
const summary = summarizeSelections("conv-a");
|
|
200
|
+
expect(summary.bySource).toEqual({
|
|
201
|
+
needle: 2,
|
|
202
|
+
dense: 0,
|
|
203
|
+
edge: 1,
|
|
204
|
+
"carry-forward": 1,
|
|
205
|
+
});
|
|
206
|
+
expect(summary.turns).toBe(2);
|
|
207
|
+
// page-1 and page-2 — distinct across the two turns.
|
|
208
|
+
expect(summary.distinctSlugs).toBe(2);
|
|
209
|
+
});
|
|
210
|
+
|
|
211
|
+
test("returns zeroed counts for a conversation with no rows", () => {
|
|
212
|
+
expect(summarizeSelections("conv-none")).toEqual({
|
|
213
|
+
bySource: { needle: 0, dense: 0, edge: 0, "carry-forward": 0 },
|
|
214
|
+
turns: 0,
|
|
215
|
+
distinctSlugs: 0,
|
|
216
|
+
});
|
|
217
|
+
});
|
|
218
|
+
|
|
219
|
+
test("ignores unknown/free-text historical source labels in bySource but counts the turn", () => {
|
|
220
|
+
// A pre-lane historical row with a legacy label (column is free-text).
|
|
221
|
+
seed("conv-c", 1, [
|
|
222
|
+
{ slug: "domain-a/page-1", source: "l1+l2" },
|
|
223
|
+
{ slug: "domain-a/page-2", source: "needle" },
|
|
224
|
+
]);
|
|
225
|
+
const summary = summarizeSelections("conv-c");
|
|
226
|
+
expect(summary.bySource.needle).toBe(1);
|
|
227
|
+
// The unknown label is not counted in any known bucket.
|
|
228
|
+
const total = Object.values(summary.bySource).reduce((a, b) => a + b, 0);
|
|
229
|
+
expect(total).toBe(1);
|
|
230
|
+
// But the turn and both distinct slugs are still reflected.
|
|
231
|
+
expect(summary.turns).toBe(1);
|
|
232
|
+
expect(summary.distinctSlugs).toBe(2);
|
|
233
|
+
});
|
|
234
|
+
});
|
|
@@ -0,0 +1,446 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Shadow-mode end-to-end integration test for the memory-v3 section-lane
|
|
3
|
+
* pipeline.
|
|
4
|
+
*
|
|
5
|
+
* SCOPE / ALTITUDE. A full daemon-assembly run (plugin registry → flag read →
|
|
6
|
+
* runtime assembly → provider call → DB write) is too heavy and too
|
|
7
|
+
* mock-fragile for a unit test. Instead this composes the REAL shadow units
|
|
8
|
+
* with a mocked select provider, a stubbed dense lane, an in-memory selections
|
|
9
|
+
* DB, and synthetic fixtures, driving them over a MULTI-TURN sequence:
|
|
10
|
+
*
|
|
11
|
+
* orchestrate (needle ∪ dense ∪ edge → selectPool over a shared WorkingSet
|
|
12
|
+
* → carry-forward)
|
|
13
|
+
* → attribute selections to lane sources (the shadow plugin's REAL
|
|
14
|
+
* `attributeSelections`, reading `result.laneBySlug`)
|
|
15
|
+
* → write to `memory_v3_selections` (the shadow plugin's REAL
|
|
16
|
+
* `writeSelections`)
|
|
17
|
+
* → summarizeSelections (the offline A/B readout)
|
|
18
|
+
*
|
|
19
|
+
* This is exactly the side-effect contract shadow mode observes each turn: the
|
|
20
|
+
* candidate pool is the union of the lanes — synthetic capability pages are
|
|
21
|
+
* indexed like any other page, so they enter through the needle lane rather than
|
|
22
|
+
* being always-added — a SINGLE select runs per turn, the working set carries
|
|
23
|
+
* selections forward across turns, and each selection is logged tagged with its
|
|
24
|
+
* lane source. None of this changes live injection — shadow mode is
|
|
25
|
+
* observation-only; cutover is the `memory-v3-live` flag flip.
|
|
26
|
+
*
|
|
27
|
+
* Slugs are generic placeholders (`page-a`, `topic-x`, `page-b`, …) — this is a
|
|
28
|
+
* public repo.
|
|
29
|
+
*/
|
|
30
|
+
|
|
31
|
+
import { Database } from "bun:sqlite";
|
|
32
|
+
import { afterAll, beforeEach, describe, expect, mock, test } from "bun:test";
|
|
33
|
+
|
|
34
|
+
import { drizzle } from "drizzle-orm/bun-sqlite";
|
|
35
|
+
|
|
36
|
+
import { migrateAddMemoryV3Selections } from "../../../../memory/migrations/268-add-memory-v3-selections.js";
|
|
37
|
+
import * as schema from "../../../../memory/schema.js";
|
|
38
|
+
import type { PageIndexEntry } from "../../../../memory/v2/page-index.js";
|
|
39
|
+
import type {
|
|
40
|
+
Message,
|
|
41
|
+
Provider,
|
|
42
|
+
ProviderResponse,
|
|
43
|
+
} from "../../../../providers/types.js";
|
|
44
|
+
import type { EdgeGraph } from "../edge.js";
|
|
45
|
+
import { buildEdgeGraph } from "../edge.js";
|
|
46
|
+
import type { OrchestrateResult } from "../orchestrate.js";
|
|
47
|
+
import { buildSectionNeedle } from "../section-needle.js";
|
|
48
|
+
import { buildSectionIndex } from "../sections.js";
|
|
49
|
+
import type { MemoryRoutingTurn, SectionIndex, Slug } from "../types.js";
|
|
50
|
+
|
|
51
|
+
// ---------------------------------------------------------------------------
|
|
52
|
+
// Module stubs installed BEFORE the orchestrator / store imports so they
|
|
53
|
+
// observe them at load time. Every stub DELEGATES to the real implementation
|
|
54
|
+
// unless this file's tests are running, so the process-global `mock.module`
|
|
55
|
+
// cannot leak fake behavior into sibling test files.
|
|
56
|
+
// ---------------------------------------------------------------------------
|
|
57
|
+
|
|
58
|
+
let mockActive = false;
|
|
59
|
+
|
|
60
|
+
let providerStub: Provider | null = null;
|
|
61
|
+
|
|
62
|
+
// Spread the real provider module so unrelated exports (e.g. `createTimeout`,
|
|
63
|
+
// pulled in transitively via the selection-log-store render path) stay present;
|
|
64
|
+
// override only the two entry points the select pool uses, and only while this
|
|
65
|
+
// file is active so the stub cannot leak into sibling tests.
|
|
66
|
+
const realProvider = {
|
|
67
|
+
...(await import("../../../../providers/provider-send-message.js")),
|
|
68
|
+
};
|
|
69
|
+
mock.module("../../../../providers/provider-send-message.js", () => ({
|
|
70
|
+
...realProvider,
|
|
71
|
+
getConfiguredProvider: async (
|
|
72
|
+
...args: Parameters<typeof realProvider.getConfiguredProvider>
|
|
73
|
+
) =>
|
|
74
|
+
mockActive ? providerStub : realProvider.getConfiguredProvider(...args),
|
|
75
|
+
extractToolUse: (response: ProviderResponse) =>
|
|
76
|
+
response.content.find((b) => b.type === "tool_use"),
|
|
77
|
+
}));
|
|
78
|
+
|
|
79
|
+
mock.module("../../../../util/logger.js", () => ({
|
|
80
|
+
getLogger: () =>
|
|
81
|
+
new Proxy({} as Record<string, unknown>, {
|
|
82
|
+
get: (_t, prop) => (prop === "child" ? () => ({}) : () => {}),
|
|
83
|
+
}),
|
|
84
|
+
}));
|
|
85
|
+
|
|
86
|
+
// The dense lane is stubbed to a controllable hit list; it DELEGATES to the
|
|
87
|
+
// real `denseLane` unless this file is running so dense.test.ts still exercises
|
|
88
|
+
// the real lane.
|
|
89
|
+
const realDense = { ...(await import("../dense.js")) };
|
|
90
|
+
let denseHits: Array<{ article: Slug; section: number }> = [];
|
|
91
|
+
mock.module("../dense.js", () => ({
|
|
92
|
+
...realDense,
|
|
93
|
+
denseLane: async (...args: Parameters<typeof realDense.denseLane>) =>
|
|
94
|
+
mockActive ? denseHits : realDense.denseLane(...args),
|
|
95
|
+
}));
|
|
96
|
+
|
|
97
|
+
// In-memory selections DB. `summarizeSelections` reads via getDb/getSqliteFrom;
|
|
98
|
+
// the writer below writes through the same handles.
|
|
99
|
+
const realDb = { ...(await import("../../../../memory/db-connection.js")) };
|
|
100
|
+
let testSqlite: Database;
|
|
101
|
+
let testDb = makeDb();
|
|
102
|
+
function makeDb() {
|
|
103
|
+
testSqlite = new Database(":memory:");
|
|
104
|
+
testSqlite.exec("PRAGMA journal_mode=WAL");
|
|
105
|
+
const db = drizzle(testSqlite, { schema });
|
|
106
|
+
migrateAddMemoryV3Selections(db);
|
|
107
|
+
return db;
|
|
108
|
+
}
|
|
109
|
+
mock.module("../../../../memory/db-connection.js", () => ({
|
|
110
|
+
...realDb,
|
|
111
|
+
getDb: () => (mockActive ? testDb : realDb.getDb()),
|
|
112
|
+
getSqliteFrom: (db: unknown) =>
|
|
113
|
+
mockActive
|
|
114
|
+
? testSqlite
|
|
115
|
+
: realDb.getSqliteFrom(db as Parameters<typeof realDb.getSqliteFrom>[0]),
|
|
116
|
+
}));
|
|
117
|
+
|
|
118
|
+
const { orchestrate } = await import("../orchestrate.js");
|
|
119
|
+
const { WorkingSet } = await import("../working-set.js");
|
|
120
|
+
const { summarizeSelections } = await import("../selection-log-store.js");
|
|
121
|
+
// The REAL attribution + writer from the shadow plugin, imported AFTER the
|
|
122
|
+
// db-connection mock so `writeSelections` binds to the in-memory test DB. Using
|
|
123
|
+
// the production code (rather than a local copy) means this test exercises the
|
|
124
|
+
// real `result.laneBySlug` attribution — the only thing that can emit "dense".
|
|
125
|
+
const { attributeSelections, writeSelections } =
|
|
126
|
+
await import("../shadow-plugin.js");
|
|
127
|
+
|
|
128
|
+
// ---------------------------------------------------------------------------
|
|
129
|
+
// Fixtures: a tiny corpus. `page-a` carries a curated link to `topic-x` so the
|
|
130
|
+
// edge lane surfaces it; each page has a distinctive section term so a query
|
|
131
|
+
// selects exactly that page via the needle.
|
|
132
|
+
// ---------------------------------------------------------------------------
|
|
133
|
+
|
|
134
|
+
const PAGES: Record<Slug, string> = {
|
|
135
|
+
"page-a": "lead a\n## Body\napple content for page a",
|
|
136
|
+
"page-b": "lead b\n## Body\nbanana content for page b",
|
|
137
|
+
"topic-x": "lead x\n## Body\ncherry content for topic x",
|
|
138
|
+
};
|
|
139
|
+
|
|
140
|
+
const RAW: Record<Slug, string> = {
|
|
141
|
+
"page-a": `---\nlinks:\n - "topic-x — the curated edge from a to x"\n---\n${PAGES["page-a"]}`,
|
|
142
|
+
"page-b": `---\nedges: []\n---\n${PAGES["page-b"]}`,
|
|
143
|
+
"topic-x": `---\nedges: []\n---\n${PAGES["topic-x"]}`,
|
|
144
|
+
};
|
|
145
|
+
|
|
146
|
+
const SLUGS = Object.keys(PAGES);
|
|
147
|
+
|
|
148
|
+
// A synthetic capability page (skill). Its content carries a distinctive term
|
|
149
|
+
// ("durian") so the real needle ranks it — synthetic pages are indexed and
|
|
150
|
+
// lane-ranked like any other page, not always-added to the pool.
|
|
151
|
+
const CAPABILITY_SLUG = "skills/example";
|
|
152
|
+
const CAPABILITY_CONTENT =
|
|
153
|
+
"# Skill: example\ndurian content for the example skill";
|
|
154
|
+
const INDEX_SLUGS = [...SLUGS, CAPABILITY_SLUG];
|
|
155
|
+
|
|
156
|
+
/** Page-body resolver mirroring `initLanes`: capability slug → its rendered
|
|
157
|
+
* content, on-disk slug → its page body. */
|
|
158
|
+
function bodyOf(slug: Slug): string {
|
|
159
|
+
return slug === CAPABILITY_SLUG ? CAPABILITY_CONTENT : PAGES[slug]!;
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
function makeEntries(): PageIndexEntry[] {
|
|
163
|
+
return INDEX_SLUGS.map((slug, i) => ({
|
|
164
|
+
id: i + 1,
|
|
165
|
+
slug,
|
|
166
|
+
summary: `summary of ${slug}`,
|
|
167
|
+
edges: [],
|
|
168
|
+
leaves: [],
|
|
169
|
+
modifiedAt: 0,
|
|
170
|
+
}));
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
async function buildLanes(): Promise<{
|
|
174
|
+
sectionIndex: SectionIndex;
|
|
175
|
+
needle: ReturnType<typeof buildSectionNeedle>;
|
|
176
|
+
edgeGraph: EdgeGraph;
|
|
177
|
+
}> {
|
|
178
|
+
const sectionIndex = await buildSectionIndex(INDEX_SLUGS, async (s) =>
|
|
179
|
+
bodyOf(s),
|
|
180
|
+
);
|
|
181
|
+
const needle = buildSectionNeedle(sectionIndex);
|
|
182
|
+
// The capability slug has no `links:` frontmatter, so the edge graph reads
|
|
183
|
+
// through `RAW` for the on-disk pages and its raw content otherwise.
|
|
184
|
+
const edgeGraph = await buildEdgeGraph(makeEntries(), async (s) =>
|
|
185
|
+
s === CAPABILITY_SLUG ? CAPABILITY_CONTENT : RAW[s]!,
|
|
186
|
+
);
|
|
187
|
+
return { sectionIndex, needle, edgeGraph };
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
const config = {} as never;
|
|
191
|
+
const CONV = "conv-xyz";
|
|
192
|
+
|
|
193
|
+
function makeTurn(
|
|
194
|
+
turnNumber: number,
|
|
195
|
+
currentMessage: string,
|
|
196
|
+
): MemoryRoutingTurn {
|
|
197
|
+
return {
|
|
198
|
+
conversationId: CONV,
|
|
199
|
+
turnNumber,
|
|
200
|
+
currentMessage,
|
|
201
|
+
recentContext: "prior context",
|
|
202
|
+
};
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
function toolUseResponse(input: Record<string, unknown>): ProviderResponse {
|
|
206
|
+
return {
|
|
207
|
+
model: "stub-model",
|
|
208
|
+
stopReason: "tool_use",
|
|
209
|
+
usage: { inputTokens: 0, outputTokens: 0 },
|
|
210
|
+
content: [{ type: "tool_use", id: "tu-1", name: "select_pages", input }],
|
|
211
|
+
};
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
/** Parse the numbered `<candidates>` block back into an ordered slug list. */
|
|
215
|
+
function candidateSlugs(messages: Message[]): Slug[] {
|
|
216
|
+
for (const msg of messages) {
|
|
217
|
+
for (const block of msg.content) {
|
|
218
|
+
if (block.type !== "text") continue;
|
|
219
|
+
const m = /<candidates>\n([\s\S]*?)\n<\/candidates>/.exec(block.text);
|
|
220
|
+
if (!m) continue;
|
|
221
|
+
return m[1]
|
|
222
|
+
.split("\n")
|
|
223
|
+
.map((line) => /^\[\d+\] (\S+) —/.exec(line)?.[1])
|
|
224
|
+
.filter((s): s is string => !!s);
|
|
225
|
+
}
|
|
226
|
+
}
|
|
227
|
+
return [];
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
/**
|
|
231
|
+
* Provider that selects the pooled candidates in `keep` (mapping each back to
|
|
232
|
+
* its 1-based id), pinning those in `pin`. Records the rendered pool and counts
|
|
233
|
+
* the select calls so the test can assert one select per turn over the union.
|
|
234
|
+
*/
|
|
235
|
+
let lastPool: Slug[] = [];
|
|
236
|
+
let selectCalls = 0;
|
|
237
|
+
function selectProvider(keep: Slug[], pin: Slug[] = []): Provider {
|
|
238
|
+
return {
|
|
239
|
+
name: "stub",
|
|
240
|
+
sendMessage: async (messages) => {
|
|
241
|
+
selectCalls++;
|
|
242
|
+
const pool = candidateSlugs(messages);
|
|
243
|
+
lastPool = pool;
|
|
244
|
+
const ids: number[] = [];
|
|
245
|
+
const pinned_ids: number[] = [];
|
|
246
|
+
pool.forEach((slug, i) => {
|
|
247
|
+
if (keep.includes(slug)) ids.push(i + 1);
|
|
248
|
+
if (pin.includes(slug)) pinned_ids.push(i + 1);
|
|
249
|
+
});
|
|
250
|
+
return toolUseResponse({ ids, pinned_ids });
|
|
251
|
+
},
|
|
252
|
+
};
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
// ---------------------------------------------------------------------------
|
|
256
|
+
// Selection read-back. Attribution (`attributeSelections`) and the write
|
|
257
|
+
// (`writeSelections`) are the shadow plugin's REAL functions, imported above —
|
|
258
|
+
// so this test exercises the production `result.laneBySlug` attribution rather
|
|
259
|
+
// than a local copy. The db-connection mock routes `writeSelections` at the
|
|
260
|
+
// in-memory test DB.
|
|
261
|
+
// ---------------------------------------------------------------------------
|
|
262
|
+
|
|
263
|
+
/** Read back every logged selection source for a turn, in row order. */
|
|
264
|
+
function loggedSources(turn: number): Array<{ slug: Slug; source: string }> {
|
|
265
|
+
return testSqlite
|
|
266
|
+
.query(
|
|
267
|
+
`SELECT slug, source FROM memory_v3_selections
|
|
268
|
+
WHERE conversation_id = ? AND turn = ? ORDER BY rowid`,
|
|
269
|
+
)
|
|
270
|
+
.all(CONV, turn) as Array<{ slug: Slug; source: string }>;
|
|
271
|
+
}
|
|
272
|
+
|
|
273
|
+
/** Drive one turn end-to-end: orchestrate → attribute → write. */
|
|
274
|
+
async function runTurn(
|
|
275
|
+
turnNumber: number,
|
|
276
|
+
query: string,
|
|
277
|
+
keep: Slug[],
|
|
278
|
+
pin: Slug[],
|
|
279
|
+
deps: {
|
|
280
|
+
lanes: Awaited<ReturnType<typeof buildLanes>>;
|
|
281
|
+
workingSet: InstanceType<typeof WorkingSet>;
|
|
282
|
+
},
|
|
283
|
+
): Promise<OrchestrateResult> {
|
|
284
|
+
providerStub = selectProvider(keep, pin);
|
|
285
|
+
const result = await orchestrate(makeTurn(turnNumber, query), {
|
|
286
|
+
sectionIndex: deps.lanes.sectionIndex,
|
|
287
|
+
needle: deps.lanes.needle,
|
|
288
|
+
denseConfig: config,
|
|
289
|
+
edgeGraph: deps.lanes.edgeGraph,
|
|
290
|
+
workingSet: deps.workingSet,
|
|
291
|
+
});
|
|
292
|
+
writeSelections(CONV, turnNumber, attributeSelections(result));
|
|
293
|
+
return result;
|
|
294
|
+
}
|
|
295
|
+
|
|
296
|
+
beforeEach(() => {
|
|
297
|
+
mockActive = true;
|
|
298
|
+
providerStub = null;
|
|
299
|
+
denseHits = [];
|
|
300
|
+
lastPool = [];
|
|
301
|
+
selectCalls = 0;
|
|
302
|
+
testDb = makeDb();
|
|
303
|
+
});
|
|
304
|
+
|
|
305
|
+
afterAll(() => {
|
|
306
|
+
mockActive = false;
|
|
307
|
+
});
|
|
308
|
+
|
|
309
|
+
// ---------------------------------------------------------------------------
|
|
310
|
+
// Pool composition: the candidate pool is the union of the lanes, and exactly
|
|
311
|
+
// one select runs per turn. Synthetic capability pages are indexed like any
|
|
312
|
+
// other page, so they enter through the needle lane when the query matches
|
|
313
|
+
// their content — not by always being appended.
|
|
314
|
+
// ---------------------------------------------------------------------------
|
|
315
|
+
|
|
316
|
+
describe("memory-v3 shadow integration — candidate pool", () => {
|
|
317
|
+
test("pool unions needle ∪ dense ∪ edge; one select per turn", async () => {
|
|
318
|
+
const lanes = await buildLanes();
|
|
319
|
+
// "apple" hits page-a (needle). Dense returns page-b. page-a links to
|
|
320
|
+
// topic-x (edge). "apple" does NOT match the capability page, so it is not
|
|
321
|
+
// pooled this turn — capability pages are lane-ranked, not always-added.
|
|
322
|
+
denseHits = [{ article: "page-b", section: 0 }];
|
|
323
|
+
await runTurn(1, "apple", [], [], { lanes, workingSet: new WorkingSet() });
|
|
324
|
+
|
|
325
|
+
expect(selectCalls).toBe(1);
|
|
326
|
+
expect(new Set(lastPool)).toEqual(new Set(["page-a", "page-b", "topic-x"]));
|
|
327
|
+
});
|
|
328
|
+
|
|
329
|
+
test("a synthetic capability page enters the pool via the needle lane", async () => {
|
|
330
|
+
const lanes = await buildLanes();
|
|
331
|
+
// "durian" is the distinctive term in the capability page's content, so the
|
|
332
|
+
// real needle ranks it and folds it into the pool.
|
|
333
|
+
await runTurn(1, "durian", [], [], { lanes, workingSet: new WorkingSet() });
|
|
334
|
+
|
|
335
|
+
expect(selectCalls).toBe(1);
|
|
336
|
+
expect(lastPool).toContain(CAPABILITY_SLUG);
|
|
337
|
+
});
|
|
338
|
+
});
|
|
339
|
+
|
|
340
|
+
// ---------------------------------------------------------------------------
|
|
341
|
+
// Carry-forward across turns: a page selected on turn 1 stays in turn 2's
|
|
342
|
+
// final injection without being re-selected (net-new shadow behavior), and is
|
|
343
|
+
// logged as `carry-forward` on the later turn.
|
|
344
|
+
// ---------------------------------------------------------------------------
|
|
345
|
+
|
|
346
|
+
describe("memory-v3 shadow integration — carry-forward", () => {
|
|
347
|
+
test("a turn-1 selection accumulates into turn 2's injection and logs as carry-forward", async () => {
|
|
348
|
+
const lanes = await buildLanes();
|
|
349
|
+
const workingSet = new WorkingSet();
|
|
350
|
+
|
|
351
|
+
// Turn 1 selects+pins page-a (needle-sourced).
|
|
352
|
+
const t1 = await runTurn(1, "apple", ["page-a"], ["page-a"], {
|
|
353
|
+
lanes,
|
|
354
|
+
workingSet,
|
|
355
|
+
});
|
|
356
|
+
expect(t1.currentSelections.map((s) => s.slug)).toContain("page-a");
|
|
357
|
+
expect(loggedSources(1)).toEqual([{ slug: "page-a", source: "needle" }]);
|
|
358
|
+
|
|
359
|
+
// Turn 2 selects a DIFFERENT page; page-a carries forward un-re-selected.
|
|
360
|
+
denseHits = [{ article: "topic-x", section: 0 }];
|
|
361
|
+
const t2 = await runTurn(2, "cherry", ["topic-x"], [], {
|
|
362
|
+
lanes,
|
|
363
|
+
workingSet,
|
|
364
|
+
});
|
|
365
|
+
expect(t2.currentSelections.map((s) => s.slug)).not.toContain("page-a");
|
|
366
|
+
expect(t2.finalInjection).toContain("page-a");
|
|
367
|
+
|
|
368
|
+
// The carried page is logged with the carry-forward source tag.
|
|
369
|
+
const t2Rows = loggedSources(2);
|
|
370
|
+
expect(t2Rows).toContainEqual({ slug: "topic-x", source: "needle" });
|
|
371
|
+
expect(t2Rows).toContainEqual({ slug: "page-a", source: "carry-forward" });
|
|
372
|
+
|
|
373
|
+
// Working set accumulates both pages across the two turns.
|
|
374
|
+
expect(workingSet.union()).toEqual(new Set(["page-a", "topic-x"]));
|
|
375
|
+
});
|
|
376
|
+
});
|
|
377
|
+
|
|
378
|
+
// ---------------------------------------------------------------------------
|
|
379
|
+
// Lane-source attribution: a synthetic capability page now carries a section
|
|
380
|
+
// (its rendered content), so when the needle ranks it the selection has a
|
|
381
|
+
// matched section and is attributed `needle` — exactly like a concept page.
|
|
382
|
+
// ---------------------------------------------------------------------------
|
|
383
|
+
|
|
384
|
+
describe("memory-v3 shadow integration — lane-source attribution", () => {
|
|
385
|
+
test("a needle-ranked capability selection is logged with the needle source", async () => {
|
|
386
|
+
const lanes = await buildLanes();
|
|
387
|
+
// "durian" matches the capability page's content section, so selecting it
|
|
388
|
+
// records a `sectionBySlug` entry and the coarse mapping attributes it
|
|
389
|
+
// `needle` (capabilities are indexed pages now, not sectionless add-ins).
|
|
390
|
+
const result = await runTurn(1, "durian", [CAPABILITY_SLUG], [], {
|
|
391
|
+
lanes,
|
|
392
|
+
workingSet: new WorkingSet(),
|
|
393
|
+
});
|
|
394
|
+
expect(result.currentSelections.map((s) => s.slug)).toEqual([
|
|
395
|
+
CAPABILITY_SLUG,
|
|
396
|
+
]);
|
|
397
|
+
expect(loggedSources(1)).toEqual([
|
|
398
|
+
{ slug: CAPABILITY_SLUG, source: "needle" },
|
|
399
|
+
]);
|
|
400
|
+
});
|
|
401
|
+
});
|
|
402
|
+
|
|
403
|
+
// ---------------------------------------------------------------------------
|
|
404
|
+
// A/B readout: summarizeSelections aggregates the logged shadow run by lane
|
|
405
|
+
// source, and reports turn count + distinct-slug (working-set) footprint.
|
|
406
|
+
// ---------------------------------------------------------------------------
|
|
407
|
+
|
|
408
|
+
describe("memory-v3 shadow integration — selection-log readout", () => {
|
|
409
|
+
test("summarizeSelections aggregates a multi-turn run by source", async () => {
|
|
410
|
+
const lanes = await buildLanes();
|
|
411
|
+
const workingSet = new WorkingSet();
|
|
412
|
+
|
|
413
|
+
// Turn 1: needle selects page-a (matched "apple").
|
|
414
|
+
await runTurn(1, "apple", ["page-a"], [], { lanes, workingSet });
|
|
415
|
+
// Turn 2: needle selects page-b (matched "banana"); page-a carries forward.
|
|
416
|
+
denseHits = [{ article: "page-b", section: 0 }];
|
|
417
|
+
await runTurn(2, "banana", ["page-b"], [], { lanes, workingSet });
|
|
418
|
+
// Turn 3: needle selects the capability page (matched "durian" in its
|
|
419
|
+
// content); page-a and page-b carry forward.
|
|
420
|
+
denseHits = [];
|
|
421
|
+
await runTurn(3, "durian", [CAPABILITY_SLUG], [], { lanes, workingSet });
|
|
422
|
+
|
|
423
|
+
const summary = summarizeSelections(CONV);
|
|
424
|
+
// needle: page-a (t1) + page-b (t2) + capability page (t3) = 3.
|
|
425
|
+
expect(summary.bySource.needle).toBe(3);
|
|
426
|
+
// No selection was attributed to the edge lane in this run.
|
|
427
|
+
expect(summary.bySource.edge).toBe(0);
|
|
428
|
+
// carry-forward: page-a (t2) + page-a & page-b (t3) = 3.
|
|
429
|
+
expect(summary.bySource["carry-forward"]).toBe(3);
|
|
430
|
+
// dense lane surfaced candidates but none were selected.
|
|
431
|
+
expect(summary.bySource.dense).toBe(0);
|
|
432
|
+
// Three turns logged selections.
|
|
433
|
+
expect(summary.turns).toBe(3);
|
|
434
|
+
// Distinct slugs across the run: page-a, page-b, the capability page.
|
|
435
|
+
expect(summary.distinctSlugs).toBe(3);
|
|
436
|
+
});
|
|
437
|
+
|
|
438
|
+
test("summarizeSelections reports zeros for a conversation with no rows", () => {
|
|
439
|
+
const summary = summarizeSelections("conv-empty");
|
|
440
|
+
expect(summary).toEqual({
|
|
441
|
+
bySource: { needle: 0, dense: 0, edge: 0, "carry-forward": 0 },
|
|
442
|
+
turns: 0,
|
|
443
|
+
distinctSlugs: 0,
|
|
444
|
+
});
|
|
445
|
+
});
|
|
446
|
+
});
|