@vellumai/assistant 0.8.4 → 0.8.5
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/ARCHITECTURE.md +2 -2
- package/docs/browser-use-architecture-phase2.md +1 -1
- package/knip.json +2 -1
- package/openapi.yaml +809 -11
- package/package.json +1 -1
- package/src/__tests__/anthropic-provider.test.ts +34 -37
- package/src/__tests__/assistant-event-hub-self-exclusion.test.ts +293 -0
- package/src/__tests__/assistant-feature-flags-integration.test.ts +3 -3
- package/src/__tests__/audit-log-rotation.test.ts +70 -16
- package/src/__tests__/background-workers-disk-pressure.test.ts +3 -3
- package/src/__tests__/btw-routes.test.ts +2 -3
- package/src/__tests__/call-controller.test.ts +0 -1
- package/src/__tests__/cancel-resolves-conversation-key.test.ts +1 -1
- package/src/__tests__/channel-guardian.test.ts +3 -3
- package/src/__tests__/checker.test.ts +6 -15
- package/src/__tests__/compaction-events.test.ts +1 -0
- package/src/__tests__/compactor-call-site-logging.test.ts +214 -0
- package/src/__tests__/computer-use-skill-manifest-regression.test.ts +5 -11
- package/src/__tests__/computer-use-tools.test.ts +2 -4
- package/src/__tests__/confirmation-request-guardian-bridge.test.ts +0 -1
- package/src/__tests__/conversation-agent-loop-disk-pressure.test.ts +1 -1
- package/src/__tests__/conversation-agent-loop-inference-profile.test.ts +1 -1
- package/src/__tests__/conversation-agent-loop-overflow.test.ts +197 -2
- package/src/__tests__/conversation-agent-loop.test.ts +163 -122
- package/src/__tests__/conversation-app-control-instantiation.test.ts +2 -5
- package/src/__tests__/conversation-clear-safety.test.ts +25 -25
- package/src/__tests__/conversation-delete-schedule-cleanup.test.ts +1 -1
- package/src/__tests__/conversation-disk-view-integration.test.ts +2 -2
- package/src/__tests__/conversation-error.test.ts +31 -0
- package/src/__tests__/conversation-fork-crud.test.ts +178 -15
- package/src/__tests__/conversation-lifecycle.test.ts +52 -11
- package/src/__tests__/{conversation-load-cleaned-at.test.ts → conversation-load-history-stripped.test.ts} +13 -13
- package/src/__tests__/conversation-provider-retry-repair.test.ts +1 -0
- package/src/__tests__/conversation-routes-disk-view.test.ts +109 -0
- package/src/__tests__/conversation-routes-slash-commands.test.ts +35 -0
- package/src/__tests__/conversation-skill-tools.test.ts +2 -5
- package/src/__tests__/conversation-store.test.ts +1 -1
- package/src/__tests__/conversation-sync-tags.test.ts +99 -32
- package/src/__tests__/conversation-workspace-cache-state.test.ts +1 -0
- package/src/__tests__/conversation-workspace-injection.test.ts +1 -1
- package/src/__tests__/conversation-workspace-tool-tracking.test.ts +1 -1
- package/src/__tests__/credential-execution-feature-gates.test.ts +9 -7
- package/src/__tests__/credential-execution-tools.test.ts +6 -6
- package/src/__tests__/credential-security-invariants.test.ts +1 -0
- package/src/__tests__/credential-vault-unit.test.ts +2 -2
- package/src/__tests__/dynamic-page-surface.test.ts +2 -2
- package/src/__tests__/email-html-renderer.test.ts +12 -0
- package/src/__tests__/gateway-flag-listener.test.ts +237 -0
- package/src/__tests__/gemini-provider.test.ts +78 -0
- package/src/__tests__/guardian-dispatch.test.ts +0 -1
- package/src/__tests__/guardian-outbound-http.test.ts +7 -5
- package/src/__tests__/handlers-user-message-approval-consumption.test.ts +1 -1
- package/src/__tests__/heartbeat-disk-pressure.test.ts +4 -0
- package/src/__tests__/heartbeat-service.test.ts +4 -0
- package/src/__tests__/host-shell-tool.test.ts +1 -1
- package/src/__tests__/init-feature-flag-overrides.test.ts +5 -6
- package/src/__tests__/list-messages-tool-merge.test.ts +70 -11
- package/src/__tests__/llm-request-log-call-site.test.ts +136 -0
- package/src/__tests__/llm-request-log-source-clickhouse.test.ts +26 -0
- package/src/__tests__/llm-resolver.test.ts +77 -9
- package/src/__tests__/llm-usage-store.test.ts +66 -0
- package/src/__tests__/logger.test.ts +89 -0
- package/src/__tests__/mcp-abort-signal.test.ts +2 -2
- package/src/__tests__/media-generate-image.test.ts +31 -0
- package/src/__tests__/memory-v2-static-injector.test.ts +7 -7
- package/src/__tests__/model-intents.test.ts +2 -4
- package/src/__tests__/notification-guardian-path.test.ts +0 -1
- package/src/__tests__/onboarding-template-contract.test.ts +1 -1
- package/src/__tests__/openai-provider.test.ts +46 -0
- package/src/__tests__/openai-responses-provider.test.ts +114 -12
- package/src/__tests__/pending-interactions-resolved-event.test.ts +0 -1
- package/src/__tests__/platform-bash-auto-approve.test.ts +2 -2
- package/src/__tests__/platform.test.ts +2 -2
- package/src/__tests__/plugin-api-tool-definition.test.ts +92 -0
- package/src/__tests__/plugin-bootstrap.test.ts +2 -2
- package/src/__tests__/plugin-tool-contribution.test.ts +13 -6
- package/src/__tests__/plugin-types.test.ts +3 -2
- package/src/__tests__/prechat-onboarding-contract.test.ts +131 -98
- package/src/__tests__/pricing.test.ts +12 -0
- package/src/__tests__/prune-jobs-changes-parser.test.ts +61 -0
- package/src/__tests__/registry.test.ts +2 -8
- package/src/__tests__/require-fresh-approval.test.ts +2 -2
- package/src/__tests__/runtime-events-sse-bilingual.test.ts +154 -0
- package/src/__tests__/shell-tool-proxy-mode.test.ts +1 -1
- package/src/__tests__/skill-feature-flags.test.ts +2 -2
- package/src/__tests__/skill-projection-feature-flag.test.ts +4 -7
- package/src/__tests__/skill-projection.benchmark.test.ts +2 -6
- package/src/__tests__/skill-tool-factory.test.ts +1 -1
- package/src/__tests__/subagent-notify-parent.test.ts +1 -1
- package/src/__tests__/suggestion-routes.test.ts +1 -0
- package/src/__tests__/sync-message-contract.test.ts +59 -0
- package/src/__tests__/system-prompt.test.ts +145 -131
- package/src/__tests__/terminal-tools.test.ts +1 -1
- package/src/__tests__/tool-approval-handler.test.ts +1 -5
- package/src/__tests__/tool-execute-pipeline.test.ts +2 -2
- package/src/__tests__/tool-execution-pipeline.benchmark.test.ts +2 -5
- package/src/__tests__/tool-executor-lifecycle-events.test.ts +15 -5
- package/src/__tests__/tool-executor.test.ts +9 -62
- package/src/__tests__/tool-grant-request-escalation.test.ts +1 -6
- package/src/__tests__/trusted-contact-approval-notifier.test.ts +0 -1
- package/src/__tests__/trusted-contact-inline-approval-integration.test.ts +1 -6
- package/src/__tests__/trusted-contact-multichannel.test.ts +0 -1
- package/src/__tests__/ui-file-upload-surface.test.ts +2 -2
- package/src/__tests__/usage-routes.test.ts +3 -0
- package/src/__tests__/verification-control-plane-policy.test.ts +2 -2
- package/src/__tests__/workspace-git-service.test.ts +6 -5
- package/src/__tests__/workspace-migration-089-move-memory-tree-out-of-v3.test.ts +86 -0
- package/src/acp/__tests__/prepare-agent-env.test.ts +146 -0
- package/src/acp/prepare-agent-env.ts +78 -0
- package/src/acp/session-manager.ts +1 -1
- package/src/agent/loop.ts +8 -0
- package/src/api/README.md +5 -0
- package/src/api/index.ts +4 -0
- package/src/api/package.json +10 -0
- package/src/background-wake/background-wake-routes.test.ts +233 -0
- package/src/background-wake/runtime-registry.ts +24 -0
- package/src/cli/commands/__tests__/browser.test.ts +23 -5
- package/src/cli/commands/__tests__/domain-register.test.ts +110 -0
- package/src/cli/commands/__tests__/domain-status.test.ts +33 -33
- package/src/cli/commands/__tests__/inference-send.test.ts +108 -5
- package/src/cli/commands/__tests__/memory-v2-compare-render.test.ts +98 -0
- package/src/cli/commands/__tests__/memory-v2.test.ts +1 -0
- package/src/cli/commands/__tests__/memory-v3-render.test.ts +340 -0
- package/src/cli/commands/browser.ts +247 -0
- package/src/cli/commands/domain.ts +91 -41
- package/src/cli/commands/inference.ts +93 -40
- package/src/cli/commands/memory-v2-compare-render.ts +115 -0
- package/src/cli/commands/memory-v2.ts +176 -1
- package/src/cli/commands/memory-v3-render.ts +344 -0
- package/src/cli/commands/memory-v3.ts +316 -0
- package/src/cli/program.ts +2 -0
- package/src/config/assistant-feature-flags.ts +21 -9
- package/src/config/bundled-skills/document-editor/SKILL.md +11 -2
- package/src/config/bundled-skills/document-editor/TOOLS.json +18 -0
- package/src/config/bundled-skills/document-editor/tools/document-open.ts +12 -0
- package/src/config/bundled-skills/image-studio/SKILL.md +4 -0
- package/src/config/bundled-skills/image-studio/tools/media-generate-image.ts +2 -2
- package/src/config/bundled-skills/media-processing/tools/ingest-media.ts +13 -8
- package/src/config/bundled-skills/messaging/tools/messaging-analyze-style.ts +10 -3
- package/src/config/bundled-skills/phone-calls/references/TRANSCRIPTS.md +16 -14
- package/src/config/bundled-skills/playbooks/tools/playbook-create.ts +7 -2
- package/src/config/bundled-skills/playbooks/tools/playbook-update.ts +7 -2
- package/src/config/bundled-tool-registry.ts +2 -0
- package/src/config/call-site-defaults.ts +7 -6
- package/src/config/feature-flag-registry.json +16 -0
- package/src/config/schemas/__tests__/memory-v2.test.ts +213 -1
- package/src/config/schemas/call-site-catalog.ts +21 -7
- package/src/config/schemas/llm.ts +12 -1
- package/src/config/schemas/memory-v2.ts +246 -0
- package/src/config/schemas/memory.ts +2 -1
- package/src/context/compactor.ts +52 -0
- package/src/conversations/__tests__/message-consolidation.test.ts +350 -0
- package/src/conversations/message-consolidation.ts +404 -0
- package/src/daemon/__tests__/conversation-tool-setup-exclude.test.ts +1 -1
- package/src/daemon/__tests__/meet-manifest-loader.test.ts +1 -1
- package/src/daemon/conversation-agent-loop-handlers.ts +2 -13
- package/src/daemon/conversation-agent-loop.ts +126 -76
- package/src/daemon/conversation-error.ts +31 -1
- package/src/daemon/conversation-lifecycle.ts +27 -22
- package/src/daemon/conversation-runtime-assembly.ts +10 -9
- package/src/daemon/conversation-tool-setup.ts +63 -3
- package/src/daemon/conversation-usage.ts +2 -0
- package/src/daemon/conversation.ts +14 -29
- package/src/daemon/disk-pressure-guard.ts +14 -2
- package/src/daemon/handlers/config-model.test.ts +1 -0
- package/src/daemon/handlers/conversations.ts +11 -3
- package/src/daemon/host-browser-proxy.ts +5 -5
- package/src/daemon/host-cu-proxy.ts +4 -4
- package/src/daemon/host-file-proxy.ts +4 -4
- package/src/daemon/host-proxy-base.ts +4 -4
- package/src/daemon/host-transfer-proxy.ts +10 -10
- package/src/daemon/lifecycle.ts +23 -20
- package/src/daemon/meet-manifest-loader.ts +1 -7
- package/src/daemon/message-types/conversations.ts +6 -9
- package/src/daemon/message-types/home.ts +1 -13
- package/src/daemon/message-types/messages.ts +6 -14
- package/src/daemon/message-types/sync.ts +14 -0
- package/src/daemon/shutdown-handlers.ts +24 -5
- package/src/daemon/switch-inference-profile-tool.ts +52 -0
- package/src/daemon/tool-setup-types.ts +13 -0
- package/src/events/relationship-state-updated.ts +25 -0
- package/src/heartbeat/__tests__/heartbeat-service.test.ts +1 -1
- package/src/home/home-greeting.ts +0 -9
- package/src/home/suggested-prompts.ts +0 -9
- package/src/ipc/gateway-flag-listener.ts +123 -0
- package/src/ipc/skill-routes/registries.ts +8 -12
- package/src/memory/__tests__/db-async-query.test.ts +165 -0
- package/src/memory/__tests__/db-maintenance.test.ts +115 -0
- package/src/memory/__tests__/jobs-store-enqueue-gate.test.ts +241 -0
- package/src/memory/__tests__/jobs-store-job-classes.test.ts +28 -1
- package/src/memory/__tests__/memory-retrospective-job.test.ts +7 -0
- package/src/memory/auto-analysis-enqueue.ts +5 -1
- package/src/memory/conversation-crud.ts +71 -70
- package/src/memory/conversation-starters-cadence.ts +3 -1
- package/src/memory/conversation-title-service.ts +19 -3
- package/src/memory/db-async-query.ts +214 -0
- package/src/memory/db-init.ts +10 -0
- package/src/memory/db-maintenance.ts +30 -21
- package/src/memory/graph/bootstrap.ts +8 -1
- package/src/memory/graph/capability-seed.ts +7 -3
- package/src/memory/graph/conversation-graph-memory.ts +100 -17
- package/src/memory/graph/extraction.ts +1 -5
- package/src/memory/graph/graph-search.ts +7 -1
- package/src/memory/indexer.ts +28 -18
- package/src/memory/job-handlers/cleanup.ts +76 -18
- package/src/memory/job-handlers/conversation-starters.ts +1 -4
- package/src/memory/jobs/embed-pkb-file.ts +6 -1
- package/src/memory/jobs-store.ts +14 -0
- package/src/memory/jobs-worker.ts +55 -22
- package/src/memory/llm-request-log-source-clickhouse.ts +42 -2
- package/src/memory/llm-request-log-source-local.ts +7 -0
- package/src/memory/llm-request-log-source.ts +9 -2
- package/src/memory/llm-request-log-store.ts +43 -1
- package/src/memory/llm-usage-store.ts +24 -0
- package/src/memory/memory-retrospective-enqueue.ts +8 -1
- package/src/memory/memory-retrospective-job.ts +5 -0
- package/src/memory/memory-v2-activation-log-store.ts +15 -6
- package/src/memory/migrations/260-rename-cleaned-at.ts +44 -0
- package/src/memory/migrations/261-llm-usage-add-raw-usage.ts +36 -0
- package/src/memory/migrations/262-memory-v3-coactivation.ts +57 -0
- package/src/memory/migrations/263-memory-v3-auto-edges.ts +50 -0
- package/src/memory/migrations/264-llm-request-log-call-site.ts +29 -0
- package/src/memory/migrations/index.ts +17 -0
- package/src/memory/migrations/registry.ts +33 -0
- package/src/memory/schema/conversations.ts +1 -1
- package/src/memory/schema/infrastructure.ts +21 -0
- package/src/memory/tool-usage-store.ts +36 -8
- package/src/memory/v2/__tests__/consolidation-job.test.ts +1 -0
- package/src/memory/v2/__tests__/harness-compare.test.ts +186 -0
- package/src/memory/v2/__tests__/harness-metrics.test.ts +74 -0
- package/src/memory/v2/__tests__/harness-oracle.test.ts +257 -0
- package/src/memory/v2/__tests__/harness-replay-input.test.ts +225 -0
- package/src/memory/v2/__tests__/harness-runner.test.ts +109 -0
- package/src/memory/v2/__tests__/injection.test.ts +127 -98
- package/src/memory/v2/__tests__/qdrant.test.ts +36 -0
- package/src/memory/v2/__tests__/router.test.ts +171 -3
- package/src/memory/v2/harness/compare.ts +57 -0
- package/src/memory/v2/harness/metrics.ts +124 -0
- package/src/memory/v2/harness/oracle.ts +145 -0
- package/src/memory/v2/harness/replay-input.ts +224 -0
- package/src/memory/v2/harness/retriever.ts +74 -0
- package/src/memory/v2/harness/router-retriever.ts +43 -0
- package/src/memory/v2/harness/runner.ts +106 -0
- package/src/memory/v2/harness/trace.ts +58 -0
- package/src/memory/v2/injection.ts +21 -15
- package/src/memory/v2/prompts/router.ts +26 -1
- package/src/memory/v2/qdrant.ts +14 -2
- package/src/memory/v2/router.ts +171 -18
- package/src/memory/v3/__tests__/coactivation-store.test.ts +422 -0
- package/src/memory/v3/__tests__/consolidation-job.test.ts +468 -0
- package/src/memory/v3/__tests__/edge-learning-job.test.ts +324 -0
- package/src/memory/v3/__tests__/edges.test.ts +563 -0
- package/src/memory/v3/__tests__/filter.test.ts +512 -0
- package/src/memory/v3/__tests__/gate.test.ts +574 -0
- package/src/memory/v3/__tests__/index-composition.test.ts +233 -0
- package/src/memory/v3/__tests__/loop.test.ts +530 -0
- package/src/memory/v3/__tests__/retriever.test.ts +226 -0
- package/src/memory/v3/__tests__/scouts.test.ts +440 -0
- package/src/memory/v3/__tests__/shadow-middleware.test.ts +312 -0
- package/src/memory/v3/__tests__/system-prompts.test.ts +154 -0
- package/src/memory/v3/__tests__/traversal.test.ts +469 -0
- package/src/memory/v3/__tests__/tree-index.test.ts +280 -0
- package/src/memory/v3/__tests__/tree-store.test.ts +529 -0
- package/src/memory/v3/__tests__/tree-walk.test.ts +707 -0
- package/src/memory/v3/__tests__/validate.test.ts +245 -0
- package/src/memory/v3/auto-edges.ts +223 -0
- package/src/memory/v3/coactivation-store.ts +124 -0
- package/src/memory/v3/consolidation-job.ts +323 -0
- package/src/memory/v3/edge-learning-job.ts +160 -0
- package/src/memory/v3/edges.ts +249 -0
- package/src/memory/v3/filter.ts +281 -0
- package/src/memory/v3/gate.ts +334 -0
- package/src/memory/v3/index-composition.ts +113 -0
- package/src/memory/v3/llm-capture.ts +46 -0
- package/src/memory/v3/loop.ts +382 -0
- package/src/memory/v3/maintenance.ts +144 -0
- package/src/memory/v3/prompt-context.ts +33 -0
- package/src/memory/v3/prompts/consolidation.ts +458 -0
- package/src/memory/v3/prompts/system-prompts.ts +196 -0
- package/src/memory/v3/retriever.ts +33 -0
- package/src/memory/v3/scouts.ts +420 -0
- package/src/memory/v3/shadow-middleware.ts +305 -0
- package/src/memory/v3/traversal.ts +206 -0
- package/src/memory/v3/tree-index.ts +237 -0
- package/src/memory/v3/tree-store.ts +394 -0
- package/src/memory/v3/tree-walk.ts +351 -0
- package/src/memory/v3/types.ts +65 -0
- package/src/memory/v3/validate.ts +300 -0
- package/src/notifications/adapters/macos.ts +18 -1
- package/src/notifications/adapters/platform.ts +1 -1
- package/src/notifications/decision-engine.ts +1 -4
- package/src/notifications/emit-signal.ts +29 -49
- package/src/permissions/prompter.ts +3 -3
- package/src/permissions/question-prompter.ts +5 -2
- package/src/permissions/secret-prompter.ts +2 -2
- package/src/plugin-api/index.ts +4 -0
- package/src/plugin-api/types.ts +7 -33
- package/src/plugins/defaults/index.ts +6 -0
- package/src/plugins/defaults/injectors.ts +18 -11
- package/src/plugins/external-plugin-loader.ts +5 -68
- package/src/plugins/types.ts +11 -16
- package/src/proactive-artifact/aux-message-injector.ts +17 -4
- package/src/prompts/__tests__/task-progress-hint-section.test.ts +3 -9
- package/src/prompts/persona-resolver.ts +36 -21
- package/src/prompts/sections.ts +39 -7
- package/src/prompts/system-prompt.ts +50 -185
- package/src/prompts/templates/BOOTSTRAP.md +2 -2
- package/src/prompts/templates/system-sections.ts +230 -8
- package/src/providers/__tests__/connection-model-compat.test.ts +234 -0
- package/src/providers/__tests__/retry-callsite.test.ts +85 -5
- package/src/providers/anthropic/client.ts +32 -66
- package/src/providers/call-site-routing.ts +14 -2
- package/src/providers/connection-model-compat.ts +38 -0
- package/src/providers/connection-resolution.ts +16 -2
- package/src/providers/gemini/client.ts +49 -6
- package/src/providers/inference/adapter-factory.ts +3 -0
- package/src/providers/minimax/client.ts +106 -0
- package/src/providers/model-catalog.ts +43 -0
- package/src/providers/model-intents.ts +1 -1
- package/src/providers/openai/chat-completions-provider.ts +6 -3
- package/src/providers/openai/codex-models.ts +18 -0
- package/src/providers/openai/responses-provider.ts +78 -21
- package/src/providers/provider-send-message.ts +7 -1
- package/src/providers/retry.ts +34 -3
- package/src/providers/thinking-config.ts +26 -1
- package/src/providers/usage-tracking.ts +2 -0
- package/src/runtime/AGENTS.md +2 -2
- package/src/runtime/agent-wake.ts +1 -0
- package/src/runtime/assistant-event-hub.ts +76 -6
- package/src/runtime/auth/route-policy.ts +36 -0
- package/src/runtime/btw-sidechain.ts +0 -6
- package/src/runtime/http-types.ts +0 -2
- package/src/runtime/migrations/vbundle-builder.ts +10 -3
- package/src/runtime/pending-interactions.ts +0 -1
- package/src/runtime/routes/__tests__/conversation-query-routes.test.ts +106 -0
- package/src/runtime/routes/__tests__/memory-v2-simulate-route.test.ts +25 -6
- package/src/runtime/routes/__tests__/plugins-routes.test.ts +512 -0
- package/src/runtime/routes/acp-routes.test.ts +255 -6
- package/src/runtime/routes/acp-routes.ts +8 -1
- package/src/runtime/routes/avatar-routes.ts +10 -10
- package/src/runtime/routes/background-wake-routes.ts +188 -0
- package/src/runtime/routes/browser-tabs-routes.ts +200 -0
- package/src/runtime/routes/btw-routes.ts +0 -6
- package/src/runtime/routes/conversation-cli-routes.ts +1 -1
- package/src/runtime/routes/conversation-list-routes.ts +12 -4
- package/src/runtime/routes/conversation-management-routes.ts +77 -20
- package/src/runtime/routes/conversation-query-routes.ts +142 -36
- package/src/runtime/routes/conversation-routes.ts +252 -410
- package/src/runtime/routes/conversation-starter-routes.ts +6 -3
- package/src/runtime/routes/disk-pressure-routes.ts +1 -1
- package/src/runtime/routes/domain-routes.ts +60 -10
- package/src/runtime/routes/email-routes.ts +5 -2
- package/src/runtime/routes/events-routes.ts +54 -10
- package/src/runtime/routes/group-routes.ts +24 -8
- package/src/runtime/routes/host-browser-routes.ts +10 -2
- package/src/runtime/routes/host-cu-routes.ts +2 -2
- package/src/runtime/routes/inbound-stages/acl-enforcement.ts +96 -3
- package/src/runtime/routes/index.ts +8 -0
- package/src/runtime/routes/inference-profile-session-handler.ts +22 -12
- package/src/runtime/routes/inference-profile-session-routes.ts +7 -1
- package/src/runtime/routes/llm-call-sites-routes.ts +32 -5
- package/src/runtime/routes/memory-item-routes.ts +8 -3
- package/src/runtime/routes/memory-v2-routes.ts +215 -5
- package/src/runtime/routes/memory-v3-routes.ts +316 -0
- package/src/runtime/routes/migration-routes.ts +21 -24
- package/src/runtime/routes/plugins-routes.ts +337 -0
- package/src/runtime/routes/rename-conversation-routes.ts +6 -2
- package/src/runtime/routes/secret-routes.ts +25 -5
- package/src/runtime/routes/settings-routes.ts +12 -11
- package/src/runtime/routes/slack-channel-routes.ts +5 -4
- package/src/runtime/routes/workspace-routes.ts +25 -10
- package/src/runtime/sync/resource-sync-events.ts +106 -38
- package/src/runtime/sync/sync-publisher.test.ts +49 -0
- package/src/runtime/sync/sync-publisher.ts +2 -1
- package/src/runtime/verification-outbound-actions.ts +73 -1
- package/src/telemetry/types.ts +12 -0
- package/src/telemetry/usage-telemetry-reporter.test.ts +48 -0
- package/src/telemetry/usage-telemetry-reporter.ts +1 -0
- package/src/tools/acp/spawn.test.ts +119 -0
- package/src/tools/acp/spawn.ts +15 -2
- package/src/tools/apps/definitions.ts +2 -8
- package/src/tools/ask-question/ask-question-tool.test.ts +3 -3
- package/src/tools/ask-question/ask-question-tool.ts +38 -45
- package/src/tools/browser/__tests__/pinned-tabs.test.ts +70 -0
- package/src/tools/browser/browser-execution.ts +16 -3
- package/src/tools/browser/cdp-client/__tests__/browser-tabs-factory.test.ts +402 -0
- package/src/tools/browser/cdp-client/__tests__/types.test.ts +3 -0
- package/src/tools/browser/cdp-client/cdp-inspect-client.ts +12 -0
- package/src/tools/browser/cdp-client/extension-cdp-client.ts +27 -1
- package/src/tools/browser/cdp-client/factory.ts +100 -17
- package/src/tools/browser/cdp-client/local-cdp-client.ts +12 -0
- package/src/tools/browser/cdp-client/types.ts +65 -0
- package/src/tools/browser/pinned-tabs.ts +96 -40
- package/src/tools/computer-use/definitions.ts +22 -78
- package/src/tools/credential-execution/make-authenticated-request.ts +3 -9
- package/src/tools/credential-execution/manage-secure-command-tool.ts +3 -9
- package/src/tools/credential-execution/run-authenticated-command.ts +3 -9
- package/src/tools/credentials/vault.ts +3 -9
- package/src/tools/document/document-tool.ts +59 -0
- package/src/tools/execution-target.ts +21 -23
- package/src/tools/executor.ts +6 -1
- package/src/tools/filesystem/edit.ts +3 -9
- package/src/tools/filesystem/list.ts +3 -9
- package/src/tools/filesystem/read.ts +3 -9
- package/src/tools/filesystem/write.ts +3 -9
- package/src/tools/host-filesystem/edit.ts +3 -9
- package/src/tools/host-filesystem/read.ts +3 -9
- package/src/tools/host-filesystem/transfer.ts +3 -9
- package/src/tools/host-filesystem/write.ts +3 -9
- package/src/tools/host-terminal/host-shell.ts +3 -9
- package/src/tools/mcp/mcp-tool-factory.ts +1 -8
- package/src/tools/memory/register.test.ts +1 -1
- package/src/tools/memory/register.ts +4 -9
- package/src/tools/network/web-fetch.ts +3 -9
- package/src/tools/network/web-search.ts +25 -32
- package/src/tools/registry.ts +7 -23
- package/src/tools/schema-transforms.ts +1 -1
- package/src/tools/skills/execute.ts +3 -9
- package/src/tools/skills/load.ts +3 -9
- package/src/tools/skills/skill-tool-factory.ts +1 -8
- package/src/tools/subagent/notify-parent.ts +3 -9
- package/src/tools/system/request-permission.ts +3 -9
- package/src/tools/terminal/shell.ts +3 -9
- package/src/tools/tool-defaults.ts +94 -0
- package/src/tools/types.ts +27 -98
- package/src/tools/ui-surface/definitions.ts +6 -22
- package/src/usage/pricing.ts +23 -0
- package/src/usage/types.ts +12 -0
- package/src/util/logger.ts +16 -7
- package/src/util/platform.ts +7 -2
- package/src/util/sqlite3-runtime.ts +65 -0
- package/src/workspace/migrations/086-revert-stale-gemini-mis-rewrites.ts +1 -0
- package/src/workspace/migrations/089-move-memory-tree-out-of-v3.ts +86 -0
- package/src/workspace/migrations/registry.ts +2 -0
- package/src/__tests__/compaction-strip-metadata-clear.test.ts +0 -206
- package/src/__tests__/message-complete-display-id.test.ts +0 -175
- package/src/daemon/query-complexity-router.ts +0 -75
- package/src/prompts/cache-boundary.ts +0 -8
package/src/memory/v2/router.ts
CHANGED
|
@@ -21,9 +21,11 @@
|
|
|
21
21
|
* since NOW.md only changes when the model rewrites it. We set the 1h
|
|
22
22
|
* TTL explicitly here to match the provider-side breakpoints; the
|
|
23
23
|
* default 5m would force unnecessary cache re-creation.
|
|
24
|
-
* The
|
|
25
|
-
*
|
|
26
|
-
*
|
|
24
|
+
* The trailing user-message block holds `<last_turn>` content that changes
|
|
25
|
+
* every call (new user turn + new prior assistant reply), so we pass
|
|
26
|
+
* `disableTurnStartCache: true` to the provider to suppress its auto-applied
|
|
27
|
+
* 1h breakpoint there — caching it would create unused cache entries (pure
|
|
28
|
+
* cache_creation cost with no future hit).
|
|
27
29
|
*
|
|
28
30
|
* This module is pure orchestration — it does not mutate activation state,
|
|
29
31
|
* write any files, or update the conversation. PR 10 wires it into
|
|
@@ -155,10 +157,31 @@ function emptyBatchResult(
|
|
|
155
157
|
return { selectedSlugs: [], failureReason: reason };
|
|
156
158
|
}
|
|
157
159
|
|
|
160
|
+
/**
|
|
161
|
+
* One `(assistant, user)` turn pair rendered inside `<last_turn>`. The
|
|
162
|
+
* pair represents the assistant's reply followed by the user message
|
|
163
|
+
* that came after. The most recent pair's `userMessage` is the
|
|
164
|
+
* just-arrived turn that triggered the router; older pairs are walked
|
|
165
|
+
* back from conversation history. `assistantMessage` is the empty
|
|
166
|
+
* string for the oldest pair when there was no prior assistant reply
|
|
167
|
+
* (conversation start) — `runRouterBatch` skips the `[assistant]:`
|
|
168
|
+
* line entirely in that case.
|
|
169
|
+
*/
|
|
170
|
+
export interface RouterTurnPair {
|
|
171
|
+
assistantMessage: string;
|
|
172
|
+
userMessage: string;
|
|
173
|
+
}
|
|
174
|
+
|
|
158
175
|
interface RunRouterParams {
|
|
159
176
|
workspaceDir: string;
|
|
160
|
-
|
|
161
|
-
|
|
177
|
+
/**
|
|
178
|
+
* Recent assistant/user turn pairs, oldest first. Must contain at
|
|
179
|
+
* least one entry. The last entry's `userMessage` is the just-arrived
|
|
180
|
+
* user turn the router is routing for; entries before it are walked
|
|
181
|
+
* back from conversation history. The number of pairs the production
|
|
182
|
+
* caller passes is controlled by `memory.v2.router.historical_pairs`.
|
|
183
|
+
*/
|
|
184
|
+
recentTurnPairs: readonly RouterTurnPair[];
|
|
162
185
|
/** Verbatim contents to inject into `<now>...</now>` on this turn. */
|
|
163
186
|
nowText: string;
|
|
164
187
|
/** Slugs already injected on prior turns (used to seed `<already_injected_ids>`). */
|
|
@@ -172,6 +195,28 @@ interface RunRouterParams {
|
|
|
172
195
|
* only exercise tier 1 / tier 3 paths can omit it.
|
|
173
196
|
*/
|
|
174
197
|
database?: DrizzleDb;
|
|
198
|
+
/**
|
|
199
|
+
* Per-call profile override forwarded to `getConfiguredProvider`. When
|
|
200
|
+
* set, the `memoryRouter` call site resolves against this profile name
|
|
201
|
+
* instead of the workspace active profile. The simulator route uses
|
|
202
|
+
* this to compare different profiles against the same query; live
|
|
203
|
+
* router callers leave it unset.
|
|
204
|
+
*/
|
|
205
|
+
overrideProfile?: string;
|
|
206
|
+
/**
|
|
207
|
+
* Skip the post-union truncation to `max_page_ids`. Used by the
|
|
208
|
+
* simulator so the playground can show the full untruncated router
|
|
209
|
+
* output across all batches. Live callers (`injectViaRouter`) leave
|
|
210
|
+
* this unset so the bounded-injection contract holds.
|
|
211
|
+
*/
|
|
212
|
+
disableUnionCap?: boolean;
|
|
213
|
+
/**
|
|
214
|
+
* Per-call inline router system-prompt override. Takes precedence
|
|
215
|
+
* over `memory.v2.router.router_prompt_path` and the bundled body.
|
|
216
|
+
* Used by the simulator playground for ad-hoc prompt comparisons.
|
|
217
|
+
* Live callers leave this unset.
|
|
218
|
+
*/
|
|
219
|
+
routerPromptOverride?: string;
|
|
175
220
|
}
|
|
176
221
|
|
|
177
222
|
/**
|
|
@@ -207,7 +252,11 @@ export async function runRouter(
|
|
|
207
252
|
return emptyResult("empty_index");
|
|
208
253
|
}
|
|
209
254
|
|
|
210
|
-
const provider = await getConfiguredProvider("memoryRouter"
|
|
255
|
+
const provider = await getConfiguredProvider("memoryRouter", {
|
|
256
|
+
...(params.overrideProfile !== undefined
|
|
257
|
+
? { overrideProfile: params.overrideProfile }
|
|
258
|
+
: {}),
|
|
259
|
+
});
|
|
211
260
|
if (!provider) {
|
|
212
261
|
log.warn("memoryRouter provider unavailable; router skipped");
|
|
213
262
|
return emptyResult("no_provider");
|
|
@@ -302,15 +351,18 @@ export async function runRouter(
|
|
|
302
351
|
// exceed it (e.g. 10 batches × 10 selections each ≫ 25 cap). Apply a final
|
|
303
352
|
// truncation so RouterResult honors the contract that injection.ts trusts.
|
|
304
353
|
// Iteration order above is tier 1 → tier 2 → tier 3:0 → … so earlier-tier
|
|
305
|
-
// slugs win the truncation.
|
|
306
|
-
|
|
307
|
-
if (
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
354
|
+
// slugs win the truncation. The simulator passes `disableUnionCap` so the
|
|
355
|
+
// playground can show the full untruncated union for analysis.
|
|
356
|
+
if (!params.disableUnionCap) {
|
|
357
|
+
const maxPageIds = config.memory?.v2?.router?.max_page_ids ?? 25;
|
|
358
|
+
if (selectedSlugs.length > maxPageIds) {
|
|
359
|
+
log.warn(
|
|
360
|
+
{ unionSize: selectedSlugs.length, max: maxPageIds },
|
|
361
|
+
"Router union across batches exceeded max_page_ids; truncating",
|
|
362
|
+
);
|
|
363
|
+
const dropped = selectedSlugs.splice(maxPageIds);
|
|
364
|
+
for (const slug of dropped) sourceBySlug.delete(slug);
|
|
365
|
+
}
|
|
314
366
|
}
|
|
315
367
|
return { selectedSlugs, sourceBySlug, failureReason: null };
|
|
316
368
|
}
|
|
@@ -331,8 +383,7 @@ async function runRouterBatch(
|
|
|
331
383
|
): Promise<RouterBatchResult> {
|
|
332
384
|
const {
|
|
333
385
|
workspaceDir,
|
|
334
|
-
|
|
335
|
-
assistantMessage,
|
|
386
|
+
recentTurnPairs,
|
|
336
387
|
nowText,
|
|
337
388
|
priorEverInjected,
|
|
338
389
|
config,
|
|
@@ -349,6 +400,7 @@ async function runRouterBatch(
|
|
|
349
400
|
userName: resolveUserName(workspaceDir),
|
|
350
401
|
pageIndexBlock: batchIndex.rendered,
|
|
351
402
|
},
|
|
403
|
+
params.routerPromptOverride ?? null,
|
|
352
404
|
);
|
|
353
405
|
|
|
354
406
|
// Filter prior-injected to slugs present in THIS batch and map to
|
|
@@ -360,6 +412,29 @@ async function runRouterBatch(
|
|
|
360
412
|
if (local) priorIds.push(local.id);
|
|
361
413
|
}
|
|
362
414
|
|
|
415
|
+
// Trim the pairs down to the configured `<last_turn>` content budget,
|
|
416
|
+
// newest-message-first so the just-arrived user turn keeps full claim
|
|
417
|
+
// on the cap and the oldest still-includable message is front-truncated
|
|
418
|
+
// (rather than dropping the most recent message). `null` is a no-op.
|
|
419
|
+
const cappedPairs = applyHistoricalCharBudget(
|
|
420
|
+
recentTurnPairs,
|
|
421
|
+
config.memory?.v2?.router?.historical_pairs_max_chars ?? null,
|
|
422
|
+
);
|
|
423
|
+
|
|
424
|
+
// Render `<last_turn>` chronologically: each pair emits the prior
|
|
425
|
+
// assistant reply followed by the user message that came after.
|
|
426
|
+
// `assistantMessage` is the empty string on the oldest pair when there
|
|
427
|
+
// was no prior assistant reply (conversation start) — skip that line
|
|
428
|
+
// so we don't emit a dangling `[assistant]:`.
|
|
429
|
+
const lastTurnLines: string[] = [];
|
|
430
|
+
for (const pair of cappedPairs) {
|
|
431
|
+
if (pair.assistantMessage.trim().length > 0) {
|
|
432
|
+
lastTurnLines.push(`[assistant]: ${pair.assistantMessage}`);
|
|
433
|
+
}
|
|
434
|
+
lastTurnLines.push(`[user]: ${pair.userMessage}`);
|
|
435
|
+
}
|
|
436
|
+
const lastTurnBlock = `<last_turn>\n${lastTurnLines.join("\n")}\n</last_turn>`;
|
|
437
|
+
|
|
363
438
|
const userMsg: Message = {
|
|
364
439
|
role: "user",
|
|
365
440
|
content: [
|
|
@@ -368,7 +443,7 @@ async function runRouterBatch(
|
|
|
368
443
|
type: "text",
|
|
369
444
|
text:
|
|
370
445
|
`<already_injected_ids>\n${priorIds.join(", ")}\n</already_injected_ids>\n\n` +
|
|
371
|
-
|
|
446
|
+
lastTurnBlock,
|
|
372
447
|
},
|
|
373
448
|
],
|
|
374
449
|
};
|
|
@@ -386,6 +461,7 @@ async function runRouterBatch(
|
|
|
386
461
|
config: {
|
|
387
462
|
callSite: "memoryRouter" as const,
|
|
388
463
|
tool_choice: { type: "tool" as const, name: ROUTER_TOOL_NAME },
|
|
464
|
+
disableTurnStartCache: true,
|
|
389
465
|
},
|
|
390
466
|
...(signal ? { signal } : {}),
|
|
391
467
|
},
|
|
@@ -453,6 +529,83 @@ async function runRouterBatch(
|
|
|
453
529
|
return { selectedSlugs, failureReason: null };
|
|
454
530
|
}
|
|
455
531
|
|
|
532
|
+
/** Truncation marker prepended to a front-truncated historical message. */
|
|
533
|
+
const HISTORICAL_TRUNCATION_MARKER = "…";
|
|
534
|
+
|
|
535
|
+
/**
|
|
536
|
+
* Apply the `<last_turn>` content character budget to a chronological
|
|
537
|
+
* pairs array. The just-arrived user message has first claim on the
|
|
538
|
+
* budget; older messages are added newest-first until exhausted. The
|
|
539
|
+
* oldest still-includable message is front-truncated with a leading
|
|
540
|
+
* `…` so it joins coherently with the next message in time. Older pairs
|
|
541
|
+
* whose content doesn't fit are dropped entirely.
|
|
542
|
+
*
|
|
543
|
+
* Counts message content only — framing characters (`[assistant]: `,
|
|
544
|
+
* `[user]: `, newlines) are not deducted from the budget. The cap is a
|
|
545
|
+
* conservative upper bound on the dialogue content surfaced to the
|
|
546
|
+
* router, not on the exact rendered block size.
|
|
547
|
+
*
|
|
548
|
+
* Exported for tests; production calls it via `runRouterBatch`.
|
|
549
|
+
*/
|
|
550
|
+
export function applyHistoricalCharBudget(
|
|
551
|
+
pairs: readonly RouterTurnPair[],
|
|
552
|
+
maxChars: number | null,
|
|
553
|
+
): RouterTurnPair[] {
|
|
554
|
+
if (maxChars === null || maxChars <= 0) return [...pairs];
|
|
555
|
+
|
|
556
|
+
type WalkedMsg = {
|
|
557
|
+
role: "user" | "assistant";
|
|
558
|
+
text: string;
|
|
559
|
+
pairIdx: number;
|
|
560
|
+
};
|
|
561
|
+
// Walk every message newest-first. Within a single pair the user
|
|
562
|
+
// message came AFTER the assistant message chronologically, so the
|
|
563
|
+
// user line gets first claim on the budget.
|
|
564
|
+
const walked: WalkedMsg[] = [];
|
|
565
|
+
for (let i = pairs.length - 1; i >= 0; i--) {
|
|
566
|
+
walked.push({ role: "user", text: pairs[i].userMessage, pairIdx: i });
|
|
567
|
+
walked.push({
|
|
568
|
+
role: "assistant",
|
|
569
|
+
text: pairs[i].assistantMessage,
|
|
570
|
+
pairIdx: i,
|
|
571
|
+
});
|
|
572
|
+
}
|
|
573
|
+
|
|
574
|
+
let used = 0;
|
|
575
|
+
const included = new Map<number, { assistant: string; user: string }>();
|
|
576
|
+
for (const msg of walked) {
|
|
577
|
+
const remaining = maxChars - used;
|
|
578
|
+
if (remaining <= 0) break;
|
|
579
|
+
let textToInclude: string;
|
|
580
|
+
let stop = false;
|
|
581
|
+
if (msg.text.length <= remaining) {
|
|
582
|
+
textToInclude = msg.text;
|
|
583
|
+
used += msg.text.length;
|
|
584
|
+
} else {
|
|
585
|
+
// Front-truncate so the surviving suffix of an older message
|
|
586
|
+
// connects to the next message (in chronological order) without
|
|
587
|
+
// a syntactic seam. The marker counts toward the budget so the
|
|
588
|
+
// emitted text never exceeds `maxChars` cumulatively.
|
|
589
|
+
if (remaining <= HISTORICAL_TRUNCATION_MARKER.length) break;
|
|
590
|
+
const keepChars = remaining - HISTORICAL_TRUNCATION_MARKER.length;
|
|
591
|
+
textToInclude = HISTORICAL_TRUNCATION_MARKER + msg.text.slice(-keepChars);
|
|
592
|
+
used = maxChars;
|
|
593
|
+
stop = true;
|
|
594
|
+
}
|
|
595
|
+
const slot = included.get(msg.pairIdx) ?? { assistant: "", user: "" };
|
|
596
|
+
if (msg.role === "user") slot.user = textToInclude;
|
|
597
|
+
else slot.assistant = textToInclude;
|
|
598
|
+
included.set(msg.pairIdx, slot);
|
|
599
|
+
if (stop) break;
|
|
600
|
+
}
|
|
601
|
+
|
|
602
|
+
const sortedIdxs = [...included.keys()].sort((a, b) => a - b);
|
|
603
|
+
return sortedIdxs.map((idx) => {
|
|
604
|
+
const slot = included.get(idx)!;
|
|
605
|
+
return { assistantMessage: slot.assistant, userMessage: slot.user };
|
|
606
|
+
});
|
|
607
|
+
}
|
|
608
|
+
|
|
456
609
|
/**
|
|
457
610
|
* Build a text content block carrying an ephemeral `cache_control`
|
|
458
611
|
* breakpoint with a 1h TTL. The Anthropic SDK accepts the field as an extra
|
|
@@ -0,0 +1,422 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Tests for `assistant/src/memory/v3/coactivation-store.ts`, its sibling
|
|
3
|
+
* migration `262-memory-v3-coactivation.ts`, and the loop's co-activation
|
|
4
|
+
* emission (`loop.ts`, gated by `config.memory.v3.write.coactivation`).
|
|
5
|
+
*
|
|
6
|
+
* Coverage:
|
|
7
|
+
* - Migration creates the table + both indexes; safe to re-run.
|
|
8
|
+
* - recordCoactivations / readCoactivations round-trip; empty list is a
|
|
9
|
+
* no-op; `since` filters by created_at.
|
|
10
|
+
* - A scripted 2-pass loop emits the expected pass-1 → pass-2 rows with the
|
|
11
|
+
* correct pass_gap when the flag is on, and nothing when it is off.
|
|
12
|
+
*
|
|
13
|
+
* Uses an in-memory bun:sqlite database — no real workspace DB. The loop's
|
|
14
|
+
* lane modules are stubbed via `mock.module`, matching `loop.test.ts`.
|
|
15
|
+
*/
|
|
16
|
+
|
|
17
|
+
import { Database } from "bun:sqlite";
|
|
18
|
+
import { afterEach, beforeEach, describe, expect, mock, test } from "bun:test";
|
|
19
|
+
|
|
20
|
+
import { drizzle } from "drizzle-orm/bun-sqlite";
|
|
21
|
+
|
|
22
|
+
import { makeMockLogger } from "../../../__tests__/helpers/mock-logger.js";
|
|
23
|
+
|
|
24
|
+
mock.module("../../../util/logger.js", () => ({
|
|
25
|
+
getLogger: () => makeMockLogger(),
|
|
26
|
+
}));
|
|
27
|
+
|
|
28
|
+
import type { DrizzleDb } from "../../db-connection.js";
|
|
29
|
+
import { getSqliteFrom } from "../../db-connection.js";
|
|
30
|
+
import {
|
|
31
|
+
downMemoryV3Coactivation,
|
|
32
|
+
migrateMemoryV3Coactivation,
|
|
33
|
+
} from "../../migrations/262-memory-v3-coactivation.js";
|
|
34
|
+
import * as schema from "../../schema.js";
|
|
35
|
+
import type {
|
|
36
|
+
RetrievalInput,
|
|
37
|
+
RetrievalOutput,
|
|
38
|
+
} from "../../v2/harness/retriever.js";
|
|
39
|
+
import type { GateDecision, ScoutResult } from "../../v2/harness/trace.js";
|
|
40
|
+
import {
|
|
41
|
+
type CoactivationRow,
|
|
42
|
+
readCoactivations,
|
|
43
|
+
recordCoactivations,
|
|
44
|
+
} from "../coactivation-store.js";
|
|
45
|
+
|
|
46
|
+
// memory_checkpoints is required by withCrashRecovery and is normally created
|
|
47
|
+
// by an early core migration. Stand it up by hand so the v3 migration can run
|
|
48
|
+
// in isolation against a fresh in-memory DB.
|
|
49
|
+
const CHECKPOINTS_DDL = /*sql*/ `
|
|
50
|
+
CREATE TABLE memory_checkpoints (
|
|
51
|
+
key TEXT PRIMARY KEY,
|
|
52
|
+
value TEXT NOT NULL,
|
|
53
|
+
updated_at INTEGER NOT NULL
|
|
54
|
+
)
|
|
55
|
+
`;
|
|
56
|
+
|
|
57
|
+
// ---------------------------------------------------------------------------
|
|
58
|
+
// Loop lane stubs — installed before importing the module under test. Mirrors
|
|
59
|
+
// loop.test.ts: each test rewires the `lane` refs before calling the loop.
|
|
60
|
+
// ---------------------------------------------------------------------------
|
|
61
|
+
|
|
62
|
+
interface RunScoutsResult {
|
|
63
|
+
scouts: ScoutResult[];
|
|
64
|
+
sticky: Set<string>;
|
|
65
|
+
bypass: Set<string>;
|
|
66
|
+
}
|
|
67
|
+
interface FilterResult {
|
|
68
|
+
kept: string[];
|
|
69
|
+
trace: { judged: string[]; dropped: string[] };
|
|
70
|
+
failureReason?: string;
|
|
71
|
+
}
|
|
72
|
+
interface WalkResult {
|
|
73
|
+
pages: Set<string>;
|
|
74
|
+
levels: Array<{
|
|
75
|
+
node: string;
|
|
76
|
+
considered: string[];
|
|
77
|
+
descended: string[];
|
|
78
|
+
skipped: string[];
|
|
79
|
+
reasoning: string;
|
|
80
|
+
}>;
|
|
81
|
+
}
|
|
82
|
+
interface ExpandResult {
|
|
83
|
+
pulled: Set<string>;
|
|
84
|
+
expansions: Array<{ from: string; pulled: string[] }>;
|
|
85
|
+
}
|
|
86
|
+
interface GateResult {
|
|
87
|
+
decision: GateDecision;
|
|
88
|
+
selectedSlugs: string[];
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
const lane = {
|
|
92
|
+
scouts: [] as RunScoutsResult[],
|
|
93
|
+
filter: [] as FilterResult[],
|
|
94
|
+
walk: [] as WalkResult[],
|
|
95
|
+
edges: [] as ExpandResult[],
|
|
96
|
+
gate: [] as GateResult[],
|
|
97
|
+
};
|
|
98
|
+
|
|
99
|
+
function nextOf<T>(list: T[], index: number): T {
|
|
100
|
+
return list[Math.min(index, list.length - 1)];
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
let scoutCallCount = 0;
|
|
104
|
+
let filterCallCount = 0;
|
|
105
|
+
let walkCallCount = 0;
|
|
106
|
+
let edgeCallCount = 0;
|
|
107
|
+
let gateCallCount = 0;
|
|
108
|
+
|
|
109
|
+
mock.module("../scouts.js", () => ({
|
|
110
|
+
runScouts: async (): Promise<RunScoutsResult> =>
|
|
111
|
+
nextOf(lane.scouts, scoutCallCount++),
|
|
112
|
+
}));
|
|
113
|
+
mock.module("../filter.js", () => ({
|
|
114
|
+
filterDenseHits: async (): Promise<FilterResult> =>
|
|
115
|
+
nextOf(lane.filter, filterCallCount++),
|
|
116
|
+
}));
|
|
117
|
+
mock.module("../tree-walk.js", () => ({
|
|
118
|
+
runTreeWalk: async (): Promise<WalkResult> =>
|
|
119
|
+
nextOf(lane.walk, walkCallCount++),
|
|
120
|
+
}));
|
|
121
|
+
mock.module("../edges.js", () => ({
|
|
122
|
+
expandEdges: async (): Promise<ExpandResult> =>
|
|
123
|
+
nextOf(lane.edges, edgeCallCount++),
|
|
124
|
+
}));
|
|
125
|
+
mock.module("../gate.js", () => ({
|
|
126
|
+
runGate: async (): Promise<GateResult> => nextOf(lane.gate, gateCallCount++),
|
|
127
|
+
}));
|
|
128
|
+
mock.module("../tree-index.js", () => ({
|
|
129
|
+
getTreeIndex: async () => ({
|
|
130
|
+
nodes: new Map(),
|
|
131
|
+
childrenByNode: new Map(),
|
|
132
|
+
parentsByNode: new Map(),
|
|
133
|
+
pageParents: new Map(),
|
|
134
|
+
root: "_root",
|
|
135
|
+
}),
|
|
136
|
+
}));
|
|
137
|
+
mock.module("../../v2/page-index.js", () => ({
|
|
138
|
+
getPageIndex: async () => ({
|
|
139
|
+
entries: [],
|
|
140
|
+
bySlug: new Map(),
|
|
141
|
+
byId: new Map(),
|
|
142
|
+
rendered: "",
|
|
143
|
+
}),
|
|
144
|
+
}));
|
|
145
|
+
|
|
146
|
+
const { runRetrievalLoop } = await import("../loop.js");
|
|
147
|
+
|
|
148
|
+
let sqlite: Database;
|
|
149
|
+
let database: DrizzleDb;
|
|
150
|
+
|
|
151
|
+
beforeEach(() => {
|
|
152
|
+
sqlite = new Database(":memory:");
|
|
153
|
+
database = drizzle(sqlite, { schema });
|
|
154
|
+
getSqliteFrom(database).exec(CHECKPOINTS_DDL);
|
|
155
|
+
migrateMemoryV3Coactivation(database);
|
|
156
|
+
|
|
157
|
+
lane.scouts = [];
|
|
158
|
+
lane.filter = [];
|
|
159
|
+
lane.walk = [];
|
|
160
|
+
lane.edges = [];
|
|
161
|
+
lane.gate = [];
|
|
162
|
+
scoutCallCount = 0;
|
|
163
|
+
filterCallCount = 0;
|
|
164
|
+
walkCallCount = 0;
|
|
165
|
+
edgeCallCount = 0;
|
|
166
|
+
gateCallCount = 0;
|
|
167
|
+
});
|
|
168
|
+
|
|
169
|
+
afterEach(() => {
|
|
170
|
+
sqlite.close();
|
|
171
|
+
});
|
|
172
|
+
|
|
173
|
+
function scout(laneName: ScoutResult["lane"], slugs: string[]): ScoutResult {
|
|
174
|
+
return { lane: laneName, slugs };
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
function makeInput(opts?: {
|
|
178
|
+
passCap?: number;
|
|
179
|
+
coactivation?: boolean;
|
|
180
|
+
}): RetrievalInput {
|
|
181
|
+
return {
|
|
182
|
+
workspaceDir: "/tmp/does-not-matter",
|
|
183
|
+
recentTurnPairs: [],
|
|
184
|
+
nowText: "NOW",
|
|
185
|
+
priorEverInjected: [],
|
|
186
|
+
config: {
|
|
187
|
+
memory: {
|
|
188
|
+
v3: {
|
|
189
|
+
passCap: opts?.passCap ?? 3,
|
|
190
|
+
lanes: {
|
|
191
|
+
hot: true,
|
|
192
|
+
sparse: true,
|
|
193
|
+
dense: true,
|
|
194
|
+
tree: true,
|
|
195
|
+
edges: true,
|
|
196
|
+
},
|
|
197
|
+
write: {
|
|
198
|
+
enabled: false,
|
|
199
|
+
consolidateIntervalMs: 3600000,
|
|
200
|
+
coactivation: opts?.coactivation ?? false,
|
|
201
|
+
},
|
|
202
|
+
},
|
|
203
|
+
},
|
|
204
|
+
} as unknown as RetrievalInput["config"],
|
|
205
|
+
};
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
// ---------------------------------------------------------------------------
|
|
209
|
+
// Migration.
|
|
210
|
+
// ---------------------------------------------------------------------------
|
|
211
|
+
|
|
212
|
+
describe("migrateMemoryV3Coactivation", () => {
|
|
213
|
+
test("creates table and both indexes; safe to re-run", () => {
|
|
214
|
+
migrateMemoryV3Coactivation(database);
|
|
215
|
+
migrateMemoryV3Coactivation(database);
|
|
216
|
+
|
|
217
|
+
const raw = getSqliteFrom(database);
|
|
218
|
+
const table = raw
|
|
219
|
+
.query(
|
|
220
|
+
`SELECT name FROM sqlite_master WHERE type='table' AND name='memory_v3_coactivation'`,
|
|
221
|
+
)
|
|
222
|
+
.get();
|
|
223
|
+
expect(table).toBeTruthy();
|
|
224
|
+
|
|
225
|
+
const indexNames = new Set(
|
|
226
|
+
(
|
|
227
|
+
raw
|
|
228
|
+
.query(
|
|
229
|
+
`SELECT name FROM sqlite_master WHERE type='index' AND tbl_name='memory_v3_coactivation'`,
|
|
230
|
+
)
|
|
231
|
+
.all() as Array<{ name: string }>
|
|
232
|
+
).map((r) => r.name),
|
|
233
|
+
);
|
|
234
|
+
expect(indexNames.has("idx_memory_v3_coactivation_pair")).toBe(true);
|
|
235
|
+
expect(indexNames.has("idx_memory_v3_coactivation_time")).toBe(true);
|
|
236
|
+
});
|
|
237
|
+
|
|
238
|
+
test("downMemoryV3Coactivation drops the table", () => {
|
|
239
|
+
downMemoryV3Coactivation(database);
|
|
240
|
+
const table = getSqliteFrom(database)
|
|
241
|
+
.query(
|
|
242
|
+
`SELECT name FROM sqlite_master WHERE type='table' AND name='memory_v3_coactivation'`,
|
|
243
|
+
)
|
|
244
|
+
.get();
|
|
245
|
+
expect(table).toBeFalsy();
|
|
246
|
+
});
|
|
247
|
+
});
|
|
248
|
+
|
|
249
|
+
// ---------------------------------------------------------------------------
|
|
250
|
+
// Store.
|
|
251
|
+
// ---------------------------------------------------------------------------
|
|
252
|
+
|
|
253
|
+
describe("recordCoactivations / readCoactivations", () => {
|
|
254
|
+
test("round-trips rows oldest-first", () => {
|
|
255
|
+
const rows: CoactivationRow[] = [
|
|
256
|
+
{
|
|
257
|
+
conversationId: "conv-1",
|
|
258
|
+
turn: 3,
|
|
259
|
+
sourceSlug: "alice",
|
|
260
|
+
targetSlug: "bob",
|
|
261
|
+
passGap: 1,
|
|
262
|
+
used: 0,
|
|
263
|
+
createdAt: 1_000,
|
|
264
|
+
},
|
|
265
|
+
{
|
|
266
|
+
conversationId: "conv-1",
|
|
267
|
+
turn: 3,
|
|
268
|
+
sourceSlug: "alice",
|
|
269
|
+
targetSlug: "carol",
|
|
270
|
+
passGap: 2,
|
|
271
|
+
used: 0,
|
|
272
|
+
createdAt: 2_000,
|
|
273
|
+
},
|
|
274
|
+
];
|
|
275
|
+
recordCoactivations(database, rows);
|
|
276
|
+
|
|
277
|
+
const read = readCoactivations(database);
|
|
278
|
+
expect(read).toHaveLength(2);
|
|
279
|
+
expect(read[0]).toMatchObject({
|
|
280
|
+
conversationId: "conv-1",
|
|
281
|
+
turn: 3,
|
|
282
|
+
sourceSlug: "alice",
|
|
283
|
+
targetSlug: "bob",
|
|
284
|
+
passGap: 1,
|
|
285
|
+
used: 0,
|
|
286
|
+
createdAt: 1_000,
|
|
287
|
+
});
|
|
288
|
+
expect(read[1].targetSlug).toBe("carol");
|
|
289
|
+
expect(read[1].passGap).toBe(2);
|
|
290
|
+
});
|
|
291
|
+
|
|
292
|
+
test("empty list is a no-op", () => {
|
|
293
|
+
recordCoactivations(database, []);
|
|
294
|
+
expect(readCoactivations(database)).toHaveLength(0);
|
|
295
|
+
});
|
|
296
|
+
|
|
297
|
+
test("since filters by created_at", () => {
|
|
298
|
+
recordCoactivations(database, [
|
|
299
|
+
{
|
|
300
|
+
conversationId: "c",
|
|
301
|
+
turn: 1,
|
|
302
|
+
sourceSlug: "a",
|
|
303
|
+
targetSlug: "b",
|
|
304
|
+
passGap: 1,
|
|
305
|
+
used: 0,
|
|
306
|
+
createdAt: 100,
|
|
307
|
+
},
|
|
308
|
+
{
|
|
309
|
+
conversationId: "c",
|
|
310
|
+
turn: 1,
|
|
311
|
+
sourceSlug: "a",
|
|
312
|
+
targetSlug: "c",
|
|
313
|
+
passGap: 1,
|
|
314
|
+
used: 0,
|
|
315
|
+
createdAt: 500,
|
|
316
|
+
},
|
|
317
|
+
]);
|
|
318
|
+
const recent = readCoactivations(database, 300);
|
|
319
|
+
expect(recent).toHaveLength(1);
|
|
320
|
+
expect(recent[0].targetSlug).toBe("c");
|
|
321
|
+
});
|
|
322
|
+
});
|
|
323
|
+
|
|
324
|
+
// ---------------------------------------------------------------------------
|
|
325
|
+
// Loop emission.
|
|
326
|
+
// ---------------------------------------------------------------------------
|
|
327
|
+
|
|
328
|
+
describe("runRetrievalLoop — co-activation emission", () => {
|
|
329
|
+
/**
|
|
330
|
+
* Script a 2-pass loop: pass 1 surfaces `a` (hot) + `b` (sparse); pass 2
|
|
331
|
+
* surfaces `c` (dense). The gate says "more" on pass 1 (selecting a, b) and
|
|
332
|
+
* "ready" on pass 2 (selecting a, b, c). So `c` is the only pass-2 target,
|
|
333
|
+
* paired with pass-1 hits a and b → two rows, both pass_gap=1.
|
|
334
|
+
*/
|
|
335
|
+
function scriptTwoPass(): void {
|
|
336
|
+
lane.scouts = [
|
|
337
|
+
{
|
|
338
|
+
scouts: [scout("hot", ["a"]), scout("sparse", ["b"])],
|
|
339
|
+
sticky: new Set(),
|
|
340
|
+
bypass: new Set(),
|
|
341
|
+
},
|
|
342
|
+
{
|
|
343
|
+
scouts: [scout("dense", ["c"])],
|
|
344
|
+
sticky: new Set(),
|
|
345
|
+
bypass: new Set(),
|
|
346
|
+
},
|
|
347
|
+
];
|
|
348
|
+
// Pass 1 has no dense scout, so the filter is only called on pass 2 (one
|
|
349
|
+
// filter call per dense pass) — its single entry keeps `c`.
|
|
350
|
+
lane.filter = [{ kept: ["c"], trace: { judged: ["c"], dropped: [] } }];
|
|
351
|
+
lane.walk = [
|
|
352
|
+
{ pages: new Set(), levels: [] },
|
|
353
|
+
{ pages: new Set(), levels: [] },
|
|
354
|
+
];
|
|
355
|
+
lane.edges = [
|
|
356
|
+
{ pulled: new Set(), expansions: [] },
|
|
357
|
+
{ pulled: new Set(), expansions: [] },
|
|
358
|
+
];
|
|
359
|
+
lane.gate = [
|
|
360
|
+
{
|
|
361
|
+
decision: { decision: "more", questions: ["q"] },
|
|
362
|
+
selectedSlugs: ["a", "b"],
|
|
363
|
+
},
|
|
364
|
+
{ decision: { decision: "ready" }, selectedSlugs: ["a", "b", "c"] },
|
|
365
|
+
];
|
|
366
|
+
}
|
|
367
|
+
|
|
368
|
+
test("emits pass-1 → pass-2 rows with correct pass_gap when flag is on", async () => {
|
|
369
|
+
scriptTwoPass();
|
|
370
|
+
const out: RetrievalOutput = await runRetrievalLoop(
|
|
371
|
+
makeInput({ passCap: 3, coactivation: true }),
|
|
372
|
+
{ db: database, conversationId: "conv-42", turn: 7 },
|
|
373
|
+
);
|
|
374
|
+
expect(out.selectedSlugs).toEqual(["a", "b", "c"]);
|
|
375
|
+
|
|
376
|
+
const rows = readCoactivations(database);
|
|
377
|
+
// c (pass 2) paired with each pass-1 hit a and b → two rows.
|
|
378
|
+
expect(rows).toHaveLength(2);
|
|
379
|
+
const pairs = rows.map((r) => `${r.sourceSlug}->${r.targetSlug}`).sort();
|
|
380
|
+
expect(pairs).toEqual(["a->c", "b->c"]);
|
|
381
|
+
for (const r of rows) {
|
|
382
|
+
expect(r.targetSlug).toBe("c");
|
|
383
|
+
expect(r.passGap).toBe(1);
|
|
384
|
+
expect(r.used).toBe(0);
|
|
385
|
+
expect(r.conversationId).toBe("conv-42");
|
|
386
|
+
expect(r.turn).toBe(7);
|
|
387
|
+
}
|
|
388
|
+
});
|
|
389
|
+
|
|
390
|
+
test("emits nothing when the flag is off", async () => {
|
|
391
|
+
scriptTwoPass();
|
|
392
|
+
await runRetrievalLoop(makeInput({ passCap: 3, coactivation: false }), {
|
|
393
|
+
db: database,
|
|
394
|
+
conversationId: "conv-42",
|
|
395
|
+
turn: 7,
|
|
396
|
+
});
|
|
397
|
+
expect(readCoactivations(database)).toHaveLength(0);
|
|
398
|
+
});
|
|
399
|
+
|
|
400
|
+
test("single-pass selection emits nothing (no later-surfaced target)", async () => {
|
|
401
|
+
lane.scouts = [
|
|
402
|
+
{
|
|
403
|
+
scouts: [scout("hot", ["a"]), scout("sparse", ["b"])],
|
|
404
|
+
sticky: new Set(),
|
|
405
|
+
bypass: new Set(),
|
|
406
|
+
},
|
|
407
|
+
];
|
|
408
|
+
lane.filter = [{ kept: [], trace: { judged: [], dropped: [] } }];
|
|
409
|
+
lane.walk = [{ pages: new Set(), levels: [] }];
|
|
410
|
+
lane.edges = [{ pulled: new Set(), expansions: [] }];
|
|
411
|
+
lane.gate = [
|
|
412
|
+
{ decision: { decision: "ready" }, selectedSlugs: ["a", "b"] },
|
|
413
|
+
];
|
|
414
|
+
|
|
415
|
+
await runRetrievalLoop(makeInput({ passCap: 3, coactivation: true }), {
|
|
416
|
+
db: database,
|
|
417
|
+
conversationId: "conv-1",
|
|
418
|
+
turn: 1,
|
|
419
|
+
});
|
|
420
|
+
expect(readCoactivations(database)).toHaveLength(0);
|
|
421
|
+
});
|
|
422
|
+
});
|