@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
|
@@ -1,223 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Gateway-backed guardian binding + delivery reader.
|
|
3
|
-
*
|
|
4
|
-
* Resolves the active guardian binding(s) and their per-channel delivery
|
|
5
|
-
* endpoints from the gateway via the `resolve_guardian_delivery` IPC route,
|
|
6
|
-
* validating the response against {@link ResolveGuardianDeliveryResponseSchema}.
|
|
7
|
-
*
|
|
8
|
-
* Guardian binding is near-static — it only changes on guardian onboarding /
|
|
9
|
-
* verification or revocation — yet this reader sits on many hot paths. To keep
|
|
10
|
-
* those paths off the IPC, results are cached behind a minutes-scale TTL and
|
|
11
|
-
* coalesced single-flight so a cold cache storms the gateway at most once.
|
|
12
|
-
*
|
|
13
|
-
* Freshness comes from two sources: the {@link invalidateGuardianDeliveryCache}
|
|
14
|
-
* subscription to `onContactChange` (contact mutations clear the cache), and
|
|
15
|
-
* {@link getGuardianDeliveryFresh} reads on existence guards (gateway-side
|
|
16
|
-
* binding writes don't invalidate the daemon cache, so those paths read fresh).
|
|
17
|
-
*
|
|
18
|
-
* Returns `null` on ANY failure (transport failure, malformed shape, timeout,
|
|
19
|
-
* or thrown error); failures are NOT cached, so a recovered gateway is retried
|
|
20
|
-
* on the next call.
|
|
21
|
-
*/
|
|
22
|
-
|
|
23
|
-
import {
|
|
24
|
-
type GuardianDelivery,
|
|
25
|
-
ResolveGuardianDeliveryResponseSchema,
|
|
26
|
-
} from "@vellumai/gateway-client";
|
|
27
|
-
|
|
28
|
-
import { ipcCall } from "../ipc/gateway-client.js";
|
|
29
|
-
import { onContactChange } from "./contact-events.js";
|
|
30
|
-
|
|
31
|
-
// Short IPC timeout so the read resolves promptly rather than stalling a hot
|
|
32
|
-
// path on a gateway that accepts the socket but hangs.
|
|
33
|
-
const GUARDIAN_DELIVERY_IPC_TIMEOUT_MS = 2_000;
|
|
34
|
-
|
|
35
|
-
// Guardian binding is near-static, so a minutes-scale TTL is safe; freshness is
|
|
36
|
-
// driven primarily by event-based invalidation, not by this backstop expiry.
|
|
37
|
-
const GUARDIAN_DELIVERY_CACHE_TTL_MS = 300_000;
|
|
38
|
-
|
|
39
|
-
interface CacheEntry {
|
|
40
|
-
guardians: GuardianDelivery[];
|
|
41
|
-
fetchedAt: number;
|
|
42
|
-
}
|
|
43
|
-
|
|
44
|
-
const cache = new Map<string, CacheEntry>();
|
|
45
|
-
// Tracks whether the in-flight fetch was a force-refresh, so a fresh read never
|
|
46
|
-
// coalesces with an older non-force fetch that may predate a gateway-side write.
|
|
47
|
-
const inFlight = new Map<
|
|
48
|
-
string,
|
|
49
|
-
{ promise: Promise<GuardianDelivery[] | null>; fresh: boolean }
|
|
50
|
-
>();
|
|
51
|
-
|
|
52
|
-
// Bumped on every invalidation. A fetch captures the generation when it starts
|
|
53
|
-
// and only writes its result to the cache if the generation is unchanged on
|
|
54
|
-
// resolve, so an invalidation mid-flight can't repopulate a stale pre-change
|
|
55
|
-
// result and mask a guardian-binding change.
|
|
56
|
-
let cacheGeneration = 0;
|
|
57
|
-
|
|
58
|
-
function cacheKey(channelTypes?: string[]): string {
|
|
59
|
-
if (!channelTypes || channelTypes.length === 0) return "ALL";
|
|
60
|
-
return [...channelTypes].sort().join(",");
|
|
61
|
-
}
|
|
62
|
-
|
|
63
|
-
async function fetchGuardianDelivery(
|
|
64
|
-
input: { channelTypes?: string[] },
|
|
65
|
-
): Promise<GuardianDelivery[] | null> {
|
|
66
|
-
try {
|
|
67
|
-
const result = await ipcCall(
|
|
68
|
-
"resolve_guardian_delivery",
|
|
69
|
-
input,
|
|
70
|
-
GUARDIAN_DELIVERY_IPC_TIMEOUT_MS,
|
|
71
|
-
);
|
|
72
|
-
if (!result) return null;
|
|
73
|
-
|
|
74
|
-
const parsed = ResolveGuardianDeliveryResponseSchema.safeParse(result);
|
|
75
|
-
return parsed.success ? parsed.data.guardians : null;
|
|
76
|
-
} catch {
|
|
77
|
-
return null;
|
|
78
|
-
}
|
|
79
|
-
}
|
|
80
|
-
|
|
81
|
-
// Shared fetch path for both the cached and fresh public surfaces. When
|
|
82
|
-
// `forceRefresh` is set the cached entry is bypassed; the read is still
|
|
83
|
-
// single-flight and still populates the cache with the fresh result.
|
|
84
|
-
async function readGuardianDelivery(
|
|
85
|
-
input: { channelTypes?: string[]; forceRefresh?: boolean },
|
|
86
|
-
): Promise<GuardianDelivery[] | null> {
|
|
87
|
-
const key = cacheKey(input.channelTypes);
|
|
88
|
-
|
|
89
|
-
if (!input.forceRefresh) {
|
|
90
|
-
const cached = cache.get(key);
|
|
91
|
-
if (
|
|
92
|
-
cached &&
|
|
93
|
-
Date.now() - cached.fetchedAt < GUARDIAN_DELIVERY_CACHE_TTL_MS
|
|
94
|
-
) {
|
|
95
|
-
return cached.guardians;
|
|
96
|
-
}
|
|
97
|
-
}
|
|
98
|
-
|
|
99
|
-
// A non-force read may coalesce with any in-flight fetch. A force read may
|
|
100
|
-
// only coalesce with another force fetch — never with a non-force fetch that
|
|
101
|
-
// could have started before a gateway-side binding write and resolve stale.
|
|
102
|
-
const pending = inFlight.get(key);
|
|
103
|
-
if (pending && (!input.forceRefresh || pending.fresh)) return pending.promise;
|
|
104
|
-
|
|
105
|
-
const startGen = cacheGeneration;
|
|
106
|
-
const promise = fetchGuardianDelivery({ channelTypes: input.channelTypes })
|
|
107
|
-
.then((guardians) => {
|
|
108
|
-
// Skip the write if an invalidation fired during the fetch: the result
|
|
109
|
-
// may predate the change. Return it to this caller (freshest it has) but
|
|
110
|
-
// leave the cache empty so the next call re-fetches.
|
|
111
|
-
if (guardians && cacheGeneration === startGen) {
|
|
112
|
-
cache.set(key, { guardians, fetchedAt: Date.now() });
|
|
113
|
-
}
|
|
114
|
-
return guardians;
|
|
115
|
-
})
|
|
116
|
-
.finally(() => {
|
|
117
|
-
// Only clear the slot if it still holds this fetch — a concurrent force
|
|
118
|
-
// read may have replaced a non-force entry (or vice versa).
|
|
119
|
-
if (inFlight.get(key)?.promise === promise) inFlight.delete(key);
|
|
120
|
-
});
|
|
121
|
-
|
|
122
|
-
inFlight.set(key, { promise, fresh: !!input.forceRefresh });
|
|
123
|
-
return promise;
|
|
124
|
-
}
|
|
125
|
-
|
|
126
|
-
/**
|
|
127
|
-
* Resolve active guardian deliveries, optionally filtered by channel type.
|
|
128
|
-
* Returns the cached list when fresh, otherwise fetches (single-flight) and
|
|
129
|
-
* caches on success. Returns `null` on failure without caching.
|
|
130
|
-
*
|
|
131
|
-
* To force an uncached read, call {@link getGuardianDeliveryFresh} — the only
|
|
132
|
-
* public fresh-read entry point.
|
|
133
|
-
*/
|
|
134
|
-
export async function getGuardianDelivery(
|
|
135
|
-
input?: { channelTypes?: string[] },
|
|
136
|
-
): Promise<GuardianDelivery[] | null> {
|
|
137
|
-
return readGuardianDelivery({ channelTypes: input?.channelTypes });
|
|
138
|
-
}
|
|
139
|
-
|
|
140
|
-
/**
|
|
141
|
-
* Synchronous read of the already-cached guardian deliveries, without any IO.
|
|
142
|
-
*
|
|
143
|
-
* Returns the fresh cached list for the given channel filter, or `undefined`
|
|
144
|
-
* when the cache is cold or expired. Used by sync hot paths (SSE subscribe)
|
|
145
|
-
* that cannot await {@link getGuardianDelivery} but must resolve the SAME
|
|
146
|
-
* gateway-owned principal the async paths land on. A cold/expired return lets
|
|
147
|
-
* the caller fall back to the local store as before.
|
|
148
|
-
*/
|
|
149
|
-
export function peekCachedGuardianDelivery(
|
|
150
|
-
input?: { channelTypes?: string[] },
|
|
151
|
-
): GuardianDelivery[] | undefined {
|
|
152
|
-
const cached = cache.get(cacheKey(input?.channelTypes));
|
|
153
|
-
if (!cached) return undefined;
|
|
154
|
-
if (Date.now() - cached.fetchedAt >= GUARDIAN_DELIVERY_CACHE_TTL_MS) {
|
|
155
|
-
return undefined;
|
|
156
|
-
}
|
|
157
|
-
return cached.guardians;
|
|
158
|
-
}
|
|
159
|
-
|
|
160
|
-
/**
|
|
161
|
-
* Fresh (uncached) variant of {@link getGuardianDelivery}. Existence guards read
|
|
162
|
-
* fresh because gateway-side binding writes don't invalidate the daemon cache.
|
|
163
|
-
* Still single-flight, and still populates the cache with the fresh result.
|
|
164
|
-
*/
|
|
165
|
-
export async function getGuardianDeliveryFresh(
|
|
166
|
-
input?: { channelTypes?: string[] },
|
|
167
|
-
): Promise<GuardianDelivery[] | null> {
|
|
168
|
-
return readGuardianDelivery({
|
|
169
|
-
channelTypes: input?.channelTypes,
|
|
170
|
-
forceRefresh: true,
|
|
171
|
-
});
|
|
172
|
-
}
|
|
173
|
-
|
|
174
|
-
/**
|
|
175
|
-
* Clear ALL cached guardian deliveries. Subscribed to `onContactChange` so
|
|
176
|
-
* contact mutations refetch on the next read; also exported for any caller that
|
|
177
|
-
* wants to invalidate explicitly.
|
|
178
|
-
*/
|
|
179
|
-
export function invalidateGuardianDeliveryCache(): void {
|
|
180
|
-
cacheGeneration += 1;
|
|
181
|
-
cache.clear();
|
|
182
|
-
inFlight.clear();
|
|
183
|
-
}
|
|
184
|
-
|
|
185
|
-
onContactChange(invalidateGuardianDeliveryCache);
|
|
186
|
-
|
|
187
|
-
/** First active guardian delivery for the given channel type, if any. */
|
|
188
|
-
export function guardianForChannel(
|
|
189
|
-
list: GuardianDelivery[],
|
|
190
|
-
channelType: string,
|
|
191
|
-
): GuardianDelivery | undefined {
|
|
192
|
-
return list.find(
|
|
193
|
-
(g) => g.channelType === channelType && g.status === "active",
|
|
194
|
-
);
|
|
195
|
-
}
|
|
196
|
-
|
|
197
|
-
/** First guardian delivery overall — the `listGuardianChannels` fallback. */
|
|
198
|
-
export function anyGuardian(
|
|
199
|
-
list: GuardianDelivery[],
|
|
200
|
-
): GuardianDelivery | undefined {
|
|
201
|
-
return list[0];
|
|
202
|
-
}
|
|
203
|
-
|
|
204
|
-
/**
|
|
205
|
-
* Resolve a guardian displayName for voice surfaces: prefer the phone-channel
|
|
206
|
-
* guardian, falling back to any guardian. Returns `undefined` when the list is
|
|
207
|
-
* absent or no guardian carries a displayName.
|
|
208
|
-
*/
|
|
209
|
-
export function voiceGuardianDisplayName(
|
|
210
|
-
list: GuardianDelivery[] | null,
|
|
211
|
-
): string | undefined {
|
|
212
|
-
const guardian = list
|
|
213
|
-
? (guardianForChannel(list, "phone") ?? anyGuardian(list))
|
|
214
|
-
: undefined;
|
|
215
|
-
return guardian?.displayName ?? undefined;
|
|
216
|
-
}
|
|
217
|
-
|
|
218
|
-
/** Test-only: reset cache + in-flight state for deterministic test runs. */
|
|
219
|
-
export function __resetGuardianDeliveryCacheForTest(): void {
|
|
220
|
-
cache.clear();
|
|
221
|
-
inFlight.clear();
|
|
222
|
-
cacheGeneration = 0;
|
|
223
|
-
}
|
|
@@ -1,189 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Gateway-first member-channel write relay.
|
|
3
|
-
*
|
|
4
|
-
* The gateway is the ACL source of truth. Each write goes to the gateway first;
|
|
5
|
-
* the assistant-DB write in contacts-write.ts is a best-effort local mirror that
|
|
6
|
-
* never throws and never gates the gateway-owned outcome.
|
|
7
|
-
*/
|
|
8
|
-
|
|
9
|
-
import {
|
|
10
|
-
MarkChannelRevokedIpcResponseSchema,
|
|
11
|
-
UpsertVerifiedChannelIpcResponseSchema,
|
|
12
|
-
} from "@vellumai/gateway-client/gateway-ipc-contracts";
|
|
13
|
-
|
|
14
|
-
import { log } from "../daemon/handlers/shared.js";
|
|
15
|
-
import { ipcCallPersistent } from "../ipc/gateway-client.js";
|
|
16
|
-
import { revokeMember, upsertContactChannel } from "./contacts-write.js";
|
|
17
|
-
import type { ContactWriteResult } from "./types.js";
|
|
18
|
-
|
|
19
|
-
// ── Activate ─────────────────────────────────────────────────────────
|
|
20
|
-
|
|
21
|
-
export interface ActivateMemberChannelParams {
|
|
22
|
-
sourceChannel: string;
|
|
23
|
-
externalUserId?: string;
|
|
24
|
-
externalChatId?: string;
|
|
25
|
-
contactId?: string;
|
|
26
|
-
displayName?: string;
|
|
27
|
-
username?: string;
|
|
28
|
-
inviteId?: string;
|
|
29
|
-
verifiedAt?: number;
|
|
30
|
-
verifiedVia?: string;
|
|
31
|
-
policy?: string;
|
|
32
|
-
}
|
|
33
|
-
|
|
34
|
-
/**
|
|
35
|
-
* Outcome of a gateway-first member activation. The gateway owns the ACL
|
|
36
|
-
* verdict; `activated` carries a stable memberId (gateway channel id, or the
|
|
37
|
-
* local mirror's id when present) regardless of whether the best-effort local
|
|
38
|
-
* mirror produced a row. `refused` means the gateway authoritatively denied the
|
|
39
|
-
* actor (blocked/revoked) — callers must NOT treat it as an activation.
|
|
40
|
-
*/
|
|
41
|
-
export type ActivateMemberOutcome =
|
|
42
|
-
| { status: "activated"; memberId: string; member: ContactWriteResult | null }
|
|
43
|
-
| { status: "refused" };
|
|
44
|
-
|
|
45
|
-
/**
|
|
46
|
-
* Activate a member channel gateway-first, then mirror the activation to the
|
|
47
|
-
* assistant DB best-effort. The gateway owns the ACL outcome; the local mirror
|
|
48
|
-
* supplies the native contact/channel callers still wire downstream.
|
|
49
|
-
*
|
|
50
|
-
* A gateway failure fails open with a logged warning (matching the redemption
|
|
51
|
-
* service's record_invite_redemption posture) so a transient gateway outage
|
|
52
|
-
* never drops a legitimate activation — the local mirror still stands.
|
|
53
|
-
*
|
|
54
|
-
* Returns an `activated` outcome with a stable memberId on success, or
|
|
55
|
-
* `refused` when the gateway denies the actor. A best-effort local-mirror
|
|
56
|
-
* failure never downgrades a verified gateway activation.
|
|
57
|
-
*/
|
|
58
|
-
export async function activateMemberChannel(
|
|
59
|
-
params: ActivateMemberChannelParams,
|
|
60
|
-
): Promise<ActivateMemberOutcome> {
|
|
61
|
-
const address = params.externalUserId ?? params.externalChatId;
|
|
62
|
-
const externalChatId = params.externalChatId ?? params.externalUserId;
|
|
63
|
-
|
|
64
|
-
let gatewayChannelId: string | undefined;
|
|
65
|
-
|
|
66
|
-
if (address && externalChatId) {
|
|
67
|
-
try {
|
|
68
|
-
const result = await ipcCallPersistent("upsert_verified_channel", {
|
|
69
|
-
type: params.sourceChannel,
|
|
70
|
-
address,
|
|
71
|
-
externalChatId,
|
|
72
|
-
displayName: params.displayName,
|
|
73
|
-
username: params.username,
|
|
74
|
-
verifiedVia: params.verifiedVia ?? "invite",
|
|
75
|
-
contactId: params.contactId,
|
|
76
|
-
// Invite redemption may reactivate a revoked member; blocked actors are
|
|
77
|
-
// still refused by the gateway guard.
|
|
78
|
-
allowRevokedReactivation: true,
|
|
79
|
-
});
|
|
80
|
-
const parsed = UpsertVerifiedChannelIpcResponseSchema.parse(result);
|
|
81
|
-
// The gateway refused the actor (blocked/revoked): do NOT mirror an
|
|
82
|
-
// active local channel for an actor the gateway has denied.
|
|
83
|
-
if (!parsed.verified) {
|
|
84
|
-
log.warn(
|
|
85
|
-
{ sourceChannel: params.sourceChannel },
|
|
86
|
-
"Gateway refused the channel activation; skipping the local mirror",
|
|
87
|
-
);
|
|
88
|
-
return { status: "refused" };
|
|
89
|
-
}
|
|
90
|
-
gatewayChannelId = parsed.channel?.id;
|
|
91
|
-
} catch (err) {
|
|
92
|
-
// Fail-open: the gateway write may or may not have landed. Proceed to the
|
|
93
|
-
// local mirror so a transient outage never drops a legitimate activation.
|
|
94
|
-
log.warn(
|
|
95
|
-
{ err, sourceChannel: params.sourceChannel },
|
|
96
|
-
"upsert_verified_channel relay unavailable — failing open (local mirror still applies)",
|
|
97
|
-
);
|
|
98
|
-
}
|
|
99
|
-
}
|
|
100
|
-
|
|
101
|
-
const member = mirrorLocalActivation(params);
|
|
102
|
-
const memberId = member?.channel.id ?? gatewayChannelId;
|
|
103
|
-
if (!memberId) {
|
|
104
|
-
// No gateway channel (the relay threw or was skipped) AND no local mirror —
|
|
105
|
-
// no stable id to hand the caller. Surface as refused so the caller maps it
|
|
106
|
-
// to a non-redeemed outcome instead of crashing on a missing memberId.
|
|
107
|
-
log.error(
|
|
108
|
-
{ sourceChannel: params.sourceChannel },
|
|
109
|
-
"Member activation produced no gateway channel and no local mirror; no memberId to return",
|
|
110
|
-
);
|
|
111
|
-
return { status: "refused" };
|
|
112
|
-
}
|
|
113
|
-
return { status: "activated", memberId, member };
|
|
114
|
-
}
|
|
115
|
-
|
|
116
|
-
/** Best-effort local mirror of the activation. Swallows failures. */
|
|
117
|
-
function mirrorLocalActivation(
|
|
118
|
-
params: ActivateMemberChannelParams,
|
|
119
|
-
): ContactWriteResult | null {
|
|
120
|
-
try {
|
|
121
|
-
return upsertContactChannel({
|
|
122
|
-
sourceChannel: params.sourceChannel,
|
|
123
|
-
externalUserId: params.externalUserId,
|
|
124
|
-
externalChatId: params.externalChatId,
|
|
125
|
-
displayName: params.displayName,
|
|
126
|
-
username: params.username,
|
|
127
|
-
role: "contact",
|
|
128
|
-
status: "active",
|
|
129
|
-
policy: params.policy ?? "allow",
|
|
130
|
-
inviteId: params.inviteId,
|
|
131
|
-
verifiedAt: params.verifiedAt,
|
|
132
|
-
verifiedVia: params.verifiedVia ?? "invite",
|
|
133
|
-
contactId: params.contactId,
|
|
134
|
-
});
|
|
135
|
-
} catch (err) {
|
|
136
|
-
log.error(
|
|
137
|
-
{ err, sourceChannel: params.sourceChannel },
|
|
138
|
-
"Local activation mirror failed after gateway activation; gateway outcome stands",
|
|
139
|
-
);
|
|
140
|
-
return null;
|
|
141
|
-
}
|
|
142
|
-
}
|
|
143
|
-
|
|
144
|
-
// ── Revoke ───────────────────────────────────────────────────────────
|
|
145
|
-
|
|
146
|
-
/**
|
|
147
|
-
* Revoke a member channel gateway-first, then mirror the downgrade to the
|
|
148
|
-
* assistant DB best-effort. The memberId may be a plain channel ID or the
|
|
149
|
-
* composite contactId:channelId form revokeMember accepts.
|
|
150
|
-
*
|
|
151
|
-
* Returns the local ContactWriteResult so callers still get the native
|
|
152
|
-
* contact/channel, or null when the local mirror produces no result.
|
|
153
|
-
*/
|
|
154
|
-
export async function revokeMemberChannel(
|
|
155
|
-
memberId: string,
|
|
156
|
-
reason?: string,
|
|
157
|
-
): Promise<ContactWriteResult | null> {
|
|
158
|
-
const channelId = memberId.includes(":") ? memberId.split(":")[1] : memberId;
|
|
159
|
-
|
|
160
|
-
// Always relay; the gateway owns the ACL outcome and mark_channel_revoked is
|
|
161
|
-
// idempotent (already-revoked → didWrite:false). Skipping on the local mirror
|
|
162
|
-
// status would suppress a needed revoke when the mirror lags the gateway.
|
|
163
|
-
const result = await ipcCallPersistent("mark_channel_revoked", {
|
|
164
|
-
contactChannelId: channelId,
|
|
165
|
-
reason,
|
|
166
|
-
});
|
|
167
|
-
const parsed = MarkChannelRevokedIpcResponseSchema.parse(result);
|
|
168
|
-
if (!parsed.ok) {
|
|
169
|
-
throw new Error("mark_channel_revoked relay returned ok: false");
|
|
170
|
-
}
|
|
171
|
-
|
|
172
|
-
return mirrorLocalRevoke(memberId, reason);
|
|
173
|
-
}
|
|
174
|
-
|
|
175
|
-
/** Best-effort local mirror of the revoke. Swallows failures. */
|
|
176
|
-
function mirrorLocalRevoke(
|
|
177
|
-
memberId: string,
|
|
178
|
-
reason?: string,
|
|
179
|
-
): ContactWriteResult | null {
|
|
180
|
-
try {
|
|
181
|
-
return revokeMember(memberId, reason);
|
|
182
|
-
} catch (err) {
|
|
183
|
-
log.error(
|
|
184
|
-
{ err, memberId },
|
|
185
|
-
"Local revoke mirror failed after gateway revoke; gateway downgrade stands",
|
|
186
|
-
);
|
|
187
|
-
return null;
|
|
188
|
-
}
|
|
189
|
-
}
|
|
@@ -1,60 +0,0 @@
|
|
|
1
|
-
import type { ConversationNoticeEvent } from "../api/events/conversation-notice.js";
|
|
2
|
-
|
|
3
|
-
export type PendingConversationNotice = Omit<
|
|
4
|
-
ConversationNoticeEvent,
|
|
5
|
-
"type" | "conversationId"
|
|
6
|
-
>;
|
|
7
|
-
|
|
8
|
-
const MAX_TRACKED_CONVERSATIONS = 256;
|
|
9
|
-
|
|
10
|
-
const pendingNotices = new Map<
|
|
11
|
-
string,
|
|
12
|
-
Map<string, PendingConversationNotice>
|
|
13
|
-
>();
|
|
14
|
-
|
|
15
|
-
function touchConversation(
|
|
16
|
-
conversationId: string,
|
|
17
|
-
): Map<string, PendingConversationNotice> {
|
|
18
|
-
const existing = pendingNotices.get(conversationId);
|
|
19
|
-
if (existing) {
|
|
20
|
-
pendingNotices.delete(conversationId);
|
|
21
|
-
pendingNotices.set(conversationId, existing);
|
|
22
|
-
return existing;
|
|
23
|
-
}
|
|
24
|
-
if (pendingNotices.size >= MAX_TRACKED_CONVERSATIONS) {
|
|
25
|
-
const oldest = pendingNotices.keys().next().value;
|
|
26
|
-
if (oldest !== undefined) pendingNotices.delete(oldest);
|
|
27
|
-
}
|
|
28
|
-
const next = new Map<string, PendingConversationNotice>();
|
|
29
|
-
pendingNotices.set(conversationId, next);
|
|
30
|
-
return next;
|
|
31
|
-
}
|
|
32
|
-
|
|
33
|
-
export function queueConversationNotice(
|
|
34
|
-
conversationId: string,
|
|
35
|
-
key: string,
|
|
36
|
-
notice: PendingConversationNotice,
|
|
37
|
-
): void {
|
|
38
|
-
touchConversation(conversationId).set(key, notice);
|
|
39
|
-
}
|
|
40
|
-
|
|
41
|
-
export function drainConversationNotices(
|
|
42
|
-
conversationId: string,
|
|
43
|
-
): ConversationNoticeEvent[] {
|
|
44
|
-
const notices = pendingNotices.get(conversationId);
|
|
45
|
-
if (!notices) return [];
|
|
46
|
-
pendingNotices.delete(conversationId);
|
|
47
|
-
return Array.from(notices.values(), (notice) => ({
|
|
48
|
-
type: "conversation_notice" as const,
|
|
49
|
-
conversationId,
|
|
50
|
-
...notice,
|
|
51
|
-
}));
|
|
52
|
-
}
|
|
53
|
-
|
|
54
|
-
export function clearConversationNotices(conversationId: string): void {
|
|
55
|
-
pendingNotices.delete(conversationId);
|
|
56
|
-
}
|
|
57
|
-
|
|
58
|
-
export function resetConversationNoticesForTests(): void {
|
|
59
|
-
pendingNotices.clear();
|
|
60
|
-
}
|
|
@@ -1,225 +0,0 @@
|
|
|
1
|
-
import { beforeEach, describe, expect, mock, test } from "bun:test";
|
|
2
|
-
|
|
3
|
-
import type { GuardianDelivery } from "@vellumai/gateway-client";
|
|
4
|
-
|
|
5
|
-
import type { ContactChannel } from "../../../contacts/types.js";
|
|
6
|
-
|
|
7
|
-
let mockGuardians: GuardianDelivery[] | null = null;
|
|
8
|
-
let mockBinding: { guardianExternalUserId: string; guardianDeliveryChatId: string } | null = null;
|
|
9
|
-
let mockContactChannel: { channel: ContactChannel } | null = null;
|
|
10
|
-
let mockChannel: ContactChannel | null = null;
|
|
11
|
-
let mockGwContactChannels: Array<{ id: string; status: string; verifiedAt: number | null }> | null =
|
|
12
|
-
null;
|
|
13
|
-
let ipcCalls: Array<{ method: string; payload: unknown }> = [];
|
|
14
|
-
|
|
15
|
-
mock.module("../../../contacts/guardian-delivery-reader.js", () => ({
|
|
16
|
-
getGuardianDelivery: async (input?: { channelTypes?: string[] }) => {
|
|
17
|
-
if (mockGuardians == null) return null;
|
|
18
|
-
if (!input?.channelTypes) return mockGuardians;
|
|
19
|
-
return mockGuardians.filter((g) =>
|
|
20
|
-
input.channelTypes!.includes(g.channelType),
|
|
21
|
-
);
|
|
22
|
-
},
|
|
23
|
-
}));
|
|
24
|
-
|
|
25
|
-
mock.module("../../../contacts/contact-store.js", () => ({
|
|
26
|
-
findContactChannel: () => mockContactChannel,
|
|
27
|
-
findGuardianForChannel: () => null,
|
|
28
|
-
getChannelById: () => mockChannel,
|
|
29
|
-
getContact: () => ({ id: "contact-1", displayName: "Pat" }),
|
|
30
|
-
}));
|
|
31
|
-
|
|
32
|
-
mock.module("../../../contacts/contact-events.js", () => ({
|
|
33
|
-
emitContactChange: () => {},
|
|
34
|
-
onContactChange: () => {},
|
|
35
|
-
}));
|
|
36
|
-
|
|
37
|
-
mock.module("../../../ipc/gateway-client.js", () => ({
|
|
38
|
-
ipcCallPersistent: async (method: string, payload: unknown) => {
|
|
39
|
-
ipcCalls.push({ method, payload });
|
|
40
|
-
if (method === "contacts_get_rich") {
|
|
41
|
-
if (mockGwContactChannels == null) return { ok: true, contact: null };
|
|
42
|
-
return {
|
|
43
|
-
ok: true,
|
|
44
|
-
contact: {
|
|
45
|
-
id: "contact-1",
|
|
46
|
-
displayName: "Pat",
|
|
47
|
-
role: "contact",
|
|
48
|
-
interactionCount: 0,
|
|
49
|
-
createdAt: 0,
|
|
50
|
-
updatedAt: 0,
|
|
51
|
-
channels: mockGwContactChannels.map((c) => ({
|
|
52
|
-
id: c.id,
|
|
53
|
-
contactId: "contact-1",
|
|
54
|
-
type: "telegram",
|
|
55
|
-
address: "user-123",
|
|
56
|
-
isPrimary: true,
|
|
57
|
-
externalUserId: null,
|
|
58
|
-
status: c.status,
|
|
59
|
-
policy: "allow",
|
|
60
|
-
verifiedAt: c.verifiedAt,
|
|
61
|
-
verifiedVia: null,
|
|
62
|
-
lastSeenAt: null,
|
|
63
|
-
interactionCount: 0,
|
|
64
|
-
lastInteraction: null,
|
|
65
|
-
revokedReason: null,
|
|
66
|
-
blockedReason: null,
|
|
67
|
-
})),
|
|
68
|
-
},
|
|
69
|
-
};
|
|
70
|
-
}
|
|
71
|
-
return {
|
|
72
|
-
ok: true,
|
|
73
|
-
didWrite: true,
|
|
74
|
-
channel: {
|
|
75
|
-
id: "ch-1",
|
|
76
|
-
contactId: "contact-1",
|
|
77
|
-
type: "telegram",
|
|
78
|
-
address: "user-123",
|
|
79
|
-
status: "revoked",
|
|
80
|
-
revokedReason: "guardian_binding_revoked",
|
|
81
|
-
},
|
|
82
|
-
};
|
|
83
|
-
},
|
|
84
|
-
ipcCall: async () => null,
|
|
85
|
-
}));
|
|
86
|
-
|
|
87
|
-
mock.module("../../../runtime/channel-verification-service.js", () => ({
|
|
88
|
-
getGuardianBinding: () => mockBinding,
|
|
89
|
-
revokeBinding: () => true,
|
|
90
|
-
revokePendingSessions: () => {},
|
|
91
|
-
createOutboundSession: () => ({
|
|
92
|
-
sessionId: "sess",
|
|
93
|
-
secret: "code",
|
|
94
|
-
expiresAt: Date.now() + 1000,
|
|
95
|
-
}),
|
|
96
|
-
countRecentSendsToDestination: () => 0,
|
|
97
|
-
isGuardianBoundForChannel: async () => false,
|
|
98
|
-
updateSessionDelivery: () => {},
|
|
99
|
-
}));
|
|
100
|
-
|
|
101
|
-
mock.module("../../../runtime/verification-outbound-actions.js", () => ({
|
|
102
|
-
cancelOutbound: () => {},
|
|
103
|
-
deliverVerificationEmail: () => {},
|
|
104
|
-
deliverVerificationSlack: () => {},
|
|
105
|
-
deliverVerificationTelegram: () => {},
|
|
106
|
-
DESTINATION_RATE_WINDOW_MS: 1000,
|
|
107
|
-
MAX_SENDS_PER_DESTINATION_WINDOW: 5,
|
|
108
|
-
normalizeTelegramDestination: (d: string) => d,
|
|
109
|
-
resendOutbound: () => ({}),
|
|
110
|
-
startOutbound: async () => ({}),
|
|
111
|
-
}));
|
|
112
|
-
|
|
113
|
-
import {
|
|
114
|
-
revokeVerificationForChannel,
|
|
115
|
-
verifyTrustedContact,
|
|
116
|
-
} from "../config-channels.js";
|
|
117
|
-
|
|
118
|
-
function channel(overrides: Partial<ContactChannel> = {}): ContactChannel {
|
|
119
|
-
return {
|
|
120
|
-
id: "ch-1",
|
|
121
|
-
contactId: "contact-1",
|
|
122
|
-
type: "telegram",
|
|
123
|
-
address: "user-123",
|
|
124
|
-
isPrimary: true,
|
|
125
|
-
externalChatId: "chat-123",
|
|
126
|
-
// DB columns are intentionally a terminal state to prove the gates ignore
|
|
127
|
-
// them and read from the gateway delivery instead.
|
|
128
|
-
status: "revoked",
|
|
129
|
-
policy: {} as ContactChannel["policy"],
|
|
130
|
-
verifiedAt: null,
|
|
131
|
-
verifiedVia: null,
|
|
132
|
-
inviteId: null,
|
|
133
|
-
revokedReason: null,
|
|
134
|
-
blockedReason: null,
|
|
135
|
-
lastSeenAt: null,
|
|
136
|
-
interactionCount: 0,
|
|
137
|
-
lastInteraction: null,
|
|
138
|
-
updatedAt: null,
|
|
139
|
-
createdAt: 0,
|
|
140
|
-
...overrides,
|
|
141
|
-
};
|
|
142
|
-
}
|
|
143
|
-
|
|
144
|
-
function delivery(overrides: Partial<GuardianDelivery> = {}): GuardianDelivery {
|
|
145
|
-
return {
|
|
146
|
-
channelType: "telegram",
|
|
147
|
-
contactId: "contact-1",
|
|
148
|
-
address: "user-123",
|
|
149
|
-
externalChatId: "chat-123",
|
|
150
|
-
status: "active",
|
|
151
|
-
verifiedAt: 1700000000,
|
|
152
|
-
...overrides,
|
|
153
|
-
};
|
|
154
|
-
}
|
|
155
|
-
|
|
156
|
-
describe("revokeVerificationForChannel", () => {
|
|
157
|
-
beforeEach(() => {
|
|
158
|
-
mockGuardians = null;
|
|
159
|
-
mockBinding = { guardianExternalUserId: "user-123", guardianDeliveryChatId: "chat-123" };
|
|
160
|
-
mockContactChannel = { channel: channel() };
|
|
161
|
-
ipcCalls = [];
|
|
162
|
-
});
|
|
163
|
-
|
|
164
|
-
test("relays mark_channel_revoked when the gateway delivery is live", async () => {
|
|
165
|
-
mockGuardians = [delivery({ status: "active" })];
|
|
166
|
-
await revokeVerificationForChannel("telegram");
|
|
167
|
-
expect(ipcCalls.map((c) => c.method)).toContain("mark_channel_revoked");
|
|
168
|
-
});
|
|
169
|
-
|
|
170
|
-
test("skips a redundant revoke when the gateway delivery is already revoked", async () => {
|
|
171
|
-
// Local DB status is the live "active" here, but the gateway (SoT) says
|
|
172
|
-
// revoked — the gate must follow the gateway and not relay.
|
|
173
|
-
mockContactChannel = { channel: channel({ status: "active" }) };
|
|
174
|
-
mockGuardians = [delivery({ status: "revoked" })];
|
|
175
|
-
await revokeVerificationForChannel("telegram");
|
|
176
|
-
expect(ipcCalls.map((c) => c.method)).not.toContain("mark_channel_revoked");
|
|
177
|
-
});
|
|
178
|
-
|
|
179
|
-
test("skips the relay when the gateway has no delivery for the channel", async () => {
|
|
180
|
-
mockGuardians = [];
|
|
181
|
-
await revokeVerificationForChannel("telegram");
|
|
182
|
-
expect(ipcCalls.map((c) => c.method)).not.toContain("mark_channel_revoked");
|
|
183
|
-
});
|
|
184
|
-
|
|
185
|
-
test("skips the relay when the gateway is unreachable", async () => {
|
|
186
|
-
mockGuardians = null;
|
|
187
|
-
await revokeVerificationForChannel("telegram");
|
|
188
|
-
expect(ipcCalls.map((c) => c.method)).not.toContain("mark_channel_revoked");
|
|
189
|
-
});
|
|
190
|
-
});
|
|
191
|
-
|
|
192
|
-
describe("verifyTrustedContact already-verified gate", () => {
|
|
193
|
-
beforeEach(() => {
|
|
194
|
-
mockGuardians = null;
|
|
195
|
-
mockGwContactChannels = null;
|
|
196
|
-
mockChannel = channel();
|
|
197
|
-
});
|
|
198
|
-
|
|
199
|
-
test("short-circuits when the gateway contact channel is active and verified", async () => {
|
|
200
|
-
// Arbitrary trusted contact (non-guardian) — read from the contact-channel
|
|
201
|
-
// gateway read, which covers all contacts.
|
|
202
|
-
mockGwContactChannels = [
|
|
203
|
-
{ id: "ch-1", status: "active", verifiedAt: 1700000000 },
|
|
204
|
-
];
|
|
205
|
-
const result = await verifyTrustedContact("ch-1", "assistant-1");
|
|
206
|
-
expect(result.success).toBe(false);
|
|
207
|
-
expect(result.error).toBe("already_verified");
|
|
208
|
-
});
|
|
209
|
-
|
|
210
|
-
test("does not short-circuit when the gateway channel has no verifiedAt", async () => {
|
|
211
|
-
// DB column says verified, but the gateway channel is unverified — proceed.
|
|
212
|
-
mockChannel = channel({ status: "active", verifiedAt: 1700000000 });
|
|
213
|
-
mockGwContactChannels = [
|
|
214
|
-
{ id: "ch-1", status: "pending", verifiedAt: null },
|
|
215
|
-
];
|
|
216
|
-
const result = await verifyTrustedContact("ch-1", "assistant-1");
|
|
217
|
-
expect(result.error).not.toBe("already_verified");
|
|
218
|
-
});
|
|
219
|
-
|
|
220
|
-
test("does not short-circuit when the gateway has no matching channel", async () => {
|
|
221
|
-
mockGwContactChannels = [];
|
|
222
|
-
const result = await verifyTrustedContact("ch-1", "assistant-1");
|
|
223
|
-
expect(result.error).not.toBe("already_verified");
|
|
224
|
-
});
|
|
225
|
-
});
|