@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
|
@@ -87,7 +87,7 @@ afterAll(() => {
|
|
|
87
87
|
|
|
88
88
|
const handleHostFileResult = ROUTES.find(
|
|
89
89
|
(r) => r.endpoint === "host-file-result",
|
|
90
|
-
)!.handler
|
|
90
|
+
)!.handler as (args: Record<string, unknown>) => Promise<unknown>;
|
|
91
91
|
|
|
92
92
|
// ── Helpers ─────────────────────────────────────────────────────────────────
|
|
93
93
|
|
|
@@ -189,15 +189,13 @@ describe("handleHostFileResult — targetClientId guard", () => {
|
|
|
189
189
|
).toThrow(BadRequestError);
|
|
190
190
|
});
|
|
191
191
|
|
|
192
|
-
test("interaction is NOT resolved on 400 (still pending)", () => {
|
|
192
|
+
test("interaction is NOT resolved on 400 (still pending)", async () => {
|
|
193
193
|
const requestId = "req-file-targeted-no-header-stays";
|
|
194
194
|
registerPending(requestId, { targetClientId: "client-A" });
|
|
195
195
|
|
|
196
|
-
|
|
197
|
-
handleHostFileResult({ body: fileBody(requestId) });
|
|
198
|
-
} catch {
|
|
196
|
+
await handleHostFileResult({ body: fileBody(requestId) }).catch(() => {
|
|
199
197
|
// expected
|
|
200
|
-
}
|
|
198
|
+
});
|
|
201
199
|
|
|
202
200
|
expect(resolvedIds).not.toContain(requestId);
|
|
203
201
|
expect(pendingStore.has(requestId)).toBe(true);
|
|
@@ -219,19 +217,17 @@ describe("handleHostFileResult — targetClientId guard", () => {
|
|
|
219
217
|
).toThrow(ForbiddenError);
|
|
220
218
|
});
|
|
221
219
|
|
|
222
|
-
test("ForbiddenError message names both submitting and expected client", () => {
|
|
220
|
+
test("ForbiddenError message names both submitting and expected client", async () => {
|
|
223
221
|
const requestId = "req-file-targeted-mismatch-msg";
|
|
224
222
|
registerPending(requestId, { targetClientId: "client-A" });
|
|
225
223
|
|
|
226
224
|
let caught: unknown;
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
});
|
|
232
|
-
} catch (e) {
|
|
225
|
+
await handleHostFileResult({
|
|
226
|
+
body: fileBody(requestId),
|
|
227
|
+
headers: { "x-vellum-client-id": "client-B" },
|
|
228
|
+
}).catch((e: unknown) => {
|
|
233
229
|
caught = e;
|
|
234
|
-
}
|
|
230
|
+
});
|
|
235
231
|
|
|
236
232
|
expect(caught).toBeInstanceOf(ForbiddenError);
|
|
237
233
|
const msg = (caught as ForbiddenError).message;
|
|
@@ -239,18 +235,16 @@ describe("handleHostFileResult — targetClientId guard", () => {
|
|
|
239
235
|
expect(msg).toContain("client-A");
|
|
240
236
|
});
|
|
241
237
|
|
|
242
|
-
test("interaction is NOT consumed on 403 (pendingInteractions.get still returns it)", () => {
|
|
238
|
+
test("interaction is NOT consumed on 403 (pendingInteractions.get still returns it)", async () => {
|
|
243
239
|
const requestId = "req-file-targeted-mismatch-stays";
|
|
244
240
|
registerPending(requestId, { targetClientId: "client-A" });
|
|
245
241
|
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
});
|
|
251
|
-
} catch {
|
|
242
|
+
await handleHostFileResult({
|
|
243
|
+
body: fileBody(requestId),
|
|
244
|
+
headers: { "x-vellum-client-id": "client-B" },
|
|
245
|
+
}).catch(() => {
|
|
252
246
|
// expected
|
|
253
|
-
}
|
|
247
|
+
});
|
|
254
248
|
|
|
255
249
|
expect(resolvedIds).not.toContain(requestId);
|
|
256
250
|
expect(pendingStore.has(requestId)).toBe(true);
|
|
@@ -306,22 +300,20 @@ describe("handleHostFileResult — targetClientId guard", () => {
|
|
|
306
300
|
).toThrow(ForbiddenError);
|
|
307
301
|
});
|
|
308
302
|
|
|
309
|
-
test("interaction is NOT consumed on actor-mismatch 403", () => {
|
|
303
|
+
test("interaction is NOT consumed on actor-mismatch 403", async () => {
|
|
310
304
|
const requestId = "req-file-actor-mismatch-stays";
|
|
311
305
|
clientActors.set("client-A", "actor-1");
|
|
312
306
|
registerPending(requestId, { targetClientId: "client-A" });
|
|
313
307
|
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
});
|
|
322
|
-
} catch {
|
|
308
|
+
await handleHostFileResult({
|
|
309
|
+
body: fileBody(requestId),
|
|
310
|
+
headers: {
|
|
311
|
+
"x-vellum-client-id": "client-A",
|
|
312
|
+
"x-vellum-actor-principal-id": "actor-2",
|
|
313
|
+
},
|
|
314
|
+
}).catch(() => {
|
|
323
315
|
// expected
|
|
324
|
-
}
|
|
316
|
+
});
|
|
325
317
|
|
|
326
318
|
expect(resolvedIds).not.toContain(requestId);
|
|
327
319
|
expect(pendingStore.has(requestId)).toBe(true);
|
|
@@ -360,19 +352,17 @@ describe("handleHostFileResult — targetClientId guard", () => {
|
|
|
360
352
|
).toThrow(ForbiddenError);
|
|
361
353
|
});
|
|
362
354
|
|
|
363
|
-
test("interaction is NOT consumed when submitting actor is missing", () => {
|
|
355
|
+
test("interaction is NOT consumed when submitting actor is missing", async () => {
|
|
364
356
|
const requestId = "req-file-actor-missing-stays";
|
|
365
357
|
clientActors.set("client-A", "actor-1");
|
|
366
358
|
registerPending(requestId, { targetClientId: "client-A" });
|
|
367
359
|
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
});
|
|
373
|
-
} catch {
|
|
360
|
+
await handleHostFileResult({
|
|
361
|
+
body: fileBody(requestId),
|
|
362
|
+
headers: { "x-vellum-client-id": "client-A" },
|
|
363
|
+
}).catch(() => {
|
|
374
364
|
// expected
|
|
375
|
-
}
|
|
365
|
+
});
|
|
376
366
|
|
|
377
367
|
expect(resolvedIds).not.toContain(requestId);
|
|
378
368
|
expect(pendingStore.has(requestId)).toBe(true);
|
|
@@ -398,21 +388,19 @@ describe("handleHostFileResult — targetClientId guard", () => {
|
|
|
398
388
|
).toThrow(ForbiddenError);
|
|
399
389
|
});
|
|
400
390
|
|
|
401
|
-
test("interaction is NOT consumed when target client has no stored actor", () => {
|
|
391
|
+
test("interaction is NOT consumed when target client has no stored actor", async () => {
|
|
402
392
|
const requestId = "req-file-target-no-actor-stays";
|
|
403
393
|
registerPending(requestId, { targetClientId: "client-A" });
|
|
404
394
|
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
});
|
|
413
|
-
} catch {
|
|
395
|
+
await handleHostFileResult({
|
|
396
|
+
body: fileBody(requestId),
|
|
397
|
+
headers: {
|
|
398
|
+
"x-vellum-client-id": "client-A",
|
|
399
|
+
"x-vellum-actor-principal-id": "actor-1",
|
|
400
|
+
},
|
|
401
|
+
}).catch(() => {
|
|
414
402
|
// expected
|
|
415
|
-
}
|
|
403
|
+
});
|
|
416
404
|
|
|
417
405
|
expect(resolvedIds).not.toContain(requestId);
|
|
418
406
|
expect(pendingStore.has(requestId)).toBe(true);
|
|
@@ -99,7 +99,7 @@ afterAll(() => {
|
|
|
99
99
|
|
|
100
100
|
const handleTransferContentGet = ROUTES.find(
|
|
101
101
|
(r) => r.endpoint === "transfers/:transferId/content" && r.method === "GET",
|
|
102
|
-
)!.handler
|
|
102
|
+
)!.handler as (args: Record<string, unknown>) => Promise<unknown>;
|
|
103
103
|
|
|
104
104
|
const handleTransferContentPut = ROUTES.find(
|
|
105
105
|
(r) => r.endpoint === "transfers/:transferId/content" && r.method === "PUT",
|
|
@@ -107,7 +107,7 @@ const handleTransferContentPut = ROUTES.find(
|
|
|
107
107
|
|
|
108
108
|
const handleTransferResult = ROUTES.find(
|
|
109
109
|
(r) => r.endpoint === "host-transfer-result",
|
|
110
|
-
)!.handler
|
|
110
|
+
)!.handler as (args: Record<string, unknown>) => Promise<unknown>;
|
|
111
111
|
|
|
112
112
|
// ── Helpers ─────────────────────────────────────────────────────────────────
|
|
113
113
|
|
|
@@ -186,15 +186,13 @@ describe("handleTransferContentGet — Phase 3 targetClientId guard", () => {
|
|
|
186
186
|
).toThrow(BadRequestError);
|
|
187
187
|
});
|
|
188
188
|
|
|
189
|
-
test("getTransferContent NOT called on 400", () => {
|
|
189
|
+
test("getTransferContent NOT called on 400", async () => {
|
|
190
190
|
stubTargetClientId = "client-A";
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
});
|
|
195
|
-
} catch {
|
|
191
|
+
await handleTransferContentGet({
|
|
192
|
+
pathParams: { transferId: TEST_TRANSFER_ID },
|
|
193
|
+
}).catch(() => {
|
|
196
194
|
// expected
|
|
197
|
-
}
|
|
195
|
+
});
|
|
198
196
|
expect(getTransferContentCalls).toHaveLength(0);
|
|
199
197
|
});
|
|
200
198
|
});
|
|
@@ -212,16 +210,14 @@ describe("handleTransferContentGet — Phase 3 targetClientId guard", () => {
|
|
|
212
210
|
).toThrow(ForbiddenError);
|
|
213
211
|
});
|
|
214
212
|
|
|
215
|
-
test("getTransferContent NOT called on 403", () => {
|
|
213
|
+
test("getTransferContent NOT called on 403", async () => {
|
|
216
214
|
stubTargetClientId = "client-A";
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
});
|
|
222
|
-
} catch {
|
|
215
|
+
await handleTransferContentGet({
|
|
216
|
+
pathParams: { transferId: TEST_TRANSFER_ID },
|
|
217
|
+
headers: { "x-vellum-client-id": "client-B" },
|
|
218
|
+
}).catch(() => {
|
|
223
219
|
// expected
|
|
224
|
-
}
|
|
220
|
+
});
|
|
225
221
|
expect(getTransferContentCalls).toHaveLength(0);
|
|
226
222
|
});
|
|
227
223
|
});
|
|
@@ -526,23 +522,19 @@ describe("handleTransferResult — Phase 3 targetClientId guard", () => {
|
|
|
526
522
|
);
|
|
527
523
|
});
|
|
528
524
|
|
|
529
|
-
test("resolveTransferResult NOT called on 400", () => {
|
|
525
|
+
test("resolveTransferResult NOT called on 400", async () => {
|
|
530
526
|
registerHostTransferPending("client-A");
|
|
531
|
-
|
|
532
|
-
handleTransferResult({ body: resultBody() });
|
|
533
|
-
} catch {
|
|
527
|
+
await handleTransferResult({ body: resultBody() }).catch(() => {
|
|
534
528
|
// expected
|
|
535
|
-
}
|
|
529
|
+
});
|
|
536
530
|
expect(resolveTransferResultCalls).toHaveLength(0);
|
|
537
531
|
});
|
|
538
532
|
|
|
539
|
-
test("pending interaction still present after 400", () => {
|
|
533
|
+
test("pending interaction still present after 400", async () => {
|
|
540
534
|
registerHostTransferPending("client-A");
|
|
541
|
-
|
|
542
|
-
handleTransferResult({ body: resultBody() });
|
|
543
|
-
} catch {
|
|
535
|
+
await handleTransferResult({ body: resultBody() }).catch(() => {
|
|
544
536
|
// expected
|
|
545
|
-
}
|
|
537
|
+
});
|
|
546
538
|
expect(pendingStore.has(TEST_REQUEST_ID)).toBe(true);
|
|
547
539
|
});
|
|
548
540
|
});
|
|
@@ -560,29 +552,25 @@ describe("handleTransferResult — Phase 3 targetClientId guard", () => {
|
|
|
560
552
|
).toThrow(ForbiddenError);
|
|
561
553
|
});
|
|
562
554
|
|
|
563
|
-
test("resolveTransferResult NOT called on 403", () => {
|
|
555
|
+
test("resolveTransferResult NOT called on 403", async () => {
|
|
564
556
|
registerHostTransferPending("client-A");
|
|
565
|
-
|
|
566
|
-
|
|
567
|
-
|
|
568
|
-
|
|
569
|
-
});
|
|
570
|
-
} catch {
|
|
557
|
+
await handleTransferResult({
|
|
558
|
+
body: resultBody(),
|
|
559
|
+
headers: { "x-vellum-client-id": "client-B" },
|
|
560
|
+
}).catch(() => {
|
|
571
561
|
// expected
|
|
572
|
-
}
|
|
562
|
+
});
|
|
573
563
|
expect(resolveTransferResultCalls).toHaveLength(0);
|
|
574
564
|
});
|
|
575
565
|
|
|
576
|
-
test("pending interaction still present after 403", () => {
|
|
566
|
+
test("pending interaction still present after 403", async () => {
|
|
577
567
|
registerHostTransferPending("client-A");
|
|
578
|
-
|
|
579
|
-
|
|
580
|
-
|
|
581
|
-
|
|
582
|
-
});
|
|
583
|
-
} catch {
|
|
568
|
+
await handleTransferResult({
|
|
569
|
+
body: resultBody(),
|
|
570
|
+
headers: { "x-vellum-client-id": "client-B" },
|
|
571
|
+
}).catch(() => {
|
|
584
572
|
// expected
|
|
585
|
-
}
|
|
573
|
+
});
|
|
586
574
|
expect(pendingStore.has(TEST_REQUEST_ID)).toBe(true);
|
|
587
575
|
});
|
|
588
576
|
});
|
|
@@ -62,6 +62,21 @@ mock.module("../runtime/guardian-reply-router.js", () => ({
|
|
|
62
62
|
routeGuardianReply: routeGuardianReplyMock,
|
|
63
63
|
}));
|
|
64
64
|
|
|
65
|
+
// Stub for the shared reset-drift helper. handleSendMessage only consumes its
|
|
66
|
+
// result (a guardian TrustContext or null) on a first-pass-unknown actor; the
|
|
67
|
+
// gate itself is covered in runtime/__tests__/guardian-vellum-migration.test.ts.
|
|
68
|
+
const reResolveCalls: string[] = [];
|
|
69
|
+
let mockReResolve: { trustClass: string; sourceChannel: string } | null = null;
|
|
70
|
+
mock.module("../runtime/guardian-vellum-migration.js", () => ({
|
|
71
|
+
reResolveTrustOnResetDrift: async (
|
|
72
|
+
incomingPrincipalId: string,
|
|
73
|
+
_sourceChannel: string,
|
|
74
|
+
) => {
|
|
75
|
+
reResolveCalls.push(incomingPrincipalId);
|
|
76
|
+
return mockReResolve;
|
|
77
|
+
},
|
|
78
|
+
}));
|
|
79
|
+
|
|
65
80
|
mock.module("../memory/canonical-guardian-store.js", () => ({
|
|
66
81
|
createCanonicalGuardianRequest: () => ({
|
|
67
82
|
id: "canonical-id",
|
|
@@ -94,6 +109,8 @@ mock.module("../runtime/confirmation-request-guardian-bridge.js", () => ({
|
|
|
94
109
|
}));
|
|
95
110
|
|
|
96
111
|
mock.module("../memory/conversation-crud.js", () => ({
|
|
112
|
+
setConversationProcessingStartedAt: () => {},
|
|
113
|
+
isConversationProcessing: () => false,
|
|
97
114
|
addMessage: (
|
|
98
115
|
conversationId: string,
|
|
99
116
|
role: string,
|
|
@@ -104,17 +121,32 @@ mock.module("../memory/conversation-crud.js", () => ({
|
|
|
104
121
|
}));
|
|
105
122
|
|
|
106
123
|
mock.module("../runtime/local-actor-identity.js", () => ({
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
124
|
+
findLocalGuardianPrincipalId: async () =>
|
|
125
|
+
mockGuardians?.find(
|
|
126
|
+
(g) => g.channelType === "vellum" && g.status === "active",
|
|
127
|
+
)?.principalId as string | undefined,
|
|
128
|
+
}));
|
|
129
|
+
|
|
130
|
+
let mockGuardians: Array<Record<string, unknown>> | null = [
|
|
131
|
+
{
|
|
132
|
+
channelType: "vellum",
|
|
133
|
+
contactId: "guardian-contact",
|
|
134
|
+
principalId: "test-user",
|
|
135
|
+
address: "test-user",
|
|
136
|
+
status: "active",
|
|
137
|
+
},
|
|
138
|
+
];
|
|
139
|
+
|
|
140
|
+
mock.module("../contacts/guardian-delivery-reader.js", () => ({
|
|
141
|
+
getGuardianDelivery: async () => mockGuardians,
|
|
142
|
+
guardianForChannel: (
|
|
143
|
+
list: Array<Record<string, unknown>>,
|
|
144
|
+
channelType: string,
|
|
145
|
+
) => list.find((g) => g.channelType === channelType && g.status === "active"),
|
|
111
146
|
}));
|
|
112
147
|
|
|
148
|
+
// handleSendMessage wraps the first-pass resolve with withSourceChannel.
|
|
113
149
|
mock.module("../runtime/trust-context-resolver.js", () => ({
|
|
114
|
-
resolveTrustContext: () => ({
|
|
115
|
-
trustClass: "guardian",
|
|
116
|
-
sourceChannel: "vellum",
|
|
117
|
-
}),
|
|
118
150
|
withSourceChannel: (sourceChannel: unknown, ctx: unknown) => ({
|
|
119
151
|
...(ctx as Record<string, unknown>),
|
|
120
152
|
sourceChannel,
|
|
@@ -454,3 +486,130 @@ describe("HTTP POST /v1/messages clientTimezone transport metadata", () => {
|
|
|
454
486
|
expect(runAgentLoop).toHaveBeenCalledTimes(1);
|
|
455
487
|
});
|
|
456
488
|
});
|
|
489
|
+
|
|
490
|
+
// ============================================================================
|
|
491
|
+
// TRUST CONTEXT — derived from the gateway guardian binding
|
|
492
|
+
// ============================================================================
|
|
493
|
+
describe("HTTP POST /v1/messages trust context from the gateway binding", () => {
|
|
494
|
+
beforeEach(() => {
|
|
495
|
+
mockGuardians = [
|
|
496
|
+
{
|
|
497
|
+
channelType: "vellum",
|
|
498
|
+
contactId: "guardian-contact",
|
|
499
|
+
principalId: "test-user",
|
|
500
|
+
address: "test-user",
|
|
501
|
+
status: "active",
|
|
502
|
+
},
|
|
503
|
+
];
|
|
504
|
+
reResolveCalls.length = 0;
|
|
505
|
+
mockReResolve = null;
|
|
506
|
+
});
|
|
507
|
+
|
|
508
|
+
function requestAs(principalId: string, sourceChannel = "vellum") {
|
|
509
|
+
return new Request("http://localhost/v1/messages", {
|
|
510
|
+
method: "POST",
|
|
511
|
+
headers: {
|
|
512
|
+
"Content-Type": "application/json",
|
|
513
|
+
"x-vellum-actor-principal-id": principalId,
|
|
514
|
+
"x-vellum-principal-type": "actor",
|
|
515
|
+
},
|
|
516
|
+
body: JSON.stringify({
|
|
517
|
+
conversationKey: "trust-test-key",
|
|
518
|
+
content: "hi",
|
|
519
|
+
sourceChannel,
|
|
520
|
+
interface: "macos",
|
|
521
|
+
}),
|
|
522
|
+
});
|
|
523
|
+
}
|
|
524
|
+
|
|
525
|
+
async function trustContextFor(
|
|
526
|
+
principalId: string,
|
|
527
|
+
sourceChannel = "vellum",
|
|
528
|
+
): Promise<Record<string, unknown>> {
|
|
529
|
+
let captured: Record<string, unknown> | undefined;
|
|
530
|
+
const conversation = makeConversation({
|
|
531
|
+
setTrustContext: (ctx: Record<string, unknown>) => {
|
|
532
|
+
captured = ctx;
|
|
533
|
+
},
|
|
534
|
+
});
|
|
535
|
+
const res = await callHandler(
|
|
536
|
+
(args) =>
|
|
537
|
+
handleSendMessage(args, {
|
|
538
|
+
sendMessageDeps: {
|
|
539
|
+
getOrCreateConversation: async () => conversation,
|
|
540
|
+
assistantEventHub: { publish: async () => {} } as any,
|
|
541
|
+
resolveAttachments: () => [],
|
|
542
|
+
},
|
|
543
|
+
}),
|
|
544
|
+
requestAs(principalId, sourceChannel),
|
|
545
|
+
undefined,
|
|
546
|
+
202,
|
|
547
|
+
);
|
|
548
|
+
expect(res.status).toBe(202);
|
|
549
|
+
return captured ?? {};
|
|
550
|
+
}
|
|
551
|
+
|
|
552
|
+
async function trustClassFor(principalId: string): Promise<string> {
|
|
553
|
+
return (await trustContextFor(principalId)).trustClass as string;
|
|
554
|
+
}
|
|
555
|
+
|
|
556
|
+
test("guardian principal resolves to guardian context, helper not called", async () => {
|
|
557
|
+
expect(await trustClassFor("test-user")).toBe("guardian");
|
|
558
|
+
expect(reResolveCalls).toEqual([]);
|
|
559
|
+
});
|
|
560
|
+
|
|
561
|
+
test("non-guardian principal: helper consulted, null result stays unknown", async () => {
|
|
562
|
+
mockReResolve = null;
|
|
563
|
+
expect(await trustClassFor("vellum-principal-stranger")).toBe("unknown");
|
|
564
|
+
expect(reResolveCalls).toEqual(["vellum-principal-stranger"]);
|
|
565
|
+
});
|
|
566
|
+
|
|
567
|
+
test("reset drift: helper returns guardian → route adopts it", async () => {
|
|
568
|
+
mockGuardians = [
|
|
569
|
+
{
|
|
570
|
+
channelType: "vellum",
|
|
571
|
+
contactId: "guardian-contact",
|
|
572
|
+
principalId: "vellum-principal-stale",
|
|
573
|
+
address: "vellum-principal-stale",
|
|
574
|
+
status: "active",
|
|
575
|
+
},
|
|
576
|
+
];
|
|
577
|
+
mockReResolve = { trustClass: "guardian", sourceChannel: "vellum" };
|
|
578
|
+
|
|
579
|
+
expect(await trustClassFor("vellum-principal-healed")).toBe("guardian");
|
|
580
|
+
expect(reResolveCalls).toEqual(["vellum-principal-healed"]);
|
|
581
|
+
});
|
|
582
|
+
|
|
583
|
+
test("helper returns an unknown-class ctx → trust stays unknown (not adopted)", async () => {
|
|
584
|
+
mockGuardians = [
|
|
585
|
+
{
|
|
586
|
+
channelType: "vellum",
|
|
587
|
+
contactId: "guardian-contact",
|
|
588
|
+
principalId: "vellum-principal-stale",
|
|
589
|
+
address: "vellum-principal-stale",
|
|
590
|
+
status: "active",
|
|
591
|
+
},
|
|
592
|
+
];
|
|
593
|
+
mockReResolve = { trustClass: "unknown", sourceChannel: "vellum" };
|
|
594
|
+
|
|
595
|
+
expect(await trustClassFor("vellum-principal-healed")).toBe("unknown");
|
|
596
|
+
});
|
|
597
|
+
|
|
598
|
+
test("dev-bypass maps the gateway guardian principal to guardian", async () => {
|
|
599
|
+
expect(await trustClassFor("dev-bypass")).toBe("guardian");
|
|
600
|
+
});
|
|
601
|
+
|
|
602
|
+
test("dev-bypass fails closed to unknown on an empty gateway", async () => {
|
|
603
|
+
// No active gateway binding: dev-bypass cannot translate to a real guardian,
|
|
604
|
+
// and the helper (null) leaves trust unknown — parity with /v1/surface-actions.
|
|
605
|
+
mockGuardians = [];
|
|
606
|
+
mockReResolve = null;
|
|
607
|
+
expect(await trustClassFor("dev-bypass")).toBe("unknown");
|
|
608
|
+
});
|
|
609
|
+
|
|
610
|
+
test("preserves the request body channel on the guardian-match happy path", async () => {
|
|
611
|
+
const ctx = await trustContextFor("test-user", "telegram");
|
|
612
|
+
expect(ctx.trustClass).toBe("guardian");
|
|
613
|
+
expect(ctx.sourceChannel).toBe("telegram");
|
|
614
|
+
});
|
|
615
|
+
});
|
|
@@ -35,6 +35,8 @@ const addMessageCalls: Array<{
|
|
|
35
35
|
}> = [];
|
|
36
36
|
|
|
37
37
|
mock.module("../memory/conversation-crud.js", () => ({
|
|
38
|
+
setConversationProcessingStartedAt: () => {},
|
|
39
|
+
isConversationProcessing: () => false,
|
|
38
40
|
addMessage: async (
|
|
39
41
|
conversationId: string,
|
|
40
42
|
role: string,
|
|
@@ -40,6 +40,8 @@ function resetGatewayIpc() {
|
|
|
40
40
|
gatewayIpc.calls = [];
|
|
41
41
|
}
|
|
42
42
|
|
|
43
|
+
import type { TrustVerdict } from "@vellumai/gateway-client";
|
|
44
|
+
|
|
43
45
|
import {
|
|
44
46
|
findContactChannel,
|
|
45
47
|
getContact,
|
|
@@ -56,6 +58,7 @@ import {
|
|
|
56
58
|
type InviteRedemptionOutcome,
|
|
57
59
|
redeemInvite,
|
|
58
60
|
redeemInviteByCode,
|
|
61
|
+
resolveMemberGateStatus,
|
|
59
62
|
} from "../runtime/invite-redemption-service.js";
|
|
60
63
|
import { hashVoiceCode } from "../util/voice-code.js";
|
|
61
64
|
|
|
@@ -763,3 +766,43 @@ describe("invite-redemption-service", () => {
|
|
|
763
766
|
).toBeNull();
|
|
764
767
|
});
|
|
765
768
|
});
|
|
769
|
+
|
|
770
|
+
describe("resolveMemberGateStatus", () => {
|
|
771
|
+
const memberlessVerdict: TrustVerdict = {
|
|
772
|
+
trustClass: "unverified_contact",
|
|
773
|
+
canonicalSenderId: "telegram:blocked-user",
|
|
774
|
+
};
|
|
775
|
+
const memberVerdict: TrustVerdict = {
|
|
776
|
+
trustClass: "trusted_contact",
|
|
777
|
+
canonicalSenderId: "telegram:active-user",
|
|
778
|
+
contactId: "contact-1",
|
|
779
|
+
channelId: "channel-1",
|
|
780
|
+
type: "telegram",
|
|
781
|
+
address: "active-user",
|
|
782
|
+
status: "active",
|
|
783
|
+
policy: "allow",
|
|
784
|
+
};
|
|
785
|
+
|
|
786
|
+
test("uses the verdict member status when the verdict resolves a member", async () => {
|
|
787
|
+
expect(await resolveMemberGateStatus(memberVerdict, "blocked")).toBe(
|
|
788
|
+
"active",
|
|
789
|
+
);
|
|
790
|
+
});
|
|
791
|
+
|
|
792
|
+
test("falls back to local status when a non-null verdict carries no member", async () => {
|
|
793
|
+
// A previously blocked contact with a valid invite must stay blocked even
|
|
794
|
+
// when the verdict is non-null but memberless (externalChatId-only match /
|
|
795
|
+
// resolutionFailed), so it can't bypass the gate.
|
|
796
|
+
expect(await resolveMemberGateStatus(memberlessVerdict, "blocked")).toBe(
|
|
797
|
+
"blocked",
|
|
798
|
+
);
|
|
799
|
+
});
|
|
800
|
+
|
|
801
|
+
test("falls back to local status when the verdict is null", async () => {
|
|
802
|
+
expect(await resolveMemberGateStatus(null, "blocked")).toBe("blocked");
|
|
803
|
+
});
|
|
804
|
+
|
|
805
|
+
test("returns null when neither verdict member nor local status is present", async () => {
|
|
806
|
+
expect(await resolveMemberGateStatus(memberlessVerdict, null)).toBeNull();
|
|
807
|
+
});
|
|
808
|
+
});
|
|
@@ -1827,4 +1827,109 @@ describe("normalizeLlmContextPayloads", () => {
|
|
|
1827
1827
|
},
|
|
1828
1828
|
]);
|
|
1829
1829
|
});
|
|
1830
|
+
|
|
1831
|
+
describe("provider error response payloads", () => {
|
|
1832
|
+
test("extracts a structured error from a rejected call's response payload", () => {
|
|
1833
|
+
const normalized = normalizeLlmContextPayloads({
|
|
1834
|
+
createdAt: 1_742_400_000_000,
|
|
1835
|
+
requestPayload: {
|
|
1836
|
+
model: "accounts/fireworks/models/glm-5p2",
|
|
1837
|
+
messages: [{ role: "user", content: "hello" }],
|
|
1838
|
+
},
|
|
1839
|
+
responsePayload: {
|
|
1840
|
+
error: {
|
|
1841
|
+
name: "ProviderError",
|
|
1842
|
+
message:
|
|
1843
|
+
"This model doesn't support image input. Remove the image or switch to a vision-capable model.",
|
|
1844
|
+
code: "PROVIDER_ERROR",
|
|
1845
|
+
provider: "fireworks",
|
|
1846
|
+
statusCode: 400,
|
|
1847
|
+
},
|
|
1848
|
+
},
|
|
1849
|
+
});
|
|
1850
|
+
|
|
1851
|
+
expect(normalized.error).toEqual({
|
|
1852
|
+
name: "ProviderError",
|
|
1853
|
+
message:
|
|
1854
|
+
"This model doesn't support image input. Remove the image or switch to a vision-capable model.",
|
|
1855
|
+
code: "PROVIDER_ERROR",
|
|
1856
|
+
provider: "fireworks",
|
|
1857
|
+
statusCode: 400,
|
|
1858
|
+
});
|
|
1859
|
+
// No response sections are produced for an error payload.
|
|
1860
|
+
expect(normalized.responseSections).toBeUndefined();
|
|
1861
|
+
});
|
|
1862
|
+
|
|
1863
|
+
test("preserves a normalized request alongside the error", () => {
|
|
1864
|
+
const normalized = normalizeLlmContextPayloads({
|
|
1865
|
+
createdAt: 1_742_400_000_000,
|
|
1866
|
+
requestPayload: {
|
|
1867
|
+
model: "gpt-4.1",
|
|
1868
|
+
tool_choice: "auto",
|
|
1869
|
+
messages: [{ role: "user", content: "hi" }],
|
|
1870
|
+
},
|
|
1871
|
+
responsePayload: { error: { message: "boom" } },
|
|
1872
|
+
});
|
|
1873
|
+
|
|
1874
|
+
expect(normalized.error).toEqual({ message: "boom" });
|
|
1875
|
+
// The request side still normalizes so the Prompt tab keeps working.
|
|
1876
|
+
expect(normalized.requestSections?.length).toBeGreaterThan(0);
|
|
1877
|
+
expect(normalized.summary?.provider).toBe("openai");
|
|
1878
|
+
});
|
|
1879
|
+
|
|
1880
|
+
test("carries statusCode 0 and retryAfterMs through", () => {
|
|
1881
|
+
const normalized = normalizeLlmContextPayloads({
|
|
1882
|
+
createdAt: 1_742_400_000_000,
|
|
1883
|
+
requestPayload: null,
|
|
1884
|
+
responsePayload: {
|
|
1885
|
+
error: {
|
|
1886
|
+
name: "ProviderError",
|
|
1887
|
+
message: "rate limited",
|
|
1888
|
+
provider: "anthropic",
|
|
1889
|
+
statusCode: 0,
|
|
1890
|
+
retryAfterMs: 1500,
|
|
1891
|
+
},
|
|
1892
|
+
},
|
|
1893
|
+
});
|
|
1894
|
+
|
|
1895
|
+
expect(normalized.error).toEqual({
|
|
1896
|
+
name: "ProviderError",
|
|
1897
|
+
message: "rate limited",
|
|
1898
|
+
provider: "anthropic",
|
|
1899
|
+
statusCode: 0,
|
|
1900
|
+
retryAfterMs: 1500,
|
|
1901
|
+
});
|
|
1902
|
+
});
|
|
1903
|
+
|
|
1904
|
+
test("does not treat a successful response with no error as failed", () => {
|
|
1905
|
+
const normalized = normalizeLlmContextPayloads({
|
|
1906
|
+
createdAt: 1_742_400_000_000,
|
|
1907
|
+
requestPayload: {
|
|
1908
|
+
model: "gpt-4.1",
|
|
1909
|
+
messages: [{ role: "user", content: "hi" }],
|
|
1910
|
+
},
|
|
1911
|
+
responsePayload: {
|
|
1912
|
+
model: "gpt-4.1",
|
|
1913
|
+
choices: [
|
|
1914
|
+
{
|
|
1915
|
+
finish_reason: "stop",
|
|
1916
|
+
message: { role: "assistant", content: "hello" },
|
|
1917
|
+
},
|
|
1918
|
+
],
|
|
1919
|
+
},
|
|
1920
|
+
});
|
|
1921
|
+
|
|
1922
|
+
expect(normalized.error).toBeUndefined();
|
|
1923
|
+
});
|
|
1924
|
+
|
|
1925
|
+
test("ignores an empty error object with no identifying fields", () => {
|
|
1926
|
+
const normalized = normalizeLlmContextPayloads({
|
|
1927
|
+
createdAt: 1_742_400_000_000,
|
|
1928
|
+
requestPayload: null,
|
|
1929
|
+
responsePayload: { error: {} },
|
|
1930
|
+
});
|
|
1931
|
+
|
|
1932
|
+
expect(normalized.error).toBeUndefined();
|
|
1933
|
+
});
|
|
1934
|
+
});
|
|
1830
1935
|
});
|