@vellumai/assistant 0.8.2 → 0.8.4
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 +11 -12
- package/docker-entrypoint.sh +13 -2
- package/docker-init-apt-root.sh +79 -6
- package/node_modules/@vellumai/gateway-client/src/types.ts +2 -0
- package/openapi.yaml +945 -36
- package/package.json +1 -1
- package/src/__tests__/agent-loop-exit-reason.test.ts +271 -0
- package/src/__tests__/agent-loop-override-profile.test.ts +1 -1
- package/src/__tests__/agent-loop-provider-error-recording.test.ts +195 -0
- package/src/__tests__/agent-loop.test.ts +88 -3
- package/src/__tests__/anthropic-provider.test.ts +272 -0
- package/src/__tests__/approval-cascade.test.ts +1 -1
- package/src/__tests__/background-workers-disk-pressure.test.ts +2 -1
- package/src/__tests__/channel-delivery-store.test.ts +193 -0
- package/src/__tests__/channel-reply-delivery.test.ts +284 -5
- package/src/__tests__/channel-retry-sweep.test.ts +274 -1
- package/src/__tests__/compaction-events.test.ts +1 -1
- package/src/__tests__/compactor-preserved-tail-count.test.ts +110 -0
- package/src/__tests__/compactor-tail-resolution.test.ts +107 -1
- package/src/__tests__/config-get-vision-flag.test.ts +136 -0
- package/src/__tests__/config-loader-backfill.test.ts +115 -18
- package/src/__tests__/config-watcher.test.ts +1 -1
- package/src/__tests__/context-token-estimator.test.ts +112 -57
- package/src/__tests__/conversation-abort-tool-results.test.ts +1 -1
- package/src/__tests__/conversation-agent-loop-inference-profile.test.ts +54 -3
- package/src/__tests__/conversation-agent-loop-overflow.test.ts +31 -6
- package/src/__tests__/conversation-agent-loop.test.ts +77 -3
- 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-confirmation-signals.test.ts +1 -1
- package/src/__tests__/conversation-fork-crud.test.ts +161 -0
- package/src/__tests__/conversation-lifecycle.test.ts +1 -1
- package/src/__tests__/conversation-load-cleaned-at.test.ts +279 -0
- package/src/__tests__/conversation-load-history-repair.test.ts +1 -1
- package/src/__tests__/conversation-media-retry.test.ts +19 -8
- 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 +1 -1
- package/src/__tests__/conversation-queue.test.ts +1 -1
- package/src/__tests__/conversation-runtime-assembly.test.ts +290 -85
- package/src/__tests__/conversation-seed-composer.test.ts +66 -4
- 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-surfaces-task-progress.test.ts +220 -0
- package/src/__tests__/conversation-workspace-cache-state.test.ts +1 -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-security-invariants.test.ts +6 -0
- package/src/__tests__/cu-unified-flow.test.ts +10 -1
- package/src/__tests__/date-context.test.ts +45 -0
- 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__/external-plugin-loader.test.ts +91 -19
- package/src/__tests__/first-greeting.test.ts +23 -2
- package/src/__tests__/guardian-action-no-hardcoded-copy.test.ts +0 -1
- package/src/__tests__/guardian-dispatch.test.ts +1 -0
- package/src/__tests__/headless-browser-navigate.test.ts +172 -0
- package/src/__tests__/heartbeat-service.test.ts +24 -164
- package/src/__tests__/helpers/channel-test-adapter.ts +0 -2
- package/src/__tests__/host-app-control-proxy.test.ts +241 -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-proxy-preactivation.test.ts +200 -13
- 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__/injector-background-turn.test.ts +153 -0
- package/src/__tests__/injector-chain.test.ts +7 -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__/lifecycle-memory-v2-seed.test.ts +9 -2
- 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 +17 -16
- package/src/__tests__/llm-callsite-catalog.test.ts +25 -0
- package/src/__tests__/llm-catalog-parity.test.ts +3 -0
- package/src/__tests__/llm-context-normalization.test.ts +0 -2
- package/src/__tests__/llm-request-log-agent-loop-exit-reason.test.ts +116 -0
- package/src/__tests__/llm-request-log-error-payload.test.ts +138 -0
- package/src/__tests__/llm-request-log-source-clickhouse.test.ts +2 -0
- package/src/__tests__/llm-resolver.test.ts +340 -3
- package/src/__tests__/log-export-routes.test.ts +99 -2
- package/src/__tests__/managed-profile-guard.test.ts +10 -0
- package/src/__tests__/message-queue-steer.test.ts +114 -0
- package/src/__tests__/notification-decision-fallback.test.ts +0 -91
- package/src/__tests__/notification-decision-strategy.test.ts +14 -31
- package/src/__tests__/notification-deep-link.test.ts +15 -0
- package/src/__tests__/notification-guardian-path.test.ts +1 -2
- package/src/__tests__/notification-platform-adapter.test.ts +5 -4
- package/src/__tests__/notification-telegram-adapter.test.ts +1 -0
- package/src/__tests__/notification-vellum-adapter.test.ts +113 -0
- package/src/__tests__/openai-provider.test.ts +323 -3
- package/src/__tests__/openai-responses-cutover-guard.test.ts +3 -3
- package/src/__tests__/openai-responses-provider.test.ts +4 -4
- package/src/__tests__/openrouter-provider-only.test.ts +51 -3
- package/src/__tests__/openrouter-token-estimation.test.ts +34 -25
- package/src/__tests__/outbound-slack-persistence.test.ts +187 -20
- package/src/__tests__/pending-interactions-resolved-event.test.ts +190 -0
- package/src/__tests__/platform-proxy-context.test.ts +6 -1
- package/src/__tests__/platform.test.ts +0 -3
- package/src/__tests__/plugin-source-watcher.test.ts +302 -0
- package/src/__tests__/plugin-tool-contribution.test.ts +3 -3
- package/src/__tests__/plugin-types.test.ts +2 -2
- 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__/provider-catalog-visibility.test.ts +16 -0
- package/src/__tests__/provider-platform-proxy-integration.test.ts +27 -25
- package/src/__tests__/secret-routes-platform-proxy.test.ts +1 -1
- package/src/__tests__/server-history-render.test.ts +83 -4
- package/src/__tests__/steer-tool-repair.test.ts +249 -0
- package/src/__tests__/system-prompt.test.ts +57 -101
- package/src/__tests__/terminal-tools.test.ts +11 -1
- package/src/__tests__/thinking-block-replay.test.ts +113 -0
- package/src/__tests__/thread-backfill.test.ts +370 -22
- package/src/__tests__/tool-executor.test.ts +90 -1
- package/src/__tests__/tool-result-metadata-plumbing.test.ts +167 -0
- package/src/__tests__/twilio-routes.test.ts +1 -1
- package/src/__tests__/web-fetch.test.ts +2 -2
- package/src/__tests__/workspace-git-service.test.ts +88 -5
- package/src/__tests__/workspace-migration-087-memory-router-balanced-profile.test.ts +228 -0
- package/src/__tests__/workspace-migration-088-deprecate-background-conversation-override.test.ts +158 -0
- package/src/a2a/__tests__/agent-card.test.ts +98 -0
- package/src/a2a/__tests__/e2e-a2a-channel.test.ts +597 -0
- package/src/a2a/__tests__/protocol-helpers.test.ts +113 -0
- package/src/a2a/__tests__/task-store.test.ts +246 -0
- package/src/a2a/agent-card.ts +58 -0
- package/src/a2a/feature-gate.ts +8 -0
- package/src/a2a/protocol-constants.ts +21 -0
- package/src/a2a/protocol-errors.ts +50 -0
- package/src/a2a/protocol-types.ts +162 -0
- package/src/a2a/task-store.ts +168 -0
- package/src/agent/attachments.ts +1 -0
- package/src/agent/loop.ts +208 -22
- package/src/background-wake/next-wake.test.ts +289 -0
- package/src/background-wake/next-wake.ts +172 -0
- package/src/browser/operations.ts +15 -0
- package/src/channels/config.ts +9 -0
- package/src/channels/types.ts +14 -0
- package/src/cli/commands/__tests__/conversations-slack.test.ts +572 -0
- package/src/cli/commands/__tests__/memory-v2.test.ts +9 -12
- package/src/cli/{__tests__ → commands/__tests__}/notifications.test.ts +201 -28
- package/src/cli/commands/__tests__/schedules.test.ts +469 -0
- package/src/cli/commands/conversations.ts +128 -1
- package/src/cli/commands/inference-providers.ts +147 -1
- package/src/cli/commands/memory-v2.ts +308 -0
- package/src/cli/commands/notifications.ts +89 -37
- package/src/cli/commands/plugins.ts +67 -0
- package/src/cli/commands/schedules.ts +297 -5
- package/src/cli/lib/__tests__/search-plugins.test.ts +261 -0
- package/src/cli/lib/install-from-github.ts +8 -9
- package/src/cli/lib/search-plugins.ts +163 -0
- package/src/cli/program.ts +14 -0
- package/src/cli/utils/conversation-id.ts +17 -5
- package/src/config/assistant-feature-flags.ts +24 -54
- package/src/config/bundled-skills/app-builder/SKILL.md +117 -1
- package/src/config/bundled-skills/document-editor/SKILL.md +115 -0
- package/src/config/bundled-skills/document-editor/TOOLS.json +240 -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-replace-text.ts +12 -0
- package/src/config/bundled-skills/media-processing/SKILL.md +8 -0
- package/src/config/bundled-skills/phone-calls/SKILL.md +1 -1
- package/src/config/bundled-skills/schedule/SKILL.md +8 -0
- package/src/config/bundled-tool-registry.ts +22 -12
- package/src/config/call-site-defaults.ts +124 -0
- package/src/config/feature-flag-registry.json +111 -23
- package/src/config/llm-resolver.ts +66 -1
- package/src/config/schema.ts +2 -0
- package/src/config/schemas/__tests__/memory-v2.test.ts +7 -3
- package/src/config/schemas/call-site-catalog.ts +21 -0
- package/src/config/schemas/channels.ts +9 -0
- package/src/config/schemas/conversations.ts +10 -0
- package/src/config/schemas/heartbeat.ts +14 -0
- package/src/config/schemas/llm.ts +4 -3
- package/src/config/schemas/memory-retrospective.ts +1 -1
- package/src/config/schemas/memory-v2.ts +51 -4
- package/src/config/schemas/memory.ts +3 -1
- package/src/config/seed-inference-profiles.ts +99 -29
- package/src/context/compactor.ts +80 -13
- package/src/context/token-estimator.ts +72 -31
- package/src/context/window-manager.ts +25 -0
- package/src/credential-health/credential-health-service.ts +34 -19
- package/src/daemon/__tests__/conversation-lifecycle-auto-analyze.test.ts +3 -22
- package/src/daemon/__tests__/conversation-tool-setup.test.ts +66 -6
- 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 +231 -23
- package/src/daemon/conversation-agent-loop.ts +252 -56
- package/src/daemon/conversation-lifecycle.ts +142 -116
- 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 +144 -75
- package/src/daemon/conversation-slash.ts +37 -5
- package/src/daemon/conversation-surfaces.ts +45 -2
- package/src/daemon/conversation-tool-setup.ts +7 -0
- package/src/daemon/conversation.ts +42 -12
- package/src/daemon/date-context.ts +40 -0
- package/src/daemon/first-greeting.ts +10 -0
- package/src/daemon/guardian-action-generators.ts +1 -125
- package/src/daemon/handlers/__tests__/config-a2a-accept.test.ts +498 -0
- package/src/daemon/handlers/__tests__/config-a2a-complete.test.ts +248 -0
- package/src/daemon/handlers/__tests__/config-a2a-invite.test.ts +154 -0
- package/src/daemon/handlers/__tests__/config-a2a-redeem.test.ts +133 -0
- package/src/daemon/handlers/__tests__/config-a2a.test.ts +95 -0
- package/src/daemon/handlers/config-a2a.ts +449 -0
- package/src/daemon/handlers/config-model.test.ts +1 -0
- package/src/daemon/handlers/conversations.ts +80 -0
- package/src/daemon/handlers/shared.ts +92 -29
- package/src/daemon/host-app-control-proxy.ts +69 -18
- package/src/daemon/host-bash-proxy.ts +1 -1
- package/src/daemon/host-cu-proxy.ts +1 -1
- package/src/daemon/host-file-proxy.ts +1 -1
- package/src/daemon/host-proxy-preactivation.ts +85 -18
- package/src/daemon/host-transfer-proxy.ts +1 -1
- package/src/daemon/lifecycle.ts +67 -65
- package/src/daemon/memory-v2-startup.ts +49 -13
- package/src/daemon/message-protocol.ts +4 -0
- package/src/daemon/message-types/conversations.ts +8 -0
- package/src/daemon/message-types/document-comments.ts +50 -0
- package/src/daemon/message-types/messages.ts +68 -1
- package/src/daemon/message-types/notifications.ts +21 -0
- package/src/daemon/message-types/surfaces.ts +3 -1
- package/src/daemon/message-types/web-activity.ts +57 -0
- package/src/daemon/pkb-reminder-builder.test.ts +10 -53
- package/src/daemon/pkb-reminder-builder.ts +4 -19
- package/src/daemon/plugin-source-watcher.ts +135 -3
- package/src/daemon/process-message.ts +72 -12
- package/src/daemon/query-complexity-router.ts +75 -0
- package/src/daemon/skill-memory-refresh.ts +5 -1
- package/src/daemon/trust-context.ts +6 -0
- package/src/daemon/wake-target-adapter.ts +2 -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/export/__tests__/transcript-formatter.test.ts +121 -0
- package/src/export/transcript-formatter.ts +54 -20
- package/src/heartbeat/__tests__/heartbeat-service.test.ts +44 -1
- package/src/heartbeat/heartbeat-service.ts +35 -191
- package/src/home/__tests__/feed-types.test.ts +40 -0
- package/src/home/__tests__/suggested-prompts.test.ts +33 -2
- package/src/home/feed-types.ts +20 -3
- package/src/home/home-content-refresh.ts +52 -0
- package/src/home/home-greeting-cache.ts +69 -0
- package/src/home/home-greeting.ts +94 -0
- package/src/home/suggested-prompts.ts +177 -9
- package/src/ipc/cli-client.ts +147 -45
- package/src/memory/__tests__/conversation-queries.test.ts +220 -0
- package/src/memory/__tests__/jobs-worker-v2-schedule.test.ts +135 -2
- package/src/memory/__tests__/memory-retrospective-enqueue.test.ts +2 -50
- package/src/memory/__tests__/memory-retrospective-job.test.ts +407 -10
- package/src/memory/conversation-crud.ts +133 -43
- package/src/memory/conversation-queries.ts +87 -1
- package/src/memory/conversation-title-service.ts +26 -4
- package/src/memory/db-init.ts +22 -0
- 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/__tests__/conversation-graph-memory-v2-routing.test.ts +84 -3
- package/src/memory/graph/conversation-graph-memory.ts +18 -6
- package/src/memory/graph/tools.ts +6 -37
- package/src/memory/invite-store.ts +53 -0
- package/src/memory/jobs-worker.ts +21 -1
- package/src/memory/llm-request-log-source-clickhouse.ts +7 -2
- package/src/memory/llm-request-log-store.ts +92 -1
- package/src/memory/memory-retrospective-constants.ts +28 -0
- package/src/memory/memory-retrospective-enqueue.ts +4 -22
- package/src/memory/memory-retrospective-job.ts +438 -21
- package/src/memory/memory-retrospective-startup-cleanup.ts +3 -3
- package/src/memory/memory-v2-activation-log-store.ts +26 -8
- 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/250-provider-connection-base-url-and-models.ts +28 -0
- package/src/memory/migrations/251-a2a-tasks.ts +49 -0
- package/src/memory/migrations/252-llm-request-log-agent-loop-exit-reason.ts +32 -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/index.ts +20 -0
- package/src/memory/migrations/registry.ts +33 -0
- package/src/memory/onboarding-events-store.ts +7 -0
- package/src/memory/schema/a2a.ts +15 -0
- package/src/memory/schema/calls.ts +1 -0
- package/src/memory/schema/conversations.ts +3 -0
- package/src/memory/schema/index.ts +1 -0
- package/src/memory/schema/inference.ts +2 -0
- package/src/memory/schema/infrastructure.ts +2 -0
- package/src/memory/v2/__tests__/activation-store.test.ts +25 -23
- package/src/memory/v2/__tests__/cli-command-store.test.ts +404 -0
- package/src/memory/v2/__tests__/frontmatter-sweep.test.ts +25 -4
- package/src/memory/v2/__tests__/injection-events.test.ts +318 -0
- package/src/memory/v2/__tests__/injection.test.ts +221 -17
- package/src/memory/v2/__tests__/page-index.test.ts +365 -1
- package/src/memory/v2/__tests__/router.test.ts +489 -1
- package/src/memory/v2/__tests__/static-context.test.ts +12 -1
- package/src/memory/v2/activation-store.ts +14 -16
- package/src/memory/v2/cli-command-content.ts +19 -0
- package/src/memory/v2/cli-command-store.ts +304 -0
- package/src/memory/v2/consolidation-job.ts +14 -0
- package/src/memory/v2/frontmatter-sweep.ts +7 -1
- package/src/memory/v2/injection-events.ts +101 -0
- package/src/memory/v2/injection.ts +69 -29
- package/src/memory/v2/page-index.ts +246 -19
- package/src/memory/v2/page-store.ts +18 -0
- package/src/memory/v2/router.ts +209 -55
- package/src/memory/v2/static-context.ts +4 -4
- package/src/memory/v2/types.ts +23 -0
- package/src/messaging/providers/a2a/__tests__/deliver.test.ts +274 -0
- package/src/messaging/providers/a2a/deliver.ts +156 -0
- package/src/messaging/providers/gmail/client.ts +9 -2
- package/src/messaging/providers/index.ts +18 -3
- 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__/broadcaster.test.ts +203 -0
- package/src/notifications/__tests__/decision-engine.test.ts +283 -0
- package/src/notifications/__tests__/deterministic-checks.test.ts +286 -0
- package/src/notifications/__tests__/emit-signal-home-feed.test.ts +5 -1
- package/src/notifications/__tests__/home-feed-side-effect.test.ts +521 -36
- package/src/notifications/adapters/macos.ts +12 -2
- package/src/notifications/broadcaster.ts +29 -4
- package/src/notifications/conversation-seed-composer.ts +14 -2
- package/src/notifications/copy-composer.ts +17 -64
- package/src/notifications/decision-engine.ts +111 -44
- package/src/notifications/deferred-emit.ts +135 -0
- package/src/notifications/deterministic-checks.ts +96 -0
- package/src/notifications/emit-signal.ts +10 -1
- package/src/notifications/home-feed-side-effect.ts +136 -27
- package/src/notifications/signal.ts +0 -4
- package/src/notifications/types.ts +8 -0
- 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/platform-connection.test.ts +43 -3
- package/src/oauth/platform-connection.ts +13 -4
- package/src/oauth/seed-providers.ts +22 -0
- package/src/permissions/prompter.ts +5 -2
- package/src/permissions/secret-prompter.ts +4 -1
- package/src/plugins/defaults/injectors.ts +118 -26
- package/src/plugins/external-plugin-loader.ts +82 -10
- package/src/plugins/types.ts +16 -7
- package/src/prompts/__tests__/system-prompt.test.ts +44 -45
- package/src/prompts/__tests__/task-progress-hint-section.test.ts +4 -8
- package/src/prompts/normalize-onboarding.ts +40 -0
- package/src/prompts/sections.ts +32 -14
- package/src/prompts/system-prompt.ts +105 -76
- package/src/prompts/template-detection.ts +37 -0
- package/src/prompts/templates/BOOTSTRAP-CONTENT-AUTOMATION.md +141 -0
- package/src/prompts/templates/BOOTSTRAP.md +13 -5
- package/src/prompts/templates/VOICE.md +3 -0
- package/src/prompts/templates/system-sections.ts +51 -10
- package/src/providers/__tests__/inference.test.ts +2 -0
- package/src/providers/anthropic/client.ts +132 -5
- package/src/providers/call-site-routing.ts +24 -6
- package/src/providers/connection-resolution.ts +63 -13
- package/src/providers/fireworks/client.ts +20 -2
- package/src/providers/inference/__tests__/adapter-factory-openai-compatible.test.ts +74 -0
- 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/__tests__/connections-openai-compatible.test.ts +175 -0
- package/src/providers/inference/__tests__/connections-status-label.test.ts +15 -0
- package/src/providers/inference/adapter-factory.ts +24 -21
- package/src/providers/inference/auth.ts +15 -3
- package/src/providers/inference/backfill.ts +14 -1
- package/src/providers/inference/codex-token-refresh.ts +128 -0
- package/src/providers/inference/connections.ts +85 -5
- package/src/providers/inference/resolve-auth.ts +50 -5
- package/src/providers/model-catalog.ts +244 -242
- package/src/providers/model-intents.ts +3 -3
- package/src/providers/openai/__tests__/chat-completions-provider-reasoning.test.ts +235 -0
- package/src/providers/openai/chat-completions-provider.ts +215 -25
- package/src/providers/openai/responses-provider.ts +9 -3
- package/src/providers/openrouter/client.ts +46 -4
- package/src/providers/platform-proxy/constants.ts +3 -4
- package/src/providers/provider-catalog-visibility.ts +3 -1
- package/src/providers/provider-send-message.ts +27 -12
- package/src/providers/registry.ts +30 -1
- package/src/providers/types.ts +25 -0
- 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 +212 -57
- package/src/runtime/auth/route-policy.ts +20 -3
- package/src/runtime/background-job-runner.ts +26 -0
- package/src/runtime/channel-reply-delivery.ts +182 -47
- package/src/runtime/channel-retry-sweep.ts +141 -16
- package/src/runtime/http-server.ts +7 -16
- package/src/runtime/http-types.ts +7 -51
- package/src/runtime/pending-interactions.ts +51 -8
- package/src/runtime/routes/__tests__/consolidation-routes.test.ts +258 -0
- package/src/runtime/routes/__tests__/content-source-routes.test.ts +162 -0
- package/src/runtime/routes/__tests__/conversation-query-routes.test.ts +121 -5
- package/src/runtime/routes/__tests__/inference-provider-connection-routes.test.ts +275 -44
- package/src/runtime/routes/__tests__/llm-call-sites-routes.test.ts +12 -0
- package/src/runtime/routes/__tests__/memory-v2-routes.test.ts +14 -0
- package/src/runtime/routes/__tests__/memory-v2-simulate-route.test.ts +271 -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/approval-routes.ts +4 -1
- package/src/runtime/routes/channel-availability-routes.ts +5 -0
- package/src/runtime/routes/chatgpt-subscription-auth-routes.ts +246 -0
- package/src/runtime/routes/consolidation-routes.ts +100 -0
- package/src/runtime/routes/content-source-routes.ts +78 -0
- package/src/runtime/routes/conversation-cli-routes.ts +146 -1
- package/src/runtime/routes/conversation-query-routes.ts +130 -12
- package/src/runtime/routes/conversation-routes.ts +288 -76
- package/src/runtime/routes/document-comments-routes.ts +287 -0
- package/src/runtime/routes/documents-routes.ts +33 -0
- 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 +8 -1
- 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/background-dispatch.test.ts +365 -6
- package/src/runtime/routes/inbound-stages/background-dispatch.ts +283 -82
- package/src/runtime/routes/index.ts +14 -4
- package/src/runtime/routes/inference-provider-connection-routes.ts +192 -3
- package/src/runtime/routes/integrations/a2a.ts +294 -0
- package/src/runtime/routes/llm-call-sites-routes.ts +11 -1
- package/src/runtime/routes/log-export-routes.ts +39 -0
- package/src/runtime/routes/memory-v2-routes.ts +217 -0
- package/src/runtime/routes/notification-routes.ts +19 -2
- package/src/runtime/routes/question-routes.ts +4 -1
- package/src/runtime/routes/sanity-routes.ts +159 -0
- package/src/runtime/routes/slack-channel-routes.ts +187 -0
- package/src/runtime/routes/subagents-routes.ts +41 -0
- package/src/runtime/services/conversation-serializer.ts +30 -4
- 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/subagent/manager.ts +2 -0
- package/src/tools/browser/__tests__/pinned-tabs.test.ts +80 -0
- package/src/tools/browser/browser-execution.ts +93 -0
- package/src/tools/browser/cdp-client/__tests__/factory.test.ts +28 -0
- package/src/tools/browser/cdp-client/__tests__/types.test.ts +1 -0
- package/src/tools/browser/cdp-client/cdp-inspect-client.ts +10 -0
- package/src/tools/browser/cdp-client/extension-cdp-client.ts +15 -1
- package/src/tools/browser/cdp-client/factory.ts +87 -3
- package/src/tools/browser/cdp-client/local-cdp-client.ts +9 -0
- package/src/tools/browser/cdp-client/types.ts +36 -0
- package/src/tools/browser/pinned-tabs.ts +90 -0
- 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 +128 -2
- package/src/tools/memory/register.ts +1 -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 +213 -64
- package/src/tools/network/web-search.ts +191 -66
- package/src/tools/registry.ts +2 -2
- package/src/tools/terminal/safe-env.ts +3 -2
- package/src/tools/tool-approval-handler.ts +19 -12
- package/src/tools/types.ts +41 -2
- package/src/tools/ui-surface/definitions.ts +3 -1
- package/src/types/onboarding-context.ts +4 -0
- package/src/util/__tests__/favicon.test.ts +84 -0
- package/src/util/favicon.ts +40 -0
- package/src/util/platform.ts +0 -5
- package/src/workspace/git-service.ts +75 -4
- package/src/workspace/migrations/087-memory-router-balanced-profile.ts +91 -0
- package/src/workspace/migrations/088-deprecate-background-conversation-override.ts +103 -0
- package/src/workspace/migrations/registry.ts +4 -0
- package/src/__tests__/guardian-action-conversation-turn.test.ts +0 -441
- 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/memory/graph/__tests__/remember-description.test.ts +0 -55
- package/src/runtime/guardian-action-conversation-turn.ts +0 -99
- 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;
|
|
@@ -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,12 @@ 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
|
+
}) {
|
|
198
224
|
return {
|
|
199
225
|
memory: {
|
|
200
226
|
v2: {
|
|
@@ -202,6 +228,9 @@ function makeConfig(overrides?: { maxPageIds?: number }) {
|
|
|
202
228
|
router: {
|
|
203
229
|
enabled: true,
|
|
204
230
|
max_page_ids: overrides?.maxPageIds ?? 25,
|
|
231
|
+
batch_size: overrides?.batchSize ?? null,
|
|
232
|
+
tier1_size: overrides?.tier1Size ?? null,
|
|
233
|
+
tier2_size: overrides?.tier2Size ?? null,
|
|
205
234
|
},
|
|
206
235
|
},
|
|
207
236
|
},
|
|
@@ -231,6 +260,7 @@ describe("runRouter — early bails", () => {
|
|
|
231
260
|
|
|
232
261
|
expect(result).toEqual({
|
|
233
262
|
selectedSlugs: [],
|
|
263
|
+
sourceBySlug: new Map(),
|
|
234
264
|
failureReason: "empty_index",
|
|
235
265
|
});
|
|
236
266
|
// Provider must NOT be invoked when there is nothing to route.
|
|
@@ -286,6 +316,7 @@ describe("runRouter — successful tool_use", () => {
|
|
|
286
316
|
|
|
287
317
|
expect(result).toEqual({
|
|
288
318
|
selectedSlugs: [],
|
|
319
|
+
sourceBySlug: new Map(),
|
|
289
320
|
failureReason: null,
|
|
290
321
|
});
|
|
291
322
|
});
|
|
@@ -529,3 +560,460 @@ describe("runRouter — failure modes", () => {
|
|
|
529
560
|
expect(providerCalls[0].options?.signal).toBe(controller.signal);
|
|
530
561
|
});
|
|
531
562
|
});
|
|
563
|
+
|
|
564
|
+
// ---------------------------------------------------------------------------
|
|
565
|
+
// Batched routing (config.memory.v2.router.batch_size).
|
|
566
|
+
// ---------------------------------------------------------------------------
|
|
567
|
+
|
|
568
|
+
describe("runRouter — batched (batch_size set)", () => {
|
|
569
|
+
beforeEach(async () => {
|
|
570
|
+
// 5 pages → at batch_size=2 we get ceil(5/2)=3 batches.
|
|
571
|
+
await writePage(workspaceDir, makePage("alpha", { summary: "A" }));
|
|
572
|
+
await writePage(workspaceDir, makePage("bravo", { summary: "B" }));
|
|
573
|
+
await writePage(workspaceDir, makePage("charlie", { summary: "C" }));
|
|
574
|
+
await writePage(workspaceDir, makePage("delta", { summary: "D" }));
|
|
575
|
+
await writePage(workspaceDir, makePage("echo", { summary: "E" }));
|
|
576
|
+
});
|
|
577
|
+
|
|
578
|
+
test("fires one provider call per batch in parallel", async () => {
|
|
579
|
+
// Every batch returns its local id 1 → at most 3 distinct slugs in the
|
|
580
|
+
// union (one per batch), but we don't assert WHICH slugs the FNV
|
|
581
|
+
// bucketing picks; just that the provider was called once per batch.
|
|
582
|
+
providerStub = makeProvider(toolUseResponse([1]));
|
|
583
|
+
|
|
584
|
+
const result = await runRouter({
|
|
585
|
+
workspaceDir,
|
|
586
|
+
...COMMON_PARAMS,
|
|
587
|
+
config: makeConfig({ batchSize: 2 }),
|
|
588
|
+
});
|
|
589
|
+
|
|
590
|
+
expect(result.failureReason).toBeNull();
|
|
591
|
+
expect(providerCalls.length).toBeGreaterThan(1);
|
|
592
|
+
expect(providerCalls.length).toBeLessThanOrEqual(3);
|
|
593
|
+
// Every batch picked its own local id 1 → distinct slugs in union.
|
|
594
|
+
expect(result.selectedSlugs.length).toBe(providerCalls.length);
|
|
595
|
+
expect(new Set(result.selectedSlugs).size).toBe(
|
|
596
|
+
result.selectedSlugs.length,
|
|
597
|
+
);
|
|
598
|
+
});
|
|
599
|
+
|
|
600
|
+
test("each batch's system prompt contains only its own subset of slugs", async () => {
|
|
601
|
+
providerStub = makeProvider(toolUseResponse([1]));
|
|
602
|
+
await runRouter({
|
|
603
|
+
workspaceDir,
|
|
604
|
+
...COMMON_PARAMS,
|
|
605
|
+
config: makeConfig({ batchSize: 2 }),
|
|
606
|
+
});
|
|
607
|
+
|
|
608
|
+
// Across all batch calls, every slug appears in exactly one prompt.
|
|
609
|
+
const allSlugs = ["alpha", "bravo", "charlie", "delta", "echo"];
|
|
610
|
+
const appearances = new Map<string, number>(allSlugs.map((s) => [s, 0]));
|
|
611
|
+
for (const call of providerCalls) {
|
|
612
|
+
for (const slug of allSlugs) {
|
|
613
|
+
if (call.systemPrompt?.includes(slug)) {
|
|
614
|
+
appearances.set(slug, (appearances.get(slug) ?? 0) + 1);
|
|
615
|
+
}
|
|
616
|
+
}
|
|
617
|
+
}
|
|
618
|
+
for (const slug of allSlugs) {
|
|
619
|
+
expect(appearances.get(slug)).toBe(1);
|
|
620
|
+
}
|
|
621
|
+
});
|
|
622
|
+
|
|
623
|
+
test("union of selected slugs is deduplicated across batches", async () => {
|
|
624
|
+
// Every batch returns its local id 1. Same slug could appear in only
|
|
625
|
+
// one batch (since each slug lives in exactly one batch), so the union
|
|
626
|
+
// is naturally unique. Sanity-check the dedup path with a 2-call response.
|
|
627
|
+
providerStub = makeProvider(toolUseResponse([1, 1]));
|
|
628
|
+
const result = await runRouter({
|
|
629
|
+
workspaceDir,
|
|
630
|
+
...COMMON_PARAMS,
|
|
631
|
+
config: makeConfig({ batchSize: 2 }),
|
|
632
|
+
});
|
|
633
|
+
expect(result.failureReason).toBeNull();
|
|
634
|
+
expect(new Set(result.selectedSlugs).size).toBe(
|
|
635
|
+
result.selectedSlugs.length,
|
|
636
|
+
);
|
|
637
|
+
});
|
|
638
|
+
|
|
639
|
+
test("priorEverInjected is filtered to the batch's own slugs as local IDs", async () => {
|
|
640
|
+
providerStub = makeProvider(toolUseResponse([1]));
|
|
641
|
+
await runRouter({
|
|
642
|
+
workspaceDir,
|
|
643
|
+
...COMMON_PARAMS,
|
|
644
|
+
priorEverInjected: [
|
|
645
|
+
{ slug: "alpha", turn: 1 },
|
|
646
|
+
{ slug: "echo", turn: 1 },
|
|
647
|
+
],
|
|
648
|
+
config: makeConfig({ batchSize: 2 }),
|
|
649
|
+
});
|
|
650
|
+
|
|
651
|
+
// Exactly the batches containing alpha or echo should mention any
|
|
652
|
+
// already_injected_id; other batches should have an empty list.
|
|
653
|
+
for (const call of providerCalls) {
|
|
654
|
+
const text =
|
|
655
|
+
(call.messages[0].content as Array<{ text?: string }>)[1]?.text ?? "";
|
|
656
|
+
const hasAlpha = call.systemPrompt?.includes("alpha");
|
|
657
|
+
const hasEcho = call.systemPrompt?.includes("echo");
|
|
658
|
+
const expectsId = hasAlpha || hasEcho;
|
|
659
|
+
// Block contents: "<already_injected_ids>\n{ids}\n</already_injected_ids>"
|
|
660
|
+
const match = text.match(
|
|
661
|
+
/<already_injected_ids>\n([^\n]*)\n<\/already_injected_ids>/,
|
|
662
|
+
);
|
|
663
|
+
const idsStr = match?.[1] ?? "";
|
|
664
|
+
if (expectsId) {
|
|
665
|
+
expect(idsStr.trim().length).toBeGreaterThan(0);
|
|
666
|
+
} else {
|
|
667
|
+
expect(idsStr.trim()).toBe("");
|
|
668
|
+
}
|
|
669
|
+
}
|
|
670
|
+
});
|
|
671
|
+
|
|
672
|
+
test("partial failure: one batch fails, others succeed → union returned with success", async () => {
|
|
673
|
+
let callCount = 0;
|
|
674
|
+
providerStub = {
|
|
675
|
+
name: "partial-failure",
|
|
676
|
+
sendMessage: async (messages, tools, systemPrompt, options) => {
|
|
677
|
+
callCount += 1;
|
|
678
|
+
providerCalls.push({ messages, tools, systemPrompt, options });
|
|
679
|
+
if (callCount === 1) throw new Error("batch 1 boom");
|
|
680
|
+
return toolUseResponse([1]);
|
|
681
|
+
},
|
|
682
|
+
};
|
|
683
|
+
|
|
684
|
+
const result = await runRouter({
|
|
685
|
+
workspaceDir,
|
|
686
|
+
...COMMON_PARAMS,
|
|
687
|
+
config: makeConfig({ batchSize: 2 }),
|
|
688
|
+
});
|
|
689
|
+
|
|
690
|
+
expect(result.failureReason).toBeNull();
|
|
691
|
+
expect(result.selectedSlugs.length).toBeGreaterThan(0);
|
|
692
|
+
expect(providerCalls.length).toBeGreaterThan(1);
|
|
693
|
+
});
|
|
694
|
+
|
|
695
|
+
test("all batches fail → unified failure with first batch's reason", async () => {
|
|
696
|
+
providerStub = {
|
|
697
|
+
name: "all-fail",
|
|
698
|
+
sendMessage: async () => {
|
|
699
|
+
throw new Error("all batches boom");
|
|
700
|
+
},
|
|
701
|
+
};
|
|
702
|
+
|
|
703
|
+
const result = await runRouter({
|
|
704
|
+
workspaceDir,
|
|
705
|
+
...COMMON_PARAMS,
|
|
706
|
+
config: makeConfig({ batchSize: 2 }),
|
|
707
|
+
});
|
|
708
|
+
|
|
709
|
+
expect(result.failureReason).toBe("api_error");
|
|
710
|
+
expect(result.selectedSlugs).toEqual([]);
|
|
711
|
+
});
|
|
712
|
+
|
|
713
|
+
test("union across batches is truncated to global max_page_ids", async () => {
|
|
714
|
+
// 5 pages, batch_size=1 → 5 batches, each picks its own local id 1.
|
|
715
|
+
// Without a global cap the union would be 5; max_page_ids=2 forces
|
|
716
|
+
// truncation back to 2.
|
|
717
|
+
providerStub = makeProvider(toolUseResponse([1]));
|
|
718
|
+
|
|
719
|
+
const result = await runRouter({
|
|
720
|
+
workspaceDir,
|
|
721
|
+
...COMMON_PARAMS,
|
|
722
|
+
config: makeConfig({ batchSize: 1, maxPageIds: 2 }),
|
|
723
|
+
});
|
|
724
|
+
|
|
725
|
+
expect(result.failureReason).toBeNull();
|
|
726
|
+
expect(result.selectedSlugs).toHaveLength(2);
|
|
727
|
+
// sourceBySlug must stay aligned with the truncated selection.
|
|
728
|
+
expect(result.sourceBySlug.size).toBe(2);
|
|
729
|
+
for (const slug of result.selectedSlugs) {
|
|
730
|
+
expect(result.sourceBySlug.get(slug)).toBeDefined();
|
|
731
|
+
}
|
|
732
|
+
const warned = warnLogs.some((l) =>
|
|
733
|
+
JSON.stringify(l.args).includes("union across batches exceeded"),
|
|
734
|
+
);
|
|
735
|
+
expect(warned).toBe(true);
|
|
736
|
+
});
|
|
737
|
+
|
|
738
|
+
test("batch_size larger than index size is single batch (same as v3)", async () => {
|
|
739
|
+
providerStub = makeProvider(toolUseResponse([1]));
|
|
740
|
+
const result = await runRouter({
|
|
741
|
+
workspaceDir,
|
|
742
|
+
...COMMON_PARAMS,
|
|
743
|
+
config: makeConfig({ batchSize: 1000 }),
|
|
744
|
+
});
|
|
745
|
+
expect(result.failureReason).toBeNull();
|
|
746
|
+
expect(providerCalls).toHaveLength(1);
|
|
747
|
+
});
|
|
748
|
+
});
|
|
749
|
+
|
|
750
|
+
// ---------------------------------------------------------------------------
|
|
751
|
+
// Tier 1 (recently modified) splitting.
|
|
752
|
+
// ---------------------------------------------------------------------------
|
|
753
|
+
|
|
754
|
+
const { utimes } = await import("node:fs/promises");
|
|
755
|
+
|
|
756
|
+
describe("runRouter — tier 1 (recently modified)", () => {
|
|
757
|
+
async function setMtime(slug: string, epochMs: number): Promise<void> {
|
|
758
|
+
const seconds = epochMs / 1000;
|
|
759
|
+
await utimes(
|
|
760
|
+
join(workspaceDir, "memory", "concepts", `${slug}.md`),
|
|
761
|
+
seconds,
|
|
762
|
+
seconds,
|
|
763
|
+
);
|
|
764
|
+
}
|
|
765
|
+
|
|
766
|
+
beforeEach(async () => {
|
|
767
|
+
await writePage(workspaceDir, makePage("alpha", { summary: "A" }));
|
|
768
|
+
await writePage(workspaceDir, makePage("bravo", { summary: "B" }));
|
|
769
|
+
await writePage(workspaceDir, makePage("charlie", { summary: "C" }));
|
|
770
|
+
await writePage(workspaceDir, makePage("delta", { summary: "D" }));
|
|
771
|
+
await writePage(workspaceDir, makePage("echo", { summary: "E" }));
|
|
772
|
+
});
|
|
773
|
+
|
|
774
|
+
test("tier1_size + batch_size both null is the v3 single-batch path", async () => {
|
|
775
|
+
providerStub = makeProvider(toolUseResponse([1]));
|
|
776
|
+
const result = await runRouter({
|
|
777
|
+
workspaceDir,
|
|
778
|
+
...COMMON_PARAMS,
|
|
779
|
+
config: makeConfig(),
|
|
780
|
+
});
|
|
781
|
+
expect(result.failureReason).toBeNull();
|
|
782
|
+
expect(providerCalls).toHaveLength(1);
|
|
783
|
+
});
|
|
784
|
+
|
|
785
|
+
test("tier1_size=2 + batch_size=null produces 2 batches (tier1 + rest)", async () => {
|
|
786
|
+
providerStub = makeProvider(toolUseResponse([1]));
|
|
787
|
+
const result = await runRouter({
|
|
788
|
+
workspaceDir,
|
|
789
|
+
...COMMON_PARAMS,
|
|
790
|
+
config: makeConfig({ tier1Size: 2 }),
|
|
791
|
+
});
|
|
792
|
+
expect(result.failureReason).toBeNull();
|
|
793
|
+
expect(providerCalls).toHaveLength(2);
|
|
794
|
+
});
|
|
795
|
+
|
|
796
|
+
test("tier 1 contains the most recently modified pages", async () => {
|
|
797
|
+
// Stamp distinct mtimes so the ordering is unambiguous.
|
|
798
|
+
await setMtime("alpha", 1_000_000);
|
|
799
|
+
await setMtime("bravo", 5_000_000); // most recent
|
|
800
|
+
await setMtime("charlie", 2_000_000);
|
|
801
|
+
await setMtime("delta", 4_000_000); // 2nd most recent
|
|
802
|
+
await setMtime("echo", 3_000_000);
|
|
803
|
+
|
|
804
|
+
providerStub = makeProvider(toolUseResponse([1]));
|
|
805
|
+
await runRouter({
|
|
806
|
+
workspaceDir,
|
|
807
|
+
...COMMON_PARAMS,
|
|
808
|
+
config: makeConfig({ tier1Size: 2 }),
|
|
809
|
+
});
|
|
810
|
+
|
|
811
|
+
// Tier 1 is the first provider call. Match `[N] slug` lines specifically
|
|
812
|
+
// — string-search on slug name alone would false-positive on prompt
|
|
813
|
+
// template text that may mention the same words.
|
|
814
|
+
const tier1Prompt = providerCalls[0].systemPrompt ?? "";
|
|
815
|
+
const indexedSlugs = new Set(
|
|
816
|
+
[...tier1Prompt.matchAll(/^\[\d+\] (\S+)/gm)].map((m) => m[1]),
|
|
817
|
+
);
|
|
818
|
+
expect(indexedSlugs).toEqual(new Set(["bravo", "delta"]));
|
|
819
|
+
});
|
|
820
|
+
|
|
821
|
+
test("tier1_size=2 + batch_size=2 puts every slug in exactly one batch", async () => {
|
|
822
|
+
providerStub = makeProvider(toolUseResponse([1]));
|
|
823
|
+
await runRouter({
|
|
824
|
+
workspaceDir,
|
|
825
|
+
...COMMON_PARAMS,
|
|
826
|
+
config: makeConfig({ tier1Size: 2, batchSize: 2 }),
|
|
827
|
+
});
|
|
828
|
+
// 5 pages, tier1=2, rest=3 → 1 tier1 batch + 1-or-2 tier3 batches
|
|
829
|
+
// depending on whether FNV hash distributes the 3 rest slugs into both
|
|
830
|
+
// buckets. The empty-batch filter drops a bucket that lands empty, so
|
|
831
|
+
// the strong invariant is "every slug appears in exactly one batch."
|
|
832
|
+
expect(providerCalls.length).toBeGreaterThanOrEqual(2);
|
|
833
|
+
expect(providerCalls.length).toBeLessThanOrEqual(3);
|
|
834
|
+
|
|
835
|
+
const allSlugs = ["alpha", "bravo", "charlie", "delta", "echo"];
|
|
836
|
+
const appearances = new Map<string, number>(
|
|
837
|
+
allSlugs.map((s) => [s, 0] as [string, number]),
|
|
838
|
+
);
|
|
839
|
+
for (const call of providerCalls) {
|
|
840
|
+
for (const slug of allSlugs) {
|
|
841
|
+
if (call.systemPrompt?.includes(slug)) {
|
|
842
|
+
appearances.set(slug, (appearances.get(slug) ?? 0) + 1);
|
|
843
|
+
}
|
|
844
|
+
}
|
|
845
|
+
}
|
|
846
|
+
for (const slug of allSlugs) {
|
|
847
|
+
expect(appearances.get(slug)).toBe(1);
|
|
848
|
+
}
|
|
849
|
+
});
|
|
850
|
+
|
|
851
|
+
test("tier1_size >= total pages → single tier 1 batch, no rest", async () => {
|
|
852
|
+
providerStub = makeProvider(toolUseResponse([1]));
|
|
853
|
+
await runRouter({
|
|
854
|
+
workspaceDir,
|
|
855
|
+
...COMMON_PARAMS,
|
|
856
|
+
config: makeConfig({ tier1Size: 100 }),
|
|
857
|
+
});
|
|
858
|
+
// 5 pages, tier1_size=100 → only tier 1 fires; the empty rest is dropped.
|
|
859
|
+
expect(providerCalls).toHaveLength(1);
|
|
860
|
+
});
|
|
861
|
+
});
|
|
862
|
+
|
|
863
|
+
// ---------------------------------------------------------------------------
|
|
864
|
+
// Tier 2 (highest-EMA) splitting.
|
|
865
|
+
// ---------------------------------------------------------------------------
|
|
866
|
+
|
|
867
|
+
describe("runRouter — tier 2 (highest EMA)", () => {
|
|
868
|
+
// Any non-null value passes the `params.database` check in the orchestrator;
|
|
869
|
+
// the real db is never touched because computeInjectionScores is mocked.
|
|
870
|
+
const stubDb = {} as Parameters<typeof runRouter>[0]["database"];
|
|
871
|
+
|
|
872
|
+
beforeEach(async () => {
|
|
873
|
+
await writePage(workspaceDir, makePage("alpha", { summary: "A" }));
|
|
874
|
+
await writePage(workspaceDir, makePage("bravo", { summary: "B" }));
|
|
875
|
+
await writePage(workspaceDir, makePage("charlie", { summary: "C" }));
|
|
876
|
+
await writePage(workspaceDir, makePage("delta", { summary: "D" }));
|
|
877
|
+
await writePage(workspaceDir, makePage("echo", { summary: "E" }));
|
|
878
|
+
});
|
|
879
|
+
|
|
880
|
+
test("tier2_size + tier1_size both null is the v3 single-batch path", async () => {
|
|
881
|
+
providerStub = makeProvider(toolUseResponse([1]));
|
|
882
|
+
await runRouter({
|
|
883
|
+
workspaceDir,
|
|
884
|
+
...COMMON_PARAMS,
|
|
885
|
+
config: makeConfig(),
|
|
886
|
+
database: stubDb,
|
|
887
|
+
});
|
|
888
|
+
expect(providerCalls).toHaveLength(1);
|
|
889
|
+
});
|
|
890
|
+
|
|
891
|
+
test("tier2_size=2 produces 2 batches (tier 2 + rest)", async () => {
|
|
892
|
+
scoresStub.set("alpha", 1.0);
|
|
893
|
+
scoresStub.set("bravo", 5.0);
|
|
894
|
+
scoresStub.set("delta", 4.0);
|
|
895
|
+
|
|
896
|
+
providerStub = makeProvider(toolUseResponse([1]));
|
|
897
|
+
await runRouter({
|
|
898
|
+
workspaceDir,
|
|
899
|
+
...COMMON_PARAMS,
|
|
900
|
+
config: makeConfig({ tier2Size: 2 }),
|
|
901
|
+
database: stubDb,
|
|
902
|
+
});
|
|
903
|
+
expect(providerCalls.length).toBe(2);
|
|
904
|
+
|
|
905
|
+
// Tier 2 is the first batch (no tier 1 in this test). Its prompt should
|
|
906
|
+
// contain exactly the top-2 by score: bravo (5) and delta (4).
|
|
907
|
+
const tier2Prompt = providerCalls[0].systemPrompt ?? "";
|
|
908
|
+
const indexedSlugs = new Set(
|
|
909
|
+
[...tier2Prompt.matchAll(/^\[\d+\] (\S+)/gm)].map((m) => m[1]),
|
|
910
|
+
);
|
|
911
|
+
expect(indexedSlugs).toEqual(new Set(["bravo", "delta"]));
|
|
912
|
+
});
|
|
913
|
+
|
|
914
|
+
test("tier 1 then tier 2 then rest — three batches in that order", async () => {
|
|
915
|
+
// Pin mtimes so tier 1 is deterministic: alpha + bravo are the two
|
|
916
|
+
// most recent (highest mtime values). Tier 2 runs on the rest
|
|
917
|
+
// (charlie, delta, echo) using scores we control.
|
|
918
|
+
const { utimes } = await import("node:fs/promises");
|
|
919
|
+
const stamp = async (slug: string, s: number) =>
|
|
920
|
+
utimes(join(workspaceDir, "memory", "concepts", `${slug}.md`), s, s);
|
|
921
|
+
await stamp("alpha", 9000);
|
|
922
|
+
await stamp("bravo", 8000);
|
|
923
|
+
await stamp("charlie", 3000);
|
|
924
|
+
await stamp("delta", 2000);
|
|
925
|
+
await stamp("echo", 1000);
|
|
926
|
+
invalidatePageIndex();
|
|
927
|
+
|
|
928
|
+
scoresStub.set("charlie", 2.0);
|
|
929
|
+
scoresStub.set("delta", 3.0);
|
|
930
|
+
// echo has no score → ineligible for tier 2.
|
|
931
|
+
|
|
932
|
+
providerStub = makeProvider(toolUseResponse([1]));
|
|
933
|
+
await runRouter({
|
|
934
|
+
workspaceDir,
|
|
935
|
+
...COMMON_PARAMS,
|
|
936
|
+
config: makeConfig({ tier1Size: 2, tier2Size: 2 }),
|
|
937
|
+
database: stubDb,
|
|
938
|
+
});
|
|
939
|
+
expect(providerCalls.length).toBe(3);
|
|
940
|
+
|
|
941
|
+
// Tier 2 batch (index 1) contains charlie + delta. Echo went to rest.
|
|
942
|
+
const tier2Prompt = providerCalls[1].systemPrompt ?? "";
|
|
943
|
+
const tier2Slugs = new Set(
|
|
944
|
+
[...tier2Prompt.matchAll(/^\[\d+\] (\S+)/gm)].map((m) => m[1]),
|
|
945
|
+
);
|
|
946
|
+
expect(tier2Slugs).toEqual(new Set(["charlie", "delta"]));
|
|
947
|
+
|
|
948
|
+
// Echo (no score) must land in the rest batch, not tier 2.
|
|
949
|
+
const restPrompt = providerCalls[2].systemPrompt ?? "";
|
|
950
|
+
const restSlugs = new Set(
|
|
951
|
+
[...restPrompt.matchAll(/^\[\d+\] (\S+)/gm)].map((m) => m[1]),
|
|
952
|
+
);
|
|
953
|
+
expect(restSlugs).toEqual(new Set(["echo"]));
|
|
954
|
+
});
|
|
955
|
+
|
|
956
|
+
test("score=0 pages stay in rest even when tier2_size is large", async () => {
|
|
957
|
+
// Only one page has a positive score; tier2_size=100 should NOT pull in
|
|
958
|
+
// zero-score pages.
|
|
959
|
+
scoresStub.set("bravo", 5.0);
|
|
960
|
+
|
|
961
|
+
providerStub = makeProvider(toolUseResponse([1]));
|
|
962
|
+
await runRouter({
|
|
963
|
+
workspaceDir,
|
|
964
|
+
...COMMON_PARAMS,
|
|
965
|
+
config: makeConfig({ tier2Size: 100 }),
|
|
966
|
+
database: stubDb,
|
|
967
|
+
});
|
|
968
|
+
expect(providerCalls.length).toBe(2);
|
|
969
|
+
const tier2Prompt = providerCalls[0].systemPrompt ?? "";
|
|
970
|
+
expect(tier2Prompt).toContain("[1] bravo");
|
|
971
|
+
expect(tier2Prompt).not.toMatch(/^\[\d+\] alpha/m);
|
|
972
|
+
});
|
|
973
|
+
|
|
974
|
+
test("sourceBySlug tags each selection with its batch tier", async () => {
|
|
975
|
+
scoresStub.set("bravo", 5.0);
|
|
976
|
+
scoresStub.set("delta", 3.0);
|
|
977
|
+
|
|
978
|
+
providerStub = makeProvider(toolUseResponse([1]));
|
|
979
|
+
const result = await runRouter({
|
|
980
|
+
workspaceDir,
|
|
981
|
+
...COMMON_PARAMS,
|
|
982
|
+
config: makeConfig({ tier1Size: 1, tier2Size: 1 }),
|
|
983
|
+
database: stubDb,
|
|
984
|
+
});
|
|
985
|
+
|
|
986
|
+
// Every selected slug should have a tier tag — exactly one of:
|
|
987
|
+
// "tier1", "tier2", or "tier3:N" (N starts at 0).
|
|
988
|
+
for (const slug of result.selectedSlugs) {
|
|
989
|
+
const source = result.sourceBySlug.get(slug);
|
|
990
|
+
expect(source).toBeDefined();
|
|
991
|
+
expect(
|
|
992
|
+
source === "tier1" ||
|
|
993
|
+
source === "tier2" ||
|
|
994
|
+
source!.startsWith("tier3:"),
|
|
995
|
+
).toBe(true);
|
|
996
|
+
}
|
|
997
|
+
// Tier 1 + tier 2 + (some tier 3 batches) ≥ 3 batches → at least one
|
|
998
|
+
// slug per tier should be present in the source map across the union.
|
|
999
|
+
const tags = new Set(result.sourceBySlug.values());
|
|
1000
|
+
expect(tags.has("tier1")).toBe(true);
|
|
1001
|
+
expect(tags.has("tier2")).toBe(true);
|
|
1002
|
+
});
|
|
1003
|
+
|
|
1004
|
+
test("tier2_size set without database logs a warn and skips tier 2", async () => {
|
|
1005
|
+
scoresStub.set("bravo", 5.0);
|
|
1006
|
+
providerStub = makeProvider(toolUseResponse([1]));
|
|
1007
|
+
await runRouter({
|
|
1008
|
+
workspaceDir,
|
|
1009
|
+
...COMMON_PARAMS,
|
|
1010
|
+
config: makeConfig({ tier2Size: 2 }),
|
|
1011
|
+
// No database → tier 2 silently skipped.
|
|
1012
|
+
});
|
|
1013
|
+
expect(providerCalls).toHaveLength(1);
|
|
1014
|
+
const warned = warnLogs.some((l) =>
|
|
1015
|
+
JSON.stringify(l.args).includes("tier2_size set but no database"),
|
|
1016
|
+
);
|
|
1017
|
+
expect(warned).toBe(true);
|
|
1018
|
+
});
|
|
1019
|
+
});
|
|
@@ -32,11 +32,15 @@ mock.module("../../../util/logger.js", () => ({
|
|
|
32
32
|
}));
|
|
33
33
|
|
|
34
34
|
let configMemoryV2Enabled = true;
|
|
35
|
+
let configMemoryEnabled = true;
|
|
35
36
|
|
|
36
37
|
mock.module("../../../config/loader.js", () => ({
|
|
37
38
|
getConfig: () => ({}),
|
|
38
39
|
loadConfig: () => ({
|
|
39
|
-
memory: {
|
|
40
|
+
memory: {
|
|
41
|
+
enabled: configMemoryEnabled,
|
|
42
|
+
v2: { enabled: configMemoryV2Enabled },
|
|
43
|
+
},
|
|
40
44
|
}),
|
|
41
45
|
loadRawConfig: () => ({}),
|
|
42
46
|
saveRawConfig: () => {},
|
|
@@ -71,6 +75,7 @@ describe("readMemoryV2StaticContent", () => {
|
|
|
71
75
|
beforeEach(() => {
|
|
72
76
|
mkdirSync(TEST_DIR, { recursive: true });
|
|
73
77
|
configMemoryV2Enabled = true;
|
|
78
|
+
configMemoryEnabled = true;
|
|
74
79
|
});
|
|
75
80
|
|
|
76
81
|
afterEach(() => {
|
|
@@ -83,6 +88,12 @@ describe("readMemoryV2StaticContent", () => {
|
|
|
83
88
|
expect(readMemoryV2StaticContent()).toBeNull();
|
|
84
89
|
});
|
|
85
90
|
|
|
91
|
+
test("returns null when config.memory.enabled is off even with v2 on", () => {
|
|
92
|
+
configMemoryEnabled = false;
|
|
93
|
+
for (const file of MEMORY_FILES) writeMemoryFile(file, `Content ${file}`);
|
|
94
|
+
expect(readMemoryV2StaticContent()).toBeNull();
|
|
95
|
+
});
|
|
96
|
+
|
|
86
97
|
test("returns headed sections in canonical order when all files have content", () => {
|
|
87
98
|
writeMemoryFile("essentials.md", "Alice prefers dark mode.");
|
|
88
99
|
writeMemoryFile("threads.md", "Open thread: ship PR-123 review.");
|
|
@@ -11,11 +11,7 @@ import { eq } from "drizzle-orm";
|
|
|
11
11
|
|
|
12
12
|
import type { DrizzleDb } from "../db-connection.js";
|
|
13
13
|
import { activationState } from "../schema.js";
|
|
14
|
-
import {
|
|
15
|
-
type ActivationState,
|
|
16
|
-
ActivationStateSchema,
|
|
17
|
-
type EverInjectedEntry,
|
|
18
|
-
} from "./types.js";
|
|
14
|
+
import { type ActivationState, ActivationStateSchema } from "./types.js";
|
|
19
15
|
|
|
20
16
|
/**
|
|
21
17
|
* Load the activation state for a conversation, or `null` if no row exists.
|
|
@@ -123,16 +119,18 @@ export function forkActivationState(
|
|
|
123
119
|
}
|
|
124
120
|
|
|
125
121
|
/**
|
|
126
|
-
*
|
|
127
|
-
*
|
|
128
|
-
*
|
|
122
|
+
* Clear all `everInjected` entries. Used after compaction: the cached
|
|
123
|
+
* `<memory>` attachments those slugs lived on are gone, so future turns
|
|
124
|
+
* should be free to re-inject them.
|
|
125
|
+
*
|
|
126
|
+
* Unconditionally empties the list rather than filtering by turn number.
|
|
127
|
+
* `everInjected` is persisted on every turn while the in-memory tracker's
|
|
128
|
+
* `currentTurn` is only snapshotted on graceful conversation dispose, so a
|
|
129
|
+
* non-graceful shutdown (SIGKILL, crash) followed by a reload can leave
|
|
130
|
+
* `everInjected` entries with `turn` values above the restored tracker's
|
|
131
|
+
* `currentTurn`. A turn-bounded filter misses those stale entries and they
|
|
132
|
+
* dedupe forever; a full clear is robust to that drift.
|
|
129
133
|
*/
|
|
130
|
-
export function
|
|
131
|
-
state:
|
|
132
|
-
upToTurn: number,
|
|
133
|
-
): ActivationState {
|
|
134
|
-
const everInjected: EverInjectedEntry[] = state.everInjected.filter(
|
|
135
|
-
(entry) => entry.turn > upToTurn,
|
|
136
|
-
);
|
|
137
|
-
return { ...state, everInjected };
|
|
134
|
+
export function clearEverInjected(state: ActivationState): ActivationState {
|
|
135
|
+
return { ...state, everInjected: [] };
|
|
138
136
|
}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Render the prose-style capability statement embedded into the unified
|
|
3
|
+
* `memory_v2_concept_pages` Qdrant collection (under the `cli-commands/<name>`
|
|
4
|
+
* slug prefix). Mirrors `buildSkillContent` in shape — a short prose lead-in
|
|
5
|
+
* followed by the dense capability material — so activation scoring weighs
|
|
6
|
+
* both natural-language intent and structured help text.
|
|
7
|
+
*
|
|
8
|
+
* Intentionally uncapped: CLI `--help` output averages 1–2 KB and the longest
|
|
9
|
+
* (browser, oauth) hits ~3.4 KB. The embedding backend handles inputs of this
|
|
10
|
+
* size without trouble, and trimming would drop the very examples and flag
|
|
11
|
+
* descriptions that make commands semantically findable.
|
|
12
|
+
*/
|
|
13
|
+
export function buildCliCommandContent(
|
|
14
|
+
name: string,
|
|
15
|
+
description: string,
|
|
16
|
+
helpText: string,
|
|
17
|
+
): string {
|
|
18
|
+
return `The "assistant ${name}" CLI command is available. ${description}.\n\nFull help:\n${helpText}`;
|
|
19
|
+
}
|