@vellumai/assistant 0.10.2 → 0.10.3-dev.202606252046.9075fd5
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/bun.lock +20 -0
- 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/gateway-ipc-contracts.ts +44 -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/node_modules/@vellumai/service-contracts/package.json +1 -0
- package/node_modules/@vellumai/service-contracts/src/__tests__/channels.test.ts +28 -0
- package/node_modules/@vellumai/service-contracts/src/channels.ts +41 -0
- package/node_modules/@vellumai/service-contracts/src/index.ts +1 -0
- package/openapi.yaml +196 -56
- package/package.json +4 -1
- package/scripts/test.sh +36 -15
- package/src/__tests__/actor-token-service.test.ts +36 -14
- package/src/__tests__/actor-trust-resolver-address-fallback.test.ts +83 -31
- 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__/assistant-attachments.test.ts +42 -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-approval-routes.test.ts +21 -26
- package/src/__tests__/channel-delivery-store.test.ts +28 -0
- package/src/__tests__/channel-guardian.test.ts +164 -78
- package/src/__tests__/channel-inbound-disk-pressure.test.ts +11 -19
- package/src/__tests__/channel-reply-delivery.test.ts +2 -0
- package/src/__tests__/code-search-tool.test.ts +585 -0
- package/src/__tests__/compaction-events.test.ts +2 -0
- package/src/__tests__/compaction-ledger-store.test.ts +128 -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__/config-loader-backfill.test.ts +123 -10
- package/src/__tests__/config-schema.test.ts +1 -0
- package/src/__tests__/confirmation-request-guardian-bridge.test.ts +31 -29
- package/src/__tests__/consult-deadline.test.ts +60 -0
- package/src/__tests__/contact-store-interaction-info.test.ts +156 -0
- package/src/__tests__/contact-store-user-file.test.ts +7 -10
- package/src/__tests__/contacts-relay-reads.test.ts +19 -24
- package/src/__tests__/contacts-write.test.ts +0 -2
- 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 +134 -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-attention-telegram.test.ts +9 -11
- package/src/__tests__/conversation-confirmation-signals.test.ts +2 -0
- package/src/__tests__/conversation-fork-crud.test.ts +354 -24
- 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-task-progress.test.ts +29 -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 +1 -1
- package/src/__tests__/db-compaction-events-migration.test.ts +129 -0
- package/src/__tests__/db-migration-rollback.test.ts +205 -171
- package/src/__tests__/db-test-helpers.ts +5 -4
- package/src/__tests__/delete-propagation.test.ts +5 -3
- 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-backfill.test.ts +6 -4
- package/src/__tests__/dm-persistence.test.ts +2 -0
- package/src/__tests__/emit-signal-routing-intent.test.ts +7 -6
- package/src/__tests__/events-dev-bypass-actor.test.ts +7 -1
- package/src/__tests__/exploration-drift-hook.test.ts +3 -2
- package/src/__tests__/filing-service.test.ts +2 -0
- package/src/__tests__/guardian-binding-drift-heal.test.ts +104 -19
- package/src/__tests__/guardian-dispatch.test.ts +140 -1
- package/src/__tests__/guardian-expiry-notifier.test.ts +282 -0
- package/src/__tests__/guardian-outbound-http.test.ts +13 -0
- package/src/__tests__/guardian-routing-state.test.ts +6 -10
- 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 +46 -19
- package/src/__tests__/helpers/create-guardian-binding.ts +15 -23
- package/src/__tests__/helpers/mock-logger.ts +1 -0
- package/src/__tests__/helpers/seed-contact-channel.ts +96 -0
- 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-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 +290 -8
- package/src/__tests__/inbound-invite-redemption.test.ts +115 -10
- package/src/__tests__/inbound-slack-persistence.test.ts +2 -0
- package/src/__tests__/invite-redemption-service.test.ts +471 -53
- package/src/__tests__/invite-routes-http.test.ts +34 -0
- package/src/__tests__/invite-service-ipc.test.ts +65 -2
- package/src/__tests__/llm-context-normalization.test.ts +105 -0
- package/src/__tests__/llm-request-log-error-payload.test.ts +71 -9
- package/src/__tests__/llm-usage-store.test.ts +25 -0
- package/src/__tests__/mcp-config-secret-boundary.test.ts +392 -0
- package/src/__tests__/mcp-health-check.test.ts +2 -1
- 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__/mtime-cache.test.ts +275 -5
- package/src/__tests__/native-web-search.test.ts +2 -0
- package/src/__tests__/non-member-access-request.test.ts +191 -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__/onboarding-persona-write.test.ts +52 -22
- package/src/__tests__/openai-provider.test.ts +22 -12
- package/src/__tests__/openai-responses-provider.test.ts +12 -2
- package/src/__tests__/outbound-slack-persistence.test.ts +2 -0
- package/src/__tests__/pending-interactions-resolved-event.test.ts +7 -4
- package/src/__tests__/persist-onboarding-artifacts.test.ts +1 -0
- package/src/__tests__/persistence-secret-redaction.test.ts +2 -0
- package/src/__tests__/persona-resolver.test.ts +75 -45
- package/src/__tests__/plugin-bootstrap.test.ts +21 -83
- package/src/__tests__/plugin-disabled-state.test.ts +190 -0
- package/src/__tests__/plugin-pipeline.test.ts +96 -0
- package/src/__tests__/plugin-route-contribution.test.ts +6 -19
- package/src/__tests__/plugin-tool-contribution.test.ts +5 -20
- package/src/__tests__/plugin-types.test.ts +2 -4
- 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-error-scenarios.test.ts +5 -4
- package/src/__tests__/provider-usage-tracking.test.ts +39 -0
- package/src/__tests__/reaction-intercept-cold-cache-warm.test.ts +135 -0
- package/src/__tests__/reaction-intercept-member-verdict-warm.test.ts +158 -0
- package/src/__tests__/reaction-persistence.test.ts +51 -4
- package/src/__tests__/regenerate-fire-and-forget-trace.test.ts +2 -0
- package/src/__tests__/registry.test.ts +4 -1
- package/src/__tests__/relay-server.test.ts +758 -32
- package/src/__tests__/runtime-attachment-metadata.test.ts +9 -12
- package/src/__tests__/secret-ingress-http.test.ts +14 -0
- package/src/__tests__/send-endpoint-busy.test.ts +30 -8
- package/src/__tests__/settings-routes.test.ts +32 -0
- 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 +79 -0
- package/src/__tests__/steer-on-enqueue-question.test.ts +181 -0
- package/src/__tests__/stt-hints.test.ts +49 -15
- package/src/__tests__/subagent-detail.test.ts +27 -0
- package/src/__tests__/subagent-disposal.test.ts +65 -0
- package/src/__tests__/subagent-fork-prompt-role.test.ts +195 -0
- package/src/__tests__/subagent-fork-spawn.test.ts +6 -7
- package/src/__tests__/subagent-notify-parent.test.ts +2 -0
- package/src/__tests__/subagent-role-registry.test.ts +24 -6
- package/src/__tests__/subagent-spawn-and-await.test.ts +546 -0
- package/src/__tests__/subagent-spawn-tool-fork.test.ts +2 -0
- package/src/__tests__/subagent-tools.test.ts +398 -1
- package/src/__tests__/suggestion-routes.test.ts +2 -0
- package/src/__tests__/thread-backfill.test.ts +3 -3
- 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-approval-notifier.test.ts +37 -51
- package/src/__tests__/trusted-contact-inline-approval-integration.test.ts +12 -12
- package/src/__tests__/trusted-contact-lifecycle-notifications.test.ts +9 -7
- package/src/__tests__/trusted-contact-multichannel.test.ts +16 -7
- package/src/__tests__/trusted-contact-verification.test.ts +79 -54
- package/src/__tests__/twilio-routes.test.ts +96 -0
- package/src/__tests__/ui-file-upload-surface.test.ts +86 -0
- package/src/__tests__/verification-control-plane-policy.test.ts +2 -0
- package/src/__tests__/voice-guardian-cold-cache-warm.test.ts +137 -0
- package/src/__tests__/voice-invite-redemption.test.ts +216 -20
- package/src/__tests__/web-search-backend-failure.test.ts +2 -0
- package/src/__tests__/workspace-migration-102-preserve-heartbeat-enabled-for-existing-workspaces.test.ts +3 -3
- package/src/__tests__/workspace-migration-111-prune-seeded-callsite-defaults.test.ts +208 -0
- package/src/__tests__/workspace-migration-112-remove-advisor-callsite-override.test.ts +170 -0
- package/src/__tests__/workspace-migration-drop-user-md.test.ts +196 -238
- package/src/__tests__/workspace-migration-remove-hooks.test.ts +14 -35
- package/src/__tests__/workspace-tool-loader.test.ts +195 -2
- package/src/a2a/__tests__/e2e-a2a-channel.test.ts +35 -47
- package/src/agent/loop-exclusive-tool.test.ts +154 -0
- package/src/agent/loop-native-web-search.test.ts +200 -0
- package/src/agent/loop.ts +164 -1
- package/src/api/constants/sse-replay.ts +41 -0
- package/src/api/events/conversation-notice.ts +26 -0
- package/src/api/index.ts +19 -1
- package/src/api/responses/llm-request-log-entry.ts +29 -0
- package/src/api/responses/subagent-detail.ts +17 -0
- package/src/api/surfaces.ts +39 -3
- package/src/approvals/guardian-channel-delivery.ts +30 -0
- package/src/approvals/guardian-expiry-notifier.ts +148 -0
- package/src/approvals/guardian-request-resolvers.ts +17 -15
- package/src/calls/__tests__/relay-setup-router.test.ts +267 -17
- package/src/calls/call-domain.ts +3 -3
- package/src/calls/guardian-dispatch.ts +14 -9
- package/src/calls/inbound-trust-reader.ts +24 -2
- package/src/calls/media-stream-server.ts +21 -0
- package/src/calls/relay-access-wait.ts +6 -6
- package/src/calls/relay-server.ts +188 -51
- package/src/calls/relay-setup-router.ts +47 -17
- 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/channels/types.ts +10 -20
- package/src/cli/commands/__tests__/cache.test.ts +8 -1
- package/src/cli/commands/__tests__/conversations-slack.test.ts +1 -0
- package/src/cli/commands/cache.ts +194 -181
- package/src/cli/commands/contacts.ts +10 -7
- 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 +432 -0
- package/src/cli/commands/memory/index.ts +2 -0
- package/src/cli/commands/memory/worker.ts +242 -0
- package/src/cli/commands/plugins.ts +125 -13
- 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/__tests__/publish-plugin.test.ts +404 -0
- package/src/cli/lib/list-installed-plugins.ts +179 -1
- package/src/cli/lib/publish-plugin.ts +633 -0
- package/src/config/__tests__/loader-callsite-strip-fallback.test.ts +143 -0
- package/src/config/__tests__/sync-gated-profiles.test.ts +16 -10
- 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/bundled-skills/subagent/SKILL.md +17 -2
- package/src/config/bundled-skills/subagent/TOOLS.json +5 -4
- package/src/config/call-site-defaults.ts +0 -6
- package/src/config/feature-flag-registry.json +5 -13
- package/src/config/llm-resolver.ts +0 -3
- package/src/config/loader.ts +38 -5
- package/src/config/prune-seeded-callsite-defaults.ts +110 -0
- package/src/config/schemas/__tests__/memory-v3.test.ts +1 -0
- package/src/config/schemas/call-site-catalog.ts +0 -7
- package/src/config/schemas/heartbeat.ts +2 -5
- package/src/config/schemas/llm.ts +3 -12
- 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 +30 -34
- package/src/config/skills.ts +27 -5
- package/src/config/sync-gated-profiles.ts +12 -16
- package/src/contacts/__tests__/contacts-write-revoke-relay.test.ts +128 -0
- package/src/contacts/__tests__/guardian-delivery-reader.test.ts +312 -0
- package/src/contacts/__tests__/member-write-relay.test.ts +226 -0
- package/src/contacts/contact-store.ts +27 -237
- package/src/contacts/contacts-write.ts +18 -55
- package/src/contacts/gateway-channel-read.ts +51 -0
- package/src/contacts/guardian-delivery-reader.ts +223 -0
- package/src/contacts/member-write-relay.ts +183 -0
- package/src/contacts/types.ts +3 -15
- package/src/daemon/__tests__/conversation-tool-setup-exclude.test.ts +35 -0
- package/src/daemon/__tests__/conversation-tool-setup.test.ts +0 -44
- package/src/daemon/assistant-attachments.ts +27 -4
- package/src/daemon/conversation-agent-loop.ts +28 -0
- package/src/daemon/conversation-notices.ts +60 -0
- package/src/daemon/conversation-process.ts +35 -16
- package/src/daemon/conversation-surfaces.ts +111 -38
- package/src/daemon/conversation-tool-setup.ts +71 -30
- package/src/daemon/conversation.ts +23 -1
- 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 +17 -41
- package/src/daemon/handlers/__tests__/config-a2a-accept.test.ts +0 -1
- package/src/daemon/handlers/__tests__/config-a2a-complete.test.ts +0 -2
- package/src/daemon/handlers/__tests__/config-a2a-redeem.test.ts +25 -2
- package/src/daemon/handlers/__tests__/config-channels.test.ts +220 -0
- package/src/daemon/handlers/config-a2a.ts +6 -14
- package/src/daemon/handlers/config-channels.ts +67 -26
- package/src/daemon/handlers/conversations.ts +77 -0
- package/src/daemon/lifecycle.ts +16 -0
- package/src/daemon/mcp-reload-service.ts +10 -0
- package/src/daemon/memory-v2-startup.test.ts +72 -0
- package/src/daemon/memory-v2-startup.ts +87 -19
- package/src/daemon/message-types/conversations.ts +2 -0
- package/src/daemon/message-types/surfaces.ts +12 -12
- 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/heartbeat/heartbeat-service.ts +5 -0
- package/src/home/relationship-state-writer.ts +5 -0
- package/src/hooks/hook-loader.ts +341 -0
- package/src/ipc/__tests__/clients-list-ipc.test.ts +1 -1
- package/src/ipc/assistant-server.ts +2 -2
- package/src/mcp/__tests__/mcp-auth-orchestrator.test.ts +1 -0
- package/src/mcp/client.ts +15 -1
- package/src/mcp/mcp-auth-orchestrator.ts +6 -1
- package/src/mcp/mcp-header-store.ts +134 -0
- package/src/mcp/mcp-oauth-provider.ts +19 -8
- 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/compaction-ledger-store.ts +107 -0
- package/src/memory/conversation-crud.ts +129 -61
- package/src/memory/db-connection.ts +22 -3
- package/src/memory/db-init.ts +37 -503
- package/src/memory/db-singleton.ts +6 -4
- package/src/memory/jobs-worker.ts +104 -0
- package/src/memory/llm-request-log-store.ts +26 -1
- package/src/memory/llm-usage-store.ts +48 -20
- package/src/memory/memory-retrospective-job.ts +14 -8
- package/src/memory/migrations/209-strip-thinking-from-consolidated.ts +152 -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/302-create-compaction-events.ts +107 -0
- package/src/memory/migrations/__tests__/209-strip-thinking-from-consolidated.test.ts +297 -0
- package/src/memory/migrations/__tests__/run-migrations.test.ts +2 -2
- package/src/memory/migrations/run-migrations.ts +106 -10
- package/src/memory/migrations/validate-migration-state.ts +105 -67
- package/src/memory/prompt-override.ts +129 -0
- package/src/memory/schema/contacts.ts +6 -2
- package/src/memory/schema/conversations.ts +37 -0
- package/src/memory/schema/infrastructure.ts +20 -0
- package/src/memory/steps.ts +1294 -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 +204 -0
- package/src/memory/worker-process.ts +89 -0
- package/src/messaging/channel-binding-metadata.ts +31 -0
- package/src/messaging/provider-types.ts +8 -0
- package/src/messaging/providers/slack/binding-metadata.ts +60 -0
- package/src/notifications/__tests__/broadcaster.test.ts +8 -8
- package/src/notifications/__tests__/connected-channels.test.ts +86 -0
- package/src/notifications/__tests__/decision-engine.test.ts +78 -9
- package/src/notifications/__tests__/destination-resolver.test.ts +151 -0
- package/src/notifications/broadcaster.ts +8 -1
- package/src/notifications/decision-engine.ts +15 -7
- package/src/notifications/destination-resolver.ts +53 -25
- package/src/notifications/emit-signal.ts +34 -15
- package/src/onboarding/checkin-event.test.ts +222 -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 +12 -12
- package/src/plugin-api/types.ts +12 -14
- package/src/plugin-api/vision-support.test.ts +28 -4
- package/src/plugin-api/vision-support.ts +66 -31
- package/src/plugins/defaults/empty-response/hooks/post-model-call.ts +2 -2
- package/src/plugins/defaults/empty-response/hooks/stop.ts +2 -2
- package/src/plugins/defaults/exploration-drift/hooks/post-tool-use.ts +4 -3
- package/src/plugins/defaults/history-repair/hooks/post-model-call.ts +2 -2
- package/src/plugins/defaults/history-repair/hooks/stop.ts +2 -2
- package/src/plugins/defaults/history-repair/hooks/user-prompt-submit.ts +2 -2
- package/src/plugins/defaults/image-fallback/__tests__/image-fallback.test.ts +47 -7
- package/src/plugins/defaults/image-fallback/hooks/post-tool-use.ts +12 -13
- package/src/plugins/defaults/image-fallback/hooks/user-prompt-submit.ts +14 -22
- package/src/plugins/defaults/image-fallback/src/caption-blocks.ts +42 -11
- package/src/plugins/defaults/image-recovery/hooks/post-model-call.ts +2 -2
- package/src/plugins/defaults/image-recovery/hooks/stop.ts +2 -2
- package/src/plugins/defaults/index.ts +0 -35
- package/src/plugins/defaults/max-tokens-continue/hooks/post-model-call.ts +2 -2
- package/src/plugins/defaults/max-tokens-continue/hooks/stop.ts +2 -2
- package/src/plugins/defaults/memory-retrieval/hooks/post-compact.ts +2 -2
- package/src/plugins/defaults/memory-retrieval/hooks/user-prompt-submit.ts +2 -2
- package/src/plugins/defaults/memory-v3-shadow/__tests__/injection.test.ts +33 -3
- package/src/plugins/defaults/memory-v3-shadow/__tests__/pool-select.test.ts +48 -4
- package/src/plugins/defaults/memory-v3-shadow/__tests__/shadow-plugin.test.ts +4 -8
- package/src/plugins/defaults/memory-v3-shadow/hooks/post-compact.ts +2 -2
- package/src/plugins/defaults/memory-v3-shadow/hooks/user-prompt-submit.ts +2 -2
- package/src/plugins/defaults/memory-v3-shadow/injector.ts +43 -15
- 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 +77 -13
- package/src/plugins/defaults/memory-v3-shadow/shadow-plugin.ts +12 -11
- package/src/plugins/defaults/surface-completion-nudge/hooks/post-model-call.ts +2 -2
- package/src/plugins/defaults/surface-completion-nudge/hooks/stop.ts +2 -2
- package/src/plugins/defaults/task-progress-nudge/hooks/post-tool-use.ts +2 -2
- package/src/plugins/defaults/title-generate/hooks/stop.ts +2 -2
- package/src/plugins/defaults/title-generate/hooks/user-prompt-submit.ts +2 -2
- package/src/plugins/defaults/tool-error/hooks/post-tool-use.ts +2 -2
- package/src/plugins/defaults/tool-result-truncate/hooks/post-tool-use.ts +2 -2
- package/src/plugins/disabled-state.ts +31 -0
- package/src/plugins/external-plugin-loader.ts +2 -2
- package/src/plugins/mtime-cache.ts +186 -330
- package/src/plugins/pipeline.ts +111 -13
- package/src/plugins/registry.ts +59 -16
- package/src/plugins/surface-import.ts +121 -0
- package/src/plugins/types.ts +7 -9
- package/src/prompts/persona-resolver.ts +43 -11
- package/src/providers/anthropic/client.ts +5 -0
- package/src/providers/call-site-routing.ts +45 -0
- package/src/providers/model-catalog.ts +16 -0
- package/src/providers/openai/__tests__/api-error-normalization.test.ts +321 -0
- package/src/providers/openai/api-error-normalization.ts +270 -0
- package/src/providers/openai/chat-completions-provider.ts +37 -83
- package/src/providers/openai/responses-provider.ts +50 -46
- package/src/providers/openrouter/client.ts +5 -0
- package/src/providers/provider-send-message.ts +10 -0
- package/src/providers/ratelimit.ts +10 -0
- package/src/providers/registry.ts +1 -1
- package/src/providers/retry.ts +10 -0
- package/src/providers/types.ts +22 -0
- package/src/providers/usage-tracking.ts +10 -0
- package/src/runtime/__tests__/channel-verification-service.test.ts +133 -0
- package/src/runtime/__tests__/guardian-vellum-migration.test.ts +184 -0
- package/src/runtime/__tests__/is-guardian-bound-for-channel.test.ts +66 -0
- package/src/runtime/__tests__/local-principal-trust.test.ts +162 -0
- package/src/runtime/__tests__/member-verdict-cache.test.ts +119 -0
- package/src/runtime/__tests__/trust-verdict-consumer.test.ts +402 -123
- package/src/runtime/access-request-helper.ts +18 -39
- package/src/runtime/actor-trust-resolver.ts +46 -19
- package/src/runtime/anchored-guardian.test.ts +109 -0
- package/src/runtime/anchored-guardian.ts +86 -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 +69 -8
- package/src/runtime/invite-redemption-service.ts +213 -187
- package/src/runtime/local-actor-identity.test.ts +108 -0
- package/src/runtime/local-actor-identity.ts +85 -13
- package/src/runtime/local-principal-trust.ts +52 -0
- package/src/runtime/member-verdict-cache.ts +0 -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 +305 -0
- package/src/runtime/routes/__tests__/global-search-routes.test.ts +92 -0
- package/src/runtime/routes/__tests__/http-adapter-actor-header.test.ts +129 -0
- package/src/runtime/routes/__tests__/surface-action-routes.test.ts +188 -0
- package/src/runtime/routes/browser-routes.ts +1 -1
- package/src/runtime/routes/canonical-guardian-expiry-sweep.ts +13 -5
- package/src/runtime/routes/channel-verification-routes.ts +3 -3
- package/src/runtime/routes/contact-routes.ts +48 -57
- 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-query-routes.ts +72 -0
- package/src/runtime/routes/conversation-routes.ts +84 -85
- 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 +16 -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 -59
- package/src/runtime/routes/inbound-stages/background-dispatch.ts +13 -5
- 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/inbound-stages/reaction-intercept.ts +19 -0
- package/src/runtime/routes/index.ts +2 -0
- package/src/runtime/routes/llm-context-normalization.ts +83 -0
- package/src/runtime/routes/mcp-auth-routes.ts +171 -19
- 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/settings-routes.ts +8 -3
- package/src/runtime/routes/subagents-routes.ts +5 -0
- package/src/runtime/routes/surface-action-routes.ts +42 -56
- package/src/runtime/services/__tests__/conversation-serializer.test.ts +1 -0
- package/src/runtime/services/conversation-serializer.ts +13 -58
- package/src/runtime/tool-grant-request-helper.ts +3 -3
- package/src/runtime/trust-verdict-consumer.ts +111 -40
- package/src/runtime/verification-outbound-actions.ts +18 -18
- package/src/signals/user-message.ts +16 -0
- package/src/subagent/__tests__/consult-prompt.test.ts +35 -0
- package/src/{plugins/defaults/advisor/__tests__/transcript.test.ts → subagent/__tests__/consult-transcript.test.ts} +47 -10
- package/src/{plugins/defaults/advisor/steering.ts → subagent/consult-prompt.ts} +22 -32
- package/src/{plugins/defaults/advisor/transcript.ts → subagent/consult-transcript.ts} +18 -8
- package/src/subagent/index.ts +1 -1
- package/src/subagent/manager.ts +254 -33
- package/src/subagent/types.ts +11 -4
- 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/executor.ts +4 -4
- package/src/tools/filesystem/search.ts +543 -0
- package/src/tools/registry.ts +38 -0
- package/src/tools/shared/filesystem/path-policy.ts +12 -5
- package/src/tools/subagent/consult-deadline.ts +49 -0
- package/src/tools/subagent/spawn.ts +234 -5
- package/src/tools/tool-approval-handler.ts +1 -1
- package/src/tools/tool-defaults.ts +9 -2
- package/src/tools/tool-manifest.ts +3 -0
- package/src/tools/types.ts +131 -25
- package/src/tools/workspace-tools/loader.ts +348 -244
- package/src/util/errors.ts +26 -1
- package/src/util/logger.ts +9 -0
- package/src/util/platform.ts +19 -0
- package/src/util/telemetry-db-path.ts +24 -0
- package/src/workflows/library.test.ts +140 -0
- package/src/workflows/library.ts +82 -28
- 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/workspace/migrations/031-drop-user-md.ts +232 -148
- package/src/workspace/migrations/048-remove-workspace-hooks.ts +14 -66
- package/src/workspace/migrations/111-prune-seeded-callsite-defaults.ts +134 -0
- package/src/workspace/migrations/112-remove-advisor-callsite-override.ts +64 -0
- package/src/workspace/migrations/registry.ts +4 -0
- 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
- package/src/plugins/defaults/advisor/__tests__/advisor-gate.test.ts +0 -56
- package/src/plugins/defaults/advisor/__tests__/advisor-state-store.test.ts +0 -43
- package/src/plugins/defaults/advisor/__tests__/agent-loop-integration.test.ts +0 -137
- package/src/plugins/defaults/advisor/__tests__/consult.test.ts +0 -153
- package/src/plugins/defaults/advisor/__tests__/hooks.test.ts +0 -138
- package/src/plugins/defaults/advisor/advisor-gate.ts +0 -29
- package/src/plugins/defaults/advisor/advisor-state-store.ts +0 -94
- package/src/plugins/defaults/advisor/config.ts +0 -21
- package/src/plugins/defaults/advisor/consult.ts +0 -93
- package/src/plugins/defaults/advisor/hooks/post-model-call.ts +0 -34
- package/src/plugins/defaults/advisor/hooks/pre-model-call.ts +0 -30
- package/src/plugins/defaults/advisor/hooks/user-prompt-submit.ts +0 -19
- package/src/plugins/defaults/advisor/package.json +0 -14
- package/src/plugins/defaults/advisor/tools/advisor.ts +0 -65
- package/src/providers/openai/__tests__/api-error-detail.test.ts +0 -120
|
@@ -13,7 +13,11 @@
|
|
|
13
13
|
|
|
14
14
|
import type { ChannelId } from "../channels/types.js";
|
|
15
15
|
import { isHttpAuthDisabled } from "../config/env.js";
|
|
16
|
-
import {
|
|
16
|
+
import {
|
|
17
|
+
getGuardianDelivery,
|
|
18
|
+
guardianForChannel,
|
|
19
|
+
peekCachedGuardianDelivery,
|
|
20
|
+
} from "../contacts/guardian-delivery-reader.js";
|
|
17
21
|
import type { TrustContext } from "../daemon/trust-context.js";
|
|
18
22
|
import { getLogger } from "../util/logger.js";
|
|
19
23
|
import { DAEMON_INTERNAL_ASSISTANT_ID } from "./assistant-scope.js";
|
|
@@ -45,14 +49,57 @@ export function buildLocalAuthContext(conversationId: string): AuthContext {
|
|
|
45
49
|
}
|
|
46
50
|
|
|
47
51
|
/**
|
|
48
|
-
*
|
|
52
|
+
* Resolve the local vellum guardian's principalId from the gateway.
|
|
53
|
+
*
|
|
54
|
+
* The gateway owns guardian binding; this reads it through the cached
|
|
55
|
+
* `getGuardianDelivery` reader (PR-3 TTL + single-flight) so hot paths don't
|
|
56
|
+
* storm the IPC.
|
|
49
57
|
*
|
|
50
58
|
* Returns `undefined` when no vellum guardian binding exists (e.g. fresh
|
|
51
|
-
* install before bootstrap). Callers should
|
|
52
|
-
* "not yet available" and
|
|
59
|
+
* install before bootstrap, or the gateway is unreachable). Callers should
|
|
60
|
+
* treat that case as "not yet available" and proceed without a principalId.
|
|
61
|
+
*/
|
|
62
|
+
export async function findLocalGuardianPrincipalId(): Promise<
|
|
63
|
+
string | undefined
|
|
64
|
+
> {
|
|
65
|
+
const list = await getGuardianDelivery({ channelTypes: ["vellum"] });
|
|
66
|
+
if (!list) return undefined;
|
|
67
|
+
return guardianForChannel(list, "vellum")?.principalId ?? undefined;
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
/**
|
|
71
|
+
* Eagerly warm the gateway guardian-delivery cache for the vellum channel.
|
|
72
|
+
*
|
|
73
|
+
* The SSE eager-subscribe path resolves the actor principal synchronously via
|
|
74
|
+
* {@link findLocalGuardianPrincipalIdFromStore}, which reads only the IO-free
|
|
75
|
+
* cache snapshot. On a cold cache (auth-disabled / local startup, before any
|
|
76
|
+
* async `getGuardianDelivery` has run) it returns undefined, so the FIRST SSE
|
|
77
|
+
* registration would carry no `actorPrincipalId` and host-proxy same-user
|
|
78
|
+
* targeting would regress until a later reconnect warms the cache.
|
|
79
|
+
*
|
|
80
|
+
* Called during daemon startup (after the gateway IPC is reachable) so the
|
|
81
|
+
* cache is populated before clients register. Best-effort: a cold gateway
|
|
82
|
+
* leaves the cache empty (failures aren't cached), and the async hot paths
|
|
83
|
+
* warm it on their next read.
|
|
53
84
|
*/
|
|
54
|
-
export function
|
|
55
|
-
|
|
85
|
+
export async function warmLocalGuardianPrincipalCache(): Promise<void> {
|
|
86
|
+
await findLocalGuardianPrincipalId();
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
/**
|
|
90
|
+
* Synchronous read of the vellum guardian's principalId for paths that cannot
|
|
91
|
+
* await {@link findLocalGuardianPrincipalId} — namely the SSE eager-subscribe
|
|
92
|
+
* path (`events-routes`), which registers before the stream is created.
|
|
93
|
+
*
|
|
94
|
+
* Reads the same gateway-owned binding as the async path via a sync, IO-free
|
|
95
|
+
* snapshot of the guardian-delivery cache (kept fresh by the async hot paths
|
|
96
|
+
* and event-driven invalidation), so SSE registers the SAME principal the
|
|
97
|
+
* send/result routes resolve.
|
|
98
|
+
*/
|
|
99
|
+
export function findLocalGuardianPrincipalIdFromStore(): string | undefined {
|
|
100
|
+
const cached = peekCachedGuardianDelivery({ channelTypes: ["vellum"] });
|
|
101
|
+
if (!cached) return undefined;
|
|
102
|
+
return guardianForChannel(cached, "vellum")?.principalId ?? undefined;
|
|
56
103
|
}
|
|
57
104
|
|
|
58
105
|
/**
|
|
@@ -76,12 +123,35 @@ export function findLocalGuardianPrincipalId(): string | undefined {
|
|
|
76
123
|
* yet (e.g. fresh install before bootstrap); callers must treat this the
|
|
77
124
|
* same as a missing principal.
|
|
78
125
|
*/
|
|
79
|
-
export function resolveActorPrincipalIdForLocalGuardian(
|
|
126
|
+
export async function resolveActorPrincipalIdForLocalGuardian(
|
|
127
|
+
rawHeader: string | undefined,
|
|
128
|
+
): Promise<string | undefined> {
|
|
129
|
+
if (rawHeader !== "dev-bypass" || !isHttpAuthDisabled()) return rawHeader;
|
|
130
|
+
|
|
131
|
+
const guardianPrincipalId = await findLocalGuardianPrincipalId();
|
|
132
|
+
if (guardianPrincipalId) return guardianPrincipalId;
|
|
133
|
+
|
|
134
|
+
log.warn(
|
|
135
|
+
"dev-bypass actor principal received but no vellum guardian binding found; returning undefined",
|
|
136
|
+
);
|
|
137
|
+
return undefined;
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
/**
|
|
141
|
+
* Synchronous variant of {@link resolveActorPrincipalIdForLocalGuardian} for
|
|
142
|
+
* the SSE eager-subscribe path, which registers before the response stream is
|
|
143
|
+
* created and cannot await. Resolves the guardian from the IO-free gateway
|
|
144
|
+
* cache snapshot first (same source the async path reads), falling back to the
|
|
145
|
+
* local store when the cache is cold — so SSE registers the SAME principal the
|
|
146
|
+
* send/result routes resolve and host-proxy targeting matches the same-user
|
|
147
|
+
* client even when the local contact row is stale.
|
|
148
|
+
*/
|
|
149
|
+
export function resolveActorPrincipalIdForLocalGuardianSync(
|
|
80
150
|
rawHeader: string | undefined,
|
|
81
151
|
): string | undefined {
|
|
82
152
|
if (rawHeader !== "dev-bypass" || !isHttpAuthDisabled()) return rawHeader;
|
|
83
153
|
|
|
84
|
-
const guardianPrincipalId =
|
|
154
|
+
const guardianPrincipalId = findLocalGuardianPrincipalIdFromStore();
|
|
85
155
|
if (guardianPrincipalId) return guardianPrincipalId;
|
|
86
156
|
|
|
87
157
|
log.warn(
|
|
@@ -102,12 +172,12 @@ export function resolveActorPrincipalIdForLocalGuardian(
|
|
|
102
172
|
* bootstrap), falls back to a minimal guardian context so the local
|
|
103
173
|
* user is not incorrectly denied.
|
|
104
174
|
*/
|
|
105
|
-
export function resolveLocalTrustContext(
|
|
175
|
+
export async function resolveLocalTrustContext(
|
|
106
176
|
sourceChannel: ChannelId = "vellum",
|
|
107
|
-
): TrustContext {
|
|
177
|
+
): Promise<TrustContext> {
|
|
108
178
|
const assistantId = DAEMON_INTERNAL_ASSISTANT_ID;
|
|
109
179
|
|
|
110
|
-
const guardianPrincipalId = findLocalGuardianPrincipalId();
|
|
180
|
+
const guardianPrincipalId = await findLocalGuardianPrincipalId();
|
|
111
181
|
if (guardianPrincipalId) {
|
|
112
182
|
const trustCtx = resolveTrustContext({
|
|
113
183
|
assistantId,
|
|
@@ -139,10 +209,12 @@ export function resolveLocalTrustContext(
|
|
|
139
209
|
* downstream code to resolve guardian context using the same
|
|
140
210
|
* `authContext.actorPrincipalId` path as HTTP sessions.
|
|
141
211
|
*/
|
|
142
|
-
export function resolveLocalAuthContext(
|
|
212
|
+
export async function resolveLocalAuthContext(
|
|
213
|
+
conversationId: string,
|
|
214
|
+
): Promise<AuthContext> {
|
|
143
215
|
const authContext = buildLocalAuthContext(conversationId);
|
|
144
216
|
|
|
145
|
-
const guardianPrincipalId = findLocalGuardianPrincipalId();
|
|
217
|
+
const guardianPrincipalId = await findLocalGuardianPrincipalId();
|
|
146
218
|
if (guardianPrincipalId) {
|
|
147
219
|
return { ...authContext, actorPrincipalId: guardianPrincipalId };
|
|
148
220
|
}
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Derives a local principal's {@link TrustContext} from the gateway guardian
|
|
3
|
+
* binding. Fails closed to unknown on a missing or null read.
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import type { ChannelId } from "../channels/types.js";
|
|
7
|
+
import { getGuardianDelivery } from "../contacts/guardian-delivery-reader.js";
|
|
8
|
+
import type { TrustContext } from "../daemon/trust-context.js";
|
|
9
|
+
import { canonicalizeInboundIdentity } from "../util/canonicalize-identity.js";
|
|
10
|
+
|
|
11
|
+
export interface ResolveLocalPrincipalTrustInput {
|
|
12
|
+
actorPrincipalId: string;
|
|
13
|
+
sourceChannel: ChannelId;
|
|
14
|
+
conversationExternalId: string;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
/** Guardian match → guardian ctx; miss or null read → unknown (fail closed). */
|
|
18
|
+
export async function resolveLocalPrincipalTrustContext(
|
|
19
|
+
input: ResolveLocalPrincipalTrustInput,
|
|
20
|
+
): Promise<TrustContext> {
|
|
21
|
+
const unknownContext: TrustContext = {
|
|
22
|
+
sourceChannel: input.sourceChannel,
|
|
23
|
+
trustClass: "unknown",
|
|
24
|
+
requesterExternalUserId: input.actorPrincipalId,
|
|
25
|
+
requesterChatId: input.conversationExternalId,
|
|
26
|
+
};
|
|
27
|
+
|
|
28
|
+
// Fail closed: a null read means the gateway is unreachable — never grant
|
|
29
|
+
// guardian on a miss.
|
|
30
|
+
const guardians = await getGuardianDelivery({ channelTypes: ["vellum"] });
|
|
31
|
+
if (!guardians) return unknownContext;
|
|
32
|
+
|
|
33
|
+
const guardian = guardians.find(
|
|
34
|
+
(g) => g.principalId === input.actorPrincipalId,
|
|
35
|
+
);
|
|
36
|
+
if (!guardian) return unknownContext;
|
|
37
|
+
|
|
38
|
+
return {
|
|
39
|
+
sourceChannel: input.sourceChannel,
|
|
40
|
+
trustClass: "guardian",
|
|
41
|
+
guardianChatId: guardian.externalChatId ?? input.conversationExternalId,
|
|
42
|
+
guardianExternalUserId:
|
|
43
|
+
canonicalizeInboundIdentity(input.sourceChannel, guardian.address) ??
|
|
44
|
+
undefined,
|
|
45
|
+
guardianPrincipalId: guardian.principalId ?? undefined,
|
|
46
|
+
// Mirror toTrustContext: with no username the requester identifier is the
|
|
47
|
+
// canonical sender id, which for a vellum principal is actorPrincipalId.
|
|
48
|
+
requesterIdentifier: input.actorPrincipalId,
|
|
49
|
+
requesterExternalUserId: input.actorPrincipalId,
|
|
50
|
+
requesterChatId: input.conversationExternalId,
|
|
51
|
+
};
|
|
52
|
+
}
|
|
Binary file
|
|
@@ -229,6 +229,15 @@ export function getByConversation(
|
|
|
229
229
|
* /v1/host-browser-result, /v1/host-app-control-result, or
|
|
230
230
|
* /v1/host-transfer-result after completing the operation, get a 404, and the
|
|
231
231
|
* proxy timer would fire with a spurious timeout error.
|
|
232
|
+
*
|
|
233
|
+
* `question` interactions are also skipped: a new message supersedes an open
|
|
234
|
+
* ask_question by steering to it (see the enqueue path in
|
|
235
|
+
* conversation-routes.ts), which aborts the parked turn and settles the
|
|
236
|
+
* question via its abort signal. Clearing the entry here instead would drop it
|
|
237
|
+
* without settling the prompt's Promise (questions carry no `rpcResolve`
|
|
238
|
+
* fallback like secrets do) and would strip the steer of the entry it needs to
|
|
239
|
+
* fire — which can co-occur with a confirmation, since one model response can
|
|
240
|
+
* open both tools concurrently.
|
|
232
241
|
*/
|
|
233
242
|
export function removeByConversation(
|
|
234
243
|
conversationId: string,
|
|
@@ -244,7 +253,8 @@ export function removeByConversation(
|
|
|
244
253
|
interaction.kind !== "host_browser" &&
|
|
245
254
|
interaction.kind !== "host_app_control" &&
|
|
246
255
|
interaction.kind !== "host_transfer" &&
|
|
247
|
-
interaction.kind !== "acp_confirmation"
|
|
256
|
+
interaction.kind !== "acp_confirmation" &&
|
|
257
|
+
interaction.kind !== "question"
|
|
248
258
|
) {
|
|
249
259
|
// resolve() clears the stored timer and detaches abort listeners.
|
|
250
260
|
resolve(requestId, state);
|
|
@@ -10,6 +10,7 @@
|
|
|
10
10
|
|
|
11
11
|
import { beforeEach, describe, expect, mock, test } from "bun:test";
|
|
12
12
|
|
|
13
|
+
import type { GuardianDelivery } from "@vellumai/gateway-client";
|
|
13
14
|
import { IpcCallError } from "@vellumai/gateway-client/ipc-client";
|
|
14
15
|
|
|
15
16
|
type IpcCall = { method: string; params?: Record<string, unknown> };
|
|
@@ -73,8 +74,16 @@ mock.module("../../channel-verification-service.js", () => ({
|
|
|
73
74
|
getGuardianBinding: mock(() => guardianBinding),
|
|
74
75
|
}));
|
|
75
76
|
|
|
76
|
-
// Contact-store lookup that resolves the guardian's channel to downgrade.
|
|
77
|
-
|
|
77
|
+
// Contact-store lookup that resolves the guardian's channel to downgrade. The
|
|
78
|
+
// channel carries the type/address/externalChatId the gateway delivery is
|
|
79
|
+
// matched against (see deliveryForChannel).
|
|
80
|
+
let contactChannel: {
|
|
81
|
+
id: string;
|
|
82
|
+
status: string;
|
|
83
|
+
type: string;
|
|
84
|
+
address: string;
|
|
85
|
+
externalChatId: string;
|
|
86
|
+
} | null = null;
|
|
78
87
|
const actualContactStore = await import("../../../contacts/contact-store.js");
|
|
79
88
|
mock.module("../../../contacts/contact-store.js", () => ({
|
|
80
89
|
...actualContactStore,
|
|
@@ -83,6 +92,20 @@ mock.module("../../../contacts/contact-store.js", () => ({
|
|
|
83
92
|
),
|
|
84
93
|
}));
|
|
85
94
|
|
|
95
|
+
// Gateway delivery (ACL source of truth). The revoke gate relays only when the
|
|
96
|
+
// matching delivery is live (active/pending/unverified); already-revoked or a
|
|
97
|
+
// missing delivery short-circuits the relay.
|
|
98
|
+
let guardianDeliveries: GuardianDelivery[] | null = null;
|
|
99
|
+
mock.module("../../../contacts/guardian-delivery-reader.js", () => ({
|
|
100
|
+
getGuardianDelivery: mock(async (input?: { channelTypes?: string[] }) => {
|
|
101
|
+
if (guardianDeliveries == null) return null;
|
|
102
|
+
if (!input?.channelTypes) return guardianDeliveries;
|
|
103
|
+
return guardianDeliveries.filter((g) =>
|
|
104
|
+
input.channelTypes!.includes(g.channelType),
|
|
105
|
+
);
|
|
106
|
+
}),
|
|
107
|
+
}));
|
|
108
|
+
|
|
86
109
|
// Guard: the contact-channel ACL write moves to the gateway relay and must
|
|
87
110
|
// never run locally. The assistant-owned guardian-binding teardown
|
|
88
111
|
// (revokeGuardianBinding) still runs locally — assert it is invoked.
|
|
@@ -122,7 +145,24 @@ describe("verification revoke relay", () => {
|
|
|
122
145
|
guardianExternalUserId: "guardian-user",
|
|
123
146
|
guardianDeliveryChatId: "chat-1",
|
|
124
147
|
};
|
|
125
|
-
contactChannel = {
|
|
148
|
+
contactChannel = {
|
|
149
|
+
id: "ch1",
|
|
150
|
+
status: "active",
|
|
151
|
+
type: "telegram",
|
|
152
|
+
address: "guardian-user",
|
|
153
|
+
externalChatId: "chat-1",
|
|
154
|
+
};
|
|
155
|
+
// Gateway delivery is live by default, so the revoke relay fires.
|
|
156
|
+
guardianDeliveries = [
|
|
157
|
+
{
|
|
158
|
+
channelType: "telegram",
|
|
159
|
+
contactId: "c1",
|
|
160
|
+
address: "guardian-user",
|
|
161
|
+
externalChatId: "chat-1",
|
|
162
|
+
status: "active",
|
|
163
|
+
verifiedAt: 1700000000,
|
|
164
|
+
},
|
|
165
|
+
];
|
|
126
166
|
ipcCallPersistentMock.mockClear();
|
|
127
167
|
cancelOutboundMock.mockClear();
|
|
128
168
|
revokePendingSessionsMock.mockClear();
|
|
@@ -263,8 +303,19 @@ describe("verification revoke relay", () => {
|
|
|
263
303
|
expect(result.bound).toBe(false);
|
|
264
304
|
});
|
|
265
305
|
|
|
266
|
-
test("skips the relay when the
|
|
267
|
-
|
|
306
|
+
test("skips the relay when the gateway delivery is already revoked", async () => {
|
|
307
|
+
// The gateway (source of truth) already shows the channel revoked, so the
|
|
308
|
+
// redundant relay is skipped even though session/binding teardown runs.
|
|
309
|
+
guardianDeliveries = [
|
|
310
|
+
{
|
|
311
|
+
channelType: "telegram",
|
|
312
|
+
contactId: "c1",
|
|
313
|
+
address: "guardian-user",
|
|
314
|
+
externalChatId: "chat-1",
|
|
315
|
+
status: "revoked",
|
|
316
|
+
verifiedAt: 1700000000,
|
|
317
|
+
},
|
|
318
|
+
];
|
|
268
319
|
|
|
269
320
|
await revokeHandler({ body: { channel: "telegram" } });
|
|
270
321
|
|
|
@@ -24,7 +24,7 @@ mock.module("../../../daemon/handlers/config-channels.js", () => ({
|
|
|
24
24
|
verifyTrustedContactCalls.push([contactChannelId, assistantId]);
|
|
25
25
|
return verifyTrustedContactImpl(contactChannelId, assistantId);
|
|
26
26
|
},
|
|
27
|
-
createInboundChallenge: () => ({ success: true }),
|
|
27
|
+
createInboundChallenge: async () => ({ success: true }),
|
|
28
28
|
getVerificationStatus: () => ({ success: true }),
|
|
29
29
|
revokeVerificationForChannel: () => ({ success: true }),
|
|
30
30
|
}));
|
|
@@ -0,0 +1,305 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Unit tests for the contacts read API.
|
|
3
|
+
*
|
|
4
|
+
* `handleListContacts`, `handleGetContact`, and the `search_contacts`
|
|
5
|
+
* no-filter case relay to the gateway rich read (`contacts_list_rich` /
|
|
6
|
+
* `contacts_get_rich`), which is the source of truth for the ACL fields
|
|
7
|
+
* (`role`/`status`/`policy`/`verifiedAt`/`interactionCount`/`lastInteraction`).
|
|
8
|
+
* These tests assert the serialized response shape is gateway-sourced and
|
|
9
|
+
* unchanged for the web client, that no read path falls back to the assistant
|
|
10
|
+
* DB, and that a relay failure fails closed (surfaces an error) instead of
|
|
11
|
+
* reading local ACL.
|
|
12
|
+
*/
|
|
13
|
+
|
|
14
|
+
import { beforeEach, describe, expect, mock, test } from "bun:test";
|
|
15
|
+
|
|
16
|
+
import { IpcCallError } from "@vellumai/gateway-client/ipc-client";
|
|
17
|
+
import { z } from "zod";
|
|
18
|
+
|
|
19
|
+
let ipcCalls: { method: string; params?: Record<string, unknown> }[] = [];
|
|
20
|
+
let ipcResult: unknown = {};
|
|
21
|
+
let ipcError: Error | undefined;
|
|
22
|
+
|
|
23
|
+
const ipcCallPersistentMock = mock(
|
|
24
|
+
async (method: string, params?: Record<string, unknown>) => {
|
|
25
|
+
ipcCalls.push({ method, params });
|
|
26
|
+
if (ipcError) throw ipcError;
|
|
27
|
+
return ipcResult;
|
|
28
|
+
},
|
|
29
|
+
);
|
|
30
|
+
|
|
31
|
+
const actualGatewayClient = await import("../../../ipc/gateway-client.js");
|
|
32
|
+
|
|
33
|
+
mock.module("../../../ipc/gateway-client.js", () => ({
|
|
34
|
+
...actualGatewayClient,
|
|
35
|
+
ipcCallPersistent: ipcCallPersistentMock,
|
|
36
|
+
}));
|
|
37
|
+
|
|
38
|
+
// Guard: fail loudly if any read path falls back to the assistant DB for ACL
|
|
39
|
+
// fields. The relay read paths must source role/status/stats from the gateway.
|
|
40
|
+
const actualContactStore = await import("../../../contacts/contact-store.js");
|
|
41
|
+
|
|
42
|
+
const contactStoreReadGuard = mock(() => {
|
|
43
|
+
throw new Error(
|
|
44
|
+
"assistant contact-store read must not happen on the gateway relay path",
|
|
45
|
+
);
|
|
46
|
+
});
|
|
47
|
+
|
|
48
|
+
// Filtered/native reads (search) legitimately go to the assistant DB. Drive
|
|
49
|
+
// them deterministically so the daemon-native response shape can be asserted.
|
|
50
|
+
let searchContactsResult: unknown[] = [];
|
|
51
|
+
const searchContactsMock = mock(() => searchContactsResult);
|
|
52
|
+
|
|
53
|
+
mock.module("../../../contacts/contact-store.js", () => ({
|
|
54
|
+
...actualContactStore,
|
|
55
|
+
getContact: contactStoreReadGuard,
|
|
56
|
+
listContacts: contactStoreReadGuard,
|
|
57
|
+
getAssistantContactMetadata: contactStoreReadGuard,
|
|
58
|
+
searchContacts: searchContactsMock,
|
|
59
|
+
}));
|
|
60
|
+
|
|
61
|
+
const { handleListContacts, handleGetContact, ROUTES } =
|
|
62
|
+
await import("../contact-routes.js");
|
|
63
|
+
|
|
64
|
+
// Daemon-native contact: INFO is hydrated locally; channel-level ACL fields
|
|
65
|
+
// (status/policy/verification) are gateway-owned and absent on native reads.
|
|
66
|
+
// Contact-level `role` is stored locally (NOT NULL) and always returned.
|
|
67
|
+
const nativeContact = {
|
|
68
|
+
id: "ct_2",
|
|
69
|
+
displayName: "Bob",
|
|
70
|
+
notes: null,
|
|
71
|
+
role: "contact",
|
|
72
|
+
contactType: "human",
|
|
73
|
+
lastInteraction: 4200,
|
|
74
|
+
interactionCount: 4,
|
|
75
|
+
createdAt: 1000,
|
|
76
|
+
updatedAt: 1500,
|
|
77
|
+
userFile: "bob.md",
|
|
78
|
+
channels: [
|
|
79
|
+
{
|
|
80
|
+
id: "ch_2",
|
|
81
|
+
contactId: "ct_2",
|
|
82
|
+
type: "phone",
|
|
83
|
+
address: "+15550200",
|
|
84
|
+
isPrimary: true,
|
|
85
|
+
externalChatId: null,
|
|
86
|
+
inviteId: null,
|
|
87
|
+
lastSeenAt: 4100,
|
|
88
|
+
interactionCount: 4,
|
|
89
|
+
lastInteraction: 4200,
|
|
90
|
+
updatedAt: 1500,
|
|
91
|
+
createdAt: 1000,
|
|
92
|
+
},
|
|
93
|
+
],
|
|
94
|
+
};
|
|
95
|
+
|
|
96
|
+
const gatewayChannel = {
|
|
97
|
+
id: "ch_1",
|
|
98
|
+
contactId: "ct_1",
|
|
99
|
+
type: "sms",
|
|
100
|
+
address: "+15550100",
|
|
101
|
+
isPrimary: true,
|
|
102
|
+
externalUserId: null,
|
|
103
|
+
status: "active",
|
|
104
|
+
policy: "allow",
|
|
105
|
+
verifiedAt: 1700,
|
|
106
|
+
verifiedVia: "invite",
|
|
107
|
+
lastSeenAt: 1800,
|
|
108
|
+
interactionCount: 7,
|
|
109
|
+
lastInteraction: 1900,
|
|
110
|
+
revokedReason: null,
|
|
111
|
+
blockedReason: null,
|
|
112
|
+
};
|
|
113
|
+
|
|
114
|
+
const gatewayContact = {
|
|
115
|
+
id: "ct_1",
|
|
116
|
+
displayName: "Alice",
|
|
117
|
+
role: "guardian",
|
|
118
|
+
notes: "a note",
|
|
119
|
+
contactType: "human",
|
|
120
|
+
lastInteraction: 1900,
|
|
121
|
+
interactionCount: 7,
|
|
122
|
+
createdAt: 1000,
|
|
123
|
+
updatedAt: 1500,
|
|
124
|
+
channels: [gatewayChannel],
|
|
125
|
+
};
|
|
126
|
+
|
|
127
|
+
describe("contacts read API relays from the gateway", () => {
|
|
128
|
+
beforeEach(() => {
|
|
129
|
+
ipcCalls = [];
|
|
130
|
+
ipcResult = {};
|
|
131
|
+
ipcError = undefined;
|
|
132
|
+
ipcCallPersistentMock.mockClear();
|
|
133
|
+
contactStoreReadGuard.mockClear();
|
|
134
|
+
searchContactsResult = [];
|
|
135
|
+
searchContactsMock.mockClear();
|
|
136
|
+
});
|
|
137
|
+
|
|
138
|
+
test("list relays to contacts_list_rich and serializes the gateway ACL fields", async () => {
|
|
139
|
+
ipcResult = { ok: true, contacts: [gatewayContact] };
|
|
140
|
+
|
|
141
|
+
const result = await handleListContacts({ limit: "50" });
|
|
142
|
+
|
|
143
|
+
expect(ipcCalls).toEqual([
|
|
144
|
+
{ method: "contacts_list_rich", params: { limit: 50 } },
|
|
145
|
+
]);
|
|
146
|
+
expect(result.ok).toBe(true);
|
|
147
|
+
expect(result.contacts).toHaveLength(1);
|
|
148
|
+
|
|
149
|
+
const [contact] = result.contacts;
|
|
150
|
+
// ACL fields are gateway-sourced and reach the web client unchanged.
|
|
151
|
+
expect((contact as { role?: string }).role).toBe("guardian");
|
|
152
|
+
expect(contact.interactionCount).toBe(7);
|
|
153
|
+
expect(contact.lastInteraction).toBe(1900);
|
|
154
|
+
const channel = contact.channels[0] as Record<string, unknown>;
|
|
155
|
+
expect(channel.status).toBe("active");
|
|
156
|
+
expect(channel.policy).toBe("allow");
|
|
157
|
+
expect(channel.verifiedAt).toBe(1700);
|
|
158
|
+
expect(channel.interactionCount).toBe(7);
|
|
159
|
+
expect(channel.lastInteraction).toBe(1900);
|
|
160
|
+
// Channel compat echoes address into externalUserId for older clients.
|
|
161
|
+
expect(channel.externalUserId).toBe("+15550100");
|
|
162
|
+
|
|
163
|
+
expect(contactStoreReadGuard).not.toHaveBeenCalled();
|
|
164
|
+
});
|
|
165
|
+
|
|
166
|
+
test("list forwards the role filter to the gateway", async () => {
|
|
167
|
+
ipcResult = { ok: true, contacts: [] };
|
|
168
|
+
|
|
169
|
+
await handleListContacts({ limit: "10", role: "guardian" });
|
|
170
|
+
|
|
171
|
+
expect(ipcCalls).toEqual([
|
|
172
|
+
{
|
|
173
|
+
method: "contacts_list_rich",
|
|
174
|
+
params: { limit: 10, role: "guardian" },
|
|
175
|
+
},
|
|
176
|
+
]);
|
|
177
|
+
});
|
|
178
|
+
|
|
179
|
+
test("list fails closed when the gateway relay is unavailable", async () => {
|
|
180
|
+
ipcError = new IpcCallError("gateway down", {
|
|
181
|
+
statusCode: 503,
|
|
182
|
+
errorCode: "UNAVAILABLE",
|
|
183
|
+
});
|
|
184
|
+
|
|
185
|
+
await expect(handleListContacts({ limit: "50" })).rejects.toMatchObject({
|
|
186
|
+
message: "gateway down",
|
|
187
|
+
statusCode: 503,
|
|
188
|
+
});
|
|
189
|
+
// No assistant-DB fallback read.
|
|
190
|
+
expect(contactStoreReadGuard).not.toHaveBeenCalled();
|
|
191
|
+
});
|
|
192
|
+
|
|
193
|
+
test("get relays to contacts_get_rich and serializes the gateway ACL fields", async () => {
|
|
194
|
+
ipcResult = { ok: true, contact: gatewayContact };
|
|
195
|
+
|
|
196
|
+
const result = await handleGetContact("ct_1");
|
|
197
|
+
|
|
198
|
+
expect(ipcCalls).toEqual([
|
|
199
|
+
{ method: "contacts_get_rich", params: { contactId: "ct_1" } },
|
|
200
|
+
]);
|
|
201
|
+
expect(result.ok).toBe(true);
|
|
202
|
+
expect(result.contact.role).toBe("guardian");
|
|
203
|
+
expect(result.contact.interactionCount).toBe(7);
|
|
204
|
+
const channel = result.contact.channels[0] as Record<string, unknown>;
|
|
205
|
+
expect(channel.status).toBe("active");
|
|
206
|
+
expect(channel.externalUserId).toBe("+15550100");
|
|
207
|
+
expect(contactStoreReadGuard).not.toHaveBeenCalled();
|
|
208
|
+
});
|
|
209
|
+
|
|
210
|
+
test("get surfaces a clean gateway not-found as a 404", async () => {
|
|
211
|
+
ipcResult = { ok: true };
|
|
212
|
+
|
|
213
|
+
await expect(handleGetContact("missing")).rejects.toMatchObject({
|
|
214
|
+
statusCode: 404,
|
|
215
|
+
});
|
|
216
|
+
expect(contactStoreReadGuard).not.toHaveBeenCalled();
|
|
217
|
+
});
|
|
218
|
+
|
|
219
|
+
test("get fails closed on a relay failure (no assistant-DB fallback)", async () => {
|
|
220
|
+
ipcError = new IpcCallError("gateway down", {
|
|
221
|
+
statusCode: 503,
|
|
222
|
+
errorCode: "UNAVAILABLE",
|
|
223
|
+
});
|
|
224
|
+
|
|
225
|
+
await expect(handleGetContact("ct_1")).rejects.toMatchObject({
|
|
226
|
+
message: "gateway down",
|
|
227
|
+
statusCode: 503,
|
|
228
|
+
});
|
|
229
|
+
expect(contactStoreReadGuard).not.toHaveBeenCalled();
|
|
230
|
+
});
|
|
231
|
+
|
|
232
|
+
test("search no-filter relays to the gateway list read", async () => {
|
|
233
|
+
ipcResult = { ok: true, contacts: [gatewayContact] };
|
|
234
|
+
|
|
235
|
+
const route = ROUTES.find((r) => r.operationId === "search_contacts");
|
|
236
|
+
expect(route).toBeDefined();
|
|
237
|
+
|
|
238
|
+
const contacts = (await route!.handler({ body: {} })) as Array<{
|
|
239
|
+
role: string;
|
|
240
|
+
interactionCount: number;
|
|
241
|
+
channels: { status: string }[];
|
|
242
|
+
}>;
|
|
243
|
+
|
|
244
|
+
expect(ipcCalls).toEqual([
|
|
245
|
+
{ method: "contacts_list_rich", params: { limit: 50 } },
|
|
246
|
+
]);
|
|
247
|
+
expect(contacts[0].role).toBe("guardian");
|
|
248
|
+
expect(contacts[0].interactionCount).toBe(7);
|
|
249
|
+
expect(contacts[0].channels[0].status).toBe("active");
|
|
250
|
+
expect(contactStoreReadGuard).not.toHaveBeenCalled();
|
|
251
|
+
});
|
|
252
|
+
});
|
|
253
|
+
|
|
254
|
+
describe("filtered/native contact reads stay daemon-native", () => {
|
|
255
|
+
const listRoute = ROUTES.find((r) => r.operationId === "listContacts")!;
|
|
256
|
+
const listResponseSchema = listRoute.responseBody as z.ZodTypeAny;
|
|
257
|
+
const searchRoute = ROUTES.find((r) => r.operationId === "search_contacts")!;
|
|
258
|
+
const searchResponseSchema = searchRoute.responseBody as z.ZodTypeAny;
|
|
259
|
+
|
|
260
|
+
beforeEach(() => {
|
|
261
|
+
ipcCalls = [];
|
|
262
|
+
ipcResult = {};
|
|
263
|
+
ipcError = undefined;
|
|
264
|
+
ipcCallPersistentMock.mockClear();
|
|
265
|
+
contactStoreReadGuard.mockClear();
|
|
266
|
+
searchContactsResult = [];
|
|
267
|
+
searchContactsMock.mockClear();
|
|
268
|
+
});
|
|
269
|
+
|
|
270
|
+
test("query-filtered list serves daemon-native INFO and validates against the response schema", async () => {
|
|
271
|
+
searchContactsResult = [nativeContact];
|
|
272
|
+
|
|
273
|
+
const result = await handleListContacts({ query: "Bob", limit: "10" });
|
|
274
|
+
|
|
275
|
+
// No gateway relay for a true search.
|
|
276
|
+
expect(ipcCalls).toEqual([]);
|
|
277
|
+
expect(searchContactsMock).toHaveBeenCalled();
|
|
278
|
+
|
|
279
|
+
const [contact] = result.contacts;
|
|
280
|
+
// INFO telemetry is present (re-hydrated locally, not dropped).
|
|
281
|
+
expect(contact.interactionCount).toBe(4);
|
|
282
|
+
expect(contact.lastInteraction).toBe(4200);
|
|
283
|
+
const channel = contact.channels[0] as Record<string, unknown>;
|
|
284
|
+
expect(channel.interactionCount).toBe(4);
|
|
285
|
+
expect(channel.lastSeenAt).toBe(4100);
|
|
286
|
+
expect(channel.externalUserId).toBe("+15550200");
|
|
287
|
+
// Contact-level `role` is locally stored (NOT NULL) and always present.
|
|
288
|
+
expect((contact as { role: string }).role).toBe("contact");
|
|
289
|
+
// Channel-level ACL fields (status/policy) are gateway-owned and absent.
|
|
290
|
+
expect("status" in channel).toBe(false);
|
|
291
|
+
expect(() => listResponseSchema.parse(result)).not.toThrow();
|
|
292
|
+
});
|
|
293
|
+
|
|
294
|
+
test("POST search with a filter validates against the response schema", async () => {
|
|
295
|
+
searchContactsResult = [nativeContact];
|
|
296
|
+
|
|
297
|
+
const contacts = (await searchRoute.handler({
|
|
298
|
+
body: { query: "Bob" },
|
|
299
|
+
})) as unknown[];
|
|
300
|
+
|
|
301
|
+
expect(ipcCalls).toEqual([]);
|
|
302
|
+
expect(searchContactsMock).toHaveBeenCalled();
|
|
303
|
+
expect(() => searchResponseSchema.parse(contacts)).not.toThrow();
|
|
304
|
+
});
|
|
305
|
+
});
|