@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
|
@@ -7,11 +7,22 @@
|
|
|
7
7
|
* assistant-side since it reacts to incoming JWT principals.
|
|
8
8
|
*/
|
|
9
9
|
|
|
10
|
+
import type { ChannelId } from "../channels/types.js";
|
|
10
11
|
import {
|
|
11
12
|
findGuardianForChannel,
|
|
12
13
|
updateContactPrincipalAndChannel,
|
|
13
14
|
} from "../contacts/contact-store.js";
|
|
15
|
+
import {
|
|
16
|
+
getGuardianDelivery,
|
|
17
|
+
guardianForChannel,
|
|
18
|
+
} from "../contacts/guardian-delivery-reader.js";
|
|
19
|
+
import type { TrustContext } from "../daemon/trust-context.js";
|
|
14
20
|
import { getLogger } from "../util/logger.js";
|
|
21
|
+
import { DAEMON_INTERNAL_ASSISTANT_ID } from "./assistant-scope.js";
|
|
22
|
+
import {
|
|
23
|
+
resolveTrustContext,
|
|
24
|
+
withSourceChannel,
|
|
25
|
+
} from "./trust-context-resolver.js";
|
|
15
26
|
|
|
16
27
|
const log = getLogger("guardian-vellum-migration");
|
|
17
28
|
|
|
@@ -31,18 +42,37 @@ const log = getLogger("guardian-vellum-migration");
|
|
|
31
42
|
* minted by this daemon's signing key.
|
|
32
43
|
*
|
|
33
44
|
* Returns true if healing occurred, false otherwise.
|
|
45
|
+
*
|
|
46
|
+
* The gateway binding supplies the authoritative principal; the local
|
|
47
|
+
* assistant-mirror row is repaired whenever it diverges from the JWT
|
|
48
|
+
* principal — even when the gateway binding already matches — because the
|
|
49
|
+
* /v1/messages trust path still resolves against the local mirror in this
|
|
50
|
+
* plan. A stale mirror must be repaired or valid guardians stay `unknown`.
|
|
34
51
|
*/
|
|
35
|
-
export function healGuardianBindingDrift(
|
|
52
|
+
export async function healGuardianBindingDrift(
|
|
53
|
+
incomingPrincipalId: string,
|
|
54
|
+
): Promise<boolean> {
|
|
36
55
|
if (!incomingPrincipalId.startsWith("vellum-principal-")) {
|
|
37
56
|
return false;
|
|
38
57
|
}
|
|
39
58
|
|
|
59
|
+
const guardians = await getGuardianDelivery({ channelTypes: ["vellum"] });
|
|
60
|
+
if (!guardians) return false;
|
|
61
|
+
const guardian = guardianForChannel(guardians, "vellum");
|
|
62
|
+
if (!guardian) return false;
|
|
63
|
+
|
|
64
|
+
const currentPrincipalId = guardian.principalId;
|
|
65
|
+
if (!currentPrincipalId?.startsWith("vellum-principal-")) return false;
|
|
66
|
+
|
|
67
|
+
// Resolve the assistant-mirror row whose principal drives local trust.
|
|
40
68
|
const guardianResult = findGuardianForChannel("vellum");
|
|
41
69
|
if (!guardianResult) return false;
|
|
42
70
|
|
|
43
|
-
const
|
|
44
|
-
|
|
45
|
-
if (
|
|
71
|
+
const localPrincipalId = guardianResult.contact.principalId;
|
|
72
|
+
// Only repair auto-generated local principals — never overwrite a real one.
|
|
73
|
+
if (!localPrincipalId?.startsWith("vellum-principal-")) return false;
|
|
74
|
+
// No-op when the local mirror already matches the JWT principal.
|
|
75
|
+
if (localPrincipalId === incomingPrincipalId) return false;
|
|
46
76
|
|
|
47
77
|
const updated = updateContactPrincipalAndChannel(
|
|
48
78
|
guardianResult.contact.id,
|
|
@@ -53,7 +83,7 @@ export function healGuardianBindingDrift(incomingPrincipalId: string): boolean {
|
|
|
53
83
|
if (!updated) {
|
|
54
84
|
log.warn(
|
|
55
85
|
{
|
|
56
|
-
oldPrincipalId:
|
|
86
|
+
oldPrincipalId: localPrincipalId,
|
|
57
87
|
newPrincipalId: incomingPrincipalId,
|
|
58
88
|
},
|
|
59
89
|
"Skipped guardian binding drift heal — address collision on contact_channels",
|
|
@@ -63,11 +93,40 @@ export function healGuardianBindingDrift(incomingPrincipalId: string): boolean {
|
|
|
63
93
|
|
|
64
94
|
log.info(
|
|
65
95
|
{
|
|
66
|
-
oldPrincipalId:
|
|
96
|
+
oldPrincipalId: localPrincipalId,
|
|
67
97
|
newPrincipalId: incomingPrincipalId,
|
|
68
98
|
},
|
|
69
|
-
"Healed vellum guardian binding drift — updated principalId to match JWT actor",
|
|
99
|
+
"Healed vellum guardian binding drift — updated local mirror principalId to match JWT actor",
|
|
70
100
|
);
|
|
71
101
|
|
|
72
102
|
return true;
|
|
73
103
|
}
|
|
104
|
+
|
|
105
|
+
/**
|
|
106
|
+
* Re-resolve trust from the local mirror only for the narrow vellum-principal
|
|
107
|
+
* reset-drift case; null when it isn't drift (caller keeps the gateway verdict).
|
|
108
|
+
*/
|
|
109
|
+
export async function reResolveTrustOnResetDrift(
|
|
110
|
+
incomingPrincipalId: string,
|
|
111
|
+
sourceChannel: ChannelId,
|
|
112
|
+
): Promise<TrustContext | null> {
|
|
113
|
+
const guardians = await getGuardianDelivery({ channelTypes: ["vellum"] });
|
|
114
|
+
const gatewayPrincipal = guardians
|
|
115
|
+
? guardianForChannel(guardians, "vellum")?.principalId
|
|
116
|
+
: undefined;
|
|
117
|
+
const isResetDrift =
|
|
118
|
+
incomingPrincipalId.startsWith("vellum-principal-") &&
|
|
119
|
+
!!gatewayPrincipal?.startsWith("vellum-principal-") &&
|
|
120
|
+
gatewayPrincipal !== incomingPrincipalId;
|
|
121
|
+
if (!isResetDrift) return null;
|
|
122
|
+
await healGuardianBindingDrift(incomingPrincipalId);
|
|
123
|
+
return withSourceChannel(
|
|
124
|
+
sourceChannel,
|
|
125
|
+
resolveTrustContext({
|
|
126
|
+
assistantId: DAEMON_INTERNAL_ASSISTANT_ID,
|
|
127
|
+
sourceChannel: "vellum",
|
|
128
|
+
conversationExternalId: "local",
|
|
129
|
+
actorExternalId: incomingPrincipalId,
|
|
130
|
+
}),
|
|
131
|
+
);
|
|
132
|
+
}
|
|
@@ -7,9 +7,14 @@
|
|
|
7
7
|
* persisted, or returned in the outcome.
|
|
8
8
|
*/
|
|
9
9
|
|
|
10
|
+
import {
|
|
11
|
+
getInboundTrustVerdict,
|
|
12
|
+
getPhoneCallerVerdict,
|
|
13
|
+
} from "../calls/inbound-trust-reader.js";
|
|
10
14
|
import type { ChannelId } from "../channels/types.js";
|
|
11
15
|
import { findContactChannel, getContact } from "../contacts/contact-store.js";
|
|
12
16
|
import { upsertContactChannel } from "../contacts/contacts-write.js";
|
|
17
|
+
import type { ChannelStatus } from "../contacts/types.js";
|
|
13
18
|
import { ipcCallPersistent } from "../ipc/gateway-client.js";
|
|
14
19
|
import { getSqlite } from "../memory/db-connection.js";
|
|
15
20
|
import {
|
|
@@ -23,9 +28,27 @@ import {
|
|
|
23
28
|
import { canonicalizeInboundIdentity } from "../util/canonicalize-identity.js";
|
|
24
29
|
import { getLogger } from "../util/logger.js";
|
|
25
30
|
import { hashVoiceCode } from "../util/voice-code.js";
|
|
31
|
+
import { verdictMemberFromVerdict } from "./trust-verdict-consumer.js";
|
|
26
32
|
|
|
27
33
|
const log = getLogger("invite-redemption-service");
|
|
28
34
|
|
|
35
|
+
/**
|
|
36
|
+
* Resolve the sender's existing member status for the already_member/blocked
|
|
37
|
+
* gate from the gateway trust verdict. Falls back to the local channel status
|
|
38
|
+
* when the verdict is absent or carries no resolvable member status (e.g. an
|
|
39
|
+
* externalChatId-only match or a resolutionFailed verdict), so a locally
|
|
40
|
+
* blocked contact can't bypass the gate.
|
|
41
|
+
*/
|
|
42
|
+
export async function resolveMemberGateStatus(
|
|
43
|
+
verdict: Awaited<ReturnType<typeof getInboundTrustVerdict>>,
|
|
44
|
+
localChannelStatus: ChannelStatus | null,
|
|
45
|
+
): Promise<ChannelStatus | null> {
|
|
46
|
+
const memberStatus = verdict
|
|
47
|
+
? verdictMemberFromVerdict(verdict)?.status
|
|
48
|
+
: null;
|
|
49
|
+
return memberStatus ?? localChannelStatus;
|
|
50
|
+
}
|
|
51
|
+
|
|
29
52
|
// ---------------------------------------------------------------------------
|
|
30
53
|
// Gateway lifecycle bridge (shared by all redemption paths)
|
|
31
54
|
// ---------------------------------------------------------------------------
|
|
@@ -218,18 +241,22 @@ export async function redeemInvite(params: {
|
|
|
218
241
|
const targetMismatch =
|
|
219
242
|
existingContact && existingContact.id !== invite.contactId;
|
|
220
243
|
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
244
|
+
const gateStatus = await resolveMemberGateStatus(
|
|
245
|
+
await getInboundTrustVerdict({
|
|
246
|
+
channelType: sourceChannel as ChannelId,
|
|
247
|
+
actorExternalId: canonicalUserId,
|
|
248
|
+
}),
|
|
249
|
+
existingChannel?.status ?? null,
|
|
250
|
+
);
|
|
251
|
+
|
|
252
|
+
if (existingChannel && gateStatus === "active" && !targetMismatch) {
|
|
226
253
|
return { ok: true, type: "already_member", memberId: existingChannel.id };
|
|
227
254
|
}
|
|
228
255
|
|
|
229
256
|
// Blocked members cannot bypass the guardian's explicit block via invite
|
|
230
257
|
// links. Return the same generic failure as an invalid token to avoid
|
|
231
258
|
// leaking membership status to the caller.
|
|
232
|
-
if (existingChannel &&
|
|
259
|
+
if (existingChannel && gateStatus === "blocked") {
|
|
233
260
|
return { ok: false, reason: "invalid_token" };
|
|
234
261
|
}
|
|
235
262
|
|
|
@@ -465,11 +492,12 @@ export async function redeemVoiceInviteCode(params: {
|
|
|
465
492
|
// should bind the sender's identity to the target contact, not the existing one.
|
|
466
493
|
const targetMismatch = voiceContact && voiceContact.id !== invite.contactId;
|
|
467
494
|
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
existingVoiceChannel
|
|
471
|
-
|
|
472
|
-
|
|
495
|
+
const gateStatus = await resolveMemberGateStatus(
|
|
496
|
+
await getPhoneCallerVerdict(canonicalCallerId),
|
|
497
|
+
existingVoiceChannel?.status ?? null,
|
|
498
|
+
);
|
|
499
|
+
|
|
500
|
+
if (existingVoiceChannel && gateStatus === "active" && !targetMismatch) {
|
|
473
501
|
return {
|
|
474
502
|
ok: true,
|
|
475
503
|
type: "already_member",
|
|
@@ -478,7 +506,7 @@ export async function redeemVoiceInviteCode(params: {
|
|
|
478
506
|
}
|
|
479
507
|
|
|
480
508
|
// Blocked members cannot bypass the guardian's explicit block
|
|
481
|
-
if (existingVoiceChannel &&
|
|
509
|
+
if (existingVoiceChannel && gateStatus === "blocked") {
|
|
482
510
|
return { ok: false, reason: "invalid_or_expired" };
|
|
483
511
|
}
|
|
484
512
|
|
|
@@ -639,18 +667,22 @@ export async function redeemInviteByCode(params: {
|
|
|
639
667
|
const targetMismatch =
|
|
640
668
|
existingContact && existingContact.id !== invite.contactId;
|
|
641
669
|
|
|
642
|
-
|
|
643
|
-
|
|
644
|
-
|
|
645
|
-
|
|
646
|
-
|
|
670
|
+
const gateStatus = await resolveMemberGateStatus(
|
|
671
|
+
await getInboundTrustVerdict({
|
|
672
|
+
channelType: sourceChannel as ChannelId,
|
|
673
|
+
actorExternalId: canonicalUserId,
|
|
674
|
+
}),
|
|
675
|
+
existingChannel?.status ?? null,
|
|
676
|
+
);
|
|
677
|
+
|
|
678
|
+
if (existingChannel && gateStatus === "active" && !targetMismatch) {
|
|
647
679
|
return { ok: true, type: "already_member", memberId: existingChannel.id };
|
|
648
680
|
}
|
|
649
681
|
|
|
650
682
|
// Blocked members cannot bypass the guardian's explicit block via invite
|
|
651
683
|
// codes. Return the same generic failure as an invalid token to avoid
|
|
652
684
|
// leaking membership status to the caller.
|
|
653
|
-
if (existingChannel &&
|
|
685
|
+
if (existingChannel && gateStatus === "blocked") {
|
|
654
686
|
return { ok: false, reason: "invalid_token" };
|
|
655
687
|
}
|
|
656
688
|
|
|
@@ -14,6 +14,11 @@
|
|
|
14
14
|
import type { ChannelId } from "../channels/types.js";
|
|
15
15
|
import { isHttpAuthDisabled } from "../config/env.js";
|
|
16
16
|
import { findGuardianForChannel } from "../contacts/contact-store.js";
|
|
17
|
+
import {
|
|
18
|
+
getGuardianDelivery,
|
|
19
|
+
guardianForChannel,
|
|
20
|
+
peekCachedGuardianDelivery,
|
|
21
|
+
} from "../contacts/guardian-delivery-reader.js";
|
|
17
22
|
import type { TrustContext } from "../daemon/trust-context.js";
|
|
18
23
|
import { getLogger } from "../util/logger.js";
|
|
19
24
|
import { DAEMON_INTERNAL_ASSISTANT_ID } from "./assistant-scope.js";
|
|
@@ -45,13 +50,48 @@ export function buildLocalAuthContext(conversationId: string): AuthContext {
|
|
|
45
50
|
}
|
|
46
51
|
|
|
47
52
|
/**
|
|
48
|
-
*
|
|
53
|
+
* Resolve the local vellum guardian's principalId from the gateway.
|
|
49
54
|
*
|
|
50
|
-
*
|
|
51
|
-
*
|
|
55
|
+
* The gateway owns guardian binding; this reads it through the cached
|
|
56
|
+
* `getGuardianDelivery` reader (PR-3 TTL + single-flight) so hot paths don't
|
|
57
|
+
* storm the IPC. Falls back to the local contacts table for the bootstrap /
|
|
58
|
+
* first-run window where the gateway has no guardian yet or is unreachable
|
|
59
|
+
* (the reader returns `null`).
|
|
60
|
+
*
|
|
61
|
+
* Returns `undefined` when no vellum guardian binding exists in either source
|
|
62
|
+
* (e.g. fresh install before bootstrap). Callers should treat that case as
|
|
52
63
|
* "not yet available" and either fall back or proceed without a principalId.
|
|
53
64
|
*/
|
|
54
|
-
export function findLocalGuardianPrincipalId():
|
|
65
|
+
export async function findLocalGuardianPrincipalId(): Promise<
|
|
66
|
+
string | undefined
|
|
67
|
+
> {
|
|
68
|
+
const list = await getGuardianDelivery({ channelTypes: ["vellum"] });
|
|
69
|
+
if (list) {
|
|
70
|
+
const principalId = guardianForChannel(list, "vellum")?.principalId;
|
|
71
|
+
if (principalId) return principalId;
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
return findLocalGuardianPrincipalIdFromStore();
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
/**
|
|
78
|
+
* Synchronous read of the vellum guardian's principalId for paths that cannot
|
|
79
|
+
* await {@link findLocalGuardianPrincipalId} — namely the SSE eager-subscribe
|
|
80
|
+
* path (`events-routes`), which registers before the stream is created.
|
|
81
|
+
*
|
|
82
|
+
* Reads the same gateway-owned binding as the async path via a sync, IO-free
|
|
83
|
+
* snapshot of the guardian-delivery cache (kept fresh by the async hot paths
|
|
84
|
+
* and event-driven invalidation), so SSE registers the SAME principal the
|
|
85
|
+
* send/result routes resolve. Falls back to the local store when the cache is
|
|
86
|
+
* cold — the same fallback the async path lands on during bootstrap.
|
|
87
|
+
*/
|
|
88
|
+
export function findLocalGuardianPrincipalIdFromStore(): string | undefined {
|
|
89
|
+
const cached = peekCachedGuardianDelivery({ channelTypes: ["vellum"] });
|
|
90
|
+
if (cached) {
|
|
91
|
+
const principalId = guardianForChannel(cached, "vellum")?.principalId;
|
|
92
|
+
if (principalId) return principalId;
|
|
93
|
+
}
|
|
94
|
+
|
|
55
95
|
return findGuardianForChannel("vellum")?.contact.principalId ?? undefined;
|
|
56
96
|
}
|
|
57
97
|
|
|
@@ -76,12 +116,35 @@ export function findLocalGuardianPrincipalId(): string | undefined {
|
|
|
76
116
|
* yet (e.g. fresh install before bootstrap); callers must treat this the
|
|
77
117
|
* same as a missing principal.
|
|
78
118
|
*/
|
|
79
|
-
export function resolveActorPrincipalIdForLocalGuardian(
|
|
119
|
+
export async function resolveActorPrincipalIdForLocalGuardian(
|
|
120
|
+
rawHeader: string | undefined,
|
|
121
|
+
): Promise<string | undefined> {
|
|
122
|
+
if (rawHeader !== "dev-bypass" || !isHttpAuthDisabled()) return rawHeader;
|
|
123
|
+
|
|
124
|
+
const guardianPrincipalId = await findLocalGuardianPrincipalId();
|
|
125
|
+
if (guardianPrincipalId) return guardianPrincipalId;
|
|
126
|
+
|
|
127
|
+
log.warn(
|
|
128
|
+
"dev-bypass actor principal received but no vellum guardian binding found; returning undefined",
|
|
129
|
+
);
|
|
130
|
+
return undefined;
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
/**
|
|
134
|
+
* Synchronous variant of {@link resolveActorPrincipalIdForLocalGuardian} for
|
|
135
|
+
* the SSE eager-subscribe path, which registers before the response stream is
|
|
136
|
+
* created and cannot await. Resolves the guardian from the IO-free gateway
|
|
137
|
+
* cache snapshot first (same source the async path reads), falling back to the
|
|
138
|
+
* local store when the cache is cold — so SSE registers the SAME principal the
|
|
139
|
+
* send/result routes resolve and host-proxy targeting matches the same-user
|
|
140
|
+
* client even when the local contact row is stale.
|
|
141
|
+
*/
|
|
142
|
+
export function resolveActorPrincipalIdForLocalGuardianSync(
|
|
80
143
|
rawHeader: string | undefined,
|
|
81
144
|
): string | undefined {
|
|
82
145
|
if (rawHeader !== "dev-bypass" || !isHttpAuthDisabled()) return rawHeader;
|
|
83
146
|
|
|
84
|
-
const guardianPrincipalId =
|
|
147
|
+
const guardianPrincipalId = findLocalGuardianPrincipalIdFromStore();
|
|
85
148
|
if (guardianPrincipalId) return guardianPrincipalId;
|
|
86
149
|
|
|
87
150
|
log.warn(
|
|
@@ -102,12 +165,12 @@ export function resolveActorPrincipalIdForLocalGuardian(
|
|
|
102
165
|
* bootstrap), falls back to a minimal guardian context so the local
|
|
103
166
|
* user is not incorrectly denied.
|
|
104
167
|
*/
|
|
105
|
-
export function resolveLocalTrustContext(
|
|
168
|
+
export async function resolveLocalTrustContext(
|
|
106
169
|
sourceChannel: ChannelId = "vellum",
|
|
107
|
-
): TrustContext {
|
|
170
|
+
): Promise<TrustContext> {
|
|
108
171
|
const assistantId = DAEMON_INTERNAL_ASSISTANT_ID;
|
|
109
172
|
|
|
110
|
-
const guardianPrincipalId = findLocalGuardianPrincipalId();
|
|
173
|
+
const guardianPrincipalId = await findLocalGuardianPrincipalId();
|
|
111
174
|
if (guardianPrincipalId) {
|
|
112
175
|
const trustCtx = resolveTrustContext({
|
|
113
176
|
assistantId,
|
|
@@ -139,10 +202,12 @@ export function resolveLocalTrustContext(
|
|
|
139
202
|
* downstream code to resolve guardian context using the same
|
|
140
203
|
* `authContext.actorPrincipalId` path as HTTP sessions.
|
|
141
204
|
*/
|
|
142
|
-
export function resolveLocalAuthContext(
|
|
205
|
+
export async function resolveLocalAuthContext(
|
|
206
|
+
conversationId: string,
|
|
207
|
+
): Promise<AuthContext> {
|
|
143
208
|
const authContext = buildLocalAuthContext(conversationId);
|
|
144
209
|
|
|
145
|
-
const guardianPrincipalId = findLocalGuardianPrincipalId();
|
|
210
|
+
const guardianPrincipalId = await findLocalGuardianPrincipalId();
|
|
146
211
|
if (guardianPrincipalId) {
|
|
147
212
|
return { ...authContext, actorPrincipalId: guardianPrincipalId };
|
|
148
213
|
}
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Derives a local principal's {@link TrustContext} from the gateway guardian
|
|
3
|
+
* binding. Fails closed to unknown on a missing or null read.
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import type { ChannelId } from "../channels/types.js";
|
|
7
|
+
import { getGuardianDelivery } from "../contacts/guardian-delivery-reader.js";
|
|
8
|
+
import type { TrustContext } from "../daemon/trust-context.js";
|
|
9
|
+
import { canonicalizeInboundIdentity } from "../util/canonicalize-identity.js";
|
|
10
|
+
|
|
11
|
+
export interface ResolveLocalPrincipalTrustInput {
|
|
12
|
+
actorPrincipalId: string;
|
|
13
|
+
sourceChannel: ChannelId;
|
|
14
|
+
conversationExternalId: string;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
/** Guardian match → guardian ctx; miss or null read → unknown (fail closed). */
|
|
18
|
+
export async function resolveLocalPrincipalTrustContext(
|
|
19
|
+
input: ResolveLocalPrincipalTrustInput,
|
|
20
|
+
): Promise<TrustContext> {
|
|
21
|
+
const unknownContext: TrustContext = {
|
|
22
|
+
sourceChannel: input.sourceChannel,
|
|
23
|
+
trustClass: "unknown",
|
|
24
|
+
requesterExternalUserId: input.actorPrincipalId,
|
|
25
|
+
requesterChatId: input.conversationExternalId,
|
|
26
|
+
};
|
|
27
|
+
|
|
28
|
+
// Fail closed: a null read means the gateway is unreachable — never grant
|
|
29
|
+
// guardian on a miss.
|
|
30
|
+
const guardians = await getGuardianDelivery({ channelTypes: ["vellum"] });
|
|
31
|
+
if (!guardians) return unknownContext;
|
|
32
|
+
|
|
33
|
+
const guardian = guardians.find(
|
|
34
|
+
(g) => g.principalId === input.actorPrincipalId,
|
|
35
|
+
);
|
|
36
|
+
if (!guardian) return unknownContext;
|
|
37
|
+
|
|
38
|
+
return {
|
|
39
|
+
sourceChannel: input.sourceChannel,
|
|
40
|
+
trustClass: "guardian",
|
|
41
|
+
guardianChatId: guardian.externalChatId ?? input.conversationExternalId,
|
|
42
|
+
guardianExternalUserId:
|
|
43
|
+
canonicalizeInboundIdentity(input.sourceChannel, guardian.address) ??
|
|
44
|
+
undefined,
|
|
45
|
+
guardianPrincipalId: guardian.principalId ?? undefined,
|
|
46
|
+
// Mirror toTrustContext: with no username the requester identifier is the
|
|
47
|
+
// canonical sender id, which for a vellum principal is actorPrincipalId.
|
|
48
|
+
requesterIdentifier: input.actorPrincipalId,
|
|
49
|
+
requesterExternalUserId: input.actorPrincipalId,
|
|
50
|
+
requesterChatId: input.conversationExternalId,
|
|
51
|
+
};
|
|
52
|
+
}
|
|
@@ -229,6 +229,15 @@ export function getByConversation(
|
|
|
229
229
|
* /v1/host-browser-result, /v1/host-app-control-result, or
|
|
230
230
|
* /v1/host-transfer-result after completing the operation, get a 404, and the
|
|
231
231
|
* proxy timer would fire with a spurious timeout error.
|
|
232
|
+
*
|
|
233
|
+
* `question` interactions are also skipped: a new message supersedes an open
|
|
234
|
+
* ask_question by steering to it (see the enqueue path in
|
|
235
|
+
* conversation-routes.ts), which aborts the parked turn and settles the
|
|
236
|
+
* question via its abort signal. Clearing the entry here instead would drop it
|
|
237
|
+
* without settling the prompt's Promise (questions carry no `rpcResolve`
|
|
238
|
+
* fallback like secrets do) and would strip the steer of the entry it needs to
|
|
239
|
+
* fire — which can co-occur with a confirmation, since one model response can
|
|
240
|
+
* open both tools concurrently.
|
|
232
241
|
*/
|
|
233
242
|
export function removeByConversation(
|
|
234
243
|
conversationId: string,
|
|
@@ -244,7 +253,8 @@ export function removeByConversation(
|
|
|
244
253
|
interaction.kind !== "host_browser" &&
|
|
245
254
|
interaction.kind !== "host_app_control" &&
|
|
246
255
|
interaction.kind !== "host_transfer" &&
|
|
247
|
-
interaction.kind !== "acp_confirmation"
|
|
256
|
+
interaction.kind !== "acp_confirmation" &&
|
|
257
|
+
interaction.kind !== "question"
|
|
248
258
|
) {
|
|
249
259
|
// resolve() clears the stored timer and detaches abort listeners.
|
|
250
260
|
resolve(requestId, state);
|
|
@@ -10,6 +10,7 @@
|
|
|
10
10
|
|
|
11
11
|
import { beforeEach, describe, expect, mock, test } from "bun:test";
|
|
12
12
|
|
|
13
|
+
import type { GuardianDelivery } from "@vellumai/gateway-client";
|
|
13
14
|
import { IpcCallError } from "@vellumai/gateway-client/ipc-client";
|
|
14
15
|
|
|
15
16
|
type IpcCall = { method: string; params?: Record<string, unknown> };
|
|
@@ -73,8 +74,16 @@ mock.module("../../channel-verification-service.js", () => ({
|
|
|
73
74
|
getGuardianBinding: mock(() => guardianBinding),
|
|
74
75
|
}));
|
|
75
76
|
|
|
76
|
-
// Contact-store lookup that resolves the guardian's channel to downgrade.
|
|
77
|
-
|
|
77
|
+
// Contact-store lookup that resolves the guardian's channel to downgrade. The
|
|
78
|
+
// channel carries the type/address/externalChatId the gateway delivery is
|
|
79
|
+
// matched against (see deliveryForChannel).
|
|
80
|
+
let contactChannel: {
|
|
81
|
+
id: string;
|
|
82
|
+
status: string;
|
|
83
|
+
type: string;
|
|
84
|
+
address: string;
|
|
85
|
+
externalChatId: string;
|
|
86
|
+
} | null = null;
|
|
78
87
|
const actualContactStore = await import("../../../contacts/contact-store.js");
|
|
79
88
|
mock.module("../../../contacts/contact-store.js", () => ({
|
|
80
89
|
...actualContactStore,
|
|
@@ -83,6 +92,20 @@ mock.module("../../../contacts/contact-store.js", () => ({
|
|
|
83
92
|
),
|
|
84
93
|
}));
|
|
85
94
|
|
|
95
|
+
// Gateway delivery (ACL source of truth). The revoke gate relays only when the
|
|
96
|
+
// matching delivery is live (active/pending/unverified); already-revoked or a
|
|
97
|
+
// missing delivery short-circuits the relay.
|
|
98
|
+
let guardianDeliveries: GuardianDelivery[] | null = null;
|
|
99
|
+
mock.module("../../../contacts/guardian-delivery-reader.js", () => ({
|
|
100
|
+
getGuardianDelivery: mock(async (input?: { channelTypes?: string[] }) => {
|
|
101
|
+
if (guardianDeliveries == null) return null;
|
|
102
|
+
if (!input?.channelTypes) return guardianDeliveries;
|
|
103
|
+
return guardianDeliveries.filter((g) =>
|
|
104
|
+
input.channelTypes!.includes(g.channelType),
|
|
105
|
+
);
|
|
106
|
+
}),
|
|
107
|
+
}));
|
|
108
|
+
|
|
86
109
|
// Guard: the contact-channel ACL write moves to the gateway relay and must
|
|
87
110
|
// never run locally. The assistant-owned guardian-binding teardown
|
|
88
111
|
// (revokeGuardianBinding) still runs locally — assert it is invoked.
|
|
@@ -122,7 +145,24 @@ describe("verification revoke relay", () => {
|
|
|
122
145
|
guardianExternalUserId: "guardian-user",
|
|
123
146
|
guardianDeliveryChatId: "chat-1",
|
|
124
147
|
};
|
|
125
|
-
contactChannel = {
|
|
148
|
+
contactChannel = {
|
|
149
|
+
id: "ch1",
|
|
150
|
+
status: "active",
|
|
151
|
+
type: "telegram",
|
|
152
|
+
address: "guardian-user",
|
|
153
|
+
externalChatId: "chat-1",
|
|
154
|
+
};
|
|
155
|
+
// Gateway delivery is live by default, so the revoke relay fires.
|
|
156
|
+
guardianDeliveries = [
|
|
157
|
+
{
|
|
158
|
+
channelType: "telegram",
|
|
159
|
+
contactId: "c1",
|
|
160
|
+
address: "guardian-user",
|
|
161
|
+
externalChatId: "chat-1",
|
|
162
|
+
status: "active",
|
|
163
|
+
verifiedAt: 1700000000,
|
|
164
|
+
},
|
|
165
|
+
];
|
|
126
166
|
ipcCallPersistentMock.mockClear();
|
|
127
167
|
cancelOutboundMock.mockClear();
|
|
128
168
|
revokePendingSessionsMock.mockClear();
|
|
@@ -263,8 +303,19 @@ describe("verification revoke relay", () => {
|
|
|
263
303
|
expect(result.bound).toBe(false);
|
|
264
304
|
});
|
|
265
305
|
|
|
266
|
-
test("skips the relay when the
|
|
267
|
-
|
|
306
|
+
test("skips the relay when the gateway delivery is already revoked", async () => {
|
|
307
|
+
// The gateway (source of truth) already shows the channel revoked, so the
|
|
308
|
+
// redundant relay is skipped even though session/binding teardown runs.
|
|
309
|
+
guardianDeliveries = [
|
|
310
|
+
{
|
|
311
|
+
channelType: "telegram",
|
|
312
|
+
contactId: "c1",
|
|
313
|
+
address: "guardian-user",
|
|
314
|
+
externalChatId: "chat-1",
|
|
315
|
+
status: "revoked",
|
|
316
|
+
verifiedAt: 1700000000,
|
|
317
|
+
},
|
|
318
|
+
];
|
|
268
319
|
|
|
269
320
|
await revokeHandler({ body: { channel: "telegram" } });
|
|
270
321
|
|
|
@@ -24,7 +24,7 @@ mock.module("../../../daemon/handlers/config-channels.js", () => ({
|
|
|
24
24
|
verifyTrustedContactCalls.push([contactChannelId, assistantId]);
|
|
25
25
|
return verifyTrustedContactImpl(contactChannelId, assistantId);
|
|
26
26
|
},
|
|
27
|
-
createInboundChallenge: () => ({ success: true }),
|
|
27
|
+
createInboundChallenge: async () => ({ success: true }),
|
|
28
28
|
getVerificationStatus: () => ({ success: true }),
|
|
29
29
|
revokeVerificationForChannel: () => ({ success: true }),
|
|
30
30
|
}));
|