@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
|
@@ -22,21 +22,27 @@ mock.module("../util/logger.js", () => ({
|
|
|
22
22
|
}),
|
|
23
23
|
}));
|
|
24
24
|
|
|
25
|
-
import {
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
} from "../contacts/contact-store.js";
|
|
25
|
+
import { and, desc, eq } from "drizzle-orm";
|
|
26
|
+
|
|
27
|
+
import type { ChannelId } from "../channels/types.js";
|
|
28
|
+
import { findContactChannel } from "../contacts/contact-store.js";
|
|
29
29
|
import {
|
|
30
30
|
revokeMember,
|
|
31
31
|
upsertContactChannel,
|
|
32
32
|
} from "../contacts/contacts-write.js";
|
|
33
|
+
import type { ChannelStatus } from "../contacts/types.js";
|
|
33
34
|
import { getDb } from "../memory/db-connection.js";
|
|
34
35
|
import { initializeDb } from "../memory/db-init.js";
|
|
36
|
+
import { contactChannels, contacts } from "../memory/schema.js";
|
|
35
37
|
import { resolveActorTrust } from "../runtime/actor-trust-resolver.js";
|
|
36
38
|
import {
|
|
37
39
|
createOutboundSession,
|
|
38
40
|
validateAndConsumeVerification,
|
|
39
41
|
} from "../runtime/channel-verification-service.js";
|
|
42
|
+
import {
|
|
43
|
+
__resetMemberVerdictCacheForTest,
|
|
44
|
+
setMemberVerdict,
|
|
45
|
+
} from "../runtime/member-verdict-cache.js";
|
|
40
46
|
import { createGuardianBinding } from "./helpers/create-guardian-binding.js";
|
|
41
47
|
|
|
42
48
|
await initializeDb();
|
|
@@ -53,6 +59,48 @@ function resetTables(): void {
|
|
|
53
59
|
db.run("DELETE FROM contacts");
|
|
54
60
|
}
|
|
55
61
|
|
|
62
|
+
// Mirror a warmed gateway verdict so the sync resolveActorTrust fallback
|
|
63
|
+
// resolves the member with the given status; the local ACL columns are no
|
|
64
|
+
// longer read.
|
|
65
|
+
function warmMemberVerdict(
|
|
66
|
+
channelType: ChannelId,
|
|
67
|
+
address: string,
|
|
68
|
+
status: ChannelStatus = "unverified",
|
|
69
|
+
): void {
|
|
70
|
+
const found = findContactChannel({ channelType, address });
|
|
71
|
+
if (!found) return;
|
|
72
|
+
setMemberVerdict(channelType, address, {
|
|
73
|
+
trustClass: "unknown",
|
|
74
|
+
canonicalSenderId: address,
|
|
75
|
+
contactId: found.contact.id,
|
|
76
|
+
channelId: found.channel.id,
|
|
77
|
+
status,
|
|
78
|
+
policy: "allow",
|
|
79
|
+
});
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
/**
|
|
83
|
+
* Read the local active guardian channel for a channel type, mirroring the
|
|
84
|
+
* gateway's role/status resolution. Used by assertions that confirm the local
|
|
85
|
+
* guardian binding state after verification flows.
|
|
86
|
+
*/
|
|
87
|
+
function localGuardianForChannel(channelType: string) {
|
|
88
|
+
const row = getDb()
|
|
89
|
+
.select({ contact: contacts, channel: contactChannels })
|
|
90
|
+
.from(contacts)
|
|
91
|
+
.innerJoin(contactChannels, eq(contacts.id, contactChannels.contactId))
|
|
92
|
+
.where(
|
|
93
|
+
and(
|
|
94
|
+
eq(contacts.role, "guardian"),
|
|
95
|
+
eq(contactChannels.type, channelType),
|
|
96
|
+
eq(contactChannels.status, "active"),
|
|
97
|
+
),
|
|
98
|
+
)
|
|
99
|
+
.orderBy(desc(contactChannels.verifiedAt))
|
|
100
|
+
.get();
|
|
101
|
+
return row ? { contact: row.contact, channel: row.channel } : null;
|
|
102
|
+
}
|
|
103
|
+
|
|
56
104
|
// ---------------------------------------------------------------------------
|
|
57
105
|
// Tests
|
|
58
106
|
// ---------------------------------------------------------------------------
|
|
@@ -60,6 +108,7 @@ function resetTables(): void {
|
|
|
60
108
|
describe("trusted contact verification → member activation", () => {
|
|
61
109
|
beforeEach(() => {
|
|
62
110
|
resetTables();
|
|
111
|
+
__resetMemberVerdictCacheForTest();
|
|
63
112
|
});
|
|
64
113
|
|
|
65
114
|
test("successful verification creates active member with allow policy", () => {
|
|
@@ -88,26 +137,24 @@ describe("trusted contact verification → member activation", () => {
|
|
|
88
137
|
expect(result.verificationType).toBe("trusted_contact");
|
|
89
138
|
}
|
|
90
139
|
|
|
91
|
-
// Simulate the member upsert that inbound-message-handler performs on
|
|
140
|
+
// Simulate the member upsert that inbound-message-handler performs on
|
|
141
|
+
// success. The local mirror persists identity only; the gateway owns the
|
|
142
|
+
// ACL verdict, so the channel lands at the schema-default status.
|
|
92
143
|
upsertContactChannel({
|
|
93
144
|
sourceChannel: "telegram",
|
|
94
145
|
externalUserId: "requester-user-123",
|
|
95
146
|
externalChatId: "requester-chat-123",
|
|
96
|
-
status: "active",
|
|
97
|
-
policy: "allow",
|
|
98
147
|
displayName: "Requester Name",
|
|
99
148
|
username: "requester_username",
|
|
100
149
|
});
|
|
101
150
|
|
|
102
|
-
// Verify:
|
|
151
|
+
// Verify: member identity record exists
|
|
103
152
|
const contactResult = findContactChannel({
|
|
104
153
|
channelType: "telegram",
|
|
105
154
|
address: "requester-user-123",
|
|
106
155
|
});
|
|
107
156
|
|
|
108
157
|
expect(contactResult).not.toBeNull();
|
|
109
|
-
expect(contactResult!.channel.status).toBe("active");
|
|
110
|
-
expect(contactResult!.channel.policy).toBe("allow");
|
|
111
158
|
expect(contactResult!.channel.address).toBe("requester-user-123");
|
|
112
159
|
expect(contactResult!.channel.externalChatId).toBe("requester-chat-123");
|
|
113
160
|
expect(contactResult!.contact.displayName).toBe("Requester Name");
|
|
@@ -119,11 +166,10 @@ describe("trusted contact verification → member activation", () => {
|
|
|
119
166
|
sourceChannel: "telegram",
|
|
120
167
|
externalUserId: "requester-user-jeff",
|
|
121
168
|
externalChatId: "requester-chat-jeff",
|
|
122
|
-
status: "active",
|
|
123
|
-
policy: "allow",
|
|
124
169
|
displayName: "Jeff",
|
|
125
170
|
username: "jeff_handle",
|
|
126
171
|
});
|
|
172
|
+
warmMemberVerdict("telegram", "requester-user-jeff");
|
|
127
173
|
|
|
128
174
|
const trust = resolveActorTrust({
|
|
129
175
|
assistantId: "self",
|
|
@@ -132,7 +178,9 @@ describe("trusted contact verification → member activation", () => {
|
|
|
132
178
|
actorExternalId: "requester-user-jeff",
|
|
133
179
|
});
|
|
134
180
|
|
|
135
|
-
|
|
181
|
+
// The local mirror persists identity only; the schema-default status places
|
|
182
|
+
// the contact in the unverified_contact tier (gateway owns elevation).
|
|
183
|
+
expect(trust.trustClass).toBe("unverified_contact");
|
|
136
184
|
expect(trust.actorMetadata.displayName).toBe("Jeff");
|
|
137
185
|
expect(trust.actorMetadata.senderDisplayName).toBeUndefined();
|
|
138
186
|
expect(trust.actorMetadata.memberDisplayName).toBe("Jeff");
|
|
@@ -147,11 +195,10 @@ describe("trusted contact verification → member activation", () => {
|
|
|
147
195
|
sourceChannel: "telegram",
|
|
148
196
|
externalUserId: "requester-user-jeff-priority",
|
|
149
197
|
externalChatId: "requester-chat-jeff-priority",
|
|
150
|
-
status: "active",
|
|
151
|
-
policy: "allow",
|
|
152
198
|
displayName: "Jeff",
|
|
153
199
|
username: "jeff_handle",
|
|
154
200
|
});
|
|
201
|
+
warmMemberVerdict("telegram", "requester-user-jeff-priority");
|
|
155
202
|
|
|
156
203
|
const trust = resolveActorTrust({
|
|
157
204
|
assistantId: "self",
|
|
@@ -162,7 +209,7 @@ describe("trusted contact verification → member activation", () => {
|
|
|
162
209
|
actorDisplayName: "Jeffrey",
|
|
163
210
|
});
|
|
164
211
|
|
|
165
|
-
expect(trust.trustClass).toBe("
|
|
212
|
+
expect(trust.trustClass).toBe("unverified_contact");
|
|
166
213
|
expect(trust.actorMetadata.displayName).toBe("Jeff");
|
|
167
214
|
expect(trust.actorMetadata.senderDisplayName).toBe("Jeffrey");
|
|
168
215
|
expect(trust.actorMetadata.memberDisplayName).toBe("Jeff");
|
|
@@ -179,8 +226,6 @@ describe("trusted contact verification → member activation", () => {
|
|
|
179
226
|
sourceChannel: "telegram",
|
|
180
227
|
externalUserId: "other-user-in-group",
|
|
181
228
|
externalChatId: "shared-group-chat",
|
|
182
|
-
status: "active",
|
|
183
|
-
policy: "allow",
|
|
184
229
|
displayName: "Other User",
|
|
185
230
|
username: "other_handle",
|
|
186
231
|
});
|
|
@@ -229,20 +274,17 @@ describe("trusted contact verification → member activation", () => {
|
|
|
229
274
|
sourceChannel: "telegram",
|
|
230
275
|
externalUserId: "requester-user-456",
|
|
231
276
|
externalChatId: "requester-chat-456",
|
|
232
|
-
status: "active",
|
|
233
|
-
policy: "allow",
|
|
234
277
|
});
|
|
235
278
|
|
|
236
|
-
//
|
|
279
|
+
// The local mirror persists the member identity; the gateway owns the ACL
|
|
280
|
+
// verdict the inbound handler enforces.
|
|
237
281
|
const contactResult = findContactChannel({
|
|
238
282
|
channelType: "telegram",
|
|
239
283
|
address: "requester-user-456",
|
|
240
284
|
});
|
|
241
285
|
|
|
242
286
|
expect(contactResult).not.toBeNull();
|
|
243
|
-
expect(contactResult!.channel.
|
|
244
|
-
expect(contactResult!.channel.policy).toBe("allow");
|
|
245
|
-
// ACL check passes: member exists, is active, and has allow policy
|
|
287
|
+
expect(contactResult!.channel.address).toBe("requester-user-456");
|
|
246
288
|
});
|
|
247
289
|
|
|
248
290
|
test("member lookup is scoped by channel type", () => {
|
|
@@ -267,8 +309,6 @@ describe("trusted contact verification → member activation", () => {
|
|
|
267
309
|
sourceChannel: "telegram",
|
|
268
310
|
externalUserId: "user-cross-test",
|
|
269
311
|
externalChatId: "chat-cross-test",
|
|
270
|
-
status: "active",
|
|
271
|
-
policy: "allow",
|
|
272
312
|
});
|
|
273
313
|
|
|
274
314
|
// Member should be found via contacts
|
|
@@ -277,7 +317,7 @@ describe("trusted contact verification → member activation", () => {
|
|
|
277
317
|
address: "user-cross-test",
|
|
278
318
|
});
|
|
279
319
|
expect(contactResult).not.toBeNull();
|
|
280
|
-
expect(contactResult!.channel.
|
|
320
|
+
expect(contactResult!.channel.address).toBe("user-cross-test");
|
|
281
321
|
|
|
282
322
|
// Member should NOT be found for a different channel type
|
|
283
323
|
const otherChannel = findContactChannel({
|
|
@@ -287,31 +327,22 @@ describe("trusted contact verification → member activation", () => {
|
|
|
287
327
|
expect(otherChannel).toBeNull();
|
|
288
328
|
});
|
|
289
329
|
|
|
290
|
-
test("
|
|
291
|
-
// Create
|
|
330
|
+
test("revokeMember resolves the member identity without mutating local ACL", () => {
|
|
331
|
+
// Create a member identity row.
|
|
292
332
|
const member = upsertContactChannel({
|
|
293
333
|
sourceChannel: "telegram",
|
|
294
334
|
externalUserId: "user-revoked",
|
|
295
335
|
externalChatId: "chat-revoked",
|
|
296
|
-
status: "active",
|
|
297
|
-
policy: "allow",
|
|
298
336
|
displayName: "Revoked User",
|
|
299
337
|
});
|
|
300
338
|
|
|
301
|
-
//
|
|
302
|
-
|
|
339
|
+
// The local revoke is a pure resolver now — the gateway owns the ACL
|
|
340
|
+
// downgrade; revokeMember returns the resolved native contact/channel.
|
|
341
|
+
const revoked = revokeMember(member!.channel.id);
|
|
303
342
|
expect(revoked).not.toBeNull();
|
|
304
|
-
expect(revoked!.channel.
|
|
305
|
-
|
|
306
|
-
// Verify the member is indeed revoked (ACL would reject)
|
|
307
|
-
const revokedResult = findContactChannel({
|
|
308
|
-
channelType: "telegram",
|
|
309
|
-
address: "user-revoked",
|
|
310
|
-
});
|
|
311
|
-
expect(revokedResult).not.toBeNull();
|
|
312
|
-
expect(revokedResult!.channel.status).toBe("revoked");
|
|
343
|
+
expect(revoked!.channel.id).toBe(member!.channel.id);
|
|
313
344
|
|
|
314
|
-
//
|
|
345
|
+
// Re-upsert on the same identity is idempotent on the identity row.
|
|
315
346
|
const session = createOutboundSession({
|
|
316
347
|
channel: "telegram",
|
|
317
348
|
expectedExternalUserId: "user-revoked",
|
|
@@ -321,7 +352,6 @@ describe("trusted contact verification → member activation", () => {
|
|
|
321
352
|
verificationPurpose: "trusted_contact",
|
|
322
353
|
});
|
|
323
354
|
|
|
324
|
-
// Requester enters the new code
|
|
325
355
|
const result = validateAndConsumeVerification(
|
|
326
356
|
"telegram",
|
|
327
357
|
session.secret,
|
|
@@ -333,23 +363,18 @@ describe("trusted contact verification → member activation", () => {
|
|
|
333
363
|
expect(result.verificationType).toBe("trusted_contact");
|
|
334
364
|
}
|
|
335
365
|
|
|
336
|
-
// upsertContactChannel reactivates the existing record
|
|
337
366
|
upsertContactChannel({
|
|
338
367
|
sourceChannel: "telegram",
|
|
339
368
|
externalUserId: "user-revoked",
|
|
340
369
|
externalChatId: "chat-revoked",
|
|
341
|
-
status: "active",
|
|
342
|
-
policy: "allow",
|
|
343
370
|
});
|
|
344
371
|
|
|
345
|
-
|
|
346
|
-
const reactivatedResult = findContactChannel({
|
|
372
|
+
const reResolved = findContactChannel({
|
|
347
373
|
channelType: "telegram",
|
|
348
374
|
address: "user-revoked",
|
|
349
375
|
});
|
|
350
|
-
expect(
|
|
351
|
-
expect(
|
|
352
|
-
expect(reactivatedResult!.channel.policy).toBe("allow");
|
|
376
|
+
expect(reResolved).not.toBeNull();
|
|
377
|
+
expect(reResolved!.channel.id).toBe(member!.channel.id);
|
|
353
378
|
});
|
|
354
379
|
|
|
355
380
|
test("trusted contact verification does NOT create a guardian binding", () => {
|
|
@@ -388,7 +413,7 @@ describe("trusted contact verification → member activation", () => {
|
|
|
388
413
|
}
|
|
389
414
|
|
|
390
415
|
// The original guardian binding should remain intact
|
|
391
|
-
const guardianResult =
|
|
416
|
+
const guardianResult = localGuardianForChannel("telegram");
|
|
392
417
|
expect(guardianResult).not.toBeNull();
|
|
393
418
|
expect(guardianResult!.channel.address).toBe("guardian-user-original");
|
|
394
419
|
});
|
|
@@ -412,7 +437,7 @@ describe("trusted contact verification → member activation", () => {
|
|
|
412
437
|
expect(result.verificationType).toBe("guardian");
|
|
413
438
|
}
|
|
414
439
|
|
|
415
|
-
const guardianResult =
|
|
440
|
+
const guardianResult = localGuardianForChannel("telegram");
|
|
416
441
|
expect(guardianResult).toBeNull();
|
|
417
442
|
});
|
|
418
443
|
});
|
|
@@ -109,6 +109,25 @@ mock.module("../calls/channel-admission-reader.js", () => ({
|
|
|
109
109
|
getChannelAdmissionPolicy: async () => mockAdmissionPolicy,
|
|
110
110
|
}));
|
|
111
111
|
|
|
112
|
+
// Mock the inbound trust reader used by the media-stream preflight. Captures
|
|
113
|
+
// its args so tests can assert the inbound caller's verdict is fetched, and
|
|
114
|
+
// returns mockInboundVerdict which is threaded into the preflight routeSetup.
|
|
115
|
+
let mockInboundVerdict: unknown = null;
|
|
116
|
+
let lastInboundVerdictArgs: Record<string, unknown> | null = null;
|
|
117
|
+
mock.module("../calls/inbound-trust-reader.js", () => ({
|
|
118
|
+
getInboundTrustVerdict: async (args: Record<string, unknown>) => {
|
|
119
|
+
lastInboundVerdictArgs = args;
|
|
120
|
+
return mockInboundVerdict;
|
|
121
|
+
},
|
|
122
|
+
getPhoneCallerVerdict: async (otherPartyNumber: string | undefined) => {
|
|
123
|
+
lastInboundVerdictArgs = {
|
|
124
|
+
channelType: "phone",
|
|
125
|
+
actorExternalId: otherPartyNumber || undefined,
|
|
126
|
+
};
|
|
127
|
+
return mockInboundVerdict;
|
|
128
|
+
},
|
|
129
|
+
}));
|
|
130
|
+
|
|
112
131
|
mock.module("../config/env.js", () => ({
|
|
113
132
|
isHttpAuthDisabled: () => true,
|
|
114
133
|
getGatewayInternalBaseUrl: () => "http://gateway.internal:7830",
|
|
@@ -470,6 +489,8 @@ describe("twilio webhook routes", () => {
|
|
|
470
489
|
// Reset admission policy + captured routeSetup context between tests
|
|
471
490
|
mockAdmissionPolicy = null;
|
|
472
491
|
lastRouteSetupCtx = null;
|
|
492
|
+
mockInboundVerdict = null;
|
|
493
|
+
lastInboundVerdictArgs = null;
|
|
473
494
|
// Reset routeSetup mock to default normal_call
|
|
474
495
|
mockRouteSetupResult = {
|
|
475
496
|
outcome: { action: "normal_call", isInbound: true },
|
|
@@ -1269,6 +1290,81 @@ describe("twilio webhook routes", () => {
|
|
|
1269
1290
|
expect(lastRouteSetupCtx?.admissionPolicy).toBe("guardian_only");
|
|
1270
1291
|
});
|
|
1271
1292
|
|
|
1293
|
+
test("media-stream inbound: fetches the caller's verdict (From) and threads it into the preflight routeSetup", async () => {
|
|
1294
|
+
mockConfigObj.services.stt.provider = "openai-whisper" as any;
|
|
1295
|
+
mockInboundVerdict = {
|
|
1296
|
+
channelType: "phone",
|
|
1297
|
+
actorExternalId: "+14155551234",
|
|
1298
|
+
contactId: "contact-1",
|
|
1299
|
+
channelId: "channel-1",
|
|
1300
|
+
status: "verified",
|
|
1301
|
+
policy: "allow",
|
|
1302
|
+
resolutionFailed: false,
|
|
1303
|
+
};
|
|
1304
|
+
|
|
1305
|
+
const req = makeInboundVoiceRequest({
|
|
1306
|
+
CallSid: "CA_ms_verdict_inbound_1",
|
|
1307
|
+
From: "+14155551234",
|
|
1308
|
+
To: "+15550001111",
|
|
1309
|
+
});
|
|
1310
|
+
|
|
1311
|
+
const res = await handleVoiceWebhook(req);
|
|
1312
|
+
expect(res.status).toBe(200);
|
|
1313
|
+
|
|
1314
|
+
// Inbound: verdict fetched for the caller (From) on the phone channel.
|
|
1315
|
+
expect(lastInboundVerdictArgs).toEqual({
|
|
1316
|
+
channelType: "phone",
|
|
1317
|
+
actorExternalId: "+14155551234",
|
|
1318
|
+
});
|
|
1319
|
+
// Verdict threaded into the preflight routeSetup.
|
|
1320
|
+
expect(lastRouteSetupCtx?.verdict).toEqual(mockInboundVerdict);
|
|
1321
|
+
});
|
|
1322
|
+
|
|
1323
|
+
test("media-stream inbound: a blocked/denied member verdict is classified deny in the preflight", async () => {
|
|
1324
|
+
mockConfigObj.services.stt.provider = "openai-whisper" as any;
|
|
1325
|
+
mockInboundVerdict = {
|
|
1326
|
+
channelType: "phone",
|
|
1327
|
+
actorExternalId: "+14155551234",
|
|
1328
|
+
contactId: "contact-1",
|
|
1329
|
+
channelId: "channel-1",
|
|
1330
|
+
status: "blocked",
|
|
1331
|
+
policy: "deny",
|
|
1332
|
+
resolutionFailed: false,
|
|
1333
|
+
};
|
|
1334
|
+
// The real router returns `deny` for a blocked member verdict; the mock
|
|
1335
|
+
// reflects that outcome. deny is supported on media-stream, so the
|
|
1336
|
+
// preflight still emits Stream TwiML (denial spoken at stream start).
|
|
1337
|
+
mockRouteSetupResult = {
|
|
1338
|
+
outcome: {
|
|
1339
|
+
action: "deny",
|
|
1340
|
+
message:
|
|
1341
|
+
"This number is not authorized to reach the assistant right now.",
|
|
1342
|
+
logReason: "Inbound voice ACL: member blocked",
|
|
1343
|
+
},
|
|
1344
|
+
resolved: {
|
|
1345
|
+
assistantId: "self",
|
|
1346
|
+
isInbound: true,
|
|
1347
|
+
otherPartyNumber: "+14155551234",
|
|
1348
|
+
actorTrust: { trustClass: "unknown", memberRecord: null },
|
|
1349
|
+
},
|
|
1350
|
+
};
|
|
1351
|
+
|
|
1352
|
+
const req = makeInboundVoiceRequest({
|
|
1353
|
+
CallSid: "CA_ms_verdict_deny_1",
|
|
1354
|
+
From: "+14155551234",
|
|
1355
|
+
To: "+15550001111",
|
|
1356
|
+
});
|
|
1357
|
+
|
|
1358
|
+
const res = await handleVoiceWebhook(req);
|
|
1359
|
+
expect(res.status).toBe(200);
|
|
1360
|
+
|
|
1361
|
+
// Verdict was threaded into routeSetup, which denied the caller.
|
|
1362
|
+
expect(lastRouteSetupCtx?.verdict).toEqual(mockInboundVerdict);
|
|
1363
|
+
const twiml = await res.text();
|
|
1364
|
+
expect(twiml).toContain("<Stream");
|
|
1365
|
+
expect(twiml).not.toContain("<ConversationRelay");
|
|
1366
|
+
});
|
|
1367
|
+
|
|
1272
1368
|
test("media-stream: floor-denied caller classified deny still produces Stream TwiML (deny handled at stream level)", async () => {
|
|
1273
1369
|
mockConfigObj.services.stt.provider = "openai-whisper" as any;
|
|
1274
1370
|
mockAdmissionPolicy = "guardian_only";
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { describe, expect, test } from "bun:test";
|
|
2
2
|
|
|
3
|
+
import { FileUploadSurfaceDataSchema } from "../api/surfaces.js";
|
|
3
4
|
import type {
|
|
4
5
|
FileUploadSurfaceData,
|
|
5
6
|
UiSurfaceShowFileUpload,
|
|
@@ -103,3 +104,88 @@ describe("UI surface tool registration", () => {
|
|
|
103
104
|
]);
|
|
104
105
|
});
|
|
105
106
|
});
|
|
107
|
+
|
|
108
|
+
// ---------------------------------------------------------------------------
|
|
109
|
+
// FileUploadSurfaceDataSchema coercion
|
|
110
|
+
// ---------------------------------------------------------------------------
|
|
111
|
+
//
|
|
112
|
+
// `acceptedTypes` is contractually a string[], but the model frequently emits a
|
|
113
|
+
// comma-joined string or a bare string. The renderer calls `.join`/`.some` on
|
|
114
|
+
// it, so the schema coerces every shape to the array contract.
|
|
115
|
+
|
|
116
|
+
describe("FileUploadSurfaceDataSchema coercion", () => {
|
|
117
|
+
test("passes a well-formed payload through unchanged", () => {
|
|
118
|
+
expect(
|
|
119
|
+
FileUploadSurfaceDataSchema.parse({
|
|
120
|
+
prompt: "Share the receipt PDF",
|
|
121
|
+
acceptedTypes: ["image/*", "application/pdf"],
|
|
122
|
+
maxFiles: 3,
|
|
123
|
+
}),
|
|
124
|
+
).toEqual({
|
|
125
|
+
prompt: "Share the receipt PDF",
|
|
126
|
+
acceptedTypes: ["image/*", "application/pdf"],
|
|
127
|
+
maxFiles: 3,
|
|
128
|
+
});
|
|
129
|
+
});
|
|
130
|
+
|
|
131
|
+
test("coerces a comma-joined acceptedTypes string into an array", () => {
|
|
132
|
+
expect(
|
|
133
|
+
FileUploadSurfaceDataSchema.parse({
|
|
134
|
+
prompt: "p",
|
|
135
|
+
acceptedTypes: "image/*, application/pdf",
|
|
136
|
+
}).acceptedTypes,
|
|
137
|
+
).toEqual(["image/*", "application/pdf"]);
|
|
138
|
+
});
|
|
139
|
+
|
|
140
|
+
test("wraps a single acceptedTypes string into a one-element array", () => {
|
|
141
|
+
expect(
|
|
142
|
+
FileUploadSurfaceDataSchema.parse({ acceptedTypes: "application/pdf" })
|
|
143
|
+
.acceptedTypes,
|
|
144
|
+
).toEqual(["application/pdf"]);
|
|
145
|
+
});
|
|
146
|
+
|
|
147
|
+
test("the parsed acceptedTypes always supports .join", () => {
|
|
148
|
+
const parsed = FileUploadSurfaceDataSchema.parse({
|
|
149
|
+
acceptedTypes: "image/*,application/pdf",
|
|
150
|
+
});
|
|
151
|
+
// `.join` is the call the renderer makes on `acceptedTypes`.
|
|
152
|
+
expect(parsed.acceptedTypes?.join(",")).toBe("image/*,application/pdf");
|
|
153
|
+
});
|
|
154
|
+
|
|
155
|
+
test("drops blanks and non-string entries from an array", () => {
|
|
156
|
+
expect(
|
|
157
|
+
FileUploadSurfaceDataSchema.parse({
|
|
158
|
+
acceptedTypes: [" application/pdf ", "", null, 42, {}],
|
|
159
|
+
}).acceptedTypes,
|
|
160
|
+
).toEqual(["application/pdf", "42"]);
|
|
161
|
+
});
|
|
162
|
+
|
|
163
|
+
test("treats a non-string, non-array acceptedTypes as absent", () => {
|
|
164
|
+
expect(
|
|
165
|
+
FileUploadSurfaceDataSchema.parse({ acceptedTypes: { pdf: true } })
|
|
166
|
+
.acceptedTypes,
|
|
167
|
+
).toBeUndefined();
|
|
168
|
+
expect(
|
|
169
|
+
FileUploadSurfaceDataSchema.parse({ acceptedTypes: null }).acceptedTypes,
|
|
170
|
+
).toBeUndefined();
|
|
171
|
+
});
|
|
172
|
+
|
|
173
|
+
test("coerces numeric-string maxFiles and drops invalid numbers", () => {
|
|
174
|
+
const parsed = FileUploadSurfaceDataSchema.parse({
|
|
175
|
+
maxFiles: "2",
|
|
176
|
+
maxSizeBytes: "not-a-number",
|
|
177
|
+
});
|
|
178
|
+
expect(parsed.maxFiles).toBe(2);
|
|
179
|
+
expect(parsed.maxSizeBytes).toBeUndefined();
|
|
180
|
+
});
|
|
181
|
+
|
|
182
|
+
test("never rejects a fully malformed payload", () => {
|
|
183
|
+
expect(
|
|
184
|
+
FileUploadSurfaceDataSchema.safeParse({
|
|
185
|
+
prompt: 5,
|
|
186
|
+
acceptedTypes: 99,
|
|
187
|
+
maxFiles: -1,
|
|
188
|
+
}).success,
|
|
189
|
+
).toBe(true);
|
|
190
|
+
});
|
|
191
|
+
});
|
|
@@ -72,6 +72,8 @@ mock.module("../permissions/checker.js", () => ({
|
|
|
72
72
|
}));
|
|
73
73
|
|
|
74
74
|
mock.module("../memory/conversation-crud.js", () => ({
|
|
75
|
+
setConversationProcessingStartedAt: () => {},
|
|
76
|
+
isConversationProcessing: () => false,
|
|
75
77
|
createConversation: (title: string) => ({ id: "conversation-1", title }),
|
|
76
78
|
reserveMessage: mock(async () => ({ id: "msg-reserve" })),
|
|
77
79
|
}));
|
|
@@ -0,0 +1,137 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Cold-cache guardian voice/phone regression.
|
|
3
|
+
*
|
|
4
|
+
* The sync trust resolver (`resolveActorTrust`) reads the IO-free guardian-
|
|
5
|
+
* delivery cache snapshot (`peekCachedGuardianDelivery`) keyed per channel
|
|
6
|
+
* filter. On a cold process only `vellum` is warmed at daemon startup, so for
|
|
7
|
+
* `phone` the snapshot is empty until some read warms that exact channel key.
|
|
8
|
+
* The voice setup path therefore awaits
|
|
9
|
+
* `getGuardianDeliveryFresh({ channelTypes: ["phone"] })` BEFORE the sync
|
|
10
|
+
* resolve so an inbound guardian call classifies as `guardian` rather than
|
|
11
|
+
* misclassifying during a gateway verdict blip. It reads FRESH because gateway-
|
|
12
|
+
* side binding writes don't invalidate the daemon cache: a stale empty snapshot
|
|
13
|
+
* from an earlier setup would otherwise survive the TTL.
|
|
14
|
+
*
|
|
15
|
+
* This test drives the REAL guardian-delivery reader cache (mocking only the
|
|
16
|
+
* gateway `ipcCall`) so the cold→warm transition is exercised end to end.
|
|
17
|
+
*/
|
|
18
|
+
import { beforeEach, describe, expect, mock, test } from "bun:test";
|
|
19
|
+
|
|
20
|
+
const GUARDIAN_PHONE = "+15550100";
|
|
21
|
+
|
|
22
|
+
// Gateway IPC stub: returns the phone guardian delivery. The real reader caches
|
|
23
|
+
// the result under the `phone` key, so a subsequent sync `peek` finds it. When
|
|
24
|
+
// `guardianBound` is false the gateway reports no guardian — simulating the
|
|
25
|
+
// pre-binding state whose empty result the cache would otherwise pin.
|
|
26
|
+
let guardianBound = true;
|
|
27
|
+
let ipcCalls: Array<{ route: string; input: unknown }> = [];
|
|
28
|
+
mock.module("../ipc/gateway-client.js", () => ({
|
|
29
|
+
ipcCall: async (route: string, input: unknown) => {
|
|
30
|
+
ipcCalls.push({ route, input });
|
|
31
|
+
return {
|
|
32
|
+
guardians: guardianBound
|
|
33
|
+
? [
|
|
34
|
+
{
|
|
35
|
+
channelType: "phone",
|
|
36
|
+
contactId: "guardian-contact",
|
|
37
|
+
principalId: "P_GUARDIAN_COLD",
|
|
38
|
+
address: GUARDIAN_PHONE,
|
|
39
|
+
externalChatId: null,
|
|
40
|
+
status: "active",
|
|
41
|
+
},
|
|
42
|
+
]
|
|
43
|
+
: [],
|
|
44
|
+
};
|
|
45
|
+
},
|
|
46
|
+
}));
|
|
47
|
+
|
|
48
|
+
// Member lookup is irrelevant to guardian classification (address match on the
|
|
49
|
+
// cached delivery decides it); return null so the member path is a no-op.
|
|
50
|
+
mock.module("../contacts/contact-store.js", () => ({
|
|
51
|
+
findContactByAddress: () => null,
|
|
52
|
+
}));
|
|
53
|
+
|
|
54
|
+
import {
|
|
55
|
+
__resetGuardianDeliveryCacheForTest,
|
|
56
|
+
getGuardianDelivery,
|
|
57
|
+
getGuardianDeliveryFresh,
|
|
58
|
+
peekCachedGuardianDelivery,
|
|
59
|
+
} from "../contacts/guardian-delivery-reader.js";
|
|
60
|
+
import { resolveActorTrust } from "../runtime/actor-trust-resolver.js";
|
|
61
|
+
|
|
62
|
+
describe("voice path warms the phone guardian cache before sync trust", () => {
|
|
63
|
+
beforeEach(() => {
|
|
64
|
+
__resetGuardianDeliveryCacheForTest();
|
|
65
|
+
ipcCalls = [];
|
|
66
|
+
guardianBound = true;
|
|
67
|
+
});
|
|
68
|
+
|
|
69
|
+
test("cold phone cache: guardian call misclassifies until upstream warm", async () => {
|
|
70
|
+
// Precondition: cold cache for phone — the sync peek would miss.
|
|
71
|
+
expect(
|
|
72
|
+
peekCachedGuardianDelivery({ channelTypes: ["phone"] }),
|
|
73
|
+
).toBeUndefined();
|
|
74
|
+
|
|
75
|
+
// Sync resolve on a cold cache: no guardian snapshot → classified unknown.
|
|
76
|
+
const cold = resolveActorTrust({
|
|
77
|
+
assistantId: "asst-1",
|
|
78
|
+
sourceChannel: "phone",
|
|
79
|
+
conversationExternalId: GUARDIAN_PHONE,
|
|
80
|
+
actorExternalId: GUARDIAN_PHONE,
|
|
81
|
+
});
|
|
82
|
+
expect(cold.trustClass).toBe("unknown");
|
|
83
|
+
|
|
84
|
+
// The voice setup path warms the phone-specific key via the fresh reader.
|
|
85
|
+
await getGuardianDeliveryFresh({ channelTypes: ["phone"] });
|
|
86
|
+
expect(
|
|
87
|
+
ipcCalls.some(
|
|
88
|
+
(c) =>
|
|
89
|
+
c.route === "resolve_guardian_delivery" &&
|
|
90
|
+
JSON.stringify(c.input) ===
|
|
91
|
+
JSON.stringify({ channelTypes: ["phone"] }),
|
|
92
|
+
),
|
|
93
|
+
).toBe(true);
|
|
94
|
+
|
|
95
|
+
// The sync resolve, reading the now-warm snapshot, classifies the caller as
|
|
96
|
+
// the guardian — not misclassified as `unknown`.
|
|
97
|
+
const warm = resolveActorTrust({
|
|
98
|
+
assistantId: "asst-1",
|
|
99
|
+
sourceChannel: "phone",
|
|
100
|
+
conversationExternalId: GUARDIAN_PHONE,
|
|
101
|
+
actorExternalId: GUARDIAN_PHONE,
|
|
102
|
+
});
|
|
103
|
+
expect(warm.trustClass).toBe("guardian");
|
|
104
|
+
});
|
|
105
|
+
|
|
106
|
+
test("fresh warm bypasses a stale empty phone snapshot after a gateway-side binding", async () => {
|
|
107
|
+
// An earlier setup cached an empty phone snapshot (no guardian yet). Gateway-
|
|
108
|
+
// side binding writes don't invalidate the daemon cache, so the entry stays
|
|
109
|
+
// warm-but-stale until the TTL.
|
|
110
|
+
guardianBound = false;
|
|
111
|
+
await getGuardianDelivery({ channelTypes: ["phone"] });
|
|
112
|
+
expect(peekCachedGuardianDelivery({ channelTypes: ["phone"] })).toEqual([]);
|
|
113
|
+
|
|
114
|
+
// Guardian binding now exists gateway-side. A non-force read would still
|
|
115
|
+
// return the pinned empty snapshot; the fresh read bypasses it.
|
|
116
|
+
guardianBound = true;
|
|
117
|
+
expect(await getGuardianDelivery({ channelTypes: ["phone"] })).toEqual([]);
|
|
118
|
+
await getGuardianDeliveryFresh({ channelTypes: ["phone"] });
|
|
119
|
+
|
|
120
|
+
// The sync resolve now reads the refreshed snapshot and classifies guardian.
|
|
121
|
+
const warm = resolveActorTrust({
|
|
122
|
+
assistantId: "asst-1",
|
|
123
|
+
sourceChannel: "phone",
|
|
124
|
+
conversationExternalId: GUARDIAN_PHONE,
|
|
125
|
+
actorExternalId: GUARDIAN_PHONE,
|
|
126
|
+
});
|
|
127
|
+
expect(warm.trustClass).toBe("guardian");
|
|
128
|
+
});
|
|
129
|
+
|
|
130
|
+
test("startup vellum-only warm leaves the phone key cold", async () => {
|
|
131
|
+
// Daemon startup warms only `vellum`; that must not populate the `phone` key.
|
|
132
|
+
await getGuardianDelivery({ channelTypes: ["vellum"] });
|
|
133
|
+
expect(
|
|
134
|
+
peekCachedGuardianDelivery({ channelTypes: ["phone"] }),
|
|
135
|
+
).toBeUndefined();
|
|
136
|
+
});
|
|
137
|
+
});
|