@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
package/src/memory/v2/router.ts
CHANGED
|
@@ -47,7 +47,15 @@ import type {
|
|
|
47
47
|
ToolDefinition,
|
|
48
48
|
} from "../../providers/types.js";
|
|
49
49
|
import { getLogger } from "../../util/logger.js";
|
|
50
|
-
import {
|
|
50
|
+
import type { DrizzleDb } from "../db-connection.js";
|
|
51
|
+
import { computeInjectionScores } from "./injection-events.js";
|
|
52
|
+
import type { PageIndex } from "./page-index.js";
|
|
53
|
+
import {
|
|
54
|
+
getPageIndex,
|
|
55
|
+
partitionPageIndex,
|
|
56
|
+
splitTier1,
|
|
57
|
+
splitTier2,
|
|
58
|
+
} from "./page-index.js";
|
|
51
59
|
import { resolveRouterPrompt } from "./prompts/router.js";
|
|
52
60
|
import type { EverInjectedEntry } from "./types.js";
|
|
53
61
|
|
|
@@ -66,14 +74,28 @@ export type RouterFailureReason =
|
|
|
66
74
|
| "api_error"
|
|
67
75
|
| "empty_index";
|
|
68
76
|
|
|
77
|
+
/**
|
|
78
|
+
* Tags which batch a router-selected slug came from. Tier 3 carries the
|
|
79
|
+
* batch index so the inspector can distinguish e.g. `tier3:0` from
|
|
80
|
+
* `tier3:3` — useful for debugging hash bucketing and batch-quality
|
|
81
|
+
* regressions per tier 3 bucket.
|
|
82
|
+
*/
|
|
83
|
+
export type RouterSource = "tier1" | "tier2" | `tier3:${number}`;
|
|
84
|
+
|
|
69
85
|
/**
|
|
70
86
|
* Result of a single router call. `selectedSlugs` preserves the order the
|
|
71
87
|
* model returned and is already capped at `config.memory.v2.router.max_page_ids`
|
|
72
|
-
* with out-of-range IDs dropped.
|
|
88
|
+
* with out-of-range IDs dropped. `sourceBySlug` attributes each selection
|
|
89
|
+
* to the batch it came from for inspector display.
|
|
73
90
|
*/
|
|
74
91
|
export interface RouterResult {
|
|
75
92
|
/** Selected page slugs in the order the model returned them. */
|
|
76
93
|
selectedSlugs: string[];
|
|
94
|
+
/**
|
|
95
|
+
* Per-slug provenance covering every entry in `selectedSlugs`. Empty when
|
|
96
|
+
* `failureReason !== null` or no batch returned any selections.
|
|
97
|
+
*/
|
|
98
|
+
sourceBySlug: ReadonlyMap<string, RouterSource>;
|
|
77
99
|
/** `null` on success; one of the failure reasons above otherwise. */
|
|
78
100
|
failureReason: RouterFailureReason | null;
|
|
79
101
|
}
|
|
@@ -112,8 +134,24 @@ const RouterResultSchema = z.object({
|
|
|
112
134
|
page_ids: z.array(z.number().int()),
|
|
113
135
|
});
|
|
114
136
|
|
|
115
|
-
/**
|
|
137
|
+
/**
|
|
138
|
+
* Per-batch internal result. The orchestrator stamps provenance during the
|
|
139
|
+
* union so individual batches never need to know their own tier tag.
|
|
140
|
+
*/
|
|
141
|
+
interface RouterBatchResult {
|
|
142
|
+
selectedSlugs: string[];
|
|
143
|
+
failureReason: RouterFailureReason | null;
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
/** Empty orchestrator result. */
|
|
116
147
|
function emptyResult(reason: RouterFailureReason | null): RouterResult {
|
|
148
|
+
return { selectedSlugs: [], sourceBySlug: new Map(), failureReason: reason };
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
/** Empty batch result — slimmer shape; orchestrator builds provenance. */
|
|
152
|
+
function emptyBatchResult(
|
|
153
|
+
reason: RouterFailureReason | null,
|
|
154
|
+
): RouterBatchResult {
|
|
117
155
|
return { selectedSlugs: [], failureReason: reason };
|
|
118
156
|
}
|
|
119
157
|
|
|
@@ -127,46 +165,42 @@ interface RunRouterParams {
|
|
|
127
165
|
priorEverInjected: readonly EverInjectedEntry[];
|
|
128
166
|
config: AssistantConfig;
|
|
129
167
|
signal?: AbortSignal;
|
|
168
|
+
/**
|
|
169
|
+
* Database handle for reading EMA scores when `tier2_size` is set. When
|
|
170
|
+
* absent, tier 2 is silently skipped (pages flow tier 1 → tier 3). The
|
|
171
|
+
* production caller (`injectViaRouter`) always passes it; tests that
|
|
172
|
+
* only exercise tier 1 / tier 3 paths can omit it.
|
|
173
|
+
*/
|
|
174
|
+
database?: DrizzleDb;
|
|
130
175
|
}
|
|
131
176
|
|
|
132
177
|
/**
|
|
133
|
-
* Run the router for one turn.
|
|
134
|
-
*
|
|
178
|
+
* Run the router for one turn.
|
|
179
|
+
*
|
|
180
|
+
* Top-level orchestration. When `config.memory.v2.router.batch_size` is
|
|
181
|
+
* `null` (default), the entire page index is sent in one call — bit-
|
|
182
|
+
* identical to the pre-batching code path so v3's KV cache is preserved.
|
|
183
|
+
* When set, `partitionPageIndex` splits the index into stable hash-bucketed
|
|
184
|
+
* batches and we fire one provider call per batch in parallel; the selected
|
|
185
|
+
* slugs are unioned across batches.
|
|
135
186
|
*
|
|
136
|
-
*
|
|
137
|
-
*
|
|
138
|
-
*
|
|
139
|
-
*
|
|
140
|
-
*
|
|
141
|
-
* 3. Build system + user prompts. The system prompt is the rendered
|
|
142
|
-
* router template with the page index inlined and gets one ephemeral
|
|
143
|
-
* breakpoint at the end (the page-index block). The user message is
|
|
144
|
-
* *two* text blocks: the cached `<now>` block and the uncached
|
|
145
|
-
* already-injected/last-turn block.
|
|
146
|
-
* 4. Force `tool_choice` so the model can only emit `select_pages_to_inject`.
|
|
147
|
-
* 5. Parse the tool input via Zod. Anything off-shape collapses to
|
|
148
|
-
* `schema_mismatch`.
|
|
149
|
-
* 6. Map IDs to slugs through the page index, dropping IDs outside
|
|
150
|
-
* `[1, N]` and truncating at `max_page_ids`.
|
|
187
|
+
* Per-batch failure does not abort the turn — as long as at least one batch
|
|
188
|
+
* returns a usable selection, the union is returned with `failureReason:
|
|
189
|
+
* null`. Only when EVERY batch fails do we surface a failure; in that case
|
|
190
|
+
* the first batch's reason is returned for parity with the single-batch
|
|
191
|
+
* v3 behavior.
|
|
151
192
|
*
|
|
152
|
-
*
|
|
153
|
-
*
|
|
154
|
-
*
|
|
155
|
-
*
|
|
156
|
-
*
|
|
193
|
+
* Single batch error semantics, preserved from v3:
|
|
194
|
+
* - `empty_index` — workspace has no concept pages or skill entries.
|
|
195
|
+
* - `no_provider` — `getConfiguredProvider("memoryRouter")` returned null.
|
|
196
|
+
* - `api_error` — any uncaught throw during the provider call (incl. abort).
|
|
197
|
+
* - `tool_use_missing` — the model returned no `select_pages_to_inject` tool_use.
|
|
198
|
+
* - `schema_mismatch` — tool input failed Zod validation.
|
|
157
199
|
*/
|
|
158
200
|
export async function runRouter(
|
|
159
201
|
params: RunRouterParams,
|
|
160
202
|
): Promise<RouterResult> {
|
|
161
|
-
const {
|
|
162
|
-
workspaceDir,
|
|
163
|
-
userMessage,
|
|
164
|
-
assistantMessage,
|
|
165
|
-
nowText,
|
|
166
|
-
priorEverInjected,
|
|
167
|
-
config,
|
|
168
|
-
signal,
|
|
169
|
-
} = params;
|
|
203
|
+
const { workspaceDir, priorEverInjected, config } = params;
|
|
170
204
|
|
|
171
205
|
const pageIndex = await getPageIndex(workspaceDir);
|
|
172
206
|
if (pageIndex.entries.length === 0) {
|
|
@@ -179,31 +213,153 @@ export async function runRouter(
|
|
|
179
213
|
return emptyResult("no_provider");
|
|
180
214
|
}
|
|
181
215
|
|
|
216
|
+
const batchSize = config.memory?.v2?.router?.batch_size ?? null;
|
|
217
|
+
const tier1Size = config.memory?.v2?.router?.tier1_size ?? null;
|
|
218
|
+
const tier2Size = config.memory?.v2?.router?.tier2_size ?? null;
|
|
219
|
+
|
|
220
|
+
// Carve in tier order so each later tier sees only what's left. With
|
|
221
|
+
// every tier disabled (defaults) we hit the bit-identical single-batch
|
|
222
|
+
// path that preserves v3's KV cache.
|
|
223
|
+
const { tier1, rest: afterTier1 } = splitTier1(pageIndex, tier1Size);
|
|
224
|
+
|
|
225
|
+
let tier2: PageIndex | null = null;
|
|
226
|
+
let afterTier2: PageIndex = afterTier1;
|
|
227
|
+
if (tier2Size !== null && params.database && afterTier1.entries.length > 0) {
|
|
228
|
+
const slugs = afterTier1.entries.map((e) => e.slug);
|
|
229
|
+
const scores = computeInjectionScores(params.database, slugs, Date.now());
|
|
230
|
+
const split = splitTier2(afterTier1, tier2Size, scores);
|
|
231
|
+
tier2 = split.tier2;
|
|
232
|
+
afterTier2 = split.rest;
|
|
233
|
+
} else if (tier2Size !== null && !params.database) {
|
|
234
|
+
log.warn(
|
|
235
|
+
"tier2_size set but no database passed to runRouter; skipping tier 2",
|
|
236
|
+
);
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
const tier3Batches = partitionPageIndex(afterTier2, batchSize).filter(
|
|
240
|
+
(b) => b.entries.length > 0,
|
|
241
|
+
);
|
|
242
|
+
|
|
243
|
+
// Tag each batch with its provenance string. Tier 3 batches carry their
|
|
244
|
+
// bucket index so the inspector can attribute selections per-bucket.
|
|
245
|
+
const taggedBatches: Array<{ source: RouterSource; index: PageIndex }> = [];
|
|
246
|
+
if (tier1) taggedBatches.push({ source: "tier1", index: tier1 });
|
|
247
|
+
if (tier2) taggedBatches.push({ source: "tier2", index: tier2 });
|
|
248
|
+
tier3Batches.forEach((index, i) => {
|
|
249
|
+
taggedBatches.push({ source: `tier3:${i}` as const, index });
|
|
250
|
+
});
|
|
251
|
+
if (taggedBatches.length === 0) {
|
|
252
|
+
return emptyResult("empty_index");
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
const batchResults = await Promise.all(
|
|
256
|
+
taggedBatches.map(({ index }) =>
|
|
257
|
+
runRouterBatch({
|
|
258
|
+
...params,
|
|
259
|
+
batchIndex: index,
|
|
260
|
+
priorEverInjected,
|
|
261
|
+
provider,
|
|
262
|
+
}),
|
|
263
|
+
),
|
|
264
|
+
);
|
|
265
|
+
|
|
266
|
+
const successes = batchResults.filter((r) => r.failureReason === null);
|
|
267
|
+
if (successes.length === 0) {
|
|
268
|
+
// For the single-batch (K=null) path this preserves v3's behavior:
|
|
269
|
+
// one batch, one failure reason surfaces directly.
|
|
270
|
+
return emptyResult(batchResults[0].failureReason);
|
|
271
|
+
}
|
|
272
|
+
|
|
273
|
+
// Union selected slugs preserving first-seen order across batches; batch
|
|
274
|
+
// ordering is deterministic so the union and provenance map are stable.
|
|
275
|
+
// First-seen wins if a slug somehow appears in multiple batches (shouldn't
|
|
276
|
+
// happen — tier 1/2/3 partition is disjoint — but be defensive).
|
|
277
|
+
const sourceBySlug = new Map<string, RouterSource>();
|
|
278
|
+
const selectedSlugs: string[] = [];
|
|
279
|
+
for (let i = 0; i < batchResults.length; i++) {
|
|
280
|
+
const result = batchResults[i];
|
|
281
|
+
const source = taggedBatches[i].source;
|
|
282
|
+
for (const slug of result.selectedSlugs) {
|
|
283
|
+
if (sourceBySlug.has(slug)) continue;
|
|
284
|
+
sourceBySlug.set(slug, source);
|
|
285
|
+
selectedSlugs.push(slug);
|
|
286
|
+
}
|
|
287
|
+
}
|
|
288
|
+
if (successes.length < batchResults.length) {
|
|
289
|
+
log.warn(
|
|
290
|
+
{
|
|
291
|
+
totalBatches: batchResults.length,
|
|
292
|
+
failedBatches: batchResults.length - successes.length,
|
|
293
|
+
failureReasons: batchResults
|
|
294
|
+
.filter((r) => r.failureReason !== null)
|
|
295
|
+
.map((r) => r.failureReason),
|
|
296
|
+
},
|
|
297
|
+
"Some router batches failed; returning union of successful batches",
|
|
298
|
+
);
|
|
299
|
+
}
|
|
300
|
+
|
|
301
|
+
// Each per-batch call caps at max_page_ids, but the union across batches can
|
|
302
|
+
// exceed it (e.g. 10 batches × 10 selections each ≫ 25 cap). Apply a final
|
|
303
|
+
// truncation so RouterResult honors the contract that injection.ts trusts.
|
|
304
|
+
// Iteration order above is tier 1 → tier 2 → tier 3:0 → … so earlier-tier
|
|
305
|
+
// slugs win the truncation.
|
|
306
|
+
const maxPageIds = config.memory?.v2?.router?.max_page_ids ?? 25;
|
|
307
|
+
if (selectedSlugs.length > maxPageIds) {
|
|
308
|
+
log.warn(
|
|
309
|
+
{ unionSize: selectedSlugs.length, max: maxPageIds },
|
|
310
|
+
"Router union across batches exceeded max_page_ids; truncating",
|
|
311
|
+
);
|
|
312
|
+
const dropped = selectedSlugs.splice(maxPageIds);
|
|
313
|
+
for (const slug of dropped) sourceBySlug.delete(slug);
|
|
314
|
+
}
|
|
315
|
+
return { selectedSlugs, sourceBySlug, failureReason: null };
|
|
316
|
+
}
|
|
317
|
+
|
|
318
|
+
interface RunRouterBatchParams extends RunRouterParams {
|
|
319
|
+
batchIndex: PageIndex;
|
|
320
|
+
provider: NonNullable<Awaited<ReturnType<typeof getConfiguredProvider>>>;
|
|
321
|
+
}
|
|
322
|
+
|
|
323
|
+
/**
|
|
324
|
+
* Route one batch of the page index. Uses batch-local IDs everywhere
|
|
325
|
+
* (including `<already_injected_ids>`, which is filtered to slugs present
|
|
326
|
+
* in this batch). Provider is passed in by the orchestrator so we don't
|
|
327
|
+
* re-resolve it N times for an N-batch turn.
|
|
328
|
+
*/
|
|
329
|
+
async function runRouterBatch(
|
|
330
|
+
params: RunRouterBatchParams,
|
|
331
|
+
): Promise<RouterBatchResult> {
|
|
332
|
+
const {
|
|
333
|
+
workspaceDir,
|
|
334
|
+
userMessage,
|
|
335
|
+
assistantMessage,
|
|
336
|
+
nowText,
|
|
337
|
+
priorEverInjected,
|
|
338
|
+
config,
|
|
339
|
+
signal,
|
|
340
|
+
batchIndex,
|
|
341
|
+
provider,
|
|
342
|
+
} = params;
|
|
343
|
+
|
|
182
344
|
const systemPrompt = resolveRouterPrompt(
|
|
183
345
|
config.memory?.v2?.router?.router_prompt_path ?? null,
|
|
184
346
|
workspaceDir,
|
|
185
347
|
{
|
|
186
348
|
assistantName: getAssistantName(),
|
|
187
349
|
userName: resolveUserName(workspaceDir),
|
|
188
|
-
pageIndexBlock:
|
|
350
|
+
pageIndexBlock: batchIndex.rendered,
|
|
189
351
|
},
|
|
190
352
|
);
|
|
191
353
|
|
|
192
|
-
//
|
|
193
|
-
//
|
|
194
|
-
//
|
|
354
|
+
// Filter prior-injected to slugs present in THIS batch and map to
|
|
355
|
+
// batch-local IDs. The model in batch B can't reference global IDs that
|
|
356
|
+
// aren't in its prompt, so listing them would just be noise.
|
|
195
357
|
const priorIds: number[] = [];
|
|
196
358
|
for (const entry of priorEverInjected) {
|
|
197
|
-
const
|
|
198
|
-
if (
|
|
359
|
+
const local = batchIndex.bySlug.get(entry.slug);
|
|
360
|
+
if (local) priorIds.push(local.id);
|
|
199
361
|
}
|
|
200
362
|
|
|
201
|
-
// Cache breakpoint 2 — `<now>` is stable across most turns (NOW.md only
|
|
202
|
-
// changes when the model rewrites it), so the bulk of the user message
|
|
203
|
-
// rides the cache. We use a 1h TTL to match the system-prompt breakpoint
|
|
204
|
-
// and the provider's auto-applied breakpoints. The trailing block has no
|
|
205
|
-
// `cache_control`; the Anthropic provider auto-applies a 1h breakpoint on
|
|
206
|
-
// the last text block of a turn-starting user message, which covers it.
|
|
207
363
|
const userMsg: Message = {
|
|
208
364
|
role: "user",
|
|
209
365
|
content: [
|
|
@@ -236,7 +392,7 @@ export async function runRouter(
|
|
|
236
392
|
);
|
|
237
393
|
} catch (err) {
|
|
238
394
|
log.warn({ err }, "Router provider call threw; treating as api_error");
|
|
239
|
-
return
|
|
395
|
+
return emptyBatchResult("api_error");
|
|
240
396
|
}
|
|
241
397
|
|
|
242
398
|
const toolBlock = extractToolUse(response);
|
|
@@ -245,7 +401,7 @@ export async function runRouter(
|
|
|
245
401
|
{ stopReason: response.stopReason },
|
|
246
402
|
"Router model returned no select_pages_to_inject tool_use block",
|
|
247
403
|
);
|
|
248
|
-
return
|
|
404
|
+
return emptyBatchResult("tool_use_missing");
|
|
249
405
|
}
|
|
250
406
|
|
|
251
407
|
const parsed = RouterResultSchema.safeParse(toolBlock.input);
|
|
@@ -254,11 +410,10 @@ export async function runRouter(
|
|
|
254
410
|
{ error: parsed.error.message },
|
|
255
411
|
"Router tool input did not match schema",
|
|
256
412
|
);
|
|
257
|
-
return
|
|
413
|
+
return emptyBatchResult("schema_mismatch");
|
|
258
414
|
}
|
|
259
415
|
|
|
260
|
-
const N =
|
|
261
|
-
|
|
416
|
+
const N = batchIndex.entries.length;
|
|
262
417
|
const inRangeIds: number[] = [];
|
|
263
418
|
const droppedIds: number[] = [];
|
|
264
419
|
for (const id of parsed.data.page_ids) {
|
|
@@ -275,9 +430,8 @@ export async function runRouter(
|
|
|
275
430
|
);
|
|
276
431
|
}
|
|
277
432
|
|
|
278
|
-
// De-duplicate BEFORE applying the cap —
|
|
279
|
-
//
|
|
280
|
-
// dedupes to `[1]`, under-filling the cap.
|
|
433
|
+
// De-duplicate BEFORE applying the cap — `[1, 1, 2]` with max=2 must
|
|
434
|
+
// yield 2 distinct slugs, not collapse to 1 after slicing duplicates.
|
|
281
435
|
const dedupedIds = Array.from(new Set(inRangeIds));
|
|
282
436
|
|
|
283
437
|
const truncated = dedupedIds.length > maxPageIds;
|
|
@@ -291,7 +445,7 @@ export async function runRouter(
|
|
|
291
445
|
|
|
292
446
|
const selectedSlugs: string[] = [];
|
|
293
447
|
for (const id of finalIds) {
|
|
294
|
-
const entry =
|
|
448
|
+
const entry = batchIndex.byId.get(id);
|
|
295
449
|
if (!entry) continue;
|
|
296
450
|
selectedSlugs.push(entry.slug);
|
|
297
451
|
}
|
|
@@ -35,9 +35,9 @@ const MEMORY_V2_STATIC_BLOCKS: readonly MemoryV2StaticBlock[] = [
|
|
|
35
35
|
];
|
|
36
36
|
|
|
37
37
|
/**
|
|
38
|
-
* Build the v2 static memory block, gated on `config.memory.
|
|
39
|
-
* Empty/missing files are skipped; returns `null`
|
|
40
|
-
* every file is empty.
|
|
38
|
+
* Build the v2 static memory block, gated on `config.memory.enabled` and
|
|
39
|
+
* `config.memory.v2.enabled`. Empty/missing files are skipped; returns `null`
|
|
40
|
+
* when either gate is off or every file is empty.
|
|
41
41
|
*/
|
|
42
42
|
export function readMemoryV2StaticContent(): string | null {
|
|
43
43
|
let config;
|
|
@@ -46,7 +46,7 @@ export function readMemoryV2StaticContent(): string | null {
|
|
|
46
46
|
} catch {
|
|
47
47
|
return null;
|
|
48
48
|
}
|
|
49
|
-
if (!config.memory.v2.enabled) {
|
|
49
|
+
if (!config.memory.enabled || !config.memory.v2.enabled) {
|
|
50
50
|
return null;
|
|
51
51
|
}
|
|
52
52
|
|
package/src/memory/v2/types.ts
CHANGED
|
@@ -115,3 +115,26 @@ export interface SkillEntry {
|
|
|
115
115
|
id: string;
|
|
116
116
|
content: string;
|
|
117
117
|
}
|
|
118
|
+
|
|
119
|
+
// ---------------------------------------------------------------------------
|
|
120
|
+
// CLI-command entries (synthetic concept-collection rows, not on-disk pages)
|
|
121
|
+
// ---------------------------------------------------------------------------
|
|
122
|
+
|
|
123
|
+
/**
|
|
124
|
+
* Per-CLI-subcommand capability snapshot held in-process and embedded into the
|
|
125
|
+
* unified `memory_v2_concept_pages` Qdrant collection under the slug
|
|
126
|
+
* `cli-commands/<name>`. `content` is the full `helpInformation()` output for
|
|
127
|
+
* the top-level subcommand — the embedding target, intentionally uncapped so
|
|
128
|
+
* activation hints in flag descriptions and examples carry semantic weight.
|
|
129
|
+
* `description` is the one-line Commander description, rendered terse in
|
|
130
|
+
* `### CLI Commands You Can Use` so the injection block stays compact even
|
|
131
|
+
* for verbose `--help` outputs.
|
|
132
|
+
*
|
|
133
|
+
* Plain interface (no Zod) — same in-process-only justification as
|
|
134
|
+
* `SkillEntry`.
|
|
135
|
+
*/
|
|
136
|
+
export interface CliCommandEntry {
|
|
137
|
+
id: string;
|
|
138
|
+
description: string;
|
|
139
|
+
content: string;
|
|
140
|
+
}
|
|
@@ -0,0 +1,274 @@
|
|
|
1
|
+
import { afterEach, beforeEach, describe, expect, mock, test } from "bun:test";
|
|
2
|
+
|
|
3
|
+
import type { ChannelReplyPayload } from "@vellumai/gateway-client";
|
|
4
|
+
|
|
5
|
+
import type { A2ATask, Artifact } from "../../../../a2a/protocol-types.js";
|
|
6
|
+
|
|
7
|
+
// ---------------------------------------------------------------------------
|
|
8
|
+
// Mock state
|
|
9
|
+
// ---------------------------------------------------------------------------
|
|
10
|
+
|
|
11
|
+
let completedTask: A2ATask | null = null;
|
|
12
|
+
let completeWithArtifactsCalls: Array<{
|
|
13
|
+
taskId: string;
|
|
14
|
+
artifacts: Artifact[];
|
|
15
|
+
}> = [];
|
|
16
|
+
let pushUrlByTaskId: Record<string, string | null> = {};
|
|
17
|
+
let completeError: Error | null = null;
|
|
18
|
+
|
|
19
|
+
const fetchCalls: Array<{
|
|
20
|
+
url: string;
|
|
21
|
+
init: RequestInit;
|
|
22
|
+
}> = [];
|
|
23
|
+
let fetchResponses: Array<{ ok: boolean; status: number; body: string }> = [];
|
|
24
|
+
let fetchCallIndex = 0;
|
|
25
|
+
|
|
26
|
+
// ---------------------------------------------------------------------------
|
|
27
|
+
// Mocks
|
|
28
|
+
// ---------------------------------------------------------------------------
|
|
29
|
+
|
|
30
|
+
const defaultTask: A2ATask = {
|
|
31
|
+
id: "task-123",
|
|
32
|
+
status: { state: "completed", timestamp: new Date().toISOString() },
|
|
33
|
+
artifacts: [
|
|
34
|
+
{
|
|
35
|
+
artifact_id: "art-1",
|
|
36
|
+
parts: [{ kind: "text", text: "Hello from assistant" }],
|
|
37
|
+
},
|
|
38
|
+
],
|
|
39
|
+
};
|
|
40
|
+
|
|
41
|
+
mock.module("../../../../a2a/task-store.js", () => ({
|
|
42
|
+
completeWithArtifacts: (taskId: string, artifacts: Artifact[]): A2ATask => {
|
|
43
|
+
completeWithArtifactsCalls.push({ taskId, artifacts });
|
|
44
|
+
if (completeError) throw completeError;
|
|
45
|
+
return completedTask ?? defaultTask;
|
|
46
|
+
},
|
|
47
|
+
getPushUrl: (taskId: string): string | null => {
|
|
48
|
+
return pushUrlByTaskId[taskId] ?? null;
|
|
49
|
+
},
|
|
50
|
+
}));
|
|
51
|
+
|
|
52
|
+
mock.module("../../../../util/logger.js", () => ({
|
|
53
|
+
getLogger: () => ({
|
|
54
|
+
debug: () => {},
|
|
55
|
+
info: () => {},
|
|
56
|
+
warn: () => {},
|
|
57
|
+
error: () => {},
|
|
58
|
+
}),
|
|
59
|
+
}));
|
|
60
|
+
|
|
61
|
+
// Intercept global fetch for push notification testing
|
|
62
|
+
const originalFetch = globalThis.fetch;
|
|
63
|
+
|
|
64
|
+
// Import the module under test AFTER mocks are set up
|
|
65
|
+
const { deliverA2AReply } = await import("../deliver.js");
|
|
66
|
+
|
|
67
|
+
// ---------------------------------------------------------------------------
|
|
68
|
+
// Setup / teardown
|
|
69
|
+
// ---------------------------------------------------------------------------
|
|
70
|
+
|
|
71
|
+
beforeEach(() => {
|
|
72
|
+
completedTask = null;
|
|
73
|
+
completeWithArtifactsCalls = [];
|
|
74
|
+
pushUrlByTaskId = {};
|
|
75
|
+
completeError = null;
|
|
76
|
+
fetchCalls.length = 0;
|
|
77
|
+
fetchResponses = [];
|
|
78
|
+
fetchCallIndex = 0;
|
|
79
|
+
|
|
80
|
+
globalThis.fetch = (async (
|
|
81
|
+
input: string | URL | Request,
|
|
82
|
+
init?: RequestInit,
|
|
83
|
+
) => {
|
|
84
|
+
const url = typeof input === "string" ? input : input.toString();
|
|
85
|
+
fetchCalls.push({ url, init: init ?? {} });
|
|
86
|
+
const responseSpec = fetchResponses[fetchCallIndex++] ?? {
|
|
87
|
+
ok: true,
|
|
88
|
+
status: 200,
|
|
89
|
+
body: "{}",
|
|
90
|
+
};
|
|
91
|
+
return new Response(responseSpec.body, {
|
|
92
|
+
status: responseSpec.status,
|
|
93
|
+
statusText: responseSpec.ok ? "OK" : "Error",
|
|
94
|
+
});
|
|
95
|
+
}) as typeof fetch;
|
|
96
|
+
});
|
|
97
|
+
|
|
98
|
+
afterEach(() => {
|
|
99
|
+
globalThis.fetch = originalFetch;
|
|
100
|
+
});
|
|
101
|
+
|
|
102
|
+
// ---------------------------------------------------------------------------
|
|
103
|
+
// Tests
|
|
104
|
+
// ---------------------------------------------------------------------------
|
|
105
|
+
|
|
106
|
+
describe("deliverA2AReply", () => {
|
|
107
|
+
const baseCallbackUrl = "https://example.com/deliver/a2a?taskId=task-123";
|
|
108
|
+
|
|
109
|
+
test("completes task with text artifact", async () => {
|
|
110
|
+
const payload: ChannelReplyPayload = {
|
|
111
|
+
chatId: "chat-1",
|
|
112
|
+
text: "Hello from the assistant",
|
|
113
|
+
};
|
|
114
|
+
|
|
115
|
+
const result = await deliverA2AReply(baseCallbackUrl, payload);
|
|
116
|
+
|
|
117
|
+
expect(result.ok).toBe(true);
|
|
118
|
+
expect(completeWithArtifactsCalls).toHaveLength(1);
|
|
119
|
+
expect(completeWithArtifactsCalls[0].taskId).toBe("task-123");
|
|
120
|
+
expect(completeWithArtifactsCalls[0].artifacts).toHaveLength(1);
|
|
121
|
+
expect(completeWithArtifactsCalls[0].artifacts[0].parts).toEqual([
|
|
122
|
+
{ kind: "text", text: "Hello from the assistant" },
|
|
123
|
+
]);
|
|
124
|
+
});
|
|
125
|
+
|
|
126
|
+
test("completes task with file attachments", async () => {
|
|
127
|
+
const payload: ChannelReplyPayload = {
|
|
128
|
+
chatId: "chat-1",
|
|
129
|
+
text: "Here is a file",
|
|
130
|
+
attachments: [
|
|
131
|
+
{
|
|
132
|
+
id: "att-1",
|
|
133
|
+
filename: "report.pdf",
|
|
134
|
+
mimeType: "application/pdf",
|
|
135
|
+
sizeBytes: 1024,
|
|
136
|
+
kind: "file",
|
|
137
|
+
data: "data:application/pdf;base64,abc123",
|
|
138
|
+
},
|
|
139
|
+
],
|
|
140
|
+
};
|
|
141
|
+
|
|
142
|
+
const result = await deliverA2AReply(baseCallbackUrl, payload);
|
|
143
|
+
|
|
144
|
+
expect(result.ok).toBe(true);
|
|
145
|
+
expect(completeWithArtifactsCalls).toHaveLength(1);
|
|
146
|
+
const parts = completeWithArtifactsCalls[0].artifacts[0].parts;
|
|
147
|
+
expect(parts).toHaveLength(2);
|
|
148
|
+
expect(parts[0]).toEqual({
|
|
149
|
+
kind: "text",
|
|
150
|
+
text: "Here is a file",
|
|
151
|
+
});
|
|
152
|
+
expect(parts[1]).toEqual({
|
|
153
|
+
kind: "file",
|
|
154
|
+
filename: "report.pdf",
|
|
155
|
+
media_type: "application/pdf",
|
|
156
|
+
url: "data:application/pdf;base64,abc123",
|
|
157
|
+
});
|
|
158
|
+
});
|
|
159
|
+
|
|
160
|
+
test("returns ok: false when taskId is missing from URL", async () => {
|
|
161
|
+
const result = await deliverA2AReply("https://example.com/deliver/a2a", {
|
|
162
|
+
chatId: "chat-1",
|
|
163
|
+
text: "Hello",
|
|
164
|
+
});
|
|
165
|
+
|
|
166
|
+
expect(result.ok).toBe(false);
|
|
167
|
+
expect(completeWithArtifactsCalls).toHaveLength(0);
|
|
168
|
+
});
|
|
169
|
+
|
|
170
|
+
test("returns ok: true when payload has no content", async () => {
|
|
171
|
+
const result = await deliverA2AReply(baseCallbackUrl, {
|
|
172
|
+
chatId: "chat-1",
|
|
173
|
+
});
|
|
174
|
+
|
|
175
|
+
expect(result.ok).toBe(true);
|
|
176
|
+
expect(completeWithArtifactsCalls).toHaveLength(0);
|
|
177
|
+
});
|
|
178
|
+
|
|
179
|
+
test("returns ok: false when task completion throws", async () => {
|
|
180
|
+
completeError = new Error("A2A task not found: task-123");
|
|
181
|
+
|
|
182
|
+
const result = await deliverA2AReply(baseCallbackUrl, {
|
|
183
|
+
chatId: "chat-1",
|
|
184
|
+
text: "Hello",
|
|
185
|
+
});
|
|
186
|
+
|
|
187
|
+
expect(result.ok).toBe(false);
|
|
188
|
+
});
|
|
189
|
+
|
|
190
|
+
test("returns ok: false when task is already terminal", async () => {
|
|
191
|
+
completeError = new Error(
|
|
192
|
+
'Cannot transition task task-123 from terminal state "completed" to "completed"',
|
|
193
|
+
);
|
|
194
|
+
|
|
195
|
+
const result = await deliverA2AReply(baseCallbackUrl, {
|
|
196
|
+
chatId: "chat-1",
|
|
197
|
+
text: "Hello",
|
|
198
|
+
});
|
|
199
|
+
|
|
200
|
+
expect(result.ok).toBe(false);
|
|
201
|
+
});
|
|
202
|
+
|
|
203
|
+
describe("push notifications", () => {
|
|
204
|
+
test("POSTs completed task to push URL", async () => {
|
|
205
|
+
pushUrlByTaskId["task-123"] = "https://requester.example.com/push";
|
|
206
|
+
fetchResponses = [{ ok: true, status: 200, body: "{}" }];
|
|
207
|
+
|
|
208
|
+
const result = await deliverA2AReply(baseCallbackUrl, {
|
|
209
|
+
chatId: "chat-1",
|
|
210
|
+
text: "Done",
|
|
211
|
+
});
|
|
212
|
+
|
|
213
|
+
expect(result.ok).toBe(true);
|
|
214
|
+
|
|
215
|
+
// Wait for the fire-and-forget push to complete
|
|
216
|
+
await new Promise((r) => setTimeout(r, 50));
|
|
217
|
+
|
|
218
|
+
expect(fetchCalls).toHaveLength(1);
|
|
219
|
+
expect(fetchCalls[0].url).toBe("https://requester.example.com/push");
|
|
220
|
+
expect(fetchCalls[0].init.method).toBe("POST");
|
|
221
|
+
|
|
222
|
+
const headers = fetchCalls[0].init.headers as Record<string, string>;
|
|
223
|
+
expect(headers["Content-Type"]).toBe("application/a2a+json");
|
|
224
|
+
expect(headers["A2A-Version"]).toBe("1.0");
|
|
225
|
+
});
|
|
226
|
+
|
|
227
|
+
test("does not push when no push URL configured", async () => {
|
|
228
|
+
const result = await deliverA2AReply(baseCallbackUrl, {
|
|
229
|
+
chatId: "chat-1",
|
|
230
|
+
text: "Done",
|
|
231
|
+
});
|
|
232
|
+
|
|
233
|
+
expect(result.ok).toBe(true);
|
|
234
|
+
|
|
235
|
+
await new Promise((r) => setTimeout(r, 50));
|
|
236
|
+
|
|
237
|
+
expect(fetchCalls).toHaveLength(0);
|
|
238
|
+
});
|
|
239
|
+
|
|
240
|
+
test("push failure does not affect delivery result", async () => {
|
|
241
|
+
pushUrlByTaskId["task-123"] = "https://requester.example.com/push";
|
|
242
|
+
// All retries fail with 500
|
|
243
|
+
fetchResponses = Array(4).fill({
|
|
244
|
+
ok: false,
|
|
245
|
+
status: 500,
|
|
246
|
+
body: "Internal Server Error",
|
|
247
|
+
});
|
|
248
|
+
|
|
249
|
+
const result = await deliverA2AReply(baseCallbackUrl, {
|
|
250
|
+
chatId: "chat-1",
|
|
251
|
+
text: "Done",
|
|
252
|
+
});
|
|
253
|
+
|
|
254
|
+
// Delivery still succeeds even though push will fail
|
|
255
|
+
expect(result.ok).toBe(true);
|
|
256
|
+
});
|
|
257
|
+
|
|
258
|
+
test("stops retrying on non-retryable client error", async () => {
|
|
259
|
+
pushUrlByTaskId["task-123"] = "https://requester.example.com/push";
|
|
260
|
+
fetchResponses = [{ ok: false, status: 404, body: "Not Found" }];
|
|
261
|
+
|
|
262
|
+
await deliverA2AReply(baseCallbackUrl, {
|
|
263
|
+
chatId: "chat-1",
|
|
264
|
+
text: "Done",
|
|
265
|
+
});
|
|
266
|
+
|
|
267
|
+
// Wait for the fire-and-forget push to settle
|
|
268
|
+
await new Promise((r) => setTimeout(r, 50));
|
|
269
|
+
|
|
270
|
+
// Should only attempt once on a 4xx (non-429) error
|
|
271
|
+
expect(fetchCalls).toHaveLength(1);
|
|
272
|
+
});
|
|
273
|
+
});
|
|
274
|
+
});
|