@vellumai/assistant 0.11.4 → 0.11.5-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/AGENTS.md +2 -2
- package/ARCHITECTURE.md +29 -4
- package/README.md +1 -1
- package/docs/architecture/memory.md +17 -5
- package/docs/architecture/security.md +96 -152
- package/docs/guardian-request-flow.md +13 -2
- package/node_modules/@vellumai/ces-client/node_modules/@vellumai/service-contracts/package.json +1 -0
- package/node_modules/@vellumai/ces-client/node_modules/@vellumai/service-contracts/src/__tests__/url-normalization.test.ts +135 -0
- package/node_modules/@vellumai/ces-client/node_modules/@vellumai/service-contracts/src/index.ts +1 -0
- package/node_modules/@vellumai/ces-client/node_modules/@vellumai/service-contracts/src/url-normalization.ts +107 -0
- package/node_modules/@vellumai/gateway-client/node_modules/@vellumai/service-contracts/package.json +1 -0
- package/node_modules/@vellumai/gateway-client/node_modules/@vellumai/service-contracts/src/__tests__/url-normalization.test.ts +135 -0
- package/node_modules/@vellumai/gateway-client/node_modules/@vellumai/service-contracts/src/index.ts +1 -0
- package/node_modules/@vellumai/gateway-client/node_modules/@vellumai/service-contracts/src/url-normalization.ts +107 -0
- package/node_modules/@vellumai/gateway-client/src/gateway-ipc-contracts.ts +166 -1
- package/node_modules/@vellumai/gateway-client/src/http-delivery.ts +7 -14
- package/node_modules/@vellumai/gateway-client/src/index.ts +2 -0
- package/node_modules/@vellumai/gateway-client/src/outbound-contract.ts +29 -31
- package/node_modules/@vellumai/service-contracts/package.json +1 -0
- package/node_modules/@vellumai/service-contracts/src/__tests__/url-normalization.test.ts +135 -0
- package/node_modules/@vellumai/service-contracts/src/index.ts +1 -0
- package/node_modules/@vellumai/service-contracts/src/url-normalization.ts +107 -0
- package/openapi.yaml +193 -3
- package/package.json +1 -1
- package/src/__tests__/anthropic-provider.test.ts +10 -3
- package/src/__tests__/approval-interception-trust-gates.test.ts +40 -0
- package/src/__tests__/background-workers-disk-pressure.test.ts +1 -1
- package/src/__tests__/channel-approval-routes.test.ts +4 -0
- package/src/__tests__/channel-inbound-disk-pressure.test.ts +5 -6
- package/src/__tests__/channel-reply-delivery.test.ts +26 -13
- package/src/__tests__/checker.test.ts +20 -314
- package/src/__tests__/cli-memory-v2-reembed-skills.test.ts +6 -2
- package/src/__tests__/client-os-metadata-persistence.test.ts +16 -0
- package/src/__tests__/compactor-retained-image-validation.test.ts +142 -0
- package/src/__tests__/config-loader-backfill.test.ts +19 -10
- package/src/__tests__/container-cpu-sampler.test.ts +153 -0
- package/src/__tests__/context-search-memory-v2-source.test.ts +0 -1
- package/src/__tests__/conversation-attachments.test.ts +0 -1
- package/src/__tests__/conversation-error.test.ts +17 -0
- package/src/__tests__/conversation-lifecycle.test.ts +20 -26
- package/src/__tests__/conversation-pairing.test.ts +186 -0
- package/src/__tests__/conversation-runtime-assembly.test.ts +21 -2
- package/src/__tests__/credential-broker-server-use.test.ts +24 -5
- package/src/__tests__/credential-routes.test.ts +22 -3
- package/src/__tests__/credential-security-invariants.test.ts +2 -1
- package/src/__tests__/daemon-credential-client.test.ts +88 -0
- package/src/__tests__/default-plugin-names-guard.test.ts +12 -1
- package/src/__tests__/dm-backfill.test.ts +63 -0
- package/src/__tests__/dynamic-skill-background-guard.test.ts +0 -2
- package/src/__tests__/edit-propagation.test.ts +107 -4
- package/src/__tests__/events-client-registration.test.ts +28 -0
- package/src/__tests__/file-write-tool.test.ts +4 -2
- package/src/__tests__/guardian-prompt-notice-privacy.test.ts +133 -0
- package/src/__tests__/guardian-verify-setup-skill-regression.test.ts +143 -97
- package/src/__tests__/helpers/gateway-classify-mock.ts +27 -6
- package/src/__tests__/host-proxy-interface.test.ts +11 -1
- package/src/__tests__/host-shell-tool.test.ts +23 -4
- package/src/__tests__/image-conversion.test.ts +143 -1
- package/src/__tests__/inline-skill-load-permissions.test.ts +47 -36
- package/src/__tests__/input-repairs.test.ts +207 -0
- package/src/__tests__/live-workspace-guard.test.ts +75 -0
- package/src/__tests__/managed-profile-guard.test.ts +4 -2
- package/src/__tests__/mcp-abort-signal.test.ts +1 -1
- package/src/__tests__/mcp-client-auth.test.ts +1 -1
- package/src/__tests__/mcp-tool-annotations-risk.test.ts +1 -1
- package/src/__tests__/media-resolve-image-validation.test.ts +310 -0
- package/src/__tests__/mtime-cache.test.ts +2 -0
- package/src/__tests__/notification-vellum-adapter.test.ts +124 -1
- package/src/__tests__/openai-provider.test.ts +22 -0
- package/src/__tests__/personal-memory-auth-bypass.test.ts +22 -6
- package/src/__tests__/platform-bash-auto-approve.test.ts +0 -4
- package/src/__tests__/platform.test.ts +16 -1
- package/src/__tests__/plugin-api-resolve-credential.test.ts +22 -13
- package/src/__tests__/plugin-api-shim.test.ts +5 -0
- package/src/__tests__/plugin-api-store-credential.test.ts +268 -0
- package/src/__tests__/plugin-config-data-migration.test.ts +8 -1
- package/src/__tests__/plugin-disabled-state.test.ts +2 -0
- package/src/__tests__/plugin-effective-enabled-set.test.ts +55 -3
- package/src/__tests__/plugin-execution-context.test.ts +73 -0
- package/src/__tests__/plugin-import-boundary-guard.test.ts +0 -1
- package/src/__tests__/plugin-import-boundary-reverse-guard.test.ts +0 -1
- package/src/__tests__/reaction-persistence.test.ts +200 -7
- package/src/__tests__/require-fresh-approval.test.ts +0 -4
- package/src/__tests__/resource-pressure-guard.test.ts +428 -0
- package/src/__tests__/resource-pressure-routes.test.ts +113 -0
- package/src/__tests__/risk-classification-boundary-guard.test.ts +115 -0
- package/src/__tests__/run-conversation-turn-persistence.test.ts +27 -1
- package/src/__tests__/secret-routes-platform-proxy.test.ts +7 -0
- package/src/__tests__/skill-tool-factory.test.ts +161 -0
- package/src/__tests__/tool-execution-pipeline.benchmark.test.ts +1 -1
- package/src/__tests__/tool-executor-lifecycle-events.test.ts +132 -5
- package/src/__tests__/tool-executor.test.ts +63 -38
- package/src/__tests__/tool-policy.test.ts +97 -1
- package/src/__tests__/trusted-contact-inline-approval-integration.test.ts +7 -4
- package/src/__tests__/user-plugin-loader.test.ts +2 -0
- package/src/__tests__/validate-input.test.ts +243 -7
- package/src/__tests__/verification-control-plane-policy.test.ts +0 -2
- package/src/__tests__/voice-scoped-grant-consumer.test.ts +2 -0
- package/src/__tests__/voice-session-bridge.test.ts +42 -0
- package/src/acp/__tests__/acp-claude-oauth.test.ts +97 -29
- package/src/acp/__tests__/prepare-agent-env.test.ts +376 -153
- package/src/acp/acp-claude-oauth.ts +38 -21
- package/src/acp/prepare-agent-env.ts +146 -55
- package/src/agent/loop-tool-dedup.test.ts +161 -0
- package/src/agent/loop.ts +68 -22
- package/src/api/constants/app-tools.ts +34 -0
- package/src/api/events/resource-pressure-status-changed.ts +53 -0
- package/src/api/index.ts +19 -0
- package/src/api/responses/resource-pressure-status.ts +25 -0
- package/src/approvals/guardian-channel-delivery.ts +70 -6
- package/src/approvals/guardian-request-resolvers.ts +32 -66
- package/src/calls/__tests__/voice-session-bridge.test.ts +114 -0
- package/src/calls/voice-session-bridge.ts +60 -6
- package/src/channels/__tests__/message-audience.test.ts +49 -0
- package/src/channels/__tests__/types.test.ts +17 -3
- package/src/channels/message-audience.ts +40 -0
- package/src/channels/types.ts +16 -12
- package/src/cli/__tests__/catalog-search-help.test.ts +50 -0
- package/src/cli/commands/__tests__/channel-verification-sessions.test.ts +31 -0
- package/src/cli/commands/__tests__/conversations-search.test.ts +289 -0
- package/src/cli/commands/__tests__/conversations-slack.test.ts +1 -0
- package/src/cli/commands/__tests__/inference-providers.test.ts +68 -0
- package/src/cli/commands/__tests__/keys.test.ts +89 -8
- package/src/cli/commands/__tests__/plugins.test.ts +57 -1
- package/src/cli/commands/channel-verification-sessions.help.ts +11 -9
- package/src/cli/commands/channel-verification-sessions.ts +11 -21
- package/src/cli/commands/conversations.help.ts +34 -0
- package/src/cli/commands/conversations.ts +125 -0
- package/src/cli/commands/inference-providers.ts +9 -4
- package/src/cli/commands/inference.help.ts +9 -4
- package/src/cli/commands/keys.help.ts +13 -1
- package/src/cli/commands/keys.ts +26 -15
- package/src/cli/commands/memory/__tests__/memory-v2.test.ts +57 -7
- package/src/cli/commands/memory/index.help.ts +41 -27
- package/src/cli/commands/memory/index.ts +2 -0
- package/src/cli/commands/memory/memory-v2.ts +58 -54
- package/src/cli/commands/memory/memory-validate.ts +18 -0
- package/src/cli/commands/monitoring.ts +1 -0
- package/src/cli/commands/plugins.help.ts +28 -31
- package/src/cli/commands/plugins.ts +10 -0
- package/src/cli/commands/skills.help.ts +13 -16
- package/src/cli/commands/trust.ts +3 -14
- package/src/cli/lib/__tests__/install-from-github.test.ts +38 -0
- package/src/cli/lib/__tests__/merge-plugin-tree.test.ts +34 -0
- package/src/cli/lib/__tests__/plugin-surfaces.test.ts +32 -1
- package/src/cli/lib/__tests__/toggle-plugin.test.ts +2 -1
- package/src/cli/lib/__tests__/upgrade-plugin.test.ts +64 -0
- package/src/cli/lib/bundled-marketplace.json +13 -0
- package/src/cli/lib/daemon-credential-client.ts +25 -3
- package/src/cli/lib/install-from-github.ts +35 -24
- package/src/cli/lib/merge-plugin-tree.ts +22 -4
- package/src/cli/lib/plugin-surfaces.ts +27 -0
- package/src/config/__tests__/balanced-model-experiment.test.ts +7 -7
- package/src/config/__tests__/default-profile-catalog.test.ts +3 -3
- package/src/config/default-profile-catalog.ts +5 -6
- package/src/config/feature-flag-registry.json +32 -32
- package/src/config/webhook-routing.ts +8 -0
- package/src/context/compactor.ts +31 -2
- package/src/daemon/__tests__/provider-rejection-log-fields.test.ts +272 -0
- package/src/daemon/conversation-agent-loop-handlers.ts +4 -1
- package/src/daemon/conversation-error.ts +13 -0
- package/src/daemon/conversation-messaging.ts +22 -2
- package/src/daemon/conversation-process.ts +11 -0
- package/src/daemon/conversation-store.ts +10 -0
- package/src/daemon/conversation-surfaces.ts +21 -8
- package/src/daemon/conversation.ts +12 -10
- package/src/daemon/lifecycle.ts +6 -2
- package/src/daemon/message-types/conversations.ts +5 -7
- package/src/daemon/provider-rejection-log-fields.ts +124 -0
- package/src/daemon/resource-pressure-guard-lifecycle.ts +66 -0
- package/src/daemon/resource-pressure-guard.ts +387 -0
- package/src/daemon/shutdown-handlers.ts +2 -0
- package/src/daemon/startup-error.ts +16 -0
- package/src/daemon/trust-context.ts +14 -10
- package/src/daemon/unsendable-image-notice.ts +76 -0
- package/src/hooks/registry.ts +65 -51
- package/src/ipc/__tests__/socket-path.test.ts +15 -7
- package/src/ipc/gateway-client.test.ts +1 -1
- package/src/ipc/gateway-client.ts +14 -21
- package/src/ipc/socket-cleanup.ts +2 -11
- package/src/mcp/__tests__/mcp-auth-orchestrator.test.ts +1 -1
- package/src/messaging/provider-message-metadata.ts +128 -0
- package/src/messaging/providers/__tests__/transport-dispatch.test.ts +116 -68
- package/src/messaging/providers/channel-transport.ts +90 -13
- package/src/messaging/providers/discord/send.ts +16 -0
- package/src/messaging/providers/discord/transport.ts +11 -1
- package/src/messaging/providers/index.ts +109 -16
- package/src/messaging/providers/slack/message-metadata.ts +45 -0
- package/src/messaging/providers/slack/render-transcript.ts +1 -4
- package/src/messaging/providers/slack/send.test.ts +6 -9
- package/src/messaging/providers/slack/send.ts +40 -28
- package/src/messaging/providers/slack/transport.ts +22 -26
- package/src/messaging/providers/telegram-bot/send.test.ts +2 -8
- package/src/messaging/providers/telegram-bot/transport.ts +3 -6
- package/src/messaging/read-provider-metadata.test.ts +157 -0
- package/src/messaging/read-provider-metadata.ts +51 -0
- package/src/notifications/__tests__/assistant-reply-producer.test.ts +94 -0
- package/src/notifications/__tests__/edit-notification.test.ts +174 -2
- package/src/notifications/__tests__/home-feed-side-effect.test.ts +536 -4
- package/src/notifications/adapters/macos.ts +55 -0
- package/src/notifications/adapters/slack.ts +10 -5
- package/src/notifications/assistant-reply-producer.ts +37 -4
- package/src/notifications/conversation-pairing.ts +111 -21
- package/src/notifications/edit-notification.ts +38 -8
- package/src/notifications/emit-signal.ts +12 -14
- package/src/notifications/home-feed-side-effect.ts +218 -15
- package/src/notifications/types.ts +5 -0
- package/src/permissions/AGENTS.md +16 -0
- package/src/permissions/checker.test.ts +75 -122
- package/src/permissions/checker.ts +44 -560
- package/src/permissions/confirmation-guardian-request.test.ts +28 -3
- package/src/permissions/confirmation-guardian-request.ts +5 -1
- package/src/persistence/conversation-crud.ts +23 -20
- package/src/persistence/conversation-queries.ts +14 -1
- package/src/persistence/conversation-types.ts +22 -0
- package/src/persistence/delivery-crud.ts +122 -15
- package/src/plugin-api/__tests__/import-graph-partial-mock.test.ts +67 -0
- package/src/plugin-api/conversation-turn.ts +27 -0
- package/src/plugin-api/credential-scope.test.ts +15 -0
- package/src/plugin-api/credential-scope.ts +14 -0
- package/src/plugin-api/index.ts +19 -1
- package/src/plugin-api/resolve-credential.ts +15 -10
- package/src/plugin-api/store-credential.ts +148 -0
- package/src/plugin-api/system-card.ts +36 -0
- package/src/plugin-api/vision-support.test.ts +82 -0
- package/src/plugin-api/vision-support.ts +14 -2
- package/src/plugins/defaults/image-fallback/__tests__/image-fallback.test.ts +361 -110
- package/src/plugins/defaults/image-fallback/__tests__/vision-recovery.test.ts +4 -0
- package/src/plugins/defaults/image-fallback/hooks/user-prompt-submit.ts +58 -0
- package/src/plugins/defaults/image-fallback/src/caption-blocks.ts +64 -23
- package/src/plugins/defaults/image-recovery/detect.ts +24 -1
- package/src/plugins/defaults/main.ts +15 -0
- package/src/plugins/defaults/memory/AGENTS.md +11 -3
- package/src/plugins/defaults/memory/__tests__/memory-tier-boundary-guard.test.ts +0 -1
- package/src/plugins/defaults/memory/graph-topology/pending-buffer.ts +9 -12
- package/src/plugins/defaults/memory/memory-retrospective-prompt.ts +1 -1
- package/src/plugins/defaults/memory/src/memory-v2-routes.ts +12 -10
- package/src/plugins/defaults/memory/substrate/__tests__/consolidation-job.test.ts +114 -0
- package/src/plugins/defaults/memory/substrate/__tests__/consolidation-prompt-flag-gating-guard.test.ts +8 -0
- package/src/plugins/defaults/memory/substrate/__tests__/edge-index.test.ts +0 -30
- package/src/plugins/defaults/memory/substrate/__tests__/ingest.test.ts +73 -1
- package/src/plugins/defaults/memory/substrate/__tests__/page-index.test.ts +37 -0
- package/src/plugins/defaults/memory/substrate/__tests__/page-links.test.ts +208 -0
- package/src/plugins/defaults/memory/substrate/__tests__/prompts-consolidation.test.ts +158 -1
- package/src/plugins/defaults/memory/substrate/__tests__/static-context.test.ts +1 -61
- package/src/plugins/defaults/memory/substrate/consolidation-job.ts +68 -6
- package/src/plugins/defaults/memory/substrate/edge-index.ts +0 -31
- package/src/plugins/defaults/memory/substrate/ingest.ts +59 -0
- package/src/plugins/defaults/memory/substrate/page-index.ts +35 -8
- package/src/plugins/defaults/memory/substrate/page-links.ts +133 -0
- package/src/plugins/defaults/memory/substrate/page-store.ts +10 -0
- package/src/plugins/defaults/memory/substrate/prompts/consolidation.ts +124 -34
- package/src/plugins/defaults/memory/substrate/static-context.ts +0 -29
- package/src/plugins/defaults/memory/v3/__tests__/shadow-plugin.test.ts +3 -1
- package/src/plugins/defaults/memory/v3/card.ts +9 -9
- package/src/plugins/defaults/memory/v3/core-set.test.ts +7 -0
- package/src/plugins/defaults/memory/v3/core-set.ts +5 -1
- package/src/plugins/defaults/memory/v3/edge.ts +13 -57
- package/src/plugins/mtime-cache.ts +1 -1
- package/src/plugins/pipeline.ts +14 -7
- package/src/plugins/plugin-execution-context.ts +35 -11
- package/src/plugins/plugin-tree-walk.ts +63 -4
- package/src/providers/anthropic/__tests__/pause-turn-continuation.test.ts +170 -0
- package/src/providers/anthropic/client.ts +332 -241
- package/src/providers/connection-resolution.ts +2 -1
- package/src/providers/content-blocks.ts +22 -0
- package/src/providers/gemini/client.ts +5 -2
- package/src/providers/inference/__tests__/adapter-factory-litellm.test.ts +47 -1
- package/src/providers/inference/__tests__/adapter-factory-ollama.test.ts +83 -0
- package/src/providers/inference/__tests__/adapter-factory-openai-compatible.test.ts +98 -1
- package/src/providers/inference/__tests__/base-url-route-validation.test.ts +38 -0
- package/src/providers/inference/__tests__/base-url-security.test.ts +10 -0
- package/src/providers/inference/__tests__/connections-ollama.test.ts +90 -0
- package/src/providers/inference/__tests__/missing-credential-guard.test.ts +117 -0
- package/src/providers/inference/adapter-factory.ts +33 -2
- package/src/providers/inference/auth.ts +13 -2
- package/src/providers/inference/credential-usage.ts +37 -0
- package/src/providers/inference/missing-credential-guard.ts +110 -0
- package/src/providers/inference/resolve-auth.ts +7 -7
- package/src/providers/media-resolve.ts +176 -15
- package/src/providers/openai/__tests__/chat-completions-provider-reasoning.test.ts +576 -46
- package/src/providers/openai/__tests__/tool-choice-mapping.test.ts +125 -2
- package/src/providers/openai/chat-completions-provider.ts +322 -42
- package/src/routes/route-host-protocol.ts +7 -0
- package/src/routes/worker.ts +31 -10
- package/src/runtime/AGENTS.md +35 -0
- package/src/runtime/__tests__/runtime-http-port-collision.test.ts +131 -0
- package/src/runtime/__tests__/web-presence.test.ts +234 -0
- package/src/runtime/assistant-event-hub.ts +38 -0
- package/src/runtime/channel-reply-delivery.ts +54 -31
- package/src/runtime/channel-retry-sweep.ts +6 -4
- package/src/runtime/effective-capabilities.test.ts +19 -0
- package/src/runtime/effective-capabilities.ts +25 -0
- package/src/runtime/guardian-reply-router.ts +6 -2
- package/src/runtime/http-errors.ts +1 -0
- package/src/runtime/http-server.ts +20 -5
- package/src/runtime/routes/__tests__/client-routes.test.ts +150 -0
- package/src/runtime/routes/__tests__/conversation-list-routes.test.ts +133 -0
- package/src/runtime/routes/__tests__/credential-delete-in-use.test.ts +155 -0
- package/src/runtime/routes/__tests__/inference-provider-connection-routes.test.ts +52 -1
- package/src/runtime/routes/__tests__/monitoring-routes.test.ts +1 -0
- package/src/runtime/routes/__tests__/pending-interactions-route.test.ts +175 -0
- package/src/runtime/routes/__tests__/plugins-routes.test.ts +88 -35
- package/src/runtime/routes/__tests__/surface-action-routes.test.ts +55 -6
- package/src/runtime/routes/__tests__/system-card-turn-termination.test.ts +107 -0
- package/src/runtime/routes/__tests__/user-route-dispatcher-host.test.ts +52 -1
- package/src/runtime/routes/__tests__/user-route-dispatcher.test.ts +75 -4
- package/src/runtime/routes/approval-routes.ts +37 -4
- package/src/runtime/routes/approval-strategies/guardian-text-engine-strategy.ts +16 -22
- package/src/runtime/routes/canned-message-complete.ts +68 -29
- package/src/runtime/routes/client-routes.ts +113 -0
- package/src/runtime/routes/conversation-list-routes.ts +22 -7
- package/src/runtime/routes/conversation-management-routes.ts +15 -9
- package/src/runtime/routes/conversation-routes.ts +36 -22
- package/src/runtime/routes/credential-in-use.ts +85 -0
- package/src/runtime/routes/credential-routes.ts +55 -85
- package/src/runtime/routes/guardian-approval-interception.ts +22 -22
- package/src/runtime/routes/guardian-approval-reply-helpers.ts +14 -12
- package/src/runtime/routes/identity-routes.ts +5 -121
- package/src/runtime/routes/inbound-message-handler.ts +71 -27
- package/src/runtime/routes/inbound-stages/acl-enforcement.ts +16 -17
- package/src/runtime/routes/inbound-stages/background-dispatch.test.ts +77 -50
- package/src/runtime/routes/inbound-stages/background-dispatch.ts +61 -97
- package/src/runtime/routes/inbound-stages/edit-intercept.ts +101 -77
- package/src/runtime/routes/inbound-stages/guardian-reply-intercept.ts +17 -8
- package/src/runtime/routes/inbound-stages/reaction-intercept.test.ts +91 -7
- package/src/runtime/routes/inbound-stages/reaction-intercept.ts +72 -70
- package/src/runtime/routes/index.ts +2 -0
- package/src/runtime/routes/inference-provider-connection-routes.ts +9 -7
- package/src/runtime/routes/monitoring-routes.ts +9 -1
- package/src/runtime/routes/plugins-routes.ts +17 -5
- package/src/runtime/routes/resource-pressure-routes.ts +22 -0
- package/src/runtime/routes/secret-routes.ts +39 -8
- package/src/runtime/routes/settings-routes.ts +6 -6
- package/src/runtime/routes/surface-action-routes.ts +20 -19
- package/src/runtime/routes/user-route-dispatcher.ts +31 -2
- package/src/runtime/routes/user-route-resolution.ts +18 -1
- package/src/runtime/slack-reply-session.test.ts +23 -13
- package/src/runtime/slack-reply-session.ts +38 -55
- package/src/runtime/web-presence.ts +90 -0
- package/src/skills/validate-input.ts +177 -40
- package/src/subagent/__tests__/consult-context-gating.test.ts +6 -10
- package/src/subagent/consult-context.ts +7 -23
- package/src/tools/credentials/broker.ts +24 -79
- package/src/tools/credentials/ref-parse.ts +35 -0
- package/src/tools/credentials/resolve.ts +4 -10
- package/src/tools/credentials/store.ts +168 -0
- package/src/tools/credentials/tool-policy.ts +67 -0
- package/src/tools/executor.ts +53 -15
- package/src/tools/network/__tests__/web-search.test.ts +41 -1
- package/src/tools/network/url-safety.ts +9 -16
- package/src/tools/network/web-search.ts +21 -3
- package/src/tools/permission-checker.ts +43 -52
- package/src/tools/schema-transforms.ts +40 -5
- package/src/tools/shared/input-repairs.ts +160 -0
- package/src/tools/skills/skill-tool-factory.ts +18 -4
- package/src/tools/subagent/spawn.ts +0 -1
- package/src/tools/tool-approval-handler.ts +16 -10
- package/src/tools/tool-types.ts +26 -13
- package/src/tools/types.ts +6 -6
- package/src/util/__tests__/cgroup-memory.test.ts +3 -0
- package/src/util/cgroup-memory.ts +3 -0
- package/src/util/container-cpu-sampler.ts +250 -0
- package/src/util/image-conversion.ts +178 -14
- package/src/util/platform.ts +93 -10
- package/src/permissions/ipc-risk-types.ts +0 -143
- package/src/permissions/risk-types.ts +0 -76
|
@@ -691,17 +691,16 @@ async function handleRetryLastAssistantTurn({
|
|
|
691
691
|
const conversation = await getOrCreateConversationInstance(conversationId);
|
|
692
692
|
touchConversation(conversationId);
|
|
693
693
|
|
|
694
|
-
//
|
|
695
|
-
//
|
|
696
|
-
//
|
|
697
|
-
//
|
|
694
|
+
// Resolved before the busy gate so the awaits stay outside the
|
|
695
|
+
// check-then-claim window below, but bound only once the claim succeeds: a
|
|
696
|
+
// retry that loses the race is rejected, and a rejected request must not
|
|
697
|
+
// repoint the conversation's actor at its requester.
|
|
698
698
|
const actorPrincipalId = headers?.["x-vellum-actor-principal-id"];
|
|
699
|
-
|
|
700
|
-
|
|
701
|
-
|
|
702
|
-
}),
|
|
699
|
+
const retryTrustContext = await resolveVellumActorTrustContext(
|
|
700
|
+
actorPrincipalId,
|
|
701
|
+
{ healResetDrift: true },
|
|
703
702
|
);
|
|
704
|
-
|
|
703
|
+
const retrySourceActorPrincipalId =
|
|
705
704
|
await resolveActorPrincipalIdForLocalGuardian(actorPrincipalId);
|
|
706
705
|
|
|
707
706
|
// Synchronous check-then-claim (no await between them) so a concurrent
|
|
@@ -714,6 +713,13 @@ async function handleRetryLastAssistantTurn({
|
|
|
714
713
|
}
|
|
715
714
|
conversation.setProcessing(true);
|
|
716
715
|
|
|
716
|
+
// Bind the requesting actor's trust for the turn we just claimed. A freshly
|
|
717
|
+
// hydrated conversation has no trust context, and `loadFromDb` under an
|
|
718
|
+
// unset context filters guardian-provenance history — the re-run would see
|
|
719
|
+
// an empty transcript.
|
|
720
|
+
conversation.setTrustContext(retryTrustContext);
|
|
721
|
+
conversation.currentTurnSourceActorPrincipalId = retrySourceActorPrincipalId;
|
|
722
|
+
|
|
717
723
|
const requestId = uuidv7();
|
|
718
724
|
const abortController = new AbortController();
|
|
719
725
|
let discarded: ReturnType<typeof discardLastAssistantDisplayTurn>;
|
|
@@ -1236,12 +1236,10 @@ export async function handleListMessages({
|
|
|
1236
1236
|
|
|
1237
1237
|
const alignedContentOrder = aligned.rewriteContentOrder(contentOrder);
|
|
1238
1238
|
|
|
1239
|
-
// Use sentAt (actual event time) for the display timestamp when
|
|
1240
|
-
// falling back to createdAt (persistence time).
|
|
1241
|
-
//
|
|
1242
|
-
//
|
|
1243
|
-
// benign: it may return slightly extra data on a page boundary but never
|
|
1244
|
-
// loses messages.
|
|
1239
|
+
// Use sentAt (actual event time) for the display timestamp when
|
|
1240
|
+
// available, falling back to createdAt (persistence time). Pagination
|
|
1241
|
+
// is unaffected: the cursor is `oldestTimestamp`, built from
|
|
1242
|
+
// `createdAt` on both ends.
|
|
1245
1243
|
const displayTimestamp = m.sentAt ?? m.createdAt;
|
|
1246
1244
|
return {
|
|
1247
1245
|
id: m.id ?? "",
|
|
@@ -1849,6 +1847,11 @@ export async function handleSendMessage(
|
|
|
1849
1847
|
|
|
1850
1848
|
// Resolve guardian context from the AuthContext's actorPrincipalId via the
|
|
1851
1849
|
// gateway guardian binding: a vellum principal is the guardian or nobody.
|
|
1850
|
+
//
|
|
1851
|
+
// Resolved into a local; the conversation's slot is stamped only where this
|
|
1852
|
+
// request commits to running a turn, since that write is what supplies the
|
|
1853
|
+
// acting actor for a run and must not fire for a send that merely queues.
|
|
1854
|
+
let resolvedTrustCtx: TrustContext;
|
|
1852
1855
|
if (actorPrincipalId) {
|
|
1853
1856
|
// Dev bypass (HTTP auth disabled): the synthetic "dev-bypass" principal
|
|
1854
1857
|
// won't match any guardian binding. Resolve the real guardian principal and
|
|
@@ -1874,7 +1877,7 @@ export async function handleSendMessage(
|
|
|
1874
1877
|
trustCtx = healed;
|
|
1875
1878
|
}
|
|
1876
1879
|
}
|
|
1877
|
-
|
|
1880
|
+
resolvedTrustCtx = trustCtx;
|
|
1878
1881
|
} else {
|
|
1879
1882
|
let trustCtx = withSourceChannel(
|
|
1880
1883
|
sourceChannel,
|
|
@@ -1907,22 +1910,19 @@ export async function handleSendMessage(
|
|
|
1907
1910
|
);
|
|
1908
1911
|
}
|
|
1909
1912
|
}
|
|
1910
|
-
|
|
1913
|
+
resolvedTrustCtx = trustCtx;
|
|
1911
1914
|
}
|
|
1912
1915
|
} else {
|
|
1913
1916
|
// Service principals (svc_gateway) or tokens without an actor ID
|
|
1914
1917
|
// get a minimal guardian context so downstream code has something.
|
|
1915
|
-
|
|
1918
|
+
resolvedTrustCtx = { trustClass: "guardian", sourceChannel };
|
|
1916
1919
|
}
|
|
1917
1920
|
|
|
1918
|
-
// The trust this request's turn runs under
|
|
1919
|
-
//
|
|
1920
|
-
//
|
|
1921
|
-
//
|
|
1922
|
-
|
|
1923
|
-
// pointer elevation, the voice bridge), and the loop would otherwise
|
|
1924
|
-
// re-read it and run this turn as whoever wrote last.
|
|
1925
|
-
const turnTrustContext = conversation.trustContext;
|
|
1921
|
+
// The trust this request's turn runs under: the sender resolved above,
|
|
1922
|
+
// never a read of the shared slot. Between here and the loop that slot is
|
|
1923
|
+
// writable by paths that do not own this turn (channel ingress for another
|
|
1924
|
+
// actor, live-voice hydration, pointer elevation, the voice bridge).
|
|
1925
|
+
const turnTrustContext = resolvedTrustCtx;
|
|
1926
1926
|
|
|
1927
1927
|
const isInteractive = isInteractiveInterface(sourceInterface);
|
|
1928
1928
|
// Translate the dev-bypass actor principal to the real guardian principal
|
|
@@ -2053,12 +2053,15 @@ export async function handleSendMessage(
|
|
|
2053
2053
|
requestId: uuidv7(),
|
|
2054
2054
|
metadata: greetingMeta,
|
|
2055
2055
|
clientMessageId,
|
|
2056
|
+
// This path answers and returns without starting a turn, so it never
|
|
2057
|
+
// reaches the stamp below; name the sender on the row directly.
|
|
2058
|
+
trustContext: resolvedTrustCtx,
|
|
2056
2059
|
...(clientOs ? { requestClientOs: clientOs } : {}),
|
|
2057
2060
|
});
|
|
2058
2061
|
|
|
2059
2062
|
const conversationId = mapping.conversationId;
|
|
2060
2063
|
const channelMeta = buildChannelMetadata(sourceChannel, sourceInterface, {
|
|
2061
|
-
trustContext:
|
|
2064
|
+
trustContext: resolvedTrustCtx,
|
|
2062
2065
|
});
|
|
2063
2066
|
|
|
2064
2067
|
const assistantMsg = createAssistantMessage(cannedGreeting);
|
|
@@ -2165,10 +2168,9 @@ export async function handleSendMessage(
|
|
|
2165
2168
|
|
|
2166
2169
|
// Resolve the verified actor's external user ID and principal for inline
|
|
2167
2170
|
// approval routing from the conversation's guardian context.
|
|
2168
|
-
const verifiedActorExternalUserId =
|
|
2169
|
-
conversation.trustContext?.guardianExternalUserId;
|
|
2171
|
+
const verifiedActorExternalUserId = resolvedTrustCtx.guardianExternalUserId;
|
|
2170
2172
|
const verifiedActorPrincipalId =
|
|
2171
|
-
|
|
2173
|
+
resolvedTrustCtx.guardianPrincipalId ?? undefined;
|
|
2172
2174
|
|
|
2173
2175
|
// Try to consume the message as a guardian approval/rejection reply.
|
|
2174
2176
|
// On failure, degrade to the existing queue/auto-deny path rather than
|
|
@@ -2242,6 +2244,10 @@ export async function handleSendMessage(
|
|
|
2242
2244
|
sourceActorPrincipalId,
|
|
2243
2245
|
transport,
|
|
2244
2246
|
clientMessageId,
|
|
2247
|
+
// The sender's own trust, so the drain runs this message as the actor
|
|
2248
|
+
// who sent it rather than as whoever the slot happens to hold when the
|
|
2249
|
+
// queue is worked.
|
|
2250
|
+
trustContext: resolvedTrustCtx,
|
|
2245
2251
|
});
|
|
2246
2252
|
if (enqueueResult.rejected) {
|
|
2247
2253
|
return new RouteResponse(
|
|
@@ -2340,7 +2346,15 @@ export async function handleSendMessage(
|
|
|
2340
2346
|
// matching in-memory pending interaction (e.g. prompter timeouts).
|
|
2341
2347
|
await expireOrphanedGuardianRequests(mapping.conversationId);
|
|
2342
2348
|
|
|
2343
|
-
// Conversation is idle — persist and fire agent loop immediately
|
|
2349
|
+
// Conversation is idle — persist and fire agent loop immediately.
|
|
2350
|
+
//
|
|
2351
|
+
// Stamping the sender here rather than at resolution is what keeps the two
|
|
2352
|
+
// in step: the slot hydrates and scopes the turn started just below
|
|
2353
|
+
// (`ensureActorScopedHistory`, persisted provenance, the loop's own trust),
|
|
2354
|
+
// so it must name whoever this request is about to run as. A request that
|
|
2355
|
+
// queues instead returns above without stamping — it is not starting a run,
|
|
2356
|
+
// and its actor rides the queue item to the drain.
|
|
2357
|
+
conversation.setTrustContext(resolvedTrustCtx);
|
|
2344
2358
|
conversation.setTurnChannelContext({
|
|
2345
2359
|
userMessageChannel: sourceChannel,
|
|
2346
2360
|
assistantMessageChannel: sourceChannel,
|
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Shared in-use guard for the credential-delete surfaces
|
|
3
|
+
* (`POST /v1/credentials/delete` and `DELETE /v1/secrets`).
|
|
4
|
+
*
|
|
5
|
+
* A credential removed while an LLM connection resolves its auth through it
|
|
6
|
+
* takes that connection offline, so such a delete is refused until the caller
|
|
7
|
+
* confirms it with `force`. Both surfaces run the same guard, so a protection on
|
|
8
|
+
* only one of them is one a client can route around.
|
|
9
|
+
*/
|
|
10
|
+
|
|
11
|
+
import { findConnectionsUsingCredential } from "../../providers/inference/credential-usage.js";
|
|
12
|
+
import { clearConnectionProviderCache } from "../../providers/registry.js";
|
|
13
|
+
import { getLogger } from "../../util/logger.js";
|
|
14
|
+
import { RouteError } from "./errors.js";
|
|
15
|
+
|
|
16
|
+
const log = getLogger("runtime/routes/credential-in-use");
|
|
17
|
+
|
|
18
|
+
/**
|
|
19
|
+
* A credential is still referenced by at least one provider connection.
|
|
20
|
+
*
|
|
21
|
+
* Carries the dependent connection names in both the message and
|
|
22
|
+
* `details.connections`, so a client can name them in its confirmation
|
|
23
|
+
* prompt, and a distinct `code` so it can tell this refusal apart from an
|
|
24
|
+
* ordinary bad request and offer the forced retry.
|
|
25
|
+
*/
|
|
26
|
+
export class CredentialInUseError extends RouteError {
|
|
27
|
+
constructor(
|
|
28
|
+
readonly connections: string[],
|
|
29
|
+
message: string,
|
|
30
|
+
) {
|
|
31
|
+
super(message, "CREDENTIAL_IN_USE", 400, { connections });
|
|
32
|
+
this.name = "CredentialInUseError";
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
/**
|
|
37
|
+
* Refuse to delete `credentialAccount` while provider connections resolve
|
|
38
|
+
* their auth through it, unless `force` is set. Returns the affected
|
|
39
|
+
* connection names so the caller can invalidate what depended on them.
|
|
40
|
+
*
|
|
41
|
+
* The credential itself is never logged beyond its `credential/{service}/
|
|
42
|
+
* {field}` account name, which holds no secret material.
|
|
43
|
+
*/
|
|
44
|
+
export function assertCredentialNotInUse(
|
|
45
|
+
credentialAccount: string,
|
|
46
|
+
force: boolean,
|
|
47
|
+
): string[] {
|
|
48
|
+
const connections = findConnectionsUsingCredential(credentialAccount);
|
|
49
|
+
if (connections.length === 0) {
|
|
50
|
+
return connections;
|
|
51
|
+
}
|
|
52
|
+
if (!force) {
|
|
53
|
+
const list = connections.map((name) => `"${name}"`).join(", ");
|
|
54
|
+
throw new CredentialInUseError(
|
|
55
|
+
connections,
|
|
56
|
+
`Credential ${credentialAccount} is in use by ${
|
|
57
|
+
connections.length === 1 ? "connection" : "connections"
|
|
58
|
+
} ${list}. ${
|
|
59
|
+
connections.length === 1 ? "It" : "They"
|
|
60
|
+
} will stop working without it. Delete it anyway to continue.`,
|
|
61
|
+
);
|
|
62
|
+
}
|
|
63
|
+
log.warn(
|
|
64
|
+
{ credential: credentialAccount, connections },
|
|
65
|
+
"Force-deleting a credential that provider connections depend on. Those connections can no longer dispatch until a new credential is added",
|
|
66
|
+
);
|
|
67
|
+
return connections;
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
/**
|
|
71
|
+
* Drop cached adapters built around a deleted credential so the next dispatch
|
|
72
|
+
* re-resolves auth and reports the missing credential instead of replaying a
|
|
73
|
+
* cached adapter holding the removed key.
|
|
74
|
+
*/
|
|
75
|
+
export function invalidateConnectionsAfterCredentialDelete(
|
|
76
|
+
affectedConnections: string[],
|
|
77
|
+
): void {
|
|
78
|
+
if (affectedConnections.length === 0) {
|
|
79
|
+
return;
|
|
80
|
+
}
|
|
81
|
+
// The cache is keyed by connection + model + provider, so entries for one
|
|
82
|
+
// connection are not addressable individually; clearing it costs the next
|
|
83
|
+
// dispatch per connection one credential re-read.
|
|
84
|
+
clearConnectionProviderCache();
|
|
85
|
+
}
|
|
@@ -16,22 +16,13 @@
|
|
|
16
16
|
|
|
17
17
|
import { z } from "zod";
|
|
18
18
|
|
|
19
|
-
import {
|
|
20
|
-
ACP_SERVICE,
|
|
21
|
-
AcpCredentialFormatError,
|
|
22
|
-
assertAcpCredentialFormat,
|
|
23
|
-
} from "../../acp/acp-credentials.js";
|
|
19
|
+
import { AcpCredentialFormatError } from "../../acp/acp-credentials.js";
|
|
24
20
|
import { isAssistantFeatureFlagEnabled } from "../../config/assistant-feature-flags.js";
|
|
25
21
|
import {
|
|
26
22
|
fetchManagedCatalog,
|
|
27
23
|
type ManagedCredentialDescriptor,
|
|
28
24
|
} from "../../credential-execution/managed-catalog.js";
|
|
29
25
|
import { buildForChatSentinel } from "../../daemon/chat-credential-redaction.js";
|
|
30
|
-
import {
|
|
31
|
-
isNonSecretPlatformField,
|
|
32
|
-
scrubStoredCredentialFromTranscripts,
|
|
33
|
-
} from "../../daemon/credential-transcript-scrub.js";
|
|
34
|
-
import { syncManualTokenConnection } from "../../oauth/manual-token-connection.js";
|
|
35
26
|
import {
|
|
36
27
|
disconnectOAuthProvider,
|
|
37
28
|
getConnectionByProvider,
|
|
@@ -39,14 +30,11 @@ import {
|
|
|
39
30
|
type OAuthConnectionRow,
|
|
40
31
|
} from "../../oauth/oauth-store.js";
|
|
41
32
|
import { credentialKey } from "../../security/credential-key.js";
|
|
42
|
-
import { normalizeSecretValue } from "../../security/secret-normalize.js";
|
|
43
33
|
import {
|
|
44
34
|
deleteSecureKeyAsync,
|
|
45
35
|
getActiveBackendInfoAsync,
|
|
46
|
-
getActiveBackendName,
|
|
47
36
|
getSecureKeyAsync,
|
|
48
37
|
getSecureKeyResultAsync,
|
|
49
|
-
setSecureKeyAsync,
|
|
50
38
|
} from "../../security/secure-keys.js";
|
|
51
39
|
import {
|
|
52
40
|
assertMetadataWritable,
|
|
@@ -55,19 +43,24 @@ import {
|
|
|
55
43
|
getCredentialMetadata,
|
|
56
44
|
getCredentialMetadataById,
|
|
57
45
|
listCredentialMetadata,
|
|
58
|
-
upsertCredentialMetadata,
|
|
59
46
|
} from "../../tools/credentials/metadata-store.js";
|
|
60
47
|
import type { CredentialInjectionTemplate } from "../../tools/credentials/policy-types.js";
|
|
61
|
-
import {
|
|
48
|
+
import {
|
|
49
|
+
CredentialStorageError,
|
|
50
|
+
InvalidCredentialInputError,
|
|
51
|
+
storeCredentialValue,
|
|
52
|
+
} from "../../tools/credentials/store.js";
|
|
62
53
|
import { ACTOR_PRINCIPALS } from "../auth/route-policy.js";
|
|
63
54
|
import { recordForChatMint } from "../for-chat-mint-registry.js";
|
|
64
55
|
import { recordRevealSuccess } from "../reveal-success-registry.js";
|
|
56
|
+
import {
|
|
57
|
+
assertCredentialNotInUse,
|
|
58
|
+
invalidateConnectionsAfterCredentialDelete,
|
|
59
|
+
} from "./credential-in-use.js";
|
|
65
60
|
import { InjectionTemplateSchema } from "./credential-prompt-routes.js";
|
|
66
61
|
import { BadRequestError, InternalError } from "./errors.js";
|
|
67
62
|
import type { RouteDefinition, RouteHandlerArgs } from "./types.js";
|
|
68
63
|
|
|
69
|
-
const log = getLogger("credential-routes");
|
|
70
|
-
|
|
71
64
|
// ---------------------------------------------------------------------------
|
|
72
65
|
// Shared helpers
|
|
73
66
|
// ---------------------------------------------------------------------------
|
|
@@ -458,74 +451,29 @@ async function handleCredentialsSet({ body }: RouteHandlerArgs) {
|
|
|
458
451
|
throw new BadRequestError("value is required");
|
|
459
452
|
}
|
|
460
453
|
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
}
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
|
|
454
|
+
try {
|
|
455
|
+
return await storeCredentialValue({
|
|
456
|
+
service,
|
|
457
|
+
field,
|
|
458
|
+
value,
|
|
459
|
+
alias: label,
|
|
460
|
+
usageDescription: description,
|
|
461
|
+
allowedTools,
|
|
462
|
+
allowedDomains,
|
|
463
|
+
injectionTemplates,
|
|
464
|
+
});
|
|
465
|
+
} catch (err) {
|
|
466
|
+
if (
|
|
467
|
+
err instanceof InvalidCredentialInputError ||
|
|
468
|
+
err instanceof AcpCredentialFormatError
|
|
469
|
+
) {
|
|
470
|
+
throw new BadRequestError(err.message);
|
|
476
471
|
}
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
assertMetadataWritable();
|
|
480
|
-
|
|
481
|
-
const key = credentialKey(service, field);
|
|
482
|
-
const stored = await setSecureKeyAsync(key, normalizedValue);
|
|
483
|
-
if (!stored) {
|
|
484
|
-
throw new InternalError(
|
|
485
|
-
`Failed to store credential in secure storage (backend: ${getActiveBackendName()})`,
|
|
486
|
-
);
|
|
487
|
-
}
|
|
488
|
-
|
|
489
|
-
// The stored plaintext may already sit in recent transcripts: the user
|
|
490
|
-
// message that pasted it, the persisted tool_use input, the tool result
|
|
491
|
-
// echoing the command. This route is the scrub seam — not
|
|
492
|
-
// setSecureKeyAsync, which also fires on OAuth refresh rotations and MCP
|
|
493
|
-
// header writes whose values never transited a transcript. The scrub runs
|
|
494
|
-
// immediately after the secure-store write, BEFORE the metadata upsert and
|
|
495
|
-
// connection sync: those side effects can throw (oauth-store work), and a
|
|
496
|
-
// stored-but-unscrubbed secret must not depend on them succeeding. The
|
|
497
|
-
// credential IS stored at this point; the scrub is best-effort hygiene and
|
|
498
|
-
// must stay invisible to the caller.
|
|
499
|
-
if (!isNonSecretPlatformField(service, field)) {
|
|
500
|
-
try {
|
|
501
|
-
const scrubbed =
|
|
502
|
-
await scrubStoredCredentialFromTranscripts(normalizedValue);
|
|
503
|
-
log.info(
|
|
504
|
-
{ service, field, ...scrubbed },
|
|
505
|
-
"Credential stored; scrubbed value from recent transcripts",
|
|
506
|
-
);
|
|
507
|
-
} catch (err) {
|
|
508
|
-
log.warn(
|
|
509
|
-
{ err, service, field },
|
|
510
|
-
"Credential stored, but transcript scrub failed",
|
|
511
|
-
);
|
|
472
|
+
if (err instanceof CredentialStorageError) {
|
|
473
|
+
throw new InternalError(err.message);
|
|
512
474
|
}
|
|
475
|
+
throw err;
|
|
513
476
|
}
|
|
514
|
-
|
|
515
|
-
const metadata = upsertCredentialMetadata(service, field, {
|
|
516
|
-
alias: label,
|
|
517
|
-
usageDescription: description,
|
|
518
|
-
allowedTools,
|
|
519
|
-
allowedDomains,
|
|
520
|
-
injectionTemplates,
|
|
521
|
-
});
|
|
522
|
-
await syncManualTokenConnection(service);
|
|
523
|
-
|
|
524
|
-
return {
|
|
525
|
-
credentialId: metadata.credentialId,
|
|
526
|
-
service,
|
|
527
|
-
field,
|
|
528
|
-
};
|
|
529
477
|
}
|
|
530
478
|
|
|
531
479
|
async function handleCredentialsDelete({ body }: RouteHandlerArgs) {
|
|
@@ -533,9 +481,10 @@ async function handleCredentialsDelete({ body }: RouteHandlerArgs) {
|
|
|
533
481
|
throw new BadRequestError("Request body is required");
|
|
534
482
|
}
|
|
535
483
|
|
|
536
|
-
const { service, field } = body as {
|
|
484
|
+
const { service, field, force } = body as {
|
|
537
485
|
service?: string;
|
|
538
486
|
field?: string;
|
|
487
|
+
force?: boolean;
|
|
539
488
|
};
|
|
540
489
|
|
|
541
490
|
if (!service || typeof service !== "string") {
|
|
@@ -557,6 +506,7 @@ async function handleCredentialsDelete({ body }: RouteHandlerArgs) {
|
|
|
557
506
|
service === "slack_channel" && field === "user_token";
|
|
558
507
|
|
|
559
508
|
const key = credentialKey(service, field);
|
|
509
|
+
const affectedConnections = assertCredentialNotInUse(key, force === true);
|
|
560
510
|
const existing = await getSecureKeyAsync(key);
|
|
561
511
|
const deleteResult =
|
|
562
512
|
existing != null ? await deleteSecureKeyAsync(key) : "not-found";
|
|
@@ -567,6 +517,11 @@ async function handleCredentialsDelete({ body }: RouteHandlerArgs) {
|
|
|
567
517
|
);
|
|
568
518
|
}
|
|
569
519
|
|
|
520
|
+
// Cached provider adapters hold the deleted credential, so they are dropped
|
|
521
|
+
// as soon as the secure vault no longer has it: a later failure in this
|
|
522
|
+
// handler must not leave them dispatching with a credential that is gone.
|
|
523
|
+
invalidateConnectionsAfterCredentialDelete(affectedConnections);
|
|
524
|
+
|
|
570
525
|
const metadataDeleted = deleteCredentialMetadata(service, field);
|
|
571
526
|
|
|
572
527
|
// Clean up OAuth connection (best-effort).
|
|
@@ -593,7 +548,9 @@ async function handleCredentialsDelete({ body }: RouteHandlerArgs) {
|
|
|
593
548
|
throw new BadRequestError("Credential not found");
|
|
594
549
|
}
|
|
595
550
|
|
|
596
|
-
|
|
551
|
+
invalidateConnectionsAfterCredentialDelete(affectedConnections);
|
|
552
|
+
|
|
553
|
+
return { service, field, affectedConnections };
|
|
597
554
|
}
|
|
598
555
|
|
|
599
556
|
async function handleCredentialsStatus() {
|
|
@@ -746,15 +703,28 @@ export const ROUTES: RouteDefinition[] = [
|
|
|
746
703
|
},
|
|
747
704
|
summary: "Delete a credential",
|
|
748
705
|
description:
|
|
749
|
-
"Remove a secret, its metadata, and any associated OAuth connection from the vault."
|
|
706
|
+
"Remove a secret, its metadata, and any associated OAuth connection from the vault. " +
|
|
707
|
+
"Refused with CREDENTIAL_IN_USE while an LLM provider connection resolves its auth " +
|
|
708
|
+
"through the credential, unless `force` is set.",
|
|
750
709
|
tags: ["credentials"],
|
|
751
710
|
requestBody: z.object({
|
|
752
711
|
service: z.string().describe("Service namespace"),
|
|
753
712
|
field: z.string().describe("Field name"),
|
|
713
|
+
force: z
|
|
714
|
+
.boolean()
|
|
715
|
+
.optional()
|
|
716
|
+
.describe(
|
|
717
|
+
"Delete even when provider connections depend on the credential",
|
|
718
|
+
),
|
|
754
719
|
}),
|
|
755
720
|
responseBody: z.object({
|
|
756
721
|
service: z.string(),
|
|
757
722
|
field: z.string(),
|
|
723
|
+
affectedConnections: z
|
|
724
|
+
.array(z.string())
|
|
725
|
+
.describe(
|
|
726
|
+
"Provider connections that depended on the deleted credential",
|
|
727
|
+
),
|
|
758
728
|
}),
|
|
759
729
|
handler: handleCredentialsDelete,
|
|
760
730
|
},
|
|
@@ -7,8 +7,10 @@
|
|
|
7
7
|
*/
|
|
8
8
|
import type { KnownBlock } from "@slack/types";
|
|
9
9
|
|
|
10
|
+
import { audienceForReader } from "../../channels/message-audience.js";
|
|
10
11
|
import type { ChannelId } from "../../channels/types.js";
|
|
11
12
|
import type { TrustContext } from "../../daemon/trust-context-types.js";
|
|
13
|
+
import { editChannelMessage } from "../../messaging/providers/index.js";
|
|
12
14
|
import { getLogger } from "../../util/logger.js";
|
|
13
15
|
import { resolveCapabilities } from "../capabilities.js";
|
|
14
16
|
import type { ApprovalDecisionResult } from "../channel-approval-types.js";
|
|
@@ -33,18 +35,6 @@ import { deliverStaleApprovalReply } from "./guardian-approval-reply-helpers.js"
|
|
|
33
35
|
|
|
34
36
|
const log = getLogger("runtime-http");
|
|
35
37
|
|
|
36
|
-
/**
|
|
37
|
-
* Resolve the Slack ephemeral user ID when the source channel is Slack.
|
|
38
|
-
* Returns `undefined` for non-Slack channels so callers can pass the
|
|
39
|
-
* result directly to `ephemeralUserId` without branching.
|
|
40
|
-
*/
|
|
41
|
-
function slackEphemeralUserId(
|
|
42
|
-
sourceChannel: ChannelId,
|
|
43
|
-
userId: string | undefined,
|
|
44
|
-
): string | undefined {
|
|
45
|
-
return sourceChannel === "slack" && userId ? userId : undefined;
|
|
46
|
-
}
|
|
47
|
-
|
|
48
38
|
export interface ApprovalInterceptionParams {
|
|
49
39
|
conversationId: string;
|
|
50
40
|
callbackData?: string;
|
|
@@ -170,7 +160,11 @@ export async function handleApprovalInterception(
|
|
|
170
160
|
errorLogMessage:
|
|
171
161
|
"Failed to deliver guardian-pending notice to non-guardian actor (pre-row guard)",
|
|
172
162
|
errorLogContext: { conversationId },
|
|
173
|
-
|
|
163
|
+
audience: audienceForReader(
|
|
164
|
+
sourceChannel,
|
|
165
|
+
conversationExternalId,
|
|
166
|
+
actorExternalId,
|
|
167
|
+
),
|
|
174
168
|
});
|
|
175
169
|
return { handled: true, type: "assistant_turn" };
|
|
176
170
|
}
|
|
@@ -205,8 +199,9 @@ export async function handleApprovalInterception(
|
|
|
205
199
|
"Blocking guardian-class approval decision: acting principal missing or does not match the bound guardian principal",
|
|
206
200
|
);
|
|
207
201
|
if (replyCallbackUrl) {
|
|
208
|
-
const
|
|
202
|
+
const rejectionAudience = audienceForReader(
|
|
209
203
|
sourceChannel,
|
|
204
|
+
conversationExternalId,
|
|
210
205
|
actorExternalId,
|
|
211
206
|
);
|
|
212
207
|
try {
|
|
@@ -214,7 +209,10 @@ export async function handleApprovalInterception(
|
|
|
214
209
|
chatId: conversationExternalId,
|
|
215
210
|
text: "Sorry, I couldn't process that. Please try again.",
|
|
216
211
|
assistantId,
|
|
217
|
-
|
|
212
|
+
// Stated rather than spread. A rejection that loses its
|
|
213
|
+
// audience becomes a public one, so the key is always present
|
|
214
|
+
// and the compiler checks it.
|
|
215
|
+
audience: rejectionAudience,
|
|
218
216
|
});
|
|
219
217
|
} catch (err) {
|
|
220
218
|
log.error(
|
|
@@ -273,11 +271,10 @@ export async function handleApprovalInterception(
|
|
|
273
271
|
decisionOutcome === "approved" ? "\u2713" : "\u2717";
|
|
274
272
|
const statusLabel =
|
|
275
273
|
decisionOutcome === "approved" ? "Approved" : "Denied";
|
|
276
|
-
|
|
274
|
+
editChannelMessage(replyCallbackUrl, {
|
|
277
275
|
chatId: conversationExternalId,
|
|
276
|
+
messageId: approvalMessageTs,
|
|
278
277
|
text: `${statusEmoji} ${statusLabel}`,
|
|
279
|
-
messageTs: approvalMessageTs,
|
|
280
|
-
assistantId,
|
|
281
278
|
}).catch((err) => {
|
|
282
279
|
log.error(
|
|
283
280
|
{ err, conversationId, messageTs: approvalMessageTs },
|
|
@@ -362,7 +359,11 @@ export async function handleApprovalInterception(
|
|
|
362
359
|
toolName: pending.length > 0 ? pending[0].toolName : undefined,
|
|
363
360
|
},
|
|
364
361
|
errorLogContext: { conversationId },
|
|
365
|
-
|
|
362
|
+
audience: audienceForReader(
|
|
363
|
+
sourceChannel,
|
|
364
|
+
conversationExternalId,
|
|
365
|
+
actorExternalId,
|
|
366
|
+
),
|
|
366
367
|
});
|
|
367
368
|
|
|
368
369
|
return { handled: true, type: "assistant_turn" };
|
|
@@ -395,12 +396,11 @@ function editStaleSlackApprovalMessage(params: {
|
|
|
395
396
|
elements: [{ type: "mrkdwn", text: statusText }],
|
|
396
397
|
},
|
|
397
398
|
];
|
|
398
|
-
|
|
399
|
+
editChannelMessage(params.replyCallbackUrl, {
|
|
399
400
|
chatId: params.chatId,
|
|
401
|
+
messageId: params.messageTs,
|
|
400
402
|
text: statusText,
|
|
401
403
|
blocks,
|
|
402
|
-
messageTs: params.messageTs,
|
|
403
|
-
assistantId: params.assistantId,
|
|
404
404
|
}).catch((err) => {
|
|
405
405
|
log.error(
|
|
406
406
|
{
|
|
@@ -3,6 +3,7 @@
|
|
|
3
3
|
* identity mismatch notices, reminders, etc.) that were duplicated across
|
|
4
4
|
* guardian-approval-interception.ts.
|
|
5
5
|
*/
|
|
6
|
+
import type { MessageAudience } from "@vellumai/gateway-client";
|
|
6
7
|
import type pino from "pino";
|
|
7
8
|
|
|
8
9
|
import type { ChannelId } from "../../channels/types.js";
|
|
@@ -45,7 +46,8 @@ interface DeliverApprovalReplyParams {
|
|
|
45
46
|
* sees the message. Used to keep approval-related noise out of shared
|
|
46
47
|
* channels.
|
|
47
48
|
*/
|
|
48
|
-
|
|
49
|
+
/** Restricts the reply to one reader. Absent means the room. */
|
|
50
|
+
audience?: MessageAudience;
|
|
49
51
|
}
|
|
50
52
|
|
|
51
53
|
/**
|
|
@@ -61,7 +63,7 @@ async function composeAndDeliver(
|
|
|
61
63
|
chatId,
|
|
62
64
|
assistantId,
|
|
63
65
|
approvalCopyGenerator,
|
|
64
|
-
|
|
66
|
+
audience,
|
|
65
67
|
} = params;
|
|
66
68
|
|
|
67
69
|
const text = await composeApprovalMessageGenerative(
|
|
@@ -74,9 +76,8 @@ async function composeAndDeliver(
|
|
|
74
76
|
text,
|
|
75
77
|
assistantId,
|
|
76
78
|
};
|
|
77
|
-
if (
|
|
78
|
-
payload.
|
|
79
|
-
payload.user = ephemeralUserId;
|
|
79
|
+
if (audience) {
|
|
80
|
+
payload.audience = audience;
|
|
80
81
|
}
|
|
81
82
|
await deliverChannelReply(replyCallbackUrl, payload);
|
|
82
83
|
}
|
|
@@ -120,7 +121,8 @@ export interface DeliverStaleApprovalReplyParams {
|
|
|
120
121
|
* When set, deliver via `chat.postEphemeral` so only this Slack user
|
|
121
122
|
* sees the message. Keeps approval noise out of shared channels.
|
|
122
123
|
*/
|
|
123
|
-
|
|
124
|
+
/** Restricts the reply to one reader. Absent means the room. */
|
|
125
|
+
audience?: MessageAudience;
|
|
124
126
|
}
|
|
125
127
|
|
|
126
128
|
/**
|
|
@@ -134,12 +136,11 @@ export interface DeliverStaleApprovalReplyParams {
|
|
|
134
136
|
export async function deliverStaleApprovalReply(
|
|
135
137
|
params: DeliverStaleApprovalReplyParams,
|
|
136
138
|
): Promise<void> {
|
|
137
|
-
const { scenario, sourceChannel, extraContext,
|
|
138
|
-
params;
|
|
139
|
+
const { scenario, sourceChannel, extraContext, audience, ...rest } = params;
|
|
139
140
|
|
|
140
141
|
const replyParams: DeliverApprovalReplyParams = {
|
|
141
142
|
...rest,
|
|
142
|
-
|
|
143
|
+
audience,
|
|
143
144
|
context: {
|
|
144
145
|
scenario,
|
|
145
146
|
channel: sourceChannel,
|
|
@@ -190,7 +191,8 @@ export interface DeliverIdentityMismatchReplyParams {
|
|
|
190
191
|
* When set, deliver via `chat.postEphemeral` so only this Slack user
|
|
191
192
|
* sees the message.
|
|
192
193
|
*/
|
|
193
|
-
|
|
194
|
+
/** Restricts the reply to one reader. Absent means the room. */
|
|
195
|
+
audience?: MessageAudience;
|
|
194
196
|
}
|
|
195
197
|
|
|
196
198
|
/**
|
|
@@ -200,11 +202,11 @@ export interface DeliverIdentityMismatchReplyParams {
|
|
|
200
202
|
export async function deliverIdentityMismatchReply(
|
|
201
203
|
params: DeliverIdentityMismatchReplyParams,
|
|
202
204
|
): Promise<void> {
|
|
203
|
-
const { sourceChannel,
|
|
205
|
+
const { sourceChannel, audience, ...rest } = params;
|
|
204
206
|
|
|
205
207
|
await deliverApprovalReply({
|
|
206
208
|
...rest,
|
|
207
|
-
|
|
209
|
+
audience,
|
|
208
210
|
context: {
|
|
209
211
|
scenario: "guardian_identity_mismatch",
|
|
210
212
|
channel: sourceChannel,
|