@vellumai/assistant 0.8.5 → 0.8.6
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/AGENTS.md +33 -1
- package/ARCHITECTURE.md +1 -1
- package/bunfig.toml +6 -1
- package/docs/credential-execution-service.md +6 -6
- package/docs/plugins.md +4 -3
- package/node_modules/@vellumai/skill-host-contracts/src/client.ts +12 -13
- package/node_modules/@vellumai/skill-host-contracts/src/skill-host.ts +4 -1
- package/node_modules/@vellumai/skill-host-contracts/src/tool-types.ts +16 -14
- package/openapi.yaml +1900 -166
- package/package.json +1 -1
- package/src/__tests__/actor-token-service.test.ts +3 -2
- package/src/__tests__/agent-loop-exit-reason.test.ts +102 -9
- package/src/__tests__/agent-loop-override-profile.test.ts +2 -1
- package/src/__tests__/agent-wake-disk-pressure-callsite.test.ts +1 -0
- package/src/__tests__/agent-wake-override-profile.test.ts +1 -0
- package/src/__tests__/always-loaded-tools-guard.test.ts +2 -2
- package/src/__tests__/annotate-risk-options.test.ts +1 -0
- package/src/__tests__/approval-cascade.test.ts +1 -0
- package/src/__tests__/approval-routes-http.test.ts +9 -13
- package/src/__tests__/assert-not-live-db.ts +79 -0
- package/src/__tests__/assistant-feature-flags-integration.test.ts +9 -25
- package/src/__tests__/audit-log-rotation.test.ts +2 -2
- package/src/__tests__/auto-analysis-end-to-end.test.ts +6 -6
- package/src/__tests__/background-workers-disk-pressure.test.ts +5 -8
- package/src/__tests__/browser-skill-endstate.test.ts +3 -3
- package/src/__tests__/btw-routes.test.ts +3 -2
- package/src/__tests__/call-controller.test.ts +3 -2
- package/src/__tests__/channel-approval-routes.test.ts +3 -2
- package/src/__tests__/channel-guardian.test.ts +3 -2
- package/src/__tests__/channel-readiness-slack-remote.test.ts +175 -0
- package/src/__tests__/channel-reply-delivery.test.ts +35 -0
- package/src/__tests__/channel-retry-sweep.test.ts +320 -3
- package/src/__tests__/checker.test.ts +12 -12
- package/src/__tests__/compaction-events.test.ts +1 -0
- package/src/__tests__/compaction-trail-store.test.ts +264 -0
- package/src/__tests__/compactor-call-site-logging.test.ts +1 -0
- package/src/__tests__/compactor-preserved-tail-count.test.ts +1 -0
- package/src/__tests__/computer-use-skill-manifest-regression.test.ts +7 -5
- package/src/__tests__/computer-use-tools.test.ts +12 -14
- package/src/__tests__/config-loader-backfill.test.ts +13 -28
- package/src/__tests__/config-loader-corrupt.test.ts +5 -5
- package/src/__tests__/config-loader-platform-defaults.test.ts +93 -26
- package/src/__tests__/config-loader-quarantine-bulletin.test.ts +3 -3
- package/src/__tests__/config-managed-gemini-defaults.test.ts +3 -4
- package/src/__tests__/config-schema.test.ts +10 -10
- package/src/__tests__/connection-model-compat.test.ts +83 -0
- package/src/__tests__/contacts-tools.test.ts +3 -2
- package/src/__tests__/context-token-estimator.test.ts +22 -0
- package/src/__tests__/conversation-abort-tool-results.test.ts +5 -0
- package/src/__tests__/conversation-agent-loop-disk-pressure.test.ts +1 -0
- package/src/__tests__/conversation-agent-loop-handlers-max-tokens.test.ts +55 -0
- package/src/__tests__/conversation-agent-loop-inference-profile.test.ts +1 -0
- package/src/__tests__/conversation-agent-loop-overflow.test.ts +34 -0
- package/src/__tests__/conversation-agent-loop.test.ts +488 -2
- package/src/__tests__/conversation-analysis-routes.test.ts +1 -0
- package/src/__tests__/conversation-app-control-instantiation.test.ts +29 -19
- package/src/__tests__/conversation-app-control-lifecycle.test.ts +1 -0
- package/src/__tests__/conversation-attention-store.test.ts +101 -0
- package/src/__tests__/conversation-attention-telegram.test.ts +3 -2
- package/src/__tests__/conversation-confirmation-signals.test.ts +1 -0
- package/src/__tests__/conversation-error.test.ts +30 -0
- package/src/__tests__/conversation-fork-crud.test.ts +69 -8
- package/src/__tests__/conversation-fork-route.test.ts +3 -2
- package/src/__tests__/conversation-history-web-search.test.ts +1 -0
- package/src/__tests__/conversation-inference-profile-list.test.ts +3 -2
- package/src/__tests__/conversation-inference-profile-route.test.ts +3 -2
- package/src/__tests__/conversation-lifecycle.test.ts +1 -0
- package/src/__tests__/conversation-list-source.test.ts +3 -2
- package/src/__tests__/conversation-load-history-repair.test.ts +2 -1
- package/src/__tests__/conversation-load-history-stripped.test.ts +1 -0
- package/src/__tests__/conversation-pairing.test.ts +53 -0
- package/src/__tests__/conversation-process-app-control-preactivation.test.ts +26 -7
- package/src/__tests__/conversation-process-callsite.test.ts +1 -0
- package/src/__tests__/conversation-provider-retry-repair.test.ts +5 -0
- package/src/__tests__/conversation-queue.test.ts +333 -291
- package/src/__tests__/conversation-routes-disk-view.test.ts +3 -18
- package/src/__tests__/conversation-routes-guardian-reply.test.ts +33 -8
- package/src/__tests__/conversation-routes-slash-commands.test.ts +33 -2
- package/src/__tests__/conversation-runtime-assembly.test.ts +78 -0
- package/src/__tests__/conversation-skill-tools.test.ts +38 -142
- package/src/__tests__/conversation-slash-queue.test.ts +84 -32
- package/src/__tests__/conversation-slash-unknown.test.ts +5 -0
- package/src/__tests__/conversation-speed-override.test.ts +1 -0
- package/src/__tests__/conversation-surfaces-action-delivery.test.ts +46 -0
- package/src/__tests__/conversation-surfaces-data-persist.test.ts +1 -0
- package/src/__tests__/conversation-surfaces-standalone-payloads.test.ts +6 -3
- package/src/__tests__/conversation-surfaces-standalone.test.ts +6 -3
- package/src/__tests__/conversation-surfaces-state-update.test.ts +3 -3
- package/src/__tests__/conversation-surfaces-table-action.test.ts +7 -17
- package/src/__tests__/conversation-sync-tags.test.ts +128 -12
- package/src/__tests__/conversation-title-service.test.ts +1 -0
- package/src/__tests__/conversation-tool-setup-app-refresh.test.ts +30 -0
- package/src/__tests__/conversation-usage.test.ts +1 -0
- package/src/__tests__/conversation-workspace-cache-state.test.ts +1 -0
- package/src/__tests__/conversation-workspace-injection.test.ts +5 -0
- package/src/__tests__/conversation-workspace-tool-tracking.test.ts +5 -0
- package/src/__tests__/credential-broker-browser-fill.test.ts +3 -3
- package/src/__tests__/credential-broker-server-use.test.ts +5 -5
- package/src/__tests__/credential-execution-client.test.ts +72 -1
- package/src/__tests__/credential-execution-feature-gates.test.ts +10 -12
- package/src/__tests__/credential-health-service.test.ts +252 -3
- package/src/__tests__/credential-security-invariants.test.ts +5 -5
- package/src/__tests__/credential-vault-unit.test.ts +19 -19
- package/src/__tests__/credential-vault.test.ts +5 -5
- package/src/__tests__/cross-provider-web-search.test.ts +56 -2
- package/src/__tests__/db-connection-isolation.test.ts +7 -6
- package/src/__tests__/db-conversation-fork-lineage-migration.test.ts +8 -10
- package/src/__tests__/db-conversation-inference-profile-migration.test.ts +7 -10
- package/src/__tests__/db-llm-request-log-provider-migration.test.ts +9 -15
- package/src/__tests__/db-test-helpers.ts +58 -0
- package/src/__tests__/disk-pressure-guard.test.ts +58 -41
- package/src/__tests__/disk-pressure-lifecycle.test.ts +13 -10
- package/src/__tests__/disk-pressure-routes.test.ts +0 -33
- package/src/__tests__/disk-pressure-tools.test.ts +0 -4
- package/src/__tests__/dm-persistence.test.ts +26 -40
- package/src/__tests__/document-create-dedupe.test.ts +189 -0
- package/src/__tests__/document-find-replace.test.ts +3 -2
- package/src/__tests__/document-tool-security.test.ts +81 -2
- package/src/__tests__/dynamic-skill-workflow-prompt.test.ts +5 -4
- package/src/__tests__/encrypted-store-test-helpers.ts +56 -0
- package/src/__tests__/encrypted-store.test.ts +11 -9
- package/src/__tests__/feature-flag-test-helpers.ts +53 -0
- package/src/__tests__/filing-service.test.ts +1 -0
- package/src/__tests__/first-greeting.test.ts +62 -12
- package/src/__tests__/gateway-flag-listener.test.ts +0 -1
- package/src/__tests__/gemini-provider.test.ts +26 -0
- package/src/__tests__/guardian-action-sweep.test.ts +3 -2
- package/src/__tests__/guardian-outbound-http.test.ts +3 -2
- package/src/__tests__/handlers-skills-memory-v2-reseed.test.ts +48 -3
- package/src/__tests__/handlers-user-message-approval-consumption.test.ts +1 -0
- package/src/__tests__/heartbeat-disk-pressure.test.ts +1 -0
- package/src/__tests__/heartbeat-service.test.ts +1 -0
- package/src/__tests__/helpers/mock-logger.ts +26 -0
- package/src/__tests__/host-bash-routes.test.ts +1 -0
- package/src/__tests__/host-cu-routes-targeted.test.ts +1 -0
- package/src/__tests__/host-file-routes-targeted.test.ts +1 -0
- package/src/__tests__/host-shell-tool.test.ts +5 -4
- package/src/__tests__/host-transfer-routes-targeted.test.ts +1 -0
- package/src/__tests__/http-conversation-lineage.test.ts +3 -2
- package/src/__tests__/http-user-message-parity.test.ts +29 -7
- package/src/__tests__/identity-intro-cache.test.ts +133 -22
- package/src/__tests__/inbound-slack-persistence.test.ts +44 -72
- package/src/__tests__/inference-profile-reaper.test.ts +3 -2
- package/src/__tests__/inference-profile-session-ipc.test.ts +3 -2
- package/src/__tests__/injector-disk-pressure.test.ts +3 -17
- package/src/__tests__/inline-skill-load-permissions.test.ts +4 -4
- package/src/__tests__/list-messages-hidden-metadata.test.ts +80 -0
- package/src/__tests__/llm-context-normalization.test.ts +42 -0
- package/src/__tests__/llm-resolver.test.ts +331 -0
- package/src/__tests__/llm-schema.test.ts +1 -1
- package/src/__tests__/manual-token-reconciliation.test.ts +76 -1
- package/src/__tests__/mcp-abort-signal.test.ts +14 -0
- package/src/__tests__/mcp-client-auth.test.ts +14 -0
- package/src/__tests__/messaging-send-tool.test.ts +1 -0
- package/src/__tests__/migration-import-from-url.test.ts +3 -3
- package/src/__tests__/mock-gateway-ipc.ts +18 -2
- package/src/__tests__/model-intents.test.ts +3 -3
- package/src/__tests__/native-web-search.test.ts +30 -2
- package/src/__tests__/notification-deep-link.test.ts +62 -0
- package/src/__tests__/oauth-commands-routes.test.ts +37 -0
- package/src/__tests__/oauth-provider-visibility.test.ts +8 -8
- package/src/__tests__/oauth-store.test.ts +3 -2
- package/src/__tests__/onboarding-template-contract.test.ts +3 -2
- package/src/__tests__/openai-provider.test.ts +8 -9
- package/src/__tests__/openai-responses-provider.test.ts +70 -10
- package/src/__tests__/openrouter-provider-only.test.ts +27 -5
- package/src/__tests__/outbound-slack-persistence.test.ts +46 -1
- package/src/__tests__/persistence-pipeline.test.ts +139 -1
- package/src/__tests__/persistence-secret-redaction.test.ts +83 -12
- package/src/__tests__/plugin-bootstrap.test.ts +9 -11
- package/src/__tests__/plugin-tool-contribution.test.ts +41 -38
- package/src/__tests__/process-message-background-slack.test.ts +21 -16
- package/src/__tests__/process-message-display-content.test.ts +19 -22
- package/src/__tests__/provider-catalog-visibility.test.ts +9 -9
- package/src/__tests__/provider-platform-proxy-integration.test.ts +216 -4
- package/src/__tests__/provider-registry-ollama.test.ts +45 -22
- package/src/__tests__/recording-handler.test.ts +1 -0
- package/src/__tests__/regenerate-fire-and-forget-trace.test.ts +1 -0
- package/src/__tests__/registry.test.ts +82 -76
- package/src/__tests__/relay-server.test.ts +10 -10
- package/src/__tests__/runtime-attachment-metadata.test.ts +3 -2
- package/src/__tests__/schedule-store.test.ts +16 -1
- package/src/__tests__/scheduler-reuse-conversation.test.ts +48 -3
- package/src/__tests__/secret-ingress-http.test.ts +5 -1
- package/src/__tests__/secure-keys.test.ts +3 -3
- package/src/__tests__/send-endpoint-busy.test.ts +81 -42
- package/src/__tests__/server-history-render.test.ts +4 -1
- package/src/__tests__/skill-feature-flags-integration.test.ts +8 -10
- package/src/__tests__/skill-feature-flags.test.ts +14 -16
- package/src/__tests__/skill-load-feature-flag.test.ts +5 -5
- package/src/__tests__/skill-projection-feature-flag.test.ts +44 -30
- package/src/__tests__/skill-projection.benchmark.test.ts +5 -7
- package/src/__tests__/skill-tool-factory.test.ts +96 -95
- package/src/__tests__/slack-channel-config.test.ts +3 -3
- package/src/__tests__/subagent-call-site-routing.test.ts +11 -3
- package/src/__tests__/subagent-disposal.test.ts +27 -8
- package/src/__tests__/subagent-fork-notifications.test.ts +24 -9
- package/src/__tests__/subagent-fork-spawn.test.ts +13 -4
- package/src/__tests__/subagent-manager-notify.test.ts +20 -8
- package/src/__tests__/subagent-notify-parent.test.ts +5 -4
- package/src/__tests__/subagent-spawn-tool-fork.test.ts +58 -0
- package/src/__tests__/subagent-tools.test.ts +2 -1
- package/src/__tests__/suggestion-routes.test.ts +1 -0
- package/src/__tests__/system-prompt.test.ts +38 -0
- package/src/__tests__/test-preload-verifier.ts +68 -0
- package/src/__tests__/test-preload.ts +32 -39
- package/src/__tests__/tool-executor-lifecycle-events.test.ts +20 -7
- package/src/__tests__/tool-executor.test.ts +55 -10
- package/src/__tests__/tool-preview-lifecycle.test.ts +1 -0
- package/src/__tests__/tool-result-metadata-plumbing.test.ts +1 -0
- package/src/__tests__/twilio-routes.test.ts +3 -2
- package/src/__tests__/validate-input.test.ts +381 -0
- package/src/__tests__/verification-control-plane-policy.test.ts +1 -0
- package/src/__tests__/voice-scoped-grant-consumer.test.ts +2 -1
- package/src/__tests__/voice-session-bridge.test.ts +37 -28
- package/src/__tests__/workspace-migration-090-memory-router-cost-optimized-profile.test.ts +326 -0
- package/src/__tests__/workspace-migration-091-retighten-migration-onboarding-thread.test.ts +166 -0
- package/src/acp/session-manager.ts +5 -6
- package/src/agent/loop.ts +80 -0
- package/src/api/README.md +124 -2
- package/src/api/constants/call-sites.ts +27 -0
- package/src/api/events/assistant-outbound-attachment.ts +51 -0
- package/src/api/events/assistant-text-delta.ts +32 -0
- package/src/api/events/assistant-turn-start.ts +33 -0
- package/src/api/events/document-comment-created.ts +48 -0
- package/src/api/events/document-comment-deleted.ts +24 -0
- package/src/api/events/document-comment-reopened.ts +25 -0
- package/src/api/events/document-comment-resolved.ts +27 -0
- package/src/api/events/generation-cancelled.ts +24 -0
- package/src/api/events/generation-handoff.ts +41 -0
- package/src/api/events/message-complete.ts +42 -0
- package/src/api/events/open-url.ts +30 -0
- package/src/{events → api/events}/relationship-state-updated.ts +3 -3
- package/src/api/events/tool-use-start.ts +32 -0
- package/src/api/index.ts +128 -3
- package/src/api/responses/llm-context-response.ts +39 -0
- package/src/api/responses/llm-request-log-entry.ts +93 -0
- package/src/api/responses/memory-recall-log.ts +65 -0
- package/src/api/responses/memory-v2-activation-log.ts +78 -0
- package/src/background-wake/background-wake-routes.test.ts +687 -52
- package/src/background-wake/platform-client.test.ts +308 -0
- package/src/background-wake/platform-client.ts +167 -0
- package/src/background-wake/publisher.ts +91 -0
- package/src/background-wake/runtime-registry.ts +2 -2
- package/src/background-wake/wake-intent-hooks.test.ts +282 -0
- package/src/calls/guardian-dispatch.ts +1 -0
- package/src/calls/voice-session-bridge.ts +4 -4
- package/src/cli/commands/__tests__/conversations-slack.test.ts +16 -0
- package/src/cli/commands/__tests__/notifications.test.ts +184 -40
- package/src/cli/commands/channels/__tests__/channels.test.ts +143 -0
- package/src/cli/commands/channels/index.ts +229 -0
- package/src/cli/commands/memory-v3-render.ts +147 -0
- package/src/cli/commands/memory-v3.ts +255 -4
- package/src/cli/commands/notifications.ts +365 -55
- package/src/cli/lib/open-browser.ts +7 -2
- package/src/cli/program.ts +2 -0
- package/src/config/assistant-feature-flags.ts +23 -42
- package/src/config/bundled-skills/document-editor/SKILL.md +5 -1
- package/src/config/bundled-skills/schedule/SKILL.md +1 -1
- package/src/config/bundled-skills/schedule/TOOLS.json +2 -2
- package/src/config/bundled-skills/settings/tools/open-system-settings.ts +1 -0
- package/src/config/call-site-defaults.ts +1 -1
- package/src/config/feature-flag-cache.ts +86 -0
- package/src/config/feature-flag-registry.json +17 -17
- package/src/config/llm-context-resolution.ts +10 -1
- package/src/config/llm-resolver.ts +121 -15
- package/src/config/loader.ts +4 -5
- package/src/config/schemas/__tests__/memory-v2.test.ts +15 -0
- package/src/config/schemas/heartbeat.ts +1 -1
- package/src/config/schemas/llm.ts +90 -1
- package/src/config/schemas/memory-v2.ts +26 -0
- package/src/config/schemas/services.ts +6 -2
- package/src/config/seed-inference-profiles.ts +36 -16
- package/src/context/token-estimator.ts +10 -5
- package/src/credential-execution/executable-discovery.ts +40 -0
- package/src/credential-execution/process-manager.ts +6 -2
- package/src/credential-health/credential-health-service.ts +125 -40
- package/src/daemon/__tests__/conversation-lifecycle-auto-analyze.test.ts +3 -6
- package/src/daemon/__tests__/conversation-surfaces-launch.test.ts +13 -15
- package/src/daemon/__tests__/conversation-tool-setup-exclude.test.ts +1 -2
- package/src/daemon/__tests__/daemon-skill-host.test.ts +2 -0
- package/src/daemon/__tests__/meet-manifest-loader.test.ts +25 -12
- package/src/daemon/__tests__/native-web-search-metadata.test.ts +1 -0
- package/src/daemon/__tests__/switch-inference-profile-tool.test.ts +107 -0
- package/src/daemon/__tests__/web-search-status-text.test.ts +1 -0
- package/src/daemon/conversation-agent-loop-handlers.ts +389 -68
- package/src/daemon/conversation-agent-loop.ts +132 -28
- package/src/daemon/conversation-error.ts +33 -5
- package/src/daemon/conversation-messaging.ts +84 -43
- package/src/daemon/conversation-process.ts +74 -37
- package/src/daemon/conversation-runtime-assembly.ts +29 -9
- package/src/daemon/conversation-skill-tools.ts +14 -30
- package/src/daemon/conversation-surfaces.ts +69 -34
- package/src/daemon/conversation-tool-setup.ts +33 -48
- package/src/daemon/conversation.ts +26 -46
- package/src/daemon/daemon-control.ts +1 -1
- package/src/daemon/daemon-skill-host.ts +9 -2
- package/src/daemon/disk-pressure-guard.ts +27 -29
- package/src/daemon/first-greeting.ts +31 -13
- package/src/daemon/handlers/shared.ts +6 -1
- package/src/daemon/lifecycle.ts +12 -12
- package/src/daemon/mcp-reload-service.ts +1 -1
- package/src/daemon/meet-manifest-loader.ts +10 -17
- package/src/daemon/message-types/conversations.ts +20 -22
- package/src/daemon/message-types/document-comments.ts +8 -44
- package/src/daemon/message-types/home.ts +2 -2
- package/src/daemon/message-types/integrations.ts +2 -7
- package/src/daemon/message-types/messages.ts +23 -38
- package/src/daemon/message-types/subagents.ts +6 -0
- package/src/daemon/process-message.ts +9 -9
- package/src/daemon/providers-setup.ts +1 -1
- package/src/daemon/server.ts +16 -0
- package/src/daemon/switch-inference-profile-tool.ts +13 -3
- package/src/daemon/tool-setup-types.ts +0 -6
- package/src/daemon/wake-target-adapter.ts +10 -0
- package/src/documents/document-store.ts +38 -0
- package/src/export/__tests__/transcript-formatter.test.ts +1 -0
- package/src/heartbeat/__tests__/heartbeat-service.test.ts +29 -0
- package/src/heartbeat/heartbeat-service.ts +63 -0
- package/src/home/__tests__/feed-writer.test.ts +161 -0
- package/src/home/__tests__/post-connect-feed.test.ts +1 -0
- package/src/home/__tests__/suggested-prompts.test.ts +55 -59
- package/src/home/feed-writer.ts +146 -7
- package/src/home/suggested-prompts.ts +27 -145
- package/src/ipc/__tests__/cli-ipc.test.ts +1 -0
- package/src/ipc/gateway-client.test.ts +4 -1
- package/src/ipc/skill-routes/__tests__/memory.test.ts +1 -0
- package/src/ipc/skill-routes/__tests__/registries.test.ts +36 -7
- package/src/ipc/skill-routes/memory.ts +4 -3
- package/src/ipc/skill-routes/registries.ts +28 -29
- package/src/memory/__tests__/jobs-store-enqueue-gate.test.ts +1 -0
- package/src/memory/__tests__/jobs-worker-v2-schedule.test.ts +26 -5
- package/src/memory/__tests__/memory-retrospective-enqueue.test.ts +1 -0
- package/src/memory/__tests__/memory-retrospective-job.test.ts +1 -0
- package/src/memory/__tests__/memory-retrospective-startup-cleanup.test.ts +1 -0
- package/src/memory/__tests__/memory-v2-activation-log-store.test.ts +31 -0
- package/src/memory/conversation-attention-store.ts +17 -3
- package/src/memory/conversation-crud.ts +352 -112
- package/src/memory/db-connection.ts +29 -19
- package/src/memory/db-init.ts +4 -0
- package/src/memory/db-singleton.ts +77 -0
- package/src/memory/delivery-channels.ts +82 -0
- package/src/memory/graph/__tests__/conversation-graph-memory-v2-routing.test.ts +2 -4
- package/src/memory/graph/retriever.test.ts +3 -3
- package/src/memory/job-handlers/embedding.test.ts +3 -2
- package/src/memory/jobs/__tests__/embed-concept-page.test.ts +5 -2
- package/src/memory/jobs-worker.ts +12 -1
- package/src/memory/llm-request-log-source-clickhouse.ts +80 -0
- package/src/memory/llm-request-log-source-local.ts +24 -0
- package/src/memory/llm-request-log-source.ts +31 -0
- package/src/memory/llm-request-log-store.ts +188 -3
- package/src/memory/memory-v2-activation-log-store.ts +95 -1
- package/src/memory/migrations/265-drop-provider-connection-status.ts +26 -0
- package/src/memory/migrations/266-messages-client-message-id.ts +43 -0
- package/src/memory/migrations/index.ts +2 -0
- package/src/memory/schema/conversations.ts +9 -1
- package/src/memory/schema/inference.ts +0 -1
- package/src/memory/v2/__tests__/backfill-jobs.test.ts +5 -2
- package/src/memory/v2/__tests__/harness-metrics.test.ts +9 -0
- package/src/memory/v2/__tests__/harness-replay-input.test.ts +9 -4
- package/src/memory/v2/__tests__/harness-runner.test.ts +26 -0
- package/src/memory/v2/__tests__/sweep-job.test.ts +6 -3
- package/src/memory/v2/harness/metrics.ts +5 -1
- package/src/memory/v2/harness/replay-input.ts +19 -3
- package/src/memory/v2/harness/runner.ts +6 -0
- package/src/memory/v2/harness/trace.ts +6 -0
- package/src/memory/v3/__tests__/consolidation-job.test.ts +2 -4
- package/src/memory/v3/__tests__/coretrieval-seed.test.ts +270 -0
- package/src/memory/v3/__tests__/edges.test.ts +144 -1
- package/src/memory/v3/__tests__/filter.test.ts +48 -0
- package/src/memory/v3/__tests__/gate.test.ts +96 -33
- package/src/memory/v3/__tests__/index-composition.test.ts +58 -0
- package/src/memory/v3/__tests__/loop.test.ts +250 -5
- package/src/memory/v3/__tests__/scouts.test.ts +49 -0
- package/src/memory/v3/__tests__/shadow-diff.test.ts +225 -0
- package/src/memory/v3/__tests__/shadow-middleware.test.ts +88 -2
- package/src/memory/v3/__tests__/traversal.test.ts +39 -0
- package/src/memory/v3/__tests__/tree-walk.test.ts +77 -0
- package/src/memory/v3/__tests__/validate.test.ts +32 -0
- package/src/memory/v3/coretrieval-seed.ts +240 -0
- package/src/memory/v3/edges.ts +58 -21
- package/src/memory/v3/filter.ts +27 -22
- package/src/memory/v3/gate.ts +51 -36
- package/src/memory/v3/index-composition.ts +18 -5
- package/src/memory/v3/loop.ts +65 -17
- package/src/memory/v3/scouts.ts +15 -4
- package/src/memory/v3/shadow-diff.ts +287 -0
- package/src/memory/v3/shadow-middleware.ts +44 -2
- package/src/memory/v3/traversal.ts +6 -1
- package/src/memory/v3/tree-walk.ts +6 -1
- package/src/memory/v3/validate.ts +56 -33
- package/src/notifications/__tests__/emit-signal-home-feed.test.ts +1 -0
- package/src/notifications/__tests__/home-feed-side-effect.test.ts +1 -0
- package/src/notifications/adapters/slack.ts +45 -11
- package/src/notifications/broadcaster.ts +114 -63
- package/src/notifications/conversation-pairing.ts +23 -3
- package/src/notifications/decisions-store.ts +32 -1
- package/src/notifications/deliveries-store.ts +45 -0
- package/src/notifications/edit-notification.ts +201 -0
- package/src/notifications/emit-signal.ts +11 -1
- package/src/notifications/signal.ts +10 -0
- package/src/notifications/types.ts +37 -0
- package/src/oauth/byo-connection.test.ts +67 -3
- package/src/oauth/byo-connection.ts +32 -5
- package/src/oauth/connect-orchestrator.ts +9 -0
- package/src/oauth/connection-resolver.test.ts +76 -0
- package/src/oauth/connection-resolver.ts +49 -10
- package/src/oauth/manual-token-connection.ts +51 -3
- package/src/oauth/seed-providers.ts +3 -0
- package/src/permissions/approval-policy.test.ts +19 -5
- package/src/permissions/approval-policy.ts +14 -3
- package/src/permissions/checker.ts +21 -8
- package/src/platform/client.test.ts +24 -1
- package/src/platform/client.ts +8 -0
- package/src/platform/feature-gate.ts +15 -0
- package/src/plugins/defaults/injectors.ts +2 -8
- package/src/plugins/defaults/persistence.ts +25 -6
- package/src/plugins/types.ts +57 -13
- package/src/proactive-artifact/job.test.ts +1 -0
- package/src/prompts/__tests__/system-prompt.test.ts +4 -4
- package/src/prompts/system-prompt.ts +38 -40
- package/src/prompts/template-detection.ts +10 -4
- package/src/prompts/templates/BOOTSTRAP.md +7 -11
- package/src/prompts/templates/IDENTITY.md +0 -2
- package/src/providers/__tests__/connection-model-compat.test.ts +3 -4
- package/src/providers/__tests__/registry-native-web-search.test.ts +122 -0
- package/src/providers/call-site-routing.ts +33 -9
- package/src/providers/connection-model-compat.ts +23 -0
- package/src/providers/connection-resolution.ts +39 -20
- package/src/providers/fireworks/client.ts +1 -0
- package/src/providers/gemini/client.ts +24 -3
- package/src/providers/inference/__tests__/adapter-factory-openai-compatible.test.ts +0 -2
- package/src/providers/inference/__tests__/base-url-security.test.ts +2 -3
- package/src/providers/inference/__tests__/{connections-status-label.test.ts → connections-label.test.ts} +12 -111
- package/src/providers/inference/auth.ts +0 -8
- package/src/providers/inference/connections.ts +3 -66
- package/src/providers/inference/resolve-auth.ts +2 -3
- package/src/providers/model-catalog.ts +35 -1
- package/src/providers/model-intents.ts +3 -3
- package/src/providers/openai/__tests__/api-error-detail.test.ts +120 -0
- package/src/providers/openai/__tests__/chat-completions-provider-reasoning.test.ts +157 -5
- package/src/providers/openai/chat-completions-provider.ts +110 -12
- package/src/providers/openai/codex-models.ts +2 -0
- package/src/providers/openai/responses-provider.ts +53 -53
- package/src/providers/openrouter/client.ts +13 -8
- package/src/providers/provider-send-message.ts +18 -9
- package/src/providers/registry.ts +48 -8
- package/src/providers/retry.ts +16 -4
- package/src/providers/search-provider-catalog.ts +17 -9
- package/src/providers/types.ts +9 -0
- package/src/runtime/__tests__/agent-wake.test.ts +1 -0
- package/src/runtime/__tests__/background-job-runner.test.ts +1 -0
- package/src/runtime/access-request-helper.ts +1 -0
- package/src/runtime/auth/route-policy.ts +10 -0
- package/src/runtime/channel-readiness-service.ts +68 -0
- package/src/runtime/channel-reply-delivery.ts +23 -0
- package/src/runtime/channel-retry-sweep.ts +47 -14
- package/src/runtime/confirmation-request-guardian-bridge.ts +1 -1
- package/src/runtime/migrations/vbundle-builder.ts +3 -2
- package/src/runtime/routes/__tests__/bookmark-routes.test.ts +1 -0
- package/src/runtime/routes/__tests__/conversation-compaction-routes.test.ts +406 -0
- package/src/runtime/routes/__tests__/conversation-query-routes.test.ts +98 -0
- package/src/runtime/routes/__tests__/heartbeat-routes.test.ts +1 -1
- package/src/runtime/routes/__tests__/home-feed-routes.test.ts +209 -1
- package/src/runtime/routes/__tests__/inference-provider-connection-routes.test.ts +13 -50
- package/src/runtime/routes/__tests__/memory-v2-simulate-route.test.ts +51 -3
- package/src/runtime/routes/__tests__/memory-v3-simulate-params.test.ts +35 -0
- package/src/runtime/routes/__tests__/slack-channel-routes.test.ts +3 -2
- package/src/runtime/routes/__tests__/surface-content-routes.test.ts +294 -0
- package/src/runtime/routes/__tests__/task-routes.test.ts +48 -3
- package/src/runtime/routes/acp-routes-list.test.ts +3 -0
- package/src/runtime/routes/app-management-routes.ts +111 -4
- package/src/runtime/routes/background-wake-routes.ts +188 -20
- package/src/runtime/routes/btw-routes.ts +4 -4
- package/src/runtime/routes/conversation-analysis-routes.ts +6 -0
- package/src/runtime/routes/conversation-compaction-routes.ts +263 -0
- package/src/runtime/routes/conversation-list-routes.ts +147 -0
- package/src/runtime/routes/conversation-management-routes.ts +39 -14
- package/src/runtime/routes/conversation-query-routes.ts +60 -10
- package/src/runtime/routes/conversation-routes.ts +186 -140
- package/src/runtime/routes/conversations-import-routes.ts +19 -6
- package/src/runtime/routes/documents-routes.ts +10 -1
- package/src/runtime/routes/group-routes.ts +11 -0
- package/src/runtime/routes/home-feed-routes.ts +129 -0
- package/src/runtime/routes/identity-intro-cache.ts +61 -16
- package/src/runtime/routes/identity-routes.ts +30 -9
- package/src/runtime/routes/inbound-stages/background-dispatch.test.ts +530 -6
- package/src/runtime/routes/inbound-stages/background-dispatch.ts +57 -8
- package/src/runtime/routes/index.ts +2 -0
- package/src/runtime/routes/inference-provider-connection-routes.ts +5 -26
- package/src/runtime/routes/integrations/vercel.ts +15 -0
- package/src/runtime/routes/llm-context-normalization.ts +7 -2
- package/src/runtime/routes/memory-v3-routes.ts +160 -2
- package/src/runtime/routes/migration-routes.ts +20 -13
- package/src/runtime/routes/notification-routes.ts +63 -1
- package/src/runtime/routes/oauth-commands-routes.ts +6 -1
- package/src/runtime/routes/surface-action-routes.ts +1 -38
- package/src/runtime/routes/surface-content-routes.ts +12 -5
- package/src/runtime/routes/surface-conversation-resolver.ts +65 -0
- package/src/runtime/routes/wipe-conversation-routes.ts +3 -0
- package/src/runtime/services/__tests__/analyze-conversation.test.ts +2 -0
- package/src/runtime/slack-dm-text-delivery.ts +177 -0
- package/src/runtime/sync/resource-sync-events.ts +1 -1
- package/src/runtime/tool-grant-request-helper.ts +1 -0
- package/src/schedule/schedule-store.ts +8 -1
- package/src/schedule/scheduler.ts +111 -15
- package/src/security/__tests__/provider-key-env-fallback.test.ts +3 -3
- package/src/security/encrypted-store.ts +7 -16
- package/src/security/store-path-override.ts +61 -0
- package/src/signals/user-message.ts +5 -8
- package/src/skills/validate-input.ts +177 -0
- package/src/subagent/manager.ts +13 -13
- package/src/subagent/types.ts +6 -0
- package/src/tasks/tool-sanitizer.ts +2 -2
- package/src/tools/apps/definitions.ts +35 -21
- package/src/tools/browser/__tests__/browser-execution-acquire.test.ts +2 -8
- package/src/tools/computer-use/definitions.ts +268 -266
- package/src/tools/document/document-tool.ts +131 -8
- package/src/tools/execution-target.ts +2 -5
- package/src/tools/executor.ts +18 -55
- package/src/tools/host-filesystem/edit.test.ts +1 -0
- package/src/tools/host-filesystem/read.test.ts +1 -0
- package/src/tools/host-filesystem/transfer.test.ts +31 -6
- package/src/tools/host-filesystem/write.test.ts +1 -0
- package/src/tools/mcp/mcp-tool-factory.ts +0 -2
- package/src/tools/network/__tests__/managed-search-proxy.test.ts +282 -0
- package/src/tools/network/__tests__/web-search.test.ts +211 -3
- package/src/tools/network/managed-search-proxy.ts +183 -0
- package/src/tools/network/web-search.ts +199 -44
- package/src/tools/policy-context.ts +3 -1
- package/src/tools/registry.ts +146 -103
- package/src/tools/schedule/create.ts +1 -1
- package/src/tools/skills/skill-tool-factory.ts +17 -36
- package/src/tools/subagent/spawn.ts +3 -0
- package/src/tools/tool-approval-handler.ts +10 -4
- package/src/tools/tool-name-aliases.ts +72 -14
- package/src/tools/types.ts +17 -15
- package/src/tools/ui-surface/definitions.ts +98 -86
- package/src/types/onboarding-context.ts +6 -0
- package/src/usage/attribution.ts +32 -1
- package/src/util/browser.ts +7 -2
- package/src/workspace/migrations/090-memory-router-cost-optimized-profile.ts +109 -0
- package/src/workspace/migrations/091-retighten-migration-onboarding-thread.ts +41 -0
- package/src/workspace/migrations/registry.ts +4 -0
|
@@ -2,8 +2,8 @@
|
|
|
2
2
|
* Tests for `assistant/src/memory/v3/gate.ts`.
|
|
3
3
|
*
|
|
4
4
|
* Coverage matrix:
|
|
5
|
-
* - ready + selection →
|
|
6
|
-
* includes sticky slugs even when the model omits them.
|
|
5
|
+
* - ready + selection → numbered `[N]` candidate ids map back to slugs in
|
|
6
|
+
* model order, and includes sticky slugs even when the model omits them.
|
|
7
7
|
* - more + questions → `decision.questions` surfaced; selection still returned.
|
|
8
8
|
* - more with no/blank questions → decision is `{ decision: "more" }` (no
|
|
9
9
|
* empty `questions` array).
|
|
@@ -12,7 +12,7 @@
|
|
|
12
12
|
* - provider throws → fail-safe (ready, all candidates).
|
|
13
13
|
* - missing tool_use block → fail-safe (ready, all candidates).
|
|
14
14
|
* - tool input failing schema → fail-safe (ready, all candidates).
|
|
15
|
-
* - model
|
|
15
|
+
* - model returning an out-of-range id (0 / past the end) → dropped.
|
|
16
16
|
* - request shape: forced tool_choice on `decide_selection`, candidate set in
|
|
17
17
|
* the user message, abort signal forwarded.
|
|
18
18
|
*
|
|
@@ -132,7 +132,7 @@ describe("runGate — ready decision", () => {
|
|
|
132
132
|
const provider = makeProvider(
|
|
133
133
|
// Model selects b, a (its own order). Sticky `c` is omitted by the
|
|
134
134
|
// model but must survive in the final selection.
|
|
135
|
-
gateToolResponse({ decision: "ready",
|
|
135
|
+
gateToolResponse({ decision: "ready", selected_ids: [2, 1] }),
|
|
136
136
|
calls,
|
|
137
137
|
);
|
|
138
138
|
|
|
@@ -153,7 +153,7 @@ describe("runGate — ready decision", () => {
|
|
|
153
153
|
test("forces tool_choice on decide_selection and surfaces candidates", async () => {
|
|
154
154
|
const calls: ProviderCall[] = [];
|
|
155
155
|
const provider = makeProvider(
|
|
156
|
-
gateToolResponse({ decision: "ready",
|
|
156
|
+
gateToolResponse({ decision: "ready", selected_ids: [1] }),
|
|
157
157
|
calls,
|
|
158
158
|
);
|
|
159
159
|
|
|
@@ -172,18 +172,30 @@ describe("runGate — ready decision", () => {
|
|
|
172
172
|
});
|
|
173
173
|
expect(call.options?.config?.callSite).toBe("memoryV3Gate");
|
|
174
174
|
expect(call.tools?.[0].name).toBe("decide_selection");
|
|
175
|
+
// The output schema is candidate-independent: selected_ids is a plain
|
|
176
|
+
// integer array (no per-candidate enum), so it stays byte-identical per turn.
|
|
177
|
+
const schema = call.tools![0].input_schema as {
|
|
178
|
+
properties: Record<
|
|
179
|
+
string,
|
|
180
|
+
{ type?: string; items?: { type?: string; enum?: unknown } }
|
|
181
|
+
>;
|
|
182
|
+
};
|
|
183
|
+
expect(schema.properties.selected_ids?.type).toBe("array");
|
|
184
|
+
expect(schema.properties.selected_ids?.items?.type).toBe("integer");
|
|
185
|
+
expect(schema.properties.selected_ids?.items?.enum).toBeUndefined();
|
|
175
186
|
const userText = call.messages[0].content
|
|
176
187
|
.map((b) => (b.type === "text" ? b.text : ""))
|
|
177
188
|
.join("\n");
|
|
178
189
|
expect(userText).toContain("NOW-MARKER");
|
|
179
|
-
|
|
180
|
-
expect(userText).toContain("
|
|
190
|
+
// Candidates are numbered from 1 in candidate order.
|
|
191
|
+
expect(userText).toContain("[1] a");
|
|
192
|
+
expect(userText).toContain("[2] b");
|
|
181
193
|
});
|
|
182
194
|
|
|
183
195
|
test("includes the just-arrived turn so the selection is query-aware", async () => {
|
|
184
196
|
const calls: ProviderCall[] = [];
|
|
185
197
|
const provider = makeProvider(
|
|
186
|
-
gateToolResponse({ decision: "ready",
|
|
198
|
+
gateToolResponse({ decision: "ready", selected_ids: [1] }),
|
|
187
199
|
calls,
|
|
188
200
|
);
|
|
189
201
|
|
|
@@ -211,10 +223,58 @@ describe("runGate — ready decision", () => {
|
|
|
211
223
|
expect(userText).toContain("an earlier reply");
|
|
212
224
|
});
|
|
213
225
|
|
|
214
|
-
test("
|
|
226
|
+
test("omitted selected_ids falls back to all candidates (recall-safe)", async () => {
|
|
227
|
+
const calls: ProviderCall[] = [];
|
|
228
|
+
// `ready` verdict with no `selected_ids` field at all. A silent omission
|
|
229
|
+
// must keep everything surfaced, not drop all non-sticky context.
|
|
230
|
+
const provider = makeProvider(
|
|
231
|
+
gateToolResponse({ decision: "ready" }),
|
|
232
|
+
calls,
|
|
233
|
+
);
|
|
234
|
+
|
|
235
|
+
const result = await runGate({
|
|
236
|
+
input: makeInput(),
|
|
237
|
+
candidates: new Set(["frames/example-a", "people/alice", "people/bob"]),
|
|
238
|
+
sticky: new Set(["people/bob"]),
|
|
239
|
+
passNumber: 1,
|
|
240
|
+
provider,
|
|
241
|
+
});
|
|
242
|
+
|
|
243
|
+
expect(result.decision).toEqual({ decision: "ready" });
|
|
244
|
+
expect([...result.selectedSlugs].sort()).toEqual([
|
|
245
|
+
"frames/example-a",
|
|
246
|
+
"people/alice",
|
|
247
|
+
"people/bob",
|
|
248
|
+
]);
|
|
249
|
+
});
|
|
250
|
+
|
|
251
|
+
test("explicit empty selected_ids selects nothing but sticky", async () => {
|
|
252
|
+
const calls: ProviderCall[] = [];
|
|
253
|
+
// An *explicit* `[]` is the model genuinely choosing nothing — honored as-is
|
|
254
|
+
// (only sticky survives), unlike an omitted field.
|
|
255
|
+
const provider = makeProvider(
|
|
256
|
+
gateToolResponse({ decision: "ready", selected_ids: [] }),
|
|
257
|
+
calls,
|
|
258
|
+
);
|
|
259
|
+
|
|
260
|
+
const result = await runGate({
|
|
261
|
+
input: makeInput(),
|
|
262
|
+
candidates: new Set(["frames/example-a", "people/alice", "people/bob"]),
|
|
263
|
+
sticky: new Set(["people/bob"]),
|
|
264
|
+
passNumber: 1,
|
|
265
|
+
provider,
|
|
266
|
+
});
|
|
267
|
+
|
|
268
|
+
expect(result.decision).toEqual({ decision: "ready" });
|
|
269
|
+
// Only sticky carries over; the model dropped everything else.
|
|
270
|
+
expect(result.selectedSlugs).toEqual(["people/bob"]);
|
|
271
|
+
});
|
|
272
|
+
|
|
273
|
+
test("drops out-of-range ids (0, past the end) without throwing", async () => {
|
|
215
274
|
const calls: ProviderCall[] = [];
|
|
216
275
|
const provider = makeProvider(
|
|
217
|
-
|
|
276
|
+
// id 1 -> "a"; 0 and 99 are out of range (1-based, only 2 candidates).
|
|
277
|
+
gateToolResponse({ decision: "ready", selected_ids: [1, 0, 99] }),
|
|
218
278
|
calls,
|
|
219
279
|
);
|
|
220
280
|
|
|
@@ -234,7 +294,7 @@ describe("runGate — ready decision", () => {
|
|
|
234
294
|
const controller = new AbortController();
|
|
235
295
|
controller.abort();
|
|
236
296
|
const provider = makeProvider(
|
|
237
|
-
gateToolResponse({ decision: "ready",
|
|
297
|
+
gateToolResponse({ decision: "ready", selected_ids: [1] }),
|
|
238
298
|
calls,
|
|
239
299
|
);
|
|
240
300
|
|
|
@@ -257,7 +317,8 @@ describe("runGate — candidate summaries", () => {
|
|
|
257
317
|
test("renders candidates as `slug — summary` when summaryBySlug is provided", async () => {
|
|
258
318
|
const calls: ProviderCall[] = [];
|
|
259
319
|
const provider = makeProvider(
|
|
260
|
-
|
|
320
|
+
// id 1 -> "a/one".
|
|
321
|
+
gateToolResponse({ decision: "ready", selected_ids: [1] }),
|
|
261
322
|
calls,
|
|
262
323
|
);
|
|
263
324
|
|
|
@@ -273,19 +334,20 @@ describe("runGate — candidate summaries", () => {
|
|
|
273
334
|
provider,
|
|
274
335
|
});
|
|
275
336
|
|
|
276
|
-
// The model
|
|
337
|
+
// The model answers with the [N] numbers, mapped back to slugs by the gate.
|
|
277
338
|
expect(result.selectedSlugs).toEqual(["a/one"]);
|
|
278
339
|
const userText = calls[0].messages[0].content
|
|
279
340
|
.map((b) => (b.type === "text" ? b.text : ""))
|
|
280
341
|
.join("\n");
|
|
281
|
-
|
|
282
|
-
expect(userText).toContain("
|
|
342
|
+
// Candidates are numbered `[N] slug — summary`, in candidate order.
|
|
343
|
+
expect(userText).toContain("[1] a/one — first summary");
|
|
344
|
+
expect(userText).toContain("[2] b/two — second summary");
|
|
283
345
|
});
|
|
284
346
|
|
|
285
347
|
test("falls back to the bare slug when no summary is available", async () => {
|
|
286
348
|
const calls: ProviderCall[] = [];
|
|
287
349
|
const provider = makeProvider(
|
|
288
|
-
gateToolResponse({ decision: "ready",
|
|
350
|
+
gateToolResponse({ decision: "ready", selected_ids: [] }),
|
|
289
351
|
calls,
|
|
290
352
|
);
|
|
291
353
|
|
|
@@ -300,7 +362,7 @@ describe("runGate — candidate summaries", () => {
|
|
|
300
362
|
const userText = calls[0].messages[0].content
|
|
301
363
|
.map((b) => (b.type === "text" ? b.text : ""))
|
|
302
364
|
.join("\n");
|
|
303
|
-
expect(userText).toContain("a/one");
|
|
365
|
+
expect(userText).toContain("[1] a/one");
|
|
304
366
|
expect(userText).not.toContain("a/one —");
|
|
305
367
|
});
|
|
306
368
|
});
|
|
@@ -311,7 +373,7 @@ describe("runGate — more decision", () => {
|
|
|
311
373
|
const provider = makeProvider(
|
|
312
374
|
gateToolResponse({
|
|
313
375
|
decision: "more",
|
|
314
|
-
|
|
376
|
+
selected_ids: [1],
|
|
315
377
|
questions: ["What is the user's deadline?", "Who else is involved?"],
|
|
316
378
|
}),
|
|
317
379
|
calls,
|
|
@@ -338,7 +400,7 @@ describe("runGate — more decision", () => {
|
|
|
338
400
|
const provider = makeProvider(
|
|
339
401
|
gateToolResponse({
|
|
340
402
|
decision: "more",
|
|
341
|
-
|
|
403
|
+
selected_ids: [1],
|
|
342
404
|
questions: [" ", ""],
|
|
343
405
|
}),
|
|
344
406
|
calls,
|
|
@@ -360,7 +422,7 @@ describe("runGate — more decision", () => {
|
|
|
360
422
|
const provider = makeProvider(
|
|
361
423
|
gateToolResponse({
|
|
362
424
|
decision: "more",
|
|
363
|
-
|
|
425
|
+
selected_ids: [1],
|
|
364
426
|
questions: ["follow-up?"],
|
|
365
427
|
}),
|
|
366
428
|
calls,
|
|
@@ -382,7 +444,7 @@ describe("runGate — system prompt", () => {
|
|
|
382
444
|
test("uses the bundled default when no override is configured", async () => {
|
|
383
445
|
const calls: ProviderCall[] = [];
|
|
384
446
|
const provider = makeProvider(
|
|
385
|
-
gateToolResponse({ decision: "ready",
|
|
447
|
+
gateToolResponse({ decision: "ready", selected_ids: [1] }),
|
|
386
448
|
calls,
|
|
387
449
|
);
|
|
388
450
|
|
|
@@ -400,7 +462,7 @@ describe("runGate — system prompt", () => {
|
|
|
400
462
|
test("uses the configured inline override as the system prompt", async () => {
|
|
401
463
|
const calls: ProviderCall[] = [];
|
|
402
464
|
const provider = makeProvider(
|
|
403
|
-
gateToolResponse({ decision: "ready",
|
|
465
|
+
gateToolResponse({ decision: "ready", selected_ids: [1] }),
|
|
404
466
|
calls,
|
|
405
467
|
);
|
|
406
468
|
|
|
@@ -468,10 +530,7 @@ describe("runGate — fail-safe", () => {
|
|
|
468
530
|
sticky: new Set(),
|
|
469
531
|
passNumber: 1,
|
|
470
532
|
// `decision` is required; missing it fails the Zod schema.
|
|
471
|
-
provider: makeProvider(
|
|
472
|
-
gateToolResponse({ selected_slugs: ["a"] }),
|
|
473
|
-
calls,
|
|
474
|
-
),
|
|
533
|
+
provider: makeProvider(gateToolResponse({ selected_ids: [1] }), calls),
|
|
475
534
|
});
|
|
476
535
|
|
|
477
536
|
expect(result.decision).toEqual({ decision: "ready" });
|
|
@@ -484,7 +543,7 @@ describe("runGate — capture", () => {
|
|
|
484
543
|
const calls: ProviderCall[] = [];
|
|
485
544
|
const captured: Omit<LlmCallRecord, "pass">[] = [];
|
|
486
545
|
const provider = makeProvider(
|
|
487
|
-
gateToolResponse({ decision: "ready",
|
|
546
|
+
gateToolResponse({ decision: "ready", selected_ids: [1] }),
|
|
488
547
|
calls,
|
|
489
548
|
);
|
|
490
549
|
|
|
@@ -526,7 +585,7 @@ describe("runGate — reasoning field", () => {
|
|
|
526
585
|
test("exposes an optional reasoning property in the forced tool schema", async () => {
|
|
527
586
|
const calls: ProviderCall[] = [];
|
|
528
587
|
const provider = makeProvider(
|
|
529
|
-
gateToolResponse({ decision: "ready",
|
|
588
|
+
gateToolResponse({ decision: "ready", selected_ids: [1] }),
|
|
530
589
|
calls,
|
|
531
590
|
);
|
|
532
591
|
|
|
@@ -547,12 +606,12 @@ describe("runGate — reasoning field", () => {
|
|
|
547
606
|
expect(schema.required ?? []).not.toContain("reasoning");
|
|
548
607
|
});
|
|
549
608
|
|
|
550
|
-
test("
|
|
609
|
+
test("surfaces model-supplied reasoning on the decision without altering the verdict", async () => {
|
|
551
610
|
const calls: ProviderCall[] = [];
|
|
552
611
|
const provider = makeProvider(
|
|
553
612
|
gateToolResponse({
|
|
554
613
|
decision: "ready",
|
|
555
|
-
|
|
614
|
+
selected_ids: [2, 1],
|
|
556
615
|
reasoning: "kept the two query-relevant pages, dropped the rest",
|
|
557
616
|
}),
|
|
558
617
|
calls,
|
|
@@ -566,9 +625,13 @@ describe("runGate — reasoning field", () => {
|
|
|
566
625
|
provider,
|
|
567
626
|
});
|
|
568
627
|
|
|
569
|
-
// Reasoning
|
|
570
|
-
// (model order, omitted sticky appended) are unchanged
|
|
571
|
-
|
|
628
|
+
// Reasoning does not alter control flow — the verdict and ordered selection
|
|
629
|
+
// (model order, omitted sticky appended) are unchanged — but it IS carried
|
|
630
|
+
// on the decision so a run can be analyzed (trace + shadow telemetry).
|
|
631
|
+
expect(result.decision).toEqual({
|
|
632
|
+
decision: "ready",
|
|
633
|
+
reasoning: "kept the two query-relevant pages, dropped the rest",
|
|
634
|
+
});
|
|
572
635
|
expect(result.selectedSlugs).toEqual(["b", "a", "c"]);
|
|
573
636
|
});
|
|
574
637
|
});
|
|
@@ -230,4 +230,62 @@ describe("composeNodeIndex", () => {
|
|
|
230
230
|
|
|
231
231
|
expect(composeNodeIndex("root", tree, pageIndex([]))).toBe("[node:empty]");
|
|
232
232
|
});
|
|
233
|
+
|
|
234
|
+
test("collapses a multi-line node summary to a single index line", () => {
|
|
235
|
+
const tree = treeIndex(
|
|
236
|
+
[
|
|
237
|
+
treeNode("root", {}),
|
|
238
|
+
treeNode("multiline", {
|
|
239
|
+
summary: "First sentence.\nSecond sentence.\n Third.",
|
|
240
|
+
}),
|
|
241
|
+
],
|
|
242
|
+
{ root: [{ kind: "node", ref: "multiline" }] },
|
|
243
|
+
);
|
|
244
|
+
|
|
245
|
+
const block = composeNodeIndex("root", tree, pageIndex([]));
|
|
246
|
+
|
|
247
|
+
expect(block).toBe(
|
|
248
|
+
"[node:multiline] First sentence. Second sentence. Third.",
|
|
249
|
+
);
|
|
250
|
+
expect(block.split("\n")).toHaveLength(1);
|
|
251
|
+
});
|
|
252
|
+
|
|
253
|
+
test("collapses a multi-line page summary to a single index line", () => {
|
|
254
|
+
const tree = treeIndex([treeNode("root", {})], {
|
|
255
|
+
root: [{ kind: "page", ref: "notes" }],
|
|
256
|
+
});
|
|
257
|
+
const pages = pageIndex([
|
|
258
|
+
pageEntry("notes", "Line one\nLine two\n\nLine three"),
|
|
259
|
+
]);
|
|
260
|
+
|
|
261
|
+
const block = composeNodeIndex("root", tree, pages);
|
|
262
|
+
|
|
263
|
+
expect(block).toBe("[page:notes] Line one Line two Line three");
|
|
264
|
+
expect(block.split("\n")).toHaveLength(1);
|
|
265
|
+
});
|
|
266
|
+
|
|
267
|
+
test("a multi-line summary cannot inject extra index lines", () => {
|
|
268
|
+
// A summary crafted to look like another child entry must not become its
|
|
269
|
+
// own parsed line once collapsed.
|
|
270
|
+
const tree = treeIndex(
|
|
271
|
+
[
|
|
272
|
+
treeNode("root", {}),
|
|
273
|
+
treeNode("a", { summary: "Real summary\n[node:injected] fake entry" }),
|
|
274
|
+
treeNode("b", { summary: "Sibling" }),
|
|
275
|
+
],
|
|
276
|
+
{
|
|
277
|
+
root: [
|
|
278
|
+
{ kind: "node", ref: "a" },
|
|
279
|
+
{ kind: "node", ref: "b" },
|
|
280
|
+
],
|
|
281
|
+
},
|
|
282
|
+
);
|
|
283
|
+
|
|
284
|
+
const block = composeNodeIndex("root", tree, pageIndex([]));
|
|
285
|
+
|
|
286
|
+
expect(block.split("\n")).toEqual([
|
|
287
|
+
"[node:a] Real summary [node:injected] fake entry",
|
|
288
|
+
"[node:b] Sibling",
|
|
289
|
+
]);
|
|
290
|
+
});
|
|
233
291
|
});
|
|
@@ -86,12 +86,20 @@ const lane = {
|
|
|
86
86
|
/** Records the args the loop passed each lane, one entry per call. */
|
|
87
87
|
const laneCalls = {
|
|
88
88
|
scouts: [] as Array<{ nowText: string }>,
|
|
89
|
-
filter: [] as Array<{
|
|
89
|
+
filter: [] as Array<{
|
|
90
|
+
nowText: string;
|
|
91
|
+
dense: ScoutResult;
|
|
92
|
+
sticky: string[];
|
|
93
|
+
bypass: string[];
|
|
94
|
+
}>,
|
|
90
95
|
walk: [] as Array<{
|
|
91
96
|
nowText: string;
|
|
92
97
|
scouts: ScoutResult[];
|
|
93
98
|
}>,
|
|
94
|
-
edges: [] as Array<{
|
|
99
|
+
edges: [] as Array<{
|
|
100
|
+
seeds: string[];
|
|
101
|
+
extraAdjacency?: ReadonlyMap<string, ReadonlySet<string>>;
|
|
102
|
+
}>,
|
|
95
103
|
gate: [] as Array<{
|
|
96
104
|
nowText: string;
|
|
97
105
|
passNumber: number;
|
|
@@ -110,6 +118,14 @@ let walkCallCount = 0;
|
|
|
110
118
|
let edgeCallCount = 0;
|
|
111
119
|
let gateCallCount = 0;
|
|
112
120
|
|
|
121
|
+
// Learned-adjacency mock state: what `aboveThreshold` returns, and a recorder of
|
|
122
|
+
// the thresholds the loop read it at (empty when the loop never reads it).
|
|
123
|
+
let aboveThresholdCalls: Array<{ threshold: number }> = [];
|
|
124
|
+
let learnedAdjacencyResult: ReadonlyMap<
|
|
125
|
+
string,
|
|
126
|
+
ReadonlySet<string>
|
|
127
|
+
> = new Map();
|
|
128
|
+
|
|
113
129
|
mock.module("../scouts.js", () => ({
|
|
114
130
|
runScouts: async (input: RetrievalInput): Promise<RunScoutsResult> => {
|
|
115
131
|
laneCalls.scouts.push({ nowText: input.nowText });
|
|
@@ -121,8 +137,15 @@ mock.module("../filter.js", () => ({
|
|
|
121
137
|
filterDenseHits: async (args: {
|
|
122
138
|
input: RetrievalInput;
|
|
123
139
|
dense: ScoutResult;
|
|
140
|
+
sticky: Set<string>;
|
|
141
|
+
bypass: Set<string>;
|
|
124
142
|
}): Promise<FilterResult> => {
|
|
125
|
-
laneCalls.filter.push({
|
|
143
|
+
laneCalls.filter.push({
|
|
144
|
+
nowText: args.input.nowText,
|
|
145
|
+
dense: args.dense,
|
|
146
|
+
sticky: [...args.sticky],
|
|
147
|
+
bypass: [...args.bypass],
|
|
148
|
+
});
|
|
126
149
|
// Filter calls share the scout pass index (one filter call per dense pass).
|
|
127
150
|
return nextOf(lane.filter, laneCalls.filter.length - 1);
|
|
128
151
|
},
|
|
@@ -144,12 +167,23 @@ mock.module("../tree-walk.js", () => ({
|
|
|
144
167
|
mock.module("../edges.js", () => ({
|
|
145
168
|
expandEdges: async (args: {
|
|
146
169
|
seeds: Iterable<string>;
|
|
170
|
+
extraAdjacency?: ReadonlyMap<string, ReadonlySet<string>>;
|
|
147
171
|
}): Promise<ExpandResult> => {
|
|
148
|
-
laneCalls.edges.push({
|
|
172
|
+
laneCalls.edges.push({
|
|
173
|
+
seeds: [...args.seeds],
|
|
174
|
+
extraAdjacency: args.extraAdjacency,
|
|
175
|
+
});
|
|
149
176
|
return nextOf(lane.edges, edgeCallCount++);
|
|
150
177
|
},
|
|
151
178
|
}));
|
|
152
179
|
|
|
180
|
+
mock.module("../auto-edges.js", () => ({
|
|
181
|
+
aboveThreshold: (_db: unknown, threshold: number) => {
|
|
182
|
+
aboveThresholdCalls.push({ threshold });
|
|
183
|
+
return learnedAdjacencyResult;
|
|
184
|
+
},
|
|
185
|
+
}));
|
|
186
|
+
|
|
153
187
|
mock.module("../gate.js", () => ({
|
|
154
188
|
runGate: async (args: {
|
|
155
189
|
input: RetrievalInput;
|
|
@@ -213,6 +247,7 @@ function makeInput(opts?: {
|
|
|
213
247
|
nowText?: string;
|
|
214
248
|
passCap?: number;
|
|
215
249
|
lanes?: LaneConfig;
|
|
250
|
+
edgesThreshold?: number;
|
|
216
251
|
}): RetrievalInput {
|
|
217
252
|
const lanes = {
|
|
218
253
|
hot: true,
|
|
@@ -228,7 +263,13 @@ function makeInput(opts?: {
|
|
|
228
263
|
nowText: opts?.nowText ?? "NOW",
|
|
229
264
|
priorEverInjected: [],
|
|
230
265
|
config: {
|
|
231
|
-
memory: {
|
|
266
|
+
memory: {
|
|
267
|
+
v3: {
|
|
268
|
+
passCap: opts?.passCap ?? 3,
|
|
269
|
+
lanes,
|
|
270
|
+
edges: { learnedAdjacencyThreshold: opts?.edgesThreshold ?? 0 },
|
|
271
|
+
},
|
|
272
|
+
},
|
|
232
273
|
} as unknown as RetrievalInput["config"],
|
|
233
274
|
};
|
|
234
275
|
}
|
|
@@ -260,6 +301,8 @@ function reset(): void {
|
|
|
260
301
|
walkCallCount = 0;
|
|
261
302
|
edgeCallCount = 0;
|
|
262
303
|
gateCallCount = 0;
|
|
304
|
+
aboveThresholdCalls = [];
|
|
305
|
+
learnedAdjacencyResult = new Map();
|
|
263
306
|
}
|
|
264
307
|
|
|
265
308
|
beforeEach(reset);
|
|
@@ -405,6 +448,46 @@ describe("runRetrievalLoop — multi pass", () => {
|
|
|
405
448
|
expect(out.selectedSlugs).toEqual(["p1", "p2"]);
|
|
406
449
|
});
|
|
407
450
|
|
|
451
|
+
test("candidates accumulate across passes so the final gate sees pass-1 hits", async () => {
|
|
452
|
+
// Each pass surfaces a distinct dense hit. Without cross-pass accumulation
|
|
453
|
+
// the pass-2 gate would only see "p2"; with it, the cumulative pool carries
|
|
454
|
+
// pass-1's "p1" into the final gate input.
|
|
455
|
+
lane.scouts = [
|
|
456
|
+
{
|
|
457
|
+
scouts: [scout("dense", ["p1"])],
|
|
458
|
+
sticky: new Set(),
|
|
459
|
+
bypass: new Set(),
|
|
460
|
+
},
|
|
461
|
+
{
|
|
462
|
+
scouts: [scout("dense", ["p2"])],
|
|
463
|
+
sticky: new Set(),
|
|
464
|
+
bypass: new Set(),
|
|
465
|
+
},
|
|
466
|
+
];
|
|
467
|
+
lane.filter = [
|
|
468
|
+
{ kept: ["p1"], trace: { judged: ["p1"], dropped: [] } },
|
|
469
|
+
{ kept: ["p2"], trace: { judged: ["p2"], dropped: [] } },
|
|
470
|
+
];
|
|
471
|
+
lane.walk = [
|
|
472
|
+
{ pages: new Set(), levels: [] },
|
|
473
|
+
{ pages: new Set(), levels: [] },
|
|
474
|
+
];
|
|
475
|
+
lane.edges = [
|
|
476
|
+
{ pulled: new Set(), expansions: [] },
|
|
477
|
+
{ pulled: new Set(), expansions: [] },
|
|
478
|
+
];
|
|
479
|
+
lane.gate = [moreGate(["p1"], ["more?"]), readyGate(["p1", "p2"])];
|
|
480
|
+
|
|
481
|
+
const out = await runRetrievalLoop(makeInput({ passCap: 3 }), { db });
|
|
482
|
+
|
|
483
|
+
// Pass 1's gate saw only p1; pass 2's gate saw the cumulative pool.
|
|
484
|
+
expect(laneCalls.gate[0].candidates).toEqual(["p1"]);
|
|
485
|
+
expect(laneCalls.gate[1].candidates).toEqual(
|
|
486
|
+
expect.arrayContaining(["p1", "p2"]),
|
|
487
|
+
);
|
|
488
|
+
expect(out.selectedSlugs).toEqual(["p1", "p2"]);
|
|
489
|
+
});
|
|
490
|
+
|
|
408
491
|
test("passCap force-exits with the current selection when the gate keeps asking for more", async () => {
|
|
409
492
|
lane.scouts = [
|
|
410
493
|
{ scouts: [scout("dense", ["p"])], sticky: new Set(), bypass: new Set() },
|
|
@@ -492,6 +575,125 @@ describe("runRetrievalLoop — lane toggles", () => {
|
|
|
492
575
|
});
|
|
493
576
|
});
|
|
494
577
|
|
|
578
|
+
describe("runRetrievalLoop — sourceBySlug lane trust", () => {
|
|
579
|
+
test("upgrades a slug's lane when a higher-trust lane re-surfaces it in one pass", async () => {
|
|
580
|
+
// `shared` is surfaced by the low-trust hot lane AND survives the
|
|
581
|
+
// higher-trust dense filter in the same pass. Provenance must end on the
|
|
582
|
+
// more trusted lane (dense) so the downstream seed cap ranks it by its
|
|
583
|
+
// strongest signal, not the stale first-seen hot lane.
|
|
584
|
+
lane.scouts = [
|
|
585
|
+
{
|
|
586
|
+
scouts: [scout("hot", ["shared"]), scout("dense", ["shared"])],
|
|
587
|
+
sticky: new Set(),
|
|
588
|
+
bypass: new Set(),
|
|
589
|
+
},
|
|
590
|
+
];
|
|
591
|
+
lane.filter = [
|
|
592
|
+
{ kept: ["shared"], trace: { judged: ["shared"], dropped: [] } },
|
|
593
|
+
];
|
|
594
|
+
lane.walk = [{ pages: new Set(), levels: [] }];
|
|
595
|
+
lane.edges = [{ pulled: new Set(), expansions: [] }];
|
|
596
|
+
lane.gate = [readyGate(["shared"])];
|
|
597
|
+
|
|
598
|
+
const out = await runRetrievalLoop(makeInput(), { db });
|
|
599
|
+
|
|
600
|
+
// hot tagged first, then upgraded to the more trusted dense lane.
|
|
601
|
+
expect(out.sourceBySlug.get("shared")).toBe("dense");
|
|
602
|
+
});
|
|
603
|
+
|
|
604
|
+
test("does not downgrade a slug's lane when a lower-trust lane re-surfaces it", async () => {
|
|
605
|
+
// `s` is a dense survivor (trusted) in pass 1, then edge-pulled (lowest
|
|
606
|
+
// trust) in pass 2. The lane must stay dense — only upgrades apply.
|
|
607
|
+
lane.scouts = [
|
|
608
|
+
{
|
|
609
|
+
scouts: [scout("dense", ["s"])],
|
|
610
|
+
sticky: new Set(),
|
|
611
|
+
bypass: new Set(),
|
|
612
|
+
},
|
|
613
|
+
{ scouts: [], sticky: new Set(), bypass: new Set() },
|
|
614
|
+
];
|
|
615
|
+
lane.filter = [
|
|
616
|
+
{ kept: ["s"], trace: { judged: ["s"], dropped: [] } },
|
|
617
|
+
{ kept: [], trace: { judged: [], dropped: [] } },
|
|
618
|
+
];
|
|
619
|
+
lane.walk = [
|
|
620
|
+
{ pages: new Set(), levels: [] },
|
|
621
|
+
{ pages: new Set(), levels: [] },
|
|
622
|
+
];
|
|
623
|
+
// Pass 2's edge expansion re-pulls the already-dense `s`.
|
|
624
|
+
lane.edges = [
|
|
625
|
+
{ pulled: new Set(), expansions: [] },
|
|
626
|
+
{ pulled: new Set(["s"]), expansions: [{ from: "s", pulled: ["s"] }] },
|
|
627
|
+
];
|
|
628
|
+
lane.gate = [moreGate(["s"], ["more?"]), readyGate(["s"])];
|
|
629
|
+
|
|
630
|
+
const out = await runRetrievalLoop(makeInput({ passCap: 2 }), { db });
|
|
631
|
+
|
|
632
|
+
expect(out.sourceBySlug.get("s")).toBe("dense");
|
|
633
|
+
});
|
|
634
|
+
|
|
635
|
+
test("upgrades the lane across passes while preserving earliest-pass provenance", async () => {
|
|
636
|
+
// `s` first enters via the low-trust edge lane on pass 1, then is surfaced
|
|
637
|
+
// by the higher-trust dense lane on pass 2. The lane upgrades to dense, but
|
|
638
|
+
// the co-activation provenance must still treat it as a pass-1 hit: the lane
|
|
639
|
+
// upgrades, the first-seen pass does not.
|
|
640
|
+
lane.scouts = [
|
|
641
|
+
{ scouts: [], sticky: new Set(), bypass: new Set() },
|
|
642
|
+
{
|
|
643
|
+
scouts: [scout("dense", ["s"])],
|
|
644
|
+
sticky: new Set(),
|
|
645
|
+
bypass: new Set(),
|
|
646
|
+
},
|
|
647
|
+
];
|
|
648
|
+
// Pass 1 has no dense scout, so the filter is called only once (pass 2);
|
|
649
|
+
// the mock indexes filter results by call count, so the single entry keeps `s`.
|
|
650
|
+
lane.filter = [{ kept: ["s"], trace: { judged: ["s"], dropped: [] } }];
|
|
651
|
+
lane.walk = [
|
|
652
|
+
{ pages: new Set(), levels: [] },
|
|
653
|
+
{ pages: new Set(), levels: [] },
|
|
654
|
+
];
|
|
655
|
+
lane.edges = [
|
|
656
|
+
{ pulled: new Set(["s"]), expansions: [{ from: "x", pulled: ["s"] }] },
|
|
657
|
+
{ pulled: new Set(), expansions: [] },
|
|
658
|
+
];
|
|
659
|
+
lane.gate = [moreGate([], ["more?"]), readyGate(["s"])];
|
|
660
|
+
|
|
661
|
+
const out = await runRetrievalLoop(makeInput({ passCap: 2 }), { db });
|
|
662
|
+
|
|
663
|
+
// Lane upgraded from edge (pass 1) to dense (pass 2).
|
|
664
|
+
expect(out.sourceBySlug.get("s")).toBe("dense");
|
|
665
|
+
});
|
|
666
|
+
});
|
|
667
|
+
|
|
668
|
+
describe("runRetrievalLoop — sticky excluded from dense filter", () => {
|
|
669
|
+
test("forwards the full sticky set to the filter while keeping sticky in the selection", async () => {
|
|
670
|
+
// `sk` is sticky (and a dense hit); `keep` is a plain dense candidate. The
|
|
671
|
+
// loop must hand the filter the full sticky set so it can exclude `sk` from
|
|
672
|
+
// judgment — `sk` is force-selected by the gate regardless. `keep` is
|
|
673
|
+
// judged and kept normally.
|
|
674
|
+
lane.scouts = [
|
|
675
|
+
{
|
|
676
|
+
scouts: [scout("dense", ["sk", "keep"])],
|
|
677
|
+
sticky: new Set(["sk"]),
|
|
678
|
+
bypass: new Set(),
|
|
679
|
+
},
|
|
680
|
+
];
|
|
681
|
+
lane.filter = [
|
|
682
|
+
{ kept: ["keep"], trace: { judged: ["keep"], dropped: [] } },
|
|
683
|
+
];
|
|
684
|
+
lane.walk = [{ pages: new Set(), levels: [] }];
|
|
685
|
+
lane.edges = [{ pulled: new Set(), expansions: [] }];
|
|
686
|
+
lane.gate = [readyGate(["sk", "keep"])];
|
|
687
|
+
|
|
688
|
+
const out = await runRetrievalLoop(makeInput(), { db });
|
|
689
|
+
|
|
690
|
+
// The loop forwarded the full sticky set to the filter so it can subtract it.
|
|
691
|
+
expect(laneCalls.filter[0].sticky).toEqual(["sk"]);
|
|
692
|
+
// The sticky slug is still in the final selection (gate force-injects it).
|
|
693
|
+
expect(out.selectedSlugs).toEqual(expect.arrayContaining(["sk", "keep"]));
|
|
694
|
+
});
|
|
695
|
+
});
|
|
696
|
+
|
|
495
697
|
describe("runRetrievalLoop — failure + cost", () => {
|
|
496
698
|
test("surfaces a filter failureReason on the output", async () => {
|
|
497
699
|
lane.scouts = [
|
|
@@ -528,3 +730,46 @@ describe("runRetrievalLoop — failure + cost", () => {
|
|
|
528
730
|
expect(out.cost?.ms).toBeGreaterThanOrEqual(0);
|
|
529
731
|
});
|
|
530
732
|
});
|
|
733
|
+
|
|
734
|
+
describe("runRetrievalLoop — learned edge adjacency", () => {
|
|
735
|
+
/** Minimal single-pass fixture; the edge lane is the subject under test. */
|
|
736
|
+
function singlePassFixture(): void {
|
|
737
|
+
lane.scouts = [
|
|
738
|
+
{
|
|
739
|
+
scouts: [scout("hot", ["seed-x"])],
|
|
740
|
+
sticky: new Set(),
|
|
741
|
+
bypass: new Set(),
|
|
742
|
+
},
|
|
743
|
+
];
|
|
744
|
+
lane.filter = [{ kept: [], trace: { judged: [], dropped: [] } }];
|
|
745
|
+
lane.walk = [{ pages: new Set(), levels: [] }];
|
|
746
|
+
lane.edges = [
|
|
747
|
+
{
|
|
748
|
+
pulled: new Set(["learned-y"]),
|
|
749
|
+
expansions: [{ from: "seed-x", pulled: ["learned-y"] }],
|
|
750
|
+
},
|
|
751
|
+
];
|
|
752
|
+
lane.gate = [readyGate(["seed-x", "learned-y"])];
|
|
753
|
+
}
|
|
754
|
+
|
|
755
|
+
test("reads and merges learned adjacency into edge expansion when threshold > 0", async () => {
|
|
756
|
+
singlePassFixture();
|
|
757
|
+
learnedAdjacencyResult = new Map([["seed-x", new Set(["learned-y"])]]);
|
|
758
|
+
|
|
759
|
+
await runRetrievalLoop(makeInput({ edgesThreshold: 1.5 }), { db });
|
|
760
|
+
|
|
761
|
+
// The loop reads the learned graph once at the configured threshold and
|
|
762
|
+
// hands it to the edge lane as extraAdjacency.
|
|
763
|
+
expect(aboveThresholdCalls).toEqual([{ threshold: 1.5 }]);
|
|
764
|
+
expect(laneCalls.edges[0].extraAdjacency).toBe(learnedAdjacencyResult);
|
|
765
|
+
});
|
|
766
|
+
|
|
767
|
+
test("does not read or pass learned adjacency when the threshold is 0", async () => {
|
|
768
|
+
singlePassFixture();
|
|
769
|
+
|
|
770
|
+
await runRetrievalLoop(makeInput({ edgesThreshold: 0 }), { db });
|
|
771
|
+
|
|
772
|
+
expect(aboveThresholdCalls).toHaveLength(0);
|
|
773
|
+
expect(laneCalls.edges[0].extraAdjacency).toBeUndefined();
|
|
774
|
+
});
|
|
775
|
+
});
|