@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
|
@@ -12,6 +12,7 @@
|
|
|
12
12
|
* (e.g. `surface_id is required`, `mode must be one of "replace", "append"`).
|
|
13
13
|
*/
|
|
14
14
|
|
|
15
|
+
import { parseJsonSafe } from "../util/json.js";
|
|
15
16
|
import { isPlainObject } from "../util/object.js";
|
|
16
17
|
|
|
17
18
|
export interface InputValidationSuccess {
|
|
@@ -67,22 +68,27 @@ function quoteList(values: readonly string[]): string {
|
|
|
67
68
|
}
|
|
68
69
|
|
|
69
70
|
/**
|
|
70
|
-
*
|
|
71
|
-
*
|
|
71
|
+
* Walk the properties a schema declares as `declaredType`, offering each
|
|
72
|
+
* present value to `coerceValue`. A converter returns `{ value }` to replace
|
|
73
|
+
* the input's value, or `undefined` to leave it alone.
|
|
72
74
|
*
|
|
73
|
-
*
|
|
74
|
-
*
|
|
75
|
-
*
|
|
76
|
-
*
|
|
77
|
-
*
|
|
78
|
-
* forms preserves intent.
|
|
75
|
+
* The shared traversal is what keeps the coercions below agreeing on the
|
|
76
|
+
* details a caller depends on: union types (`["boolean", "null"]`) are skipped
|
|
77
|
+
* rather than coerced, an absent property is never introduced, and the input
|
|
78
|
+
* object is cloned lazily so an input with nothing to coerce comes back by
|
|
79
|
+
* reference.
|
|
79
80
|
*
|
|
80
81
|
* Pure: returns a new object when a coercion applies, otherwise returns
|
|
81
82
|
* `input` unchanged. Never mutates `input` or `schema`.
|
|
82
83
|
*/
|
|
83
|
-
|
|
84
|
+
function coercePropertiesOfType(
|
|
84
85
|
input: Record<string, unknown>,
|
|
85
86
|
schema: Record<string, unknown> | undefined,
|
|
87
|
+
declaredType: string,
|
|
88
|
+
coerceValue: (
|
|
89
|
+
value: unknown,
|
|
90
|
+
subSchema: Record<string, unknown>,
|
|
91
|
+
) => { value: unknown } | undefined,
|
|
86
92
|
): Record<string, unknown> {
|
|
87
93
|
if (!schema) {
|
|
88
94
|
return input;
|
|
@@ -94,24 +100,47 @@ export function coerceStringBooleans(
|
|
|
94
100
|
|
|
95
101
|
let coerced: Record<string, unknown> | undefined;
|
|
96
102
|
for (const [key, rawSubSchema] of Object.entries(properties)) {
|
|
97
|
-
if (!isPlainObject(rawSubSchema)) {
|
|
103
|
+
if (!isPlainObject(rawSubSchema) || rawSubSchema.type !== declaredType) {
|
|
98
104
|
continue;
|
|
99
105
|
}
|
|
100
|
-
|
|
106
|
+
const result = coerceValue(input[key], rawSubSchema);
|
|
107
|
+
if (!result) {
|
|
101
108
|
continue;
|
|
102
109
|
}
|
|
103
|
-
|
|
110
|
+
coerced ??= { ...input };
|
|
111
|
+
coerced[key] = result.value;
|
|
112
|
+
}
|
|
113
|
+
return coerced ?? input;
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
/**
|
|
117
|
+
* Coerce string-encoded booleans (`"true"`/`"false"`) to real booleans for
|
|
118
|
+
* properties the schema declares as `type: "boolean"`.
|
|
119
|
+
*
|
|
120
|
+
* Some providers' models serialize booleans as JSON strings. Rejecting those
|
|
121
|
+
* loses the caller's intent: the model's typical recovery is to drop the field
|
|
122
|
+
* and retry, at which point the field's default silently inverts what it asked
|
|
123
|
+
* for (e.g. `app_create` with `auto_open: "false"` → retry omits the field →
|
|
124
|
+
* default `true` opens a half-built app). Accepting the unambiguous string
|
|
125
|
+
* forms preserves intent.
|
|
126
|
+
*
|
|
127
|
+
* Pure: returns a new object when a coercion applies, otherwise returns
|
|
128
|
+
* `input` unchanged. Never mutates `input` or `schema`.
|
|
129
|
+
*/
|
|
130
|
+
export function coerceStringBooleans(
|
|
131
|
+
input: Record<string, unknown>,
|
|
132
|
+
schema: Record<string, unknown> | undefined,
|
|
133
|
+
): Record<string, unknown> {
|
|
134
|
+
return coercePropertiesOfType(input, schema, "boolean", (value) => {
|
|
104
135
|
if (typeof value !== "string") {
|
|
105
|
-
|
|
136
|
+
return undefined;
|
|
106
137
|
}
|
|
107
138
|
const normalized = value.trim().toLowerCase();
|
|
108
139
|
if (normalized !== "true" && normalized !== "false") {
|
|
109
|
-
|
|
140
|
+
return undefined;
|
|
110
141
|
}
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
}
|
|
114
|
-
return coerced ?? input;
|
|
142
|
+
return { value: normalized === "true" };
|
|
143
|
+
});
|
|
115
144
|
}
|
|
116
145
|
|
|
117
146
|
/**
|
|
@@ -141,36 +170,135 @@ export function coerceStringNumbers(
|
|
|
141
170
|
input: Record<string, unknown>,
|
|
142
171
|
schema: Record<string, unknown> | undefined,
|
|
143
172
|
): Record<string, unknown> {
|
|
144
|
-
|
|
145
|
-
return input;
|
|
146
|
-
}
|
|
147
|
-
const properties = schema.properties;
|
|
148
|
-
if (!isPlainObject(properties)) {
|
|
149
|
-
return input;
|
|
150
|
-
}
|
|
151
|
-
|
|
152
|
-
let coerced: Record<string, unknown> | undefined;
|
|
153
|
-
for (const [key, rawSubSchema] of Object.entries(properties)) {
|
|
154
|
-
if (!isPlainObject(rawSubSchema)) {
|
|
155
|
-
continue;
|
|
156
|
-
}
|
|
157
|
-
if (rawSubSchema.type !== "string") {
|
|
158
|
-
continue;
|
|
159
|
-
}
|
|
160
|
-
const value = input[key];
|
|
173
|
+
return coercePropertiesOfType(input, schema, "string", (value) => {
|
|
161
174
|
if (typeof value !== "number" || !Number.isFinite(value)) {
|
|
162
|
-
|
|
175
|
+
return undefined;
|
|
163
176
|
}
|
|
164
177
|
// An integer beyond the safe range was already rounded by JSON.parse;
|
|
165
178
|
// coercing it would lock in a corrupted identifier. Skip it so validation
|
|
166
179
|
// fails and the model retries with a lossless quoted string.
|
|
167
180
|
if (Number.isInteger(value) && !Number.isSafeInteger(value)) {
|
|
168
|
-
|
|
181
|
+
return undefined;
|
|
169
182
|
}
|
|
170
|
-
|
|
171
|
-
|
|
183
|
+
return { value: String(value) };
|
|
184
|
+
});
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
/**
|
|
188
|
+
* Repair the shapes a model sends for a property the schema declares as
|
|
189
|
+
* `type: "array"`, when the intent is unambiguous.
|
|
190
|
+
*
|
|
191
|
+
* Two shapes dominate what models actually send, and neither is recoverable
|
|
192
|
+
* from the model's side: it cannot see the difference between what it meant
|
|
193
|
+
* and what arrived, so the observed recovery is to drop the field and retry.
|
|
194
|
+
* That is how a skill gets scaffolded with no `activation_hints` (losing the
|
|
195
|
+
* intent-routing signal that makes it findable later) or with its companion
|
|
196
|
+
* `files` folded into the body instead.
|
|
197
|
+
*
|
|
198
|
+
* - **The JSON text of the array** (`"[\"a\",\"b\"]"`) rather than the array.
|
|
199
|
+
* Decoded back into the array it spells.
|
|
200
|
+
* - **A single element where a list was expected**: a bare phrase for a
|
|
201
|
+
* string-item array (`avoid_when: "when the repo is dirty"`), or one object
|
|
202
|
+
* for an object-item array. Wrapped into a one-element array.
|
|
203
|
+
*
|
|
204
|
+
* A string is wrapped only when the schema declares string items and the text
|
|
205
|
+
* does not open a JSON structure it failed to close, and an object only when
|
|
206
|
+
* every key it carries is a declared property of the item schema. A shape that
|
|
207
|
+
* means something else (a truncated array, a map keyed by something the item
|
|
208
|
+
* schema does not name) is left for the validator to reject rather than
|
|
209
|
+
* silently reinterpreted. Per-element `items.type` checks still run on the
|
|
210
|
+
* repaired array, so a genuinely wrong element keeps its own error, and each
|
|
211
|
+
* element an array gains here is coerced against the item schema so a repaired
|
|
212
|
+
* element cannot carry a shape a natively-sent one could not.
|
|
213
|
+
*
|
|
214
|
+
* Pure: returns a new object when a repair applies, otherwise returns `input`
|
|
215
|
+
* unchanged. Never mutates `input` or `schema`.
|
|
216
|
+
*/
|
|
217
|
+
export function coerceArrayShapes(
|
|
218
|
+
input: Record<string, unknown>,
|
|
219
|
+
schema: Record<string, unknown> | undefined,
|
|
220
|
+
): Record<string, unknown> {
|
|
221
|
+
return coercePropertiesOfType(input, schema, "array", (value, subSchema) => {
|
|
222
|
+
const items = isPlainObject(subSchema.items) ? subSchema.items : undefined;
|
|
223
|
+
const itemType = typeof items?.type === "string" ? items.type : undefined;
|
|
224
|
+
|
|
225
|
+
if (typeof value === "string") {
|
|
226
|
+
const decoded = parseJsonSafe(value);
|
|
227
|
+
if (Array.isArray(decoded)) {
|
|
228
|
+
return { value: coerceElements(decoded, items) };
|
|
229
|
+
}
|
|
230
|
+
if (itemType !== "string") {
|
|
231
|
+
return undefined;
|
|
232
|
+
}
|
|
233
|
+
if (typeof decoded === "string" && decoded.trim()) {
|
|
234
|
+
return { value: [decoded] };
|
|
235
|
+
}
|
|
236
|
+
// Text that opens a JSON structure and then fails to parse is a
|
|
237
|
+
// truncated or malformed array, not a phrase. Wrapping it would store
|
|
238
|
+
// the broken JSON as if it were the element the caller wrote.
|
|
239
|
+
const trimmed = value.trim();
|
|
240
|
+
if (!trimmed || trimmed.startsWith("[") || trimmed.startsWith("{")) {
|
|
241
|
+
return undefined;
|
|
242
|
+
}
|
|
243
|
+
return { value: [value] };
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
if (
|
|
247
|
+
itemType === "object" &&
|
|
248
|
+
isPlainObject(value) &&
|
|
249
|
+
matchesItemProperties(value, items)
|
|
250
|
+
) {
|
|
251
|
+
return { value: coerceElements([value], items) };
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
return undefined;
|
|
255
|
+
});
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
/**
|
|
259
|
+
* Apply the scalar coercions to each object element against the item schema.
|
|
260
|
+
*
|
|
261
|
+
* Only top-level properties are coerced when a call arrives already in the
|
|
262
|
+
* declared shape, because that is the only level the validator type-checks.
|
|
263
|
+
* An array this function builds has no such level yet: its elements were text
|
|
264
|
+
* a moment ago, and their properties carry whatever the model serialized,
|
|
265
|
+
* including the string-encoded booleans this module exists to read. Coercing
|
|
266
|
+
* them here keeps a repaired element from reaching an executor in a shape a
|
|
267
|
+
* natively-sent one could not.
|
|
268
|
+
*/
|
|
269
|
+
function coerceElements(
|
|
270
|
+
elements: unknown[],
|
|
271
|
+
itemSchema: Record<string, unknown> | undefined,
|
|
272
|
+
): unknown[] {
|
|
273
|
+
if (!isPlainObject(itemSchema?.properties)) {
|
|
274
|
+
return elements;
|
|
172
275
|
}
|
|
173
|
-
return
|
|
276
|
+
return elements.map((element) =>
|
|
277
|
+
isPlainObject(element)
|
|
278
|
+
? coerceStringNumbers(
|
|
279
|
+
coerceStringBooleans(element, itemSchema),
|
|
280
|
+
itemSchema,
|
|
281
|
+
)
|
|
282
|
+
: element,
|
|
283
|
+
);
|
|
284
|
+
}
|
|
285
|
+
|
|
286
|
+
/**
|
|
287
|
+
* Whether `value` reads as one element of `itemSchema`: every key it carries
|
|
288
|
+
* is a property that schema declares. An object that fails this is some other
|
|
289
|
+
* shape (most often a map keyed by data rather than by field name), which is
|
|
290
|
+
* not a single element and must not be wrapped as one.
|
|
291
|
+
*/
|
|
292
|
+
function matchesItemProperties(
|
|
293
|
+
value: Record<string, unknown>,
|
|
294
|
+
itemSchema: Record<string, unknown> | undefined,
|
|
295
|
+
): boolean {
|
|
296
|
+
const properties = itemSchema?.properties;
|
|
297
|
+
if (!isPlainObject(properties)) {
|
|
298
|
+
return false;
|
|
299
|
+
}
|
|
300
|
+
const keys = Object.keys(value);
|
|
301
|
+
return keys.length > 0 && keys.every((key) => key in properties);
|
|
174
302
|
}
|
|
175
303
|
|
|
176
304
|
/**
|
|
@@ -241,6 +369,15 @@ export function validateInputAgainstSchema(
|
|
|
241
369
|
}
|
|
242
370
|
if (typeof declaredType === "string" && SUPPORTED_TYPES.has(declaredType)) {
|
|
243
371
|
const type = declaredType as SupportedType;
|
|
372
|
+
if (
|
|
373
|
+
type === "array" &&
|
|
374
|
+
(typeof value === "string" || isPlainObject(value))
|
|
375
|
+
) {
|
|
376
|
+
errors.push(
|
|
377
|
+
`${key} must be an array: pass a JSON array, not ${typeof value === "string" ? "a string" : "an object"}`,
|
|
378
|
+
);
|
|
379
|
+
continue;
|
|
380
|
+
}
|
|
244
381
|
if (!matchesType(value, type)) {
|
|
245
382
|
errors.push(
|
|
246
383
|
type === "boolean" && typeof value === "string"
|
|
@@ -50,10 +50,9 @@ const sources = {
|
|
|
50
50
|
// A path that does not exist, so the workspace-tree section stays empty and
|
|
51
51
|
// the assertions isolate the gated surfaces.
|
|
52
52
|
workingDir: "/tmp/does-not-exist-consult-gating",
|
|
53
|
-
// A
|
|
54
|
-
//
|
|
53
|
+
// A non-guardian per-turn snapshot: the case the live-state read could have
|
|
54
|
+
// wrongly elevated.
|
|
55
55
|
trustClass: "unknown" as const,
|
|
56
|
-
sourceChannel: "telegram",
|
|
57
56
|
allowedToolNames: new Set<string>(),
|
|
58
57
|
};
|
|
59
58
|
|
|
@@ -87,12 +86,9 @@ describe("advisor context pack: personal-memory gating", () => {
|
|
|
87
86
|
test("feeds the gate the per-turn trust snapshot, not live conversation state", async () => {
|
|
88
87
|
personalAllowed = true;
|
|
89
88
|
await buildAdvisorContext(sources);
|
|
90
|
-
// The gate must see exactly the
|
|
91
|
-
//
|
|
92
|
-
//
|
|
93
|
-
expect(gateArg).toEqual({
|
|
94
|
-
sourceChannel: "telegram",
|
|
95
|
-
trustClass: "unknown",
|
|
96
|
-
});
|
|
89
|
+
// The gate must see exactly the class threaded from the ToolContext
|
|
90
|
+
// snapshot, so a concurrent live-trust change cannot elevate this
|
|
91
|
+
// invocation.
|
|
92
|
+
expect(gateArg).toEqual({ trustClass: "unknown" });
|
|
97
93
|
});
|
|
98
94
|
});
|
|
@@ -33,7 +33,6 @@
|
|
|
33
33
|
import { readdir } from "node:fs/promises";
|
|
34
34
|
import { join } from "node:path";
|
|
35
35
|
|
|
36
|
-
import type { ChannelId } from "../channels/types.js";
|
|
37
36
|
import type { SkillSummary } from "../config/skills.js";
|
|
38
37
|
import type { TrustContext } from "../daemon/trust-context-types.js";
|
|
39
38
|
import type { TrustClass } from "../runtime/actor-trust-resolver.js";
|
|
@@ -46,16 +45,11 @@ export interface AdvisorContextSources {
|
|
|
46
45
|
allowedToolNames?: ReadonlySet<string>;
|
|
47
46
|
/**
|
|
48
47
|
* Trust class of the turn's actor, from the per-turn `ToolContext.trustClass`
|
|
49
|
-
* snapshot. Gates
|
|
48
|
+
* snapshot. Gates the personal-memory surfaces, evaluated exactly as the
|
|
49
|
+
* injectors do and off the same per-turn snapshot rather than the mutable
|
|
50
|
+
* live conversation trust.
|
|
50
51
|
*/
|
|
51
52
|
trustClass: TrustClass;
|
|
52
|
-
/**
|
|
53
|
-
* Channel the turn originates on, from the per-turn `ToolContext.executionChannel`
|
|
54
|
-
* snapshot. Combined with {@link trustClass} to evaluate personal-memory
|
|
55
|
-
* access exactly as the injectors do, off the same per-turn snapshot rather
|
|
56
|
-
* than the mutable live conversation trust.
|
|
57
|
-
*/
|
|
58
|
-
sourceChannel?: string;
|
|
59
53
|
/**
|
|
60
54
|
* Per-chat plugin scope from `ToolContext.enabledPluginSet`: `null` means no
|
|
61
55
|
* restriction; otherwise plugin-owned skills outside the set are omitted
|
|
@@ -263,18 +257,13 @@ export async function buildWorkspaceTree(
|
|
|
263
257
|
*/
|
|
264
258
|
async function personalMemoryAllowedForAdvisor(
|
|
265
259
|
trustClass: TrustClass,
|
|
266
|
-
sourceChannel: string | undefined,
|
|
267
260
|
): Promise<boolean> {
|
|
268
261
|
try {
|
|
269
262
|
const { isPersonalMemoryAllowed } =
|
|
270
263
|
await import("../daemon/trust-context.js");
|
|
271
|
-
//
|
|
272
|
-
//
|
|
273
|
-
|
|
274
|
-
const snapshot = {
|
|
275
|
-
sourceChannel: sourceChannel as ChannelId | undefined,
|
|
276
|
-
trustClass,
|
|
277
|
-
} as TrustContext;
|
|
264
|
+
// The gate decides from the trust class alone, so the per-turn snapshot's
|
|
265
|
+
// class is the whole input.
|
|
266
|
+
const snapshot = { trustClass } as TrustContext;
|
|
278
267
|
return isPersonalMemoryAllowed(snapshot);
|
|
279
268
|
} catch {
|
|
280
269
|
return false;
|
|
@@ -318,12 +307,7 @@ async function buildWorkspaceSection(
|
|
|
318
307
|
// toggle) the runtime injectors use, evaluated off the per-turn trust
|
|
319
308
|
// snapshot, so a low-risk advisor consult cannot forward private content the
|
|
320
309
|
// main agent would never receive.
|
|
321
|
-
if (
|
|
322
|
-
await personalMemoryAllowedForAdvisor(
|
|
323
|
-
sources.trustClass,
|
|
324
|
-
sources.sourceChannel,
|
|
325
|
-
)
|
|
326
|
-
) {
|
|
310
|
+
if (await personalMemoryAllowedForAdvisor(sources.trustClass)) {
|
|
327
311
|
try {
|
|
328
312
|
const [{ readNowScratchpad }, { getConfig }] = await Promise.all([
|
|
329
313
|
import("../daemon/now-scratchpad.js"),
|
|
@@ -18,37 +18,17 @@ import type {
|
|
|
18
18
|
import { isDomainAllowed } from "./domain-policy.js";
|
|
19
19
|
import { getCredentialMetadata } from "./metadata-store.js";
|
|
20
20
|
import { resolveById } from "./resolve.js";
|
|
21
|
-
import {
|
|
21
|
+
import {
|
|
22
|
+
isToolAllowed,
|
|
23
|
+
serverUseDenialReason,
|
|
24
|
+
toolNotAllowedReason,
|
|
25
|
+
} from "./tool-policy.js";
|
|
22
26
|
|
|
23
27
|
const log = getLogger("credential-broker");
|
|
24
28
|
|
|
25
29
|
/** Tokens expire after 5 minutes to limit the window for using stale/revoked credentials. */
|
|
26
30
|
const TOKEN_TTL_MS = 5 * 60 * 1000;
|
|
27
31
|
|
|
28
|
-
/**
|
|
29
|
-
* Remediation for a credential whose allowed_tools list is empty. Points at
|
|
30
|
-
* `credentials prompt` (not inline `credentials set`, which agent shells
|
|
31
|
-
* refuse): the secure prompt re-collects the value and sets allowed_tools.
|
|
32
|
-
*/
|
|
33
|
-
const NO_TOOLS_ALLOWED_REMEDIATION =
|
|
34
|
-
"No tools are currently allowed - grant access via `assistant credentials prompt --service <service> --field <field> --label <label> --allowed-tools <tools>` (re-collects the value securely and sets allowed_tools).";
|
|
35
|
-
|
|
36
|
-
/** Denial reason for a tool that is not in a credential's allowed_tools list. */
|
|
37
|
-
function toolNotAllowedReason(
|
|
38
|
-
toolName: string,
|
|
39
|
-
service: string,
|
|
40
|
-
field: string,
|
|
41
|
-
allowedTools: string[] | undefined,
|
|
42
|
-
): string {
|
|
43
|
-
const tools = allowedTools ?? [];
|
|
44
|
-
return (
|
|
45
|
-
`Tool "${toolName}" is not allowed to use credential ${service}/${field}. ` +
|
|
46
|
-
(tools.length === 0
|
|
47
|
-
? NO_TOOLS_ALLOWED_REMEDIATION
|
|
48
|
-
: `Allowed tools: ${tools.join(", ")}.`)
|
|
49
|
-
);
|
|
50
|
-
}
|
|
51
|
-
|
|
52
32
|
/**
|
|
53
33
|
* Credential broker that issues single-use tokens for policy-checked credential access.
|
|
54
34
|
*
|
|
@@ -292,36 +272,14 @@ export class CredentialBroker {
|
|
|
292
272
|
request: ServerUseRequest<T>,
|
|
293
273
|
): Promise<ServerUseResult<T>> {
|
|
294
274
|
const metadata = getCredentialMetadata(request.service, request.field);
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
return {
|
|
304
|
-
success: false,
|
|
305
|
-
reason: toolNotAllowedReason(
|
|
306
|
-
request.toolName,
|
|
307
|
-
request.service,
|
|
308
|
-
request.field,
|
|
309
|
-
metadata.allowedTools,
|
|
310
|
-
),
|
|
311
|
-
};
|
|
312
|
-
}
|
|
313
|
-
|
|
314
|
-
// Domain policy enforcement - credentials with domain restrictions are
|
|
315
|
-
// scoped to browser use on those domains and cannot be used server-side.
|
|
316
|
-
const serverDomains = metadata.allowedDomains ?? [];
|
|
317
|
-
if (serverDomains.length > 0) {
|
|
318
|
-
return {
|
|
319
|
-
success: false,
|
|
320
|
-
reason:
|
|
321
|
-
`Credential ${request.service}/${request.field} has domain restrictions ` +
|
|
322
|
-
`(${serverDomains.join(", ")}) and cannot be used server-side. ` +
|
|
323
|
-
"Remove domain restrictions or use a separate credential without domain policy.",
|
|
324
|
-
};
|
|
275
|
+
const denialReason = serverUseDenialReason(
|
|
276
|
+
metadata,
|
|
277
|
+
request.toolName,
|
|
278
|
+
request.service,
|
|
279
|
+
request.field,
|
|
280
|
+
);
|
|
281
|
+
if (denialReason) {
|
|
282
|
+
return { success: false, reason: denialReason };
|
|
325
283
|
}
|
|
326
284
|
|
|
327
285
|
const storageKey = credentialKey(request.service, request.field);
|
|
@@ -381,30 +339,17 @@ export class CredentialBroker {
|
|
|
381
339
|
|
|
382
340
|
const { metadata } = resolved;
|
|
383
341
|
|
|
384
|
-
//
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
};
|
|
395
|
-
}
|
|
396
|
-
|
|
397
|
-
// Domain policy enforcement - credentials with domain restrictions are
|
|
398
|
-
// scoped to browser use on those domains and cannot be used server-side.
|
|
399
|
-
const domains = metadata.allowedDomains ?? [];
|
|
400
|
-
if (domains.length > 0) {
|
|
401
|
-
return {
|
|
402
|
-
success: false,
|
|
403
|
-
reason:
|
|
404
|
-
`Credential ${metadata.service}/${metadata.field} has domain restrictions ` +
|
|
405
|
-
`(${domains.join(", ")}) and cannot be used server-side. ` +
|
|
406
|
-
"Remove domain restrictions or use a separate credential without domain policy.",
|
|
407
|
-
};
|
|
342
|
+
// Shared server-use policy: the ID lookup above is the only by-ID-specific
|
|
343
|
+
// gate, so tool and domain enforcement come from the same helper serverUse
|
|
344
|
+
// uses.
|
|
345
|
+
const denialReason = serverUseDenialReason(
|
|
346
|
+
metadata,
|
|
347
|
+
request.requestingTool,
|
|
348
|
+
metadata.service,
|
|
349
|
+
metadata.field,
|
|
350
|
+
);
|
|
351
|
+
if (denialReason) {
|
|
352
|
+
return { success: false, reason: denialReason };
|
|
408
353
|
}
|
|
409
354
|
|
|
410
355
|
// Fail-closed: verify the secret value actually exists in secure storage.
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Credential reference parsing. Pure string work, no store access.
|
|
3
|
+
*
|
|
4
|
+
* `"service/field"` is the shared vocabulary of every credential entry point:
|
|
5
|
+
* the CLI, the reveal and set routes, and the plugin-facing read and write
|
|
6
|
+
* APIs all name credentials this way. The rules for what counts as a valid
|
|
7
|
+
* reference live here so each path applies the same ones.
|
|
8
|
+
*
|
|
9
|
+
* Deliberately separate from {@link ./resolve}, which reads the metadata store:
|
|
10
|
+
* a caller that only needs to split a reference should not import a module
|
|
11
|
+
* whose behavior depends on what is stored.
|
|
12
|
+
*/
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* Parse a `"service/field"` credential reference into its parts.
|
|
16
|
+
*
|
|
17
|
+
* Returns undefined for anything that is not exactly one non-empty segment on
|
|
18
|
+
* each side of a single slash: no slash, a leading or trailing slash, or more
|
|
19
|
+
* than one slash as in `"fal/api/key"`.
|
|
20
|
+
*/
|
|
21
|
+
export function parseServiceFieldRef(
|
|
22
|
+
ref: string,
|
|
23
|
+
): { service: string; field: string } | undefined {
|
|
24
|
+
const slashIndex = ref.indexOf("/");
|
|
25
|
+
if (slashIndex <= 0 || slashIndex >= ref.length - 1) {
|
|
26
|
+
return undefined;
|
|
27
|
+
}
|
|
28
|
+
if (ref.indexOf("/", slashIndex + 1) !== -1) {
|
|
29
|
+
return undefined;
|
|
30
|
+
}
|
|
31
|
+
return {
|
|
32
|
+
service: ref.slice(0, slashIndex),
|
|
33
|
+
field: ref.slice(slashIndex + 1),
|
|
34
|
+
};
|
|
35
|
+
}
|
|
@@ -16,6 +16,7 @@ import {
|
|
|
16
16
|
listCredentialMetadata,
|
|
17
17
|
} from "./metadata-store.js";
|
|
18
18
|
import type { CredentialInjectionTemplate } from "./policy-types.js";
|
|
19
|
+
import { parseServiceFieldRef } from "./ref-parse.js";
|
|
19
20
|
|
|
20
21
|
export interface ResolvedCredential {
|
|
21
22
|
credentialId: string;
|
|
@@ -95,18 +96,11 @@ export function resolveCredentialRef(
|
|
|
95
96
|
}
|
|
96
97
|
|
|
97
98
|
// Try as service/field
|
|
98
|
-
const
|
|
99
|
-
if (
|
|
99
|
+
const parsed = parseServiceFieldRef(ref);
|
|
100
|
+
if (!parsed) {
|
|
100
101
|
return undefined;
|
|
101
102
|
}
|
|
102
|
-
|
|
103
|
-
if (ref.indexOf("/", slashIndex + 1) !== -1) {
|
|
104
|
-
return undefined;
|
|
105
|
-
}
|
|
106
|
-
|
|
107
|
-
const service = ref.slice(0, slashIndex);
|
|
108
|
-
const field = ref.slice(slashIndex + 1);
|
|
109
|
-
return resolveByServiceField(service, field);
|
|
103
|
+
return resolveByServiceField(parsed.service, parsed.field);
|
|
110
104
|
}
|
|
111
105
|
|
|
112
106
|
/**
|