@vellumai/assistant 0.11.4-staging.4 → 0.11.5-staging.1
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__/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__/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/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/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/feature-flag-registry.json +35 -19
- 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
package/AGENTS.md
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
For error handling conventions (throw vs result objects vs null), see [docs/error-handling.md](docs/error-handling.md).
|
|
4
4
|
|
|
5
|
-
Subdirectory-scoped rules live in local AGENTS.md files: `src/cli/`, `src/runtime/`, `src/approvals/`, `src/notifications/`, `src/plugins/`, `src/workspace/migrations/`.
|
|
5
|
+
Subdirectory-scoped rules live in local AGENTS.md files: `src/cli/`, `src/runtime/`, `src/approvals/`, `src/notifications/`, `src/permissions/`, `src/plugins/`, `src/workspace/migrations/`.
|
|
6
6
|
|
|
7
7
|
## Adding new environment variables
|
|
8
8
|
|
|
@@ -18,7 +18,7 @@ When you introduce a new env var that the assistant process needs to read at run
|
|
|
18
18
|
|
|
19
19
|
The daemon must **never** block startup due to **subsystem** failures (DB, Qdrant, plugins, feature flags, etc.). If an individual subsystem fails, log the error and continue in degraded mode so the process remains reachable for health checks and diagnostics.
|
|
20
20
|
|
|
21
|
-
**Exception
|
|
21
|
+
**Exception, occupied client-facing transports:** A **transport** is not a subsystem. If either client-facing transport's address is taken (the IPC socket in `ipc/assistant-server.ts`, the runtime HTTP port in `runtime/http-server.ts`), the daemon exits with an `EADDRINUSE`-coded error so `emitDaemonError` reports `PORT_IN_USE`. Half a daemon is worse than none: missing IPC makes it unmanageable while its background jobs still write the shared database, and missing HTTP makes it read healthy over IPC while the gateway proxies `/v1/*` to a foreign listener. Bind failures that are not address collisions (permission denied, fd exhaustion) stay non-fatal.
|
|
22
22
|
|
|
23
23
|
## DB migration readiness gating
|
|
24
24
|
|
package/ARCHITECTURE.md
CHANGED
|
@@ -35,6 +35,16 @@ Safe storage limits protect the workspace volume from running out of disk. The d
|
|
|
35
35
|
|
|
36
36
|
**Prompt and tools:** Cleanup-mode turns carry `diskPressureContext` through runtime assembly and receive the concise `<disk_pressure_warning>` injector in `src/plugins/defaults/memory-retrieval/injectors.ts`. The instruction tells the assistant to warn first, call `skill_load` for `system-storage-cleanup`, and explain that background processes and trusted-contact messages are blocked. Tool setup marks the turn as cleanup mode; `skill_load` remains available so the assistant can load the cleanup skill (or another already-installed skill) for its instructions, but under the lock it performs **no side effects** — it skips catalog auto-install (workspace writes / `bun install`) and strips inline command tokens instead of executing them, so loading a skill cannot write to the workspace or run shell. `skill_execute` and skill-origin tools remain unavailable, and a loaded skill's own tools stay filtered by the cleanup allowlist. The bundled `system-storage-cleanup` skill (`src/config/bundled-skills/system-storage-cleanup/SKILL.md`) carries the detailed cleanup procedure and deletion safety rules, including read-only SQLite diagnosis only; product-owned retention and maintenance work remains tracked separately by ATL-450 and related tickets. `src/tools/tool-approval-handler.ts` rejects non-cleanup-safe tools, and foreground shell inspection remains available while background `bash` and `host_bash` modes are rejected. When a new lock is created, active background terminal tools are cancelled with reason `disk_pressure`.
|
|
37
37
|
|
|
38
|
+
### Resource Pressure Monitoring
|
|
39
|
+
|
|
40
|
+
Resource pressure monitoring reports sustained CPU/memory pressure on platform-hosted assistants so clients can suggest a plan upgrade. Unlike disk pressure it never locks or blocks work; it is observe-and-report only.
|
|
41
|
+
|
|
42
|
+
**Guard state:** `src/daemon/resource-pressure-guard.ts` is gated on `getIsPlatform()`: off-platform there is no plan allocation to measure against, so the guard stays disabled and samples nothing. On platform it samples every 30 seconds. CPU percent comes from the shared rolling container CPU sampler (`src/util/container-cpu-sampler.ts`) measured against the cgroup CPU allocation (null when no CPU cores are reported); memory percent is the working set (cgroup usage minus reclaimable file cache) measured against the container memory limit. Each signal keeps a 20-sample window (10 minutes) with hysteresis: it enters `elevated` only when the window is full and at least 18 samples exceeded the enter threshold (CPU 85%, memory 90%), and clears only after 10 consecutive samples below the clear threshold (CPU 70%, memory 80%). An unavailable sample resets that signal's window; when both samples fail the status becomes `unknown` with the sample error. The overall state is `elevated` while either signal holds.
|
|
43
|
+
|
|
44
|
+
**Runtime API and events:** `src/runtime/routes/resource-pressure-routes.ts` exposes the read-only `GET /v1/resource-pressure/status` (scope `settings.read`, actor principals); there are no acknowledge or override transitions. `resource_pressure_status_changed` events broadcast the same status shape, but only on substantive transitions: the raw percents and `lastCheckedAt` are excluded from the change fingerprint so the 30-second cadence does not spam the SSE hub. The canonical wire contract lives in `src/api/events/resource-pressure-status-changed.ts`.
|
|
45
|
+
|
|
46
|
+
**Lifecycle:** `src/daemon/resource-pressure-guard-lifecycle.ts` starts the guard at daemon boot with the first sample deferred onto a macrotask so it never blocks startup, and stops the guard (cancelling any pending deferred sample) on shutdown. The web chat banner built on this status is documented in the repo-level [`/ARCHITECTURE.md`](../ARCHITECTURE.md) "Resource Pressure Monitoring" section.
|
|
47
|
+
|
|
38
48
|
### Single-Header JWT Auth Model
|
|
39
49
|
|
|
40
50
|
All HTTP API requests use a single `Authorization: Bearer <jwt>` header for authentication. The JWT carries identity, permissions, and policy versioning in a unified token.
|
|
@@ -888,17 +898,29 @@ startup pass in `daemon/lifecycle.ts` (after plugin init, before the
|
|
|
888
898
|
scheduler starts), the end of `reconcilePluginSourcesNow()` in
|
|
889
899
|
`plugins/mtime-cache.ts` (install/uninstall/upgrade and sentinel-driven
|
|
890
900
|
changes), the plugin enable/disable routes, and a 60s backstop sweep
|
|
891
|
-
registered with the HTTP server's background sweeps.
|
|
901
|
+
registered with the HTTP server's background sweeps. The desired set is
|
|
902
|
+
gated on activation as well as on what is on disk:
|
|
903
|
+
`collectDesiredDeclarations` skips any plugin directory that
|
|
904
|
+
`isPluginDirActivated` (`plugins/mtime-cache.ts`) does not report as brought
|
|
905
|
+
up in this process, so a directory that merely exists under the plugins root
|
|
906
|
+
never arms a row.
|
|
892
907
|
|
|
893
908
|
Reconcile lag never lets a disabled plugin run. The disable path writes a
|
|
894
909
|
`.disabled` sentinel that only a reconcile pass turns into disarmed rows, so
|
|
895
910
|
the scheduler re-reads the sentinel at fire time and records a skipped run
|
|
896
911
|
instead of executing a claimed row whose plugin is off. Run-now applies the
|
|
897
|
-
same boundary through `
|
|
912
|
+
same boundary through `pluginScheduleSourceAvailable`
|
|
913
|
+
(`schedule/plugin-schedule-availability.ts`), which composes the activation
|
|
914
|
+
ledger with `declarationExistsOnDisk`. That disk probe also covers a plugin
|
|
898
915
|
whose manifest no longer parses, a declaration directory that is gone, and a
|
|
899
916
|
plugin root or declaration directory resolving outside the tree it belongs to
|
|
900
917
|
(the same `isInsidePluginRoot` containment the loader applies before importing
|
|
901
|
-
a plugin).
|
|
918
|
+
a plugin). Fire time in the scheduler and the user re-enable path in
|
|
919
|
+
`schedule-store.ts` deliberately use the disk probe on its own, because both
|
|
920
|
+
can run outside the daemon process, where the activation ledger is empty and
|
|
921
|
+
every plugin would read as unactivated. The reconciler's sweep disarms the
|
|
922
|
+
rows of a plugin it has not activated within one pass, which bounds what
|
|
923
|
+
those disk-only probes can let through.
|
|
902
924
|
|
|
903
925
|
A declaration that stops parsing keeps its execute row armed on the message
|
|
904
926
|
already stored in the row, but disarms its script rows: a script row fires its
|
|
@@ -911,7 +933,10 @@ timezone, message/script, retry policy, `definition_hash`); the execution
|
|
|
911
933
|
engine owns runtime columns (`next_run_at`, `status`, `last_*`,
|
|
912
934
|
`retry_count`) and its latches are never overridden; the user owns
|
|
913
935
|
`user_enabled`, a sticky override consulted when computing effective
|
|
914
|
-
`enabled`.
|
|
936
|
+
`enabled`. `definition_hash` is a sha256 over the relPath and bytes of
|
|
937
|
+
exactly two files, the declaration's `config.json` and its entrypoint, so
|
|
938
|
+
nothing else under `schedules/<name>/` can produce a definition change.
|
|
939
|
+
Nothing ever writes to plugin files. Execution itself is
|
|
915
940
|
unchanged: declared rows fire through the same `claimDueSchedules` path as
|
|
916
941
|
imperative ones.
|
|
917
942
|
|
package/README.md
CHANGED
|
@@ -76,7 +76,7 @@ bun run src/index.ts # interactive CLI session
|
|
|
76
76
|
| `vellum sleep` | Stop assistant + gateway processes |
|
|
77
77
|
| `vellum ps` | List assistants and per-assistant process status |
|
|
78
78
|
| `assistant` | Launch interactive CLI session |
|
|
79
|
-
| `assistant conversations list\|new\|export\|clear` | Manage conversations |
|
|
79
|
+
| `assistant conversations list\|search\|new\|export\|clear` | Manage conversations |
|
|
80
80
|
| `assistant config set\|get\|list` | Manage configuration |
|
|
81
81
|
| `assistant keys set\|list\|delete` | Manage API keys in secure storage |
|
|
82
82
|
| `assistant trust list\|add\|update\|remove` | Manage trust rules |
|
|
@@ -99,6 +99,17 @@ graph LR
|
|
|
99
99
|
failure-backoff-respecting);
|
|
100
100
|
- manual "Run now" via `POST /v1/consolidation/run-now`.
|
|
101
101
|
Failed runs enter an exponential backoff (transient vs billing curves).
|
|
102
|
+
- The agent writes pages through the file tools, so nothing validates a
|
|
103
|
+
page at write time. Two corpus-level defects are instead reported by the
|
|
104
|
+
page index and fed back into the next pass's prompt as repair steps:
|
|
105
|
+
pages the index could not parse (`PageIndex.parseFailures`) and
|
|
106
|
+
structural references (`links:`, inline `[[wikilinks]]`, `edges:`) whose
|
|
107
|
+
target page does not exist (`PageIndex.danglingLinks`). The read-side
|
|
108
|
+
graph drops a dangling reference silently, so the job also counts them
|
|
109
|
+
after each run (`danglingLinks` on the outcome, a warn line) without
|
|
110
|
+
withholding the reindex follow-ups: the pages that were written still
|
|
111
|
+
become retrievable. The `memory validate` CLI subcommand reports the same
|
|
112
|
+
list.
|
|
102
113
|
- **Ingestion** (`substrate/ingest.ts`, exposed as `POST /v1/memory/ingest`;
|
|
103
114
|
generated HTTP operation id `memory_ingest_post`, IPC method
|
|
104
115
|
`memory_ingest`) is the second sanctioned writer of
|
|
@@ -110,11 +121,12 @@ graph LR
|
|
|
110
121
|
minute, and its same-minute burst guard falls back to processing the
|
|
111
122
|
whole buffer in a single oversized run. Ingest writes validated pages
|
|
112
123
|
directly instead. Purely mechanical: each page is validated and reported
|
|
113
|
-
individually
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
124
|
+
individually (a `links:`/`[[wikilink]]`/`edges:` target that is neither on
|
|
125
|
+
disk nor in the batch is a per-page warning, not a rejection), writes hold
|
|
126
|
+
the consolidation lock so a batch cannot interleave with a consolidation
|
|
127
|
+
pass, and a batch that wrote at least one page enqueues the same reindex
|
|
128
|
+
follow-ups as consolidation (`memory_v2_reembed`, `memory_v3_maintain`).
|
|
129
|
+
Consolidation remains the only LLM-driven writer.
|
|
118
130
|
|
|
119
131
|
### Ingestion tracks and provenance
|
|
120
132
|
|
|
@@ -4,207 +4,151 @@ Permission, trust, and credential-security architecture details.
|
|
|
4
4
|
|
|
5
5
|
## Permission and Trust Security Model
|
|
6
6
|
|
|
7
|
-
The permission system
|
|
7
|
+
The permission system decides which tool actions the agent may execute without explicit approval. Two processes share the work, and the split is the load-bearing fact of this section:
|
|
8
|
+
|
|
9
|
+
- The **gateway** owns risk classification (`gateway/src/risk/*`, entered through the `classify_risk` IPC method), the trust rules that raise or lower a classified risk (stored in the gateway's SQLite `trust_rules` table and applied inside the classifiers), the auto-approve thresholds and channel-permission cells, and the per-actor `TrustClass` verdict.
|
|
10
|
+
- The **assistant** owns the turn's actor and capabilities (`runtime/capabilities.ts`), the sensitive-tool capability floor (`tools/tool-approval-handler.ts`), the allow / prompt / deny decision over risk × threshold × capabilities (`permissions/checker.ts` `check()` and `DefaultApprovalPolicy`), the prompt UX (`permissions/prompter.ts`), execution, and the `tool_invocations` audit row.
|
|
11
|
+
|
|
12
|
+
The assistant has no local classifier and no fallback: an unreachable gateway fails closed. It classifies each tool invocation exactly once and passes that classification down; nothing in the assistant memoises or re-derives risk. `assistant/src/permissions/AGENTS.md` and `gateway/src/risk/AGENTS.md` state the rules that follow.
|
|
8
13
|
|
|
9
14
|
### Permission Evaluation Flow
|
|
10
15
|
|
|
11
16
|
```mermaid
|
|
12
17
|
graph TB
|
|
13
|
-
TOOL_CALL["Tool invocation<br/>(toolName, input,
|
|
14
|
-
CLASSIFY -->
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
RISK_CHECK -->|"High"| RISK_THRESHOLD{"Risk-based<br/>threshold fallback"}
|
|
27
|
-
|
|
28
|
-
NO_MATCH -->|"tool.origin === 'skill'"| PROMPT_SKILL["decision: prompt<br/>Skill tools always ask"]
|
|
29
|
-
NO_MATCH -->|"workspace-scoped<br/>+ Low risk"| AUTO_WS["decision: allow<br/>Workspace-scoped auto-allow"]
|
|
30
|
-
NO_MATCH -->|"otherwise"| RISK_THRESHOLD
|
|
31
|
-
|
|
32
|
-
RISK_THRESHOLD{"risk ≤ autoApproveUpTo<br/>threshold?"}
|
|
33
|
-
RISK_THRESHOLD -->|"yes"| AUTO_THRESHOLD["decision: allow<br/>within auto-approve threshold"]
|
|
34
|
-
RISK_THRESHOLD -->|"no"| PROMPT_THRESHOLD["decision: prompt<br/>above auto-approve threshold"]
|
|
18
|
+
TOOL_CALL["Tool invocation<br/>(toolName, input, context)"] --> CLASSIFY["classifyRisk() once, before the gates<br/>gateway classify_risk over IPC<br/>→ level, reason, matchType, options"]
|
|
19
|
+
CLASSIFY --> GATES["Pre-execution gates<br/>abort · unparseable args · guardian control-plane policy ·<br/>sensitive-tool floor + approval-matrix cell · disk pressure ·<br/>unknown tool · allowedToolNames · channel policy · Zod parse"]
|
|
20
|
+
GATES -->|"blocked"| GATE_OUT["denied / error<br/>audited with the classified level"]
|
|
21
|
+
GATES -->|"sensitive, non-guardian"| GRANT{"scoped grant<br/>consumed?"}
|
|
22
|
+
GRANT -->|"yes"| EXECUTE["execute<br/>(no permission check;<br/>provenance grant_scoped_consumed)"]
|
|
23
|
+
GRANT -->|"escalate-and-wait"| ESCALATE["guardian tool-grant request<br/>+ inline wait"]
|
|
24
|
+
GRANT -->|"deny actor / no grant"| GATE_OUT
|
|
25
|
+
GATES -->|"passed"| CHECK["checkPermission → check()<br/>threshold + capability context"]
|
|
26
|
+
CHECK --> POLICY["DefaultApprovalPolicy.evaluate"]
|
|
27
|
+
POLICY -->|"allow"| EXECUTE
|
|
28
|
+
POLICY -->|"prompt"| PROMPT_ROUTE{"presence?"}
|
|
29
|
+
PROMPT_ROUTE -->|"non-interactive turn"| AUTO_OR_DENY["guardian within background threshold → allow<br/>otherwise deny (no human to ask)"]
|
|
30
|
+
PROMPT_ROUTE -->|"interactive"| PROMPT["confirmation_request → user allow / deny"]
|
|
35
31
|
```
|
|
36
32
|
|
|
33
|
+
The order inside `check()`: the memory-retrospective skill-authoring grant, then the classification, then the auto-approve threshold for the turn's execution context (per-conversation override, then channel-permission cell, then global), then `DefaultApprovalPolicy.evaluate`. A `prompt` computed from a cached threshold is re-checked against a fresh read before the user is interrupted. `checkPermission` then applies `forcePromptSideEffects` and `requireFreshApproval` (allow → prompt), the non-interactive denial for uncovered inline-command skill loads, and platform-hosted sandboxed-bash auto-approve for guardians.
|
|
34
|
+
|
|
37
35
|
### Auto-Approve Threshold
|
|
38
36
|
|
|
39
|
-
|
|
37
|
+
Thresholds are **gateway-owned**: stored in the gateway's SQLite database, read by the assistant over IPC (`get_global_thresholds`, `get_conversation_threshold`), and set from the Settings UI (Permissions & Privacy) or the per-conversation risk tolerance picker. When the gateway is unreachable the assistant resolves `"none"` (Strict), fail-closed with no local fallback.
|
|
38
|
+
|
|
39
|
+
Gateway defaults per execution context (`gateway/src/ipc/threshold-handlers.ts`): `interactive` (a conversation with a client) `medium`, `autonomous` (background/scheduled) `low`, `headless` `none`. A per-conversation override wins over the global value; for non-guardian actors a channel-permission cell can only lower the effective threshold.
|
|
40
40
|
|
|
41
41
|
| `autoApproveUpTo` | Low-risk tools | Medium-risk tools | High-risk tools |
|
|
42
42
|
| ----------------- | -------------- | ----------------- | --------------- |
|
|
43
43
|
| `"none"` | Prompted | Prompted | Prompted |
|
|
44
|
-
| `"low"`
|
|
44
|
+
| `"low"` | Auto-allowed | Prompted | Prompted |
|
|
45
45
|
| `"medium"` | Auto-allowed | Auto-allowed | Prompted |
|
|
46
46
|
| `"high"` | Auto-allowed | Auto-allowed | Auto-allowed |
|
|
47
47
|
|
|
48
|
-
|
|
48
|
+
### Approval Policy
|
|
49
49
|
|
|
50
|
-
|
|
50
|
+
`DefaultApprovalPolicy.evaluate` (`assistant/src/permissions/approval-policy.ts`) turns a classified risk into allow / prompt, in this order:
|
|
51
51
|
|
|
52
|
-
|
|
52
|
+
1. `bash` with the gateway's `sandboxAutoApprove` verdict, when the threshold is not `"none"`: allow.
|
|
53
|
+
2. Third-party code (skill- or plugin-owned tools that are not first-party bundled, or a builtin running under a manifest override): allow within threshold, otherwise prompt.
|
|
54
|
+
3. Low risk, workspace-scoped invocation, within threshold: allow.
|
|
55
|
+
4. Low risk, bundled-skill tool, within threshold: allow.
|
|
56
|
+
5. Otherwise: allow when risk ≤ threshold, prompt when above.
|
|
53
57
|
|
|
54
|
-
|
|
55
|
-
| ----------------- | ---------------------- | ------------------------------------------------------------------------ |
|
|
56
|
-
| `id` | `string` | Unique identifier (UUID for user rules, `default:*` for system defaults) |
|
|
57
|
-
| `tool` | `string` | Tool name to match (e.g., `bash`, `file_write`, `skill_load`) |
|
|
58
|
-
| `pattern` | `string` | Minimatch glob pattern for the command/target string |
|
|
59
|
-
| `scope` | `string` | Path prefix or `everywhere` — restricts where the rule applies |
|
|
60
|
-
| `decision` | `allow \| deny \| ask` | What to do when the rule matches |
|
|
61
|
-
| `priority` | `number` | Higher priority wins; deny wins ties at equal priority |
|
|
62
|
-
| `executionTarget` | `string?` | `sandbox` or `host` — restricts by execution context |
|
|
58
|
+
The policy never returns deny; denials come from the gates, the capability floor, and the checks in `checkPermission`. There is no allow / deny / ask rule axis: trust rules act on the classified risk, upstream of this policy.
|
|
63
59
|
|
|
64
|
-
|
|
60
|
+
### Trust Rules (v3)
|
|
65
61
|
|
|
66
|
-
|
|
62
|
+
Rules live in the gateway (`gateway/src/db/trust-rule-store.ts`, cached in-process by `gateway/src/risk/trust-rule-cache.ts`, mutated only through the gateway HTTP routes, which refresh the cache). A rule is `{ tool, pattern, risk: low | medium | high, description, origin: default | user_defined, userModified, deleted }`, unique on `(tool, pattern)`. Default rules are seeded at gateway start from the bash command registry (`gateway/src/db/seed-trust-rules.ts`) and can be modified or reset; user rules are created, updated, and deleted through `/v1/trust-rules`.
|
|
67
63
|
|
|
68
|
-
|
|
64
|
+
Matching happens inside the classifiers: the bash classifier looks the command up exact, path-stripped, then by shorter subcommand prefixes, each in literal and `action:` form, user rules winning over defaults; the file, web, skill, and schedule classifiers look up a per-tool override. A matched rule replaces the base risk and the classification carries `matchType: "user_rule"`. The assistant sees only that: it never stores, matches, or lists rules except to proxy the list over IPC for clients.
|
|
69
65
|
|
|
70
|
-
|
|
71
|
-
| ---------------------------------------------------------------- | --------------------------- | -------------------------------------------------------------------------------------------- |
|
|
72
|
-
| `file_read`, `web_search`, `skill_load` | Low | Read-only or informational |
|
|
73
|
-
| `file_write`, `file_edit` | Medium (default) | Filesystem mutations |
|
|
74
|
-
| `file_write`, `file_edit` targeting skill source paths | **High** | `isSkillSourcePath()` detects managed/bundled/workspace/extra skill roots |
|
|
75
|
-
| `host_file_write`, `host_file_edit` targeting skill source paths | **High** | Same path classification, host variant |
|
|
76
|
-
| `bash`, `host_bash` | Varies | Parsed via tree-sitter: low-risk programs = Low, high-risk programs = High, unknown = Medium |
|
|
77
|
-
| `scaffold_managed_skill`, `delete_managed_skill` | High | Skill lifecycle mutations always high-risk |
|
|
78
|
-
| `evaluate_typescript_code` | High | Arbitrary code execution |
|
|
79
|
-
| Skill-origin tools with no matching rule | Prompted regardless of risk | Even Low-risk skill tools default to `ask` |
|
|
66
|
+
### Risk Classification
|
|
80
67
|
|
|
81
|
-
|
|
68
|
+
Classifiers (`gateway/src/risk/`), keyed by tool:
|
|
82
69
|
|
|
83
|
-
|
|
70
|
+
| Tool | Classifier | Notes |
|
|
71
|
+
| -------------------------------------------------------------- | --------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
72
|
+
| `bash`, `host_bash` | `bash-risk-classifier.ts` | Tree-sitter parse (`shell-parser.ts`, memoised on the command text) against `command-registry/`; per-command arg rules; dangerous patterns; `sandboxAutoApprove` for allowlisted sandbox commands with lexically-resolved path args |
|
|
73
|
+
| `file_*`, `host_file_*` | `file-risk-classifier.ts` | Writes to skill source, workspace code, and other code-loaded directories escalate to High; the assistant sends symlink-resolved paths and the protected/skill directories with the request |
|
|
74
|
+
| `web_fetch`, `network_request` | `web-risk-classifier.ts` | URL-based; private-network access escalates |
|
|
75
|
+
| `skill_load`, `scaffold_managed_skill`, `delete_managed_skill` | `skill-risk-classifier.ts` | A `skill_load` whose skill has inline command expansions (executes shell at load time) is High; skill lifecycle mutations are High |
|
|
76
|
+
| `schedule_create`, `schedule_update` | `schedule-risk-classifier.ts` | Scheduled command risk |
|
|
77
|
+
| Everything else | fallback in `risk-classification-handlers.ts` | The tool's `defaultRiskLevel` from the assistant's registry (`matchType: "registry"`), or `medium` with an "Unknown tool" reason. This branch consults no trust rule and emits no allowlist options, so a user rule cannot cover an MCP or other classifier-less tool today |
|
|
84
78
|
|
|
85
|
-
The `
|
|
79
|
+
The response (`ClassifyRiskIpcResponse`, the shared contract in `packages/gateway-client/src/gateway-ipc-contracts.ts`, validated by both sides) carries the level, reason, `matchType`, allowlist / scope / directory-scope options, command candidates and action keys, `sandboxAutoApprove`, and the path args it was based on. The assistant's one adjustment is the bash symlink-escape re-check: it resolves the gateway's lexically-checked path args through the real filesystem and revokes `sandboxAutoApprove` if any escapes the workspace. It runs on every classification.
|
|
86
80
|
|
|
87
|
-
|
|
88
|
-
2. `skill_load:<skill-id>` — matches any-version rules
|
|
89
|
-
3. `skill_load:<raw-selector>` — matches the raw user-provided selector
|
|
81
|
+
### Sensitive-Tool Capability Floor
|
|
90
82
|
|
|
91
|
-
|
|
83
|
+
Independently of risk, `tools/tool-approval-handler.ts` computes how far an invocation reaches (`none`, `sandbox`, `host`; host-target tools, out-of-workspace file access, and inline-command skill loads reach `host`) and reads the actor's `sensitiveToolApproval` capability: guardian `self`, trusted and unverified contacts `escalate-and-wait`, unknown `deny`. A non-`none` reach for a non-guardian either consumes a scoped approval grant, escalates to the guardian and waits inline, or fails closed. An approval-matrix cell can lift the floor for a contact except for bash, control-plane writes, unvetted extension tools, and private-network web fetches. Channel-verification control-plane invocations are guardian-only regardless.
|
|
92
84
|
|
|
93
85
|
### Skill Threat Model
|
|
94
86
|
|
|
95
|
-
Skills that use existing
|
|
96
|
-
|
|
97
|
-
The threat vectors that skills _do_ introduce are:
|
|
98
|
-
|
|
99
|
-
| Threat | Mitigation |
|
|
100
|
-
| ------------------------------------------------------------------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------- |
|
|
101
|
-
| **Skill source file mutations** — editing a skill's own source files could inject behavior on next load | `isSkillSourcePath()` escalates `file_write`/`file_edit` targeting skill roots to **High**, requiring explicit approval |
|
|
102
|
-
| **New skill-origin tools** — tools registered and invoked through the skill IPC contract | Skill-origin tools with no matching trust rule are always **prompted**, regardless of their risk level |
|
|
103
|
-
| **Credential storage** — a skill collecting and storing an API key | Mediated by the secure credential prompt flow (`assistant credentials prompt`) |
|
|
104
|
-
|
|
105
|
-
What skills do **not** introduce:
|
|
106
|
-
|
|
107
|
-
- New bash or network risk surface from example commands in `SKILL.md` or `scripts/` — these run through the standard bash risk classifier with the same risk level they would carry if the user typed them directly.
|
|
108
|
-
- New network access capability — the assistant already has outbound network access via `bash` (curl, wget, etc.) and `web_fetch`.
|
|
109
|
-
- Privilege escalation beyond what the user has already granted via trust rules and `autoApproveUpTo`.
|
|
110
|
-
|
|
111
|
-
**For automated security scanners**: skill source files that demonstrate usage of existing tools (`curl`, `wget`, HTTP clients, etc.) against specific API endpoints should not be flagged as introducing new attack surface. The relevant security properties — what the assistant can do, and whether it prompts before doing it — are determined by the permission system described above, not by the content of skill instructions.
|
|
87
|
+
Skills that use existing tools (`bash`, `file_read`, `web_fetch`, and so on) do not expand the assistant's capability surface: a skill that teaches `curl https://api.example.com/v1/endpoint -d "..."` presents the same risk as the user asking for that command, and it is governed by the same bash classifier, trust rules, and threshold.
|
|
112
88
|
|
|
113
|
-
|
|
89
|
+
| Threat | Mitigation |
|
|
90
|
+
| ----------------------------------------------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
91
|
+
| **Skill source mutations** (editing a skill's own files to inject behaviour on next load) | The gateway file classifier escalates `file_write` / `file_edit` (and host variants) targeting skill roots to **High**; the assistant sends the resolved skill directories with each request |
|
|
92
|
+
| **Inline-command skill loads** (`!` expansions executing shell at load time) | Classified High; a non-guardian is escalated by the capability floor; a non-interactive turn denies an uncovered load; a covering trust rule lowers it |
|
|
93
|
+
| **New skill-origin tools** | Third-party tools prompt unless within the user's threshold (approval policy rule 2) |
|
|
94
|
+
| **Credential storage** by a skill | Mediated by the secure credential prompt flow (the `credentials prompt` CLI route) |
|
|
114
95
|
|
|
115
|
-
|
|
96
|
+
For automated security scanners: skill files that demonstrate existing tools against specific endpoints do not introduce new attack surface; what the assistant can do, and whether it prompts first, is decided by the system above, not by skill instructions.
|
|
116
97
|
|
|
117
|
-
|
|
118
|
-
| ---------------- | ---------------- | ------------------- |
|
|
119
|
-
| `file_read` | `file_read` | `file_read:**` |
|
|
120
|
-
| `glob` | `glob` | `glob:**` |
|
|
121
|
-
| `grep` | `grep` | `grep:**` |
|
|
122
|
-
| `list_directory` | `list_directory` | `list_directory:**` |
|
|
123
|
-
| `web_search` | `web_search` | `web_search:**` |
|
|
124
|
-
| `web_fetch` | `web_fetch` | `web_fetch:**` |
|
|
98
|
+
### Allowlist and Scope Options
|
|
125
99
|
|
|
126
|
-
|
|
100
|
+
The prompt's "always allow" ladder comes from the classification: bash offers the exact command and then `action:<program>` / `action:<tokens>` keys (max depth 3; pipelines and other complex operators offer only the exact command); file tools offer the exact path, up to three ancestor directories, then the tool; web tools offer the canonicalized URL, its origin, then the tool; skill tools offer a version-pinned and an any-version option in the `skill_load` or `skill_load_dynamic` namespace. Web URLs are canonicalized through `@vellumai/service-contracts/url-normalization`, so the pattern a rule is saved under has one spelling. Rule lookup in the classifiers is an exact-string match on the invocation as written, so a saved rule matches only an identically spelled call. A tool whose classifier produced no ladder gets none: the assistant builds no options of its own.
|
|
127
101
|
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
In addition to the opt-in starter bundle, the permission system seeds unconditional default allow rules at priority 100 for two categories:
|
|
131
|
-
|
|
132
|
-
| Rule ID | Tool | Pattern | Rationale |
|
|
133
|
-
| ---------------------------------------------- | ------------------------- | --------------------------- | -------------------------------------------------------------------------------------------------------- |
|
|
134
|
-
| `default:allow-skill_load-global` | `skill_load` | `skill_load:*` | Loading any skill is globally allowed — no prompt for activating bundled, managed, or workspace skills |
|
|
135
|
-
| `default:allow-browser_navigate-global` | `browser_navigate` | `browser_navigate:*` | Browser tools migrated from core to the bundled `browser` skill; default allow preserves frictionless UX |
|
|
136
|
-
| `default:allow-browser_snapshot-global` | `browser_snapshot` | `browser_snapshot:*` | (same) |
|
|
137
|
-
| `default:allow-browser_screenshot-global` | `browser_screenshot` | `browser_screenshot:*` | (same) |
|
|
138
|
-
| `default:allow-browser_close-global` | `browser_close` | `browser_close:*` | (same) |
|
|
139
|
-
| `default:allow-browser_click-global` | `browser_click` | `browser_click:*` | (same) |
|
|
140
|
-
| `default:allow-browser_type-global` | `browser_type` | `browser_type:*` | (same) |
|
|
141
|
-
| `default:allow-browser_press_key-global` | `browser_press_key` | `browser_press_key:*` | (same) |
|
|
142
|
-
| `default:allow-browser_wait_for-global` | `browser_wait_for` | `browser_wait_for:*` | (same) |
|
|
143
|
-
| `default:allow-browser_extract-global` | `browser_extract` | `browser_extract:*` | (same) |
|
|
144
|
-
| `default:allow-browser_fill_credential-global` | `browser_fill_credential` | `browser_fill_credential:*` | (same) |
|
|
145
|
-
|
|
146
|
-
These rules are emitted by `getDefaultRuleTemplates()` in `assistant/src/permissions/defaults.ts`. Because they use priority 100 (equal to user rules), they take effect regardless of the `autoApproveUpTo` threshold. The `skill_load` rule means skill activation never prompts; the `browser_*` rules mean the browser skill's tools behave identically to the old core `headless-browser` tool from a permission standpoint.
|
|
147
|
-
|
|
148
|
-
### Shell Command Identity and Allowlist Options
|
|
149
|
-
|
|
150
|
-
For `bash` and `host_bash` tool invocations, the permission system uses parser-derived action keys (via `shell-identity.ts`) instead of raw whitespace-split patterns. This produces more meaningful allowlist options that reflect the actual command structure.
|
|
151
|
-
|
|
152
|
-
**Candidate building** (`buildShellCommandCandidates`): The shell parser (`tools/terminal/parser.ts`) produces segments and operators. `analyzeShellCommand()` extracts segments, operators, opaque-construct flags, and dangerous patterns. `deriveShellActionKeys()` then classifies the command:
|
|
153
|
-
|
|
154
|
-
- **Simple action** (optional setup-prefix segments like `cd`, `export`, `pushd` + exactly one action segment): Produces hierarchical `action:` keys. For example, `cd /repo && gh pr view 5525 --json title` yields candidates: the full original command text (`cd /repo && gh pr view 5525 --json title`), and action keys `action:gh pr view`, `action:gh pr`, `action:gh` (narrowest to broadest, max depth 3).
|
|
155
|
-
- **Complex command** (pipelines with `|`, or multiple non-prefix action segments): Only the full original command text is returned as a candidate — no action keys.
|
|
156
|
-
|
|
157
|
-
**Allowlist option ranking** (`buildShellAllowlistOptions`): For simple actions, the prompt offers options ordered from most specific to broadest: the full original command text (exact match), then action keys from deepest to shallowest. For complex commands, only the full original command text is offered. This prevents over-generalization of pipelines into permissive rules.
|
|
158
|
-
|
|
159
|
-
**Trust rule pattern format**: Action keys use the `action:` prefix in trust rules (e.g., `action:gh pr view`). The trust store matches these via `findHighestPriorityRule()` against the candidate list produced by `buildShellCommandCandidates()`.
|
|
160
|
-
|
|
161
|
-
**Scope ordering**: Scope options for all tools (including shell) are ordered from narrowest to broadest: project > parent directories > everywhere. The macOS chat UI uses a two-step flow for persistent rules: the user first selects the allowlist pattern, then selects the scope. This explicit scope selection replaces any silent auto-selection, ensuring the user always knows where the rule will apply.
|
|
102
|
+
Directory-scope ladders (`directoryScopeOptions`) come from the gateway; the coarser workingDir scope ladder (`generateScopeOptions`) is still built assistant-side. Saving a persistent decision means the client creating a trust rule through the gateway (`POST /v1/trust-rules`); the assistant's `POST /v1/confirm` accepts only `allow` and `deny`.
|
|
162
103
|
|
|
163
104
|
### Prompt UX
|
|
164
105
|
|
|
165
|
-
|
|
106
|
+
`confirmation_request` (SSE) carries `requestId`, `toolName`, redacted `input`, `riskLevel`, `riskReason`, `isContainerized`, `executionTarget`, `allowlistOptions`, `scopeOptions`, `directoryScopeOptions`, an optional preview `diff`, `conversationId`, `persistentDecisionsAllowed`, and `toolUseId`; it is also promoted to a guardian request for channel delivery. A prompt that times out or loses its client resolves to deny.
|
|
166
107
|
|
|
167
|
-
|
|
168
|
-
| ------------------ | --------------------------------------------------- |
|
|
169
|
-
| `toolName` | The tool being invoked |
|
|
170
|
-
| `input` | Redacted tool input (sensitive fields removed) |
|
|
171
|
-
| `riskLevel` | `low`, `medium`, or `high` |
|
|
172
|
-
| `executionTarget` | `sandbox` or `host` — where the action will execute |
|
|
173
|
-
| `allowlistOptions` | Suggested patterns for "always allow" rules |
|
|
174
|
-
| `scopeOptions` | Suggested scopes for rule persistence |
|
|
108
|
+
### Canonical Paths
|
|
175
109
|
|
|
176
|
-
The
|
|
110
|
+
The assistant symlink-resolves file-tool paths and the working directory before sending them (`resolveFileToolPaths` in `permissions/checker.ts`, `normalizeFilePath` in `skills/path-classifier.ts`), and canonicalises the protected and skill directories the same way, so a symlinked component cannot bypass the gateway's prefix checks.
|
|
177
111
|
|
|
178
|
-
###
|
|
112
|
+
### Audit
|
|
179
113
|
|
|
180
|
-
|
|
114
|
+
Every invocation ends in one `tool_invocations` row (`telemetry/tool-audit.ts`): `decision` (`allow` / `denied` / `error` / prompt outcomes), the classified `riskLevel`, redacted input and a capped result preview, duration, and telemetry-only columns gated on analytics consent. Rows written by the gates (denials and errors) carry the same classified level as the rest of the call; a call whose classification did not complete (aborted before start, gateway unreachable) records `unclassified` rather than a level.
|
|
181
115
|
|
|
182
116
|
### Key Source Files
|
|
183
117
|
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
|
187
|
-
|
|
|
188
|
-
| `assistant/src/permissions/
|
|
189
|
-
| `assistant/src/permissions/
|
|
190
|
-
| `assistant/src/permissions/
|
|
191
|
-
| `
|
|
192
|
-
| `assistant/src/
|
|
193
|
-
| `assistant/src/
|
|
194
|
-
| `assistant/src/tools/executor.ts`
|
|
195
|
-
| `assistant/src/
|
|
118
|
+
Assistant:
|
|
119
|
+
|
|
120
|
+
| File | Role |
|
|
121
|
+
| -------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------- |
|
|
122
|
+
| `assistant/src/permissions/checker.ts` | `classifyRisk()` (builds the request, calls the gateway, applies the symlink-escape re-check), `check()`, the workingDir scope ladder |
|
|
123
|
+
| `assistant/src/permissions/approval-policy.ts` | `DefaultApprovalPolicy` |
|
|
124
|
+
| `assistant/src/permissions/gateway-threshold-reader.ts`, `channel-permission-query.ts` | Threshold and channel-permission-cell reads over IPC |
|
|
125
|
+
| `packages/gateway-client/src/gateway-ipc-contracts.ts` | `ClassifyRiskIpcParamsSchema` / `ClassifyRiskIpcResponseSchema`, the `classify_risk` contract both sides import |
|
|
126
|
+
| `assistant/src/permissions/prompter.ts` | `confirmation_request` → `confirmation_response` |
|
|
127
|
+
| `assistant/src/permissions/types.ts` | `PolicyContext`, `RiskLevel`, `UserDecision`, thresholds |
|
|
128
|
+
| `assistant/src/tools/executor.ts` | `ToolExecutor`: one classification per invocation, gates, permission check, execution, audit |
|
|
129
|
+
| `assistant/src/tools/tool-approval-handler.ts` | Pre-execution gates and the sensitive-tool capability floor |
|
|
130
|
+
| `assistant/src/tools/permission-checker.ts` | `checkPermission`: policy adjustments, non-interactive routing, prompting |
|
|
131
|
+
| `assistant/src/runtime/capabilities.ts` | `resolveCapabilities(trustClass)` |
|
|
132
|
+
| `assistant/src/skills/path-classifier.ts`, `skills/version-hash.ts` | Path canonicalisation and skill version hashes sent with skill classifications |
|
|
133
|
+
| `assistant/src/telemetry/tool-audit.ts` | `tool_invocations` audit terminals |
|
|
134
|
+
|
|
135
|
+
Gateway:
|
|
136
|
+
|
|
137
|
+
| File | Role |
|
|
138
|
+
| ------------------------------------------------------------------------------------------------------------------ | --------------------------------------------------------------------------------------- |
|
|
139
|
+
| `gateway/src/ipc/risk-classification-handlers.ts` | `classify_risk` IPC handler, the entry point the assistant calls |
|
|
140
|
+
| `gateway/src/risk/bash-risk-classifier.ts` | Shell command risk, via `shell-parser.ts` / `shell-identity.ts` and `command-registry/` |
|
|
141
|
+
| `gateway/src/risk/file-risk-classifier.ts` | File tool risk, including code-loaded-directory escalation |
|
|
142
|
+
| `gateway/src/risk/web-risk-classifier.ts` | Web tool risk |
|
|
143
|
+
| `gateway/src/risk/skill-risk-classifier.ts` | Skill lifecycle and inline-command load risk |
|
|
144
|
+
| `gateway/src/risk/schedule-risk-classifier.ts` | Scheduled task risk |
|
|
145
|
+
| `gateway/src/risk/trust-rule-cache.ts`, `gateway/src/db/trust-rule-store.ts`, `gateway/src/db/seed-trust-rules.ts` | Trust rules: storage, seeding, in-process cache |
|
|
146
|
+
| `gateway/src/http/routes/trust-rules.ts` | Trust rule CRUD, refreshing the cache on every mutation |
|
|
147
|
+
| `gateway/src/ipc/threshold-handlers.ts` | Global and per-conversation thresholds |
|
|
196
148
|
|
|
197
149
|
### Permission Simulation (Tool Permission Tester)
|
|
198
150
|
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
**Simulation semantics:**
|
|
202
|
-
|
|
203
|
-
- The request specifies `toolName`, `input`, and optional context overrides (`workingDir`, `isInteractive`).
|
|
204
|
-
- The daemon runs `classifyRisk()` and `check()` against the live trust rules, then returns the decision (`allow`, `deny`, or `prompt`), risk level, reason, matched rule ID, and (when decision is `prompt`) the full `promptPayload` with allowlist/scope options.
|
|
205
|
-
- **Simulation-only allow/deny**: A simulated `allow` or `deny` decision does not persist any state. No trust rules are created or modified.
|
|
206
|
-
- **Always-allow persistence**: When the tester UI's "Always Allow" action is used, the client sends a separate `add_trust_rule` message that persists the rule to `trust.json`, identical to the existing confirmation flow.
|
|
207
|
-
- **Non-interactive override**: When `isInteractive` is false, `prompt` decisions are converted to `deny` (no client available to approve).
|
|
151
|
+
`POST tools/simulate-permission` (`tools_simulate_permission_post`, `assistant/src/runtime/routes/settings-routes.ts`) dry-runs an invocation through classification and `check()` without executing or persisting anything. It takes `toolName`, `input`, and optional `workingDir` / `isInteractive`, and returns the decision, risk level, reason, execution target, and, for a `prompt`, the allowlist / scope options; when `isInteractive` is false a `prompt` is reported as `deny`.
|
|
208
152
|
|
|
209
153
|
---
|
|
210
154
|
|
|
@@ -141,7 +141,18 @@ at the wrong level.
|
|
|
141
141
|
|
|
142
142
|
`routes/guardian-approval-interception.ts` + the approval prompt watcher in
|
|
143
143
|
`background-dispatch.ts` predate this pipeline: they deliver a guardian's own
|
|
144
|
-
tool-approval prompt
|
|
145
|
-
|
|
144
|
+
tool-approval prompt mid-turn and resolve `apr:` taps against the in-memory
|
|
145
|
+
confirmation directly. That prompt is addressed to the guardian, not to the
|
|
146
|
+
chat the turn is running in. On Slack that chat can be a shared room, and the
|
|
147
|
+
card carries the tool, a command preview and live buttons.
|
|
148
|
+
`resolveGuardianPromptDelivery` addresses it to the guardian's bound DM
|
|
149
|
+
instead, by chat id rather than user id because that address is written to the
|
|
150
|
+
delivery row and read back to match reactions, scope plain-text replies and
|
|
151
|
+
edit the decided card. It returns the address and its route together, since
|
|
152
|
+
the turn's own callback carries a `threadTs` naming a thread that does not
|
|
153
|
+
exist in the DM. When no private address resolves it returns nothing and the
|
|
154
|
+
prompt is left to the in-app confirmation, because the room is the disclosure
|
|
155
|
+
this exists to prevent. Telegram group chats carry the same exposure and are
|
|
156
|
+
not covered: only Slack has a chat whose privacy can be read off its id. They remain load-bearing for that flow, and
|
|
146
157
|
the reply router runs first for everything the pipeline owns. Converge new
|
|
147
158
|
work on the pipeline; do not extend the legacy interception.
|
package/node_modules/@vellumai/ces-client/node_modules/@vellumai/service-contracts/package.json
CHANGED
|
@@ -14,6 +14,7 @@
|
|
|
14
14
|
"./remote-web-pairing": "./src/remote-web-pairing.ts",
|
|
15
15
|
"./twilio-ingress": "./src/twilio-ingress.ts",
|
|
16
16
|
"./trust-rules": "./src/trust-rules.ts",
|
|
17
|
+
"./url-normalization": "./src/url-normalization.ts",
|
|
17
18
|
"./handles": "./src/handles.ts",
|
|
18
19
|
"./rpc": "./src/rpc.ts",
|
|
19
20
|
"./attachment-naming": "./src/attachment-naming.ts",
|