@vellumai/assistant 0.8.3 → 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/docker-entrypoint.sh +0 -1
- package/docs/browser-use-architecture-phase2.md +1 -1
- package/knip.json +2 -1
- package/node_modules/@vellumai/gateway-client/src/types.ts +2 -0
- package/openapi.yaml +1492 -100
- package/package.json +1 -1
- package/src/__tests__/agent-loop-exit-reason.test.ts +4 -5
- package/src/__tests__/agent-loop-override-profile.test.ts +1 -1
- package/src/__tests__/agent-loop.test.ts +88 -3
- package/src/__tests__/anthropic-provider.test.ts +302 -33
- package/src/__tests__/approval-cascade.test.ts +1 -1
- 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 +4 -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-delivery-store.test.ts +193 -0
- package/src/__tests__/channel-guardian.test.ts +3 -3
- package/src/__tests__/channel-reply-delivery.test.ts +284 -5
- package/src/__tests__/channel-retry-sweep.test.ts +274 -1
- package/src/__tests__/checker.test.ts +6 -15
- package/src/__tests__/compaction-events.test.ts +2 -1
- package/src/__tests__/compactor-call-site-logging.test.ts +214 -0
- package/src/__tests__/compactor-preserved-tail-count.test.ts +110 -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__/config-watcher.test.ts +1 -1
- package/src/__tests__/confirmation-request-guardian-bridge.test.ts +0 -1
- package/src/__tests__/context-token-estimator.test.ts +91 -1
- package/src/__tests__/conversation-abort-tool-results.test.ts +1 -1
- package/src/__tests__/conversation-agent-loop-disk-pressure.test.ts +1 -1
- package/src/__tests__/conversation-agent-loop-inference-profile.test.ts +55 -4
- package/src/__tests__/conversation-agent-loop-overflow.test.ts +228 -8
- package/src/__tests__/conversation-agent-loop.test.ts +188 -129
- package/src/__tests__/conversation-app-control-instantiation.test.ts +2 -5
- package/src/__tests__/conversation-app-control-lifecycle.test.ts +1 -1
- package/src/__tests__/conversation-clean-command.test.ts +137 -0
- package/src/__tests__/conversation-clear-safety.test.ts +25 -25
- package/src/__tests__/conversation-confirmation-signals.test.ts +1 -1
- 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 +324 -0
- package/src/__tests__/conversation-lifecycle.test.ts +53 -12
- package/src/__tests__/conversation-load-history-repair.test.ts +1 -1
- package/src/__tests__/conversation-load-history-stripped.test.ts +279 -0
- package/src/__tests__/conversation-pairing.test.ts +2 -2
- package/src/__tests__/conversation-process-callsite.test.ts +1 -1
- package/src/__tests__/conversation-provider-retry-repair.test.ts +2 -1
- package/src/__tests__/conversation-queue.test.ts +1 -1
- 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-runtime-assembly.test.ts +264 -81
- package/src/__tests__/conversation-seed-composer.test.ts +66 -4
- package/src/__tests__/conversation-skill-tools.test.ts +2 -5
- package/src/__tests__/conversation-slash-commands.test.ts +36 -8
- package/src/__tests__/conversation-slash-queue.test.ts +1 -1
- package/src/__tests__/conversation-slash-unknown.test.ts +1 -1
- package/src/__tests__/conversation-speed-override.test.ts +1 -1
- package/src/__tests__/conversation-store.test.ts +1 -1
- package/src/__tests__/conversation-surfaces-task-progress.test.ts +220 -0
- package/src/__tests__/conversation-sync-tags.test.ts +99 -32
- package/src/__tests__/conversation-workspace-cache-state.test.ts +2 -1
- package/src/__tests__/conversation-workspace-injection.test.ts +5 -1
- package/src/__tests__/conversation-workspace-tool-tracking.test.ts +5 -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 +7 -0
- package/src/__tests__/credential-vault-unit.test.ts +2 -2
- package/src/__tests__/cu-unified-flow.test.ts +10 -1
- package/src/__tests__/dm-backfill.test.ts +64 -0
- package/src/__tests__/dm-persistence.test.ts +33 -0
- package/src/__tests__/document-find-replace.test.ts +501 -0
- package/src/__tests__/dynamic-page-surface.test.ts +2 -2
- package/src/__tests__/email-html-renderer.test.ts +12 -0
- package/src/__tests__/first-greeting.test.ts +23 -2
- 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__/headless-browser-navigate.test.ts +172 -0
- package/src/__tests__/heartbeat-disk-pressure.test.ts +4 -0
- package/src/__tests__/heartbeat-service.test.ts +4 -0
- package/src/__tests__/host-bash-proxy.test.ts +6 -0
- package/src/__tests__/host-browser-proxy.test.ts +10 -0
- package/src/__tests__/host-cu-proxy.test.ts +8 -1
- package/src/__tests__/host-file-proxy.test.ts +8 -1
- package/src/__tests__/host-shell-tool.test.ts +1 -1
- package/src/__tests__/host-transfer-proxy.test.ts +8 -1
- package/src/__tests__/identity-routes.test.ts +57 -0
- package/src/__tests__/inbound-slack-persistence.test.ts +3 -0
- package/src/__tests__/init-feature-flag-overrides.test.ts +5 -6
- package/src/__tests__/injector-chain.test.ts +2 -0
- package/src/__tests__/injector-document-comments.test.ts +378 -0
- package/src/__tests__/injector-pkb-v2-silenced.test.ts +4 -25
- package/src/__tests__/list-messages-attachments.test.ts +21 -17
- package/src/__tests__/list-messages-hidden-metadata.test.ts +217 -0
- package/src/__tests__/list-messages-page-latest.test.ts +130 -14
- package/src/__tests__/list-messages-tool-merge.test.ts +77 -17
- package/src/__tests__/llm-context-normalization.test.ts +0 -2
- 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 +161 -9
- package/src/__tests__/llm-usage-store.test.ts +66 -0
- package/src/__tests__/log-export-routes.test.ts +99 -2
- 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__/message-queue-steer.test.ts +114 -0
- 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 +151 -0
- package/src/__tests__/openai-responses-provider.test.ts +118 -16
- package/src/__tests__/outbound-slack-persistence.test.ts +187 -20
- package/src/__tests__/pending-interactions-resolved-event.test.ts +189 -0
- package/src/__tests__/platform-bash-auto-approve.test.ts +2 -2
- package/src/__tests__/platform.test.ts +2 -5
- package/src/__tests__/plugin-api-tool-definition.test.ts +92 -0
- package/src/__tests__/plugin-bootstrap.test.ts +2 -2
- package/src/__tests__/plugin-source-watcher.test.ts +302 -0
- 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__/process-message-background-slack.test.ts +1 -51
- package/src/__tests__/process-message-display-content.test.ts +21 -16
- 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__/server-history-render.test.ts +83 -4
- 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__/steer-tool-repair.test.ts +249 -0
- 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 +161 -124
- package/src/__tests__/terminal-tools.test.ts +12 -2
- package/src/__tests__/thinking-block-replay.test.ts +113 -0
- package/src/__tests__/thread-backfill.test.ts +370 -22
- 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 +89 -53
- package/src/__tests__/tool-grant-request-escalation.test.ts +1 -6
- package/src/__tests__/tool-result-metadata-plumbing.test.ts +167 -0
- 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__/twilio-routes.test.ts +1 -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__/web-fetch.test.ts +2 -2
- package/src/__tests__/workspace-git-service.test.ts +94 -10
- package/src/__tests__/workspace-migration-088-deprecate-background-conversation-override.test.ts +158 -0
- 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/attachments.ts +1 -0
- package/src/agent/loop.ts +65 -20
- 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/next-wake.test.ts +289 -0
- package/src/background-wake/next-wake.ts +172 -0
- package/src/background-wake/runtime-registry.ts +24 -0
- package/src/browser/operations.ts +15 -0
- package/src/cli/commands/__tests__/browser.test.ts +23 -5
- package/src/cli/commands/__tests__/conversations-slack.test.ts +572 -0
- 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 +10 -12
- package/src/cli/commands/__tests__/memory-v3-render.test.ts +340 -0
- package/src/cli/commands/browser.ts +247 -0
- package/src/cli/commands/conversations.ts +128 -1
- package/src/cli/commands/domain.ts +91 -41
- package/src/cli/commands/inference-providers.ts +147 -1
- 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 +483 -0
- package/src/cli/commands/memory-v3-render.ts +344 -0
- package/src/cli/commands/memory-v3.ts +316 -0
- package/src/cli/commands/notifications.ts +24 -2
- package/src/cli/program.ts +2 -0
- package/src/cli/utils/conversation-id.ts +17 -5
- package/src/config/assistant-feature-flags.ts +21 -9
- package/src/config/bundled-skills/app-builder/SKILL.md +2 -2
- package/src/config/bundled-skills/document-editor/SKILL.md +124 -0
- package/src/config/bundled-skills/document-editor/TOOLS.json +258 -0
- package/src/config/bundled-skills/document-editor/tools/comment-list.ts +12 -0
- package/src/config/bundled-skills/document-editor/tools/comment-reply.ts +12 -0
- package/src/config/bundled-skills/document-editor/tools/comment-resolve.ts +12 -0
- package/src/config/bundled-skills/document-editor/tools/document-find.ts +12 -0
- package/src/config/bundled-skills/document-editor/tools/document-open.ts +12 -0
- package/src/config/bundled-skills/document-editor/tools/document-replace-text.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/SKILL.md +8 -0
- 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-skills/schedule/SKILL.md +8 -0
- package/src/config/bundled-tool-registry.ts +24 -12
- package/src/config/call-site-defaults.ts +20 -0
- package/src/config/feature-flag-registry.json +115 -3
- package/src/config/llm-resolver.ts +16 -2
- package/src/config/schemas/__tests__/memory-v2.test.ts +217 -1
- package/src/config/schemas/call-site-catalog.ts +35 -0
- package/src/config/schemas/llm.ts +14 -0
- package/src/config/schemas/memory-v2.ts +294 -1
- package/src/config/schemas/memory.ts +2 -1
- package/src/context/compactor.ts +60 -1
- package/src/context/token-estimator.ts +47 -4
- package/src/context/window-manager.ts +25 -0
- package/src/conversations/__tests__/message-consolidation.test.ts +350 -0
- package/src/conversations/message-consolidation.ts +404 -0
- package/src/credential-health/credential-health-service.ts +34 -19
- package/src/daemon/__tests__/conversation-tool-setup-exclude.test.ts +1 -1
- package/src/daemon/__tests__/conversation-tool-setup.test.ts +66 -6
- package/src/daemon/__tests__/meet-manifest-loader.test.ts +1 -1
- package/src/daemon/__tests__/native-web-search-metadata.test.ts +357 -0
- package/src/daemon/__tests__/web-search-status-text.test.ts +287 -0
- package/src/daemon/conversation-agent-loop-handlers.ts +155 -36
- package/src/daemon/conversation-agent-loop.ts +307 -88
- package/src/daemon/conversation-error.ts +31 -1
- package/src/daemon/conversation-lifecycle.ts +149 -118
- package/src/daemon/conversation-messaging.ts +3 -0
- package/src/daemon/conversation-process.ts +273 -0
- package/src/daemon/conversation-queue-manager.ts +14 -0
- package/src/daemon/conversation-runtime-assembly.ts +145 -84
- package/src/daemon/conversation-slash.ts +37 -5
- package/src/daemon/conversation-surfaces.ts +45 -2
- package/src/daemon/conversation-tool-setup.ts +70 -3
- package/src/daemon/conversation-usage.ts +2 -0
- package/src/daemon/conversation.ts +54 -32
- package/src/daemon/disk-pressure-guard.ts +14 -2
- package/src/daemon/first-greeting.ts +10 -0
- package/src/daemon/handlers/__tests__/config-a2a-accept.test.ts +498 -0
- package/src/daemon/handlers/config-a2a.ts +160 -0
- package/src/daemon/handlers/config-model.test.ts +2 -0
- package/src/daemon/handlers/conversations.ts +90 -3
- package/src/daemon/handlers/shared.ts +92 -29
- package/src/daemon/host-bash-proxy.ts +1 -1
- package/src/daemon/host-browser-proxy.ts +5 -5
- package/src/daemon/host-cu-proxy.ts +5 -5
- package/src/daemon/host-file-proxy.ts +5 -5
- package/src/daemon/host-proxy-base.ts +4 -4
- package/src/daemon/host-transfer-proxy.ts +11 -11
- package/src/daemon/lifecycle.ts +40 -23
- package/src/daemon/meet-manifest-loader.ts +1 -7
- package/src/daemon/message-protocol.ts +4 -0
- package/src/daemon/message-types/conversations.ts +14 -9
- package/src/daemon/message-types/document-comments.ts +50 -0
- package/src/daemon/message-types/home.ts +1 -13
- package/src/daemon/message-types/messages.ts +66 -7
- package/src/daemon/message-types/surfaces.ts +3 -1
- package/src/daemon/message-types/sync.ts +14 -0
- package/src/daemon/message-types/web-activity.ts +57 -0
- package/src/daemon/plugin-source-watcher.ts +135 -3
- package/src/daemon/process-message.ts +69 -12
- 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/daemon/trust-context.ts +6 -0
- package/src/documents/document-comments-store.test.ts +338 -0
- package/src/documents/document-comments-store.ts +237 -0
- package/src/documents/document-store.ts +202 -0
- package/src/events/relationship-state-updated.ts +25 -0
- package/src/heartbeat/__tests__/heartbeat-service.test.ts +1 -2
- package/src/heartbeat/heartbeat-service.ts +1 -0
- package/src/home/__tests__/suggested-prompts.test.ts +33 -2
- package/src/home/feed-types.ts +6 -1
- package/src/home/home-content-refresh.ts +52 -0
- package/src/home/home-greeting-cache.ts +69 -0
- package/src/home/home-greeting.ts +85 -0
- package/src/home/suggested-prompts.ts +168 -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__/jobs-worker-v2-schedule.test.ts +135 -2
- package/src/memory/__tests__/memory-retrospective-job.test.ts +327 -6
- package/src/memory/auto-analysis-enqueue.ts +5 -1
- package/src/memory/conversation-crud.ts +191 -100
- 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 +26 -0
- package/src/memory/db-maintenance.ts +30 -21
- package/src/memory/delivery-crud.ts +41 -0
- package/src/memory/delivery-status.ts +141 -15
- package/src/memory/external-conversation-store.ts +32 -1
- 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 +68 -15
- 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-constants.ts +28 -0
- package/src/memory/memory-retrospective-enqueue.ts +11 -3
- package/src/memory/memory-retrospective-job.ts +413 -18
- package/src/memory/memory-retrospective-startup-cleanup.ts +3 -3
- package/src/memory/memory-v2-activation-log-store.ts +41 -14
- package/src/memory/migrations/100-core-tables.ts +1 -0
- package/src/memory/migrations/109-external-conversation-bindings.ts +1 -0
- package/src/memory/migrations/253-conversation-last-notified-profile.ts +15 -0
- package/src/memory/migrations/253-document-comments.ts +47 -0
- package/src/memory/migrations/254-external-conversation-binding-chat-name.ts +43 -0
- package/src/memory/migrations/255-channel-inbound-delivery-attempts.ts +24 -0
- package/src/memory/migrations/256-memory-v2-injection-events.ts +113 -0
- package/src/memory/migrations/257-strip-base-url-non-openai-compatible.ts +22 -0
- package/src/memory/migrations/258-onboarding-events-prior-assistants.ts +13 -0
- package/src/memory/migrations/259-conversation-cleaned-at.ts +33 -0
- 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 +34 -0
- package/src/memory/migrations/registry.ts +58 -0
- package/src/memory/onboarding-events-store.ts +7 -0
- package/src/memory/schema/calls.ts +1 -0
- package/src/memory/schema/conversations.ts +3 -0
- package/src/memory/schema/infrastructure.ts +22 -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-events.test.ts +318 -0
- package/src/memory/v2/__tests__/injection.test.ts +158 -112
- package/src/memory/v2/__tests__/page-index.test.ts +365 -1
- package/src/memory/v2/__tests__/qdrant.test.ts +36 -0
- package/src/memory/v2/__tests__/router.test.ts +660 -4
- package/src/memory/v2/consolidation-job.ts +14 -0
- 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-events.ts +101 -0
- package/src/memory/v2/injection.ts +42 -25
- package/src/memory/v2/page-index.ts +209 -7
- package/src/memory/v2/page-store.ts +18 -0
- package/src/memory/v2/prompts/router.ts +26 -1
- package/src/memory/v2/qdrant.ts +14 -2
- package/src/memory/v2/router.ts +369 -62
- 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/messaging/providers/index.ts +7 -1
- package/src/messaging/providers/slack/__tests__/adapter-mention-rendering.test.ts +329 -3
- package/src/messaging/providers/slack/__tests__/adapter-token-routing.test.ts +34 -1
- package/src/messaging/providers/slack/adapter.ts +178 -25
- package/src/messaging/providers/slack/api.test.ts +54 -0
- package/src/messaging/providers/slack/api.ts +119 -3
- package/src/messaging/providers/slack/client.ts +12 -0
- package/src/messaging/providers/slack/deep-link.ts +20 -1
- package/src/messaging/providers/slack/message-metadata.test.ts +48 -0
- package/src/messaging/providers/slack/message-metadata.ts +156 -0
- package/src/messaging/providers/slack/render-transcript.test.ts +107 -75
- package/src/messaging/providers/slack/render-transcript.ts +176 -49
- package/src/messaging/providers/slack/send.test.ts +77 -0
- package/src/messaging/providers/slack/send.ts +8 -2
- package/src/messaging/providers/slack/types.ts +14 -0
- package/src/notifications/__tests__/emit-signal-home-feed.test.ts +4 -1
- package/src/notifications/__tests__/home-feed-side-effect.test.ts +116 -54
- package/src/notifications/adapters/macos.ts +18 -1
- package/src/notifications/adapters/platform.ts +1 -1
- package/src/notifications/conversation-seed-composer.ts +14 -2
- package/src/notifications/decision-engine.ts +1 -4
- package/src/notifications/deferred-emit.ts +135 -0
- package/src/notifications/emit-signal.ts +38 -50
- package/src/notifications/home-feed-side-effect.ts +60 -30
- package/src/oauth/connect-orchestrator.ts +3 -0
- package/src/oauth/credential-token-resolver.ts +2 -0
- package/src/oauth/manual-token-connection.ts +19 -0
- package/src/oauth/oauth-store.ts +12 -0
- package/src/oauth/seed-providers.ts +22 -0
- package/src/permissions/prompter.ts +8 -5
- package/src/permissions/question-prompter.ts +5 -2
- package/src/permissions/secret-prompter.ts +6 -3
- 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 +100 -20
- 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__/system-prompt.test.ts +46 -2
- package/src/prompts/__tests__/task-progress-hint-section.test.ts +3 -9
- package/src/prompts/normalize-onboarding.ts +40 -0
- package/src/prompts/persona-resolver.ts +36 -21
- package/src/prompts/sections.ts +69 -19
- package/src/prompts/system-prompt.ts +118 -216
- package/src/prompts/template-detection.ts +37 -0
- package/src/prompts/templates/BOOTSTRAP-CONTENT-AUTOMATION.md +141 -0
- package/src/prompts/templates/BOOTSTRAP.md +10 -2
- package/src/prompts/templates/VOICE.md +3 -0
- package/src/prompts/templates/system-sections.ts +281 -9
- 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 +159 -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/fireworks/client.ts +20 -2
- package/src/providers/gemini/client.ts +49 -6
- package/src/providers/inference/__tests__/base-url-route-validation.test.ts +342 -0
- package/src/providers/inference/__tests__/base-url-security.test.ts +189 -0
- package/src/providers/inference/__tests__/codex-token-refresh.test.ts +254 -0
- package/src/providers/inference/adapter-factory.ts +18 -1
- package/src/providers/inference/auth.ts +3 -3
- package/src/providers/inference/codex-token-refresh.ts +128 -0
- package/src/providers/inference/resolve-auth.ts +49 -6
- package/src/providers/minimax/client.ts +106 -0
- package/src/providers/model-catalog.ts +91 -1
- package/src/providers/model-intents.ts +1 -1
- package/src/providers/openai/chat-completions-provider.ts +63 -23
- package/src/providers/openai/codex-models.ts +18 -0
- package/src/providers/openai/responses-provider.ts +86 -23
- package/src/providers/openrouter/client.ts +5 -1
- 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/types.ts +25 -0
- package/src/providers/usage-tracking.ts +2 -0
- package/src/runtime/AGENTS.md +2 -2
- package/src/runtime/__tests__/agent-wake.test.ts +214 -0
- package/src/runtime/__tests__/background-job-runner.test.ts +128 -0
- package/src/runtime/agent-wake.ts +152 -56
- package/src/runtime/assistant-event-hub.ts +76 -6
- package/src/runtime/auth/route-policy.ts +43 -3
- package/src/runtime/background-job-runner.ts +26 -0
- package/src/runtime/btw-sidechain.ts +0 -6
- package/src/runtime/channel-reply-delivery.ts +182 -47
- package/src/runtime/channel-retry-sweep.ts +141 -16
- package/src/runtime/http-types.ts +7 -6
- package/src/runtime/migrations/vbundle-builder.ts +10 -3
- package/src/runtime/pending-interactions.ts +50 -8
- package/src/runtime/routes/__tests__/content-source-routes.test.ts +162 -0
- package/src/runtime/routes/__tests__/conversation-query-routes.test.ts +161 -1
- package/src/runtime/routes/__tests__/memory-v2-routes.test.ts +14 -0
- package/src/runtime/routes/__tests__/memory-v2-simulate-route.test.ts +290 -0
- package/src/runtime/routes/__tests__/plugins-routes.test.ts +512 -0
- package/src/runtime/routes/__tests__/sanity-routes.test.ts +280 -0
- package/src/runtime/routes/__tests__/slack-channel-routes.test.ts +266 -0
- package/src/runtime/routes/acp-routes.test.ts +255 -6
- package/src/runtime/routes/acp-routes.ts +8 -1
- package/src/runtime/routes/approval-routes.ts +4 -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/chatgpt-subscription-auth-routes.ts +246 -0
- package/src/runtime/routes/content-source-routes.ts +78 -0
- package/src/runtime/routes/conversation-cli-routes.ts +147 -2
- 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 +196 -31
- package/src/runtime/routes/conversation-routes.ts +472 -425
- package/src/runtime/routes/conversation-starter-routes.ts +6 -3
- package/src/runtime/routes/disk-pressure-routes.ts +1 -1
- package/src/runtime/routes/document-comments-routes.ts +287 -0
- package/src/runtime/routes/documents-routes.ts +33 -0
- 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/home-feed-routes.ts +6 -3
- package/src/runtime/routes/host-app-control-routes.ts +1 -1
- package/src/runtime/routes/host-browser-routes.ts +17 -2
- package/src/runtime/routes/host-cu-routes.ts +2 -2
- package/src/runtime/routes/identity-routes.ts +21 -0
- package/src/runtime/routes/inbound-message-handler.ts +288 -58
- package/src/runtime/routes/inbound-stages/acl-enforcement.ts +96 -3
- package/src/runtime/routes/inbound-stages/background-dispatch.test.ts +365 -6
- package/src/runtime/routes/inbound-stages/background-dispatch.ts +283 -82
- package/src/runtime/routes/index.ts +20 -4
- 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/inference-provider-connection-routes.ts +63 -7
- package/src/runtime/routes/integrations/a2a.ts +60 -1
- package/src/runtime/routes/llm-call-sites-routes.ts +32 -5
- package/src/runtime/routes/log-export-routes.ts +39 -0
- package/src/runtime/routes/memory-item-routes.ts +8 -3
- package/src/runtime/routes/memory-v2-routes.ts +427 -0
- package/src/runtime/routes/memory-v3-routes.ts +316 -0
- package/src/runtime/routes/migration-routes.ts +21 -24
- package/src/runtime/routes/notification-routes.ts +19 -2
- package/src/runtime/routes/plugins-routes.ts +337 -0
- package/src/runtime/routes/question-routes.ts +4 -1
- package/src/runtime/routes/rename-conversation-routes.ts +6 -2
- package/src/runtime/routes/sanity-routes.ts +159 -0
- 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 +188 -0
- package/src/runtime/routes/workspace-routes.ts +25 -10
- package/src/runtime/services/conversation-serializer.ts +30 -4
- 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/schedule/integration-status.ts +3 -1
- package/src/security/__tests__/oauth2-device-code.test.ts +479 -0
- package/src/security/oauth2-device-code.ts +307 -0
- package/src/security/oauth2.ts +26 -9
- package/src/security/secure-keys.ts +5 -0
- package/src/skills/catalog-install.ts +6 -2
- 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 +150 -0
- package/src/tools/browser/browser-execution.ts +106 -0
- package/src/tools/browser/cdp-client/__tests__/browser-tabs-factory.test.ts +402 -0
- package/src/tools/browser/cdp-client/__tests__/factory.test.ts +28 -0
- package/src/tools/browser/cdp-client/__tests__/types.test.ts +4 -0
- package/src/tools/browser/cdp-client/cdp-inspect-client.ts +22 -0
- package/src/tools/browser/cdp-client/extension-cdp-client.ts +42 -2
- package/src/tools/browser/cdp-client/factory.ts +171 -4
- package/src/tools/browser/cdp-client/local-cdp-client.ts +21 -0
- package/src/tools/browser/cdp-client/types.ts +101 -0
- package/src/tools/browser/pinned-tabs.ts +146 -0
- 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-comment-tool.test.ts +379 -0
- package/src/tools/document/document-comment-tool.ts +156 -0
- package/src/tools/document/document-tool.ts +187 -2
- 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/__tests__/web-fetch-metadata.test.ts +229 -0
- package/src/tools/network/__tests__/web-search-metadata.test.ts +346 -0
- package/src/tools/network/domain-normalize.ts +17 -0
- package/src/tools/network/web-fetch.ts +216 -73
- package/src/tools/network/web-search.ts +216 -98
- 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/safe-env.ts +3 -2
- package/src/tools/terminal/shell.ts +3 -9
- package/src/tools/tool-approval-handler.ts +19 -12
- package/src/tools/tool-defaults.ts +94 -0
- package/src/tools/types.ts +31 -98
- package/src/tools/ui-surface/definitions.ts +9 -23
- package/src/types/onboarding-context.ts +4 -0
- package/src/usage/pricing.ts +23 -0
- package/src/usage/types.ts +12 -0
- package/src/util/__tests__/favicon.test.ts +84 -0
- package/src/util/favicon.ts +40 -0
- package/src/util/logger.ts +16 -7
- package/src/util/platform.ts +7 -7
- package/src/util/sqlite3-runtime.ts +65 -0
- package/src/workspace/git-service.ts +75 -4
- package/src/workspace/migrations/086-revert-stale-gemini-mis-rewrites.ts +1 -0
- package/src/workspace/migrations/088-deprecate-background-conversation-override.ts +103 -0
- package/src/workspace/migrations/089-move-memory-tree-out-of-v3.ts +86 -0
- package/src/workspace/migrations/registry.ts +4 -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/config/bundled-skills/document/SKILL.md +0 -54
- package/src/config/bundled-skills/document/TOOLS.json +0 -106
- package/src/daemon/seed-files.ts +0 -18
- package/src/prompts/cache-boundary.ts +0 -8
- package/src/runtime/routes/interface-routes.ts +0 -43
- /package/src/config/bundled-skills/{document → document-editor}/tools/document-create.ts +0 -0
- /package/src/config/bundled-skills/{document → document-editor}/tools/document-delete.ts +0 -0
- /package/src/config/bundled-skills/{document → document-editor}/tools/document-list.ts +0 -0
- /package/src/config/bundled-skills/{document → document-editor}/tools/document-read.ts +0 -0
- /package/src/config/bundled-skills/{document → document-editor}/tools/document-update.ts +0 -0
|
@@ -73,6 +73,26 @@ mock.module("../skill-store.js", () => ({
|
|
|
73
73
|
listSkillEntries: () => skillState.entries,
|
|
74
74
|
}));
|
|
75
75
|
|
|
76
|
+
// Stub `computeInjectionScores` so tier-2 tests can dictate scores
|
|
77
|
+
// without spinning up a real bun:sqlite db. Real production wiring is
|
|
78
|
+
// covered by the splitTier2 unit tests in page-index.test.ts and the
|
|
79
|
+
// score-formula tests in injection-events.test.ts.
|
|
80
|
+
const scoresStub = new Map<string, number>();
|
|
81
|
+
mock.module("../injection-events.js", () => ({
|
|
82
|
+
computeInjectionScores: (
|
|
83
|
+
_db: unknown,
|
|
84
|
+
slugs: readonly string[],
|
|
85
|
+
_now: number,
|
|
86
|
+
): Map<string, number> => {
|
|
87
|
+
const out = new Map<string, number>();
|
|
88
|
+
for (const slug of slugs) {
|
|
89
|
+
const score = scoresStub.get(slug);
|
|
90
|
+
if (score !== undefined && score > 0) out.set(slug, score);
|
|
91
|
+
}
|
|
92
|
+
return out;
|
|
93
|
+
},
|
|
94
|
+
}));
|
|
95
|
+
|
|
76
96
|
// Provider stub. Each test sets `providerStub` to control the response;
|
|
77
97
|
// `null` simulates "no configured provider available".
|
|
78
98
|
let providerStub: Provider | null = null;
|
|
@@ -96,7 +116,7 @@ mock.module("../../../providers/provider-send-message.js", () => ({
|
|
|
96
116
|
// them. No mock needed for `daemon/identity-helpers.js`; it tolerates a
|
|
97
117
|
// missing IDENTITY.md by returning null.
|
|
98
118
|
|
|
99
|
-
const { runRouter } = await import("../router.js");
|
|
119
|
+
const { runRouter, applyHistoricalCharBudget } = await import("../router.js");
|
|
100
120
|
const { getPageIndex, invalidatePageIndex } = await import("../page-index.js");
|
|
101
121
|
const { writePage } = await import("../page-store.js");
|
|
102
122
|
|
|
@@ -112,6 +132,7 @@ beforeEach(() => {
|
|
|
112
132
|
providerStub = null;
|
|
113
133
|
providerCalls.length = 0;
|
|
114
134
|
warnLogs.length = 0;
|
|
135
|
+
scoresStub.clear();
|
|
115
136
|
invalidatePageIndex();
|
|
116
137
|
});
|
|
117
138
|
|
|
@@ -194,7 +215,13 @@ function makePage(
|
|
|
194
215
|
// fields the router actually reads. Cast through `as unknown` because the
|
|
195
216
|
// production type is a heavy nested schema; we only exercise the v2.router
|
|
196
217
|
// branch in this test file.
|
|
197
|
-
function makeConfig(overrides?: {
|
|
218
|
+
function makeConfig(overrides?: {
|
|
219
|
+
maxPageIds?: number;
|
|
220
|
+
batchSize?: number | null;
|
|
221
|
+
tier1Size?: number | null;
|
|
222
|
+
tier2Size?: number | null;
|
|
223
|
+
historicalPairsMaxChars?: number | null;
|
|
224
|
+
}) {
|
|
198
225
|
return {
|
|
199
226
|
memory: {
|
|
200
227
|
v2: {
|
|
@@ -202,6 +229,11 @@ function makeConfig(overrides?: { maxPageIds?: number }) {
|
|
|
202
229
|
router: {
|
|
203
230
|
enabled: true,
|
|
204
231
|
max_page_ids: overrides?.maxPageIds ?? 25,
|
|
232
|
+
batch_size: overrides?.batchSize ?? null,
|
|
233
|
+
tier1_size: overrides?.tier1Size ?? null,
|
|
234
|
+
tier2_size: overrides?.tier2Size ?? null,
|
|
235
|
+
historical_pairs_max_chars:
|
|
236
|
+
overrides?.historicalPairsMaxChars ?? null,
|
|
205
237
|
},
|
|
206
238
|
},
|
|
207
239
|
},
|
|
@@ -209,8 +241,12 @@ function makeConfig(overrides?: { maxPageIds?: number }) {
|
|
|
209
241
|
}
|
|
210
242
|
|
|
211
243
|
const COMMON_PARAMS = {
|
|
212
|
-
|
|
213
|
-
|
|
244
|
+
recentTurnPairs: [
|
|
245
|
+
{
|
|
246
|
+
assistantMessage: "Let me check your plan.",
|
|
247
|
+
userMessage: "What's on my plate today?",
|
|
248
|
+
},
|
|
249
|
+
],
|
|
214
250
|
nowText: "2026-05-10 14:00 PT",
|
|
215
251
|
priorEverInjected: [] as { slug: string; turn: number }[],
|
|
216
252
|
};
|
|
@@ -231,6 +267,7 @@ describe("runRouter — early bails", () => {
|
|
|
231
267
|
|
|
232
268
|
expect(result).toEqual({
|
|
233
269
|
selectedSlugs: [],
|
|
270
|
+
sourceBySlug: new Map(),
|
|
234
271
|
failureReason: "empty_index",
|
|
235
272
|
});
|
|
236
273
|
// Provider must NOT be invoked when there is nothing to route.
|
|
@@ -286,6 +323,7 @@ describe("runRouter — successful tool_use", () => {
|
|
|
286
323
|
|
|
287
324
|
expect(result).toEqual({
|
|
288
325
|
selectedSlugs: [],
|
|
326
|
+
sourceBySlug: new Map(),
|
|
289
327
|
failureReason: null,
|
|
290
328
|
});
|
|
291
329
|
});
|
|
@@ -387,6 +425,78 @@ describe("runRouter — successful tool_use", () => {
|
|
|
387
425
|
expect(blockB.cache_control).toBeUndefined();
|
|
388
426
|
});
|
|
389
427
|
|
|
428
|
+
test("runRouterBatch front-truncates the oldest <last_turn> message when the char budget is exceeded", async () => {
|
|
429
|
+
await writePage(workspaceDir, makePage("alpha", { summary: "A" }));
|
|
430
|
+
providerStub = makeProvider(toolUseResponse([1]));
|
|
431
|
+
|
|
432
|
+
const longAssistant = "A".repeat(2_000);
|
|
433
|
+
const longUser = "B".repeat(2_000);
|
|
434
|
+
const recentAssistant = "Short prior.";
|
|
435
|
+
const justArrived = "What's relevant?";
|
|
436
|
+
|
|
437
|
+
await runRouter({
|
|
438
|
+
workspaceDir,
|
|
439
|
+
recentTurnPairs: [
|
|
440
|
+
{ assistantMessage: longAssistant, userMessage: longUser },
|
|
441
|
+
{ assistantMessage: recentAssistant, userMessage: justArrived },
|
|
442
|
+
],
|
|
443
|
+
nowText: "now",
|
|
444
|
+
priorEverInjected: [],
|
|
445
|
+
// Budget: just enough room for the most-recent pair plus the old user
|
|
446
|
+
// line in full, leaving a small slice for the very oldest assistant
|
|
447
|
+
// (which should be front-truncated with the `…` marker).
|
|
448
|
+
config: makeConfig({
|
|
449
|
+
historicalPairsMaxChars:
|
|
450
|
+
recentAssistant.length + justArrived.length + longUser.length + 50,
|
|
451
|
+
}),
|
|
452
|
+
});
|
|
453
|
+
|
|
454
|
+
const [call] = providerCalls;
|
|
455
|
+
const userMsg = call.messages[0];
|
|
456
|
+
const blockB = userMsg.content[1] as { text: string };
|
|
457
|
+
|
|
458
|
+
// The just-arrived user message and the prior assistant reply survive
|
|
459
|
+
// verbatim because they're newest in the walk.
|
|
460
|
+
expect(blockB.text).toContain(`[user]: ${justArrived}`);
|
|
461
|
+
expect(blockB.text).toContain(`[assistant]: ${recentAssistant}`);
|
|
462
|
+
|
|
463
|
+
// The older user message survives verbatim (next newest after the
|
|
464
|
+
// most-recent pair).
|
|
465
|
+
expect(blockB.text).toContain(`[user]: ${longUser}`);
|
|
466
|
+
|
|
467
|
+
// The oldest message in the walk (the older assistant) is
|
|
468
|
+
// front-truncated, so its rendered line starts with the `…` marker
|
|
469
|
+
// and ends with the suffix of the original text.
|
|
470
|
+
expect(blockB.text).toContain("[assistant]: …");
|
|
471
|
+
expect(blockB.text.endsWith(`A\n</last_turn>`)).toBe(false); // sanity
|
|
472
|
+
// The full untruncated long-assistant string must NOT appear.
|
|
473
|
+
expect(blockB.text.includes(longAssistant)).toBe(false);
|
|
474
|
+
// The TAIL of the long-assistant string SHOULD appear (kept from front-truncation).
|
|
475
|
+
expect(blockB.text).toContain(longAssistant.slice(-10));
|
|
476
|
+
});
|
|
477
|
+
|
|
478
|
+
test("null historical_pairs_max_chars renders pairs verbatim regardless of size", async () => {
|
|
479
|
+
await writePage(workspaceDir, makePage("alpha", { summary: "A" }));
|
|
480
|
+
providerStub = makeProvider(toolUseResponse([1]));
|
|
481
|
+
|
|
482
|
+
const huge = "X".repeat(5_000);
|
|
483
|
+
await runRouter({
|
|
484
|
+
workspaceDir,
|
|
485
|
+
recentTurnPairs: [
|
|
486
|
+
{ assistantMessage: huge, userMessage: "just arrived" },
|
|
487
|
+
],
|
|
488
|
+
nowText: "now",
|
|
489
|
+
priorEverInjected: [],
|
|
490
|
+
config: makeConfig(), // historical_pairs_max_chars: null
|
|
491
|
+
});
|
|
492
|
+
|
|
493
|
+
const [call] = providerCalls;
|
|
494
|
+
const blockB = call.messages[0].content[1] as { text: string };
|
|
495
|
+
expect(blockB.text).toContain(`[assistant]: ${huge}`);
|
|
496
|
+
expect(blockB.text).toContain("[user]: just arrived");
|
|
497
|
+
expect(blockB.text).not.toContain("…");
|
|
498
|
+
});
|
|
499
|
+
|
|
390
500
|
test("de-duplicates repeated IDs from the model while preserving order", async () => {
|
|
391
501
|
providerStub = makeProvider(toolUseResponse([2, 1, 2]));
|
|
392
502
|
|
|
@@ -529,3 +639,549 @@ describe("runRouter — failure modes", () => {
|
|
|
529
639
|
expect(providerCalls[0].options?.signal).toBe(controller.signal);
|
|
530
640
|
});
|
|
531
641
|
});
|
|
642
|
+
|
|
643
|
+
// ---------------------------------------------------------------------------
|
|
644
|
+
// Batched routing (config.memory.v2.router.batch_size).
|
|
645
|
+
// ---------------------------------------------------------------------------
|
|
646
|
+
|
|
647
|
+
describe("runRouter — batched (batch_size set)", () => {
|
|
648
|
+
beforeEach(async () => {
|
|
649
|
+
// 5 pages → at batch_size=2 we get ceil(5/2)=3 batches.
|
|
650
|
+
await writePage(workspaceDir, makePage("alpha", { summary: "A" }));
|
|
651
|
+
await writePage(workspaceDir, makePage("bravo", { summary: "B" }));
|
|
652
|
+
await writePage(workspaceDir, makePage("charlie", { summary: "C" }));
|
|
653
|
+
await writePage(workspaceDir, makePage("delta", { summary: "D" }));
|
|
654
|
+
await writePage(workspaceDir, makePage("echo", { summary: "E" }));
|
|
655
|
+
});
|
|
656
|
+
|
|
657
|
+
test("fires one provider call per batch in parallel", async () => {
|
|
658
|
+
// Every batch returns its local id 1 → at most 3 distinct slugs in the
|
|
659
|
+
// union (one per batch), but we don't assert WHICH slugs the FNV
|
|
660
|
+
// bucketing picks; just that the provider was called once per batch.
|
|
661
|
+
providerStub = makeProvider(toolUseResponse([1]));
|
|
662
|
+
|
|
663
|
+
const result = await runRouter({
|
|
664
|
+
workspaceDir,
|
|
665
|
+
...COMMON_PARAMS,
|
|
666
|
+
config: makeConfig({ batchSize: 2 }),
|
|
667
|
+
});
|
|
668
|
+
|
|
669
|
+
expect(result.failureReason).toBeNull();
|
|
670
|
+
expect(providerCalls.length).toBeGreaterThan(1);
|
|
671
|
+
expect(providerCalls.length).toBeLessThanOrEqual(3);
|
|
672
|
+
// Every batch picked its own local id 1 → distinct slugs in union.
|
|
673
|
+
expect(result.selectedSlugs.length).toBe(providerCalls.length);
|
|
674
|
+
expect(new Set(result.selectedSlugs).size).toBe(
|
|
675
|
+
result.selectedSlugs.length,
|
|
676
|
+
);
|
|
677
|
+
});
|
|
678
|
+
|
|
679
|
+
test("each batch's system prompt contains only its own subset of slugs", async () => {
|
|
680
|
+
providerStub = makeProvider(toolUseResponse([1]));
|
|
681
|
+
await runRouter({
|
|
682
|
+
workspaceDir,
|
|
683
|
+
...COMMON_PARAMS,
|
|
684
|
+
config: makeConfig({ batchSize: 2 }),
|
|
685
|
+
});
|
|
686
|
+
|
|
687
|
+
// Across all batch calls, every slug appears in exactly one prompt.
|
|
688
|
+
const allSlugs = ["alpha", "bravo", "charlie", "delta", "echo"];
|
|
689
|
+
const appearances = new Map<string, number>(allSlugs.map((s) => [s, 0]));
|
|
690
|
+
for (const call of providerCalls) {
|
|
691
|
+
for (const slug of allSlugs) {
|
|
692
|
+
if (call.systemPrompt?.includes(slug)) {
|
|
693
|
+
appearances.set(slug, (appearances.get(slug) ?? 0) + 1);
|
|
694
|
+
}
|
|
695
|
+
}
|
|
696
|
+
}
|
|
697
|
+
for (const slug of allSlugs) {
|
|
698
|
+
expect(appearances.get(slug)).toBe(1);
|
|
699
|
+
}
|
|
700
|
+
});
|
|
701
|
+
|
|
702
|
+
test("union of selected slugs is deduplicated across batches", async () => {
|
|
703
|
+
// Every batch returns its local id 1. Same slug could appear in only
|
|
704
|
+
// one batch (since each slug lives in exactly one batch), so the union
|
|
705
|
+
// is naturally unique. Sanity-check the dedup path with a 2-call response.
|
|
706
|
+
providerStub = makeProvider(toolUseResponse([1, 1]));
|
|
707
|
+
const result = await runRouter({
|
|
708
|
+
workspaceDir,
|
|
709
|
+
...COMMON_PARAMS,
|
|
710
|
+
config: makeConfig({ batchSize: 2 }),
|
|
711
|
+
});
|
|
712
|
+
expect(result.failureReason).toBeNull();
|
|
713
|
+
expect(new Set(result.selectedSlugs).size).toBe(
|
|
714
|
+
result.selectedSlugs.length,
|
|
715
|
+
);
|
|
716
|
+
});
|
|
717
|
+
|
|
718
|
+
test("priorEverInjected is filtered to the batch's own slugs as local IDs", async () => {
|
|
719
|
+
providerStub = makeProvider(toolUseResponse([1]));
|
|
720
|
+
await runRouter({
|
|
721
|
+
workspaceDir,
|
|
722
|
+
...COMMON_PARAMS,
|
|
723
|
+
priorEverInjected: [
|
|
724
|
+
{ slug: "alpha", turn: 1 },
|
|
725
|
+
{ slug: "echo", turn: 1 },
|
|
726
|
+
],
|
|
727
|
+
config: makeConfig({ batchSize: 2 }),
|
|
728
|
+
});
|
|
729
|
+
|
|
730
|
+
// Exactly the batches containing alpha or echo should mention any
|
|
731
|
+
// already_injected_id; other batches should have an empty list.
|
|
732
|
+
for (const call of providerCalls) {
|
|
733
|
+
const text =
|
|
734
|
+
(call.messages[0].content as Array<{ text?: string }>)[1]?.text ?? "";
|
|
735
|
+
const hasAlpha = call.systemPrompt?.includes("alpha");
|
|
736
|
+
const hasEcho = call.systemPrompt?.includes("echo");
|
|
737
|
+
const expectsId = hasAlpha || hasEcho;
|
|
738
|
+
// Block contents: "<already_injected_ids>\n{ids}\n</already_injected_ids>"
|
|
739
|
+
const match = text.match(
|
|
740
|
+
/<already_injected_ids>\n([^\n]*)\n<\/already_injected_ids>/,
|
|
741
|
+
);
|
|
742
|
+
const idsStr = match?.[1] ?? "";
|
|
743
|
+
if (expectsId) {
|
|
744
|
+
expect(idsStr.trim().length).toBeGreaterThan(0);
|
|
745
|
+
} else {
|
|
746
|
+
expect(idsStr.trim()).toBe("");
|
|
747
|
+
}
|
|
748
|
+
}
|
|
749
|
+
});
|
|
750
|
+
|
|
751
|
+
test("partial failure: one batch fails, others succeed → union returned with success", async () => {
|
|
752
|
+
let callCount = 0;
|
|
753
|
+
providerStub = {
|
|
754
|
+
name: "partial-failure",
|
|
755
|
+
sendMessage: async (messages, tools, systemPrompt, options) => {
|
|
756
|
+
callCount += 1;
|
|
757
|
+
providerCalls.push({ messages, tools, systemPrompt, options });
|
|
758
|
+
if (callCount === 1) throw new Error("batch 1 boom");
|
|
759
|
+
return toolUseResponse([1]);
|
|
760
|
+
},
|
|
761
|
+
};
|
|
762
|
+
|
|
763
|
+
const result = await runRouter({
|
|
764
|
+
workspaceDir,
|
|
765
|
+
...COMMON_PARAMS,
|
|
766
|
+
config: makeConfig({ batchSize: 2 }),
|
|
767
|
+
});
|
|
768
|
+
|
|
769
|
+
expect(result.failureReason).toBeNull();
|
|
770
|
+
expect(result.selectedSlugs.length).toBeGreaterThan(0);
|
|
771
|
+
expect(providerCalls.length).toBeGreaterThan(1);
|
|
772
|
+
});
|
|
773
|
+
|
|
774
|
+
test("all batches fail → unified failure with first batch's reason", async () => {
|
|
775
|
+
providerStub = {
|
|
776
|
+
name: "all-fail",
|
|
777
|
+
sendMessage: async () => {
|
|
778
|
+
throw new Error("all batches boom");
|
|
779
|
+
},
|
|
780
|
+
};
|
|
781
|
+
|
|
782
|
+
const result = await runRouter({
|
|
783
|
+
workspaceDir,
|
|
784
|
+
...COMMON_PARAMS,
|
|
785
|
+
config: makeConfig({ batchSize: 2 }),
|
|
786
|
+
});
|
|
787
|
+
|
|
788
|
+
expect(result.failureReason).toBe("api_error");
|
|
789
|
+
expect(result.selectedSlugs).toEqual([]);
|
|
790
|
+
});
|
|
791
|
+
|
|
792
|
+
test("union across batches is truncated to global max_page_ids", async () => {
|
|
793
|
+
// 5 pages, batch_size=1 → 5 batches, each picks its own local id 1.
|
|
794
|
+
// Without a global cap the union would be 5; max_page_ids=2 forces
|
|
795
|
+
// truncation back to 2.
|
|
796
|
+
providerStub = makeProvider(toolUseResponse([1]));
|
|
797
|
+
|
|
798
|
+
const result = await runRouter({
|
|
799
|
+
workspaceDir,
|
|
800
|
+
...COMMON_PARAMS,
|
|
801
|
+
config: makeConfig({ batchSize: 1, maxPageIds: 2 }),
|
|
802
|
+
});
|
|
803
|
+
|
|
804
|
+
expect(result.failureReason).toBeNull();
|
|
805
|
+
expect(result.selectedSlugs).toHaveLength(2);
|
|
806
|
+
// sourceBySlug must stay aligned with the truncated selection.
|
|
807
|
+
expect(result.sourceBySlug.size).toBe(2);
|
|
808
|
+
for (const slug of result.selectedSlugs) {
|
|
809
|
+
expect(result.sourceBySlug.get(slug)).toBeDefined();
|
|
810
|
+
}
|
|
811
|
+
const warned = warnLogs.some((l) =>
|
|
812
|
+
JSON.stringify(l.args).includes("union across batches exceeded"),
|
|
813
|
+
);
|
|
814
|
+
expect(warned).toBe(true);
|
|
815
|
+
});
|
|
816
|
+
|
|
817
|
+
test("batch_size larger than index size is single batch (same as v3)", async () => {
|
|
818
|
+
providerStub = makeProvider(toolUseResponse([1]));
|
|
819
|
+
const result = await runRouter({
|
|
820
|
+
workspaceDir,
|
|
821
|
+
...COMMON_PARAMS,
|
|
822
|
+
config: makeConfig({ batchSize: 1000 }),
|
|
823
|
+
});
|
|
824
|
+
expect(result.failureReason).toBeNull();
|
|
825
|
+
expect(providerCalls).toHaveLength(1);
|
|
826
|
+
});
|
|
827
|
+
});
|
|
828
|
+
|
|
829
|
+
// ---------------------------------------------------------------------------
|
|
830
|
+
// Tier 1 (recently modified) splitting.
|
|
831
|
+
// ---------------------------------------------------------------------------
|
|
832
|
+
|
|
833
|
+
const { utimes } = await import("node:fs/promises");
|
|
834
|
+
|
|
835
|
+
describe("runRouter — tier 1 (recently modified)", () => {
|
|
836
|
+
async function setMtime(slug: string, epochMs: number): Promise<void> {
|
|
837
|
+
const seconds = epochMs / 1000;
|
|
838
|
+
await utimes(
|
|
839
|
+
join(workspaceDir, "memory", "concepts", `${slug}.md`),
|
|
840
|
+
seconds,
|
|
841
|
+
seconds,
|
|
842
|
+
);
|
|
843
|
+
}
|
|
844
|
+
|
|
845
|
+
beforeEach(async () => {
|
|
846
|
+
await writePage(workspaceDir, makePage("alpha", { summary: "A" }));
|
|
847
|
+
await writePage(workspaceDir, makePage("bravo", { summary: "B" }));
|
|
848
|
+
await writePage(workspaceDir, makePage("charlie", { summary: "C" }));
|
|
849
|
+
await writePage(workspaceDir, makePage("delta", { summary: "D" }));
|
|
850
|
+
await writePage(workspaceDir, makePage("echo", { summary: "E" }));
|
|
851
|
+
});
|
|
852
|
+
|
|
853
|
+
test("tier1_size + batch_size both null is the v3 single-batch path", async () => {
|
|
854
|
+
providerStub = makeProvider(toolUseResponse([1]));
|
|
855
|
+
const result = await runRouter({
|
|
856
|
+
workspaceDir,
|
|
857
|
+
...COMMON_PARAMS,
|
|
858
|
+
config: makeConfig(),
|
|
859
|
+
});
|
|
860
|
+
expect(result.failureReason).toBeNull();
|
|
861
|
+
expect(providerCalls).toHaveLength(1);
|
|
862
|
+
});
|
|
863
|
+
|
|
864
|
+
test("tier1_size=2 + batch_size=null produces 2 batches (tier1 + rest)", async () => {
|
|
865
|
+
providerStub = makeProvider(toolUseResponse([1]));
|
|
866
|
+
const result = await runRouter({
|
|
867
|
+
workspaceDir,
|
|
868
|
+
...COMMON_PARAMS,
|
|
869
|
+
config: makeConfig({ tier1Size: 2 }),
|
|
870
|
+
});
|
|
871
|
+
expect(result.failureReason).toBeNull();
|
|
872
|
+
expect(providerCalls).toHaveLength(2);
|
|
873
|
+
});
|
|
874
|
+
|
|
875
|
+
test("tier 1 contains the most recently modified pages", async () => {
|
|
876
|
+
// Stamp distinct mtimes so the ordering is unambiguous.
|
|
877
|
+
await setMtime("alpha", 1_000_000);
|
|
878
|
+
await setMtime("bravo", 5_000_000); // most recent
|
|
879
|
+
await setMtime("charlie", 2_000_000);
|
|
880
|
+
await setMtime("delta", 4_000_000); // 2nd most recent
|
|
881
|
+
await setMtime("echo", 3_000_000);
|
|
882
|
+
|
|
883
|
+
providerStub = makeProvider(toolUseResponse([1]));
|
|
884
|
+
await runRouter({
|
|
885
|
+
workspaceDir,
|
|
886
|
+
...COMMON_PARAMS,
|
|
887
|
+
config: makeConfig({ tier1Size: 2 }),
|
|
888
|
+
});
|
|
889
|
+
|
|
890
|
+
// Tier 1 is the first provider call. Match `[N] slug` lines specifically
|
|
891
|
+
// — string-search on slug name alone would false-positive on prompt
|
|
892
|
+
// template text that may mention the same words.
|
|
893
|
+
const tier1Prompt = providerCalls[0].systemPrompt ?? "";
|
|
894
|
+
const indexedSlugs = new Set(
|
|
895
|
+
[...tier1Prompt.matchAll(/^\[\d+\] (\S+)/gm)].map((m) => m[1]),
|
|
896
|
+
);
|
|
897
|
+
expect(indexedSlugs).toEqual(new Set(["bravo", "delta"]));
|
|
898
|
+
});
|
|
899
|
+
|
|
900
|
+
test("tier1_size=2 + batch_size=2 puts every slug in exactly one batch", async () => {
|
|
901
|
+
providerStub = makeProvider(toolUseResponse([1]));
|
|
902
|
+
await runRouter({
|
|
903
|
+
workspaceDir,
|
|
904
|
+
...COMMON_PARAMS,
|
|
905
|
+
config: makeConfig({ tier1Size: 2, batchSize: 2 }),
|
|
906
|
+
});
|
|
907
|
+
// 5 pages, tier1=2, rest=3 → 1 tier1 batch + 1-or-2 tier3 batches
|
|
908
|
+
// depending on whether FNV hash distributes the 3 rest slugs into both
|
|
909
|
+
// buckets. The empty-batch filter drops a bucket that lands empty, so
|
|
910
|
+
// the strong invariant is "every slug appears in exactly one batch."
|
|
911
|
+
expect(providerCalls.length).toBeGreaterThanOrEqual(2);
|
|
912
|
+
expect(providerCalls.length).toBeLessThanOrEqual(3);
|
|
913
|
+
|
|
914
|
+
const allSlugs = ["alpha", "bravo", "charlie", "delta", "echo"];
|
|
915
|
+
const appearances = new Map<string, number>(
|
|
916
|
+
allSlugs.map((s) => [s, 0] as [string, number]),
|
|
917
|
+
);
|
|
918
|
+
for (const call of providerCalls) {
|
|
919
|
+
for (const slug of allSlugs) {
|
|
920
|
+
if (call.systemPrompt?.includes(slug)) {
|
|
921
|
+
appearances.set(slug, (appearances.get(slug) ?? 0) + 1);
|
|
922
|
+
}
|
|
923
|
+
}
|
|
924
|
+
}
|
|
925
|
+
for (const slug of allSlugs) {
|
|
926
|
+
expect(appearances.get(slug)).toBe(1);
|
|
927
|
+
}
|
|
928
|
+
});
|
|
929
|
+
|
|
930
|
+
test("tier1_size >= total pages → single tier 1 batch, no rest", async () => {
|
|
931
|
+
providerStub = makeProvider(toolUseResponse([1]));
|
|
932
|
+
await runRouter({
|
|
933
|
+
workspaceDir,
|
|
934
|
+
...COMMON_PARAMS,
|
|
935
|
+
config: makeConfig({ tier1Size: 100 }),
|
|
936
|
+
});
|
|
937
|
+
// 5 pages, tier1_size=100 → only tier 1 fires; the empty rest is dropped.
|
|
938
|
+
expect(providerCalls).toHaveLength(1);
|
|
939
|
+
});
|
|
940
|
+
});
|
|
941
|
+
|
|
942
|
+
// ---------------------------------------------------------------------------
|
|
943
|
+
// Tier 2 (highest-EMA) splitting.
|
|
944
|
+
// ---------------------------------------------------------------------------
|
|
945
|
+
|
|
946
|
+
describe("runRouter — tier 2 (highest EMA)", () => {
|
|
947
|
+
// Any non-null value passes the `params.database` check in the orchestrator;
|
|
948
|
+
// the real db is never touched because computeInjectionScores is mocked.
|
|
949
|
+
const stubDb = {} as Parameters<typeof runRouter>[0]["database"];
|
|
950
|
+
|
|
951
|
+
beforeEach(async () => {
|
|
952
|
+
await writePage(workspaceDir, makePage("alpha", { summary: "A" }));
|
|
953
|
+
await writePage(workspaceDir, makePage("bravo", { summary: "B" }));
|
|
954
|
+
await writePage(workspaceDir, makePage("charlie", { summary: "C" }));
|
|
955
|
+
await writePage(workspaceDir, makePage("delta", { summary: "D" }));
|
|
956
|
+
await writePage(workspaceDir, makePage("echo", { summary: "E" }));
|
|
957
|
+
});
|
|
958
|
+
|
|
959
|
+
test("tier2_size + tier1_size both null is the v3 single-batch path", async () => {
|
|
960
|
+
providerStub = makeProvider(toolUseResponse([1]));
|
|
961
|
+
await runRouter({
|
|
962
|
+
workspaceDir,
|
|
963
|
+
...COMMON_PARAMS,
|
|
964
|
+
config: makeConfig(),
|
|
965
|
+
database: stubDb,
|
|
966
|
+
});
|
|
967
|
+
expect(providerCalls).toHaveLength(1);
|
|
968
|
+
});
|
|
969
|
+
|
|
970
|
+
test("tier2_size=2 produces 2 batches (tier 2 + rest)", async () => {
|
|
971
|
+
scoresStub.set("alpha", 1.0);
|
|
972
|
+
scoresStub.set("bravo", 5.0);
|
|
973
|
+
scoresStub.set("delta", 4.0);
|
|
974
|
+
|
|
975
|
+
providerStub = makeProvider(toolUseResponse([1]));
|
|
976
|
+
await runRouter({
|
|
977
|
+
workspaceDir,
|
|
978
|
+
...COMMON_PARAMS,
|
|
979
|
+
config: makeConfig({ tier2Size: 2 }),
|
|
980
|
+
database: stubDb,
|
|
981
|
+
});
|
|
982
|
+
expect(providerCalls.length).toBe(2);
|
|
983
|
+
|
|
984
|
+
// Tier 2 is the first batch (no tier 1 in this test). Its prompt should
|
|
985
|
+
// contain exactly the top-2 by score: bravo (5) and delta (4).
|
|
986
|
+
const tier2Prompt = providerCalls[0].systemPrompt ?? "";
|
|
987
|
+
const indexedSlugs = new Set(
|
|
988
|
+
[...tier2Prompt.matchAll(/^\[\d+\] (\S+)/gm)].map((m) => m[1]),
|
|
989
|
+
);
|
|
990
|
+
expect(indexedSlugs).toEqual(new Set(["bravo", "delta"]));
|
|
991
|
+
});
|
|
992
|
+
|
|
993
|
+
test("tier 1 then tier 2 then rest — three batches in that order", async () => {
|
|
994
|
+
// Pin mtimes so tier 1 is deterministic: alpha + bravo are the two
|
|
995
|
+
// most recent (highest mtime values). Tier 2 runs on the rest
|
|
996
|
+
// (charlie, delta, echo) using scores we control.
|
|
997
|
+
const { utimes } = await import("node:fs/promises");
|
|
998
|
+
const stamp = async (slug: string, s: number) =>
|
|
999
|
+
utimes(join(workspaceDir, "memory", "concepts", `${slug}.md`), s, s);
|
|
1000
|
+
await stamp("alpha", 9000);
|
|
1001
|
+
await stamp("bravo", 8000);
|
|
1002
|
+
await stamp("charlie", 3000);
|
|
1003
|
+
await stamp("delta", 2000);
|
|
1004
|
+
await stamp("echo", 1000);
|
|
1005
|
+
invalidatePageIndex();
|
|
1006
|
+
|
|
1007
|
+
scoresStub.set("charlie", 2.0);
|
|
1008
|
+
scoresStub.set("delta", 3.0);
|
|
1009
|
+
// echo has no score → ineligible for tier 2.
|
|
1010
|
+
|
|
1011
|
+
providerStub = makeProvider(toolUseResponse([1]));
|
|
1012
|
+
await runRouter({
|
|
1013
|
+
workspaceDir,
|
|
1014
|
+
...COMMON_PARAMS,
|
|
1015
|
+
config: makeConfig({ tier1Size: 2, tier2Size: 2 }),
|
|
1016
|
+
database: stubDb,
|
|
1017
|
+
});
|
|
1018
|
+
expect(providerCalls.length).toBe(3);
|
|
1019
|
+
|
|
1020
|
+
// Tier 2 batch (index 1) contains charlie + delta. Echo went to rest.
|
|
1021
|
+
const tier2Prompt = providerCalls[1].systemPrompt ?? "";
|
|
1022
|
+
const tier2Slugs = new Set(
|
|
1023
|
+
[...tier2Prompt.matchAll(/^\[\d+\] (\S+)/gm)].map((m) => m[1]),
|
|
1024
|
+
);
|
|
1025
|
+
expect(tier2Slugs).toEqual(new Set(["charlie", "delta"]));
|
|
1026
|
+
|
|
1027
|
+
// Echo (no score) must land in the rest batch, not tier 2.
|
|
1028
|
+
const restPrompt = providerCalls[2].systemPrompt ?? "";
|
|
1029
|
+
const restSlugs = new Set(
|
|
1030
|
+
[...restPrompt.matchAll(/^\[\d+\] (\S+)/gm)].map((m) => m[1]),
|
|
1031
|
+
);
|
|
1032
|
+
expect(restSlugs).toEqual(new Set(["echo"]));
|
|
1033
|
+
});
|
|
1034
|
+
|
|
1035
|
+
test("score=0 pages stay in rest even when tier2_size is large", async () => {
|
|
1036
|
+
// Only one page has a positive score; tier2_size=100 should NOT pull in
|
|
1037
|
+
// zero-score pages.
|
|
1038
|
+
scoresStub.set("bravo", 5.0);
|
|
1039
|
+
|
|
1040
|
+
providerStub = makeProvider(toolUseResponse([1]));
|
|
1041
|
+
await runRouter({
|
|
1042
|
+
workspaceDir,
|
|
1043
|
+
...COMMON_PARAMS,
|
|
1044
|
+
config: makeConfig({ tier2Size: 100 }),
|
|
1045
|
+
database: stubDb,
|
|
1046
|
+
});
|
|
1047
|
+
expect(providerCalls.length).toBe(2);
|
|
1048
|
+
const tier2Prompt = providerCalls[0].systemPrompt ?? "";
|
|
1049
|
+
expect(tier2Prompt).toContain("[1] bravo");
|
|
1050
|
+
expect(tier2Prompt).not.toMatch(/^\[\d+\] alpha/m);
|
|
1051
|
+
});
|
|
1052
|
+
|
|
1053
|
+
test("sourceBySlug tags each selection with its batch tier", async () => {
|
|
1054
|
+
scoresStub.set("bravo", 5.0);
|
|
1055
|
+
scoresStub.set("delta", 3.0);
|
|
1056
|
+
|
|
1057
|
+
providerStub = makeProvider(toolUseResponse([1]));
|
|
1058
|
+
const result = await runRouter({
|
|
1059
|
+
workspaceDir,
|
|
1060
|
+
...COMMON_PARAMS,
|
|
1061
|
+
config: makeConfig({ tier1Size: 1, tier2Size: 1 }),
|
|
1062
|
+
database: stubDb,
|
|
1063
|
+
});
|
|
1064
|
+
|
|
1065
|
+
// Every selected slug should have a tier tag — exactly one of:
|
|
1066
|
+
// "tier1", "tier2", or "tier3:N" (N starts at 0).
|
|
1067
|
+
for (const slug of result.selectedSlugs) {
|
|
1068
|
+
const source = result.sourceBySlug.get(slug);
|
|
1069
|
+
expect(source).toBeDefined();
|
|
1070
|
+
expect(
|
|
1071
|
+
source === "tier1" ||
|
|
1072
|
+
source === "tier2" ||
|
|
1073
|
+
source!.startsWith("tier3:"),
|
|
1074
|
+
).toBe(true);
|
|
1075
|
+
}
|
|
1076
|
+
// Tier 1 + tier 2 + (some tier 3 batches) ≥ 3 batches → at least one
|
|
1077
|
+
// slug per tier should be present in the source map across the union.
|
|
1078
|
+
const tags = new Set(result.sourceBySlug.values());
|
|
1079
|
+
expect(tags.has("tier1")).toBe(true);
|
|
1080
|
+
expect(tags.has("tier2")).toBe(true);
|
|
1081
|
+
});
|
|
1082
|
+
|
|
1083
|
+
test("tier2_size set without database logs a warn and skips tier 2", async () => {
|
|
1084
|
+
scoresStub.set("bravo", 5.0);
|
|
1085
|
+
providerStub = makeProvider(toolUseResponse([1]));
|
|
1086
|
+
await runRouter({
|
|
1087
|
+
workspaceDir,
|
|
1088
|
+
...COMMON_PARAMS,
|
|
1089
|
+
config: makeConfig({ tier2Size: 2 }),
|
|
1090
|
+
// No database → tier 2 silently skipped.
|
|
1091
|
+
});
|
|
1092
|
+
expect(providerCalls).toHaveLength(1);
|
|
1093
|
+
const warned = warnLogs.some((l) =>
|
|
1094
|
+
JSON.stringify(l.args).includes("tier2_size set but no database"),
|
|
1095
|
+
);
|
|
1096
|
+
expect(warned).toBe(true);
|
|
1097
|
+
});
|
|
1098
|
+
});
|
|
1099
|
+
|
|
1100
|
+
// ---------------------------------------------------------------------------
|
|
1101
|
+
// applyHistoricalCharBudget — pure helper covering the cap semantics.
|
|
1102
|
+
// ---------------------------------------------------------------------------
|
|
1103
|
+
|
|
1104
|
+
describe("applyHistoricalCharBudget", () => {
|
|
1105
|
+
test("null budget is a no-op (returns a shallow copy)", () => {
|
|
1106
|
+
const pairs = [
|
|
1107
|
+
{ assistantMessage: "older asst", userMessage: "older user" },
|
|
1108
|
+
{ assistantMessage: "newer asst", userMessage: "newer user" },
|
|
1109
|
+
];
|
|
1110
|
+
const out = applyHistoricalCharBudget(pairs, null);
|
|
1111
|
+
expect(out).toEqual(pairs);
|
|
1112
|
+
// shallow copy — not the same array reference, so callers can mutate freely
|
|
1113
|
+
expect(out).not.toBe(pairs);
|
|
1114
|
+
});
|
|
1115
|
+
|
|
1116
|
+
test("budget that fits every message returns content unchanged", () => {
|
|
1117
|
+
const pairs = [
|
|
1118
|
+
{ assistantMessage: "AA", userMessage: "UU" },
|
|
1119
|
+
{ assistantMessage: "BB", userMessage: "VV" },
|
|
1120
|
+
];
|
|
1121
|
+
const total = "AA".length + "UU".length + "BB".length + "VV".length; // 8
|
|
1122
|
+
const out = applyHistoricalCharBudget(pairs, total);
|
|
1123
|
+
expect(out).toEqual(pairs);
|
|
1124
|
+
});
|
|
1125
|
+
|
|
1126
|
+
test("front-truncates the oldest still-includable message when the cap is exceeded", () => {
|
|
1127
|
+
// Newest user is 10 chars, newest assistant is 10, older user is 10,
|
|
1128
|
+
// older assistant is 20. Budget 35 leaves remaining = 35 - 10 - 10 - 10 = 5
|
|
1129
|
+
// for the older assistant; 5 - 1 marker char = 4 kept chars from the END.
|
|
1130
|
+
const pairs = [
|
|
1131
|
+
{ assistantMessage: "ABCDEFGHIJKLMNOPQRST", userMessage: "old-user--" },
|
|
1132
|
+
{ assistantMessage: "abcdefghij", userMessage: "uvwxyzUVWX" },
|
|
1133
|
+
];
|
|
1134
|
+
const out = applyHistoricalCharBudget(pairs, 35);
|
|
1135
|
+
expect(out).toEqual([
|
|
1136
|
+
{ assistantMessage: "…QRST", userMessage: "old-user--" },
|
|
1137
|
+
{ assistantMessage: "abcdefghij", userMessage: "uvwxyzUVWX" },
|
|
1138
|
+
]);
|
|
1139
|
+
// Sanity: total content chars equals the budget.
|
|
1140
|
+
const totalChars = out.reduce(
|
|
1141
|
+
(acc, p) => acc + p.assistantMessage.length + p.userMessage.length,
|
|
1142
|
+
0,
|
|
1143
|
+
);
|
|
1144
|
+
expect(totalChars).toBe(35);
|
|
1145
|
+
});
|
|
1146
|
+
|
|
1147
|
+
test("drops older pairs entirely when even their first message has no room", () => {
|
|
1148
|
+
// Budget 20 fits the most-recent pair exactly (10 + 10 = 20) and leaves
|
|
1149
|
+
// zero room for the older pair, which is dropped entirely.
|
|
1150
|
+
const pairs = [
|
|
1151
|
+
{ assistantMessage: "OLD-ASST00", userMessage: "OLD-USER00" },
|
|
1152
|
+
{ assistantMessage: "NEW-ASST00", userMessage: "NEW-USER00" },
|
|
1153
|
+
];
|
|
1154
|
+
const out = applyHistoricalCharBudget(pairs, 20);
|
|
1155
|
+
expect(out).toEqual([
|
|
1156
|
+
{ assistantMessage: "NEW-ASST00", userMessage: "NEW-USER00" },
|
|
1157
|
+
]);
|
|
1158
|
+
});
|
|
1159
|
+
|
|
1160
|
+
test("drops the older message of the current pair when the user line consumes the whole budget", () => {
|
|
1161
|
+
// Budget 10 just barely covers the newest user (10 chars). The pair's
|
|
1162
|
+
// own assistant message has no room and is dropped (left empty).
|
|
1163
|
+
const pairs = [
|
|
1164
|
+
{ assistantMessage: "ASSISTANTX", userMessage: "USER-NEW10" },
|
|
1165
|
+
];
|
|
1166
|
+
const out = applyHistoricalCharBudget(pairs, 10);
|
|
1167
|
+
expect(out).toEqual([{ assistantMessage: "", userMessage: "USER-NEW10" }]);
|
|
1168
|
+
});
|
|
1169
|
+
|
|
1170
|
+
test("non-positive budgets return an empty array (no message survives)", () => {
|
|
1171
|
+
const pairs = [{ assistantMessage: "x", userMessage: "y" }];
|
|
1172
|
+
expect(applyHistoricalCharBudget(pairs, 0)).toEqual(pairs);
|
|
1173
|
+
// Negative budgets are degenerate but should not throw.
|
|
1174
|
+
expect(applyHistoricalCharBudget(pairs, -5)).toEqual(pairs);
|
|
1175
|
+
});
|
|
1176
|
+
|
|
1177
|
+
test("budget smaller than the truncation marker drops the would-truncate message", () => {
|
|
1178
|
+
// Budget 11: covers full newest user (10 chars). Remaining 1 char is not
|
|
1179
|
+
// enough room for the marker, so the next message (newest assistant)
|
|
1180
|
+
// is dropped entirely rather than emitting a marker-only message.
|
|
1181
|
+
const pairs = [
|
|
1182
|
+
{ assistantMessage: "ASSISTANTX", userMessage: "USER-NEW10" },
|
|
1183
|
+
];
|
|
1184
|
+
const out = applyHistoricalCharBudget(pairs, 11);
|
|
1185
|
+
expect(out).toEqual([{ assistantMessage: "", userMessage: "USER-NEW10" }]);
|
|
1186
|
+
});
|
|
1187
|
+
});
|