@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
|
@@ -43,6 +43,9 @@ import type {
|
|
|
43
43
|
|
|
44
44
|
export class CallSiteRoutingProvider implements Provider {
|
|
45
45
|
public readonly tokenEstimationProvider?: string;
|
|
46
|
+
// Forward native web-search capability so it survives the wrapper chain
|
|
47
|
+
// (callers like the advisor consult gate on it). Fixed at construction.
|
|
48
|
+
public readonly supportsNativeWebSearch?: boolean;
|
|
46
49
|
|
|
47
50
|
// Per-call async context that tracks which provider is currently executing.
|
|
48
51
|
// Using AsyncLocalStorage instead of a plain instance field means concurrent
|
|
@@ -94,6 +97,7 @@ export class CallSiteRoutingProvider implements Provider {
|
|
|
94
97
|
) => Promise<Provider | null>,
|
|
95
98
|
) {
|
|
96
99
|
this.tokenEstimationProvider = defaultProvider.tokenEstimationProvider;
|
|
100
|
+
this.supportsNativeWebSearch = defaultProvider.supportsNativeWebSearch;
|
|
97
101
|
if (defaultProvider.countInputTokens) {
|
|
98
102
|
this.countInputTokens =
|
|
99
103
|
defaultProvider.countInputTokens.bind(defaultProvider);
|
|
@@ -760,6 +760,22 @@ const RAW_PROVIDER_CATALOG: ProviderCatalogEntry[] = [
|
|
|
760
760
|
maxEffort: "max",
|
|
761
761
|
pricing: { inputPer1mTokens: 1.74, outputPer1mTokens: 3.48 },
|
|
762
762
|
},
|
|
763
|
+
{
|
|
764
|
+
id: "accounts/fireworks/models/deepseek-v4-flash",
|
|
765
|
+
displayName: "DeepSeek V4 Flash",
|
|
766
|
+
contextWindowTokens: 1040000,
|
|
767
|
+
maxOutputTokens: 131072,
|
|
768
|
+
supportsThinking: true,
|
|
769
|
+
supportsCaching: true,
|
|
770
|
+
supportsVision: false,
|
|
771
|
+
supportsToolUse: true,
|
|
772
|
+
maxEffort: "max",
|
|
773
|
+
pricing: {
|
|
774
|
+
inputPer1mTokens: 0.14,
|
|
775
|
+
outputPer1mTokens: 0.28,
|
|
776
|
+
cacheReadPer1mTokens: 0.03,
|
|
777
|
+
},
|
|
778
|
+
},
|
|
763
779
|
],
|
|
764
780
|
defaultModel: "accounts/fireworks/models/kimi-k2p5",
|
|
765
781
|
apiKeyUrl: "https://fireworks.ai/account/api-keys",
|
|
@@ -194,6 +194,11 @@ export class OpenAIResponsesProvider implements Provider {
|
|
|
194
194
|
this.useNativeWebSearch = options.useNativeWebSearch ?? false;
|
|
195
195
|
}
|
|
196
196
|
|
|
197
|
+
/** See {@link Provider.supportsNativeWebSearch}. */
|
|
198
|
+
get supportsNativeWebSearch(): boolean {
|
|
199
|
+
return this.useNativeWebSearch;
|
|
200
|
+
}
|
|
201
|
+
|
|
197
202
|
async sendMessage(
|
|
198
203
|
messages: Message[],
|
|
199
204
|
options?: SendMessageOptions,
|
|
@@ -140,6 +140,11 @@ export class OpenRouterProvider extends OpenAIChatCompletionsProvider {
|
|
|
140
140
|
return isAnthropicModel(this.defaultModel) ? "anthropic" : this.name;
|
|
141
141
|
}
|
|
142
142
|
|
|
143
|
+
/** See {@link Provider.supportsNativeWebSearch}. Set per model at construction. */
|
|
144
|
+
get supportsNativeWebSearch(): boolean {
|
|
145
|
+
return this.useNativeWebSearch;
|
|
146
|
+
}
|
|
147
|
+
|
|
143
148
|
override async sendMessage(
|
|
144
149
|
messages: Message[],
|
|
145
150
|
options?: SendMessageOptions,
|
|
@@ -43,6 +43,9 @@ let lazyInitPromise: Promise<void> | null = null;
|
|
|
43
43
|
export class CallSiteConfiguredProvider implements Provider {
|
|
44
44
|
public readonly name: string;
|
|
45
45
|
public readonly tokenEstimationProvider?: string;
|
|
46
|
+
// Forward native web-search capability so it survives the wrapper chain
|
|
47
|
+
// (callers like the advisor consult gate on it). Fixed at construction.
|
|
48
|
+
public readonly supportsNativeWebSearch?: boolean;
|
|
46
49
|
|
|
47
50
|
constructor(
|
|
48
51
|
private readonly inner: Provider,
|
|
@@ -52,6 +55,7 @@ export class CallSiteConfiguredProvider implements Provider {
|
|
|
52
55
|
) {
|
|
53
56
|
this.name = inner.name;
|
|
54
57
|
this.tokenEstimationProvider = inner.tokenEstimationProvider;
|
|
58
|
+
this.supportsNativeWebSearch = inner.supportsNativeWebSearch;
|
|
55
59
|
}
|
|
56
60
|
|
|
57
61
|
sendMessage(
|
|
@@ -23,6 +23,10 @@ export class RateLimitProvider implements Provider {
|
|
|
23
23
|
return this.inner.tokenEstimationProvider;
|
|
24
24
|
}
|
|
25
25
|
|
|
26
|
+
get supportsNativeWebSearch(): boolean | undefined {
|
|
27
|
+
return this.inner.supportsNativeWebSearch;
|
|
28
|
+
}
|
|
29
|
+
|
|
26
30
|
private requestTimestamps: number[];
|
|
27
31
|
|
|
28
32
|
// Forward the optional token-counting endpoint so the capability survives
|
package/src/providers/retry.ts
CHANGED
|
@@ -618,6 +618,10 @@ export class RetryProvider implements Provider {
|
|
|
618
618
|
return this.inner.tokenEstimationProvider;
|
|
619
619
|
}
|
|
620
620
|
|
|
621
|
+
get supportsNativeWebSearch(): boolean | undefined {
|
|
622
|
+
return this.inner.supportsNativeWebSearch;
|
|
623
|
+
}
|
|
624
|
+
|
|
621
625
|
// Forward the optional token-counting endpoint so the capability survives
|
|
622
626
|
// the wrapper chain (callers gate on its presence). Bound straight to the
|
|
623
627
|
// inner provider — count_tokens is a cheap separate endpoint and its caller
|
package/src/providers/types.ts
CHANGED
|
@@ -265,6 +265,15 @@ export interface Provider {
|
|
|
265
265
|
* Falls back to `name` when unset.
|
|
266
266
|
*/
|
|
267
267
|
tokenEstimationProvider?: string;
|
|
268
|
+
/**
|
|
269
|
+
* True when this provider instance was constructed to run web search
|
|
270
|
+
* server-side (provider-native). The native search only activates when a
|
|
271
|
+
* `web_search`-named tool is passed in the request, so callers that want to
|
|
272
|
+
* enable web search on a one-shot completion (e.g. the advisor consult) check
|
|
273
|
+
* this first — passing the tool to a non-native instance would surface an
|
|
274
|
+
* unexecutable client tool call. Absent/false on providers without it.
|
|
275
|
+
*/
|
|
276
|
+
supportsNativeWebSearch?: boolean;
|
|
268
277
|
sendMessage(
|
|
269
278
|
messages: Message[],
|
|
270
279
|
options?: SendMessageOptions,
|
|
@@ -18,6 +18,9 @@ const log = getLogger("provider-usage-tracking");
|
|
|
18
18
|
export class UsageTrackingProvider implements Provider {
|
|
19
19
|
public readonly name: string;
|
|
20
20
|
public readonly tokenEstimationProvider?: string;
|
|
21
|
+
// Forward native web-search capability so it survives the wrapper chain
|
|
22
|
+
// (callers like the advisor consult gate on it). Fixed at construction.
|
|
23
|
+
public readonly supportsNativeWebSearch?: boolean;
|
|
21
24
|
// Forward the optional token-counting endpoint so the capability survives
|
|
22
25
|
// the wrapper chain. Bound straight to the inner provider — count_tokens is
|
|
23
26
|
// not billed, so there's no usage to track.
|
|
@@ -26,6 +29,7 @@ export class UsageTrackingProvider implements Provider {
|
|
|
26
29
|
constructor(private readonly inner: Provider) {
|
|
27
30
|
this.name = inner.name;
|
|
28
31
|
this.tokenEstimationProvider = inner.tokenEstimationProvider;
|
|
32
|
+
this.supportsNativeWebSearch = inner.supportsNativeWebSearch;
|
|
29
33
|
if (inner.countInputTokens) {
|
|
30
34
|
this.countInputTokens = inner.countInputTokens.bind(inner);
|
|
31
35
|
}
|
|
@@ -0,0 +1,133 @@
|
|
|
1
|
+
import { beforeEach, describe, expect, mock, test } from "bun:test";
|
|
2
|
+
|
|
3
|
+
// Gateway guardian-delivery list drives both getGuardianBinding and isGuardian:
|
|
4
|
+
// null = couldn't determine, [] = authoritative unbound, one active entry =
|
|
5
|
+
// bound. Tests set this to mirror the gateway-owned ACL state.
|
|
6
|
+
let mockGuardianList: Array<Record<string, unknown>> | null = [];
|
|
7
|
+
const cachedCalls: Array<{ channelTypes?: string[] } | undefined> = [];
|
|
8
|
+
|
|
9
|
+
const resolveList = (input?: { channelTypes?: string[] }) => {
|
|
10
|
+
cachedCalls.push(input);
|
|
11
|
+
return Promise.resolve(mockGuardianList);
|
|
12
|
+
};
|
|
13
|
+
|
|
14
|
+
mock.module("../../contacts/guardian-delivery-reader.js", () => ({
|
|
15
|
+
getGuardianDelivery: resolveList,
|
|
16
|
+
getGuardianDeliveryFresh: resolveList,
|
|
17
|
+
guardianForChannel: (
|
|
18
|
+
list: Array<{ channelType: string; status: string }>,
|
|
19
|
+
channelType: string,
|
|
20
|
+
) => list.find((g) => g.channelType === channelType && g.status === "active"),
|
|
21
|
+
}));
|
|
22
|
+
|
|
23
|
+
const { getGuardianBinding, isGuardian } = await import(
|
|
24
|
+
"../channel-verification-service.js"
|
|
25
|
+
);
|
|
26
|
+
|
|
27
|
+
const TELEGRAM_DELIVERY = {
|
|
28
|
+
channelType: "telegram",
|
|
29
|
+
contactId: "contact-1",
|
|
30
|
+
principalId: "principal-1",
|
|
31
|
+
displayName: "Guardian",
|
|
32
|
+
address: "guardian-handle",
|
|
33
|
+
externalChatId: "chat-1",
|
|
34
|
+
status: "active",
|
|
35
|
+
verifiedAt: 1700,
|
|
36
|
+
};
|
|
37
|
+
|
|
38
|
+
describe("getGuardianBinding", () => {
|
|
39
|
+
beforeEach(() => {
|
|
40
|
+
mockGuardianList = [];
|
|
41
|
+
cachedCalls.length = 0;
|
|
42
|
+
});
|
|
43
|
+
|
|
44
|
+
test("filters delivery by the requested channel type", async () => {
|
|
45
|
+
await getGuardianBinding("asst-1", "telegram");
|
|
46
|
+
expect(cachedCalls).toEqual([{ channelTypes: ["telegram"] }]);
|
|
47
|
+
});
|
|
48
|
+
|
|
49
|
+
test("returns null when no guardian is bound", async () => {
|
|
50
|
+
mockGuardianList = [];
|
|
51
|
+
expect(await getGuardianBinding("asst-1", "telegram")).toBeNull();
|
|
52
|
+
});
|
|
53
|
+
|
|
54
|
+
test("returns null when the gateway is unreachable", async () => {
|
|
55
|
+
mockGuardianList = null;
|
|
56
|
+
expect(await getGuardianBinding("asst-1", "telegram")).toBeNull();
|
|
57
|
+
});
|
|
58
|
+
|
|
59
|
+
test("synthesizes the binding from the gateway delivery", async () => {
|
|
60
|
+
mockGuardianList = [TELEGRAM_DELIVERY];
|
|
61
|
+
|
|
62
|
+
const binding = await getGuardianBinding("asst-1", "telegram");
|
|
63
|
+
|
|
64
|
+
expect(binding).not.toBeNull();
|
|
65
|
+
expect(binding?.assistantId).toBe("asst-1");
|
|
66
|
+
expect(binding?.channel).toBe("telegram");
|
|
67
|
+
expect(binding?.id).toBe("contact-1");
|
|
68
|
+
expect(binding?.guardianPrincipalId).toBe("principal-1");
|
|
69
|
+
expect(binding?.guardianExternalUserId).toBe("guardian-handle");
|
|
70
|
+
expect(binding?.guardianDeliveryChatId).toBe("chat-1");
|
|
71
|
+
expect(binding?.verifiedAt).toBe(1700);
|
|
72
|
+
expect(binding?.status).toBe("active");
|
|
73
|
+
expect(binding?.verifiedVia).toBe("verified");
|
|
74
|
+
});
|
|
75
|
+
|
|
76
|
+
test("falls back to empty strings for absent optional delivery fields", async () => {
|
|
77
|
+
mockGuardianList = [
|
|
78
|
+
{
|
|
79
|
+
channelType: "telegram",
|
|
80
|
+
contactId: "contact-2",
|
|
81
|
+
address: "addr",
|
|
82
|
+
status: "active",
|
|
83
|
+
},
|
|
84
|
+
];
|
|
85
|
+
|
|
86
|
+
const binding = await getGuardianBinding("asst-1", "telegram");
|
|
87
|
+
|
|
88
|
+
expect(binding?.guardianPrincipalId).toBe("");
|
|
89
|
+
expect(binding?.guardianDeliveryChatId).toBe("");
|
|
90
|
+
expect(binding?.verifiedAt).toBe(0);
|
|
91
|
+
});
|
|
92
|
+
|
|
93
|
+
test("ignores deliveries for a different channel", async () => {
|
|
94
|
+
mockGuardianList = [TELEGRAM_DELIVERY];
|
|
95
|
+
expect(await getGuardianBinding("asst-1", "phone")).toBeNull();
|
|
96
|
+
});
|
|
97
|
+
});
|
|
98
|
+
|
|
99
|
+
describe("isGuardian", () => {
|
|
100
|
+
beforeEach(() => {
|
|
101
|
+
mockGuardianList = [];
|
|
102
|
+
cachedCalls.length = 0;
|
|
103
|
+
});
|
|
104
|
+
|
|
105
|
+
test("returns true when the address matches the gateway guardian", async () => {
|
|
106
|
+
mockGuardianList = [TELEGRAM_DELIVERY];
|
|
107
|
+
expect(await isGuardian("asst-1", "telegram", "guardian-handle")).toBe(true);
|
|
108
|
+
});
|
|
109
|
+
|
|
110
|
+
test("compares case-insensitively", async () => {
|
|
111
|
+
mockGuardianList = [TELEGRAM_DELIVERY];
|
|
112
|
+
expect(await isGuardian("asst-1", "telegram", "GUARDIAN-HANDLE")).toBe(true);
|
|
113
|
+
});
|
|
114
|
+
|
|
115
|
+
test("returns false for a non-matching address", async () => {
|
|
116
|
+
mockGuardianList = [TELEGRAM_DELIVERY];
|
|
117
|
+
expect(await isGuardian("asst-1", "telegram", "someone-else")).toBe(false);
|
|
118
|
+
});
|
|
119
|
+
|
|
120
|
+
test("returns false when no guardian is bound", async () => {
|
|
121
|
+
mockGuardianList = [];
|
|
122
|
+
expect(await isGuardian("asst-1", "telegram", "guardian-handle")).toBe(
|
|
123
|
+
false,
|
|
124
|
+
);
|
|
125
|
+
});
|
|
126
|
+
|
|
127
|
+
test("returns false when the gateway is unreachable", async () => {
|
|
128
|
+
mockGuardianList = null;
|
|
129
|
+
expect(await isGuardian("asst-1", "telegram", "guardian-handle")).toBe(
|
|
130
|
+
false,
|
|
131
|
+
);
|
|
132
|
+
});
|
|
133
|
+
});
|
|
@@ -0,0 +1,181 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Unit tests for the narrow reset-drift trust-recovery helper
|
|
3
|
+
* `reResolveTrustOnResetDrift`.
|
|
4
|
+
*
|
|
5
|
+
* The real helper runs against mocked leaf deps: the gateway guardian read
|
|
6
|
+
* (`getGuardianDelivery`/`guardianForChannel`), the local-mirror heal
|
|
7
|
+
* (`findGuardianForChannel`/`updateContactPrincipalAndChannel`, which the real
|
|
8
|
+
* `healGuardianBindingDrift` drives), and the local trust resolver
|
|
9
|
+
* (`resolveTrustContext`). Heal invocations are observed via the contact-store
|
|
10
|
+
* write mock.
|
|
11
|
+
*/
|
|
12
|
+
import { beforeEach, describe, expect, mock, test } from "bun:test";
|
|
13
|
+
|
|
14
|
+
mock.module("../../util/logger.js", () => ({
|
|
15
|
+
getLogger: () =>
|
|
16
|
+
new Proxy({} as Record<string, unknown>, { get: () => () => {} }),
|
|
17
|
+
}));
|
|
18
|
+
|
|
19
|
+
let mockGuardianList: Array<Record<string, unknown>> | null = [];
|
|
20
|
+
|
|
21
|
+
mock.module("../../contacts/guardian-delivery-reader.js", () => ({
|
|
22
|
+
getGuardianDelivery: async () => mockGuardianList,
|
|
23
|
+
guardianForChannel: (
|
|
24
|
+
list: Array<Record<string, unknown>>,
|
|
25
|
+
channelType: string,
|
|
26
|
+
) => list.find((g) => g.channelType === channelType && g.status === "active"),
|
|
27
|
+
}));
|
|
28
|
+
|
|
29
|
+
// Local mirror the real heal reads/writes. `findGuardianForChannel` returns the
|
|
30
|
+
// stored guardian; `updateContactPrincipalAndChannel` records heal writes.
|
|
31
|
+
let mockLocalGuardian: {
|
|
32
|
+
contact: { id: string; principalId: string };
|
|
33
|
+
channel: { id: string };
|
|
34
|
+
} | null = null;
|
|
35
|
+
const healWrites: Array<{ principalId: string }> = [];
|
|
36
|
+
|
|
37
|
+
mock.module("../../contacts/contact-store.js", () => ({
|
|
38
|
+
findGuardianForChannel: () => mockLocalGuardian,
|
|
39
|
+
updateContactPrincipalAndChannel: (
|
|
40
|
+
_contactId: string,
|
|
41
|
+
_channelId: string,
|
|
42
|
+
principalId: string,
|
|
43
|
+
) => {
|
|
44
|
+
healWrites.push({ principalId });
|
|
45
|
+
return true;
|
|
46
|
+
},
|
|
47
|
+
}));
|
|
48
|
+
|
|
49
|
+
// The local trust resolver returns guardian for the actor; the gate threads
|
|
50
|
+
// sourceChannel via the real withSourceChannel wrapper.
|
|
51
|
+
mock.module("../../runtime/trust-context-resolver.js", () => ({
|
|
52
|
+
resolveTrustContext: (input: { actorExternalId?: string }) => ({
|
|
53
|
+
trustClass: "guardian",
|
|
54
|
+
sourceChannel: "vellum",
|
|
55
|
+
resolvedActor: input.actorExternalId,
|
|
56
|
+
}),
|
|
57
|
+
withSourceChannel: (sourceChannel: unknown, ctx: Record<string, unknown>) => ({
|
|
58
|
+
...ctx,
|
|
59
|
+
sourceChannel,
|
|
60
|
+
}),
|
|
61
|
+
}));
|
|
62
|
+
|
|
63
|
+
const { reResolveTrustOnResetDrift } = await import(
|
|
64
|
+
"../guardian-vellum-migration.js"
|
|
65
|
+
);
|
|
66
|
+
|
|
67
|
+
function gatewayGuardian(principalId: string): Record<string, unknown> {
|
|
68
|
+
return {
|
|
69
|
+
channelType: "vellum",
|
|
70
|
+
contactId: "guardian-contact",
|
|
71
|
+
principalId,
|
|
72
|
+
address: principalId,
|
|
73
|
+
status: "active",
|
|
74
|
+
};
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
function localGuardian(principalId: string) {
|
|
78
|
+
return {
|
|
79
|
+
contact: { id: "contact-1", principalId },
|
|
80
|
+
channel: { id: "channel-1" },
|
|
81
|
+
};
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
describe("reResolveTrustOnResetDrift", () => {
|
|
85
|
+
beforeEach(() => {
|
|
86
|
+
mockGuardianList = [];
|
|
87
|
+
mockLocalGuardian = null;
|
|
88
|
+
healWrites.length = 0;
|
|
89
|
+
});
|
|
90
|
+
|
|
91
|
+
test("reset drift: heals and returns the re-resolved guardian ctx", async () => {
|
|
92
|
+
// Stale local mirror still holds the pre-reset principal; the incoming JWT
|
|
93
|
+
// carries the old one. Heal repairs the mirror toward the incoming actor.
|
|
94
|
+
mockGuardianList = [gatewayGuardian("vellum-principal-new")];
|
|
95
|
+
mockLocalGuardian = localGuardian("vellum-principal-stale");
|
|
96
|
+
|
|
97
|
+
const ctx = await reResolveTrustOnResetDrift(
|
|
98
|
+
"vellum-principal-old",
|
|
99
|
+
"vellum",
|
|
100
|
+
);
|
|
101
|
+
|
|
102
|
+
expect(ctx?.trustClass).toBe("guardian");
|
|
103
|
+
expect(healWrites).toEqual([{ principalId: "vellum-principal-old" }]);
|
|
104
|
+
});
|
|
105
|
+
|
|
106
|
+
test("repeat drift where heal no-ops still returns the guardian ctx", async () => {
|
|
107
|
+
// Local mirror already matches the incoming principal, so heal's write is
|
|
108
|
+
// skipped, but the gate still passes and the re-resolve yields guardian.
|
|
109
|
+
mockGuardianList = [gatewayGuardian("vellum-principal-new")];
|
|
110
|
+
mockLocalGuardian = localGuardian("vellum-principal-old");
|
|
111
|
+
|
|
112
|
+
const ctx = await reResolveTrustOnResetDrift(
|
|
113
|
+
"vellum-principal-old",
|
|
114
|
+
"vellum",
|
|
115
|
+
);
|
|
116
|
+
|
|
117
|
+
expect(ctx?.trustClass).toBe("guardian");
|
|
118
|
+
expect(healWrites).toEqual([]);
|
|
119
|
+
});
|
|
120
|
+
|
|
121
|
+
test("gateway unreachable (null): returns null, heal not called", async () => {
|
|
122
|
+
mockGuardianList = null;
|
|
123
|
+
mockLocalGuardian = localGuardian("vellum-principal-old");
|
|
124
|
+
|
|
125
|
+
const ctx = await reResolveTrustOnResetDrift(
|
|
126
|
+
"vellum-principal-old",
|
|
127
|
+
"vellum",
|
|
128
|
+
);
|
|
129
|
+
|
|
130
|
+
expect(ctx).toBeNull();
|
|
131
|
+
expect(healWrites).toEqual([]);
|
|
132
|
+
});
|
|
133
|
+
|
|
134
|
+
test("empty/revoked gateway (no active guardian): returns null, heal not called", async () => {
|
|
135
|
+
mockGuardianList = [];
|
|
136
|
+
mockLocalGuardian = localGuardian("vellum-principal-old");
|
|
137
|
+
|
|
138
|
+
const ctx = await reResolveTrustOnResetDrift(
|
|
139
|
+
"vellum-principal-old",
|
|
140
|
+
"vellum",
|
|
141
|
+
);
|
|
142
|
+
|
|
143
|
+
expect(ctx).toBeNull();
|
|
144
|
+
expect(healWrites).toEqual([]);
|
|
145
|
+
});
|
|
146
|
+
|
|
147
|
+
test("gateway guardian is a real (non vellum-principal-*) id: returns null", async () => {
|
|
148
|
+
mockGuardianList = [gatewayGuardian("user@example.com")];
|
|
149
|
+
mockLocalGuardian = localGuardian("vellum-principal-old");
|
|
150
|
+
|
|
151
|
+
const ctx = await reResolveTrustOnResetDrift(
|
|
152
|
+
"vellum-principal-old",
|
|
153
|
+
"vellum",
|
|
154
|
+
);
|
|
155
|
+
|
|
156
|
+
expect(ctx).toBeNull();
|
|
157
|
+
expect(healWrites).toEqual([]);
|
|
158
|
+
});
|
|
159
|
+
|
|
160
|
+
test("incoming principal is not vellum-principal-*: returns null", async () => {
|
|
161
|
+
mockGuardianList = [gatewayGuardian("vellum-principal-new")];
|
|
162
|
+
mockLocalGuardian = localGuardian("vellum-principal-old");
|
|
163
|
+
|
|
164
|
+
const ctx = await reResolveTrustOnResetDrift("user@example.com", "vellum");
|
|
165
|
+
|
|
166
|
+
expect(ctx).toBeNull();
|
|
167
|
+
expect(healWrites).toEqual([]);
|
|
168
|
+
});
|
|
169
|
+
|
|
170
|
+
test("threads sourceChannel into the returned ctx", async () => {
|
|
171
|
+
mockGuardianList = [gatewayGuardian("vellum-principal-new")];
|
|
172
|
+
mockLocalGuardian = localGuardian("vellum-principal-old");
|
|
173
|
+
|
|
174
|
+
const ctx = await reResolveTrustOnResetDrift(
|
|
175
|
+
"vellum-principal-old",
|
|
176
|
+
"telegram",
|
|
177
|
+
);
|
|
178
|
+
|
|
179
|
+
expect(ctx?.sourceChannel).toBe("telegram");
|
|
180
|
+
});
|
|
181
|
+
});
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
import { beforeEach, describe, expect, mock, test } from "bun:test";
|
|
2
|
+
|
|
3
|
+
// Gateway guardian-delivery list: null = couldn't determine (transport failure
|
|
4
|
+
// OR gateway-side resolver/DB error), [] = authoritative unbound, one active
|
|
5
|
+
// entry = bound.
|
|
6
|
+
let mockGuardianList: Array<Record<string, unknown>> | null = [];
|
|
7
|
+
const freshCalls: Array<{ channelTypes?: string[] } | undefined> = [];
|
|
8
|
+
|
|
9
|
+
mock.module("../../contacts/guardian-delivery-reader.js", () => ({
|
|
10
|
+
// Existence guard reads fresh (uncached); the binding/identity reads use the
|
|
11
|
+
// cached variant. The service imports both, so both must be stubbed.
|
|
12
|
+
getGuardianDeliveryFresh: (input?: { channelTypes?: string[] }) => {
|
|
13
|
+
freshCalls.push(input);
|
|
14
|
+
return Promise.resolve(mockGuardianList);
|
|
15
|
+
},
|
|
16
|
+
getGuardianDelivery: () => Promise.resolve(mockGuardianList),
|
|
17
|
+
guardianForChannel: (
|
|
18
|
+
list: Array<{ channelType: string; status: string }>,
|
|
19
|
+
channelType: string,
|
|
20
|
+
) => list.find((g) => g.channelType === channelType && g.status === "active"),
|
|
21
|
+
}));
|
|
22
|
+
|
|
23
|
+
const { isGuardianBoundForChannel } = await import(
|
|
24
|
+
"../channel-verification-service.js"
|
|
25
|
+
);
|
|
26
|
+
|
|
27
|
+
describe("isGuardianBoundForChannel", () => {
|
|
28
|
+
beforeEach(() => {
|
|
29
|
+
mockGuardianList = [];
|
|
30
|
+
freshCalls.length = 0;
|
|
31
|
+
});
|
|
32
|
+
|
|
33
|
+
test("reads fresh so a stale cached empty list can't mask a present guardian", async () => {
|
|
34
|
+
await isGuardianBoundForChannel("telegram");
|
|
35
|
+
expect(freshCalls).toEqual([{ channelTypes: ["telegram"] }]);
|
|
36
|
+
});
|
|
37
|
+
|
|
38
|
+
test("returns false when no guardian is bound", async () => {
|
|
39
|
+
mockGuardianList = [];
|
|
40
|
+
expect(await isGuardianBoundForChannel("telegram")).toBe(false);
|
|
41
|
+
});
|
|
42
|
+
|
|
43
|
+
test("returns true when a guardian is bound", async () => {
|
|
44
|
+
mockGuardianList = [{ channelType: "telegram", status: "active" }];
|
|
45
|
+
expect(await isGuardianBoundForChannel("telegram")).toBe(true);
|
|
46
|
+
});
|
|
47
|
+
|
|
48
|
+
test("null list (gateway unreachable) is treated as bound", async () => {
|
|
49
|
+
mockGuardianList = null;
|
|
50
|
+
expect(await isGuardianBoundForChannel("telegram")).toBe(true);
|
|
51
|
+
});
|
|
52
|
+
|
|
53
|
+
test("gateway resolver error (null, not []) is treated as bound — no duplicate", async () => {
|
|
54
|
+
// A gateway-side DB/resolver error now reaches the reader as null (the
|
|
55
|
+
// handler no longer swallows it into an empty list), so the guard's
|
|
56
|
+
// null fail-safe applies and reports bound instead of mis-reading the
|
|
57
|
+
// error as "no guardian" and allowing a duplicate binding.
|
|
58
|
+
mockGuardianList = null;
|
|
59
|
+
expect(await isGuardianBoundForChannel("telegram")).toBe(true);
|
|
60
|
+
});
|
|
61
|
+
|
|
62
|
+
test("genuine empty ([], not null) reports unbound so first-bind is allowed", async () => {
|
|
63
|
+
mockGuardianList = [];
|
|
64
|
+
expect(await isGuardianBoundForChannel("telegram")).toBe(false);
|
|
65
|
+
});
|
|
66
|
+
});
|