@vellumai/assistant 0.8.8 → 0.8.9-dev.202606091926.ebb2d62
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 +273 -121
- package/package.json +2 -2
- package/scripts/sync-llm-catalog.ts +2 -0
- 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__/build-persisted-content.test.ts +75 -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__/config-managed-gemini-defaults.test.ts +2 -46
- 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-execution-feature-gates.test.ts +5 -4
- 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__/embedding-managed-proxy-selection.test.ts +6 -40
- 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-catalog-parity.test.ts +4 -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__/persistence-secret-redaction.test.ts +78 -0
- 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__/retry-thinking-adaptive-only.test.ts +245 -0
- package/src/__tests__/send-endpoint-busy.test.ts +4 -1
- package/src/__tests__/server-history-render.test.ts +39 -0
- 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__/tool-start-timestamp.test.ts +218 -0
- 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/events/tool-result.ts +9 -0
- package/src/api/events/tool-use-start.ts +7 -0
- package/src/api/responses/conversation-message.ts +72 -13
- 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 -56
- package/src/config/llm-resolver.ts +39 -7
- package/src/config/loader.ts +5 -22
- 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 +120 -42
- 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/shared.ts +9 -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/embedding-backend.ts +5 -22
- 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 +57 -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 +57 -7
- 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
|
@@ -1,432 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Tests for `assistant/src/memory/v3/shadow-plugin.ts`.
|
|
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`;
|
|
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 loaders only once across multiple turns.
|
|
13
|
-
*
|
|
14
|
-
* All heavy dependencies (config, flag resolver, conversation reads, v2 page
|
|
15
|
-
* index + page store, v3 loaders, orchestrate) are mocked BEFORE importing the
|
|
16
|
-
* plugin so the module observes them at load time. A real in-memory SQLite DB
|
|
17
|
-
* backs the write assertions via the `memory_v3_selections` migration.
|
|
18
|
-
*/
|
|
19
|
-
|
|
20
|
-
import { Database } from "bun:sqlite";
|
|
21
|
-
import { afterAll, beforeEach, describe, expect, mock, test } from "bun:test";
|
|
22
|
-
|
|
23
|
-
import { drizzle } from "drizzle-orm/bun-sqlite";
|
|
24
|
-
|
|
25
|
-
import { migrateAddMemoryV3Selections } from "../../migrations/268-add-memory-v3-selections.js";
|
|
26
|
-
import * as schema from "../../schema.js";
|
|
27
|
-
import type { LeafTree, SelectionSource } from "../types.js";
|
|
28
|
-
|
|
29
|
-
// `mock.module` is process-global and, in Bun, neither `mock.restore()` nor a
|
|
30
|
-
// re-mock in `afterAll` reverts it for files that load LATER in the same
|
|
31
|
-
// `bun test src/memory/v3/` run. Sibling files (orchestrate.test.ts,
|
|
32
|
-
// tree.test.ts) import these same modules for real, so an unconditional stub
|
|
33
|
-
// here would leak in and break them. Instead each stub below DELEGATES to the
|
|
34
|
-
// real implementation unless this test is actively running (`shadowMockActive`,
|
|
35
|
-
// toggled in beforeEach/afterAll).
|
|
36
|
-
//
|
|
37
|
-
// Snapshot the real exports into plain objects NOW: a module namespace object
|
|
38
|
-
// is a live view, so reading `realTree.loadLeafTree` *after* the stub is
|
|
39
|
-
// installed would resolve back to the stub (infinite recursion).
|
|
40
|
-
const realTree = { ...(await import("../tree.js")) };
|
|
41
|
-
const realCore = { ...(await import("../core.js")) };
|
|
42
|
-
const realNeedle = { ...(await import("../needle.js")) };
|
|
43
|
-
const realOrchestrate = { ...(await import("../orchestrate.js")) };
|
|
44
|
-
const realPlatform = { ...(await import("../../../util/platform.js")) };
|
|
45
|
-
const realPageStore = { ...(await import("../../v2/page-store.js")) };
|
|
46
|
-
const realConversationCrud = {
|
|
47
|
-
...(await import("../../conversation-crud.js")),
|
|
48
|
-
};
|
|
49
|
-
|
|
50
|
-
let shadowMockActive = false;
|
|
51
|
-
|
|
52
|
-
// ─── mutable test state, read by the mocks below ────────────────────────────
|
|
53
|
-
|
|
54
|
-
// Per-flag toggles. The plugin reads `memory-v3-live` and `memory-v3-shadow`
|
|
55
|
-
// independently, so the mock resolves by flag key rather than a single boolean.
|
|
56
|
-
let liveEnabled = false;
|
|
57
|
-
let shadowEnabled = false;
|
|
58
|
-
let messages: Array<{ role: string; content: string }> = [];
|
|
59
|
-
const orchestrateSpy = mock(async () => ({
|
|
60
|
-
openedLeaves: [],
|
|
61
|
-
currentSelections: [
|
|
62
|
-
{ slug: "domain-a/page-1", pinned: true },
|
|
63
|
-
{ slug: "domain-b/page-2", pinned: false },
|
|
64
|
-
],
|
|
65
|
-
workingSetUnion: new Set<string>(["domain-a/page-1", "domain-b/page-2"]),
|
|
66
|
-
finalInjection: ["domain-a/page-1", "domain-b/page-2", "domain-a/carried"],
|
|
67
|
-
}));
|
|
68
|
-
let treeLoads = 0;
|
|
69
|
-
let coreLoads = 0;
|
|
70
|
-
let needleBuilds = 0;
|
|
71
|
-
let configL2Concurrency = 16;
|
|
72
|
-
|
|
73
|
-
// Shared in-memory DB so writes are observable from the test. We hold the raw
|
|
74
|
-
// sqlite handle alongside the drizzle wrapper so the test can both read rows
|
|
75
|
-
// directly and feed the same handle through the mocked `getSqliteFrom`.
|
|
76
|
-
let testSqlite: Database;
|
|
77
|
-
let testDb = makeDb();
|
|
78
|
-
function makeDb() {
|
|
79
|
-
testSqlite = new Database(":memory:");
|
|
80
|
-
testSqlite.exec("PRAGMA journal_mode=WAL");
|
|
81
|
-
const db = drizzle(testSqlite, { schema });
|
|
82
|
-
migrateAddMemoryV3Selections(db);
|
|
83
|
-
return db;
|
|
84
|
-
}
|
|
85
|
-
|
|
86
|
-
// A tree where `domain-a/*` pages are owned by a core leaf and `domain-b/*`
|
|
87
|
-
// are not, so attribution maps to core+l2 / l1+l2 respectively.
|
|
88
|
-
const FAKE_TREE = {
|
|
89
|
-
leaves: new Map([
|
|
90
|
-
["domain-a", { members: ["domain-a/page-1", "domain-a/carried"] }],
|
|
91
|
-
["domain-b", { members: ["domain-b/page-2"] }],
|
|
92
|
-
]),
|
|
93
|
-
byPage: new Map(),
|
|
94
|
-
} as unknown as LeafTree;
|
|
95
|
-
|
|
96
|
-
// ─── module mocks (installed before the plugin import) ──────────────────────
|
|
97
|
-
|
|
98
|
-
mock.module("../../../config/assistant-feature-flags.js", () => ({
|
|
99
|
-
isAssistantFeatureFlagEnabled: (key: string) =>
|
|
100
|
-
key === "memory-v3-live"
|
|
101
|
-
? liveEnabled
|
|
102
|
-
: key === "memory-v3-shadow"
|
|
103
|
-
? shadowEnabled
|
|
104
|
-
: false,
|
|
105
|
-
}));
|
|
106
|
-
|
|
107
|
-
mock.module("../../../config/loader.js", () => ({
|
|
108
|
-
getConfig: () => ({
|
|
109
|
-
memory: {
|
|
110
|
-
v3: {
|
|
111
|
-
workingSet: { maxPages: 150, evictWindow: 5 },
|
|
112
|
-
l2Concurrency: configL2Concurrency,
|
|
113
|
-
},
|
|
114
|
-
},
|
|
115
|
-
}),
|
|
116
|
-
}));
|
|
117
|
-
|
|
118
|
-
// Spread the real module so every export the live path transitively imports
|
|
119
|
-
// (e.g. `getMessageById` via page-content.ts) stays present — replacing the
|
|
120
|
-
// whole module with a bare `{ getMessages }` made those consumers fail to load.
|
|
121
|
-
// Only `getMessages` is overridden, since that's the one the plugin reads.
|
|
122
|
-
mock.module("../../conversation-crud.js", () => ({
|
|
123
|
-
...realConversationCrud,
|
|
124
|
-
getMessages: () => messages.map((m, i) => ({ ...m, id: `m${i}` })),
|
|
125
|
-
}));
|
|
126
|
-
|
|
127
|
-
mock.module("../../db-connection.js", () => ({
|
|
128
|
-
getDb: () => testDb,
|
|
129
|
-
getSqliteFrom: () => testSqlite,
|
|
130
|
-
}));
|
|
131
|
-
|
|
132
|
-
mock.module("../v2/page-index.js", () => ({
|
|
133
|
-
getPageIndex: async () => ({ bySlug: new Map() }),
|
|
134
|
-
}));
|
|
135
|
-
|
|
136
|
-
// `pageContent` (live mode) reads the full page via `readPage`/`renderPageContent`.
|
|
137
|
-
// Stub them to return a deterministic body per slug so the rendered `<memory>`
|
|
138
|
-
// block is assertable without touching the filesystem.
|
|
139
|
-
mock.module("../../v2/page-store.js", () => ({
|
|
140
|
-
...realPageStore,
|
|
141
|
-
readPage: async (workspaceDir: string, slug: string) =>
|
|
142
|
-
shadowMockActive
|
|
143
|
-
? { slug, frontmatter: {}, body: `body for ${slug}` }
|
|
144
|
-
: realPageStore.readPage(workspaceDir, slug),
|
|
145
|
-
renderPageContent: (page: { slug: string; body: string }) =>
|
|
146
|
-
shadowMockActive
|
|
147
|
-
? page.body
|
|
148
|
-
: realPageStore.renderPageContent(
|
|
149
|
-
page as Parameters<typeof realPageStore.renderPageContent>[0],
|
|
150
|
-
),
|
|
151
|
-
}));
|
|
152
|
-
|
|
153
|
-
mock.module("../../../util/platform.js", () => ({
|
|
154
|
-
...realPlatform,
|
|
155
|
-
getWorkspaceDir: () =>
|
|
156
|
-
shadowMockActive
|
|
157
|
-
? "/tmp/shadow-test-workspace"
|
|
158
|
-
: realPlatform.getWorkspaceDir(),
|
|
159
|
-
}));
|
|
160
|
-
|
|
161
|
-
mock.module("../tree.js", () => ({
|
|
162
|
-
...realTree,
|
|
163
|
-
resolveDataDir: () =>
|
|
164
|
-
shadowMockActive ? "/tmp/shadow-test-data" : realTree.resolveDataDir(),
|
|
165
|
-
loadLeafTree: async (dataDir: string) => {
|
|
166
|
-
if (!shadowMockActive) return realTree.loadLeafTree(dataDir);
|
|
167
|
-
treeLoads++;
|
|
168
|
-
return FAKE_TREE;
|
|
169
|
-
},
|
|
170
|
-
membersOf: (tree: LeafTree, leaf: string) =>
|
|
171
|
-
shadowMockActive
|
|
172
|
-
? ((tree.leaves.get(leaf) as unknown as { members?: string[] })
|
|
173
|
-
?.members ?? [])
|
|
174
|
-
: realTree.membersOf(tree, leaf),
|
|
175
|
-
}));
|
|
176
|
-
|
|
177
|
-
mock.module("../core.js", () => ({
|
|
178
|
-
...realCore,
|
|
179
|
-
loadCore: async (dataDir: string) => {
|
|
180
|
-
if (!shadowMockActive) return realCore.loadCore(dataDir);
|
|
181
|
-
coreLoads++;
|
|
182
|
-
return new Set(["domain-a"]);
|
|
183
|
-
},
|
|
184
|
-
}));
|
|
185
|
-
|
|
186
|
-
mock.module("../needle.js", () => ({
|
|
187
|
-
...realNeedle,
|
|
188
|
-
buildNeedleIndex: async (
|
|
189
|
-
...args: Parameters<typeof realNeedle.buildNeedleIndex>
|
|
190
|
-
) => {
|
|
191
|
-
if (!shadowMockActive) return realNeedle.buildNeedleIndex(...args);
|
|
192
|
-
needleBuilds++;
|
|
193
|
-
return { query: () => [] } as unknown as Awaited<
|
|
194
|
-
ReturnType<typeof realNeedle.buildNeedleIndex>
|
|
195
|
-
>;
|
|
196
|
-
},
|
|
197
|
-
}));
|
|
198
|
-
|
|
199
|
-
mock.module("../orchestrate.js", () => ({
|
|
200
|
-
...realOrchestrate,
|
|
201
|
-
orchestrate: (...args: Parameters<typeof realOrchestrate.orchestrate>) =>
|
|
202
|
-
shadowMockActive
|
|
203
|
-
? orchestrateSpy(...(args as unknown as []))
|
|
204
|
-
: realOrchestrate.orchestrate(...args),
|
|
205
|
-
}));
|
|
206
|
-
|
|
207
|
-
// Import AFTER mocks so the plugin binds to them.
|
|
208
|
-
const {
|
|
209
|
-
runShadowObservation,
|
|
210
|
-
resetShadowLanesForTests,
|
|
211
|
-
invalidateLanes,
|
|
212
|
-
memoryV3Injector,
|
|
213
|
-
} = await import("../shadow-plugin.js");
|
|
214
|
-
|
|
215
|
-
// The module stubs above stay installed for the rest of the process (Bun can't
|
|
216
|
-
// reliably uninstall them), but `shadowMockActive` gates their fake behavior to
|
|
217
|
-
// this file's tests only, so later-loaded sibling files see real behavior.
|
|
218
|
-
afterAll(() => {
|
|
219
|
-
shadowMockActive = false;
|
|
220
|
-
});
|
|
221
|
-
|
|
222
|
-
function readRows() {
|
|
223
|
-
return testSqlite
|
|
224
|
-
.query(
|
|
225
|
-
`SELECT slug, source, pinned FROM memory_v3_selections ORDER BY slug`,
|
|
226
|
-
)
|
|
227
|
-
.all() as Array<{ slug: string; source: SelectionSource; pinned: number }>;
|
|
228
|
-
}
|
|
229
|
-
|
|
230
|
-
beforeEach(() => {
|
|
231
|
-
shadowMockActive = true;
|
|
232
|
-
liveEnabled = false;
|
|
233
|
-
shadowEnabled = false;
|
|
234
|
-
messages = [
|
|
235
|
-
{
|
|
236
|
-
role: "user",
|
|
237
|
-
content: JSON.stringify([{ type: "text", text: "hello world" }]),
|
|
238
|
-
},
|
|
239
|
-
];
|
|
240
|
-
orchestrateSpy.mockClear();
|
|
241
|
-
treeLoads = 0;
|
|
242
|
-
coreLoads = 0;
|
|
243
|
-
needleBuilds = 0;
|
|
244
|
-
configL2Concurrency = 16;
|
|
245
|
-
testDb = makeDb();
|
|
246
|
-
resetShadowLanesForTests();
|
|
247
|
-
});
|
|
248
|
-
|
|
249
|
-
/** Invoke the memory-v3 injector's `produce()` for a turn. */
|
|
250
|
-
function produce(conversationId: string, turnIndex: number) {
|
|
251
|
-
return memoryV3Injector.produce({
|
|
252
|
-
requestId: "r1",
|
|
253
|
-
conversationId,
|
|
254
|
-
turnIndex,
|
|
255
|
-
trust: {} as never,
|
|
256
|
-
});
|
|
257
|
-
}
|
|
258
|
-
|
|
259
|
-
describe("memory-v3 shadow plugin", () => {
|
|
260
|
-
test("shadow flag OFF → orchestrate not called, no DB writes", async () => {
|
|
261
|
-
shadowEnabled = false;
|
|
262
|
-
await runShadowObservation("conv-1", 0);
|
|
263
|
-
expect(orchestrateSpy).not.toHaveBeenCalled();
|
|
264
|
-
expect(treeLoads).toBe(0);
|
|
265
|
-
expect(readRows()).toHaveLength(0);
|
|
266
|
-
});
|
|
267
|
-
|
|
268
|
-
test("shadow flag ON → orchestrate runs and rows are written", async () => {
|
|
269
|
-
shadowEnabled = true;
|
|
270
|
-
await runShadowObservation("conv-1", 2);
|
|
271
|
-
|
|
272
|
-
expect(orchestrateSpy).toHaveBeenCalledTimes(1);
|
|
273
|
-
const rows = readRows();
|
|
274
|
-
expect(rows).toEqual([
|
|
275
|
-
{ slug: "domain-a/carried", source: "carry-forward", pinned: 0 },
|
|
276
|
-
// page-1 belongs to the core leaf `domain-a` → core+l2, pinned.
|
|
277
|
-
{ slug: "domain-a/page-1", source: "core+l2", pinned: 1 },
|
|
278
|
-
// page-2 belongs to non-core `domain-b` → l1+l2.
|
|
279
|
-
{ slug: "domain-b/page-2", source: "l1+l2", pinned: 0 },
|
|
280
|
-
]);
|
|
281
|
-
});
|
|
282
|
-
|
|
283
|
-
test("the turn passed to orchestrate carries the latest user message", async () => {
|
|
284
|
-
shadowEnabled = true;
|
|
285
|
-
await runShadowObservation("conv-1", 0);
|
|
286
|
-
const turn = (
|
|
287
|
-
orchestrateSpy.mock.calls as unknown as unknown[][]
|
|
288
|
-
)[0]![0] as {
|
|
289
|
-
conversationId: string;
|
|
290
|
-
turnNumber: number;
|
|
291
|
-
currentMessage: string;
|
|
292
|
-
};
|
|
293
|
-
expect(turn.conversationId).toBe("conv-1");
|
|
294
|
-
expect(turn.turnNumber).toBe(0);
|
|
295
|
-
expect(turn.currentMessage).toBe("hello world");
|
|
296
|
-
});
|
|
297
|
-
|
|
298
|
-
test("orchestrate receives the configured L2 concurrency", async () => {
|
|
299
|
-
shadowEnabled = true;
|
|
300
|
-
configL2Concurrency = 9;
|
|
301
|
-
await runShadowObservation("conv-1", 0);
|
|
302
|
-
const deps = (
|
|
303
|
-
orchestrateSpy.mock.calls as unknown as unknown[][]
|
|
304
|
-
)[0]![1] as {
|
|
305
|
-
l2Concurrency?: number;
|
|
306
|
-
};
|
|
307
|
-
expect(deps.l2Concurrency).toBe(9);
|
|
308
|
-
});
|
|
309
|
-
|
|
310
|
-
test("both flags OFF → produce returns null, no orchestrate, no writes", async () => {
|
|
311
|
-
liveEnabled = false;
|
|
312
|
-
shadowEnabled = false;
|
|
313
|
-
const block = await produce("conv-1", 0);
|
|
314
|
-
expect(block).toBeNull();
|
|
315
|
-
expect(orchestrateSpy).not.toHaveBeenCalled();
|
|
316
|
-
expect(readRows()).toHaveLength(0);
|
|
317
|
-
});
|
|
318
|
-
|
|
319
|
-
test("shadow-only (live off) → produce returns null but still logs", async () => {
|
|
320
|
-
liveEnabled = false;
|
|
321
|
-
shadowEnabled = true;
|
|
322
|
-
const block = await produce("conv-1", 0);
|
|
323
|
-
expect(block).toBeNull();
|
|
324
|
-
expect(orchestrateSpy).toHaveBeenCalledTimes(1);
|
|
325
|
-
expect(readRows().length).toBeGreaterThan(0);
|
|
326
|
-
});
|
|
327
|
-
|
|
328
|
-
test("live on → produce returns the rendered <memory> block and logs", async () => {
|
|
329
|
-
liveEnabled = true;
|
|
330
|
-
shadowEnabled = false;
|
|
331
|
-
const block = await produce("conv-1", 0);
|
|
332
|
-
expect(block).not.toBeNull();
|
|
333
|
-
expect(block!.placement).toBe("after-memory-prefix");
|
|
334
|
-
expect(block!.text.startsWith("<memory>\n")).toBe(true);
|
|
335
|
-
expect(block!.text.endsWith("\n</memory>")).toBe(true);
|
|
336
|
-
// finalInjection slugs are rendered into the block in order.
|
|
337
|
-
expect(block!.text).toContain("body for domain-a/page-1");
|
|
338
|
-
expect(block!.text).toContain("body for domain-a/carried");
|
|
339
|
-
// Selections are still logged in live mode.
|
|
340
|
-
expect(readRows().length).toBeGreaterThan(0);
|
|
341
|
-
});
|
|
342
|
-
|
|
343
|
-
test("live on but empty selection → produce returns null", async () => {
|
|
344
|
-
liveEnabled = true;
|
|
345
|
-
shadowEnabled = false;
|
|
346
|
-
orchestrateSpy.mockImplementationOnce(async () => ({
|
|
347
|
-
openedLeaves: [],
|
|
348
|
-
currentSelections: [],
|
|
349
|
-
workingSetUnion: new Set<string>(),
|
|
350
|
-
finalInjection: [],
|
|
351
|
-
}));
|
|
352
|
-
const block = await produce("conv-1", 0);
|
|
353
|
-
expect(block).toBeNull();
|
|
354
|
-
// Orchestration still ran (and logged nothing, since there were no rows).
|
|
355
|
-
expect(orchestrateSpy).toHaveBeenCalledTimes(1);
|
|
356
|
-
expect(readRows()).toHaveLength(0);
|
|
357
|
-
});
|
|
358
|
-
|
|
359
|
-
test("lazy-init runs the loaders only once across turns", async () => {
|
|
360
|
-
shadowEnabled = true;
|
|
361
|
-
await runShadowObservation("conv-1", 0);
|
|
362
|
-
await runShadowObservation("conv-1", 1);
|
|
363
|
-
await runShadowObservation("conv-1", 2);
|
|
364
|
-
expect(treeLoads).toBe(1);
|
|
365
|
-
expect(coreLoads).toBe(1);
|
|
366
|
-
expect(needleBuilds).toBe(1);
|
|
367
|
-
expect(orchestrateSpy).toHaveBeenCalledTimes(3);
|
|
368
|
-
});
|
|
369
|
-
|
|
370
|
-
test("invalidateLanes forces a one-time rebuild on the next turn", async () => {
|
|
371
|
-
shadowEnabled = true;
|
|
372
|
-
await runShadowObservation("conv-1", 0);
|
|
373
|
-
await runShadowObservation("conv-1", 1);
|
|
374
|
-
// One build so far; invalidation drops it.
|
|
375
|
-
expect(treeLoads).toBe(1);
|
|
376
|
-
expect(needleBuilds).toBe(1);
|
|
377
|
-
|
|
378
|
-
invalidateLanes();
|
|
379
|
-
|
|
380
|
-
// The next turn rebuilds (fresh tree load + fresh needle index)...
|
|
381
|
-
await runShadowObservation("conv-1", 2);
|
|
382
|
-
expect(treeLoads).toBe(2);
|
|
383
|
-
expect(coreLoads).toBe(2);
|
|
384
|
-
expect(needleBuilds).toBe(2);
|
|
385
|
-
|
|
386
|
-
// ...and the rebuild is memoized again — no further builds until the next
|
|
387
|
-
// invalidation.
|
|
388
|
-
await runShadowObservation("conv-1", 3);
|
|
389
|
-
expect(treeLoads).toBe(2);
|
|
390
|
-
expect(needleBuilds).toBe(2);
|
|
391
|
-
});
|
|
392
|
-
|
|
393
|
-
test("resetShadowLanesForTests invalidates like invalidateLanes", async () => {
|
|
394
|
-
shadowEnabled = true;
|
|
395
|
-
await runShadowObservation("conv-1", 0);
|
|
396
|
-
expect(treeLoads).toBe(1);
|
|
397
|
-
|
|
398
|
-
resetShadowLanesForTests();
|
|
399
|
-
|
|
400
|
-
await runShadowObservation("conv-1", 1);
|
|
401
|
-
expect(treeLoads).toBe(2);
|
|
402
|
-
});
|
|
403
|
-
|
|
404
|
-
test("concurrent first turns after invalidation share a single build", async () => {
|
|
405
|
-
shadowEnabled = true;
|
|
406
|
-
await runShadowObservation("conv-1", 0);
|
|
407
|
-
expect(treeLoads).toBe(1);
|
|
408
|
-
|
|
409
|
-
invalidateLanes();
|
|
410
|
-
|
|
411
|
-
// Both turns race the rebuild; memoization must collapse them to one build.
|
|
412
|
-
await Promise.all([
|
|
413
|
-
runShadowObservation("conv-1", 1),
|
|
414
|
-
runShadowObservation("conv-1", 2),
|
|
415
|
-
]);
|
|
416
|
-
expect(treeLoads).toBe(2);
|
|
417
|
-
expect(needleBuilds).toBe(2);
|
|
418
|
-
});
|
|
419
|
-
|
|
420
|
-
test("no user message → no orchestrate, no writes", async () => {
|
|
421
|
-
shadowEnabled = true;
|
|
422
|
-
messages = [
|
|
423
|
-
{
|
|
424
|
-
role: "assistant",
|
|
425
|
-
content: JSON.stringify([{ type: "text", text: "hi" }]),
|
|
426
|
-
},
|
|
427
|
-
];
|
|
428
|
-
await runShadowObservation("conv-1", 0);
|
|
429
|
-
expect(orchestrateSpy).not.toHaveBeenCalled();
|
|
430
|
-
expect(readRows()).toHaveLength(0);
|
|
431
|
-
});
|
|
432
|
-
});
|
|
@@ -1,168 +0,0 @@
|
|
|
1
|
-
import { promises as fs } from "node:fs";
|
|
2
|
-
import os from "node:os";
|
|
3
|
-
import path from "node:path";
|
|
4
|
-
import { afterEach, beforeEach, describe, expect, test } from "bun:test";
|
|
5
|
-
|
|
6
|
-
import { restoreDataDir, snapshotDataDir } from "../snapshot.js";
|
|
7
|
-
|
|
8
|
-
let tmpRoot: string;
|
|
9
|
-
let dataDir: string;
|
|
10
|
-
|
|
11
|
-
async function readJson(file: string): Promise<unknown> {
|
|
12
|
-
return JSON.parse(await fs.readFile(file, "utf8")) as unknown;
|
|
13
|
-
}
|
|
14
|
-
|
|
15
|
-
async function listLeaves(dir: string): Promise<Record<string, string>> {
|
|
16
|
-
const leavesDir = path.join(dir, "leaves");
|
|
17
|
-
const out: Record<string, string> = {};
|
|
18
|
-
async function walk(current: string, prefix: string): Promise<void> {
|
|
19
|
-
let entries: import("node:fs").Dirent[];
|
|
20
|
-
try {
|
|
21
|
-
entries = await fs.readdir(current, { withFileTypes: true });
|
|
22
|
-
} catch {
|
|
23
|
-
return;
|
|
24
|
-
}
|
|
25
|
-
for (const entry of entries) {
|
|
26
|
-
const rel = prefix ? `${prefix}/${entry.name}` : entry.name;
|
|
27
|
-
const abs = path.join(current, entry.name);
|
|
28
|
-
if (entry.isDirectory()) {
|
|
29
|
-
await walk(abs, rel);
|
|
30
|
-
} else if (entry.isFile()) {
|
|
31
|
-
out[rel] = await fs.readFile(abs, "utf8");
|
|
32
|
-
}
|
|
33
|
-
}
|
|
34
|
-
}
|
|
35
|
-
await walk(leavesDir, "");
|
|
36
|
-
return out;
|
|
37
|
-
}
|
|
38
|
-
|
|
39
|
-
async function seedDataDir(): Promise<void> {
|
|
40
|
-
await fs.mkdir(path.join(dataDir, "leaves", "alpha"), { recursive: true });
|
|
41
|
-
await fs.writeFile(
|
|
42
|
-
path.join(dataDir, "leaves", "alpha", "leaf-1.md"),
|
|
43
|
-
"leaf one body",
|
|
44
|
-
);
|
|
45
|
-
await fs.writeFile(
|
|
46
|
-
path.join(dataDir, "leaves", "leaf-2.md"),
|
|
47
|
-
"leaf two body",
|
|
48
|
-
);
|
|
49
|
-
await fs.writeFile(
|
|
50
|
-
path.join(dataDir, "assignments.json"),
|
|
51
|
-
JSON.stringify({ "leaf-1": ["topic-a"] }),
|
|
52
|
-
);
|
|
53
|
-
await fs.writeFile(
|
|
54
|
-
path.join(dataDir, "core.json"),
|
|
55
|
-
JSON.stringify({ version: 1, root: "topic-a" }),
|
|
56
|
-
);
|
|
57
|
-
}
|
|
58
|
-
|
|
59
|
-
beforeEach(async () => {
|
|
60
|
-
tmpRoot = await fs.mkdtemp(path.join(os.tmpdir(), "v3-snapshot-"));
|
|
61
|
-
dataDir = path.join(tmpRoot, "data");
|
|
62
|
-
await fs.mkdir(dataDir, { recursive: true });
|
|
63
|
-
});
|
|
64
|
-
|
|
65
|
-
afterEach(async () => {
|
|
66
|
-
await fs.rm(tmpRoot, { recursive: true, force: true });
|
|
67
|
-
});
|
|
68
|
-
|
|
69
|
-
describe("snapshotDataDir / restoreDataDir", () => {
|
|
70
|
-
test("roundtrips leaves, assignments, and core", async () => {
|
|
71
|
-
await seedDataDir();
|
|
72
|
-
const before = {
|
|
73
|
-
leaves: await listLeaves(dataDir),
|
|
74
|
-
assignments: await readJson(path.join(dataDir, "assignments.json")),
|
|
75
|
-
core: await readJson(path.join(dataDir, "core.json")),
|
|
76
|
-
};
|
|
77
|
-
|
|
78
|
-
const snapshotPath = await snapshotDataDir(dataDir, { label: "snap-1" });
|
|
79
|
-
|
|
80
|
-
// Mutate the live data dir after snapshotting.
|
|
81
|
-
await fs.writeFile(
|
|
82
|
-
path.join(dataDir, "leaves", "alpha", "leaf-1.md"),
|
|
83
|
-
"CORRUPTED",
|
|
84
|
-
);
|
|
85
|
-
await fs.rm(path.join(dataDir, "leaves", "leaf-2.md"));
|
|
86
|
-
await fs.writeFile(
|
|
87
|
-
path.join(dataDir, "assignments.json"),
|
|
88
|
-
JSON.stringify({ broken: true }),
|
|
89
|
-
);
|
|
90
|
-
await fs.writeFile(
|
|
91
|
-
path.join(dataDir, "core.json"),
|
|
92
|
-
JSON.stringify({ broken: true }),
|
|
93
|
-
);
|
|
94
|
-
|
|
95
|
-
await restoreDataDir(snapshotPath, dataDir);
|
|
96
|
-
|
|
97
|
-
expect(await listLeaves(dataDir)).toEqual(before.leaves);
|
|
98
|
-
expect(await readJson(path.join(dataDir, "assignments.json"))).toEqual(
|
|
99
|
-
before.assignments,
|
|
100
|
-
);
|
|
101
|
-
expect(await readJson(path.join(dataDir, "core.json"))).toEqual(
|
|
102
|
-
before.core,
|
|
103
|
-
);
|
|
104
|
-
});
|
|
105
|
-
|
|
106
|
-
test("roundtrips the pageRefs diff", async () => {
|
|
107
|
-
await seedDataDir();
|
|
108
|
-
const pageRefs = new Map<string, string[]>([
|
|
109
|
-
["slug-one", ["leaf-1", "leaf-2"]],
|
|
110
|
-
["slug-two", ["leaf-3"]],
|
|
111
|
-
]);
|
|
112
|
-
|
|
113
|
-
const snapshotPath = await snapshotDataDir(dataDir, {
|
|
114
|
-
label: "snap-refs",
|
|
115
|
-
pageRefs,
|
|
116
|
-
});
|
|
117
|
-
|
|
118
|
-
const { pageRefs: restored } = await restoreDataDir(snapshotPath, dataDir);
|
|
119
|
-
expect(restored).toBeDefined();
|
|
120
|
-
expect(restored).toEqual(pageRefs);
|
|
121
|
-
});
|
|
122
|
-
|
|
123
|
-
test("returns no pageRefs when none were captured", async () => {
|
|
124
|
-
await seedDataDir();
|
|
125
|
-
const snapshotPath = await snapshotDataDir(dataDir, { label: "snap-none" });
|
|
126
|
-
const result = await restoreDataDir(snapshotPath, dataDir);
|
|
127
|
-
expect(result.pageRefs).toBeUndefined();
|
|
128
|
-
});
|
|
129
|
-
|
|
130
|
-
test("restoring removes leaves added after the snapshot", async () => {
|
|
131
|
-
await seedDataDir();
|
|
132
|
-
const snapshotPath = await snapshotDataDir(dataDir, { label: "snap-add" });
|
|
133
|
-
|
|
134
|
-
await fs.writeFile(
|
|
135
|
-
path.join(dataDir, "leaves", "leaf-extra.md"),
|
|
136
|
-
"added later",
|
|
137
|
-
);
|
|
138
|
-
|
|
139
|
-
await restoreDataDir(snapshotPath, dataDir);
|
|
140
|
-
const leaves = await listLeaves(dataDir);
|
|
141
|
-
expect(leaves["leaf-extra.md"]).toBeUndefined();
|
|
142
|
-
expect(leaves["leaf-2.md"]).toBe("leaf two body");
|
|
143
|
-
});
|
|
144
|
-
|
|
145
|
-
test("retention is bounded to the last N snapshots", async () => {
|
|
146
|
-
await seedDataDir();
|
|
147
|
-
// Zero-padded labels sort chronologically.
|
|
148
|
-
const labels = Array.from(
|
|
149
|
-
{ length: 8 },
|
|
150
|
-
(_, i) => `snap-${String(i).padStart(2, "0")}`,
|
|
151
|
-
);
|
|
152
|
-
for (const label of labels) {
|
|
153
|
-
await snapshotDataDir(dataDir, { label });
|
|
154
|
-
}
|
|
155
|
-
|
|
156
|
-
const snapshotsRoot = path.join(tmpRoot, "v3-snapshots");
|
|
157
|
-
const remaining = (await fs.readdir(snapshotsRoot)).sort();
|
|
158
|
-
expect(remaining.length).toBe(5);
|
|
159
|
-
// Oldest three pruned, newest five kept.
|
|
160
|
-
expect(remaining).toEqual([
|
|
161
|
-
"snap-03",
|
|
162
|
-
"snap-04",
|
|
163
|
-
"snap-05",
|
|
164
|
-
"snap-06",
|
|
165
|
-
"snap-07",
|
|
166
|
-
]);
|
|
167
|
-
});
|
|
168
|
-
});
|