@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
|
@@ -1,274 +0,0 @@
|
|
|
1
|
-
import { mkdir, mkdtemp, readFile, rm, writeFile } from "node:fs/promises";
|
|
2
|
-
import { tmpdir } from "node:os";
|
|
3
|
-
import { join } from "node:path";
|
|
4
|
-
import { afterEach, beforeEach, describe, expect, test } from "bun:test";
|
|
5
|
-
|
|
6
|
-
import type { Provider } from "../../../providers/types.js";
|
|
7
|
-
import { readPage, writePage } from "../../v2/page-store.js";
|
|
8
|
-
import { type LeafRef, reconcileTree } from "../reconcile.js";
|
|
9
|
-
|
|
10
|
-
/**
|
|
11
|
-
* A canned provider whose `assign_leaves` tool-use response is computed from
|
|
12
|
-
* the user message (the page content) via `idsForMessage`. This lets a test
|
|
13
|
-
* deterministically re-home each page onto specific surviving leaves without a
|
|
14
|
-
* real LLM. The leaf index in the system prompt is sorted by path; tests pass
|
|
15
|
-
* the 1-based id of the target leaf in that sorted order.
|
|
16
|
-
*/
|
|
17
|
-
function stubProvider(
|
|
18
|
-
idsForMessage: (message: string, systemPrompt: string) => number[],
|
|
19
|
-
): Provider {
|
|
20
|
-
return {
|
|
21
|
-
name: "stub",
|
|
22
|
-
async sendMessage(messages: unknown, opts: unknown) {
|
|
23
|
-
const msg = messages as Array<{
|
|
24
|
-
content: Array<{ type: string; text?: string }>;
|
|
25
|
-
}>;
|
|
26
|
-
const text = msg[0]?.content?.map((c) => c.text ?? "").join("") ?? "";
|
|
27
|
-
const systemPrompt =
|
|
28
|
-
(opts as { systemPrompt?: string }).systemPrompt ?? "";
|
|
29
|
-
const ids = idsForMessage(text, systemPrompt);
|
|
30
|
-
return {
|
|
31
|
-
id: "msg_stub",
|
|
32
|
-
role: "assistant",
|
|
33
|
-
model: "stub-model",
|
|
34
|
-
stopReason: "tool_use",
|
|
35
|
-
content: [
|
|
36
|
-
{
|
|
37
|
-
type: "tool_use",
|
|
38
|
-
id: "tool_1",
|
|
39
|
-
name: "assign_leaves",
|
|
40
|
-
input: { ids },
|
|
41
|
-
},
|
|
42
|
-
],
|
|
43
|
-
};
|
|
44
|
-
},
|
|
45
|
-
} as unknown as Provider;
|
|
46
|
-
}
|
|
47
|
-
|
|
48
|
-
let workspaceDir: string;
|
|
49
|
-
let dataDir: string;
|
|
50
|
-
|
|
51
|
-
beforeEach(async () => {
|
|
52
|
-
workspaceDir = await mkdtemp(join(tmpdir(), "reconcile-ws-"));
|
|
53
|
-
dataDir = await mkdtemp(join(tmpdir(), "reconcile-data-"));
|
|
54
|
-
});
|
|
55
|
-
|
|
56
|
-
afterEach(async () => {
|
|
57
|
-
await rm(workspaceDir, { recursive: true, force: true });
|
|
58
|
-
await rm(dataDir, { recursive: true, force: true });
|
|
59
|
-
await rm(join(dataDir, "..", "v3-snapshots"), {
|
|
60
|
-
recursive: true,
|
|
61
|
-
force: true,
|
|
62
|
-
}).catch(() => {});
|
|
63
|
-
});
|
|
64
|
-
|
|
65
|
-
/** Write a leaf `.md` with `{path, in_core, id}` frontmatter. */
|
|
66
|
-
async function writeLeaf(
|
|
67
|
-
path: string,
|
|
68
|
-
opts: { id?: string; inCore?: boolean; body?: string } = {},
|
|
69
|
-
): Promise<void> {
|
|
70
|
-
const full = join(dataDir, "leaves", `${path}.md`);
|
|
71
|
-
await mkdir(join(full, ".."), { recursive: true });
|
|
72
|
-
const lines = [`path: ${path}`, `in_core: ${opts.inCore ?? false}`];
|
|
73
|
-
if (opts.id) lines.push(`id: ${opts.id}`);
|
|
74
|
-
await writeFile(
|
|
75
|
-
full,
|
|
76
|
-
`---\n${lines.join("\n")}\n---\n${opts.body ?? `Leaf ${path}.`}\n`,
|
|
77
|
-
);
|
|
78
|
-
}
|
|
79
|
-
|
|
80
|
-
async function writeCore(alwaysOn: string[]): Promise<void> {
|
|
81
|
-
await writeFile(join(dataDir, "core.json"), JSON.stringify({ alwaysOn }));
|
|
82
|
-
}
|
|
83
|
-
|
|
84
|
-
/**
|
|
85
|
-
* `loadLeafTree` (used by the re-homing path) reads `assignments.json`
|
|
86
|
-
* unconditionally, so seed an empty one. Page→leaf membership comes from page
|
|
87
|
-
* frontmatter, not this file.
|
|
88
|
-
*/
|
|
89
|
-
async function seedAssignments(): Promise<void> {
|
|
90
|
-
await writeFile(join(dataDir, "assignments.json"), "{}");
|
|
91
|
-
}
|
|
92
|
-
|
|
93
|
-
async function writePageWithLeaves(
|
|
94
|
-
slug: string,
|
|
95
|
-
leaves: string[],
|
|
96
|
-
): Promise<void> {
|
|
97
|
-
await writePage(workspaceDir, {
|
|
98
|
-
slug,
|
|
99
|
-
frontmatter: { edges: [], ref_files: [], ref_urls: [], leaves },
|
|
100
|
-
body: `Body of ${slug}.`,
|
|
101
|
-
});
|
|
102
|
-
}
|
|
103
|
-
|
|
104
|
-
async function pageLeaves(slug: string): Promise<string[]> {
|
|
105
|
-
const page = await readPage(workspaceDir, slug);
|
|
106
|
-
return page?.frontmatter.leaves ?? [];
|
|
107
|
-
}
|
|
108
|
-
|
|
109
|
-
async function coreOf(): Promise<string[]> {
|
|
110
|
-
const raw = await readFile(join(dataDir, "core.json"), "utf8");
|
|
111
|
-
return (JSON.parse(raw) as { alwaysOn: string[] }).alwaysOn;
|
|
112
|
-
}
|
|
113
|
-
|
|
114
|
-
describe("reconcileTree", () => {
|
|
115
|
-
test("rename preserves assignments (refs rewritten old → new)", async () => {
|
|
116
|
-
// Current tree: the leaf moved from domain-a/topic-x → domain-b/topic-x,
|
|
117
|
-
// keeping its stable id.
|
|
118
|
-
await writeLeaf("domain-b/topic-x", { id: "leaf-x" });
|
|
119
|
-
await writeCore(["domain-a/topic-x"]);
|
|
120
|
-
await writePageWithLeaves("page-a", ["domain-a/topic-x"]);
|
|
121
|
-
|
|
122
|
-
const prev: LeafRef[] = [{ id: "leaf-x", path: "domain-a/topic-x" }];
|
|
123
|
-
const result = await reconcileTree({
|
|
124
|
-
prevLeaves: prev,
|
|
125
|
-
dataDir,
|
|
126
|
-
workspaceDir,
|
|
127
|
-
});
|
|
128
|
-
|
|
129
|
-
expect(await pageLeaves("page-a")).toEqual(["domain-b/topic-x"]);
|
|
130
|
-
expect(await coreOf()).toEqual(["domain-b/topic-x"]);
|
|
131
|
-
expect(result.renames).toEqual([
|
|
132
|
-
{
|
|
133
|
-
id: "leaf-x",
|
|
134
|
-
oldPath: "domain-a/topic-x",
|
|
135
|
-
newPath: "domain-b/topic-x",
|
|
136
|
-
},
|
|
137
|
-
]);
|
|
138
|
-
expect(result.idToNewPath.get("leaf-x")).toBe("domain-b/topic-x");
|
|
139
|
-
});
|
|
140
|
-
|
|
141
|
-
test("delete re-homes members with no orphan", async () => {
|
|
142
|
-
// leaf-x is deleted; leaf-y survives. page-a was only on leaf-x.
|
|
143
|
-
await writeLeaf("domain-a/topic-y", { id: "leaf-y" });
|
|
144
|
-
await writeCore([]);
|
|
145
|
-
await seedAssignments();
|
|
146
|
-
await writePageWithLeaves("page-a", ["domain-a/topic-x"]);
|
|
147
|
-
|
|
148
|
-
// Surviving leaf is sorted index 1 (only leaf in tree).
|
|
149
|
-
const provider = stubProvider(() => [1]);
|
|
150
|
-
|
|
151
|
-
const prev: LeafRef[] = [
|
|
152
|
-
{ id: "leaf-x", path: "domain-a/topic-x" },
|
|
153
|
-
{ id: "leaf-y", path: "domain-a/topic-y" },
|
|
154
|
-
];
|
|
155
|
-
const result = await reconcileTree({
|
|
156
|
-
prevLeaves: prev,
|
|
157
|
-
dataDir,
|
|
158
|
-
workspaceDir,
|
|
159
|
-
provider,
|
|
160
|
-
});
|
|
161
|
-
|
|
162
|
-
const after = await pageLeaves("page-a");
|
|
163
|
-
// No orphan: at least one surviving leaf, and the dead path is gone.
|
|
164
|
-
expect(after.length).toBeGreaterThanOrEqual(1);
|
|
165
|
-
expect(after).not.toContain("domain-a/topic-x");
|
|
166
|
-
expect(after).toContain("domain-a/topic-y");
|
|
167
|
-
expect(result.deleted).toEqual(["domain-a/topic-x"]);
|
|
168
|
-
});
|
|
169
|
-
|
|
170
|
-
test("split distributes members across toPaths", async () => {
|
|
171
|
-
// leaf-x splits into leaf-x1 (domain-a/x1) and leaf-x2 (domain-a/x2).
|
|
172
|
-
await writeLeaf("domain-a/x1", { id: "leaf-x1" });
|
|
173
|
-
await writeLeaf("domain-a/x2", { id: "leaf-x2" });
|
|
174
|
-
// A third surviving leaf NOT in the split target set — members must not
|
|
175
|
-
// land here.
|
|
176
|
-
await writeLeaf("domain-b/other", { id: "leaf-other" });
|
|
177
|
-
await writeCore([]);
|
|
178
|
-
await seedAssignments();
|
|
179
|
-
await writePageWithLeaves("page-a", ["domain-a/topic-x"]);
|
|
180
|
-
await writePageWithLeaves("page-b", ["domain-a/topic-x"]);
|
|
181
|
-
|
|
182
|
-
// Restricted tree (split.toPaths) sorts as [domain-a/x1, domain-a/x2].
|
|
183
|
-
// page-a → x1 (id 1), page-b → x2 (id 2).
|
|
184
|
-
const provider = stubProvider((message) =>
|
|
185
|
-
message.includes("page-a") ? [1] : [2],
|
|
186
|
-
);
|
|
187
|
-
|
|
188
|
-
const prev: LeafRef[] = [{ id: "leaf-x", path: "domain-a/topic-x" }];
|
|
189
|
-
await reconcileTree({
|
|
190
|
-
prevLeaves: prev,
|
|
191
|
-
dataDir,
|
|
192
|
-
workspaceDir,
|
|
193
|
-
provider,
|
|
194
|
-
splits: [{ fromId: "leaf-x", toPaths: ["domain-a/x1", "domain-a/x2"] }],
|
|
195
|
-
});
|
|
196
|
-
|
|
197
|
-
expect(await pageLeaves("page-a")).toEqual(["domain-a/x1"]);
|
|
198
|
-
expect(await pageLeaves("page-b")).toEqual(["domain-a/x2"]);
|
|
199
|
-
// Neither page landed on the off-target leaf.
|
|
200
|
-
expect(await pageLeaves("page-a")).not.toContain("domain-b/other");
|
|
201
|
-
expect(await pageLeaves("page-b")).not.toContain("domain-b/other");
|
|
202
|
-
});
|
|
203
|
-
|
|
204
|
-
test("dangling core entry is pruned", async () => {
|
|
205
|
-
await writeLeaf("domain-a/topic-x", { id: "leaf-x" });
|
|
206
|
-
// core.json points at a leaf that no longer exists in the tree.
|
|
207
|
-
await writeCore(["domain-a/topic-x", "domain-a/gone"]);
|
|
208
|
-
await writePageWithLeaves("page-a", ["domain-a/topic-x"]);
|
|
209
|
-
|
|
210
|
-
// No prev entry for the gone leaf and no page references it, so reconcile
|
|
211
|
-
// succeeds and prunes the stale core entry.
|
|
212
|
-
const prev: LeafRef[] = [{ id: "leaf-x", path: "domain-a/topic-x" }];
|
|
213
|
-
const result = await reconcileTree({
|
|
214
|
-
prevLeaves: prev,
|
|
215
|
-
dataDir,
|
|
216
|
-
workspaceDir,
|
|
217
|
-
});
|
|
218
|
-
|
|
219
|
-
expect(await coreOf()).toEqual(["domain-a/topic-x"]);
|
|
220
|
-
expect(result.prunedCore).toEqual(["domain-a/gone"]);
|
|
221
|
-
});
|
|
222
|
-
|
|
223
|
-
test("out-of-band dangling page ref converges (prune pass)", async () => {
|
|
224
|
-
// The current tree has only leaf-x. page-a references leaf-x (fine) PLUS a
|
|
225
|
-
// residual leaf path that exists in NEITHER prev nor current — an out-of-band
|
|
226
|
-
// ref a maintainer left when deleting a leaf file without a `prevLeaves`
|
|
227
|
-
// entry. v1 reconcile is a convergence/prune pass: it drops the dangling ref
|
|
228
|
-
// (the page keeps its surviving leaf, so no re-home is needed) instead of
|
|
229
|
-
// failing closed.
|
|
230
|
-
await writeLeaf("domain-a/topic-x", { id: "leaf-x" });
|
|
231
|
-
await writeCore(["domain-a/topic-x"]);
|
|
232
|
-
await seedAssignments();
|
|
233
|
-
await writePageWithLeaves("page-a", [
|
|
234
|
-
"domain-a/topic-x",
|
|
235
|
-
"domain-a/residual",
|
|
236
|
-
]);
|
|
237
|
-
|
|
238
|
-
const prev: LeafRef[] = [{ id: "leaf-x", path: "domain-a/topic-x" }];
|
|
239
|
-
const result = await reconcileTree({
|
|
240
|
-
prevLeaves: prev,
|
|
241
|
-
dataDir,
|
|
242
|
-
workspaceDir,
|
|
243
|
-
});
|
|
244
|
-
|
|
245
|
-
// Converged: the dangling residual ref is gone; the surviving leaf remains.
|
|
246
|
-
expect(await pageLeaves("page-a")).toEqual(["domain-a/topic-x"]);
|
|
247
|
-
expect(await coreOf()).toEqual(["domain-a/topic-x"]);
|
|
248
|
-
// The out-of-band drop is not a reconciler-tracked delete (no prev entry),
|
|
249
|
-
// so it is not reported in `deleted`.
|
|
250
|
-
expect(result.deleted).toEqual([]);
|
|
251
|
-
});
|
|
252
|
-
|
|
253
|
-
test("page whose ONLY leaf dangles out-of-band is re-homed, not orphaned", async () => {
|
|
254
|
-
// page-a's sole leaf was deleted on disk with no `prevLeaves` entry. The
|
|
255
|
-
// convergence pass must re-home it onto a surviving leaf before dropping the
|
|
256
|
-
// dead ref, so the page is never left with zero leaves.
|
|
257
|
-
await writeLeaf("domain-a/topic-x", { id: "leaf-x" });
|
|
258
|
-
await writeCore([]);
|
|
259
|
-
await seedAssignments();
|
|
260
|
-
// page-a points only at a leaf absent from the tree (out-of-band delete).
|
|
261
|
-
await writePageWithLeaves("page-a", ["domain-a/gone"]);
|
|
262
|
-
|
|
263
|
-
// Only surviving leaf is sorted index 1 → re-home target.
|
|
264
|
-
const provider = stubProvider(() => [1]);
|
|
265
|
-
|
|
266
|
-
const prev: LeafRef[] = [{ id: "leaf-x", path: "domain-a/topic-x" }];
|
|
267
|
-
await reconcileTree({ prevLeaves: prev, dataDir, workspaceDir, provider });
|
|
268
|
-
|
|
269
|
-
const after = await pageLeaves("page-a");
|
|
270
|
-
expect(after).not.toContain("domain-a/gone"); // dangling ref dropped
|
|
271
|
-
expect(after).toEqual(["domain-a/topic-x"]); // re-homed onto the survivor
|
|
272
|
-
expect(after.length).toBeGreaterThanOrEqual(1); // never orphaned
|
|
273
|
-
});
|
|
274
|
-
});
|
|
@@ -1,61 +0,0 @@
|
|
|
1
|
-
import { describe, expect, test } from "bun:test";
|
|
2
|
-
|
|
3
|
-
import { wrapMemoryBlock } from "../../memory-marker.js";
|
|
4
|
-
import { renderMemoryBlock } from "../render-injection.js";
|
|
5
|
-
import { Slug } from "../types.js";
|
|
6
|
-
|
|
7
|
-
const resolver =
|
|
8
|
-
(pages: Record<Slug, string>) =>
|
|
9
|
-
async (slug: Slug): Promise<string> =>
|
|
10
|
-
pages[slug] ?? "";
|
|
11
|
-
|
|
12
|
-
describe("renderMemoryBlock", () => {
|
|
13
|
-
test("wraps the selection in a single <memory> block, in order", async () => {
|
|
14
|
-
const pages: Record<Slug, string> = {
|
|
15
|
-
"page-a": "alpha body",
|
|
16
|
-
"page-b": "beta body",
|
|
17
|
-
};
|
|
18
|
-
|
|
19
|
-
const block = await renderMemoryBlock(
|
|
20
|
-
["page-a", "page-b"],
|
|
21
|
-
resolver(pages),
|
|
22
|
-
);
|
|
23
|
-
|
|
24
|
-
expect(block).toBe("<memory>\nalpha body\nbeta body\n</memory>");
|
|
25
|
-
});
|
|
26
|
-
|
|
27
|
-
test("empty selection renders the empty string", async () => {
|
|
28
|
-
const block = await renderMemoryBlock([], async () => "unused");
|
|
29
|
-
expect(block).toBe("");
|
|
30
|
-
});
|
|
31
|
-
|
|
32
|
-
test("preserves input order deterministically", async () => {
|
|
33
|
-
const pages: Record<Slug, string> = {
|
|
34
|
-
"page-a": "A",
|
|
35
|
-
"page-b": "B",
|
|
36
|
-
"page-c": "C",
|
|
37
|
-
};
|
|
38
|
-
|
|
39
|
-
const forward = await renderMemoryBlock(
|
|
40
|
-
["page-a", "page-b", "page-c"],
|
|
41
|
-
resolver(pages),
|
|
42
|
-
);
|
|
43
|
-
const reversed = await renderMemoryBlock(
|
|
44
|
-
["page-c", "page-b", "page-a"],
|
|
45
|
-
resolver(pages),
|
|
46
|
-
);
|
|
47
|
-
|
|
48
|
-
expect(forward).toBe("<memory>\nA\nB\nC\n</memory>");
|
|
49
|
-
expect(reversed).toBe("<memory>\nC\nB\nA\n</memory>");
|
|
50
|
-
});
|
|
51
|
-
|
|
52
|
-
test("emits the shared wrapMemoryBlock marker the v2 stripper recognizes", async () => {
|
|
53
|
-
const block = await renderMemoryBlock(
|
|
54
|
-
["page-a"],
|
|
55
|
-
resolver({ "page-a": "x" }),
|
|
56
|
-
);
|
|
57
|
-
// Guard the v2/v3 marker contract: the rendered block must be byte-identical
|
|
58
|
-
// to wrapping the joined content with the shared helper.
|
|
59
|
-
expect(block).toBe(wrapMemoryBlock("x"));
|
|
60
|
-
});
|
|
61
|
-
});
|
|
@@ -1,332 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Tests for `assistant/src/memory/v3/router.ts`.
|
|
3
|
-
*
|
|
4
|
-
* Coverage matrix:
|
|
5
|
-
* - Returned IDs map to the right leaves by 1-based index, in model order.
|
|
6
|
-
* - Omitted `ids` → no leaves (the router must name leaves explicitly,
|
|
7
|
-
* never open the whole tree).
|
|
8
|
-
* - Explicit `ids: []` → no leaves (deliberate abstention).
|
|
9
|
-
* - Out-of-range / duplicate IDs ignored, no throw.
|
|
10
|
-
* - No provider / missing tool_use / schema mismatch / throw → no leaves
|
|
11
|
-
* (degrade to the deterministic lanes), the last three after a re-prompt
|
|
12
|
-
* retry; a malformed response that recovers on retry returns its IDs.
|
|
13
|
-
* - The rendered leaf block is byte-identical across two calls with
|
|
14
|
-
* different queries (the cache invariant).
|
|
15
|
-
* - The system prompt mentions "register" (locks the routing commitment).
|
|
16
|
-
*
|
|
17
|
-
* The provider is stubbed so no network calls fire.
|
|
18
|
-
*/
|
|
19
|
-
|
|
20
|
-
import { beforeEach, describe, expect, mock, test } from "bun:test";
|
|
21
|
-
|
|
22
|
-
import type {
|
|
23
|
-
Message,
|
|
24
|
-
Provider,
|
|
25
|
-
ProviderResponse,
|
|
26
|
-
SendMessageOptions,
|
|
27
|
-
ToolUseContent,
|
|
28
|
-
} from "../../../providers/types.js";
|
|
29
|
-
import type { LeafNode, LeafPath, LeafTree, TurnContext } from "../types.js";
|
|
30
|
-
|
|
31
|
-
// ---------------------------------------------------------------------------
|
|
32
|
-
// Mocks installed BEFORE the router import so the module observes them at
|
|
33
|
-
// load time.
|
|
34
|
-
// ---------------------------------------------------------------------------
|
|
35
|
-
|
|
36
|
-
let providerStub: Provider | null = null;
|
|
37
|
-
|
|
38
|
-
interface ProviderCall {
|
|
39
|
-
messages: Message[];
|
|
40
|
-
options: SendMessageOptions | undefined;
|
|
41
|
-
}
|
|
42
|
-
const providerCalls: ProviderCall[] = [];
|
|
43
|
-
|
|
44
|
-
mock.module("../../../providers/provider-send-message.js", () => ({
|
|
45
|
-
getConfiguredProvider: async () => providerStub,
|
|
46
|
-
extractToolUse: (response: ProviderResponse) =>
|
|
47
|
-
response.content.find((b): b is ToolUseContent => b.type === "tool_use"),
|
|
48
|
-
}));
|
|
49
|
-
|
|
50
|
-
mock.module("../../../util/logger.js", () => ({
|
|
51
|
-
getLogger: () =>
|
|
52
|
-
new Proxy({} as Record<string, unknown>, {
|
|
53
|
-
get: (_t, prop) => (prop === "child" ? () => ({}) : () => {}),
|
|
54
|
-
}),
|
|
55
|
-
}));
|
|
56
|
-
|
|
57
|
-
const { routeL1, renderLeafBlock } = await import("../router.js");
|
|
58
|
-
|
|
59
|
-
// ---------------------------------------------------------------------------
|
|
60
|
-
// Helpers.
|
|
61
|
-
// ---------------------------------------------------------------------------
|
|
62
|
-
|
|
63
|
-
function makeProvider(response: ProviderResponse): Provider {
|
|
64
|
-
return {
|
|
65
|
-
name: "stub",
|
|
66
|
-
sendMessage: async (messages, options) => {
|
|
67
|
-
providerCalls.push({ messages, options });
|
|
68
|
-
return response;
|
|
69
|
-
},
|
|
70
|
-
};
|
|
71
|
-
}
|
|
72
|
-
|
|
73
|
-
function toolUseResponse(input: Record<string, unknown>): ProviderResponse {
|
|
74
|
-
return {
|
|
75
|
-
model: "stub-model",
|
|
76
|
-
stopReason: "tool_use",
|
|
77
|
-
usage: { inputTokens: 0, outputTokens: 0 },
|
|
78
|
-
content: [{ type: "tool_use", id: "tu-1", name: "open_leaves", input }],
|
|
79
|
-
};
|
|
80
|
-
}
|
|
81
|
-
|
|
82
|
-
/** A 200 response that carries no tool_use — the malformed-but-successful case
|
|
83
|
-
* the re-prompt retry exists to recover from. */
|
|
84
|
-
function noToolResponse(): ProviderResponse {
|
|
85
|
-
return {
|
|
86
|
-
model: "stub-model",
|
|
87
|
-
stopReason: "end_turn",
|
|
88
|
-
usage: { inputTokens: 0, outputTokens: 0 },
|
|
89
|
-
content: [{ type: "text", text: "no tool call" }],
|
|
90
|
-
};
|
|
91
|
-
}
|
|
92
|
-
|
|
93
|
-
/** Provider returning a different response per call (the i-th call returns
|
|
94
|
-
* responses[i], or the last entry once exhausted), recording each call so a
|
|
95
|
-
* test can assert how many attempts were made. */
|
|
96
|
-
function makeSequenceProvider(responses: ProviderResponse[]): Provider {
|
|
97
|
-
let i = 0;
|
|
98
|
-
return {
|
|
99
|
-
name: "sequence",
|
|
100
|
-
sendMessage: async (messages, options) => {
|
|
101
|
-
providerCalls.push({ messages, options });
|
|
102
|
-
const response = responses[Math.min(i, responses.length - 1)];
|
|
103
|
-
i += 1;
|
|
104
|
-
return response;
|
|
105
|
-
},
|
|
106
|
-
};
|
|
107
|
-
}
|
|
108
|
-
|
|
109
|
-
/** Provider that records each call and then throws — for the throw-after-retries
|
|
110
|
-
* path (the provider's own RetryProvider has already exhausted its backoff). */
|
|
111
|
-
function makeThrowingProvider(): Provider {
|
|
112
|
-
return {
|
|
113
|
-
name: "throwing",
|
|
114
|
-
sendMessage: async (messages, options) => {
|
|
115
|
-
providerCalls.push({ messages, options });
|
|
116
|
-
throw new Error("boom");
|
|
117
|
-
},
|
|
118
|
-
};
|
|
119
|
-
}
|
|
120
|
-
|
|
121
|
-
function makeLeaf(path: LeafPath, description: string): LeafNode {
|
|
122
|
-
return {
|
|
123
|
-
path,
|
|
124
|
-
frontmatter: { path, in_core: false },
|
|
125
|
-
description,
|
|
126
|
-
members: [],
|
|
127
|
-
domain: path.split("/")[0],
|
|
128
|
-
};
|
|
129
|
-
}
|
|
130
|
-
|
|
131
|
-
/** Tree whose leaves are intentionally inserted out of sorted order so the
|
|
132
|
-
* tests verify the router sorts by path (not insertion order) before
|
|
133
|
-
* assigning 1-based IDs. Sorted → [people/alice, people/bob, projects/atlas]. */
|
|
134
|
-
function makeTree(): LeafTree {
|
|
135
|
-
const entries: LeafNode[] = [
|
|
136
|
-
makeLeaf("projects/atlas", "The Atlas project roadmap and status."),
|
|
137
|
-
makeLeaf("people/bob", "Bob — colleague, prefers async."),
|
|
138
|
-
makeLeaf("people/alice", "Alice — manager, weekly 1:1s."),
|
|
139
|
-
];
|
|
140
|
-
return {
|
|
141
|
-
leaves: new Map(entries.map((n) => [n.path, n])),
|
|
142
|
-
byPage: new Map(),
|
|
143
|
-
};
|
|
144
|
-
}
|
|
145
|
-
|
|
146
|
-
function makeTurn(currentMessage: string): TurnContext {
|
|
147
|
-
return {
|
|
148
|
-
conversationId: "conv-xyz",
|
|
149
|
-
turnNumber: 1,
|
|
150
|
-
currentMessage,
|
|
151
|
-
recentContext: "earlier we talked about the timeline",
|
|
152
|
-
};
|
|
153
|
-
}
|
|
154
|
-
|
|
155
|
-
beforeEach(() => {
|
|
156
|
-
providerStub = null;
|
|
157
|
-
providerCalls.length = 0;
|
|
158
|
-
});
|
|
159
|
-
|
|
160
|
-
// ---------------------------------------------------------------------------
|
|
161
|
-
// Tests.
|
|
162
|
-
// ---------------------------------------------------------------------------
|
|
163
|
-
|
|
164
|
-
describe("routeL1 — id mapping", () => {
|
|
165
|
-
test("returned IDs map to leaves by 1-based index, in model order", async () => {
|
|
166
|
-
providerStub = makeProvider(toolUseResponse({ ids: [3, 1] }));
|
|
167
|
-
const result = await routeL1(makeTurn("how's atlas?"), makeTree());
|
|
168
|
-
expect(result).toEqual(["projects/atlas", "people/alice"]);
|
|
169
|
-
});
|
|
170
|
-
|
|
171
|
-
test("omitted ids opens no leaves (must name leaves explicitly)", async () => {
|
|
172
|
-
providerStub = makeProvider(toolUseResponse({}));
|
|
173
|
-
const result = await routeL1(makeTurn("anything"), makeTree());
|
|
174
|
-
expect(result).toEqual([]);
|
|
175
|
-
});
|
|
176
|
-
|
|
177
|
-
test("explicit empty ids opens no leaves (abstention)", async () => {
|
|
178
|
-
providerStub = makeProvider(toolUseResponse({ ids: [] }));
|
|
179
|
-
const result = await routeL1(makeTurn("nothing relevant"), makeTree());
|
|
180
|
-
expect(result).toEqual([]);
|
|
181
|
-
});
|
|
182
|
-
|
|
183
|
-
test("out-of-range and duplicate IDs are ignored without throwing", async () => {
|
|
184
|
-
providerStub = makeProvider(toolUseResponse({ ids: [2, 99, 0, -1, 2] }));
|
|
185
|
-
const result = await routeL1(makeTurn("bob?"), makeTree());
|
|
186
|
-
expect(result).toEqual(["people/bob"]);
|
|
187
|
-
});
|
|
188
|
-
|
|
189
|
-
test("empty tree returns no leaves and never calls the provider", async () => {
|
|
190
|
-
providerStub = makeProvider(toolUseResponse({ ids: [1] }));
|
|
191
|
-
const result = await routeL1(makeTurn("hi"), {
|
|
192
|
-
leaves: new Map(),
|
|
193
|
-
byPage: new Map(),
|
|
194
|
-
});
|
|
195
|
-
expect(result).toEqual([]);
|
|
196
|
-
expect(providerCalls).toHaveLength(0);
|
|
197
|
-
});
|
|
198
|
-
});
|
|
199
|
-
|
|
200
|
-
describe("routeL1 — degradation on failure", () => {
|
|
201
|
-
test("no provider → no leaves, without calling the provider", async () => {
|
|
202
|
-
providerStub = null;
|
|
203
|
-
const result = await routeL1(makeTurn("x"), makeTree());
|
|
204
|
-
expect(result).toEqual([]);
|
|
205
|
-
expect(providerCalls).toHaveLength(0);
|
|
206
|
-
});
|
|
207
|
-
|
|
208
|
-
test("missing tool_use → no leaves after retrying", async () => {
|
|
209
|
-
providerStub = makeProvider(noToolResponse());
|
|
210
|
-
const result = await routeL1(makeTurn("x"), makeTree());
|
|
211
|
-
expect(result).toEqual([]);
|
|
212
|
-
expect(providerCalls).toHaveLength(3);
|
|
213
|
-
});
|
|
214
|
-
|
|
215
|
-
test("schema mismatch → no leaves after retrying", async () => {
|
|
216
|
-
providerStub = makeProvider(toolUseResponse({ ids: "not-an-array" }));
|
|
217
|
-
const result = await routeL1(makeTurn("x"), makeTree());
|
|
218
|
-
expect(result).toEqual([]);
|
|
219
|
-
expect(providerCalls).toHaveLength(3);
|
|
220
|
-
});
|
|
221
|
-
|
|
222
|
-
test("provider throw → no leaves after retrying", async () => {
|
|
223
|
-
providerStub = makeThrowingProvider();
|
|
224
|
-
const result = await routeL1(makeTurn("x"), makeTree());
|
|
225
|
-
expect(result).toEqual([]);
|
|
226
|
-
expect(providerCalls).toHaveLength(3);
|
|
227
|
-
});
|
|
228
|
-
|
|
229
|
-
test("a malformed response that recovers on retry returns its IDs", async () => {
|
|
230
|
-
providerStub = makeSequenceProvider([
|
|
231
|
-
noToolResponse(),
|
|
232
|
-
toolUseResponse({ ids: [2] }),
|
|
233
|
-
]);
|
|
234
|
-
const result = await routeL1(makeTurn("bob?"), makeTree());
|
|
235
|
-
expect(result).toEqual(["people/bob"]);
|
|
236
|
-
expect(providerCalls).toHaveLength(2);
|
|
237
|
-
});
|
|
238
|
-
});
|
|
239
|
-
|
|
240
|
-
describe("routeL1 — request shape", () => {
|
|
241
|
-
test("forces tool_choice to open_leaves with the v3 call site", async () => {
|
|
242
|
-
providerStub = makeProvider(toolUseResponse({ ids: [1] }));
|
|
243
|
-
await routeL1(makeTurn("alice?"), makeTree());
|
|
244
|
-
|
|
245
|
-
expect(providerCalls).toHaveLength(1);
|
|
246
|
-
const [call] = providerCalls;
|
|
247
|
-
const cfg = call.options?.config as Record<string, unknown>;
|
|
248
|
-
expect(cfg?.callSite).toBe("memoryV3RouteL1");
|
|
249
|
-
expect(cfg?.tool_choice).toEqual({ type: "tool", name: "open_leaves" });
|
|
250
|
-
expect(call.options?.tools?.[0]?.name).toBe("open_leaves");
|
|
251
|
-
});
|
|
252
|
-
|
|
253
|
-
test("leaf block is the first content block with an ephemeral cache breakpoint", async () => {
|
|
254
|
-
providerStub = makeProvider(toolUseResponse({ ids: [1] }));
|
|
255
|
-
await routeL1(makeTurn("alice?"), makeTree());
|
|
256
|
-
|
|
257
|
-
const [blockA, blockB] = providerCalls[0].messages[0].content as Array<{
|
|
258
|
-
type: string;
|
|
259
|
-
text: string;
|
|
260
|
-
cache_control?: { type: string; ttl?: string };
|
|
261
|
-
}>;
|
|
262
|
-
expect(blockA.type).toBe("text");
|
|
263
|
-
expect(blockA.text).toContain("<leaves>");
|
|
264
|
-
expect(blockA.cache_control).toEqual({ type: "ephemeral", ttl: "1h" });
|
|
265
|
-
|
|
266
|
-
expect(blockB.type).toBe("text");
|
|
267
|
-
expect(blockB.text).toContain("<current_message>alice?</current_message>");
|
|
268
|
-
expect(blockB.text).toContain("<recent_context>");
|
|
269
|
-
expect(blockB.cache_control).toBeUndefined();
|
|
270
|
-
});
|
|
271
|
-
|
|
272
|
-
test("situational context renders in the per-turn block when present", async () => {
|
|
273
|
-
providerStub = makeProvider(toolUseResponse({ ids: [1] }));
|
|
274
|
-
await routeL1(
|
|
275
|
-
{
|
|
276
|
-
...makeTurn("x"),
|
|
277
|
-
situationalContext: "Today is Saturday. Alice's anniversary is today.",
|
|
278
|
-
},
|
|
279
|
-
makeTree(),
|
|
280
|
-
);
|
|
281
|
-
const blockB = providerCalls[0].messages[0].content[1] as { text: string };
|
|
282
|
-
expect(blockB.text).toContain(
|
|
283
|
-
"<situation>Today is Saturday. Alice's anniversary is today.</situation>",
|
|
284
|
-
);
|
|
285
|
-
});
|
|
286
|
-
|
|
287
|
-
test("situational context is omitted when the turn has none", async () => {
|
|
288
|
-
providerStub = makeProvider(toolUseResponse({ ids: [1] }));
|
|
289
|
-
await routeL1(makeTurn("x"), makeTree());
|
|
290
|
-
const blockB = providerCalls[0].messages[0].content[1] as { text: string };
|
|
291
|
-
expect(blockB.text).not.toContain("<situation>");
|
|
292
|
-
});
|
|
293
|
-
|
|
294
|
-
test("system prompt mentions situation (locks the routing commitment)", async () => {
|
|
295
|
-
providerStub = makeProvider(toolUseResponse({ ids: [1] }));
|
|
296
|
-
await routeL1(makeTurn("x"), makeTree());
|
|
297
|
-
expect(providerCalls[0].options?.systemPrompt).toMatch(/[Ss]ituation/);
|
|
298
|
-
});
|
|
299
|
-
|
|
300
|
-
test("system prompt mentions register (locks the routing commitment)", async () => {
|
|
301
|
-
providerStub = makeProvider(toolUseResponse({ ids: [1] }));
|
|
302
|
-
await routeL1(makeTurn("x"), makeTree());
|
|
303
|
-
expect(providerCalls[0].options?.systemPrompt).toMatch(/register/);
|
|
304
|
-
});
|
|
305
|
-
});
|
|
306
|
-
|
|
307
|
-
describe("renderLeafBlock — cache invariant", () => {
|
|
308
|
-
test("is byte-identical across two calls with different queries", async () => {
|
|
309
|
-
const tree = makeTree();
|
|
310
|
-
providerStub = makeProvider(toolUseResponse({ ids: [1] }));
|
|
311
|
-
|
|
312
|
-
await routeL1(makeTurn("first query about alice"), tree);
|
|
313
|
-
await routeL1(makeTurn("totally different query about atlas"), tree);
|
|
314
|
-
|
|
315
|
-
const prefixA = (
|
|
316
|
-
providerCalls[0].messages[0].content[0] as { text: string }
|
|
317
|
-
).text;
|
|
318
|
-
const prefixB = (
|
|
319
|
-
providerCalls[1].messages[0].content[0] as { text: string }
|
|
320
|
-
).text;
|
|
321
|
-
expect(prefixA).toBe(prefixB);
|
|
322
|
-
// And it equals the pure renderer's output.
|
|
323
|
-
expect(prefixA).toBe(renderLeafBlock(tree));
|
|
324
|
-
});
|
|
325
|
-
|
|
326
|
-
test("renders leaves sorted by path with 1-based numbering", () => {
|
|
327
|
-
const block = renderLeafBlock(makeTree());
|
|
328
|
-
expect(block).toContain("[1] people/alice — ");
|
|
329
|
-
expect(block).toContain("[2] people/bob — ");
|
|
330
|
-
expect(block).toContain("[3] projects/atlas — ");
|
|
331
|
-
});
|
|
332
|
-
});
|