@vellumai/assistant 0.10.0 → 0.10.1-dev.202606240317.ea25efe
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/ARCHITECTURE.md +36 -37
- package/bun.lock +3 -0
- package/docs/workflows.md +12 -7
- package/eslint-rules/cli-no-daemon-internals.js +12 -0
- package/node_modules/@slack/types/LICENSE +23 -0
- package/node_modules/@slack/types/README.md +32 -0
- package/node_modules/@slack/types/dist/block-kit/block-elements.d.ts +953 -0
- package/node_modules/@slack/types/dist/block-kit/block-elements.d.ts.map +1 -0
- package/node_modules/@slack/types/dist/block-kit/block-elements.js +4 -0
- package/node_modules/@slack/types/dist/block-kit/block-elements.js.map +1 -0
- package/node_modules/@slack/types/dist/block-kit/blocks.d.ts +474 -0
- package/node_modules/@slack/types/dist/block-kit/blocks.d.ts.map +1 -0
- package/node_modules/@slack/types/dist/block-kit/blocks.js +3 -0
- package/node_modules/@slack/types/dist/block-kit/blocks.js.map +1 -0
- package/node_modules/@slack/types/dist/block-kit/composition-objects.d.ts +237 -0
- package/node_modules/@slack/types/dist/block-kit/composition-objects.d.ts.map +1 -0
- package/node_modules/@slack/types/dist/block-kit/composition-objects.js +4 -0
- package/node_modules/@slack/types/dist/block-kit/composition-objects.js.map +1 -0
- package/node_modules/@slack/types/dist/block-kit/extensions.d.ts +88 -0
- package/node_modules/@slack/types/dist/block-kit/extensions.d.ts.map +1 -0
- package/node_modules/@slack/types/dist/block-kit/extensions.js +3 -0
- package/node_modules/@slack/types/dist/block-kit/extensions.js.map +1 -0
- package/node_modules/@slack/types/dist/calls.d.ts +26 -0
- package/node_modules/@slack/types/dist/calls.d.ts.map +1 -0
- package/node_modules/@slack/types/dist/calls.js +6 -0
- package/node_modules/@slack/types/dist/calls.js.map +1 -0
- package/node_modules/@slack/types/dist/chunk.d.ts +52 -0
- package/node_modules/@slack/types/dist/chunk.d.ts.map +1 -0
- package/node_modules/@slack/types/dist/chunk.js +3 -0
- package/node_modules/@slack/types/dist/chunk.js.map +1 -0
- package/node_modules/@slack/types/dist/common/bot-profile.d.ts +12 -0
- package/node_modules/@slack/types/dist/common/bot-profile.d.ts.map +1 -0
- package/node_modules/@slack/types/dist/common/bot-profile.js +3 -0
- package/node_modules/@slack/types/dist/common/bot-profile.js.map +1 -0
- package/node_modules/@slack/types/dist/common/status-emoji-display-info.d.ts +6 -0
- package/node_modules/@slack/types/dist/common/status-emoji-display-info.d.ts.map +1 -0
- package/node_modules/@slack/types/dist/common/status-emoji-display-info.js +3 -0
- package/node_modules/@slack/types/dist/common/status-emoji-display-info.js.map +1 -0
- package/node_modules/@slack/types/dist/dialog.d.ts +36 -0
- package/node_modules/@slack/types/dist/dialog.d.ts.map +1 -0
- package/node_modules/@slack/types/dist/dialog.js +3 -0
- package/node_modules/@slack/types/dist/dialog.js.map +1 -0
- package/node_modules/@slack/types/dist/events/app.d.ts +204 -0
- package/node_modules/@slack/types/dist/events/app.d.ts.map +1 -0
- package/node_modules/@slack/types/dist/events/app.js +3 -0
- package/node_modules/@slack/types/dist/events/app.js.map +1 -0
- package/node_modules/@slack/types/dist/events/assistant.d.ts +29 -0
- package/node_modules/@slack/types/dist/events/assistant.d.ts.map +1 -0
- package/node_modules/@slack/types/dist/events/assistant.js +3 -0
- package/node_modules/@slack/types/dist/events/assistant.js.map +1 -0
- package/node_modules/@slack/types/dist/events/call.d.ts +8 -0
- package/node_modules/@slack/types/dist/events/call.d.ts.map +1 -0
- package/node_modules/@slack/types/dist/events/call.js +3 -0
- package/node_modules/@slack/types/dist/events/call.js.map +1 -0
- package/node_modules/@slack/types/dist/events/channel.d.ts +85 -0
- package/node_modules/@slack/types/dist/events/channel.d.ts.map +1 -0
- package/node_modules/@slack/types/dist/events/channel.js +3 -0
- package/node_modules/@slack/types/dist/events/channel.js.map +1 -0
- package/node_modules/@slack/types/dist/events/dnd.d.ts +24 -0
- package/node_modules/@slack/types/dist/events/dnd.d.ts.map +1 -0
- package/node_modules/@slack/types/dist/events/dnd.js +3 -0
- package/node_modules/@slack/types/dist/events/dnd.js.map +1 -0
- package/node_modules/@slack/types/dist/events/email.d.ts +6 -0
- package/node_modules/@slack/types/dist/events/email.d.ts.map +1 -0
- package/node_modules/@slack/types/dist/events/email.js +3 -0
- package/node_modules/@slack/types/dist/events/email.js.map +1 -0
- package/node_modules/@slack/types/dist/events/emoji.d.ts +11 -0
- package/node_modules/@slack/types/dist/events/emoji.d.ts.map +1 -0
- package/node_modules/@slack/types/dist/events/emoji.js +3 -0
- package/node_modules/@slack/types/dist/events/emoji.js.map +1 -0
- package/node_modules/@slack/types/dist/events/entity-details-requested.d.ts +21 -0
- package/node_modules/@slack/types/dist/events/entity-details-requested.d.ts.map +1 -0
- package/node_modules/@slack/types/dist/events/entity-details-requested.js +3 -0
- package/node_modules/@slack/types/dist/events/entity-details-requested.js.map +1 -0
- package/node_modules/@slack/types/dist/events/file.d.ts +60 -0
- package/node_modules/@slack/types/dist/events/file.d.ts.map +1 -0
- package/node_modules/@slack/types/dist/events/file.js +4 -0
- package/node_modules/@slack/types/dist/events/file.js.map +1 -0
- package/node_modules/@slack/types/dist/events/function.d.ts +33 -0
- package/node_modules/@slack/types/dist/events/function.d.ts.map +1 -0
- package/node_modules/@slack/types/dist/events/function.js +3 -0
- package/node_modules/@slack/types/dist/events/function.js.map +1 -0
- package/node_modules/@slack/types/dist/events/grid-migration.d.ts +9 -0
- package/node_modules/@slack/types/dist/events/grid-migration.d.ts.map +1 -0
- package/node_modules/@slack/types/dist/events/grid-migration.js +3 -0
- package/node_modules/@slack/types/dist/events/grid-migration.js.map +1 -0
- package/node_modules/@slack/types/dist/events/group.d.ts +55 -0
- package/node_modules/@slack/types/dist/events/group.d.ts.map +1 -0
- package/node_modules/@slack/types/dist/events/group.js +3 -0
- package/node_modules/@slack/types/dist/events/group.js.map +1 -0
- package/node_modules/@slack/types/dist/events/im.d.ts +26 -0
- package/node_modules/@slack/types/dist/events/im.d.ts.map +1 -0
- package/node_modules/@slack/types/dist/events/im.js +3 -0
- package/node_modules/@slack/types/dist/events/im.js.map +1 -0
- package/node_modules/@slack/types/dist/events/index.d.ts +60 -0
- package/node_modules/@slack/types/dist/events/index.d.ts.map +1 -0
- package/node_modules/@slack/types/dist/events/index.js +43 -0
- package/node_modules/@slack/types/dist/events/index.js.map +1 -0
- package/node_modules/@slack/types/dist/events/invite.d.ts +20 -0
- package/node_modules/@slack/types/dist/events/invite.d.ts.map +1 -0
- package/node_modules/@slack/types/dist/events/invite.js +3 -0
- package/node_modules/@slack/types/dist/events/invite.js.map +1 -0
- package/node_modules/@slack/types/dist/events/link-shared.d.ts +16 -0
- package/node_modules/@slack/types/dist/events/link-shared.d.ts.map +1 -0
- package/node_modules/@slack/types/dist/events/link-shared.js +3 -0
- package/node_modules/@slack/types/dist/events/link-shared.js.map +1 -0
- package/node_modules/@slack/types/dist/events/member.d.ts +19 -0
- package/node_modules/@slack/types/dist/events/member.d.ts.map +1 -0
- package/node_modules/@slack/types/dist/events/member.js +3 -0
- package/node_modules/@slack/types/dist/events/member.js.map +1 -0
- package/node_modules/@slack/types/dist/events/message-metadata.d.ts +38 -0
- package/node_modules/@slack/types/dist/events/message-metadata.d.ts.map +1 -0
- package/node_modules/@slack/types/dist/events/message-metadata.js +3 -0
- package/node_modules/@slack/types/dist/events/message-metadata.js.map +1 -0
- package/node_modules/@slack/types/dist/events/message.d.ts +306 -0
- package/node_modules/@slack/types/dist/events/message.d.ts.map +1 -0
- package/node_modules/@slack/types/dist/events/message.js +3 -0
- package/node_modules/@slack/types/dist/events/message.js.map +1 -0
- package/node_modules/@slack/types/dist/events/pin.d.ts +60 -0
- package/node_modules/@slack/types/dist/events/pin.d.ts.map +1 -0
- package/node_modules/@slack/types/dist/events/pin.js +3 -0
- package/node_modules/@slack/types/dist/events/pin.js.map +1 -0
- package/node_modules/@slack/types/dist/events/reaction.d.ts +23 -0
- package/node_modules/@slack/types/dist/events/reaction.d.ts.map +1 -0
- package/node_modules/@slack/types/dist/events/reaction.js +3 -0
- package/node_modules/@slack/types/dist/events/reaction.js.map +1 -0
- package/node_modules/@slack/types/dist/events/shared-channel.d.ts +134 -0
- package/node_modules/@slack/types/dist/events/shared-channel.d.ts.map +1 -0
- package/node_modules/@slack/types/dist/events/shared-channel.js +3 -0
- package/node_modules/@slack/types/dist/events/shared-channel.js.map +1 -0
- package/node_modules/@slack/types/dist/events/star.d.ts +13 -0
- package/node_modules/@slack/types/dist/events/star.d.ts.map +1 -0
- package/node_modules/@slack/types/dist/events/star.js +3 -0
- package/node_modules/@slack/types/dist/events/star.js.map +1 -0
- package/node_modules/@slack/types/dist/events/steps-from-apps.d.ts +82 -0
- package/node_modules/@slack/types/dist/events/steps-from-apps.d.ts.map +1 -0
- package/node_modules/@slack/types/dist/events/steps-from-apps.js +3 -0
- package/node_modules/@slack/types/dist/events/steps-from-apps.js.map +1 -0
- package/node_modules/@slack/types/dist/events/subteam.d.ts +66 -0
- package/node_modules/@slack/types/dist/events/subteam.d.ts.map +1 -0
- package/node_modules/@slack/types/dist/events/subteam.js +3 -0
- package/node_modules/@slack/types/dist/events/subteam.js.map +1 -0
- package/node_modules/@slack/types/dist/events/team.d.ts +99 -0
- package/node_modules/@slack/types/dist/events/team.d.ts.map +1 -0
- package/node_modules/@slack/types/dist/events/team.js +3 -0
- package/node_modules/@slack/types/dist/events/team.js.map +1 -0
- package/node_modules/@slack/types/dist/events/token.d.ts +8 -0
- package/node_modules/@slack/types/dist/events/token.d.ts.map +1 -0
- package/node_modules/@slack/types/dist/events/token.js +3 -0
- package/node_modules/@slack/types/dist/events/token.js.map +1 -0
- package/node_modules/@slack/types/dist/events/user.d.ts +313 -0
- package/node_modules/@slack/types/dist/events/user.d.ts.map +1 -0
- package/node_modules/@slack/types/dist/events/user.js +3 -0
- package/node_modules/@slack/types/dist/events/user.js.map +1 -0
- package/node_modules/@slack/types/dist/index.d.ts +12 -0
- package/node_modules/@slack/types/dist/index.d.ts.map +1 -0
- package/node_modules/@slack/types/dist/index.js +28 -0
- package/node_modules/@slack/types/dist/index.js.map +1 -0
- package/node_modules/@slack/types/dist/message-attachments.d.ts +171 -0
- package/node_modules/@slack/types/dist/message-attachments.d.ts.map +1 -0
- package/node_modules/@slack/types/dist/message-attachments.js +3 -0
- package/node_modules/@slack/types/dist/message-attachments.js.map +1 -0
- package/node_modules/@slack/types/dist/message-metadata.d.ts +281 -0
- package/node_modules/@slack/types/dist/message-metadata.d.ts.map +1 -0
- package/node_modules/@slack/types/dist/message-metadata.js +27 -0
- package/node_modules/@slack/types/dist/message-metadata.js.map +1 -0
- package/node_modules/@slack/types/dist/views.d.ts +71 -0
- package/node_modules/@slack/types/dist/views.d.ts.map +1 -0
- package/node_modules/@slack/types/dist/views.js +3 -0
- package/node_modules/@slack/types/dist/views.js.map +1 -0
- package/node_modules/@slack/types/package.json +47 -0
- package/node_modules/@vellumai/gateway-client/bun.lock +3 -0
- package/node_modules/@vellumai/gateway-client/package.json +1 -0
- package/node_modules/@vellumai/gateway-client/src/__tests__/contact-read-contracts.test.ts +69 -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 +96 -0
- package/node_modules/@vellumai/gateway-client/src/gateway-ipc-contracts.ts +162 -0
- package/node_modules/@vellumai/gateway-client/src/guardian-delivery-contract.ts +48 -0
- package/node_modules/@vellumai/gateway-client/src/inbound-contract.ts +8 -0
- package/node_modules/@vellumai/gateway-client/src/index.ts +28 -0
- package/node_modules/@vellumai/gateway-client/src/ipc-client.ts +4 -2
- package/node_modules/@vellumai/gateway-client/src/outbound-contract.ts +3 -2
- package/node_modules/@vellumai/gateway-client/src/trust-verdict-contract.ts +95 -0
- package/openapi.yaml +458 -18
- package/package.json +2 -1
- package/scripts/memory-inspect.ts +24 -14
- package/scripts/test.sh +36 -15
- package/src/__tests__/access-request-seed-content-blocks.test.ts +83 -103
- package/src/__tests__/activation-early-marking.test.ts +1 -1
- package/src/__tests__/actor-token-service.test.ts +39 -17
- package/src/__tests__/agent-loop-callsite-precedence.test.ts +1 -40
- package/src/__tests__/agent-loop-compaction-events.test.ts +0 -1
- package/src/__tests__/agent-loop-compaction-strip.test.ts +0 -1
- package/src/__tests__/agent-loop-exit-reason.test.ts +0 -1
- package/src/__tests__/agent-loop-pushes-post-hook-prompt.test.ts +306 -0
- package/src/__tests__/agent-loop-regrowth-guard.test.ts +0 -1
- package/src/__tests__/agent-loop.test.ts +3 -0
- package/src/__tests__/agent-wake-override-profile.test.ts +2 -0
- package/src/__tests__/anthropic-provider.test.ts +210 -9
- package/src/__tests__/app-builder-skill-instructions.test.ts +47 -5
- package/src/__tests__/app-conversation-ids-backfill.test.ts +1 -1
- package/src/__tests__/app-source-watcher.test.ts +30 -10
- package/src/__tests__/approval-cascade.test.ts +6 -0
- package/src/__tests__/approval-interception-trust-gates.test.ts +151 -0
- package/src/__tests__/approval-primitive.test.ts +1 -1
- package/src/__tests__/approval-routes-http.test.ts +1 -1
- package/src/__tests__/assistant-attachments.test.ts +155 -0
- package/src/__tests__/assistant-event-hub-machine-name.test.ts +2 -4
- package/src/__tests__/assistant-events-sse-hardening.test.ts +1 -1
- package/src/__tests__/assistant-events-sse-shed.test.ts +1 -1
- package/src/__tests__/attachment-upload-trusted-source.test.ts +13 -8
- package/src/__tests__/attachments-store.test.ts +1 -1
- package/src/__tests__/audit-log-rotation.test.ts +50 -54
- package/src/__tests__/auth-fallback-events-store.test.ts +1 -1
- package/src/__tests__/auto-analysis-end-to-end.test.ts +9 -14
- package/src/__tests__/background-shell-bash.test.ts +4 -1
- package/src/__tests__/background-shell-host-bash.test.ts +17 -3
- package/src/__tests__/background-workers-disk-pressure.test.ts +1 -0
- package/src/__tests__/call-controller.test.ts +20 -1
- package/src/__tests__/call-conversation-messages.test.ts +1 -1
- package/src/__tests__/call-domain.test.ts +1 -1
- package/src/__tests__/call-pointer-messages.test.ts +3 -4
- package/src/__tests__/call-recovery.test.ts +1 -1
- package/src/__tests__/call-routes-http.test.ts +1 -1
- package/src/__tests__/call-store.test.ts +1 -1
- package/src/__tests__/cancel-resolves-conversation-key.test.ts +1 -1
- package/src/__tests__/canonical-guardian-store.test.ts +24 -1
- package/src/__tests__/card-surface-data.test.ts +60 -0
- package/src/__tests__/channel-approval-routes.test.ts +73 -1119
- package/src/__tests__/channel-delivery-store.test.ts +1 -1
- package/src/__tests__/channel-guardian.test.ts +291 -641
- package/src/__tests__/channel-inbound-disk-pressure.test.ts +1 -2
- package/src/__tests__/channel-retry-sweep.test.ts +1 -1
- package/src/__tests__/compaction-events.test.ts +6 -0
- package/src/__tests__/compaction-trail-store.test.ts +6 -5
- package/src/__tests__/compaction.benchmark.test.ts +0 -1
- package/src/__tests__/compactor-image-manifest-trust.test.ts +1 -1
- package/src/__tests__/config-loader-backfill.test.ts +188 -52
- package/src/__tests__/config-schema.test.ts +35 -0
- package/src/__tests__/confirmation-request-guardian-bridge.test.ts +1 -2
- package/src/__tests__/contact-store-user-file.test.ts +2 -2
- package/src/__tests__/contacts-relay-reads.test.ts +409 -0
- package/src/__tests__/contacts-tools.test.ts +4 -4
- package/src/__tests__/contacts-write.test.ts +1 -2
- package/src/__tests__/context-search-conversations-source.test.ts +1 -1
- package/src/__tests__/context-window-manager-compact-retry.test.ts +6 -2
- package/src/__tests__/context-window-manager-overflow-rung.test.ts +6 -2
- package/src/__tests__/conversation-abort-tool-results.test.ts +6 -0
- package/src/__tests__/conversation-agent-loop-disk-pressure.test.ts +3 -0
- package/src/__tests__/conversation-agent-loop-inference-profile.test.ts +3 -0
- package/src/__tests__/conversation-agent-loop-overflow.test.ts +3 -0
- package/src/__tests__/conversation-agent-loop.test.ts +7 -0
- package/src/__tests__/conversation-attachments.test.ts +2 -5
- package/src/__tests__/conversation-attention-store.test.ts +1 -1
- package/src/__tests__/conversation-attention-telegram.test.ts +1 -2
- package/src/__tests__/conversation-clear-safety.test.ts +1 -1
- package/src/__tests__/conversation-confirmation-signals.test.ts +6 -0
- package/src/__tests__/conversation-crud-inference-profile.test.ts +1 -1
- package/src/__tests__/conversation-delete-schedule-cleanup.test.ts +12 -19
- package/src/__tests__/conversation-disk-view-integration.test.ts +1 -1
- package/src/__tests__/conversation-disk-view.test.ts +1 -1
- package/src/__tests__/conversation-fork-crud.test.ts +10 -8
- package/src/__tests__/conversation-fork-retrospective.test.ts +250 -0
- package/src/__tests__/conversation-fork-route.test.ts +1 -1
- package/src/__tests__/conversation-inference-profile-list.test.ts +1 -1
- package/src/__tests__/conversation-inference-profile-route.test.ts +1 -1
- package/src/__tests__/conversation-init.benchmark.test.ts +1 -1
- package/src/__tests__/conversation-key-store-disk-view.test.ts +1 -1
- package/src/__tests__/conversation-lifecycle.test.ts +117 -0
- package/src/__tests__/conversation-list-source.test.ts +3 -3
- package/src/__tests__/conversation-process-callsite.test.ts +6 -14
- package/src/__tests__/conversation-provider-retry-repair.test.ts +6 -0
- package/src/__tests__/conversation-queue.test.ts +95 -0
- package/src/__tests__/conversation-routes-disk-view.test.ts +1 -1
- package/src/__tests__/conversation-routes-guardian-reply.test.ts +12 -0
- package/src/__tests__/conversation-routes-slash-commands.test.ts +12 -0
- package/src/__tests__/conversation-runtime-assembly.test.ts +115 -12
- package/src/__tests__/conversation-slash-queue.test.ts +6 -0
- package/src/__tests__/conversation-slash-unknown.test.ts +6 -0
- package/src/__tests__/conversation-speed-override.test.ts +6 -0
- package/src/__tests__/conversation-starter-routes.test.ts +5 -5
- package/src/__tests__/conversation-store.test.ts +1 -1
- package/src/__tests__/conversation-surfaces-activation-emit.test.ts +4 -4
- package/src/__tests__/conversation-surfaces-task-progress.test.ts +352 -0
- package/src/__tests__/conversation-sync-tags.test.ts +1 -1
- package/src/__tests__/conversation-tool-setup-attribution.test.ts +47 -0
- package/src/__tests__/conversation-usage.test.ts +1 -1
- package/src/__tests__/conversation-wipe.test.ts +9 -8
- package/src/__tests__/conversation-workspace-cache-state.test.ts +6 -0
- package/src/__tests__/conversation-workspace-injection.test.ts +6 -0
- package/src/__tests__/conversation-workspace-tool-tracking.test.ts +6 -0
- package/src/__tests__/conversations-import-system-filter.test.ts +1 -1
- package/src/__tests__/copy-composer-tc-templates.test.ts +17 -0
- package/src/__tests__/credential-security-invariants.test.ts +0 -1
- package/src/__tests__/db-acp-history.test.ts +2 -2
- package/src/__tests__/db-conversation-fork-lineage-migration.test.ts +5 -7
- package/src/__tests__/db-conversation-inference-profile-migration.test.ts +6 -7
- package/src/__tests__/db-llm-request-log-provider-migration.test.ts +5 -10
- package/src/__tests__/db-migration-rollback.test.ts +129 -39
- package/src/__tests__/db-proxy-transaction.test.ts +1 -1
- package/src/__tests__/db-schedule-syntax-migration.test.ts +0 -11
- package/src/__tests__/db-test-helpers.ts +36 -19
- package/src/__tests__/delete-propagation.test.ts +1 -1
- package/src/__tests__/deterministic-verification-control-plane.test.ts +28 -8
- package/src/__tests__/disk-pressure-guard.test.ts +41 -0
- package/src/__tests__/disk-pressure-tools.test.ts +41 -1
- package/src/__tests__/dm-backfill.test.ts +1 -1
- package/src/__tests__/drop-capability-card-state-migration.test.ts +0 -8
- package/src/__tests__/dynamic-page-surface.test.ts +0 -94
- package/src/__tests__/edit-propagation.test.ts +1 -1
- package/src/__tests__/emit-signal-routing-intent.test.ts +93 -5
- package/src/__tests__/empty-response-hook.test.ts +42 -0
- package/src/__tests__/events-client-registration.test.ts +1 -1
- package/src/__tests__/events-dev-bypass-actor.test.ts +7 -1
- package/src/__tests__/followup-tools.test.ts +1 -1
- package/src/__tests__/gemini-count-tokens.test.ts +70 -0
- package/src/__tests__/guardian-action-sweep.test.ts +9 -2
- package/src/__tests__/guardian-binding-drift-heal.test.ts +76 -11
- package/src/__tests__/guardian-card-withdrawal.test.ts +1 -1
- package/src/__tests__/guardian-decision-primitive-canonical.test.ts +1 -1
- package/src/__tests__/guardian-dispatch.test.ts +96 -2
- package/src/__tests__/guardian-outbound-http.test.ts +20 -12
- package/src/__tests__/guardian-principal-id-roundtrip.test.ts +1 -1
- package/src/__tests__/guardian-routing-invariants.test.ts +2 -4
- package/src/__tests__/guardian-routing-state.test.ts +1 -2
- package/src/__tests__/guardian-verification-voice-binding.test.ts +1 -1
- package/src/__tests__/headless-browser-mode.test.ts +2 -2
- package/src/__tests__/heartbeat-disk-pressure.test.ts +4 -0
- package/src/__tests__/heartbeat-service.test.ts +6 -0
- package/src/__tests__/helpers/channel-test-adapter.ts +92 -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-conversation-lineage.test.ts +1 -1
- package/src/__tests__/http-user-message-parity.test.ts +165 -8
- package/src/__tests__/image-recovery-hook.test.ts +1 -1
- package/src/__tests__/inbound-invite-redemption.test.ts +1 -2
- package/src/__tests__/inbound-trust-verdict.test.ts +254 -0
- package/src/__tests__/inference-profile-reaper.test.ts +1 -1
- package/src/__tests__/inference-profile-session-handler.test.ts +1 -1
- package/src/__tests__/inference-profile-session-ipc.test.ts +1 -1
- package/src/__tests__/injector-chain.test.ts +1 -1
- package/src/__tests__/injector-disk-pressure.test.ts +11 -6
- package/src/__tests__/internal-telemetry-routes.test.ts +1 -1
- package/src/__tests__/invite-redemption-service.test.ts +244 -43
- package/src/__tests__/invite-routes-http.test.ts +35 -186
- package/src/__tests__/invite-service-ipc.test.ts +287 -0
- package/src/__tests__/jobs-store-qdrant-breaker.test.ts +5 -5
- package/src/__tests__/jobs-store-upsert-debounced.test.ts +9 -12
- package/src/__tests__/list-messages-attachments.test.ts +42 -1
- package/src/__tests__/list-messages-client-message-id.test.ts +1 -1
- package/src/__tests__/list-messages-hidden-metadata.test.ts +1 -1
- package/src/__tests__/list-messages-page-latest.test.ts +1 -1
- package/src/__tests__/list-messages-tool-merge.test.ts +1 -1
- package/src/__tests__/llm-context-normalization.test.ts +105 -0
- package/src/__tests__/llm-context-route-provider.test.ts +69 -4
- package/src/__tests__/llm-request-log-agent-loop-exit-reason.test.ts +9 -5
- package/src/__tests__/llm-request-log-call-site.test.ts +6 -6
- package/src/__tests__/llm-request-log-turn-query.test.ts +27 -13
- package/src/__tests__/llm-resolver.test.ts +205 -5
- package/src/__tests__/llm-usage-store.test.ts +65 -1
- package/src/__tests__/log-export-routes.test.ts +1 -1
- package/src/__tests__/log-export-workspace.test.ts +3 -3
- package/src/__tests__/media-stream-server-integration.test.ts +127 -0
- package/src/__tests__/memory-jobs-worker-lanes.test.ts +5 -5
- package/src/__tests__/memory-recall-log-store.test.ts +1 -1
- package/src/__tests__/memory-upsert-concurrency.test.ts +3 -4
- package/src/__tests__/messages-after-tiebreaker.test.ts +1 -1
- package/src/__tests__/migration-import-from-url.test.ts +2 -2
- package/src/__tests__/mtime-cache.test.ts +375 -0
- package/src/__tests__/non-member-access-request.test.ts +190 -19
- package/src/__tests__/notification-broadcaster.test.ts +4 -0
- package/src/__tests__/notification-candidate-guardian-context.test.ts +203 -0
- package/src/__tests__/notification-decision-recipient-context.test.ts +33 -32
- package/src/__tests__/notification-deep-link.test.ts +4 -0
- package/src/__tests__/notification-guardian-path.test.ts +20 -1
- package/src/__tests__/notification-schedule-notify-dedup.test.ts +1 -1
- package/src/__tests__/oauth-provider-profiles.test.ts +1 -1
- package/src/__tests__/oauth-provider-visibility.test.ts +1 -1
- package/src/__tests__/oauth-store.test.ts +1 -1
- package/src/__tests__/pending-interactions-resolved-event.test.ts +7 -4
- package/src/__tests__/persist-unsendable-image-downscale.test.ts +1 -1
- package/src/__tests__/persist-unsendable-image.test.ts +1 -1
- package/src/__tests__/persona-resolver.test.ts +39 -1
- package/src/__tests__/platform-bash-auto-approve.test.ts +1 -1
- package/src/__tests__/playbook-execution.test.ts +1 -1
- package/src/__tests__/playbook-tools.test.ts +1 -1
- package/src/__tests__/plugin-api-model-profiles.test.ts +74 -21
- package/src/__tests__/plugin-bootstrap.test.ts +78 -0
- package/src/__tests__/provider-platform-proxy-integration.test.ts +25 -5
- package/src/__tests__/provider-usage-tracking.test.ts +40 -1
- package/src/__tests__/prune-old-conversations-job.test.ts +1 -1
- package/src/__tests__/reaction-persistence.test.ts +1 -1
- package/src/__tests__/registry.test.ts +3 -0
- package/src/__tests__/relay-server.test.ts +1026 -73
- package/src/__tests__/runtime-attachment-metadata.test.ts +9 -1
- package/src/__tests__/runtime-events-sse-bilingual.test.ts +7 -9
- package/src/__tests__/runtime-events-sse-parity.test.ts +1 -1
- package/src/__tests__/runtime-events-sse-reconnect.test.ts +1 -1
- package/src/__tests__/runtime-events-sse.test.ts +1 -1
- package/src/__tests__/schedule-retry.test.ts +1 -1
- package/src/__tests__/schedule-routes-workflow-validation.test.ts +1 -1
- package/src/__tests__/schedule-routes.test.ts +1 -1
- package/src/__tests__/schedule-store.test.ts +1 -1
- package/src/__tests__/schedule-tools.test.ts +1 -1
- package/src/__tests__/scheduler-disk-pressure.test.ts +1 -1
- package/src/__tests__/scheduler-recurrence.test.ts +1 -1
- package/src/__tests__/scheduler-reuse-conversation.test.ts +1 -1
- package/src/__tests__/scheduler-wake.test.ts +2 -1
- package/src/__tests__/scoped-approval-grants.test.ts +1 -1
- package/src/__tests__/scoped-grant-security-matrix.test.ts +5 -5
- package/src/__tests__/scrub-corrupted-image-attachments.test.ts +0 -8
- package/src/__tests__/secret-ingress-http.test.ts +12 -0
- package/src/__tests__/secret-routes-platform-proxy.test.ts +1 -0
- package/src/__tests__/send-endpoint-busy.test.ts +31 -9
- package/src/__tests__/sequence-store.test.ts +1 -1
- package/src/__tests__/server-history-render.test.ts +40 -1
- package/src/__tests__/settings-routes.test.ts +11 -10
- package/src/__tests__/skill-load-tool.test.ts +72 -0
- package/src/__tests__/skills.test.ts +44 -0
- package/src/__tests__/slack-inbound-verification.test.ts +48 -5
- package/src/__tests__/slack-messaging-token-resolution.test.ts +13 -2
- package/src/__tests__/slack-reaction-canonical-approval.test.ts +1 -1
- package/src/__tests__/sse-actor-principal-guardian-source.test.ts +102 -0
- package/src/__tests__/steer-on-enqueue-question.test.ts +181 -0
- package/src/__tests__/stt-hints.test.ts +44 -13
- package/src/__tests__/subagent-detail.test.ts +27 -0
- package/src/__tests__/subagent-disposal.test.ts +65 -0
- package/src/__tests__/subagent-tool-gate-mode.test.ts +2 -73
- package/src/__tests__/subagent-tools.test.ts +1 -31
- package/src/__tests__/system-prompt.test.ts +1 -1
- package/src/__tests__/system-storage-cleanup-skill.test.ts +56 -0
- package/src/__tests__/task-compiler.test.ts +1 -1
- package/src/__tests__/task-management-tools.test.ts +1 -1
- package/src/__tests__/task-memory-cleanup.test.ts +9 -6
- package/src/__tests__/task-scheduler.test.ts +1 -1
- package/src/__tests__/thread-backfill.test.ts +1 -1
- package/src/__tests__/tool-approval-handler.test.ts +1 -1
- package/src/__tests__/tool-approval-seed-content-blocks.test.ts +2 -0
- package/src/__tests__/tool-executor.test.ts +37 -1
- package/src/__tests__/tool-grant-request-escalation.test.ts +1 -2
- package/src/__tests__/trusted-contact-inline-approval-integration.test.ts +73 -1
- package/src/__tests__/trusted-contact-lifecycle-notifications.test.ts +34 -34
- package/src/__tests__/trusted-contact-multichannel.test.ts +1 -2
- package/src/__tests__/trusted-contact-verification.test.ts +1 -1
- package/src/__tests__/turn-boundary-resolution.test.ts +3 -3
- package/src/__tests__/turn-events-store.test.ts +1 -1
- package/src/__tests__/twilio-routes.test.ts +98 -3
- package/src/__tests__/usage-cache-backfill-migration.test.ts +20 -10
- package/src/__tests__/usage-routes.test.ts +1 -1
- package/src/__tests__/user-plugin-loader.test.ts +34 -29
- package/src/__tests__/verification-control-plane-policy.test.ts +2 -2
- package/src/__tests__/voice-invite-redemption.test.ts +134 -36
- package/src/__tests__/voice-scoped-grant-consumer.test.ts +1 -1
- package/src/__tests__/voice-session-bridge.test.ts +1 -1
- package/src/__tests__/workspace-git-service.test.ts +114 -1
- package/src/__tests__/workspace-heartbeat-service.test.ts +45 -0
- package/src/__tests__/workspace-migration-009-backfill-conversation-disk-view.test.ts +1 -1
- package/src/__tests__/workspace-migration-013-repair-conversation-disk-view.test.ts +1 -1
- package/src/__tests__/workspace-migration-028-recover-conversations-from-disk-view.test.ts +88 -18
- package/src/__tests__/workspace-migration-108-drop-balanced-economy-profile.test.ts +6 -6
- package/src/__tests__/workspace-migration-109-swap-quality-profile-to-glm-5p2.test.ts +281 -0
- package/src/__tests__/workspace-migration-110-flip-balanced-profile-to-together.test.ts +167 -0
- package/src/__tests__/workspace-migrations-runner.test.ts +55 -0
- package/src/__tests__/workspace-tool-loader.test.ts +3 -0
- package/src/a2a/__tests__/e2e-a2a-channel.test.ts +1 -1
- package/src/a2a/__tests__/task-store.test.ts +1 -1
- package/src/acp/__tests__/session-manager-persistence.test.ts +1 -1
- package/src/acp/__tests__/session-manager-resume.test.ts +22 -11
- package/src/acp/__tests__/session-manager-startup.test.ts +1 -1
- package/src/acp/__tests__/session-manager.test.ts +72 -1
- package/src/acp/index.ts +10 -0
- package/src/acp/session-manager.ts +35 -0
- package/src/agent/loop-exclusive-tool.test.ts +150 -0
- package/src/agent/loop.ts +101 -27
- package/src/api/constants/sse-replay.ts +41 -0
- package/src/api/events/ui-surface-show.ts +8 -3
- package/src/api/index.ts +7 -6
- package/src/api/responses/conversation-message.ts +4 -0
- package/src/api/responses/llm-request-log-entry.ts +25 -0
- package/src/api/responses/subagent-detail.ts +17 -0
- package/src/api/surfaces.ts +33 -0
- package/src/approvals/AGENTS.md +1 -2
- package/src/approvals/guardian-decision-primitive.ts +13 -210
- package/src/approvals/guardian-request-resolvers.ts +104 -58
- package/src/background-wake/wake-intent-hooks.test.ts +1 -1
- package/src/calls/__tests__/inbound-trust-reader.test.ts +110 -0
- package/src/calls/__tests__/relay-setup-router.test.ts +349 -65
- package/src/calls/guardian-dispatch.ts +10 -8
- package/src/calls/inbound-trust-reader.ts +56 -0
- package/src/calls/media-stream-server.ts +21 -0
- package/src/calls/relay-server.ts +231 -72
- package/src/calls/relay-setup-router.ts +57 -13
- package/src/calls/relay-verification.ts +7 -7
- package/src/calls/stt-hints.ts +9 -12
- package/src/calls/twilio-routes.ts +13 -3
- package/src/cli/commands/__tests__/cache.test.ts +8 -1
- package/src/cli/commands/cache.ts +194 -181
- package/src/cli/commands/contacts.ts +6 -24
- package/src/cli/commands/db/__tests__/repair.test.ts +15 -6
- package/src/cli/commands/db/__tests__/status.test.ts +7 -3
- package/src/cli/commands/db/status.ts +212 -33
- package/src/cli/commands/mcp.ts +252 -218
- package/src/cli/commands/memory/__tests__/memory-v3.test.ts +6 -1
- package/src/cli/commands/memory/__tests__/worker.test.ts +302 -0
- package/src/cli/commands/memory/index.ts +4 -0
- package/src/cli/commands/memory/memory-retrospective.ts +129 -0
- package/src/cli/commands/memory/memory-v3.ts +176 -4
- package/src/cli/commands/memory/worker.ts +175 -0
- package/src/cli/commands/plugins.ts +343 -14
- package/src/cli/lib/__tests__/install-from-github.test.ts +40 -0
- package/src/cli/lib/__tests__/list-installed-plugins.test.ts +160 -1
- package/src/cli/lib/__tests__/plugin-pin-history.test.ts +162 -0
- package/src/cli/lib/__tests__/toggle-plugin.test.ts +158 -0
- package/src/cli/lib/install-from-github.ts +47 -6
- package/src/cli/lib/list-installed-plugins.ts +179 -1
- package/src/cli/lib/plugin-marketplace.ts +11 -0
- package/src/cli/lib/plugin-pin-history.ts +257 -0
- package/src/cli/lib/toggle-plugin.ts +146 -0
- package/src/config/__tests__/loader-callsite-strip-fallback.test.ts +143 -0
- package/src/config/__tests__/sync-gated-profiles.test.ts +2 -2
- package/src/config/bundled-skills/app-builder/SKILL.md +15 -33
- package/src/config/bundled-skills/app-builder/references/DESIGN_SYSTEM.md +3 -8
- package/src/config/bundled-skills/app-builder/references/INTERACTION_HOOKS.md +64 -37
- package/src/config/bundled-skills/app-builder/references/RESPONSIVE.md +1 -1
- package/src/config/bundled-skills/app-builder/references/WIDGETS.md +14 -72
- package/src/config/bundled-skills/app-builder/references/examples/README.md +1 -2
- package/src/config/bundled-skills/contacts/SKILL.md +7 -12
- package/src/config/bundled-skills/messaging/tools/shared.ts +4 -1
- package/src/config/bundled-skills/system-storage-cleanup/SKILL.md +74 -0
- package/src/config/bundled-skills/workflows/SKILL.md +4 -3
- package/src/config/call-site-defaults.ts +11 -2
- package/src/config/feature-flag-registry.json +0 -8
- package/src/config/llm-resolver.ts +151 -14
- package/src/config/loader.ts +36 -5
- package/src/config/profile-dispatchability.ts +11 -0
- package/src/config/schemas/__tests__/memory-v3.test.ts +1 -0
- package/src/config/schemas/call-site-catalog.ts +7 -0
- package/src/config/schemas/llm.ts +2 -0
- package/src/config/schemas/memory-lifecycle.ts +17 -3
- package/src/config/schemas/memory-v3.ts +7 -0
- package/src/config/schemas/memory.ts +4 -0
- package/src/config/schemas/timeouts.ts +32 -0
- package/src/config/seed-inference-profiles.ts +147 -50
- package/src/config/skills.ts +27 -5
- package/src/config/sync-gated-profiles.ts +13 -1
- package/src/contacts/__tests__/guardian-delivery-reader.test.ts +312 -0
- package/src/contacts/contact-store.ts +21 -0
- package/src/contacts/contacts-write.ts +3 -0
- package/src/contacts/guardian-delivery-reader.ts +223 -0
- package/src/contacts/member-status.ts +9 -0
- package/src/credential-health/credential-health-service.ts +1 -5
- package/src/daemon/__tests__/conversation-tool-setup.test.ts +44 -0
- package/src/daemon/app-source-watcher.ts +31 -18
- package/src/daemon/assistant-attachments.ts +94 -4
- package/src/daemon/conversation-agent-loop-handlers.ts +3 -0
- package/src/daemon/conversation-agent-loop.ts +18 -36
- package/src/daemon/conversation-process.ts +35 -16
- package/src/daemon/conversation-runtime-assembly.ts +91 -66
- package/src/daemon/conversation-surfaces.ts +273 -18
- package/src/daemon/conversation-tool-setup.ts +24 -64
- package/src/daemon/conversation.ts +149 -53
- package/src/daemon/disk-pressure-guard.ts +12 -2
- package/src/daemon/event-loop-watchdog.test.ts +85 -0
- package/src/daemon/event-loop-watchdog.ts +133 -0
- package/src/daemon/external-plugins-bootstrap.ts +26 -80
- package/src/daemon/handlers/__tests__/config-a2a-accept.test.ts +1 -1
- package/src/daemon/handlers/__tests__/config-a2a-complete.test.ts +1 -1
- package/src/daemon/handlers/__tests__/config-a2a-invite.test.ts +1 -1
- package/src/daemon/handlers/__tests__/config-a2a-redeem.test.ts +1 -1
- package/src/daemon/handlers/__tests__/config-a2a.test.ts +1 -1
- package/src/daemon/handlers/config-channels.ts +41 -27
- package/src/daemon/handlers/conversations.ts +84 -0
- package/src/daemon/handlers/shared.ts +7 -0
- package/src/daemon/lifecycle.ts +44 -5
- package/src/daemon/memory-v2-startup.test.ts +72 -0
- package/src/daemon/memory-v2-startup.ts +87 -19
- package/src/daemon/message-types/inbox.ts +0 -6
- package/src/daemon/message-types/messages.ts +0 -4
- package/src/daemon/message-types/surfaces.ts +12 -11
- package/src/daemon/server.ts +0 -4
- package/src/daemon/shutdown-handlers.ts +20 -0
- package/src/daemon/tool-setup-types.ts +7 -5
- package/src/daemon/trust-context.ts +6 -0
- package/src/daemon/wake-conversation-ops.ts +70 -0
- package/src/daemon/workspace-tools-watcher.ts +7 -3
- package/src/documents/document-comments-store.test.ts +1 -1
- package/src/heartbeat/__tests__/heartbeat-run-store.test.ts +1 -1
- package/src/heartbeat/__tests__/heartbeat-service.test.ts +6 -0
- package/src/heartbeat/heartbeat-service.ts +3 -4
- package/src/ipc/__tests__/attachment-ipc.test.ts +1 -1
- package/src/ipc/__tests__/browser-ipc.test.ts +73 -2
- package/src/ipc/__tests__/clients-list-ipc.test.ts +1 -1
- package/src/ipc/__tests__/watcher-ipc.test.ts +59 -39
- package/src/ipc/assistant-server.ts +10 -2
- package/src/ipc/gateway-client.ts +2 -1
- package/src/ipc/routes/__tests__/invite-ipc-routes.test.ts +58 -0
- package/src/ipc/routes/invite-ipc-routes.ts +66 -0
- package/src/live-voice/__tests__/live-voice-archive.test.ts +1 -1
- package/src/memory/__tests__/activation-session-store.test.ts +1 -1
- package/src/memory/__tests__/auto-analysis-guard.test.ts +1 -1
- package/src/memory/__tests__/conversation-group-migration.test.ts +1 -1
- package/src/memory/__tests__/conversation-queries.test.ts +1 -1
- package/src/memory/__tests__/db-async-query.test.ts +1 -1
- package/src/memory/__tests__/db-logs-attach.test.ts +110 -0
- package/src/memory/__tests__/db-maintenance.test.ts +28 -36
- package/src/memory/__tests__/db-memory-attach.test.ts +113 -0
- package/src/memory/__tests__/find-analysis-conversation.test.ts +1 -1
- package/src/memory/__tests__/find-most-recent-retrospective-for.test.ts +1 -1
- package/src/memory/__tests__/fork-message-copy.test.ts +232 -0
- package/src/memory/__tests__/jobs-store-enqueue-gate.test.ts +3 -0
- package/src/memory/__tests__/jobs-worker-v2-graph-trigger-embed.test.ts +5 -5
- package/src/memory/__tests__/jobs-worker-v2-schedule.test.ts +8 -6
- package/src/memory/__tests__/memory-retrospective-job.test.ts +30 -37
- package/src/memory/__tests__/memory-retrospective-startup-cleanup.test.ts +69 -66
- package/src/memory/__tests__/memory-retrospective-state.test.ts +1 -1
- package/src/memory/__tests__/memory-v2-activation-log-store.test.ts +1 -1
- package/src/memory/__tests__/memory-v2-concept-frequency.test.ts +1 -1
- package/src/memory/__tests__/onboarding-events-store.test.ts +1 -1
- package/src/memory/__tests__/prompt-override.test.ts +192 -0
- package/src/memory/__tests__/table-relocation.test.ts +129 -0
- package/src/memory/conversation-crud.ts +461 -152
- package/src/memory/db-async-query.ts +89 -5
- package/src/memory/db-connection.ts +101 -18
- package/src/memory/db-init.ts +409 -234
- package/src/memory/db-maintenance.ts +43 -38
- package/src/memory/db-singleton.ts +45 -19
- package/src/memory/embedding-gemini.test.ts +3 -1
- package/src/memory/embedding-gemini.ts +18 -2
- package/src/memory/fork-message-copy.ts +170 -0
- package/src/memory/graph/__tests__/handle-remember-v2.test.ts +92 -0
- package/src/memory/graph/bootstrap.test.ts +6 -3
- package/src/memory/graph/retriever.test.ts +12 -12
- package/src/memory/graph/store.test.ts +15 -25
- package/src/memory/graph/store.ts +23 -14
- package/src/memory/graph/tool-handlers.ts +34 -5
- package/src/memory/graph/tools.ts +5 -2
- package/src/memory/indexer.ts +21 -9
- package/src/memory/job-handlers/cleanup.ts +10 -3
- package/src/memory/job-handlers/embedding.test.ts +4 -4
- package/src/memory/jobs/__tests__/embed-concept-page.test.ts +4 -4
- package/src/memory/jobs/embed-pkb-file.test.ts +7 -7
- package/src/memory/jobs-store.ts +36 -24
- package/src/memory/llm-request-log-store.ts +51 -19
- package/src/memory/llm-usage-store.ts +79 -21
- package/src/memory/memory-retrospective-job.ts +27 -19
- package/src/memory/memory-retrospective-startup-cleanup.ts +10 -2
- package/src/memory/migrations/{100-core-tables.ts → 000-core-tables.ts} +6 -10
- package/src/memory/migrations/014-backfill-inbox-thread-state.ts +13 -3
- package/src/memory/migrations/104-core-indexes.ts +1 -1
- package/src/memory/migrations/126-backfill-guardian-principal-id.ts +189 -196
- package/src/memory/migrations/127-guardian-principal-id-not-null.ts +98 -105
- package/src/memory/migrations/134-contacts-notes-column.ts +66 -69
- package/src/memory/migrations/135-backfill-contact-interaction-stats.ts +19 -22
- package/src/memory/migrations/136-drop-assistant-id-columns.ts +241 -219
- package/src/memory/migrations/140-backfill-usage-cache-accounting.ts +204 -209
- package/src/memory/migrations/141-rename-verification-table.ts +45 -48
- package/src/memory/migrations/142-rename-verification-session-id-column.ts +16 -23
- package/src/memory/migrations/143-rename-guardian-verification-values.ts +23 -30
- package/src/memory/migrations/144-rename-voice-to-phone.ts +133 -136
- package/src/memory/migrations/145-drop-accounts-table.ts +4 -7
- package/src/memory/migrations/147-migrate-reminders-to-schedules.ts +79 -82
- package/src/memory/migrations/148-drop-reminders-table.ts +3 -6
- package/src/memory/migrations/150-oauth-apps-client-secret-path.ts +71 -78
- package/src/memory/migrations/157-invite-contact-id.ts +73 -76
- package/src/memory/migrations/162-guardian-timestamps-epoch-ms.ts +44 -58
- package/src/memory/migrations/169-rename-gmail-provider-key-to-google.ts +36 -43
- package/src/memory/migrations/174-rename-thread-starters-table.ts +30 -37
- package/src/memory/migrations/176-drop-capability-card-state.ts +17 -22
- package/src/memory/migrations/177-create-trace-events-table.ts +23 -28
- package/src/memory/migrations/180-backfill-inline-attachments-to-disk.ts +36 -43
- package/src/memory/migrations/181-rename-thread-starters-checkpoints.ts +14 -21
- package/src/memory/migrations/191-backfill-audio-attachment-mime-types.ts +17 -24
- package/src/memory/migrations/192-contacts-user-file-column.ts +6 -9
- package/src/memory/migrations/193-add-source-type-columns.ts +33 -36
- package/src/memory/migrations/194-memory-recall-logs.ts +34 -39
- package/src/memory/migrations/196-strip-integration-prefix-from-provider-keys.ts +59 -66
- package/src/memory/migrations/199-guardian-request-enrichment-columns.ts +41 -48
- package/src/memory/migrations/204-rename-memory-graph-type-values.ts +11 -18
- package/src/memory/migrations/206-scrub-corrupted-image-attachments.ts +76 -83
- package/src/memory/migrations/209-strip-thinking-from-consolidated.ts +135 -68
- package/src/memory/migrations/211-memory-recall-logs-query-context.ts +6 -11
- package/src/memory/migrations/212-llm-request-logs-created-at-index.ts +4 -9
- package/src/memory/migrations/217-conversation-host-access.ts +13 -18
- package/src/memory/migrations/220-normalize-user-file-by-principal.ts +86 -93
- package/src/memory/migrations/222-strip-placeholder-sentinels-from-messages.ts +41 -48
- package/src/memory/migrations/230-acp-session-history.ts +23 -28
- package/src/memory/migrations/231-repair-memory-graph-event-dates.ts +58 -62
- package/src/memory/migrations/232-activation-state.ts +11 -16
- package/src/memory/migrations/233-document-conversations.ts +20 -25
- package/src/memory/migrations/234-memory-v2-activation-logs.ts +26 -31
- package/src/memory/migrations/235-slack-compaction-watermark.ts +5 -10
- package/src/memory/migrations/236-tool-invocations-matched-rule-id.ts +6 -11
- package/src/memory/migrations/237-heartbeat-runs.ts +22 -27
- package/src/memory/migrations/239-trace-events-created-at-index.ts +4 -9
- package/src/memory/migrations/242-message-bookmarks.ts +17 -22
- package/src/memory/migrations/245-memory-retrospective-state.ts +8 -13
- package/src/memory/migrations/249-normalize-slack-external-content.ts +37 -41
- package/src/memory/migrations/251-a2a-tasks.ts +27 -32
- package/src/memory/migrations/254-external-conversation-binding-chat-name.ts +12 -17
- package/src/memory/migrations/255-channel-inbound-delivery-attempts.ts +10 -15
- package/src/memory/migrations/256-memory-v2-injection-events.ts +70 -74
- package/src/memory/migrations/259-conversation-cleaned-at.ts +4 -9
- package/src/memory/migrations/260-rename-cleaned-at.ts +11 -16
- package/src/memory/migrations/261-llm-usage-add-raw-usage.ts +3 -8
- package/src/memory/migrations/262-memory-v3-coactivation.ts +21 -26
- package/src/memory/migrations/263-memory-v3-auto-edges.ts +14 -19
- package/src/memory/migrations/270-schedule-description.ts +7 -12
- package/src/memory/migrations/272-acp-session-history-cwd.ts +8 -13
- package/src/memory/migrations/281-memory-retrospective-remembered-log.ts +8 -13
- package/src/memory/migrations/297-move-llm-request-logs-to-logs-db.ts +111 -0
- package/src/memory/migrations/298-move-memory-jobs-to-memory-db.ts +128 -0
- package/src/memory/migrations/299-canonical-guardian-deliveries-conversation-index.ts +19 -0
- package/src/memory/migrations/__tests__/014-backfill-inbox-thread-state.test.ts +108 -0
- package/src/memory/migrations/__tests__/136-drop-assistant-id-columns.test.ts +82 -0
- package/src/memory/migrations/__tests__/209-strip-thinking-from-consolidated.test.ts +224 -0
- package/src/memory/migrations/__tests__/297-move-llm-request-logs.test.ts +180 -0
- package/src/memory/migrations/__tests__/run-migrations.test.ts +333 -7
- package/src/memory/migrations/helpers/relocation.ts +227 -0
- package/src/memory/migrations/registry.ts +63 -0
- package/src/memory/migrations/run-migrations.ts +187 -16
- package/src/memory/migrations/schema-introspection.ts +14 -0
- package/src/memory/migrations/validate-migration-state.ts +50 -145
- package/src/memory/prompt-override.ts +129 -0
- package/src/memory/raw-query.ts +47 -2
- package/src/memory/skill-loaded-events-store.test.ts +1 -1
- package/src/memory/task-memory-cleanup.ts +62 -41
- package/src/memory/tool-executed-events-store.test.ts +1 -1
- package/src/memory/turn-trace-store.test.ts +1 -1
- package/src/memory/v2/__tests__/backfill-jobs.test.ts +16 -15
- package/src/memory/v2/__tests__/cli-command-store.test.ts +25 -0
- package/src/memory/v2/__tests__/harness-compare.test.ts +1 -1
- package/src/memory/v2/__tests__/harness-oracle.test.ts +1 -1
- package/src/memory/v2/__tests__/harness-replay-input.test.ts +1 -1
- package/src/memory/v2/__tests__/skill-store.test.ts +80 -0
- package/src/memory/v2/__tests__/sweep-job.test.ts +2 -2
- 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/v3-eval/__tests__/eval-packets.test.ts +38 -0
- package/src/memory/v3-eval/__tests__/eval-tally.test.ts +139 -0
- package/src/memory/v3-eval/eval-packets.ts +197 -12
- package/src/memory/v3-eval/eval-tally.ts +234 -0
- package/src/memory/worker-control.ts +118 -0
- package/src/memory/worker-process.ts +72 -0
- package/src/messaging/provider.ts +10 -0
- package/src/messaging/providers/gmail/adapter.ts +1 -0
- package/src/messaging/providers/gmail/client.ts +13 -0
- package/src/messaging/providers/index.ts +1 -1
- package/src/messaging/providers/slack/send.test.ts +87 -39
- package/src/messaging/providers/slack/send.ts +84 -105
- package/src/notifications/README.md +9 -5
- package/src/notifications/__tests__/broadcaster.test.ts +16 -8
- package/src/notifications/__tests__/connected-channels.test.ts +114 -0
- package/src/notifications/__tests__/decision-engine.test.ts +78 -9
- package/src/notifications/__tests__/destination-resolver.test.ts +256 -0
- package/src/notifications/__tests__/deterministic-checks.test.ts +43 -1
- package/src/notifications/adapters/slack.ts +12 -10
- package/src/notifications/approval-card-builder.ts +81 -20
- package/src/notifications/approval-card-data.ts +8 -5
- package/src/notifications/broadcaster.ts +8 -1
- package/src/notifications/canonical-delivery-recorder.ts +7 -5
- package/src/notifications/conversation-candidates.ts +24 -59
- package/src/notifications/copy-composer.ts +48 -68
- package/src/notifications/decision-engine.ts +15 -7
- package/src/notifications/destination-resolver.ts +68 -24
- package/src/notifications/deterministic-checks.ts +19 -16
- package/src/notifications/emit-signal.ts +68 -15
- package/src/notifications/trusted-contact-payloads.ts +70 -0
- package/src/oauth/byo-connection.test.ts +9 -0
- package/src/oauth/connection-resolver.test.ts +174 -6
- package/src/oauth/connection-resolver.ts +132 -5
- package/src/oauth/oauth-store.ts +16 -3
- package/src/oauth/scope-utils.ts +39 -0
- package/src/permissions/question-prompter.test.ts +1 -1
- package/src/permissions/question-prompter.ts +7 -4
- package/src/plugin-api/index.ts +9 -4
- package/src/plugin-api/model-profiles.test.ts +123 -0
- package/src/plugin-api/model-profiles.ts +5 -1
- package/src/plugin-api/vision-support.test.ts +173 -0
- package/src/plugin-api/vision-support.ts +113 -0
- package/src/plugins/defaults/advisor/__tests__/consult.test.ts +90 -0
- package/src/plugins/defaults/advisor/__tests__/context-pack-gating.test.ts +106 -0
- package/src/plugins/defaults/advisor/__tests__/context-pack.test.ts +60 -0
- package/src/plugins/defaults/advisor/consult.ts +65 -6
- package/src/plugins/defaults/advisor/context-pack.ts +288 -0
- package/src/plugins/defaults/advisor/steering.ts +14 -2
- package/src/plugins/defaults/advisor/tools/advisor.ts +32 -5
- package/src/plugins/defaults/compaction/window-manager.ts +45 -64
- package/src/plugins/defaults/empty-response/hooks/post-model-call.ts +13 -4
- package/src/plugins/defaults/image-fallback/__tests__/image-fallback.test.ts +441 -0
- package/src/plugins/defaults/image-fallback/hooks/post-tool-use.ts +57 -0
- package/src/plugins/defaults/image-fallback/hooks/user-prompt-submit.ts +61 -0
- package/src/plugins/defaults/image-fallback/package.json +14 -0
- package/src/plugins/defaults/image-fallback/src/caption-blocks.ts +108 -0
- package/src/plugins/defaults/image-fallback/src/caption-cache.ts +49 -0
- package/src/plugins/defaults/image-fallback/src/image-persist.ts +56 -0
- package/src/plugins/defaults/image-fallback/src/vision-caption.ts +120 -0
- package/src/plugins/defaults/index.ts +27 -0
- package/src/plugins/defaults/memory-retrieval/hooks/user-prompt-submit.ts +14 -1
- package/src/plugins/defaults/memory-retrieval/injectors.ts +4 -4
- package/src/plugins/defaults/memory-v3-shadow/__tests__/pool-select.test.ts +134 -5
- 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 +246 -19
- package/src/plugins/defaults/memory-v3-shadow/shadow-plugin.ts +8 -1
- package/src/plugins/external-plugin-loader.ts +47 -6
- package/src/plugins/mtime-cache.ts +772 -0
- package/src/plugins/pipeline.ts +7 -2
- package/src/plugins/registry.ts +16 -5
- package/src/plugins/user-loader.ts +22 -76
- package/src/prompts/persona-resolver.ts +29 -11
- package/src/prompts/system-prompt.ts +1 -1
- package/src/prompts/templates/system-sections.ts +4 -4
- package/src/providers/__tests__/count-tokens-forwarding.test.ts +98 -0
- package/src/providers/anthropic/client.ts +290 -185
- package/src/providers/call-site-routing.ts +14 -0
- package/src/providers/gemini/client.ts +43 -0
- package/src/providers/inference/adapter-factory.ts +6 -0
- package/src/providers/inference/connections.ts +6 -1
- package/src/providers/model-catalog.ts +53 -0
- package/src/providers/openai/responses-provider.ts +5 -0
- package/src/providers/openrouter/client.ts +5 -0
- package/src/providers/platform-proxy/constants.ts +5 -0
- package/src/providers/provider-send-message.ts +4 -0
- package/src/providers/ratelimit.ts +13 -0
- package/src/providers/retry.ts +14 -0
- package/src/providers/together/client.ts +35 -0
- package/src/providers/types.ts +25 -0
- package/src/providers/usage-tracking.ts +11 -0
- package/src/runtime/AGENTS.md +9 -1
- package/src/runtime/__tests__/agent-wake.test.ts +259 -4
- package/src/runtime/__tests__/guardian-vellum-migration.test.ts +181 -0
- package/src/runtime/__tests__/is-guardian-bound-for-channel.test.ts +64 -0
- package/src/runtime/__tests__/local-principal-trust.test.ts +164 -0
- package/src/runtime/__tests__/slack-block-formatting.test.ts +39 -10
- package/src/runtime/__tests__/trust-verdict-consumer.test.ts +670 -0
- package/src/runtime/access-request-helper.ts +19 -39
- package/src/runtime/actor-trust-resolver.ts +8 -16
- package/src/runtime/agent-wake.ts +183 -60
- package/src/runtime/anchored-guardian.test.ts +156 -0
- package/src/runtime/anchored-guardian.ts +135 -0
- 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-reply-delivery.ts +6 -3
- package/src/runtime/channel-verification-service.ts +24 -0
- package/src/runtime/guardian-decision-types.ts +3 -22
- package/src/runtime/guardian-vellum-migration.ts +66 -7
- package/src/runtime/http-server.ts +1 -15
- package/src/runtime/invite-redemption-service.ts +155 -6
- package/src/runtime/invite-service.ts +113 -62
- package/src/runtime/local-actor-identity.ts +76 -11
- package/src/runtime/local-principal-trust.ts +52 -0
- package/src/runtime/migrations/__tests__/vbundle-builder-fd-leak.test.ts +3 -0
- package/src/runtime/pending-interactions.ts +11 -1
- package/src/runtime/routes/__tests__/acp-routes.test.ts +1 -1
- package/src/runtime/routes/__tests__/bookmark-routes.test.ts +1 -1
- package/src/runtime/routes/__tests__/channel-verification-revoke.test.ts +277 -0
- package/src/runtime/routes/__tests__/channel-verification-routes.test.ts +140 -0
- package/src/runtime/routes/__tests__/connection-routes-vs-cli-parity.test.ts +26 -7
- package/src/runtime/routes/__tests__/consolidation-routes.test.ts +14 -10
- package/src/runtime/routes/__tests__/contact-routes-update-channel-relay.test.ts +164 -0
- package/src/runtime/routes/__tests__/conversation-list-routes.test.ts +1 -1
- package/src/runtime/routes/__tests__/conversation-management-routes.test.ts +1 -1
- package/src/runtime/routes/__tests__/conversation-query-routes.test.ts +8 -8
- package/src/runtime/routes/__tests__/conversation-surface-routes.test.ts +1 -1
- package/src/runtime/routes/__tests__/inference-provider-connection-routes.test.ts +1 -3
- package/src/runtime/routes/__tests__/invite-relay-routes.test.ts +240 -0
- package/src/runtime/routes/__tests__/memory-v2-simulate-route.test.ts +4 -0
- package/src/runtime/routes/__tests__/plugins-routes.test.ts +143 -0
- package/src/runtime/routes/__tests__/retrospective-routes.test.ts +1 -1
- package/src/runtime/routes/__tests__/slack-channel-routes.test.ts +1 -1
- package/src/runtime/routes/__tests__/surface-action-routes.test.ts +163 -0
- package/src/runtime/routes/acp-routes-list.test.ts +4 -0
- package/src/runtime/routes/acp-routes.test.ts +5 -6
- package/src/runtime/routes/attachment-routes.ts +21 -17
- package/src/runtime/routes/browser-routes.ts +19 -1
- package/src/runtime/routes/canonical-guardian-expiry-sweep.ts +5 -9
- package/src/runtime/routes/channel-verification-routes.ts +13 -2
- package/src/runtime/routes/contact-routes.ts +275 -164
- package/src/runtime/routes/conversation-query-routes.ts +15 -5
- package/src/runtime/routes/conversation-routes.ts +80 -66
- package/src/runtime/routes/conversation-starter-routes.ts +7 -8
- package/src/runtime/routes/events-routes.ts +2 -2
- package/src/runtime/routes/guardian-approval-interception.ts +13 -274
- package/src/runtime/routes/host-app-control-routes.ts +5 -4
- package/src/runtime/routes/host-bash-routes.ts +5 -4
- package/src/runtime/routes/host-browser-routes.ts +9 -11
- package/src/runtime/routes/host-cu-routes.ts +5 -4
- package/src/runtime/routes/host-file-routes.ts +5 -4
- package/src/runtime/routes/host-transfer-routes.ts +6 -6
- package/src/runtime/routes/http-adapter.ts +1 -1
- package/src/runtime/routes/inbound-message-handler.ts +21 -16
- package/src/runtime/routes/inbound-stages/acl-enforcement.test.ts +376 -0
- package/src/runtime/routes/inbound-stages/acl-enforcement.ts +86 -64
- package/src/runtime/routes/inbound-stages/admission-policy.ts +20 -5
- package/src/runtime/routes/inbound-stages/background-dispatch.ts +16 -4
- 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/llm-context-normalization.ts +71 -0
- package/src/runtime/routes/log-export-routes.ts +2 -2
- package/src/runtime/routes/mcp-auth-routes.ts +38 -15
- package/src/runtime/routes/memory-eval-routes.ts +92 -0
- package/src/runtime/routes/memory-item-routes.test.ts +12 -11
- package/src/runtime/routes/migration-routes.ts +51 -40
- package/src/runtime/routes/plugins-routes.ts +164 -8
- package/src/runtime/routes/schedule-routes.ts +1 -0
- package/src/runtime/routes/subagents-routes.ts +5 -0
- package/src/runtime/routes/surface-action-routes.ts +39 -51
- package/src/runtime/routes/usage-routes.ts +3 -0
- package/src/runtime/routes/work-items-routes.test.ts +1 -1
- package/src/runtime/slack-block-formatting.ts +46 -48
- package/src/runtime/trust-verdict-consumer.ts +210 -0
- package/src/schedule/scheduler.ts +6 -9
- package/src/signals/user-message.ts +16 -0
- package/src/subagent/manager.ts +9 -0
- package/src/telemetry/usage-telemetry-reporter.test.ts +1 -1
- package/src/tools/ask-question/ask-question-tool.test.ts +89 -52
- package/src/tools/ask-question/ask-question-tool.ts +27 -73
- package/src/tools/browser/__tests__/browser-status.test.ts +20 -0
- package/src/tools/browser/browser-execution.ts +16 -4
- package/src/tools/document/document-comment-tool.test.ts +1 -1
- package/src/tools/executor.ts +15 -3
- package/src/tools/host-terminal/host-shell.ts +28 -9
- package/src/tools/memory/register.test.ts +32 -0
- package/src/tools/skills/load.ts +43 -2
- package/src/tools/subagent/spawn.ts +4 -10
- package/src/tools/terminal/shell.ts +16 -5
- package/src/tools/tool-defaults.ts +2 -0
- package/src/tools/types.ts +18 -2
- package/src/tools/ui-surface/definitions.ts +0 -43
- package/src/util/fs-watcher-error.ts +36 -0
- package/src/util/log-redact.ts +2 -4
- package/src/util/logs-db-path.ts +22 -0
- package/src/util/memory-db-path.ts +23 -0
- package/src/util/platform.ts +5 -0
- package/src/watcher/providers/gmail.ts +7 -2
- package/src/workflows/engine-integration.test.ts +1 -1
- package/src/workflows/engine.test.ts +1 -1
- package/src/workflows/engine.ts +22 -0
- package/src/workflows/fanout-load.test.ts +1 -1
- package/src/workflows/journal-store.test.ts +1 -1
- package/src/workflows/leaf-runner.test.ts +40 -1
- package/src/workflows/leaf-runner.ts +26 -1
- package/src/workspace/git-service.ts +144 -29
- package/src/workspace/migrations/109-swap-quality-profile-to-glm-5p2.ts +121 -0
- package/src/workspace/migrations/110-flip-balanced-profile-to-together.ts +82 -0
- package/src/workspace/migrations/registry.ts +4 -0
- package/src/workspace/migrations/runner.ts +32 -2
- package/src/__tests__/access-request-decision.test.ts +0 -375
- package/src/__tests__/guardian-grant-minting.test.ts +0 -607
- package/src/__tests__/plugin-source-watcher.test.ts +0 -302
- package/src/api/events/turn-profile-auto-routed.ts +0 -28
- package/src/daemon/__tests__/switch-inference-profile-tool.test.ts +0 -107
- package/src/daemon/plugin-source-watcher.ts +0 -278
- package/src/daemon/switch-inference-profile-tool.ts +0 -62
- package/src/memory/guardian-approvals.ts +0 -361
- package/src/memory/migrations/010-ext-conv-bindings-channel-chat-unique.ts +0 -66
- package/src/memory/migrations/038-actor-token-records.ts +0 -45
- package/src/memory/migrations/039-actor-refresh-token-records.ts +0 -57
- package/src/memory/migrations/103-complex-migrations.ts +0 -23
- package/src/memory/migrations/113-late-migrations.ts +0 -30
- package/src/memory/migrations/index.ts +0 -301
- package/src/runtime/routes/access-request-decision.ts +0 -297
- package/src/runtime/routes/approval-strategies/guardian-callback-strategy.ts +0 -963
- package/src/runtime/routes/channel-guardian-routes.ts +0 -19
- package/src/runtime/routes/guardian-expiry-sweep.ts +0 -132
|
@@ -26,10 +26,6 @@ import {
|
|
|
26
26
|
test,
|
|
27
27
|
} from "bun:test";
|
|
28
28
|
|
|
29
|
-
import { ADMISSION_FLOOR } from "@vellumai/gateway-client";
|
|
30
|
-
|
|
31
|
-
import { TRUST_CLASS_RANK } from "../runtime/actor-trust-resolver.js";
|
|
32
|
-
|
|
33
29
|
// ── Platform + logger mocks (must come before any source imports) ────
|
|
34
30
|
|
|
35
31
|
// eslint-disable-next-line @typescript-eslint/no-require-imports
|
|
@@ -42,6 +38,28 @@ mock.module("../util/logger.js", () => ({
|
|
|
42
38
|
}),
|
|
43
39
|
}));
|
|
44
40
|
|
|
41
|
+
// The voice redemption path now claims the gateway-canonical row over IPC
|
|
42
|
+
// before mutating. Stub it so tests don't attempt a real socket connect; the
|
|
43
|
+
// claim returns consumed (updated:true) so redemption proceeds.
|
|
44
|
+
//
|
|
45
|
+
// `inviteClaimCalls` counts gateway claims so concurrency tests can assert that
|
|
46
|
+
// a repeated code does NOT launch a second claim. `inviteClaimGate`, when set,
|
|
47
|
+
// holds the claim open mid-flight so a test can drive the race window where a
|
|
48
|
+
// second code arrives while the first redemption is still awaiting the gateway.
|
|
49
|
+
let inviteClaimCalls = 0;
|
|
50
|
+
let inviteClaimGate: Promise<void> | null = null;
|
|
51
|
+
mock.module("../ipc/gateway-client.js", () => ({
|
|
52
|
+
ipcCall: async () => undefined,
|
|
53
|
+
ipcCallPersistent: async (method: string) => {
|
|
54
|
+
if (method === "record_invite_redemption") {
|
|
55
|
+
inviteClaimCalls += 1;
|
|
56
|
+
if (inviteClaimGate) await inviteClaimGate;
|
|
57
|
+
return { ok: true, updated: true, mirrored: true };
|
|
58
|
+
}
|
|
59
|
+
return undefined;
|
|
60
|
+
},
|
|
61
|
+
}));
|
|
62
|
+
|
|
45
63
|
// ── Identity helpers mock ─────────────────────────────────────────────
|
|
46
64
|
|
|
47
65
|
let mockAssistantName: string | null = "Vellum";
|
|
@@ -69,6 +87,19 @@ mock.module("../prompts/user-reference.js", () => ({
|
|
|
69
87
|
},
|
|
70
88
|
}));
|
|
71
89
|
|
|
90
|
+
// ── Guardian delivery reader mock ───────────────────────────────────
|
|
91
|
+
//
|
|
92
|
+
// resolveGuardianLabel primes its displayName from the gateway binding via
|
|
93
|
+
// getGuardianDelivery at setup. Tests drive the binding through this list.
|
|
94
|
+
|
|
95
|
+
// Tests set this to drive the guardian binding directly. When null (the
|
|
96
|
+
// default), the guardian-delivery-reader mock below derives the binding from
|
|
97
|
+
// the DB-seeded createGuardianBinding setup. Single mock registration lives
|
|
98
|
+
// below since `mock.module` is process-global and last-write-wins in Bun.
|
|
99
|
+
let mockGuardianDeliveryList:
|
|
100
|
+
| Array<{ channelType: string; status: string; displayName: string | null }>
|
|
101
|
+
| null = null;
|
|
102
|
+
|
|
72
103
|
// ── Config mock ─────────────────────────────────────────────────────
|
|
73
104
|
|
|
74
105
|
const mockConfig = {
|
|
@@ -145,37 +176,89 @@ mock.module("../calls/channel-admission-reader.js", () => ({
|
|
|
145
176
|
},
|
|
146
177
|
}));
|
|
147
178
|
|
|
148
|
-
//
|
|
149
|
-
//
|
|
150
|
-
//
|
|
151
|
-
//
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
179
|
+
// ── Inbound trust verdict reader mock ───────────────────────────────
|
|
180
|
+
//
|
|
181
|
+
// Mid-call re-resolution (post verification/activation) prefers the gateway
|
|
182
|
+
// verdict via getInboundTrustVerdict, falling back to local resolution on a
|
|
183
|
+
// missing/failed/unusable verdict. Tests drive the verdict through
|
|
184
|
+
// `mockMidCallVerdict`; null (the default) exercises the local fallback. As
|
|
185
|
+
// with the admission reader, delegate to the real module for sibling files
|
|
186
|
+
// that load later in the same worker.
|
|
187
|
+
let mockMidCallVerdict:
|
|
188
|
+
| import("@vellumai/gateway-client").TrustVerdict
|
|
189
|
+
| null = null;
|
|
190
|
+
// When set, the mid-call verdict reader blocks on this gate before returning,
|
|
191
|
+
// simulating a slow gateway round-trip so a test can drive a prompt into the
|
|
192
|
+
// re-resolution await window.
|
|
193
|
+
let mockMidCallVerdictGate: Promise<void> | null = null;
|
|
194
|
+
const realInboundTrustReaderModule = {
|
|
195
|
+
...(await import("../calls/inbound-trust-reader.js")),
|
|
196
|
+
};
|
|
197
|
+
let inboundTrustMockActive = false;
|
|
198
|
+
mock.module("../calls/inbound-trust-reader.js", () => ({
|
|
199
|
+
...realInboundTrustReaderModule,
|
|
200
|
+
getInboundTrustVerdict: async (input: {
|
|
201
|
+
channelType: import("../channels/types.js").ChannelId;
|
|
202
|
+
actorExternalId?: string;
|
|
159
203
|
}) => {
|
|
160
|
-
if (
|
|
161
|
-
return
|
|
162
|
-
admitted: false,
|
|
163
|
-
reason:
|
|
164
|
-
input.memberStatus === "blocked" ? "member_blocked" : "member_revoked",
|
|
165
|
-
shouldChallenge: false,
|
|
166
|
-
effectivePolicy: input.policy,
|
|
167
|
-
};
|
|
204
|
+
if (!inboundTrustMockActive) {
|
|
205
|
+
return realInboundTrustReaderModule.getInboundTrustVerdict(input);
|
|
168
206
|
}
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
207
|
+
if (mockMidCallVerdictGate) await mockMidCallVerdictGate;
|
|
208
|
+
return mockMidCallVerdict;
|
|
209
|
+
},
|
|
210
|
+
}));
|
|
211
|
+
|
|
212
|
+
// ── Guardian delivery reader ────────────────────────────────────────
|
|
213
|
+
//
|
|
214
|
+
// Guardian identity now resolves via the gateway delivery reader. Derive the
|
|
215
|
+
// list from the DB-seeded guardian bindings so the existing createGuardianBinding
|
|
216
|
+
// setup keeps driving guardian resolution without per-test changes.
|
|
217
|
+
const realContactStoreModule = {
|
|
218
|
+
...(await import("../contacts/contact-store.js")),
|
|
219
|
+
};
|
|
220
|
+
mock.module("../contacts/guardian-delivery-reader.js", () => ({
|
|
221
|
+
getGuardianDelivery: async () => {
|
|
222
|
+
// Tests that set mockGuardianDeliveryList drive the binding directly;
|
|
223
|
+
// otherwise derive from the DB-seeded createGuardianBinding bindings.
|
|
224
|
+
if (mockGuardianDeliveryList) return mockGuardianDeliveryList;
|
|
225
|
+
const guardians = realContactStoreModule.listGuardianChannels();
|
|
226
|
+
if (!guardians) return [];
|
|
227
|
+
return guardians.channels.map((ch) => ({
|
|
228
|
+
channelType: ch.type,
|
|
229
|
+
contactId: guardians.contact.id,
|
|
230
|
+
principalId: guardians.contact.principalId ?? null,
|
|
231
|
+
displayName: guardians.contact.displayName ?? null,
|
|
232
|
+
address: ch.address,
|
|
233
|
+
externalChatId: ch.externalChatId ?? null,
|
|
234
|
+
status: ch.status,
|
|
235
|
+
verifiedAt: ch.verifiedAt ?? null,
|
|
236
|
+
}));
|
|
237
|
+
},
|
|
238
|
+
guardianForChannel: (
|
|
239
|
+
list: Array<{ channelType: string; status: string }>,
|
|
240
|
+
channelType: string,
|
|
241
|
+
) => list.find((g) => g.channelType === channelType && g.status === "active"),
|
|
242
|
+
anyGuardian: (list: unknown[]) => list[0],
|
|
243
|
+
}));
|
|
244
|
+
|
|
245
|
+
// ── Trust verdict consumer spy ──────────────────────────────────────
|
|
246
|
+
//
|
|
247
|
+
// Tracks whether the verdict mapper produced the final mid-call context, so a
|
|
248
|
+
// test can assert the local resolver was used instead (verdict not consumed).
|
|
249
|
+
let trustVerdictMapperUsed = false;
|
|
250
|
+
const realTrustVerdictConsumerModule = {
|
|
251
|
+
...(await import("../runtime/trust-verdict-consumer.js")),
|
|
252
|
+
};
|
|
253
|
+
mock.module("../runtime/trust-verdict-consumer.js", () => ({
|
|
254
|
+
...realTrustVerdictConsumerModule,
|
|
255
|
+
trustContextFromVerdict: (
|
|
256
|
+
...args: Parameters<
|
|
257
|
+
typeof realTrustVerdictConsumerModule.trustContextFromVerdict
|
|
258
|
+
>
|
|
259
|
+
) => {
|
|
260
|
+
trustVerdictMapperUsed = true;
|
|
261
|
+
return realTrustVerdictConsumerModule.trustContextFromVerdict(...args);
|
|
179
262
|
},
|
|
180
263
|
}));
|
|
181
264
|
|
|
@@ -317,17 +400,19 @@ import { generateVoiceCode, hashVoiceCode } from "../util/voice-code.js";
|
|
|
317
400
|
import { resetDbForTesting } from "./db-test-helpers.js";
|
|
318
401
|
import { createGuardianBinding } from "./helpers/create-guardian-binding.js";
|
|
319
402
|
|
|
320
|
-
initializeDb();
|
|
403
|
+
await initializeDb();
|
|
321
404
|
|
|
322
405
|
// Activate the channel-admission-reader stub only while this file's tests run,
|
|
323
406
|
// so the registered (process-global) mock delegates to the real module for
|
|
324
407
|
// sibling files that load later in the same worker.
|
|
325
408
|
beforeAll(() => {
|
|
326
409
|
admissionMockActive = true;
|
|
410
|
+
inboundTrustMockActive = true;
|
|
327
411
|
});
|
|
328
412
|
|
|
329
413
|
afterAll(() => {
|
|
330
414
|
admissionMockActive = false;
|
|
415
|
+
inboundTrustMockActive = false;
|
|
331
416
|
resetDbForTesting();
|
|
332
417
|
});
|
|
333
418
|
|
|
@@ -489,10 +574,16 @@ describe("relay-server", () => {
|
|
|
489
574
|
verifiedVia: "bootstrap",
|
|
490
575
|
});
|
|
491
576
|
activeRelayConnections.clear();
|
|
577
|
+
inviteClaimCalls = 0;
|
|
578
|
+
inviteClaimGate = null;
|
|
492
579
|
mockUserReference = "my human";
|
|
493
580
|
mockAssistantName = "Vellum";
|
|
581
|
+
mockGuardianDeliveryList = null;
|
|
494
582
|
mockAdmissionPolicy = null;
|
|
495
583
|
mockAdmissionGate = null;
|
|
584
|
+
mockMidCallVerdict = null;
|
|
585
|
+
mockMidCallVerdictGate = null;
|
|
586
|
+
trustVerdictMapperUsed = false;
|
|
496
587
|
mockSendMessage.mockImplementation(createMockProviderResponse(["Hello"]));
|
|
497
588
|
mockConfig.calls.verification.enabled = false;
|
|
498
589
|
mockConfig.calls.verification.maxAttempts = 3;
|
|
@@ -2311,8 +2402,9 @@ describe("relay-server", () => {
|
|
|
2311
2402
|
|
|
2312
2403
|
// ── Inbound voice invite redemption ──────────────────────────────────
|
|
2313
2404
|
|
|
2314
|
-
test("inbound voice invite redemption: personalized welcome prompt
|
|
2405
|
+
test("inbound voice invite redemption: personalized welcome prompt uses contact displayName", async () => {
|
|
2315
2406
|
ensureConversation("conv-invite-welcome");
|
|
2407
|
+
mockUserReference = "Bob";
|
|
2316
2408
|
const session = createCallSession({
|
|
2317
2409
|
conversationId: "conv-invite-welcome",
|
|
2318
2410
|
provider: "twilio",
|
|
@@ -2320,18 +2412,16 @@ describe("relay-server", () => {
|
|
|
2320
2412
|
toNumber: "+15551111111",
|
|
2321
2413
|
});
|
|
2322
2414
|
|
|
2323
|
-
//
|
|
2415
|
+
// The invitee's name is read from the bound contact's displayName.
|
|
2324
2416
|
const code = generateVoiceCode(6);
|
|
2325
2417
|
const codeHash = hashVoiceCode(code);
|
|
2326
2418
|
createInvite({
|
|
2327
2419
|
sourceChannel: "phone",
|
|
2328
|
-
contactId: createTargetContact(),
|
|
2420
|
+
contactId: createTargetContact("Alice"),
|
|
2329
2421
|
maxUses: 1,
|
|
2330
2422
|
expectedExternalUserId: "+15558887777",
|
|
2331
2423
|
voiceCodeHash: codeHash,
|
|
2332
2424
|
voiceCodeDigits: 6,
|
|
2333
|
-
friendName: "Alice",
|
|
2334
|
-
guardianName: "Bob",
|
|
2335
2425
|
});
|
|
2336
2426
|
|
|
2337
2427
|
mockSendMessage.mockImplementation(
|
|
@@ -2352,7 +2442,8 @@ describe("relay-server", () => {
|
|
|
2352
2442
|
// Should be in verification-pending state for invite redemption
|
|
2353
2443
|
expect(relay.getConnectionState()).toBe("verification_pending");
|
|
2354
2444
|
|
|
2355
|
-
//
|
|
2445
|
+
// Welcome prompt should use the contact's first-name and the resolved
|
|
2446
|
+
// guardian label (not stale name flags from the invite row).
|
|
2356
2447
|
const textMessages = ws.sentMessages
|
|
2357
2448
|
.map((raw) => JSON.parse(raw) as { type: string; token?: string })
|
|
2358
2449
|
.filter((m) => m.type === "text");
|
|
@@ -2387,6 +2478,7 @@ describe("relay-server", () => {
|
|
|
2387
2478
|
|
|
2388
2479
|
test("inbound voice invite redemption: invalid code gets exact failure copy with guardian name and call ends", async () => {
|
|
2389
2480
|
ensureConversation("conv-invite-fail");
|
|
2481
|
+
mockUserReference = "Dave";
|
|
2390
2482
|
const session = createCallSession({
|
|
2391
2483
|
conversationId: "conv-invite-fail",
|
|
2392
2484
|
provider: "twilio",
|
|
@@ -2394,18 +2486,16 @@ describe("relay-server", () => {
|
|
|
2394
2486
|
toNumber: "+15551111111",
|
|
2395
2487
|
});
|
|
2396
2488
|
|
|
2397
|
-
//
|
|
2489
|
+
// Bound contact's displayName is used; guardian label is resolved at runtime.
|
|
2398
2490
|
const code = generateVoiceCode(6);
|
|
2399
2491
|
const codeHash = hashVoiceCode(code);
|
|
2400
2492
|
createInvite({
|
|
2401
2493
|
sourceChannel: "phone",
|
|
2402
|
-
contactId: createTargetContact(),
|
|
2494
|
+
contactId: createTargetContact("Carol"),
|
|
2403
2495
|
maxUses: 1,
|
|
2404
2496
|
expectedExternalUserId: "+15558886666",
|
|
2405
2497
|
voiceCodeHash: codeHash,
|
|
2406
2498
|
voiceCodeDigits: 6,
|
|
2407
|
-
friendName: "Carol",
|
|
2408
|
-
guardianName: "Dave",
|
|
2409
2499
|
});
|
|
2410
2500
|
|
|
2411
2501
|
const { ws, relay } = createMockWs(session.id);
|
|
@@ -2426,7 +2516,11 @@ describe("relay-server", () => {
|
|
|
2426
2516
|
await relay.handleMessage(JSON.stringify({ type: "dtmf", digit }));
|
|
2427
2517
|
}
|
|
2428
2518
|
|
|
2429
|
-
//
|
|
2519
|
+
// Redemption is dispatched async (it now consults the gateway lifecycle
|
|
2520
|
+
// pre-check over IPC), so flush the microtask/timer queue before asserting.
|
|
2521
|
+
await new Promise((resolve) => setTimeout(resolve, 10));
|
|
2522
|
+
|
|
2523
|
+
// Call should be marked as failed
|
|
2430
2524
|
const updated = getCallSession(session.id);
|
|
2431
2525
|
expect(updated).not.toBeNull();
|
|
2432
2526
|
expect(updated!.status).toBe("failed");
|
|
@@ -2466,6 +2560,184 @@ describe("relay-server", () => {
|
|
|
2466
2560
|
relay.destroy();
|
|
2467
2561
|
});
|
|
2468
2562
|
|
|
2563
|
+
test("inbound voice invite redemption: repeated code during in-flight claim is deduped (no second redemption, no spurious failure)", async () => {
|
|
2564
|
+
ensureConversation("conv-invite-dedup");
|
|
2565
|
+
const session = createCallSession({
|
|
2566
|
+
conversationId: "conv-invite-dedup",
|
|
2567
|
+
provider: "twilio",
|
|
2568
|
+
fromNumber: "+15558885555",
|
|
2569
|
+
toNumber: "+15551111111",
|
|
2570
|
+
});
|
|
2571
|
+
|
|
2572
|
+
const code = generateVoiceCode(6);
|
|
2573
|
+
const codeHash = hashVoiceCode(code);
|
|
2574
|
+
createInvite({
|
|
2575
|
+
sourceChannel: "phone",
|
|
2576
|
+
contactId: createTargetContact("Eve"),
|
|
2577
|
+
maxUses: 1,
|
|
2578
|
+
expectedExternalUserId: "+15558885555",
|
|
2579
|
+
voiceCodeHash: codeHash,
|
|
2580
|
+
voiceCodeDigits: 6,
|
|
2581
|
+
});
|
|
2582
|
+
|
|
2583
|
+
mockSendMessage.mockImplementation(
|
|
2584
|
+
createMockProviderResponse(["Hello, how can I help?"]),
|
|
2585
|
+
);
|
|
2586
|
+
|
|
2587
|
+
const { ws, relay } = createMockWs(session.id);
|
|
2588
|
+
|
|
2589
|
+
await relay.handleMessage(
|
|
2590
|
+
JSON.stringify({
|
|
2591
|
+
type: "setup",
|
|
2592
|
+
callSid: "CA_invite_dedup",
|
|
2593
|
+
from: "+15558885555",
|
|
2594
|
+
to: "+15551111111",
|
|
2595
|
+
}),
|
|
2596
|
+
);
|
|
2597
|
+
|
|
2598
|
+
expect(relay.getConnectionState()).toBe("verification_pending");
|
|
2599
|
+
|
|
2600
|
+
// Hold the gateway claim open so the first redemption stays in flight while
|
|
2601
|
+
// the caller re-speaks the SAME code (the race this guard prevents).
|
|
2602
|
+
let releaseClaim: () => void = () => {};
|
|
2603
|
+
inviteClaimGate = new Promise<void>((resolve) => {
|
|
2604
|
+
releaseClaim = resolve;
|
|
2605
|
+
});
|
|
2606
|
+
|
|
2607
|
+
// First attempt: enter the full code via DTMF — dispatched fire-and-forget.
|
|
2608
|
+
for (const digit of code) {
|
|
2609
|
+
await relay.handleMessage(JSON.stringify({ type: "dtmf", digit }));
|
|
2610
|
+
}
|
|
2611
|
+
// Let the async handler reach the awaited gateway claim.
|
|
2612
|
+
await new Promise((resolve) => setTimeout(resolve, 0));
|
|
2613
|
+
expect(inviteClaimCalls).toBe(1);
|
|
2614
|
+
|
|
2615
|
+
// Second attempt with the SAME code arrives while the first is in flight.
|
|
2616
|
+
// It must be ignored — no second claim, no failure branch.
|
|
2617
|
+
for (const digit of code) {
|
|
2618
|
+
await relay.handleMessage(JSON.stringify({ type: "dtmf", digit }));
|
|
2619
|
+
}
|
|
2620
|
+
await new Promise((resolve) => setTimeout(resolve, 0));
|
|
2621
|
+
expect(inviteClaimCalls).toBe(1);
|
|
2622
|
+
|
|
2623
|
+
// Now let the first redemption resolve.
|
|
2624
|
+
releaseClaim();
|
|
2625
|
+
await new Promise((resolve) => setTimeout(resolve, 10));
|
|
2626
|
+
|
|
2627
|
+
// Exactly one gateway claim ran across both attempts.
|
|
2628
|
+
expect(inviteClaimCalls).toBe(1);
|
|
2629
|
+
|
|
2630
|
+
// Activation succeeded — call continued, never marked failed.
|
|
2631
|
+
expect(relay.getConnectionState()).toBe("connected");
|
|
2632
|
+
const updated = getCallSession(session.id);
|
|
2633
|
+
expect(updated).not.toBeNull();
|
|
2634
|
+
expect(updated!.status).not.toBe("failed");
|
|
2635
|
+
|
|
2636
|
+
const events = getCallEvents(session.id);
|
|
2637
|
+
expect(
|
|
2638
|
+
events.some((e) => e.eventType === "invite_redemption_succeeded"),
|
|
2639
|
+
).toBe(true);
|
|
2640
|
+
expect(events.some((e) => e.eventType === "invite_redemption_failed")).toBe(
|
|
2641
|
+
false,
|
|
2642
|
+
);
|
|
2643
|
+
|
|
2644
|
+
// No failure copy was sent for the deduped second attempt.
|
|
2645
|
+
const failureCopy = ws.sentMessages
|
|
2646
|
+
.map((raw) => JSON.parse(raw) as { type: string; token?: string })
|
|
2647
|
+
.filter(
|
|
2648
|
+
(m) =>
|
|
2649
|
+
m.type === "text" &&
|
|
2650
|
+
(m.token ?? "").includes("incorrect or has since expired"),
|
|
2651
|
+
);
|
|
2652
|
+
expect(failureCopy.length).toBe(0);
|
|
2653
|
+
|
|
2654
|
+
relay.destroy();
|
|
2655
|
+
});
|
|
2656
|
+
|
|
2657
|
+
test("inbound voice invite redemption: a fresh redemption after a prior attempt fully resolves still proceeds (guard cleared, no deadlock)", async () => {
|
|
2658
|
+
// Prior attempt: a fully-resolved redemption on its own call.
|
|
2659
|
+
ensureConversation("conv-invite-prior");
|
|
2660
|
+
const priorSession = createCallSession({
|
|
2661
|
+
conversationId: "conv-invite-prior",
|
|
2662
|
+
provider: "twilio",
|
|
2663
|
+
fromNumber: "+15558883333",
|
|
2664
|
+
toNumber: "+15551111111",
|
|
2665
|
+
});
|
|
2666
|
+
const priorCode = generateVoiceCode(6);
|
|
2667
|
+
createInvite({
|
|
2668
|
+
sourceChannel: "phone",
|
|
2669
|
+
contactId: createTargetContact("Ivan"),
|
|
2670
|
+
maxUses: 1,
|
|
2671
|
+
expectedExternalUserId: "+15558883333",
|
|
2672
|
+
voiceCodeHash: hashVoiceCode(priorCode),
|
|
2673
|
+
voiceCodeDigits: 6,
|
|
2674
|
+
});
|
|
2675
|
+
|
|
2676
|
+
mockSendMessage.mockImplementation(
|
|
2677
|
+
createMockProviderResponse(["Hello, how can I help?"]),
|
|
2678
|
+
);
|
|
2679
|
+
|
|
2680
|
+
const prior = createMockWs(priorSession.id);
|
|
2681
|
+
await prior.relay.handleMessage(
|
|
2682
|
+
JSON.stringify({
|
|
2683
|
+
type: "setup",
|
|
2684
|
+
callSid: "CA_invite_prior",
|
|
2685
|
+
from: "+15558883333",
|
|
2686
|
+
to: "+15551111111",
|
|
2687
|
+
}),
|
|
2688
|
+
);
|
|
2689
|
+
for (const digit of priorCode) {
|
|
2690
|
+
await prior.relay.handleMessage(
|
|
2691
|
+
JSON.stringify({ type: "dtmf", digit }),
|
|
2692
|
+
);
|
|
2693
|
+
}
|
|
2694
|
+
await new Promise((resolve) => setTimeout(resolve, 10));
|
|
2695
|
+
expect(prior.relay.getConnectionState()).toBe("connected");
|
|
2696
|
+
expect(inviteClaimCalls).toBe(1);
|
|
2697
|
+
prior.relay.destroy();
|
|
2698
|
+
|
|
2699
|
+
// Fresh attempt: a brand-new redemption proceeds (the in-flight guard from
|
|
2700
|
+
// the resolved attempt does not block it — it was cleared in finally).
|
|
2701
|
+
ensureConversation("conv-invite-fresh");
|
|
2702
|
+
const freshSession = createCallSession({
|
|
2703
|
+
conversationId: "conv-invite-fresh",
|
|
2704
|
+
provider: "twilio",
|
|
2705
|
+
fromNumber: "+15558882222",
|
|
2706
|
+
toNumber: "+15551111111",
|
|
2707
|
+
});
|
|
2708
|
+
const freshCode = generateVoiceCode(6);
|
|
2709
|
+
createInvite({
|
|
2710
|
+
sourceChannel: "phone",
|
|
2711
|
+
contactId: createTargetContact("Kim"),
|
|
2712
|
+
maxUses: 1,
|
|
2713
|
+
expectedExternalUserId: "+15558882222",
|
|
2714
|
+
voiceCodeHash: hashVoiceCode(freshCode),
|
|
2715
|
+
voiceCodeDigits: 6,
|
|
2716
|
+
});
|
|
2717
|
+
|
|
2718
|
+
const fresh = createMockWs(freshSession.id);
|
|
2719
|
+
await fresh.relay.handleMessage(
|
|
2720
|
+
JSON.stringify({
|
|
2721
|
+
type: "setup",
|
|
2722
|
+
callSid: "CA_invite_fresh",
|
|
2723
|
+
from: "+15558882222",
|
|
2724
|
+
to: "+15551111111",
|
|
2725
|
+
}),
|
|
2726
|
+
);
|
|
2727
|
+
for (const digit of freshCode) {
|
|
2728
|
+
await fresh.relay.handleMessage(
|
|
2729
|
+
JSON.stringify({ type: "dtmf", digit }),
|
|
2730
|
+
);
|
|
2731
|
+
}
|
|
2732
|
+
await new Promise((resolve) => setTimeout(resolve, 10));
|
|
2733
|
+
|
|
2734
|
+
// A second claim ran for the fresh attempt — guard did not deadlock it.
|
|
2735
|
+
expect(inviteClaimCalls).toBe(2);
|
|
2736
|
+
expect(fresh.relay.getConnectionState()).toBe("connected");
|
|
2737
|
+
|
|
2738
|
+
fresh.relay.destroy();
|
|
2739
|
+
});
|
|
2740
|
+
|
|
2469
2741
|
test("inbound voice: unknown caller with no active invite enters name capture flow", async () => {
|
|
2470
2742
|
ensureConversation("conv-invite-no-invite");
|
|
2471
2743
|
const session = createCallSession({
|
|
@@ -4519,7 +4791,7 @@ describe("relay-server", () => {
|
|
|
4519
4791
|
relay.destroy();
|
|
4520
4792
|
});
|
|
4521
4793
|
|
|
4522
|
-
test("invite redemption success:
|
|
4794
|
+
test("invite redemption success: greeting uses bound contact's displayName first-token, not friendName", async () => {
|
|
4523
4795
|
ensureConversation("conv-invite-continue");
|
|
4524
4796
|
const session = createCallSession({
|
|
4525
4797
|
conversationId: "conv-invite-continue",
|
|
@@ -4530,15 +4802,19 @@ describe("relay-server", () => {
|
|
|
4530
4802
|
|
|
4531
4803
|
const code = generateVoiceCode(6);
|
|
4532
4804
|
const codeHash = hashVoiceCode(code);
|
|
4805
|
+
// Bound contact's displayName is the source of truth — its first token
|
|
4806
|
+
// (here "Carolina") is what the post-redemption greeting should use.
|
|
4807
|
+
// The legacy friendName column carries an out-of-date free-text label
|
|
4808
|
+
// ("Stale Name") to prove the greeting reads from the contact, not the
|
|
4809
|
+
// legacy column.
|
|
4533
4810
|
createInvite({
|
|
4534
4811
|
sourceChannel: "phone",
|
|
4535
|
-
contactId: createTargetContact(),
|
|
4812
|
+
contactId: createTargetContact("Carolina Flaherty"),
|
|
4536
4813
|
maxUses: 1,
|
|
4537
4814
|
expectedExternalUserId: "+15557776666",
|
|
4538
4815
|
voiceCodeHash: codeHash,
|
|
4539
4816
|
voiceCodeDigits: 6,
|
|
4540
|
-
friendName: "
|
|
4541
|
-
guardianName: "Frank",
|
|
4817
|
+
friendName: "Stale Name",
|
|
4542
4818
|
});
|
|
4543
4819
|
|
|
4544
4820
|
mockSendMessage.mockImplementation(
|
|
@@ -4569,15 +4845,18 @@ describe("relay-server", () => {
|
|
|
4569
4845
|
// Call should remain connected
|
|
4570
4846
|
expect(relay.getConnectionState()).toBe("connected");
|
|
4571
4847
|
|
|
4572
|
-
// Handoff copy should
|
|
4848
|
+
// Handoff copy should use the contact's first-name, not the stale friendName
|
|
4573
4849
|
const textMessages = ws.sentMessages
|
|
4574
4850
|
.map((raw) => JSON.parse(raw) as { type: string; token?: string })
|
|
4575
4851
|
.filter((m) => m.type === "text");
|
|
4576
4852
|
expect(
|
|
4577
4853
|
textMessages.some((m) =>
|
|
4578
|
-
(m.token ?? "").includes("verified that you are
|
|
4854
|
+
(m.token ?? "").includes("verified that you are Carolina"),
|
|
4579
4855
|
),
|
|
4580
4856
|
).toBe(true);
|
|
4857
|
+
expect(
|
|
4858
|
+
textMessages.every((m) => !(m.token ?? "").includes("Stale Name")),
|
|
4859
|
+
).toBe(true);
|
|
4581
4860
|
|
|
4582
4861
|
// No end message — call stays alive
|
|
4583
4862
|
const endMessages = ws.sentMessages
|
|
@@ -4602,6 +4881,7 @@ describe("relay-server", () => {
|
|
|
4602
4881
|
test("outbound invite prompt uses assistant introduction", async () => {
|
|
4603
4882
|
ensureConversation("conv-outbound-invite-origin");
|
|
4604
4883
|
ensureConversation("conv-outbound-invite");
|
|
4884
|
+
mockUserReference = "Hank";
|
|
4605
4885
|
const session = createCallSession({
|
|
4606
4886
|
conversationId: "conv-outbound-invite",
|
|
4607
4887
|
provider: "twilio",
|
|
@@ -4644,20 +4924,141 @@ describe("relay-server", () => {
|
|
|
4644
4924
|
relay.destroy();
|
|
4645
4925
|
});
|
|
4646
4926
|
|
|
4927
|
+
test("invite redemption success: empty contact displayName triggers neutral 'Hi there' greeting", async () => {
|
|
4928
|
+
ensureConversation("conv-invite-blank-name");
|
|
4929
|
+
mockUserReference = "my human";
|
|
4930
|
+
mockAssistantName = "";
|
|
4931
|
+
const session = createCallSession({
|
|
4932
|
+
conversationId: "conv-invite-blank-name",
|
|
4933
|
+
provider: "twilio",
|
|
4934
|
+
fromNumber: "+15557775555",
|
|
4935
|
+
toNumber: "+15551111111",
|
|
4936
|
+
});
|
|
4937
|
+
|
|
4938
|
+
const code = generateVoiceCode(6);
|
|
4939
|
+
const codeHash = hashVoiceCode(code);
|
|
4940
|
+
// displayName is whitespace-only — greeting falls back to "Hi there"
|
|
4941
|
+
// rather than substituting the channel address.
|
|
4942
|
+
createInvite({
|
|
4943
|
+
sourceChannel: "phone",
|
|
4944
|
+
contactId: createTargetContact(" "),
|
|
4945
|
+
maxUses: 1,
|
|
4946
|
+
expectedExternalUserId: "+15557775555",
|
|
4947
|
+
voiceCodeHash: codeHash,
|
|
4948
|
+
voiceCodeDigits: 6,
|
|
4949
|
+
});
|
|
4950
|
+
|
|
4951
|
+
mockSendMessage.mockImplementation(
|
|
4952
|
+
createMockProviderResponse(["I'd be happy to help."]),
|
|
4953
|
+
);
|
|
4954
|
+
|
|
4955
|
+
const { ws, relay } = createMockWs(session.id);
|
|
4956
|
+
|
|
4957
|
+
await relay.handleMessage(
|
|
4958
|
+
JSON.stringify({
|
|
4959
|
+
type: "setup",
|
|
4960
|
+
callSid: "CA_invite_blank_name",
|
|
4961
|
+
from: "+15557775555",
|
|
4962
|
+
to: "+15551111111",
|
|
4963
|
+
}),
|
|
4964
|
+
);
|
|
4965
|
+
|
|
4966
|
+
for (const digit of code) {
|
|
4967
|
+
await relay.handleMessage(JSON.stringify({ type: "dtmf", digit }));
|
|
4968
|
+
}
|
|
4969
|
+
await new Promise((resolve) => setTimeout(resolve, 10));
|
|
4970
|
+
|
|
4971
|
+
expect(relay.getConnectionState()).toBe("connected");
|
|
4972
|
+
|
|
4973
|
+
const textMessages = ws.sentMessages
|
|
4974
|
+
.map((raw) => JSON.parse(raw) as { type: string; token?: string })
|
|
4975
|
+
.filter((m) => m.type === "text");
|
|
4976
|
+
expect(
|
|
4977
|
+
textMessages.some((m) => (m.token ?? "").startsWith("Hi there!")),
|
|
4978
|
+
).toBe(true);
|
|
4979
|
+
expect(
|
|
4980
|
+
textMessages.every(
|
|
4981
|
+
(m) => !(m.token ?? "").includes("+15557775555"),
|
|
4982
|
+
),
|
|
4983
|
+
).toBe(true);
|
|
4984
|
+
|
|
4985
|
+
relay.destroy();
|
|
4986
|
+
});
|
|
4987
|
+
|
|
4988
|
+
test("invite redemption success: empty contact displayName with stale friendName column still greets 'Hi there'", async () => {
|
|
4989
|
+
// Guards the Codex P2 on #35581 (discussion_r3453033493): when a bound
|
|
4990
|
+
// contact's displayName is blank and the invite row carries a stale
|
|
4991
|
+
// free-text friend_name, the greeting must NOT fall back to that stale
|
|
4992
|
+
// label. contact_id is NOT NULL, so every invite is bound — empty
|
|
4993
|
+
// displayName falls through to the neutral "Hi there" copy.
|
|
4994
|
+
ensureConversation("conv-invite-stale-friend");
|
|
4995
|
+
mockUserReference = "my human";
|
|
4996
|
+
mockAssistantName = "";
|
|
4997
|
+
const session = createCallSession({
|
|
4998
|
+
conversationId: "conv-invite-stale-friend",
|
|
4999
|
+
provider: "twilio",
|
|
5000
|
+
fromNumber: "+15557774444",
|
|
5001
|
+
toNumber: "+15551111111",
|
|
5002
|
+
});
|
|
5003
|
+
|
|
5004
|
+
const code = generateVoiceCode(6);
|
|
5005
|
+
const codeHash = hashVoiceCode(code);
|
|
5006
|
+
createInvite({
|
|
5007
|
+
sourceChannel: "phone",
|
|
5008
|
+
contactId: createTargetContact(" "),
|
|
5009
|
+
maxUses: 1,
|
|
5010
|
+
expectedExternalUserId: "+15557774444",
|
|
5011
|
+
voiceCodeHash: codeHash,
|
|
5012
|
+
voiceCodeDigits: 6,
|
|
5013
|
+
friendName: "Stale Legacy Name",
|
|
5014
|
+
});
|
|
5015
|
+
|
|
5016
|
+
mockSendMessage.mockImplementation(
|
|
5017
|
+
createMockProviderResponse(["I'd be happy to help."]),
|
|
5018
|
+
);
|
|
5019
|
+
|
|
5020
|
+
const { ws, relay } = createMockWs(session.id);
|
|
5021
|
+
|
|
5022
|
+
await relay.handleMessage(
|
|
5023
|
+
JSON.stringify({
|
|
5024
|
+
type: "setup",
|
|
5025
|
+
callSid: "CA_invite_stale_friend",
|
|
5026
|
+
from: "+15557774444",
|
|
5027
|
+
to: "+15551111111",
|
|
5028
|
+
}),
|
|
5029
|
+
);
|
|
5030
|
+
|
|
5031
|
+
for (const digit of code) {
|
|
5032
|
+
await relay.handleMessage(JSON.stringify({ type: "dtmf", digit }));
|
|
5033
|
+
}
|
|
5034
|
+
await new Promise((resolve) => setTimeout(resolve, 10));
|
|
5035
|
+
|
|
5036
|
+
expect(relay.getConnectionState()).toBe("connected");
|
|
5037
|
+
|
|
5038
|
+
const textMessages = ws.sentMessages
|
|
5039
|
+
.map((raw) => JSON.parse(raw) as { type: string; token?: string })
|
|
5040
|
+
.filter((m) => m.type === "text");
|
|
5041
|
+
expect(
|
|
5042
|
+
textMessages.some((m) => (m.token ?? "").startsWith("Hi there!")),
|
|
5043
|
+
).toBe(true);
|
|
5044
|
+
expect(
|
|
5045
|
+
textMessages.every(
|
|
5046
|
+
(m) => !(m.token ?? "").includes("Stale Legacy Name"),
|
|
5047
|
+
),
|
|
5048
|
+
).toBe(true);
|
|
5049
|
+
|
|
5050
|
+
relay.destroy();
|
|
5051
|
+
});
|
|
5052
|
+
|
|
4647
5053
|
// ── resolveGuardianLabel resolution priority ─────────────────────────
|
|
4648
5054
|
|
|
4649
5055
|
test("guardian label: guardian persona name takes precedence over Contact.displayName", async () => {
|
|
4650
5056
|
mockUserReference = "Alice";
|
|
4651
5057
|
|
|
4652
|
-
//
|
|
4653
|
-
|
|
4654
|
-
|
|
4655
|
-
|
|
4656
|
-
guardianDeliveryChatId: "+15559990001",
|
|
4657
|
-
guardianPrincipalId: "+15559990001",
|
|
4658
|
-
verifiedVia: "test",
|
|
4659
|
-
metadataJson: JSON.stringify({ displayName: "Bob" }),
|
|
4660
|
-
});
|
|
5058
|
+
// Gateway binding carries a different displayName
|
|
5059
|
+
mockGuardianDeliveryList = [
|
|
5060
|
+
{ channelType: "phone", status: "active", displayName: "Bob" },
|
|
5061
|
+
];
|
|
4661
5062
|
|
|
4662
5063
|
ensureConversation("conv-label-user-md");
|
|
4663
5064
|
const session = createCallSession({
|
|
@@ -4694,15 +5095,10 @@ describe("relay-server", () => {
|
|
|
4694
5095
|
test("guardian label: Contact.displayName used when guardian persona name is empty", async () => {
|
|
4695
5096
|
mockUserReference = "my human";
|
|
4696
5097
|
|
|
4697
|
-
//
|
|
4698
|
-
|
|
4699
|
-
|
|
4700
|
-
|
|
4701
|
-
guardianDeliveryChatId: "+15559990002",
|
|
4702
|
-
guardianPrincipalId: "+15559990002",
|
|
4703
|
-
verifiedVia: "test",
|
|
4704
|
-
metadataJson: JSON.stringify({ displayName: "Charlie" }),
|
|
4705
|
-
});
|
|
5098
|
+
// Gateway binding carries the guardian displayName
|
|
5099
|
+
mockGuardianDeliveryList = [
|
|
5100
|
+
{ channelType: "phone", status: "active", displayName: "Charlie" },
|
|
5101
|
+
];
|
|
4706
5102
|
|
|
4707
5103
|
ensureConversation("conv-label-contact");
|
|
4708
5104
|
const session = createCallSession({
|
|
@@ -4738,10 +5134,8 @@ describe("relay-server", () => {
|
|
|
4738
5134
|
test("guardian label: DEFAULT_USER_REFERENCE used when both guardian persona name and Contact.displayName are empty", async () => {
|
|
4739
5135
|
mockUserReference = "my human";
|
|
4740
5136
|
|
|
4741
|
-
//
|
|
4742
|
-
|
|
4743
|
-
db.run("DELETE FROM contact_channels");
|
|
4744
|
-
db.run("DELETE FROM contacts");
|
|
5137
|
+
// Empty binding list so resolveGuardianLabel falls back to DEFAULT_USER_REFERENCE
|
|
5138
|
+
mockGuardianDeliveryList = [];
|
|
4745
5139
|
|
|
4746
5140
|
ensureConversation("conv-label-default");
|
|
4747
5141
|
const session = createCallSession({
|
|
@@ -5139,4 +5533,563 @@ describe("relay-server", () => {
|
|
|
5139
5533
|
relay.destroy();
|
|
5140
5534
|
});
|
|
5141
5535
|
});
|
|
5536
|
+
|
|
5537
|
+
// ── Mid-call trust re-resolution from the gateway verdict ───────────
|
|
5538
|
+
//
|
|
5539
|
+
// After a verification/activation success the relay re-resolves caller trust.
|
|
5540
|
+
// It prefers the gateway verdict (authoritative right after the gateway
|
|
5541
|
+
// updated the binding) and falls back to local resolution on a missing/
|
|
5542
|
+
// failed/unusable verdict so a blip never drops the call.
|
|
5543
|
+
|
|
5544
|
+
function readControllerTrustClass(relay: RelayConnection): string | undefined {
|
|
5545
|
+
return (
|
|
5546
|
+
relay.getController() as unknown as {
|
|
5547
|
+
trustContext?: { trustClass?: string };
|
|
5548
|
+
}
|
|
5549
|
+
)?.trustContext?.trustClass;
|
|
5550
|
+
}
|
|
5551
|
+
|
|
5552
|
+
test("inbound guardian verification: re-resolves trust from the gateway verdict", async () => {
|
|
5553
|
+
ensureConversation("conv-midcall-verdict-guardian");
|
|
5554
|
+
const session = createCallSession({
|
|
5555
|
+
conversationId: "conv-midcall-verdict-guardian",
|
|
5556
|
+
provider: "twilio",
|
|
5557
|
+
fromNumber: "+15559999999",
|
|
5558
|
+
toNumber: "+15551111111",
|
|
5559
|
+
});
|
|
5560
|
+
|
|
5561
|
+
const secret = createPendingVoiceGuardianChallenge();
|
|
5562
|
+
|
|
5563
|
+
// The gateway verdict upgrades the caller to guardian post-verification.
|
|
5564
|
+
mockMidCallVerdict = {
|
|
5565
|
+
trustClass: "guardian",
|
|
5566
|
+
canonicalSenderId: "+15559999999",
|
|
5567
|
+
guardianExternalUserId: "+15559999999",
|
|
5568
|
+
guardianPrincipalId: "+15559999999",
|
|
5569
|
+
};
|
|
5570
|
+
|
|
5571
|
+
mockSendMessage.mockImplementation(
|
|
5572
|
+
createMockProviderResponse(["Hello, verified guardian!"]),
|
|
5573
|
+
);
|
|
5574
|
+
|
|
5575
|
+
const { relay } = createMockWs(session.id);
|
|
5576
|
+
|
|
5577
|
+
await relay.handleMessage(
|
|
5578
|
+
JSON.stringify({
|
|
5579
|
+
type: "setup",
|
|
5580
|
+
callSid: "CA_midcall_verdict_guardian",
|
|
5581
|
+
from: "+15559999999",
|
|
5582
|
+
to: "+15551111111",
|
|
5583
|
+
}),
|
|
5584
|
+
);
|
|
5585
|
+
|
|
5586
|
+
for (const digit of secret) {
|
|
5587
|
+
await relay.handleMessage(JSON.stringify({ type: "dtmf", digit }));
|
|
5588
|
+
}
|
|
5589
|
+
await new Promise((resolve) => setTimeout(resolve, 10));
|
|
5590
|
+
|
|
5591
|
+
expect(relay.getConnectionState()).toBe("connected");
|
|
5592
|
+
// Controller trust reflects the gateway verdict's upgraded class.
|
|
5593
|
+
expect(readControllerTrustClass(relay)).toBe("guardian");
|
|
5594
|
+
|
|
5595
|
+
relay.destroy();
|
|
5596
|
+
});
|
|
5597
|
+
|
|
5598
|
+
test("inbound guardian verification: resolutionFailed verdict falls back to local resolution without dropping the call", async () => {
|
|
5599
|
+
ensureConversation("conv-midcall-verdict-failed");
|
|
5600
|
+
const session = createCallSession({
|
|
5601
|
+
conversationId: "conv-midcall-verdict-failed",
|
|
5602
|
+
provider: "twilio",
|
|
5603
|
+
fromNumber: "+15559999999",
|
|
5604
|
+
toNumber: "+15551111111",
|
|
5605
|
+
});
|
|
5606
|
+
|
|
5607
|
+
const secret = createPendingVoiceGuardianChallenge();
|
|
5608
|
+
|
|
5609
|
+
// A failed verdict must fall back to local resolution — the call stays up.
|
|
5610
|
+
mockMidCallVerdict = {
|
|
5611
|
+
trustClass: "unknown",
|
|
5612
|
+
canonicalSenderId: null,
|
|
5613
|
+
resolutionFailed: true,
|
|
5614
|
+
};
|
|
5615
|
+
|
|
5616
|
+
mockSendMessage.mockImplementation(
|
|
5617
|
+
createMockProviderResponse(["Hello, how can I help you?"]),
|
|
5618
|
+
);
|
|
5619
|
+
|
|
5620
|
+
const { relay } = createMockWs(session.id);
|
|
5621
|
+
|
|
5622
|
+
await relay.handleMessage(
|
|
5623
|
+
JSON.stringify({
|
|
5624
|
+
type: "setup",
|
|
5625
|
+
callSid: "CA_midcall_verdict_failed",
|
|
5626
|
+
from: "+15559999999",
|
|
5627
|
+
to: "+15551111111",
|
|
5628
|
+
}),
|
|
5629
|
+
);
|
|
5630
|
+
|
|
5631
|
+
for (const digit of secret) {
|
|
5632
|
+
await relay.handleMessage(JSON.stringify({ type: "dtmf", digit }));
|
|
5633
|
+
}
|
|
5634
|
+
await new Promise((resolve) => setTimeout(resolve, 10));
|
|
5635
|
+
|
|
5636
|
+
// Fail-soft: verification still completes and the call connects.
|
|
5637
|
+
expect(relay.isVerificationSessionActive()).toBe(false);
|
|
5638
|
+
expect(relay.getConnectionState()).toBe("connected");
|
|
5639
|
+
expect(readControllerTrustClass(relay)).toBeDefined();
|
|
5640
|
+
|
|
5641
|
+
relay.destroy();
|
|
5642
|
+
});
|
|
5643
|
+
|
|
5644
|
+
test("inbound guardian verification: member-claiming but unusable verdict falls back to local resolution", async () => {
|
|
5645
|
+
ensureConversation("conv-midcall-verdict-unusable");
|
|
5646
|
+
const session = createCallSession({
|
|
5647
|
+
conversationId: "conv-midcall-verdict-unusable",
|
|
5648
|
+
provider: "twilio",
|
|
5649
|
+
fromNumber: "+15559999999",
|
|
5650
|
+
toNumber: "+15551111111",
|
|
5651
|
+
});
|
|
5652
|
+
|
|
5653
|
+
const secret = createPendingVoiceGuardianChallenge();
|
|
5654
|
+
|
|
5655
|
+
// Claims a member (contactId/channelId) but the ACL can't be reassembled
|
|
5656
|
+
// (missing status/policy) — mirrors the setup path's unusable condition.
|
|
5657
|
+
mockMidCallVerdict = {
|
|
5658
|
+
trustClass: "trusted_contact",
|
|
5659
|
+
canonicalSenderId: "+15559999999",
|
|
5660
|
+
contactId: "ct_unusable",
|
|
5661
|
+
channelId: "ch_unusable",
|
|
5662
|
+
};
|
|
5663
|
+
|
|
5664
|
+
mockSendMessage.mockImplementation(
|
|
5665
|
+
createMockProviderResponse(["Hello there."]),
|
|
5666
|
+
);
|
|
5667
|
+
|
|
5668
|
+
const { relay } = createMockWs(session.id);
|
|
5669
|
+
|
|
5670
|
+
await relay.handleMessage(
|
|
5671
|
+
JSON.stringify({
|
|
5672
|
+
type: "setup",
|
|
5673
|
+
callSid: "CA_midcall_verdict_unusable",
|
|
5674
|
+
from: "+15559999999",
|
|
5675
|
+
to: "+15551111111",
|
|
5676
|
+
}),
|
|
5677
|
+
);
|
|
5678
|
+
|
|
5679
|
+
for (const digit of secret) {
|
|
5680
|
+
await relay.handleMessage(JSON.stringify({ type: "dtmf", digit }));
|
|
5681
|
+
}
|
|
5682
|
+
await new Promise((resolve) => setTimeout(resolve, 10));
|
|
5683
|
+
|
|
5684
|
+
// Fail-soft: unusable verdict does not drop the call; local fallback fires.
|
|
5685
|
+
expect(relay.getConnectionState()).toBe("connected");
|
|
5686
|
+
expect(readControllerTrustClass(relay)).toBeDefined();
|
|
5687
|
+
|
|
5688
|
+
relay.destroy();
|
|
5689
|
+
});
|
|
5690
|
+
|
|
5691
|
+
test("inbound guardian verification: memberless unknown verdict falls back to local resolution (just-activated invitee not downgraded)", async () => {
|
|
5692
|
+
ensureConversation("conv-midcall-verdict-unknown");
|
|
5693
|
+
const session = createCallSession({
|
|
5694
|
+
conversationId: "conv-midcall-verdict-unknown",
|
|
5695
|
+
provider: "twilio",
|
|
5696
|
+
fromNumber: "+15559999999",
|
|
5697
|
+
toNumber: "+15551111111",
|
|
5698
|
+
});
|
|
5699
|
+
|
|
5700
|
+
const secret = createPendingVoiceGuardianChallenge();
|
|
5701
|
+
|
|
5702
|
+
// Invite redemption writes the channel assistant-side, so right after
|
|
5703
|
+
// activation the gateway has no member and returns a memberless unknown
|
|
5704
|
+
// verdict. Mid-call re-resolution must treat it as a stale gateway view
|
|
5705
|
+
// and fall back to local resolution (which has the fresh channel) rather
|
|
5706
|
+
// than downgrade the just-activated invitee to unknown.
|
|
5707
|
+
mockMidCallVerdict = {
|
|
5708
|
+
trustClass: "unknown",
|
|
5709
|
+
canonicalSenderId: "+15559999999",
|
|
5710
|
+
};
|
|
5711
|
+
|
|
5712
|
+
mockSendMessage.mockImplementation(
|
|
5713
|
+
createMockProviderResponse(["Hello there."]),
|
|
5714
|
+
);
|
|
5715
|
+
|
|
5716
|
+
const { relay } = createMockWs(session.id);
|
|
5717
|
+
|
|
5718
|
+
await relay.handleMessage(
|
|
5719
|
+
JSON.stringify({
|
|
5720
|
+
type: "setup",
|
|
5721
|
+
callSid: "CA_midcall_verdict_unknown",
|
|
5722
|
+
from: "+15559999999",
|
|
5723
|
+
to: "+15551111111",
|
|
5724
|
+
}),
|
|
5725
|
+
);
|
|
5726
|
+
|
|
5727
|
+
for (const digit of secret) {
|
|
5728
|
+
await relay.handleMessage(JSON.stringify({ type: "dtmf", digit }));
|
|
5729
|
+
}
|
|
5730
|
+
await new Promise((resolve) => setTimeout(resolve, 10));
|
|
5731
|
+
|
|
5732
|
+
expect(relay.getConnectionState()).toBe("connected");
|
|
5733
|
+
// Local resolver produced the final context; the unknown verdict was not
|
|
5734
|
+
// consumed as authoritative.
|
|
5735
|
+
expect(trustVerdictMapperUsed).toBe(false);
|
|
5736
|
+
expect(readControllerTrustClass(relay)).toBeDefined();
|
|
5737
|
+
|
|
5738
|
+
relay.destroy();
|
|
5739
|
+
});
|
|
5740
|
+
|
|
5741
|
+
function readControllerMemberStatus(
|
|
5742
|
+
relay: RelayConnection,
|
|
5743
|
+
): string | undefined {
|
|
5744
|
+
return (
|
|
5745
|
+
relay.getController() as unknown as {
|
|
5746
|
+
trustContext?: { memberStatus?: string };
|
|
5747
|
+
}
|
|
5748
|
+
)?.trustContext?.memberStatus;
|
|
5749
|
+
}
|
|
5750
|
+
|
|
5751
|
+
test("inbound guardian verification: memberful blocked unknown verdict is honored (verdict path enforces blocked status)", async () => {
|
|
5752
|
+
ensureConversation("conv-midcall-verdict-blocked");
|
|
5753
|
+
const session = createCallSession({
|
|
5754
|
+
conversationId: "conv-midcall-verdict-blocked",
|
|
5755
|
+
provider: "twilio",
|
|
5756
|
+
fromNumber: "+15559999999",
|
|
5757
|
+
toNumber: "+15551111111",
|
|
5758
|
+
});
|
|
5759
|
+
|
|
5760
|
+
const secret = createPendingVoiceGuardianChallenge();
|
|
5761
|
+
|
|
5762
|
+
mockSendMessage.mockImplementation(
|
|
5763
|
+
createMockProviderResponse(["Hello there."]),
|
|
5764
|
+
);
|
|
5765
|
+
|
|
5766
|
+
const { relay } = createMockWs(session.id);
|
|
5767
|
+
|
|
5768
|
+
// Setup resolves locally (no verdict) so the pending guardian challenge
|
|
5769
|
+
// drives verification rather than denying at the door.
|
|
5770
|
+
await relay.handleMessage(
|
|
5771
|
+
JSON.stringify({
|
|
5772
|
+
type: "setup",
|
|
5773
|
+
callSid: "CA_midcall_verdict_blocked",
|
|
5774
|
+
from: "+15559999999",
|
|
5775
|
+
to: "+15551111111",
|
|
5776
|
+
}),
|
|
5777
|
+
);
|
|
5778
|
+
|
|
5779
|
+
// The gateway classifies a blocked member as trustClass "unknown" but still
|
|
5780
|
+
// carries contactId/channelId and the deny ACL. This memberful unknown must
|
|
5781
|
+
// take the verdict path on mid-call re-resolution so its blocked status is
|
|
5782
|
+
// enforced — not fall back to local, which could miss a stale block.
|
|
5783
|
+
mockMidCallVerdict = {
|
|
5784
|
+
trustClass: "unknown",
|
|
5785
|
+
canonicalSenderId: "+15559999999",
|
|
5786
|
+
contactId: "ct_blocked",
|
|
5787
|
+
channelId: "ch_blocked",
|
|
5788
|
+
status: "blocked",
|
|
5789
|
+
policy: "deny",
|
|
5790
|
+
};
|
|
5791
|
+
|
|
5792
|
+
for (const digit of secret) {
|
|
5793
|
+
await relay.handleMessage(JSON.stringify({ type: "dtmf", digit }));
|
|
5794
|
+
}
|
|
5795
|
+
await new Promise((resolve) => setTimeout(resolve, 10));
|
|
5796
|
+
|
|
5797
|
+
expect(relay.getConnectionState()).toBe("connected");
|
|
5798
|
+
// Verdict path consumed the memberful unknown verdict; blocked status lands.
|
|
5799
|
+
expect(trustVerdictMapperUsed).toBe(true);
|
|
5800
|
+
expect(readControllerMemberStatus(relay)).toBe("blocked");
|
|
5801
|
+
|
|
5802
|
+
relay.destroy();
|
|
5803
|
+
});
|
|
5804
|
+
|
|
5805
|
+
test("inbound guardian verification: memberful revoked unknown verdict is honored (verdict path enforces revoked status)", async () => {
|
|
5806
|
+
ensureConversation("conv-midcall-verdict-revoked");
|
|
5807
|
+
const session = createCallSession({
|
|
5808
|
+
conversationId: "conv-midcall-verdict-revoked",
|
|
5809
|
+
provider: "twilio",
|
|
5810
|
+
fromNumber: "+15559999999",
|
|
5811
|
+
toNumber: "+15551111111",
|
|
5812
|
+
});
|
|
5813
|
+
|
|
5814
|
+
const secret = createPendingVoiceGuardianChallenge();
|
|
5815
|
+
|
|
5816
|
+
mockSendMessage.mockImplementation(
|
|
5817
|
+
createMockProviderResponse(["Hello there."]),
|
|
5818
|
+
);
|
|
5819
|
+
|
|
5820
|
+
const { relay } = createMockWs(session.id);
|
|
5821
|
+
|
|
5822
|
+
await relay.handleMessage(
|
|
5823
|
+
JSON.stringify({
|
|
5824
|
+
type: "setup",
|
|
5825
|
+
callSid: "CA_midcall_verdict_revoked",
|
|
5826
|
+
from: "+15559999999",
|
|
5827
|
+
to: "+15551111111",
|
|
5828
|
+
}),
|
|
5829
|
+
);
|
|
5830
|
+
|
|
5831
|
+
mockMidCallVerdict = {
|
|
5832
|
+
trustClass: "unknown",
|
|
5833
|
+
canonicalSenderId: "+15559999999",
|
|
5834
|
+
contactId: "ct_revoked",
|
|
5835
|
+
channelId: "ch_revoked",
|
|
5836
|
+
status: "revoked",
|
|
5837
|
+
policy: "deny",
|
|
5838
|
+
};
|
|
5839
|
+
|
|
5840
|
+
for (const digit of secret) {
|
|
5841
|
+
await relay.handleMessage(JSON.stringify({ type: "dtmf", digit }));
|
|
5842
|
+
}
|
|
5843
|
+
await new Promise((resolve) => setTimeout(resolve, 10));
|
|
5844
|
+
|
|
5845
|
+
expect(relay.getConnectionState()).toBe("connected");
|
|
5846
|
+
expect(trustVerdictMapperUsed).toBe(true);
|
|
5847
|
+
expect(readControllerMemberStatus(relay)).toBe("revoked");
|
|
5848
|
+
|
|
5849
|
+
relay.destroy();
|
|
5850
|
+
});
|
|
5851
|
+
|
|
5852
|
+
test("a prompt arriving during the mid-call re-resolution await is deferred and runs under the upgraded trust", async () => {
|
|
5853
|
+
ensureConversation("conv-midcall-race");
|
|
5854
|
+
const session = createCallSession({
|
|
5855
|
+
conversationId: "conv-midcall-race",
|
|
5856
|
+
provider: "twilio",
|
|
5857
|
+
fromNumber: "+15559999999",
|
|
5858
|
+
toNumber: "+15551111111",
|
|
5859
|
+
});
|
|
5860
|
+
|
|
5861
|
+
const secret = createPendingVoiceGuardianChallenge();
|
|
5862
|
+
|
|
5863
|
+
// Capture the controller's trust class at the moment a turn actually fires,
|
|
5864
|
+
// so we can prove the deferred prompt did not run under the stale context.
|
|
5865
|
+
const trustClassAtTurn: Array<string | undefined> = [];
|
|
5866
|
+
mockSendMessage.mockImplementation((...args: unknown[]) => {
|
|
5867
|
+
trustClassAtTurn.push(readControllerTrustClass(relay));
|
|
5868
|
+
return createMockProviderResponse(["Hello, verified guardian!"])(
|
|
5869
|
+
...(args as Parameters<ReturnType<typeof createMockProviderResponse>>),
|
|
5870
|
+
);
|
|
5871
|
+
});
|
|
5872
|
+
|
|
5873
|
+
const { relay } = createMockWs(session.id);
|
|
5874
|
+
|
|
5875
|
+
// Setup resolves locally (no verdict) so the pending challenge drives
|
|
5876
|
+
// verification; the gated guardian verdict is armed only for the mid-call
|
|
5877
|
+
// re-resolution below.
|
|
5878
|
+
await relay.handleMessage(
|
|
5879
|
+
JSON.stringify({
|
|
5880
|
+
type: "setup",
|
|
5881
|
+
callSid: "CA_midcall_race",
|
|
5882
|
+
from: "+15559999999",
|
|
5883
|
+
to: "+15551111111",
|
|
5884
|
+
}),
|
|
5885
|
+
);
|
|
5886
|
+
|
|
5887
|
+
// The gateway verdict upgrades the caller to guardian, but the round-trip is
|
|
5888
|
+
// slow — gate it so a prompt can land in the re-resolution await window.
|
|
5889
|
+
mockMidCallVerdict = {
|
|
5890
|
+
trustClass: "guardian",
|
|
5891
|
+
canonicalSenderId: "+15559999999",
|
|
5892
|
+
guardianExternalUserId: "+15559999999",
|
|
5893
|
+
guardianPrincipalId: "+15559999999",
|
|
5894
|
+
};
|
|
5895
|
+
let releaseVerdict!: () => void;
|
|
5896
|
+
mockMidCallVerdictGate = new Promise<void>((resolve) => {
|
|
5897
|
+
releaseVerdict = resolve;
|
|
5898
|
+
});
|
|
5899
|
+
|
|
5900
|
+
// Enter the gated re-resolution: the final DTMF digit triggers
|
|
5901
|
+
// handleVerificationCodeResult, which awaits the slow verdict.
|
|
5902
|
+
const digits = secret.split("");
|
|
5903
|
+
for (const digit of digits.slice(0, -1)) {
|
|
5904
|
+
await relay.handleMessage(JSON.stringify({ type: "dtmf", digit }));
|
|
5905
|
+
}
|
|
5906
|
+
const verificationDone = relay.handleMessage(
|
|
5907
|
+
JSON.stringify({ type: "dtmf", digit: digits[digits.length - 1] }),
|
|
5908
|
+
);
|
|
5909
|
+
// Let the verdict await begin (connectionState is now "connected", guard set).
|
|
5910
|
+
await new Promise((resolve) => setTimeout(resolve, 0));
|
|
5911
|
+
expect(relay.getConnectionState()).toBe("connected");
|
|
5912
|
+
// Trust is still stale (verdict gated) — caller is not yet guardian.
|
|
5913
|
+
expect(readControllerTrustClass(relay)).not.toBe("guardian");
|
|
5914
|
+
|
|
5915
|
+
// Prompt arrives mid-await: it must be deferred, not processed under stale trust.
|
|
5916
|
+
await relay.handleMessage(
|
|
5917
|
+
JSON.stringify({
|
|
5918
|
+
type: "prompt",
|
|
5919
|
+
voicePrompt: "Are my appointments confirmed?",
|
|
5920
|
+
lang: "en-US",
|
|
5921
|
+
last: true,
|
|
5922
|
+
}),
|
|
5923
|
+
);
|
|
5924
|
+
// No turn yet — the prompt was buffered, not run under the stale context.
|
|
5925
|
+
expect(trustClassAtTurn).toHaveLength(0);
|
|
5926
|
+
|
|
5927
|
+
// Release the verdict; re-resolution installs the upgraded context, then the
|
|
5928
|
+
// deferred prompt is flushed and its turn runs under guardian trust.
|
|
5929
|
+
releaseVerdict();
|
|
5930
|
+
await verificationDone;
|
|
5931
|
+
await new Promise((resolve) => setTimeout(resolve, 20));
|
|
5932
|
+
|
|
5933
|
+
expect(readControllerTrustClass(relay)).toBe("guardian");
|
|
5934
|
+
expect(trustClassAtTurn.length).toBeGreaterThan(0);
|
|
5935
|
+
expect(trustClassAtTurn.every((c) => c === "guardian")).toBe(true);
|
|
5936
|
+
|
|
5937
|
+
relay.destroy();
|
|
5938
|
+
});
|
|
5939
|
+
|
|
5940
|
+
test("invite redemption: a prompt buffered during re-resolution runs as a real turn after activation (not dropped)", async () => {
|
|
5941
|
+
ensureConversation("conv-midcall-invite-flush");
|
|
5942
|
+
const session = createCallSession({
|
|
5943
|
+
conversationId: "conv-midcall-invite-flush",
|
|
5944
|
+
provider: "twilio",
|
|
5945
|
+
fromNumber: "+15558887777",
|
|
5946
|
+
toNumber: "+15551111111",
|
|
5947
|
+
});
|
|
5948
|
+
|
|
5949
|
+
const code = generateVoiceCode(6);
|
|
5950
|
+
createInvite({
|
|
5951
|
+
sourceChannel: "phone",
|
|
5952
|
+
contactId: createTargetContact("Alice"),
|
|
5953
|
+
maxUses: 1,
|
|
5954
|
+
expectedExternalUserId: "+15558887777",
|
|
5955
|
+
voiceCodeHash: hashVoiceCode(code),
|
|
5956
|
+
voiceCodeDigits: 6,
|
|
5957
|
+
});
|
|
5958
|
+
|
|
5959
|
+
const turnCountBefore = mockSendMessage.mock.calls.length;
|
|
5960
|
+
mockSendMessage.mockImplementation(
|
|
5961
|
+
createMockProviderResponse(["Sure, here you go."]),
|
|
5962
|
+
);
|
|
5963
|
+
|
|
5964
|
+
const { relay } = createMockWs(session.id);
|
|
5965
|
+
|
|
5966
|
+
await relay.handleMessage(
|
|
5967
|
+
JSON.stringify({
|
|
5968
|
+
type: "setup",
|
|
5969
|
+
callSid: "CA_midcall_invite_flush",
|
|
5970
|
+
from: "+15558887777",
|
|
5971
|
+
to: "+15551111111",
|
|
5972
|
+
}),
|
|
5973
|
+
);
|
|
5974
|
+
expect(relay.getConnectionState()).toBe("verification_pending");
|
|
5975
|
+
|
|
5976
|
+
// Gate the mid-call verdict so the prompt lands inside the re-resolution
|
|
5977
|
+
// await, after activation flips connectionState to "connected".
|
|
5978
|
+
let releaseVerdict!: () => void;
|
|
5979
|
+
mockMidCallVerdictGate = new Promise<void>((resolve) => {
|
|
5980
|
+
releaseVerdict = resolve;
|
|
5981
|
+
});
|
|
5982
|
+
|
|
5983
|
+
const digits = code.split("");
|
|
5984
|
+
for (const digit of digits.slice(0, -1)) {
|
|
5985
|
+
await relay.handleMessage(JSON.stringify({ type: "dtmf", digit }));
|
|
5986
|
+
}
|
|
5987
|
+
const redemptionDone = relay.handleMessage(
|
|
5988
|
+
JSON.stringify({ type: "dtmf", digit: digits[digits.length - 1] }),
|
|
5989
|
+
);
|
|
5990
|
+
await new Promise((resolve) => setTimeout(resolve, 0));
|
|
5991
|
+
// Activation already reached the terminal state before re-resolution.
|
|
5992
|
+
expect(relay.getConnectionState()).toBe("connected");
|
|
5993
|
+
|
|
5994
|
+
await relay.handleMessage(
|
|
5995
|
+
JSON.stringify({
|
|
5996
|
+
type: "prompt",
|
|
5997
|
+
voicePrompt: "What's on my calendar today?",
|
|
5998
|
+
lang: "en-US",
|
|
5999
|
+
last: true,
|
|
6000
|
+
}),
|
|
6001
|
+
);
|
|
6002
|
+
// Buffered, not yet run (verdict gated).
|
|
6003
|
+
expect(mockSendMessage.mock.calls.length).toBe(turnCountBefore);
|
|
6004
|
+
|
|
6005
|
+
releaseVerdict();
|
|
6006
|
+
await redemptionDone;
|
|
6007
|
+
await new Promise((resolve) => setTimeout(resolve, 20));
|
|
6008
|
+
|
|
6009
|
+
// Flushed onto the real-turn path: the prompt produced an LLM turn rather
|
|
6010
|
+
// than being dropped by the verification-pending branch.
|
|
6011
|
+
expect(mockSendMessage.mock.calls.length).toBeGreaterThan(turnCountBefore);
|
|
6012
|
+
|
|
6013
|
+
relay.destroy();
|
|
6014
|
+
});
|
|
6015
|
+
|
|
6016
|
+
test("access-request approval: a prompt buffered during re-resolution runs as a real turn (not misrouted to wait-state)", async () => {
|
|
6017
|
+
ensureConversation("conv-midcall-access-flush");
|
|
6018
|
+
const session = createCallSession({
|
|
6019
|
+
conversationId: "conv-midcall-access-flush",
|
|
6020
|
+
provider: "twilio",
|
|
6021
|
+
fromNumber: "+15557770003",
|
|
6022
|
+
toNumber: "+15551111111",
|
|
6023
|
+
});
|
|
6024
|
+
|
|
6025
|
+
const turnCountBefore = mockSendMessage.mock.calls.length;
|
|
6026
|
+
mockSendMessage.mockImplementation(
|
|
6027
|
+
createMockProviderResponse(["Sure, here you go."]),
|
|
6028
|
+
);
|
|
6029
|
+
|
|
6030
|
+
const { relay } = createMockWs(session.id);
|
|
6031
|
+
|
|
6032
|
+
await relay.handleMessage(
|
|
6033
|
+
JSON.stringify({
|
|
6034
|
+
type: "setup",
|
|
6035
|
+
callSid: "CA_midcall_access_flush",
|
|
6036
|
+
from: "+15557770003",
|
|
6037
|
+
to: "+15551111111",
|
|
6038
|
+
}),
|
|
6039
|
+
);
|
|
6040
|
+
|
|
6041
|
+
await relay.handleMessage(
|
|
6042
|
+
JSON.stringify({
|
|
6043
|
+
type: "prompt",
|
|
6044
|
+
voicePrompt: "Bob Smith",
|
|
6045
|
+
lang: "en-US",
|
|
6046
|
+
last: true,
|
|
6047
|
+
}),
|
|
6048
|
+
);
|
|
6049
|
+
expect(relay.getConnectionState()).toBe("awaiting_guardian_decision");
|
|
6050
|
+
|
|
6051
|
+
// Gate the mid-call verdict so the prompt lands inside the re-resolution
|
|
6052
|
+
// await triggered by the approval poll.
|
|
6053
|
+
let releaseVerdict!: () => void;
|
|
6054
|
+
mockMidCallVerdictGate = new Promise<void>((resolve) => {
|
|
6055
|
+
releaseVerdict = resolve;
|
|
6056
|
+
});
|
|
6057
|
+
|
|
6058
|
+
const pending = listCanonicalGuardianRequests({
|
|
6059
|
+
status: "pending",
|
|
6060
|
+
requesterExternalUserId: "+15557770003",
|
|
6061
|
+
sourceChannel: "phone",
|
|
6062
|
+
kind: "access_request",
|
|
6063
|
+
});
|
|
6064
|
+
expect(pending.length).toBe(1);
|
|
6065
|
+
resolveCanonicalGuardianRequest(pending[0].id, "pending", {
|
|
6066
|
+
status: "approved",
|
|
6067
|
+
answerText: undefined,
|
|
6068
|
+
decidedByExternalUserId: undefined,
|
|
6069
|
+
});
|
|
6070
|
+
|
|
6071
|
+
// Let the poll detect approval and enter the gated re-resolution.
|
|
6072
|
+
await new Promise((resolve) => setTimeout(resolve, 200));
|
|
6073
|
+
expect(relay.getConnectionState()).toBe("connected");
|
|
6074
|
+
|
|
6075
|
+
await relay.handleMessage(
|
|
6076
|
+
JSON.stringify({
|
|
6077
|
+
type: "prompt",
|
|
6078
|
+
voicePrompt: "What's on my calendar today?",
|
|
6079
|
+
lang: "en-US",
|
|
6080
|
+
last: true,
|
|
6081
|
+
}),
|
|
6082
|
+
);
|
|
6083
|
+
// Buffered, not yet run (verdict gated).
|
|
6084
|
+
expect(mockSendMessage.mock.calls.length).toBe(turnCountBefore);
|
|
6085
|
+
|
|
6086
|
+
releaseVerdict();
|
|
6087
|
+
await new Promise((resolve) => setTimeout(resolve, 20));
|
|
6088
|
+
|
|
6089
|
+
// Flushed onto the real-turn path rather than the awaiting-guardian-decision
|
|
6090
|
+
// wait-state classifier: the prompt produced an LLM turn.
|
|
6091
|
+
expect(mockSendMessage.mock.calls.length).toBeGreaterThan(turnCountBefore);
|
|
6092
|
+
|
|
6093
|
+
relay.destroy();
|
|
6094
|
+
});
|
|
5142
6095
|
});
|