@vellumai/assistant 0.10.2 → 0.10.3-dev.202606252046.9075fd5
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/bun.lock +20 -0
- package/docs/workspace-tools.md +42 -33
- package/eslint-rules/cli-no-daemon-internals.js +6 -0
- package/node_modules/@vellumai/gateway-client/src/__tests__/guardian-delivery-contract.test.ts +91 -0
- package/node_modules/@vellumai/gateway-client/src/__tests__/trust-verdict-contract.test.ts +31 -0
- package/node_modules/@vellumai/gateway-client/src/gateway-ipc-contracts.ts +44 -0
- package/node_modules/@vellumai/gateway-client/src/guardian-delivery-contract.ts +48 -0
- package/node_modules/@vellumai/gateway-client/src/index.ts +14 -0
- package/node_modules/@vellumai/gateway-client/src/trust-verdict-contract.ts +17 -0
- package/node_modules/@vellumai/service-contracts/package.json +1 -0
- package/node_modules/@vellumai/service-contracts/src/__tests__/channels.test.ts +28 -0
- package/node_modules/@vellumai/service-contracts/src/channels.ts +41 -0
- package/node_modules/@vellumai/service-contracts/src/index.ts +1 -0
- package/openapi.yaml +196 -56
- package/package.json +4 -1
- package/scripts/test.sh +36 -15
- package/src/__tests__/actor-token-service.test.ts +36 -14
- package/src/__tests__/actor-trust-resolver-address-fallback.test.ts +83 -31
- package/src/__tests__/agent-loop-override-profile.test.ts +1 -0
- package/src/__tests__/agent-wake-disk-pressure-callsite.test.ts +2 -0
- package/src/__tests__/agent-wake-override-profile.test.ts +2 -0
- package/src/__tests__/annotate-activity-metadata.test.ts +2 -0
- package/src/__tests__/annotate-risk-options.test.ts +2 -0
- package/src/__tests__/approval-cascade.test.ts +2 -0
- package/src/__tests__/assistant-attachments.test.ts +42 -0
- package/src/__tests__/background-workers-disk-pressure.test.ts +2 -0
- package/src/__tests__/btw-routes.test.ts +2 -0
- package/src/__tests__/build-persisted-content.test.ts +2 -0
- package/src/__tests__/call-controller.test.ts +19 -0
- package/src/__tests__/channel-approval-routes.test.ts +21 -26
- package/src/__tests__/channel-delivery-store.test.ts +28 -0
- package/src/__tests__/channel-guardian.test.ts +164 -78
- package/src/__tests__/channel-inbound-disk-pressure.test.ts +11 -19
- package/src/__tests__/channel-reply-delivery.test.ts +2 -0
- package/src/__tests__/code-search-tool.test.ts +585 -0
- package/src/__tests__/compaction-events.test.ts +2 -0
- package/src/__tests__/compaction-ledger-store.test.ts +128 -0
- package/src/__tests__/compaction.benchmark.test.ts +2 -0
- package/src/__tests__/compactor-call-site-logging.test.ts +2 -0
- package/src/__tests__/compactor-low-watermark-cut.test.ts +2 -0
- package/src/__tests__/compactor-preserved-tail-count.test.ts +2 -0
- package/src/__tests__/compactor-summary-call-truncation.test.ts +2 -0
- package/src/__tests__/compactor-web-search-strip.test.ts +2 -0
- package/src/__tests__/config-loader-backfill.test.ts +123 -10
- package/src/__tests__/config-schema.test.ts +1 -0
- package/src/__tests__/confirmation-request-guardian-bridge.test.ts +31 -29
- package/src/__tests__/consult-deadline.test.ts +60 -0
- package/src/__tests__/contact-store-interaction-info.test.ts +156 -0
- package/src/__tests__/contact-store-user-file.test.ts +7 -10
- package/src/__tests__/contacts-relay-reads.test.ts +19 -24
- package/src/__tests__/contacts-write.test.ts +0 -2
- package/src/__tests__/conversation-abort-tool-results.test.ts +2 -0
- package/src/__tests__/conversation-agent-loop-disk-pressure.test.ts +2 -0
- package/src/__tests__/conversation-agent-loop-inference-profile.test.ts +2 -0
- package/src/__tests__/conversation-agent-loop-overflow.test.ts +2 -0
- package/src/__tests__/conversation-agent-loop.test.ts +134 -0
- package/src/__tests__/conversation-analysis-routes.test.ts +2 -0
- package/src/__tests__/conversation-app-control-lifecycle.test.ts +2 -0
- package/src/__tests__/conversation-attention-telegram.test.ts +9 -11
- package/src/__tests__/conversation-confirmation-signals.test.ts +2 -0
- package/src/__tests__/conversation-fork-crud.test.ts +354 -24
- package/src/__tests__/conversation-history-web-search.test.ts +2 -0
- package/src/__tests__/conversation-load-history-repair.test.ts +2 -0
- package/src/__tests__/conversation-load-history-stripped.test.ts +2 -0
- package/src/__tests__/conversation-pairing.test.ts +2 -0
- package/src/__tests__/conversation-process-app-control-preactivation.test.ts +2 -0
- package/src/__tests__/conversation-process-callsite.test.ts +2 -0
- package/src/__tests__/conversation-provider-retry-repair.test.ts +2 -0
- package/src/__tests__/conversation-queue.test.ts +91 -0
- package/src/__tests__/conversation-routes-guardian-reply.test.ts +14 -0
- package/src/__tests__/conversation-routes-slash-commands.test.ts +14 -0
- package/src/__tests__/conversation-slash-queue.test.ts +2 -0
- package/src/__tests__/conversation-slash-unknown.test.ts +2 -0
- package/src/__tests__/conversation-speed-override.test.ts +2 -0
- package/src/__tests__/conversation-surfaces-task-progress.test.ts +29 -0
- package/src/__tests__/conversation-title-service.test.ts +2 -0
- package/src/__tests__/conversation-tool-setup-attribution.test.ts +47 -0
- package/src/__tests__/conversation-usage.test.ts +2 -0
- package/src/__tests__/conversation-workspace-cache-state.test.ts +2 -0
- package/src/__tests__/conversation-workspace-injection.test.ts +2 -0
- package/src/__tests__/conversation-workspace-tool-tracking.test.ts +2 -0
- package/src/__tests__/credential-security-invariants.test.ts +1 -1
- package/src/__tests__/db-compaction-events-migration.test.ts +129 -0
- package/src/__tests__/db-migration-rollback.test.ts +205 -171
- package/src/__tests__/db-test-helpers.ts +5 -4
- package/src/__tests__/delete-propagation.test.ts +5 -3
- package/src/__tests__/deterministic-verification-control-plane.test.ts +4 -2
- package/src/__tests__/disk-pressure-guard.test.ts +41 -0
- package/src/__tests__/dm-backfill.test.ts +6 -4
- package/src/__tests__/dm-persistence.test.ts +2 -0
- package/src/__tests__/emit-signal-routing-intent.test.ts +7 -6
- package/src/__tests__/events-dev-bypass-actor.test.ts +7 -1
- package/src/__tests__/exploration-drift-hook.test.ts +3 -2
- package/src/__tests__/filing-service.test.ts +2 -0
- package/src/__tests__/guardian-binding-drift-heal.test.ts +104 -19
- package/src/__tests__/guardian-dispatch.test.ts +140 -1
- package/src/__tests__/guardian-expiry-notifier.test.ts +282 -0
- package/src/__tests__/guardian-outbound-http.test.ts +13 -0
- package/src/__tests__/guardian-routing-state.test.ts +6 -10
- package/src/__tests__/heartbeat-disk-pressure.test.ts +2 -0
- package/src/__tests__/heartbeat-service.test.ts +2 -0
- package/src/__tests__/helpers/channel-test-adapter.ts +46 -19
- package/src/__tests__/helpers/create-guardian-binding.ts +15 -23
- package/src/__tests__/helpers/mock-logger.ts +1 -0
- package/src/__tests__/helpers/seed-contact-channel.ts +96 -0
- package/src/__tests__/host-app-control-routes.test.ts +24 -30
- package/src/__tests__/host-bash-routes.test.ts +31 -41
- package/src/__tests__/host-browser-routes.test.ts +26 -32
- package/src/__tests__/host-cu-routes-targeted.test.ts +25 -33
- package/src/__tests__/host-file-routes-targeted.test.ts +40 -52
- package/src/__tests__/host-transfer-routes-targeted.test.ts +31 -43
- package/src/__tests__/http-user-message-parity.test.ts +290 -8
- package/src/__tests__/inbound-invite-redemption.test.ts +115 -10
- package/src/__tests__/inbound-slack-persistence.test.ts +2 -0
- package/src/__tests__/invite-redemption-service.test.ts +471 -53
- package/src/__tests__/invite-routes-http.test.ts +34 -0
- package/src/__tests__/invite-service-ipc.test.ts +65 -2
- package/src/__tests__/llm-context-normalization.test.ts +105 -0
- package/src/__tests__/llm-request-log-error-payload.test.ts +71 -9
- package/src/__tests__/llm-usage-store.test.ts +25 -0
- package/src/__tests__/mcp-config-secret-boundary.test.ts +392 -0
- package/src/__tests__/mcp-health-check.test.ts +2 -1
- package/src/__tests__/media-stream-server-integration.test.ts +127 -0
- package/src/__tests__/memory-retrieval-hook.test.ts +2 -0
- package/src/__tests__/messaging-send-tool.test.ts +2 -0
- package/src/__tests__/migration-import-from-url.test.ts +2 -2
- package/src/__tests__/mtime-cache.test.ts +275 -5
- package/src/__tests__/native-web-search.test.ts +2 -0
- package/src/__tests__/non-member-access-request.test.ts +191 -17
- package/src/__tests__/notification-broadcaster.test.ts +4 -0
- package/src/__tests__/notification-decision-recipient-context.test.ts +33 -32
- package/src/__tests__/notification-deep-link.test.ts +6 -0
- package/src/__tests__/notification-guardian-path.test.ts +19 -0
- package/src/__tests__/onboarding-persona-write.test.ts +52 -22
- package/src/__tests__/openai-provider.test.ts +22 -12
- package/src/__tests__/openai-responses-provider.test.ts +12 -2
- package/src/__tests__/outbound-slack-persistence.test.ts +2 -0
- package/src/__tests__/pending-interactions-resolved-event.test.ts +7 -4
- package/src/__tests__/persist-onboarding-artifacts.test.ts +1 -0
- package/src/__tests__/persistence-secret-redaction.test.ts +2 -0
- package/src/__tests__/persona-resolver.test.ts +75 -45
- package/src/__tests__/plugin-bootstrap.test.ts +21 -83
- package/src/__tests__/plugin-disabled-state.test.ts +190 -0
- package/src/__tests__/plugin-pipeline.test.ts +96 -0
- package/src/__tests__/plugin-route-contribution.test.ts +6 -19
- package/src/__tests__/plugin-tool-contribution.test.ts +5 -20
- package/src/__tests__/plugin-types.test.ts +2 -4
- package/src/__tests__/process-message-background-slack.test.ts +2 -0
- package/src/__tests__/process-message-display-content.test.ts +2 -0
- package/src/__tests__/provider-error-scenarios.test.ts +5 -4
- package/src/__tests__/provider-usage-tracking.test.ts +39 -0
- package/src/__tests__/reaction-intercept-cold-cache-warm.test.ts +135 -0
- package/src/__tests__/reaction-intercept-member-verdict-warm.test.ts +158 -0
- package/src/__tests__/reaction-persistence.test.ts +51 -4
- package/src/__tests__/regenerate-fire-and-forget-trace.test.ts +2 -0
- package/src/__tests__/registry.test.ts +4 -1
- package/src/__tests__/relay-server.test.ts +758 -32
- package/src/__tests__/runtime-attachment-metadata.test.ts +9 -12
- package/src/__tests__/secret-ingress-http.test.ts +14 -0
- package/src/__tests__/send-endpoint-busy.test.ts +30 -8
- package/src/__tests__/settings-routes.test.ts +32 -0
- package/src/__tests__/skills.test.ts +44 -0
- package/src/__tests__/slack-inbound-verification.test.ts +47 -2
- package/src/__tests__/sse-actor-principal-guardian-source.test.ts +79 -0
- package/src/__tests__/steer-on-enqueue-question.test.ts +181 -0
- package/src/__tests__/stt-hints.test.ts +49 -15
- package/src/__tests__/subagent-detail.test.ts +27 -0
- package/src/__tests__/subagent-disposal.test.ts +65 -0
- package/src/__tests__/subagent-fork-prompt-role.test.ts +195 -0
- package/src/__tests__/subagent-fork-spawn.test.ts +6 -7
- package/src/__tests__/subagent-notify-parent.test.ts +2 -0
- package/src/__tests__/subagent-role-registry.test.ts +24 -6
- package/src/__tests__/subagent-spawn-and-await.test.ts +546 -0
- package/src/__tests__/subagent-spawn-tool-fork.test.ts +2 -0
- package/src/__tests__/subagent-tools.test.ts +398 -1
- package/src/__tests__/suggestion-routes.test.ts +2 -0
- package/src/__tests__/thread-backfill.test.ts +3 -3
- package/src/__tests__/title-generate-hook.test.ts +2 -0
- package/src/__tests__/tool-executor-lifecycle-events.test.ts +2 -0
- package/src/__tests__/tool-executor.test.ts +16 -11
- package/src/__tests__/tool-preview-lifecycle.test.ts +2 -0
- package/src/__tests__/tool-result-metadata-plumbing.test.ts +2 -0
- package/src/__tests__/tool-start-timestamp.test.ts +2 -0
- package/src/__tests__/trusted-contact-approval-notifier.test.ts +37 -51
- package/src/__tests__/trusted-contact-inline-approval-integration.test.ts +12 -12
- package/src/__tests__/trusted-contact-lifecycle-notifications.test.ts +9 -7
- package/src/__tests__/trusted-contact-multichannel.test.ts +16 -7
- package/src/__tests__/trusted-contact-verification.test.ts +79 -54
- package/src/__tests__/twilio-routes.test.ts +96 -0
- package/src/__tests__/ui-file-upload-surface.test.ts +86 -0
- package/src/__tests__/verification-control-plane-policy.test.ts +2 -0
- package/src/__tests__/voice-guardian-cold-cache-warm.test.ts +137 -0
- package/src/__tests__/voice-invite-redemption.test.ts +216 -20
- package/src/__tests__/web-search-backend-failure.test.ts +2 -0
- package/src/__tests__/workspace-migration-102-preserve-heartbeat-enabled-for-existing-workspaces.test.ts +3 -3
- package/src/__tests__/workspace-migration-111-prune-seeded-callsite-defaults.test.ts +208 -0
- package/src/__tests__/workspace-migration-112-remove-advisor-callsite-override.test.ts +170 -0
- package/src/__tests__/workspace-migration-drop-user-md.test.ts +196 -238
- package/src/__tests__/workspace-migration-remove-hooks.test.ts +14 -35
- package/src/__tests__/workspace-tool-loader.test.ts +195 -2
- package/src/a2a/__tests__/e2e-a2a-channel.test.ts +35 -47
- package/src/agent/loop-exclusive-tool.test.ts +154 -0
- package/src/agent/loop-native-web-search.test.ts +200 -0
- package/src/agent/loop.ts +164 -1
- package/src/api/constants/sse-replay.ts +41 -0
- package/src/api/events/conversation-notice.ts +26 -0
- package/src/api/index.ts +19 -1
- package/src/api/responses/llm-request-log-entry.ts +29 -0
- package/src/api/responses/subagent-detail.ts +17 -0
- package/src/api/surfaces.ts +39 -3
- package/src/approvals/guardian-channel-delivery.ts +30 -0
- package/src/approvals/guardian-expiry-notifier.ts +148 -0
- package/src/approvals/guardian-request-resolvers.ts +17 -15
- package/src/calls/__tests__/relay-setup-router.test.ts +267 -17
- package/src/calls/call-domain.ts +3 -3
- package/src/calls/guardian-dispatch.ts +14 -9
- package/src/calls/inbound-trust-reader.ts +24 -2
- package/src/calls/media-stream-server.ts +21 -0
- package/src/calls/relay-access-wait.ts +6 -6
- package/src/calls/relay-server.ts +188 -51
- package/src/calls/relay-setup-router.ts +47 -17
- package/src/calls/relay-verification.ts +4 -4
- package/src/calls/stt-hints.ts +9 -12
- package/src/calls/twilio-routes.ts +14 -4
- package/src/channels/types.ts +10 -20
- package/src/cli/commands/__tests__/cache.test.ts +8 -1
- package/src/cli/commands/__tests__/conversations-slack.test.ts +1 -0
- package/src/cli/commands/cache.ts +194 -181
- package/src/cli/commands/contacts.ts +10 -7
- package/src/cli/commands/db/__tests__/repair.test.ts +6 -5
- package/src/cli/commands/db/status.ts +37 -1
- package/src/cli/commands/mcp.ts +252 -218
- package/src/cli/commands/memory/__tests__/worker.test.ts +432 -0
- package/src/cli/commands/memory/index.ts +2 -0
- package/src/cli/commands/memory/worker.ts +242 -0
- package/src/cli/commands/plugins.ts +125 -13
- package/src/cli/lib/__tests__/install-from-github.test.ts +102 -0
- package/src/cli/lib/__tests__/list-installed-plugins.test.ts +160 -1
- package/src/cli/lib/__tests__/publish-plugin.test.ts +404 -0
- package/src/cli/lib/list-installed-plugins.ts +179 -1
- package/src/cli/lib/publish-plugin.ts +633 -0
- package/src/config/__tests__/loader-callsite-strip-fallback.test.ts +143 -0
- package/src/config/__tests__/sync-gated-profiles.test.ts +16 -10
- package/src/config/bundled-skills/contacts/tools/contact-merge.ts +27 -17
- package/src/config/bundled-skills/contacts/tools/contact-search.ts +13 -3
- package/src/config/bundled-skills/subagent/SKILL.md +17 -2
- package/src/config/bundled-skills/subagent/TOOLS.json +5 -4
- package/src/config/call-site-defaults.ts +0 -6
- package/src/config/feature-flag-registry.json +5 -13
- package/src/config/llm-resolver.ts +0 -3
- package/src/config/loader.ts +38 -5
- package/src/config/prune-seeded-callsite-defaults.ts +110 -0
- package/src/config/schemas/__tests__/memory-v3.test.ts +1 -0
- package/src/config/schemas/call-site-catalog.ts +0 -7
- package/src/config/schemas/heartbeat.ts +2 -5
- package/src/config/schemas/llm.ts +3 -12
- package/src/config/schemas/memory-lifecycle.ts +12 -0
- package/src/config/schemas/memory-v3.ts +7 -0
- package/src/config/schemas/memory.ts +4 -0
- package/src/config/schemas/timeouts.ts +8 -0
- package/src/config/seed-inference-profiles.ts +30 -34
- package/src/config/skills.ts +27 -5
- package/src/config/sync-gated-profiles.ts +12 -16
- package/src/contacts/__tests__/contacts-write-revoke-relay.test.ts +128 -0
- package/src/contacts/__tests__/guardian-delivery-reader.test.ts +312 -0
- package/src/contacts/__tests__/member-write-relay.test.ts +226 -0
- package/src/contacts/contact-store.ts +27 -237
- package/src/contacts/contacts-write.ts +18 -55
- package/src/contacts/gateway-channel-read.ts +51 -0
- package/src/contacts/guardian-delivery-reader.ts +223 -0
- package/src/contacts/member-write-relay.ts +183 -0
- package/src/contacts/types.ts +3 -15
- package/src/daemon/__tests__/conversation-tool-setup-exclude.test.ts +35 -0
- package/src/daemon/__tests__/conversation-tool-setup.test.ts +0 -44
- package/src/daemon/assistant-attachments.ts +27 -4
- package/src/daemon/conversation-agent-loop.ts +28 -0
- package/src/daemon/conversation-notices.ts +60 -0
- package/src/daemon/conversation-process.ts +35 -16
- package/src/daemon/conversation-surfaces.ts +111 -38
- package/src/daemon/conversation-tool-setup.ts +71 -30
- package/src/daemon/conversation.ts +23 -1
- package/src/daemon/disk-pressure-guard.ts +12 -2
- package/src/daemon/event-loop-watchdog.ts +28 -1
- package/src/daemon/external-plugins-bootstrap.ts +17 -41
- package/src/daemon/handlers/__tests__/config-a2a-accept.test.ts +0 -1
- package/src/daemon/handlers/__tests__/config-a2a-complete.test.ts +0 -2
- package/src/daemon/handlers/__tests__/config-a2a-redeem.test.ts +25 -2
- package/src/daemon/handlers/__tests__/config-channels.test.ts +220 -0
- package/src/daemon/handlers/config-a2a.ts +6 -14
- package/src/daemon/handlers/config-channels.ts +67 -26
- package/src/daemon/handlers/conversations.ts +77 -0
- package/src/daemon/lifecycle.ts +16 -0
- package/src/daemon/mcp-reload-service.ts +10 -0
- package/src/daemon/memory-v2-startup.test.ts +72 -0
- package/src/daemon/memory-v2-startup.ts +87 -19
- package/src/daemon/message-types/conversations.ts +2 -0
- package/src/daemon/message-types/surfaces.ts +12 -12
- package/src/daemon/server.ts +0 -4
- package/src/daemon/shutdown-handlers.ts +20 -0
- package/src/daemon/tool-setup-types.ts +9 -0
- package/src/heartbeat/heartbeat-service.ts +5 -0
- package/src/home/relationship-state-writer.ts +5 -0
- package/src/hooks/hook-loader.ts +341 -0
- package/src/ipc/__tests__/clients-list-ipc.test.ts +1 -1
- package/src/ipc/assistant-server.ts +2 -2
- package/src/mcp/__tests__/mcp-auth-orchestrator.test.ts +1 -0
- package/src/mcp/client.ts +15 -1
- package/src/mcp/mcp-auth-orchestrator.ts +6 -1
- package/src/mcp/mcp-header-store.ts +134 -0
- package/src/mcp/mcp-oauth-provider.ts +19 -8
- package/src/memory/__tests__/301-create-watchdog-events.test.ts +110 -0
- package/src/memory/__tests__/memory-retrospective-job.test.ts +8 -0
- package/src/memory/__tests__/prompt-override.test.ts +192 -0
- package/src/memory/__tests__/watchdog-events-store.test.ts +161 -0
- package/src/memory/compaction-ledger-store.ts +107 -0
- package/src/memory/conversation-crud.ts +129 -61
- package/src/memory/db-connection.ts +22 -3
- package/src/memory/db-init.ts +37 -503
- package/src/memory/db-singleton.ts +6 -4
- package/src/memory/jobs-worker.ts +104 -0
- package/src/memory/llm-request-log-store.ts +26 -1
- package/src/memory/llm-usage-store.ts +48 -20
- package/src/memory/memory-retrospective-job.ts +14 -8
- package/src/memory/migrations/209-strip-thinking-from-consolidated.ts +152 -56
- package/src/memory/migrations/300-add-processing-started-at.ts +30 -0
- package/src/memory/migrations/301-create-watchdog-events.ts +45 -0
- package/src/memory/migrations/302-create-compaction-events.ts +107 -0
- package/src/memory/migrations/__tests__/209-strip-thinking-from-consolidated.test.ts +297 -0
- package/src/memory/migrations/__tests__/run-migrations.test.ts +2 -2
- package/src/memory/migrations/run-migrations.ts +106 -10
- package/src/memory/migrations/validate-migration-state.ts +105 -67
- package/src/memory/prompt-override.ts +129 -0
- package/src/memory/schema/contacts.ts +6 -2
- package/src/memory/schema/conversations.ts +37 -0
- package/src/memory/schema/infrastructure.ts +20 -0
- package/src/memory/steps.ts +1294 -0
- package/src/memory/v2/__tests__/cli-command-store.test.ts +25 -0
- package/src/memory/v2/__tests__/skill-store.test.ts +80 -0
- package/src/memory/v2/cli-command-store.ts +75 -38
- package/src/memory/v2/prompts/consolidation.ts +13 -82
- package/src/memory/v2/prompts/router.ts +21 -93
- package/src/memory/v2/skill-store.ts +68 -31
- package/src/memory/watchdog-events-store.ts +87 -0
- package/src/memory/worker-control.ts +204 -0
- package/src/memory/worker-process.ts +89 -0
- package/src/messaging/channel-binding-metadata.ts +31 -0
- package/src/messaging/provider-types.ts +8 -0
- package/src/messaging/providers/slack/binding-metadata.ts +60 -0
- package/src/notifications/__tests__/broadcaster.test.ts +8 -8
- package/src/notifications/__tests__/connected-channels.test.ts +86 -0
- package/src/notifications/__tests__/decision-engine.test.ts +78 -9
- package/src/notifications/__tests__/destination-resolver.test.ts +151 -0
- package/src/notifications/broadcaster.ts +8 -1
- package/src/notifications/decision-engine.ts +15 -7
- package/src/notifications/destination-resolver.ts +53 -25
- package/src/notifications/emit-signal.ts +34 -15
- package/src/onboarding/checkin-event.test.ts +222 -0
- package/src/onboarding/checkin-event.ts +321 -0
- package/src/onboarding/schedule-checkin.ts +190 -0
- package/src/permissions/question-prompter.test.ts +1 -1
- package/src/permissions/question-prompter.ts +7 -4
- package/src/plugin-api/index.ts +12 -12
- package/src/plugin-api/types.ts +12 -14
- package/src/plugin-api/vision-support.test.ts +28 -4
- package/src/plugin-api/vision-support.ts +66 -31
- package/src/plugins/defaults/empty-response/hooks/post-model-call.ts +2 -2
- package/src/plugins/defaults/empty-response/hooks/stop.ts +2 -2
- package/src/plugins/defaults/exploration-drift/hooks/post-tool-use.ts +4 -3
- package/src/plugins/defaults/history-repair/hooks/post-model-call.ts +2 -2
- package/src/plugins/defaults/history-repair/hooks/stop.ts +2 -2
- package/src/plugins/defaults/history-repair/hooks/user-prompt-submit.ts +2 -2
- package/src/plugins/defaults/image-fallback/__tests__/image-fallback.test.ts +47 -7
- package/src/plugins/defaults/image-fallback/hooks/post-tool-use.ts +12 -13
- package/src/plugins/defaults/image-fallback/hooks/user-prompt-submit.ts +14 -22
- package/src/plugins/defaults/image-fallback/src/caption-blocks.ts +42 -11
- package/src/plugins/defaults/image-recovery/hooks/post-model-call.ts +2 -2
- package/src/plugins/defaults/image-recovery/hooks/stop.ts +2 -2
- package/src/plugins/defaults/index.ts +0 -35
- package/src/plugins/defaults/max-tokens-continue/hooks/post-model-call.ts +2 -2
- package/src/plugins/defaults/max-tokens-continue/hooks/stop.ts +2 -2
- package/src/plugins/defaults/memory-retrieval/hooks/post-compact.ts +2 -2
- package/src/plugins/defaults/memory-retrieval/hooks/user-prompt-submit.ts +2 -2
- package/src/plugins/defaults/memory-v3-shadow/__tests__/injection.test.ts +33 -3
- package/src/plugins/defaults/memory-v3-shadow/__tests__/pool-select.test.ts +48 -4
- package/src/plugins/defaults/memory-v3-shadow/__tests__/shadow-plugin.test.ts +4 -8
- package/src/plugins/defaults/memory-v3-shadow/hooks/post-compact.ts +2 -2
- package/src/plugins/defaults/memory-v3-shadow/hooks/user-prompt-submit.ts +2 -2
- package/src/plugins/defaults/memory-v3-shadow/injector.ts +43 -15
- package/src/plugins/defaults/memory-v3-shadow/orchestrate.ts +11 -2
- package/src/plugins/defaults/memory-v3-shadow/pool-select.test.ts +146 -0
- package/src/plugins/defaults/memory-v3-shadow/pool-select.ts +77 -13
- package/src/plugins/defaults/memory-v3-shadow/shadow-plugin.ts +12 -11
- package/src/plugins/defaults/surface-completion-nudge/hooks/post-model-call.ts +2 -2
- package/src/plugins/defaults/surface-completion-nudge/hooks/stop.ts +2 -2
- package/src/plugins/defaults/task-progress-nudge/hooks/post-tool-use.ts +2 -2
- package/src/plugins/defaults/title-generate/hooks/stop.ts +2 -2
- package/src/plugins/defaults/title-generate/hooks/user-prompt-submit.ts +2 -2
- package/src/plugins/defaults/tool-error/hooks/post-tool-use.ts +2 -2
- package/src/plugins/defaults/tool-result-truncate/hooks/post-tool-use.ts +2 -2
- package/src/plugins/disabled-state.ts +31 -0
- package/src/plugins/external-plugin-loader.ts +2 -2
- package/src/plugins/mtime-cache.ts +186 -330
- package/src/plugins/pipeline.ts +111 -13
- package/src/plugins/registry.ts +59 -16
- package/src/plugins/surface-import.ts +121 -0
- package/src/plugins/types.ts +7 -9
- package/src/prompts/persona-resolver.ts +43 -11
- package/src/providers/anthropic/client.ts +5 -0
- package/src/providers/call-site-routing.ts +45 -0
- package/src/providers/model-catalog.ts +16 -0
- package/src/providers/openai/__tests__/api-error-normalization.test.ts +321 -0
- package/src/providers/openai/api-error-normalization.ts +270 -0
- package/src/providers/openai/chat-completions-provider.ts +37 -83
- package/src/providers/openai/responses-provider.ts +50 -46
- package/src/providers/openrouter/client.ts +5 -0
- package/src/providers/provider-send-message.ts +10 -0
- package/src/providers/ratelimit.ts +10 -0
- package/src/providers/registry.ts +1 -1
- package/src/providers/retry.ts +10 -0
- package/src/providers/types.ts +22 -0
- package/src/providers/usage-tracking.ts +10 -0
- package/src/runtime/__tests__/channel-verification-service.test.ts +133 -0
- package/src/runtime/__tests__/guardian-vellum-migration.test.ts +184 -0
- package/src/runtime/__tests__/is-guardian-bound-for-channel.test.ts +66 -0
- package/src/runtime/__tests__/local-principal-trust.test.ts +162 -0
- package/src/runtime/__tests__/member-verdict-cache.test.ts +119 -0
- package/src/runtime/__tests__/trust-verdict-consumer.test.ts +402 -123
- package/src/runtime/access-request-helper.ts +18 -39
- package/src/runtime/actor-trust-resolver.ts +46 -19
- package/src/runtime/anchored-guardian.test.ts +109 -0
- package/src/runtime/anchored-guardian.ts +86 -0
- package/src/runtime/assistant-event-hub.ts +1 -1
- package/src/runtime/assistant-stream-state.ts +9 -2
- package/src/runtime/auth/__tests__/require-bound-guardian.test.ts +99 -0
- package/src/runtime/auth/require-bound-guardian.ts +21 -11
- package/src/runtime/channel-verification-service.ts +56 -31
- package/src/runtime/confirmation-request-guardian-bridge.ts +3 -3
- package/src/runtime/guardian-vellum-migration.ts +69 -8
- package/src/runtime/invite-redemption-service.ts +213 -187
- package/src/runtime/local-actor-identity.test.ts +108 -0
- package/src/runtime/local-actor-identity.ts +85 -13
- package/src/runtime/local-principal-trust.ts +52 -0
- package/src/runtime/member-verdict-cache.ts +0 -0
- package/src/runtime/pending-interactions.ts +11 -1
- package/src/runtime/routes/__tests__/channel-verification-revoke.test.ts +56 -5
- package/src/runtime/routes/__tests__/channel-verification-routes.test.ts +1 -1
- package/src/runtime/routes/__tests__/contact-routes.test.ts +305 -0
- package/src/runtime/routes/__tests__/global-search-routes.test.ts +92 -0
- package/src/runtime/routes/__tests__/http-adapter-actor-header.test.ts +129 -0
- package/src/runtime/routes/__tests__/surface-action-routes.test.ts +188 -0
- package/src/runtime/routes/browser-routes.ts +1 -1
- package/src/runtime/routes/canonical-guardian-expiry-sweep.ts +13 -5
- package/src/runtime/routes/channel-verification-routes.ts +3 -3
- package/src/runtime/routes/contact-routes.ts +48 -57
- package/src/runtime/routes/conversation-cli-routes.ts +4 -5
- package/src/runtime/routes/conversation-list-routes.ts +4 -7
- package/src/runtime/routes/conversation-query-routes.ts +72 -0
- package/src/runtime/routes/conversation-routes.ts +84 -85
- package/src/runtime/routes/events-routes.ts +2 -2
- package/src/runtime/routes/global-search-routes.ts +3 -1
- package/src/runtime/routes/guardian-action-routes.ts +4 -5
- package/src/runtime/routes/host-app-control-routes.ts +5 -4
- package/src/runtime/routes/host-bash-routes.ts +5 -4
- package/src/runtime/routes/host-browser-routes.ts +9 -11
- package/src/runtime/routes/host-cu-routes.ts +5 -4
- package/src/runtime/routes/host-file-routes.ts +5 -4
- package/src/runtime/routes/host-transfer-routes.ts +6 -6
- package/src/runtime/routes/http-adapter.ts +16 -1
- package/src/runtime/routes/identity-routes.ts +3 -2
- package/src/runtime/routes/inbound-message-handler.ts +5 -5
- package/src/runtime/routes/inbound-stages/acl-enforcement.test.ts +97 -5
- package/src/runtime/routes/inbound-stages/acl-enforcement.ts +61 -59
- package/src/runtime/routes/inbound-stages/background-dispatch.ts +13 -5
- package/src/runtime/routes/inbound-stages/escalation-intercept.ts +7 -7
- package/src/runtime/routes/inbound-stages/guardian-activation-intercept.test.ts +21 -8
- package/src/runtime/routes/inbound-stages/guardian-activation-intercept.ts +14 -3
- package/src/runtime/routes/inbound-stages/reaction-intercept.ts +19 -0
- package/src/runtime/routes/index.ts +2 -0
- package/src/runtime/routes/llm-context-normalization.ts +83 -0
- package/src/runtime/routes/mcp-auth-routes.ts +171 -19
- package/src/runtime/routes/migration-rollback-routes.ts +4 -3
- package/src/runtime/routes/migration-routes.ts +4 -1
- package/src/runtime/routes/onboarding-checkin-routes.ts +86 -0
- package/src/runtime/routes/settings-routes.ts +8 -3
- package/src/runtime/routes/subagents-routes.ts +5 -0
- package/src/runtime/routes/surface-action-routes.ts +42 -56
- package/src/runtime/services/__tests__/conversation-serializer.test.ts +1 -0
- package/src/runtime/services/conversation-serializer.ts +13 -58
- package/src/runtime/tool-grant-request-helper.ts +3 -3
- package/src/runtime/trust-verdict-consumer.ts +111 -40
- package/src/runtime/verification-outbound-actions.ts +18 -18
- package/src/signals/user-message.ts +16 -0
- package/src/subagent/__tests__/consult-prompt.test.ts +35 -0
- package/src/{plugins/defaults/advisor/__tests__/transcript.test.ts → subagent/__tests__/consult-transcript.test.ts} +47 -10
- package/src/{plugins/defaults/advisor/steering.ts → subagent/consult-prompt.ts} +22 -32
- package/src/{plugins/defaults/advisor/transcript.ts → subagent/consult-transcript.ts} +18 -8
- package/src/subagent/index.ts +1 -1
- package/src/subagent/manager.ts +254 -33
- package/src/subagent/types.ts +11 -4
- package/src/telemetry/types.ts +34 -1
- package/src/telemetry/usage-telemetry-reporter.test.ts +3 -2
- package/src/telemetry/usage-telemetry-reporter.ts +87 -3
- package/src/tools/ask-question/ask-question-tool.test.ts +29 -0
- package/src/tools/ask-question/ask-question-tool.ts +13 -0
- package/src/tools/executor.ts +4 -4
- package/src/tools/filesystem/search.ts +543 -0
- package/src/tools/registry.ts +38 -0
- package/src/tools/shared/filesystem/path-policy.ts +12 -5
- package/src/tools/subagent/consult-deadline.ts +49 -0
- package/src/tools/subagent/spawn.ts +234 -5
- package/src/tools/tool-approval-handler.ts +1 -1
- package/src/tools/tool-defaults.ts +9 -2
- package/src/tools/tool-manifest.ts +3 -0
- package/src/tools/types.ts +131 -25
- package/src/tools/workspace-tools/loader.ts +348 -244
- package/src/util/errors.ts +26 -1
- package/src/util/logger.ts +9 -0
- package/src/util/platform.ts +19 -0
- package/src/util/telemetry-db-path.ts +24 -0
- package/src/workflows/library.test.ts +140 -0
- package/src/workflows/library.ts +82 -28
- package/src/workspace/migrations/017-seed-persona-dirs.ts +3 -34
- package/src/workspace/migrations/019-scope-journal-to-guardian.ts +3 -24
- package/src/workspace/migrations/031-drop-user-md.ts +232 -148
- package/src/workspace/migrations/048-remove-workspace-hooks.ts +14 -66
- package/src/workspace/migrations/111-prune-seeded-callsite-defaults.ts +134 -0
- package/src/workspace/migrations/112-remove-advisor-callsite-override.ts +64 -0
- package/src/workspace/migrations/registry.ts +4 -0
- package/src/__tests__/workspace-tools-watcher-flag.test.ts +0 -70
- package/src/daemon/workspace-tools-watcher.ts +0 -328
- package/src/memory/migrations/registry.ts +0 -573
- package/src/plugins/defaults/advisor/__tests__/advisor-gate.test.ts +0 -56
- package/src/plugins/defaults/advisor/__tests__/advisor-state-store.test.ts +0 -43
- package/src/plugins/defaults/advisor/__tests__/agent-loop-integration.test.ts +0 -137
- package/src/plugins/defaults/advisor/__tests__/consult.test.ts +0 -153
- package/src/plugins/defaults/advisor/__tests__/hooks.test.ts +0 -138
- package/src/plugins/defaults/advisor/advisor-gate.ts +0 -29
- package/src/plugins/defaults/advisor/advisor-state-store.ts +0 -94
- package/src/plugins/defaults/advisor/config.ts +0 -21
- package/src/plugins/defaults/advisor/consult.ts +0 -93
- package/src/plugins/defaults/advisor/hooks/post-model-call.ts +0 -34
- package/src/plugins/defaults/advisor/hooks/pre-model-call.ts +0 -30
- package/src/plugins/defaults/advisor/hooks/user-prompt-submit.ts +0 -19
- package/src/plugins/defaults/advisor/package.json +0 -14
- package/src/plugins/defaults/advisor/tools/advisor.ts +0 -65
- package/src/providers/openai/__tests__/api-error-detail.test.ts +0 -120
|
@@ -7,11 +7,16 @@
|
|
|
7
7
|
* persisted, or returned in the outcome.
|
|
8
8
|
*/
|
|
9
9
|
|
|
10
|
+
import {
|
|
11
|
+
getInboundTrustVerdict,
|
|
12
|
+
getPhoneCallerVerdict,
|
|
13
|
+
} from "../calls/inbound-trust-reader.js";
|
|
10
14
|
import type { ChannelId } from "../channels/types.js";
|
|
11
15
|
import { findContactChannel, getContact } from "../contacts/contact-store.js";
|
|
12
|
-
import {
|
|
16
|
+
import { gatewayContactChannelState } from "../contacts/gateway-channel-read.js";
|
|
17
|
+
import { activateMemberChannel } from "../contacts/member-write-relay.js";
|
|
18
|
+
import type { ChannelStatus, ContactChannel } from "../contacts/types.js";
|
|
13
19
|
import { ipcCallPersistent } from "../ipc/gateway-client.js";
|
|
14
|
-
import { getSqlite } from "../memory/db-connection.js";
|
|
15
20
|
import {
|
|
16
21
|
findActiveVoiceInvites,
|
|
17
22
|
findByInviteCodeHash,
|
|
@@ -23,9 +28,41 @@ import {
|
|
|
23
28
|
import { canonicalizeInboundIdentity } from "../util/canonicalize-identity.js";
|
|
24
29
|
import { getLogger } from "../util/logger.js";
|
|
25
30
|
import { hashVoiceCode } from "../util/voice-code.js";
|
|
31
|
+
import { verdictMemberFromVerdict } from "./trust-verdict-consumer.js";
|
|
26
32
|
|
|
27
33
|
const log = getLogger("invite-redemption-service");
|
|
28
34
|
|
|
35
|
+
/**
|
|
36
|
+
* Resolve the sender's existing member status for the already_member/blocked
|
|
37
|
+
* gate from the gateway trust verdict. Falls back to the gateway-sourced channel
|
|
38
|
+
* status when the verdict is absent or carries no resolvable member status (e.g.
|
|
39
|
+
* an externalChatId-only match or a resolutionFailed verdict), so a blocked
|
|
40
|
+
* contact can't bypass the gate.
|
|
41
|
+
*/
|
|
42
|
+
export async function resolveMemberGateStatus(
|
|
43
|
+
verdict: Awaited<ReturnType<typeof getInboundTrustVerdict>>,
|
|
44
|
+
fallbackStatus: ChannelStatus | null,
|
|
45
|
+
): Promise<ChannelStatus | null> {
|
|
46
|
+
const memberStatus = verdict
|
|
47
|
+
? verdictMemberFromVerdict(verdict)?.status
|
|
48
|
+
: null;
|
|
49
|
+
return memberStatus ?? fallbackStatus;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
/**
|
|
53
|
+
* Gateway-sourced status for an existing local channel, used as the gate-status
|
|
54
|
+
* fallback when the verdict resolves no member. The local row is only located by
|
|
55
|
+
* identity; its status is read from the gateway (ACL source of truth), never the
|
|
56
|
+
* local column.
|
|
57
|
+
*/
|
|
58
|
+
async function gatewayFallbackStatus(
|
|
59
|
+
channel: Pick<ContactChannel, "contactId" | "type" | "address"> | null,
|
|
60
|
+
): Promise<ChannelStatus | null> {
|
|
61
|
+
if (!channel) return null;
|
|
62
|
+
const state = await gatewayContactChannelState(channel);
|
|
63
|
+
return (state?.status as ChannelStatus | undefined) ?? null;
|
|
64
|
+
}
|
|
65
|
+
|
|
29
66
|
// ---------------------------------------------------------------------------
|
|
30
67
|
// Gateway lifecycle bridge (shared by all redemption paths)
|
|
31
68
|
// ---------------------------------------------------------------------------
|
|
@@ -218,18 +255,22 @@ export async function redeemInvite(params: {
|
|
|
218
255
|
const targetMismatch =
|
|
219
256
|
existingContact && existingContact.id !== invite.contactId;
|
|
220
257
|
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
258
|
+
const gateStatus = await resolveMemberGateStatus(
|
|
259
|
+
await getInboundTrustVerdict({
|
|
260
|
+
channelType: sourceChannel as ChannelId,
|
|
261
|
+
actorExternalId: canonicalUserId,
|
|
262
|
+
}),
|
|
263
|
+
await gatewayFallbackStatus(existingChannel),
|
|
264
|
+
);
|
|
265
|
+
|
|
266
|
+
if (existingChannel && gateStatus === "active" && !targetMismatch) {
|
|
226
267
|
return { ok: true, type: "already_member", memberId: existingChannel.id };
|
|
227
268
|
}
|
|
228
269
|
|
|
229
270
|
// Blocked members cannot bypass the guardian's explicit block via invite
|
|
230
271
|
// links. Return the same generic failure as an invalid token to avoid
|
|
231
272
|
// leaking membership status to the caller.
|
|
232
|
-
if (existingChannel &&
|
|
273
|
+
if (existingChannel && gateStatus === "blocked") {
|
|
233
274
|
return { ok: false, reason: "invalid_token" };
|
|
234
275
|
}
|
|
235
276
|
|
|
@@ -247,14 +288,10 @@ export async function redeemInvite(params: {
|
|
|
247
288
|
return { ok: false, reason: "invalid_token" };
|
|
248
289
|
}
|
|
249
290
|
|
|
250
|
-
// Inactive member reactivation: when the user already has a member record
|
|
251
|
-
//
|
|
252
|
-
//
|
|
253
|
-
// uses upsertContactChannel to keep contacts in sync.
|
|
291
|
+
// Inactive member reactivation: when the user already has a member record in
|
|
292
|
+
// a non-active state (revoked/pending), reactivate it gateway-first then
|
|
293
|
+
// consume an invite use. The fresh-member path below mirrors this.
|
|
254
294
|
if (existingChannel && !targetMismatch) {
|
|
255
|
-
// Sentinel error used to trigger a transaction rollback when the invite
|
|
256
|
-
// was concurrently revoked/expired between pre-validation and write time.
|
|
257
|
-
const STALE_INVITE = Symbol("stale_invite");
|
|
258
295
|
const canonicalMemberId = existingChannel.address;
|
|
259
296
|
const canonicalCallerId = externalUserId
|
|
260
297
|
? canonicalizeInboundIdentity(sourceChannel as ChannelId, externalUserId)
|
|
@@ -266,42 +303,17 @@ export async function redeemInvite(params: {
|
|
|
266
303
|
? existingContact.displayName
|
|
267
304
|
: displayName;
|
|
268
305
|
|
|
269
|
-
|
|
306
|
+
// Consume the assistant invite use BEFORE activating the member, so a
|
|
307
|
+
// concurrent revoke/exhaustion (recordInviteUse returns false) leaves no
|
|
308
|
+
// active member behind.
|
|
270
309
|
try {
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
externalUserId,
|
|
276
|
-
externalChatId,
|
|
277
|
-
// Reactivation should not overwrite a guardian-managed nickname.
|
|
278
|
-
displayName: preservedDisplayName,
|
|
279
|
-
username,
|
|
280
|
-
role: "contact",
|
|
281
|
-
status: "active",
|
|
282
|
-
policy: "allow",
|
|
283
|
-
inviteId: invite.id,
|
|
284
|
-
verifiedAt: Date.now(),
|
|
285
|
-
verifiedVia: "invite",
|
|
286
|
-
contactId: invite.contactId,
|
|
287
|
-
});
|
|
288
|
-
|
|
289
|
-
const recorded = recordInviteUse({
|
|
290
|
-
inviteId: invite.id,
|
|
291
|
-
externalUserId,
|
|
292
|
-
externalChatId,
|
|
293
|
-
});
|
|
294
|
-
|
|
295
|
-
// If the invite was revoked/expired between pre-validation and this
|
|
296
|
-
// write, recordInviteUse returns false — throw to roll back the
|
|
297
|
-
// member reactivation so the DB stays consistent.
|
|
298
|
-
if (!recorded) throw STALE_INVITE;
|
|
299
|
-
})
|
|
300
|
-
.immediate();
|
|
301
|
-
} catch (err) {
|
|
302
|
-
if (err === STALE_INVITE) {
|
|
310
|
+
if (
|
|
311
|
+
!recordInviteUse({ inviteId: invite.id, externalUserId, externalChatId })
|
|
312
|
+
) {
|
|
313
|
+
// Invite revoked/expired between pre-validation and write.
|
|
303
314
|
return { ok: false, reason: "invalid_token" };
|
|
304
315
|
}
|
|
316
|
+
} catch (err) {
|
|
305
317
|
// Rare: the gateway claim already consumed the row but the assistant
|
|
306
318
|
// mutation failed — a recoverable wasted gateway use; no cross-process
|
|
307
319
|
// rollback is attempted.
|
|
@@ -312,10 +324,30 @@ export async function redeemInvite(params: {
|
|
|
312
324
|
throw err;
|
|
313
325
|
}
|
|
314
326
|
|
|
327
|
+
// Gateway-first: activate the member channel on the authoritative gateway
|
|
328
|
+
// before the assistant DB; the local mirror is best-effort.
|
|
329
|
+
const reactivated = await activateMemberChannel({
|
|
330
|
+
sourceChannel,
|
|
331
|
+
externalUserId,
|
|
332
|
+
externalChatId,
|
|
333
|
+
// Reactivation should not overwrite a guardian-managed nickname.
|
|
334
|
+
displayName: preservedDisplayName,
|
|
335
|
+
username,
|
|
336
|
+
policy: "allow",
|
|
337
|
+
inviteId: invite.id,
|
|
338
|
+
verifiedAt: Date.now(),
|
|
339
|
+
verifiedVia: "invite",
|
|
340
|
+
contactId: invite.contactId,
|
|
341
|
+
});
|
|
342
|
+
|
|
343
|
+
if (reactivated.status === "refused") {
|
|
344
|
+
return { ok: false, reason: "invalid_token" };
|
|
345
|
+
}
|
|
346
|
+
|
|
315
347
|
return {
|
|
316
348
|
ok: true,
|
|
317
349
|
type: "redeemed",
|
|
318
|
-
memberId: reactivated
|
|
350
|
+
memberId: reactivated.memberId,
|
|
319
351
|
inviteId: invite.id,
|
|
320
352
|
};
|
|
321
353
|
}
|
|
@@ -332,39 +364,17 @@ export async function redeemInvite(params: {
|
|
|
332
364
|
}
|
|
333
365
|
}
|
|
334
366
|
|
|
335
|
-
|
|
336
|
-
|
|
367
|
+
// Consume the assistant invite use BEFORE activating the member, so a
|
|
368
|
+
// concurrent revoke/exhaustion (recordInviteUse returns false) leaves no
|
|
369
|
+
// active member behind.
|
|
337
370
|
try {
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
externalUserId,
|
|
343
|
-
externalChatId,
|
|
344
|
-
displayName: freshDisplayName,
|
|
345
|
-
username,
|
|
346
|
-
role: "contact",
|
|
347
|
-
status: "active",
|
|
348
|
-
policy: "allow",
|
|
349
|
-
inviteId: invite.id,
|
|
350
|
-
verifiedAt: Date.now(),
|
|
351
|
-
verifiedVia: "invite",
|
|
352
|
-
contactId: invite.contactId,
|
|
353
|
-
});
|
|
354
|
-
|
|
355
|
-
const recorded = recordInviteUse({
|
|
356
|
-
inviteId: invite.id,
|
|
357
|
-
externalUserId,
|
|
358
|
-
externalChatId,
|
|
359
|
-
});
|
|
360
|
-
|
|
361
|
-
if (!recorded) throw STALE_INVITE_FRESH;
|
|
362
|
-
})
|
|
363
|
-
.immediate();
|
|
364
|
-
} catch (err) {
|
|
365
|
-
if (err === STALE_INVITE_FRESH) {
|
|
371
|
+
if (
|
|
372
|
+
!recordInviteUse({ inviteId: invite.id, externalUserId, externalChatId })
|
|
373
|
+
) {
|
|
374
|
+
// Invite revoked/expired between pre-validation and write.
|
|
366
375
|
return { ok: false, reason: "invalid_token" };
|
|
367
376
|
}
|
|
377
|
+
} catch (err) {
|
|
368
378
|
// Rare: gateway claim succeeded but the assistant mutation failed — a
|
|
369
379
|
// recoverable wasted gateway use; no cross-process rollback attempted.
|
|
370
380
|
log.error(
|
|
@@ -374,10 +384,29 @@ export async function redeemInvite(params: {
|
|
|
374
384
|
throw err;
|
|
375
385
|
}
|
|
376
386
|
|
|
387
|
+
// Gateway-first: activate the member channel on the authoritative gateway
|
|
388
|
+
// before the assistant DB; the local mirror is best-effort.
|
|
389
|
+
const freshResult = await activateMemberChannel({
|
|
390
|
+
sourceChannel,
|
|
391
|
+
externalUserId,
|
|
392
|
+
externalChatId,
|
|
393
|
+
displayName: freshDisplayName,
|
|
394
|
+
username,
|
|
395
|
+
policy: "allow",
|
|
396
|
+
inviteId: invite.id,
|
|
397
|
+
verifiedAt: Date.now(),
|
|
398
|
+
verifiedVia: "invite",
|
|
399
|
+
contactId: invite.contactId,
|
|
400
|
+
});
|
|
401
|
+
|
|
402
|
+
if (freshResult.status === "refused") {
|
|
403
|
+
return { ok: false, reason: "invalid_token" };
|
|
404
|
+
}
|
|
405
|
+
|
|
377
406
|
return {
|
|
378
407
|
ok: true,
|
|
379
408
|
type: "redeemed",
|
|
380
|
-
memberId: freshResult
|
|
409
|
+
memberId: freshResult.memberId,
|
|
381
410
|
inviteId: invite.id,
|
|
382
411
|
};
|
|
383
412
|
}
|
|
@@ -465,11 +494,12 @@ export async function redeemVoiceInviteCode(params: {
|
|
|
465
494
|
// should bind the sender's identity to the target contact, not the existing one.
|
|
466
495
|
const targetMismatch = voiceContact && voiceContact.id !== invite.contactId;
|
|
467
496
|
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
existingVoiceChannel
|
|
471
|
-
|
|
472
|
-
|
|
497
|
+
const gateStatus = await resolveMemberGateStatus(
|
|
498
|
+
await getPhoneCallerVerdict(canonicalCallerId),
|
|
499
|
+
await gatewayFallbackStatus(existingVoiceChannel),
|
|
500
|
+
);
|
|
501
|
+
|
|
502
|
+
if (existingVoiceChannel && gateStatus === "active" && !targetMismatch) {
|
|
473
503
|
return {
|
|
474
504
|
ok: true,
|
|
475
505
|
type: "already_member",
|
|
@@ -478,7 +508,7 @@ export async function redeemVoiceInviteCode(params: {
|
|
|
478
508
|
}
|
|
479
509
|
|
|
480
510
|
// Blocked members cannot bypass the guardian's explicit block
|
|
481
|
-
if (existingVoiceChannel &&
|
|
511
|
+
if (existingVoiceChannel && gateStatus === "blocked") {
|
|
482
512
|
return { ok: false, reason: "invalid_or_expired" };
|
|
483
513
|
}
|
|
484
514
|
|
|
@@ -496,10 +526,6 @@ export async function redeemVoiceInviteCode(params: {
|
|
|
496
526
|
return { ok: false, reason: "invalid_or_expired" };
|
|
497
527
|
}
|
|
498
528
|
|
|
499
|
-
// Atomic redemption: upsert member + consume invite use in a transaction
|
|
500
|
-
const STALE_INVITE = Symbol("stale_invite");
|
|
501
|
-
let memberId: string | undefined;
|
|
502
|
-
|
|
503
529
|
// When the invite targets a specific contact, preserve the target contact's
|
|
504
530
|
// guardian-assigned display name if it has one.
|
|
505
531
|
let preservedDisplayName = voiceContact?.displayName?.trim().length
|
|
@@ -512,36 +538,20 @@ export async function redeemVoiceInviteCode(params: {
|
|
|
512
538
|
}
|
|
513
539
|
}
|
|
514
540
|
|
|
541
|
+
// Consume the assistant invite use BEFORE activating the member, so a
|
|
542
|
+
// concurrent revoke/exhaustion (recordInviteUse returns false) leaves no
|
|
543
|
+
// active member behind.
|
|
515
544
|
try {
|
|
516
|
-
|
|
517
|
-
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
externalUserId: callerExternalUserId,
|
|
521
|
-
externalChatId: callerExternalUserId,
|
|
522
|
-
displayName: preservedDisplayName,
|
|
523
|
-
role: "contact",
|
|
524
|
-
status: "active",
|
|
525
|
-
policy: "allow",
|
|
526
|
-
inviteId: invite.id,
|
|
527
|
-
verifiedAt: Date.now(),
|
|
528
|
-
verifiedVia: "invite",
|
|
529
|
-
contactId: invite.contactId,
|
|
530
|
-
});
|
|
531
|
-
memberId = writeResult!.channel.id;
|
|
532
|
-
|
|
533
|
-
const recorded = recordInviteUse({
|
|
534
|
-
inviteId: invite.id,
|
|
535
|
-
externalUserId: callerExternalUserId,
|
|
536
|
-
});
|
|
537
|
-
|
|
538
|
-
if (!recorded) throw STALE_INVITE;
|
|
545
|
+
if (
|
|
546
|
+
!recordInviteUse({
|
|
547
|
+
inviteId: invite.id,
|
|
548
|
+
externalUserId: callerExternalUserId,
|
|
539
549
|
})
|
|
540
|
-
|
|
541
|
-
|
|
542
|
-
if (err === STALE_INVITE) {
|
|
550
|
+
) {
|
|
551
|
+
// Invite revoked/expired between pre-validation and write.
|
|
543
552
|
return { ok: false, reason: "invalid_or_expired" };
|
|
544
553
|
}
|
|
554
|
+
} catch (err) {
|
|
545
555
|
// Rare: gateway claim succeeded but the assistant mutation failed — a
|
|
546
556
|
// recoverable wasted gateway use; no cross-process rollback attempted.
|
|
547
557
|
log.error(
|
|
@@ -551,10 +561,28 @@ export async function redeemVoiceInviteCode(params: {
|
|
|
551
561
|
throw err;
|
|
552
562
|
}
|
|
553
563
|
|
|
564
|
+
// Gateway-first: activate the member channel on the authoritative gateway
|
|
565
|
+
// before the assistant DB; the local mirror is best-effort.
|
|
566
|
+
const writeResult = await activateMemberChannel({
|
|
567
|
+
sourceChannel: "phone",
|
|
568
|
+
externalUserId: callerExternalUserId,
|
|
569
|
+
externalChatId: callerExternalUserId,
|
|
570
|
+
displayName: preservedDisplayName,
|
|
571
|
+
policy: "allow",
|
|
572
|
+
inviteId: invite.id,
|
|
573
|
+
verifiedAt: Date.now(),
|
|
574
|
+
verifiedVia: "invite",
|
|
575
|
+
contactId: invite.contactId,
|
|
576
|
+
});
|
|
577
|
+
|
|
578
|
+
if (writeResult.status === "refused") {
|
|
579
|
+
return { ok: false, reason: "invalid_or_expired" };
|
|
580
|
+
}
|
|
581
|
+
|
|
554
582
|
return {
|
|
555
583
|
ok: true,
|
|
556
584
|
type: "redeemed",
|
|
557
|
-
memberId: memberId
|
|
585
|
+
memberId: writeResult.memberId,
|
|
558
586
|
inviteId: invite.id,
|
|
559
587
|
};
|
|
560
588
|
}
|
|
@@ -639,18 +667,22 @@ export async function redeemInviteByCode(params: {
|
|
|
639
667
|
const targetMismatch =
|
|
640
668
|
existingContact && existingContact.id !== invite.contactId;
|
|
641
669
|
|
|
642
|
-
|
|
643
|
-
|
|
644
|
-
|
|
645
|
-
|
|
646
|
-
|
|
670
|
+
const gateStatus = await resolveMemberGateStatus(
|
|
671
|
+
await getInboundTrustVerdict({
|
|
672
|
+
channelType: sourceChannel as ChannelId,
|
|
673
|
+
actorExternalId: canonicalUserId,
|
|
674
|
+
}),
|
|
675
|
+
await gatewayFallbackStatus(existingChannel),
|
|
676
|
+
);
|
|
677
|
+
|
|
678
|
+
if (existingChannel && gateStatus === "active" && !targetMismatch) {
|
|
647
679
|
return { ok: true, type: "already_member", memberId: existingChannel.id };
|
|
648
680
|
}
|
|
649
681
|
|
|
650
682
|
// Blocked members cannot bypass the guardian's explicit block via invite
|
|
651
683
|
// codes. Return the same generic failure as an invalid token to avoid
|
|
652
684
|
// leaking membership status to the caller.
|
|
653
|
-
if (existingChannel &&
|
|
685
|
+
if (existingChannel && gateStatus === "blocked") {
|
|
654
686
|
return { ok: false, reason: "invalid_token" };
|
|
655
687
|
}
|
|
656
688
|
|
|
@@ -667,10 +699,9 @@ export async function redeemInviteByCode(params: {
|
|
|
667
699
|
return { ok: false, reason: "invalid_token" };
|
|
668
700
|
}
|
|
669
701
|
|
|
670
|
-
// Inactive member reactivation: reactivate
|
|
671
|
-
//
|
|
702
|
+
// Inactive member reactivation: reactivate gateway-first, then consume an
|
|
703
|
+
// invite use.
|
|
672
704
|
if (existingChannel && !targetMismatch) {
|
|
673
|
-
const STALE_INVITE_REACTIVATE = Symbol("stale_invite_reactivate");
|
|
674
705
|
const canonicalMemberId = existingChannel.address;
|
|
675
706
|
const canonicalCallerId = externalUserId
|
|
676
707
|
? canonicalizeInboundIdentity(sourceChannel as ChannelId, externalUserId)
|
|
@@ -682,38 +713,17 @@ export async function redeemInviteByCode(params: {
|
|
|
682
713
|
? existingContact.displayName
|
|
683
714
|
: displayName;
|
|
684
715
|
|
|
685
|
-
|
|
716
|
+
// Consume the assistant invite use BEFORE activating the member, so a
|
|
717
|
+
// concurrent revoke/exhaustion (recordInviteUse returns false) leaves no
|
|
718
|
+
// active member behind.
|
|
686
719
|
try {
|
|
687
|
-
|
|
688
|
-
|
|
689
|
-
|
|
690
|
-
|
|
691
|
-
externalUserId,
|
|
692
|
-
externalChatId,
|
|
693
|
-
displayName: preservedDisplayName,
|
|
694
|
-
username,
|
|
695
|
-
role: "contact",
|
|
696
|
-
status: "active",
|
|
697
|
-
policy: "allow",
|
|
698
|
-
inviteId: invite.id,
|
|
699
|
-
verifiedAt: Date.now(),
|
|
700
|
-
verifiedVia: "invite",
|
|
701
|
-
contactId: invite.contactId,
|
|
702
|
-
});
|
|
703
|
-
|
|
704
|
-
const recorded = recordInviteUse({
|
|
705
|
-
inviteId: invite.id,
|
|
706
|
-
externalUserId,
|
|
707
|
-
externalChatId,
|
|
708
|
-
});
|
|
709
|
-
|
|
710
|
-
if (!recorded) throw STALE_INVITE_REACTIVATE;
|
|
711
|
-
})
|
|
712
|
-
.immediate();
|
|
713
|
-
} catch (err) {
|
|
714
|
-
if (err === STALE_INVITE_REACTIVATE) {
|
|
720
|
+
if (
|
|
721
|
+
!recordInviteUse({ inviteId: invite.id, externalUserId, externalChatId })
|
|
722
|
+
) {
|
|
723
|
+
// Invite revoked/expired between pre-validation and write.
|
|
715
724
|
return { ok: false, reason: "invalid_token" };
|
|
716
725
|
}
|
|
726
|
+
} catch (err) {
|
|
717
727
|
// Rare: gateway claim succeeded but the assistant mutation failed — a
|
|
718
728
|
// recoverable wasted gateway use; no cross-process rollback attempted.
|
|
719
729
|
log.error(
|
|
@@ -723,10 +733,29 @@ export async function redeemInviteByCode(params: {
|
|
|
723
733
|
throw err;
|
|
724
734
|
}
|
|
725
735
|
|
|
736
|
+
// Gateway-first: activate the member channel on the authoritative gateway
|
|
737
|
+
// before the assistant DB; the local mirror is best-effort.
|
|
738
|
+
const reactivated = await activateMemberChannel({
|
|
739
|
+
sourceChannel,
|
|
740
|
+
externalUserId,
|
|
741
|
+
externalChatId,
|
|
742
|
+
displayName: preservedDisplayName,
|
|
743
|
+
username,
|
|
744
|
+
policy: "allow",
|
|
745
|
+
inviteId: invite.id,
|
|
746
|
+
verifiedAt: Date.now(),
|
|
747
|
+
verifiedVia: "invite",
|
|
748
|
+
contactId: invite.contactId,
|
|
749
|
+
});
|
|
750
|
+
|
|
751
|
+
if (reactivated.status === "refused") {
|
|
752
|
+
return { ok: false, reason: "invalid_token" };
|
|
753
|
+
}
|
|
754
|
+
|
|
726
755
|
return {
|
|
727
756
|
ok: true,
|
|
728
757
|
type: "redeemed",
|
|
729
|
-
memberId: reactivated
|
|
758
|
+
memberId: reactivated.memberId,
|
|
730
759
|
inviteId: invite.id,
|
|
731
760
|
};
|
|
732
761
|
}
|
|
@@ -743,39 +772,17 @@ export async function redeemInviteByCode(params: {
|
|
|
743
772
|
}
|
|
744
773
|
}
|
|
745
774
|
|
|
746
|
-
|
|
747
|
-
|
|
775
|
+
// Consume the assistant invite use BEFORE activating the member, so a
|
|
776
|
+
// concurrent revoke/exhaustion (recordInviteUse returns false) leaves no
|
|
777
|
+
// active member behind.
|
|
748
778
|
try {
|
|
749
|
-
|
|
750
|
-
|
|
751
|
-
|
|
752
|
-
|
|
753
|
-
externalUserId,
|
|
754
|
-
externalChatId,
|
|
755
|
-
displayName: freshDisplayName,
|
|
756
|
-
username,
|
|
757
|
-
role: "contact",
|
|
758
|
-
status: "active",
|
|
759
|
-
policy: "allow",
|
|
760
|
-
inviteId: invite.id,
|
|
761
|
-
verifiedAt: Date.now(),
|
|
762
|
-
verifiedVia: "invite",
|
|
763
|
-
contactId: invite.contactId,
|
|
764
|
-
});
|
|
765
|
-
|
|
766
|
-
const recorded = recordInviteUse({
|
|
767
|
-
inviteId: invite.id,
|
|
768
|
-
externalUserId,
|
|
769
|
-
externalChatId,
|
|
770
|
-
});
|
|
771
|
-
|
|
772
|
-
if (!recorded) throw STALE_INVITE_FRESH;
|
|
773
|
-
})
|
|
774
|
-
.immediate();
|
|
775
|
-
} catch (err) {
|
|
776
|
-
if (err === STALE_INVITE_FRESH) {
|
|
779
|
+
if (
|
|
780
|
+
!recordInviteUse({ inviteId: invite.id, externalUserId, externalChatId })
|
|
781
|
+
) {
|
|
782
|
+
// Invite revoked/expired between pre-validation and write.
|
|
777
783
|
return { ok: false, reason: "invalid_token" };
|
|
778
784
|
}
|
|
785
|
+
} catch (err) {
|
|
779
786
|
// Rare: gateway claim succeeded but the assistant mutation failed — a
|
|
780
787
|
// recoverable wasted gateway use; no cross-process rollback attempted.
|
|
781
788
|
log.error(
|
|
@@ -785,10 +792,29 @@ export async function redeemInviteByCode(params: {
|
|
|
785
792
|
throw err;
|
|
786
793
|
}
|
|
787
794
|
|
|
795
|
+
// Gateway-first: activate the member channel on the authoritative gateway
|
|
796
|
+
// before the assistant DB; the local mirror is best-effort.
|
|
797
|
+
const freshResult = await activateMemberChannel({
|
|
798
|
+
sourceChannel,
|
|
799
|
+
externalUserId,
|
|
800
|
+
externalChatId,
|
|
801
|
+
displayName: freshDisplayName,
|
|
802
|
+
username,
|
|
803
|
+
policy: "allow",
|
|
804
|
+
inviteId: invite.id,
|
|
805
|
+
verifiedAt: Date.now(),
|
|
806
|
+
verifiedVia: "invite",
|
|
807
|
+
contactId: invite.contactId,
|
|
808
|
+
});
|
|
809
|
+
|
|
810
|
+
if (freshResult.status === "refused") {
|
|
811
|
+
return { ok: false, reason: "invalid_token" };
|
|
812
|
+
}
|
|
813
|
+
|
|
788
814
|
return {
|
|
789
815
|
ok: true,
|
|
790
816
|
type: "redeemed",
|
|
791
|
-
memberId: freshResult
|
|
817
|
+
memberId: freshResult.memberId,
|
|
792
818
|
inviteId: invite.id,
|
|
793
819
|
};
|
|
794
820
|
}
|
|
@@ -0,0 +1,108 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Tests for the local-actor-identity cache-warm path.
|
|
3
|
+
*
|
|
4
|
+
* The SSE eager-subscribe path resolves the local actor principal
|
|
5
|
+
* synchronously from the IO-free guardian-delivery cache snapshot. A cold
|
|
6
|
+
* cache returns undefined, so the daemon warms it at startup
|
|
7
|
+
* (`warmLocalGuardianPrincipalCache`) before clients register. These tests pin
|
|
8
|
+
* that the sync read is cold before the warm and resolves the gateway-owned
|
|
9
|
+
* principal after it.
|
|
10
|
+
*/
|
|
11
|
+
|
|
12
|
+
import { afterEach, beforeEach, describe, expect, mock, test } from "bun:test";
|
|
13
|
+
|
|
14
|
+
import type { GuardianDelivery } from "@vellumai/gateway-client";
|
|
15
|
+
|
|
16
|
+
// ── Controllable IPC mock ──────────────────────────────────────────────────
|
|
17
|
+
|
|
18
|
+
type IpcHandler = (params?: Record<string, unknown>) => unknown;
|
|
19
|
+
const ipcHandlers = new Map<string, IpcHandler>();
|
|
20
|
+
|
|
21
|
+
mock.module("../ipc/gateway-client.js", () => ({
|
|
22
|
+
ipcCall: async (method: string, params?: Record<string, unknown>) => {
|
|
23
|
+
const handler = ipcHandlers.get(method);
|
|
24
|
+
return handler ? handler(params) : undefined;
|
|
25
|
+
},
|
|
26
|
+
ipcCallPersistent: async () => undefined,
|
|
27
|
+
resetPersistentClient: () => {},
|
|
28
|
+
}));
|
|
29
|
+
|
|
30
|
+
let httpAuthDisabled = false;
|
|
31
|
+
mock.module("../config/env.js", () => ({
|
|
32
|
+
isHttpAuthDisabled: () => httpAuthDisabled,
|
|
33
|
+
}));
|
|
34
|
+
|
|
35
|
+
import { __resetGuardianDeliveryCacheForTest } from "../contacts/guardian-delivery-reader.js";
|
|
36
|
+
import {
|
|
37
|
+
findLocalGuardianPrincipalIdFromStore,
|
|
38
|
+
resolveActorPrincipalIdForLocalGuardianSync,
|
|
39
|
+
warmLocalGuardianPrincipalCache,
|
|
40
|
+
} from "./local-actor-identity.js";
|
|
41
|
+
|
|
42
|
+
const METHOD = "resolve_guardian_delivery";
|
|
43
|
+
|
|
44
|
+
const vellumGuardian: GuardianDelivery = {
|
|
45
|
+
channelType: "vellum",
|
|
46
|
+
contactId: "contact-1",
|
|
47
|
+
address: "self",
|
|
48
|
+
status: "active",
|
|
49
|
+
principalId: "principal-abc",
|
|
50
|
+
};
|
|
51
|
+
|
|
52
|
+
describe("warmLocalGuardianPrincipalCache", () => {
|
|
53
|
+
beforeEach(() => {
|
|
54
|
+
__resetGuardianDeliveryCacheForTest();
|
|
55
|
+
ipcHandlers.clear();
|
|
56
|
+
httpAuthDisabled = false;
|
|
57
|
+
});
|
|
58
|
+
|
|
59
|
+
afterEach(() => {
|
|
60
|
+
__resetGuardianDeliveryCacheForTest();
|
|
61
|
+
});
|
|
62
|
+
|
|
63
|
+
test("sync read is cold before warming", () => {
|
|
64
|
+
ipcHandlers.set(METHOD, () => ({ guardians: [vellumGuardian] }));
|
|
65
|
+
|
|
66
|
+
// No warm yet — the cache snapshot is empty.
|
|
67
|
+
expect(findLocalGuardianPrincipalIdFromStore()).toBeUndefined();
|
|
68
|
+
});
|
|
69
|
+
|
|
70
|
+
test("warming populates the cache for the sync read", async () => {
|
|
71
|
+
ipcHandlers.set(METHOD, () => ({ guardians: [vellumGuardian] }));
|
|
72
|
+
|
|
73
|
+
await warmLocalGuardianPrincipalCache();
|
|
74
|
+
|
|
75
|
+
expect(findLocalGuardianPrincipalIdFromStore()).toBe("principal-abc");
|
|
76
|
+
});
|
|
77
|
+
|
|
78
|
+
test("cold-start SSE registration resolves the principal after warm", async () => {
|
|
79
|
+
httpAuthDisabled = true;
|
|
80
|
+
ipcHandlers.set(METHOD, () => ({ guardians: [vellumGuardian] }));
|
|
81
|
+
|
|
82
|
+
// Cold cache: dev-bypass header resolves to no principal.
|
|
83
|
+
expect(
|
|
84
|
+
resolveActorPrincipalIdForLocalGuardianSync("dev-bypass"),
|
|
85
|
+
).toBeUndefined();
|
|
86
|
+
|
|
87
|
+
await warmLocalGuardianPrincipalCache();
|
|
88
|
+
|
|
89
|
+
// Warmed: the SSE sync path now resolves the gateway-owned principal.
|
|
90
|
+
expect(resolveActorPrincipalIdForLocalGuardianSync("dev-bypass")).toBe(
|
|
91
|
+
"principal-abc",
|
|
92
|
+
);
|
|
93
|
+
});
|
|
94
|
+
|
|
95
|
+
test("warm tolerates an unreachable gateway without caching a failure", async () => {
|
|
96
|
+
ipcHandlers.set(METHOD, () => {
|
|
97
|
+
throw new Error("gateway down");
|
|
98
|
+
});
|
|
99
|
+
|
|
100
|
+
await warmLocalGuardianPrincipalCache();
|
|
101
|
+
|
|
102
|
+
// Failure not cached; a later successful read warms the cache.
|
|
103
|
+
expect(findLocalGuardianPrincipalIdFromStore()).toBeUndefined();
|
|
104
|
+
ipcHandlers.set(METHOD, () => ({ guardians: [vellumGuardian] }));
|
|
105
|
+
await warmLocalGuardianPrincipalCache();
|
|
106
|
+
expect(findLocalGuardianPrincipalIdFromStore()).toBe("principal-abc");
|
|
107
|
+
});
|
|
108
|
+
});
|