@vellumai/assistant 0.10.1 → 0.10.2-dev.202606241651.2d2b40d
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/docs/workspace-tools.md +42 -33
- package/eslint-rules/cli-no-daemon-internals.js +6 -0
- package/node_modules/@vellumai/gateway-client/src/__tests__/guardian-delivery-contract.test.ts +91 -0
- package/node_modules/@vellumai/gateway-client/src/__tests__/trust-verdict-contract.test.ts +31 -0
- package/node_modules/@vellumai/gateway-client/src/guardian-delivery-contract.ts +48 -0
- package/node_modules/@vellumai/gateway-client/src/index.ts +14 -0
- package/node_modules/@vellumai/gateway-client/src/trust-verdict-contract.ts +17 -0
- package/openapi.yaml +74 -1
- package/package.json +1 -1
- package/scripts/test.sh +36 -15
- package/src/__tests__/actor-token-service.test.ts +36 -14
- package/src/__tests__/agent-loop-override-profile.test.ts +1 -0
- package/src/__tests__/agent-wake-disk-pressure-callsite.test.ts +2 -0
- package/src/__tests__/agent-wake-override-profile.test.ts +2 -0
- package/src/__tests__/annotate-activity-metadata.test.ts +2 -0
- package/src/__tests__/annotate-risk-options.test.ts +2 -0
- package/src/__tests__/approval-cascade.test.ts +2 -0
- package/src/__tests__/background-workers-disk-pressure.test.ts +2 -0
- package/src/__tests__/btw-routes.test.ts +2 -0
- package/src/__tests__/build-persisted-content.test.ts +2 -0
- package/src/__tests__/call-controller.test.ts +19 -0
- package/src/__tests__/channel-guardian.test.ts +94 -58
- package/src/__tests__/channel-reply-delivery.test.ts +2 -0
- package/src/__tests__/compaction-events.test.ts +2 -0
- package/src/__tests__/compaction.benchmark.test.ts +2 -0
- package/src/__tests__/compactor-call-site-logging.test.ts +2 -0
- package/src/__tests__/compactor-low-watermark-cut.test.ts +2 -0
- package/src/__tests__/compactor-preserved-tail-count.test.ts +2 -0
- package/src/__tests__/compactor-summary-call-truncation.test.ts +2 -0
- package/src/__tests__/compactor-web-search-strip.test.ts +2 -0
- package/src/__tests__/computer-use-tools.test.ts +13 -0
- package/src/__tests__/config-loader-backfill.test.ts +5 -1
- package/src/__tests__/config-schema.test.ts +1 -0
- package/src/__tests__/confirmation-request-guardian-bridge.test.ts +31 -29
- package/src/__tests__/contacts-relay-reads.test.ts +13 -15
- package/src/__tests__/conversation-abort-tool-results.test.ts +2 -0
- package/src/__tests__/conversation-agent-loop-disk-pressure.test.ts +2 -0
- package/src/__tests__/conversation-agent-loop-inference-profile.test.ts +2 -0
- package/src/__tests__/conversation-agent-loop-overflow.test.ts +2 -0
- package/src/__tests__/conversation-agent-loop.test.ts +7 -0
- package/src/__tests__/conversation-analysis-routes.test.ts +2 -0
- package/src/__tests__/conversation-app-control-lifecycle.test.ts +2 -0
- package/src/__tests__/conversation-confirmation-signals.test.ts +2 -0
- package/src/__tests__/conversation-history-web-search.test.ts +2 -0
- package/src/__tests__/conversation-load-history-repair.test.ts +2 -0
- package/src/__tests__/conversation-load-history-stripped.test.ts +2 -0
- package/src/__tests__/conversation-pairing.test.ts +2 -0
- package/src/__tests__/conversation-process-app-control-preactivation.test.ts +2 -0
- package/src/__tests__/conversation-process-callsite.test.ts +2 -0
- package/src/__tests__/conversation-provider-retry-repair.test.ts +2 -0
- package/src/__tests__/conversation-queue.test.ts +91 -0
- package/src/__tests__/conversation-routes-guardian-reply.test.ts +14 -0
- package/src/__tests__/conversation-routes-slash-commands.test.ts +14 -0
- package/src/__tests__/conversation-slash-queue.test.ts +2 -0
- package/src/__tests__/conversation-slash-unknown.test.ts +2 -0
- package/src/__tests__/conversation-speed-override.test.ts +2 -0
- package/src/__tests__/conversation-surfaces-action-delivery.test.ts +65 -0
- package/src/__tests__/conversation-title-service.test.ts +2 -0
- package/src/__tests__/conversation-tool-setup-attribution.test.ts +47 -0
- package/src/__tests__/conversation-usage.test.ts +2 -0
- package/src/__tests__/conversation-workspace-cache-state.test.ts +2 -0
- package/src/__tests__/conversation-workspace-injection.test.ts +2 -0
- package/src/__tests__/conversation-workspace-tool-tracking.test.ts +2 -0
- package/src/__tests__/credential-security-invariants.test.ts +0 -1
- package/src/__tests__/db-migration-rollback.test.ts +205 -171
- package/src/__tests__/db-test-helpers.ts +5 -4
- package/src/__tests__/deterministic-verification-control-plane.test.ts +4 -2
- package/src/__tests__/disk-pressure-guard.test.ts +41 -0
- package/src/__tests__/dm-persistence.test.ts +2 -0
- package/src/__tests__/emit-signal-routing-intent.test.ts +10 -5
- package/src/__tests__/events-dev-bypass-actor.test.ts +7 -1
- package/src/__tests__/filing-service.test.ts +2 -0
- package/src/__tests__/guardian-binding-drift-heal.test.ts +75 -10
- package/src/__tests__/guardian-dispatch.test.ts +95 -1
- package/src/__tests__/guardian-outbound-http.test.ts +13 -0
- package/src/__tests__/heartbeat-disk-pressure.test.ts +2 -0
- package/src/__tests__/heartbeat-service.test.ts +2 -0
- package/src/__tests__/helpers/channel-test-adapter.ts +1 -7
- package/src/__tests__/host-app-control-routes.test.ts +24 -30
- package/src/__tests__/host-bash-routes.test.ts +31 -41
- package/src/__tests__/host-browser-routes.test.ts +26 -32
- package/src/__tests__/host-cu-proxy.test.ts +299 -0
- package/src/__tests__/host-cu-routes-targeted.test.ts +25 -33
- package/src/__tests__/host-file-routes-targeted.test.ts +40 -52
- package/src/__tests__/host-transfer-routes-targeted.test.ts +31 -43
- package/src/__tests__/http-user-message-parity.test.ts +167 -8
- package/src/__tests__/inbound-slack-persistence.test.ts +2 -0
- package/src/__tests__/invite-redemption-service.test.ts +43 -0
- package/src/__tests__/llm-context-normalization.test.ts +105 -0
- package/src/__tests__/llm-usage-store.test.ts +25 -0
- package/src/__tests__/media-stream-server-integration.test.ts +127 -0
- package/src/__tests__/memory-retrieval-hook.test.ts +2 -0
- package/src/__tests__/messaging-send-tool.test.ts +2 -0
- package/src/__tests__/migration-import-from-url.test.ts +2 -2
- package/src/__tests__/native-web-search.test.ts +2 -0
- package/src/__tests__/non-member-access-request.test.ts +189 -17
- package/src/__tests__/notification-broadcaster.test.ts +4 -0
- package/src/__tests__/notification-decision-recipient-context.test.ts +33 -32
- package/src/__tests__/notification-deep-link.test.ts +6 -0
- package/src/__tests__/notification-guardian-path.test.ts +19 -0
- package/src/__tests__/outbound-slack-persistence.test.ts +2 -0
- package/src/__tests__/pending-interactions-resolved-event.test.ts +7 -4
- package/src/__tests__/persistence-secret-redaction.test.ts +2 -0
- package/src/__tests__/plugin-bootstrap.test.ts +3 -73
- package/src/__tests__/plugin-route-contribution.test.ts +4 -17
- package/src/__tests__/plugin-tool-contribution.test.ts +3 -18
- package/src/__tests__/plugin-types.test.ts +0 -2
- package/src/__tests__/process-message-background-slack.test.ts +2 -0
- package/src/__tests__/process-message-display-content.test.ts +2 -0
- package/src/__tests__/provider-usage-tracking.test.ts +39 -0
- package/src/__tests__/regenerate-fire-and-forget-trace.test.ts +2 -0
- package/src/__tests__/registry.test.ts +3 -0
- package/src/__tests__/relay-server.test.ts +694 -25
- package/src/__tests__/runtime-attachment-metadata.test.ts +0 -1
- package/src/__tests__/secret-ingress-http.test.ts +14 -0
- package/src/__tests__/send-endpoint-busy.test.ts +30 -8
- package/src/__tests__/skills.test.ts +44 -0
- package/src/__tests__/slack-inbound-verification.test.ts +47 -2
- package/src/__tests__/sse-actor-principal-guardian-source.test.ts +102 -0
- package/src/__tests__/steer-on-enqueue-question.test.ts +181 -0
- package/src/__tests__/stt-hints.test.ts +44 -13
- package/src/__tests__/subagent-detail.test.ts +27 -0
- package/src/__tests__/subagent-disposal.test.ts +65 -0
- package/src/__tests__/subagent-notify-parent.test.ts +2 -0
- package/src/__tests__/subagent-spawn-tool-fork.test.ts +2 -0
- package/src/__tests__/subagent-tools.test.ts +2 -0
- package/src/__tests__/suggestion-routes.test.ts +2 -0
- package/src/__tests__/title-generate-hook.test.ts +2 -0
- package/src/__tests__/tool-executor-lifecycle-events.test.ts +2 -0
- package/src/__tests__/tool-executor.test.ts +16 -11
- package/src/__tests__/tool-preview-lifecycle.test.ts +2 -0
- package/src/__tests__/tool-result-metadata-plumbing.test.ts +2 -0
- package/src/__tests__/tool-start-timestamp.test.ts +2 -0
- package/src/__tests__/trusted-contact-inline-approval-integration.test.ts +10 -10
- package/src/__tests__/twilio-routes.test.ts +96 -0
- package/src/__tests__/verification-control-plane-policy.test.ts +2 -0
- package/src/__tests__/web-search-backend-failure.test.ts +2 -0
- package/src/__tests__/workspace-tool-loader.test.ts +195 -2
- package/src/agent/loop-exclusive-tool.test.ts +150 -0
- package/src/agent/loop.ts +56 -0
- package/src/api/constants/sse-replay.ts +41 -0
- package/src/api/index.ts +6 -0
- package/src/api/responses/llm-request-log-entry.ts +25 -0
- package/src/api/responses/subagent-detail.ts +17 -0
- package/src/calls/__tests__/relay-setup-router.test.ts +262 -4
- package/src/calls/call-domain.ts +3 -3
- package/src/calls/guardian-dispatch.ts +10 -8
- package/src/calls/inbound-trust-reader.ts +17 -1
- package/src/calls/media-stream-server.ts +21 -0
- package/src/calls/relay-server.ts +167 -50
- package/src/calls/relay-setup-router.ts +37 -7
- package/src/calls/relay-verification.ts +4 -4
- package/src/calls/stt-hints.ts +9 -12
- package/src/calls/twilio-routes.ts +14 -4
- package/src/cli/commands/__tests__/cache.test.ts +8 -1
- package/src/cli/commands/cache.ts +194 -181
- package/src/cli/commands/db/__tests__/repair.test.ts +6 -5
- package/src/cli/commands/db/status.ts +37 -1
- package/src/cli/commands/mcp.ts +252 -218
- package/src/cli/commands/memory/__tests__/worker.test.ts +302 -0
- package/src/cli/commands/memory/index.ts +2 -0
- package/src/cli/commands/memory/worker.ts +175 -0
- package/src/cli/commands/plugins.ts +75 -3
- package/src/cli/lib/__tests__/install-from-github.test.ts +102 -0
- package/src/cli/lib/__tests__/list-installed-plugins.test.ts +160 -1
- package/src/cli/lib/list-installed-plugins.ts +179 -1
- package/src/config/__tests__/loader-callsite-strip-fallback.test.ts +143 -0
- package/src/config/bundled-skills/computer-use/TOOLS.json +6 -1
- package/src/config/bundled-skills/contacts/tools/contact-merge.ts +27 -17
- package/src/config/bundled-skills/contacts/tools/contact-search.ts +13 -3
- package/src/config/feature-flag-registry.json +0 -8
- package/src/config/loader.ts +36 -5
- package/src/config/schemas/__tests__/memory-v3.test.ts +1 -0
- package/src/config/schemas/memory-lifecycle.ts +12 -0
- package/src/config/schemas/memory-v3.ts +7 -0
- package/src/config/schemas/memory.ts +4 -0
- package/src/config/schemas/timeouts.ts +8 -0
- package/src/config/seed-inference-profiles.ts +14 -5
- package/src/config/skills.ts +27 -5
- package/src/contacts/__tests__/guardian-delivery-reader.test.ts +312 -0
- package/src/contacts/contacts-write.ts +3 -0
- package/src/contacts/guardian-delivery-reader.ts +223 -0
- package/src/daemon/conversation-agent-loop.ts +9 -0
- package/src/daemon/conversation-process.ts +39 -17
- package/src/daemon/conversation-surfaces.ts +8 -0
- package/src/daemon/conversation-tool-setup.ts +49 -16
- package/src/daemon/conversation.ts +21 -2
- package/src/daemon/disk-pressure-guard.ts +12 -2
- package/src/daemon/event-loop-watchdog.ts +28 -1
- package/src/daemon/external-plugins-bootstrap.ts +4 -34
- package/src/daemon/handlers/__tests__/config-a2a-redeem.test.ts +25 -0
- package/src/daemon/handlers/__tests__/config-channels.test.ts +225 -0
- package/src/daemon/handlers/config-a2a.ts +6 -14
- package/src/daemon/handlers/config-channels.ts +78 -22
- package/src/daemon/handlers/conversations.ts +77 -0
- package/src/daemon/host-cu-proxy.ts +102 -11
- package/src/daemon/lifecycle.ts +4 -0
- package/src/daemon/memory-v2-startup.test.ts +72 -0
- package/src/daemon/memory-v2-startup.ts +87 -19
- package/src/daemon/server.ts +0 -4
- package/src/daemon/shutdown-handlers.ts +20 -0
- package/src/daemon/tool-setup-types.ts +9 -0
- package/src/ipc/__tests__/clients-list-ipc.test.ts +1 -1
- package/src/ipc/assistant-server.ts +2 -2
- package/src/memory/__tests__/301-create-watchdog-events.test.ts +110 -0
- package/src/memory/__tests__/memory-retrospective-job.test.ts +8 -0
- package/src/memory/__tests__/prompt-override.test.ts +192 -0
- package/src/memory/__tests__/watchdog-events-store.test.ts +161 -0
- package/src/memory/conversation-crud.ts +38 -0
- package/src/memory/db-connection.ts +22 -3
- package/src/memory/db-init.ts +36 -502
- package/src/memory/db-singleton.ts +6 -4
- package/src/memory/jobs-worker.ts +58 -0
- package/src/memory/llm-usage-store.ts +48 -20
- package/src/memory/memory-retrospective-job.ts +9 -8
- package/src/memory/migrations/014-backfill-inbox-thread-state.ts +13 -3
- package/src/memory/migrations/136-drop-assistant-id-columns.ts +52 -27
- package/src/memory/migrations/209-strip-thinking-from-consolidated.ts +130 -56
- package/src/memory/migrations/300-add-processing-started-at.ts +30 -0
- package/src/memory/migrations/301-create-watchdog-events.ts +45 -0
- package/src/memory/migrations/__tests__/014-backfill-inbox-thread-state.test.ts +108 -0
- package/src/memory/migrations/__tests__/136-drop-assistant-id-columns.test.ts +82 -0
- package/src/memory/migrations/__tests__/209-strip-thinking-from-consolidated.test.ts +224 -0
- package/src/memory/migrations/__tests__/run-migrations.test.ts +2 -2
- package/src/memory/migrations/run-migrations.ts +90 -6
- package/src/memory/migrations/schema-introspection.ts +14 -0
- package/src/memory/migrations/validate-migration-state.ts +101 -66
- package/src/memory/prompt-override.ts +129 -0
- package/src/memory/schema/conversations.ts +9 -0
- package/src/memory/schema/infrastructure.ts +20 -0
- package/src/memory/steps.ts +573 -0
- package/src/memory/v2/__tests__/cli-command-store.test.ts +25 -0
- package/src/memory/v2/__tests__/skill-store.test.ts +80 -0
- package/src/memory/v2/cli-command-store.ts +75 -38
- package/src/memory/v2/prompts/consolidation.ts +13 -82
- package/src/memory/v2/prompts/router.ts +21 -93
- package/src/memory/v2/skill-store.ts +68 -31
- package/src/memory/watchdog-events-store.ts +87 -0
- package/src/memory/worker-control.ts +118 -0
- package/src/memory/worker-process.ts +72 -0
- package/src/notifications/__tests__/broadcaster.test.ts +16 -8
- package/src/notifications/__tests__/connected-channels.test.ts +114 -0
- package/src/notifications/__tests__/decision-engine.test.ts +78 -9
- package/src/notifications/__tests__/destination-resolver.test.ts +256 -0
- package/src/notifications/broadcaster.ts +8 -1
- package/src/notifications/decision-engine.ts +15 -7
- package/src/notifications/destination-resolver.ts +68 -24
- package/src/notifications/emit-signal.ts +39 -14
- package/src/onboarding/checkin-event.test.ts +220 -0
- package/src/onboarding/checkin-event.ts +321 -0
- package/src/onboarding/schedule-checkin.ts +190 -0
- package/src/permissions/question-prompter.test.ts +1 -1
- package/src/permissions/question-prompter.ts +7 -4
- package/src/plugin-api/index.ts +6 -6
- package/src/plugin-api/types.ts +3 -5
- package/src/plugin-api/vision-support.test.ts +28 -4
- package/src/plugin-api/vision-support.ts +66 -31
- package/src/plugins/defaults/advisor/__tests__/consult.test.ts +161 -0
- package/src/plugins/defaults/advisor/__tests__/context-pack-gating.test.ts +106 -0
- package/src/plugins/defaults/advisor/__tests__/context-pack.test.ts +60 -0
- package/src/plugins/defaults/advisor/consult.ts +110 -6
- package/src/plugins/defaults/advisor/context-pack.ts +288 -0
- package/src/plugins/defaults/advisor/steering.ts +14 -2
- package/src/plugins/defaults/advisor/tools/advisor.ts +32 -5
- package/src/plugins/defaults/image-fallback/__tests__/image-fallback.test.ts +47 -7
- package/src/plugins/defaults/image-fallback/hooks/post-tool-use.ts +10 -11
- package/src/plugins/defaults/image-fallback/hooks/user-prompt-submit.ts +12 -20
- package/src/plugins/defaults/image-fallback/src/caption-blocks.ts +42 -11
- package/src/plugins/defaults/memory-v3-shadow/orchestrate.ts +11 -2
- package/src/plugins/defaults/memory-v3-shadow/pool-select.test.ts +146 -0
- package/src/plugins/defaults/memory-v3-shadow/pool-select.ts +29 -1
- package/src/plugins/defaults/memory-v3-shadow/shadow-plugin.ts +8 -1
- package/src/plugins/mtime-cache.ts +7 -2
- package/src/plugins/types.ts +0 -2
- package/src/providers/anthropic/client.ts +5 -0
- package/src/providers/call-site-routing.ts +4 -0
- package/src/providers/model-catalog.ts +16 -0
- package/src/providers/openai/responses-provider.ts +5 -0
- package/src/providers/openrouter/client.ts +5 -0
- package/src/providers/provider-send-message.ts +4 -0
- package/src/providers/ratelimit.ts +4 -0
- package/src/providers/retry.ts +4 -0
- package/src/providers/types.ts +9 -0
- package/src/providers/usage-tracking.ts +4 -0
- package/src/runtime/__tests__/channel-verification-service.test.ts +133 -0
- package/src/runtime/__tests__/guardian-vellum-migration.test.ts +181 -0
- package/src/runtime/__tests__/is-guardian-bound-for-channel.test.ts +66 -0
- package/src/runtime/__tests__/local-principal-trust.test.ts +164 -0
- package/src/runtime/__tests__/trust-verdict-consumer.test.ts +335 -3
- package/src/runtime/access-request-helper.ts +19 -39
- package/src/runtime/actor-trust-resolver.ts +2 -2
- package/src/runtime/anchored-guardian.test.ts +156 -0
- package/src/runtime/anchored-guardian.ts +135 -0
- package/src/runtime/assistant-event-hub.ts +1 -1
- package/src/runtime/assistant-stream-state.ts +9 -2
- package/src/runtime/auth/__tests__/require-bound-guardian.test.ts +99 -0
- package/src/runtime/auth/require-bound-guardian.ts +21 -11
- package/src/runtime/channel-verification-service.ts +56 -31
- package/src/runtime/confirmation-request-guardian-bridge.ts +3 -3
- package/src/runtime/guardian-vellum-migration.ts +66 -7
- package/src/runtime/invite-redemption-service.ts +50 -18
- package/src/runtime/local-actor-identity.ts +76 -11
- package/src/runtime/local-principal-trust.ts +52 -0
- package/src/runtime/pending-interactions.ts +11 -1
- package/src/runtime/routes/__tests__/channel-verification-revoke.test.ts +56 -5
- package/src/runtime/routes/__tests__/channel-verification-routes.test.ts +1 -1
- package/src/runtime/routes/__tests__/contact-routes.test.ts +212 -0
- package/src/runtime/routes/__tests__/global-search-routes.test.ts +93 -0
- package/src/runtime/routes/__tests__/surface-action-routes.test.ts +215 -1
- package/src/runtime/routes/browser-routes.ts +1 -1
- package/src/runtime/routes/channel-verification-routes.ts +3 -3
- package/src/runtime/routes/contact-routes.ts +8 -32
- package/src/runtime/routes/conversation-cli-routes.ts +4 -5
- package/src/runtime/routes/conversation-list-routes.ts +4 -7
- package/src/runtime/routes/conversation-routes.ts +74 -81
- package/src/runtime/routes/events-routes.ts +2 -2
- package/src/runtime/routes/global-search-routes.ts +3 -1
- package/src/runtime/routes/guardian-action-routes.ts +4 -5
- package/src/runtime/routes/host-app-control-routes.ts +5 -4
- package/src/runtime/routes/host-bash-routes.ts +5 -4
- package/src/runtime/routes/host-browser-routes.ts +9 -11
- package/src/runtime/routes/host-cu-routes.ts +5 -4
- package/src/runtime/routes/host-file-routes.ts +5 -4
- package/src/runtime/routes/host-transfer-routes.ts +6 -6
- package/src/runtime/routes/http-adapter.ts +1 -1
- package/src/runtime/routes/identity-routes.ts +3 -2
- package/src/runtime/routes/inbound-message-handler.ts +5 -5
- package/src/runtime/routes/inbound-stages/acl-enforcement.test.ts +97 -5
- package/src/runtime/routes/inbound-stages/acl-enforcement.ts +61 -49
- package/src/runtime/routes/inbound-stages/background-dispatch.ts +16 -4
- package/src/runtime/routes/inbound-stages/escalation-intercept.ts +7 -7
- package/src/runtime/routes/inbound-stages/guardian-activation-intercept.test.ts +21 -8
- package/src/runtime/routes/inbound-stages/guardian-activation-intercept.ts +14 -3
- package/src/runtime/routes/index.ts +2 -0
- package/src/runtime/routes/llm-context-normalization.ts +71 -0
- package/src/runtime/routes/mcp-auth-routes.ts +38 -15
- package/src/runtime/routes/migration-rollback-routes.ts +4 -3
- package/src/runtime/routes/migration-routes.ts +4 -1
- package/src/runtime/routes/onboarding-checkin-routes.ts +86 -0
- package/src/runtime/routes/subagents-routes.ts +5 -0
- package/src/runtime/routes/surface-action-routes.ts +51 -55
- package/src/runtime/services/__tests__/conversation-serializer.test.ts +1 -0
- package/src/runtime/services/conversation-serializer.ts +7 -9
- package/src/runtime/tool-grant-request-helper.ts +3 -3
- package/src/runtime/trust-verdict-consumer.ts +85 -9
- package/src/runtime/verification-outbound-actions.ts +18 -18
- package/src/signals/user-message.ts +16 -0
- package/src/subagent/manager.ts +9 -0
- package/src/telemetry/types.ts +34 -1
- package/src/telemetry/usage-telemetry-reporter.test.ts +3 -2
- package/src/telemetry/usage-telemetry-reporter.ts +87 -3
- package/src/tools/ask-question/ask-question-tool.test.ts +29 -0
- package/src/tools/ask-question/ask-question-tool.ts +13 -0
- package/src/tools/computer-use/definitions.ts +8 -2
- package/src/tools/executor.ts +4 -4
- package/src/tools/registry.ts +18 -0
- package/src/tools/tool-approval-handler.ts +1 -1
- package/src/tools/tool-defaults.ts +9 -2
- package/src/tools/types.ts +17 -2
- package/src/tools/workspace-tools/loader.ts +348 -244
- package/src/util/platform.ts +5 -0
- package/src/util/telemetry-db-path.ts +24 -0
- package/src/workspace/migrations/017-seed-persona-dirs.ts +3 -34
- package/src/workspace/migrations/019-scope-journal-to-guardian.ts +3 -24
- package/src/__tests__/workspace-tools-watcher-flag.test.ts +0 -70
- package/src/daemon/workspace-tools-watcher.ts +0 -328
- package/src/memory/migrations/registry.ts +0 -573
|
@@ -19,19 +19,26 @@ import { RiskLevel } from "@vellumai/plugin-api";
|
|
|
19
19
|
import { advisorEnabledForProfile } from "../advisor-gate.js";
|
|
20
20
|
import { getCapture } from "../advisor-state-store.js";
|
|
21
21
|
import { consultAdvisor } from "../consult.js";
|
|
22
|
+
import { buildAdvisorContext } from "../context-pack.js";
|
|
22
23
|
|
|
23
24
|
const advisorTool: ToolDefinition = {
|
|
24
25
|
name: "advisor",
|
|
25
26
|
description:
|
|
26
27
|
"Consult a stronger advisor model to shape your plan and get strategic guidance. " +
|
|
27
28
|
"Takes NO parameters — your full conversation (the task, every tool call, and every " +
|
|
28
|
-
"result) is forwarded automatically
|
|
29
|
-
"
|
|
30
|
-
"
|
|
31
|
-
"
|
|
29
|
+
"result) is forwarded automatically, along with your available tools and skills, the " +
|
|
30
|
+
"workspace/project context, and relevant memory. Call it BEFORE you start building: it " +
|
|
31
|
+
"can lay out a plan when you don't have one yet, or review and sharpen the plan you've " +
|
|
32
|
+
"already drafted. Also call it when you're stuck, when weighing a change in approach, and " +
|
|
33
|
+
"once before declaring a task complete. It runs on its own — if you call it alongside " +
|
|
34
|
+
"other tools, those are held back until you've seen its guidance. Give its guidance " +
|
|
35
|
+
"serious weight.",
|
|
32
36
|
input_schema: { type: "object", properties: {}, additionalProperties: false },
|
|
33
37
|
// Read-only advice; low risk so the consult isn't gated behind a prompt.
|
|
34
38
|
defaultRiskLevel: RiskLevel.Low,
|
|
39
|
+
// Runs alone in its turn: the loop defers any sibling tool calls so the model
|
|
40
|
+
// incorporates the advisor's guidance before acting on anything else.
|
|
41
|
+
exclusive: true,
|
|
35
42
|
async execute(
|
|
36
43
|
_input: Record<string, unknown>,
|
|
37
44
|
ctx: ToolContext,
|
|
@@ -48,10 +55,30 @@ const advisorTool: ToolDefinition = {
|
|
|
48
55
|
}
|
|
49
56
|
try {
|
|
50
57
|
const capture = getCapture(ctx.conversationId);
|
|
58
|
+
const messages = capture?.messages ?? [];
|
|
59
|
+
// Gather the agent's situational context (tools, skills, workspace,
|
|
60
|
+
// memory) so the advisor reasons with the same awareness the agent has.
|
|
61
|
+
// Best-effort: a failure here must not block the consult.
|
|
62
|
+
const runtimeContext = await buildAdvisorContext({
|
|
63
|
+
conversationId: ctx.conversationId,
|
|
64
|
+
workingDir: ctx.workingDir,
|
|
65
|
+
allowedToolNames: ctx.allowedToolNames,
|
|
66
|
+
// Per-turn trust snapshot — gates personal-memory surfaces off the same
|
|
67
|
+
// values the executor captured for this invocation, not live state.
|
|
68
|
+
trustClass: ctx.trustClass,
|
|
69
|
+
sourceChannel: ctx.executionChannel,
|
|
70
|
+
transcript: messages,
|
|
71
|
+
signal: ctx.signal,
|
|
72
|
+
}).catch(() => null);
|
|
51
73
|
const advice = await consultAdvisor({
|
|
52
74
|
systemPrompt: capture?.systemPrompt ?? null,
|
|
53
|
-
messages
|
|
75
|
+
messages,
|
|
76
|
+
runtimeContext,
|
|
54
77
|
signal: ctx.signal,
|
|
78
|
+
// Stream the advisor's guidance live as it generates: each delta is
|
|
79
|
+
// surfaced as a `tool_output_chunk` for this tool call and rendered in
|
|
80
|
+
// the tool-detail drawer. The complete text is still returned below.
|
|
81
|
+
onText: (chunk) => ctx.onOutput?.(chunk),
|
|
55
82
|
});
|
|
56
83
|
return { content: advice, isError: false };
|
|
57
84
|
} catch (err) {
|
|
@@ -12,8 +12,11 @@ import type {
|
|
|
12
12
|
|
|
13
13
|
// ─── Mocks ──────────────────────────────────────────────────────────────────
|
|
14
14
|
|
|
15
|
-
// Control doesSupportVision
|
|
15
|
+
// Control doesSupportVision from the test: by profile key for the
|
|
16
|
+
// user-prompt-submit path (ModelProfileInfo) and by model id for the
|
|
17
|
+
// post-tool-use path (bare string).
|
|
16
18
|
let visionProfiles: Set<string>;
|
|
19
|
+
let visionModels: Set<string>;
|
|
17
20
|
let mockProfiles: ModelProfileInfo[];
|
|
18
21
|
let sendMessageResponse = {
|
|
19
22
|
content: [{ type: "text", text: "A red chart showing Q3 revenue." }],
|
|
@@ -30,8 +33,10 @@ const fakeProvider = {
|
|
|
30
33
|
// Mock @vellumai/plugin-api — only the runtime handles the plugin imports.
|
|
31
34
|
// `extractAllText` stays real (imported from the relative path, not plugin-api).
|
|
32
35
|
mock.module("@vellumai/plugin-api", () => ({
|
|
33
|
-
doesSupportVision: (
|
|
34
|
-
|
|
36
|
+
doesSupportVision: (arg: ModelProfileInfo | string) =>
|
|
37
|
+
typeof arg === "string"
|
|
38
|
+
? visionModels.has(arg)
|
|
39
|
+
: visionProfiles.has(arg.key),
|
|
35
40
|
getModelProfiles: () => mockProfiles,
|
|
36
41
|
getConfiguredProvider: async () => (providerResolves ? fakeProvider : null),
|
|
37
42
|
}));
|
|
@@ -136,6 +141,9 @@ function makeToolCtx(
|
|
|
136
141
|
|
|
137
142
|
beforeEach(() => {
|
|
138
143
|
visionProfiles = new Set<string>(["vision-profile"]);
|
|
144
|
+
// "text-only-model" (the default post-tool-use ctx.model) is absent, so it
|
|
145
|
+
// reads as text-only; a vision model id is added per-test.
|
|
146
|
+
visionModels = new Set<string>();
|
|
139
147
|
mockProfiles = [
|
|
140
148
|
profile("text-only", { label: "Text Only", isActive: true }),
|
|
141
149
|
profile("vision-profile", { label: "Vision" }),
|
|
@@ -255,7 +263,10 @@ describe("image-fallback user-prompt-submit hook", () => {
|
|
|
255
263
|
};
|
|
256
264
|
// Override the mock to track calls.
|
|
257
265
|
mock.module("@vellumai/plugin-api", () => ({
|
|
258
|
-
doesSupportVision: (
|
|
266
|
+
doesSupportVision: (arg: ModelProfileInfo | string) =>
|
|
267
|
+
typeof arg === "string"
|
|
268
|
+
? visionModels.has(arg)
|
|
269
|
+
: visionProfiles.has(arg.key),
|
|
259
270
|
getModelProfiles: () => mockProfiles,
|
|
260
271
|
getConfiguredProvider: async () => trackingProvider,
|
|
261
272
|
}));
|
|
@@ -273,7 +284,10 @@ describe("image-fallback user-prompt-submit hook", () => {
|
|
|
273
284
|
|
|
274
285
|
// Restore the original mock for other tests.
|
|
275
286
|
mock.module("@vellumai/plugin-api", () => ({
|
|
276
|
-
doesSupportVision: (
|
|
287
|
+
doesSupportVision: (arg: ModelProfileInfo | string) =>
|
|
288
|
+
typeof arg === "string"
|
|
289
|
+
? visionModels.has(arg)
|
|
290
|
+
: visionProfiles.has(arg.key),
|
|
277
291
|
getModelProfiles: () => mockProfiles,
|
|
278
292
|
getConfiguredProvider: async () =>
|
|
279
293
|
providerResolves ? fakeProvider : null,
|
|
@@ -346,9 +360,10 @@ describe("image-fallback post-tool-use hook", () => {
|
|
|
346
360
|
);
|
|
347
361
|
});
|
|
348
362
|
|
|
349
|
-
test("is a no-op when the
|
|
350
|
-
|
|
363
|
+
test("is a no-op when the model that ran supports vision", async () => {
|
|
364
|
+
visionModels = new Set(["vision-model"]);
|
|
351
365
|
const ctx = makeToolCtx({
|
|
366
|
+
model: "vision-model",
|
|
352
367
|
toolResponse: toolResult([imageBlock("shot1")]),
|
|
353
368
|
});
|
|
354
369
|
await postToolUse(ctx);
|
|
@@ -398,4 +413,29 @@ describe("image-fallback post-tool-use hook", () => {
|
|
|
398
413
|
const text = (ctx.toolResponse.contentBlocks![0] as { text: string }).text;
|
|
399
414
|
expect(text).not.toContain("saved to");
|
|
400
415
|
});
|
|
416
|
+
|
|
417
|
+
test("is a no-op when contentBlocks carry no image", async () => {
|
|
418
|
+
const textBlock = { type: "text" as const, text: "just text" };
|
|
419
|
+
const ctx = makeToolCtx({ toolResponse: toolResult([textBlock]) });
|
|
420
|
+
await postToolUse(ctx);
|
|
421
|
+
expect(ctx.toolResponse.contentBlocks![0]).toEqual(textBlock);
|
|
422
|
+
});
|
|
423
|
+
|
|
424
|
+
test("gates on ctx.model, not the workspace active profile", async () => {
|
|
425
|
+
// The active profile is vision-capable, but the model that actually ran
|
|
426
|
+
// (ctx.model) is text-only — the model that ran must win, so the image is
|
|
427
|
+
// captioned.
|
|
428
|
+
mockProfiles = [
|
|
429
|
+
profile("vision-active", { isActive: true }),
|
|
430
|
+
profile("vision-profile", {}),
|
|
431
|
+
];
|
|
432
|
+
visionProfiles = new Set(["vision-active", "vision-profile"]);
|
|
433
|
+
visionModels = new Set<string>(); // "text-only-model" is text-only
|
|
434
|
+
const ctx = makeToolCtx({
|
|
435
|
+
model: "text-only-model",
|
|
436
|
+
toolResponse: toolResult([imageBlock("shot1")]),
|
|
437
|
+
});
|
|
438
|
+
await postToolUse(ctx);
|
|
439
|
+
expect(ctx.toolResponse.contentBlocks![0].type).toBe("text");
|
|
440
|
+
});
|
|
401
441
|
});
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Default `post-tool-use` hook: when the
|
|
2
|
+
* Default `post-tool-use` hook: when the turn's model is text-only, captions
|
|
3
3
|
* the image blocks a tool returns (e.g. a `browser_screenshot`) and
|
|
4
4
|
* substitutes the caption as a text block so the result stays sendable to a
|
|
5
5
|
* provider that would otherwise reject the raw image.
|
|
@@ -9,15 +9,14 @@
|
|
|
9
9
|
* rather than the top-level message content the `user-prompt-submit` hook
|
|
10
10
|
* handles. Both share {@link captionImageBlocks}.
|
|
11
11
|
*
|
|
12
|
-
*
|
|
13
|
-
*
|
|
14
|
-
*
|
|
15
|
-
*
|
|
12
|
+
* Capability is read straight off `ctx.model` — the provider-reported model id
|
|
13
|
+
* for the turn that issued this tool call — so the decision tracks the model
|
|
14
|
+
* that actually ran, including a text-only override. The substitution is in
|
|
15
|
+
* place, so the persisted/displayed tool result carries the caption too.
|
|
16
16
|
*/
|
|
17
17
|
|
|
18
18
|
import {
|
|
19
19
|
doesSupportVision,
|
|
20
|
-
getModelProfiles,
|
|
21
20
|
type PluginHookFn,
|
|
22
21
|
type PostToolUseContext,
|
|
23
22
|
} from "@vellumai/plugin-api";
|
|
@@ -26,13 +25,13 @@ import { captionImageBlocks } from "../src/caption-blocks.js";
|
|
|
26
25
|
import { findVisionProfile } from "../src/vision-caption.js";
|
|
27
26
|
|
|
28
27
|
const postToolUse: PluginHookFn<PostToolUseContext> = async (ctx) => {
|
|
28
|
+
// Cheapest gate first: bail unless the tool actually returned an image,
|
|
29
|
+
// before touching the model catalog or resolving a vision profile.
|
|
29
30
|
const blocks = ctx.toolResponse.contentBlocks;
|
|
30
|
-
if (blocks == null || blocks.
|
|
31
|
+
if (blocks == null || !blocks.some((b) => b.type === "image")) return;
|
|
31
32
|
|
|
32
|
-
// If the
|
|
33
|
-
|
|
34
|
-
if (activeProfile == null) return;
|
|
35
|
-
if (doesSupportVision(activeProfile)) return;
|
|
33
|
+
// If the model that ran already supports vision, leave the image in place.
|
|
34
|
+
if (doesSupportVision(ctx.model)) return;
|
|
36
35
|
|
|
37
36
|
// Find a vision-capable profile for captioning.
|
|
38
37
|
const visionProfileKey = findVisionProfile();
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Default `user-prompt-submit` hook: when the
|
|
2
|
+
* Default `user-prompt-submit` hook: when the turn's model is text-only,
|
|
3
3
|
* captions image blocks via a vision-capable profile and substitutes the
|
|
4
4
|
* caption as a text block so the model can still reason about the image's
|
|
5
5
|
* content.
|
|
@@ -7,13 +7,14 @@
|
|
|
7
7
|
* The hook runs once per user turn, after the assistant assembles
|
|
8
8
|
* `latestMessages` and before they flow into `agentLoop.run()`. It:
|
|
9
9
|
*
|
|
10
|
-
* 1.
|
|
11
|
-
*
|
|
12
|
-
*
|
|
10
|
+
* 1. Checks whether the turn's model needs image→text fallback via
|
|
11
|
+
* {@link needsImageFallback} (resolving the turn's `modelProfileKey`, or the
|
|
12
|
+
* workspace's active profile when the key is `null`). If the model handles
|
|
13
|
+
* images, the hook is a no-op.
|
|
13
14
|
* 2. Finds a vision-capable profile for captioning via `findVisionProfile`.
|
|
14
15
|
* If none exists, images are replaced with a fail-open placeholder so the
|
|
15
16
|
* model at least knows an image was present.
|
|
16
|
-
* 3. Replaces each
|
|
17
|
+
* 3. Replaces each image block with a `[Image …]` text caption via
|
|
17
18
|
* {@link captionImageBlocks} (which also persists the original and caches
|
|
18
19
|
* captions across turns).
|
|
19
20
|
*
|
|
@@ -22,28 +23,19 @@
|
|
|
22
23
|
*/
|
|
23
24
|
|
|
24
25
|
import {
|
|
25
|
-
doesSupportVision,
|
|
26
|
-
getModelProfiles,
|
|
27
26
|
type PluginHookFn,
|
|
28
27
|
type UserPromptSubmitContext,
|
|
29
28
|
} from "@vellumai/plugin-api";
|
|
30
29
|
|
|
31
|
-
import {
|
|
30
|
+
import {
|
|
31
|
+
captionImageBlocks,
|
|
32
|
+
needsImageFallback,
|
|
33
|
+
} from "../src/caption-blocks.js";
|
|
32
34
|
import { findVisionProfile } from "../src/vision-caption.js";
|
|
33
35
|
|
|
34
36
|
const userPromptSubmit: PluginHookFn<UserPromptSubmitContext> = async (ctx) => {
|
|
35
|
-
//
|
|
36
|
-
|
|
37
|
-
// the last notified turn).
|
|
38
|
-
const profiles = getModelProfiles();
|
|
39
|
-
const activeProfile =
|
|
40
|
-
ctx.modelProfileKey != null
|
|
41
|
-
? profiles.find((p) => p.key === ctx.modelProfileKey)
|
|
42
|
-
: profiles.find((p) => p.isActive);
|
|
43
|
-
if (activeProfile == null) return;
|
|
44
|
-
|
|
45
|
-
// If the active model already supports vision, nothing to do.
|
|
46
|
-
if (doesSupportVision(activeProfile)) return;
|
|
37
|
+
// If the turn's model already supports vision, nothing to do.
|
|
38
|
+
if (!needsImageFallback(ctx.modelProfileKey)) return;
|
|
47
39
|
|
|
48
40
|
// Find a vision-capable profile for captioning.
|
|
49
41
|
const visionProfileKey = findVisionProfile();
|
|
@@ -1,31 +1,62 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Shared image→text substitution for the image-fallback plugin's hooks.
|
|
3
3
|
*
|
|
4
|
-
* Two hooks replace `image` content blocks with a text caption when the
|
|
4
|
+
* Two hooks replace `image` content blocks with a text caption when the turn's
|
|
5
5
|
* model can't process images: `user-prompt-submit` handles user-attached
|
|
6
6
|
* images, and `post-tool-use` handles images a tool returns (e.g. a browser
|
|
7
|
-
* screenshot). This module holds
|
|
8
|
-
*
|
|
9
|
-
*
|
|
7
|
+
* screenshot). This module holds what they share — deciding whether a profile
|
|
8
|
+
* needs the fallback ({@link needsImageFallback}) and the per-block
|
|
9
|
+
* substitution ({@link captionImageBlocks}): persist the original image to a
|
|
10
|
+
* known location, caption it via a vision-capable profile, and swap in a
|
|
11
|
+
* `[Image …]` text block.
|
|
10
12
|
*
|
|
11
|
-
* The
|
|
12
|
-
*
|
|
13
|
-
*
|
|
13
|
+
* The substitution mutates the blocks in place, so the caption replaces the
|
|
14
|
+
* image everywhere the block is referenced (the provider-bound history and the
|
|
15
|
+
* persisted/displayed copy alike) — a text-only turn does not keep the raw
|
|
16
|
+
* image around.
|
|
17
|
+
*
|
|
18
|
+
* The caption text states up front that the model can't view images and the
|
|
19
|
+
* image was auto-described to text, so the model treats the block as a derived
|
|
20
|
+
* description rather than a verbatim transcript.
|
|
14
21
|
*
|
|
15
22
|
* Fail-open is the dominant error mode: a captioning failure leaves a
|
|
16
23
|
* placeholder text block rather than the raw image (which a text-only provider
|
|
17
24
|
* would reject) or nothing (which would lose information).
|
|
18
25
|
*/
|
|
19
26
|
|
|
20
|
-
import
|
|
21
|
-
ContentBlock,
|
|
22
|
-
|
|
23
|
-
|
|
27
|
+
import {
|
|
28
|
+
type ContentBlock,
|
|
29
|
+
doesSupportVision,
|
|
30
|
+
getModelProfiles,
|
|
31
|
+
type ImageContent,
|
|
32
|
+
type PluginLogger,
|
|
24
33
|
} from "@vellumai/plugin-api";
|
|
25
34
|
|
|
26
35
|
import { persistImage } from "./image-persist.js";
|
|
27
36
|
import { captionImage } from "./vision-caption.js";
|
|
28
37
|
|
|
38
|
+
/**
|
|
39
|
+
* Whether the profile a turn runs needs image→text fallback (i.e. it can't
|
|
40
|
+
* process images itself).
|
|
41
|
+
*
|
|
42
|
+
* Used by `user-prompt-submit`, whose context carries the profile key rather
|
|
43
|
+
* than the resolved model id: prefer the turn's `modelProfileKey` — which
|
|
44
|
+
* carries a text-only override even when the workspace's active profile is
|
|
45
|
+
* vision-capable — and fall back to the active profile only when the key is
|
|
46
|
+
* `null` (profile unchanged since the last notified turn). Returns `false` when
|
|
47
|
+
* no profile resolves or the resolved model already supports vision, in which
|
|
48
|
+
* case the image reaches the model untouched.
|
|
49
|
+
*/
|
|
50
|
+
export function needsImageFallback(modelProfileKey: string | null): boolean {
|
|
51
|
+
const profiles = getModelProfiles();
|
|
52
|
+
const activeProfile =
|
|
53
|
+
modelProfileKey != null
|
|
54
|
+
? profiles.find((p) => p.key === modelProfileKey)
|
|
55
|
+
: profiles.find((p) => p.isActive);
|
|
56
|
+
if (activeProfile == null) return false;
|
|
57
|
+
return !doesSupportVision(activeProfile);
|
|
58
|
+
}
|
|
59
|
+
|
|
29
60
|
/**
|
|
30
61
|
* Replace every `image` block in `blocks` (in place) with a text caption so a
|
|
31
62
|
* text-only model can still reason about the image's content. Returns the
|
|
@@ -119,6 +119,10 @@ export interface OrchestrateDeps {
|
|
|
119
119
|
/** Hard cap on total learned-lane surfaced articles; `0` disables the pass
|
|
120
120
|
* (canonical value: `memory.v3.learnedEdges.cap`). */
|
|
121
121
|
learnedCap?: number;
|
|
122
|
+
/** The selector's system prompt. Omitted → the selector's bundled default.
|
|
123
|
+
* The live caller resolves `memory.v3.selectorPromptPath` (workspace-relative
|
|
124
|
+
* file override) via `resolveSelectorPrompt` and threads the result here. */
|
|
125
|
+
selectorPrompt?: string;
|
|
122
126
|
}
|
|
123
127
|
|
|
124
128
|
/** A finder-lane candidate: the slug, the descriptor that justified it, and
|
|
@@ -378,8 +382,13 @@ export async function orchestrate(
|
|
|
378
382
|
|
|
379
383
|
// Step 3: a SINGLE forced-tool select over the cache-ordered pool. The
|
|
380
384
|
// selections come back slug-deduped (pinned flags ORed) — `selectPool`'s
|
|
381
|
-
// contract.
|
|
382
|
-
|
|
385
|
+
// contract. `selectorPrompt` is the (optionally overridden) instruction
|
|
386
|
+
// scaffold; `undefined` falls through to the bundled default.
|
|
387
|
+
const selections = await selectPool(
|
|
388
|
+
{ stable, finder: finderTail },
|
|
389
|
+
turn,
|
|
390
|
+
deps.selectorPrompt,
|
|
391
|
+
);
|
|
383
392
|
|
|
384
393
|
return {
|
|
385
394
|
selections,
|
|
@@ -0,0 +1,146 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Tests for `pool-select.ts` `selectPool` — focused on error SURFACING:
|
|
3
|
+
* - a provider call that THROWS on every attempt surfaces the underlying
|
|
4
|
+
* provider error (e.g. an upstream HTTP 4xx) in the thrown
|
|
5
|
+
* `MemoryV3RetrievalUnavailableError` message, rather than the generic
|
|
6
|
+
* "no usable selection" string that hid it;
|
|
7
|
+
* - a 200 response carrying no usable `tool_use` still throws the generic
|
|
8
|
+
* "no usable selection" message;
|
|
9
|
+
* - happy paths preserved: explicit ids → selection, omitted ids → keepAll,
|
|
10
|
+
* empty pool → [].
|
|
11
|
+
*
|
|
12
|
+
* `mock.module` is process-global and leaks into sibling files in a directory
|
|
13
|
+
* run, so the provider-send-message stub DELEGATES to the real implementation
|
|
14
|
+
* (keeping the real `extractToolUse`) unless this test is actively running
|
|
15
|
+
* (`selectMockActive`) — mirrors `prune.test.ts` / `ever-injected-store.test.ts`.
|
|
16
|
+
*/
|
|
17
|
+
|
|
18
|
+
import { afterAll, beforeEach, describe, expect, mock, test } from "bun:test";
|
|
19
|
+
|
|
20
|
+
import type {
|
|
21
|
+
ContentBlock,
|
|
22
|
+
Provider,
|
|
23
|
+
ProviderResponse,
|
|
24
|
+
} from "../../../providers/types.js";
|
|
25
|
+
import type { MemoryRoutingTurn, Slug } from "./types.js";
|
|
26
|
+
|
|
27
|
+
const realProviderSend = {
|
|
28
|
+
...(await import("../../../providers/provider-send-message.js")),
|
|
29
|
+
};
|
|
30
|
+
|
|
31
|
+
let selectMockActive = false;
|
|
32
|
+
let sendMessageImpl: (() => Promise<ProviderResponse>) | null = null;
|
|
33
|
+
|
|
34
|
+
const mockProvider = {
|
|
35
|
+
name: "mock-memory-v3-selector",
|
|
36
|
+
async sendMessage(): Promise<ProviderResponse> {
|
|
37
|
+
if (!sendMessageImpl) throw new Error("sendMessageImpl not configured");
|
|
38
|
+
return sendMessageImpl();
|
|
39
|
+
},
|
|
40
|
+
} as unknown as Provider;
|
|
41
|
+
|
|
42
|
+
mock.module("../../../providers/provider-send-message.js", () => ({
|
|
43
|
+
...realProviderSend,
|
|
44
|
+
getConfiguredProvider: (
|
|
45
|
+
...args: Parameters<typeof realProviderSend.getConfiguredProvider>
|
|
46
|
+
) =>
|
|
47
|
+
selectMockActive
|
|
48
|
+
? Promise.resolve(mockProvider)
|
|
49
|
+
: realProviderSend.getConfiguredProvider(...args),
|
|
50
|
+
}));
|
|
51
|
+
|
|
52
|
+
const { MemoryV3RetrievalUnavailableError, selectPool } =
|
|
53
|
+
await import("./pool-select.js");
|
|
54
|
+
|
|
55
|
+
function response(content: ContentBlock[]): ProviderResponse {
|
|
56
|
+
return {
|
|
57
|
+
content,
|
|
58
|
+
model: "mock",
|
|
59
|
+
usage: { inputTokens: 0, outputTokens: 0 },
|
|
60
|
+
} as ProviderResponse;
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
const turn: MemoryRoutingTurn = {
|
|
64
|
+
conversationId: "conv-1",
|
|
65
|
+
turnNumber: 0,
|
|
66
|
+
currentMessage: "echo something back",
|
|
67
|
+
recentContext: "",
|
|
68
|
+
};
|
|
69
|
+
|
|
70
|
+
const pool = {
|
|
71
|
+
stable: [],
|
|
72
|
+
finder: [{ slug: "page-a" as Slug, descriptor: "a descriptor" }],
|
|
73
|
+
};
|
|
74
|
+
|
|
75
|
+
describe("selectPool", () => {
|
|
76
|
+
beforeEach(() => {
|
|
77
|
+
selectMockActive = true;
|
|
78
|
+
sendMessageImpl = null;
|
|
79
|
+
});
|
|
80
|
+
afterAll(() => {
|
|
81
|
+
selectMockActive = false;
|
|
82
|
+
});
|
|
83
|
+
|
|
84
|
+
test("a provider throw surfaces the underlying error in the thrown message", async () => {
|
|
85
|
+
const upstream = "Together AI API error (400): 400 status code (no body)";
|
|
86
|
+
sendMessageImpl = async () => {
|
|
87
|
+
throw new Error(upstream);
|
|
88
|
+
};
|
|
89
|
+
let caught: unknown;
|
|
90
|
+
try {
|
|
91
|
+
await selectPool(pool, turn);
|
|
92
|
+
} catch (err) {
|
|
93
|
+
caught = err;
|
|
94
|
+
}
|
|
95
|
+
expect(caught).toBeInstanceOf(MemoryV3RetrievalUnavailableError);
|
|
96
|
+
expect((caught as Error).message).toContain(
|
|
97
|
+
"provider call failed after retries",
|
|
98
|
+
);
|
|
99
|
+
// The real upstream error is no longer hidden behind the generic message.
|
|
100
|
+
expect((caught as Error).message).toContain(upstream);
|
|
101
|
+
});
|
|
102
|
+
|
|
103
|
+
test("a 200 with no usable tool_use throws the generic message", async () => {
|
|
104
|
+
sendMessageImpl = async () =>
|
|
105
|
+
response([{ type: "text", text: "I cannot call the tool." }]);
|
|
106
|
+
let caught: unknown;
|
|
107
|
+
try {
|
|
108
|
+
await selectPool(pool, turn);
|
|
109
|
+
} catch (err) {
|
|
110
|
+
caught = err;
|
|
111
|
+
}
|
|
112
|
+
expect(caught).toBeInstanceOf(MemoryV3RetrievalUnavailableError);
|
|
113
|
+
expect((caught as Error).message).toBe(
|
|
114
|
+
"memory-v3 pool selector returned no usable selection after retries",
|
|
115
|
+
);
|
|
116
|
+
});
|
|
117
|
+
|
|
118
|
+
test("explicit ids select the matching candidates", async () => {
|
|
119
|
+
sendMessageImpl = async () =>
|
|
120
|
+
response([
|
|
121
|
+
{
|
|
122
|
+
type: "tool_use",
|
|
123
|
+
id: "call-1",
|
|
124
|
+
name: "select_pages",
|
|
125
|
+
input: { ids: [1] },
|
|
126
|
+
},
|
|
127
|
+
]);
|
|
128
|
+
expect(await selectPool(pool, turn)).toEqual([
|
|
129
|
+
{ slug: "page-a", pinned: false },
|
|
130
|
+
]);
|
|
131
|
+
});
|
|
132
|
+
|
|
133
|
+
test("omitted ids keep all candidates (recall-safe)", async () => {
|
|
134
|
+
sendMessageImpl = async () =>
|
|
135
|
+
response([
|
|
136
|
+
{ type: "tool_use", id: "call-1", name: "select_pages", input: {} },
|
|
137
|
+
]);
|
|
138
|
+
expect(await selectPool(pool, turn)).toEqual([
|
|
139
|
+
{ slug: "page-a", pinned: false },
|
|
140
|
+
]);
|
|
141
|
+
});
|
|
142
|
+
|
|
143
|
+
test("an empty candidate pool returns no selections", async () => {
|
|
144
|
+
expect(await selectPool({ stable: [], finder: [] }, turn)).toEqual([]);
|
|
145
|
+
});
|
|
146
|
+
});
|
|
@@ -50,6 +50,7 @@
|
|
|
50
50
|
|
|
51
51
|
import { z } from "zod";
|
|
52
52
|
|
|
53
|
+
import { loadPromptOverride } from "../../../memory/prompt-override.js";
|
|
53
54
|
import { cachedTextBlock } from "../../../providers/cache-control.js";
|
|
54
55
|
import {
|
|
55
56
|
extractToolUse,
|
|
@@ -201,6 +202,28 @@ A page can be relevant because of the current situation — the date or the live
|
|
|
201
202
|
|
|
202
203
|
If the conversation is centrally ABOUT a page (rather than only peripherally relevant to it), mark that page as pinned. Call \`select_pages\` with the chosen IDs. Omit \`ids\` only as a recall-safe fallback when you cannot judge the pool (keeps every candidate); return \`[]\` when candidates are present but none are relevant.`;
|
|
203
204
|
|
|
205
|
+
/**
|
|
206
|
+
* Resolve the selector system prompt: the file at `overridePath` when it is set
|
|
207
|
+
* and usable, otherwise the bundled {@link SYSTEM_PROMPT}. Path resolution and
|
|
208
|
+
* fallback follow the shared override loader (workspace-relative; a missing,
|
|
209
|
+
* empty, oversized, or unreadable file degrades to the bundled prompt with a
|
|
210
|
+
* warning). The selector prompt takes no placeholders — the candidate pool is
|
|
211
|
+
* the user message — so an override file is used verbatim.
|
|
212
|
+
*/
|
|
213
|
+
export function resolveSelectorPrompt(
|
|
214
|
+
overridePath: string | null,
|
|
215
|
+
workspaceDir: string,
|
|
216
|
+
): string {
|
|
217
|
+
return (
|
|
218
|
+
loadPromptOverride({
|
|
219
|
+
overridePath,
|
|
220
|
+
workspaceDir,
|
|
221
|
+
log,
|
|
222
|
+
label: "memory-v3 selector prompt",
|
|
223
|
+
}) ?? SYSTEM_PROMPT
|
|
224
|
+
);
|
|
225
|
+
}
|
|
226
|
+
|
|
204
227
|
/** Collapse a descriptor to one line and cap its length for a finder line. */
|
|
205
228
|
function renderSnippet(descriptor: string): string {
|
|
206
229
|
return truncate(descriptor.replace(/\s+/g, " ").trim(), SNIPPET_MAX_CHARS);
|
|
@@ -315,10 +338,15 @@ function dedupeBySlug(
|
|
|
315
338
|
* relevant" signal); an explicit `[]` keeps none; an infrastructure failure
|
|
316
339
|
* (after a short re-prompt retry) keeps none, degrading to the deterministic
|
|
317
340
|
* recall lanes the orchestrator unions in.
|
|
341
|
+
*
|
|
342
|
+
* `systemPrompt` is the selector's instruction scaffold; it defaults to the
|
|
343
|
+
* bundled {@link SYSTEM_PROMPT} and is overridable via `memory.v3.selectorPromptPath`
|
|
344
|
+
* (resolved by {@link resolveSelectorPrompt} at the call site).
|
|
318
345
|
*/
|
|
319
346
|
export async function selectPool(
|
|
320
347
|
pool: SelectorPool,
|
|
321
348
|
turn: MemoryRoutingTurn,
|
|
349
|
+
systemPrompt: string = SYSTEM_PROMPT,
|
|
322
350
|
): Promise<SelectedPage[]> {
|
|
323
351
|
// The concatenated numbering: ids 1…m are the stable-prefix cards, ids
|
|
324
352
|
// m+1… are the finder lines.
|
|
@@ -401,7 +429,7 @@ export async function selectPool(
|
|
|
401
429
|
try {
|
|
402
430
|
response = await provider.sendMessage([userMsg], {
|
|
403
431
|
tools: [SELECT_PAGES_TOOL],
|
|
404
|
-
systemPrompt
|
|
432
|
+
systemPrompt,
|
|
405
433
|
config: {
|
|
406
434
|
callSite: MEMORY_V3_SELECT_CALL_SITE,
|
|
407
435
|
tool_choice: { type: "tool" as const, name: SELECT_PAGES_TOOL_NAME },
|
|
@@ -56,7 +56,10 @@ import { computeHotSet } from "./hot-set.js";
|
|
|
56
56
|
import { computeLearnedEdgeGraph } from "./learned-edges.js";
|
|
57
57
|
import type { OrchestrateResult } from "./orchestrate.js";
|
|
58
58
|
import { orchestrate } from "./orchestrate.js";
|
|
59
|
-
import {
|
|
59
|
+
import {
|
|
60
|
+
MemoryV3RetrievalUnavailableError,
|
|
61
|
+
resolveSelectorPrompt,
|
|
62
|
+
} from "./pool-select.js";
|
|
60
63
|
import { ensureSectionCollection } from "./section-dense-store.js";
|
|
61
64
|
import type { SectionNeedle } from "./section-needle.js";
|
|
62
65
|
import { buildSectionNeedle } from "./section-needle.js";
|
|
@@ -576,6 +579,10 @@ export async function observeTurn(
|
|
|
576
579
|
learnedGraph: lanes.learnedGraph,
|
|
577
580
|
learnedPerSeed: v3.learnedEdges.perSeed,
|
|
578
581
|
learnedCap: v3.learnedEdges.cap,
|
|
582
|
+
selectorPrompt: resolveSelectorPrompt(
|
|
583
|
+
v3.selectorPromptPath,
|
|
584
|
+
getWorkspaceDir(),
|
|
585
|
+
),
|
|
579
586
|
});
|
|
580
587
|
|
|
581
588
|
// A zero-selection turn over a non-trivial pool is unusual enough to be
|
|
@@ -18,7 +18,13 @@
|
|
|
18
18
|
* cached entry.
|
|
19
19
|
*/
|
|
20
20
|
|
|
21
|
-
import {
|
|
21
|
+
import {
|
|
22
|
+
existsSync,
|
|
23
|
+
mkdirSync,
|
|
24
|
+
readdirSync,
|
|
25
|
+
readFileSync,
|
|
26
|
+
statSync,
|
|
27
|
+
} from "node:fs";
|
|
22
28
|
import { join } from "node:path";
|
|
23
29
|
|
|
24
30
|
import { getConfig } from "../config/loader.js";
|
|
@@ -648,7 +654,6 @@ export async function populateCacheAtBoot(
|
|
|
648
654
|
try {
|
|
649
655
|
const initContext: PluginInitContext = {
|
|
650
656
|
config: getConfig().plugins?.[pluginName],
|
|
651
|
-
credentials: {},
|
|
652
657
|
logger: log.child({ plugin: pluginName }),
|
|
653
658
|
pluginStorageDir: ensurePluginStorageDir(pluginName),
|
|
654
659
|
assistantVersion: APP_VERSION,
|
package/src/plugins/types.ts
CHANGED
|
@@ -47,8 +47,6 @@ export interface PluginManifest {
|
|
|
47
47
|
* own version at load time.
|
|
48
48
|
*/
|
|
49
49
|
version: string;
|
|
50
|
-
/** Credential keys the plugin needs resolved before `init()` runs. */
|
|
51
|
-
requiresCredential?: string[];
|
|
52
50
|
/**
|
|
53
51
|
* Assistant feature-flag keys that must all be enabled for this plugin to
|
|
54
52
|
* activate. Checked by `bootstrapPlugins` via `isAssistantFeatureFlagEnabled`
|
|
@@ -786,6 +786,11 @@ export class AnthropicProvider implements Provider {
|
|
|
786
786
|
this.useNativeWebSearch = options.useNativeWebSearch ?? false;
|
|
787
787
|
}
|
|
788
788
|
|
|
789
|
+
/** See {@link Provider.supportsNativeWebSearch}. */
|
|
790
|
+
get supportsNativeWebSearch(): boolean {
|
|
791
|
+
return this.useNativeWebSearch;
|
|
792
|
+
}
|
|
793
|
+
|
|
789
794
|
async sendMessage(
|
|
790
795
|
messages: Message[],
|
|
791
796
|
options?: SendMessageOptions,
|