@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
|
@@ -38,6 +38,7 @@ mock.module("../runtime/assistant-event-hub.js", () => ({
|
|
|
38
38
|
|
|
39
39
|
const {
|
|
40
40
|
DISK_PRESSURE_CLEAR_THRESHOLD_PERCENT,
|
|
41
|
+
DISK_PRESSURE_MIN_FREE_FLOOR_MB,
|
|
41
42
|
DISK_PRESSURE_OVERRIDE_CONFIRMATION,
|
|
42
43
|
DISK_PRESSURE_THRESHOLD_PERCENT,
|
|
43
44
|
DISK_PRESSURE_WARNING_CLEAR_THRESHOLD_PERCENT,
|
|
@@ -342,4 +343,44 @@ describe("disk pressure guard", () => {
|
|
|
342
343
|
setDiskUsage(DISK_PRESSURE_WARNING_CLEAR_THRESHOLD_PERCENT - 1);
|
|
343
344
|
expect(evaluateDiskPressureNow().state).toBe("ok");
|
|
344
345
|
});
|
|
346
|
+
|
|
347
|
+
test("stays ok at a critical usage percentage while ample free space remains", () => {
|
|
348
|
+
// 99% used of a large volume still leaves gigabytes free — above the floor.
|
|
349
|
+
const totalMb = 1_000_000;
|
|
350
|
+
const usedMb = Math.round(totalMb * 0.99); // freeMb ~= 10_000 MiB
|
|
351
|
+
setDiskUsage(usedMb, totalMb);
|
|
352
|
+
expect(diskSample!.freeMb).toBeGreaterThanOrEqual(
|
|
353
|
+
DISK_PRESSURE_MIN_FREE_FLOOR_MB,
|
|
354
|
+
);
|
|
355
|
+
|
|
356
|
+
const status = evaluateDiskPressureNow();
|
|
357
|
+
|
|
358
|
+
expect(status.state).toBe("ok");
|
|
359
|
+
expect(status.locked).toBe(false);
|
|
360
|
+
expect(status.effectivelyLocked).toBe(false);
|
|
361
|
+
});
|
|
362
|
+
|
|
363
|
+
test("stays ok at a warning usage percentage while ample free space remains", () => {
|
|
364
|
+
const totalMb = 1_000_000;
|
|
365
|
+
const usedMb = Math.round(totalMb * 0.85); // 85% used, freeMb ~= 150_000 MiB
|
|
366
|
+
setDiskUsage(usedMb, totalMb);
|
|
367
|
+
|
|
368
|
+
const status = evaluateDiskPressureNow();
|
|
369
|
+
|
|
370
|
+
expect(status.state).toBe("ok");
|
|
371
|
+
});
|
|
372
|
+
|
|
373
|
+
test("locks at a critical usage percentage once free space drops below the floor", () => {
|
|
374
|
+
// High percentage AND little absolute headroom: floor does not apply.
|
|
375
|
+
const totalMb = 100_000;
|
|
376
|
+
const freeMb = DISK_PRESSURE_MIN_FREE_FLOOR_MB - 1;
|
|
377
|
+
setDiskUsage(totalMb - freeMb, totalMb);
|
|
378
|
+
expect(diskSample!.freeMb).toBeLessThan(DISK_PRESSURE_MIN_FREE_FLOOR_MB);
|
|
379
|
+
|
|
380
|
+
const status = evaluateDiskPressureNow();
|
|
381
|
+
|
|
382
|
+
expect(status.state).toBe("critical");
|
|
383
|
+
expect(status.locked).toBe(true);
|
|
384
|
+
expect(status.effectivelyLocked).toBe(true);
|
|
385
|
+
});
|
|
345
386
|
});
|
|
@@ -30,6 +30,8 @@ const addMessageCalls: Array<{
|
|
|
30
30
|
}> = [];
|
|
31
31
|
|
|
32
32
|
mock.module("../memory/conversation-crud.js", () => ({
|
|
33
|
+
setConversationProcessingStartedAt: () => {},
|
|
34
|
+
isConversationProcessing: () => false,
|
|
33
35
|
addMessage: async (
|
|
34
36
|
conversationId: string,
|
|
35
37
|
role: string,
|
|
@@ -19,13 +19,18 @@ mock.module("../channels/config.js", () => ({
|
|
|
19
19
|
getDeliverableChannels: () => ["vellum", "telegram"],
|
|
20
20
|
}));
|
|
21
21
|
|
|
22
|
-
|
|
23
|
-
|
|
22
|
+
// Guardian connectivity is resolved from the gateway pull. No active guardian
|
|
23
|
+
// binding ⇒ binding-based channels (telegram) are not reported connected.
|
|
24
|
+
mock.module("../contacts/guardian-delivery-reader.js", () => ({
|
|
25
|
+
getGuardianDelivery: async () => [],
|
|
26
|
+
guardianForChannel: () => undefined,
|
|
24
27
|
}));
|
|
25
28
|
|
|
26
|
-
//
|
|
27
|
-
//
|
|
28
|
-
|
|
29
|
+
// connectivity falls back to the local contacts read on a per-channel gateway
|
|
30
|
+
// no-match; no local binding ⇒ telegram stays disconnected.
|
|
31
|
+
mock.module("../contacts/contact-store.js", () => ({
|
|
32
|
+
findGuardianForChannel: () => null,
|
|
33
|
+
}));
|
|
29
34
|
|
|
30
35
|
mock.module("../notifications/adapters/macos.js", () => ({
|
|
31
36
|
VellumAdapter: class {
|
|
@@ -29,7 +29,13 @@ mock.module("../config/env.js", () => ({
|
|
|
29
29
|
}));
|
|
30
30
|
|
|
31
31
|
mock.module("../runtime/local-actor-identity.js", () => ({
|
|
32
|
-
|
|
32
|
+
findLocalGuardianPrincipalIdFromStore: () => fakeGuardianPrincipalId,
|
|
33
|
+
resolveActorPrincipalIdForLocalGuardianSync: (
|
|
34
|
+
rawHeader: string | undefined,
|
|
35
|
+
) => {
|
|
36
|
+
if (rawHeader !== "dev-bypass" || !fakeHttpAuthDisabled) return rawHeader;
|
|
37
|
+
return fakeGuardianPrincipalId;
|
|
38
|
+
},
|
|
33
39
|
}));
|
|
34
40
|
|
|
35
41
|
mock.module("../util/logger.js", () => ({
|
|
@@ -51,6 +51,8 @@ const createdConversations: Array<{
|
|
|
51
51
|
let conversationIdCounter = 0;
|
|
52
52
|
|
|
53
53
|
mock.module("../memory/conversation-crud.js", () => ({
|
|
54
|
+
setConversationProcessingStartedAt: () => {},
|
|
55
|
+
isConversationProcessing: () => false,
|
|
54
56
|
setConversationOriginChannelIfUnset: () => {},
|
|
55
57
|
updateConversationContextWindow: () => {},
|
|
56
58
|
deleteMessageById: () => {},
|
|
@@ -7,6 +7,16 @@ mock.module("../util/logger.js", () => ({
|
|
|
7
7
|
}),
|
|
8
8
|
}));
|
|
9
9
|
|
|
10
|
+
import type { GuardianDelivery } from "@vellumai/gateway-client";
|
|
11
|
+
|
|
12
|
+
let mockGuardians: GuardianDelivery[] | null = [];
|
|
13
|
+
|
|
14
|
+
mock.module("../contacts/guardian-delivery-reader.js", () => ({
|
|
15
|
+
getGuardianDelivery: async () => mockGuardians,
|
|
16
|
+
guardianForChannel: (list: GuardianDelivery[], channelType: string) =>
|
|
17
|
+
list.find((g) => g.channelType === channelType && g.status === "active"),
|
|
18
|
+
}));
|
|
19
|
+
|
|
10
20
|
import { findGuardianForChannel } from "../contacts/contact-store.js";
|
|
11
21
|
import { getDb } from "../memory/db-connection.js";
|
|
12
22
|
import { initializeDb } from "../memory/db-init.js";
|
|
@@ -21,12 +31,24 @@ function resetTables(): void {
|
|
|
21
31
|
db.run("DELETE FROM contacts");
|
|
22
32
|
}
|
|
23
33
|
|
|
34
|
+
/** Gateway delivery mirroring the local guardian binding's principal. */
|
|
35
|
+
function gatewayGuardian(principalId: string): GuardianDelivery {
|
|
36
|
+
return {
|
|
37
|
+
channelType: "vellum",
|
|
38
|
+
contactId: "guardian-contact",
|
|
39
|
+
principalId,
|
|
40
|
+
address: principalId,
|
|
41
|
+
status: "active",
|
|
42
|
+
};
|
|
43
|
+
}
|
|
44
|
+
|
|
24
45
|
describe("healGuardianBindingDrift", () => {
|
|
25
46
|
beforeEach(() => {
|
|
26
47
|
resetTables();
|
|
48
|
+
mockGuardians = [];
|
|
27
49
|
});
|
|
28
50
|
|
|
29
|
-
test("heals drift when both principals have vellum-principal- prefix", () => {
|
|
51
|
+
test("heals drift when both principals have vellum-principal- prefix", async () => {
|
|
30
52
|
// Simulate DB reset: new guardian binding with a different UUID
|
|
31
53
|
createGuardianBinding({
|
|
32
54
|
channel: "vellum",
|
|
@@ -35,9 +57,10 @@ describe("healGuardianBindingDrift", () => {
|
|
|
35
57
|
guardianPrincipalId: "vellum-principal-new-uuid",
|
|
36
58
|
verifiedVia: "startup-migration",
|
|
37
59
|
});
|
|
60
|
+
mockGuardians = [gatewayGuardian("vellum-principal-new-uuid")];
|
|
38
61
|
|
|
39
62
|
// Client arrives with the old JWT principal
|
|
40
|
-
const healed = healGuardianBindingDrift("vellum-principal-old-uuid");
|
|
63
|
+
const healed = await healGuardianBindingDrift("vellum-principal-old-uuid");
|
|
41
64
|
expect(healed).toBe(true);
|
|
42
65
|
|
|
43
66
|
// Guardian binding now matches the old JWT
|
|
@@ -47,7 +70,31 @@ describe("healGuardianBindingDrift", () => {
|
|
|
47
70
|
expect(guardian!.channel.address).toBe("vellum-principal-old-uuid");
|
|
48
71
|
});
|
|
49
72
|
|
|
50
|
-
test("
|
|
73
|
+
test("repairs the stale local mirror even when the gateway already matches the JWT", async () => {
|
|
74
|
+
// Gateway binding already matches the incoming JWT principal, but the
|
|
75
|
+
// local mirror is stale — the gateway-source-of-truth drift mode. The
|
|
76
|
+
// /v1/messages trust path still reads the local mirror in this plan, so
|
|
77
|
+
// a stale row must be repaired or the actor stays classified `unknown`.
|
|
78
|
+
createGuardianBinding({
|
|
79
|
+
channel: "vellum",
|
|
80
|
+
guardianExternalUserId: "vellum-principal-stale-local",
|
|
81
|
+
guardianDeliveryChatId: "local",
|
|
82
|
+
guardianPrincipalId: "vellum-principal-stale-local",
|
|
83
|
+
verifiedVia: "startup-migration",
|
|
84
|
+
});
|
|
85
|
+
mockGuardians = [gatewayGuardian("vellum-principal-jwt")];
|
|
86
|
+
|
|
87
|
+
const healed = await healGuardianBindingDrift("vellum-principal-jwt");
|
|
88
|
+
expect(healed).toBe(true);
|
|
89
|
+
|
|
90
|
+
// Local mirror now matches the JWT, so a subsequent local trust
|
|
91
|
+
// resolution classifies the actor as guardian rather than unknown.
|
|
92
|
+
const guardian = findGuardianForChannel("vellum");
|
|
93
|
+
expect(guardian!.contact.principalId).toBe("vellum-principal-jwt");
|
|
94
|
+
expect(guardian!.channel.address).toBe("vellum-principal-jwt");
|
|
95
|
+
});
|
|
96
|
+
|
|
97
|
+
test("no-op when principals already match", async () => {
|
|
51
98
|
createGuardianBinding({
|
|
52
99
|
channel: "vellum",
|
|
53
100
|
guardianExternalUserId: "vellum-principal-same",
|
|
@@ -55,12 +102,13 @@ describe("healGuardianBindingDrift", () => {
|
|
|
55
102
|
guardianPrincipalId: "vellum-principal-same",
|
|
56
103
|
verifiedVia: "startup-migration",
|
|
57
104
|
});
|
|
105
|
+
mockGuardians = [gatewayGuardian("vellum-principal-same")];
|
|
58
106
|
|
|
59
|
-
const healed = healGuardianBindingDrift("vellum-principal-same");
|
|
107
|
+
const healed = await healGuardianBindingDrift("vellum-principal-same");
|
|
60
108
|
expect(healed).toBe(false);
|
|
61
109
|
});
|
|
62
110
|
|
|
63
|
-
test("refuses to heal when incoming principal lacks vellum-principal- prefix", () => {
|
|
111
|
+
test("refuses to heal when incoming principal lacks vellum-principal- prefix", async () => {
|
|
64
112
|
createGuardianBinding({
|
|
65
113
|
channel: "vellum",
|
|
66
114
|
guardianExternalUserId: "vellum-principal-aaa",
|
|
@@ -68,9 +116,10 @@ describe("healGuardianBindingDrift", () => {
|
|
|
68
116
|
guardianPrincipalId: "vellum-principal-aaa",
|
|
69
117
|
verifiedVia: "startup-migration",
|
|
70
118
|
});
|
|
119
|
+
mockGuardians = [gatewayGuardian("vellum-principal-aaa")];
|
|
71
120
|
|
|
72
121
|
// External/platform principal — should NOT be adopted
|
|
73
|
-
const healed = healGuardianBindingDrift("platform-user-12345");
|
|
122
|
+
const healed = await healGuardianBindingDrift("platform-user-12345");
|
|
74
123
|
expect(healed).toBe(false);
|
|
75
124
|
|
|
76
125
|
// Guardian unchanged
|
|
@@ -78,7 +127,7 @@ describe("healGuardianBindingDrift", () => {
|
|
|
78
127
|
expect(guardian!.contact.principalId).toBe("vellum-principal-aaa");
|
|
79
128
|
});
|
|
80
129
|
|
|
81
|
-
test("refuses to heal when stored principal lacks vellum-principal- prefix", () => {
|
|
130
|
+
test("refuses to heal when stored principal lacks vellum-principal- prefix", async () => {
|
|
82
131
|
createGuardianBinding({
|
|
83
132
|
channel: "vellum",
|
|
84
133
|
guardianExternalUserId: "verified-phone-guardian",
|
|
@@ -86,17 +135,33 @@ describe("healGuardianBindingDrift", () => {
|
|
|
86
135
|
guardianPrincipalId: "verified-phone-guardian",
|
|
87
136
|
verifiedVia: "challenge",
|
|
88
137
|
});
|
|
138
|
+
mockGuardians = [gatewayGuardian("verified-phone-guardian")];
|
|
89
139
|
|
|
90
140
|
// Even with a vellum-principal- incoming, don't overwrite a real binding
|
|
91
|
-
const healed = healGuardianBindingDrift("vellum-principal-attacker");
|
|
141
|
+
const healed = await healGuardianBindingDrift("vellum-principal-attacker");
|
|
92
142
|
expect(healed).toBe(false);
|
|
93
143
|
|
|
94
144
|
const guardian = findGuardianForChannel("vellum");
|
|
95
145
|
expect(guardian!.contact.principalId).toBe("verified-phone-guardian");
|
|
96
146
|
});
|
|
97
147
|
|
|
98
|
-
test("returns false when no guardian binding
|
|
99
|
-
|
|
148
|
+
test("returns false when gateway reports no guardian binding", async () => {
|
|
149
|
+
mockGuardians = [];
|
|
150
|
+
const healed = await healGuardianBindingDrift("vellum-principal-orphan");
|
|
151
|
+
expect(healed).toBe(false);
|
|
152
|
+
});
|
|
153
|
+
|
|
154
|
+
test("returns false when the gateway is unreachable (null list)", async () => {
|
|
155
|
+
createGuardianBinding({
|
|
156
|
+
channel: "vellum",
|
|
157
|
+
guardianExternalUserId: "vellum-principal-aaa",
|
|
158
|
+
guardianDeliveryChatId: "local",
|
|
159
|
+
guardianPrincipalId: "vellum-principal-aaa",
|
|
160
|
+
verifiedVia: "startup-migration",
|
|
161
|
+
});
|
|
162
|
+
mockGuardians = null;
|
|
163
|
+
|
|
164
|
+
const healed = await healGuardianBindingDrift("vellum-principal-old-uuid");
|
|
100
165
|
expect(healed).toBe(false);
|
|
101
166
|
});
|
|
102
167
|
});
|
|
@@ -24,6 +24,11 @@ mock.module("../config/loader.js", () => ({
|
|
|
24
24
|
}),
|
|
25
25
|
}));
|
|
26
26
|
|
|
27
|
+
// The pending_question request principal is resolved via the SAME local source
|
|
28
|
+
// the Vellum actor uses — findGuardianForChannel("vellum")?.contact.principalId
|
|
29
|
+
// — so the stamped principal always equals the submitting actor principal. The
|
|
30
|
+
// real contacts DB is seeded in resetTables(); tests model drift / missing
|
|
31
|
+
// guardian by reseeding or clearing that local binding directly.
|
|
27
32
|
const emitCalls: unknown[] = [];
|
|
28
33
|
let conversationCreatedFromMock: ConversationCreatedInfo | null = null;
|
|
29
34
|
let mockEmitResult: {
|
|
@@ -154,11 +159,18 @@ describe("guardian-dispatch", () => {
|
|
|
154
159
|
"SELECT * FROM canonical_guardian_requests WHERE call_session_id = ?",
|
|
155
160
|
)
|
|
156
161
|
.get(session.id) as
|
|
157
|
-
| {
|
|
162
|
+
| {
|
|
163
|
+
id: string;
|
|
164
|
+
status: string;
|
|
165
|
+
question_text: string;
|
|
166
|
+
guardian_principal_id: string | null;
|
|
167
|
+
}
|
|
158
168
|
| undefined;
|
|
159
169
|
expect(request).toBeDefined();
|
|
160
170
|
expect(request!.status).toBe("pending");
|
|
161
171
|
expect(request!.question_text).toBe("What is the gate code?");
|
|
172
|
+
// principalId comes from the local guardian binding (same source the actor submits)
|
|
173
|
+
expect(request!.guardian_principal_id).toBe("test-principal-id");
|
|
162
174
|
|
|
163
175
|
const vellumDelivery = raw
|
|
164
176
|
.query(
|
|
@@ -175,6 +187,88 @@ describe("guardian-dispatch", () => {
|
|
|
175
187
|
expect(typeof signalParams.onConversationCreated).toBe("function");
|
|
176
188
|
});
|
|
177
189
|
|
|
190
|
+
test("stamps the request principal from the local source the actor submits, not the (possibly drifted) gateway binding", async () => {
|
|
191
|
+
// Simulate gateway/local binding drift: the local guardian binding (the
|
|
192
|
+
// source the actor submit path reads) carries a different principal than
|
|
193
|
+
// the gateway would. The request must be stamped with that local value so
|
|
194
|
+
// a later actor submission matches (no identity_mismatch under drift).
|
|
195
|
+
const db = getDb();
|
|
196
|
+
db.run("DELETE FROM contact_channels");
|
|
197
|
+
db.run("DELETE FROM contacts");
|
|
198
|
+
createGuardianBinding({
|
|
199
|
+
channel: "vellum",
|
|
200
|
+
guardianExternalUserId: "local-actor-principal",
|
|
201
|
+
guardianDeliveryChatId: "local",
|
|
202
|
+
guardianPrincipalId: "local-actor-principal",
|
|
203
|
+
verifiedVia: "bootstrap",
|
|
204
|
+
});
|
|
205
|
+
|
|
206
|
+
const convId = "conv-dispatch-drift";
|
|
207
|
+
ensureConversation(convId);
|
|
208
|
+
|
|
209
|
+
const session = createCallSession({
|
|
210
|
+
conversationId: convId,
|
|
211
|
+
provider: "twilio",
|
|
212
|
+
fromNumber: "+15550001111",
|
|
213
|
+
toNumber: "+15550002222",
|
|
214
|
+
});
|
|
215
|
+
const pq = createPendingQuestion(session.id, "Drifted bindings?");
|
|
216
|
+
|
|
217
|
+
await dispatchGuardianQuestion({
|
|
218
|
+
callSessionId: session.id,
|
|
219
|
+
conversationId: convId,
|
|
220
|
+
assistantId: "self",
|
|
221
|
+
pendingQuestion: pq,
|
|
222
|
+
});
|
|
223
|
+
|
|
224
|
+
const raw = (db as unknown as { $client: import("bun:sqlite").Database })
|
|
225
|
+
.$client;
|
|
226
|
+
const request = raw
|
|
227
|
+
.query(
|
|
228
|
+
"SELECT * FROM canonical_guardian_requests WHERE call_session_id = ?",
|
|
229
|
+
)
|
|
230
|
+
.get(session.id) as
|
|
231
|
+
| { guardian_principal_id: string | null }
|
|
232
|
+
| undefined;
|
|
233
|
+
expect(request).toBeDefined();
|
|
234
|
+
expect(request!.guardian_principal_id).toBe("local-actor-principal");
|
|
235
|
+
});
|
|
236
|
+
|
|
237
|
+
test("skips dispatch when no local guardian binding exists (no principal to stamp)", async () => {
|
|
238
|
+
const db = getDb();
|
|
239
|
+
db.run("DELETE FROM contact_channels");
|
|
240
|
+
db.run("DELETE FROM contacts");
|
|
241
|
+
|
|
242
|
+
const convId = "conv-dispatch-no-principal";
|
|
243
|
+
ensureConversation(convId);
|
|
244
|
+
|
|
245
|
+
const session = createCallSession({
|
|
246
|
+
conversationId: convId,
|
|
247
|
+
provider: "twilio",
|
|
248
|
+
fromNumber: "+15550001111",
|
|
249
|
+
toNumber: "+15550002222",
|
|
250
|
+
});
|
|
251
|
+
const pq = createPendingQuestion(session.id, "No principal available");
|
|
252
|
+
|
|
253
|
+
await dispatchGuardianQuestion({
|
|
254
|
+
callSessionId: session.id,
|
|
255
|
+
conversationId: convId,
|
|
256
|
+
assistantId: "self",
|
|
257
|
+
pendingQuestion: pq,
|
|
258
|
+
});
|
|
259
|
+
|
|
260
|
+
// No request is created and the pipeline is never invoked.
|
|
261
|
+
const raw = (db as unknown as { $client: import("bun:sqlite").Database })
|
|
262
|
+
.$client;
|
|
263
|
+
const request = raw
|
|
264
|
+
.query(
|
|
265
|
+
"SELECT * FROM canonical_guardian_requests WHERE call_session_id = ?",
|
|
266
|
+
)
|
|
267
|
+
.get(session.id) as { id: string } | null;
|
|
268
|
+
expect(request).toBeNull();
|
|
269
|
+
expect(emitCalls).toHaveLength(0);
|
|
270
|
+
});
|
|
271
|
+
|
|
178
272
|
test("creates a telegram guardian delivery with binding metadata when pipeline sends telegram", async () => {
|
|
179
273
|
const convId = "conv-dispatch-2";
|
|
180
274
|
ensureConversation(convId);
|
|
@@ -84,6 +84,19 @@ globalThis.fetch = (async (
|
|
|
84
84
|
return originalFetch(input, init as never);
|
|
85
85
|
}) as unknown as typeof fetch;
|
|
86
86
|
|
|
87
|
+
// Guardian-delivery reader mock — the inbound challenge guard reads guardian
|
|
88
|
+
// existence from the gateway. These tests seed no binding, so report an empty
|
|
89
|
+
// list (not bound) rather than a null that would fail closed as already-bound.
|
|
90
|
+
mock.module("../contacts/guardian-delivery-reader.js", () => ({
|
|
91
|
+
getGuardianDelivery: async () => [],
|
|
92
|
+
getGuardianDeliveryFresh: async () => [],
|
|
93
|
+
guardianForChannel: (
|
|
94
|
+
list: Array<{ channelType: string; status: string }>,
|
|
95
|
+
channelType: string,
|
|
96
|
+
) =>
|
|
97
|
+
list.find((g) => g.channelType === channelType && g.status === "active"),
|
|
98
|
+
}));
|
|
99
|
+
|
|
87
100
|
// ---------------------------------------------------------------------------
|
|
88
101
|
// Now import modules under test (after mocks are in place)
|
|
89
102
|
// ---------------------------------------------------------------------------
|
|
@@ -70,6 +70,8 @@ mock.module("../schedule/recurrence-engine.js", () => ({
|
|
|
70
70
|
|
|
71
71
|
const createdConversations: Array<{ conversationType: string }> = [];
|
|
72
72
|
mock.module("../memory/conversation-crud.js", () => ({
|
|
73
|
+
setConversationProcessingStartedAt: () => {},
|
|
74
|
+
isConversationProcessing: () => false,
|
|
73
75
|
getConversation: () => null,
|
|
74
76
|
getMessages: () => [],
|
|
75
77
|
createConversation: (opts: { conversationType: string }) => {
|
|
@@ -137,6 +137,8 @@ const mockStoredMessages: Array<{
|
|
|
137
137
|
}> = [];
|
|
138
138
|
|
|
139
139
|
mock.module("../memory/conversation-crud.js", () => ({
|
|
140
|
+
setConversationProcessingStartedAt: () => {},
|
|
141
|
+
isConversationProcessing: () => false,
|
|
140
142
|
setConversationOriginChannelIfUnset: () => {},
|
|
141
143
|
updateConversationContextWindow: () => {},
|
|
142
144
|
deleteMessageById: () => {},
|
|
@@ -138,16 +138,11 @@ function stampTrustVerdict(body: Record<string, unknown>): void {
|
|
|
138
138
|
const channelType = String(body.sourceChannel ?? "");
|
|
139
139
|
const actorExternalId =
|
|
140
140
|
typeof body.actorExternalId === "string" ? body.actorExternalId : undefined;
|
|
141
|
-
const externalChatId =
|
|
142
|
-
typeof body.conversationExternalId === "string"
|
|
143
|
-
? body.conversationExternalId
|
|
144
|
-
: undefined;
|
|
145
141
|
if (!channelType) return;
|
|
146
142
|
|
|
147
143
|
const verdict = resolveLocalTrustVerdict({
|
|
148
144
|
channelType,
|
|
149
145
|
actorExternalId,
|
|
150
|
-
externalChatId,
|
|
151
146
|
});
|
|
152
147
|
body.sourceMetadata = { ...(meta ?? {}), trustVerdict: verdict };
|
|
153
148
|
}
|
|
@@ -156,15 +151,14 @@ function stampTrustVerdict(body: Record<string, unknown>): void {
|
|
|
156
151
|
export function resolveLocalTrustVerdict(input: {
|
|
157
152
|
channelType: string;
|
|
158
153
|
actorExternalId?: string;
|
|
159
|
-
externalChatId?: string;
|
|
160
154
|
}): TrustVerdict {
|
|
161
155
|
const canonicalSenderId = input.actorExternalId ?? null;
|
|
162
156
|
|
|
157
|
+
// Match the gateway's address-only member resolution (no externalChatId).
|
|
163
158
|
const member = input.actorExternalId
|
|
164
159
|
? findContactChannel({
|
|
165
160
|
channelType: input.channelType,
|
|
166
161
|
address: input.actorExternalId,
|
|
167
|
-
externalChatId: input.externalChatId,
|
|
168
162
|
})
|
|
169
163
|
: null;
|
|
170
164
|
const guardian = findGuardianForChannel(input.channelType);
|
|
@@ -62,7 +62,7 @@ afterAll(() => {
|
|
|
62
62
|
|
|
63
63
|
const handleHostAppControlResult = ROUTES.find(
|
|
64
64
|
(r) => r.endpoint === "host-app-control-result",
|
|
65
|
-
)!.handler
|
|
65
|
+
)!.handler as (args: Record<string, unknown>) => Promise<unknown>;
|
|
66
66
|
|
|
67
67
|
// ── Tests ────────────────────────────────────────────────────────────
|
|
68
68
|
|
|
@@ -395,7 +395,7 @@ describe("handleHostAppControlResult — same-actor guard", () => {
|
|
|
395
395
|
).toThrow(BadRequestError);
|
|
396
396
|
});
|
|
397
397
|
|
|
398
|
-
test("targeted + missing header: interaction is NOT consumed (still pending)", () => {
|
|
398
|
+
test("targeted + missing header: interaction is NOT consumed (still pending)", async () => {
|
|
399
399
|
const requestId = "ac-req-targeted-no-header-stays";
|
|
400
400
|
pending.set(requestId, {
|
|
401
401
|
conversationId: "conv-1",
|
|
@@ -404,13 +404,11 @@ describe("handleHostAppControlResult — same-actor guard", () => {
|
|
|
404
404
|
targetActorPrincipalId: "user-1",
|
|
405
405
|
});
|
|
406
406
|
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
});
|
|
411
|
-
} catch {
|
|
407
|
+
await handleHostAppControlResult({
|
|
408
|
+
body: { requestId, state: "running" },
|
|
409
|
+
}).catch(() => {
|
|
412
410
|
// expected
|
|
413
|
-
}
|
|
411
|
+
});
|
|
414
412
|
|
|
415
413
|
expect(pending.has(requestId)).toBe(true);
|
|
416
414
|
});
|
|
@@ -437,7 +435,7 @@ describe("handleHostAppControlResult — same-actor guard", () => {
|
|
|
437
435
|
).toThrow(ForbiddenError);
|
|
438
436
|
});
|
|
439
437
|
|
|
440
|
-
test("targeted + wrong client id: interaction is NOT consumed", () => {
|
|
438
|
+
test("targeted + wrong client id: interaction is NOT consumed", async () => {
|
|
441
439
|
const requestId = "ac-req-targeted-wrong-client-stays";
|
|
442
440
|
pending.set(requestId, {
|
|
443
441
|
conversationId: "conv-1",
|
|
@@ -446,17 +444,15 @@ describe("handleHostAppControlResult — same-actor guard", () => {
|
|
|
446
444
|
targetActorPrincipalId: "user-1",
|
|
447
445
|
});
|
|
448
446
|
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
});
|
|
457
|
-
} catch {
|
|
447
|
+
await handleHostAppControlResult({
|
|
448
|
+
body: { requestId, state: "running" },
|
|
449
|
+
headers: {
|
|
450
|
+
"x-vellum-client-id": "client-B",
|
|
451
|
+
"x-vellum-actor-principal-id": "user-1",
|
|
452
|
+
},
|
|
453
|
+
}).catch(() => {
|
|
458
454
|
// expected
|
|
459
|
-
}
|
|
455
|
+
});
|
|
460
456
|
|
|
461
457
|
expect(pending.has(requestId)).toBe(true);
|
|
462
458
|
});
|
|
@@ -483,7 +479,7 @@ describe("handleHostAppControlResult — same-actor guard", () => {
|
|
|
483
479
|
).toThrow(ForbiddenError);
|
|
484
480
|
});
|
|
485
481
|
|
|
486
|
-
test("targeted + wrong actor principal: interaction is NOT consumed", () => {
|
|
482
|
+
test("targeted + wrong actor principal: interaction is NOT consumed", async () => {
|
|
487
483
|
const requestId = "ac-req-targeted-wrong-actor-stays";
|
|
488
484
|
pending.set(requestId, {
|
|
489
485
|
conversationId: "conv-1",
|
|
@@ -492,17 +488,15 @@ describe("handleHostAppControlResult — same-actor guard", () => {
|
|
|
492
488
|
targetActorPrincipalId: "user-1",
|
|
493
489
|
});
|
|
494
490
|
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
});
|
|
503
|
-
} catch {
|
|
491
|
+
await handleHostAppControlResult({
|
|
492
|
+
body: { requestId, state: "running" },
|
|
493
|
+
headers: {
|
|
494
|
+
"x-vellum-client-id": "client-A",
|
|
495
|
+
"x-vellum-actor-principal-id": "user-2",
|
|
496
|
+
},
|
|
497
|
+
}).catch(() => {
|
|
504
498
|
// expected
|
|
505
|
-
}
|
|
499
|
+
});
|
|
506
500
|
|
|
507
501
|
expect(pending.has(requestId)).toBe(true);
|
|
508
502
|
});
|