@vellumai/assistant 0.8.4 → 0.8.5
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 +2 -2
- package/docs/browser-use-architecture-phase2.md +1 -1
- package/knip.json +2 -1
- package/openapi.yaml +809 -11
- package/package.json +1 -1
- package/src/__tests__/anthropic-provider.test.ts +34 -37
- package/src/__tests__/assistant-event-hub-self-exclusion.test.ts +293 -0
- package/src/__tests__/assistant-feature-flags-integration.test.ts +3 -3
- package/src/__tests__/audit-log-rotation.test.ts +70 -16
- package/src/__tests__/background-workers-disk-pressure.test.ts +3 -3
- package/src/__tests__/btw-routes.test.ts +2 -3
- package/src/__tests__/call-controller.test.ts +0 -1
- package/src/__tests__/cancel-resolves-conversation-key.test.ts +1 -1
- package/src/__tests__/channel-guardian.test.ts +3 -3
- package/src/__tests__/checker.test.ts +6 -15
- package/src/__tests__/compaction-events.test.ts +1 -0
- package/src/__tests__/compactor-call-site-logging.test.ts +214 -0
- package/src/__tests__/computer-use-skill-manifest-regression.test.ts +5 -11
- package/src/__tests__/computer-use-tools.test.ts +2 -4
- package/src/__tests__/confirmation-request-guardian-bridge.test.ts +0 -1
- package/src/__tests__/conversation-agent-loop-disk-pressure.test.ts +1 -1
- package/src/__tests__/conversation-agent-loop-inference-profile.test.ts +1 -1
- package/src/__tests__/conversation-agent-loop-overflow.test.ts +197 -2
- package/src/__tests__/conversation-agent-loop.test.ts +163 -122
- package/src/__tests__/conversation-app-control-instantiation.test.ts +2 -5
- package/src/__tests__/conversation-clear-safety.test.ts +25 -25
- package/src/__tests__/conversation-delete-schedule-cleanup.test.ts +1 -1
- package/src/__tests__/conversation-disk-view-integration.test.ts +2 -2
- package/src/__tests__/conversation-error.test.ts +31 -0
- package/src/__tests__/conversation-fork-crud.test.ts +178 -15
- package/src/__tests__/conversation-lifecycle.test.ts +52 -11
- package/src/__tests__/{conversation-load-cleaned-at.test.ts → conversation-load-history-stripped.test.ts} +13 -13
- package/src/__tests__/conversation-provider-retry-repair.test.ts +1 -0
- package/src/__tests__/conversation-routes-disk-view.test.ts +109 -0
- package/src/__tests__/conversation-routes-slash-commands.test.ts +35 -0
- package/src/__tests__/conversation-skill-tools.test.ts +2 -5
- package/src/__tests__/conversation-store.test.ts +1 -1
- package/src/__tests__/conversation-sync-tags.test.ts +99 -32
- package/src/__tests__/conversation-workspace-cache-state.test.ts +1 -0
- package/src/__tests__/conversation-workspace-injection.test.ts +1 -1
- package/src/__tests__/conversation-workspace-tool-tracking.test.ts +1 -1
- package/src/__tests__/credential-execution-feature-gates.test.ts +9 -7
- package/src/__tests__/credential-execution-tools.test.ts +6 -6
- package/src/__tests__/credential-security-invariants.test.ts +1 -0
- package/src/__tests__/credential-vault-unit.test.ts +2 -2
- package/src/__tests__/dynamic-page-surface.test.ts +2 -2
- package/src/__tests__/email-html-renderer.test.ts +12 -0
- package/src/__tests__/gateway-flag-listener.test.ts +237 -0
- package/src/__tests__/gemini-provider.test.ts +78 -0
- package/src/__tests__/guardian-dispatch.test.ts +0 -1
- package/src/__tests__/guardian-outbound-http.test.ts +7 -5
- package/src/__tests__/handlers-user-message-approval-consumption.test.ts +1 -1
- package/src/__tests__/heartbeat-disk-pressure.test.ts +4 -0
- package/src/__tests__/heartbeat-service.test.ts +4 -0
- package/src/__tests__/host-shell-tool.test.ts +1 -1
- package/src/__tests__/init-feature-flag-overrides.test.ts +5 -6
- package/src/__tests__/list-messages-tool-merge.test.ts +70 -11
- package/src/__tests__/llm-request-log-call-site.test.ts +136 -0
- package/src/__tests__/llm-request-log-source-clickhouse.test.ts +26 -0
- package/src/__tests__/llm-resolver.test.ts +77 -9
- package/src/__tests__/llm-usage-store.test.ts +66 -0
- package/src/__tests__/logger.test.ts +89 -0
- package/src/__tests__/mcp-abort-signal.test.ts +2 -2
- package/src/__tests__/media-generate-image.test.ts +31 -0
- package/src/__tests__/memory-v2-static-injector.test.ts +7 -7
- package/src/__tests__/model-intents.test.ts +2 -4
- package/src/__tests__/notification-guardian-path.test.ts +0 -1
- package/src/__tests__/onboarding-template-contract.test.ts +1 -1
- package/src/__tests__/openai-provider.test.ts +46 -0
- package/src/__tests__/openai-responses-provider.test.ts +114 -12
- package/src/__tests__/pending-interactions-resolved-event.test.ts +0 -1
- package/src/__tests__/platform-bash-auto-approve.test.ts +2 -2
- package/src/__tests__/platform.test.ts +2 -2
- package/src/__tests__/plugin-api-tool-definition.test.ts +92 -0
- package/src/__tests__/plugin-bootstrap.test.ts +2 -2
- package/src/__tests__/plugin-tool-contribution.test.ts +13 -6
- package/src/__tests__/plugin-types.test.ts +3 -2
- package/src/__tests__/prechat-onboarding-contract.test.ts +131 -98
- package/src/__tests__/pricing.test.ts +12 -0
- package/src/__tests__/prune-jobs-changes-parser.test.ts +61 -0
- package/src/__tests__/registry.test.ts +2 -8
- package/src/__tests__/require-fresh-approval.test.ts +2 -2
- package/src/__tests__/runtime-events-sse-bilingual.test.ts +154 -0
- package/src/__tests__/shell-tool-proxy-mode.test.ts +1 -1
- package/src/__tests__/skill-feature-flags.test.ts +2 -2
- package/src/__tests__/skill-projection-feature-flag.test.ts +4 -7
- package/src/__tests__/skill-projection.benchmark.test.ts +2 -6
- package/src/__tests__/skill-tool-factory.test.ts +1 -1
- package/src/__tests__/subagent-notify-parent.test.ts +1 -1
- package/src/__tests__/suggestion-routes.test.ts +1 -0
- package/src/__tests__/sync-message-contract.test.ts +59 -0
- package/src/__tests__/system-prompt.test.ts +145 -131
- package/src/__tests__/terminal-tools.test.ts +1 -1
- package/src/__tests__/tool-approval-handler.test.ts +1 -5
- package/src/__tests__/tool-execute-pipeline.test.ts +2 -2
- package/src/__tests__/tool-execution-pipeline.benchmark.test.ts +2 -5
- package/src/__tests__/tool-executor-lifecycle-events.test.ts +15 -5
- package/src/__tests__/tool-executor.test.ts +9 -62
- package/src/__tests__/tool-grant-request-escalation.test.ts +1 -6
- package/src/__tests__/trusted-contact-approval-notifier.test.ts +0 -1
- package/src/__tests__/trusted-contact-inline-approval-integration.test.ts +1 -6
- package/src/__tests__/trusted-contact-multichannel.test.ts +0 -1
- package/src/__tests__/ui-file-upload-surface.test.ts +2 -2
- package/src/__tests__/usage-routes.test.ts +3 -0
- package/src/__tests__/verification-control-plane-policy.test.ts +2 -2
- package/src/__tests__/workspace-git-service.test.ts +6 -5
- package/src/__tests__/workspace-migration-089-move-memory-tree-out-of-v3.test.ts +86 -0
- package/src/acp/__tests__/prepare-agent-env.test.ts +146 -0
- package/src/acp/prepare-agent-env.ts +78 -0
- package/src/acp/session-manager.ts +1 -1
- package/src/agent/loop.ts +8 -0
- package/src/api/README.md +5 -0
- package/src/api/index.ts +4 -0
- package/src/api/package.json +10 -0
- package/src/background-wake/background-wake-routes.test.ts +233 -0
- package/src/background-wake/runtime-registry.ts +24 -0
- package/src/cli/commands/__tests__/browser.test.ts +23 -5
- package/src/cli/commands/__tests__/domain-register.test.ts +110 -0
- package/src/cli/commands/__tests__/domain-status.test.ts +33 -33
- package/src/cli/commands/__tests__/inference-send.test.ts +108 -5
- package/src/cli/commands/__tests__/memory-v2-compare-render.test.ts +98 -0
- package/src/cli/commands/__tests__/memory-v2.test.ts +1 -0
- package/src/cli/commands/__tests__/memory-v3-render.test.ts +340 -0
- package/src/cli/commands/browser.ts +247 -0
- package/src/cli/commands/domain.ts +91 -41
- package/src/cli/commands/inference.ts +93 -40
- package/src/cli/commands/memory-v2-compare-render.ts +115 -0
- package/src/cli/commands/memory-v2.ts +176 -1
- package/src/cli/commands/memory-v3-render.ts +344 -0
- package/src/cli/commands/memory-v3.ts +316 -0
- package/src/cli/program.ts +2 -0
- package/src/config/assistant-feature-flags.ts +21 -9
- package/src/config/bundled-skills/document-editor/SKILL.md +11 -2
- package/src/config/bundled-skills/document-editor/TOOLS.json +18 -0
- package/src/config/bundled-skills/document-editor/tools/document-open.ts +12 -0
- package/src/config/bundled-skills/image-studio/SKILL.md +4 -0
- package/src/config/bundled-skills/image-studio/tools/media-generate-image.ts +2 -2
- package/src/config/bundled-skills/media-processing/tools/ingest-media.ts +13 -8
- package/src/config/bundled-skills/messaging/tools/messaging-analyze-style.ts +10 -3
- package/src/config/bundled-skills/phone-calls/references/TRANSCRIPTS.md +16 -14
- package/src/config/bundled-skills/playbooks/tools/playbook-create.ts +7 -2
- package/src/config/bundled-skills/playbooks/tools/playbook-update.ts +7 -2
- package/src/config/bundled-tool-registry.ts +2 -0
- package/src/config/call-site-defaults.ts +7 -6
- package/src/config/feature-flag-registry.json +16 -0
- package/src/config/schemas/__tests__/memory-v2.test.ts +213 -1
- package/src/config/schemas/call-site-catalog.ts +21 -7
- package/src/config/schemas/llm.ts +12 -1
- package/src/config/schemas/memory-v2.ts +246 -0
- package/src/config/schemas/memory.ts +2 -1
- package/src/context/compactor.ts +52 -0
- package/src/conversations/__tests__/message-consolidation.test.ts +350 -0
- package/src/conversations/message-consolidation.ts +404 -0
- package/src/daemon/__tests__/conversation-tool-setup-exclude.test.ts +1 -1
- package/src/daemon/__tests__/meet-manifest-loader.test.ts +1 -1
- package/src/daemon/conversation-agent-loop-handlers.ts +2 -13
- package/src/daemon/conversation-agent-loop.ts +126 -76
- package/src/daemon/conversation-error.ts +31 -1
- package/src/daemon/conversation-lifecycle.ts +27 -22
- package/src/daemon/conversation-runtime-assembly.ts +10 -9
- package/src/daemon/conversation-tool-setup.ts +63 -3
- package/src/daemon/conversation-usage.ts +2 -0
- package/src/daemon/conversation.ts +14 -29
- package/src/daemon/disk-pressure-guard.ts +14 -2
- package/src/daemon/handlers/config-model.test.ts +1 -0
- package/src/daemon/handlers/conversations.ts +11 -3
- package/src/daemon/host-browser-proxy.ts +5 -5
- package/src/daemon/host-cu-proxy.ts +4 -4
- package/src/daemon/host-file-proxy.ts +4 -4
- package/src/daemon/host-proxy-base.ts +4 -4
- package/src/daemon/host-transfer-proxy.ts +10 -10
- package/src/daemon/lifecycle.ts +23 -20
- package/src/daemon/meet-manifest-loader.ts +1 -7
- package/src/daemon/message-types/conversations.ts +6 -9
- package/src/daemon/message-types/home.ts +1 -13
- package/src/daemon/message-types/messages.ts +6 -14
- package/src/daemon/message-types/sync.ts +14 -0
- package/src/daemon/shutdown-handlers.ts +24 -5
- package/src/daemon/switch-inference-profile-tool.ts +52 -0
- package/src/daemon/tool-setup-types.ts +13 -0
- package/src/events/relationship-state-updated.ts +25 -0
- package/src/heartbeat/__tests__/heartbeat-service.test.ts +1 -1
- package/src/home/home-greeting.ts +0 -9
- package/src/home/suggested-prompts.ts +0 -9
- package/src/ipc/gateway-flag-listener.ts +123 -0
- package/src/ipc/skill-routes/registries.ts +8 -12
- package/src/memory/__tests__/db-async-query.test.ts +165 -0
- package/src/memory/__tests__/db-maintenance.test.ts +115 -0
- package/src/memory/__tests__/jobs-store-enqueue-gate.test.ts +241 -0
- package/src/memory/__tests__/jobs-store-job-classes.test.ts +28 -1
- package/src/memory/__tests__/memory-retrospective-job.test.ts +7 -0
- package/src/memory/auto-analysis-enqueue.ts +5 -1
- package/src/memory/conversation-crud.ts +71 -70
- package/src/memory/conversation-starters-cadence.ts +3 -1
- package/src/memory/conversation-title-service.ts +19 -3
- package/src/memory/db-async-query.ts +214 -0
- package/src/memory/db-init.ts +10 -0
- package/src/memory/db-maintenance.ts +30 -21
- package/src/memory/graph/bootstrap.ts +8 -1
- package/src/memory/graph/capability-seed.ts +7 -3
- package/src/memory/graph/conversation-graph-memory.ts +100 -17
- package/src/memory/graph/extraction.ts +1 -5
- package/src/memory/graph/graph-search.ts +7 -1
- package/src/memory/indexer.ts +28 -18
- package/src/memory/job-handlers/cleanup.ts +76 -18
- package/src/memory/job-handlers/conversation-starters.ts +1 -4
- package/src/memory/jobs/embed-pkb-file.ts +6 -1
- package/src/memory/jobs-store.ts +14 -0
- package/src/memory/jobs-worker.ts +55 -22
- package/src/memory/llm-request-log-source-clickhouse.ts +42 -2
- package/src/memory/llm-request-log-source-local.ts +7 -0
- package/src/memory/llm-request-log-source.ts +9 -2
- package/src/memory/llm-request-log-store.ts +43 -1
- package/src/memory/llm-usage-store.ts +24 -0
- package/src/memory/memory-retrospective-enqueue.ts +8 -1
- package/src/memory/memory-retrospective-job.ts +5 -0
- package/src/memory/memory-v2-activation-log-store.ts +15 -6
- package/src/memory/migrations/260-rename-cleaned-at.ts +44 -0
- package/src/memory/migrations/261-llm-usage-add-raw-usage.ts +36 -0
- package/src/memory/migrations/262-memory-v3-coactivation.ts +57 -0
- package/src/memory/migrations/263-memory-v3-auto-edges.ts +50 -0
- package/src/memory/migrations/264-llm-request-log-call-site.ts +29 -0
- package/src/memory/migrations/index.ts +17 -0
- package/src/memory/migrations/registry.ts +33 -0
- package/src/memory/schema/conversations.ts +1 -1
- package/src/memory/schema/infrastructure.ts +21 -0
- package/src/memory/tool-usage-store.ts +36 -8
- package/src/memory/v2/__tests__/consolidation-job.test.ts +1 -0
- package/src/memory/v2/__tests__/harness-compare.test.ts +186 -0
- package/src/memory/v2/__tests__/harness-metrics.test.ts +74 -0
- package/src/memory/v2/__tests__/harness-oracle.test.ts +257 -0
- package/src/memory/v2/__tests__/harness-replay-input.test.ts +225 -0
- package/src/memory/v2/__tests__/harness-runner.test.ts +109 -0
- package/src/memory/v2/__tests__/injection.test.ts +127 -98
- package/src/memory/v2/__tests__/qdrant.test.ts +36 -0
- package/src/memory/v2/__tests__/router.test.ts +171 -3
- package/src/memory/v2/harness/compare.ts +57 -0
- package/src/memory/v2/harness/metrics.ts +124 -0
- package/src/memory/v2/harness/oracle.ts +145 -0
- package/src/memory/v2/harness/replay-input.ts +224 -0
- package/src/memory/v2/harness/retriever.ts +74 -0
- package/src/memory/v2/harness/router-retriever.ts +43 -0
- package/src/memory/v2/harness/runner.ts +106 -0
- package/src/memory/v2/harness/trace.ts +58 -0
- package/src/memory/v2/injection.ts +21 -15
- package/src/memory/v2/prompts/router.ts +26 -1
- package/src/memory/v2/qdrant.ts +14 -2
- package/src/memory/v2/router.ts +171 -18
- package/src/memory/v3/__tests__/coactivation-store.test.ts +422 -0
- package/src/memory/v3/__tests__/consolidation-job.test.ts +468 -0
- package/src/memory/v3/__tests__/edge-learning-job.test.ts +324 -0
- package/src/memory/v3/__tests__/edges.test.ts +563 -0
- package/src/memory/v3/__tests__/filter.test.ts +512 -0
- package/src/memory/v3/__tests__/gate.test.ts +574 -0
- package/src/memory/v3/__tests__/index-composition.test.ts +233 -0
- package/src/memory/v3/__tests__/loop.test.ts +530 -0
- package/src/memory/v3/__tests__/retriever.test.ts +226 -0
- package/src/memory/v3/__tests__/scouts.test.ts +440 -0
- package/src/memory/v3/__tests__/shadow-middleware.test.ts +312 -0
- package/src/memory/v3/__tests__/system-prompts.test.ts +154 -0
- package/src/memory/v3/__tests__/traversal.test.ts +469 -0
- package/src/memory/v3/__tests__/tree-index.test.ts +280 -0
- package/src/memory/v3/__tests__/tree-store.test.ts +529 -0
- package/src/memory/v3/__tests__/tree-walk.test.ts +707 -0
- package/src/memory/v3/__tests__/validate.test.ts +245 -0
- package/src/memory/v3/auto-edges.ts +223 -0
- package/src/memory/v3/coactivation-store.ts +124 -0
- package/src/memory/v3/consolidation-job.ts +323 -0
- package/src/memory/v3/edge-learning-job.ts +160 -0
- package/src/memory/v3/edges.ts +249 -0
- package/src/memory/v3/filter.ts +281 -0
- package/src/memory/v3/gate.ts +334 -0
- package/src/memory/v3/index-composition.ts +113 -0
- package/src/memory/v3/llm-capture.ts +46 -0
- package/src/memory/v3/loop.ts +382 -0
- package/src/memory/v3/maintenance.ts +144 -0
- package/src/memory/v3/prompt-context.ts +33 -0
- package/src/memory/v3/prompts/consolidation.ts +458 -0
- package/src/memory/v3/prompts/system-prompts.ts +196 -0
- package/src/memory/v3/retriever.ts +33 -0
- package/src/memory/v3/scouts.ts +420 -0
- package/src/memory/v3/shadow-middleware.ts +305 -0
- package/src/memory/v3/traversal.ts +206 -0
- package/src/memory/v3/tree-index.ts +237 -0
- package/src/memory/v3/tree-store.ts +394 -0
- package/src/memory/v3/tree-walk.ts +351 -0
- package/src/memory/v3/types.ts +65 -0
- package/src/memory/v3/validate.ts +300 -0
- package/src/notifications/adapters/macos.ts +18 -1
- package/src/notifications/adapters/platform.ts +1 -1
- package/src/notifications/decision-engine.ts +1 -4
- package/src/notifications/emit-signal.ts +29 -49
- package/src/permissions/prompter.ts +3 -3
- package/src/permissions/question-prompter.ts +5 -2
- package/src/permissions/secret-prompter.ts +2 -2
- package/src/plugin-api/index.ts +4 -0
- package/src/plugin-api/types.ts +7 -33
- package/src/plugins/defaults/index.ts +6 -0
- package/src/plugins/defaults/injectors.ts +18 -11
- package/src/plugins/external-plugin-loader.ts +5 -68
- package/src/plugins/types.ts +11 -16
- package/src/proactive-artifact/aux-message-injector.ts +17 -4
- package/src/prompts/__tests__/task-progress-hint-section.test.ts +3 -9
- package/src/prompts/persona-resolver.ts +36 -21
- package/src/prompts/sections.ts +39 -7
- package/src/prompts/system-prompt.ts +50 -185
- package/src/prompts/templates/BOOTSTRAP.md +2 -2
- package/src/prompts/templates/system-sections.ts +230 -8
- package/src/providers/__tests__/connection-model-compat.test.ts +234 -0
- package/src/providers/__tests__/retry-callsite.test.ts +85 -5
- package/src/providers/anthropic/client.ts +32 -66
- package/src/providers/call-site-routing.ts +14 -2
- package/src/providers/connection-model-compat.ts +38 -0
- package/src/providers/connection-resolution.ts +16 -2
- package/src/providers/gemini/client.ts +49 -6
- package/src/providers/inference/adapter-factory.ts +3 -0
- package/src/providers/minimax/client.ts +106 -0
- package/src/providers/model-catalog.ts +43 -0
- package/src/providers/model-intents.ts +1 -1
- package/src/providers/openai/chat-completions-provider.ts +6 -3
- package/src/providers/openai/codex-models.ts +18 -0
- package/src/providers/openai/responses-provider.ts +78 -21
- package/src/providers/provider-send-message.ts +7 -1
- package/src/providers/retry.ts +34 -3
- package/src/providers/thinking-config.ts +26 -1
- package/src/providers/usage-tracking.ts +2 -0
- package/src/runtime/AGENTS.md +2 -2
- package/src/runtime/agent-wake.ts +1 -0
- package/src/runtime/assistant-event-hub.ts +76 -6
- package/src/runtime/auth/route-policy.ts +36 -0
- package/src/runtime/btw-sidechain.ts +0 -6
- package/src/runtime/http-types.ts +0 -2
- package/src/runtime/migrations/vbundle-builder.ts +10 -3
- package/src/runtime/pending-interactions.ts +0 -1
- package/src/runtime/routes/__tests__/conversation-query-routes.test.ts +106 -0
- package/src/runtime/routes/__tests__/memory-v2-simulate-route.test.ts +25 -6
- package/src/runtime/routes/__tests__/plugins-routes.test.ts +512 -0
- package/src/runtime/routes/acp-routes.test.ts +255 -6
- package/src/runtime/routes/acp-routes.ts +8 -1
- package/src/runtime/routes/avatar-routes.ts +10 -10
- package/src/runtime/routes/background-wake-routes.ts +188 -0
- package/src/runtime/routes/browser-tabs-routes.ts +200 -0
- package/src/runtime/routes/btw-routes.ts +0 -6
- package/src/runtime/routes/conversation-cli-routes.ts +1 -1
- package/src/runtime/routes/conversation-list-routes.ts +12 -4
- package/src/runtime/routes/conversation-management-routes.ts +77 -20
- package/src/runtime/routes/conversation-query-routes.ts +142 -36
- package/src/runtime/routes/conversation-routes.ts +252 -410
- package/src/runtime/routes/conversation-starter-routes.ts +6 -3
- package/src/runtime/routes/disk-pressure-routes.ts +1 -1
- package/src/runtime/routes/domain-routes.ts +60 -10
- package/src/runtime/routes/email-routes.ts +5 -2
- package/src/runtime/routes/events-routes.ts +54 -10
- package/src/runtime/routes/group-routes.ts +24 -8
- package/src/runtime/routes/host-browser-routes.ts +10 -2
- package/src/runtime/routes/host-cu-routes.ts +2 -2
- package/src/runtime/routes/inbound-stages/acl-enforcement.ts +96 -3
- package/src/runtime/routes/index.ts +8 -0
- package/src/runtime/routes/inference-profile-session-handler.ts +22 -12
- package/src/runtime/routes/inference-profile-session-routes.ts +7 -1
- package/src/runtime/routes/llm-call-sites-routes.ts +32 -5
- package/src/runtime/routes/memory-item-routes.ts +8 -3
- package/src/runtime/routes/memory-v2-routes.ts +215 -5
- package/src/runtime/routes/memory-v3-routes.ts +316 -0
- package/src/runtime/routes/migration-routes.ts +21 -24
- package/src/runtime/routes/plugins-routes.ts +337 -0
- package/src/runtime/routes/rename-conversation-routes.ts +6 -2
- package/src/runtime/routes/secret-routes.ts +25 -5
- package/src/runtime/routes/settings-routes.ts +12 -11
- package/src/runtime/routes/slack-channel-routes.ts +5 -4
- package/src/runtime/routes/workspace-routes.ts +25 -10
- package/src/runtime/sync/resource-sync-events.ts +106 -38
- package/src/runtime/sync/sync-publisher.test.ts +49 -0
- package/src/runtime/sync/sync-publisher.ts +2 -1
- package/src/runtime/verification-outbound-actions.ts +73 -1
- package/src/telemetry/types.ts +12 -0
- package/src/telemetry/usage-telemetry-reporter.test.ts +48 -0
- package/src/telemetry/usage-telemetry-reporter.ts +1 -0
- package/src/tools/acp/spawn.test.ts +119 -0
- package/src/tools/acp/spawn.ts +15 -2
- package/src/tools/apps/definitions.ts +2 -8
- package/src/tools/ask-question/ask-question-tool.test.ts +3 -3
- package/src/tools/ask-question/ask-question-tool.ts +38 -45
- package/src/tools/browser/__tests__/pinned-tabs.test.ts +70 -0
- package/src/tools/browser/browser-execution.ts +16 -3
- package/src/tools/browser/cdp-client/__tests__/browser-tabs-factory.test.ts +402 -0
- package/src/tools/browser/cdp-client/__tests__/types.test.ts +3 -0
- package/src/tools/browser/cdp-client/cdp-inspect-client.ts +12 -0
- package/src/tools/browser/cdp-client/extension-cdp-client.ts +27 -1
- package/src/tools/browser/cdp-client/factory.ts +100 -17
- package/src/tools/browser/cdp-client/local-cdp-client.ts +12 -0
- package/src/tools/browser/cdp-client/types.ts +65 -0
- package/src/tools/browser/pinned-tabs.ts +96 -40
- package/src/tools/computer-use/definitions.ts +22 -78
- package/src/tools/credential-execution/make-authenticated-request.ts +3 -9
- package/src/tools/credential-execution/manage-secure-command-tool.ts +3 -9
- package/src/tools/credential-execution/run-authenticated-command.ts +3 -9
- package/src/tools/credentials/vault.ts +3 -9
- package/src/tools/document/document-tool.ts +59 -0
- package/src/tools/execution-target.ts +21 -23
- package/src/tools/executor.ts +6 -1
- package/src/tools/filesystem/edit.ts +3 -9
- package/src/tools/filesystem/list.ts +3 -9
- package/src/tools/filesystem/read.ts +3 -9
- package/src/tools/filesystem/write.ts +3 -9
- package/src/tools/host-filesystem/edit.ts +3 -9
- package/src/tools/host-filesystem/read.ts +3 -9
- package/src/tools/host-filesystem/transfer.ts +3 -9
- package/src/tools/host-filesystem/write.ts +3 -9
- package/src/tools/host-terminal/host-shell.ts +3 -9
- package/src/tools/mcp/mcp-tool-factory.ts +1 -8
- package/src/tools/memory/register.test.ts +1 -1
- package/src/tools/memory/register.ts +4 -9
- package/src/tools/network/web-fetch.ts +3 -9
- package/src/tools/network/web-search.ts +25 -32
- package/src/tools/registry.ts +7 -23
- package/src/tools/schema-transforms.ts +1 -1
- package/src/tools/skills/execute.ts +3 -9
- package/src/tools/skills/load.ts +3 -9
- package/src/tools/skills/skill-tool-factory.ts +1 -8
- package/src/tools/subagent/notify-parent.ts +3 -9
- package/src/tools/system/request-permission.ts +3 -9
- package/src/tools/terminal/shell.ts +3 -9
- package/src/tools/tool-defaults.ts +94 -0
- package/src/tools/types.ts +27 -98
- package/src/tools/ui-surface/definitions.ts +6 -22
- package/src/usage/pricing.ts +23 -0
- package/src/usage/types.ts +12 -0
- package/src/util/logger.ts +16 -7
- package/src/util/platform.ts +7 -2
- package/src/util/sqlite3-runtime.ts +65 -0
- package/src/workspace/migrations/086-revert-stale-gemini-mis-rewrites.ts +1 -0
- package/src/workspace/migrations/089-move-memory-tree-out-of-v3.ts +86 -0
- package/src/workspace/migrations/registry.ts +2 -0
- package/src/__tests__/compaction-strip-metadata-clear.test.ts +0 -206
- package/src/__tests__/message-complete-display-id.test.ts +0 -175
- package/src/daemon/query-complexity-router.ts +0 -75
- package/src/prompts/cache-boundary.ts +0 -8
|
@@ -0,0 +1,707 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Tests for `assistant/src/memory/v3/tree-walk.ts`.
|
|
3
|
+
*
|
|
4
|
+
* The descent provider is always a scripted stub injected via the `provider`
|
|
5
|
+
* arg — no real LLM, no network, no `mock.module`, `~/.vellum/` untouched. The
|
|
6
|
+
* stub keys its scripted decision off the `<node id="...">` marker in the user
|
|
7
|
+
* message so one fixture provider can drive a whole multi-node walk.
|
|
8
|
+
*
|
|
9
|
+
* Coverage:
|
|
10
|
+
* - scripted descent collects the kept leaf pages and records
|
|
11
|
+
* considered/descended/skipped + reasoning per node.
|
|
12
|
+
* - one descent call per *visited node with children* (node or page) — leaf
|
|
13
|
+
* buckets are now judged for page selection, not bulk-collected.
|
|
14
|
+
* - the descender keeps only the pages the model selects (drops the rest).
|
|
15
|
+
* - breadthBudget caps descents per node; maxDepth halts the walk.
|
|
16
|
+
* - the walk starts at root only — scout hits steer it as prompt pressure,
|
|
17
|
+
* not as mid-tree seeds.
|
|
18
|
+
* - provider === null → fail-safe: descend + keep nothing, walk still
|
|
19
|
+
* terminates, reasoning records the failure.
|
|
20
|
+
* - the forced tool exposes keep_pages (enum = offered page slugs).
|
|
21
|
+
* - request shape: forced tool_choice on `choose_branches`, abort signal
|
|
22
|
+
* forwarded.
|
|
23
|
+
*/
|
|
24
|
+
|
|
25
|
+
import { describe, expect, test } from "bun:test";
|
|
26
|
+
|
|
27
|
+
import type {
|
|
28
|
+
Message,
|
|
29
|
+
Provider,
|
|
30
|
+
ProviderResponse,
|
|
31
|
+
SendMessageOptions,
|
|
32
|
+
ToolDefinition,
|
|
33
|
+
} from "../../../providers/types.js";
|
|
34
|
+
import type { RetrievalInput } from "../../v2/harness/retriever.js";
|
|
35
|
+
import type { ScoutResult } from "../../v2/harness/trace.js";
|
|
36
|
+
import type { PageIndex } from "../../v2/page-index.js";
|
|
37
|
+
import type { LlmCallRecord } from "../llm-capture.js";
|
|
38
|
+
import { DESCENT_SYSTEM_PROMPT } from "../prompts/system-prompts.js";
|
|
39
|
+
import type { ChildRef, TreeIndex } from "../tree-index.js";
|
|
40
|
+
import { createDescender, runTreeWalk } from "../tree-walk.js";
|
|
41
|
+
import type { TreeNode } from "../types.js";
|
|
42
|
+
|
|
43
|
+
// ---------------------------------------------------------------------------
|
|
44
|
+
// Fixture helpers.
|
|
45
|
+
// ---------------------------------------------------------------------------
|
|
46
|
+
|
|
47
|
+
function page(ref: string): ChildRef {
|
|
48
|
+
return { kind: "page", ref };
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
function node(ref: string): ChildRef {
|
|
52
|
+
return { kind: "node", ref };
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
interface ProviderCall {
|
|
56
|
+
messages: Message[];
|
|
57
|
+
tools: ToolDefinition[] | undefined;
|
|
58
|
+
systemPrompt: string | undefined;
|
|
59
|
+
options: SendMessageOptions | undefined;
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
/**
|
|
63
|
+
* Build a tree node with the given children refs. `summary` defaults to the id
|
|
64
|
+
* so `composeNodeIndex` produces deterministic, inspectable lines.
|
|
65
|
+
*/
|
|
66
|
+
function makeNode(id: string, children: ChildRef[]): TreeNode {
|
|
67
|
+
return {
|
|
68
|
+
id,
|
|
69
|
+
frontmatter: {
|
|
70
|
+
children: children.map((c) => `${c.kind}:${c.ref}`),
|
|
71
|
+
summary: `summary of ${id}`,
|
|
72
|
+
},
|
|
73
|
+
body: "",
|
|
74
|
+
};
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
/**
|
|
78
|
+
* Build an in-memory `TreeIndex` from a forward-adjacency spec, materializing
|
|
79
|
+
* `nodes`, `childrenByNode`, and the `pageParents` reverse edges. `parentsByNode`
|
|
80
|
+
* is left empty — the driver never reads it.
|
|
81
|
+
*/
|
|
82
|
+
function makeTree(
|
|
83
|
+
root: string,
|
|
84
|
+
childrenByNode: Record<string, ChildRef[]>,
|
|
85
|
+
): TreeIndex {
|
|
86
|
+
const nodes = new Map<string, TreeNode>();
|
|
87
|
+
const children = new Map<string, ReadonlyArray<ChildRef>>();
|
|
88
|
+
const pageParents = new Map<string, Set<string>>();
|
|
89
|
+
for (const [id, refs] of Object.entries(childrenByNode)) {
|
|
90
|
+
nodes.set(id, makeNode(id, refs));
|
|
91
|
+
children.set(id, refs);
|
|
92
|
+
for (const ref of refs) {
|
|
93
|
+
if (ref.kind !== "page") continue;
|
|
94
|
+
let parents = pageParents.get(ref.ref);
|
|
95
|
+
if (!parents) {
|
|
96
|
+
parents = new Set();
|
|
97
|
+
pageParents.set(ref.ref, parents);
|
|
98
|
+
}
|
|
99
|
+
parents.add(id);
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
return {
|
|
103
|
+
nodes,
|
|
104
|
+
childrenByNode: children,
|
|
105
|
+
parentsByNode: new Map(),
|
|
106
|
+
pageParents,
|
|
107
|
+
root,
|
|
108
|
+
};
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
/** Empty page index — the driver only needs `bySlug` for page summaries. */
|
|
112
|
+
function makePages(slugs: string[]): PageIndex {
|
|
113
|
+
const bySlug = new Map();
|
|
114
|
+
const byId = new Map();
|
|
115
|
+
let id = 1;
|
|
116
|
+
for (const slug of slugs) {
|
|
117
|
+
const entry = {
|
|
118
|
+
id,
|
|
119
|
+
slug,
|
|
120
|
+
summary: `summary of ${slug}`,
|
|
121
|
+
edges: [],
|
|
122
|
+
modifiedAt: 0,
|
|
123
|
+
};
|
|
124
|
+
bySlug.set(slug, entry);
|
|
125
|
+
byId.set(id, entry);
|
|
126
|
+
id++;
|
|
127
|
+
}
|
|
128
|
+
return { entries: [...bySlug.values()], bySlug, byId, rendered: "" };
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
/** Minimal `RetrievalInput` carrying just the fields the driver reads. */
|
|
132
|
+
function makeInput(
|
|
133
|
+
overrides?: Partial<RetrievalInput> & {
|
|
134
|
+
breadthBudget?: number;
|
|
135
|
+
maxDepth?: number;
|
|
136
|
+
/** Inline override for `memory.v3.prompts.descent`. */
|
|
137
|
+
descentOverride?: string;
|
|
138
|
+
},
|
|
139
|
+
): RetrievalInput {
|
|
140
|
+
const breadthBudget = overrides?.breadthBudget ?? 8;
|
|
141
|
+
const maxDepth = overrides?.maxDepth ?? 8;
|
|
142
|
+
const config = {
|
|
143
|
+
memory: {
|
|
144
|
+
v3: {
|
|
145
|
+
breadthBudget,
|
|
146
|
+
maxDepth,
|
|
147
|
+
...(overrides?.descentOverride !== undefined
|
|
148
|
+
? {
|
|
149
|
+
prompts: {
|
|
150
|
+
descent: { override: overrides.descentOverride, path: null },
|
|
151
|
+
},
|
|
152
|
+
}
|
|
153
|
+
: {}),
|
|
154
|
+
},
|
|
155
|
+
},
|
|
156
|
+
} as unknown as RetrievalInput["config"];
|
|
157
|
+
const {
|
|
158
|
+
breadthBudget: _b,
|
|
159
|
+
maxDepth: _m,
|
|
160
|
+
descentOverride: _d,
|
|
161
|
+
...rest
|
|
162
|
+
} = overrides ?? {};
|
|
163
|
+
return {
|
|
164
|
+
workspaceDir: "/tmp/does-not-matter",
|
|
165
|
+
recentTurnPairs: [{ assistantMessage: "", userMessage: "tell me about a" }],
|
|
166
|
+
nowText: "2026-05-25 10:00 PT",
|
|
167
|
+
priorEverInjected: [],
|
|
168
|
+
config,
|
|
169
|
+
...rest,
|
|
170
|
+
};
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
/** Pull the `<node id="...">` id out of a recorded descend prompt. */
|
|
174
|
+
function nodeIdFromCall(call: ProviderCall): string | null {
|
|
175
|
+
for (const block of call.messages[0]?.content ?? []) {
|
|
176
|
+
if (block.type !== "text") continue;
|
|
177
|
+
const match = block.text.match(/<node id="([^"]*)">/);
|
|
178
|
+
if (match) return match[1];
|
|
179
|
+
}
|
|
180
|
+
return null;
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
/** Read the `keep_pages` enum (offered page slugs) out of a built descend tool. */
|
|
184
|
+
function keepPagesEnum(tool: ToolDefinition | undefined): string[] {
|
|
185
|
+
const schema = tool?.input_schema as
|
|
186
|
+
| { properties?: { keep_pages?: { items?: { enum?: string[] } } } }
|
|
187
|
+
| undefined;
|
|
188
|
+
return schema?.properties?.keep_pages?.items?.enum ?? [];
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
/**
|
|
192
|
+
* A scripted descent provider. `script` maps a node id to the bare child-node
|
|
193
|
+
* ids to descend, an optional explicit `keep` (page slugs), and an optional
|
|
194
|
+
* reasoning. When `keep` is omitted the stub keeps *every* offered page (the old
|
|
195
|
+
* bulk behavior), so a test only sets `keep` when exercising selective keeping.
|
|
196
|
+
* Records every call and honors an already-aborted signal by throwing.
|
|
197
|
+
*/
|
|
198
|
+
function makeProvider(
|
|
199
|
+
script: Record<
|
|
200
|
+
string,
|
|
201
|
+
{ descend: string[]; keep?: string[]; reasoning?: string }
|
|
202
|
+
>,
|
|
203
|
+
calls: ProviderCall[],
|
|
204
|
+
): Provider {
|
|
205
|
+
return {
|
|
206
|
+
name: "stub",
|
|
207
|
+
sendMessage: async (messages, tools, systemPrompt, options) => {
|
|
208
|
+
calls.push({ messages, tools, systemPrompt, options });
|
|
209
|
+
if (options?.signal?.aborted) {
|
|
210
|
+
const err = new Error("aborted");
|
|
211
|
+
err.name = "AbortError";
|
|
212
|
+
throw err;
|
|
213
|
+
}
|
|
214
|
+
const nodeId =
|
|
215
|
+
nodeIdFromCall({ messages, tools, systemPrompt, options }) ?? "";
|
|
216
|
+
const decision = script[nodeId] ?? { descend: [] };
|
|
217
|
+
const keep = decision.keep ?? keepPagesEnum(tools?.[0]);
|
|
218
|
+
const input: Record<string, unknown> = {
|
|
219
|
+
descend: decision.descend,
|
|
220
|
+
keep_pages: keep,
|
|
221
|
+
};
|
|
222
|
+
if (decision.reasoning !== undefined)
|
|
223
|
+
input.reasoning = decision.reasoning;
|
|
224
|
+
const response: ProviderResponse = {
|
|
225
|
+
model: "stub-model",
|
|
226
|
+
stopReason: "tool_use",
|
|
227
|
+
usage: { inputTokens: 0, outputTokens: 0 },
|
|
228
|
+
content: [
|
|
229
|
+
{
|
|
230
|
+
type: "tool_use",
|
|
231
|
+
id: `tu-${nodeId}`,
|
|
232
|
+
name: "choose_branches",
|
|
233
|
+
input,
|
|
234
|
+
},
|
|
235
|
+
],
|
|
236
|
+
};
|
|
237
|
+
return response;
|
|
238
|
+
},
|
|
239
|
+
};
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
// ---------------------------------------------------------------------------
|
|
243
|
+
// runTreeWalk — scripted descent
|
|
244
|
+
// ---------------------------------------------------------------------------
|
|
245
|
+
|
|
246
|
+
describe("runTreeWalk — scripted descent", () => {
|
|
247
|
+
test("collects the kept leaf pages and records the descend/skip split", async () => {
|
|
248
|
+
// _root → {a, b}; a → leaf pa; b → leaf pb. Script descends only "a".
|
|
249
|
+
const tree = makeTree("_root", {
|
|
250
|
+
_root: [node("a"), node("b")],
|
|
251
|
+
a: [page("pa")],
|
|
252
|
+
b: [page("pb")],
|
|
253
|
+
});
|
|
254
|
+
const pages = makePages(["pa", "pb"]);
|
|
255
|
+
const calls: ProviderCall[] = [];
|
|
256
|
+
const provider = makeProvider(
|
|
257
|
+
{ _root: { descend: ["a"], reasoning: "a matches the turn" } },
|
|
258
|
+
calls,
|
|
259
|
+
);
|
|
260
|
+
|
|
261
|
+
const { pages: collected, levels } = await runTreeWalk({
|
|
262
|
+
input: makeInput(),
|
|
263
|
+
tree,
|
|
264
|
+
pages,
|
|
265
|
+
scouts: [],
|
|
266
|
+
provider,
|
|
267
|
+
});
|
|
268
|
+
|
|
269
|
+
// Only the descended branch's page is kept; b is skipped entirely.
|
|
270
|
+
expect([...collected]).toEqual(["pa"]);
|
|
271
|
+
|
|
272
|
+
const rootLevel = levels.find((l) => l.node === "_root")!;
|
|
273
|
+
expect(rootLevel.considered).toEqual(["a", "b"]);
|
|
274
|
+
expect(rootLevel.descended).toEqual(["a"]);
|
|
275
|
+
expect(rootLevel.skipped).toEqual(["b"]);
|
|
276
|
+
expect(rootLevel.reasoning).toBe("a matches the turn");
|
|
277
|
+
|
|
278
|
+
// _root (node children) and a (page child) are both walked; b is skipped.
|
|
279
|
+
expect(levels.map((l) => l.node).sort()).toEqual(["_root", "a"]);
|
|
280
|
+
});
|
|
281
|
+
|
|
282
|
+
test("keeps only the pages the model selects at a node", async () => {
|
|
283
|
+
// _root → a, a leaf bucket of two pages. The model keeps only one.
|
|
284
|
+
const tree = makeTree("_root", {
|
|
285
|
+
_root: [node("a")],
|
|
286
|
+
a: [page("pa-keep"), page("pa-drop")],
|
|
287
|
+
});
|
|
288
|
+
const pages = makePages(["pa-keep", "pa-drop"]);
|
|
289
|
+
const calls: ProviderCall[] = [];
|
|
290
|
+
const provider = makeProvider(
|
|
291
|
+
{
|
|
292
|
+
_root: { descend: ["a"] },
|
|
293
|
+
a: { descend: [], keep: ["pa-keep"] },
|
|
294
|
+
},
|
|
295
|
+
calls,
|
|
296
|
+
);
|
|
297
|
+
|
|
298
|
+
const { pages: collected } = await runTreeWalk({
|
|
299
|
+
input: makeInput(),
|
|
300
|
+
tree,
|
|
301
|
+
pages,
|
|
302
|
+
scouts: [],
|
|
303
|
+
provider,
|
|
304
|
+
});
|
|
305
|
+
|
|
306
|
+
expect([...collected]).toEqual(["pa-keep"]);
|
|
307
|
+
});
|
|
308
|
+
|
|
309
|
+
test("makes one descent call per visited node with children", async () => {
|
|
310
|
+
const tree = makeTree("_root", {
|
|
311
|
+
_root: [node("a"), node("b")],
|
|
312
|
+
a: [node("c"), page("pa")],
|
|
313
|
+
b: [page("pb")],
|
|
314
|
+
c: [page("pc")],
|
|
315
|
+
});
|
|
316
|
+
const pages = makePages(["pa", "pb", "pc"]);
|
|
317
|
+
const calls: ProviderCall[] = [];
|
|
318
|
+
const provider = makeProvider(
|
|
319
|
+
{ _root: { descend: ["a", "b"] }, a: { descend: ["c"] } },
|
|
320
|
+
calls,
|
|
321
|
+
);
|
|
322
|
+
|
|
323
|
+
await runTreeWalk({
|
|
324
|
+
input: makeInput(),
|
|
325
|
+
tree,
|
|
326
|
+
pages,
|
|
327
|
+
scouts: [],
|
|
328
|
+
provider,
|
|
329
|
+
});
|
|
330
|
+
|
|
331
|
+
// Every visited node has children (b and c are page-only leaf buckets that
|
|
332
|
+
// now get a page-selection call too), so all four are called.
|
|
333
|
+
const calledNodes = calls.map(nodeIdFromCall).sort();
|
|
334
|
+
expect(calledNodes).toEqual(["_root", "a", "b", "c"]);
|
|
335
|
+
});
|
|
336
|
+
|
|
337
|
+
test("breadthBudget caps descents per node", async () => {
|
|
338
|
+
const tree = makeTree("_root", {
|
|
339
|
+
_root: [node("a"), node("b"), node("c")],
|
|
340
|
+
a: [page("pa")],
|
|
341
|
+
b: [page("pb")],
|
|
342
|
+
c: [page("pc")],
|
|
343
|
+
});
|
|
344
|
+
const pages = makePages(["pa", "pb", "pc"]);
|
|
345
|
+
const calls: ProviderCall[] = [];
|
|
346
|
+
// Model picks all three; budget 2 admits only the first two.
|
|
347
|
+
const provider = makeProvider(
|
|
348
|
+
{ _root: { descend: ["a", "b", "c"] } },
|
|
349
|
+
calls,
|
|
350
|
+
);
|
|
351
|
+
|
|
352
|
+
const { pages: collected, levels } = await runTreeWalk({
|
|
353
|
+
input: makeInput({ breadthBudget: 2 }),
|
|
354
|
+
tree,
|
|
355
|
+
pages,
|
|
356
|
+
scouts: [],
|
|
357
|
+
provider,
|
|
358
|
+
});
|
|
359
|
+
|
|
360
|
+
const rootLevel = levels.find((l) => l.node === "_root")!;
|
|
361
|
+
expect(rootLevel.descended).toEqual(["a", "b"]);
|
|
362
|
+
expect(rootLevel.skipped).toEqual(["c"]);
|
|
363
|
+
expect([...collected].sort()).toEqual(["pa", "pb"]);
|
|
364
|
+
});
|
|
365
|
+
|
|
366
|
+
test("maxDepth halts the walk", async () => {
|
|
367
|
+
const tree = makeTree("_root", {
|
|
368
|
+
_root: [node("a")],
|
|
369
|
+
a: [node("b"), page("pa")],
|
|
370
|
+
b: [page("pb")],
|
|
371
|
+
});
|
|
372
|
+
const pages = makePages(["pa", "pb"]);
|
|
373
|
+
const calls: ProviderCall[] = [];
|
|
374
|
+
const provider = makeProvider(
|
|
375
|
+
{ _root: { descend: ["a"] }, a: { descend: ["b"] } },
|
|
376
|
+
calls,
|
|
377
|
+
);
|
|
378
|
+
|
|
379
|
+
const { pages: collected, levels } = await runTreeWalk({
|
|
380
|
+
input: makeInput({ maxDepth: 1 }),
|
|
381
|
+
tree,
|
|
382
|
+
pages,
|
|
383
|
+
scouts: [],
|
|
384
|
+
provider,
|
|
385
|
+
});
|
|
386
|
+
|
|
387
|
+
// Depth 0 (_root) and depth 1 (a) walked; b never reached.
|
|
388
|
+
expect(levels.map((l) => l.node)).toEqual(["_root", "a"]);
|
|
389
|
+
expect([...collected]).toEqual(["pa"]);
|
|
390
|
+
});
|
|
391
|
+
});
|
|
392
|
+
|
|
393
|
+
// ---------------------------------------------------------------------------
|
|
394
|
+
// runTreeWalk — root-only walk + scout pressure
|
|
395
|
+
// ---------------------------------------------------------------------------
|
|
396
|
+
|
|
397
|
+
describe("runTreeWalk — root-only walk", () => {
|
|
398
|
+
test("starts at root only — a scout hit in an unreachable subtree is not walked", async () => {
|
|
399
|
+
// root only links to `a`; `island` is unreachable from root. A scout
|
|
400
|
+
// surfaced its leaf page, but the walk no longer seeds at scout parents.
|
|
401
|
+
const tree = makeTree("_root", {
|
|
402
|
+
_root: [node("a")],
|
|
403
|
+
a: [page("pa")],
|
|
404
|
+
island: [page("treasure")],
|
|
405
|
+
});
|
|
406
|
+
const pages = makePages(["pa", "treasure"]);
|
|
407
|
+
const calls: ProviderCall[] = [];
|
|
408
|
+
const provider = makeProvider({ _root: { descend: ["a"] } }, calls);
|
|
409
|
+
|
|
410
|
+
const scouts: ScoutResult[] = [{ lane: "dense", slugs: ["treasure"] }];
|
|
411
|
+
const { pages: collected, levels } = await runTreeWalk({
|
|
412
|
+
input: makeInput(),
|
|
413
|
+
tree,
|
|
414
|
+
pages,
|
|
415
|
+
scouts,
|
|
416
|
+
provider,
|
|
417
|
+
});
|
|
418
|
+
|
|
419
|
+
// Only the root branch is walked; `island`/treasure is never reached.
|
|
420
|
+
expect([...collected]).toEqual(["pa"]);
|
|
421
|
+
expect(levels.map((l) => l.node).sort()).toEqual(["_root", "a"]);
|
|
422
|
+
});
|
|
423
|
+
|
|
424
|
+
test("renders scout hits into the descend prompt as pressure", async () => {
|
|
425
|
+
const tree = makeTree("_root", {
|
|
426
|
+
_root: [node("a"), node("b")],
|
|
427
|
+
a: [page("pa")],
|
|
428
|
+
b: [page("pb")],
|
|
429
|
+
});
|
|
430
|
+
const pages = makePages(["pa", "pb"]);
|
|
431
|
+
const calls: ProviderCall[] = [];
|
|
432
|
+
const provider = makeProvider({ _root: { descend: ["a"] } }, calls);
|
|
433
|
+
|
|
434
|
+
const scouts: ScoutResult[] = [{ lane: "sparse", slugs: ["pb"] }];
|
|
435
|
+
await runTreeWalk({
|
|
436
|
+
input: makeInput(),
|
|
437
|
+
tree,
|
|
438
|
+
pages,
|
|
439
|
+
scouts,
|
|
440
|
+
provider,
|
|
441
|
+
});
|
|
442
|
+
|
|
443
|
+
const rootCall = calls.find((c) => nodeIdFromCall(c) === "_root")!;
|
|
444
|
+
const promptText = rootCall.messages[0].content
|
|
445
|
+
.filter((b) => b.type === "text")
|
|
446
|
+
.map((b) => (b as { text: string }).text)
|
|
447
|
+
.join("\n");
|
|
448
|
+
expect(promptText).toContain("<scout_hits>");
|
|
449
|
+
expect(promptText).toContain("[sparse]: pb");
|
|
450
|
+
});
|
|
451
|
+
});
|
|
452
|
+
|
|
453
|
+
// ---------------------------------------------------------------------------
|
|
454
|
+
// runTreeWalk — fail-safe
|
|
455
|
+
// ---------------------------------------------------------------------------
|
|
456
|
+
|
|
457
|
+
describe("runTreeWalk — fail-safe", () => {
|
|
458
|
+
test("provider null descends and keeps nothing but still terminates", async () => {
|
|
459
|
+
const tree = makeTree("_root", {
|
|
460
|
+
_root: [node("a"), page("pr")],
|
|
461
|
+
a: [page("pa")],
|
|
462
|
+
});
|
|
463
|
+
const pages = makePages(["pr", "pa"]);
|
|
464
|
+
|
|
465
|
+
const { pages: collected, levels } = await runTreeWalk({
|
|
466
|
+
input: makeInput(),
|
|
467
|
+
tree,
|
|
468
|
+
pages,
|
|
469
|
+
scouts: [],
|
|
470
|
+
provider: null,
|
|
471
|
+
});
|
|
472
|
+
|
|
473
|
+
// No provider → the node keeps nothing (the scout lanes carry recall in the
|
|
474
|
+
// loop), descends nothing, and the walk stops at root.
|
|
475
|
+
expect([...collected]).toEqual([]);
|
|
476
|
+
expect(levels.map((l) => l.node)).toEqual(["_root"]);
|
|
477
|
+
const rootLevel = levels[0];
|
|
478
|
+
expect(rootLevel.descended).toEqual([]);
|
|
479
|
+
expect(rootLevel.skipped).toEqual(["a"]);
|
|
480
|
+
expect(rootLevel.reasoning).toContain("no provider");
|
|
481
|
+
});
|
|
482
|
+
|
|
483
|
+
test("malformed tool input fails closed for that node", async () => {
|
|
484
|
+
const tree = makeTree("_root", {
|
|
485
|
+
_root: [node("a")],
|
|
486
|
+
a: [page("pa")],
|
|
487
|
+
});
|
|
488
|
+
const pages = makePages(["pa"]);
|
|
489
|
+
const calls: ProviderCall[] = [];
|
|
490
|
+
// Provider returns a non-conforming tool input (descend is not an array).
|
|
491
|
+
const provider: Provider = {
|
|
492
|
+
name: "bad-schema",
|
|
493
|
+
sendMessage: async (messages, tools, systemPrompt, options) => {
|
|
494
|
+
calls.push({ messages, tools, systemPrompt, options });
|
|
495
|
+
return {
|
|
496
|
+
model: "stub-model",
|
|
497
|
+
stopReason: "tool_use",
|
|
498
|
+
usage: { inputTokens: 0, outputTokens: 0 },
|
|
499
|
+
content: [
|
|
500
|
+
{
|
|
501
|
+
type: "tool_use",
|
|
502
|
+
id: "tu-1",
|
|
503
|
+
name: "choose_branches",
|
|
504
|
+
input: { descend: "not-an-array" },
|
|
505
|
+
},
|
|
506
|
+
],
|
|
507
|
+
};
|
|
508
|
+
},
|
|
509
|
+
};
|
|
510
|
+
|
|
511
|
+
const { levels } = await runTreeWalk({
|
|
512
|
+
input: makeInput(),
|
|
513
|
+
tree,
|
|
514
|
+
pages,
|
|
515
|
+
scouts: [],
|
|
516
|
+
provider,
|
|
517
|
+
});
|
|
518
|
+
|
|
519
|
+
const rootLevel = levels.find((l) => l.node === "_root")!;
|
|
520
|
+
expect(rootLevel.descended).toEqual([]);
|
|
521
|
+
expect(rootLevel.reasoning).toContain("validation");
|
|
522
|
+
});
|
|
523
|
+
});
|
|
524
|
+
|
|
525
|
+
// ---------------------------------------------------------------------------
|
|
526
|
+
// createDescender — request shape + page selection
|
|
527
|
+
// ---------------------------------------------------------------------------
|
|
528
|
+
|
|
529
|
+
describe("createDescender — request shape", () => {
|
|
530
|
+
test("forces tool_choice on choose_branches and forwards the abort signal", async () => {
|
|
531
|
+
const tree = makeTree("_root", {
|
|
532
|
+
_root: [node("a")],
|
|
533
|
+
a: [page("pa")],
|
|
534
|
+
});
|
|
535
|
+
const pages = makePages(["pa"]);
|
|
536
|
+
const calls: ProviderCall[] = [];
|
|
537
|
+
const provider = makeProvider({ _root: { descend: ["a"] } }, calls);
|
|
538
|
+
|
|
539
|
+
const descender = createDescender({
|
|
540
|
+
input: makeInput({ signal: AbortSignal.timeout(10_000) }),
|
|
541
|
+
tree,
|
|
542
|
+
pages,
|
|
543
|
+
scouts: [],
|
|
544
|
+
provider,
|
|
545
|
+
});
|
|
546
|
+
|
|
547
|
+
await descender("_root", [...tree.childrenByNode.get("_root")!]);
|
|
548
|
+
|
|
549
|
+
expect(calls).toHaveLength(1);
|
|
550
|
+
const call = calls[0];
|
|
551
|
+
expect(call.tools?.[0]?.name).toBe("choose_branches");
|
|
552
|
+
expect(call.options?.config?.tool_choice).toEqual({
|
|
553
|
+
type: "tool",
|
|
554
|
+
name: "choose_branches",
|
|
555
|
+
});
|
|
556
|
+
expect(call.options?.config?.callSite).toBe("memoryV3Descent");
|
|
557
|
+
expect(call.options?.signal).toBeDefined();
|
|
558
|
+
});
|
|
559
|
+
|
|
560
|
+
test("exposes a keep_pages enum of the node's offered page slugs", async () => {
|
|
561
|
+
const tree = makeTree("bucket", {
|
|
562
|
+
bucket: [page("p1"), page("p2")],
|
|
563
|
+
});
|
|
564
|
+
const pages = makePages(["p1", "p2"]);
|
|
565
|
+
const calls: ProviderCall[] = [];
|
|
566
|
+
const provider = makeProvider({}, calls);
|
|
567
|
+
|
|
568
|
+
const descender = createDescender({
|
|
569
|
+
input: makeInput(),
|
|
570
|
+
tree,
|
|
571
|
+
pages,
|
|
572
|
+
scouts: [],
|
|
573
|
+
provider,
|
|
574
|
+
});
|
|
575
|
+
|
|
576
|
+
await descender("bucket", [...tree.childrenByNode.get("bucket")!]);
|
|
577
|
+
|
|
578
|
+
expect(keepPagesEnum(calls[0].tools?.[0]).sort()).toEqual(["p1", "p2"]);
|
|
579
|
+
});
|
|
580
|
+
|
|
581
|
+
test("a node with no children at all makes no provider call", async () => {
|
|
582
|
+
const tree = makeTree("empty", { empty: [] });
|
|
583
|
+
const pages = makePages([]);
|
|
584
|
+
const calls: ProviderCall[] = [];
|
|
585
|
+
const provider = makeProvider({}, calls);
|
|
586
|
+
|
|
587
|
+
const descender = createDescender({
|
|
588
|
+
input: makeInput(),
|
|
589
|
+
tree,
|
|
590
|
+
pages,
|
|
591
|
+
scouts: [],
|
|
592
|
+
provider,
|
|
593
|
+
});
|
|
594
|
+
|
|
595
|
+
const result = await descender("empty", [
|
|
596
|
+
...(tree.childrenByNode.get("empty") ?? []),
|
|
597
|
+
]);
|
|
598
|
+
expect(result).toEqual({ descend: [], keep: [], reasoning: "" });
|
|
599
|
+
expect(calls).toHaveLength(0);
|
|
600
|
+
});
|
|
601
|
+
|
|
602
|
+
test("a leaf bucket of pages makes a call and keeps the selected pages", async () => {
|
|
603
|
+
const tree = makeTree("leaf", { leaf: [page("p1"), page("p2")] });
|
|
604
|
+
const pages = makePages(["p1", "p2"]);
|
|
605
|
+
const calls: ProviderCall[] = [];
|
|
606
|
+
// Keep only p1.
|
|
607
|
+
const provider = makeProvider(
|
|
608
|
+
{ leaf: { descend: [], keep: ["p1"] } },
|
|
609
|
+
calls,
|
|
610
|
+
);
|
|
611
|
+
|
|
612
|
+
const descender = createDescender({
|
|
613
|
+
input: makeInput(),
|
|
614
|
+
tree,
|
|
615
|
+
pages,
|
|
616
|
+
scouts: [],
|
|
617
|
+
provider,
|
|
618
|
+
});
|
|
619
|
+
|
|
620
|
+
const result = await descender("leaf", [
|
|
621
|
+
...tree.childrenByNode.get("leaf")!,
|
|
622
|
+
]);
|
|
623
|
+
|
|
624
|
+
expect(calls).toHaveLength(1);
|
|
625
|
+
expect(result.descend).toEqual([]);
|
|
626
|
+
expect(result.keep).toEqual([page("p1")]);
|
|
627
|
+
});
|
|
628
|
+
});
|
|
629
|
+
|
|
630
|
+
// ---------------------------------------------------------------------------
|
|
631
|
+
// runTreeWalk — descent system prompt
|
|
632
|
+
// ---------------------------------------------------------------------------
|
|
633
|
+
|
|
634
|
+
describe("runTreeWalk — descent system prompt", () => {
|
|
635
|
+
const tree = makeTree("_root", {
|
|
636
|
+
_root: [node("a")],
|
|
637
|
+
a: [page("pa")],
|
|
638
|
+
});
|
|
639
|
+
|
|
640
|
+
test("uses the bundled default when no override is configured", async () => {
|
|
641
|
+
const pages = makePages(["pa"]);
|
|
642
|
+
const calls: ProviderCall[] = [];
|
|
643
|
+
const provider = makeProvider({ _root: { descend: ["a"] } }, calls);
|
|
644
|
+
|
|
645
|
+
await runTreeWalk({
|
|
646
|
+
input: makeInput(),
|
|
647
|
+
tree,
|
|
648
|
+
pages,
|
|
649
|
+
scouts: [],
|
|
650
|
+
provider,
|
|
651
|
+
});
|
|
652
|
+
|
|
653
|
+
const rootCall = calls.find((c) => nodeIdFromCall(c) === "_root")!;
|
|
654
|
+
expect(rootCall.systemPrompt).toBe(DESCENT_SYSTEM_PROMPT);
|
|
655
|
+
});
|
|
656
|
+
|
|
657
|
+
test("uses the configured inline override as the descent system prompt", async () => {
|
|
658
|
+
const pages = makePages(["pa"]);
|
|
659
|
+
const calls: ProviderCall[] = [];
|
|
660
|
+
const provider = makeProvider({ _root: { descend: ["a"] } }, calls);
|
|
661
|
+
|
|
662
|
+
const override = "CUSTOM DESCENT PROMPT — descend everything plausible.";
|
|
663
|
+
await runTreeWalk({
|
|
664
|
+
input: makeInput({ descentOverride: override }),
|
|
665
|
+
tree,
|
|
666
|
+
pages,
|
|
667
|
+
scouts: [],
|
|
668
|
+
provider,
|
|
669
|
+
});
|
|
670
|
+
|
|
671
|
+
const rootCall = calls.find((c) => nodeIdFromCall(c) === "_root")!;
|
|
672
|
+
expect(rootCall.systemPrompt).toBe(override);
|
|
673
|
+
expect(rootCall.systemPrompt).not.toBe(DESCENT_SYSTEM_PROMPT);
|
|
674
|
+
});
|
|
675
|
+
});
|
|
676
|
+
|
|
677
|
+
describe("runTreeWalk — capture", () => {
|
|
678
|
+
test("emits one record per descender LLM call, tagged with the node", async () => {
|
|
679
|
+
// _root has node children but descends nothing, so exactly one call fires.
|
|
680
|
+
const tree = makeTree("_root", {
|
|
681
|
+
_root: [node("a"), node("b")],
|
|
682
|
+
a: [page("pa")],
|
|
683
|
+
b: [page("pb")],
|
|
684
|
+
});
|
|
685
|
+
const pages = makePages(["pa", "pb"]);
|
|
686
|
+
const calls: ProviderCall[] = [];
|
|
687
|
+
const provider = makeProvider({ _root: { descend: [] } }, calls);
|
|
688
|
+
const captured: Omit<LlmCallRecord, "pass">[] = [];
|
|
689
|
+
|
|
690
|
+
await runTreeWalk({
|
|
691
|
+
input: makeInput(),
|
|
692
|
+
tree,
|
|
693
|
+
pages,
|
|
694
|
+
scouts: [],
|
|
695
|
+
provider,
|
|
696
|
+
capture: (record) => captured.push(record),
|
|
697
|
+
});
|
|
698
|
+
|
|
699
|
+
expect(captured).toHaveLength(1);
|
|
700
|
+
const rec = captured[0]!;
|
|
701
|
+
expect(rec.lane).toBe("descent");
|
|
702
|
+
expect(rec.callSite).toBe("memoryV3Descent");
|
|
703
|
+
expect(rec.node).toBe("_root");
|
|
704
|
+
expect(rec.request.tools[0]!.name).toBe("choose_branches");
|
|
705
|
+
expect(rec.response.stopReason).toBe("tool_use");
|
|
706
|
+
});
|
|
707
|
+
});
|