@vellumai/assistant 0.10.1 → 0.10.2-dev.202606241651.2d2b40d
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/workspace-tools.md +42 -33
- package/eslint-rules/cli-no-daemon-internals.js +6 -0
- package/node_modules/@vellumai/gateway-client/src/__tests__/guardian-delivery-contract.test.ts +91 -0
- package/node_modules/@vellumai/gateway-client/src/__tests__/trust-verdict-contract.test.ts +31 -0
- package/node_modules/@vellumai/gateway-client/src/guardian-delivery-contract.ts +48 -0
- package/node_modules/@vellumai/gateway-client/src/index.ts +14 -0
- package/node_modules/@vellumai/gateway-client/src/trust-verdict-contract.ts +17 -0
- package/openapi.yaml +74 -1
- package/package.json +1 -1
- package/scripts/test.sh +36 -15
- package/src/__tests__/actor-token-service.test.ts +36 -14
- package/src/__tests__/agent-loop-override-profile.test.ts +1 -0
- package/src/__tests__/agent-wake-disk-pressure-callsite.test.ts +2 -0
- package/src/__tests__/agent-wake-override-profile.test.ts +2 -0
- package/src/__tests__/annotate-activity-metadata.test.ts +2 -0
- package/src/__tests__/annotate-risk-options.test.ts +2 -0
- package/src/__tests__/approval-cascade.test.ts +2 -0
- package/src/__tests__/background-workers-disk-pressure.test.ts +2 -0
- package/src/__tests__/btw-routes.test.ts +2 -0
- package/src/__tests__/build-persisted-content.test.ts +2 -0
- package/src/__tests__/call-controller.test.ts +19 -0
- package/src/__tests__/channel-guardian.test.ts +94 -58
- package/src/__tests__/channel-reply-delivery.test.ts +2 -0
- package/src/__tests__/compaction-events.test.ts +2 -0
- package/src/__tests__/compaction.benchmark.test.ts +2 -0
- package/src/__tests__/compactor-call-site-logging.test.ts +2 -0
- package/src/__tests__/compactor-low-watermark-cut.test.ts +2 -0
- package/src/__tests__/compactor-preserved-tail-count.test.ts +2 -0
- package/src/__tests__/compactor-summary-call-truncation.test.ts +2 -0
- package/src/__tests__/compactor-web-search-strip.test.ts +2 -0
- package/src/__tests__/computer-use-tools.test.ts +13 -0
- package/src/__tests__/config-loader-backfill.test.ts +5 -1
- package/src/__tests__/config-schema.test.ts +1 -0
- package/src/__tests__/confirmation-request-guardian-bridge.test.ts +31 -29
- package/src/__tests__/contacts-relay-reads.test.ts +13 -15
- package/src/__tests__/conversation-abort-tool-results.test.ts +2 -0
- package/src/__tests__/conversation-agent-loop-disk-pressure.test.ts +2 -0
- package/src/__tests__/conversation-agent-loop-inference-profile.test.ts +2 -0
- package/src/__tests__/conversation-agent-loop-overflow.test.ts +2 -0
- package/src/__tests__/conversation-agent-loop.test.ts +7 -0
- package/src/__tests__/conversation-analysis-routes.test.ts +2 -0
- package/src/__tests__/conversation-app-control-lifecycle.test.ts +2 -0
- package/src/__tests__/conversation-confirmation-signals.test.ts +2 -0
- package/src/__tests__/conversation-history-web-search.test.ts +2 -0
- package/src/__tests__/conversation-load-history-repair.test.ts +2 -0
- package/src/__tests__/conversation-load-history-stripped.test.ts +2 -0
- package/src/__tests__/conversation-pairing.test.ts +2 -0
- package/src/__tests__/conversation-process-app-control-preactivation.test.ts +2 -0
- package/src/__tests__/conversation-process-callsite.test.ts +2 -0
- package/src/__tests__/conversation-provider-retry-repair.test.ts +2 -0
- package/src/__tests__/conversation-queue.test.ts +91 -0
- package/src/__tests__/conversation-routes-guardian-reply.test.ts +14 -0
- package/src/__tests__/conversation-routes-slash-commands.test.ts +14 -0
- package/src/__tests__/conversation-slash-queue.test.ts +2 -0
- package/src/__tests__/conversation-slash-unknown.test.ts +2 -0
- package/src/__tests__/conversation-speed-override.test.ts +2 -0
- package/src/__tests__/conversation-surfaces-action-delivery.test.ts +65 -0
- package/src/__tests__/conversation-title-service.test.ts +2 -0
- package/src/__tests__/conversation-tool-setup-attribution.test.ts +47 -0
- package/src/__tests__/conversation-usage.test.ts +2 -0
- package/src/__tests__/conversation-workspace-cache-state.test.ts +2 -0
- package/src/__tests__/conversation-workspace-injection.test.ts +2 -0
- package/src/__tests__/conversation-workspace-tool-tracking.test.ts +2 -0
- package/src/__tests__/credential-security-invariants.test.ts +0 -1
- package/src/__tests__/db-migration-rollback.test.ts +205 -171
- package/src/__tests__/db-test-helpers.ts +5 -4
- package/src/__tests__/deterministic-verification-control-plane.test.ts +4 -2
- package/src/__tests__/disk-pressure-guard.test.ts +41 -0
- package/src/__tests__/dm-persistence.test.ts +2 -0
- package/src/__tests__/emit-signal-routing-intent.test.ts +10 -5
- package/src/__tests__/events-dev-bypass-actor.test.ts +7 -1
- package/src/__tests__/filing-service.test.ts +2 -0
- package/src/__tests__/guardian-binding-drift-heal.test.ts +75 -10
- package/src/__tests__/guardian-dispatch.test.ts +95 -1
- package/src/__tests__/guardian-outbound-http.test.ts +13 -0
- package/src/__tests__/heartbeat-disk-pressure.test.ts +2 -0
- package/src/__tests__/heartbeat-service.test.ts +2 -0
- package/src/__tests__/helpers/channel-test-adapter.ts +1 -7
- package/src/__tests__/host-app-control-routes.test.ts +24 -30
- package/src/__tests__/host-bash-routes.test.ts +31 -41
- package/src/__tests__/host-browser-routes.test.ts +26 -32
- package/src/__tests__/host-cu-proxy.test.ts +299 -0
- package/src/__tests__/host-cu-routes-targeted.test.ts +25 -33
- package/src/__tests__/host-file-routes-targeted.test.ts +40 -52
- package/src/__tests__/host-transfer-routes-targeted.test.ts +31 -43
- package/src/__tests__/http-user-message-parity.test.ts +167 -8
- package/src/__tests__/inbound-slack-persistence.test.ts +2 -0
- package/src/__tests__/invite-redemption-service.test.ts +43 -0
- package/src/__tests__/llm-context-normalization.test.ts +105 -0
- package/src/__tests__/llm-usage-store.test.ts +25 -0
- package/src/__tests__/media-stream-server-integration.test.ts +127 -0
- package/src/__tests__/memory-retrieval-hook.test.ts +2 -0
- package/src/__tests__/messaging-send-tool.test.ts +2 -0
- package/src/__tests__/migration-import-from-url.test.ts +2 -2
- package/src/__tests__/native-web-search.test.ts +2 -0
- package/src/__tests__/non-member-access-request.test.ts +189 -17
- package/src/__tests__/notification-broadcaster.test.ts +4 -0
- package/src/__tests__/notification-decision-recipient-context.test.ts +33 -32
- package/src/__tests__/notification-deep-link.test.ts +6 -0
- package/src/__tests__/notification-guardian-path.test.ts +19 -0
- package/src/__tests__/outbound-slack-persistence.test.ts +2 -0
- package/src/__tests__/pending-interactions-resolved-event.test.ts +7 -4
- package/src/__tests__/persistence-secret-redaction.test.ts +2 -0
- package/src/__tests__/plugin-bootstrap.test.ts +3 -73
- package/src/__tests__/plugin-route-contribution.test.ts +4 -17
- package/src/__tests__/plugin-tool-contribution.test.ts +3 -18
- package/src/__tests__/plugin-types.test.ts +0 -2
- package/src/__tests__/process-message-background-slack.test.ts +2 -0
- package/src/__tests__/process-message-display-content.test.ts +2 -0
- package/src/__tests__/provider-usage-tracking.test.ts +39 -0
- package/src/__tests__/regenerate-fire-and-forget-trace.test.ts +2 -0
- package/src/__tests__/registry.test.ts +3 -0
- package/src/__tests__/relay-server.test.ts +694 -25
- package/src/__tests__/runtime-attachment-metadata.test.ts +0 -1
- package/src/__tests__/secret-ingress-http.test.ts +14 -0
- package/src/__tests__/send-endpoint-busy.test.ts +30 -8
- package/src/__tests__/skills.test.ts +44 -0
- package/src/__tests__/slack-inbound-verification.test.ts +47 -2
- package/src/__tests__/sse-actor-principal-guardian-source.test.ts +102 -0
- package/src/__tests__/steer-on-enqueue-question.test.ts +181 -0
- package/src/__tests__/stt-hints.test.ts +44 -13
- package/src/__tests__/subagent-detail.test.ts +27 -0
- package/src/__tests__/subagent-disposal.test.ts +65 -0
- package/src/__tests__/subagent-notify-parent.test.ts +2 -0
- package/src/__tests__/subagent-spawn-tool-fork.test.ts +2 -0
- package/src/__tests__/subagent-tools.test.ts +2 -0
- package/src/__tests__/suggestion-routes.test.ts +2 -0
- package/src/__tests__/title-generate-hook.test.ts +2 -0
- package/src/__tests__/tool-executor-lifecycle-events.test.ts +2 -0
- package/src/__tests__/tool-executor.test.ts +16 -11
- package/src/__tests__/tool-preview-lifecycle.test.ts +2 -0
- package/src/__tests__/tool-result-metadata-plumbing.test.ts +2 -0
- package/src/__tests__/tool-start-timestamp.test.ts +2 -0
- package/src/__tests__/trusted-contact-inline-approval-integration.test.ts +10 -10
- package/src/__tests__/twilio-routes.test.ts +96 -0
- package/src/__tests__/verification-control-plane-policy.test.ts +2 -0
- package/src/__tests__/web-search-backend-failure.test.ts +2 -0
- package/src/__tests__/workspace-tool-loader.test.ts +195 -2
- package/src/agent/loop-exclusive-tool.test.ts +150 -0
- package/src/agent/loop.ts +56 -0
- package/src/api/constants/sse-replay.ts +41 -0
- package/src/api/index.ts +6 -0
- package/src/api/responses/llm-request-log-entry.ts +25 -0
- package/src/api/responses/subagent-detail.ts +17 -0
- package/src/calls/__tests__/relay-setup-router.test.ts +262 -4
- package/src/calls/call-domain.ts +3 -3
- package/src/calls/guardian-dispatch.ts +10 -8
- package/src/calls/inbound-trust-reader.ts +17 -1
- package/src/calls/media-stream-server.ts +21 -0
- package/src/calls/relay-server.ts +167 -50
- package/src/calls/relay-setup-router.ts +37 -7
- package/src/calls/relay-verification.ts +4 -4
- package/src/calls/stt-hints.ts +9 -12
- package/src/calls/twilio-routes.ts +14 -4
- package/src/cli/commands/__tests__/cache.test.ts +8 -1
- package/src/cli/commands/cache.ts +194 -181
- package/src/cli/commands/db/__tests__/repair.test.ts +6 -5
- package/src/cli/commands/db/status.ts +37 -1
- package/src/cli/commands/mcp.ts +252 -218
- package/src/cli/commands/memory/__tests__/worker.test.ts +302 -0
- package/src/cli/commands/memory/index.ts +2 -0
- package/src/cli/commands/memory/worker.ts +175 -0
- package/src/cli/commands/plugins.ts +75 -3
- package/src/cli/lib/__tests__/install-from-github.test.ts +102 -0
- package/src/cli/lib/__tests__/list-installed-plugins.test.ts +160 -1
- package/src/cli/lib/list-installed-plugins.ts +179 -1
- package/src/config/__tests__/loader-callsite-strip-fallback.test.ts +143 -0
- package/src/config/bundled-skills/computer-use/TOOLS.json +6 -1
- package/src/config/bundled-skills/contacts/tools/contact-merge.ts +27 -17
- package/src/config/bundled-skills/contacts/tools/contact-search.ts +13 -3
- package/src/config/feature-flag-registry.json +0 -8
- package/src/config/loader.ts +36 -5
- package/src/config/schemas/__tests__/memory-v3.test.ts +1 -0
- package/src/config/schemas/memory-lifecycle.ts +12 -0
- package/src/config/schemas/memory-v3.ts +7 -0
- package/src/config/schemas/memory.ts +4 -0
- package/src/config/schemas/timeouts.ts +8 -0
- package/src/config/seed-inference-profiles.ts +14 -5
- package/src/config/skills.ts +27 -5
- package/src/contacts/__tests__/guardian-delivery-reader.test.ts +312 -0
- package/src/contacts/contacts-write.ts +3 -0
- package/src/contacts/guardian-delivery-reader.ts +223 -0
- package/src/daemon/conversation-agent-loop.ts +9 -0
- package/src/daemon/conversation-process.ts +39 -17
- package/src/daemon/conversation-surfaces.ts +8 -0
- package/src/daemon/conversation-tool-setup.ts +49 -16
- package/src/daemon/conversation.ts +21 -2
- package/src/daemon/disk-pressure-guard.ts +12 -2
- package/src/daemon/event-loop-watchdog.ts +28 -1
- package/src/daemon/external-plugins-bootstrap.ts +4 -34
- package/src/daemon/handlers/__tests__/config-a2a-redeem.test.ts +25 -0
- package/src/daemon/handlers/__tests__/config-channels.test.ts +225 -0
- package/src/daemon/handlers/config-a2a.ts +6 -14
- package/src/daemon/handlers/config-channels.ts +78 -22
- package/src/daemon/handlers/conversations.ts +77 -0
- package/src/daemon/host-cu-proxy.ts +102 -11
- package/src/daemon/lifecycle.ts +4 -0
- package/src/daemon/memory-v2-startup.test.ts +72 -0
- package/src/daemon/memory-v2-startup.ts +87 -19
- package/src/daemon/server.ts +0 -4
- package/src/daemon/shutdown-handlers.ts +20 -0
- package/src/daemon/tool-setup-types.ts +9 -0
- package/src/ipc/__tests__/clients-list-ipc.test.ts +1 -1
- package/src/ipc/assistant-server.ts +2 -2
- package/src/memory/__tests__/301-create-watchdog-events.test.ts +110 -0
- package/src/memory/__tests__/memory-retrospective-job.test.ts +8 -0
- package/src/memory/__tests__/prompt-override.test.ts +192 -0
- package/src/memory/__tests__/watchdog-events-store.test.ts +161 -0
- package/src/memory/conversation-crud.ts +38 -0
- package/src/memory/db-connection.ts +22 -3
- package/src/memory/db-init.ts +36 -502
- package/src/memory/db-singleton.ts +6 -4
- package/src/memory/jobs-worker.ts +58 -0
- package/src/memory/llm-usage-store.ts +48 -20
- package/src/memory/memory-retrospective-job.ts +9 -8
- package/src/memory/migrations/014-backfill-inbox-thread-state.ts +13 -3
- package/src/memory/migrations/136-drop-assistant-id-columns.ts +52 -27
- package/src/memory/migrations/209-strip-thinking-from-consolidated.ts +130 -56
- package/src/memory/migrations/300-add-processing-started-at.ts +30 -0
- package/src/memory/migrations/301-create-watchdog-events.ts +45 -0
- package/src/memory/migrations/__tests__/014-backfill-inbox-thread-state.test.ts +108 -0
- package/src/memory/migrations/__tests__/136-drop-assistant-id-columns.test.ts +82 -0
- package/src/memory/migrations/__tests__/209-strip-thinking-from-consolidated.test.ts +224 -0
- package/src/memory/migrations/__tests__/run-migrations.test.ts +2 -2
- package/src/memory/migrations/run-migrations.ts +90 -6
- package/src/memory/migrations/schema-introspection.ts +14 -0
- package/src/memory/migrations/validate-migration-state.ts +101 -66
- package/src/memory/prompt-override.ts +129 -0
- package/src/memory/schema/conversations.ts +9 -0
- package/src/memory/schema/infrastructure.ts +20 -0
- package/src/memory/steps.ts +573 -0
- package/src/memory/v2/__tests__/cli-command-store.test.ts +25 -0
- package/src/memory/v2/__tests__/skill-store.test.ts +80 -0
- package/src/memory/v2/cli-command-store.ts +75 -38
- package/src/memory/v2/prompts/consolidation.ts +13 -82
- package/src/memory/v2/prompts/router.ts +21 -93
- package/src/memory/v2/skill-store.ts +68 -31
- package/src/memory/watchdog-events-store.ts +87 -0
- package/src/memory/worker-control.ts +118 -0
- package/src/memory/worker-process.ts +72 -0
- package/src/notifications/__tests__/broadcaster.test.ts +16 -8
- package/src/notifications/__tests__/connected-channels.test.ts +114 -0
- package/src/notifications/__tests__/decision-engine.test.ts +78 -9
- package/src/notifications/__tests__/destination-resolver.test.ts +256 -0
- package/src/notifications/broadcaster.ts +8 -1
- package/src/notifications/decision-engine.ts +15 -7
- package/src/notifications/destination-resolver.ts +68 -24
- package/src/notifications/emit-signal.ts +39 -14
- package/src/onboarding/checkin-event.test.ts +220 -0
- package/src/onboarding/checkin-event.ts +321 -0
- package/src/onboarding/schedule-checkin.ts +190 -0
- package/src/permissions/question-prompter.test.ts +1 -1
- package/src/permissions/question-prompter.ts +7 -4
- package/src/plugin-api/index.ts +6 -6
- package/src/plugin-api/types.ts +3 -5
- package/src/plugin-api/vision-support.test.ts +28 -4
- package/src/plugin-api/vision-support.ts +66 -31
- package/src/plugins/defaults/advisor/__tests__/consult.test.ts +161 -0
- package/src/plugins/defaults/advisor/__tests__/context-pack-gating.test.ts +106 -0
- package/src/plugins/defaults/advisor/__tests__/context-pack.test.ts +60 -0
- package/src/plugins/defaults/advisor/consult.ts +110 -6
- package/src/plugins/defaults/advisor/context-pack.ts +288 -0
- package/src/plugins/defaults/advisor/steering.ts +14 -2
- package/src/plugins/defaults/advisor/tools/advisor.ts +32 -5
- package/src/plugins/defaults/image-fallback/__tests__/image-fallback.test.ts +47 -7
- package/src/plugins/defaults/image-fallback/hooks/post-tool-use.ts +10 -11
- package/src/plugins/defaults/image-fallback/hooks/user-prompt-submit.ts +12 -20
- package/src/plugins/defaults/image-fallback/src/caption-blocks.ts +42 -11
- package/src/plugins/defaults/memory-v3-shadow/orchestrate.ts +11 -2
- package/src/plugins/defaults/memory-v3-shadow/pool-select.test.ts +146 -0
- package/src/plugins/defaults/memory-v3-shadow/pool-select.ts +29 -1
- package/src/plugins/defaults/memory-v3-shadow/shadow-plugin.ts +8 -1
- package/src/plugins/mtime-cache.ts +7 -2
- package/src/plugins/types.ts +0 -2
- package/src/providers/anthropic/client.ts +5 -0
- package/src/providers/call-site-routing.ts +4 -0
- package/src/providers/model-catalog.ts +16 -0
- package/src/providers/openai/responses-provider.ts +5 -0
- package/src/providers/openrouter/client.ts +5 -0
- package/src/providers/provider-send-message.ts +4 -0
- package/src/providers/ratelimit.ts +4 -0
- package/src/providers/retry.ts +4 -0
- package/src/providers/types.ts +9 -0
- package/src/providers/usage-tracking.ts +4 -0
- package/src/runtime/__tests__/channel-verification-service.test.ts +133 -0
- package/src/runtime/__tests__/guardian-vellum-migration.test.ts +181 -0
- package/src/runtime/__tests__/is-guardian-bound-for-channel.test.ts +66 -0
- package/src/runtime/__tests__/local-principal-trust.test.ts +164 -0
- package/src/runtime/__tests__/trust-verdict-consumer.test.ts +335 -3
- package/src/runtime/access-request-helper.ts +19 -39
- package/src/runtime/actor-trust-resolver.ts +2 -2
- package/src/runtime/anchored-guardian.test.ts +156 -0
- package/src/runtime/anchored-guardian.ts +135 -0
- package/src/runtime/assistant-event-hub.ts +1 -1
- package/src/runtime/assistant-stream-state.ts +9 -2
- package/src/runtime/auth/__tests__/require-bound-guardian.test.ts +99 -0
- package/src/runtime/auth/require-bound-guardian.ts +21 -11
- package/src/runtime/channel-verification-service.ts +56 -31
- package/src/runtime/confirmation-request-guardian-bridge.ts +3 -3
- package/src/runtime/guardian-vellum-migration.ts +66 -7
- package/src/runtime/invite-redemption-service.ts +50 -18
- package/src/runtime/local-actor-identity.ts +76 -11
- package/src/runtime/local-principal-trust.ts +52 -0
- package/src/runtime/pending-interactions.ts +11 -1
- package/src/runtime/routes/__tests__/channel-verification-revoke.test.ts +56 -5
- package/src/runtime/routes/__tests__/channel-verification-routes.test.ts +1 -1
- package/src/runtime/routes/__tests__/contact-routes.test.ts +212 -0
- package/src/runtime/routes/__tests__/global-search-routes.test.ts +93 -0
- package/src/runtime/routes/__tests__/surface-action-routes.test.ts +215 -1
- package/src/runtime/routes/browser-routes.ts +1 -1
- package/src/runtime/routes/channel-verification-routes.ts +3 -3
- package/src/runtime/routes/contact-routes.ts +8 -32
- package/src/runtime/routes/conversation-cli-routes.ts +4 -5
- package/src/runtime/routes/conversation-list-routes.ts +4 -7
- package/src/runtime/routes/conversation-routes.ts +74 -81
- package/src/runtime/routes/events-routes.ts +2 -2
- package/src/runtime/routes/global-search-routes.ts +3 -1
- package/src/runtime/routes/guardian-action-routes.ts +4 -5
- package/src/runtime/routes/host-app-control-routes.ts +5 -4
- package/src/runtime/routes/host-bash-routes.ts +5 -4
- package/src/runtime/routes/host-browser-routes.ts +9 -11
- package/src/runtime/routes/host-cu-routes.ts +5 -4
- package/src/runtime/routes/host-file-routes.ts +5 -4
- package/src/runtime/routes/host-transfer-routes.ts +6 -6
- package/src/runtime/routes/http-adapter.ts +1 -1
- package/src/runtime/routes/identity-routes.ts +3 -2
- package/src/runtime/routes/inbound-message-handler.ts +5 -5
- package/src/runtime/routes/inbound-stages/acl-enforcement.test.ts +97 -5
- package/src/runtime/routes/inbound-stages/acl-enforcement.ts +61 -49
- package/src/runtime/routes/inbound-stages/background-dispatch.ts +16 -4
- package/src/runtime/routes/inbound-stages/escalation-intercept.ts +7 -7
- package/src/runtime/routes/inbound-stages/guardian-activation-intercept.test.ts +21 -8
- package/src/runtime/routes/inbound-stages/guardian-activation-intercept.ts +14 -3
- package/src/runtime/routes/index.ts +2 -0
- package/src/runtime/routes/llm-context-normalization.ts +71 -0
- package/src/runtime/routes/mcp-auth-routes.ts +38 -15
- package/src/runtime/routes/migration-rollback-routes.ts +4 -3
- package/src/runtime/routes/migration-routes.ts +4 -1
- package/src/runtime/routes/onboarding-checkin-routes.ts +86 -0
- package/src/runtime/routes/subagents-routes.ts +5 -0
- package/src/runtime/routes/surface-action-routes.ts +51 -55
- package/src/runtime/services/__tests__/conversation-serializer.test.ts +1 -0
- package/src/runtime/services/conversation-serializer.ts +7 -9
- package/src/runtime/tool-grant-request-helper.ts +3 -3
- package/src/runtime/trust-verdict-consumer.ts +85 -9
- package/src/runtime/verification-outbound-actions.ts +18 -18
- package/src/signals/user-message.ts +16 -0
- package/src/subagent/manager.ts +9 -0
- package/src/telemetry/types.ts +34 -1
- package/src/telemetry/usage-telemetry-reporter.test.ts +3 -2
- package/src/telemetry/usage-telemetry-reporter.ts +87 -3
- package/src/tools/ask-question/ask-question-tool.test.ts +29 -0
- package/src/tools/ask-question/ask-question-tool.ts +13 -0
- package/src/tools/computer-use/definitions.ts +8 -2
- package/src/tools/executor.ts +4 -4
- package/src/tools/registry.ts +18 -0
- package/src/tools/tool-approval-handler.ts +1 -1
- package/src/tools/tool-defaults.ts +9 -2
- package/src/tools/types.ts +17 -2
- package/src/tools/workspace-tools/loader.ts +348 -244
- package/src/util/platform.ts +5 -0
- package/src/util/telemetry-db-path.ts +24 -0
- package/src/workspace/migrations/017-seed-persona-dirs.ts +3 -34
- package/src/workspace/migrations/019-scope-journal-to-guardian.ts +3 -24
- package/src/__tests__/workspace-tools-watcher-flag.test.ts +0 -70
- package/src/daemon/workspace-tools-watcher.ts +0 -328
- package/src/memory/migrations/registry.ts +0 -573
|
@@ -19,14 +19,28 @@ mock.module("../../../util/logger.js", () => ({
|
|
|
19
19
|
}));
|
|
20
20
|
|
|
21
21
|
// Track contact-store reads to prove findContactChannel is NOT used on the
|
|
22
|
-
// verdict path.
|
|
22
|
+
// verdict path.
|
|
23
23
|
const findContactChannelCalls: unknown[] = [];
|
|
24
24
|
mock.module("../../../contacts/contact-store.js", () => ({
|
|
25
25
|
findContactChannel: (params: unknown) => {
|
|
26
26
|
findContactChannelCalls.push(params);
|
|
27
27
|
return null;
|
|
28
28
|
},
|
|
29
|
-
|
|
29
|
+
}));
|
|
30
|
+
|
|
31
|
+
// resolveGuardianLabel resolves the guardian via the gateway delivery reader.
|
|
32
|
+
let guardianDeliveryList: Array<Record<string, unknown>> = [];
|
|
33
|
+
mock.module("../../../contacts/guardian-delivery-reader.js", () => ({
|
|
34
|
+
getGuardianDelivery: async () => guardianDeliveryList,
|
|
35
|
+
guardianForChannel: (
|
|
36
|
+
list: Array<Record<string, unknown>>,
|
|
37
|
+
channelType: string,
|
|
38
|
+
) => list.find((g) => g.channelType === channelType && g.status === "active"),
|
|
39
|
+
}));
|
|
40
|
+
|
|
41
|
+
mock.module("../../../prompts/user-reference.js", () => ({
|
|
42
|
+
resolveGuardianName: (displayName?: string | null) =>
|
|
43
|
+
displayName && displayName.trim().length > 0 ? displayName.trim() : "my human",
|
|
30
44
|
}));
|
|
31
45
|
|
|
32
46
|
const deliverReplyCalls: Array<{ url: string; payload: unknown }> = [];
|
|
@@ -126,6 +140,7 @@ beforeEach(() => {
|
|
|
126
140
|
deliverReplyCalls.length = 0;
|
|
127
141
|
accessRequestCalls.length = 0;
|
|
128
142
|
inviteTokenForTest = undefined;
|
|
143
|
+
guardianDeliveryList = [];
|
|
129
144
|
});
|
|
130
145
|
|
|
131
146
|
afterEach(() => {
|
|
@@ -140,8 +155,8 @@ describe("enforceIngressAcl — verdict-sourced member resolution", () => {
|
|
|
140
155
|
|
|
141
156
|
expect(result.earlyResponse).toBeUndefined();
|
|
142
157
|
expect(result.resolvedMember).not.toBeNull();
|
|
143
|
-
expect(result.resolvedMember!.
|
|
144
|
-
expect(result.resolvedMember!.
|
|
158
|
+
expect(result.resolvedMember!.status).toBe("active");
|
|
159
|
+
expect(result.resolvedMember!.policy).toBe("allow");
|
|
145
160
|
// Member came from the verdict, never the local contact store.
|
|
146
161
|
expect(findContactChannelCalls.length).toBe(0);
|
|
147
162
|
});
|
|
@@ -156,7 +171,8 @@ describe("enforceIngressAcl — verdict-sourced member resolution", () => {
|
|
|
156
171
|
);
|
|
157
172
|
|
|
158
173
|
expect(result.earlyResponse).toBeUndefined();
|
|
159
|
-
expect(result.resolvedMember
|
|
174
|
+
expect(result.resolvedMember).not.toBeNull();
|
|
175
|
+
expect(result.resolvedMember!.status).toBe("active");
|
|
160
176
|
expect(findContactChannelCalls.length).toBe(0);
|
|
161
177
|
});
|
|
162
178
|
});
|
|
@@ -249,6 +265,82 @@ describe("enforceIngressAcl — fail-closed on absent verdict", () => {
|
|
|
249
265
|
});
|
|
250
266
|
});
|
|
251
267
|
|
|
268
|
+
describe("enforceIngressAcl — fail-closed on resolutionFailed verdict", () => {
|
|
269
|
+
test("resolutionFailed verdict → not_a_member deny, does not flow to intercepts", async () => {
|
|
270
|
+
inviteTokenForTest = "iv_token123";
|
|
271
|
+
const result = await enforceIngressAcl(
|
|
272
|
+
makeParams({
|
|
273
|
+
sourceMetadata: withVerdict({
|
|
274
|
+
trustClass: "unknown",
|
|
275
|
+
canonicalSenderId: "sender-1",
|
|
276
|
+
resolutionFailed: true,
|
|
277
|
+
}),
|
|
278
|
+
effectiveAdmissionPolicy: "strangers",
|
|
279
|
+
}),
|
|
280
|
+
);
|
|
281
|
+
|
|
282
|
+
expect(result.earlyResponse).toBeDefined();
|
|
283
|
+
expect(result.earlyResponse!.reason).toBe("not_a_member");
|
|
284
|
+
expect(result.resolvedMember).toBeNull();
|
|
285
|
+
// Distinct from a stranger: no invite redemption, onboarding, or
|
|
286
|
+
// guardian notification fires.
|
|
287
|
+
expect(result.earlyResponse!.inviteRedemption).toBeUndefined();
|
|
288
|
+
expect(deliverReplyCalls.length).toBe(0);
|
|
289
|
+
expect(accessRequestCalls.length).toBe(0);
|
|
290
|
+
expect(findContactChannelCalls.length).toBe(0);
|
|
291
|
+
});
|
|
292
|
+
|
|
293
|
+
test("real unknown stranger (no resolutionFailed) still redeems via intercept", async () => {
|
|
294
|
+
inviteTokenForTest = "iv_token123";
|
|
295
|
+
const result = await enforceIngressAcl(
|
|
296
|
+
makeParams({
|
|
297
|
+
sourceMetadata: withVerdict({
|
|
298
|
+
trustClass: "unknown",
|
|
299
|
+
canonicalSenderId: "sender-1",
|
|
300
|
+
}),
|
|
301
|
+
}),
|
|
302
|
+
);
|
|
303
|
+
|
|
304
|
+
expect(result.earlyResponse!.inviteRedemption).toBe("redeemed");
|
|
305
|
+
expect(result.resolvedMember).toBeNull();
|
|
306
|
+
});
|
|
307
|
+
});
|
|
308
|
+
|
|
309
|
+
describe("enforceIngressAcl — deny copy names the gateway guardian", () => {
|
|
310
|
+
test("non-member deny reply uses the guardian displayName from the gateway list", async () => {
|
|
311
|
+
guardianDeliveryList = [
|
|
312
|
+
{
|
|
313
|
+
channelType: "vellum",
|
|
314
|
+
contactId: "c-1",
|
|
315
|
+
principalId: "p-anchor",
|
|
316
|
+
displayName: "Alice Guardian",
|
|
317
|
+
address: "p-anchor",
|
|
318
|
+
status: "active",
|
|
319
|
+
},
|
|
320
|
+
];
|
|
321
|
+
|
|
322
|
+
const result = await enforceIngressAcl(
|
|
323
|
+
makeParams({
|
|
324
|
+
sourceMetadata: withVerdict({
|
|
325
|
+
trustClass: "unknown",
|
|
326
|
+
canonicalSenderId: "stranger-1",
|
|
327
|
+
}),
|
|
328
|
+
}),
|
|
329
|
+
);
|
|
330
|
+
|
|
331
|
+
expect(result.earlyResponse!.reason).toBe("not_a_member");
|
|
332
|
+
const denyReply = deliverReplyCalls.find((c) =>
|
|
333
|
+
String((c.payload as { text?: string }).text ?? "").includes(
|
|
334
|
+
"tried talking to me",
|
|
335
|
+
),
|
|
336
|
+
);
|
|
337
|
+
expect(denyReply).toBeDefined();
|
|
338
|
+
expect((denyReply!.payload as { text: string }).text).toContain(
|
|
339
|
+
"Alice Guardian",
|
|
340
|
+
);
|
|
341
|
+
});
|
|
342
|
+
});
|
|
343
|
+
|
|
252
344
|
describe("enforceIngressAcl — fail-closed on malformed member verdict", () => {
|
|
253
345
|
test("member identity + unknown policy → not_a_member deny even under strangers", async () => {
|
|
254
346
|
const result = await enforceIngressAcl(
|
|
@@ -7,7 +7,7 @@ import type { AdmissionPolicy, SourceMetadata } from "@vellumai/gateway-client";
|
|
|
7
7
|
|
|
8
8
|
import { isInviteCodeRedemptionEnabled } from "../../../channels/config.js";
|
|
9
9
|
import type { ChannelId } from "../../../channels/types.js";
|
|
10
|
-
import {
|
|
10
|
+
import { getGuardianDelivery } from "../../../contacts/guardian-delivery-reader.js";
|
|
11
11
|
import { channelStatusToMemberStatus } from "../../../contacts/member-status.js";
|
|
12
12
|
import type {
|
|
13
13
|
ContactChannel,
|
|
@@ -25,6 +25,7 @@ import { getLogger } from "../../../util/logger.js";
|
|
|
25
25
|
import { truncate } from "../../../util/truncate.js";
|
|
26
26
|
import { hashVoiceCode } from "../../../util/voice-code.js";
|
|
27
27
|
import { notifyGuardianOfAccessRequest } from "../../access-request-helper.js";
|
|
28
|
+
import { resolveAnchoredGuardian } from "../../anchored-guardian.js";
|
|
28
29
|
import { getInviteAdapterRegistry } from "../../channel-invite-transport.js";
|
|
29
30
|
import {
|
|
30
31
|
createOutboundSession,
|
|
@@ -38,7 +39,8 @@ import {
|
|
|
38
39
|
redeemInviteByCode,
|
|
39
40
|
} from "../../invite-redemption-service.js";
|
|
40
41
|
import { getInviteRedemptionReply } from "../../invite-redemption-templates.js";
|
|
41
|
-
import {
|
|
42
|
+
import type { VerdictMember } from "../../trust-verdict-consumer.js";
|
|
43
|
+
import { verdictMemberFromVerdict } from "../../trust-verdict-consumer.js";
|
|
42
44
|
|
|
43
45
|
const log = getLogger("runtime-http");
|
|
44
46
|
|
|
@@ -46,28 +48,20 @@ const log = getLogger("runtime-http");
|
|
|
46
48
|
* Resolve the guardian's display name for use in requester-facing messages.
|
|
47
49
|
*
|
|
48
50
|
* Uses the assistant's anchored vellum principal to validate the guardian
|
|
49
|
-
*
|
|
50
|
-
* This prevents stale or cross-assistant
|
|
51
|
+
* binding, matching the same strategy used by `notifyGuardianOfAccessRequest`.
|
|
52
|
+
* This prevents stale or cross-assistant bindings from leaking a wrong name.
|
|
53
|
+
* Cosmetic copy, not an admission decision, so a null gateway list degrades
|
|
54
|
+
* gracefully to the default reference.
|
|
51
55
|
*/
|
|
52
|
-
function resolveGuardianLabel(sourceChannel: ChannelId): string {
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
// matches the assistant's anchor.
|
|
62
|
-
const sourceGuardian = findGuardianForChannel(sourceChannel);
|
|
63
|
-
if (
|
|
64
|
-
sourceGuardian &&
|
|
65
|
-
sourceGuardian.contact.principalId === anchoredPrincipalId
|
|
66
|
-
) {
|
|
67
|
-
return resolveGuardianName(sourceGuardian.contact.displayName);
|
|
68
|
-
}
|
|
69
|
-
|
|
70
|
-
return resolveGuardianName(vellumGuardian.contact.displayName);
|
|
56
|
+
async function resolveGuardianLabel(sourceChannel: ChannelId): Promise<string> {
|
|
57
|
+
// Cosmetic copy, not an admission decision: no local-store fallback, and a
|
|
58
|
+
// missing anchor principal degrades to the default reference.
|
|
59
|
+
const anchored = resolveAnchoredGuardian({
|
|
60
|
+
guardians: await getGuardianDelivery(),
|
|
61
|
+
sourceChannel,
|
|
62
|
+
requireAnchorPrincipal: true,
|
|
63
|
+
});
|
|
64
|
+
return resolveGuardianName(anchored?.displayName);
|
|
71
65
|
}
|
|
72
66
|
|
|
73
67
|
// ---------------------------------------------------------------------------
|
|
@@ -109,7 +103,7 @@ export type ResolvedMember = {
|
|
|
109
103
|
};
|
|
110
104
|
|
|
111
105
|
export interface AclResult {
|
|
112
|
-
resolvedMember:
|
|
106
|
+
resolvedMember: VerdictMember | null;
|
|
113
107
|
/** When set, the caller must return this response immediately. */
|
|
114
108
|
earlyResponse?: Record<string, unknown>;
|
|
115
109
|
/**
|
|
@@ -173,9 +167,27 @@ export async function enforceIngressAcl(
|
|
|
173
167
|
};
|
|
174
168
|
}
|
|
175
169
|
|
|
170
|
+
// Gateway attempted resolution but failed (DB error) → fail-closed deny,
|
|
171
|
+
// distinct from an absent verdict and from a real stranger. TEXT does not
|
|
172
|
+
// fall back to local ACL reads; the sender can retry.
|
|
173
|
+
if (verdict.resolutionFailed === true) {
|
|
174
|
+
log.warn(
|
|
175
|
+
{ sourceChannel, externalUserId: canonicalSenderId },
|
|
176
|
+
"Ingress ACL: gateway trust resolution failed, denying fail-closed",
|
|
177
|
+
);
|
|
178
|
+
return {
|
|
179
|
+
resolvedMember: null,
|
|
180
|
+
earlyResponse: {
|
|
181
|
+
accepted: true,
|
|
182
|
+
denied: true,
|
|
183
|
+
reason: "not_a_member",
|
|
184
|
+
},
|
|
185
|
+
};
|
|
186
|
+
}
|
|
187
|
+
|
|
176
188
|
// Member resolved from the gateway verdict (ACL + identity only); null for a
|
|
177
189
|
// stranger verdict, which falls through to the non-member intercepts.
|
|
178
|
-
const resolvedMember:
|
|
190
|
+
const resolvedMember: VerdictMember | null = verdictMemberFromVerdict(verdict);
|
|
179
191
|
|
|
180
192
|
// A verdict carrying member identity but no resolvable member
|
|
181
193
|
// (malformed/unknown ACL) fails closed, not treated as a stranger.
|
|
@@ -329,7 +341,7 @@ export async function enforceIngressAcl(
|
|
|
329
341
|
if (slackVerifyResult.initiated) {
|
|
330
342
|
// Still notify the guardian about the access attempt
|
|
331
343
|
try {
|
|
332
|
-
notifyGuardianOfAccessRequest({
|
|
344
|
+
await notifyGuardianOfAccessRequest({
|
|
333
345
|
canonicalAssistantId,
|
|
334
346
|
sourceChannel,
|
|
335
347
|
conversationExternalId,
|
|
@@ -369,7 +381,7 @@ export async function enforceIngressAcl(
|
|
|
369
381
|
try {
|
|
370
382
|
await deliverChannelReply(dmCallbackUrl, {
|
|
371
383
|
chatId: senderUserId,
|
|
372
|
-
text: `I don't recognize you yet! I've let ${resolveGuardianLabel(sourceChannel)} know you're trying to reach me. They'll need to share a 6-digit verification code with you — ask them directly if you know them. Once you have the code, reply here with it.`,
|
|
384
|
+
text: `I don't recognize you yet! I've let ${await resolveGuardianLabel(sourceChannel)} know you're trying to reach me. They'll need to share a 6-digit verification code with you — ask them directly if you know them. Once you have the code, reply here with it.`,
|
|
373
385
|
assistantId,
|
|
374
386
|
});
|
|
375
387
|
} catch (err) {
|
|
@@ -404,7 +416,7 @@ export async function enforceIngressAcl(
|
|
|
404
416
|
|
|
405
417
|
if (emailVerifyResult.initiated) {
|
|
406
418
|
try {
|
|
407
|
-
notifyGuardianOfAccessRequest({
|
|
419
|
+
await notifyGuardianOfAccessRequest({
|
|
408
420
|
canonicalAssistantId,
|
|
409
421
|
sourceChannel,
|
|
410
422
|
conversationExternalId,
|
|
@@ -443,7 +455,7 @@ export async function enforceIngressAcl(
|
|
|
443
455
|
// deduplication, canonical request creation, and notification emission.
|
|
444
456
|
let guardianNotified = false;
|
|
445
457
|
try {
|
|
446
|
-
const accessResult = notifyGuardianOfAccessRequest({
|
|
458
|
+
const accessResult = await notifyGuardianOfAccessRequest({
|
|
447
459
|
canonicalAssistantId,
|
|
448
460
|
sourceChannel,
|
|
449
461
|
conversationExternalId,
|
|
@@ -467,7 +479,7 @@ export async function enforceIngressAcl(
|
|
|
467
479
|
}
|
|
468
480
|
|
|
469
481
|
const replyText = guardianNotified
|
|
470
|
-
? `Hmm looks like you don't have access to talk to me. I'll let ${resolveGuardianLabel(sourceChannel)} know you tried talking to me and get back to you.`
|
|
482
|
+
? `Hmm looks like you don't have access to talk to me. I'll let ${await resolveGuardianLabel(sourceChannel)} know you tried talking to me and get back to you.`
|
|
471
483
|
: "Sorry, you haven't been approved to message this assistant.";
|
|
472
484
|
let replyDelivered = false;
|
|
473
485
|
if (replyCallbackUrl) {
|
|
@@ -507,8 +519,8 @@ export async function enforceIngressAcl(
|
|
|
507
519
|
}
|
|
508
520
|
|
|
509
521
|
if (resolvedMember) {
|
|
510
|
-
if (resolvedMember.
|
|
511
|
-
const isBlockedMember = resolvedMember.
|
|
522
|
+
if (resolvedMember.status !== "active") {
|
|
523
|
+
const isBlockedMember = resolvedMember.status === "blocked";
|
|
512
524
|
// Bootstrap commands must pass through for re-verifiable states
|
|
513
525
|
// (pending/revoked), but never for blocked members.
|
|
514
526
|
let denyInactiveMember = true;
|
|
@@ -529,7 +541,7 @@ export async function enforceIngressAcl(
|
|
|
529
541
|
log.info(
|
|
530
542
|
{
|
|
531
543
|
sourceChannel,
|
|
532
|
-
channelId: resolvedMember.
|
|
544
|
+
channelId: resolvedMember.channelId,
|
|
533
545
|
hasValidBootstrapSession: false,
|
|
534
546
|
},
|
|
535
547
|
"Ingress ACL: inactive member bootstrap bypass denied",
|
|
@@ -614,11 +626,11 @@ export async function enforceIngressAcl(
|
|
|
614
626
|
if (!isBlockedMember && denyInactiveMember) {
|
|
615
627
|
if (
|
|
616
628
|
(effectiveAdmissionPolicy === "strangers" &&
|
|
617
|
-
resolvedMember.
|
|
629
|
+
resolvedMember.status !== "revoked") ||
|
|
618
630
|
((effectiveAdmissionPolicy === "any_contact" ||
|
|
619
631
|
effectiveAdmissionPolicy === "guardian_only") &&
|
|
620
|
-
(resolvedMember.
|
|
621
|
-
resolvedMember.
|
|
632
|
+
(resolvedMember.status === "pending" ||
|
|
633
|
+
resolvedMember.status === "unverified"))
|
|
622
634
|
) {
|
|
623
635
|
denyInactiveMember = false;
|
|
624
636
|
}
|
|
@@ -628,8 +640,8 @@ export async function enforceIngressAcl(
|
|
|
628
640
|
log.info(
|
|
629
641
|
{
|
|
630
642
|
sourceChannel,
|
|
631
|
-
channelId: resolvedMember.
|
|
632
|
-
status: resolvedMember.
|
|
643
|
+
channelId: resolvedMember.channelId,
|
|
644
|
+
status: resolvedMember.status,
|
|
633
645
|
},
|
|
634
646
|
"Ingress ACL: member not active, denying",
|
|
635
647
|
);
|
|
@@ -639,7 +651,7 @@ export async function enforceIngressAcl(
|
|
|
639
651
|
// the guardian made an explicit decision to block them.
|
|
640
652
|
if (
|
|
641
653
|
sourceChannel === "slack" &&
|
|
642
|
-
resolvedMember.
|
|
654
|
+
resolvedMember.status !== "blocked" &&
|
|
643
655
|
(canonicalSenderId ?? rawSenderId)
|
|
644
656
|
) {
|
|
645
657
|
const slackVerifyResult = initiateSlackVerificationChallenge({
|
|
@@ -649,7 +661,7 @@ export async function enforceIngressAcl(
|
|
|
649
661
|
|
|
650
662
|
if (slackVerifyResult.initiated) {
|
|
651
663
|
try {
|
|
652
|
-
notifyGuardianOfAccessRequest({
|
|
664
|
+
await notifyGuardianOfAccessRequest({
|
|
653
665
|
canonicalAssistantId,
|
|
654
666
|
sourceChannel,
|
|
655
667
|
conversationExternalId,
|
|
@@ -657,7 +669,7 @@ export async function enforceIngressAcl(
|
|
|
657
669
|
actorDisplayName,
|
|
658
670
|
actorUsername,
|
|
659
671
|
previousMemberStatus: channelStatusToMemberStatus(
|
|
660
|
-
resolvedMember.
|
|
672
|
+
resolvedMember.status,
|
|
661
673
|
),
|
|
662
674
|
messagePreview: truncate(
|
|
663
675
|
trimmedContent,
|
|
@@ -688,7 +700,7 @@ export async function enforceIngressAcl(
|
|
|
688
700
|
try {
|
|
689
701
|
await deliverChannelReply(dmCallbackUrl, {
|
|
690
702
|
chatId: senderUserId,
|
|
691
|
-
text: `I don't recognize you yet! I've let ${resolveGuardianLabel(sourceChannel)} know you're trying to reach me. They'll need to share a 6-digit verification code with you — ask them directly if you know them. Once you have the code, reply here with it.`,
|
|
703
|
+
text: `I don't recognize you yet! I've let ${await resolveGuardianLabel(sourceChannel)} know you're trying to reach me. They'll need to share a 6-digit verification code with you — ask them directly if you know them. Once you have the code, reply here with it.`,
|
|
692
704
|
assistantId,
|
|
693
705
|
});
|
|
694
706
|
} catch (err) {
|
|
@@ -715,9 +727,9 @@ export async function enforceIngressAcl(
|
|
|
715
727
|
// re-approve. Blocked members are intentionally excluded — the
|
|
716
728
|
// guardian already made an explicit decision to block them.
|
|
717
729
|
let guardianNotified = false;
|
|
718
|
-
if (resolvedMember.
|
|
730
|
+
if (resolvedMember.status !== "blocked") {
|
|
719
731
|
try {
|
|
720
|
-
const accessResult = notifyGuardianOfAccessRequest({
|
|
732
|
+
const accessResult = await notifyGuardianOfAccessRequest({
|
|
721
733
|
canonicalAssistantId,
|
|
722
734
|
sourceChannel,
|
|
723
735
|
conversationExternalId,
|
|
@@ -725,7 +737,7 @@ export async function enforceIngressAcl(
|
|
|
725
737
|
actorDisplayName,
|
|
726
738
|
actorUsername,
|
|
727
739
|
previousMemberStatus: channelStatusToMemberStatus(
|
|
728
|
-
resolvedMember.
|
|
740
|
+
resolvedMember.status,
|
|
729
741
|
),
|
|
730
742
|
messagePreview: truncate(
|
|
731
743
|
trimmedContent,
|
|
@@ -745,7 +757,7 @@ export async function enforceIngressAcl(
|
|
|
745
757
|
}
|
|
746
758
|
|
|
747
759
|
const inactiveReplyText = guardianNotified
|
|
748
|
-
? `Hmm looks like you don't have access to talk to me. I'll let ${resolveGuardianLabel(sourceChannel)} know you tried talking to me and get back to you.`
|
|
760
|
+
? `Hmm looks like you don't have access to talk to me. I'll let ${await resolveGuardianLabel(sourceChannel)} know you tried talking to me and get back to you.`
|
|
749
761
|
: "Sorry, you haven't been approved to message this assistant.";
|
|
750
762
|
let inactiveReplyDelivered = false;
|
|
751
763
|
if (replyCallbackUrl) {
|
|
@@ -779,16 +791,16 @@ export async function enforceIngressAcl(
|
|
|
779
791
|
earlyResponse: {
|
|
780
792
|
accepted: true,
|
|
781
793
|
denied: true,
|
|
782
|
-
reason: `member_${channelStatusToMemberStatus(resolvedMember.
|
|
794
|
+
reason: `member_${channelStatusToMemberStatus(resolvedMember.status)}`,
|
|
783
795
|
...(!inactiveReplyDelivered && { replyText: inactiveReplyText }),
|
|
784
796
|
},
|
|
785
797
|
};
|
|
786
798
|
}
|
|
787
799
|
}
|
|
788
800
|
|
|
789
|
-
if (resolvedMember.
|
|
801
|
+
if (resolvedMember.policy === "deny") {
|
|
790
802
|
log.info(
|
|
791
|
-
{ sourceChannel, channelId: resolvedMember.
|
|
803
|
+
{ sourceChannel, channelId: resolvedMember.channelId },
|
|
792
804
|
"Ingress ACL: member policy deny",
|
|
793
805
|
);
|
|
794
806
|
const denyReplyText =
|
|
@@ -9,6 +9,10 @@
|
|
|
9
9
|
*/
|
|
10
10
|
import type { ChannelId, InterfaceId } from "../../../channels/types.js";
|
|
11
11
|
import { findGuardianForChannel } from "../../../contacts/contact-store.js";
|
|
12
|
+
import {
|
|
13
|
+
getGuardianDelivery,
|
|
14
|
+
guardianForChannel,
|
|
15
|
+
} from "../../../contacts/guardian-delivery-reader.js";
|
|
12
16
|
import type { ServerMessage } from "../../../daemon/message-protocol.js";
|
|
13
17
|
import type { TrustContext } from "../../../daemon/trust-context.js";
|
|
14
18
|
import {
|
|
@@ -960,10 +964,18 @@ function startTrustedContactApprovalNotifier(params: {
|
|
|
960
964
|
|
|
961
965
|
if (info && !globalNotifiedApprovalRequestIds.has(info.requestId)) {
|
|
962
966
|
globalNotifiedApprovalRequestIds.set(info.requestId, conversationId);
|
|
963
|
-
|
|
964
|
-
|
|
965
|
-
|
|
966
|
-
|
|
967
|
+
// Gateway-resolved guardian display name with the transitional
|
|
968
|
+
// local fallback on null/no-match (display-only). Removed in Combo 11.
|
|
969
|
+
const guardians = await getGuardianDelivery({
|
|
970
|
+
channelTypes: [sourceChannel],
|
|
971
|
+
});
|
|
972
|
+
const gatewayDisplayName = guardians
|
|
973
|
+
? guardianForChannel(guardians, sourceChannel)?.displayName
|
|
974
|
+
: undefined;
|
|
975
|
+
const displayName =
|
|
976
|
+
gatewayDisplayName ??
|
|
977
|
+
findGuardianForChannel(sourceChannel)?.contact.displayName;
|
|
978
|
+
const guardianName = resolveGuardianName(displayName);
|
|
967
979
|
const waitingText = `Waiting for ${guardianName}'s approval...`;
|
|
968
980
|
try {
|
|
969
981
|
await deliverChannelReply(replyCallbackUrl, {
|
|
@@ -13,8 +13,8 @@ import { storePayload } from "../../../memory/delivery-crud.js";
|
|
|
13
13
|
import { emitNotificationSignal } from "../../../notifications/emit-signal.js";
|
|
14
14
|
import { getLogger } from "../../../util/logger.js";
|
|
15
15
|
import { getGuardianBinding } from "../../channel-verification-service.js";
|
|
16
|
+
import type { VerdictMember } from "../../trust-verdict-consumer.js";
|
|
16
17
|
import { GUARDIAN_APPROVAL_TTL_MS } from "../channel-route-shared.js";
|
|
17
|
-
import type { ResolvedMember } from "./acl-enforcement.js";
|
|
18
18
|
|
|
19
19
|
const log = getLogger("runtime-http");
|
|
20
20
|
|
|
@@ -23,7 +23,7 @@ const log = getLogger("runtime-http");
|
|
|
23
23
|
// ---------------------------------------------------------------------------
|
|
24
24
|
|
|
25
25
|
export interface EscalationInterceptParams {
|
|
26
|
-
resolvedMember:
|
|
26
|
+
resolvedMember: VerdictMember | null;
|
|
27
27
|
canonicalAssistantId: string;
|
|
28
28
|
sourceChannel: ChannelId;
|
|
29
29
|
sourceInterface: InterfaceId;
|
|
@@ -49,9 +49,9 @@ export interface EscalationInterceptParams {
|
|
|
49
49
|
* Returns a Response if the escalation was handled (the pipeline should
|
|
50
50
|
* short-circuit), or null to continue the pipeline.
|
|
51
51
|
*/
|
|
52
|
-
export function handleEscalationIntercept(
|
|
52
|
+
export async function handleEscalationIntercept(
|
|
53
53
|
params: EscalationInterceptParams,
|
|
54
|
-
): Record<string, unknown> | null {
|
|
54
|
+
): Promise<Record<string, unknown> | null> {
|
|
55
55
|
const {
|
|
56
56
|
resolvedMember,
|
|
57
57
|
canonicalAssistantId,
|
|
@@ -72,15 +72,15 @@ export function handleEscalationIntercept(
|
|
|
72
72
|
rawSenderId,
|
|
73
73
|
} = params;
|
|
74
74
|
|
|
75
|
-
if (resolvedMember?.
|
|
75
|
+
if (resolvedMember?.policy !== "escalate") {
|
|
76
76
|
return null;
|
|
77
77
|
}
|
|
78
78
|
|
|
79
|
-
const binding = getGuardianBinding(canonicalAssistantId, sourceChannel);
|
|
79
|
+
const binding = await getGuardianBinding(canonicalAssistantId, sourceChannel);
|
|
80
80
|
if (!binding) {
|
|
81
81
|
// Fail-closed: can't escalate without a guardian to route to
|
|
82
82
|
log.info(
|
|
83
|
-
{ sourceChannel, channelId: resolvedMember.
|
|
83
|
+
{ sourceChannel, channelId: resolvedMember.channelId },
|
|
84
84
|
"Ingress ACL: escalate policy but no guardian binding, denying",
|
|
85
85
|
);
|
|
86
86
|
return {
|
|
@@ -4,7 +4,9 @@ import { afterEach, beforeEach, describe, expect, mock, test } from "bun:test";
|
|
|
4
4
|
// Mocks — must be set up before importing the module under test
|
|
5
5
|
// ---------------------------------------------------------------------------
|
|
6
6
|
|
|
7
|
-
|
|
7
|
+
// Gateway guardian-delivery list: empty = unbound, one entry = bound,
|
|
8
|
+
// null = gateway unreachable.
|
|
9
|
+
let mockGuardianList: Array<Record<string, unknown>> | null = [];
|
|
8
10
|
let mockActiveSession: Record<string, unknown> | null = null;
|
|
9
11
|
let mockSessionResult = {
|
|
10
12
|
sessionId: "sess-1",
|
|
@@ -20,8 +22,14 @@ let deliverChannelReplyCalls: unknown[][] = [];
|
|
|
20
22
|
let emitNotificationSignalCalls: unknown[] = [];
|
|
21
23
|
let messageIdCounter = 0;
|
|
22
24
|
|
|
23
|
-
mock.module("../../../contacts/
|
|
24
|
-
|
|
25
|
+
mock.module("../../../contacts/guardian-delivery-reader.js", () => ({
|
|
26
|
+
// Existence guard reads fresh (uncached) — only this variant is stubbed.
|
|
27
|
+
getGuardianDeliveryFresh: () => Promise.resolve(mockGuardianList),
|
|
28
|
+
guardianForChannel: (
|
|
29
|
+
list: Array<{ channelType: string; status: string }>,
|
|
30
|
+
channelType: string,
|
|
31
|
+
) =>
|
|
32
|
+
list.find((g) => g.channelType === channelType && g.status === "active"),
|
|
25
33
|
}));
|
|
26
34
|
|
|
27
35
|
mock.module("../../channel-verification-service.js", () => ({
|
|
@@ -96,7 +104,7 @@ function makeParams(
|
|
|
96
104
|
|
|
97
105
|
describe("handleGuardianActivationIntercept", () => {
|
|
98
106
|
beforeEach(() => {
|
|
99
|
-
|
|
107
|
+
mockGuardianList = [];
|
|
100
108
|
mockActiveSession = null;
|
|
101
109
|
mockSessionResult = {
|
|
102
110
|
sessionId: "sess-1",
|
|
@@ -155,10 +163,15 @@ describe("handleGuardianActivationIntercept", () => {
|
|
|
155
163
|
});
|
|
156
164
|
|
|
157
165
|
test("bare /start with existing guardian returns null", async () => {
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
166
|
+
mockGuardianList = [{ channelType: "telegram", status: "active" }];
|
|
167
|
+
|
|
168
|
+
const result = await handleGuardianActivationIntercept(makeParams());
|
|
169
|
+
expect(result).toBeNull();
|
|
170
|
+
expect(createOutboundSessionCalls).toHaveLength(0);
|
|
171
|
+
});
|
|
172
|
+
|
|
173
|
+
test("null guardian list (gateway unreachable) does NOT auto-start", async () => {
|
|
174
|
+
mockGuardianList = null;
|
|
162
175
|
|
|
163
176
|
const result = await handleGuardianActivationIntercept(makeParams());
|
|
164
177
|
expect(result).toBeNull();
|
|
@@ -9,7 +9,10 @@
|
|
|
9
9
|
* the guardian binding, and sends a success reply.
|
|
10
10
|
*/
|
|
11
11
|
import type { ChannelId } from "../../../channels/types.js";
|
|
12
|
-
import {
|
|
12
|
+
import {
|
|
13
|
+
getGuardianDeliveryFresh,
|
|
14
|
+
guardianForChannel,
|
|
15
|
+
} from "../../../contacts/guardian-delivery-reader.js";
|
|
13
16
|
import { emitNotificationSignal } from "../../../notifications/emit-signal.js";
|
|
14
17
|
import { getLogger } from "../../../util/logger.js";
|
|
15
18
|
import {
|
|
@@ -88,8 +91,16 @@ export async function handleGuardianActivationIntercept(
|
|
|
88
91
|
// Only proceed for Telegram (can be extended later)
|
|
89
92
|
if (sourceChannel !== "telegram") return null;
|
|
90
93
|
|
|
91
|
-
// If a guardian already exists for this channel, continue to normal flow
|
|
92
|
-
|
|
94
|
+
// If a guardian already exists for this channel, continue to normal flow.
|
|
95
|
+
// Null-list (gateway unreachable) is treated as guardian-present so a
|
|
96
|
+
// transient miss does NOT spuriously auto-start verification.
|
|
97
|
+
// Read fresh: gateway-side binding writes don't invalidate the daemon cache.
|
|
98
|
+
const guardianList = await getGuardianDeliveryFresh({
|
|
99
|
+
channelTypes: [sourceChannel],
|
|
100
|
+
});
|
|
101
|
+
if (guardianList === null || guardianForChannel(guardianList, sourceChannel)) {
|
|
102
|
+
return null;
|
|
103
|
+
}
|
|
93
104
|
|
|
94
105
|
// Can't bind a session without sender identity
|
|
95
106
|
if (!rawSenderId) return null;
|
|
@@ -102,6 +102,7 @@ import { ROUTES as OAUTH_COMMANDS_ROUTES } from "./oauth-commands-routes.js";
|
|
|
102
102
|
import { ROUTES as OAUTH_CONNECT_ROUTES } from "./oauth-connect-routes.js";
|
|
103
103
|
import { ROUTES as OAUTH_LIFECYCLE_ROUTES } from "./oauth-lifecycle-routes.js";
|
|
104
104
|
import { ROUTES as OAUTH_PROVIDERS_ROUTES } from "./oauth-providers.js";
|
|
105
|
+
import { ROUTES as ONBOARDING_CHECKIN_ROUTES } from "./onboarding-checkin-routes.js";
|
|
105
106
|
import { ROUTES as PLATFORM_ROUTES } from "./platform-routes.js";
|
|
106
107
|
import { ROUTES as PLAYGROUND_ROUTES } from "./playground/index.js";
|
|
107
108
|
import { ROUTES as PLUGINS_ROUTES } from "./plugins-routes.js";
|
|
@@ -234,6 +235,7 @@ export const ROUTES: RouteDefinition[] = [
|
|
|
234
235
|
...OAUTH_LIFECYCLE_ROUTES,
|
|
235
236
|
...OAUTH_COMMANDS_ROUTES,
|
|
236
237
|
...OAUTH_PROVIDERS_ROUTES,
|
|
238
|
+
...ONBOARDING_CHECKIN_ROUTES,
|
|
237
239
|
...PLATFORM_ROUTES,
|
|
238
240
|
...PLAYGROUND_ROUTES,
|
|
239
241
|
...PLUGINS_ROUTES,
|