@vellumai/assistant 0.9.1-staging.1 → 0.10.0-staging.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/docs/activation-funnel-telemetry.md +24 -18
- package/node_modules/@vellumai/gateway-client/src/admission-policy-contract.ts +97 -0
- package/node_modules/@vellumai/gateway-client/src/inbound-contract.ts +10 -0
- package/node_modules/@vellumai/gateway-client/src/index.ts +15 -0
- package/openapi.yaml +852 -15
- package/package.json +1 -1
- package/src/__tests__/access-request-card-view.test.ts +98 -0
- package/src/__tests__/access-request-seed-content-blocks.test.ts +2 -4
- package/src/__tests__/actor-trust-resolver-address-fallback.test.ts +59 -7
- package/src/__tests__/agent-loop-compaction-strip.test.ts +17 -16
- package/src/__tests__/agent-loop-mutable-latest-user-message.test.ts +16 -13
- package/src/__tests__/app-compiler.test.ts +15 -1
- package/src/__tests__/auth-fallback-events-store.test.ts +6 -14
- package/src/__tests__/call-pointer-messages.test.ts +28 -0
- package/src/__tests__/cancel-clears-processing.test.ts +89 -0
- package/src/__tests__/channel-approval-routes.test.ts +0 -4
- package/src/__tests__/channel-inbound-disk-pressure.test.ts +5 -15
- package/src/__tests__/cli-memory-v2-reembed-skills.test.ts +3 -4
- package/src/__tests__/compactor-image-manifest-trust.test.ts +21 -1
- package/src/__tests__/compactor-summary-call-truncation.test.ts +223 -0
- package/src/__tests__/config-loader-backfill.test.ts +174 -30
- package/src/__tests__/config-schema.test.ts +35 -0
- package/src/__tests__/confirmation-request-guardian-bridge.test.ts +2 -2
- package/src/__tests__/contact-store-user-file.test.ts +0 -6
- package/src/__tests__/contacts-tools.test.ts +29 -0
- package/src/__tests__/conversation-agent-loop-overflow.test.ts +1 -0
- package/src/__tests__/conversation-agent-loop.test.ts +58 -0
- package/src/__tests__/conversation-attention-telegram.test.ts +0 -1
- package/src/__tests__/conversation-lifecycle.test.ts +7 -9
- package/src/__tests__/conversation-load-history-repair.test.ts +101 -0
- package/src/__tests__/conversation-routes-guardian-reply.test.ts +15 -12
- package/src/__tests__/conversation-surfaces-activation-emit.test.ts +6 -3
- package/src/__tests__/conversation-title-service.test.ts +62 -0
- package/src/__tests__/credential-execution-shell-lockdown.test.ts +18 -11
- package/src/__tests__/credential-prompt-route.test.ts +1 -0
- package/src/__tests__/credential-security-invariants.test.ts +2 -0
- package/src/__tests__/disk-pressure-policy.test.ts +12 -0
- package/src/__tests__/disk-usage.test.ts +65 -0
- package/src/__tests__/dynamic-page-surface.test.ts +51 -0
- package/src/__tests__/gateway-flag-listener.test.ts +110 -1
- package/src/__tests__/guardian-binding-drift-heal.test.ts +1 -1
- package/src/__tests__/guardian-card-withdrawal.test.ts +403 -0
- package/src/__tests__/guardian-decision-primitive-canonical.test.ts +5 -3
- package/src/__tests__/guardian-grant-minting.test.ts +3 -35
- package/src/__tests__/guardian-routing-invariants.test.ts +64 -26
- package/src/__tests__/guardian-routing-state.test.ts +0 -1
- package/src/__tests__/headless-browser-mode.test.ts +10 -0
- package/src/__tests__/headless-browser-navigate.test.ts +8 -3
- package/src/__tests__/helpers/create-guardian-binding.ts +0 -1
- package/src/__tests__/host-browser-proxy.test.ts +87 -0
- package/src/__tests__/injector-v3-suppression.test.ts +27 -20
- package/src/__tests__/internal-telemetry-routes.test.ts +6 -14
- package/src/__tests__/invite-redemption-service.test.ts +0 -3
- package/src/__tests__/llm-catalog-parity.test.ts +30 -1
- package/src/__tests__/llm-resolver.test.ts +21 -0
- package/src/__tests__/llm-schema.test.ts +1 -0
- package/src/__tests__/managed-profile-guard.test.ts +163 -4
- package/src/__tests__/mcp-health-check.test.ts +6 -7
- package/src/__tests__/media-stream-server-integration.test.ts +317 -13
- package/src/__tests__/path-policy.test.ts +34 -0
- package/src/__tests__/persona-resolver.test.ts +38 -0
- package/src/__tests__/plugin-api-provider.test.ts +24 -0
- package/src/__tests__/plugin-tool-contribution.test.ts +6 -3
- package/src/__tests__/post-compaction-reinjection-idempotency.test.ts +214 -0
- package/src/__tests__/provider-send-message-override-profile.test.ts +76 -0
- package/src/__tests__/reaction-persistence.test.ts +150 -29
- package/src/__tests__/relay-server.test.ts +285 -0
- package/src/__tests__/runtime-attachment-metadata.test.ts +0 -1
- package/src/__tests__/scheduler-reuse-conversation.test.ts +8 -5
- package/src/__tests__/skill-execute-input.test.ts +5 -0
- package/src/__tests__/skills.test.ts +51 -0
- package/src/__tests__/slack-notification-approval-card.test.ts +176 -0
- package/src/__tests__/slack-reaction-canonical-approval.test.ts +285 -0
- package/src/__tests__/subagent-tools.test.ts +150 -0
- package/src/__tests__/task-progress-nudge-hook.test.ts +1 -1
- package/src/__tests__/title-generate-hook.test.ts +100 -3
- package/src/__tests__/tool-approval-seed-content-blocks.test.ts +1 -1
- package/src/__tests__/tool-audit-listener.test.ts +7 -7
- package/src/__tests__/tool-executor-lifecycle-events.test.ts +6 -3
- package/src/__tests__/trusted-contact-approval-notifier.test.ts +4 -2
- package/src/__tests__/trusted-contact-inline-approval-integration.test.ts +220 -3
- package/src/__tests__/trusted-contact-verification.test.ts +2 -4
- package/src/__tests__/twilio-routes.test.ts +81 -1
- package/src/__tests__/voice-invite-redemption.test.ts +0 -1
- package/src/__tests__/weak-open-model.test.ts +30 -0
- package/src/__tests__/workspace-migration-105-enable-memory-v3-live-for-new-workspaces.test.ts +149 -0
- package/src/__tests__/workspace-migration-108-drop-balanced-economy-profile.test.ts +285 -0
- package/src/__tests__/workspace-migration-add-send-diagnostics.test.ts +1 -1
- package/src/__tests__/workspace-migration-drop-collect-usage-data.test.ts +118 -0
- package/src/__tests__/workspace-migration-drop-send-diagnostics.test.ts +118 -0
- package/src/a2a/__tests__/e2e-a2a-channel.test.ts +0 -4
- package/src/agent/loop.ts +33 -33
- package/src/api/events/tool-result.ts +6 -0
- package/src/api/events/workflow-completed.ts +53 -0
- package/src/api/events/workflow-leaf-finished.ts +38 -0
- package/src/api/events/workflow-leaf-started.ts +35 -0
- package/src/api/events/workflow-progress.ts +32 -0
- package/src/api/events/workflow-started.ts +31 -0
- package/src/api/index.ts +40 -0
- package/src/api/responses/conversation-message.ts +26 -0
- package/src/api/responses/home.ts +26 -0
- package/src/api/responses/workflow-journal.ts +53 -0
- package/src/approvals/guardian-card-withdrawal.ts +145 -0
- package/src/approvals/guardian-decision-primitive.ts +26 -3
- package/src/approvals/guardian-request-resolvers.ts +181 -78
- package/src/calls/__tests__/channel-admission-reader.test.ts +132 -0
- package/src/calls/__tests__/relay-setup-router.test.ts +350 -0
- package/src/calls/call-pointer-messages.ts +10 -4
- package/src/calls/channel-admission-reader.ts +104 -0
- package/src/calls/guardian-dispatch.ts +17 -45
- package/src/calls/media-stream-server.ts +84 -2
- package/src/calls/relay-server.ts +66 -0
- package/src/calls/relay-setup-router.ts +82 -1
- package/src/calls/twilio-routes.ts +17 -8
- package/src/cli/commands/clients.ts +3 -0
- package/src/cli/commands/{__tests__ → memory/__tests__}/memory-v2-compare-render.test.ts +1 -1
- package/src/cli/commands/{__tests__ → memory/__tests__}/memory-v2.test.ts +8 -7
- package/src/cli/commands/{__tests__ → memory/__tests__}/memory-v3.test.ts +5 -4
- package/src/cli/commands/memory/index.ts +30 -0
- package/src/cli/commands/{memory-v2-compare-render.ts → memory/memory-v2-compare-render.ts} +1 -1
- package/src/cli/commands/{memory-v2.ts → memory/memory-v2.ts} +6 -15
- package/src/cli/commands/{memory-v3.ts → memory/memory-v3.ts} +97 -11
- package/src/cli/commands/oauth/status.test.ts +36 -0
- package/src/cli/commands/oauth/status.ts +23 -3
- package/src/cli/commands/plugins.ts +57 -5
- package/src/cli/lib/__tests__/inspect-plugin.test.ts +54 -0
- package/src/cli/lib/__tests__/merge-plugin-tree.test.ts +134 -4
- package/src/cli/lib/__tests__/plugin-surfaces.test.ts +111 -0
- package/src/cli/lib/__tests__/upgrade-plugin.test.ts +53 -11
- package/src/cli/lib/inspect-plugin.ts +12 -1
- package/src/cli/lib/merge-plugin-tree.ts +149 -49
- package/src/cli/lib/plugin-surfaces.ts +104 -0
- package/src/cli/lib/upgrade-plugin.ts +64 -36
- package/src/cli/program.ts +2 -4
- package/src/config/__tests__/sync-gated-profiles.test.ts +368 -0
- package/src/config/assistant-feature-flags.ts +22 -7
- package/src/config/bundled-skills/contacts/tools/contact-search.ts +0 -1
- package/src/config/bundled-skills/messaging/SKILL.md +6 -4
- package/src/config/bundled-skills/messaging/tools/messaging-archive-by-sender.ts +9 -8
- package/src/config/bundled-skills/workflows/SKILL.md +14 -7
- package/src/config/call-site-defaults.ts +3 -0
- package/src/config/feature-flag-registry.json +49 -18
- package/src/config/llm-resolver.ts +3 -0
- package/src/config/memory-v3-gate.ts +11 -0
- package/src/config/schema.ts +8 -6
- package/src/config/schemas/__tests__/memory-v3.test.ts +1 -0
- package/src/config/schemas/call-site-catalog.ts +7 -0
- package/src/config/schemas/channels.ts +11 -0
- package/src/config/schemas/llm.ts +31 -0
- package/src/config/schemas/memory-lifecycle.ts +3 -7
- package/src/config/schemas/memory-v3.ts +6 -0
- package/src/config/schemas/services.ts +18 -0
- package/src/config/seed-inference-profiles.ts +94 -34
- package/src/config/skills.ts +21 -0
- package/src/config/sync-gated-profiles.ts +220 -0
- package/src/contacts/contact-store.ts +2 -10
- package/src/contacts/contacts-write.ts +1 -2
- package/src/contacts/types.ts +0 -1
- package/src/context/compactor.ts +86 -52
- package/src/context/strip-injections.ts +58 -10
- package/src/context/token-estimator.ts +1 -1
- package/src/daemon/__tests__/conversation-lifecycle-auto-analyze.test.ts +3 -2
- package/src/daemon/conversation-agent-loop-handlers.ts +2 -0
- package/src/daemon/conversation-agent-loop.ts +100 -19
- package/src/daemon/conversation-history.ts +1 -1
- package/src/daemon/conversation-lifecycle.ts +3 -5
- package/src/daemon/conversation-process.ts +13 -5
- package/src/daemon/conversation-runtime-assembly.ts +13 -15
- package/src/daemon/conversation-surfaces.ts +26 -0
- package/src/daemon/conversation-tool-setup.ts +16 -11
- package/src/daemon/conversation.ts +64 -14
- package/src/daemon/disk-pressure-policy.ts +5 -3
- package/src/daemon/handlers/__tests__/config-a2a-complete.test.ts +0 -1
- package/src/daemon/handlers/__tests__/config-a2a-redeem.test.ts +0 -1
- package/src/daemon/handlers/config-a2a.ts +0 -2
- package/src/daemon/handlers/config-channels.ts +5 -10
- package/src/daemon/handlers/config-slack-channel.ts +20 -0
- package/src/daemon/handlers/conversations.ts +107 -0
- package/src/daemon/host-browser-proxy.ts +41 -0
- package/src/daemon/lifecycle.ts +55 -20
- package/src/daemon/message-provenance.ts +2 -0
- package/src/daemon/message-types/contacts.ts +0 -1
- package/src/daemon/message-types/web-activity.ts +7 -1
- package/src/daemon/message-types/workflows.ts +83 -1
- package/src/daemon/tool-setup-types.ts +4 -0
- package/src/daemon/trust-context.ts +1 -1
- package/src/events/tool-audit-listener.ts +2 -2
- package/src/home/feed-source-enrichment.test.ts +151 -0
- package/src/home/feed-source-enrichment.ts +176 -0
- package/src/instrument.ts +18 -6
- package/src/ipc/__tests__/binary-result-ipc.test.ts +81 -0
- package/src/ipc/__tests__/clients-list-ipc.test.ts +20 -0
- package/src/ipc/assistant-server.ts +37 -4
- package/src/ipc/gateway-flag-listener.ts +18 -2
- package/src/memory/__tests__/auto-analysis-enqueue.test.ts +5 -16
- package/src/memory/__tests__/jobs-store-enqueue-gate.test.ts +7 -11
- package/src/memory/__tests__/memory-retrospective-enqueue.test.ts +37 -7
- package/src/memory/__tests__/memory-retrospective-job.test.ts +34 -0
- package/src/memory/__tests__/onboarding-events-store.test.ts +7 -7
- package/src/memory/auth-fallback-events-store.ts +2 -2
- package/src/memory/auto-analysis-enqueue.ts +3 -5
- package/src/memory/canonical-guardian-store.ts +39 -1
- package/src/memory/conversation-crud.ts +9 -4
- package/src/memory/conversation-key-store.ts +17 -2
- package/src/memory/conversation-title-service.ts +64 -7
- package/src/memory/db-init.ts +10 -0
- package/src/memory/embedding-backend.ts +15 -1
- package/src/memory/jobs-worker.ts +2 -1
- package/src/memory/lifecycle-events-store.ts +2 -2
- package/src/memory/memory-retrospective-enqueue.ts +31 -6
- package/src/memory/memory-retrospective-job.ts +9 -0
- package/src/memory/migrations/129-contact-channels-access-fields.ts +18 -9
- package/src/memory/migrations/131-drop-legacy-member-guardian-tables.ts +14 -2
- package/src/memory/migrations/289-contact-channels-unique-ext-user.ts +10 -0
- package/src/memory/migrations/291-contact-channels-renormalize-addresses.ts +10 -0
- package/src/memory/migrations/292-schedule-default-no-reuse-conversation.test.ts +67 -0
- package/src/memory/migrations/292-schedule-default-no-reuse-conversation.ts +25 -0
- package/src/memory/migrations/293-workflow-journal-leaf-tokens.ts +32 -0
- package/src/memory/migrations/294-drop-external-user-id.ts +31 -0
- package/src/memory/migrations/295-drop-approval-prompt-ts-tracker.ts +20 -0
- package/src/memory/migrations/296-rewrite-balanced-economy-profile-pins.test.ts +110 -0
- package/src/memory/migrations/296-rewrite-balanced-economy-profile-pins.ts +68 -0
- package/src/memory/migrations/__tests__/131-drop-legacy-member-guardian-tables.test.ts +154 -0
- package/src/memory/migrations/__tests__/289-contact-channels-unique-ext-user.test.ts +31 -0
- package/src/memory/migrations/__tests__/291-contact-channels-renormalize-addresses.test.ts +30 -0
- package/src/memory/migrations/index.ts +5 -0
- package/src/memory/onboarding-events-store.ts +3 -3
- package/src/memory/schema/contacts.ts +0 -1
- package/src/memory/skill-loaded-events-store.test.ts +7 -15
- package/src/memory/skill-loaded-events-store.ts +2 -2
- package/src/memory/tool-executed-events-store.test.ts +7 -7
- package/src/memory/turn-trace-store.test.ts +736 -0
- package/src/memory/turn-trace-store.ts +364 -0
- package/src/memory/v2/__tests__/consolidation-job.test.ts +8 -0
- package/src/memory/v2/__tests__/skill-content.test.ts +30 -0
- package/src/memory/v2/consolidation-job.ts +2 -2
- package/src/memory/v2/skill-content.ts +25 -7
- package/src/memory/v2/skill-store.ts +7 -1
- package/src/memory/v3-eval/__tests__/eval-packets.test.ts +248 -0
- package/src/memory/v3-eval/eval-packets.ts +546 -0
- package/src/messaging/providers/slack/api.ts +31 -0
- package/src/messaging/providers/slack/send.test.ts +114 -2
- package/src/messaging/providers/slack/send.ts +30 -7
- package/src/messaging/providers/slack/withdraw.test.ts +200 -0
- package/src/messaging/providers/slack/withdraw.ts +161 -0
- package/src/notifications/AGENTS.md +2 -0
- package/src/notifications/access-request-copy.ts +72 -59
- package/src/notifications/adapters/slack.ts +55 -73
- package/src/notifications/approval-card-data.ts +333 -0
- package/src/notifications/broadcaster.ts +6 -2
- package/src/notifications/canonical-delivery-recorder.ts +139 -0
- package/src/notifications/copy-composer.ts +3 -3
- package/src/notifications/decision-engine.ts +4 -2
- package/src/notifications/destination-resolver.ts +4 -6
- package/src/notifications/guardian-question-mode.ts +10 -0
- package/src/notifications/home-feed-side-effect.ts +3 -13
- package/src/notifications/notification-utils.ts +2 -1
- package/src/notifications/signal.ts +79 -43
- package/src/notifications/types.ts +98 -128
- package/src/permissions/checker.test.ts +51 -0
- package/src/permissions/checker.ts +185 -26
- package/src/permissions/ipc-risk-types.ts +24 -0
- package/src/permissions/question-prompter.test.ts +27 -0
- package/src/permissions/question-prompter.ts +4 -0
- package/src/platform/client.test.ts +119 -0
- package/src/platform/client.ts +66 -0
- package/src/platform/consent-cache.test.ts +267 -0
- package/src/platform/consent-cache.ts +174 -0
- package/src/plugin-api/index.ts +27 -0
- package/src/plugins/defaults/advisor/__tests__/advisor-gate.test.ts +56 -0
- package/src/plugins/defaults/advisor/__tests__/advisor-state-store.test.ts +43 -0
- package/src/plugins/defaults/advisor/__tests__/agent-loop-integration.test.ts +137 -0
- package/src/plugins/defaults/advisor/__tests__/consult.test.ts +153 -0
- package/src/plugins/defaults/advisor/__tests__/hooks.test.ts +138 -0
- package/src/plugins/defaults/advisor/__tests__/transcript.test.ts +147 -0
- package/src/plugins/defaults/advisor/advisor-gate.ts +29 -0
- package/src/plugins/defaults/advisor/advisor-state-store.ts +94 -0
- package/src/plugins/defaults/advisor/config.ts +21 -0
- package/src/plugins/defaults/advisor/consult.ts +93 -0
- package/src/plugins/defaults/advisor/hooks/post-model-call.ts +34 -0
- package/src/plugins/defaults/advisor/hooks/pre-model-call.ts +30 -0
- package/src/plugins/defaults/advisor/hooks/user-prompt-submit.ts +19 -0
- package/src/plugins/defaults/advisor/package.json +14 -0
- package/src/plugins/defaults/advisor/steering.ts +67 -0
- package/src/plugins/defaults/advisor/tools/advisor.ts +65 -0
- package/src/plugins/defaults/advisor/transcript.ts +76 -0
- package/src/plugins/defaults/index.ts +35 -0
- package/src/plugins/defaults/memory-retrieval/hooks/post-compact.ts +22 -9
- package/src/plugins/defaults/memory-retrieval/hooks/user-prompt-submit.ts +2 -2
- package/src/plugins/defaults/memory-retrieval/tail-reinjection-strip.ts +64 -0
- package/src/plugins/defaults/memory-retrieval/unified-turn-context.ts +29 -21
- package/src/plugins/defaults/memory-v3-shadow/__tests__/carry-integration.test.ts +1 -0
- package/src/plugins/defaults/memory-v3-shadow/__tests__/injection.test.ts +1 -0
- package/src/plugins/defaults/memory-v3-shadow/__tests__/maintain-job.test.ts +75 -7
- package/src/plugins/defaults/memory-v3-shadow/__tests__/orchestrate.test.ts +31 -4
- package/src/plugins/defaults/memory-v3-shadow/__tests__/selection-log-store.test.ts +77 -2
- package/src/plugins/defaults/memory-v3-shadow/__tests__/shadow-plugin.test.ts +1 -0
- package/src/plugins/defaults/memory-v3-shadow/injector.ts +7 -10
- package/src/plugins/defaults/memory-v3-shadow/maintain-job.ts +37 -4
- package/src/plugins/defaults/memory-v3-shadow/orchestrate.ts +32 -20
- package/src/plugins/defaults/memory-v3-shadow/selection-log-store.ts +56 -3
- package/src/plugins/defaults/memory-v3-shadow/shadow-plugin.ts +23 -2
- package/src/plugins/defaults/task-progress-nudge/hooks/post-tool-use.ts +2 -12
- package/src/plugins/defaults/title-generate/hooks/stop.ts +56 -21
- package/src/prompts/persona-resolver.ts +12 -2
- package/src/prompts/templates/system-sections.ts +7 -2
- package/src/providers/__tests__/provider-env-vars.test.ts +6 -0
- package/src/providers/__tests__/provider-secret-catalog.test.ts +1 -0
- package/src/providers/__tests__/retry-callsite.test.ts +176 -0
- package/src/providers/atlascloud/client.ts +85 -0
- package/src/providers/fetch-provider-catalog.ts +85 -0
- package/src/providers/inference/adapter-factory.ts +3 -0
- package/src/providers/model-catalog.ts +58 -0
- package/src/providers/openai/__tests__/chat-completions-provider-reasoning.test.ts +33 -0
- package/src/providers/openai/chat-completions-provider.ts +7 -0
- package/src/providers/openai/responses-provider.ts +10 -0
- package/src/providers/provider-send-message.ts +11 -3
- package/src/providers/retry.ts +53 -12
- package/src/providers/search-provider-catalog.ts +10 -0
- package/src/providers/weak-open-model.ts +22 -0
- package/src/runtime/__tests__/agent-wake.test.ts +181 -0
- package/src/runtime/__tests__/client-health.test.ts +44 -0
- package/src/runtime/access-request-helper.ts +21 -53
- package/src/runtime/actor-trust-resolver.ts +49 -21
- package/src/runtime/agent-wake.ts +52 -0
- package/src/runtime/assistant-event-hub.ts +18 -4
- package/src/runtime/auth/__tests__/route-policy.test.ts +12 -0
- package/src/runtime/auth/require-bound-guardian.ts +1 -4
- package/src/runtime/capabilities.test.ts +120 -0
- package/src/runtime/capabilities.ts +197 -0
- package/src/runtime/channel-approval-types.ts +5 -1
- package/src/runtime/channel-retry-sweep.ts +1 -0
- package/src/runtime/channel-verification-service.ts +1 -2
- package/src/runtime/client-health.ts +26 -0
- package/src/runtime/confirmation-request-guardian-bridge.ts +38 -29
- package/src/runtime/effective-capabilities.test.ts +128 -0
- package/src/runtime/effective-capabilities.ts +84 -0
- package/src/runtime/guardian-reply-router.ts +106 -21
- package/src/runtime/invite-redemption-service.ts +6 -22
- package/src/runtime/migrations/__tests__/vbundle-builder-fd-leak.test.ts +123 -0
- package/src/runtime/migrations/vbundle-builder.ts +49 -20
- package/src/runtime/pending-interactions.ts +15 -0
- package/src/runtime/routes/__tests__/client-routes.test.ts +13 -0
- package/src/runtime/routes/__tests__/conversation-management-routes.test.ts +67 -0
- package/src/runtime/routes/__tests__/plugins-routes.test.ts +35 -13
- package/src/runtime/routes/browser-tabs-routes.ts +9 -0
- package/src/runtime/routes/canonical-guardian-expiry-sweep.ts +17 -8
- package/src/runtime/routes/client-routes.ts +10 -0
- package/src/runtime/routes/contact-routes.ts +31 -8
- package/src/runtime/routes/conversation-management-routes.ts +80 -1
- package/src/runtime/routes/conversation-query-routes.ts +68 -22
- package/src/runtime/routes/conversation-routes.ts +37 -12
- package/src/runtime/routes/events-routes.ts +1 -3
- package/src/runtime/routes/guardian-approval-interception.ts +14 -73
- package/src/runtime/routes/guardian-approval-prompt.ts +22 -4
- package/src/runtime/routes/home-feed-routes.ts +8 -3
- package/src/runtime/routes/inbound-message-handler.ts +214 -228
- package/src/runtime/routes/inbound-stages/acl-enforcement.ts +88 -6
- package/src/runtime/routes/inbound-stages/admission-policy.test.ts +154 -0
- package/src/runtime/routes/inbound-stages/admission-policy.ts +140 -0
- package/src/runtime/routes/inbound-stages/background-dispatch.test.ts +3 -3
- package/src/runtime/routes/inbound-stages/background-dispatch.ts +11 -6
- package/src/runtime/routes/inbound-stages/escalation-intercept.ts +1 -2
- package/src/runtime/routes/inbound-stages/guardian-activation-intercept.ts +1 -2
- package/src/runtime/routes/inbound-stages/guardian-reply-intercept.test.ts +7 -7
- package/src/runtime/routes/inbound-stages/guardian-reply-intercept.ts +47 -28
- package/src/runtime/routes/inbound-stages/reaction-intercept.ts +358 -0
- package/src/runtime/routes/index.ts +2 -0
- package/src/runtime/routes/integrations/slack/__tests__/channel.test.ts +8 -0
- package/src/runtime/routes/integrations/slack/channel.ts +36 -0
- package/src/runtime/routes/internal-telemetry-routes.ts +1 -1
- package/src/runtime/routes/mcp-auth-routes.ts +233 -41
- package/src/runtime/routes/memory-eval-routes.ts +87 -0
- package/src/runtime/routes/notification-routes.ts +122 -133
- package/src/runtime/routes/platform-routes.ts +2 -2
- package/src/runtime/routes/plugins-routes.ts +40 -7
- package/src/runtime/routes/secret-routes.ts +10 -0
- package/src/runtime/routes/surface-action-routes.ts +2 -1
- package/src/runtime/routes/tool-call-question-enrichment.test.ts +146 -0
- package/src/runtime/routes/tool-call-question-enrichment.ts +66 -0
- package/src/runtime/routes/workflow-routes.test.ts +225 -1
- package/src/runtime/routes/workflow-routes.ts +131 -1
- package/src/runtime/tool-grant-request-helper.ts +18 -16
- package/src/runtime/trust-context-resolver.ts +8 -5
- package/src/schedule/schedule-store.ts +1 -1
- package/src/schedule/scheduler-types.ts +5 -1
- package/src/security/__tests__/provider-key-env-fallback.test.ts +6 -0
- package/src/security/secret-patterns.ts +3 -0
- package/src/subagent/manager.ts +11 -4
- package/src/telemetry/trace-collection-policy.test.ts +28 -0
- package/src/telemetry/trace-collection-policy.ts +30 -0
- package/src/telemetry/types.ts +89 -0
- package/src/telemetry/usage-telemetry-reporter.test.ts +586 -36
- package/src/telemetry/usage-telemetry-reporter.ts +148 -41
- package/src/tools/browser/__tests__/browser-execution-acquire.test.ts +31 -0
- package/src/tools/browser/browser-execution.ts +29 -18
- package/src/tools/document/document-tool.ts +2 -3
- package/src/tools/executor.ts +5 -3
- package/src/tools/host-terminal/host-shell.ts +5 -4
- package/src/tools/memory/register.ts +2 -2
- package/src/tools/network/__tests__/web-fetch-firecrawl.test.ts +360 -0
- package/src/tools/network/__tests__/web-search.test.ts +143 -0
- package/src/tools/network/web-fetch.ts +372 -1
- package/src/tools/network/web-search.ts +213 -10
- package/src/tools/permission-checker.ts +3 -2
- package/src/tools/registry.ts +20 -0
- package/src/tools/schedule/create.ts +4 -3
- package/src/tools/schedule/update.ts +2 -1
- package/src/tools/shared/filesystem/path-policy.ts +39 -13
- package/src/tools/skills/execute.ts +1 -2
- package/src/tools/subagent/spawn.ts +37 -13
- package/src/tools/terminal/shell.ts +10 -4
- package/src/tools/tool-approval-handler.ts +17 -10
- package/src/tools/types.ts +9 -0
- package/src/tools/ui-surface/definitions.ts +25 -2
- package/src/tools/verification-control-plane-policy.ts +3 -1
- package/src/tools/workflows/run-workflow.ts +1 -0
- package/src/util/disk-usage.ts +78 -23
- package/src/util/platform.ts +8 -1
- package/src/watcher/telemetry.ts +2 -2
- package/src/workflows/engine.test.ts +175 -1
- package/src/workflows/engine.ts +82 -0
- package/src/workflows/journal-store.test.ts +70 -0
- package/src/workflows/journal-store.ts +18 -3
- package/src/workflows/run-manager.test.ts +171 -3
- package/src/workflows/run-manager.ts +64 -0
- package/src/workspace/migrations/105-enable-memory-v3-live-for-new-workspaces.ts +63 -0
- package/src/workspace/migrations/106-drop-collect-usage-data.ts +47 -0
- package/src/workspace/migrations/107-drop-send-diagnostics.ts +47 -0
- package/src/workspace/migrations/108-drop-balanced-economy-profile.ts +129 -0
- package/src/workspace/migrations/registry.ts +8 -0
- package/src/notifications/tool-approval-copy.ts +0 -142
- package/src/runtime/routes/approval-prompt-ts-tracker.ts +0 -78
|
@@ -0,0 +1,350 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Tests for the inbound admission-floor enforcement in `routeSetup`.
|
|
3
|
+
*
|
|
4
|
+
* `routeSetup` is pure routing logic but reads several module-level singletons
|
|
5
|
+
* (config, trust resolver, pending verification session, invite store). These
|
|
6
|
+
* are mocked so the table below can drive trust class, member status, pending
|
|
7
|
+
* challenge, and active invites independently of any DB.
|
|
8
|
+
*
|
|
9
|
+
* The floor verdict is exercised through the REAL `enforceAdmissionPolicy`
|
|
10
|
+
* floor tables (`TRUST_CLASS_RANK` × `ADMISSION_FLOOR`), with the exempt-channel
|
|
11
|
+
* short-circuit bypassed in the mock so the tests assert the true floor
|
|
12
|
+
* semantics independent of any channel's exempt status (`phone` is now
|
|
13
|
+
* enforced).
|
|
14
|
+
*/
|
|
15
|
+
|
|
16
|
+
import { beforeEach, describe, expect, mock, test } from "bun:test";
|
|
17
|
+
|
|
18
|
+
import {
|
|
19
|
+
ADMISSION_FLOOR,
|
|
20
|
+
type AdmissionPolicy,
|
|
21
|
+
} from "@vellumai/gateway-client";
|
|
22
|
+
|
|
23
|
+
import type {
|
|
24
|
+
ChannelPolicy,
|
|
25
|
+
ChannelStatus,
|
|
26
|
+
} from "../../contacts/types.js";
|
|
27
|
+
import type {
|
|
28
|
+
ActorTrustContext,
|
|
29
|
+
TrustClass,
|
|
30
|
+
} from "../../runtime/actor-trust-resolver.js";
|
|
31
|
+
import { TRUST_CLASS_RANK } from "../../runtime/actor-trust-resolver.js";
|
|
32
|
+
|
|
33
|
+
mock.module("../../util/logger.js", () => ({
|
|
34
|
+
getLogger: () =>
|
|
35
|
+
new Proxy({} as Record<string, unknown>, { get: () => () => {} }),
|
|
36
|
+
}));
|
|
37
|
+
|
|
38
|
+
mock.module("../../config/loader.js", () => ({
|
|
39
|
+
getConfig: () => ({ calls: { verification: { enabled: false } } }),
|
|
40
|
+
}));
|
|
41
|
+
|
|
42
|
+
// Controllable resolved trust context.
|
|
43
|
+
let nextTrust: ActorTrustContext;
|
|
44
|
+
mock.module("../../runtime/actor-trust-resolver.js", () => ({
|
|
45
|
+
resolveActorTrust: () => nextTrust,
|
|
46
|
+
// Re-export the real rank table; the floor mock below consumes it.
|
|
47
|
+
TRUST_CLASS_RANK,
|
|
48
|
+
}));
|
|
49
|
+
|
|
50
|
+
// Controllable pending verification challenge.
|
|
51
|
+
let pendingChallenge: unknown = null;
|
|
52
|
+
mock.module("../../runtime/channel-verification-service.js", () => ({
|
|
53
|
+
getPendingSession: () => pendingChallenge,
|
|
54
|
+
}));
|
|
55
|
+
|
|
56
|
+
// Controllable active voice invites.
|
|
57
|
+
let activeInvites: Array<{
|
|
58
|
+
friendName: string | null;
|
|
59
|
+
guardianName: string | null;
|
|
60
|
+
expiresAt?: number;
|
|
61
|
+
}> = [];
|
|
62
|
+
mock.module("../../memory/invite-store.js", () => ({
|
|
63
|
+
findActiveVoiceInvites: () => activeInvites,
|
|
64
|
+
}));
|
|
65
|
+
|
|
66
|
+
// Real floor semantics, exemption bypassed (see file header).
|
|
67
|
+
mock.module(
|
|
68
|
+
"../../runtime/routes/inbound-stages/admission-policy.js",
|
|
69
|
+
() => ({
|
|
70
|
+
enforceAdmissionPolicy: (input: {
|
|
71
|
+
trustClass: TrustClass;
|
|
72
|
+
memberStatus: ChannelStatus | undefined;
|
|
73
|
+
policy: AdmissionPolicy;
|
|
74
|
+
}) => {
|
|
75
|
+
if (input.memberStatus === "blocked" || input.memberStatus === "revoked") {
|
|
76
|
+
return {
|
|
77
|
+
admitted: false,
|
|
78
|
+
reason:
|
|
79
|
+
input.memberStatus === "blocked"
|
|
80
|
+
? "member_blocked"
|
|
81
|
+
: "member_revoked",
|
|
82
|
+
shouldChallenge: false,
|
|
83
|
+
effectivePolicy: input.policy,
|
|
84
|
+
};
|
|
85
|
+
}
|
|
86
|
+
const rank = TRUST_CLASS_RANK[input.trustClass];
|
|
87
|
+
const floor = ADMISSION_FLOOR[input.policy];
|
|
88
|
+
if (rank >= floor) return { admitted: true };
|
|
89
|
+
return {
|
|
90
|
+
admitted: false,
|
|
91
|
+
reason: `admission_policy_${input.policy}`,
|
|
92
|
+
shouldChallenge: false,
|
|
93
|
+
effectivePolicy: input.policy,
|
|
94
|
+
};
|
|
95
|
+
},
|
|
96
|
+
}),
|
|
97
|
+
);
|
|
98
|
+
|
|
99
|
+
const { routeSetup } = await import("../relay-setup-router.js");
|
|
100
|
+
|
|
101
|
+
// ---------------------------------------------------------------------------
|
|
102
|
+
// Fixtures
|
|
103
|
+
// ---------------------------------------------------------------------------
|
|
104
|
+
|
|
105
|
+
function makeTrust(
|
|
106
|
+
trustClass: TrustClass,
|
|
107
|
+
channel?: { status: ChannelStatus; policy?: ChannelPolicy; role?: string },
|
|
108
|
+
): ActorTrustContext {
|
|
109
|
+
const memberRecord = channel
|
|
110
|
+
? {
|
|
111
|
+
contact: {
|
|
112
|
+
displayName: "Test Caller",
|
|
113
|
+
role: channel.role ?? "trusted_contact",
|
|
114
|
+
} as never,
|
|
115
|
+
channel: {
|
|
116
|
+
id: "ch_1",
|
|
117
|
+
status: channel.status,
|
|
118
|
+
policy: channel.policy ?? "allow",
|
|
119
|
+
} as never,
|
|
120
|
+
}
|
|
121
|
+
: null;
|
|
122
|
+
return {
|
|
123
|
+
canonicalSenderId: "+15551234567",
|
|
124
|
+
guardianBindingMatch: null,
|
|
125
|
+
memberRecord,
|
|
126
|
+
trustClass,
|
|
127
|
+
actorMetadata: {
|
|
128
|
+
identifier: "+15551234567",
|
|
129
|
+
displayName: undefined,
|
|
130
|
+
senderDisplayName: undefined,
|
|
131
|
+
memberDisplayName: undefined,
|
|
132
|
+
username: undefined,
|
|
133
|
+
channel: "phone",
|
|
134
|
+
trustStatus: trustClass,
|
|
135
|
+
},
|
|
136
|
+
} as ActorTrustContext;
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
function route(admissionPolicy?: AdmissionPolicy | null) {
|
|
140
|
+
return routeSetup({
|
|
141
|
+
callSessionId: "cs_1",
|
|
142
|
+
session: null, // inbound
|
|
143
|
+
from: "+15551234567",
|
|
144
|
+
to: "+15559999999",
|
|
145
|
+
admissionPolicy,
|
|
146
|
+
});
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
beforeEach(() => {
|
|
150
|
+
pendingChallenge = null;
|
|
151
|
+
activeInvites = [];
|
|
152
|
+
});
|
|
153
|
+
|
|
154
|
+
// ---------------------------------------------------------------------------
|
|
155
|
+
// Floor table: trustClass × policy → admit/deny
|
|
156
|
+
// ---------------------------------------------------------------------------
|
|
157
|
+
|
|
158
|
+
describe("routeSetup — admission floor table", () => {
|
|
159
|
+
// For "known" classes (trusted_contact, guardian) the resolver yields a
|
|
160
|
+
// member channel; unknown/unverified flow through the unknown ACL branch.
|
|
161
|
+
function setTrust(trustClass: TrustClass) {
|
|
162
|
+
if (trustClass === "guardian") {
|
|
163
|
+
nextTrust = makeTrust("guardian", {
|
|
164
|
+
status: "active",
|
|
165
|
+
role: "guardian",
|
|
166
|
+
});
|
|
167
|
+
} else if (trustClass === "trusted_contact") {
|
|
168
|
+
nextTrust = makeTrust("trusted_contact", { status: "active" });
|
|
169
|
+
} else if (trustClass === "unverified_contact") {
|
|
170
|
+
nextTrust = makeTrust("unverified_contact", { status: "unverified" });
|
|
171
|
+
} else {
|
|
172
|
+
nextTrust = makeTrust("unknown");
|
|
173
|
+
}
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
const cases: Array<{
|
|
177
|
+
policy: AdmissionPolicy;
|
|
178
|
+
admits: TrustClass[];
|
|
179
|
+
denies: TrustClass[];
|
|
180
|
+
}> = [
|
|
181
|
+
{
|
|
182
|
+
policy: "strangers",
|
|
183
|
+
admits: ["unknown", "unverified_contact", "trusted_contact", "guardian"],
|
|
184
|
+
denies: [],
|
|
185
|
+
},
|
|
186
|
+
{
|
|
187
|
+
policy: "any_contact",
|
|
188
|
+
admits: ["unverified_contact", "trusted_contact", "guardian"],
|
|
189
|
+
denies: ["unknown"],
|
|
190
|
+
},
|
|
191
|
+
{
|
|
192
|
+
policy: "trusted_contacts",
|
|
193
|
+
admits: ["trusted_contact", "guardian"],
|
|
194
|
+
denies: ["unknown", "unverified_contact"],
|
|
195
|
+
},
|
|
196
|
+
{
|
|
197
|
+
policy: "guardian_only",
|
|
198
|
+
admits: ["guardian"],
|
|
199
|
+
denies: ["unknown", "unverified_contact", "trusted_contact"],
|
|
200
|
+
},
|
|
201
|
+
{
|
|
202
|
+
policy: "no_one",
|
|
203
|
+
admits: [],
|
|
204
|
+
denies: ["unknown", "unverified_contact", "trusted_contact", "guardian"],
|
|
205
|
+
},
|
|
206
|
+
];
|
|
207
|
+
|
|
208
|
+
for (const { policy, admits, denies } of cases) {
|
|
209
|
+
for (const trustClass of denies) {
|
|
210
|
+
test(`${policy} denies ${trustClass}`, () => {
|
|
211
|
+
setTrust(trustClass);
|
|
212
|
+
const { outcome } = route(policy);
|
|
213
|
+
expect(outcome.action).toBe("deny");
|
|
214
|
+
if (outcome.action === "deny") {
|
|
215
|
+
expect(outcome.logReason).toBe(
|
|
216
|
+
`Inbound voice admission floor: ${policy}`,
|
|
217
|
+
);
|
|
218
|
+
}
|
|
219
|
+
});
|
|
220
|
+
}
|
|
221
|
+
for (const trustClass of admits) {
|
|
222
|
+
test(`${policy} admits ${trustClass}`, () => {
|
|
223
|
+
setTrust(trustClass);
|
|
224
|
+
const { outcome } = route(policy);
|
|
225
|
+
expect(outcome.action).not.toBe("deny");
|
|
226
|
+
});
|
|
227
|
+
}
|
|
228
|
+
}
|
|
229
|
+
});
|
|
230
|
+
|
|
231
|
+
// ---------------------------------------------------------------------------
|
|
232
|
+
// Blocked / revoked always deny
|
|
233
|
+
// ---------------------------------------------------------------------------
|
|
234
|
+
|
|
235
|
+
describe("routeSetup — blocked / revoked", () => {
|
|
236
|
+
test("blocked caller is denied (pre-floor ACL check)", () => {
|
|
237
|
+
nextTrust = makeTrust("unknown", { status: "blocked" });
|
|
238
|
+
const { outcome } = route("strangers");
|
|
239
|
+
expect(outcome.action).toBe("deny");
|
|
240
|
+
});
|
|
241
|
+
|
|
242
|
+
test("revoked member is denied under permissive floor", () => {
|
|
243
|
+
// Revoked → resolver classifies as unknown; the floor mock denies on
|
|
244
|
+
// memberStatus regardless of the permissive policy.
|
|
245
|
+
nextTrust = makeTrust("unknown", { status: "revoked" });
|
|
246
|
+
const { outcome } = route("strangers");
|
|
247
|
+
expect(outcome.action).toBe("deny");
|
|
248
|
+
});
|
|
249
|
+
});
|
|
250
|
+
|
|
251
|
+
// ---------------------------------------------------------------------------
|
|
252
|
+
// admissionPolicy null/undefined → current behavior unchanged
|
|
253
|
+
// ---------------------------------------------------------------------------
|
|
254
|
+
|
|
255
|
+
describe("routeSetup — no policy preserves current behavior", () => {
|
|
256
|
+
test("unknown caller → name_capture", () => {
|
|
257
|
+
nextTrust = makeTrust("unknown");
|
|
258
|
+
expect(route(null).outcome.action).toBe("name_capture");
|
|
259
|
+
expect(route(undefined).outcome.action).toBe("name_capture");
|
|
260
|
+
});
|
|
261
|
+
|
|
262
|
+
test("unverified known caller → unverified_caller", () => {
|
|
263
|
+
nextTrust = makeTrust("unverified_contact", { status: "unverified" });
|
|
264
|
+
expect(route(null).outcome.action).toBe("unverified_caller");
|
|
265
|
+
});
|
|
266
|
+
|
|
267
|
+
test("trusted contact → normal_call", () => {
|
|
268
|
+
nextTrust = makeTrust("trusted_contact", { status: "active" });
|
|
269
|
+
expect(route(null).outcome.action).toBe("normal_call");
|
|
270
|
+
});
|
|
271
|
+
|
|
272
|
+
test("guardian → normal_call", () => {
|
|
273
|
+
nextTrust = makeTrust("guardian", { status: "active", role: "guardian" });
|
|
274
|
+
expect(route(null).outcome.action).toBe("normal_call");
|
|
275
|
+
});
|
|
276
|
+
|
|
277
|
+
test("member policy deny still denies", () => {
|
|
278
|
+
nextTrust = makeTrust("trusted_contact", {
|
|
279
|
+
status: "active",
|
|
280
|
+
policy: "deny",
|
|
281
|
+
});
|
|
282
|
+
expect(route(null).outcome.action).toBe("deny");
|
|
283
|
+
});
|
|
284
|
+
});
|
|
285
|
+
|
|
286
|
+
// ---------------------------------------------------------------------------
|
|
287
|
+
// Active permissive floor admits straight to a normal call
|
|
288
|
+
// ---------------------------------------------------------------------------
|
|
289
|
+
// When a policy is ACTIVELY set and admits the caller, the floor is the access
|
|
290
|
+
// decision: unknown/unverified callers bypass name_capture / unverified_caller
|
|
291
|
+
// and connect directly. With a null policy the legacy identity flows persist.
|
|
292
|
+
|
|
293
|
+
describe("routeSetup — permissive floor admits to normal_call", () => {
|
|
294
|
+
test("any_contact admits an unverified_contact to normal_call (not unverified_caller)", () => {
|
|
295
|
+
nextTrust = makeTrust("unverified_contact", { status: "unverified" });
|
|
296
|
+
const { outcome } = route("any_contact");
|
|
297
|
+
expect(outcome.action).toBe("normal_call");
|
|
298
|
+
});
|
|
299
|
+
|
|
300
|
+
test("strangers admits an unknown caller to normal_call (not name_capture)", () => {
|
|
301
|
+
nextTrust = makeTrust("unknown");
|
|
302
|
+
const { outcome } = route("strangers");
|
|
303
|
+
expect(outcome.action).toBe("normal_call");
|
|
304
|
+
});
|
|
305
|
+
|
|
306
|
+
test("strangers admits an unverified_contact to normal_call", () => {
|
|
307
|
+
nextTrust = makeTrust("unverified_contact", { status: "unverified" });
|
|
308
|
+
const { outcome } = route("strangers");
|
|
309
|
+
expect(outcome.action).toBe("normal_call");
|
|
310
|
+
});
|
|
311
|
+
|
|
312
|
+
test("null policy preserves legacy name_capture for unknown caller", () => {
|
|
313
|
+
nextTrust = makeTrust("unknown");
|
|
314
|
+
expect(route(null).outcome.action).toBe("name_capture");
|
|
315
|
+
});
|
|
316
|
+
|
|
317
|
+
test("null policy preserves legacy unverified_caller for unverified caller", () => {
|
|
318
|
+
nextTrust = makeTrust("unverified_contact", { status: "unverified" });
|
|
319
|
+
expect(route(null).outcome.action).toBe("unverified_caller");
|
|
320
|
+
});
|
|
321
|
+
|
|
322
|
+
test("trusted_contacts (default) still denies unknown and unverified", () => {
|
|
323
|
+
nextTrust = makeTrust("unknown");
|
|
324
|
+
expect(route("trusted_contacts").outcome.action).toBe("deny");
|
|
325
|
+
nextTrust = makeTrust("unverified_contact", { status: "unverified" });
|
|
326
|
+
expect(route("trusted_contacts").outcome.action).toBe("deny");
|
|
327
|
+
});
|
|
328
|
+
});
|
|
329
|
+
|
|
330
|
+
// ---------------------------------------------------------------------------
|
|
331
|
+
// Invites & pending challenges bypass the floor
|
|
332
|
+
// ---------------------------------------------------------------------------
|
|
333
|
+
|
|
334
|
+
describe("routeSetup — floor bypasses", () => {
|
|
335
|
+
test("active voice invite bypasses the floor (no_one policy)", () => {
|
|
336
|
+
nextTrust = makeTrust("unknown");
|
|
337
|
+
activeInvites = [{ friendName: "Friend", guardianName: "Guardian" }];
|
|
338
|
+
const { outcome } = route("no_one");
|
|
339
|
+
expect(outcome.action).toBe("invite_redemption");
|
|
340
|
+
});
|
|
341
|
+
|
|
342
|
+
test("pending verification challenge bypasses the floor (no_one policy)", () => {
|
|
343
|
+
// A pending challenge for a known member routes to verification regardless
|
|
344
|
+
// of the floor.
|
|
345
|
+
nextTrust = makeTrust("trusted_contact", { status: "active" });
|
|
346
|
+
pendingChallenge = { id: "vs_1" };
|
|
347
|
+
const { outcome } = route("no_one");
|
|
348
|
+
expect(outcome.action).toBe("verification");
|
|
349
|
+
});
|
|
350
|
+
});
|
|
@@ -78,7 +78,8 @@ export function resetPointerMessageProcessor(): void {
|
|
|
78
78
|
* Resolve whether the audience for a pointer message is trusted.
|
|
79
79
|
*
|
|
80
80
|
* Trusted when:
|
|
81
|
-
* - recent message provenance trust class is 'guardian'
|
|
81
|
+
* - recent message provenance trust class is 'guardian', 'trusted_contact',
|
|
82
|
+
* or 'unverified_contact'
|
|
82
83
|
* - conversation origin channel is 'vellum' (desktop app)
|
|
83
84
|
*
|
|
84
85
|
* Untrusted by default when insufficient evidence.
|
|
@@ -86,11 +87,16 @@ export function resetPointerMessageProcessor(): void {
|
|
|
86
87
|
function resolvePointerAudienceTrust(conversationId: string): boolean {
|
|
87
88
|
try {
|
|
88
89
|
// Check provenance trust class on recent messages first — this catches
|
|
89
|
-
//
|
|
90
|
-
//
|
|
90
|
+
// identity-known non-guardian contacts (trusted and unverified) who
|
|
91
|
+
// initiate calls from gateway channels (e.g. WhatsApp) where the
|
|
92
|
+
// conversation itself isn't desktop-origin.
|
|
91
93
|
const provenance =
|
|
92
94
|
getConversationRecentProvenanceTrustClass(conversationId);
|
|
93
|
-
if (
|
|
95
|
+
if (
|
|
96
|
+
provenance === "guardian" ||
|
|
97
|
+
provenance === "trusted_contact" ||
|
|
98
|
+
provenance === "unverified_contact"
|
|
99
|
+
)
|
|
94
100
|
return true;
|
|
95
101
|
|
|
96
102
|
const originChannel = getConversationOriginChannel(conversationId);
|
|
@@ -0,0 +1,104 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Gateway-backed channel admission policy reader.
|
|
3
|
+
*
|
|
4
|
+
* Reads a channel's resolved admission floor from the gateway via the
|
|
5
|
+
* `get_channel_admission_policy` IPC route. Admission must never break call
|
|
6
|
+
* setup, so this reader FAILS OPEN: any transport failure, malformed shape,
|
|
7
|
+
* or thrown error resolves to `null` (= admit, no enforcement). A policy is
|
|
8
|
+
* only returned when the gateway responds with `{ policy: <valid policy> }`.
|
|
9
|
+
*
|
|
10
|
+
* Caches per channelType with a short TTL, mirroring the conversation
|
|
11
|
+
* threshold cache in `../permissions/gateway-threshold-reader.ts`. Only the
|
|
12
|
+
* result of a SUCCESSFUL gateway round-trip is cached (a valid policy, or an
|
|
13
|
+
* explicit `{ policy: null }` "no enforcement" answer); a transport failure /
|
|
14
|
+
* throw / malformed shape fails open to `null` WITHOUT caching, so a recovered
|
|
15
|
+
* gateway is re-consulted on the next call setup rather than skipping the
|
|
16
|
+
* admission floor for the rest of the TTL.
|
|
17
|
+
*/
|
|
18
|
+
|
|
19
|
+
import {
|
|
20
|
+
type AdmissionPolicy,
|
|
21
|
+
isAdmissionPolicy,
|
|
22
|
+
} from "@vellumai/gateway-client";
|
|
23
|
+
|
|
24
|
+
import type { ChannelId } from "../channels/types.js";
|
|
25
|
+
import { ipcCall } from "../ipc/gateway-client.js";
|
|
26
|
+
|
|
27
|
+
const CACHE_TTL_MS = 5_000;
|
|
28
|
+
|
|
29
|
+
// Short IPC timeout so admission fails open PROMPTLY: a gateway that accepts
|
|
30
|
+
// the socket but stalls must never delay a live call handshake. 1s is generous
|
|
31
|
+
// under normal conditions yet far below ipcCall's 5s default.
|
|
32
|
+
const ADMISSION_IPC_TIMEOUT_MS = 1_000;
|
|
33
|
+
|
|
34
|
+
const cache = new Map<
|
|
35
|
+
ChannelId,
|
|
36
|
+
{ policy: AdmissionPolicy | null; timestamp: number }
|
|
37
|
+
>();
|
|
38
|
+
|
|
39
|
+
/** Test-only: clear the policy cache. */
|
|
40
|
+
export function _clearCacheForTesting(): void {
|
|
41
|
+
cache.clear();
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
/**
|
|
45
|
+
* Outcome of a single gateway fetch. Only a SUCCESSFUL round-trip (a valid
|
|
46
|
+
* policy, or an explicit `{ policy: null }` "no enforcement" answer) is
|
|
47
|
+
* cacheable. A transport failure, thrown error, or malformed shape is NOT
|
|
48
|
+
* cached so a recovered gateway is re-consulted on the next call setup.
|
|
49
|
+
*/
|
|
50
|
+
type FetchResult =
|
|
51
|
+
| { ok: true; policy: AdmissionPolicy | null }
|
|
52
|
+
| { ok: false };
|
|
53
|
+
|
|
54
|
+
async function fetchAdmissionPolicy(
|
|
55
|
+
channelType: ChannelId,
|
|
56
|
+
): Promise<FetchResult> {
|
|
57
|
+
try {
|
|
58
|
+
// ipcCall() returns undefined on transport failure (socket not found,
|
|
59
|
+
// timeout, parse error). That, a throw, or a malformed shape is a FAILURE:
|
|
60
|
+
// fail open without caching so the next setup re-attempts the IPC.
|
|
61
|
+
const result = (await ipcCall(
|
|
62
|
+
"get_channel_admission_policy",
|
|
63
|
+
{ channelType },
|
|
64
|
+
ADMISSION_IPC_TIMEOUT_MS,
|
|
65
|
+
)) as { policy?: unknown } | null | undefined;
|
|
66
|
+
|
|
67
|
+
if (result === undefined) return { ok: false };
|
|
68
|
+
if (result && isAdmissionPolicy(result.policy)) {
|
|
69
|
+
return { ok: true, policy: result.policy };
|
|
70
|
+
}
|
|
71
|
+
// Explicit "no enforcement" — the gateway successfully answered. Cacheable.
|
|
72
|
+
if (result && result.policy === null) return { ok: true, policy: null };
|
|
73
|
+
// Anything else (missing/invalid policy field) is a malformed shape.
|
|
74
|
+
return { ok: false };
|
|
75
|
+
} catch {
|
|
76
|
+
return { ok: false };
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
/**
|
|
81
|
+
* Resolve a channel's admission policy from the gateway, or `null` on any
|
|
82
|
+
* failure / absence. `null` means admit with no enforcement.
|
|
83
|
+
*
|
|
84
|
+
* Always fails open to `null` on failure, but only caches the result of a
|
|
85
|
+
* SUCCESSFUL gateway round-trip — a transient hiccup must not skip the
|
|
86
|
+
* admission floor for the rest of the TTL.
|
|
87
|
+
*/
|
|
88
|
+
export async function getChannelAdmissionPolicy(
|
|
89
|
+
channelType: ChannelId,
|
|
90
|
+
): Promise<AdmissionPolicy | null> {
|
|
91
|
+
const cached = cache.get(channelType);
|
|
92
|
+
if (cached && Date.now() - cached.timestamp < CACHE_TTL_MS) {
|
|
93
|
+
return cached.policy;
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
const result = await fetchAdmissionPolicy(channelType);
|
|
97
|
+
if (!result.ok) {
|
|
98
|
+
// Fail open WITHOUT caching so a recovered gateway is re-consulted next time.
|
|
99
|
+
return null;
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
cache.set(channelType, { policy: result.policy, timestamp: Date.now() });
|
|
103
|
+
return result.policy;
|
|
104
|
+
}
|
|
@@ -9,14 +9,15 @@
|
|
|
9
9
|
|
|
10
10
|
import { findGuardianForChannel } from "../contacts/contact-store.js";
|
|
11
11
|
import {
|
|
12
|
-
createCanonicalGuardianDelivery,
|
|
13
12
|
createCanonicalGuardianRequest,
|
|
14
13
|
listCanonicalGuardianDeliveries,
|
|
15
14
|
listCanonicalGuardianRequests,
|
|
16
|
-
updateCanonicalGuardianDelivery,
|
|
17
15
|
} from "../memory/canonical-guardian-store.js";
|
|
16
|
+
import {
|
|
17
|
+
recordApprovalCardDelivery,
|
|
18
|
+
recordGuardianRequestDeliveries,
|
|
19
|
+
} from "../notifications/canonical-delivery-recorder.js";
|
|
18
20
|
import { emitNotificationSignal } from "../notifications/emit-signal.js";
|
|
19
|
-
import type { NotificationDeliveryResult } from "../notifications/types.js";
|
|
20
21
|
import { getLogger } from "../util/logger.js";
|
|
21
22
|
import { getUserConsultationTimeoutMs } from "./call-constants.js";
|
|
22
23
|
import type { CallPendingQuestion } from "./types.js";
|
|
@@ -39,17 +40,6 @@ export interface GuardianDispatchParams {
|
|
|
39
40
|
inputDigest?: string;
|
|
40
41
|
}
|
|
41
42
|
|
|
42
|
-
function applyDeliveryStatus(
|
|
43
|
-
deliveryId: string,
|
|
44
|
-
result: NotificationDeliveryResult,
|
|
45
|
-
): void {
|
|
46
|
-
if (result.status === "sent") {
|
|
47
|
-
updateCanonicalGuardianDelivery(deliveryId, { status: "sent" });
|
|
48
|
-
return;
|
|
49
|
-
}
|
|
50
|
-
updateCanonicalGuardianDelivery(deliveryId, { status: "failed" });
|
|
51
|
-
}
|
|
52
|
-
|
|
53
43
|
/**
|
|
54
44
|
* Dispatch a guardian action request to all configured channels.
|
|
55
45
|
* Fire-and-forget: errors are logged but do not propagate.
|
|
@@ -179,7 +169,7 @@ async function dispatchGuardianQuestionInner(
|
|
|
179
169
|
|
|
180
170
|
// Route through the canonical notification pipeline. The paired vellum
|
|
181
171
|
// conversation from this pipeline is the canonical guardian conversation.
|
|
182
|
-
let vellumDeliveryId: string |
|
|
172
|
+
let vellumDeliveryId: string | undefined;
|
|
183
173
|
const requestCode =
|
|
184
174
|
request.requestCode ?? request.id.slice(0, 6).toUpperCase();
|
|
185
175
|
const signalResult = await emitNotificationSignal({
|
|
@@ -208,44 +198,26 @@ async function dispatchGuardianQuestionInner(
|
|
|
208
198
|
onConversationCreated: (info) => {
|
|
209
199
|
if (info.sourceEventName !== "guardian.question" || vellumDeliveryId)
|
|
210
200
|
return;
|
|
211
|
-
|
|
201
|
+
vellumDeliveryId = recordApprovalCardDelivery({
|
|
212
202
|
requestId: request.id,
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
});
|
|
216
|
-
vellumDeliveryId = delivery.id;
|
|
203
|
+
channel: "vellum",
|
|
204
|
+
conversationId: info.conversationId,
|
|
205
|
+
})?.id;
|
|
217
206
|
},
|
|
218
207
|
});
|
|
219
208
|
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
destinationChannel: "vellum",
|
|
226
|
-
destinationConversationId: result.conversationId,
|
|
227
|
-
});
|
|
228
|
-
vellumDeliveryId = delivery.id;
|
|
229
|
-
}
|
|
230
|
-
applyDeliveryStatus(vellumDeliveryId, result);
|
|
231
|
-
continue;
|
|
232
|
-
}
|
|
233
|
-
|
|
234
|
-
const delivery = createCanonicalGuardianDelivery({
|
|
235
|
-
requestId: request.id,
|
|
236
|
-
destinationChannel: result.channel,
|
|
237
|
-
destinationChatId:
|
|
238
|
-
result.destination.length > 0 ? result.destination : undefined,
|
|
239
|
-
});
|
|
240
|
-
applyDeliveryStatus(delivery.id, result);
|
|
241
|
-
}
|
|
209
|
+
vellumDeliveryId = recordGuardianRequestDeliveries({
|
|
210
|
+
requestId: request.id,
|
|
211
|
+
deliveryResults: signalResult.deliveryResults,
|
|
212
|
+
vellumDeliveryId,
|
|
213
|
+
});
|
|
242
214
|
|
|
243
215
|
if (!vellumDeliveryId) {
|
|
244
|
-
|
|
216
|
+
recordApprovalCardDelivery({
|
|
245
217
|
requestId: request.id,
|
|
246
|
-
|
|
218
|
+
channel: "vellum",
|
|
219
|
+
status: "failed",
|
|
247
220
|
});
|
|
248
|
-
updateCanonicalGuardianDelivery(fallback.id, { status: "failed" });
|
|
249
221
|
log.warn(
|
|
250
222
|
{ requestId: request.id, reason: signalResult.reason },
|
|
251
223
|
"Notification pipeline did not produce a vellum delivery result",
|