@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
|
@@ -69,17 +69,28 @@ mock.module("../../../contacts/contact-store.js", () => ({
|
|
|
69
69
|
// Downstream side-effect stubs
|
|
70
70
|
// ---------------------------------------------------------------------------
|
|
71
71
|
|
|
72
|
-
let recordInboundCalls =
|
|
72
|
+
let recordInboundCalls: Array<{ conversationId?: string }> = [];
|
|
73
|
+
let recordedEvent: { eventId: string; conversationId: string } | null = null;
|
|
74
|
+
let outboundTargetConversationId: string | null = null;
|
|
75
|
+
let storedTarget: { messageId: string; conversationId: string } | null = null;
|
|
73
76
|
mock.module("../../../persistence/delivery-crud.js", () => ({
|
|
74
|
-
recordInbound: (
|
|
75
|
-
|
|
77
|
+
recordInbound: (
|
|
78
|
+
_channel: string,
|
|
79
|
+
_chat: string,
|
|
80
|
+
_externalMessageId: string,
|
|
81
|
+
options?: { conversationId?: string },
|
|
82
|
+
) => {
|
|
83
|
+
recordInboundCalls.push({ conversationId: options?.conversationId });
|
|
76
84
|
return {
|
|
77
85
|
eventId: "evt-1",
|
|
78
|
-
conversationId: "conv-
|
|
86
|
+
conversationId: options?.conversationId ?? "conv-minted",
|
|
79
87
|
accepted: true,
|
|
80
88
|
duplicate: false,
|
|
81
89
|
};
|
|
82
90
|
},
|
|
91
|
+
findMessageBySourceId: () => storedTarget,
|
|
92
|
+
findSlackConversationByMessageTs: () => outboundTargetConversationId,
|
|
93
|
+
findInboundEvent: () => recordedEvent,
|
|
83
94
|
clearPayload: () => {},
|
|
84
95
|
linkMessage: () => {},
|
|
85
96
|
}));
|
|
@@ -184,7 +195,6 @@ function buildParams(overrides: {
|
|
|
184
195
|
? { trustVerdict: overrides.trustVerdict }
|
|
185
196
|
: {}),
|
|
186
197
|
} as never,
|
|
187
|
-
slackChannelName: "general",
|
|
188
198
|
approvalConversationGenerator: undefined,
|
|
189
199
|
};
|
|
190
200
|
}
|
|
@@ -193,7 +203,7 @@ function expectDropped(result: Record<string, unknown>): void {
|
|
|
193
203
|
expect(result.reaction).toBe("dropped_unknown_actor");
|
|
194
204
|
// Dropped before any write or routing: no dedup record, no transcript row,
|
|
195
205
|
// no approval-pipeline dispatch.
|
|
196
|
-
expect(recordInboundCalls).toBe(0);
|
|
206
|
+
expect(recordInboundCalls.length).toBe(0);
|
|
197
207
|
expect(addMessageCalls).toBe(0);
|
|
198
208
|
expect(guardianReplyCalls.length).toBe(0);
|
|
199
209
|
}
|
|
@@ -204,10 +214,13 @@ describe("reaction intercept consumes the stamped verdict directly", () => {
|
|
|
204
214
|
guardianDeliveryReads = 0;
|
|
205
215
|
setMemberVerdictCalls = 0;
|
|
206
216
|
contactLookups = 0;
|
|
207
|
-
recordInboundCalls =
|
|
217
|
+
recordInboundCalls = [];
|
|
218
|
+
recordedEvent = null;
|
|
219
|
+
outboundTargetConversationId = null;
|
|
208
220
|
addMessageCalls = 0;
|
|
209
221
|
guardianReplyCalls = [];
|
|
210
222
|
guardianReplyResponse = undefined;
|
|
223
|
+
storedTarget = { messageId: "msg-target", conversationId: "conv-target" };
|
|
211
224
|
});
|
|
212
225
|
|
|
213
226
|
test("guardian verdict routes the reaction into the approval decision pipeline", async () => {
|
|
@@ -244,6 +257,77 @@ describe("reaction intercept consumes the stamped verdict directly", () => {
|
|
|
244
257
|
expect(result.accepted).toBe(true);
|
|
245
258
|
expect(result.reaction).toBeUndefined();
|
|
246
259
|
expect(addMessageCalls).toBe(1);
|
|
260
|
+
// Recorded into the reacted message's conversation, never a fresh one.
|
|
261
|
+
expect(recordInboundCalls).toEqual([{ conversationId: "conv-target" }]);
|
|
262
|
+
});
|
|
263
|
+
|
|
264
|
+
test("a reaction on the assistant's own post resolves through slackMeta", async () => {
|
|
265
|
+
// Outbound posts open no inbound event, so the ts is only on the row.
|
|
266
|
+
storedTarget = null;
|
|
267
|
+
outboundTargetConversationId = "conv-assistant-post";
|
|
268
|
+
|
|
269
|
+
const result = await handleSlackReactionIntercept(
|
|
270
|
+
buildParams({
|
|
271
|
+
rawSenderId: MEMBER_USER_ID,
|
|
272
|
+
trustVerdict: MEMBER_VERDICT,
|
|
273
|
+
}),
|
|
274
|
+
);
|
|
275
|
+
|
|
276
|
+
expect(recordInboundCalls).toEqual([
|
|
277
|
+
{ conversationId: "conv-assistant-post" },
|
|
278
|
+
]);
|
|
279
|
+
expect(addMessageCalls).toBe(1);
|
|
280
|
+
expect(result).toMatchObject({ accepted: true, duplicate: false });
|
|
281
|
+
});
|
|
282
|
+
|
|
283
|
+
test("a reaction on a message that is not stored is dropped without minting", async () => {
|
|
284
|
+
storedTarget = null;
|
|
285
|
+
|
|
286
|
+
const result = await handleSlackReactionIntercept(
|
|
287
|
+
buildParams({
|
|
288
|
+
rawSenderId: MEMBER_USER_ID,
|
|
289
|
+
trustVerdict: MEMBER_VERDICT,
|
|
290
|
+
}),
|
|
291
|
+
);
|
|
292
|
+
|
|
293
|
+
expect(result.reaction).toBe("dropped_unknown_target");
|
|
294
|
+
expect(recordInboundCalls.length).toBe(0);
|
|
295
|
+
expect(addMessageCalls).toBe(0);
|
|
296
|
+
});
|
|
297
|
+
|
|
298
|
+
test("a redelivered reaction never reaches the guardian rail twice", async () => {
|
|
299
|
+
recordedEvent = { eventId: "evt-1", conversationId: "conv-target" };
|
|
300
|
+
|
|
301
|
+
const result = await handleSlackReactionIntercept(
|
|
302
|
+
buildParams({
|
|
303
|
+
rawSenderId: GUARDIAN_USER_ID,
|
|
304
|
+
trustVerdict: GUARDIAN_VERDICT,
|
|
305
|
+
}),
|
|
306
|
+
);
|
|
307
|
+
|
|
308
|
+
expect(result).toEqual({
|
|
309
|
+
accepted: true,
|
|
310
|
+
duplicate: true,
|
|
311
|
+
eventId: "evt-1",
|
|
312
|
+
});
|
|
313
|
+
expect(guardianReplyCalls.length).toBe(0);
|
|
314
|
+
expect(addMessageCalls).toBe(0);
|
|
315
|
+
expect(recordInboundCalls.length).toBe(0);
|
|
316
|
+
});
|
|
317
|
+
|
|
318
|
+
test("a guardian card reaction still applies when the card is not a stored message", async () => {
|
|
319
|
+
storedTarget = null;
|
|
320
|
+
guardianReplyResponse = { accepted: true, canonicalRouter: "applied" };
|
|
321
|
+
|
|
322
|
+
const result = await handleSlackReactionIntercept(
|
|
323
|
+
buildParams({
|
|
324
|
+
rawSenderId: GUARDIAN_USER_ID,
|
|
325
|
+
trustVerdict: GUARDIAN_VERDICT,
|
|
326
|
+
}),
|
|
327
|
+
);
|
|
328
|
+
|
|
329
|
+
expect(result).toEqual(guardianReplyResponse);
|
|
330
|
+
expect(recordInboundCalls.length).toBe(0);
|
|
247
331
|
});
|
|
248
332
|
|
|
249
333
|
test("unknown verdict is dropped before any write", async () => {
|
|
@@ -9,7 +9,9 @@
|
|
|
9
9
|
* or an access-request notification (LUM-2489),
|
|
10
10
|
* - a stranger's reaction creates no conversation, binding, or transcript
|
|
11
11
|
* row — it is dropped as channel noise,
|
|
12
|
-
* - a known contact's reaction is recorded as an inline transcript signal
|
|
12
|
+
* - a known contact's reaction is recorded as an inline transcript signal in
|
|
13
|
+
* the conversation of the message it was attached to, whether that message
|
|
14
|
+
* arrived from the channel or the assistant posted it,
|
|
13
15
|
* - a guardian's reaction on an approval card is routed through the guardian
|
|
14
16
|
* decision pipeline (the same path as buttons and text replies).
|
|
15
17
|
*
|
|
@@ -28,12 +30,13 @@ import {
|
|
|
28
30
|
} from "../../../messaging/providers/slack/message-metadata.js";
|
|
29
31
|
import { addMessage } from "../../../persistence/conversation-crud.js";
|
|
30
32
|
import {
|
|
31
|
-
|
|
33
|
+
findInboundEvent,
|
|
34
|
+
findMessageBySourceId,
|
|
35
|
+
findSlackConversationByMessageTs,
|
|
32
36
|
linkMessage,
|
|
33
37
|
recordInbound,
|
|
34
38
|
} from "../../../persistence/delivery-crud.js";
|
|
35
39
|
import { markProcessed } from "../../../persistence/delivery-status.js";
|
|
36
|
-
import { upsertBinding } from "../../../persistence/external-conversation-store.js";
|
|
37
40
|
import { getLogger } from "../../../util/logger.js";
|
|
38
41
|
import type { ApprovalConversationGenerator } from "../../http-types.js";
|
|
39
42
|
import {
|
|
@@ -105,8 +108,6 @@ export interface ReactionInterceptParams {
|
|
|
105
108
|
actorUsername: string | undefined;
|
|
106
109
|
replyCallbackUrl: string | undefined;
|
|
107
110
|
sourceMetadata: SourceMetadata | undefined;
|
|
108
|
-
/** Slack channel display name, for the conversation binding. */
|
|
109
|
-
slackChannelName: string | null;
|
|
110
111
|
approvalConversationGenerator: ApprovalConversationGenerator | undefined;
|
|
111
112
|
}
|
|
112
113
|
|
|
@@ -130,7 +131,6 @@ export async function handleSlackReactionIntercept(
|
|
|
130
131
|
actorUsername,
|
|
131
132
|
replyCallbackUrl,
|
|
132
133
|
sourceMetadata,
|
|
133
|
-
slackChannelName,
|
|
134
134
|
approvalConversationGenerator,
|
|
135
135
|
} = params;
|
|
136
136
|
|
|
@@ -165,27 +165,11 @@ export async function handleSlackReactionIntercept(
|
|
|
165
165
|
typeof sourceMetadata?.messageId === "string"
|
|
166
166
|
? sourceMetadata.messageId
|
|
167
167
|
: undefined;
|
|
168
|
-
const threadTs =
|
|
169
|
-
typeof sourceMetadata?.threadId === "string" &&
|
|
170
|
-
sourceMetadata.threadId.trim().length > 0
|
|
171
|
-
? sourceMetadata.threadId.trim()
|
|
172
|
-
: undefined;
|
|
173
|
-
|
|
174
|
-
// Record for dedup + conversation resolution (known contacts only — strangers
|
|
175
|
-
// were dropped above).
|
|
176
|
-
const result = recordInbound(
|
|
177
|
-
sourceChannel,
|
|
178
|
-
conversationExternalId,
|
|
179
|
-
externalMessageId,
|
|
180
|
-
{
|
|
181
|
-
sourceMessageId: reactedMessageTs,
|
|
182
|
-
sourceThreadId: threadTs,
|
|
183
|
-
},
|
|
184
|
-
);
|
|
185
|
-
|
|
186
168
|
// Respect disk-pressure cleanup so reactions don't bypass storage
|
|
187
169
|
// protection. Guardians resolve to `allow-cleanup-mode` (not `block`), so a
|
|
188
|
-
// guardian's approval-by-reaction still flows.
|
|
170
|
+
// guardian's approval-by-reaction still flows. Blocked silently and before
|
|
171
|
+
// any write: a reaction is a passive signal, so the message pipeline's
|
|
172
|
+
// "storage is low, try again" notice is meaningless for an emoji.
|
|
189
173
|
const diskPressure = classifyDiskPressureTurnPolicy(getDiskPressureStatus(), {
|
|
190
174
|
sourceChannel,
|
|
191
175
|
sourceInterface,
|
|
@@ -195,44 +179,39 @@ export async function handleSlackReactionIntercept(
|
|
|
195
179
|
},
|
|
196
180
|
});
|
|
197
181
|
if (diskPressure.action === "block") {
|
|
198
|
-
// Block silently: a reaction is a passive signal, so the message
|
|
199
|
-
// pipeline's "storage is low, try again" notice is meaningless for an
|
|
200
|
-
// emoji — there is nothing to retry. Mark the event processed and stop
|
|
201
|
-
// before binding/persistence.
|
|
202
|
-
if (!result.duplicate) {
|
|
203
|
-
clearPayload(result.eventId);
|
|
204
|
-
markProcessed(result.eventId);
|
|
205
|
-
}
|
|
206
182
|
return {
|
|
207
183
|
accepted: true,
|
|
208
|
-
|
|
209
|
-
eventId: result.eventId,
|
|
184
|
+
reaction: "dropped_disk_pressure",
|
|
210
185
|
diskPressure: "blocked",
|
|
211
186
|
reason: diskPressure.reason,
|
|
212
187
|
};
|
|
213
188
|
}
|
|
214
189
|
|
|
215
|
-
//
|
|
216
|
-
|
|
217
|
-
|
|
190
|
+
// A redelivery is answered before anything acts on it. Recording the event
|
|
191
|
+
// is what dedups a reaction, and that happens after the guardian rail, so
|
|
192
|
+
// without this probe the same emoji could drive a decision twice.
|
|
193
|
+
const alreadyRecorded = findInboundEvent(
|
|
218
194
|
sourceChannel,
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
195
|
+
conversationExternalId,
|
|
196
|
+
externalMessageId,
|
|
197
|
+
);
|
|
198
|
+
if (alreadyRecorded) {
|
|
199
|
+
return {
|
|
200
|
+
accepted: true,
|
|
201
|
+
duplicate: true,
|
|
202
|
+
eventId: alreadyRecorded.eventId,
|
|
203
|
+
};
|
|
204
|
+
}
|
|
226
205
|
|
|
227
|
-
// Guardian approval-by-reaction
|
|
228
|
-
//
|
|
206
|
+
// Guardian approval-by-reaction runs before the reacted message is resolved:
|
|
207
|
+
// an approval card is addressed by its own message id and is not always a
|
|
208
|
+
// stored message. Only `reaction:` (added) expresses intent;
|
|
229
209
|
// `reaction_removed:` never does. `handleGuardianReplyIntercept` self-gates
|
|
230
|
-
// on `trustClass === "guardian"`, so a contact's reaction returns no
|
|
231
|
-
// and falls through to persistence.
|
|
232
|
-
|
|
233
|
-
if (isReactionAdded && replyCallbackUrl && !result.duplicate) {
|
|
210
|
+
// on `trustClass === "guardian"`, so a contact's reaction returns no
|
|
211
|
+
// response and falls through to persistence.
|
|
212
|
+
if (callbackData.startsWith("reaction:") && replyCallbackUrl) {
|
|
234
213
|
const reactionIntercept = await handleGuardianReplyIntercept({
|
|
235
|
-
isDuplicate:
|
|
214
|
+
isDuplicate: false,
|
|
236
215
|
trimmedContent: "",
|
|
237
216
|
hasCallbackData: true,
|
|
238
217
|
callbackData,
|
|
@@ -241,35 +220,56 @@ export async function handleSlackReactionIntercept(
|
|
|
241
220
|
canonicalSenderId,
|
|
242
221
|
sourceChannel,
|
|
243
222
|
conversationExternalId,
|
|
244
|
-
conversationId: result.conversationId,
|
|
245
|
-
eventId: result.eventId,
|
|
246
223
|
replyCallbackUrl,
|
|
247
224
|
trustClass: trustCtx.trustClass,
|
|
248
225
|
guardianPrincipalId: trustCtx.guardianPrincipalId,
|
|
249
226
|
approvalConversationGenerator,
|
|
250
227
|
});
|
|
251
|
-
// Consumed as a guardian decision (applied, or a surfaced failure delivered
|
|
252
|
-
// as an ephemeral reply). Short-circuit so we do not also persist a
|
|
253
|
-
// transcript row.
|
|
254
228
|
if (reactionIntercept.response) {
|
|
255
229
|
return reactionIntercept.response;
|
|
256
230
|
}
|
|
257
231
|
}
|
|
258
232
|
|
|
259
|
-
//
|
|
260
|
-
//
|
|
261
|
-
|
|
233
|
+
// The reaction belongs to the conversation of the message it was attached
|
|
234
|
+
// to. Slack sends no `thread_ts` on a reaction, so resolving a conversation
|
|
235
|
+
// from the reaction's own address keys one on the reacted message instead of
|
|
236
|
+
// finding the one that message lives in, minting an orphan per reaction.
|
|
237
|
+
// A message the assistant never stored has nothing to annotate, so the
|
|
238
|
+
// reaction is dropped rather than given a conversation of its own.
|
|
239
|
+
// Inbound messages carry their provider id on the event that delivered
|
|
240
|
+
// them. The assistant's own posts open no inbound event, so a reaction on
|
|
241
|
+
// one is resolved through the `slackMeta` those rows carry.
|
|
242
|
+
const targetConversationId = reactedMessageTs
|
|
243
|
+
? (findMessageBySourceId(
|
|
244
|
+
sourceChannel,
|
|
245
|
+
conversationExternalId,
|
|
246
|
+
reactedMessageTs,
|
|
247
|
+
)?.conversationId ??
|
|
248
|
+
findSlackConversationByMessageTs(
|
|
249
|
+
conversationExternalId,
|
|
250
|
+
reactedMessageTs,
|
|
251
|
+
))
|
|
252
|
+
: null;
|
|
253
|
+
if (!targetConversationId || !reactedMessageTs) {
|
|
262
254
|
log.debug(
|
|
263
|
-
{
|
|
264
|
-
"
|
|
255
|
+
{ sourceChannel, conversationExternalId, reactedMessageTs },
|
|
256
|
+
"Dropping reaction: reacted message is not stored",
|
|
265
257
|
);
|
|
266
|
-
return {
|
|
267
|
-
accepted: result.accepted,
|
|
268
|
-
duplicate: result.duplicate,
|
|
269
|
-
eventId: result.eventId,
|
|
270
|
-
};
|
|
258
|
+
return { accepted: true, reaction: "dropped_unknown_target" };
|
|
271
259
|
}
|
|
272
260
|
|
|
261
|
+
const result = recordInbound(
|
|
262
|
+
sourceChannel,
|
|
263
|
+
conversationExternalId,
|
|
264
|
+
externalMessageId,
|
|
265
|
+
// No `sourceMessageId`: that column names the provider id of the event's
|
|
266
|
+
// own message, and a reaction is not one. Claiming the reacted message's
|
|
267
|
+
// id here would put two linked rows on that id, leaving a later edit or
|
|
268
|
+
// delete of it free to resolve to the reaction instead.
|
|
269
|
+
{ conversationId: targetConversationId },
|
|
270
|
+
);
|
|
271
|
+
|
|
272
|
+
// Record the reaction as an inline transcript signal.
|
|
273
273
|
try {
|
|
274
274
|
await persistSlackReactionAsMessage({
|
|
275
275
|
conversationId: result.conversationId,
|
|
@@ -277,7 +277,6 @@ export async function handleSlackReactionIntercept(
|
|
|
277
277
|
eventId: result.eventId,
|
|
278
278
|
callbackData,
|
|
279
279
|
actorDisplayName,
|
|
280
|
-
threadTs,
|
|
281
280
|
reactedMessageTs,
|
|
282
281
|
duplicate: result.duplicate,
|
|
283
282
|
});
|
|
@@ -311,7 +310,6 @@ async function persistSlackReactionAsMessage(params: {
|
|
|
311
310
|
eventId: string;
|
|
312
311
|
callbackData: string;
|
|
313
312
|
actorDisplayName?: string;
|
|
314
|
-
threadTs?: string;
|
|
315
313
|
reactedMessageTs: string;
|
|
316
314
|
duplicate: boolean;
|
|
317
315
|
}): Promise<void> {
|
|
@@ -336,7 +334,11 @@ async function persistSlackReactionAsMessage(params: {
|
|
|
336
334
|
channelId: params.conversationExternalId,
|
|
337
335
|
channelTs: params.reactedMessageTs,
|
|
338
336
|
eventKind: "reaction",
|
|
339
|
-
|
|
337
|
+
// No `threadTs`: Slack sends none on a reaction, so the gateway's thread
|
|
338
|
+
// id here is the reacted message's own ts. It equals `channelTs`, which
|
|
339
|
+
// every reader treats as "not in a thread" anyway, and storing it makes
|
|
340
|
+
// the row false evidence that a thread belongs to this conversation
|
|
341
|
+
// (`legacySlackConversationHasThreadEvidence`).
|
|
340
342
|
...(params.actorDisplayName
|
|
341
343
|
? { displayName: params.actorDisplayName }
|
|
342
344
|
: {}),
|
|
@@ -127,6 +127,7 @@ import { ROUTES as PUBLISH_ROUTES } from "./publish-routes.js";
|
|
|
127
127
|
import { ROUTES as QUESTION_ROUTES } from "./question-routes.js";
|
|
128
128
|
import { ROUTES as RECORDING_ROUTES } from "./recording-routes.js";
|
|
129
129
|
import { ROUTES as RENAME_CONVERSATION_ROUTES } from "./rename-conversation-routes.js";
|
|
130
|
+
import { ROUTES as RESOURCE_PRESSURE_ROUTES } from "./resource-pressure-routes.js";
|
|
130
131
|
import { ROUTES as RETROSPECTIVE_ROUTES } from "./retrospective-routes.js";
|
|
131
132
|
import { ROUTES as ROUTE_HOST_WORKER_ROUTES } from "./route-host-worker-routes.js";
|
|
132
133
|
import { ROUTES as SANITY_ROUTES } from "./sanity-routes.js";
|
|
@@ -210,6 +211,7 @@ export const ROUTES: RouteDefinition[] = [
|
|
|
210
211
|
...DEFAULT_PROVIDER_ROUTES,
|
|
211
212
|
...DIAGNOSTICS_ROUTES,
|
|
212
213
|
...DISK_PRESSURE_ROUTES,
|
|
214
|
+
...RESOURCE_PRESSURE_ROUTES,
|
|
213
215
|
...DOMAIN_ROUTES,
|
|
214
216
|
...DOCUMENT_COMMENT_ROUTES,
|
|
215
217
|
...DOCUMENT_ROUTES,
|
|
@@ -27,7 +27,7 @@ import {
|
|
|
27
27
|
ConnectionProviderSchema,
|
|
28
28
|
deriveAuthForProvider,
|
|
29
29
|
ProviderConnectionSchema,
|
|
30
|
-
|
|
30
|
+
PROVIDERS_ALLOWING_CUSTOM_BASE_URL,
|
|
31
31
|
VALID_CONNECTION_PROVIDERS,
|
|
32
32
|
} from "../../providers/inference/auth.js";
|
|
33
33
|
import {
|
|
@@ -72,14 +72,16 @@ const providerConnectionResponseSchema = ProviderConnectionSchema;
|
|
|
72
72
|
* Parse and validate `base_url` and `models` from the request body.
|
|
73
73
|
*
|
|
74
74
|
* `base_url` is only accepted for providers in
|
|
75
|
-
* `
|
|
75
|
+
* `PROVIDERS_ALLOWING_CUSTOM_BASE_URL` (openai-compatible and ollama).
|
|
76
76
|
* For all other providers, supplying `base_url` returns a 400. This prevents
|
|
77
77
|
* API-key exfiltration: an attacker cannot create an `anthropic` connection
|
|
78
78
|
* with a `base_url` pointing to their own server, which would redirect all
|
|
79
79
|
* LLM calls (and the API key) to the attacker.
|
|
80
80
|
*
|
|
81
|
-
* Even for
|
|
82
|
-
* networks or cloud metadata endpoints
|
|
81
|
+
* Even for allowed providers, a platform-hosted daemon rejects `base_url`
|
|
82
|
+
* values that point to private networks or cloud metadata endpoints
|
|
83
|
+
* (SSRF protection). Self-hosted daemons allow those addresses because
|
|
84
|
+
* localhost and LAN hosts are the expected target.
|
|
83
85
|
*/
|
|
84
86
|
async function parseCustomProviderFields(
|
|
85
87
|
body: Record<string, unknown>,
|
|
@@ -96,14 +98,14 @@ async function parseCustomProviderFields(
|
|
|
96
98
|
if ("base_url" in body) {
|
|
97
99
|
const raw = body.base_url;
|
|
98
100
|
|
|
99
|
-
// Gate: base_url is only valid for
|
|
101
|
+
// Gate: base_url is only valid for providers that persist a custom endpoint.
|
|
100
102
|
if (
|
|
101
103
|
raw !== null &&
|
|
102
104
|
raw !== undefined &&
|
|
103
|
-
!
|
|
105
|
+
!PROVIDERS_ALLOWING_CUSTOM_BASE_URL.has(provider)
|
|
104
106
|
) {
|
|
105
107
|
throw new BadRequestError(
|
|
106
|
-
`base_url is only valid for openai-compatible providers. Remove base_url or use
|
|
108
|
+
`base_url is only valid for openai-compatible and ollama providers. Remove base_url or use a provider that accepts a custom endpoint.`,
|
|
107
109
|
);
|
|
108
110
|
}
|
|
109
111
|
|
|
@@ -56,6 +56,7 @@ const sampleEventsSchema = z.object({
|
|
|
56
56
|
const sampleMemoryStatSchema = z.object({
|
|
57
57
|
anonBytes: z.number().nullable(),
|
|
58
58
|
fileBytes: z.number().nullable(),
|
|
59
|
+
inactiveFileBytes: z.number().nullable(),
|
|
59
60
|
kernelBytes: z.number().nullable(),
|
|
60
61
|
slabReclaimableBytes: z.number().nullable(),
|
|
61
62
|
slabUnreclaimableBytes: z.number().nullable(),
|
|
@@ -195,9 +196,16 @@ function readLatestSample(): ResourceSample | null {
|
|
|
195
196
|
}
|
|
196
197
|
// Samples persisted by an older monitor may predate some fields; fill
|
|
197
198
|
// them with null so the response always matches the documented shape.
|
|
199
|
+
// The nested memoryStat needs the same backfill for fields added after
|
|
200
|
+
// a sample was written.
|
|
198
201
|
return {
|
|
199
202
|
...sample,
|
|
200
|
-
memoryStat: sample.memoryStat
|
|
203
|
+
memoryStat: sample.memoryStat
|
|
204
|
+
? {
|
|
205
|
+
...sample.memoryStat,
|
|
206
|
+
inactiveFileBytes: sample.memoryStat.inactiveFileBytes ?? null,
|
|
207
|
+
}
|
|
208
|
+
: null,
|
|
201
209
|
reclaim: sample.reclaim ?? null,
|
|
202
210
|
cpu: sample.cpu ?? null,
|
|
203
211
|
deltas: sample.deltas ?? null,
|
|
@@ -1643,7 +1643,7 @@ function mapTogglePluginError(err: unknown): RouteError {
|
|
|
1643
1643
|
|
|
1644
1644
|
/**
|
|
1645
1645
|
* Converge plugin-declared schedules against the sentinel this route just
|
|
1646
|
-
* wrote, so a
|
|
1646
|
+
* wrote, so a disabled plugin's rows disarm now rather than at the
|
|
1647
1647
|
* reconciler's next backstop sweep.
|
|
1648
1648
|
*
|
|
1649
1649
|
* Fire-and-forget: the toggle itself already succeeded on disk, so a reconcile
|
|
@@ -1671,15 +1671,27 @@ function reconcilePluginSchedulesInBackground(): void {
|
|
|
1671
1671
|
* names WHICH resource is stale, not the new value. The origin client id is
|
|
1672
1672
|
* threaded through for self-echo suppression.
|
|
1673
1673
|
*/
|
|
1674
|
-
function handleEnablePlugin({
|
|
1674
|
+
async function handleEnablePlugin({
|
|
1675
|
+
pathParams = {},
|
|
1676
|
+
headers,
|
|
1677
|
+
}: RouteHandlerArgs) {
|
|
1675
1678
|
try {
|
|
1676
1679
|
enablePlugin(pathParams.name ?? "");
|
|
1677
|
-
publishPluginsChanged(getOriginClientId(headers));
|
|
1678
|
-
reconcilePluginSchedulesInBackground();
|
|
1679
|
-
return { ok: true };
|
|
1680
1680
|
} catch (err) {
|
|
1681
1681
|
throw mapTogglePluginError(err);
|
|
1682
1682
|
}
|
|
1683
|
+
// A plugin the boot scan skipped for its sentinel has no hooks, tools or
|
|
1684
|
+
// MCP servers in the caches, so clearing the sentinel is not enough to
|
|
1685
|
+
// bring it up. Run the same imperative source reconcile the install and
|
|
1686
|
+
// upgrade routes use, which activates the plugin and then converges its
|
|
1687
|
+
// schedules and MCP servers. Awaited, like the install route, so a client
|
|
1688
|
+
// that lists tools right after enabling sees the plugin up; the reconcile
|
|
1689
|
+
// contains its own failures, so a broken plugin cannot turn a successful
|
|
1690
|
+
// enable into a route error. The invalidation publishes after it for the
|
|
1691
|
+
// same reason: refetching clients should see the post-activation state.
|
|
1692
|
+
await reconcilePluginSourcesNow();
|
|
1693
|
+
publishPluginsChanged(getOriginClientId(headers));
|
|
1694
|
+
return { ok: true };
|
|
1683
1695
|
}
|
|
1684
1696
|
|
|
1685
1697
|
function handleDisablePlugin({ pathParams = {}, headers }: RouteHandlerArgs) {
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import { ResourcePressureStatusResponseSchema } from "../../api/responses/resource-pressure-status.js";
|
|
2
|
+
import { getResourcePressureStatus } from "../../daemon/resource-pressure-guard.js";
|
|
3
|
+
import { ACTOR_PRINCIPALS } from "../auth/route-policy.js";
|
|
4
|
+
import type { RouteDefinition } from "./types.js";
|
|
5
|
+
|
|
6
|
+
export const ROUTES: RouteDefinition[] = [
|
|
7
|
+
{
|
|
8
|
+
operationId: "getResourcePressureStatus",
|
|
9
|
+
endpoint: "resource-pressure/status",
|
|
10
|
+
method: "GET",
|
|
11
|
+
policy: {
|
|
12
|
+
requiredScopes: ["settings.read"],
|
|
13
|
+
allowedPrincipalTypes: ACTOR_PRINCIPALS,
|
|
14
|
+
},
|
|
15
|
+
summary: "Get resource pressure status",
|
|
16
|
+
description:
|
|
17
|
+
"Return the current resource pressure status snapshot. Off-platform the guard does not run, so a disabled status is returned.",
|
|
18
|
+
tags: ["resource-pressure"],
|
|
19
|
+
responseBody: ResourcePressureStatusResponseSchema,
|
|
20
|
+
handler: () => ({ status: getResourcePressureStatus() }),
|
|
21
|
+
},
|
|
22
|
+
];
|
|
@@ -58,7 +58,16 @@ import {
|
|
|
58
58
|
} from "../../tools/credentials/metadata-store.js";
|
|
59
59
|
import { getLogger } from "../../util/logger.js";
|
|
60
60
|
import { ACTOR_PRINCIPALS } from "../auth/route-policy.js";
|
|
61
|
-
import {
|
|
61
|
+
import {
|
|
62
|
+
assertCredentialNotInUse,
|
|
63
|
+
invalidateConnectionsAfterCredentialDelete,
|
|
64
|
+
} from "./credential-in-use.js";
|
|
65
|
+
import {
|
|
66
|
+
BadRequestError,
|
|
67
|
+
InternalError,
|
|
68
|
+
NotFoundError,
|
|
69
|
+
RouteError,
|
|
70
|
+
} from "./errors.js";
|
|
62
71
|
import type { RouteDefinition, RouteHandlerArgs } from "./types.js";
|
|
63
72
|
|
|
64
73
|
const log = getLogger("runtime-http");
|
|
@@ -550,7 +559,11 @@ async function handleDeleteSecret({ body }: RouteHandlerArgs) {
|
|
|
550
559
|
throw new BadRequestError("Request body is required");
|
|
551
560
|
}
|
|
552
561
|
|
|
553
|
-
const { type, name } = body as {
|
|
562
|
+
const { type, name, force } = body as {
|
|
563
|
+
type?: string;
|
|
564
|
+
name?: string;
|
|
565
|
+
force?: boolean;
|
|
566
|
+
};
|
|
554
567
|
|
|
555
568
|
if (!type || typeof type !== "string") {
|
|
556
569
|
throw new BadRequestError("type is required");
|
|
@@ -583,6 +596,13 @@ async function handleDeleteSecret({ body }: RouteHandlerArgs) {
|
|
|
583
596
|
throw new NotFoundError(`API key not found: ${name}`);
|
|
584
597
|
}
|
|
585
598
|
}
|
|
599
|
+
// Provider connections reference the namespaced account, so the in-use
|
|
600
|
+
// check keys off `credKey`: the bare pre-migration account is never a
|
|
601
|
+
// connection's `resolve_auth` target.
|
|
602
|
+
const affectedConnections = assertCredentialNotInUse(
|
|
603
|
+
credKey,
|
|
604
|
+
force === true,
|
|
605
|
+
);
|
|
586
606
|
// Delete from both locations. During a migration overlap both may exist;
|
|
587
607
|
// ignore "not-found" since one location may already be empty.
|
|
588
608
|
const credDeleteResult = await deleteSecureKeyAsync(credKey);
|
|
@@ -598,6 +618,7 @@ async function handleDeleteSecret({ body }: RouteHandlerArgs) {
|
|
|
598
618
|
);
|
|
599
619
|
}
|
|
600
620
|
await refreshProvidersAfterSecretChange();
|
|
621
|
+
invalidateConnectionsAfterCredentialDelete(affectedConnections);
|
|
601
622
|
log.info({ provider: name }, "API key deleted via HTTP");
|
|
602
623
|
return { success: true, type, name };
|
|
603
624
|
}
|
|
@@ -617,6 +638,7 @@ async function handleDeleteSecret({ body }: RouteHandlerArgs) {
|
|
|
617
638
|
if (existing === undefined) {
|
|
618
639
|
throw new NotFoundError(`Credential not found: ${name}`);
|
|
619
640
|
}
|
|
641
|
+
const affectedConnections = assertCredentialNotInUse(key, force === true);
|
|
620
642
|
const deleteResult = await deleteSecureKeyAsync(key);
|
|
621
643
|
if (deleteResult === "error") {
|
|
622
644
|
throw new InternalError(
|
|
@@ -639,6 +661,7 @@ async function handleDeleteSecret({ body }: RouteHandlerArgs) {
|
|
|
639
661
|
if (isManagedProxyCredential(service, field)) {
|
|
640
662
|
await refreshProvidersAfterSecretChange();
|
|
641
663
|
}
|
|
664
|
+
invalidateConnectionsAfterCredentialDelete(affectedConnections);
|
|
642
665
|
log.info({ service, field }, "Credential deleted via HTTP");
|
|
643
666
|
return { success: true, type, name };
|
|
644
667
|
}
|
|
@@ -647,11 +670,10 @@ async function handleDeleteSecret({ body }: RouteHandlerArgs) {
|
|
|
647
670
|
`Unknown secret type: ${type}. Valid types: api_key, credential`,
|
|
648
671
|
);
|
|
649
672
|
} catch (err) {
|
|
650
|
-
|
|
651
|
-
|
|
652
|
-
|
|
653
|
-
|
|
654
|
-
) {
|
|
673
|
+
// Every RouteError is already a deliberate client-facing outcome (including
|
|
674
|
+
// CREDENTIAL_IN_USE); re-wrapping one as a 500 would hide its code, status,
|
|
675
|
+
// and details from the caller.
|
|
676
|
+
if (err instanceof RouteError) {
|
|
655
677
|
throw err;
|
|
656
678
|
}
|
|
657
679
|
const message = err instanceof Error ? err.message : String(err);
|
|
@@ -793,11 +815,20 @@ export const ROUTES: RouteDefinition[] = [
|
|
|
793
815
|
allowedPrincipalTypes: ACTOR_PRINCIPALS,
|
|
794
816
|
},
|
|
795
817
|
summary: "Delete a secret",
|
|
796
|
-
description:
|
|
818
|
+
description:
|
|
819
|
+
"Remove a secret from the credential vault by name. Refused with " +
|
|
820
|
+
"CREDENTIAL_IN_USE while an LLM provider connection resolves its auth " +
|
|
821
|
+
"through the credential, unless `force` is set.",
|
|
797
822
|
tags: ["secrets"],
|
|
798
823
|
requestBody: z.object({
|
|
799
824
|
type: z.string().describe("Secret type: 'api_key' or 'credential'"),
|
|
800
825
|
name: z.string().describe("Name of the secret to delete"),
|
|
826
|
+
force: z
|
|
827
|
+
.boolean()
|
|
828
|
+
.optional()
|
|
829
|
+
.describe(
|
|
830
|
+
"Delete even when provider connections depend on the credential",
|
|
831
|
+
),
|
|
801
832
|
}),
|
|
802
833
|
responseBody: z.object({
|
|
803
834
|
success: z.boolean(),
|