@vellumai/assistant 0.10.2-dev.202606250318.5e7cfb0 → 0.10.2
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 +0 -20
- package/docs/workspace-tools.md +33 -42
- package/eslint-rules/cli-no-daemon-internals.js +0 -6
- package/node_modules/@vellumai/gateway-client/src/__tests__/trust-verdict-contract.test.ts +0 -31
- package/node_modules/@vellumai/gateway-client/src/gateway-ipc-contracts.ts +0 -44
- package/node_modules/@vellumai/gateway-client/src/index.ts +0 -14
- package/node_modules/@vellumai/gateway-client/src/trust-verdict-contract.ts +0 -17
- package/node_modules/@vellumai/service-contracts/package.json +0 -1
- package/node_modules/@vellumai/service-contracts/src/index.ts +0 -1
- package/openapi.yaml +0 -155
- package/package.json +1 -4
- package/scripts/test.sh +15 -36
- package/src/__tests__/actor-token-service.test.ts +14 -36
- package/src/__tests__/agent-loop-override-profile.test.ts +0 -1
- package/src/__tests__/agent-wake-disk-pressure-callsite.test.ts +0 -2
- package/src/__tests__/agent-wake-override-profile.test.ts +0 -2
- package/src/__tests__/annotate-activity-metadata.test.ts +0 -2
- package/src/__tests__/annotate-risk-options.test.ts +0 -2
- package/src/__tests__/approval-cascade.test.ts +0 -2
- package/src/__tests__/assistant-attachments.test.ts +0 -42
- package/src/__tests__/background-workers-disk-pressure.test.ts +0 -2
- package/src/__tests__/btw-routes.test.ts +0 -2
- package/src/__tests__/build-persisted-content.test.ts +0 -2
- package/src/__tests__/call-controller.test.ts +0 -19
- package/src/__tests__/channel-guardian.test.ts +58 -94
- package/src/__tests__/channel-reply-delivery.test.ts +0 -2
- package/src/__tests__/compaction-events.test.ts +0 -2
- package/src/__tests__/compaction.benchmark.test.ts +0 -2
- package/src/__tests__/compactor-call-site-logging.test.ts +0 -2
- package/src/__tests__/compactor-low-watermark-cut.test.ts +0 -2
- package/src/__tests__/compactor-preserved-tail-count.test.ts +0 -2
- package/src/__tests__/compactor-summary-call-truncation.test.ts +0 -2
- package/src/__tests__/compactor-web-search-strip.test.ts +0 -2
- package/src/__tests__/config-loader-backfill.test.ts +10 -123
- package/src/__tests__/config-schema.test.ts +0 -1
- package/src/__tests__/confirmation-request-guardian-bridge.test.ts +29 -31
- package/src/__tests__/contacts-relay-reads.test.ts +15 -13
- package/src/__tests__/conversation-abort-tool-results.test.ts +0 -2
- package/src/__tests__/conversation-agent-loop-disk-pressure.test.ts +0 -2
- package/src/__tests__/conversation-agent-loop-inference-profile.test.ts +0 -2
- package/src/__tests__/conversation-agent-loop-overflow.test.ts +0 -2
- package/src/__tests__/conversation-agent-loop.test.ts +0 -134
- package/src/__tests__/conversation-analysis-routes.test.ts +0 -2
- package/src/__tests__/conversation-app-control-lifecycle.test.ts +0 -2
- package/src/__tests__/conversation-confirmation-signals.test.ts +0 -2
- package/src/__tests__/conversation-history-web-search.test.ts +0 -2
- package/src/__tests__/conversation-load-history-repair.test.ts +0 -2
- package/src/__tests__/conversation-load-history-stripped.test.ts +0 -2
- package/src/__tests__/conversation-pairing.test.ts +0 -2
- package/src/__tests__/conversation-process-app-control-preactivation.test.ts +0 -2
- package/src/__tests__/conversation-process-callsite.test.ts +0 -2
- package/src/__tests__/conversation-provider-retry-repair.test.ts +0 -2
- package/src/__tests__/conversation-queue.test.ts +0 -91
- package/src/__tests__/conversation-routes-guardian-reply.test.ts +0 -14
- package/src/__tests__/conversation-routes-slash-commands.test.ts +0 -14
- package/src/__tests__/conversation-slash-queue.test.ts +0 -2
- package/src/__tests__/conversation-slash-unknown.test.ts +0 -2
- package/src/__tests__/conversation-speed-override.test.ts +0 -2
- package/src/__tests__/conversation-surfaces-task-progress.test.ts +0 -29
- package/src/__tests__/conversation-title-service.test.ts +0 -2
- package/src/__tests__/conversation-tool-setup-attribution.test.ts +0 -47
- package/src/__tests__/conversation-usage.test.ts +0 -2
- package/src/__tests__/conversation-workspace-cache-state.test.ts +0 -2
- package/src/__tests__/conversation-workspace-injection.test.ts +0 -2
- package/src/__tests__/conversation-workspace-tool-tracking.test.ts +0 -2
- package/src/__tests__/credential-security-invariants.test.ts +1 -1
- package/src/__tests__/db-migration-rollback.test.ts +171 -205
- package/src/__tests__/db-test-helpers.ts +4 -5
- package/src/__tests__/deterministic-verification-control-plane.test.ts +2 -4
- package/src/__tests__/disk-pressure-guard.test.ts +0 -41
- package/src/__tests__/dm-persistence.test.ts +0 -2
- package/src/__tests__/emit-signal-routing-intent.test.ts +5 -10
- package/src/__tests__/events-dev-bypass-actor.test.ts +1 -7
- package/src/__tests__/exploration-drift-hook.test.ts +2 -3
- package/src/__tests__/filing-service.test.ts +0 -2
- package/src/__tests__/guardian-binding-drift-heal.test.ts +10 -75
- package/src/__tests__/guardian-dispatch.test.ts +1 -95
- package/src/__tests__/guardian-outbound-http.test.ts +0 -13
- package/src/__tests__/heartbeat-disk-pressure.test.ts +0 -2
- package/src/__tests__/heartbeat-service.test.ts +0 -2
- package/src/__tests__/helpers/channel-test-adapter.ts +7 -1
- package/src/__tests__/host-app-control-routes.test.ts +30 -24
- package/src/__tests__/host-bash-routes.test.ts +41 -31
- package/src/__tests__/host-browser-routes.test.ts +32 -26
- package/src/__tests__/host-cu-routes-targeted.test.ts +33 -25
- package/src/__tests__/host-file-routes-targeted.test.ts +52 -40
- package/src/__tests__/host-transfer-routes-targeted.test.ts +43 -31
- package/src/__tests__/http-user-message-parity.test.ts +8 -290
- package/src/__tests__/inbound-invite-redemption.test.ts +0 -28
- package/src/__tests__/inbound-slack-persistence.test.ts +0 -2
- package/src/__tests__/invite-redemption-service.test.ts +0 -198
- package/src/__tests__/llm-context-normalization.test.ts +0 -105
- package/src/__tests__/llm-request-log-error-payload.test.ts +9 -71
- package/src/__tests__/llm-usage-store.test.ts +0 -25
- package/src/__tests__/mcp-health-check.test.ts +1 -2
- package/src/__tests__/media-stream-server-integration.test.ts +0 -127
- package/src/__tests__/memory-retrieval-hook.test.ts +0 -2
- package/src/__tests__/messaging-send-tool.test.ts +0 -2
- package/src/__tests__/migration-import-from-url.test.ts +2 -2
- package/src/__tests__/mtime-cache.test.ts +5 -146
- package/src/__tests__/native-web-search.test.ts +0 -2
- package/src/__tests__/non-member-access-request.test.ts +17 -189
- package/src/__tests__/notification-broadcaster.test.ts +0 -4
- package/src/__tests__/notification-decision-recipient-context.test.ts +32 -33
- package/src/__tests__/notification-deep-link.test.ts +0 -6
- package/src/__tests__/notification-guardian-path.test.ts +0 -19
- package/src/__tests__/openai-provider.test.ts +12 -22
- package/src/__tests__/openai-responses-provider.test.ts +2 -12
- package/src/__tests__/outbound-slack-persistence.test.ts +0 -2
- package/src/__tests__/pending-interactions-resolved-event.test.ts +4 -7
- package/src/__tests__/persistence-secret-redaction.test.ts +0 -2
- package/src/__tests__/plugin-bootstrap.test.ts +73 -3
- package/src/__tests__/plugin-route-contribution.test.ts +17 -4
- package/src/__tests__/plugin-tool-contribution.test.ts +18 -3
- package/src/__tests__/plugin-types.test.ts +2 -0
- package/src/__tests__/process-message-background-slack.test.ts +0 -2
- package/src/__tests__/process-message-display-content.test.ts +0 -2
- package/src/__tests__/provider-error-scenarios.test.ts +4 -5
- package/src/__tests__/provider-usage-tracking.test.ts +0 -39
- package/src/__tests__/regenerate-fire-and-forget-trace.test.ts +0 -2
- package/src/__tests__/registry.test.ts +1 -4
- package/src/__tests__/relay-server.test.ts +25 -694
- package/src/__tests__/runtime-attachment-metadata.test.ts +1 -0
- package/src/__tests__/secret-ingress-http.test.ts +0 -14
- package/src/__tests__/send-endpoint-busy.test.ts +8 -30
- package/src/__tests__/skills.test.ts +0 -44
- package/src/__tests__/slack-inbound-verification.test.ts +2 -47
- package/src/__tests__/stt-hints.test.ts +13 -44
- package/src/__tests__/subagent-detail.test.ts +0 -27
- package/src/__tests__/subagent-disposal.test.ts +0 -65
- package/src/__tests__/subagent-notify-parent.test.ts +0 -2
- package/src/__tests__/subagent-role-registry.test.ts +2 -7
- package/src/__tests__/subagent-spawn-tool-fork.test.ts +0 -2
- package/src/__tests__/subagent-tools.test.ts +0 -2
- package/src/__tests__/suggestion-routes.test.ts +0 -2
- package/src/__tests__/title-generate-hook.test.ts +0 -2
- package/src/__tests__/tool-executor-lifecycle-events.test.ts +0 -2
- package/src/__tests__/tool-executor.test.ts +11 -16
- package/src/__tests__/tool-preview-lifecycle.test.ts +0 -2
- package/src/__tests__/tool-result-metadata-plumbing.test.ts +0 -2
- package/src/__tests__/tool-start-timestamp.test.ts +0 -2
- package/src/__tests__/trusted-contact-inline-approval-integration.test.ts +10 -10
- package/src/__tests__/twilio-routes.test.ts +0 -96
- package/src/__tests__/ui-file-upload-surface.test.ts +0 -86
- package/src/__tests__/verification-control-plane-policy.test.ts +0 -2
- package/src/__tests__/voice-invite-redemption.test.ts +0 -33
- package/src/__tests__/web-search-backend-failure.test.ts +0 -2
- package/src/__tests__/workspace-migration-remove-hooks.test.ts +35 -14
- package/src/__tests__/workspace-tool-loader.test.ts +2 -195
- package/src/__tests__/workspace-tools-watcher-flag.test.ts +70 -0
- package/src/agent/loop.ts +0 -56
- package/src/api/index.ts +1 -19
- package/src/api/responses/llm-request-log-entry.ts +0 -29
- package/src/api/responses/subagent-detail.ts +0 -17
- package/src/api/surfaces.ts +3 -39
- package/src/approvals/guardian-request-resolvers.ts +11 -1
- package/src/calls/__tests__/relay-setup-router.test.ts +4 -262
- package/src/calls/call-domain.ts +3 -3
- package/src/calls/guardian-dispatch.ts +8 -10
- package/src/calls/inbound-trust-reader.ts +1 -17
- package/src/calls/media-stream-server.ts +0 -21
- package/src/calls/relay-server.ts +50 -167
- package/src/calls/relay-setup-router.ts +7 -37
- package/src/calls/relay-verification.ts +4 -4
- package/src/calls/stt-hints.ts +12 -9
- package/src/calls/twilio-routes.ts +4 -14
- package/src/channels/types.ts +20 -10
- package/src/cli/commands/__tests__/cache.test.ts +1 -8
- package/src/cli/commands/cache.ts +181 -194
- package/src/cli/commands/db/__tests__/repair.test.ts +5 -6
- package/src/cli/commands/db/status.ts +1 -37
- package/src/cli/commands/mcp.ts +218 -252
- package/src/cli/commands/memory/index.ts +0 -2
- package/src/cli/commands/plugins.ts +3 -75
- package/src/cli/lib/__tests__/install-from-github.test.ts +0 -102
- package/src/cli/lib/__tests__/list-installed-plugins.test.ts +1 -160
- package/src/cli/lib/list-installed-plugins.ts +1 -179
- package/src/config/__tests__/sync-gated-profiles.test.ts +3 -11
- package/src/config/bundled-skills/contacts/tools/contact-merge.ts +17 -27
- package/src/config/bundled-skills/contacts/tools/contact-search.ts +3 -13
- package/src/config/bundled-skills/subagent/SKILL.md +1 -1
- package/src/config/bundled-skills/subagent/TOOLS.json +1 -1
- package/src/config/feature-flag-registry.json +13 -5
- package/src/config/loader.ts +5 -38
- package/src/config/schemas/__tests__/memory-v3.test.ts +0 -1
- package/src/config/schemas/memory-lifecycle.ts +0 -12
- package/src/config/schemas/memory-v3.ts +0 -7
- package/src/config/schemas/memory.ts +0 -4
- package/src/config/schemas/timeouts.ts +0 -8
- package/src/config/seed-inference-profiles.ts +11 -21
- package/src/config/skills.ts +5 -27
- package/src/config/sync-gated-profiles.ts +13 -12
- package/src/contacts/contacts-write.ts +0 -3
- package/src/daemon/assistant-attachments.ts +4 -27
- package/src/daemon/conversation-agent-loop.ts +0 -28
- package/src/daemon/conversation-process.ts +16 -35
- package/src/daemon/conversation-surfaces.ts +38 -111
- package/src/daemon/conversation-tool-setup.ts +16 -50
- package/src/daemon/conversation.ts +1 -13
- package/src/daemon/disk-pressure-guard.ts +2 -12
- package/src/daemon/event-loop-watchdog.ts +1 -28
- package/src/daemon/external-plugins-bootstrap.ts +34 -4
- package/src/daemon/handlers/__tests__/config-a2a-redeem.test.ts +0 -25
- package/src/daemon/handlers/config-a2a.ts +14 -6
- package/src/daemon/handlers/config-channels.ts +22 -78
- package/src/daemon/handlers/conversations.ts +0 -77
- package/src/daemon/lifecycle.ts +0 -4
- package/src/daemon/mcp-reload-service.ts +0 -10
- package/src/daemon/memory-v2-startup.test.ts +0 -72
- package/src/daemon/memory-v2-startup.ts +19 -87
- package/src/daemon/message-types/conversations.ts +0 -2
- package/src/daemon/message-types/surfaces.ts +12 -12
- package/src/daemon/server.ts +4 -0
- package/src/daemon/shutdown-handlers.ts +0 -20
- package/src/daemon/tool-setup-types.ts +0 -9
- package/src/daemon/workspace-tools-watcher.ts +328 -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 +0 -1
- package/src/mcp/client.ts +1 -15
- package/src/mcp/mcp-auth-orchestrator.ts +1 -6
- package/src/mcp/mcp-oauth-provider.ts +8 -19
- package/src/memory/__tests__/memory-retrospective-job.test.ts +0 -8
- package/src/memory/conversation-crud.ts +0 -38
- package/src/memory/db-connection.ts +3 -22
- package/src/memory/db-init.ts +502 -36
- package/src/memory/db-singleton.ts +4 -6
- package/src/memory/jobs-worker.ts +0 -58
- package/src/memory/llm-request-log-store.ts +1 -26
- package/src/memory/llm-usage-store.ts +20 -48
- package/src/memory/memory-retrospective-job.ts +8 -9
- package/src/memory/migrations/209-strip-thinking-from-consolidated.ts +56 -130
- package/src/memory/migrations/__tests__/run-migrations.test.ts +2 -2
- package/src/memory/migrations/registry.ts +573 -0
- package/src/memory/migrations/run-migrations.ts +6 -90
- package/src/memory/migrations/validate-migration-state.ts +66 -101
- package/src/memory/schema/conversations.ts +0 -9
- package/src/memory/schema/infrastructure.ts +0 -20
- package/src/memory/v2/__tests__/cli-command-store.test.ts +0 -25
- package/src/memory/v2/__tests__/skill-store.test.ts +0 -80
- package/src/memory/v2/cli-command-store.ts +38 -75
- package/src/memory/v2/prompts/consolidation.ts +82 -13
- package/src/memory/v2/prompts/router.ts +93 -21
- package/src/memory/v2/skill-store.ts +31 -68
- package/src/notifications/__tests__/broadcaster.test.ts +8 -16
- package/src/notifications/__tests__/decision-engine.test.ts +9 -78
- package/src/notifications/broadcaster.ts +1 -8
- package/src/notifications/decision-engine.ts +7 -15
- package/src/notifications/destination-resolver.ts +24 -68
- package/src/notifications/emit-signal.ts +14 -39
- package/src/permissions/question-prompter.test.ts +1 -1
- package/src/permissions/question-prompter.ts +4 -7
- package/src/plugin-api/index.ts +6 -6
- package/src/plugin-api/types.ts +5 -3
- package/src/plugin-api/vision-support.test.ts +4 -28
- package/src/plugin-api/vision-support.ts +31 -66
- package/src/plugins/defaults/advisor/__tests__/consult.test.ts +0 -161
- package/src/plugins/defaults/advisor/consult.ts +6 -110
- package/src/plugins/defaults/advisor/steering.ts +2 -14
- package/src/plugins/defaults/advisor/tools/advisor.ts +5 -32
- package/src/plugins/defaults/exploration-drift/hooks/post-tool-use.ts +1 -2
- package/src/plugins/defaults/image-fallback/__tests__/image-fallback.test.ts +7 -47
- package/src/plugins/defaults/image-fallback/hooks/post-tool-use.ts +11 -10
- package/src/plugins/defaults/image-fallback/hooks/user-prompt-submit.ts +20 -12
- package/src/plugins/defaults/image-fallback/src/caption-blocks.ts +11 -42
- package/src/plugins/defaults/memory-v3-shadow/__tests__/injection.test.ts +3 -33
- package/src/plugins/defaults/memory-v3-shadow/__tests__/pool-select.test.ts +4 -48
- package/src/plugins/defaults/memory-v3-shadow/__tests__/shadow-plugin.test.ts +8 -4
- package/src/plugins/defaults/memory-v3-shadow/injector.ts +15 -43
- package/src/plugins/defaults/memory-v3-shadow/orchestrate.ts +2 -11
- package/src/plugins/defaults/memory-v3-shadow/pool-select.ts +13 -77
- package/src/plugins/defaults/memory-v3-shadow/shadow-plugin.ts +11 -12
- package/src/plugins/mtime-cache.ts +291 -76
- package/src/plugins/pipeline.ts +13 -111
- package/src/plugins/types.ts +2 -0
- package/src/providers/anthropic/client.ts +0 -5
- package/src/providers/call-site-routing.ts +0 -4
- package/src/providers/model-catalog.ts +0 -16
- package/src/providers/openai/__tests__/api-error-detail.test.ts +120 -0
- package/src/providers/openai/chat-completions-provider.ts +83 -37
- package/src/providers/openai/responses-provider.ts +46 -50
- package/src/providers/openrouter/client.ts +0 -5
- package/src/providers/provider-send-message.ts +0 -4
- package/src/providers/ratelimit.ts +0 -4
- package/src/providers/retry.ts +0 -4
- package/src/providers/types.ts +0 -9
- package/src/providers/usage-tracking.ts +0 -4
- package/src/runtime/__tests__/trust-verdict-consumer.test.ts +3 -335
- package/src/runtime/access-request-helper.ts +39 -19
- package/src/runtime/actor-trust-resolver.ts +2 -2
- package/src/runtime/assistant-event-hub.ts +1 -1
- package/src/runtime/assistant-stream-state.ts +2 -9
- package/src/runtime/auth/require-bound-guardian.ts +11 -21
- package/src/runtime/channel-verification-service.ts +31 -56
- package/src/runtime/confirmation-request-guardian-bridge.ts +3 -3
- package/src/runtime/guardian-vellum-migration.ts +7 -66
- package/src/runtime/invite-redemption-service.ts +187 -198
- package/src/runtime/local-actor-identity.ts +11 -76
- package/src/runtime/pending-interactions.ts +1 -11
- package/src/runtime/routes/__tests__/channel-verification-revoke.test.ts +5 -56
- package/src/runtime/routes/__tests__/channel-verification-routes.test.ts +1 -1
- package/src/runtime/routes/__tests__/surface-action-routes.test.ts +0 -187
- package/src/runtime/routes/browser-routes.ts +1 -1
- package/src/runtime/routes/canonical-guardian-expiry-sweep.ts +5 -13
- package/src/runtime/routes/channel-verification-routes.ts +3 -3
- package/src/runtime/routes/contact-routes.ts +32 -8
- package/src/runtime/routes/conversation-cli-routes.ts +5 -4
- package/src/runtime/routes/conversation-list-routes.ts +7 -4
- package/src/runtime/routes/conversation-query-routes.ts +0 -72
- package/src/runtime/routes/conversation-routes.ts +85 -84
- package/src/runtime/routes/events-routes.ts +2 -2
- package/src/runtime/routes/global-search-routes.ts +1 -3
- package/src/runtime/routes/guardian-action-routes.ts +5 -4
- package/src/runtime/routes/host-app-control-routes.ts +4 -5
- package/src/runtime/routes/host-bash-routes.ts +4 -5
- package/src/runtime/routes/host-browser-routes.ts +11 -9
- package/src/runtime/routes/host-cu-routes.ts +4 -5
- package/src/runtime/routes/host-file-routes.ts +4 -5
- package/src/runtime/routes/host-transfer-routes.ts +6 -6
- package/src/runtime/routes/http-adapter.ts +1 -1
- package/src/runtime/routes/identity-routes.ts +2 -3
- package/src/runtime/routes/inbound-message-handler.ts +5 -5
- package/src/runtime/routes/inbound-stages/acl-enforcement.test.ts +5 -97
- package/src/runtime/routes/inbound-stages/acl-enforcement.ts +49 -61
- package/src/runtime/routes/inbound-stages/background-dispatch.ts +4 -16
- package/src/runtime/routes/inbound-stages/escalation-intercept.ts +7 -7
- package/src/runtime/routes/inbound-stages/guardian-activation-intercept.test.ts +8 -21
- package/src/runtime/routes/inbound-stages/guardian-activation-intercept.ts +3 -14
- package/src/runtime/routes/index.ts +0 -2
- package/src/runtime/routes/llm-context-normalization.ts +0 -83
- package/src/runtime/routes/mcp-auth-routes.ts +19 -171
- package/src/runtime/routes/migration-rollback-routes.ts +3 -4
- package/src/runtime/routes/migration-routes.ts +1 -4
- package/src/runtime/routes/subagents-routes.ts +0 -5
- package/src/runtime/routes/surface-action-routes.ts +56 -42
- package/src/runtime/services/__tests__/conversation-serializer.test.ts +0 -1
- package/src/runtime/services/conversation-serializer.ts +9 -7
- package/src/runtime/tool-grant-request-helper.ts +3 -3
- package/src/runtime/trust-verdict-consumer.ts +9 -85
- package/src/runtime/verification-outbound-actions.ts +18 -18
- package/src/signals/user-message.ts +0 -16
- package/src/subagent/manager.ts +0 -9
- package/src/subagent/types.ts +3 -3
- package/src/telemetry/types.ts +1 -34
- package/src/telemetry/usage-telemetry-reporter.test.ts +2 -3
- package/src/telemetry/usage-telemetry-reporter.ts +3 -87
- package/src/tools/ask-question/ask-question-tool.test.ts +0 -29
- package/src/tools/ask-question/ask-question-tool.ts +0 -13
- package/src/tools/executor.ts +4 -4
- package/src/tools/registry.ts +0 -18
- package/src/tools/shared/filesystem/path-policy.ts +5 -12
- package/src/tools/tool-approval-handler.ts +1 -1
- package/src/tools/tool-defaults.ts +2 -9
- package/src/tools/tool-manifest.ts +0 -3
- package/src/tools/types.ts +2 -17
- package/src/tools/workspace-tools/loader.ts +244 -348
- package/src/util/errors.ts +1 -26
- package/src/util/platform.ts +0 -5
- package/src/workflows/library.test.ts +0 -140
- package/src/workflows/library.ts +28 -82
- package/src/workspace/migrations/017-seed-persona-dirs.ts +34 -3
- package/src/workspace/migrations/019-scope-journal-to-guardian.ts +24 -3
- package/src/workspace/migrations/048-remove-workspace-hooks.ts +66 -14
- package/src/workspace/migrations/registry.ts +0 -2
- package/node_modules/@vellumai/gateway-client/src/__tests__/guardian-delivery-contract.test.ts +0 -91
- package/node_modules/@vellumai/gateway-client/src/guardian-delivery-contract.ts +0 -48
- package/node_modules/@vellumai/service-contracts/src/__tests__/channels.test.ts +0 -28
- package/node_modules/@vellumai/service-contracts/src/channels.ts +0 -41
- package/src/__tests__/code-search-tool.test.ts +0 -585
- package/src/__tests__/guardian-expiry-notifier.test.ts +0 -282
- package/src/__tests__/mcp-config-secret-boundary.test.ts +0 -390
- package/src/__tests__/plugin-pipeline.test.ts +0 -96
- package/src/__tests__/sse-actor-principal-guardian-source.test.ts +0 -102
- package/src/__tests__/steer-on-enqueue-question.test.ts +0 -181
- package/src/__tests__/workspace-migration-111-prune-seeded-callsite-defaults.test.ts +0 -208
- package/src/agent/loop-exclusive-tool.test.ts +0 -150
- package/src/api/constants/sse-replay.ts +0 -41
- package/src/api/events/conversation-notice.ts +0 -26
- package/src/approvals/guardian-channel-delivery.ts +0 -30
- package/src/approvals/guardian-expiry-notifier.ts +0 -148
- package/src/cli/commands/memory/__tests__/worker.test.ts +0 -302
- package/src/cli/commands/memory/worker.ts +0 -175
- package/src/config/__tests__/loader-callsite-strip-fallback.test.ts +0 -143
- package/src/config/prune-seeded-callsite-defaults.ts +0 -110
- package/src/contacts/__tests__/contacts-write-revoke-relay.test.ts +0 -129
- package/src/contacts/__tests__/guardian-delivery-reader.test.ts +0 -312
- package/src/contacts/__tests__/member-write-relay.test.ts +0 -202
- package/src/contacts/guardian-delivery-reader.ts +0 -223
- package/src/contacts/member-write-relay.ts +0 -189
- package/src/daemon/conversation-notices.ts +0 -60
- package/src/daemon/handlers/__tests__/config-channels.test.ts +0 -225
- package/src/hooks/hook-loader.ts +0 -341
- package/src/mcp/mcp-header-store.ts +0 -134
- package/src/memory/__tests__/301-create-watchdog-events.test.ts +0 -110
- package/src/memory/__tests__/prompt-override.test.ts +0 -192
- package/src/memory/__tests__/watchdog-events-store.test.ts +0 -161
- package/src/memory/migrations/300-add-processing-started-at.ts +0 -30
- package/src/memory/migrations/301-create-watchdog-events.ts +0 -45
- package/src/memory/migrations/__tests__/209-strip-thinking-from-consolidated.test.ts +0 -224
- package/src/memory/prompt-override.ts +0 -129
- package/src/memory/steps.ts +0 -573
- package/src/memory/watchdog-events-store.ts +0 -87
- package/src/memory/worker-control.ts +0 -118
- package/src/memory/worker-process.ts +0 -72
- package/src/notifications/__tests__/connected-channels.test.ts +0 -114
- package/src/notifications/__tests__/destination-resolver.test.ts +0 -256
- package/src/onboarding/checkin-event.test.ts +0 -222
- package/src/onboarding/checkin-event.ts +0 -321
- package/src/onboarding/schedule-checkin.ts +0 -190
- package/src/plugins/defaults/advisor/__tests__/context-pack-gating.test.ts +0 -106
- package/src/plugins/defaults/advisor/__tests__/context-pack.test.ts +0 -60
- package/src/plugins/defaults/advisor/context-pack.ts +0 -288
- package/src/plugins/defaults/memory-v3-shadow/pool-select.test.ts +0 -146
- package/src/plugins/surface-import.ts +0 -121
- package/src/providers/openai/__tests__/api-error-normalization.test.ts +0 -321
- package/src/providers/openai/api-error-normalization.ts +0 -270
- package/src/runtime/__tests__/channel-verification-service.test.ts +0 -133
- package/src/runtime/__tests__/guardian-vellum-migration.test.ts +0 -181
- package/src/runtime/__tests__/is-guardian-bound-for-channel.test.ts +0 -66
- package/src/runtime/__tests__/local-principal-trust.test.ts +0 -164
- package/src/runtime/anchored-guardian.test.ts +0 -156
- package/src/runtime/anchored-guardian.ts +0 -135
- package/src/runtime/auth/__tests__/require-bound-guardian.test.ts +0 -99
- package/src/runtime/local-principal-trust.ts +0 -52
- package/src/runtime/routes/__tests__/contact-routes.test.ts +0 -212
- package/src/runtime/routes/__tests__/global-search-routes.test.ts +0 -93
- package/src/runtime/routes/onboarding-checkin-routes.ts +0 -86
- package/src/tools/filesystem/search.ts +0 -543
- package/src/util/telemetry-db-path.ts +0 -24
- package/src/workspace/migrations/111-prune-seeded-callsite-defaults.ts +0 -134
|
@@ -7,15 +7,11 @@
|
|
|
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";
|
|
14
10
|
import type { ChannelId } from "../channels/types.js";
|
|
15
11
|
import { findContactChannel, getContact } from "../contacts/contact-store.js";
|
|
16
|
-
import {
|
|
17
|
-
import type { ChannelStatus } from "../contacts/types.js";
|
|
12
|
+
import { upsertContactChannel } from "../contacts/contacts-write.js";
|
|
18
13
|
import { ipcCallPersistent } from "../ipc/gateway-client.js";
|
|
14
|
+
import { getSqlite } from "../memory/db-connection.js";
|
|
19
15
|
import {
|
|
20
16
|
findActiveVoiceInvites,
|
|
21
17
|
findByInviteCodeHash,
|
|
@@ -27,27 +23,9 @@ import {
|
|
|
27
23
|
import { canonicalizeInboundIdentity } from "../util/canonicalize-identity.js";
|
|
28
24
|
import { getLogger } from "../util/logger.js";
|
|
29
25
|
import { hashVoiceCode } from "../util/voice-code.js";
|
|
30
|
-
import { verdictMemberFromVerdict } from "./trust-verdict-consumer.js";
|
|
31
26
|
|
|
32
27
|
const log = getLogger("invite-redemption-service");
|
|
33
28
|
|
|
34
|
-
/**
|
|
35
|
-
* Resolve the sender's existing member status for the already_member/blocked
|
|
36
|
-
* gate from the gateway trust verdict. Falls back to the local channel status
|
|
37
|
-
* when the verdict is absent or carries no resolvable member status (e.g. an
|
|
38
|
-
* externalChatId-only match or a resolutionFailed verdict), so a locally
|
|
39
|
-
* blocked contact can't bypass the gate.
|
|
40
|
-
*/
|
|
41
|
-
export async function resolveMemberGateStatus(
|
|
42
|
-
verdict: Awaited<ReturnType<typeof getInboundTrustVerdict>>,
|
|
43
|
-
localChannelStatus: ChannelStatus | null,
|
|
44
|
-
): Promise<ChannelStatus | null> {
|
|
45
|
-
const memberStatus = verdict
|
|
46
|
-
? verdictMemberFromVerdict(verdict)?.status
|
|
47
|
-
: null;
|
|
48
|
-
return memberStatus ?? localChannelStatus;
|
|
49
|
-
}
|
|
50
|
-
|
|
51
29
|
// ---------------------------------------------------------------------------
|
|
52
30
|
// Gateway lifecycle bridge (shared by all redemption paths)
|
|
53
31
|
// ---------------------------------------------------------------------------
|
|
@@ -240,22 +218,18 @@ export async function redeemInvite(params: {
|
|
|
240
218
|
const targetMismatch =
|
|
241
219
|
existingContact && existingContact.id !== invite.contactId;
|
|
242
220
|
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
existingChannel?.status ?? null,
|
|
249
|
-
);
|
|
250
|
-
|
|
251
|
-
if (existingChannel && gateStatus === "active" && !targetMismatch) {
|
|
221
|
+
if (
|
|
222
|
+
existingChannel &&
|
|
223
|
+
existingChannel.status === "active" &&
|
|
224
|
+
!targetMismatch
|
|
225
|
+
) {
|
|
252
226
|
return { ok: true, type: "already_member", memberId: existingChannel.id };
|
|
253
227
|
}
|
|
254
228
|
|
|
255
229
|
// Blocked members cannot bypass the guardian's explicit block via invite
|
|
256
230
|
// links. Return the same generic failure as an invalid token to avoid
|
|
257
231
|
// leaking membership status to the caller.
|
|
258
|
-
if (existingChannel &&
|
|
232
|
+
if (existingChannel && existingChannel.status === "blocked") {
|
|
259
233
|
return { ok: false, reason: "invalid_token" };
|
|
260
234
|
}
|
|
261
235
|
|
|
@@ -273,10 +247,14 @@ export async function redeemInvite(params: {
|
|
|
273
247
|
return { ok: false, reason: "invalid_token" };
|
|
274
248
|
}
|
|
275
249
|
|
|
276
|
-
// Inactive member reactivation: when the user already has a member record
|
|
277
|
-
// a non-active state (revoked/pending), reactivate it
|
|
278
|
-
// consume an invite use. The fresh-member path below
|
|
250
|
+
// Inactive member reactivation: when the user already has a member record
|
|
251
|
+
// in a non-active state (revoked/pending), reactivate it via upsertContactChannel
|
|
252
|
+
// and consume an invite use atomically. The fresh-member path below also
|
|
253
|
+
// uses upsertContactChannel to keep contacts in sync.
|
|
279
254
|
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");
|
|
280
258
|
const canonicalMemberId = existingChannel.address;
|
|
281
259
|
const canonicalCallerId = externalUserId
|
|
282
260
|
? canonicalizeInboundIdentity(sourceChannel as ChannelId, externalUserId)
|
|
@@ -288,17 +266,42 @@ export async function redeemInvite(params: {
|
|
|
288
266
|
? existingContact.displayName
|
|
289
267
|
: displayName;
|
|
290
268
|
|
|
291
|
-
|
|
292
|
-
// concurrent revoke/exhaustion (recordInviteUse returns false) leaves no
|
|
293
|
-
// active member behind.
|
|
269
|
+
let reactivated: ReturnType<typeof upsertContactChannel> | undefined;
|
|
294
270
|
try {
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
271
|
+
getSqlite()
|
|
272
|
+
.transaction(() => {
|
|
273
|
+
reactivated = upsertContactChannel({
|
|
274
|
+
sourceChannel,
|
|
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) {
|
|
299
303
|
return { ok: false, reason: "invalid_token" };
|
|
300
304
|
}
|
|
301
|
-
} catch (err) {
|
|
302
305
|
// Rare: the gateway claim already consumed the row but the assistant
|
|
303
306
|
// mutation failed — a recoverable wasted gateway use; no cross-process
|
|
304
307
|
// rollback is attempted.
|
|
@@ -309,30 +312,10 @@ export async function redeemInvite(params: {
|
|
|
309
312
|
throw err;
|
|
310
313
|
}
|
|
311
314
|
|
|
312
|
-
// Gateway-first: activate the member channel on the authoritative gateway
|
|
313
|
-
// before the assistant DB; the local mirror is best-effort.
|
|
314
|
-
const reactivated = await activateMemberChannel({
|
|
315
|
-
sourceChannel,
|
|
316
|
-
externalUserId,
|
|
317
|
-
externalChatId,
|
|
318
|
-
// Reactivation should not overwrite a guardian-managed nickname.
|
|
319
|
-
displayName: preservedDisplayName,
|
|
320
|
-
username,
|
|
321
|
-
policy: "allow",
|
|
322
|
-
inviteId: invite.id,
|
|
323
|
-
verifiedAt: Date.now(),
|
|
324
|
-
verifiedVia: "invite",
|
|
325
|
-
contactId: invite.contactId,
|
|
326
|
-
});
|
|
327
|
-
|
|
328
|
-
if (reactivated.status === "refused") {
|
|
329
|
-
return { ok: false, reason: "invalid_token" };
|
|
330
|
-
}
|
|
331
|
-
|
|
332
315
|
return {
|
|
333
316
|
ok: true,
|
|
334
317
|
type: "redeemed",
|
|
335
|
-
memberId: reactivated.
|
|
318
|
+
memberId: reactivated!.channel.id,
|
|
336
319
|
inviteId: invite.id,
|
|
337
320
|
};
|
|
338
321
|
}
|
|
@@ -349,17 +332,39 @@ export async function redeemInvite(params: {
|
|
|
349
332
|
}
|
|
350
333
|
}
|
|
351
334
|
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
// active member behind.
|
|
335
|
+
const STALE_INVITE_FRESH = Symbol("stale_invite_fresh");
|
|
336
|
+
let freshResult: ReturnType<typeof upsertContactChannel> | undefined;
|
|
355
337
|
try {
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
338
|
+
getSqlite()
|
|
339
|
+
.transaction(() => {
|
|
340
|
+
freshResult = upsertContactChannel({
|
|
341
|
+
sourceChannel,
|
|
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) {
|
|
360
366
|
return { ok: false, reason: "invalid_token" };
|
|
361
367
|
}
|
|
362
|
-
} catch (err) {
|
|
363
368
|
// Rare: gateway claim succeeded but the assistant mutation failed — a
|
|
364
369
|
// recoverable wasted gateway use; no cross-process rollback attempted.
|
|
365
370
|
log.error(
|
|
@@ -369,29 +374,10 @@ export async function redeemInvite(params: {
|
|
|
369
374
|
throw err;
|
|
370
375
|
}
|
|
371
376
|
|
|
372
|
-
// Gateway-first: activate the member channel on the authoritative gateway
|
|
373
|
-
// before the assistant DB; the local mirror is best-effort.
|
|
374
|
-
const freshResult = await activateMemberChannel({
|
|
375
|
-
sourceChannel,
|
|
376
|
-
externalUserId,
|
|
377
|
-
externalChatId,
|
|
378
|
-
displayName: freshDisplayName,
|
|
379
|
-
username,
|
|
380
|
-
policy: "allow",
|
|
381
|
-
inviteId: invite.id,
|
|
382
|
-
verifiedAt: Date.now(),
|
|
383
|
-
verifiedVia: "invite",
|
|
384
|
-
contactId: invite.contactId,
|
|
385
|
-
});
|
|
386
|
-
|
|
387
|
-
if (freshResult.status === "refused") {
|
|
388
|
-
return { ok: false, reason: "invalid_token" };
|
|
389
|
-
}
|
|
390
|
-
|
|
391
377
|
return {
|
|
392
378
|
ok: true,
|
|
393
379
|
type: "redeemed",
|
|
394
|
-
memberId: freshResult.
|
|
380
|
+
memberId: freshResult!.channel.id,
|
|
395
381
|
inviteId: invite.id,
|
|
396
382
|
};
|
|
397
383
|
}
|
|
@@ -479,12 +465,11 @@ export async function redeemVoiceInviteCode(params: {
|
|
|
479
465
|
// should bind the sender's identity to the target contact, not the existing one.
|
|
480
466
|
const targetMismatch = voiceContact && voiceContact.id !== invite.contactId;
|
|
481
467
|
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
existingVoiceChannel
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
if (existingVoiceChannel && gateStatus === "active" && !targetMismatch) {
|
|
468
|
+
if (
|
|
469
|
+
existingVoiceChannel &&
|
|
470
|
+
existingVoiceChannel.status === "active" &&
|
|
471
|
+
!targetMismatch
|
|
472
|
+
) {
|
|
488
473
|
return {
|
|
489
474
|
ok: true,
|
|
490
475
|
type: "already_member",
|
|
@@ -493,7 +478,7 @@ export async function redeemVoiceInviteCode(params: {
|
|
|
493
478
|
}
|
|
494
479
|
|
|
495
480
|
// Blocked members cannot bypass the guardian's explicit block
|
|
496
|
-
if (existingVoiceChannel &&
|
|
481
|
+
if (existingVoiceChannel && existingVoiceChannel.status === "blocked") {
|
|
497
482
|
return { ok: false, reason: "invalid_or_expired" };
|
|
498
483
|
}
|
|
499
484
|
|
|
@@ -511,6 +496,10 @@ export async function redeemVoiceInviteCode(params: {
|
|
|
511
496
|
return { ok: false, reason: "invalid_or_expired" };
|
|
512
497
|
}
|
|
513
498
|
|
|
499
|
+
// Atomic redemption: upsert member + consume invite use in a transaction
|
|
500
|
+
const STALE_INVITE = Symbol("stale_invite");
|
|
501
|
+
let memberId: string | undefined;
|
|
502
|
+
|
|
514
503
|
// When the invite targets a specific contact, preserve the target contact's
|
|
515
504
|
// guardian-assigned display name if it has one.
|
|
516
505
|
let preservedDisplayName = voiceContact?.displayName?.trim().length
|
|
@@ -523,20 +512,36 @@ export async function redeemVoiceInviteCode(params: {
|
|
|
523
512
|
}
|
|
524
513
|
}
|
|
525
514
|
|
|
526
|
-
// Consume the assistant invite use BEFORE activating the member, so a
|
|
527
|
-
// concurrent revoke/exhaustion (recordInviteUse returns false) leaves no
|
|
528
|
-
// active member behind.
|
|
529
515
|
try {
|
|
530
|
-
|
|
531
|
-
|
|
532
|
-
|
|
533
|
-
|
|
516
|
+
getSqlite()
|
|
517
|
+
.transaction(() => {
|
|
518
|
+
const writeResult = upsertContactChannel({
|
|
519
|
+
sourceChannel: "phone",
|
|
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;
|
|
534
539
|
})
|
|
535
|
-
|
|
536
|
-
|
|
540
|
+
.immediate();
|
|
541
|
+
} catch (err) {
|
|
542
|
+
if (err === STALE_INVITE) {
|
|
537
543
|
return { ok: false, reason: "invalid_or_expired" };
|
|
538
544
|
}
|
|
539
|
-
} catch (err) {
|
|
540
545
|
// Rare: gateway claim succeeded but the assistant mutation failed — a
|
|
541
546
|
// recoverable wasted gateway use; no cross-process rollback attempted.
|
|
542
547
|
log.error(
|
|
@@ -546,28 +551,10 @@ export async function redeemVoiceInviteCode(params: {
|
|
|
546
551
|
throw err;
|
|
547
552
|
}
|
|
548
553
|
|
|
549
|
-
// Gateway-first: activate the member channel on the authoritative gateway
|
|
550
|
-
// before the assistant DB; the local mirror is best-effort.
|
|
551
|
-
const writeResult = await activateMemberChannel({
|
|
552
|
-
sourceChannel: "phone",
|
|
553
|
-
externalUserId: callerExternalUserId,
|
|
554
|
-
externalChatId: callerExternalUserId,
|
|
555
|
-
displayName: preservedDisplayName,
|
|
556
|
-
policy: "allow",
|
|
557
|
-
inviteId: invite.id,
|
|
558
|
-
verifiedAt: Date.now(),
|
|
559
|
-
verifiedVia: "invite",
|
|
560
|
-
contactId: invite.contactId,
|
|
561
|
-
});
|
|
562
|
-
|
|
563
|
-
if (writeResult.status === "refused") {
|
|
564
|
-
return { ok: false, reason: "invalid_or_expired" };
|
|
565
|
-
}
|
|
566
|
-
|
|
567
554
|
return {
|
|
568
555
|
ok: true,
|
|
569
556
|
type: "redeemed",
|
|
570
|
-
memberId:
|
|
557
|
+
memberId: memberId!,
|
|
571
558
|
inviteId: invite.id,
|
|
572
559
|
};
|
|
573
560
|
}
|
|
@@ -652,22 +639,18 @@ export async function redeemInviteByCode(params: {
|
|
|
652
639
|
const targetMismatch =
|
|
653
640
|
existingContact && existingContact.id !== invite.contactId;
|
|
654
641
|
|
|
655
|
-
|
|
656
|
-
|
|
657
|
-
|
|
658
|
-
|
|
659
|
-
|
|
660
|
-
existingChannel?.status ?? null,
|
|
661
|
-
);
|
|
662
|
-
|
|
663
|
-
if (existingChannel && gateStatus === "active" && !targetMismatch) {
|
|
642
|
+
if (
|
|
643
|
+
existingChannel &&
|
|
644
|
+
existingChannel.status === "active" &&
|
|
645
|
+
!targetMismatch
|
|
646
|
+
) {
|
|
664
647
|
return { ok: true, type: "already_member", memberId: existingChannel.id };
|
|
665
648
|
}
|
|
666
649
|
|
|
667
650
|
// Blocked members cannot bypass the guardian's explicit block via invite
|
|
668
651
|
// codes. Return the same generic failure as an invalid token to avoid
|
|
669
652
|
// leaking membership status to the caller.
|
|
670
|
-
if (existingChannel &&
|
|
653
|
+
if (existingChannel && existingChannel.status === "blocked") {
|
|
671
654
|
return { ok: false, reason: "invalid_token" };
|
|
672
655
|
}
|
|
673
656
|
|
|
@@ -684,9 +667,10 @@ export async function redeemInviteByCode(params: {
|
|
|
684
667
|
return { ok: false, reason: "invalid_token" };
|
|
685
668
|
}
|
|
686
669
|
|
|
687
|
-
// Inactive member reactivation: reactivate
|
|
688
|
-
// invite use.
|
|
670
|
+
// Inactive member reactivation: reactivate via upsertContactChannel and consume
|
|
671
|
+
// an invite use atomically.
|
|
689
672
|
if (existingChannel && !targetMismatch) {
|
|
673
|
+
const STALE_INVITE_REACTIVATE = Symbol("stale_invite_reactivate");
|
|
690
674
|
const canonicalMemberId = existingChannel.address;
|
|
691
675
|
const canonicalCallerId = externalUserId
|
|
692
676
|
? canonicalizeInboundIdentity(sourceChannel as ChannelId, externalUserId)
|
|
@@ -698,17 +682,38 @@ export async function redeemInviteByCode(params: {
|
|
|
698
682
|
? existingContact.displayName
|
|
699
683
|
: displayName;
|
|
700
684
|
|
|
701
|
-
|
|
702
|
-
// concurrent revoke/exhaustion (recordInviteUse returns false) leaves no
|
|
703
|
-
// active member behind.
|
|
685
|
+
let reactivated: ReturnType<typeof upsertContactChannel> | undefined;
|
|
704
686
|
try {
|
|
705
|
-
|
|
706
|
-
|
|
707
|
-
|
|
708
|
-
|
|
687
|
+
getSqlite()
|
|
688
|
+
.transaction(() => {
|
|
689
|
+
reactivated = upsertContactChannel({
|
|
690
|
+
sourceChannel,
|
|
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) {
|
|
709
715
|
return { ok: false, reason: "invalid_token" };
|
|
710
716
|
}
|
|
711
|
-
} catch (err) {
|
|
712
717
|
// Rare: gateway claim succeeded but the assistant mutation failed — a
|
|
713
718
|
// recoverable wasted gateway use; no cross-process rollback attempted.
|
|
714
719
|
log.error(
|
|
@@ -718,29 +723,10 @@ export async function redeemInviteByCode(params: {
|
|
|
718
723
|
throw err;
|
|
719
724
|
}
|
|
720
725
|
|
|
721
|
-
// Gateway-first: activate the member channel on the authoritative gateway
|
|
722
|
-
// before the assistant DB; the local mirror is best-effort.
|
|
723
|
-
const reactivated = await activateMemberChannel({
|
|
724
|
-
sourceChannel,
|
|
725
|
-
externalUserId,
|
|
726
|
-
externalChatId,
|
|
727
|
-
displayName: preservedDisplayName,
|
|
728
|
-
username,
|
|
729
|
-
policy: "allow",
|
|
730
|
-
inviteId: invite.id,
|
|
731
|
-
verifiedAt: Date.now(),
|
|
732
|
-
verifiedVia: "invite",
|
|
733
|
-
contactId: invite.contactId,
|
|
734
|
-
});
|
|
735
|
-
|
|
736
|
-
if (reactivated.status === "refused") {
|
|
737
|
-
return { ok: false, reason: "invalid_token" };
|
|
738
|
-
}
|
|
739
|
-
|
|
740
726
|
return {
|
|
741
727
|
ok: true,
|
|
742
728
|
type: "redeemed",
|
|
743
|
-
memberId: reactivated.
|
|
729
|
+
memberId: reactivated!.channel.id,
|
|
744
730
|
inviteId: invite.id,
|
|
745
731
|
};
|
|
746
732
|
}
|
|
@@ -757,17 +743,39 @@ export async function redeemInviteByCode(params: {
|
|
|
757
743
|
}
|
|
758
744
|
}
|
|
759
745
|
|
|
760
|
-
|
|
761
|
-
|
|
762
|
-
// active member behind.
|
|
746
|
+
const STALE_INVITE_FRESH = Symbol("stale_invite_fresh");
|
|
747
|
+
let freshResult: ReturnType<typeof upsertContactChannel> | undefined;
|
|
763
748
|
try {
|
|
764
|
-
|
|
765
|
-
|
|
766
|
-
|
|
767
|
-
|
|
749
|
+
getSqlite()
|
|
750
|
+
.transaction(() => {
|
|
751
|
+
freshResult = upsertContactChannel({
|
|
752
|
+
sourceChannel,
|
|
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) {
|
|
768
777
|
return { ok: false, reason: "invalid_token" };
|
|
769
778
|
}
|
|
770
|
-
} catch (err) {
|
|
771
779
|
// Rare: gateway claim succeeded but the assistant mutation failed — a
|
|
772
780
|
// recoverable wasted gateway use; no cross-process rollback attempted.
|
|
773
781
|
log.error(
|
|
@@ -777,29 +785,10 @@ export async function redeemInviteByCode(params: {
|
|
|
777
785
|
throw err;
|
|
778
786
|
}
|
|
779
787
|
|
|
780
|
-
// Gateway-first: activate the member channel on the authoritative gateway
|
|
781
|
-
// before the assistant DB; the local mirror is best-effort.
|
|
782
|
-
const freshResult = await activateMemberChannel({
|
|
783
|
-
sourceChannel,
|
|
784
|
-
externalUserId,
|
|
785
|
-
externalChatId,
|
|
786
|
-
displayName: freshDisplayName,
|
|
787
|
-
username,
|
|
788
|
-
policy: "allow",
|
|
789
|
-
inviteId: invite.id,
|
|
790
|
-
verifiedAt: Date.now(),
|
|
791
|
-
verifiedVia: "invite",
|
|
792
|
-
contactId: invite.contactId,
|
|
793
|
-
});
|
|
794
|
-
|
|
795
|
-
if (freshResult.status === "refused") {
|
|
796
|
-
return { ok: false, reason: "invalid_token" };
|
|
797
|
-
}
|
|
798
|
-
|
|
799
788
|
return {
|
|
800
789
|
ok: true,
|
|
801
790
|
type: "redeemed",
|
|
802
|
-
memberId: freshResult.
|
|
791
|
+
memberId: freshResult!.channel.id,
|
|
803
792
|
inviteId: invite.id,
|
|
804
793
|
};
|
|
805
794
|
}
|