@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,574 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Tests for `assistant/src/memory/v3/gate.ts`.
|
|
3
|
+
*
|
|
4
|
+
* Coverage matrix:
|
|
5
|
+
* - ready + selection → selection maps from candidates, in model order, and
|
|
6
|
+
* includes sticky slugs even when the model omits them.
|
|
7
|
+
* - more + questions → `decision.questions` surfaced; selection still returned.
|
|
8
|
+
* - more with no/blank questions → decision is `{ decision: "more" }` (no
|
|
9
|
+
* empty `questions` array).
|
|
10
|
+
* - provider === null (no provider configured) → fail-safe: ready, all
|
|
11
|
+
* candidates selected, sticky present.
|
|
12
|
+
* - provider throws → fail-safe (ready, all candidates).
|
|
13
|
+
* - missing tool_use block → fail-safe (ready, all candidates).
|
|
14
|
+
* - tool input failing schema → fail-safe (ready, all candidates).
|
|
15
|
+
* - model selecting a slug outside the candidate set → dropped.
|
|
16
|
+
* - request shape: forced tool_choice on `decide_selection`, candidate set in
|
|
17
|
+
* the user message, abort signal forwarded.
|
|
18
|
+
*
|
|
19
|
+
* The provider is injected via `runGate({ provider })` — no real LLM, no
|
|
20
|
+
* network, no `mock.module`. `~/.vellum/` is never touched.
|
|
21
|
+
*/
|
|
22
|
+
|
|
23
|
+
import { describe, expect, test } from "bun:test";
|
|
24
|
+
|
|
25
|
+
import type {
|
|
26
|
+
Message,
|
|
27
|
+
Provider,
|
|
28
|
+
ProviderResponse,
|
|
29
|
+
SendMessageOptions,
|
|
30
|
+
ToolDefinition,
|
|
31
|
+
} from "../../../providers/types.js";
|
|
32
|
+
import type { RetrievalInput } from "../../v2/harness/retriever.js";
|
|
33
|
+
import { runGate } from "../gate.js";
|
|
34
|
+
import type { LlmCallRecord } from "../llm-capture.js";
|
|
35
|
+
import { GATE_SYSTEM_PROMPT } from "../prompts/system-prompts.js";
|
|
36
|
+
|
|
37
|
+
// ---------------------------------------------------------------------------
|
|
38
|
+
// Helpers.
|
|
39
|
+
// ---------------------------------------------------------------------------
|
|
40
|
+
|
|
41
|
+
interface ProviderCall {
|
|
42
|
+
messages: Message[];
|
|
43
|
+
tools: ToolDefinition[] | undefined;
|
|
44
|
+
systemPrompt: string | undefined;
|
|
45
|
+
options: SendMessageOptions | undefined;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
/**
|
|
49
|
+
* A stub provider that records its calls and returns a fixed response.
|
|
50
|
+
* Honors an already-aborted signal by throwing an AbortError so signal
|
|
51
|
+
* forwarding can be asserted.
|
|
52
|
+
*/
|
|
53
|
+
function makeProvider(
|
|
54
|
+
response: ProviderResponse,
|
|
55
|
+
calls: ProviderCall[],
|
|
56
|
+
): Provider {
|
|
57
|
+
return {
|
|
58
|
+
name: "stub",
|
|
59
|
+
sendMessage: async (messages, tools, systemPrompt, options) => {
|
|
60
|
+
calls.push({ messages, tools, systemPrompt, options });
|
|
61
|
+
if (options?.signal?.aborted) {
|
|
62
|
+
const err = new Error("aborted");
|
|
63
|
+
err.name = "AbortError";
|
|
64
|
+
throw err;
|
|
65
|
+
}
|
|
66
|
+
return response;
|
|
67
|
+
},
|
|
68
|
+
};
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
/** A provider whose sendMessage always throws. */
|
|
72
|
+
function makeThrowingProvider(): Provider {
|
|
73
|
+
return {
|
|
74
|
+
name: "throwing-stub",
|
|
75
|
+
sendMessage: async () => {
|
|
76
|
+
throw new Error("boom");
|
|
77
|
+
},
|
|
78
|
+
};
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
function gateToolResponse(input: Record<string, unknown>): ProviderResponse {
|
|
82
|
+
return {
|
|
83
|
+
model: "stub-model",
|
|
84
|
+
stopReason: "tool_use",
|
|
85
|
+
usage: { inputTokens: 0, outputTokens: 0 },
|
|
86
|
+
content: [
|
|
87
|
+
{ type: "tool_use", id: "tu-1", name: "decide_selection", input },
|
|
88
|
+
],
|
|
89
|
+
};
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
/** A response with no tool_use block (e.g. the model emitted only text). */
|
|
93
|
+
function textOnlyResponse(): ProviderResponse {
|
|
94
|
+
return {
|
|
95
|
+
model: "stub-model",
|
|
96
|
+
stopReason: "end_turn",
|
|
97
|
+
usage: { inputTokens: 0, outputTokens: 0 },
|
|
98
|
+
content: [{ type: "text", text: "no tool here" }],
|
|
99
|
+
};
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
/** Minimal `RetrievalInput` — the gate only reads `nowText` and `signal`. */
|
|
103
|
+
function makeInput(overrides?: Partial<RetrievalInput>): RetrievalInput {
|
|
104
|
+
return {
|
|
105
|
+
workspaceDir: "/tmp/does-not-matter",
|
|
106
|
+
recentTurnPairs: [],
|
|
107
|
+
nowText: "2026-05-25 10:00 PT",
|
|
108
|
+
priorEverInjected: [],
|
|
109
|
+
config: {} as unknown as RetrievalInput["config"],
|
|
110
|
+
...overrides,
|
|
111
|
+
};
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
/**
|
|
115
|
+
* Build a `RetrievalInput["config"]` carrying a `memory.v3.prompts.gate`
|
|
116
|
+
* inline override. The cast mirrors `makeInput`'s — the gate only reads the
|
|
117
|
+
* prompts path on config.
|
|
118
|
+
*/
|
|
119
|
+
function configWithGateOverride(override: string): RetrievalInput["config"] {
|
|
120
|
+
return {
|
|
121
|
+
memory: { v3: { prompts: { gate: { override, path: null } } } },
|
|
122
|
+
} as unknown as RetrievalInput["config"];
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
// ---------------------------------------------------------------------------
|
|
126
|
+
// Tests.
|
|
127
|
+
// ---------------------------------------------------------------------------
|
|
128
|
+
|
|
129
|
+
describe("runGate — ready decision", () => {
|
|
130
|
+
test("maps model selection to slugs in order and includes sticky", async () => {
|
|
131
|
+
const calls: ProviderCall[] = [];
|
|
132
|
+
const provider = makeProvider(
|
|
133
|
+
// Model selects b, a (its own order). Sticky `c` is omitted by the
|
|
134
|
+
// model but must survive in the final selection.
|
|
135
|
+
gateToolResponse({ decision: "ready", selected_slugs: ["b", "a"] }),
|
|
136
|
+
calls,
|
|
137
|
+
);
|
|
138
|
+
|
|
139
|
+
const result = await runGate({
|
|
140
|
+
input: makeInput(),
|
|
141
|
+
candidates: new Set(["a", "b", "c"]),
|
|
142
|
+
sticky: new Set(["c"]),
|
|
143
|
+
passNumber: 1,
|
|
144
|
+
provider,
|
|
145
|
+
});
|
|
146
|
+
|
|
147
|
+
expect(result.decision).toEqual({ decision: "ready" });
|
|
148
|
+
// Model order preserved (b, a), then omitted sticky appended (c).
|
|
149
|
+
expect(result.selectedSlugs).toEqual(["b", "a", "c"]);
|
|
150
|
+
expect(calls).toHaveLength(1);
|
|
151
|
+
});
|
|
152
|
+
|
|
153
|
+
test("forces tool_choice on decide_selection and surfaces candidates", async () => {
|
|
154
|
+
const calls: ProviderCall[] = [];
|
|
155
|
+
const provider = makeProvider(
|
|
156
|
+
gateToolResponse({ decision: "ready", selected_slugs: ["a"] }),
|
|
157
|
+
calls,
|
|
158
|
+
);
|
|
159
|
+
|
|
160
|
+
await runGate({
|
|
161
|
+
input: makeInput({ nowText: "NOW-MARKER" }),
|
|
162
|
+
candidates: new Set(["a", "b"]),
|
|
163
|
+
sticky: new Set(),
|
|
164
|
+
passNumber: 3,
|
|
165
|
+
provider,
|
|
166
|
+
});
|
|
167
|
+
|
|
168
|
+
const call = calls[0];
|
|
169
|
+
expect(call.options?.config?.tool_choice).toEqual({
|
|
170
|
+
type: "tool",
|
|
171
|
+
name: "decide_selection",
|
|
172
|
+
});
|
|
173
|
+
expect(call.options?.config?.callSite).toBe("memoryV3Gate");
|
|
174
|
+
expect(call.tools?.[0].name).toBe("decide_selection");
|
|
175
|
+
const userText = call.messages[0].content
|
|
176
|
+
.map((b) => (b.type === "text" ? b.text : ""))
|
|
177
|
+
.join("\n");
|
|
178
|
+
expect(userText).toContain("NOW-MARKER");
|
|
179
|
+
expect(userText).toContain("a");
|
|
180
|
+
expect(userText).toContain("b");
|
|
181
|
+
});
|
|
182
|
+
|
|
183
|
+
test("includes the just-arrived turn so the selection is query-aware", async () => {
|
|
184
|
+
const calls: ProviderCall[] = [];
|
|
185
|
+
const provider = makeProvider(
|
|
186
|
+
gateToolResponse({ decision: "ready", selected_slugs: ["a"] }),
|
|
187
|
+
calls,
|
|
188
|
+
);
|
|
189
|
+
|
|
190
|
+
await runGate({
|
|
191
|
+
input: makeInput({
|
|
192
|
+
recentTurnPairs: [
|
|
193
|
+
{
|
|
194
|
+
assistantMessage: "an earlier reply",
|
|
195
|
+
userMessage: "tell me about the people you know",
|
|
196
|
+
},
|
|
197
|
+
],
|
|
198
|
+
}),
|
|
199
|
+
candidates: new Set(["a", "b"]),
|
|
200
|
+
sticky: new Set(),
|
|
201
|
+
passNumber: 1,
|
|
202
|
+
provider,
|
|
203
|
+
});
|
|
204
|
+
|
|
205
|
+
const userText = calls[0].messages[0].content
|
|
206
|
+
.map((b) => (b.type === "text" ? b.text : ""))
|
|
207
|
+
.join("\n");
|
|
208
|
+
// The gate must see the user's actual question, not just NOW + slugs.
|
|
209
|
+
expect(userText).toContain("<last_turn>");
|
|
210
|
+
expect(userText).toContain("tell me about the people you know");
|
|
211
|
+
expect(userText).toContain("an earlier reply");
|
|
212
|
+
});
|
|
213
|
+
|
|
214
|
+
test("drops a model-selected slug outside the candidate set", async () => {
|
|
215
|
+
const calls: ProviderCall[] = [];
|
|
216
|
+
const provider = makeProvider(
|
|
217
|
+
gateToolResponse({ decision: "ready", selected_slugs: ["a", "ghost"] }),
|
|
218
|
+
calls,
|
|
219
|
+
);
|
|
220
|
+
|
|
221
|
+
const result = await runGate({
|
|
222
|
+
input: makeInput(),
|
|
223
|
+
candidates: new Set(["a", "b"]),
|
|
224
|
+
sticky: new Set(),
|
|
225
|
+
passNumber: 1,
|
|
226
|
+
provider,
|
|
227
|
+
});
|
|
228
|
+
|
|
229
|
+
expect(result.selectedSlugs).toEqual(["a"]);
|
|
230
|
+
});
|
|
231
|
+
|
|
232
|
+
test("forwards an abort signal to the provider call", async () => {
|
|
233
|
+
const calls: ProviderCall[] = [];
|
|
234
|
+
const controller = new AbortController();
|
|
235
|
+
controller.abort();
|
|
236
|
+
const provider = makeProvider(
|
|
237
|
+
gateToolResponse({ decision: "ready", selected_slugs: ["a"] }),
|
|
238
|
+
calls,
|
|
239
|
+
);
|
|
240
|
+
|
|
241
|
+
// Aborted signal makes the stub throw → gate fails open (ready, all).
|
|
242
|
+
const result = await runGate({
|
|
243
|
+
input: makeInput({ signal: controller.signal }),
|
|
244
|
+
candidates: new Set(["a", "b"]),
|
|
245
|
+
sticky: new Set(),
|
|
246
|
+
passNumber: 1,
|
|
247
|
+
provider,
|
|
248
|
+
});
|
|
249
|
+
|
|
250
|
+
expect(calls[0].options?.signal).toBe(controller.signal);
|
|
251
|
+
expect(result.decision).toEqual({ decision: "ready" });
|
|
252
|
+
expect(result.selectedSlugs).toEqual(["a", "b"]);
|
|
253
|
+
});
|
|
254
|
+
});
|
|
255
|
+
|
|
256
|
+
describe("runGate — candidate summaries", () => {
|
|
257
|
+
test("renders candidates as `slug — summary` when summaryBySlug is provided", async () => {
|
|
258
|
+
const calls: ProviderCall[] = [];
|
|
259
|
+
const provider = makeProvider(
|
|
260
|
+
gateToolResponse({ decision: "ready", selected_slugs: ["a/one"] }),
|
|
261
|
+
calls,
|
|
262
|
+
);
|
|
263
|
+
|
|
264
|
+
const result = await runGate({
|
|
265
|
+
input: makeInput(),
|
|
266
|
+
candidates: new Set(["a/one", "b/two"]),
|
|
267
|
+
sticky: new Set(),
|
|
268
|
+
passNumber: 1,
|
|
269
|
+
summaryBySlug: new Map([
|
|
270
|
+
["a/one", "first summary"],
|
|
271
|
+
["b/two", "second summary"],
|
|
272
|
+
]),
|
|
273
|
+
provider,
|
|
274
|
+
});
|
|
275
|
+
|
|
276
|
+
// The model still answers in bare slugs (the enum is slug-only).
|
|
277
|
+
expect(result.selectedSlugs).toEqual(["a/one"]);
|
|
278
|
+
const userText = calls[0].messages[0].content
|
|
279
|
+
.map((b) => (b.type === "text" ? b.text : ""))
|
|
280
|
+
.join("\n");
|
|
281
|
+
expect(userText).toContain("a/one — first summary");
|
|
282
|
+
expect(userText).toContain("b/two — second summary");
|
|
283
|
+
});
|
|
284
|
+
|
|
285
|
+
test("falls back to the bare slug when no summary is available", async () => {
|
|
286
|
+
const calls: ProviderCall[] = [];
|
|
287
|
+
const provider = makeProvider(
|
|
288
|
+
gateToolResponse({ decision: "ready", selected_slugs: [] }),
|
|
289
|
+
calls,
|
|
290
|
+
);
|
|
291
|
+
|
|
292
|
+
await runGate({
|
|
293
|
+
input: makeInput(),
|
|
294
|
+
candidates: new Set(["a/one"]),
|
|
295
|
+
sticky: new Set(),
|
|
296
|
+
passNumber: 1,
|
|
297
|
+
provider,
|
|
298
|
+
});
|
|
299
|
+
|
|
300
|
+
const userText = calls[0].messages[0].content
|
|
301
|
+
.map((b) => (b.type === "text" ? b.text : ""))
|
|
302
|
+
.join("\n");
|
|
303
|
+
expect(userText).toContain("a/one");
|
|
304
|
+
expect(userText).not.toContain("a/one —");
|
|
305
|
+
});
|
|
306
|
+
});
|
|
307
|
+
|
|
308
|
+
describe("runGate — more decision", () => {
|
|
309
|
+
test("surfaces generated follow-up questions", async () => {
|
|
310
|
+
const calls: ProviderCall[] = [];
|
|
311
|
+
const provider = makeProvider(
|
|
312
|
+
gateToolResponse({
|
|
313
|
+
decision: "more",
|
|
314
|
+
selected_slugs: ["a"],
|
|
315
|
+
questions: ["What is the user's deadline?", "Who else is involved?"],
|
|
316
|
+
}),
|
|
317
|
+
calls,
|
|
318
|
+
);
|
|
319
|
+
|
|
320
|
+
const result = await runGate({
|
|
321
|
+
input: makeInput(),
|
|
322
|
+
candidates: new Set(["a", "b"]),
|
|
323
|
+
sticky: new Set(),
|
|
324
|
+
passNumber: 1,
|
|
325
|
+
provider,
|
|
326
|
+
});
|
|
327
|
+
|
|
328
|
+
expect(result.decision).toEqual({
|
|
329
|
+
decision: "more",
|
|
330
|
+
questions: ["What is the user's deadline?", "Who else is involved?"],
|
|
331
|
+
});
|
|
332
|
+
// Selection is still returned alongside the "more" verdict.
|
|
333
|
+
expect(result.selectedSlugs).toEqual(["a"]);
|
|
334
|
+
});
|
|
335
|
+
|
|
336
|
+
test("omits questions array when the model gave none (or only blanks)", async () => {
|
|
337
|
+
const calls: ProviderCall[] = [];
|
|
338
|
+
const provider = makeProvider(
|
|
339
|
+
gateToolResponse({
|
|
340
|
+
decision: "more",
|
|
341
|
+
selected_slugs: ["a"],
|
|
342
|
+
questions: [" ", ""],
|
|
343
|
+
}),
|
|
344
|
+
calls,
|
|
345
|
+
);
|
|
346
|
+
|
|
347
|
+
const result = await runGate({
|
|
348
|
+
input: makeInput(),
|
|
349
|
+
candidates: new Set(["a"]),
|
|
350
|
+
sticky: new Set(),
|
|
351
|
+
passNumber: 1,
|
|
352
|
+
provider,
|
|
353
|
+
});
|
|
354
|
+
|
|
355
|
+
expect(result.decision).toEqual({ decision: "more" });
|
|
356
|
+
});
|
|
357
|
+
|
|
358
|
+
test("preserves sticky even on a more decision", async () => {
|
|
359
|
+
const calls: ProviderCall[] = [];
|
|
360
|
+
const provider = makeProvider(
|
|
361
|
+
gateToolResponse({
|
|
362
|
+
decision: "more",
|
|
363
|
+
selected_slugs: ["a"],
|
|
364
|
+
questions: ["follow-up?"],
|
|
365
|
+
}),
|
|
366
|
+
calls,
|
|
367
|
+
);
|
|
368
|
+
|
|
369
|
+
const result = await runGate({
|
|
370
|
+
input: makeInput(),
|
|
371
|
+
candidates: new Set(["a", "sticky-page"]),
|
|
372
|
+
sticky: new Set(["sticky-page"]),
|
|
373
|
+
passNumber: 1,
|
|
374
|
+
provider,
|
|
375
|
+
});
|
|
376
|
+
|
|
377
|
+
expect(result.selectedSlugs).toContain("sticky-page");
|
|
378
|
+
});
|
|
379
|
+
});
|
|
380
|
+
|
|
381
|
+
describe("runGate — system prompt", () => {
|
|
382
|
+
test("uses the bundled default when no override is configured", async () => {
|
|
383
|
+
const calls: ProviderCall[] = [];
|
|
384
|
+
const provider = makeProvider(
|
|
385
|
+
gateToolResponse({ decision: "ready", selected_slugs: ["a"] }),
|
|
386
|
+
calls,
|
|
387
|
+
);
|
|
388
|
+
|
|
389
|
+
await runGate({
|
|
390
|
+
input: makeInput(),
|
|
391
|
+
candidates: new Set(["a", "b"]),
|
|
392
|
+
sticky: new Set(),
|
|
393
|
+
passNumber: 1,
|
|
394
|
+
provider,
|
|
395
|
+
});
|
|
396
|
+
|
|
397
|
+
expect(calls[0].systemPrompt).toBe(GATE_SYSTEM_PROMPT);
|
|
398
|
+
});
|
|
399
|
+
|
|
400
|
+
test("uses the configured inline override as the system prompt", async () => {
|
|
401
|
+
const calls: ProviderCall[] = [];
|
|
402
|
+
const provider = makeProvider(
|
|
403
|
+
gateToolResponse({ decision: "ready", selected_slugs: ["a"] }),
|
|
404
|
+
calls,
|
|
405
|
+
);
|
|
406
|
+
|
|
407
|
+
const override = "CUSTOM GATE PROMPT — finalize aggressively.";
|
|
408
|
+
await runGate({
|
|
409
|
+
input: makeInput({ config: configWithGateOverride(override) }),
|
|
410
|
+
candidates: new Set(["a", "b"]),
|
|
411
|
+
sticky: new Set(),
|
|
412
|
+
passNumber: 1,
|
|
413
|
+
provider,
|
|
414
|
+
});
|
|
415
|
+
|
|
416
|
+
expect(calls[0].systemPrompt).toBe(override);
|
|
417
|
+
expect(calls[0].systemPrompt).not.toBe(GATE_SYSTEM_PROMPT);
|
|
418
|
+
});
|
|
419
|
+
});
|
|
420
|
+
|
|
421
|
+
describe("runGate — fail-safe", () => {
|
|
422
|
+
test("provider === null selects all candidates with sticky and ready", async () => {
|
|
423
|
+
const result = await runGate({
|
|
424
|
+
input: makeInput(),
|
|
425
|
+
candidates: new Set(["a", "b", "c"]),
|
|
426
|
+
sticky: new Set(["c"]),
|
|
427
|
+
passNumber: 1,
|
|
428
|
+
provider: null,
|
|
429
|
+
});
|
|
430
|
+
|
|
431
|
+
expect(result.decision).toEqual({ decision: "ready" });
|
|
432
|
+
expect([...result.selectedSlugs].sort()).toEqual(["a", "b", "c"]);
|
|
433
|
+
expect(result.selectedSlugs).toContain("c");
|
|
434
|
+
});
|
|
435
|
+
|
|
436
|
+
test("provider throw falls back to ready + all candidates", async () => {
|
|
437
|
+
const result = await runGate({
|
|
438
|
+
input: makeInput(),
|
|
439
|
+
candidates: new Set(["a", "b"]),
|
|
440
|
+
sticky: new Set(),
|
|
441
|
+
passNumber: 1,
|
|
442
|
+
provider: makeThrowingProvider(),
|
|
443
|
+
});
|
|
444
|
+
|
|
445
|
+
expect(result.decision).toEqual({ decision: "ready" });
|
|
446
|
+
expect([...result.selectedSlugs].sort()).toEqual(["a", "b"]);
|
|
447
|
+
});
|
|
448
|
+
|
|
449
|
+
test("missing tool_use block falls back to ready + all candidates", async () => {
|
|
450
|
+
const calls: ProviderCall[] = [];
|
|
451
|
+
const result = await runGate({
|
|
452
|
+
input: makeInput(),
|
|
453
|
+
candidates: new Set(["a", "b"]),
|
|
454
|
+
sticky: new Set(),
|
|
455
|
+
passNumber: 1,
|
|
456
|
+
provider: makeProvider(textOnlyResponse(), calls),
|
|
457
|
+
});
|
|
458
|
+
|
|
459
|
+
expect(result.decision).toEqual({ decision: "ready" });
|
|
460
|
+
expect([...result.selectedSlugs].sort()).toEqual(["a", "b"]);
|
|
461
|
+
});
|
|
462
|
+
|
|
463
|
+
test("schema-mismatched tool input falls back to ready + all candidates", async () => {
|
|
464
|
+
const calls: ProviderCall[] = [];
|
|
465
|
+
const result = await runGate({
|
|
466
|
+
input: makeInput(),
|
|
467
|
+
candidates: new Set(["a", "b"]),
|
|
468
|
+
sticky: new Set(),
|
|
469
|
+
passNumber: 1,
|
|
470
|
+
// `decision` is required; missing it fails the Zod schema.
|
|
471
|
+
provider: makeProvider(
|
|
472
|
+
gateToolResponse({ selected_slugs: ["a"] }),
|
|
473
|
+
calls,
|
|
474
|
+
),
|
|
475
|
+
});
|
|
476
|
+
|
|
477
|
+
expect(result.decision).toEqual({ decision: "ready" });
|
|
478
|
+
expect([...result.selectedSlugs].sort()).toEqual(["a", "b"]);
|
|
479
|
+
});
|
|
480
|
+
});
|
|
481
|
+
|
|
482
|
+
describe("runGate — capture", () => {
|
|
483
|
+
test("emits one record with the gate's input + raw response", async () => {
|
|
484
|
+
const calls: ProviderCall[] = [];
|
|
485
|
+
const captured: Omit<LlmCallRecord, "pass">[] = [];
|
|
486
|
+
const provider = makeProvider(
|
|
487
|
+
gateToolResponse({ decision: "ready", selected_slugs: ["a"] }),
|
|
488
|
+
calls,
|
|
489
|
+
);
|
|
490
|
+
|
|
491
|
+
await runGate({
|
|
492
|
+
input: makeInput(),
|
|
493
|
+
candidates: new Set(["a", "b"]),
|
|
494
|
+
sticky: new Set(),
|
|
495
|
+
passNumber: 1,
|
|
496
|
+
provider,
|
|
497
|
+
capture: (record) => captured.push(record),
|
|
498
|
+
});
|
|
499
|
+
|
|
500
|
+
expect(captured).toHaveLength(1);
|
|
501
|
+
const rec = captured[0]!;
|
|
502
|
+
expect(rec.lane).toBe("gate");
|
|
503
|
+
expect(rec.callSite).toBe("memoryV3Gate");
|
|
504
|
+
expect(rec.request.tools[0]!.name).toBe("decide_selection");
|
|
505
|
+
expect(rec.request.systemPrompt.length).toBeGreaterThan(0);
|
|
506
|
+
expect(rec.request.messages).toHaveLength(1);
|
|
507
|
+
expect(rec.response.stopReason).toBe("tool_use");
|
|
508
|
+
expect(rec.ms).toBeGreaterThanOrEqual(0);
|
|
509
|
+
});
|
|
510
|
+
|
|
511
|
+
test("emits nothing when the provider is unavailable (fail-safe path)", async () => {
|
|
512
|
+
const captured: Omit<LlmCallRecord, "pass">[] = [];
|
|
513
|
+
await runGate({
|
|
514
|
+
input: makeInput(),
|
|
515
|
+
candidates: new Set(["a"]),
|
|
516
|
+
sticky: new Set(),
|
|
517
|
+
passNumber: 1,
|
|
518
|
+
provider: null,
|
|
519
|
+
capture: (record) => captured.push(record),
|
|
520
|
+
});
|
|
521
|
+
expect(captured).toHaveLength(0);
|
|
522
|
+
});
|
|
523
|
+
});
|
|
524
|
+
|
|
525
|
+
describe("runGate — reasoning field", () => {
|
|
526
|
+
test("exposes an optional reasoning property in the forced tool schema", async () => {
|
|
527
|
+
const calls: ProviderCall[] = [];
|
|
528
|
+
const provider = makeProvider(
|
|
529
|
+
gateToolResponse({ decision: "ready", selected_slugs: ["a"] }),
|
|
530
|
+
calls,
|
|
531
|
+
);
|
|
532
|
+
|
|
533
|
+
await runGate({
|
|
534
|
+
input: makeInput(),
|
|
535
|
+
candidates: new Set(["a", "b"]),
|
|
536
|
+
sticky: new Set(),
|
|
537
|
+
passNumber: 1,
|
|
538
|
+
provider,
|
|
539
|
+
});
|
|
540
|
+
|
|
541
|
+
const schema = calls[0].tools![0].input_schema as {
|
|
542
|
+
properties: Record<string, { type?: string }>;
|
|
543
|
+
required?: string[];
|
|
544
|
+
};
|
|
545
|
+
expect(schema.properties.reasoning?.type).toBe("string");
|
|
546
|
+
// Reasoning is purely additive — the model may omit it.
|
|
547
|
+
expect(schema.required ?? []).not.toContain("reasoning");
|
|
548
|
+
});
|
|
549
|
+
|
|
550
|
+
test("accepts model-supplied reasoning without altering the decision", async () => {
|
|
551
|
+
const calls: ProviderCall[] = [];
|
|
552
|
+
const provider = makeProvider(
|
|
553
|
+
gateToolResponse({
|
|
554
|
+
decision: "ready",
|
|
555
|
+
selected_slugs: ["b", "a"],
|
|
556
|
+
reasoning: "kept the two query-relevant pages, dropped the rest",
|
|
557
|
+
}),
|
|
558
|
+
calls,
|
|
559
|
+
);
|
|
560
|
+
|
|
561
|
+
const result = await runGate({
|
|
562
|
+
input: makeInput(),
|
|
563
|
+
candidates: new Set(["a", "b", "c"]),
|
|
564
|
+
sticky: new Set(["c"]),
|
|
565
|
+
passNumber: 1,
|
|
566
|
+
provider,
|
|
567
|
+
});
|
|
568
|
+
|
|
569
|
+
// Reasoning is ignored by control flow: decision + ordered selection
|
|
570
|
+
// (model order, omitted sticky appended) are unchanged.
|
|
571
|
+
expect(result.decision).toEqual({ decision: "ready" });
|
|
572
|
+
expect(result.selectedSlugs).toEqual(["b", "a", "c"]);
|
|
573
|
+
});
|
|
574
|
+
});
|