@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
|
@@ -779,6 +779,31 @@ describe("getUsageDayBuckets", () => {
|
|
|
779
779
|
expect(buckets[0].totalEstimatedCostUsd).toBeCloseTo(0.05);
|
|
780
780
|
expect(buckets[0].eventCount).toBe(2);
|
|
781
781
|
});
|
|
782
|
+
|
|
783
|
+
test("collapses many events within a sub-bucket span without losing totals", () => {
|
|
784
|
+
// The read path pre-aggregates events into 15-minute UTC buckets in SQL
|
|
785
|
+
// before local-time bucketing. Many events inside one such window must
|
|
786
|
+
// still sum exactly, and events split across the 15-minute boundary but
|
|
787
|
+
// within the same local day must land in the same day bucket.
|
|
788
|
+
const windowStart = utcMs(2025, 3, 1, 10); // 10:00:00 UTC
|
|
789
|
+
for (let i = 0; i < 50; i++) {
|
|
790
|
+
// Spread across ~16 minutes so the events straddle a 15-minute boundary.
|
|
791
|
+
insertEventAt(windowStart + i * 20_000, {
|
|
792
|
+
inputTokens: 2,
|
|
793
|
+
outputTokens: 1,
|
|
794
|
+
});
|
|
795
|
+
}
|
|
796
|
+
|
|
797
|
+
const buckets = getUsageDayBuckets({
|
|
798
|
+
from: utcMs(2025, 3, 1),
|
|
799
|
+
to: utcMs(2025, 3, 2),
|
|
800
|
+
});
|
|
801
|
+
expect(buckets).toHaveLength(1);
|
|
802
|
+
expect(buckets[0].date).toBe("2025-03-01");
|
|
803
|
+
expect(buckets[0].totalInputTokens).toBe(100);
|
|
804
|
+
expect(buckets[0].totalOutputTokens).toBe(50);
|
|
805
|
+
expect(buckets[0].eventCount).toBe(50);
|
|
806
|
+
});
|
|
782
807
|
});
|
|
783
808
|
|
|
784
809
|
describe("getUsageDayBuckets — timezone-aware", () => {
|
|
@@ -176,6 +176,22 @@ mock.module("../calls/channel-admission-reader.js", () => ({
|
|
|
176
176
|
getChannelAdmissionPolicy: mockGetChannelAdmissionPolicy,
|
|
177
177
|
}));
|
|
178
178
|
|
|
179
|
+
// Mock the inbound trust reader. handleStart awaits this and threads the
|
|
180
|
+
// verdict into routeSetup so the media-stream transport enforces the same
|
|
181
|
+
// gateway ACL as ConversationRelay. Tests override mockInboundVerdict.
|
|
182
|
+
let mockInboundVerdict: unknown = null;
|
|
183
|
+
const mockGetInboundTrustVerdict = jest.fn(
|
|
184
|
+
async (_args?: Record<string, unknown>) => mockInboundVerdict,
|
|
185
|
+
);
|
|
186
|
+
mock.module("../calls/inbound-trust-reader.js", () => ({
|
|
187
|
+
getInboundTrustVerdict: mockGetInboundTrustVerdict,
|
|
188
|
+
getPhoneCallerVerdict: (otherPartyNumber: string | undefined) =>
|
|
189
|
+
mockGetInboundTrustVerdict({
|
|
190
|
+
channelType: "phone",
|
|
191
|
+
actorExternalId: otherPartyNumber || undefined,
|
|
192
|
+
}),
|
|
193
|
+
}));
|
|
194
|
+
|
|
179
195
|
// Mock the actor trust resolver (used by handleStart to derive trust context)
|
|
180
196
|
mock.module("../runtime/actor-trust-resolver.js", () => ({
|
|
181
197
|
toTrustContext: jest.fn(() => ({
|
|
@@ -356,6 +372,8 @@ beforeEach(() => {
|
|
|
356
372
|
mockGetChannelAdmissionPolicy.mockClear();
|
|
357
373
|
mockAdmissionPolicy = null;
|
|
358
374
|
mockAdmissionGate = null;
|
|
375
|
+
mockGetInboundTrustVerdict.mockClear();
|
|
376
|
+
mockInboundVerdict = null;
|
|
359
377
|
// Reset routeSetup to default normal_call
|
|
360
378
|
mockRouteSetupResult = {
|
|
361
379
|
outcome: { action: "normal_call" as const, isInbound: true },
|
|
@@ -1535,4 +1553,113 @@ describe("media-stream setup outcome scenarios", () => {
|
|
|
1535
1553
|
expect(speakSystemPrompt).not.toHaveBeenCalled();
|
|
1536
1554
|
});
|
|
1537
1555
|
});
|
|
1556
|
+
|
|
1557
|
+
// ── Gateway trust verdict ──────────────────────────────────────────
|
|
1558
|
+
// handleStart fetches getInboundTrustVerdict for the inbound caller and
|
|
1559
|
+
// threads it into routeSetup, so the media-stream transport enforces the
|
|
1560
|
+
// same gateway ACL as ConversationRelay. routeSetup itself decides
|
|
1561
|
+
// verdict-vs-local; these tests assert the verdict is fetched and passed.
|
|
1562
|
+
|
|
1563
|
+
describe("gateway trust verdict", () => {
|
|
1564
|
+
test("fetches the inbound caller's verdict and threads it into routeSetup", async () => {
|
|
1565
|
+
mockInboundVerdict = {
|
|
1566
|
+
channelType: "phone",
|
|
1567
|
+
actorExternalId: "+14155550000",
|
|
1568
|
+
contactId: "contact-1",
|
|
1569
|
+
channelId: "channel-1",
|
|
1570
|
+
status: "verified",
|
|
1571
|
+
policy: "allow",
|
|
1572
|
+
resolutionFailed: false,
|
|
1573
|
+
};
|
|
1574
|
+
|
|
1575
|
+
const mockWs = createMockWs();
|
|
1576
|
+
mockSessions.set("call-verdict-thread-1", {
|
|
1577
|
+
id: "call-verdict-thread-1",
|
|
1578
|
+
conversationId: "conv-verdict-thread-1",
|
|
1579
|
+
status: "initiated",
|
|
1580
|
+
task: null,
|
|
1581
|
+
startedAt: null,
|
|
1582
|
+
fromNumber: "+14155550000",
|
|
1583
|
+
toNumber: "+15550001111",
|
|
1584
|
+
});
|
|
1585
|
+
|
|
1586
|
+
const session = new MediaStreamCallSession(
|
|
1587
|
+
mockWs.ws,
|
|
1588
|
+
"call-verdict-thread-1",
|
|
1589
|
+
);
|
|
1590
|
+
session.handleMessage(makeStartMessage());
|
|
1591
|
+
await session.whenSetupSettled();
|
|
1592
|
+
|
|
1593
|
+
// Verdict fetched for the inbound caller (from number) on the phone channel.
|
|
1594
|
+
expect(mockGetInboundTrustVerdict).toHaveBeenCalledWith({
|
|
1595
|
+
channelType: "phone",
|
|
1596
|
+
actorExternalId: "+14155550000",
|
|
1597
|
+
});
|
|
1598
|
+
// Verdict threaded into routeSetup.
|
|
1599
|
+
expect(routeSetup).toHaveBeenCalledWith(
|
|
1600
|
+
expect.objectContaining({ verdict: mockInboundVerdict }),
|
|
1601
|
+
);
|
|
1602
|
+
});
|
|
1603
|
+
|
|
1604
|
+
test("a blocked/denied member verdict is enforced (deny) on the media-stream transport", async () => {
|
|
1605
|
+
// The real router returns `deny` for a member verdict whose ACL is
|
|
1606
|
+
// blocked/revoked/deny; the mock reflects that outcome here.
|
|
1607
|
+
mockInboundVerdict = {
|
|
1608
|
+
channelType: "phone",
|
|
1609
|
+
actorExternalId: "+14155550000",
|
|
1610
|
+
contactId: "contact-1",
|
|
1611
|
+
channelId: "channel-1",
|
|
1612
|
+
status: "blocked",
|
|
1613
|
+
policy: "deny",
|
|
1614
|
+
resolutionFailed: false,
|
|
1615
|
+
};
|
|
1616
|
+
mockRouteSetupResult = {
|
|
1617
|
+
outcome: {
|
|
1618
|
+
action: "deny",
|
|
1619
|
+
message:
|
|
1620
|
+
"This number is not authorized to reach the assistant right now.",
|
|
1621
|
+
logReason: "Inbound voice ACL: member blocked",
|
|
1622
|
+
},
|
|
1623
|
+
resolved: {
|
|
1624
|
+
assistantId: "self",
|
|
1625
|
+
isInbound: true,
|
|
1626
|
+
otherPartyNumber: "+14155550000",
|
|
1627
|
+
actorTrust: { trustClass: "unknown", memberRecord: null },
|
|
1628
|
+
},
|
|
1629
|
+
};
|
|
1630
|
+
|
|
1631
|
+
const mockWs = createMockWs();
|
|
1632
|
+
mockSessions.set("call-verdict-deny-1", {
|
|
1633
|
+
id: "call-verdict-deny-1",
|
|
1634
|
+
conversationId: "conv-verdict-deny-1",
|
|
1635
|
+
status: "initiated",
|
|
1636
|
+
task: null,
|
|
1637
|
+
startedAt: null,
|
|
1638
|
+
fromNumber: "+14155550000",
|
|
1639
|
+
toNumber: "+15550001111",
|
|
1640
|
+
});
|
|
1641
|
+
|
|
1642
|
+
const session = new MediaStreamCallSession(
|
|
1643
|
+
mockWs.ws,
|
|
1644
|
+
"call-verdict-deny-1",
|
|
1645
|
+
);
|
|
1646
|
+
session.handleMessage(makeStartMessage());
|
|
1647
|
+
await session.whenSetupSettled();
|
|
1648
|
+
|
|
1649
|
+
// Verdict was passed into routeSetup, which denied the caller.
|
|
1650
|
+
expect(routeSetup).toHaveBeenCalledWith(
|
|
1651
|
+
expect.objectContaining({ verdict: mockInboundVerdict }),
|
|
1652
|
+
);
|
|
1653
|
+
expect(speakSystemPrompt).toHaveBeenCalledWith(
|
|
1654
|
+
expect.anything(),
|
|
1655
|
+
"This number is not authorized to reach the assistant right now.",
|
|
1656
|
+
);
|
|
1657
|
+
expect(updateCallSession).toHaveBeenCalledWith(
|
|
1658
|
+
"call-verdict-deny-1",
|
|
1659
|
+
expect.objectContaining({ status: "failed" }),
|
|
1660
|
+
);
|
|
1661
|
+
expect(registerCallController).not.toHaveBeenCalled();
|
|
1662
|
+
expect(mockStartInitialGreeting).not.toHaveBeenCalled();
|
|
1663
|
+
});
|
|
1664
|
+
});
|
|
1538
1665
|
});
|
|
@@ -19,6 +19,8 @@ import { beforeEach, describe, expect, mock, test } from "bun:test";
|
|
|
19
19
|
// bindings resolve through the mocks.
|
|
20
20
|
const updateMessageMetadataMock = mock((_id: string, _updates: unknown) => {});
|
|
21
21
|
mock.module("../memory/conversation-crud.js", () => ({
|
|
22
|
+
setConversationProcessingStartedAt: () => {},
|
|
23
|
+
isConversationProcessing: () => false,
|
|
22
24
|
updateMessageMetadata: updateMessageMetadataMock,
|
|
23
25
|
}));
|
|
24
26
|
|
|
@@ -79,6 +79,8 @@ const getBindingByChannelChatMock = mock(
|
|
|
79
79
|
);
|
|
80
80
|
|
|
81
81
|
mock.module("../memory/conversation-crud.js", () => ({
|
|
82
|
+
setConversationProcessingStartedAt: () => {},
|
|
83
|
+
isConversationProcessing: () => false,
|
|
82
84
|
addMessage: addMessageMock,
|
|
83
85
|
getConversation: getConversationMock,
|
|
84
86
|
reserveMessage: mock(async () => ({ id: "msg-reserve" })),
|
|
@@ -421,8 +421,8 @@ describe("handleMigrationImport — JSON {url} body", () => {
|
|
|
421
421
|
describe("handleMigrationImport — no-swap path omits newer-migration warning", () => {
|
|
422
422
|
test("credentials-only bundle does not inherit live-DB migration warnings", async () => {
|
|
423
423
|
// Seed the live workspace DB with a step:* checkpoint that's NOT
|
|
424
|
-
// in the
|
|
425
|
-
// version" and would otherwise push a warning into the report. With
|
|
424
|
+
// in the known step list. validateMigrationState treats this as a
|
|
425
|
+
// "newer version" and would otherwise push a warning into the report. With
|
|
426
426
|
// the gate in appendNewerMigrationWarningsIfAny the warning must be
|
|
427
427
|
// suppressed when the import didn't modify the workspace.
|
|
428
428
|
const dbDir = join(testWorkspaceRoot, "data", "db");
|
|
@@ -95,6 +95,8 @@ mock.module("../config/loader.js", () => ({
|
|
|
95
95
|
}));
|
|
96
96
|
|
|
97
97
|
mock.module("../memory/conversation-crud.js", () => ({
|
|
98
|
+
setConversationProcessingStartedAt: () => {},
|
|
99
|
+
isConversationProcessing: () => false,
|
|
98
100
|
addMessage: () => ({ id: "mock-msg-id" }),
|
|
99
101
|
getMessageById: () => null,
|
|
100
102
|
updateMessageContent: () => {},
|
|
@@ -61,6 +61,36 @@ mock.module("../runtime/gateway-client.js", () => ({
|
|
|
61
61
|
},
|
|
62
62
|
}));
|
|
63
63
|
|
|
64
|
+
// Guardian identity for the access request resolves via the gateway delivery
|
|
65
|
+
// reader, not the local contacts DB. Seed it per-test via seedGatewayGuardian.
|
|
66
|
+
interface GatewayGuardian {
|
|
67
|
+
channelType: string;
|
|
68
|
+
contactId: string;
|
|
69
|
+
principalId?: string | null;
|
|
70
|
+
displayName?: string | null;
|
|
71
|
+
address: string;
|
|
72
|
+
externalChatId?: string | null;
|
|
73
|
+
status: string;
|
|
74
|
+
verifiedAt?: number | null;
|
|
75
|
+
}
|
|
76
|
+
let gatewayGuardians: GatewayGuardian[] = [];
|
|
77
|
+
mock.module("../contacts/guardian-delivery-reader.js", () => ({
|
|
78
|
+
getGuardianDelivery: async () => gatewayGuardians,
|
|
79
|
+
guardianForChannel: (list: GatewayGuardian[], channelType: string) =>
|
|
80
|
+
list.find((g) => g.channelType === channelType && g.status === "active"),
|
|
81
|
+
}));
|
|
82
|
+
|
|
83
|
+
function seedGatewayGuardian(g: Partial<GatewayGuardian> & {
|
|
84
|
+
channelType: string;
|
|
85
|
+
address: string;
|
|
86
|
+
}): void {
|
|
87
|
+
gatewayGuardians.push({
|
|
88
|
+
contactId: `c-${g.channelType}`,
|
|
89
|
+
status: "active",
|
|
90
|
+
...g,
|
|
91
|
+
});
|
|
92
|
+
}
|
|
93
|
+
|
|
64
94
|
import {
|
|
65
95
|
listCanonicalGuardianDeliveries,
|
|
66
96
|
listCanonicalGuardianRequests,
|
|
@@ -95,6 +125,7 @@ function resetState(): string {
|
|
|
95
125
|
db.run("DELETE FROM contacts");
|
|
96
126
|
emitSignalCalls.length = 0;
|
|
97
127
|
deliverReplyCalls.length = 0;
|
|
128
|
+
gatewayGuardians = [];
|
|
98
129
|
mockEmitResult = {
|
|
99
130
|
signalId: "mock-signal-id",
|
|
100
131
|
deduplicated: false,
|
|
@@ -102,8 +133,15 @@ function resetState(): string {
|
|
|
102
133
|
reason: "mock",
|
|
103
134
|
deliveryResults: [],
|
|
104
135
|
};
|
|
105
|
-
// Seed the vellum anchor binding (gateway does this at
|
|
136
|
+
// Seed the vellum anchor binding in the gateway list (gateway does this at
|
|
137
|
+
// startup in production). The DB write mirrors it for any local INFO reads.
|
|
106
138
|
const principalId = `vellum-principal-${crypto.randomUUID()}`;
|
|
139
|
+
seedGatewayGuardian({
|
|
140
|
+
channelType: "vellum",
|
|
141
|
+
address: principalId,
|
|
142
|
+
principalId,
|
|
143
|
+
displayName: principalId,
|
|
144
|
+
});
|
|
107
145
|
createGuardianBinding({
|
|
108
146
|
channel: "vellum",
|
|
109
147
|
guardianExternalUserId: principalId,
|
|
@@ -172,6 +210,12 @@ describe("non-member access request notification", () => {
|
|
|
172
210
|
|
|
173
211
|
test("guardian is notified when a non-member messages and a guardian binding exists", async () => {
|
|
174
212
|
// Set up a guardian binding for this channel using the anchor principal
|
|
213
|
+
seedGatewayGuardian({
|
|
214
|
+
channelType: "telegram",
|
|
215
|
+
address: "guardian-user-789",
|
|
216
|
+
externalChatId: "guardian-chat-789",
|
|
217
|
+
principalId: anchorPrincipalId,
|
|
218
|
+
});
|
|
175
219
|
createGuardianBinding({
|
|
176
220
|
channel: "telegram",
|
|
177
221
|
guardianExternalUserId: "guardian-user-789",
|
|
@@ -217,6 +261,12 @@ describe("non-member access request notification", () => {
|
|
|
217
261
|
});
|
|
218
262
|
|
|
219
263
|
test("no duplicate approval requests for repeated messages from same non-member", async () => {
|
|
264
|
+
seedGatewayGuardian({
|
|
265
|
+
channelType: "telegram",
|
|
266
|
+
address: "guardian-user-789",
|
|
267
|
+
externalChatId: "guardian-chat-789",
|
|
268
|
+
principalId: anchorPrincipalId,
|
|
269
|
+
});
|
|
220
270
|
createGuardianBinding({
|
|
221
271
|
channel: "telegram",
|
|
222
272
|
guardianExternalUserId: "guardian-user-789",
|
|
@@ -289,6 +339,12 @@ describe("non-member access request notification", () => {
|
|
|
289
339
|
// Only a voice guardian binding exists — no Telegram binding.
|
|
290
340
|
// Since cross-channel fallback was removed, the access request resolves
|
|
291
341
|
// to the assistant's vellum anchor identity instead.
|
|
342
|
+
seedGatewayGuardian({
|
|
343
|
+
channelType: "phone",
|
|
344
|
+
address: "guardian-voice-user",
|
|
345
|
+
externalChatId: "guardian-voice-chat",
|
|
346
|
+
principalId: anchorPrincipalId,
|
|
347
|
+
});
|
|
292
348
|
createGuardianBinding({
|
|
293
349
|
channel: "phone",
|
|
294
350
|
guardianExternalUserId: "guardian-voice-user",
|
|
@@ -351,8 +407,8 @@ describe("access-request-helper unit tests", () => {
|
|
|
351
407
|
anchorPrincipalId = resetState();
|
|
352
408
|
});
|
|
353
409
|
|
|
354
|
-
test("notifyGuardianOfAccessRequest returns no_sender_id when actorExternalId is absent", () => {
|
|
355
|
-
const result = notifyGuardianOfAccessRequest({
|
|
410
|
+
test("notifyGuardianOfAccessRequest returns no_sender_id when actorExternalId is absent", async () => {
|
|
411
|
+
const result = await notifyGuardianOfAccessRequest({
|
|
356
412
|
canonicalAssistantId: "self",
|
|
357
413
|
sourceChannel: "telegram",
|
|
358
414
|
conversationExternalId: "chat-123",
|
|
@@ -372,8 +428,8 @@ describe("access-request-helper unit tests", () => {
|
|
|
372
428
|
expect(pending.length).toBe(0);
|
|
373
429
|
});
|
|
374
430
|
|
|
375
|
-
test("notifyGuardianOfAccessRequest creates request with self-healed principal when no binding exists", () => {
|
|
376
|
-
const result = notifyGuardianOfAccessRequest({
|
|
431
|
+
test("notifyGuardianOfAccessRequest creates request with self-healed principal when no binding exists", async () => {
|
|
432
|
+
const result = await notifyGuardianOfAccessRequest({
|
|
377
433
|
canonicalAssistantId: "self",
|
|
378
434
|
sourceChannel: "telegram",
|
|
379
435
|
conversationExternalId: "chat-123",
|
|
@@ -400,8 +456,14 @@ describe("access-request-helper unit tests", () => {
|
|
|
400
456
|
expect(emitSignalCalls.length).toBe(1);
|
|
401
457
|
});
|
|
402
458
|
|
|
403
|
-
test("notifyGuardianOfAccessRequest falls back to assistant-anchored vellum identity when source-channel binding is missing", () => {
|
|
459
|
+
test("notifyGuardianOfAccessRequest falls back to assistant-anchored vellum identity when source-channel binding is missing", async () => {
|
|
404
460
|
// Only voice binding exists
|
|
461
|
+
seedGatewayGuardian({
|
|
462
|
+
channelType: "phone",
|
|
463
|
+
address: "guardian-voice",
|
|
464
|
+
externalChatId: "voice-chat",
|
|
465
|
+
principalId: "test-principal-id",
|
|
466
|
+
});
|
|
405
467
|
createGuardianBinding({
|
|
406
468
|
channel: "phone",
|
|
407
469
|
guardianExternalUserId: "guardian-voice",
|
|
@@ -410,7 +472,7 @@ describe("access-request-helper unit tests", () => {
|
|
|
410
472
|
verifiedVia: "test",
|
|
411
473
|
});
|
|
412
474
|
|
|
413
|
-
const result = notifyGuardianOfAccessRequest({
|
|
475
|
+
const result = await notifyGuardianOfAccessRequest({
|
|
414
476
|
canonicalAssistantId: "self",
|
|
415
477
|
sourceChannel: "telegram",
|
|
416
478
|
conversationExternalId: "tg-chat",
|
|
@@ -435,8 +497,20 @@ describe("access-request-helper unit tests", () => {
|
|
|
435
497
|
expect(payload.guardianBindingChannel).toBe("vellum");
|
|
436
498
|
});
|
|
437
499
|
|
|
438
|
-
test("notifyGuardianOfAccessRequest prefers source-channel binding over vellum anchor", () => {
|
|
500
|
+
test("notifyGuardianOfAccessRequest prefers source-channel binding over vellum anchor", async () => {
|
|
439
501
|
// Both Telegram and voice bindings exist with the anchor principal
|
|
502
|
+
seedGatewayGuardian({
|
|
503
|
+
channelType: "telegram",
|
|
504
|
+
address: "guardian-tg",
|
|
505
|
+
externalChatId: "tg-chat",
|
|
506
|
+
principalId: anchorPrincipalId,
|
|
507
|
+
});
|
|
508
|
+
seedGatewayGuardian({
|
|
509
|
+
channelType: "phone",
|
|
510
|
+
address: "guardian-voice",
|
|
511
|
+
externalChatId: "voice-chat",
|
|
512
|
+
principalId: anchorPrincipalId,
|
|
513
|
+
});
|
|
440
514
|
createGuardianBinding({
|
|
441
515
|
channel: "telegram",
|
|
442
516
|
guardianExternalUserId: "guardian-tg",
|
|
@@ -452,7 +526,7 @@ describe("access-request-helper unit tests", () => {
|
|
|
452
526
|
verifiedVia: "test",
|
|
453
527
|
});
|
|
454
528
|
|
|
455
|
-
const result = notifyGuardianOfAccessRequest({
|
|
529
|
+
const result = await notifyGuardianOfAccessRequest({
|
|
456
530
|
canonicalAssistantId: "self",
|
|
457
531
|
sourceChannel: "telegram",
|
|
458
532
|
conversationExternalId: "chat-123",
|
|
@@ -477,8 +551,100 @@ describe("access-request-helper unit tests", () => {
|
|
|
477
551
|
expect(payload.guardianBindingChannel).toBe("telegram");
|
|
478
552
|
});
|
|
479
553
|
|
|
480
|
-
test("notifyGuardianOfAccessRequest
|
|
481
|
-
|
|
554
|
+
test("notifyGuardianOfAccessRequest falls back to local source-channel binding when gateway delivery is empty", async () => {
|
|
555
|
+
// Gateway delivery read yields nothing (restart/timeout/malformed IPC).
|
|
556
|
+
gatewayGuardians = [];
|
|
557
|
+
// Local dual-written mirror still has the vellum anchor + telegram binding.
|
|
558
|
+
createGuardianBinding({
|
|
559
|
+
channel: "telegram",
|
|
560
|
+
guardianExternalUserId: "guardian-tg",
|
|
561
|
+
guardianDeliveryChatId: "tg-chat",
|
|
562
|
+
guardianPrincipalId: anchorPrincipalId,
|
|
563
|
+
verifiedVia: "test",
|
|
564
|
+
});
|
|
565
|
+
|
|
566
|
+
const result = await notifyGuardianOfAccessRequest({
|
|
567
|
+
canonicalAssistantId: "self",
|
|
568
|
+
sourceChannel: "telegram",
|
|
569
|
+
conversationExternalId: "chat-123",
|
|
570
|
+
actorExternalId: "unknown-user",
|
|
571
|
+
});
|
|
572
|
+
|
|
573
|
+
expect(result.notified).toBe(true);
|
|
574
|
+
|
|
575
|
+
const pending = listCanonicalGuardianRequests({
|
|
576
|
+
status: "pending",
|
|
577
|
+
requesterExternalUserId: "unknown-user",
|
|
578
|
+
kind: "access_request",
|
|
579
|
+
});
|
|
580
|
+
expect(pending.length).toBe(1);
|
|
581
|
+
// Request is decidable: local read supplied the principal + source binding.
|
|
582
|
+
expect(pending[0].guardianPrincipalId).toBe(anchorPrincipalId);
|
|
583
|
+
expect(pending[0].guardianExternalUserId).toBe("guardian-tg");
|
|
584
|
+
|
|
585
|
+
const payload = emitSignalCalls[0].contextPayload as Record<
|
|
586
|
+
string,
|
|
587
|
+
unknown
|
|
588
|
+
>;
|
|
589
|
+
expect(payload.guardianBindingChannel).toBe("telegram");
|
|
590
|
+
});
|
|
591
|
+
|
|
592
|
+
test("notifyGuardianOfAccessRequest falls back to local vellum anchor when gateway delivery is empty", async () => {
|
|
593
|
+
// Gateway empty; only the local vellum anchor from resetState remains.
|
|
594
|
+
gatewayGuardians = [];
|
|
595
|
+
|
|
596
|
+
const result = await notifyGuardianOfAccessRequest({
|
|
597
|
+
canonicalAssistantId: "self",
|
|
598
|
+
sourceChannel: "telegram",
|
|
599
|
+
conversationExternalId: "chat-123",
|
|
600
|
+
actorExternalId: "unknown-user",
|
|
601
|
+
});
|
|
602
|
+
|
|
603
|
+
expect(result.notified).toBe(true);
|
|
604
|
+
|
|
605
|
+
const pending = listCanonicalGuardianRequests({
|
|
606
|
+
status: "pending",
|
|
607
|
+
requesterExternalUserId: "unknown-user",
|
|
608
|
+
kind: "access_request",
|
|
609
|
+
});
|
|
610
|
+
expect(pending.length).toBe(1);
|
|
611
|
+
// Decidable via the local vellum anchor principal.
|
|
612
|
+
expect(pending[0].guardianPrincipalId).toBe(anchorPrincipalId);
|
|
613
|
+
|
|
614
|
+
const payload = emitSignalCalls[0].contextPayload as Record<
|
|
615
|
+
string,
|
|
616
|
+
unknown
|
|
617
|
+
>;
|
|
618
|
+
expect(payload.guardianBindingChannel).toBe("vellum");
|
|
619
|
+
});
|
|
620
|
+
|
|
621
|
+
test("notifyGuardianOfAccessRequest does not create a decisionable request when both gateway and local reads are empty", async () => {
|
|
622
|
+
// Genuinely unbound assistant: gateway empty AND no local binding. Prior
|
|
623
|
+
// behavior rejects creation of a decisionable request without a principal.
|
|
624
|
+
gatewayGuardians = [];
|
|
625
|
+
const db = getDb();
|
|
626
|
+
db.run("DELETE FROM contact_channels");
|
|
627
|
+
db.run("DELETE FROM contacts");
|
|
628
|
+
|
|
629
|
+
await expect(
|
|
630
|
+
notifyGuardianOfAccessRequest({
|
|
631
|
+
canonicalAssistantId: "self",
|
|
632
|
+
sourceChannel: "telegram",
|
|
633
|
+
conversationExternalId: "chat-123",
|
|
634
|
+
actorExternalId: "unknown-user",
|
|
635
|
+
}),
|
|
636
|
+
).rejects.toThrow();
|
|
637
|
+
|
|
638
|
+
const pending = listCanonicalGuardianRequests({
|
|
639
|
+
status: "pending",
|
|
640
|
+
requesterExternalUserId: "unknown-user",
|
|
641
|
+
kind: "access_request",
|
|
642
|
+
});
|
|
643
|
+
expect(pending.length).toBe(0);
|
|
644
|
+
});
|
|
645
|
+
|
|
646
|
+
test("notifyGuardianOfAccessRequest for voice channel includes actorDisplayName in contextPayload", async () => {
|
|
647
|
+
const result = await notifyGuardianOfAccessRequest({
|
|
482
648
|
canonicalAssistantId: "self",
|
|
483
649
|
sourceChannel: "phone",
|
|
484
650
|
conversationExternalId: "+15559998888",
|
|
@@ -508,8 +674,8 @@ describe("access-request-helper unit tests", () => {
|
|
|
508
674
|
expect(pending.length).toBe(1);
|
|
509
675
|
});
|
|
510
676
|
|
|
511
|
-
test("notifyGuardianOfAccessRequest includes requestCode in contextPayload", () => {
|
|
512
|
-
const result = notifyGuardianOfAccessRequest({
|
|
677
|
+
test("notifyGuardianOfAccessRequest includes requestCode in contextPayload", async () => {
|
|
678
|
+
const result = await notifyGuardianOfAccessRequest({
|
|
513
679
|
canonicalAssistantId: "self",
|
|
514
680
|
sourceChannel: "telegram",
|
|
515
681
|
conversationExternalId: "chat-123",
|
|
@@ -529,8 +695,8 @@ describe("access-request-helper unit tests", () => {
|
|
|
529
695
|
expect((payload.requestCode as string).length).toBe(6);
|
|
530
696
|
});
|
|
531
697
|
|
|
532
|
-
test("notifyGuardianOfAccessRequest includes previousMemberStatus in contextPayload", () => {
|
|
533
|
-
const result = notifyGuardianOfAccessRequest({
|
|
698
|
+
test("notifyGuardianOfAccessRequest includes previousMemberStatus in contextPayload", async () => {
|
|
699
|
+
const result = await notifyGuardianOfAccessRequest({
|
|
534
700
|
canonicalAssistantId: "self",
|
|
535
701
|
sourceChannel: "telegram",
|
|
536
702
|
conversationExternalId: "chat-123",
|
|
@@ -570,7 +736,7 @@ describe("access-request-helper unit tests", () => {
|
|
|
570
736
|
],
|
|
571
737
|
};
|
|
572
738
|
|
|
573
|
-
const result = notifyGuardianOfAccessRequest({
|
|
739
|
+
const result = await notifyGuardianOfAccessRequest({
|
|
574
740
|
canonicalAssistantId: "self",
|
|
575
741
|
sourceChannel: "phone",
|
|
576
742
|
conversationExternalId: "+15556667777",
|
|
@@ -603,6 +769,12 @@ describe("access-request-helper unit tests", () => {
|
|
|
603
769
|
// Set up a telegram guardian binding with the anchor principal so
|
|
604
770
|
// guardianResolutionSource resolves to "source-channel-contact" and
|
|
605
771
|
// sameChannelOnly is true.
|
|
772
|
+
seedGatewayGuardian({
|
|
773
|
+
channelType: "telegram",
|
|
774
|
+
address: "guardian-user-456",
|
|
775
|
+
externalChatId: "guardian-chat-456",
|
|
776
|
+
principalId: anchorPrincipalId,
|
|
777
|
+
});
|
|
606
778
|
createGuardianBinding({
|
|
607
779
|
channel: "telegram",
|
|
608
780
|
guardianExternalUserId: "guardian-user-456",
|
|
@@ -625,7 +797,7 @@ describe("access-request-helper unit tests", () => {
|
|
|
625
797
|
],
|
|
626
798
|
};
|
|
627
799
|
|
|
628
|
-
const result = notifyGuardianOfAccessRequest({
|
|
800
|
+
const result = await notifyGuardianOfAccessRequest({
|
|
629
801
|
canonicalAssistantId: "self",
|
|
630
802
|
sourceChannel: "telegram",
|
|
631
803
|
conversationExternalId: "chat-123",
|
|
@@ -24,6 +24,10 @@ mock.module("../util/logger.js", () => ({
|
|
|
24
24
|
}),
|
|
25
25
|
}));
|
|
26
26
|
|
|
27
|
+
mock.module("../contacts/guardian-delivery-reader.js", () => ({
|
|
28
|
+
getGuardianDelivery: async () => null,
|
|
29
|
+
}));
|
|
30
|
+
|
|
27
31
|
// Mock destination-resolver to return a destination for every requested channel.
|
|
28
32
|
// External channels (telegram, slack) include bindingContext.
|
|
29
33
|
mock.module("../notifications/destination-resolver.js", () => ({
|