@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
|
@@ -0,0 +1,212 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Unit tests for the contacts read API.
|
|
3
|
+
*
|
|
4
|
+
* `handleListContacts`, `handleGetContact`, and the `search_contacts`
|
|
5
|
+
* no-filter case relay to the gateway rich read (`contacts_list_rich` /
|
|
6
|
+
* `contacts_get_rich`), which is the source of truth for the ACL fields
|
|
7
|
+
* (`role`/`status`/`policy`/`verifiedAt`/`interactionCount`/`lastInteraction`).
|
|
8
|
+
* These tests assert the serialized response shape is gateway-sourced and
|
|
9
|
+
* unchanged for the web client, that no read path falls back to the assistant
|
|
10
|
+
* DB, and that a relay failure fails closed (surfaces an error) instead of
|
|
11
|
+
* reading local ACL.
|
|
12
|
+
*/
|
|
13
|
+
|
|
14
|
+
import { beforeEach, describe, expect, mock, test } from "bun:test";
|
|
15
|
+
|
|
16
|
+
import { IpcCallError } from "@vellumai/gateway-client/ipc-client";
|
|
17
|
+
|
|
18
|
+
let ipcCalls: { method: string; params?: Record<string, unknown> }[] = [];
|
|
19
|
+
let ipcResult: unknown = {};
|
|
20
|
+
let ipcError: Error | undefined;
|
|
21
|
+
|
|
22
|
+
const ipcCallPersistentMock = mock(
|
|
23
|
+
async (method: string, params?: Record<string, unknown>) => {
|
|
24
|
+
ipcCalls.push({ method, params });
|
|
25
|
+
if (ipcError) throw ipcError;
|
|
26
|
+
return ipcResult;
|
|
27
|
+
},
|
|
28
|
+
);
|
|
29
|
+
|
|
30
|
+
const actualGatewayClient = await import("../../../ipc/gateway-client.js");
|
|
31
|
+
|
|
32
|
+
mock.module("../../../ipc/gateway-client.js", () => ({
|
|
33
|
+
...actualGatewayClient,
|
|
34
|
+
ipcCallPersistent: ipcCallPersistentMock,
|
|
35
|
+
}));
|
|
36
|
+
|
|
37
|
+
// Guard: fail loudly if any read path falls back to the assistant DB for ACL
|
|
38
|
+
// fields. The relay read paths must source role/status/stats from the gateway.
|
|
39
|
+
const actualContactStore = await import("../../../contacts/contact-store.js");
|
|
40
|
+
|
|
41
|
+
const contactStoreReadGuard = mock(() => {
|
|
42
|
+
throw new Error(
|
|
43
|
+
"assistant contact-store read must not happen on the gateway relay path",
|
|
44
|
+
);
|
|
45
|
+
});
|
|
46
|
+
|
|
47
|
+
mock.module("../../../contacts/contact-store.js", () => ({
|
|
48
|
+
...actualContactStore,
|
|
49
|
+
getContact: contactStoreReadGuard,
|
|
50
|
+
listContacts: contactStoreReadGuard,
|
|
51
|
+
getAssistantContactMetadata: contactStoreReadGuard,
|
|
52
|
+
}));
|
|
53
|
+
|
|
54
|
+
const { handleListContacts, handleGetContact, ROUTES } = await import(
|
|
55
|
+
"../contact-routes.js"
|
|
56
|
+
);
|
|
57
|
+
|
|
58
|
+
const gatewayChannel = {
|
|
59
|
+
id: "ch_1",
|
|
60
|
+
contactId: "ct_1",
|
|
61
|
+
type: "sms",
|
|
62
|
+
address: "+15550100",
|
|
63
|
+
isPrimary: true,
|
|
64
|
+
externalUserId: null,
|
|
65
|
+
status: "active",
|
|
66
|
+
policy: "allow",
|
|
67
|
+
verifiedAt: 1700,
|
|
68
|
+
verifiedVia: "invite",
|
|
69
|
+
lastSeenAt: 1800,
|
|
70
|
+
interactionCount: 7,
|
|
71
|
+
lastInteraction: 1900,
|
|
72
|
+
revokedReason: null,
|
|
73
|
+
blockedReason: null,
|
|
74
|
+
};
|
|
75
|
+
|
|
76
|
+
const gatewayContact = {
|
|
77
|
+
id: "ct_1",
|
|
78
|
+
displayName: "Alice",
|
|
79
|
+
role: "member",
|
|
80
|
+
notes: "a note",
|
|
81
|
+
contactType: "human",
|
|
82
|
+
lastInteraction: 1900,
|
|
83
|
+
interactionCount: 7,
|
|
84
|
+
createdAt: 1000,
|
|
85
|
+
updatedAt: 1500,
|
|
86
|
+
channels: [gatewayChannel],
|
|
87
|
+
};
|
|
88
|
+
|
|
89
|
+
describe("contacts read API relays from the gateway", () => {
|
|
90
|
+
beforeEach(() => {
|
|
91
|
+
ipcCalls = [];
|
|
92
|
+
ipcResult = {};
|
|
93
|
+
ipcError = undefined;
|
|
94
|
+
ipcCallPersistentMock.mockClear();
|
|
95
|
+
contactStoreReadGuard.mockClear();
|
|
96
|
+
});
|
|
97
|
+
|
|
98
|
+
test("list relays to contacts_list_rich and serializes the gateway ACL fields", async () => {
|
|
99
|
+
ipcResult = { ok: true, contacts: [gatewayContact] };
|
|
100
|
+
|
|
101
|
+
const result = await handleListContacts({ limit: "50" });
|
|
102
|
+
|
|
103
|
+
expect(ipcCalls).toEqual([
|
|
104
|
+
{ method: "contacts_list_rich", params: { limit: 50 } },
|
|
105
|
+
]);
|
|
106
|
+
expect(result.ok).toBe(true);
|
|
107
|
+
expect(result.contacts).toHaveLength(1);
|
|
108
|
+
|
|
109
|
+
const [contact] = result.contacts;
|
|
110
|
+
// ACL fields are gateway-sourced and reach the web client unchanged.
|
|
111
|
+
expect(contact.role).toBe("member");
|
|
112
|
+
expect(contact.interactionCount).toBe(7);
|
|
113
|
+
expect(contact.lastInteraction).toBe(1900);
|
|
114
|
+
const channel = contact.channels[0] as Record<string, unknown>;
|
|
115
|
+
expect(channel.status).toBe("active");
|
|
116
|
+
expect(channel.policy).toBe("allow");
|
|
117
|
+
expect(channel.verifiedAt).toBe(1700);
|
|
118
|
+
expect(channel.interactionCount).toBe(7);
|
|
119
|
+
expect(channel.lastInteraction).toBe(1900);
|
|
120
|
+
// Channel compat echoes address into externalUserId for older clients.
|
|
121
|
+
expect(channel.externalUserId).toBe("+15550100");
|
|
122
|
+
|
|
123
|
+
expect(contactStoreReadGuard).not.toHaveBeenCalled();
|
|
124
|
+
});
|
|
125
|
+
|
|
126
|
+
test("list forwards the role filter to the gateway", async () => {
|
|
127
|
+
ipcResult = { ok: true, contacts: [] };
|
|
128
|
+
|
|
129
|
+
await handleListContacts({ limit: "10", role: "guardian" });
|
|
130
|
+
|
|
131
|
+
expect(ipcCalls).toEqual([
|
|
132
|
+
{
|
|
133
|
+
method: "contacts_list_rich",
|
|
134
|
+
params: { limit: 10, role: "guardian" },
|
|
135
|
+
},
|
|
136
|
+
]);
|
|
137
|
+
});
|
|
138
|
+
|
|
139
|
+
test("list fails closed when the gateway relay is unavailable", async () => {
|
|
140
|
+
ipcError = new IpcCallError("gateway down", {
|
|
141
|
+
statusCode: 503,
|
|
142
|
+
errorCode: "UNAVAILABLE",
|
|
143
|
+
});
|
|
144
|
+
|
|
145
|
+
await expect(handleListContacts({ limit: "50" })).rejects.toMatchObject({
|
|
146
|
+
message: "gateway down",
|
|
147
|
+
statusCode: 503,
|
|
148
|
+
});
|
|
149
|
+
// No assistant-DB fallback read.
|
|
150
|
+
expect(contactStoreReadGuard).not.toHaveBeenCalled();
|
|
151
|
+
});
|
|
152
|
+
|
|
153
|
+
test("get relays to contacts_get_rich and serializes the gateway ACL fields", async () => {
|
|
154
|
+
ipcResult = { ok: true, contact: gatewayContact };
|
|
155
|
+
|
|
156
|
+
const result = await handleGetContact("ct_1");
|
|
157
|
+
|
|
158
|
+
expect(ipcCalls).toEqual([
|
|
159
|
+
{ method: "contacts_get_rich", params: { contactId: "ct_1" } },
|
|
160
|
+
]);
|
|
161
|
+
expect(result.ok).toBe(true);
|
|
162
|
+
expect(result.contact.role).toBe("member");
|
|
163
|
+
expect(result.contact.interactionCount).toBe(7);
|
|
164
|
+
const channel = result.contact.channels[0] as Record<string, unknown>;
|
|
165
|
+
expect(channel.status).toBe("active");
|
|
166
|
+
expect(channel.externalUserId).toBe("+15550100");
|
|
167
|
+
expect(contactStoreReadGuard).not.toHaveBeenCalled();
|
|
168
|
+
});
|
|
169
|
+
|
|
170
|
+
test("get surfaces a clean gateway not-found as a 404", async () => {
|
|
171
|
+
ipcResult = { ok: true };
|
|
172
|
+
|
|
173
|
+
await expect(handleGetContact("missing")).rejects.toMatchObject({
|
|
174
|
+
statusCode: 404,
|
|
175
|
+
});
|
|
176
|
+
expect(contactStoreReadGuard).not.toHaveBeenCalled();
|
|
177
|
+
});
|
|
178
|
+
|
|
179
|
+
test("get fails closed on a relay failure (no assistant-DB fallback)", async () => {
|
|
180
|
+
ipcError = new IpcCallError("gateway down", {
|
|
181
|
+
statusCode: 503,
|
|
182
|
+
errorCode: "UNAVAILABLE",
|
|
183
|
+
});
|
|
184
|
+
|
|
185
|
+
await expect(handleGetContact("ct_1")).rejects.toMatchObject({
|
|
186
|
+
message: "gateway down",
|
|
187
|
+
statusCode: 503,
|
|
188
|
+
});
|
|
189
|
+
expect(contactStoreReadGuard).not.toHaveBeenCalled();
|
|
190
|
+
});
|
|
191
|
+
|
|
192
|
+
test("search no-filter relays to the gateway list read", async () => {
|
|
193
|
+
ipcResult = { ok: true, contacts: [gatewayContact] };
|
|
194
|
+
|
|
195
|
+
const route = ROUTES.find((r) => r.operationId === "search_contacts");
|
|
196
|
+
expect(route).toBeDefined();
|
|
197
|
+
|
|
198
|
+
const contacts = (await route!.handler({ body: {} })) as Array<{
|
|
199
|
+
role: string;
|
|
200
|
+
interactionCount: number;
|
|
201
|
+
channels: { status: string }[];
|
|
202
|
+
}>;
|
|
203
|
+
|
|
204
|
+
expect(ipcCalls).toEqual([
|
|
205
|
+
{ method: "contacts_list_rich", params: { limit: 50 } },
|
|
206
|
+
]);
|
|
207
|
+
expect(contacts[0].role).toBe("member");
|
|
208
|
+
expect(contacts[0].interactionCount).toBe(7);
|
|
209
|
+
expect(contacts[0].channels[0].status).toBe("active");
|
|
210
|
+
expect(contactStoreReadGuard).not.toHaveBeenCalled();
|
|
211
|
+
});
|
|
212
|
+
});
|
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Unit tests for the global-search contacts ordering source.
|
|
3
|
+
*
|
|
4
|
+
* Recency ordering for contacts surfaces `contacts.updatedAt`, never the
|
|
5
|
+
* channel-derived `lastInteraction` column. Daemon-native contact search
|
|
6
|
+
* carries no gateway-relayed ContactRead, so `updatedAt` is the deterministic
|
|
7
|
+
* ordering key.
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
import { afterEach, describe, expect, mock, test } from "bun:test";
|
|
11
|
+
|
|
12
|
+
import type { ContactWithChannels } from "../../../contacts/types.js";
|
|
13
|
+
|
|
14
|
+
let searchContactsResult: ContactWithChannels[] = [];
|
|
15
|
+
|
|
16
|
+
const searchContactsMock = mock(() => searchContactsResult);
|
|
17
|
+
|
|
18
|
+
const actualContactStore = await import("../../../contacts/contact-store.js");
|
|
19
|
+
|
|
20
|
+
mock.module("../../../contacts/contact-store.js", () => ({
|
|
21
|
+
...actualContactStore,
|
|
22
|
+
searchContacts: searchContactsMock,
|
|
23
|
+
}));
|
|
24
|
+
|
|
25
|
+
const { ROUTES } = await import("../global-search-routes.js");
|
|
26
|
+
|
|
27
|
+
const handler = ROUTES.find((r) => r.operationId === "search_global")!.handler;
|
|
28
|
+
|
|
29
|
+
function makeContact(
|
|
30
|
+
overrides: Partial<ContactWithChannels>,
|
|
31
|
+
): ContactWithChannels {
|
|
32
|
+
return {
|
|
33
|
+
id: "ct_1",
|
|
34
|
+
displayName: "Alice",
|
|
35
|
+
notes: null,
|
|
36
|
+
lastInteraction: null,
|
|
37
|
+
interactionCount: 0,
|
|
38
|
+
createdAt: 1,
|
|
39
|
+
updatedAt: 1,
|
|
40
|
+
role: "contact",
|
|
41
|
+
contactType: "human",
|
|
42
|
+
principalId: null,
|
|
43
|
+
userFile: null,
|
|
44
|
+
channels: [],
|
|
45
|
+
...overrides,
|
|
46
|
+
};
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
afterEach(() => {
|
|
50
|
+
searchContactsResult = [];
|
|
51
|
+
searchContactsMock.mockClear();
|
|
52
|
+
});
|
|
53
|
+
|
|
54
|
+
describe("global-search contacts recency source", () => {
|
|
55
|
+
test("surfaces contacts.updatedAt as lastInteraction, not the channel column", async () => {
|
|
56
|
+
searchContactsResult = [
|
|
57
|
+
makeContact({
|
|
58
|
+
id: "ct_1",
|
|
59
|
+
displayName: "Alice",
|
|
60
|
+
updatedAt: 5000,
|
|
61
|
+
lastInteraction: 9999,
|
|
62
|
+
}),
|
|
63
|
+
];
|
|
64
|
+
|
|
65
|
+
const result = (await handler({
|
|
66
|
+
queryParams: { q: "ali", categories: "contacts" },
|
|
67
|
+
})) as { results: { contacts: { id: string; lastInteraction: number }[] } };
|
|
68
|
+
|
|
69
|
+
expect(result.results.contacts).toHaveLength(1);
|
|
70
|
+
expect(result.results.contacts[0].lastInteraction).toBe(5000);
|
|
71
|
+
});
|
|
72
|
+
|
|
73
|
+
test("preserves searchContacts ordering deterministically", async () => {
|
|
74
|
+
searchContactsResult = [
|
|
75
|
+
makeContact({ id: "ct_a", displayName: "A", updatedAt: 300 }),
|
|
76
|
+
makeContact({ id: "ct_b", displayName: "B", updatedAt: 200 }),
|
|
77
|
+
makeContact({ id: "ct_c", displayName: "C", updatedAt: 100 }),
|
|
78
|
+
];
|
|
79
|
+
|
|
80
|
+
const result = (await handler({
|
|
81
|
+
queryParams: { q: "x", categories: "contacts" },
|
|
82
|
+
})) as { results: { contacts: { id: string; lastInteraction: number }[] } };
|
|
83
|
+
|
|
84
|
+
expect(result.results.contacts.map((c) => c.id)).toEqual([
|
|
85
|
+
"ct_a",
|
|
86
|
+
"ct_b",
|
|
87
|
+
"ct_c",
|
|
88
|
+
]);
|
|
89
|
+
expect(result.results.contacts.map((c) => c.lastInteraction)).toEqual([
|
|
90
|
+
300, 200, 100,
|
|
91
|
+
]);
|
|
92
|
+
});
|
|
93
|
+
});
|
|
@@ -25,10 +25,12 @@ interface StubConversation {
|
|
|
25
25
|
handleSurfaceActionThrows?: Error;
|
|
26
26
|
handleSurfaceUndoCalled?: boolean;
|
|
27
27
|
handleSurfaceUndoThrows?: Error;
|
|
28
|
+
trustContext?: { trustClass: string; sourceChannel: string };
|
|
28
29
|
surfaceActionCalls: Array<{
|
|
29
30
|
surfaceId: string;
|
|
30
31
|
actionId: string;
|
|
31
32
|
data: unknown;
|
|
33
|
+
sourceActorPrincipalId?: string;
|
|
32
34
|
}>;
|
|
33
35
|
}
|
|
34
36
|
|
|
@@ -42,6 +44,47 @@ const findBySurfaceCalls: string[] = [];
|
|
|
42
44
|
const getOrCreateCalls: string[] = [];
|
|
43
45
|
const rawGetCalls: Array<{ sql: string; params: unknown[] }> = [];
|
|
44
46
|
|
|
47
|
+
// Gateway guardian-delivery list (shared by the route's dev-bypass lookup and
|
|
48
|
+
// the local-principal-trust mapper): null = unreachable, [] = no guardian.
|
|
49
|
+
let mockGuardianList: Array<Record<string, unknown>> | null = [];
|
|
50
|
+
let httpAuthDisabled = false;
|
|
51
|
+
|
|
52
|
+
// Stub for the shared reset-drift helper. The route under test only consumes
|
|
53
|
+
// its result (a guardian TrustContext or null); the gate itself is covered in
|
|
54
|
+
// runtime/__tests__/guardian-vellum-migration.test.ts. Tests set
|
|
55
|
+
// `mockReResolve` per case and read `reResolveCalls` to assert routing.
|
|
56
|
+
const reResolveCalls: string[] = [];
|
|
57
|
+
let mockReResolve: { trustClass: string; sourceChannel: string } | null = null;
|
|
58
|
+
|
|
59
|
+
mock.module("../../../contacts/guardian-delivery-reader.js", () => ({
|
|
60
|
+
getGuardianDelivery: (_input?: { channelTypes?: string[] }) =>
|
|
61
|
+
Promise.resolve(mockGuardianList),
|
|
62
|
+
peekCachedGuardianDelivery: () => mockGuardianList ?? undefined,
|
|
63
|
+
guardianForChannel: (
|
|
64
|
+
list: Array<Record<string, unknown>>,
|
|
65
|
+
channelType: string,
|
|
66
|
+
) => list.find((g) => g.channelType === channelType && g.status === "active"),
|
|
67
|
+
}));
|
|
68
|
+
|
|
69
|
+
mock.module("../../../contacts/contact-store.js", () => ({
|
|
70
|
+
findGuardianForChannel: (_channelType: string) => null,
|
|
71
|
+
findContactByAddress: () => null,
|
|
72
|
+
}));
|
|
73
|
+
|
|
74
|
+
mock.module("../../../config/env.js", () => ({
|
|
75
|
+
isHttpAuthDisabled: () => httpAuthDisabled,
|
|
76
|
+
}));
|
|
77
|
+
|
|
78
|
+
mock.module("../../guardian-vellum-migration.js", () => ({
|
|
79
|
+
reResolveTrustOnResetDrift: async (
|
|
80
|
+
incomingPrincipalId: string,
|
|
81
|
+
_sourceChannel: string,
|
|
82
|
+
) => {
|
|
83
|
+
reResolveCalls.push(incomingPrincipalId);
|
|
84
|
+
return mockReResolve;
|
|
85
|
+
},
|
|
86
|
+
}));
|
|
87
|
+
|
|
45
88
|
mock.module("../../../daemon/conversation-registry.js", () => ({
|
|
46
89
|
findConversation: (id: string) => {
|
|
47
90
|
findConvCalls.push(id);
|
|
@@ -106,8 +149,14 @@ function makeStub(id: string): StubConversation {
|
|
|
106
149
|
surfaceId: string,
|
|
107
150
|
actionId: string,
|
|
108
151
|
data: unknown,
|
|
152
|
+
sourceActorPrincipalId?: string,
|
|
109
153
|
) => {
|
|
110
|
-
stub.surfaceActionCalls.push({
|
|
154
|
+
stub.surfaceActionCalls.push({
|
|
155
|
+
surfaceId,
|
|
156
|
+
actionId,
|
|
157
|
+
data,
|
|
158
|
+
sourceActorPrincipalId,
|
|
159
|
+
});
|
|
111
160
|
if (stub.handleSurfaceActionThrows) throw stub.handleSurfaceActionThrows;
|
|
112
161
|
return stub.handleSurfaceActionResult;
|
|
113
162
|
},
|
|
@@ -115,6 +164,9 @@ function makeStub(id: string): StubConversation {
|
|
|
115
164
|
stub.handleSurfaceUndoCalled = true;
|
|
116
165
|
if (stub.handleSurfaceUndoThrows) throw stub.handleSurfaceUndoThrows;
|
|
117
166
|
},
|
|
167
|
+
setTrustContext: (ctx: { trustClass: string; sourceChannel: string }) => {
|
|
168
|
+
stub.trustContext = ctx;
|
|
169
|
+
},
|
|
118
170
|
});
|
|
119
171
|
return stub;
|
|
120
172
|
}
|
|
@@ -132,6 +184,10 @@ beforeEach(() => {
|
|
|
132
184
|
findBySurfaceCalls.length = 0;
|
|
133
185
|
getOrCreateCalls.length = 0;
|
|
134
186
|
rawGetCalls.length = 0;
|
|
187
|
+
mockGuardianList = [];
|
|
188
|
+
httpAuthDisabled = false;
|
|
189
|
+
reResolveCalls.length = 0;
|
|
190
|
+
mockReResolve = null;
|
|
135
191
|
});
|
|
136
192
|
|
|
137
193
|
// ---------------------------------------------------------------------------
|
|
@@ -310,6 +366,50 @@ describe("triggerSurfaceAction handler", () => {
|
|
|
310
366
|
expect(rawGetCalls[0]!.params).toEqual([`%"surfaceId":"\\%"%`]);
|
|
311
367
|
});
|
|
312
368
|
|
|
369
|
+
test("threads x-vellum-actor-principal-id into handleSurfaceAction", async () => {
|
|
370
|
+
const live = makeStub("conv-principal");
|
|
371
|
+
memoryBySurface = live;
|
|
372
|
+
|
|
373
|
+
const handler = findHandler("triggerSurfaceAction");
|
|
374
|
+
await handler({
|
|
375
|
+
body: { surfaceId: "surf-p", actionId: "act-p" },
|
|
376
|
+
headers: { "x-vellum-actor-principal-id": "principal-committer" },
|
|
377
|
+
});
|
|
378
|
+
|
|
379
|
+
expect(live.surfaceActionCalls).toEqual([
|
|
380
|
+
{
|
|
381
|
+
surfaceId: "surf-p",
|
|
382
|
+
actionId: "act-p",
|
|
383
|
+
data: undefined,
|
|
384
|
+
sourceActorPrincipalId: "principal-committer",
|
|
385
|
+
},
|
|
386
|
+
]);
|
|
387
|
+
});
|
|
388
|
+
|
|
389
|
+
test("resolves dev-bypass to the guardian principal before threading the turn", async () => {
|
|
390
|
+
httpAuthDisabled = true;
|
|
391
|
+
mockGuardianList = [guardianDelivery(GUARDIAN_PRINCIPAL)];
|
|
392
|
+
const live = makeStub("conv-dev-thread");
|
|
393
|
+
memoryBySurface = live;
|
|
394
|
+
|
|
395
|
+
const handler = findHandler("triggerSurfaceAction");
|
|
396
|
+
await handler({
|
|
397
|
+
body: { surfaceId: "surf-dt", actionId: "act-dt" },
|
|
398
|
+
headers: { "x-vellum-actor-principal-id": "dev-bypass" },
|
|
399
|
+
});
|
|
400
|
+
|
|
401
|
+
// dev-bypass is translated so the surface turn matches the SSE host-proxy
|
|
402
|
+
// client's registered guardian principal (CU/app-control same-actor check).
|
|
403
|
+
expect(live.surfaceActionCalls).toEqual([
|
|
404
|
+
{
|
|
405
|
+
surfaceId: "surf-dt",
|
|
406
|
+
actionId: "act-dt",
|
|
407
|
+
data: undefined,
|
|
408
|
+
sourceActorPrincipalId: GUARDIAN_PRINCIPAL,
|
|
409
|
+
},
|
|
410
|
+
]);
|
|
411
|
+
});
|
|
412
|
+
|
|
313
413
|
test("propagates accepted=false rejection as BadRequestError", async () => {
|
|
314
414
|
const live = makeStub("conv-reject");
|
|
315
415
|
live.handleSurfaceActionResult = {
|
|
@@ -335,6 +435,120 @@ describe("triggerSurfaceAction handler", () => {
|
|
|
335
435
|
});
|
|
336
436
|
});
|
|
337
437
|
|
|
438
|
+
// ---------------------------------------------------------------------------
|
|
439
|
+
// Trust context resolution
|
|
440
|
+
// ---------------------------------------------------------------------------
|
|
441
|
+
|
|
442
|
+
const GUARDIAN_PRINCIPAL = "principal-guardian";
|
|
443
|
+
// Daemon-minted vellum-principal-* ids: the DB-reset drift signature. The
|
|
444
|
+
// gateway rebinds to a fresh id while the client still holds a JWT for the old.
|
|
445
|
+
const VELLUM_PRINCIPAL_OLD = "vellum-principal-old";
|
|
446
|
+
const VELLUM_PRINCIPAL_NEW = "vellum-principal-new";
|
|
447
|
+
|
|
448
|
+
function guardianDelivery(principalId: string): Record<string, unknown> {
|
|
449
|
+
return {
|
|
450
|
+
channelType: "vellum",
|
|
451
|
+
contactId: "contact-1",
|
|
452
|
+
principalId,
|
|
453
|
+
address: "guardian-address",
|
|
454
|
+
externalChatId: "guardian-chat",
|
|
455
|
+
status: "active",
|
|
456
|
+
};
|
|
457
|
+
}
|
|
458
|
+
|
|
459
|
+
// A guardian TrustContext the stubbed helper hands back on a recovered drift.
|
|
460
|
+
function guardianCtx(): { trustClass: string; sourceChannel: string } {
|
|
461
|
+
return { trustClass: "guardian", sourceChannel: "vellum" };
|
|
462
|
+
}
|
|
463
|
+
|
|
464
|
+
describe("triggerSurfaceAction trust context", () => {
|
|
465
|
+
test("guardian principal → guardian from the gateway binding, helper not called", async () => {
|
|
466
|
+
mockGuardianList = [guardianDelivery(GUARDIAN_PRINCIPAL)];
|
|
467
|
+
const live = makeStub("conv-guardian");
|
|
468
|
+
memoryBySurface = live;
|
|
469
|
+
|
|
470
|
+
const handler = findHandler("triggerSurfaceAction");
|
|
471
|
+
await handler({
|
|
472
|
+
body: { surfaceId: "surf-g", actionId: "act-g" },
|
|
473
|
+
headers: { "x-vellum-actor-principal-id": GUARDIAN_PRINCIPAL },
|
|
474
|
+
});
|
|
475
|
+
|
|
476
|
+
expect(live.trustContext?.trustClass).toBe("guardian");
|
|
477
|
+
expect(live.trustContext?.sourceChannel).toBe("vellum");
|
|
478
|
+
// First-pass resolve already granted guardian, so the drift helper is skipped.
|
|
479
|
+
expect(reResolveCalls).toEqual([]);
|
|
480
|
+
});
|
|
481
|
+
|
|
482
|
+
test("unknown principal: helper consulted, null result stays unknown (fail closed)", async () => {
|
|
483
|
+
mockGuardianList = [guardianDelivery(GUARDIAN_PRINCIPAL)];
|
|
484
|
+
mockReResolve = null;
|
|
485
|
+
const live = makeStub("conv-unknown");
|
|
486
|
+
memoryBySurface = live;
|
|
487
|
+
|
|
488
|
+
const handler = findHandler("triggerSurfaceAction");
|
|
489
|
+
await handler({
|
|
490
|
+
body: { surfaceId: "surf-u", actionId: "act-u" },
|
|
491
|
+
headers: { "x-vellum-actor-principal-id": VELLUM_PRINCIPAL_OLD },
|
|
492
|
+
});
|
|
493
|
+
|
|
494
|
+
expect(reResolveCalls).toEqual([VELLUM_PRINCIPAL_OLD]);
|
|
495
|
+
expect(live.trustContext?.trustClass).toBe("unknown");
|
|
496
|
+
});
|
|
497
|
+
|
|
498
|
+
test("reset drift: helper returns guardian → route adopts it", async () => {
|
|
499
|
+
mockGuardianList = [guardianDelivery(VELLUM_PRINCIPAL_NEW)];
|
|
500
|
+
mockReResolve = guardianCtx();
|
|
501
|
+
const live = makeStub("conv-drift");
|
|
502
|
+
memoryBySurface = live;
|
|
503
|
+
|
|
504
|
+
const handler = findHandler("triggerSurfaceAction");
|
|
505
|
+
await handler({
|
|
506
|
+
body: { surfaceId: "surf-d", actionId: "act-d" },
|
|
507
|
+
headers: { "x-vellum-actor-principal-id": VELLUM_PRINCIPAL_OLD },
|
|
508
|
+
});
|
|
509
|
+
|
|
510
|
+
expect(reResolveCalls).toEqual([VELLUM_PRINCIPAL_OLD]);
|
|
511
|
+
expect(live.trustContext?.trustClass).toBe("guardian");
|
|
512
|
+
});
|
|
513
|
+
|
|
514
|
+
test("dev-bypass resolves the real guardian principal from the gateway, helper not called", async () => {
|
|
515
|
+
httpAuthDisabled = true;
|
|
516
|
+
mockGuardianList = [guardianDelivery(GUARDIAN_PRINCIPAL)];
|
|
517
|
+
const live = makeStub("conv-dev");
|
|
518
|
+
memoryBySurface = live;
|
|
519
|
+
|
|
520
|
+
const handler = findHandler("triggerSurfaceAction");
|
|
521
|
+
await handler({
|
|
522
|
+
body: { surfaceId: "surf-dev", actionId: "act-dev" },
|
|
523
|
+
headers: { "x-vellum-actor-principal-id": "dev-bypass" },
|
|
524
|
+
});
|
|
525
|
+
|
|
526
|
+
// The synthetic dev-bypass principal is translated to the real guardian,
|
|
527
|
+
// yielding a guardian trust context without consulting the drift helper.
|
|
528
|
+
expect(live.trustContext?.trustClass).toBe("guardian");
|
|
529
|
+
expect(reResolveCalls).toEqual([]);
|
|
530
|
+
});
|
|
531
|
+
|
|
532
|
+
test("dev-bypass with an empty gateway: helper null result → unknown (fail closed)", async () => {
|
|
533
|
+
httpAuthDisabled = true;
|
|
534
|
+
// The gateway has no active binding, so dev-bypass cannot translate to a
|
|
535
|
+
// real guardian; the first-pass resolve is unknown and the helper, returning
|
|
536
|
+
// null, leaves trust unknown.
|
|
537
|
+
mockGuardianList = [];
|
|
538
|
+
mockReResolve = null;
|
|
539
|
+
const live = makeStub("conv-dev-fallback");
|
|
540
|
+
memoryBySurface = live;
|
|
541
|
+
|
|
542
|
+
const handler = findHandler("triggerSurfaceAction");
|
|
543
|
+
await handler({
|
|
544
|
+
body: { surfaceId: "surf-devf", actionId: "act-devf" },
|
|
545
|
+
headers: { "x-vellum-actor-principal-id": "dev-bypass" },
|
|
546
|
+
});
|
|
547
|
+
|
|
548
|
+
expect(live.trustContext?.trustClass).toBe("unknown");
|
|
549
|
+
});
|
|
550
|
+
});
|
|
551
|
+
|
|
338
552
|
// ---------------------------------------------------------------------------
|
|
339
553
|
// undoSurfaceAction
|
|
340
554
|
// ---------------------------------------------------------------------------
|
|
@@ -92,7 +92,7 @@ async function handleBrowserExecute({
|
|
|
92
92
|
// register with (dev-bypass otherwise mismatches).
|
|
93
93
|
const headerActor =
|
|
94
94
|
headers["x-vellum-actor-principal-id"]?.trim() || undefined;
|
|
95
|
-
const sourceActorPrincipalId = resolveActorPrincipalIdForLocalGuardian(
|
|
95
|
+
const sourceActorPrincipalId = await resolveActorPrincipalIdForLocalGuardian(
|
|
96
96
|
conversation?.getTurnActorPrincipalId() ?? headerActor,
|
|
97
97
|
);
|
|
98
98
|
|
|
@@ -179,7 +179,7 @@ export async function handleCreateVerificationSession({
|
|
|
179
179
|
}
|
|
180
180
|
|
|
181
181
|
// Inbound challenge path
|
|
182
|
-
const result = createInboundChallenge(channel, rebind, conversationId);
|
|
182
|
+
const result = await createInboundChallenge(channel, rebind, conversationId);
|
|
183
183
|
if (!result.success) {
|
|
184
184
|
throw new BadRequestError(
|
|
185
185
|
(result as { message?: string }).message ??
|
|
@@ -192,12 +192,12 @@ export async function handleCreateVerificationSession({
|
|
|
192
192
|
/**
|
|
193
193
|
* GET /v1/channel-verification-sessions/status
|
|
194
194
|
*/
|
|
195
|
-
function handleGetVerificationStatus({
|
|
195
|
+
async function handleGetVerificationStatus({
|
|
196
196
|
queryParams = {},
|
|
197
197
|
body = {},
|
|
198
198
|
}: RouteHandlerArgs) {
|
|
199
199
|
const channel = (queryParams.channel ?? (body as Record<string, unknown>).channel) as ChannelId | undefined;
|
|
200
|
-
return getVerificationStatus(channel);
|
|
200
|
+
return await getVerificationStatus(channel);
|
|
201
201
|
}
|
|
202
202
|
|
|
203
203
|
/**
|
|
@@ -17,8 +17,6 @@ import { IpcCallError } from "@vellumai/gateway-client/ipc-client";
|
|
|
17
17
|
import { z } from "zod";
|
|
18
18
|
|
|
19
19
|
import {
|
|
20
|
-
getAssistantContactMetadata,
|
|
21
|
-
getContact,
|
|
22
20
|
listContacts,
|
|
23
21
|
mergeContacts,
|
|
24
22
|
searchContacts,
|
|
@@ -139,9 +137,10 @@ const contactSchema = z.object({
|
|
|
139
137
|
|
|
140
138
|
/**
|
|
141
139
|
* Relay a non-search contact list read to the gateway (source of truth for ACL
|
|
142
|
-
* fields)
|
|
143
|
-
*
|
|
144
|
-
*
|
|
140
|
+
* fields). Shared by the GET `contacts` list and the `search_contacts`
|
|
141
|
+
* no-filter case so both serve gateway-sourced data consistently. Fail-closed:
|
|
142
|
+
* a relay failure surfaces as an error rather than reading ACL from the
|
|
143
|
+
* assistant DB.
|
|
145
144
|
*/
|
|
146
145
|
async function relayListContacts(
|
|
147
146
|
limit: number,
|
|
@@ -158,15 +157,7 @@ async function relayListContacts(
|
|
|
158
157
|
contacts: contacts.map(prepareContactResponse),
|
|
159
158
|
};
|
|
160
159
|
} catch (err) {
|
|
161
|
-
|
|
162
|
-
{ err },
|
|
163
|
-
"relayListContacts: gateway relay failed; falling back to assistant DB",
|
|
164
|
-
);
|
|
165
|
-
const contacts = listContacts(limit, role);
|
|
166
|
-
return {
|
|
167
|
-
ok: true,
|
|
168
|
-
contacts: contacts.map(prepareContactResponse),
|
|
169
|
-
};
|
|
160
|
+
rethrowGatewayError(err);
|
|
170
161
|
}
|
|
171
162
|
}
|
|
172
163
|
|
|
@@ -240,25 +231,10 @@ export async function handleGetContact(contactId: string) {
|
|
|
240
231
|
assistantMetadata: assistantMetadata ?? undefined,
|
|
241
232
|
};
|
|
242
233
|
} catch (err) {
|
|
243
|
-
// A clean not-found is a real 404
|
|
234
|
+
// A clean not-found is a real 404. Any other relay failure fails closed
|
|
235
|
+
// rather than reading ACL from the assistant DB.
|
|
244
236
|
if (err instanceof NotFoundError) throw err;
|
|
245
|
-
|
|
246
|
-
{ err, contactId },
|
|
247
|
-
"handleGetContact: gateway relay failed; falling back to assistant DB",
|
|
248
|
-
);
|
|
249
|
-
const contact = getContact(contactId);
|
|
250
|
-
if (!contact) {
|
|
251
|
-
throw new NotFoundError(`Contact "${contactId}" not found`);
|
|
252
|
-
}
|
|
253
|
-
const assistantMeta =
|
|
254
|
-
contact.contactType === "assistant"
|
|
255
|
-
? getAssistantContactMetadata(contact.id)
|
|
256
|
-
: undefined;
|
|
257
|
-
return {
|
|
258
|
-
ok: true,
|
|
259
|
-
contact: prepareContactResponse(contact),
|
|
260
|
-
assistantMetadata: assistantMeta ?? undefined,
|
|
261
|
-
};
|
|
237
|
+
rethrowGatewayError(err);
|
|
262
238
|
}
|
|
263
239
|
}
|
|
264
240
|
|