@vellumai/assistant 0.9.0 → 0.10.0-staging.2
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 +18 -34
- package/bun.lock +7 -8
- package/docs/activation-funnel-telemetry.md +28 -22
- package/docs/architecture/security.md +29 -28
- package/docs/stt-provider-onboarding.md +3 -5
- package/docs/workflows-testing.md +13 -44
- package/docs/workflows.md +3 -5
- package/node_modules/@vellumai/ces-client/src/__tests__/ces-client.test.ts +47 -0
- package/node_modules/@vellumai/ces-client/src/rpc-client.ts +28 -5
- package/node_modules/@vellumai/environments/src/seeds.ts +2 -5
- package/node_modules/@vellumai/gateway-client/src/admission-policy-contract.ts +97 -0
- package/node_modules/@vellumai/gateway-client/src/inbound-contract.ts +10 -0
- package/node_modules/@vellumai/gateway-client/src/index.ts +32 -6
- package/node_modules/@vellumai/gateway-client/src/outbound-contract.ts +119 -0
- package/node_modules/@vellumai/gateway-client/src/types.ts +15 -84
- package/openapi.yaml +976 -63
- package/package.json +2 -1
- package/scripts/sync-llm-catalog.ts +6 -15
- package/scripts/sync-web-search-catalog.ts +3 -11
- package/src/__tests__/access-request-card-view.test.ts +98 -0
- package/src/__tests__/access-request-seed-content-blocks.test.ts +2 -4
- package/src/__tests__/actor-trust-resolver-address-fallback.test.ts +72 -32
- package/src/__tests__/agent-loop-compaction-strip.test.ts +241 -0
- package/src/__tests__/agent-loop-mutable-latest-user-message.test.ts +16 -13
- package/src/__tests__/agent-loop-output-hooks.test.ts +69 -0
- package/src/__tests__/agent-loop-override-profile.test.ts +25 -0
- package/src/__tests__/always-loaded-tools-guard.test.ts +2 -3
- package/src/__tests__/app-compiler.test.ts +15 -1
- package/src/__tests__/app-dir-path-guard.test.ts +0 -1
- package/src/__tests__/assistant-feature-flag-guard.test.ts +1 -4
- package/src/__tests__/assistant-feature-flag-guardrails.test.ts +0 -2
- package/src/__tests__/auth-fallback-events-store.test.ts +6 -14
- package/src/__tests__/avatar-identity-sync.test.ts +2 -27
- package/src/__tests__/btw-routes.test.ts +6 -8
- package/src/__tests__/call-pointer-messages.test.ts +28 -0
- package/src/__tests__/cancel-clears-processing.test.ts +89 -0
- package/src/__tests__/channel-approval-routes.test.ts +0 -4
- package/src/__tests__/channel-inbound-disk-pressure.test.ts +5 -15
- package/src/__tests__/checker.test.ts +0 -3
- package/src/__tests__/cli-memory-v2-reembed-skills.test.ts +3 -4
- package/src/__tests__/compactor-image-manifest-trust.test.ts +21 -1
- package/src/__tests__/compactor-summary-call-truncation.test.ts +223 -0
- package/src/__tests__/config-loader-backfill.test.ts +268 -27
- package/src/__tests__/config-schema.test.ts +35 -0
- package/src/__tests__/config-watcher.test.ts +0 -18
- package/src/__tests__/confirmation-request-guardian-bridge.test.ts +2 -2
- package/src/__tests__/contact-store-user-file.test.ts +0 -6
- package/src/__tests__/contacts-tools.test.ts +29 -0
- package/src/__tests__/conversation-agent-loop-inference-profile.test.ts +22 -0
- package/src/__tests__/conversation-agent-loop-overflow.test.ts +1 -0
- package/src/__tests__/conversation-agent-loop.test.ts +58 -0
- package/src/__tests__/conversation-attention-telegram.test.ts +0 -1
- package/src/__tests__/conversation-lifecycle.test.ts +7 -9
- package/src/__tests__/conversation-load-history-repair.test.ts +101 -0
- package/src/__tests__/conversation-routes-guardian-reply.test.ts +15 -12
- package/src/__tests__/conversation-surfaces-activation-emit.test.ts +6 -3
- package/src/__tests__/conversation-title-service.test.ts +62 -0
- package/src/__tests__/credential-broker.test.ts +449 -1
- package/src/__tests__/credential-execution-shell-lockdown.test.ts +18 -11
- package/src/__tests__/credential-execution-tools.test.ts +0 -1
- package/src/__tests__/credential-prompt-route.test.ts +4 -4
- package/src/__tests__/credential-routes.test.ts +360 -0
- package/src/__tests__/credential-security-invariants.test.ts +4 -13
- package/src/__tests__/disk-pressure-policy.test.ts +12 -0
- package/src/__tests__/disk-usage.test.ts +65 -0
- package/src/__tests__/dynamic-page-surface.test.ts +152 -1
- package/src/__tests__/fixtures/credential-security-fixtures.ts +2 -33
- package/src/__tests__/gateway-flag-listener.test.ts +110 -1
- package/src/__tests__/gateway-only-guard.test.ts +3 -7
- package/src/__tests__/guardian-binding-drift-heal.test.ts +1 -1
- package/src/__tests__/guardian-card-withdrawal.test.ts +403 -0
- package/src/__tests__/guardian-decision-primitive-canonical.test.ts +5 -3
- package/src/__tests__/guardian-grant-minting.test.ts +3 -35
- package/src/__tests__/guardian-routing-invariants.test.ts +64 -26
- package/src/__tests__/guardian-routing-state.test.ts +0 -1
- package/src/__tests__/headless-browser-mode.test.ts +10 -0
- package/src/__tests__/headless-browser-navigate.test.ts +8 -3
- package/src/__tests__/helpers/create-guardian-binding.ts +0 -1
- package/src/__tests__/host-browser-proxy.test.ts +87 -0
- package/src/__tests__/identity-routes.test.ts +0 -189
- package/src/__tests__/inbound-invite-redemption.test.ts +4 -4
- package/src/__tests__/injector-v3-suppression.test.ts +27 -20
- package/src/__tests__/internal-telemetry-routes.test.ts +6 -14
- package/src/__tests__/invite-redemption-service.test.ts +4 -7
- package/src/__tests__/llm-callsite-catalog.test.ts +5 -6
- package/src/__tests__/llm-catalog-parity.test.ts +30 -23
- package/src/__tests__/llm-resolver.test.ts +70 -24
- package/src/__tests__/llm-schema.test.ts +1 -0
- package/src/__tests__/managed-profile-guard.test.ts +163 -4
- package/src/__tests__/mcp-health-check.test.ts +6 -7
- package/src/__tests__/media-stream-server-integration.test.ts +317 -13
- package/src/__tests__/oauth-provider-seed-logos.test.ts +4 -6
- package/src/__tests__/onboarding-persona-write.test.ts +1 -1
- package/src/__tests__/path-policy.test.ts +34 -0
- package/src/__tests__/persona-resolver.test.ts +49 -14
- package/src/__tests__/plugin-api-model-profiles.test.ts +178 -0
- package/src/__tests__/plugin-api-provider.test.ts +24 -0
- package/src/__tests__/plugin-tool-contribution.test.ts +6 -3
- package/src/__tests__/post-compaction-reinjection-idempotency.test.ts +214 -0
- package/src/__tests__/provider-send-message-override-profile.test.ts +76 -0
- package/src/__tests__/reaction-persistence.test.ts +150 -29
- package/src/__tests__/registry.test.ts +2 -7
- package/src/__tests__/relay-server.test.ts +285 -0
- package/src/__tests__/runtime-attachment-metadata.test.ts +0 -1
- package/src/__tests__/schedule-routes-workflow-validation.test.ts +1 -10
- package/src/__tests__/schedule-routes.test.ts +0 -30
- package/src/__tests__/schedule-tools.test.ts +2 -18
- package/src/__tests__/scheduler-reuse-conversation.test.ts +8 -5
- package/src/__tests__/skill-execute-input.test.ts +51 -1
- package/src/__tests__/skill-runtime-path.test.ts +2 -3
- package/src/__tests__/skills.test.ts +51 -0
- package/src/__tests__/slack-notification-approval-card.test.ts +176 -0
- package/src/__tests__/slack-reaction-canonical-approval.test.ts +285 -0
- package/src/__tests__/subagent-tools.test.ts +266 -0
- package/src/__tests__/surface-completion-nudge-hook.test.ts +367 -0
- package/src/__tests__/task-progress-nudge-hook.test.ts +1 -1
- package/src/__tests__/title-generate-hook.test.ts +100 -3
- package/src/__tests__/token-estimator-accuracy.benchmark.test.ts +1 -29
- package/src/__tests__/token-manager.test.ts +519 -0
- package/src/__tests__/tool-approval-seed-content-blocks.test.ts +1 -1
- package/src/__tests__/tool-audit-listener.test.ts +7 -7
- package/src/__tests__/tool-executor-lifecycle-events.test.ts +6 -3
- package/src/__tests__/tool-executor.test.ts +0 -79
- package/src/__tests__/trusted-contact-approval-notifier.test.ts +4 -2
- package/src/__tests__/trusted-contact-inline-approval-integration.test.ts +220 -3
- package/src/__tests__/trusted-contact-multichannel.test.ts +3 -3
- package/src/__tests__/trusted-contact-verification.test.ts +8 -10
- package/src/__tests__/twilio-routes.test.ts +81 -1
- package/src/__tests__/voice-invite-redemption.test.ts +2 -3
- package/src/__tests__/weak-open-model.test.ts +30 -0
- package/src/__tests__/web-search-catalog-parity.test.ts +6 -25
- package/src/__tests__/workspace-greetings.test.ts +152 -0
- package/src/__tests__/workspace-migration-105-enable-memory-v3-live-for-new-workspaces.test.ts +149 -0
- package/src/__tests__/workspace-migration-108-drop-balanced-economy-profile.test.ts +285 -0
- package/src/__tests__/workspace-migration-add-send-diagnostics.test.ts +1 -1
- package/src/__tests__/workspace-migration-drop-collect-usage-data.test.ts +118 -0
- package/src/__tests__/workspace-migration-drop-send-diagnostics.test.ts +118 -0
- package/src/a2a/__tests__/e2e-a2a-channel.test.ts +0 -4
- package/src/agent/loop.ts +49 -29
- package/src/api/README.md +6 -6
- package/src/api/events/tool-result.ts +6 -0
- package/src/api/events/workflow-completed.ts +53 -0
- package/src/api/events/workflow-leaf-finished.ts +38 -0
- package/src/api/events/workflow-leaf-started.ts +35 -0
- package/src/api/events/workflow-progress.ts +32 -0
- package/src/api/events/workflow-started.ts +31 -0
- package/src/api/index.ts +40 -0
- package/src/api/responses/conversation-message.ts +28 -4
- package/src/api/responses/home.ts +26 -4
- package/src/api/responses/workflow-journal.ts +53 -0
- package/src/approvals/guardian-card-withdrawal.ts +145 -0
- package/src/approvals/guardian-decision-primitive.ts +26 -3
- package/src/approvals/guardian-request-resolvers.ts +183 -80
- package/src/calls/__tests__/channel-admission-reader.test.ts +132 -0
- package/src/calls/__tests__/relay-setup-router.test.ts +350 -0
- package/src/calls/call-pointer-messages.ts +10 -4
- package/src/calls/channel-admission-reader.ts +104 -0
- package/src/calls/guardian-dispatch.ts +17 -45
- package/src/calls/media-stream-server.ts +84 -2
- package/src/calls/relay-access-wait.ts +1 -1
- package/src/calls/relay-server.ts +66 -0
- package/src/calls/relay-setup-router.ts +82 -1
- package/src/calls/twilio-routes.ts +17 -8
- package/src/calls/voice-session-bridge.ts +2 -2
- package/src/cli/commands/clients.ts +3 -0
- package/src/cli/commands/{__tests__ → memory/__tests__}/memory-v2-compare-render.test.ts +1 -1
- package/src/cli/commands/{__tests__ → memory/__tests__}/memory-v2.test.ts +8 -7
- package/src/cli/commands/{__tests__ → memory/__tests__}/memory-v3.test.ts +5 -4
- package/src/cli/commands/memory/index.ts +30 -0
- package/src/cli/commands/{memory-v2-compare-render.ts → memory/memory-v2-compare-render.ts} +1 -1
- package/src/cli/commands/{memory-v2.ts → memory/memory-v2.ts} +6 -15
- package/src/cli/commands/{memory-v3.ts → memory/memory-v3.ts} +97 -11
- package/src/cli/commands/oauth/status.test.ts +36 -0
- package/src/cli/commands/oauth/status.ts +23 -3
- package/src/cli/commands/plugins.ts +197 -4
- package/src/cli/lib/__tests__/diff-plugin.test.ts +443 -0
- package/src/cli/lib/__tests__/inspect-plugin.test.ts +54 -0
- package/src/cli/lib/__tests__/merge-plugin-tree.test.ts +443 -0
- package/src/cli/lib/__tests__/plugin-surfaces.test.ts +111 -0
- package/src/cli/lib/__tests__/upgrade-plugin.test.ts +295 -2
- package/src/cli/lib/diff-plugin.ts +346 -0
- package/src/cli/lib/inspect-plugin.ts +12 -1
- package/src/cli/lib/install-from-github.ts +105 -17
- package/src/cli/lib/merge-plugin-tree.ts +328 -0
- package/src/cli/lib/plugin-fingerprint.ts +14 -0
- package/src/cli/lib/plugin-surfaces.ts +104 -0
- package/src/cli/lib/upgrade-plugin.ts +298 -10
- package/src/cli/program.ts +2 -6
- package/src/config/__tests__/sync-gated-profiles.test.ts +368 -0
- package/src/config/assistant-feature-flags.ts +22 -7
- package/src/config/bundled-skills/contacts/tools/contact-search.ts +0 -1
- package/src/config/bundled-skills/messaging/SKILL.md +6 -4
- package/src/config/bundled-skills/messaging/tools/messaging-archive-by-sender.ts +9 -8
- package/src/config/bundled-skills/subagent/SKILL.md +4 -0
- package/src/config/bundled-skills/subagent/TOOLS.json +4 -0
- package/src/config/bundled-skills/workflows/SKILL.md +14 -8
- package/src/config/bundled-tool-registry.ts +2 -7
- package/src/config/call-site-defaults.ts +15 -2
- package/src/config/feature-flag-registry.json +46 -31
- package/src/config/inference-profile-validation.ts +26 -0
- package/src/config/llm-resolver.ts +3 -0
- package/src/config/loader.ts +4 -0
- package/src/config/memory-v3-gate.ts +11 -0
- package/src/config/profile-order.ts +28 -0
- package/src/config/schema.ts +8 -6
- package/src/config/schemas/__tests__/memory-v3.test.ts +1 -0
- package/src/config/schemas/call-site-catalog.ts +7 -0
- package/src/config/schemas/channels.ts +11 -0
- package/src/config/schemas/elevenlabs.ts +0 -1
- package/src/config/schemas/llm.ts +31 -0
- package/src/config/schemas/memory-lifecycle.ts +3 -7
- package/src/config/schemas/memory-v3.ts +6 -0
- package/src/config/schemas/platform.ts +0 -8
- package/src/config/schemas/services.ts +18 -0
- package/src/config/seed-inference-profiles.ts +109 -44
- package/src/config/skills.ts +21 -0
- package/src/config/sync-gated-profiles.ts +220 -0
- package/src/contacts/contact-store.ts +89 -106
- package/src/contacts/contacts-write.ts +5 -22
- package/src/contacts/types.ts +0 -1
- package/src/context/compactor.ts +88 -54
- package/src/context/strip-injections.ts +58 -10
- package/src/context/token-estimator.ts +1 -1
- package/src/credential-execution/process-manager.ts +55 -14
- package/src/credential-execution/prompted-credential.ts +2 -3
- package/src/daemon/__tests__/conversation-lifecycle-auto-analyze.test.ts +3 -2
- package/src/daemon/config-watcher.ts +0 -4
- package/src/daemon/conversation-agent-loop-handlers.ts +2 -0
- package/src/daemon/conversation-agent-loop.ts +114 -22
- package/src/daemon/conversation-history.ts +1 -1
- package/src/daemon/conversation-lifecycle.ts +3 -5
- package/src/daemon/conversation-process.ts +13 -5
- package/src/daemon/conversation-runtime-assembly.ts +13 -15
- package/src/daemon/conversation-slash.ts +2 -23
- package/src/daemon/conversation-surfaces.ts +26 -0
- package/src/daemon/conversation-tool-setup.ts +27 -14
- package/src/daemon/conversation.ts +66 -14
- package/src/daemon/disk-pressure-policy.ts +5 -3
- package/src/daemon/handlers/__tests__/config-a2a-complete.test.ts +0 -1
- package/src/daemon/handlers/__tests__/config-a2a-redeem.test.ts +0 -1
- package/src/daemon/handlers/config-a2a.ts +0 -2
- package/src/daemon/handlers/config-channels.ts +15 -16
- package/src/daemon/handlers/config-slack-channel.ts +22 -3
- package/src/daemon/handlers/conversations.ts +107 -0
- package/src/daemon/host-browser-proxy.ts +41 -0
- package/src/daemon/lifecycle.ts +55 -27
- package/src/daemon/message-provenance.ts +2 -0
- package/src/daemon/message-types/contacts.ts +0 -1
- package/src/daemon/message-types/conversations.ts +3 -3
- package/src/daemon/message-types/sync.ts +0 -1
- package/src/daemon/message-types/web-activity.ts +7 -1
- package/src/daemon/message-types/workflows.ts +83 -1
- package/src/daemon/orphan-reaper.test.ts +0 -19
- package/src/daemon/orphan-reaper.ts +2 -24
- package/src/daemon/server.ts +0 -10
- package/src/daemon/tool-setup-types.ts +4 -0
- package/src/daemon/trust-context.ts +1 -1
- package/src/events/tool-audit-listener.ts +2 -2
- package/src/home/feed-source-enrichment.test.ts +151 -0
- package/src/home/feed-source-enrichment.ts +176 -0
- package/src/home/relationship-state.ts +2 -4
- package/src/instrument.ts +18 -6
- package/src/ipc/__tests__/binary-result-ipc.test.ts +81 -0
- package/src/ipc/__tests__/clients-list-ipc.test.ts +20 -0
- package/src/ipc/assistant-server.ts +37 -4
- package/src/ipc/gateway-flag-listener.ts +18 -2
- package/src/memory/__tests__/auto-analysis-enqueue.test.ts +5 -16
- package/src/memory/__tests__/jobs-store-enqueue-gate.test.ts +7 -11
- package/src/memory/__tests__/memory-retrospective-enqueue.test.ts +37 -7
- package/src/memory/__tests__/memory-retrospective-job.test.ts +229 -401
- package/src/memory/__tests__/onboarding-events-store.test.ts +7 -7
- package/src/memory/auth-fallback-events-store.ts +2 -2
- package/src/memory/auto-analysis-enqueue.ts +3 -5
- package/src/memory/bookmark-crud.ts +1 -2
- package/src/memory/canonical-guardian-store.ts +39 -1
- package/src/memory/conversation-crud.ts +9 -4
- package/src/memory/conversation-key-store.ts +17 -2
- package/src/memory/conversation-title-service.ts +64 -7
- package/src/memory/db-init.ts +17 -17
- package/src/memory/embedding-backend.ts +38 -1
- package/src/memory/embedding-billing-breaker.ts +96 -0
- package/src/memory/jobs-store.ts +25 -13
- package/src/memory/jobs-worker.ts +54 -1
- package/src/memory/lifecycle-events-store.ts +2 -2
- package/src/memory/memory-retrospective-constants.ts +4 -4
- package/src/memory/memory-retrospective-enqueue.ts +31 -6
- package/src/memory/memory-retrospective-job.ts +28 -227
- package/src/memory/migrations/129-contact-channels-access-fields.ts +18 -9
- package/src/memory/migrations/131-drop-legacy-member-guardian-tables.ts +14 -2
- package/src/memory/migrations/289-contact-channels-unique-ext-user.ts +10 -0
- package/src/memory/migrations/291-contact-channels-renormalize-addresses.ts +72 -0
- package/src/memory/migrations/292-schedule-default-no-reuse-conversation.test.ts +67 -0
- package/src/memory/migrations/292-schedule-default-no-reuse-conversation.ts +25 -0
- package/src/memory/migrations/293-workflow-journal-leaf-tokens.ts +32 -0
- package/src/memory/migrations/294-drop-external-user-id.ts +31 -0
- package/src/memory/migrations/295-drop-approval-prompt-ts-tracker.ts +20 -0
- package/src/memory/migrations/296-rewrite-balanced-economy-profile-pins.test.ts +110 -0
- package/src/memory/migrations/296-rewrite-balanced-economy-profile-pins.ts +68 -0
- package/src/memory/migrations/__tests__/131-drop-legacy-member-guardian-tables.test.ts +154 -0
- package/src/memory/migrations/__tests__/289-contact-channels-unique-ext-user.test.ts +31 -0
- package/src/memory/migrations/__tests__/291-contact-channels-renormalize-addresses.test.ts +341 -0
- package/src/memory/migrations/__tests__/run-migrations.test.ts +52 -0
- package/src/memory/migrations/index.ts +6 -0
- package/src/memory/migrations/run-migrations.ts +41 -0
- package/src/memory/migrations/validate-migration-state.ts +1 -1
- package/src/memory/onboarding-events-store.ts +3 -3
- package/src/memory/schema/contacts.ts +0 -5
- package/src/memory/skill-loaded-events-store.test.ts +7 -15
- package/src/memory/skill-loaded-events-store.ts +2 -2
- package/src/memory/tool-executed-events-store.test.ts +7 -7
- package/src/memory/turn-trace-store.test.ts +736 -0
- package/src/memory/turn-trace-store.ts +364 -0
- package/src/memory/v2/__tests__/consolidation-job.test.ts +8 -0
- package/src/memory/v2/__tests__/skill-content.test.ts +30 -0
- package/src/memory/v2/consolidation-job.ts +2 -2
- package/src/memory/v2/skill-content.ts +25 -7
- package/src/memory/v2/skill-store.ts +7 -1
- package/src/memory/v3-eval/__tests__/eval-packets.test.ts +248 -0
- package/src/memory/v3-eval/eval-packets.ts +546 -0
- package/src/messaging/providers/slack/adapter.ts +1 -1
- package/src/messaging/providers/slack/api.ts +31 -0
- package/src/messaging/providers/slack/send.test.ts +114 -2
- package/src/messaging/providers/slack/send.ts +30 -7
- package/src/messaging/providers/slack/withdraw.test.ts +200 -0
- package/src/messaging/providers/slack/withdraw.ts +161 -0
- package/src/notifications/AGENTS.md +2 -0
- package/src/notifications/access-request-copy.ts +72 -59
- package/src/notifications/adapters/shared.ts +29 -0
- package/src/notifications/adapters/slack.ts +58 -103
- package/src/notifications/adapters/telegram.ts +2 -20
- package/src/notifications/approval-card-data.ts +333 -0
- package/src/notifications/broadcaster.ts +16 -3
- package/src/notifications/canonical-delivery-recorder.ts +139 -0
- package/src/notifications/copy-composer.ts +3 -3
- package/src/notifications/decision-engine.ts +4 -2
- package/src/notifications/destination-resolver.ts +4 -6
- package/src/notifications/guardian-question-mode.ts +10 -0
- package/src/notifications/home-feed-side-effect.ts +7 -16
- package/src/notifications/notification-utils.ts +19 -20
- package/src/notifications/signal.ts +79 -43
- package/src/notifications/types.ts +98 -121
- package/src/oauth/AGENTS.md +5 -24
- package/src/permissions/checker.test.ts +51 -0
- package/src/permissions/checker.ts +185 -26
- package/src/permissions/ipc-risk-types.ts +24 -0
- package/src/permissions/question-prompter.test.ts +27 -0
- package/src/permissions/question-prompter.ts +4 -0
- package/src/platform/client.test.ts +119 -0
- package/src/platform/client.ts +66 -0
- package/src/platform/consent-cache.test.ts +267 -0
- package/src/platform/consent-cache.ts +174 -0
- package/src/plugin-api/constants.ts +1 -1
- package/src/plugin-api/index.ts +33 -1
- package/src/plugin-api/model-profiles.ts +33 -0
- package/src/plugin-api/types.ts +50 -2
- package/src/plugins/defaults/advisor/__tests__/advisor-gate.test.ts +56 -0
- package/src/plugins/defaults/advisor/__tests__/advisor-state-store.test.ts +43 -0
- package/src/plugins/defaults/advisor/__tests__/agent-loop-integration.test.ts +137 -0
- package/src/plugins/defaults/advisor/__tests__/consult.test.ts +153 -0
- package/src/plugins/defaults/advisor/__tests__/hooks.test.ts +138 -0
- package/src/plugins/defaults/advisor/__tests__/transcript.test.ts +147 -0
- package/src/plugins/defaults/advisor/advisor-gate.ts +29 -0
- package/src/plugins/defaults/advisor/advisor-state-store.ts +94 -0
- package/src/plugins/defaults/advisor/config.ts +21 -0
- package/src/plugins/defaults/advisor/consult.ts +93 -0
- package/src/plugins/defaults/advisor/hooks/post-model-call.ts +34 -0
- package/src/plugins/defaults/advisor/hooks/pre-model-call.ts +30 -0
- package/src/plugins/defaults/advisor/hooks/user-prompt-submit.ts +19 -0
- package/src/plugins/defaults/advisor/package.json +14 -0
- package/src/plugins/defaults/advisor/steering.ts +67 -0
- package/src/plugins/defaults/advisor/tools/advisor.ts +65 -0
- package/src/plugins/defaults/advisor/transcript.ts +76 -0
- package/src/plugins/defaults/index.ts +60 -0
- package/src/plugins/defaults/memory-retrieval/hooks/post-compact.ts +22 -9
- package/src/plugins/defaults/memory-retrieval/hooks/user-prompt-submit.ts +2 -2
- package/src/plugins/defaults/memory-retrieval/tail-reinjection-strip.ts +64 -0
- package/src/plugins/defaults/memory-retrieval/unified-turn-context.ts +29 -21
- package/src/plugins/defaults/memory-v3-shadow/__tests__/carry-integration.test.ts +1 -0
- package/src/plugins/defaults/memory-v3-shadow/__tests__/injection.test.ts +1 -0
- package/src/plugins/defaults/memory-v3-shadow/__tests__/maintain-job.test.ts +129 -9
- package/src/plugins/defaults/memory-v3-shadow/__tests__/orchestrate.test.ts +31 -4
- package/src/plugins/defaults/memory-v3-shadow/__tests__/selection-log-store.test.ts +77 -2
- package/src/plugins/defaults/memory-v3-shadow/__tests__/shadow-plugin.test.ts +1 -0
- package/src/plugins/defaults/memory-v3-shadow/injector.ts +7 -10
- package/src/plugins/defaults/memory-v3-shadow/maintain-job.ts +144 -11
- package/src/plugins/defaults/memory-v3-shadow/orchestrate.ts +32 -20
- package/src/plugins/defaults/memory-v3-shadow/selection-log-store.ts +56 -3
- package/src/plugins/defaults/memory-v3-shadow/shadow-plugin.ts +23 -2
- package/src/plugins/defaults/surface-completion-nudge/hooks/post-model-call.ts +276 -0
- package/src/plugins/defaults/surface-completion-nudge/hooks/stop.ts +22 -0
- package/src/plugins/defaults/surface-completion-nudge/nudge-state-store.ts +46 -0
- package/src/plugins/defaults/surface-completion-nudge/package.json +14 -0
- package/src/plugins/defaults/task-progress-nudge/hooks/post-tool-use.ts +3 -13
- package/src/plugins/defaults/title-generate/hooks/stop.ts +56 -21
- package/src/prompts/persona-resolver.ts +14 -4
- package/src/prompts/templates/system-sections.ts +7 -2
- package/src/providers/__tests__/provider-env-vars.test.ts +6 -0
- package/src/providers/__tests__/provider-secret-catalog.test.ts +1 -0
- package/src/providers/__tests__/retry-callsite.test.ts +176 -0
- package/src/providers/atlascloud/client.ts +85 -0
- package/src/providers/fetch-provider-catalog.ts +85 -0
- package/src/providers/inference/adapter-factory.ts +3 -0
- package/src/providers/model-catalog.ts +58 -0
- package/src/providers/openai/__tests__/chat-completions-provider-reasoning.test.ts +33 -0
- package/src/providers/openai/chat-completions-provider.ts +7 -0
- package/src/providers/openai/responses-provider.ts +10 -0
- package/src/providers/provider-send-message.ts +11 -3
- package/src/providers/retry.ts +53 -12
- package/src/providers/search-provider-catalog.ts +10 -0
- package/src/providers/weak-open-model.ts +22 -0
- package/src/runtime/AGENTS.md +0 -1
- package/src/runtime/__tests__/agent-wake.test.ts +181 -0
- package/src/runtime/__tests__/client-health.test.ts +44 -0
- package/src/runtime/access-request-helper.ts +21 -53
- package/src/runtime/actor-trust-resolver.ts +59 -63
- package/src/runtime/agent-wake.ts +52 -0
- package/src/runtime/assistant-event-hub.ts +18 -4
- package/src/runtime/auth/__tests__/route-policy.test.ts +12 -0
- package/src/runtime/auth/require-bound-guardian.ts +1 -4
- package/src/runtime/btw-sidechain.ts +3 -6
- package/src/runtime/capabilities.test.ts +120 -0
- package/src/runtime/capabilities.ts +197 -0
- package/src/runtime/channel-approval-types.ts +22 -45
- package/src/runtime/channel-invite-transports/telegram.ts +4 -4
- package/src/runtime/channel-retry-sweep.ts +1 -0
- package/src/runtime/channel-verification-service.ts +3 -3
- package/src/runtime/client-health.ts +26 -0
- package/src/runtime/confirmation-request-guardian-bridge.ts +38 -29
- package/src/runtime/effective-capabilities.test.ts +128 -0
- package/src/runtime/effective-capabilities.ts +84 -0
- package/src/runtime/guardian-reply-router.ts +106 -21
- package/src/runtime/invite-redemption-service.ts +9 -25
- package/src/runtime/migrations/__tests__/vbundle-builder-fd-leak.test.ts +123 -0
- package/src/runtime/migrations/vbundle-builder.ts +49 -20
- package/src/runtime/pending-interactions.ts +15 -0
- package/src/runtime/routes/__tests__/client-routes.test.ts +13 -0
- package/src/runtime/routes/__tests__/conversation-management-routes.test.ts +67 -0
- package/src/runtime/routes/__tests__/plugins-routes.test.ts +240 -1
- package/src/runtime/routes/app-routes.ts +1 -1
- package/src/runtime/routes/approval-strategies/guardian-callback-strategy.ts +2 -2
- package/src/runtime/routes/assets/vellum-design-system.css +1959 -0
- package/src/runtime/routes/browser-tabs-routes.ts +9 -0
- package/src/runtime/routes/btw-routes.ts +1 -27
- package/src/runtime/routes/canonical-guardian-expiry-sweep.ts +17 -8
- package/src/runtime/routes/client-routes.ts +10 -0
- package/src/runtime/routes/contact-routes.ts +31 -8
- package/src/runtime/routes/conversation-compaction-routes.ts +1 -1
- package/src/runtime/routes/conversation-management-routes.ts +80 -1
- package/src/runtime/routes/conversation-query-routes.ts +68 -22
- package/src/runtime/routes/conversation-routes.ts +39 -14
- package/src/runtime/routes/credential-routes.ts +40 -16
- package/src/runtime/routes/empty-state-greeting-cache.ts +1 -2
- package/src/runtime/routes/events-routes.ts +1 -3
- package/src/runtime/routes/guardian-approval-interception.ts +14 -73
- package/src/runtime/routes/guardian-approval-prompt.ts +22 -4
- package/src/runtime/routes/home-feed-routes.ts +8 -3
- package/src/runtime/routes/identity-routes.ts +1 -296
- package/src/runtime/routes/inbound-message-handler.ts +214 -228
- package/src/runtime/routes/inbound-stages/acl-enforcement.ts +89 -7
- package/src/runtime/routes/inbound-stages/admission-policy.test.ts +154 -0
- package/src/runtime/routes/inbound-stages/admission-policy.ts +140 -0
- package/src/runtime/routes/inbound-stages/background-dispatch.test.ts +3 -3
- package/src/runtime/routes/inbound-stages/background-dispatch.ts +11 -6
- package/src/runtime/routes/inbound-stages/escalation-intercept.ts +1 -2
- package/src/runtime/routes/inbound-stages/guardian-activation-intercept.ts +1 -2
- package/src/runtime/routes/inbound-stages/guardian-reply-intercept.test.ts +7 -7
- package/src/runtime/routes/inbound-stages/guardian-reply-intercept.ts +47 -28
- package/src/runtime/routes/inbound-stages/reaction-intercept.ts +358 -0
- package/src/runtime/routes/index.ts +2 -0
- package/src/runtime/routes/integrations/slack/__tests__/channel.test.ts +8 -0
- package/src/runtime/routes/integrations/slack/channel.ts +36 -0
- package/src/runtime/routes/internal-telemetry-routes.ts +1 -1
- package/src/runtime/routes/mcp-auth-routes.ts +233 -41
- package/src/runtime/routes/memory-eval-routes.ts +87 -0
- package/src/runtime/routes/notification-routes.ts +122 -133
- package/src/runtime/routes/platform-routes.ts +2 -2
- package/src/runtime/routes/plugins-routes.ts +202 -3
- package/src/runtime/routes/schedule-routes.ts +0 -22
- package/src/runtime/routes/secret-routes.ts +10 -0
- package/src/runtime/routes/surface-action-routes.ts +2 -1
- package/src/runtime/routes/tool-call-question-enrichment.test.ts +146 -0
- package/src/runtime/routes/tool-call-question-enrichment.ts +66 -0
- package/src/runtime/routes/workflow-routes.test.ts +229 -44
- package/src/runtime/routes/workflow-routes.ts +131 -29
- package/src/runtime/routes/workspace-greetings.ts +55 -0
- package/src/runtime/sync/resource-sync-events.ts +1 -11
- package/src/runtime/tool-grant-request-helper.ts +18 -16
- package/src/runtime/trust-context-resolver.ts +8 -5
- package/src/schedule/inference-profile.ts +2 -14
- package/src/schedule/schedule-store.ts +1 -1
- package/src/schedule/scheduler-types.ts +5 -1
- package/src/security/__tests__/provider-key-env-fallback.test.ts +6 -0
- package/src/security/secret-patterns.ts +3 -0
- package/src/subagent/manager.ts +17 -4
- package/src/subagent/types.ts +6 -0
- package/src/telemetry/trace-collection-policy.test.ts +28 -0
- package/src/telemetry/trace-collection-policy.ts +30 -0
- package/src/telemetry/types.ts +89 -0
- package/src/telemetry/usage-telemetry-reporter.test.ts +586 -36
- package/src/telemetry/usage-telemetry-reporter.ts +148 -41
- package/src/tools/AGENTS.md +3 -3
- package/src/tools/browser/__tests__/browser-execution-acquire.test.ts +31 -0
- package/src/tools/browser/browser-execution.ts +30 -19
- package/src/tools/document/document-tool.ts +2 -3
- package/src/tools/executor.ts +5 -3
- package/src/tools/host-terminal/host-shell.ts +5 -4
- package/src/tools/memory/register.ts +2 -2
- package/src/tools/network/__tests__/web-fetch-firecrawl.test.ts +360 -0
- package/src/tools/network/__tests__/web-search.test.ts +143 -0
- package/src/tools/network/web-fetch.ts +372 -1
- package/src/tools/network/web-search-error.ts +1 -1
- package/src/tools/network/web-search.ts +213 -10
- package/src/tools/permission-checker.ts +4 -3
- package/src/tools/registry.ts +20 -0
- package/src/tools/schedule/create.ts +7 -12
- package/src/tools/schedule/update.ts +4 -11
- package/src/tools/shared/filesystem/path-policy.ts +39 -13
- package/src/tools/side-effects.ts +2 -17
- package/src/tools/skills/execute.ts +33 -0
- package/src/tools/subagent/spawn.ts +61 -12
- package/src/tools/terminal/shell.ts +10 -4
- package/src/tools/tool-approval-handler.ts +18 -13
- package/src/tools/tool-manifest.ts +0 -2
- package/src/tools/types.ts +9 -0
- package/src/tools/ui-surface/definitions.ts +64 -3
- package/src/tools/verification-control-plane-policy.ts +3 -1
- package/src/tools/workflows/run-workflow.test.ts +8 -18
- package/src/tools/workflows/run-workflow.ts +1 -0
- package/src/util/disk-usage.ts +78 -23
- package/src/util/platform.ts +10 -3
- package/src/watcher/telemetry.ts +2 -2
- package/src/workflows/capabilities.ts +2 -3
- package/src/workflows/engine.test.ts +175 -1
- package/src/workflows/engine.ts +82 -0
- package/src/workflows/journal-store.test.ts +70 -0
- package/src/workflows/journal-store.ts +18 -3
- package/src/workflows/run-manager.test.ts +171 -28
- package/src/workflows/run-manager.ts +66 -24
- package/src/workspace/migrations/105-enable-memory-v3-live-for-new-workspaces.ts +63 -0
- package/src/workspace/migrations/106-drop-collect-usage-data.ts +47 -0
- package/src/workspace/migrations/107-drop-send-diagnostics.ts +47 -0
- package/src/workspace/migrations/108-drop-balanced-economy-profile.ts +129 -0
- package/src/workspace/migrations/registry.ts +8 -0
- package/src/__tests__/app-control-no-global-cgevent.test.ts +0 -98
- package/src/__tests__/credential-security-e2e.test.ts +0 -362
- package/src/__tests__/credential-vault-unit.test.ts +0 -1528
- package/src/__tests__/credential-vault.test.ts +0 -1706
- package/src/__tests__/identity-intro-cache.test.ts +0 -315
- package/src/__tests__/secret-onetime-send.test.ts +0 -182
- package/src/cli/commands/__tests__/task.test.ts +0 -914
- package/src/cli/commands/task.ts +0 -771
- package/src/config/bundled-skills/personal-page/SKILL.md +0 -57
- package/src/config/bundled-skills/personal-page/TOOLS.json +0 -27
- package/src/config/bundled-skills/personal-page/tools/app-refresh.ts +0 -17
- package/src/config/preloaded-apps/personal-page/src/components/About.tsx +0 -22
- package/src/config/preloaded-apps/personal-page/src/components/App.tsx +0 -16
- package/src/config/preloaded-apps/personal-page/src/components/Features.tsx +0 -77
- package/src/config/preloaded-apps/personal-page/src/components/Hero.tsx +0 -57
- package/src/config/preloaded-apps/personal-page/src/components/Pending.tsx +0 -28
- package/src/config/preloaded-apps/personal-page/src/components/animations.tsx +0 -234
- package/src/config/preloaded-apps/personal-page/src/components/icons.tsx +0 -48
- package/src/config/preloaded-apps/personal-page/src/components/media.ts +0 -16
- package/src/config/preloaded-apps/personal-page/src/index.html +0 -20
- package/src/config/preloaded-apps/personal-page/src/main.tsx +0 -7
- package/src/config/preloaded-apps/personal-page/src/profile-data.ts +0 -82
- package/src/config/preloaded-apps/personal-page/src/styles.css +0 -759
- package/src/memory/__tests__/preloaded-apps.test.ts +0 -85
- package/src/memory/preloaded-apps.ts +0 -116
- package/src/notifications/tool-approval-copy.ts +0 -142
- package/src/runtime/routes/approval-prompt-ts-tracker.ts +0 -78
- package/src/runtime/routes/identity-intro-cache.ts +0 -172
- package/src/tools/credentials/vault.ts +0 -712
|
@@ -32,18 +32,14 @@ import {
|
|
|
32
32
|
// ── Mock state ────────────────────────────────────────────────────
|
|
33
33
|
|
|
34
34
|
let mockWorkspaceDir: string = "";
|
|
35
|
-
let mockVellumGuardian:
|
|
36
|
-
|
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
| null
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
contact: { userFile: string | null };
|
|
44
|
-
channels: Record<string, unknown>[];
|
|
45
|
-
}
|
|
46
|
-
| null = null;
|
|
35
|
+
let mockVellumGuardian: {
|
|
36
|
+
contact: { userFile: string | null };
|
|
37
|
+
channel: Record<string, unknown>;
|
|
38
|
+
} | null = null;
|
|
39
|
+
let mockAnyGuardian: {
|
|
40
|
+
contact: { userFile: string | null };
|
|
41
|
+
channels: Record<string, unknown>[];
|
|
42
|
+
} | null = null;
|
|
47
43
|
|
|
48
44
|
// ── Mock modules (must precede imports from the module under test) ──
|
|
49
45
|
|
|
@@ -52,7 +48,7 @@ mock.module("../util/platform.js", () => ({
|
|
|
52
48
|
}));
|
|
53
49
|
|
|
54
50
|
mock.module("../contacts/contact-store.js", () => ({
|
|
55
|
-
|
|
51
|
+
findContactByAddress: () => null,
|
|
56
52
|
findGuardianForChannel: (channelType: string) =>
|
|
57
53
|
channelType === "vellum" ? mockVellumGuardian : null,
|
|
58
54
|
listGuardianChannels: () => mockAnyGuardian,
|
|
@@ -60,12 +56,14 @@ mock.module("../contacts/contact-store.js", () => ({
|
|
|
60
56
|
|
|
61
57
|
// Import AFTER mocks so the module under test binds to the stubbed
|
|
62
58
|
// implementations.
|
|
59
|
+
import type { TrustContext } from "../daemon/trust-context.js";
|
|
63
60
|
import {
|
|
64
61
|
ensureGuardianPersonaFile,
|
|
65
62
|
isGuardianPersonaCustomized,
|
|
66
63
|
resolveGuardianPersona,
|
|
67
64
|
resolveGuardianPersonaPath,
|
|
68
65
|
resolveGuardianPersonaStrict,
|
|
66
|
+
resolveUserSlug,
|
|
69
67
|
} from "../prompts/persona-resolver.js";
|
|
70
68
|
|
|
71
69
|
// ── Temp workspace scaffold ───────────────────────────────────────
|
|
@@ -138,7 +136,8 @@ describe("ensureGuardianPersonaFile", () => {
|
|
|
138
136
|
const userFile = "alice.md";
|
|
139
137
|
const dir = join(mockWorkspaceDir, "users");
|
|
140
138
|
const filePath = join(dir, userFile);
|
|
141
|
-
const existingContent =
|
|
139
|
+
const existingContent =
|
|
140
|
+
"# Existing user notes\n\n- Likes sparkling water\n";
|
|
142
141
|
|
|
143
142
|
mkdirSync(dir, { recursive: true });
|
|
144
143
|
writeFileSync(filePath, existingContent, "utf-8");
|
|
@@ -249,3 +248,39 @@ describe("isGuardianPersonaCustomized", () => {
|
|
|
249
248
|
expect(isGuardianPersonaCustomized(filePath)).toBe(true);
|
|
250
249
|
});
|
|
251
250
|
});
|
|
251
|
+
|
|
252
|
+
// ── resolveUserSlug — background/scheduled guardian turns ──────────
|
|
253
|
+
//
|
|
254
|
+
// Background and scheduled turns carry a guardian trust context with no
|
|
255
|
+
// `requesterExternalUserId`. They must resolve the guardian's user file
|
|
256
|
+
// (parity with foreground guardian turns), not fall through to default.
|
|
257
|
+
|
|
258
|
+
describe("resolveUserSlug (guardian trust, no requester identity)", () => {
|
|
259
|
+
test("guardian trust context without requesterExternalUserId resolves the guardian user file", () => {
|
|
260
|
+
mockVellumGuardian = {
|
|
261
|
+
contact: { userFile: "alice.md" },
|
|
262
|
+
channel: {},
|
|
263
|
+
};
|
|
264
|
+
|
|
265
|
+
const trustContext = {
|
|
266
|
+
sourceChannel: "vellum",
|
|
267
|
+
trustClass: "guardian",
|
|
268
|
+
} as TrustContext;
|
|
269
|
+
|
|
270
|
+
expect(resolveUserSlug(trustContext)).toBe("alice");
|
|
271
|
+
});
|
|
272
|
+
|
|
273
|
+
test("non-guardian trust context without requesterExternalUserId does not borrow the guardian persona", () => {
|
|
274
|
+
mockVellumGuardian = {
|
|
275
|
+
contact: { userFile: "alice.md" },
|
|
276
|
+
channel: {},
|
|
277
|
+
};
|
|
278
|
+
|
|
279
|
+
const trustContext = {
|
|
280
|
+
sourceChannel: "vellum",
|
|
281
|
+
trustClass: "trusted_contact",
|
|
282
|
+
} as TrustContext;
|
|
283
|
+
|
|
284
|
+
expect(resolveUserSlug(trustContext)).toBeNull();
|
|
285
|
+
});
|
|
286
|
+
});
|
|
@@ -0,0 +1,178 @@
|
|
|
1
|
+
import { writeFileSync } from "node:fs";
|
|
2
|
+
import { afterEach, describe, expect, test } from "bun:test";
|
|
3
|
+
|
|
4
|
+
import { invalidateConfigCache } from "../config/loader.js";
|
|
5
|
+
import { getModelProfiles } from "../plugin-api/index.js";
|
|
6
|
+
import { getWorkspaceConfigPath } from "../util/platform.js";
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* `getModelProfiles()` — the runtime handle a plugin (e.g. a model router) calls
|
|
10
|
+
* to learn which inference profiles a workspace defines. These tests pin its
|
|
11
|
+
* contract: presentation ordering (`profileOrder` then alphabetical tail), mix
|
|
12
|
+
* profiles included and flagged via `isMix`, disabled profiles included and
|
|
13
|
+
* flagged via `isDisabled`, and the per-field fallbacks (label → key,
|
|
14
|
+
* description → null, isActive → `llm.activeProfile`).
|
|
15
|
+
*/
|
|
16
|
+
|
|
17
|
+
function writeFixtureConfig(config: Record<string, unknown>): void {
|
|
18
|
+
writeFileSync(getWorkspaceConfigPath(), JSON.stringify(config), "utf-8");
|
|
19
|
+
invalidateConfigCache();
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
describe("getModelProfiles", () => {
|
|
23
|
+
afterEach(() => {
|
|
24
|
+
invalidateConfigCache();
|
|
25
|
+
});
|
|
26
|
+
|
|
27
|
+
test("orders profiles by profileOrder then the remaining keys alphabetically", () => {
|
|
28
|
+
// GIVEN a workspace whose profileOrder names some keys, with a duplicate
|
|
29
|
+
// ("balanced") and a key that resolves to no profile ("ghost").
|
|
30
|
+
writeFixtureConfig({
|
|
31
|
+
llm: {
|
|
32
|
+
profiles: { zeta: {}, alpha: {}, balanced: {}, "cost-optimized": {} },
|
|
33
|
+
profileOrder: ["balanced", "cost-optimized", "balanced", "ghost"],
|
|
34
|
+
},
|
|
35
|
+
});
|
|
36
|
+
// WHEN the profiles are listed.
|
|
37
|
+
const keys = getModelProfiles().map((p) => p.key);
|
|
38
|
+
// THEN profileOrder keys come first (deduped, existing-only), then the rest
|
|
39
|
+
// alphabetically.
|
|
40
|
+
expect(keys).toEqual(["balanced", "cost-optimized", "alpha", "zeta"]);
|
|
41
|
+
});
|
|
42
|
+
|
|
43
|
+
test("includes mix profiles and flags them via isMix", () => {
|
|
44
|
+
// GIVEN a workspace with a weighted mix profile alongside plain ones.
|
|
45
|
+
writeFixtureConfig({
|
|
46
|
+
llm: {
|
|
47
|
+
profiles: {
|
|
48
|
+
alpha: {},
|
|
49
|
+
beta: {},
|
|
50
|
+
blend: {
|
|
51
|
+
mix: [
|
|
52
|
+
{ profile: "alpha", weight: 1 },
|
|
53
|
+
{ profile: "beta", weight: 1 },
|
|
54
|
+
],
|
|
55
|
+
},
|
|
56
|
+
},
|
|
57
|
+
profileOrder: ["alpha", "beta", "blend"],
|
|
58
|
+
},
|
|
59
|
+
});
|
|
60
|
+
// WHEN the profiles are listed.
|
|
61
|
+
const flags = getModelProfiles().map((p) => [p.key, p.isMix]);
|
|
62
|
+
// THEN the mix profile is present and flagged isMix, while plain profiles are not.
|
|
63
|
+
expect(flags).toEqual([
|
|
64
|
+
["alpha", false],
|
|
65
|
+
["beta", false],
|
|
66
|
+
["blend", true],
|
|
67
|
+
]);
|
|
68
|
+
});
|
|
69
|
+
|
|
70
|
+
test("marks an active mix profile via isActive", () => {
|
|
71
|
+
// GIVEN a workspace whose activeProfile is itself a weighted mix.
|
|
72
|
+
writeFixtureConfig({
|
|
73
|
+
llm: {
|
|
74
|
+
profiles: {
|
|
75
|
+
alpha: {},
|
|
76
|
+
blend: {
|
|
77
|
+
mix: [
|
|
78
|
+
{ profile: "alpha", weight: 1 },
|
|
79
|
+
{ profile: "beta", weight: 1 },
|
|
80
|
+
],
|
|
81
|
+
},
|
|
82
|
+
beta: {},
|
|
83
|
+
},
|
|
84
|
+
profileOrder: ["alpha", "blend", "beta"],
|
|
85
|
+
activeProfile: "blend",
|
|
86
|
+
},
|
|
87
|
+
});
|
|
88
|
+
// WHEN the profiles are listed.
|
|
89
|
+
const active = getModelProfiles()
|
|
90
|
+
.filter((p) => p.isActive)
|
|
91
|
+
.map((p) => p.key);
|
|
92
|
+
// THEN the active mix profile is the one flagged isActive.
|
|
93
|
+
expect(active).toEqual(["blend"]);
|
|
94
|
+
});
|
|
95
|
+
|
|
96
|
+
test("includes disabled profiles and flags them via isDisabled", () => {
|
|
97
|
+
// GIVEN a workspace with one active and one disabled profile.
|
|
98
|
+
writeFixtureConfig({
|
|
99
|
+
llm: {
|
|
100
|
+
profiles: {
|
|
101
|
+
alpha: { status: "active" },
|
|
102
|
+
beta: { status: "disabled" },
|
|
103
|
+
},
|
|
104
|
+
profileOrder: ["alpha", "beta"],
|
|
105
|
+
},
|
|
106
|
+
});
|
|
107
|
+
// WHEN the profiles are listed.
|
|
108
|
+
const flags = getModelProfiles().map((p) => [p.key, p.isDisabled]);
|
|
109
|
+
// THEN the disabled profile is present and marked isDisabled.
|
|
110
|
+
expect(flags).toEqual([
|
|
111
|
+
["alpha", false],
|
|
112
|
+
["beta", true],
|
|
113
|
+
]);
|
|
114
|
+
});
|
|
115
|
+
|
|
116
|
+
test("falls back to the key when a profile has no label", () => {
|
|
117
|
+
// GIVEN a workspace where one profile sets a label and one does not.
|
|
118
|
+
writeFixtureConfig({
|
|
119
|
+
llm: {
|
|
120
|
+
profiles: { balanced: { label: "Balanced" }, terse: {} },
|
|
121
|
+
profileOrder: ["balanced", "terse"],
|
|
122
|
+
},
|
|
123
|
+
});
|
|
124
|
+
// WHEN the profiles are listed.
|
|
125
|
+
const byKey = Object.fromEntries(
|
|
126
|
+
getModelProfiles().map((p) => [p.key, p.label]),
|
|
127
|
+
);
|
|
128
|
+
// THEN the unlabeled profile's label falls back to its key.
|
|
129
|
+
expect(byKey.balanced).toBe("Balanced");
|
|
130
|
+
expect(byKey.terse).toBe("terse");
|
|
131
|
+
});
|
|
132
|
+
|
|
133
|
+
test("marks the workspace active profile via isActive", () => {
|
|
134
|
+
// GIVEN a workspace whose activeProfile is "beta".
|
|
135
|
+
writeFixtureConfig({
|
|
136
|
+
llm: {
|
|
137
|
+
profiles: { alpha: {}, beta: {} },
|
|
138
|
+
profileOrder: ["alpha", "beta"],
|
|
139
|
+
activeProfile: "beta",
|
|
140
|
+
},
|
|
141
|
+
});
|
|
142
|
+
// WHEN the profiles are listed.
|
|
143
|
+
const flags = getModelProfiles().map((p) => [p.key, p.isActive]);
|
|
144
|
+
// THEN only the active profile is marked isActive.
|
|
145
|
+
expect(flags).toEqual([
|
|
146
|
+
["alpha", false],
|
|
147
|
+
["beta", true],
|
|
148
|
+
]);
|
|
149
|
+
});
|
|
150
|
+
|
|
151
|
+
test("reports description as null when a profile sets none", () => {
|
|
152
|
+
// GIVEN a workspace where one profile has a description and one does not.
|
|
153
|
+
writeFixtureConfig({
|
|
154
|
+
llm: {
|
|
155
|
+
profiles: {
|
|
156
|
+
documented: { description: "Cheaper models, slower" },
|
|
157
|
+
bare: {},
|
|
158
|
+
},
|
|
159
|
+
profileOrder: ["documented", "bare"],
|
|
160
|
+
},
|
|
161
|
+
});
|
|
162
|
+
// WHEN the profiles are listed.
|
|
163
|
+
const byKey = Object.fromEntries(
|
|
164
|
+
getModelProfiles().map((p) => [p.key, p.description]),
|
|
165
|
+
);
|
|
166
|
+
// THEN a missing description is reported as null.
|
|
167
|
+
expect(byKey.documented).toBe("Cheaper models, slower");
|
|
168
|
+
expect(byKey.bare).toBeNull();
|
|
169
|
+
});
|
|
170
|
+
|
|
171
|
+
test("returns an empty list when the workspace defines no profiles", () => {
|
|
172
|
+
// GIVEN a workspace with no profiles defined.
|
|
173
|
+
writeFixtureConfig({ llm: { profiles: {}, profileOrder: [] } });
|
|
174
|
+
// WHEN the profiles are listed.
|
|
175
|
+
// THEN the result is empty.
|
|
176
|
+
expect(getModelProfiles()).toEqual([]);
|
|
177
|
+
});
|
|
178
|
+
});
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `getConfiguredProvider` is part of the public `@vellumai/plugin-api` runtime
|
|
3
|
+
* surface, so a user-installable plugin can run inference through the
|
|
4
|
+
* workspace's configured profiles/credentials (managed-proxy or BYOK) without
|
|
5
|
+
* supplying its own API key.
|
|
6
|
+
*/
|
|
7
|
+
import { describe, expect, test } from "bun:test";
|
|
8
|
+
|
|
9
|
+
import { PLUGIN_API_EXPORTS } from "../embedded/plugin-api.js";
|
|
10
|
+
import * as pluginApi from "../plugin-api/index.js";
|
|
11
|
+
|
|
12
|
+
describe("plugin-api provider access", () => {
|
|
13
|
+
test("getConfiguredProvider is exported as a runtime value", () => {
|
|
14
|
+
expect(typeof pluginApi.getConfiguredProvider).toBe("function");
|
|
15
|
+
});
|
|
16
|
+
|
|
17
|
+
test("getConfiguredProvider is in the shim-rebound runtime surface", () => {
|
|
18
|
+
// The boot-time shim re-binds every name in PLUGIN_API_EXPORTS from the
|
|
19
|
+
// globalThis-parked namespace, so a plugin importing the bare specifier
|
|
20
|
+
// gets the assistant's real resolver (bound to its initialized provider
|
|
21
|
+
// registry), not a disjoint, uninitialized module copy.
|
|
22
|
+
expect(PLUGIN_API_EXPORTS).toContain("getConfiguredProvider");
|
|
23
|
+
});
|
|
24
|
+
});
|
|
@@ -190,13 +190,16 @@ describe("plugin tool contributions", () => {
|
|
|
190
190
|
registerPlugin(plugin);
|
|
191
191
|
|
|
192
192
|
await bootstrapPlugins();
|
|
193
|
-
//
|
|
194
|
-
|
|
193
|
+
// `bootstrapPlugins` also registers the first-party defaults (the advisor
|
|
194
|
+
// default contributes the `advisor` tool), so the global tool set is not
|
|
195
|
+
// empty. What matters here is that the no-tools plugin contributed nothing
|
|
196
|
+
// of its own — its tool refcount stays at zero.
|
|
197
|
+
expect(getPluginRefCount("no-tools")).toBe(0);
|
|
195
198
|
|
|
196
199
|
// Shutdown must also be safe — `unregisterPluginTools` is idempotent for
|
|
197
200
|
// plugins that never contributed any tools.
|
|
198
201
|
await runShutdownHooks("test-shutdown");
|
|
199
|
-
expect(
|
|
202
|
+
expect(getPluginRefCount("no-tools")).toBe(0);
|
|
200
203
|
});
|
|
201
204
|
|
|
202
205
|
test("tools declared before init() runs are only visible after bootstrap", async () => {
|
|
@@ -0,0 +1,214 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Post-compaction re-injection idempotency.
|
|
3
|
+
*
|
|
4
|
+
* The agent loop hands the post-compaction hook the full injected continuation
|
|
5
|
+
* history (it does not pre-strip it), so the hook must clear the tail's stale
|
|
6
|
+
* per-turn injection blocks before re-applying them. Without that strip,
|
|
7
|
+
* `applyRuntimeInjections` double-stacks every non-presence-gated block —
|
|
8
|
+
* `<turn_context>`, `<config_reset_notice>`, `<active_documents>`,
|
|
9
|
+
* `<document_comments>` — because it appends to the tail without removing an
|
|
10
|
+
* existing copy. These tests cover the strip primitive directly and the
|
|
11
|
+
* end-to-end re-injection it protects.
|
|
12
|
+
*/
|
|
13
|
+
import { afterEach, describe, expect, test } from "bun:test";
|
|
14
|
+
|
|
15
|
+
import {
|
|
16
|
+
clearConversations,
|
|
17
|
+
setConversation,
|
|
18
|
+
} from "../daemon/conversation-registry.js";
|
|
19
|
+
import { applyRuntimeInjections } from "../daemon/conversation-runtime-assembly.js";
|
|
20
|
+
import { stripTailInjectionsForReinjection } from "../plugins/defaults/memory-retrieval/tail-reinjection-strip.js";
|
|
21
|
+
import type { Message } from "../providers/types.js";
|
|
22
|
+
|
|
23
|
+
const WORKSPACE_BLOCK = "<workspace>\nRoot: /sandbox\n</workspace>";
|
|
24
|
+
const INFO_BLOCK = "<info>\nRemembered fact about project-x\n</info>";
|
|
25
|
+
const TURN_CONTEXT_BLOCK =
|
|
26
|
+
"<turn_context>\ncurrent_time: noon\n</turn_context>";
|
|
27
|
+
const CONFIG_RESET_BLOCK =
|
|
28
|
+
"<config_reset_notice>\nSettings were reset.\n</config_reset_notice>";
|
|
29
|
+
const ACTIVE_DOCUMENTS_BLOCK =
|
|
30
|
+
"<active_documents>\nThe following documents are open: notes.md\n</active_documents>";
|
|
31
|
+
const DOCUMENT_COMMENTS_BLOCK =
|
|
32
|
+
"<document_comments>\nOpen comments on notes.md\n</document_comments>";
|
|
33
|
+
const TURN_BODY = "Please continue the task.";
|
|
34
|
+
|
|
35
|
+
function userMsg(...texts: string[]): Message {
|
|
36
|
+
return {
|
|
37
|
+
role: "user",
|
|
38
|
+
content: texts.map((text) => ({ type: "text" as const, text })),
|
|
39
|
+
};
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
function assistantMsg(text: string): Message {
|
|
43
|
+
return { role: "assistant", content: [{ type: "text", text }] };
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
function tailContentTexts(messages: Message[]): string[] {
|
|
47
|
+
const last = messages[messages.length - 1];
|
|
48
|
+
return last.content.map((block) => (block.type === "text" ? block.text : ""));
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
function countTailBlocksWithPrefix(
|
|
52
|
+
messages: Message[],
|
|
53
|
+
prefix: string,
|
|
54
|
+
): number {
|
|
55
|
+
return tailContentTexts(messages).filter((text) => text.startsWith(prefix))
|
|
56
|
+
.length;
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
describe("stripTailInjectionsForReinjection", () => {
|
|
60
|
+
test("clears every per-turn injection block from the tail user message", () => {
|
|
61
|
+
// GIVEN a tail user message carrying the real turn body plus the full
|
|
62
|
+
// per-turn injection set, including the four blocks compaction keeps in
|
|
63
|
+
// durable history
|
|
64
|
+
const messages: Message[] = [
|
|
65
|
+
userMsg(
|
|
66
|
+
TURN_BODY,
|
|
67
|
+
WORKSPACE_BLOCK,
|
|
68
|
+
INFO_BLOCK,
|
|
69
|
+
TURN_CONTEXT_BLOCK,
|
|
70
|
+
CONFIG_RESET_BLOCK,
|
|
71
|
+
ACTIVE_DOCUMENTS_BLOCK,
|
|
72
|
+
DOCUMENT_COMMENTS_BLOCK,
|
|
73
|
+
),
|
|
74
|
+
];
|
|
75
|
+
|
|
76
|
+
// WHEN the tail injections are stripped for re-injection
|
|
77
|
+
const result = stripTailInjectionsForReinjection(messages);
|
|
78
|
+
|
|
79
|
+
// THEN only the real turn body survives on the tail
|
|
80
|
+
expect(tailContentTexts(result)).toEqual([TURN_BODY]);
|
|
81
|
+
});
|
|
82
|
+
|
|
83
|
+
test("leaves injection blocks on earlier messages untouched", () => {
|
|
84
|
+
// GIVEN an earlier user message and the tail both carrying a turn-context
|
|
85
|
+
// block
|
|
86
|
+
const messages: Message[] = [
|
|
87
|
+
userMsg("Earlier turn", TURN_CONTEXT_BLOCK),
|
|
88
|
+
assistantMsg("Working on it."),
|
|
89
|
+
userMsg(TURN_BODY, TURN_CONTEXT_BLOCK),
|
|
90
|
+
];
|
|
91
|
+
|
|
92
|
+
// WHEN the tail injections are stripped
|
|
93
|
+
const result = stripTailInjectionsForReinjection(messages);
|
|
94
|
+
|
|
95
|
+
// THEN the earlier message keeps its historical turn-context grounding
|
|
96
|
+
expect(result[0].content).toHaveLength(2);
|
|
97
|
+
expect((result[0].content[1] as { text: string }).text).toBe(
|
|
98
|
+
TURN_CONTEXT_BLOCK,
|
|
99
|
+
);
|
|
100
|
+
// AND only the tail is cleared
|
|
101
|
+
expect(tailContentTexts(result)).toEqual([TURN_BODY]);
|
|
102
|
+
});
|
|
103
|
+
|
|
104
|
+
test("returns the messages unchanged when the tail carries no injections", () => {
|
|
105
|
+
// GIVEN a tail with only real user content
|
|
106
|
+
const messages: Message[] = [userMsg(TURN_BODY)];
|
|
107
|
+
|
|
108
|
+
// WHEN the tail injections are stripped
|
|
109
|
+
const result = stripTailInjectionsForReinjection(messages);
|
|
110
|
+
|
|
111
|
+
// THEN the array is returned unchanged
|
|
112
|
+
expect(result).toBe(messages);
|
|
113
|
+
});
|
|
114
|
+
|
|
115
|
+
test("preserves the tail user message even when every block is stripped", () => {
|
|
116
|
+
// GIVEN a tail composed entirely of injection blocks
|
|
117
|
+
const messages: Message[] = [
|
|
118
|
+
assistantMsg("Earlier reply."),
|
|
119
|
+
userMsg(WORKSPACE_BLOCK, TURN_CONTEXT_BLOCK),
|
|
120
|
+
];
|
|
121
|
+
|
|
122
|
+
// WHEN the tail injections are stripped
|
|
123
|
+
const result = stripTailInjectionsForReinjection(messages);
|
|
124
|
+
|
|
125
|
+
// THEN the tail user message remains so the re-injection tail invariant
|
|
126
|
+
// holds, just with empty content
|
|
127
|
+
expect(result).toHaveLength(2);
|
|
128
|
+
expect(result[1].role).toBe("user");
|
|
129
|
+
expect(result[1].content).toHaveLength(0);
|
|
130
|
+
});
|
|
131
|
+
|
|
132
|
+
test("is idempotent — stripping an already-stripped tail is a no-op", () => {
|
|
133
|
+
// GIVEN a tail that has already been stripped once
|
|
134
|
+
const messages: Message[] = [userMsg(TURN_BODY, TURN_CONTEXT_BLOCK)];
|
|
135
|
+
const once = stripTailInjectionsForReinjection(messages);
|
|
136
|
+
|
|
137
|
+
// WHEN it is stripped again
|
|
138
|
+
const twice = stripTailInjectionsForReinjection(once);
|
|
139
|
+
|
|
140
|
+
// THEN the second strip changes nothing
|
|
141
|
+
expect(twice).toBe(once);
|
|
142
|
+
expect(tailContentTexts(twice)).toEqual([TURN_BODY]);
|
|
143
|
+
});
|
|
144
|
+
});
|
|
145
|
+
|
|
146
|
+
describe("applyRuntimeInjections re-injection idempotency", () => {
|
|
147
|
+
// `applyRuntimeInjections` synthesizes this conversation id when no
|
|
148
|
+
// turnContext is supplied, so the injectors resolve their blocks from the
|
|
149
|
+
// registry under this key.
|
|
150
|
+
const FALLBACK_CONVERSATION_ID = "runtime-assembly-fallback";
|
|
151
|
+
|
|
152
|
+
// Seed the fallback conversation with a workspace block (presence-gated) and
|
|
153
|
+
// a frozen temporal snapshot so the non-presence-gated `<turn_context>` block
|
|
154
|
+
// is produced every assembly.
|
|
155
|
+
function seedConversation(): void {
|
|
156
|
+
setConversation(FALLBACK_CONVERSATION_ID, {
|
|
157
|
+
conversationId: FALLBACK_CONVERSATION_ID,
|
|
158
|
+
workingDir: "/sandbox",
|
|
159
|
+
workspaceTopLevelContext: WORKSPACE_BLOCK,
|
|
160
|
+
workspaceTopLevelDirty: false,
|
|
161
|
+
currentTurnTemporalSnapshot: { clientTimezone: null },
|
|
162
|
+
} as never);
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
afterEach(() => {
|
|
166
|
+
clearConversations();
|
|
167
|
+
});
|
|
168
|
+
|
|
169
|
+
test("re-injecting an already-injected base without the strip double-stacks the non-presence-gated block", async () => {
|
|
170
|
+
// GIVEN a fresh turn assembled once, so its tail carries one workspace and
|
|
171
|
+
// one turn-context block
|
|
172
|
+
seedConversation();
|
|
173
|
+
const { messages: injectedOnce } = await applyRuntimeInjections(
|
|
174
|
+
[userMsg(TURN_BODY)],
|
|
175
|
+
{ conversationId: FALLBACK_CONVERSATION_ID },
|
|
176
|
+
);
|
|
177
|
+
expect(countTailBlocksWithPrefix(injectedOnce, "<turn_context>")).toBe(1);
|
|
178
|
+
|
|
179
|
+
// WHEN injections are applied again to that already-injected base
|
|
180
|
+
const { messages: injectedTwice } = await applyRuntimeInjections(
|
|
181
|
+
injectedOnce,
|
|
182
|
+
{ conversationId: FALLBACK_CONVERSATION_ID },
|
|
183
|
+
);
|
|
184
|
+
|
|
185
|
+
// THEN the non-presence-gated turn-context block double-stacks
|
|
186
|
+
expect(countTailBlocksWithPrefix(injectedTwice, "<turn_context>")).toBe(2);
|
|
187
|
+
// AND the presence-gated workspace block stays single
|
|
188
|
+
expect(countTailBlocksWithPrefix(injectedTwice, "<workspace>")).toBe(1);
|
|
189
|
+
});
|
|
190
|
+
|
|
191
|
+
test("stripping the tail before re-injecting keeps every block single", async () => {
|
|
192
|
+
// GIVEN a fresh turn assembled once
|
|
193
|
+
seedConversation();
|
|
194
|
+
const { messages: injectedOnce } = await applyRuntimeInjections(
|
|
195
|
+
[userMsg(TURN_BODY)],
|
|
196
|
+
{ conversationId: FALLBACK_CONVERSATION_ID },
|
|
197
|
+
);
|
|
198
|
+
|
|
199
|
+
// WHEN the tail is stripped before re-injection, exactly as the
|
|
200
|
+
// post-compaction hook does
|
|
201
|
+
const { messages: reinjected } = await applyRuntimeInjections(
|
|
202
|
+
stripTailInjectionsForReinjection(injectedOnce),
|
|
203
|
+
{ conversationId: FALLBACK_CONVERSATION_ID },
|
|
204
|
+
);
|
|
205
|
+
|
|
206
|
+
// THEN both the non-presence-gated and presence-gated blocks stay single
|
|
207
|
+
expect(countTailBlocksWithPrefix(reinjected, "<turn_context>")).toBe(1);
|
|
208
|
+
expect(countTailBlocksWithPrefix(reinjected, "<workspace>")).toBe(1);
|
|
209
|
+
// AND the real turn body survives
|
|
210
|
+
expect(
|
|
211
|
+
tailContentTexts(reinjected).some((text) => text === TURN_BODY),
|
|
212
|
+
).toBe(true);
|
|
213
|
+
});
|
|
214
|
+
});
|
|
@@ -261,3 +261,79 @@ describe("SendMessageOptions.config.overrideProfile", () => {
|
|
|
261
261
|
expect(captured?.overrideProfile).toBeUndefined();
|
|
262
262
|
});
|
|
263
263
|
});
|
|
264
|
+
|
|
265
|
+
describe("SendMessageOptions.config.forceOverrideProfile", () => {
|
|
266
|
+
test("CallSiteConfiguredProvider forwards forceOverrideProfile into the send config", async () => {
|
|
267
|
+
let captured: SendMessageOptions | undefined;
|
|
268
|
+
const inner: Provider = {
|
|
269
|
+
name: "anthropic",
|
|
270
|
+
async sendMessage(
|
|
271
|
+
_messages: Message[],
|
|
272
|
+
options?: SendMessageOptions,
|
|
273
|
+
): Promise<ProviderResponse> {
|
|
274
|
+
captured = options;
|
|
275
|
+
return makeResponse("anthropic");
|
|
276
|
+
},
|
|
277
|
+
};
|
|
278
|
+
|
|
279
|
+
const provider = new CallSiteConfiguredProvider(
|
|
280
|
+
inner,
|
|
281
|
+
"inference",
|
|
282
|
+
"strong",
|
|
283
|
+
true,
|
|
284
|
+
);
|
|
285
|
+
await provider.sendMessage(DUMMY_MESSAGES, {});
|
|
286
|
+
|
|
287
|
+
expect(captured?.config).toMatchObject({
|
|
288
|
+
callSite: "inference",
|
|
289
|
+
overrideProfile: "strong",
|
|
290
|
+
forceOverrideProfile: true,
|
|
291
|
+
});
|
|
292
|
+
});
|
|
293
|
+
|
|
294
|
+
test("forceOverrideProfile floats the override above a call-site profile pin", async () => {
|
|
295
|
+
// The advisor scenario in miniature: the `inference` call site is pinned to
|
|
296
|
+
// a cheap profile, but a caller forces a stronger profile for its own send.
|
|
297
|
+
setLlmConfig({
|
|
298
|
+
default: { provider: "anthropic", model: "claude-opus-4-7" },
|
|
299
|
+
profiles: {
|
|
300
|
+
cheap: { provider: "anthropic", model: "claude-haiku-4-5-20251001" },
|
|
301
|
+
strong: { provider: "anthropic", model: "claude-opus-4-8" },
|
|
302
|
+
},
|
|
303
|
+
callSites: { inference: { profile: "cheap" } },
|
|
304
|
+
});
|
|
305
|
+
|
|
306
|
+
const send = async (force: boolean) => {
|
|
307
|
+
let captured: Record<string, unknown> | undefined;
|
|
308
|
+
const inner: Provider = {
|
|
309
|
+
name: "anthropic",
|
|
310
|
+
async sendMessage(
|
|
311
|
+
_messages: Message[],
|
|
312
|
+
options?: SendMessageOptions,
|
|
313
|
+
): Promise<ProviderResponse> {
|
|
314
|
+
captured = options?.config as Record<string, unknown> | undefined;
|
|
315
|
+
return makeResponse("anthropic");
|
|
316
|
+
},
|
|
317
|
+
};
|
|
318
|
+
const provider = new RetryProvider(inner);
|
|
319
|
+
await provider.sendMessage(DUMMY_MESSAGES, {
|
|
320
|
+
config: {
|
|
321
|
+
callSite: "inference",
|
|
322
|
+
overrideProfile: "strong",
|
|
323
|
+
...(force ? { forceOverrideProfile: true } : {}),
|
|
324
|
+
},
|
|
325
|
+
});
|
|
326
|
+
return captured;
|
|
327
|
+
};
|
|
328
|
+
|
|
329
|
+
// Without the flag, the call-site pin (`cheap`) outranks `overrideProfile`.
|
|
330
|
+
expect((await send(false))?.model).toBe("claude-haiku-4-5-20251001");
|
|
331
|
+
|
|
332
|
+
// With the flag, the forced `strong` profile wins over the call-site pin.
|
|
333
|
+
const forced = await send(true);
|
|
334
|
+
expect(forced?.model).toBe("claude-opus-4-8");
|
|
335
|
+
// The routing keys are stripped before the provider wire request.
|
|
336
|
+
expect(forced?.overrideProfile).toBeUndefined();
|
|
337
|
+
expect(forced?.forceOverrideProfile).toBeUndefined();
|
|
338
|
+
});
|
|
339
|
+
});
|