@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,164 @@
|
|
|
1
|
+
import { beforeEach, describe, expect, mock, test } from "bun:test";
|
|
2
|
+
|
|
3
|
+
import type { ChannelId } from "../../channels/types.js";
|
|
4
|
+
|
|
5
|
+
// Gateway guardian-delivery list: null = couldn't determine (gateway
|
|
6
|
+
// unreachable), [] = authoritatively no guardian, one active entry = bound.
|
|
7
|
+
let mockGuardianList: Array<Record<string, unknown>> | null = [];
|
|
8
|
+
|
|
9
|
+
mock.module("../../contacts/guardian-delivery-reader.js", () => ({
|
|
10
|
+
getGuardianDelivery: (_input?: { channelTypes?: string[] }) =>
|
|
11
|
+
Promise.resolve(mockGuardianList),
|
|
12
|
+
}));
|
|
13
|
+
|
|
14
|
+
// Local-resolver guardian binding, used to construct the expected guardian
|
|
15
|
+
// TrustContext via the existing resolver and prove equivalence.
|
|
16
|
+
let mockGuardianRecord: {
|
|
17
|
+
contact: Record<string, unknown>;
|
|
18
|
+
channel: Record<string, unknown>;
|
|
19
|
+
} | null = null;
|
|
20
|
+
|
|
21
|
+
mock.module("../../contacts/contact-store.js", () => ({
|
|
22
|
+
findGuardianForChannel: (_channelType: string) => mockGuardianRecord,
|
|
23
|
+
findContactByAddress: (_channelType: string, _address: string) => null,
|
|
24
|
+
}));
|
|
25
|
+
|
|
26
|
+
const { resolveLocalPrincipalTrustContext } = await import(
|
|
27
|
+
"../local-principal-trust.js"
|
|
28
|
+
);
|
|
29
|
+
const { resolveActorTrust, toTrustContext } = await import(
|
|
30
|
+
"../actor-trust-resolver.js"
|
|
31
|
+
);
|
|
32
|
+
|
|
33
|
+
const SOURCE_CHANNEL: ChannelId = "vellum";
|
|
34
|
+
const CONVERSATION_ID = "conv-1";
|
|
35
|
+
const GUARDIAN_PRINCIPAL_ID = "principal-guardian";
|
|
36
|
+
const GUARDIAN_ADDRESS = "guardian-address";
|
|
37
|
+
const GUARDIAN_CHAT_ID = "guardian-chat";
|
|
38
|
+
|
|
39
|
+
describe("resolveLocalPrincipalTrustContext", () => {
|
|
40
|
+
beforeEach(() => {
|
|
41
|
+
mockGuardianList = [];
|
|
42
|
+
mockGuardianRecord = null;
|
|
43
|
+
});
|
|
44
|
+
|
|
45
|
+
test("principal matching the gateway guardian → guardian ctx", async () => {
|
|
46
|
+
mockGuardianList = [
|
|
47
|
+
{
|
|
48
|
+
channelType: "vellum",
|
|
49
|
+
contactId: "contact-1",
|
|
50
|
+
principalId: GUARDIAN_PRINCIPAL_ID,
|
|
51
|
+
address: GUARDIAN_ADDRESS,
|
|
52
|
+
externalChatId: GUARDIAN_CHAT_ID,
|
|
53
|
+
status: "active",
|
|
54
|
+
},
|
|
55
|
+
];
|
|
56
|
+
|
|
57
|
+
const ctx = await resolveLocalPrincipalTrustContext({
|
|
58
|
+
actorPrincipalId: GUARDIAN_PRINCIPAL_ID,
|
|
59
|
+
sourceChannel: SOURCE_CHANNEL,
|
|
60
|
+
conversationExternalId: CONVERSATION_ID,
|
|
61
|
+
});
|
|
62
|
+
|
|
63
|
+
expect(ctx.trustClass).toBe("guardian");
|
|
64
|
+
expect(ctx.guardianPrincipalId).toBe(GUARDIAN_PRINCIPAL_ID);
|
|
65
|
+
expect(ctx.guardianExternalUserId).toBe(GUARDIAN_ADDRESS);
|
|
66
|
+
expect(ctx.guardianChatId).toBe(GUARDIAN_CHAT_ID);
|
|
67
|
+
expect(ctx.requesterExternalUserId).toBe(GUARDIAN_PRINCIPAL_ID);
|
|
68
|
+
expect(ctx.requesterChatId).toBe(CONVERSATION_ID);
|
|
69
|
+
expect(ctx.sourceChannel).toBe(SOURCE_CHANNEL);
|
|
70
|
+
});
|
|
71
|
+
|
|
72
|
+
test("guardian ctx is equivalent to the prior toTrustContext output", async () => {
|
|
73
|
+
// The actor IS the guardian: its principal id is the guardian binding's
|
|
74
|
+
// address (vellum canonicalization is pass-through) so the local resolver
|
|
75
|
+
// classifies it guardian and we can compare the two outputs directly.
|
|
76
|
+
mockGuardianList = [
|
|
77
|
+
{
|
|
78
|
+
channelType: "vellum",
|
|
79
|
+
contactId: "contact-1",
|
|
80
|
+
principalId: GUARDIAN_ADDRESS,
|
|
81
|
+
address: GUARDIAN_ADDRESS,
|
|
82
|
+
externalChatId: GUARDIAN_CHAT_ID,
|
|
83
|
+
status: "active",
|
|
84
|
+
},
|
|
85
|
+
];
|
|
86
|
+
mockGuardianRecord = {
|
|
87
|
+
contact: { id: "contact-1", principalId: GUARDIAN_ADDRESS },
|
|
88
|
+
channel: {
|
|
89
|
+
type: "vellum",
|
|
90
|
+
address: GUARDIAN_ADDRESS,
|
|
91
|
+
externalChatId: GUARDIAN_CHAT_ID,
|
|
92
|
+
status: "active",
|
|
93
|
+
},
|
|
94
|
+
};
|
|
95
|
+
|
|
96
|
+
const expected = toTrustContext(
|
|
97
|
+
resolveActorTrust({
|
|
98
|
+
assistantId: "assistant-1",
|
|
99
|
+
sourceChannel: SOURCE_CHANNEL,
|
|
100
|
+
conversationExternalId: CONVERSATION_ID,
|
|
101
|
+
actorExternalId: GUARDIAN_ADDRESS,
|
|
102
|
+
}),
|
|
103
|
+
CONVERSATION_ID,
|
|
104
|
+
);
|
|
105
|
+
|
|
106
|
+
const actual = await resolveLocalPrincipalTrustContext({
|
|
107
|
+
actorPrincipalId: GUARDIAN_ADDRESS,
|
|
108
|
+
sourceChannel: SOURCE_CHANNEL,
|
|
109
|
+
conversationExternalId: CONVERSATION_ID,
|
|
110
|
+
});
|
|
111
|
+
|
|
112
|
+
expect(actual).toEqual(expected);
|
|
113
|
+
});
|
|
114
|
+
|
|
115
|
+
test("non-matching principal → unknown ctx", async () => {
|
|
116
|
+
mockGuardianList = [
|
|
117
|
+
{
|
|
118
|
+
channelType: "vellum",
|
|
119
|
+
contactId: "contact-1",
|
|
120
|
+
principalId: GUARDIAN_PRINCIPAL_ID,
|
|
121
|
+
address: GUARDIAN_ADDRESS,
|
|
122
|
+
externalChatId: GUARDIAN_CHAT_ID,
|
|
123
|
+
status: "active",
|
|
124
|
+
},
|
|
125
|
+
];
|
|
126
|
+
|
|
127
|
+
const ctx = await resolveLocalPrincipalTrustContext({
|
|
128
|
+
actorPrincipalId: "principal-other",
|
|
129
|
+
sourceChannel: SOURCE_CHANNEL,
|
|
130
|
+
conversationExternalId: CONVERSATION_ID,
|
|
131
|
+
});
|
|
132
|
+
|
|
133
|
+
expect(ctx.trustClass).toBe("unknown");
|
|
134
|
+
expect(ctx.requesterExternalUserId).toBe("principal-other");
|
|
135
|
+
expect(ctx.requesterChatId).toBe(CONVERSATION_ID);
|
|
136
|
+
expect(ctx.sourceChannel).toBe(SOURCE_CHANNEL);
|
|
137
|
+
expect(ctx.guardianPrincipalId).toBeUndefined();
|
|
138
|
+
});
|
|
139
|
+
|
|
140
|
+
test("empty guardian list → unknown ctx", async () => {
|
|
141
|
+
mockGuardianList = [];
|
|
142
|
+
|
|
143
|
+
const ctx = await resolveLocalPrincipalTrustContext({
|
|
144
|
+
actorPrincipalId: GUARDIAN_PRINCIPAL_ID,
|
|
145
|
+
sourceChannel: SOURCE_CHANNEL,
|
|
146
|
+
conversationExternalId: CONVERSATION_ID,
|
|
147
|
+
});
|
|
148
|
+
|
|
149
|
+
expect(ctx.trustClass).toBe("unknown");
|
|
150
|
+
});
|
|
151
|
+
|
|
152
|
+
test("null guardian list (gateway unreachable) → unknown (fail closed)", async () => {
|
|
153
|
+
mockGuardianList = null;
|
|
154
|
+
|
|
155
|
+
const ctx = await resolveLocalPrincipalTrustContext({
|
|
156
|
+
actorPrincipalId: GUARDIAN_PRINCIPAL_ID,
|
|
157
|
+
sourceChannel: SOURCE_CHANNEL,
|
|
158
|
+
conversationExternalId: CONVERSATION_ID,
|
|
159
|
+
});
|
|
160
|
+
|
|
161
|
+
expect(ctx.trustClass).toBe("unknown");
|
|
162
|
+
expect(ctx.requesterExternalUserId).toBe(GUARDIAN_PRINCIPAL_ID);
|
|
163
|
+
});
|
|
164
|
+
});
|
|
@@ -2,6 +2,7 @@ import { describe, expect, test } from "bun:test";
|
|
|
2
2
|
|
|
3
3
|
import type { TrustVerdict } from "@vellumai/gateway-client";
|
|
4
4
|
|
|
5
|
+
import { channelStatusToMemberStatus } from "../../contacts/member-status.js";
|
|
5
6
|
import type {
|
|
6
7
|
ContactChannel,
|
|
7
8
|
ContactWithChannels,
|
|
@@ -9,8 +10,12 @@ import type {
|
|
|
9
10
|
import type { ActorTrustContext } from "../actor-trust-resolver.js";
|
|
10
11
|
import { toTrustContext } from "../actor-trust-resolver.js";
|
|
11
12
|
import {
|
|
13
|
+
actorTrustContextFromVerdict,
|
|
12
14
|
resolvedMemberFromVerdict,
|
|
13
15
|
trustContextFromVerdict,
|
|
16
|
+
verdictHasMemberIdentity,
|
|
17
|
+
verdictMemberFromVerdict,
|
|
18
|
+
verdictMemberUnresolvable,
|
|
14
19
|
} from "../trust-verdict-consumer.js";
|
|
15
20
|
|
|
16
21
|
const CONV = "conv-123";
|
|
@@ -163,6 +168,195 @@ describe("trustContextFromVerdict", () => {
|
|
|
163
168
|
});
|
|
164
169
|
});
|
|
165
170
|
|
|
171
|
+
describe("actorTrustContextFromVerdict", () => {
|
|
172
|
+
test("maps guardian verdict fields", () => {
|
|
173
|
+
const verdict = {
|
|
174
|
+
trustClass: "guardian",
|
|
175
|
+
canonicalSenderId: "+15550100",
|
|
176
|
+
guardianExternalUserId: "+15550100",
|
|
177
|
+
guardianDeliveryChatId: "chat-9",
|
|
178
|
+
guardianPrincipalId: "vellum-principal-abc",
|
|
179
|
+
memberDisplayName: "Alice",
|
|
180
|
+
} satisfies TrustVerdict;
|
|
181
|
+
|
|
182
|
+
const ctx = actorTrustContextFromVerdict(verdict, {
|
|
183
|
+
sourceChannel: "phone",
|
|
184
|
+
conversationExternalId: CONV,
|
|
185
|
+
actorUsername: "alice",
|
|
186
|
+
actorDisplayName: "Alice Sender",
|
|
187
|
+
});
|
|
188
|
+
|
|
189
|
+
expect(ctx).toEqual({
|
|
190
|
+
canonicalSenderId: "+15550100",
|
|
191
|
+
guardianBindingMatch: {
|
|
192
|
+
guardianExternalUserId: "+15550100",
|
|
193
|
+
guardianDeliveryChatId: "chat-9",
|
|
194
|
+
},
|
|
195
|
+
guardianPrincipalId: "vellum-principal-abc",
|
|
196
|
+
memberRecord: null,
|
|
197
|
+
trustClass: "guardian",
|
|
198
|
+
actorMetadata: {
|
|
199
|
+
identifier: "@alice",
|
|
200
|
+
displayName: "Alice",
|
|
201
|
+
senderDisplayName: "Alice Sender",
|
|
202
|
+
memberDisplayName: "Alice",
|
|
203
|
+
username: "alice",
|
|
204
|
+
channel: "phone",
|
|
205
|
+
trustStatus: "guardian",
|
|
206
|
+
},
|
|
207
|
+
});
|
|
208
|
+
});
|
|
209
|
+
|
|
210
|
+
test("guardianBindingMatch is null without guardianExternalUserId", () => {
|
|
211
|
+
const verdict = {
|
|
212
|
+
trustClass: "trusted_contact",
|
|
213
|
+
canonicalSenderId: "+15550101",
|
|
214
|
+
memberDisplayName: "Bob",
|
|
215
|
+
} satisfies TrustVerdict;
|
|
216
|
+
|
|
217
|
+
const ctx = actorTrustContextFromVerdict(verdict, {
|
|
218
|
+
sourceChannel: "phone",
|
|
219
|
+
conversationExternalId: CONV,
|
|
220
|
+
});
|
|
221
|
+
|
|
222
|
+
expect(ctx.guardianBindingMatch).toBeNull();
|
|
223
|
+
expect(ctx.trustClass).toBe("trusted_contact");
|
|
224
|
+
// identifier falls back to canonicalSenderId when no username.
|
|
225
|
+
expect(ctx.actorMetadata.identifier).toBe("+15550101");
|
|
226
|
+
// displayName uses memberDisplayName when present.
|
|
227
|
+
expect(ctx.actorMetadata.displayName).toBe("Bob");
|
|
228
|
+
expect(ctx.actorMetadata.channel).toBe("phone");
|
|
229
|
+
expect(ctx.memberRecord).toBeNull();
|
|
230
|
+
});
|
|
231
|
+
|
|
232
|
+
test("displayName falls back to actorDisplayName; identifier uses @username", () => {
|
|
233
|
+
const verdict = {
|
|
234
|
+
trustClass: "unverified_contact",
|
|
235
|
+
canonicalSenderId: "u-1",
|
|
236
|
+
} satisfies TrustVerdict;
|
|
237
|
+
|
|
238
|
+
const ctx = actorTrustContextFromVerdict(verdict, {
|
|
239
|
+
sourceChannel: "slack",
|
|
240
|
+
conversationExternalId: CONV,
|
|
241
|
+
actorUsername: "carol",
|
|
242
|
+
actorDisplayName: "Carol Display",
|
|
243
|
+
});
|
|
244
|
+
|
|
245
|
+
expect(ctx.trustClass).toBe("unverified_contact");
|
|
246
|
+
expect(ctx.actorMetadata.identifier).toBe("@carol");
|
|
247
|
+
expect(ctx.actorMetadata.displayName).toBe("Carol Display");
|
|
248
|
+
expect(ctx.actorMetadata.memberDisplayName).toBeUndefined();
|
|
249
|
+
expect(ctx.actorMetadata.trustStatus).toBe("unverified_contact");
|
|
250
|
+
});
|
|
251
|
+
|
|
252
|
+
test("maps unknown verdict with no identity overrides", () => {
|
|
253
|
+
const verdict = {
|
|
254
|
+
trustClass: "unknown",
|
|
255
|
+
canonicalSenderId: "u-2",
|
|
256
|
+
} satisfies TrustVerdict;
|
|
257
|
+
|
|
258
|
+
const ctx = actorTrustContextFromVerdict(verdict, {
|
|
259
|
+
sourceChannel: "slack",
|
|
260
|
+
conversationExternalId: CONV,
|
|
261
|
+
});
|
|
262
|
+
|
|
263
|
+
expect(ctx.trustClass).toBe("unknown");
|
|
264
|
+
expect(ctx.guardianBindingMatch).toBeNull();
|
|
265
|
+
expect(ctx.guardianPrincipalId).toBeUndefined();
|
|
266
|
+
expect(ctx.memberRecord).toBeNull();
|
|
267
|
+
expect(ctx.actorMetadata.identifier).toBe("u-2");
|
|
268
|
+
expect(ctx.actorMetadata.displayName).toBeUndefined();
|
|
269
|
+
});
|
|
270
|
+
|
|
271
|
+
test("member verdict populates memberRecord from the verdict (voice ACL)", () => {
|
|
272
|
+
const verdict = {
|
|
273
|
+
trustClass: "trusted_contact",
|
|
274
|
+
canonicalSenderId: "u-1",
|
|
275
|
+
contactId: "contact-1",
|
|
276
|
+
channelId: "channel-1",
|
|
277
|
+
type: "slack",
|
|
278
|
+
address: "u-1",
|
|
279
|
+
status: "blocked",
|
|
280
|
+
policy: "deny",
|
|
281
|
+
} satisfies TrustVerdict;
|
|
282
|
+
|
|
283
|
+
const ctx = actorTrustContextFromVerdict(verdict, {
|
|
284
|
+
sourceChannel: "slack",
|
|
285
|
+
conversationExternalId: CONV,
|
|
286
|
+
});
|
|
287
|
+
|
|
288
|
+
expect(ctx.memberRecord).not.toBeNull();
|
|
289
|
+
expect(ctx.memberRecord!.contact.id).toBe("contact-1");
|
|
290
|
+
expect(ctx.memberRecord!.channel.id).toBe("channel-1");
|
|
291
|
+
expect(ctx.memberRecord!.channel.status).toBe("blocked");
|
|
292
|
+
expect(ctx.memberRecord!.channel.policy).toBe("deny");
|
|
293
|
+
});
|
|
294
|
+
|
|
295
|
+
test("stranger verdict (no contactId/channelId) leaves memberRecord null", () => {
|
|
296
|
+
const verdict = {
|
|
297
|
+
trustClass: "unknown",
|
|
298
|
+
canonicalSenderId: "u-9",
|
|
299
|
+
} satisfies TrustVerdict;
|
|
300
|
+
|
|
301
|
+
const ctx = actorTrustContextFromVerdict(verdict, {
|
|
302
|
+
sourceChannel: "slack",
|
|
303
|
+
conversationExternalId: CONV,
|
|
304
|
+
});
|
|
305
|
+
|
|
306
|
+
expect(ctx.memberRecord).toBeNull();
|
|
307
|
+
});
|
|
308
|
+
|
|
309
|
+
test("malformed member verdict (unknown status) leaves memberRecord null (fail-closed)", () => {
|
|
310
|
+
const verdict = {
|
|
311
|
+
trustClass: "trusted_contact",
|
|
312
|
+
canonicalSenderId: "u-10",
|
|
313
|
+
contactId: "contact-10",
|
|
314
|
+
channelId: "channel-10",
|
|
315
|
+
status: "quarantined",
|
|
316
|
+
policy: "allow",
|
|
317
|
+
} satisfies TrustVerdict;
|
|
318
|
+
|
|
319
|
+
const ctx = actorTrustContextFromVerdict(verdict, {
|
|
320
|
+
sourceChannel: "slack",
|
|
321
|
+
conversationExternalId: CONV,
|
|
322
|
+
});
|
|
323
|
+
|
|
324
|
+
expect(ctx.memberRecord).toBeNull();
|
|
325
|
+
});
|
|
326
|
+
|
|
327
|
+
test("trustContextFromVerdict equals member stamp applied to toTrustContext(actorTrustContextFromVerdict)", () => {
|
|
328
|
+
const verdict = {
|
|
329
|
+
trustClass: "trusted_contact",
|
|
330
|
+
canonicalSenderId: "u-1",
|
|
331
|
+
contactId: "contact-1",
|
|
332
|
+
channelId: "channel-1",
|
|
333
|
+
type: "slack",
|
|
334
|
+
address: "u-1",
|
|
335
|
+
status: "unverified",
|
|
336
|
+
policy: "escalate",
|
|
337
|
+
memberDisplayName: "Dora",
|
|
338
|
+
} satisfies TrustVerdict;
|
|
339
|
+
const input = {
|
|
340
|
+
sourceChannel: "slack",
|
|
341
|
+
conversationExternalId: CONV,
|
|
342
|
+
actorUsername: "dora",
|
|
343
|
+
actorDisplayName: "Dora Display",
|
|
344
|
+
} as const;
|
|
345
|
+
|
|
346
|
+
const expected = toTrustContext(
|
|
347
|
+
actorTrustContextFromVerdict(verdict, input),
|
|
348
|
+
input.conversationExternalId,
|
|
349
|
+
);
|
|
350
|
+
const member = resolvedMemberFromVerdict(verdict);
|
|
351
|
+
expect(member).not.toBeNull();
|
|
352
|
+
expected.requesterContactId = member!.contact.id;
|
|
353
|
+
expected.memberStatus = channelStatusToMemberStatus(member!.channel.status);
|
|
354
|
+
expected.memberPolicy = member!.channel.policy;
|
|
355
|
+
|
|
356
|
+
expect(trustContextFromVerdict(verdict, input)).toEqual(expected);
|
|
357
|
+
});
|
|
358
|
+
});
|
|
359
|
+
|
|
166
360
|
describe("toTrustContext member grounding", () => {
|
|
167
361
|
function memberChannel(
|
|
168
362
|
overrides: Partial<ContactChannel> = {},
|
|
@@ -207,9 +401,7 @@ describe("toTrustContext member grounding", () => {
|
|
|
207
401
|
};
|
|
208
402
|
}
|
|
209
403
|
|
|
210
|
-
function ctxWithMember(
|
|
211
|
-
channel: ContactChannel,
|
|
212
|
-
): ActorTrustContext {
|
|
404
|
+
function ctxWithMember(channel: ContactChannel): ActorTrustContext {
|
|
213
405
|
return {
|
|
214
406
|
canonicalSenderId: "+15550100",
|
|
215
407
|
guardianBindingMatch: null,
|
|
@@ -415,3 +607,143 @@ describe("resolvedMemberFromVerdict", () => {
|
|
|
415
607
|
}
|
|
416
608
|
});
|
|
417
609
|
});
|
|
610
|
+
|
|
611
|
+
describe("verdictMemberFromVerdict", () => {
|
|
612
|
+
test("active member verdict yields the narrow ACL view", () => {
|
|
613
|
+
const verdict = {
|
|
614
|
+
trustClass: "trusted_contact",
|
|
615
|
+
canonicalSenderId: "u-1",
|
|
616
|
+
contactId: "contact-1",
|
|
617
|
+
channelId: "channel-1",
|
|
618
|
+
type: "slack",
|
|
619
|
+
address: "u-1",
|
|
620
|
+
status: "active",
|
|
621
|
+
policy: "allow",
|
|
622
|
+
verifiedAt: 1700000000,
|
|
623
|
+
memberDisplayName: "Dora",
|
|
624
|
+
} satisfies TrustVerdict;
|
|
625
|
+
|
|
626
|
+
expect(verdictMemberFromVerdict(verdict)).toEqual({
|
|
627
|
+
contactId: "contact-1",
|
|
628
|
+
channelId: "channel-1",
|
|
629
|
+
status: "active",
|
|
630
|
+
policy: "allow",
|
|
631
|
+
verifiedAt: 1700000000,
|
|
632
|
+
displayName: "Dora",
|
|
633
|
+
});
|
|
634
|
+
});
|
|
635
|
+
|
|
636
|
+
test("blocked member verdict surfaces status/policy verbatim, null defaults", () => {
|
|
637
|
+
const verdict = {
|
|
638
|
+
trustClass: "unknown",
|
|
639
|
+
canonicalSenderId: "u-3",
|
|
640
|
+
contactId: "contact-3",
|
|
641
|
+
channelId: "channel-3",
|
|
642
|
+
status: "blocked",
|
|
643
|
+
policy: "deny",
|
|
644
|
+
} satisfies TrustVerdict;
|
|
645
|
+
|
|
646
|
+
expect(verdictMemberFromVerdict(verdict)).toEqual({
|
|
647
|
+
contactId: "contact-3",
|
|
648
|
+
channelId: "channel-3",
|
|
649
|
+
status: "blocked",
|
|
650
|
+
policy: "deny",
|
|
651
|
+
verifiedAt: null,
|
|
652
|
+
displayName: null,
|
|
653
|
+
});
|
|
654
|
+
});
|
|
655
|
+
|
|
656
|
+
test("memberless verdict (no contactId/channelId) returns null", () => {
|
|
657
|
+
expect(
|
|
658
|
+
verdictMemberFromVerdict({
|
|
659
|
+
trustClass: "unknown",
|
|
660
|
+
canonicalSenderId: "u-2",
|
|
661
|
+
} satisfies TrustVerdict),
|
|
662
|
+
).toBeNull();
|
|
663
|
+
});
|
|
664
|
+
|
|
665
|
+
test("invalid enum (unknown status/policy) returns null (fail-closed)", () => {
|
|
666
|
+
expect(
|
|
667
|
+
verdictMemberFromVerdict({
|
|
668
|
+
trustClass: "trusted_contact",
|
|
669
|
+
canonicalSenderId: "u-7",
|
|
670
|
+
contactId: "contact-7",
|
|
671
|
+
channelId: "channel-7",
|
|
672
|
+
status: "quarantined",
|
|
673
|
+
policy: "allow",
|
|
674
|
+
} satisfies TrustVerdict),
|
|
675
|
+
).toBeNull();
|
|
676
|
+
expect(
|
|
677
|
+
verdictMemberFromVerdict({
|
|
678
|
+
trustClass: "trusted_contact",
|
|
679
|
+
canonicalSenderId: "u-6",
|
|
680
|
+
contactId: "contact-6",
|
|
681
|
+
channelId: "channel-6",
|
|
682
|
+
status: "active",
|
|
683
|
+
policy: "bogus",
|
|
684
|
+
} satisfies TrustVerdict),
|
|
685
|
+
).toBeNull();
|
|
686
|
+
});
|
|
687
|
+
});
|
|
688
|
+
|
|
689
|
+
describe("verdict predicates", () => {
|
|
690
|
+
test("verdictHasMemberIdentity is true with contactId or channelId", () => {
|
|
691
|
+
expect(
|
|
692
|
+
verdictHasMemberIdentity({
|
|
693
|
+
trustClass: "unknown",
|
|
694
|
+
canonicalSenderId: "u-1",
|
|
695
|
+
contactId: "contact-1",
|
|
696
|
+
} satisfies TrustVerdict),
|
|
697
|
+
).toBe(true);
|
|
698
|
+
expect(
|
|
699
|
+
verdictHasMemberIdentity({
|
|
700
|
+
trustClass: "unknown",
|
|
701
|
+
canonicalSenderId: "u-1",
|
|
702
|
+
channelId: "channel-1",
|
|
703
|
+
} satisfies TrustVerdict),
|
|
704
|
+
).toBe(true);
|
|
705
|
+
});
|
|
706
|
+
|
|
707
|
+
test("verdictHasMemberIdentity is false for a memberless verdict", () => {
|
|
708
|
+
expect(
|
|
709
|
+
verdictHasMemberIdentity({
|
|
710
|
+
trustClass: "unknown",
|
|
711
|
+
canonicalSenderId: "u-1",
|
|
712
|
+
} satisfies TrustVerdict),
|
|
713
|
+
).toBe(false);
|
|
714
|
+
});
|
|
715
|
+
|
|
716
|
+
test("verdictMemberUnresolvable is true when member identity present but ACL unsynthesizable", () => {
|
|
717
|
+
expect(
|
|
718
|
+
verdictMemberUnresolvable({
|
|
719
|
+
trustClass: "trusted_contact",
|
|
720
|
+
canonicalSenderId: "u-1",
|
|
721
|
+
contactId: "contact-1",
|
|
722
|
+
channelId: "channel-1",
|
|
723
|
+
policy: "allow",
|
|
724
|
+
} satisfies TrustVerdict),
|
|
725
|
+
).toBe(true);
|
|
726
|
+
});
|
|
727
|
+
|
|
728
|
+
test("verdictMemberUnresolvable is false for a usable member verdict", () => {
|
|
729
|
+
expect(
|
|
730
|
+
verdictMemberUnresolvable({
|
|
731
|
+
trustClass: "trusted_contact",
|
|
732
|
+
canonicalSenderId: "u-1",
|
|
733
|
+
contactId: "contact-1",
|
|
734
|
+
channelId: "channel-1",
|
|
735
|
+
status: "active",
|
|
736
|
+
policy: "allow",
|
|
737
|
+
} satisfies TrustVerdict),
|
|
738
|
+
).toBe(false);
|
|
739
|
+
});
|
|
740
|
+
|
|
741
|
+
test("verdictMemberUnresolvable is false for a memberless verdict", () => {
|
|
742
|
+
expect(
|
|
743
|
+
verdictMemberUnresolvable({
|
|
744
|
+
trustClass: "unknown",
|
|
745
|
+
canonicalSenderId: "u-1",
|
|
746
|
+
} satisfies TrustVerdict),
|
|
747
|
+
).toBe(false);
|
|
748
|
+
});
|
|
749
|
+
});
|
|
@@ -12,7 +12,7 @@
|
|
|
12
12
|
*/
|
|
13
13
|
|
|
14
14
|
import type { ChannelId } from "../channels/types.js";
|
|
15
|
-
import {
|
|
15
|
+
import { getGuardianDelivery } from "../contacts/guardian-delivery-reader.js";
|
|
16
16
|
import type { ChannelStatus } from "../contacts/types.js";
|
|
17
17
|
import {
|
|
18
18
|
createCanonicalGuardianRequest,
|
|
@@ -25,6 +25,7 @@ import {
|
|
|
25
25
|
import { emitNotificationSignal } from "../notifications/emit-signal.js";
|
|
26
26
|
import type { GuardianResolutionSource } from "../notifications/signal.js";
|
|
27
27
|
import { getLogger } from "../util/logger.js";
|
|
28
|
+
import { resolveAnchoredGuardian } from "./anchored-guardian.js";
|
|
28
29
|
import { GUARDIAN_APPROVAL_TTL_MS } from "./routes/channel-route-shared.js";
|
|
29
30
|
|
|
30
31
|
const log = getLogger("access-request-helper");
|
|
@@ -70,12 +71,12 @@ export type AccessRequestResult =
|
|
|
70
71
|
* trust anchor and only accepts source-channel contacts that match it. This
|
|
71
72
|
* prevents stale or cross-assistant contacts from being bound to the request.
|
|
72
73
|
*
|
|
73
|
-
*
|
|
74
|
-
*
|
|
74
|
+
* The canonical store writes complete before this resolves; the notification
|
|
75
|
+
* signal emission is fire-and-forget.
|
|
75
76
|
*/
|
|
76
|
-
export function notifyGuardianOfAccessRequest(
|
|
77
|
+
export async function notifyGuardianOfAccessRequest(
|
|
77
78
|
params: AccessRequestParams,
|
|
78
|
-
): AccessRequestResult {
|
|
79
|
+
): Promise<AccessRequestResult> {
|
|
79
80
|
const {
|
|
80
81
|
canonicalAssistantId,
|
|
81
82
|
sourceChannel,
|
|
@@ -94,40 +95,19 @@ export function notifyGuardianOfAccessRequest(
|
|
|
94
95
|
return { notified: false, reason: "no_sender_id" };
|
|
95
96
|
}
|
|
96
97
|
|
|
97
|
-
// Resolve guardian identity with assistant-anchored strategy
|
|
98
|
-
//
|
|
99
|
-
//
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
const
|
|
107
|
-
const
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
// anchored principal. This blocks cross-assistant/stale contact selection.
|
|
111
|
-
const sourceGuardian = findGuardianForChannel(sourceChannel);
|
|
112
|
-
if (
|
|
113
|
-
assistantGuardianPrincipalId &&
|
|
114
|
-
sourceGuardian &&
|
|
115
|
-
sourceGuardian.contact.principalId === assistantGuardianPrincipalId
|
|
116
|
-
) {
|
|
117
|
-
guardianExternalUserId = sourceGuardian.channel.address;
|
|
118
|
-
guardianPrincipalId = sourceGuardian.contact.principalId;
|
|
119
|
-
guardianBindingChannel = sourceGuardian.channel.type;
|
|
120
|
-
guardianResolutionSource = "source-channel-contact";
|
|
121
|
-
}
|
|
122
|
-
|
|
123
|
-
// Access requests always require a principal. If source-channel resolution
|
|
124
|
-
// did not match the assistant anchor, use the anchored vellum identity.
|
|
125
|
-
if (!guardianPrincipalId && vellumGuardian) {
|
|
126
|
-
guardianExternalUserId = vellumGuardian.channel.address;
|
|
127
|
-
guardianPrincipalId = assistantGuardianPrincipalId ?? null;
|
|
128
|
-
guardianBindingChannel = guardianBindingChannel ?? "vellum";
|
|
129
|
-
guardianResolutionSource = "vellum-anchor";
|
|
130
|
-
}
|
|
98
|
+
// Resolve guardian identity with the assistant-anchored strategy (gateway
|
|
99
|
+
// source-channel match validated against the vellum anchor, else the vellum
|
|
100
|
+
// anchor), with a LOCAL-store fallback when the gateway read is empty.
|
|
101
|
+
const anchored = resolveAnchoredGuardian({
|
|
102
|
+
guardians: await getGuardianDelivery(),
|
|
103
|
+
sourceChannel,
|
|
104
|
+
useLocalFallback: true,
|
|
105
|
+
});
|
|
106
|
+
const guardianExternalUserId = anchored?.address ?? null;
|
|
107
|
+
const guardianPrincipalId = anchored?.principalId ?? null;
|
|
108
|
+
const guardianBindingChannel = anchored?.channelType ?? null;
|
|
109
|
+
const guardianResolutionSource: GuardianResolutionSource =
|
|
110
|
+
anchored?.source ?? "none";
|
|
131
111
|
|
|
132
112
|
log.debug(
|
|
133
113
|
{
|
|
@@ -323,8 +323,8 @@ export function toTrustContext(
|
|
|
323
323
|
requesterMemberDisplayName: ctx.actorMetadata.memberDisplayName,
|
|
324
324
|
requesterExternalUserId: ctx.canonicalSenderId ?? undefined,
|
|
325
325
|
requesterChatId: conversationExternalId,
|
|
326
|
-
// Member grounding from
|
|
327
|
-
//
|
|
326
|
+
// Member grounding from the resolved memberRecord (voice + verdict paths
|
|
327
|
+
// both populate it).
|
|
328
328
|
requesterContactId: ctx.memberRecord?.contact.id,
|
|
329
329
|
memberStatus: ctx.memberRecord
|
|
330
330
|
? channelStatusToMemberStatus(ctx.memberRecord.channel.status)
|