@vellumai/assistant 0.10.4 → 0.10.5-dev.202607022329.d89bf2f
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 +94 -91
- package/Dockerfile +5 -16
- package/README.md +7 -10
- package/bun.lock +0 -4
- package/docs/architecture/security.md +20 -20
- package/docs/stt-provider-onboarding.md +4 -5
- package/docs/trusted-contact-access.md +7 -7
- package/eslint-rules/cli-no-daemon-internals.js +4 -0
- package/eslint.config.mjs +7 -1
- package/knip.json +0 -1
- package/node_modules/@vellumai/gateway-client/src/__tests__/contact-read-contracts.test.ts +36 -0
- package/node_modules/@vellumai/gateway-client/src/__tests__/invite-contract.test.ts +233 -0
- package/node_modules/@vellumai/gateway-client/src/__tests__/trust-verdict-contract.test.ts +40 -0
- package/node_modules/@vellumai/gateway-client/src/gateway-ipc-contracts.ts +20 -8
- package/node_modules/@vellumai/gateway-client/src/inbound-contract.ts +10 -0
- package/node_modules/@vellumai/gateway-client/src/index.ts +33 -0
- package/node_modules/@vellumai/gateway-client/src/invite-contract.ts +216 -0
- package/node_modules/@vellumai/gateway-client/src/outbound-contract.ts +91 -0
- package/node_modules/@vellumai/gateway-client/src/trust-verdict-contract.ts +39 -3
- package/node_modules/@vellumai/service-contracts/src/__tests__/ingress.test.ts +0 -8
- package/node_modules/@vellumai/service-contracts/src/__tests__/package-boundary.test.ts +1 -5
- package/node_modules/@vellumai/service-contracts/src/twilio-ingress.ts +3 -14
- package/openapi.yaml +1073 -225
- package/package.json +1 -4
- package/scripts/test.sh +35 -13
- package/src/__tests__/access-request-seed-content-blocks.test.ts +60 -4
- package/src/__tests__/acp-session.test.ts +4 -1
- package/src/__tests__/actor-trust-resolver-address-fallback.test.ts +0 -6
- package/src/__tests__/add-message-preassigned-id.test.ts +56 -0
- package/src/__tests__/agent-loop-set-system-prompt.test.ts +139 -0
- package/src/__tests__/app-conversation-ids-backfill.test.ts +3 -0
- package/src/__tests__/app-source-watcher.test.ts +11 -0
- package/src/__tests__/approval-interception-trust-gates.test.ts +148 -3
- package/src/__tests__/assistant-attachments.test.ts +62 -4
- package/src/__tests__/assistant-id-boundary-guard.test.ts +0 -1
- package/src/__tests__/attachments-store-heic-normalize.test.ts +230 -0
- package/src/__tests__/attachments-store.test.ts +3 -0
- package/src/__tests__/background-shell-bash.test.ts +34 -0
- package/src/__tests__/background-shell-host-bash.test.ts +12 -0
- package/src/__tests__/background-tool-registry.test.ts +97 -0
- package/src/__tests__/background-tool-routes.test.ts +72 -1
- package/src/__tests__/background-workers-disk-pressure.test.ts +2 -1
- package/src/__tests__/btw-routes.test.ts +57 -3
- package/src/__tests__/call-controller.test.ts +474 -84
- package/src/__tests__/call-domain.test.ts +48 -1
- package/src/__tests__/call-pointer-messages.test.ts +57 -19
- package/src/__tests__/call-pointer-no-hardcoded-copy.guard.test.ts +2 -6
- package/src/__tests__/call-routes-http.test.ts +15 -14
- package/src/__tests__/call-setup-flow-invite.test.ts +568 -0
- package/src/__tests__/call-setup-flow-midcall-trust.test.ts +231 -0
- package/src/__tests__/call-setup-flow-name-capture.test.ts +754 -0
- package/src/__tests__/call-setup-flow-verification.test.ts +925 -0
- package/src/__tests__/call-setup-flow.test.ts +311 -0
- package/src/__tests__/cancel-resolves-conversation-key.test.ts +0 -1
- package/src/__tests__/channel-guardian.test.ts +4 -7
- package/src/__tests__/channel-readiness-service.test.ts +0 -6
- package/src/__tests__/channel-readiness-slack-remote.test.ts +0 -4
- package/src/__tests__/channel-retry-sweep.test.ts +76 -64
- package/src/__tests__/channel-setup-panel-ack.test.ts +184 -0
- package/src/__tests__/clear-all-lexical.test.ts +90 -0
- package/src/__tests__/compactor-media-strip.test.ts +177 -0
- package/src/__tests__/config-loader-backfill.test.ts +5 -5
- package/src/__tests__/config-schema.test.ts +17 -0
- package/src/__tests__/contact-store-interaction-info.test.ts +27 -70
- package/src/__tests__/contacts-relay-reads.test.ts +188 -23
- package/src/__tests__/contacts-tools.test.ts +42 -1
- package/src/__tests__/contacts-write.test.ts +181 -0
- package/src/__tests__/context-search-conversations-source.test.ts +220 -81
- package/src/__tests__/conversation-agent-loop-disk-pressure.test.ts +1 -0
- package/src/__tests__/conversation-agent-loop-inference-profile.test.ts +1 -0
- package/src/__tests__/conversation-agent-loop-overflow.test.ts +1 -0
- package/src/__tests__/conversation-agent-loop.test.ts +39 -0
- package/src/__tests__/conversation-analysis-routes.test.ts +1 -1
- package/src/__tests__/conversation-app-control-instantiation.test.ts +2 -0
- package/src/__tests__/conversation-clear-safety.test.ts +0 -1
- package/src/__tests__/conversation-crud-enabled-plugins.test.ts +60 -0
- package/src/__tests__/conversation-delete-schedule-cleanup.test.ts +0 -1
- package/src/__tests__/conversation-disk-view.test.ts +1 -0
- package/src/__tests__/conversation-enabledplugins-route.test.ts +184 -0
- package/src/__tests__/conversation-error.test.ts +0 -18
- package/src/__tests__/conversation-fork-crud.test.ts +67 -0
- package/src/__tests__/conversation-history-web-search.test.ts +7 -137
- package/src/__tests__/conversation-initial-prompt.test.ts +110 -0
- package/src/__tests__/conversation-loop-db-lock-resilience.test.ts +191 -0
- package/src/__tests__/conversation-process-callsite.test.ts +37 -0
- package/src/__tests__/conversation-queue.test.ts +149 -0
- package/src/__tests__/conversation-routes-enabled-plugins.test.ts +392 -0
- package/src/__tests__/conversation-routes-guardian-reply.test.ts +84 -0
- package/src/__tests__/conversation-routes-hidden-queue.test.ts +407 -0
- package/src/__tests__/conversation-runtime-assembly.test.ts +36 -16
- package/src/__tests__/conversation-skill-tools.test.ts +73 -0
- package/src/__tests__/conversation-wipe.test.ts +20 -5
- package/src/__tests__/credential-prompt-route.test.ts +69 -0
- package/src/__tests__/credential-security-invariants.test.ts +1 -3
- package/src/__tests__/db-drop-messages-fts.test.ts +115 -0
- package/src/__tests__/deterministic-verification-control-plane.test.ts +1 -62
- package/src/__tests__/edit-propagation.test.ts +56 -2
- package/src/__tests__/empty-state-greeting-cache.test.ts +9 -0
- package/src/__tests__/gateway-only-enforcement.test.ts +15 -110
- package/src/__tests__/guardian-expiry-notifier.test.ts +1 -0
- package/src/__tests__/guardian-question-mode.test.ts +57 -0
- package/src/__tests__/guardian-verification-voice-binding.test.ts +1 -1
- package/src/__tests__/guardian-wait-controller.test.ts +619 -0
- package/src/__tests__/handlers-skills-memory-v2-reseed.test.ts +33 -2
- package/src/__tests__/heic-fixture.ts +55 -0
- package/src/__tests__/helpers/seed-contact-channel.ts +0 -2
- package/src/__tests__/http-conversation-lineage.test.ts +6 -1
- package/src/__tests__/image-conversion.test.ts +170 -0
- package/src/__tests__/inbound-invite-redemption.test.ts +72 -341
- package/src/__tests__/introduction-card-resolver.test.ts +354 -0
- package/src/__tests__/invite-redeemed-ipc.test.ts +97 -0
- package/src/__tests__/invite-routes-http.test.ts +246 -420
- package/src/__tests__/job-handler-registration-guard.test.ts +104 -0
- package/src/__tests__/lexical-index-dual-write.test.ts +426 -0
- package/src/__tests__/lifecycle-memory-v2-seed.test.ts +2 -2
- package/src/__tests__/list-messages-attachments.test.ts +159 -0
- package/src/__tests__/list-messages-background-tool-completion.test.ts +141 -0
- package/src/__tests__/list-messages-queued.test.ts +25 -0
- package/src/__tests__/managed-store.test.ts +129 -1
- package/src/__tests__/media-stream-audio-transcode.test.ts +113 -0
- package/src/__tests__/media-stream-output.test.ts +350 -19
- package/src/__tests__/media-stream-server-integration.test.ts +943 -169
- package/src/__tests__/media-stream-stt-session.test.ts +498 -1
- package/src/__tests__/memory-jobs-worker-lanes.test.ts +2 -2
- package/src/__tests__/model-intents.test.ts +1 -1
- package/src/__tests__/mtime-cache.test.ts +19 -0
- package/src/__tests__/non-member-access-request.test.ts +349 -3
- package/src/__tests__/notification-decision-fallback.test.ts +104 -8
- package/src/__tests__/notification-decision-strategy.test.ts +57 -8
- package/src/__tests__/onboarding-template-contract.test.ts +27 -43
- package/src/__tests__/openai-provider.test.ts +33 -0
- package/src/__tests__/openai-responses-provider.test.ts +40 -0
- package/src/__tests__/persistence-layering-guard.test.ts +29 -20
- package/src/__tests__/persona-resolver.test.ts +56 -2
- package/src/__tests__/personal-memory-auth-bypass.test.ts +62 -0
- package/src/__tests__/phone.test.ts +1 -15
- package/src/__tests__/plugin-bootstrap.test.ts +25 -196
- package/src/__tests__/plugin-disabled-state.test.ts +33 -0
- package/src/__tests__/plugin-effective-enabled-set.test.ts +227 -0
- package/src/__tests__/plugin-event-hub-facade.test.ts +323 -0
- package/src/__tests__/plugin-import-boundary-guard.test.ts +156 -42
- package/src/__tests__/plugin-injector-contribution.test.ts +4 -20
- package/src/__tests__/plugin-pipeline.test.ts +63 -1
- package/src/__tests__/plugin-route-contribution.test.ts +62 -115
- package/src/__tests__/plugin-tool-contribution.test.ts +0 -22
- package/src/__tests__/plugin-types.test.ts +0 -1
- package/src/__tests__/pointer-conversation-trust.test.ts +38 -6
- package/src/__tests__/pre-model-call-sanitize.test.ts +1 -1
- package/src/__tests__/prune-old-tool-invocations-job.test.ts +97 -0
- package/src/__tests__/public-ingress-urls.test.ts +0 -48
- package/src/__tests__/qdrant-collection-migration.test.ts +145 -2
- package/src/__tests__/reaction-intercept-member-verdict-warm.test.ts +0 -1
- package/src/__tests__/resolve-trust-class.test.ts +28 -10
- package/src/__tests__/scaffold-managed-skill-tool.test.ts +137 -0
- package/src/__tests__/schedule-retry.test.ts +38 -14
- package/src/__tests__/schedule-routes-workflow-validation.test.ts +3 -3
- package/src/__tests__/schedule-routes.test.ts +14 -6
- package/src/__tests__/schedule-store.test.ts +3 -1
- package/src/__tests__/scheduler-disk-pressure.test.ts +17 -6
- package/src/__tests__/scheduler-recurrence.test.ts +57 -107
- package/src/__tests__/scheduler-reuse-conversation.test.ts +51 -46
- package/src/__tests__/scheduler-wake.test.ts +13 -23
- package/src/__tests__/scheduler-worker-standdown.test.ts +220 -0
- package/src/__tests__/secret-routes-platform-proxy.test.ts +1 -1
- package/src/__tests__/shell-observability.test.ts +1 -0
- package/src/__tests__/skill-boundary-guard.test.ts +3 -4
- package/src/__tests__/skill-load-plugin-scope.test.ts +238 -0
- package/src/__tests__/skill-projection-feature-flag.test.ts +2 -0
- package/src/__tests__/skill-script-runner-host.test.ts +53 -0
- package/src/__tests__/skill-script-runner-sandbox.test.ts +5 -2
- package/src/__tests__/skill-tool-factory.test.ts +73 -1
- package/src/__tests__/slack-app-setup-skill-regression.test.ts +37 -14
- package/src/__tests__/slack-inbound-verification.test.ts +36 -0
- package/src/__tests__/slack-notification-approval-card.test.ts +89 -2
- package/src/__tests__/slack-reaction-canonical-approval.test.ts +51 -1
- package/src/__tests__/stt-hints.test.ts +2 -256
- package/src/__tests__/subagent-allowlist-validation.test.ts +47 -0
- package/src/__tests__/subagent-call-site-routing.test.ts +72 -0
- package/src/__tests__/subagent-fork-notifications.test.ts +14 -2
- package/src/__tests__/subagent-persistence.test.ts +122 -0
- package/src/__tests__/subagent-spawn-and-await.test.ts +1 -0
- package/src/__tests__/subagent-terminal-message.test.ts +99 -0
- package/src/__tests__/subagent-tool-gate-mode.test.ts +78 -1
- package/src/__tests__/system-prompt.test.ts +17 -118
- package/src/__tests__/task-scheduler.test.ts +68 -89
- package/src/__tests__/telephony-credential-preflight.test.ts +312 -0
- package/src/__tests__/telephony-tts-guard.test.ts +538 -0
- package/src/__tests__/title-generate-hook.test.ts +16 -0
- package/src/__tests__/tool-approval-handler.test.ts +87 -1
- package/src/__tests__/tool-approval-seed-content-blocks.test.ts +45 -20
- package/src/__tests__/tool-grant-request-escalation.test.ts +39 -0
- package/src/__tests__/trust-context-guards.test.ts +21 -10
- package/src/__tests__/trusted-contact-multichannel.test.ts +4 -0
- package/src/__tests__/twilio-provider.test.ts +17 -17
- package/src/__tests__/twilio-routes-twiml.test.ts +6 -543
- package/src/__tests__/twilio-routes.test.ts +257 -387
- package/src/__tests__/twilio-validation.test.ts +1 -1
- package/src/__tests__/unified-turn-context-client-os.test.ts +45 -0
- package/src/__tests__/usage-cache-backfill-migration.test.ts +5 -0
- package/src/__tests__/voice-approval-resolver-relay.test.ts +150 -0
- package/src/__tests__/voice-ingress-preflight.test.ts +61 -0
- package/src/__tests__/voice-quality.test.ts +2 -2
- package/src/__tests__/voice-scoped-grant-consumer.test.ts +1 -7
- package/src/__tests__/voice-session-bridge-processing-wait.test.ts +27 -0
- package/src/__tests__/voice-session-bridge.test.ts +1 -10
- package/src/__tests__/workspace-git-service.test.ts +1 -1
- package/src/__tests__/workspace-migration-013-repair-conversation-disk-view.test.ts +1 -0
- package/src/__tests__/workspace-migration-118-seed-now-md.test.ts +120 -0
- package/src/__tests__/workspace-migration-120-revise-onboarding-threads.test.ts +175 -0
- package/src/__tests__/workspace-migration-121-seed-default-user-guardrails.test.ts +118 -0
- package/src/__tests__/workspace-migration-122-relocate-default-user-boundary.test.ts +119 -0
- package/src/__tests__/workspace-migration-123-swap-quality-profile-to-fable.test.ts +191 -0
- package/src/__tests__/workspace-migration-backfill-installation-id.test.ts +235 -241
- package/src/acp/__tests__/prepare-agent-env.test.ts +0 -97
- package/src/acp/__tests__/session-manager-persistence.test.ts +2 -0
- package/src/acp/__tests__/session-manager-resume.test.ts +8 -0
- package/src/acp/__tests__/session-manager-usage.test.ts +6 -0
- package/src/acp/__tests__/session-manager.test.ts +6 -0
- package/src/acp/agent-process.test.ts +116 -0
- package/src/acp/agent-process.ts +71 -0
- package/src/acp/auto-install.ts +12 -8
- package/src/acp/failure-error.test.ts +109 -0
- package/src/acp/failure-error.ts +111 -0
- package/src/acp/prepare-agent-env.ts +0 -12
- package/src/acp/resolve-agent.test.ts +6 -45
- package/src/acp/resolve-agent.ts +2 -4
- package/src/acp/session-manager.test.ts +312 -0
- package/src/acp/session-manager.ts +102 -46
- package/src/agent/ax-tree-compaction.test.ts +4 -1
- package/src/agent/image-optimize.ts +17 -127
- package/src/agent/loop.ts +45 -192
- package/src/api/events/background-tool-completed.test.ts +51 -0
- package/src/api/events/background-tool-completed.ts +29 -0
- package/src/api/events/background-tool-started.test.ts +36 -0
- package/src/api/events/background-tool-started.ts +28 -0
- package/src/api/events/conversation-error.ts +0 -1
- package/src/api/events/open-panel.ts +33 -0
- package/src/api/events/subagent-status-changed.ts +4 -1
- package/src/api/index.ts +19 -0
- package/src/api/responses/conversation-message.ts +34 -0
- package/src/approvals/guardian-decision-primitive.ts +74 -24
- package/src/approvals/guardian-request-resolvers.ts +450 -122
- package/src/apps/app-store.ts +1 -0
- package/src/apps/shared-app-links-store.ts +1 -0
- package/src/avatar/identity-avatar.ts +3 -7
- package/src/background-wake/wake-intent-hooks.test.ts +1 -1
- package/src/calls/__tests__/fish-audio-client.test.ts +73 -0
- package/src/calls/__tests__/gateway-invite-reader.test.ts +170 -0
- package/src/calls/__tests__/relay-setup-router.test.ts +126 -102
- package/src/calls/call-controller.ts +134 -48
- package/src/calls/call-domain.ts +18 -42
- package/src/calls/call-pointer-messages.ts +51 -67
- package/src/calls/call-setup-flow-types.ts +76 -0
- package/src/calls/call-setup-flow.ts +1743 -0
- package/src/calls/call-speech-output.ts +87 -23
- package/src/calls/call-store.ts +4 -0
- package/src/calls/call-transport.ts +22 -36
- package/src/calls/fish-audio-client.ts +3 -0
- package/src/calls/gateway-invite-reader.ts +98 -0
- package/src/calls/guardian-wait-controller.ts +451 -0
- package/src/calls/media-stream-audio-transcode.ts +82 -2
- package/src/calls/media-stream-output.ts +142 -36
- package/src/calls/media-stream-server.ts +297 -178
- package/src/calls/media-stream-stt-session.ts +393 -50
- package/src/calls/media-turn-detector.ts +4 -6
- package/src/calls/relay-access-wait.ts +3 -4
- package/src/calls/relay-setup-router.ts +22 -43
- package/src/calls/relay-verification.ts +17 -24
- package/src/calls/resolve-call-tts-provider.ts +96 -29
- package/src/calls/stt-hints.ts +24 -87
- package/src/calls/telephony-credential-preflight.ts +159 -0
- package/src/calls/telephony-tts-capability.ts +183 -0
- package/src/calls/tts-call-strategy.ts +18 -20
- package/src/calls/twilio-provider.ts +3 -2
- package/src/calls/twilio-routes.ts +47 -351
- package/src/calls/types.ts +5 -3
- package/src/calls/voice-ingress-preflight.ts +9 -0
- package/src/calls/voice-quality.ts +13 -18
- package/src/calls/voice-session-bridge.ts +25 -35
- package/src/channels/__tests__/types.test.ts +28 -0
- package/src/channels/channel-verification-sessions.ts +7 -2
- package/src/channels/config.ts +0 -42
- package/src/channels/slack-thread-store.ts +12 -0
- package/src/channels/types.ts +23 -0
- package/src/cli/commands/__tests__/conversations-slack.test.ts +1 -1
- package/src/cli/commands/__tests__/schedules.test.ts +83 -34
- package/src/cli/commands/__tests__/status.test.ts +42 -2
- package/src/cli/commands/contacts.ts +2 -2
- package/src/cli/commands/credentials.ts +17 -0
- package/src/cli/commands/monitoring.ts +191 -0
- package/src/cli/commands/schedules-worker.ts +175 -0
- package/src/cli/commands/schedules.ts +169 -43
- package/src/cli/commands/status.ts +12 -1
- package/src/cli/commands/tools.ts +5 -1
- package/src/cli/lib/__tests__/plugin-catalog-cache.test.ts +1 -0
- package/src/cli/lib/__tests__/search-plugins.test.ts +29 -1
- package/src/cli/lib/__tests__/toggle-plugin.test.ts +7 -0
- package/src/cli/lib/search-plugins.ts +6 -0
- package/src/cli/lib/toggle-plugin.ts +21 -2
- package/src/cli/program.ts +3 -1
- package/src/config/__tests__/deployment-context-defaults.test.ts +23 -0
- package/src/config/acp-defaults.test.ts +0 -10
- package/src/config/acp-defaults.ts +0 -6
- package/src/config/bundled-skills/acp/SKILL.md +13 -45
- package/src/config/bundled-skills/acp/TOOLS.json +3 -3
- package/src/config/bundled-skills/app-builder/SKILL.md +3 -3
- package/src/config/bundled-skills/app-builder/references/RESPONSIVE.md +9 -9
- package/src/config/bundled-skills/contacts/tools/contact-merge.ts +1 -1
- package/src/config/bundled-skills/contacts/tools/contact-search.ts +1 -1
- package/src/config/bundled-skills/phone-calls/SKILL.md +1 -1
- package/src/config/bundled-skills/phone-calls/references/CONFIG.md +9 -9
- package/src/config/bundled-skills/phone-calls/references/TROUBLESHOOTING.md +10 -7
- package/src/config/bundled-skills/skill-management/TOOLS.json +10 -0
- package/src/config/call-site-defaults.ts +0 -2
- package/src/config/feature-flag-registry.json +16 -8
- package/src/config/loader.ts +11 -0
- package/src/config/schema.ts +9 -1
- package/src/config/schemas/__tests__/memory-v2.test.ts +2 -2
- package/src/config/schemas/__tests__/memory-v3.test.ts +64 -0
- package/src/config/schemas/call-site-catalog.ts +0 -12
- package/src/config/schemas/calls.ts +8 -0
- package/src/config/schemas/elevenlabs.ts +1 -1
- package/src/config/schemas/llm.ts +0 -2
- package/src/config/schemas/memory-v2.ts +1 -1
- package/src/config/schemas/memory-v3.ts +82 -0
- package/src/config/schemas/monitoring.ts +61 -0
- package/src/config/schemas/schedules.ts +35 -0
- package/src/config/seed-inference-profiles.ts +2 -2
- package/src/config/skills-plugin-filter.test.ts +96 -0
- package/src/config/skills.ts +26 -0
- package/src/contacts/__tests__/member-write-relay.test.ts +156 -4
- package/src/contacts/canonical-guardian-store.ts +5 -0
- package/src/contacts/contact-store.ts +81 -35
- package/src/contacts/contacts-write.ts +50 -25
- package/src/contacts/member-write-relay.ts +115 -31
- package/src/contacts/types.ts +0 -8
- package/src/context/compactor.ts +29 -15
- package/src/context/outbound-sanitize.ts +202 -0
- package/src/daemon/__tests__/abort-null-controller.test.ts +167 -0
- package/src/daemon/__tests__/conversation-tool-setup-plugin-scope.test.ts +122 -0
- package/src/daemon/__tests__/embedding-reconcile.test.ts +99 -16
- package/src/daemon/__tests__/wake-conversation-ops-completion.test.ts +107 -0
- package/src/daemon/abort-watchdog.ts +7 -0
- package/src/daemon/app-source-watcher.ts +9 -0
- package/src/daemon/assistant-attachments.ts +42 -0
- package/src/daemon/conversation-agent-loop-handlers.ts +256 -147
- package/src/daemon/conversation-agent-loop.ts +61 -68
- package/src/daemon/conversation-error.ts +1 -15
- package/src/daemon/conversation-history.ts +32 -236
- package/src/daemon/conversation-initial-prompt.ts +77 -0
- package/src/daemon/conversation-lifecycle.ts +25 -1
- package/src/daemon/conversation-messaging.ts +2 -2
- package/src/daemon/conversation-plugin-scope.ts +38 -0
- package/src/daemon/conversation-process.ts +50 -14
- package/src/daemon/conversation-runtime-assembly.ts +36 -6
- package/src/daemon/conversation-skill-tools.ts +19 -3
- package/src/daemon/conversation-store.ts +3 -3
- package/src/daemon/conversation-surfaces.ts +171 -0
- package/src/daemon/conversation-tool-setup.test.ts +95 -0
- package/src/daemon/conversation-tool-setup.ts +116 -1
- package/src/daemon/conversation-turn-finalize.ts +211 -0
- package/src/daemon/conversation.ts +92 -1
- package/src/daemon/disk-pressure-policy.ts +2 -1
- package/src/daemon/event-loop-watchdog.ts +22 -6
- package/src/daemon/external-plugins-bootstrap.ts +47 -134
- package/src/daemon/handlers/__tests__/config-a2a-accept.test.ts +1 -1
- package/src/daemon/handlers/__tests__/config-a2a-complete.test.ts +26 -14
- package/src/daemon/handlers/__tests__/config-a2a-invite.test.ts +27 -14
- package/src/daemon/handlers/__tests__/config-channels.test.ts +0 -5
- package/src/daemon/handlers/config-a2a.ts +7 -14
- package/src/daemon/handlers/config-channels.ts +2 -10
- package/src/daemon/handlers/conversation-history.ts +3 -1
- package/src/daemon/handlers/conversations.ts +0 -52
- package/src/daemon/handlers/shared.ts +4 -24
- package/src/daemon/handlers/skills.ts +4 -1
- package/src/daemon/host-cu-proxy.ts +1 -1
- package/src/daemon/lifecycle.ts +59 -472
- package/src/daemon/message-protocol.ts +3 -3
- package/src/daemon/message-provenance.ts +12 -14
- package/src/daemon/message-types/background-tools.ts +28 -0
- package/src/daemon/message-types/contacts.ts +2 -10
- package/src/daemon/message-types/conversations.ts +10 -0
- package/src/daemon/message-types/integrations.ts +2 -0
- package/src/daemon/message-types/surfaces.ts +1 -0
- package/src/daemon/message-types/sync.ts +1 -0
- package/src/daemon/pointer-conversation-trust.ts +23 -9
- package/src/daemon/pointer-turn-runner.ts +174 -0
- package/src/daemon/providers-setup.ts +16 -0
- package/src/daemon/shutdown-handlers.ts +139 -47
- package/src/daemon/skill-memory-refresh.ts +2 -2
- package/src/daemon/sqlite-corruption-watchdog.test.ts +217 -0
- package/src/daemon/sqlite-corruption-watchdog.ts +155 -0
- package/src/daemon/tool-setup-types.ts +8 -0
- package/src/daemon/trust-context.ts +21 -6
- package/src/daemon/turn-latency-tracker.test.ts +93 -0
- package/src/daemon/turn-latency-tracker.ts +38 -0
- package/src/daemon/wake-conversation-ops.ts +10 -8
- package/src/documents/document-comments-store.test.ts +7 -1
- package/src/documents/document-comments-store.ts +8 -0
- package/src/documents/document-store.ts +15 -0
- package/src/export/__tests__/transcript-formatter.test.ts +11 -0
- package/src/export/transcript-formatter.ts +49 -67
- package/src/home/__tests__/feed-writer.test.ts +247 -1
- package/src/home/conversation-starter-checkpoints.ts +1 -0
- package/src/home/feed-writer.ts +61 -0
- package/src/home/job-handlers/conversation-starters.ts +1 -0
- package/src/hooks/hook-loader.ts +12 -0
- package/src/hooks/registry.ts +32 -4
- package/src/inbound/public-ingress-urls.ts +0 -17
- package/src/ipc/__tests__/contacts-info-ipc.test.ts +285 -0
- package/src/ipc/assistant-server.ts +28 -7
- package/src/ipc/routes/__tests__/contacts-mirror-apply-atomicity.test.ts +62 -0
- package/src/ipc/routes/__tests__/contacts-mirror-apply.test.ts +272 -0
- package/src/ipc/routes/__tests__/contacts-mirror-ipc-routes.test.ts +322 -0
- package/src/ipc/routes/__tests__/invite-ipc-routes.test.ts +60 -4
- package/src/ipc/routes/contacts-info-ipc-routes.ts +239 -0
- package/src/ipc/routes/contacts-mirror-ipc-routes.ts +202 -0
- package/src/ipc/routes/db-proxy.ts +5 -2
- package/src/ipc/routes/invite-ipc-routes.ts +53 -39
- package/src/jobs/worker.ts +9 -2
- package/src/live-voice/__tests__/live-voice-archive.test.ts +10 -1
- package/src/live-voice/live-voice-archive.ts +5 -0
- package/src/messaging/providers/__tests__/transport-dispatch.test.ts +22 -6
- package/src/messaging/providers/channel-transport.ts +6 -0
- package/src/messaging/providers/index.ts +3 -0
- package/src/messaging/providers/slack/api.test.ts +49 -1
- package/src/messaging/providers/slack/api.ts +110 -0
- package/src/messaging/providers/slack/send.test.ts +85 -0
- package/src/messaging/providers/slack/send.ts +100 -28
- package/src/messaging/providers/slack/transport.ts +7 -13
- package/src/messaging/providers/whatsapp/send.ts +29 -9
- package/src/monitoring/__tests__/control.test.ts +235 -0
- package/src/monitoring/__tests__/sample-ring-buffer.test.ts +96 -0
- package/src/monitoring/control.ts +105 -0
- package/src/monitoring/resource-sampler.ts +258 -0
- package/src/monitoring/sample-ring-buffer.ts +108 -0
- package/src/monitoring/worker.ts +94 -0
- package/src/notifications/README.md +1 -1
- package/src/notifications/access-request-copy.ts +94 -23
- package/src/notifications/adapters/slack.ts +89 -21
- package/src/notifications/approval-card-builder.ts +33 -18
- package/src/notifications/approval-card-data.ts +48 -15
- package/src/notifications/broadcaster.ts +25 -16
- package/src/notifications/decision-engine.ts +131 -32
- package/src/notifications/guardian-question-mode.ts +58 -10
- package/src/permissions/approval-policy.ts +8 -1
- package/src/permissions/checker.ts +2 -0
- package/src/permissions/gateway-threshold-reader.ts +7 -6
- package/src/permissions/ipc-risk-types.ts +1 -0
- package/src/permissions/secret-prompter.ts +11 -3
- package/src/permissions/types.ts +2 -2
- package/src/persistence/__tests__/conversation-queries-search.test.ts +397 -0
- package/src/persistence/__tests__/conversation-search-lexical.test.ts +103 -0
- package/src/persistence/__tests__/jobs-worker-memory-disabled.test.ts +95 -0
- package/src/persistence/__tests__/slow-query-log.test.ts +266 -0
- package/src/persistence/__tests__/slow-sync-log.test.ts +155 -0
- package/src/persistence/a2a-invite-store.ts +205 -0
- package/src/persistence/attachments-store.ts +94 -10
- package/src/persistence/checkpoints.ts +45 -0
- package/src/persistence/conversation-crud.ts +453 -260
- package/src/persistence/conversation-display-order-migration.ts +8 -2
- package/src/persistence/conversation-group-migration.test.ts +8 -0
- package/src/persistence/conversation-group-migration.ts +9 -0
- package/src/persistence/conversation-queries.ts +244 -125
- package/src/persistence/conversation-row-batch-delete.ts +9 -5
- package/src/persistence/conversation-search-lexical.ts +49 -0
- package/src/persistence/db-async-query.ts +42 -0
- package/src/persistence/db-connection.ts +13 -0
- package/src/persistence/delivery-channels.ts +0 -82
- package/src/persistence/delivery-crud.ts +27 -6
- package/src/persistence/embeddings/__tests__/embedding-identity.test.ts +18 -5
- package/src/persistence/embeddings/__tests__/messages-lexical-index.test.ts +334 -0
- package/src/persistence/embeddings/embedding-identity.ts +17 -5
- package/src/persistence/embeddings/embedding-runtime-manager.ts +31 -0
- package/src/persistence/embeddings/messages-lexical-index.ts +396 -0
- package/src/persistence/embeddings/qdrant-client.ts +81 -5
- package/src/persistence/group-crud.ts +13 -1
- package/src/persistence/job-handlers/__tests__/message-lexical-backfill.test.ts +506 -0
- package/src/persistence/job-handlers/__tests__/message-lexical-enqueue.test.ts +164 -0
- package/src/persistence/job-handlers/__tests__/message-lexical.test.ts +259 -0
- package/src/persistence/job-handlers/cleanup.ts +57 -9
- package/src/persistence/job-handlers/message-lexical-backfill.ts +187 -0
- package/src/persistence/job-handlers/message-lexical.ts +251 -0
- package/src/persistence/jobs-store.ts +102 -8
- package/src/persistence/jobs-worker.ts +68 -28
- package/src/persistence/llm-request-log-store.ts +35 -19
- package/src/persistence/llm-usage-store.ts +11 -0
- package/src/persistence/memory-lifecycle-hooks.test.ts +65 -0
- package/src/persistence/memory-lifecycle-hooks.ts +55 -1
- package/src/persistence/migrations/028-call-session-mode.ts +1 -1
- package/src/persistence/migrations/116-messages-fts.ts +8 -1
- package/src/persistence/migrations/311-create-subagents-table.ts +50 -0
- package/src/persistence/migrations/312-drop-inbox-conversation-state-table.ts +15 -0
- package/src/persistence/migrations/313-drop-messages-fts.ts +17 -0
- package/src/persistence/migrations/314-add-conversation-enabled-plugins.ts +25 -0
- package/src/persistence/migrations/315-create-a2a-invites.ts +58 -0
- package/src/persistence/migrations/316-drop-contact-channels-invite-id.ts +28 -0
- package/src/persistence/migrations/317-canonical-guardian-requester-signals.ts +24 -0
- package/src/persistence/migrations/318-drop-contact-channel-telemetry.ts +32 -0
- package/src/persistence/migrations/__tests__/315-create-a2a-invites.test.ts +161 -0
- package/src/persistence/migrations/__tests__/316-drop-contact-channels-invite-id.test.ts +156 -0
- package/src/persistence/migrations/__tests__/318-drop-contact-channel-telemetry.test.ts +134 -0
- package/src/persistence/migrations/__tests__/migration-prefix-guard.test.ts +99 -0
- package/src/persistence/raw-query.ts +50 -11
- package/src/persistence/schema/a2a.ts +14 -0
- package/src/persistence/schema/contacts.ts +0 -62
- package/src/persistence/schema/conversation-groups.ts +12 -0
- package/src/persistence/schema/conversations.ts +2 -0
- package/src/persistence/schema/documents.ts +65 -0
- package/src/persistence/schema/guardian.ts +4 -0
- package/src/persistence/schema/index.ts +4 -0
- package/src/persistence/schema/memory-injection.ts +62 -0
- package/src/persistence/schema/workflows.ts +55 -0
- package/src/persistence/slow-query-log.ts +437 -0
- package/src/persistence/slow-sync-log.ts +204 -0
- package/src/persistence/steps.ts +16 -0
- package/src/persistence/subagent-store.ts +125 -0
- package/src/persistence/worker-control.ts +45 -238
- package/src/plugin-api/event-hub-facade.ts +172 -0
- package/src/plugin-api/index.ts +9 -1
- package/src/plugin-api/types.ts +40 -9
- package/src/plugins/defaults/default-plugin-names.ts +60 -0
- package/src/plugins/defaults/index.ts +19 -29
- package/src/plugins/defaults/memory/__tests__/conversation-queries.test.ts +46 -67
- package/src/plugins/defaults/memory/__tests__/db-async-query.test.ts +36 -1
- package/src/plugins/defaults/memory/__tests__/find-most-recent-retrospective-for.test.ts +2 -4
- package/src/plugins/defaults/memory/__tests__/jobs-worker-v2-graph-trigger-embed.test.ts +2 -2
- package/src/plugins/defaults/memory/__tests__/jobs-worker-v2-schedule.test.ts +9 -0
- package/src/plugins/defaults/memory/__tests__/memory-retrospective-job.test.ts +7 -4
- package/src/plugins/defaults/memory/config.ts +11 -0
- package/src/plugins/defaults/memory/context-search/agent-runner.ts +1 -1
- package/src/plugins/defaults/memory/context-search/sources/conversations.ts +124 -113
- package/src/plugins/defaults/memory/embeddings.ts +77 -0
- package/src/plugins/defaults/memory/find-most-recent-retrospective-for.ts +66 -0
- package/src/plugins/defaults/memory/frontmatter.ts +50 -0
- package/src/plugins/defaults/memory/graph/bootstrap.test.ts +36 -10
- package/src/plugins/defaults/memory/graph/bootstrap.ts +6 -5
- package/src/plugins/defaults/memory/graph/consolidation.ts +3 -5
- package/src/plugins/defaults/memory/graph/extraction.ts +3 -6
- package/src/plugins/defaults/memory/graph/graph-search.ts +12 -23
- package/src/plugins/defaults/memory/graph/narrative.ts +3 -5
- package/src/plugins/defaults/memory/graph/pattern-scan.ts +3 -5
- package/src/plugins/defaults/memory/graph/retriever.test.ts +1 -6
- package/src/plugins/defaults/memory/graph/retriever.ts +5 -10
- package/src/plugins/defaults/memory/graph/tools.ts +1 -1
- package/src/plugins/defaults/memory/hooks/init.ts +31 -5
- package/src/plugins/defaults/memory/indexer.ts +2 -3
- package/src/plugins/defaults/memory/injectors.ts +2 -2
- package/src/plugins/defaults/memory/job-handler-registration.ts +96 -0
- package/src/plugins/defaults/memory/job-handlers/backfill.ts +4 -26
- package/src/plugins/defaults/memory/job-handlers/embedding.test.ts +6 -19
- package/src/plugins/defaults/memory/job-handlers/embedding.ts +9 -22
- package/src/plugins/defaults/memory/job-handlers/index-maintenance.ts +2 -3
- package/src/plugins/defaults/memory/job-handlers.ts +13 -12
- package/src/plugins/defaults/memory/jobs/embed-concept-page.ts +1 -1
- package/src/plugins/defaults/memory/llm-helpers.ts +76 -0
- package/src/plugins/defaults/memory/memory-retrospective-job.ts +8 -5
- package/src/plugins/defaults/memory/memory-retrospective-startup-cleanup.ts +2 -2
- package/src/plugins/defaults/memory/memory-v2-concept-frequency.ts +2 -0
- package/src/plugins/defaults/memory/persistence-hooks.ts +46 -1
- package/src/plugins/defaults/memory/pkb/pkb-index.ts +1 -5
- package/src/plugins/defaults/memory/pkb/pkb-search.ts +2 -2
- package/src/plugins/defaults/memory/routes/__tests__/memory-v2-simulate-route.test.ts +4 -6
- package/src/plugins/defaults/memory/routes/memory-item-routes.ts +1 -1
- package/src/plugins/defaults/memory/search/semantic.ts +2 -2
- package/src/plugins/defaults/memory/startup.ts +244 -0
- package/src/plugins/defaults/memory/task-memory-cleanup.ts +8 -0
- package/src/{daemon → plugins/defaults/memory/v2}/__tests__/memory-v2-startup.test.ts +6 -6
- package/src/plugins/defaults/memory/v2/__tests__/migration.test.ts +2 -13
- package/src/plugins/defaults/memory/v2/__tests__/router.test.ts +4 -5
- package/src/plugins/defaults/memory/v2/__tests__/sweep-job.test.ts +1 -8
- package/src/plugins/defaults/memory/v2/activation.ts +2 -4
- package/src/plugins/defaults/memory/v2/cli-command-store.ts +2 -4
- package/src/plugins/defaults/memory/v2/frontmatter-sweep.ts +1 -1
- package/src/{daemon → plugins/defaults/memory/v2}/memory-v2-startup.test.ts +8 -8
- package/src/{daemon → plugins/defaults/memory/v2}/memory-v2-startup.ts +20 -22
- package/src/plugins/defaults/memory/v2/migration.ts +2 -5
- package/src/plugins/defaults/memory/v2/page-store.ts +1 -1
- package/src/plugins/defaults/memory/v2/qdrant.ts +2 -3
- package/src/plugins/defaults/memory/v2/router.ts +5 -5
- package/src/plugins/defaults/memory/v2/sim.ts +2 -4
- package/src/plugins/defaults/memory/v2/skill-store.ts +2 -4
- package/src/plugins/defaults/memory/v2/sweep-job.ts +6 -6
- package/src/plugins/defaults/memory/v3/__tests__/carry-integration.test.ts +11 -9
- package/src/plugins/defaults/memory/v3/__tests__/dense.test.ts +107 -2
- package/src/plugins/defaults/memory/v3/__tests__/gate-flag.test.ts +69 -0
- package/src/plugins/defaults/memory/v3/__tests__/gate.test.ts +181 -0
- package/src/plugins/defaults/memory/v3/__tests__/live-integration.test.ts +7 -3
- package/src/plugins/defaults/memory/v3/__tests__/orchestrate.test.ts +345 -8
- package/src/plugins/defaults/memory/v3/__tests__/pool-select.test.ts +1 -4
- package/src/plugins/defaults/memory/v3/__tests__/section-needle.test.ts +64 -0
- package/src/plugins/defaults/memory/v3/__tests__/shadow-integration.test.ts +18 -13
- package/src/plugins/defaults/memory/v3/__tests__/shadow-plugin.test.ts +62 -1
- package/src/plugins/defaults/memory/v3/card.ts +1 -4
- package/src/plugins/defaults/memory/v3/dense.ts +60 -25
- package/src/plugins/defaults/memory/v3/edge.ts +1 -1
- package/src/plugins/defaults/memory/v3/gate-flag.ts +20 -0
- package/src/plugins/defaults/memory/v3/gate.ts +158 -0
- package/src/plugins/defaults/memory/v3/maintain-job.ts +2 -4
- package/src/plugins/defaults/memory/v3/orchestrate.ts +186 -25
- package/src/plugins/defaults/memory/v3/pool-select.test.ts +7 -9
- package/src/plugins/defaults/memory/v3/pool-select.ts +6 -6
- package/src/plugins/defaults/memory/v3/prune.ts +2 -2
- package/src/plugins/defaults/memory/v3/section-dense-store.ts +8 -11
- package/src/plugins/defaults/memory/v3/section-needle.ts +27 -7
- package/src/plugins/defaults/memory/v3/shadow-plugin.ts +10 -0
- package/src/plugins/defaults/memory/v3-eval/eval-packets.ts +1 -4
- package/src/plugins/defaults/title-generate/hooks/user-prompt-submit.ts +5 -0
- package/src/plugins/defaults/turn-context/injectors.ts +1 -0
- package/src/plugins/defaults/turn-context/unified-turn-context.ts +12 -0
- package/src/plugins/injector-registry.ts +13 -2
- package/src/plugins/mtime-cache.ts +36 -47
- package/src/plugins/pipeline.ts +15 -1
- package/src/plugins/types.ts +14 -23
- package/src/prompts/__tests__/system-prompt.test.ts +170 -32
- package/src/prompts/persona-resolver.ts +13 -9
- package/src/prompts/system-prompt.ts +30 -9
- package/src/prompts/templates/BOOTSTRAP.md +16 -119
- package/src/prompts/templates/SOUL.md +43 -13
- package/src/prompts/templates/system-sections.ts +32 -27
- package/src/prompts/templates/users/default.md +19 -0
- package/src/providers/__tests__/placeholder-sentinels.test.ts +56 -0
- package/src/providers/inference/__tests__/base-url-route-validation.test.ts +96 -1
- package/src/providers/model-intents.ts +2 -2
- package/src/providers/openai/chat-completions-provider.ts +14 -1
- package/src/providers/openai/responses-provider.ts +18 -0
- package/src/providers/placeholder-sentinels.ts +18 -8
- package/src/providers/retry.ts +1 -0
- package/src/providers/speech-to-text/__tests__/provider-catalog.test.ts +0 -64
- package/src/providers/speech-to-text/__tests__/resolve.test.ts +92 -79
- package/src/providers/speech-to-text/deepgram-realtime.test.ts +98 -0
- package/src/providers/speech-to-text/deepgram-realtime.ts +62 -3
- package/src/providers/speech-to-text/provider-catalog.ts +0 -89
- package/src/providers/speech-to-text/resolve.ts +55 -0
- package/src/providers/tool-progress-events.test.ts +50 -0
- package/src/providers/tool-progress-events.ts +99 -0
- package/src/providers/types.ts +1 -1
- package/src/providers/vellum-model-routing.test.ts +51 -0
- package/src/providers/vellum-model-routing.ts +84 -0
- package/src/runtime/AGENTS.md +5 -4
- package/src/runtime/__tests__/agent-wake.test.ts +158 -1
- package/src/runtime/__tests__/channel-verification-service.test.ts +2 -2
- package/src/runtime/__tests__/trust-verdict-consumer.test.ts +37 -8
- package/src/runtime/access-request-helper.ts +154 -3
- package/src/runtime/actor-trust-resolver.ts +7 -21
- package/src/runtime/agent-wake.ts +41 -1
- package/src/runtime/assistant-event.ts +86 -12
- package/src/runtime/auth/__tests__/route-policy.test.ts +31 -0
- package/src/runtime/channel-approval-types.ts +52 -2
- package/src/runtime/channel-approvals.ts +23 -8
- package/src/runtime/channel-invite-transport.ts +2 -2
- package/src/runtime/channel-invite-transports/voice.ts +1 -1
- package/src/runtime/channel-readiness-service.ts +3 -5
- package/src/runtime/channel-retry-sweep.ts +18 -24
- package/src/runtime/channel-verification-service.ts +9 -11
- package/src/runtime/finalize-event-delivery.ts +30 -16
- package/src/runtime/guardian-action-service.ts +10 -11
- package/src/runtime/guardian-reply-router.ts +122 -21
- package/src/runtime/http-server.ts +44 -101
- package/src/runtime/introduction-policy.test.ts +214 -0
- package/src/runtime/introduction-policy.ts +284 -0
- package/src/runtime/invite-service.ts +104 -361
- package/src/runtime/local-actor-identity.test.ts +38 -0
- package/src/runtime/local-actor-identity.ts +14 -0
- package/src/runtime/middleware/__tests__/rate-limiter.test.ts +22 -0
- package/src/runtime/middleware/rate-limiter.ts +30 -0
- package/src/runtime/middleware/twilio-validation.ts +5 -7
- package/src/runtime/migrations/__tests__/gcs-signed-url.test.ts +28 -0
- package/src/runtime/migrations/origin-mode.ts +6 -6
- package/src/runtime/pre-first-message-gate.ts +1 -0
- package/src/runtime/routes/__tests__/channel-verification-revoke.test.ts +6 -35
- package/src/runtime/routes/__tests__/consolidation-routes.test.ts +24 -3
- package/src/runtime/routes/__tests__/contact-routes.test.ts +80 -15
- package/src/runtime/routes/__tests__/conversation-list-routes.test.ts +7 -1
- package/src/runtime/routes/__tests__/global-search-routes.test.ts +0 -3
- package/src/runtime/routes/__tests__/home-feed-routes.test.ts +142 -1
- package/src/runtime/routes/__tests__/invite-relay-routes.test.ts +119 -30
- package/src/runtime/routes/__tests__/migration-import-allowed-hosts-env.test.ts +70 -0
- package/src/runtime/routes/__tests__/monitoring-routes.test.ts +194 -0
- package/src/runtime/routes/__tests__/plugins-routes.test.ts +755 -29
- package/src/runtime/routes/__tests__/retrospective-routes.test.ts +4 -0
- package/src/runtime/routes/__tests__/schedule-routes-create.test.ts +70 -0
- package/src/runtime/routes/__tests__/schedule-worker-routes.test.ts +161 -0
- package/src/runtime/routes/__tests__/surface-action-routes.test.ts +1 -1
- package/src/runtime/routes/__tests__/surface-content-routes.test.ts +1 -1
- package/src/runtime/routes/acp-routes.ts +15 -3
- package/src/runtime/routes/app-management-routes.ts +4 -7
- package/src/runtime/routes/approval-routes.ts +4 -0
- package/src/runtime/routes/attachment-routes.ts +54 -3
- package/src/runtime/routes/avatar-routes.ts +0 -4
- package/src/runtime/routes/background-tool-routes.ts +34 -5
- package/src/runtime/routes/btw-routes.ts +32 -4
- package/src/runtime/routes/channel-route-shared.ts +18 -14
- package/src/runtime/routes/channel-verification-routes.ts +0 -3
- package/src/runtime/routes/contact-routes.ts +298 -117
- package/src/runtime/routes/conversation-list-routes.ts +6 -0
- package/src/runtime/routes/conversation-management-routes.ts +74 -37
- package/src/runtime/routes/conversation-query-routes.ts +5 -2
- package/src/runtime/routes/conversation-routes.ts +222 -82
- package/src/runtime/routes/conversations-import-routes.ts +8 -1
- package/src/runtime/routes/credential-prompt-routes.ts +20 -5
- package/src/runtime/routes/debug-routes.ts +1 -0
- package/src/runtime/routes/documents-routes.ts +5 -2
- package/src/runtime/routes/empty-state-greeting-cache.ts +37 -10
- package/src/runtime/routes/global-search-routes.ts +2 -1
- package/src/runtime/routes/guardian-approval-interception.ts +51 -0
- package/src/runtime/routes/home-feed-routes.ts +60 -1
- package/src/runtime/routes/identity-routes.ts +4 -87
- package/src/runtime/routes/inbound-message-handler.ts +82 -41
- package/src/runtime/routes/inbound-stages/acl-enforcement.test.ts +430 -57
- package/src/runtime/routes/inbound-stages/acl-enforcement.ts +302 -572
- package/src/runtime/routes/inbound-stages/background-dispatch.test.ts +76 -270
- package/src/runtime/routes/inbound-stages/background-dispatch.ts +21 -116
- package/src/runtime/routes/inbound-stages/edit-intercept.ts +9 -1
- package/src/runtime/routes/inbound-stages/escalation-intercept.test.ts +158 -0
- package/src/runtime/routes/inbound-stages/escalation-intercept.ts +22 -1
- package/src/runtime/routes/index.ts +6 -0
- package/src/runtime/routes/inference-provider-connection-routes.ts +22 -16
- package/src/runtime/routes/internal-twilio-routes.ts +0 -15
- package/src/runtime/routes/messages-lexical-routes.ts +75 -0
- package/src/runtime/routes/migration-routes.ts +69 -6
- package/src/runtime/routes/monitoring-routes.ts +237 -0
- package/src/runtime/routes/plugins-routes.ts +329 -8
- package/src/runtime/routes/schedule-routes.ts +49 -8
- package/src/runtime/routes/schedule-worker-routes.ts +210 -0
- package/src/runtime/routes/secret-routes.ts +1 -1
- package/src/runtime/routes/surface-conversation-resolver.ts +1 -0
- package/src/runtime/services/__tests__/analyze-conversation.test.ts +54 -9
- package/src/runtime/services/__tests__/auto-analysis-enqueue.test.ts +30 -6
- package/src/runtime/services/__tests__/conversation-analyze-job.test.ts +43 -8
- package/src/runtime/services/__tests__/conversation-serializer.test.ts +20 -0
- package/src/runtime/services/analyze-conversation.ts +14 -4
- package/src/runtime/services/conversation-serializer.ts +5 -0
- package/src/runtime/slack-no-response.ts +47 -0
- package/src/runtime/slack-reply-session.test.ts +633 -0
- package/src/runtime/slack-reply-session.ts +382 -0
- package/src/runtime/slack-task-progress.ts +95 -0
- package/src/runtime/sync/resource-sync-events.ts +25 -0
- package/src/runtime/tool-grant-request-helper.ts +17 -1
- package/src/runtime/trust-class.test.ts +141 -0
- package/src/runtime/trust-class.ts +121 -0
- package/src/runtime/trust-verdict-consumer.ts +7 -6
- package/src/schedule/__tests__/run-script.test.ts +21 -0
- package/src/schedule/__tests__/worker-control.test.ts +241 -0
- package/src/schedule/run-script.ts +13 -6
- package/src/schedule/schedule-usage-store.ts +1 -0
- package/src/schedule/scheduler.ts +178 -127
- package/src/schedule/worker-control.ts +118 -0
- package/src/schedule/worker.ts +131 -0
- package/src/skills/__tests__/categories-cache.test.ts +38 -0
- package/src/skills/categories-cache.ts +40 -5
- package/src/skills/managed-store.ts +41 -6
- package/src/skills/validate-input.ts +47 -0
- package/src/stt/types.ts +5 -9
- package/src/subagent/manager.ts +267 -39
- package/src/subagent/types.ts +6 -2
- package/src/subagent/validate-allowlists.ts +65 -0
- package/src/telemetry/watchdog-direct-emit.test.ts +109 -0
- package/src/telemetry/watchdog-direct-emit.ts +89 -0
- package/src/tools/acp/list-agents.test.ts +0 -6
- package/src/tools/background-tool-registry.ts +61 -0
- package/src/tools/document/document-comment-tool.test.ts +4 -0
- package/src/tools/host-terminal/host-shell.test.ts +397 -0
- package/src/tools/host-terminal/host-shell.ts +203 -14
- package/src/tools/skills/find-similar-skills.test.ts +152 -1
- package/src/tools/skills/find-similar-skills.ts +30 -4
- package/src/tools/skills/load.ts +36 -0
- package/src/tools/skills/scaffold-managed.ts +66 -34
- package/src/tools/skills/skill-script-runner.ts +17 -0
- package/src/tools/skills/skill-tool-factory.ts +5 -1
- package/src/tools/terminal/safe-env.ts +3 -0
- package/src/tools/terminal/shell.test.ts +177 -0
- package/src/tools/terminal/shell.ts +98 -4
- package/src/tools/tool-approval-handler.ts +106 -29
- package/{node_modules/@vellumai/skill-host-contracts/src → src/tools}/tool-types.ts +16 -17
- package/src/tools/types.ts +29 -17
- package/src/tools/ui-surface/definitions.ts +3 -1
- package/src/tts/__tests__/provider-adapters.test.ts +37 -0
- package/src/tts/__tests__/provider-catalog.test.ts +21 -0
- package/src/tts/provider-catalog.ts +40 -9
- package/src/tts/providers/fish-audio-provider.ts +19 -10
- package/src/tts/types.ts +3 -4
- package/src/util/__tests__/process-tree.test.ts +10 -2
- package/src/util/__tests__/text-spacing.test.ts +53 -0
- package/src/util/cgroup-memory.ts +142 -0
- package/src/util/image-conversion.ts +281 -0
- package/src/util/phone.ts +1 -8
- package/src/util/platform.ts +33 -0
- package/src/util/process-tree.ts +10 -2
- package/src/util/text-spacing.ts +53 -0
- package/src/util/worker-process.ts +271 -0
- package/src/workflows/journal-store.ts +16 -1
- package/src/workflows/library.test.ts +35 -20
- package/src/workflows/library.ts +105 -66
- package/src/workspace/git-service.ts +1 -0
- package/src/workspace/migrations/011-backfill-installation-id.ts +83 -31
- package/src/workspace/migrations/041-backfill-google-gmail-settings-scope.ts +41 -22
- package/src/workspace/migrations/070-memory-v2-summary-schema-rebuild.ts +1 -1
- package/src/workspace/migrations/118-seed-now-md.ts +64 -0
- package/src/workspace/migrations/119-strip-persisted-memory-v3-tuning-defaults.ts +169 -0
- package/src/workspace/migrations/120-revise-onboarding-threads.ts +47 -0
- package/src/workspace/migrations/121-seed-default-user-guardrails.ts +82 -0
- package/src/workspace/migrations/122-relocate-default-user-boundary.ts +111 -0
- package/src/workspace/migrations/123-swap-quality-profile-to-fable.ts +85 -0
- package/src/workspace/migrations/__tests__/119-strip-persisted-memory-v3-tuning-defaults.test.ts +181 -0
- package/src/workspace/migrations/registry.ts +12 -0
- package/drizzle/0000_dizzy_maggott.sql +0 -301
- package/drizzle/meta/0000_snapshot.json +0 -1933
- package/drizzle/meta/_journal.json +0 -13
- package/node_modules/@vellumai/skill-host-contracts/__tests__/client.test.ts +0 -887
- package/node_modules/@vellumai/skill-host-contracts/bun.lock +0 -24
- package/node_modules/@vellumai/skill-host-contracts/package.json +0 -18
- package/node_modules/@vellumai/skill-host-contracts/src/assistant-event.ts +0 -97
- package/node_modules/@vellumai/skill-host-contracts/src/client.ts +0 -1349
- package/node_modules/@vellumai/skill-host-contracts/src/index.ts +0 -6
- package/node_modules/@vellumai/skill-host-contracts/src/runtime-mode.ts +0 -11
- package/node_modules/@vellumai/skill-host-contracts/src/server-message.ts +0 -32
- package/node_modules/@vellumai/skill-host-contracts/src/skill-host.ts +0 -333
- package/node_modules/@vellumai/skill-host-contracts/tsconfig.json +0 -20
- package/node_modules/@vellumai/skill-host-contracts/tsconfig.test.json +0 -12
- package/src/__tests__/invite-redemption-service.test.ts +0 -1180
- package/src/__tests__/invite-service-ipc.test.ts +0 -350
- package/src/__tests__/job-handler-registry-guard.test.ts +0 -125
- package/src/__tests__/regenerate-fire-and-forget-trace.test.ts +0 -203
- package/src/__tests__/relay-server.test.ts +0 -6153
- package/src/__tests__/telephony-stt-routing.test.ts +0 -329
- package/src/__tests__/voice-invite-redemption.test.ts +0 -702
- package/src/calls/relay-server.ts +0 -2463
- package/src/calls/telephony-stt-routing.ts +0 -145
- package/src/contacts/__tests__/contacts-write-revoke-relay.test.ts +0 -128
- package/src/daemon/__tests__/daemon-skill-host.test.ts +0 -273
- package/src/daemon/daemon-skill-host.ts +0 -262
- package/src/daemon/message-types/meet.ts +0 -143
- package/src/daemon/shutdown-registry.ts +0 -40
- package/src/ipc/__tests__/skill-server-bidirectional.test.ts +0 -253
- package/src/ipc/__tests__/skill-server.test.ts +0 -182
- package/src/ipc/skill-ipc-types.ts +0 -54
- package/src/ipc/skill-routes/__tests__/config.test.ts +0 -146
- package/src/ipc/skill-routes/__tests__/events-ipc.test.ts +0 -448
- package/src/ipc/skill-routes/__tests__/identity.test.ts +0 -62
- package/src/ipc/skill-routes/__tests__/log.test.ts +0 -133
- package/src/ipc/skill-routes/__tests__/memory.test.ts +0 -203
- package/src/ipc/skill-routes/__tests__/platform.test.ts +0 -111
- package/src/ipc/skill-routes/__tests__/providers.test.ts +0 -265
- package/src/ipc/skill-routes/__tests__/registries.test.ts +0 -400
- package/src/ipc/skill-routes/config.ts +0 -47
- package/src/ipc/skill-routes/events.ts +0 -147
- package/src/ipc/skill-routes/identity.ts +0 -21
- package/src/ipc/skill-routes/index.ts +0 -37
- package/src/ipc/skill-routes/log.ts +0 -40
- package/src/ipc/skill-routes/memory.ts +0 -92
- package/src/ipc/skill-routes/platform.ts +0 -39
- package/src/ipc/skill-routes/providers.ts +0 -162
- package/src/ipc/skill-routes/registries.ts +0 -338
- package/src/ipc/skill-server.ts +0 -831
- package/src/ipc/skill-socket-path.ts +0 -20
- package/src/jobs/register-job-handlers.ts +0 -74
- package/src/persistence/invite-store.ts +0 -481
- package/src/plugins/job-handler-registry.ts +0 -94
- package/src/providers/cache-control.ts +0 -26
- package/src/runtime/__tests__/runtime-mode.test.ts +0 -62
- package/src/runtime/invite-redemption-service.ts +0 -828
- package/src/runtime/invite-redemption-templates.ts +0 -46
- package/src/runtime/runtime-mode.ts +0 -40
- package/src/runtime/slack-dm-text-delivery.ts +0 -177
- package/src/util/voice-code.ts +0 -31
|
@@ -1,1180 +0,0 @@
|
|
|
1
|
-
import { beforeEach, describe, expect, mock, test } from "bun:test";
|
|
2
|
-
|
|
3
|
-
mock.module("../util/logger.js", () => ({
|
|
4
|
-
getLogger: () =>
|
|
5
|
-
new Proxy({} as Record<string, unknown>, {
|
|
6
|
-
get: () => () => {},
|
|
7
|
-
}),
|
|
8
|
-
}));
|
|
9
|
-
|
|
10
|
-
// Wrap the real contacts-write so a test can force the best-effort local mirror
|
|
11
|
-
// to fail (simulating a gateway-verified activation whose local DB row failed),
|
|
12
|
-
// while normal setup keeps the real implementation. Capture the concrete real
|
|
13
|
-
// function BEFORE registering the mock so the wrapper never recurses into
|
|
14
|
-
// itself via the (now live) module namespace.
|
|
15
|
-
const contactsWriteState = { mirrorThrows: false };
|
|
16
|
-
const realContactsWrite = await import("../contacts/contacts-write.js");
|
|
17
|
-
const realUpsertContactChannel = realContactsWrite.upsertContactChannel;
|
|
18
|
-
mock.module("../contacts/contacts-write.js", () => ({
|
|
19
|
-
...realContactsWrite,
|
|
20
|
-
upsertContactChannel: (
|
|
21
|
-
params: Parameters<typeof realUpsertContactChannel>[0],
|
|
22
|
-
) => {
|
|
23
|
-
if (contactsWriteState.mirrorThrows) {
|
|
24
|
-
throw new Error("local mirror exploded");
|
|
25
|
-
}
|
|
26
|
-
return realUpsertContactChannel(params);
|
|
27
|
-
},
|
|
28
|
-
}));
|
|
29
|
-
|
|
30
|
-
// Mock the gateway IPC bridge used by the redemption service for the
|
|
31
|
-
// authoritative pre-mutation claim (record_invite_redemption). Tests drive the
|
|
32
|
-
// claim result via `gatewayIpc`.
|
|
33
|
-
const gatewayIpc = {
|
|
34
|
-
claim: { ok: true, updated: true, mirrored: true } as {
|
|
35
|
-
ok: boolean;
|
|
36
|
-
updated: boolean;
|
|
37
|
-
mirrored: boolean;
|
|
38
|
-
},
|
|
39
|
-
claimThrows: false,
|
|
40
|
-
// When set, contacts_get_rich throws (gateway read unreachable) so the
|
|
41
|
-
// gate-status fallback must fail open.
|
|
42
|
-
richThrows: false,
|
|
43
|
-
// When set, overrides the contacts_get_rich response (e.g. a gateway row
|
|
44
|
-
// under a divergent UUID for the same (type,address)).
|
|
45
|
-
richOverride: null as ((contactId: string | undefined) => unknown) | null,
|
|
46
|
-
// Drives the upsert_verified_channel relay verdict. When false the gateway
|
|
47
|
-
// refuses the actor (blocked/revoked) and the activation is refused.
|
|
48
|
-
activationVerified: true,
|
|
49
|
-
// Gateway channel returned on a verified activation, surfaced as the memberId
|
|
50
|
-
// when the local mirror produces no row.
|
|
51
|
-
activationChannelId: "gw-channel-id" as string | undefined,
|
|
52
|
-
calls: [] as { method: string; params?: Record<string, unknown> }[],
|
|
53
|
-
};
|
|
54
|
-
|
|
55
|
-
mock.module("../ipc/gateway-client.js", () => ({
|
|
56
|
-
ipcCallPersistent: async (
|
|
57
|
-
method: string,
|
|
58
|
-
params?: Record<string, unknown>,
|
|
59
|
-
) => {
|
|
60
|
-
gatewayIpc.calls.push({ method, params });
|
|
61
|
-
if (method === "contacts_get_rich") {
|
|
62
|
-
if (gatewayIpc.richThrows) throw new Error("gateway read unreachable");
|
|
63
|
-
if (gatewayIpc.richOverride) {
|
|
64
|
-
return gatewayIpc.richOverride(params?.contactId as string);
|
|
65
|
-
}
|
|
66
|
-
return richContactForId(params?.contactId as string);
|
|
67
|
-
}
|
|
68
|
-
if (method === "record_invite_redemption") {
|
|
69
|
-
if (gatewayIpc.claimThrows) throw new Error("gateway unreachable");
|
|
70
|
-
onGatewayClaim?.();
|
|
71
|
-
return gatewayIpc.claim;
|
|
72
|
-
}
|
|
73
|
-
if (method === "upsert_verified_channel") {
|
|
74
|
-
if (!gatewayIpc.activationVerified) {
|
|
75
|
-
return { ok: true, verified: false };
|
|
76
|
-
}
|
|
77
|
-
return {
|
|
78
|
-
ok: true,
|
|
79
|
-
verified: true,
|
|
80
|
-
channel: gatewayIpc.activationChannelId
|
|
81
|
-
? {
|
|
82
|
-
id: gatewayIpc.activationChannelId,
|
|
83
|
-
contactId: (params?.contactId as string) ?? "gw-contact",
|
|
84
|
-
type: (params?.type as string) ?? "telegram",
|
|
85
|
-
address: (params?.address as string) ?? "gw-addr",
|
|
86
|
-
status: "active",
|
|
87
|
-
verifiedAt: 1,
|
|
88
|
-
verifiedVia: (params?.verifiedVia as string) ?? "invite",
|
|
89
|
-
}
|
|
90
|
-
: undefined,
|
|
91
|
-
};
|
|
92
|
-
}
|
|
93
|
-
return undefined;
|
|
94
|
-
},
|
|
95
|
-
}));
|
|
96
|
-
|
|
97
|
-
// Serves contacts_get_rich (the gateway ACL read backing the gate-status
|
|
98
|
-
// fallback) from the seeded local contact, so gate resolution sources status
|
|
99
|
-
// from the gateway path rather than the local channel column.
|
|
100
|
-
function richContactForId(contactId: string | undefined) {
|
|
101
|
-
if (!contactId) return undefined;
|
|
102
|
-
const contact = getContact(contactId);
|
|
103
|
-
if (!contact) return undefined;
|
|
104
|
-
// The gateway owns the ACL; tests seed it into the gateway ACL store. Source
|
|
105
|
-
// role/status/policy from there to build the gateway-rich response the
|
|
106
|
-
// production read parses (never the Phase-B-dropped assistant ACL columns).
|
|
107
|
-
return {
|
|
108
|
-
ok: true,
|
|
109
|
-
contact: {
|
|
110
|
-
id: contact.id,
|
|
111
|
-
displayName: contact.displayName,
|
|
112
|
-
role: gatewayContactRole(contact.id) ?? "contact",
|
|
113
|
-
interactionCount: contact.interactionCount,
|
|
114
|
-
createdAt: contact.createdAt,
|
|
115
|
-
updatedAt: contact.updatedAt,
|
|
116
|
-
channels: contact.channels.map((c) => {
|
|
117
|
-
const acl = gatewayChannelAcl(c.id);
|
|
118
|
-
return {
|
|
119
|
-
id: c.id,
|
|
120
|
-
contactId: c.contactId,
|
|
121
|
-
type: c.type,
|
|
122
|
-
address: c.address,
|
|
123
|
-
isPrimary: c.isPrimary,
|
|
124
|
-
externalUserId: c.externalChatId,
|
|
125
|
-
status: acl.status,
|
|
126
|
-
policy: acl.policy,
|
|
127
|
-
verifiedAt: acl.verifiedAt,
|
|
128
|
-
verifiedVia: null,
|
|
129
|
-
lastSeenAt: null,
|
|
130
|
-
interactionCount: 0,
|
|
131
|
-
lastInteraction: null,
|
|
132
|
-
revokedReason: null,
|
|
133
|
-
blockedReason: null,
|
|
134
|
-
};
|
|
135
|
-
}),
|
|
136
|
-
},
|
|
137
|
-
};
|
|
138
|
-
}
|
|
139
|
-
|
|
140
|
-
/** Read a channel's seeded ACL view from the gateway ACL store. */
|
|
141
|
-
function gatewayChannelAcl(channelId: string): {
|
|
142
|
-
status: string;
|
|
143
|
-
policy: string;
|
|
144
|
-
verifiedAt: number | null;
|
|
145
|
-
} {
|
|
146
|
-
const row = gatewayAclByChannelId(channelId);
|
|
147
|
-
return {
|
|
148
|
-
status: row?.status ?? "unverified",
|
|
149
|
-
policy: row?.policy ?? "allow",
|
|
150
|
-
verifiedAt: row?.verifiedAt ?? null,
|
|
151
|
-
};
|
|
152
|
-
}
|
|
153
|
-
|
|
154
|
-
/** The seeded gateway role for any of a contact's channels. */
|
|
155
|
-
function gatewayContactRole(contactId: string): string | undefined {
|
|
156
|
-
return gatewayAclRows().find((r) => r.contactId === contactId)?.role;
|
|
157
|
-
}
|
|
158
|
-
|
|
159
|
-
// Lets a test inject a side-effect into the gateway claim — runs after the
|
|
160
|
-
// service's pre-validation but before the assistant use-bump, so it can race a
|
|
161
|
-
// revoke into the window that makes `recordInviteUse` return false.
|
|
162
|
-
let onGatewayClaim: (() => void) | null = null;
|
|
163
|
-
|
|
164
|
-
function resetGatewayIpc() {
|
|
165
|
-
gatewayIpc.claim = { ok: true, updated: true, mirrored: true };
|
|
166
|
-
gatewayIpc.claimThrows = false;
|
|
167
|
-
gatewayIpc.richThrows = false;
|
|
168
|
-
gatewayIpc.richOverride = null;
|
|
169
|
-
gatewayIpc.activationVerified = true;
|
|
170
|
-
gatewayIpc.activationChannelId = "gw-channel-id";
|
|
171
|
-
gatewayIpc.calls = [];
|
|
172
|
-
onGatewayClaim = null;
|
|
173
|
-
}
|
|
174
|
-
|
|
175
|
-
import type { TrustVerdict } from "@vellumai/gateway-client";
|
|
176
|
-
|
|
177
|
-
import {
|
|
178
|
-
findContactChannel,
|
|
179
|
-
getContact,
|
|
180
|
-
upsertContact,
|
|
181
|
-
} from "../contacts/contact-store.js";
|
|
182
|
-
import { getSqlite } from "../persistence/db-connection.js";
|
|
183
|
-
import { initializeDb } from "../persistence/db-init.js";
|
|
184
|
-
import {
|
|
185
|
-
createInvite,
|
|
186
|
-
revokeInvite as revokeStoreFn,
|
|
187
|
-
} from "../persistence/invite-store.js";
|
|
188
|
-
import {
|
|
189
|
-
type InviteRedemptionOutcome,
|
|
190
|
-
redeemInvite,
|
|
191
|
-
redeemInviteByCode,
|
|
192
|
-
resolveMemberGateStatus,
|
|
193
|
-
} from "../runtime/invite-redemption-service.js";
|
|
194
|
-
import { hashVoiceCode } from "../util/voice-code.js";
|
|
195
|
-
import {
|
|
196
|
-
gatewayAclByChannelId,
|
|
197
|
-
gatewayAclRows,
|
|
198
|
-
resetGatewayAclStore,
|
|
199
|
-
} from "./helpers/gateway-acl-store.js";
|
|
200
|
-
import { seedContactChannel } from "./helpers/seed-contact-channel.js";
|
|
201
|
-
|
|
202
|
-
await initializeDb();
|
|
203
|
-
|
|
204
|
-
function resetTables() {
|
|
205
|
-
getSqlite().run("DELETE FROM assistant_ingress_invites");
|
|
206
|
-
getSqlite().run("DELETE FROM contact_channels");
|
|
207
|
-
getSqlite().run("DELETE FROM contacts");
|
|
208
|
-
resetGatewayAclStore();
|
|
209
|
-
}
|
|
210
|
-
|
|
211
|
-
/** Create a throwaway contact and return its ID, for use as the invite's contactId. */
|
|
212
|
-
function createTargetContact(displayName = "Target Contact"): string {
|
|
213
|
-
return upsertContact({ displayName, role: "contact" }).id;
|
|
214
|
-
}
|
|
215
|
-
|
|
216
|
-
/** Read a contact's seeded gateway role (gateway-owned, not a local column). */
|
|
217
|
-
function localContactRole(contactId: string): string | undefined {
|
|
218
|
-
return gatewayContactRole(contactId);
|
|
219
|
-
}
|
|
220
|
-
|
|
221
|
-
describe("invite-redemption-service", () => {
|
|
222
|
-
beforeEach(() => {
|
|
223
|
-
resetTables();
|
|
224
|
-
resetGatewayIpc();
|
|
225
|
-
contactsWriteState.mirrorThrows = false;
|
|
226
|
-
});
|
|
227
|
-
|
|
228
|
-
test("redeems a valid invite and returns typed outcome", async () => {
|
|
229
|
-
const targetContactId = createTargetContact();
|
|
230
|
-
const { rawToken, invite } = createInvite({
|
|
231
|
-
sourceChannel: "telegram",
|
|
232
|
-
contactId: targetContactId,
|
|
233
|
-
maxUses: 1,
|
|
234
|
-
});
|
|
235
|
-
|
|
236
|
-
const outcome = await redeemInvite({
|
|
237
|
-
rawToken,
|
|
238
|
-
sourceChannel: "telegram",
|
|
239
|
-
externalUserId: "user-1",
|
|
240
|
-
});
|
|
241
|
-
|
|
242
|
-
expect(outcome.ok).toBe(true);
|
|
243
|
-
expect(outcome).toEqual({
|
|
244
|
-
ok: true,
|
|
245
|
-
type: "redeemed",
|
|
246
|
-
memberId: expect.any(String),
|
|
247
|
-
inviteId: invite.id,
|
|
248
|
-
});
|
|
249
|
-
|
|
250
|
-
// The activation is written to the gateway via upsert_verified_channel.
|
|
251
|
-
expect(
|
|
252
|
-
gatewayIpc.calls.some((c) => c.method === "upsert_verified_channel"),
|
|
253
|
-
).toBe(true);
|
|
254
|
-
});
|
|
255
|
-
|
|
256
|
-
test("marks channel as verified via invite on redemption", async () => {
|
|
257
|
-
const targetContactId = createTargetContact();
|
|
258
|
-
const { rawToken } = createInvite({
|
|
259
|
-
sourceChannel: "telegram",
|
|
260
|
-
contactId: targetContactId,
|
|
261
|
-
maxUses: 1,
|
|
262
|
-
});
|
|
263
|
-
|
|
264
|
-
const outcome = await redeemInvite({
|
|
265
|
-
rawToken,
|
|
266
|
-
sourceChannel: "telegram",
|
|
267
|
-
externalUserId: "user-1",
|
|
268
|
-
});
|
|
269
|
-
|
|
270
|
-
expect(outcome.ok).toBe(true);
|
|
271
|
-
|
|
272
|
-
// The gateway owns the verified ACL verdict (relayed via
|
|
273
|
-
// upsert_verified_channel); the local mirror persists identity only.
|
|
274
|
-
const result = findContactChannel({
|
|
275
|
-
channelType: "telegram",
|
|
276
|
-
address: "user-1",
|
|
277
|
-
});
|
|
278
|
-
expect(result).not.toBeNull();
|
|
279
|
-
expect(
|
|
280
|
-
gatewayIpc.calls.some((c) => c.method === "upsert_verified_channel"),
|
|
281
|
-
).toBe(true);
|
|
282
|
-
});
|
|
283
|
-
|
|
284
|
-
test("marks channel as verified via invite on 6-digit code redemption", async () => {
|
|
285
|
-
const targetContactId = createTargetContact();
|
|
286
|
-
const inviteCode = "123456";
|
|
287
|
-
createInvite({
|
|
288
|
-
sourceChannel: "telegram",
|
|
289
|
-
contactId: targetContactId,
|
|
290
|
-
maxUses: 1,
|
|
291
|
-
inviteCodeHash: hashVoiceCode(inviteCode),
|
|
292
|
-
});
|
|
293
|
-
|
|
294
|
-
const outcome = await redeemInviteByCode({
|
|
295
|
-
code: inviteCode,
|
|
296
|
-
sourceChannel: "telegram",
|
|
297
|
-
externalUserId: "code-user-1",
|
|
298
|
-
});
|
|
299
|
-
|
|
300
|
-
expect(outcome.ok).toBe(true);
|
|
301
|
-
|
|
302
|
-
// The gateway owns the verified ACL verdict; the local mirror is identity-only.
|
|
303
|
-
const result = findContactChannel({
|
|
304
|
-
channelType: "telegram",
|
|
305
|
-
address: "code-user-1",
|
|
306
|
-
});
|
|
307
|
-
expect(result).not.toBeNull();
|
|
308
|
-
expect(
|
|
309
|
-
gatewayIpc.calls.some((c) => c.method === "upsert_verified_channel"),
|
|
310
|
-
).toBe(true);
|
|
311
|
-
});
|
|
312
|
-
|
|
313
|
-
test("returns invalid_token for a bogus token", async () => {
|
|
314
|
-
const outcome = await redeemInvite({
|
|
315
|
-
rawToken: "totally-bogus-token",
|
|
316
|
-
sourceChannel: "telegram",
|
|
317
|
-
externalUserId: "user-1",
|
|
318
|
-
});
|
|
319
|
-
|
|
320
|
-
expect(outcome).toEqual({ ok: false, reason: "invalid_token" });
|
|
321
|
-
});
|
|
322
|
-
|
|
323
|
-
test("returns expired for an expired invite", async () => {
|
|
324
|
-
const targetContactId = createTargetContact();
|
|
325
|
-
// Create an invite that expired 1 ms ago
|
|
326
|
-
const { rawToken } = createInvite({
|
|
327
|
-
sourceChannel: "telegram",
|
|
328
|
-
contactId: targetContactId,
|
|
329
|
-
maxUses: 1,
|
|
330
|
-
expiresInMs: -1,
|
|
331
|
-
});
|
|
332
|
-
|
|
333
|
-
const outcome = await redeemInvite({
|
|
334
|
-
rawToken,
|
|
335
|
-
sourceChannel: "telegram",
|
|
336
|
-
externalUserId: "user-1",
|
|
337
|
-
});
|
|
338
|
-
|
|
339
|
-
expect(outcome).toEqual({ ok: false, reason: "expired" });
|
|
340
|
-
});
|
|
341
|
-
|
|
342
|
-
test("returns revoked for a revoked invite", async () => {
|
|
343
|
-
const targetContactId = createTargetContact();
|
|
344
|
-
const { rawToken, invite } = createInvite({
|
|
345
|
-
sourceChannel: "telegram",
|
|
346
|
-
contactId: targetContactId,
|
|
347
|
-
maxUses: 1,
|
|
348
|
-
});
|
|
349
|
-
revokeStoreFn(invite.id);
|
|
350
|
-
|
|
351
|
-
const outcome = await redeemInvite({
|
|
352
|
-
rawToken,
|
|
353
|
-
sourceChannel: "telegram",
|
|
354
|
-
externalUserId: "user-1",
|
|
355
|
-
});
|
|
356
|
-
|
|
357
|
-
expect(outcome).toEqual({ ok: false, reason: "revoked" });
|
|
358
|
-
});
|
|
359
|
-
|
|
360
|
-
test("returns max_uses_reached when invite is fully consumed", async () => {
|
|
361
|
-
const targetContactId = createTargetContact();
|
|
362
|
-
const { rawToken } = createInvite({
|
|
363
|
-
sourceChannel: "telegram",
|
|
364
|
-
contactId: targetContactId,
|
|
365
|
-
maxUses: 1,
|
|
366
|
-
});
|
|
367
|
-
|
|
368
|
-
// First redemption should succeed
|
|
369
|
-
const first = await redeemInvite({
|
|
370
|
-
rawToken,
|
|
371
|
-
sourceChannel: "telegram",
|
|
372
|
-
externalUserId: "user-1",
|
|
373
|
-
});
|
|
374
|
-
expect(first.ok).toBe(true);
|
|
375
|
-
|
|
376
|
-
// Second attempt should fail — the invite is now fully redeemed
|
|
377
|
-
const second = await redeemInvite({
|
|
378
|
-
rawToken,
|
|
379
|
-
sourceChannel: "telegram",
|
|
380
|
-
externalUserId: "user-2",
|
|
381
|
-
});
|
|
382
|
-
|
|
383
|
-
expect(second).toEqual({ ok: false, reason: "max_uses_reached" });
|
|
384
|
-
});
|
|
385
|
-
|
|
386
|
-
test("returns channel_mismatch when redeeming on wrong channel", async () => {
|
|
387
|
-
const targetContactId = createTargetContact();
|
|
388
|
-
const { rawToken } = createInvite({
|
|
389
|
-
sourceChannel: "telegram",
|
|
390
|
-
contactId: targetContactId,
|
|
391
|
-
maxUses: 1,
|
|
392
|
-
});
|
|
393
|
-
|
|
394
|
-
const outcome = await redeemInvite({
|
|
395
|
-
rawToken,
|
|
396
|
-
sourceChannel: "phone",
|
|
397
|
-
externalUserId: "user-1",
|
|
398
|
-
});
|
|
399
|
-
|
|
400
|
-
expect(outcome).toEqual({ ok: false, reason: "channel_mismatch" });
|
|
401
|
-
});
|
|
402
|
-
|
|
403
|
-
test("returns missing_identity when no externalUserId or externalChatId", async () => {
|
|
404
|
-
const targetContactId = createTargetContact();
|
|
405
|
-
const { rawToken } = createInvite({
|
|
406
|
-
sourceChannel: "telegram",
|
|
407
|
-
contactId: targetContactId,
|
|
408
|
-
maxUses: 1,
|
|
409
|
-
});
|
|
410
|
-
|
|
411
|
-
const outcome = await redeemInvite({
|
|
412
|
-
rawToken,
|
|
413
|
-
sourceChannel: "telegram",
|
|
414
|
-
});
|
|
415
|
-
|
|
416
|
-
expect(outcome).toEqual({ ok: false, reason: "missing_identity" });
|
|
417
|
-
});
|
|
418
|
-
|
|
419
|
-
test("returns already_member when user is already an active member", async () => {
|
|
420
|
-
// Pre-create an active member and find their contact
|
|
421
|
-
const member = seedContactChannel({
|
|
422
|
-
sourceChannel: "telegram",
|
|
423
|
-
externalUserId: "existing-user",
|
|
424
|
-
status: "active",
|
|
425
|
-
});
|
|
426
|
-
|
|
427
|
-
// Create an invite targeting the same contact that owns the channel
|
|
428
|
-
const { rawToken } = createInvite({
|
|
429
|
-
sourceChannel: "telegram",
|
|
430
|
-
contactId: member.contactId,
|
|
431
|
-
maxUses: 5,
|
|
432
|
-
});
|
|
433
|
-
|
|
434
|
-
const outcome = await redeemInvite({
|
|
435
|
-
rawToken,
|
|
436
|
-
sourceChannel: "telegram",
|
|
437
|
-
externalUserId: "existing-user",
|
|
438
|
-
});
|
|
439
|
-
|
|
440
|
-
expect(outcome.ok).toBe(true);
|
|
441
|
-
expect(
|
|
442
|
-
(outcome as Extract<InviteRedemptionOutcome, { type: "already_member" }>)
|
|
443
|
-
.type,
|
|
444
|
-
).toBe("already_member");
|
|
445
|
-
expect(
|
|
446
|
-
(outcome as Extract<InviteRedemptionOutcome, { type: "already_member" }>)
|
|
447
|
-
.memberId,
|
|
448
|
-
).toEqual(expect.any(String));
|
|
449
|
-
});
|
|
450
|
-
|
|
451
|
-
test("returns invalid_token for a blocked member to avoid leaking membership status", async () => {
|
|
452
|
-
// Pre-create a blocked member and find their contact
|
|
453
|
-
const member = seedContactChannel({
|
|
454
|
-
sourceChannel: "telegram",
|
|
455
|
-
externalUserId: "blocked-user",
|
|
456
|
-
status: "blocked",
|
|
457
|
-
});
|
|
458
|
-
|
|
459
|
-
// Create an invite targeting the same contact that owns the channel
|
|
460
|
-
const { rawToken } = createInvite({
|
|
461
|
-
sourceChannel: "telegram",
|
|
462
|
-
contactId: member.contactId,
|
|
463
|
-
maxUses: 5,
|
|
464
|
-
});
|
|
465
|
-
|
|
466
|
-
const outcome = await redeemInvite({
|
|
467
|
-
rawToken,
|
|
468
|
-
sourceChannel: "telegram",
|
|
469
|
-
externalUserId: "blocked-user",
|
|
470
|
-
});
|
|
471
|
-
|
|
472
|
-
expect(outcome).toEqual({ ok: false, reason: "invalid_token" });
|
|
473
|
-
});
|
|
474
|
-
|
|
475
|
-
test("matches an active member by (type,address) when the gateway row has a divergent uuid", async () => {
|
|
476
|
-
const member = seedContactChannel({
|
|
477
|
-
sourceChannel: "telegram",
|
|
478
|
-
externalUserId: "divergent-user",
|
|
479
|
-
status: "active",
|
|
480
|
-
});
|
|
481
|
-
|
|
482
|
-
// The gateway row for the same (type,address) carries a DIFFERENT id, as a
|
|
483
|
-
// reconcile divergence would produce. Matching by id alone would miss it.
|
|
484
|
-
gatewayIpc.richOverride = () => ({
|
|
485
|
-
ok: true,
|
|
486
|
-
contact: {
|
|
487
|
-
id: member.contactId,
|
|
488
|
-
displayName: "divergent-user",
|
|
489
|
-
role: "contact",
|
|
490
|
-
interactionCount: 0,
|
|
491
|
-
createdAt: 1,
|
|
492
|
-
updatedAt: 1,
|
|
493
|
-
channels: [
|
|
494
|
-
{
|
|
495
|
-
id: "gateway-divergent-uuid",
|
|
496
|
-
contactId: member.contactId,
|
|
497
|
-
type: "telegram",
|
|
498
|
-
address: "divergent-user",
|
|
499
|
-
isPrimary: false,
|
|
500
|
-
externalUserId: null,
|
|
501
|
-
status: "active",
|
|
502
|
-
policy: "allow",
|
|
503
|
-
verifiedAt: 1,
|
|
504
|
-
verifiedVia: "invite",
|
|
505
|
-
lastSeenAt: null,
|
|
506
|
-
interactionCount: 0,
|
|
507
|
-
lastInteraction: null,
|
|
508
|
-
revokedReason: null,
|
|
509
|
-
blockedReason: null,
|
|
510
|
-
},
|
|
511
|
-
],
|
|
512
|
-
},
|
|
513
|
-
});
|
|
514
|
-
|
|
515
|
-
const { rawToken } = createInvite({
|
|
516
|
-
sourceChannel: "telegram",
|
|
517
|
-
contactId: member.contactId,
|
|
518
|
-
maxUses: 5,
|
|
519
|
-
});
|
|
520
|
-
|
|
521
|
-
const outcome = await redeemInvite({
|
|
522
|
-
rawToken,
|
|
523
|
-
sourceChannel: "telegram",
|
|
524
|
-
externalUserId: "divergent-user",
|
|
525
|
-
});
|
|
526
|
-
|
|
527
|
-
expect(outcome.ok).toBe(true);
|
|
528
|
-
expect((outcome as { type: string }).type).toBe("already_member");
|
|
529
|
-
});
|
|
530
|
-
|
|
531
|
-
test("blocks via the (type,address) match when the gateway row has a divergent uuid", async () => {
|
|
532
|
-
const member = seedContactChannel({
|
|
533
|
-
sourceChannel: "telegram",
|
|
534
|
-
externalUserId: "divergent-blocked",
|
|
535
|
-
status: "blocked",
|
|
536
|
-
});
|
|
537
|
-
|
|
538
|
-
gatewayIpc.richOverride = () => ({
|
|
539
|
-
ok: true,
|
|
540
|
-
contact: {
|
|
541
|
-
id: member.contactId,
|
|
542
|
-
displayName: "divergent-blocked",
|
|
543
|
-
role: "contact",
|
|
544
|
-
interactionCount: 0,
|
|
545
|
-
createdAt: 1,
|
|
546
|
-
updatedAt: 1,
|
|
547
|
-
channels: [
|
|
548
|
-
{
|
|
549
|
-
id: "gateway-divergent-blocked-uuid",
|
|
550
|
-
contactId: member.contactId,
|
|
551
|
-
type: "telegram",
|
|
552
|
-
// Case-divergent address must still match (COLLATE NOCASE).
|
|
553
|
-
address: "DIVERGENT-BLOCKED",
|
|
554
|
-
isPrimary: false,
|
|
555
|
-
externalUserId: null,
|
|
556
|
-
status: "blocked",
|
|
557
|
-
policy: "deny",
|
|
558
|
-
verifiedAt: null,
|
|
559
|
-
verifiedVia: null,
|
|
560
|
-
lastSeenAt: null,
|
|
561
|
-
interactionCount: 0,
|
|
562
|
-
lastInteraction: null,
|
|
563
|
-
revokedReason: null,
|
|
564
|
-
blockedReason: "guardian blocked",
|
|
565
|
-
},
|
|
566
|
-
],
|
|
567
|
-
},
|
|
568
|
-
});
|
|
569
|
-
|
|
570
|
-
const { rawToken } = createInvite({
|
|
571
|
-
sourceChannel: "telegram",
|
|
572
|
-
contactId: member.contactId,
|
|
573
|
-
maxUses: 5,
|
|
574
|
-
});
|
|
575
|
-
|
|
576
|
-
const outcome = await redeemInvite({
|
|
577
|
-
rawToken,
|
|
578
|
-
sourceChannel: "telegram",
|
|
579
|
-
externalUserId: "divergent-blocked",
|
|
580
|
-
});
|
|
581
|
-
|
|
582
|
-
expect(outcome).toEqual({ ok: false, reason: "invalid_token" });
|
|
583
|
-
});
|
|
584
|
-
|
|
585
|
-
test("fails open (no throw) when the gateway gate-status read is unreachable", async () => {
|
|
586
|
-
// No verdict member and an unreachable gateway read must degrade to the
|
|
587
|
-
// fail-open path: redemption still resolves rather than throwing.
|
|
588
|
-
const member = seedContactChannel({
|
|
589
|
-
sourceChannel: "telegram",
|
|
590
|
-
externalUserId: "readfail-user",
|
|
591
|
-
status: "revoked",
|
|
592
|
-
});
|
|
593
|
-
|
|
594
|
-
gatewayIpc.richThrows = true;
|
|
595
|
-
|
|
596
|
-
const { rawToken } = createInvite({
|
|
597
|
-
sourceChannel: "telegram",
|
|
598
|
-
contactId: member.contactId,
|
|
599
|
-
maxUses: 1,
|
|
600
|
-
});
|
|
601
|
-
|
|
602
|
-
const outcome = await redeemInvite({
|
|
603
|
-
rawToken,
|
|
604
|
-
sourceChannel: "telegram",
|
|
605
|
-
externalUserId: "readfail-user",
|
|
606
|
-
});
|
|
607
|
-
|
|
608
|
-
expect(outcome.ok).toBe(true);
|
|
609
|
-
});
|
|
610
|
-
|
|
611
|
-
test("binds redeemer to the invite's target contact, not the guardian", async () => {
|
|
612
|
-
// Pre-create a guardian contact with a revoked telegram channel
|
|
613
|
-
const guardianSeed = seedContactChannel({
|
|
614
|
-
sourceChannel: "telegram",
|
|
615
|
-
externalUserId: "guardian-tg-id",
|
|
616
|
-
displayName: "Guardian",
|
|
617
|
-
role: "guardian",
|
|
618
|
-
status: "revoked",
|
|
619
|
-
});
|
|
620
|
-
|
|
621
|
-
// Create a separate target contact "Mom"
|
|
622
|
-
const momContact = upsertContact({
|
|
623
|
-
displayName: "Mom",
|
|
624
|
-
role: "contact",
|
|
625
|
-
});
|
|
626
|
-
|
|
627
|
-
// Create an invite targeting Mom's contact
|
|
628
|
-
const { rawToken } = createInvite({
|
|
629
|
-
sourceChannel: "telegram",
|
|
630
|
-
contactId: momContact.id,
|
|
631
|
-
maxUses: 5,
|
|
632
|
-
});
|
|
633
|
-
|
|
634
|
-
// Redeem using the guardian's Telegram identity
|
|
635
|
-
const outcome = await redeemInvite({
|
|
636
|
-
rawToken,
|
|
637
|
-
sourceChannel: "telegram",
|
|
638
|
-
externalUserId: "guardian-tg-id",
|
|
639
|
-
});
|
|
640
|
-
|
|
641
|
-
// Should succeed — redeemer's channel is bound to Mom
|
|
642
|
-
expect(outcome.ok).toBe(true);
|
|
643
|
-
expect((outcome as { type: string }).type).toBe("redeemed");
|
|
644
|
-
|
|
645
|
-
// Gateway-first: the activation relays the target contactId so the gateway
|
|
646
|
-
// binds the channel to Mom (not the guardian) — the binding is not lost.
|
|
647
|
-
const upsert = gatewayIpc.calls.find(
|
|
648
|
-
(c) => c.method === "upsert_verified_channel",
|
|
649
|
-
);
|
|
650
|
-
expect(upsert).toBeDefined();
|
|
651
|
-
expect(upsert!.params).toMatchObject({
|
|
652
|
-
type: "telegram",
|
|
653
|
-
address: "guardian-tg-id",
|
|
654
|
-
contactId: momContact.id,
|
|
655
|
-
});
|
|
656
|
-
|
|
657
|
-
// Verify the redeemer's Telegram ID is now bound to Mom's contact
|
|
658
|
-
const result = findContactChannel({
|
|
659
|
-
channelType: "telegram",
|
|
660
|
-
address: "guardian-tg-id",
|
|
661
|
-
});
|
|
662
|
-
expect(result).not.toBeNull();
|
|
663
|
-
expect(result!.contact.id).toBe(momContact.id);
|
|
664
|
-
|
|
665
|
-
// Verify the original guardian contact was NOT modified
|
|
666
|
-
const guardian = getContact(guardianSeed.contactId);
|
|
667
|
-
expect(guardian).not.toBeNull();
|
|
668
|
-
expect(localContactRole(guardianSeed.contactId)).toBe("guardian");
|
|
669
|
-
});
|
|
670
|
-
|
|
671
|
-
test("downgrades guardian to contact when redeeming invite targeting own contact", async () => {
|
|
672
|
-
// Create a guardian contact with a revoked channel
|
|
673
|
-
const guardianSeed = seedContactChannel({
|
|
674
|
-
sourceChannel: "telegram",
|
|
675
|
-
externalUserId: "guardian-own-id",
|
|
676
|
-
displayName: "Guardian",
|
|
677
|
-
role: "guardian",
|
|
678
|
-
status: "revoked",
|
|
679
|
-
});
|
|
680
|
-
|
|
681
|
-
// Create invite targeting the guardian's own contact
|
|
682
|
-
const { rawToken } = createInvite({
|
|
683
|
-
sourceChannel: "telegram",
|
|
684
|
-
contactId: guardianSeed.contactId,
|
|
685
|
-
maxUses: 5,
|
|
686
|
-
});
|
|
687
|
-
|
|
688
|
-
const outcome = await redeemInvite({
|
|
689
|
-
rawToken,
|
|
690
|
-
sourceChannel: "telegram",
|
|
691
|
-
externalUserId: "guardian-own-id",
|
|
692
|
-
});
|
|
693
|
-
|
|
694
|
-
expect(outcome.ok).toBe(true);
|
|
695
|
-
|
|
696
|
-
// The role downgrade is gateway-owned; redemption relays the activation for
|
|
697
|
-
// the guardian's own contact rather than mutating the local role.
|
|
698
|
-
expect(
|
|
699
|
-
gatewayIpc.calls.some((c) => c.method === "upsert_verified_channel"),
|
|
700
|
-
).toBe(true);
|
|
701
|
-
const updated = getContact(guardianSeed.contactId);
|
|
702
|
-
expect(updated).not.toBeNull();
|
|
703
|
-
});
|
|
704
|
-
|
|
705
|
-
test("binds redeemer to the invite's target contact via 6-digit code, not the guardian", async () => {
|
|
706
|
-
// Pre-create a guardian contact with a revoked telegram channel
|
|
707
|
-
const guardianSeed = seedContactChannel({
|
|
708
|
-
sourceChannel: "telegram",
|
|
709
|
-
externalUserId: "guardian-code-id",
|
|
710
|
-
displayName: "Guardian",
|
|
711
|
-
role: "guardian",
|
|
712
|
-
status: "revoked",
|
|
713
|
-
});
|
|
714
|
-
|
|
715
|
-
// Create a separate target contact "Mom"
|
|
716
|
-
const momContact = upsertContact({
|
|
717
|
-
displayName: "Mom",
|
|
718
|
-
role: "contact",
|
|
719
|
-
});
|
|
720
|
-
|
|
721
|
-
// Create an invite targeting Mom's contact with a 6-digit code
|
|
722
|
-
const code = "123456";
|
|
723
|
-
const inviteCodeHash = hashVoiceCode(code);
|
|
724
|
-
createInvite({
|
|
725
|
-
sourceChannel: "telegram",
|
|
726
|
-
contactId: momContact.id,
|
|
727
|
-
maxUses: 5,
|
|
728
|
-
inviteCodeHash,
|
|
729
|
-
});
|
|
730
|
-
|
|
731
|
-
// Redeem using the guardian's Telegram identity
|
|
732
|
-
const outcome = await redeemInviteByCode({
|
|
733
|
-
code,
|
|
734
|
-
sourceChannel: "telegram",
|
|
735
|
-
externalUserId: "guardian-code-id",
|
|
736
|
-
});
|
|
737
|
-
|
|
738
|
-
// Should succeed — redeemer's channel is bound to Mom
|
|
739
|
-
expect(outcome.ok).toBe(true);
|
|
740
|
-
expect((outcome as { type: string }).type).toBe("redeemed");
|
|
741
|
-
|
|
742
|
-
// Verify the redeemer's Telegram ID is now bound to Mom's contact
|
|
743
|
-
const result = findContactChannel({
|
|
744
|
-
channelType: "telegram",
|
|
745
|
-
address: "guardian-code-id",
|
|
746
|
-
});
|
|
747
|
-
expect(result).not.toBeNull();
|
|
748
|
-
expect(result!.contact.id).toBe(momContact.id);
|
|
749
|
-
|
|
750
|
-
// Verify the original guardian contact was NOT modified
|
|
751
|
-
const guardian = getContact(guardianSeed.contactId);
|
|
752
|
-
expect(guardian).not.toBeNull();
|
|
753
|
-
expect(localContactRole(guardianSeed.contactId)).toBe("guardian");
|
|
754
|
-
});
|
|
755
|
-
|
|
756
|
-
test("does not return already_member for a revoked member", async () => {
|
|
757
|
-
const targetContactId = createTargetContact();
|
|
758
|
-
const { rawToken } = createInvite({
|
|
759
|
-
sourceChannel: "telegram",
|
|
760
|
-
contactId: targetContactId,
|
|
761
|
-
maxUses: 5,
|
|
762
|
-
});
|
|
763
|
-
|
|
764
|
-
// Pre-create a revoked member
|
|
765
|
-
seedContactChannel({
|
|
766
|
-
sourceChannel: "telegram",
|
|
767
|
-
externalUserId: "revoked-user",
|
|
768
|
-
status: "revoked",
|
|
769
|
-
});
|
|
770
|
-
|
|
771
|
-
const outcome = await redeemInvite({
|
|
772
|
-
rawToken,
|
|
773
|
-
sourceChannel: "telegram",
|
|
774
|
-
externalUserId: "revoked-user",
|
|
775
|
-
});
|
|
776
|
-
|
|
777
|
-
// Should redeem, not return already_member
|
|
778
|
-
expect(outcome.ok).toBe(true);
|
|
779
|
-
expect(
|
|
780
|
-
(outcome as Extract<InviteRedemptionOutcome, { type: "redeemed" }>).type,
|
|
781
|
-
).toBe("redeemed");
|
|
782
|
-
});
|
|
783
|
-
|
|
784
|
-
test("raw token is not present in the outcome object", async () => {
|
|
785
|
-
const targetContactId = createTargetContact();
|
|
786
|
-
const { rawToken } = createInvite({
|
|
787
|
-
sourceChannel: "telegram",
|
|
788
|
-
contactId: targetContactId,
|
|
789
|
-
maxUses: 1,
|
|
790
|
-
});
|
|
791
|
-
|
|
792
|
-
const outcome = await redeemInvite({
|
|
793
|
-
rawToken,
|
|
794
|
-
sourceChannel: "telegram",
|
|
795
|
-
externalUserId: "user-1",
|
|
796
|
-
});
|
|
797
|
-
|
|
798
|
-
// Verify the raw token does not appear anywhere in the serialized outcome
|
|
799
|
-
const serialized = JSON.stringify(outcome);
|
|
800
|
-
expect(serialized).not.toContain(rawToken);
|
|
801
|
-
});
|
|
802
|
-
|
|
803
|
-
test("channel enforcement blocks cross-channel redemption (voice invite via slack)", async () => {
|
|
804
|
-
const targetContactId = createTargetContact();
|
|
805
|
-
const { rawToken } = createInvite({
|
|
806
|
-
sourceChannel: "phone",
|
|
807
|
-
contactId: targetContactId,
|
|
808
|
-
maxUses: 1,
|
|
809
|
-
});
|
|
810
|
-
|
|
811
|
-
const outcome = await redeemInvite({
|
|
812
|
-
rawToken,
|
|
813
|
-
sourceChannel: "slack",
|
|
814
|
-
externalUserId: "user-1",
|
|
815
|
-
});
|
|
816
|
-
|
|
817
|
-
expect(outcome).toEqual({ ok: false, reason: "channel_mismatch" });
|
|
818
|
-
});
|
|
819
|
-
|
|
820
|
-
test("returns invalid_token for an active member with a bogus token (no membership probing)", async () => {
|
|
821
|
-
// Pre-create an active member
|
|
822
|
-
seedContactChannel({
|
|
823
|
-
sourceChannel: "telegram",
|
|
824
|
-
externalUserId: "probed-user",
|
|
825
|
-
status: "active",
|
|
826
|
-
});
|
|
827
|
-
|
|
828
|
-
// Attempt to redeem with a bogus token — must NOT leak membership status
|
|
829
|
-
const outcome = await redeemInvite({
|
|
830
|
-
rawToken: "completely-bogus-token",
|
|
831
|
-
sourceChannel: "telegram",
|
|
832
|
-
externalUserId: "probed-user",
|
|
833
|
-
});
|
|
834
|
-
|
|
835
|
-
expect(outcome).toEqual({ ok: false, reason: "invalid_token" });
|
|
836
|
-
});
|
|
837
|
-
|
|
838
|
-
test("returns expired for an active member with an expired invite token", async () => {
|
|
839
|
-
const targetContactId = createTargetContact();
|
|
840
|
-
// Create an expired invite
|
|
841
|
-
const { rawToken } = createInvite({
|
|
842
|
-
sourceChannel: "telegram",
|
|
843
|
-
contactId: targetContactId,
|
|
844
|
-
maxUses: 5,
|
|
845
|
-
expiresInMs: -1,
|
|
846
|
-
});
|
|
847
|
-
|
|
848
|
-
// Pre-create an active member
|
|
849
|
-
seedContactChannel({
|
|
850
|
-
sourceChannel: "telegram",
|
|
851
|
-
externalUserId: "expired-token-user",
|
|
852
|
-
status: "active",
|
|
853
|
-
});
|
|
854
|
-
|
|
855
|
-
// Expired token must return expired, not already_member
|
|
856
|
-
const outcome = await redeemInvite({
|
|
857
|
-
rawToken,
|
|
858
|
-
sourceChannel: "telegram",
|
|
859
|
-
externalUserId: "expired-token-user",
|
|
860
|
-
});
|
|
861
|
-
|
|
862
|
-
expect(outcome).toEqual({ ok: false, reason: "expired" });
|
|
863
|
-
});
|
|
864
|
-
|
|
865
|
-
test("returns channel_mismatch for an active member with a valid token for a different channel", async () => {
|
|
866
|
-
const targetContactId = createTargetContact();
|
|
867
|
-
// Create an invite for voice
|
|
868
|
-
const { rawToken } = createInvite({
|
|
869
|
-
sourceChannel: "phone",
|
|
870
|
-
contactId: targetContactId,
|
|
871
|
-
maxUses: 5,
|
|
872
|
-
});
|
|
873
|
-
|
|
874
|
-
// Pre-create an active member on telegram
|
|
875
|
-
seedContactChannel({
|
|
876
|
-
sourceChannel: "telegram",
|
|
877
|
-
externalUserId: "cross-channel-user",
|
|
878
|
-
status: "active",
|
|
879
|
-
});
|
|
880
|
-
|
|
881
|
-
// Valid token for wrong channel must return channel_mismatch, not already_member
|
|
882
|
-
const outcome = await redeemInvite({
|
|
883
|
-
rawToken,
|
|
884
|
-
sourceChannel: "telegram",
|
|
885
|
-
externalUserId: "cross-channel-user",
|
|
886
|
-
});
|
|
887
|
-
|
|
888
|
-
expect(outcome).toEqual({ ok: false, reason: "channel_mismatch" });
|
|
889
|
-
});
|
|
890
|
-
|
|
891
|
-
// ── Gateway lifecycle pre-check + redemption mirror ────────────────────
|
|
892
|
-
|
|
893
|
-
test("rejects redemption when the gateway claim is not consumable (no assistant mutation)", async () => {
|
|
894
|
-
const targetContactId = createTargetContact();
|
|
895
|
-
const { rawToken } = createInvite({
|
|
896
|
-
sourceChannel: "telegram",
|
|
897
|
-
contactId: targetContactId,
|
|
898
|
-
maxUses: 1,
|
|
899
|
-
});
|
|
900
|
-
|
|
901
|
-
// Gateway row exists but was NOT consumable (revoked/exhausted/raced).
|
|
902
|
-
gatewayIpc.claim = { ok: true, updated: false, mirrored: true };
|
|
903
|
-
|
|
904
|
-
const outcome = await redeemInvite({
|
|
905
|
-
rawToken,
|
|
906
|
-
sourceChannel: "telegram",
|
|
907
|
-
externalUserId: "gw-revoked-user",
|
|
908
|
-
});
|
|
909
|
-
|
|
910
|
-
expect(outcome).toEqual({ ok: false, reason: "invalid_token" });
|
|
911
|
-
|
|
912
|
-
// No member was created — the assistant DB was never mutated.
|
|
913
|
-
expect(
|
|
914
|
-
findContactChannel({
|
|
915
|
-
channelType: "telegram",
|
|
916
|
-
address: "gw-revoked-user",
|
|
917
|
-
}),
|
|
918
|
-
).toBeNull();
|
|
919
|
-
});
|
|
920
|
-
|
|
921
|
-
test("does not activate the member when recordInviteUse loses the race (returns false)", async () => {
|
|
922
|
-
const targetContactId = createTargetContact();
|
|
923
|
-
const { rawToken, invite } = createInvite({
|
|
924
|
-
sourceChannel: "telegram",
|
|
925
|
-
contactId: targetContactId,
|
|
926
|
-
maxUses: 1,
|
|
927
|
-
});
|
|
928
|
-
|
|
929
|
-
// Legacy gateway row so the claim proceeds past the gateway gate. Revoke
|
|
930
|
-
// the assistant invite during the claim — after pre-validation, before the
|
|
931
|
-
// use-bump — so `recordInviteUse` sees a non-active row and returns false.
|
|
932
|
-
gatewayIpc.claim = { ok: true, updated: false, mirrored: false };
|
|
933
|
-
onGatewayClaim = () => {
|
|
934
|
-
revokeStoreFn(invite.id);
|
|
935
|
-
};
|
|
936
|
-
|
|
937
|
-
const outcome = await redeemInvite({
|
|
938
|
-
rawToken,
|
|
939
|
-
sourceChannel: "telegram",
|
|
940
|
-
externalUserId: "raced-user",
|
|
941
|
-
});
|
|
942
|
-
|
|
943
|
-
expect(outcome).toEqual({ ok: false, reason: "invalid_token" });
|
|
944
|
-
|
|
945
|
-
// The member was NOT activated: no gateway upsert and no active channel.
|
|
946
|
-
expect(
|
|
947
|
-
gatewayIpc.calls.some((c) => c.method === "upsert_verified_channel"),
|
|
948
|
-
).toBe(false);
|
|
949
|
-
expect(
|
|
950
|
-
findContactChannel({ channelType: "telegram", address: "raced-user" }),
|
|
951
|
-
).toBeNull();
|
|
952
|
-
});
|
|
953
|
-
|
|
954
|
-
test("proceeds and mutates when the gateway claim is consumed (updated:true)", async () => {
|
|
955
|
-
const targetContactId = createTargetContact();
|
|
956
|
-
const { rawToken, invite } = createInvite({
|
|
957
|
-
sourceChannel: "telegram",
|
|
958
|
-
contactId: targetContactId,
|
|
959
|
-
maxUses: 1,
|
|
960
|
-
});
|
|
961
|
-
|
|
962
|
-
gatewayIpc.claim = { ok: true, updated: true, mirrored: true };
|
|
963
|
-
|
|
964
|
-
const outcome = await redeemInvite({
|
|
965
|
-
rawToken,
|
|
966
|
-
sourceChannel: "telegram",
|
|
967
|
-
externalUserId: "gw-active-user",
|
|
968
|
-
});
|
|
969
|
-
|
|
970
|
-
expect(outcome.ok).toBe(true);
|
|
971
|
-
|
|
972
|
-
// The claim was performed against the resolved invite id.
|
|
973
|
-
const claim = gatewayIpc.calls.find(
|
|
974
|
-
(c) => c.method === "record_invite_redemption",
|
|
975
|
-
);
|
|
976
|
-
expect(claim).toBeDefined();
|
|
977
|
-
expect(claim!.params).toMatchObject({ inviteId: invite.id });
|
|
978
|
-
|
|
979
|
-
// The assistant DB was mutated.
|
|
980
|
-
expect(
|
|
981
|
-
findContactChannel({
|
|
982
|
-
channelType: "telegram",
|
|
983
|
-
address: "gw-active-user",
|
|
984
|
-
}),
|
|
985
|
-
).not.toBeNull();
|
|
986
|
-
});
|
|
987
|
-
|
|
988
|
-
test("proceeds for a legacy invite the gateway has never seen (mirrored:false)", async () => {
|
|
989
|
-
const targetContactId = createTargetContact();
|
|
990
|
-
const { rawToken } = createInvite({
|
|
991
|
-
sourceChannel: "telegram",
|
|
992
|
-
contactId: targetContactId,
|
|
993
|
-
maxUses: 1,
|
|
994
|
-
});
|
|
995
|
-
|
|
996
|
-
gatewayIpc.claim = { ok: true, updated: false, mirrored: false };
|
|
997
|
-
|
|
998
|
-
const outcome = await redeemInvite({
|
|
999
|
-
rawToken,
|
|
1000
|
-
sourceChannel: "telegram",
|
|
1001
|
-
externalUserId: "legacy-user",
|
|
1002
|
-
});
|
|
1003
|
-
|
|
1004
|
-
expect(outcome.ok).toBe(true);
|
|
1005
|
-
expect(
|
|
1006
|
-
findContactChannel({ channelType: "telegram", address: "legacy-user" }),
|
|
1007
|
-
).not.toBeNull();
|
|
1008
|
-
});
|
|
1009
|
-
|
|
1010
|
-
test("fails open when the gateway claim throws (assistant-side checks still apply)", async () => {
|
|
1011
|
-
const targetContactId = createTargetContact();
|
|
1012
|
-
const { rawToken } = createInvite({
|
|
1013
|
-
sourceChannel: "telegram",
|
|
1014
|
-
contactId: targetContactId,
|
|
1015
|
-
maxUses: 1,
|
|
1016
|
-
});
|
|
1017
|
-
|
|
1018
|
-
gatewayIpc.claimThrows = true;
|
|
1019
|
-
|
|
1020
|
-
const outcome = await redeemInvite({
|
|
1021
|
-
rawToken,
|
|
1022
|
-
sourceChannel: "telegram",
|
|
1023
|
-
externalUserId: "failopen-user",
|
|
1024
|
-
});
|
|
1025
|
-
|
|
1026
|
-
expect(outcome.ok).toBe(true);
|
|
1027
|
-
expect(
|
|
1028
|
-
findContactChannel({ channelType: "telegram", address: "failopen-user" }),
|
|
1029
|
-
).not.toBeNull();
|
|
1030
|
-
});
|
|
1031
|
-
|
|
1032
|
-
test("does not claim the gateway row for an already-active member", async () => {
|
|
1033
|
-
const targetContactId = createTargetContact();
|
|
1034
|
-
const { rawToken } = createInvite({
|
|
1035
|
-
sourceChannel: "telegram",
|
|
1036
|
-
contactId: targetContactId,
|
|
1037
|
-
maxUses: 1,
|
|
1038
|
-
});
|
|
1039
|
-
|
|
1040
|
-
// Seed an already-active member bound to the invite's target contact.
|
|
1041
|
-
seedContactChannel({
|
|
1042
|
-
sourceChannel: "telegram",
|
|
1043
|
-
externalUserId: "already-member-user",
|
|
1044
|
-
role: "contact",
|
|
1045
|
-
status: "active",
|
|
1046
|
-
policy: "allow",
|
|
1047
|
-
contactId: targetContactId,
|
|
1048
|
-
});
|
|
1049
|
-
|
|
1050
|
-
const outcome = await redeemInvite({
|
|
1051
|
-
rawToken,
|
|
1052
|
-
sourceChannel: "telegram",
|
|
1053
|
-
externalUserId: "already-member-user",
|
|
1054
|
-
});
|
|
1055
|
-
|
|
1056
|
-
expect(outcome.ok).toBe(true);
|
|
1057
|
-
expect((outcome as { type: string }).type).toBe("already_member");
|
|
1058
|
-
|
|
1059
|
-
// No gateway claim was made — no use must be consumed for a non-redemption.
|
|
1060
|
-
expect(
|
|
1061
|
-
gatewayIpc.calls.some((c) => c.method === "record_invite_redemption"),
|
|
1062
|
-
).toBe(false);
|
|
1063
|
-
});
|
|
1064
|
-
|
|
1065
|
-
test("rejects 6-digit code redemption when the gateway claim is not consumable", async () => {
|
|
1066
|
-
const targetContactId = createTargetContact();
|
|
1067
|
-
const code = "654321";
|
|
1068
|
-
createInvite({
|
|
1069
|
-
sourceChannel: "telegram",
|
|
1070
|
-
contactId: targetContactId,
|
|
1071
|
-
maxUses: 1,
|
|
1072
|
-
inviteCodeHash: hashVoiceCode(code),
|
|
1073
|
-
});
|
|
1074
|
-
|
|
1075
|
-
gatewayIpc.claim = { ok: true, updated: false, mirrored: true };
|
|
1076
|
-
|
|
1077
|
-
const outcome = await redeemInviteByCode({
|
|
1078
|
-
code,
|
|
1079
|
-
sourceChannel: "telegram",
|
|
1080
|
-
externalUserId: "gw-revoked-code-user",
|
|
1081
|
-
});
|
|
1082
|
-
|
|
1083
|
-
expect(outcome).toEqual({ ok: false, reason: "invalid_token" });
|
|
1084
|
-
expect(
|
|
1085
|
-
findContactChannel({
|
|
1086
|
-
channelType: "telegram",
|
|
1087
|
-
address: "gw-revoked-code-user",
|
|
1088
|
-
}),
|
|
1089
|
-
).toBeNull();
|
|
1090
|
-
});
|
|
1091
|
-
|
|
1092
|
-
test("returns invalid_token (no throw) when the gateway refuses the activation", async () => {
|
|
1093
|
-
const targetContactId = createTargetContact();
|
|
1094
|
-
const { rawToken } = createInvite({
|
|
1095
|
-
sourceChannel: "telegram",
|
|
1096
|
-
contactId: targetContactId,
|
|
1097
|
-
maxUses: 1,
|
|
1098
|
-
});
|
|
1099
|
-
|
|
1100
|
-
// Gateway claim consumes the row, then refuses the channel activation
|
|
1101
|
-
// (blocked/revoked actor). The committed use is acceptable for this rare
|
|
1102
|
-
// blocked-backstop path; the outcome must be the branch failure, not a 500.
|
|
1103
|
-
gatewayIpc.activationVerified = false;
|
|
1104
|
-
|
|
1105
|
-
const outcome = await redeemInvite({
|
|
1106
|
-
rawToken,
|
|
1107
|
-
sourceChannel: "telegram",
|
|
1108
|
-
externalUserId: "refused-user",
|
|
1109
|
-
});
|
|
1110
|
-
|
|
1111
|
-
expect(outcome).toEqual({ ok: false, reason: "invalid_token" });
|
|
1112
|
-
});
|
|
1113
|
-
|
|
1114
|
-
test("redeems with the gateway channel id when the gateway verifies but the local mirror fails", async () => {
|
|
1115
|
-
const targetContactId = createTargetContact();
|
|
1116
|
-
const { rawToken, invite } = createInvite({
|
|
1117
|
-
sourceChannel: "telegram",
|
|
1118
|
-
contactId: targetContactId,
|
|
1119
|
-
maxUses: 1,
|
|
1120
|
-
});
|
|
1121
|
-
|
|
1122
|
-
// Gateway verifies and returns its channel; the best-effort local mirror
|
|
1123
|
-
// throws. The activation must still stand using the gateway channel id.
|
|
1124
|
-
gatewayIpc.activationChannelId = "gw-verified-channel";
|
|
1125
|
-
contactsWriteState.mirrorThrows = true;
|
|
1126
|
-
|
|
1127
|
-
const outcome = await redeemInvite({
|
|
1128
|
-
rawToken,
|
|
1129
|
-
sourceChannel: "telegram",
|
|
1130
|
-
externalUserId: "mirrorless-user",
|
|
1131
|
-
});
|
|
1132
|
-
|
|
1133
|
-
expect(outcome).toEqual({
|
|
1134
|
-
ok: true,
|
|
1135
|
-
type: "redeemed",
|
|
1136
|
-
memberId: "gw-verified-channel",
|
|
1137
|
-
inviteId: invite.id,
|
|
1138
|
-
});
|
|
1139
|
-
});
|
|
1140
|
-
});
|
|
1141
|
-
|
|
1142
|
-
describe("resolveMemberGateStatus", () => {
|
|
1143
|
-
const memberlessVerdict: TrustVerdict = {
|
|
1144
|
-
trustClass: "unverified_contact",
|
|
1145
|
-
canonicalSenderId: "telegram:blocked-user",
|
|
1146
|
-
};
|
|
1147
|
-
const memberVerdict: TrustVerdict = {
|
|
1148
|
-
trustClass: "trusted_contact",
|
|
1149
|
-
canonicalSenderId: "telegram:active-user",
|
|
1150
|
-
contactId: "contact-1",
|
|
1151
|
-
channelId: "channel-1",
|
|
1152
|
-
type: "telegram",
|
|
1153
|
-
address: "active-user",
|
|
1154
|
-
status: "active",
|
|
1155
|
-
policy: "allow",
|
|
1156
|
-
};
|
|
1157
|
-
|
|
1158
|
-
test("uses the verdict member status when the verdict resolves a member", async () => {
|
|
1159
|
-
expect(await resolveMemberGateStatus(memberVerdict, "blocked")).toBe(
|
|
1160
|
-
"active",
|
|
1161
|
-
);
|
|
1162
|
-
});
|
|
1163
|
-
|
|
1164
|
-
test("falls back to local status when a non-null verdict carries no member", async () => {
|
|
1165
|
-
// A previously blocked contact with a valid invite must stay blocked even
|
|
1166
|
-
// when the verdict is non-null but memberless (externalChatId-only match /
|
|
1167
|
-
// resolutionFailed), so it can't bypass the gate.
|
|
1168
|
-
expect(await resolveMemberGateStatus(memberlessVerdict, "blocked")).toBe(
|
|
1169
|
-
"blocked",
|
|
1170
|
-
);
|
|
1171
|
-
});
|
|
1172
|
-
|
|
1173
|
-
test("falls back to local status when the verdict is null", async () => {
|
|
1174
|
-
expect(await resolveMemberGateStatus(null, "blocked")).toBe("blocked");
|
|
1175
|
-
});
|
|
1176
|
-
|
|
1177
|
-
test("returns null when neither verdict member nor local status is present", async () => {
|
|
1178
|
-
expect(await resolveMemberGateStatus(memberlessVerdict, null)).toBeNull();
|
|
1179
|
-
});
|
|
1180
|
-
});
|